From a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 Mon Sep 17 00:00:00 2001 From: Jinke Han Date: Tue, 8 Sep 2026 15:37:42 +0800 Subject: x86/kprobes: Fix crash when probing CS CALL instructions When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) Signed-off-by: Jinke Han Signed-off-by: Ingo Molnar Reviewed-by: Masami Hiramatsu (Google) Acked-by: Yafang Shao Acked-by: Borislav Petkov Cc: Peter Zijlstra Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000 --- drivers/iio/Kconfig | 117 + drivers/iio/Makefile | 49 + drivers/iio/TODO | 21 + drivers/iio/accel/Kconfig | 708 +++++ drivers/iio/accel/Makefile | 90 + drivers/iio/accel/adis16201.c | 303 ++ drivers/iio/accel/adis16209.c | 313 ++ drivers/iio/accel/adxl313.h | 114 + drivers/iio/accel/adxl313_core.c | 1328 ++++++++ drivers/iio/accel/adxl313_i2c.c | 98 + drivers/iio/accel/adxl313_spi.c | 124 + drivers/iio/accel/adxl345.h | 126 + drivers/iio/accel/adxl345_core.c | 2042 ++++++++++++ drivers/iio/accel/adxl345_i2c.c | 79 + drivers/iio/accel/adxl345_spi.c | 96 + drivers/iio/accel/adxl355.h | 39 + drivers/iio/accel/adxl355_core.c | 833 +++++ drivers/iio/accel/adxl355_i2c.c | 66 + drivers/iio/accel/adxl355_spi.c | 69 + drivers/iio/accel/adxl367.c | 1497 +++++++++ drivers/iio/accel/adxl367.h | 23 + drivers/iio/accel/adxl367_i2c.c | 88 + drivers/iio/accel/adxl367_spi.c | 165 + drivers/iio/accel/adxl372.c | 1335 ++++++++ drivers/iio/accel/adxl372.h | 33 + drivers/iio/accel/adxl372_i2c.c | 74 + drivers/iio/accel/adxl372_spi.c | 65 + drivers/iio/accel/adxl380.c | 2007 ++++++++++++ drivers/iio/accel/adxl380.h | 38 + drivers/iio/accel/adxl380_i2c.c | 67 + drivers/iio/accel/adxl380_spi.c | 69 + drivers/iio/accel/bma180.c | 1134 +++++++ drivers/iio/accel/bma220.h | 28 + drivers/iio/accel/bma220_core.c | 583 ++++ drivers/iio/accel/bma220_i2c.c | 68 + drivers/iio/accel/bma220_spi.c | 59 + drivers/iio/accel/bma400.h | 192 ++ drivers/iio/accel/bma400_core.c | 1808 +++++++++++ drivers/iio/accel/bma400_i2c.c | 55 + drivers/iio/accel/bma400_spi.c | 114 + drivers/iio/accel/bmc150-accel-core.c | 1868 +++++++++++ drivers/iio/accel/bmc150-accel-i2c.c | 293 ++ drivers/iio/accel/bmc150-accel-spi.c | 83 + drivers/iio/accel/bmc150-accel.h | 97 + drivers/iio/accel/bmi088-accel-core.c | 627 ++++ drivers/iio/accel/bmi088-accel-i2c.c | 69 + drivers/iio/accel/bmi088-accel-spi.c | 97 + drivers/iio/accel/bmi088-accel.h | 25 + drivers/iio/accel/cros_ec_accel_legacy.c | 252 ++ drivers/iio/accel/da280.c | 186 ++ drivers/iio/accel/da311.c | 289 ++ drivers/iio/accel/dmard06.c | 229 ++ drivers/iio/accel/dmard09.c | 151 + drivers/iio/accel/dmard10.c | 250 ++ drivers/iio/accel/fxls8962af-core.c | 1318 ++++++++ drivers/iio/accel/fxls8962af-i2c.c | 59 + drivers/iio/accel/fxls8962af-spi.c | 57 + drivers/iio/accel/fxls8962af.h | 24 + drivers/iio/accel/hid-sensor-accel-3d.c | 447 +++ drivers/iio/accel/kionix-kx022a-i2c.c | 72 + drivers/iio/accel/kionix-kx022a-spi.c | 72 + drivers/iio/accel/kionix-kx022a.c | 1456 +++++++++ drivers/iio/accel/kionix-kx022a.h | 206 ++ drivers/iio/accel/kxcjk-1013.c | 1684 ++++++++++ drivers/iio/accel/kxsd9-i2c.c | 64 + drivers/iio/accel/kxsd9-spi.c | 65 + drivers/iio/accel/kxsd9.c | 511 +++ drivers/iio/accel/kxsd9.h | 13 + drivers/iio/accel/mc3230.c | 261 ++ drivers/iio/accel/mma7455.h | 16 + drivers/iio/accel/mma7455_core.c | 315 ++ drivers/iio/accel/mma7455_i2c.c | 62 + drivers/iio/accel/mma7455_spi.c | 50 + drivers/iio/accel/mma7660.c | 295 ++ drivers/iio/accel/mma8452.c | 1855 +++++++++++ drivers/iio/accel/mma9551.c | 605 ++++ drivers/iio/accel/mma9551_core.c | 803 +++++ drivers/iio/accel/mma9551_core.h | 73 + drivers/iio/accel/mma9553.c | 1240 ++++++++ drivers/iio/accel/msa311.c | 1295 ++++++++ drivers/iio/accel/mxc4005.c | 600 ++++ drivers/iio/accel/mxc6255.c | 192 ++ drivers/iio/accel/sca3000.c | 1526 +++++++++ drivers/iio/accel/sca3300.c | 685 ++++ drivers/iio/accel/ssp_accel_sensor.c | 144 + drivers/iio/accel/st_accel.h | 58 + drivers/iio/accel/st_accel_buffer.c | 66 + drivers/iio/accel/st_accel_core.c | 1622 ++++++++++ drivers/iio/accel/st_accel_i2c.c | 218 ++ drivers/iio/accel/st_accel_spi.c | 186 ++ drivers/iio/accel/stk8312.c | 658 ++++ drivers/iio/accel/stk8ba50.c | 552 ++++ drivers/iio/adc/88pm886-gpadc.c | 392 +++ drivers/iio/adc/Kconfig | 2101 ++++++++++++ drivers/iio/adc/Makefile | 181 ++ drivers/iio/adc/ab8500-gpadc.c | 1202 +++++++ drivers/iio/adc/ad4000.c | 1263 ++++++++ drivers/iio/adc/ad4030.c | 1802 +++++++++++ drivers/iio/adc/ad4062.c | 1614 ++++++++++ drivers/iio/adc/ad4080.c | 918 ++++++ drivers/iio/adc/ad4130.c | 2450 ++++++++++++++ drivers/iio/adc/ad4134.c | 499 +++ drivers/iio/adc/ad4170-4.c | 3010 ++++++++++++++++++ drivers/iio/adc/ad4691.c | 2083 ++++++++++++ drivers/iio/adc/ad4695.c | 2019 ++++++++++++ drivers/iio/adc/ad4851.c | 1316 ++++++++ drivers/iio/adc/ad7091r-base.c | 397 +++ drivers/iio/adc/ad7091r-base.h | 99 + drivers/iio/adc/ad7091r5.c | 132 + drivers/iio/adc/ad7091r8.c | 272 ++ drivers/iio/adc/ad7124.c | 1705 ++++++++++ drivers/iio/adc/ad7173.c | 2099 ++++++++++++ drivers/iio/adc/ad7191.c | 556 ++++ drivers/iio/adc/ad7192.c | 1473 +++++++++ drivers/iio/adc/ad7266.c | 473 +++ drivers/iio/adc/ad7280a.c | 1101 +++++++ drivers/iio/adc/ad7291.c | 563 ++++ drivers/iio/adc/ad7292.c | 325 ++ drivers/iio/adc/ad7298.c | 373 +++ drivers/iio/adc/ad7380.c | 2154 +++++++++++++ drivers/iio/adc/ad7405.c | 252 ++ drivers/iio/adc/ad7476.c | 465 +++ drivers/iio/adc/ad7606.c | 1712 ++++++++++ drivers/iio/adc/ad7606.h | 251 ++ drivers/iio/adc/ad7606_bus_iface.h | 16 + drivers/iio/adc/ad7606_par.c | 264 ++ drivers/iio/adc/ad7606_spi.c | 512 +++ drivers/iio/adc/ad7625.c | 686 ++++ drivers/iio/adc/ad7766.c | 309 ++ drivers/iio/adc/ad7768-1.c | 1951 ++++++++++++ drivers/iio/adc/ad7779.c | 1045 ++++++ drivers/iio/adc/ad7780.c | 370 +++ drivers/iio/adc/ad7791.c | 482 +++ drivers/iio/adc/ad7793.c | 857 +++++ drivers/iio/adc/ad7887.c | 347 ++ drivers/iio/adc/ad7923.c | 399 +++ drivers/iio/adc/ad7944.c | 889 ++++++ drivers/iio/adc/ad7949.c | 432 +++ drivers/iio/adc/ad799x.c | 968 ++++++ drivers/iio/adc/ad9467.c | 1433 +++++++++ drivers/iio/adc/ad_sigma_delta.c | 950 ++++++ drivers/iio/adc/ade9000.c | 1794 +++++++++++ drivers/iio/adc/adi-axi-adc.c | 842 +++++ drivers/iio/adc/aspeed_adc.c | 789 +++++ drivers/iio/adc/at91-sama5d2_adc.c | 2629 +++++++++++++++ drivers/iio/adc/at91_adc.c | 1348 ++++++++ drivers/iio/adc/axp20x_adc.c | 1192 +++++++ drivers/iio/adc/axp288_adc.c | 319 ++ drivers/iio/adc/bcm_iproc_adc.c | 622 ++++ drivers/iio/adc/berlin2-adc.c | 368 +++ drivers/iio/adc/cc10001_adc.c | 409 +++ drivers/iio/adc/cpcap-adc.c | 1035 ++++++ drivers/iio/adc/da9150-gpadc.c | 358 +++ drivers/iio/adc/dln2-adc.c | 678 ++++ drivers/iio/adc/envelope-detector.c | 408 +++ drivers/iio/adc/ep93xx_adc.c | 248 ++ drivers/iio/adc/exynos_adc.c | 721 +++++ drivers/iio/adc/fsl-imx25-gcq.c | 389 +++ drivers/iio/adc/gehc-pmc-adc.c | 228 ++ drivers/iio/adc/hi8435.c | 546 ++++ drivers/iio/adc/hx711.c | 629 ++++ drivers/iio/adc/imx7d_adc.c | 560 ++++ drivers/iio/adc/imx8qxp-adc.c | 496 +++ drivers/iio/adc/imx93_adc.c | 494 +++ drivers/iio/adc/ina2xx-adc.c | 1133 +++++++ drivers/iio/adc/industrialio-adc.c | 82 + drivers/iio/adc/ingenic-adc.c | 925 ++++++ drivers/iio/adc/intel_dc_ti_adc.c | 327 ++ drivers/iio/adc/intel_mrfld_adc.c | 241 ++ drivers/iio/adc/lp8788_adc.c | 219 ++ drivers/iio/adc/lpc18xx_adc.c | 205 ++ drivers/iio/adc/lpc32xx_adc.c | 235 ++ drivers/iio/adc/ltc2309.c | 264 ++ drivers/iio/adc/ltc2378.c | 857 +++++ drivers/iio/adc/ltc2471.c | 157 + drivers/iio/adc/ltc2485.c | 143 + drivers/iio/adc/ltc2496.c | 112 + drivers/iio/adc/ltc2497-core.c | 244 ++ drivers/iio/adc/ltc2497.c | 170 + drivers/iio/adc/ltc2497.h | 26 + drivers/iio/adc/max1027.c | 635 ++++ drivers/iio/adc/max11100.c | 162 + drivers/iio/adc/max1118.c | 286 ++ drivers/iio/adc/max11205.c | 180 ++ drivers/iio/adc/max11410.c | 1046 ++++++ drivers/iio/adc/max1241.c | 198 ++ drivers/iio/adc/max1363.c | 1691 ++++++++++ drivers/iio/adc/max14001.c | 390 +++ drivers/iio/adc/max34408.c | 276 ++ drivers/iio/adc/max77541-adc.c | 193 ++ drivers/iio/adc/max9611.c | 562 ++++ drivers/iio/adc/mcp320x.c | 508 +++ drivers/iio/adc/mcp3422.c | 415 +++ drivers/iio/adc/mcp3564.c | 1483 +++++++++ drivers/iio/adc/mcp3911.c | 954 ++++++ drivers/iio/adc/men_z188_adc.c | 174 + drivers/iio/adc/meson_saradc.c | 1530 +++++++++ drivers/iio/adc/mp2629_adc.c | 203 ++ drivers/iio/adc/mt6323-auxadc.c | 313 ++ drivers/iio/adc/mt6359-auxadc.c | 908 ++++++ drivers/iio/adc/mt6360-adc.c | 371 +++ drivers/iio/adc/mt6370-adc.c | 353 ++ drivers/iio/adc/mt6577_auxadc.c | 335 ++ drivers/iio/adc/mxs-lradc-adc.c | 829 +++++ drivers/iio/adc/nau7802.c | 558 ++++ drivers/iio/adc/nct7201.c | 500 +++ drivers/iio/adc/npcm_adc.c | 341 ++ drivers/iio/adc/nxp-sar-adc.c | 1030 ++++++ drivers/iio/adc/pac1921.c | 1345 ++++++++ drivers/iio/adc/pac1934.c | 1632 ++++++++++ drivers/iio/adc/palmas_gpadc.c | 1182 +++++++ drivers/iio/adc/qcom-pm8xxx-xoadc.c | 1027 ++++++ drivers/iio/adc/qcom-spmi-adc5-gen3.c | 828 +++++ drivers/iio/adc/qcom-spmi-adc5.c | 939 ++++++ drivers/iio/adc/qcom-spmi-iadc.c | 598 ++++ drivers/iio/adc/qcom-spmi-rradc.c | 1013 ++++++ drivers/iio/adc/qcom-spmi-vadc.c | 950 ++++++ drivers/iio/adc/qcom-vadc-common.c | 772 +++++ drivers/iio/adc/rcar-gyroadc.c | 594 ++++ drivers/iio/adc/rn5t618-adc.c | 259 ++ drivers/iio/adc/rockchip_saradc.c | 626 ++++ drivers/iio/adc/rohm-bd79112.c | 550 ++++ drivers/iio/adc/rohm-bd79124.c | 1124 +++++++ drivers/iio/adc/rtq6056.c | 900 ++++++ drivers/iio/adc/rzg2l_adc.c | 605 ++++ drivers/iio/adc/rzn1-adc.c | 489 +++ drivers/iio/adc/rzt2h_adc.c | 303 ++ drivers/iio/adc/sc27xx_adc.c | 954 ++++++ drivers/iio/adc/sd_adc_modulator.c | 162 + drivers/iio/adc/sophgo-cv1800b-adc.c | 226 ++ drivers/iio/adc/spear_adc.c | 365 +++ drivers/iio/adc/stm32-adc-core.c | 917 ++++++ drivers/iio/adc/stm32-adc-core.h | 279 ++ drivers/iio/adc/stm32-adc.c | 2791 ++++++++++++++++ drivers/iio/adc/stm32-dfsdm-adc.c | 1907 +++++++++++ drivers/iio/adc/stm32-dfsdm-core.c | 522 +++ drivers/iio/adc/stm32-dfsdm.h | 348 ++ drivers/iio/adc/stmpe-adc.c | 365 +++ drivers/iio/adc/sun20i-gpadc-iio.c | 279 ++ drivers/iio/adc/sun4i-gpadc-iio.c | 705 ++++ drivers/iio/adc/ti-adc081c.c | 234 ++ drivers/iio/adc/ti-adc0832.c | 336 ++ drivers/iio/adc/ti-adc084s021.c | 262 ++ drivers/iio/adc/ti-adc108s102.c | 297 ++ drivers/iio/adc/ti-adc12138.c | 539 ++++ drivers/iio/adc/ti-adc128s052.c | 285 ++ drivers/iio/adc/ti-adc161s626.c | 250 ++ drivers/iio/adc/ti-ads1015.c | 1153 +++++++ drivers/iio/adc/ti-ads1018.c | 738 +++++ drivers/iio/adc/ti-ads1100.c | 432 +++ drivers/iio/adc/ti-ads1119.c | 829 +++++ drivers/iio/adc/ti-ads112c14.c | 1454 +++++++++ drivers/iio/adc/ti-ads124s08.c | 376 +++ drivers/iio/adc/ti-ads1298.c | 769 +++++ drivers/iio/adc/ti-ads131e08.c | 939 ++++++ drivers/iio/adc/ti-ads131m02.c | 967 ++++++ drivers/iio/adc/ti-ads7138.c | 773 +++++ drivers/iio/adc/ti-ads7924.c | 469 +++ drivers/iio/adc/ti-ads7950.c | 667 ++++ drivers/iio/adc/ti-ads8344.c | 193 ++ drivers/iio/adc/ti-ads8688.c | 481 +++ drivers/iio/adc/ti-lmp92064.c | 383 +++ drivers/iio/adc/ti-tlc4541.c | 262 ++ drivers/iio/adc/ti-tsc2046.c | 838 +++++ drivers/iio/adc/ti_am335x_adc.c | 753 +++++ drivers/iio/adc/twl4030-madc.c | 926 ++++++ drivers/iio/adc/twl6030-gpadc.c | 1020 ++++++ drivers/iio/adc/versal-sysmon-core.c | 1052 ++++++ drivers/iio/adc/versal-sysmon-i2c.c | 134 + drivers/iio/adc/versal-sysmon.c | 92 + drivers/iio/adc/versal-sysmon.h | 120 + drivers/iio/adc/vf610_adc.c | 961 ++++++ drivers/iio/adc/viperboard_adc.c | 150 + drivers/iio/adc/xilinx-ams.c | 1401 ++++++++ drivers/iio/adc/xilinx-xadc-core.c | 1459 +++++++++ drivers/iio/adc/xilinx-xadc-events.c | 244 ++ drivers/iio/adc/xilinx-xadc.h | 214 ++ drivers/iio/addac/Kconfig | 55 + drivers/iio/addac/Makefile | 9 + drivers/iio/addac/ad74115.c | 1933 +++++++++++ drivers/iio/addac/ad74413r.c | 1531 +++++++++ drivers/iio/addac/stx104.c | 523 +++ drivers/iio/afe/Kconfig | 19 + drivers/iio/afe/Makefile | 7 + drivers/iio/afe/iio-rescale.c | 611 ++++ drivers/iio/amplifiers/Kconfig | 69 + drivers/iio/amplifiers/Makefile | 10 + drivers/iio/amplifiers/ad8366.c | 402 +++ drivers/iio/amplifiers/ada4250.c | 384 +++ drivers/iio/amplifiers/adl8113.c | 268 ++ drivers/iio/amplifiers/hmc425a.c | 424 +++ drivers/iio/buffer/Kconfig | 55 + drivers/iio/buffer/Makefile | 12 + drivers/iio/buffer/industrialio-buffer-cb.c | 157 + drivers/iio/buffer/industrialio-buffer-dma.c | 875 +++++ drivers/iio/buffer/industrialio-buffer-dmaengine.c | 459 +++ drivers/iio/buffer/industrialio-hw-consumer.c | 219 ++ drivers/iio/buffer/industrialio-triggered-buffer.c | 139 + drivers/iio/buffer/kfifo_buf.c | 272 ++ drivers/iio/cdc/Kconfig | 27 + drivers/iio/cdc/Makefile | 7 + drivers/iio/cdc/ad7150.c | 656 ++++ drivers/iio/cdc/ad7746.c | 819 +++++ drivers/iio/chemical/Kconfig | 260 ++ drivers/iio/chemical/Makefile | 31 + drivers/iio/chemical/ags02ma.c | 165 + drivers/iio/chemical/ams-iaq-core.c | 188 ++ drivers/iio/chemical/atlas-ezo-sensor.c | 243 ++ drivers/iio/chemical/atlas-sensor.c | 763 +++++ drivers/iio/chemical/bme680.h | 89 + drivers/iio/chemical/bme680_core.c | 1266 ++++++++ drivers/iio/chemical/bme680_i2c.c | 63 + drivers/iio/chemical/bme680_spi.c | 157 + drivers/iio/chemical/ccs811.c | 577 ++++ drivers/iio/chemical/ens160.h | 10 + drivers/iio/chemical/ens160_core.c | 373 +++ drivers/iio/chemical/ens160_i2c.c | 62 + drivers/iio/chemical/ens160_spi.c | 61 + drivers/iio/chemical/mhz19b.c | 332 ++ drivers/iio/chemical/pms7003.c | 351 ++ drivers/iio/chemical/scd30.h | 75 + drivers/iio/chemical/scd30_core.c | 779 +++++ drivers/iio/chemical/scd30_i2c.c | 142 + drivers/iio/chemical/scd30_serial.c | 263 ++ drivers/iio/chemical/scd4x.c | 768 +++++ drivers/iio/chemical/sen0322.c | 161 + drivers/iio/chemical/sgp30.c | 588 ++++ drivers/iio/chemical/sgp40.c | 383 +++ drivers/iio/chemical/sps30.c | 375 +++ drivers/iio/chemical/sps30.h | 35 + drivers/iio/chemical/sps30_i2c.c | 258 ++ drivers/iio/chemical/sps30_serial.c | 431 +++ drivers/iio/chemical/sunrise_co2.c | 534 ++++ drivers/iio/chemical/vz89x.c | 405 +++ drivers/iio/common/Kconfig | 12 + drivers/iio/common/Makefile | 17 + drivers/iio/common/cros_ec_sensors/Kconfig | 41 + drivers/iio/common/cros_ec_sensors/Makefile | 10 + .../iio/common/cros_ec_sensors/cros_ec_activity.c | 301 ++ .../iio/common/cros_ec_sensors/cros_ec_lid_angle.c | 137 + .../iio/common/cros_ec_sensors/cros_ec_sensors.c | 322 ++ .../common/cros_ec_sensors/cros_ec_sensors_core.c | 886 ++++++ .../common/cros_ec_sensors/cros_ec_sensors_trace.c | 32 + .../common/cros_ec_sensors/cros_ec_sensors_trace.h | 56 + drivers/iio/common/hid-sensors/Kconfig | 30 + drivers/iio/common/hid-sensors/Makefile | 8 + .../iio/common/hid-sensors/hid-sensor-attributes.c | 593 ++++ .../iio/common/hid-sensors/hid-sensor-trigger.c | 341 ++ .../iio/common/hid-sensors/hid-sensor-trigger.h | 23 + drivers/iio/common/inv_sensors/Kconfig | 7 + drivers/iio/common/inv_sensors/Makefile | 6 + .../iio/common/inv_sensors/inv_sensors_timestamp.c | 212 ++ drivers/iio/common/ms_sensors/Kconfig | 7 + drivers/iio/common/ms_sensors/Makefile | 6 + drivers/iio/common/ms_sensors/ms_sensors_i2c.c | 697 ++++ drivers/iio/common/ms_sensors/ms_sensors_i2c.h | 74 + drivers/iio/common/scmi_sensors/Kconfig | 18 + drivers/iio/common/scmi_sensors/Makefile | 5 + drivers/iio/common/scmi_sensors/scmi_iio.c | 710 +++++ drivers/iio/common/ssp_sensors/Kconfig | 27 + drivers/iio/common/ssp_sensors/Makefile | 9 + drivers/iio/common/ssp_sensors/ssp.h | 246 ++ drivers/iio/common/ssp_sensors/ssp_dev.c | 668 ++++ drivers/iio/common/ssp_sensors/ssp_iio.c | 97 + drivers/iio/common/ssp_sensors/ssp_iio_sensor.h | 60 + drivers/iio/common/ssp_sensors/ssp_spi.c | 601 ++++ drivers/iio/common/st_sensors/Kconfig | 15 + drivers/iio/common/st_sensors/Makefile | 11 + drivers/iio/common/st_sensors/st_sensors_buffer.c | 79 + drivers/iio/common/st_sensors/st_sensors_core.c | 666 ++++ drivers/iio/common/st_sensors/st_sensors_core.h | 10 + drivers/iio/common/st_sensors/st_sensors_i2c.c | 68 + drivers/iio/common/st_sensors/st_sensors_spi.c | 120 + drivers/iio/common/st_sensors/st_sensors_trigger.c | 238 ++ drivers/iio/dac/Kconfig | 700 ++++ drivers/iio/dac/Makefile | 68 + drivers/iio/dac/ad3530r.c | 822 +++++ drivers/iio/dac/ad3552r-common.c | 294 ++ drivers/iio/dac/ad3552r-hs.c | 886 ++++++ drivers/iio/dac/ad3552r-hs.h | 27 + drivers/iio/dac/ad3552r.c | 701 ++++ drivers/iio/dac/ad3552r.h | 232 ++ drivers/iio/dac/ad5064.c | 1107 +++++++ drivers/iio/dac/ad5360.c | 550 ++++ drivers/iio/dac/ad5380.c | 595 ++++ drivers/iio/dac/ad5421.c | 537 ++++ drivers/iio/dac/ad5446-i2c.c | 101 + drivers/iio/dac/ad5446-spi.c | 251 ++ drivers/iio/dac/ad5446.c | 214 ++ drivers/iio/dac/ad5446.h | 77 + drivers/iio/dac/ad5449.c | 356 +++ drivers/iio/dac/ad5504.c | 339 ++ drivers/iio/dac/ad5592r-base.c | 666 ++++ drivers/iio/dac/ad5592r-base.h | 78 + drivers/iio/dac/ad5592r.c | 170 + drivers/iio/dac/ad5593r.c | 149 + drivers/iio/dac/ad5624r.h | 75 + drivers/iio/dac/ad5624r_spi.c | 284 ++ drivers/iio/dac/ad5686-spi.c | 247 ++ drivers/iio/dac/ad5686.c | 808 +++++ drivers/iio/dac/ad5686.h | 200 ++ drivers/iio/dac/ad5696-i2c.c | 141 + drivers/iio/dac/ad5706r.c | 252 ++ drivers/iio/dac/ad5755.c | 883 +++++ drivers/iio/dac/ad5758.c | 905 ++++++ drivers/iio/dac/ad5761.c | 364 +++ drivers/iio/dac/ad5764.c | 365 +++ drivers/iio/dac/ad5766.c | 672 ++++ drivers/iio/dac/ad5770r.c | 663 ++++ drivers/iio/dac/ad5791.c | 611 ++++ drivers/iio/dac/ad7293.c | 895 ++++++ drivers/iio/dac/ad7303.c | 290 ++ drivers/iio/dac/ad8460.c | 957 ++++++ drivers/iio/dac/ad8801.c | 171 + drivers/iio/dac/ad9739a.c | 468 +++ drivers/iio/dac/adi-axi-dac.c | 1041 ++++++ drivers/iio/dac/cio-dac.c | 161 + drivers/iio/dac/dpot-dac.c | 256 ++ drivers/iio/dac/ds4424.c | 439 +++ drivers/iio/dac/lpc18xx_dac.c | 197 ++ drivers/iio/dac/ltc1660.c | 247 ++ drivers/iio/dac/ltc2632.c | 428 +++ drivers/iio/dac/ltc2664.c | 737 +++++ drivers/iio/dac/ltc2688.c | 1011 ++++++ drivers/iio/dac/m62332.c | 256 ++ drivers/iio/dac/max22007.c | 490 +++ drivers/iio/dac/max517.c | 211 ++ drivers/iio/dac/max5522.c | 168 + drivers/iio/dac/max5821.c | 362 +++ drivers/iio/dac/mcf54415_dac.c | 180 ++ drivers/iio/dac/mcp4725.c | 558 ++++ drivers/iio/dac/mcp4728.c | 598 ++++ drivers/iio/dac/mcp47a1.c | 166 + drivers/iio/dac/mcp47feb02.c | 1249 ++++++++ drivers/iio/dac/mcp4821.c | 322 ++ drivers/iio/dac/mcp4922.c | 179 ++ drivers/iio/dac/rohm-bd79703.c | 246 ++ drivers/iio/dac/stm32-dac-core.c | 259 ++ drivers/iio/dac/stm32-dac-core.h | 38 + drivers/iio/dac/stm32-dac.c | 403 +++ drivers/iio/dac/ti-dac082s085.c | 360 +++ drivers/iio/dac/ti-dac5571.c | 432 +++ drivers/iio/dac/ti-dac7311.c | 334 ++ drivers/iio/dac/ti-dac7612.c | 193 ++ drivers/iio/dac/vf610_dac.c | 272 ++ drivers/iio/dummy/Kconfig | 46 + drivers/iio/dummy/Makefile | 11 + drivers/iio/dummy/iio_dummy_evgen.c | 220 ++ drivers/iio/dummy/iio_dummy_evgen.h | 14 + drivers/iio/dummy/iio_simple_dummy.c | 736 +++++ drivers/iio/dummy/iio_simple_dummy.h | 126 + drivers/iio/dummy/iio_simple_dummy_buffer.c | 117 + drivers/iio/dummy/iio_simple_dummy_events.c | 274 ++ drivers/iio/filter/Kconfig | 19 + drivers/iio/filter/Makefile | 7 + drivers/iio/filter/admv8818.c | 815 +++++ drivers/iio/flow/Kconfig | 22 + drivers/iio/flow/Makefile | 7 + drivers/iio/flow/slf3s.c | 542 ++++ drivers/iio/frequency/Kconfig | 127 + drivers/iio/frequency/Makefile | 16 + drivers/iio/frequency/ad9523.c | 1027 ++++++ drivers/iio/frequency/adf41513.c | 1246 ++++++++ drivers/iio/frequency/adf4350.c | 712 +++++ drivers/iio/frequency/adf4371.c | 652 ++++ drivers/iio/frequency/adf4377.c | 1116 +++++++ drivers/iio/frequency/admfm2000.c | 269 ++ drivers/iio/frequency/admv1013.c | 645 ++++ drivers/iio/frequency/admv1014.c | 807 +++++ drivers/iio/frequency/admv4420.c | 395 +++ drivers/iio/frequency/adrf6780.c | 512 +++ drivers/iio/gyro/Kconfig | 182 ++ drivers/iio/gyro/Makefile | 38 + drivers/iio/gyro/adis16080.c | 232 ++ drivers/iio/gyro/adis16130.c | 175 + drivers/iio/gyro/adis16136.c | 586 ++++ drivers/iio/gyro/adis16260.c | 436 +++ drivers/iio/gyro/adxrs290.c | 692 ++++ drivers/iio/gyro/adxrs450.c | 459 +++ drivers/iio/gyro/bmg160.h | 11 + drivers/iio/gyro/bmg160_core.c | 1279 ++++++++ drivers/iio/gyro/bmg160_i2c.c | 81 + drivers/iio/gyro/bmg160_spi.c | 66 + drivers/iio/gyro/fxas21002c.h | 84 + drivers/iio/gyro/fxas21002c_core.c | 1062 +++++++ drivers/iio/gyro/fxas21002c_i2c.c | 67 + drivers/iio/gyro/fxas21002c_spi.c | 68 + drivers/iio/gyro/hid-sensor-gyro-3d.c | 381 +++ drivers/iio/gyro/itg3200_buffer.c | 153 + drivers/iio/gyro/itg3200_core.c | 418 +++ drivers/iio/gyro/mpu3050-core.c | 1301 ++++++++ drivers/iio/gyro/mpu3050-i2c.c | 122 + drivers/iio/gyro/mpu3050.h | 97 + drivers/iio/gyro/ssp_gyro_sensor.c | 144 + drivers/iio/gyro/st_gyro.h | 39 + drivers/iio/gyro/st_gyro_buffer.c | 66 + drivers/iio/gyro/st_gyro_core.c | 519 +++ drivers/iio/gyro/st_gyro_i2c.c | 121 + drivers/iio/gyro/st_gyro_spi.c | 126 + drivers/iio/health/Kconfig | 65 + drivers/iio/health/Makefile | 11 + drivers/iio/health/afe4403.c | 587 ++++ drivers/iio/health/afe4404.c | 594 ++++ drivers/iio/health/afe440x.h | 155 + drivers/iio/health/max30100.c | 532 ++++ drivers/iio/health/max30102.c | 624 ++++ drivers/iio/humidity/Kconfig | 146 + drivers/iio/humidity/Makefile | 24 + drivers/iio/humidity/am2315.c | 270 ++ drivers/iio/humidity/dht11.c | 339 ++ drivers/iio/humidity/ens210.c | 339 ++ drivers/iio/humidity/hdc100x.c | 421 +++ drivers/iio/humidity/hdc2010.c | 346 ++ drivers/iio/humidity/hdc3020.c | 904 ++++++ drivers/iio/humidity/hid-sensor-humidity.c | 297 ++ drivers/iio/humidity/hts221.h | 55 + drivers/iio/humidity/hts221_buffer.c | 201 ++ drivers/iio/humidity/hts221_core.c | 682 ++++ drivers/iio/humidity/hts221_i2c.c | 75 + drivers/iio/humidity/hts221_spi.c | 69 + drivers/iio/humidity/htu21.c | 260 ++ drivers/iio/humidity/si7005.c | 190 ++ drivers/iio/humidity/si7020.c | 293 ++ drivers/iio/iio_core.h | 112 + drivers/iio/iio_core_trigger.h | 59 + drivers/iio/imu/Kconfig | 145 + drivers/iio/imu/Makefile | 38 + drivers/iio/imu/adis.c | 550 ++++ drivers/iio/imu/adis16400.c | 1223 +++++++ drivers/iio/imu/adis16460.c | 421 +++ drivers/iio/imu/adis16475.c | 2108 ++++++++++++ drivers/iio/imu/adis16480.c | 1871 +++++++++++ drivers/iio/imu/adis16550.c | 1109 +++++++ drivers/iio/imu/adis_buffer.c | 222 ++ drivers/iio/imu/adis_trigger.c | 106 + drivers/iio/imu/bmi160/Kconfig | 33 + drivers/iio/imu/bmi160/Makefile | 7 + drivers/iio/imu/bmi160/bmi160.h | 33 + drivers/iio/imu/bmi160/bmi160_core.c | 910 ++++++ drivers/iio/imu/bmi160/bmi160_i2c.c | 84 + drivers/iio/imu/bmi160/bmi160_spi.c | 72 + drivers/iio/imu/bmi270/Kconfig | 33 + drivers/iio/imu/bmi270/Makefile | 7 + drivers/iio/imu/bmi270/bmi270.h | 25 + drivers/iio/imu/bmi270/bmi270_core.c | 1664 ++++++++++ drivers/iio/imu/bmi270/bmi270_i2c.c | 72 + drivers/iio/imu/bmi270/bmi270_spi.c | 94 + drivers/iio/imu/bmi323/Kconfig | 33 + drivers/iio/imu/bmi323/Makefile | 7 + drivers/iio/imu/bmi323/bmi323.h | 209 ++ drivers/iio/imu/bmi323/bmi323_core.c | 2303 ++++++++++++++ drivers/iio/imu/bmi323/bmi323_i2c.c | 142 + drivers/iio/imu/bmi323/bmi323_spi.c | 92 + drivers/iio/imu/bno055/Kconfig | 28 + drivers/iio/imu/bno055/Makefile | 10 + drivers/iio/imu/bno055/bno055.c | 1714 ++++++++++ drivers/iio/imu/bno055/bno055.h | 13 + drivers/iio/imu/bno055/bno055_i2c.c | 56 + drivers/iio/imu/bno055/bno055_ser_core.c | 559 ++++ drivers/iio/imu/bno055/bno055_ser_trace.c | 14 + drivers/iio/imu/bno055/bno055_ser_trace.h | 104 + drivers/iio/imu/fxos8700.h | 10 + drivers/iio/imu/fxos8700_core.c | 714 +++++ drivers/iio/imu/fxos8700_i2c.c | 68 + drivers/iio/imu/fxos8700_spi.c | 56 + drivers/iio/imu/inv_icm42600/Kconfig | 30 + drivers/iio/imu/inv_icm42600/Makefile | 14 + drivers/iio/imu/inv_icm42600/inv_icm42600.h | 491 +++ drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c | 1247 ++++++++ drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c | 636 ++++ drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h | 102 + drivers/iio/imu/inv_icm42600/inv_icm42600_core.c | 961 ++++++ drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c | 828 +++++ drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c | 129 + drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c | 126 + drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c | 86 + drivers/iio/imu/inv_icm42600/inv_icm42600_temp.h | 30 + drivers/iio/imu/inv_icm42607/Kconfig | 30 + drivers/iio/imu/inv_icm42607/Makefile | 13 + drivers/iio/imu/inv_icm42607/inv_icm42607.h | 421 +++ drivers/iio/imu/inv_icm42607/inv_icm42607_accel.c | 316 ++ drivers/iio/imu/inv_icm42607/inv_icm42607_core.c | 702 ++++ drivers/iio/imu/inv_icm42607/inv_icm42607_gyro.c | 313 ++ drivers/iio/imu/inv_icm42607/inv_icm42607_i2c.c | 97 + drivers/iio/imu/inv_icm42607/inv_icm42607_spi.c | 107 + drivers/iio/imu/inv_icm42607/inv_icm42607_temp.c | 99 + drivers/iio/imu/inv_icm42607/inv_icm42607_temp.h | 38 + drivers/iio/imu/inv_icm45600/Kconfig | 70 + drivers/iio/imu/inv_icm45600/Makefile | 16 + drivers/iio/imu/inv_icm45600/inv_icm45600.h | 385 +++ drivers/iio/imu/inv_icm45600/inv_icm45600_accel.c | 782 +++++ drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c | 561 ++++ drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.h | 101 + drivers/iio/imu/inv_icm45600/inv_icm45600_core.c | 994 ++++++ drivers/iio/imu/inv_icm45600/inv_icm45600_gyro.c | 791 +++++ drivers/iio/imu/inv_icm45600/inv_icm45600_i2c.c | 97 + drivers/iio/imu/inv_icm45600/inv_icm45600_i3c.c | 78 + drivers/iio/imu/inv_icm45600/inv_icm45600_spi.c | 107 + drivers/iio/imu/inv_mpu6050/Kconfig | 35 + drivers/iio/imu/inv_mpu6050/Makefile | 14 + drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c | 195 ++ drivers/iio/imu/inv_mpu6050/inv_mpu_aux.c | 201 ++ drivers/iio/imu/inv_mpu6050/inv_mpu_aux.h | 19 + drivers/iio/imu/inv_mpu6050/inv_mpu_core.c | 2270 +++++++++++++ drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c | 299 ++ drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h | 516 +++ drivers/iio/imu/inv_mpu6050/inv_mpu_magn.c | 365 +++ drivers/iio/imu/inv_mpu6050/inv_mpu_magn.h | 39 + drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c | 135 + drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c | 185 ++ drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c | 332 ++ drivers/iio/imu/kmx61.c | 1508 +++++++++ drivers/iio/imu/smi240.c | 620 ++++ drivers/iio/imu/smi330/Kconfig | 33 + drivers/iio/imu/smi330/Makefile | 7 + drivers/iio/imu/smi330/smi330.h | 25 + drivers/iio/imu/smi330/smi330_core.c | 913 ++++++ drivers/iio/imu/smi330/smi330_i2c.c | 132 + drivers/iio/imu/smi330/smi330_spi.c | 84 + drivers/iio/imu/st_lsm6dsx/Kconfig | 57 + drivers/iio/imu/st_lsm6dsx/Makefile | 8 + drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h | 599 ++++ drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c | 904 ++++++ drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 3093 ++++++++++++++++++ drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_fusion.c | 246 ++ drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i2c.c | 197 ++ drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i3c.c | 56 + drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c | 922 ++++++ drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_spi.c | 184 ++ drivers/iio/imu/st_lsm9ds0/Kconfig | 45 + drivers/iio/imu/st_lsm9ds0/Makefile | 5 + drivers/iio/imu/st_lsm9ds0/st_lsm9ds0.h | 25 + drivers/iio/imu/st_lsm9ds0/st_lsm9ds0_core.c | 96 + drivers/iio/imu/st_lsm9ds0/st_lsm9ds0_i2c.c | 93 + drivers/iio/imu/st_lsm9ds0/st_lsm9ds0_spi.c | 85 + drivers/iio/industrialio-acpi.c | 125 + drivers/iio/industrialio-backend.c | 1166 +++++++ drivers/iio/industrialio-buffer.c | 2559 +++++++++++++++ drivers/iio/industrialio-configfs.c | 48 + drivers/iio/industrialio-core.c | 2257 +++++++++++++ drivers/iio/industrialio-event.c | 672 ++++ drivers/iio/industrialio-gts-helper.c | 1253 ++++++++ drivers/iio/industrialio-sw-device.c | 179 ++ drivers/iio/industrialio-sw-trigger.c | 183 ++ drivers/iio/industrialio-trigger.c | 796 +++++ drivers/iio/industrialio-triggered-event.c | 64 + drivers/iio/inkern.c | 1045 ++++++ drivers/iio/light/Kconfig | 809 +++++ drivers/iio/light/Makefile | 76 + drivers/iio/light/acpi-als.c | 261 ++ drivers/iio/light/adjd_s311.c | 279 ++ drivers/iio/light/adux1020.c | 842 +++++ drivers/iio/light/al3000a.c | 209 ++ drivers/iio/light/al3010.c | 247 ++ drivers/iio/light/al3320a.c | 282 ++ drivers/iio/light/apds9160.c | 1589 +++++++++ drivers/iio/light/apds9300.c | 514 +++ drivers/iio/light/apds9306.c | 1354 ++++++++ drivers/iio/light/apds9960.c | 1187 +++++++ drivers/iio/light/apds9999.c | 332 ++ drivers/iio/light/as73211.c | 897 ++++++ drivers/iio/light/bh1745.c | 900 ++++++ drivers/iio/light/bh1750.c | 356 +++ drivers/iio/light/bh1780.c | 284 ++ drivers/iio/light/cm32181.c | 550 ++++ drivers/iio/light/cm3232.c | 424 +++ drivers/iio/light/cm3323.c | 271 ++ drivers/iio/light/cm3605.c | 325 ++ drivers/iio/light/cm36651.c | 742 +++++ drivers/iio/light/cros_ec_light_prox.c | 260 ++ drivers/iio/light/gp2ap002.c | 732 +++++ drivers/iio/light/gp2ap020a00f.c | 1551 +++++++++ drivers/iio/light/hid-sensor-als.c | 469 +++ drivers/iio/light/hid-sensor-prox.c | 373 +++ drivers/iio/light/iqs621-als.c | 593 ++++ drivers/iio/light/isl29018.c | 859 +++++ drivers/iio/light/isl29028.c | 695 ++++ drivers/iio/light/isl29125.c | 346 ++ drivers/iio/light/isl76682.c | 345 ++ drivers/iio/light/jsa1212.c | 449 +++ drivers/iio/light/lm3533-als.c | 922 ++++++ drivers/iio/light/ltr390.c | 915 ++++++ drivers/iio/light/ltr501.c | 1633 ++++++++++ drivers/iio/light/ltrf216a.c | 581 ++++ drivers/iio/light/lv0104cs.c | 529 +++ drivers/iio/light/max44000.c | 624 ++++ drivers/iio/light/max44009.c | 554 ++++ drivers/iio/light/noa1305.c | 337 ++ drivers/iio/light/opt3001.c | 981 ++++++ drivers/iio/light/opt4001.c | 451 +++ drivers/iio/light/opt4060.c | 1328 ++++++++ drivers/iio/light/pa12203001.c | 481 +++ drivers/iio/light/rohm-bu27034.c | 1329 ++++++++ drivers/iio/light/rpr0521.c | 1125 +++++++ drivers/iio/light/si1133.c | 1088 +++++++ drivers/iio/light/si1145.c | 1358 ++++++++ drivers/iio/light/st_uvis25.h | 36 + drivers/iio/light/st_uvis25_core.c | 354 +++ drivers/iio/light/st_uvis25_i2c.c | 67 + drivers/iio/light/st_uvis25_spi.c | 68 + drivers/iio/light/stk3310.c | 811 +++++ drivers/iio/light/tcs3414.c | 382 +++ drivers/iio/light/tcs3472.c | 864 +++++ drivers/iio/light/tsl2563.c | 872 +++++ drivers/iio/light/tsl2583.c | 935 ++++++ drivers/iio/light/tsl2591.c | 1152 +++++++ drivers/iio/light/tsl2772.c | 1956 ++++++++++++ drivers/iio/light/tsl4531.c | 249 ++ drivers/iio/light/us5182d.c | 979 ++++++ drivers/iio/light/vcnl4000.c | 2059 ++++++++++++ drivers/iio/light/vcnl4035.c | 684 ++++ drivers/iio/light/veml3235.c | 547 ++++ drivers/iio/light/veml3328.c | 422 +++ drivers/iio/light/veml6030.c | 1238 +++++++ drivers/iio/light/veml6040.c | 280 ++ drivers/iio/light/veml6046x00.c | 1029 ++++++ drivers/iio/light/veml6070.c | 317 ++ drivers/iio/light/veml6075.c | 482 +++ drivers/iio/light/vl6180.c | 771 +++++ drivers/iio/light/zopt2201.c | 536 ++++ drivers/iio/magnetometer/Kconfig | 334 ++ drivers/iio/magnetometer/Makefile | 45 + drivers/iio/magnetometer/af8133j.c | 527 +++ drivers/iio/magnetometer/ak8974.c | 1042 ++++++ drivers/iio/magnetometer/ak8975.c | 1175 +++++++ drivers/iio/magnetometer/als31300.c | 490 +++ drivers/iio/magnetometer/bmc150_magn.c | 1035 ++++++ drivers/iio/magnetometer/bmc150_magn.h | 12 + drivers/iio/magnetometer/bmc150_magn_i2c.c | 72 + drivers/iio/magnetometer/bmc150_magn_spi.c | 56 + drivers/iio/magnetometer/hid-sensor-magn-3d.c | 568 ++++ drivers/iio/magnetometer/hmc5843.h | 56 + drivers/iio/magnetometer/hmc5843_core.c | 688 ++++ drivers/iio/magnetometer/hmc5843_i2c.c | 106 + drivers/iio/magnetometer/hmc5843_spi.c | 100 + drivers/iio/magnetometer/mag3110.c | 647 ++++ drivers/iio/magnetometer/mmc35240.c | 582 ++++ drivers/iio/magnetometer/mmc5633.c | 585 ++++ drivers/iio/magnetometer/mmc5983.c | 347 ++ drivers/iio/magnetometer/qmc5883l.c | 516 +++ drivers/iio/magnetometer/qmc6308.c | 601 ++++ drivers/iio/magnetometer/rm3100-core.c | 616 ++++ drivers/iio/magnetometer/rm3100-i2c.c | 55 + drivers/iio/magnetometer/rm3100-spi.c | 64 + drivers/iio/magnetometer/rm3100.h | 17 + drivers/iio/magnetometer/si7210.c | 438 +++ drivers/iio/magnetometer/st_magn.h | 39 + drivers/iio/magnetometer/st_magn_buffer.c | 46 + drivers/iio/magnetometer/st_magn_core.c | 665 ++++ drivers/iio/magnetometer/st_magn_i2c.c | 121 + drivers/iio/magnetometer/st_magn_spi.c | 113 + drivers/iio/magnetometer/tlv493d.c | 525 +++ drivers/iio/magnetometer/tmag5273.c | 735 +++++ drivers/iio/magnetometer/yamaha-yas530.c | 1620 ++++++++++ drivers/iio/multiplexer/Kconfig | 18 + drivers/iio/multiplexer/Makefile | 7 + drivers/iio/multiplexer/iio-mux.c | 466 +++ drivers/iio/orientation/Kconfig | 30 + drivers/iio/orientation/Makefile | 8 + drivers/iio/orientation/hid-sensor-incl-3d.c | 414 +++ drivers/iio/orientation/hid-sensor-rotation.c | 434 +++ drivers/iio/position/Kconfig | 35 + drivers/iio/position/Makefile | 8 + .../iio/position/hid-sensor-custom-intel-hinge.c | 379 +++ drivers/iio/position/iqs624-pos.c | 285 ++ drivers/iio/potentiometer/Kconfig | 149 + drivers/iio/potentiometer/Makefile | 18 + drivers/iio/potentiometer/ad5110.c | 349 ++ drivers/iio/potentiometer/ad5272.c | 228 ++ drivers/iio/potentiometer/ds1803.c | 259 ++ drivers/iio/potentiometer/max5432.c | 132 + drivers/iio/potentiometer/max5481.c | 193 ++ drivers/iio/potentiometer/max5487.c | 156 + drivers/iio/potentiometer/mcp4018.c | 187 ++ drivers/iio/potentiometer/mcp41010.c | 201 ++ drivers/iio/potentiometer/mcp4131.c | 490 +++ drivers/iio/potentiometer/mcp4531.c | 398 +++ drivers/iio/potentiometer/tpl0102.c | 172 + drivers/iio/potentiometer/x9250.c | 220 ++ drivers/iio/potentiostat/Kconfig | 23 + drivers/iio/potentiostat/Makefile | 7 + drivers/iio/potentiostat/lmp91000.c | 425 +++ drivers/iio/pressure/Kconfig | 387 +++ drivers/iio/pressure/Makefile | 47 + drivers/iio/pressure/abp060mg.c | 287 ++ drivers/iio/pressure/abp2030pa.c | 543 ++++ drivers/iio/pressure/abp2030pa.h | 73 + drivers/iio/pressure/abp2030pa_i2c.c | 89 + drivers/iio/pressure/abp2030pa_spi.c | 66 + drivers/iio/pressure/adp810.c | 224 ++ drivers/iio/pressure/bmp280-core.c | 3361 ++++++++++++++++++++ drivers/iio/pressure/bmp280-i2c.c | 65 + drivers/iio/pressure/bmp280-regmap.c | 239 ++ drivers/iio/pressure/bmp280-spi.c | 145 + drivers/iio/pressure/bmp280.h | 551 ++++ drivers/iio/pressure/cros_ec_baro.c | 203 ++ drivers/iio/pressure/dlhl60d.c | 359 +++ drivers/iio/pressure/dps310.c | 912 ++++++ drivers/iio/pressure/hid-sensor-press.c | 348 ++ drivers/iio/pressure/hp03.c | 292 ++ drivers/iio/pressure/hp206c.c | 421 +++ drivers/iio/pressure/hsc030pa.c | 540 ++++ drivers/iio/pressure/hsc030pa.h | 81 + drivers/iio/pressure/hsc030pa_i2c.c | 78 + drivers/iio/pressure/hsc030pa_spi.c | 60 + drivers/iio/pressure/icp10100.c | 653 ++++ drivers/iio/pressure/mpl115.c | 255 ++ drivers/iio/pressure/mpl115.h | 26 + drivers/iio/pressure/mpl115_i2c.c | 66 + drivers/iio/pressure/mpl115_spi.c | 105 + drivers/iio/pressure/mpl3115.c | 811 +++++ drivers/iio/pressure/mprls0025pa.c | 443 +++ drivers/iio/pressure/mprls0025pa.h | 90 + drivers/iio/pressure/mprls0025pa_i2c.c | 89 + drivers/iio/pressure/mprls0025pa_spi.c | 80 + drivers/iio/pressure/ms5611.h | 58 + drivers/iio/pressure/ms5611_core.c | 454 +++ drivers/iio/pressure/ms5611_i2c.c | 134 + drivers/iio/pressure/ms5611_spi.c | 135 + drivers/iio/pressure/ms5637.c | 250 ++ drivers/iio/pressure/rohm-bm1390.c | 922 ++++++ drivers/iio/pressure/sdp500.c | 155 + drivers/iio/pressure/st_pressure.h | 58 + drivers/iio/pressure/st_pressure_buffer.c | 45 + drivers/iio/pressure/st_pressure_core.c | 798 +++++ drivers/iio/pressure/st_pressure_i2c.c | 123 + drivers/iio/pressure/st_pressure_spi.c | 125 + drivers/iio/pressure/t5403.c | 270 ++ drivers/iio/pressure/zpa2326.c | 1708 ++++++++++ drivers/iio/pressure/zpa2326.h | 81 + drivers/iio/pressure/zpa2326_i2c.c | 86 + drivers/iio/pressure/zpa2326_spi.c | 89 + drivers/iio/proximity/Kconfig | 273 ++ drivers/iio/proximity/Makefile | 28 + drivers/iio/proximity/as3935.c | 467 +++ drivers/iio/proximity/aw96103.c | 846 +++++ drivers/iio/proximity/cros_ec_mkbp_proximity.c | 263 ++ drivers/iio/proximity/d3323aa.c | 814 +++++ drivers/iio/proximity/hx9023s.c | 1226 +++++++ drivers/iio/proximity/irsd200.c | 955 ++++++ drivers/iio/proximity/isl29501.c | 1021 ++++++ drivers/iio/proximity/mb1232.c | 267 ++ drivers/iio/proximity/ping.c | 329 ++ drivers/iio/proximity/pulsedlight-lidar-lite-v2.c | 372 +++ drivers/iio/proximity/rfd77402.c | 461 +++ drivers/iio/proximity/srf04.c | 399 +++ drivers/iio/proximity/srf08.c | 552 ++++ drivers/iio/proximity/sx9310.c | 1038 ++++++ drivers/iio/proximity/sx9324.c | 1164 +++++++ drivers/iio/proximity/sx9360.c | 874 +++++ drivers/iio/proximity/sx9500.c | 1048 ++++++ drivers/iio/proximity/sx_common.c | 549 ++++ drivers/iio/proximity/sx_common.h | 155 + drivers/iio/proximity/vcnl3020.c | 667 ++++ drivers/iio/proximity/vl53l0x-i2c.c | 416 +++ drivers/iio/proximity/vl53l1x-i2c.c | 756 +++++ drivers/iio/resolver/Kconfig | 44 + drivers/iio/resolver/Makefile | 8 + drivers/iio/resolver/ad2s1200.c | 200 ++ drivers/iio/resolver/ad2s1210.c | 1621 ++++++++++ drivers/iio/resolver/ad2s90.c | 130 + drivers/iio/temperature/Kconfig | 187 ++ drivers/iio/temperature/Makefile | 21 + drivers/iio/temperature/hid-sensor-temperature.c | 294 ++ drivers/iio/temperature/iqs620at-temp.c | 98 + drivers/iio/temperature/ltc2983.c | 2106 ++++++++++++ drivers/iio/temperature/max30208.c | 251 ++ drivers/iio/temperature/max31856.c | 488 +++ drivers/iio/temperature/max31865.c | 350 ++ drivers/iio/temperature/maxim_thermocouple.c | 304 ++ drivers/iio/temperature/mcp9600.c | 579 ++++ drivers/iio/temperature/mlx90614.c | 782 +++++ drivers/iio/temperature/mlx90632.c | 1343 ++++++++ drivers/iio/temperature/mlx90635.c | 1093 +++++++ drivers/iio/temperature/tmp006.c | 411 +++ drivers/iio/temperature/tmp007.c | 581 ++++ drivers/iio/temperature/tmp117.c | 243 ++ drivers/iio/temperature/tsys01.c | 234 ++ drivers/iio/temperature/tsys02d.c | 190 ++ drivers/iio/test/Kconfig | 54 + drivers/iio/test/Makefile | 11 + drivers/iio/test/iio-test-format.c | 324 ++ drivers/iio/test/iio-test-gts.c | 515 +++ drivers/iio/test/iio-test-multiply.c | 212 ++ drivers/iio/test/iio-test-rescale.c | 719 +++++ drivers/iio/trigger/Kconfig | 62 + drivers/iio/trigger/Makefile | 12 + drivers/iio/trigger/iio-trig-hrtimer.c | 200 ++ drivers/iio/trigger/iio-trig-loop.c | 141 + drivers/iio/trigger/iio-trig-sysfs.c | 225 ++ drivers/iio/trigger/stm32-lptimer-trigger.c | 152 + drivers/iio/trigger/stm32-timer-trigger.c | 941 ++++++ 892 files changed, 430038 insertions(+) create mode 100644 drivers/iio/Kconfig create mode 100644 drivers/iio/Makefile create mode 100644 drivers/iio/TODO create mode 100644 drivers/iio/accel/Kconfig create mode 100644 drivers/iio/accel/Makefile create mode 100644 drivers/iio/accel/adis16201.c create mode 100644 drivers/iio/accel/adis16209.c create mode 100644 drivers/iio/accel/adxl313.h create mode 100644 drivers/iio/accel/adxl313_core.c create mode 100644 drivers/iio/accel/adxl313_i2c.c create mode 100644 drivers/iio/accel/adxl313_spi.c create mode 100644 drivers/iio/accel/adxl345.h create mode 100644 drivers/iio/accel/adxl345_core.c create mode 100644 drivers/iio/accel/adxl345_i2c.c create mode 100644 drivers/iio/accel/adxl345_spi.c create mode 100644 drivers/iio/accel/adxl355.h create mode 100644 drivers/iio/accel/adxl355_core.c create mode 100644 drivers/iio/accel/adxl355_i2c.c create mode 100644 drivers/iio/accel/adxl355_spi.c create mode 100644 drivers/iio/accel/adxl367.c create mode 100644 drivers/iio/accel/adxl367.h create mode 100644 drivers/iio/accel/adxl367_i2c.c create mode 100644 drivers/iio/accel/adxl367_spi.c create mode 100644 drivers/iio/accel/adxl372.c create mode 100644 drivers/iio/accel/adxl372.h create mode 100644 drivers/iio/accel/adxl372_i2c.c create mode 100644 drivers/iio/accel/adxl372_spi.c create mode 100644 drivers/iio/accel/adxl380.c create mode 100644 drivers/iio/accel/adxl380.h create mode 100644 drivers/iio/accel/adxl380_i2c.c create mode 100644 drivers/iio/accel/adxl380_spi.c create mode 100644 drivers/iio/accel/bma180.c create mode 100644 drivers/iio/accel/bma220.h create mode 100644 drivers/iio/accel/bma220_core.c create mode 100644 drivers/iio/accel/bma220_i2c.c create mode 100644 drivers/iio/accel/bma220_spi.c create mode 100644 drivers/iio/accel/bma400.h create mode 100644 drivers/iio/accel/bma400_core.c create mode 100644 drivers/iio/accel/bma400_i2c.c create mode 100644 drivers/iio/accel/bma400_spi.c create mode 100644 drivers/iio/accel/bmc150-accel-core.c create mode 100644 drivers/iio/accel/bmc150-accel-i2c.c create mode 100644 drivers/iio/accel/bmc150-accel-spi.c create mode 100644 drivers/iio/accel/bmc150-accel.h create mode 100644 drivers/iio/accel/bmi088-accel-core.c create mode 100644 drivers/iio/accel/bmi088-accel-i2c.c create mode 100644 drivers/iio/accel/bmi088-accel-spi.c create mode 100644 drivers/iio/accel/bmi088-accel.h create mode 100644 drivers/iio/accel/cros_ec_accel_legacy.c create mode 100644 drivers/iio/accel/da280.c create mode 100644 drivers/iio/accel/da311.c create mode 100644 drivers/iio/accel/dmard06.c create mode 100644 drivers/iio/accel/dmard09.c create mode 100644 drivers/iio/accel/dmard10.c create mode 100644 drivers/iio/accel/fxls8962af-core.c create mode 100644 drivers/iio/accel/fxls8962af-i2c.c create mode 100644 drivers/iio/accel/fxls8962af-spi.c create mode 100644 drivers/iio/accel/fxls8962af.h create mode 100644 drivers/iio/accel/hid-sensor-accel-3d.c create mode 100644 drivers/iio/accel/kionix-kx022a-i2c.c create mode 100644 drivers/iio/accel/kionix-kx022a-spi.c create mode 100644 drivers/iio/accel/kionix-kx022a.c create mode 100644 drivers/iio/accel/kionix-kx022a.h create mode 100644 drivers/iio/accel/kxcjk-1013.c create mode 100644 drivers/iio/accel/kxsd9-i2c.c create mode 100644 drivers/iio/accel/kxsd9-spi.c create mode 100644 drivers/iio/accel/kxsd9.c create mode 100644 drivers/iio/accel/kxsd9.h create mode 100644 drivers/iio/accel/mc3230.c create mode 100644 drivers/iio/accel/mma7455.h create mode 100644 drivers/iio/accel/mma7455_core.c create mode 100644 drivers/iio/accel/mma7455_i2c.c create mode 100644 drivers/iio/accel/mma7455_spi.c create mode 100644 drivers/iio/accel/mma7660.c create mode 100644 drivers/iio/accel/mma8452.c create mode 100644 drivers/iio/accel/mma9551.c create mode 100644 drivers/iio/accel/mma9551_core.c create mode 100644 drivers/iio/accel/mma9551_core.h create mode 100644 drivers/iio/accel/mma9553.c create mode 100644 drivers/iio/accel/msa311.c create mode 100644 drivers/iio/accel/mxc4005.c create mode 100644 drivers/iio/accel/mxc6255.c create mode 100644 drivers/iio/accel/sca3000.c create mode 100644 drivers/iio/accel/sca3300.c create mode 100644 drivers/iio/accel/ssp_accel_sensor.c create mode 100644 drivers/iio/accel/st_accel.h create mode 100644 drivers/iio/accel/st_accel_buffer.c create mode 100644 drivers/iio/accel/st_accel_core.c create mode 100644 drivers/iio/accel/st_accel_i2c.c create mode 100644 drivers/iio/accel/st_accel_spi.c create mode 100644 drivers/iio/accel/stk8312.c create mode 100644 drivers/iio/accel/stk8ba50.c create mode 100644 drivers/iio/adc/88pm886-gpadc.c create mode 100644 drivers/iio/adc/Kconfig create mode 100644 drivers/iio/adc/Makefile create mode 100644 drivers/iio/adc/ab8500-gpadc.c create mode 100644 drivers/iio/adc/ad4000.c create mode 100644 drivers/iio/adc/ad4030.c create mode 100644 drivers/iio/adc/ad4062.c create mode 100644 drivers/iio/adc/ad4080.c create mode 100644 drivers/iio/adc/ad4130.c create mode 100644 drivers/iio/adc/ad4134.c create mode 100644 drivers/iio/adc/ad4170-4.c create mode 100644 drivers/iio/adc/ad4691.c create mode 100644 drivers/iio/adc/ad4695.c create mode 100644 drivers/iio/adc/ad4851.c create mode 100644 drivers/iio/adc/ad7091r-base.c create mode 100644 drivers/iio/adc/ad7091r-base.h create mode 100644 drivers/iio/adc/ad7091r5.c create mode 100644 drivers/iio/adc/ad7091r8.c create mode 100644 drivers/iio/adc/ad7124.c create mode 100644 drivers/iio/adc/ad7173.c create mode 100644 drivers/iio/adc/ad7191.c create mode 100644 drivers/iio/adc/ad7192.c create mode 100644 drivers/iio/adc/ad7266.c create mode 100644 drivers/iio/adc/ad7280a.c create mode 100644 drivers/iio/adc/ad7291.c create mode 100644 drivers/iio/adc/ad7292.c create mode 100644 drivers/iio/adc/ad7298.c create mode 100644 drivers/iio/adc/ad7380.c create mode 100644 drivers/iio/adc/ad7405.c create mode 100644 drivers/iio/adc/ad7476.c create mode 100644 drivers/iio/adc/ad7606.c create mode 100644 drivers/iio/adc/ad7606.h create mode 100644 drivers/iio/adc/ad7606_bus_iface.h create mode 100644 drivers/iio/adc/ad7606_par.c create mode 100644 drivers/iio/adc/ad7606_spi.c create mode 100644 drivers/iio/adc/ad7625.c create mode 100644 drivers/iio/adc/ad7766.c create mode 100644 drivers/iio/adc/ad7768-1.c create mode 100644 drivers/iio/adc/ad7779.c create mode 100644 drivers/iio/adc/ad7780.c create mode 100644 drivers/iio/adc/ad7791.c create mode 100644 drivers/iio/adc/ad7793.c create mode 100644 drivers/iio/adc/ad7887.c create mode 100644 drivers/iio/adc/ad7923.c create mode 100644 drivers/iio/adc/ad7944.c create mode 100644 drivers/iio/adc/ad7949.c create mode 100644 drivers/iio/adc/ad799x.c create mode 100644 drivers/iio/adc/ad9467.c create mode 100644 drivers/iio/adc/ad_sigma_delta.c create mode 100644 drivers/iio/adc/ade9000.c create mode 100644 drivers/iio/adc/adi-axi-adc.c create mode 100644 drivers/iio/adc/aspeed_adc.c create mode 100644 drivers/iio/adc/at91-sama5d2_adc.c create mode 100644 drivers/iio/adc/at91_adc.c create mode 100644 drivers/iio/adc/axp20x_adc.c create mode 100644 drivers/iio/adc/axp288_adc.c create mode 100644 drivers/iio/adc/bcm_iproc_adc.c create mode 100644 drivers/iio/adc/berlin2-adc.c create mode 100644 drivers/iio/adc/cc10001_adc.c create mode 100644 drivers/iio/adc/cpcap-adc.c create mode 100644 drivers/iio/adc/da9150-gpadc.c create mode 100644 drivers/iio/adc/dln2-adc.c create mode 100644 drivers/iio/adc/envelope-detector.c create mode 100644 drivers/iio/adc/ep93xx_adc.c create mode 100644 drivers/iio/adc/exynos_adc.c create mode 100644 drivers/iio/adc/fsl-imx25-gcq.c create mode 100644 drivers/iio/adc/gehc-pmc-adc.c create mode 100644 drivers/iio/adc/hi8435.c create mode 100644 drivers/iio/adc/hx711.c create mode 100644 drivers/iio/adc/imx7d_adc.c create mode 100644 drivers/iio/adc/imx8qxp-adc.c create mode 100644 drivers/iio/adc/imx93_adc.c create mode 100644 drivers/iio/adc/ina2xx-adc.c create mode 100644 drivers/iio/adc/industrialio-adc.c create mode 100644 drivers/iio/adc/ingenic-adc.c create mode 100644 drivers/iio/adc/intel_dc_ti_adc.c create mode 100644 drivers/iio/adc/intel_mrfld_adc.c create mode 100644 drivers/iio/adc/lp8788_adc.c create mode 100644 drivers/iio/adc/lpc18xx_adc.c create mode 100644 drivers/iio/adc/lpc32xx_adc.c create mode 100644 drivers/iio/adc/ltc2309.c create mode 100644 drivers/iio/adc/ltc2378.c create mode 100644 drivers/iio/adc/ltc2471.c create mode 100644 drivers/iio/adc/ltc2485.c create mode 100644 drivers/iio/adc/ltc2496.c create mode 100644 drivers/iio/adc/ltc2497-core.c create mode 100644 drivers/iio/adc/ltc2497.c create mode 100644 drivers/iio/adc/ltc2497.h create mode 100644 drivers/iio/adc/max1027.c create mode 100644 drivers/iio/adc/max11100.c create mode 100644 drivers/iio/adc/max1118.c create mode 100644 drivers/iio/adc/max11205.c create mode 100644 drivers/iio/adc/max11410.c create mode 100644 drivers/iio/adc/max1241.c create mode 100644 drivers/iio/adc/max1363.c create mode 100644 drivers/iio/adc/max14001.c create mode 100644 drivers/iio/adc/max34408.c create mode 100644 drivers/iio/adc/max77541-adc.c create mode 100644 drivers/iio/adc/max9611.c create mode 100644 drivers/iio/adc/mcp320x.c create mode 100644 drivers/iio/adc/mcp3422.c create mode 100644 drivers/iio/adc/mcp3564.c create mode 100644 drivers/iio/adc/mcp3911.c create mode 100644 drivers/iio/adc/men_z188_adc.c create mode 100644 drivers/iio/adc/meson_saradc.c create mode 100644 drivers/iio/adc/mp2629_adc.c create mode 100644 drivers/iio/adc/mt6323-auxadc.c create mode 100644 drivers/iio/adc/mt6359-auxadc.c create mode 100644 drivers/iio/adc/mt6360-adc.c create mode 100644 drivers/iio/adc/mt6370-adc.c create mode 100644 drivers/iio/adc/mt6577_auxadc.c create mode 100644 drivers/iio/adc/mxs-lradc-adc.c create mode 100644 drivers/iio/adc/nau7802.c create mode 100644 drivers/iio/adc/nct7201.c create mode 100644 drivers/iio/adc/npcm_adc.c create mode 100644 drivers/iio/adc/nxp-sar-adc.c create mode 100644 drivers/iio/adc/pac1921.c create mode 100644 drivers/iio/adc/pac1934.c create mode 100644 drivers/iio/adc/palmas_gpadc.c create mode 100644 drivers/iio/adc/qcom-pm8xxx-xoadc.c create mode 100644 drivers/iio/adc/qcom-spmi-adc5-gen3.c create mode 100644 drivers/iio/adc/qcom-spmi-adc5.c create mode 100644 drivers/iio/adc/qcom-spmi-iadc.c create mode 100644 drivers/iio/adc/qcom-spmi-rradc.c create mode 100644 drivers/iio/adc/qcom-spmi-vadc.c create mode 100644 drivers/iio/adc/qcom-vadc-common.c create mode 100644 drivers/iio/adc/rcar-gyroadc.c create mode 100644 drivers/iio/adc/rn5t618-adc.c create mode 100644 drivers/iio/adc/rockchip_saradc.c create mode 100644 drivers/iio/adc/rohm-bd79112.c create mode 100644 drivers/iio/adc/rohm-bd79124.c create mode 100644 drivers/iio/adc/rtq6056.c create mode 100644 drivers/iio/adc/rzg2l_adc.c create mode 100644 drivers/iio/adc/rzn1-adc.c create mode 100644 drivers/iio/adc/rzt2h_adc.c create mode 100644 drivers/iio/adc/sc27xx_adc.c create mode 100644 drivers/iio/adc/sd_adc_modulator.c create mode 100644 drivers/iio/adc/sophgo-cv1800b-adc.c create mode 100644 drivers/iio/adc/spear_adc.c create mode 100644 drivers/iio/adc/stm32-adc-core.c create mode 100644 drivers/iio/adc/stm32-adc-core.h create mode 100644 drivers/iio/adc/stm32-adc.c create mode 100644 drivers/iio/adc/stm32-dfsdm-adc.c create mode 100644 drivers/iio/adc/stm32-dfsdm-core.c create mode 100644 drivers/iio/adc/stm32-dfsdm.h create mode 100644 drivers/iio/adc/stmpe-adc.c create mode 100644 drivers/iio/adc/sun20i-gpadc-iio.c create mode 100644 drivers/iio/adc/sun4i-gpadc-iio.c create mode 100644 drivers/iio/adc/ti-adc081c.c create mode 100644 drivers/iio/adc/ti-adc0832.c create mode 100644 drivers/iio/adc/ti-adc084s021.c create mode 100644 drivers/iio/adc/ti-adc108s102.c create mode 100644 drivers/iio/adc/ti-adc12138.c create mode 100644 drivers/iio/adc/ti-adc128s052.c create mode 100644 drivers/iio/adc/ti-adc161s626.c create mode 100644 drivers/iio/adc/ti-ads1015.c create mode 100644 drivers/iio/adc/ti-ads1018.c create mode 100644 drivers/iio/adc/ti-ads1100.c create mode 100644 drivers/iio/adc/ti-ads1119.c create mode 100644 drivers/iio/adc/ti-ads112c14.c create mode 100644 drivers/iio/adc/ti-ads124s08.c create mode 100644 drivers/iio/adc/ti-ads1298.c create mode 100644 drivers/iio/adc/ti-ads131e08.c create mode 100644 drivers/iio/adc/ti-ads131m02.c create mode 100644 drivers/iio/adc/ti-ads7138.c create mode 100644 drivers/iio/adc/ti-ads7924.c create mode 100644 drivers/iio/adc/ti-ads7950.c create mode 100644 drivers/iio/adc/ti-ads8344.c create mode 100644 drivers/iio/adc/ti-ads8688.c create mode 100644 drivers/iio/adc/ti-lmp92064.c create mode 100644 drivers/iio/adc/ti-tlc4541.c create mode 100644 drivers/iio/adc/ti-tsc2046.c create mode 100644 drivers/iio/adc/ti_am335x_adc.c create mode 100644 drivers/iio/adc/twl4030-madc.c create mode 100644 drivers/iio/adc/twl6030-gpadc.c create mode 100644 drivers/iio/adc/versal-sysmon-core.c create mode 100644 drivers/iio/adc/versal-sysmon-i2c.c create mode 100644 drivers/iio/adc/versal-sysmon.c create mode 100644 drivers/iio/adc/versal-sysmon.h create mode 100644 drivers/iio/adc/vf610_adc.c create mode 100644 drivers/iio/adc/viperboard_adc.c create mode 100644 drivers/iio/adc/xilinx-ams.c create mode 100644 drivers/iio/adc/xilinx-xadc-core.c create mode 100644 drivers/iio/adc/xilinx-xadc-events.c create mode 100644 drivers/iio/adc/xilinx-xadc.h create mode 100644 drivers/iio/addac/Kconfig create mode 100644 drivers/iio/addac/Makefile create mode 100644 drivers/iio/addac/ad74115.c create mode 100644 drivers/iio/addac/ad74413r.c create mode 100644 drivers/iio/addac/stx104.c create mode 100644 drivers/iio/afe/Kconfig create mode 100644 drivers/iio/afe/Makefile create mode 100644 drivers/iio/afe/iio-rescale.c create mode 100644 drivers/iio/amplifiers/Kconfig create mode 100644 drivers/iio/amplifiers/Makefile create mode 100644 drivers/iio/amplifiers/ad8366.c create mode 100644 drivers/iio/amplifiers/ada4250.c create mode 100644 drivers/iio/amplifiers/adl8113.c create mode 100644 drivers/iio/amplifiers/hmc425a.c create mode 100644 drivers/iio/buffer/Kconfig create mode 100644 drivers/iio/buffer/Makefile create mode 100644 drivers/iio/buffer/industrialio-buffer-cb.c create mode 100644 drivers/iio/buffer/industrialio-buffer-dma.c create mode 100644 drivers/iio/buffer/industrialio-buffer-dmaengine.c create mode 100644 drivers/iio/buffer/industrialio-hw-consumer.c create mode 100644 drivers/iio/buffer/industrialio-triggered-buffer.c create mode 100644 drivers/iio/buffer/kfifo_buf.c create mode 100644 drivers/iio/cdc/Kconfig create mode 100644 drivers/iio/cdc/Makefile create mode 100644 drivers/iio/cdc/ad7150.c create mode 100644 drivers/iio/cdc/ad7746.c create mode 100644 drivers/iio/chemical/Kconfig create mode 100644 drivers/iio/chemical/Makefile create mode 100644 drivers/iio/chemical/ags02ma.c create mode 100644 drivers/iio/chemical/ams-iaq-core.c create mode 100644 drivers/iio/chemical/atlas-ezo-sensor.c create mode 100644 drivers/iio/chemical/atlas-sensor.c create mode 100644 drivers/iio/chemical/bme680.h create mode 100644 drivers/iio/chemical/bme680_core.c create mode 100644 drivers/iio/chemical/bme680_i2c.c create mode 100644 drivers/iio/chemical/bme680_spi.c create mode 100644 drivers/iio/chemical/ccs811.c create mode 100644 drivers/iio/chemical/ens160.h create mode 100644 drivers/iio/chemical/ens160_core.c create mode 100644 drivers/iio/chemical/ens160_i2c.c create mode 100644 drivers/iio/chemical/ens160_spi.c create mode 100644 drivers/iio/chemical/mhz19b.c create mode 100644 drivers/iio/chemical/pms7003.c create mode 100644 drivers/iio/chemical/scd30.h create mode 100644 drivers/iio/chemical/scd30_core.c create mode 100644 drivers/iio/chemical/scd30_i2c.c create mode 100644 drivers/iio/chemical/scd30_serial.c create mode 100644 drivers/iio/chemical/scd4x.c create mode 100644 drivers/iio/chemical/sen0322.c create mode 100644 drivers/iio/chemical/sgp30.c create mode 100644 drivers/iio/chemical/sgp40.c create mode 100644 drivers/iio/chemical/sps30.c create mode 100644 drivers/iio/chemical/sps30.h create mode 100644 drivers/iio/chemical/sps30_i2c.c create mode 100644 drivers/iio/chemical/sps30_serial.c create mode 100644 drivers/iio/chemical/sunrise_co2.c create mode 100644 drivers/iio/chemical/vz89x.c create mode 100644 drivers/iio/common/Kconfig create mode 100644 drivers/iio/common/Makefile create mode 100644 drivers/iio/common/cros_ec_sensors/Kconfig create mode 100644 drivers/iio/common/cros_ec_sensors/Makefile create mode 100644 drivers/iio/common/cros_ec_sensors/cros_ec_activity.c create mode 100644 drivers/iio/common/cros_ec_sensors/cros_ec_lid_angle.c create mode 100644 drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c create mode 100644 drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c create mode 100644 drivers/iio/common/cros_ec_sensors/cros_ec_sensors_trace.c create mode 100644 drivers/iio/common/cros_ec_sensors/cros_ec_sensors_trace.h create mode 100644 drivers/iio/common/hid-sensors/Kconfig create mode 100644 drivers/iio/common/hid-sensors/Makefile create mode 100644 drivers/iio/common/hid-sensors/hid-sensor-attributes.c create mode 100644 drivers/iio/common/hid-sensors/hid-sensor-trigger.c create mode 100644 drivers/iio/common/hid-sensors/hid-sensor-trigger.h create mode 100644 drivers/iio/common/inv_sensors/Kconfig create mode 100644 drivers/iio/common/inv_sensors/Makefile create mode 100644 drivers/iio/common/inv_sensors/inv_sensors_timestamp.c create mode 100644 drivers/iio/common/ms_sensors/Kconfig create mode 100644 drivers/iio/common/ms_sensors/Makefile create mode 100644 drivers/iio/common/ms_sensors/ms_sensors_i2c.c create mode 100644 drivers/iio/common/ms_sensors/ms_sensors_i2c.h create mode 100644 drivers/iio/common/scmi_sensors/Kconfig create mode 100644 drivers/iio/common/scmi_sensors/Makefile create mode 100644 drivers/iio/common/scmi_sensors/scmi_iio.c create mode 100644 drivers/iio/common/ssp_sensors/Kconfig create mode 100644 drivers/iio/common/ssp_sensors/Makefile create mode 100644 drivers/iio/common/ssp_sensors/ssp.h create mode 100644 drivers/iio/common/ssp_sensors/ssp_dev.c create mode 100644 drivers/iio/common/ssp_sensors/ssp_iio.c create mode 100644 drivers/iio/common/ssp_sensors/ssp_iio_sensor.h create mode 100644 drivers/iio/common/ssp_sensors/ssp_spi.c create mode 100644 drivers/iio/common/st_sensors/Kconfig create mode 100644 drivers/iio/common/st_sensors/Makefile create mode 100644 drivers/iio/common/st_sensors/st_sensors_buffer.c create mode 100644 drivers/iio/common/st_sensors/st_sensors_core.c create mode 100644 drivers/iio/common/st_sensors/st_sensors_core.h create mode 100644 drivers/iio/common/st_sensors/st_sensors_i2c.c create mode 100644 drivers/iio/common/st_sensors/st_sensors_spi.c create mode 100644 drivers/iio/common/st_sensors/st_sensors_trigger.c create mode 100644 drivers/iio/dac/Kconfig create mode 100644 drivers/iio/dac/Makefile create mode 100644 drivers/iio/dac/ad3530r.c create mode 100644 drivers/iio/dac/ad3552r-common.c create mode 100644 drivers/iio/dac/ad3552r-hs.c create mode 100644 drivers/iio/dac/ad3552r-hs.h create mode 100644 drivers/iio/dac/ad3552r.c create mode 100644 drivers/iio/dac/ad3552r.h create mode 100644 drivers/iio/dac/ad5064.c create mode 100644 drivers/iio/dac/ad5360.c create mode 100644 drivers/iio/dac/ad5380.c create mode 100644 drivers/iio/dac/ad5421.c create mode 100644 drivers/iio/dac/ad5446-i2c.c create mode 100644 drivers/iio/dac/ad5446-spi.c create mode 100644 drivers/iio/dac/ad5446.c create mode 100644 drivers/iio/dac/ad5446.h create mode 100644 drivers/iio/dac/ad5449.c create mode 100644 drivers/iio/dac/ad5504.c create mode 100644 drivers/iio/dac/ad5592r-base.c create mode 100644 drivers/iio/dac/ad5592r-base.h create mode 100644 drivers/iio/dac/ad5592r.c create mode 100644 drivers/iio/dac/ad5593r.c create mode 100644 drivers/iio/dac/ad5624r.h create mode 100644 drivers/iio/dac/ad5624r_spi.c create mode 100644 drivers/iio/dac/ad5686-spi.c create mode 100644 drivers/iio/dac/ad5686.c create mode 100644 drivers/iio/dac/ad5686.h create mode 100644 drivers/iio/dac/ad5696-i2c.c create mode 100644 drivers/iio/dac/ad5706r.c create mode 100644 drivers/iio/dac/ad5755.c create mode 100644 drivers/iio/dac/ad5758.c create mode 100644 drivers/iio/dac/ad5761.c create mode 100644 drivers/iio/dac/ad5764.c create mode 100644 drivers/iio/dac/ad5766.c create mode 100644 drivers/iio/dac/ad5770r.c create mode 100644 drivers/iio/dac/ad5791.c create mode 100644 drivers/iio/dac/ad7293.c create mode 100644 drivers/iio/dac/ad7303.c create mode 100644 drivers/iio/dac/ad8460.c create mode 100644 drivers/iio/dac/ad8801.c create mode 100644 drivers/iio/dac/ad9739a.c create mode 100644 drivers/iio/dac/adi-axi-dac.c create mode 100644 drivers/iio/dac/cio-dac.c create mode 100644 drivers/iio/dac/dpot-dac.c create mode 100644 drivers/iio/dac/ds4424.c create mode 100644 drivers/iio/dac/lpc18xx_dac.c create mode 100644 drivers/iio/dac/ltc1660.c create mode 100644 drivers/iio/dac/ltc2632.c create mode 100644 drivers/iio/dac/ltc2664.c create mode 100644 drivers/iio/dac/ltc2688.c create mode 100644 drivers/iio/dac/m62332.c create mode 100644 drivers/iio/dac/max22007.c create mode 100644 drivers/iio/dac/max517.c create mode 100644 drivers/iio/dac/max5522.c create mode 100644 drivers/iio/dac/max5821.c create mode 100644 drivers/iio/dac/mcf54415_dac.c create mode 100644 drivers/iio/dac/mcp4725.c create mode 100644 drivers/iio/dac/mcp4728.c create mode 100644 drivers/iio/dac/mcp47a1.c create mode 100644 drivers/iio/dac/mcp47feb02.c create mode 100644 drivers/iio/dac/mcp4821.c create mode 100644 drivers/iio/dac/mcp4922.c create mode 100644 drivers/iio/dac/rohm-bd79703.c create mode 100644 drivers/iio/dac/stm32-dac-core.c create mode 100644 drivers/iio/dac/stm32-dac-core.h create mode 100644 drivers/iio/dac/stm32-dac.c create mode 100644 drivers/iio/dac/ti-dac082s085.c create mode 100644 drivers/iio/dac/ti-dac5571.c create mode 100644 drivers/iio/dac/ti-dac7311.c create mode 100644 drivers/iio/dac/ti-dac7612.c create mode 100644 drivers/iio/dac/vf610_dac.c create mode 100644 drivers/iio/dummy/Kconfig create mode 100644 drivers/iio/dummy/Makefile create mode 100644 drivers/iio/dummy/iio_dummy_evgen.c create mode 100644 drivers/iio/dummy/iio_dummy_evgen.h create mode 100644 drivers/iio/dummy/iio_simple_dummy.c create mode 100644 drivers/iio/dummy/iio_simple_dummy.h create mode 100644 drivers/iio/dummy/iio_simple_dummy_buffer.c create mode 100644 drivers/iio/dummy/iio_simple_dummy_events.c create mode 100644 drivers/iio/filter/Kconfig create mode 100644 drivers/iio/filter/Makefile create mode 100644 drivers/iio/filter/admv8818.c create mode 100644 drivers/iio/flow/Kconfig create mode 100644 drivers/iio/flow/Makefile create mode 100644 drivers/iio/flow/slf3s.c create mode 100644 drivers/iio/frequency/Kconfig create mode 100644 drivers/iio/frequency/Makefile create mode 100644 drivers/iio/frequency/ad9523.c create mode 100644 drivers/iio/frequency/adf41513.c create mode 100644 drivers/iio/frequency/adf4350.c create mode 100644 drivers/iio/frequency/adf4371.c create mode 100644 drivers/iio/frequency/adf4377.c create mode 100644 drivers/iio/frequency/admfm2000.c create mode 100644 drivers/iio/frequency/admv1013.c create mode 100644 drivers/iio/frequency/admv1014.c create mode 100644 drivers/iio/frequency/admv4420.c create mode 100644 drivers/iio/frequency/adrf6780.c create mode 100644 drivers/iio/gyro/Kconfig create mode 100644 drivers/iio/gyro/Makefile create mode 100644 drivers/iio/gyro/adis16080.c create mode 100644 drivers/iio/gyro/adis16130.c create mode 100644 drivers/iio/gyro/adis16136.c create mode 100644 drivers/iio/gyro/adis16260.c create mode 100644 drivers/iio/gyro/adxrs290.c create mode 100644 drivers/iio/gyro/adxrs450.c create mode 100644 drivers/iio/gyro/bmg160.h create mode 100644 drivers/iio/gyro/bmg160_core.c create mode 100644 drivers/iio/gyro/bmg160_i2c.c create mode 100644 drivers/iio/gyro/bmg160_spi.c create mode 100644 drivers/iio/gyro/fxas21002c.h create mode 100644 drivers/iio/gyro/fxas21002c_core.c create mode 100644 drivers/iio/gyro/fxas21002c_i2c.c create mode 100644 drivers/iio/gyro/fxas21002c_spi.c create mode 100644 drivers/iio/gyro/hid-sensor-gyro-3d.c create mode 100644 drivers/iio/gyro/itg3200_buffer.c create mode 100644 drivers/iio/gyro/itg3200_core.c create mode 100644 drivers/iio/gyro/mpu3050-core.c create mode 100644 drivers/iio/gyro/mpu3050-i2c.c create mode 100644 drivers/iio/gyro/mpu3050.h create mode 100644 drivers/iio/gyro/ssp_gyro_sensor.c create mode 100644 drivers/iio/gyro/st_gyro.h create mode 100644 drivers/iio/gyro/st_gyro_buffer.c create mode 100644 drivers/iio/gyro/st_gyro_core.c create mode 100644 drivers/iio/gyro/st_gyro_i2c.c create mode 100644 drivers/iio/gyro/st_gyro_spi.c create mode 100644 drivers/iio/health/Kconfig create mode 100644 drivers/iio/health/Makefile create mode 100644 drivers/iio/health/afe4403.c create mode 100644 drivers/iio/health/afe4404.c create mode 100644 drivers/iio/health/afe440x.h create mode 100644 drivers/iio/health/max30100.c create mode 100644 drivers/iio/health/max30102.c create mode 100644 drivers/iio/humidity/Kconfig create mode 100644 drivers/iio/humidity/Makefile create mode 100644 drivers/iio/humidity/am2315.c create mode 100644 drivers/iio/humidity/dht11.c create mode 100644 drivers/iio/humidity/ens210.c create mode 100644 drivers/iio/humidity/hdc100x.c create mode 100644 drivers/iio/humidity/hdc2010.c create mode 100644 drivers/iio/humidity/hdc3020.c create mode 100644 drivers/iio/humidity/hid-sensor-humidity.c create mode 100644 drivers/iio/humidity/hts221.h create mode 100644 drivers/iio/humidity/hts221_buffer.c create mode 100644 drivers/iio/humidity/hts221_core.c create mode 100644 drivers/iio/humidity/hts221_i2c.c create mode 100644 drivers/iio/humidity/hts221_spi.c create mode 100644 drivers/iio/humidity/htu21.c create mode 100644 drivers/iio/humidity/si7005.c create mode 100644 drivers/iio/humidity/si7020.c create mode 100644 drivers/iio/iio_core.h create mode 100644 drivers/iio/iio_core_trigger.h create mode 100644 drivers/iio/imu/Kconfig create mode 100644 drivers/iio/imu/Makefile create mode 100644 drivers/iio/imu/adis.c create mode 100644 drivers/iio/imu/adis16400.c create mode 100644 drivers/iio/imu/adis16460.c create mode 100644 drivers/iio/imu/adis16475.c create mode 100644 drivers/iio/imu/adis16480.c create mode 100644 drivers/iio/imu/adis16550.c create mode 100644 drivers/iio/imu/adis_buffer.c create mode 100644 drivers/iio/imu/adis_trigger.c create mode 100644 drivers/iio/imu/bmi160/Kconfig create mode 100644 drivers/iio/imu/bmi160/Makefile create mode 100644 drivers/iio/imu/bmi160/bmi160.h create mode 100644 drivers/iio/imu/bmi160/bmi160_core.c create mode 100644 drivers/iio/imu/bmi160/bmi160_i2c.c create mode 100644 drivers/iio/imu/bmi160/bmi160_spi.c create mode 100644 drivers/iio/imu/bmi270/Kconfig create mode 100644 drivers/iio/imu/bmi270/Makefile create mode 100644 drivers/iio/imu/bmi270/bmi270.h create mode 100644 drivers/iio/imu/bmi270/bmi270_core.c create mode 100644 drivers/iio/imu/bmi270/bmi270_i2c.c create mode 100644 drivers/iio/imu/bmi270/bmi270_spi.c create mode 100644 drivers/iio/imu/bmi323/Kconfig create mode 100644 drivers/iio/imu/bmi323/Makefile create mode 100644 drivers/iio/imu/bmi323/bmi323.h create mode 100644 drivers/iio/imu/bmi323/bmi323_core.c create mode 100644 drivers/iio/imu/bmi323/bmi323_i2c.c create mode 100644 drivers/iio/imu/bmi323/bmi323_spi.c create mode 100644 drivers/iio/imu/bno055/Kconfig create mode 100644 drivers/iio/imu/bno055/Makefile create mode 100644 drivers/iio/imu/bno055/bno055.c create mode 100644 drivers/iio/imu/bno055/bno055.h create mode 100644 drivers/iio/imu/bno055/bno055_i2c.c create mode 100644 drivers/iio/imu/bno055/bno055_ser_core.c create mode 100644 drivers/iio/imu/bno055/bno055_ser_trace.c create mode 100644 drivers/iio/imu/bno055/bno055_ser_trace.h create mode 100644 drivers/iio/imu/fxos8700.h create mode 100644 drivers/iio/imu/fxos8700_core.c create mode 100644 drivers/iio/imu/fxos8700_i2c.c create mode 100644 drivers/iio/imu/fxos8700_spi.c create mode 100644 drivers/iio/imu/inv_icm42600/Kconfig create mode 100644 drivers/iio/imu/inv_icm42600/Makefile create mode 100644 drivers/iio/imu/inv_icm42600/inv_icm42600.h create mode 100644 drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c create mode 100644 drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c create mode 100644 drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h create mode 100644 drivers/iio/imu/inv_icm42600/inv_icm42600_core.c create mode 100644 drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c create mode 100644 drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c create mode 100644 drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c create mode 100644 drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c create mode 100644 drivers/iio/imu/inv_icm42600/inv_icm42600_temp.h create mode 100644 drivers/iio/imu/inv_icm42607/Kconfig create mode 100644 drivers/iio/imu/inv_icm42607/Makefile create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607.h create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_accel.c create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_core.c create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_gyro.c create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_i2c.c create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_spi.c create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_temp.c create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_temp.h create mode 100644 drivers/iio/imu/inv_icm45600/Kconfig create mode 100644 drivers/iio/imu/inv_icm45600/Makefile create mode 100644 drivers/iio/imu/inv_icm45600/inv_icm45600.h create mode 100644 drivers/iio/imu/inv_icm45600/inv_icm45600_accel.c create mode 100644 drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c create mode 100644 drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.h create mode 100644 drivers/iio/imu/inv_icm45600/inv_icm45600_core.c create mode 100644 drivers/iio/imu/inv_icm45600/inv_icm45600_gyro.c create mode 100644 drivers/iio/imu/inv_icm45600/inv_icm45600_i2c.c create mode 100644 drivers/iio/imu/inv_icm45600/inv_icm45600_i3c.c create mode 100644 drivers/iio/imu/inv_icm45600/inv_icm45600_spi.c create mode 100644 drivers/iio/imu/inv_mpu6050/Kconfig create mode 100644 drivers/iio/imu/inv_mpu6050/Makefile create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_aux.c create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_aux.h create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_core.c create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_magn.c create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_magn.h create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c create mode 100644 drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c create mode 100644 drivers/iio/imu/kmx61.c create mode 100644 drivers/iio/imu/smi240.c create mode 100644 drivers/iio/imu/smi330/Kconfig create mode 100644 drivers/iio/imu/smi330/Makefile create mode 100644 drivers/iio/imu/smi330/smi330.h create mode 100644 drivers/iio/imu/smi330/smi330_core.c create mode 100644 drivers/iio/imu/smi330/smi330_i2c.c create mode 100644 drivers/iio/imu/smi330/smi330_spi.c create mode 100644 drivers/iio/imu/st_lsm6dsx/Kconfig create mode 100644 drivers/iio/imu/st_lsm6dsx/Makefile create mode 100644 drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h create mode 100644 drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c create mode 100644 drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c create mode 100644 drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_fusion.c create mode 100644 drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i2c.c create mode 100644 drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i3c.c create mode 100644 drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c create mode 100644 drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_spi.c create mode 100644 drivers/iio/imu/st_lsm9ds0/Kconfig create mode 100644 drivers/iio/imu/st_lsm9ds0/Makefile create mode 100644 drivers/iio/imu/st_lsm9ds0/st_lsm9ds0.h create mode 100644 drivers/iio/imu/st_lsm9ds0/st_lsm9ds0_core.c create mode 100644 drivers/iio/imu/st_lsm9ds0/st_lsm9ds0_i2c.c create mode 100644 drivers/iio/imu/st_lsm9ds0/st_lsm9ds0_spi.c create mode 100644 drivers/iio/industrialio-acpi.c create mode 100644 drivers/iio/industrialio-backend.c create mode 100644 drivers/iio/industrialio-buffer.c create mode 100644 drivers/iio/industrialio-configfs.c create mode 100644 drivers/iio/industrialio-core.c create mode 100644 drivers/iio/industrialio-event.c create mode 100644 drivers/iio/industrialio-gts-helper.c create mode 100644 drivers/iio/industrialio-sw-device.c create mode 100644 drivers/iio/industrialio-sw-trigger.c create mode 100644 drivers/iio/industrialio-trigger.c create mode 100644 drivers/iio/industrialio-triggered-event.c create mode 100644 drivers/iio/inkern.c create mode 100644 drivers/iio/light/Kconfig create mode 100644 drivers/iio/light/Makefile create mode 100644 drivers/iio/light/acpi-als.c create mode 100644 drivers/iio/light/adjd_s311.c create mode 100644 drivers/iio/light/adux1020.c create mode 100644 drivers/iio/light/al3000a.c create mode 100644 drivers/iio/light/al3010.c create mode 100644 drivers/iio/light/al3320a.c create mode 100644 drivers/iio/light/apds9160.c create mode 100644 drivers/iio/light/apds9300.c create mode 100644 drivers/iio/light/apds9306.c create mode 100644 drivers/iio/light/apds9960.c create mode 100644 drivers/iio/light/apds9999.c create mode 100644 drivers/iio/light/as73211.c create mode 100644 drivers/iio/light/bh1745.c create mode 100644 drivers/iio/light/bh1750.c create mode 100644 drivers/iio/light/bh1780.c create mode 100644 drivers/iio/light/cm32181.c create mode 100644 drivers/iio/light/cm3232.c create mode 100644 drivers/iio/light/cm3323.c create mode 100644 drivers/iio/light/cm3605.c create mode 100644 drivers/iio/light/cm36651.c create mode 100644 drivers/iio/light/cros_ec_light_prox.c create mode 100644 drivers/iio/light/gp2ap002.c create mode 100644 drivers/iio/light/gp2ap020a00f.c create mode 100644 drivers/iio/light/hid-sensor-als.c create mode 100644 drivers/iio/light/hid-sensor-prox.c create mode 100644 drivers/iio/light/iqs621-als.c create mode 100644 drivers/iio/light/isl29018.c create mode 100644 drivers/iio/light/isl29028.c create mode 100644 drivers/iio/light/isl29125.c create mode 100644 drivers/iio/light/isl76682.c create mode 100644 drivers/iio/light/jsa1212.c create mode 100644 drivers/iio/light/lm3533-als.c create mode 100644 drivers/iio/light/ltr390.c create mode 100644 drivers/iio/light/ltr501.c create mode 100644 drivers/iio/light/ltrf216a.c create mode 100644 drivers/iio/light/lv0104cs.c create mode 100644 drivers/iio/light/max44000.c create mode 100644 drivers/iio/light/max44009.c create mode 100644 drivers/iio/light/noa1305.c create mode 100644 drivers/iio/light/opt3001.c create mode 100644 drivers/iio/light/opt4001.c create mode 100644 drivers/iio/light/opt4060.c create mode 100644 drivers/iio/light/pa12203001.c create mode 100644 drivers/iio/light/rohm-bu27034.c create mode 100644 drivers/iio/light/rpr0521.c create mode 100644 drivers/iio/light/si1133.c create mode 100644 drivers/iio/light/si1145.c create mode 100644 drivers/iio/light/st_uvis25.h create mode 100644 drivers/iio/light/st_uvis25_core.c create mode 100644 drivers/iio/light/st_uvis25_i2c.c create mode 100644 drivers/iio/light/st_uvis25_spi.c create mode 100644 drivers/iio/light/stk3310.c create mode 100644 drivers/iio/light/tcs3414.c create mode 100644 drivers/iio/light/tcs3472.c create mode 100644 drivers/iio/light/tsl2563.c create mode 100644 drivers/iio/light/tsl2583.c create mode 100644 drivers/iio/light/tsl2591.c create mode 100644 drivers/iio/light/tsl2772.c create mode 100644 drivers/iio/light/tsl4531.c create mode 100644 drivers/iio/light/us5182d.c create mode 100644 drivers/iio/light/vcnl4000.c create mode 100644 drivers/iio/light/vcnl4035.c create mode 100644 drivers/iio/light/veml3235.c create mode 100644 drivers/iio/light/veml3328.c create mode 100644 drivers/iio/light/veml6030.c create mode 100644 drivers/iio/light/veml6040.c create mode 100644 drivers/iio/light/veml6046x00.c create mode 100644 drivers/iio/light/veml6070.c create mode 100644 drivers/iio/light/veml6075.c create mode 100644 drivers/iio/light/vl6180.c create mode 100644 drivers/iio/light/zopt2201.c create mode 100644 drivers/iio/magnetometer/Kconfig create mode 100644 drivers/iio/magnetometer/Makefile create mode 100644 drivers/iio/magnetometer/af8133j.c create mode 100644 drivers/iio/magnetometer/ak8974.c create mode 100644 drivers/iio/magnetometer/ak8975.c create mode 100644 drivers/iio/magnetometer/als31300.c create mode 100644 drivers/iio/magnetometer/bmc150_magn.c create mode 100644 drivers/iio/magnetometer/bmc150_magn.h create mode 100644 drivers/iio/magnetometer/bmc150_magn_i2c.c create mode 100644 drivers/iio/magnetometer/bmc150_magn_spi.c create mode 100644 drivers/iio/magnetometer/hid-sensor-magn-3d.c create mode 100644 drivers/iio/magnetometer/hmc5843.h create mode 100644 drivers/iio/magnetometer/hmc5843_core.c create mode 100644 drivers/iio/magnetometer/hmc5843_i2c.c create mode 100644 drivers/iio/magnetometer/hmc5843_spi.c create mode 100644 drivers/iio/magnetometer/mag3110.c create mode 100644 drivers/iio/magnetometer/mmc35240.c create mode 100644 drivers/iio/magnetometer/mmc5633.c create mode 100644 drivers/iio/magnetometer/mmc5983.c create mode 100644 drivers/iio/magnetometer/qmc5883l.c create mode 100644 drivers/iio/magnetometer/qmc6308.c create mode 100644 drivers/iio/magnetometer/rm3100-core.c create mode 100644 drivers/iio/magnetometer/rm3100-i2c.c create mode 100644 drivers/iio/magnetometer/rm3100-spi.c create mode 100644 drivers/iio/magnetometer/rm3100.h create mode 100644 drivers/iio/magnetometer/si7210.c create mode 100644 drivers/iio/magnetometer/st_magn.h create mode 100644 drivers/iio/magnetometer/st_magn_buffer.c create mode 100644 drivers/iio/magnetometer/st_magn_core.c create mode 100644 drivers/iio/magnetometer/st_magn_i2c.c create mode 100644 drivers/iio/magnetometer/st_magn_spi.c create mode 100644 drivers/iio/magnetometer/tlv493d.c create mode 100644 drivers/iio/magnetometer/tmag5273.c create mode 100644 drivers/iio/magnetometer/yamaha-yas530.c create mode 100644 drivers/iio/multiplexer/Kconfig create mode 100644 drivers/iio/multiplexer/Makefile create mode 100644 drivers/iio/multiplexer/iio-mux.c create mode 100644 drivers/iio/orientation/Kconfig create mode 100644 drivers/iio/orientation/Makefile create mode 100644 drivers/iio/orientation/hid-sensor-incl-3d.c create mode 100644 drivers/iio/orientation/hid-sensor-rotation.c create mode 100644 drivers/iio/position/Kconfig create mode 100644 drivers/iio/position/Makefile create mode 100644 drivers/iio/position/hid-sensor-custom-intel-hinge.c create mode 100644 drivers/iio/position/iqs624-pos.c create mode 100644 drivers/iio/potentiometer/Kconfig create mode 100644 drivers/iio/potentiometer/Makefile create mode 100644 drivers/iio/potentiometer/ad5110.c create mode 100644 drivers/iio/potentiometer/ad5272.c create mode 100644 drivers/iio/potentiometer/ds1803.c create mode 100644 drivers/iio/potentiometer/max5432.c create mode 100644 drivers/iio/potentiometer/max5481.c create mode 100644 drivers/iio/potentiometer/max5487.c create mode 100644 drivers/iio/potentiometer/mcp4018.c create mode 100644 drivers/iio/potentiometer/mcp41010.c create mode 100644 drivers/iio/potentiometer/mcp4131.c create mode 100644 drivers/iio/potentiometer/mcp4531.c create mode 100644 drivers/iio/potentiometer/tpl0102.c create mode 100644 drivers/iio/potentiometer/x9250.c create mode 100644 drivers/iio/potentiostat/Kconfig create mode 100644 drivers/iio/potentiostat/Makefile create mode 100644 drivers/iio/potentiostat/lmp91000.c create mode 100644 drivers/iio/pressure/Kconfig create mode 100644 drivers/iio/pressure/Makefile create mode 100644 drivers/iio/pressure/abp060mg.c create mode 100644 drivers/iio/pressure/abp2030pa.c create mode 100644 drivers/iio/pressure/abp2030pa.h create mode 100644 drivers/iio/pressure/abp2030pa_i2c.c create mode 100644 drivers/iio/pressure/abp2030pa_spi.c create mode 100644 drivers/iio/pressure/adp810.c create mode 100644 drivers/iio/pressure/bmp280-core.c create mode 100644 drivers/iio/pressure/bmp280-i2c.c create mode 100644 drivers/iio/pressure/bmp280-regmap.c create mode 100644 drivers/iio/pressure/bmp280-spi.c create mode 100644 drivers/iio/pressure/bmp280.h create mode 100644 drivers/iio/pressure/cros_ec_baro.c create mode 100644 drivers/iio/pressure/dlhl60d.c create mode 100644 drivers/iio/pressure/dps310.c create mode 100644 drivers/iio/pressure/hid-sensor-press.c create mode 100644 drivers/iio/pressure/hp03.c create mode 100644 drivers/iio/pressure/hp206c.c create mode 100644 drivers/iio/pressure/hsc030pa.c create mode 100644 drivers/iio/pressure/hsc030pa.h create mode 100644 drivers/iio/pressure/hsc030pa_i2c.c create mode 100644 drivers/iio/pressure/hsc030pa_spi.c create mode 100644 drivers/iio/pressure/icp10100.c create mode 100644 drivers/iio/pressure/mpl115.c create mode 100644 drivers/iio/pressure/mpl115.h create mode 100644 drivers/iio/pressure/mpl115_i2c.c create mode 100644 drivers/iio/pressure/mpl115_spi.c create mode 100644 drivers/iio/pressure/mpl3115.c create mode 100644 drivers/iio/pressure/mprls0025pa.c create mode 100644 drivers/iio/pressure/mprls0025pa.h create mode 100644 drivers/iio/pressure/mprls0025pa_i2c.c create mode 100644 drivers/iio/pressure/mprls0025pa_spi.c create mode 100644 drivers/iio/pressure/ms5611.h create mode 100644 drivers/iio/pressure/ms5611_core.c create mode 100644 drivers/iio/pressure/ms5611_i2c.c create mode 100644 drivers/iio/pressure/ms5611_spi.c create mode 100644 drivers/iio/pressure/ms5637.c create mode 100644 drivers/iio/pressure/rohm-bm1390.c create mode 100644 drivers/iio/pressure/sdp500.c create mode 100644 drivers/iio/pressure/st_pressure.h create mode 100644 drivers/iio/pressure/st_pressure_buffer.c create mode 100644 drivers/iio/pressure/st_pressure_core.c create mode 100644 drivers/iio/pressure/st_pressure_i2c.c create mode 100644 drivers/iio/pressure/st_pressure_spi.c create mode 100644 drivers/iio/pressure/t5403.c create mode 100644 drivers/iio/pressure/zpa2326.c create mode 100644 drivers/iio/pressure/zpa2326.h create mode 100644 drivers/iio/pressure/zpa2326_i2c.c create mode 100644 drivers/iio/pressure/zpa2326_spi.c create mode 100644 drivers/iio/proximity/Kconfig create mode 100644 drivers/iio/proximity/Makefile create mode 100644 drivers/iio/proximity/as3935.c create mode 100644 drivers/iio/proximity/aw96103.c create mode 100644 drivers/iio/proximity/cros_ec_mkbp_proximity.c create mode 100644 drivers/iio/proximity/d3323aa.c create mode 100644 drivers/iio/proximity/hx9023s.c create mode 100644 drivers/iio/proximity/irsd200.c create mode 100644 drivers/iio/proximity/isl29501.c create mode 100644 drivers/iio/proximity/mb1232.c create mode 100644 drivers/iio/proximity/ping.c create mode 100644 drivers/iio/proximity/pulsedlight-lidar-lite-v2.c create mode 100644 drivers/iio/proximity/rfd77402.c create mode 100644 drivers/iio/proximity/srf04.c create mode 100644 drivers/iio/proximity/srf08.c create mode 100644 drivers/iio/proximity/sx9310.c create mode 100644 drivers/iio/proximity/sx9324.c create mode 100644 drivers/iio/proximity/sx9360.c create mode 100644 drivers/iio/proximity/sx9500.c create mode 100644 drivers/iio/proximity/sx_common.c create mode 100644 drivers/iio/proximity/sx_common.h create mode 100644 drivers/iio/proximity/vcnl3020.c create mode 100644 drivers/iio/proximity/vl53l0x-i2c.c create mode 100644 drivers/iio/proximity/vl53l1x-i2c.c create mode 100644 drivers/iio/resolver/Kconfig create mode 100644 drivers/iio/resolver/Makefile create mode 100644 drivers/iio/resolver/ad2s1200.c create mode 100644 drivers/iio/resolver/ad2s1210.c create mode 100644 drivers/iio/resolver/ad2s90.c create mode 100644 drivers/iio/temperature/Kconfig create mode 100644 drivers/iio/temperature/Makefile create mode 100644 drivers/iio/temperature/hid-sensor-temperature.c create mode 100644 drivers/iio/temperature/iqs620at-temp.c create mode 100644 drivers/iio/temperature/ltc2983.c create mode 100644 drivers/iio/temperature/max30208.c create mode 100644 drivers/iio/temperature/max31856.c create mode 100644 drivers/iio/temperature/max31865.c create mode 100644 drivers/iio/temperature/maxim_thermocouple.c create mode 100644 drivers/iio/temperature/mcp9600.c create mode 100644 drivers/iio/temperature/mlx90614.c create mode 100644 drivers/iio/temperature/mlx90632.c create mode 100644 drivers/iio/temperature/mlx90635.c create mode 100644 drivers/iio/temperature/tmp006.c create mode 100644 drivers/iio/temperature/tmp007.c create mode 100644 drivers/iio/temperature/tmp117.c create mode 100644 drivers/iio/temperature/tsys01.c create mode 100644 drivers/iio/temperature/tsys02d.c create mode 100644 drivers/iio/test/Kconfig create mode 100644 drivers/iio/test/Makefile create mode 100644 drivers/iio/test/iio-test-format.c create mode 100644 drivers/iio/test/iio-test-gts.c create mode 100644 drivers/iio/test/iio-test-multiply.c create mode 100644 drivers/iio/test/iio-test-rescale.c create mode 100644 drivers/iio/trigger/Kconfig create mode 100644 drivers/iio/trigger/Makefile create mode 100644 drivers/iio/trigger/iio-trig-hrtimer.c create mode 100644 drivers/iio/trigger/iio-trig-loop.c create mode 100644 drivers/iio/trigger/iio-trig-sysfs.c create mode 100644 drivers/iio/trigger/stm32-lptimer-trigger.c create mode 100644 drivers/iio/trigger/stm32-timer-trigger.c (limited to 'drivers/iio') diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig new file mode 100644 index 000000000..652557a5b --- /dev/null +++ b/drivers/iio/Kconfig @@ -0,0 +1,117 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Industrial I/O subsystem configuration +# + +menuconfig IIO + tristate "Industrial I/O support" + help + The industrial I/O subsystem provides a unified framework for + drivers for many different types of embedded sensors using a + number of different physical interfaces (i2c, spi, etc). + +if IIO + +config IIO_BUFFER + bool "Enable buffer support within IIO" + select DMA_SHARED_BUFFER + help + Provide core support for various buffer based data + acquisition methods. + +if IIO_BUFFER + source "drivers/iio/buffer/Kconfig" +endif # IIO_BUFFER + +config IIO_CONFIGFS + tristate "Enable IIO configuration via configfs" + select CONFIGFS_FS + help + This allows configuring various IIO bits through configfs + (e.g. software triggers). For more info see + Documentation/iio/iio_configfs.rst. + +config IIO_GTS_HELPER + tristate + +config IIO_TRIGGER + bool "Enable triggered sampling support" + help + Provides IIO core support for triggers. Currently these + are used to initialize capture of samples to push into + buffers. The triggers are effectively a 'capture + data now' interrupt. + +config IIO_CONSUMERS_PER_TRIGGER + int "Maximum number of consumers per trigger" + depends on IIO_TRIGGER + default "2" + help + This value controls the maximum number of consumers that a + given trigger may handle. Default is 2. + +config IIO_SW_DEVICE + tristate "Enable software IIO device support" + select IIO_CONFIGFS + help + Provides IIO core support for software devices. A software + device can be created via configfs or directly by a driver + using the API provided. + +config IIO_SW_TRIGGER + tristate "Enable software triggers support" + select IIO_CONFIGFS + help + Provides IIO core support for software triggers. A software + trigger can be created via configfs or directly by a driver + using the API provided. + +config IIO_TRIGGERED_EVENT + tristate "Enable triggered events support" + select IIO_TRIGGER + help + Provides helper functions for setting up triggered events. + +config IIO_BACKEND + tristate + help + Framework to handle complex IIO aggregate devices. The typical + architecture that can make use of this framework is to have one + device as the frontend device which can be "linked" against one or + multiple backend devices. The framework then makes it easy to get + and control such backend devices. + +source "drivers/iio/accel/Kconfig" +source "drivers/iio/adc/Kconfig" +source "drivers/iio/addac/Kconfig" +source "drivers/iio/afe/Kconfig" +source "drivers/iio/amplifiers/Kconfig" +source "drivers/iio/cdc/Kconfig" +source "drivers/iio/chemical/Kconfig" +source "drivers/iio/common/Kconfig" +source "drivers/iio/dac/Kconfig" +source "drivers/iio/dummy/Kconfig" +source "drivers/iio/filter/Kconfig" +source "drivers/iio/flow/Kconfig" +source "drivers/iio/frequency/Kconfig" +source "drivers/iio/gyro/Kconfig" +source "drivers/iio/health/Kconfig" +source "drivers/iio/humidity/Kconfig" +source "drivers/iio/imu/Kconfig" +source "drivers/iio/light/Kconfig" +source "drivers/iio/magnetometer/Kconfig" +source "drivers/iio/multiplexer/Kconfig" +source "drivers/iio/orientation/Kconfig" +source "drivers/iio/test/Kconfig" +if IIO_TRIGGER + source "drivers/iio/trigger/Kconfig" +endif #IIO_TRIGGER +source "drivers/iio/position/Kconfig" +source "drivers/iio/potentiometer/Kconfig" +source "drivers/iio/potentiostat/Kconfig" +source "drivers/iio/pressure/Kconfig" +source "drivers/iio/proximity/Kconfig" +source "drivers/iio/resolver/Kconfig" +source "drivers/iio/temperature/Kconfig" + +endif # IIO diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile new file mode 100644 index 000000000..f03a4100c --- /dev/null +++ b/drivers/iio/Makefile @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the industrial I/O core. +# + +obj-$(CONFIG_IIO) += industrialio.o +industrialio-y := industrialio-core.o industrialio-event.o inkern.o +industrialio-$(CONFIG_IIO_BUFFER) += industrialio-buffer.o +industrialio-$(CONFIG_IIO_TRIGGER) += industrialio-trigger.o +industrialio-$(CONFIG_ACPI) += industrialio-acpi.o + +obj-$(CONFIG_IIO_CONFIGFS) += industrialio-configfs.o +obj-$(CONFIG_IIO_GTS_HELPER) += industrialio-gts-helper.o +obj-$(CONFIG_IIO_SW_DEVICE) += industrialio-sw-device.o +obj-$(CONFIG_IIO_SW_TRIGGER) += industrialio-sw-trigger.o +obj-$(CONFIG_IIO_TRIGGERED_EVENT) += industrialio-triggered-event.o +obj-$(CONFIG_IIO_BACKEND) += industrialio-backend.o + +obj-y += accel/ +obj-y += adc/ +obj-y += addac/ +obj-y += afe/ +obj-y += amplifiers/ +obj-y += buffer/ +obj-y += cdc/ +obj-y += chemical/ +obj-y += common/ +obj-y += dac/ +obj-y += dummy/ +obj-y += gyro/ +obj-y += filter/ +obj-y += flow/ +obj-y += frequency/ +obj-y += health/ +obj-y += humidity/ +obj-y += imu/ +obj-y += light/ +obj-y += magnetometer/ +obj-y += multiplexer/ +obj-y += orientation/ +obj-y += position/ +obj-y += potentiometer/ +obj-y += potentiostat/ +obj-y += pressure/ +obj-y += proximity/ +obj-y += resolver/ +obj-y += temperature/ +obj-y += test/ +obj-y += trigger/ diff --git a/drivers/iio/TODO b/drivers/iio/TODO new file mode 100644 index 000000000..7b0530824 --- /dev/null +++ b/drivers/iio/TODO @@ -0,0 +1,21 @@ +2020-02-29 + +Documentation + - Binding docs for devices that are obviously used via device +tree + - Yaml conversions for abandoned drivers + - ABI Documentation + - Audit drivers/staging/iio/Documentation + +- Converting drivers from device tree centric to generic property handlers. + +- Refactor old platform_data constructs from drivers and convert it + to state struct and using property handlers and readers. + +- Convert selected IIO drivers to use fully device managed resource APIs. + +- Evaluate and possibly adopt cleanup.h helpers (guard()(), __free(), et cetera) + in drivers where manual resource management is still used and there is a + significant improvement to the code flow and complexity to be had. + +Mailing list: linux-iio@vger.kernel.org diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig new file mode 100644 index 000000000..4094299e2 --- /dev/null +++ b/drivers/iio/accel/Kconfig @@ -0,0 +1,708 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Accelerometer drivers +# +# When adding new entries keep the list in alphabetical order + +menu "Accelerometers" + +config ADIS16201 + tristate "Analog Devices ADIS16201 Dual-Axis Digital Inclinometer and Accelerometer" + depends on SPI + select IIO_ADIS_LIB + select IIO_ADIS_LIB_BUFFER if IIO_BUFFER + help + Say Y here to build support for Analog Devices adis16201 dual-axis + digital inclinometer and accelerometer. + + To compile this driver as a module, say M here: the module will + be called adis16201. + +config ADIS16209 + tristate "Analog Devices ADIS16209 Dual-Axis Digital Inclinometer and Accelerometer" + depends on SPI + select IIO_ADIS_LIB + select IIO_ADIS_LIB_BUFFER if IIO_BUFFER + help + Say Y here to build support for Analog Devices adis16209 dual-axis digital inclinometer + and accelerometer. + + To compile this driver as a module, say M here: the module will be + called adis16209. + +config ADXL313 + tristate + +config ADXL313_I2C + tristate "Analog Devices ADXL313 3-Axis Digital Accelerometer I2C Driver" + depends on I2C + select ADXL313 + select REGMAP_I2C + help + Say Y here if you want to build i2c support for the Analog Devices + ADXL313 3-axis digital accelerometer. + + To compile this driver as a module, choose M here: the module + will be called adxl313_i2c and you will also get adxl313_core + for the core module. + +config ADXL313_SPI + tristate "Analog Devices ADXL313 3-Axis Digital Accelerometer SPI Driver" + depends on SPI + select ADXL313 + select REGMAP_SPI + help + Say Y here if you want to build spi support for the Analog Devices + ADXL313 3-axis digital accelerometer. + + To compile this driver as a module, choose M here: the module + will be called adxl313_spi and you will also get adxl313_core + for the core module. + +config ADXL345 + tristate + +config ADXL345_I2C + tristate "Analog Devices ADXL345 3-Axis Digital Accelerometer I2C Driver" + depends on !INPUT_ADXL34X + depends on I2C + select ADXL345 + select REGMAP_I2C + help + Say Y here if you want to build support for the Analog Devices + ADXL345 or ADXL375 3-axis digital accelerometer. + + To compile this driver as a module, choose M here: the module + will be called adxl345_i2c and you will also get adxl345_core + for the core module. INPUT_ADXL34X share compatibles with this + driver, do not add both modules to the kernel. + +config ADXL345_SPI + tristate "Analog Devices ADXL345 3-Axis Digital Accelerometer SPI Driver" + depends on !INPUT_ADXL34X + depends on SPI + select ADXL345 + select REGMAP_SPI + help + Say Y here if you want to build support for the Analog Devices + ADXL345 or ADXL375 3-axis digital accelerometer. + + To compile this driver as a module, choose M here: the module + will be called adxl345_spi and you will also get adxl345_core + for the core module. INPUT_ADXL34X share compatibles with this + driver, do not add both modules to the kernel. + +config ADXL355 + tristate + +config ADXL355_I2C + tristate "Analog Devices ADXL355 3-Axis Digital Accelerometer I2C Driver" + depends on I2C + select ADXL355 + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here if you want to build i2c support for the Analog Devices + ADXL355 3-axis digital accelerometer. + + To compile this driver as a module, choose M here: the module + will be called adxl355_i2c and you will also get adxl355_core + for the core module. + +config ADXL355_SPI + tristate "Analog Devices ADXL355 3-Axis Digital Accelerometer SPI Driver" + depends on SPI + select ADXL355 + select REGMAP_SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here if you want to build spi support for the Analog Devices + ADXL355 3-axis digital accelerometer. + + To compile this driver as a module, choose M here: the module + will be called adxl355_spi and you will also get adxl355_core + for the core module. + +config ADXL367 + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config ADXL367_SPI + tristate "Analog Devices ADXL367 3-Axis Accelerometer SPI Driver" + depends on SPI + select ADXL367 + select REGMAP_SPI + help + Say yes here to add support for the Analog Devices ADXL367 triaxial + acceleration sensor. + To compile this driver as a module, choose M here: the + module will be called adxl367_spi. + +config ADXL367_I2C + tristate "Analog Devices ADXL367 3-Axis Accelerometer I2C Driver" + depends on I2C + select ADXL367 + select REGMAP_I2C + help + Say yes here to add support for the Analog Devices ADXL367 triaxial + acceleration sensor. + To compile this driver as a module, choose M here: the + module will be called adxl367_i2c. + +config ADXL372 + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config ADXL372_SPI + tristate "Analog Devices ADXL371/ADXL372 3-Axis Accelerometer SPI Driver" + depends on SPI + select ADXL372 + select REGMAP_SPI + help + Say yes here to add support for the Analog Devices ADXL371/ADXL372 + triaxial acceleration sensor. + To compile this driver as a module, choose M here: the + module will be called adxl372_spi. + +config ADXL372_I2C + tristate "Analog Devices ADXL371/ADXL372 3-Axis Accelerometer I2C Driver" + depends on I2C + select ADXL372 + select REGMAP_I2C + help + Say yes here to add support for the Analog Devices ADXL371/ADXL372 + triaxial acceleration sensor. + To compile this driver as a module, choose M here: the + module will be called adxl372_i2c. + +config ADXL380 + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config ADXL380_SPI + tristate "Analog Devices ADXL380 3-Axis Accelerometer SPI Driver" + depends on SPI + select ADXL380 + select REGMAP_SPI + help + Say yes here to add support for the Analog Devices ADXL380 triaxial + acceleration sensor. + To compile this driver as a module, choose M here: the + module will be called adxl380_spi. + +config ADXL380_I2C + tristate "Analog Devices ADXL380 3-Axis Accelerometer I2C Driver" + depends on I2C + select ADXL380 + select REGMAP_I2C + help + Say yes here to add support for the Analog Devices ADXL380 triaxial + acceleration sensor. + To compile this driver as a module, choose M here: the + module will be called adxl380_i2c. + +config BMA180 + tristate "Bosch BMA023/BMA1x0/BMA250 3-Axis Accelerometer Driver" + depends on I2C && INPUT_BMA150=n + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here if you want to build a driver for the Bosch BMA023, BMA150 + BMA180, BMA250 or SMB380 triaxial acceleration sensor. + + To compile this driver as a module, choose M here: the + module will be called bma180. + +config BMA220 + tristate "Bosch BMA220 3-Axis Accelerometer Driver" + depends on I2C || SPI + select REGMAP + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select BMA220_I2C if I2C + select BMA220_SPI if SPI + help + Say yes here to add support for the Bosch BMA220 triaxial + acceleration sensor. + + To compile this driver as a module, choose M here: the + module will be called bma220_core and you will also get + bma220_i2c if I2C is enabled and bma220_spi if SPI is + enabled. + +config BMA220_I2C + tristate + select REGMAP_I2C + depends on BMA220 + +config BMA220_SPI + tristate + select REGMAP_SPI + depends on BMA220 + +config BMA400 + tristate "Bosch BMA400 3-Axis Accelerometer Driver" + select REGMAP + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select BMA400_I2C if I2C + select BMA400_SPI if SPI + help + Say Y here if you want to build a driver for the Bosch BMA400 + triaxial acceleration sensor. + + To compile this driver as a module, choose M here: the + module will be called bma400_core and you will also get + bma400_i2c if I2C is enabled and bma400_spi if SPI is + enabled. + +config BMA400_I2C + tristate + select REGMAP_I2C + depends on BMA400 + +config BMA400_SPI + tristate + select REGMAP_SPI + depends on BMA400 + +config BMC150_ACCEL + tristate "Bosch BMC150 Accelerometer Driver" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select REGMAP + select BMC150_ACCEL_I2C if I2C + select BMC150_ACCEL_SPI if SPI + help + Say yes here to build support for the following Bosch accelerometers: + BMA222, BMA222E, BMA250E, BMA253, BMA254, BMA255, BMA280, BMC150, BMC156 + BMI055. + + Note that some of these are combo modules: + - BMC150/BMC156: accelerometer and magnetometer + - BMI055: accelerometer and gyroscope + + This driver is only implementing accelerometer part, which has + its own address and register map. + +config BMC150_ACCEL_I2C + tristate + select REGMAP_I2C + +config BMC150_ACCEL_SPI + tristate + select REGMAP_SPI + +config BMI088_ACCEL + tristate "Bosch BMI088 Accelerometer Driver" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select REGMAP + select BMI088_ACCEL_SPI if SPI + select BMI088_ACCEL_I2C if I2C + help + Say yes here to build support for the following Bosch accelerometers: + BMI088, BMI085, BMI090L. Note that all of these are combo module that + include both accelerometer and gyroscope. + + This driver only implements the accelerometer part, which has its own + address and register map. BMG160 provides the gyroscope driver. + +config BMI088_ACCEL_I2C + tristate + select REGMAP_I2C + +config BMI088_ACCEL_SPI + tristate + select REGMAP_SPI + +config DA280 + tristate "MiraMEMS DA280 3-axis 14-bit digital accelerometer driver" + depends on I2C + help + Say yes here to build support for the MiraMEMS DA280 3-axis 14-bit + digital accelerometer. + + To compile this driver as a module, choose M here: the + module will be called da280. + +config DA311 + tristate "MiraMEMS DA311 3-axis 12-bit digital accelerometer driver" + depends on I2C + help + Say yes here to build support for the MiraMEMS DA311 3-axis 12-bit + digital accelerometer. + + To compile this driver as a module, choose M here: the + module will be called da311. + +config DMARD06 + tristate "Domintech DMARD06 Digital Accelerometer Driver" + depends on I2C + help + Say yes here to build support for the Domintech low-g tri-axial + digital accelerometers: DMARD05, DMARD06, DMARD07. + + To compile this driver as a module, choose M here: the + module will be called dmard06. + +config DMARD09 + tristate "Domintech DMARD09 3-axis Accelerometer Driver" + depends on I2C + help + Say yes here to get support for the Domintech DMARD09 3-axis + accelerometer. + + Choosing M will build the driver as a module. If so, the module + will be called dmard09. + +config DMARD10 + tristate "Domintech DMARD10 3-axis Accelerometer Driver" + depends on I2C + help + Say yes here to get support for the Domintech DMARD10 3-axis + accelerometer. + + Choosing M will build the driver as a module. If so, the module + will be called dmard10. + +config FXLS8962AF + tristate + depends on I2C || !I2C # cannot be built-in for modular I2C + select IIO_BUFFER + select IIO_KFIFO_BUF + +config FXLS8962AF_I2C + tristate "NXP FXLS8962AF/FXLS8964AF Accelerometer I2C Driver" + depends on I2C + select FXLS8962AF + select REGMAP_I2C + help + Say yes here to build support for the NXP 3-axis automotive + accelerometer FXLS8962AF/FXLS8964AF with I2C support. + + To compile this driver as a module, choose M here: the module + will be called fxls8962af_i2c. + +config FXLS8962AF_SPI + tristate "NXP FXLS8962AF/FXLS8964AF Accelerometer SPI Driver" + depends on SPI + depends on I2C || !I2C + select FXLS8962AF + select REGMAP_SPI + help + Say yes here to build support for the NXP 3-axis automotive + accelerometer FXLS8962AF/FXLS8964AF with SPI support. + + To compile this driver as a module, choose M here: the module + will be called fxls8962af_spi. + +config HID_SENSOR_ACCEL_3D + depends on HID_SENSOR_HUB + select IIO_BUFFER + select HID_SENSOR_IIO_COMMON + select HID_SENSOR_IIO_TRIGGER + tristate "HID Accelerometers 3D" + help + Say yes here to build support for the HID SENSOR + accelerometers 3D. + + To compile this driver as a module, choose M here: the + module will be called hid-sensor-accel-3d. + +config IIO_CROS_EC_ACCEL_LEGACY + tristate "ChromeOS EC Legacy Accelerometer Sensor" + depends on IIO_CROS_EC_SENSORS_CORE + help + Say yes here to get support for accelerometers on Chromebook using + legacy EC firmware. + Sensor data is retrieved through IO memory. + Newer devices should use IIO_CROS_EC_SENSORS. + +config IIO_ST_ACCEL_3AXIS + tristate "STMicroelectronics accelerometers 3-Axis Driver" + depends on (I2C || SPI_MASTER) && SYSFS + depends on !SENSORS_LIS3_I2C + depends on !SENSORS_LIS3_SPI + select IIO_ST_SENSORS_CORE + select IIO_TRIGGERED_BUFFER if (IIO_BUFFER) + help + Say yes here to build support for STMicroelectronics accelerometers: + LSM303C, LSM303DLH, LSM303DLHC, LIS3DH, LSM330D, LSM330DL, LSM330DLC, + LIS331DLH, LSM303DL, LSM303DLM, LSM330, LIS2DH12, H3LIS331DL, + LNG2DM, LIS3DE, LIS2DE12, LIS2HH12 + + Also need to enable at least one of I2C and SPI interface drivers + below. + +config IIO_ST_ACCEL_I2C_3AXIS + tristate "STMicroelectronics accelerometers 3-Axis I2C Interface" + depends on I2C && IIO_ST_ACCEL_3AXIS + default I2C && IIO_ST_ACCEL_3AXIS + select IIO_ST_SENSORS_I2C + help + Build support for STMicroelectronics accelerometers I2C interface. + + To compile this driver as a module, choose M here. The module + will be called st_accel_i2c. + +config IIO_ST_ACCEL_SPI_3AXIS + tristate "STMicroelectronics accelerometers 3-Axis SPI Interface" + depends on SPI_MASTER && IIO_ST_ACCEL_3AXIS + default SPI_MASTER && IIO_ST_ACCEL_3AXIS + select IIO_ST_SENSORS_SPI + help + Build support for STMicroelectronics accelerometers SPI interface. + + To compile this driver as a module, choose M here. The module + will be called st_accel_spi. + +config IIO_KX022A + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config IIO_KX022A_SPI + tristate "Kionix KX022A tri-axis digital accelerometer SPI interface" + depends on SPI + select IIO_KX022A + select REGMAP_SPI + help + Enable support for the Kionix digital tri-axis accelerometers + connected to SPI interface. Supported devices are: + KX022A, KX132-1211, KX132ACR-LBZ + +config IIO_KX022A_I2C + tristate "Kionix KX022A tri-axis digital accelerometer I2C interface" + depends on I2C + select IIO_KX022A + select REGMAP_I2C + help + Enable support for the Kionix digital tri-axis accelerometers + connected to I2C interface. Supported devices are: + KX022A, KX132-1211, KX132ACR-LBZ + +config KXSD9 + tristate "Kionix KXSD9 Accelerometer Driver" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for the Kionix KXSD9 accelerometer. + It can be accessed using an (optional) SPI or I2C interface. + + To compile this driver as a module, choose M here: the module + will be called kxsd9. + +config KXSD9_SPI + tristate "Kionix KXSD9 SPI transport" + depends on KXSD9 + depends on SPI + default KXSD9 + select REGMAP_SPI + help + Say yes here to enable the Kionix KXSD9 accelerometer + SPI transport channel. + +config KXSD9_I2C + tristate "Kionix KXSD9 I2C transport" + depends on KXSD9 + depends on I2C + default KXSD9 + select REGMAP_I2C + help + Say yes here to enable the Kionix KXSD9 accelerometer + I2C transport channel. + +config KXCJK1013 + tristate "Kionix 3-Axis Accelerometer Driver" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here if you want to build a driver for the Kionix KXCJK-1013 + triaxial acceleration sensor. This driver also supports KXCJ9-1008, + KXTJ2-1009 and KXTF9. + + To compile this driver as a module, choose M here: the module will + be called kxcjk-1013. + +config MC3230 + tristate "mCube MC3230 Digital Accelerometer Driver" + depends on I2C + help + Say yes here to build support for the mCube MC3230 low-g tri-axial + digital accelerometer. + + To compile this driver as a module, choose M here: the + module will be called mc3230. + +config MMA7455 + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config MMA7455_I2C + tristate "Freescale MMA7455L/MMA7456L Accelerometer I2C Driver" + depends on I2C + select MMA7455 + select REGMAP_I2C + help + Say yes here to build support for the Freescale MMA7455L and + MMA7456L 3-axis accelerometer. + + To compile this driver as a module, choose M here: the module + will be called mma7455_i2c. + +config MMA7455_SPI + tristate "Freescale MMA7455L/MMA7456L Accelerometer SPI Driver" + depends on SPI_MASTER + select MMA7455 + select REGMAP_SPI + help + Say yes here to build support for the Freescale MMA7455L and + MMA7456L 3-axis accelerometer. + + To compile this driver as a module, choose M here: the module + will be called mma7455_spi. + +config MMA7660 + tristate "Freescale MMA7660FC 3-Axis Accelerometer Driver" + depends on I2C + help + Say yes here to get support for the Freescale MMA7660FC 3-Axis + accelerometer. + + Choosing M will build the driver as a module. If so, the module + will be called mma7660. + +config MMA8452 + tristate "Freescale / NXP MMA8452Q and similar Accelerometers Driver" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for the following Freescale / NXP 3-axis + accelerometers: MMA8451Q, MMA8452Q, MMA8453Q, MMA8652FC, MMA8653FC, + FXLS8471Q. + + To compile this driver as a module, choose M here: the module + will be called mma8452. + +config MMA9551_CORE + tristate + +config MMA9551 + tristate "Freescale MMA9551L Intelligent Motion-Sensing Platform Driver" + depends on I2C + select MMA9551_CORE + + help + Say yes here to build support for the Freescale MMA9551L + Intelligent Motion-Sensing Platform Driver. + + To compile this driver as a module, choose M here: the module + will be called mma9551. + +config MMA9553 + tristate "Freescale MMA9553L Intelligent Pedometer Platform Driver" + depends on I2C + select MMA9551_CORE + help + Say yes here to build support for the Freescale MMA9553L + Intelligent Pedometer Platform Driver. + + To compile this driver as a module, choose M here: the module + will be called mma9553. + +config MSA311 + tristate "MEMSensing Digital 3-Axis Accelerometer Driver" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select REGMAP_I2C + help + Say yes here to build support for the MEMSensing MSA311 + accelerometer driver. + + To compile this driver as a module, choose M here: the module will be + called msa311. + +config MXC4005 + tristate "Memsic MXC4005XC 3-Axis Accelerometer Driver" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select REGMAP_I2C + help + Say yes here to build support for the Memsic MXC4005XC 3-axis + accelerometer. + + To compile this driver as a module, choose M. The module will be + called mxc4005. + +config MXC6255 + tristate "Memsic MXC6255 Orientation Sensing Accelerometer Driver" + depends on I2C + select REGMAP_I2C + help + Say yes here to build support for the Memsic MXC6255 Orientation + Sensing Accelerometer Driver. + + To compile this driver as a module, choose M here: the module will be + called mxc6255. + +config SCA3000 + select IIO_BUFFER + select IIO_KFIFO_BUF + depends on SPI + tristate "VTI SCA3000 series accelerometers" + help + Say Y here to build support for the VTI SCA3000 series of SPI + accelerometers. These devices use a hardware ring buffer. + + To compile this driver as a module, say M here: the module will be + called sca3000. + +config SCA3300 + tristate "Murata SCA3300 3-Axis Accelerometer Driver" + depends on SPI + select CRC8 + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Murata SCA3300 3-Axis + accelerometer. + + To compile this driver as a module, choose M here: the module will be + called sca3300. + +config STK8312 + tristate "Sensortek STK8312 3-Axis Accelerometer Driver" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to get support for the Sensortek STK8312 3-axis + accelerometer. + + Choosing M will build the driver as a module. If so, the module + will be called stk8312. + +config STK8BA50 + tristate "Sensortek STK8BA50 3-Axis Accelerometer Driver" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to get support for the Sensortek STK8BA50 3-axis + accelerometer. + + Choosing M will build the driver as a module. If so, the module + will be called stk8ba50. + +endmenu diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile new file mode 100644 index 000000000..fa440a859 --- /dev/null +++ b/drivers/iio/accel/Makefile @@ -0,0 +1,90 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for industrial I/O accelerometer drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_ADIS16201) += adis16201.o +obj-$(CONFIG_ADIS16209) += adis16209.o +obj-$(CONFIG_ADXL313) += adxl313_core.o +obj-$(CONFIG_ADXL313_I2C) += adxl313_i2c.o +obj-$(CONFIG_ADXL313_SPI) += adxl313_spi.o +obj-$(CONFIG_ADXL345) += adxl345_core.o +obj-$(CONFIG_ADXL345_I2C) += adxl345_i2c.o +obj-$(CONFIG_ADXL345_SPI) += adxl345_spi.o +obj-$(CONFIG_ADXL355) += adxl355_core.o +obj-$(CONFIG_ADXL355_I2C) += adxl355_i2c.o +obj-$(CONFIG_ADXL355_SPI) += adxl355_spi.o +obj-$(CONFIG_ADXL367) += adxl367.o +obj-$(CONFIG_ADXL367_I2C) += adxl367_i2c.o +obj-$(CONFIG_ADXL367_SPI) += adxl367_spi.o +obj-$(CONFIG_ADXL372) += adxl372.o +obj-$(CONFIG_ADXL372_I2C) += adxl372_i2c.o +obj-$(CONFIG_ADXL372_SPI) += adxl372_spi.o +obj-$(CONFIG_ADXL380) += adxl380.o +obj-$(CONFIG_ADXL380_I2C) += adxl380_i2c.o +obj-$(CONFIG_ADXL380_SPI) += adxl380_spi.o +obj-$(CONFIG_BMA180) += bma180.o +obj-$(CONFIG_BMA220) += bma220_core.o +obj-$(CONFIG_BMA220_I2C) += bma220_i2c.o +obj-$(CONFIG_BMA220_SPI) += bma220_spi.o +obj-$(CONFIG_BMA400) += bma400_core.o +obj-$(CONFIG_BMA400_I2C) += bma400_i2c.o +obj-$(CONFIG_BMA400_SPI) += bma400_spi.o +obj-$(CONFIG_BMC150_ACCEL) += bmc150-accel-core.o +obj-$(CONFIG_BMC150_ACCEL_I2C) += bmc150-accel-i2c.o +obj-$(CONFIG_BMC150_ACCEL_SPI) += bmc150-accel-spi.o +obj-$(CONFIG_BMI088_ACCEL) += bmi088-accel-core.o +obj-$(CONFIG_BMI088_ACCEL_I2C) += bmi088-accel-i2c.o +obj-$(CONFIG_BMI088_ACCEL_SPI) += bmi088-accel-spi.o +obj-$(CONFIG_DA280) += da280.o +obj-$(CONFIG_DA311) += da311.o +obj-$(CONFIG_DMARD06) += dmard06.o +obj-$(CONFIG_DMARD09) += dmard09.o +obj-$(CONFIG_DMARD10) += dmard10.o +obj-$(CONFIG_FXLS8962AF) += fxls8962af-core.o +obj-$(CONFIG_FXLS8962AF_I2C) += fxls8962af-i2c.o +obj-$(CONFIG_FXLS8962AF_SPI) += fxls8962af-spi.o +obj-$(CONFIG_HID_SENSOR_ACCEL_3D) += hid-sensor-accel-3d.o +obj-$(CONFIG_IIO_KX022A) += kionix-kx022a.o +obj-$(CONFIG_IIO_KX022A_I2C) += kionix-kx022a-i2c.o +obj-$(CONFIG_IIO_KX022A_SPI) += kionix-kx022a-spi.o +obj-$(CONFIG_KXCJK1013) += kxcjk-1013.o +obj-$(CONFIG_KXSD9) += kxsd9.o +obj-$(CONFIG_KXSD9_SPI) += kxsd9-spi.o +obj-$(CONFIG_KXSD9_I2C) += kxsd9-i2c.o +obj-$(CONFIG_MC3230) += mc3230.o + +obj-$(CONFIG_MMA7455) += mma7455_core.o +obj-$(CONFIG_MMA7455_I2C) += mma7455_i2c.o +obj-$(CONFIG_MMA7455_SPI) += mma7455_spi.o + +obj-$(CONFIG_MMA7660) += mma7660.o + +obj-$(CONFIG_MMA8452) += mma8452.o + +obj-$(CONFIG_MMA9551_CORE) += mma9551_core.o +obj-$(CONFIG_MMA9551) += mma9551.o +obj-$(CONFIG_MMA9553) += mma9553.o + +obj-$(CONFIG_MSA311) += msa311.o + +obj-$(CONFIG_MXC4005) += mxc4005.o +obj-$(CONFIG_MXC6255) += mxc6255.o + +obj-$(CONFIG_SCA3000) += sca3000.o +obj-$(CONFIG_SCA3300) += sca3300.o + +obj-$(CONFIG_STK8312) += stk8312.o +obj-$(CONFIG_STK8BA50) += stk8ba50.o + +obj-$(CONFIG_IIO_CROS_EC_ACCEL_LEGACY) += cros_ec_accel_legacy.o + +obj-$(CONFIG_IIO_SSP_SENSORS_COMMONS) += ssp_accel_sensor.o + +obj-$(CONFIG_IIO_ST_ACCEL_3AXIS) += st_accel.o +st_accel-y := st_accel_core.o +st_accel-$(CONFIG_IIO_BUFFER) += st_accel_buffer.o + +obj-$(CONFIG_IIO_ST_ACCEL_I2C_3AXIS) += st_accel_i2c.o +obj-$(CONFIG_IIO_ST_ACCEL_SPI_3AXIS) += st_accel_spi.o diff --git a/drivers/iio/accel/adis16201.c b/drivers/iio/accel/adis16201.c new file mode 100644 index 000000000..ba0f97944 --- /dev/null +++ b/drivers/iio/accel/adis16201.c @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ADIS16201 Dual-Axis Digital Inclinometer and Accelerometer + * + * Copyright 2010 Analog Devices Inc. + */ + +#include +#include +#include +#include + +#include +#include + +#define ADIS16201_STARTUP_DELAY_MS 220 +#define ADIS16201_FLASH_CNT 0x00 + +/* Data Output Register Information */ +#define ADIS16201_SUPPLY_OUT_REG 0x02 +#define ADIS16201_XACCL_OUT_REG 0x04 +#define ADIS16201_YACCL_OUT_REG 0x06 +#define ADIS16201_AUX_ADC_REG 0x08 +#define ADIS16201_TEMP_OUT_REG 0x0A +#define ADIS16201_XINCL_OUT_REG 0x0C +#define ADIS16201_YINCL_OUT_REG 0x0E + +/* Calibration Register Definition */ +#define ADIS16201_XACCL_OFFS_REG 0x10 +#define ADIS16201_YACCL_OFFS_REG 0x12 +#define ADIS16201_XACCL_SCALE_REG 0x14 +#define ADIS16201_YACCL_SCALE_REG 0x16 +#define ADIS16201_XINCL_OFFS_REG 0x18 +#define ADIS16201_YINCL_OFFS_REG 0x1A +#define ADIS16201_XINCL_SCALE_REG 0x1C +#define ADIS16201_YINCL_SCALE_REG 0x1E + +/* Alarm Register Definition */ +#define ADIS16201_ALM_MAG1_REG 0x20 +#define ADIS16201_ALM_MAG2_REG 0x22 +#define ADIS16201_ALM_SMPL1_REG 0x24 +#define ADIS16201_ALM_SMPL2_REG 0x26 +#define ADIS16201_ALM_CTRL_REG 0x28 + +#define ADIS16201_AUX_DAC_REG 0x30 +#define ADIS16201_GPIO_CTRL_REG 0x32 +#define ADIS16201_SMPL_PRD_REG 0x36 +/* Operation, filter configuration */ +#define ADIS16201_AVG_CNT_REG 0x38 +#define ADIS16201_SLP_CNT_REG 0x3A + +/* Miscellaneous Control Register Definition */ +#define ADIS16201_MSC_CTRL_REG 0x34 +#define ADIS16201_MSC_CTRL_SELF_TEST_EN BIT(8) +/* Data-ready enable: 1 = enabled, 0 = disabled */ +#define ADIS16201_MSC_CTRL_DATA_RDY_EN BIT(2) +/* Data-ready polarity: 1 = active high, 0 = active low */ +#define ADIS16201_MSC_CTRL_ACTIVE_DATA_RDY_HIGH BIT(1) +/* Data-ready line selection: 1 = DIO1, 0 = DIO0 */ +#define ADIS16201_MSC_CTRL_DATA_RDY_DIO1 BIT(0) + +/* Diagnostics System Status Register Definition */ +#define ADIS16201_DIAG_STAT_REG 0x3C +#define ADIS16201_DIAG_STAT_ALARM2 BIT(9) +#define ADIS16201_DIAG_STAT_ALARM1 BIT(8) +#define ADIS16201_DIAG_STAT_SPI_FAIL_BIT 3 +#define ADIS16201_DIAG_STAT_FLASH_UPT_FAIL_BIT 2 +/* Power supply above 3.625 V */ +#define ADIS16201_DIAG_STAT_POWER_HIGH_BIT 1 +/* Power supply below 2.975 V */ +#define ADIS16201_DIAG_STAT_POWER_LOW_BIT 0 + +/* System Command Register Definition */ +#define ADIS16201_GLOB_CMD_REG 0x3E +#define ADIS16201_GLOB_CMD_SW_RESET BIT(7) +#define ADIS16201_GLOB_CMD_FACTORY_RESET BIT(1) + +#define ADIS16201_ERROR_ACTIVE BIT(14) + +enum adis16201_scan { + ADIS16201_SCAN_ACC_X, + ADIS16201_SCAN_ACC_Y, + ADIS16201_SCAN_INCLI_X, + ADIS16201_SCAN_INCLI_Y, + ADIS16201_SCAN_SUPPLY, + ADIS16201_SCAN_AUX_ADC, + ADIS16201_SCAN_TEMP, +}; + +static const u8 adis16201_addresses[] = { + [ADIS16201_SCAN_ACC_X] = ADIS16201_XACCL_OFFS_REG, + [ADIS16201_SCAN_ACC_Y] = ADIS16201_YACCL_OFFS_REG, + [ADIS16201_SCAN_INCLI_X] = ADIS16201_XINCL_OFFS_REG, + [ADIS16201_SCAN_INCLI_Y] = ADIS16201_YINCL_OFFS_REG, +}; + +static int adis16201_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct adis *st = iio_priv(indio_dev); + int ret; + int bits; + u8 addr; + s16 val16; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return adis_single_conversion(indio_dev, chan, + ADIS16201_ERROR_ACTIVE, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->channel == 0) { + /* Voltage base units are mV hence 1.22 mV */ + *val = 1; + *val2 = 220000; + } else { + /* Voltage base units are mV hence 0.61 mV */ + *val = 0; + *val2 = 610000; + } + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = -470; + *val2 = 0; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ACCEL: + /* + * IIO base unit for sensitivity of accelerometer + * is milli g. + * 1 LSB represents 0.244 mg. + */ + *val = 0; + *val2 = IIO_G_TO_M_S_2(462400); + return IIO_VAL_INT_PLUS_NANO; + case IIO_INCLI: + *val = 0; + *val2 = 100000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_OFFSET: + /* + * The raw ADC value is 1278 when the temperature + * is 25 degrees and the scale factor per milli + * degree Celsius is -470. + */ + *val = 25000 / -470 - 1278; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_ACCEL: + bits = 12; + break; + case IIO_INCLI: + bits = 9; + break; + default: + return -EINVAL; + } + addr = adis16201_addresses[chan->scan_index]; + ret = adis_read_reg_16(st, addr, &val16); + if (ret) + return ret; + + *val = sign_extend32(val16, bits - 1); + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int adis16201_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct adis *st = iio_priv(indio_dev); + int m; + + if (mask != IIO_CHAN_INFO_CALIBBIAS) + return -EINVAL; + + switch (chan->type) { + case IIO_ACCEL: + m = GENMASK(11, 0); + break; + case IIO_INCLI: + m = GENMASK(8, 0); + break; + default: + return -EINVAL; + } + + return adis_write_reg_16(st, adis16201_addresses[chan->scan_index], + val & m); +} + +static const struct iio_chan_spec adis16201_channels[] = { + ADIS_SUPPLY_CHAN(ADIS16201_SUPPLY_OUT_REG, ADIS16201_SCAN_SUPPLY, 0, + 12), + ADIS_TEMP_CHAN(ADIS16201_TEMP_OUT_REG, ADIS16201_SCAN_TEMP, 0, 12), + ADIS_ACCEL_CHAN(X, ADIS16201_XACCL_OUT_REG, ADIS16201_SCAN_ACC_X, + BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14), + ADIS_ACCEL_CHAN(Y, ADIS16201_YACCL_OUT_REG, ADIS16201_SCAN_ACC_Y, + BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14), + ADIS_AUX_ADC_CHAN(ADIS16201_AUX_ADC_REG, ADIS16201_SCAN_AUX_ADC, 0, 12), + ADIS_INCLI_CHAN(X, ADIS16201_XINCL_OUT_REG, ADIS16201_SCAN_INCLI_X, + BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 12), + ADIS_INCLI_CHAN(Y, ADIS16201_YINCL_OUT_REG, ADIS16201_SCAN_INCLI_Y, + BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 12), + IIO_CHAN_SOFT_TIMESTAMP(7) +}; + +static const struct iio_info adis16201_info = { + .read_raw = adis16201_read_raw, + .write_raw = adis16201_write_raw, + .update_scan_mode = adis_update_scan_mode, +}; + +static const char * const adis16201_status_error_msgs[] = { + [ADIS16201_DIAG_STAT_SPI_FAIL_BIT] = "SPI failure", + [ADIS16201_DIAG_STAT_FLASH_UPT_FAIL_BIT] = "Flash update failed", + [ADIS16201_DIAG_STAT_POWER_HIGH_BIT] = "Power supply above 3.625V", + [ADIS16201_DIAG_STAT_POWER_LOW_BIT] = "Power supply below 2.975V", +}; + +static const struct adis_timeout adis16201_timeouts = { + .reset_ms = ADIS16201_STARTUP_DELAY_MS, + .sw_reset_ms = ADIS16201_STARTUP_DELAY_MS, + .self_test_ms = ADIS16201_STARTUP_DELAY_MS, +}; + +static const struct adis_data adis16201_data = { + .read_delay = 20, + .msc_ctrl_reg = ADIS16201_MSC_CTRL_REG, + .glob_cmd_reg = ADIS16201_GLOB_CMD_REG, + .diag_stat_reg = ADIS16201_DIAG_STAT_REG, + + .self_test_mask = ADIS16201_MSC_CTRL_SELF_TEST_EN, + .self_test_reg = ADIS16201_MSC_CTRL_REG, + .self_test_no_autoclear = true, + .timeouts = &adis16201_timeouts, + + .status_error_msgs = adis16201_status_error_msgs, + .status_error_mask = BIT(ADIS16201_DIAG_STAT_SPI_FAIL_BIT) | + BIT(ADIS16201_DIAG_STAT_FLASH_UPT_FAIL_BIT) | + BIT(ADIS16201_DIAG_STAT_POWER_HIGH_BIT) | + BIT(ADIS16201_DIAG_STAT_POWER_LOW_BIT), +}; + +static int adis16201_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct adis *st; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + indio_dev->name = spi->dev.driver->name; + indio_dev->info = &adis16201_info; + + indio_dev->channels = adis16201_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16201_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = adis_init(st, indio_dev, spi, &adis16201_data); + if (ret) + return ret; + + ret = devm_adis_setup_buffer_and_trigger(st, indio_dev, NULL); + if (ret) + return ret; + + ret = __adis_initial_startup(st); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static struct spi_driver adis16201_driver = { + .driver = { + .name = "adis16201", + }, + .probe = adis16201_probe, +}; +module_spi_driver(adis16201_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADIS16201 Dual-Axis Digital Inclinometer and Accelerometer"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:adis16201"); +MODULE_IMPORT_NS("IIO_ADISLIB"); diff --git a/drivers/iio/accel/adis16209.c b/drivers/iio/accel/adis16209.c new file mode 100644 index 000000000..04e169f22 --- /dev/null +++ b/drivers/iio/accel/adis16209.c @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ADIS16209 Dual-Axis Digital Inclinometer and Accelerometer + * + * Copyright 2010 Analog Devices Inc. + */ + +#include +#include +#include +#include + +#include +#include + +#define ADIS16209_STARTUP_DELAY_MS 220 +#define ADIS16209_FLASH_CNT_REG 0x00 + +/* Data Output Register Definitions */ +#define ADIS16209_SUPPLY_OUT_REG 0x02 +#define ADIS16209_XACCL_OUT_REG 0x04 +#define ADIS16209_YACCL_OUT_REG 0x06 +/* Output, auxiliary ADC input */ +#define ADIS16209_AUX_ADC_REG 0x08 +/* Output, temperature */ +#define ADIS16209_TEMP_OUT_REG 0x0A +/* Output, +/- 90 degrees X-axis inclination */ +#define ADIS16209_XINCL_OUT_REG 0x0C +#define ADIS16209_YINCL_OUT_REG 0x0E +/* Output, +/-180 vertical rotational position */ +#define ADIS16209_ROT_OUT_REG 0x10 + +/* + * Calibration Register Definitions. + * Acceleration, inclination or rotation offset null. + */ +#define ADIS16209_XACCL_NULL_REG 0x12 +#define ADIS16209_YACCL_NULL_REG 0x14 +#define ADIS16209_XINCL_NULL_REG 0x16 +#define ADIS16209_YINCL_NULL_REG 0x18 +#define ADIS16209_ROT_NULL_REG 0x1A + +/* Alarm Register Definitions */ +#define ADIS16209_ALM_MAG1_REG 0x20 +#define ADIS16209_ALM_MAG2_REG 0x22 +#define ADIS16209_ALM_SMPL1_REG 0x24 +#define ADIS16209_ALM_SMPL2_REG 0x26 +#define ADIS16209_ALM_CTRL_REG 0x28 + +#define ADIS16209_AUX_DAC_REG 0x30 +#define ADIS16209_GPIO_CTRL_REG 0x32 +#define ADIS16209_SMPL_PRD_REG 0x36 +#define ADIS16209_AVG_CNT_REG 0x38 +#define ADIS16209_SLP_CNT_REG 0x3A + +#define ADIS16209_MSC_CTRL_REG 0x34 +#define ADIS16209_MSC_CTRL_PWRUP_SELF_TEST BIT(10) +#define ADIS16209_MSC_CTRL_SELF_TEST_EN BIT(8) +#define ADIS16209_MSC_CTRL_DATA_RDY_EN BIT(2) +/* Data-ready polarity: 1 = active high, 0 = active low */ +#define ADIS16209_MSC_CTRL_ACTIVE_HIGH BIT(1) +#define ADIS16209_MSC_CTRL_DATA_RDY_DIO2 BIT(0) + +#define ADIS16209_STAT_REG 0x3C +#define ADIS16209_STAT_ALARM2 BIT(9) +#define ADIS16209_STAT_ALARM1 BIT(8) +#define ADIS16209_STAT_SELFTEST_FAIL_BIT 5 +#define ADIS16209_STAT_SPI_FAIL_BIT 3 +#define ADIS16209_STAT_FLASH_UPT_FAIL_BIT 2 +/* Power supply above 3.625 V */ +#define ADIS16209_STAT_POWER_HIGH_BIT 1 +/* Power supply below 2.975 V */ +#define ADIS16209_STAT_POWER_LOW_BIT 0 + +#define ADIS16209_CMD_REG 0x3E +#define ADIS16209_CMD_SW_RESET BIT(7) +#define ADIS16209_CMD_CLEAR_STAT BIT(4) +#define ADIS16209_CMD_FACTORY_CAL BIT(1) + +#define ADIS16209_ERROR_ACTIVE BIT(14) + +enum adis16209_scan { + ADIS16209_SCAN_SUPPLY, + ADIS16209_SCAN_ACC_X, + ADIS16209_SCAN_ACC_Y, + ADIS16209_SCAN_AUX_ADC, + ADIS16209_SCAN_TEMP, + ADIS16209_SCAN_INCLI_X, + ADIS16209_SCAN_INCLI_Y, + ADIS16209_SCAN_ROT, +}; + +static const u8 adis16209_addresses[8][1] = { + [ADIS16209_SCAN_SUPPLY] = { }, + [ADIS16209_SCAN_AUX_ADC] = { }, + [ADIS16209_SCAN_ACC_X] = { ADIS16209_XACCL_NULL_REG }, + [ADIS16209_SCAN_ACC_Y] = { ADIS16209_YACCL_NULL_REG }, + [ADIS16209_SCAN_INCLI_X] = { ADIS16209_XINCL_NULL_REG }, + [ADIS16209_SCAN_INCLI_Y] = { ADIS16209_YINCL_NULL_REG }, + [ADIS16209_SCAN_ROT] = { }, + [ADIS16209_SCAN_TEMP] = { }, +}; + +static int adis16209_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct adis *st = iio_priv(indio_dev); + int m; + + if (mask != IIO_CHAN_INFO_CALIBBIAS) + return -EINVAL; + + switch (chan->type) { + case IIO_ACCEL: + case IIO_INCLI: + m = GENMASK(13, 0); + break; + default: + return -EINVAL; + } + + return adis_write_reg_16(st, adis16209_addresses[chan->scan_index][0], + val & m); +} + +static int adis16209_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct adis *st = iio_priv(indio_dev); + int ret; + int bits; + u8 addr; + s16 val16; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return adis_single_conversion(indio_dev, chan, + ADIS16209_ERROR_ACTIVE, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + *val = 0; + switch (chan->channel) { + case 0: + *val2 = 305180; /* 0.30518 mV */ + break; + case 1: + *val2 = 610500; /* 0.6105 mV */ + break; + default: + return -EINVAL; + } + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = -470; + *val2 = 0; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ACCEL: + /* + * IIO base unit for sensitivity of accelerometer + * is milli g. + * 1 LSB represents 0.244 mg. + */ + *val = 0; + *val2 = IIO_G_TO_M_S_2(244140); + return IIO_VAL_INT_PLUS_NANO; + case IIO_INCLI: + case IIO_ROT: + /* + * IIO base units for rotation are degrees. + * 1 LSB represents 0.025 milli degrees. + */ + *val = 0; + *val2 = 25000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_OFFSET: + /* + * The raw ADC value is 0x4FE when the temperature + * is 45 degrees and the scale factor per milli + * degree Celsius is -470. + */ + *val = 25000 / -470 - 0x4FE; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_ACCEL: + bits = 14; + break; + default: + return -EINVAL; + } + addr = adis16209_addresses[chan->scan_index][0]; + ret = adis_read_reg_16(st, addr, &val16); + if (ret) + return ret; + + *val = sign_extend32(val16, bits - 1); + return IIO_VAL_INT; + } + return -EINVAL; +} + +static const struct iio_chan_spec adis16209_channels[] = { + ADIS_SUPPLY_CHAN(ADIS16209_SUPPLY_OUT_REG, ADIS16209_SCAN_SUPPLY, + 0, 14), + ADIS_TEMP_CHAN(ADIS16209_TEMP_OUT_REG, ADIS16209_SCAN_TEMP, 0, 12), + ADIS_ACCEL_CHAN(X, ADIS16209_XACCL_OUT_REG, ADIS16209_SCAN_ACC_X, + BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14), + ADIS_ACCEL_CHAN(Y, ADIS16209_YACCL_OUT_REG, ADIS16209_SCAN_ACC_Y, + BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14), + ADIS_AUX_ADC_CHAN(ADIS16209_AUX_ADC_REG, ADIS16209_SCAN_AUX_ADC, 0, 12), + ADIS_INCLI_CHAN(X, ADIS16209_XINCL_OUT_REG, ADIS16209_SCAN_INCLI_X, + 0, 0, 14), + ADIS_INCLI_CHAN(Y, ADIS16209_YINCL_OUT_REG, ADIS16209_SCAN_INCLI_Y, + 0, 0, 14), + ADIS_ROT_CHAN(X, ADIS16209_ROT_OUT_REG, ADIS16209_SCAN_ROT, 0, 0, 14), + IIO_CHAN_SOFT_TIMESTAMP(8) +}; + +static const struct iio_info adis16209_info = { + .read_raw = adis16209_read_raw, + .write_raw = adis16209_write_raw, + .update_scan_mode = adis_update_scan_mode, +}; + +static const char * const adis16209_status_error_msgs[] = { + [ADIS16209_STAT_SELFTEST_FAIL_BIT] = "Self test failure", + [ADIS16209_STAT_SPI_FAIL_BIT] = "SPI failure", + [ADIS16209_STAT_FLASH_UPT_FAIL_BIT] = "Flash update failed", + [ADIS16209_STAT_POWER_HIGH_BIT] = "Power supply above 3.625V", + [ADIS16209_STAT_POWER_LOW_BIT] = "Power supply below 2.975V", +}; + +static const struct adis_timeout adis16209_timeouts = { + .reset_ms = ADIS16209_STARTUP_DELAY_MS, + .self_test_ms = ADIS16209_STARTUP_DELAY_MS, + .sw_reset_ms = ADIS16209_STARTUP_DELAY_MS, +}; + +static const struct adis_data adis16209_data = { + .read_delay = 30, + .msc_ctrl_reg = ADIS16209_MSC_CTRL_REG, + .glob_cmd_reg = ADIS16209_CMD_REG, + .diag_stat_reg = ADIS16209_STAT_REG, + + .self_test_mask = ADIS16209_MSC_CTRL_SELF_TEST_EN, + .self_test_reg = ADIS16209_MSC_CTRL_REG, + .self_test_no_autoclear = true, + .timeouts = &adis16209_timeouts, + + .status_error_msgs = adis16209_status_error_msgs, + .status_error_mask = BIT(ADIS16209_STAT_SELFTEST_FAIL_BIT) | + BIT(ADIS16209_STAT_SPI_FAIL_BIT) | + BIT(ADIS16209_STAT_FLASH_UPT_FAIL_BIT) | + BIT(ADIS16209_STAT_POWER_HIGH_BIT) | + BIT(ADIS16209_STAT_POWER_LOW_BIT), +}; + +static int adis16209_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct adis *st; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + indio_dev->name = spi->dev.driver->name; + indio_dev->info = &adis16209_info; + indio_dev->channels = adis16209_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16209_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = adis_init(st, indio_dev, spi, &adis16209_data); + if (ret) + return ret; + + ret = devm_adis_setup_buffer_and_trigger(st, indio_dev, NULL); + if (ret) + return ret; + + ret = __adis_initial_startup(st); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static struct spi_driver adis16209_driver = { + .driver = { + .name = "adis16209", + }, + .probe = adis16209_probe, +}; +module_spi_driver(adis16209_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADIS16209 Dual-Axis Digital Inclinometer and Accelerometer"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:adis16209"); +MODULE_IMPORT_NS("IIO_ADISLIB"); diff --git a/drivers/iio/accel/adxl313.h b/drivers/iio/accel/adxl313.h new file mode 100644 index 000000000..75ef54b60 --- /dev/null +++ b/drivers/iio/accel/adxl313.h @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * ADXL313 3-Axis Digital Accelerometer + * + * Copyright (c) 2021 Lucas Stankus + */ + +#ifndef _ADXL313_H_ +#define _ADXL313_H_ + +#include + +/* ADXL313 register definitions */ +#define ADXL313_REG_DEVID0 0x00 +#define ADXL313_REG_DEVID1 0x01 +#define ADXL313_REG_PARTID 0x02 +#define ADXL313_REG_XID 0x04 +#define ADXL313_REG_SOFT_RESET 0x18 +#define ADXL313_REG_OFS_AXIS(index) (0x1E + (index)) +#define ADXL313_REG_THRESH_ACT 0x24 +#define ADXL313_REG_THRESH_INACT 0x25 +#define ADXL313_REG_TIME_INACT 0x26 +#define ADXL313_REG_ACT_INACT_CTL 0x27 +#define ADXL313_REG_BW_RATE 0x2C +#define ADXL313_REG_POWER_CTL 0x2D +#define ADXL313_REG_INT_ENABLE 0x2E +#define ADXL313_REG_INT_MAP 0x2F +#define ADXL313_REG_INT_SOURCE 0x30 +#define ADXL313_REG_DATA_FORMAT 0x31 +#define ADXL313_REG_DATA_AXIS(index) (0x32 + ((index) * 2)) +#define ADXL313_REG_FIFO_CTL 0x38 +#define ADXL313_REG_FIFO_STATUS 0x39 + +#define ADXL313_DEVID0 0xAD +#define ADXL313_DEVID0_ADXL312_314 0xE5 +#define ADXL313_DEVID1 0x1D +#define ADXL313_PARTID 0xCB +#define ADXL313_SOFT_RESET 0x52 + +#define ADXL313_RATE_MSK GENMASK(3, 0) +#define ADXL313_RATE_BASE 6 + +#define ADXL313_POWER_CTL_MSK BIT(3) +#define ADXL313_POWER_CTL_INACT_MSK GENMASK(5, 4) +#define ADXL313_POWER_CTL_LINK BIT(5) +#define ADXL313_POWER_CTL_AUTO_SLEEP BIT(4) + +#define ADXL313_RANGE_MSK GENMASK(1, 0) +#define ADXL313_RANGE_MAX 3 + +#define ADXL313_FULL_RES BIT(3) +#define ADXL313_SPI_3WIRE BIT(6) +#define ADXL313_I2C_DISABLE BIT(6) + +#define ADXL313_INT_OVERRUN BIT(0) +#define ADXL313_INT_WATERMARK BIT(1) +#define ADXL313_INT_INACTIVITY BIT(3) +#define ADXL313_INT_ACTIVITY BIT(4) +#define ADXL313_INT_DREADY BIT(7) + +/* FIFO entries: how many values are stored in the FIFO */ +#define ADXL313_REG_FIFO_STATUS_ENTRIES_MSK GENMASK(5, 0) +/* FIFO samples: number of samples needed for watermark (FIFO mode) */ +#define ADXL313_REG_FIFO_CTL_SAMPLES_MSK GENMASK(4, 0) +#define ADXL313_REG_FIFO_CTL_MODE_MSK GENMASK(7, 6) + +#define ADXL313_FIFO_BYPASS 0 +#define ADXL313_FIFO_STREAM 2 + +#define ADXL313_FIFO_SIZE 32 + +#define ADXL313_NUM_AXIS 3 + +extern const struct regmap_access_table adxl312_readable_regs_table; +extern const struct regmap_access_table adxl313_readable_regs_table; +extern const struct regmap_access_table adxl314_readable_regs_table; + +extern const struct regmap_access_table adxl312_writable_regs_table; +extern const struct regmap_access_table adxl313_writable_regs_table; +extern const struct regmap_access_table adxl314_writable_regs_table; + +bool adxl313_is_volatile_reg(struct device *dev, unsigned int reg); + +enum adxl313_device_type { + ADXL312, + ADXL313, + ADXL314, +}; + +struct adxl313_data { + struct regmap *regmap; + const struct adxl313_chip_info *chip_info; + struct mutex lock; /* lock to protect transf_buf */ + u8 watermark; + __le16 transf_buf __aligned(IIO_DMA_MINALIGN); + __le16 fifo_buf[ADXL313_NUM_AXIS * ADXL313_FIFO_SIZE + 1]; +}; + +struct adxl313_chip_info { + const char *name; + enum adxl313_device_type type; + int scale_factor; + bool variable_range; + bool soft_reset; + int (*check_id)(struct device *dev, struct adxl313_data *data); +}; + +extern const struct adxl313_chip_info adxl31x_chip_info[]; + +int adxl313_core_probe(struct device *dev, + struct regmap *regmap, + const struct adxl313_chip_info *chip_info, + int (*setup)(struct device *, struct regmap *)); +#endif /* _ADXL313_H_ */ diff --git a/drivers/iio/accel/adxl313_core.c b/drivers/iio/accel/adxl313_core.c new file mode 100644 index 000000000..6dc918c4a --- /dev/null +++ b/drivers/iio/accel/adxl313_core.c @@ -0,0 +1,1328 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADXL313 3-Axis Digital Accelerometer + * + * Copyright (c) 2021 Lucas Stankus + * + * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADXL313.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "adxl313.h" + +#define ADXL313_INT_NONE U8_MAX +#define ADXL313_INT1 1 +#define ADXL313_INT2 2 + +#define ADXL313_REG_XYZ_BASE ADXL313_REG_DATA_AXIS(0) + +#define ADXL313_ACT_XYZ_EN GENMASK(6, 4) +#define ADXL313_INACT_XYZ_EN GENMASK(2, 0) + +#define ADXL313_REG_ACT_ACDC_MSK BIT(7) +#define ADXL313_REG_INACT_ACDC_MSK BIT(3) +#define ADXL313_COUPLING_DC 0 +#define ADXL313_COUPLING_AC 1 + +/* activity/inactivity */ +enum adxl313_activity_type { + ADXL313_ACTIVITY, + ADXL313_INACTIVITY, + ADXL313_ACTIVITY_AC, + ADXL313_INACTIVITY_AC, +}; + +static const unsigned int adxl313_act_int_reg[] = { + [ADXL313_ACTIVITY] = ADXL313_INT_ACTIVITY, + [ADXL313_INACTIVITY] = ADXL313_INT_INACTIVITY, + [ADXL313_ACTIVITY_AC] = ADXL313_INT_ACTIVITY, + [ADXL313_INACTIVITY_AC] = ADXL313_INT_INACTIVITY, +}; + +static const unsigned int adxl313_act_thresh_reg[] = { + [ADXL313_ACTIVITY] = ADXL313_REG_THRESH_ACT, + [ADXL313_INACTIVITY] = ADXL313_REG_THRESH_INACT, + [ADXL313_ACTIVITY_AC] = ADXL313_REG_THRESH_ACT, + [ADXL313_INACTIVITY_AC] = ADXL313_REG_THRESH_INACT, +}; + +static const unsigned int adxl313_act_acdc_msk[] = { + [ADXL313_ACTIVITY] = ADXL313_REG_ACT_ACDC_MSK, + [ADXL313_INACTIVITY] = ADXL313_REG_INACT_ACDC_MSK, + [ADXL313_ACTIVITY_AC] = ADXL313_REG_ACT_ACDC_MSK, + [ADXL313_INACTIVITY_AC] = ADXL313_REG_INACT_ACDC_MSK, +}; + +static const struct regmap_range adxl312_readable_reg_range[] = { + regmap_reg_range(ADXL313_REG_DEVID0, ADXL313_REG_DEVID0), + regmap_reg_range(ADXL313_REG_OFS_AXIS(0), ADXL313_REG_OFS_AXIS(2)), + regmap_reg_range(ADXL313_REG_THRESH_ACT, ADXL313_REG_ACT_INACT_CTL), + regmap_reg_range(ADXL313_REG_BW_RATE, ADXL313_REG_FIFO_STATUS), +}; + +static const struct regmap_range adxl313_readable_reg_range[] = { + regmap_reg_range(ADXL313_REG_DEVID0, ADXL313_REG_XID), + regmap_reg_range(ADXL313_REG_SOFT_RESET, ADXL313_REG_SOFT_RESET), + regmap_reg_range(ADXL313_REG_OFS_AXIS(0), ADXL313_REG_OFS_AXIS(2)), + regmap_reg_range(ADXL313_REG_THRESH_ACT, ADXL313_REG_ACT_INACT_CTL), + regmap_reg_range(ADXL313_REG_BW_RATE, ADXL313_REG_FIFO_STATUS), +}; + +const struct regmap_access_table adxl312_readable_regs_table = { + .yes_ranges = adxl312_readable_reg_range, + .n_yes_ranges = ARRAY_SIZE(adxl312_readable_reg_range), +}; +EXPORT_SYMBOL_NS_GPL(adxl312_readable_regs_table, "IIO_ADXL313"); + +const struct regmap_access_table adxl313_readable_regs_table = { + .yes_ranges = adxl313_readable_reg_range, + .n_yes_ranges = ARRAY_SIZE(adxl313_readable_reg_range), +}; +EXPORT_SYMBOL_NS_GPL(adxl313_readable_regs_table, "IIO_ADXL313"); + +const struct regmap_access_table adxl314_readable_regs_table = { + .yes_ranges = adxl312_readable_reg_range, + .n_yes_ranges = ARRAY_SIZE(adxl312_readable_reg_range), +}; +EXPORT_SYMBOL_NS_GPL(adxl314_readable_regs_table, "IIO_ADXL313"); + +bool adxl313_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ADXL313_REG_DATA_AXIS(0): + case ADXL313_REG_DATA_AXIS(1): + case ADXL313_REG_DATA_AXIS(2): + case ADXL313_REG_DATA_AXIS(3): + case ADXL313_REG_DATA_AXIS(4): + case ADXL313_REG_DATA_AXIS(5): + case ADXL313_REG_FIFO_STATUS: + case ADXL313_REG_INT_SOURCE: + return true; + default: + return false; + } +} +EXPORT_SYMBOL_NS_GPL(adxl313_is_volatile_reg, "IIO_ADXL313"); + +static int adxl313_set_measure_en(struct adxl313_data *data, bool en) +{ + return regmap_assign_bits(data->regmap, ADXL313_REG_POWER_CTL, + ADXL313_POWER_CTL_MSK, en); +} + +static int adxl312_check_id(struct device *dev, + struct adxl313_data *data) +{ + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, ADXL313_REG_DEVID0, ®val); + if (ret) + return ret; + + if (regval != ADXL313_DEVID0_ADXL312_314) + dev_warn(dev, "Invalid manufacturer ID: %#02x\n", regval); + + return 0; +} + +static int adxl313_check_id(struct device *dev, + struct adxl313_data *data) +{ + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, ADXL313_REG_DEVID0, ®val); + if (ret) + return ret; + + if (regval != ADXL313_DEVID0) + dev_warn(dev, "Invalid manufacturer ID: 0x%02x\n", regval); + + /* Check DEVID1 and PARTID */ + if (regval == ADXL313_DEVID0) { + ret = regmap_read(data->regmap, ADXL313_REG_DEVID1, ®val); + if (ret) + return ret; + + if (regval != ADXL313_DEVID1) + dev_warn(dev, "Invalid mems ID: 0x%02x\n", regval); + + ret = regmap_read(data->regmap, ADXL313_REG_PARTID, ®val); + if (ret) + return ret; + + if (regval != ADXL313_PARTID) + dev_warn(dev, "Invalid device ID: 0x%02x\n", regval); + } + + return 0; +} + +const struct adxl313_chip_info adxl31x_chip_info[] = { + [ADXL312] = { + .name = "adxl312", + .type = ADXL312, + .scale_factor = 28425072, + .variable_range = true, + .soft_reset = false, + .check_id = &adxl312_check_id, + }, + [ADXL313] = { + .name = "adxl313", + .type = ADXL313, + .scale_factor = 9576806, + .variable_range = true, + .soft_reset = true, + .check_id = &adxl313_check_id, + }, + [ADXL314] = { + .name = "adxl314", + .type = ADXL314, + .scale_factor = 478858719, + .variable_range = false, + .soft_reset = false, + .check_id = &adxl312_check_id, + }, +}; +EXPORT_SYMBOL_NS_GPL(adxl31x_chip_info, "IIO_ADXL313"); + +static const struct regmap_range adxl312_writable_reg_range[] = { + regmap_reg_range(ADXL313_REG_OFS_AXIS(0), ADXL313_REG_OFS_AXIS(2)), + regmap_reg_range(ADXL313_REG_THRESH_ACT, ADXL313_REG_ACT_INACT_CTL), + regmap_reg_range(ADXL313_REG_BW_RATE, ADXL313_REG_INT_MAP), + regmap_reg_range(ADXL313_REG_DATA_FORMAT, ADXL313_REG_DATA_FORMAT), + regmap_reg_range(ADXL313_REG_FIFO_CTL, ADXL313_REG_FIFO_CTL), +}; + +static const struct regmap_range adxl313_writable_reg_range[] = { + regmap_reg_range(ADXL313_REG_SOFT_RESET, ADXL313_REG_SOFT_RESET), + regmap_reg_range(ADXL313_REG_OFS_AXIS(0), ADXL313_REG_OFS_AXIS(2)), + regmap_reg_range(ADXL313_REG_THRESH_ACT, ADXL313_REG_ACT_INACT_CTL), + regmap_reg_range(ADXL313_REG_BW_RATE, ADXL313_REG_INT_MAP), + regmap_reg_range(ADXL313_REG_DATA_FORMAT, ADXL313_REG_DATA_FORMAT), + regmap_reg_range(ADXL313_REG_FIFO_CTL, ADXL313_REG_FIFO_CTL), +}; + +const struct regmap_access_table adxl312_writable_regs_table = { + .yes_ranges = adxl312_writable_reg_range, + .n_yes_ranges = ARRAY_SIZE(adxl312_writable_reg_range), +}; +EXPORT_SYMBOL_NS_GPL(adxl312_writable_regs_table, "IIO_ADXL313"); + +const struct regmap_access_table adxl313_writable_regs_table = { + .yes_ranges = adxl313_writable_reg_range, + .n_yes_ranges = ARRAY_SIZE(adxl313_writable_reg_range), +}; +EXPORT_SYMBOL_NS_GPL(adxl313_writable_regs_table, "IIO_ADXL313"); + +const struct regmap_access_table adxl314_writable_regs_table = { + .yes_ranges = adxl312_writable_reg_range, + .n_yes_ranges = ARRAY_SIZE(adxl312_writable_reg_range), +}; +EXPORT_SYMBOL_NS_GPL(adxl314_writable_regs_table, "IIO_ADXL313"); + +static const int adxl313_odr_freqs[][2] = { + [0] = { 6, 250000 }, + [1] = { 12, 500000 }, + [2] = { 25, 0 }, + [3] = { 50, 0 }, + [4] = { 100, 0 }, + [5] = { 200, 0 }, + [6] = { 400, 0 }, + [7] = { 800, 0 }, + [8] = { 1600, 0 }, + [9] = { 3200, 0 }, +}; + +#define ADXL313_ACCEL_CHANNEL(index, reg, axis) { \ + .type = IIO_ACCEL, \ + .scan_index = (index), \ + .address = (reg), \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_type = { \ + .sign = 's', \ + .realbits = 13, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ +} + +static const struct iio_event_spec adxl313_activity_events[] = { + { + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE), + }, + { + /* activity, AC bit set */ + .type = IIO_EV_TYPE_MAG_ADAPTIVE, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE), + }, +}; + +static const struct iio_event_spec adxl313_inactivity_events[] = { + { + /* inactivity */ + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD), + }, + { + /* inactivity, AC bit set */ + .type = IIO_EV_TYPE_MAG_ADAPTIVE, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD), + }, +}; + +enum adxl313_chans { + chan_x, chan_y, chan_z, +}; + +static const struct iio_chan_spec adxl313_channels[] = { + ADXL313_ACCEL_CHANNEL(0, chan_x, X), + ADXL313_ACCEL_CHANNEL(1, chan_y, Y), + ADXL313_ACCEL_CHANNEL(2, chan_z, Z), + { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_X_OR_Y_OR_Z, + .scan_index = -1, /* Fake channel for axis OR'ing */ + .event_spec = adxl313_activity_events, + .num_event_specs = ARRAY_SIZE(adxl313_activity_events), + }, + { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_X_AND_Y_AND_Z, + .scan_index = -1, /* Fake channel for axis AND'ing */ + .event_spec = adxl313_inactivity_events, + .num_event_specs = ARRAY_SIZE(adxl313_inactivity_events), + }, +}; + +static const unsigned long adxl313_scan_masks[] = { + BIT(chan_x) | BIT(chan_y) | BIT(chan_z), + 0 +}; + +static int adxl313_set_odr(struct adxl313_data *data, + unsigned int freq1, unsigned int freq2) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(adxl313_odr_freqs); i++) { + if (adxl313_odr_freqs[i][0] == freq1 && + adxl313_odr_freqs[i][1] == freq2) + break; + } + + if (i == ARRAY_SIZE(adxl313_odr_freqs)) + return -EINVAL; + + return regmap_update_bits(data->regmap, ADXL313_REG_BW_RATE, + ADXL313_RATE_MSK, + FIELD_PREP(ADXL313_RATE_MSK, ADXL313_RATE_BASE + i)); +} + +static int adxl313_read_axis(struct adxl313_data *data, + struct iio_chan_spec const *chan) +{ + int ret; + + guard(mutex)(&data->lock); + + ret = regmap_bulk_read(data->regmap, + ADXL313_REG_DATA_AXIS(chan->address), + &data->transf_buf, sizeof(data->transf_buf)); + if (ret) + return ret; + + return le16_to_cpu(data->transf_buf); +} + +static int adxl313_read_freq_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = (const int *)adxl313_odr_freqs; + *length = ARRAY_SIZE(adxl313_odr_freqs) * 2; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int adxl313_set_inact_time_s(struct adxl313_data *data, + unsigned int val_s) +{ + unsigned int max_boundary = U8_MAX; /* by register size */ + unsigned int val = min(val_s, max_boundary); + + return regmap_write(data->regmap, ADXL313_REG_TIME_INACT, val); +} + +/** + * adxl313_is_act_inact_ac() - Check if AC coupling is enabled. + * @data: The device data. + * @type: The activity or inactivity type. + * + * Provide a type of activity or inactivity, combined with either AC coupling + * set, or default to DC coupling. This function verifies if the combination is + * currently enabled or not. + * + * Return: if the provided activity type has AC coupling enabled or a negative + * error value. + */ +static int adxl313_is_act_inact_ac(struct adxl313_data *data, + enum adxl313_activity_type type) +{ + unsigned int regval; + bool coupling; + int ret; + + ret = regmap_read(data->regmap, ADXL313_REG_ACT_INACT_CTL, ®val); + if (ret) + return ret; + + coupling = adxl313_act_acdc_msk[type] & regval; + + switch (type) { + case ADXL313_ACTIVITY: + case ADXL313_INACTIVITY: + return coupling == ADXL313_COUPLING_DC; + case ADXL313_ACTIVITY_AC: + case ADXL313_INACTIVITY_AC: + return coupling == ADXL313_COUPLING_AC; + default: + return -EINVAL; + } +} + +static int adxl313_set_act_inact_ac(struct adxl313_data *data, + enum adxl313_activity_type type, + bool cmd_en) +{ + unsigned int act_inact_ac; + + switch (type) { + case ADXL313_ACTIVITY_AC: + case ADXL313_INACTIVITY_AC: + act_inact_ac = ADXL313_COUPLING_AC && cmd_en; + break; + case ADXL313_ACTIVITY: + case ADXL313_INACTIVITY: + act_inact_ac = ADXL313_COUPLING_DC && cmd_en; + break; + default: + return -EINVAL; + } + + return regmap_assign_bits(data->regmap, ADXL313_REG_ACT_INACT_CTL, + adxl313_act_acdc_msk[type], act_inact_ac); +} + +static int adxl313_is_act_inact_en(struct adxl313_data *data, + enum adxl313_activity_type type) +{ + unsigned int axis_ctrl; + unsigned int regval; + bool int_en; + int ret; + + ret = regmap_read(data->regmap, ADXL313_REG_ACT_INACT_CTL, &axis_ctrl); + if (ret) + return ret; + + /* Check if axis for activity are enabled */ + switch (type) { + case ADXL313_ACTIVITY: + case ADXL313_ACTIVITY_AC: + if (!FIELD_GET(ADXL313_ACT_XYZ_EN, axis_ctrl)) + return false; + break; + case ADXL313_INACTIVITY: + case ADXL313_INACTIVITY_AC: + if (!FIELD_GET(ADXL313_INACT_XYZ_EN, axis_ctrl)) + return false; + break; + default: + return -EINVAL; + } + + /* Check if specific interrupt is enabled */ + ret = regmap_read(data->regmap, ADXL313_REG_INT_ENABLE, ®val); + if (ret) + return ret; + + int_en = adxl313_act_int_reg[type] & regval; + if (!int_en) + return false; + + /* Check if configured coupling matches provided type */ + return adxl313_is_act_inact_ac(data, type); +} + +static int adxl313_set_act_inact_linkbit(struct adxl313_data *data, bool en) +{ + int act_ac_en, inact_ac_en; + int act_en, inact_en; + + act_en = adxl313_is_act_inact_en(data, ADXL313_ACTIVITY); + if (act_en < 0) + return act_en; + + act_ac_en = adxl313_is_act_inact_en(data, ADXL313_ACTIVITY_AC); + if (act_ac_en < 0) + return act_ac_en; + + inact_en = adxl313_is_act_inact_en(data, ADXL313_INACTIVITY); + if (inact_en < 0) + return inact_en; + + inact_ac_en = adxl313_is_act_inact_en(data, ADXL313_INACTIVITY_AC); + if (inact_ac_en < 0) + return inact_ac_en; + + act_en = act_en || act_ac_en; + + inact_en = inact_en || inact_ac_en; + + return regmap_assign_bits(data->regmap, ADXL313_REG_POWER_CTL, + ADXL313_POWER_CTL_AUTO_SLEEP | ADXL313_POWER_CTL_LINK, + en && act_en && inact_en); +} + +static int adxl313_set_act_inact_en(struct adxl313_data *data, + enum adxl313_activity_type type, + bool cmd_en) +{ + unsigned int axis_ctrl; + unsigned int threshold; + unsigned int inact_time_s; + int ret; + + if (cmd_en) { + /* When turning on, check if threshold is valid */ + ret = regmap_read(data->regmap, adxl313_act_thresh_reg[type], + &threshold); + if (ret) + return ret; + + if (!threshold) /* Just ignore the command if threshold is 0 */ + return 0; + + /* When turning on inactivity, check if inact time is valid */ + if (type == ADXL313_INACTIVITY || type == ADXL313_INACTIVITY_AC) { + ret = regmap_read(data->regmap, + ADXL313_REG_TIME_INACT, + &inact_time_s); + if (ret) + return ret; + + if (!inact_time_s) + return 0; + } + } else { + /* + * When turning off an activity, ensure that the correct + * coupling event is specified. This step helps prevent misuse - + * for example, if an AC-coupled activity is active and the + * current call attempts to turn off a DC-coupled activity, this + * inconsistency should be detected here. + */ + if (adxl313_is_act_inact_ac(data, type) <= 0) + return 0; + } + + /* Start modifying configuration registers */ + ret = adxl313_set_measure_en(data, false); + if (ret) + return ret; + + /* Enable axis according to the command */ + switch (type) { + case ADXL313_ACTIVITY: + case ADXL313_ACTIVITY_AC: + axis_ctrl = ADXL313_ACT_XYZ_EN; + break; + case ADXL313_INACTIVITY: + case ADXL313_INACTIVITY_AC: + axis_ctrl = ADXL313_INACT_XYZ_EN; + break; + default: + return -EINVAL; + } + ret = regmap_assign_bits(data->regmap, ADXL313_REG_ACT_INACT_CTL, + axis_ctrl, cmd_en); + if (ret) + return ret; + + /* Update AC/DC-coupling according to the command */ + ret = adxl313_set_act_inact_ac(data, type, cmd_en); + if (ret) + return ret; + + /* Enable the interrupt line, according to the command */ + ret = regmap_assign_bits(data->regmap, ADXL313_REG_INT_ENABLE, + adxl313_act_int_reg[type], cmd_en); + if (ret) + return ret; + + /* Set link-bit and auto-sleep only when ACT and INACT are enabled */ + ret = adxl313_set_act_inact_linkbit(data, cmd_en); + if (ret) + return ret; + + return adxl313_set_measure_en(data, true); +} + +static int adxl313_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct adxl313_data *data = iio_priv(indio_dev); + unsigned int regval; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = adxl313_read_axis(data, chan); + if (ret < 0) + return ret; + + *val = sign_extend32(ret, chan->scan_type.realbits - 1); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + + *val2 = data->chip_info->scale_factor; + + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_CALIBBIAS: + ret = regmap_read(data->regmap, + ADXL313_REG_OFS_AXIS(chan->address), ®val); + if (ret) + return ret; + + /* + * 8-bit resolution at minimum range, that is 4x accel data scale + * factor at full resolution + */ + *val = sign_extend32(regval, 7) * 4; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = regmap_read(data->regmap, ADXL313_REG_BW_RATE, ®val); + if (ret) + return ret; + + ret = FIELD_GET(ADXL313_RATE_MSK, regval) - ADXL313_RATE_BASE; + *val = adxl313_odr_freqs[ret][0]; + *val2 = adxl313_odr_freqs[ret][1]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int adxl313_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct adxl313_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + /* + * 8-bit resolution at minimum range, that is 4x accel data scale + * factor at full resolution + */ + if (clamp_val(val, -128 * 4, 127 * 4) != val) + return -EINVAL; + + return regmap_write(data->regmap, + ADXL313_REG_OFS_AXIS(chan->address), + val / 4); + case IIO_CHAN_INFO_SAMP_FREQ: + return adxl313_set_odr(data, val, val2); + default: + return -EINVAL; + } +} + +static int adxl313_read_mag_config(struct adxl313_data *data, + enum iio_event_direction dir, + enum adxl313_activity_type type_act, + enum adxl313_activity_type type_inact) +{ + switch (dir) { + case IIO_EV_DIR_RISING: + return !!adxl313_is_act_inact_en(data, type_act); + case IIO_EV_DIR_FALLING: + return !!adxl313_is_act_inact_en(data, type_inact); + default: + return -EINVAL; + } +} + +static int adxl313_write_mag_config(struct adxl313_data *data, + enum iio_event_direction dir, + enum adxl313_activity_type type_act, + enum adxl313_activity_type type_inact, + bool state) +{ + switch (dir) { + case IIO_EV_DIR_RISING: + return adxl313_set_act_inact_en(data, type_act, state); + case IIO_EV_DIR_FALLING: + return adxl313_set_act_inact_en(data, type_inact, state); + default: + return -EINVAL; + } +} + +static int adxl313_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct adxl313_data *data = iio_priv(indio_dev); + + switch (type) { + case IIO_EV_TYPE_MAG: + return adxl313_read_mag_config(data, dir, + ADXL313_ACTIVITY, + ADXL313_INACTIVITY); + case IIO_EV_TYPE_MAG_ADAPTIVE: + return adxl313_read_mag_config(data, dir, + ADXL313_ACTIVITY_AC, + ADXL313_INACTIVITY_AC); + default: + return -EINVAL; + } +} + +static int adxl313_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct adxl313_data *data = iio_priv(indio_dev); + + switch (type) { + case IIO_EV_TYPE_MAG: + return adxl313_write_mag_config(data, dir, + ADXL313_ACTIVITY, + ADXL313_INACTIVITY, + state); + case IIO_EV_TYPE_MAG_ADAPTIVE: + return adxl313_write_mag_config(data, dir, + ADXL313_ACTIVITY_AC, + ADXL313_INACTIVITY_AC, + state); + default: + return -EINVAL; + } +} + +static int adxl313_read_mag_value(struct adxl313_data *data, + enum iio_event_direction dir, + enum iio_event_info info, + enum adxl313_activity_type type_act, + enum adxl313_activity_type type_inact, + int *val, int *val2) +{ + unsigned int threshold; + unsigned int period; + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + ret = regmap_read(data->regmap, + adxl313_act_thresh_reg[type_act], + &threshold); + if (ret) + return ret; + *val = threshold * 15625; + *val2 = MICRO; + return IIO_VAL_FRACTIONAL; + case IIO_EV_DIR_FALLING: + ret = regmap_read(data->regmap, + adxl313_act_thresh_reg[type_inact], + &threshold); + if (ret) + return ret; + *val = threshold * 15625; + *val2 = MICRO; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_EV_INFO_PERIOD: + ret = regmap_read(data->regmap, ADXL313_REG_TIME_INACT, + &period); + if (ret) + return ret; + *val = period; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int adxl313_write_mag_value(struct adxl313_data *data, + enum iio_event_direction dir, + enum iio_event_info info, + enum adxl313_activity_type type_act, + enum adxl313_activity_type type_inact, + int val, int val2) +{ + unsigned int regval; + + switch (info) { + case IIO_EV_INFO_VALUE: + /* Scale factor 15.625 mg/LSB */ + regval = DIV_ROUND_CLOSEST(MICRO * val + val2, 15625); + switch (dir) { + case IIO_EV_DIR_RISING: + return regmap_write(data->regmap, + adxl313_act_thresh_reg[type_act], + regval); + case IIO_EV_DIR_FALLING: + return regmap_write(data->regmap, + adxl313_act_thresh_reg[type_inact], + regval); + default: + return -EINVAL; + } + case IIO_EV_INFO_PERIOD: + return adxl313_set_inact_time_s(data, val); + default: + return -EINVAL; + } +} + +static int adxl313_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct adxl313_data *data = iio_priv(indio_dev); + + switch (type) { + case IIO_EV_TYPE_MAG: + return adxl313_read_mag_value(data, dir, info, + ADXL313_ACTIVITY, + ADXL313_INACTIVITY, + val, val2); + case IIO_EV_TYPE_MAG_ADAPTIVE: + return adxl313_read_mag_value(data, dir, info, + ADXL313_ACTIVITY_AC, + ADXL313_INACTIVITY_AC, + val, val2); + default: + return -EINVAL; + } +} + +static int adxl313_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct adxl313_data *data = iio_priv(indio_dev); + + switch (type) { + case IIO_EV_TYPE_MAG: + return adxl313_write_mag_value(data, dir, info, + ADXL313_ACTIVITY, + ADXL313_INACTIVITY, + val, val2); + case IIO_EV_TYPE_MAG_ADAPTIVE: + return adxl313_write_mag_value(data, dir, info, + ADXL313_ACTIVITY_AC, + ADXL313_INACTIVITY_AC, + val, val2); + default: + return -EINVAL; + } +} + +static int adxl313_set_watermark(struct iio_dev *indio_dev, unsigned int value) +{ + struct adxl313_data *data = iio_priv(indio_dev); + int ret; + + value = min(value, ADXL313_FIFO_SIZE - 1); + + ret = adxl313_set_measure_en(data, false); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, ADXL313_REG_FIFO_CTL, + ADXL313_REG_FIFO_CTL_MODE_MSK, value); + if (ret) + return ret; + + data->watermark = value; + + ret = regmap_set_bits(data->regmap, ADXL313_REG_INT_ENABLE, + ADXL313_INT_WATERMARK); + if (ret) + return ret; + + return adxl313_set_measure_en(data, true); +} + +static int adxl313_get_samples(struct adxl313_data *data) +{ + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, ADXL313_REG_FIFO_STATUS, ®val); + if (ret) + return ret; + + return FIELD_GET(ADXL313_REG_FIFO_STATUS_ENTRIES_MSK, regval); +} + +static int adxl313_fifo_transfer(struct adxl313_data *data, int samples) +{ + unsigned int i; + int ret; + + for (i = 0; i < samples; i++) { + ret = regmap_bulk_read(data->regmap, ADXL313_REG_XYZ_BASE, + data->fifo_buf + (i * ADXL313_NUM_AXIS), + sizeof(data->fifo_buf[0]) * ADXL313_NUM_AXIS); + if (ret) + return ret; + } + + return 0; +} + +/** + * adxl313_fifo_reset() - Reset the FIFO and interrupt status registers. + * @data: The device data. + * + * Reset the FIFO status registers. Reading out status registers clears the + * FIFO and interrupt configuration. Thus do not evaluate regmap return values. + * Ignore particular read register content. Register content is not processed + * any further. Therefore the function returns void. + */ +static void adxl313_fifo_reset(struct adxl313_data *data) +{ + unsigned int regval; + int samples; + + adxl313_set_measure_en(data, false); + + samples = adxl313_get_samples(data); + if (samples > 0) + adxl313_fifo_transfer(data, samples); + + regmap_read(data->regmap, ADXL313_REG_INT_SOURCE, ®val); + + adxl313_set_measure_en(data, true); +} + +static int adxl313_buffer_postenable(struct iio_dev *indio_dev) +{ + struct adxl313_data *data = iio_priv(indio_dev); + int ret; + + /* Set FIFO modes with measurement turned off, according to datasheet */ + ret = adxl313_set_measure_en(data, false); + if (ret) + return ret; + + ret = regmap_write(data->regmap, ADXL313_REG_FIFO_CTL, + FIELD_PREP(ADXL313_REG_FIFO_CTL_SAMPLES_MSK, data->watermark) | + FIELD_PREP(ADXL313_REG_FIFO_CTL_MODE_MSK, ADXL313_FIFO_STREAM)); + if (ret) + return ret; + + return adxl313_set_measure_en(data, true); +} + +static int adxl313_buffer_predisable(struct iio_dev *indio_dev) +{ + struct adxl313_data *data = iio_priv(indio_dev); + int ret; + + ret = adxl313_set_measure_en(data, false); + if (ret) + return ret; + + ret = regmap_write(data->regmap, ADXL313_REG_FIFO_CTL, + FIELD_PREP(ADXL313_REG_FIFO_CTL_MODE_MSK, ADXL313_FIFO_BYPASS)); + if (ret) + return ret; + + ret = regmap_write(data->regmap, ADXL313_REG_INT_ENABLE, 0); + if (ret) + return ret; + + return adxl313_set_measure_en(data, true); +} + +static const struct iio_buffer_setup_ops adxl313_buffer_ops = { + .postenable = adxl313_buffer_postenable, + .predisable = adxl313_buffer_predisable, +}; + +static int adxl313_fifo_push(struct iio_dev *indio_dev, int samples) +{ + struct adxl313_data *data = iio_priv(indio_dev); + unsigned int i; + int ret; + + ret = adxl313_fifo_transfer(data, samples); + if (ret) + return ret; + + for (i = 0; i < ADXL313_NUM_AXIS * samples; i += ADXL313_NUM_AXIS) + iio_push_to_buffers(indio_dev, &data->fifo_buf[i]); + + return 0; +} + +static int adxl313_push_events(struct iio_dev *indio_dev, int int_stat) +{ + s64 ts = iio_get_time_ns(indio_dev); + struct adxl313_data *data = iio_priv(indio_dev); + unsigned int regval; + int ret = -ENOENT; + + if (FIELD_GET(ADXL313_INT_ACTIVITY, int_stat)) { + ret = regmap_read(data->regmap, ADXL313_REG_ACT_INACT_CTL, ®val); + if (ret) + return ret; + + if (FIELD_GET(ADXL313_REG_ACT_ACDC_MSK, regval)) { + /* AC coupled */ + ret = iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_MAG_ADAPTIVE, + IIO_EV_DIR_RISING), + ts); + if (ret) + return ret; + } else { + /* DC coupled, relying on THRESH */ + ret = iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_RISING), + ts); + if (ret) + return ret; + } + } + + if (FIELD_GET(ADXL313_INT_INACTIVITY, int_stat)) { + ret = regmap_read(data->regmap, ADXL313_REG_ACT_INACT_CTL, ®val); + if (ret) + return ret; + + if (FIELD_GET(ADXL313_REG_INACT_ACDC_MSK, regval)) { + /* AC coupled */ + ret = iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_AND_Y_AND_Z, + IIO_EV_TYPE_MAG_ADAPTIVE, + IIO_EV_DIR_FALLING), + ts); + if (ret) + return ret; + } else { + /* DC coupled, relying on THRESH */ + ret = iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_AND_Y_AND_Z, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_FALLING), + ts); + if (ret) + return ret; + } + } + + return ret; +} + +static irqreturn_t adxl313_irq_handler(int irq, void *p) +{ + struct iio_dev *indio_dev = p; + struct adxl313_data *data = iio_priv(indio_dev); + int samples, int_stat; + + if (regmap_read(data->regmap, ADXL313_REG_INT_SOURCE, &int_stat)) + return IRQ_NONE; + + /* + * In cases of sensor events not handled (still not implemented) by + * this driver, the FIFO needs to be drained to become operational + * again. In general the sensor configuration only should issue events + * which were configured by this driver. Anyway a miss-configuration + * easily might end up in a hanging sensor FIFO. + */ + if (adxl313_push_events(indio_dev, int_stat)) + goto err_reset_fifo; + + if (FIELD_GET(ADXL313_INT_WATERMARK, int_stat)) { + samples = adxl313_get_samples(data); + if (samples < 0) + goto err_reset_fifo; + + if (adxl313_fifo_push(indio_dev, samples)) + goto err_reset_fifo; + } + + if (FIELD_GET(ADXL313_INT_OVERRUN, int_stat)) + goto err_reset_fifo; + + return IRQ_HANDLED; + +err_reset_fifo: + adxl313_fifo_reset(data); + + return IRQ_HANDLED; +} + +static int adxl313_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct adxl313_data *data = iio_priv(indio_dev); + + if (readval) + return regmap_read(data->regmap, reg, readval); + return regmap_write(data->regmap, reg, writeval); +} + +static const struct iio_info adxl313_info = { + .read_raw = adxl313_read_raw, + .write_raw = adxl313_write_raw, + .read_event_config = adxl313_read_event_config, + .write_event_config = adxl313_write_event_config, + .read_event_value = adxl313_read_event_value, + .write_event_value = adxl313_write_event_value, + .read_avail = adxl313_read_freq_avail, + .hwfifo_set_watermark = adxl313_set_watermark, + .debugfs_reg_access = &adxl313_reg_access, +}; + +static int adxl313_setup(struct device *dev, struct adxl313_data *data, + int (*setup)(struct device *, struct regmap *)) +{ + int ret; + + /* + * If sw reset available, ensures the device is in a consistent + * state after start up + */ + if (data->chip_info->soft_reset) { + ret = regmap_write(data->regmap, ADXL313_REG_SOFT_RESET, + ADXL313_SOFT_RESET); + if (ret) + return ret; + } + + if (setup) { + ret = setup(dev, data->regmap); + if (ret) + return ret; + } + + ret = data->chip_info->check_id(dev, data); + if (ret) + return ret; + + /* Sets the range to maximum, full resolution, if applicable */ + if (data->chip_info->variable_range) { + ret = regmap_update_bits(data->regmap, ADXL313_REG_DATA_FORMAT, + ADXL313_RANGE_MSK, + FIELD_PREP(ADXL313_RANGE_MSK, ADXL313_RANGE_MAX)); + if (ret) + return ret; + + /* Enables full resolution */ + ret = regmap_update_bits(data->regmap, ADXL313_REG_DATA_FORMAT, + ADXL313_FULL_RES, ADXL313_FULL_RES); + if (ret) + return ret; + } + + /* Enables measurement mode */ + return adxl313_set_measure_en(data, true); +} + +static unsigned int adxl313_get_int_type(struct device *dev, int *irq) +{ + *irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT1"); + if (*irq > 0) + return ADXL313_INT1; + + *irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT2"); + if (*irq > 0) + return ADXL313_INT2; + + return ADXL313_INT_NONE; +} + +/** + * adxl313_core_probe() - probe and setup for adxl313 accelerometer + * @dev: Driver model representation of the device + * @regmap: Register map of the device + * @chip_info: Structure containing device specific data + * @setup: Setup routine to be executed right before the standard device + * setup, can also be set to NULL if not required + * + * Return: 0 on success, negative errno on error cases + */ +int adxl313_core_probe(struct device *dev, + struct regmap *regmap, + const struct adxl313_chip_info *chip_info, + int (*setup)(struct device *, struct regmap *)) +{ + struct adxl313_data *data; + struct iio_dev *indio_dev; + u8 int_line; + u8 int_map_msk; + int irq, ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->regmap = regmap; + data->chip_info = chip_info; + + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; + + indio_dev->name = chip_info->name; + indio_dev->info = &adxl313_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = adxl313_channels; + indio_dev->num_channels = ARRAY_SIZE(adxl313_channels); + indio_dev->available_scan_masks = adxl313_scan_masks; + + ret = adxl313_setup(dev, data, setup); + if (ret) + return dev_err_probe(dev, ret, "ADXL313 setup failed\n"); + + int_line = adxl313_get_int_type(dev, &irq); + if (int_line == ADXL313_INT_NONE) { + /* + * FIFO_BYPASSED mode + * + * When no interrupt lines are specified, the driver falls back + * to use the sensor in FIFO_BYPASS mode. This means turning off + * internal FIFO and interrupt generation (since there is no + * line specified). Unmaskable interrupts such as overrun or + * data ready won't interfere. Even that a FIFO_STREAM mode w/o + * connected interrupt line might allow for obtaining raw + * measurements, a fallback to disable interrupts when no + * interrupt lines are connected seems to be the cleaner + * solution. + */ + ret = regmap_write(data->regmap, ADXL313_REG_FIFO_CTL, + FIELD_PREP(ADXL313_REG_FIFO_CTL_MODE_MSK, + ADXL313_FIFO_BYPASS)); + if (ret) + return ret; + } else { + /* FIFO_STREAM mode */ + int_map_msk = ADXL313_INT_DREADY | ADXL313_INT_ACTIVITY | + ADXL313_INT_INACTIVITY | ADXL313_INT_WATERMARK | + ADXL313_INT_OVERRUN; + ret = regmap_assign_bits(data->regmap, ADXL313_REG_INT_MAP, + int_map_msk, int_line == ADXL313_INT2); + if (ret) + return ret; + + /* + * Reset or configure the registers with reasonable default + * values. As having 0 in most cases may result in undesirable + * behavior if the interrupts are enabled. + */ + ret = regmap_write(data->regmap, ADXL313_REG_ACT_INACT_CTL, 0x00); + if (ret) + return ret; + + ret = regmap_write(data->regmap, ADXL313_REG_TIME_INACT, 5); + if (ret) + return ret; + + ret = regmap_write(data->regmap, ADXL313_REG_THRESH_INACT, 0x4f); + if (ret) + return ret; + + ret = regmap_write(data->regmap, ADXL313_REG_THRESH_ACT, 0x52); + if (ret) + return ret; + + ret = devm_iio_kfifo_buffer_setup(dev, indio_dev, + &adxl313_buffer_ops); + if (ret) + return ret; + + ret = devm_request_threaded_irq(dev, irq, NULL, + &adxl313_irq_handler, + IRQF_SHARED | IRQF_ONESHOT, + indio_dev->name, indio_dev); + if (ret) + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS_GPL(adxl313_core_probe, "IIO_ADXL313"); + +MODULE_AUTHOR("Lucas Stankus "); +MODULE_DESCRIPTION("ADXL313 3-Axis Digital Accelerometer core driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/adxl313_i2c.c b/drivers/iio/accel/adxl313_i2c.c new file mode 100644 index 000000000..2eea8d4d0 --- /dev/null +++ b/drivers/iio/accel/adxl313_i2c.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADXL313 3-Axis Digital Accelerometer + * + * Copyright (c) 2021 Lucas Stankus + * + * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADXL313.pdf + */ + +#include +#include +#include + +#include "adxl313.h" + +static const struct regmap_config adxl31x_i2c_regmap_config[] = { + [ADXL312] = { + .reg_bits = 8, + .val_bits = 8, + .rd_table = &adxl312_readable_regs_table, + .wr_table = &adxl312_writable_regs_table, + .max_register = 0x39, + .volatile_reg = adxl313_is_volatile_reg, + .cache_type = REGCACHE_MAPLE, + }, + [ADXL313] = { + .reg_bits = 8, + .val_bits = 8, + .rd_table = &adxl313_readable_regs_table, + .wr_table = &adxl313_writable_regs_table, + .max_register = 0x39, + .volatile_reg = adxl313_is_volatile_reg, + .cache_type = REGCACHE_MAPLE, + }, + [ADXL314] = { + .reg_bits = 8, + .val_bits = 8, + .rd_table = &adxl314_readable_regs_table, + .wr_table = &adxl314_writable_regs_table, + .max_register = 0x39, + .volatile_reg = adxl313_is_volatile_reg, + .cache_type = REGCACHE_MAPLE, + }, +}; + +static const struct i2c_device_id adxl313_i2c_id[] = { + { .name = "adxl312", .driver_data = (kernel_ulong_t)&adxl31x_chip_info[ADXL312] }, + { .name = "adxl313", .driver_data = (kernel_ulong_t)&adxl31x_chip_info[ADXL313] }, + { .name = "adxl314", .driver_data = (kernel_ulong_t)&adxl31x_chip_info[ADXL314] }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, adxl313_i2c_id); + +static const struct of_device_id adxl313_of_match[] = { + { .compatible = "adi,adxl312", .data = &adxl31x_chip_info[ADXL312] }, + { .compatible = "adi,adxl313", .data = &adxl31x_chip_info[ADXL313] }, + { .compatible = "adi,adxl314", .data = &adxl31x_chip_info[ADXL314] }, + { } +}; + +MODULE_DEVICE_TABLE(of, adxl313_of_match); + +static int adxl313_i2c_probe(struct i2c_client *client) +{ + const struct adxl313_chip_info *chip_data; + struct device *dev = &client->dev; + struct regmap *regmap; + + /* + * Retrieves device specific data as a pointer to a + * adxl313_chip_info structure + */ + chip_data = i2c_get_match_data(client); + + regmap = devm_regmap_init_i2c(client, + &adxl31x_i2c_regmap_config[chip_data->type]); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Error initializing i2c regmap\n"); + + return adxl313_core_probe(dev, regmap, chip_data, NULL); +} + +static struct i2c_driver adxl313_i2c_driver = { + .driver = { + .name = "adxl313_i2c", + .of_match_table = adxl313_of_match, + }, + .probe = adxl313_i2c_probe, + .id_table = adxl313_i2c_id, +}; + +module_i2c_driver(adxl313_i2c_driver); + +MODULE_AUTHOR("Lucas Stankus "); +MODULE_DESCRIPTION("ADXL313 3-Axis Digital Accelerometer I2C driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ADXL313"); diff --git a/drivers/iio/accel/adxl313_spi.c b/drivers/iio/accel/adxl313_spi.c new file mode 100644 index 000000000..a61295de1 --- /dev/null +++ b/drivers/iio/accel/adxl313_spi.c @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADXL313 3-Axis Digital Accelerometer + * + * Copyright (c) 2021 Lucas Stankus + * + * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADXL313.pdf + */ + +#include +#include +#include +#include + +#include "adxl313.h" + +static const struct regmap_config adxl31x_spi_regmap_config[] = { + [ADXL312] = { + .reg_bits = 8, + .val_bits = 8, + .rd_table = &adxl312_readable_regs_table, + .wr_table = &adxl312_writable_regs_table, + .max_register = 0x39, + /* Setting bits 7 and 6 enables multiple-byte read */ + .read_flag_mask = BIT(7) | BIT(6), + .volatile_reg = adxl313_is_volatile_reg, + .cache_type = REGCACHE_MAPLE, + }, + [ADXL313] = { + .reg_bits = 8, + .val_bits = 8, + .rd_table = &adxl313_readable_regs_table, + .wr_table = &adxl313_writable_regs_table, + .max_register = 0x39, + /* Setting bits 7 and 6 enables multiple-byte read */ + .read_flag_mask = BIT(7) | BIT(6), + .volatile_reg = adxl313_is_volatile_reg, + .cache_type = REGCACHE_MAPLE, + }, + [ADXL314] = { + .reg_bits = 8, + .val_bits = 8, + .rd_table = &adxl314_readable_regs_table, + .wr_table = &adxl314_writable_regs_table, + .max_register = 0x39, + /* Setting bits 7 and 6 enables multiple-byte read */ + .read_flag_mask = BIT(7) | BIT(6), + .volatile_reg = adxl313_is_volatile_reg, + .cache_type = REGCACHE_MAPLE, + }, +}; + +static int adxl313_spi_setup(struct device *dev, struct regmap *regmap) +{ + struct spi_device *spi = container_of(dev, struct spi_device, dev); + int ret; + + if (spi->mode & SPI_3WIRE) { + ret = regmap_write(regmap, ADXL313_REG_DATA_FORMAT, + ADXL313_SPI_3WIRE); + if (ret) + return ret; + } + + return regmap_update_bits(regmap, ADXL313_REG_POWER_CTL, + ADXL313_I2C_DISABLE, ADXL313_I2C_DISABLE); +} + +static int adxl313_spi_probe(struct spi_device *spi) +{ + const struct adxl313_chip_info *chip_data; + struct device *dev = &spi->dev; + struct regmap *regmap; + int ret; + + spi->mode |= SPI_MODE_3; + ret = spi_setup(spi); + if (ret) + return ret; + + chip_data = spi_get_device_match_data(spi); + + regmap = devm_regmap_init_spi(spi, + &adxl31x_spi_regmap_config[chip_data->type]); + + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Error initializing spi regmap\n"); + + return adxl313_core_probe(dev, regmap, chip_data, &adxl313_spi_setup); +} + +static const struct spi_device_id adxl313_spi_id[] = { + { .name = "adxl312", .driver_data = (kernel_ulong_t)&adxl31x_chip_info[ADXL312] }, + { .name = "adxl313", .driver_data = (kernel_ulong_t)&adxl31x_chip_info[ADXL313] }, + { .name = "adxl314", .driver_data = (kernel_ulong_t)&adxl31x_chip_info[ADXL314] }, + { } +}; + +MODULE_DEVICE_TABLE(spi, adxl313_spi_id); + +static const struct of_device_id adxl313_of_match[] = { + { .compatible = "adi,adxl312", .data = &adxl31x_chip_info[ADXL312] }, + { .compatible = "adi,adxl313", .data = &adxl31x_chip_info[ADXL313] }, + { .compatible = "adi,adxl314", .data = &adxl31x_chip_info[ADXL314] }, + { } +}; + +MODULE_DEVICE_TABLE(of, adxl313_of_match); + +static struct spi_driver adxl313_spi_driver = { + .driver = { + .name = "adxl313_spi", + .of_match_table = adxl313_of_match, + }, + .probe = adxl313_spi_probe, + .id_table = adxl313_spi_id, +}; + +module_spi_driver(adxl313_spi_driver); + +MODULE_AUTHOR("Lucas Stankus "); +MODULE_DESCRIPTION("ADXL313 3-Axis Digital Accelerometer SPI driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ADXL313"); diff --git a/drivers/iio/accel/adxl345.h b/drivers/iio/accel/adxl345.h new file mode 100644 index 000000000..9385affde --- /dev/null +++ b/drivers/iio/accel/adxl345.h @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * ADXL345 3-Axis Digital Accelerometer + * + * Copyright (c) 2017 Eva Rachel Retuya + */ + +#ifndef _ADXL345_H_ +#define _ADXL345_H_ + +#define ADXL345_REG_DEVID 0x00 +#define ADXL345_REG_THRESH_TAP 0x1D +#define ADXL345_REG_OFSX 0x1E +#define ADXL345_REG_OFSY 0x1F +#define ADXL345_REG_OFSZ 0x20 +#define ADXL345_REG_OFS_AXIS(index) (ADXL345_REG_OFSX + (index)) + +/* Tap duration */ +#define ADXL345_REG_DUR 0x21 +/* Tap latency */ +#define ADXL345_REG_LATENT 0x22 +/* Tap window */ +#define ADXL345_REG_WINDOW 0x23 +/* Activity threshold */ +#define ADXL345_REG_THRESH_ACT 0x24 +/* Inactivity threshold */ +#define ADXL345_REG_THRESH_INACT 0x25 +/* Inactivity time */ +#define ADXL345_REG_TIME_INACT 0x26 +/* Axis enable control for activity and inactivity detection */ +#define ADXL345_REG_ACT_INACT_CTRL 0x27 +/* Free-fall threshold */ +#define ADXL345_REG_THRESH_FF 0x28 +/* Free-fall time */ +#define ADXL345_REG_TIME_FF 0x29 +/* Axis control for single tap or double tap */ +#define ADXL345_REG_TAP_AXIS 0x2A +/* Source of single tap or double tap */ +#define ADXL345_REG_ACT_TAP_STATUS 0x2B +/* Data rate and power mode control */ +#define ADXL345_REG_BW_RATE 0x2C +#define ADXL345_REG_POWER_CTL 0x2D +#define ADXL345_REG_INT_ENABLE 0x2E +#define ADXL345_REG_INT_MAP 0x2F +#define ADXL345_REG_INT_SOURCE 0x30 +#define ADXL345_REG_DATA_FORMAT 0x31 +#define ADXL345_REG_XYZ_BASE 0x32 +#define ADXL345_REG_DATA_AXIS(index) \ + (ADXL345_REG_XYZ_BASE + (index) * sizeof(__le16)) + +#define ADXL345_REG_FIFO_CTL 0x38 +#define ADXL345_FIFO_CTL_SAMPLES_MSK GENMASK(4, 0) +/* 0: INT1, 1: INT2 */ +#define ADXL345_FIFO_CTL_TRIGGER_MSK BIT(5) +#define ADXL345_FIFO_CTL_MODE_MSK GENMASK(7, 6) +#define ADXL345_REG_FIFO_STATUS 0x39 +#define ADXL345_REG_FIFO_STATUS_MSK 0x3F + +#define ADXL345_INT_OVERRUN BIT(0) +#define ADXL345_INT_WATERMARK BIT(1) +#define ADXL345_INT_FREE_FALL BIT(2) +#define ADXL345_INT_INACTIVITY BIT(3) +#define ADXL345_INT_ACTIVITY BIT(4) +#define ADXL345_INT_DOUBLE_TAP BIT(5) +#define ADXL345_INT_SINGLE_TAP BIT(6) +#define ADXL345_INT_DATA_READY BIT(7) + +/* + * BW_RATE bits - Bandwidth and output data rate. The default value is + * 0x0A, which translates to a 100 Hz output data rate + */ +#define ADXL345_BW_RATE_MSK GENMASK(3, 0) +#define ADXL345_BW_LOW_POWER BIT(4) +#define ADXL345_BASE_RATE_NANO_HZ 97656250LL + +#define ADXL345_POWER_CTL_WAKEUP GENMASK(1, 0) +#define ADXL345_POWER_CTL_SLEEP BIT(2) +#define ADXL345_POWER_CTL_MEASURE BIT(3) +#define ADXL345_POWER_CTL_AUTO_SLEEP BIT(4) +#define ADXL345_POWER_CTL_LINK BIT(5) + +/* Set the g range */ +#define ADXL345_DATA_FORMAT_RANGE GENMASK(1, 0) +/* Data is left justified */ +#define ADXL345_DATA_FORMAT_JUSTIFY BIT(2) +/* Up to 13-bits resolution */ +#define ADXL345_DATA_FORMAT_FULL_RES BIT(3) +#define ADXL345_DATA_FORMAT_SPI_3WIRE BIT(6) +#define ADXL345_DATA_FORMAT_SELF_TEST BIT(7) +#define ADXL345_DATA_FORMAT_2G 0 +#define ADXL345_DATA_FORMAT_4G 1 +#define ADXL345_DATA_FORMAT_8G 2 +#define ADXL345_DATA_FORMAT_16G 3 + +#define ADXL345_DEVID 0xE5 +#define ADXL345_FIFO_SIZE 32 + +/* + * In full-resolution mode, scale factor is maintained at ~4 mg/LSB + * in all g ranges. + * + * At +/- 16g with 13-bit resolution, scale is computed as: + * (16 + 16) * 9.81 / (2^13 - 1) = 0.0383 + */ +#define ADXL345_USCALE 38300 + +/* + * The Datasheet lists a resolution of Resolution is ~49 mg per LSB. That's + * ~480mm/s**2 per LSB. + */ +#define ADXL375_USCALE 480000 + +struct regmap; + +bool adxl345_is_volatile_reg(struct device *dev, unsigned int reg); + +struct adxl345_chip_info { + const char *name; + int uscale; +}; + +int adxl345_core_probe(struct device *dev, struct regmap *regmap, + bool fifo_delay_default, + int (*setup)(struct device*, struct regmap*)); + +#endif /* _ADXL345_H_ */ diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c new file mode 100644 index 000000000..6c9080d88 --- /dev/null +++ b/drivers/iio/accel/adxl345_core.c @@ -0,0 +1,2042 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADXL345 3-Axis Digital Accelerometer IIO core driver + * + * Copyright (c) 2017 Eva Rachel Retuya + * + * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADXL345.pdf + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "adxl345.h" + +#define ADXL345_FIFO_BYPASS 0 +#define ADXL345_FIFO_FIFO 1 +#define ADXL345_FIFO_STREAM 2 + +#define ADXL345_DIRS 3 + +#define ADXL345_INT_NONE 0xff +#define ADXL345_INT1 0 +#define ADXL345_INT2 1 + +#define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0) +#define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3) +#define ADXL345_REG_TAP_SUPPRESS BIT(3) +#define ADXL345_POWER_CTL_INACT_MSK (ADXL345_POWER_CTL_AUTO_SLEEP | ADXL345_POWER_CTL_LINK) + +#define ADXL345_TAP_Z_EN BIT(0) +#define ADXL345_TAP_Y_EN BIT(1) +#define ADXL345_TAP_X_EN BIT(2) +#define ADXL345_REG_TAP_SUPPRESS BIT(3) + +#define ADXL345_INACT_Z_EN BIT(0) +#define ADXL345_INACT_Y_EN BIT(1) +#define ADXL345_INACT_X_EN BIT(2) +#define ADXL345_REG_INACT_ACDC BIT(3) +#define ADXL345_ACT_INACT_NO_AXIS_EN 0x00 +#define ADXL345_INACT_XYZ_EN (ADXL345_INACT_Z_EN | ADXL345_INACT_Y_EN | ADXL345_INACT_X_EN) + +#define ADXL345_ACT_Z_EN BIT(4) +#define ADXL345_ACT_Y_EN BIT(5) +#define ADXL345_ACT_X_EN BIT(6) +#define ADXL345_REG_ACT_ACDC BIT(7) +#define ADXL345_ACT_XYZ_EN (ADXL345_ACT_Z_EN | ADXL345_ACT_Y_EN | ADXL345_ACT_X_EN) + +#define ADXL345_COUPLING_DC 0 +#define ADXL345_COUPLING_AC 1 +#define ADXL345_REG_NO_ACDC 0x00 + +/* single/double tap */ +enum adxl345_tap_type { + ADXL345_SINGLE_TAP, + ADXL345_DOUBLE_TAP, +}; + +static const unsigned int adxl345_tap_int_reg[] = { + [ADXL345_SINGLE_TAP] = ADXL345_INT_SINGLE_TAP, + [ADXL345_DOUBLE_TAP] = ADXL345_INT_DOUBLE_TAP, +}; + +enum adxl345_tap_time_type { + ADXL345_TAP_TIME_LATENT, + ADXL345_TAP_TIME_WINDOW, + ADXL345_TAP_TIME_DUR, +}; + +static const unsigned int adxl345_tap_time_reg[] = { + [ADXL345_TAP_TIME_LATENT] = ADXL345_REG_LATENT, + [ADXL345_TAP_TIME_WINDOW] = ADXL345_REG_WINDOW, + [ADXL345_TAP_TIME_DUR] = ADXL345_REG_DUR, +}; + +/* activity/inactivity */ +enum adxl345_activity_type { + ADXL345_ACTIVITY, + ADXL345_INACTIVITY, + ADXL345_ACTIVITY_AC, + ADXL345_INACTIVITY_AC, + ADXL345_INACTIVITY_FF, +}; + +static const unsigned int adxl345_act_int_reg[] = { + [ADXL345_ACTIVITY] = ADXL345_INT_ACTIVITY, + [ADXL345_INACTIVITY] = ADXL345_INT_INACTIVITY, + [ADXL345_ACTIVITY_AC] = ADXL345_INT_ACTIVITY, + [ADXL345_INACTIVITY_AC] = ADXL345_INT_INACTIVITY, + [ADXL345_INACTIVITY_FF] = ADXL345_INT_FREE_FALL, +}; + +static const unsigned int adxl345_act_thresh_reg[] = { + [ADXL345_ACTIVITY] = ADXL345_REG_THRESH_ACT, + [ADXL345_INACTIVITY] = ADXL345_REG_THRESH_INACT, + [ADXL345_ACTIVITY_AC] = ADXL345_REG_THRESH_ACT, + [ADXL345_INACTIVITY_AC] = ADXL345_REG_THRESH_INACT, + [ADXL345_INACTIVITY_FF] = ADXL345_REG_THRESH_FF, +}; + +static const unsigned int adxl345_act_acdc_msk[] = { + [ADXL345_ACTIVITY] = ADXL345_REG_ACT_ACDC, + [ADXL345_INACTIVITY] = ADXL345_REG_INACT_ACDC, + [ADXL345_ACTIVITY_AC] = ADXL345_REG_ACT_ACDC, + [ADXL345_INACTIVITY_AC] = ADXL345_REG_INACT_ACDC, + [ADXL345_INACTIVITY_FF] = ADXL345_REG_NO_ACDC, +}; + +enum adxl345_odr { + ADXL345_ODR_0P10HZ = 0, + ADXL345_ODR_0P20HZ, + ADXL345_ODR_0P39HZ, + ADXL345_ODR_0P78HZ, + ADXL345_ODR_1P56HZ, + ADXL345_ODR_3P13HZ, + ADXL345_ODR_6P25HZ, + ADXL345_ODR_12P50HZ, + ADXL345_ODR_25HZ, + ADXL345_ODR_50HZ, + ADXL345_ODR_100HZ, + ADXL345_ODR_200HZ, + ADXL345_ODR_400HZ, + ADXL345_ODR_800HZ, + ADXL345_ODR_1600HZ, + ADXL345_ODR_3200HZ, +}; + +enum adxl345_range { + ADXL345_2G_RANGE = 0, + ADXL345_4G_RANGE, + ADXL345_8G_RANGE, + ADXL345_16G_RANGE, +}; + +/* Certain features recommend 12.5 Hz - 400 Hz ODR */ +static const int adxl345_odr_tbl[][2] = { + [ADXL345_ODR_0P10HZ] = { 0, 97000 }, + [ADXL345_ODR_0P20HZ] = { 0, 195000 }, + [ADXL345_ODR_0P39HZ] = { 0, 390000 }, + [ADXL345_ODR_0P78HZ] = { 0, 781000 }, + [ADXL345_ODR_1P56HZ] = { 1, 562000 }, + [ADXL345_ODR_3P13HZ] = { 3, 125000 }, + [ADXL345_ODR_6P25HZ] = { 6, 250000 }, + [ADXL345_ODR_12P50HZ] = { 12, 500000 }, + [ADXL345_ODR_25HZ] = { 25, 0 }, + [ADXL345_ODR_50HZ] = { 50, 0 }, + [ADXL345_ODR_100HZ] = { 100, 0 }, + [ADXL345_ODR_200HZ] = { 200, 0 }, + [ADXL345_ODR_400HZ] = { 400, 0 }, + [ADXL345_ODR_800HZ] = { 800, 0 }, + [ADXL345_ODR_1600HZ] = { 1600, 0 }, + [ADXL345_ODR_3200HZ] = { 3200, 0 }, +}; + +/* + * Full resolution frequency table: + * (g * 2 * 9.80665) / (2^(resolution) - 1) + * + * resolution := 13 (full) + * g := 2|4|8|16 + * + * 2g at 13bit: 0.004789 + * 4g at 13bit: 0.009578 + * 8g at 13bit: 0.019156 + * 16g at 16bit: 0.038312 + */ +static const int adxl345_fullres_range_tbl[][2] = { + [ADXL345_2G_RANGE] = { 0, 4789 }, + [ADXL345_4G_RANGE] = { 0, 9578 }, + [ADXL345_8G_RANGE] = { 0, 19156 }, + [ADXL345_16G_RANGE] = { 0, 38312 }, +}; + +/* scaling */ +static const int adxl345_range_factor_tbl[] = { + [ADXL345_2G_RANGE] = 1, + [ADXL345_4G_RANGE] = 2, + [ADXL345_8G_RANGE] = 4, + [ADXL345_16G_RANGE] = 8, +}; + +struct adxl345_state { + const struct adxl345_chip_info *info; + struct regmap *regmap; + bool fifo_delay; /* delay: delay is needed for SPI */ + u8 watermark; + u8 fifo_mode; + + u8 inact_threshold; + u32 inact_time_ms; + + u32 tap_duration_us; + u32 tap_latent_us; + u32 tap_window_us; + + __le16 fifo_buf[ADXL345_DIRS * ADXL345_FIFO_SIZE + 1] __aligned(IIO_DMA_MINALIGN); +}; + +static const struct iio_event_spec adxl345_events[] = { + { + /* activity */ + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_type = + BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_SCALE) | + BIT(IIO_EV_INFO_VALUE), + }, + { + /* activity, ac bit set */ + .type = IIO_EV_TYPE_MAG_ADAPTIVE, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_type = + BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_SCALE) | + BIT(IIO_EV_INFO_VALUE), + }, + { + /* single tap */ + .type = IIO_EV_TYPE_GESTURE, + .dir = IIO_EV_DIR_SINGLETAP, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = + BIT(IIO_EV_INFO_SCALE) | + BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_TIMEOUT), + }, + { + /* double tap */ + .type = IIO_EV_TYPE_GESTURE, + .dir = IIO_EV_DIR_DOUBLETAP, + .mask_shared_by_type = + BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_SCALE) | + BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_RESET_TIMEOUT) | + BIT(IIO_EV_INFO_TAP2_MIN_DELAY), + }, +}; + +#define ADXL345_CHANNEL(index, reg, axis) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .address = (reg), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = (index), \ + .scan_type = { \ + .sign = 's', \ + .realbits = 13, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ + .event_spec = adxl345_events, \ + .num_event_specs = ARRAY_SIZE(adxl345_events), \ +} + +enum adxl345_chans { + chan_x, chan_y, chan_z, +}; + +static const struct iio_event_spec adxl345_fake_chan_events[] = { + { + /* inactivity */ + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = + BIT(IIO_EV_INFO_SCALE) | + BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD), + }, + { + /* inactivity, AC bit set */ + .type = IIO_EV_TYPE_MAG_ADAPTIVE, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = + BIT(IIO_EV_INFO_SCALE) | + BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD), + }, +}; + +static const struct iio_chan_spec adxl345_channels[] = { + ADXL345_CHANNEL(0, chan_x, X), + ADXL345_CHANNEL(1, chan_y, Y), + ADXL345_CHANNEL(2, chan_z, Z), + { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_X_AND_Y_AND_Z, + .scan_index = -1, /* Fake channel */ + .event_spec = adxl345_fake_chan_events, + .num_event_specs = ARRAY_SIZE(adxl345_fake_chan_events), + }, +}; + +static const unsigned long adxl345_scan_masks[] = { + BIT(chan_x) | BIT(chan_y) | BIT(chan_z), + 0 +}; + +bool adxl345_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ADXL345_REG_DATA_AXIS(0): + case ADXL345_REG_DATA_AXIS(1): + case ADXL345_REG_DATA_AXIS(2): + case ADXL345_REG_DATA_AXIS(3): + case ADXL345_REG_DATA_AXIS(4): + case ADXL345_REG_DATA_AXIS(5): + case ADXL345_REG_ACT_TAP_STATUS: + case ADXL345_REG_FIFO_STATUS: + case ADXL345_REG_INT_SOURCE: + return true; + default: + return false; + } +} +EXPORT_SYMBOL_NS_GPL(adxl345_is_volatile_reg, "IIO_ADXL345"); + +/** + * adxl345_set_measure_en() - Enable and disable measuring. + * + * @st: The device data. + * @en: Enable measurements, else standby mode. + * + * For lowest power operation, standby mode can be used. In standby mode, + * current consumption is supposed to be reduced to 0.1uA (typical). In this + * mode no measurements are made. Placing the device into standby mode + * preserves the contents of FIFO. + * + * Return: Returns 0 if successful, or a negative error value. + */ +static int adxl345_set_measure_en(struct adxl345_state *st, bool en) +{ + return regmap_assign_bits(st->regmap, ADXL345_REG_POWER_CTL, + ADXL345_POWER_CTL_MEASURE, en); +} + +/* activity / inactivity */ + +static int adxl345_set_inact_threshold(struct adxl345_state *st, + unsigned int threshold) +{ + int ret; + + st->inact_threshold = min(U8_MAX, threshold); + + ret = regmap_write(st->regmap, + adxl345_act_thresh_reg[ADXL345_INACTIVITY], + st->inact_threshold); + if (ret) + return ret; + + return regmap_write(st->regmap, + adxl345_act_thresh_reg[ADXL345_INACTIVITY_FF], + st->inact_threshold); +} + +static int adxl345_set_default_time(struct adxl345_state *st) +{ + int max_boundary = U8_MAX; + int min_boundary = 10; + enum adxl345_odr odr; + unsigned int regval; + unsigned int val; + int ret; + + /* Generated inactivity time based on ODR */ + ret = regmap_read(st->regmap, ADXL345_REG_BW_RATE, ®val); + if (ret) + return ret; + + odr = FIELD_GET(ADXL345_BW_RATE_MSK, regval); + val = clamp(max_boundary - adxl345_odr_tbl[odr][0], + min_boundary, max_boundary); + st->inact_time_ms = MILLI * val; + + /* Inactivity time in s */ + return regmap_write(st->regmap, ADXL345_REG_TIME_INACT, val); +} + +static int adxl345_set_inactivity_time(struct adxl345_state *st, u32 val_int) +{ + st->inact_time_ms = MILLI * val_int; + + return regmap_write(st->regmap, ADXL345_REG_TIME_INACT, val_int); +} + +static int adxl345_set_freefall_time(struct adxl345_state *st, u32 val_fract) +{ + /* + * Datasheet max. value is 255 * 5000 us = 1.275000 seconds. + * + * Recommended values between 100ms and 350ms (0x14 to 0x46) + */ + st->inact_time_ms = DIV_ROUND_UP(val_fract, MILLI); + + return regmap_write(st->regmap, ADXL345_REG_TIME_FF, + DIV_ROUND_CLOSEST(val_fract, 5)); +} + +/** + * adxl345_set_inact_time - Configure inactivity time explicitly or by ODR. + * @st: The sensor state instance. + * @val_int: The inactivity time, integer part. + * @val_fract: The inactivity time, fractional part when val_int is 0. + * + * Inactivity time can be configured between 1 and 255 seconds. If a user sets + * val_s to 0, a default inactivity time is calculated automatically (since 0 is + * also invalid and undefined by the sensor). + * + * In such cases, power consumption should be considered: the inactivity period + * should be shorter at higher sampling frequencies and longer at lower ones. + * Specifically, for frequencies above 255 Hz, the default is set to 10 seconds; + * for frequencies below 10 Hz, it defaults to 255 seconds. + * + * The calculation method subtracts the integer part of the configured sample + * frequency from 255 to estimate the inactivity time in seconds. Sub-Hertz + * values are ignored in this approximation. Since the recommended output data + * rates (ODRs) for features like activity/inactivity detection, sleep modes, + * and free fall range between 12.5 Hz and 400 Hz, frequencies outside this + * range will either use the defined boundary defaults or require explicit + * configuration via val_s. + * + * Return: 0 or error value. + */ +static int adxl345_set_inact_time(struct adxl345_state *st, u32 val_int, + u32 val_fract) +{ + if (val_int > 0) { + /* Time >= 1s, inactivity */ + return adxl345_set_inactivity_time(st, val_int); + } else if (val_int == 0) { + if (val_fract > 0) { + /* Time < 1s, free-fall */ + return adxl345_set_freefall_time(st, val_fract); + } else if (val_fract == 0) { + /* Time == 0.0s */ + return adxl345_set_default_time(st); + } + } + + /* Do not support negative or wrong input. */ + return -EINVAL; +} + +/** + * adxl345_is_act_inact_ac() - Verify if AC or DC coupling is currently enabled. + * + * @st: The device data. + * @type: The activity or inactivity type. + * + * Given a type of activity / inactivity combined with either AC coupling set or + * default to DC, this function verifies if the combination is currently + * configured, hence enabled or not. + * + * Return: true if configured coupling matches the provided type, else a negative + * error value. + */ +static int adxl345_is_act_inact_ac(struct adxl345_state *st, + enum adxl345_activity_type type) +{ + unsigned int regval; + bool coupling; + int ret; + + if (type == ADXL345_INACTIVITY_FF) + return true; + + ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, ®val); + if (ret) + return ret; + + coupling = adxl345_act_acdc_msk[type] & regval; + + switch (type) { + case ADXL345_ACTIVITY: + case ADXL345_INACTIVITY: + return coupling == ADXL345_COUPLING_DC; + case ADXL345_ACTIVITY_AC: + case ADXL345_INACTIVITY_AC: + return coupling == ADXL345_COUPLING_AC; + default: + return -EINVAL; + } +} + +/** + * adxl345_set_act_inact_ac() - Configure AC coupling or DC coupling. + * + * @st: The device data. + * @type: Provide a type of activity or inactivity. + * @cmd_en: enable or disable AC coupling. + * + * Enables AC coupling or DC coupling depending on the provided type argument. + * Note: Activity and inactivity can be either AC coupled or DC coupled not + * both at the same time. + * + * Return: 0 if successful, else error value. + */ +static int adxl345_set_act_inact_ac(struct adxl345_state *st, + enum adxl345_activity_type type, + bool cmd_en) +{ + unsigned int act_inact_ac; + + if (type == ADXL345_ACTIVITY_AC || type == ADXL345_INACTIVITY_AC) + act_inact_ac = ADXL345_COUPLING_AC && cmd_en; + else + act_inact_ac = ADXL345_COUPLING_DC && cmd_en; + + /* + * A setting of false selects dc-coupled operation, and a setting of + * true enables ac-coupled operation. In dc-coupled operation, the + * current acceleration magnitude is compared directly with + * ADXL345_REG_THRESH_ACT and ADXL345_REG_THRESH_INACT to determine + * whether activity or inactivity is detected. + * + * In ac-coupled operation for activity detection, the acceleration + * value at the start of activity detection is taken as a reference + * value. New samples of acceleration are then compared to this + * reference value, and if the magnitude of the difference exceeds the + * ADXL345_REG_THRESH_ACT value, the device triggers an activity + * interrupt. + * + * Similarly, in ac-coupled operation for inactivity detection, a + * reference value is used for comparison and is updated whenever the + * device exceeds the inactivity threshold. After the reference value + * is selected, the device compares the magnitude of the difference + * between the reference value and the current acceleration with + * ADXL345_REG_THRESH_INACT. If the difference is less than the value in + * ADXL345_REG_THRESH_INACT for the time in ADXL345_REG_TIME_INACT, the + * device is considered inactive and the inactivity interrupt is + * triggered. [quoted from p. 24, ADXL345 datasheet Rev. G] + * + * In a conclusion, the first acceleration snapshot sample which hit the + * threshold in a particular direction is always taken as acceleration + * reference value to that direction. Since for the hardware activity + * and inactivity depend on the x/y/z axis, so do ac and dc coupling. + * Note, this sw driver always enables or disables all three x/y/z axis + * for detection via act_axis_ctrl and inact_axis_ctrl, respectively. + * Where in dc-coupling samples are compared against the thresholds, in + * ac-coupling measurement difference to the first acceleration + * reference value are compared against the threshold. So, ac-coupling + * allows for a bit more dynamic compensation depending on the initial + * sample. + */ + return regmap_assign_bits(st->regmap, ADXL345_REG_ACT_INACT_CTRL, + adxl345_act_acdc_msk[type], act_inact_ac); +} + +static int adxl345_is_act_inact_en(struct adxl345_state *st, + enum adxl345_activity_type type) +{ + unsigned int axis_ctrl; + unsigned int regval; + bool int_en, en; + int ret; + + ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, &axis_ctrl); + if (ret) + return ret; + + /* Check if axis for activity are enabled */ + switch (type) { + case ADXL345_ACTIVITY: + case ADXL345_ACTIVITY_AC: + en = FIELD_GET(ADXL345_ACT_XYZ_EN, axis_ctrl); + if (!en) + return false; + break; + case ADXL345_INACTIVITY: + case ADXL345_INACTIVITY_AC: + en = FIELD_GET(ADXL345_INACT_XYZ_EN, axis_ctrl); + if (!en) + return false; + break; + case ADXL345_INACTIVITY_FF: + en = true; + break; + default: + return -EINVAL; + } + + /* Check if specific interrupt is enabled */ + ret = regmap_read(st->regmap, ADXL345_REG_INT_ENABLE, ®val); + if (ret) + return ret; + + int_en = adxl345_act_int_reg[type] & regval; + if (!int_en) + return false; + + /* Check if configured coupling matches provided type */ + return adxl345_is_act_inact_ac(st, type); +} + +static int adxl345_set_act_inact_linkbit(struct adxl345_state *st, + enum adxl345_activity_type type, + bool en) +{ + int act_ac_en, inact_ac_en; + int act_en, inact_en; + + act_en = adxl345_is_act_inact_en(st, ADXL345_ACTIVITY); + if (act_en < 0) + return act_en; + + act_ac_en = adxl345_is_act_inact_en(st, ADXL345_ACTIVITY_AC); + if (act_ac_en < 0) + return act_ac_en; + + if (type == ADXL345_INACTIVITY_FF) { + inact_en = false; + } else { + inact_en = adxl345_is_act_inact_en(st, ADXL345_INACTIVITY); + if (inact_en < 0) + return inact_en; + + inact_ac_en = adxl345_is_act_inact_en(st, ADXL345_INACTIVITY_AC); + if (inact_ac_en < 0) + return inact_ac_en; + + inact_en = inact_en || inact_ac_en; + } + + act_en = act_en || act_ac_en; + + return regmap_assign_bits(st->regmap, ADXL345_REG_POWER_CTL, + ADXL345_POWER_CTL_INACT_MSK, + en && act_en && inact_en); +} + +static int adxl345_set_act_inact_en(struct adxl345_state *st, + enum adxl345_activity_type type, + bool cmd_en) +{ + unsigned int axis_ctrl; + unsigned int threshold; + unsigned int period; + int ret; + + if (cmd_en) { + /* When turning on, check if threshold is valid */ + if (type == ADXL345_ACTIVITY || type == ADXL345_ACTIVITY_AC) { + ret = regmap_read(st->regmap, + adxl345_act_thresh_reg[type], + &threshold); + if (ret) + return ret; + } else { + threshold = st->inact_threshold; + } + + if (!threshold) /* Just ignore the command if threshold is 0 */ + return 0; + + /* When turning on inactivity, check if inact time is valid */ + if (type == ADXL345_INACTIVITY || type == ADXL345_INACTIVITY_AC) { + ret = regmap_read(st->regmap, + ADXL345_REG_TIME_INACT, + &period); + if (ret) + return ret; + + if (!period) + return 0; + } + } else { + /* + * When turning off an activity, ensure that the correct + * coupling event is specified. This step helps prevent misuse - + * for example, if an AC-coupled activity is active and the + * current call attempts to turn off a DC-coupled activity, this + * inconsistency should be detected here. + */ + if (adxl345_is_act_inact_ac(st, type) <= 0) + return 0; + } + + /* Start modifying configuration registers */ + ret = adxl345_set_measure_en(st, false); + if (ret) + return ret; + + /* Enable axis according to the command */ + switch (type) { + case ADXL345_ACTIVITY: + case ADXL345_ACTIVITY_AC: + axis_ctrl = ADXL345_ACT_XYZ_EN; + break; + case ADXL345_INACTIVITY: + case ADXL345_INACTIVITY_AC: + axis_ctrl = ADXL345_INACT_XYZ_EN; + break; + case ADXL345_INACTIVITY_FF: + axis_ctrl = ADXL345_ACT_INACT_NO_AXIS_EN; + break; + default: + return -EINVAL; + } + + ret = regmap_assign_bits(st->regmap, ADXL345_REG_ACT_INACT_CTRL, + axis_ctrl, cmd_en); + if (ret) + return ret; + + /* Update AC/DC-coupling according to the command */ + ret = adxl345_set_act_inact_ac(st, type, cmd_en); + if (ret) + return ret; + + /* Enable the interrupt line, according to the command */ + ret = regmap_assign_bits(st->regmap, ADXL345_REG_INT_ENABLE, + adxl345_act_int_reg[type], cmd_en); + if (ret) + return ret; + + /* Set link-bit and auto-sleep only when ACT and INACT are enabled */ + ret = adxl345_set_act_inact_linkbit(st, type, cmd_en); + if (ret) + return ret; + + return adxl345_set_measure_en(st, true); +} + +/* tap */ + +static int _adxl345_set_tap_int(struct adxl345_state *st, + enum adxl345_tap_type type, bool state) +{ + unsigned int int_map = 0x00; + unsigned int tap_threshold; + bool axis_valid; + bool singletap_args_valid = false; + bool doubletap_args_valid = false; + bool en = false; + u32 axis_ctrl; + int ret; + + ret = regmap_read(st->regmap, ADXL345_REG_TAP_AXIS, &axis_ctrl); + if (ret) + return ret; + + axis_valid = FIELD_GET(ADXL345_REG_TAP_AXIS_MSK, axis_ctrl) > 0; + + ret = regmap_read(st->regmap, ADXL345_REG_THRESH_TAP, &tap_threshold); + if (ret) + return ret; + + /* + * Note: A value of 0 for threshold and/or dur may result in undesirable + * behavior if single tap/double tap interrupts are enabled. + */ + singletap_args_valid = tap_threshold > 0 && st->tap_duration_us > 0; + + if (type == ADXL345_SINGLE_TAP) { + en = axis_valid && singletap_args_valid; + } else { + /* doubletap: Window must be equal or greater than latent! */ + doubletap_args_valid = st->tap_latent_us > 0 && + st->tap_window_us > 0 && + st->tap_window_us >= st->tap_latent_us; + + en = axis_valid && singletap_args_valid && doubletap_args_valid; + } + + if (state && en) + int_map |= adxl345_tap_int_reg[type]; + + return regmap_update_bits(st->regmap, ADXL345_REG_INT_ENABLE, + adxl345_tap_int_reg[type], int_map); +} + +static int adxl345_is_tap_en(struct adxl345_state *st, + enum iio_modifier axis, + enum adxl345_tap_type type, bool *en) +{ + unsigned int regval; + u32 axis_ctrl; + int ret; + + ret = regmap_read(st->regmap, ADXL345_REG_TAP_AXIS, &axis_ctrl); + if (ret) + return ret; + + /* Verify if axis is enabled for the tap detection. */ + switch (axis) { + case IIO_MOD_X: + *en = FIELD_GET(ADXL345_TAP_X_EN, axis_ctrl); + break; + case IIO_MOD_Y: + *en = FIELD_GET(ADXL345_TAP_Y_EN, axis_ctrl); + break; + case IIO_MOD_Z: + *en = FIELD_GET(ADXL345_TAP_Z_EN, axis_ctrl); + break; + default: + *en = false; + return -EINVAL; + } + + if (*en) { + /* + * If axis allow for tap detection, verify if the interrupt is + * enabled for tap detection. + */ + ret = regmap_read(st->regmap, ADXL345_REG_INT_ENABLE, ®val); + if (ret) + return ret; + + *en = adxl345_tap_int_reg[type] & regval; + } + + return 0; +} + +static int adxl345_set_singletap_en(struct adxl345_state *st, + enum iio_modifier axis, bool en) +{ + int ret; + u32 axis_ctrl; + + switch (axis) { + case IIO_MOD_X: + axis_ctrl = ADXL345_TAP_X_EN; + break; + case IIO_MOD_Y: + axis_ctrl = ADXL345_TAP_Y_EN; + break; + case IIO_MOD_Z: + axis_ctrl = ADXL345_TAP_Z_EN; + break; + default: + return -EINVAL; + } + + if (en) + ret = regmap_set_bits(st->regmap, ADXL345_REG_TAP_AXIS, + axis_ctrl); + else + ret = regmap_clear_bits(st->regmap, ADXL345_REG_TAP_AXIS, + axis_ctrl); + if (ret) + return ret; + + return _adxl345_set_tap_int(st, ADXL345_SINGLE_TAP, en); +} + +static int adxl345_set_doubletap_en(struct adxl345_state *st, bool en) +{ + int ret; + + /* + * Generally suppress detection of spikes during the latency period as + * double taps here, this is fully optional for double tap detection + */ + ret = regmap_assign_bits(st->regmap, ADXL345_REG_TAP_AXIS, + ADXL345_REG_TAP_SUPPRESS, en); + if (ret) + return ret; + + return _adxl345_set_tap_int(st, ADXL345_DOUBLE_TAP, en); +} + +static int _adxl345_set_tap_time(struct adxl345_state *st, + enum adxl345_tap_time_type type, u32 val_us) +{ + unsigned int regval; + + switch (type) { + case ADXL345_TAP_TIME_WINDOW: + st->tap_window_us = val_us; + break; + case ADXL345_TAP_TIME_LATENT: + st->tap_latent_us = val_us; + break; + case ADXL345_TAP_TIME_DUR: + st->tap_duration_us = val_us; + break; + } + + /* + * The scale factor is 1250us / LSB for tap_window_us and tap_latent_us. + * For tap_duration_us the scale factor is 625us / LSB. + */ + if (type == ADXL345_TAP_TIME_DUR) + regval = DIV_ROUND_CLOSEST(val_us, 625); + else + regval = DIV_ROUND_CLOSEST(val_us, 1250); + + return regmap_write(st->regmap, adxl345_tap_time_reg[type], regval); +} + +static int adxl345_set_tap_duration(struct adxl345_state *st, u32 val_int, + u32 val_fract_us) +{ + /* + * Max value is 255 * 625 us = 0.159375 seconds + * + * Note: the scaling is similar to the scaling in the ADXL380 + */ + if (val_int || val_fract_us > 159375) + return -EINVAL; + + return _adxl345_set_tap_time(st, ADXL345_TAP_TIME_DUR, val_fract_us); +} + +static int adxl345_set_tap_window(struct adxl345_state *st, u32 val_int, + u32 val_fract_us) +{ + /* + * Max value is 255 * 1250 us = 0.318750 seconds + * + * Note: the scaling is similar to the scaling in the ADXL380 + */ + if (val_int || val_fract_us > 318750) + return -EINVAL; + + return _adxl345_set_tap_time(st, ADXL345_TAP_TIME_WINDOW, val_fract_us); +} + +static int adxl345_set_tap_latent(struct adxl345_state *st, u32 val_int, + u32 val_fract_us) +{ + /* + * Max value is 255 * 1250 us = 0.318750 seconds + * + * Note: the scaling is similar to the scaling in the ADXL380 + */ + if (val_int || val_fract_us > 318750) + return -EINVAL; + + return _adxl345_set_tap_time(st, ADXL345_TAP_TIME_LATENT, val_fract_us); +} + +static int adxl345_find_odr(struct adxl345_state *st, int val, + int val2, enum adxl345_odr *odr) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(adxl345_odr_tbl); i++) { + if (val == adxl345_odr_tbl[i][0] && + val2 == adxl345_odr_tbl[i][1]) { + *odr = i; + return 0; + } + } + + return -EINVAL; +} + +static int adxl345_set_odr(struct adxl345_state *st, enum adxl345_odr odr) +{ + int ret; + + ret = regmap_update_bits(st->regmap, ADXL345_REG_BW_RATE, + ADXL345_BW_RATE_MSK, + FIELD_PREP(ADXL345_BW_RATE_MSK, odr)); + if (ret) + return ret; + + /* update inactivity time by ODR */ + return adxl345_set_inact_time(st, 0, 0); +} + +static int adxl345_find_range(struct adxl345_state *st, int val, int val2, + enum adxl345_range *range) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(adxl345_fullres_range_tbl); i++) { + if (val == adxl345_fullres_range_tbl[i][0] && + val2 == adxl345_fullres_range_tbl[i][1]) { + *range = i; + return 0; + } + } + + return -EINVAL; +} + +static int adxl345_set_range(struct adxl345_state *st, enum adxl345_range range) +{ + unsigned int act_threshold, inact_threshold; + unsigned int range_old; + unsigned int regval; + int ret; + + ret = regmap_read(st->regmap, ADXL345_REG_DATA_FORMAT, ®val); + if (ret) + return ret; + range_old = FIELD_GET(ADXL345_DATA_FORMAT_RANGE, regval); + + ret = regmap_read(st->regmap, + adxl345_act_thresh_reg[ADXL345_ACTIVITY], + &act_threshold); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, ADXL345_REG_DATA_FORMAT, + ADXL345_DATA_FORMAT_RANGE, + FIELD_PREP(ADXL345_DATA_FORMAT_RANGE, range)); + if (ret) + return ret; + + act_threshold = act_threshold * adxl345_range_factor_tbl[range_old] + / adxl345_range_factor_tbl[range]; + act_threshold = min(U8_MAX, max(1, act_threshold)); + + inact_threshold = st->inact_threshold; + inact_threshold = inact_threshold * adxl345_range_factor_tbl[range_old] + / adxl345_range_factor_tbl[range]; + inact_threshold = min(U8_MAX, max(1, inact_threshold)); + + ret = regmap_write(st->regmap, adxl345_act_thresh_reg[ADXL345_ACTIVITY], + act_threshold); + if (ret) + return ret; + + return adxl345_set_inact_threshold(st, inact_threshold); +} + +static int adxl345_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, + int *length, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = (int *)adxl345_fullres_range_tbl; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = ARRAY_SIZE(adxl345_fullres_range_tbl) * 2; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = (int *)adxl345_odr_tbl; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = ARRAY_SIZE(adxl345_odr_tbl) * 2; + return IIO_AVAIL_LIST; + } + + return -EINVAL; +} + +static int adxl345_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct adxl345_state *st = iio_priv(indio_dev); + __le16 accel; + unsigned int regval; + enum adxl345_odr odr; + enum adxl345_range range; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* + * Data is stored in adjacent registers: + * ADXL345_REG_DATA(X0/Y0/Z0) contain the least significant byte + * and ADXL345_REG_DATA(X0/Y0/Z0) + 1 the most significant byte + */ + ret = regmap_bulk_read(st->regmap, + ADXL345_REG_DATA_AXIS(chan->address), + &accel, sizeof(accel)); + if (ret) + return ret; + + *val = sign_extend32(le16_to_cpu(accel), 12); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = regmap_read(st->regmap, ADXL345_REG_DATA_FORMAT, ®val); + if (ret) + return ret; + range = FIELD_GET(ADXL345_DATA_FORMAT_RANGE, regval); + *val = adxl345_fullres_range_tbl[range][0]; + *val2 = adxl345_fullres_range_tbl[range][1]; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBBIAS: + ret = regmap_read(st->regmap, + ADXL345_REG_OFS_AXIS(chan->address), ®val); + if (ret) + return ret; + /* + * 8-bit resolution at +/- 2g, that is 4x accel data scale + * factor + */ + *val = sign_extend32(regval, 7) * 4; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = regmap_read(st->regmap, ADXL345_REG_BW_RATE, ®val); + if (ret) + return ret; + odr = FIELD_GET(ADXL345_BW_RATE_MSK, regval); + *val = adxl345_odr_tbl[odr][0]; + *val2 = adxl345_odr_tbl[odr][1]; + return IIO_VAL_INT_PLUS_MICRO; + } + + return -EINVAL; +} + +static int adxl345_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct adxl345_state *st = iio_priv(indio_dev); + enum adxl345_range range; + enum adxl345_odr odr; + int ret; + + ret = adxl345_set_measure_en(st, false); + if (ret) + return ret; + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + /* + * 8-bit resolution at +/- 2g, that is 4x accel data scale + * factor + */ + ret = regmap_write(st->regmap, + ADXL345_REG_OFS_AXIS(chan->address), + val / 4); + if (ret) + return ret; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = adxl345_find_odr(st, val, val2, &odr); + if (ret) + return ret; + + ret = adxl345_set_odr(st, odr); + if (ret) + return ret; + break; + case IIO_CHAN_INFO_SCALE: + ret = adxl345_find_range(st, val, val2, &range); + if (ret) + return ret; + + ret = adxl345_set_range(st, range); + if (ret) + return ret; + break; + default: + return -EINVAL; + } + + return adxl345_set_measure_en(st, true); +} + +static int adxl345_read_mag_config(struct adxl345_state *st, + enum iio_event_direction dir, + enum adxl345_activity_type type_act, + enum adxl345_activity_type type_inact) +{ + switch (dir) { + case IIO_EV_DIR_RISING: + return !!adxl345_is_act_inact_en(st, type_act); + case IIO_EV_DIR_FALLING: + return !!adxl345_is_act_inact_en(st, type_inact); + default: + return -EINVAL; + } +} + +static int adxl345_write_mag_config(struct adxl345_state *st, + enum iio_event_direction dir, + enum adxl345_activity_type type_act, + enum adxl345_activity_type type_inact, + bool state) +{ + switch (dir) { + case IIO_EV_DIR_RISING: + return adxl345_set_act_inact_en(st, type_act, state); + case IIO_EV_DIR_FALLING: + return adxl345_set_act_inact_en(st, type_inact, state); + default: + return -EINVAL; + } +} + +static int adxl345_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct adxl345_state *st = iio_priv(indio_dev); + bool int_en; + int ret; + + switch (type) { + case IIO_EV_TYPE_MAG: + return adxl345_read_mag_config(st, dir, + ADXL345_ACTIVITY, + ADXL345_INACTIVITY); + case IIO_EV_TYPE_MAG_ADAPTIVE: + return adxl345_read_mag_config(st, dir, + ADXL345_ACTIVITY_AC, + ADXL345_INACTIVITY_AC); + case IIO_EV_TYPE_GESTURE: + switch (dir) { + case IIO_EV_DIR_SINGLETAP: + ret = adxl345_is_tap_en(st, chan->channel2, + ADXL345_SINGLE_TAP, &int_en); + if (ret) + return ret; + return int_en; + case IIO_EV_DIR_DOUBLETAP: + ret = adxl345_is_tap_en(st, chan->channel2, + ADXL345_DOUBLE_TAP, &int_en); + if (ret) + return ret; + return int_en; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int adxl345_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct adxl345_state *st = iio_priv(indio_dev); + + switch (type) { + case IIO_EV_TYPE_MAG: + return adxl345_write_mag_config(st, dir, + ADXL345_ACTIVITY, + ADXL345_INACTIVITY, + state); + case IIO_EV_TYPE_MAG_ADAPTIVE: + return adxl345_write_mag_config(st, dir, + ADXL345_ACTIVITY_AC, + ADXL345_INACTIVITY_AC, + state); + case IIO_EV_TYPE_GESTURE: + switch (dir) { + case IIO_EV_DIR_SINGLETAP: + return adxl345_set_singletap_en(st, chan->channel2, state); + case IIO_EV_DIR_DOUBLETAP: + return adxl345_set_doubletap_en(st, state); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int adxl345_read_mag_value(struct adxl345_state *st, + enum iio_event_direction dir, + enum iio_event_info info, + enum adxl345_activity_type type_act, + enum adxl345_activity_type type_inact, + int *val, int *val2) +{ + unsigned int threshold; + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + ret = regmap_read(st->regmap, + adxl345_act_thresh_reg[type_act], + &threshold); + if (ret) + return ret; + *val = 62500 * threshold; + *val2 = MICRO; + return IIO_VAL_FRACTIONAL; + case IIO_EV_DIR_FALLING: + *val = 62500 * st->inact_threshold; + *val2 = MICRO; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_EV_INFO_PERIOD: + *val = st->inact_time_ms; + *val2 = MILLI; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } +} + +static int adxl345_write_mag_value(struct adxl345_state *st, + enum iio_event_direction dir, + enum iio_event_info info, + enum adxl345_activity_type type_act, + enum adxl345_activity_type type_inact, + int val, int val2) +{ + switch (info) { + case IIO_EV_INFO_VALUE: + /* Scaling factor 62.5mg/LSB, i.e. ~16g corresponds to 0xff */ + val = DIV_ROUND_CLOSEST(val * MICRO + val2, 62500); + switch (dir) { + case IIO_EV_DIR_RISING: + return regmap_write(st->regmap, + adxl345_act_thresh_reg[type_act], + val); + case IIO_EV_DIR_FALLING: + return adxl345_set_inact_threshold(st, val); + default: + return -EINVAL; + } + case IIO_EV_INFO_PERIOD: + return adxl345_set_inact_time(st, val, val2); + default: + return -EINVAL; + } +} + +static int adxl345_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct adxl345_state *st = iio_priv(indio_dev); + unsigned int tap_threshold; + int ret; + + /* + * The event threshold LSB is fixed at 62.5 mg/LSB + * 0.0625 * 9.80665 = 0.612915625 m/s^2 + */ + if (info == IIO_EV_INFO_SCALE) { + *val = 0; + *val2 = 612915; + return IIO_VAL_INT_PLUS_MICRO; + } + + switch (type) { + case IIO_EV_TYPE_MAG: + return adxl345_read_mag_value(st, dir, info, + ADXL345_ACTIVITY, + ADXL345_INACTIVITY, + val, val2); + case IIO_EV_TYPE_MAG_ADAPTIVE: + return adxl345_read_mag_value(st, dir, info, + ADXL345_ACTIVITY_AC, + ADXL345_INACTIVITY_AC, + val, val2); + case IIO_EV_TYPE_GESTURE: + switch (info) { + case IIO_EV_INFO_VALUE: + ret = regmap_read(st->regmap, ADXL345_REG_THRESH_TAP, + &tap_threshold); + if (ret) + return ret; + *val = sign_extend32(tap_threshold, 7); + return IIO_VAL_INT; + case IIO_EV_INFO_TIMEOUT: + *val = st->tap_duration_us; + *val2 = MICRO; + return IIO_VAL_FRACTIONAL; + case IIO_EV_INFO_RESET_TIMEOUT: + *val = st->tap_window_us; + *val2 = MICRO; + return IIO_VAL_FRACTIONAL; + case IIO_EV_INFO_TAP2_MIN_DELAY: + *val = st->tap_latent_us; + *val2 = MICRO; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int adxl345_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct adxl345_state *st = iio_priv(indio_dev); + int ret; + + ret = adxl345_set_measure_en(st, false); + if (ret) + return ret; + + if (info == IIO_EV_INFO_SCALE) + return -EINVAL; + + switch (type) { + case IIO_EV_TYPE_MAG: + ret = adxl345_write_mag_value(st, dir, info, + ADXL345_ACTIVITY, + ADXL345_INACTIVITY, + val, val2); + if (ret) + return ret; + break; + case IIO_EV_TYPE_MAG_ADAPTIVE: + ret = adxl345_write_mag_value(st, dir, info, + ADXL345_ACTIVITY_AC, + ADXL345_INACTIVITY_AC, + val, val2); + if (ret) + return ret; + break; + case IIO_EV_TYPE_GESTURE: + switch (info) { + case IIO_EV_INFO_VALUE: + ret = regmap_write(st->regmap, ADXL345_REG_THRESH_TAP, + min(val, 0xFF)); + if (ret) + return ret; + break; + case IIO_EV_INFO_TIMEOUT: + ret = adxl345_set_tap_duration(st, val, val2); + if (ret) + return ret; + break; + case IIO_EV_INFO_RESET_TIMEOUT: + ret = adxl345_set_tap_window(st, val, val2); + if (ret) + return ret; + break; + case IIO_EV_INFO_TAP2_MIN_DELAY: + ret = adxl345_set_tap_latent(st, val, val2); + if (ret) + return ret; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return adxl345_set_measure_en(st, true); +} + +static int adxl345_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct adxl345_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + return regmap_write(st->regmap, reg, writeval); +} + +static int adxl345_set_watermark(struct iio_dev *indio_dev, unsigned int value) +{ + struct adxl345_state *st = iio_priv(indio_dev); + const unsigned int fifo_mask = 0x1F, watermark_mask = 0x02; + int ret; + + value = min(value, ADXL345_FIFO_SIZE - 1); + + ret = regmap_update_bits(st->regmap, ADXL345_REG_FIFO_CTL, fifo_mask, value); + if (ret) + return ret; + + st->watermark = value; + return regmap_update_bits(st->regmap, ADXL345_REG_INT_ENABLE, + watermark_mask, ADXL345_INT_WATERMARK); +} + +static int adxl345_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static void adxl345_powerdown(void *ptr) +{ + struct adxl345_state *st = ptr; + + adxl345_set_measure_en(st, false); +} + +static int adxl345_set_fifo(struct adxl345_state *st) +{ + unsigned int intio; + int ret; + + /* FIFO should only be configured while in standby mode */ + ret = adxl345_set_measure_en(st, false); + if (ret) + return ret; + + ret = regmap_read(st->regmap, ADXL345_REG_INT_MAP, &intio); + if (ret) + return ret; + + ret = regmap_write(st->regmap, ADXL345_REG_FIFO_CTL, + FIELD_PREP(ADXL345_FIFO_CTL_SAMPLES_MSK, + st->watermark) | + FIELD_PREP(ADXL345_FIFO_CTL_TRIGGER_MSK, intio) | + FIELD_PREP(ADXL345_FIFO_CTL_MODE_MSK, + st->fifo_mode)); + if (ret) + return ret; + + return adxl345_set_measure_en(st, true); +} + +/** + * adxl345_get_samples() - Read number of FIFO entries. + * @st: The initialized state instance of this driver. + * + * The sensor does not support treating any axis individually, or exclude them + * from measuring. + * + * Return: negative error, or value. + */ +static int adxl345_get_samples(struct adxl345_state *st) +{ + unsigned int regval = 0; + int ret; + + ret = regmap_read(st->regmap, ADXL345_REG_FIFO_STATUS, ®val); + if (ret) + return ret; + + return FIELD_GET(ADXL345_REG_FIFO_STATUS_MSK, regval); +} + +/** + * adxl345_fifo_transfer() - Read samples number of elements. + * @st: The instance of the state object of this sensor. + * @samples: The number of lines in the FIFO referred to as fifo_entry. + * + * It is recommended that a multiple-byte read of all registers be performed to + * prevent a change in data between reads of sequential registers. That is to + * read out the data registers X0, X1, Y0, Y1, Z0, Z1, i.e. 6 bytes at once. + * + * Return: 0 or error value. + */ +static int adxl345_fifo_transfer(struct adxl345_state *st, int samples) +{ + int i, ret = 0; + + for (i = 0; i < samples; i++) { + ret = regmap_bulk_read(st->regmap, ADXL345_REG_XYZ_BASE, + st->fifo_buf + (i * ADXL345_DIRS), + sizeof(st->fifo_buf[0]) * ADXL345_DIRS); + if (ret) + return ret; + + /* + * To ensure that the FIFO has completely popped, there must be at least 5 + * us between the end of reading the data registers, signified by the + * transition to register 0x38 from 0x37 or the CS pin going high, and the + * start of new reads of the FIFO or reading the FIFO_STATUS register. For + * SPI operation at 1.5 MHz or lower, the register addressing portion of the + * transmission is sufficient delay to ensure the FIFO has completely + * popped. It is necessary for SPI operation greater than 1.5 MHz to + * de-assert the CS pin to ensure a total of 5 us, which is at most 3.4 us + * at 5 MHz operation. + */ + if (st->fifo_delay && samples > 1) + udelay(3); + } + return ret; +} + +/** + * adxl345_fifo_reset() - Empty the FIFO in error condition. + * @st: The instance to the state object of the sensor. + * + * Read all elements of the FIFO. Reading the interrupt source register + * resets the sensor. + */ +static void adxl345_fifo_reset(struct adxl345_state *st) +{ + int regval; + int samples; + + adxl345_set_measure_en(st, false); + + samples = adxl345_get_samples(st); + if (samples > 0) + adxl345_fifo_transfer(st, samples); + + regmap_read(st->regmap, ADXL345_REG_INT_SOURCE, ®val); + + adxl345_set_measure_en(st, true); +} + +static int adxl345_buffer_postenable(struct iio_dev *indio_dev) +{ + struct adxl345_state *st = iio_priv(indio_dev); + + st->fifo_mode = ADXL345_FIFO_STREAM; + return adxl345_set_fifo(st); +} + +static int adxl345_buffer_predisable(struct iio_dev *indio_dev) +{ + struct adxl345_state *st = iio_priv(indio_dev); + int ret; + + st->fifo_mode = ADXL345_FIFO_BYPASS; + ret = adxl345_set_fifo(st); + if (ret) + return ret; + + return regmap_write(st->regmap, ADXL345_REG_INT_ENABLE, 0x00); +} + +static const struct iio_buffer_setup_ops adxl345_buffer_ops = { + .postenable = adxl345_buffer_postenable, + .predisable = adxl345_buffer_predisable, +}; + +static int adxl345_fifo_push(struct iio_dev *indio_dev, + int samples) +{ + struct adxl345_state *st = iio_priv(indio_dev); + int i, ret; + + if (samples <= 0) + return -EINVAL; + + ret = adxl345_fifo_transfer(st, samples); + if (ret) + return ret; + + for (i = 0; i < ADXL345_DIRS * samples; i += ADXL345_DIRS) + iio_push_to_buffers(indio_dev, &st->fifo_buf[i]); + + return 0; +} + +static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat, + enum iio_modifier act_dir, + enum iio_modifier tap_dir) +{ + s64 ts = iio_get_time_ns(indio_dev); + struct adxl345_state *st = iio_priv(indio_dev); + unsigned int regval; + int samples; + int ret = -ENOENT; + + if (FIELD_GET(ADXL345_INT_SINGLE_TAP, int_stat)) { + ret = iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, tap_dir, + IIO_EV_TYPE_GESTURE, + IIO_EV_DIR_SINGLETAP), + ts); + if (ret) + return ret; + } + + if (FIELD_GET(ADXL345_INT_DOUBLE_TAP, int_stat)) { + ret = iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, tap_dir, + IIO_EV_TYPE_GESTURE, + IIO_EV_DIR_DOUBLETAP), + ts); + if (ret) + return ret; + } + + if (FIELD_GET(ADXL345_INT_ACTIVITY, int_stat)) { + ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, ®val); + if (ret) + return ret; + + if (FIELD_GET(ADXL345_REG_ACT_ACDC, regval)) { + /* AC coupled */ + ret = iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, act_dir, + IIO_EV_TYPE_MAG_ADAPTIVE, + IIO_EV_DIR_RISING), + ts); + + } else { + /* DC coupled, relying on THRESH */ + ret = iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, act_dir, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_RISING), + ts); + } + if (ret) + return ret; + } + + if (FIELD_GET(ADXL345_INT_INACTIVITY, int_stat)) { + ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, ®val); + if (ret) + return ret; + + if (FIELD_GET(ADXL345_REG_INACT_ACDC, regval)) { + /* AC coupled */ + ret = iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_AND_Y_AND_Z, + IIO_EV_TYPE_MAG_ADAPTIVE, + IIO_EV_DIR_FALLING), + ts); + } else { + /* DC coupled, relying on THRESH */ + ret = iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_AND_Y_AND_Z, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_FALLING), + ts); + } + if (ret) + return ret; + } + + if (FIELD_GET(ADXL345_INT_FREE_FALL, int_stat)) { + ret = iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_AND_Y_AND_Z, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_FALLING), + ts); + if (ret) + return ret; + } + + if (FIELD_GET(ADXL345_INT_WATERMARK, int_stat)) { + samples = adxl345_get_samples(st); + if (samples < 0) + return -EINVAL; + + if (adxl345_fifo_push(indio_dev, samples) < 0) + return -EINVAL; + + ret = 0; + } + + return ret; +} + +/** + * adxl345_irq_handler() - Handle irqs of the ADXL345. + * @irq: The irq being handled. + * @p: The struct iio_device pointer for the device. + * + * Return: The interrupt was handled. + */ +static irqreturn_t adxl345_irq_handler(int irq, void *p) +{ + struct iio_dev *indio_dev = p; + struct adxl345_state *st = iio_priv(indio_dev); + unsigned int regval; + enum iio_modifier tap_dir = IIO_NO_MOD; + enum iio_modifier act_dir = IIO_NO_MOD; + u32 axis_ctrl; + int int_stat; + int ret; + + ret = regmap_read(st->regmap, ADXL345_REG_TAP_AXIS, &axis_ctrl); + if (ret) + return IRQ_NONE; + + if (FIELD_GET(ADXL345_REG_TAP_AXIS_MSK, axis_ctrl) || + FIELD_GET(ADXL345_ACT_XYZ_EN, axis_ctrl)) { + ret = regmap_read(st->regmap, ADXL345_REG_ACT_TAP_STATUS, ®val); + if (ret) + return IRQ_NONE; + + if (FIELD_GET(ADXL345_TAP_Z_EN, regval)) + tap_dir = IIO_MOD_Z; + else if (FIELD_GET(ADXL345_TAP_Y_EN, regval)) + tap_dir = IIO_MOD_Y; + else if (FIELD_GET(ADXL345_TAP_X_EN, regval)) + tap_dir = IIO_MOD_X; + + if (FIELD_GET(ADXL345_ACT_Z_EN, regval)) + act_dir = IIO_MOD_Z; + else if (FIELD_GET(ADXL345_ACT_Y_EN, regval)) + act_dir = IIO_MOD_Y; + else if (FIELD_GET(ADXL345_ACT_X_EN, regval)) + act_dir = IIO_MOD_X; + } + + if (regmap_read(st->regmap, ADXL345_REG_INT_SOURCE, &int_stat)) + return IRQ_NONE; + + if (adxl345_push_event(indio_dev, int_stat, act_dir, tap_dir)) + goto err; + + if (FIELD_GET(ADXL345_INT_OVERRUN, int_stat)) + goto err; + + return IRQ_HANDLED; + +err: + adxl345_fifo_reset(st); + + return IRQ_HANDLED; +} + +static const struct iio_info adxl345_info = { + .read_raw = adxl345_read_raw, + .write_raw = adxl345_write_raw, + .read_avail = adxl345_read_avail, + .write_raw_get_fmt = adxl345_write_raw_get_fmt, + .read_event_config = adxl345_read_event_config, + .write_event_config = adxl345_write_event_config, + .read_event_value = adxl345_read_event_value, + .write_event_value = adxl345_write_event_value, + .debugfs_reg_access = &adxl345_reg_access, + .hwfifo_set_watermark = adxl345_set_watermark, +}; + +static int adxl345_get_int_line(struct device *dev, int *irq) +{ + *irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT1"); + if (*irq > 0) + return ADXL345_INT1; + + *irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT2"); + if (*irq > 0) + return ADXL345_INT2; + + return ADXL345_INT_NONE; +} + +/** + * adxl345_core_probe() - Probe and setup for the accelerometer. + * @dev: Driver model representation of the device + * @regmap: Regmap instance for the device + * @fifo_delay_default: Using FIFO with SPI needs delay + * @setup: Setup routine to be executed right before the standard device + * setup + * + * For SPI operation greater than 1.6 MHz, it is necessary to deassert the CS + * pin to ensure a total delay of 5 us; otherwise, the delay is not sufficient. + * The total delay necessary for 5 MHz operation is at most 3.4 us. This is not + * a concern when using I2C mode because the communication rate is low enough + * to ensure a sufficient delay between FIFO reads. + * Ref: "Retrieving Data from FIFO", p. 21 of 36, Data Sheet ADXL345 Rev. G + * + * Return: 0 on success, negative errno on error + */ +int adxl345_core_probe(struct device *dev, struct regmap *regmap, + bool fifo_delay_default, + int (*setup)(struct device*, struct regmap*)) +{ + struct adxl345_state *st; + struct iio_dev *indio_dev; + u32 regval; + u8 intio = ADXL345_INT1; + unsigned int data_format_mask = (ADXL345_DATA_FORMAT_RANGE | + ADXL345_DATA_FORMAT_JUSTIFY | + ADXL345_DATA_FORMAT_FULL_RES | + ADXL345_DATA_FORMAT_SELF_TEST); + unsigned int tap_threshold; + int irq; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->regmap = regmap; + st->info = device_get_match_data(dev); + if (!st->info) + return -ENODEV; + st->fifo_delay = fifo_delay_default; + + /* Init with reasonable values */ + tap_threshold = 48; /* 48 [0x30] -> ~3g */ + st->tap_duration_us = 16; /* 16 [0x10] -> .010 */ + st->tap_window_us = 64; /* 64 [0x40] -> .080 */ + st->tap_latent_us = 16; /* 16 [0x10] -> .020 */ + + indio_dev->name = st->info->name; + indio_dev->info = &adxl345_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = adxl345_channels; + indio_dev->num_channels = ARRAY_SIZE(adxl345_channels); + indio_dev->available_scan_masks = adxl345_scan_masks; + + /* + * Using I2C at 100kHz would limit the maximum ODR to 200Hz, operation + * at an output rate above the recommended maximum may result in + * undesired behavior. + */ + ret = adxl345_set_odr(st, ADXL345_ODR_200HZ); + if (ret) + return ret; + + ret = adxl345_set_range(st, ADXL345_16G_RANGE); + if (ret) + return ret; + + /* Reset interrupts at start up */ + ret = regmap_write(st->regmap, ADXL345_REG_INT_ENABLE, 0x00); + if (ret) + return ret; + + if (setup) { + /* Perform optional initial bus specific configuration */ + ret = setup(dev, st->regmap); + if (ret) + return ret; + + /* Enable full-resolution mode */ + ret = regmap_update_bits(st->regmap, ADXL345_REG_DATA_FORMAT, + data_format_mask, + ADXL345_DATA_FORMAT_FULL_RES); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set data range\n"); + + } else { + /* Enable full-resolution mode (init all data_format bits) */ + ret = regmap_write(st->regmap, ADXL345_REG_DATA_FORMAT, + ADXL345_DATA_FORMAT_FULL_RES); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set data range\n"); + } + + ret = regmap_read(st->regmap, ADXL345_REG_DEVID, ®val); + if (ret) + return dev_err_probe(dev, ret, "Error reading device ID\n"); + + if (regval != ADXL345_DEVID) + return dev_err_probe(dev, -ENODEV, "Invalid device ID: %x, expected %x\n", + regval, ADXL345_DEVID); + + /* Enable measurement mode */ + ret = adxl345_set_measure_en(st, true); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable measurement mode\n"); + + ret = devm_add_action_or_reset(dev, adxl345_powerdown, st); + if (ret) + return ret; + + intio = adxl345_get_int_line(dev, &irq); + if (intio != ADXL345_INT_NONE) { + /* + * In the INT map register, bits set to 0 route their + * corresponding interrupts to the INT1 pin, while bits set to 1 + * route them to the INT2 pin. The intio should handle this + * mapping accordingly. + */ + ret = regmap_assign_bits(st->regmap, ADXL345_REG_INT_MAP, + U8_MAX, intio); + if (ret) + return ret; + + /* + * Initialized with sensible default values to streamline + * sensor operation. These defaults are partly derived from + * the previous input driver for the ADXL345 and partly + * based on the recommendations provided in the datasheet. + */ + ret = regmap_write(st->regmap, ADXL345_REG_ACT_INACT_CTRL, 0); + if (ret) + return ret; + + ret = regmap_write(st->regmap, ADXL345_REG_THRESH_ACT, 6); + if (ret) + return ret; + + ret = adxl345_set_inact_threshold(st, 4); + if (ret) + return ret; + + ret = regmap_write(st->regmap, ADXL345_REG_THRESH_TAP, tap_threshold); + if (ret) + return ret; + + /* FIFO_STREAM mode is going to be activated later */ + ret = devm_iio_kfifo_buffer_setup(dev, indio_dev, &adxl345_buffer_ops); + if (ret) + return ret; + + ret = devm_request_threaded_irq(dev, irq, NULL, + &adxl345_irq_handler, + IRQF_SHARED | IRQF_ONESHOT, + indio_dev->name, indio_dev); + if (ret) + return ret; + } else { + ret = regmap_write(st->regmap, ADXL345_REG_FIFO_CTL, + FIELD_PREP(ADXL345_FIFO_CTL_MODE_MSK, + ADXL345_FIFO_BYPASS)); + if (ret) + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS_GPL(adxl345_core_probe, "IIO_ADXL345"); + +MODULE_AUTHOR("Eva Rachel Retuya "); +MODULE_DESCRIPTION("ADXL345 3-Axis Digital Accelerometer core driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/adxl345_i2c.c b/drivers/iio/accel/adxl345_i2c.c new file mode 100644 index 000000000..511fb6104 --- /dev/null +++ b/drivers/iio/accel/adxl345_i2c.c @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADXL345 3-Axis Digital Accelerometer I2C driver + * + * Copyright (c) 2017 Eva Rachel Retuya + * + * 7-bit I2C slave address: 0x1D (ALT ADDRESS pin tied to VDDIO) or + * 0x53 (ALT ADDRESS pin grounded) + */ + +#include +#include +#include + +#include "adxl345.h" + +static const struct regmap_config adxl345_i2c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = adxl345_is_volatile_reg, + .cache_type = REGCACHE_MAPLE, +}; + +static int adxl345_i2c_probe(struct i2c_client *client) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, &adxl345_i2c_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(&client->dev, PTR_ERR(regmap), "Error initializing regmap\n"); + + return adxl345_core_probe(&client->dev, regmap, false, NULL); +} + +static const struct adxl345_chip_info adxl345_i2c_info = { + .name = "adxl345", + .uscale = ADXL345_USCALE, +}; + +static const struct adxl345_chip_info adxl375_i2c_info = { + .name = "adxl375", + .uscale = ADXL375_USCALE, +}; + +static const struct i2c_device_id adxl345_i2c_id[] = { + { .name = "adxl345", .driver_data = (kernel_ulong_t)&adxl345_i2c_info }, + { .name = "adxl375", .driver_data = (kernel_ulong_t)&adxl375_i2c_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adxl345_i2c_id); + +static const struct of_device_id adxl345_of_match[] = { + { .compatible = "adi,adxl345", .data = &adxl345_i2c_info }, + { .compatible = "adi,adxl375", .data = &adxl375_i2c_info }, + { } +}; +MODULE_DEVICE_TABLE(of, adxl345_of_match); + +static const struct acpi_device_id adxl345_acpi_match[] = { + { "ADS0345", (kernel_ulong_t)&adxl345_i2c_info }, + { } +}; +MODULE_DEVICE_TABLE(acpi, adxl345_acpi_match); + +static struct i2c_driver adxl345_i2c_driver = { + .driver = { + .name = "adxl345_i2c", + .of_match_table = adxl345_of_match, + .acpi_match_table = adxl345_acpi_match, + }, + .probe = adxl345_i2c_probe, + .id_table = adxl345_i2c_id, +}; +module_i2c_driver(adxl345_i2c_driver); + +MODULE_AUTHOR("Eva Rachel Retuya "); +MODULE_DESCRIPTION("ADXL345 3-Axis Digital Accelerometer I2C driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ADXL345"); diff --git a/drivers/iio/accel/adxl345_spi.c b/drivers/iio/accel/adxl345_spi.c new file mode 100644 index 000000000..4fa1b04f7 --- /dev/null +++ b/drivers/iio/accel/adxl345_spi.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADXL345 3-Axis Digital Accelerometer SPI driver + * + * Copyright (c) 2017 Eva Rachel Retuya + */ + +#include +#include +#include + +#include "adxl345.h" + +#define ADXL345_MAX_SPI_FREQ_HZ 5000000 +#define ADXL345_MAX_FREQ_NO_FIFO_DELAY 1500000 + +static const struct regmap_config adxl345_spi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + /* Setting bits 7 and 6 enables multiple-byte read */ + .read_flag_mask = BIT(7) | BIT(6), + .volatile_reg = adxl345_is_volatile_reg, + .cache_type = REGCACHE_MAPLE, +}; + +static int adxl345_spi_setup(struct device *dev, struct regmap *regmap) +{ + return regmap_write(regmap, ADXL345_REG_DATA_FORMAT, ADXL345_DATA_FORMAT_SPI_3WIRE); +} + +static int adxl345_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + bool needs_delay; + + /* Bail out if max_speed_hz exceeds 5 MHz */ + if (spi->max_speed_hz > ADXL345_MAX_SPI_FREQ_HZ) + return dev_err_probe(&spi->dev, -EINVAL, "SPI CLK, %d Hz exceeds 5 MHz\n", + spi->max_speed_hz); + + regmap = devm_regmap_init_spi(spi, &adxl345_spi_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(&spi->dev, PTR_ERR(regmap), "Error initializing regmap\n"); + + needs_delay = spi->max_speed_hz > ADXL345_MAX_FREQ_NO_FIFO_DELAY; + if (spi->mode & SPI_3WIRE) + return adxl345_core_probe(&spi->dev, regmap, needs_delay, adxl345_spi_setup); + else + return adxl345_core_probe(&spi->dev, regmap, needs_delay, NULL); +} + +static const struct adxl345_chip_info adxl345_spi_info = { + .name = "adxl345", + .uscale = ADXL345_USCALE, +}; + +static const struct adxl345_chip_info adxl375_spi_info = { + .name = "adxl375", + .uscale = ADXL375_USCALE, +}; + +static const struct spi_device_id adxl345_spi_id[] = { + { .name = "adxl345", .driver_data = (kernel_ulong_t)&adxl345_spi_info }, + { .name = "adxl375", .driver_data = (kernel_ulong_t)&adxl375_spi_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, adxl345_spi_id); + +static const struct of_device_id adxl345_of_match[] = { + { .compatible = "adi,adxl345", .data = &adxl345_spi_info }, + { .compatible = "adi,adxl375", .data = &adxl375_spi_info }, + { } +}; +MODULE_DEVICE_TABLE(of, adxl345_of_match); + +static const struct acpi_device_id adxl345_acpi_match[] = { + { "ADS0345", (kernel_ulong_t)&adxl345_spi_info }, + { } +}; +MODULE_DEVICE_TABLE(acpi, adxl345_acpi_match); + +static struct spi_driver adxl345_spi_driver = { + .driver = { + .name = "adxl345_spi", + .of_match_table = adxl345_of_match, + .acpi_match_table = adxl345_acpi_match, + }, + .probe = adxl345_spi_probe, + .id_table = adxl345_spi_id, +}; +module_spi_driver(adxl345_spi_driver); + +MODULE_AUTHOR("Eva Rachel Retuya "); +MODULE_DESCRIPTION("ADXL345 3-Axis Digital Accelerometer SPI driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ADXL345"); diff --git a/drivers/iio/accel/adxl355.h b/drivers/iio/accel/adxl355.h new file mode 100644 index 000000000..061e66dc7 --- /dev/null +++ b/drivers/iio/accel/adxl355.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * ADXL355 3-Axis Digital Accelerometer + * + * Copyright (c) 2021 Puranjay Mohan + */ + +#ifndef _ADXL355_H_ +#define _ADXL355_H_ + +#include + +enum adxl355_device_type { + ADXL355, + ADXL359, +}; + +struct adxl355_fractional_type { + int integer; + int decimal; +}; + +struct device; + +struct adxl355_chip_info { + const char *name; + u8 part_id; + struct adxl355_fractional_type accel_scale; + struct adxl355_fractional_type temp_offset; +}; + +extern const struct regmap_access_table adxl355_readable_regs_tbl; +extern const struct regmap_access_table adxl355_writeable_regs_tbl; +extern const struct adxl355_chip_info adxl35x_chip_info[]; + +int adxl355_core_probe(struct device *dev, struct regmap *regmap, + const struct adxl355_chip_info *chip_info); + +#endif /* _ADXL355_H_ */ diff --git a/drivers/iio/accel/adxl355_core.c b/drivers/iio/accel/adxl355_core.c new file mode 100644 index 000000000..080c9f6f9 --- /dev/null +++ b/drivers/iio/accel/adxl355_core.c @@ -0,0 +1,833 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADXL355 3-Axis Digital Accelerometer IIO core driver + * + * Copyright (c) 2021 Puranjay Mohan + * + * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/adxl354_adxl355.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "adxl355.h" + +/* ADXL355 Register Definitions */ +#define ADXL355_DEVID_AD_REG 0x00 +#define ADXL355_DEVID_MST_REG 0x01 +#define ADXL355_PARTID_REG 0x02 +#define ADXL355_STATUS_REG 0x04 +#define ADXL355_FIFO_ENTRIES_REG 0x05 +#define ADXL355_TEMP2_REG 0x06 +#define ADXL355_XDATA3_REG 0x08 +#define ADXL355_YDATA3_REG 0x0B +#define ADXL355_ZDATA3_REG 0x0E +#define ADXL355_FIFO_DATA_REG 0x11 +#define ADXL355_OFFSET_X_H_REG 0x1E +#define ADXL355_OFFSET_Y_H_REG 0x20 +#define ADXL355_OFFSET_Z_H_REG 0x22 +#define ADXL355_ACT_EN_REG 0x24 +#define ADXL355_ACT_THRESH_H_REG 0x25 +#define ADXL355_ACT_THRESH_L_REG 0x26 +#define ADXL355_ACT_COUNT_REG 0x27 +#define ADXL355_FILTER_REG 0x28 +#define ADXL355_FILTER_ODR_MSK GENMASK(3, 0) +#define ADXL355_FILTER_HPF_MSK GENMASK(6, 4) +#define ADXL355_FIFO_SAMPLES_REG 0x29 +#define ADXL355_INT_MAP_REG 0x2A +#define ADXL355_SYNC_REG 0x2B +#define ADXL355_RANGE_REG 0x2C +#define ADXL355_POWER_CTL_REG 0x2D +#define ADXL355_POWER_CTL_MODE_MSK GENMASK(1, 0) +#define ADXL355_POWER_CTL_DRDY_MSK BIT(2) +#define ADXL355_SELF_TEST_REG 0x2E +#define ADXL355_RESET_REG 0x2F +#define ADXL355_BASE_ADDR_SHADOW_REG 0x50 +#define ADXL355_SHADOW_REG_COUNT 5 + +#define ADXL355_DEVID_AD_VAL 0xAD +#define ADXL355_DEVID_MST_VAL 0x1D +#define ADXL355_PARTID_VAL 0xED +#define ADXL359_PARTID_VAL 0xE9 +#define ADXL355_RESET_CODE 0x52 + +static const struct regmap_range adxl355_read_reg_range[] = { + regmap_reg_range(ADXL355_DEVID_AD_REG, ADXL355_FIFO_DATA_REG), + regmap_reg_range(ADXL355_OFFSET_X_H_REG, ADXL355_SELF_TEST_REG), +}; + +const struct regmap_access_table adxl355_readable_regs_tbl = { + .yes_ranges = adxl355_read_reg_range, + .n_yes_ranges = ARRAY_SIZE(adxl355_read_reg_range), +}; +EXPORT_SYMBOL_NS_GPL(adxl355_readable_regs_tbl, "IIO_ADXL355"); + +static const struct regmap_range adxl355_write_reg_range[] = { + regmap_reg_range(ADXL355_OFFSET_X_H_REG, ADXL355_RESET_REG), +}; + +const struct regmap_access_table adxl355_writeable_regs_tbl = { + .yes_ranges = adxl355_write_reg_range, + .n_yes_ranges = ARRAY_SIZE(adxl355_write_reg_range), +}; +EXPORT_SYMBOL_NS_GPL(adxl355_writeable_regs_tbl, "IIO_ADXL355"); + +const struct adxl355_chip_info adxl35x_chip_info[] = { + [ADXL355] = { + .name = "adxl355", + .part_id = ADXL355_PARTID_VAL, + /* + * At +/- 2g with 20-bit resolution, scale is given in datasheet + * as 3.9ug/LSB = 0.0000039 * 9.80665 = 0.00003824593 m/s^2. + */ + .accel_scale = { + .integer = 0, + .decimal = 38245, + }, + /* + * The datasheet defines an intercept of 1885 LSB at 25 degC + * and a slope of -9.05 LSB/C. The following formula can be used + * to find the temperature: + * Temp = ((RAW - 1885)/(-9.05)) + 25 but this doesn't follow + * the format of the IIO which is Temp = (RAW + OFFSET) * SCALE. + * Hence using some rearranging we get the scale as -110.497238 + * and offset as -2111.25. + */ + .temp_offset = { + .integer = -2111, + .decimal = 250000, + }, + }, + [ADXL359] = { + .name = "adxl359", + .part_id = ADXL359_PARTID_VAL, + /* + * At +/- 10g with 20-bit resolution, scale is given in datasheet + * as 19.5ug/LSB = 0.0000195 * 9.80665 = 0.0.00019122967 m/s^2. + */ + .accel_scale = { + .integer = 0, + .decimal = 191229, + }, + /* + * The datasheet defines an intercept of 1852 LSB at 25 degC + * and a slope of -9.05 LSB/C. The following formula can be used + * to find the temperature: + * Temp = ((RAW - 1852)/(-9.05)) + 25 but this doesn't follow + * the format of the IIO which is Temp = (RAW + OFFSET) * SCALE. + * Hence using some rearranging we get the scale as -110.497238 + * and offset as -2079.25. + */ + .temp_offset = { + .integer = -2079, + .decimal = 250000, + }, + }, +}; +EXPORT_SYMBOL_NS_GPL(adxl35x_chip_info, "IIO_ADXL355"); + +enum adxl355_op_mode { + ADXL355_MEASUREMENT, + ADXL355_STANDBY, + ADXL355_TEMP_OFF, +}; + +enum adxl355_odr { + ADXL355_ODR_4000HZ, + ADXL355_ODR_2000HZ, + ADXL355_ODR_1000HZ, + ADXL355_ODR_500HZ, + ADXL355_ODR_250HZ, + ADXL355_ODR_125HZ, + ADXL355_ODR_62_5HZ, + ADXL355_ODR_31_25HZ, + ADXL355_ODR_15_625HZ, + ADXL355_ODR_7_813HZ, + ADXL355_ODR_3_906HZ, +}; + +enum adxl355_hpf_3db { + ADXL355_HPF_OFF, + ADXL355_HPF_24_7, + ADXL355_HPF_6_2084, + ADXL355_HPF_1_5545, + ADXL355_HPF_0_3862, + ADXL355_HPF_0_0954, + ADXL355_HPF_0_0238, +}; + +static const int adxl355_odr_table[][2] = { + [0] = {4000, 0}, + [1] = {2000, 0}, + [2] = {1000, 0}, + [3] = {500, 0}, + [4] = {250, 0}, + [5] = {125, 0}, + [6] = {62, 500000}, + [7] = {31, 250000}, + [8] = {15, 625000}, + [9] = {7, 813000}, + [10] = {3, 906000}, +}; + +static const int adxl355_hpf_3db_multipliers[] = { + 0, + 247000, + 62084, + 15545, + 3862, + 954, + 238, +}; + +enum adxl355_chans { + chan_x, chan_y, chan_z, +}; + +struct adxl355_chan_info { + u8 data_reg; + u8 offset_reg; +}; + +static const struct adxl355_chan_info adxl355_chans[] = { + [chan_x] = { + .data_reg = ADXL355_XDATA3_REG, + .offset_reg = ADXL355_OFFSET_X_H_REG + }, + [chan_y] = { + .data_reg = ADXL355_YDATA3_REG, + .offset_reg = ADXL355_OFFSET_Y_H_REG + }, + [chan_z] = { + .data_reg = ADXL355_ZDATA3_REG, + .offset_reg = ADXL355_OFFSET_Z_H_REG + }, +}; + +struct adxl355_data { + const struct adxl355_chip_info *chip_info; + struct regmap *regmap; + struct device *dev; + struct mutex lock; /* lock to protect op_mode */ + enum adxl355_op_mode op_mode; + enum adxl355_odr odr; + enum adxl355_hpf_3db hpf_3db; + int calibbias[3]; + int adxl355_hpf_3db_table[7][2]; + struct iio_trigger *dready_trig; + union { + u8 transf_buf[3]; + struct { + u8 buf[14]; + aligned_s64 ts; + } buffer; + } __aligned(IIO_DMA_MINALIGN); +}; + +static int adxl355_set_op_mode(struct adxl355_data *data, + enum adxl355_op_mode op_mode) +{ + int ret; + + if (data->op_mode == op_mode) + return 0; + + ret = regmap_update_bits(data->regmap, ADXL355_POWER_CTL_REG, + ADXL355_POWER_CTL_MODE_MSK, op_mode); + if (ret) + return ret; + + data->op_mode = op_mode; + + return ret; +} + +static int adxl355_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct adxl355_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->lock); + ret = regmap_update_bits(data->regmap, ADXL355_POWER_CTL_REG, + ADXL355_POWER_CTL_DRDY_MSK, + FIELD_PREP(ADXL355_POWER_CTL_DRDY_MSK, + state ? 0 : 1)); + mutex_unlock(&data->lock); + + return ret; +} + +static void adxl355_fill_3db_frequency_table(struct adxl355_data *data) +{ + u32 multiplier; + u64 div, rem; + u64 odr; + int i; + + odr = mul_u64_u32_shr(adxl355_odr_table[data->odr][0], MEGA, 0) + + adxl355_odr_table[data->odr][1]; + + for (i = 0; i < ARRAY_SIZE(adxl355_hpf_3db_multipliers); i++) { + multiplier = adxl355_hpf_3db_multipliers[i]; + div = div64_u64_rem(mul_u64_u32_shr(odr, multiplier, 0), + TERA * 100, &rem); + + data->adxl355_hpf_3db_table[i][0] = div; + data->adxl355_hpf_3db_table[i][1] = div_u64(rem, MEGA * 100); + } +} + +static int adxl355_setup(struct adxl355_data *data) +{ + unsigned int regval; + int retries = 5; /* the number is chosen based on empirical reasons */ + int ret; + u8 *shadow_regs __free(kfree) = kzalloc(ADXL355_SHADOW_REG_COUNT, GFP_KERNEL); + + if (!shadow_regs) + return -ENOMEM; + + ret = regmap_read(data->regmap, ADXL355_DEVID_AD_REG, ®val); + if (ret) + return ret; + + if (regval != ADXL355_DEVID_AD_VAL) { + dev_err(data->dev, "Invalid ADI ID 0x%02x\n", regval); + return -ENODEV; + } + + ret = regmap_read(data->regmap, ADXL355_DEVID_MST_REG, ®val); + if (ret) + return ret; + + if (regval != ADXL355_DEVID_MST_VAL) { + dev_err(data->dev, "Invalid MEMS ID 0x%02x\n", regval); + return -ENODEV; + } + + ret = regmap_read(data->regmap, ADXL355_PARTID_REG, ®val); + if (ret) + return ret; + + if (regval != ADXL355_PARTID_VAL) + dev_warn(data->dev, "Invalid DEV ID 0x%02x\n", regval); + + /* Read shadow registers to be compared after reset */ + ret = regmap_bulk_read(data->regmap, + ADXL355_BASE_ADDR_SHADOW_REG, + shadow_regs, ADXL355_SHADOW_REG_COUNT); + if (ret) + return ret; + + do { + if (--retries == 0) + return dev_err_probe(data->dev, -EIO, "Shadow registers mismatch\n"); + + /* + * Perform a software reset to make sure the device is in a consistent + * state after start-up. + */ + ret = regmap_write(data->regmap, ADXL355_RESET_REG, + ADXL355_RESET_CODE); + if (ret) + return ret; + + /* Wait at least 5ms after software reset */ + fsleep(5 * USEC_PER_MSEC); + + /* Read shadow registers for comparison */ + ret = regmap_bulk_read(data->regmap, + ADXL355_BASE_ADDR_SHADOW_REG, + data->buffer.buf, + ADXL355_SHADOW_REG_COUNT); + if (ret) + return ret; + } while (memcmp(shadow_regs, data->buffer.buf, + ADXL355_SHADOW_REG_COUNT)); + + ret = regmap_update_bits(data->regmap, ADXL355_POWER_CTL_REG, + ADXL355_POWER_CTL_DRDY_MSK, + FIELD_PREP(ADXL355_POWER_CTL_DRDY_MSK, 1)); + if (ret) + return ret; + + adxl355_fill_3db_frequency_table(data); + + return adxl355_set_op_mode(data, ADXL355_MEASUREMENT); +} + +static int adxl355_get_temp_data(struct adxl355_data *data, u8 addr) +{ + return regmap_bulk_read(data->regmap, addr, data->transf_buf, 2); +} + +static int adxl355_read_axis(struct adxl355_data *data, u8 addr) +{ + int ret; + + ret = regmap_bulk_read(data->regmap, addr, data->transf_buf, + ARRAY_SIZE(data->transf_buf)); + if (ret) + return ret; + + return get_unaligned_be24(data->transf_buf); +} + +static int adxl355_find_match(const int (*freq_tbl)[2], const int n, + const int val, const int val2) +{ + int i; + + for (i = 0; i < n; i++) { + if (freq_tbl[i][0] == val && freq_tbl[i][1] == val2) + return i; + } + + return -EINVAL; +} + +static int adxl355_set_odr(struct adxl355_data *data, + enum adxl355_odr odr) +{ + int ret; + + mutex_lock(&data->lock); + + if (data->odr == odr) { + mutex_unlock(&data->lock); + return 0; + } + + ret = adxl355_set_op_mode(data, ADXL355_STANDBY); + if (ret) + goto err_unlock; + + ret = regmap_update_bits(data->regmap, ADXL355_FILTER_REG, + ADXL355_FILTER_ODR_MSK, + FIELD_PREP(ADXL355_FILTER_ODR_MSK, odr)); + if (ret) + goto err_set_opmode; + + data->odr = odr; + adxl355_fill_3db_frequency_table(data); + + ret = adxl355_set_op_mode(data, ADXL355_MEASUREMENT); + if (ret) + goto err_set_opmode; + + mutex_unlock(&data->lock); + return 0; + +err_set_opmode: + adxl355_set_op_mode(data, ADXL355_MEASUREMENT); +err_unlock: + mutex_unlock(&data->lock); + return ret; +} + +static int adxl355_set_hpf_3db(struct adxl355_data *data, + enum adxl355_hpf_3db hpf) +{ + int ret; + + mutex_lock(&data->lock); + + if (data->hpf_3db == hpf) { + mutex_unlock(&data->lock); + return 0; + } + + ret = adxl355_set_op_mode(data, ADXL355_STANDBY); + if (ret) + goto err_unlock; + + ret = regmap_update_bits(data->regmap, ADXL355_FILTER_REG, + ADXL355_FILTER_HPF_MSK, + FIELD_PREP(ADXL355_FILTER_HPF_MSK, hpf)); + if (ret) + goto err_set_opmode; + + data->hpf_3db = hpf; + + ret = adxl355_set_op_mode(data, ADXL355_MEASUREMENT); + if (ret) + goto err_set_opmode; + + mutex_unlock(&data->lock); + return 0; + +err_set_opmode: + adxl355_set_op_mode(data, ADXL355_MEASUREMENT); +err_unlock: + mutex_unlock(&data->lock); + return ret; +} + +static int adxl355_set_calibbias(struct adxl355_data *data, + enum adxl355_chans chan, int calibbias) +{ + int ret; + + mutex_lock(&data->lock); + + ret = adxl355_set_op_mode(data, ADXL355_STANDBY); + if (ret) + goto err_unlock; + + put_unaligned_be16(calibbias, data->transf_buf); + ret = regmap_bulk_write(data->regmap, + adxl355_chans[chan].offset_reg, + data->transf_buf, 2); + if (ret) + goto err_set_opmode; + + data->calibbias[chan] = calibbias; + + ret = adxl355_set_op_mode(data, ADXL355_MEASUREMENT); + if (ret) + goto err_set_opmode; + + mutex_unlock(&data->lock); + return 0; + +err_set_opmode: + adxl355_set_op_mode(data, ADXL355_MEASUREMENT); +err_unlock: + mutex_unlock(&data->lock); + return ret; +} + +static int adxl355_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct adxl355_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_TEMP: + ret = adxl355_get_temp_data(data, chan->address); + if (ret < 0) + return ret; + *val = get_unaligned_be16(data->transf_buf); + + return IIO_VAL_INT; + case IIO_ACCEL: + ret = adxl355_read_axis(data, adxl355_chans[ + chan->address].data_reg); + if (ret < 0) + return ret; + *val = sign_extend32(ret >> chan->scan_type.shift, + chan->scan_type.realbits - 1); + return IIO_VAL_INT; + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + /* + * Temperature scale is -110.497238. + * See the detailed explanation in adxl35x_chip_info + * definition above. + */ + *val = -110; + *val2 = 497238; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ACCEL: + *val = data->chip_info->accel_scale.integer; + *val2 = data->chip_info->accel_scale.decimal; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + *val = data->chip_info->temp_offset.integer; + *val2 = data->chip_info->temp_offset.decimal; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBBIAS: + *val = sign_extend32(data->calibbias[chan->address], 15); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = adxl355_odr_table[data->odr][0]; + *val2 = adxl355_odr_table[data->odr][1]; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: + *val = data->adxl355_hpf_3db_table[data->hpf_3db][0]; + *val2 = data->adxl355_hpf_3db_table[data->hpf_3db][1]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int adxl355_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct adxl355_data *data = iio_priv(indio_dev); + int odr_idx, hpf_idx, calibbias; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + odr_idx = adxl355_find_match(adxl355_odr_table, + ARRAY_SIZE(adxl355_odr_table), + val, val2); + if (odr_idx < 0) + return odr_idx; + + return adxl355_set_odr(data, odr_idx); + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: + hpf_idx = adxl355_find_match(data->adxl355_hpf_3db_table, + ARRAY_SIZE(data->adxl355_hpf_3db_table), + val, val2); + if (hpf_idx < 0) + return hpf_idx; + + return adxl355_set_hpf_3db(data, hpf_idx); + case IIO_CHAN_INFO_CALIBBIAS: + calibbias = clamp_t(int, val, S16_MIN, S16_MAX); + + return adxl355_set_calibbias(data, chan->address, calibbias); + default: + return -EINVAL; + } +} + +static int adxl355_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct adxl355_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = (const int *)adxl355_odr_table; + *type = IIO_VAL_INT_PLUS_MICRO; + /* Values are stored in a 2D matrix */ + *length = ARRAY_SIZE(adxl355_odr_table) * 2; + + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: + *vals = (const int *)data->adxl355_hpf_3db_table; + *type = IIO_VAL_INT_PLUS_MICRO; + /* Values are stored in a 2D matrix */ + *length = ARRAY_SIZE(data->adxl355_hpf_3db_table) * 2; + + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const unsigned long adxl355_avail_scan_masks[] = { + GENMASK(3, 0), + 0 +}; + +static const struct iio_info adxl355_info = { + .read_raw = adxl355_read_raw, + .write_raw = adxl355_write_raw, + .read_avail = &adxl355_read_avail, +}; + +static const struct iio_trigger_ops adxl355_trigger_ops = { + .set_trigger_state = &adxl355_data_rdy_trigger_set_state, + .validate_device = &iio_trigger_validate_own_device, +}; + +static irqreturn_t adxl355_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adxl355_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->lock); + + /* + * data->buffer is used both for triggered buffer support + * and read/write_raw(), hence, it has to be zeroed here before usage. + */ + data->buffer.buf[0] = 0; + + /* + * The acceleration data is 24 bits and big endian. It has to be saved + * in 32 bits, hence, it is saved in the 2nd byte of the 4 byte buffer. + * The buf array is 14 bytes as it includes 3x4=12 bytes for + * acceleration data of x, y, and z axis. It also includes 2 bytes for + * temperature data. + */ + ret = regmap_bulk_read(data->regmap, ADXL355_XDATA3_REG, + &data->buffer.buf[1], 3); + if (ret) + goto out_unlock_notify; + + ret = regmap_bulk_read(data->regmap, ADXL355_YDATA3_REG, + &data->buffer.buf[5], 3); + if (ret) + goto out_unlock_notify; + + ret = regmap_bulk_read(data->regmap, ADXL355_ZDATA3_REG, + &data->buffer.buf[9], 3); + if (ret) + goto out_unlock_notify; + + ret = regmap_bulk_read(data->regmap, ADXL355_TEMP2_REG, + &data->buffer.buf[12], 2); + if (ret) + goto out_unlock_notify; + + iio_push_to_buffers_with_ts(indio_dev, &data->buffer, + sizeof(data->buffer), pf->timestamp); + +out_unlock_notify: + mutex_unlock(&data->lock); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +#define ADXL355_ACCEL_CHANNEL(index, reg, axis) { \ + .type = IIO_ACCEL, \ + .address = reg, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY), \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 20, \ + .storagebits = 32, \ + .shift = 4, \ + .endianness = IIO_BE, \ + } \ +} + +static const struct iio_chan_spec adxl355_channels[] = { + ADXL355_ACCEL_CHANNEL(0, chan_x, X), + ADXL355_ACCEL_CHANNEL(1, chan_y, Y), + ADXL355_ACCEL_CHANNEL(2, chan_z, Z), + { + .type = IIO_TEMP, + .address = ADXL355_TEMP2_REG, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = 3, + .scan_type = { + .sign = 'u', + .realbits = 12, + .storagebits = 16, + .endianness = IIO_BE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static int adxl355_probe_trigger(struct iio_dev *indio_dev, int irq) +{ + struct adxl355_data *data = iio_priv(indio_dev); + int ret; + + data->dready_trig = devm_iio_trigger_alloc(data->dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->dready_trig) + return -ENOMEM; + + data->dready_trig->ops = &adxl355_trigger_ops; + iio_trigger_set_drvdata(data->dready_trig, indio_dev); + + ret = devm_request_irq(data->dev, irq, &iio_trigger_generic_data_rdy_poll, + IRQF_NO_THREAD, "adxl355_irq", data->dready_trig); + if (ret) + return ret; + + ret = devm_iio_trigger_register(data->dev, data->dready_trig); + if (ret) + return dev_err_probe(data->dev, ret, "iio trigger register failed\n"); + + indio_dev->trig = iio_trigger_get(data->dready_trig); + + return 0; +} + +int adxl355_core_probe(struct device *dev, struct regmap *regmap, + const struct adxl355_chip_info *chip_info) +{ + struct adxl355_data *data; + struct iio_dev *indio_dev; + int ret; + int irq; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->regmap = regmap; + data->dev = dev; + data->op_mode = ADXL355_STANDBY; + data->chip_info = chip_info; + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; + + indio_dev->name = chip_info->name; + indio_dev->info = &adxl355_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = adxl355_channels; + indio_dev->num_channels = ARRAY_SIZE(adxl355_channels); + indio_dev->available_scan_masks = adxl355_avail_scan_masks; + + ret = adxl355_setup(data); + if (ret) + return dev_err_probe(dev, ret, "ADXL355 setup failed\n"); + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + &iio_pollfunc_store_time, + &adxl355_trigger_handler, NULL); + if (ret) + return dev_err_probe(dev, ret, "iio triggered buffer setup failed\n"); + + irq = fwnode_irq_get_byname(dev_fwnode(dev), "DRDY"); + if (irq > 0) { + ret = adxl355_probe_trigger(indio_dev, irq); + if (ret) + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS_GPL(adxl355_core_probe, "IIO_ADXL355"); + +MODULE_AUTHOR("Puranjay Mohan "); +MODULE_DESCRIPTION("ADXL355 3-Axis Digital Accelerometer core driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/adxl355_i2c.c b/drivers/iio/accel/adxl355_i2c.c new file mode 100644 index 000000000..e533a7aab --- /dev/null +++ b/drivers/iio/accel/adxl355_i2c.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADXL355 3-Axis Digital Accelerometer I2C driver + * + * Copyright (c) 2021 Puranjay Mohan + */ + +#include +#include +#include + +#include "adxl355.h" + +static const struct regmap_config adxl355_i2c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x2F, + .rd_table = &adxl355_readable_regs_tbl, + .wr_table = &adxl355_writeable_regs_tbl, +}; + +static int adxl355_i2c_probe(struct i2c_client *client) +{ + struct regmap *regmap; + struct device *dev = &client->dev; + const struct adxl355_chip_info *chip_data; + + chip_data = i2c_get_match_data(client); + if (!chip_data) + return -ENODEV; + + regmap = devm_regmap_init_i2c(client, &adxl355_i2c_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Error initializing i2c regmap\n"); + + return adxl355_core_probe(dev, regmap, chip_data); +} + +static const struct i2c_device_id adxl355_i2c_id[] = { + { .name = "adxl355", .driver_data = (kernel_ulong_t)&adxl35x_chip_info[ADXL355] }, + { .name = "adxl359", .driver_data = (kernel_ulong_t)&adxl35x_chip_info[ADXL359] }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adxl355_i2c_id); + +static const struct of_device_id adxl355_of_match[] = { + { .compatible = "adi,adxl355", .data = &adxl35x_chip_info[ADXL355] }, + { .compatible = "adi,adxl359", .data = &adxl35x_chip_info[ADXL359] }, + { } +}; +MODULE_DEVICE_TABLE(of, adxl355_of_match); + +static struct i2c_driver adxl355_i2c_driver = { + .driver = { + .name = "adxl355_i2c", + .of_match_table = adxl355_of_match, + }, + .probe = adxl355_i2c_probe, + .id_table = adxl355_i2c_id, +}; +module_i2c_driver(adxl355_i2c_driver); + +MODULE_AUTHOR("Puranjay Mohan "); +MODULE_DESCRIPTION("ADXL355 3-Axis Digital Accelerometer I2C driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ADXL355"); diff --git a/drivers/iio/accel/adxl355_spi.c b/drivers/iio/accel/adxl355_spi.c new file mode 100644 index 000000000..d2209ca8c --- /dev/null +++ b/drivers/iio/accel/adxl355_spi.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADXL355 3-Axis Digital Accelerometer SPI driver + * + * Copyright (c) 2021 Puranjay Mohan + */ + +#include +#include +#include +#include + +#include "adxl355.h" + +static const struct regmap_config adxl355_spi_regmap_config = { + .reg_bits = 7, + .pad_bits = 1, + .val_bits = 8, + .read_flag_mask = BIT(0), + .max_register = 0x2F, + .rd_table = &adxl355_readable_regs_tbl, + .wr_table = &adxl355_writeable_regs_tbl, +}; + +static int adxl355_spi_probe(struct spi_device *spi) +{ + const struct adxl355_chip_info *chip_data; + struct device *dev = &spi->dev; + struct regmap *regmap; + + chip_data = spi_get_device_match_data(spi); + if (!chip_data) + return -EINVAL; + + regmap = devm_regmap_init_spi(spi, &adxl355_spi_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Error initializing spi regmap\n"); + + return adxl355_core_probe(dev, regmap, chip_data); +} + +static const struct spi_device_id adxl355_spi_id[] = { + { .name = "adxl355", .driver_data = (kernel_ulong_t)&adxl35x_chip_info[ADXL355] }, + { .name = "adxl359", .driver_data = (kernel_ulong_t)&adxl35x_chip_info[ADXL359] }, + { } +}; +MODULE_DEVICE_TABLE(spi, adxl355_spi_id); + +static const struct of_device_id adxl355_of_match[] = { + { .compatible = "adi,adxl355", .data = &adxl35x_chip_info[ADXL355] }, + { .compatible = "adi,adxl359", .data = &adxl35x_chip_info[ADXL359] }, + { } +}; +MODULE_DEVICE_TABLE(of, adxl355_of_match); + +static struct spi_driver adxl355_spi_driver = { + .driver = { + .name = "adxl355_spi", + .of_match_table = adxl355_of_match, + }, + .probe = adxl355_spi_probe, + .id_table = adxl355_spi_id, +}; +module_spi_driver(adxl355_spi_driver); + +MODULE_AUTHOR("Puranjay Mohan "); +MODULE_DESCRIPTION("ADXL355 3-Axis Digital Accelerometer SPI driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ADXL355"); diff --git a/drivers/iio/accel/adxl367.c b/drivers/iio/accel/adxl367.c new file mode 100644 index 000000000..4ff1c7a09 --- /dev/null +++ b/drivers/iio/accel/adxl367.c @@ -0,0 +1,1497 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2021 Analog Devices, Inc. + * Author: Cosmin Tanislav + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "adxl367.h" + +#define ADXL367_REG_DEVID 0x00 +#define ADXL367_DEVID_AD 0xAD + +#define ADXL367_REG_STATUS 0x0B +#define ADXL367_STATUS_INACT_MASK BIT(5) +#define ADXL367_STATUS_ACT_MASK BIT(4) +#define ADXL367_STATUS_FIFO_FULL_MASK BIT(2) + +#define ADXL367_FIFO_ENT_H_MASK GENMASK(1, 0) + +#define ADXL367_REG_X_DATA_H 0x0E +#define ADXL367_REG_Y_DATA_H 0x10 +#define ADXL367_REG_Z_DATA_H 0x12 +#define ADXL367_REG_TEMP_DATA_H 0x14 +#define ADXL367_REG_EX_ADC_DATA_H 0x16 +#define ADXL367_DATA_MASK GENMASK(15, 2) + +#define ADXL367_TEMP_25C 165 +#define ADXL367_TEMP_PER_C 54 + +#define ADXL367_VOLTAGE_OFFSET 8192 +#define ADXL367_VOLTAGE_MAX_MV 1000 +#define ADXL367_VOLTAGE_MAX_RAW GENMASK(13, 0) + +#define ADXL367_REG_RESET 0x1F +#define ADXL367_RESET_CODE 0x52 + +#define ADXL367_REG_THRESH_ACT_H 0x20 +#define ADXL367_REG_THRESH_INACT_H 0x23 +#define ADXL367_THRESH_MAX GENMASK(12, 0) +#define ADXL367_THRESH_VAL_H_MASK GENMASK(12, 6) +#define ADXL367_THRESH_H_MASK GENMASK(6, 0) +#define ADXL367_THRESH_VAL_L_MASK GENMASK(5, 0) +#define ADXL367_THRESH_L_MASK GENMASK(7, 2) + +#define ADXL367_REG_TIME_ACT 0x22 +#define ADXL367_REG_TIME_INACT_H 0x25 +#define ADXL367_TIME_ACT_MAX GENMASK(7, 0) +#define ADXL367_TIME_INACT_MAX GENMASK(15, 0) +#define ADXL367_TIME_INACT_VAL_H_MASK GENMASK(15, 8) +#define ADXL367_TIME_INACT_H_MASK GENMASK(7, 0) +#define ADXL367_TIME_INACT_VAL_L_MASK GENMASK(7, 0) +#define ADXL367_TIME_INACT_L_MASK GENMASK(7, 0) + +#define ADXL367_REG_ACT_INACT_CTL 0x27 +#define ADXL367_ACT_EN_MASK GENMASK(1, 0) +#define ADXL367_ACT_LINKLOOP_MASK GENMASK(5, 4) + +#define ADXL367_REG_FIFO_CTL 0x28 +#define ADXL367_FIFO_CTL_FORMAT_MASK GENMASK(6, 3) +#define ADXL367_FIFO_CTL_MODE_MASK GENMASK(1, 0) + +#define ADXL367_REG_FIFO_SAMPLES 0x29 +#define ADXL367_FIFO_SIZE 512 +#define ADXL367_FIFO_MAX_WATERMARK 511 + +#define ADXL367_SAMPLES_VAL_H_MASK BIT(8) +#define ADXL367_SAMPLES_H_MASK BIT(2) +#define ADXL367_SAMPLES_VAL_L_MASK GENMASK(7, 0) +#define ADXL367_SAMPLES_L_MASK GENMASK(7, 0) + +#define ADXL367_REG_INT1_MAP 0x2A +#define ADXL367_INT_INACT_MASK BIT(5) +#define ADXL367_INT_ACT_MASK BIT(4) +#define ADXL367_INT_FIFO_WATERMARK_MASK BIT(2) + +#define ADXL367_REG_FILTER_CTL 0x2C +#define ADXL367_FILTER_CTL_RANGE_MASK GENMASK(7, 6) +#define ADXL367_2G_RANGE_1G 4095 +#define ADXL367_2G_RANGE_100MG 409 +#define ADXL367_FILTER_CTL_ODR_MASK GENMASK(2, 0) + +#define ADXL367_REG_POWER_CTL 0x2D +#define ADXL367_POWER_CTL_MODE_MASK GENMASK(1, 0) + +#define ADXL367_REG_ADC_CTL 0x3C +#define ADXL367_REG_TEMP_CTL 0x3D +#define ADXL367_ADC_EN_MASK BIT(0) + +enum adxl367_range { + ADXL367_2G_RANGE, + ADXL367_4G_RANGE, + ADXL367_8G_RANGE, +}; + +enum adxl367_fifo_mode { + ADXL367_FIFO_MODE_DISABLED = 0b00, + ADXL367_FIFO_MODE_STREAM = 0b10, +}; + +enum adxl367_fifo_format { + ADXL367_FIFO_FORMAT_XYZ, + ADXL367_FIFO_FORMAT_X, + ADXL367_FIFO_FORMAT_Y, + ADXL367_FIFO_FORMAT_Z, + ADXL367_FIFO_FORMAT_XYZT, + ADXL367_FIFO_FORMAT_XT, + ADXL367_FIFO_FORMAT_YT, + ADXL367_FIFO_FORMAT_ZT, + ADXL367_FIFO_FORMAT_XYZA, + ADXL367_FIFO_FORMAT_XA, + ADXL367_FIFO_FORMAT_YA, + ADXL367_FIFO_FORMAT_ZA, +}; + +enum adxl367_op_mode { + ADXL367_OP_STANDBY = 0b00, + ADXL367_OP_MEASURE = 0b10, +}; + +enum adxl367_act_proc_mode { + ADXL367_LOOPED = 0b11, +}; + +enum adxl367_act_en_mode { + ADXL367_ACT_DISABLED = 0b00, + ADCL367_ACT_REF_ENABLED = 0b11, +}; + +enum adxl367_activity_type { + ADXL367_ACTIVITY, + ADXL367_INACTIVITY, +}; + +enum adxl367_odr { + ADXL367_ODR_12P5HZ, + ADXL367_ODR_25HZ, + ADXL367_ODR_50HZ, + ADXL367_ODR_100HZ, + ADXL367_ODR_200HZ, + ADXL367_ODR_400HZ, +}; + +struct adxl367_state { + const struct adxl367_ops *ops; + void *context; + + struct device *dev; + struct regmap *regmap; + + /* + * Synchronize access to members of driver state, and ensure atomicity + * of consecutive regmap operations. + */ + struct mutex lock; + + enum adxl367_odr odr; + enum adxl367_range range; + + unsigned int act_threshold; + unsigned int act_time_ms; + unsigned int inact_threshold; + unsigned int inact_time_ms; + + unsigned int fifo_set_size; + unsigned int fifo_watermark; + + __be16 fifo_buf[ADXL367_FIFO_SIZE] __aligned(IIO_DMA_MINALIGN); + __be16 sample_buf; + u8 act_threshold_buf[2]; + u8 inact_time_buf[2]; + u8 status_buf[3]; +}; + +static const unsigned int adxl367_threshold_h_reg_tbl[] = { + [ADXL367_ACTIVITY] = ADXL367_REG_THRESH_ACT_H, + [ADXL367_INACTIVITY] = ADXL367_REG_THRESH_INACT_H, +}; + +static const unsigned int adxl367_act_en_shift_tbl[] = { + [ADXL367_ACTIVITY] = 0, + [ADXL367_INACTIVITY] = 2, +}; + +static const unsigned int adxl367_act_int_mask_tbl[] = { + [ADXL367_ACTIVITY] = ADXL367_INT_ACT_MASK, + [ADXL367_INACTIVITY] = ADXL367_INT_INACT_MASK, +}; + +static const int adxl367_samp_freq_tbl[][2] = { + [ADXL367_ODR_12P5HZ] = {12, 500000}, + [ADXL367_ODR_25HZ] = {25, 0}, + [ADXL367_ODR_50HZ] = {50, 0}, + [ADXL367_ODR_100HZ] = {100, 0}, + [ADXL367_ODR_200HZ] = {200, 0}, + [ADXL367_ODR_400HZ] = {400, 0}, +}; + +/* (g * 2) * 9.80665 * 1000000 / (2^14 - 1) */ +static const int adxl367_range_scale_tbl[][2] = { + [ADXL367_2G_RANGE] = {0, 2394347}, + [ADXL367_4G_RANGE] = {0, 4788695}, + [ADXL367_8G_RANGE] = {0, 9577391}, +}; + +static const int adxl367_range_scale_factor_tbl[] = { + [ADXL367_2G_RANGE] = 1, + [ADXL367_4G_RANGE] = 2, + [ADXL367_8G_RANGE] = 4, +}; + +enum { + ADXL367_X_CHANNEL_INDEX, + ADXL367_Y_CHANNEL_INDEX, + ADXL367_Z_CHANNEL_INDEX, + ADXL367_TEMP_CHANNEL_INDEX, + ADXL367_EX_ADC_CHANNEL_INDEX +}; + +#define ADXL367_X_CHANNEL_MASK BIT(ADXL367_X_CHANNEL_INDEX) +#define ADXL367_Y_CHANNEL_MASK BIT(ADXL367_Y_CHANNEL_INDEX) +#define ADXL367_Z_CHANNEL_MASK BIT(ADXL367_Z_CHANNEL_INDEX) +#define ADXL367_TEMP_CHANNEL_MASK BIT(ADXL367_TEMP_CHANNEL_INDEX) +#define ADXL367_EX_ADC_CHANNEL_MASK BIT(ADXL367_EX_ADC_CHANNEL_INDEX) + +static const enum adxl367_fifo_format adxl367_fifo_formats[] = { + ADXL367_FIFO_FORMAT_X, + ADXL367_FIFO_FORMAT_Y, + ADXL367_FIFO_FORMAT_Z, + ADXL367_FIFO_FORMAT_XT, + ADXL367_FIFO_FORMAT_YT, + ADXL367_FIFO_FORMAT_ZT, + ADXL367_FIFO_FORMAT_XA, + ADXL367_FIFO_FORMAT_YA, + ADXL367_FIFO_FORMAT_ZA, + ADXL367_FIFO_FORMAT_XYZ, + ADXL367_FIFO_FORMAT_XYZT, + ADXL367_FIFO_FORMAT_XYZA, +}; + +static const unsigned long adxl367_channel_masks[] = { + ADXL367_X_CHANNEL_MASK, + ADXL367_Y_CHANNEL_MASK, + ADXL367_Z_CHANNEL_MASK, + ADXL367_X_CHANNEL_MASK | ADXL367_TEMP_CHANNEL_MASK, + ADXL367_Y_CHANNEL_MASK | ADXL367_TEMP_CHANNEL_MASK, + ADXL367_Z_CHANNEL_MASK | ADXL367_TEMP_CHANNEL_MASK, + ADXL367_X_CHANNEL_MASK | ADXL367_EX_ADC_CHANNEL_MASK, + ADXL367_Y_CHANNEL_MASK | ADXL367_EX_ADC_CHANNEL_MASK, + ADXL367_Z_CHANNEL_MASK | ADXL367_EX_ADC_CHANNEL_MASK, + ADXL367_X_CHANNEL_MASK | ADXL367_Y_CHANNEL_MASK | ADXL367_Z_CHANNEL_MASK, + ADXL367_X_CHANNEL_MASK | ADXL367_Y_CHANNEL_MASK | ADXL367_Z_CHANNEL_MASK | + ADXL367_TEMP_CHANNEL_MASK, + ADXL367_X_CHANNEL_MASK | ADXL367_Y_CHANNEL_MASK | ADXL367_Z_CHANNEL_MASK | + ADXL367_EX_ADC_CHANNEL_MASK, + 0, +}; + +static int adxl367_set_measure_en(struct adxl367_state *st, bool en) +{ + enum adxl367_op_mode op_mode = en ? ADXL367_OP_MEASURE + : ADXL367_OP_STANDBY; + int ret; + + ret = regmap_update_bits(st->regmap, ADXL367_REG_POWER_CTL, + ADXL367_POWER_CTL_MODE_MASK, + FIELD_PREP(ADXL367_POWER_CTL_MODE_MASK, + op_mode)); + if (ret) + return ret; + + /* + * Wait for acceleration output to settle after entering + * measure mode. + */ + if (en) + msleep(100); + + return 0; +} + +static void adxl367_scale_act_thresholds(struct adxl367_state *st, + enum adxl367_range old_range, + enum adxl367_range new_range) +{ + st->act_threshold = st->act_threshold + * adxl367_range_scale_factor_tbl[old_range] + / adxl367_range_scale_factor_tbl[new_range]; + st->inact_threshold = st->inact_threshold + * adxl367_range_scale_factor_tbl[old_range] + / adxl367_range_scale_factor_tbl[new_range]; +} + +static int _adxl367_set_act_threshold(struct adxl367_state *st, + enum adxl367_activity_type act, + unsigned int threshold) +{ + u8 reg = adxl367_threshold_h_reg_tbl[act]; + int ret; + + if (threshold > ADXL367_THRESH_MAX) + return -EINVAL; + + st->act_threshold_buf[0] = FIELD_PREP(ADXL367_THRESH_H_MASK, + FIELD_GET(ADXL367_THRESH_VAL_H_MASK, + threshold)); + st->act_threshold_buf[1] = FIELD_PREP(ADXL367_THRESH_L_MASK, + FIELD_GET(ADXL367_THRESH_VAL_L_MASK, + threshold)); + + ret = regmap_bulk_write(st->regmap, reg, st->act_threshold_buf, + sizeof(st->act_threshold_buf)); + if (ret) + return ret; + + if (act == ADXL367_ACTIVITY) + st->act_threshold = threshold; + else + st->inact_threshold = threshold; + + return 0; +} + +static int adxl367_set_act_threshold(struct adxl367_state *st, + enum adxl367_activity_type act, + unsigned int threshold) +{ + int ret; + + guard(mutex)(&st->lock); + + ret = adxl367_set_measure_en(st, false); + if (ret) + return ret; + + ret = _adxl367_set_act_threshold(st, act, threshold); + if (ret) + return ret; + + return adxl367_set_measure_en(st, true); +} + +static int adxl367_set_act_proc_mode(struct adxl367_state *st, + enum adxl367_act_proc_mode mode) +{ + return regmap_update_bits(st->regmap, ADXL367_REG_ACT_INACT_CTL, + ADXL367_ACT_LINKLOOP_MASK, + FIELD_PREP(ADXL367_ACT_LINKLOOP_MASK, + mode)); +} + +static int adxl367_set_act_interrupt_en(struct adxl367_state *st, + enum adxl367_activity_type act, + bool en) +{ + unsigned int mask = adxl367_act_int_mask_tbl[act]; + + return regmap_update_bits(st->regmap, ADXL367_REG_INT1_MAP, + mask, en ? mask : 0); +} + +static int adxl367_get_act_interrupt_en(struct adxl367_state *st, + enum adxl367_activity_type act, + bool *en) +{ + unsigned int mask = adxl367_act_int_mask_tbl[act]; + unsigned int val; + int ret; + + ret = regmap_read(st->regmap, ADXL367_REG_INT1_MAP, &val); + if (ret) + return ret; + + *en = !!(val & mask); + + return 0; +} + +static int adxl367_set_act_en(struct adxl367_state *st, + enum adxl367_activity_type act, + enum adxl367_act_en_mode en) +{ + unsigned int ctl_shift = adxl367_act_en_shift_tbl[act]; + + return regmap_update_bits(st->regmap, ADXL367_REG_ACT_INACT_CTL, + ADXL367_ACT_EN_MASK << ctl_shift, + en << ctl_shift); +} + +static int adxl367_set_fifo_watermark_interrupt_en(struct adxl367_state *st, + bool en) +{ + return regmap_update_bits(st->regmap, ADXL367_REG_INT1_MAP, + ADXL367_INT_FIFO_WATERMARK_MASK, + en ? ADXL367_INT_FIFO_WATERMARK_MASK : 0); +} + +static int adxl367_get_fifo_mode(struct adxl367_state *st, + enum adxl367_fifo_mode *fifo_mode) +{ + unsigned int val; + int ret; + + ret = regmap_read(st->regmap, ADXL367_REG_FIFO_CTL, &val); + if (ret) + return ret; + + *fifo_mode = FIELD_GET(ADXL367_FIFO_CTL_MODE_MASK, val); + + return 0; +} + +static int adxl367_set_fifo_mode(struct adxl367_state *st, + enum adxl367_fifo_mode fifo_mode) +{ + return regmap_update_bits(st->regmap, ADXL367_REG_FIFO_CTL, + ADXL367_FIFO_CTL_MODE_MASK, + FIELD_PREP(ADXL367_FIFO_CTL_MODE_MASK, + fifo_mode)); +} + +static int adxl367_set_fifo_format(struct adxl367_state *st, + enum adxl367_fifo_format fifo_format) +{ + return regmap_update_bits(st->regmap, ADXL367_REG_FIFO_CTL, + ADXL367_FIFO_CTL_FORMAT_MASK, + FIELD_PREP(ADXL367_FIFO_CTL_FORMAT_MASK, + fifo_format)); +} + +static int adxl367_set_fifo_watermark(struct adxl367_state *st, + unsigned int fifo_watermark) +{ + unsigned int fifo_samples = fifo_watermark * st->fifo_set_size; + unsigned int fifo_samples_h, fifo_samples_l; + int ret; + + if (fifo_samples > ADXL367_FIFO_MAX_WATERMARK) + fifo_samples = ADXL367_FIFO_MAX_WATERMARK; + + fifo_samples /= st->fifo_set_size; + + fifo_samples_h = FIELD_PREP(ADXL367_SAMPLES_H_MASK, + FIELD_GET(ADXL367_SAMPLES_VAL_H_MASK, + fifo_samples)); + fifo_samples_l = FIELD_PREP(ADXL367_SAMPLES_L_MASK, + FIELD_GET(ADXL367_SAMPLES_VAL_L_MASK, + fifo_samples)); + + ret = regmap_update_bits(st->regmap, ADXL367_REG_FIFO_CTL, + ADXL367_SAMPLES_H_MASK, fifo_samples_h); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, ADXL367_REG_FIFO_SAMPLES, + ADXL367_SAMPLES_L_MASK, fifo_samples_l); + if (ret) + return ret; + + st->fifo_watermark = fifo_watermark; + + return 0; +} + +static int adxl367_set_range(struct iio_dev *indio_dev, + enum adxl367_range range) +{ + struct adxl367_state *st = iio_priv(indio_dev); + int ret; + + guard(mutex)(&st->lock); + + ret = adxl367_set_measure_en(st, false); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, ADXL367_REG_FILTER_CTL, + ADXL367_FILTER_CTL_RANGE_MASK, + FIELD_PREP(ADXL367_FILTER_CTL_RANGE_MASK, + range)); + if (ret) + return ret; + + adxl367_scale_act_thresholds(st, st->range, range); + + /* Activity thresholds depend on range */ + ret = _adxl367_set_act_threshold(st, ADXL367_ACTIVITY, + st->act_threshold); + if (ret) + return ret; + + ret = _adxl367_set_act_threshold(st, ADXL367_INACTIVITY, + st->inact_threshold); + if (ret) + return ret; + + ret = adxl367_set_measure_en(st, true); + if (ret) + return ret; + + st->range = range; + + return 0; +} + +static int adxl367_time_ms_to_samples(struct adxl367_state *st, unsigned int ms) +{ + int freq_hz = adxl367_samp_freq_tbl[st->odr][0]; + int freq_microhz = adxl367_samp_freq_tbl[st->odr][1]; + /* Scale to decihertz to prevent precision loss in 12.5Hz case. */ + int freq_dhz = freq_hz * 10 + freq_microhz / 100000; + + return DIV_ROUND_CLOSEST(ms * freq_dhz, 10000); +} + +static int _adxl367_set_act_time_ms(struct adxl367_state *st, unsigned int ms) +{ + unsigned int val = adxl367_time_ms_to_samples(st, ms); + int ret; + + if (val > ADXL367_TIME_ACT_MAX) + val = ADXL367_TIME_ACT_MAX; + + ret = regmap_write(st->regmap, ADXL367_REG_TIME_ACT, val); + if (ret) + return ret; + + st->act_time_ms = ms; + + return 0; +} + +static int _adxl367_set_inact_time_ms(struct adxl367_state *st, unsigned int ms) +{ + unsigned int val = adxl367_time_ms_to_samples(st, ms); + int ret; + + if (val > ADXL367_TIME_INACT_MAX) + val = ADXL367_TIME_INACT_MAX; + + st->inact_time_buf[0] = FIELD_PREP(ADXL367_TIME_INACT_H_MASK, + FIELD_GET(ADXL367_TIME_INACT_VAL_H_MASK, + val)); + st->inact_time_buf[1] = FIELD_PREP(ADXL367_TIME_INACT_L_MASK, + FIELD_GET(ADXL367_TIME_INACT_VAL_L_MASK, + val)); + + ret = regmap_bulk_write(st->regmap, ADXL367_REG_TIME_INACT_H, + st->inact_time_buf, sizeof(st->inact_time_buf)); + if (ret) + return ret; + + st->inact_time_ms = ms; + + return 0; +} + +static int adxl367_set_act_time_ms(struct adxl367_state *st, + enum adxl367_activity_type act, + unsigned int ms) +{ + int ret; + + guard(mutex)(&st->lock); + + ret = adxl367_set_measure_en(st, false); + if (ret) + return ret; + + if (act == ADXL367_ACTIVITY) + ret = _adxl367_set_act_time_ms(st, ms); + else + ret = _adxl367_set_inact_time_ms(st, ms); + + if (ret) + return ret; + + return adxl367_set_measure_en(st, true); +} + +static int _adxl367_set_odr(struct adxl367_state *st, enum adxl367_odr odr) +{ + int ret; + + ret = regmap_update_bits(st->regmap, ADXL367_REG_FILTER_CTL, + ADXL367_FILTER_CTL_ODR_MASK, + FIELD_PREP(ADXL367_FILTER_CTL_ODR_MASK, + odr)); + if (ret) + return ret; + + st->odr = odr; + + /* Activity timers depend on ODR */ + ret = _adxl367_set_act_time_ms(st, st->act_time_ms); + if (ret) + return ret; + + return _adxl367_set_inact_time_ms(st, st->inact_time_ms); +} + +static int adxl367_set_odr(struct iio_dev *indio_dev, enum adxl367_odr odr) +{ + struct adxl367_state *st = iio_priv(indio_dev); + int ret; + + guard(mutex)(&st->lock); + + ret = adxl367_set_measure_en(st, false); + if (ret) + return ret; + + ret = _adxl367_set_odr(st, odr); + if (ret) + return ret; + + return adxl367_set_measure_en(st, true); +} + +static int adxl367_set_temp_adc_en(struct adxl367_state *st, unsigned int reg, + bool en) +{ + return regmap_update_bits(st->regmap, reg, ADXL367_ADC_EN_MASK, + en ? ADXL367_ADC_EN_MASK : 0); +} + +static int adxl367_set_temp_adc_reg_en(struct adxl367_state *st, + unsigned int reg, bool en) +{ + int ret; + + switch (reg) { + case ADXL367_REG_TEMP_DATA_H: + ret = adxl367_set_temp_adc_en(st, ADXL367_REG_TEMP_CTL, en); + break; + case ADXL367_REG_EX_ADC_DATA_H: + ret = adxl367_set_temp_adc_en(st, ADXL367_REG_ADC_CTL, en); + break; + default: + return 0; + } + + if (ret) + return ret; + + if (en) + msleep(100); + + return 0; +} + +static int adxl367_set_temp_adc_mask_en(struct adxl367_state *st, + const unsigned long *active_scan_mask, + bool en) +{ + if (*active_scan_mask & ADXL367_TEMP_CHANNEL_MASK) + return adxl367_set_temp_adc_en(st, ADXL367_REG_TEMP_CTL, en); + else if (*active_scan_mask & ADXL367_EX_ADC_CHANNEL_MASK) + return adxl367_set_temp_adc_en(st, ADXL367_REG_ADC_CTL, en); + + return 0; +} + +static int adxl367_find_odr(struct adxl367_state *st, int val, int val2, + enum adxl367_odr *odr) +{ + size_t size = ARRAY_SIZE(adxl367_samp_freq_tbl); + int i; + + for (i = 0; i < size; i++) + if (val == adxl367_samp_freq_tbl[i][0] && + val2 == adxl367_samp_freq_tbl[i][1]) + break; + + if (i == size) + return -EINVAL; + + *odr = i; + + return 0; +} + +static int adxl367_find_range(struct adxl367_state *st, int val, int val2, + enum adxl367_range *range) +{ + size_t size = ARRAY_SIZE(adxl367_range_scale_tbl); + int i; + + for (i = 0; i < size; i++) + if (val == adxl367_range_scale_tbl[i][0] && + val2 == adxl367_range_scale_tbl[i][1]) + break; + + if (i == size) + return -EINVAL; + + *range = i; + + return 0; +} + +static int adxl367_read_sample(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct adxl367_state *st = iio_priv(indio_dev); + u16 sample; + int ret; + + guard(mutex)(&st->lock); + + ret = adxl367_set_temp_adc_reg_en(st, chan->address, true); + if (ret) + return ret; + + ret = regmap_bulk_read(st->regmap, chan->address, &st->sample_buf, + sizeof(st->sample_buf)); + if (ret) + return ret; + + sample = FIELD_GET(ADXL367_DATA_MASK, be16_to_cpu(st->sample_buf)); + *val = sign_extend32(sample, chan->scan_type.realbits - 1); + + ret = adxl367_set_temp_adc_reg_en(st, chan->address, false); + if (ret) + return ret; + + return IIO_VAL_INT; +} + +static int adxl367_get_status(struct adxl367_state *st, u8 *status, + u16 *fifo_entries) +{ + int ret; + + /* Read STATUS, FIFO_ENT_L and FIFO_ENT_H */ + ret = regmap_bulk_read(st->regmap, ADXL367_REG_STATUS, + st->status_buf, sizeof(st->status_buf)); + if (ret) + return ret; + + st->status_buf[2] &= ADXL367_FIFO_ENT_H_MASK; + + *status = st->status_buf[0]; + *fifo_entries = get_unaligned_le16(&st->status_buf[1]); + + return 0; +} + +static bool adxl367_push_event(struct iio_dev *indio_dev, u8 status) +{ + unsigned int ev_dir; + + if (FIELD_GET(ADXL367_STATUS_ACT_MASK, status)) + ev_dir = IIO_EV_DIR_RISING; + else if (FIELD_GET(ADXL367_STATUS_INACT_MASK, status)) + ev_dir = IIO_EV_DIR_FALLING; + else + return false; + + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_THRESH, ev_dir), + iio_get_time_ns(indio_dev)); + + return true; +} + +static bool adxl367_push_fifo_data(struct iio_dev *indio_dev, u8 status, + u16 fifo_entries) +{ + struct adxl367_state *st = iio_priv(indio_dev); + int ret; + int i; + + if (!FIELD_GET(ADXL367_STATUS_FIFO_FULL_MASK, status)) + return false; + + fifo_entries -= fifo_entries % st->fifo_set_size; + + ret = st->ops->read_fifo(st->context, st->fifo_buf, fifo_entries); + if (ret) { + dev_err(st->dev, "Failed to read FIFO: %d\n", ret); + return true; + } + + for (i = 0; i < fifo_entries; i += st->fifo_set_size) + iio_push_to_buffers(indio_dev, &st->fifo_buf[i]); + + return true; +} + +static irqreturn_t adxl367_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct adxl367_state *st = iio_priv(indio_dev); + u16 fifo_entries; + bool handled; + u8 status; + int ret; + + ret = adxl367_get_status(st, &status, &fifo_entries); + if (ret) + return IRQ_NONE; + + handled = adxl367_push_event(indio_dev, status); + handled |= adxl367_push_fifo_data(indio_dev, status, fifo_entries); + + return handled ? IRQ_HANDLED : IRQ_NONE; +} + +static int adxl367_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct adxl367_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + else + return regmap_write(st->regmap, reg, writeval); +} + +static int adxl367_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct adxl367_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = adxl367_read_sample(indio_dev, chan, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ACCEL: { + guard(mutex)(&st->lock); + *val = adxl367_range_scale_tbl[st->range][0]; + *val2 = adxl367_range_scale_tbl[st->range][1]; + return IIO_VAL_INT_PLUS_NANO; + } + case IIO_TEMP: + *val = 1000; + *val2 = ADXL367_TEMP_PER_C; + return IIO_VAL_FRACTIONAL; + case IIO_VOLTAGE: + *val = ADXL367_VOLTAGE_MAX_MV; + *val2 = ADXL367_VOLTAGE_MAX_RAW; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + *val = 25 * ADXL367_TEMP_PER_C - ADXL367_TEMP_25C; + return IIO_VAL_INT; + case IIO_VOLTAGE: + *val = ADXL367_VOLTAGE_OFFSET; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: { + guard(mutex)(&st->lock); + *val = adxl367_samp_freq_tbl[st->odr][0]; + *val2 = adxl367_samp_freq_tbl[st->odr][1]; + return IIO_VAL_INT_PLUS_MICRO; + } + default: + return -EINVAL; + } +} + +static int adxl367_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct adxl367_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: { + enum adxl367_odr odr; + + ret = adxl367_find_odr(st, val, val2, &odr); + if (ret) + return ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = adxl367_set_odr(indio_dev, odr); + iio_device_release_direct(indio_dev); + return ret; + } + case IIO_CHAN_INFO_SCALE: { + enum adxl367_range range; + + ret = adxl367_find_range(st, val, val2, &range); + if (ret) + return ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = adxl367_set_range(indio_dev, range); + iio_device_release_direct(indio_dev); + return ret; + } + default: + return -EINVAL; + } +} + +static int adxl367_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long info) +{ + switch (info) { + case IIO_CHAN_INFO_SCALE: + if (chan->type != IIO_ACCEL) + return -EINVAL; + + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT_PLUS_MICRO; + } +} + +static int adxl367_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long info) +{ + switch (info) { + case IIO_CHAN_INFO_SCALE: + if (chan->type != IIO_ACCEL) + return -EINVAL; + + *vals = (int *)adxl367_range_scale_tbl; + *type = IIO_VAL_INT_PLUS_NANO; + *length = ARRAY_SIZE(adxl367_range_scale_tbl) * 2; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = (int *)adxl367_samp_freq_tbl; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = ARRAY_SIZE(adxl367_samp_freq_tbl) * 2; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int adxl367_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct adxl367_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->lock); + switch (info) { + case IIO_EV_INFO_VALUE: { + switch (dir) { + case IIO_EV_DIR_RISING: + *val = st->act_threshold; + return IIO_VAL_INT; + case IIO_EV_DIR_FALLING: + *val = st->inact_threshold; + return IIO_VAL_INT; + default: + return -EINVAL; + } + } + case IIO_EV_INFO_PERIOD: + switch (dir) { + case IIO_EV_DIR_RISING: + *val = st->act_time_ms; + *val2 = 1000; + return IIO_VAL_FRACTIONAL; + case IIO_EV_DIR_FALLING: + *val = st->inact_time_ms; + *val2 = 1000; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int adxl367_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct adxl367_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_EV_INFO_VALUE: + if (val < 0) + return -EINVAL; + + switch (dir) { + case IIO_EV_DIR_RISING: + return adxl367_set_act_threshold(st, ADXL367_ACTIVITY, val); + case IIO_EV_DIR_FALLING: + return adxl367_set_act_threshold(st, ADXL367_INACTIVITY, val); + default: + return -EINVAL; + } + case IIO_EV_INFO_PERIOD: + if (val < 0) + return -EINVAL; + + val = val * 1000 + DIV_ROUND_UP(val2, 1000); + switch (dir) { + case IIO_EV_DIR_RISING: + return adxl367_set_act_time_ms(st, ADXL367_ACTIVITY, val); + case IIO_EV_DIR_FALLING: + return adxl367_set_act_time_ms(st, ADXL367_INACTIVITY, val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int adxl367_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct adxl367_state *st = iio_priv(indio_dev); + bool en; + int ret; + + switch (dir) { + case IIO_EV_DIR_RISING: + ret = adxl367_get_act_interrupt_en(st, ADXL367_ACTIVITY, &en); + return ret ?: en; + case IIO_EV_DIR_FALLING: + ret = adxl367_get_act_interrupt_en(st, ADXL367_INACTIVITY, &en); + return ret ?: en; + default: + return -EINVAL; + } +} + +static int __adxl367_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct adxl367_state *st = iio_priv(indio_dev); + enum adxl367_activity_type act; + int ret; + + switch (dir) { + case IIO_EV_DIR_RISING: + act = ADXL367_ACTIVITY; + break; + case IIO_EV_DIR_FALLING: + act = ADXL367_INACTIVITY; + break; + default: + return -EINVAL; + } + + guard(mutex)(&st->lock); + + ret = adxl367_set_measure_en(st, false); + if (ret) + return ret; + + ret = adxl367_set_act_interrupt_en(st, act, state); + if (ret) + return ret; + + ret = adxl367_set_act_en(st, act, state ? ADCL367_ACT_REF_ENABLED + : ADXL367_ACT_DISABLED); + if (ret) + return ret; + + return adxl367_set_measure_en(st, true); +} + +static int adxl367_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = __adxl367_write_event_config(indio_dev, chan, type, dir, state); + iio_device_release_direct(indio_dev); + return ret; +} + +static ssize_t adxl367_get_fifo_enabled(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct adxl367_state *st = iio_priv(dev_to_iio_dev(dev)); + enum adxl367_fifo_mode fifo_mode; + int ret; + + ret = adxl367_get_fifo_mode(st, &fifo_mode); + if (ret) + return ret; + + return sysfs_emit(buf, "%d\n", fifo_mode != ADXL367_FIFO_MODE_DISABLED); +} + +static ssize_t adxl367_get_fifo_watermark(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct adxl367_state *st = iio_priv(dev_to_iio_dev(dev)); + unsigned int fifo_watermark; + + guard(mutex)(&st->lock); + fifo_watermark = st->fifo_watermark; + + return sysfs_emit(buf, "%d\n", fifo_watermark); +} + +IIO_STATIC_CONST_DEVICE_ATTR(hwfifo_watermark_min, "1"); +IIO_STATIC_CONST_DEVICE_ATTR(hwfifo_watermark_max, + __stringify(ADXL367_FIFO_MAX_WATERMARK)); +static IIO_DEVICE_ATTR(hwfifo_watermark, 0444, + adxl367_get_fifo_watermark, NULL, 0); +static IIO_DEVICE_ATTR(hwfifo_enabled, 0444, + adxl367_get_fifo_enabled, NULL, 0); + +static const struct iio_dev_attr *adxl367_fifo_attributes[] = { + &iio_dev_attr_hwfifo_watermark_min, + &iio_dev_attr_hwfifo_watermark_max, + &iio_dev_attr_hwfifo_watermark, + &iio_dev_attr_hwfifo_enabled, + NULL, +}; + +static int adxl367_set_watermark(struct iio_dev *indio_dev, unsigned int val) +{ + struct adxl367_state *st = iio_priv(indio_dev); + int ret; + + if (val > ADXL367_FIFO_MAX_WATERMARK) + return -EINVAL; + + guard(mutex)(&st->lock); + + ret = adxl367_set_measure_en(st, false); + if (ret) + return ret; + + ret = adxl367_set_fifo_watermark(st, val); + if (ret) + return ret; + + return adxl367_set_measure_en(st, true); +} + +static bool adxl367_find_mask_fifo_format(const unsigned long *scan_mask, + enum adxl367_fifo_format *fifo_format) +{ + size_t size = ARRAY_SIZE(adxl367_fifo_formats); + int i; + + for (i = 0; i < size; i++) + if (*scan_mask == adxl367_channel_masks[i]) + break; + + if (i == size) + return false; + + *fifo_format = adxl367_fifo_formats[i]; + + return true; +} + +static int adxl367_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *active_scan_mask) +{ + struct adxl367_state *st = iio_priv(indio_dev); + enum adxl367_fifo_format fifo_format; + int ret; + + if (!adxl367_find_mask_fifo_format(active_scan_mask, &fifo_format)) + return -EINVAL; + + guard(mutex)(&st->lock); + + ret = adxl367_set_measure_en(st, false); + if (ret) + return ret; + + ret = adxl367_set_fifo_format(st, fifo_format); + if (ret) + return ret; + + ret = adxl367_set_measure_en(st, true); + if (ret) + return ret; + + st->fifo_set_size = bitmap_weight(active_scan_mask, + iio_get_masklength(indio_dev)); + + return 0; +} + +static int adxl367_buffer_postenable(struct iio_dev *indio_dev) +{ + struct adxl367_state *st = iio_priv(indio_dev); + int ret; + + guard(mutex)(&st->lock); + + ret = adxl367_set_temp_adc_mask_en(st, indio_dev->active_scan_mask, + true); + if (ret) + return ret; + + ret = adxl367_set_measure_en(st, false); + if (ret) + return ret; + + ret = adxl367_set_fifo_watermark_interrupt_en(st, true); + if (ret) + return ret; + + ret = adxl367_set_fifo_mode(st, ADXL367_FIFO_MODE_STREAM); + if (ret) + return ret; + + return adxl367_set_measure_en(st, true); +} + +static int adxl367_buffer_predisable(struct iio_dev *indio_dev) +{ + struct adxl367_state *st = iio_priv(indio_dev); + int ret; + + guard(mutex)(&st->lock); + + ret = adxl367_set_measure_en(st, false); + if (ret) + return ret; + + ret = adxl367_set_fifo_mode(st, ADXL367_FIFO_MODE_DISABLED); + if (ret) + return ret; + + ret = adxl367_set_fifo_watermark_interrupt_en(st, false); + if (ret) + return ret; + + ret = adxl367_set_measure_en(st, true); + if (ret) + return ret; + + return adxl367_set_temp_adc_mask_en(st, indio_dev->active_scan_mask, + false); +} + +static const struct iio_buffer_setup_ops adxl367_buffer_ops = { + .postenable = adxl367_buffer_postenable, + .predisable = adxl367_buffer_predisable, +}; + +static const struct iio_info adxl367_info = { + .read_raw = adxl367_read_raw, + .write_raw = adxl367_write_raw, + .write_raw_get_fmt = adxl367_write_raw_get_fmt, + .read_avail = adxl367_read_avail, + .read_event_config = adxl367_read_event_config, + .write_event_config = adxl367_write_event_config, + .read_event_value = adxl367_read_event_value, + .write_event_value = adxl367_write_event_value, + .debugfs_reg_access = adxl367_reg_access, + .hwfifo_set_watermark = adxl367_set_watermark, + .update_scan_mode = adxl367_update_scan_mode, +}; + +static const struct iio_event_spec adxl367_events[] = { + { + .type = IIO_EV_TYPE_MAG_REFERENCED, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_PERIOD) | + BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_MAG_REFERENCED, + .dir = IIO_EV_DIR_FALLING, + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_PERIOD) | + BIT(IIO_EV_INFO_VALUE), + }, +}; + +#define ADXL367_ACCEL_CHANNEL(index, reg, axis) { \ + .type = IIO_ACCEL, \ + .address = (reg), \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .event_spec = adxl367_events, \ + .num_event_specs = ARRAY_SIZE(adxl367_events), \ + .scan_index = (index), \ + .scan_type = { \ + .sign = 's', \ + .realbits = 14, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADXL367_CHANNEL(index, reg, _type) { \ + .type = (_type), \ + .address = (reg), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = (index), \ + .scan_type = { \ + .sign = 's', \ + .realbits = 14, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ +} + +static const struct iio_chan_spec adxl367_channels[] = { + ADXL367_ACCEL_CHANNEL(ADXL367_X_CHANNEL_INDEX, ADXL367_REG_X_DATA_H, X), + ADXL367_ACCEL_CHANNEL(ADXL367_Y_CHANNEL_INDEX, ADXL367_REG_Y_DATA_H, Y), + ADXL367_ACCEL_CHANNEL(ADXL367_Z_CHANNEL_INDEX, ADXL367_REG_Z_DATA_H, Z), + ADXL367_CHANNEL(ADXL367_TEMP_CHANNEL_INDEX, ADXL367_REG_TEMP_DATA_H, + IIO_TEMP), + ADXL367_CHANNEL(ADXL367_EX_ADC_CHANNEL_INDEX, ADXL367_REG_EX_ADC_DATA_H, + IIO_VOLTAGE), +}; + +static int adxl367_verify_devid(struct adxl367_state *st) +{ + unsigned int val; + int ret; + + ret = regmap_read(st->regmap, ADXL367_REG_DEVID, &val); + if (ret) + return dev_err_probe(st->dev, ret, "Failed to read dev id\n"); + + if (val != ADXL367_DEVID_AD) + return dev_err_probe(st->dev, -ENODEV, + "Invalid dev id 0x%02X, expected 0x%02X\n", + val, ADXL367_DEVID_AD); + + return 0; +} + +static int adxl367_setup(struct adxl367_state *st) +{ + int ret; + + ret = _adxl367_set_act_threshold(st, ADXL367_ACTIVITY, + ADXL367_2G_RANGE_1G); + if (ret) + return ret; + + ret = _adxl367_set_act_threshold(st, ADXL367_INACTIVITY, + ADXL367_2G_RANGE_100MG); + if (ret) + return ret; + + ret = adxl367_set_act_proc_mode(st, ADXL367_LOOPED); + if (ret) + return ret; + + ret = _adxl367_set_odr(st, ADXL367_ODR_400HZ); + if (ret) + return ret; + + ret = _adxl367_set_act_time_ms(st, 10); + if (ret) + return ret; + + ret = _adxl367_set_inact_time_ms(st, 10000); + if (ret) + return ret; + + return adxl367_set_measure_en(st, true); +} + +int adxl367_probe(struct device *dev, const struct adxl367_ops *ops, + void *context, struct regmap *regmap, int irq) +{ + static const char * const regulator_names[] = { "vdd", "vddio" }; + struct iio_dev *indio_dev; + struct adxl367_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->dev = dev; + st->regmap = regmap; + st->context = context; + st->ops = ops; + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + indio_dev->channels = adxl367_channels; + indio_dev->num_channels = ARRAY_SIZE(adxl367_channels); + indio_dev->available_scan_masks = adxl367_channel_masks; + indio_dev->name = "adxl367"; + indio_dev->info = &adxl367_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_regulator_bulk_get_enable(st->dev, + ARRAY_SIZE(regulator_names), + regulator_names); + if (ret) + return dev_err_probe(st->dev, ret, + "Failed to get regulators\n"); + + ret = regmap_write(st->regmap, ADXL367_REG_RESET, ADXL367_RESET_CODE); + if (ret) + return ret; + + fsleep(15000); + + ret = adxl367_verify_devid(st); + if (ret) + return ret; + + ret = adxl367_setup(st); + if (ret) + return ret; + + ret = devm_iio_kfifo_buffer_setup_ext(st->dev, indio_dev, + &adxl367_buffer_ops, + adxl367_fifo_attributes); + if (ret) + return ret; + + ret = devm_request_threaded_irq(st->dev, irq, NULL, + adxl367_irq_handler, IRQF_ONESHOT, + indio_dev->name, indio_dev); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS_GPL(adxl367_probe, "IIO_ADXL367"); + +MODULE_AUTHOR("Cosmin Tanislav "); +MODULE_DESCRIPTION("Analog Devices ADXL367 3-axis accelerometer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/accel/adxl367.h b/drivers/iio/accel/adxl367.h new file mode 100644 index 000000000..4a4262214 --- /dev/null +++ b/drivers/iio/accel/adxl367.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2021 Analog Devices, Inc. + * Author: Cosmin Tanislav + */ + +#ifndef _ADXL367_H_ +#define _ADXL367_H_ + +#include + +struct device; +struct regmap; + +struct adxl367_ops { + int (*read_fifo)(void *context, __be16 *fifo_buf, + unsigned int fifo_entries); +}; + +int adxl367_probe(struct device *dev, const struct adxl367_ops *ops, + void *context, struct regmap *regmap, int irq); + +#endif /* _ADXL367_H_ */ diff --git a/drivers/iio/accel/adxl367_i2c.c b/drivers/iio/accel/adxl367_i2c.c new file mode 100644 index 000000000..42e747f5d --- /dev/null +++ b/drivers/iio/accel/adxl367_i2c.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2021 Analog Devices, Inc. + * Author: Cosmin Tanislav + */ + +#include +#include +#include + +#include "adxl367.h" + +#define ADXL367_I2C_FIFO_DATA 0x18 + +struct adxl367_i2c_state { + struct regmap *regmap; +}; + +static bool adxl367_readable_noinc_reg(struct device *dev, unsigned int reg) +{ + return reg == ADXL367_I2C_FIFO_DATA; +} + +static int adxl367_i2c_read_fifo(void *context, __be16 *fifo_buf, + unsigned int fifo_entries) +{ + struct adxl367_i2c_state *st = context; + + return regmap_noinc_read(st->regmap, ADXL367_I2C_FIFO_DATA, fifo_buf, + fifo_entries * sizeof(*fifo_buf)); +} + +static const struct regmap_config adxl367_i2c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .readable_noinc_reg = adxl367_readable_noinc_reg, +}; + +static const struct adxl367_ops adxl367_i2c_ops = { + .read_fifo = adxl367_i2c_read_fifo, +}; + +static int adxl367_i2c_probe(struct i2c_client *client) +{ + struct adxl367_i2c_state *st; + struct regmap *regmap; + + st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL); + if (!st) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, &adxl367_i2c_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + st->regmap = regmap; + + return adxl367_probe(&client->dev, &adxl367_i2c_ops, st, regmap, + client->irq); +} + +static const struct i2c_device_id adxl367_i2c_id[] = { + { .name = "adxl367" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adxl367_i2c_id); + +static const struct of_device_id adxl367_of_match[] = { + { .compatible = "adi,adxl367" }, + { } +}; +MODULE_DEVICE_TABLE(of, adxl367_of_match); + +static struct i2c_driver adxl367_i2c_driver = { + .driver = { + .name = "adxl367_i2c", + .of_match_table = adxl367_of_match, + }, + .probe = adxl367_i2c_probe, + .id_table = adxl367_i2c_id, +}; + +module_i2c_driver(adxl367_i2c_driver); + +MODULE_IMPORT_NS("IIO_ADXL367"); +MODULE_AUTHOR("Cosmin Tanislav "); +MODULE_DESCRIPTION("Analog Devices ADXL367 3-axis accelerometer I2C driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/accel/adxl367_spi.c b/drivers/iio/accel/adxl367_spi.c new file mode 100644 index 000000000..1d1418452 --- /dev/null +++ b/drivers/iio/accel/adxl367_spi.c @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2021 Analog Devices, Inc. + * Author: Cosmin Tanislav + */ + +#include +#include +#include + +#include + +#include "adxl367.h" + +#define ADXL367_SPI_WRITE_COMMAND 0x0A +#define ADXL367_SPI_READ_COMMAND 0x0B +#define ADXL367_SPI_FIFO_COMMAND 0x0D + +struct adxl367_spi_state { + struct spi_device *spi; + + struct spi_message reg_write_msg; + struct spi_transfer reg_write_xfer[2]; + + struct spi_message reg_read_msg; + struct spi_transfer reg_read_xfer[2]; + + struct spi_message fifo_msg; + struct spi_transfer fifo_xfer[2]; + + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers live in their own cache lines. + */ + u8 reg_write_tx_buf[1] __aligned(IIO_DMA_MINALIGN); + u8 reg_read_tx_buf[2]; + u8 fifo_tx_buf[1]; +}; + +static int adxl367_read_fifo(void *context, __be16 *fifo_buf, + unsigned int fifo_entries) +{ + struct adxl367_spi_state *st = context; + + st->fifo_xfer[1].rx_buf = fifo_buf; + st->fifo_xfer[1].len = fifo_entries * sizeof(*fifo_buf); + + return spi_sync(st->spi, &st->fifo_msg); +} + +static int adxl367_read(void *context, const void *reg_buf, size_t reg_size, + void *val_buf, size_t val_size) +{ + struct adxl367_spi_state *st = context; + u8 reg = ((const u8 *)reg_buf)[0]; + + st->reg_read_tx_buf[1] = reg; + st->reg_read_xfer[1].rx_buf = val_buf; + st->reg_read_xfer[1].len = val_size; + + return spi_sync(st->spi, &st->reg_read_msg); +} + +static int adxl367_write(void *context, const void *val_buf, size_t val_size) +{ + struct adxl367_spi_state *st = context; + + st->reg_write_xfer[1].tx_buf = val_buf; + st->reg_write_xfer[1].len = val_size; + + return spi_sync(st->spi, &st->reg_write_msg); +} + +static const struct regmap_bus adxl367_spi_regmap_bus = { + .read = adxl367_read, + .write = adxl367_write, +}; + +static const struct regmap_config adxl367_spi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static const struct adxl367_ops adxl367_spi_ops = { + .read_fifo = adxl367_read_fifo, +}; + +static int adxl367_spi_probe(struct spi_device *spi) +{ + struct adxl367_spi_state *st; + struct regmap *regmap; + + st = devm_kzalloc(&spi->dev, sizeof(*st), GFP_KERNEL); + if (!st) + return -ENOMEM; + + st->spi = spi; + + /* + * Xfer: [XFR1] [ XFR2 ] + * Master: 0x0A ADDR DATA0 DATA1 ... DATAN + * Slave: .... .......................... + */ + st->reg_write_tx_buf[0] = ADXL367_SPI_WRITE_COMMAND; + st->reg_write_xfer[0].tx_buf = st->reg_write_tx_buf; + st->reg_write_xfer[0].len = sizeof(st->reg_write_tx_buf); + spi_message_init_with_transfers(&st->reg_write_msg, + st->reg_write_xfer, 2); + + /* + * Xfer: [ XFR1 ] [ XFR2 ] + * Master: 0x0B ADDR ..................... + * Slave: ......... DATA0 DATA1 ... DATAN + */ + st->reg_read_tx_buf[0] = ADXL367_SPI_READ_COMMAND; + st->reg_read_xfer[0].tx_buf = st->reg_read_tx_buf; + st->reg_read_xfer[0].len = sizeof(st->reg_read_tx_buf); + spi_message_init_with_transfers(&st->reg_read_msg, + st->reg_read_xfer, 2); + + /* + * Xfer: [XFR1] [ XFR2 ] + * Master: 0x0D ..................... + * Slave: .... DATA0 DATA1 ... DATAN + */ + st->fifo_tx_buf[0] = ADXL367_SPI_FIFO_COMMAND; + st->fifo_xfer[0].tx_buf = st->fifo_tx_buf; + st->fifo_xfer[0].len = sizeof(st->fifo_tx_buf); + spi_message_init_with_transfers(&st->fifo_msg, st->fifo_xfer, 2); + + regmap = devm_regmap_init(&spi->dev, &adxl367_spi_regmap_bus, st, + &adxl367_spi_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return adxl367_probe(&spi->dev, &adxl367_spi_ops, st, regmap, spi->irq); +} + +static const struct spi_device_id adxl367_spi_id[] = { + { .name = "adxl367" }, + { } +}; +MODULE_DEVICE_TABLE(spi, adxl367_spi_id); + +static const struct of_device_id adxl367_of_match[] = { + { .compatible = "adi,adxl367" }, + { } +}; +MODULE_DEVICE_TABLE(of, adxl367_of_match); + +static struct spi_driver adxl367_spi_driver = { + .driver = { + .name = "adxl367_spi", + .of_match_table = adxl367_of_match, + }, + .probe = adxl367_spi_probe, + .id_table = adxl367_spi_id, +}; + +module_spi_driver(adxl367_spi_driver); + +MODULE_IMPORT_NS("IIO_ADXL367"); +MODULE_AUTHOR("Cosmin Tanislav "); +MODULE_DESCRIPTION("Analog Devices ADXL367 3-axis accelerometer SPI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/accel/adxl372.c b/drivers/iio/accel/adxl372.c new file mode 100644 index 000000000..e375d068a --- /dev/null +++ b/drivers/iio/accel/adxl372.c @@ -0,0 +1,1335 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ADXL371/ADXL372 3-Axis Digital Accelerometer core driver + * + * Copyright 2018 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "adxl372.h" + +/* ADXL372 registers definition */ +#define ADXL372_DEVID 0x00 +#define ADXL372_DEVID_MST 0x01 +#define ADXL372_PARTID 0x02 +#define ADXL372_STATUS_1 0x04 +#define ADXL372_STATUS_2 0x05 +#define ADXL372_FIFO_ENTRIES_2 0x06 +#define ADXL372_FIFO_ENTRIES_1 0x07 +#define ADXL372_X_DATA_H 0x08 +#define ADXL372_X_DATA_L 0x09 +#define ADXL372_Y_DATA_H 0x0A +#define ADXL372_Y_DATA_L 0x0B +#define ADXL372_Z_DATA_H 0x0C +#define ADXL372_Z_DATA_L 0x0D +#define ADXL372_X_MAXPEAK_H 0x15 +#define ADXL372_X_MAXPEAK_L 0x16 +#define ADXL372_Y_MAXPEAK_H 0x17 +#define ADXL372_Y_MAXPEAK_L 0x18 +#define ADXL372_Z_MAXPEAK_H 0x19 +#define ADXL372_Z_MAXPEAK_L 0x1A +#define ADXL372_OFFSET_X 0x20 +#define ADXL372_OFFSET_Y 0x21 +#define ADXL372_OFFSET_Z 0x22 +#define ADXL372_X_THRESH_ACT_H 0x23 +#define ADXL372_X_THRESH_ACT_L 0x24 +#define ADXL372_Y_THRESH_ACT_H 0x25 +#define ADXL372_Y_THRESH_ACT_L 0x26 +#define ADXL372_Z_THRESH_ACT_H 0x27 +#define ADXL372_Z_THRESH_ACT_L 0x28 +#define ADXL372_TIME_ACT 0x29 +#define ADXL372_X_THRESH_INACT_H 0x2A +#define ADXL372_X_THRESH_INACT_L 0x2B +#define ADXL372_Y_THRESH_INACT_H 0x2C +#define ADXL372_Y_THRESH_INACT_L 0x2D +#define ADXL372_Z_THRESH_INACT_H 0x2E +#define ADXL372_Z_THRESH_INACT_L 0x2F +#define ADXL372_TIME_INACT_H 0x30 +#define ADXL372_TIME_INACT_L 0x31 +#define ADXL372_X_THRESH_ACT2_H 0x32 +#define ADXL372_X_THRESH_ACT2_L 0x33 +#define ADXL372_Y_THRESH_ACT2_H 0x34 +#define ADXL372_Y_THRESH_ACT2_L 0x35 +#define ADXL372_Z_THRESH_ACT2_H 0x36 +#define ADXL372_Z_THRESH_ACT2_L 0x37 +#define ADXL372_HPF 0x38 +#define ADXL372_FIFO_SAMPLES 0x39 +#define ADXL372_FIFO_CTL 0x3A +#define ADXL372_INT1_MAP 0x3B +#define ADXL372_INT2_MAP 0x3C +#define ADXL372_TIMING 0x3D +#define ADXL372_MEASURE 0x3E +#define ADXL372_POWER_CTL 0x3F +#define ADXL372_SELF_TEST 0x40 +#define ADXL372_RESET 0x41 +#define ADXL372_FIFO_DATA 0x42 + +#define ADXL372_DEVID_VAL 0xAD +#define ADXL372_PARTID_VAL 0xFA +#define ADXL372_RESET_CODE 0x52 + +/* ADXL372_POWER_CTL */ +#define ADXL372_POWER_CTL_MODE_MSK GENMASK_ULL(1, 0) +#define ADXL372_POWER_CTL_MODE(x) (((x) & 0x3) << 0) + +/* ADXL372_MEASURE */ +#define ADXL372_MEASURE_LINKLOOP_MSK GENMASK_ULL(5, 4) +#define ADXL372_MEASURE_LINKLOOP_MODE(x) (((x) & 0x3) << 4) +#define ADXL372_MEASURE_BANDWIDTH_MSK GENMASK_ULL(2, 0) +#define ADXL372_MEASURE_BANDWIDTH_MODE(x) (((x) & 0x7) << 0) + +/* ADXL372_TIMING */ +#define ADXL372_TIMING_ODR_MSK GENMASK_ULL(7, 5) +#define ADXL372_TIMING_ODR_MODE(x) (((x) & 0x7) << 5) + +/* ADXL372_FIFO_CTL */ +#define ADXL372_FIFO_CTL_FORMAT_MSK GENMASK(5, 3) +#define ADXL372_FIFO_CTL_FORMAT_MODE(x) (((x) & 0x7) << 3) +#define ADXL372_FIFO_CTL_MODE_MSK GENMASK(2, 1) +#define ADXL372_FIFO_CTL_MODE_MODE(x) (((x) & 0x3) << 1) +#define ADXL372_FIFO_CTL_SAMPLES_MSK BIT(1) +#define ADXL372_FIFO_CTL_SAMPLES_MODE(x) (((x) > 0xFF) ? 1 : 0) + +/* ADXL372_STATUS_1 */ +#define ADXL372_STATUS_1_DATA_RDY(x) (((x) >> 0) & 0x1) +#define ADXL372_STATUS_1_FIFO_RDY(x) (((x) >> 1) & 0x1) +#define ADXL372_STATUS_1_FIFO_FULL(x) (((x) >> 2) & 0x1) +#define ADXL372_STATUS_1_FIFO_OVR(x) (((x) >> 3) & 0x1) +#define ADXL372_STATUS_1_USR_NVM_BUSY(x) (((x) >> 5) & 0x1) +#define ADXL372_STATUS_1_AWAKE(x) (((x) >> 6) & 0x1) +#define ADXL372_STATUS_1_ERR_USR_REGS(x) (((x) >> 7) & 0x1) + +/* ADXL372_STATUS_2 */ +#define ADXL372_STATUS_2_INACT(x) (((x) >> 4) & 0x1) +#define ADXL372_STATUS_2_ACT(x) (((x) >> 5) & 0x1) +#define ADXL372_STATUS_2_AC2(x) (((x) >> 6) & 0x1) + +/* ADXL372_INT1_MAP */ +#define ADXL372_INT1_MAP_DATA_RDY_MSK BIT(0) +#define ADXL372_INT1_MAP_DATA_RDY_MODE(x) (((x) & 0x1) << 0) +#define ADXL372_INT1_MAP_FIFO_RDY_MSK BIT(1) +#define ADXL372_INT1_MAP_FIFO_RDY_MODE(x) (((x) & 0x1) << 1) +#define ADXL372_INT1_MAP_FIFO_FULL_MSK BIT(2) +#define ADXL372_INT1_MAP_FIFO_FULL_MODE(x) (((x) & 0x1) << 2) +#define ADXL372_INT1_MAP_FIFO_OVR_MSK BIT(3) +#define ADXL372_INT1_MAP_FIFO_OVR_MODE(x) (((x) & 0x1) << 3) +#define ADXL372_INT1_MAP_INACT_MSK BIT(4) +#define ADXL372_INT1_MAP_INACT_MODE(x) (((x) & 0x1) << 4) +#define ADXL372_INT1_MAP_ACT_MSK BIT(5) +#define ADXL372_INT1_MAP_ACT_MODE(x) (((x) & 0x1) << 5) +#define ADXL372_INT1_MAP_AWAKE_MSK BIT(6) +#define ADXL372_INT1_MAP_AWAKE_MODE(x) (((x) & 0x1) << 6) +#define ADXL372_INT1_MAP_LOW_MSK BIT(7) +#define ADXL372_INT1_MAP_LOW_MODE(x) (((x) & 0x1) << 7) + +/* ADX372_THRESH */ +#define ADXL372_THRESH_VAL_H_MSK GENMASK(10, 3) +#define ADXL372_THRESH_VAL_H_SEL(x) FIELD_GET(ADXL372_THRESH_VAL_H_MSK, x) +#define ADXL372_THRESH_VAL_L_MSK GENMASK(2, 0) +#define ADXL372_THRESH_VAL_L_SEL(x) FIELD_GET(ADXL372_THRESH_VAL_L_MSK, x) + +/* The ADXL372 includes a deep, 512 sample FIFO buffer */ +#define ADXL372_FIFO_SIZE 512 +#define ADXL372_X_AXIS_EN(x) ((x) & BIT(0)) +#define ADXL372_Y_AXIS_EN(x) ((x) & BIT(1)) +#define ADXL372_Z_AXIS_EN(x) ((x) & BIT(2)) + +/* + * At +/- 200g with 12-bit resolution, scale is computed as: + * (200 + 200) * 9.81 / (2^12 - 1) = 0.958241 + */ +#define ADXL372_USCALE 958241 + +enum adxl372_op_mode { + ADXL372_STANDBY, + ADXL372_WAKE_UP, + ADXL372_INSTANT_ON, + ADXL372_FULL_BW_MEASUREMENT, +}; + +enum adxl372_act_proc_mode { + ADXL372_DEFAULT, + ADXL372_LINKED, + ADXL372_LOOPED, +}; + +enum adxl372_th_activity { + ADXL372_ACTIVITY, + ADXL372_ACTIVITY2, + ADXL372_INACTIVITY, +}; + +enum adxl372_odr { + ADXL372_ODR_400HZ, + ADXL372_ODR_800HZ, + ADXL372_ODR_1600HZ, + ADXL372_ODR_3200HZ, + ADXL372_ODR_6400HZ, + ADXL372_ODR_NUM +}; + +enum adxl371_odr { + ADXL371_ODR_320HZ, + ADXL371_ODR_640HZ, + ADXL371_ODR_1280HZ, + ADXL371_ODR_2560HZ, + ADXL371_ODR_5120HZ, + ADXL371_ODR_NUM +}; + +enum adxl372_bandwidth { + ADXL372_BW_200HZ, + ADXL372_BW_400HZ, + ADXL372_BW_800HZ, + ADXL372_BW_1600HZ, + ADXL372_BW_3200HZ, +}; + +static const unsigned int adxl372_th_reg_high_addr[3] = { + [ADXL372_ACTIVITY] = ADXL372_X_THRESH_ACT_H, + [ADXL372_ACTIVITY2] = ADXL372_X_THRESH_ACT2_H, + [ADXL372_INACTIVITY] = ADXL372_X_THRESH_INACT_H, +}; + +enum adxl372_fifo_format { + ADXL372_XYZ_FIFO, + ADXL372_X_FIFO, + ADXL372_Y_FIFO, + ADXL372_XY_FIFO, + ADXL372_Z_FIFO, + ADXL372_XZ_FIFO, + ADXL372_YZ_FIFO, + ADXL372_XYZ_PEAK_FIFO, +}; + +enum adxl372_fifo_mode { + ADXL372_FIFO_BYPASSED, + ADXL372_FIFO_STREAMED, + ADXL372_FIFO_TRIGGERED, + ADXL372_FIFO_OLD_SAVED +}; + +static const int adxl372_samp_freq_tbl[ADXL372_ODR_NUM] = { + [ADXL372_ODR_400HZ] = 400, + [ADXL372_ODR_800HZ] = 800, + [ADXL372_ODR_1600HZ] = 1600, + [ADXL372_ODR_3200HZ] = 3200, + [ADXL372_ODR_6400HZ] = 6400, +}; + +static const int adxl372_bw_freq_tbl[ADXL372_ODR_NUM] = { + [ADXL372_BW_200HZ] = 200, + [ADXL372_BW_400HZ] = 400, + [ADXL372_BW_800HZ] = 800, + [ADXL372_BW_1600HZ] = 1600, + [ADXL372_BW_3200HZ] = 3200, +}; + +static const int adxl371_samp_freq_tbl[ADXL371_ODR_NUM] = { + [ADXL371_ODR_320HZ] = 320, + [ADXL371_ODR_640HZ] = 640, + [ADXL371_ODR_1280HZ] = 1280, + [ADXL371_ODR_2560HZ] = 2560, + [ADXL371_ODR_5120HZ] = 5120, +}; + +static const int adxl371_bw_freq_tbl[ADXL371_ODR_NUM] = { + [ADXL371_ODR_320HZ] = 160, + [ADXL371_ODR_640HZ] = 320, + [ADXL371_ODR_1280HZ] = 640, + [ADXL371_ODR_2560HZ] = 1280, + [ADXL371_ODR_5120HZ] = 2560, +}; + +const struct adxl372_chip_info adxl371_chip_info = { + .name = "adxl371", + .samp_freq_tbl = adxl371_samp_freq_tbl, + .bw_freq_tbl = adxl371_bw_freq_tbl, + .num_freqs = ARRAY_SIZE(adxl371_samp_freq_tbl), + .act_time_scale_us = 4125, + .act_time_scale_low_us = 8250, + .inact_time_scale_ms = 16, + .inact_time_scale_low_ms = 32, + .max_odr = ADXL371_ODR_5120HZ, + /* Silicon erratum (er001) causes FIFO data misalignment on ADXL371 */ + .fifo_supported = false, +}; +EXPORT_SYMBOL_NS_GPL(adxl371_chip_info, "IIO_ADXL372"); + +const struct adxl372_chip_info adxl372_chip_info = { + .name = "adxl372", + .samp_freq_tbl = adxl372_samp_freq_tbl, + .bw_freq_tbl = adxl372_bw_freq_tbl, + .num_freqs = ARRAY_SIZE(adxl372_samp_freq_tbl), + .act_time_scale_us = 3300, + .act_time_scale_low_us = 6600, + .inact_time_scale_ms = 13, + .inact_time_scale_low_ms = 26, + .max_odr = ADXL372_ODR_6400HZ, + .fifo_supported = true, +}; +EXPORT_SYMBOL_NS_GPL(adxl372_chip_info, "IIO_ADXL372"); + +struct adxl372_axis_lookup { + unsigned int bits; + enum adxl372_fifo_format fifo_format; +}; + +static const struct adxl372_axis_lookup adxl372_axis_lookup_table[] = { + { BIT(0), ADXL372_X_FIFO }, + { BIT(1), ADXL372_Y_FIFO }, + { BIT(2), ADXL372_Z_FIFO }, + { BIT(0) | BIT(1), ADXL372_XY_FIFO }, + { BIT(0) | BIT(2), ADXL372_XZ_FIFO }, + { BIT(1) | BIT(2), ADXL372_YZ_FIFO }, + { BIT(0) | BIT(1) | BIT(2), ADXL372_XYZ_FIFO }, +}; + +static const struct iio_event_spec adxl372_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + .mask_shared_by_all = BIT(IIO_EV_INFO_PERIOD) | BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + .mask_shared_by_all = BIT(IIO_EV_INFO_PERIOD) | BIT(IIO_EV_INFO_ENABLE), + }, +}; + +#define ADXL372_ACCEL_CHANNEL(index, reg, axis) { \ + .type = IIO_ACCEL, \ + .address = reg, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 12, \ + .storagebits = 16, \ + .shift = 4, \ + .endianness = IIO_BE, \ + }, \ + .event_spec = adxl372_events, \ + .num_event_specs = ARRAY_SIZE(adxl372_events) \ +} + +static const struct iio_chan_spec adxl372_channels[] = { + ADXL372_ACCEL_CHANNEL(0, ADXL372_X_DATA_H, X), + ADXL372_ACCEL_CHANNEL(1, ADXL372_Y_DATA_H, Y), + ADXL372_ACCEL_CHANNEL(2, ADXL372_Z_DATA_H, Z), +}; + +struct adxl372_state { + const struct adxl372_chip_info *chip_info; + int irq; + struct device *dev; + struct regmap *regmap; + struct iio_trigger *dready_trig; + struct iio_trigger *peak_datardy_trig; + enum adxl372_fifo_mode fifo_mode; + enum adxl372_fifo_format fifo_format; + unsigned int fifo_axis_mask; + enum adxl372_op_mode op_mode; + enum adxl372_act_proc_mode act_proc_mode; + enum adxl372_odr odr; + enum adxl372_bandwidth bw; + u32 act_time_ms; + u32 inact_time_ms; + u8 fifo_set_size; + unsigned long int1_bitmask; + u16 watermark; + __be16 fifo_buf[ADXL372_FIFO_SIZE]; + bool peak_fifo_mode_en; + struct mutex threshold_m; /* lock for threshold */ +}; + +static const unsigned long adxl372_channel_masks[] = { + BIT(0), BIT(1), BIT(2), + BIT(0) | BIT(1), + BIT(0) | BIT(2), + BIT(1) | BIT(2), + BIT(0) | BIT(1) | BIT(2), + 0 +}; + +static ssize_t adxl372_read_threshold_value(struct iio_dev *indio_dev, unsigned int addr, + u16 *threshold) +{ + struct adxl372_state *st = iio_priv(indio_dev); + __be16 raw_regval; + u16 regval; + int ret; + + ret = regmap_bulk_read(st->regmap, addr, &raw_regval, sizeof(raw_regval)); + if (ret < 0) + return ret; + + regval = be16_to_cpu(raw_regval); + regval >>= 5; + + *threshold = regval; + + return 0; +} + +static ssize_t adxl372_write_threshold_value(struct iio_dev *indio_dev, unsigned int addr, + u16 threshold) +{ + struct adxl372_state *st = iio_priv(indio_dev); + int ret; + + guard(mutex)(&st->threshold_m); + + ret = regmap_write(st->regmap, addr, ADXL372_THRESH_VAL_H_SEL(threshold)); + if (ret < 0) + return ret; + + return regmap_update_bits(st->regmap, addr + 1, GENMASK(7, 5), + ADXL372_THRESH_VAL_L_SEL(threshold) << 5); +} + +static int adxl372_read_axis(struct adxl372_state *st, u8 addr) +{ + __be16 regval; + int ret; + + ret = regmap_bulk_read(st->regmap, addr, ®val, sizeof(regval)); + if (ret < 0) + return ret; + + return be16_to_cpu(regval); +} + +static int adxl372_set_op_mode(struct adxl372_state *st, + enum adxl372_op_mode op_mode) +{ + int ret; + + ret = regmap_update_bits(st->regmap, ADXL372_POWER_CTL, + ADXL372_POWER_CTL_MODE_MSK, + ADXL372_POWER_CTL_MODE(op_mode)); + if (ret < 0) + return ret; + + st->op_mode = op_mode; + + return ret; +} + +static int adxl372_set_odr(struct adxl372_state *st, + enum adxl372_odr odr) +{ + int ret; + + ret = regmap_update_bits(st->regmap, ADXL372_TIMING, + ADXL372_TIMING_ODR_MSK, + ADXL372_TIMING_ODR_MODE(odr)); + if (ret < 0) + return ret; + + st->odr = odr; + + return ret; +} + +static int adxl372_find_closest_match(const int *array, + unsigned int size, int val) +{ + int i; + + for (i = 0; i < size; i++) { + if (val <= array[i]) + return i; + } + + return size - 1; +} + +static int adxl372_set_bandwidth(struct adxl372_state *st, + enum adxl372_bandwidth bw) +{ + int ret; + + ret = regmap_update_bits(st->regmap, ADXL372_MEASURE, + ADXL372_MEASURE_BANDWIDTH_MSK, + ADXL372_MEASURE_BANDWIDTH_MODE(bw)); + if (ret < 0) + return ret; + + st->bw = bw; + + return ret; +} + +static int adxl372_set_act_proc_mode(struct adxl372_state *st, + enum adxl372_act_proc_mode mode) +{ + int ret; + + ret = regmap_update_bits(st->regmap, + ADXL372_MEASURE, + ADXL372_MEASURE_LINKLOOP_MSK, + ADXL372_MEASURE_LINKLOOP_MODE(mode)); + if (ret < 0) + return ret; + + st->act_proc_mode = mode; + + return ret; +} + +static int adxl372_set_activity_threshold(struct adxl372_state *st, + enum adxl372_th_activity act, + bool ref_en, bool enable, + unsigned int threshold) +{ + unsigned char buf[6]; + unsigned char th_reg_high_val, th_reg_low_val, th_reg_high_addr; + + /* scale factor is 100 mg/code */ + th_reg_high_val = (threshold / 100) >> 3; + th_reg_low_val = ((threshold / 100) << 5) | (ref_en << 1) | enable; + th_reg_high_addr = adxl372_th_reg_high_addr[act]; + + buf[0] = th_reg_high_val; + buf[1] = th_reg_low_val; + buf[2] = th_reg_high_val; + buf[3] = th_reg_low_val; + buf[4] = th_reg_high_val; + buf[5] = th_reg_low_val; + + return regmap_bulk_write(st->regmap, th_reg_high_addr, + buf, ARRAY_SIZE(buf)); +} + +static int adxl372_set_activity_time_ms(struct adxl372_state *st, + unsigned int act_time_ms) +{ + unsigned int reg_val, scale_factor; + int ret; + + /* + * The scale factor of the TIME_ACT register depends on the ODR. + * A higher scale factor is used at the maximum ODR and a lower + * one at all other rates. + */ + if (st->odr == st->chip_info->max_odr) + scale_factor = st->chip_info->act_time_scale_us; + else + scale_factor = st->chip_info->act_time_scale_low_us; + + reg_val = DIV_ROUND_CLOSEST(act_time_ms * 1000, scale_factor); + + /* TIME_ACT register is 8 bits wide */ + if (reg_val > 0xFF) + reg_val = 0xFF; + + ret = regmap_write(st->regmap, ADXL372_TIME_ACT, reg_val); + if (ret < 0) + return ret; + + st->act_time_ms = act_time_ms; + + return ret; +} + +static int adxl372_set_inactivity_time_ms(struct adxl372_state *st, + unsigned int inact_time_ms) +{ + unsigned int reg_val_h, reg_val_l, res, scale_factor; + int ret; + + /* + * The scale factor of the TIME_INACT register depends on the ODR. + * A higher scale factor is used at the maximum ODR and a lower + * one at all other rates. + */ + if (st->odr == st->chip_info->max_odr) + scale_factor = st->chip_info->inact_time_scale_ms; + else + scale_factor = st->chip_info->inact_time_scale_low_ms; + + res = DIV_ROUND_CLOSEST(inact_time_ms, scale_factor); + reg_val_h = (res >> 8) & 0xFF; + reg_val_l = res & 0xFF; + + ret = regmap_write(st->regmap, ADXL372_TIME_INACT_H, reg_val_h); + if (ret < 0) + return ret; + + ret = regmap_write(st->regmap, ADXL372_TIME_INACT_L, reg_val_l); + if (ret < 0) + return ret; + + st->inact_time_ms = inact_time_ms; + + return ret; +} + +static int adxl372_set_interrupts(struct adxl372_state *st, + unsigned long int1_bitmask, + unsigned long int2_bitmask) +{ + int ret; + + ret = regmap_write(st->regmap, ADXL372_INT1_MAP, int1_bitmask); + if (ret < 0) + return ret; + + return regmap_write(st->regmap, ADXL372_INT2_MAP, int2_bitmask); +} + +static int adxl372_configure_fifo(struct adxl372_state *st) +{ + unsigned int fifo_samples, fifo_ctl; + int ret; + + /* FIFO must be configured while in standby mode */ + ret = adxl372_set_op_mode(st, ADXL372_STANDBY); + if (ret < 0) + return ret; + + /* + * watermark stores the number of sets; we need to write the FIFO + * registers with the number of samples + */ + fifo_samples = (st->watermark * st->fifo_set_size); + fifo_ctl = ADXL372_FIFO_CTL_FORMAT_MODE(st->fifo_format) | + ADXL372_FIFO_CTL_MODE_MODE(st->fifo_mode) | + ADXL372_FIFO_CTL_SAMPLES_MODE(fifo_samples); + + ret = regmap_write(st->regmap, + ADXL372_FIFO_SAMPLES, fifo_samples & 0xFF); + if (ret < 0) + return ret; + + ret = regmap_write(st->regmap, ADXL372_FIFO_CTL, fifo_ctl); + if (ret < 0) + return ret; + + return adxl372_set_op_mode(st, ADXL372_FULL_BW_MEASUREMENT); +} + +static int adxl372_get_status(struct adxl372_state *st, + u8 *status1, u8 *status2, + u16 *fifo_entries) +{ + __be32 buf; + u32 val; + int ret; + + /* STATUS1, STATUS2, FIFO_ENTRIES2 and FIFO_ENTRIES are adjacent regs */ + ret = regmap_bulk_read(st->regmap, ADXL372_STATUS_1, + &buf, sizeof(buf)); + if (ret < 0) + return ret; + + val = be32_to_cpu(buf); + + *status1 = (val >> 24) & 0x0F; + *status2 = (val >> 16) & 0x0F; + /* + * FIFO_ENTRIES contains the least significant byte, and FIFO_ENTRIES2 + * contains the two most significant bits + */ + *fifo_entries = val & 0x3FF; + + return ret; +} + +static void adxl372_arrange_axis_data(struct adxl372_state *st, __be16 *sample) +{ + __be16 axis_sample[3] = { }; + int i = 0; + + if (ADXL372_X_AXIS_EN(st->fifo_axis_mask)) + axis_sample[i++] = sample[0]; + if (ADXL372_Y_AXIS_EN(st->fifo_axis_mask)) + axis_sample[i++] = sample[1]; + if (ADXL372_Z_AXIS_EN(st->fifo_axis_mask)) + axis_sample[i++] = sample[2]; + + memcpy(sample, axis_sample, 3 * sizeof(__be16)); +} + +static void adxl372_push_event(struct iio_dev *indio_dev, s64 timestamp, u8 status2) +{ + unsigned int ev_dir = IIO_EV_DIR_NONE; + + if (ADXL372_STATUS_2_ACT(status2)) + ev_dir = IIO_EV_DIR_RISING; + + if (ADXL372_STATUS_2_INACT(status2)) + ev_dir = IIO_EV_DIR_FALLING; + + if (ev_dir != IIO_EV_DIR_NONE) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_THRESH, ev_dir), + timestamp); +} + +static irqreturn_t adxl372_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adxl372_state *st = iio_priv(indio_dev); + u8 status1, status2; + u16 fifo_entries; + int i, ret; + + ret = adxl372_get_status(st, &status1, &status2, &fifo_entries); + if (ret < 0) + goto err; + + adxl372_push_event(indio_dev, iio_get_time_ns(indio_dev), status2); + + if (st->fifo_mode != ADXL372_FIFO_BYPASSED && + ADXL372_STATUS_1_FIFO_FULL(status1)) { + /* + * When reading data from multiple axes from the FIFO, + * to ensure that data is not overwritten and stored out + * of order at least one sample set must be left in the + * FIFO after every read. + */ + fifo_entries -= st->fifo_set_size; + + /* Read data from the FIFO */ + ret = regmap_noinc_read(st->regmap, ADXL372_FIFO_DATA, + st->fifo_buf, + fifo_entries * sizeof(u16)); + if (ret < 0) + goto err; + + /* Each sample is 2 bytes */ + for (i = 0; i < fifo_entries; i += st->fifo_set_size) { + /* filter peak detection data */ + if (st->peak_fifo_mode_en) + adxl372_arrange_axis_data(st, &st->fifo_buf[i]); + iio_push_to_buffers(indio_dev, &st->fifo_buf[i]); + } + } +err: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int adxl372_setup(struct adxl372_state *st) +{ + unsigned int regval; + int ret; + + ret = regmap_read(st->regmap, ADXL372_DEVID, ®val); + if (ret < 0) + return ret; + + if (regval != ADXL372_DEVID_VAL) { + dev_err(st->dev, "Invalid chip id %x\n", regval); + return -ENODEV; + } + + /* + * Perform a software reset to make sure the device is in a consistent + * state after start up. + */ + ret = regmap_write(st->regmap, ADXL372_RESET, ADXL372_RESET_CODE); + if (ret < 0) + return ret; + + ret = adxl372_set_op_mode(st, ADXL372_STANDBY); + if (ret < 0) + return ret; + + /* Set threshold for activity detection to 1g */ + ret = adxl372_set_activity_threshold(st, ADXL372_ACTIVITY, + true, true, 1000); + if (ret < 0) + return ret; + + /* Set threshold for inactivity detection to 100mg */ + ret = adxl372_set_activity_threshold(st, ADXL372_INACTIVITY, + true, true, 100); + if (ret < 0) + return ret; + + /* Set activity processing in Looped mode */ + ret = adxl372_set_act_proc_mode(st, ADXL372_LOOPED); + if (ret < 0) + return ret; + + ret = adxl372_set_odr(st, st->chip_info->max_odr); + if (ret < 0) + return ret; + + ret = adxl372_set_bandwidth(st, ADXL372_BW_3200HZ); + if (ret < 0) + return ret; + + /* Set activity timer to 1ms */ + ret = adxl372_set_activity_time_ms(st, 1); + if (ret < 0) + return ret; + + /* Set inactivity timer to 10s */ + ret = adxl372_set_inactivity_time_ms(st, 10000); + if (ret < 0) + return ret; + + /* Set the mode of operation to full bandwidth measurement mode */ + return adxl372_set_op_mode(st, ADXL372_FULL_BW_MEASUREMENT); +} + +static int adxl372_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct adxl372_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + else + return regmap_write(st->regmap, reg, writeval); +} + +static int adxl372_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct adxl372_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = adxl372_read_axis(st, chan->address); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + + *val = sign_extend32(ret >> chan->scan_type.shift, + chan->scan_type.realbits - 1); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = ADXL372_USCALE; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = st->chip_info->samp_freq_tbl[st->odr]; + return IIO_VAL_INT; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + *val = st->chip_info->bw_freq_tbl[st->bw]; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int adxl372_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct adxl372_state *st = iio_priv(indio_dev); + int odr_index, bw_index, ret; + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + odr_index = adxl372_find_closest_match(st->chip_info->samp_freq_tbl, + st->chip_info->num_freqs, + val); + ret = adxl372_set_odr(st, odr_index); + if (ret < 0) + return ret; + /* Recalculate activity time as the timer period depends on ODR */ + ret = adxl372_set_activity_time_ms(st, st->act_time_ms); + if (ret < 0) + return ret; + /* Recalculate inactivity time as the timer period depends on ODR */ + ret = adxl372_set_inactivity_time_ms(st, st->inact_time_ms); + if (ret < 0) + return ret; + /* + * The maximum bandwidth is constrained to at most half of + * the ODR to ensure that the Nyquist criteria is not violated + */ + if (st->bw > odr_index) + ret = adxl372_set_bandwidth(st, odr_index); + + return ret; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + bw_index = adxl372_find_closest_match(st->chip_info->bw_freq_tbl, + st->chip_info->num_freqs, + val); + return adxl372_set_bandwidth(st, bw_index); + default: + return -EINVAL; + } +} + +static int adxl372_read_event_value(struct iio_dev *indio_dev, const struct iio_chan_spec *chan, + enum iio_event_type type, enum iio_event_direction dir, + enum iio_event_info info, int *val, int *val2) +{ + struct adxl372_state *st = iio_priv(indio_dev); + unsigned int addr; + u16 raw_value; + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + addr = ADXL372_X_THRESH_ACT_H + 2 * chan->scan_index; + ret = adxl372_read_threshold_value(indio_dev, addr, &raw_value); + if (ret < 0) + return ret; + *val = raw_value * ADXL372_USCALE; + *val2 = 1000000; + return IIO_VAL_FRACTIONAL; + case IIO_EV_DIR_FALLING: + addr = ADXL372_X_THRESH_INACT_H + 2 * chan->scan_index; + ret = adxl372_read_threshold_value(indio_dev, addr, &raw_value); + if (ret < 0) + return ret; + *val = raw_value * ADXL372_USCALE; + *val2 = 1000000; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_EV_INFO_PERIOD: + switch (dir) { + case IIO_EV_DIR_RISING: + *val = st->act_time_ms; + *val2 = 1000; + return IIO_VAL_FRACTIONAL; + case IIO_EV_DIR_FALLING: + *val = st->inact_time_ms; + *val2 = 1000; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int adxl372_write_event_value(struct iio_dev *indio_dev, const struct iio_chan_spec *chan, + enum iio_event_type type, enum iio_event_direction dir, + enum iio_event_info info, int val, int val2) +{ + struct adxl372_state *st = iio_priv(indio_dev); + unsigned int val_ms; + unsigned int addr; + u16 raw_val; + + switch (info) { + case IIO_EV_INFO_VALUE: + raw_val = DIV_ROUND_UP(val * 1000000, ADXL372_USCALE); + switch (dir) { + case IIO_EV_DIR_RISING: + addr = ADXL372_X_THRESH_ACT_H + 2 * chan->scan_index; + return adxl372_write_threshold_value(indio_dev, addr, raw_val); + case IIO_EV_DIR_FALLING: + addr = ADXL372_X_THRESH_INACT_H + 2 * chan->scan_index; + return adxl372_write_threshold_value(indio_dev, addr, raw_val); + default: + return -EINVAL; + } + case IIO_EV_INFO_PERIOD: + val_ms = val * 1000 + DIV_ROUND_UP(val2, 1000); + switch (dir) { + case IIO_EV_DIR_RISING: + return adxl372_set_activity_time_ms(st, val_ms); + case IIO_EV_DIR_FALLING: + return adxl372_set_inactivity_time_ms(st, val_ms); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int adxl372_read_event_config(struct iio_dev *indio_dev, const struct iio_chan_spec *chan, + enum iio_event_type type, enum iio_event_direction dir) +{ + struct adxl372_state *st = iio_priv(indio_dev); + + switch (dir) { + case IIO_EV_DIR_RISING: + return FIELD_GET(ADXL372_INT1_MAP_ACT_MSK, st->int1_bitmask); + case IIO_EV_DIR_FALLING: + return FIELD_GET(ADXL372_INT1_MAP_INACT_MSK, st->int1_bitmask); + default: + return -EINVAL; + } +} + +static int adxl372_write_event_config(struct iio_dev *indio_dev, const struct iio_chan_spec *chan, + enum iio_event_type type, enum iio_event_direction dir, + bool state) +{ + struct adxl372_state *st = iio_priv(indio_dev); + + switch (dir) { + case IIO_EV_DIR_RISING: + set_mask_bits(&st->int1_bitmask, ADXL372_INT1_MAP_ACT_MSK, + ADXL372_INT1_MAP_ACT_MODE(state)); + break; + case IIO_EV_DIR_FALLING: + set_mask_bits(&st->int1_bitmask, ADXL372_INT1_MAP_INACT_MSK, + ADXL372_INT1_MAP_INACT_MODE(state)); + break; + default: + return -EINVAL; + } + + return adxl372_set_interrupts(st, st->int1_bitmask, 0); +} + +static ssize_t adxl372_get_fifo_enabled(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct adxl372_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", st->fifo_mode); +} + +static ssize_t adxl372_get_fifo_watermark(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct adxl372_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", st->watermark); +} + +IIO_STATIC_CONST_DEVICE_ATTR(hwfifo_watermark_min, "1"); +IIO_STATIC_CONST_DEVICE_ATTR(hwfifo_watermark_max, + __stringify(ADXL372_FIFO_SIZE)); +static IIO_DEVICE_ATTR(hwfifo_watermark, 0444, + adxl372_get_fifo_watermark, NULL, 0); +static IIO_DEVICE_ATTR(hwfifo_enabled, 0444, + adxl372_get_fifo_enabled, NULL, 0); + +static const struct iio_dev_attr *adxl372_fifo_attributes[] = { + &iio_dev_attr_hwfifo_watermark_min, + &iio_dev_attr_hwfifo_watermark_max, + &iio_dev_attr_hwfifo_watermark, + &iio_dev_attr_hwfifo_enabled, + NULL, +}; + +static int adxl372_set_watermark(struct iio_dev *indio_dev, unsigned int val) +{ + struct adxl372_state *st = iio_priv(indio_dev); + + if (val > ADXL372_FIFO_SIZE) + val = ADXL372_FIFO_SIZE; + + st->watermark = val; + + return 0; +} + +static int adxl372_buffer_postenable(struct iio_dev *indio_dev) +{ + struct adxl372_state *st = iio_priv(indio_dev); + unsigned int mask; + int i, ret; + + st->int1_bitmask |= ADXL372_INT1_MAP_FIFO_FULL_MSK; + ret = adxl372_set_interrupts(st, st->int1_bitmask, 0); + if (ret < 0) + return ret; + + mask = *indio_dev->active_scan_mask; + + for (i = 0; i < ARRAY_SIZE(adxl372_axis_lookup_table); i++) { + if (mask == adxl372_axis_lookup_table[i].bits) + break; + } + + if (i == ARRAY_SIZE(adxl372_axis_lookup_table)) + return -EINVAL; + + st->fifo_format = adxl372_axis_lookup_table[i].fifo_format; + st->fifo_axis_mask = adxl372_axis_lookup_table[i].bits; + st->fifo_set_size = bitmap_weight(indio_dev->active_scan_mask, + iio_get_masklength(indio_dev)); + + /* Configure the FIFO to store sets of impact event peak. */ + if (st->peak_fifo_mode_en) { + st->fifo_set_size = 3; + st->fifo_format = ADXL372_XYZ_PEAK_FIFO; + } + + /* + * The 512 FIFO samples can be allotted in several ways, such as: + * 170 sample sets of concurrent 3-axis data + * 256 sample sets of concurrent 2-axis data (user selectable) + * 512 sample sets of single-axis data + * 170 sets of impact event peak (x, y, z) + */ + if ((st->watermark * st->fifo_set_size) > ADXL372_FIFO_SIZE) + st->watermark = (ADXL372_FIFO_SIZE / st->fifo_set_size); + + st->fifo_mode = ADXL372_FIFO_STREAMED; + + ret = adxl372_configure_fifo(st); + if (ret < 0) { + st->fifo_mode = ADXL372_FIFO_BYPASSED; + st->int1_bitmask &= ~ADXL372_INT1_MAP_FIFO_FULL_MSK; + adxl372_set_interrupts(st, st->int1_bitmask, 0); + return ret; + } + + return 0; +} + +static int adxl372_buffer_predisable(struct iio_dev *indio_dev) +{ + struct adxl372_state *st = iio_priv(indio_dev); + + st->int1_bitmask &= ~ADXL372_INT1_MAP_FIFO_FULL_MSK; + adxl372_set_interrupts(st, st->int1_bitmask, 0); + st->fifo_mode = ADXL372_FIFO_BYPASSED; + adxl372_configure_fifo(st); + + return 0; +} + +static const struct iio_buffer_setup_ops adxl372_buffer_ops = { + .postenable = adxl372_buffer_postenable, + .predisable = adxl372_buffer_predisable, +}; + +static int adxl372_dready_trig_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct adxl372_state *st = iio_priv(indio_dev); + + if (state) + st->int1_bitmask |= ADXL372_INT1_MAP_FIFO_FULL_MSK; + + return adxl372_set_interrupts(st, st->int1_bitmask, 0); +} + +static int adxl372_validate_trigger(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + struct adxl372_state *st = iio_priv(indio_dev); + + if (st->dready_trig != trig && st->peak_datardy_trig != trig) + return -EINVAL; + + return 0; +} + +static const struct iio_trigger_ops adxl372_trigger_ops = { + .validate_device = &iio_trigger_validate_own_device, + .set_trigger_state = adxl372_dready_trig_set_state, +}; + +static int adxl372_peak_dready_trig_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct adxl372_state *st = iio_priv(indio_dev); + + if (state) + st->int1_bitmask |= ADXL372_INT1_MAP_FIFO_FULL_MSK; + + st->peak_fifo_mode_en = state; + + return adxl372_set_interrupts(st, st->int1_bitmask, 0); +} + +static const struct iio_trigger_ops adxl372_peak_data_trigger_ops = { + .validate_device = &iio_trigger_validate_own_device, + .set_trigger_state = adxl372_peak_dready_trig_set_state, +}; + +static int adxl372_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct adxl372_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = st->chip_info->samp_freq_tbl; + *type = IIO_VAL_INT; + *length = st->chip_info->num_freqs; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + *vals = st->chip_info->bw_freq_tbl; + *type = IIO_VAL_INT; + /* + * Bandwidth cannot exceed half the sampling frequency + * (Nyquist), so limit available values based on current ODR. + */ + *length = st->odr + 1; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const struct iio_info adxl372_info = { + .validate_trigger = &adxl372_validate_trigger, + .read_raw = adxl372_read_raw, + .write_raw = adxl372_write_raw, + .read_avail = adxl372_read_avail, + .read_event_config = adxl372_read_event_config, + .write_event_config = adxl372_write_event_config, + .read_event_value = adxl372_read_event_value, + .write_event_value = adxl372_write_event_value, + .debugfs_reg_access = &adxl372_reg_access, + .hwfifo_set_watermark = adxl372_set_watermark, +}; + +bool adxl372_readable_noinc_reg(struct device *dev, unsigned int reg) +{ + return (reg == ADXL372_FIFO_DATA); +} +EXPORT_SYMBOL_NS_GPL(adxl372_readable_noinc_reg, "IIO_ADXL372"); + +static int adxl372_buffer_setup(struct iio_dev *indio_dev) +{ + struct adxl372_state *st = iio_priv(indio_dev); + struct device *dev = st->dev; + int ret; + + ret = devm_iio_triggered_buffer_setup_ext(dev, indio_dev, NULL, + adxl372_trigger_handler, + IIO_BUFFER_DIRECTION_IN, + &adxl372_buffer_ops, + adxl372_fifo_attributes); + if (ret) + return ret; + + if (!st->irq) + return 0; + + st->dready_trig = devm_iio_trigger_alloc(dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!st->dready_trig) + return -ENOMEM; + + st->peak_datardy_trig = devm_iio_trigger_alloc(dev, "%s-dev%d-peak", + indio_dev->name, + iio_device_id(indio_dev)); + if (!st->peak_datardy_trig) + return -ENOMEM; + + st->dready_trig->ops = &adxl372_trigger_ops; + st->peak_datardy_trig->ops = &adxl372_peak_data_trigger_ops; + iio_trigger_set_drvdata(st->dready_trig, indio_dev); + iio_trigger_set_drvdata(st->peak_datardy_trig, indio_dev); + ret = devm_iio_trigger_register(dev, st->dready_trig); + if (ret) + return ret; + + ret = devm_iio_trigger_register(dev, st->peak_datardy_trig); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(st->dready_trig); + + return devm_request_irq(dev, st->irq, + iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_RISING | IRQF_NO_THREAD, + indio_dev->name, st->dready_trig); +} + +int adxl372_probe(struct device *dev, struct regmap *regmap, + int irq, const struct adxl372_chip_info *chip_info) +{ + struct iio_dev *indio_dev; + struct adxl372_state *st; + int ret; + + 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; + st->regmap = regmap; + st->irq = irq; + st->chip_info = chip_info; + + ret = devm_mutex_init(dev, &st->threshold_m); + if (ret < 0) + return ret; + + indio_dev->channels = adxl372_channels; + indio_dev->num_channels = ARRAY_SIZE(adxl372_channels); + indio_dev->name = chip_info->name; + indio_dev->info = &adxl372_info; + + if (chip_info->fifo_supported) { + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + indio_dev->available_scan_masks = adxl372_channel_masks; + } else { + indio_dev->modes = INDIO_DIRECT_MODE; + } + + ret = adxl372_setup(st); + if (ret < 0) + return dev_err_probe(dev, ret, "ADXL372 setup failed\n"); + + if (chip_info->fifo_supported) { + ret = adxl372_buffer_setup(indio_dev); + if (ret < 0) + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS_GPL(adxl372_probe, "IIO_ADXL372"); + +MODULE_AUTHOR("Stefan Popa "); +MODULE_AUTHOR("Antoniu Miclaus "); +MODULE_DESCRIPTION("Analog Devices ADXL371/ADXL372 3-axis accelerometer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/accel/adxl372.h b/drivers/iio/accel/adxl372.h new file mode 100644 index 000000000..353a8b3a9 --- /dev/null +++ b/drivers/iio/accel/adxl372.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * ADXL371/ADXL372 3-Axis Digital Accelerometer + * + * Copyright 2018 Analog Devices Inc. + */ + +#ifndef _ADXL372_H_ +#define _ADXL372_H_ + +#define ADXL372_REVID 0x03 + +struct adxl372_chip_info { + const char *name; + const int *samp_freq_tbl; + const int *bw_freq_tbl; + unsigned int num_freqs; + unsigned int act_time_scale_us; + unsigned int act_time_scale_low_us; + unsigned int inact_time_scale_ms; + unsigned int inact_time_scale_low_ms; + unsigned int max_odr; + bool fifo_supported; +}; + +extern const struct adxl372_chip_info adxl371_chip_info; +extern const struct adxl372_chip_info adxl372_chip_info; + +int adxl372_probe(struct device *dev, struct regmap *regmap, + int irq, const struct adxl372_chip_info *chip_info); +bool adxl372_readable_noinc_reg(struct device *dev, unsigned int reg); + +#endif /* _ADXL372_H_ */ diff --git a/drivers/iio/accel/adxl372_i2c.c b/drivers/iio/accel/adxl372_i2c.c new file mode 100644 index 000000000..e06ddb9c9 --- /dev/null +++ b/drivers/iio/accel/adxl372_i2c.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ADXL371/ADXL372 3-Axis Digital Accelerometer I2C driver + * + * Copyright 2018 Analog Devices Inc. + */ + +#include +#include +#include + +#include "adxl372.h" + +static const struct regmap_config adxl372_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .readable_noinc_reg = adxl372_readable_noinc_reg, +}; + +static int adxl372_i2c_probe(struct i2c_client *client) +{ + const struct adxl372_chip_info *chip_info; + struct regmap *regmap; + unsigned int regval; + int ret; + + chip_info = i2c_get_match_data(client); + + regmap = devm_regmap_init_i2c(client, &adxl372_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + ret = regmap_read(regmap, ADXL372_REVID, ®val); + if (ret < 0) + return ret; + + /* Starting with the 3rd revision an I2C chip bug was fixed */ + if (regval < 3) + dev_warn(&client->dev, + "I2C might not work properly with other devices on the bus"); + + return adxl372_probe(&client->dev, regmap, client->irq, chip_info); +} + +static const struct i2c_device_id adxl372_i2c_id[] = { + { .name = "adxl371", .driver_data = (kernel_ulong_t)&adxl371_chip_info }, + { .name = "adxl372", .driver_data = (kernel_ulong_t)&adxl372_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adxl372_i2c_id); + +static const struct of_device_id adxl372_of_match[] = { + { .compatible = "adi,adxl371", .data = &adxl371_chip_info }, + { .compatible = "adi,adxl372", .data = &adxl372_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, adxl372_of_match); + +static struct i2c_driver adxl372_i2c_driver = { + .driver = { + .name = "adxl372_i2c", + .of_match_table = adxl372_of_match, + }, + .probe = adxl372_i2c_probe, + .id_table = adxl372_i2c_id, +}; + +module_i2c_driver(adxl372_i2c_driver); + +MODULE_AUTHOR("Stefan Popa "); +MODULE_AUTHOR("Antoniu Miclaus "); +MODULE_DESCRIPTION("Analog Devices ADXL371/ADXL372 3-axis accelerometer I2C driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_ADXL372"); diff --git a/drivers/iio/accel/adxl372_spi.c b/drivers/iio/accel/adxl372_spi.c new file mode 100644 index 000000000..128e148c4 --- /dev/null +++ b/drivers/iio/accel/adxl372_spi.c @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ADXL371/ADXL372 3-Axis Digital Accelerometer SPI driver + * + * Copyright 2018 Analog Devices Inc. + */ + +#include +#include +#include + +#include "adxl372.h" + +static const struct regmap_config adxl372_spi_regmap_config = { + .reg_bits = 7, + .pad_bits = 1, + .val_bits = 8, + .read_flag_mask = BIT(0), + .readable_noinc_reg = adxl372_readable_noinc_reg, +}; + +static int adxl372_spi_probe(struct spi_device *spi) +{ + const struct adxl372_chip_info *chip_info; + struct regmap *regmap; + + chip_info = spi_get_device_match_data(spi); + + regmap = devm_regmap_init_spi(spi, &adxl372_spi_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return adxl372_probe(&spi->dev, regmap, spi->irq, chip_info); +} + +static const struct spi_device_id adxl372_spi_id[] = { + { .name = "adxl371", .driver_data = (kernel_ulong_t)&adxl371_chip_info }, + { .name = "adxl372", .driver_data = (kernel_ulong_t)&adxl372_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, adxl372_spi_id); + +static const struct of_device_id adxl372_of_match[] = { + { .compatible = "adi,adxl371", .data = &adxl371_chip_info }, + { .compatible = "adi,adxl372", .data = &adxl372_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, adxl372_of_match); + +static struct spi_driver adxl372_spi_driver = { + .driver = { + .name = "adxl372_spi", + .of_match_table = adxl372_of_match, + }, + .probe = adxl372_spi_probe, + .id_table = adxl372_spi_id, +}; + +module_spi_driver(adxl372_spi_driver); + +MODULE_AUTHOR("Stefan Popa "); +MODULE_AUTHOR("Antoniu Miclaus "); +MODULE_DESCRIPTION("Analog Devices ADXL371/ADXL372 3-axis accelerometer SPI driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_ADXL372"); diff --git a/drivers/iio/accel/adxl380.c b/drivers/iio/accel/adxl380.c new file mode 100644 index 000000000..7dca55230 --- /dev/null +++ b/drivers/iio/accel/adxl380.c @@ -0,0 +1,2007 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ADXL380 3-Axis Digital Accelerometer core driver + * + * Copyright 2024 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#include "adxl380.h" + +#define ADXL380_ID_VAL 380 +#define ADXL318_ID_VAL 380 +#define ADXL382_ID_VAL 382 +#define ADXL319_ID_VAL 382 + +#define ADXL380_DEVID_AD_REG 0x00 +#define ADXL380_PART_ID_REG 0x02 + +#define ADXL380_X_DATA_H_REG 0x15 +#define ADXL380_Y_DATA_H_REG 0x17 +#define ADXL380_Z_DATA_H_REG 0x19 +#define ADXL380_T_DATA_H_REG 0x1B + +#define ADXL380_MISC_0_REG 0x20 +#define ADXL380_XL382_MSK BIT(7) + +#define ADXL380_MISC_1_REG 0x21 + +#define ADXL380_X_DSM_OFFSET_REG 0x4D + +#define ADXL380_ACT_INACT_CTL_REG 0x37 +#define ADXL380_INACT_EN_MSK BIT(2) +#define ADXL380_ACT_EN_MSK BIT(0) + +#define ADXL380_SNSR_AXIS_EN_REG 0x38 +#define ADXL380_ACT_INACT_AXIS_EN_MSK GENMASK(2, 0) + +#define ADXL380_THRESH_ACT_H_REG 0x39 +#define ADXL380_TIME_ACT_H_REG 0x3B +#define ADXL380_THRESH_INACT_H_REG 0x3E +#define ADXL380_TIME_INACT_H_REG 0x40 +#define ADXL380_THRESH_MAX GENMASK(12, 0) +#define ADXL380_TIME_MAX GENMASK(24, 0) + +#define ADXL380_FIFO_CONFIG_0_REG 0x30 +#define ADXL380_FIFO_SAMPLES_8_MSK BIT(0) +#define ADXL380_FIFO_MODE_MSK GENMASK(5, 4) + +#define ADXL380_FIFO_DISABLED 0 +#define ADXL380_FIFO_NORMAL 1 +#define ADXL380_FIFO_STREAMED 2 +#define ADXL380_FIFO_TRIGGERED 3 + +#define ADXL380_FIFO_CONFIG_1_REG 0x31 +#define ADXL380_FIFO_STATUS_0_REG 0x1E + +#define ADXL380_TAP_THRESH_REG 0x43 +#define ADXL380_TAP_DUR_REG 0x44 +#define ADXL380_TAP_LATENT_REG 0x45 +#define ADXL380_TAP_WINDOW_REG 0x46 +#define ADXL380_TAP_TIME_MAX GENMASK(7, 0) + +#define ADXL380_TAP_CFG_REG 0x47 +#define ADXL380_TAP_AXIS_MSK GENMASK(1, 0) + +#define ADXL380_TRIG_CFG_REG 0x49 +#define ADXL380_TRIG_CFG_DEC_2X_MSK BIT(7) +#define ADXL380_TRIG_CFG_SINC_RATE_MSK BIT(6) + +#define ADXL380_FILTER_REG 0x50 +#define ADXL380_FILTER_EQ_FILT_MSK BIT(6) +#define ADXL380_FILTER_LPF_MODE_MSK GENMASK(5, 4) +#define ADXL380_FILTER_HPF_PATH_MSK BIT(3) +#define ADXL380_FILTER_HPF_CORNER_MSK GENMASK(2, 0) + +#define ADXL380_OP_MODE_REG 0x26 +#define ADXL380_OP_MODE_RANGE_MSK GENMASK(7, 6) +#define ADXL380_OP_MODE_MSK GENMASK(3, 0) +#define ADXL380_OP_MODE_STANDBY 0 +#define ADXL380_OP_MODE_HEART_SOUND 1 +#define ADXL380_OP_MODE_ULP 2 +#define ADXL380_OP_MODE_VLP 3 +#define ADXL380_OP_MODE_LP 4 +#define ADXL380_OP_MODE_LP_ULP 6 +#define ADXL380_OP_MODE_LP_VLP 7 +#define ADXL380_OP_MODE_RBW 8 +#define ADXL380_OP_MODE_RBW_ULP 10 +#define ADXL380_OP_MODE_RBW_VLP 11 +#define ADXL380_OP_MODE_HP 12 +#define ADXL380_OP_MODE_HP_ULP 14 +#define ADXL380_OP_MODE_HP_VLP 15 + +#define ADXL380_OP_MODE_4G_RANGE 0 +#define ADXL382_OP_MODE_15G_RANGE 0 +#define ADXL380_OP_MODE_8G_RANGE 1 +#define ADXL382_OP_MODE_30G_RANGE 1 +#define ADXL380_OP_MODE_16G_RANGE 2 +#define ADXL382_OP_MODE_60G_RANGE 2 + +#define ADXL380_DIG_EN_REG 0x27 +#define ADXL380_CHAN_EN_MSK(chan) BIT(4 + (chan)) +#define ADXL380_FIFO_EN_MSK BIT(3) + +#define ADXL380_INT0_MAP0_REG 0x2B +#define ADXL380_INT1_MAP0_REG 0x2D +#define ADXL380_INT_MAP0_INACT_INT0_MSK BIT(6) +#define ADXL380_INT_MAP0_ACT_INT0_MSK BIT(5) +#define ADXL380_INT_MAP0_FIFO_WM_INT0_MSK BIT(3) + +#define ADXL380_INT0_MAP1_REG 0x2C +#define ADXL380_INT1_MAP1_REG 0x2E +#define ADXL380_INT_MAP1_DOUBLE_TAP_INT0_MSK BIT(1) +#define ADXL380_INT_MAP1_SINGLE_TAP_INT0_MSK BIT(0) + +#define ADXL380_INT0_REG 0x5D +#define ADXL380_INT0_POL_MSK BIT(7) + +#define ADXL380_RESET_REG 0x2A +#define ADXL380_FIFO_DATA 0x1D + +#define ADXL380_DEVID_AD_VAL 0xAD +#define ADXL380_RESET_CODE 0x52 + +#define ADXL380_STATUS_0_REG 0x11 +#define ADXL380_STATUS_0_FIFO_FULL_MSK BIT(1) +#define ADXL380_STATUS_0_FIFO_WM_MSK BIT(3) + +#define ADXL380_STATUS_1_INACT_MSK BIT(6) +#define ADXL380_STATUS_1_ACT_MSK BIT(5) +#define ADXL380_STATUS_1_DOUBLE_TAP_MSK BIT(1) +#define ADXL380_STATUS_1_SINGLE_TAP_MSK BIT(0) + +#define ADXL380_FIFO_SAMPLES 315UL + +enum adxl380_channels { + ADXL380_ACCEL_X, + ADXL380_ACCEL_Y, + ADXL380_ACCEL_Z, + ADXL380_TEMP, + ADXL380_CH_NUM +}; + +enum adxl380_axis { + ADXL380_X_AXIS, + ADXL380_Y_AXIS, + ADXL380_Z_AXIS, +}; + +enum adxl380_activity_type { + ADXL380_ACTIVITY, + ADXL380_INACTIVITY, +}; + +enum adxl380_tap_type { + ADXL380_SINGLE_TAP, + ADXL380_DOUBLE_TAP, +}; + +enum adxl380_tap_time_type { + ADXL380_TAP_TIME_LATENT, + ADXL380_TAP_TIME_WINDOW, +}; + +static const int adxl380_range_scale_factor_tbl[] = { 1, 2, 4 }; + +static const unsigned int adxl380_th_reg_high_addr[2] = { + [ADXL380_ACTIVITY] = ADXL380_THRESH_ACT_H_REG, + [ADXL380_INACTIVITY] = ADXL380_THRESH_INACT_H_REG, +}; + +static const unsigned int adxl380_time_reg_high_addr[2] = { + [ADXL380_ACTIVITY] = ADXL380_TIME_ACT_H_REG, + [ADXL380_INACTIVITY] = ADXL380_TIME_INACT_H_REG, +}; + +static const unsigned int adxl380_tap_time_reg[2] = { + [ADXL380_TAP_TIME_LATENT] = ADXL380_TAP_LATENT_REG, + [ADXL380_TAP_TIME_WINDOW] = ADXL380_TAP_WINDOW_REG, +}; + +struct adxl380_state { + struct regmap *regmap; + struct device *dev; + const struct adxl380_chip_info *chip_info; + /* + * Synchronize access to members of driver state, and ensure atomicity + * of consecutive regmap operations. + */ + struct mutex lock; + enum adxl380_axis tap_axis_en; + u8 range; + u8 odr; + u8 fifo_set_size; + u8 transf_buf[3]; + u16 watermark; + u32 act_time_ms; + u32 act_threshold; + u32 inact_time_ms; + u32 inact_threshold; + u32 tap_latent_us; + u32 tap_window_us; + u32 tap_duration_us; + u32 tap_threshold; + int irq; + int int_map[2]; + int lpf_tbl[4]; + int hpf_tbl[7][2]; + + __be16 fifo_buf[ADXL380_FIFO_SAMPLES] __aligned(IIO_DMA_MINALIGN); +}; + +bool adxl380_readable_noinc_reg(struct device *dev, unsigned int reg) +{ + return reg == ADXL380_FIFO_DATA; +} +EXPORT_SYMBOL_NS_GPL(adxl380_readable_noinc_reg, "IIO_ADXL380"); + +static int adxl380_act_inact_enabled(struct adxl380_state *st, bool *enabled) +{ + unsigned int act_inact_ctl; + int ret; + + if (!st->chip_info->has_low_power) { + *enabled = false; + return 0; + } + + ret = regmap_read(st->regmap, ADXL380_ACT_INACT_CTL_REG, &act_inact_ctl); + if (ret) + return ret; + + *enabled = FIELD_GET(ADXL380_ACT_EN_MSK, act_inact_ctl) || + FIELD_GET(ADXL380_INACT_EN_MSK, act_inact_ctl); + + return 0; +} + +static int adxl380_set_measure_en(struct adxl380_state *st, bool en) +{ + int ret; + u8 op_mode = ADXL380_OP_MODE_STANDBY; + + if (en) { + bool act_inact_enabled; + + ret = adxl380_act_inact_enabled(st, &act_inact_enabled); + if (ret) + return ret; + + /* + * Activity/Inactivity detection available only in VLP/ULP + * mode and for devices that support low power modes. + */ + if (act_inact_enabled) + st->odr = ADXL380_ODR_VLP; + + if (st->odr == ADXL380_ODR_VLP) + op_mode = ADXL380_OP_MODE_VLP; + else + op_mode = ADXL380_OP_MODE_HP; + } + + return regmap_update_bits(st->regmap, ADXL380_OP_MODE_REG, + ADXL380_OP_MODE_MSK, + FIELD_PREP(ADXL380_OP_MODE_MSK, op_mode)); +} + +static void adxl380_scale_act_inact_thresholds(struct adxl380_state *st, + u8 old_range, + u8 new_range) +{ + st->act_threshold = mult_frac(st->act_threshold, + adxl380_range_scale_factor_tbl[old_range], + adxl380_range_scale_factor_tbl[new_range]); + st->inact_threshold = mult_frac(st->inact_threshold, + adxl380_range_scale_factor_tbl[old_range], + adxl380_range_scale_factor_tbl[new_range]); +} + +static int adxl380_write_act_inact_threshold(struct adxl380_state *st, + enum adxl380_activity_type act, + unsigned int th) +{ + int ret; + u8 reg = adxl380_th_reg_high_addr[act]; + + if (th > ADXL380_THRESH_MAX) + return -EINVAL; + + ret = regmap_write(st->regmap, reg + 1, th & GENMASK(7, 0)); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, reg, GENMASK(2, 0), th >> 8); + if (ret) + return ret; + + if (act == ADXL380_ACTIVITY) + st->act_threshold = th; + else + st->inact_threshold = th; + + return 0; +} + +static int adxl380_set_act_inact_threshold(struct iio_dev *indio_dev, + enum adxl380_activity_type act, + u16 th) +{ + struct adxl380_state *st = iio_priv(indio_dev); + int ret; + + guard(mutex)(&st->lock); + + ret = adxl380_set_measure_en(st, false); + if (ret) + return ret; + + ret = adxl380_write_act_inact_threshold(st, act, th); + if (ret) + return ret; + + return adxl380_set_measure_en(st, true); +} + +static int adxl380_set_tap_threshold_value(struct iio_dev *indio_dev, u8 th) +{ + int ret; + struct adxl380_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->lock); + + ret = adxl380_set_measure_en(st, false); + if (ret) + return ret; + + ret = regmap_write(st->regmap, ADXL380_TAP_THRESH_REG, th); + if (ret) + return ret; + + st->tap_threshold = th; + + return adxl380_set_measure_en(st, true); +} + +static int _adxl380_write_tap_time_us(struct adxl380_state *st, + enum adxl380_tap_time_type tap_time_type, + u32 us) +{ + u8 reg = adxl380_tap_time_reg[tap_time_type]; + unsigned int reg_val; + int ret; + + /* scale factor for tap window is 1250us / LSB */ + reg_val = DIV_ROUND_CLOSEST(us, 1250); + if (reg_val > ADXL380_TAP_TIME_MAX) + reg_val = ADXL380_TAP_TIME_MAX; + + ret = regmap_write(st->regmap, reg, reg_val); + if (ret) + return ret; + + if (tap_time_type == ADXL380_TAP_TIME_WINDOW) + st->tap_window_us = us; + else + st->tap_latent_us = us; + + return 0; +} + +static int adxl380_write_tap_time_us(struct adxl380_state *st, + enum adxl380_tap_time_type tap_time_type, u32 us) +{ + int ret; + + guard(mutex)(&st->lock); + + ret = adxl380_set_measure_en(st, false); + if (ret) + return ret; + + ret = _adxl380_write_tap_time_us(st, tap_time_type, us); + if (ret) + return ret; + + return adxl380_set_measure_en(st, true); +} + +static int adxl380_write_tap_dur_us(struct iio_dev *indio_dev, u32 us) +{ + int ret; + unsigned int reg_val; + struct adxl380_state *st = iio_priv(indio_dev); + + /* 625us per code is the scale factor of TAP_DUR register */ + reg_val = DIV_ROUND_CLOSEST(us, 625); + + ret = adxl380_set_measure_en(st, false); + if (ret) + return ret; + + ret = regmap_write(st->regmap, ADXL380_TAP_DUR_REG, reg_val); + if (ret) + return ret; + + return adxl380_set_measure_en(st, true); +} + +static int adxl380_read_chn(struct adxl380_state *st, u8 addr) +{ + int ret; + + guard(mutex)(&st->lock); + + ret = regmap_bulk_read(st->regmap, addr, &st->transf_buf, 2); + if (ret) + return ret; + + return get_unaligned_be16(st->transf_buf); +} + +static int adxl380_get_odr(struct adxl380_state *st, int *odr) +{ + *odr = st->chip_info->samp_freq_tbl[st->odr]; + + return 0; +} + +static const int adxl380_lpf_div[] = { + 1, 4, 8, 16, +}; + +static int adxl380_fill_lpf_tbl(struct adxl380_state *st) +{ + int ret, i; + int odr; + + ret = adxl380_get_odr(st, &odr); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(st->lpf_tbl); i++) + st->lpf_tbl[i] = DIV_ROUND_CLOSEST(odr, adxl380_lpf_div[i]); + + return 0; +} + +static const int adxl380_hpf_mul[] = { + 0, 247000, 62084, 15545, 3862, 954, 238, +}; + +static int adxl380_fill_hpf_tbl(struct adxl380_state *st) +{ + int i, ret, odr_hz; + u32 multiplier; + u64 div, rem, odr; + + ret = adxl380_get_odr(st, &odr_hz); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(adxl380_hpf_mul); i++) { + odr = mul_u64_u32_shr(odr_hz, MEGA, 0); + multiplier = adxl380_hpf_mul[i]; + div = div64_u64_rem(mul_u64_u32_shr(odr, multiplier, 0), + TERA * 100, &rem); + + st->hpf_tbl[i][0] = div; + st->hpf_tbl[i][1] = div_u64(rem, MEGA * 100); + } + + return 0; +} + +static int adxl380_set_odr(struct adxl380_state *st, u8 odr) +{ + int ret; + + guard(mutex)(&st->lock); + + ret = adxl380_set_measure_en(st, false); + if (ret) + return ret; + + if (odr >= ADXL380_ODR_DSM) { + u8 mul = odr - ADXL380_ODR_DSM; + u8 field; + + field = FIELD_PREP(ADXL380_TRIG_CFG_DEC_2X_MSK, mul & 1); + ret = regmap_update_bits(st->regmap, ADXL380_TRIG_CFG_REG, + ADXL380_TRIG_CFG_DEC_2X_MSK, field); + if (ret) + return ret; + + field = FIELD_PREP(ADXL380_TRIG_CFG_SINC_RATE_MSK, mul >> 1); + ret = regmap_update_bits(st->regmap, ADXL380_TRIG_CFG_REG, + ADXL380_TRIG_CFG_SINC_RATE_MSK, field); + if (ret) + return ret; + } + + st->odr = odr; + ret = adxl380_set_measure_en(st, true); + if (ret) + return ret; + + ret = adxl380_fill_lpf_tbl(st); + if (ret) + return ret; + + return adxl380_fill_hpf_tbl(st); +} + +static int adxl380_find_match_1d_tbl(const int *array, unsigned int size, + int val) +{ + int i; + + for (i = 0; i < size; i++) { + if (val == array[i]) + return i; + } + + return size - 1; +} + +static int adxl380_find_match_2d_tbl(const int (*freq_tbl)[2], int n, int val, int val2) +{ + int i; + + for (i = 0; i < n; i++) { + if (freq_tbl[i][0] == val && freq_tbl[i][1] == val2) + return i; + } + + return -EINVAL; +} + +static int adxl380_get_lpf(struct adxl380_state *st, int *lpf) +{ + int ret; + unsigned int trig_cfg, lpf_idx; + + guard(mutex)(&st->lock); + + ret = regmap_read(st->regmap, ADXL380_FILTER_REG, &trig_cfg); + if (ret) + return ret; + + lpf_idx = FIELD_GET(ADXL380_FILTER_LPF_MODE_MSK, trig_cfg); + + *lpf = st->lpf_tbl[lpf_idx]; + + return 0; +} + +static int adxl380_set_lpf(struct adxl380_state *st, u8 lpf) +{ + int ret; + u8 eq_bypass = 0; + + guard(mutex)(&st->lock); + + ret = adxl380_set_measure_en(st, false); + if (ret) + return ret; + + if (lpf) + eq_bypass = 1; + + ret = regmap_update_bits(st->regmap, ADXL380_FILTER_REG, + ADXL380_FILTER_EQ_FILT_MSK, + FIELD_PREP(ADXL380_FILTER_EQ_FILT_MSK, eq_bypass)); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, ADXL380_FILTER_REG, + ADXL380_FILTER_LPF_MODE_MSK, + FIELD_PREP(ADXL380_FILTER_LPF_MODE_MSK, lpf)); + if (ret) + return ret; + + return adxl380_set_measure_en(st, true); +} + +static int adxl380_get_hpf(struct adxl380_state *st, int *hpf_int, int *hpf_frac) +{ + int ret; + unsigned int trig_cfg, hpf_idx; + + guard(mutex)(&st->lock); + + ret = regmap_read(st->regmap, ADXL380_FILTER_REG, &trig_cfg); + if (ret) + return ret; + + hpf_idx = FIELD_GET(ADXL380_FILTER_HPF_CORNER_MSK, trig_cfg); + + *hpf_int = st->hpf_tbl[hpf_idx][0]; + *hpf_frac = st->hpf_tbl[hpf_idx][1]; + + return 0; +} + +static int adxl380_set_hpf(struct adxl380_state *st, u8 hpf) +{ + int ret; + u8 hpf_path = 0; + + guard(mutex)(&st->lock); + + ret = adxl380_set_measure_en(st, false); + if (ret) + return ret; + + if (hpf) + hpf_path = 1; + + ret = regmap_update_bits(st->regmap, ADXL380_FILTER_REG, + ADXL380_FILTER_HPF_PATH_MSK, + FIELD_PREP(ADXL380_FILTER_HPF_PATH_MSK, hpf_path)); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, ADXL380_FILTER_REG, + ADXL380_FILTER_HPF_CORNER_MSK, + FIELD_PREP(ADXL380_FILTER_HPF_CORNER_MSK, hpf)); + if (ret) + return ret; + + return adxl380_set_measure_en(st, true); +} + +static int _adxl380_set_act_inact_time_ms(struct adxl380_state *st, + enum adxl380_activity_type act, + u32 ms) +{ + u8 reg = adxl380_time_reg_high_addr[act]; + unsigned int reg_val; + int ret; + + /* 500us per code is the scale factor of TIME_ACT / TIME_INACT registers */ + reg_val = min(DIV_ROUND_CLOSEST(ms * 1000, 500), ADXL380_TIME_MAX); + + put_unaligned_be24(reg_val, &st->transf_buf[0]); + + ret = regmap_bulk_write(st->regmap, reg, st->transf_buf, sizeof(st->transf_buf)); + if (ret) + return ret; + + if (act == ADXL380_ACTIVITY) + st->act_time_ms = ms; + else + st->inact_time_ms = ms; + + return 0; +} + +static int adxl380_set_act_inact_time_ms(struct adxl380_state *st, + enum adxl380_activity_type act, + u32 ms) +{ + int ret; + + guard(mutex)(&st->lock); + + ret = adxl380_set_measure_en(st, false); + if (ret) + return ret; + + ret = _adxl380_set_act_inact_time_ms(st, act, ms); + if (ret) + return ret; + + return adxl380_set_measure_en(st, true); +} + +static int adxl380_set_range(struct adxl380_state *st, u8 range) +{ + int ret; + + guard(mutex)(&st->lock); + + ret = adxl380_set_measure_en(st, false); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, ADXL380_OP_MODE_REG, + ADXL380_OP_MODE_RANGE_MSK, + FIELD_PREP(ADXL380_OP_MODE_RANGE_MSK, range)); + + if (ret) + return ret; + + adxl380_scale_act_inact_thresholds(st, st->range, range); + + /* Activity thresholds depend on range */ + ret = adxl380_write_act_inact_threshold(st, ADXL380_ACTIVITY, + st->act_threshold); + if (ret) + return ret; + + ret = adxl380_write_act_inact_threshold(st, ADXL380_INACTIVITY, + st->inact_threshold); + if (ret) + return ret; + + st->range = range; + + return adxl380_set_measure_en(st, true); +} + +static int adxl380_write_act_inact_en(struct adxl380_state *st, + enum adxl380_activity_type type, + bool en) +{ + if (type == ADXL380_ACTIVITY) + return regmap_update_bits(st->regmap, ADXL380_ACT_INACT_CTL_REG, + ADXL380_ACT_EN_MSK, + FIELD_PREP(ADXL380_ACT_EN_MSK, en)); + + return regmap_update_bits(st->regmap, ADXL380_ACT_INACT_CTL_REG, + ADXL380_INACT_EN_MSK, + FIELD_PREP(ADXL380_INACT_EN_MSK, en)); +} + +static int adxl380_read_act_inact_int(struct adxl380_state *st, + enum adxl380_activity_type type, + bool *en) +{ + int ret; + unsigned int reg_val; + + guard(mutex)(&st->lock); + + ret = regmap_read(st->regmap, st->int_map[0], ®_val); + if (ret) + return ret; + + if (type == ADXL380_ACTIVITY) + *en = FIELD_GET(ADXL380_INT_MAP0_ACT_INT0_MSK, reg_val); + else + *en = FIELD_GET(ADXL380_INT_MAP0_INACT_INT0_MSK, reg_val); + + return 0; +} + +static int adxl380_write_act_inact_int(struct adxl380_state *st, + enum adxl380_activity_type act, + bool en) +{ + if (act == ADXL380_ACTIVITY) + return regmap_update_bits(st->regmap, st->int_map[0], + ADXL380_INT_MAP0_ACT_INT0_MSK, + FIELD_PREP(ADXL380_INT_MAP0_ACT_INT0_MSK, en)); + + return regmap_update_bits(st->regmap, st->int_map[0], + ADXL380_INT_MAP0_INACT_INT0_MSK, + FIELD_PREP(ADXL380_INT_MAP0_INACT_INT0_MSK, en)); +} + +static int adxl380_act_inact_config(struct adxl380_state *st, + enum adxl380_activity_type type, + bool en) +{ + int ret; + + guard(mutex)(&st->lock); + + ret = adxl380_set_measure_en(st, false); + if (ret) + return ret; + + ret = adxl380_write_act_inact_en(st, type, en); + if (ret) + return ret; + + ret = adxl380_write_act_inact_int(st, type, en); + if (ret) + return ret; + + return adxl380_set_measure_en(st, true); +} + +static int adxl380_write_tap_axis(struct adxl380_state *st, + enum adxl380_axis axis) +{ + int ret; + + ret = regmap_update_bits(st->regmap, ADXL380_TAP_CFG_REG, + ADXL380_TAP_AXIS_MSK, + FIELD_PREP(ADXL380_TAP_AXIS_MSK, axis)); + + if (ret) + return ret; + + st->tap_axis_en = axis; + + return 0; +} + +static int adxl380_read_tap_int(struct adxl380_state *st, enum adxl380_tap_type type, bool *en) +{ + int ret; + unsigned int reg_val; + + ret = regmap_read(st->regmap, st->int_map[1], ®_val); + if (ret) + return ret; + + if (type == ADXL380_SINGLE_TAP) + *en = FIELD_GET(ADXL380_INT_MAP1_SINGLE_TAP_INT0_MSK, reg_val); + else + *en = FIELD_GET(ADXL380_INT_MAP1_DOUBLE_TAP_INT0_MSK, reg_val); + + return 0; +} + +static int adxl380_write_tap_int(struct adxl380_state *st, enum adxl380_tap_type type, bool en) +{ + if (type == ADXL380_SINGLE_TAP) + return regmap_update_bits(st->regmap, st->int_map[1], + ADXL380_INT_MAP1_SINGLE_TAP_INT0_MSK, + FIELD_PREP(ADXL380_INT_MAP1_SINGLE_TAP_INT0_MSK, en)); + + return regmap_update_bits(st->regmap, st->int_map[1], + ADXL380_INT_MAP1_DOUBLE_TAP_INT0_MSK, + FIELD_PREP(ADXL380_INT_MAP1_DOUBLE_TAP_INT0_MSK, en)); +} + +static int adxl380_tap_config(struct adxl380_state *st, + enum adxl380_axis axis, + enum adxl380_tap_type type, + bool en) +{ + int ret; + + guard(mutex)(&st->lock); + + ret = adxl380_set_measure_en(st, false); + if (ret) + return ret; + + ret = adxl380_write_tap_axis(st, axis); + if (ret) + return ret; + + ret = adxl380_write_tap_int(st, type, en); + if (ret) + return ret; + + return adxl380_set_measure_en(st, true); +} + +static int adxl380_set_fifo_samples(struct adxl380_state *st) +{ + int ret; + u16 fifo_samples = st->watermark * st->fifo_set_size; + + ret = regmap_update_bits(st->regmap, ADXL380_FIFO_CONFIG_0_REG, + ADXL380_FIFO_SAMPLES_8_MSK, + FIELD_PREP(ADXL380_FIFO_SAMPLES_8_MSK, + !!(fifo_samples & BIT(8)))); + if (ret) + return ret; + + return regmap_write(st->regmap, ADXL380_FIFO_CONFIG_1_REG, + fifo_samples & 0xFF); +} + +static int adxl380_get_status(struct adxl380_state *st, u8 *status0, u8 *status1) +{ + int ret; + + /* STATUS0, STATUS1 are adjacent regs */ + ret = regmap_bulk_read(st->regmap, ADXL380_STATUS_0_REG, + &st->transf_buf, 2); + if (ret) + return ret; + + *status0 = st->transf_buf[0]; + *status1 = st->transf_buf[1]; + + return 0; +} + +static int adxl380_get_fifo_entries(struct adxl380_state *st, u16 *fifo_entries) +{ + int ret; + + ret = regmap_bulk_read(st->regmap, ADXL380_FIFO_STATUS_0_REG, + &st->transf_buf, 2); + if (ret) + return ret; + + *fifo_entries = st->transf_buf[0] | ((BIT(0) & st->transf_buf[1]) << 8); + + return 0; +} + +static void adxl380_push_event(struct iio_dev *indio_dev, s64 timestamp, + u8 status1) +{ + if (FIELD_GET(ADXL380_STATUS_1_ACT_MSK, status1)) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + timestamp); + + if (FIELD_GET(ADXL380_STATUS_1_INACT_MSK, status1)) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING), + timestamp); + if (FIELD_GET(ADXL380_STATUS_1_SINGLE_TAP_MSK, status1)) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_GESTURE, IIO_EV_DIR_SINGLETAP), + timestamp); + + if (FIELD_GET(ADXL380_STATUS_1_DOUBLE_TAP_MSK, status1)) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_GESTURE, IIO_EV_DIR_DOUBLETAP), + timestamp); +} + +static irqreturn_t adxl380_irq_handler(int irq, void *p) +{ + struct iio_dev *indio_dev = p; + struct adxl380_state *st = iio_priv(indio_dev); + u8 status0, status1; + u16 fifo_entries; + int i; + int ret; + + guard(mutex)(&st->lock); + + ret = adxl380_get_status(st, &status0, &status1); + if (ret) + return IRQ_HANDLED; + + adxl380_push_event(indio_dev, iio_get_time_ns(indio_dev), status1); + + if (!FIELD_GET(ADXL380_STATUS_0_FIFO_WM_MSK, status0)) + return IRQ_HANDLED; + + ret = adxl380_get_fifo_entries(st, &fifo_entries); + if (ret) + return IRQ_HANDLED; + + fifo_entries = rounddown(fifo_entries, st->fifo_set_size); + ret = regmap_noinc_read(st->regmap, ADXL380_FIFO_DATA, &st->fifo_buf, + sizeof(*st->fifo_buf) * fifo_entries); + if (ret) + return IRQ_HANDLED; + for (i = 0; i < fifo_entries; i += st->fifo_set_size) + iio_push_to_buffers(indio_dev, &st->fifo_buf[i]); + + return IRQ_HANDLED; +} + +static int adxl380_write_calibbias_value(struct adxl380_state *st, + unsigned long chan_addr, + s8 calibbias) +{ + int ret; + + guard(mutex)(&st->lock); + + ret = adxl380_set_measure_en(st, false); + if (ret) + return ret; + + ret = regmap_write(st->regmap, ADXL380_X_DSM_OFFSET_REG + chan_addr, calibbias); + if (ret) + return ret; + + return adxl380_set_measure_en(st, true); +} + +static int adxl380_read_calibbias_value(struct adxl380_state *st, + unsigned long chan_addr, + int *calibbias) +{ + int ret; + unsigned int reg_val; + + guard(mutex)(&st->lock); + + ret = regmap_read(st->regmap, ADXL380_X_DSM_OFFSET_REG + chan_addr, ®_val); + if (ret) + return ret; + + *calibbias = sign_extend32(reg_val, 7); + + return 0; +} + +static ssize_t hwfifo_watermark_min_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "1\n"); +} + +static ssize_t hwfifo_watermark_max_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%lu\n", ADXL380_FIFO_SAMPLES); +} + +static ssize_t adxl380_get_fifo_watermark(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct adxl380_state *st = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", st->watermark); +} + +static ssize_t adxl380_get_fifo_enabled(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct adxl380_state *st = iio_priv(indio_dev); + int ret; + unsigned int reg_val; + + ret = regmap_read(st->regmap, ADXL380_DIG_EN_REG, ®_val); + if (ret) + return ret; + + return sysfs_emit(buf, "%lu\n", + FIELD_GET(ADXL380_FIFO_EN_MSK, reg_val)); +} + +static IIO_DEVICE_ATTR_RO(hwfifo_watermark_min, 0); +static IIO_DEVICE_ATTR_RO(hwfifo_watermark_max, 0); +static IIO_DEVICE_ATTR(hwfifo_watermark, 0444, + adxl380_get_fifo_watermark, NULL, 0); +static IIO_DEVICE_ATTR(hwfifo_enabled, 0444, + adxl380_get_fifo_enabled, NULL, 0); + +static const struct iio_dev_attr *adxl380_fifo_attributes[] = { + &iio_dev_attr_hwfifo_watermark_min, + &iio_dev_attr_hwfifo_watermark_max, + &iio_dev_attr_hwfifo_watermark, + &iio_dev_attr_hwfifo_enabled, + NULL +}; + +static int adxl380_buffer_postenable(struct iio_dev *indio_dev) +{ + struct adxl380_state *st = iio_priv(indio_dev); + int i; + int ret; + + guard(mutex)(&st->lock); + + ret = adxl380_set_measure_en(st, false); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, + st->int_map[0], + ADXL380_INT_MAP0_FIFO_WM_INT0_MSK, + FIELD_PREP(ADXL380_INT_MAP0_FIFO_WM_INT0_MSK, 1)); + if (ret) + return ret; + + for_each_clear_bit(i, indio_dev->active_scan_mask, ADXL380_CH_NUM) { + ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG, + ADXL380_CHAN_EN_MSK(i), + 0 << (4 + i)); + if (ret) + return ret; + } + + st->fifo_set_size = bitmap_weight(indio_dev->active_scan_mask, + iio_get_masklength(indio_dev)); + + if ((st->watermark * st->fifo_set_size) > ADXL380_FIFO_SAMPLES) + st->watermark = (ADXL380_FIFO_SAMPLES / st->fifo_set_size); + + ret = adxl380_set_fifo_samples(st); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG, ADXL380_FIFO_EN_MSK, + FIELD_PREP(ADXL380_FIFO_EN_MSK, 1)); + if (ret) + return ret; + + return adxl380_set_measure_en(st, true); +} + +static int adxl380_buffer_predisable(struct iio_dev *indio_dev) +{ + struct adxl380_state *st = iio_priv(indio_dev); + int ret, i; + + guard(mutex)(&st->lock); + + ret = adxl380_set_measure_en(st, false); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, + st->int_map[0], + ADXL380_INT_MAP0_FIFO_WM_INT0_MSK, + FIELD_PREP(ADXL380_INT_MAP0_FIFO_WM_INT0_MSK, 0)); + if (ret) + return ret; + + for (i = 0; i < indio_dev->num_channels; i++) { + ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG, + ADXL380_CHAN_EN_MSK(i), + 1 << (4 + i)); + if (ret) + return ret; + } + + ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG, ADXL380_FIFO_EN_MSK, + FIELD_PREP(ADXL380_FIFO_EN_MSK, 0)); + if (ret) + return ret; + + return adxl380_set_measure_en(st, true); +} + +static const struct iio_buffer_setup_ops adxl380_buffer_ops = { + .postenable = adxl380_buffer_postenable, + .predisable = adxl380_buffer_predisable, +}; + +static int adxl380_samp_freq_avail(struct adxl380_state *st, const int **vals, + int *length) +{ + bool act_inact_enabled; + int ret; + + if (!st->chip_info->has_low_power) { + *vals = st->chip_info->samp_freq_tbl + ADXL380_ODR_DSM; + *length = ADXL380_ODR_MAX - ADXL380_ODR_DSM; + return 0; + } + + ret = adxl380_act_inact_enabled(st, &act_inact_enabled); + if (ret) + return 0; + + /* + * Motion detection is only functional in low-power mode, and this + * affects the available sampling frequencies. + */ + *vals = st->chip_info->samp_freq_tbl; + *length = act_inact_enabled ? ADXL380_ODR_DSM : ADXL380_ODR_MAX; + + return 0; +} + +static int adxl380_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct adxl380_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = adxl380_read_chn(st, chan->address); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + + *val = sign_extend32(ret >> chan->scan_type.shift, + chan->scan_type.realbits - 1); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ACCEL: + scoped_guard(mutex, &st->lock) { + *val = st->chip_info->scale_tbl[st->range][0]; + *val2 = st->chip_info->scale_tbl[st->range][1]; + } + return IIO_VAL_INT_PLUS_NANO; + case IIO_TEMP: + /* 10.2 LSB / Degree Celsius */ + *val = 10000; + *val2 = 102; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + *val = st->chip_info->temp_offset; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_ACCEL: + ret = adxl380_read_calibbias_value(st, chan->scan_index, val); + if (ret) + return ret; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + ret = adxl380_get_odr(st, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + ret = adxl380_get_lpf(st, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: + ret = adxl380_get_hpf(st, val, val2); + if (ret) + return ret; + return IIO_VAL_INT_PLUS_MICRO; + } + + return -EINVAL; +} + +static int adxl380_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct adxl380_state *st = iio_priv(indio_dev); + int ret; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = (const int *)st->chip_info->scale_tbl; + *type = IIO_VAL_INT_PLUS_NANO; + *length = ARRAY_SIZE(st->chip_info->scale_tbl) * 2; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = adxl380_samp_freq_avail(st, vals, length); + if (ret) + return ret; + + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + *vals = (const int *)st->lpf_tbl; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(st->lpf_tbl); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: + *vals = (const int *)st->hpf_tbl; + *type = IIO_VAL_INT_PLUS_MICRO; + /* Values are stored in a 2D matrix */ + *length = ARRAY_SIZE(st->hpf_tbl) * 2; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int adxl380_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct adxl380_state *st = iio_priv(indio_dev); + const int *freq_vals; + int odr_index, lpf_index, hpf_index, range_index, freq_count, ret; + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = adxl380_samp_freq_avail(st, &freq_vals, &freq_count); + if (ret) + return ret; + + odr_index = adxl380_find_match_1d_tbl(freq_vals, freq_count, + val); + return adxl380_set_odr(st, odr_index); + case IIO_CHAN_INFO_CALIBBIAS: + return adxl380_write_calibbias_value(st, chan->scan_index, val); + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + lpf_index = adxl380_find_match_1d_tbl(st->lpf_tbl, + ARRAY_SIZE(st->lpf_tbl), + val); + return adxl380_set_lpf(st, lpf_index); + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: + hpf_index = adxl380_find_match_2d_tbl(st->hpf_tbl, + ARRAY_SIZE(st->hpf_tbl), + val, val2); + if (hpf_index < 0) + return hpf_index; + return adxl380_set_hpf(st, hpf_index); + case IIO_CHAN_INFO_SCALE: + range_index = adxl380_find_match_2d_tbl(st->chip_info->scale_tbl, + ARRAY_SIZE(st->chip_info->scale_tbl), + val, val2); + if (range_index < 0) + return range_index; + return adxl380_set_range(st, range_index); + default: + return -EINVAL; + } +} + +static int adxl380_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long info) +{ + switch (info) { + case IIO_CHAN_INFO_SCALE: + if (chan->type != IIO_ACCEL) + return -EINVAL; + + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT_PLUS_MICRO; + } +} + +static int adxl380_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct adxl380_state *st = iio_priv(indio_dev); + int ret; + bool int_en; + bool tap_axis_en = false; + + switch (chan->channel2) { + case IIO_MOD_X: + tap_axis_en = st->tap_axis_en == ADXL380_X_AXIS; + break; + case IIO_MOD_Y: + tap_axis_en = st->tap_axis_en == ADXL380_Y_AXIS; + break; + case IIO_MOD_Z: + tap_axis_en = st->tap_axis_en == ADXL380_Z_AXIS; + break; + default: + return -EINVAL; + } + + switch (dir) { + case IIO_EV_DIR_RISING: + ret = adxl380_read_act_inact_int(st, ADXL380_ACTIVITY, &int_en); + if (ret) + return ret; + return int_en; + case IIO_EV_DIR_FALLING: + ret = adxl380_read_act_inact_int(st, ADXL380_INACTIVITY, &int_en); + if (ret) + return ret; + return int_en; + case IIO_EV_DIR_SINGLETAP: + ret = adxl380_read_tap_int(st, ADXL380_SINGLE_TAP, &int_en); + if (ret) + return ret; + return int_en && tap_axis_en; + case IIO_EV_DIR_DOUBLETAP: + ret = adxl380_read_tap_int(st, ADXL380_DOUBLE_TAP, &int_en); + if (ret) + return ret; + return int_en && tap_axis_en; + default: + return -EINVAL; + } +} + +static int adxl380_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct adxl380_state *st = iio_priv(indio_dev); + enum adxl380_axis axis; + + switch (chan->channel2) { + case IIO_MOD_X: + axis = ADXL380_X_AXIS; + break; + case IIO_MOD_Y: + axis = ADXL380_Y_AXIS; + break; + case IIO_MOD_Z: + axis = ADXL380_Z_AXIS; + break; + default: + return -EINVAL; + } + + switch (dir) { + case IIO_EV_DIR_RISING: + return adxl380_act_inact_config(st, ADXL380_ACTIVITY, state); + case IIO_EV_DIR_FALLING: + return adxl380_act_inact_config(st, ADXL380_INACTIVITY, state); + case IIO_EV_DIR_SINGLETAP: + return adxl380_tap_config(st, axis, ADXL380_SINGLE_TAP, state); + case IIO_EV_DIR_DOUBLETAP: + return adxl380_tap_config(st, axis, ADXL380_DOUBLE_TAP, state); + default: + return -EINVAL; + } +} + +static int adxl380_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct adxl380_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->lock); + + switch (type) { + case IIO_EV_TYPE_THRESH: + switch (info) { + case IIO_EV_INFO_VALUE: { + switch (dir) { + case IIO_EV_DIR_RISING: + *val = st->act_threshold; + return IIO_VAL_INT; + case IIO_EV_DIR_FALLING: + *val = st->inact_threshold; + return IIO_VAL_INT; + default: + return -EINVAL; + } + } + case IIO_EV_INFO_PERIOD: + switch (dir) { + case IIO_EV_DIR_RISING: + *val = st->act_time_ms; + *val2 = 1000; + return IIO_VAL_FRACTIONAL; + case IIO_EV_DIR_FALLING: + *val = st->inact_time_ms; + *val2 = 1000; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + default: + return -EINVAL; + } + case IIO_EV_TYPE_GESTURE: + switch (info) { + case IIO_EV_INFO_VALUE: + *val = st->tap_threshold; + return IIO_VAL_INT; + case IIO_EV_INFO_RESET_TIMEOUT: + *val = st->tap_window_us; + *val2 = 1000000; + return IIO_VAL_FRACTIONAL; + case IIO_EV_INFO_TAP2_MIN_DELAY: + *val = st->tap_latent_us; + *val2 = 1000000; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int adxl380_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, enum iio_event_direction dir, + enum iio_event_info info, int val, int val2) +{ + struct adxl380_state *st = iio_priv(indio_dev); + u32 val_ms, val_us; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (type) { + case IIO_EV_TYPE_THRESH: + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + return adxl380_set_act_inact_threshold(indio_dev, + ADXL380_ACTIVITY, val); + case IIO_EV_DIR_FALLING: + return adxl380_set_act_inact_threshold(indio_dev, + ADXL380_INACTIVITY, val); + default: + return -EINVAL; + } + case IIO_EV_INFO_PERIOD: + val_ms = val * 1000 + DIV_ROUND_UP(val2, 1000); + switch (dir) { + case IIO_EV_DIR_RISING: + return adxl380_set_act_inact_time_ms(st, + ADXL380_ACTIVITY, val_ms); + case IIO_EV_DIR_FALLING: + return adxl380_set_act_inact_time_ms(st, + ADXL380_INACTIVITY, val_ms); + default: + return -EINVAL; + } + + default: + return -EINVAL; + } + case IIO_EV_TYPE_GESTURE: + switch (info) { + case IIO_EV_INFO_VALUE: + return adxl380_set_tap_threshold_value(indio_dev, val); + case IIO_EV_INFO_RESET_TIMEOUT: + val_us = val * 1000000 + val2; + return adxl380_write_tap_time_us(st, + ADXL380_TAP_TIME_WINDOW, + val_us); + case IIO_EV_INFO_TAP2_MIN_DELAY: + val_us = val * 1000000 + val2; + return adxl380_write_tap_time_us(st, + ADXL380_TAP_TIME_LATENT, + val_us); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static ssize_t in_accel_gesture_tap_maxtomin_time_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int vals[2]; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct adxl380_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->lock); + + vals[0] = st->tap_duration_us; + vals[1] = MICRO; + + return iio_format_value(buf, IIO_VAL_FRACTIONAL, 2, vals); +} + +static ssize_t in_accel_gesture_tap_maxtomin_time_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct adxl380_state *st = iio_priv(indio_dev); + int ret, val_int, val_fract_us; + + guard(mutex)(&st->lock); + + ret = iio_str_to_fixpoint(buf, 100000, &val_int, &val_fract_us); + if (ret) + return ret; + + /* maximum value is 255 * 625 us = 0.159375 seconds */ + if (val_int || val_fract_us > 159375 || val_fract_us < 0) + return -EINVAL; + + ret = adxl380_write_tap_dur_us(indio_dev, val_fract_us); + if (ret) + return ret; + + return len; +} + +static IIO_DEVICE_ATTR_RW(in_accel_gesture_tap_maxtomin_time, 0); + +static struct attribute *adxl380_event_attributes[] = { + &iio_dev_attr_in_accel_gesture_tap_maxtomin_time.dev_attr.attr, + NULL +}; + +static const struct attribute_group adxl380_event_attribute_group = { + .attrs = adxl380_event_attributes, +}; + +static int adxl380_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct adxl380_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); +} + +static int adxl380_set_watermark(struct iio_dev *indio_dev, unsigned int val) +{ + struct adxl380_state *st = iio_priv(indio_dev); + + st->watermark = min(val, ADXL380_FIFO_SAMPLES); + + return 0; +} + +static const struct iio_info adxl318_info = { + .read_raw = adxl380_read_raw, + .read_avail = &adxl380_read_avail, + .write_raw = adxl380_write_raw, + .write_raw_get_fmt = adxl380_write_raw_get_fmt, + .debugfs_reg_access = &adxl380_reg_access, + .hwfifo_set_watermark = adxl380_set_watermark, +}; + +static const struct iio_info adxl380_info = { + .read_raw = adxl380_read_raw, + .read_avail = &adxl380_read_avail, + .write_raw = adxl380_write_raw, + .write_raw_get_fmt = adxl380_write_raw_get_fmt, + .read_event_config = adxl380_read_event_config, + .write_event_config = adxl380_write_event_config, + .read_event_value = adxl380_read_event_value, + .write_event_value = adxl380_write_event_value, + .event_attrs = &adxl380_event_attribute_group, + .debugfs_reg_access = &adxl380_reg_access, + .hwfifo_set_watermark = adxl380_set_watermark, +}; + +const struct adxl380_chip_info adxl318_chip_info = { + .name = "adxl318", + .chip_id = ADXL318_ID_VAL, + .scale_tbl = { + [ADXL380_OP_MODE_4G_RANGE] = { 0, 1307226 }, + [ADXL380_OP_MODE_8G_RANGE] = { 0, 2615434 }, + [ADXL380_OP_MODE_16G_RANGE] = { 0, 5229886 }, + }, + .samp_freq_tbl = { 0, 8000, 16000, 32000 }, + /* + * The datasheet defines an intercept of 550 LSB at 25 degC + * and a sensitivity of 10.2 LSB/C. + */ + .temp_offset = 25 * 102 / 10 - 550, + .info = &adxl318_info, +}; +EXPORT_SYMBOL_NS_GPL(adxl318_chip_info, "IIO_ADXL380"); + +const struct adxl380_chip_info adxl319_chip_info = { + .name = "adxl319", + .chip_id = ADXL319_ID_VAL, + .scale_tbl = { + [ADXL382_OP_MODE_15G_RANGE] = { 0, 4903325 }, + [ADXL382_OP_MODE_30G_RANGE] = { 0, 9806650 }, + [ADXL382_OP_MODE_60G_RANGE] = { 0, 19613300 }, + }, + .samp_freq_tbl = { 0, 16000, 32000, 64000 }, + /* + * The datasheet defines an intercept of 550 LSB at 25 degC + * and a sensitivity of 10.2 LSB/C. + */ + .temp_offset = 25 * 102 / 10 - 550, + .info = &adxl318_info, +}; +EXPORT_SYMBOL_NS_GPL(adxl319_chip_info, "IIO_ADXL380"); + +const struct adxl380_chip_info adxl380_chip_info = { + .name = "adxl380", + .chip_id = ADXL380_ID_VAL, + .scale_tbl = { + [ADXL380_OP_MODE_4G_RANGE] = { 0, 1307226 }, + [ADXL380_OP_MODE_8G_RANGE] = { 0, 2615434 }, + [ADXL380_OP_MODE_16G_RANGE] = { 0, 5229886 }, + }, + .samp_freq_tbl = { 1000, 8000, 16000, 32000 }, + /* + * The datasheet defines an intercept of 470 LSB at 25 degC + * and a sensitivity of 10.2 LSB/C. + */ + .temp_offset = 25 * 102 / 10 - 470, + .has_low_power = true, + .info = &adxl380_info, + +}; +EXPORT_SYMBOL_NS_GPL(adxl380_chip_info, "IIO_ADXL380"); + +const struct adxl380_chip_info adxl382_chip_info = { + .name = "adxl382", + .chip_id = ADXL382_ID_VAL, + .scale_tbl = { + [ADXL382_OP_MODE_15G_RANGE] = { 0, 4903325 }, + [ADXL382_OP_MODE_30G_RANGE] = { 0, 9806650 }, + [ADXL382_OP_MODE_60G_RANGE] = { 0, 19613300 }, + }, + .samp_freq_tbl = { 1000, 16000, 32000, 64000 }, + /* + * The datasheet defines an intercept of 570 LSB at 25 degC + * and a sensitivity of 10.2 LSB/C. + */ + .temp_offset = 25 * 102 / 10 - 570, + .has_low_power = true, + .info = &adxl380_info, +}; +EXPORT_SYMBOL_NS_GPL(adxl382_chip_info, "IIO_ADXL380"); + +static const struct iio_event_spec adxl380_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD), + }, + { + .type = IIO_EV_TYPE_GESTURE, + .dir = IIO_EV_DIR_SINGLETAP, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_RESET_TIMEOUT), + }, + { + .type = IIO_EV_TYPE_GESTURE, + .dir = IIO_EV_DIR_DOUBLETAP, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_RESET_TIMEOUT) | + BIT(IIO_EV_INFO_TAP2_MIN_DELAY), + }, +}; + +#define ADXL380_ACCEL_CHANNEL(index, reg, axis) { \ + .type = IIO_ACCEL, \ + .address = reg, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \ + BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \ + BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY), \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + .event_spec = adxl380_events, \ + .num_event_specs = ARRAY_SIZE(adxl380_events) \ +} + +static const struct iio_chan_spec adxl380_channels[] = { + ADXL380_ACCEL_CHANNEL(0, ADXL380_X_DATA_H_REG, X), + ADXL380_ACCEL_CHANNEL(1, ADXL380_Y_DATA_H_REG, Y), + ADXL380_ACCEL_CHANNEL(2, ADXL380_Z_DATA_H_REG, Z), + { + .type = IIO_TEMP, + .address = ADXL380_T_DATA_H_REG, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = 3, + .scan_type = { + .sign = 's', + .realbits = 12, + .storagebits = 16, + .shift = 4, + .endianness = IIO_BE, + }, + }, +}; + +static int adxl380_config_irq(struct iio_dev *indio_dev) +{ + struct adxl380_state *st = iio_priv(indio_dev); + unsigned long irq_flag; + u32 irq_type; + u8 polarity; + int ret; + + st->irq = fwnode_irq_get_byname(dev_fwnode(st->dev), "INT0"); + if (st->irq > 0) { + st->int_map[0] = ADXL380_INT0_MAP0_REG; + st->int_map[1] = ADXL380_INT0_MAP1_REG; + } else { + st->irq = fwnode_irq_get_byname(dev_fwnode(st->dev), "INT1"); + if (st->irq < 0) + return dev_err_probe(st->dev, st->irq, + "no interrupt name specified\n"); + st->int_map[0] = ADXL380_INT1_MAP0_REG; + st->int_map[1] = ADXL380_INT1_MAP1_REG; + } + + irq_type = irq_get_trigger_type(st->irq); + if (irq_type == IRQ_TYPE_LEVEL_HIGH) { + polarity = 0; + irq_flag = IRQF_TRIGGER_HIGH | IRQF_ONESHOT; + } else if (irq_type == IRQ_TYPE_LEVEL_LOW) { + polarity = 1; + irq_flag = IRQF_TRIGGER_LOW | IRQF_ONESHOT; + } else { + return dev_err_probe(st->dev, -EINVAL, + "Invalid interrupt 0x%x. Only level interrupts supported\n", + irq_type); + } + + ret = regmap_update_bits(st->regmap, ADXL380_INT0_REG, + ADXL380_INT0_POL_MSK, + FIELD_PREP(ADXL380_INT0_POL_MSK, polarity)); + if (ret) + return ret; + + return devm_request_threaded_irq(st->dev, st->irq, NULL, + adxl380_irq_handler, irq_flag, + indio_dev->name, indio_dev); +} + +static int adxl380_setup(struct iio_dev *indio_dev) +{ + unsigned int reg_val; + u16 part_id, chip_id; + int ret, i; + struct adxl380_state *st = iio_priv(indio_dev); + + ret = regmap_read(st->regmap, ADXL380_DEVID_AD_REG, ®_val); + if (ret) + return ret; + + if (reg_val != ADXL380_DEVID_AD_VAL) + dev_warn(st->dev, "Unknown chip id %x\n", reg_val); + + ret = regmap_bulk_read(st->regmap, ADXL380_PART_ID_REG, + &st->transf_buf, 2); + if (ret) + return ret; + + part_id = get_unaligned_be16(st->transf_buf); + part_id >>= 4; + + if (part_id != ADXL380_ID_VAL) + dev_warn(st->dev, "Unknown part id %x\n", part_id); + + ret = regmap_read(st->regmap, ADXL380_MISC_0_REG, ®_val); + if (ret) + return ret; + + /* Bit to differentiate between ADXL380/382. */ + if (reg_val & ADXL380_XL382_MSK) + chip_id = ADXL382_ID_VAL; + else + chip_id = ADXL380_ID_VAL; + + if (chip_id != st->chip_info->chip_id) + dev_warn(st->dev, "Unknown chip id %x\n", chip_id); + + ret = regmap_write(st->regmap, ADXL380_RESET_REG, ADXL380_RESET_CODE); + if (ret) + return ret; + + /* + * A latency of approximately 0.5 ms is required after soft reset. + * Stated in the register REG_RESET description. + */ + fsleep(500); + + for (i = 0; i < indio_dev->num_channels; i++) { + ret = regmap_update_bits(st->regmap, ADXL380_DIG_EN_REG, + ADXL380_CHAN_EN_MSK(i), + 1 << (4 + i)); + if (ret) + return ret; + } + + ret = regmap_update_bits(st->regmap, ADXL380_FIFO_CONFIG_0_REG, + ADXL380_FIFO_MODE_MSK, + FIELD_PREP(ADXL380_FIFO_MODE_MSK, ADXL380_FIFO_STREAMED)); + if (ret) + return ret; + + /* Select all 3 axis for act/inact detection. */ + ret = regmap_update_bits(st->regmap, ADXL380_SNSR_AXIS_EN_REG, + ADXL380_ACT_INACT_AXIS_EN_MSK, + FIELD_PREP(ADXL380_ACT_INACT_AXIS_EN_MSK, + ADXL380_ACT_INACT_AXIS_EN_MSK)); + if (ret) + return ret; + + ret = adxl380_config_irq(indio_dev); + if (ret) + return ret; + + ret = adxl380_fill_lpf_tbl(st); + if (ret) + return ret; + + ret = adxl380_fill_hpf_tbl(st); + if (ret) + return ret; + + return adxl380_set_measure_en(st, true); +} + +int adxl380_probe(struct device *dev, struct regmap *regmap, + const struct adxl380_chip_info *chip_info) +{ + struct iio_dev *indio_dev; + struct adxl380_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->dev = dev; + st->regmap = regmap; + st->chip_info = chip_info; + st->odr = ADXL380_ODR_DSM; + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + indio_dev->channels = adxl380_channels; + indio_dev->num_channels = ARRAY_SIZE(adxl380_channels); + indio_dev->name = chip_info->name; + indio_dev->info = chip_info->info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_regulator_get_enable(dev, "vddio"); + if (ret) + return dev_err_probe(st->dev, ret, + "Failed to get vddio regulator\n"); + + ret = devm_regulator_get_enable(st->dev, "vsupply"); + if (ret) + return dev_err_probe(st->dev, ret, + "Failed to get vsupply regulator\n"); + + ret = adxl380_setup(indio_dev); + if (ret) + return ret; + + ret = devm_iio_kfifo_buffer_setup_ext(st->dev, indio_dev, + &adxl380_buffer_ops, + adxl380_fifo_attributes); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS_GPL(adxl380_probe, "IIO_ADXL380"); + +MODULE_AUTHOR("Ramona Gradinariu "); +MODULE_AUTHOR("Antoniu Miclaus "); +MODULE_DESCRIPTION("Analog Devices ADXL380 3-axis accelerometer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/accel/adxl380.h b/drivers/iio/accel/adxl380.h new file mode 100644 index 000000000..d2c260c8b --- /dev/null +++ b/drivers/iio/accel/adxl380.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * ADXL380 3-Axis Digital Accelerometer + * + * Copyright 2024 Analog Devices Inc. + */ + +#ifndef _ADXL380_H_ +#define _ADXL380_H_ + +enum adxl380_odr { + ADXL380_ODR_VLP, + ADXL380_ODR_DSM, + ADXL380_ODR_DSM_2X, + ADXL380_ODR_DSM_4X, + ADXL380_ODR_MAX +}; + +struct adxl380_chip_info { + const char *name; + const int scale_tbl[3][2]; + const int samp_freq_tbl[ADXL380_ODR_MAX]; + const struct iio_info *info; + const int temp_offset; + const u16 chip_id; + const bool has_low_power; +}; + +extern const struct adxl380_chip_info adxl318_chip_info; +extern const struct adxl380_chip_info adxl319_chip_info; +extern const struct adxl380_chip_info adxl380_chip_info; +extern const struct adxl380_chip_info adxl382_chip_info; + +int adxl380_probe(struct device *dev, struct regmap *regmap, + const struct adxl380_chip_info *chip_info); +bool adxl380_readable_noinc_reg(struct device *dev, unsigned int reg); + +#endif /* _ADXL380_H_ */ diff --git a/drivers/iio/accel/adxl380_i2c.c b/drivers/iio/accel/adxl380_i2c.c new file mode 100644 index 000000000..2673ddbbd --- /dev/null +++ b/drivers/iio/accel/adxl380_i2c.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ADXL380 3-Axis Digital Accelerometer I2C driver + * + * Copyright 2024 Analog Devices Inc. + */ + +#include +#include +#include + +#include "adxl380.h" + +static const struct regmap_config adxl380_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .readable_noinc_reg = adxl380_readable_noinc_reg, +}; + +static int adxl380_i2c_probe(struct i2c_client *client) +{ + struct regmap *regmap; + const struct adxl380_chip_info *chip_data; + + chip_data = i2c_get_match_data(client); + + regmap = devm_regmap_init_i2c(client, &adxl380_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return adxl380_probe(&client->dev, regmap, chip_data); +} + +static const struct i2c_device_id adxl380_i2c_id[] = { + { .name = "adxl318", .driver_data = (kernel_ulong_t)&adxl318_chip_info }, + { .name = "adxl319", .driver_data = (kernel_ulong_t)&adxl319_chip_info }, + { .name = "adxl380", .driver_data = (kernel_ulong_t)&adxl380_chip_info }, + { .name = "adxl382", .driver_data = (kernel_ulong_t)&adxl382_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adxl380_i2c_id); + +static const struct of_device_id adxl380_of_match[] = { + { .compatible = "adi,adxl318", .data = &adxl318_chip_info }, + { .compatible = "adi,adxl319", .data = &adxl319_chip_info }, + { .compatible = "adi,adxl380", .data = &adxl380_chip_info }, + { .compatible = "adi,adxl382", .data = &adxl382_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, adxl380_of_match); + +static struct i2c_driver adxl380_i2c_driver = { + .driver = { + .name = "adxl380_i2c", + .of_match_table = adxl380_of_match, + }, + .probe = adxl380_i2c_probe, + .id_table = adxl380_i2c_id, +}; + +module_i2c_driver(adxl380_i2c_driver); + +MODULE_AUTHOR("Ramona Gradinariu "); +MODULE_AUTHOR("Antoniu Miclaus "); +MODULE_DESCRIPTION("Analog Devices ADXL380 3-axis accelerometer I2C driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_ADXL380"); diff --git a/drivers/iio/accel/adxl380_spi.c b/drivers/iio/accel/adxl380_spi.c new file mode 100644 index 000000000..ae8467f4e --- /dev/null +++ b/drivers/iio/accel/adxl380_spi.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ADXL380 3-Axis Digital Accelerometer SPI driver + * + * Copyright 2024 Analog Devices Inc. + */ + +#include +#include +#include + +#include "adxl380.h" + +static const struct regmap_config adxl380_spi_regmap_config = { + .reg_bits = 7, + .pad_bits = 1, + .val_bits = 8, + .read_flag_mask = BIT(0), + .readable_noinc_reg = adxl380_readable_noinc_reg, +}; + +static int adxl380_spi_probe(struct spi_device *spi) +{ + const struct adxl380_chip_info *chip_data; + struct regmap *regmap; + + chip_data = spi_get_device_match_data(spi); + + regmap = devm_regmap_init_spi(spi, &adxl380_spi_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return adxl380_probe(&spi->dev, regmap, chip_data); +} + +static const struct spi_device_id adxl380_spi_id[] = { + { .name = "adxl318", .driver_data = (kernel_ulong_t)&adxl318_chip_info }, + { .name = "adxl319", .driver_data = (kernel_ulong_t)&adxl319_chip_info }, + { .name = "adxl380", .driver_data = (kernel_ulong_t)&adxl380_chip_info }, + { .name = "adxl382", .driver_data = (kernel_ulong_t)&adxl382_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, adxl380_spi_id); + +static const struct of_device_id adxl380_of_match[] = { + { .compatible = "adi,adxl318", .data = &adxl318_chip_info }, + { .compatible = "adi,adxl319", .data = &adxl319_chip_info }, + { .compatible = "adi,adxl380", .data = &adxl380_chip_info }, + { .compatible = "adi,adxl382", .data = &adxl382_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, adxl380_of_match); + +static struct spi_driver adxl380_spi_driver = { + .driver = { + .name = "adxl380_spi", + .of_match_table = adxl380_of_match, + }, + .probe = adxl380_spi_probe, + .id_table = adxl380_spi_id, +}; + +module_spi_driver(adxl380_spi_driver); + +MODULE_AUTHOR("Ramona Gradinariu "); +MODULE_AUTHOR("Antoniu Miclaus "); +MODULE_DESCRIPTION("Analog Devices ADXL380 3-axis accelerometer SPI driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_ADXL380"); diff --git a/drivers/iio/accel/bma180.c b/drivers/iio/accel/bma180.c new file mode 100644 index 000000000..e7dbbd476 --- /dev/null +++ b/drivers/iio/accel/bma180.c @@ -0,0 +1,1134 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * bma180.c - IIO driver for Bosch BMA180 triaxial acceleration sensor + * + * Copyright 2013 Oleksandr Kravchenko + * + * Support for BMA250 (c) Peter Meerwald + * + * SPI is not supported by driver + * BMA023/BMA150/SMB380: 7-bit I2C slave address 0x38 + * BMA180: 7-bit I2C slave address 0x40 or 0x41 + * BMA250: 7-bit I2C slave address 0x18 or 0x19 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum chip_ids { + BMA023, + BMA150, + BMA180, + BMA250, +}; + +struct bma180_data; + +struct bma180_part_info { + u8 chip_id; + const struct iio_chan_spec *channels; + unsigned int num_channels; + const int *scale_table; + unsigned int num_scales; + const int *bw_table; + unsigned int num_bw; + int temp_offset; + + u8 int_reset_reg, int_reset_mask; + u8 sleep_reg, sleep_mask; + u8 bw_reg, bw_mask, bw_offset; + u8 scale_reg, scale_mask; + u8 power_reg, power_mask, lowpower_val; + u8 int_enable_reg, int_enable_mask; + u8 softreset_reg, softreset_val; + + int (*chip_config)(struct bma180_data *data); + void (*chip_disable)(struct bma180_data *data); +}; + +/* Register set */ +#define BMA023_CTRL_REG0 0x0a +#define BMA023_CTRL_REG1 0x0b +#define BMA023_CTRL_REG2 0x14 +#define BMA023_CTRL_REG3 0x15 + +#define BMA023_RANGE_MASK GENMASK(4, 3) /* Range of accel values */ +#define BMA023_BW_MASK GENMASK(2, 0) /* Accel bandwidth */ +#define BMA023_SLEEP BIT(0) +#define BMA023_INT_RESET_MASK BIT(6) +#define BMA023_NEW_DATA_INT BIT(5) /* Intr every new accel data is ready */ +#define BMA023_RESET_VAL BIT(1) + +#define BMA180_CHIP_ID 0x00 /* Need to distinguish BMA180 from other */ +#define BMA180_ACC_X_LSB 0x02 /* First of 6 registers of accel data */ +#define BMA180_TEMP 0x08 +#define BMA180_CTRL_REG0 0x0d +#define BMA180_RESET 0x10 +#define BMA180_BW_TCS 0x20 +#define BMA180_CTRL_REG3 0x21 +#define BMA180_TCO_Z 0x30 +#define BMA180_OFFSET_LSB1 0x35 + +/* BMA180_CTRL_REG0 bits */ +#define BMA180_DIS_WAKE_UP BIT(0) /* Disable wake up mode */ +#define BMA180_SLEEP BIT(1) /* 1 - chip will sleep */ +#define BMA180_EE_W BIT(4) /* Unlock writing to addr from 0x20 */ +#define BMA180_RESET_INT BIT(6) /* Reset pending interrupts */ + +/* BMA180_CTRL_REG3 bits */ +#define BMA180_NEW_DATA_INT BIT(1) /* Intr every new accel data is ready */ + +/* BMA180_OFFSET_LSB1 skipping mode bit */ +#define BMA180_SMP_SKIP BIT(0) + +/* Bit masks for registers bit fields */ +#define BMA180_RANGE 0x0e /* Range of measured accel values */ +#define BMA180_BW 0xf0 /* Accel bandwidth */ +#define BMA180_MODE_CONFIG 0x03 /* Config operation modes */ + +/* We have to write this value in reset register to do soft reset */ +#define BMA180_RESET_VAL 0xb6 + +#define BMA023_ID_REG_VAL 0x02 +#define BMA180_ID_REG_VAL 0x03 +#define BMA250_ID_REG_VAL 0x03 + +/* Chip power modes */ +#define BMA180_LOW_POWER 0x03 + +#define BMA250_RANGE_REG 0x0f +#define BMA250_BW_REG 0x10 +#define BMA250_POWER_REG 0x11 +#define BMA250_RESET_REG 0x14 +#define BMA250_INT_ENABLE_REG 0x17 +#define BMA250_INT_MAP_REG 0x1a +#define BMA250_INT_RESET_REG 0x21 + +#define BMA250_RANGE_MASK GENMASK(3, 0) /* Range of accel values */ +#define BMA250_BW_MASK GENMASK(4, 0) /* Accel bandwidth */ +#define BMA250_BW_OFFSET 8 +#define BMA250_SUSPEND_MASK BIT(7) /* chip will sleep */ +#define BMA250_LOWPOWER_MASK BIT(6) +#define BMA250_DATA_INTEN_MASK BIT(4) +#define BMA250_INT1_DATA_MASK BIT(0) +#define BMA250_INT_RESET_MASK BIT(7) /* Reset pending interrupts */ + +struct bma180_data { + struct regulator *vdd_supply; + struct regulator *vddio_supply; + struct i2c_client *client; + struct iio_trigger *trig; + const struct bma180_part_info *part_info; + struct iio_mount_matrix orientation; + struct mutex mutex; + bool sleep_state; + int scale; + int bw; + bool pmode; +}; + +enum bma180_chan { + AXIS_X, + AXIS_Y, + AXIS_Z, + TEMP +}; + +static int bma023_bw_table[] = { 25, 50, 100, 190, 375, 750, 1500 }; /* Hz */ +static int bma023_scale_table[] = { 2452, 4903, 9709, }; + +static int bma180_bw_table[] = { 10, 20, 40, 75, 150, 300 }; /* Hz */ +static int bma180_scale_table[] = { 1275, 1863, 2452, 3727, 4903, 9709, 19417 }; + +static int bma250_bw_table[] = { 8, 16, 31, 63, 125, 250, 500, 1000 }; /* Hz */ +static int bma250_scale_table[] = { 0, 0, 0, 38344, 0, 76590, 0, 0, 153180, 0, + 0, 0, 306458 }; + +static int bma180_get_data_reg(struct bma180_data *data, enum bma180_chan chan) +{ + int ret; + + if (data->sleep_state) + return -EBUSY; + + switch (chan) { + case TEMP: + ret = i2c_smbus_read_byte_data(data->client, BMA180_TEMP); + if (ret < 0) + dev_err(&data->client->dev, "failed to read temp register\n"); + break; + default: + ret = i2c_smbus_read_word_data(data->client, + BMA180_ACC_X_LSB + chan * 2); + if (ret < 0) + dev_err(&data->client->dev, + "failed to read accel_%c register\n", + 'x' + chan); + } + + return ret; +} + +static int bma180_set_bits(struct bma180_data *data, u8 reg, u8 mask, u8 val) +{ + int ret = i2c_smbus_read_byte_data(data->client, reg); + u8 reg_val = (ret & ~mask) | (val << (ffs(mask) - 1)); + + if (ret < 0) + return ret; + + return i2c_smbus_write_byte_data(data->client, reg, reg_val); +} + +static int bma180_reset_intr(struct bma180_data *data) +{ + int ret = bma180_set_bits(data, data->part_info->int_reset_reg, + data->part_info->int_reset_mask, 1); + + if (ret) + dev_err(&data->client->dev, "failed to reset interrupt\n"); + + return ret; +} + +static int bma180_set_new_data_intr_state(struct bma180_data *data, bool state) +{ + int ret = bma180_set_bits(data, data->part_info->int_enable_reg, + data->part_info->int_enable_mask, state); + if (ret) + goto err; + ret = bma180_reset_intr(data); + if (ret) + goto err; + + return 0; + +err: + dev_err(&data->client->dev, + "failed to set new data interrupt state %d\n", state); + return ret; +} + +static int bma180_set_sleep_state(struct bma180_data *data, bool state) +{ + int ret = bma180_set_bits(data, data->part_info->sleep_reg, + data->part_info->sleep_mask, state); + + if (ret) { + dev_err(&data->client->dev, + "failed to set sleep state %d\n", state); + return ret; + } + data->sleep_state = state; + + return 0; +} + +static int bma180_set_ee_writing_state(struct bma180_data *data, bool state) +{ + int ret = bma180_set_bits(data, BMA180_CTRL_REG0, BMA180_EE_W, state); + + if (ret) + dev_err(&data->client->dev, + "failed to set ee writing state %d\n", state); + + return ret; +} + +static int bma180_set_bw(struct bma180_data *data, int val) +{ + int ret, i; + + if (data->sleep_state) + return -EBUSY; + + for (i = 0; i < data->part_info->num_bw; ++i) { + if (data->part_info->bw_table[i] == val) { + ret = bma180_set_bits(data, data->part_info->bw_reg, + data->part_info->bw_mask, + i + data->part_info->bw_offset); + if (ret) { + dev_err(&data->client->dev, + "failed to set bandwidth\n"); + return ret; + } + data->bw = val; + return 0; + } + } + + return -EINVAL; +} + +static int bma180_set_scale(struct bma180_data *data, int val) +{ + int ret, i; + + if (data->sleep_state) + return -EBUSY; + + for (i = 0; i < data->part_info->num_scales; ++i) + if (data->part_info->scale_table[i] == val) { + ret = bma180_set_bits(data, data->part_info->scale_reg, + data->part_info->scale_mask, i); + if (ret) { + dev_err(&data->client->dev, + "failed to set scale\n"); + return ret; + } + data->scale = val; + return 0; + } + + return -EINVAL; +} + +static int bma180_set_pmode(struct bma180_data *data, bool mode) +{ + u8 reg_val = mode ? data->part_info->lowpower_val : 0; + int ret = bma180_set_bits(data, data->part_info->power_reg, + data->part_info->power_mask, reg_val); + + if (ret) { + dev_err(&data->client->dev, "failed to set power mode\n"); + return ret; + } + data->pmode = mode; + + return 0; +} + +static int bma180_soft_reset(struct bma180_data *data) +{ + int ret = i2c_smbus_write_byte_data(data->client, + data->part_info->softreset_reg, + data->part_info->softreset_val); + + if (ret) + dev_err(&data->client->dev, "failed to reset the chip\n"); + + return ret; +} + +static int bma180_chip_init(struct bma180_data *data) +{ + /* Try to read chip_id register. It must return 0x03. */ + int ret = i2c_smbus_read_byte_data(data->client, BMA180_CHIP_ID); + + if (ret < 0) + return ret; + if (ret != data->part_info->chip_id) { + dev_err(&data->client->dev, "wrong chip ID %d expected %d\n", + ret, data->part_info->chip_id); + return -ENODEV; + } + + ret = bma180_soft_reset(data); + if (ret) + return ret; + /* + * No serial transaction should occur within minimum 10 us + * after soft_reset command + */ + msleep(20); + + return bma180_set_new_data_intr_state(data, false); +} + +static int bma023_chip_config(struct bma180_data *data) +{ + int ret = bma180_chip_init(data); + + if (ret) + goto err; + + ret = bma180_set_bw(data, 50); /* 50 Hz */ + if (ret) + goto err; + ret = bma180_set_scale(data, 2452); /* 2 G */ + if (ret) + goto err; + + return 0; + +err: + dev_err(&data->client->dev, "failed to config the chip\n"); + return ret; +} + +static int bma180_chip_config(struct bma180_data *data) +{ + int ret = bma180_chip_init(data); + + if (ret) + goto err; + ret = bma180_set_pmode(data, false); + if (ret) + goto err; + ret = bma180_set_bits(data, BMA180_CTRL_REG0, BMA180_DIS_WAKE_UP, 1); + if (ret) + goto err; + ret = bma180_set_ee_writing_state(data, true); + if (ret) + goto err; + ret = bma180_set_bits(data, BMA180_OFFSET_LSB1, BMA180_SMP_SKIP, 1); + if (ret) + goto err; + ret = bma180_set_bw(data, 20); /* 20 Hz */ + if (ret) + goto err; + ret = bma180_set_scale(data, 2452); /* 2 G */ + if (ret) + goto err; + + return 0; + +err: + dev_err(&data->client->dev, "failed to config the chip\n"); + return ret; +} + +static int bma250_chip_config(struct bma180_data *data) +{ + int ret = bma180_chip_init(data); + + if (ret) + goto err; + ret = bma180_set_pmode(data, false); + if (ret) + goto err; + ret = bma180_set_bw(data, 16); /* 16 Hz */ + if (ret) + goto err; + ret = bma180_set_scale(data, 38344); /* 2 G */ + if (ret) + goto err; + /* + * This enables dataready interrupt on the INT1 pin + * FIXME: support using the INT2 pin + */ + ret = bma180_set_bits(data, BMA250_INT_MAP_REG, BMA250_INT1_DATA_MASK, 1); + if (ret) + goto err; + + return 0; + +err: + dev_err(&data->client->dev, "failed to config the chip\n"); + return ret; +} + +static void bma023_chip_disable(struct bma180_data *data) +{ + if (bma180_set_sleep_state(data, true)) + goto err; + + return; + +err: + dev_err(&data->client->dev, "failed to disable the chip\n"); +} + +static void bma180_chip_disable(struct bma180_data *data) +{ + if (bma180_set_new_data_intr_state(data, false)) + goto err; + if (bma180_set_ee_writing_state(data, false)) + goto err; + if (bma180_set_sleep_state(data, true)) + goto err; + + return; + +err: + dev_err(&data->client->dev, "failed to disable the chip\n"); +} + +static void bma250_chip_disable(struct bma180_data *data) +{ + if (bma180_set_new_data_intr_state(data, false)) + goto err; + if (bma180_set_sleep_state(data, true)) + goto err; + + return; + +err: + dev_err(&data->client->dev, "failed to disable the chip\n"); +} + +static ssize_t bma180_show_avail(char *buf, const int *vals, unsigned int n, + bool micros) +{ + size_t len = 0; + int i; + + for (i = 0; i < n; i++) { + if (!vals[i]) + continue; + len += scnprintf(buf + len, PAGE_SIZE - len, + micros ? "0.%06d " : "%d ", vals[i]); + } + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t bma180_show_filter_freq_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bma180_data *data = iio_priv(dev_to_iio_dev(dev)); + + return bma180_show_avail(buf, data->part_info->bw_table, + data->part_info->num_bw, false); +} + +static ssize_t bma180_show_scale_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bma180_data *data = iio_priv(dev_to_iio_dev(dev)); + + return bma180_show_avail(buf, data->part_info->scale_table, + data->part_info->num_scales, true); +} + +static IIO_DEVICE_ATTR(in_accel_filter_low_pass_3db_frequency_available, + S_IRUGO, bma180_show_filter_freq_avail, NULL, 0); + +static IIO_DEVICE_ATTR(in_accel_scale_available, + S_IRUGO, bma180_show_scale_avail, NULL, 0); + +static struct attribute *bma180_attributes[] = { + &iio_dev_attr_in_accel_filter_low_pass_3db_frequency_available. + dev_attr.attr, + &iio_dev_attr_in_accel_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group bma180_attrs_group = { + .attrs = bma180_attributes, +}; + +static int bma180_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, + long mask) +{ + struct bma180_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + mutex_lock(&data->mutex); + ret = bma180_get_data_reg(data, chan->scan_index); + mutex_unlock(&data->mutex); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + if (chan->scan_type.sign == 's') { + *val = sign_extend32(ret >> chan->scan_type.shift, + chan->scan_type.realbits - 1); + } else { + *val = ret; + } + return IIO_VAL_INT; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + *val = data->bw; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ACCEL: + *val = 0; + *val2 = data->scale; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = 500; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + *val = data->part_info->temp_offset; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int bma180_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + struct bma180_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (val) + return -EINVAL; + mutex_lock(&data->mutex); + ret = bma180_set_scale(data, val2); + mutex_unlock(&data->mutex); + return ret; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + if (val2) + return -EINVAL; + mutex_lock(&data->mutex); + ret = bma180_set_bw(data, val); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } +} + +static const struct iio_info bma180_info = { + .attrs = &bma180_attrs_group, + .read_raw = bma180_read_raw, + .write_raw = bma180_write_raw, +}; + +static const char * const bma180_power_modes[] = { "low_noise", "low_power" }; + +static int bma180_get_power_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct bma180_data *data = iio_priv(indio_dev); + + return data->pmode; +} + +static int bma180_set_power_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int mode) +{ + struct bma180_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = bma180_set_pmode(data, mode); + mutex_unlock(&data->mutex); + + return ret; +} + +static const struct iio_mount_matrix * +bma180_accel_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct bma180_data *data = iio_priv(indio_dev); + + return &data->orientation; +} + +static const struct iio_enum bma180_power_mode_enum = { + .items = bma180_power_modes, + .num_items = ARRAY_SIZE(bma180_power_modes), + .get = bma180_get_power_mode, + .set = bma180_set_power_mode, +}; + +static const struct iio_chan_spec_ext_info bma023_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, bma180_accel_get_mount_matrix), + { } +}; + +static const struct iio_chan_spec_ext_info bma180_ext_info[] = { + IIO_ENUM("power_mode", IIO_SHARED_BY_TYPE, &bma180_power_mode_enum), + IIO_ENUM_AVAILABLE("power_mode", IIO_SHARED_BY_TYPE, &bma180_power_mode_enum), + IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, bma180_accel_get_mount_matrix), + { } +}; + +#define BMA023_ACC_CHANNEL(_axis, _bits) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .scan_index = AXIS_##_axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = _bits, \ + .storagebits = 16, \ + .shift = 16 - _bits, \ + }, \ + .ext_info = bma023_ext_info, \ +} + +#define BMA150_TEMP_CHANNEL { \ + .type = IIO_TEMP, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET), \ + .scan_index = TEMP, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 8, \ + .storagebits = 16, \ + }, \ +} + +#define BMA180_ACC_CHANNEL(_axis, _bits) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .scan_index = AXIS_##_axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = _bits, \ + .storagebits = 16, \ + .shift = 16 - _bits, \ + }, \ + .ext_info = bma180_ext_info, \ +} + +#define BMA180_TEMP_CHANNEL { \ + .type = IIO_TEMP, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET), \ + .scan_index = TEMP, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 8, \ + .storagebits = 16, \ + }, \ +} + +static const struct iio_chan_spec bma023_channels[] = { + BMA023_ACC_CHANNEL(X, 10), + BMA023_ACC_CHANNEL(Y, 10), + BMA023_ACC_CHANNEL(Z, 10), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const struct iio_chan_spec bma150_channels[] = { + BMA023_ACC_CHANNEL(X, 10), + BMA023_ACC_CHANNEL(Y, 10), + BMA023_ACC_CHANNEL(Z, 10), + BMA150_TEMP_CHANNEL, + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const struct iio_chan_spec bma180_channels[] = { + BMA180_ACC_CHANNEL(X, 14), + BMA180_ACC_CHANNEL(Y, 14), + BMA180_ACC_CHANNEL(Z, 14), + BMA180_TEMP_CHANNEL, + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const struct iio_chan_spec bma250_channels[] = { + BMA180_ACC_CHANNEL(X, 10), + BMA180_ACC_CHANNEL(Y, 10), + BMA180_ACC_CHANNEL(Z, 10), + BMA180_TEMP_CHANNEL, + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const struct bma180_part_info bma180_part_info[] = { + [BMA023] = { + .chip_id = BMA023_ID_REG_VAL, + .channels = bma023_channels, + .num_channels = ARRAY_SIZE(bma023_channels), + .scale_table = bma023_scale_table, + .num_scales = ARRAY_SIZE(bma023_scale_table), + .bw_table = bma023_bw_table, + .num_bw = ARRAY_SIZE(bma023_bw_table), + /* No temperature channel */ + .temp_offset = 0, + .int_reset_reg = BMA023_CTRL_REG0, + .int_reset_mask = BMA023_INT_RESET_MASK, + .sleep_reg = BMA023_CTRL_REG0, + .sleep_mask = BMA023_SLEEP, + .bw_reg = BMA023_CTRL_REG2, + .bw_mask = BMA023_BW_MASK, + .scale_reg = BMA023_CTRL_REG2, + .scale_mask = BMA023_RANGE_MASK, + /* No power mode on bma023 */ + .power_reg = 0, + .power_mask = 0, + .lowpower_val = 0, + .int_enable_reg = BMA023_CTRL_REG3, + .int_enable_mask = BMA023_NEW_DATA_INT, + .softreset_reg = BMA023_CTRL_REG0, + .softreset_val = BMA023_RESET_VAL, + .chip_config = bma023_chip_config, + .chip_disable = bma023_chip_disable, + }, + [BMA150] = { + .chip_id = BMA023_ID_REG_VAL, + .channels = bma150_channels, + .num_channels = ARRAY_SIZE(bma150_channels), + .scale_table = bma023_scale_table, + .num_scales = ARRAY_SIZE(bma023_scale_table), + .bw_table = bma023_bw_table, + .num_bw = ARRAY_SIZE(bma023_bw_table), + .temp_offset = -60, /* 0 LSB @ -30 degree C */ + .int_reset_reg = BMA023_CTRL_REG0, + .int_reset_mask = BMA023_INT_RESET_MASK, + .sleep_reg = BMA023_CTRL_REG0, + .sleep_mask = BMA023_SLEEP, + .bw_reg = BMA023_CTRL_REG2, + .bw_mask = BMA023_BW_MASK, + .scale_reg = BMA023_CTRL_REG2, + .scale_mask = BMA023_RANGE_MASK, + /* No power mode on bma150 */ + .power_reg = 0, + .power_mask = 0, + .lowpower_val = 0, + .int_enable_reg = BMA023_CTRL_REG3, + .int_enable_mask = BMA023_NEW_DATA_INT, + .softreset_reg = BMA023_CTRL_REG0, + .softreset_val = BMA023_RESET_VAL, + .chip_config = bma023_chip_config, + .chip_disable = bma023_chip_disable, + }, + [BMA180] = { + .chip_id = BMA180_ID_REG_VAL, + .channels = bma180_channels, + .num_channels = ARRAY_SIZE(bma180_channels), + .scale_table = bma180_scale_table, + .num_scales = ARRAY_SIZE(bma180_scale_table), + .bw_table = bma180_bw_table, + .num_bw = ARRAY_SIZE(bma180_bw_table), + .temp_offset = 48, /* 0 LSB @ 24 degree C */ + .int_reset_reg = BMA180_CTRL_REG0, + .int_reset_mask = BMA180_RESET_INT, + .sleep_reg = BMA180_CTRL_REG0, + .sleep_mask = BMA180_SLEEP, + .bw_reg = BMA180_BW_TCS, + .bw_mask = BMA180_BW, + .scale_reg = BMA180_OFFSET_LSB1, + .scale_mask = BMA180_RANGE, + .power_reg = BMA180_TCO_Z, + .power_mask = BMA180_MODE_CONFIG, + .lowpower_val = BMA180_LOW_POWER, + .int_enable_reg = BMA180_CTRL_REG3, + .int_enable_mask = BMA180_NEW_DATA_INT, + .softreset_reg = BMA180_RESET, + .softreset_val = BMA180_RESET_VAL, + .chip_config = bma180_chip_config, + .chip_disable = bma180_chip_disable, + }, + [BMA250] = { + .chip_id = BMA250_ID_REG_VAL, + .channels = bma250_channels, + .num_channels = ARRAY_SIZE(bma250_channels), + .scale_table = bma250_scale_table, + .num_scales = ARRAY_SIZE(bma250_scale_table), + .bw_table = bma250_bw_table, + .num_bw = ARRAY_SIZE(bma250_bw_table), + .temp_offset = 48, /* 0 LSB @ 24 degree C */ + .int_reset_reg = BMA250_INT_RESET_REG, + .int_reset_mask = BMA250_INT_RESET_MASK, + .sleep_reg = BMA250_POWER_REG, + .sleep_mask = BMA250_SUSPEND_MASK, + .bw_reg = BMA250_BW_REG, + .bw_mask = BMA250_BW_MASK, + .bw_offset = BMA250_BW_OFFSET, + .scale_reg = BMA250_RANGE_REG, + .scale_mask = BMA250_RANGE_MASK, + .power_reg = BMA250_POWER_REG, + .power_mask = BMA250_LOWPOWER_MASK, + .lowpower_val = 1, + .int_enable_reg = BMA250_INT_ENABLE_REG, + .int_enable_mask = BMA250_DATA_INTEN_MASK, + .softreset_reg = BMA250_RESET_REG, + .softreset_val = BMA180_RESET_VAL, + .chip_config = bma250_chip_config, + .chip_disable = bma250_chip_disable, + }, +}; + +static irqreturn_t bma180_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bma180_data *data = iio_priv(indio_dev); + s64 time_ns = iio_get_time_ns(indio_dev); + int bit, ret, i = 0; + struct { + s16 chan[4]; + aligned_s64 timestamp; + } scan = { }; + + mutex_lock(&data->mutex); + + iio_for_each_active_channel(indio_dev, bit) { + ret = bma180_get_data_reg(data, bit); + if (ret < 0) { + mutex_unlock(&data->mutex); + goto err; + } + scan.chan[i++] = ret; + } + + mutex_unlock(&data->mutex); + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), time_ns); +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int bma180_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct bma180_data *data = iio_priv(indio_dev); + + return bma180_set_new_data_intr_state(data, state); +} + +static void bma180_trig_reen(struct iio_trigger *trig) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct bma180_data *data = iio_priv(indio_dev); + int ret; + + ret = bma180_reset_intr(data); + if (ret) + dev_err(&data->client->dev, "failed to reset interrupt\n"); +} + +static const struct iio_trigger_ops bma180_trigger_ops = { + .set_trigger_state = bma180_data_rdy_trigger_set_state, + .reenable = bma180_trig_reen, +}; + +static int bma180_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct device *dev = &client->dev; + struct bma180_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->part_info = i2c_get_match_data(client); + + ret = iio_read_mount_matrix(dev, &data->orientation); + if (ret) + return ret; + + data->vdd_supply = devm_regulator_get(dev, "vdd"); + if (IS_ERR(data->vdd_supply)) + return dev_err_probe(dev, PTR_ERR(data->vdd_supply), + "Failed to get vdd regulator\n"); + + data->vddio_supply = devm_regulator_get(dev, "vddio"); + if (IS_ERR(data->vddio_supply)) + return dev_err_probe(dev, PTR_ERR(data->vddio_supply), + "Failed to get vddio regulator\n"); + + /* Typical voltage 2.4V these are min and max */ + ret = regulator_set_voltage(data->vdd_supply, 1620000, 3600000); + if (ret) + return ret; + ret = regulator_set_voltage(data->vddio_supply, 1200000, 3600000); + if (ret) + return ret; + ret = regulator_enable(data->vdd_supply); + if (ret) { + dev_err(dev, "Failed to enable vdd regulator: %d\n", ret); + return ret; + } + ret = regulator_enable(data->vddio_supply); + if (ret) { + dev_err(dev, "Failed to enable vddio regulator: %d\n", ret); + goto err_disable_vdd; + } + /* Wait to make sure we started up properly (3 ms at least) */ + usleep_range(3000, 5000); + + ret = data->part_info->chip_config(data); + if (ret < 0) + goto err_chip_disable; + + mutex_init(&data->mutex); + indio_dev->channels = data->part_info->channels; + indio_dev->num_channels = data->part_info->num_channels; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &bma180_info; + + if (client->irq > 0) { + data->trig = iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, + iio_device_id(indio_dev)); + if (!data->trig) { + ret = -ENOMEM; + goto err_chip_disable; + } + + ret = devm_request_irq(dev, client->irq, + iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_RISING | IRQF_NO_THREAD, + "bma180_event", data->trig); + if (ret) + goto err_trigger_free; + + data->trig->ops = &bma180_trigger_ops; + iio_trigger_set_drvdata(data->trig, indio_dev); + + ret = iio_trigger_register(data->trig); + if (ret) + goto err_trigger_free; + + indio_dev->trig = iio_trigger_get(data->trig); + } + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + bma180_trigger_handler, NULL); + if (ret < 0) { + dev_err(dev, "unable to setup iio triggered buffer\n"); + goto err_trigger_unregister; + } + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(dev, "unable to register iio device\n"); + goto err_buffer_cleanup; + } + + return 0; + +err_buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); +err_trigger_unregister: + if (data->trig) + iio_trigger_unregister(data->trig); +err_trigger_free: + iio_trigger_free(data->trig); +err_chip_disable: + data->part_info->chip_disable(data); + regulator_disable(data->vddio_supply); +err_disable_vdd: + regulator_disable(data->vdd_supply); + + return ret; +} + +static void bma180_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct bma180_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + if (data->trig) { + iio_trigger_unregister(data->trig); + iio_trigger_free(data->trig); + } + + mutex_lock(&data->mutex); + data->part_info->chip_disable(data); + mutex_unlock(&data->mutex); + regulator_disable(data->vddio_supply); + regulator_disable(data->vdd_supply); +} + +static int bma180_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct bma180_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = bma180_set_sleep_state(data, true); + mutex_unlock(&data->mutex); + + return ret; +} + +static int bma180_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct bma180_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = bma180_set_sleep_state(data, false); + mutex_unlock(&data->mutex); + + return ret; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(bma180_pm_ops, bma180_suspend, bma180_resume); + +static const struct i2c_device_id bma180_ids[] = { + { .name = "bma023", .driver_data = (kernel_ulong_t)&bma180_part_info[BMA023] }, + { .name = "bma150", .driver_data = (kernel_ulong_t)&bma180_part_info[BMA150] }, + { .name = "bma180", .driver_data = (kernel_ulong_t)&bma180_part_info[BMA180] }, + { .name = "bma250", .driver_data = (kernel_ulong_t)&bma180_part_info[BMA250] }, + { .name = "smb380", .driver_data = (kernel_ulong_t)&bma180_part_info[BMA150] }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, bma180_ids); + +static const struct of_device_id bma180_of_match[] = { + { + .compatible = "bosch,bma023", + .data = &bma180_part_info[BMA023] + }, + { + .compatible = "bosch,bma150", + .data = &bma180_part_info[BMA150] + }, + { + .compatible = "bosch,bma180", + .data = &bma180_part_info[BMA180] + }, + { + .compatible = "bosch,bma250", + .data = &bma180_part_info[BMA250] + }, + { + .compatible = "bosch,smb380", + .data = &bma180_part_info[BMA150] + }, + { } +}; +MODULE_DEVICE_TABLE(of, bma180_of_match); + +static struct i2c_driver bma180_driver = { + .driver = { + .name = "bma180", + .pm = pm_sleep_ptr(&bma180_pm_ops), + .of_match_table = bma180_of_match, + }, + .probe = bma180_probe, + .remove = bma180_remove, + .id_table = bma180_ids, +}; + +module_i2c_driver(bma180_driver); + +MODULE_AUTHOR("Kravchenko Oleksandr "); +MODULE_AUTHOR("Texas Instruments, Inc."); +MODULE_DESCRIPTION("Bosch BMA023/BMA1x0/BMA250 triaxial acceleration sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/accel/bma220.h b/drivers/iio/accel/bma220.h new file mode 100644 index 000000000..00dfe2752 --- /dev/null +++ b/drivers/iio/accel/bma220.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Forward declarations needed by the bma220 sources. + * + * Copyright 2025 Petre Rodan + */ + +#ifndef _BMA220_H +#define _BMA220_H + +#include +#include + +#define BMA220_REG_WDT 0x17 +#define BMA220_WDT_MASK GENMASK(2, 1) +#define BMA220_WDT_OFF 0x0 +#define BMA220_WDT_1MS 0x2 +#define BMA220_WDT_10MS 0x3 + +struct device; + +extern const struct regmap_config bma220_i2c_regmap_config; +extern const struct regmap_config bma220_spi_regmap_config; +extern const struct dev_pm_ops bma220_pm_ops; + +int bma220_common_probe(struct device *dev, struct regmap *regmap, int irq); + +#endif diff --git a/drivers/iio/accel/bma220_core.c b/drivers/iio/accel/bma220_core.c new file mode 100644 index 000000000..b83abfdd8 --- /dev/null +++ b/drivers/iio/accel/bma220_core.c @@ -0,0 +1,583 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * BMA220 Digital triaxial acceleration sensor driver + * + * Copyright (c) 2016,2020 Intel Corporation. + * Copyright (c) 2025 Petre Rodan + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "bma220.h" + +#define BMA220_REG_ID 0x00 +#define BMA220_REG_REVISION_ID 0x01 +#define BMA220_REG_ACCEL_X 0x02 +#define BMA220_REG_ACCEL_Y 0x03 +#define BMA220_REG_ACCEL_Z 0x04 +#define BMA220_REG_CONF0 0x05 +#define BMA220_HIGH_DUR_MSK GENMASK(5, 0) +#define BMA220_HIGH_HY_MSK GENMASK(7, 6) +#define BMA220_REG_CONF1 0x06 +#define BMA220_HIGH_TH_MSK GENMASK(3, 0) +#define BMA220_LOW_TH_MSK GENMASK(7, 4) +#define BMA220_REG_CONF2 0x07 +#define BMA220_LOW_DUR_MSK GENMASK(5, 0) +#define BMA220_LOW_HY_MSK GENMASK(7, 6) +#define BMA220_REG_CONF3 0x08 +#define BMA220_TT_DUR_MSK GENMASK(2, 0) +#define BMA220_TT_TH_MSK GENMASK(6, 3) +#define BMA220_REG_CONF4 0x09 +#define BMA220_SLOPE_DUR_MSK GENMASK(1, 0) +#define BMA220_SLOPE_TH_MSK GENMASK(5, 2) +#define BMA220_REG_CONF5 0x0a +#define BMA220_TIP_EN_MSK BIT(4) +#define BMA220_REG_IF0 0x0b +#define BMA220_REG_IF1 0x0c +#define BMA220_IF_SLOPE BIT(0) +#define BMA220_IF_DRDY BIT(1) +#define BMA220_IF_HIGH BIT(2) +#define BMA220_IF_LOW BIT(3) +#define BMA220_IF_TT BIT(4) +#define BMA220_REG_IE0 0x0d +#define BMA220_INT_EN_TAP_Z_MSK BIT(0) +#define BMA220_INT_EN_TAP_Y_MSK BIT(1) +#define BMA220_INT_EN_TAP_X_MSK BIT(2) +#define BMA220_INT_EN_SLOPE_Z_MSK BIT(3) +#define BMA220_INT_EN_SLOPE_Y_MSK BIT(4) +#define BMA220_INT_EN_SLOPE_X_MSK BIT(5) +#define BMA220_INT_EN_DRDY_MSK BIT(7) +#define BMA220_REG_IE1 0x0e +#define BMA220_INT_EN_HIGH_Z_MSK BIT(0) +#define BMA220_INT_EN_HIGH_Y_MSK BIT(1) +#define BMA220_INT_EN_HIGH_X_MSK BIT(2) +#define BMA220_INT_EN_LOW_MSK BIT(3) +#define BMA220_INT_LATCH_MSK GENMASK(6, 4) +#define BMA220_INT_RST_MSK BIT(7) +#define BMA220_REG_IE2 0x0f +#define BMA220_REG_FILTER 0x10 +#define BMA220_FILTER_MASK GENMASK(3, 0) +#define BMA220_REG_RANGE 0x11 +#define BMA220_RANGE_MASK GENMASK(1, 0) +#define BMA220_REG_SUSPEND 0x18 +#define BMA220_REG_SOFTRESET 0x19 + +#define BMA220_CHIP_ID 0xDD +#define BMA220_SUSPEND_SLEEP 0xFF +#define BMA220_SUSPEND_WAKE 0x00 +#define BMA220_RESET_MODE 0xFF +#define BMA220_NONRESET_MODE 0x00 + +#define BMA220_DEVICE_NAME "bma220" + +#define BMA220_COF_1000Hz 0x0 +#define BMA220_COF_500Hz 0x1 +#define BMA220_COF_250Hz 0x2 +#define BMA220_COF_125Hz 0x3 +#define BMA220_COF_64Hz 0x4 +#define BMA220_COF_32Hz 0x5 + +#define BMA220_ACCEL_CHANNEL(index, reg, axis) { \ + .type = IIO_ACCEL, \ + .address = reg, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE) |\ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 6, \ + .storagebits = 8, \ + .shift = 2, \ + .endianness = IIO_CPU, \ + }, \ +} + +enum bma220_axis { + AXIS_X, + AXIS_Y, + AXIS_Z, +}; + +static const int bma220_scale_table[][2] = { + { 0, 623000 }, { 1, 248000 }, { 2, 491000 }, { 4, 983000 }, +}; + +struct bma220_data { + struct regmap *regmap; + struct mutex lock; + u8 lpf_3dB_freq_idx; + u8 range_idx; + struct iio_trigger *trig; + struct { + s8 chans[3]; + /* Ensure timestamp is naturally aligned. */ + aligned_s64 timestamp; + } scan __aligned(IIO_DMA_MINALIGN); +}; + +static const struct iio_chan_spec bma220_channels[] = { + BMA220_ACCEL_CHANNEL(0, BMA220_REG_ACCEL_X, X), + BMA220_ACCEL_CHANNEL(1, BMA220_REG_ACCEL_Y, Y), + BMA220_ACCEL_CHANNEL(2, BMA220_REG_ACCEL_Z, Z), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +/* Available cut-off frequencies of the low pass filter in Hz. */ +static const int bma220_lpf_3dB_freq_Hz_table[] = { + [BMA220_COF_1000Hz] = 1000, + [BMA220_COF_500Hz] = 500, + [BMA220_COF_250Hz] = 250, + [BMA220_COF_125Hz] = 125, + [BMA220_COF_64Hz] = 64, + [BMA220_COF_32Hz] = 32, +}; + +static const unsigned long bma220_accel_scan_masks[] = { + BIT(AXIS_X) | BIT(AXIS_Y) | BIT(AXIS_Z), + 0 +}; + +static bool bma220_is_writable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMA220_REG_CONF0: + case BMA220_REG_CONF1: + case BMA220_REG_CONF2: + case BMA220_REG_CONF3: + case BMA220_REG_CONF4: + case BMA220_REG_CONF5: + case BMA220_REG_IE0: + case BMA220_REG_IE1: + case BMA220_REG_IE2: + case BMA220_REG_FILTER: + case BMA220_REG_RANGE: + case BMA220_REG_WDT: + return true; + default: + return false; + } +} + +const struct regmap_config bma220_spi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .read_flag_mask = BIT(7), + .max_register = BMA220_REG_SOFTRESET, + .cache_type = REGCACHE_NONE, + .writeable_reg = bma220_is_writable_reg, +}; +EXPORT_SYMBOL_NS_GPL(bma220_spi_regmap_config, "IIO_BOSCH_BMA220"); + +/* + * Based on the datasheet the memory map differs between the SPI and the I2C + * implementations. I2C register addresses are simply shifted to the left + * by 1 bit yet the register size remains unchanged. + * This driver employs the SPI memory map to correlate register names to + * addresses regardless of the bus type. + */ + +const struct regmap_config bma220_i2c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .reg_shift = -1, + .max_register = BMA220_REG_SOFTRESET, + .cache_type = REGCACHE_NONE, + .writeable_reg = bma220_is_writable_reg, +}; +EXPORT_SYMBOL_NS_GPL(bma220_i2c_regmap_config, "IIO_BOSCH_BMA220"); + +static int bma220_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct bma220_data *data = iio_priv(indio_dev); + + return regmap_update_bits(data->regmap, BMA220_REG_IE0, + BMA220_INT_EN_DRDY_MSK, + FIELD_PREP(BMA220_INT_EN_DRDY_MSK, state)); +} + +static const struct iio_trigger_ops bma220_trigger_ops = { + .set_trigger_state = &bma220_data_rdy_trigger_set_state, + .validate_device = &iio_trigger_validate_own_device, +}; + +static irqreturn_t bma220_trigger_handler(int irq, void *p) +{ + int ret; + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bma220_data *data = iio_priv(indio_dev); + + ret = regmap_bulk_read(data->regmap, BMA220_REG_ACCEL_X, + &data->scan.chans, + sizeof(data->scan.chans)); + if (ret < 0) + return IRQ_NONE; + + iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + iio_get_time_ns(indio_dev)); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int bma220_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + u8 index; + unsigned int reg; + struct bma220_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(data->regmap, chan->address, ®); + if (ret < 0) + return -EINVAL; + *val = sign_extend32(reg >> chan->scan_type.shift, + chan->scan_type.realbits - 1); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + index = data->range_idx; + *val = bma220_scale_table[index][0]; + *val2 = bma220_scale_table[index][1]; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + index = data->lpf_3dB_freq_idx; + *val = bma220_lpf_3dB_freq_Hz_table[index]; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int bma220_find_match_2dt(const int (*tbl)[2], const int n, + const int val, const int val2) +{ + int i; + + for (i = 0; i < n; i++) { + if (tbl[i][0] == val && tbl[i][1] == val2) + return i; + } + + return -EINVAL; +} + +static int bma220_find_match(const int *arr, const int n, const int val) +{ + int i; + + for (i = 0; i < n; i++) { + if (arr[i] == val) + return i; + } + + return -EINVAL; +} + +static int bma220_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret; + int index = -1; + struct bma220_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + index = bma220_find_match_2dt(bma220_scale_table, + ARRAY_SIZE(bma220_scale_table), + val, val2); + if (index < 0) + return -EINVAL; + + ret = regmap_update_bits(data->regmap, BMA220_REG_RANGE, + BMA220_RANGE_MASK, + FIELD_PREP(BMA220_RANGE_MASK, index)); + if (ret < 0) + return ret; + data->range_idx = index; + + return 0; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + index = bma220_find_match(bma220_lpf_3dB_freq_Hz_table, + ARRAY_SIZE(bma220_lpf_3dB_freq_Hz_table), + val); + if (index < 0) + return -EINVAL; + + ret = regmap_update_bits(data->regmap, BMA220_REG_FILTER, + BMA220_FILTER_MASK, + FIELD_PREP(BMA220_FILTER_MASK, index)); + if (ret < 0) + return ret; + data->lpf_3dB_freq_idx = index; + + return 0; + } + + return -EINVAL; +} + +static int bma220_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = (int *)bma220_scale_table; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = ARRAY_SIZE(bma220_scale_table) * 2; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + *vals = (const int *)bma220_lpf_3dB_freq_Hz_table; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(bma220_lpf_3dB_freq_Hz_table); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int bma220_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct bma220_data *data = iio_priv(indio_dev); + + if (readval) + return regmap_read(data->regmap, reg, readval); + return regmap_write(data->regmap, reg, writeval); +} + +static const struct iio_info bma220_info = { + .read_raw = bma220_read_raw, + .write_raw = bma220_write_raw, + .read_avail = bma220_read_avail, + .debugfs_reg_access = &bma220_reg_access, +}; + +static int bma220_reset(struct bma220_data *data, bool up) +{ + int ret; + unsigned int i, val; + + /* + * The chip can be reset by a simple register read. + * We need up to 2 register reads of the softreset register + * to make sure that the device is in the desired state. + */ + for (i = 0; i < 2; i++) { + ret = regmap_read(data->regmap, BMA220_REG_SOFTRESET, &val); + if (ret < 0) + return ret; + + if (up && val == BMA220_RESET_MODE) + return 0; + + if (!up && val == BMA220_NONRESET_MODE) + return 0; + } + + return -EBUSY; +} + +static int bma220_power(struct bma220_data *data, bool up) +{ + int ret; + unsigned int i, val; + + /* + * The chip can be suspended/woken up by a simple register read. + * So, we need up to 2 register reads of the suspend register + * to make sure that the device is in the desired state. + */ + for (i = 0; i < 2; i++) { + ret = regmap_read(data->regmap, BMA220_REG_SUSPEND, &val); + if (ret < 0) + return ret; + + if (up && val == BMA220_SUSPEND_SLEEP) + return 0; + + if (!up && val == BMA220_SUSPEND_WAKE) + return 0; + } + + return -EBUSY; +} + +static int bma220_init(struct device *dev, struct bma220_data *data) +{ + int ret; + unsigned int val; + static const char * const regulator_names[] = { "vddd", "vddio", "vdda" }; + + ret = devm_regulator_bulk_get_enable(dev, + ARRAY_SIZE(regulator_names), + regulator_names); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + ret = regmap_read(data->regmap, BMA220_REG_ID, &val); + if (ret) + return dev_err_probe(dev, ret, + "Failed to read chip id register\n"); + + if (val != BMA220_CHIP_ID) + dev_info(dev, "Unknown chip found: 0x%02x\n", val); + + ret = bma220_power(data, true); + if (ret) + return dev_err_probe(dev, ret, "Failed to power-on chip\n"); + + ret = bma220_reset(data, true); + if (ret) + return dev_err_probe(dev, ret, "Failed to soft reset chip\n"); + + return 0; +} + +static void bma220_deinit(void *data_ptr) +{ + struct bma220_data *data = data_ptr; + int ret; + struct device *dev = regmap_get_device(data->regmap); + + ret = bma220_power(data, false); + if (ret) + dev_warn(dev, + "Failed to put device into suspend mode (%pe)\n", + ERR_PTR(ret)); +} + +static irqreturn_t bma220_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct bma220_data *data = iio_priv(indio_dev); + int ret; + unsigned int bma220_reg_if1; + + ret = regmap_read(data->regmap, BMA220_REG_IF1, &bma220_reg_if1); + if (ret) + return IRQ_NONE; + + if (FIELD_GET(BMA220_IF_DRDY, bma220_reg_if1)) + iio_trigger_poll_nested(data->trig); + + return IRQ_HANDLED; +} + +int bma220_common_probe(struct device *dev, struct regmap *regmap, int irq) +{ + int ret; + struct iio_dev *indio_dev; + struct bma220_data *data; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->regmap = regmap; + + ret = bma220_init(dev, data); + if (ret) + return ret; + + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; + + indio_dev->info = &bma220_info; + indio_dev->name = BMA220_DEVICE_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = bma220_channels; + indio_dev->num_channels = ARRAY_SIZE(bma220_channels); + indio_dev->available_scan_masks = bma220_accel_scan_masks; + + if (irq > 0) { + data->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->trig) + return -ENOMEM; + + data->trig->ops = &bma220_trigger_ops; + iio_trigger_set_drvdata(data->trig, indio_dev); + + ret = devm_iio_trigger_register(dev, data->trig); + if (ret) + return dev_err_probe(dev, ret, + "iio trigger register fail\n"); + indio_dev->trig = iio_trigger_get(data->trig); + ret = devm_request_threaded_irq(dev, irq, NULL, + &bma220_irq_handler, IRQF_ONESHOT, + indio_dev->name, indio_dev); + if (ret) + return ret; + } + + ret = devm_add_action_or_reset(dev, bma220_deinit, data); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + bma220_trigger_handler, NULL); + if (ret < 0) + dev_err_probe(dev, ret, "iio triggered buffer setup failed\n"); + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS_GPL(bma220_common_probe, "IIO_BOSCH_BMA220"); + +static int bma220_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bma220_data *data = iio_priv(indio_dev); + + return bma220_power(data, false); +} + +static int bma220_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bma220_data *data = iio_priv(indio_dev); + + return bma220_power(data, true); +} +EXPORT_NS_SIMPLE_DEV_PM_OPS(bma220_pm_ops, bma220_suspend, bma220_resume, + IIO_BOSCH_BMA220); + +MODULE_AUTHOR("Tiberiu Breana "); +MODULE_DESCRIPTION("BMA220 acceleration sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/accel/bma220_i2c.c b/drivers/iio/accel/bma220_i2c.c new file mode 100644 index 000000000..7e850e95c --- /dev/null +++ b/drivers/iio/accel/bma220_i2c.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Bosch triaxial acceleration sensor + * + * Copyright (c) 2025 Petre Rodan + * + * Datasheet: https://media.digikey.com/pdf/Data%20Sheets/Bosch/BMA220.pdf + * I2C address is either 0x0b or 0x0a depending on CSB (pin 10) + */ + +#include +#include +#include +#include +#include + +#include "bma220.h" + +static int bma220_set_wdt(struct regmap *regmap, const u8 val) +{ + return regmap_update_bits(regmap, BMA220_REG_WDT, BMA220_WDT_MASK, + FIELD_PREP(BMA220_WDT_MASK, val)); +} + +static int bma220_i2c_probe(struct i2c_client *client) +{ + struct regmap *regmap; + int ret; + + regmap = devm_regmap_init_i2c(client, &bma220_i2c_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(&client->dev, PTR_ERR(regmap), + "failed to create regmap\n"); + + ret = bma220_common_probe(&client->dev, regmap, client->irq); + if (ret) + return ret; + + return bma220_set_wdt(regmap, BMA220_WDT_1MS); +} + +static const struct of_device_id bma220_i2c_match[] = { + { .compatible = "bosch,bma220" }, + { } +}; +MODULE_DEVICE_TABLE(of, bma220_i2c_match); + +static const struct i2c_device_id bma220_i2c_id[] = { + { .name = "bma220" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bma220_i2c_id); + +static struct i2c_driver bma220_i2c_driver = { + .driver = { + .name = "bma220_i2c", + .pm = pm_sleep_ptr(&bma220_pm_ops), + .of_match_table = bma220_i2c_match, + }, + .probe = bma220_i2c_probe, + .id_table = bma220_i2c_id, +}; +module_i2c_driver(bma220_i2c_driver); + +MODULE_AUTHOR("Petre Rodan "); +MODULE_DESCRIPTION("Bosch triaxial acceleration sensor i2c driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_BOSCH_BMA220"); diff --git a/drivers/iio/accel/bma220_spi.c b/drivers/iio/accel/bma220_spi.c new file mode 100644 index 000000000..d6c1087cc --- /dev/null +++ b/drivers/iio/accel/bma220_spi.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * BMA220 Digital triaxial acceleration sensor driver + * + * Copyright (c) 2016,2020 Intel Corporation. + */ + +#include +#include +#include +#include + +#include "bma220.h" + +static int bma220_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_spi(spi, &bma220_spi_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(&spi->dev, PTR_ERR(regmap), + "failed to create regmap\n"); + + return bma220_common_probe(&spi->dev, regmap, spi->irq); +} + +static const struct spi_device_id bma220_spi_id[] = { + { .name = "bma220" }, + { } +}; + +static const struct acpi_device_id bma220_acpi_id[] = { + { "BMA0220", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, bma220_spi_id); + +static const struct of_device_id bma220_of_spi_match[] = { + { .compatible = "bosch,bma220" }, + { } +}; +MODULE_DEVICE_TABLE(of, bma220_of_spi_match); + +static struct spi_driver bma220_spi_driver = { + .driver = { + .name = "bma220_spi", + .pm = pm_sleep_ptr(&bma220_pm_ops), + .of_match_table = bma220_of_spi_match, + .acpi_match_table = bma220_acpi_id, + }, + .probe = bma220_spi_probe, + .id_table = bma220_spi_id, +}; +module_spi_driver(bma220_spi_driver); + +MODULE_AUTHOR("Tiberiu Breana "); +MODULE_DESCRIPTION("BMA220 triaxial acceleration sensor spi driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_BOSCH_BMA220"); diff --git a/drivers/iio/accel/bma400.h b/drivers/iio/accel/bma400.h new file mode 100644 index 000000000..b5f3cac51 --- /dev/null +++ b/drivers/iio/accel/bma400.h @@ -0,0 +1,192 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Register constants and other forward declarations needed by the bma400 + * sources. + * + * Copyright 2019 Dan Robertson + */ + +#ifndef _BMA400_H_ +#define _BMA400_H_ + +#include +#include + +/* + * Read-Only Registers + */ + +/* Chip ID of BMA 400 devices found in the chip ID register. */ +#define BMA400_ID_REG_VAL 0x90 + +/* Status and ID registers */ +#define BMA400_CHIP_ID_REG 0x00 +#define BMA400_ERR_REG 0x02 +#define BMA400_STATUS_REG 0x03 + +/* Acceleration registers */ +#define BMA400_ACC_X_LSB_REG 0x04 +#define BMA400_ACC_X_MSB_REG 0x05 +#define BMA400_ACC_Y_LSB_REG 0x06 +#define BMA400_ACC_Y_MSB_REG 0x07 +#define BMA400_ACC_Z_LSB_REG 0x08 +#define BMA400_ACC_Z_MSB_REG 0x09 + +/* Sensor time registers */ +#define BMA400_SENSOR_TIME0_REG 0x0a +#define BMA400_SENSOR_TIME1_REG 0x0b +#define BMA400_SENSOR_TIME2_REG 0x0c + +/* Event and interrupt registers */ +#define BMA400_EVENT_REG 0x0d + +#define BMA400_INT_STAT0_REG 0x0e +#define BMA400_INT_STAT0_GEN1_MASK BIT(2) +#define BMA400_INT_STAT0_GEN2_MASK BIT(3) +#define BMA400_INT_STAT0_DRDY_MASK BIT(7) + +#define BMA400_INT_STAT1_REG 0x0f +#define BMA400_INT_STAT1_STEP_INT_MASK GENMASK(9, 8) +#define BMA400_INT_STAT1_S_TAP_MASK BIT(10) +#define BMA400_INT_STAT1_D_TAP_MASK BIT(11) + +#define BMA400_INT_STAT2_REG 0x10 + +/* Bit present in all INT_STAT registers */ +#define BMA400_INT_STAT_ENG_OVRRUN_MASK BIT(4) + +/* Temperature register */ +#define BMA400_TEMP_DATA_REG 0x11 + +/* FIFO length and data registers */ +#define BMA400_FIFO_LENGTH0_REG 0x12 +#define BMA400_FIFO_LENGTH1_REG 0x13 +#define BMA400_FIFO_DATA_REG 0x14 + +/* Step count registers */ +#define BMA400_STEP_CNT0_REG 0x15 +#define BMA400_STEP_CNT1_REG 0x16 +#define BMA400_STEP_CNT3_REG 0x17 +#define BMA400_STEP_STAT_REG 0x18 +#define BMA400_STEP_RAW_LEN 0x03 + +/* + * Read-write configuration registers + */ +#define BMA400_ACC_CONFIG0_REG 0x19 +#define BMA400_ACC_CONFIG0_LP_OSR_MASK GENMASK(6, 5) + +#define BMA400_ACC_CONFIG1_REG 0x1a +#define BMA400_ACC_CONFIG1_ODR_MASK GENMASK(3, 0) +#define BMA400_ACC_CONFIG1_ODR_MIN_RAW 0x05 +#define BMA400_ACC_CONFIG1_ODR_LP_RAW 0x06 +#define BMA400_ACC_CONFIG1_ODR_MAX_RAW 0x0b +#define BMA400_ACC_CONFIG1_ODR_MAX_HZ 800 +#define BMA400_ACC_CONFIG1_ODR_MIN_WHOLE_HZ 25 +#define BMA400_ACC_CONFIG1_ODR_MIN_HZ 12 +#define BMA400_ACC_CONFIG1_NP_OSR_MASK GENMASK(5, 4) +#define BMA400_ACC_CONFIG1_ACC_RANGE_MASK GENMASK(7, 6) + +#define BMA400_ACC_CONFIG2_REG 0x1b + +/* Interrupt registers */ +#define BMA400_INT_CONFIG0_REG 0x1f +#define BMA400_INT_CONFIG0_GEN1_MASK BIT(2) +#define BMA400_INT_CONFIG0_GEN2_MASK BIT(3) +#define BMA400_INT_CONFIG0_DRDY_MASK BIT(7) + +enum bma400_generic_intr { + BMA400_GEN1_INTR = 0x1, + BMA400_GEN2_INTR = 0x2, +}; + +#define BMA400_INT_CONFIG1_REG 0x20 +#define BMA400_INT_CONFIG1_STEP_INT_MASK BIT(0) +#define BMA400_INT_CONFIG1_S_TAP_MASK BIT(2) +#define BMA400_INT_CONFIG1_D_TAP_MASK BIT(3) + +#define BMA400_INT1_MAP_REG 0x21 +#define BMA400_INT12_MAP_REG 0x23 +#define BMA400_INT_IO_CTRL_REG 0x24 + +#define BMA400_TWO_BITS_MASK GENMASK(1, 0) + +/* Generic interrupts register */ +#define BMA400_GENINT_CONFIG_REG_BASE 0x3f +#define BMA400_NUM_GENINT_CONFIG_REGS 11 +#define BMA400_GENINT_CONFIG_REG(gen_intr, config_idx) \ + (BMA400_GENINT_CONFIG_REG_BASE + \ + (gen_intr - 1) * BMA400_NUM_GENINT_CONFIG_REGS + \ + (config_idx)) +#define BMA400_GENINT_CONFIG0_HYST_MASK GENMASK(1, 0) +#define BMA400_GENINT_CONFIG0_REF_UPD_MODE_MASK GENMASK(3, 2) +#define BMA400_GENINT_CONFIG0_DATA_SRC_MASK BIT(4) +#define BMA400_GENINT_CONFIG0_X_EN_MASK BIT(5) +#define BMA400_GENINT_CONFIG0_Y_EN_MASK BIT(6) +#define BMA400_GENINT_CONFIG0_Z_EN_MASK BIT(7) + +enum bma400_accel_data_src { + ACCEL_FILT1 = 0x0, + ACCEL_FILT2 = 0x1, +}; + +enum bma400_ref_updt_mode { + BMA400_REF_MANUAL_UPDT_MODE = 0x0, + BMA400_REF_ONETIME_UPDT_MODE = 0x1, + BMA400_REF_EVERYTIME_UPDT_MODE = 0x2, + BMA400_REF_EVERYTIME_LP_UPDT_MODE = 0x3, +}; + +#define BMA400_GEN_CONFIG1_OFF 0x01 +#define BMA400_GENINT_CONFIG1_AXES_COMB_MASK BIT(0) +#define BMA400_GENINT_CONFIG1_DETCT_CRIT_MASK BIT(1) + +enum bma400_genintr_acceleval_axescomb { + BMA400_EVAL_X_OR_Y_OR_Z = 0x0, + BMA400_EVAL_X_AND_Y_AND_Z = 0x1, +}; + +enum bma400_detect_criterion { + BMA400_DETECT_INACTIVITY = 0x0, + BMA400_DETECT_ACTIVITY = 0x1, +}; + +/* TAP config registers */ +#define BMA400_TAP_CONFIG_REG 0x57 +#define BMA400_TAP_CONFIG_SEN_MASK GENMASK(2, 0) + +#define BMA400_TAP_CONFIG1_REG 0x58 +#define BMA400_TAP_CONFIG1_TICSTH_MASK GENMASK(1, 0) +#define BMA400_TAP_CONFIG1_QUIET_MASK GENMASK(3, 2) +#define BMA400_TAP_CONFIG1_QUIETDT_MASK GENMASK(5, 4) +#define BMA400_TAP_TIM_LIST_LEN 4 + +#define BMA400_CMD_REG 0x7e +/* + * BMA400_SCALE_MIN macro value represents m/s^2 for 1 LSB before + * converting to micro values for +-2g range. + * + * For +-2g - 1 LSB = 0.976562 milli g = 0.009576 m/s^2 + * For +-4g - 1 LSB = 1.953125 milli g = 0.019153 m/s^2 + * For +-16g - 1 LSB = 7.8125 milli g = 0.076614 m/s^2 + * + * The raw value which is used to select the different ranges is determined + * by the first bit set position from the scale value, so BMA400_SCALE_MIN + * should be odd. + * + * Scale values for +-2g, +-4g, +-8g and +-16g are populated into bma400_scales + * array by left shifting BMA400_SCALE_MIN. + * e.g.: + * To select +-2g = 9577 << 0 = raw value to write is 0. + * To select +-8g = 9577 << 2 = raw value to write is 2. + * To select +-16g = 9577 << 3 = raw value to write is 3. + */ +#define BMA400_ACC_SCALE_MIN 9577 +#define BMA400_ACC_SCALE_MAX 76617 + +extern const struct regmap_config bma400_regmap_config; + +int bma400_probe(struct device *dev, struct regmap *regmap, int irq, + const char *name); + +#endif diff --git a/drivers/iio/accel/bma400_core.c b/drivers/iio/accel/bma400_core.c new file mode 100644 index 000000000..7e3cff4cc --- /dev/null +++ b/drivers/iio/accel/bma400_core.c @@ -0,0 +1,1808 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Core IIO driver for Bosch BMA400 triaxial acceleration sensor. + * + * Copyright 2019 Dan Robertson + * + * TODO: + * - Support for power management + * - Support events and interrupts + * - Create channel for step count + * - Create channel for sensor time + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "bma400.h" + +/* + * The G-range selection may be one of 2g, 4g, 8, or 16g. The scale may + * be selected with the acc_range bits of the ACC_CONFIG1 register. + * NB: This buffer is populated in the device init. + */ +static int bma400_scales[8]; + +/* + * See the ACC_CONFIG1 section of the datasheet. + * NB: This buffer is populated in the device init. + */ +static int bma400_sample_freqs[14]; + +static const int bma400_osr_range[] = { 0, 1, 3 }; + +static int tap_reset_timeout[BMA400_TAP_TIM_LIST_LEN] = { + 300000, + 400000, + 500000, + 600000 +}; + +static int tap_max2min_time[BMA400_TAP_TIM_LIST_LEN] = { + 30000, + 45000, + 60000, + 90000 +}; + +static int double_tap2_min_delay[BMA400_TAP_TIM_LIST_LEN] = { + 20000, + 40000, + 60000, + 80000 +}; + +/* See the ACC_CONFIG0 section of the datasheet */ +enum bma400_power_mode { + POWER_MODE_SLEEP = 0x00, + POWER_MODE_LOW = 0x01, + POWER_MODE_NORMAL = 0x02, + POWER_MODE_INVALID = 0x03, +}; + +enum bma400_scan { + BMA400_ACCL_X, + BMA400_ACCL_Y, + BMA400_ACCL_Z, + BMA400_TEMP, +}; + +struct bma400_sample_freq { + int hz; + int uhz; +}; + +enum bma400_activity { + BMA400_STILL, + BMA400_WALKING, + BMA400_RUNNING, +}; + +struct bma400_data { + struct device *dev; + struct regmap *regmap; + struct mutex mutex; /* data register lock */ + struct iio_mount_matrix orientation; + enum bma400_power_mode power_mode; + struct bma400_sample_freq sample_freq; + int oversampling_ratio; + int scale; + struct iio_trigger *trig; + int steps_enabled; + bool step_event_en; + bool activity_event_en; + unsigned int generic_event_en; + unsigned int tap_event_en_bitmask; + /* Correct time stamp alignment */ + struct { + __le16 buff[3]; + u8 temperature; + aligned_s64 ts; + } buffer __aligned(IIO_DMA_MINALIGN); + __le16 status; + __be16 duration; +}; + +struct bma400_genintr_info { + enum bma400_generic_intr genintr; + unsigned int intrmask; + enum iio_event_direction dir; + enum bma400_detect_criterion detect_mode; +}; + +/* Lookup struct for determining GEN1/GEN2 based on dir */ +static const struct bma400_genintr_info bma400_genintrs[] = { + [IIO_EV_DIR_RISING] = { + .genintr = BMA400_GEN1_INTR, + .intrmask = BMA400_INT_CONFIG0_GEN1_MASK, + .dir = IIO_EV_DIR_RISING, + .detect_mode = BMA400_DETECT_ACTIVITY, + }, + [IIO_EV_DIR_FALLING] = { + .genintr = BMA400_GEN2_INTR, + .intrmask = BMA400_INT_CONFIG0_GEN2_MASK, + .dir = IIO_EV_DIR_FALLING, + .detect_mode = BMA400_DETECT_INACTIVITY, + } +}; + +static inline const struct bma400_genintr_info * +get_bma400_genintr_info(enum iio_event_direction dir) +{ + switch (dir) { + case IIO_EV_DIR_RISING: + case IIO_EV_DIR_FALLING: + return &bma400_genintrs[dir]; + default: + return NULL; + }; +} + +static bool bma400_is_writable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMA400_CHIP_ID_REG: + case BMA400_ERR_REG: + case BMA400_STATUS_REG: + case BMA400_ACC_X_LSB_REG: + case BMA400_ACC_X_MSB_REG: + case BMA400_ACC_Y_LSB_REG: + case BMA400_ACC_Y_MSB_REG: + case BMA400_ACC_Z_LSB_REG: + case BMA400_ACC_Z_MSB_REG: + case BMA400_SENSOR_TIME0_REG: + case BMA400_SENSOR_TIME1_REG: + case BMA400_SENSOR_TIME2_REG: + case BMA400_EVENT_REG: + case BMA400_INT_STAT0_REG: + case BMA400_INT_STAT1_REG: + case BMA400_INT_STAT2_REG: + case BMA400_TEMP_DATA_REG: + case BMA400_FIFO_LENGTH0_REG: + case BMA400_FIFO_LENGTH1_REG: + case BMA400_FIFO_DATA_REG: + case BMA400_STEP_CNT0_REG: + case BMA400_STEP_CNT1_REG: + case BMA400_STEP_CNT3_REG: + case BMA400_STEP_STAT_REG: + return false; + default: + return true; + } +} + +static bool bma400_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMA400_ERR_REG: + case BMA400_STATUS_REG: + case BMA400_ACC_X_LSB_REG: + case BMA400_ACC_X_MSB_REG: + case BMA400_ACC_Y_LSB_REG: + case BMA400_ACC_Y_MSB_REG: + case BMA400_ACC_Z_LSB_REG: + case BMA400_ACC_Z_MSB_REG: + case BMA400_SENSOR_TIME0_REG: + case BMA400_SENSOR_TIME1_REG: + case BMA400_SENSOR_TIME2_REG: + case BMA400_EVENT_REG: + case BMA400_INT_STAT0_REG: + case BMA400_INT_STAT1_REG: + case BMA400_INT_STAT2_REG: + case BMA400_TEMP_DATA_REG: + case BMA400_FIFO_LENGTH0_REG: + case BMA400_FIFO_LENGTH1_REG: + case BMA400_FIFO_DATA_REG: + case BMA400_STEP_CNT0_REG: + case BMA400_STEP_CNT1_REG: + case BMA400_STEP_CNT3_REG: + case BMA400_STEP_STAT_REG: + return true; + default: + return false; + } +} + +const struct regmap_config bma400_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = BMA400_CMD_REG, + .cache_type = REGCACHE_MAPLE, + .writeable_reg = bma400_is_writable_reg, + .volatile_reg = bma400_is_volatile_reg, +}; +EXPORT_SYMBOL_NS(bma400_regmap_config, "IIO_BMA400"); + +static const struct iio_mount_matrix * +bma400_accel_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct bma400_data *data = iio_priv(indio_dev); + + return &data->orientation; +} + +static const struct iio_chan_spec_ext_info bma400_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, bma400_accel_get_mount_matrix), + { } +}; + +static const struct iio_event_spec bma400_step_detect_event = { + .type = IIO_EV_TYPE_CHANGE, + .dir = IIO_EV_DIR_NONE, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), +}; + +static const struct iio_event_spec bma400_activity_event = { + .type = IIO_EV_TYPE_CHANGE, + .dir = IIO_EV_DIR_NONE, + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE), +}; + +static const struct iio_event_spec bma400_accel_event[] = { + { + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_FALLING, + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD) | + BIT(IIO_EV_INFO_HYSTERESIS) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD) | + BIT(IIO_EV_INFO_HYSTERESIS) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_GESTURE, + .dir = IIO_EV_DIR_SINGLETAP, + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_RESET_TIMEOUT), + }, + { + .type = IIO_EV_TYPE_GESTURE, + .dir = IIO_EV_DIR_DOUBLETAP, + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_RESET_TIMEOUT) | + BIT(IIO_EV_INFO_TAP2_MIN_DELAY), + }, +}; + +static int usec_to_tapreg_raw(int usec, const int *time_list) +{ + int index; + + for (index = 0; index < BMA400_TAP_TIM_LIST_LEN; index++) { + if (usec == time_list[index]) + return index; + } + return -EINVAL; +} + +static ssize_t in_accel_gesture_tap_maxtomin_time_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct bma400_data *data = iio_priv(indio_dev); + int ret, reg_val, raw, vals[2]; + + ret = regmap_read(data->regmap, BMA400_TAP_CONFIG1_REG, ®_val); + if (ret) + return ret; + + raw = FIELD_GET(BMA400_TAP_CONFIG1_TICSTH_MASK, reg_val); + vals[0] = 0; + vals[1] = tap_max2min_time[raw]; + + return iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, 2, vals); +} + +static ssize_t in_accel_gesture_tap_maxtomin_time_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct bma400_data *data = iio_priv(indio_dev); + int ret, val_int, val_fract, raw; + + ret = iio_str_to_fixpoint(buf, 100000, &val_int, &val_fract); + if (ret) + return ret; + + raw = usec_to_tapreg_raw(val_fract, tap_max2min_time); + if (raw < 0) + return -EINVAL; + + ret = regmap_update_bits(data->regmap, BMA400_TAP_CONFIG1_REG, + BMA400_TAP_CONFIG1_TICSTH_MASK, + FIELD_PREP(BMA400_TAP_CONFIG1_TICSTH_MASK, raw)); + if (ret) + return ret; + + return len; +} + +static IIO_DEVICE_ATTR_RW(in_accel_gesture_tap_maxtomin_time, 0); + +/* + * Tap interrupts works with 200 Hz input data rate and the time based tap + * controls are in the terms of data samples so the below calculation is + * used to convert the configuration values into seconds. + * e.g.: + * 60 data samples * 0.005 ms = 0.3 seconds. + * 80 data samples * 0.005 ms = 0.4 seconds. + */ + +/* quiet configuration values in seconds */ +static IIO_CONST_ATTR(in_accel_gesture_tap_reset_timeout_available, + "0.3 0.4 0.5 0.6"); + +/* tics_th configuration values in seconds */ +static IIO_CONST_ATTR(in_accel_gesture_tap_maxtomin_time_available, + "0.03 0.045 0.06 0.09"); + +/* quiet_dt configuration values in seconds */ +static IIO_CONST_ATTR(in_accel_gesture_doubletap_tap2_min_delay_available, + "0.02 0.04 0.06 0.08"); + +/* List of sensitivity values available to configure tap interrupts */ +static IIO_CONST_ATTR(in_accel_gesture_tap_value_available, "0 1 2 3 4 5 6 7"); + +static struct attribute *bma400_event_attributes[] = { + &iio_const_attr_in_accel_gesture_tap_value_available.dev_attr.attr, + &iio_const_attr_in_accel_gesture_tap_reset_timeout_available.dev_attr.attr, + &iio_const_attr_in_accel_gesture_tap_maxtomin_time_available.dev_attr.attr, + &iio_const_attr_in_accel_gesture_doubletap_tap2_min_delay_available.dev_attr.attr, + &iio_dev_attr_in_accel_gesture_tap_maxtomin_time.dev_attr.attr, + NULL +}; + +static const struct attribute_group bma400_event_attribute_group = { + .attrs = bma400_event_attributes, +}; + +#define BMA400_ACC_CHANNEL(_index, _axis) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .ext_info = bma400_ext_info, \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 12, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ + .event_spec = bma400_accel_event, \ + .num_event_specs = ARRAY_SIZE(bma400_accel_event) \ +} + +#define BMA400_ACTIVITY_CHANNEL(_chan2) { \ + .type = IIO_ACTIVITY, \ + .modified = 1, \ + .channel2 = _chan2, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ + .scan_index = -1, /* No buffer support */ \ + .event_spec = &bma400_activity_event, \ + .num_event_specs = 1, \ +} + +static const struct iio_chan_spec bma400_channels[] = { + BMA400_ACC_CHANNEL(0, X), + BMA400_ACC_CHANNEL(1, Y), + BMA400_ACC_CHANNEL(2, Z), + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = 3, + .scan_type = { + .sign = 's', + .realbits = 8, + .storagebits = 8, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_STEPS, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_ENABLE), + .scan_index = -1, /* No buffer support */ + .event_spec = &bma400_step_detect_event, + .num_event_specs = 1, + }, + BMA400_ACTIVITY_CHANNEL(IIO_MOD_STILL), + BMA400_ACTIVITY_CHANNEL(IIO_MOD_WALKING), + BMA400_ACTIVITY_CHANNEL(IIO_MOD_RUNNING), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static int bma400_get_temp_reg(struct bma400_data *data, int *val, int *val2) +{ + unsigned int raw_temp; + int host_temp; + int ret; + + if (data->power_mode == POWER_MODE_SLEEP) + return -EBUSY; + + ret = regmap_read(data->regmap, BMA400_TEMP_DATA_REG, &raw_temp); + if (ret) + return ret; + + host_temp = sign_extend32(raw_temp, 7); + /* + * The formula for the TEMP_DATA register in the datasheet + * is: x * 0.5 + 23 + */ + *val = (host_temp >> 1) + 23; + *val2 = (host_temp & 0x1) * 500000; + return IIO_VAL_INT_PLUS_MICRO; +} + +static int bma400_get_accel_reg(struct bma400_data *data, + const struct iio_chan_spec *chan, + int *val) +{ + __le16 raw_accel; + int lsb_reg; + int ret; + + if (data->power_mode == POWER_MODE_SLEEP) + return -EBUSY; + + switch (chan->channel2) { + case IIO_MOD_X: + lsb_reg = BMA400_ACC_X_LSB_REG; + break; + case IIO_MOD_Y: + lsb_reg = BMA400_ACC_Y_LSB_REG; + break; + case IIO_MOD_Z: + lsb_reg = BMA400_ACC_Z_LSB_REG; + break; + default: + dev_err(data->dev, "invalid axis channel modifier\n"); + return -EINVAL; + } + + /* bulk read two registers, with the base being the LSB register */ + ret = regmap_bulk_read(data->regmap, lsb_reg, &raw_accel, + sizeof(raw_accel)); + if (ret) + return ret; + + *val = sign_extend32(le16_to_cpu(raw_accel), 11); + return IIO_VAL_INT; +} + +static void bma400_output_data_rate_from_raw(int raw, unsigned int *val, + unsigned int *val2) +{ + *val = BMA400_ACC_CONFIG1_ODR_MAX_HZ >> (BMA400_ACC_CONFIG1_ODR_MAX_RAW - raw); + if (raw > BMA400_ACC_CONFIG1_ODR_MIN_RAW) + *val2 = 0; + else + *val2 = 500000; +} + +static int bma400_get_accel_output_data_rate(struct bma400_data *data) +{ + unsigned int val; + unsigned int odr; + int ret; + + switch (data->power_mode) { + case POWER_MODE_LOW: + /* + * Runs at a fixed rate in low-power mode. See section 4.3 + * in the datasheet. + */ + bma400_output_data_rate_from_raw(BMA400_ACC_CONFIG1_ODR_LP_RAW, + &data->sample_freq.hz, + &data->sample_freq.uhz); + return 0; + case POWER_MODE_NORMAL: + /* + * In normal mode the ODR can be found in the ACC_CONFIG1 + * register. + */ + ret = regmap_read(data->regmap, BMA400_ACC_CONFIG1_REG, &val); + if (ret) + goto error; + + odr = val & BMA400_ACC_CONFIG1_ODR_MASK; + if (odr < BMA400_ACC_CONFIG1_ODR_MIN_RAW || + odr > BMA400_ACC_CONFIG1_ODR_MAX_RAW) { + ret = -EINVAL; + goto error; + } + + bma400_output_data_rate_from_raw(odr, &data->sample_freq.hz, + &data->sample_freq.uhz); + return 0; + case POWER_MODE_SLEEP: + data->sample_freq.hz = 0; + data->sample_freq.uhz = 0; + return 0; + default: + ret = 0; + goto error; + } +error: + data->sample_freq.hz = -1; + data->sample_freq.uhz = -1; + return ret; +} + +static int bma400_set_accel_output_data_rate(struct bma400_data *data, + int hz, int uhz) +{ + unsigned int idx; + unsigned int odr; + unsigned int val; + int ret; + + if (hz >= BMA400_ACC_CONFIG1_ODR_MIN_WHOLE_HZ) { + if (uhz || hz > BMA400_ACC_CONFIG1_ODR_MAX_HZ) + return -EINVAL; + + /* Note this works because MIN_WHOLE_HZ is odd */ + idx = __ffs(hz); + + if (hz >> idx != BMA400_ACC_CONFIG1_ODR_MIN_WHOLE_HZ) + return -EINVAL; + + idx += BMA400_ACC_CONFIG1_ODR_MIN_RAW + 1; + } else if (hz == BMA400_ACC_CONFIG1_ODR_MIN_HZ && uhz == 500000) { + idx = BMA400_ACC_CONFIG1_ODR_MIN_RAW; + } else { + return -EINVAL; + } + + ret = regmap_read(data->regmap, BMA400_ACC_CONFIG1_REG, &val); + if (ret) + return ret; + + /* preserve the range and normal mode osr */ + odr = (~BMA400_ACC_CONFIG1_ODR_MASK & val) | idx; + + ret = regmap_write(data->regmap, BMA400_ACC_CONFIG1_REG, odr); + if (ret) + return ret; + + bma400_output_data_rate_from_raw(idx, &data->sample_freq.hz, + &data->sample_freq.uhz); + return 0; +} + +static int bma400_get_accel_oversampling_ratio(struct bma400_data *data) +{ + unsigned int val; + unsigned int osr; + int ret; + + /* + * The oversampling ratio is stored in a different register + * based on the power-mode. In normal mode the OSR is stored + * in ACC_CONFIG1. In low-power mode it is stored in + * ACC_CONFIG0. + */ + switch (data->power_mode) { + case POWER_MODE_LOW: + ret = regmap_read(data->regmap, BMA400_ACC_CONFIG0_REG, &val); + if (ret) { + data->oversampling_ratio = -1; + return ret; + } + + osr = FIELD_GET(BMA400_ACC_CONFIG0_LP_OSR_MASK, val); + + data->oversampling_ratio = osr; + return 0; + case POWER_MODE_NORMAL: + ret = regmap_read(data->regmap, BMA400_ACC_CONFIG1_REG, &val); + if (ret) { + data->oversampling_ratio = -1; + return ret; + } + + osr = FIELD_GET(BMA400_ACC_CONFIG1_NP_OSR_MASK, val); + + data->oversampling_ratio = osr; + return 0; + case POWER_MODE_SLEEP: + data->oversampling_ratio = 0; + return 0; + default: + data->oversampling_ratio = -1; + return -EINVAL; + } +} + +static int bma400_set_accel_oversampling_ratio(struct bma400_data *data, + int val) +{ + unsigned int acc_config; + int ret; + + if (val & ~BMA400_TWO_BITS_MASK) + return -EINVAL; + + /* + * The oversampling ratio is stored in a different register + * based on the power-mode. + */ + switch (data->power_mode) { + case POWER_MODE_LOW: + ret = regmap_read(data->regmap, BMA400_ACC_CONFIG0_REG, + &acc_config); + if (ret) + return ret; + + ret = regmap_write(data->regmap, BMA400_ACC_CONFIG0_REG, + (acc_config & ~BMA400_ACC_CONFIG0_LP_OSR_MASK) | + FIELD_PREP(BMA400_ACC_CONFIG0_LP_OSR_MASK, val)); + if (ret) { + dev_err(data->dev, "Failed to write out OSR\n"); + return ret; + } + + data->oversampling_ratio = val; + return 0; + case POWER_MODE_NORMAL: + ret = regmap_read(data->regmap, BMA400_ACC_CONFIG1_REG, + &acc_config); + if (ret) + return ret; + + ret = regmap_write(data->regmap, BMA400_ACC_CONFIG1_REG, + (acc_config & ~BMA400_ACC_CONFIG1_NP_OSR_MASK) | + FIELD_PREP(BMA400_ACC_CONFIG1_NP_OSR_MASK, val)); + if (ret) { + dev_err(data->dev, "Failed to write out OSR\n"); + return ret; + } + + data->oversampling_ratio = val; + return 0; + default: + return -EINVAL; + } + return ret; +} + +static int bma400_accel_scale_to_raw(struct bma400_data *data, + unsigned int val) +{ + int raw; + + if (val == 0) + return -EINVAL; + + /* Note this works because BMA400_SCALE_MIN is odd */ + raw = __ffs(val); + + if (val >> raw != BMA400_ACC_SCALE_MIN) + return -EINVAL; + + return raw; +} + +static int bma400_get_accel_scale(struct bma400_data *data) +{ + unsigned int raw_scale; + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, BMA400_ACC_CONFIG1_REG, &val); + if (ret) + return ret; + + raw_scale = FIELD_GET(BMA400_ACC_CONFIG1_ACC_RANGE_MASK, val); + if (raw_scale > BMA400_TWO_BITS_MASK) + return -EINVAL; + + data->scale = BMA400_ACC_SCALE_MIN << raw_scale; + + return 0; +} + +static int bma400_set_accel_scale(struct bma400_data *data, unsigned int val) +{ + unsigned int acc_config; + int raw; + int ret; + + ret = regmap_read(data->regmap, BMA400_ACC_CONFIG1_REG, &acc_config); + if (ret) + return ret; + + raw = bma400_accel_scale_to_raw(data, val); + if (raw < 0) + return raw; + + ret = regmap_write(data->regmap, BMA400_ACC_CONFIG1_REG, + (acc_config & ~BMA400_ACC_CONFIG1_ACC_RANGE_MASK) | + FIELD_PREP(BMA400_ACC_CONFIG1_ACC_RANGE_MASK, raw)); + if (ret) + return ret; + + data->scale = val; + return 0; +} + +static int bma400_get_power_mode(struct bma400_data *data) +{ + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, BMA400_STATUS_REG, &val); + if (ret) { + dev_err(data->dev, "Failed to read status register\n"); + return ret; + } + + data->power_mode = (val >> 1) & BMA400_TWO_BITS_MASK; + return 0; +} + +static int bma400_set_power_mode(struct bma400_data *data, + enum bma400_power_mode mode) +{ + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, BMA400_ACC_CONFIG0_REG, &val); + if (ret) + return ret; + + if (data->power_mode == mode) + return 0; + + if (mode == POWER_MODE_INVALID) + return -EINVAL; + + /* Preserve the low-power oversample ratio etc */ + ret = regmap_write(data->regmap, BMA400_ACC_CONFIG0_REG, + mode | (val & ~BMA400_TWO_BITS_MASK)); + if (ret) { + dev_err(data->dev, "Failed to write to power-mode\n"); + return ret; + } + + data->power_mode = mode; + + /* + * Update our cached osr and odr based on the new + * power-mode. + */ + bma400_get_accel_output_data_rate(data); + bma400_get_accel_oversampling_ratio(data); + return 0; +} + +static int bma400_enable_steps(struct bma400_data *data, int val) +{ + int ret; + + if (data->steps_enabled == val) + return 0; + + ret = regmap_update_bits(data->regmap, BMA400_INT_CONFIG1_REG, + BMA400_INT_CONFIG1_STEP_INT_MASK, + FIELD_PREP(BMA400_INT_CONFIG1_STEP_INT_MASK, val ? 1 : 0)); + if (ret) + return ret; + data->steps_enabled = val; + return ret; +} + +static int bma400_get_steps_reg(struct bma400_data *data, int *val) +{ + int ret; + + u8 *steps_raw __free(kfree) = kmalloc(BMA400_STEP_RAW_LEN, GFP_KERNEL); + if (!steps_raw) + return -ENOMEM; + + ret = regmap_bulk_read(data->regmap, BMA400_STEP_CNT0_REG, + steps_raw, BMA400_STEP_RAW_LEN); + if (ret) + return ret; + + *val = get_unaligned_le24(steps_raw); + + return IIO_VAL_INT; +} + +static void bma400_init_tables(void) +{ + int raw; + int i; + + for (i = 0; i + 1 < ARRAY_SIZE(bma400_sample_freqs); i += 2) { + raw = (i / 2) + 5; + bma400_output_data_rate_from_raw(raw, &bma400_sample_freqs[i], + &bma400_sample_freqs[i + 1]); + } + + for (i = 0; i + 1 < ARRAY_SIZE(bma400_scales); i += 2) { + raw = i / 2; + bma400_scales[i] = 0; + bma400_scales[i + 1] = BMA400_ACC_SCALE_MIN << raw; + } +} + +static void bma400_power_disable(void *data_ptr) +{ + struct bma400_data *data = data_ptr; + int ret; + + mutex_lock(&data->mutex); + ret = bma400_set_power_mode(data, POWER_MODE_SLEEP); + mutex_unlock(&data->mutex); + if (ret) + dev_warn(data->dev, "Failed to put device into sleep mode (%pe)\n", + ERR_PTR(ret)); +} + +static enum iio_modifier bma400_act_to_mod(enum bma400_activity activity) +{ + switch (activity) { + case BMA400_STILL: + return IIO_MOD_STILL; + case BMA400_WALKING: + return IIO_MOD_WALKING; + case BMA400_RUNNING: + return IIO_MOD_RUNNING; + default: + return IIO_NO_MOD; + } +} + +static int bma400_init(struct bma400_data *data) +{ + static const char * const regulator_names[] = { "vdd", "vddio" }; + unsigned int val; + int ret; + + ret = devm_regulator_bulk_get_enable(data->dev, + ARRAY_SIZE(regulator_names), + regulator_names); + if (ret) + return dev_err_probe(data->dev, ret, "Failed to get regulators\n"); + + /* Try to read chip_id register. It must return 0x90. */ + ret = regmap_read(data->regmap, BMA400_CHIP_ID_REG, &val); + if (ret) { + dev_err(data->dev, "Failed to read chip id register\n"); + return ret; + } + + if (val != BMA400_ID_REG_VAL) { + dev_err(data->dev, "Chip ID mismatch\n"); + return -ENODEV; + } + + ret = bma400_get_power_mode(data); + if (ret) { + dev_err(data->dev, "Failed to get the initial power-mode\n"); + return ret; + } + + if (data->power_mode != POWER_MODE_NORMAL) { + ret = bma400_set_power_mode(data, POWER_MODE_NORMAL); + if (ret) { + dev_err(data->dev, "Failed to wake up the device\n"); + return ret; + } + /* + * TODO: The datasheet waits 1500us here in the example, but + * lists 2/ODR as the wakeup time. + */ + usleep_range(1500, 2000); + } + + ret = devm_add_action_or_reset(data->dev, bma400_power_disable, data); + if (ret) + return ret; + + bma400_init_tables(); + + ret = bma400_get_accel_output_data_rate(data); + if (ret) + return ret; + + ret = bma400_get_accel_oversampling_ratio(data); + if (ret) + return ret; + + ret = bma400_get_accel_scale(data); + if (ret) + return ret; + + /* Configure INT1 pin to open drain */ + ret = regmap_write(data->regmap, BMA400_INT_IO_CTRL_REG, 0x06); + if (ret) + return ret; + /* + * Once the interrupt engine is supported we might use the + * data_src_reg, but for now ensure this is set to the + * variable ODR filter selectable by the sample frequency + * channel. + */ + return regmap_write(data->regmap, BMA400_ACC_CONFIG2_REG, 0x00); +} + +static int bma400_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct bma400_data *data = iio_priv(indio_dev); + unsigned int activity; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_TEMP: + mutex_lock(&data->mutex); + ret = bma400_get_temp_reg(data, val, val2); + mutex_unlock(&data->mutex); + return ret; + case IIO_STEPS: + return bma400_get_steps_reg(data, val); + case IIO_ACTIVITY: + ret = regmap_read(data->regmap, BMA400_STEP_STAT_REG, + &activity); + if (ret) + return ret; + /* + * The device does not support confidence value levels, + * so we will always have 100% for current activity and + * 0% for the others. + */ + if (chan->channel2 == bma400_act_to_mod(activity)) + *val = 100; + else + *val = 0; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_RAW: + mutex_lock(&data->mutex); + ret = bma400_get_accel_reg(data, chan, val); + mutex_unlock(&data->mutex); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + switch (chan->type) { + case IIO_ACCEL: + if (data->sample_freq.hz < 0) + return -EINVAL; + + *val = data->sample_freq.hz; + *val2 = data->sample_freq.uhz; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + /* + * Runs at a fixed sampling frequency. See Section 4.4 + * of the datasheet. + */ + *val = 6; + *val2 = 250000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = data->scale; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + /* + * TODO: We could avoid this logic and returning -EINVAL here if + * we set both the low-power and normal mode OSR registers when + * we configure the device. + */ + if (data->oversampling_ratio < 0) + return -EINVAL; + + *val = data->oversampling_ratio; + return IIO_VAL_INT; + case IIO_CHAN_INFO_ENABLE: + *val = data->steps_enabled; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int bma400_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *type = IIO_VAL_INT_PLUS_MICRO; + *vals = bma400_scales; + *length = ARRAY_SIZE(bma400_scales); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *type = IIO_VAL_INT; + *vals = bma400_osr_range; + *length = ARRAY_SIZE(bma400_osr_range); + return IIO_AVAIL_RANGE; + case IIO_CHAN_INFO_SAMP_FREQ: + *type = IIO_VAL_INT_PLUS_MICRO; + *vals = bma400_sample_freqs; + *length = ARRAY_SIZE(bma400_sample_freqs); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int bma400_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, + long mask) +{ + struct bma400_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + /* + * The sample frequency is readonly for the temperature + * register and a fixed value in low-power mode. + */ + if (chan->type != IIO_ACCEL) + return -EINVAL; + + mutex_lock(&data->mutex); + ret = bma400_set_accel_output_data_rate(data, val, val2); + mutex_unlock(&data->mutex); + return ret; + case IIO_CHAN_INFO_SCALE: + if (val != 0 || + val2 < BMA400_ACC_SCALE_MIN || val2 > BMA400_ACC_SCALE_MAX) + return -EINVAL; + + mutex_lock(&data->mutex); + ret = bma400_set_accel_scale(data, val2); + mutex_unlock(&data->mutex); + return ret; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + mutex_lock(&data->mutex); + ret = bma400_set_accel_oversampling_ratio(data, val); + mutex_unlock(&data->mutex); + return ret; + case IIO_CHAN_INFO_ENABLE: + mutex_lock(&data->mutex); + ret = bma400_enable_steps(data, val); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } +} + +static int bma400_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return IIO_VAL_INT; + case IIO_CHAN_INFO_ENABLE: + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int bma400_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct bma400_data *data = iio_priv(indio_dev); + + switch (chan->type) { + case IIO_ACCEL: + switch (dir) { + case IIO_EV_DIR_RISING: + return FIELD_GET(BMA400_INT_CONFIG0_GEN1_MASK, + data->generic_event_en); + case IIO_EV_DIR_FALLING: + return FIELD_GET(BMA400_INT_CONFIG0_GEN2_MASK, + data->generic_event_en); + case IIO_EV_DIR_SINGLETAP: + return FIELD_GET(BMA400_INT_CONFIG1_S_TAP_MASK, + data->tap_event_en_bitmask); + case IIO_EV_DIR_DOUBLETAP: + return FIELD_GET(BMA400_INT_CONFIG1_D_TAP_MASK, + data->tap_event_en_bitmask); + default: + return -EINVAL; + } + case IIO_STEPS: + return data->step_event_en; + case IIO_ACTIVITY: + return data->activity_event_en; + default: + return -EINVAL; + } +} + +static int bma400_steps_event_enable(struct bma400_data *data, int state) +{ + int ret; + + ret = bma400_enable_steps(data, 1); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, BMA400_INT12_MAP_REG, + BMA400_INT_CONFIG1_STEP_INT_MASK, + FIELD_PREP(BMA400_INT_CONFIG1_STEP_INT_MASK, + state)); + if (ret) + return ret; + data->step_event_en = state; + return 0; +} + +static int bma400_generic_event_en(struct bma400_data *data, + enum iio_event_direction dir, + int state) +{ + int ret; + unsigned int intrmask, regval; + enum bma400_generic_intr genintr; + enum bma400_detect_criterion detect_criterion; + const struct bma400_genintr_info *bma400_genintr; + + bma400_genintr = get_bma400_genintr_info(dir); + if (!bma400_genintr) + return -EINVAL; + + genintr = bma400_genintr->genintr; + detect_criterion = bma400_genintr->detect_mode; + intrmask = bma400_genintr->intrmask; + + /* + * Enabling all axis for interrupt evaluation + * Acc_filt2 is recommended as data source in datasheet (Section 4.7) + */ + ret = regmap_write(data->regmap, BMA400_GENINT_CONFIG_REG(genintr, 0), + BMA400_GENINT_CONFIG0_X_EN_MASK | + BMA400_GENINT_CONFIG0_Y_EN_MASK | + BMA400_GENINT_CONFIG0_Z_EN_MASK| + FIELD_PREP(BMA400_GENINT_CONFIG0_DATA_SRC_MASK, ACCEL_FILT2)| + FIELD_PREP(BMA400_GENINT_CONFIG0_REF_UPD_MODE_MASK, + BMA400_REF_EVERYTIME_UPDT_MODE)); + if (ret) + return ret; + + /* OR combination of all axis for interrupt evaluation */ + regval = FIELD_PREP(BMA400_GENINT_CONFIG1_AXES_COMB_MASK, BMA400_EVAL_X_OR_Y_OR_Z) | + FIELD_PREP(BMA400_GENINT_CONFIG1_DETCT_CRIT_MASK, detect_criterion); + ret = regmap_write(data->regmap, BMA400_GENINT_CONFIG_REG(genintr, 1), regval); + if (ret) + return ret; + + /* + * Initial value to avoid interrupts while enabling + * Value is in units of 8mg/lsb, i.e. effective val is val * 8mg/lsb + */ + ret = regmap_write(data->regmap, BMA400_GENINT_CONFIG_REG(genintr, 2), 0x0A); + if (ret) + return ret; + + /* Initial duration value to avoid interrupts while enabling*/ + ret = regmap_write(data->regmap, BMA400_GENINT_CONFIG_REG(genintr, 4), 0x0F); + if (ret) + return ret; + + regval = state ? intrmask : 0; + ret = regmap_update_bits(data->regmap, BMA400_INT1_MAP_REG, intrmask, regval); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, BMA400_INT_CONFIG0_REG, intrmask, regval); + if (ret) + return ret; + + set_mask_bits(&data->generic_event_en, intrmask, regval); + return 0; +} + +static int bma400_tap_event_en(struct bma400_data *data, + enum iio_event_direction dir, int state) +{ + unsigned int mask; + unsigned int field_value = 0; + int ret; + + /* + * Tap interrupts can be configured only in normal mode. + * See table in section 4.3 "Power modes - performance modes" of + * datasheet v1.2. + */ + if (data->power_mode != POWER_MODE_NORMAL) + return -EINVAL; + + /* + * Tap interrupts are operating with a data rate of 200Hz. + * See section 4.7 "Tap sensing interrupt" in datasheet v1.2. + */ + if (data->sample_freq.hz != 200 && state) { + dev_err(data->dev, "Invalid data rate for tap interrupts.\n"); + return -EINVAL; + } + + ret = regmap_update_bits(data->regmap, BMA400_INT12_MAP_REG, + BMA400_INT_CONFIG1_S_TAP_MASK, + FIELD_PREP(BMA400_INT_CONFIG1_S_TAP_MASK, state)); + if (ret) + return ret; + + switch (dir) { + case IIO_EV_DIR_SINGLETAP: + mask = BMA400_INT_CONFIG1_S_TAP_MASK; + set_mask_bits(&field_value, BMA400_INT_CONFIG1_S_TAP_MASK, + FIELD_PREP(BMA400_INT_CONFIG1_S_TAP_MASK, state)); + break; + case IIO_EV_DIR_DOUBLETAP: + mask = BMA400_INT_CONFIG1_D_TAP_MASK; + set_mask_bits(&field_value, BMA400_INT_CONFIG1_D_TAP_MASK, + FIELD_PREP(BMA400_INT_CONFIG1_D_TAP_MASK, state)); + break; + default: + return -EINVAL; + } + + ret = regmap_update_bits(data->regmap, BMA400_INT_CONFIG1_REG, mask, + field_value); + if (ret) + return ret; + + set_mask_bits(&data->tap_event_en_bitmask, mask, field_value); + + return 0; +} + +static int bma400_disable_adv_interrupt(struct bma400_data *data) +{ + int ret; + + ret = regmap_write(data->regmap, BMA400_INT_CONFIG0_REG, 0); + if (ret) + return ret; + + ret = regmap_write(data->regmap, BMA400_INT_CONFIG1_REG, 0); + if (ret) + return ret; + + data->tap_event_en_bitmask = 0; + data->generic_event_en = 0; + data->step_event_en = false; + data->activity_event_en = false; + + return 0; +} + +static int bma400_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct bma400_data *data = iio_priv(indio_dev); + int ret; + + switch (chan->type) { + case IIO_ACCEL: + switch (type) { + case IIO_EV_TYPE_MAG: + mutex_lock(&data->mutex); + ret = bma400_generic_event_en(data, dir, state); + mutex_unlock(&data->mutex); + return ret; + case IIO_EV_TYPE_GESTURE: + mutex_lock(&data->mutex); + ret = bma400_tap_event_en(data, dir, state); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } + case IIO_STEPS: + mutex_lock(&data->mutex); + ret = bma400_steps_event_enable(data, state); + mutex_unlock(&data->mutex); + return ret; + case IIO_ACTIVITY: + mutex_lock(&data->mutex); + if (!data->step_event_en) { + ret = bma400_steps_event_enable(data, true); + if (ret) { + mutex_unlock(&data->mutex); + return ret; + } + } + data->activity_event_en = state; + mutex_unlock(&data->mutex); + return 0; + default: + return -EINVAL; + } +} + +static int bma400_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct bma400_data *data = iio_priv(indio_dev); + int ret, reg_val, raw; + enum bma400_generic_intr genintr; + const struct bma400_genintr_info *bma400_genintr; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (type) { + case IIO_EV_TYPE_MAG: + bma400_genintr = get_bma400_genintr_info(dir); + if (!bma400_genintr) + return -EINVAL; + genintr = bma400_genintr->genintr; + + *val2 = 0; + switch (info) { + case IIO_EV_INFO_VALUE: + ret = regmap_read(data->regmap, + BMA400_GENINT_CONFIG_REG(genintr, 2), + val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_EV_INFO_PERIOD: + mutex_lock(&data->mutex); + ret = regmap_bulk_read(data->regmap, + BMA400_GENINT_CONFIG_REG(genintr, 3), + &data->duration, + sizeof(data->duration)); + if (ret) { + mutex_unlock(&data->mutex); + return ret; + } + *val = be16_to_cpu(data->duration); + mutex_unlock(&data->mutex); + return IIO_VAL_INT; + case IIO_EV_INFO_HYSTERESIS: + ret = regmap_read(data->regmap, + BMA400_GENINT_CONFIG_REG(genintr, 0), + val); + if (ret) + return ret; + *val = FIELD_GET(BMA400_GENINT_CONFIG0_HYST_MASK, *val); + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_EV_TYPE_GESTURE: + switch (info) { + case IIO_EV_INFO_VALUE: + ret = regmap_read(data->regmap, BMA400_TAP_CONFIG_REG, + ®_val); + if (ret) + return ret; + + *val = FIELD_GET(BMA400_TAP_CONFIG_SEN_MASK, reg_val); + return IIO_VAL_INT; + case IIO_EV_INFO_RESET_TIMEOUT: + ret = regmap_read(data->regmap, BMA400_TAP_CONFIG1_REG, + ®_val); + if (ret) + return ret; + + raw = FIELD_GET(BMA400_TAP_CONFIG1_QUIET_MASK, reg_val); + *val = 0; + *val2 = tap_reset_timeout[raw]; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_EV_INFO_TAP2_MIN_DELAY: + ret = regmap_read(data->regmap, BMA400_TAP_CONFIG1_REG, + ®_val); + if (ret) + return ret; + + raw = FIELD_GET(BMA400_TAP_CONFIG1_QUIETDT_MASK, reg_val); + *val = 0; + *val2 = double_tap2_min_delay[raw]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int bma400_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct bma400_data *data = iio_priv(indio_dev); + int ret, raw; + enum bma400_generic_intr genintr; + const struct bma400_genintr_info *bma400_genintr; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (type) { + case IIO_EV_TYPE_MAG: + bma400_genintr = get_bma400_genintr_info(dir); + if (!bma400_genintr) + return -EINVAL; + genintr = bma400_genintr->genintr; + + switch (info) { + case IIO_EV_INFO_VALUE: + if (val < 1 || val > 255) + return -EINVAL; + + return regmap_write(data->regmap, + BMA400_GENINT_CONFIG_REG(genintr, 2), + val); + case IIO_EV_INFO_PERIOD: + if (val < 1 || val > 65535) + return -EINVAL; + + mutex_lock(&data->mutex); + put_unaligned_be16(val, &data->duration); + ret = regmap_bulk_write(data->regmap, + BMA400_GENINT_CONFIG_REG(genintr, 3), + &data->duration, + sizeof(data->duration)); + mutex_unlock(&data->mutex); + return ret; + case IIO_EV_INFO_HYSTERESIS: + if (val < 0 || val > 3) + return -EINVAL; + + return regmap_update_bits(data->regmap, + BMA400_GENINT_CONFIG_REG(genintr, 0), + BMA400_GENINT_CONFIG0_HYST_MASK, + FIELD_PREP(BMA400_GENINT_CONFIG0_HYST_MASK, + val)); + default: + return -EINVAL; + } + case IIO_EV_TYPE_GESTURE: + switch (info) { + case IIO_EV_INFO_VALUE: + if (val < 0 || val > 7) + return -EINVAL; + + return regmap_update_bits(data->regmap, + BMA400_TAP_CONFIG_REG, + BMA400_TAP_CONFIG_SEN_MASK, + FIELD_PREP(BMA400_TAP_CONFIG_SEN_MASK, + val)); + case IIO_EV_INFO_RESET_TIMEOUT: + raw = usec_to_tapreg_raw(val2, tap_reset_timeout); + if (raw < 0) + return -EINVAL; + + return regmap_update_bits(data->regmap, + BMA400_TAP_CONFIG1_REG, + BMA400_TAP_CONFIG1_QUIET_MASK, + FIELD_PREP(BMA400_TAP_CONFIG1_QUIET_MASK, + raw)); + case IIO_EV_INFO_TAP2_MIN_DELAY: + raw = usec_to_tapreg_raw(val2, double_tap2_min_delay); + if (raw < 0) + return -EINVAL; + + return regmap_update_bits(data->regmap, + BMA400_TAP_CONFIG1_REG, + BMA400_TAP_CONFIG1_QUIETDT_MASK, + FIELD_PREP(BMA400_TAP_CONFIG1_QUIETDT_MASK, + raw)); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int bma400_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct bma400_data *data = iio_priv(indio_dev); + int ret; + + ret = regmap_update_bits(data->regmap, BMA400_INT_CONFIG0_REG, + BMA400_INT_CONFIG0_DRDY_MASK, + FIELD_PREP(BMA400_INT_CONFIG0_DRDY_MASK, state)); + if (ret) + return ret; + + return regmap_update_bits(data->regmap, BMA400_INT1_MAP_REG, + BMA400_INT_CONFIG0_DRDY_MASK, + FIELD_PREP(BMA400_INT_CONFIG0_DRDY_MASK, state)); +} + +static const unsigned long bma400_avail_scan_masks[] = { + BIT(BMA400_ACCL_X) | BIT(BMA400_ACCL_Y) | BIT(BMA400_ACCL_Z), + BIT(BMA400_ACCL_X) | BIT(BMA400_ACCL_Y) | BIT(BMA400_ACCL_Z) + | BIT(BMA400_TEMP), + 0 +}; + +static const struct iio_info bma400_info = { + .read_raw = bma400_read_raw, + .read_avail = bma400_read_avail, + .write_raw = bma400_write_raw, + .write_raw_get_fmt = bma400_write_raw_get_fmt, + .read_event_config = bma400_read_event_config, + .write_event_config = bma400_write_event_config, + .write_event_value = bma400_write_event_value, + .read_event_value = bma400_read_event_value, + .event_attrs = &bma400_event_attribute_group, +}; + +static const struct iio_trigger_ops bma400_trigger_ops = { + .set_trigger_state = &bma400_data_rdy_trigger_set_state, + .validate_device = &iio_trigger_validate_own_device, +}; + +static irqreturn_t bma400_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bma400_data *data = iio_priv(indio_dev); + int ret, temp; + + /* Lock to protect the data->buffer */ + mutex_lock(&data->mutex); + + /* bulk read six registers, with the base being the LSB register */ + ret = regmap_bulk_read(data->regmap, BMA400_ACC_X_LSB_REG, + &data->buffer.buff, sizeof(data->buffer.buff)); + if (ret) + goto unlock_err; + + if (test_bit(BMA400_TEMP, indio_dev->active_scan_mask)) { + ret = regmap_read(data->regmap, BMA400_TEMP_DATA_REG, &temp); + if (ret) + goto unlock_err; + + data->buffer.temperature = temp; + } + + iio_push_to_buffers_with_ts(indio_dev, &data->buffer, + sizeof(data->buffer), + iio_get_time_ns(indio_dev)); + + mutex_unlock(&data->mutex); + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; + +unlock_err: + mutex_unlock(&data->mutex); + return IRQ_NONE; +} + +static irqreturn_t bma400_interrupt(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct bma400_data *data = iio_priv(indio_dev); + s64 timestamp = iio_get_time_ns(indio_dev); + unsigned int act, ev_dir = IIO_EV_DIR_NONE; + int ret; + + /* Lock to protect the data->status */ + mutex_lock(&data->mutex); + ret = regmap_bulk_read(data->regmap, BMA400_INT_STAT0_REG, + &data->status, + sizeof(data->status)); + /* + * if none of the bit is set in the status register then it is + * spurious interrupt. + */ + if (ret || !data->status) + goto unlock_err; + + /* + * Disable all advance interrupts if interrupt engine overrun occurs. + * See section 4.7 "Interrupt engine overrun" in datasheet v1.2. + */ + if (FIELD_GET(BMA400_INT_STAT_ENG_OVRRUN_MASK, le16_to_cpu(data->status))) { + bma400_disable_adv_interrupt(data); + dev_err(data->dev, "Interrupt engine overrun\n"); + goto unlock_err; + } + + if (FIELD_GET(BMA400_INT_STAT1_S_TAP_MASK, le16_to_cpu(data->status))) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_GESTURE, + IIO_EV_DIR_SINGLETAP), + timestamp); + + if (FIELD_GET(BMA400_INT_STAT1_D_TAP_MASK, le16_to_cpu(data->status))) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_GESTURE, + IIO_EV_DIR_DOUBLETAP), + timestamp); + + if (FIELD_GET(BMA400_INT_STAT0_GEN1_MASK, le16_to_cpu(data->status))) + ev_dir = IIO_EV_DIR_RISING; + + if (FIELD_GET(BMA400_INT_STAT0_GEN2_MASK, le16_to_cpu(data->status))) + ev_dir = IIO_EV_DIR_FALLING; + + if (ev_dir != IIO_EV_DIR_NONE) { + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_MAG, ev_dir), + timestamp); + } + + if (FIELD_GET(BMA400_INT_STAT1_STEP_INT_MASK, le16_to_cpu(data->status))) { + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_STEPS, 0, IIO_NO_MOD, + IIO_EV_TYPE_CHANGE, + IIO_EV_DIR_NONE), + timestamp); + + if (data->activity_event_en) { + ret = regmap_read(data->regmap, BMA400_STEP_STAT_REG, + &act); + if (ret) + goto unlock_err; + + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACTIVITY, 0, + bma400_act_to_mod(act), + IIO_EV_TYPE_CHANGE, + IIO_EV_DIR_NONE), + timestamp); + } + } + + if (FIELD_GET(BMA400_INT_STAT0_DRDY_MASK, le16_to_cpu(data->status))) { + mutex_unlock(&data->mutex); + iio_trigger_poll_nested(data->trig); + return IRQ_HANDLED; + } + + mutex_unlock(&data->mutex); + return IRQ_HANDLED; + +unlock_err: + mutex_unlock(&data->mutex); + return IRQ_NONE; +} + +int bma400_probe(struct device *dev, struct regmap *regmap, int irq, + const char *name) +{ + struct iio_dev *indio_dev; + struct bma400_data *data; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->regmap = regmap; + data->dev = dev; + + ret = bma400_init(data); + if (ret) + return ret; + + ret = iio_read_mount_matrix(dev, &data->orientation); + if (ret) + return ret; + + mutex_init(&data->mutex); + indio_dev->name = name; + indio_dev->info = &bma400_info; + indio_dev->channels = bma400_channels; + indio_dev->num_channels = ARRAY_SIZE(bma400_channels); + indio_dev->available_scan_masks = bma400_avail_scan_masks; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (irq > 0) { + data->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->trig) + return -ENOMEM; + + data->trig->ops = &bma400_trigger_ops; + iio_trigger_set_drvdata(data->trig, indio_dev); + + ret = devm_iio_trigger_register(data->dev, data->trig); + if (ret) + return dev_err_probe(data->dev, ret, + "iio trigger register fail\n"); + + indio_dev->trig = iio_trigger_get(data->trig); + ret = devm_request_threaded_irq(dev, irq, NULL, + &bma400_interrupt, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + indio_dev->name, indio_dev); + if (ret) + return ret; + } + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + &bma400_trigger_handler, NULL); + if (ret) + return dev_err_probe(data->dev, ret, + "iio triggered buffer setup failed\n"); + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS(bma400_probe, "IIO_BMA400"); + +MODULE_AUTHOR("Dan Robertson "); +MODULE_AUTHOR("Jagath Jog J "); +MODULE_DESCRIPTION("Bosch BMA400 triaxial acceleration sensor core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/accel/bma400_i2c.c b/drivers/iio/accel/bma400_i2c.c new file mode 100644 index 000000000..a3fb3b81b --- /dev/null +++ b/drivers/iio/accel/bma400_i2c.c @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * I2C IIO driver for Bosch BMA400 triaxial acceleration sensor. + * + * Copyright 2019 Dan Robertson + * + * I2C address is either 0x14 or 0x15 depending on SDO + */ +#include +#include +#include + +#include "bma400.h" + +static int bma400_i2c_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, &bma400_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "failed to create regmap\n"); + return PTR_ERR(regmap); + } + + return bma400_probe(&client->dev, regmap, client->irq, id->name); +} + +static const struct i2c_device_id bma400_i2c_ids[] = { + { .name = "bma400" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bma400_i2c_ids); + +static const struct of_device_id bma400_of_i2c_match[] = { + { .compatible = "bosch,bma400" }, + { } +}; +MODULE_DEVICE_TABLE(of, bma400_of_i2c_match); + +static struct i2c_driver bma400_i2c_driver = { + .driver = { + .name = "bma400", + .of_match_table = bma400_of_i2c_match, + }, + .probe = bma400_i2c_probe, + .id_table = bma400_i2c_ids, +}; + +module_i2c_driver(bma400_i2c_driver); + +MODULE_AUTHOR("Dan Robertson "); +MODULE_DESCRIPTION("Bosch BMA400 triaxial acceleration sensor (I2C)"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_BMA400"); diff --git a/drivers/iio/accel/bma400_spi.c b/drivers/iio/accel/bma400_spi.c new file mode 100644 index 000000000..7ac0a7881 --- /dev/null +++ b/drivers/iio/accel/bma400_spi.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * SPI IIO driver for Bosch BMA400 triaxial acceleration sensor. + * + * Copyright 2020 Dan Robertson + * + */ +#include +#include +#include +#include +#include + +#include "bma400.h" + +#define BMA400_MAX_SPI_READ 2 +#define BMA400_SPI_READ_BUFFER_SIZE (BMA400_MAX_SPI_READ + 1) + +static int bma400_regmap_spi_read(void *context, + const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + struct device *dev = context; + struct spi_device *spi = to_spi_device(dev); + u8 result[BMA400_SPI_READ_BUFFER_SIZE]; + ssize_t status; + + if (val_size > BMA400_MAX_SPI_READ) + return -EINVAL; + + status = spi_write_then_read(spi, reg, 1, result, val_size + 1); + if (status) + return status; + + /* + * From the BMA400 datasheet: + * + * > For a basic read operation two bytes have to be read and the first + * > has to be dropped and the second byte must be interpreted. + */ + memcpy(val, result + 1, val_size); + + return 0; +} + +static int bma400_regmap_spi_write(void *context, const void *data, + size_t count) +{ + struct device *dev = context; + struct spi_device *spi = to_spi_device(dev); + + return spi_write(spi, data, count); +} + +static const struct regmap_bus bma400_regmap_bus = { + .read = bma400_regmap_spi_read, + .write = bma400_regmap_spi_write, + .read_flag_mask = BIT(7), + .max_raw_read = BMA400_MAX_SPI_READ, +}; + +static int bma400_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct regmap *regmap; + unsigned int val; + int ret; + + regmap = devm_regmap_init(&spi->dev, &bma400_regmap_bus, + &spi->dev, &bma400_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "failed to create regmap\n"); + return PTR_ERR(regmap); + } + + /* + * Per the bma400 datasheet, the first SPI read may + * return garbage. As the datasheet recommends, the + * chip ID register will be read here and checked + * again in the following probe. + */ + ret = regmap_read(regmap, BMA400_CHIP_ID_REG, &val); + if (ret) + dev_err(&spi->dev, "Failed to read chip id register\n"); + + return bma400_probe(&spi->dev, regmap, spi->irq, id->name); +} + +static const struct spi_device_id bma400_spi_ids[] = { + { .name = "bma400" }, + { } +}; +MODULE_DEVICE_TABLE(spi, bma400_spi_ids); + +static const struct of_device_id bma400_of_spi_match[] = { + { .compatible = "bosch,bma400" }, + { } +}; +MODULE_DEVICE_TABLE(of, bma400_of_spi_match); + +static struct spi_driver bma400_spi_driver = { + .driver = { + .name = "bma400", + .of_match_table = bma400_of_spi_match, + }, + .probe = bma400_spi_probe, + .id_table = bma400_spi_ids, +}; + +module_spi_driver(bma400_spi_driver); +MODULE_AUTHOR("Dan Robertson "); +MODULE_DESCRIPTION("Bosch BMA400 triaxial acceleration sensor (SPI)"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_BMA400"); diff --git a/drivers/iio/accel/bmc150-accel-core.c b/drivers/iio/accel/bmc150-accel-core.c new file mode 100644 index 000000000..46bb36d93 --- /dev/null +++ b/drivers/iio/accel/bmc150-accel-core.c @@ -0,0 +1,1868 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 3-axis accelerometer driver supporting many Bosch-Sensortec chips + * Copyright (c) 2014, Intel Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bmc150-accel.h" + +#define BMC150_ACCEL_REG_CHIP_ID 0x00 + +#define BMC150_ACCEL_REG_INT_STATUS_2 0x0B +#define BMC150_ACCEL_ANY_MOTION_MASK 0x07 +#define BMC150_ACCEL_ANY_MOTION_BIT_X BIT(0) +#define BMC150_ACCEL_ANY_MOTION_BIT_Y BIT(1) +#define BMC150_ACCEL_ANY_MOTION_BIT_Z BIT(2) +#define BMC150_ACCEL_ANY_MOTION_BIT_SIGN BIT(3) + +#define BMC150_ACCEL_REG_PMU_LPW 0x11 +#define BMC150_ACCEL_PMU_MODE_MASK 0xE0 +#define BMC150_ACCEL_PMU_MODE_SHIFT 5 +#define BMC150_ACCEL_PMU_BIT_SLEEP_DUR_MASK 0x17 +#define BMC150_ACCEL_PMU_BIT_SLEEP_DUR_SHIFT 1 + +#define BMC150_ACCEL_REG_PMU_RANGE 0x0F + +#define BMC150_ACCEL_DEF_RANGE_2G 0x03 +#define BMC150_ACCEL_DEF_RANGE_4G 0x05 +#define BMC150_ACCEL_DEF_RANGE_8G 0x08 +#define BMC150_ACCEL_DEF_RANGE_16G 0x0C + +/* Default BW: 125Hz */ +#define BMC150_ACCEL_REG_PMU_BW 0x10 +#define BMC150_ACCEL_DEF_BW 125 + +#define BMC150_ACCEL_REG_RESET 0x14 +#define BMC150_ACCEL_RESET_VAL 0xB6 + +#define BMC150_ACCEL_REG_INT_MAP_0 0x19 +#define BMC150_ACCEL_INT_MAP_0_BIT_INT1_SLOPE BIT(2) + +#define BMC150_ACCEL_REG_INT_MAP_1 0x1A +#define BMC150_ACCEL_INT_MAP_1_BIT_INT1_DATA BIT(0) +#define BMC150_ACCEL_INT_MAP_1_BIT_INT1_FWM BIT(1) +#define BMC150_ACCEL_INT_MAP_1_BIT_INT1_FFULL BIT(2) +#define BMC150_ACCEL_INT_MAP_1_BIT_INT2_FFULL BIT(5) +#define BMC150_ACCEL_INT_MAP_1_BIT_INT2_FWM BIT(6) +#define BMC150_ACCEL_INT_MAP_1_BIT_INT2_DATA BIT(7) + +#define BMC150_ACCEL_REG_INT_MAP_2 0x1B +#define BMC150_ACCEL_INT_MAP_2_BIT_INT2_SLOPE BIT(2) + +#define BMC150_ACCEL_REG_INT_RST_LATCH 0x21 +#define BMC150_ACCEL_INT_MODE_LATCH_RESET 0x80 +#define BMC150_ACCEL_INT_MODE_LATCH_INT 0x0F +#define BMC150_ACCEL_INT_MODE_NON_LATCH_INT 0x00 + +#define BMC150_ACCEL_REG_INT_EN_0 0x16 +#define BMC150_ACCEL_INT_EN_BIT_SLP_X BIT(0) +#define BMC150_ACCEL_INT_EN_BIT_SLP_Y BIT(1) +#define BMC150_ACCEL_INT_EN_BIT_SLP_Z BIT(2) + +#define BMC150_ACCEL_REG_INT_EN_1 0x17 +#define BMC150_ACCEL_INT_EN_BIT_DATA_EN BIT(4) +#define BMC150_ACCEL_INT_EN_BIT_FFULL_EN BIT(5) +#define BMC150_ACCEL_INT_EN_BIT_FWM_EN BIT(6) + +#define BMC150_ACCEL_REG_INT_OUT_CTRL 0x20 +#define BMC150_ACCEL_INT_OUT_CTRL_INT1_LVL BIT(0) +#define BMC150_ACCEL_INT_OUT_CTRL_INT2_LVL BIT(2) + +#define BMC150_ACCEL_REG_INT_5 0x27 +#define BMC150_ACCEL_SLOPE_DUR_MASK 0x03 + +#define BMC150_ACCEL_REG_INT_6 0x28 +#define BMC150_ACCEL_SLOPE_THRES_MASK 0xFF + +/* Slope duration in terms of number of samples */ +#define BMC150_ACCEL_DEF_SLOPE_DURATION 1 +/* in terms of multiples of g's/LSB, based on range */ +#define BMC150_ACCEL_DEF_SLOPE_THRESHOLD 1 + +#define BMC150_ACCEL_REG_XOUT_L 0x02 + +#define BMC150_ACCEL_MAX_STARTUP_TIME_MS 100 + +/* Sleep Duration values */ +#define BMC150_ACCEL_SLEEP_500_MICRO 0x05 +#define BMC150_ACCEL_SLEEP_1_MS 0x06 +#define BMC150_ACCEL_SLEEP_2_MS 0x07 +#define BMC150_ACCEL_SLEEP_4_MS 0x08 +#define BMC150_ACCEL_SLEEP_6_MS 0x09 +#define BMC150_ACCEL_SLEEP_10_MS 0x0A +#define BMC150_ACCEL_SLEEP_25_MS 0x0B +#define BMC150_ACCEL_SLEEP_50_MS 0x0C +#define BMC150_ACCEL_SLEEP_100_MS 0x0D +#define BMC150_ACCEL_SLEEP_500_MS 0x0E +#define BMC150_ACCEL_SLEEP_1_SEC 0x0F + +#define BMC150_ACCEL_REG_TEMP 0x08 +#define BMC150_ACCEL_TEMP_CENTER_VAL 23 + +#define BMC150_ACCEL_AXIS_TO_REG(axis) (BMC150_ACCEL_REG_XOUT_L + (axis * 2)) +#define BMC150_AUTO_SUSPEND_DELAY_MS 2000 + +#define BMC150_ACCEL_REG_FIFO_STATUS 0x0E +#define BMC150_ACCEL_REG_FIFO_CONFIG0 0x30 +#define BMC150_ACCEL_REG_FIFO_CONFIG1 0x3E +#define BMC150_ACCEL_REG_FIFO_DATA 0x3F +#define BMC150_ACCEL_FIFO_LENGTH 32 + +enum bmc150_accel_axis { + AXIS_X, + AXIS_Y, + AXIS_Z, + AXIS_MAX, +}; + +enum bmc150_power_modes { + BMC150_ACCEL_SLEEP_MODE_NORMAL, + BMC150_ACCEL_SLEEP_MODE_DEEP_SUSPEND, + BMC150_ACCEL_SLEEP_MODE_LPM, + BMC150_ACCEL_SLEEP_MODE_SUSPEND = 0x04, +}; + +struct bmc150_scale_info { + int scale; + u8 reg_range; +}; + +struct bmc150_accel_chip_info { + const char *name; + u8 chip_id; + const struct iio_chan_spec *channels; + int num_channels; + const struct bmc150_scale_info scale_table[4]; +}; + +static const struct { + int val; + int val2; + u8 bw_bits; +} bmc150_accel_samp_freq_table[] = { {15, 620000, 0x08}, + {31, 260000, 0x09}, + {62, 500000, 0x0A}, + {125, 0, 0x0B}, + {250, 0, 0x0C}, + {500, 0, 0x0D}, + {1000, 0, 0x0E}, + {2000, 0, 0x0F} }; + +static __maybe_unused const struct { + int bw_bits; + int msec; +} bmc150_accel_sample_upd_time[] = { {0x08, 64}, + {0x09, 32}, + {0x0A, 16}, + {0x0B, 8}, + {0x0C, 4}, + {0x0D, 2}, + {0x0E, 1}, + {0x0F, 1} }; + +static const struct { + int sleep_dur; + u8 reg_value; +} bmc150_accel_sleep_value_table[] = { {0, 0}, + {500, BMC150_ACCEL_SLEEP_500_MICRO}, + {1000, BMC150_ACCEL_SLEEP_1_MS}, + {2000, BMC150_ACCEL_SLEEP_2_MS}, + {4000, BMC150_ACCEL_SLEEP_4_MS}, + {6000, BMC150_ACCEL_SLEEP_6_MS}, + {10000, BMC150_ACCEL_SLEEP_10_MS}, + {25000, BMC150_ACCEL_SLEEP_25_MS}, + {50000, BMC150_ACCEL_SLEEP_50_MS}, + {100000, BMC150_ACCEL_SLEEP_100_MS}, + {500000, BMC150_ACCEL_SLEEP_500_MS}, + {1000000, BMC150_ACCEL_SLEEP_1_SEC} }; + +const struct regmap_config bmc150_regmap_conf = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x3f, +}; +EXPORT_SYMBOL_NS_GPL(bmc150_regmap_conf, "IIO_BMC150"); + +static int bmc150_accel_set_mode(struct bmc150_accel_data *data, + enum bmc150_power_modes mode, + int dur_us) +{ + struct device *dev = regmap_get_device(data->regmap); + int i; + int ret; + u8 lpw_bits; + int dur_val = -1; + + if (dur_us > 0) { + for (i = 0; i < ARRAY_SIZE(bmc150_accel_sleep_value_table); + ++i) { + if (bmc150_accel_sleep_value_table[i].sleep_dur == + dur_us) + dur_val = + bmc150_accel_sleep_value_table[i].reg_value; + } + } else { + dur_val = 0; + } + + if (dur_val < 0) + return -EINVAL; + + lpw_bits = mode << BMC150_ACCEL_PMU_MODE_SHIFT; + lpw_bits |= (dur_val << BMC150_ACCEL_PMU_BIT_SLEEP_DUR_SHIFT); + + dev_dbg(dev, "Set Mode bits %x\n", lpw_bits); + + ret = regmap_write(data->regmap, BMC150_ACCEL_REG_PMU_LPW, lpw_bits); + if (ret < 0) { + dev_err(dev, "Error writing reg_pmu_lpw\n"); + return ret; + } + + return 0; +} + +static int bmc150_accel_set_bw(struct bmc150_accel_data *data, int val, + int val2) +{ + int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(bmc150_accel_samp_freq_table); ++i) { + if (bmc150_accel_samp_freq_table[i].val == val && + bmc150_accel_samp_freq_table[i].val2 == val2) { + ret = regmap_write(data->regmap, + BMC150_ACCEL_REG_PMU_BW, + bmc150_accel_samp_freq_table[i].bw_bits); + if (ret < 0) + return ret; + + data->bw_bits = + bmc150_accel_samp_freq_table[i].bw_bits; + return 0; + } + } + + return -EINVAL; +} + +static int bmc150_accel_update_slope(struct bmc150_accel_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = regmap_write(data->regmap, BMC150_ACCEL_REG_INT_6, + data->slope_thres); + if (ret < 0) { + dev_err(dev, "Error writing reg_int_6\n"); + return ret; + } + + ret = regmap_update_bits(data->regmap, BMC150_ACCEL_REG_INT_5, + BMC150_ACCEL_SLOPE_DUR_MASK, data->slope_dur); + if (ret < 0) { + dev_err(dev, "Error updating reg_int_5\n"); + return ret; + } + + dev_dbg(dev, "%x %x\n", data->slope_thres, data->slope_dur); + + return ret; +} + +static int bmc150_accel_any_motion_setup(struct bmc150_accel_trigger *t, + bool state) +{ + if (state) + return bmc150_accel_update_slope(t->data); + + return 0; +} + +static int bmc150_accel_get_bw(struct bmc150_accel_data *data, int *val, + int *val2) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bmc150_accel_samp_freq_table); ++i) { + if (bmc150_accel_samp_freq_table[i].bw_bits == data->bw_bits) { + *val = bmc150_accel_samp_freq_table[i].val; + *val2 = bmc150_accel_samp_freq_table[i].val2; + return IIO_VAL_INT_PLUS_MICRO; + } + } + + return -EINVAL; +} + +#ifdef CONFIG_PM +static int bmc150_accel_get_startup_times(struct bmc150_accel_data *data) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bmc150_accel_sample_upd_time); ++i) { + if (bmc150_accel_sample_upd_time[i].bw_bits == data->bw_bits) + return bmc150_accel_sample_upd_time[i].msec; + } + + return BMC150_ACCEL_MAX_STARTUP_TIME_MS; +} + +static int bmc150_accel_set_power_state(struct bmc150_accel_data *data, bool on) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + + if (on) + ret = pm_runtime_resume_and_get(dev); + else + ret = pm_runtime_put_autosuspend(dev); + if (ret < 0) { + dev_err(dev, + "Failed: %s for %d\n", __func__, on); + return ret; + } + + return 0; +} +#else +static int bmc150_accel_set_power_state(struct bmc150_accel_data *data, bool on) +{ + return 0; +} +#endif + +#ifdef CONFIG_ACPI +/* + * Support for getting accelerometer information from BOSC0200 ACPI nodes. + * + * There are 2 variants of the BOSC0200 ACPI node. Some 2-in-1s with 360 degree + * hinges declare 2 I2C ACPI-resources for 2 accelerometers, 1 in the display + * and 1 in the base of the 2-in-1. On these 2-in-1s the ROMS ACPI object + * contains the mount-matrix for the sensor in the display and ROMK contains + * the mount-matrix for the sensor in the base. On devices using a single + * sensor there is a ROTM ACPI object which contains the mount-matrix. + * + * Here is an incomplete list of devices known to use 1 of these setups: + * + * Yoga devices with 2 accelerometers using ROMS + ROMK for the mount-matrices: + * Lenovo Thinkpad Yoga 11e 3th gen + * Lenovo Thinkpad Yoga 11e 4th gen + * + * Tablets using a single accelerometer using ROTM for the mount-matrix: + * Chuwi Hi8 Pro (CWI513) + * Chuwi Vi8 Plus (CWI519) + * Chuwi Hi13 + * Irbis TW90 + * Jumper EZpad mini 3 + * Onda V80 plus + * Predia Basic Tablet + */ +static bool bmc150_apply_bosc0200_acpi_orientation(struct device *dev, + struct iio_mount_matrix *orientation) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + acpi_handle handle = ACPI_HANDLE(dev); + char *name, *alt_name, *label; + + if (strcmp(dev_name(dev), "i2c-BOSC0200:base") == 0) { + alt_name = "ROMK"; + label = "accel-base"; + } else { + alt_name = "ROMS"; + label = "accel-display"; + } + + if (acpi_has_method(handle, "ROTM")) { + name = "ROTM"; + } else if (acpi_has_method(handle, alt_name)) { + name = alt_name; + indio_dev->label = label; + } else { + return false; + } + + return iio_read_acpi_mount_matrix(dev, orientation, name); +} + +static bool bmc150_apply_dual250e_acpi_orientation(struct device *dev, + struct iio_mount_matrix *orientation) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + if (strcmp(dev_name(dev), "i2c-DUAL250E:base") == 0) + indio_dev->label = "accel-base"; + else + indio_dev->label = "accel-display"; + + return false; /* DUAL250E fwnodes have no mount matrix info */ +} + +static bool bmc150_apply_acpi_orientation(struct device *dev, + struct iio_mount_matrix *orientation) +{ + struct acpi_device *adev = ACPI_COMPANION(dev); + + if (adev && acpi_dev_hid_uid_match(adev, "BOSC0200", NULL)) + return bmc150_apply_bosc0200_acpi_orientation(dev, orientation); + + if (adev && acpi_dev_hid_uid_match(adev, "DUAL250E", NULL)) + return bmc150_apply_dual250e_acpi_orientation(dev, orientation); + + return false; +} +#else +static bool bmc150_apply_acpi_orientation(struct device *dev, + struct iio_mount_matrix *orientation) +{ + return false; +} +#endif + +struct bmc150_accel_interrupt_info { + u8 map_reg; + u8 map_bitmask; + u8 en_reg; + u8 en_bitmask; +}; + +static const struct bmc150_accel_interrupt_info +bmc150_accel_interrupts_int1[BMC150_ACCEL_INTERRUPTS] = { + { /* data ready interrupt */ + .map_reg = BMC150_ACCEL_REG_INT_MAP_1, + .map_bitmask = BMC150_ACCEL_INT_MAP_1_BIT_INT1_DATA, + .en_reg = BMC150_ACCEL_REG_INT_EN_1, + .en_bitmask = BMC150_ACCEL_INT_EN_BIT_DATA_EN, + }, + { /* motion interrupt */ + .map_reg = BMC150_ACCEL_REG_INT_MAP_0, + .map_bitmask = BMC150_ACCEL_INT_MAP_0_BIT_INT1_SLOPE, + .en_reg = BMC150_ACCEL_REG_INT_EN_0, + .en_bitmask = BMC150_ACCEL_INT_EN_BIT_SLP_X | + BMC150_ACCEL_INT_EN_BIT_SLP_Y | + BMC150_ACCEL_INT_EN_BIT_SLP_Z + }, + { /* fifo watermark interrupt */ + .map_reg = BMC150_ACCEL_REG_INT_MAP_1, + .map_bitmask = BMC150_ACCEL_INT_MAP_1_BIT_INT1_FWM, + .en_reg = BMC150_ACCEL_REG_INT_EN_1, + .en_bitmask = BMC150_ACCEL_INT_EN_BIT_FWM_EN, + }, +}; + +static const struct bmc150_accel_interrupt_info +bmc150_accel_interrupts_int2[BMC150_ACCEL_INTERRUPTS] = { + { /* data ready interrupt */ + .map_reg = BMC150_ACCEL_REG_INT_MAP_1, + .map_bitmask = BMC150_ACCEL_INT_MAP_1_BIT_INT2_DATA, + .en_reg = BMC150_ACCEL_REG_INT_EN_1, + .en_bitmask = BMC150_ACCEL_INT_EN_BIT_DATA_EN, + }, + { /* motion interrupt */ + .map_reg = BMC150_ACCEL_REG_INT_MAP_2, + .map_bitmask = BMC150_ACCEL_INT_MAP_2_BIT_INT2_SLOPE, + .en_reg = BMC150_ACCEL_REG_INT_EN_0, + .en_bitmask = BMC150_ACCEL_INT_EN_BIT_SLP_X | + BMC150_ACCEL_INT_EN_BIT_SLP_Y | + BMC150_ACCEL_INT_EN_BIT_SLP_Z + }, + { /* fifo watermark interrupt */ + .map_reg = BMC150_ACCEL_REG_INT_MAP_1, + .map_bitmask = BMC150_ACCEL_INT_MAP_1_BIT_INT2_FWM, + .en_reg = BMC150_ACCEL_REG_INT_EN_1, + .en_bitmask = BMC150_ACCEL_INT_EN_BIT_FWM_EN, + }, +}; + +static void bmc150_accel_interrupts_setup(struct iio_dev *indio_dev, + struct bmc150_accel_data *data, int irq) +{ + const struct bmc150_accel_interrupt_info *irq_info = NULL; + struct device *dev = regmap_get_device(data->regmap); + int i; + + /* + * For now we map all interrupts to the same output pin. + * However, some boards may have just INT2 (and not INT1) connected, + * so we try to detect which IRQ it is based on the interrupt-names. + * Without interrupt-names, we assume the irq belongs to INT1. + */ + irq_info = bmc150_accel_interrupts_int1; + if (data->type == BOSCH_BMC156 || + irq == fwnode_irq_get_byname(dev_fwnode(dev), "INT2")) + irq_info = bmc150_accel_interrupts_int2; + + for (i = 0; i < BMC150_ACCEL_INTERRUPTS; i++) + data->interrupts[i].info = &irq_info[i]; +} + +static int bmc150_accel_set_interrupt(struct bmc150_accel_data *data, int i, + bool state) +{ + struct device *dev = regmap_get_device(data->regmap); + struct bmc150_accel_interrupt *intr = &data->interrupts[i]; + const struct bmc150_accel_interrupt_info *info = intr->info; + int ret; + + /* We do not always have an IRQ */ + if (data->irq <= 0) + return 0; + + if (state) { + if (atomic_inc_return(&intr->users) > 1) + return 0; + } else { + if (atomic_dec_return(&intr->users) > 0) + return 0; + } + + /* + * We will expect the enable and disable to do operation in reverse + * order. This will happen here anyway, as our resume operation uses + * sync mode runtime pm calls. The suspend operation will be delayed + * by autosuspend delay. + * So the disable operation will still happen in reverse order of + * enable operation. When runtime pm is disabled the mode is always on, + * so sequence doesn't matter. + */ + ret = bmc150_accel_set_power_state(data, state); + if (ret < 0) + return ret; + + /* map the interrupt to the appropriate pins */ + ret = regmap_update_bits(data->regmap, info->map_reg, info->map_bitmask, + (state ? info->map_bitmask : 0)); + if (ret < 0) { + dev_err(dev, "Error updating reg_int_map\n"); + goto out_fix_power_state; + } + + /* enable/disable the interrupt */ + ret = regmap_update_bits(data->regmap, info->en_reg, info->en_bitmask, + (state ? info->en_bitmask : 0)); + if (ret < 0) { + dev_err(dev, "Error updating reg_int_en\n"); + goto out_fix_power_state; + } + + return 0; + +out_fix_power_state: + bmc150_accel_set_power_state(data, false); + return ret; +} + +static int bmc150_accel_set_scale(struct bmc150_accel_data *data, int val) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret, i; + + for (i = 0; i < ARRAY_SIZE(data->chip_info->scale_table); ++i) { + if (data->chip_info->scale_table[i].scale == val) { + ret = regmap_write(data->regmap, + BMC150_ACCEL_REG_PMU_RANGE, + data->chip_info->scale_table[i].reg_range); + if (ret < 0) { + dev_err(dev, "Error writing pmu_range\n"); + return ret; + } + + data->range = data->chip_info->scale_table[i].reg_range; + return 0; + } + } + + return -EINVAL; +} + +static int bmc150_accel_get_temp(struct bmc150_accel_data *data, int *val) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + unsigned int value; + + mutex_lock(&data->mutex); + + ret = regmap_read(data->regmap, BMC150_ACCEL_REG_TEMP, &value); + if (ret < 0) { + dev_err(dev, "Error reading reg_temp\n"); + mutex_unlock(&data->mutex); + return ret; + } + *val = sign_extend32(value, 7); + + mutex_unlock(&data->mutex); + + return IIO_VAL_INT; +} + +static int bmc150_accel_get_axis(struct bmc150_accel_data *data, + struct iio_chan_spec const *chan, + int *val) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + int axis = chan->scan_index; + __le16 raw_val; + + mutex_lock(&data->mutex); + ret = bmc150_accel_set_power_state(data, true); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + + ret = regmap_bulk_read(data->regmap, BMC150_ACCEL_AXIS_TO_REG(axis), + &raw_val, sizeof(raw_val)); + if (ret < 0) { + dev_err(dev, "Error reading axis %d\n", axis); + bmc150_accel_set_power_state(data, false); + mutex_unlock(&data->mutex); + return ret; + } + *val = sign_extend32(le16_to_cpu(raw_val) >> chan->scan_type.shift, + chan->scan_type.realbits - 1); + ret = bmc150_accel_set_power_state(data, false); + mutex_unlock(&data->mutex); + if (ret < 0) + return ret; + + return IIO_VAL_INT; +} + +static int bmc150_accel_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_TEMP: + return bmc150_accel_get_temp(data, val); + case IIO_ACCEL: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + else + return bmc150_accel_get_axis(data, chan, val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + if (chan->type == IIO_TEMP) { + *val = BMC150_ACCEL_TEMP_CENTER_VAL; + return IIO_VAL_INT; + } else { + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + *val = 0; + switch (chan->type) { + case IIO_TEMP: + *val2 = 500000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ACCEL: + { + int i; + const struct bmc150_scale_info *si; + int st_size = ARRAY_SIZE(data->chip_info->scale_table); + + for (i = 0; i < st_size; ++i) { + si = &data->chip_info->scale_table[i]; + if (si->reg_range == data->range) { + *val2 = si->scale; + return IIO_VAL_INT_PLUS_MICRO; + } + } + return -EINVAL; + } + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + mutex_lock(&data->mutex); + ret = bmc150_accel_get_bw(data, val, val2); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } +} + +static int bmc150_accel_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + mutex_lock(&data->mutex); + ret = bmc150_accel_set_bw(data, val, val2); + mutex_unlock(&data->mutex); + break; + case IIO_CHAN_INFO_SCALE: + if (val) + return -EINVAL; + + mutex_lock(&data->mutex); + ret = bmc150_accel_set_scale(data, val2); + mutex_unlock(&data->mutex); + return ret; + default: + ret = -EINVAL; + } + + return ret; +} + +static int bmc150_accel_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + + *val2 = 0; + switch (info) { + case IIO_EV_INFO_VALUE: + *val = data->slope_thres; + break; + case IIO_EV_INFO_PERIOD: + *val = data->slope_dur; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; +} + +static int bmc150_accel_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + + if (data->ev_enable_state) + return -EBUSY; + + switch (info) { + case IIO_EV_INFO_VALUE: + data->slope_thres = val & BMC150_ACCEL_SLOPE_THRES_MASK; + break; + case IIO_EV_INFO_PERIOD: + data->slope_dur = val & BMC150_ACCEL_SLOPE_DUR_MASK; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int bmc150_accel_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + + return data->ev_enable_state; +} + +static int bmc150_accel_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + int ret; + + if (state == data->ev_enable_state) + return 0; + + mutex_lock(&data->mutex); + + ret = bmc150_accel_set_interrupt(data, BMC150_ACCEL_INT_ANY_MOTION, + state); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + + data->ev_enable_state = state; + mutex_unlock(&data->mutex); + + return 0; +} + +static int bmc150_accel_validate_trigger(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + int i; + + for (i = 0; i < BMC150_ACCEL_TRIGGERS; i++) { + if (data->triggers[i].indio_trig == trig) + return 0; + } + + return -EINVAL; +} + +static ssize_t bmc150_accel_get_fifo_watermark(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct bmc150_accel_data *data = iio_priv(indio_dev); + int wm; + + mutex_lock(&data->mutex); + wm = data->watermark; + mutex_unlock(&data->mutex); + + return sysfs_emit(buf, "%d\n", wm); +} + +static ssize_t bmc150_accel_get_fifo_state(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct bmc150_accel_data *data = iio_priv(indio_dev); + bool state; + + mutex_lock(&data->mutex); + state = data->fifo_mode; + mutex_unlock(&data->mutex); + + return sysfs_emit(buf, "%d\n", state); +} + +static const struct iio_mount_matrix * +bmc150_accel_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + + return &data->orientation; +} + +static const struct iio_chan_spec_ext_info bmc150_accel_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, bmc150_accel_get_mount_matrix), + { } +}; + +IIO_STATIC_CONST_DEVICE_ATTR(hwfifo_watermark_min, "1"); +IIO_STATIC_CONST_DEVICE_ATTR(hwfifo_watermark_max, + __stringify(BMC150_ACCEL_FIFO_LENGTH)); +static IIO_DEVICE_ATTR(hwfifo_enabled, S_IRUGO, + bmc150_accel_get_fifo_state, NULL, 0); +static IIO_DEVICE_ATTR(hwfifo_watermark, S_IRUGO, + bmc150_accel_get_fifo_watermark, NULL, 0); + +static const struct iio_dev_attr *bmc150_accel_fifo_attributes[] = { + &iio_dev_attr_hwfifo_watermark_min, + &iio_dev_attr_hwfifo_watermark_max, + &iio_dev_attr_hwfifo_watermark, + &iio_dev_attr_hwfifo_enabled, + NULL, +}; + +static int bmc150_accel_set_watermark(struct iio_dev *indio_dev, unsigned val) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + + if (val > BMC150_ACCEL_FIFO_LENGTH) + val = BMC150_ACCEL_FIFO_LENGTH; + + mutex_lock(&data->mutex); + data->watermark = val; + mutex_unlock(&data->mutex); + + return 0; +} + +/* + * We must read at least one full frame in one burst, otherwise the rest of the + * frame data is discarded. + */ +static int bmc150_accel_fifo_transfer(struct bmc150_accel_data *data, + char *buffer, int samples) +{ + struct device *dev = regmap_get_device(data->regmap); + int sample_length = 3 * 2; + int ret; + int total_length = samples * sample_length; + + ret = regmap_raw_read(data->regmap, BMC150_ACCEL_REG_FIFO_DATA, + buffer, total_length); + if (ret) + dev_err(dev, + "Error transferring data from fifo: %d\n", ret); + + return ret; +} + +static int __bmc150_accel_fifo_flush(struct iio_dev *indio_dev, + unsigned samples, bool irq) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); + int ret, i; + u8 count; + u16 buffer[BMC150_ACCEL_FIFO_LENGTH * 3]; + int64_t tstamp; + uint64_t sample_period; + unsigned int val; + + ret = regmap_read(data->regmap, BMC150_ACCEL_REG_FIFO_STATUS, &val); + if (ret < 0) { + dev_err(dev, "Error reading reg_fifo_status\n"); + return ret; + } + + count = val & 0x7F; + + if (!count) + return 0; + + /* + * If we getting called from IRQ handler we know the stored timestamp is + * fairly accurate for the last stored sample. Otherwise, if we are + * called as a result of a read operation from userspace and hence + * before the watermark interrupt was triggered, take a timestamp + * now. We can fall anywhere in between two samples so the error in this + * case is at most one sample period. + */ + if (!irq) { + data->old_timestamp = data->timestamp; + data->timestamp = iio_get_time_ns(indio_dev); + } + + /* + * Approximate timestamps for each of the sample based on the sampling + * frequency, timestamp for last sample and number of samples. + * + * Note that we can't use the current bandwidth settings to compute the + * sample period because the sample rate varies with the device + * (e.g. between 31.70ms to 32.20ms for a bandwidth of 15.63HZ). That + * small variation adds when we store a large number of samples and + * creates significant jitter between the last and first samples in + * different batches (e.g. 32ms vs 21ms). + * + * To avoid this issue we compute the actual sample period ourselves + * based on the timestamp delta between the last two flush operations. + */ + sample_period = (data->timestamp - data->old_timestamp); + do_div(sample_period, count); + tstamp = data->timestamp - (count - 1) * sample_period; + + if (samples) + count = min3(count, samples, BMC150_ACCEL_FIFO_LENGTH); + else + count = min(count, BMC150_ACCEL_FIFO_LENGTH); + + count = min_t(u8, count, BMC150_ACCEL_FIFO_LENGTH); + + ret = bmc150_accel_fifo_transfer(data, (u8 *)buffer, count); + if (ret) + return ret; + + /* + * Ideally we want the IIO core to handle the demux when running in fifo + * mode but not when running in triggered buffer mode. Unfortunately + * this does not seem to be possible, so stick with driver demux for + * now. + */ + for (i = 0; i < count; i++) { + int j, bit; + + j = 0; + iio_for_each_active_channel(indio_dev, bit) + memcpy(&data->scan.channels[j++], &buffer[i * 3 + bit], + sizeof(data->scan.channels[0])); + + iio_push_to_buffers_with_timestamp(indio_dev, &data->scan, + tstamp); + + tstamp += sample_period; + } + + return count; +} + +static int bmc150_accel_fifo_flush(struct iio_dev *indio_dev, unsigned samples) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = __bmc150_accel_fifo_flush(indio_dev, samples, false); + mutex_unlock(&data->mutex); + + return ret; +} + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL( + "15.620000 31.260000 62.50000 125 250 500 1000 2000"); + +static struct attribute *bmc150_accel_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group bmc150_accel_attrs_group = { + .attrs = bmc150_accel_attributes, +}; + +static const struct iio_event_spec bmc150_accel_event = { + .type = IIO_EV_TYPE_ROC, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_PERIOD) +}; + +#define BMC150_ACCEL_CHANNEL(_axis, bits) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = AXIS_##_axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 16 - (bits), \ + .endianness = IIO_LE, \ + }, \ + .ext_info = bmc150_accel_ext_info, \ + .event_spec = &bmc150_accel_event, \ + .num_event_specs = 1 \ +} + +#define BMC150_ACCEL_CHANNELS(bits) { \ + { \ + .type = IIO_TEMP, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .scan_index = -1, \ + }, \ + BMC150_ACCEL_CHANNEL(X, bits), \ + BMC150_ACCEL_CHANNEL(Y, bits), \ + BMC150_ACCEL_CHANNEL(Z, bits), \ + IIO_CHAN_SOFT_TIMESTAMP(3), \ +} + +static const struct iio_chan_spec bma222e_accel_channels[] = + BMC150_ACCEL_CHANNELS(8); +static const struct iio_chan_spec bma250e_accel_channels[] = + BMC150_ACCEL_CHANNELS(10); +static const struct iio_chan_spec bmc150_accel_channels[] = + BMC150_ACCEL_CHANNELS(12); +static const struct iio_chan_spec bma280_accel_channels[] = + BMC150_ACCEL_CHANNELS(14); + +/* + * The range for the Bosch sensors is typically +-2g/4g/8g/16g, distributed + * over the amount of bits (see above). The scale table can be calculated using + * (range / 2^bits) * g = (range / 2^bits) * 9.80665 m/s^2 + * e.g. for +-2g and 12 bits: (4 / 2^12) * 9.80665 m/s^2 = 0.0095768... m/s^2 + * Multiply 10^6 and round to get the values listed below. + */ +static const struct bmc150_accel_chip_info bmc150_accel_chip_info_tbl[] = { + { + .name = "BMA222", + .chip_id = 0x03, + .channels = bma222e_accel_channels, + .num_channels = ARRAY_SIZE(bma222e_accel_channels), + .scale_table = { {153229, BMC150_ACCEL_DEF_RANGE_2G}, + {306458, BMC150_ACCEL_DEF_RANGE_4G}, + {612916, BMC150_ACCEL_DEF_RANGE_8G}, + {1225831, BMC150_ACCEL_DEF_RANGE_16G} }, + }, + { + .name = "BMA222E", + .chip_id = 0xF8, + .channels = bma222e_accel_channels, + .num_channels = ARRAY_SIZE(bma222e_accel_channels), + .scale_table = { {153229, BMC150_ACCEL_DEF_RANGE_2G}, + {306458, BMC150_ACCEL_DEF_RANGE_4G}, + {612916, BMC150_ACCEL_DEF_RANGE_8G}, + {1225831, BMC150_ACCEL_DEF_RANGE_16G} }, + }, + { + .name = "BMA250E", + .chip_id = 0xF9, + .channels = bma250e_accel_channels, + .num_channels = ARRAY_SIZE(bma250e_accel_channels), + .scale_table = { {38307, BMC150_ACCEL_DEF_RANGE_2G}, + {76614, BMC150_ACCEL_DEF_RANGE_4G}, + {153229, BMC150_ACCEL_DEF_RANGE_8G}, + {306458, BMC150_ACCEL_DEF_RANGE_16G} }, + }, + { + .name = "BMA253/BMA254/BMA255/BMC150/BMC156/BMI055", + .chip_id = 0xFA, + .channels = bmc150_accel_channels, + .num_channels = ARRAY_SIZE(bmc150_accel_channels), + .scale_table = { {9577, BMC150_ACCEL_DEF_RANGE_2G}, + {19154, BMC150_ACCEL_DEF_RANGE_4G}, + {38307, BMC150_ACCEL_DEF_RANGE_8G}, + {76614, BMC150_ACCEL_DEF_RANGE_16G} }, + }, + { + .name = "BMA280", + .chip_id = 0xFB, + .channels = bma280_accel_channels, + .num_channels = ARRAY_SIZE(bma280_accel_channels), + .scale_table = { {2394, BMC150_ACCEL_DEF_RANGE_2G}, + {4788, BMC150_ACCEL_DEF_RANGE_4G}, + {9577, BMC150_ACCEL_DEF_RANGE_8G}, + {19154, BMC150_ACCEL_DEF_RANGE_16G} }, + }, +}; + +static const struct iio_info bmc150_accel_info = { + .attrs = &bmc150_accel_attrs_group, + .read_raw = bmc150_accel_read_raw, + .write_raw = bmc150_accel_write_raw, + .read_event_value = bmc150_accel_read_event, + .write_event_value = bmc150_accel_write_event, + .write_event_config = bmc150_accel_write_event_config, + .read_event_config = bmc150_accel_read_event_config, +}; + +static const struct iio_info bmc150_accel_info_fifo = { + .attrs = &bmc150_accel_attrs_group, + .read_raw = bmc150_accel_read_raw, + .write_raw = bmc150_accel_write_raw, + .read_event_value = bmc150_accel_read_event, + .write_event_value = bmc150_accel_write_event, + .write_event_config = bmc150_accel_write_event_config, + .read_event_config = bmc150_accel_read_event_config, + .validate_trigger = bmc150_accel_validate_trigger, + .hwfifo_set_watermark = bmc150_accel_set_watermark, + .hwfifo_flush_to_buffer = bmc150_accel_fifo_flush, +}; + +static const unsigned long bmc150_accel_scan_masks[] = { + BIT(AXIS_X) | BIT(AXIS_Y) | BIT(AXIS_Z), + 0}; + +static irqreturn_t bmc150_accel_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bmc150_accel_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = regmap_bulk_read(data->regmap, BMC150_ACCEL_REG_XOUT_L, + data->buffer, AXIS_MAX * 2); + mutex_unlock(&data->mutex); + if (ret < 0) + goto err_read; + + iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, + pf->timestamp); +err_read: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static void bmc150_accel_trig_reen(struct iio_trigger *trig) +{ + struct bmc150_accel_trigger *t = iio_trigger_get_drvdata(trig); + struct bmc150_accel_data *data = t->data; + struct device *dev = regmap_get_device(data->regmap); + int ret; + + /* new data interrupts don't need ack */ + if (t == &t->data->triggers[BMC150_ACCEL_TRIGGER_DATA_READY]) + return; + + mutex_lock(&data->mutex); + /* clear any latched interrupt */ + ret = regmap_write(data->regmap, BMC150_ACCEL_REG_INT_RST_LATCH, + BMC150_ACCEL_INT_MODE_LATCH_INT | + BMC150_ACCEL_INT_MODE_LATCH_RESET); + mutex_unlock(&data->mutex); + if (ret < 0) + dev_err(dev, "Error writing reg_int_rst_latch\n"); +} + +static int bmc150_accel_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct bmc150_accel_trigger *t = iio_trigger_get_drvdata(trig); + struct bmc150_accel_data *data = t->data; + int ret; + + mutex_lock(&data->mutex); + + if (t->enabled == state) { + mutex_unlock(&data->mutex); + return 0; + } + + if (t->setup) { + ret = t->setup(t, state); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + } + + ret = bmc150_accel_set_interrupt(data, t->intr, state); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + + t->enabled = state; + + mutex_unlock(&data->mutex); + + return ret; +} + +static const struct iio_trigger_ops bmc150_accel_trigger_ops = { + .set_trigger_state = bmc150_accel_trigger_set_state, + .reenable = bmc150_accel_trig_reen, +}; + +static int bmc150_accel_handle_roc_event(struct iio_dev *indio_dev) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); + int dir; + int ret; + unsigned int val; + + ret = regmap_read(data->regmap, BMC150_ACCEL_REG_INT_STATUS_2, &val); + if (ret < 0) { + dev_err(dev, "Error reading reg_int_status_2\n"); + return ret; + } + + if (val & BMC150_ACCEL_ANY_MOTION_BIT_SIGN) + dir = IIO_EV_DIR_FALLING; + else + dir = IIO_EV_DIR_RISING; + + if (val & BMC150_ACCEL_ANY_MOTION_BIT_X) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_X, + IIO_EV_TYPE_ROC, + dir), + data->timestamp); + + if (val & BMC150_ACCEL_ANY_MOTION_BIT_Y) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Y, + IIO_EV_TYPE_ROC, + dir), + data->timestamp); + + if (val & BMC150_ACCEL_ANY_MOTION_BIT_Z) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Z, + IIO_EV_TYPE_ROC, + dir), + data->timestamp); + + return ret; +} + +static irqreturn_t bmc150_accel_irq_thread_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct bmc150_accel_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); + bool ack = false; + int ret; + + mutex_lock(&data->mutex); + + if (data->fifo_mode) { + ret = __bmc150_accel_fifo_flush(indio_dev, + BMC150_ACCEL_FIFO_LENGTH, true); + if (ret > 0) + ack = true; + } + + if (data->ev_enable_state) { + ret = bmc150_accel_handle_roc_event(indio_dev); + if (ret > 0) + ack = true; + } + + if (ack) { + ret = regmap_write(data->regmap, BMC150_ACCEL_REG_INT_RST_LATCH, + BMC150_ACCEL_INT_MODE_LATCH_INT | + BMC150_ACCEL_INT_MODE_LATCH_RESET); + if (ret) + dev_err(dev, "Error writing reg_int_rst_latch\n"); + + ret = IRQ_HANDLED; + } else { + ret = IRQ_NONE; + } + + mutex_unlock(&data->mutex); + + return ret; +} + +static irqreturn_t bmc150_accel_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct bmc150_accel_data *data = iio_priv(indio_dev); + bool ack = false; + int i; + + data->old_timestamp = data->timestamp; + data->timestamp = iio_get_time_ns(indio_dev); + + for (i = 0; i < BMC150_ACCEL_TRIGGERS; i++) { + if (data->triggers[i].enabled) { + iio_trigger_poll(data->triggers[i].indio_trig); + ack = true; + break; + } + } + + if (data->ev_enable_state || data->fifo_mode) + return IRQ_WAKE_THREAD; + + if (ack) + return IRQ_HANDLED; + + return IRQ_NONE; +} + +static const struct { + int intr; + const char *name; + int (*setup)(struct bmc150_accel_trigger *t, bool state); +} bmc150_accel_triggers[BMC150_ACCEL_TRIGGERS] = { + { + .intr = 0, + .name = "%s-dev%d", + }, + { + .intr = 1, + .name = "%s-any-motion-dev%d", + .setup = bmc150_accel_any_motion_setup, + }, +}; + +static void bmc150_accel_unregister_triggers(struct bmc150_accel_data *data, + int from) +{ + int i; + + for (i = from; i >= 0; i--) { + if (data->triggers[i].indio_trig) { + iio_trigger_unregister(data->triggers[i].indio_trig); + data->triggers[i].indio_trig = NULL; + } + } +} + +static int bmc150_accel_triggers_setup(struct iio_dev *indio_dev, + struct bmc150_accel_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int i, ret; + + for (i = 0; i < BMC150_ACCEL_TRIGGERS; i++) { + struct bmc150_accel_trigger *t = &data->triggers[i]; + + t->indio_trig = devm_iio_trigger_alloc(dev, + bmc150_accel_triggers[i].name, + indio_dev->name, + iio_device_id(indio_dev)); + if (!t->indio_trig) { + ret = -ENOMEM; + break; + } + + t->indio_trig->ops = &bmc150_accel_trigger_ops; + t->intr = bmc150_accel_triggers[i].intr; + t->data = data; + t->setup = bmc150_accel_triggers[i].setup; + iio_trigger_set_drvdata(t->indio_trig, t); + + ret = iio_trigger_register(t->indio_trig); + if (ret) + break; + } + + if (ret) + bmc150_accel_unregister_triggers(data, i - 1); + + return ret; +} + +#define BMC150_ACCEL_FIFO_MODE_STREAM 0x80 +#define BMC150_ACCEL_FIFO_MODE_FIFO 0x40 +#define BMC150_ACCEL_FIFO_MODE_BYPASS 0x00 + +static int bmc150_accel_fifo_set_mode(struct bmc150_accel_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + u8 reg = BMC150_ACCEL_REG_FIFO_CONFIG1; + int ret; + + ret = regmap_write(data->regmap, reg, data->fifo_mode); + if (ret < 0) { + dev_err(dev, "Error writing reg_fifo_config1\n"); + return ret; + } + + if (!data->fifo_mode) + return 0; + + ret = regmap_write(data->regmap, BMC150_ACCEL_REG_FIFO_CONFIG0, + data->watermark); + if (ret < 0) + dev_err(dev, "Error writing reg_fifo_config0\n"); + + return ret; +} + +static int bmc150_accel_buffer_preenable(struct iio_dev *indio_dev) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + + return bmc150_accel_set_power_state(data, true); +} + +static int bmc150_accel_buffer_postenable(struct iio_dev *indio_dev) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + int ret = 0; + + if (iio_device_get_current_mode(indio_dev) == INDIO_BUFFER_TRIGGERED) + return 0; + + mutex_lock(&data->mutex); + + if (!data->watermark) + goto out; + + ret = bmc150_accel_set_interrupt(data, BMC150_ACCEL_INT_WATERMARK, + true); + if (ret) + goto out; + + data->fifo_mode = BMC150_ACCEL_FIFO_MODE_FIFO; + + ret = bmc150_accel_fifo_set_mode(data); + if (ret) { + data->fifo_mode = 0; + bmc150_accel_set_interrupt(data, BMC150_ACCEL_INT_WATERMARK, + false); + } + +out: + mutex_unlock(&data->mutex); + + return ret; +} + +static int bmc150_accel_buffer_predisable(struct iio_dev *indio_dev) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + + if (iio_device_get_current_mode(indio_dev) == INDIO_BUFFER_TRIGGERED) + return 0; + + mutex_lock(&data->mutex); + + if (!data->fifo_mode) + goto out; + + bmc150_accel_set_interrupt(data, BMC150_ACCEL_INT_WATERMARK, false); + __bmc150_accel_fifo_flush(indio_dev, BMC150_ACCEL_FIFO_LENGTH, false); + data->fifo_mode = 0; + bmc150_accel_fifo_set_mode(data); + +out: + mutex_unlock(&data->mutex); + + return 0; +} + +static int bmc150_accel_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + + return bmc150_accel_set_power_state(data, false); +} + +static const struct iio_buffer_setup_ops bmc150_accel_buffer_ops = { + .preenable = bmc150_accel_buffer_preenable, + .postenable = bmc150_accel_buffer_postenable, + .predisable = bmc150_accel_buffer_predisable, + .postdisable = bmc150_accel_buffer_postdisable, +}; + +static int bmc150_accel_chip_init(struct bmc150_accel_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret, i; + unsigned int val; + + /* + * Reset chip to get it in a known good state. A delay of 1.8ms after + * reset is required according to the data sheets of supported chips. + */ + regmap_write(data->regmap, BMC150_ACCEL_REG_RESET, + BMC150_ACCEL_RESET_VAL); + usleep_range(1800, 2500); + + ret = regmap_read(data->regmap, BMC150_ACCEL_REG_CHIP_ID, &val); + if (ret < 0) { + dev_err(dev, "Error: Reading chip id\n"); + return ret; + } + + dev_dbg(dev, "Chip Id %x\n", val); + for (i = 0; i < ARRAY_SIZE(bmc150_accel_chip_info_tbl); i++) { + if (bmc150_accel_chip_info_tbl[i].chip_id == val) { + data->chip_info = &bmc150_accel_chip_info_tbl[i]; + break; + } + } + + if (!data->chip_info) { + dev_err(dev, "Invalid chip %x\n", val); + return -ENODEV; + } + + ret = bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_NORMAL, 0); + if (ret < 0) + return ret; + + /* Set Bandwidth */ + ret = bmc150_accel_set_bw(data, BMC150_ACCEL_DEF_BW, 0); + if (ret < 0) + return ret; + + /* Set Default Range */ + ret = regmap_write(data->regmap, BMC150_ACCEL_REG_PMU_RANGE, + BMC150_ACCEL_DEF_RANGE_4G); + if (ret < 0) { + dev_err(dev, "Error writing reg_pmu_range\n"); + return ret; + } + + data->range = BMC150_ACCEL_DEF_RANGE_4G; + + /* Set default slope duration and thresholds */ + data->slope_thres = BMC150_ACCEL_DEF_SLOPE_THRESHOLD; + data->slope_dur = BMC150_ACCEL_DEF_SLOPE_DURATION; + ret = bmc150_accel_update_slope(data); + if (ret < 0) + return ret; + + /* Set default as latched interrupts */ + ret = regmap_write(data->regmap, BMC150_ACCEL_REG_INT_RST_LATCH, + BMC150_ACCEL_INT_MODE_LATCH_INT | + BMC150_ACCEL_INT_MODE_LATCH_RESET); + if (ret < 0) { + dev_err(dev, "Error writing reg_int_rst_latch\n"); + return ret; + } + + return 0; +} + +int bmc150_accel_core_probe(struct device *dev, struct regmap *regmap, int irq, + enum bmc150_type type, const char *name, + bool block_supported) +{ + const struct iio_dev_attr **fifo_attrs; + struct bmc150_accel_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + dev_set_drvdata(dev, indio_dev); + + data->regmap = regmap; + data->type = type; + + if (!bmc150_apply_acpi_orientation(dev, &data->orientation)) { + ret = iio_read_mount_matrix(dev, &data->orientation); + if (ret) + return ret; + } + + /* + * VDD is the analog and digital domain voltage supply + * VDDIO is the digital I/O voltage supply + */ + data->regulators[0].supply = "vdd"; + data->regulators[1].supply = "vddio"; + ret = devm_regulator_bulk_get(dev, + ARRAY_SIZE(data->regulators), + data->regulators); + if (ret) + return dev_err_probe(dev, ret, "failed to get regulators\n"); + + ret = regulator_bulk_enable(ARRAY_SIZE(data->regulators), + data->regulators); + if (ret) { + dev_err(dev, "failed to enable regulators: %d\n", ret); + return ret; + } + /* + * 2ms or 3ms power-on time according to datasheets, let's better + * be safe than sorry and set this delay to 5ms. + */ + msleep(5); + + ret = bmc150_accel_chip_init(data); + if (ret < 0) + goto err_disable_regulators; + + mutex_init(&data->mutex); + + indio_dev->channels = data->chip_info->channels; + indio_dev->num_channels = data->chip_info->num_channels; + indio_dev->name = name ? name : data->chip_info->name; + indio_dev->available_scan_masks = bmc150_accel_scan_masks; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &bmc150_accel_info; + + if (block_supported) { + indio_dev->modes |= INDIO_BUFFER_SOFTWARE; + indio_dev->info = &bmc150_accel_info_fifo; + fifo_attrs = bmc150_accel_fifo_attributes; + } else { + fifo_attrs = NULL; + } + + ret = iio_triggered_buffer_setup_ext(indio_dev, + &iio_pollfunc_store_time, + bmc150_accel_trigger_handler, + IIO_BUFFER_DIRECTION_IN, + &bmc150_accel_buffer_ops, + fifo_attrs); + if (ret < 0) { + dev_err(dev, "Failed: iio triggered buffer setup\n"); + goto err_disable_regulators; + } + + if (irq > 0) { + data->irq = irq; + ret = devm_request_threaded_irq(dev, irq, + bmc150_accel_irq_handler, + bmc150_accel_irq_thread_handler, + IRQF_TRIGGER_RISING, + "bmc150_accel_event", + indio_dev); + if (ret) + goto err_buffer_cleanup; + + /* + * Set latched mode interrupt. While certain interrupts are + * non-latched regardless of this settings (e.g. new data) we + * want to use latch mode when we can to prevent interrupt + * flooding. + */ + ret = regmap_write(data->regmap, BMC150_ACCEL_REG_INT_RST_LATCH, + BMC150_ACCEL_INT_MODE_LATCH_RESET); + if (ret < 0) { + dev_err(dev, "Error writing reg_int_rst_latch\n"); + goto err_buffer_cleanup; + } + + bmc150_accel_interrupts_setup(indio_dev, data, irq); + + ret = bmc150_accel_triggers_setup(indio_dev, data); + if (ret) + goto err_buffer_cleanup; + } + + ret = pm_runtime_set_active(dev); + if (ret) + goto err_trigger_unregister; + + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, BMC150_AUTO_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(dev, "Unable to register iio device\n"); + goto err_pm_cleanup; + } + + return 0; + +err_pm_cleanup: + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_disable(dev); +err_trigger_unregister: + bmc150_accel_unregister_triggers(data, BMC150_ACCEL_TRIGGERS - 1); +err_buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); +err_disable_regulators: + regulator_bulk_disable(ARRAY_SIZE(data->regulators), + data->regulators); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(bmc150_accel_core_probe, "IIO_BMC150"); + +void bmc150_accel_core_remove(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmc150_accel_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + + bmc150_accel_unregister_triggers(data, BMC150_ACCEL_TRIGGERS - 1); + + iio_triggered_buffer_cleanup(indio_dev); + + mutex_lock(&data->mutex); + bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_DEEP_SUSPEND, 0); + mutex_unlock(&data->mutex); + + regulator_bulk_disable(ARRAY_SIZE(data->regulators), + data->regulators); +} +EXPORT_SYMBOL_NS_GPL(bmc150_accel_core_remove, "IIO_BMC150"); + +#ifdef CONFIG_PM_SLEEP +static int bmc150_accel_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmc150_accel_data *data = iio_priv(indio_dev); + + mutex_lock(&data->mutex); + bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_SUSPEND, 0); + mutex_unlock(&data->mutex); + + return 0; +} + +static int bmc150_accel_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmc150_accel_data *data = iio_priv(indio_dev); + + mutex_lock(&data->mutex); + bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_NORMAL, 0); + bmc150_accel_fifo_set_mode(data); + mutex_unlock(&data->mutex); + + if (data->resume_callback) + data->resume_callback(dev); + + return 0; +} +#endif + +#ifdef CONFIG_PM +static int bmc150_accel_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmc150_accel_data *data = iio_priv(indio_dev); + int ret; + + ret = bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_SUSPEND, 0); + if (ret < 0) + return -EAGAIN; + + return 0; +} + +static int bmc150_accel_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmc150_accel_data *data = iio_priv(indio_dev); + int ret; + int sleep_val; + + ret = bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_NORMAL, 0); + if (ret < 0) + return ret; + ret = bmc150_accel_fifo_set_mode(data); + if (ret < 0) + return ret; + + sleep_val = bmc150_accel_get_startup_times(data); + if (sleep_val < 20) + usleep_range(sleep_val * 1000, 20000); + else + msleep_interruptible(sleep_val); + + return 0; +} +#endif + +const struct dev_pm_ops bmc150_accel_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(bmc150_accel_suspend, bmc150_accel_resume) + SET_RUNTIME_PM_OPS(bmc150_accel_runtime_suspend, + bmc150_accel_runtime_resume, NULL) +}; +EXPORT_SYMBOL_NS_GPL(bmc150_accel_pm_ops, "IIO_BMC150"); + +MODULE_AUTHOR("Srinivas Pandruvada "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("BMC150 accelerometer driver"); diff --git a/drivers/iio/accel/bmc150-accel-i2c.c b/drivers/iio/accel/bmc150-accel-i2c.c new file mode 100644 index 000000000..336866aad --- /dev/null +++ b/drivers/iio/accel/bmc150-accel-i2c.c @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 3-axis accelerometer driver supporting many I2C Bosch-Sensortec chips + * Copyright (c) 2014, Intel Corporation. + */ + +#include +#include +#include +#include +#include + +#include "bmc150-accel.h" + +#ifdef CONFIG_ACPI +static const struct acpi_device_id bmc150_acpi_dual_accel_ids[] = { + {"BOSC0200"}, + {"DUAL250E"}, + { } +}; + +/* + * The DUAL250E ACPI device for 360° hinges type 2-in-1s with 1 accelerometer + * in the display and 1 in the hinge has an ACPI-method (DSM) to tell the + * ACPI code about the angle between the 2 halves. This will make the ACPI + * code enable/disable the keyboard and touchpad. We need to call this to avoid + * the keyboard being disabled when the 2-in-1 is turned-on or resumed while + * fully folded into tablet mode (which gets detected with a HALL-sensor). + * If we don't call this then the keyboard won't work even when the 2-in-1 is + * changed to be used in laptop mode after the power-on / resume. + * + * This DSM takes 2 angles, selected by setting aux0 to 0 or 1, these presumably + * define the angle between the gravity vector measured by the accelerometer in + * the display (aux0=0) resp. the base (aux0=1) and some reference vector. + * The 2 angles get subtracted from each other so the reference vector does + * not matter and we can simply leave the second angle at 0. + */ + +#define BMC150_DSM_GUID "7681541e-8827-4239-8d9d-36be7fe12542" +#define DUAL250E_SET_ANGLE_FN_INDEX 3 + +struct dual250e_set_angle_args { + u32 aux0; + u32 ang0; + u32 rawx; + u32 rawy; + u32 rawz; +} __packed; + +static bool bmc150_acpi_set_angle_dsm(struct i2c_client *client, u32 aux0, u32 ang0) +{ + struct acpi_device *adev = ACPI_COMPANION(&client->dev); + struct dual250e_set_angle_args args = { + .aux0 = aux0, + .ang0 = ang0, + }; + union acpi_object args_obj, *obj; + guid_t guid; + + if (!acpi_dev_hid_uid_match(adev, "DUAL250E", NULL)) + return false; + + guid_parse(BMC150_DSM_GUID, &guid); + + if (!acpi_check_dsm(adev->handle, &guid, 0, BIT(DUAL250E_SET_ANGLE_FN_INDEX))) + return false; + + /* + * Note this triggers the following warning: + * "ACPI Warning: \_SB.PCI0.I2C2.ACC1._DSM: Argument #4 type mismatch - + * Found [Buffer], ACPI requires [Package]" + * This is unavoidable since the _DSM implementation expects a "naked" + * buffer, so wrapping it in a package will _not_ work. + */ + args_obj.type = ACPI_TYPE_BUFFER; + args_obj.buffer.length = sizeof(args); + args_obj.buffer.pointer = (u8 *)&args; + + obj = acpi_evaluate_dsm(adev->handle, &guid, 0, DUAL250E_SET_ANGLE_FN_INDEX, &args_obj); + if (!obj) { + dev_err(&client->dev, "Failed to call DSM to enable keyboard and touchpad\n"); + return false; + } + + ACPI_FREE(obj); + return true; +} + +static bool bmc150_acpi_enable_keyboard(struct i2c_client *client) +{ + /* + * The EC must see a change for it to re-enable the kbd, so first + * set the angle to 270° (tent/stand mode) and then change it to + * 90° (laptop mode). + */ + if (!bmc150_acpi_set_angle_dsm(client, 0, 270)) + return false; + + /* The EC needs some time to notice the angle being changed */ + msleep(100); + + return bmc150_acpi_set_angle_dsm(client, 0, 90); +} + +static void bmc150_acpi_resume_work(struct work_struct *work) +{ + struct bmc150_accel_data *data = + container_of(work, struct bmc150_accel_data, resume_work.work); + + bmc150_acpi_enable_keyboard(data->second_device); +} + +static void bmc150_acpi_resume_handler(struct device *dev) +{ + struct bmc150_accel_data *data = iio_priv(dev_get_drvdata(dev)); + + /* + * Delay the bmc150_acpi_enable_keyboard() call till after the system + * resume has completed, otherwise it will not work. + */ + schedule_delayed_work(&data->resume_work, msecs_to_jiffies(1000)); +} + +/* + * Some acpi_devices describe 2 accelerometers in a single ACPI device, + * try instantiating a second i2c_client for an I2cSerialBusV2 ACPI resource + * with index 1. + */ +static void bmc150_acpi_dual_accel_probe(struct i2c_client *client) +{ + struct bmc150_accel_data *data = iio_priv(i2c_get_clientdata(client)); + struct acpi_device *adev = ACPI_COMPANION(&client->dev); + char dev_name[16]; + struct i2c_board_info board_info = { + .type = "bmc150_accel", + .dev_name = dev_name, + .fwnode = client->dev.fwnode, + }; + + if (acpi_match_device_ids(adev, bmc150_acpi_dual_accel_ids)) + return; + + /* + * The 2nd accel sits in the base of 2-in-1s. The suffix is static, as + * there should never be more then 1 ACPI node with 2 accelerometers. + */ + snprintf(dev_name, sizeof(dev_name), "%s:base", acpi_device_hid(adev)); + + board_info.irq = acpi_dev_gpio_irq_get(adev, 1); + + data->second_device = i2c_acpi_new_device(&client->dev, 1, &board_info); + + if (!IS_ERR(data->second_device) && bmc150_acpi_enable_keyboard(data->second_device)) { + INIT_DELAYED_WORK(&data->resume_work, bmc150_acpi_resume_work); + data->resume_callback = bmc150_acpi_resume_handler; + } +} + +static void bmc150_acpi_dual_accel_remove(struct i2c_client *client) +{ + struct bmc150_accel_data *data = iio_priv(i2c_get_clientdata(client)); + + if (data->resume_callback) + cancel_delayed_work_sync(&data->resume_work); + + i2c_unregister_device(data->second_device); +} +#else +static void bmc150_acpi_dual_accel_probe(struct i2c_client *client) {} +static void bmc150_acpi_dual_accel_remove(struct i2c_client *client) {} +#endif + +static int bmc150_accel_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct regmap *regmap; + const char *name = NULL; + enum bmc150_type type = BOSCH_UNKNOWN; + bool block_supported = + i2c_check_functionality(client->adapter, I2C_FUNC_I2C) || + i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_I2C_BLOCK); + int ret; + + regmap = devm_regmap_init_i2c(client, &bmc150_regmap_conf); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Failed to initialize i2c regmap\n"); + return PTR_ERR(regmap); + } + + if (id) { + name = id->name; + type = id->driver_data; + } + + ret = bmc150_accel_core_probe(&client->dev, regmap, client->irq, + type, name, block_supported); + if (ret) + return ret; + + /* + * The !id check avoids recursion when probe() gets called + * for the second client. + */ + if (!id && has_acpi_companion(&client->dev)) + bmc150_acpi_dual_accel_probe(client); + + return 0; +} + +static void bmc150_accel_remove(struct i2c_client *client) +{ + bmc150_acpi_dual_accel_remove(client); + + bmc150_accel_core_remove(&client->dev); +} + +static const struct acpi_device_id bmc150_accel_acpi_match[] = { + {"BMA0255"}, + {"BMA0280"}, + {"BMA222"}, + {"BMA222E"}, + {"BMA250E"}, + {"BMC150A"}, + {"BMI055A"}, + /* + * The "BOSC0200" identifier used here is not unique to devices using + * bmc150. The same "BOSC0200" identifier is found in the ACPI tables + * of the ASUS ROG ALLY and Ayaneo AIR Plus which both use a Bosch + * BMI323 chip. This creates a conflict with duplicate ACPI identifiers + * which multiple drivers want to use. Fortunately, when the bmc150 + * driver starts to load on the ASUS ROG ALLY, the chip ID check + * portion fails (correctly) because the chip IDs received (via i2c) + * are unique between bmc150 and bmi323 and a dmesg output similar to + * this: "bmc150_accel_i2c i2c-BOSC0200:00: Invalid chip 0" can be + * seen. This allows the bmi323 driver to take over for ASUS ROG ALLY, + * and other devices using the bmi323 chip. + */ + {"BOSC0200"}, + {"BSBA0150"}, + {"DUAL250E"}, + { } +}; +MODULE_DEVICE_TABLE(acpi, bmc150_accel_acpi_match); + +static const struct i2c_device_id bmc150_accel_id[] = { + { .name = "bma222", .driver_data = BOSCH_UNKNOWN }, + { .name = "bma222e", .driver_data = BOSCH_UNKNOWN }, + { .name = "bma250e", .driver_data = BOSCH_UNKNOWN }, + { .name = "bma253", .driver_data = BOSCH_UNKNOWN }, + { .name = "bma254", .driver_data = BOSCH_UNKNOWN }, + { .name = "bma255", .driver_data = BOSCH_UNKNOWN }, + { .name = "bma280", .driver_data = BOSCH_UNKNOWN }, + { .name = "bmc150_accel", .driver_data = BOSCH_UNKNOWN }, + { .name = "bmc156_accel", .driver_data = BOSCH_BMC156 }, + { .name = "bmi055_accel", .driver_data = BOSCH_UNKNOWN }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, bmc150_accel_id); + +static const struct of_device_id bmc150_accel_of_match[] = { + { .compatible = "bosch,bma222" }, + { .compatible = "bosch,bma222e" }, + { .compatible = "bosch,bma250e" }, + { .compatible = "bosch,bma253" }, + { .compatible = "bosch,bma254" }, + { .compatible = "bosch,bma255" }, + { .compatible = "bosch,bma280" }, + { .compatible = "bosch,bmc150_accel" }, + { .compatible = "bosch,bmc156_accel" }, + { .compatible = "bosch,bmi055_accel" }, + { } +}; +MODULE_DEVICE_TABLE(of, bmc150_accel_of_match); + +static struct i2c_driver bmc150_accel_driver = { + .driver = { + .name = "bmc150_accel_i2c", + .of_match_table = bmc150_accel_of_match, + .acpi_match_table = bmc150_accel_acpi_match, + .pm = &bmc150_accel_pm_ops, + }, + .probe = bmc150_accel_probe, + .remove = bmc150_accel_remove, + .id_table = bmc150_accel_id, +}; +module_i2c_driver(bmc150_accel_driver); + +MODULE_AUTHOR("Srinivas Pandruvada "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("BMC150 I2C accelerometer driver"); +MODULE_IMPORT_NS("IIO_BMC150"); diff --git a/drivers/iio/accel/bmc150-accel-spi.c b/drivers/iio/accel/bmc150-accel-spi.c new file mode 100644 index 000000000..405bd7b2d --- /dev/null +++ b/drivers/iio/accel/bmc150-accel-spi.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * 3-axis accelerometer driver supporting SPI Bosch-Sensortec accelerometer chip + * Copyright © 2015 Pengutronix, Markus Pargmann + */ + +#include +#include +#include +#include + +#include "bmc150-accel.h" + +static int bmc150_accel_probe(struct spi_device *spi) +{ + struct regmap *regmap; + const char *name = NULL; + enum bmc150_type type = BOSCH_UNKNOWN; + const struct spi_device_id *id = spi_get_device_id(spi); + + regmap = devm_regmap_init_spi(spi, &bmc150_regmap_conf); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "Failed to initialize spi regmap\n"); + return PTR_ERR(regmap); + } + + if (id) { + name = id->name; + type = id->driver_data; + } + + return bmc150_accel_core_probe(&spi->dev, regmap, spi->irq, type, name, + true); +} + +static void bmc150_accel_remove(struct spi_device *spi) +{ + bmc150_accel_core_remove(&spi->dev); +} + +static const struct acpi_device_id bmc150_accel_acpi_match[] = { + {"BMA0255"}, + {"BMA0280"}, + {"BMA222"}, + {"BMA222E"}, + {"BMA250E"}, + {"BMC150A"}, + {"BMI055A"}, + {"BSBA0150"}, + { } +}; +MODULE_DEVICE_TABLE(acpi, bmc150_accel_acpi_match); + +static const struct spi_device_id bmc150_accel_id[] = { + { .name = "bma222", .driver_data = BOSCH_UNKNOWN }, + { .name = "bma222e", .driver_data = BOSCH_UNKNOWN }, + { .name = "bma250e", .driver_data = BOSCH_UNKNOWN }, + { .name = "bma253", .driver_data = BOSCH_UNKNOWN }, + { .name = "bma255", .driver_data = BOSCH_UNKNOWN }, + { .name = "bma280", .driver_data = BOSCH_UNKNOWN }, + { .name = "bmc150_accel", .driver_data = BOSCH_UNKNOWN }, + { .name = "bmc156_accel", .driver_data = BOSCH_BMC156 }, + { .name = "bmi055_accel", .driver_data = BOSCH_UNKNOWN }, + { } +}; +MODULE_DEVICE_TABLE(spi, bmc150_accel_id); + +static struct spi_driver bmc150_accel_driver = { + .driver = { + .name = "bmc150_accel_spi", + .acpi_match_table = bmc150_accel_acpi_match, + .pm = &bmc150_accel_pm_ops, + }, + .probe = bmc150_accel_probe, + .remove = bmc150_accel_remove, + .id_table = bmc150_accel_id, +}; +module_spi_driver(bmc150_accel_driver); + +MODULE_AUTHOR("Markus Pargmann "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("BMC150 SPI accelerometer driver"); +MODULE_IMPORT_NS("IIO_BMC150"); diff --git a/drivers/iio/accel/bmc150-accel.h b/drivers/iio/accel/bmc150-accel.h new file mode 100644 index 000000000..e8f261983 --- /dev/null +++ b/drivers/iio/accel/bmc150-accel.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _BMC150_ACCEL_H_ +#define _BMC150_ACCEL_H_ + +#include +#include +#include +#include +#include +#include + +struct regmap; +struct i2c_client; +struct bmc150_accel_chip_info; +struct bmc150_accel_interrupt_info; + +/* + * We can often guess better than "UNKNOWN" based on the device IDs + * but unfortunately this information is not always accurate. There are some + * devices where ACPI firmware specifies an ID like "BMA250E" when the device + * actually has a BMA222E. The driver attempts to detect those by reading the + * chip ID from the registers but this information is not always enough either. + * + * Therefore, this enum should be only used when the chip ID detection is not + * enough and we can be reasonably sure that the device IDs are reliable + * in practice (e.g. for device tree platforms). + */ +enum bmc150_type { + BOSCH_UNKNOWN, + BOSCH_BMC156, +}; + +struct bmc150_accel_interrupt { + const struct bmc150_accel_interrupt_info *info; + atomic_t users; +}; + +struct bmc150_accel_trigger { + struct bmc150_accel_data *data; + struct iio_trigger *indio_trig; + int (*setup)(struct bmc150_accel_trigger *t, bool state); + int intr; + bool enabled; +}; + +enum bmc150_accel_interrupt_id { + BMC150_ACCEL_INT_DATA_READY, + BMC150_ACCEL_INT_ANY_MOTION, + BMC150_ACCEL_INT_WATERMARK, + BMC150_ACCEL_INTERRUPTS, +}; + +enum bmc150_accel_trigger_id { + BMC150_ACCEL_TRIGGER_DATA_READY, + BMC150_ACCEL_TRIGGER_ANY_MOTION, + BMC150_ACCEL_TRIGGERS, +}; + +struct bmc150_accel_data { + struct regmap *regmap; + int irq; + struct regulator_bulk_data regulators[2]; + struct bmc150_accel_interrupt interrupts[BMC150_ACCEL_INTERRUPTS]; + struct bmc150_accel_trigger triggers[BMC150_ACCEL_TRIGGERS]; + struct mutex mutex; + u8 fifo_mode, watermark; + s16 buffer[8]; + /* + * Ensure there is sufficient space and correct alignment for + * the timestamp if enabled + */ + struct { + __le16 channels[3]; + aligned_s64 ts; + } scan; + u8 bw_bits; + u32 slope_dur; + u32 slope_thres; + u32 range; + int ev_enable_state; + int64_t timestamp, old_timestamp; /* Only used in hw fifo mode. */ + const struct bmc150_accel_chip_info *chip_info; + enum bmc150_type type; + struct i2c_client *second_device; + void (*resume_callback)(struct device *dev); + struct delayed_work resume_work; + struct iio_mount_matrix orientation; +}; + +int bmc150_accel_core_probe(struct device *dev, struct regmap *regmap, int irq, + enum bmc150_type type, const char *name, + bool block_supported); +void bmc150_accel_core_remove(struct device *dev); +extern const struct dev_pm_ops bmc150_accel_pm_ops; +extern const struct regmap_config bmc150_regmap_conf; + +#endif /* _BMC150_ACCEL_H_ */ diff --git a/drivers/iio/accel/bmi088-accel-core.c b/drivers/iio/accel/bmi088-accel-core.c new file mode 100644 index 000000000..c7da90af0 --- /dev/null +++ b/drivers/iio/accel/bmi088-accel-core.c @@ -0,0 +1,627 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 3-axis accelerometer driver supporting following Bosch-Sensortec chips: + * - BMI088 + * - BMI085 + * - BMI090L + * + * Copyright (c) 2018-2021, Topic Embedded Products + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bmi088-accel.h" + +#define BMI088_ACCEL_REG_CHIP_ID 0x00 +#define BMI088_ACCEL_REG_ERROR 0x02 + +#define BMI088_ACCEL_REG_INT_STATUS 0x1D +#define BMI088_ACCEL_INT_STATUS_BIT_DRDY BIT(7) + +#define BMI088_ACCEL_REG_RESET 0x7E +#define BMI088_ACCEL_RESET_VAL 0xB6 + +#define BMI088_ACCEL_REG_PWR_CTRL 0x7D +#define BMI088_ACCEL_REG_PWR_CONF 0x7C + +#define BMI088_ACCEL_REG_INT_MAP_DATA 0x58 +#define BMI088_ACCEL_INT_MAP_DATA_BIT_INT1_DRDY BIT(2) +#define BMI088_ACCEL_INT_MAP_DATA_BIT_INT2_FWM BIT(5) + +#define BMI088_ACCEL_REG_INT1_IO_CONF 0x53 +#define BMI088_ACCEL_INT1_IO_CONF_BIT_ENABLE_OUT BIT(3) +#define BMI088_ACCEL_INT1_IO_CONF_BIT_LVL BIT(1) + +#define BMI088_ACCEL_REG_INT2_IO_CONF 0x54 +#define BMI088_ACCEL_INT2_IO_CONF_BIT_ENABLE_OUT BIT(3) +#define BMI088_ACCEL_INT2_IO_CONF_BIT_LVL BIT(1) + +#define BMI088_ACCEL_REG_ACC_CONF 0x40 +#define BMI088_ACCEL_MODE_ODR_MASK 0x0f + +#define BMI088_ACCEL_REG_ACC_RANGE 0x41 +#define BMI088_ACCEL_RANGE_3G 0x00 +#define BMI088_ACCEL_RANGE_6G 0x01 +#define BMI088_ACCEL_RANGE_12G 0x02 +#define BMI088_ACCEL_RANGE_24G 0x03 + +#define BMI088_ACCEL_REG_TEMP 0x22 +#define BMI088_ACCEL_REG_TEMP_SHIFT 5 +#define BMI088_ACCEL_TEMP_UNIT 125 +#define BMI088_ACCEL_TEMP_OFFSET 23000 + +#define BMI088_ACCEL_REG_XOUT_L 0x12 +#define BMI088_ACCEL_AXIS_TO_REG(axis) \ + (BMI088_ACCEL_REG_XOUT_L + (axis * 2)) + +#define BMI088_ACCEL_MAX_STARTUP_TIME_US 1000 +#define BMI088_AUTO_SUSPEND_DELAY_MS 2000 + +#define BMI088_ACCEL_REG_FIFO_STATUS 0x0E +#define BMI088_ACCEL_REG_FIFO_CONFIG0 0x48 +#define BMI088_ACCEL_REG_FIFO_CONFIG1 0x49 +#define BMI088_ACCEL_REG_FIFO_DATA 0x3F +#define BMI088_ACCEL_FIFO_LENGTH 100 + +#define BMI088_ACCEL_FIFO_MODE_FIFO 0x40 +#define BMI088_ACCEL_FIFO_MODE_STREAM 0x80 + +#define BMIO088_ACCEL_ACC_RANGE_MSK GENMASK(1, 0) + +enum bmi088_accel_axis { + AXIS_X, + AXIS_Y, + AXIS_Z, +}; + +static const int bmi088_sample_freqs[] = { + 12, 500000, + 25, 0, + 50, 0, + 100, 0, + 200, 0, + 400, 0, + 800, 0, + 1600, 0, +}; + +/* Available OSR (over sampling rate) sets the 3dB cut-off frequency */ +enum bmi088_osr_modes { + BMI088_ACCEL_MODE_OSR_NORMAL = 0xA, + BMI088_ACCEL_MODE_OSR_2 = 0x9, + BMI088_ACCEL_MODE_OSR_4 = 0x8, +}; + +/* Available ODR (output data rates) in Hz */ +enum bmi088_odr_modes { + BMI088_ACCEL_MODE_ODR_12_5 = 0x5, + BMI088_ACCEL_MODE_ODR_25 = 0x6, + BMI088_ACCEL_MODE_ODR_50 = 0x7, + BMI088_ACCEL_MODE_ODR_100 = 0x8, + BMI088_ACCEL_MODE_ODR_200 = 0x9, + BMI088_ACCEL_MODE_ODR_400 = 0xa, + BMI088_ACCEL_MODE_ODR_800 = 0xb, + BMI088_ACCEL_MODE_ODR_1600 = 0xc, +}; + +struct bmi088_accel_chip_info { + const char *name; + u8 chip_id; + const struct iio_chan_spec *channels; + int num_channels; + const int scale_table[4][2]; +}; + +struct bmi088_accel_data { + struct regmap *regmap; + const struct bmi088_accel_chip_info *chip_info; + u8 buffer[2] __aligned(IIO_DMA_MINALIGN); /* shared DMA safe buffer */ +}; + +static const struct regmap_range bmi088_volatile_ranges[] = { + /* All registers below 0x40 are volatile, except the CHIP ID. */ + regmap_reg_range(BMI088_ACCEL_REG_ERROR, 0x3f), + /* Mark the RESET as volatile too, it is self-clearing */ + regmap_reg_range(BMI088_ACCEL_REG_RESET, BMI088_ACCEL_REG_RESET), +}; + +static const struct regmap_access_table bmi088_volatile_table = { + .yes_ranges = bmi088_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(bmi088_volatile_ranges), +}; + +const struct regmap_config bmi088_regmap_conf = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x7E, + .volatile_table = &bmi088_volatile_table, + .cache_type = REGCACHE_MAPLE, +}; +EXPORT_SYMBOL_NS_GPL(bmi088_regmap_conf, "IIO_BMI088"); + +static int bmi088_accel_power_up(struct bmi088_accel_data *data) +{ + int ret; + + /* Enable accelerometer and temperature sensor */ + ret = regmap_write(data->regmap, BMI088_ACCEL_REG_PWR_CTRL, 0x4); + if (ret) + return ret; + + /* Datasheet recommends to wait at least 5ms before communication */ + usleep_range(5000, 6000); + + /* Disable suspend mode */ + ret = regmap_write(data->regmap, BMI088_ACCEL_REG_PWR_CONF, 0x0); + if (ret) + return ret; + + /* Recommended at least 1ms before further communication */ + usleep_range(1000, 1200); + + return 0; +} + +static int bmi088_accel_power_down(struct bmi088_accel_data *data) +{ + int ret; + + /* Enable suspend mode */ + ret = regmap_write(data->regmap, BMI088_ACCEL_REG_PWR_CONF, 0x3); + if (ret) + return ret; + + /* Recommended at least 1ms before further communication */ + usleep_range(1000, 1200); + + /* Disable accelerometer and temperature sensor */ + ret = regmap_write(data->regmap, BMI088_ACCEL_REG_PWR_CTRL, 0x0); + if (ret) + return ret; + + /* Datasheet recommends to wait at least 5ms before communication */ + usleep_range(5000, 6000); + + return 0; +} + +static int bmi088_accel_get_sample_freq(struct bmi088_accel_data *data, + int *val, int *val2) +{ + unsigned int value; + int ret; + + ret = regmap_read(data->regmap, BMI088_ACCEL_REG_ACC_CONF, + &value); + if (ret) + return ret; + + value &= BMI088_ACCEL_MODE_ODR_MASK; + value -= BMI088_ACCEL_MODE_ODR_12_5; + value <<= 1; + + if (value >= ARRAY_SIZE(bmi088_sample_freqs) - 1) + return -EINVAL; + + *val = bmi088_sample_freqs[value]; + *val2 = bmi088_sample_freqs[value + 1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int bmi088_accel_set_sample_freq(struct bmi088_accel_data *data, int val) +{ + unsigned int regval; + int index = 0; + + while (index < ARRAY_SIZE(bmi088_sample_freqs) && + bmi088_sample_freqs[index] != val) + index += 2; + + if (index >= ARRAY_SIZE(bmi088_sample_freqs)) + return -EINVAL; + + regval = (index >> 1) + BMI088_ACCEL_MODE_ODR_12_5; + + return regmap_update_bits(data->regmap, BMI088_ACCEL_REG_ACC_CONF, + BMI088_ACCEL_MODE_ODR_MASK, regval); +} + +static int bmi088_accel_set_scale(struct bmi088_accel_data *data, int val, int val2) +{ + unsigned int i; + + for (i = 0; i < 4; i++) + if (val == data->chip_info->scale_table[i][0] && + val2 == data->chip_info->scale_table[i][1]) + break; + + if (i == 4) + return -EINVAL; + + return regmap_write(data->regmap, BMI088_ACCEL_REG_ACC_RANGE, i); +} + +static int bmi088_accel_get_temp(struct bmi088_accel_data *data, int *val) +{ + int ret; + s16 temp; + + ret = regmap_bulk_read(data->regmap, BMI088_ACCEL_REG_TEMP, + &data->buffer, sizeof(__be16)); + if (ret) + return ret; + + /* data->buffer is cacheline aligned */ + temp = be16_to_cpu(*(__be16 *)data->buffer); + + *val = temp >> BMI088_ACCEL_REG_TEMP_SHIFT; + + return IIO_VAL_INT; +} + +static int bmi088_accel_get_axis(struct bmi088_accel_data *data, + struct iio_chan_spec const *chan, + int *val) +{ + int ret; + s16 raw_val; + + ret = regmap_bulk_read(data->regmap, + BMI088_ACCEL_AXIS_TO_REG(chan->scan_index), + data->buffer, sizeof(__le16)); + if (ret) + return ret; + + raw_val = le16_to_cpu(*(__le16 *)data->buffer); + *val = raw_val; + + return IIO_VAL_INT; +} + +static int bmi088_accel_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bmi088_accel_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); + int ret; + int reg; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_TEMP: + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = bmi088_accel_get_temp(data, val); + goto out_read_raw_pm_put; + case IIO_ACCEL: + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + if (!iio_device_claim_direct(indio_dev)) { + ret = -EBUSY; + goto out_read_raw_pm_put; + } + + ret = bmi088_accel_get_axis(data, chan, val); + iio_device_release_direct(indio_dev); + if (!ret) + ret = IIO_VAL_INT; + + goto out_read_raw_pm_put; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + /* Offset applies before scale */ + *val = BMI088_ACCEL_TEMP_OFFSET/BMI088_ACCEL_TEMP_UNIT; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + /* 0.125 degrees per LSB */ + *val = BMI088_ACCEL_TEMP_UNIT; + return IIO_VAL_INT; + case IIO_ACCEL: + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = regmap_read(data->regmap, + BMI088_ACCEL_REG_ACC_RANGE, ®); + if (ret) + goto out_read_raw_pm_put; + + reg = FIELD_GET(BMIO088_ACCEL_ACC_RANGE_MSK, reg); + *val = data->chip_info->scale_table[reg][0]; + *val2 = data->chip_info->scale_table[reg][1]; + ret = IIO_VAL_INT_PLUS_MICRO; + + goto out_read_raw_pm_put; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = bmi088_accel_get_sample_freq(data, val, val2); + goto out_read_raw_pm_put; + default: + break; + } + + return -EINVAL; + +out_read_raw_pm_put: + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static int bmi088_accel_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct bmi088_accel_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = (const int *)data->chip_info->scale_table; + *length = 8; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *type = IIO_VAL_INT_PLUS_MICRO; + *vals = bmi088_sample_freqs; + *length = ARRAY_SIZE(bmi088_sample_freqs); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int bmi088_accel_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bmi088_accel_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = bmi088_accel_set_scale(data, val, val2); + pm_runtime_put_autosuspend(dev); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = bmi088_accel_set_sample_freq(data, val); + pm_runtime_put_autosuspend(dev); + return ret; + default: + return -EINVAL; + } +} + +#define BMI088_ACCEL_CHANNEL(_axis) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = AXIS_##_axis, \ +} + +static const struct iio_chan_spec bmi088_accel_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = -1, + }, + BMI088_ACCEL_CHANNEL(X), + BMI088_ACCEL_CHANNEL(Y), + BMI088_ACCEL_CHANNEL(Z), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct bmi088_accel_chip_info bmi088_accel_chip_info_tbl[] = { + [BOSCH_BMI085] = { + .name = "bmi085-accel", + .chip_id = 0x1F, + .channels = bmi088_accel_channels, + .num_channels = ARRAY_SIZE(bmi088_accel_channels), + .scale_table = {{0, 598}, {0, 1196}, {0, 2393}, {0, 4785}}, + }, + [BOSCH_BMI088] = { + .name = "bmi088-accel", + .chip_id = 0x1E, + .channels = bmi088_accel_channels, + .num_channels = ARRAY_SIZE(bmi088_accel_channels), + .scale_table = {{0, 897}, {0, 1794}, {0, 3589}, {0, 7178}}, + }, + [BOSCH_BMI090L] = { + .name = "bmi090l-accel", + .chip_id = 0x1A, + .channels = bmi088_accel_channels, + .num_channels = ARRAY_SIZE(bmi088_accel_channels), + .scale_table = {{0, 897}, {0, 1794}, {0, 3589}, {0, 7178}}, + }, +}; + +static const struct iio_info bmi088_accel_info = { + .read_raw = bmi088_accel_read_raw, + .write_raw = bmi088_accel_write_raw, + .read_avail = bmi088_accel_read_avail, +}; + +static const unsigned long bmi088_accel_scan_masks[] = { + BIT(AXIS_X) | BIT(AXIS_Y) | BIT(AXIS_Z), + 0 +}; + +static int bmi088_accel_chip_init(struct bmi088_accel_data *data, enum bmi_device_type type) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret, i; + unsigned int val; + + if (type >= BOSCH_UNKNOWN) + return -ENODEV; + + /* Do a dummy read to enable SPI interface, won't harm I2C */ + regmap_read(data->regmap, BMI088_ACCEL_REG_INT_STATUS, &val); + + /* + * Reset chip to get it in a known good state. A delay of 1ms after + * reset is required according to the data sheet + */ + ret = regmap_write(data->regmap, BMI088_ACCEL_REG_RESET, + BMI088_ACCEL_RESET_VAL); + if (ret) + return ret; + + usleep_range(1000, 2000); + + /* Do a dummy read again after a reset to enable the SPI interface */ + regmap_read(data->regmap, BMI088_ACCEL_REG_INT_STATUS, &val); + + /* Read chip ID */ + ret = regmap_read(data->regmap, BMI088_ACCEL_REG_CHIP_ID, &val); + if (ret) { + dev_err(dev, "Error: Reading chip id\n"); + return ret; + } + + /* Validate chip ID */ + for (i = 0; i < ARRAY_SIZE(bmi088_accel_chip_info_tbl); i++) + if (bmi088_accel_chip_info_tbl[i].chip_id == val) + break; + + if (i == ARRAY_SIZE(bmi088_accel_chip_info_tbl)) + data->chip_info = &bmi088_accel_chip_info_tbl[type]; + else + data->chip_info = &bmi088_accel_chip_info_tbl[i]; + + if (i != type) + dev_warn(dev, "unexpected chip id 0x%X\n", val); + + return 0; +} + +int bmi088_accel_core_probe(struct device *dev, struct regmap *regmap, + int irq, enum bmi_device_type type) +{ + struct bmi088_accel_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + dev_set_drvdata(dev, indio_dev); + + data->regmap = regmap; + + ret = bmi088_accel_chip_init(data, type); + if (ret) + return ret; + + indio_dev->channels = data->chip_info->channels; + indio_dev->num_channels = data->chip_info->num_channels; + indio_dev->name = data->chip_info->name; + indio_dev->available_scan_masks = bmi088_accel_scan_masks; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &bmi088_accel_info; + + /* Enable runtime PM */ + pm_runtime_get_noresume(dev); + pm_runtime_set_suspended(dev); + pm_runtime_enable(dev); + /* We need ~6ms to startup, so set the delay to 6 seconds */ + pm_runtime_set_autosuspend_delay(dev, 6000); + pm_runtime_use_autosuspend(dev); + pm_runtime_put(dev); + + ret = iio_device_register(indio_dev); + if (ret) + dev_err(dev, "Unable to register iio device\n"); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(bmi088_accel_core_probe, "IIO_BMI088"); + + +void bmi088_accel_core_remove(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmi088_accel_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + bmi088_accel_power_down(data); +} +EXPORT_SYMBOL_NS_GPL(bmi088_accel_core_remove, "IIO_BMI088"); + +static int bmi088_accel_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmi088_accel_data *data = iio_priv(indio_dev); + + return bmi088_accel_power_down(data); +} + +static int bmi088_accel_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmi088_accel_data *data = iio_priv(indio_dev); + + return bmi088_accel_power_up(data); +} + +EXPORT_NS_GPL_RUNTIME_DEV_PM_OPS(bmi088_accel_pm_ops, + bmi088_accel_runtime_suspend, + bmi088_accel_runtime_resume, NULL, + IIO_BMI088); + +MODULE_AUTHOR("Niek van Agt "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("BMI088 accelerometer driver (core)"); diff --git a/drivers/iio/accel/bmi088-accel-i2c.c b/drivers/iio/accel/bmi088-accel-i2c.c new file mode 100644 index 000000000..aecd66e56 --- /dev/null +++ b/drivers/iio/accel/bmi088-accel-i2c.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 3-axis accelerometer driver supporting following Bosch-Sensortec chips: + * - BMI088 + * - BMI085 + * - BMI090L + * + * Copyright 2023 Jun Yan + */ + +#include +#include +#include +#include + +#include "bmi088-accel.h" + +static int bmi088_accel_probe(struct i2c_client *i2c) +{ + struct regmap *regmap; + const struct i2c_device_id *id = i2c_client_get_device_id(i2c); + + regmap = devm_regmap_init_i2c(i2c, &bmi088_regmap_conf); + if (IS_ERR(regmap)) { + dev_err(&i2c->dev, "Failed to initialize i2c regmap\n"); + return PTR_ERR(regmap); + } + + return bmi088_accel_core_probe(&i2c->dev, regmap, i2c->irq, + id->driver_data); +} + +static void bmi088_accel_remove(struct i2c_client *i2c) +{ + bmi088_accel_core_remove(&i2c->dev); +} + +static const struct of_device_id bmi088_of_match[] = { + { .compatible = "bosch,bmi085-accel" }, + { .compatible = "bosch,bmi088-accel" }, + { .compatible = "bosch,bmi090l-accel" }, + { } +}; +MODULE_DEVICE_TABLE(of, bmi088_of_match); + +static const struct i2c_device_id bmi088_accel_id[] = { + { .name = "bmi085-accel", .driver_data = BOSCH_BMI085 }, + { .name = "bmi088-accel", .driver_data = BOSCH_BMI088 }, + { .name = "bmi090l-accel", .driver_data = BOSCH_BMI090L }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bmi088_accel_id); + +static struct i2c_driver bmi088_accel_driver = { + .driver = { + .name = "bmi088_accel_i2c", + .pm = pm_ptr(&bmi088_accel_pm_ops), + .of_match_table = bmi088_of_match, + }, + .probe = bmi088_accel_probe, + .remove = bmi088_accel_remove, + .id_table = bmi088_accel_id, +}; +module_i2c_driver(bmi088_accel_driver); + +MODULE_AUTHOR("Jun Yan "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("BMI088 accelerometer driver (I2C)"); +MODULE_IMPORT_NS("IIO_BMI088"); diff --git a/drivers/iio/accel/bmi088-accel-spi.c b/drivers/iio/accel/bmi088-accel-spi.c new file mode 100644 index 000000000..ae89e162e --- /dev/null +++ b/drivers/iio/accel/bmi088-accel-spi.c @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 3-axis accelerometer driver supporting following Bosch-Sensortec chips: + * - BMI088 + * - BMI085 + * - BMI090L + * + * Copyright (c) 2018-2020, Topic Embedded Products + */ + +#include +#include +#include +#include + +#include "bmi088-accel.h" + +static int bmi088_regmap_spi_write(void *context, const void *data, size_t count) +{ + struct spi_device *spi = context; + + /* Write register is same as generic SPI */ + return spi_write(spi, data, count); +} + +static int bmi088_regmap_spi_read(void *context, const void *reg, + size_t reg_size, void *val, size_t val_size) +{ + struct spi_device *spi = context; + u8 addr[2]; + + addr[0] = *(u8 *)reg; + addr[0] |= BIT(7); /* Set RW = '1' */ + addr[1] = 0; /* Read requires a dummy byte transfer */ + + return spi_write_then_read(spi, addr, sizeof(addr), val, val_size); +} + +static const struct regmap_bus bmi088_regmap_bus = { + .write = bmi088_regmap_spi_write, + .read = bmi088_regmap_spi_read, +}; + +static int bmi088_accel_probe(struct spi_device *spi) +{ + struct regmap *regmap; + const struct spi_device_id *id = spi_get_device_id(spi); + + regmap = devm_regmap_init(&spi->dev, &bmi088_regmap_bus, + spi, &bmi088_regmap_conf); + + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "Failed to initialize spi regmap\n"); + return PTR_ERR(regmap); + } + + return bmi088_accel_core_probe(&spi->dev, regmap, spi->irq, + id->driver_data); +} + +static void bmi088_accel_remove(struct spi_device *spi) +{ + bmi088_accel_core_remove(&spi->dev); +} + +static const struct of_device_id bmi088_of_match[] = { + { .compatible = "bosch,bmi085-accel" }, + { .compatible = "bosch,bmi088-accel" }, + { .compatible = "bosch,bmi090l-accel" }, + { } +}; +MODULE_DEVICE_TABLE(of, bmi088_of_match); + +static const struct spi_device_id bmi088_accel_id[] = { + { .name = "bmi085-accel", .driver_data = BOSCH_BMI085 }, + { .name = "bmi088-accel", .driver_data = BOSCH_BMI088 }, + { .name = "bmi090l-accel", .driver_data = BOSCH_BMI090L }, + { } +}; +MODULE_DEVICE_TABLE(spi, bmi088_accel_id); + +static struct spi_driver bmi088_accel_driver = { + .driver = { + .name = "bmi088_accel_spi", + .pm = pm_ptr(&bmi088_accel_pm_ops), + .of_match_table = bmi088_of_match, + }, + .probe = bmi088_accel_probe, + .remove = bmi088_accel_remove, + .id_table = bmi088_accel_id, +}; +module_spi_driver(bmi088_accel_driver); + +MODULE_AUTHOR("Niek van Agt "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("BMI088 accelerometer driver (SPI)"); +MODULE_IMPORT_NS("IIO_BMI088"); diff --git a/drivers/iio/accel/bmi088-accel.h b/drivers/iio/accel/bmi088-accel.h new file mode 100644 index 000000000..80cd396a3 --- /dev/null +++ b/drivers/iio/accel/bmi088-accel.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef BMI088_ACCEL_H +#define BMI088_ACCEL_H + +#include +#include +#include + +struct device; + +enum bmi_device_type { + BOSCH_BMI085, + BOSCH_BMI088, + BOSCH_BMI090L, + BOSCH_UNKNOWN, +}; + +extern const struct regmap_config bmi088_regmap_conf; +extern const struct dev_pm_ops bmi088_accel_pm_ops; + +int bmi088_accel_core_probe(struct device *dev, struct regmap *regmap, int irq, + enum bmi_device_type type); +void bmi088_accel_core_remove(struct device *dev); + +#endif /* BMI088_ACCEL_H */ diff --git a/drivers/iio/accel/cros_ec_accel_legacy.c b/drivers/iio/accel/cros_ec_accel_legacy.c new file mode 100644 index 000000000..f7e4dc02b --- /dev/null +++ b/drivers/iio/accel/cros_ec_accel_legacy.c @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for older Chrome OS EC accelerometer + * + * Copyright 2017 Google, Inc + * + * This driver uses the memory mapper cros-ec interface to communicate + * with the Chrome OS EC about accelerometer data or older commands. + * Accelerometer access is presented through iio sysfs. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "cros-ec-accel-legacy" + +#define CROS_EC_SENSOR_LEGACY_NUM 2 +/* + * Sensor scale hard coded at 10 bits per g, computed as: + * g / (2^10 - 1) = 0.009586168; with g = 9.80665 m.s^-2 + */ +#define ACCEL_LEGACY_NSCALE 9586168 + +/* + * Sensor frequency is hard-coded to 10Hz. + */ +static const int cros_ec_legacy_sample_freq[] = { 10, 0 }; + +static int cros_ec_accel_legacy_read_cmd(struct iio_dev *indio_dev, + unsigned long scan_mask, s16 *data) +{ + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + int ret; + unsigned int i; + u8 sensor_num; + + /* + * Read all sensor data through a command. + * Save sensor_num, it is assumed to stay. + */ + sensor_num = st->param.info.sensor_num; + st->param.cmd = MOTIONSENSE_CMD_DUMP; + st->param.dump.max_sensor_count = CROS_EC_SENSOR_LEGACY_NUM; + ret = cros_ec_motion_send_host_cmd(st, + sizeof(st->resp->dump) + CROS_EC_SENSOR_LEGACY_NUM * + sizeof(struct ec_response_motion_sensor_data)); + st->param.info.sensor_num = sensor_num; + if (ret != 0) { + dev_warn(&indio_dev->dev, "Unable to read sensor data\n"); + return ret; + } + + for_each_set_bit(i, &scan_mask, iio_get_masklength(indio_dev)) { + *data = st->resp->dump.sensor[sensor_num].data[i] * + st->sign[i]; + data++; + } + + return 0; +} + +static int cros_ec_accel_legacy_read(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + s16 data = 0; + int ret; + int idx = chan->scan_index; + + mutex_lock(&st->cmd_lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = st->read_ec_sensors_data(indio_dev, 1 << idx, &data); + if (ret < 0) + break; + ret = IIO_VAL_INT; + *val = data; + break; + case IIO_CHAN_INFO_SCALE: + WARN_ON(st->type != MOTIONSENSE_TYPE_ACCEL); + *val = 0; + *val2 = ACCEL_LEGACY_NSCALE; + ret = IIO_VAL_INT_PLUS_NANO; + break; + case IIO_CHAN_INFO_CALIBBIAS: + /* Calibration not supported. */ + *val = 0; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = cros_ec_legacy_sample_freq[0]; + *val2 = cros_ec_legacy_sample_freq[1]; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + ret = cros_ec_sensors_core_read(st, chan, val, val2, + mask); + break; + } + mutex_unlock(&st->cmd_lock); + + return ret; +} + +static int cros_ec_accel_legacy_write(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + /* + * Do nothing but don't return an error code to allow calibration + * script to work. + */ + if (mask == IIO_CHAN_INFO_CALIBBIAS) + return 0; + + return -EINVAL; +} + +/** + * cros_ec_accel_legacy_read_avail() - get available values + * @indio_dev: pointer to state information for device + * @chan: channel specification structure table + * @vals: list of available values + * @type: type of data returned + * @length: number of data returned in the array + * @mask: specifies which values to be requested + * + * Return: an error code or IIO_AVAIL_LIST + */ +static int cros_ec_accel_legacy_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, + int *type, + int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *length = ARRAY_SIZE(cros_ec_legacy_sample_freq); + *vals = cros_ec_legacy_sample_freq; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + } + + return -EINVAL; +} + +static const struct iio_info cros_ec_accel_legacy_info = { + .read_raw = &cros_ec_accel_legacy_read, + .write_raw = &cros_ec_accel_legacy_write, + .read_avail = &cros_ec_accel_legacy_read_avail, +}; + +/* + * Present the channel using HTML5 standard: + * need to invert X and Y and invert some lid axis. + */ +#define CROS_EC_ACCEL_ROTATE_AXIS(_axis) \ + ((_axis) == CROS_EC_SENSOR_Z ? CROS_EC_SENSOR_Z : \ + ((_axis) == CROS_EC_SENSOR_X ? CROS_EC_SENSOR_Y : \ + CROS_EC_SENSOR_X)) + +#define CROS_EC_ACCEL_LEGACY_CHAN(_axis) \ + { \ + .type = IIO_ACCEL, \ + .channel2 = IIO_MOD_X + (_axis), \ + .modified = 1, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_all = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .ext_info = cros_ec_sensors_ext_info, \ + .scan_type = { \ + .sign = 's', \ + .realbits = CROS_EC_SENSOR_BITS, \ + .storagebits = CROS_EC_SENSOR_BITS, \ + }, \ + .scan_index = CROS_EC_ACCEL_ROTATE_AXIS(_axis), \ + } \ + +static const struct iio_chan_spec cros_ec_accel_legacy_channels[] = { + CROS_EC_ACCEL_LEGACY_CHAN(CROS_EC_SENSOR_X), + CROS_EC_ACCEL_LEGACY_CHAN(CROS_EC_SENSOR_Y), + CROS_EC_ACCEL_LEGACY_CHAN(CROS_EC_SENSOR_Z), + IIO_CHAN_SOFT_TIMESTAMP(CROS_EC_SENSOR_MAX_AXIS) +}; + +static int cros_ec_accel_legacy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct cros_ec_sensors_core_state *state; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*state)); + if (!indio_dev) + return -ENOMEM; + + ret = cros_ec_sensors_core_init(pdev, indio_dev, true, + cros_ec_sensors_capture); + if (ret) + return ret; + + indio_dev->info = &cros_ec_accel_legacy_info; + state = iio_priv(indio_dev); + + if (state->ec->cmd_readmem != NULL) + state->read_ec_sensors_data = cros_ec_sensors_read_lpc; + else + state->read_ec_sensors_data = cros_ec_accel_legacy_read_cmd; + + indio_dev->channels = cros_ec_accel_legacy_channels; + indio_dev->num_channels = ARRAY_SIZE(cros_ec_accel_legacy_channels); + /* The lid sensor needs to be presented inverted. */ + if (!strcmp(indio_dev->label, "accel-display")) { + state->sign[CROS_EC_SENSOR_X] = -1; + state->sign[CROS_EC_SENSOR_Z] = -1; + } + + return cros_ec_sensors_core_register(dev, indio_dev, NULL); +} + +static struct platform_driver cros_ec_accel_platform_driver = { + .driver = { + .name = DRV_NAME, + }, + .probe = cros_ec_accel_legacy_probe, +}; +module_platform_driver(cros_ec_accel_platform_driver); + +MODULE_DESCRIPTION("ChromeOS EC legacy accelerometer driver"); +MODULE_AUTHOR("Gwendal Grignou "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/iio/accel/da280.c b/drivers/iio/accel/da280.c new file mode 100644 index 000000000..15acbe1c2 --- /dev/null +++ b/drivers/iio/accel/da280.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * IIO driver for the MiraMEMS DA217 and DA280 3-axis accelerometer and + * IIO driver for the MiraMEMS DA226 2-axis accelerometer + * + * Copyright (c) 2016 Hans de Goede + */ + +#include +#include +#include +#include +#include +#include + +#define DA280_REG_CHIP_ID 0x01 +#define DA280_REG_ACC_X_LSB 0x02 +#define DA280_REG_ACC_Y_LSB 0x04 +#define DA280_REG_ACC_Z_LSB 0x06 +#define DA280_REG_MODE_BW 0x11 + +#define DA280_CHIP_ID 0x13 +#define DA280_MODE_ENABLE 0x1e +#define DA280_MODE_DISABLE 0x9e + +/* + * a value of + or -4096 corresponds to + or - 1G + * scale = 9.81 / 4096 = 0.002395019 + */ + +static const int da280_nscale = 2395019; + +#define DA280_CHANNEL(reg, axis) { \ + .type = IIO_ACCEL, \ + .address = reg, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec da280_channels[] = { + DA280_CHANNEL(DA280_REG_ACC_X_LSB, X), + DA280_CHANNEL(DA280_REG_ACC_Y_LSB, Y), + DA280_CHANNEL(DA280_REG_ACC_Z_LSB, Z), +}; + +struct da280_match_data { + const char *name; + int num_channels; +}; + +struct da280_data { + struct i2c_client *client; +}; + +static int da280_enable(struct i2c_client *client, bool enable) +{ + u8 data = enable ? DA280_MODE_ENABLE : DA280_MODE_DISABLE; + + return i2c_smbus_write_byte_data(client, DA280_REG_MODE_BW, data); +} + +static int da280_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct da280_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = i2c_smbus_read_word_data(data->client, chan->address); + if (ret < 0) + return ret; + /* + * Values are 14 bits, stored as 16 bits with the 2 + * least significant bits always 0. + */ + *val = (short)ret >> 2; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = da280_nscale; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static const struct iio_info da280_info = { + .read_raw = da280_read_raw, +}; + +static void da280_disable(void *client) +{ + da280_enable(client, false); +} + +static int da280_probe(struct i2c_client *client) +{ + const struct da280_match_data *match_data; + struct iio_dev *indio_dev; + struct da280_data *data; + int ret; + + ret = i2c_smbus_read_byte_data(client, DA280_REG_CHIP_ID); + if (ret != DA280_CHIP_ID) + return (ret < 0) ? ret : -ENODEV; + + match_data = i2c_get_match_data(client); + if (!match_data) { + dev_err(&client->dev, "Error match-data not set\n"); + return -EINVAL; + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + + indio_dev->info = &da280_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = da280_channels; + indio_dev->num_channels = match_data->num_channels; + indio_dev->name = match_data->name; + + ret = da280_enable(client, true); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(&client->dev, da280_disable, client); + if (ret) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static int da280_suspend(struct device *dev) +{ + return da280_enable(to_i2c_client(dev), false); +} + +static int da280_resume(struct device *dev) +{ + return da280_enable(to_i2c_client(dev), true); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(da280_pm_ops, da280_suspend, da280_resume); + +static const struct da280_match_data da217_match_data = { "da217", 3 }; +static const struct da280_match_data da226_match_data = { "da226", 2 }; +static const struct da280_match_data da280_match_data = { "da280", 3 }; + +static const struct acpi_device_id da280_acpi_match[] = { + { "NSA2513", (kernel_ulong_t)&da217_match_data }, + { "MIRAACC", (kernel_ulong_t)&da280_match_data }, + { } +}; +MODULE_DEVICE_TABLE(acpi, da280_acpi_match); + +static const struct i2c_device_id da280_i2c_id[] = { + { .name = "da217", .driver_data = (kernel_ulong_t)&da217_match_data }, + { .name = "da226", .driver_data = (kernel_ulong_t)&da226_match_data }, + { .name = "da280", .driver_data = (kernel_ulong_t)&da280_match_data }, + { } +}; +MODULE_DEVICE_TABLE(i2c, da280_i2c_id); + +static struct i2c_driver da280_driver = { + .driver = { + .name = "da280", + .acpi_match_table = da280_acpi_match, + .pm = pm_sleep_ptr(&da280_pm_ops), + }, + .probe = da280_probe, + .id_table = da280_i2c_id, +}; + +module_i2c_driver(da280_driver); + +MODULE_AUTHOR("Hans de Goede "); +MODULE_DESCRIPTION("MiraMEMS DA280 3-Axis Accelerometer driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/da311.c b/drivers/iio/accel/da311.c new file mode 100644 index 000000000..dcb69c8ea --- /dev/null +++ b/drivers/iio/accel/da311.c @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * IIO driver for the MiraMEMS DA311 3-axis accelerometer + * + * Copyright (c) 2016 Hans de Goede + * Copyright (c) 2011-2013 MiraMEMS Sensing Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include + +#define DA311_CHIP_ID 0x13 + +/* + * Note register addressed go from 0 - 0x3f and then wrap. + * For some reason there are 2 banks with 0 - 0x3f addresses, + * rather then a single 0-0x7f bank. + */ + +/* Bank 0 regs */ +#define DA311_REG_BANK 0x0000 +#define DA311_REG_LDO_REG 0x0006 +#define DA311_REG_CHIP_ID 0x000f +#define DA311_REG_TEMP_CFG_REG 0x001f +#define DA311_REG_CTRL_REG1 0x0020 +#define DA311_REG_CTRL_REG3 0x0022 +#define DA311_REG_CTRL_REG4 0x0023 +#define DA311_REG_CTRL_REG5 0x0024 +#define DA311_REG_CTRL_REG6 0x0025 +#define DA311_REG_STATUS_REG 0x0027 +#define DA311_REG_OUT_X_L 0x0028 +#define DA311_REG_OUT_X_H 0x0029 +#define DA311_REG_OUT_Y_L 0x002a +#define DA311_REG_OUT_Y_H 0x002b +#define DA311_REG_OUT_Z_L 0x002c +#define DA311_REG_OUT_Z_H 0x002d +#define DA311_REG_INT1_CFG 0x0030 +#define DA311_REG_INT1_SRC 0x0031 +#define DA311_REG_INT1_THS 0x0032 +#define DA311_REG_INT1_DURATION 0x0033 +#define DA311_REG_INT2_CFG 0x0034 +#define DA311_REG_INT2_SRC 0x0035 +#define DA311_REG_INT2_THS 0x0036 +#define DA311_REG_INT2_DURATION 0x0037 +#define DA311_REG_CLICK_CFG 0x0038 +#define DA311_REG_CLICK_SRC 0x0039 +#define DA311_REG_CLICK_THS 0x003a +#define DA311_REG_TIME_LIMIT 0x003b +#define DA311_REG_TIME_LATENCY 0x003c +#define DA311_REG_TIME_WINDOW 0x003d + +/* Bank 1 regs */ +#define DA311_REG_SOFT_RESET 0x0105 +#define DA311_REG_OTP_XOFF_L 0x0110 +#define DA311_REG_OTP_XOFF_H 0x0111 +#define DA311_REG_OTP_YOFF_L 0x0112 +#define DA311_REG_OTP_YOFF_H 0x0113 +#define DA311_REG_OTP_ZOFF_L 0x0114 +#define DA311_REG_OTP_ZOFF_H 0x0115 +#define DA311_REG_OTP_XSO 0x0116 +#define DA311_REG_OTP_YSO 0x0117 +#define DA311_REG_OTP_ZSO 0x0118 +#define DA311_REG_OTP_TRIM_OSC 0x011b +#define DA311_REG_LPF_ABSOLUTE 0x011c +#define DA311_REG_TEMP_OFF1 0x0127 +#define DA311_REG_TEMP_OFF2 0x0128 +#define DA311_REG_TEMP_OFF3 0x0129 +#define DA311_REG_OTP_TRIM_THERM_H 0x011a + +/* + * a value of + or -1024 corresponds to + or - 1G + * scale = 9.81 / 1024 = 0.009580078 + */ + +static const int da311_nscale = 9580078; + +#define DA311_CHANNEL(reg, axis) { \ + .type = IIO_ACCEL, \ + .address = reg, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec da311_channels[] = { + /* | 0x80 comes from the android driver */ + DA311_CHANNEL(DA311_REG_OUT_X_L | 0x80, X), + DA311_CHANNEL(DA311_REG_OUT_Y_L | 0x80, Y), + DA311_CHANNEL(DA311_REG_OUT_Z_L | 0x80, Z), +}; + +struct da311_data { + struct i2c_client *client; +}; + +static int da311_register_mask_write(struct i2c_client *client, u16 addr, + u8 mask, u8 data) +{ + int ret; + u8 tmp_data = 0; + + if (addr & 0xff00) { + /* Select bank 1 */ + ret = i2c_smbus_write_byte_data(client, DA311_REG_BANK, 0x01); + if (ret < 0) + return ret; + } + + if (mask != 0xff) { + ret = i2c_smbus_read_byte_data(client, addr); + if (ret < 0) + return ret; + tmp_data = ret; + } + + tmp_data &= ~mask; + tmp_data |= data & mask; + ret = i2c_smbus_write_byte_data(client, addr & 0xff, tmp_data); + if (ret < 0) + return ret; + + if (addr & 0xff00) { + /* Back to bank 0 */ + ret = i2c_smbus_write_byte_data(client, DA311_REG_BANK, 0x00); + if (ret < 0) + return ret; + } + + return 0; +} + +/* Init sequence taken from the android driver */ +static int da311_reset(struct i2c_client *client) +{ + static const struct { + u16 addr; + u8 mask; + u8 data; + } init_data[] = { + { DA311_REG_TEMP_CFG_REG, 0xff, 0x08 }, + { DA311_REG_CTRL_REG5, 0xff, 0x80 }, + { DA311_REG_CTRL_REG4, 0x30, 0x00 }, + { DA311_REG_CTRL_REG1, 0xff, 0x6f }, + { DA311_REG_TEMP_CFG_REG, 0xff, 0x88 }, + { DA311_REG_LDO_REG, 0xff, 0x02 }, + { DA311_REG_OTP_TRIM_OSC, 0xff, 0x27 }, + { DA311_REG_LPF_ABSOLUTE, 0xff, 0x30 }, + { DA311_REG_TEMP_OFF1, 0xff, 0x3f }, + { DA311_REG_TEMP_OFF2, 0xff, 0xff }, + { DA311_REG_TEMP_OFF3, 0xff, 0x0f }, + }; + int i, ret; + + /* Reset */ + ret = da311_register_mask_write(client, DA311_REG_SOFT_RESET, + 0xff, 0xaa); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(init_data); i++) { + ret = da311_register_mask_write(client, + init_data[i].addr, + init_data[i].mask, + init_data[i].data); + if (ret < 0) + return ret; + } + + return 0; +} + +static int da311_enable(struct i2c_client *client, bool enable) +{ + u8 data = enable ? 0x00 : 0x20; + + return da311_register_mask_write(client, DA311_REG_TEMP_CFG_REG, + 0x20, data); +} + +static int da311_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct da311_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = i2c_smbus_read_word_data(data->client, chan->address); + if (ret < 0) + return ret; + /* + * Values are 12 bits, stored as 16 bits with the 4 + * least significant bits always 0. + */ + *val = (short)ret >> 4; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = da311_nscale; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static const struct iio_info da311_info = { + .read_raw = da311_read_raw, +}; + +static void da311_disable(void *client) +{ + da311_enable(client, false); +} + +static int da311_probe(struct i2c_client *client) +{ + int ret; + struct iio_dev *indio_dev; + struct da311_data *data; + + ret = i2c_smbus_read_byte_data(client, DA311_REG_CHIP_ID); + if (ret != DA311_CHIP_ID) + return (ret < 0) ? ret : -ENODEV; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + + indio_dev->info = &da311_info; + indio_dev->name = "da311"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = da311_channels; + indio_dev->num_channels = ARRAY_SIZE(da311_channels); + + ret = da311_reset(client); + if (ret < 0) + return ret; + + ret = da311_enable(client, true); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(&client->dev, da311_disable, client); + if (ret) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static int da311_suspend(struct device *dev) +{ + return da311_enable(to_i2c_client(dev), false); +} + +static int da311_resume(struct device *dev) +{ + return da311_enable(to_i2c_client(dev), true); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(da311_pm_ops, da311_suspend, da311_resume); + +static const struct i2c_device_id da311_i2c_id[] = { + { .name = "da311" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, da311_i2c_id); + +static struct i2c_driver da311_driver = { + .driver = { + .name = "da311", + .pm = pm_sleep_ptr(&da311_pm_ops), + }, + .probe = da311_probe, + .id_table = da311_i2c_id, +}; + +module_i2c_driver(da311_driver); + +MODULE_AUTHOR("Hans de Goede "); +MODULE_DESCRIPTION("MiraMEMS DA311 3-Axis Accelerometer driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/dmard06.c b/drivers/iio/accel/dmard06.c new file mode 100644 index 000000000..82816fa44 --- /dev/null +++ b/drivers/iio/accel/dmard06.c @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * IIO driver for Domintech DMARD06 accelerometer + * + * Copyright (C) 2016 Aleksei Mamlin + */ + +#include +#include +#include + +#define DMARD06_DRV_NAME "dmard06" + +/* Device data registers */ +#define DMARD06_CHIP_ID_REG 0x0f +#define DMARD06_TOUT_REG 0x40 +#define DMARD06_XOUT_REG 0x41 +#define DMARD06_YOUT_REG 0x42 +#define DMARD06_ZOUT_REG 0x43 +#define DMARD06_CTRL1_REG 0x44 + +/* Device ID value */ +#define DMARD05_CHIP_ID 0x05 +#define DMARD06_CHIP_ID 0x06 +#define DMARD07_CHIP_ID 0x07 + +/* Device values */ +#define DMARD05_AXIS_SCALE_VAL 15625 +#define DMARD06_AXIS_SCALE_VAL 31250 +#define DMARD06_TEMP_CENTER_VAL 25 +#define DMARD06_SIGN_BIT 7 + +/* Device power modes */ +#define DMARD06_MODE_NORMAL 0x27 +#define DMARD06_MODE_POWERDOWN 0x00 + +/* Device channels */ +#define DMARD06_ACCEL_CHANNEL(_axis, _reg) { \ + .type = IIO_ACCEL, \ + .address = _reg, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .modified = 1, \ +} + +#define DMARD06_TEMP_CHANNEL(_reg) { \ + .type = IIO_TEMP, \ + .address = _reg, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ +} + +struct dmard06_data { + struct i2c_client *client; + u8 chip_id; +}; + +static const struct iio_chan_spec dmard06_channels[] = { + DMARD06_ACCEL_CHANNEL(X, DMARD06_XOUT_REG), + DMARD06_ACCEL_CHANNEL(Y, DMARD06_YOUT_REG), + DMARD06_ACCEL_CHANNEL(Z, DMARD06_ZOUT_REG), + DMARD06_TEMP_CHANNEL(DMARD06_TOUT_REG), +}; + +static int dmard06_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct dmard06_data *dmard06 = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = i2c_smbus_read_byte_data(dmard06->client, + chan->address); + if (ret < 0) { + dev_err(&dmard06->client->dev, + "Error reading data: %d\n", ret); + return ret; + } + + *val = sign_extend32(ret, DMARD06_SIGN_BIT); + + if (dmard06->chip_id == DMARD06_CHIP_ID) + *val = *val >> 1; + + switch (chan->type) { + case IIO_ACCEL: + return IIO_VAL_INT; + case IIO_TEMP: + if (dmard06->chip_id != DMARD06_CHIP_ID) + *val = *val / 2; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + *val = DMARD06_TEMP_CENTER_VAL; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ACCEL: + *val = 0; + if (dmard06->chip_id == DMARD06_CHIP_ID) + *val2 = DMARD06_AXIS_SCALE_VAL; + else + *val2 = DMARD05_AXIS_SCALE_VAL; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static const struct iio_info dmard06_info = { + .read_raw = dmard06_read_raw, +}; + +static int dmard06_probe(struct i2c_client *client) +{ + int ret; + struct iio_dev *indio_dev; + struct dmard06_data *dmard06; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "I2C check functionality failed\n"); + return -ENXIO; + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*dmard06)); + if (!indio_dev) + return -ENOMEM; + + dmard06 = iio_priv(indio_dev); + dmard06->client = client; + + ret = i2c_smbus_read_byte_data(dmard06->client, DMARD06_CHIP_ID_REG); + if (ret < 0) { + dev_err(&client->dev, "Error reading chip id: %d\n", ret); + return ret; + } + + if (ret != DMARD05_CHIP_ID && ret != DMARD06_CHIP_ID && + ret != DMARD07_CHIP_ID) { + dev_err(&client->dev, "Invalid chip id: %02d\n", ret); + return -ENODEV; + } + + dmard06->chip_id = ret; + + i2c_set_clientdata(client, indio_dev); + indio_dev->name = DMARD06_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = dmard06_channels; + indio_dev->num_channels = ARRAY_SIZE(dmard06_channels); + indio_dev->info = &dmard06_info; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static int dmard06_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct dmard06_data *dmard06 = iio_priv(indio_dev); + int ret; + + ret = i2c_smbus_write_byte_data(dmard06->client, DMARD06_CTRL1_REG, + DMARD06_MODE_POWERDOWN); + if (ret < 0) + return ret; + + return 0; +} + +static int dmard06_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct dmard06_data *dmard06 = iio_priv(indio_dev); + int ret; + + ret = i2c_smbus_write_byte_data(dmard06->client, DMARD06_CTRL1_REG, + DMARD06_MODE_NORMAL); + if (ret < 0) + return ret; + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(dmard06_pm_ops, dmard06_suspend, + dmard06_resume); + +static const struct i2c_device_id dmard06_id[] = { + { .name = "dmard05" }, + { .name = "dmard06" }, + { .name = "dmard07" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, dmard06_id); + +static const struct of_device_id dmard06_of_match[] = { + { .compatible = "domintech,dmard05" }, + { .compatible = "domintech,dmard06" }, + { .compatible = "domintech,dmard07" }, + { } +}; +MODULE_DEVICE_TABLE(of, dmard06_of_match); + +static struct i2c_driver dmard06_driver = { + .probe = dmard06_probe, + .id_table = dmard06_id, + .driver = { + .name = DMARD06_DRV_NAME, + .of_match_table = dmard06_of_match, + .pm = pm_sleep_ptr(&dmard06_pm_ops), + }, +}; +module_i2c_driver(dmard06_driver); + +MODULE_AUTHOR("Aleksei Mamlin "); +MODULE_DESCRIPTION("Domintech DMARD06 accelerometer driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/dmard09.c b/drivers/iio/accel/dmard09.c new file mode 100644 index 000000000..6f0497ab6 --- /dev/null +++ b/drivers/iio/accel/dmard09.c @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * IIO driver for the 3-axis accelerometer Domintech DMARD09. + * + * Copyright (c) 2016, Jelle van der Waa + */ + +#include +#include +#include +#include +#include + +#define DMARD09_DRV_NAME "dmard09" + +#define DMARD09_REG_CHIPID 0x18 +#define DMARD09_REG_STAT 0x0A +#define DMARD09_REG_X 0x0C +#define DMARD09_REG_Y 0x0E +#define DMARD09_REG_Z 0x10 +#define DMARD09_CHIPID 0x95 + +#define DMARD09_BUF_LEN 8 +#define DMARD09_AXIS_X 0 +#define DMARD09_AXIS_Y 1 +#define DMARD09_AXIS_Z 2 +#define DMARD09_AXIS_X_OFFSET ((DMARD09_AXIS_X + 1) * 2) +#define DMARD09_AXIS_Y_OFFSET ((DMARD09_AXIS_Y + 1) * 2) +#define DMARD09_AXIS_Z_OFFSET ((DMARD09_AXIS_Z + 1) * 2) + +struct dmard09_data { + struct i2c_client *client; +}; + +#define DMARD09_CHANNEL(_axis, offset) { \ + .type = IIO_ACCEL, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .modified = 1, \ + .address = offset, \ + .channel2 = IIO_MOD_##_axis, \ +} + +static const struct iio_chan_spec dmard09_channels[] = { + DMARD09_CHANNEL(X, DMARD09_AXIS_X_OFFSET), + DMARD09_CHANNEL(Y, DMARD09_AXIS_Y_OFFSET), + DMARD09_CHANNEL(Z, DMARD09_AXIS_Z_OFFSET), +}; + +static int dmard09_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct dmard09_data *data = iio_priv(indio_dev); + u8 buf[DMARD09_BUF_LEN]; + int ret; + s16 accel; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* + * Read from the DMAR09_REG_STAT register, since the chip + * caches reads from the individual X, Y, Z registers. + */ + ret = i2c_smbus_read_i2c_block_data(data->client, + DMARD09_REG_STAT, + DMARD09_BUF_LEN, buf); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg %d\n", + DMARD09_REG_STAT); + return ret; + } + + accel = get_unaligned_le16(&buf[chan->address]); + + /* Remove lower 3 bits and sign extend */ + accel <<= 4; + accel >>= 7; + + *val = accel; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + /* 1 g / 32 LSB, in m/s^2 */ + *val2 = IIO_G_TO_M_S_2(NANO / 32); + + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static const struct iio_info dmard09_info = { + .read_raw = dmard09_read_raw, +}; + +static int dmard09_probe(struct i2c_client *client) +{ + int ret; + struct iio_dev *indio_dev; + struct dmard09_data *data; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + + ret = i2c_smbus_read_byte_data(data->client, DMARD09_REG_CHIPID); + if (ret < 0) { + dev_err(&client->dev, "Error reading chip id %d\n", ret); + return ret; + } + + if (ret != DMARD09_CHIPID) { + dev_err(&client->dev, "Invalid chip id %d\n", ret); + return -ENODEV; + } + + i2c_set_clientdata(client, indio_dev); + indio_dev->name = DMARD09_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = dmard09_channels; + indio_dev->num_channels = ARRAY_SIZE(dmard09_channels); + indio_dev->info = &dmard09_info; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id dmard09_id[] = { + { .name = "dmard09" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, dmard09_id); + +static struct i2c_driver dmard09_driver = { + .driver = { + .name = DMARD09_DRV_NAME + }, + .probe = dmard09_probe, + .id_table = dmard09_id, +}; + +module_i2c_driver(dmard09_driver); + +MODULE_AUTHOR("Jelle van der Waa "); +MODULE_DESCRIPTION("DMARD09 3-axis accelerometer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/accel/dmard10.c b/drivers/iio/accel/dmard10.c new file mode 100644 index 000000000..8ba00fd57 --- /dev/null +++ b/drivers/iio/accel/dmard10.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * IIO driver for the 3-axis accelerometer Domintech ARD10. + * + * Copyright (c) 2016 Hans de Goede + * Copyright (c) 2012 Domintech Technology Co., Ltd + */ + +#include +#include +#include +#include +#include + +#define DMARD10_REG_ACTR 0x00 +#define DMARD10_REG_AFEM 0x0c +#define DMARD10_REG_STADR 0x12 +#define DMARD10_REG_STAINT 0x1c +#define DMARD10_REG_MISC2 0x1f +#define DMARD10_REG_PD 0x21 + +#define DMARD10_MODE_OFF 0x00 +#define DMARD10_MODE_STANDBY 0x02 +#define DMARD10_MODE_ACTIVE 0x06 +#define DMARD10_MODE_READ_OTP 0x12 +#define DMARD10_MODE_RESET_DATA_PATH 0x82 + +/* AFEN set 1, ATM[2:0]=b'000 (normal), EN_Z/Y/X/T=1 */ +#define DMARD10_VALUE_AFEM_AFEN_NORMAL 0x8f +/* ODR[3:0]=b'0111 (100Hz), CCK[3:0]=b'0100 (204.8kHZ) */ +#define DMARD10_VALUE_CKSEL_ODR_100_204 0x74 +/* INTC[6:5]=b'00 */ +#define DMARD10_VALUE_INTC 0x00 +/* TAP1/TAP2 Average 2 */ +#define DMARD10_VALUE_TAPNS_AVE_2 0x11 + +#define DMARD10_VALUE_STADR 0x55 +#define DMARD10_VALUE_STAINT 0xaa +#define DMARD10_VALUE_MISC2_OSCA_EN 0x08 +#define DMARD10_VALUE_PD_RST 0x52 + +/* Offsets into the buffer read in dmard10_read_raw() */ +#define DMARD10_X_OFFSET 1 +#define DMARD10_Y_OFFSET 2 +#define DMARD10_Z_OFFSET 3 + +/* + * a value of + or -128 corresponds to + or - 1G + * scale = 9.81 / 128 = 0.076640625 + */ + +static const int dmard10_nscale = 76640625; + +#define DMARD10_CHANNEL(reg, axis) { \ + .type = IIO_ACCEL, \ + .address = reg, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec dmard10_channels[] = { + DMARD10_CHANNEL(DMARD10_X_OFFSET, X), + DMARD10_CHANNEL(DMARD10_Y_OFFSET, Y), + DMARD10_CHANNEL(DMARD10_Z_OFFSET, Z), +}; + +struct dmard10_data { + struct i2c_client *client; +}; + +/* Init sequence taken from the android driver */ +static int dmard10_reset(struct i2c_client *client) +{ + unsigned char buffer[7]; + int ret; + + /* 1. Powerdown reset */ + ret = i2c_smbus_write_byte_data(client, DMARD10_REG_PD, + DMARD10_VALUE_PD_RST); + if (ret < 0) + return ret; + + /* + * 2. ACTR => Standby mode => Download OTP to parameter reg => + * Standby mode => Reset data path => Standby mode + */ + buffer[0] = DMARD10_REG_ACTR; + buffer[1] = DMARD10_MODE_STANDBY; + buffer[2] = DMARD10_MODE_READ_OTP; + buffer[3] = DMARD10_MODE_STANDBY; + buffer[4] = DMARD10_MODE_RESET_DATA_PATH; + buffer[5] = DMARD10_MODE_STANDBY; + ret = i2c_master_send(client, buffer, 6); + if (ret < 0) + return ret; + + /* 3. OSCA_EN = 1, TSTO = b'000 (INT1 = normal, TEST0 = normal) */ + ret = i2c_smbus_write_byte_data(client, DMARD10_REG_MISC2, + DMARD10_VALUE_MISC2_OSCA_EN); + if (ret < 0) + return ret; + + /* 4. AFEN = 1 (AFE will powerdown after ADC) */ + buffer[0] = DMARD10_REG_AFEM; + buffer[1] = DMARD10_VALUE_AFEM_AFEN_NORMAL; + buffer[2] = DMARD10_VALUE_CKSEL_ODR_100_204; + buffer[3] = DMARD10_VALUE_INTC; + buffer[4] = DMARD10_VALUE_TAPNS_AVE_2; + buffer[5] = 0x00; /* DLYC, no delay timing */ + buffer[6] = 0x07; /* INTD=1 push-pull, INTA=1 active high, AUTOT=1 */ + ret = i2c_master_send(client, buffer, 7); + if (ret < 0) + return ret; + + /* 5. Activation mode */ + ret = i2c_smbus_write_byte_data(client, DMARD10_REG_ACTR, + DMARD10_MODE_ACTIVE); + if (ret < 0) + return ret; + + return 0; +} + +/* Shutdown sequence taken from the android driver */ +static int dmard10_shutdown(struct i2c_client *client) +{ + unsigned char buffer[3]; + + buffer[0] = DMARD10_REG_ACTR; + buffer[1] = DMARD10_MODE_STANDBY; + buffer[2] = DMARD10_MODE_OFF; + + return i2c_master_send(client, buffer, 3); +} + +static int dmard10_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct dmard10_data *data = iio_priv(indio_dev); + __le16 buf[4]; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* + * Read 8 bytes starting at the REG_STADR register, trying to + * read the individual X, Y, Z registers will always read 0. + */ + ret = i2c_smbus_read_i2c_block_data(data->client, + DMARD10_REG_STADR, + sizeof(buf), (u8 *)buf); + if (ret < 0) + return ret; + ret = le16_to_cpu(buf[chan->address]); + *val = sign_extend32(ret, 12); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = dmard10_nscale; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static const struct iio_info dmard10_info = { + .read_raw = dmard10_read_raw, +}; + +static void dmard10_shutdown_cleanup(void *client) +{ + dmard10_shutdown(client); +} + +static int dmard10_probe(struct i2c_client *client) +{ + int ret; + struct iio_dev *indio_dev; + struct dmard10_data *data; + + /* These 2 registers have special POR reset values used for id */ + ret = i2c_smbus_read_byte_data(client, DMARD10_REG_STADR); + if (ret != DMARD10_VALUE_STADR) + return (ret < 0) ? ret : -ENODEV; + + ret = i2c_smbus_read_byte_data(client, DMARD10_REG_STAINT); + if (ret != DMARD10_VALUE_STAINT) + return (ret < 0) ? ret : -ENODEV; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + + indio_dev->info = &dmard10_info; + indio_dev->name = "dmard10"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = dmard10_channels; + indio_dev->num_channels = ARRAY_SIZE(dmard10_channels); + + ret = dmard10_reset(client); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(&client->dev, dmard10_shutdown_cleanup, + client); + if (ret) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static int dmard10_suspend(struct device *dev) +{ + return dmard10_shutdown(to_i2c_client(dev)); +} + +static int dmard10_resume(struct device *dev) +{ + return dmard10_reset(to_i2c_client(dev)); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(dmard10_pm_ops, dmard10_suspend, + dmard10_resume); + +static const struct i2c_device_id dmard10_i2c_id[] = { + { .name = "dmard10" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, dmard10_i2c_id); + +static struct i2c_driver dmard10_driver = { + .driver = { + .name = "dmard10", + .pm = pm_sleep_ptr(&dmard10_pm_ops), + }, + .probe = dmard10_probe, + .id_table = dmard10_i2c_id, +}; + +module_i2c_driver(dmard10_driver); + +MODULE_AUTHOR("Hans de Goede "); +MODULE_DESCRIPTION("Domintech ARD10 3-Axis Accelerometer driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/fxls8962af-core.c b/drivers/iio/accel/fxls8962af-core.c new file mode 100644 index 000000000..18d7b09bd --- /dev/null +++ b/drivers/iio/accel/fxls8962af-core.c @@ -0,0 +1,1318 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * NXP FXLS8962AF/FXLS8964AF Accelerometer Core Driver + * + * Copyright 2021 Connected Cars A/S + * + * Datasheet: + * https://www.nxp.com/docs/en/data-sheet/FXLS8962AF.pdf + * https://www.nxp.com/docs/en/data-sheet/FXLS8964AF.pdf + * + * Errata: + * https://www.nxp.com/docs/en/errata/ES_FXLS8962AF.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "fxls8962af.h" + +#define FXLS8962AF_INT_STATUS 0x00 +#define FXLS8962AF_INT_STATUS_SRC_BOOT BIT(0) +#define FXLS8962AF_INT_STATUS_SRC_SDCD_OT BIT(4) +#define FXLS8962AF_INT_STATUS_SRC_BUF BIT(5) +#define FXLS8962AF_INT_STATUS_SRC_DRDY BIT(7) +#define FXLS8962AF_TEMP_OUT 0x01 +#define FXLS8962AF_VECM_LSB 0x02 +#define FXLS8962AF_OUT_X_LSB 0x04 +#define FXLS8962AF_OUT_Y_LSB 0x06 +#define FXLS8962AF_OUT_Z_LSB 0x08 +#define FXLS8962AF_BUF_STATUS 0x0b +#define FXLS8962AF_BUF_STATUS_BUF_CNT GENMASK(5, 0) +#define FXLS8962AF_BUF_STATUS_BUF_OVF BIT(6) +#define FXLS8962AF_BUF_STATUS_BUF_WMRK BIT(7) +#define FXLS8962AF_BUF_X_LSB 0x0c +#define FXLS8962AF_BUF_Y_LSB 0x0e +#define FXLS8962AF_BUF_Z_LSB 0x10 + +#define FXLS8962AF_PROD_REV 0x12 +#define FXLS8962AF_WHO_AM_I 0x13 + +#define FXLS8962AF_SYS_MODE 0x14 +#define FXLS8962AF_SENS_CONFIG1 0x15 +#define FXLS8962AF_SENS_CONFIG1_ACTIVE BIT(0) +#define FXLS8962AF_SENS_CONFIG1_RST BIT(7) +#define FXLS8962AF_SC1_FSR_MASK GENMASK(2, 1) +#define FXLS8962AF_SC1_FSR_PREP(x) FIELD_PREP(FXLS8962AF_SC1_FSR_MASK, (x)) +#define FXLS8962AF_SC1_FSR_GET(x) FIELD_GET(FXLS8962AF_SC1_FSR_MASK, (x)) + +#define FXLS8962AF_SENS_CONFIG2 0x16 +#define FXLS8962AF_SENS_CONFIG3 0x17 +#define FXLS8962AF_SC3_WAKE_ODR_MASK GENMASK(7, 4) +#define FXLS8962AF_SC3_WAKE_ODR_PREP(x) FIELD_PREP(FXLS8962AF_SC3_WAKE_ODR_MASK, (x)) +#define FXLS8962AF_SC3_WAKE_ODR_GET(x) FIELD_GET(FXLS8962AF_SC3_WAKE_ODR_MASK, (x)) +#define FXLS8962AF_SENS_CONFIG4 0x18 +#define FXLS8962AF_SC4_INT_PP_OD_MASK BIT(1) +#define FXLS8962AF_SC4_INT_PP_OD_PREP(x) FIELD_PREP(FXLS8962AF_SC4_INT_PP_OD_MASK, (x)) +#define FXLS8962AF_SC4_INT_POL_MASK BIT(0) +#define FXLS8962AF_SC4_INT_POL_PREP(x) FIELD_PREP(FXLS8962AF_SC4_INT_POL_MASK, (x)) +#define FXLS8962AF_SENS_CONFIG5 0x19 + +#define FXLS8962AF_WAKE_IDLE_LSB 0x1b +#define FXLS8962AF_SLEEP_IDLE_LSB 0x1c +#define FXLS8962AF_ASLP_COUNT_LSB 0x1e + +#define FXLS8962AF_INT_EN 0x20 +#define FXLS8962AF_INT_EN_SDCD_OT_EN BIT(5) +#define FXLS8962AF_INT_EN_BUF_EN BIT(6) +#define FXLS8962AF_INT_PIN_SEL 0x21 +#define FXLS8962AF_INT_PIN_SEL_MASK GENMASK(7, 0) +#define FXLS8962AF_INT_PIN_SEL_INT1 0x00 +#define FXLS8962AF_INT_PIN_SEL_INT2 GENMASK(7, 0) + +#define FXLS8962AF_OFF_X 0x22 +#define FXLS8962AF_OFF_Y 0x23 +#define FXLS8962AF_OFF_Z 0x24 + +#define FXLS8962AF_BUF_CONFIG1 0x26 +#define FXLS8962AF_BC1_BUF_MODE_MASK GENMASK(6, 5) +#define FXLS8962AF_BC1_BUF_MODE_PREP(x) FIELD_PREP(FXLS8962AF_BC1_BUF_MODE_MASK, (x)) +#define FXLS8962AF_BUF_CONFIG2 0x27 +#define FXLS8962AF_BUF_CONFIG2_BUF_WMRK GENMASK(5, 0) + +#define FXLS8962AF_ORIENT_STATUS 0x28 +#define FXLS8962AF_ORIENT_CONFIG 0x29 +#define FXLS8962AF_ORIENT_DBCOUNT 0x2a +#define FXLS8962AF_ORIENT_BF_ZCOMP 0x2b +#define FXLS8962AF_ORIENT_THS_REG 0x2c + +#define FXLS8962AF_SDCD_INT_SRC1 0x2d +#define FXLS8962AF_SDCD_INT_SRC1_X_OT BIT(5) +#define FXLS8962AF_SDCD_INT_SRC1_X_POL BIT(4) +#define FXLS8962AF_SDCD_INT_SRC1_Y_OT BIT(3) +#define FXLS8962AF_SDCD_INT_SRC1_Y_POL BIT(2) +#define FXLS8962AF_SDCD_INT_SRC1_Z_OT BIT(1) +#define FXLS8962AF_SDCD_INT_SRC1_Z_POL BIT(0) +#define FXLS8962AF_SDCD_INT_SRC2 0x2e +#define FXLS8962AF_SDCD_CONFIG1 0x2f +#define FXLS8962AF_SDCD_CONFIG1_Z_OT_EN BIT(3) +#define FXLS8962AF_SDCD_CONFIG1_Y_OT_EN BIT(4) +#define FXLS8962AF_SDCD_CONFIG1_X_OT_EN BIT(5) +#define FXLS8962AF_SDCD_CONFIG1_OT_ELE BIT(7) +#define FXLS8962AF_SDCD_CONFIG2 0x30 +#define FXLS8962AF_SDCD_CONFIG2_SDCD_EN BIT(7) +#define FXLS8962AF_SC2_REF_UPDM_AC GENMASK(6, 5) +#define FXLS8962AF_SDCD_OT_DBCNT 0x31 +#define FXLS8962AF_SDCD_WT_DBCNT 0x32 +#define FXLS8962AF_SDCD_LTHS_LSB 0x33 +#define FXLS8962AF_SDCD_UTHS_LSB 0x35 + +#define FXLS8962AF_SELF_TEST_CONFIG1 0x37 +#define FXLS8962AF_SELF_TEST_CONFIG2 0x38 + +#define FXLS8962AF_MAX_REG 0x38 + +#define FXLS8962AF_DEVICE_ID 0x62 +#define FXLS8964AF_DEVICE_ID 0x84 +#define FXLS8974CF_DEVICE_ID 0x86 +#define FXLS8967AF_DEVICE_ID 0x87 + +/* Raw temp channel offset */ +#define FXLS8962AF_TEMP_CENTER_VAL 25 + +#define FXLS8962AF_AUTO_SUSPEND_DELAY_MS 2000 + +#define FXLS8962AF_FIFO_LENGTH 32 +#define FXLS8962AF_SCALE_TABLE_LEN 4 +#define FXLS8962AF_SAMP_FREQ_TABLE_LEN 13 + +static const int fxls8962af_scale_table[FXLS8962AF_SCALE_TABLE_LEN][2] = { + {0, IIO_G_TO_M_S_2(980000)}, + {0, IIO_G_TO_M_S_2(1950000)}, + {0, IIO_G_TO_M_S_2(3910000)}, + {0, IIO_G_TO_M_S_2(7810000)}, +}; + +static const int fxls8962af_samp_freq_table[FXLS8962AF_SAMP_FREQ_TABLE_LEN][2] = { + {3200, 0}, {1600, 0}, {800, 0}, {400, 0}, {200, 0}, {100, 0}, + {50, 0}, {25, 0}, {12, 500000}, {6, 250000}, {3, 125000}, + {1, 563000}, {0, 781000}, +}; + +struct fxls8962af_chip_info { + const char *name; + const struct iio_chan_spec *channels; + int num_channels; + u8 chip_id; +}; + +struct fxls8962af_data { + struct regmap *regmap; + const struct fxls8962af_chip_info *chip_info; + struct { + __le16 channels[3]; + aligned_s64 ts; + } scan; + int64_t timestamp, old_timestamp; /* Only used in hw fifo mode. */ + struct iio_mount_matrix orientation; + int irq; + u8 watermark; + u8 enable_event; + u16 lower_thres; + u16 upper_thres; +}; + +const struct regmap_config fxls8962af_i2c_regmap_conf = { + .reg_bits = 8, + .val_bits = 8, + .max_register = FXLS8962AF_MAX_REG, +}; +EXPORT_SYMBOL_NS_GPL(fxls8962af_i2c_regmap_conf, "IIO_FXLS8962AF"); + +const struct regmap_config fxls8962af_spi_regmap_conf = { + .reg_bits = 8, + .pad_bits = 8, + .val_bits = 8, + .max_register = FXLS8962AF_MAX_REG, +}; +EXPORT_SYMBOL_NS_GPL(fxls8962af_spi_regmap_conf, "IIO_FXLS8962AF"); + +enum { + fxls8962af_idx_x, + fxls8962af_idx_y, + fxls8962af_idx_z, + fxls8962af_idx_ts, +}; + +enum fxls8962af_int_pin { + FXLS8962AF_PIN_INT1, + FXLS8962AF_PIN_INT2, +}; + +static int fxls8962af_power_on(struct fxls8962af_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + dev_err(dev, "failed to power on\n"); + + return ret; +} + +static int fxls8962af_power_off(struct fxls8962af_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = pm_runtime_put_autosuspend(dev); + if (ret) + dev_err(dev, "failed to power off\n"); + + return ret; +} + +static int fxls8962af_standby(struct fxls8962af_data *data) +{ + return regmap_clear_bits(data->regmap, FXLS8962AF_SENS_CONFIG1, + FXLS8962AF_SENS_CONFIG1_ACTIVE); +} + +static int fxls8962af_active(struct fxls8962af_data *data) +{ + return regmap_update_bits(data->regmap, FXLS8962AF_SENS_CONFIG1, + FXLS8962AF_SENS_CONFIG1_ACTIVE, 1); +} + +static int fxls8962af_is_active(struct fxls8962af_data *data) +{ + unsigned int reg; + int ret; + + ret = regmap_read(data->regmap, FXLS8962AF_SENS_CONFIG1, ®); + if (ret) + return ret; + + return reg & FXLS8962AF_SENS_CONFIG1_ACTIVE; +} + +static int fxls8962af_get_out(struct fxls8962af_data *data, + struct iio_chan_spec const *chan, int *val) +{ + struct device *dev = regmap_get_device(data->regmap); + __le16 raw_val; + int is_active; + int ret; + + is_active = fxls8962af_is_active(data); + if (!is_active) { + ret = fxls8962af_power_on(data); + if (ret) + return ret; + } + + ret = regmap_bulk_read(data->regmap, chan->address, + &raw_val, sizeof(data->lower_thres)); + + if (!is_active) + fxls8962af_power_off(data); + + if (ret) { + dev_err(dev, "failed to get out reg 0x%lx\n", chan->address); + return ret; + } + + *val = sign_extend32(le16_to_cpu(raw_val), + chan->scan_type.realbits - 1); + + return IIO_VAL_INT; +} + +static int fxls8962af_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *type = IIO_VAL_INT_PLUS_NANO; + *vals = (int *)fxls8962af_scale_table; + *length = ARRAY_SIZE(fxls8962af_scale_table) * 2; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *type = IIO_VAL_INT_PLUS_MICRO; + *vals = (int *)fxls8962af_samp_freq_table; + *length = ARRAY_SIZE(fxls8962af_samp_freq_table) * 2; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int fxls8962af_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + default: + return IIO_VAL_INT_PLUS_NANO; + } +} + +static int fxls8962af_update_config(struct fxls8962af_data *data, u8 reg, + u8 mask, u8 val) +{ + int ret; + int is_active; + + is_active = fxls8962af_is_active(data); + if (is_active) { + ret = fxls8962af_standby(data); + if (ret) + return ret; + } + + ret = regmap_update_bits(data->regmap, reg, mask, val); + if (ret) + return ret; + + if (is_active) { + ret = fxls8962af_active(data); + if (ret) + return ret; + } + + return 0; +} + +static int fxls8962af_set_full_scale(struct fxls8962af_data *data, u32 scale) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(fxls8962af_scale_table); i++) + if (scale == fxls8962af_scale_table[i][1]) + break; + + if (i == ARRAY_SIZE(fxls8962af_scale_table)) + return -EINVAL; + + return fxls8962af_update_config(data, FXLS8962AF_SENS_CONFIG1, + FXLS8962AF_SC1_FSR_MASK, + FXLS8962AF_SC1_FSR_PREP(i)); +} + +static unsigned int fxls8962af_read_full_scale(struct fxls8962af_data *data, + int *val) +{ + int ret; + unsigned int reg; + u8 range_idx; + + ret = regmap_read(data->regmap, FXLS8962AF_SENS_CONFIG1, ®); + if (ret) + return ret; + + range_idx = FXLS8962AF_SC1_FSR_GET(reg); + + *val = fxls8962af_scale_table[range_idx][1]; + + return IIO_VAL_INT_PLUS_NANO; +} + +static int fxls8962af_set_samp_freq(struct fxls8962af_data *data, u32 val, + u32 val2) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(fxls8962af_samp_freq_table); i++) + if (val == fxls8962af_samp_freq_table[i][0] && + val2 == fxls8962af_samp_freq_table[i][1]) + break; + + if (i == ARRAY_SIZE(fxls8962af_samp_freq_table)) + return -EINVAL; + + return fxls8962af_update_config(data, FXLS8962AF_SENS_CONFIG3, + FXLS8962AF_SC3_WAKE_ODR_MASK, + FXLS8962AF_SC3_WAKE_ODR_PREP(i)); +} + +static unsigned int fxls8962af_read_samp_freq(struct fxls8962af_data *data, + int *val, int *val2) +{ + int ret; + unsigned int reg; + u8 range_idx; + + ret = regmap_read(data->regmap, FXLS8962AF_SENS_CONFIG3, ®); + if (ret) + return ret; + + range_idx = FXLS8962AF_SC3_WAKE_ODR_GET(reg); + + *val = fxls8962af_samp_freq_table[range_idx][0]; + *val2 = fxls8962af_samp_freq_table[range_idx][1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int fxls8962af_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct fxls8962af_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_TEMP: + case IIO_ACCEL: + return fxls8962af_get_out(data, chan, val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + if (chan->type != IIO_TEMP) + return -EINVAL; + + *val = FXLS8962AF_TEMP_CENTER_VAL; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + *val = MILLIDEGREE_PER_DEGREE; + return IIO_VAL_INT; + case IIO_ACCEL: + *val = 0; + return fxls8962af_read_full_scale(data, val2); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + return fxls8962af_read_samp_freq(data, val, val2); + default: + return -EINVAL; + } +} + +static int fxls8962af_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct fxls8962af_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (val != 0) + return -EINVAL; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = fxls8962af_set_full_scale(data, val2); + + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = fxls8962af_set_samp_freq(data, val, val2); + + iio_device_release_direct(indio_dev); + return ret; + default: + return -EINVAL; + } +} + +static int fxls8962af_event_setup(struct fxls8962af_data *data, int state) +{ + /* Enable wakeup interrupt */ + int mask = FXLS8962AF_INT_EN_SDCD_OT_EN; + int value = state ? mask : 0; + + return regmap_update_bits(data->regmap, FXLS8962AF_INT_EN, mask, value); +} + +static int fxls8962af_set_watermark(struct iio_dev *indio_dev, unsigned val) +{ + struct fxls8962af_data *data = iio_priv(indio_dev); + + if (val > FXLS8962AF_FIFO_LENGTH) + val = FXLS8962AF_FIFO_LENGTH; + + data->watermark = val; + + return 0; +} + +static int __fxls8962af_set_thresholds(struct fxls8962af_data *data, + const struct iio_chan_spec *chan, + enum iio_event_direction dir, + int val) +{ + switch (dir) { + case IIO_EV_DIR_FALLING: + data->lower_thres = val; + return regmap_bulk_write(data->regmap, FXLS8962AF_SDCD_LTHS_LSB, + &data->lower_thres, sizeof(data->lower_thres)); + case IIO_EV_DIR_RISING: + data->upper_thres = val; + return regmap_bulk_write(data->regmap, FXLS8962AF_SDCD_UTHS_LSB, + &data->upper_thres, sizeof(data->upper_thres)); + default: + return -EINVAL; + } +} + +static int fxls8962af_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct fxls8962af_data *data = iio_priv(indio_dev); + int ret; + + if (type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + switch (dir) { + case IIO_EV_DIR_FALLING: + ret = regmap_bulk_read(data->regmap, FXLS8962AF_SDCD_LTHS_LSB, + &data->lower_thres, sizeof(data->lower_thres)); + if (ret) + return ret; + + *val = sign_extend32(data->lower_thres, chan->scan_type.realbits - 1); + return IIO_VAL_INT; + case IIO_EV_DIR_RISING: + ret = regmap_bulk_read(data->regmap, FXLS8962AF_SDCD_UTHS_LSB, + &data->upper_thres, sizeof(data->upper_thres)); + if (ret) + return ret; + + *val = sign_extend32(data->upper_thres, chan->scan_type.realbits - 1); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int fxls8962af_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct fxls8962af_data *data = iio_priv(indio_dev); + int ret, val_masked; + + if (type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + if (val < -2048 || val > 2047) + return -EINVAL; + + if (data->enable_event) + return -EBUSY; + + val_masked = val & GENMASK(11, 0); + if (fxls8962af_is_active(data)) { + ret = fxls8962af_standby(data); + if (ret) + return ret; + + ret = __fxls8962af_set_thresholds(data, chan, dir, val_masked); + if (ret) + return ret; + + return fxls8962af_active(data); + } else { + return __fxls8962af_set_thresholds(data, chan, dir, val_masked); + } +} + +static int +fxls8962af_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct fxls8962af_data *data = iio_priv(indio_dev); + + if (type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + return !!(FXLS8962AF_SDCD_CONFIG1_X_OT_EN & data->enable_event); + case IIO_MOD_Y: + return !!(FXLS8962AF_SDCD_CONFIG1_Y_OT_EN & data->enable_event); + case IIO_MOD_Z: + return !!(FXLS8962AF_SDCD_CONFIG1_Z_OT_EN & data->enable_event); + default: + return -EINVAL; + } +} + +static int +fxls8962af_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct fxls8962af_data *data = iio_priv(indio_dev); + u8 enable_event, enable_bits; + int ret, value; + + if (type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + enable_bits = FXLS8962AF_SDCD_CONFIG1_X_OT_EN; + break; + case IIO_MOD_Y: + enable_bits = FXLS8962AF_SDCD_CONFIG1_Y_OT_EN; + break; + case IIO_MOD_Z: + enable_bits = FXLS8962AF_SDCD_CONFIG1_Z_OT_EN; + break; + default: + return -EINVAL; + } + + if (state) + enable_event = data->enable_event | enable_bits; + else + enable_event = data->enable_event & ~enable_bits; + + if (data->enable_event == enable_event) + return 0; + + ret = fxls8962af_standby(data); + if (ret) + return ret; + + /* Enable events */ + value = enable_event | FXLS8962AF_SDCD_CONFIG1_OT_ELE; + ret = regmap_write(data->regmap, FXLS8962AF_SDCD_CONFIG1, value); + if (ret) + return ret; + + /* + * Enable update of SDCD_REF_X/Y/Z values with the current decimated and + * trimmed X/Y/Z acceleration input data. This allows for acceleration + * slope detection with Data(n) to Data(n–1) always used as the input + * to the window comparator. + */ + value = enable_event ? + FXLS8962AF_SDCD_CONFIG2_SDCD_EN | FXLS8962AF_SC2_REF_UPDM_AC : + 0x00; + ret = regmap_write(data->regmap, FXLS8962AF_SDCD_CONFIG2, value); + if (ret) + return ret; + + ret = fxls8962af_event_setup(data, state); + if (ret) + return ret; + + data->enable_event = enable_event; + + if (data->enable_event) { + fxls8962af_active(data); + ret = fxls8962af_power_on(data); + } else { + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + /* Not in buffered mode so disable power */ + ret = fxls8962af_power_off(data); + + iio_device_release_direct(indio_dev); + } + + return ret; +} + +static const struct iio_event_spec fxls8962af_event[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, +}; + +#define FXLS8962AF_CHANNEL(axis, reg, idx) { \ + .type = IIO_ACCEL, \ + .address = reg, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = idx, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 12, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ + .event_spec = fxls8962af_event, \ + .num_event_specs = ARRAY_SIZE(fxls8962af_event), \ +} + +#define FXLS8962AF_TEMP_CHANNEL { \ + .type = IIO_TEMP, \ + .address = FXLS8962AF_TEMP_OUT, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET),\ + .scan_index = -1, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 8, \ + .storagebits = 8, \ + }, \ +} + +static const struct iio_chan_spec fxls8962af_channels[] = { + FXLS8962AF_CHANNEL(X, FXLS8962AF_OUT_X_LSB, fxls8962af_idx_x), + FXLS8962AF_CHANNEL(Y, FXLS8962AF_OUT_Y_LSB, fxls8962af_idx_y), + FXLS8962AF_CHANNEL(Z, FXLS8962AF_OUT_Z_LSB, fxls8962af_idx_z), + IIO_CHAN_SOFT_TIMESTAMP(fxls8962af_idx_ts), + FXLS8962AF_TEMP_CHANNEL, +}; + +static const struct fxls8962af_chip_info fxls_chip_info_table[] = { + [fxls8962af] = { + .chip_id = FXLS8962AF_DEVICE_ID, + .name = "fxls8962af", + .channels = fxls8962af_channels, + .num_channels = ARRAY_SIZE(fxls8962af_channels), + }, + [fxls8964af] = { + .chip_id = FXLS8964AF_DEVICE_ID, + .name = "fxls8964af", + .channels = fxls8962af_channels, + .num_channels = ARRAY_SIZE(fxls8962af_channels), + }, + [fxls8967af] = { + .chip_id = FXLS8967AF_DEVICE_ID, + .name = "fxls8967af", + .channels = fxls8962af_channels, + .num_channels = ARRAY_SIZE(fxls8962af_channels), + }, + [fxls8974cf] = { + .chip_id = FXLS8974CF_DEVICE_ID, + .name = "fxls8974cf", + .channels = fxls8962af_channels, + .num_channels = ARRAY_SIZE(fxls8962af_channels), + }, +}; + +static const struct iio_info fxls8962af_info = { + .read_raw = &fxls8962af_read_raw, + .write_raw = &fxls8962af_write_raw, + .write_raw_get_fmt = fxls8962af_write_raw_get_fmt, + .read_event_value = fxls8962af_read_event, + .write_event_value = fxls8962af_write_event, + .read_event_config = fxls8962af_read_event_config, + .write_event_config = fxls8962af_write_event_config, + .read_avail = fxls8962af_read_avail, + .hwfifo_set_watermark = fxls8962af_set_watermark, +}; + +static int fxls8962af_reset(struct fxls8962af_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + unsigned int reg; + int ret; + + ret = regmap_set_bits(data->regmap, FXLS8962AF_SENS_CONFIG1, + FXLS8962AF_SENS_CONFIG1_RST); + if (ret) + return ret; + + /* TBOOT1, TBOOT2, specifies we have to wait between 1 - 17.7ms */ + ret = regmap_read_poll_timeout(data->regmap, FXLS8962AF_INT_STATUS, reg, + (reg & FXLS8962AF_INT_STATUS_SRC_BOOT), + 1000, 18000); + if (ret == -ETIMEDOUT) + dev_err(dev, "reset timeout, int_status = 0x%x\n", reg); + + return ret; +} + +static int __fxls8962af_fifo_set_mode(struct fxls8962af_data *data, bool onoff) +{ + int ret; + + /* Enable watermark at max fifo size */ + ret = regmap_update_bits(data->regmap, FXLS8962AF_BUF_CONFIG2, + FXLS8962AF_BUF_CONFIG2_BUF_WMRK, + data->watermark); + if (ret) + return ret; + + return regmap_update_bits(data->regmap, FXLS8962AF_BUF_CONFIG1, + FXLS8962AF_BC1_BUF_MODE_MASK, + FXLS8962AF_BC1_BUF_MODE_PREP(onoff)); +} + +static int fxls8962af_buffer_preenable(struct iio_dev *indio_dev) +{ + return fxls8962af_power_on(iio_priv(indio_dev)); +} + +static int fxls8962af_buffer_postenable(struct iio_dev *indio_dev) +{ + struct fxls8962af_data *data = iio_priv(indio_dev); + int ret; + + fxls8962af_standby(data); + + /* Enable buffer interrupt */ + ret = regmap_set_bits(data->regmap, FXLS8962AF_INT_EN, + FXLS8962AF_INT_EN_BUF_EN); + if (ret) + return ret; + + ret = __fxls8962af_fifo_set_mode(data, true); + + fxls8962af_active(data); + + return ret; +} + +static int fxls8962af_buffer_predisable(struct iio_dev *indio_dev) +{ + struct fxls8962af_data *data = iio_priv(indio_dev); + int ret; + + fxls8962af_standby(data); + + /* Disable buffer interrupt */ + ret = regmap_clear_bits(data->regmap, FXLS8962AF_INT_EN, + FXLS8962AF_INT_EN_BUF_EN); + if (ret) + return ret; + + synchronize_irq(data->irq); + + ret = __fxls8962af_fifo_set_mode(data, false); + + if (data->enable_event) + fxls8962af_active(data); + + return ret; +} + +static int fxls8962af_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct fxls8962af_data *data = iio_priv(indio_dev); + + if (!data->enable_event) + fxls8962af_power_off(data); + + return 0; +} + +static const struct iio_buffer_setup_ops fxls8962af_buffer_ops = { + .preenable = fxls8962af_buffer_preenable, + .postenable = fxls8962af_buffer_postenable, + .predisable = fxls8962af_buffer_predisable, + .postdisable = fxls8962af_buffer_postdisable, +}; + +static int fxls8962af_i2c_raw_read_errata3(struct fxls8962af_data *data, + u16 *buffer, int samples, + int sample_length) +{ + int i, ret; + + for (i = 0; i < samples; i++) { + ret = regmap_raw_read(data->regmap, FXLS8962AF_BUF_X_LSB, + &buffer[i * 3], sample_length); + if (ret) + return ret; + } + + return 0; +} + +static int fxls8962af_fifo_transfer(struct fxls8962af_data *data, + u16 *buffer, int samples) +{ + struct device *dev = regmap_get_device(data->regmap); + int sample_length = 3 * sizeof(*buffer); + int total_length = samples * sample_length; + int ret; + + if (i2c_verify_client(dev) && + data->chip_info->chip_id == FXLS8962AF_DEVICE_ID) + /* + * Due to errata bug (only applicable on fxls8962af): + * E3: FIFO burst read operation error using I2C interface + * We have to avoid burst reads on I2C.. + */ + ret = fxls8962af_i2c_raw_read_errata3(data, buffer, samples, + sample_length); + else + ret = regmap_raw_read(data->regmap, FXLS8962AF_BUF_X_LSB, buffer, + total_length); + + if (ret) + dev_err(dev, "Error transferring data from fifo: %d\n", ret); + + return ret; +} + +static int fxls8962af_fifo_flush(struct iio_dev *indio_dev) +{ + struct fxls8962af_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); + u16 buffer[FXLS8962AF_FIFO_LENGTH * 3]; + uint64_t sample_period; + unsigned int reg; + int64_t tstamp; + int ret, i; + u8 count; + + ret = regmap_read(data->regmap, FXLS8962AF_BUF_STATUS, ®); + if (ret) + return ret; + + if (reg & FXLS8962AF_BUF_STATUS_BUF_OVF) { + dev_err(dev, "Buffer overflow"); + return -EOVERFLOW; + } + + count = reg & FXLS8962AF_BUF_STATUS_BUF_CNT; + if (!count) + return 0; + + count = min(count, FXLS8962AF_FIFO_LENGTH); + + data->old_timestamp = data->timestamp; + data->timestamp = iio_get_time_ns(indio_dev); + + /* + * Approximate timestamps for each of the sample based on the sampling, + * frequency, timestamp for last sample and number of samples. + */ + sample_period = (data->timestamp - data->old_timestamp); + do_div(sample_period, count); + tstamp = data->timestamp - (count - 1) * sample_period; + + ret = fxls8962af_fifo_transfer(data, buffer, count); + if (ret) + return ret; + + /* Demux hw FIFO into kfifo. */ + for (i = 0; i < count; i++) { + int j, bit; + + j = 0; + iio_for_each_active_channel(indio_dev, bit) { + memcpy(&data->scan.channels[j++], &buffer[i * 3 + bit], + sizeof(data->scan.channels[0])); + } + + iio_push_to_buffers_with_ts(indio_dev, &data->scan, + sizeof(data->scan), tstamp); + + tstamp += sample_period; + } + + return count; +} + +static int fxls8962af_event_interrupt(struct iio_dev *indio_dev) +{ + struct fxls8962af_data *data = iio_priv(indio_dev); + s64 ts = iio_get_time_ns(indio_dev); + unsigned int reg; + u64 ev_code; + int ret; + + ret = regmap_read(data->regmap, FXLS8962AF_SDCD_INT_SRC1, ®); + if (ret) + return ret; + + if (reg & FXLS8962AF_SDCD_INT_SRC1_X_OT) { + ev_code = reg & FXLS8962AF_SDCD_INT_SRC1_X_POL ? + IIO_EV_DIR_RISING : IIO_EV_DIR_FALLING; + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X, + IIO_EV_TYPE_THRESH, ev_code), ts); + } + + if (reg & FXLS8962AF_SDCD_INT_SRC1_Y_OT) { + ev_code = reg & FXLS8962AF_SDCD_INT_SRC1_Y_POL ? + IIO_EV_DIR_RISING : IIO_EV_DIR_FALLING; + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X, + IIO_EV_TYPE_THRESH, ev_code), ts); + } + + if (reg & FXLS8962AF_SDCD_INT_SRC1_Z_OT) { + ev_code = reg & FXLS8962AF_SDCD_INT_SRC1_Z_POL ? + IIO_EV_DIR_RISING : IIO_EV_DIR_FALLING; + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X, + IIO_EV_TYPE_THRESH, ev_code), ts); + } + + return 0; +} + +static irqreturn_t fxls8962af_interrupt(int irq, void *p) +{ + struct iio_dev *indio_dev = p; + struct fxls8962af_data *data = iio_priv(indio_dev); + unsigned int reg; + int ret; + + ret = regmap_read(data->regmap, FXLS8962AF_INT_STATUS, ®); + if (ret) + return IRQ_NONE; + + if (reg & FXLS8962AF_INT_STATUS_SRC_BUF) { + ret = fxls8962af_fifo_flush(indio_dev); + if (ret < 0) + return IRQ_NONE; + + return IRQ_HANDLED; + } + + if (reg & FXLS8962AF_INT_STATUS_SRC_SDCD_OT) { + ret = fxls8962af_event_interrupt(indio_dev); + if (ret < 0) + return IRQ_NONE; + + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static void fxls8962af_pm_disable(void *dev_ptr) +{ + struct device *dev = dev_ptr; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_put_noidle(dev); + + fxls8962af_standby(iio_priv(indio_dev)); +} + +static void fxls8962af_get_irq(struct device *dev, + enum fxls8962af_int_pin *pin) +{ + int irq; + + irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT2"); + if (irq > 0) { + *pin = FXLS8962AF_PIN_INT2; + return; + } + + *pin = FXLS8962AF_PIN_INT1; +} + +static int fxls8962af_irq_setup(struct iio_dev *indio_dev, int irq) +{ + struct fxls8962af_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); + unsigned long irq_type; + bool irq_active_high; + enum fxls8962af_int_pin int_pin; + u8 int_pin_sel; + int ret; + + fxls8962af_get_irq(dev, &int_pin); + switch (int_pin) { + case FXLS8962AF_PIN_INT1: + int_pin_sel = FXLS8962AF_INT_PIN_SEL_INT1; + break; + case FXLS8962AF_PIN_INT2: + int_pin_sel = FXLS8962AF_INT_PIN_SEL_INT2; + break; + default: + dev_err(dev, "unsupported int pin selected\n"); + return -EINVAL; + } + + ret = regmap_update_bits(data->regmap, FXLS8962AF_INT_PIN_SEL, + FXLS8962AF_INT_PIN_SEL_MASK, int_pin_sel); + if (ret) + return ret; + + irq_type = irq_get_trigger_type(irq); + switch (irq_type) { + case IRQF_TRIGGER_HIGH: + case IRQF_TRIGGER_RISING: + irq_active_high = true; + break; + case IRQF_TRIGGER_LOW: + case IRQF_TRIGGER_FALLING: + irq_active_high = false; + break; + default: + dev_info(dev, "mode %lx unsupported\n", irq_type); + return -EINVAL; + } + + ret = regmap_update_bits(data->regmap, FXLS8962AF_SENS_CONFIG4, + FXLS8962AF_SC4_INT_POL_MASK, + FXLS8962AF_SC4_INT_POL_PREP(irq_active_high)); + if (ret) + return ret; + + if (device_property_read_bool(dev, "drive-open-drain")) { + ret = regmap_update_bits(data->regmap, FXLS8962AF_SENS_CONFIG4, + FXLS8962AF_SC4_INT_PP_OD_MASK, + FXLS8962AF_SC4_INT_PP_OD_PREP(1)); + if (ret) + return ret; + + irq_type |= IRQF_SHARED; + } + + return devm_request_threaded_irq(dev, + irq, + NULL, fxls8962af_interrupt, + irq_type | IRQF_ONESHOT, + indio_dev->name, indio_dev); +} + +int fxls8962af_core_probe(struct device *dev, struct regmap *regmap, int irq) +{ + struct fxls8962af_data *data; + struct iio_dev *indio_dev; + unsigned int reg; + int ret, i; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + dev_set_drvdata(dev, indio_dev); + data->regmap = regmap; + data->irq = irq; + + ret = iio_read_mount_matrix(dev, &data->orientation); + if (ret) + return ret; + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get vdd regulator\n"); + + ret = regmap_read(data->regmap, FXLS8962AF_WHO_AM_I, ®); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(fxls_chip_info_table); i++) { + if (fxls_chip_info_table[i].chip_id == reg) { + data->chip_info = &fxls_chip_info_table[i]; + break; + } + } + if (i == ARRAY_SIZE(fxls_chip_info_table)) { + dev_err(dev, "failed to match device in table\n"); + return -ENXIO; + } + + indio_dev->channels = data->chip_info->channels; + indio_dev->num_channels = data->chip_info->num_channels; + indio_dev->name = data->chip_info->name; + indio_dev->info = &fxls8962af_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = fxls8962af_reset(data); + if (ret) + return ret; + + if (irq) { + ret = fxls8962af_irq_setup(indio_dev, irq); + if (ret) + return ret; + + ret = devm_iio_kfifo_buffer_setup(dev, indio_dev, + &fxls8962af_buffer_ops); + if (ret) + return ret; + } + + ret = pm_runtime_set_active(dev); + if (ret) + return ret; + + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, FXLS8962AF_AUTO_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + + ret = devm_add_action_or_reset(dev, fxls8962af_pm_disable, dev); + if (ret) + return ret; + + if (device_property_read_bool(dev, "wakeup-source")) { + ret = devm_device_init_wakeup(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to init wakeup\n"); + } + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS_GPL(fxls8962af_core_probe, "IIO_FXLS8962AF"); + +static int fxls8962af_runtime_suspend(struct device *dev) +{ + struct fxls8962af_data *data = iio_priv(dev_get_drvdata(dev)); + int ret; + + ret = fxls8962af_standby(data); + if (ret) { + dev_err(dev, "powering off device failed\n"); + return ret; + } + + return 0; +} + +static int fxls8962af_runtime_resume(struct device *dev) +{ + struct fxls8962af_data *data = iio_priv(dev_get_drvdata(dev)); + + return fxls8962af_active(data); +} + +static int fxls8962af_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct fxls8962af_data *data = iio_priv(indio_dev); + + if (device_may_wakeup(dev) && data->enable_event) { + enable_irq_wake(data->irq); + + /* + * Disable buffer, as the buffer is so small the device will wake + * almost immediately. + */ + if (iio_buffer_enabled(indio_dev)) + fxls8962af_buffer_predisable(indio_dev); + } else { + fxls8962af_runtime_suspend(dev); + } + + return 0; +} + +static int fxls8962af_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct fxls8962af_data *data = iio_priv(indio_dev); + + if (device_may_wakeup(dev) && data->enable_event) { + disable_irq_wake(data->irq); + + if (iio_buffer_enabled(indio_dev)) + fxls8962af_buffer_postenable(indio_dev); + } else { + fxls8962af_runtime_resume(dev); + } + + return 0; +} + +EXPORT_NS_GPL_DEV_PM_OPS(fxls8962af_pm_ops, IIO_FXLS8962AF) = { + SYSTEM_SLEEP_PM_OPS(fxls8962af_suspend, fxls8962af_resume) + RUNTIME_PM_OPS(fxls8962af_runtime_suspend, fxls8962af_runtime_resume, NULL) +}; + +MODULE_AUTHOR("Sean Nyekjaer "); +MODULE_DESCRIPTION("NXP FXLS8962AF/FXLS8964AF accelerometer driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/fxls8962af-i2c.c b/drivers/iio/accel/fxls8962af-i2c.c new file mode 100644 index 000000000..e72842b14 --- /dev/null +++ b/drivers/iio/accel/fxls8962af-i2c.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * NXP FXLS8962AF/FXLS8964AF Accelerometer I2C Driver + * + * Copyright 2021 Connected Cars A/S + */ + +#include +#include +#include +#include +#include + +#include "fxls8962af.h" + +static int fxls8962af_probe(struct i2c_client *client) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, &fxls8962af_i2c_regmap_conf); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Failed to initialize i2c regmap\n"); + return PTR_ERR(regmap); + } + + return fxls8962af_core_probe(&client->dev, regmap, client->irq); +} + +static const struct i2c_device_id fxls8962af_id[] = { + { .name = "fxls8962af", .driver_data = fxls8962af }, + { .name = "fxls8964af", .driver_data = fxls8964af }, + { .name = "fxls8967af", .driver_data = fxls8967af }, + { .name = "fxls8974cf", .driver_data = fxls8974cf }, + { } +}; +MODULE_DEVICE_TABLE(i2c, fxls8962af_id); + +static const struct of_device_id fxls8962af_of_match[] = { + { .compatible = "nxp,fxls8962af" }, + { .compatible = "nxp,fxls8964af" }, + { } +}; +MODULE_DEVICE_TABLE(of, fxls8962af_of_match); + +static struct i2c_driver fxls8962af_driver = { + .driver = { + .name = "fxls8962af_i2c", + .of_match_table = fxls8962af_of_match, + .pm = pm_ptr(&fxls8962af_pm_ops), + }, + .probe = fxls8962af_probe, + .id_table = fxls8962af_id, +}; +module_i2c_driver(fxls8962af_driver); + +MODULE_AUTHOR("Sean Nyekjaer "); +MODULE_DESCRIPTION("NXP FXLS8962AF/FXLS8964AF accelerometer i2c driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_FXLS8962AF"); diff --git a/drivers/iio/accel/fxls8962af-spi.c b/drivers/iio/accel/fxls8962af-spi.c new file mode 100644 index 000000000..baf3c4352 --- /dev/null +++ b/drivers/iio/accel/fxls8962af-spi.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * NXP FXLS8962AF/FXLS8964AF Accelerometer SPI Driver + * + * Copyright 2021 Connected Cars A/S + */ + +#include +#include +#include +#include +#include + +#include "fxls8962af.h" + +static int fxls8962af_probe(struct spi_device *spi) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_spi(spi, &fxls8962af_spi_regmap_conf); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "Failed to initialize spi regmap\n"); + return PTR_ERR(regmap); + } + + return fxls8962af_core_probe(&spi->dev, regmap, spi->irq); +} + +static const struct of_device_id fxls8962af_spi_of_match[] = { + { .compatible = "nxp,fxls8962af" }, + { .compatible = "nxp,fxls8964af" }, + { } +}; +MODULE_DEVICE_TABLE(of, fxls8962af_spi_of_match); + +static const struct spi_device_id fxls8962af_spi_id_table[] = { + { .name = "fxls8962af", .driver_data = fxls8962af }, + { .name = "fxls8964af", .driver_data = fxls8964af }, + { } +}; +MODULE_DEVICE_TABLE(spi, fxls8962af_spi_id_table); + +static struct spi_driver fxls8962af_driver = { + .driver = { + .name = "fxls8962af_spi", + .pm = pm_ptr(&fxls8962af_pm_ops), + .of_match_table = fxls8962af_spi_of_match, + }, + .probe = fxls8962af_probe, + .id_table = fxls8962af_spi_id_table, +}; +module_spi_driver(fxls8962af_driver); + +MODULE_AUTHOR("Sean Nyekjaer "); +MODULE_DESCRIPTION("NXP FXLS8962AF/FXLS8964AF accelerometer spi driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_FXLS8962AF"); diff --git a/drivers/iio/accel/fxls8962af.h b/drivers/iio/accel/fxls8962af.h new file mode 100644 index 000000000..1c9adfc8c --- /dev/null +++ b/drivers/iio/accel/fxls8962af.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2021 Connected Cars A/S + */ +#ifndef _FXLS8962AF_H_ +#define _FXLS8962AF_H_ + +struct regmap; +struct device; + +enum { + fxls8962af, + fxls8964af, + fxls8967af, + fxls8974cf, +}; + +int fxls8962af_core_probe(struct device *dev, struct regmap *regmap, int irq); + +extern const struct dev_pm_ops fxls8962af_pm_ops; +extern const struct regmap_config fxls8962af_i2c_regmap_conf; +extern const struct regmap_config fxls8962af_spi_regmap_conf; + +#endif /* _FXLS8962AF_H_ */ diff --git a/drivers/iio/accel/hid-sensor-accel-3d.c b/drivers/iio/accel/hid-sensor-accel-3d.c new file mode 100644 index 000000000..eb521125f --- /dev/null +++ b/drivers/iio/accel/hid-sensor-accel-3d.c @@ -0,0 +1,447 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HID Sensors Driver + * Copyright (c) 2012, Intel Corporation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "../common/hid-sensors/hid-sensor-trigger.h" + +enum accel_3d_channel { + CHANNEL_SCAN_INDEX_X, + CHANNEL_SCAN_INDEX_Y, + CHANNEL_SCAN_INDEX_Z, + ACCEL_3D_CHANNEL_MAX, +}; + +#define CHANNEL_SCAN_INDEX_TIMESTAMP ACCEL_3D_CHANNEL_MAX +struct accel_3d_state { + struct hid_sensor_hub_callbacks callbacks; + struct hid_sensor_common common_attributes; + struct hid_sensor_hub_attribute_info accel[ACCEL_3D_CHANNEL_MAX]; + /* Ensure timestamp is naturally aligned */ + struct { + u32 accel_val[3]; + aligned_s64 timestamp; + } scan; + int scale_pre_decml; + int scale_post_decml; + int scale_precision; + int value_offset; + int64_t timestamp; +}; + +static const u32 accel_3d_addresses[ACCEL_3D_CHANNEL_MAX] = { + HID_USAGE_SENSOR_ACCEL_X_AXIS, + HID_USAGE_SENSOR_ACCEL_Y_AXIS, + HID_USAGE_SENSOR_ACCEL_Z_AXIS +}; + +static const u32 accel_3d_sensitivity_addresses[] = { + HID_USAGE_SENSOR_DATA_ACCELERATION, +}; + +/* Channel definitions */ +static const struct iio_chan_spec accel_3d_channels[] = { + { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + .scan_index = CHANNEL_SCAN_INDEX_X, + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + .scan_index = CHANNEL_SCAN_INDEX_Y, + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + .scan_index = CHANNEL_SCAN_INDEX_Z, + }, + IIO_CHAN_SOFT_TIMESTAMP(CHANNEL_SCAN_INDEX_TIMESTAMP) +}; + +/* Channel definitions */ +static const struct iio_chan_spec gravity_channels[] = { + { + .type = IIO_GRAVITY, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + .scan_index = CHANNEL_SCAN_INDEX_X, + }, { + .type = IIO_GRAVITY, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + .scan_index = CHANNEL_SCAN_INDEX_Y, + }, { + .type = IIO_GRAVITY, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + .scan_index = CHANNEL_SCAN_INDEX_Z, + }, + IIO_CHAN_SOFT_TIMESTAMP(CHANNEL_SCAN_INDEX_TIMESTAMP), +}; + +/* Channel read_raw handler */ +static int accel_3d_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct accel_3d_state *accel_state = iio_priv(indio_dev); + int report_id = -1; + u32 address; + int ret_type; + s32 min; + struct hid_sensor_hub_device *hsdev = + accel_state->common_attributes.hsdev; + + *val = 0; + *val2 = 0; + switch (mask) { + case IIO_CHAN_INFO_RAW: + hid_sensor_power_state(&accel_state->common_attributes, true); + report_id = accel_state->accel[chan->scan_index].report_id; + min = accel_state->accel[chan->scan_index].logical_minimum; + address = accel_3d_addresses[chan->scan_index]; + if (report_id >= 0) + *val = sensor_hub_input_attr_get_raw_value( + accel_state->common_attributes.hsdev, + hsdev->usage, address, report_id, + SENSOR_HUB_SYNC, + min < 0); + else { + *val = 0; + hid_sensor_power_state(&accel_state->common_attributes, + false); + return -EINVAL; + } + hid_sensor_power_state(&accel_state->common_attributes, false); + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + *val = accel_state->scale_pre_decml; + *val2 = accel_state->scale_post_decml; + ret_type = accel_state->scale_precision; + break; + case IIO_CHAN_INFO_OFFSET: + *val = accel_state->value_offset; + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret_type = hid_sensor_read_samp_freq_value( + &accel_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret_type = hid_sensor_read_raw_hyst_value( + &accel_state->common_attributes, val, val2); + break; + default: + ret_type = -EINVAL; + break; + } + + return ret_type; +} + +/* Channel write_raw handler */ +static int accel_3d_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct accel_3d_state *accel_state = iio_priv(indio_dev); + int ret = 0; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = hid_sensor_write_samp_freq_value( + &accel_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret = hid_sensor_write_raw_hyst_value( + &accel_state->common_attributes, val, val2); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info accel_3d_info = { + .read_raw = &accel_3d_read_raw, + .write_raw = &accel_3d_write_raw, +}; + +/* Function to push data to buffer */ +static void hid_sensor_push_data(struct iio_dev *indio_dev, void *data, + int len, int64_t timestamp) +{ + dev_dbg(&indio_dev->dev, "hid_sensor_push_data\n"); + iio_push_to_buffers_with_ts(indio_dev, data, len, timestamp); +} + +/* Callback handler to send event after all samples are received and captured */ +static int accel_3d_proc_event(struct hid_sensor_hub_device *hsdev, + u32 usage_id, void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct accel_3d_state *accel_state = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "accel_3d_proc_event\n"); + if (atomic_read(&accel_state->common_attributes.data_ready)) { + if (!accel_state->timestamp) + accel_state->timestamp = iio_get_time_ns(indio_dev); + + hid_sensor_push_data(indio_dev, + &accel_state->scan, + sizeof(accel_state->scan), + accel_state->timestamp); + + accel_state->timestamp = 0; + } + + return 0; +} + +/* Capture samples in local storage */ +static int accel_3d_capture_sample(struct hid_sensor_hub_device *hsdev, + u32 usage_id, + size_t raw_len, char *raw_data, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct accel_3d_state *accel_state = iio_priv(indio_dev); + int offset; + int ret = -EINVAL; + + switch (usage_id) { + case HID_USAGE_SENSOR_ACCEL_X_AXIS: + case HID_USAGE_SENSOR_ACCEL_Y_AXIS: + case HID_USAGE_SENSOR_ACCEL_Z_AXIS: + offset = usage_id - HID_USAGE_SENSOR_ACCEL_X_AXIS; + accel_state->scan.accel_val[CHANNEL_SCAN_INDEX_X + offset] = + *(u32 *)raw_data; + ret = 0; + break; + case HID_USAGE_SENSOR_TIME_TIMESTAMP: + accel_state->timestamp = + hid_sensor_convert_timestamp( + &accel_state->common_attributes, + *(int64_t *)raw_data); + ret = 0; + break; + default: + break; + } + + return ret; +} + +/* Parse report which is specific to an usage id*/ +static int accel_3d_parse_report(struct platform_device *pdev, + struct hid_sensor_hub_device *hsdev, + struct iio_chan_spec *channels, + u32 usage_id, + struct accel_3d_state *st) +{ + int ret; + + for (unsigned int ch = CHANNEL_SCAN_INDEX_X; ch <= CHANNEL_SCAN_INDEX_Z; ch++) { + ret = sensor_hub_input_get_attribute_info(hsdev, + HID_INPUT_REPORT, + usage_id, + HID_USAGE_SENSOR_ACCEL_X_AXIS + ch, + &st->accel[ch]); + if (ret < 0) + break; + channels[ch].scan_type = (struct iio_scan_type) { + .format = 's', + .realbits = BYTES_TO_BITS(st->accel[ch].size), + .storagebits = BITS_PER_TYPE(u32), + }; + } + dev_dbg(&pdev->dev, "accel_3d %x:%x, %x:%x, %x:%x\n", + st->accel[0].index, + st->accel[0].report_id, + st->accel[1].index, st->accel[1].report_id, + st->accel[2].index, st->accel[2].report_id); + + st->scale_precision = hid_sensor_format_scale( + hsdev->usage, + &st->accel[CHANNEL_SCAN_INDEX_X], + &st->scale_pre_decml, &st->scale_post_decml); + + return ret; +} + +/* Function to initialize the processing for usage id */ +static int hid_accel_3d_probe(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + int ret = 0; + const char *name; + struct iio_dev *indio_dev; + struct accel_3d_state *accel_state; + const struct iio_chan_spec *channel_spec; + int channel_size; + + indio_dev = devm_iio_device_alloc(&pdev->dev, + sizeof(struct accel_3d_state)); + if (!indio_dev) + return -ENOMEM; + + platform_set_drvdata(pdev, indio_dev); + + accel_state = iio_priv(indio_dev); + accel_state->common_attributes.hsdev = hsdev; + accel_state->common_attributes.pdev = pdev; + + if (hsdev->usage == HID_USAGE_SENSOR_ACCEL_3D) { + name = "accel_3d"; + channel_spec = accel_3d_channels; + channel_size = sizeof(accel_3d_channels); + indio_dev->num_channels = ARRAY_SIZE(accel_3d_channels); + } else { + name = "gravity"; + channel_spec = gravity_channels; + channel_size = sizeof(gravity_channels); + indio_dev->num_channels = ARRAY_SIZE(gravity_channels); + } + ret = hid_sensor_parse_common_attributes(hsdev, + hsdev->usage, + &accel_state->common_attributes, + accel_3d_sensitivity_addresses, + ARRAY_SIZE(accel_3d_sensitivity_addresses)); + if (ret) { + dev_err(&pdev->dev, "failed to setup common attributes\n"); + return ret; + } + indio_dev->channels = devm_kmemdup(&pdev->dev, channel_spec, + channel_size, GFP_KERNEL); + + if (!indio_dev->channels) { + dev_err(&pdev->dev, "failed to duplicate channels\n"); + return -ENOMEM; + } + ret = accel_3d_parse_report(pdev, hsdev, + (struct iio_chan_spec *)indio_dev->channels, + hsdev->usage, accel_state); + if (ret) { + dev_err(&pdev->dev, "failed to setup attributes\n"); + return ret; + } + + indio_dev->info = &accel_3d_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + atomic_set(&accel_state->common_attributes.data_ready, 0); + + ret = hid_sensor_setup_trigger(indio_dev, name, + &accel_state->common_attributes); + if (ret < 0) { + dev_err(&pdev->dev, "trigger setup failed\n"); + return ret; + } + + accel_state->callbacks.send_event = accel_3d_proc_event; + accel_state->callbacks.capture_sample = accel_3d_capture_sample; + accel_state->callbacks.pdev = pdev; + ret = sensor_hub_register_callback(hsdev, hsdev->usage, + &accel_state->callbacks); + if (ret < 0) { + dev_err(&pdev->dev, "callback reg failed\n"); + goto error_remove_trigger; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "device register failed\n"); + goto error_remove_callback; + } + + return ret; + +error_remove_callback: + sensor_hub_remove_callback(hsdev, hsdev->usage); +error_remove_trigger: + hid_sensor_remove_trigger(indio_dev, &accel_state->common_attributes); + return ret; +} + +/* Function to deinitialize the processing for usage id */ +static void hid_accel_3d_remove(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct accel_3d_state *accel_state = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + sensor_hub_remove_callback(hsdev, hsdev->usage); + hid_sensor_remove_trigger(indio_dev, &accel_state->common_attributes); +} + +static const struct platform_device_id hid_accel_3d_ids[] = { + { + /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-200073", + }, + { /* gravity sensor */ + .name = "HID-SENSOR-20007b", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, hid_accel_3d_ids); + +static struct platform_driver hid_accel_3d_platform_driver = { + .id_table = hid_accel_3d_ids, + .driver = { + .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, + }, + .probe = hid_accel_3d_probe, + .remove = hid_accel_3d_remove, +}; +module_platform_driver(hid_accel_3d_platform_driver); + +MODULE_DESCRIPTION("HID Sensor Accel 3D"); +MODULE_AUTHOR("Srinivas Pandruvada "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HID"); diff --git a/drivers/iio/accel/kionix-kx022a-i2c.c b/drivers/iio/accel/kionix-kx022a-i2c.c new file mode 100644 index 000000000..42388636c --- /dev/null +++ b/drivers/iio/accel/kionix-kx022a-i2c.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 ROHM Semiconductors + * + * ROHM/KIONIX accelerometer driver + */ + +#include +#include +#include +#include + +#include "kionix-kx022a.h" + +static int kx022a_i2c_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + const struct kx022a_chip_info *chip_info; + struct regmap *regmap; + + if (!i2c->irq) { + dev_err(dev, "No IRQ configured\n"); + return -EINVAL; + } + + chip_info = i2c_get_match_data(i2c); + if (!chip_info) + return -EINVAL; + + regmap = devm_regmap_init_i2c(i2c, chip_info->regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Failed to initialize Regmap\n"); + + return kx022a_probe_internal(dev, chip_info); +} + +static const struct i2c_device_id kx022a_i2c_id[] = { + { .name = "kx022a", .driver_data = (kernel_ulong_t)&kx022a_chip_info }, + { .name = "kx132-1211", .driver_data = (kernel_ulong_t)&kx132_chip_info }, + { .name = "kx134-1211", .driver_data = (kernel_ulong_t)&kx134_chip_info }, + { .name = "kx132acr-lbz", .driver_data = (kernel_ulong_t)&kx132acr_chip_info }, + { .name = "kx134acr-lbz", .driver_data = (kernel_ulong_t)&kx134acr_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, kx022a_i2c_id); + +static const struct of_device_id kx022a_of_match[] = { + { .compatible = "kionix,kx022a", .data = &kx022a_chip_info }, + { .compatible = "kionix,kx132-1211", .data = &kx132_chip_info }, + { .compatible = "kionix,kx134-1211", .data = &kx134_chip_info }, + { .compatible = "rohm,kx132acr-lbz", .data = &kx132acr_chip_info }, + { .compatible = "rohm,kx134acr-lbz", .data = &kx134acr_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, kx022a_of_match); + +static struct i2c_driver kx022a_i2c_driver = { + .driver = { + .name = "kx022a-i2c", + .of_match_table = kx022a_of_match, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .probe = kx022a_i2c_probe, + .id_table = kx022a_i2c_id, +}; +module_i2c_driver(kx022a_i2c_driver); + +MODULE_DESCRIPTION("ROHM/Kionix KX022A accelerometer driver"); +MODULE_AUTHOR("Matti Vaittinen "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_KX022A"); diff --git a/drivers/iio/accel/kionix-kx022a-spi.c b/drivers/iio/accel/kionix-kx022a-spi.c new file mode 100644 index 000000000..e30d21083 --- /dev/null +++ b/drivers/iio/accel/kionix-kx022a-spi.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 ROHM Semiconductors + * + * ROHM/KIONIX accelerometer driver + */ + +#include +#include +#include +#include + +#include "kionix-kx022a.h" + +static int kx022a_spi_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + const struct kx022a_chip_info *chip_info; + struct regmap *regmap; + + if (!spi->irq) { + dev_err(dev, "No IRQ configured\n"); + return -EINVAL; + } + + chip_info = spi_get_device_match_data(spi); + if (!chip_info) + return -EINVAL; + + regmap = devm_regmap_init_spi(spi, chip_info->regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Failed to initialize Regmap\n"); + + return kx022a_probe_internal(dev, chip_info); +} + +static const struct spi_device_id kx022a_id[] = { + { .name = "kx022a", .driver_data = (kernel_ulong_t)&kx022a_chip_info }, + { .name = "kx132-1211", .driver_data = (kernel_ulong_t)&kx132_chip_info }, + { .name = "kx134-1211", .driver_data = (kernel_ulong_t)&kx134_chip_info }, + { .name = "kx132acr-lbz", .driver_data = (kernel_ulong_t)&kx132acr_chip_info }, + { .name = "kx134acr-lbz", .driver_data = (kernel_ulong_t)&kx134acr_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, kx022a_id); + +static const struct of_device_id kx022a_of_match[] = { + { .compatible = "kionix,kx022a", .data = &kx022a_chip_info }, + { .compatible = "kionix,kx132-1211", .data = &kx132_chip_info }, + { .compatible = "kionix,kx134-1211", .data = &kx134_chip_info }, + { .compatible = "rohm,kx132acr-lbz", .data = &kx132acr_chip_info }, + { .compatible = "rohm,kx134acr-lbz", .data = &kx134acr_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, kx022a_of_match); + +static struct spi_driver kx022a_spi_driver = { + .driver = { + .name = "kx022a-spi", + .of_match_table = kx022a_of_match, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .probe = kx022a_spi_probe, + .id_table = kx022a_id, +}; +module_spi_driver(kx022a_spi_driver); + +MODULE_DESCRIPTION("ROHM/Kionix kx022A accelerometer driver"); +MODULE_AUTHOR("Matti Vaittinen "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_KX022A"); diff --git a/drivers/iio/accel/kionix-kx022a.c b/drivers/iio/accel/kionix-kx022a.c new file mode 100644 index 000000000..02dd1db7a --- /dev/null +++ b/drivers/iio/accel/kionix-kx022a.c @@ -0,0 +1,1456 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 ROHM Semiconductors + * + * ROHM/KIONIX accelerometer driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "kionix-kx022a.h" + +/* + * The KX022A has FIFO which can store 43 samples of HiRes data from 2 + * channels. This equals to 43 (samples) * 3 (channels) * 2 (bytes/sample) to + * 258 bytes of sample data. The quirk to know is that the amount of bytes in + * the FIFO is advertised via 8 bit register (max value 255). The thing to note + * is that full 258 bytes of data is indicated using the max value 255. + */ +#define KX022A_FIFO_LENGTH 43 +#define KX022A_FIFO_FULL_VALUE 255 +#define KX022A_SOFT_RESET_WAIT_TIME_US (5 * USEC_PER_MSEC) +#define KX022A_SOFT_RESET_TOTAL_WAIT_TIME_US (500 * USEC_PER_MSEC) + +/* 3 axis, 2 bytes of data for each of the axis */ +#define KX022A_FIFO_SAMPLES_SIZE_BYTES 6 +#define KX022A_FIFO_MAX_BYTES \ + (KX022A_FIFO_LENGTH * KX022A_FIFO_SAMPLES_SIZE_BYTES) + +enum { + KX022A_STATE_SAMPLE, + KX022A_STATE_FIFO, +}; + +/* kx022a Regmap configs */ +static const struct regmap_range kx022a_volatile_ranges[] = { + { + .range_min = KX022A_REG_XHP_L, + .range_max = KX022A_REG_COTR, + }, { + .range_min = KX022A_REG_TSCP, + .range_max = KX022A_REG_INT_REL, + }, { + /* The reset bit will be cleared by sensor */ + .range_min = KX022A_REG_CNTL2, + .range_max = KX022A_REG_CNTL2, + }, { + .range_min = KX022A_REG_BUF_STATUS_1, + .range_max = KX022A_REG_BUF_READ, + }, +}; + +static const struct regmap_access_table kx022a_volatile_regs = { + .yes_ranges = &kx022a_volatile_ranges[0], + .n_yes_ranges = ARRAY_SIZE(kx022a_volatile_ranges), +}; + +static const struct regmap_range kx022a_precious_ranges[] = { + { + .range_min = KX022A_REG_INT_REL, + .range_max = KX022A_REG_INT_REL, + }, +}; + +static const struct regmap_access_table kx022a_precious_regs = { + .yes_ranges = &kx022a_precious_ranges[0], + .n_yes_ranges = ARRAY_SIZE(kx022a_precious_ranges), +}; + +/* + * The HW does not set WHO_AM_I reg as read-only but we don't want to write it + * so we still include it in the read-only ranges. + */ +static const struct regmap_range kx022a_read_only_ranges[] = { + { + .range_min = KX022A_REG_XHP_L, + .range_max = KX022A_REG_INT_REL, + }, { + .range_min = KX022A_REG_BUF_STATUS_1, + .range_max = KX022A_REG_BUF_STATUS_2, + }, { + .range_min = KX022A_REG_BUF_READ, + .range_max = KX022A_REG_BUF_READ, + }, +}; + +static const struct regmap_access_table kx022a_ro_regs = { + .no_ranges = &kx022a_read_only_ranges[0], + .n_no_ranges = ARRAY_SIZE(kx022a_read_only_ranges), +}; + +static const struct regmap_range kx022a_write_only_ranges[] = { + { + .range_min = KX022A_REG_BTS_WUF_TH, + .range_max = KX022A_REG_BTS_WUF_TH, + }, { + .range_min = KX022A_REG_MAN_WAKE, + .range_max = KX022A_REG_MAN_WAKE, + }, { + .range_min = KX022A_REG_SELF_TEST, + .range_max = KX022A_REG_SELF_TEST, + }, { + .range_min = KX022A_REG_BUF_CLEAR, + .range_max = KX022A_REG_BUF_CLEAR, + }, +}; + +static const struct regmap_access_table kx022a_wo_regs = { + .no_ranges = &kx022a_write_only_ranges[0], + .n_no_ranges = ARRAY_SIZE(kx022a_write_only_ranges), +}; + +static const struct regmap_range kx022a_noinc_read_ranges[] = { + { + .range_min = KX022A_REG_BUF_READ, + .range_max = KX022A_REG_BUF_READ, + }, +}; + +static const struct regmap_access_table kx022a_nir_regs = { + .yes_ranges = &kx022a_noinc_read_ranges[0], + .n_yes_ranges = ARRAY_SIZE(kx022a_noinc_read_ranges), +}; + +static const struct regmap_config kx022a_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_table = &kx022a_volatile_regs, + .rd_table = &kx022a_wo_regs, + .wr_table = &kx022a_ro_regs, + .rd_noinc_table = &kx022a_nir_regs, + .precious_table = &kx022a_precious_regs, + .max_register = KX022A_MAX_REGISTER, + .cache_type = REGCACHE_MAPLE, +}; + +/* Regmap configs kx132 */ +static const struct regmap_range kx132_volatile_ranges[] = { + { + .range_min = KX132_REG_XADP_L, + .range_max = KX132_REG_COTR, + }, { + .range_min = KX132_REG_TSCP, + .range_max = KX132_REG_INT_REL, + }, { + /* The reset bit will be cleared by sensor */ + .range_min = KX132_REG_CNTL2, + .range_max = KX132_REG_CNTL2, + }, { + .range_min = KX132_REG_CNTL5, + .range_max = KX132_REG_CNTL5, + }, { + .range_min = KX132_REG_BUF_STATUS_1, + .range_max = KX132_REG_BUF_READ, + }, +}; + +static const struct regmap_access_table kx132_volatile_regs = { + .yes_ranges = &kx132_volatile_ranges[0], + .n_yes_ranges = ARRAY_SIZE(kx132_volatile_ranges), +}; + +static const struct regmap_range kx132_precious_ranges[] = { + { + .range_min = KX132_REG_INT_REL, + .range_max = KX132_REG_INT_REL, + }, +}; + +static const struct regmap_access_table kx132_precious_regs = { + .yes_ranges = &kx132_precious_ranges[0], + .n_yes_ranges = ARRAY_SIZE(kx132_precious_ranges), +}; + +static const struct regmap_range kx132_read_only_ranges[] = { + { + .range_min = KX132_REG_XADP_L, + .range_max = KX132_REG_INT_REL, + }, { + .range_min = KX132_REG_BUF_STATUS_1, + .range_max = KX132_REG_BUF_STATUS_2, + }, { + .range_min = KX132_REG_BUF_READ, + .range_max = KX132_REG_BUF_READ, + }, { + /* Kionix reserved registers: should not be written */ + .range_min = 0x28, + .range_max = 0x28, + }, { + .range_min = 0x35, + .range_max = 0x36, + }, { + .range_min = 0x3c, + .range_max = 0x48, + }, { + .range_min = 0x4e, + .range_max = 0x5c, + }, { + .range_min = 0x77, + .range_max = 0x7f, + }, +}; + +static const struct regmap_access_table kx132_ro_regs = { + .no_ranges = &kx132_read_only_ranges[0], + .n_no_ranges = ARRAY_SIZE(kx132_read_only_ranges), +}; + +static const struct regmap_range kx132_write_only_ranges[] = { + { + .range_min = KX132_REG_SELF_TEST, + .range_max = KX132_REG_SELF_TEST, + }, { + .range_min = KX132_REG_BUF_CLEAR, + .range_max = KX132_REG_BUF_CLEAR, + }, +}; + +static const struct regmap_access_table kx132_wo_regs = { + .no_ranges = &kx132_write_only_ranges[0], + .n_no_ranges = ARRAY_SIZE(kx132_write_only_ranges), +}; + +static const struct regmap_range kx132_noinc_read_ranges[] = { + { + .range_min = KX132_REG_BUF_READ, + .range_max = KX132_REG_BUF_READ, + }, +}; + +static const struct regmap_access_table kx132_nir_regs = { + .yes_ranges = &kx132_noinc_read_ranges[0], + .n_yes_ranges = ARRAY_SIZE(kx132_noinc_read_ranges), +}; + +static const struct regmap_config kx132_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_table = &kx132_volatile_regs, + .rd_table = &kx132_wo_regs, + .wr_table = &kx132_ro_regs, + .rd_noinc_table = &kx132_nir_regs, + .precious_table = &kx132_precious_regs, + .max_register = KX132_MAX_REGISTER, + .cache_type = REGCACHE_MAPLE, +}; + +struct kx022a_data { + struct regmap *regmap; + const struct kx022a_chip_info *chip_info; + struct iio_trigger *trig; + struct device *dev; + struct iio_mount_matrix orientation; + int64_t timestamp, old_timestamp; + + int irq; + int inc_reg; + int ien_reg; + + unsigned int state; + unsigned int odr_ns; + + bool trigger_enabled; + /* + * Prevent toggling the sensor stby/active state (PC1 bit) in the + * middle of a configuration, or when the fifo is enabled. Also, + * protect the data stored/retrieved from this structure from + * concurrent accesses. + */ + struct mutex mutex; + u8 watermark; + + __le16 *fifo_buffer; + + /* 3 x 16bit accel data + timestamp */ + __le16 buffer[8] __aligned(IIO_DMA_MINALIGN); + struct { + __le16 channels[3]; + aligned_s64 ts; + } scan; +}; + +static const struct iio_mount_matrix * +kx022a_get_mount_matrix(const struct iio_dev *idev, + const struct iio_chan_spec *chan) +{ + struct kx022a_data *data = iio_priv(idev); + + return &data->orientation; +} + +enum { + AXIS_X, + AXIS_Y, + AXIS_Z, + AXIS_MAX +}; + +static const unsigned long kx022a_scan_masks[] = { + BIT(AXIS_X) | BIT(AXIS_Y) | BIT(AXIS_Z), 0 +}; + +static const struct iio_chan_spec_ext_info kx022a_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_TYPE, kx022a_get_mount_matrix), + { } +}; + +#define KX022A_ACCEL_CHAN(axis, reg, index) \ +{ \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .ext_info = kx022a_ext_info, \ + .address = reg, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ +} + +static const struct iio_chan_spec kx022a_channels[] = { + KX022A_ACCEL_CHAN(X, KX022A_REG_XOUT_L, 0), + KX022A_ACCEL_CHAN(Y, KX022A_REG_YOUT_L, 1), + KX022A_ACCEL_CHAN(Z, KX022A_REG_ZOUT_L, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_chan_spec kx132_channels[] = { + KX022A_ACCEL_CHAN(X, KX132_REG_XOUT_L, 0), + KX022A_ACCEL_CHAN(Y, KX132_REG_YOUT_L, 1), + KX022A_ACCEL_CHAN(Z, KX132_REG_ZOUT_L, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +/* + * The sensor HW can support ODR up to 1600 Hz, which is beyond what most of the + * Linux CPUs can handle without dropping samples. Also, the low power mode is + * not available for higher sample rates. Thus, the driver only supports 200 Hz + * and slower ODRs. The slowest is 0.78 Hz. + */ +static const int kx022a_accel_samp_freq_table[][2] = { + { 0, 780000 }, + { 1, 563000 }, + { 3, 125000 }, + { 6, 250000 }, + { 12, 500000 }, + { 25, 0 }, + { 50, 0 }, + { 100, 0 }, + { 200, 0 }, +}; + +static const unsigned int kx022a_odrs[] = { + 1282051282, + 639795266, + 320 * MEGA, + 160 * MEGA, + 80 * MEGA, + 40 * MEGA, + 20 * MEGA, + 10 * MEGA, + 5 * MEGA, +}; + +/* + * range is typically +-2G/4G/8G/16G, distributed over the amount of bits. + * The scale table can be calculated using + * (range / 2^bits) * g = (range / 2^bits) * 9.80665 m/s^2 + * => KX022A uses 16 bit (HiRes mode - assume the low 8 bits are zeroed + * in low-power mode(?) ) + * => +/-2G => 4 / 2^16 * 9,80665 + * => +/-2G - 0.000598550415 + * +/-4G - 0.00119710083 + * +/-8G - 0.00239420166 + * +/-16G - 0.00478840332 + */ +static const int kx022a_scale_table[][2] = { + { 0, 598550 }, + { 0, 1197101 }, + { 0, 2394202 }, + { 0, 4788403 }, +}; + +/* KX134ACR-LBZ ranges are (+/-) 8, 16, 32, 64 G */ +static const int kx134acr_lbz_scale_table[][2] = { + { 0, 2394202 }, + { 0, 4788403 }, + { 0, 9576807 }, + { 0, 19153613 }, +}; + +static int kx022a_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct kx022a_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = (const int *)kx022a_accel_samp_freq_table; + *length = ARRAY_SIZE(kx022a_accel_samp_freq_table) * + ARRAY_SIZE(kx022a_accel_samp_freq_table[0]); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SCALE: + *vals = (const int *)data->chip_info->scale_table; + *length = data->chip_info->scale_table_size; + *type = IIO_VAL_INT_PLUS_NANO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +#define KX022A_DEFAULT_PERIOD_NS (20 * NSEC_PER_MSEC) + +static void kx022a_reg2freq(unsigned int val, int *val1, int *val2) +{ + *val1 = kx022a_accel_samp_freq_table[val & KX022A_MASK_ODR][0]; + *val2 = kx022a_accel_samp_freq_table[val & KX022A_MASK_ODR][1]; +} + +static void kx022a_reg2scale(struct kx022a_data *data, unsigned int val, + unsigned int *val1, unsigned int *val2) +{ + val &= KX022A_MASK_GSEL; + val >>= KX022A_GSEL_SHIFT; + + *val1 = data->chip_info->scale_table[val][0]; + *val2 = data->chip_info->scale_table[val][1]; +} + +static int __kx022a_turn_on_off(struct kx022a_data *data, bool on) +{ + int ret; + + if (on) + ret = regmap_set_bits(data->regmap, data->chip_info->cntl, + KX022A_MASK_PC1); + else + ret = regmap_clear_bits(data->regmap, data->chip_info->cntl, + KX022A_MASK_PC1); + if (ret) + dev_err(data->dev, "Turn %s fail %d\n", str_on_off(on), ret); + + return ret; +} + +static int kx022a_turn_off_lock(struct kx022a_data *data) +{ + int ret; + + mutex_lock(&data->mutex); + ret = __kx022a_turn_on_off(data, false); + if (ret) + mutex_unlock(&data->mutex); + + return ret; +} + +static int kx022a_turn_on_unlock(struct kx022a_data *data) +{ + int ret; + + ret = __kx022a_turn_on_off(data, true); + mutex_unlock(&data->mutex); + + return ret; +} + +static int kx022a_write_raw_get_fmt(struct iio_dev *idev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int __kx022a_write_raw(struct iio_dev *idev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct kx022a_data *data = iio_priv(idev); + int ret, n; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + n = ARRAY_SIZE(kx022a_accel_samp_freq_table); + + while (n--) + if (val == kx022a_accel_samp_freq_table[n][0] && + val2 == kx022a_accel_samp_freq_table[n][1]) + break; + if (n < 0) + return -EINVAL; + + ret = kx022a_turn_off_lock(data); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, + data->chip_info->odcntl, + KX022A_MASK_ODR, n); + data->odr_ns = kx022a_odrs[n]; + kx022a_turn_on_unlock(data); + return ret; + case IIO_CHAN_INFO_SCALE: + n = data->chip_info->scale_table_size / 2; + + while (n-- > 0) + if (val == data->chip_info->scale_table[n][0] && + val2 == data->chip_info->scale_table[n][1]) + break; + if (n < 0) + return -EINVAL; + + ret = kx022a_turn_off_lock(data); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, data->chip_info->cntl, + KX022A_MASK_GSEL, + n << KX022A_GSEL_SHIFT); + kx022a_turn_on_unlock(data); + return ret; + default: + return -EINVAL; + } +} + +static int kx022a_write_raw(struct iio_dev *idev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret; + + /* + * We should not allow changing scale or frequency when FIFO is running + * as it will mess the timestamp/scale for samples existing in the + * buffer. If this turns out to be an issue we can later change logic + * to internally flush the fifo before reconfiguring so the samples in + * fifo keep matching the freq/scale settings. (Such setup could cause + * issues if users trust the watermark to be reached within known + * time-limit). + */ + if (!iio_device_claim_direct(idev)) + return -EBUSY; + + ret = __kx022a_write_raw(idev, chan, val, val2, mask); + + iio_device_release_direct(idev); + + return ret; +} + +static int kx022a_fifo_set_wmi(struct kx022a_data *data) +{ + u8 threshold; + + threshold = data->watermark; + + return regmap_update_bits(data->regmap, data->chip_info->buf_cntl1, + KX022A_MASK_WM_TH, threshold); +} + +static int kx022a_get_axis(struct kx022a_data *data, + struct iio_chan_spec const *chan, + int *val) +{ + int ret; + + ret = regmap_bulk_read(data->regmap, chan->address, &data->buffer[0], + sizeof(__le16)); + if (ret) + return ret; + + *val = (s16)le16_to_cpu(data->buffer[0]); + + return IIO_VAL_INT; +} + +static int kx022a_read_raw(struct iio_dev *idev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct kx022a_data *data = iio_priv(idev); + unsigned int regval; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(idev)) + return -EBUSY; + + mutex_lock(&data->mutex); + ret = kx022a_get_axis(data, chan, val); + mutex_unlock(&data->mutex); + + iio_device_release_direct(idev); + + return ret; + + case IIO_CHAN_INFO_SAMP_FREQ: + ret = regmap_read(data->regmap, data->chip_info->odcntl, ®val); + if (ret) + return ret; + + if ((regval & KX022A_MASK_ODR) > + ARRAY_SIZE(kx022a_accel_samp_freq_table)) { + dev_err(data->dev, "Invalid ODR\n"); + return -EINVAL; + } + + kx022a_reg2freq(regval, val, val2); + + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_CHAN_INFO_SCALE: + ret = regmap_read(data->regmap, data->chip_info->cntl, ®val); + if (ret < 0) + return ret; + + kx022a_reg2scale(data, regval, val, val2); + + return IIO_VAL_INT_PLUS_NANO; + } + + return -EINVAL; +}; + +static int kx022a_set_watermark(struct iio_dev *idev, unsigned int val) +{ + struct kx022a_data *data = iio_priv(idev); + + val = min(data->chip_info->fifo_length, val); + + mutex_lock(&data->mutex); + data->watermark = val; + mutex_unlock(&data->mutex); + + return 0; +} + +static ssize_t hwfifo_enabled_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *idev = dev_to_iio_dev(dev); + struct kx022a_data *data = iio_priv(idev); + bool state; + + mutex_lock(&data->mutex); + state = data->state; + mutex_unlock(&data->mutex); + + return sysfs_emit(buf, "%d\n", state); +} + +static ssize_t hwfifo_watermark_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *idev = dev_to_iio_dev(dev); + struct kx022a_data *data = iio_priv(idev); + int wm; + + mutex_lock(&data->mutex); + wm = data->watermark; + mutex_unlock(&data->mutex); + + return sysfs_emit(buf, "%d\n", wm); +} + +static IIO_DEVICE_ATTR_RO(hwfifo_enabled, 0); +static IIO_DEVICE_ATTR_RO(hwfifo_watermark, 0); + +static const struct iio_dev_attr *kx022a_fifo_attributes[] = { + &iio_dev_attr_hwfifo_watermark, + &iio_dev_attr_hwfifo_enabled, + NULL +}; + +static int kx022a_drop_fifo_contents(struct kx022a_data *data) +{ + /* + * We must clear the old time-stamp to avoid computing the timestamps + * based on samples acquired when buffer was last enabled. + * + * We don't need to protect the timestamp as long as we are only + * called from fifo-disable where we can guarantee the sensor is not + * triggering interrupts and where the mutex is locked to prevent the + * user-space access. + */ + data->timestamp = 0; + + return regmap_write(data->regmap, data->chip_info->buf_clear, 0x0); +} + +static int kx022a_get_fifo_bytes_available(struct kx022a_data *data) +{ + int ret, fifo_bytes; + + ret = regmap_read(data->regmap, KX022A_REG_BUF_STATUS_1, &fifo_bytes); + if (ret) { + dev_err(data->dev, "Error reading buffer status\n"); + return ret; + } + + if (fifo_bytes == KX022A_FIFO_FULL_VALUE) + return KX022A_FIFO_MAX_BYTES; + + return fifo_bytes; +} + +static int kx132_get_fifo_bytes_available(struct kx022a_data *data) +{ + __le16 buf_status; + int ret, fifo_bytes; + + ret = regmap_bulk_read(data->regmap, data->chip_info->buf_status1, + &buf_status, sizeof(buf_status)); + if (ret) { + dev_err(data->dev, "Error reading buffer status\n"); + return ret; + } + + fifo_bytes = le16_to_cpu(buf_status); + fifo_bytes &= data->chip_info->buf_smp_lvl_mask; + fifo_bytes = min((unsigned int)fifo_bytes, data->chip_info->fifo_length * + KX022A_FIFO_SAMPLES_SIZE_BYTES); + + return fifo_bytes; +} + +static int __kx022a_fifo_flush(struct iio_dev *idev, unsigned int samples, + bool irq) +{ + struct kx022a_data *data = iio_priv(idev); + uint64_t sample_period; + int count, fifo_bytes; + bool renable = false; + int64_t tstamp; + int ret, i; + + fifo_bytes = data->chip_info->get_fifo_bytes_available(data); + + if (fifo_bytes % KX022A_FIFO_SAMPLES_SIZE_BYTES) + dev_warn(data->dev, "Bad FIFO alignment. Data may be corrupt\n"); + + count = fifo_bytes / KX022A_FIFO_SAMPLES_SIZE_BYTES; + if (!count) + return 0; + + /* + * If we are being called from IRQ handler we know the stored timestamp + * is fairly accurate for the last stored sample. Otherwise, if we are + * called as a result of a read operation from userspace and hence + * before the watermark interrupt was triggered, take a timestamp + * now. We can fall anywhere in between two samples so the error in this + * case is at most one sample period. + */ + if (!irq) { + /* + * We need to have the IRQ disabled or we risk of messing-up + * the timestamps. If we are ran from IRQ, then the + * IRQF_ONESHOT has us covered - but if we are ran by the + * user-space read we need to disable the IRQ to be on a safe + * side. We do this usng synchronous disable so that if the + * IRQ thread is being ran on other CPU we wait for it to be + * finished. + */ + disable_irq(data->irq); + renable = true; + + data->old_timestamp = data->timestamp; + data->timestamp = iio_get_time_ns(idev); + } + + /* + * Approximate timestamps for each of the sample based on the sampling + * frequency, timestamp for last sample and number of samples. + * + * We'd better not use the current bandwidth settings to compute the + * sample period. The real sample rate varies with the device and + * small variation adds when we store a large number of samples. + * + * To avoid this issue we compute the actual sample period ourselves + * based on the timestamp delta between the last two flush operations. + */ + if (data->old_timestamp) { + sample_period = data->timestamp - data->old_timestamp; + do_div(sample_period, count); + } else { + sample_period = data->odr_ns; + } + tstamp = data->timestamp - (count - 1) * sample_period; + + if (samples && count > samples) { + /* + * Here we leave some old samples to the buffer. We need to + * adjust the timestamp to match the first sample in the buffer + * or we will miscalculate the sample_period at next round. + */ + data->timestamp -= (count - samples) * sample_period; + count = samples; + } + + fifo_bytes = count * KX022A_FIFO_SAMPLES_SIZE_BYTES; + ret = regmap_noinc_read(data->regmap, data->chip_info->buf_read, + data->fifo_buffer, fifo_bytes); + if (ret) + goto renable_out; + + for (i = 0; i < count; i++) { + __le16 *sam = &data->fifo_buffer[i * 3]; + __le16 *chs; + int bit; + + chs = &data->scan.channels[0]; + for_each_set_bit(bit, idev->active_scan_mask, AXIS_MAX) + chs[bit] = sam[bit]; + + iio_push_to_buffers_with_timestamp(idev, &data->scan, tstamp); + + tstamp += sample_period; + } + + ret = count; + +renable_out: + if (renable) + enable_irq(data->irq); + + return ret; +} + +static int kx022a_fifo_flush(struct iio_dev *idev, unsigned int samples) +{ + struct kx022a_data *data = iio_priv(idev); + int ret; + + mutex_lock(&data->mutex); + ret = __kx022a_fifo_flush(idev, samples, false); + mutex_unlock(&data->mutex); + + return ret; +} + +static const struct iio_info kx022a_info = { + .read_raw = &kx022a_read_raw, + .write_raw = &kx022a_write_raw, + .write_raw_get_fmt = &kx022a_write_raw_get_fmt, + .read_avail = &kx022a_read_avail, + + .validate_trigger = iio_validate_own_trigger, + .hwfifo_set_watermark = kx022a_set_watermark, + .hwfifo_flush_to_buffer = kx022a_fifo_flush, +}; + +static int kx022a_set_drdy_irq(struct kx022a_data *data, bool en) +{ + if (en) + return regmap_set_bits(data->regmap, data->chip_info->cntl, + KX022A_MASK_DRDY); + + return regmap_clear_bits(data->regmap, data->chip_info->cntl, + KX022A_MASK_DRDY); +} + +static int kx022a_prepare_irq_pin(struct kx022a_data *data) +{ + /* Enable IRQ1 pin. Set polarity to active low */ + int mask = KX022A_MASK_IEN | KX022A_MASK_IPOL | + KX022A_MASK_ITYP; + int val = KX022A_MASK_IEN | KX022A_IPOL_LOW | + KX022A_ITYP_LEVEL; + int ret; + + ret = regmap_update_bits(data->regmap, data->inc_reg, mask, val); + if (ret) + return ret; + + /* We enable WMI to IRQ pin only at buffer_enable */ + mask = KX022A_MASK_INS2_DRDY; + + return regmap_set_bits(data->regmap, data->ien_reg, mask); +} + +static int kx022a_fifo_disable(struct kx022a_data *data) +{ + int ret = 0; + + guard(mutex)(&data->mutex); + ret = __kx022a_turn_on_off(data, false); + if (ret) + return ret; + + ret = regmap_clear_bits(data->regmap, data->ien_reg, KX022A_MASK_WMI); + if (ret) + return ret; + + ret = regmap_clear_bits(data->regmap, data->chip_info->buf_cntl2, + KX022A_MASK_BUF_EN); + if (ret) + return ret; + + data->state &= ~KX022A_STATE_FIFO; + + kx022a_drop_fifo_contents(data); + + kfree(data->fifo_buffer); + + return __kx022a_turn_on_off(data, true); +} + +static int kx022a_buffer_predisable(struct iio_dev *idev) +{ + struct kx022a_data *data = iio_priv(idev); + + if (iio_device_get_current_mode(idev) == INDIO_BUFFER_TRIGGERED) + return 0; + + return kx022a_fifo_disable(data); +} + +static int kx022a_fifo_enable(struct kx022a_data *data) +{ + int ret; + + data->fifo_buffer = kmalloc_array(data->chip_info->fifo_length, + KX022A_FIFO_SAMPLES_SIZE_BYTES, + GFP_KERNEL); + if (!data->fifo_buffer) + return -ENOMEM; + + guard(mutex)(&data->mutex); + ret = __kx022a_turn_on_off(data, false); + if (ret) + return ret; + + /* Update watermark to HW */ + ret = kx022a_fifo_set_wmi(data); + if (ret) + return ret; + + /* Enable buffer */ + ret = regmap_set_bits(data->regmap, data->chip_info->buf_cntl2, + KX022A_MASK_BUF_EN); + if (ret) + return ret; + + data->state |= KX022A_STATE_FIFO; + ret = regmap_set_bits(data->regmap, data->ien_reg, + KX022A_MASK_WMI); + if (ret) + return ret; + + return __kx022a_turn_on_off(data, true); +} + +static int kx022a_buffer_postenable(struct iio_dev *idev) +{ + struct kx022a_data *data = iio_priv(idev); + + /* + * If we use data-ready trigger, then the IRQ masks should be handled by + * trigger enable and the hardware buffer is not used but we just update + * results to the IIO fifo when data-ready triggers. + */ + if (iio_device_get_current_mode(idev) == INDIO_BUFFER_TRIGGERED) + return 0; + + return kx022a_fifo_enable(data); +} + +static const struct iio_buffer_setup_ops kx022a_buffer_ops = { + .postenable = kx022a_buffer_postenable, + .predisable = kx022a_buffer_predisable, +}; + +static irqreturn_t kx022a_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *idev = pf->indio_dev; + struct kx022a_data *data = iio_priv(idev); + int ret; + + ret = regmap_bulk_read(data->regmap, data->chip_info->xout_l, data->buffer, + KX022A_FIFO_SAMPLES_SIZE_BYTES); + if (ret < 0) + goto err_read; + + iio_push_to_buffers_with_timestamp(idev, data->buffer, data->timestamp); +err_read: + iio_trigger_notify_done(idev->trig); + + return IRQ_HANDLED; +} + +/* Get timestamps and wake the thread if we need to read data */ +static irqreturn_t kx022a_irq_handler(int irq, void *private) +{ + struct iio_dev *idev = private; + struct kx022a_data *data = iio_priv(idev); + + data->old_timestamp = data->timestamp; + data->timestamp = iio_get_time_ns(idev); + + if (data->state & KX022A_STATE_FIFO || data->trigger_enabled) + return IRQ_WAKE_THREAD; + + return IRQ_NONE; +} + +/* + * WMI and data-ready IRQs are acked when results are read. If we add + * TILT/WAKE or other IRQs - then we may need to implement the acking + * (which is racy). + */ +static irqreturn_t kx022a_irq_thread_handler(int irq, void *private) +{ + struct iio_dev *idev = private; + struct kx022a_data *data = iio_priv(idev); + irqreturn_t ret = IRQ_NONE; + + guard(mutex)(&data->mutex); + + if (data->trigger_enabled) { + iio_trigger_poll_nested(data->trig); + ret = IRQ_HANDLED; + } + + if (data->state & KX022A_STATE_FIFO) { + int ok; + + ok = __kx022a_fifo_flush(idev, data->chip_info->fifo_length, true); + if (ok > 0) + ret = IRQ_HANDLED; + } + + return ret; +} + +static int kx022a_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct kx022a_data *data = iio_trigger_get_drvdata(trig); + int ret = 0; + + guard(mutex)(&data->mutex); + + if (data->trigger_enabled == state) + return 0; + + if (data->state & KX022A_STATE_FIFO) { + dev_warn(data->dev, "Can't set trigger when FIFO enabled\n"); + return -EBUSY; + } + + ret = __kx022a_turn_on_off(data, false); + if (ret) + return ret; + + data->trigger_enabled = state; + ret = kx022a_set_drdy_irq(data, state); + if (ret) + return ret; + + return __kx022a_turn_on_off(data, true); +} + +static const struct iio_trigger_ops kx022a_trigger_ops = { + .set_trigger_state = kx022a_trigger_set_state, +}; + +static int kx022a_chip_init(struct kx022a_data *data) +{ + int ret, val; + + /* Reset the senor */ + ret = regmap_write(data->regmap, data->chip_info->cntl2, KX022A_MASK_SRST); + if (ret) + return ret; + + /* + * According to the power-on procedure documents, there is (at least) + * 2ms delay required after the software reset. This should be same for + * all, KX022ACR-Z, KX132-1211, KX132ACR-LBZ and KX134ACR-LBZ. + * + * https://fscdn.rohm.com/kionix/en/document/AN010_KX022ACR-Z_Power-on_Procedure_E.pdf + * https://fscdn.rohm.com/kionix/en/document/TN027-Power-On-Procedure.pdf + * https://fscdn.rohm.com/kionix/en/document/AN011_KX134ACR-LBZ_Power-on_Procedure_E.pdf + */ + msleep(2); + + ret = regmap_read_poll_timeout(data->regmap, data->chip_info->cntl2, val, + !(val & KX022A_MASK_SRST), + KX022A_SOFT_RESET_WAIT_TIME_US, + KX022A_SOFT_RESET_TOTAL_WAIT_TIME_US); + if (ret) { + dev_err(data->dev, "Sensor reset %s\n", + val & KX022A_MASK_SRST ? "timeout" : "fail#"); + return ret; + } + + ret = regmap_reinit_cache(data->regmap, data->chip_info->regmap_config); + if (ret) { + dev_err(data->dev, "Failed to reinit reg cache\n"); + return ret; + } + + /* set data res 16bit */ + ret = regmap_set_bits(data->regmap, data->chip_info->buf_cntl2, + KX022A_MASK_BRES16); + if (ret) { + dev_err(data->dev, "Failed to set data resolution\n"); + return ret; + } + + return kx022a_prepare_irq_pin(data); +} + +const struct kx022a_chip_info kx022a_chip_info = { + .name = "kx022-accel", + .regmap_config = &kx022a_regmap_config, + .channels = kx022a_channels, + .num_channels = ARRAY_SIZE(kx022a_channels), + .scale_table = kx022a_scale_table, + .scale_table_size = ARRAY_SIZE(kx022a_scale_table) * + ARRAY_SIZE(kx022a_scale_table[0]), + .fifo_length = KX022A_FIFO_LENGTH, + .who = KX022A_REG_WHO, + .id = KX022A_ID, + .cntl = KX022A_REG_CNTL, + .cntl2 = KX022A_REG_CNTL2, + .odcntl = KX022A_REG_ODCNTL, + .buf_cntl1 = KX022A_REG_BUF_CNTL1, + .buf_cntl2 = KX022A_REG_BUF_CNTL2, + .buf_clear = KX022A_REG_BUF_CLEAR, + .buf_status1 = KX022A_REG_BUF_STATUS_1, + .buf_read = KX022A_REG_BUF_READ, + .inc1 = KX022A_REG_INC1, + .inc4 = KX022A_REG_INC4, + .inc5 = KX022A_REG_INC5, + .inc6 = KX022A_REG_INC6, + .xout_l = KX022A_REG_XOUT_L, + .get_fifo_bytes_available = kx022a_get_fifo_bytes_available, +}; +EXPORT_SYMBOL_NS_GPL(kx022a_chip_info, "IIO_KX022A"); + +const struct kx022a_chip_info kx132_chip_info = { + .name = "kx132-1211", + .regmap_config = &kx132_regmap_config, + .channels = kx132_channels, + .num_channels = ARRAY_SIZE(kx132_channels), + .scale_table = kx022a_scale_table, + .scale_table_size = ARRAY_SIZE(kx022a_scale_table) * + ARRAY_SIZE(kx022a_scale_table[0]), + .fifo_length = KX132_FIFO_LENGTH, + .who = KX132_REG_WHO, + .id = KX132_ID, + .cntl = KX132_REG_CNTL, + .cntl2 = KX132_REG_CNTL2, + .odcntl = KX132_REG_ODCNTL, + .buf_cntl1 = KX132_REG_BUF_CNTL1, + .buf_cntl2 = KX132_REG_BUF_CNTL2, + .buf_clear = KX132_REG_BUF_CLEAR, + .buf_status1 = KX132_REG_BUF_STATUS_1, + .buf_smp_lvl_mask = KX132_MASK_BUF_SMP_LVL, + .buf_read = KX132_REG_BUF_READ, + .inc1 = KX132_REG_INC1, + .inc4 = KX132_REG_INC4, + .inc5 = KX132_REG_INC5, + .inc6 = KX132_REG_INC6, + .xout_l = KX132_REG_XOUT_L, + .get_fifo_bytes_available = kx132_get_fifo_bytes_available, +}; +EXPORT_SYMBOL_NS_GPL(kx132_chip_info, "IIO_KX022A"); + +const struct kx022a_chip_info kx134_chip_info = { + .name = "kx134-1211", + .regmap_config = &kx132_regmap_config, + .channels = kx132_channels, + .num_channels = ARRAY_SIZE(kx132_channels), + .scale_table = kx134acr_lbz_scale_table, + .scale_table_size = ARRAY_SIZE(kx134acr_lbz_scale_table) * + ARRAY_SIZE(kx134acr_lbz_scale_table[0]), + .fifo_length = KX132_FIFO_LENGTH, + .who = KX132_REG_WHO, + .id = KX134_1211_ID, + .cntl = KX132_REG_CNTL, + .cntl2 = KX132_REG_CNTL2, + .odcntl = KX132_REG_ODCNTL, + .buf_cntl1 = KX132_REG_BUF_CNTL1, + .buf_cntl2 = KX132_REG_BUF_CNTL2, + .buf_clear = KX132_REG_BUF_CLEAR, + .buf_status1 = KX132_REG_BUF_STATUS_1, + .buf_smp_lvl_mask = KX132_MASK_BUF_SMP_LVL, + .buf_read = KX132_REG_BUF_READ, + .inc1 = KX132_REG_INC1, + .inc4 = KX132_REG_INC4, + .inc5 = KX132_REG_INC5, + .inc6 = KX132_REG_INC6, + .xout_l = KX132_REG_XOUT_L, + .get_fifo_bytes_available = kx132_get_fifo_bytes_available, +}; +EXPORT_SYMBOL_NS_GPL(kx134_chip_info, "IIO_KX022A"); + +/* + * Despite the naming, KX132ACR-LBZ is not similar to KX132-1211 but it is + * exact subset of KX022A. KX132ACR-LBZ is meant to be used for industrial + * applications and the tap/double tap, free fall and tilt engines were + * removed. Rest of the registers and functionalities (excluding the ID + * register) are exact match to what is found in KX022. + */ +const struct kx022a_chip_info kx132acr_chip_info = { + .name = "kx132acr-lbz", + .regmap_config = &kx022a_regmap_config, + .channels = kx022a_channels, + .num_channels = ARRAY_SIZE(kx022a_channels), + .scale_table = kx022a_scale_table, + .scale_table_size = ARRAY_SIZE(kx022a_scale_table) * + ARRAY_SIZE(kx022a_scale_table[0]), + .fifo_length = KX022A_FIFO_LENGTH, + .who = KX022A_REG_WHO, + .id = KX132ACR_LBZ_ID, + .cntl = KX022A_REG_CNTL, + .cntl2 = KX022A_REG_CNTL2, + .odcntl = KX022A_REG_ODCNTL, + .buf_cntl1 = KX022A_REG_BUF_CNTL1, + .buf_cntl2 = KX022A_REG_BUF_CNTL2, + .buf_clear = KX022A_REG_BUF_CLEAR, + .buf_status1 = KX022A_REG_BUF_STATUS_1, + .buf_read = KX022A_REG_BUF_READ, + .inc1 = KX022A_REG_INC1, + .inc4 = KX022A_REG_INC4, + .inc5 = KX022A_REG_INC5, + .inc6 = KX022A_REG_INC6, + .xout_l = KX022A_REG_XOUT_L, + .get_fifo_bytes_available = kx022a_get_fifo_bytes_available, +}; +EXPORT_SYMBOL_NS_GPL(kx132acr_chip_info, "IIO_KX022A"); + +const struct kx022a_chip_info kx134acr_chip_info = { + .name = "kx134acr-lbz", + .regmap_config = &kx022a_regmap_config, + .channels = kx022a_channels, + .num_channels = ARRAY_SIZE(kx022a_channels), + .scale_table = kx134acr_lbz_scale_table, + .scale_table_size = ARRAY_SIZE(kx134acr_lbz_scale_table) * + ARRAY_SIZE(kx134acr_lbz_scale_table[0]), + .fifo_length = KX022A_FIFO_LENGTH, + .who = KX022A_REG_WHO, + .id = KX134ACR_LBZ_ID, + .cntl = KX022A_REG_CNTL, + .cntl2 = KX022A_REG_CNTL2, + .odcntl = KX022A_REG_ODCNTL, + .buf_cntl1 = KX022A_REG_BUF_CNTL1, + .buf_cntl2 = KX022A_REG_BUF_CNTL2, + .buf_clear = KX022A_REG_BUF_CLEAR, + .buf_status1 = KX022A_REG_BUF_STATUS_1, + .buf_read = KX022A_REG_BUF_READ, + .inc1 = KX022A_REG_INC1, + .inc4 = KX022A_REG_INC4, + .inc5 = KX022A_REG_INC5, + .inc6 = KX022A_REG_INC6, + .xout_l = KX022A_REG_XOUT_L, + .get_fifo_bytes_available = kx022a_get_fifo_bytes_available, +}; +EXPORT_SYMBOL_NS_GPL(kx134acr_chip_info, "IIO_KX022A"); + +int kx022a_probe_internal(struct device *dev, const struct kx022a_chip_info *chip_info) +{ + static const char * const regulator_names[] = {"io-vdd", "vdd"}; + struct iio_trigger *indio_trig; + struct fwnode_handle *fwnode; + struct kx022a_data *data; + struct regmap *regmap; + unsigned int chip_id; + struct iio_dev *idev; + int ret, irq; + char *name; + + regmap = dev_get_regmap(dev, NULL); + if (!regmap) { + dev_err(dev, "no regmap\n"); + return -EINVAL; + } + + fwnode = dev_fwnode(dev); + if (!fwnode) + return -ENODEV; + + idev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!idev) + return -ENOMEM; + + data = iio_priv(idev); + data->chip_info = chip_info; + + /* + * VDD is the analog and digital domain voltage supply and + * IO_VDD is the digital I/O voltage supply. + */ + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulator_names), + regulator_names); + if (ret && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to enable regulator\n"); + + ret = regmap_read(regmap, chip_info->who, &chip_id); + if (ret) + return dev_err_probe(dev, ret, "Failed to access sensor\n"); + + if (chip_id != chip_info->id) + dev_warn(dev, "unknown device 0x%x\n", chip_id); + + irq = fwnode_irq_get_byname(fwnode, "INT1"); + if (irq > 0) { + data->inc_reg = chip_info->inc1; + data->ien_reg = chip_info->inc4; + } else { + irq = fwnode_irq_get_byname(fwnode, "INT2"); + if (irq < 0) + return dev_err_probe(dev, irq, "No suitable IRQ\n"); + + data->inc_reg = chip_info->inc5; + data->ien_reg = chip_info->inc6; + } + + data->regmap = regmap; + data->dev = dev; + data->irq = irq; + data->odr_ns = KX022A_DEFAULT_PERIOD_NS; + mutex_init(&data->mutex); + + idev->channels = chip_info->channels; + idev->num_channels = chip_info->num_channels; + idev->name = chip_info->name; + idev->info = &kx022a_info; + idev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + idev->available_scan_masks = kx022a_scan_masks; + + /* Read the mounting matrix, if present */ + ret = iio_read_mount_matrix(dev, &data->orientation); + if (ret) + return ret; + + /* The sensor must be turned off for configuration */ + ret = kx022a_turn_off_lock(data); + if (ret) + return ret; + + ret = kx022a_chip_init(data); + if (ret) { + mutex_unlock(&data->mutex); + return ret; + } + + ret = kx022a_turn_on_unlock(data); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup_ext(dev, idev, + &iio_pollfunc_store_time, + kx022a_trigger_handler, + IIO_BUFFER_DIRECTION_IN, + &kx022a_buffer_ops, + kx022a_fifo_attributes); + + if (ret) + return dev_err_probe(data->dev, ret, + "iio_triggered_buffer_setup_ext FAIL\n"); + indio_trig = devm_iio_trigger_alloc(dev, "%sdata-rdy-dev%d", idev->name, + iio_device_id(idev)); + if (!indio_trig) + return -ENOMEM; + + data->trig = indio_trig; + + indio_trig->ops = &kx022a_trigger_ops; + iio_trigger_set_drvdata(indio_trig, data); + + /* + * No need to check for NULL. request_threaded_irq() defaults to + * dev_name() should the alloc fail. + */ + name = devm_kasprintf(data->dev, GFP_KERNEL, "%s-kx022a", + dev_name(data->dev)); + + ret = devm_request_threaded_irq(data->dev, irq, kx022a_irq_handler, + &kx022a_irq_thread_handler, + IRQF_ONESHOT, name, idev); + if (ret) + return ret; + + ret = devm_iio_trigger_register(dev, indio_trig); + if (ret) + return dev_err_probe(data->dev, ret, + "Trigger registration failed\n"); + + ret = devm_iio_device_register(data->dev, idev); + if (ret < 0) + return dev_err_probe(dev, ret, + "Unable to register iio device\n"); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(kx022a_probe_internal, "IIO_KX022A"); + +MODULE_DESCRIPTION("ROHM/Kionix KX022A accelerometer driver"); +MODULE_AUTHOR("Matti Vaittinen "); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/accel/kionix-kx022a.h b/drivers/iio/accel/kionix-kx022a.h new file mode 100644 index 000000000..0ed54f584 --- /dev/null +++ b/drivers/iio/accel/kionix-kx022a.h @@ -0,0 +1,206 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2022 ROHM Semiconductors + * + * ROHM/KIONIX KX022A accelerometer driver + */ + +#ifndef _KX022A_H_ +#define _KX022A_H_ + +#include +#include + +#define KX022A_REG_WHO 0x0f +#define KX022A_ID 0xc8 +#define KX132ACR_LBZ_ID 0xd8 +#define KX134ACR_LBZ_ID 0xcc + +#define KX022A_REG_CNTL2 0x19 +#define KX022A_MASK_SRST BIT(7) +#define KX022A_REG_CNTL 0x18 +#define KX022A_MASK_PC1 BIT(7) +#define KX022A_MASK_RES BIT(6) +#define KX022A_MASK_DRDY BIT(5) +#define KX022A_MASK_GSEL GENMASK(4, 3) +#define KX022A_GSEL_SHIFT 3 +#define KX022A_GSEL_2 0x0 +#define KX022A_GSEL_4 BIT(3) +#define KX022A_GSEL_8 BIT(4) +#define KX022A_GSEL_16 GENMASK(4, 3) + +#define KX022A_REG_INS2 0x13 +#define KX022A_MASK_INS2_DRDY BIT(4) +#define KX122_MASK_INS2_WMI BIT(5) + +#define KX022A_REG_XHP_L 0x0 +#define KX022A_REG_XOUT_L 0x06 +#define KX022A_REG_YOUT_L 0x08 +#define KX022A_REG_ZOUT_L 0x0a +#define KX022A_REG_COTR 0x0c +#define KX022A_REG_TSCP 0x10 +#define KX022A_REG_INT_REL 0x17 + +#define KX022A_REG_ODCNTL 0x1b + +#define KX022A_REG_BTS_WUF_TH 0x31 +#define KX022A_REG_MAN_WAKE 0x2c + +#define KX022A_REG_BUF_CNTL1 0x3a +#define KX022A_MASK_WM_TH GENMASK(6, 0) +#define KX022A_REG_BUF_CNTL2 0x3b +#define KX022A_MASK_BUF_EN BIT(7) +#define KX022A_MASK_BRES16 BIT(6) +#define KX022A_REG_BUF_STATUS_1 0x3c +#define KX022A_REG_BUF_STATUS_2 0x3d +#define KX022A_REG_BUF_CLEAR 0x3e +#define KX022A_REG_BUF_READ 0x3f +#define KX022A_MASK_ODR GENMASK(3, 0) +#define KX022A_ODR_SHIFT 3 +#define KX022A_FIFO_MAX_WMI_TH 41 + +#define KX022A_REG_INC1 0x1c +#define KX022A_REG_INC5 0x20 +#define KX022A_REG_INC6 0x21 +#define KX022A_MASK_IEN BIT(5) +#define KX022A_MASK_IPOL BIT(4) +#define KX022A_IPOL_LOW 0 +#define KX022A_IPOL_HIGH KX022A_MASK_IPOL1 +#define KX022A_MASK_ITYP BIT(3) +#define KX022A_ITYP_PULSE KX022A_MASK_ITYP +#define KX022A_ITYP_LEVEL 0 + +#define KX022A_REG_INC4 0x1f +#define KX022A_MASK_WMI BIT(5) + +#define KX022A_REG_SELF_TEST 0x60 +#define KX022A_MAX_REGISTER 0x60 + +#define KX132_REG_WHO 0x13 +#define KX132_ID 0x3d +#define KX134_1211_ID 0x46 + +#define KX132_FIFO_LENGTH 86 + +#define KX132_REG_CNTL 0x1b +#define KX132_REG_CNTL2 0x1c +#define KX132_REG_CNTL5 0x1f +#define KX132_MASK_RES BIT(6) +#define KX132_GSEL_2 0x0 +#define KX132_GSEL_4 BIT(3) +#define KX132_GSEL_8 BIT(4) +#define KX132_GSEL_16 GENMASK(4, 3) + +#define KX132_REG_INS2 0x17 +#define KX132_MASK_INS2_WMI BIT(5) + +#define KX132_REG_XADP_L 0x02 +#define KX132_REG_XOUT_L 0x08 +#define KX132_REG_YOUT_L 0x0a +#define KX132_REG_ZOUT_L 0x0c +#define KX132_REG_COTR 0x12 +#define KX132_REG_TSCP 0x14 +#define KX132_REG_INT_REL 0x1a + +#define KX132_REG_ODCNTL 0x21 + +#define KX132_REG_BTS_WUF_TH 0x4a + +#define KX132_REG_BUF_CNTL1 0x5e +#define KX132_REG_BUF_CNTL2 0x5f +#define KX132_REG_BUF_STATUS_1 0x60 +#define KX132_REG_BUF_STATUS_2 0x61 +#define KX132_MASK_BUF_SMP_LVL GENMASK(9, 0) +#define KX132_REG_BUF_CLEAR 0x62 +#define KX132_REG_BUF_READ 0x63 +#define KX132_ODR_SHIFT 3 +#define KX132_FIFO_MAX_WMI_TH 86 + +#define KX132_REG_INC1 0x22 +#define KX132_REG_INC5 0x26 +#define KX132_REG_INC6 0x27 +#define KX132_IPOL_LOW 0 +#define KX132_IPOL_HIGH KX022A_MASK_IPOL +#define KX132_ITYP_PULSE KX022A_MASK_ITYP + +#define KX132_REG_INC4 0x25 + +#define KX132_REG_SELF_TEST 0x5d +#define KX132_MAX_REGISTER 0x76 + +struct device; + +struct kx022a_data; + +/** + * struct kx022a_chip_info - Kionix accelerometer chip specific information + * + * @name: name of the device + * @regmap_config: pointer to register map configuration + * @scale_table: An array of tables of scaling factors for + * a supported acceleration measurement range. + * Each table containing a single scaling + * factor consisting of two integers. The first + * value in a table is the integer part, and + * the second value is the fractional part as + * parts per billion. + * @scale_table_size: Amount of values in tables. + * @channels: pointer to iio_chan_spec array + * @num_channels: number of iio_chan_spec channels + * @fifo_length: number of 16-bit samples in a full buffer + * @buf_smp_lvl_mask: buffer sample level mask + * @who: WHO_AM_I register + * @id: WHO_AM_I register value + * @cntl: control register 1 + * @cntl2: control register 2 + * @odcntl: output data control register + * @buf_cntl1: buffer control register 1 + * @buf_cntl2: buffer control register 2 + * @buf_clear: buffer clear register + * @buf_status1: buffer status register 1 + * @buf_read: buffer read register + * @inc1: interrupt control register 1 + * @inc4: interrupt control register 4 + * @inc5: interrupt control register 5 + * @inc6: interrupt control register 6 + * @xout_l: x-axis output least significant byte + * @get_fifo_bytes_available: function pointer to get amount of acceleration + * data bytes currently stored in the sensor's FIFO + * buffer + */ +struct kx022a_chip_info { + const char *name; + const struct regmap_config *regmap_config; + const int (*scale_table)[2]; + const int scale_table_size; + const struct iio_chan_spec *channels; + unsigned int num_channels; + unsigned int fifo_length; + u16 buf_smp_lvl_mask; + u8 who; + u8 id; + u8 cntl; + u8 cntl2; + u8 odcntl; + u8 buf_cntl1; + u8 buf_cntl2; + u8 buf_clear; + u8 buf_status1; + u8 buf_read; + u8 inc1; + u8 inc4; + u8 inc5; + u8 inc6; + u8 xout_l; + int (*get_fifo_bytes_available)(struct kx022a_data *); +}; + +int kx022a_probe_internal(struct device *dev, const struct kx022a_chip_info *chip_info); + +extern const struct kx022a_chip_info kx022a_chip_info; +extern const struct kx022a_chip_info kx132_chip_info; +extern const struct kx022a_chip_info kx134_chip_info; +extern const struct kx022a_chip_info kx132acr_chip_info; +extern const struct kx022a_chip_info kx134acr_chip_info; + +#endif diff --git a/drivers/iio/accel/kxcjk-1013.c b/drivers/iio/accel/kxcjk-1013.c new file mode 100644 index 000000000..166fb7864 --- /dev/null +++ b/drivers/iio/accel/kxcjk-1013.c @@ -0,0 +1,1684 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * KXCJK-1013 3-axis accelerometer driver + * Copyright (c) 2014, Intel Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define KXTF9_REG_HP_XOUT_L 0x00 +#define KXTF9_REG_HP_XOUT_H 0x01 +#define KXTF9_REG_HP_YOUT_L 0x02 +#define KXTF9_REG_HP_YOUT_H 0x03 +#define KXTF9_REG_HP_ZOUT_L 0x04 +#define KXTF9_REG_HP_ZOUT_H 0x05 + +#define KXCJK1013_REG_XOUT_L 0x06 +/* + * From low byte X axis register, all the other addresses of Y and Z can be + * obtained by just applying axis offset. The following axis defines are just + * provide clarity, but not used. + */ +#define KXCJK1013_REG_XOUT_H 0x07 +#define KXCJK1013_REG_YOUT_L 0x08 +#define KXCJK1013_REG_YOUT_H 0x09 +#define KXCJK1013_REG_ZOUT_L 0x0A +#define KXCJK1013_REG_ZOUT_H 0x0B + +#define KXCJK1013_REG_DCST_RESP 0x0C +#define KXCJK1013_REG_WHO_AM_I 0x0F +#define KXTF9_REG_TILT_POS_CUR 0x10 +#define KXTF9_REG_TILT_POS_PREV 0x11 +#define KXTF9_REG_INT_SRC1 0x15 +#define KXTF9_REG_INT_SRC2 0x16 +#define KXCJK1013_REG_INT_SRC1 0x16 +#define KXCJK1013_REG_INT_SRC2 0x17 +#define KXCJK1013_REG_STATUS_REG 0x18 +#define KXCJK1013_REG_INT_REL 0x1A +#define KXCJK1013_REG_CTRL1 0x1B +#define KXTF9_REG_CTRL2 0x1C +#define KXTF9_REG_CTRL3 0x1D +#define KXCJK1013_REG_CTRL2 0x1D +#define KXCJK1013_REG_INT_CTRL1 0x1E +#define KXCJK1013_REG_INT_CTRL2 0x1F +#define KXTF9_REG_INT_CTRL3 0x20 +#define KXCJK1013_REG_DATA_CTRL 0x21 +#define KXTF9_REG_TILT_TIMER 0x28 +#define KXCJK1013_REG_WAKE_TIMER 0x29 +#define KXTF9_REG_TDT_TIMER 0x2B +#define KXTF9_REG_TDT_THRESH_H 0x2C +#define KXTF9_REG_TDT_THRESH_L 0x2D +#define KXTF9_REG_TDT_TAP_TIMER 0x2E +#define KXTF9_REG_TDT_TOTAL_TIMER 0x2F +#define KXTF9_REG_TDT_LATENCY_TIMER 0x30 +#define KXTF9_REG_TDT_WINDOW_TIMER 0x31 +#define KXCJK1013_REG_SELF_TEST 0x3A +#define KXTF9_REG_WAKE_THRESH 0x5A +#define KXTF9_REG_TILT_ANGLE 0x5C +#define KXTF9_REG_HYST_SET 0x5F +#define KXCJK1013_REG_WAKE_THRES 0x6A + +/* Everything up to 0x11 is equal to KXCJK1013/KXTF9 above */ +#define KX023_REG_INS1 0x12 +#define KX023_REG_INS2 0x13 +#define KX023_REG_INS3 0x14 +#define KX023_REG_STAT 0x15 +#define KX023_REG_INT_REL 0x17 +#define KX023_REG_CNTL1 0x18 +#define KX023_REG_CNTL2 0x19 +#define KX023_REG_CNTL3 0x1A +#define KX023_REG_ODCNTL 0x1B +#define KX023_REG_INC1 0x1C +#define KX023_REG_INC2 0x1D +#define KX023_REG_INC3 0x1E +#define KX023_REG_INC4 0x1F +#define KX023_REG_INC5 0x20 +#define KX023_REG_INC6 0x21 +#define KX023_REG_TILT_TIMER 0x22 +#define KX023_REG_WUFC 0x23 +#define KX023_REG_TDTRC 0x24 +#define KX023_REG_TDTC 0x25 +#define KX023_REG_TTH 0x26 +#define KX023_REG_TTL 0x27 +#define KX023_REG_FTD 0x28 +#define KX023_REG_STD 0x29 +#define KX023_REG_TLT 0x2A +#define KX023_REG_TWS 0x2B +#define KX023_REG_ATH 0x30 +#define KX023_REG_TILT_ANGLE_LL 0x32 +#define KX023_REG_TILT_ANGLE_HL 0x33 +#define KX023_REG_HYST_SET 0x34 +#define KX023_REG_LP_CNTL 0x35 +#define KX023_REG_BUF_CNTL1 0x3A +#define KX023_REG_BUF_CNTL2 0x3B +#define KX023_REG_BUF_STATUS_1 0x3C +#define KX023_REG_BUF_STATUS_2 0x3D +#define KX023_REG_BUF_CLEAR 0x3E +#define KX023_REG_BUF_READ 0x3F +#define KX023_REG_SELF_TEST 0x60 + +#define KXCJK1013_REG_CTRL1_BIT_PC1 BIT(7) +#define KXCJK1013_REG_CTRL1_BIT_RES BIT(6) +#define KXCJK1013_REG_CTRL1_BIT_DRDY BIT(5) +#define KXCJK1013_REG_CTRL1_BIT_GSEL1 BIT(4) +#define KXCJK1013_REG_CTRL1_BIT_GSEL0 BIT(3) +#define KXCJK1013_REG_CTRL1_BIT_WUFE BIT(1) + +#define KXCJK1013_REG_INT_CTRL1_BIT_IEU BIT(2) /* KXTF9 */ +#define KXCJK1013_REG_INT_CTRL1_BIT_IEL BIT(3) +#define KXCJK1013_REG_INT_CTRL1_BIT_IEA BIT(4) +#define KXCJK1013_REG_INT_CTRL1_BIT_IEN BIT(5) + +#define KXTF9_REG_TILT_BIT_LEFT_EDGE BIT(5) +#define KXTF9_REG_TILT_BIT_RIGHT_EDGE BIT(4) +#define KXTF9_REG_TILT_BIT_LOWER_EDGE BIT(3) +#define KXTF9_REG_TILT_BIT_UPPER_EDGE BIT(2) +#define KXTF9_REG_TILT_BIT_FACE_DOWN BIT(1) +#define KXTF9_REG_TILT_BIT_FACE_UP BIT(0) + +#define KXCJK1013_DATA_MASK_12_BIT 0x0FFF +#define KXCJK1013_MAX_STARTUP_TIME_US 100000 + +#define KXCJK1013_SLEEP_DELAY_MS 2000 + +#define KXCJK1013_REG_INT_SRC1_BIT_TPS BIT(0) /* KXTF9 */ +#define KXCJK1013_REG_INT_SRC1_BIT_WUFS BIT(1) +#define KXCJK1013_REG_INT_SRC1_MASK_TDTS (BIT(2) | BIT(3)) /* KXTF9 */ +#define KXCJK1013_REG_INT_SRC1_TAP_NONE 0 +#define KXCJK1013_REG_INT_SRC1_TAP_SINGLE BIT(2) +#define KXCJK1013_REG_INT_SRC1_TAP_DOUBLE BIT(3) +#define KXCJK1013_REG_INT_SRC1_BIT_DRDY BIT(4) + +/* KXCJK: INT_SOURCE2: motion detect, KXTF9: INT_SRC_REG1: tap detect */ +#define KXCJK1013_REG_INT_SRC2_BIT_ZP BIT(0) +#define KXCJK1013_REG_INT_SRC2_BIT_ZN BIT(1) +#define KXCJK1013_REG_INT_SRC2_BIT_YP BIT(2) +#define KXCJK1013_REG_INT_SRC2_BIT_YN BIT(3) +#define KXCJK1013_REG_INT_SRC2_BIT_XP BIT(4) +#define KXCJK1013_REG_INT_SRC2_BIT_XN BIT(5) + +/* KX023 interrupt routing to INT1. INT2 can be configured with INC6 */ +#define KX023_REG_INC4_BFI1 BIT(6) +#define KX023_REG_INC4_WMI1 BIT(5) +#define KX023_REG_INC4_DRDY1 BIT(4) +#define KX023_REG_INC4_TDTI1 BIT(2) +#define KX023_REG_INC4_WUFI1 BIT(1) +#define KX023_REG_INC4_TPI1 BIT(0) + +#define KXCJK1013_DEFAULT_WAKE_THRES 1 + +/* Refer to section 4 of the specification */ +struct kx_odr_start_up_time { + int odr_bits; + int usec; +}; + +/* KXCJK-1013 */ +static const struct kx_odr_start_up_time kxcjk1013_odr_start_up_times[] = { + { 0x08, 100000 }, + { 0x09, 100000 }, + { 0x0A, 100000 }, + { 0x0B, 100000 }, + { 0x00, 80000 }, + { 0x01, 41000 }, + { 0x02, 21000 }, + { 0x03, 11000 }, + { 0x04, 6400 }, + { 0x05, 3900 }, + { 0x06, 2700 }, + { 0x07, 2100 }, + { } +}; + +/* KXCTJ2-1009 */ +static const struct kx_odr_start_up_time kxtj21009_odr_start_up_times[] = { + { 0x08, 1240000 }, + { 0x09, 621000 }, + { 0x0A, 309000 }, + { 0x0B, 151000 }, + { 0x00, 80000 }, + { 0x01, 41000 }, + { 0x02, 21000 }, + { 0x03, 11000 }, + { 0x04, 6000 }, + { 0x05, 4000 }, + { 0x06, 3000 }, + { 0x07, 2000 }, + { } +}; + +/* KXTF9 */ +static const struct kx_odr_start_up_time kxtf9_odr_start_up_times[] = { + { 0x01, 81000 }, + { 0x02, 41000 }, + { 0x03, 21000 }, + { 0x04, 11000 }, + { 0x05, 5100 }, + { 0x06, 2700 }, + { } +}; + +/* KX023-1025 */ +static const struct kx_odr_start_up_time kx0231025_odr_start_up_times[] = { + /* First 4 are not in datasheet, taken from KXCTJ2-1009 */ + { 0x08, 1240000 }, + { 0x09, 621000 }, + { 0x0A, 309000 }, + { 0x0B, 151000 }, + { 0x00, 81000 }, + { 0x01, 40000 }, + { 0x02, 22000 }, + { 0x03, 12000 }, + { 0x04, 7000 }, + { 0x05, 4400 }, + { 0x06, 3000 }, + { 0x07, 3000 }, + { } +}; + +enum kx_acpi_type { + ACPI_GENERIC, + ACPI_SMO8500, + ACPI_KIOX010A, +}; + +struct kx_chipset_regs { + u8 int_src1; + u8 int_src2; + u8 int_rel; + u8 ctrl1; + u8 wuf_ctrl; + u8 int_ctrl1; + u8 data_ctrl; + u8 wake_timer; + u8 wake_thres; +}; + +static const struct kx_chipset_regs kxcjk1013_regs = { + .int_src1 = KXCJK1013_REG_INT_SRC1, + .int_src2 = KXCJK1013_REG_INT_SRC2, + .int_rel = KXCJK1013_REG_INT_REL, + .ctrl1 = KXCJK1013_REG_CTRL1, + .wuf_ctrl = KXCJK1013_REG_CTRL2, + .int_ctrl1 = KXCJK1013_REG_INT_CTRL1, + .data_ctrl = KXCJK1013_REG_DATA_CTRL, + .wake_timer = KXCJK1013_REG_WAKE_TIMER, + .wake_thres = KXCJK1013_REG_WAKE_THRES, +}; + +static const struct kx_chipset_regs kxtf9_regs = { + /* .int_src1 was moved to INT_SRC2 on KXTF9 */ + .int_src1 = KXTF9_REG_INT_SRC2, + /* .int_src2 is not available */ + .int_rel = KXCJK1013_REG_INT_REL, + .ctrl1 = KXCJK1013_REG_CTRL1, + .wuf_ctrl = KXTF9_REG_CTRL3, + .int_ctrl1 = KXCJK1013_REG_INT_CTRL1, + .data_ctrl = KXCJK1013_REG_DATA_CTRL, + .wake_timer = KXCJK1013_REG_WAKE_TIMER, + .wake_thres = KXTF9_REG_WAKE_THRESH, +}; + +/* The registers have totally different names but the bits are compatible */ +static const struct kx_chipset_regs kx0231025_regs = { + .int_src1 = KX023_REG_INS2, + .int_src2 = KX023_REG_INS3, + .int_rel = KX023_REG_INT_REL, + .ctrl1 = KX023_REG_CNTL1, + .wuf_ctrl = KX023_REG_CNTL3, + .int_ctrl1 = KX023_REG_INC1, + .data_ctrl = KX023_REG_ODCNTL, + .wake_timer = KX023_REG_WUFC, + .wake_thres = KX023_REG_ATH, +}; + +struct kx_chipset_info { + const struct kx_chipset_regs *regs; + const struct kx_odr_start_up_time *times; + enum kx_acpi_type acpi_type; +}; + +static const struct kx_chipset_info kxcjk1013_info = { + .regs = &kxcjk1013_regs, + .times = pm_ptr(kxcjk1013_odr_start_up_times), +}; + +static const struct kx_chipset_info kxcj91008_info = { + .regs = &kxcjk1013_regs, + .times = pm_ptr(kxcjk1013_odr_start_up_times), +}; + +static const struct kx_chipset_info kxcj91008_kiox010a_info = { + .regs = &kxcjk1013_regs, + .times = pm_ptr(kxcjk1013_odr_start_up_times), + .acpi_type = ACPI_KIOX010A, +}; + +static const struct kx_chipset_info kxcj91008_kiox020a_info = { + .regs = &kxcjk1013_regs, + .times = pm_ptr(kxcjk1013_odr_start_up_times), + .acpi_type = ACPI_GENERIC, +}; + +static const struct kx_chipset_info kxcj91008_smo8500_info = { + .regs = &kxcjk1013_regs, + .times = pm_ptr(kxcjk1013_odr_start_up_times), + .acpi_type = ACPI_SMO8500, +}; + +static const struct kx_chipset_info kxtj21009_info = { + .regs = &kxcjk1013_regs, + .times = pm_ptr(kxtj21009_odr_start_up_times), +}; + +static const struct kx_chipset_info kxtf9_info = { + .regs = &kxtf9_regs, + .times = pm_ptr(kxtf9_odr_start_up_times), +}; + +static const struct kx_chipset_info kx0231025_info = { + .regs = &kx0231025_regs, + .times = pm_ptr(kx0231025_odr_start_up_times), +}; + +enum kxcjk1013_axis { + AXIS_X, + AXIS_Y, + AXIS_Z, + AXIS_MAX +}; + +struct kxcjk1013_data { + struct i2c_client *client; + struct iio_trigger *dready_trig; + struct iio_trigger *motion_trig; + struct iio_mount_matrix orientation; + struct mutex mutex; + /* Ensure timestamp naturally aligned */ + struct { + s16 chans[AXIS_MAX]; + aligned_s64 timestamp; + } scan; + u8 odr_bits; + u8 range; + int wake_thres; + int wake_dur; + bool active_high_intr; + bool dready_trigger_on; + int ev_enable_state; + bool motion_trigger_on; + int64_t timestamp; + const struct kx_chipset_info *info; +}; + +enum kxcjk1013_mode { + STANDBY, + OPERATION, +}; + +enum kxcjk1013_range { + KXCJK1013_RANGE_2G, + KXCJK1013_RANGE_4G, + KXCJK1013_RANGE_8G, +}; + +struct kx_odr_map { + int val; + int val2; + int odr_bits; + int wuf_bits; +}; + +static const struct kx_odr_map samp_freq_table[] = { + { 0, 781000, 0x08, 0x00 }, + { 1, 563000, 0x09, 0x01 }, + { 3, 125000, 0x0A, 0x02 }, + { 6, 250000, 0x0B, 0x03 }, + { 12, 500000, 0x00, 0x04 }, + { 25, 0, 0x01, 0x05 }, + { 50, 0, 0x02, 0x06 }, + { 100, 0, 0x03, 0x06 }, + { 200, 0, 0x04, 0x06 }, + { 400, 0, 0x05, 0x06 }, + { 800, 0, 0x06, 0x06 }, + { 1600, 0, 0x07, 0x06 }, +}; + +static const char *const kxcjk1013_samp_freq_avail = + "0.781000 1.563000 3.125000 6.250000 12.500000 25 50 100 200 400 800 1600"; + +static const struct kx_odr_map kxtf9_samp_freq_table[] = { + { 25, 0, 0x01, 0x00 }, + { 50, 0, 0x02, 0x01 }, + { 100, 0, 0x03, 0x01 }, + { 200, 0, 0x04, 0x01 }, + { 400, 0, 0x05, 0x01 }, + { 800, 0, 0x06, 0x01 }, +}; + +static const char *const kxtf9_samp_freq_avail = + "25 50 100 200 400 800"; + +static const struct { + u16 scale; + u8 gsel_0; + u8 gsel_1; +} KXCJK1013_scale_table[] = { {9582, 0, 0}, + {19163, 1, 0}, + {38326, 0, 1} }; + +#ifdef CONFIG_ACPI +enum kiox010a_fn_index { + KIOX010A_SET_LAPTOP_MODE = 1, + KIOX010A_SET_TABLET_MODE = 2, +}; + +static int kiox010a_dsm(struct device *dev, int fn_index) +{ + acpi_handle handle = ACPI_HANDLE(dev); + guid_t kiox010a_dsm_guid; + union acpi_object *obj; + + if (!handle) + return -ENODEV; + + guid_parse("1f339696-d475-4e26-8cad-2e9f8e6d7a91", &kiox010a_dsm_guid); + + obj = acpi_evaluate_dsm(handle, &kiox010a_dsm_guid, 1, fn_index, NULL); + if (!obj) + return -EIO; + + ACPI_FREE(obj); + return 0; +} + +#endif + +static int kxcjk1013_set_mode(struct kxcjk1013_data *data, + enum kxcjk1013_mode mode) +{ + const struct kx_chipset_regs *regs = data->info->regs; + int ret; + + ret = i2c_smbus_read_byte_data(data->client, regs->ctrl1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_ctrl1\n"); + return ret; + } + + if (mode == STANDBY) + ret &= ~KXCJK1013_REG_CTRL1_BIT_PC1; + else + ret |= KXCJK1013_REG_CTRL1_BIT_PC1; + + ret = i2c_smbus_write_byte_data(data->client, regs->ctrl1, ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_ctrl1\n"); + return ret; + } + + return 0; +} + +static int kxcjk1013_get_mode(struct kxcjk1013_data *data, + enum kxcjk1013_mode *mode) +{ + const struct kx_chipset_regs *regs = data->info->regs; + int ret; + + ret = i2c_smbus_read_byte_data(data->client, regs->ctrl1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_ctrl1\n"); + return ret; + } + + if (ret & KXCJK1013_REG_CTRL1_BIT_PC1) + *mode = OPERATION; + else + *mode = STANDBY; + + return 0; +} + +static int kxcjk1013_set_range(struct kxcjk1013_data *data, int range_index) +{ + const struct kx_chipset_regs *regs = data->info->regs; + int ret; + + ret = i2c_smbus_read_byte_data(data->client, regs->ctrl1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_ctrl1\n"); + return ret; + } + + ret &= ~(KXCJK1013_REG_CTRL1_BIT_GSEL0 | + KXCJK1013_REG_CTRL1_BIT_GSEL1); + ret |= (KXCJK1013_scale_table[range_index].gsel_0 << 3); + ret |= (KXCJK1013_scale_table[range_index].gsel_1 << 4); + + ret = i2c_smbus_write_byte_data(data->client, regs->ctrl1, ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_ctrl1\n"); + return ret; + } + + data->range = range_index; + + return 0; +} + +static int kxcjk1013_chip_init(struct kxcjk1013_data *data) +{ + const struct kx_chipset_regs *regs = data->info->regs; + int ret; + +#ifdef CONFIG_ACPI + if (data->info->acpi_type == ACPI_KIOX010A) { + /* Make sure the kbd and touchpad on 2-in-1s using 2 KXCJ91008-s work */ + kiox010a_dsm(&data->client->dev, KIOX010A_SET_LAPTOP_MODE); + } +#endif + + ret = i2c_smbus_read_byte_data(data->client, KXCJK1013_REG_WHO_AM_I); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading who_am_i\n"); + return ret; + } + + dev_dbg(&data->client->dev, "KXCJK1013 Chip Id %x\n", ret); + + ret = kxcjk1013_set_mode(data, STANDBY); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(data->client, regs->ctrl1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_ctrl1\n"); + return ret; + } + + /* Set 12 bit mode */ + ret |= KXCJK1013_REG_CTRL1_BIT_RES; + + ret = i2c_smbus_write_byte_data(data->client, regs->ctrl1, ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_ctrl\n"); + return ret; + } + + /* Setting range to 4G */ + ret = kxcjk1013_set_range(data, KXCJK1013_RANGE_4G); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(data->client, regs->data_ctrl); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_data_ctrl\n"); + return ret; + } + + data->odr_bits = ret; + + /* Set up INT polarity */ + ret = i2c_smbus_read_byte_data(data->client, regs->int_ctrl1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_int_ctrl1\n"); + return ret; + } + + if (data->active_high_intr) + ret |= KXCJK1013_REG_INT_CTRL1_BIT_IEA; + else + ret &= ~KXCJK1013_REG_INT_CTRL1_BIT_IEA; + + ret = i2c_smbus_write_byte_data(data->client, regs->int_ctrl1, ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_int_ctrl1\n"); + return ret; + } + + /* On KX023, route all used interrupts to INT1 for now */ + if (data->info == &kx0231025_info && data->client->irq > 0) { + ret = i2c_smbus_write_byte_data(data->client, KX023_REG_INC4, + KX023_REG_INC4_DRDY1 | + KX023_REG_INC4_WUFI1); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_inc4\n"); + return ret; + } + } + + ret = kxcjk1013_set_mode(data, OPERATION); + if (ret < 0) + return ret; + + data->wake_thres = KXCJK1013_DEFAULT_WAKE_THRES; + + return 0; +} + +static int kxcjk1013_get_startup_times(struct kxcjk1013_data *data) +{ + const struct kx_odr_start_up_time *times; + + for (times = data->info->times; times->usec; times++) { + if (times->odr_bits == data->odr_bits) + return times->usec; + } + + return KXCJK1013_MAX_STARTUP_TIME_US; +} + +static int kxcjk1013_set_power_state(struct kxcjk1013_data *data, bool on) +{ +#ifdef CONFIG_PM + int ret; + + if (on) + ret = pm_runtime_resume_and_get(&data->client->dev); + else + ret = pm_runtime_put_autosuspend(&data->client->dev); + if (ret < 0) { + dev_err(&data->client->dev, + "Failed: %s for %d\n", __func__, on); + return ret; + } +#endif + + return 0; +} + +static int kxcjk1013_chip_update_thresholds(struct kxcjk1013_data *data) +{ + const struct kx_chipset_regs *regs = data->info->regs; + int ret; + + ret = i2c_smbus_write_byte_data(data->client, regs->wake_timer, data->wake_dur); + if (ret < 0) { + dev_err(&data->client->dev, + "Error writing reg_wake_timer\n"); + return ret; + } + + ret = i2c_smbus_write_byte_data(data->client, regs->wake_thres, data->wake_thres); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_wake_thres\n"); + return ret; + } + + return 0; +} + +static int kxcjk1013_setup_interrupt(struct kxcjk1013_data *data, + bool status, bool is_new_data) +{ + const struct kx_chipset_regs *regs = data->info->regs; + int ret; + enum kxcjk1013_mode store_mode; + + ret = kxcjk1013_get_mode(data, &store_mode); + if (ret < 0) + return ret; + + /* This is requirement by spec to change state to STANDBY */ + ret = kxcjk1013_set_mode(data, STANDBY); + if (ret < 0) + return ret; + + if (is_new_data == true) { + ret = kxcjk1013_chip_update_thresholds(data); + if (ret < 0) + return ret; + } + + ret = i2c_smbus_read_byte_data(data->client, regs->int_ctrl1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_int_ctrl1\n"); + return ret; + } + + if (status) + ret |= KXCJK1013_REG_INT_CTRL1_BIT_IEN; + else + ret &= ~KXCJK1013_REG_INT_CTRL1_BIT_IEN; + + ret = i2c_smbus_write_byte_data(data->client, regs->int_ctrl1, ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_int_ctrl1\n"); + return ret; + } + + ret = i2c_smbus_read_byte_data(data->client, regs->ctrl1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_ctrl1\n"); + return ret; + } + + if (is_new_data) { + if (status) + ret |= KXCJK1013_REG_CTRL1_BIT_DRDY; + else + ret &= ~KXCJK1013_REG_CTRL1_BIT_DRDY; + } else { + if (status) + ret |= KXCJK1013_REG_CTRL1_BIT_WUFE; + else + ret &= ~KXCJK1013_REG_CTRL1_BIT_WUFE; + } + + ret = i2c_smbus_write_byte_data(data->client, regs->ctrl1, ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_ctrl1\n"); + return ret; + } + + if (store_mode == OPERATION) { + ret = kxcjk1013_set_mode(data, OPERATION); + if (ret < 0) + return ret; + } + + return 0; +} + +static const struct kx_odr_map *kxcjk1013_find_odr_value( + const struct kx_odr_map *map, size_t map_size, int val, int val2) +{ + int i; + + for (i = 0; i < map_size; ++i) { + if (map[i].val == val && map[i].val2 == val2) + return &map[i]; + } + + return ERR_PTR(-EINVAL); +} + +static int kxcjk1013_convert_odr_value(const struct kx_odr_map *map, + size_t map_size, int odr_bits, + int *val, int *val2) +{ + int i; + + for (i = 0; i < map_size; ++i) { + if (map[i].odr_bits == odr_bits) { + *val = map[i].val; + *val2 = map[i].val2; + return IIO_VAL_INT_PLUS_MICRO; + } + } + + return -EINVAL; +} + +static int kxcjk1013_set_odr(struct kxcjk1013_data *data, int val, int val2) +{ + const struct kx_chipset_regs *regs = data->info->regs; + int ret; + enum kxcjk1013_mode store_mode; + const struct kx_odr_map *odr_setting; + + ret = kxcjk1013_get_mode(data, &store_mode); + if (ret < 0) + return ret; + + if (data->info == &kxtf9_info) + odr_setting = kxcjk1013_find_odr_value(kxtf9_samp_freq_table, + ARRAY_SIZE(kxtf9_samp_freq_table), + val, val2); + else + odr_setting = kxcjk1013_find_odr_value(samp_freq_table, + ARRAY_SIZE(samp_freq_table), + val, val2); + + if (IS_ERR(odr_setting)) + return PTR_ERR(odr_setting); + + /* To change ODR, the chip must be set to STANDBY as per spec */ + ret = kxcjk1013_set_mode(data, STANDBY); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(data->client, regs->data_ctrl, + odr_setting->odr_bits); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing data_ctrl\n"); + return ret; + } + + data->odr_bits = odr_setting->odr_bits; + + ret = i2c_smbus_write_byte_data(data->client, regs->wuf_ctrl, + odr_setting->wuf_bits); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_ctrl2\n"); + return ret; + } + + if (store_mode == OPERATION) { + ret = kxcjk1013_set_mode(data, OPERATION); + if (ret < 0) + return ret; + } + + return 0; +} + +static int kxcjk1013_get_odr(struct kxcjk1013_data *data, int *val, int *val2) +{ + if (data->info == &kxtf9_info) + return kxcjk1013_convert_odr_value(kxtf9_samp_freq_table, + ARRAY_SIZE(kxtf9_samp_freq_table), + data->odr_bits, val, val2); + else + return kxcjk1013_convert_odr_value(samp_freq_table, + ARRAY_SIZE(samp_freq_table), + data->odr_bits, val, val2); +} + +static int kxcjk1013_get_acc_reg(struct kxcjk1013_data *data, int axis) +{ + u8 reg = KXCJK1013_REG_XOUT_L + axis * 2; + int ret; + + ret = i2c_smbus_read_word_data(data->client, reg); + if (ret < 0) { + dev_err(&data->client->dev, + "failed to read accel_%c registers\n", 'x' + axis); + return ret; + } + + return ret; +} + +static int kxcjk1013_set_scale(struct kxcjk1013_data *data, int val) +{ + int ret, i; + enum kxcjk1013_mode store_mode; + + for (i = 0; i < ARRAY_SIZE(KXCJK1013_scale_table); ++i) { + if (KXCJK1013_scale_table[i].scale == val) { + ret = kxcjk1013_get_mode(data, &store_mode); + if (ret < 0) + return ret; + + ret = kxcjk1013_set_mode(data, STANDBY); + if (ret < 0) + return ret; + + ret = kxcjk1013_set_range(data, i); + if (ret < 0) + return ret; + + if (store_mode == OPERATION) { + ret = kxcjk1013_set_mode(data, OPERATION); + if (ret) + return ret; + } + + return 0; + } + } + + return -EINVAL; +} + +static int kxcjk1013_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct kxcjk1013_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&data->mutex); + if (iio_buffer_enabled(indio_dev)) + ret = -EBUSY; + else { + ret = kxcjk1013_set_power_state(data, true); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + ret = kxcjk1013_get_acc_reg(data, chan->scan_index); + if (ret < 0) { + kxcjk1013_set_power_state(data, false); + mutex_unlock(&data->mutex); + return ret; + } + *val = sign_extend32(ret >> chan->scan_type.shift, + chan->scan_type.realbits - 1); + ret = kxcjk1013_set_power_state(data, false); + } + mutex_unlock(&data->mutex); + + if (ret < 0) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = KXCJK1013_scale_table[data->range].scale; + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_CHAN_INFO_SAMP_FREQ: + mutex_lock(&data->mutex); + ret = kxcjk1013_get_odr(data, val, val2); + mutex_unlock(&data->mutex); + return ret; + + default: + return -EINVAL; + } +} + +static int kxcjk1013_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct kxcjk1013_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + mutex_lock(&data->mutex); + ret = kxcjk1013_set_odr(data, val, val2); + mutex_unlock(&data->mutex); + break; + case IIO_CHAN_INFO_SCALE: + if (val) + return -EINVAL; + + mutex_lock(&data->mutex); + ret = kxcjk1013_set_scale(data, val2); + mutex_unlock(&data->mutex); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int kxcjk1013_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct kxcjk1013_data *data = iio_priv(indio_dev); + + *val2 = 0; + switch (info) { + case IIO_EV_INFO_VALUE: + *val = data->wake_thres; + break; + case IIO_EV_INFO_PERIOD: + *val = data->wake_dur; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; +} + +static int kxcjk1013_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct kxcjk1013_data *data = iio_priv(indio_dev); + + if (data->ev_enable_state) + return -EBUSY; + + switch (info) { + case IIO_EV_INFO_VALUE: + data->wake_thres = val; + break; + case IIO_EV_INFO_PERIOD: + data->wake_dur = val; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int kxcjk1013_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct kxcjk1013_data *data = iio_priv(indio_dev); + + return data->ev_enable_state; +} + +static int kxcjk1013_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct kxcjk1013_data *data = iio_priv(indio_dev); + int ret; + + if (state && data->ev_enable_state) + return 0; + + mutex_lock(&data->mutex); + + if (!state && data->motion_trigger_on) { + data->ev_enable_state = 0; + mutex_unlock(&data->mutex); + return 0; + } + + /* + * We will expect the enable and disable to do operation in + * reverse order. This will happen here anyway as our + * resume operation uses sync mode runtime pm calls, the + * suspend operation will be delayed by autosuspend delay + * So the disable operation will still happen in reverse of + * enable operation. When runtime pm is disabled the mode + * is always on so sequence doesn't matter + */ + ret = kxcjk1013_set_power_state(data, state); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + + ret = kxcjk1013_setup_interrupt(data, state, false); + if (ret < 0) { + kxcjk1013_set_power_state(data, false); + data->ev_enable_state = 0; + mutex_unlock(&data->mutex); + return ret; + } + + data->ev_enable_state = state; + mutex_unlock(&data->mutex); + + return 0; +} + +static int kxcjk1013_buffer_preenable(struct iio_dev *indio_dev) +{ + struct kxcjk1013_data *data = iio_priv(indio_dev); + + return kxcjk1013_set_power_state(data, true); +} + +static int kxcjk1013_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct kxcjk1013_data *data = iio_priv(indio_dev); + + return kxcjk1013_set_power_state(data, false); +} + +static ssize_t kxcjk1013_get_samp_freq_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct kxcjk1013_data *data = iio_priv(indio_dev); + const char *str; + + if (data->info == &kxtf9_info) + str = kxtf9_samp_freq_avail; + else + str = kxcjk1013_samp_freq_avail; + + return sprintf(buf, "%s\n", str); +} + +static IIO_DEVICE_ATTR(in_accel_sampling_frequency_available, S_IRUGO, + kxcjk1013_get_samp_freq_avail, NULL, 0); + +static IIO_CONST_ATTR(in_accel_scale_available, "0.009582 0.019163 0.038326"); + +static struct attribute *kxcjk1013_attributes[] = { + &iio_dev_attr_in_accel_sampling_frequency_available.dev_attr.attr, + &iio_const_attr_in_accel_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group kxcjk1013_attrs_group = { + .attrs = kxcjk1013_attributes, +}; + +static const struct iio_event_spec kxcjk1013_event = { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_PERIOD) +}; + +static const struct iio_mount_matrix * +kxcjk1013_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct kxcjk1013_data *data = iio_priv(indio_dev); + + return &data->orientation; +} + +static const struct iio_chan_spec_ext_info kxcjk1013_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_TYPE, kxcjk1013_get_mount_matrix), + { } +}; + +#define KXCJK1013_CHANNEL(_axis) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = AXIS_##_axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 12, \ + .storagebits = 16, \ + .shift = 4, \ + .endianness = IIO_LE, \ + }, \ + .event_spec = &kxcjk1013_event, \ + .ext_info = kxcjk1013_ext_info, \ + .num_event_specs = 1 \ +} + +static const struct iio_chan_spec kxcjk1013_channels[] = { + KXCJK1013_CHANNEL(X), + KXCJK1013_CHANNEL(Y), + KXCJK1013_CHANNEL(Z), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_buffer_setup_ops kxcjk1013_buffer_setup_ops = { + .preenable = kxcjk1013_buffer_preenable, + .postdisable = kxcjk1013_buffer_postdisable, +}; + +static const struct iio_info kxcjk1013_iio_info = { + .attrs = &kxcjk1013_attrs_group, + .read_raw = kxcjk1013_read_raw, + .write_raw = kxcjk1013_write_raw, + .read_event_value = kxcjk1013_read_event, + .write_event_value = kxcjk1013_write_event, + .write_event_config = kxcjk1013_write_event_config, + .read_event_config = kxcjk1013_read_event_config, +}; + +static const unsigned long kxcjk1013_scan_masks[] = {0x7, 0}; + +static irqreturn_t kxcjk1013_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct kxcjk1013_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = i2c_smbus_read_i2c_block_data_or_emulated(data->client, + KXCJK1013_REG_XOUT_L, + AXIS_MAX * 2, + (u8 *)data->scan.chans); + mutex_unlock(&data->mutex); + if (ret < 0) + goto err; + + iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + data->timestamp); +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static void kxcjk1013_trig_reen(struct iio_trigger *trig) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct kxcjk1013_data *data = iio_priv(indio_dev); + const struct kx_chipset_regs *regs = data->info->regs; + int ret; + + ret = i2c_smbus_read_byte_data(data->client, regs->int_rel); + if (ret < 0) + dev_err(&data->client->dev, "Error reading reg_int_rel\n"); +} + +static int kxcjk1013_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct kxcjk1013_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + + if (!state && data->ev_enable_state && data->motion_trigger_on) { + data->motion_trigger_on = false; + mutex_unlock(&data->mutex); + return 0; + } + + ret = kxcjk1013_set_power_state(data, state); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + ret = kxcjk1013_setup_interrupt(data, state, data->motion_trig != trig); + if (ret < 0) { + kxcjk1013_set_power_state(data, false); + mutex_unlock(&data->mutex); + return ret; + } + if (data->motion_trig == trig) + data->motion_trigger_on = state; + else + data->dready_trigger_on = state; + + mutex_unlock(&data->mutex); + + return 0; +} + +static const struct iio_trigger_ops kxcjk1013_trigger_ops = { + .set_trigger_state = kxcjk1013_data_rdy_trigger_set_state, + .reenable = kxcjk1013_trig_reen, +}; + +static void kxcjk1013_report_motion_event(struct iio_dev *indio_dev) +{ + struct kxcjk1013_data *data = iio_priv(indio_dev); + const struct kx_chipset_regs *regs = data->info->regs; + + int ret = i2c_smbus_read_byte_data(data->client, regs->int_src2); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_int_src2\n"); + return; + } + + if (ret & KXCJK1013_REG_INT_SRC2_BIT_XN) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_X, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + data->timestamp); + + if (ret & KXCJK1013_REG_INT_SRC2_BIT_XP) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_X, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + data->timestamp); + + if (ret & KXCJK1013_REG_INT_SRC2_BIT_YN) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Y, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + data->timestamp); + + if (ret & KXCJK1013_REG_INT_SRC2_BIT_YP) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Y, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + data->timestamp); + + if (ret & KXCJK1013_REG_INT_SRC2_BIT_ZN) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Z, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + data->timestamp); + + if (ret & KXCJK1013_REG_INT_SRC2_BIT_ZP) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Z, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + data->timestamp); +} + +static irqreturn_t kxcjk1013_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct kxcjk1013_data *data = iio_priv(indio_dev); + const struct kx_chipset_regs *regs = data->info->regs; + int ret; + + ret = i2c_smbus_read_byte_data(data->client, regs->int_src1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_int_src1\n"); + goto ack_intr; + } + + if (ret & KXCJK1013_REG_INT_SRC1_BIT_WUFS) { + if (data->info == &kxtf9_info) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_X_AND_Y_AND_Z, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + data->timestamp); + else + kxcjk1013_report_motion_event(indio_dev); + } + +ack_intr: + if (data->dready_trigger_on) + return IRQ_HANDLED; + + ret = i2c_smbus_read_byte_data(data->client, regs->int_rel); + if (ret < 0) + dev_err(&data->client->dev, "Error reading reg_int_rel\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t kxcjk1013_data_rdy_trig_poll(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct kxcjk1013_data *data = iio_priv(indio_dev); + + data->timestamp = iio_get_time_ns(indio_dev); + + if (data->dready_trigger_on) + iio_trigger_poll(data->dready_trig); + else if (data->motion_trigger_on) + iio_trigger_poll(data->motion_trig); + + if (data->ev_enable_state) + return IRQ_WAKE_THREAD; + else + return IRQ_HANDLED; +} + +static int kxcjk1013_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + static const char * const regulator_names[] = { "vdd", "vddio" }; + struct kxcjk1013_data *data; + struct iio_dev *indio_dev; + struct kxcjk_1013_platform_data *pdata; + const void *ddata = NULL; + const char *name; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + pdata = dev_get_platdata(&client->dev); + if (pdata) { + data->active_high_intr = pdata->active_high_intr; + data->orientation = pdata->orientation; + } else { + data->active_high_intr = true; /* default polarity */ + + if (!iio_read_acpi_mount_matrix(&client->dev, &data->orientation, "ROTM")) { + ret = iio_read_mount_matrix(&client->dev, &data->orientation); + if (ret) + return ret; + } + + } + + ret = devm_regulator_bulk_get_enable(&client->dev, + ARRAY_SIZE(regulator_names), + regulator_names); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to get regulators\n"); + + /* + * A typical delay of 10ms is required for powering up + * according to the data sheets of supported chips. + * Hence double that to play safe. + */ + msleep(20); + + if (id) { + name = id->name; + data->info = (const struct kx_chipset_info *)(id->driver_data); + } else { + name = iio_get_acpi_device_name_and_data(&client->dev, &ddata); + data->info = ddata; + if (data->info == &kxcj91008_kiox010a_info) + indio_dev->label = "accel-display"; + else if (data->info == &kxcj91008_kiox020a_info) + indio_dev->label = "accel-base"; + } + if (!name) + return -ENODEV; + + ret = kxcjk1013_chip_init(data); + if (ret < 0) + return ret; + + mutex_init(&data->mutex); + + indio_dev->channels = kxcjk1013_channels; + indio_dev->num_channels = ARRAY_SIZE(kxcjk1013_channels); + indio_dev->available_scan_masks = kxcjk1013_scan_masks; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &kxcjk1013_iio_info; + + if (client->irq > 0 && data->info->acpi_type != ACPI_SMO8500) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + kxcjk1013_data_rdy_trig_poll, + kxcjk1013_event_handler, + IRQF_TRIGGER_RISING, + "kxcjk1013_event", + indio_dev); + if (ret) + goto err_poweroff; + + data->dready_trig = devm_iio_trigger_alloc(&client->dev, + "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->dready_trig) { + ret = -ENOMEM; + goto err_poweroff; + } + + data->motion_trig = devm_iio_trigger_alloc(&client->dev, + "%s-any-motion-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->motion_trig) { + ret = -ENOMEM; + goto err_poweroff; + } + + data->dready_trig->ops = &kxcjk1013_trigger_ops; + iio_trigger_set_drvdata(data->dready_trig, indio_dev); + ret = iio_trigger_register(data->dready_trig); + if (ret) + goto err_poweroff; + + indio_dev->trig = iio_trigger_get(data->dready_trig); + + data->motion_trig->ops = &kxcjk1013_trigger_ops; + iio_trigger_set_drvdata(data->motion_trig, indio_dev); + ret = iio_trigger_register(data->motion_trig); + if (ret) { + data->motion_trig = NULL; + goto err_trigger_unregister; + } + } + + ret = iio_triggered_buffer_setup(indio_dev, + &iio_pollfunc_store_time, + kxcjk1013_trigger_handler, + &kxcjk1013_buffer_setup_ops); + if (ret < 0) { + dev_err(&client->dev, "iio triggered buffer setup failed\n"); + goto err_trigger_unregister; + } + + ret = pm_runtime_set_active(&client->dev); + if (ret) + goto err_buffer_cleanup; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, + KXCJK1013_SLEEP_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "unable to register iio device\n"); + goto err_pm_cleanup; + } + + return 0; + +err_pm_cleanup: + pm_runtime_dont_use_autosuspend(&client->dev); + pm_runtime_disable(&client->dev); +err_buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); +err_trigger_unregister: + if (data->dready_trig) + iio_trigger_unregister(data->dready_trig); + if (data->motion_trig) + iio_trigger_unregister(data->motion_trig); +err_poweroff: + kxcjk1013_set_mode(data, STANDBY); + + return ret; +} + +static void kxcjk1013_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct kxcjk1013_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + iio_triggered_buffer_cleanup(indio_dev); + if (data->dready_trig) { + iio_trigger_unregister(data->dready_trig); + iio_trigger_unregister(data->motion_trig); + } + + mutex_lock(&data->mutex); + kxcjk1013_set_mode(data, STANDBY); + mutex_unlock(&data->mutex); +} + +static int kxcjk1013_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct kxcjk1013_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = kxcjk1013_set_mode(data, STANDBY); + mutex_unlock(&data->mutex); + + return ret; +} + +static int kxcjk1013_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct kxcjk1013_data *data = iio_priv(indio_dev); + int ret = 0; + + mutex_lock(&data->mutex); + ret = kxcjk1013_set_mode(data, OPERATION); + if (ret == 0) + ret = kxcjk1013_set_range(data, data->range); + mutex_unlock(&data->mutex); + + return ret; +} + +static int kxcjk1013_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct kxcjk1013_data *data = iio_priv(indio_dev); + int ret; + + ret = kxcjk1013_set_mode(data, STANDBY); + if (ret < 0) { + dev_err(&data->client->dev, "powering off device failed\n"); + return -EAGAIN; + } + return 0; +} + +static int kxcjk1013_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct kxcjk1013_data *data = iio_priv(indio_dev); + int ret; + int sleep_val; + + ret = kxcjk1013_set_mode(data, OPERATION); + if (ret < 0) + return ret; + + sleep_val = kxcjk1013_get_startup_times(data); + if (sleep_val < 20000) + usleep_range(sleep_val, 20000); + else + msleep_interruptible(sleep_val/1000); + + return 0; +} + +static const struct dev_pm_ops kxcjk1013_pm_ops = { + SYSTEM_SLEEP_PM_OPS(kxcjk1013_suspend, kxcjk1013_resume) + RUNTIME_PM_OPS(kxcjk1013_runtime_suspend, kxcjk1013_runtime_resume, NULL) +}; + +static const struct i2c_device_id kxcjk1013_id[] = { + { .name = "kxcjk1013", .driver_data = (kernel_ulong_t)&kxcjk1013_info }, + { .name = "kxcj91008", .driver_data = (kernel_ulong_t)&kxcj91008_info }, + { .name = "kxtj21009", .driver_data = (kernel_ulong_t)&kxtj21009_info }, + { .name = "kxtf9", .driver_data = (kernel_ulong_t)&kxtf9_info }, + { .name = "kx023-1025", .driver_data = (kernel_ulong_t)&kx0231025_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, kxcjk1013_id); + +static const struct of_device_id kxcjk1013_of_match[] = { + { .compatible = "kionix,kxcjk1013", &kxcjk1013_info }, + { .compatible = "kionix,kxcj91008", &kxcj91008_info }, + { .compatible = "kionix,kxtj21009", &kxtj21009_info }, + { .compatible = "kionix,kxtf9", &kxtf9_info }, + { .compatible = "kionix,kx023-1025", &kx0231025_info }, + { } +}; +MODULE_DEVICE_TABLE(of, kxcjk1013_of_match); + +static const struct acpi_device_id kx_acpi_match[] = { + { "KIOX0008", (kernel_ulong_t)&kxcj91008_info }, + { "KIOX0009", (kernel_ulong_t)&kxtj21009_info }, + { "KIOX000A", (kernel_ulong_t)&kxcj91008_info }, + /* KXCJ91008 in the display of a yoga 2-in-1 */ + { "KIOX010A", (kernel_ulong_t)&kxcj91008_kiox010a_info }, + /* KXCJ91008 in the base of a yoga 2-in-1 */ + { "KIOX020A", (kernel_ulong_t)&kxcj91008_kiox020a_info }, + { "KXCJ1008", (kernel_ulong_t)&kxcj91008_info }, + { "KXCJ1013", (kernel_ulong_t)&kxcjk1013_info }, + { "KXCJ9000", (kernel_ulong_t)&kxcj91008_info }, + { "KXJ2109", (kernel_ulong_t)&kxtj21009_info }, + { "KXTJ1009", (kernel_ulong_t)&kxtj21009_info }, + { "SMO8500", (kernel_ulong_t)&kxcj91008_smo8500_info }, + { } +}; +MODULE_DEVICE_TABLE(acpi, kx_acpi_match); + +static struct i2c_driver kxcjk1013_driver = { + .driver = { + .name = "kxcjk1013", + .acpi_match_table = kx_acpi_match, + .of_match_table = kxcjk1013_of_match, + .pm = pm_ptr(&kxcjk1013_pm_ops), + }, + .probe = kxcjk1013_probe, + .remove = kxcjk1013_remove, + .id_table = kxcjk1013_id, +}; +module_i2c_driver(kxcjk1013_driver); + +MODULE_AUTHOR("Srinivas Pandruvada "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("KXCJK1013 accelerometer driver"); diff --git a/drivers/iio/accel/kxsd9-i2c.c b/drivers/iio/accel/kxsd9-i2c.c new file mode 100644 index 000000000..f19626c2f --- /dev/null +++ b/drivers/iio/accel/kxsd9-i2c.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include + +#include "kxsd9.h" + +static int kxsd9_i2c_probe(struct i2c_client *i2c) +{ + static const struct regmap_config config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x0e, + }; + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(i2c, &config); + if (IS_ERR(regmap)) { + dev_err(&i2c->dev, "Failed to register i2c regmap: %pe\n", + regmap); + return PTR_ERR(regmap); + } + + return kxsd9_common_probe(&i2c->dev, + regmap, + i2c->name); +} + +static void kxsd9_i2c_remove(struct i2c_client *client) +{ + kxsd9_common_remove(&client->dev); +} + +static const struct of_device_id kxsd9_of_match[] = { + { .compatible = "kionix,kxsd9", }, + { } +}; +MODULE_DEVICE_TABLE(of, kxsd9_of_match); + +static const struct i2c_device_id kxsd9_i2c_id[] = { + { .name = "kxsd9" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, kxsd9_i2c_id); + +static struct i2c_driver kxsd9_i2c_driver = { + .driver = { + .name = "kxsd9", + .of_match_table = kxsd9_of_match, + .pm = pm_ptr(&kxsd9_dev_pm_ops), + }, + .probe = kxsd9_i2c_probe, + .remove = kxsd9_i2c_remove, + .id_table = kxsd9_i2c_id, +}; +module_i2c_driver(kxsd9_i2c_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("KXSD9 accelerometer I2C interface"); +MODULE_IMPORT_NS("IIO_KXSD9"); diff --git a/drivers/iio/accel/kxsd9-spi.c b/drivers/iio/accel/kxsd9-spi.c new file mode 100644 index 000000000..71da958d4 --- /dev/null +++ b/drivers/iio/accel/kxsd9-spi.c @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include +#include +#include +#include + +#include "kxsd9.h" + +static int kxsd9_spi_probe(struct spi_device *spi) +{ + static const struct regmap_config config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x0e, + }; + struct regmap *regmap; + + spi->mode = SPI_MODE_0; + regmap = devm_regmap_init_spi(spi, &config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "%s: regmap allocation failed: %ld\n", + __func__, PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + return kxsd9_common_probe(&spi->dev, + regmap, + spi_get_device_id(spi)->name); +} + +static void kxsd9_spi_remove(struct spi_device *spi) +{ + kxsd9_common_remove(&spi->dev); +} + +static const struct spi_device_id kxsd9_spi_id[] = { + { .name = "kxsd9" }, + { } +}; +MODULE_DEVICE_TABLE(spi, kxsd9_spi_id); + +static const struct of_device_id kxsd9_of_match[] = { + { .compatible = "kionix,kxsd9" }, + { } +}; +MODULE_DEVICE_TABLE(of, kxsd9_of_match); + +static struct spi_driver kxsd9_spi_driver = { + .driver = { + .name = "kxsd9", + .pm = pm_ptr(&kxsd9_dev_pm_ops), + .of_match_table = kxsd9_of_match, + }, + .probe = kxsd9_spi_probe, + .remove = kxsd9_spi_remove, + .id_table = kxsd9_spi_id, +}; +module_spi_driver(kxsd9_spi_driver); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("Kionix KXSD9 SPI driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_KXSD9"); diff --git a/drivers/iio/accel/kxsd9.c b/drivers/iio/accel/kxsd9.c new file mode 100644 index 000000000..7ac885d94 --- /dev/null +++ b/drivers/iio/accel/kxsd9.c @@ -0,0 +1,511 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * kxsd9.c simple support for the Kionix KXSD9 3D + * accelerometer. + * + * Copyright (c) 2008-2009 Jonathan Cameron + * + * The i2c interface is very similar, so shouldn't be a problem once + * I have a suitable wire made up. + * + * TODO: Support the motion detector + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kxsd9.h" + +#define KXSD9_REG_X 0x00 +#define KXSD9_REG_Y 0x02 +#define KXSD9_REG_Z 0x04 +#define KXSD9_REG_AUX 0x06 +#define KXSD9_REG_RESET 0x0a +#define KXSD9_REG_CTRL_C 0x0c + +#define KXSD9_CTRL_C_FS_MASK 0x03 +#define KXSD9_CTRL_C_FS_8G 0x00 +#define KXSD9_CTRL_C_FS_6G 0x01 +#define KXSD9_CTRL_C_FS_4G 0x02 +#define KXSD9_CTRL_C_FS_2G 0x03 +#define KXSD9_CTRL_C_MOT_LAT BIT(3) +#define KXSD9_CTRL_C_MOT_LEV BIT(4) +#define KXSD9_CTRL_C_LP_MASK 0xe0 +#define KXSD9_CTRL_C_LP_NONE 0x00 +#define KXSD9_CTRL_C_LP_2000HZC BIT(5) +#define KXSD9_CTRL_C_LP_2000HZB BIT(6) +#define KXSD9_CTRL_C_LP_2000HZA (BIT(5)|BIT(6)) +#define KXSD9_CTRL_C_LP_1000HZ BIT(7) +#define KXSD9_CTRL_C_LP_500HZ (BIT(7)|BIT(5)) +#define KXSD9_CTRL_C_LP_100HZ (BIT(7)|BIT(6)) +#define KXSD9_CTRL_C_LP_50HZ (BIT(7)|BIT(6)|BIT(5)) + +#define KXSD9_REG_CTRL_B 0x0d + +#define KXSD9_CTRL_B_CLK_HLD BIT(7) +#define KXSD9_CTRL_B_ENABLE BIT(6) +#define KXSD9_CTRL_B_ST BIT(5) /* Self-test */ + +#define KXSD9_REG_CTRL_A 0x0e + +/** + * struct kxsd9_state - device related storage + * @dev: pointer to the parent device + * @map: regmap to the device + * @orientation: mounting matrix, flipped axis etc + * @regs: regulators for this device, VDD and IOVDD + * @scale: the current scaling setting + */ +struct kxsd9_state { + struct device *dev; + struct regmap *map; + struct iio_mount_matrix orientation; + struct regulator_bulk_data regs[2]; + u8 scale; +}; + +#define KXSD9_SCALE_2G "0.011978" +#define KXSD9_SCALE_4G "0.023927" +#define KXSD9_SCALE_6G "0.035934" +#define KXSD9_SCALE_8G "0.047853" + +/* reverse order */ +static const int kxsd9_micro_scales[4] = { 47853, 35934, 23927, 11978 }; + +#define KXSD9_ZERO_G_OFFSET -2048 + +/* + * Regulator names + */ +static const char kxsd9_reg_vdd[] = "vdd"; +static const char kxsd9_reg_iovdd[] = "iovdd"; + +static int kxsd9_write_scale(struct iio_dev *indio_dev, int micro) +{ + int ret, i; + struct kxsd9_state *st = iio_priv(indio_dev); + bool foundit = false; + + for (i = 0; i < 4; i++) + if (micro == kxsd9_micro_scales[i]) { + foundit = true; + break; + } + if (!foundit) + return -EINVAL; + + ret = regmap_update_bits(st->map, + KXSD9_REG_CTRL_C, + KXSD9_CTRL_C_FS_MASK, + i); + if (ret < 0) + goto error_ret; + + /* Cached scale when the sensor is powered down */ + st->scale = i; + +error_ret: + return ret; +} + +static IIO_CONST_ATTR(accel_scale_available, + KXSD9_SCALE_2G " " + KXSD9_SCALE_4G " " + KXSD9_SCALE_6G " " + KXSD9_SCALE_8G); + +static struct attribute *kxsd9_attributes[] = { + &iio_const_attr_accel_scale_available.dev_attr.attr, + NULL, +}; + +static int kxsd9_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + int ret = -EINVAL; + struct kxsd9_state *st = iio_priv(indio_dev); + + pm_runtime_get_sync(st->dev); + + if (mask == IIO_CHAN_INFO_SCALE) { + /* Check no integer component */ + if (val) + ret = -EINVAL; + else + ret = kxsd9_write_scale(indio_dev, val2); + } + + pm_runtime_put_autosuspend(st->dev); + + return ret; +} + +static int kxsd9_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret = -EINVAL; + struct kxsd9_state *st = iio_priv(indio_dev); + unsigned int regval; + __be16 raw_val; + u16 nval; + + pm_runtime_get_sync(st->dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_bulk_read(st->map, chan->address, &raw_val, + sizeof(raw_val)); + if (ret) + goto error_ret; + nval = be16_to_cpu(raw_val); + /* Only 12 bits are valid */ + nval >>= 4; + *val = nval; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_OFFSET: + /* This has a bias of -2048 */ + *val = KXSD9_ZERO_G_OFFSET; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + ret = regmap_read(st->map, + KXSD9_REG_CTRL_C, + ®val); + if (ret < 0) + goto error_ret; + *val = 0; + *val2 = kxsd9_micro_scales[regval & KXSD9_CTRL_C_FS_MASK]; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + } + +error_ret: + pm_runtime_put_autosuspend(st->dev); + + return ret; +}; + +static irqreturn_t kxsd9_trigger_handler(int irq, void *p) +{ + const struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct kxsd9_state *st = iio_priv(indio_dev); + /* + * Ensure correct positioning and alignment of timestamp. + * No need to zero initialize as all elements written. + */ + struct { + __be16 chan[4]; + aligned_s64 ts; + } hw_values; + int ret; + + ret = regmap_bulk_read(st->map, + KXSD9_REG_X, + hw_values.chan, + sizeof(hw_values.chan)); + if (ret) { + dev_err(st->dev, "error reading data: %d\n", ret); + goto out; + } + + iio_push_to_buffers_with_ts(indio_dev, &hw_values, sizeof(hw_values), + iio_get_time_ns(indio_dev)); +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int kxsd9_buffer_preenable(struct iio_dev *indio_dev) +{ + struct kxsd9_state *st = iio_priv(indio_dev); + + pm_runtime_get_sync(st->dev); + + return 0; +} + +static int kxsd9_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct kxsd9_state *st = iio_priv(indio_dev); + + pm_runtime_put_autosuspend(st->dev); + + return 0; +} + +static const struct iio_buffer_setup_ops kxsd9_buffer_setup_ops = { + .preenable = kxsd9_buffer_preenable, + .postdisable = kxsd9_buffer_postdisable, +}; + +static const struct iio_mount_matrix * +kxsd9_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct kxsd9_state *st = iio_priv(indio_dev); + + return &st->orientation; +} + +static const struct iio_chan_spec_ext_info kxsd9_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_TYPE, kxsd9_get_mount_matrix), + { } +}; + +#define KXSD9_ACCEL_CHAN(axis, index) \ + { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .ext_info = kxsd9_ext_info, \ + .address = KXSD9_REG_##axis, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 12, \ + .storagebits = 16, \ + .shift = 4, \ + .endianness = IIO_BE, \ + }, \ + } + +static const struct iio_chan_spec kxsd9_channels[] = { + KXSD9_ACCEL_CHAN(X, 0), + KXSD9_ACCEL_CHAN(Y, 1), + KXSD9_ACCEL_CHAN(Z, 2), + { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .indexed = 1, + .address = KXSD9_REG_AUX, + .scan_index = 3, + .scan_type = { + .sign = 'u', + .realbits = 12, + .storagebits = 16, + .shift = 4, + .endianness = IIO_BE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const struct attribute_group kxsd9_attribute_group = { + .attrs = kxsd9_attributes, +}; + +static int kxsd9_power_up(struct kxsd9_state *st) +{ + int ret; + + /* Enable the regulators */ + ret = regulator_bulk_enable(ARRAY_SIZE(st->regs), st->regs); + if (ret) { + dev_err(st->dev, "Cannot enable regulators\n"); + return ret; + } + + /* Power up */ + ret = regmap_write(st->map, + KXSD9_REG_CTRL_B, + KXSD9_CTRL_B_ENABLE); + if (ret) + return ret; + + /* + * Set 1000Hz LPF, 2g fullscale, motion wakeup threshold 1g, + * latched wakeup + */ + ret = regmap_write(st->map, + KXSD9_REG_CTRL_C, + KXSD9_CTRL_C_LP_1000HZ | + KXSD9_CTRL_C_MOT_LEV | + KXSD9_CTRL_C_MOT_LAT | + st->scale); + if (ret) + return ret; + + /* + * Power-up time depends on the LPF setting, but typ 15.9 ms, let's + * set 20 ms to allow for some slack. + */ + msleep(20); + + return 0; +}; + +static int kxsd9_power_down(struct kxsd9_state *st) +{ + int ret; + + /* + * Set into low power mode - since there may be more users of the + * regulators this is the first step of the power saving: it will + * make sure we conserve power even if there are others users on the + * regulators. + */ + ret = regmap_clear_bits(st->map, KXSD9_REG_CTRL_B, KXSD9_CTRL_B_ENABLE); + if (ret) + return ret; + + /* Disable the regulators */ + ret = regulator_bulk_disable(ARRAY_SIZE(st->regs), st->regs); + if (ret) { + dev_err(st->dev, "Cannot disable regulators\n"); + return ret; + } + + return 0; +} + +static const struct iio_info kxsd9_info = { + .read_raw = &kxsd9_read_raw, + .write_raw = &kxsd9_write_raw, + .attrs = &kxsd9_attribute_group, +}; + +/* Four channels apart from timestamp, scan mask = 0x0f */ +static const unsigned long kxsd9_scan_masks[] = { 0xf, 0 }; + +int kxsd9_common_probe(struct device *dev, + struct regmap *map, + const char *name) +{ + struct iio_dev *indio_dev; + struct kxsd9_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->dev = dev; + st->map = map; + + indio_dev->channels = kxsd9_channels; + indio_dev->num_channels = ARRAY_SIZE(kxsd9_channels); + indio_dev->name = name; + indio_dev->info = &kxsd9_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->available_scan_masks = kxsd9_scan_masks; + + /* Read the mounting matrix, if present */ + ret = iio_read_mount_matrix(dev, &st->orientation); + if (ret) + return ret; + + /* Fetch and turn on regulators */ + st->regs[0].supply = kxsd9_reg_vdd; + st->regs[1].supply = kxsd9_reg_iovdd; + ret = devm_regulator_bulk_get(dev, + ARRAY_SIZE(st->regs), + st->regs); + if (ret) { + dev_err(dev, "Cannot get regulators\n"); + return ret; + } + /* Default scaling */ + st->scale = KXSD9_CTRL_C_FS_2G; + + kxsd9_power_up(st); + + ret = iio_triggered_buffer_setup(indio_dev, + iio_pollfunc_store_time, + kxsd9_trigger_handler, + &kxsd9_buffer_setup_ops); + if (ret) { + dev_err(dev, "triggered buffer setup failed\n"); + goto err_power_down; + } + + ret = iio_device_register(indio_dev); + if (ret) + goto err_cleanup_buffer; + + dev_set_drvdata(dev, indio_dev); + + /* Enable runtime PM */ + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + /* + * Set autosuspend to two orders of magnitude larger than the + * start-up time. 20ms start-up time means 2000ms autosuspend, + * i.e. 2 seconds. + */ + pm_runtime_set_autosuspend_delay(dev, 2000); + pm_runtime_use_autosuspend(dev); + pm_runtime_put(dev); + + return 0; + +err_cleanup_buffer: + iio_triggered_buffer_cleanup(indio_dev); +err_power_down: + kxsd9_power_down(st); + + return ret; +} +EXPORT_SYMBOL_NS(kxsd9_common_probe, "IIO_KXSD9"); + +void kxsd9_common_remove(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct kxsd9_state *st = iio_priv(indio_dev); + + iio_triggered_buffer_cleanup(indio_dev); + iio_device_unregister(indio_dev); + pm_runtime_get_sync(dev); + pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); + kxsd9_power_down(st); +} +EXPORT_SYMBOL_NS(kxsd9_common_remove, "IIO_KXSD9"); + +static int kxsd9_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct kxsd9_state *st = iio_priv(indio_dev); + + return kxsd9_power_down(st); +} + +static int kxsd9_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct kxsd9_state *st = iio_priv(indio_dev); + + return kxsd9_power_up(st); +} + +EXPORT_NS_RUNTIME_DEV_PM_OPS(kxsd9_dev_pm_ops, kxsd9_runtime_suspend, + kxsd9_runtime_resume, NULL, IIO_KXSD9); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("Kionix KXSD9 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/kxsd9.h b/drivers/iio/accel/kxsd9.h new file mode 100644 index 000000000..c04dbfa4e --- /dev/null +++ b/drivers/iio/accel/kxsd9.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include +#include + +#define KXSD9_STATE_RX_SIZE 2 +#define KXSD9_STATE_TX_SIZE 2 + +int kxsd9_common_probe(struct device *dev, + struct regmap *map, + const char *name); +void kxsd9_common_remove(struct device *dev); + +extern const struct dev_pm_ops kxsd9_dev_pm_ops; diff --git a/drivers/iio/accel/mc3230.c b/drivers/iio/accel/mc3230.c new file mode 100644 index 000000000..b45897210 --- /dev/null +++ b/drivers/iio/accel/mc3230.c @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * mCube MC3230 3-Axis Accelerometer + * + * Copyright (c) 2016 Hans de Goede + * + * IIO driver for mCube MC3230; 7-bit I2C address: 0x4c. + */ + +#include +#include +#include +#include + +#define MC3230_REG_XOUT 0x00 +#define MC3230_REG_YOUT 0x01 +#define MC3230_REG_ZOUT 0x02 + +#define MC3230_REG_MODE 0x07 +#define MC3230_MODE_OPCON_MASK 0x03 +#define MC3230_MODE_OPCON_WAKE 0x01 +#define MC3230_MODE_OPCON_STANDBY 0x03 + +#define MC3230_REG_CHIP_ID 0x18 +#define MC3230_REG_PRODUCT_CODE 0x3b + +/* + * The accelerometer has one measurement range: + * + * -1.5g - +1.5g (8-bit, signed) + * + */ + +struct mc3230_chip_info { + const char *name; + const u8 chip_id; + const u8 product_code; + const int scale; +}; + +static const struct mc3230_chip_info mc3230_chip_info = { + .name = "mc3230", + .chip_id = 0x01, + .product_code = 0x19, + /* (1.5 + 1.5) * 9.81 / (2^8 - 1) = 0.115411765 */ + .scale = 115411765, +}; + +static const struct mc3230_chip_info mc3510c_chip_info = { + .name = "mc3510c", + .chip_id = 0x23, + .product_code = 0x10, + /* Was obtained empirically */ + .scale = 625000000, +}; + +#define MC3230_CHANNEL(reg, axis) { \ + .type = IIO_ACCEL, \ + .address = reg, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info = mc3230_ext_info, \ +} + +struct mc3230_data { + const struct mc3230_chip_info *chip_info; + struct i2c_client *client; + struct iio_mount_matrix orientation; +}; + +static const struct iio_mount_matrix * +mc3230_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct mc3230_data *data = iio_priv(indio_dev); + + return &data->orientation; +} + +static const struct iio_chan_spec_ext_info mc3230_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, mc3230_get_mount_matrix), + { } +}; + +static const struct iio_chan_spec mc3230_channels[] = { + MC3230_CHANNEL(MC3230_REG_XOUT, X), + MC3230_CHANNEL(MC3230_REG_YOUT, Y), + MC3230_CHANNEL(MC3230_REG_ZOUT, Z), +}; + +static int mc3230_set_opcon(struct mc3230_data *data, int opcon) +{ + int ret; + struct i2c_client *client = data->client; + + ret = i2c_smbus_read_byte_data(client, MC3230_REG_MODE); + if (ret < 0) { + dev_err(&client->dev, "failed to read mode reg: %d\n", ret); + return ret; + } + + ret &= ~MC3230_MODE_OPCON_MASK; + ret |= opcon; + + ret = i2c_smbus_write_byte_data(client, MC3230_REG_MODE, ret); + if (ret < 0) { + dev_err(&client->dev, "failed to write mode reg: %d\n", ret); + return ret; + } + + return 0; +} + +static int mc3230_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mc3230_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = i2c_smbus_read_byte_data(data->client, chan->address); + if (ret < 0) + return ret; + *val = sign_extend32(ret, 7); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = data->chip_info->scale; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static const struct iio_info mc3230_info = { + .read_raw = mc3230_read_raw, +}; + +static int mc3230_probe(struct i2c_client *client) +{ + int ret; + struct iio_dev *indio_dev; + struct mc3230_data *data; + const struct mc3230_chip_info *chip_info; + + chip_info = i2c_get_match_data(client); + if (chip_info == NULL) { + dev_err(&client->dev, "failed to get match data"); + return -ENODATA; + } + + /* First check chip-id and product-id */ + ret = i2c_smbus_read_byte_data(client, MC3230_REG_CHIP_ID); + if (ret != chip_info->chip_id) { + dev_info(&client->dev, + "chip id check fail: 0x%x != 0x%x !\n", + ret, chip_info->chip_id); + } + + ret = i2c_smbus_read_byte_data(client, MC3230_REG_PRODUCT_CODE); + if (ret != chip_info->product_code) { + dev_info(&client->dev, + "product code check fail: 0x%x != 0x%x !\n", + ret, chip_info->product_code); + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->chip_info = chip_info; + data->client = client; + i2c_set_clientdata(client, indio_dev); + + indio_dev->info = &mc3230_info; + indio_dev->name = chip_info->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = mc3230_channels; + indio_dev->num_channels = ARRAY_SIZE(mc3230_channels); + + ret = mc3230_set_opcon(data, MC3230_MODE_OPCON_WAKE); + if (ret < 0) + return ret; + + ret = iio_read_mount_matrix(&client->dev, &data->orientation); + if (ret) + return ret; + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "device_register failed\n"); + mc3230_set_opcon(data, MC3230_MODE_OPCON_STANDBY); + } + + return ret; +} + +static void mc3230_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + + mc3230_set_opcon(iio_priv(indio_dev), MC3230_MODE_OPCON_STANDBY); +} + +static int mc3230_suspend(struct device *dev) +{ + struct mc3230_data *data; + + data = iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + return mc3230_set_opcon(data, MC3230_MODE_OPCON_STANDBY); +} + +static int mc3230_resume(struct device *dev) +{ + struct mc3230_data *data; + + data = iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + return mc3230_set_opcon(data, MC3230_MODE_OPCON_WAKE); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(mc3230_pm_ops, mc3230_suspend, mc3230_resume); + +static const struct i2c_device_id mc3230_i2c_id[] = { + { .name = "mc3230", .driver_data = (kernel_ulong_t)&mc3230_chip_info }, + { .name = "mc3510c", .driver_data = (kernel_ulong_t)&mc3510c_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mc3230_i2c_id); + +static const struct of_device_id mc3230_of_match[] = { + { .compatible = "mcube,mc3230", &mc3230_chip_info }, + { .compatible = "mcube,mc3510c", &mc3510c_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, mc3230_of_match); + +static struct i2c_driver mc3230_driver = { + .driver = { + .name = "mc3230", + .of_match_table = mc3230_of_match, + .pm = pm_sleep_ptr(&mc3230_pm_ops), + }, + .probe = mc3230_probe, + .remove = mc3230_remove, + .id_table = mc3230_i2c_id, +}; + +module_i2c_driver(mc3230_driver); + +MODULE_AUTHOR("Hans de Goede "); +MODULE_DESCRIPTION("mCube MC3230 3-Axis Accelerometer driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/mma7455.h b/drivers/iio/accel/mma7455.h new file mode 100644 index 000000000..1fcc4b64b --- /dev/null +++ b/drivers/iio/accel/mma7455.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * IIO accel driver for Freescale MMA7455L 3-axis 10-bit accelerometer + * Copyright 2015 Joachim Eastwood + */ + +#ifndef __MMA7455_H +#define __MMA7455_H + +extern const struct regmap_config mma7455_core_regmap; + +int mma7455_core_probe(struct device *dev, struct regmap *regmap, + const char *name); +void mma7455_core_remove(struct device *dev); + +#endif diff --git a/drivers/iio/accel/mma7455_core.c b/drivers/iio/accel/mma7455_core.c new file mode 100644 index 000000000..a2b5bdf14 --- /dev/null +++ b/drivers/iio/accel/mma7455_core.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * IIO accel core driver for Freescale MMA7455L 3-axis 10-bit accelerometer + * Copyright 2015 Joachim Eastwood + * + * UNSUPPORTED hardware features: + * - 8-bit mode with different scales + * - INT1/INT2 interrupts + * - Offset calibration + * - Events + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mma7455.h" + +#define MMA7455_REG_XOUTL 0x00 +#define MMA7455_REG_XOUTH 0x01 +#define MMA7455_REG_YOUTL 0x02 +#define MMA7455_REG_YOUTH 0x03 +#define MMA7455_REG_ZOUTL 0x04 +#define MMA7455_REG_ZOUTH 0x05 +#define MMA7455_REG_STATUS 0x09 +#define MMA7455_STATUS_DRDY BIT(0) +#define MMA7455_REG_WHOAMI 0x0f +#define MMA7455_WHOAMI_ID 0x55 +#define MMA7455_REG_MCTL 0x16 +#define MMA7455_MCTL_MODE_STANDBY 0x00 +#define MMA7455_MCTL_MODE_MEASURE 0x01 +#define MMA7455_REG_CTL1 0x18 +#define MMA7455_CTL1_DFBW_MASK BIT(7) +#define MMA7455_CTL1_DFBW_125HZ BIT(7) +#define MMA7455_CTL1_DFBW_62_5HZ 0 +#define MMA7455_REG_TW 0x1e + +/* + * When MMA7455 is used in 10-bit it has a fullscale of -8g + * corresponding to raw value -512. The userspace interface + * uses m/s^2 and we declare micro units. + * So scale factor is given by: + * g * 8 * 1e6 / 512 = 153228.90625, with g = 9.80665 + */ +#define MMA7455_10BIT_SCALE 153229 + +struct mma7455_data { + struct regmap *regmap; + /* + * Used to reorganize data. Will ensure correct alignment of + * the timestamp if present + */ + struct { + __le16 channels[3]; + aligned_s64 ts; + } scan; +}; + +static int mma7455_drdy(struct mma7455_data *mma7455) +{ + struct device *dev = regmap_get_device(mma7455->regmap); + unsigned int reg; + int tries = 3; + int ret; + + while (tries-- > 0) { + ret = regmap_read(mma7455->regmap, MMA7455_REG_STATUS, ®); + if (ret) + return ret; + + if (reg & MMA7455_STATUS_DRDY) + return 0; + + msleep(20); + } + + dev_warn(dev, "data not ready\n"); + + return -EIO; +} + +static irqreturn_t mma7455_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct mma7455_data *mma7455 = iio_priv(indio_dev); + int ret; + + ret = mma7455_drdy(mma7455); + if (ret) + goto done; + + ret = regmap_bulk_read(mma7455->regmap, MMA7455_REG_XOUTL, + mma7455->scan.channels, + sizeof(mma7455->scan.channels)); + if (ret) + goto done; + + iio_push_to_buffers_with_ts(indio_dev, &mma7455->scan, + sizeof(mma7455->scan), + iio_get_time_ns(indio_dev)); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int mma7455_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mma7455_data *mma7455 = iio_priv(indio_dev); + unsigned int reg; + __le16 data; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + + ret = mma7455_drdy(mma7455); + if (ret) + return ret; + + ret = regmap_bulk_read(mma7455->regmap, chan->address, &data, + sizeof(data)); + if (ret) + return ret; + + *val = sign_extend32(le16_to_cpu(data), + chan->scan_type.realbits - 1); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = MMA7455_10BIT_SCALE; + + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_CHAN_INFO_SAMP_FREQ: + ret = regmap_read(mma7455->regmap, MMA7455_REG_CTL1, ®); + if (ret) + return ret; + + if (reg & MMA7455_CTL1_DFBW_MASK) + *val = 250; + else + *val = 125; + + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int mma7455_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct mma7455_data *mma7455 = iio_priv(indio_dev); + int i; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (val == 250 && val2 == 0) + i = MMA7455_CTL1_DFBW_125HZ; + else if (val == 125 && val2 == 0) + i = MMA7455_CTL1_DFBW_62_5HZ; + else + return -EINVAL; + + return regmap_update_bits(mma7455->regmap, MMA7455_REG_CTL1, + MMA7455_CTL1_DFBW_MASK, i); + + case IIO_CHAN_INFO_SCALE: + /* In 10-bit mode there is only one scale available */ + if (val == 0 && val2 == MMA7455_10BIT_SCALE) + return 0; + break; + } + + return -EINVAL; +} + +static IIO_CONST_ATTR(sampling_frequency_available, "125 250"); + +static struct attribute *mma7455_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group mma7455_group = { + .attrs = mma7455_attributes, +}; + +static const struct iio_info mma7455_info = { + .attrs = &mma7455_group, + .read_raw = mma7455_read_raw, + .write_raw = mma7455_write_raw, +}; + +#define MMA7455_CHANNEL(axis, idx) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .address = MMA7455_REG_##axis##OUTL,\ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = idx, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 10, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ +} + +static const struct iio_chan_spec mma7455_channels[] = { + MMA7455_CHANNEL(X, 0), + MMA7455_CHANNEL(Y, 1), + MMA7455_CHANNEL(Z, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const unsigned long mma7455_scan_masks[] = {0x7, 0}; + +const struct regmap_config mma7455_core_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MMA7455_REG_TW, +}; +EXPORT_SYMBOL_NS_GPL(mma7455_core_regmap, "IIO_MMA7455"); + +int mma7455_core_probe(struct device *dev, struct regmap *regmap, + const char *name) +{ + struct mma7455_data *mma7455; + struct iio_dev *indio_dev; + unsigned int reg; + int ret; + + ret = regmap_read(regmap, MMA7455_REG_WHOAMI, ®); + if (ret) { + dev_err(dev, "unable to read reg\n"); + return ret; + } + + if (reg != MMA7455_WHOAMI_ID) { + dev_err(dev, "device id mismatch\n"); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(dev, sizeof(*mma7455)); + if (!indio_dev) + return -ENOMEM; + + dev_set_drvdata(dev, indio_dev); + mma7455 = iio_priv(indio_dev); + mma7455->regmap = regmap; + + indio_dev->info = &mma7455_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = mma7455_channels; + indio_dev->num_channels = ARRAY_SIZE(mma7455_channels); + indio_dev->available_scan_masks = mma7455_scan_masks; + + regmap_write(mma7455->regmap, MMA7455_REG_MCTL, + MMA7455_MCTL_MODE_MEASURE); + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + mma7455_trigger_handler, NULL); + if (ret) { + dev_err(dev, "unable to setup triggered buffer\n"); + return ret; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(dev, "unable to register device\n"); + iio_triggered_buffer_cleanup(indio_dev); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(mma7455_core_probe, "IIO_MMA7455"); + +void mma7455_core_remove(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct mma7455_data *mma7455 = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + + regmap_write(mma7455->regmap, MMA7455_REG_MCTL, + MMA7455_MCTL_MODE_STANDBY); +} +EXPORT_SYMBOL_NS_GPL(mma7455_core_remove, "IIO_MMA7455"); + +MODULE_AUTHOR("Joachim Eastwood "); +MODULE_DESCRIPTION("Freescale MMA7455L core accelerometer driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/mma7455_i2c.c b/drivers/iio/accel/mma7455_i2c.c new file mode 100644 index 000000000..9588fd133 --- /dev/null +++ b/drivers/iio/accel/mma7455_i2c.c @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * IIO accel I2C driver for Freescale MMA7455L 3-axis 10-bit accelerometer + * Copyright 2015 Joachim Eastwood + */ + +#include +#include +#include + +#include "mma7455.h" + +static int mma7455_i2c_probe(struct i2c_client *i2c) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(i2c); + struct regmap *regmap; + const char *name = NULL; + + regmap = devm_regmap_init_i2c(i2c, &mma7455_core_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + if (id) + name = id->name; + + return mma7455_core_probe(&i2c->dev, regmap, name); +} + +static void mma7455_i2c_remove(struct i2c_client *i2c) +{ + mma7455_core_remove(&i2c->dev); +} + +static const struct i2c_device_id mma7455_i2c_ids[] = { + { .name = "mma7455" }, + { .name = "mma7456" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mma7455_i2c_ids); + +static const struct of_device_id mma7455_of_match[] = { + { .compatible = "fsl,mma7455" }, + { .compatible = "fsl,mma7456" }, + { } +}; +MODULE_DEVICE_TABLE(of, mma7455_of_match); + +static struct i2c_driver mma7455_i2c_driver = { + .probe = mma7455_i2c_probe, + .remove = mma7455_i2c_remove, + .id_table = mma7455_i2c_ids, + .driver = { + .name = "mma7455-i2c", + .of_match_table = mma7455_of_match, + }, +}; +module_i2c_driver(mma7455_i2c_driver); + +MODULE_AUTHOR("Joachim Eastwood "); +MODULE_DESCRIPTION("Freescale MMA7455L I2C accelerometer driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_MMA7455"); diff --git a/drivers/iio/accel/mma7455_spi.c b/drivers/iio/accel/mma7455_spi.c new file mode 100644 index 000000000..872c47acf --- /dev/null +++ b/drivers/iio/accel/mma7455_spi.c @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * IIO accel SPI driver for Freescale MMA7455L 3-axis 10-bit accelerometer + * Copyright 2015 Joachim Eastwood + */ + +#include +#include +#include + +#include "mma7455.h" + +static int mma7455_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct regmap *regmap; + + regmap = devm_regmap_init_spi(spi, &mma7455_core_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return mma7455_core_probe(&spi->dev, regmap, id->name); +} + +static void mma7455_spi_remove(struct spi_device *spi) +{ + mma7455_core_remove(&spi->dev); +} + +static const struct spi_device_id mma7455_spi_ids[] = { + { .name = "mma7455" }, + { .name = "mma7456" }, + { } +}; +MODULE_DEVICE_TABLE(spi, mma7455_spi_ids); + +static struct spi_driver mma7455_spi_driver = { + .probe = mma7455_spi_probe, + .remove = mma7455_spi_remove, + .id_table = mma7455_spi_ids, + .driver = { + .name = "mma7455-spi", + }, +}; +module_spi_driver(mma7455_spi_driver); + +MODULE_AUTHOR("Joachim Eastwood "); +MODULE_DESCRIPTION("Freescale MMA7455L SPI accelerometer driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_MMA7455"); diff --git a/drivers/iio/accel/mma7660.c b/drivers/iio/accel/mma7660.c new file mode 100644 index 000000000..38b4df76c --- /dev/null +++ b/drivers/iio/accel/mma7660.c @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Freescale MMA7660FC 3-Axis Accelerometer + * + * Copyright (c) 2016, Intel Corporation. + * + * IIO driver for Freescale MMA7660FC; 7-bit I2C address: 0x4c. + */ + +#include +#include +#include +#include + +#define MMA7660_DRIVER_NAME "mma7660" + +#define MMA7660_REG_XOUT 0x00 +#define MMA7660_REG_YOUT 0x01 +#define MMA7660_REG_ZOUT 0x02 +#define MMA7660_REG_OUT_BIT_ALERT BIT(6) + +#define MMA7660_REG_MODE 0x07 +#define MMA7660_REG_MODE_BIT_MODE BIT(0) +#define MMA7660_REG_MODE_BIT_TON BIT(2) + +#define MMA7660_I2C_READ_RETRIES 5 + +/* + * The accelerometer has one measurement range: + * + * -1.5g - +1.5g (6-bit, signed) + * + * scale = (1.5 + 1.5) * 9.81 / (2^6 - 1) = 0.467142857 + */ + +#define MMA7660_SCALE_AVAIL "0.467142857" + +static const int mma7660_nscale = 467142857; + +enum mma7660_mode { + MMA7660_MODE_STANDBY, + MMA7660_MODE_ACTIVE +}; + +struct mma7660_data { + struct i2c_client *client; + struct mutex lock; + enum mma7660_mode mode; + struct iio_mount_matrix orientation; +}; + +static const struct iio_mount_matrix * +mma7660_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct mma7660_data *data = iio_priv(indio_dev); + + return &data->orientation; +} + +static const struct iio_chan_spec_ext_info mma7660_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, mma7660_get_mount_matrix), + { } +}; + +static IIO_CONST_ATTR(in_accel_scale_available, MMA7660_SCALE_AVAIL); + +static struct attribute *mma7660_attributes[] = { + &iio_const_attr_in_accel_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group mma7660_attribute_group = { + .attrs = mma7660_attributes +}; + +#define MMA7660_CHANNEL(reg, axis) { \ + .type = IIO_ACCEL, \ + .address = reg, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info = mma7660_ext_info, \ +} + +static const struct iio_chan_spec mma7660_channels[] = { + MMA7660_CHANNEL(MMA7660_REG_XOUT, X), + MMA7660_CHANNEL(MMA7660_REG_YOUT, Y), + MMA7660_CHANNEL(MMA7660_REG_ZOUT, Z), +}; + +static int mma7660_set_mode(struct mma7660_data *data, + enum mma7660_mode mode) +{ + int ret; + struct i2c_client *client = data->client; + + if (mode == data->mode) + return 0; + + ret = i2c_smbus_read_byte_data(client, MMA7660_REG_MODE); + if (ret < 0) { + dev_err(&client->dev, "failed to read sensor mode\n"); + return ret; + } + + if (mode == MMA7660_MODE_ACTIVE) { + ret &= ~MMA7660_REG_MODE_BIT_TON; + ret |= MMA7660_REG_MODE_BIT_MODE; + } else { + ret &= ~MMA7660_REG_MODE_BIT_TON; + ret &= ~MMA7660_REG_MODE_BIT_MODE; + } + + ret = i2c_smbus_write_byte_data(client, MMA7660_REG_MODE, ret); + if (ret < 0) { + dev_err(&client->dev, "failed to change sensor mode\n"); + return ret; + } + + data->mode = mode; + + return ret; +} + +static int mma7660_read_accel(struct mma7660_data *data, u8 address) +{ + int ret, retries = MMA7660_I2C_READ_RETRIES; + struct i2c_client *client = data->client; + + /* + * Read data. If the Alert bit is set, the register was read at + * the same time as the device was attempting to update the content. + * The solution is to read the register again. Do this only + * MMA7660_I2C_READ_RETRIES times to avoid spending too much time + * in the kernel. + */ + do { + ret = i2c_smbus_read_byte_data(client, address); + if (ret < 0) { + dev_err(&client->dev, "register read failed\n"); + return ret; + } + } while (retries-- > 0 && ret & MMA7660_REG_OUT_BIT_ALERT); + + if (ret & MMA7660_REG_OUT_BIT_ALERT) { + dev_err(&client->dev, "all register read retries failed\n"); + return -ETIMEDOUT; + } + + return ret; +} + +static int mma7660_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mma7660_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&data->lock); + ret = mma7660_read_accel(data, chan->address); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + *val = sign_extend32(ret, 5); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = mma7660_nscale; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static const struct iio_info mma7660_info = { + .read_raw = mma7660_read_raw, + .attrs = &mma7660_attribute_group, +}; + +static int mma7660_probe(struct i2c_client *client) +{ + int ret; + struct iio_dev *indio_dev; + struct mma7660_data *data; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + i2c_set_clientdata(client, indio_dev); + mutex_init(&data->lock); + data->mode = MMA7660_MODE_STANDBY; + + ret = iio_read_mount_matrix(&client->dev, &data->orientation); + if (ret) + return ret; + + indio_dev->info = &mma7660_info; + indio_dev->name = MMA7660_DRIVER_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = mma7660_channels; + indio_dev->num_channels = ARRAY_SIZE(mma7660_channels); + + ret = mma7660_set_mode(data, MMA7660_MODE_ACTIVE); + if (ret < 0) + return ret; + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "device_register failed\n"); + mma7660_set_mode(data, MMA7660_MODE_STANDBY); + } + + return ret; +} + +static void mma7660_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + int ret; + + iio_device_unregister(indio_dev); + + ret = mma7660_set_mode(iio_priv(indio_dev), MMA7660_MODE_STANDBY); + if (ret) + dev_warn(&client->dev, "Failed to put device in stand-by mode (%pe), ignoring\n", + ERR_PTR(ret)); +} + +static int mma7660_suspend(struct device *dev) +{ + struct mma7660_data *data; + + data = iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + return mma7660_set_mode(data, MMA7660_MODE_STANDBY); +} + +static int mma7660_resume(struct device *dev) +{ + struct mma7660_data *data; + + data = iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + return mma7660_set_mode(data, MMA7660_MODE_ACTIVE); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(mma7660_pm_ops, mma7660_suspend, + mma7660_resume); + +static const struct i2c_device_id mma7660_i2c_id[] = { + { .name = "mma7660" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mma7660_i2c_id); + +static const struct of_device_id mma7660_of_match[] = { + { .compatible = "fsl,mma7660" }, + { } +}; +MODULE_DEVICE_TABLE(of, mma7660_of_match); + +static const struct acpi_device_id mma7660_acpi_id[] = { + {"MMA7660", 0}, + { } +}; + +MODULE_DEVICE_TABLE(acpi, mma7660_acpi_id); + +static struct i2c_driver mma7660_driver = { + .driver = { + .name = "mma7660", + .pm = pm_sleep_ptr(&mma7660_pm_ops), + .of_match_table = mma7660_of_match, + .acpi_match_table = mma7660_acpi_id, + }, + .probe = mma7660_probe, + .remove = mma7660_remove, + .id_table = mma7660_i2c_id, +}; + +module_i2c_driver(mma7660_driver); + +MODULE_AUTHOR("Constantin Musca "); +MODULE_DESCRIPTION("Freescale MMA7660FC 3-Axis Accelerometer driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/mma8452.c b/drivers/iio/accel/mma8452.c new file mode 100644 index 000000000..7d683686d --- /dev/null +++ b/drivers/iio/accel/mma8452.c @@ -0,0 +1,1855 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mma8452.c - Support for following Freescale / NXP 3-axis accelerometers: + * + * device name digital output 7-bit I2C slave address (pin selectable) + * --------------------------------------------------------------------- + * MMA8451Q 14 bit 0x1c / 0x1d + * MMA8452Q 12 bit 0x1c / 0x1d + * MMA8453Q 10 bit 0x1c / 0x1d + * MMA8652FC 12 bit 0x1d + * MMA8653FC 10 bit 0x1d + * FXLS8471Q 14 bit 0x1e / 0x1d / 0x1c / 0x1f + * + * Copyright 2015 Martin Kepplinger + * Copyright 2014 Peter Meerwald + * + * + * TODO: orientation events + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define MMA8452_STATUS 0x00 +#define MMA8452_STATUS_DRDY (BIT(2) | BIT(1) | BIT(0)) +#define MMA8452_OUT_X 0x01 /* MSB first */ +#define MMA8452_OUT_Y 0x03 +#define MMA8452_OUT_Z 0x05 +#define MMA8452_INT_SRC 0x0c +#define MMA8452_WHO_AM_I 0x0d +#define MMA8452_DATA_CFG 0x0e +#define MMA8452_DATA_CFG_FS_MASK GENMASK(1, 0) +#define MMA8452_DATA_CFG_FS_2G 0 +#define MMA8452_DATA_CFG_FS_4G 1 +#define MMA8452_DATA_CFG_FS_8G 2 +#define MMA8452_DATA_CFG_HPF_MASK BIT(4) +#define MMA8452_HP_FILTER_CUTOFF 0x0f +#define MMA8452_HP_FILTER_CUTOFF_SEL_MASK GENMASK(1, 0) +#define MMA8452_FF_MT_CFG 0x15 +#define MMA8452_FF_MT_CFG_OAE BIT(6) +#define MMA8452_FF_MT_CFG_ELE BIT(7) +#define MMA8452_FF_MT_SRC 0x16 +#define MMA8452_FF_MT_SRC_XHE BIT(1) +#define MMA8452_FF_MT_SRC_YHE BIT(3) +#define MMA8452_FF_MT_SRC_ZHE BIT(5) +#define MMA8452_FF_MT_THS 0x17 +#define MMA8452_FF_MT_THS_MASK 0x7f +#define MMA8452_FF_MT_COUNT 0x18 +#define MMA8452_FF_MT_CHAN_SHIFT 3 +#define MMA8452_TRANSIENT_CFG 0x1d +#define MMA8452_TRANSIENT_CFG_CHAN(chan) BIT(chan + 1) +#define MMA8452_TRANSIENT_CFG_HPF_BYP BIT(0) +#define MMA8452_TRANSIENT_CFG_ELE BIT(4) +#define MMA8452_TRANSIENT_SRC 0x1e +#define MMA8452_TRANSIENT_SRC_XTRANSE BIT(1) +#define MMA8452_TRANSIENT_SRC_YTRANSE BIT(3) +#define MMA8452_TRANSIENT_SRC_ZTRANSE BIT(5) +#define MMA8452_TRANSIENT_THS 0x1f +#define MMA8452_TRANSIENT_THS_MASK GENMASK(6, 0) +#define MMA8452_TRANSIENT_COUNT 0x20 +#define MMA8452_TRANSIENT_CHAN_SHIFT 1 +#define MMA8452_CTRL_REG1 0x2a +#define MMA8452_CTRL_ACTIVE BIT(0) +#define MMA8452_CTRL_DR_MASK GENMASK(5, 3) +#define MMA8452_CTRL_DR_SHIFT 3 +#define MMA8452_CTRL_DR_DEFAULT 0x4 /* 50 Hz sample frequency */ +#define MMA8452_CTRL_REG2 0x2b +#define MMA8452_CTRL_REG2_RST BIT(6) +#define MMA8452_CTRL_REG2_MODS_SHIFT 3 +#define MMA8452_CTRL_REG2_MODS_MASK 0x1b +#define MMA8452_CTRL_REG4 0x2d +#define MMA8452_CTRL_REG5 0x2e +#define MMA8452_OFF_X 0x2f +#define MMA8452_OFF_Y 0x30 +#define MMA8452_OFF_Z 0x31 + +#define MMA8452_MAX_REG 0x31 + +#define MMA8452_INT_DRDY BIT(0) +#define MMA8452_INT_FF_MT BIT(2) +#define MMA8452_INT_TRANS BIT(5) + +#define MMA8451_DEVICE_ID 0x1a +#define MMA8452_DEVICE_ID 0x2a +#define MMA8453_DEVICE_ID 0x3a +#define MMA8652_DEVICE_ID 0x4a +#define MMA8653_DEVICE_ID 0x5a +#define FXLS8471_DEVICE_ID 0x6a + +#define MMA8452_AUTO_SUSPEND_DELAY_MS 2000 + +struct mma8452_data { + struct i2c_client *client; + struct mutex lock; + struct iio_mount_matrix orientation; + u8 ctrl_reg1; + u8 data_cfg; + const struct mma_chip_info *chip_info; + int sleep_val; + struct regulator *vdd_reg; + struct regulator *vddio_reg; + + /* Ensure correct alignment of time stamp when present */ + struct { + __be16 channels[3]; + aligned_s64 ts; + } buffer; +}; + + /** + * struct mma8452_event_regs - chip specific data related to events + * @ev_cfg: event config register address + * @ev_cfg_ele: latch bit in event config register + * @ev_cfg_chan_shift: number of the bit to enable events in X + * direction; in event config register + * @ev_src: event source register address + * @ev_ths: event threshold register address + * @ev_ths_mask: mask for the threshold value + * @ev_count: event count (period) register address + * + * Since not all chips supported by the driver support comparing high pass + * filtered data for events (interrupts), different interrupt sources are + * used for different chips and the relevant registers are included here. + */ +struct mma8452_event_regs { + u8 ev_cfg; + u8 ev_cfg_ele; + u8 ev_cfg_chan_shift; + u8 ev_src; + u8 ev_ths; + u8 ev_ths_mask; + u8 ev_count; +}; + +static const struct mma8452_event_regs ff_mt_ev_regs = { + .ev_cfg = MMA8452_FF_MT_CFG, + .ev_cfg_ele = MMA8452_FF_MT_CFG_ELE, + .ev_cfg_chan_shift = MMA8452_FF_MT_CHAN_SHIFT, + .ev_src = MMA8452_FF_MT_SRC, + .ev_ths = MMA8452_FF_MT_THS, + .ev_ths_mask = MMA8452_FF_MT_THS_MASK, + .ev_count = MMA8452_FF_MT_COUNT +}; + +static const struct mma8452_event_regs trans_ev_regs = { + .ev_cfg = MMA8452_TRANSIENT_CFG, + .ev_cfg_ele = MMA8452_TRANSIENT_CFG_ELE, + .ev_cfg_chan_shift = MMA8452_TRANSIENT_CHAN_SHIFT, + .ev_src = MMA8452_TRANSIENT_SRC, + .ev_ths = MMA8452_TRANSIENT_THS, + .ev_ths_mask = MMA8452_TRANSIENT_THS_MASK, + .ev_count = MMA8452_TRANSIENT_COUNT, +}; + +/** + * struct mma_chip_info - chip specific data + * @name: part number of device reported via 'name' attr + * @chip_id: WHO_AM_I register's value + * @channels: struct iio_chan_spec matching the device's + * capabilities + * @num_channels: number of channels + * @mma_scales: scale factors for converting register values + * to m/s^2; 3 modes: 2g, 4g, 8g; 2 integers + * per mode: m/s^2 and micro m/s^2 + * @all_events: all events supported by this chip + * @enabled_events: event flags enabled and handled by this driver + */ +struct mma_chip_info { + const char *name; + u8 chip_id; + const struct iio_chan_spec *channels; + int num_channels; + const int mma_scales[3][2]; + int all_events; + int enabled_events; +}; + +enum { + idx_x, + idx_y, + idx_z, + idx_ts, +}; + +static int mma8452_drdy(struct mma8452_data *data) +{ + int tries = 150; + + while (tries-- > 0) { + int ret = i2c_smbus_read_byte_data(data->client, + MMA8452_STATUS); + if (ret < 0) + return ret; + if ((ret & MMA8452_STATUS_DRDY) == MMA8452_STATUS_DRDY) + return 0; + + if (data->sleep_val <= 20) + usleep_range(data->sleep_val * 250, + data->sleep_val * 500); + else + msleep(20); + } + + dev_err(&data->client->dev, "data not ready\n"); + + return -EIO; +} + +static int mma8452_set_runtime_pm_state(struct i2c_client *client, bool on) +{ +#ifdef CONFIG_PM + int ret; + + if (on) + ret = pm_runtime_resume_and_get(&client->dev); + else + ret = pm_runtime_put_autosuspend(&client->dev); + if (ret < 0) { + dev_err(&client->dev, + "failed to change power state to %d\n", on); + + return ret; + } +#endif + + return 0; +} + +static int mma8452_read(struct mma8452_data *data, __be16 buf[3]) +{ + int ret = mma8452_drdy(data); + + if (ret < 0) + return ret; + + ret = mma8452_set_runtime_pm_state(data->client, true); + if (ret) + return ret; + + ret = i2c_smbus_read_i2c_block_data(data->client, MMA8452_OUT_X, + 3 * sizeof(__be16), (u8 *)buf); + if (ret < 0) + return ret; + + ret = mma8452_set_runtime_pm_state(data->client, false); + + return ret; +} + +static ssize_t mma8452_show_int_plus_micros(char *buf, const int (*vals)[2], + int n) +{ + size_t len = 0; + + while (n-- > 0) + len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06d ", + vals[n][0], vals[n][1]); + + /* replace trailing space by newline */ + buf[len - 1] = '\n'; + + return len; +} + +static int mma8452_get_int_plus_micros_index(const int (*vals)[2], int n, + int val, int val2) +{ + while (n-- > 0) + if (val == vals[n][0] && val2 == vals[n][1]) + return n; + + return -EINVAL; +} + +static unsigned int mma8452_get_odr_index(struct mma8452_data *data) +{ + return (data->ctrl_reg1 & MMA8452_CTRL_DR_MASK) >> + MMA8452_CTRL_DR_SHIFT; +} + +static const int mma8452_samp_freq[8][2] = { + {800, 0}, {400, 0}, {200, 0}, {100, 0}, {50, 0}, {12, 500000}, + {6, 250000}, {1, 560000} +}; + +/* Datasheet table: step time "Relationship with the ODR" (sample frequency) */ +static const unsigned int mma8452_time_step_us[4][8] = { + { 1250, 2500, 5000, 10000, 20000, 20000, 20000, 20000 }, /* normal */ + { 1250, 2500, 5000, 10000, 20000, 80000, 80000, 80000 }, /* l p l n */ + { 1250, 2500, 2500, 2500, 2500, 2500, 2500, 2500 }, /* high res*/ + { 1250, 2500, 5000, 10000, 20000, 80000, 160000, 160000 } /* l p */ +}; + +/* Datasheet table "High-Pass Filter Cutoff Options" */ +static const int mma8452_hp_filter_cutoff[4][8][4][2] = { + { /* normal */ + { {16, 0}, {8, 0}, {4, 0}, {2, 0} }, /* 800 Hz sample */ + { {16, 0}, {8, 0}, {4, 0}, {2, 0} }, /* 400 Hz sample */ + { {8, 0}, {4, 0}, {2, 0}, {1, 0} }, /* 200 Hz sample */ + { {4, 0}, {2, 0}, {1, 0}, {0, 500000} }, /* 100 Hz sample */ + { {2, 0}, {1, 0}, {0, 500000}, {0, 250000} }, /* 50 Hz sample */ + { {2, 0}, {1, 0}, {0, 500000}, {0, 250000} }, /* 12.5 Hz sample */ + { {2, 0}, {1, 0}, {0, 500000}, {0, 250000} }, /* 6.25 Hz sample */ + { {2, 0}, {1, 0}, {0, 500000}, {0, 250000} } /* 1.56 Hz sample */ + }, + { /* low noise low power */ + { {16, 0}, {8, 0}, {4, 0}, {2, 0} }, + { {16, 0}, {8, 0}, {4, 0}, {2, 0} }, + { {8, 0}, {4, 0}, {2, 0}, {1, 0} }, + { {4, 0}, {2, 0}, {1, 0}, {0, 500000} }, + { {2, 0}, {1, 0}, {0, 500000}, {0, 250000} }, + { {0, 500000}, {0, 250000}, {0, 125000}, {0, 063000} }, + { {0, 500000}, {0, 250000}, {0, 125000}, {0, 063000} }, + { {0, 500000}, {0, 250000}, {0, 125000}, {0, 063000} } + }, + { /* high resolution */ + { {16, 0}, {8, 0}, {4, 0}, {2, 0} }, + { {16, 0}, {8, 0}, {4, 0}, {2, 0} }, + { {16, 0}, {8, 0}, {4, 0}, {2, 0} }, + { {16, 0}, {8, 0}, {4, 0}, {2, 0} }, + { {16, 0}, {8, 0}, {4, 0}, {2, 0} }, + { {16, 0}, {8, 0}, {4, 0}, {2, 0} }, + { {16, 0}, {8, 0}, {4, 0}, {2, 0} }, + { {16, 0}, {8, 0}, {4, 0}, {2, 0} } + }, + { /* low power */ + { {16, 0}, {8, 0}, {4, 0}, {2, 0} }, + { {8, 0}, {4, 0}, {2, 0}, {1, 0} }, + { {4, 0}, {2, 0}, {1, 0}, {0, 500000} }, + { {2, 0}, {1, 0}, {0, 500000}, {0, 250000} }, + { {1, 0}, {0, 500000}, {0, 250000}, {0, 125000} }, + { {0, 250000}, {0, 125000}, {0, 063000}, {0, 031000} }, + { {0, 250000}, {0, 125000}, {0, 063000}, {0, 031000} }, + { {0, 250000}, {0, 125000}, {0, 063000}, {0, 031000} } + } +}; + +/* Datasheet table "MODS Oversampling modes averaging values at each ODR" */ +static const u16 mma8452_os_ratio[4][8] = { + /* 800 Hz, 400 Hz, ... , 1.56 Hz */ + { 2, 4, 4, 4, 4, 16, 32, 128 }, /* normal */ + { 2, 4, 4, 4, 4, 4, 8, 32 }, /* low power low noise */ + { 2, 4, 8, 16, 32, 128, 256, 1024 }, /* high resolution */ + { 2, 2, 2, 2, 2, 2, 4, 16 } /* low power */ +}; + +static int mma8452_get_power_mode(struct mma8452_data *data) +{ + int reg; + + reg = i2c_smbus_read_byte_data(data->client, + MMA8452_CTRL_REG2); + if (reg < 0) + return reg; + + return ((reg & MMA8452_CTRL_REG2_MODS_MASK) >> + MMA8452_CTRL_REG2_MODS_SHIFT); +} + +static ssize_t mma8452_show_samp_freq_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return mma8452_show_int_plus_micros(buf, mma8452_samp_freq, + ARRAY_SIZE(mma8452_samp_freq)); +} + +static ssize_t mma8452_show_scale_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct mma8452_data *data = iio_priv(indio_dev); + + return mma8452_show_int_plus_micros(buf, data->chip_info->mma_scales, + ARRAY_SIZE(data->chip_info->mma_scales)); +} + +static ssize_t mma8452_show_hp_cutoff_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct mma8452_data *data = iio_priv(indio_dev); + int i, j; + + i = mma8452_get_odr_index(data); + j = mma8452_get_power_mode(data); + if (j < 0) + return j; + + return mma8452_show_int_plus_micros(buf, mma8452_hp_filter_cutoff[j][i], + ARRAY_SIZE(mma8452_hp_filter_cutoff[0][0])); +} + +static ssize_t mma8452_show_os_ratio_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct mma8452_data *data = iio_priv(indio_dev); + int i = mma8452_get_odr_index(data); + int j; + u16 val = 0; + size_t len = 0; + + for (j = 0; j < ARRAY_SIZE(mma8452_os_ratio); j++) { + if (val == mma8452_os_ratio[j][i]) + continue; + + val = mma8452_os_ratio[j][i]; + + len += scnprintf(buf + len, PAGE_SIZE - len, "%d ", val); + } + buf[len - 1] = '\n'; + + return len; +} + +static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(mma8452_show_samp_freq_avail); +static IIO_DEVICE_ATTR(in_accel_scale_available, 0444, + mma8452_show_scale_avail, NULL, 0); +static IIO_DEVICE_ATTR(in_accel_filter_high_pass_3db_frequency_available, + 0444, mma8452_show_hp_cutoff_avail, NULL, 0); +static IIO_DEVICE_ATTR(in_accel_oversampling_ratio_available, 0444, + mma8452_show_os_ratio_avail, NULL, 0); + +static int mma8452_get_samp_freq_index(struct mma8452_data *data, + int val, int val2) +{ + return mma8452_get_int_plus_micros_index(mma8452_samp_freq, + ARRAY_SIZE(mma8452_samp_freq), + val, val2); +} + +static int mma8452_get_scale_index(struct mma8452_data *data, int val, int val2) +{ + return mma8452_get_int_plus_micros_index(data->chip_info->mma_scales, + ARRAY_SIZE(data->chip_info->mma_scales), val, val2); +} + +static int mma8452_get_hp_filter_index(struct mma8452_data *data, + int val, int val2) +{ + int i, j; + + i = mma8452_get_odr_index(data); + j = mma8452_get_power_mode(data); + if (j < 0) + return j; + + return mma8452_get_int_plus_micros_index(mma8452_hp_filter_cutoff[j][i], + ARRAY_SIZE(mma8452_hp_filter_cutoff[0][0]), val, val2); +} + +static int mma8452_read_hp_filter(struct mma8452_data *data, int *hz, int *uHz) +{ + int j, i, ret; + + ret = i2c_smbus_read_byte_data(data->client, MMA8452_HP_FILTER_CUTOFF); + if (ret < 0) + return ret; + + i = mma8452_get_odr_index(data); + j = mma8452_get_power_mode(data); + if (j < 0) + return j; + + ret &= MMA8452_HP_FILTER_CUTOFF_SEL_MASK; + *hz = mma8452_hp_filter_cutoff[j][i][ret][0]; + *uHz = mma8452_hp_filter_cutoff[j][i][ret][1]; + + return 0; +} + +static int mma8452_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mma8452_data *data = iio_priv(indio_dev); + __be16 buffer[3]; + int i, ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + mutex_lock(&data->lock); + ret = mma8452_read(data, buffer); + mutex_unlock(&data->lock); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + + *val = sign_extend32(be16_to_cpu( + buffer[chan->scan_index]) >> chan->scan_type.shift, + chan->scan_type.realbits - 1); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + i = data->data_cfg & MMA8452_DATA_CFG_FS_MASK; + *val = data->chip_info->mma_scales[i][0]; + *val2 = data->chip_info->mma_scales[i][1]; + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + i = mma8452_get_odr_index(data); + *val = mma8452_samp_freq[i][0]; + *val2 = mma8452_samp_freq[i][1]; + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBBIAS: + ret = i2c_smbus_read_byte_data(data->client, + MMA8452_OFF_X + + chan->scan_index); + if (ret < 0) + return ret; + + *val = sign_extend32(ret, 7); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: + if (data->data_cfg & MMA8452_DATA_CFG_HPF_MASK) { + ret = mma8452_read_hp_filter(data, val, val2); + if (ret < 0) + return ret; + } else { + *val = 0; + *val2 = 0; + } + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + ret = mma8452_get_power_mode(data); + if (ret < 0) + return ret; + + i = mma8452_get_odr_index(data); + + *val = mma8452_os_ratio[ret][i]; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int mma8452_calculate_sleep(struct mma8452_data *data) +{ + int ret, i = mma8452_get_odr_index(data); + + if (mma8452_samp_freq[i][0] > 0) + ret = 1000 / mma8452_samp_freq[i][0]; + else + ret = 1000; + + return ret == 0 ? 1 : ret; +} + +static int mma8452_standby(struct mma8452_data *data) +{ + return i2c_smbus_write_byte_data(data->client, MMA8452_CTRL_REG1, + data->ctrl_reg1 & ~MMA8452_CTRL_ACTIVE); +} + +static int mma8452_active(struct mma8452_data *data) +{ + return i2c_smbus_write_byte_data(data->client, MMA8452_CTRL_REG1, + data->ctrl_reg1); +} + +/* returns >0 if active, 0 if in standby and <0 on error */ +static int mma8452_is_active(struct mma8452_data *data) +{ + int reg; + + reg = i2c_smbus_read_byte_data(data->client, MMA8452_CTRL_REG1); + if (reg < 0) + return reg; + + return reg & MMA8452_CTRL_ACTIVE; +} + +static int mma8452_change_config(struct mma8452_data *data, u8 reg, u8 val) +{ + int ret; + int is_active; + + mutex_lock(&data->lock); + + is_active = mma8452_is_active(data); + if (is_active < 0) { + ret = is_active; + goto fail; + } + + /* config can only be changed when in standby */ + if (is_active > 0) { + ret = mma8452_standby(data); + if (ret < 0) + goto fail; + } + + ret = i2c_smbus_write_byte_data(data->client, reg, val); + if (ret < 0) + goto fail; + + if (is_active > 0) { + ret = mma8452_active(data); + if (ret < 0) + goto fail; + } + + ret = 0; +fail: + mutex_unlock(&data->lock); + + return ret; +} + +static int mma8452_set_power_mode(struct mma8452_data *data, u8 mode) +{ + int reg; + + reg = i2c_smbus_read_byte_data(data->client, + MMA8452_CTRL_REG2); + if (reg < 0) + return reg; + + reg &= ~MMA8452_CTRL_REG2_MODS_MASK; + reg |= mode << MMA8452_CTRL_REG2_MODS_SHIFT; + + return mma8452_change_config(data, MMA8452_CTRL_REG2, reg); +} + +/* returns >0 if in freefall mode, 0 if not or <0 if an error occurred */ +static int mma8452_freefall_mode_enabled(struct mma8452_data *data) +{ + int val; + + val = i2c_smbus_read_byte_data(data->client, MMA8452_FF_MT_CFG); + if (val < 0) + return val; + + return !(val & MMA8452_FF_MT_CFG_OAE); +} + +static int mma8452_set_freefall_mode(struct mma8452_data *data, bool state) +{ + int val; + + if ((state && mma8452_freefall_mode_enabled(data)) || + (!state && !(mma8452_freefall_mode_enabled(data)))) + return 0; + + val = i2c_smbus_read_byte_data(data->client, MMA8452_FF_MT_CFG); + if (val < 0) + return val; + + if (state) { + val |= BIT(idx_x + MMA8452_FF_MT_CHAN_SHIFT); + val |= BIT(idx_y + MMA8452_FF_MT_CHAN_SHIFT); + val |= BIT(idx_z + MMA8452_FF_MT_CHAN_SHIFT); + val &= ~MMA8452_FF_MT_CFG_OAE; + } else { + val &= ~BIT(idx_x + MMA8452_FF_MT_CHAN_SHIFT); + val &= ~BIT(idx_y + MMA8452_FF_MT_CHAN_SHIFT); + val &= ~BIT(idx_z + MMA8452_FF_MT_CHAN_SHIFT); + val |= MMA8452_FF_MT_CFG_OAE; + } + + return mma8452_change_config(data, MMA8452_FF_MT_CFG, val); +} + +static int mma8452_set_hp_filter_frequency(struct mma8452_data *data, + int val, int val2) +{ + int i, reg; + + i = mma8452_get_hp_filter_index(data, val, val2); + if (i < 0) + return i; + + reg = i2c_smbus_read_byte_data(data->client, + MMA8452_HP_FILTER_CUTOFF); + if (reg < 0) + return reg; + + reg &= ~MMA8452_HP_FILTER_CUTOFF_SEL_MASK; + reg |= i; + + return mma8452_change_config(data, MMA8452_HP_FILTER_CUTOFF, reg); +} + +static int __mma8452_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct mma8452_data *data = iio_priv(indio_dev); + int i, j, ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + i = mma8452_get_samp_freq_index(data, val, val2); + if (i < 0) + return i; + + data->ctrl_reg1 &= ~MMA8452_CTRL_DR_MASK; + data->ctrl_reg1 |= i << MMA8452_CTRL_DR_SHIFT; + + data->sleep_val = mma8452_calculate_sleep(data); + + return mma8452_change_config(data, MMA8452_CTRL_REG1, + data->ctrl_reg1); + + case IIO_CHAN_INFO_SCALE: + i = mma8452_get_scale_index(data, val, val2); + if (i < 0) + return i; + + data->data_cfg &= ~MMA8452_DATA_CFG_FS_MASK; + data->data_cfg |= i; + + return mma8452_change_config(data, MMA8452_DATA_CFG, + data->data_cfg); + + case IIO_CHAN_INFO_CALIBBIAS: + if (val < -128 || val > 127) + return -EINVAL; + + return mma8452_change_config(data, + MMA8452_OFF_X + chan->scan_index, + val); + + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: + if (val == 0 && val2 == 0) { + data->data_cfg &= ~MMA8452_DATA_CFG_HPF_MASK; + } else { + data->data_cfg |= MMA8452_DATA_CFG_HPF_MASK; + ret = mma8452_set_hp_filter_frequency(data, val, val2); + if (ret < 0) + return ret; + } + + return mma8452_change_config(data, MMA8452_DATA_CFG, + data->data_cfg); + + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + j = mma8452_get_odr_index(data); + + for (i = 0; i < ARRAY_SIZE(mma8452_os_ratio); i++) { + if (mma8452_os_ratio[i][j] == val) + return mma8452_set_power_mode(data, i); + } + + return -EINVAL; + + default: + return -EINVAL; + } +} + +static int mma8452_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = __mma8452_write_raw(indio_dev, chan, val, val2, mask); + iio_device_release_direct(indio_dev); + return ret; +} + +static int mma8452_get_event_regs(struct mma8452_data *data, + const struct iio_chan_spec *chan, + enum iio_event_direction dir, + const struct mma8452_event_regs **ev_reg) +{ + if (!chan) + return -EINVAL; + + switch (chan->type) { + case IIO_ACCEL: + switch (dir) { + case IIO_EV_DIR_RISING: + if ((data->chip_info->all_events + & MMA8452_INT_TRANS) && + (data->chip_info->enabled_events + & MMA8452_INT_TRANS)) + *ev_reg = &trans_ev_regs; + else + *ev_reg = &ff_mt_ev_regs; + return 0; + case IIO_EV_DIR_FALLING: + *ev_reg = &ff_mt_ev_regs; + return 0; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int mma8452_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct mma8452_data *data = iio_priv(indio_dev); + int ret, us, power_mode; + const struct mma8452_event_regs *ev_regs; + + ret = mma8452_get_event_regs(data, chan, dir, &ev_regs); + if (ret) + return ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + ret = i2c_smbus_read_byte_data(data->client, ev_regs->ev_ths); + if (ret < 0) + return ret; + + *val = ret & ev_regs->ev_ths_mask; + + return IIO_VAL_INT; + + case IIO_EV_INFO_PERIOD: + ret = i2c_smbus_read_byte_data(data->client, ev_regs->ev_count); + if (ret < 0) + return ret; + + power_mode = mma8452_get_power_mode(data); + if (power_mode < 0) + return power_mode; + + us = ret * mma8452_time_step_us[power_mode][ + mma8452_get_odr_index(data)]; + *val = us / USEC_PER_SEC; + *val2 = us % USEC_PER_SEC; + + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_EV_INFO_HIGH_PASS_FILTER_3DB: + ret = i2c_smbus_read_byte_data(data->client, + MMA8452_TRANSIENT_CFG); + if (ret < 0) + return ret; + + if (ret & MMA8452_TRANSIENT_CFG_HPF_BYP) { + *val = 0; + *val2 = 0; + } else { + ret = mma8452_read_hp_filter(data, val, val2); + if (ret < 0) + return ret; + } + + return IIO_VAL_INT_PLUS_MICRO; + + default: + return -EINVAL; + } +} + +static int mma8452_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct mma8452_data *data = iio_priv(indio_dev); + int ret, reg, steps; + const struct mma8452_event_regs *ev_regs; + + ret = mma8452_get_event_regs(data, chan, dir, &ev_regs); + if (ret) + return ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + if (val < 0 || val > ev_regs->ev_ths_mask) + return -EINVAL; + + return mma8452_change_config(data, ev_regs->ev_ths, val); + + case IIO_EV_INFO_PERIOD: + ret = mma8452_get_power_mode(data); + if (ret < 0) + return ret; + + steps = (val * USEC_PER_SEC + val2) / + mma8452_time_step_us[ret][ + mma8452_get_odr_index(data)]; + + if (steps < 0 || steps > 0xff) + return -EINVAL; + + return mma8452_change_config(data, ev_regs->ev_count, steps); + + case IIO_EV_INFO_HIGH_PASS_FILTER_3DB: + reg = i2c_smbus_read_byte_data(data->client, + MMA8452_TRANSIENT_CFG); + if (reg < 0) + return reg; + + if (val == 0 && val2 == 0) { + reg |= MMA8452_TRANSIENT_CFG_HPF_BYP; + } else { + reg &= ~MMA8452_TRANSIENT_CFG_HPF_BYP; + ret = mma8452_set_hp_filter_frequency(data, val, val2); + if (ret < 0) + return ret; + } + + return mma8452_change_config(data, MMA8452_TRANSIENT_CFG, reg); + + default: + return -EINVAL; + } +} + +static int mma8452_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct mma8452_data *data = iio_priv(indio_dev); + int ret; + const struct mma8452_event_regs *ev_regs; + + ret = mma8452_get_event_regs(data, chan, dir, &ev_regs); + if (ret) + return ret; + + switch (dir) { + case IIO_EV_DIR_FALLING: + return mma8452_freefall_mode_enabled(data); + case IIO_EV_DIR_RISING: + ret = i2c_smbus_read_byte_data(data->client, ev_regs->ev_cfg); + if (ret < 0) + return ret; + + return !!(ret & BIT(chan->scan_index + + ev_regs->ev_cfg_chan_shift)); + default: + return -EINVAL; + } +} + +static int mma8452_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct mma8452_data *data = iio_priv(indio_dev); + int val, ret; + const struct mma8452_event_regs *ev_regs; + + ret = mma8452_get_event_regs(data, chan, dir, &ev_regs); + if (ret) + return ret; + + ret = mma8452_set_runtime_pm_state(data->client, state); + if (ret) + return ret; + + switch (dir) { + case IIO_EV_DIR_FALLING: + return mma8452_set_freefall_mode(data, state); + case IIO_EV_DIR_RISING: + val = i2c_smbus_read_byte_data(data->client, ev_regs->ev_cfg); + if (val < 0) + return val; + + if (state) { + if (mma8452_freefall_mode_enabled(data)) { + val &= ~BIT(idx_x + ev_regs->ev_cfg_chan_shift); + val &= ~BIT(idx_y + ev_regs->ev_cfg_chan_shift); + val &= ~BIT(idx_z + ev_regs->ev_cfg_chan_shift); + val |= MMA8452_FF_MT_CFG_OAE; + } + val |= BIT(chan->scan_index + + ev_regs->ev_cfg_chan_shift); + } else { + if (mma8452_freefall_mode_enabled(data)) + return 0; + + val &= ~BIT(chan->scan_index + + ev_regs->ev_cfg_chan_shift); + } + + val |= ev_regs->ev_cfg_ele; + + return mma8452_change_config(data, ev_regs->ev_cfg, val); + default: + return -EINVAL; + } +} + +static void mma8452_transient_interrupt(struct iio_dev *indio_dev) +{ + struct mma8452_data *data = iio_priv(indio_dev); + s64 ts = iio_get_time_ns(indio_dev); + int src; + + src = i2c_smbus_read_byte_data(data->client, MMA8452_TRANSIENT_SRC); + if (src < 0) + return; + + if (src & MMA8452_TRANSIENT_SRC_XTRANSE) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_RISING), + ts); + + if (src & MMA8452_TRANSIENT_SRC_YTRANSE) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_Y, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_RISING), + ts); + + if (src & MMA8452_TRANSIENT_SRC_ZTRANSE) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_Z, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_RISING), + ts); +} + +static irqreturn_t mma8452_interrupt(int irq, void *p) +{ + struct iio_dev *indio_dev = p; + struct mma8452_data *data = iio_priv(indio_dev); + irqreturn_t ret = IRQ_NONE; + int src; + + src = i2c_smbus_read_byte_data(data->client, MMA8452_INT_SRC); + if (src < 0) + return IRQ_NONE; + + if (!(src & (data->chip_info->enabled_events | MMA8452_INT_DRDY))) + return IRQ_NONE; + + if (src & MMA8452_INT_DRDY) { + iio_trigger_poll_nested(indio_dev->trig); + ret = IRQ_HANDLED; + } + + if (src & MMA8452_INT_FF_MT) { + if (mma8452_freefall_mode_enabled(data)) { + s64 ts = iio_get_time_ns(indio_dev); + + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_AND_Y_AND_Z, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_FALLING), + ts); + } + ret = IRQ_HANDLED; + } + + if (src & MMA8452_INT_TRANS) { + mma8452_transient_interrupt(indio_dev); + ret = IRQ_HANDLED; + } + + return ret; +} + +static irqreturn_t mma8452_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct mma8452_data *data = iio_priv(indio_dev); + int ret; + + ret = mma8452_read(data, data->buffer.channels); + if (ret < 0) + goto done; + + iio_push_to_buffers_with_ts(indio_dev, &data->buffer, + sizeof(data->buffer), + iio_get_time_ns(indio_dev)); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int mma8452_reg_access_dbg(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + int ret; + struct mma8452_data *data = iio_priv(indio_dev); + + if (reg > MMA8452_MAX_REG) + return -EINVAL; + + if (!readval) + return mma8452_change_config(data, reg, writeval); + + ret = i2c_smbus_read_byte_data(data->client, reg); + if (ret < 0) + return ret; + + *readval = ret; + + return 0; +} + +static const struct iio_event_spec mma8452_freefall_event[] = { + { + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD) | + BIT(IIO_EV_INFO_HIGH_PASS_FILTER_3DB) + }, +}; + +static const struct iio_event_spec mma8652_freefall_event[] = { + { + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD) + }, +}; + +static const struct iio_event_spec mma8452_transient_event[] = { + { + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD) | + BIT(IIO_EV_INFO_HIGH_PASS_FILTER_3DB) + }, +}; + +static const struct iio_event_spec mma8452_motion_event[] = { + { + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD) + }, +}; + +/* + * Threshold is configured in fixed 8G/127 steps regardless of + * currently selected scale for measurement. + */ +static IIO_CONST_ATTR_NAMED(accel_transient_scale, in_accel_scale, "0.617742"); + +static struct attribute *mma8452_event_attributes[] = { + &iio_const_attr_accel_transient_scale.dev_attr.attr, + NULL, +}; + +static const struct attribute_group mma8452_event_attribute_group = { + .attrs = mma8452_event_attributes, +}; + +static const struct iio_mount_matrix * +mma8452_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct mma8452_data *data = iio_priv(indio_dev); + + return &data->orientation; +} + +static const struct iio_chan_spec_ext_info mma8452_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_TYPE, mma8452_get_mount_matrix), + { } +}; + +#define MMA8452_FREEFALL_CHANNEL(modifier) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = modifier, \ + .scan_index = -1, \ + .event_spec = mma8452_freefall_event, \ + .num_event_specs = ARRAY_SIZE(mma8452_freefall_event), \ +} + +#define MMA8652_FREEFALL_CHANNEL(modifier) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = modifier, \ + .scan_index = -1, \ + .event_spec = mma8652_freefall_event, \ + .num_event_specs = ARRAY_SIZE(mma8652_freefall_event), \ +} + +#define MMA8452_CHANNEL(axis, idx, bits) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .scan_index = idx, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 16 - (bits), \ + .endianness = IIO_BE, \ + }, \ + .event_spec = mma8452_transient_event, \ + .num_event_specs = ARRAY_SIZE(mma8452_transient_event), \ + .ext_info = mma8452_ext_info, \ +} + +#define MMA8652_CHANNEL(axis, idx, bits) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .scan_index = idx, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 16 - (bits), \ + .endianness = IIO_BE, \ + }, \ + .event_spec = mma8452_motion_event, \ + .num_event_specs = ARRAY_SIZE(mma8452_motion_event), \ + .ext_info = mma8452_ext_info, \ +} + +static const struct iio_chan_spec mma8451_channels[] = { + MMA8452_CHANNEL(X, idx_x, 14), + MMA8452_CHANNEL(Y, idx_y, 14), + MMA8452_CHANNEL(Z, idx_z, 14), + IIO_CHAN_SOFT_TIMESTAMP(idx_ts), + MMA8452_FREEFALL_CHANNEL(IIO_MOD_X_AND_Y_AND_Z), +}; + +static const struct iio_chan_spec mma8452_channels[] = { + MMA8452_CHANNEL(X, idx_x, 12), + MMA8452_CHANNEL(Y, idx_y, 12), + MMA8452_CHANNEL(Z, idx_z, 12), + IIO_CHAN_SOFT_TIMESTAMP(idx_ts), + MMA8452_FREEFALL_CHANNEL(IIO_MOD_X_AND_Y_AND_Z), +}; + +static const struct iio_chan_spec mma8453_channels[] = { + MMA8452_CHANNEL(X, idx_x, 10), + MMA8452_CHANNEL(Y, idx_y, 10), + MMA8452_CHANNEL(Z, idx_z, 10), + IIO_CHAN_SOFT_TIMESTAMP(idx_ts), + MMA8452_FREEFALL_CHANNEL(IIO_MOD_X_AND_Y_AND_Z), +}; + +static const struct iio_chan_spec mma8652_channels[] = { + MMA8652_CHANNEL(X, idx_x, 12), + MMA8652_CHANNEL(Y, idx_y, 12), + MMA8652_CHANNEL(Z, idx_z, 12), + IIO_CHAN_SOFT_TIMESTAMP(idx_ts), + MMA8652_FREEFALL_CHANNEL(IIO_MOD_X_AND_Y_AND_Z), +}; + +static const struct iio_chan_spec mma8653_channels[] = { + MMA8652_CHANNEL(X, idx_x, 10), + MMA8652_CHANNEL(Y, idx_y, 10), + MMA8652_CHANNEL(Z, idx_z, 10), + IIO_CHAN_SOFT_TIMESTAMP(idx_ts), + MMA8652_FREEFALL_CHANNEL(IIO_MOD_X_AND_Y_AND_Z), +}; + +enum { + mma8451, + mma8452, + mma8453, + mma8652, + mma8653, + fxls8471, +}; + +static const struct mma_chip_info mma_chip_info_table[] = { + [mma8451] = { + .name = "mma8451", + .chip_id = MMA8451_DEVICE_ID, + .channels = mma8451_channels, + .num_channels = ARRAY_SIZE(mma8451_channels), + /* + * Hardware has fullscale of -2G, -4G, -8G corresponding to + * raw value -8192 for 14 bit, -2048 for 12 bit or -512 for 10 + * bit. + * The userspace interface uses m/s^2 and we declare micro units + * So scale factor for 12 bit here is given by: + * g * N * 1000000 / 2048 for N = 2, 4, 8 and g=9.80665 + */ + .mma_scales = { {0, 2394}, {0, 4788}, {0, 9577} }, + /* + * Although we enable the interrupt sources once and for + * all here the event detection itself is not enabled until + * userspace asks for it by mma8452_write_event_config() + */ + .all_events = MMA8452_INT_DRDY | + MMA8452_INT_TRANS | + MMA8452_INT_FF_MT, + .enabled_events = MMA8452_INT_TRANS | + MMA8452_INT_FF_MT, + }, + [mma8452] = { + .name = "mma8452", + .chip_id = MMA8452_DEVICE_ID, + .channels = mma8452_channels, + .num_channels = ARRAY_SIZE(mma8452_channels), + .mma_scales = { {0, 9577}, {0, 19154}, {0, 38307} }, + /* + * Although we enable the interrupt sources once and for + * all here the event detection itself is not enabled until + * userspace asks for it by mma8452_write_event_config() + */ + .all_events = MMA8452_INT_DRDY | + MMA8452_INT_TRANS | + MMA8452_INT_FF_MT, + .enabled_events = MMA8452_INT_TRANS | + MMA8452_INT_FF_MT, + }, + [mma8453] = { + .name = "mma8453", + .chip_id = MMA8453_DEVICE_ID, + .channels = mma8453_channels, + .num_channels = ARRAY_SIZE(mma8453_channels), + .mma_scales = { {0, 38307}, {0, 76614}, {0, 153228} }, + /* + * Although we enable the interrupt sources once and for + * all here the event detection itself is not enabled until + * userspace asks for it by mma8452_write_event_config() + */ + .all_events = MMA8452_INT_DRDY | + MMA8452_INT_TRANS | + MMA8452_INT_FF_MT, + .enabled_events = MMA8452_INT_TRANS | + MMA8452_INT_FF_MT, + }, + [mma8652] = { + .name = "mma8652", + .chip_id = MMA8652_DEVICE_ID, + .channels = mma8652_channels, + .num_channels = ARRAY_SIZE(mma8652_channels), + .mma_scales = { {0, 9577}, {0, 19154}, {0, 38307} }, + .all_events = MMA8452_INT_DRDY | + MMA8452_INT_FF_MT, + .enabled_events = MMA8452_INT_FF_MT, + }, + [mma8653] = { + .name = "mma8653", + .chip_id = MMA8653_DEVICE_ID, + .channels = mma8653_channels, + .num_channels = ARRAY_SIZE(mma8653_channels), + .mma_scales = { {0, 38307}, {0, 76614}, {0, 153228} }, + /* + * Although we enable the interrupt sources once and for + * all here the event detection itself is not enabled until + * userspace asks for it by mma8452_write_event_config() + */ + .all_events = MMA8452_INT_DRDY | + MMA8452_INT_FF_MT, + .enabled_events = MMA8452_INT_FF_MT, + }, + [fxls8471] = { + .name = "fxls8471", + .chip_id = FXLS8471_DEVICE_ID, + .channels = mma8451_channels, + .num_channels = ARRAY_SIZE(mma8451_channels), + .mma_scales = { {0, 2394}, {0, 4788}, {0, 9577} }, + /* + * Although we enable the interrupt sources once and for + * all here the event detection itself is not enabled until + * userspace asks for it by mma8452_write_event_config() + */ + .all_events = MMA8452_INT_DRDY | + MMA8452_INT_TRANS | + MMA8452_INT_FF_MT, + .enabled_events = MMA8452_INT_TRANS | + MMA8452_INT_FF_MT, + }, +}; + +static struct attribute *mma8452_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_accel_scale_available.dev_attr.attr, + &iio_dev_attr_in_accel_filter_high_pass_3db_frequency_available.dev_attr.attr, + &iio_dev_attr_in_accel_oversampling_ratio_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group mma8452_group = { + .attrs = mma8452_attributes, +}; + +static const struct iio_info mma8452_info = { + .attrs = &mma8452_group, + .read_raw = &mma8452_read_raw, + .write_raw = &mma8452_write_raw, + .event_attrs = &mma8452_event_attribute_group, + .read_event_value = &mma8452_read_event_value, + .write_event_value = &mma8452_write_event_value, + .read_event_config = &mma8452_read_event_config, + .write_event_config = &mma8452_write_event_config, + .debugfs_reg_access = &mma8452_reg_access_dbg, +}; + +static const unsigned long mma8452_scan_masks[] = {0x7, 0}; + +static int mma8452_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct mma8452_data *data = iio_priv(indio_dev); + int reg, ret; + + ret = mma8452_set_runtime_pm_state(data->client, state); + if (ret) + return ret; + + reg = i2c_smbus_read_byte_data(data->client, MMA8452_CTRL_REG4); + if (reg < 0) + return reg; + + if (state) + reg |= MMA8452_INT_DRDY; + else + reg &= ~MMA8452_INT_DRDY; + + return mma8452_change_config(data, MMA8452_CTRL_REG4, reg); +} + +static const struct iio_trigger_ops mma8452_trigger_ops = { + .set_trigger_state = mma8452_data_rdy_trigger_set_state, + .validate_device = iio_trigger_validate_own_device, +}; + +static int mma8452_trigger_setup(struct iio_dev *indio_dev) +{ + struct mma8452_data *data = iio_priv(indio_dev); + struct iio_trigger *trig; + int ret; + + trig = devm_iio_trigger_alloc(&data->client->dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!trig) + return -ENOMEM; + + trig->ops = &mma8452_trigger_ops; + iio_trigger_set_drvdata(trig, indio_dev); + + ret = iio_trigger_register(trig); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(trig); + + return 0; +} + +static void mma8452_trigger_cleanup(struct iio_dev *indio_dev) +{ + if (indio_dev->trig) + iio_trigger_unregister(indio_dev->trig); +} + +static int mma8452_reset(struct i2c_client *client) +{ + int i; + int ret; + + /* + * Find on fxls8471, after config reset bit, it reset immediately, + * and will not give ACK, so here do not check the return value. + * The following code will read the reset register, and check whether + * this reset works. + */ + i2c_smbus_write_byte_data(client, + MMA8452_CTRL_REG2, + MMA8452_CTRL_REG2_RST); + + for (i = 0; i < 10; i++) { + usleep_range(100, 200); + ret = i2c_smbus_read_byte_data(client, MMA8452_CTRL_REG2); + if (ret == -EIO) + continue; /* I2C comm reset */ + if (ret < 0) + return ret; + if (!(ret & MMA8452_CTRL_REG2_RST)) + return 0; + } + + return -ETIMEDOUT; +} + +static const struct of_device_id mma8452_dt_ids[] = { + { .compatible = "fsl,fxls8471", .data = &mma_chip_info_table[fxls8471] }, + { .compatible = "fsl,mma8451", .data = &mma_chip_info_table[mma8451] }, + { .compatible = "fsl,mma8452", .data = &mma_chip_info_table[mma8452] }, + { .compatible = "fsl,mma8453", .data = &mma_chip_info_table[mma8453] }, + { .compatible = "fsl,mma8652", .data = &mma_chip_info_table[mma8652] }, + { .compatible = "fsl,mma8653", .data = &mma_chip_info_table[mma8653] }, + { } +}; +MODULE_DEVICE_TABLE(of, mma8452_dt_ids); + +static int mma8452_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct mma8452_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + mutex_init(&data->lock); + + data->chip_info = i2c_get_match_data(client); + if (!data->chip_info) + return dev_err_probe(&client->dev, -ENODEV, + "unknown device model\n"); + + ret = iio_read_mount_matrix(&client->dev, &data->orientation); + if (ret) + return ret; + + data->vdd_reg = devm_regulator_get(&client->dev, "vdd"); + if (IS_ERR(data->vdd_reg)) + return dev_err_probe(&client->dev, PTR_ERR(data->vdd_reg), + "failed to get VDD regulator!\n"); + + data->vddio_reg = devm_regulator_get(&client->dev, "vddio"); + if (IS_ERR(data->vddio_reg)) + return dev_err_probe(&client->dev, PTR_ERR(data->vddio_reg), + "failed to get VDDIO regulator!\n"); + + ret = regulator_enable(data->vdd_reg); + if (ret) + return dev_err_probe(dev, ret, "failed to enable VDD regulator!\n"); + + ret = regulator_enable(data->vddio_reg); + if (ret) { + dev_err_probe(dev, ret, "failed to enable VDDIO regulator!\n"); + goto disable_regulator_vdd; + } + + ret = i2c_smbus_read_byte_data(client, MMA8452_WHO_AM_I); + if (ret < 0) + goto disable_regulators; + + switch (ret) { + case MMA8451_DEVICE_ID: + case MMA8452_DEVICE_ID: + case MMA8453_DEVICE_ID: + case MMA8652_DEVICE_ID: + case MMA8653_DEVICE_ID: + case FXLS8471_DEVICE_ID: + if (ret == data->chip_info->chip_id) + break; + fallthrough; + default: + ret = -ENODEV; + goto disable_regulators; + } + + dev_info(&client->dev, "registering %s accelerometer; ID 0x%x\n", + data->chip_info->name, data->chip_info->chip_id); + + i2c_set_clientdata(client, indio_dev); + indio_dev->info = &mma8452_info; + indio_dev->name = data->chip_info->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = data->chip_info->channels; + indio_dev->num_channels = data->chip_info->num_channels; + indio_dev->available_scan_masks = mma8452_scan_masks; + + ret = mma8452_reset(client); + if (ret < 0) + goto disable_regulators; + + data->data_cfg = MMA8452_DATA_CFG_FS_2G; + ret = i2c_smbus_write_byte_data(client, MMA8452_DATA_CFG, + data->data_cfg); + if (ret < 0) + goto disable_regulators; + + /* + * By default set transient threshold to max to avoid events if + * enabling without configuring threshold. + */ + ret = i2c_smbus_write_byte_data(client, MMA8452_TRANSIENT_THS, + MMA8452_TRANSIENT_THS_MASK); + if (ret < 0) + goto disable_regulators; + + if (client->irq) { + int irq2; + + irq2 = fwnode_irq_get_byname(dev_fwnode(&client->dev), "INT2"); + + if (irq2 == client->irq) { + dev_dbg(&client->dev, "using interrupt line INT2\n"); + } else { + ret = i2c_smbus_write_byte_data(client, + MMA8452_CTRL_REG5, + data->chip_info->all_events); + if (ret < 0) + goto disable_regulators; + + dev_dbg(&client->dev, "using interrupt line INT1\n"); + } + + ret = i2c_smbus_write_byte_data(client, + MMA8452_CTRL_REG4, + data->chip_info->enabled_events); + if (ret < 0) + goto disable_regulators; + + ret = mma8452_trigger_setup(indio_dev); + if (ret < 0) + goto disable_regulators; + } + + data->ctrl_reg1 = MMA8452_CTRL_ACTIVE | + (MMA8452_CTRL_DR_DEFAULT << MMA8452_CTRL_DR_SHIFT); + + data->sleep_val = mma8452_calculate_sleep(data); + + ret = i2c_smbus_write_byte_data(client, MMA8452_CTRL_REG1, + data->ctrl_reg1); + if (ret < 0) + goto trigger_cleanup; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + mma8452_trigger_handler, NULL); + if (ret < 0) + goto trigger_cleanup; + + if (client->irq) { + ret = request_threaded_irq(client->irq, NULL, mma8452_interrupt, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + client->name, indio_dev); + if (ret) + goto buffer_cleanup; + } + + ret = pm_runtime_set_active(&client->dev); + if (ret < 0) + goto free_irq; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, + MMA8452_AUTO_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto free_irq; + + ret = mma8452_set_freefall_mode(data, false); + if (ret < 0) + goto unregister_device; + + return 0; + +unregister_device: + iio_device_unregister(indio_dev); + +free_irq: + if (client->irq) + free_irq(client->irq, indio_dev); + +buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); + +trigger_cleanup: + mma8452_trigger_cleanup(indio_dev); + +disable_regulators: + regulator_disable(data->vddio_reg); + +disable_regulator_vdd: + regulator_disable(data->vdd_reg); + + return ret; +} + +static void mma8452_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct mma8452_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + if (client->irq) + free_irq(client->irq, indio_dev); + + iio_triggered_buffer_cleanup(indio_dev); + mma8452_trigger_cleanup(indio_dev); + mma8452_standby(iio_priv(indio_dev)); + + regulator_disable(data->vddio_reg); + regulator_disable(data->vdd_reg); +} + +#ifdef CONFIG_PM +static int mma8452_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma8452_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->lock); + ret = mma8452_standby(data); + mutex_unlock(&data->lock); + if (ret < 0) { + dev_err(&data->client->dev, "powering off device failed\n"); + return -EAGAIN; + } + + ret = regulator_disable(data->vddio_reg); + if (ret) { + dev_err(dev, "failed to disable VDDIO regulator\n"); + return ret; + } + + ret = regulator_disable(data->vdd_reg); + if (ret) { + dev_err(dev, "failed to disable VDD regulator\n"); + return ret; + } + + return 0; +} + +static int mma8452_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma8452_data *data = iio_priv(indio_dev); + int ret, sleep_val; + + ret = regulator_enable(data->vdd_reg); + if (ret) { + dev_err(dev, "failed to enable VDD regulator\n"); + return ret; + } + + ret = regulator_enable(data->vddio_reg); + if (ret) { + dev_err(dev, "failed to enable VDDIO regulator\n"); + regulator_disable(data->vdd_reg); + return ret; + } + + ret = mma8452_active(data); + if (ret < 0) + goto runtime_resume_failed; + + ret = mma8452_get_odr_index(data); + sleep_val = 1000 / mma8452_samp_freq[ret][0]; + if (sleep_val < 20) + usleep_range(sleep_val * 1000, 20000); + else + msleep_interruptible(sleep_val); + + return 0; + +runtime_resume_failed: + regulator_disable(data->vddio_reg); + regulator_disable(data->vdd_reg); + + return ret; +} +#endif + +static const struct dev_pm_ops mma8452_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(mma8452_runtime_suspend, + mma8452_runtime_resume, NULL) +}; + +static const struct i2c_device_id mma8452_id[] = { + { .name = "fxls8471", .driver_data = (kernel_ulong_t)&mma_chip_info_table[fxls8471] }, + { .name = "mma8451", .driver_data = (kernel_ulong_t)&mma_chip_info_table[mma8451] }, + { .name = "mma8452", .driver_data = (kernel_ulong_t)&mma_chip_info_table[mma8452] }, + { .name = "mma8453", .driver_data = (kernel_ulong_t)&mma_chip_info_table[mma8453] }, + { .name = "mma8652", .driver_data = (kernel_ulong_t)&mma_chip_info_table[mma8652] }, + { .name = "mma8653", .driver_data = (kernel_ulong_t)&mma_chip_info_table[mma8653] }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mma8452_id); + +static struct i2c_driver mma8452_driver = { + .driver = { + .name = "mma8452", + .of_match_table = mma8452_dt_ids, + .pm = &mma8452_pm_ops, + }, + .probe = mma8452_probe, + .remove = mma8452_remove, + .id_table = mma8452_id, +}; +module_i2c_driver(mma8452_driver); + +MODULE_AUTHOR("Peter Meerwald "); +MODULE_DESCRIPTION("Freescale / NXP MMA8452 accelerometer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/accel/mma9551.c b/drivers/iio/accel/mma9551.c new file mode 100644 index 000000000..7e01427fd --- /dev/null +++ b/drivers/iio/accel/mma9551.c @@ -0,0 +1,605 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Freescale MMA9551L Intelligent Motion-Sensing Platform driver + * Copyright (c) 2014, Intel Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mma9551_core.h" + +#define MMA9551_GPIO_COUNT 4 + +/* Tilt application (inclination in IIO terms). */ +#define MMA9551_TILT_XZ_ANG_REG 0x00 +#define MMA9551_TILT_YZ_ANG_REG 0x01 +#define MMA9551_TILT_XY_ANG_REG 0x02 +#define MMA9551_TILT_ANGFLG BIT(7) +#define MMA9551_TILT_QUAD_REG 0x03 +#define MMA9551_TILT_XY_QUAD_SHIFT 0 +#define MMA9551_TILT_YZ_QUAD_SHIFT 2 +#define MMA9551_TILT_XZ_QUAD_SHIFT 4 +#define MMA9551_TILT_CFG_REG 0x01 +#define MMA9551_TILT_ANG_THRESH_MASK GENMASK(3, 0) + +#define MMA9551_DEFAULT_SAMPLE_RATE 122 /* Hz */ + +/* Tilt events are mapped to the first three GPIO pins. */ +enum mma9551_tilt_axis { + mma9551_x = 0, + mma9551_y, + mma9551_z, +}; + +struct mma9551_data { + struct i2c_client *client; + struct mutex mutex; + bool event_enabled[3]; + int irqs[MMA9551_GPIO_COUNT]; +}; + +static int mma9551_read_incli_chan(struct i2c_client *client, + const struct iio_chan_spec *chan, + int *val) +{ + u8 quad_shift, angle, quadrant; + u16 reg_addr; + int ret; + + switch (chan->channel2) { + case IIO_MOD_X: + reg_addr = MMA9551_TILT_YZ_ANG_REG; + quad_shift = MMA9551_TILT_YZ_QUAD_SHIFT; + break; + case IIO_MOD_Y: + reg_addr = MMA9551_TILT_XZ_ANG_REG; + quad_shift = MMA9551_TILT_XZ_QUAD_SHIFT; + break; + case IIO_MOD_Z: + reg_addr = MMA9551_TILT_XY_ANG_REG; + quad_shift = MMA9551_TILT_XY_QUAD_SHIFT; + break; + default: + return -EINVAL; + } + + ret = mma9551_set_power_state(client, true); + if (ret < 0) + return ret; + + ret = mma9551_read_status_byte(client, MMA9551_APPID_TILT, + reg_addr, &angle); + if (ret < 0) + goto out_poweroff; + + ret = mma9551_read_status_byte(client, MMA9551_APPID_TILT, + MMA9551_TILT_QUAD_REG, &quadrant); + if (ret < 0) + goto out_poweroff; + + angle &= ~MMA9551_TILT_ANGFLG; + quadrant = (quadrant >> quad_shift) & 0x03; + + if (quadrant == 1 || quadrant == 3) + *val = 90 * (quadrant + 1) - angle; + else + *val = angle + 90 * quadrant; + + ret = IIO_VAL_INT; + +out_poweroff: + mma9551_set_power_state(client, false); + return ret; +} + +static int mma9551_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mma9551_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_INCLI: + mutex_lock(&data->mutex); + ret = mma9551_read_incli_chan(data->client, chan, val); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_ACCEL: + mutex_lock(&data->mutex); + ret = mma9551_read_accel_chan(data->client, + chan, val, val2); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ACCEL: + return mma9551_read_accel_scale(val, val2); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int mma9551_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct mma9551_data *data = iio_priv(indio_dev); + + switch (chan->type) { + case IIO_INCLI: + /* IIO counts axes from 1, because IIO_NO_MOD is 0. */ + return data->event_enabled[chan->channel2 - 1]; + default: + return -EINVAL; + } +} + +static int mma9551_config_incli_event(struct iio_dev *indio_dev, + enum iio_modifier axis, + bool state) +{ + struct mma9551_data *data = iio_priv(indio_dev); + enum mma9551_tilt_axis mma_axis; + int ret; + + /* IIO counts axes from 1, because IIO_NO_MOD is 0. */ + mma_axis = axis - 1; + + if (data->event_enabled[mma_axis] == state) + return 0; + + if (!state) { + ret = mma9551_gpio_config(data->client, + (enum mma9551_gpio_pin)mma_axis, + MMA9551_APPID_NONE, 0, 0); + if (ret < 0) + return ret; + + ret = mma9551_set_power_state(data->client, false); + if (ret < 0) + return ret; + } else { + int bitnum; + + /* Bit 7 of each angle register holds the angle flag. */ + switch (axis) { + case IIO_MOD_X: + bitnum = 7 + 8 * MMA9551_TILT_YZ_ANG_REG; + break; + case IIO_MOD_Y: + bitnum = 7 + 8 * MMA9551_TILT_XZ_ANG_REG; + break; + case IIO_MOD_Z: + bitnum = 7 + 8 * MMA9551_TILT_XY_ANG_REG; + break; + default: + return -EINVAL; + } + + + ret = mma9551_set_power_state(data->client, true); + if (ret < 0) + return ret; + + ret = mma9551_gpio_config(data->client, + (enum mma9551_gpio_pin)mma_axis, + MMA9551_APPID_TILT, bitnum, 0); + if (ret < 0) { + mma9551_set_power_state(data->client, false); + return ret; + } + } + + data->event_enabled[mma_axis] = state; + + return ret; +} + +static int mma9551_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct mma9551_data *data = iio_priv(indio_dev); + int ret; + + switch (chan->type) { + case IIO_INCLI: + mutex_lock(&data->mutex); + ret = mma9551_config_incli_event(indio_dev, + chan->channel2, state); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } +} + +static int mma9551_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct mma9551_data *data = iio_priv(indio_dev); + int ret; + + switch (chan->type) { + case IIO_INCLI: + if (val2 != 0 || val < 1 || val > 10) + return -EINVAL; + mutex_lock(&data->mutex); + ret = mma9551_update_config_bits(data->client, + MMA9551_APPID_TILT, + MMA9551_TILT_CFG_REG, + MMA9551_TILT_ANG_THRESH_MASK, + val); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } +} + +static int mma9551_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct mma9551_data *data = iio_priv(indio_dev); + int ret; + u8 tmp; + + switch (chan->type) { + case IIO_INCLI: + mutex_lock(&data->mutex); + ret = mma9551_read_config_byte(data->client, + MMA9551_APPID_TILT, + MMA9551_TILT_CFG_REG, &tmp); + mutex_unlock(&data->mutex); + if (ret < 0) + return ret; + *val = tmp & MMA9551_TILT_ANG_THRESH_MASK; + *val2 = 0; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static const struct iio_event_spec mma9551_incli_event = { + .type = IIO_EV_TYPE_ROC, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE), +}; + +#define MMA9551_INCLI_CHANNEL(axis) { \ + .type = IIO_INCLI, \ + .modified = 1, \ + .channel2 = axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ + .event_spec = &mma9551_incli_event, \ + .num_event_specs = 1, \ +} + +static const struct iio_chan_spec mma9551_channels[] = { + MMA9551_ACCEL_CHANNEL(IIO_MOD_X), + MMA9551_ACCEL_CHANNEL(IIO_MOD_Y), + MMA9551_ACCEL_CHANNEL(IIO_MOD_Z), + + MMA9551_INCLI_CHANNEL(IIO_MOD_X), + MMA9551_INCLI_CHANNEL(IIO_MOD_Y), + MMA9551_INCLI_CHANNEL(IIO_MOD_Z), +}; + +static const struct iio_info mma9551_info = { + .read_raw = mma9551_read_raw, + .read_event_config = mma9551_read_event_config, + .write_event_config = mma9551_write_event_config, + .read_event_value = mma9551_read_event_value, + .write_event_value = mma9551_write_event_value, +}; + +static irqreturn_t mma9551_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct mma9551_data *data = iio_priv(indio_dev); + int i, ret, mma_axis = -1; + u16 reg; + u8 val; + + mutex_lock(&data->mutex); + + for (i = 0; i < 3; i++) + if (irq == data->irqs[i]) { + mma_axis = i; + break; + } + + if (mma_axis == -1) { + /* IRQ was triggered on 4th line, which we don't use. */ + dev_warn(&data->client->dev, + "irq triggered on unused line %d\n", data->irqs[3]); + goto out; + } + + switch (mma_axis) { + case mma9551_x: + reg = MMA9551_TILT_YZ_ANG_REG; + break; + case mma9551_y: + reg = MMA9551_TILT_XZ_ANG_REG; + break; + case mma9551_z: + reg = MMA9551_TILT_XY_ANG_REG; + break; + } + + /* + * Read the angle even though we don't use it, otherwise we + * won't get any further interrupts. + */ + ret = mma9551_read_status_byte(data->client, MMA9551_APPID_TILT, + reg, &val); + if (ret < 0) { + dev_err(&data->client->dev, + "error %d reading tilt register in IRQ\n", ret); + goto out; + } + + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_INCLI, 0, (mma_axis + 1), + IIO_EV_TYPE_ROC, IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + +out: + mutex_unlock(&data->mutex); + + return IRQ_HANDLED; +} + +static int mma9551_init(struct mma9551_data *data) +{ + int ret; + + ret = mma9551_read_version(data->client); + if (ret) + return ret; + + return mma9551_set_device_state(data->client, true); +} + +static int mma9551_gpio_probe(struct iio_dev *indio_dev) +{ + struct gpio_desc *gpio; + int i, ret; + struct mma9551_data *data = iio_priv(indio_dev); + struct device *dev = &data->client->dev; + + for (i = 0; i < MMA9551_GPIO_COUNT; i++) { + gpio = devm_gpiod_get_index(dev, NULL, i, GPIOD_IN); + if (IS_ERR(gpio)) { + dev_err(dev, "acpi gpio get index failed\n"); + return PTR_ERR(gpio); + } + + ret = gpiod_to_irq(gpio); + if (ret < 0) + return ret; + + data->irqs[i] = ret; + ret = devm_request_threaded_irq(dev, data->irqs[i], + NULL, mma9551_event_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "mma9551_event", indio_dev); + if (ret) + return ret; + + dev_dbg(dev, "gpio resource, no:%d irq:%d\n", + desc_to_gpio(gpio), data->irqs[i]); + } + + return 0; +} + +static int mma9551_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct mma9551_data *data; + struct iio_dev *indio_dev; + const char *name = NULL; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + if (id) + name = id->name; + else + name = iio_get_acpi_device_name(&client->dev); + + ret = mma9551_init(data); + if (ret < 0) + return ret; + + mutex_init(&data->mutex); + + indio_dev->channels = mma9551_channels; + indio_dev->num_channels = ARRAY_SIZE(mma9551_channels); + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &mma9551_info; + + ret = mma9551_gpio_probe(indio_dev); + if (ret < 0) + goto out_poweroff; + + ret = pm_runtime_set_active(&client->dev); + if (ret < 0) + goto out_poweroff; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, + MMA9551_AUTO_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "unable to register iio device\n"); + goto err_pm_cleanup; + } + + return 0; + +err_pm_cleanup: + pm_runtime_dont_use_autosuspend(&client->dev); + pm_runtime_disable(&client->dev); +out_poweroff: + mma9551_set_device_state(client, false); + + return ret; +} + +static void mma9551_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct mma9551_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + mutex_lock(&data->mutex); + mma9551_set_device_state(data->client, false); + mutex_unlock(&data->mutex); +} + +static int mma9551_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma9551_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = mma9551_set_device_state(data->client, false); + mutex_unlock(&data->mutex); + if (ret < 0) { + dev_err(&data->client->dev, "powering off device failed\n"); + return -EAGAIN; + } + + return 0; +} + +static int mma9551_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma9551_data *data = iio_priv(indio_dev); + int ret; + + ret = mma9551_set_device_state(data->client, true); + if (ret < 0) + return ret; + + mma9551_sleep(MMA9551_DEFAULT_SAMPLE_RATE); + + return 0; +} + +static int mma9551_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma9551_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = mma9551_set_device_state(data->client, false); + mutex_unlock(&data->mutex); + + return ret; +} + +static int mma9551_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma9551_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = mma9551_set_device_state(data->client, true); + mutex_unlock(&data->mutex); + + return ret; +} + +static const struct dev_pm_ops mma9551_pm_ops = { + SYSTEM_SLEEP_PM_OPS(mma9551_suspend, mma9551_resume) + RUNTIME_PM_OPS(mma9551_runtime_suspend, mma9551_runtime_resume, NULL) +}; + +static const struct acpi_device_id mma9551_acpi_match[] = { + {"MMA9551", 0}, + { } +}; + +MODULE_DEVICE_TABLE(acpi, mma9551_acpi_match); + +static const struct i2c_device_id mma9551_id[] = { + { .name = "mma9551" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, mma9551_id); + +static struct i2c_driver mma9551_driver = { + .driver = { + .name = "mma9551", + .acpi_match_table = mma9551_acpi_match, + .pm = pm_ptr(&mma9551_pm_ops), + }, + .probe = mma9551_probe, + .remove = mma9551_remove, + .id_table = mma9551_id, +}; + +module_i2c_driver(mma9551_driver); + +MODULE_AUTHOR("Irina Tirdea "); +MODULE_AUTHOR("Vlad Dogaru "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MMA9551L motion-sensing platform driver"); +MODULE_IMPORT_NS("IIO_MMA9551"); diff --git a/drivers/iio/accel/mma9551_core.c b/drivers/iio/accel/mma9551_core.c new file mode 100644 index 000000000..2ccb1fb19 --- /dev/null +++ b/drivers/iio/accel/mma9551_core.c @@ -0,0 +1,803 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Common code for Freescale MMA955x Intelligent Sensor Platform drivers + * Copyright (c) 2014, Intel Corporation. + */ + +#include +#include +#include +#include +#include +#include "mma9551_core.h" + +/* Command masks for mailbox write command */ +#define MMA9551_CMD_READ_VERSION_INFO 0x00 +#define MMA9551_CMD_READ_CONFIG 0x10 +#define MMA9551_CMD_WRITE_CONFIG 0x20 +#define MMA9551_CMD_READ_STATUS 0x30 + +/* Mailbox read command */ +#define MMA9551_RESPONSE_COCO BIT(7) + +/* Error-Status codes returned in mailbox read command */ +#define MMA9551_MCI_ERROR_NONE 0x00 +#define MMA9551_MCI_ERROR_PARAM 0x04 +#define MMA9551_MCI_INVALID_COUNT 0x19 +#define MMA9551_MCI_ERROR_COMMAND 0x1C +#define MMA9551_MCI_ERROR_INVALID_LENGTH 0x21 +#define MMA9551_MCI_ERROR_FIFO_BUSY 0x22 +#define MMA9551_MCI_ERROR_FIFO_ALLOCATED 0x23 +#define MMA9551_MCI_ERROR_FIFO_OVERSIZE 0x24 + +/* GPIO Application */ +#define MMA9551_GPIO_POL_MSB 0x08 +#define MMA9551_GPIO_POL_LSB 0x09 + +/* Sleep/Wake application */ +#define MMA9551_SLEEP_CFG 0x06 +#define MMA9551_SLEEP_CFG_SNCEN BIT(0) +#define MMA9551_SLEEP_CFG_FLEEN BIT(1) +#define MMA9551_SLEEP_CFG_SCHEN BIT(2) + +/* AFE application */ +#define MMA9551_AFE_X_ACCEL_REG 0x00 +#define MMA9551_AFE_Y_ACCEL_REG 0x02 +#define MMA9551_AFE_Z_ACCEL_REG 0x04 + +/* Reset/Suspend/Clear application */ +#define MMA9551_RSC_RESET 0x00 +#define MMA9551_RSC_OFFSET(mask) (3 - (ffs(mask) - 1) / 8) +#define MMA9551_RSC_VAL(mask) (mask >> (((ffs(mask) - 1) / 8) * 8)) + +/* + * A response is composed of: + * - control registers: MB0-3 + * - data registers: MB4-31 + * + * A request is composed of: + * - mbox to write to (always 0) + * - control registers: MB1-4 + * - data registers: MB5-31 + */ +#define MMA9551_MAILBOX_CTRL_REGS 4 +#define MMA9551_MAX_MAILBOX_DATA_REGS 28 +#define MMA9551_MAILBOX_REGS 32 + +#define MMA9551_I2C_READ_RETRIES 5 +#define MMA9551_I2C_READ_DELAY 50 /* us */ + +struct mma9551_mbox_request { + u8 start_mbox; /* Always 0. */ + u8 app_id; + /* + * See Section 5.3.1 of the MMA955xL Software Reference Manual. + * + * Bit 7: reserved, always 0 + * Bits 6-4: command + * Bits 3-0: upper bits of register offset + */ + u8 cmd_off; + u8 lower_off; + u8 nbytes; + u8 buf[MMA9551_MAX_MAILBOX_DATA_REGS - 1]; +} __packed; + +struct mma9551_mbox_response { + u8 app_id; + /* + * See Section 5.3.3 of the MMA955xL Software Reference Manual. + * + * Bit 7: COCO + * Bits 6-0: Error code. + */ + u8 coco_err; + u8 nbytes; + u8 req_bytes; + u8 buf[MMA9551_MAX_MAILBOX_DATA_REGS]; +} __packed; + +struct mma9551_version_info { + __be32 device_id; + u8 rom_version[2]; + u8 fw_version[2]; + u8 hw_version[2]; + u8 fw_build[2]; +}; + +static int mma9551_transfer(struct i2c_client *client, + u8 app_id, u8 command, u16 offset, + u8 *inbytes, int num_inbytes, + u8 *outbytes, int num_outbytes) +{ + struct mma9551_mbox_request req; + struct mma9551_mbox_response rsp; + struct i2c_msg in, out; + u8 req_len, err_code; + int ret, retries; + + if (offset >= 1 << 12) { + dev_err(&client->dev, "register offset too large\n"); + return -EINVAL; + } + + req_len = 1 + MMA9551_MAILBOX_CTRL_REGS + num_inbytes; + req.start_mbox = 0; + req.app_id = app_id; + req.cmd_off = command | (offset >> 8); + req.lower_off = offset; + + if (command == MMA9551_CMD_WRITE_CONFIG) + req.nbytes = num_inbytes; + else + req.nbytes = num_outbytes; + if (num_inbytes) + memcpy(req.buf, inbytes, num_inbytes); + + out.addr = client->addr; + out.flags = 0; + out.len = req_len; + out.buf = (u8 *)&req; + + ret = i2c_transfer(client->adapter, &out, 1); + if (ret < 0) { + dev_err(&client->dev, "i2c write failed\n"); + return ret; + } + + retries = MMA9551_I2C_READ_RETRIES; + do { + udelay(MMA9551_I2C_READ_DELAY); + + in.addr = client->addr; + in.flags = I2C_M_RD; + in.len = sizeof(rsp); + in.buf = (u8 *)&rsp; + + ret = i2c_transfer(client->adapter, &in, 1); + if (ret < 0) { + dev_err(&client->dev, "i2c read failed\n"); + return ret; + } + + if (rsp.coco_err & MMA9551_RESPONSE_COCO) + break; + } while (--retries > 0); + + if (retries == 0) { + dev_err(&client->dev, + "timed out while waiting for command response\n"); + return -ETIMEDOUT; + } + + if (rsp.app_id != app_id) { + dev_err(&client->dev, + "app_id mismatch in response got %02x expected %02x\n", + rsp.app_id, app_id); + return -EINVAL; + } + + err_code = rsp.coco_err & ~MMA9551_RESPONSE_COCO; + if (err_code != MMA9551_MCI_ERROR_NONE) { + dev_err(&client->dev, "read returned error %x\n", err_code); + return -EINVAL; + } + + if (rsp.nbytes != rsp.req_bytes) { + dev_err(&client->dev, + "output length mismatch got %d expected %d\n", + rsp.nbytes, rsp.req_bytes); + return -EINVAL; + } + + if (num_outbytes) + memcpy(outbytes, rsp.buf, num_outbytes); + + return 0; +} + +/** + * mma9551_read_config_byte() - read 1 configuration byte + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @val: Pointer to store value read + * + * Read one configuration byte from the device using MMA955xL command format. + * Commands to the MMA955xL platform consist of a write followed + * by one or more reads. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_read_config_byte(struct i2c_client *client, u8 app_id, + u16 reg, u8 *val) +{ + return mma9551_transfer(client, app_id, MMA9551_CMD_READ_CONFIG, + reg, NULL, 0, val, 1); +} +EXPORT_SYMBOL_NS(mma9551_read_config_byte, "IIO_MMA9551"); + +/** + * mma9551_write_config_byte() - write 1 configuration byte + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @val: Value to write + * + * Write one configuration byte from the device using MMA955xL command format. + * Commands to the MMA955xL platform consist of a write followed by one or + * more reads. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_write_config_byte(struct i2c_client *client, u8 app_id, + u16 reg, u8 val) +{ + return mma9551_transfer(client, app_id, MMA9551_CMD_WRITE_CONFIG, reg, + &val, 1, NULL, 0); +} +EXPORT_SYMBOL_NS(mma9551_write_config_byte, "IIO_MMA9551"); + +/** + * mma9551_read_status_byte() - read 1 status byte + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @val: Pointer to store value read + * + * Read one status byte from the device using MMA955xL command format. + * Commands to the MMA955xL platform consist of a write followed by one or + * more reads. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_read_status_byte(struct i2c_client *client, u8 app_id, + u16 reg, u8 *val) +{ + return mma9551_transfer(client, app_id, MMA9551_CMD_READ_STATUS, + reg, NULL, 0, val, 1); +} +EXPORT_SYMBOL_NS(mma9551_read_status_byte, "IIO_MMA9551"); + +/** + * mma9551_read_config_word() - read 1 config word + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @val: Pointer to store value read + * + * Read one configuration word from the device using MMA955xL command format. + * Commands to the MMA955xL platform consist of a write followed by one or + * more reads. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_read_config_word(struct i2c_client *client, u8 app_id, + u16 reg, u16 *val) +{ + int ret; + __be16 v; + + ret = mma9551_transfer(client, app_id, MMA9551_CMD_READ_CONFIG, + reg, NULL, 0, (u8 *)&v, 2); + if (ret < 0) + return ret; + + *val = be16_to_cpu(v); + + return 0; +} +EXPORT_SYMBOL_NS(mma9551_read_config_word, "IIO_MMA9551"); + +/** + * mma9551_write_config_word() - write 1 config word + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @val: Value to write + * + * Write one configuration word from the device using MMA955xL command format. + * Commands to the MMA955xL platform consist of a write followed by one or + * more reads. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_write_config_word(struct i2c_client *client, u8 app_id, + u16 reg, u16 val) +{ + __be16 v = cpu_to_be16(val); + + return mma9551_transfer(client, app_id, MMA9551_CMD_WRITE_CONFIG, reg, + (u8 *)&v, 2, NULL, 0); +} +EXPORT_SYMBOL_NS(mma9551_write_config_word, "IIO_MMA9551"); + +/** + * mma9551_read_status_word() - read 1 status word + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @val: Pointer to store value read + * + * Read one status word from the device using MMA955xL command format. + * Commands to the MMA955xL platform consist of a write followed by one or + * more reads. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_read_status_word(struct i2c_client *client, u8 app_id, + u16 reg, u16 *val) +{ + int ret; + __be16 v; + + ret = mma9551_transfer(client, app_id, MMA9551_CMD_READ_STATUS, + reg, NULL, 0, (u8 *)&v, 2); + if (ret < 0) + return ret; + + *val = be16_to_cpu(v); + + return 0; +} +EXPORT_SYMBOL_NS(mma9551_read_status_word, "IIO_MMA9551"); + +/** + * mma9551_read_config_words() - read multiple config words + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @len: Length of array to read (in words) + * @buf: Array of words to read + * + * Read multiple configuration registers (word-sized registers). + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_read_config_words(struct i2c_client *client, u8 app_id, + u16 reg, u8 len, u16 *buf) +{ + int ret, i; + __be16 be_buf[MMA9551_MAX_MAILBOX_DATA_REGS / 2]; + + if (len > ARRAY_SIZE(be_buf)) { + dev_err(&client->dev, "Invalid buffer size %d\n", len); + return -EINVAL; + } + + ret = mma9551_transfer(client, app_id, MMA9551_CMD_READ_CONFIG, + reg, NULL, 0, (u8 *)be_buf, len * sizeof(u16)); + if (ret < 0) + return ret; + + for (i = 0; i < len; i++) + buf[i] = be16_to_cpu(be_buf[i]); + + return 0; +} +EXPORT_SYMBOL_NS(mma9551_read_config_words, "IIO_MMA9551"); + +/** + * mma9551_read_status_words() - read multiple status words + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @len: Length of array to read (in words) + * @buf: Array of words to read + * + * Read multiple status registers (word-sized registers). + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_read_status_words(struct i2c_client *client, u8 app_id, + u16 reg, u8 len, u16 *buf) +{ + int ret, i; + __be16 be_buf[MMA9551_MAX_MAILBOX_DATA_REGS / 2]; + + if (len > ARRAY_SIZE(be_buf)) { + dev_err(&client->dev, "Invalid buffer size %d\n", len); + return -EINVAL; + } + + ret = mma9551_transfer(client, app_id, MMA9551_CMD_READ_STATUS, + reg, NULL, 0, (u8 *)be_buf, len * sizeof(u16)); + if (ret < 0) + return ret; + + for (i = 0; i < len; i++) + buf[i] = be16_to_cpu(be_buf[i]); + + return 0; +} +EXPORT_SYMBOL_NS(mma9551_read_status_words, "IIO_MMA9551"); + +/** + * mma9551_write_config_words() - write multiple config words + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @len: Length of array to write (in words) + * @buf: Array of words to write + * + * Write multiple configuration registers (word-sized registers). + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_write_config_words(struct i2c_client *client, u8 app_id, + u16 reg, u8 len, u16 *buf) +{ + int i; + __be16 be_buf[(MMA9551_MAX_MAILBOX_DATA_REGS - 1) / 2]; + + if (len > ARRAY_SIZE(be_buf)) { + dev_err(&client->dev, "Invalid buffer size %d\n", len); + return -EINVAL; + } + + for (i = 0; i < len; i++) + be_buf[i] = cpu_to_be16(buf[i]); + + return mma9551_transfer(client, app_id, MMA9551_CMD_WRITE_CONFIG, + reg, (u8 *)be_buf, len * sizeof(u16), NULL, 0); +} +EXPORT_SYMBOL_NS(mma9551_write_config_words, "IIO_MMA9551"); + +/** + * mma9551_update_config_bits() - update bits in register + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @mask: Mask for the bits to update + * @val: Value of the bits to update + * + * Update bits in the given register using a bit mask. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_update_config_bits(struct i2c_client *client, u8 app_id, + u16 reg, u8 mask, u8 val) +{ + int ret; + u8 tmp, orig; + + ret = mma9551_read_config_byte(client, app_id, reg, &orig); + if (ret < 0) + return ret; + + tmp = orig & ~mask; + tmp |= val & mask; + + if (tmp == orig) + return 0; + + return mma9551_write_config_byte(client, app_id, reg, tmp); +} +EXPORT_SYMBOL_NS(mma9551_update_config_bits, "IIO_MMA9551"); + +/** + * mma9551_gpio_config() - configure gpio + * @client: I2C client + * @pin: GPIO pin to configure + * @app_id: Application ID + * @bitnum: Bit number of status register being assigned to the GPIO pin. + * @polarity: The polarity parameter is described in section 6.2.2, page 66, + * of the Software Reference Manual. Basically, polarity=0 means + * the interrupt line has the same value as the selected bit, + * while polarity=1 means the line is inverted. + * + * Assign a bit from an application’s status register to a specific GPIO pin. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_gpio_config(struct i2c_client *client, enum mma9551_gpio_pin pin, + u8 app_id, u8 bitnum, int polarity) +{ + u8 reg, pol_mask, pol_val; + int ret; + + if (pin > mma9551_gpio_max) { + dev_err(&client->dev, "bad GPIO pin\n"); + return -EINVAL; + } + + /* + * Pin 6 is configured by regs 0x00 and 0x01, pin 7 by 0x02 and + * 0x03, and so on. + */ + reg = pin * 2; + + ret = mma9551_write_config_byte(client, MMA9551_APPID_GPIO, + reg, app_id); + if (ret < 0) { + dev_err(&client->dev, "error setting GPIO app_id\n"); + return ret; + } + + ret = mma9551_write_config_byte(client, MMA9551_APPID_GPIO, + reg + 1, bitnum); + if (ret < 0) { + dev_err(&client->dev, "error setting GPIO bit number\n"); + return ret; + } + + switch (pin) { + case mma9551_gpio6: + reg = MMA9551_GPIO_POL_LSB; + pol_mask = 1 << 6; + break; + case mma9551_gpio7: + reg = MMA9551_GPIO_POL_LSB; + pol_mask = 1 << 7; + break; + case mma9551_gpio8: + reg = MMA9551_GPIO_POL_MSB; + pol_mask = 1 << 0; + break; + case mma9551_gpio9: + reg = MMA9551_GPIO_POL_MSB; + pol_mask = 1 << 1; + break; + } + pol_val = polarity ? pol_mask : 0; + + ret = mma9551_update_config_bits(client, MMA9551_APPID_GPIO, reg, + pol_mask, pol_val); + if (ret < 0) + dev_err(&client->dev, "error setting GPIO polarity\n"); + + return ret; +} +EXPORT_SYMBOL_NS(mma9551_gpio_config, "IIO_MMA9551"); + +/** + * mma9551_read_version() - read device version information + * @client: I2C client + * + * Read version information and print device id and firmware version. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_read_version(struct i2c_client *client) +{ + struct mma9551_version_info info; + int ret; + + ret = mma9551_transfer(client, MMA9551_APPID_VERSION, 0x00, 0x00, + NULL, 0, (u8 *)&info, sizeof(info)); + if (ret < 0) + return ret; + + dev_info(&client->dev, "device ID 0x%x, firmware version %02x.%02x\n", + be32_to_cpu(info.device_id), info.fw_version[0], + info.fw_version[1]); + + return 0; +} +EXPORT_SYMBOL_NS(mma9551_read_version, "IIO_MMA9551"); + +/** + * mma9551_set_device_state() - sets HW power mode + * @client: I2C client + * @enable: Use true to power on device, false to cause the device + * to enter sleep. + * + * Set power on/off for device using the Sleep/Wake Application. + * When enable is true, power on chip and enable doze mode. + * When enable is false, enter sleep mode (device remains in the + * lowest-power mode). + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_set_device_state(struct i2c_client *client, bool enable) +{ + return mma9551_update_config_bits(client, MMA9551_APPID_SLEEP_WAKE, + MMA9551_SLEEP_CFG, + MMA9551_SLEEP_CFG_SNCEN | + MMA9551_SLEEP_CFG_FLEEN | + MMA9551_SLEEP_CFG_SCHEN, + enable ? MMA9551_SLEEP_CFG_SCHEN | + MMA9551_SLEEP_CFG_FLEEN : + MMA9551_SLEEP_CFG_SNCEN); +} +EXPORT_SYMBOL_NS(mma9551_set_device_state, "IIO_MMA9551"); + +/** + * mma9551_set_power_state() - sets runtime PM state + * @client: I2C client + * @on: Use true to power on device, false to power off + * + * Resume or suspend the device using Runtime PM. + * The device will suspend after the autosuspend delay. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_set_power_state(struct i2c_client *client, bool on) +{ +#ifdef CONFIG_PM + int ret; + + if (on) + ret = pm_runtime_resume_and_get(&client->dev); + else + ret = pm_runtime_put_autosuspend(&client->dev); + if (ret < 0) { + dev_err(&client->dev, + "failed to change power state to %d\n", on); + + return ret; + } +#endif + + return 0; +} +EXPORT_SYMBOL_NS(mma9551_set_power_state, "IIO_MMA9551"); + +/** + * mma9551_sleep() - sleep + * @freq: Application frequency + * + * Firmware applications run at a certain frequency on the + * device. Sleep for one application cycle to make sure the + * application had time to run once and initialize set values. + */ +void mma9551_sleep(int freq) +{ + int sleep_val = 1000 / freq; + + if (sleep_val < 20) + usleep_range(sleep_val * 1000, 20000); + else + msleep_interruptible(sleep_val); +} +EXPORT_SYMBOL_NS(mma9551_sleep, "IIO_MMA9551"); + +/** + * mma9551_read_accel_chan() - read accelerometer channel + * @client: I2C client + * @chan: IIO channel + * @val: Pointer to the accelerometer value read + * @val2: Unused + * + * Read accelerometer value for the specified channel. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: IIO_VAL_INT on success, negative value on failure. + */ +int mma9551_read_accel_chan(struct i2c_client *client, + const struct iio_chan_spec *chan, + int *val, int *val2) +{ + u16 reg_addr; + s16 raw_accel; + int ret; + + switch (chan->channel2) { + case IIO_MOD_X: + reg_addr = MMA9551_AFE_X_ACCEL_REG; + break; + case IIO_MOD_Y: + reg_addr = MMA9551_AFE_Y_ACCEL_REG; + break; + case IIO_MOD_Z: + reg_addr = MMA9551_AFE_Z_ACCEL_REG; + break; + default: + return -EINVAL; + } + + ret = mma9551_set_power_state(client, true); + if (ret < 0) + return ret; + + ret = mma9551_read_status_word(client, MMA9551_APPID_AFE, + reg_addr, &raw_accel); + if (ret < 0) + goto out_poweroff; + + *val = raw_accel; + + ret = IIO_VAL_INT; + +out_poweroff: + mma9551_set_power_state(client, false); + return ret; +} +EXPORT_SYMBOL_NS(mma9551_read_accel_chan, "IIO_MMA9551"); + +/** + * mma9551_read_accel_scale() - read accelerometer scale + * @val: Pointer to the accelerometer scale (int value) + * @val2: Pointer to the accelerometer scale (micro value) + * + * Read accelerometer scale. + * + * Returns: IIO_VAL_INT_PLUS_MICRO. + */ +int mma9551_read_accel_scale(int *val, int *val2) +{ + *val = 0; + *val2 = 2440; + + return IIO_VAL_INT_PLUS_MICRO; +} +EXPORT_SYMBOL_NS(mma9551_read_accel_scale, "IIO_MMA9551"); + +/** + * mma9551_app_reset() - reset application + * @client: I2C client + * @app_mask: Application to reset + * + * Reset the given application (using the Reset/Suspend/Clear + * Control Application) + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_app_reset(struct i2c_client *client, u32 app_mask) +{ + return mma9551_write_config_byte(client, MMA9551_APPID_RSC, + MMA9551_RSC_RESET + + MMA9551_RSC_OFFSET(app_mask), + MMA9551_RSC_VAL(app_mask)); +} +EXPORT_SYMBOL_NS(mma9551_app_reset, "IIO_MMA9551"); + +MODULE_AUTHOR("Irina Tirdea "); +MODULE_AUTHOR("Vlad Dogaru "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MMA955xL sensors core"); diff --git a/drivers/iio/accel/mma9551_core.h b/drivers/iio/accel/mma9551_core.h new file mode 100644 index 000000000..543454a31 --- /dev/null +++ b/drivers/iio/accel/mma9551_core.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Common code for Freescale MMA955x Intelligent Sensor Platform drivers + * Copyright (c) 2014, Intel Corporation. + */ + +#ifndef _MMA9551_CORE_H_ +#define _MMA9551_CORE_H_ + +/* Applications IDs */ +#define MMA9551_APPID_VERSION 0x00 +#define MMA9551_APPID_GPIO 0x03 +#define MMA9551_APPID_AFE 0x06 +#define MMA9551_APPID_TILT 0x0B +#define MMA9551_APPID_SLEEP_WAKE 0x12 +#define MMA9551_APPID_PEDOMETER 0x15 +#define MMA9551_APPID_RSC 0x17 +#define MMA9551_APPID_NONE 0xff + +/* Reset/Suspend/Clear application app masks */ +#define MMA9551_RSC_PED BIT(21) + +#define MMA9551_AUTO_SUSPEND_DELAY_MS 2000 + +enum mma9551_gpio_pin { + mma9551_gpio6 = 0, + mma9551_gpio7, + mma9551_gpio8, + mma9551_gpio9, + mma9551_gpio_max = mma9551_gpio9, +}; + +#define MMA9551_ACCEL_CHANNEL(axis) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +int mma9551_read_config_byte(struct i2c_client *client, u8 app_id, + u16 reg, u8 *val); +int mma9551_write_config_byte(struct i2c_client *client, u8 app_id, + u16 reg, u8 val); +int mma9551_read_status_byte(struct i2c_client *client, u8 app_id, + u16 reg, u8 *val); +int mma9551_read_config_word(struct i2c_client *client, u8 app_id, + u16 reg, u16 *val); +int mma9551_write_config_word(struct i2c_client *client, u8 app_id, + u16 reg, u16 val); +int mma9551_read_status_word(struct i2c_client *client, u8 app_id, + u16 reg, u16 *val); +int mma9551_read_config_words(struct i2c_client *client, u8 app_id, + u16 reg, u8 len, u16 *buf); +int mma9551_read_status_words(struct i2c_client *client, u8 app_id, + u16 reg, u8 len, u16 *buf); +int mma9551_write_config_words(struct i2c_client *client, u8 app_id, + u16 reg, u8 len, u16 *buf); +int mma9551_update_config_bits(struct i2c_client *client, u8 app_id, + u16 reg, u8 mask, u8 val); +int mma9551_gpio_config(struct i2c_client *client, enum mma9551_gpio_pin pin, + u8 app_id, u8 bitnum, int polarity); +int mma9551_read_version(struct i2c_client *client); +int mma9551_set_device_state(struct i2c_client *client, bool enable); +int mma9551_set_power_state(struct i2c_client *client, bool on); +void mma9551_sleep(int freq); +int mma9551_read_accel_chan(struct i2c_client *client, + const struct iio_chan_spec *chan, + int *val, int *val2); +int mma9551_read_accel_scale(int *val, int *val2); +int mma9551_app_reset(struct i2c_client *client, u32 app_mask); + +#endif /* _MMA9551_CORE_H_ */ diff --git a/drivers/iio/accel/mma9553.c b/drivers/iio/accel/mma9553.c new file mode 100644 index 000000000..8e7aaac89 --- /dev/null +++ b/drivers/iio/accel/mma9553.c @@ -0,0 +1,1240 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Freescale MMA9553L Intelligent Pedometer driver + * Copyright (c) 2014, Intel Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "mma9551_core.h" + +/* Pedometer configuration registers (R/W) */ +#define MMA9553_REG_CONF_SLEEPMIN 0x00 +#define MMA9553_REG_CONF_SLEEPMAX 0x02 +#define MMA9553_REG_CONF_SLEEPTHD 0x04 +#define MMA9553_MASK_CONF_WORD GENMASK(15, 0) + +#define MMA9553_REG_CONF_CONF_STEPLEN 0x06 +#define MMA9553_MASK_CONF_CONFIG BIT(15) +#define MMA9553_MASK_CONF_ACT_DBCNTM BIT(14) +#define MMA9553_MASK_CONF_SLP_DBCNTM BIT(13) +#define MMA9553_MASK_CONF_STEPLEN GENMASK(7, 0) + +#define MMA9553_REG_CONF_HEIGHT_WEIGHT 0x08 +#define MMA9553_MASK_CONF_HEIGHT GENMASK(15, 8) +#define MMA9553_MASK_CONF_WEIGHT GENMASK(7, 0) + +#define MMA9553_REG_CONF_FILTER 0x0A +#define MMA9553_MASK_CONF_FILTSTEP GENMASK(15, 8) +#define MMA9553_MASK_CONF_MALE BIT(7) +#define MMA9553_MASK_CONF_FILTTIME GENMASK(6, 0) + +#define MMA9553_REG_CONF_SPEED_STEP 0x0C +#define MMA9553_MASK_CONF_SPDPRD GENMASK(15, 8) +#define MMA9553_MASK_CONF_STEPCOALESCE GENMASK(7, 0) + +#define MMA9553_REG_CONF_ACTTHD 0x0E +#define MMA9553_MAX_ACTTHD GENMASK(15, 0) + +/* Pedometer status registers (R-only) */ +#define MMA9553_REG_STATUS 0x00 +#define MMA9553_MASK_STATUS_MRGFL BIT(15) +#define MMA9553_MASK_STATUS_SUSPCHG BIT(14) +#define MMA9553_MASK_STATUS_STEPCHG BIT(13) +#define MMA9553_MASK_STATUS_ACTCHG BIT(12) +#define MMA9553_MASK_STATUS_SUSP BIT(11) +#define MMA9553_MASK_STATUS_ACTIVITY GENMASK(10, 8) +#define MMA9553_MASK_STATUS_VERSION GENMASK(7, 0) + +#define MMA9553_REG_STEPCNT 0x02 +#define MMA9553_REG_DISTANCE 0x04 +#define MMA9553_REG_SPEED 0x06 +#define MMA9553_REG_CALORIES 0x08 +#define MMA9553_REG_SLEEPCNT 0x0A + +/* Pedometer events are always mapped to this pin. */ +#define MMA9553_DEFAULT_GPIO_PIN mma9551_gpio6 +#define MMA9553_DEFAULT_GPIO_POLARITY 0 + +/* Bitnum used for GPIO configuration = bit number in high status byte */ +#define MMA9553_STATUS_TO_BITNUM(bit) (ffs(bit) - 9) +#define MMA9553_MAX_BITNUM MMA9553_STATUS_TO_BITNUM(BIT(16)) + +#define MMA9553_DEFAULT_SAMPLE_RATE 30 /* Hz */ + +/* + * The internal activity level must be stable for ACTTHD samples before + * ACTIVITY is updated. The ACTIVITY variable contains the current activity + * level and is updated every time a step is detected or once a second + * if there are no steps. + */ +#define MMA9553_ACTIVITY_THD_TO_SEC(thd) ((thd) / MMA9553_DEFAULT_SAMPLE_RATE) +#define MMA9553_ACTIVITY_SEC_TO_THD(sec) ((sec) * MMA9553_DEFAULT_SAMPLE_RATE) + +/* + * Autonomously suspend pedometer if acceleration vector magnitude + * is near 1g (4096 at 0.244 mg/LSB resolution) for 30 seconds. + */ +#define MMA9553_DEFAULT_SLEEPMIN 3688 /* 0,9 g */ +#define MMA9553_DEFAULT_SLEEPMAX 4508 /* 1,1 g */ +#define MMA9553_DEFAULT_SLEEPTHD (MMA9553_DEFAULT_SAMPLE_RATE * 30) + +#define MMA9553_CONFIG_RETRIES 2 + +/* Status register - activity field */ +enum activity_level { + ACTIVITY_UNKNOWN, + ACTIVITY_REST, + ACTIVITY_WALKING, + ACTIVITY_JOGGING, + ACTIVITY_RUNNING, +}; + +static const struct mma9553_event_info { + enum iio_chan_type type; + enum iio_modifier mod; + enum iio_event_direction dir; +} mma9553_events_info[] = { + { + .type = IIO_STEPS, + .mod = IIO_NO_MOD, + .dir = IIO_EV_DIR_NONE, + }, + { + .type = IIO_ACTIVITY, + .mod = IIO_MOD_STILL, + .dir = IIO_EV_DIR_RISING, + }, + { + .type = IIO_ACTIVITY, + .mod = IIO_MOD_STILL, + .dir = IIO_EV_DIR_FALLING, + }, + { + .type = IIO_ACTIVITY, + .mod = IIO_MOD_WALKING, + .dir = IIO_EV_DIR_RISING, + }, + { + .type = IIO_ACTIVITY, + .mod = IIO_MOD_WALKING, + .dir = IIO_EV_DIR_FALLING, + }, + { + .type = IIO_ACTIVITY, + .mod = IIO_MOD_JOGGING, + .dir = IIO_EV_DIR_RISING, + }, + { + .type = IIO_ACTIVITY, + .mod = IIO_MOD_JOGGING, + .dir = IIO_EV_DIR_FALLING, + }, + { + .type = IIO_ACTIVITY, + .mod = IIO_MOD_RUNNING, + .dir = IIO_EV_DIR_RISING, + }, + { + .type = IIO_ACTIVITY, + .mod = IIO_MOD_RUNNING, + .dir = IIO_EV_DIR_FALLING, + }, +}; + +#define MMA9553_EVENTS_INFO_SIZE ARRAY_SIZE(mma9553_events_info) + +struct mma9553_event { + const struct mma9553_event_info *info; + bool enabled; +}; + +struct mma9553_conf_regs { + u16 sleepmin; + u16 sleepmax; + u16 sleepthd; + u16 config; + u16 height_weight; + u16 filter; + u16 speed_step; + u16 actthd; +} __packed; + +struct mma9553_data { + struct i2c_client *client; + /* + * 1. Serialize access to HW (requested by mma9551_core API). + * 2. Serialize sequences that power on/off the device and access HW. + */ + struct mutex mutex; + struct mma9553_conf_regs conf; + struct mma9553_event events[MMA9553_EVENTS_INFO_SIZE]; + int num_events; + u8 gpio_bitnum; + /* + * This is used for all features that depend on step count: + * step count, distance, speed, calories. + */ + bool stepcnt_enabled; + u16 stepcnt; + u8 activity; + s64 timestamp; +}; + +static u8 mma9553_get_bits(u16 val, u16 mask) +{ + return (val & mask) >> (ffs(mask) - 1); +} + +static u16 mma9553_set_bits(u16 current_val, u16 val, u16 mask) +{ + return (current_val & ~mask) | (val << (ffs(mask) - 1)); +} + +static enum iio_modifier mma9553_activity_to_mod(enum activity_level activity) +{ + switch (activity) { + case ACTIVITY_RUNNING: + return IIO_MOD_RUNNING; + case ACTIVITY_JOGGING: + return IIO_MOD_JOGGING; + case ACTIVITY_WALKING: + return IIO_MOD_WALKING; + case ACTIVITY_REST: + return IIO_MOD_STILL; + case ACTIVITY_UNKNOWN: + default: + return IIO_NO_MOD; + } +} + +static void mma9553_init_events(struct mma9553_data *data) +{ + int i; + + data->num_events = MMA9553_EVENTS_INFO_SIZE; + for (i = 0; i < data->num_events; i++) { + data->events[i].info = &mma9553_events_info[i]; + data->events[i].enabled = false; + } +} + +static struct mma9553_event *mma9553_get_event(struct mma9553_data *data, + enum iio_chan_type type, + enum iio_modifier mod, + enum iio_event_direction dir) +{ + int i; + + for (i = 0; i < data->num_events; i++) + if (data->events[i].info->type == type && + data->events[i].info->mod == mod && + data->events[i].info->dir == dir) + return &data->events[i]; + + return NULL; +} + +static bool mma9553_is_any_event_enabled(struct mma9553_data *data, + bool check_type, + enum iio_chan_type type) +{ + int i; + + for (i = 0; i < data->num_events; i++) + if ((check_type && data->events[i].info->type == type && + data->events[i].enabled) || + (!check_type && data->events[i].enabled)) + return true; + + return false; +} + +static int mma9553_set_config(struct mma9553_data *data, u16 reg, + u16 *p_reg_val, u16 val, u16 mask) +{ + int ret, retries; + u16 reg_val, config; + + reg_val = *p_reg_val; + if (val == mma9553_get_bits(reg_val, mask)) + return 0; + + reg_val = mma9553_set_bits(reg_val, val, mask); + ret = mma9551_write_config_word(data->client, MMA9551_APPID_PEDOMETER, + reg, reg_val); + if (ret < 0) { + dev_err(&data->client->dev, + "error writing config register 0x%x\n", reg); + return ret; + } + + *p_reg_val = reg_val; + + /* Reinitializes the pedometer with current configuration values */ + config = mma9553_set_bits(data->conf.config, 1, + MMA9553_MASK_CONF_CONFIG); + + ret = mma9551_write_config_word(data->client, MMA9551_APPID_PEDOMETER, + MMA9553_REG_CONF_CONF_STEPLEN, config); + if (ret < 0) { + dev_err(&data->client->dev, + "error writing config register 0x%x\n", + MMA9553_REG_CONF_CONF_STEPLEN); + return ret; + } + + retries = MMA9553_CONFIG_RETRIES; + do { + mma9551_sleep(MMA9553_DEFAULT_SAMPLE_RATE); + ret = mma9551_read_config_word(data->client, + MMA9551_APPID_PEDOMETER, + MMA9553_REG_CONF_CONF_STEPLEN, + &config); + if (ret < 0) + return ret; + } while (mma9553_get_bits(config, MMA9553_MASK_CONF_CONFIG) && + --retries > 0); + + return 0; +} + +static int mma9553_read_activity_stepcnt(struct mma9553_data *data, + u8 *activity, u16 *stepcnt) +{ + u16 buf[2]; + int ret; + + ret = mma9551_read_status_words(data->client, MMA9551_APPID_PEDOMETER, + MMA9553_REG_STATUS, ARRAY_SIZE(buf), + buf); + if (ret < 0) { + dev_err(&data->client->dev, + "error reading status and stepcnt\n"); + return ret; + } + + *activity = mma9553_get_bits(buf[0], MMA9553_MASK_STATUS_ACTIVITY); + *stepcnt = buf[1]; + + return 0; +} + +static int mma9553_conf_gpio(struct mma9553_data *data) +{ + u8 bitnum = 0, appid = MMA9551_APPID_PEDOMETER; + int ret; + struct mma9553_event *ev_step_detect; + bool activity_enabled; + + activity_enabled = mma9553_is_any_event_enabled(data, true, + IIO_ACTIVITY); + ev_step_detect = mma9553_get_event(data, IIO_STEPS, IIO_NO_MOD, + IIO_EV_DIR_NONE); + + /* + * If both step detector and activity are enabled, use the MRGFL bit. + * This bit is the logical OR of the SUSPCHG, STEPCHG, and ACTCHG flags. + */ + if (activity_enabled && ev_step_detect->enabled) + bitnum = MMA9553_STATUS_TO_BITNUM(MMA9553_MASK_STATUS_MRGFL); + else if (ev_step_detect->enabled) + bitnum = MMA9553_STATUS_TO_BITNUM(MMA9553_MASK_STATUS_STEPCHG); + else if (activity_enabled) + bitnum = MMA9553_STATUS_TO_BITNUM(MMA9553_MASK_STATUS_ACTCHG); + else /* Reset */ + appid = MMA9551_APPID_NONE; + + if (data->gpio_bitnum == bitnum) + return 0; + + /* Save initial values for activity and stepcnt */ + if (activity_enabled || ev_step_detect->enabled) { + ret = mma9553_read_activity_stepcnt(data, &data->activity, + &data->stepcnt); + if (ret < 0) + return ret; + } + + ret = mma9551_gpio_config(data->client, MMA9553_DEFAULT_GPIO_PIN, appid, + bitnum, MMA9553_DEFAULT_GPIO_POLARITY); + if (ret < 0) + return ret; + data->gpio_bitnum = bitnum; + + return 0; +} + +static int mma9553_init(struct mma9553_data *data) +{ + int ret; + + ret = mma9551_read_version(data->client); + if (ret) + return ret; + + /* + * Read all the pedometer configuration registers. This is used as + * a device identification command to differentiate the MMA9553L + * from the MMA9550L. + */ + ret = mma9551_read_config_words(data->client, MMA9551_APPID_PEDOMETER, + MMA9553_REG_CONF_SLEEPMIN, + sizeof(data->conf) / sizeof(u16), + (u16 *)&data->conf); + if (ret < 0) { + dev_err(&data->client->dev, + "failed to read configuration registers\n"); + return ret; + } + + /* Reset GPIO */ + data->gpio_bitnum = MMA9553_MAX_BITNUM; + ret = mma9553_conf_gpio(data); + if (ret < 0) + return ret; + + ret = mma9551_app_reset(data->client, MMA9551_RSC_PED); + if (ret < 0) + return ret; + + /* Init config registers */ + data->conf.sleepmin = MMA9553_DEFAULT_SLEEPMIN; + data->conf.sleepmax = MMA9553_DEFAULT_SLEEPMAX; + data->conf.sleepthd = MMA9553_DEFAULT_SLEEPTHD; + data->conf.config = mma9553_set_bits(data->conf.config, 1, + MMA9553_MASK_CONF_CONFIG); + /* + * Clear the activity debounce counter when the activity level changes, + * so that the confidence level applies for any activity level. + */ + data->conf.config = mma9553_set_bits(data->conf.config, 1, + MMA9553_MASK_CONF_ACT_DBCNTM); + ret = mma9551_write_config_words(data->client, MMA9551_APPID_PEDOMETER, + MMA9553_REG_CONF_SLEEPMIN, + sizeof(data->conf) / sizeof(u16), + (u16 *)&data->conf); + if (ret < 0) { + dev_err(&data->client->dev, + "failed to write configuration registers\n"); + return ret; + } + + return mma9551_set_device_state(data->client, true); +} + +static int mma9553_read_status_word(struct mma9553_data *data, u16 reg, + u16 *tmp) +{ + bool powered_on; + int ret; + + /* + * The HW only counts steps and other dependent + * parameters (speed, distance, calories, activity) + * if power is on (from enabling an event or the + * step counter). + */ + powered_on = mma9553_is_any_event_enabled(data, false, 0) || + data->stepcnt_enabled; + if (!powered_on) { + dev_err(&data->client->dev, "No channels enabled\n"); + return -EINVAL; + } + + mutex_lock(&data->mutex); + ret = mma9551_read_status_word(data->client, MMA9551_APPID_PEDOMETER, + reg, tmp); + mutex_unlock(&data->mutex); + return ret; +} + +static int mma9553_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mma9553_data *data = iio_priv(indio_dev); + int ret; + u16 tmp; + u8 activity; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_STEPS: + ret = mma9553_read_status_word(data, + MMA9553_REG_STEPCNT, + &tmp); + if (ret < 0) + return ret; + *val = tmp; + return IIO_VAL_INT; + case IIO_DISTANCE: + ret = mma9553_read_status_word(data, + MMA9553_REG_DISTANCE, + &tmp); + if (ret < 0) + return ret; + *val = tmp; + return IIO_VAL_INT; + case IIO_ACTIVITY: + ret = mma9553_read_status_word(data, + MMA9553_REG_STATUS, + &tmp); + if (ret < 0) + return ret; + + activity = + mma9553_get_bits(tmp, MMA9553_MASK_STATUS_ACTIVITY); + + /* + * The device does not support confidence value levels, + * so we will always have 100% for current activity and + * 0% for the others. + */ + if (chan->channel2 == mma9553_activity_to_mod(activity)) + *val = 100; + else + *val = 0; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_VELOCITY: /* m/h */ + if (chan->channel2 != IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z) + return -EINVAL; + ret = mma9553_read_status_word(data, + MMA9553_REG_SPEED, + &tmp); + if (ret < 0) + return ret; + *val = tmp; + return IIO_VAL_INT; + case IIO_ENERGY: /* Cal or kcal */ + ret = mma9553_read_status_word(data, + MMA9553_REG_CALORIES, + &tmp); + if (ret < 0) + return ret; + *val = tmp; + return IIO_VAL_INT; + case IIO_ACCEL: + mutex_lock(&data->mutex); + ret = mma9551_read_accel_chan(data->client, + chan, val, val2); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VELOCITY: /* m/h to m/s */ + if (chan->channel2 != IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z) + return -EINVAL; + *val = 0; + *val2 = 277; /* 0.000277 */ + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ENERGY: /* Cal or kcal to J */ + *val = 4184; + return IIO_VAL_INT; + case IIO_ACCEL: + return mma9551_read_accel_scale(val, val2); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_ENABLE: + *val = data->stepcnt_enabled; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBHEIGHT: + tmp = mma9553_get_bits(data->conf.height_weight, + MMA9553_MASK_CONF_HEIGHT); + *val = tmp / 100; /* cm to m */ + *val2 = (tmp % 100) * 10000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBWEIGHT: + *val = mma9553_get_bits(data->conf.height_weight, + MMA9553_MASK_CONF_WEIGHT); + return IIO_VAL_INT; + case IIO_CHAN_INFO_DEBOUNCE_COUNT: + switch (chan->type) { + case IIO_STEPS: + *val = mma9553_get_bits(data->conf.filter, + MMA9553_MASK_CONF_FILTSTEP); + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_DEBOUNCE_TIME: + switch (chan->type) { + case IIO_STEPS: + *val = mma9553_get_bits(data->conf.filter, + MMA9553_MASK_CONF_FILTTIME); + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_VELOCITY: + if (chan->channel2 != IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z) + return -EINVAL; + *val = mma9553_get_bits(data->conf.speed_step, + MMA9553_MASK_CONF_SPDPRD); + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int mma9553_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct mma9553_data *data = iio_priv(indio_dev); + int ret, tmp; + + switch (mask) { + case IIO_CHAN_INFO_ENABLE: + if (data->stepcnt_enabled == !!val) + return 0; + mutex_lock(&data->mutex); + ret = mma9551_set_power_state(data->client, val); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + data->stepcnt_enabled = val; + mutex_unlock(&data->mutex); + return 0; + case IIO_CHAN_INFO_CALIBHEIGHT: + /* m to cm */ + tmp = val * 100 + val2 / 10000; + if (tmp < 0 || tmp > 255) + return -EINVAL; + mutex_lock(&data->mutex); + ret = mma9553_set_config(data, + MMA9553_REG_CONF_HEIGHT_WEIGHT, + &data->conf.height_weight, + tmp, MMA9553_MASK_CONF_HEIGHT); + mutex_unlock(&data->mutex); + return ret; + case IIO_CHAN_INFO_CALIBWEIGHT: + if (val < 0 || val > 255) + return -EINVAL; + mutex_lock(&data->mutex); + ret = mma9553_set_config(data, + MMA9553_REG_CONF_HEIGHT_WEIGHT, + &data->conf.height_weight, + val, MMA9553_MASK_CONF_WEIGHT); + mutex_unlock(&data->mutex); + return ret; + case IIO_CHAN_INFO_DEBOUNCE_COUNT: + switch (chan->type) { + case IIO_STEPS: + /* + * Set to 0 to disable step filtering. If the value + * specified is greater than 6, then 6 will be used. + */ + if (val < 0) + return -EINVAL; + if (val > 6) + val = 6; + mutex_lock(&data->mutex); + ret = mma9553_set_config(data, MMA9553_REG_CONF_FILTER, + &data->conf.filter, val, + MMA9553_MASK_CONF_FILTSTEP); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_DEBOUNCE_TIME: + switch (chan->type) { + case IIO_STEPS: + if (val < 0 || val > 127) + return -EINVAL; + mutex_lock(&data->mutex); + ret = mma9553_set_config(data, MMA9553_REG_CONF_FILTER, + &data->conf.filter, val, + MMA9553_MASK_CONF_FILTTIME); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_VELOCITY: + if (chan->channel2 != IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z) + return -EINVAL; + /* + * If set to a value greater than 5, then 5 will be + * used. Warning: Do not set SPDPRD to 0 or 1 as + * this may cause undesirable behavior. + */ + if (val < 2) + return -EINVAL; + if (val > 5) + val = 5; + mutex_lock(&data->mutex); + ret = mma9553_set_config(data, + MMA9553_REG_CONF_SPEED_STEP, + &data->conf.speed_step, val, + MMA9553_MASK_CONF_SPDPRD); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int mma9553_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct mma9553_data *data = iio_priv(indio_dev); + struct mma9553_event *event; + + event = mma9553_get_event(data, chan->type, chan->channel2, dir); + if (!event) + return -EINVAL; + + return event->enabled; +} + +static int mma9553_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct mma9553_data *data = iio_priv(indio_dev); + struct mma9553_event *event; + int ret; + + event = mma9553_get_event(data, chan->type, chan->channel2, dir); + if (!event) + return -EINVAL; + + if (event->enabled == state) + return 0; + + mutex_lock(&data->mutex); + + ret = mma9551_set_power_state(data->client, state); + if (ret < 0) + goto err_out; + event->enabled = state; + + ret = mma9553_conf_gpio(data); + if (ret < 0) + goto err_conf_gpio; + + mutex_unlock(&data->mutex); + + return 0; + +err_conf_gpio: + if (state) { + event->enabled = false; + mma9551_set_power_state(data->client, false); + } +err_out: + mutex_unlock(&data->mutex); + return ret; +} + +static int mma9553_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct mma9553_data *data = iio_priv(indio_dev); + + *val2 = 0; + switch (info) { + case IIO_EV_INFO_VALUE: + switch (chan->type) { + case IIO_STEPS: + *val = mma9553_get_bits(data->conf.speed_step, + MMA9553_MASK_CONF_STEPCOALESCE); + return IIO_VAL_INT; + case IIO_ACTIVITY: + /* + * The device does not support confidence value levels. + * We set an average of 50%. + */ + *val = 50; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_EV_INFO_PERIOD: + switch (chan->type) { + case IIO_ACTIVITY: + *val = MMA9553_ACTIVITY_THD_TO_SEC(data->conf.actthd); + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int mma9553_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct mma9553_data *data = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (chan->type) { + case IIO_STEPS: + if (val < 0 || val > 255) + return -EINVAL; + mutex_lock(&data->mutex); + ret = mma9553_set_config(data, + MMA9553_REG_CONF_SPEED_STEP, + &data->conf.speed_step, val, + MMA9553_MASK_CONF_STEPCOALESCE); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } + case IIO_EV_INFO_PERIOD: + switch (chan->type) { + case IIO_ACTIVITY: + if (val < 0 || val > MMA9553_ACTIVITY_THD_TO_SEC( + MMA9553_MAX_ACTTHD)) + return -EINVAL; + mutex_lock(&data->mutex); + ret = mma9553_set_config(data, MMA9553_REG_CONF_ACTTHD, + &data->conf.actthd, + MMA9553_ACTIVITY_SEC_TO_THD + (val), MMA9553_MASK_CONF_WORD); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int mma9553_get_calibgender_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct mma9553_data *data = iio_priv(indio_dev); + u8 gender; + + gender = mma9553_get_bits(data->conf.filter, MMA9553_MASK_CONF_MALE); + /* + * HW expects 0 for female and 1 for male, + * while iio index is 0 for male and 1 for female. + */ + return !gender; +} + +static int mma9553_set_calibgender_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct mma9553_data *data = iio_priv(indio_dev); + u8 gender = !mode; + int ret; + + if ((mode != 0) && (mode != 1)) + return -EINVAL; + mutex_lock(&data->mutex); + ret = mma9553_set_config(data, MMA9553_REG_CONF_FILTER, + &data->conf.filter, gender, + MMA9553_MASK_CONF_MALE); + mutex_unlock(&data->mutex); + + return ret; +} + +static const struct iio_event_spec mma9553_step_event = { + .type = IIO_EV_TYPE_CHANGE, + .dir = IIO_EV_DIR_NONE, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | BIT(IIO_EV_INFO_VALUE), +}; + +static const struct iio_event_spec mma9553_activity_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD), + }, +}; + +static const char * const mma9553_calibgender_modes[] = { "male", "female" }; + +static const struct iio_enum mma9553_calibgender_enum = { + .items = mma9553_calibgender_modes, + .num_items = ARRAY_SIZE(mma9553_calibgender_modes), + .get = mma9553_get_calibgender_mode, + .set = mma9553_set_calibgender_mode, +}; + +static const struct iio_chan_spec_ext_info mma9553_ext_info[] = { + IIO_ENUM("calibgender", IIO_SHARED_BY_TYPE, &mma9553_calibgender_enum), + IIO_ENUM_AVAILABLE("calibgender", IIO_SHARED_BY_TYPE, &mma9553_calibgender_enum), + { } +}; + +#define MMA9553_PEDOMETER_CHANNEL(_type, _mask) { \ + .type = _type, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | \ + BIT(IIO_CHAN_INFO_CALIBHEIGHT) | \ + _mask, \ + .ext_info = mma9553_ext_info, \ +} + +#define MMA9553_ACTIVITY_CHANNEL(_chan2) { \ + .type = IIO_ACTIVITY, \ + .modified = 1, \ + .channel2 = _chan2, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBHEIGHT) | \ + BIT(IIO_CHAN_INFO_ENABLE), \ + .event_spec = mma9553_activity_events, \ + .num_event_specs = ARRAY_SIZE(mma9553_activity_events), \ + .ext_info = mma9553_ext_info, \ +} + +static const struct iio_chan_spec mma9553_channels[] = { + MMA9551_ACCEL_CHANNEL(IIO_MOD_X), + MMA9551_ACCEL_CHANNEL(IIO_MOD_Y), + MMA9551_ACCEL_CHANNEL(IIO_MOD_Z), + + { + .type = IIO_STEPS, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_DEBOUNCE_COUNT) | + BIT(IIO_CHAN_INFO_DEBOUNCE_TIME), + .event_spec = &mma9553_step_event, + .num_event_specs = 1, + }, + + MMA9553_PEDOMETER_CHANNEL(IIO_DISTANCE, BIT(IIO_CHAN_INFO_PROCESSED)), + { + .type = IIO_VELOCITY, + .modified = 1, + .channel2 = IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_ENABLE), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBHEIGHT), + .ext_info = mma9553_ext_info, + }, + MMA9553_PEDOMETER_CHANNEL(IIO_ENERGY, BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_CALIBWEIGHT)), + + MMA9553_ACTIVITY_CHANNEL(IIO_MOD_RUNNING), + MMA9553_ACTIVITY_CHANNEL(IIO_MOD_JOGGING), + MMA9553_ACTIVITY_CHANNEL(IIO_MOD_WALKING), + MMA9553_ACTIVITY_CHANNEL(IIO_MOD_STILL), +}; + +static const struct iio_info mma9553_info = { + .read_raw = mma9553_read_raw, + .write_raw = mma9553_write_raw, + .read_event_config = mma9553_read_event_config, + .write_event_config = mma9553_write_event_config, + .read_event_value = mma9553_read_event_value, + .write_event_value = mma9553_write_event_value, +}; + +static irqreturn_t mma9553_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct mma9553_data *data = iio_priv(indio_dev); + + data->timestamp = iio_get_time_ns(indio_dev); + /* + * Since we only configure the interrupt pin when an + * event is enabled, we are sure we have at least + * one event enabled at this point. + */ + return IRQ_WAKE_THREAD; +} + +static irqreturn_t mma9553_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct mma9553_data *data = iio_priv(indio_dev); + u16 stepcnt; + u8 activity; + struct mma9553_event *ev_activity, *ev_prev_activity, *ev_step_detect; + int ret; + + mutex_lock(&data->mutex); + ret = mma9553_read_activity_stepcnt(data, &activity, &stepcnt); + if (ret < 0) { + mutex_unlock(&data->mutex); + return IRQ_HANDLED; + } + + ev_prev_activity = mma9553_get_event(data, IIO_ACTIVITY, + mma9553_activity_to_mod( + data->activity), + IIO_EV_DIR_FALLING); + ev_activity = mma9553_get_event(data, IIO_ACTIVITY, + mma9553_activity_to_mod(activity), + IIO_EV_DIR_RISING); + ev_step_detect = mma9553_get_event(data, IIO_STEPS, IIO_NO_MOD, + IIO_EV_DIR_NONE); + + if (ev_step_detect->enabled && (stepcnt != data->stepcnt)) { + data->stepcnt = stepcnt; + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_STEPS, 0, + IIO_EV_TYPE_CHANGE, + IIO_EV_DIR_NONE), + data->timestamp); + } + + if (activity != data->activity) { + data->activity = activity; + /* ev_activity can be NULL if activity == ACTIVITY_UNKNOWN */ + if (ev_prev_activity && ev_prev_activity->enabled) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACTIVITY, 0, + ev_prev_activity->info->mod, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + data->timestamp); + + if (ev_activity && ev_activity->enabled) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACTIVITY, 0, + ev_activity->info->mod, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + data->timestamp); + } + mutex_unlock(&data->mutex); + + return IRQ_HANDLED; +} + +static int mma9553_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct mma9553_data *data; + struct iio_dev *indio_dev; + const char *name = NULL; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + if (id) + name = id->name; + else + name = iio_get_acpi_device_name(&client->dev); + if (!name) + return -ENOSYS; + + mutex_init(&data->mutex); + mma9553_init_events(data); + + ret = mma9553_init(data); + if (ret < 0) + return ret; + + indio_dev->channels = mma9553_channels; + indio_dev->num_channels = ARRAY_SIZE(mma9553_channels); + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &mma9553_info; + + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + mma9553_irq_handler, + mma9553_event_handler, + IRQF_TRIGGER_RISING, + "mma9553_event", indio_dev); + if (ret) + goto out_poweroff; + } + + ret = pm_runtime_set_active(&client->dev); + if (ret < 0) + goto out_poweroff; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, + MMA9551_AUTO_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "unable to register iio device\n"); + goto err_pm_cleanup; + } + + dev_dbg(&indio_dev->dev, "Registered device %s\n", name); + return 0; + +err_pm_cleanup: + pm_runtime_dont_use_autosuspend(&client->dev); + pm_runtime_disable(&client->dev); +out_poweroff: + mma9551_set_device_state(client, false); + return ret; +} + +static void mma9553_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct mma9553_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + mutex_lock(&data->mutex); + mma9551_set_device_state(data->client, false); + mutex_unlock(&data->mutex); +} + +static int mma9553_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma9553_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = mma9551_set_device_state(data->client, false); + mutex_unlock(&data->mutex); + if (ret < 0) { + dev_err(&data->client->dev, "powering off device failed\n"); + return -EAGAIN; + } + + return 0; +} + +static int mma9553_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma9553_data *data = iio_priv(indio_dev); + int ret; + + ret = mma9551_set_device_state(data->client, true); + if (ret < 0) + return ret; + + mma9551_sleep(MMA9553_DEFAULT_SAMPLE_RATE); + + return 0; +} + +static int mma9553_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma9553_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = mma9551_set_device_state(data->client, false); + mutex_unlock(&data->mutex); + + return ret; +} + +static int mma9553_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma9553_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = mma9551_set_device_state(data->client, true); + mutex_unlock(&data->mutex); + + return ret; +} + +static const struct dev_pm_ops mma9553_pm_ops = { + SYSTEM_SLEEP_PM_OPS(mma9553_suspend, mma9553_resume) + RUNTIME_PM_OPS(mma9553_runtime_suspend, mma9553_runtime_resume, NULL) +}; + +static const struct acpi_device_id mma9553_acpi_match[] = { + {"MMA9553", 0}, + { } +}; + +MODULE_DEVICE_TABLE(acpi, mma9553_acpi_match); + +static const struct i2c_device_id mma9553_id[] = { + { .name = "mma9553" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, mma9553_id); + +static struct i2c_driver mma9553_driver = { + .driver = { + .name = "mma9553", + .acpi_match_table = mma9553_acpi_match, + .pm = pm_ptr(&mma9553_pm_ops), + }, + .probe = mma9553_probe, + .remove = mma9553_remove, + .id_table = mma9553_id, +}; + +module_i2c_driver(mma9553_driver); + +MODULE_AUTHOR("Irina Tirdea "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MMA9553L pedometer platform driver"); +MODULE_IMPORT_NS("IIO_MMA9551"); diff --git a/drivers/iio/accel/msa311.c b/drivers/iio/accel/msa311.c new file mode 100644 index 000000000..caf9b1b63 --- /dev/null +++ b/drivers/iio/accel/msa311.c @@ -0,0 +1,1295 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MEMSensing digital 3-Axis accelerometer + * + * MSA311 is a tri-axial, low-g accelerometer with I2C digital output for + * sensitivity consumer applications. It has dynamic user-selectable full + * scales range of +-2g/+-4g/+-8g/+-16g and allows acceleration measurements + * with output data rates from 1Hz to 1000Hz. + * + * MSA311 is available in an ultra small (2mm x 2mm, height 0.95mm) LGA package + * and is guaranteed to operate over -40C to +85C. + * + * This driver supports following MSA311 features: + * - IIO interface + * - Different power modes: NORMAL, SUSPEND + * - ODR (Output Data Rate) selection + * - Scale selection + * - IIO triggered buffer + * - NEW_DATA interrupt + trigger + * + * Below features to be done: + * - Motion Events: ACTIVE, TAP, ORIENT, FREEFALL + * - Low Power mode + * + * Copyright (c) 2022, SberDevices. All Rights Reserved. + * + * Author: Dmitry Rokosov + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define MSA311_SOFT_RESET_REG 0x00 +#define MSA311_PARTID_REG 0x01 +#define MSA311_ACC_X_REG 0x02 +#define MSA311_ACC_Y_REG 0x04 +#define MSA311_ACC_Z_REG 0x06 +#define MSA311_MOTION_INT_REG 0x09 +#define MSA311_DATA_INT_REG 0x0A +#define MSA311_TAP_ACTIVE_STS_REG 0x0B +#define MSA311_ORIENT_STS_REG 0x0C +#define MSA311_RANGE_REG 0x0F +#define MSA311_ODR_REG 0x10 +#define MSA311_PWR_MODE_REG 0x11 +#define MSA311_SWAP_POLARITY_REG 0x12 +#define MSA311_INT_SET_0_REG 0x16 +#define MSA311_INT_SET_1_REG 0x17 +#define MSA311_INT_MAP_0_REG 0x19 +#define MSA311_INT_MAP_1_REG 0x1A +#define MSA311_INT_CONFIG_REG 0x20 +#define MSA311_INT_LATCH_REG 0x21 +#define MSA311_FREEFALL_DUR_REG 0x22 +#define MSA311_FREEFALL_TH_REG 0x23 +#define MSA311_FREEFALL_HY_REG 0x24 +#define MSA311_ACTIVE_DUR_REG 0x27 +#define MSA311_ACTIVE_TH_REG 0x28 +#define MSA311_TAP_DUR_REG 0x2A +#define MSA311_TAP_TH_REG 0x2B +#define MSA311_ORIENT_HY_REG 0x2C +#define MSA311_Z_BLOCK_REG 0x2D +#define MSA311_OFFSET_X_REG 0x38 +#define MSA311_OFFSET_Y_REG 0x39 +#define MSA311_OFFSET_Z_REG 0x3A + +enum msa311_fields { + /* Soft_Reset */ + F_SOFT_RESET_I2C, F_SOFT_RESET_SPI, + /* Motion_Interrupt */ + F_ORIENT_INT, F_S_TAP_INT, F_D_TAP_INT, F_ACTIVE_INT, F_FREEFALL_INT, + /* Data_Interrupt */ + F_NEW_DATA_INT, + /* Tap_Active_Status */ + F_TAP_SIGN, F_TAP_FIRST_X, F_TAP_FIRST_Y, F_TAP_FIRST_Z, F_ACTV_SIGN, + F_ACTV_FIRST_X, F_ACTV_FIRST_Y, F_ACTV_FIRST_Z, + /* Orientation_Status */ + F_ORIENT_Z, F_ORIENT_X_Y, + /* Range */ + F_FS, + /* ODR */ + F_X_AXIS_DIS, F_Y_AXIS_DIS, F_Z_AXIS_DIS, F_ODR, + /* Power Mode/Bandwidth */ + F_PWR_MODE, F_LOW_POWER_BW, + /* Swap_Polarity */ + F_X_POLARITY, F_Y_POLARITY, F_Z_POLARITY, F_X_Y_SWAP, + /* Int_Set_0 */ + F_ORIENT_INT_EN, F_S_TAP_INT_EN, F_D_TAP_INT_EN, F_ACTIVE_INT_EN_Z, + F_ACTIVE_INT_EN_Y, F_ACTIVE_INT_EN_X, + /* Int_Set_1 */ + F_NEW_DATA_INT_EN, F_FREEFALL_INT_EN, + /* Int_Map_0 */ + F_INT1_ORIENT, F_INT1_S_TAP, F_INT1_D_TAP, F_INT1_ACTIVE, + F_INT1_FREEFALL, + /* Int_Map_1 */ + F_INT1_NEW_DATA, + /* Int_Config */ + F_INT1_OD, F_INT1_LVL, + /* Int_Latch */ + F_RESET_INT, F_LATCH_INT, + /* Freefall_Hy */ + F_FREEFALL_MODE, F_FREEFALL_HY, + /* Active_Dur */ + F_ACTIVE_DUR, + /* Tap_Dur */ + F_TAP_QUIET, F_TAP_SHOCK, F_TAP_DUR, + /* Tap_Th */ + F_TAP_TH, + /* Orient_Hy */ + F_ORIENT_HYST, F_ORIENT_BLOCKING, F_ORIENT_MODE, + /* Z_Block */ + F_Z_BLOCKING, + /* End of register map */ + F_MAX_FIELDS, +}; + +static const struct reg_field msa311_reg_fields[] = { + /* Soft_Reset */ + [F_SOFT_RESET_I2C] = REG_FIELD(MSA311_SOFT_RESET_REG, 2, 2), + [F_SOFT_RESET_SPI] = REG_FIELD(MSA311_SOFT_RESET_REG, 5, 5), + /* Motion_Interrupt */ + [F_ORIENT_INT] = REG_FIELD(MSA311_MOTION_INT_REG, 6, 6), + [F_S_TAP_INT] = REG_FIELD(MSA311_MOTION_INT_REG, 5, 5), + [F_D_TAP_INT] = REG_FIELD(MSA311_MOTION_INT_REG, 4, 4), + [F_ACTIVE_INT] = REG_FIELD(MSA311_MOTION_INT_REG, 2, 2), + [F_FREEFALL_INT] = REG_FIELD(MSA311_MOTION_INT_REG, 0, 0), + /* Data_Interrupt */ + [F_NEW_DATA_INT] = REG_FIELD(MSA311_DATA_INT_REG, 0, 0), + /* Tap_Active_Status */ + [F_TAP_SIGN] = REG_FIELD(MSA311_TAP_ACTIVE_STS_REG, 7, 7), + [F_TAP_FIRST_X] = REG_FIELD(MSA311_TAP_ACTIVE_STS_REG, 6, 6), + [F_TAP_FIRST_Y] = REG_FIELD(MSA311_TAP_ACTIVE_STS_REG, 5, 5), + [F_TAP_FIRST_Z] = REG_FIELD(MSA311_TAP_ACTIVE_STS_REG, 4, 4), + [F_ACTV_SIGN] = REG_FIELD(MSA311_TAP_ACTIVE_STS_REG, 3, 3), + [F_ACTV_FIRST_X] = REG_FIELD(MSA311_TAP_ACTIVE_STS_REG, 2, 2), + [F_ACTV_FIRST_Y] = REG_FIELD(MSA311_TAP_ACTIVE_STS_REG, 1, 1), + [F_ACTV_FIRST_Z] = REG_FIELD(MSA311_TAP_ACTIVE_STS_REG, 0, 0), + /* Orientation_Status */ + [F_ORIENT_Z] = REG_FIELD(MSA311_ORIENT_STS_REG, 6, 6), + [F_ORIENT_X_Y] = REG_FIELD(MSA311_ORIENT_STS_REG, 4, 5), + /* Range */ + [F_FS] = REG_FIELD(MSA311_RANGE_REG, 0, 1), + /* ODR */ + [F_X_AXIS_DIS] = REG_FIELD(MSA311_ODR_REG, 7, 7), + [F_Y_AXIS_DIS] = REG_FIELD(MSA311_ODR_REG, 6, 6), + [F_Z_AXIS_DIS] = REG_FIELD(MSA311_ODR_REG, 5, 5), + [F_ODR] = REG_FIELD(MSA311_ODR_REG, 0, 3), + /* Power Mode/Bandwidth */ + [F_PWR_MODE] = REG_FIELD(MSA311_PWR_MODE_REG, 6, 7), + [F_LOW_POWER_BW] = REG_FIELD(MSA311_PWR_MODE_REG, 1, 4), + /* Swap_Polarity */ + [F_X_POLARITY] = REG_FIELD(MSA311_SWAP_POLARITY_REG, 3, 3), + [F_Y_POLARITY] = REG_FIELD(MSA311_SWAP_POLARITY_REG, 2, 2), + [F_Z_POLARITY] = REG_FIELD(MSA311_SWAP_POLARITY_REG, 1, 1), + [F_X_Y_SWAP] = REG_FIELD(MSA311_SWAP_POLARITY_REG, 0, 0), + /* Int_Set_0 */ + [F_ORIENT_INT_EN] = REG_FIELD(MSA311_INT_SET_0_REG, 6, 6), + [F_S_TAP_INT_EN] = REG_FIELD(MSA311_INT_SET_0_REG, 5, 5), + [F_D_TAP_INT_EN] = REG_FIELD(MSA311_INT_SET_0_REG, 4, 4), + [F_ACTIVE_INT_EN_Z] = REG_FIELD(MSA311_INT_SET_0_REG, 2, 2), + [F_ACTIVE_INT_EN_Y] = REG_FIELD(MSA311_INT_SET_0_REG, 1, 1), + [F_ACTIVE_INT_EN_X] = REG_FIELD(MSA311_INT_SET_0_REG, 0, 0), + /* Int_Set_1 */ + [F_NEW_DATA_INT_EN] = REG_FIELD(MSA311_INT_SET_1_REG, 4, 4), + [F_FREEFALL_INT_EN] = REG_FIELD(MSA311_INT_SET_1_REG, 3, 3), + /* Int_Map_0 */ + [F_INT1_ORIENT] = REG_FIELD(MSA311_INT_MAP_0_REG, 6, 6), + [F_INT1_S_TAP] = REG_FIELD(MSA311_INT_MAP_0_REG, 5, 5), + [F_INT1_D_TAP] = REG_FIELD(MSA311_INT_MAP_0_REG, 4, 4), + [F_INT1_ACTIVE] = REG_FIELD(MSA311_INT_MAP_0_REG, 2, 2), + [F_INT1_FREEFALL] = REG_FIELD(MSA311_INT_MAP_0_REG, 0, 0), + /* Int_Map_1 */ + [F_INT1_NEW_DATA] = REG_FIELD(MSA311_INT_MAP_1_REG, 0, 0), + /* Int_Config */ + [F_INT1_OD] = REG_FIELD(MSA311_INT_CONFIG_REG, 1, 1), + [F_INT1_LVL] = REG_FIELD(MSA311_INT_CONFIG_REG, 0, 0), + /* Int_Latch */ + [F_RESET_INT] = REG_FIELD(MSA311_INT_LATCH_REG, 7, 7), + [F_LATCH_INT] = REG_FIELD(MSA311_INT_LATCH_REG, 0, 3), + /* Freefall_Hy */ + [F_FREEFALL_MODE] = REG_FIELD(MSA311_FREEFALL_HY_REG, 2, 2), + [F_FREEFALL_HY] = REG_FIELD(MSA311_FREEFALL_HY_REG, 0, 1), + /* Active_Dur */ + [F_ACTIVE_DUR] = REG_FIELD(MSA311_ACTIVE_DUR_REG, 0, 1), + /* Tap_Dur */ + [F_TAP_QUIET] = REG_FIELD(MSA311_TAP_DUR_REG, 7, 7), + [F_TAP_SHOCK] = REG_FIELD(MSA311_TAP_DUR_REG, 6, 6), + [F_TAP_DUR] = REG_FIELD(MSA311_TAP_DUR_REG, 0, 2), + /* Tap_Th */ + [F_TAP_TH] = REG_FIELD(MSA311_TAP_TH_REG, 0, 4), + /* Orient_Hy */ + [F_ORIENT_HYST] = REG_FIELD(MSA311_ORIENT_HY_REG, 4, 6), + [F_ORIENT_BLOCKING] = REG_FIELD(MSA311_ORIENT_HY_REG, 2, 3), + [F_ORIENT_MODE] = REG_FIELD(MSA311_ORIENT_HY_REG, 0, 1), + /* Z_Block */ + [F_Z_BLOCKING] = REG_FIELD(MSA311_Z_BLOCK_REG, 0, 3), +}; + +#define MSA311_WHO_AM_I 0x13 + +/* + * Possible Full Scale ranges + * + * Axis data is 12-bit signed value, so + * + * fs0 = (2 + 2) * 9.81 / (2^11) = 0.009580 + * fs1 = (4 + 4) * 9.81 / (2^11) = 0.019160 + * fs2 = (8 + 8) * 9.81 / (2^11) = 0.038320 + * fs3 = (16 + 16) * 9.81 / (2^11) = 0.076641 + */ +enum { + MSA311_FS_2G, + MSA311_FS_4G, + MSA311_FS_8G, + MSA311_FS_16G, +}; + +struct iio_decimal_fract { + int integral; + int microfract; +}; + +static const struct iio_decimal_fract msa311_fs_table[] = { + {0, 9580}, {0, 19160}, {0, 38320}, {0, 76641}, +}; + +/* Possible Output Data Rate values */ +enum { + MSA311_ODR_1_HZ, + MSA311_ODR_1_95_HZ, + MSA311_ODR_3_9_HZ, + MSA311_ODR_7_81_HZ, + MSA311_ODR_15_63_HZ, + MSA311_ODR_31_25_HZ, + MSA311_ODR_62_5_HZ, + MSA311_ODR_125_HZ, + MSA311_ODR_250_HZ, + MSA311_ODR_500_HZ, + MSA311_ODR_1000_HZ, +}; + +static const struct iio_decimal_fract msa311_odr_table[] = { + {1, 0}, {1, 950000}, {3, 900000}, {7, 810000}, {15, 630000}, + {31, 250000}, {62, 500000}, {125, 0}, {250, 0}, {500, 0}, {1000, 0}, +}; + +/* All supported power modes */ +#define MSA311_PWR_MODE_NORMAL 0b00 +#define MSA311_PWR_MODE_LOW 0b01 +#define MSA311_PWR_MODE_UNKNOWN 0b10 +#define MSA311_PWR_MODE_SUSPEND 0b11 +static const char * const msa311_pwr_modes[] = { + [MSA311_PWR_MODE_NORMAL] = "normal", + [MSA311_PWR_MODE_LOW] = "low", + [MSA311_PWR_MODE_UNKNOWN] = "unknown", + [MSA311_PWR_MODE_SUSPEND] = "suspend", +}; + +/* Autosuspend delay */ +#define MSA311_PWR_SLEEP_DELAY_MS 2000 + +/* Possible INT1 types and levels */ +enum { + MSA311_INT1_OD_PUSH_PULL, + MSA311_INT1_OD_OPEN_DRAIN, +}; + +enum { + MSA311_INT1_LVL_LOW, + MSA311_INT1_LVL_HIGH, +}; + +/* Latch INT modes */ +#define MSA311_LATCH_INT_NOT_LATCHED 0b0000 +#define MSA311_LATCH_INT_250MS 0b0001 +#define MSA311_LATCH_INT_500MS 0b0010 +#define MSA311_LATCH_INT_1S 0b0011 +#define MSA311_LATCH_INT_2S 0b0100 +#define MSA311_LATCH_INT_4S 0b0101 +#define MSA311_LATCH_INT_8S 0b0110 +#define MSA311_LATCH_INT_1MS 0b1010 +#define MSA311_LATCH_INT_2MS 0b1011 +#define MSA311_LATCH_INT_25MS 0b1100 +#define MSA311_LATCH_INT_50MS 0b1101 +#define MSA311_LATCH_INT_100MS 0b1110 +#define MSA311_LATCH_INT_LATCHED 0b0111 + +static const struct regmap_range msa311_readonly_registers[] = { + regmap_reg_range(MSA311_PARTID_REG, MSA311_ORIENT_STS_REG), +}; + +static const struct regmap_access_table msa311_writeable_table = { + .no_ranges = msa311_readonly_registers, + .n_no_ranges = ARRAY_SIZE(msa311_readonly_registers), +}; + +static const struct regmap_range msa311_writeonly_registers[] = { + regmap_reg_range(MSA311_SOFT_RESET_REG, MSA311_SOFT_RESET_REG), +}; + +static const struct regmap_access_table msa311_readable_table = { + .no_ranges = msa311_writeonly_registers, + .n_no_ranges = ARRAY_SIZE(msa311_writeonly_registers), +}; + +static const struct regmap_range msa311_volatile_registers[] = { + regmap_reg_range(MSA311_ACC_X_REG, MSA311_ORIENT_STS_REG), +}; + +static const struct regmap_access_table msa311_volatile_table = { + .yes_ranges = msa311_volatile_registers, + .n_yes_ranges = ARRAY_SIZE(msa311_volatile_registers), +}; + +static const struct regmap_config msa311_regmap_config = { + .name = "msa311", + .reg_bits = 8, + .val_bits = 8, + .max_register = MSA311_OFFSET_Z_REG, + .wr_table = &msa311_writeable_table, + .rd_table = &msa311_readable_table, + .volatile_table = &msa311_volatile_table, + .cache_type = REGCACHE_MAPLE, +}; + +#define MSA311_GENMASK(field) ({ \ + typeof(&(msa311_reg_fields)[0]) _field; \ + _field = &msa311_reg_fields[(field)]; \ + GENMASK(_field->msb, _field->lsb); \ +}) + +/** + * struct msa311_priv - MSA311 internal private state + * @regs: Underlying I2C bus adapter used to abstract slave + * register accesses + * @fields: Abstract objects for each registers fields access + * @dev: Device handler associated with appropriate bus client + * @lock: Protects msa311 device state between setup and data access routines + * (power transitions, samp_freq/scale tune, retrieving axes data, etc) + * @chip_name: Chip name in the format "msa311-%02x" % partid + * @new_data_trig: Optional NEW_DATA interrupt driven trigger used + * to notify external consumers a new sample is ready + */ +struct msa311_priv { + struct regmap *regs; + struct regmap_field *fields[F_MAX_FIELDS]; + + struct device *dev; + struct mutex lock; + char *chip_name; + + struct iio_trigger *new_data_trig; +}; + +enum msa311_si { + MSA311_SI_X, + MSA311_SI_Y, + MSA311_SI_Z, + MSA311_SI_TIMESTAMP, +}; + +#define MSA311_ACCEL_CHANNEL(axis) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = MSA311_SI_##axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 12, \ + .storagebits = 16, \ + .shift = 4, \ + .endianness = IIO_LE, \ + }, \ + .datasheet_name = "ACC_"#axis, \ +} + +static const struct iio_chan_spec msa311_channels[] = { + MSA311_ACCEL_CHANNEL(X), + MSA311_ACCEL_CHANNEL(Y), + MSA311_ACCEL_CHANNEL(Z), + IIO_CHAN_SOFT_TIMESTAMP(MSA311_SI_TIMESTAMP), +}; + +/** + * msa311_get_odr() - Read Output Data Rate (ODR) value from MSA311 accel + * @msa311: MSA311 internal private state + * @odr: output ODR value + * + * This function should be called under msa311->lock. + * + * Return: 0 on success, -ERRNO in other failures + */ +static int msa311_get_odr(struct msa311_priv *msa311, unsigned int *odr) +{ + int err; + + err = regmap_field_read(msa311->fields[F_ODR], odr); + if (err) + return err; + + /* + * Filter the same 1000Hz ODR register values based on datasheet info. + * ODR can be equal to 1010-1111 for 1000Hz, but function returns 1010 + * all the time. + */ + if (*odr > MSA311_ODR_1000_HZ) + *odr = MSA311_ODR_1000_HZ; + + return 0; +} + +/** + * msa311_set_odr() - Setup Output Data Rate (ODR) value for MSA311 accel + * @msa311: MSA311 internal private state + * @odr: requested ODR value + * + * This function should be called under msa311->lock. Possible ODR values: + * - 1Hz (not available in normal mode) + * - 1.95Hz (not available in normal mode) + * - 3.9Hz + * - 7.81Hz + * - 15.63Hz + * - 31.25Hz + * - 62.5Hz + * - 125Hz + * - 250Hz + * - 500Hz + * - 1000Hz + * + * Return: 0 on success, -EINVAL for bad ODR value in the certain power mode, + * -ERRNO in other failures + */ +static int msa311_set_odr(struct msa311_priv *msa311, unsigned int odr) +{ + struct device *dev = msa311->dev; + unsigned int pwr_mode; + bool good_odr; + int err; + + err = regmap_field_read(msa311->fields[F_PWR_MODE], &pwr_mode); + if (err) + return err; + + /* Filter bad ODR values */ + if (pwr_mode == MSA311_PWR_MODE_NORMAL) + good_odr = (odr > MSA311_ODR_1_95_HZ); + else + good_odr = false; + + if (!good_odr) { + dev_err(dev, + "can't set odr %u.%06uHz, not available in %s mode\n", + msa311_odr_table[odr].integral, + msa311_odr_table[odr].microfract, + msa311_pwr_modes[pwr_mode]); + return -EINVAL; + } + + return regmap_field_write(msa311->fields[F_ODR], odr); +} + +/** + * msa311_wait_for_next_data() - Wait next accel data available after resume + * @msa311: MSA311 internal private state + * + * Return: 0 on success, -EINTR if msleep() was interrupted, + * -ERRNO in other failures + */ +static int msa311_wait_for_next_data(struct msa311_priv *msa311) +{ + static const unsigned int unintr_thresh_ms = 20; + struct device *dev = msa311->dev; + unsigned long freq_uhz; + unsigned long wait_ms; + unsigned int odr; + int err; + + err = msa311_get_odr(msa311, &odr); + if (err) { + dev_err(dev, "can't get actual frequency (%pe)\n", + ERR_PTR(err)); + return err; + } + + /* + * After msa311 resuming is done, we need to wait for data + * to be refreshed by accel logic. + * A certain timeout is calculated based on the current ODR value. + * If requested timeout isn't so long (let's assume 20ms), + * we can wait for next data in uninterruptible sleep. + */ + freq_uhz = msa311_odr_table[odr].integral * MICROHZ_PER_HZ + + msa311_odr_table[odr].microfract; + wait_ms = (MICROHZ_PER_HZ / freq_uhz) * MSEC_PER_SEC; + + if (wait_ms < unintr_thresh_ms) + usleep_range(wait_ms * USEC_PER_MSEC, + unintr_thresh_ms * USEC_PER_MSEC); + else if (msleep_interruptible(wait_ms)) + return -EINTR; + + return 0; +} + +/** + * msa311_set_pwr_mode() - Install certain MSA311 power mode + * @msa311: MSA311 internal private state + * @mode: Power mode can be equal to NORMAL or SUSPEND + * + * This function should be called under msa311->lock. + * + * Return: 0 on success, -ERRNO on failure + */ +static int msa311_set_pwr_mode(struct msa311_priv *msa311, unsigned int mode) +{ + struct device *dev = msa311->dev; + unsigned int prev_mode; + int err; + + if (mode >= ARRAY_SIZE(msa311_pwr_modes)) + return -EINVAL; + + dev_dbg(dev, "transition to %s mode\n", msa311_pwr_modes[mode]); + + err = regmap_field_read(msa311->fields[F_PWR_MODE], &prev_mode); + if (err) + return err; + + err = regmap_field_write(msa311->fields[F_PWR_MODE], mode); + if (err) + return err; + + /* Wait actual data if we wake up */ + if (prev_mode == MSA311_PWR_MODE_SUSPEND && + mode == MSA311_PWR_MODE_NORMAL) + return msa311_wait_for_next_data(msa311); + + return 0; +} + +/** + * msa311_get_axis() - Read MSA311 accel data for certain IIO channel axis spec + * @msa311: MSA311 internal private state + * @chan: IIO channel specification + * @axis: Output accel axis data for requested IIO channel spec + * + * This function should be called under msa311->lock. + * + * Return: 0 on success, -EINVAL for unknown IIO channel specification, + * -ERRNO in other failures + */ +static int msa311_get_axis(struct msa311_priv *msa311, + const struct iio_chan_spec * const chan, + __le16 *axis) +{ + struct device *dev = msa311->dev; + unsigned int axis_reg; + + if (chan->scan_index < MSA311_SI_X || chan->scan_index > MSA311_SI_Z) { + dev_err(dev, "invalid scan_index value [%d]\n", + chan->scan_index); + return -EINVAL; + } + + /* Axes data layout has 2 byte gap for each axis starting from X axis */ + axis_reg = MSA311_ACC_X_REG + (chan->scan_index << 1); + + return regmap_bulk_read(msa311->regs, axis_reg, axis, sizeof(*axis)); +} + +static int msa311_read_raw_data(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + struct msa311_priv *msa311 = iio_priv(indio_dev); + struct device *dev = msa311->dev; + __le16 axis; + int err; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + err = pm_runtime_resume_and_get(dev); + if (err) { + iio_device_release_direct(indio_dev); + return err; + } + + mutex_lock(&msa311->lock); + err = msa311_get_axis(msa311, chan, &axis); + mutex_unlock(&msa311->lock); + + pm_runtime_put_autosuspend(dev); + + iio_device_release_direct(indio_dev); + + if (err) { + dev_err(dev, "can't get axis %s (%pe)\n", + chan->datasheet_name, ERR_PTR(err)); + return err; + } + + /* + * Axis data format is: + * ACC_X = (ACC_X_MSB[7:0] << 4) | ACC_X_LSB[7:4] + */ + *val = sign_extend32(le16_to_cpu(axis) >> chan->scan_type.shift, + chan->scan_type.realbits - 1); + + return IIO_VAL_INT; +} + +static int msa311_read_scale(struct iio_dev *indio_dev, int *val, int *val2) +{ + struct msa311_priv *msa311 = iio_priv(indio_dev); + struct device *dev = msa311->dev; + unsigned int fs; + int err; + + mutex_lock(&msa311->lock); + err = regmap_field_read(msa311->fields[F_FS], &fs); + mutex_unlock(&msa311->lock); + if (err) { + dev_err(dev, "can't get actual scale (%pe)\n", ERR_PTR(err)); + return err; + } + + *val = msa311_fs_table[fs].integral; + *val2 = msa311_fs_table[fs].microfract; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int msa311_read_samp_freq(struct iio_dev *indio_dev, + int *val, int *val2) +{ + struct msa311_priv *msa311 = iio_priv(indio_dev); + struct device *dev = msa311->dev; + unsigned int odr; + int err; + + mutex_lock(&msa311->lock); + err = msa311_get_odr(msa311, &odr); + mutex_unlock(&msa311->lock); + if (err) { + dev_err(dev, "can't get actual frequency (%pe)\n", + ERR_PTR(err)); + return err; + } + + *val = msa311_odr_table[odr].integral; + *val2 = msa311_odr_table[odr].microfract; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int msa311_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_RAW: + return msa311_read_raw_data(indio_dev, chan, val, val2); + + case IIO_CHAN_INFO_SCALE: + return msa311_read_scale(indio_dev, val, val2); + + case IIO_CHAN_INFO_SAMP_FREQ: + return msa311_read_samp_freq(indio_dev, val, val2); + + default: + return -EINVAL; + } +} + +static int msa311_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, + int *length, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = (int *)msa311_odr_table; + *type = IIO_VAL_INT_PLUS_MICRO; + /* ODR value has 2 ints (integer and fractional parts) */ + *length = ARRAY_SIZE(msa311_odr_table) * 2; + return IIO_AVAIL_LIST; + + case IIO_CHAN_INFO_SCALE: + *vals = (int *)msa311_fs_table; + *type = IIO_VAL_INT_PLUS_MICRO; + /* FS value has 2 ints (integer and fractional parts) */ + *length = ARRAY_SIZE(msa311_fs_table) * 2; + return IIO_AVAIL_LIST; + + default: + return -EINVAL; + } +} + +static int msa311_write_scale(struct iio_dev *indio_dev, int val, int val2) +{ + struct msa311_priv *msa311 = iio_priv(indio_dev); + struct device *dev = msa311->dev; + unsigned int fs; + int err; + + /* We do not have fs >= 1, so skip such values */ + if (val) + return 0; + + err = pm_runtime_resume_and_get(dev); + if (err) + return err; + + err = -EINVAL; + for (fs = 0; fs < ARRAY_SIZE(msa311_fs_table); fs++) + /* Do not check msa311_fs_table[fs].integral, it's always 0 */ + if (val2 == msa311_fs_table[fs].microfract) { + mutex_lock(&msa311->lock); + err = regmap_field_write(msa311->fields[F_FS], fs); + mutex_unlock(&msa311->lock); + break; + } + + pm_runtime_put_autosuspend(dev); + + if (err) + dev_err(dev, "can't update scale (%pe)\n", ERR_PTR(err)); + + return err; +} + +static int msa311_write_samp_freq(struct iio_dev *indio_dev, int val, int val2) +{ + struct msa311_priv *msa311 = iio_priv(indio_dev); + struct device *dev = msa311->dev; + unsigned int odr; + int err; + + /* + * Sampling frequency changing is prohibited when buffer mode is + * enabled, because sometimes MSA311 chip returns outliers during + * frequency values growing up in the read operation moment. + */ + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + err = pm_runtime_resume_and_get(dev); + if (err) { + iio_device_release_direct(indio_dev); + return err; + } + + err = -EINVAL; + for (odr = 0; odr < ARRAY_SIZE(msa311_odr_table); odr++) + if (val == msa311_odr_table[odr].integral && + val2 == msa311_odr_table[odr].microfract) { + mutex_lock(&msa311->lock); + err = msa311_set_odr(msa311, odr); + mutex_unlock(&msa311->lock); + break; + } + + pm_runtime_put_autosuspend(dev); + + iio_device_release_direct(indio_dev); + + if (err) + dev_err(dev, "can't update frequency (%pe)\n", ERR_PTR(err)); + + return err; +} + +static int msa311_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return msa311_write_scale(indio_dev, val, val2); + + case IIO_CHAN_INFO_SAMP_FREQ: + return msa311_write_samp_freq(indio_dev, val, val2); + + default: + return -EINVAL; + } +} + +static int msa311_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + struct msa311_priv *msa311 = iio_priv(indio_dev); + struct device *dev = msa311->dev; + int err; + + if (reg > regmap_get_max_register(msa311->regs)) + return -EINVAL; + + err = pm_runtime_resume_and_get(dev); + if (err) + return err; + + mutex_lock(&msa311->lock); + + if (readval) + err = regmap_read(msa311->regs, reg, readval); + else + err = regmap_write(msa311->regs, reg, writeval); + + mutex_unlock(&msa311->lock); + + pm_runtime_put_autosuspend(dev); + + if (err) + dev_err(dev, "can't %s register %u from debugfs (%pe)\n", + str_read_write(readval), reg, ERR_PTR(err)); + + return err; +} + +static int msa311_buffer_preenable(struct iio_dev *indio_dev) +{ + struct msa311_priv *msa311 = iio_priv(indio_dev); + struct device *dev = msa311->dev; + + return pm_runtime_resume_and_get(dev); +} + +static int msa311_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct msa311_priv *msa311 = iio_priv(indio_dev); + struct device *dev = msa311->dev; + + pm_runtime_put_autosuspend(dev); + + return 0; +} + +static int msa311_set_new_data_trig_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct msa311_priv *msa311 = iio_priv(indio_dev); + struct device *dev = msa311->dev; + int err; + + mutex_lock(&msa311->lock); + err = regmap_field_write(msa311->fields[F_NEW_DATA_INT_EN], state); + mutex_unlock(&msa311->lock); + if (err) + dev_err(dev, + "can't %s buffer due to new_data_int failure (%pe)\n", + str_enable_disable(state), ERR_PTR(err)); + + return err; +} + +static int msa311_validate_device(struct iio_trigger *trig, + struct iio_dev *indio_dev) +{ + return iio_trigger_get_drvdata(trig) == indio_dev ? 0 : -EINVAL; +} + +static irqreturn_t msa311_buffer_thread(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct msa311_priv *msa311 = iio_priv(pf->indio_dev); + struct iio_dev *indio_dev = pf->indio_dev; + const struct iio_chan_spec *chan; + struct device *dev = msa311->dev; + int bit, err, i = 0; + __le16 axis; + struct { + __le16 channels[MSA311_SI_Z + 1]; + aligned_s64 ts; + } buf = { }; + + mutex_lock(&msa311->lock); + + iio_for_each_active_channel(indio_dev, bit) { + chan = &msa311_channels[bit]; + + err = msa311_get_axis(msa311, chan, &axis); + if (err) { + mutex_unlock(&msa311->lock); + dev_err(dev, "can't get axis %s (%pe)\n", + chan->datasheet_name, ERR_PTR(err)); + goto notify_done; + } + + buf.channels[i++] = axis; + } + + mutex_unlock(&msa311->lock); + + iio_push_to_buffers_with_ts(indio_dev, &buf, sizeof(buf), + iio_get_time_ns(indio_dev)); + +notify_done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static irqreturn_t msa311_irq_thread(int irq, void *p) +{ + struct msa311_priv *msa311 = iio_priv(p); + unsigned int new_data_int_enabled; + struct device *dev = msa311->dev; + int err; + + mutex_lock(&msa311->lock); + + /* + * We do not check NEW_DATA int status, because based on the + * specification it's cleared automatically after a fixed time. + * So just check that is enabled by driver logic. + */ + err = regmap_field_read(msa311->fields[F_NEW_DATA_INT_EN], + &new_data_int_enabled); + + mutex_unlock(&msa311->lock); + if (err) { + dev_err(dev, "can't read new_data interrupt state (%pe)\n", + ERR_PTR(err)); + return IRQ_NONE; + } + + if (new_data_int_enabled) + iio_trigger_poll_nested(msa311->new_data_trig); + + return IRQ_HANDLED; +} + +static const struct iio_info msa311_info = { + .read_raw = msa311_read_raw, + .read_avail = msa311_read_avail, + .write_raw = msa311_write_raw, + .debugfs_reg_access = msa311_debugfs_reg_access, +}; + +static const struct iio_buffer_setup_ops msa311_buffer_setup_ops = { + .preenable = msa311_buffer_preenable, + .postdisable = msa311_buffer_postdisable, +}; + +static const struct iio_trigger_ops msa311_new_data_trig_ops = { + .set_trigger_state = msa311_set_new_data_trig_state, + .validate_device = msa311_validate_device, +}; + +static int msa311_check_partid(struct msa311_priv *msa311) +{ + struct device *dev = msa311->dev; + unsigned int partid; + int err; + + err = regmap_read(msa311->regs, MSA311_PARTID_REG, &partid); + if (err) + return dev_err_probe(dev, err, "failed to read partid\n"); + + if (partid != MSA311_WHO_AM_I) + dev_warn(dev, "invalid partid (%#x), expected (%#x)\n", + partid, MSA311_WHO_AM_I); + + msa311->chip_name = devm_kasprintf(dev, GFP_KERNEL, + "msa311-%02x", partid); + if (!msa311->chip_name) + return -ENOMEM; + + return 0; +} + +static int msa311_soft_reset(struct msa311_priv *msa311) +{ + struct device *dev = msa311->dev; + int err; + + err = regmap_write(msa311->regs, MSA311_SOFT_RESET_REG, + MSA311_GENMASK(F_SOFT_RESET_I2C) | + MSA311_GENMASK(F_SOFT_RESET_SPI)); + if (err) + return dev_err_probe(dev, err, "can't soft reset all logic\n"); + + return 0; +} + +static int msa311_chip_init(struct msa311_priv *msa311) +{ + struct device *dev = msa311->dev; + const char zero_bulk[2] = { }; + int err; + + err = regmap_write(msa311->regs, MSA311_RANGE_REG, MSA311_FS_16G); + if (err) + return dev_err_probe(dev, err, "failed to setup accel range\n"); + + /* Disable all interrupts by default */ + err = regmap_bulk_write(msa311->regs, MSA311_INT_SET_0_REG, + zero_bulk, sizeof(zero_bulk)); + if (err) + return dev_err_probe(dev, err, + "can't disable set0/set1 interrupts\n"); + + /* Unmap all INT1 interrupts by default */ + err = regmap_bulk_write(msa311->regs, MSA311_INT_MAP_0_REG, + zero_bulk, sizeof(zero_bulk)); + if (err) + return dev_err_probe(dev, err, + "failed to unmap map0/map1 interrupts\n"); + + /* Disable all axes by default */ + err = regmap_clear_bits(msa311->regs, MSA311_ODR_REG, + MSA311_GENMASK(F_X_AXIS_DIS) | + MSA311_GENMASK(F_Y_AXIS_DIS) | + MSA311_GENMASK(F_Z_AXIS_DIS)); + if (err) + return dev_err_probe(dev, err, "can't enable all axes\n"); + + err = msa311_set_odr(msa311, MSA311_ODR_125_HZ); + if (err) + return dev_err_probe(dev, err, + "failed to set accel frequency\n"); + + return 0; +} + +static int msa311_setup_interrupts(struct msa311_priv *msa311) +{ + struct device *dev = msa311->dev; + struct i2c_client *i2c = to_i2c_client(dev); + struct iio_dev *indio_dev = i2c_get_clientdata(i2c); + struct iio_trigger *trig; + int err; + + /* Keep going without interrupts if no initialized I2C IRQ */ + if (i2c->irq <= 0) + return 0; + + err = devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL, + msa311_irq_thread, IRQF_ONESHOT, + msa311->chip_name, indio_dev); + if (err) + return err; + + trig = devm_iio_trigger_alloc(dev, "%s-new-data", msa311->chip_name); + if (!trig) + return -ENOMEM; + + msa311->new_data_trig = trig; + msa311->new_data_trig->ops = &msa311_new_data_trig_ops; + iio_trigger_set_drvdata(msa311->new_data_trig, indio_dev); + + err = devm_iio_trigger_register(dev, msa311->new_data_trig); + if (err) + return dev_err_probe(dev, err, + "can't register newdata trigger\n"); + + err = regmap_field_write(msa311->fields[F_INT1_OD], + MSA311_INT1_OD_PUSH_PULL); + if (err) + return dev_err_probe(dev, err, + "can't enable push-pull interrupt\n"); + + err = regmap_field_write(msa311->fields[F_INT1_LVL], + MSA311_INT1_LVL_HIGH); + if (err) + return dev_err_probe(dev, err, + "can't set active interrupt level\n"); + + err = regmap_field_write(msa311->fields[F_LATCH_INT], + MSA311_LATCH_INT_LATCHED); + if (err) + return dev_err_probe(dev, err, + "can't latch interrupt\n"); + + err = regmap_field_write(msa311->fields[F_RESET_INT], 1); + if (err) + return dev_err_probe(dev, err, + "can't reset interrupt\n"); + + err = regmap_field_write(msa311->fields[F_INT1_NEW_DATA], 1); + if (err) + return dev_err_probe(dev, err, + "can't map new data interrupt\n"); + + return 0; +} + +static int msa311_regmap_init(struct msa311_priv *msa311) +{ + struct regmap_field **fields = msa311->fields; + struct device *dev = msa311->dev; + struct i2c_client *i2c = to_i2c_client(dev); + struct regmap *regmap; + int i; + + regmap = devm_regmap_init_i2c(i2c, &msa311_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "failed to register i2c regmap\n"); + + msa311->regs = regmap; + + for (i = 0; i < F_MAX_FIELDS; i++) { + fields[i] = devm_regmap_field_alloc(dev, + msa311->regs, + msa311_reg_fields[i]); + if (IS_ERR(msa311->fields[i])) + return dev_err_probe(dev, PTR_ERR(msa311->fields[i]), + "can't alloc field[%d]\n", i); + } + + return 0; +} + +static void msa311_powerdown(void *msa311) +{ + msa311_set_pwr_mode(msa311, MSA311_PWR_MODE_SUSPEND); +} + +static int msa311_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct msa311_priv *msa311; + struct iio_dev *indio_dev; + int err; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*msa311)); + if (!indio_dev) + return -ENOMEM; + + msa311 = iio_priv(indio_dev); + msa311->dev = dev; + i2c_set_clientdata(i2c, indio_dev); + + err = msa311_regmap_init(msa311); + if (err) + return err; + + mutex_init(&msa311->lock); + + err = devm_regulator_get_enable(dev, "vdd"); + if (err) + return dev_err_probe(dev, err, "can't get vdd supply\n"); + + err = msa311_check_partid(msa311); + if (err) + return err; + + err = msa311_soft_reset(msa311); + if (err) + return err; + + err = msa311_set_pwr_mode(msa311, MSA311_PWR_MODE_NORMAL); + if (err) + return dev_err_probe(dev, err, "failed to power on device\n"); + + /* + * Register powerdown deferred callback which suspends the chip + * after module unloaded. + * + * MSA311 should be in SUSPEND mode in the two cases: + * 1) When driver is loaded, but we do not have any data or + * configuration requests to it (we are solving it using + * autosuspend feature). + * 2) When driver is unloaded and device is not used (devm action is + * used in this case). + */ + err = devm_add_action_or_reset(dev, msa311_powerdown, msa311); + if (err) + return err; + + err = pm_runtime_set_active(dev); + if (err) + return err; + + err = devm_pm_runtime_enable(dev); + if (err) + return err; + + pm_runtime_get_noresume(dev); + pm_runtime_set_autosuspend_delay(dev, MSA311_PWR_SLEEP_DELAY_MS); + pm_runtime_use_autosuspend(dev); + + err = msa311_chip_init(msa311); + if (err) + return err; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = msa311_channels; + indio_dev->num_channels = ARRAY_SIZE(msa311_channels); + indio_dev->name = msa311->chip_name; + indio_dev->info = &msa311_info; + + err = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + msa311_buffer_thread, + &msa311_buffer_setup_ops); + if (err) + return dev_err_probe(dev, err, + "can't setup IIO trigger buffer\n"); + + err = msa311_setup_interrupts(msa311); + if (err) + return err; + + pm_runtime_put_autosuspend(dev); + + err = devm_iio_device_register(dev, indio_dev); + if (err) + return dev_err_probe(dev, err, "IIO device register failed\n"); + + return 0; +} + +static int msa311_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct msa311_priv *msa311 = iio_priv(indio_dev); + int err; + + mutex_lock(&msa311->lock); + err = msa311_set_pwr_mode(msa311, MSA311_PWR_MODE_SUSPEND); + mutex_unlock(&msa311->lock); + if (err) + dev_err(dev, "failed to power off device (%pe)\n", + ERR_PTR(err)); + + return err; +} + +static int msa311_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct msa311_priv *msa311 = iio_priv(indio_dev); + int err; + + mutex_lock(&msa311->lock); + err = msa311_set_pwr_mode(msa311, MSA311_PWR_MODE_NORMAL); + mutex_unlock(&msa311->lock); + if (err) + dev_err(dev, "failed to power on device (%pe)\n", + ERR_PTR(err)); + + return err; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(msa311_pm_ops, msa311_runtime_suspend, + msa311_runtime_resume, NULL); + +static const struct i2c_device_id msa311_i2c_id[] = { + { .name = "msa311" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, msa311_i2c_id); + +static const struct of_device_id msa311_of_match[] = { + { .compatible = "memsensing,msa311" }, + { } +}; +MODULE_DEVICE_TABLE(of, msa311_of_match); + +static struct i2c_driver msa311_driver = { + .driver = { + .name = "msa311", + .of_match_table = msa311_of_match, + .pm = pm_ptr(&msa311_pm_ops), + }, + .probe = msa311_probe, + .id_table = msa311_i2c_id, +}; +module_i2c_driver(msa311_driver); + +MODULE_AUTHOR("Dmitry Rokosov "); +MODULE_DESCRIPTION("MEMSensing MSA311 3-axis accelerometer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/accel/mxc4005.c b/drivers/iio/accel/mxc4005.c new file mode 100644 index 000000000..a66fee828 --- /dev/null +++ b/drivers/iio/accel/mxc4005.c @@ -0,0 +1,600 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 3-axis accelerometer driver for MXC4005XC Memsic sensor + * + * Copyright (c) 2014, Intel Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MXC4005_DRV_NAME "mxc4005" + +#define MXC4005_REG_XOUT_UPPER 0x03 +#define MXC4005_REG_XOUT_LOWER 0x04 +#define MXC4005_REG_YOUT_UPPER 0x05 +#define MXC4005_REG_YOUT_LOWER 0x06 +#define MXC4005_REG_ZOUT_UPPER 0x07 +#define MXC4005_REG_ZOUT_LOWER 0x08 + +#define MXC4005_REG_INT_MASK0 0x0A + +#define MXC4005_REG_INT_MASK1 0x0B +#define MXC4005_REG_INT_MASK1_BIT_DRDYE 0x01 + +#define MXC4005_REG_INT_CLR0 0x00 + +#define MXC4005_REG_INT_CLR1 0x01 +#define MXC4005_REG_INT_CLR1_BIT_DRDYC 0x01 +#define MXC4005_REG_INT_CLR1_SW_RST 0x10 + +#define MXC4005_REG_CONTROL 0x0D +#define MXC4005_REG_CONTROL_MASK_FSR GENMASK(6, 5) +#define MXC4005_CONTROL_FSR_SHIFT 5 + +#define MXC4005_REG_DEVICE_ID 0x0E + +/* Datasheet does not specify a reset time, this is a conservative guess */ +#define MXC4005_RESET_TIME_US 2000 + +enum mxc4005_axis { + AXIS_X, + AXIS_Y, + AXIS_Z, +}; + +enum mxc4005_range { + MXC4005_RANGE_2G, + MXC4005_RANGE_4G, + MXC4005_RANGE_8G, +}; + +struct mxc4005_data { + struct device *dev; + struct mutex mutex; + struct regmap *regmap; + struct iio_trigger *dready_trig; + struct iio_mount_matrix orientation; + /* Ensure timestamp is naturally aligned */ + struct { + __be16 chans[3]; + aligned_s64 timestamp; + } scan; + bool trigger_enabled; + unsigned int control; + unsigned int int_mask1; +}; + +/* + * MXC4005 can operate in the following ranges: + * +/- 2G, 4G, 8G (the default +/-2G) + * + * (2 + 2) * 9.81 / (2^12 - 1) = 0.009582 + * (4 + 4) * 9.81 / (2^12 - 1) = 0.019164 + * (8 + 8) * 9.81 / (2^12 - 1) = 0.038329 + */ +static const struct { + u8 range; + int scale; +} mxc4005_scale_table[] = { + {MXC4005_RANGE_2G, 9582}, + {MXC4005_RANGE_4G, 19164}, + {MXC4005_RANGE_8G, 38329}, +}; + + +static IIO_CONST_ATTR(in_accel_scale_available, "0.009582 0.019164 0.038329"); + +static struct attribute *mxc4005_attributes[] = { + &iio_const_attr_in_accel_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group mxc4005_attrs_group = { + .attrs = mxc4005_attributes, +}; + +static bool mxc4005_is_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MXC4005_REG_XOUT_UPPER: + case MXC4005_REG_XOUT_LOWER: + case MXC4005_REG_YOUT_UPPER: + case MXC4005_REG_YOUT_LOWER: + case MXC4005_REG_ZOUT_UPPER: + case MXC4005_REG_ZOUT_LOWER: + case MXC4005_REG_DEVICE_ID: + case MXC4005_REG_CONTROL: + return true; + default: + return false; + } +} + +static bool mxc4005_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MXC4005_REG_INT_CLR0: + case MXC4005_REG_INT_CLR1: + case MXC4005_REG_INT_MASK0: + case MXC4005_REG_INT_MASK1: + case MXC4005_REG_CONTROL: + return true; + default: + return false; + } +} + +static const struct regmap_config mxc4005_regmap_config = { + .name = "mxc4005_regmap", + + .reg_bits = 8, + .val_bits = 8, + + .max_register = MXC4005_REG_DEVICE_ID, + + .readable_reg = mxc4005_is_readable_reg, + .writeable_reg = mxc4005_is_writeable_reg, +}; + +static int mxc4005_read_xyz(struct mxc4005_data *data) +{ + int ret; + + ret = regmap_bulk_read(data->regmap, MXC4005_REG_XOUT_UPPER, + data->scan.chans, sizeof(data->scan.chans)); + if (ret < 0) { + dev_err(data->dev, "failed to read axes\n"); + return ret; + } + + return 0; +} + +static int mxc4005_read_axis(struct mxc4005_data *data, + unsigned int addr) +{ + __be16 reg; + int ret; + + ret = regmap_bulk_read(data->regmap, addr, ®, sizeof(reg)); + if (ret < 0) { + dev_err(data->dev, "failed to read reg %02x\n", addr); + return ret; + } + + return be16_to_cpu(reg); +} + +static int mxc4005_read_scale(struct mxc4005_data *data) +{ + unsigned int reg; + int ret; + int i; + + ret = regmap_read(data->regmap, MXC4005_REG_CONTROL, ®); + if (ret < 0) { + dev_err(data->dev, "failed to read reg_control\n"); + return ret; + } + + i = reg >> MXC4005_CONTROL_FSR_SHIFT; + + if (i < 0 || i >= ARRAY_SIZE(mxc4005_scale_table)) + return -EINVAL; + + return mxc4005_scale_table[i].scale; +} + +static int mxc4005_set_scale(struct mxc4005_data *data, int val) +{ + unsigned int reg; + int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(mxc4005_scale_table); i++) { + if (mxc4005_scale_table[i].scale == val) { + reg = i << MXC4005_CONTROL_FSR_SHIFT; + ret = regmap_update_bits(data->regmap, + MXC4005_REG_CONTROL, + MXC4005_REG_CONTROL_MASK_FSR, + reg); + if (ret < 0) + dev_err(data->dev, + "failed to write reg_control\n"); + return ret; + } + } + + return -EINVAL; +} + +static int mxc4005_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mxc4005_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_ACCEL: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + + ret = mxc4005_read_axis(data, chan->address); + if (ret < 0) + return ret; + *val = sign_extend32(ret >> chan->scan_type.shift, + chan->scan_type.realbits - 1); + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + ret = mxc4005_read_scale(data); + if (ret < 0) + return ret; + + *val = 0; + *val2 = ret; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int mxc4005_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct mxc4005_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (val != 0) + return -EINVAL; + + return mxc4005_set_scale(data, val2); + default: + return -EINVAL; + } +} + +static const struct iio_mount_matrix * +mxc4005_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct mxc4005_data *data = iio_priv(indio_dev); + + return &data->orientation; +} + +static const struct iio_chan_spec_ext_info mxc4005_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_TYPE, mxc4005_get_mount_matrix), + { } +}; + +static const struct iio_info mxc4005_info = { + .read_raw = mxc4005_read_raw, + .write_raw = mxc4005_write_raw, + .attrs = &mxc4005_attrs_group, +}; + +static const unsigned long mxc4005_scan_masks[] = { + BIT(AXIS_X) | BIT(AXIS_Y) | BIT(AXIS_Z), + 0 +}; + +#define MXC4005_CHANNEL(_axis, _addr) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .address = _addr, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = AXIS_##_axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 12, \ + .storagebits = 16, \ + .shift = 4, \ + .endianness = IIO_BE, \ + }, \ + .ext_info = mxc4005_ext_info, \ +} + +static const struct iio_chan_spec mxc4005_channels[] = { + MXC4005_CHANNEL(X, MXC4005_REG_XOUT_UPPER), + MXC4005_CHANNEL(Y, MXC4005_REG_YOUT_UPPER), + MXC4005_CHANNEL(Z, MXC4005_REG_ZOUT_UPPER), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static irqreturn_t mxc4005_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct mxc4005_data *data = iio_priv(indio_dev); + int ret; + + ret = mxc4005_read_xyz(data); + if (ret < 0) + goto err; + + iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + pf->timestamp); + +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static void mxc4005_clr_intr(struct mxc4005_data *data) +{ + int ret; + + /* clear interrupt */ + ret = regmap_write(data->regmap, MXC4005_REG_INT_CLR1, + MXC4005_REG_INT_CLR1_BIT_DRDYC); + if (ret < 0) + dev_err(data->dev, "failed to write to reg_int_clr1\n"); +} + +static int mxc4005_set_trigger_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct mxc4005_data *data = iio_priv(indio_dev); + unsigned int val; + int ret; + + mutex_lock(&data->mutex); + + val = state ? MXC4005_REG_INT_MASK1_BIT_DRDYE : 0; + ret = regmap_write(data->regmap, MXC4005_REG_INT_MASK1, val); + if (ret < 0) { + mutex_unlock(&data->mutex); + dev_err(data->dev, "failed to update reg_int_mask1"); + return ret; + } + + data->int_mask1 = val; + data->trigger_enabled = state; + mutex_unlock(&data->mutex); + + return 0; +} + +static void mxc4005_trigger_reen(struct iio_trigger *trig) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct mxc4005_data *data = iio_priv(indio_dev); + + if (!data->dready_trig) + return; + + mxc4005_clr_intr(data); +} + +static const struct iio_trigger_ops mxc4005_trigger_ops = { + .set_trigger_state = mxc4005_set_trigger_state, + .reenable = mxc4005_trigger_reen, +}; + +static int mxc4005_chip_init(struct mxc4005_data *data) +{ + int ret; + unsigned int reg; + + ret = regmap_read(data->regmap, MXC4005_REG_DEVICE_ID, ®); + if (ret < 0) { + dev_err(data->dev, "failed to read chip id\n"); + return ret; + } + + dev_dbg(data->dev, "MXC4005 chip id %02x\n", reg); + + ret = regmap_write(data->regmap, MXC4005_REG_INT_CLR1, + MXC4005_REG_INT_CLR1_SW_RST); + if (ret < 0) + return dev_err_probe(data->dev, ret, "resetting chip\n"); + + fsleep(MXC4005_RESET_TIME_US); + + ret = regmap_write(data->regmap, MXC4005_REG_INT_MASK0, 0); + if (ret < 0) + return dev_err_probe(data->dev, ret, "writing INT_MASK0\n"); + + ret = regmap_write(data->regmap, MXC4005_REG_INT_MASK1, 0); + if (ret < 0) + return dev_err_probe(data->dev, ret, "writing INT_MASK1\n"); + + return 0; +} + +static int mxc4005_probe(struct i2c_client *client) +{ + struct mxc4005_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, &mxc4005_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "failed to initialize regmap\n"); + return PTR_ERR(regmap); + } + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->dev = &client->dev; + data->regmap = regmap; + + ret = mxc4005_chip_init(data); + if (ret < 0) { + dev_err(&client->dev, "failed to initialize chip\n"); + return ret; + } + + mutex_init(&data->mutex); + + if (!iio_read_acpi_mount_matrix(&client->dev, &data->orientation, "ROTM")) { + ret = iio_read_mount_matrix(&client->dev, &data->orientation); + if (ret) + return ret; + } + + indio_dev->channels = mxc4005_channels; + indio_dev->num_channels = ARRAY_SIZE(mxc4005_channels); + indio_dev->available_scan_masks = mxc4005_scan_masks; + indio_dev->name = MXC4005_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &mxc4005_info; + + ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, + iio_pollfunc_store_time, + mxc4005_trigger_handler, + NULL); + if (ret < 0) { + dev_err(&client->dev, + "failed to setup iio triggered buffer\n"); + return ret; + } + + if (client->irq > 0) { + data->dready_trig = devm_iio_trigger_alloc(&client->dev, + "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->dready_trig) + return -ENOMEM; + + ret = devm_request_irq(&client->dev, client->irq, + iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_FALLING | IRQF_NO_THREAD, + "mxc4005_event", data->dready_trig); + if (ret) + return ret; + + data->dready_trig->ops = &mxc4005_trigger_ops; + iio_trigger_set_drvdata(data->dready_trig, indio_dev); + ret = devm_iio_trigger_register(&client->dev, + data->dready_trig); + if (ret) { + dev_err(&client->dev, + "failed to register trigger\n"); + return ret; + } + + indio_dev->trig = iio_trigger_get(data->dready_trig); + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static int mxc4005_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct mxc4005_data *data = iio_priv(indio_dev); + int ret; + + /* Save control to restore it on resume */ + ret = regmap_read(data->regmap, MXC4005_REG_CONTROL, &data->control); + if (ret < 0) + dev_err(data->dev, "failed to read reg_control\n"); + + return ret; +} + +static int mxc4005_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct mxc4005_data *data = iio_priv(indio_dev); + int ret; + + ret = regmap_write(data->regmap, MXC4005_REG_INT_CLR1, + MXC4005_REG_INT_CLR1_SW_RST); + if (ret) { + dev_err(data->dev, "failed to reset chip: %d\n", ret); + return ret; + } + + fsleep(MXC4005_RESET_TIME_US); + + ret = regmap_write(data->regmap, MXC4005_REG_CONTROL, data->control); + if (ret) { + dev_err(data->dev, "failed to restore control register\n"); + return ret; + } + + ret = regmap_write(data->regmap, MXC4005_REG_INT_MASK0, 0); + if (ret) { + dev_err(data->dev, "failed to restore interrupt 0 mask\n"); + return ret; + } + + ret = regmap_write(data->regmap, MXC4005_REG_INT_MASK1, data->int_mask1); + if (ret) { + dev_err(data->dev, "failed to restore interrupt 1 mask\n"); + return ret; + } + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(mxc4005_pm_ops, mxc4005_suspend, mxc4005_resume); + +static const struct acpi_device_id mxc4005_acpi_match[] = { + {"MXC4005", 0}, + {"MXC6655", 0}, + {"MDA6655", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, mxc4005_acpi_match); + +static const struct of_device_id mxc4005_of_match[] = { + { .compatible = "memsic,mxc4005", }, + { .compatible = "memsic,mxc6655", }, + { } +}; +MODULE_DEVICE_TABLE(of, mxc4005_of_match); + +static const struct i2c_device_id mxc4005_id[] = { + { .name = "mxc4005" }, + { .name = "mxc6655" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mxc4005_id); + +static struct i2c_driver mxc4005_driver = { + .driver = { + .name = MXC4005_DRV_NAME, + .acpi_match_table = mxc4005_acpi_match, + .of_match_table = mxc4005_of_match, + .pm = pm_sleep_ptr(&mxc4005_pm_ops), + }, + .probe = mxc4005_probe, + .id_table = mxc4005_id, +}; + +module_i2c_driver(mxc4005_driver); + +MODULE_AUTHOR("Teodora Baluta "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MXC4005 3-axis accelerometer driver"); diff --git a/drivers/iio/accel/mxc6255.c b/drivers/iio/accel/mxc6255.c new file mode 100644 index 000000000..a9f41cf27 --- /dev/null +++ b/drivers/iio/accel/mxc6255.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MXC6255 - MEMSIC orientation sensing accelerometer + * + * Copyright (c) 2015, Intel Corporation. + * + * IIO driver for MXC6255 (7-bit I2C slave address 0x15). + */ + +#include +#include +#include +#include +#include +#include +#include + +#define MXC6255_DRV_NAME "mxc6255" + +#define MXC6255_REG_XOUT 0x00 +#define MXC6255_REG_YOUT 0x01 +#define MXC6255_REG_CHIP_ID 0x08 + +#define MXC6255_CHIP_ID 0x05 + +/* + * MXC6255 has only one measurement range: +/- 2G. + * The acceleration output is an 8-bit value. + * + * Scale is calculated as follows: + * (2 + 2) * 9.80665 / (2^8 - 1) = 0.153829 + * + * Scale value for +/- 2G measurement range + */ +#define MXC6255_SCALE 153829 + +enum mxc6255_axis { + AXIS_X, + AXIS_Y, +}; + +struct mxc6255_data { + struct i2c_client *client; + struct regmap *regmap; +}; + +static int mxc6255_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mxc6255_data *data = iio_priv(indio_dev); + unsigned int reg; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(data->regmap, chan->address, ®); + if (ret < 0) { + dev_err(&data->client->dev, + "Error reading reg %lu\n", chan->address); + return ret; + } + + *val = sign_extend32(reg, 7); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = MXC6255_SCALE; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static const struct iio_info mxc6255_info = { + .read_raw = mxc6255_read_raw, +}; + +#define MXC6255_CHANNEL(_axis, reg) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .address = reg, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec mxc6255_channels[] = { + MXC6255_CHANNEL(X, MXC6255_REG_XOUT), + MXC6255_CHANNEL(Y, MXC6255_REG_YOUT), +}; + +static bool mxc6255_is_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MXC6255_REG_XOUT: + case MXC6255_REG_YOUT: + case MXC6255_REG_CHIP_ID: + return true; + default: + return false; + } +} + +static const struct regmap_config mxc6255_regmap_config = { + .name = "mxc6255_regmap", + + .reg_bits = 8, + .val_bits = 8, + + .readable_reg = mxc6255_is_readable_reg, +}; + +static int mxc6255_probe(struct i2c_client *client) +{ + struct mxc6255_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + unsigned int chip_id; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, &mxc6255_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Error initializing regmap\n"); + return PTR_ERR(regmap); + } + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->regmap = regmap; + + indio_dev->name = MXC6255_DRV_NAME; + indio_dev->channels = mxc6255_channels; + indio_dev->num_channels = ARRAY_SIZE(mxc6255_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &mxc6255_info; + + ret = regmap_read(data->regmap, MXC6255_REG_CHIP_ID, &chip_id); + if (ret < 0) { + dev_err(&client->dev, "Error reading chip id %d\n", ret); + return ret; + } + + if ((chip_id & 0x1f) != MXC6255_CHIP_ID) { + dev_err(&client->dev, "Invalid chip id %x\n", chip_id); + return -ENODEV; + } + + dev_dbg(&client->dev, "Chip id %x\n", chip_id); + + ret = devm_iio_device_register(&client->dev, indio_dev); + if (ret < 0) { + dev_err(&client->dev, "Could not register IIO device\n"); + return ret; + } + + return 0; +} + +static const struct acpi_device_id mxc6255_acpi_match[] = { + {"MXC6225", 0}, + {"MXC6255", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, mxc6255_acpi_match); + +static const struct i2c_device_id mxc6255_id[] = { + { .name = "mxc6225" }, + { .name = "mxc6255" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mxc6255_id); + +static struct i2c_driver mxc6255_driver = { + .driver = { + .name = MXC6255_DRV_NAME, + .acpi_match_table = mxc6255_acpi_match, + }, + .probe = mxc6255_probe, + .id_table = mxc6255_id, +}; + +module_i2c_driver(mxc6255_driver); + +MODULE_AUTHOR("Teodora Baluta "); +MODULE_DESCRIPTION("MEMSIC MXC6255 orientation sensing accelerometer driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/sca3000.c b/drivers/iio/accel/sca3000.c new file mode 100644 index 000000000..a92a563f6 --- /dev/null +++ b/drivers/iio/accel/sca3000.c @@ -0,0 +1,1526 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * sca3000_core.c -- support VTI sca3000 series accelerometers via SPI + * + * Copyright (c) 2009 Jonathan Cameron + * + * See industrialio/accels/sca3000.h for comments. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SCA3000_WRITE_REG(a) (((a) << 2) | 0x02) +#define SCA3000_READ_REG(a) ((a) << 2) + +#define SCA3000_REG_REVID_ADDR 0x00 +#define SCA3000_REG_REVID_MAJOR_MASK GENMASK(8, 4) +#define SCA3000_REG_REVID_MINOR_MASK GENMASK(3, 0) + +#define SCA3000_REG_STATUS_ADDR 0x02 +#define SCA3000_LOCKED BIT(5) +#define SCA3000_EEPROM_CS_ERROR BIT(1) +#define SCA3000_SPI_FRAME_ERROR BIT(0) + +/* All reads done using register decrement so no need to directly access LSBs */ +#define SCA3000_REG_X_MSB_ADDR 0x05 +#define SCA3000_REG_Y_MSB_ADDR 0x07 +#define SCA3000_REG_Z_MSB_ADDR 0x09 + +#define SCA3000_REG_RING_OUT_ADDR 0x0f + +/* Temp read untested - the e05 doesn't have the sensor */ +#define SCA3000_REG_TEMP_MSB_ADDR 0x13 + +#define SCA3000_REG_MODE_ADDR 0x14 +#define SCA3000_MODE_PROT_MASK 0x28 +#define SCA3000_REG_MODE_RING_BUF_ENABLE BIT(7) +#define SCA3000_REG_MODE_RING_BUF_8BIT BIT(6) + +/* + * Free fall detection triggers an interrupt if the acceleration + * is below a threshold for equivalent of 25cm drop + */ +#define SCA3000_REG_MODE_FREE_FALL_DETECT BIT(4) +#define SCA3000_REG_MODE_MEAS_MODE_NORMAL 0x00 +#define SCA3000_REG_MODE_MEAS_MODE_OP_1 0x01 +#define SCA3000_REG_MODE_MEAS_MODE_OP_2 0x02 + +/* + * In motion detection mode the accelerations are band pass filtered + * (approx 1 - 25Hz) and then a programmable threshold used to trigger + * and interrupt. + */ +#define SCA3000_REG_MODE_MEAS_MODE_MOT_DET 0x03 +#define SCA3000_REG_MODE_MODE_MASK 0x03 + +#define SCA3000_REG_BUF_COUNT_ADDR 0x15 + +#define SCA3000_REG_INT_STATUS_ADDR 0x16 +#define SCA3000_REG_INT_STATUS_THREE_QUARTERS BIT(7) +#define SCA3000_REG_INT_STATUS_HALF BIT(6) + +#define SCA3000_INT_STATUS_FREE_FALL BIT(3) +#define SCA3000_INT_STATUS_Y_TRIGGER BIT(2) +#define SCA3000_INT_STATUS_X_TRIGGER BIT(1) +#define SCA3000_INT_STATUS_Z_TRIGGER BIT(0) + +/* Used to allow access to multiplexed registers */ +#define SCA3000_REG_CTRL_SEL_ADDR 0x18 +/* Only available for SCA3000-D03 and SCA3000-D01 */ +#define SCA3000_REG_CTRL_SEL_I2C_DISABLE 0x01 +#define SCA3000_REG_CTRL_SEL_MD_CTRL 0x02 +#define SCA3000_REG_CTRL_SEL_MD_Y_TH 0x03 +#define SCA3000_REG_CTRL_SEL_MD_X_TH 0x04 +#define SCA3000_REG_CTRL_SEL_MD_Z_TH 0x05 +/* + * BE VERY CAREFUL WITH THIS, IF 3 BITS ARE NOT SET the device + * will not function + */ +#define SCA3000_REG_CTRL_SEL_OUT_CTRL 0x0B + +#define SCA3000_REG_OUT_CTRL_PROT_MASK 0xE0 +#define SCA3000_REG_OUT_CTRL_BUF_X_EN 0x10 +#define SCA3000_REG_OUT_CTRL_BUF_Y_EN 0x08 +#define SCA3000_REG_OUT_CTRL_BUF_Z_EN 0x04 +#define SCA3000_REG_OUT_CTRL_BUF_DIV_MASK 0x03 +#define SCA3000_REG_OUT_CTRL_BUF_DIV_4 0x02 +#define SCA3000_REG_OUT_CTRL_BUF_DIV_2 0x01 + + +/* + * Control which motion detector interrupts are on. + * For now only OR combinations are supported. + */ +#define SCA3000_MD_CTRL_PROT_MASK 0xC0 +#define SCA3000_MD_CTRL_OR_Y BIT(0) +#define SCA3000_MD_CTRL_OR_X BIT(1) +#define SCA3000_MD_CTRL_OR_Z BIT(2) +/* Currently unsupported */ +#define SCA3000_MD_CTRL_AND_Y BIT(3) +#define SCA3000_MD_CTRL_AND_X BIT(4) +#define SCA3000_MD_CTRL_AND_Z BIT(5) + +/* + * Some control registers of complex access methods requiring this register to + * be used to remove a lock. + */ +#define SCA3000_REG_UNLOCK_ADDR 0x1e + +#define SCA3000_REG_INT_MASK_ADDR 0x21 +#define SCA3000_REG_INT_MASK_PROT_MASK 0x1C + +#define SCA3000_REG_INT_MASK_RING_THREE_QUARTER BIT(7) +#define SCA3000_REG_INT_MASK_RING_HALF BIT(6) + +#define SCA3000_REG_INT_MASK_ALL_INTS 0x02 +#define SCA3000_REG_INT_MASK_ACTIVE_HIGH 0x01 +#define SCA3000_REG_INT_MASK_ACTIVE_LOW 0x00 +/* Values of multiplexed registers (write to ctrl_data after select) */ +#define SCA3000_REG_CTRL_DATA_ADDR 0x22 + +/* + * Measurement modes available on some sca3000 series chips. Code assumes others + * may become available in the future. + * + * Bypass - Bypass the low-pass filter in the signal channel so as to increase + * signal bandwidth. + * + * Narrow - Narrow low-pass filtering of the signal channel and half output + * data rate by decimation. + * + * Wide - Widen low-pass filtering of signal channel to increase bandwidth + */ +#define SCA3000_OP_MODE_BYPASS 0x01 +#define SCA3000_OP_MODE_NARROW 0x02 +#define SCA3000_OP_MODE_WIDE 0x04 +#define SCA3000_MAX_TX 6 +#define SCA3000_MAX_RX 2 + +/** + * struct sca3000_state - device instance state information + * @us: the associated spi device + * @info: chip variant information + * @mo_det_use_count: reference counter for the motion detection unit + * @lock: lock used to protect elements of sca3000_state + * and the underlying device state. + * @tx: dma-able transmit buffer + * @rx: dma-able receive buffer + **/ +struct sca3000_state { + struct spi_device *us; + const struct sca3000_chip_info *info; + int mo_det_use_count; + struct mutex lock; + /* Can these share a cacheline ? */ + u8 rx[384] __aligned(IIO_DMA_MINALIGN); + u8 tx[6] __aligned(IIO_DMA_MINALIGN); +}; + +/** + * struct sca3000_chip_info - model dependent parameters + * @name: name of the chip + * @scale: scale * 10^-6 + * @temp_output: some devices have temperature sensors. + * @measurement_mode_freq: normal mode sampling frequency + * @measurement_mode_3db_freq: 3db cutoff frequency of the low pass filter for + * the normal measurement mode. + * @option_mode_1: first optional mode. Not all models have one + * @option_mode_1_freq: option mode 1 sampling frequency + * @option_mode_1_3db_freq: 3db cutoff frequency of the low pass filter for + * the first option mode. + * @option_mode_2: second optional mode. Not all chips have one + * @option_mode_2_freq: option mode 2 sampling frequency + * @option_mode_2_3db_freq: 3db cutoff frequency of the low pass filter for + * the second option mode. + * @mot_det_mult_xz: Bit wise multipliers to calculate the threshold + * for motion detection in the x and z axis. + * @mot_det_mult_y: Bit wise multipliers to calculate the threshold + * for motion detection in the y axis. + * + * This structure is used to hold information about the functionality of a given + * sca3000 variant. + **/ +struct sca3000_chip_info { + const char *name; + unsigned int scale; + bool temp_output; + int measurement_mode_freq; + int measurement_mode_3db_freq; + int option_mode_1; + int option_mode_1_freq; + int option_mode_1_3db_freq; + int option_mode_2; + int option_mode_2_freq; + int option_mode_2_3db_freq; + int mot_det_mult_xz[6]; + int mot_det_mult_y[7]; +}; + +static const struct sca3000_chip_info sca3000_chip_info_d01 = { + .name = "sca3000_d01", + .scale = 7357, + .temp_output = true, + .measurement_mode_freq = 250, + .measurement_mode_3db_freq = 45, + .option_mode_1 = SCA3000_OP_MODE_BYPASS, + .option_mode_1_freq = 250, + .option_mode_1_3db_freq = 70, + .mot_det_mult_xz = { 50, 100, 200, 350, 650, 1300 }, + .mot_det_mult_y = { 50, 100, 150, 250, 450, 850, 1750 }, +}; + +static const struct sca3000_chip_info sca3000_chip_info_e02 = { + .name = "sca3000_e02", + .scale = 9810, + .measurement_mode_freq = 125, + .measurement_mode_3db_freq = 40, + .option_mode_1 = SCA3000_OP_MODE_NARROW, + .option_mode_1_freq = 63, + .option_mode_1_3db_freq = 11, + .mot_det_mult_xz = { 100, 150, 300, 550, 1050, 2050 }, + .mot_det_mult_y = { 50, 100, 200, 350, 700, 1350, 2700 }, +}; + +static const struct sca3000_chip_info sca3000_chip_info_e04 = { + .name = "sca3000_e04", + .scale = 19620, + .measurement_mode_freq = 100, + .measurement_mode_3db_freq = 38, + .option_mode_1 = SCA3000_OP_MODE_NARROW, + .option_mode_1_freq = 50, + .option_mode_1_3db_freq = 9, + .option_mode_2 = SCA3000_OP_MODE_WIDE, + .option_mode_2_freq = 400, + .option_mode_2_3db_freq = 70, + .mot_det_mult_xz = { 200, 300, 600, 1100, 2100, 4100 }, + .mot_det_mult_y = { 100, 200, 400, 7000, 1400, 2700, 54000 }, +}; + +static const struct sca3000_chip_info sca3000_chip_info_e05 = { + .name = "sca3000_e05", + .scale = 61313, + .measurement_mode_freq = 200, + .measurement_mode_3db_freq = 60, + .option_mode_1 = SCA3000_OP_MODE_NARROW, + .option_mode_1_freq = 50, + .option_mode_1_3db_freq = 9, + .option_mode_2 = SCA3000_OP_MODE_WIDE, + .option_mode_2_freq = 400, + .option_mode_2_3db_freq = 75, + .mot_det_mult_xz = { 600, 900, 1700, 3200, 6100, 11900 }, + .mot_det_mult_y = { 300, 600, 1200, 2000, 4100, 7800, 15600 }, +}; + +static int sca3000_write_reg(struct sca3000_state *st, u8 address, u8 val) +{ + st->tx[0] = SCA3000_WRITE_REG(address); + st->tx[1] = val; + return spi_write(st->us, st->tx, 2); +} + +static int sca3000_read_data_short(struct sca3000_state *st, + u8 reg_address_high, + int len) +{ + struct spi_transfer xfer[2] = { + { + .len = 1, + .tx_buf = st->tx, + }, { + .len = len, + .rx_buf = st->rx, + } + }; + st->tx[0] = SCA3000_READ_REG(reg_address_high); + + return spi_sync_transfer(st->us, xfer, ARRAY_SIZE(xfer)); +} + +/** + * sca3000_reg_lock_on() - test if the ctrl register lock is on + * @st: Driver specific device instance data. + * + * Lock must be held. + **/ +static int sca3000_reg_lock_on(struct sca3000_state *st) +{ + int ret; + + ret = sca3000_read_data_short(st, SCA3000_REG_STATUS_ADDR, 1); + if (ret < 0) + return ret; + + return !(st->rx[0] & SCA3000_LOCKED); +} + +/** + * __sca3000_unlock_reg_lock() - unlock the control registers + * @st: Driver specific device instance data. + * + * Note the device does not appear to support doing this in a single transfer. + * This should only ever be used as part of ctrl reg read. + * Lock must be held before calling this + */ +static int __sca3000_unlock_reg_lock(struct sca3000_state *st) +{ + struct spi_transfer xfer[3] = { + { + .len = 2, + .cs_change = 1, + .tx_buf = st->tx, + }, { + .len = 2, + .cs_change = 1, + .tx_buf = st->tx + 2, + }, { + .len = 2, + .tx_buf = st->tx + 4, + }, + }; + st->tx[0] = SCA3000_WRITE_REG(SCA3000_REG_UNLOCK_ADDR); + st->tx[1] = 0x00; + st->tx[2] = SCA3000_WRITE_REG(SCA3000_REG_UNLOCK_ADDR); + st->tx[3] = 0x50; + st->tx[4] = SCA3000_WRITE_REG(SCA3000_REG_UNLOCK_ADDR); + st->tx[5] = 0xA0; + + return spi_sync_transfer(st->us, xfer, ARRAY_SIZE(xfer)); +} + +/** + * sca3000_write_ctrl_reg() - write to a lock protect ctrl register + * @st: Driver specific device instance data. + * @sel: selects which registers we wish to write to + * @val: the value to be written + * + * Certain control registers are protected against overwriting by the lock + * register and use a shared write address. This function allows writing of + * these registers. + * Lock must be held. + */ +static int sca3000_write_ctrl_reg(struct sca3000_state *st, + u8 sel, + uint8_t val) +{ + int ret; + + ret = sca3000_reg_lock_on(st); + if (ret < 0) + return ret; + if (ret) { + ret = __sca3000_unlock_reg_lock(st); + if (ret) + return ret; + } + + /* Set the control select register */ + ret = sca3000_write_reg(st, SCA3000_REG_CTRL_SEL_ADDR, sel); + if (ret) + return ret; + + /* Write the actual value into the register */ + return sca3000_write_reg(st, SCA3000_REG_CTRL_DATA_ADDR, val); +} + +/** + * sca3000_read_ctrl_reg() - read from lock protected control register. + * @st: Driver specific device instance data. + * @ctrl_reg: Which ctrl register do we want to read. + * + * Lock must be held. + */ +static int sca3000_read_ctrl_reg(struct sca3000_state *st, + u8 ctrl_reg) +{ + int ret; + + ret = sca3000_reg_lock_on(st); + if (ret < 0) + return ret; + if (ret) { + ret = __sca3000_unlock_reg_lock(st); + if (ret) + return ret; + } + /* Set the control select register */ + ret = sca3000_write_reg(st, SCA3000_REG_CTRL_SEL_ADDR, ctrl_reg); + if (ret) + return ret; + ret = sca3000_read_data_short(st, SCA3000_REG_CTRL_DATA_ADDR, 1); + if (ret) + return ret; + return st->rx[0]; +} + +/** + * sca3000_print_rev() - sysfs interface to read the chip revision number + * @indio_dev: Device instance specific generic IIO data. + * Driver specific device instance data can be obtained via + * iio_priv(indio_dev) + */ +static int sca3000_print_rev(struct iio_dev *indio_dev) +{ + int ret; + struct sca3000_state *st = iio_priv(indio_dev); + + mutex_lock(&st->lock); + ret = sca3000_read_data_short(st, SCA3000_REG_REVID_ADDR, 1); + if (ret < 0) + goto error_ret; + dev_info(&indio_dev->dev, + "sca3000 revision major=%lu, minor=%lu\n", + st->rx[0] & SCA3000_REG_REVID_MAJOR_MASK, + st->rx[0] & SCA3000_REG_REVID_MINOR_MASK); +error_ret: + mutex_unlock(&st->lock); + + return ret; +} + +static ssize_t +sca3000_show_available_3db_freqs(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct sca3000_state *st = iio_priv(indio_dev); + int len; + + len = sprintf(buf, "%d", st->info->measurement_mode_3db_freq); + if (st->info->option_mode_1) + len += sprintf(buf + len, " %d", + st->info->option_mode_1_3db_freq); + if (st->info->option_mode_2) + len += sprintf(buf + len, " %d", + st->info->option_mode_2_3db_freq); + len += sprintf(buf + len, "\n"); + + return len; +} + +static IIO_DEVICE_ATTR(in_accel_filter_low_pass_3db_frequency_available, + S_IRUGO, sca3000_show_available_3db_freqs, + NULL, 0); + +static const struct iio_event_spec sca3000_event = { + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE), +}; + +/* + * Note the hack in the number of bits to pretend we have 2 more than + * we do in the fifo. + */ +#define SCA3000_CHAN(index, mod) \ + { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = mod, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) |\ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY),\ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\ + .address = index, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 13, \ + .storagebits = 16, \ + .shift = 3, \ + .endianness = IIO_BE, \ + }, \ + .event_spec = &sca3000_event, \ + .num_event_specs = 1, \ + } + +static const struct iio_event_spec sca3000_freefall_event_spec = { + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_PERIOD), +}; + +static const struct iio_chan_spec sca3000_channels[] = { + SCA3000_CHAN(0, IIO_MOD_X), + SCA3000_CHAN(1, IIO_MOD_Y), + SCA3000_CHAN(2, IIO_MOD_Z), + { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_X_AND_Y_AND_Z, + .scan_index = -1, /* Fake channel */ + .event_spec = &sca3000_freefall_event_spec, + .num_event_specs = 1, + }, +}; + +static const struct iio_chan_spec sca3000_channels_with_temp[] = { + SCA3000_CHAN(0, IIO_MOD_X), + SCA3000_CHAN(1, IIO_MOD_Y), + SCA3000_CHAN(2, IIO_MOD_Z), + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + /* No buffer support */ + .scan_index = -1, + .scan_type = { + .sign = 'u', + .realbits = 9, + .storagebits = 16, + .shift = 5, + .endianness = IIO_BE, + }, + }, + { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_X_AND_Y_AND_Z, + .scan_index = -1, /* Fake channel */ + .event_spec = &sca3000_freefall_event_spec, + .num_event_specs = 1, + }, +}; + +static u8 sca3000_addresses[3][3] = { + [0] = {SCA3000_REG_X_MSB_ADDR, SCA3000_REG_CTRL_SEL_MD_X_TH, + SCA3000_MD_CTRL_OR_X}, + [1] = {SCA3000_REG_Y_MSB_ADDR, SCA3000_REG_CTRL_SEL_MD_Y_TH, + SCA3000_MD_CTRL_OR_Y}, + [2] = {SCA3000_REG_Z_MSB_ADDR, SCA3000_REG_CTRL_SEL_MD_Z_TH, + SCA3000_MD_CTRL_OR_Z}, +}; + +/** + * __sca3000_get_base_freq() - obtain mode specific base frequency + * @st: Private driver specific device instance specific state. + * @info: chip type specific information. + * @base_freq: Base frequency for the current measurement mode. + * + * lock must be held + */ +static inline int __sca3000_get_base_freq(struct sca3000_state *st, + const struct sca3000_chip_info *info, + int *base_freq) +{ + int ret; + + ret = sca3000_read_data_short(st, SCA3000_REG_MODE_ADDR, 1); + if (ret) + return ret; + + switch (SCA3000_REG_MODE_MODE_MASK & st->rx[0]) { + case SCA3000_REG_MODE_MEAS_MODE_NORMAL: + *base_freq = info->measurement_mode_freq; + break; + case SCA3000_REG_MODE_MEAS_MODE_OP_1: + *base_freq = info->option_mode_1_freq; + break; + case SCA3000_REG_MODE_MEAS_MODE_OP_2: + *base_freq = info->option_mode_2_freq; + break; + default: + ret = -EINVAL; + } + return ret; +} + +/** + * sca3000_read_raw_samp_freq() - read_raw handler for IIO_CHAN_INFO_SAMP_FREQ + * @st: Private driver specific device instance specific state. + * @val: The frequency read back. + * + * lock must be held + **/ +static int sca3000_read_raw_samp_freq(struct sca3000_state *st, int *val) +{ + int ret; + + ret = __sca3000_get_base_freq(st, st->info, val); + if (ret) + return ret; + + ret = sca3000_read_ctrl_reg(st, SCA3000_REG_CTRL_SEL_OUT_CTRL); + if (ret < 0) + return ret; + + if (*val > 0) { + ret &= SCA3000_REG_OUT_CTRL_BUF_DIV_MASK; + switch (ret) { + case SCA3000_REG_OUT_CTRL_BUF_DIV_2: + *val /= 2; + break; + case SCA3000_REG_OUT_CTRL_BUF_DIV_4: + *val /= 4; + break; + } + } + + return 0; +} + +/** + * sca3000_write_raw_samp_freq() - write_raw handler for IIO_CHAN_INFO_SAMP_FREQ + * @st: Private driver specific device instance specific state. + * @val: The frequency desired. + * + * lock must be held + */ +static int sca3000_write_raw_samp_freq(struct sca3000_state *st, int val) +{ + int ret, base_freq, ctrlval; + + ret = __sca3000_get_base_freq(st, st->info, &base_freq); + if (ret) + return ret; + + ret = sca3000_read_ctrl_reg(st, SCA3000_REG_CTRL_SEL_OUT_CTRL); + if (ret < 0) + return ret; + + ctrlval = ret & ~SCA3000_REG_OUT_CTRL_BUF_DIV_MASK; + + if (val == base_freq / 2) + ctrlval |= SCA3000_REG_OUT_CTRL_BUF_DIV_2; + if (val == base_freq / 4) + ctrlval |= SCA3000_REG_OUT_CTRL_BUF_DIV_4; + else if (val != base_freq) + return -EINVAL; + + return sca3000_write_ctrl_reg(st, SCA3000_REG_CTRL_SEL_OUT_CTRL, + ctrlval); +} + +static int sca3000_read_3db_freq(struct sca3000_state *st, int *val) +{ + int ret; + + ret = sca3000_read_data_short(st, SCA3000_REG_MODE_ADDR, 1); + if (ret) + return ret; + + /* mask bottom 2 bits - only ones that are relevant */ + st->rx[0] &= SCA3000_REG_MODE_MODE_MASK; + switch (st->rx[0]) { + case SCA3000_REG_MODE_MEAS_MODE_NORMAL: + *val = st->info->measurement_mode_3db_freq; + return IIO_VAL_INT; + case SCA3000_REG_MODE_MEAS_MODE_MOT_DET: + return -EBUSY; + case SCA3000_REG_MODE_MEAS_MODE_OP_1: + *val = st->info->option_mode_1_3db_freq; + return IIO_VAL_INT; + case SCA3000_REG_MODE_MEAS_MODE_OP_2: + *val = st->info->option_mode_2_3db_freq; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int sca3000_write_3db_freq(struct sca3000_state *st, int val) +{ + int ret; + int mode; + + if (val == st->info->measurement_mode_3db_freq) + mode = SCA3000_REG_MODE_MEAS_MODE_NORMAL; + else if (st->info->option_mode_1 && + (val == st->info->option_mode_1_3db_freq)) + mode = SCA3000_REG_MODE_MEAS_MODE_OP_1; + else if (st->info->option_mode_2 && + (val == st->info->option_mode_2_3db_freq)) + mode = SCA3000_REG_MODE_MEAS_MODE_OP_2; + else + return -EINVAL; + ret = sca3000_read_data_short(st, SCA3000_REG_MODE_ADDR, 1); + if (ret) + return ret; + + st->rx[0] &= ~SCA3000_REG_MODE_MODE_MASK; + st->rx[0] |= (mode & SCA3000_REG_MODE_MODE_MASK); + + return sca3000_write_reg(st, SCA3000_REG_MODE_ADDR, st->rx[0]); +} + +static int sca3000_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct sca3000_state *st = iio_priv(indio_dev); + int ret; + u8 address; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&st->lock); + if (chan->type == IIO_ACCEL) { + if (st->mo_det_use_count) { + mutex_unlock(&st->lock); + return -EBUSY; + } + address = sca3000_addresses[chan->address][0]; + ret = sca3000_read_data_short(st, address, 2); + if (ret < 0) { + mutex_unlock(&st->lock); + return ret; + } + *val = sign_extend32(be16_to_cpup((__be16 *)st->rx) >> + chan->scan_type.shift, + chan->scan_type.realbits - 1); + } else { + /* get the temperature when available */ + ret = sca3000_read_data_short(st, + SCA3000_REG_TEMP_MSB_ADDR, + 2); + if (ret < 0) { + mutex_unlock(&st->lock); + return ret; + } + *val = (be16_to_cpup((__be16 *)st->rx) >> + chan->scan_type.shift) & + GENMASK(chan->scan_type.realbits - 1, 0); + } + mutex_unlock(&st->lock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + if (chan->type == IIO_ACCEL) + *val2 = st->info->scale; + else /* temperature */ + *val2 = 555556; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OFFSET: + *val = -214; + *val2 = 600000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + mutex_lock(&st->lock); + ret = sca3000_read_raw_samp_freq(st, val); + mutex_unlock(&st->lock); + return ret ? ret : IIO_VAL_INT; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + mutex_lock(&st->lock); + ret = sca3000_read_3db_freq(st, val); + mutex_unlock(&st->lock); + return ret; + default: + return -EINVAL; + } +} + +static int sca3000_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct sca3000_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (val2) + return -EINVAL; + mutex_lock(&st->lock); + ret = sca3000_write_raw_samp_freq(st, val); + mutex_unlock(&st->lock); + return ret; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + if (val2) + return -EINVAL; + mutex_lock(&st->lock); + ret = sca3000_write_3db_freq(st, val); + mutex_unlock(&st->lock); + return ret; + default: + return -EINVAL; + } + + return ret; +} + +/** + * sca3000_read_av_freq() - sysfs function to get available frequencies + * @dev: Device structure for this device. + * @attr: Description of the attribute. + * @buf: Incoming string + * + * The later modes are only relevant to the ring buffer - and depend on current + * mode. Note that data sheet gives rather wide tolerances for these so integer + * division will give good enough answer and not all chips have them specified + * at all. + **/ +static ssize_t sca3000_read_av_freq(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct sca3000_state *st = iio_priv(indio_dev); + int len = 0, ret, val; + + mutex_lock(&st->lock); + ret = sca3000_read_data_short(st, SCA3000_REG_MODE_ADDR, 1); + val = st->rx[0]; + mutex_unlock(&st->lock); + if (ret) + return ret; + + switch (val & SCA3000_REG_MODE_MODE_MASK) { + case SCA3000_REG_MODE_MEAS_MODE_NORMAL: + len += sprintf(buf + len, "%d %d %d\n", + st->info->measurement_mode_freq, + st->info->measurement_mode_freq / 2, + st->info->measurement_mode_freq / 4); + break; + case SCA3000_REG_MODE_MEAS_MODE_OP_1: + len += sprintf(buf + len, "%d %d %d\n", + st->info->option_mode_1_freq, + st->info->option_mode_1_freq / 2, + st->info->option_mode_1_freq / 4); + break; + case SCA3000_REG_MODE_MEAS_MODE_OP_2: + len += sprintf(buf + len, "%d %d %d\n", + st->info->option_mode_2_freq, + st->info->option_mode_2_freq / 2, + st->info->option_mode_2_freq / 4); + break; + } + return len; +} + +/* + * Should only really be registered if ring buffer support is compiled in. + * Does no harm however and doing it right would add a fair bit of complexity + */ +static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(sca3000_read_av_freq); + +/* + * sca3000_read_event_value() - query of a threshold or period + */ +static int sca3000_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct sca3000_state *st = iio_priv(indio_dev); + long ret; + int i; + + switch (info) { + case IIO_EV_INFO_VALUE: + mutex_lock(&st->lock); + ret = sca3000_read_ctrl_reg(st, + sca3000_addresses[chan->address][1]); + mutex_unlock(&st->lock); + if (ret < 0) + return ret; + *val = 0; + if (chan->channel2 == IIO_MOD_Y) + for_each_set_bit(i, &ret, + ARRAY_SIZE(st->info->mot_det_mult_y)) + *val += st->info->mot_det_mult_y[i]; + else + for_each_set_bit(i, &ret, + ARRAY_SIZE(st->info->mot_det_mult_xz)) + *val += st->info->mot_det_mult_xz[i]; + + return IIO_VAL_INT; + case IIO_EV_INFO_PERIOD: + *val = 0; + *val2 = 226000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +/** + * sca3000_write_event_value() - control of threshold and period + * @indio_dev: Device instance specific IIO information. + * @chan: Description of the channel for which the event is being + * configured. + * @type: The type of event being configured, here magnitude rising + * as everything else is read only. + * @dir: Direction of the event (here rising) + * @info: What information about the event are we configuring. + * Here the threshold only. + * @val: Integer part of the value being written.. + * @val2: Non integer part of the value being written. Here always 0. + */ +static int sca3000_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct sca3000_state *st = iio_priv(indio_dev); + int ret; + int i; + u8 nonlinear = 0; + + if (chan->channel2 == IIO_MOD_Y) { + i = ARRAY_SIZE(st->info->mot_det_mult_y); + while (i > 0) + if (val >= st->info->mot_det_mult_y[--i]) { + nonlinear |= (1 << i); + val -= st->info->mot_det_mult_y[i]; + } + } else { + i = ARRAY_SIZE(st->info->mot_det_mult_xz); + while (i > 0) + if (val >= st->info->mot_det_mult_xz[--i]) { + nonlinear |= (1 << i); + val -= st->info->mot_det_mult_xz[i]; + } + } + + mutex_lock(&st->lock); + ret = sca3000_write_ctrl_reg(st, + sca3000_addresses[chan->address][1], + nonlinear); + mutex_unlock(&st->lock); + + return ret; +} + +static struct attribute *sca3000_attributes[] = { + &iio_dev_attr_in_accel_filter_low_pass_3db_frequency_available.dev_attr.attr, + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group sca3000_attribute_group = { + .attrs = sca3000_attributes, +}; + +static int sca3000_read_data(struct sca3000_state *st, + u8 reg_address_high, + u8 *rx, + int len) +{ + int ret; + struct spi_transfer xfer[2] = { + { + .len = 1, + .tx_buf = st->tx, + }, { + .len = len, + .rx_buf = rx, + } + }; + + st->tx[0] = SCA3000_READ_REG(reg_address_high); + ret = spi_sync_transfer(st->us, xfer, ARRAY_SIZE(xfer)); + if (ret) { + dev_err(&st->us->dev, "problem reading register\n"); + return ret; + } + + return 0; +} + +/** + * sca3000_ring_int_process() - ring specific interrupt handling. + * @val: Value of the interrupt status register. + * @indio_dev: Device instance specific IIO device structure. + */ +static void sca3000_ring_int_process(u8 val, struct iio_dev *indio_dev) +{ + struct sca3000_state *st = iio_priv(indio_dev); + int ret, i, num_available; + + mutex_lock(&st->lock); + + if (val & SCA3000_REG_INT_STATUS_HALF) { + ret = sca3000_read_data_short(st, SCA3000_REG_BUF_COUNT_ADDR, + 1); + if (ret) + goto error_ret; + num_available = st->rx[0]; + /* + * num_available is the total number of samples available + * i.e. number of time points * number of channels. + */ + ret = sca3000_read_data(st, SCA3000_REG_RING_OUT_ADDR, st->rx, + num_available * 2); + if (ret) + goto error_ret; + for (i = 0; i < num_available / 3; i++) { + /* + * Dirty hack to cover for 11 bit in fifo, 13 bit + * direct reading. + * + * In theory the bottom two bits are undefined. + * In reality they appear to always be 0. + */ + iio_push_to_buffers(indio_dev, st->rx + i * 3 * 2); + } + } +error_ret: + mutex_unlock(&st->lock); +} + +/** + * sca3000_event_handler() - handling ring and non ring events + * @irq: The irq being handled. + * @private: struct iio_device pointer for the device. + * + * Ring related interrupt handler. Depending on event, push to + * the ring buffer event chrdev or the event one. + * + * This function is complicated by the fact that the devices can signify ring + * and non ring events via the same interrupt line and they can only + * be distinguished via a read of the relevant status register. + */ +static irqreturn_t sca3000_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct sca3000_state *st = iio_priv(indio_dev); + int ret, val; + s64 last_timestamp = iio_get_time_ns(indio_dev); + + /* + * Could lead if badly timed to an extra read of status reg, + * but ensures no interrupt is missed. + */ + mutex_lock(&st->lock); + ret = sca3000_read_data_short(st, SCA3000_REG_INT_STATUS_ADDR, 1); + val = st->rx[0]; + mutex_unlock(&st->lock); + if (ret) + goto done; + + sca3000_ring_int_process(val, indio_dev); + + if (val & SCA3000_INT_STATUS_FREE_FALL) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_X_AND_Y_AND_Z, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_FALLING), + last_timestamp); + + if (val & SCA3000_INT_STATUS_Y_TRIGGER) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Y, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_RISING), + last_timestamp); + + if (val & SCA3000_INT_STATUS_X_TRIGGER) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_X, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_RISING), + last_timestamp); + + if (val & SCA3000_INT_STATUS_Z_TRIGGER) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Z, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_RISING), + last_timestamp); + +done: + return IRQ_HANDLED; +} + +/* + * sca3000_read_event_config() what events are enabled + */ +static int sca3000_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct sca3000_state *st = iio_priv(indio_dev); + int ret; + /* read current value of mode register */ + mutex_lock(&st->lock); + + ret = sca3000_read_data_short(st, SCA3000_REG_MODE_ADDR, 1); + if (ret) + goto error_ret; + + switch (chan->channel2) { + case IIO_MOD_X_AND_Y_AND_Z: + ret = !!(st->rx[0] & SCA3000_REG_MODE_FREE_FALL_DETECT); + break; + case IIO_MOD_X: + case IIO_MOD_Y: + case IIO_MOD_Z: + /* + * Motion detection mode cannot run at the same time as + * acceleration data being read. + */ + if ((st->rx[0] & SCA3000_REG_MODE_MODE_MASK) + != SCA3000_REG_MODE_MEAS_MODE_MOT_DET) { + ret = 0; + } else { + ret = sca3000_read_ctrl_reg(st, + SCA3000_REG_CTRL_SEL_MD_CTRL); + if (ret < 0) + goto error_ret; + /* only supporting logical or's for now */ + ret = !!(ret & sca3000_addresses[chan->address][2]); + } + break; + default: + ret = -EINVAL; + } + +error_ret: + mutex_unlock(&st->lock); + + return ret; +} + +static int sca3000_freefall_set_state(struct iio_dev *indio_dev, bool state) +{ + struct sca3000_state *st = iio_priv(indio_dev); + int ret; + + /* read current value of mode register */ + ret = sca3000_read_data_short(st, SCA3000_REG_MODE_ADDR, 1); + if (ret) + return ret; + + /* if off and should be on */ + if (state && !(st->rx[0] & SCA3000_REG_MODE_FREE_FALL_DETECT)) + return sca3000_write_reg(st, SCA3000_REG_MODE_ADDR, + st->rx[0] | SCA3000_REG_MODE_FREE_FALL_DETECT); + /* if on and should be off */ + else if (!state && (st->rx[0] & SCA3000_REG_MODE_FREE_FALL_DETECT)) + return sca3000_write_reg(st, SCA3000_REG_MODE_ADDR, + st->rx[0] & ~SCA3000_REG_MODE_FREE_FALL_DETECT); + else + return 0; +} + +static int sca3000_motion_detect_set_state(struct iio_dev *indio_dev, int axis, + bool state) +{ + struct sca3000_state *st = iio_priv(indio_dev); + int ret, ctrlval; + + /* + * First read the motion detector config to find out if + * this axis is on + */ + ret = sca3000_read_ctrl_reg(st, SCA3000_REG_CTRL_SEL_MD_CTRL); + if (ret < 0) + return ret; + ctrlval = ret; + /* if off and should be on */ + if (state && !(ctrlval & sca3000_addresses[axis][2])) { + ret = sca3000_write_ctrl_reg(st, + SCA3000_REG_CTRL_SEL_MD_CTRL, + ctrlval | + sca3000_addresses[axis][2]); + if (ret) + return ret; + st->mo_det_use_count++; + } else if (!state && (ctrlval & sca3000_addresses[axis][2])) { + ret = sca3000_write_ctrl_reg(st, + SCA3000_REG_CTRL_SEL_MD_CTRL, + ctrlval & + ~(sca3000_addresses[axis][2])); + if (ret) + return ret; + st->mo_det_use_count--; + } + + /* read current value of mode register */ + ret = sca3000_read_data_short(st, SCA3000_REG_MODE_ADDR, 1); + if (ret) + return ret; + /* if off and should be on */ + if ((st->mo_det_use_count) && + ((st->rx[0] & SCA3000_REG_MODE_MODE_MASK) + != SCA3000_REG_MODE_MEAS_MODE_MOT_DET)) + return sca3000_write_reg(st, SCA3000_REG_MODE_ADDR, + (st->rx[0] & ~SCA3000_REG_MODE_MODE_MASK) + | SCA3000_REG_MODE_MEAS_MODE_MOT_DET); + /* if on and should be off */ + else if (!(st->mo_det_use_count) && + ((st->rx[0] & SCA3000_REG_MODE_MODE_MASK) + == SCA3000_REG_MODE_MEAS_MODE_MOT_DET)) + return sca3000_write_reg(st, SCA3000_REG_MODE_ADDR, + st->rx[0] & SCA3000_REG_MODE_MODE_MASK); + else + return 0; +} + +/** + * sca3000_write_event_config() - simple on off control for motion detector + * @indio_dev: IIO device instance specific structure. Data specific to this + * particular driver may be accessed via iio_priv(indio_dev). + * @chan: Description of the channel whose event we are configuring. + * @type: The type of event. + * @dir: The direction of the event. + * @state: Desired state of event being configured. + * + * This is a per axis control, but enabling any will result in the + * motion detector unit being enabled. + * N.B. enabling motion detector stops normal data acquisition. + * There is a complexity in knowing which mode to return to when + * this mode is disabled. Currently normal mode is assumed. + **/ +static int sca3000_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct sca3000_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->lock); + switch (chan->channel2) { + case IIO_MOD_X_AND_Y_AND_Z: + ret = sca3000_freefall_set_state(indio_dev, state); + break; + + case IIO_MOD_X: + case IIO_MOD_Y: + case IIO_MOD_Z: + ret = sca3000_motion_detect_set_state(indio_dev, + chan->address, + state); + break; + default: + ret = -EINVAL; + break; + } + mutex_unlock(&st->lock); + + return ret; +} + +static inline +int __sca3000_hw_ring_state_set(struct iio_dev *indio_dev, bool state) +{ + struct sca3000_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->lock); + ret = sca3000_read_data_short(st, SCA3000_REG_MODE_ADDR, 1); + if (ret) + goto error_ret; + if (state) { + dev_info(&indio_dev->dev, "supposedly enabling ring buffer\n"); + ret = sca3000_write_reg(st, + SCA3000_REG_MODE_ADDR, + (st->rx[0] | SCA3000_REG_MODE_RING_BUF_ENABLE)); + } else + ret = sca3000_write_reg(st, + SCA3000_REG_MODE_ADDR, + (st->rx[0] & ~SCA3000_REG_MODE_RING_BUF_ENABLE)); +error_ret: + mutex_unlock(&st->lock); + + return ret; +} + +/** + * sca3000_hw_ring_preenable() - hw ring buffer preenable function + * @indio_dev: structure representing the IIO device. Device instance + * specific state can be accessed via iio_priv(indio_dev). + * + * Very simple enable function as the chip will allows normal reads + * during ring buffer operation so as long as it is indeed running + * before we notify the core, the precise ordering does not matter. + */ +static int sca3000_hw_ring_preenable(struct iio_dev *indio_dev) +{ + int ret; + struct sca3000_state *st = iio_priv(indio_dev); + + mutex_lock(&st->lock); + + /* Enable the 50% full interrupt */ + ret = sca3000_read_data_short(st, SCA3000_REG_INT_MASK_ADDR, 1); + if (ret) + goto error_unlock; + ret = sca3000_write_reg(st, + SCA3000_REG_INT_MASK_ADDR, + st->rx[0] | SCA3000_REG_INT_MASK_RING_HALF); + if (ret) + goto error_unlock; + + mutex_unlock(&st->lock); + + return __sca3000_hw_ring_state_set(indio_dev, 1); + +error_unlock: + mutex_unlock(&st->lock); + + return ret; +} + +static int sca3000_hw_ring_postdisable(struct iio_dev *indio_dev) +{ + int ret; + struct sca3000_state *st = iio_priv(indio_dev); + + ret = __sca3000_hw_ring_state_set(indio_dev, 0); + if (ret) + return ret; + + /* Disable the 50% full interrupt */ + mutex_lock(&st->lock); + + ret = sca3000_read_data_short(st, SCA3000_REG_INT_MASK_ADDR, 1); + if (ret) + goto unlock; + ret = sca3000_write_reg(st, + SCA3000_REG_INT_MASK_ADDR, + st->rx[0] & ~SCA3000_REG_INT_MASK_RING_HALF); +unlock: + mutex_unlock(&st->lock); + return ret; +} + +static const struct iio_buffer_setup_ops sca3000_ring_setup_ops = { + .preenable = &sca3000_hw_ring_preenable, + .postdisable = &sca3000_hw_ring_postdisable, +}; + +/** + * sca3000_clean_setup() - get the device into a predictable state + * @st: Device instance specific private data structure + * + * Devices use flash memory to store many of the register values + * and hence can come up in somewhat unpredictable states. + * Hence reset everything on driver load. + */ +static int sca3000_clean_setup(struct sca3000_state *st) +{ + int ret; + + mutex_lock(&st->lock); + /* Ensure all interrupts have been acknowledged */ + ret = sca3000_read_data_short(st, SCA3000_REG_INT_STATUS_ADDR, 1); + if (ret) + goto error_ret; + + /* Turn off all motion detection channels */ + ret = sca3000_read_ctrl_reg(st, SCA3000_REG_CTRL_SEL_MD_CTRL); + if (ret < 0) + goto error_ret; + ret = sca3000_write_ctrl_reg(st, SCA3000_REG_CTRL_SEL_MD_CTRL, + ret & SCA3000_MD_CTRL_PROT_MASK); + if (ret) + goto error_ret; + + /* Disable ring buffer */ + ret = sca3000_read_ctrl_reg(st, SCA3000_REG_CTRL_SEL_OUT_CTRL); + if (ret < 0) + goto error_ret; + ret = sca3000_write_ctrl_reg(st, SCA3000_REG_CTRL_SEL_OUT_CTRL, + (ret & SCA3000_REG_OUT_CTRL_PROT_MASK) + | SCA3000_REG_OUT_CTRL_BUF_X_EN + | SCA3000_REG_OUT_CTRL_BUF_Y_EN + | SCA3000_REG_OUT_CTRL_BUF_Z_EN + | SCA3000_REG_OUT_CTRL_BUF_DIV_4); + if (ret) + goto error_ret; + /* Enable interrupts, relevant to mode and set up as active low */ + ret = sca3000_read_data_short(st, SCA3000_REG_INT_MASK_ADDR, 1); + if (ret) + goto error_ret; + ret = sca3000_write_reg(st, + SCA3000_REG_INT_MASK_ADDR, + (ret & SCA3000_REG_INT_MASK_PROT_MASK) + | SCA3000_REG_INT_MASK_ACTIVE_LOW); + if (ret) + goto error_ret; + /* + * Select normal measurement mode, free fall off, ring off + * Ring in 12 bit mode - it is fine to overwrite reserved bits 3,5 + * as that occurs in one of the example on the datasheet + */ + ret = sca3000_read_data_short(st, SCA3000_REG_MODE_ADDR, 1); + if (ret) + goto error_ret; + ret = sca3000_write_reg(st, SCA3000_REG_MODE_ADDR, + (st->rx[0] & SCA3000_MODE_PROT_MASK)); + +error_ret: + mutex_unlock(&st->lock); + return ret; +} + +static const struct iio_info sca3000_info = { + .attrs = &sca3000_attribute_group, + .read_raw = &sca3000_read_raw, + .write_raw = &sca3000_write_raw, + .read_event_value = &sca3000_read_event_value, + .write_event_value = &sca3000_write_event_value, + .read_event_config = &sca3000_read_event_config, + .write_event_config = &sca3000_write_event_config, +}; + +static void sca3000_stop_all_interrupts(void *data) +{ + struct iio_dev *indio_dev = data; + struct sca3000_state *st = iio_priv(indio_dev); + int ret; + + guard(mutex)(&st->lock); + + ret = sca3000_read_data_short(st, SCA3000_REG_INT_MASK_ADDR, 1); + if (ret) + return; + + sca3000_write_reg(st, SCA3000_REG_INT_MASK_ADDR, + (st->rx[0] & + ~(SCA3000_REG_INT_MASK_RING_THREE_QUARTER | + SCA3000_REG_INT_MASK_RING_HALF | + SCA3000_REG_INT_MASK_ALL_INTS))); +} + +static int sca3000_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct sca3000_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->us = spi; + mutex_init(&st->lock); + st->info = spi_get_device_match_data(spi); + + indio_dev->name = st->info->name; + indio_dev->info = &sca3000_info; + if (st->info->temp_output) { + indio_dev->channels = sca3000_channels_with_temp; + indio_dev->num_channels = + ARRAY_SIZE(sca3000_channels_with_temp); + } else { + indio_dev->channels = sca3000_channels; + indio_dev->num_channels = ARRAY_SIZE(sca3000_channels); + } + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_iio_kfifo_buffer_setup(dev, indio_dev, &sca3000_ring_setup_ops); + if (ret) + return ret; + + if (spi->irq) { + ret = devm_request_threaded_irq(dev, spi->irq, NULL, + &sca3000_event_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "sca3000", + indio_dev); + if (ret) + return ret; + } + ret = sca3000_clean_setup(st); + if (ret) + return ret; + + ret = sca3000_print_rev(indio_dev); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, sca3000_stop_all_interrupts, indio_dev); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id sca3000_id[] = { + { .name = "sca3000_d01", .driver_data = (kernel_ulong_t)&sca3000_chip_info_d01 }, + { .name = "sca3000_e02", .driver_data = (kernel_ulong_t)&sca3000_chip_info_e02 }, + { .name = "sca3000_e04", .driver_data = (kernel_ulong_t)&sca3000_chip_info_e04 }, + { .name = "sca3000_e05", .driver_data = (kernel_ulong_t)&sca3000_chip_info_e05 }, + { } +}; +MODULE_DEVICE_TABLE(spi, sca3000_id); + +static struct spi_driver sca3000_driver = { + .driver = { + .name = "sca3000", + }, + .probe = sca3000_probe, + .id_table = sca3000_id, +}; +module_spi_driver(sca3000_driver); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("VTI SCA3000 Series Accelerometers SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/sca3300.c b/drivers/iio/accel/sca3300.c new file mode 100644 index 000000000..67582cc34 --- /dev/null +++ b/drivers/iio/accel/sca3300.c @@ -0,0 +1,685 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Murata SCA3300 3-axis industrial accelerometer + * + * Copyright (c) 2021 Vaisala Oyj. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#define SCA3300_CRC8_POLYNOMIAL 0x1d + +/* Device mode register */ +#define SCA3300_REG_MODE 0xd +#define SCA3300_MODE_SW_RESET 0x20 + +/* Last register in map */ +#define SCA3300_REG_SELBANK 0x1f + +/* Device status and mask */ +#define SCA3300_REG_STATUS 0x6 +#define SCA3300_STATUS_MASK GENMASK(8, 0) + +/* Device ID */ +#define SCA3300_REG_WHOAMI 0x10 +#define SCA3300_WHOAMI_ID 0x51 +#define SCL3300_WHOAMI_ID 0xC1 + +/* Device return status and mask */ +#define SCA3300_VALUE_RS_ERROR 0x3 +#define SCA3300_MASK_RS_STATUS GENMASK(1, 0) + +#define SCL3300_REG_ANG_CTRL 0x0C +#define SCL3300_ANG_ENABLE 0x1F + +enum sca3300_scan_indexes { + SCA3300_ACC_X = 0, + SCA3300_ACC_Y, + SCA3300_ACC_Z, + SCA3300_TEMP, + SCA3300_INCLI_X, + SCA3300_INCLI_Y, + SCA3300_INCLI_Z, + SCA3300_SCAN_MAX +}; + +#define SCA3300_ACCEL_CHANNEL(index, reg, axis) { \ + .type = IIO_ACCEL, \ + .address = reg, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ +} + +#define SCA3300_INCLI_CHANNEL(index, reg, axis) { \ + .type = IIO_INCLI, \ + .address = reg, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ +} + +#define SCA3300_TEMP_CHANNEL(index, reg) { \ + .type = IIO_TEMP, \ + .address = reg, \ + .scan_index = index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ +} + +static const struct iio_chan_spec sca3300_channels[] = { + SCA3300_ACCEL_CHANNEL(SCA3300_ACC_X, 0x1, X), + SCA3300_ACCEL_CHANNEL(SCA3300_ACC_Y, 0x2, Y), + SCA3300_ACCEL_CHANNEL(SCA3300_ACC_Z, 0x3, Z), + SCA3300_TEMP_CHANNEL(SCA3300_TEMP, 0x05), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const int sca3300_lp_freq[] = {70, 10}; +static const int sca3300_lp_freq_map[] = {0, 0, 0, 1}; + +static const int scl3300_lp_freq[] = {40, 70, 10}; +static const int scl3300_lp_freq_map[] = {0, 1, 2}; + +static const int sca3300_accel_scale[][2] = {{0, 370}, {0, 741}, {0, 185}}; +static const int sca3300_accel_scale_map[] = {0, 1, 2, 2}; + +static const int scl3300_accel_scale[][2] = {{0, 167}, {0, 333}, {0, 83}}; +static const int scl3300_accel_scale_map[] = {0, 1, 2}; + +static const int scl3300_incli_scale[][2] = {{0, 5495}}; +static const int scl3300_incli_scale_map[] = {0, 0, 0}; + +static const int sca3300_avail_modes_map[] = {0, 1, 2, 3}; +static const int scl3300_avail_modes_map[] = {0, 1, 3}; + +static const struct iio_chan_spec scl3300_channels[] = { + SCA3300_ACCEL_CHANNEL(SCA3300_ACC_X, 0x1, X), + SCA3300_ACCEL_CHANNEL(SCA3300_ACC_Y, 0x2, Y), + SCA3300_ACCEL_CHANNEL(SCA3300_ACC_Z, 0x3, Z), + SCA3300_TEMP_CHANNEL(SCA3300_TEMP, 0x05), + SCA3300_INCLI_CHANNEL(SCA3300_INCLI_X, 0x09, X), + SCA3300_INCLI_CHANNEL(SCA3300_INCLI_Y, 0x0A, Y), + SCA3300_INCLI_CHANNEL(SCA3300_INCLI_Z, 0x0B, Z), + IIO_CHAN_SOFT_TIMESTAMP(7), +}; + +static const unsigned long sca3300_scan_masks[] = { + BIT(SCA3300_ACC_X) | BIT(SCA3300_ACC_Y) | BIT(SCA3300_ACC_Z) | + BIT(SCA3300_TEMP), + 0 +}; + +static const unsigned long scl3300_scan_masks[] = { + BIT(SCA3300_ACC_X) | BIT(SCA3300_ACC_Y) | BIT(SCA3300_ACC_Z) | + BIT(SCA3300_TEMP) | + BIT(SCA3300_INCLI_X) | BIT(SCA3300_INCLI_Y) | BIT(SCA3300_INCLI_Z), + 0 +}; + +struct sca3300_chip_info { + const char *name; + const unsigned long *scan_masks; + const struct iio_chan_spec *channels; + u8 num_channels; + u8 num_accel_scales; + const int (*accel_scale)[2]; + const int *accel_scale_map; + const int (*incli_scale)[2]; + const int *incli_scale_map; + u8 num_incli_scales; + u8 num_freqs; + const int *freq_table; + const int *freq_map; + const int *avail_modes_table; + u8 num_avail_modes; + u8 chip_id; + bool angle_supported; +}; + +/** + * struct sca3300_data - device data + * @spi: SPI device structure + * @lock: Data buffer lock + * @chip: Sensor chip specific information + * @txbuf: Transmit buffer + * @rxbuf: Receive buffer + */ +struct sca3300_data { + struct spi_device *spi; + struct mutex lock; + const struct sca3300_chip_info *chip; + u8 txbuf[4] __aligned(IIO_DMA_MINALIGN); + u8 rxbuf[4]; +}; + +static const struct sca3300_chip_info sca3300_chip_tbl[] = { + { + .name = "sca3300", + .scan_masks = sca3300_scan_masks, + .channels = sca3300_channels, + .num_channels = ARRAY_SIZE(sca3300_channels), + .num_accel_scales = ARRAY_SIZE(sca3300_accel_scale)*2, + .accel_scale = sca3300_accel_scale, + .accel_scale_map = sca3300_accel_scale_map, + .num_freqs = ARRAY_SIZE(sca3300_lp_freq), + .freq_table = sca3300_lp_freq, + .freq_map = sca3300_lp_freq_map, + .avail_modes_table = sca3300_avail_modes_map, + .num_avail_modes = 4, + .chip_id = SCA3300_WHOAMI_ID, + .angle_supported = false, + }, + { + .name = "scl3300", + .scan_masks = scl3300_scan_masks, + .channels = scl3300_channels, + .num_channels = ARRAY_SIZE(scl3300_channels), + .num_accel_scales = ARRAY_SIZE(scl3300_accel_scale)*2, + .accel_scale = scl3300_accel_scale, + .accel_scale_map = scl3300_accel_scale_map, + .incli_scale = scl3300_incli_scale, + .incli_scale_map = scl3300_incli_scale_map, + .num_incli_scales = ARRAY_SIZE(scl3300_incli_scale)*2, + .num_freqs = ARRAY_SIZE(scl3300_lp_freq), + .freq_table = scl3300_lp_freq, + .freq_map = scl3300_lp_freq_map, + .avail_modes_table = scl3300_avail_modes_map, + .num_avail_modes = 3, + .chip_id = SCL3300_WHOAMI_ID, + .angle_supported = true, + }, +}; + +DECLARE_CRC8_TABLE(sca3300_crc_table); + +static int sca3300_transfer(struct sca3300_data *sca_data, int *val) +{ + /* Consecutive requests min. 10 us delay (Datasheet section 5.1.2) */ + struct spi_delay delay = { .value = 10, .unit = SPI_DELAY_UNIT_USECS }; + int32_t ret; + int rs; + u8 crc; + struct spi_transfer xfers[2] = { + { + .tx_buf = sca_data->txbuf, + .len = ARRAY_SIZE(sca_data->txbuf), + .delay = delay, + .cs_change = 1, + }, + { + .rx_buf = sca_data->rxbuf, + .len = ARRAY_SIZE(sca_data->rxbuf), + .delay = delay, + } + }; + + /* inverted crc value as described in device data sheet */ + crc = ~crc8(sca3300_crc_table, &sca_data->txbuf[0], 3, CRC8_INIT_VALUE); + sca_data->txbuf[3] = crc; + + ret = spi_sync_transfer(sca_data->spi, xfers, ARRAY_SIZE(xfers)); + if (ret) { + dev_err(&sca_data->spi->dev, + "transfer error, error: %d\n", ret); + return -EIO; + } + + crc = ~crc8(sca3300_crc_table, &sca_data->rxbuf[0], 3, CRC8_INIT_VALUE); + if (sca_data->rxbuf[3] != crc) { + dev_err(&sca_data->spi->dev, "CRC checksum mismatch"); + return -EIO; + } + + /* get return status */ + rs = sca_data->rxbuf[0] & SCA3300_MASK_RS_STATUS; + if (rs == SCA3300_VALUE_RS_ERROR) + ret = -EINVAL; + + *val = sign_extend32(get_unaligned_be16(&sca_data->rxbuf[1]), 15); + + return ret; +} + +static int sca3300_error_handler(struct sca3300_data *sca_data) +{ + int ret; + int val; + + mutex_lock(&sca_data->lock); + sca_data->txbuf[0] = SCA3300_REG_STATUS << 2; + ret = sca3300_transfer(sca_data, &val); + mutex_unlock(&sca_data->lock); + /* + * Return status error is cleared after reading status register once, + * expect EINVAL here. + */ + if (ret != -EINVAL) { + dev_err(&sca_data->spi->dev, + "error reading device status: %d\n", ret); + return ret; + } + + dev_err(&sca_data->spi->dev, "device status: 0x%lx\n", + val & SCA3300_STATUS_MASK); + + return 0; +} + +static int sca3300_read_reg(struct sca3300_data *sca_data, u8 reg, int *val) +{ + int ret; + + mutex_lock(&sca_data->lock); + sca_data->txbuf[0] = reg << 2; + ret = sca3300_transfer(sca_data, val); + mutex_unlock(&sca_data->lock); + if (ret != -EINVAL) + return ret; + + return sca3300_error_handler(sca_data); +} + +static int sca3300_write_reg(struct sca3300_data *sca_data, u8 reg, int val) +{ + int reg_val = 0; + int ret; + + mutex_lock(&sca_data->lock); + /* BIT(7) for write operation */ + sca_data->txbuf[0] = BIT(7) | (reg << 2); + put_unaligned_be16(val, &sca_data->txbuf[1]); + ret = sca3300_transfer(sca_data, ®_val); + mutex_unlock(&sca_data->lock); + if (ret != -EINVAL) + return ret; + + return sca3300_error_handler(sca_data); +} + +static int sca3300_set_op_mode(struct sca3300_data *sca_data, int index) +{ + if ((index < 0) || (index >= sca_data->chip->num_avail_modes)) + return -EINVAL; + + return sca3300_write_reg(sca_data, SCA3300_REG_MODE, + sca_data->chip->avail_modes_table[index]); +} + +static int sca3300_get_op_mode(struct sca3300_data *sca_data, int *index) +{ + int reg_val; + int ret; + int i; + + ret = sca3300_read_reg(sca_data, SCA3300_REG_MODE, ®_val); + if (ret) + return ret; + + for (i = 0; i < sca_data->chip->num_avail_modes; i++) { + if (sca_data->chip->avail_modes_table[i] == reg_val) + break; + } + if (i == sca_data->chip->num_avail_modes) + return -EINVAL; + + *index = i; + return 0; +} + +static int sca3300_set_frequency(struct sca3300_data *data, int val) +{ + const struct sca3300_chip_info *chip = data->chip; + unsigned int index; + int *opmode_scale; + int *new_scale; + unsigned int i; + + if (sca3300_get_op_mode(data, &index)) + return -EINVAL; + + /* + * Find a mode in which the requested sampling frequency is available + * and the scaling currently set is retained. + */ + opmode_scale = (int *)chip->accel_scale[chip->accel_scale_map[index]]; + for (i = 0; i < chip->num_avail_modes; i++) { + new_scale = (int *)chip->accel_scale[chip->accel_scale_map[i]]; + if ((val == chip->freq_table[chip->freq_map[i]]) && + (opmode_scale[1] == new_scale[1]) && + (opmode_scale[0] == new_scale[0])) + break; + } + if (i == chip->num_avail_modes) + return -EINVAL; + + return sca3300_set_op_mode(data, i); +} + +static int sca3300_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct sca3300_data *data = iio_priv(indio_dev); + int index; + int i; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (chan->type != IIO_ACCEL) + return -EINVAL; + /* + * Letting scale take priority over sampling frequency. + * That makes sense given we can only ever end up increasing + * the sampling frequency which is unlikely to be a problem. + */ + for (i = 0; i < data->chip->num_avail_modes; i++) { + index = data->chip->accel_scale_map[i]; + if ((val == data->chip->accel_scale[index][0]) && + (val2 == data->chip->accel_scale[index][1])) + return sca3300_set_op_mode(data, i); + } + return -EINVAL; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return sca3300_set_frequency(data, val); + default: + return -EINVAL; + } +} + +static int sca3300_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct sca3300_data *data = iio_priv(indio_dev); + int index; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = sca3300_read_reg(data, chan->address, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = sca3300_get_op_mode(data, &index); + if (ret) + return ret; + switch (chan->type) { + case IIO_INCLI: + index = data->chip->incli_scale_map[index]; + *val = data->chip->incli_scale[index][0]; + *val2 = data->chip->incli_scale[index][1]; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ACCEL: + index = data->chip->accel_scale_map[index]; + *val = data->chip->accel_scale[index][0]; + *val2 = data->chip->accel_scale[index][1]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + ret = sca3300_get_op_mode(data, &index); + if (ret) + return ret; + index = data->chip->freq_map[index]; + *val = data->chip->freq_table[index]; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static irqreturn_t sca3300_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct sca3300_data *data = iio_priv(indio_dev); + int bit, ret, val, i = 0; + IIO_DECLARE_BUFFER_WITH_TS(s16, channels, SCA3300_SCAN_MAX) = { }; + + iio_for_each_active_channel(indio_dev, bit) { + ret = sca3300_read_reg(data, indio_dev->channels[bit].address, &val); + if (ret) { + dev_err_ratelimited(&data->spi->dev, + "failed to read register, error: %d\n", ret); + /* handled, but bailing out due to errors */ + goto out; + } + channels[i++] = val; + } + + iio_push_to_buffers_with_ts(indio_dev, channels, sizeof(channels), + iio_get_time_ns(indio_dev)); +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +/* + * sca3300_init - Device init sequence. See datasheet rev 2 section + * 4.2 Start-Up Sequence for details. + */ +static int sca3300_init(struct sca3300_data *sca_data, + struct iio_dev *indio_dev) +{ + int value = 0; + int ret; + int i; + + ret = sca3300_write_reg(sca_data, SCA3300_REG_MODE, + SCA3300_MODE_SW_RESET); + if (ret) + return ret; + + /* + * Wait 1ms after SW-reset command. + * Wait for the settling of signal paths, + * 15ms for SCA3300 and 25ms for SCL3300, + */ + usleep_range(26e3, 50e3); + + ret = sca3300_read_reg(sca_data, SCA3300_REG_WHOAMI, &value); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(sca3300_chip_tbl); i++) { + if (sca3300_chip_tbl[i].chip_id == value) + break; + } + if (i == ARRAY_SIZE(sca3300_chip_tbl)) { + dev_err(&sca_data->spi->dev, "unknown chip id %x\n", value); + return -ENODEV; + } + + sca_data->chip = &sca3300_chip_tbl[i]; + + if (sca_data->chip->angle_supported) { + ret = sca3300_write_reg(sca_data, SCL3300_REG_ANG_CTRL, + SCL3300_ANG_ENABLE); + if (ret) + return ret; + } + + return 0; +} + +static int sca3300_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + struct sca3300_data *data = iio_priv(indio_dev); + int value; + int ret; + + if (reg > SCA3300_REG_SELBANK) + return -EINVAL; + + if (!readval) + return sca3300_write_reg(data, reg, writeval); + + ret = sca3300_read_reg(data, reg, &value); + if (ret) + return ret; + + *readval = value; + + return 0; +} + +static int sca3300_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct sca3300_data *data = iio_priv(indio_dev); + switch (mask) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_INCLI: + *vals = (const int *)data->chip->incli_scale; + *length = data->chip->num_incli_scales; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + case IIO_ACCEL: + *vals = (const int *)data->chip->accel_scale; + *length = data->chip->num_accel_scales; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + *vals = (const int *)data->chip->freq_table; + *length = data->chip->num_freqs; + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const struct iio_info sca3300_info = { + .read_raw = sca3300_read_raw, + .write_raw = sca3300_write_raw, + .debugfs_reg_access = &sca3300_debugfs_reg_access, + .read_avail = sca3300_read_avail, +}; + +static int sca3300_probe(struct spi_device *spi) +{ + struct sca3300_data *sca_data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*sca_data)); + if (!indio_dev) + return -ENOMEM; + + sca_data = iio_priv(indio_dev); + mutex_init(&sca_data->lock); + sca_data->spi = spi; + + crc8_populate_msb(sca3300_crc_table, SCA3300_CRC8_POLYNOMIAL); + + indio_dev->info = &sca3300_info; + + ret = sca3300_init(sca_data, indio_dev); + if (ret) { + dev_err(&spi->dev, "failed to init device, error: %d\n", ret); + return ret; + } + + indio_dev->name = sca_data->chip->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = sca_data->chip->channels; + indio_dev->num_channels = sca_data->chip->num_channels; + indio_dev->available_scan_masks = sca_data->chip->scan_masks; + + ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, + iio_pollfunc_store_time, + sca3300_trigger_handler, NULL); + if (ret) { + dev_err(&spi->dev, + "iio triggered buffer setup failed, error: %d\n", ret); + return ret; + } + + ret = devm_iio_device_register(&spi->dev, indio_dev); + if (ret) { + dev_err(&spi->dev, "iio device register failed, error: %d\n", + ret); + } + + return ret; +} + +static const struct of_device_id sca3300_dt_ids[] = { + { .compatible = "murata,sca3300"}, + { .compatible = "murata,scl3300"}, + { } +}; +MODULE_DEVICE_TABLE(of, sca3300_dt_ids); + +static const struct spi_device_id sca3300_ids[] = { + { .name = "sca3300" }, + { .name = "scl3300" }, + { } +}; +MODULE_DEVICE_TABLE(spi, sca3300_ids); + +static struct spi_driver sca3300_driver = { + .driver = { + .name = "sca3300", + .of_match_table = sca3300_dt_ids, + }, + .probe = sca3300_probe, + .id_table = sca3300_ids, +}; +module_spi_driver(sca3300_driver); + +MODULE_AUTHOR("Tomas Melin "); +MODULE_DESCRIPTION("Murata SCA3300 SPI Accelerometer"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/ssp_accel_sensor.c b/drivers/iio/accel/ssp_accel_sensor.c new file mode 100644 index 000000000..d1687cdd3 --- /dev/null +++ b/drivers/iio/accel/ssp_accel_sensor.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2014, Samsung Electronics Co. Ltd. All Rights Reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../common/ssp_sensors/ssp_iio_sensor.h" + +#define SSP_CHANNEL_COUNT 3 + +#define SSP_ACCEL_NAME "ssp-accelerometer" +static const char ssp_accel_device_name[] = SSP_ACCEL_NAME; + +enum ssp_accel_3d_channel { + SSP_CHANNEL_SCAN_INDEX_X, + SSP_CHANNEL_SCAN_INDEX_Y, + SSP_CHANNEL_SCAN_INDEX_Z, + SSP_CHANNEL_SCAN_INDEX_TIME, +}; + +static int ssp_accel_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + u32 t; + struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + t = ssp_get_sensor_delay(data, SSP_ACCELEROMETER_SENSOR); + ssp_convert_to_freq(t, val, val2); + return IIO_VAL_INT_PLUS_MICRO; + default: + break; + } + + return -EINVAL; +} + +static int ssp_accel_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + int ret; + struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = ssp_convert_to_time(val, val2); + ret = ssp_change_delay(data, SSP_ACCELEROMETER_SENSOR, ret); + if (ret < 0) + dev_err(&indio_dev->dev, "accel sensor enable fail\n"); + + return ret; + default: + break; + } + + return -EINVAL; +} + +static const struct iio_info ssp_accel_iio_info = { + .read_raw = &ssp_accel_read_raw, + .write_raw = &ssp_accel_write_raw, +}; + +static const unsigned long ssp_accel_scan_mask[] = { 0x7, 0, }; + +static const struct iio_chan_spec ssp_acc_channels[] = { + SSP_CHANNEL_AG(IIO_ACCEL, IIO_MOD_X, SSP_CHANNEL_SCAN_INDEX_X), + SSP_CHANNEL_AG(IIO_ACCEL, IIO_MOD_Y, SSP_CHANNEL_SCAN_INDEX_Y), + SSP_CHANNEL_AG(IIO_ACCEL, IIO_MOD_Z, SSP_CHANNEL_SCAN_INDEX_Z), + IIO_CHAN_SOFT_TIMESTAMP(SSP_CHANNEL_SCAN_INDEX_TIME), +}; + +static int ssp_process_accel_data(struct iio_dev *indio_dev, void *buf, + int64_t timestamp) +{ + return ssp_common_process_data(indio_dev, buf, SSP_ACCELEROMETER_SIZE, + timestamp); +} + +static const struct iio_buffer_setup_ops ssp_accel_buffer_ops = { + .postenable = &ssp_common_buffer_postenable, + .postdisable = &ssp_common_buffer_postdisable, +}; + +static int ssp_accel_probe(struct platform_device *pdev) +{ + int ret; + struct iio_dev *indio_dev; + struct ssp_sensor_data *spd; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*spd)); + if (!indio_dev) + return -ENOMEM; + + spd = iio_priv(indio_dev); + + spd->process_data = ssp_process_accel_data; + spd->type = SSP_ACCELEROMETER_SENSOR; + + indio_dev->name = ssp_accel_device_name; + indio_dev->info = &ssp_accel_iio_info; + indio_dev->channels = ssp_acc_channels; + indio_dev->num_channels = ARRAY_SIZE(ssp_acc_channels); + indio_dev->available_scan_masks = ssp_accel_scan_mask; + + ret = devm_iio_kfifo_buffer_setup(&pdev->dev, indio_dev, + &ssp_accel_buffer_ops); + if (ret) + return ret; + + platform_set_drvdata(pdev, indio_dev); + + ret = devm_iio_device_register(&pdev->dev, indio_dev); + if (ret < 0) + return ret; + + /* ssp registering should be done after all iio setup */ + ssp_register_consumer(indio_dev, SSP_ACCELEROMETER_SENSOR); + + return 0; +} + +static struct platform_driver ssp_accel_driver = { + .driver = { + .name = SSP_ACCEL_NAME, + }, + .probe = ssp_accel_probe, +}; + +module_platform_driver(ssp_accel_driver); + +MODULE_AUTHOR("Karol Wrona "); +MODULE_DESCRIPTION("Samsung sensorhub accelerometers driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_SSP_SENSORS"); diff --git a/drivers/iio/accel/st_accel.h b/drivers/iio/accel/st_accel.h new file mode 100644 index 000000000..2659f536c --- /dev/null +++ b/drivers/iio/accel/st_accel.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * STMicroelectronics accelerometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + * v. 1.0.0 + */ + +#ifndef ST_ACCEL_H +#define ST_ACCEL_H + +#include +#include + +#define H3LIS331DL_ACCEL_DEV_NAME "h3lis331dl_accel" +#define LIS3LV02DL_ACCEL_DEV_NAME "lis3lv02dl_accel" +#define LSM303DLHC_ACCEL_DEV_NAME "lsm303dlhc_accel" +#define LIS3DH_ACCEL_DEV_NAME "lis3dh" +#define LSM330D_ACCEL_DEV_NAME "lsm330d_accel" +#define LSM330DL_ACCEL_DEV_NAME "lsm330dl_accel" +#define LSM330DLC_ACCEL_DEV_NAME "lsm330dlc_accel" +#define LIS331DL_ACCEL_DEV_NAME "lis331dl_accel" +#define LIS331DLH_ACCEL_DEV_NAME "lis331dlh" +#define LSM303DL_ACCEL_DEV_NAME "lsm303dl_accel" +#define LSM303DLH_ACCEL_DEV_NAME "lsm303dlh_accel" +#define LSM303DLM_ACCEL_DEV_NAME "lsm303dlm_accel" +#define LSM330_ACCEL_DEV_NAME "lsm330_accel" +#define LSM303AGR_ACCEL_DEV_NAME "lsm303agr_accel" +#define LIS2DH12_ACCEL_DEV_NAME "lis2dh12_accel" +#define LIS3L02DQ_ACCEL_DEV_NAME "lis3l02dq" +#define LNG2DM_ACCEL_DEV_NAME "lng2dm" +#define LIS2DW12_ACCEL_DEV_NAME "lis2dw12" +#define LIS3DHH_ACCEL_DEV_NAME "lis3dhh" +#define LIS3DE_ACCEL_DEV_NAME "lis3de" +#define LIS2DE12_ACCEL_DEV_NAME "lis2de12" +#define LIS2DS12_ACCEL_DEV_NAME "lis2ds12" +#define LIS2HH12_ACCEL_DEV_NAME "lis2hh12" +#define LIS302DL_ACCEL_DEV_NAME "lis302dl" +#define LSM303C_ACCEL_DEV_NAME "lsm303c_accel" +#define SC7A20_ACCEL_DEV_NAME "sc7a20" +#define IIS328DQ_ACCEL_DEV_NAME "iis328dq" + + +#ifdef CONFIG_IIO_BUFFER +int st_accel_allocate_ring(struct iio_dev *indio_dev); +int st_accel_trig_set_state(struct iio_trigger *trig, bool state); +#define ST_ACCEL_TRIGGER_SET_STATE (&st_accel_trig_set_state) +#else /* CONFIG_IIO_BUFFER */ +static inline int st_accel_allocate_ring(struct iio_dev *indio_dev) +{ + return 0; +} +#define ST_ACCEL_TRIGGER_SET_STATE NULL +#endif /* CONFIG_IIO_BUFFER */ + +#endif /* ST_ACCEL_H */ diff --git a/drivers/iio/accel/st_accel_buffer.c b/drivers/iio/accel/st_accel_buffer.c new file mode 100644 index 000000000..b2977ae19 --- /dev/null +++ b/drivers/iio/accel/st_accel_buffer.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics accelerometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include +#include + +#include +#include "st_accel.h" + +int st_accel_trig_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + + return st_sensors_set_dataready_irq(indio_dev, state); +} + +static int st_accel_buffer_postenable(struct iio_dev *indio_dev) +{ + int err; + + err = st_sensors_set_axis_enable(indio_dev, indio_dev->active_scan_mask[0]); + if (err < 0) + return err; + + err = st_sensors_set_enable(indio_dev, true); + if (err < 0) + goto st_accel_buffer_enable_all_axis; + + return 0; + +st_accel_buffer_enable_all_axis: + st_sensors_set_axis_enable(indio_dev, ST_SENSORS_ENABLE_ALL_AXIS); + return err; +} + +static int st_accel_buffer_predisable(struct iio_dev *indio_dev) +{ + int err; + + err = st_sensors_set_enable(indio_dev, false); + if (err < 0) + return err; + + return st_sensors_set_axis_enable(indio_dev, + ST_SENSORS_ENABLE_ALL_AXIS); +} + +static const struct iio_buffer_setup_ops st_accel_buffer_setup_ops = { + .postenable = &st_accel_buffer_postenable, + .predisable = &st_accel_buffer_predisable, +}; + +int st_accel_allocate_ring(struct iio_dev *indio_dev) +{ + return devm_iio_triggered_buffer_setup(indio_dev->dev.parent, indio_dev, + NULL, &st_sensors_trigger_handler, &st_accel_buffer_setup_ops); +} diff --git a/drivers/iio/accel/st_accel_core.c b/drivers/iio/accel/st_accel_core.c new file mode 100644 index 000000000..1a9447c81 --- /dev/null +++ b/drivers/iio/accel/st_accel_core.c @@ -0,0 +1,1622 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics accelerometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "st_accel.h" + +#define ST_ACCEL_NUMBER_DATA_CHANNELS 3 + +/* DEFAULT VALUE FOR SENSORS */ +#define ST_ACCEL_DEFAULT_OUT_X_L_ADDR 0x28 +#define ST_ACCEL_DEFAULT_OUT_Y_L_ADDR 0x2a +#define ST_ACCEL_DEFAULT_OUT_Z_L_ADDR 0x2c + +/* FULLSCALE */ +#define ST_ACCEL_FS_AVL_2G 2 +#define ST_ACCEL_FS_AVL_4G 4 +#define ST_ACCEL_FS_AVL_6G 6 +#define ST_ACCEL_FS_AVL_8G 8 +#define ST_ACCEL_FS_AVL_16G 16 +#define ST_ACCEL_FS_AVL_100G 100 +#define ST_ACCEL_FS_AVL_200G 200 +#define ST_ACCEL_FS_AVL_400G 400 + +static const struct iio_mount_matrix * +st_accel_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct st_sensor_data *adata = iio_priv(indio_dev); + + return &adata->mount_matrix; +} + +static const struct iio_chan_spec_ext_info st_accel_mount_matrix_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, st_accel_get_mount_matrix), + { } +}; + +static const struct iio_chan_spec st_accel_8bit_channels[] = { + ST_SENSORS_LSM_CHANNELS_EXT(IIO_ACCEL, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_X, 1, IIO_MOD_X, 's', IIO_LE, 8, 8, + ST_ACCEL_DEFAULT_OUT_X_L_ADDR+1, + st_accel_mount_matrix_ext_info), + ST_SENSORS_LSM_CHANNELS_EXT(IIO_ACCEL, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Y, 1, IIO_MOD_Y, 's', IIO_LE, 8, 8, + ST_ACCEL_DEFAULT_OUT_Y_L_ADDR+1, + st_accel_mount_matrix_ext_info), + ST_SENSORS_LSM_CHANNELS_EXT(IIO_ACCEL, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Z, 1, IIO_MOD_Z, 's', IIO_LE, 8, 8, + ST_ACCEL_DEFAULT_OUT_Z_L_ADDR+1, + st_accel_mount_matrix_ext_info), + IIO_CHAN_SOFT_TIMESTAMP(3) +}; + +static const struct iio_chan_spec st_accel_12bit_channels[] = { + ST_SENSORS_LSM_CHANNELS_EXT(IIO_ACCEL, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_X, 1, IIO_MOD_X, 's', IIO_LE, 12, 16, + ST_ACCEL_DEFAULT_OUT_X_L_ADDR, + st_accel_mount_matrix_ext_info), + ST_SENSORS_LSM_CHANNELS_EXT(IIO_ACCEL, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Y, 1, IIO_MOD_Y, 's', IIO_LE, 12, 16, + ST_ACCEL_DEFAULT_OUT_Y_L_ADDR, + st_accel_mount_matrix_ext_info), + ST_SENSORS_LSM_CHANNELS_EXT(IIO_ACCEL, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Z, 1, IIO_MOD_Z, 's', IIO_LE, 12, 16, + ST_ACCEL_DEFAULT_OUT_Z_L_ADDR, + st_accel_mount_matrix_ext_info), + IIO_CHAN_SOFT_TIMESTAMP(3) +}; + +static const struct iio_chan_spec st_accel_16bit_channels[] = { + ST_SENSORS_LSM_CHANNELS_EXT(IIO_ACCEL, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_X, 1, IIO_MOD_X, 's', IIO_LE, 16, 16, + ST_ACCEL_DEFAULT_OUT_X_L_ADDR, + st_accel_mount_matrix_ext_info), + ST_SENSORS_LSM_CHANNELS_EXT(IIO_ACCEL, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Y, 1, IIO_MOD_Y, 's', IIO_LE, 16, 16, + ST_ACCEL_DEFAULT_OUT_Y_L_ADDR, + st_accel_mount_matrix_ext_info), + ST_SENSORS_LSM_CHANNELS_EXT(IIO_ACCEL, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Z, 1, IIO_MOD_Z, 's', IIO_LE, 16, 16, + ST_ACCEL_DEFAULT_OUT_Z_L_ADDR, + st_accel_mount_matrix_ext_info), + IIO_CHAN_SOFT_TIMESTAMP(3) +}; + +static const struct st_sensor_settings st_accel_sensors_settings[] = { + { + .wai = 0x33, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LIS3DH_ACCEL_DEV_NAME, + [1] = LSM303DLHC_ACCEL_DEV_NAME, + [2] = LSM330D_ACCEL_DEV_NAME, + [3] = LSM330DL_ACCEL_DEV_NAME, + [4] = LSM330DLC_ACCEL_DEV_NAME, + [5] = LSM303AGR_ACCEL_DEV_NAME, + [6] = LIS2DH12_ACCEL_DEV_NAME, + [7] = LIS3DE_ACCEL_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_accel_12bit_channels, + .odr = { + .addr = 0x20, + .mask = 0xf0, + .odr_avl = { + { .hz = 1, .value = 0x01, }, + { .hz = 10, .value = 0x02, }, + { .hz = 25, .value = 0x03, }, + { .hz = 50, .value = 0x04, }, + { .hz = 100, .value = 0x05, }, + { .hz = 200, .value = 0x06, }, + { .hz = 400, .value = 0x07, }, + { .hz = 1600, .value = 0x08, }, + }, + }, + .pw = { + .addr = 0x20, + .mask = 0xf0, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = 0x23, + .mask = 0x30, + .fs_avl = { + [0] = { + .num = ST_ACCEL_FS_AVL_2G, + .value = 0x00, + .gain = IIO_G_TO_M_S_2(1000), + }, + [1] = { + .num = ST_ACCEL_FS_AVL_4G, + .value = 0x01, + .gain = IIO_G_TO_M_S_2(2000), + }, + [2] = { + .num = ST_ACCEL_FS_AVL_8G, + .value = 0x02, + .gain = IIO_G_TO_M_S_2(4000), + }, + [3] = { + .num = ST_ACCEL_FS_AVL_16G, + .value = 0x03, + .gain = IIO_G_TO_M_S_2(12000), + }, + }, + }, + .bdu = { + .addr = 0x23, + .mask = 0x80, + }, + .drdy_irq = { + .int1 = { + .addr = 0x22, + .mask = 0x10, + }, + .addr_ihl = 0x25, + .mask_ihl = 0x02, + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x07, + }, + }, + .sim = { + .addr = 0x23, + .value = BIT(0), + }, + .multi_read_bit = true, + .bootime = 2, + }, + { + .wai = 0x32, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LIS331DLH_ACCEL_DEV_NAME, + [1] = LSM303DL_ACCEL_DEV_NAME, + [2] = LSM303DLH_ACCEL_DEV_NAME, + [3] = LSM303DLM_ACCEL_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_accel_12bit_channels, + .odr = { + .addr = 0x20, + .mask = 0x18, + .odr_avl = { + { .hz = 50, .value = 0x00, }, + { .hz = 100, .value = 0x01, }, + { .hz = 400, .value = 0x02, }, + { .hz = 1000, .value = 0x03, }, + }, + }, + .pw = { + .addr = 0x20, + .mask = 0xe0, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = 0x23, + .mask = 0x30, + .fs_avl = { + [0] = { + .num = ST_ACCEL_FS_AVL_2G, + .value = 0x00, + .gain = IIO_G_TO_M_S_2(1000), + }, + [1] = { + .num = ST_ACCEL_FS_AVL_4G, + .value = 0x01, + .gain = IIO_G_TO_M_S_2(2000), + }, + [2] = { + .num = ST_ACCEL_FS_AVL_8G, + .value = 0x03, + .gain = IIO_G_TO_M_S_2(3900), + }, + }, + }, + .bdu = { + .addr = 0x23, + .mask = 0x80, + }, + .drdy_irq = { + .int1 = { + .addr = 0x22, + .mask = 0x02, + .addr_od = 0x22, + .mask_od = 0x40, + }, + .int2 = { + .addr = 0x22, + .mask = 0x10, + .addr_od = 0x22, + .mask_od = 0x40, + }, + .addr_ihl = 0x22, + .mask_ihl = 0x80, + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x07, + }, + }, + .sim = { + .addr = 0x23, + .value = BIT(0), + }, + .multi_read_bit = true, + .bootime = 2, + }, + { + .wai = 0x40, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LSM330_ACCEL_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_accel_16bit_channels, + .odr = { + .addr = 0x20, + .mask = 0xf0, + .odr_avl = { + { .hz = 3, .value = 0x01, }, + { .hz = 6, .value = 0x02, }, + { .hz = 12, .value = 0x03, }, + { .hz = 25, .value = 0x04, }, + { .hz = 50, .value = 0x05, }, + { .hz = 100, .value = 0x06, }, + { .hz = 200, .value = 0x07, }, + { .hz = 400, .value = 0x08, }, + { .hz = 800, .value = 0x09, }, + { .hz = 1600, .value = 0x0a, }, + }, + }, + .pw = { + .addr = 0x20, + .mask = 0xf0, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = 0x24, + .mask = 0x38, + .fs_avl = { + [0] = { + .num = ST_ACCEL_FS_AVL_2G, + .value = 0x00, + .gain = IIO_G_TO_M_S_2(61), + }, + [1] = { + .num = ST_ACCEL_FS_AVL_4G, + .value = 0x01, + .gain = IIO_G_TO_M_S_2(122), + }, + [2] = { + .num = ST_ACCEL_FS_AVL_6G, + .value = 0x02, + .gain = IIO_G_TO_M_S_2(183), + }, + [3] = { + .num = ST_ACCEL_FS_AVL_8G, + .value = 0x03, + .gain = IIO_G_TO_M_S_2(244), + }, + [4] = { + .num = ST_ACCEL_FS_AVL_16G, + .value = 0x04, + .gain = IIO_G_TO_M_S_2(732), + }, + }, + }, + .bdu = { + .addr = 0x20, + .mask = 0x08, + }, + .drdy_irq = { + .int1 = { + .addr = 0x23, + .mask = 0x80, + }, + .addr_ihl = 0x23, + .mask_ihl = 0x40, + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x07, + }, + .ig1 = { + .en_addr = 0x23, + .en_mask = 0x08, + }, + }, + .sim = { + .addr = 0x24, + .value = BIT(0), + }, + .multi_read_bit = false, + .bootime = 2, + }, + { + .wai = 0x3a, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LIS3LV02DL_ACCEL_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_accel_12bit_channels, + .odr = { + .addr = 0x20, + .mask = 0x30, /* DF1 and DF0 */ + .odr_avl = { + { .hz = 40, .value = 0x00, }, + { .hz = 160, .value = 0x01, }, + { .hz = 640, .value = 0x02, }, + { .hz = 2560, .value = 0x03, }, + }, + }, + .pw = { + .addr = 0x20, + .mask = 0xc0, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = 0x21, + .mask = 0x80, + .fs_avl = { + [0] = { + .num = ST_ACCEL_FS_AVL_2G, + .value = 0x00, + .gain = IIO_G_TO_M_S_2(1000), + }, + [1] = { + .num = ST_ACCEL_FS_AVL_6G, + .value = 0x01, + .gain = IIO_G_TO_M_S_2(3000), + }, + }, + }, + .bdu = { + .addr = 0x21, + .mask = 0x40, + }, + /* + * Data Alignment Setting - needs to be set to get + * left-justified data like all other sensors. + */ + .das = { + .addr = 0x21, + .mask = 0x01, + }, + .drdy_irq = { + .int1 = { + .addr = 0x21, + .mask = 0x04, + }, + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x07, + }, + }, + .sim = { + .addr = 0x21, + .value = BIT(1), + }, + .multi_read_bit = true, + .bootime = 2, /* guess */ + }, + { + .wai = 0x3b, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LIS331DL_ACCEL_DEV_NAME, + [1] = LIS302DL_ACCEL_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_accel_8bit_channels, + .odr = { + .addr = 0x20, + .mask = 0x80, + .odr_avl = { + { .hz = 100, .value = 0x00, }, + { .hz = 400, .value = 0x01, }, + }, + }, + .pw = { + .addr = 0x20, + .mask = 0x40, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = 0x20, + .mask = 0x20, + /* + * TODO: check these resulting gain settings, these are + * not in the datsheet + */ + .fs_avl = { + [0] = { + .num = ST_ACCEL_FS_AVL_2G, + .value = 0x00, + .gain = IIO_G_TO_M_S_2(18000), + }, + [1] = { + .num = ST_ACCEL_FS_AVL_8G, + .value = 0x01, + .gain = IIO_G_TO_M_S_2(72000), + }, + }, + }, + .drdy_irq = { + .int1 = { + .addr = 0x22, + .mask = 0x04, + .addr_od = 0x22, + .mask_od = 0x40, + }, + .int2 = { + .addr = 0x22, + .mask = 0x20, + .addr_od = 0x22, + .mask_od = 0x40, + }, + .addr_ihl = 0x22, + .mask_ihl = 0x80, + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x07, + }, + }, + .sim = { + .addr = 0x21, + .value = BIT(7), + }, + .multi_read_bit = false, + .bootime = 2, /* guess */ + }, + { + .wai = 0x32, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = H3LIS331DL_ACCEL_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_accel_12bit_channels, + .odr = { + .addr = 0x20, + .mask = 0x18, + .odr_avl = { + { .hz = 50, .value = 0x00, }, + { .hz = 100, .value = 0x01, }, + { .hz = 400, .value = 0x02, }, + { .hz = 1000, .value = 0x03, }, + }, + }, + .pw = { + .addr = 0x20, + .mask = 0x20, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = 0x23, + .mask = 0x30, + .fs_avl = { + [0] = { + .num = ST_ACCEL_FS_AVL_100G, + .value = 0x00, + .gain = IIO_G_TO_M_S_2(49000), + }, + [1] = { + .num = ST_ACCEL_FS_AVL_200G, + .value = 0x01, + .gain = IIO_G_TO_M_S_2(98000), + }, + [2] = { + .num = ST_ACCEL_FS_AVL_400G, + .value = 0x03, + .gain = IIO_G_TO_M_S_2(195000), + }, + }, + }, + .bdu = { + .addr = 0x23, + .mask = 0x80, + }, + .drdy_irq = { + .int1 = { + .addr = 0x22, + .mask = 0x02, + }, + .int2 = { + .addr = 0x22, + .mask = 0x10, + }, + .addr_ihl = 0x22, + .mask_ihl = 0x80, + }, + .sim = { + .addr = 0x23, + .value = BIT(0), + }, + .multi_read_bit = true, + .bootime = 2, + }, + { + .wai = 0x32, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = IIS328DQ_ACCEL_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_accel_12bit_channels, + .odr = { + .addr = 0x20, + .mask = 0x18, + .odr_avl = { + { .hz = 50, .value = 0x00, }, + { .hz = 100, .value = 0x01, }, + { .hz = 400, .value = 0x02, }, + { .hz = 1000, .value = 0x03, }, + }, + }, + .pw = { + .addr = 0x20, + .mask = 0x20, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = 0x23, + .mask = 0x30, + .fs_avl = { + [0] = { + .num = ST_ACCEL_FS_AVL_100G, + .value = 0x00, + .gain = IIO_G_TO_M_S_2(980), + }, + [1] = { + .num = ST_ACCEL_FS_AVL_200G, + .value = 0x01, + .gain = IIO_G_TO_M_S_2(1950), + }, + [2] = { + .num = ST_ACCEL_FS_AVL_400G, + .value = 0x03, + .gain = IIO_G_TO_M_S_2(3910), + }, + }, + }, + .bdu = { + .addr = 0x23, + .mask = 0x80, + }, + .drdy_irq = { + .int1 = { + .addr = 0x22, + .mask = 0x02, + }, + .int2 = { + .addr = 0x22, + .mask = 0x10, + }, + .addr_ihl = 0x22, + .mask_ihl = 0x80, + }, + .sim = { + .addr = 0x23, + .value = BIT(0), + }, + .multi_read_bit = true, + .bootime = 2, + }, + { + /* No WAI register present */ + .sensors_supported = { + [0] = LIS3L02DQ_ACCEL_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_accel_12bit_channels, + .odr = { + .addr = 0x20, + .mask = 0x30, + .odr_avl = { + { .hz = 280, .value = 0x00, }, + { .hz = 560, .value = 0x01, }, + { .hz = 1120, .value = 0x02, }, + { .hz = 4480, .value = 0x03, }, + }, + }, + .pw = { + .addr = 0x20, + .mask = 0xc0, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .fs_avl = { + [0] = { + .num = ST_ACCEL_FS_AVL_2G, + .gain = IIO_G_TO_M_S_2(488), + }, + }, + }, + /* + * The part has a BDU bit but if set the data is never + * updated so don't set it. + */ + .bdu = { + }, + .drdy_irq = { + .int1 = { + .addr = 0x21, + .mask = 0x04, + }, + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x07, + }, + }, + .sim = { + .addr = 0x21, + .value = BIT(1), + }, + .multi_read_bit = false, + .bootime = 2, + }, + { + .wai = 0x33, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LNG2DM_ACCEL_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_accel_8bit_channels, + .odr = { + .addr = 0x20, + .mask = 0xf0, + .odr_avl = { + { .hz = 1, .value = 0x01, }, + { .hz = 10, .value = 0x02, }, + { .hz = 25, .value = 0x03, }, + { .hz = 50, .value = 0x04, }, + { .hz = 100, .value = 0x05, }, + { .hz = 200, .value = 0x06, }, + { .hz = 400, .value = 0x07, }, + { .hz = 1600, .value = 0x08, }, + }, + }, + .pw = { + .addr = 0x20, + .mask = 0xf0, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = 0x23, + .mask = 0x30, + .fs_avl = { + [0] = { + .num = ST_ACCEL_FS_AVL_2G, + .value = 0x00, + .gain = IIO_G_TO_M_S_2(15600), + }, + [1] = { + .num = ST_ACCEL_FS_AVL_4G, + .value = 0x01, + .gain = IIO_G_TO_M_S_2(31200), + }, + [2] = { + .num = ST_ACCEL_FS_AVL_8G, + .value = 0x02, + .gain = IIO_G_TO_M_S_2(62500), + }, + [3] = { + .num = ST_ACCEL_FS_AVL_16G, + .value = 0x03, + .gain = IIO_G_TO_M_S_2(187500), + }, + }, + }, + .drdy_irq = { + .int1 = { + .addr = 0x22, + .mask = 0x10, + }, + .addr_ihl = 0x25, + .mask_ihl = 0x02, + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x07, + }, + }, + .sim = { + .addr = 0x23, + .value = BIT(0), + }, + .multi_read_bit = true, + .bootime = 2, + }, + { + .wai = 0x44, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LIS2DW12_ACCEL_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_accel_12bit_channels, + .odr = { + .addr = 0x20, + .mask = 0xf0, + .odr_avl = { + { .hz = 1, .value = 0x01, }, + { .hz = 12, .value = 0x02, }, + { .hz = 25, .value = 0x03, }, + { .hz = 50, .value = 0x04, }, + { .hz = 100, .value = 0x05, }, + { .hz = 200, .value = 0x06, }, + }, + }, + .pw = { + .addr = 0x20, + .mask = 0xf0, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .fs = { + .addr = 0x25, + .mask = 0x30, + .fs_avl = { + [0] = { + .num = ST_ACCEL_FS_AVL_2G, + .value = 0x00, + .gain = IIO_G_TO_M_S_2(976), + }, + [1] = { + .num = ST_ACCEL_FS_AVL_4G, + .value = 0x01, + .gain = IIO_G_TO_M_S_2(1952), + }, + [2] = { + .num = ST_ACCEL_FS_AVL_8G, + .value = 0x02, + .gain = IIO_G_TO_M_S_2(3904), + }, + [3] = { + .num = ST_ACCEL_FS_AVL_16G, + .value = 0x03, + .gain = IIO_G_TO_M_S_2(7808), + }, + }, + }, + .bdu = { + .addr = 0x21, + .mask = 0x08, + }, + .drdy_irq = { + .int1 = { + .addr = 0x23, + .mask = 0x01, + .addr_od = 0x22, + .mask_od = 0x20, + }, + .int2 = { + .addr = 0x24, + .mask = 0x01, + .addr_od = 0x22, + .mask_od = 0x20, + }, + .addr_ihl = 0x22, + .mask_ihl = 0x08, + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x01, + }, + }, + .sim = { + .addr = 0x21, + .value = BIT(0), + }, + .multi_read_bit = false, + .bootime = 2, + }, + { + .wai = 0x11, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LIS3DHH_ACCEL_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_accel_16bit_channels, + .odr = { + /* just ODR = 1100Hz available */ + .odr_avl = { + { .hz = 1100, .value = 0x00, }, + }, + }, + .pw = { + .addr = 0x20, + .mask = 0x80, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .fs = { + .fs_avl = { + [0] = { + .num = ST_ACCEL_FS_AVL_2G, + .gain = IIO_G_TO_M_S_2(76), + }, + }, + }, + .bdu = { + .addr = 0x20, + .mask = 0x01, + }, + .drdy_irq = { + .int1 = { + .addr = 0x21, + .mask = 0x80, + .addr_od = 0x23, + .mask_od = 0x04, + }, + .int2 = { + .addr = 0x22, + .mask = 0x80, + .addr_od = 0x23, + .mask_od = 0x08, + }, + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x07, + }, + }, + .multi_read_bit = false, + .bootime = 2, + }, + { + .wai = 0x33, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LIS2DE12_ACCEL_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_accel_8bit_channels, + .odr = { + .addr = 0x20, + .mask = 0xf0, + .odr_avl = { + { .hz = 1, .value = 0x01, }, + { .hz = 10, .value = 0x02, }, + { .hz = 25, .value = 0x03, }, + { .hz = 50, .value = 0x04, }, + { .hz = 100, .value = 0x05, }, + { .hz = 200, .value = 0x06, }, + { .hz = 400, .value = 0x07, }, + { .hz = 1620, .value = 0x08, }, + { .hz = 5376, .value = 0x09, }, + }, + }, + .pw = { + .addr = 0x20, + .mask = 0xf0, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = 0x23, + .mask = 0x30, + .fs_avl = { + [0] = { + .num = ST_ACCEL_FS_AVL_2G, + .value = 0x00, + .gain = IIO_G_TO_M_S_2(15600), + }, + [1] = { + .num = ST_ACCEL_FS_AVL_4G, + .value = 0x01, + .gain = IIO_G_TO_M_S_2(31200), + }, + [2] = { + .num = ST_ACCEL_FS_AVL_8G, + .value = 0x02, + .gain = IIO_G_TO_M_S_2(62500), + }, + [3] = { + .num = ST_ACCEL_FS_AVL_16G, + .value = 0x03, + .gain = IIO_G_TO_M_S_2(187500), + }, + }, + }, + .drdy_irq = { + .int1 = { + .addr = 0x22, + .mask = 0x10, + }, + .addr_ihl = 0x25, + .mask_ihl = 0x02, + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x07, + }, + }, + .sim = { + .addr = 0x23, + .value = BIT(0), + }, + .multi_read_bit = true, + .bootime = 2, + }, + { + .wai = 0x43, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LIS2DS12_ACCEL_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_accel_16bit_channels, + .odr = { + .addr = 0x20, + .mask = 0xf0, + .odr_avl = { + { .hz = 10, .value = 0x01, }, + { .hz = 50, .value = 0x02, }, + { .hz = 100, .value = 0x03, }, + { .hz = 200, .value = 0x04, }, + { .hz = 400, .value = 0x05, }, + { .hz = 800, .value = 0x06, }, + }, + }, + .pw = { + .addr = 0x20, + .mask = 0xf0, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = 0x20, + .mask = 0x0c, + .fs_avl = { + [0] = { + .num = ST_ACCEL_FS_AVL_2G, + .value = 0x00, + .gain = IIO_G_TO_M_S_2(61), + }, + [1] = { + .num = ST_ACCEL_FS_AVL_4G, + .value = 0x02, + .gain = IIO_G_TO_M_S_2(122), + }, + [2] = { + .num = ST_ACCEL_FS_AVL_8G, + .value = 0x03, + .gain = IIO_G_TO_M_S_2(244), + }, + [3] = { + .num = ST_ACCEL_FS_AVL_16G, + .value = 0x01, + .gain = IIO_G_TO_M_S_2(488), + }, + }, + }, + .bdu = { + .addr = 0x20, + .mask = 0x01, + }, + .drdy_irq = { + .int1 = { + .addr = 0x23, + .mask = 0x01, + }, + .int2 = { + .addr = 0x24, + .mask = 0x01, + }, + .addr_ihl = 0x22, + .mask_ihl = 0x02, + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x01, + }, + }, + .sim = { + .addr = 0x21, + .value = BIT(0), + }, + .multi_read_bit = true, + .bootime = 2, + }, + { + .wai = 0x41, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LIS2HH12_ACCEL_DEV_NAME, + [1] = LSM303C_ACCEL_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_accel_16bit_channels, + .odr = { + .addr = 0x20, + .mask = 0x70, + .odr_avl = { + { .hz = 10, .value = 0x01, }, + { .hz = 50, .value = 0x02, }, + { .hz = 100, .value = 0x03, }, + { .hz = 200, .value = 0x04, }, + { .hz = 400, .value = 0x05, }, + { .hz = 800, .value = 0x06, }, + }, + }, + .pw = { + .addr = 0x20, + .mask = 0x70, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = 0x23, + .mask = 0x30, + .fs_avl = { + [0] = { + .num = ST_ACCEL_FS_AVL_2G, + .value = 0x00, + .gain = IIO_G_TO_M_S_2(61), + }, + [1] = { + .num = ST_ACCEL_FS_AVL_4G, + .value = 0x02, + .gain = IIO_G_TO_M_S_2(122), + }, + [2] = { + .num = ST_ACCEL_FS_AVL_8G, + .value = 0x03, + .gain = IIO_G_TO_M_S_2(244), + }, + }, + }, + .bdu = { + .addr = 0x20, + .mask = 0x08, + }, + .drdy_irq = { + .int1 = { + .addr = 0x22, + .mask = 0x01, + }, + .int2 = { + .addr = 0x25, + .mask = 0x01, + }, + .addr_ihl = 0x24, + .mask_ihl = 0x02, + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x07, + }, + }, + .sim = { + .addr = 0x23, + .value = BIT(0), + }, + .multi_read_bit = true, + .bootime = 2, + }, + { + .wai = 0x49, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LSM9DS0_IMU_DEV_NAME, + [1] = LSM303D_IMU_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_accel_16bit_channels, + .odr = { + .addr = 0x20, + .mask = GENMASK(7, 4), + .odr_avl = { + { 3, 0x01, }, + { 6, 0x02, }, + { 12, 0x03, }, + { 25, 0x04, }, + { 50, 0x05, }, + { 100, 0x06, }, + { 200, 0x07, }, + { 400, 0x08, }, + { 800, 0x09, }, + { 1600, 0x0a, }, + }, + }, + .pw = { + .addr = 0x20, + .mask = GENMASK(7, 4), + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = 0x21, + .mask = GENMASK(5, 3), + .fs_avl = { + [0] = { + .num = ST_ACCEL_FS_AVL_2G, + .value = 0x00, + .gain = IIO_G_TO_M_S_2(61), + }, + [1] = { + .num = ST_ACCEL_FS_AVL_4G, + .value = 0x01, + .gain = IIO_G_TO_M_S_2(122), + }, + [2] = { + .num = ST_ACCEL_FS_AVL_6G, + .value = 0x02, + .gain = IIO_G_TO_M_S_2(183), + }, + [3] = { + .num = ST_ACCEL_FS_AVL_8G, + .value = 0x03, + .gain = IIO_G_TO_M_S_2(244), + }, + [4] = { + .num = ST_ACCEL_FS_AVL_16G, + .value = 0x04, + .gain = IIO_G_TO_M_S_2(732), + }, + }, + }, + .bdu = { + .addr = 0x20, + .mask = BIT(3), + }, + .drdy_irq = { + .int1 = { + .addr = 0x22, + .mask = BIT(2), + }, + .int2 = { + .addr = 0x23, + .mask = BIT(3), + }, + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = GENMASK(2, 0), + }, + }, + .sim = { + .addr = 0x21, + .value = BIT(0), + }, + .multi_read_bit = true, + .bootime = 2, + }, + { + /* + * Not an ST part. Register-compatible with the LIS2DH, even + * though the WAI value is different. + */ + .wai = 0x11, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = SC7A20_ACCEL_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_accel_12bit_channels, + .odr = { + .addr = 0x20, + .mask = 0xf0, + .odr_avl = { + { .hz = 1, .value = 0x01, }, + { .hz = 10, .value = 0x02, }, + { .hz = 25, .value = 0x03, }, + { .hz = 50, .value = 0x04, }, + { .hz = 100, .value = 0x05, }, + { .hz = 200, .value = 0x06, }, + { .hz = 400, .value = 0x07, }, + { .hz = 1600, .value = 0x08, }, + }, + }, + .pw = { + .addr = 0x20, + .mask = 0xf0, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = 0x23, + .mask = 0x30, + .fs_avl = { + [0] = { + .num = ST_ACCEL_FS_AVL_2G, + .value = 0x00, + .gain = IIO_G_TO_M_S_2(1000), + }, + [1] = { + .num = ST_ACCEL_FS_AVL_4G, + .value = 0x01, + .gain = IIO_G_TO_M_S_2(2000), + }, + [2] = { + .num = ST_ACCEL_FS_AVL_8G, + .value = 0x02, + .gain = IIO_G_TO_M_S_2(4000), + }, + [3] = { + .num = ST_ACCEL_FS_AVL_16G, + .value = 0x03, + .gain = IIO_G_TO_M_S_2(12000), + }, + }, + }, + .bdu = { + .addr = 0x23, + .mask = 0x80, + }, + .drdy_irq = { + .int1 = { + .addr = 0x22, + .mask = 0x10, + }, + .addr_ihl = 0x25, + .mask_ihl = 0x02, + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x07, + }, + }, + .sim = { + .addr = 0x23, + .value = BIT(0), + }, + .multi_read_bit = true, + .bootime = 2, + }, +}; + +/* Default accel DRDY is available on INT1 pin */ +static const struct st_sensors_platform_data default_accel_pdata = { + .drdy_int_pin = 1, +}; + +static int st_accel_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *ch, int *val, + int *val2, long mask) +{ + int err; + struct st_sensor_data *adata = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + err = st_sensors_read_info_raw(indio_dev, ch, val); + if (err < 0) + goto read_error; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = adata->current_fullscale->gain / 1000000; + *val2 = adata->current_fullscale->gain % 1000000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = adata->odr; + return IIO_VAL_INT; + default: + return -EINVAL; + } + +read_error: + return err; +} + +static int st_accel_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: { + int gain; + + gain = val * 1000000 + val2; + return st_sensors_set_fullscale_by_gain(indio_dev, gain); + } + case IIO_CHAN_INFO_SAMP_FREQ: + if (val2) + return -EINVAL; + + return st_sensors_set_odr(indio_dev, val); + default: + return -EINVAL; + } +} + +static ST_SENSORS_DEV_ATTR_SAMP_FREQ_AVAIL(); +static ST_SENSORS_DEV_ATTR_SCALE_AVAIL(in_accel_scale_available); + +static struct attribute *st_accel_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_accel_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group st_accel_attribute_group = { + .attrs = st_accel_attributes, +}; + +static const struct iio_info accel_info = { + .attrs = &st_accel_attribute_group, + .read_raw = &st_accel_read_raw, + .write_raw = &st_accel_write_raw, + .debugfs_reg_access = &st_sensors_debugfs_reg_access, +}; + +#ifdef CONFIG_IIO_TRIGGER +static const struct iio_trigger_ops st_accel_trigger_ops = { + .set_trigger_state = ST_ACCEL_TRIGGER_SET_STATE, + .validate_device = st_sensors_validate_device, +}; +#define ST_ACCEL_TRIGGER_OPS (&st_accel_trigger_ops) +#else +#define ST_ACCEL_TRIGGER_OPS NULL +#endif + +#ifdef CONFIG_ACPI +/* Read ST-specific _ONT orientation data from ACPI and generate an + * appropriate mount matrix. + */ +static int apply_acpi_orientation(struct iio_dev *indio_dev) +{ + struct st_sensor_data *adata = iio_priv(indio_dev); + struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; + struct acpi_device *adev; + union acpi_object *ont; + union acpi_object *elements; + acpi_status status; + struct device *parent = indio_dev->dev.parent; + int ret = -EINVAL; + unsigned int val; + int i, j; + int final_ont[3][3] = { { 0 }, }; + + /* For some reason, ST's _ONT translation does not apply directly + * to the data read from the sensor. Another translation must be + * performed first, as described by the matrix below. Perhaps + * ST required this specific translation for the first product + * where the device was mounted? + */ + const int default_ont[3][3] = { + { 0, 1, 0 }, + { -1, 0, 0 }, + { 0, 0, -1 }, + }; + + + adev = ACPI_COMPANION(parent); + if (!adev) + return -ENXIO; + + /* Read _ONT data, which should be a package of 6 integers. */ + status = acpi_evaluate_object(adev->handle, "_ONT", NULL, &buffer); + if (status == AE_NOT_FOUND) { + return -ENXIO; + } else if (ACPI_FAILURE(status)) { + dev_warn(parent, "failed to execute _ONT: %d\n", status); + return status; + } + + ont = buffer.pointer; + if (ont->type != ACPI_TYPE_PACKAGE || ont->package.count != 6) + goto out; + + /* The first 3 integers provide axis order information. + * e.g. 0 1 2 would indicate normal X,Y,Z ordering. + * e.g. 1 0 2 indicates that data arrives in order Y,X,Z. + */ + elements = ont->package.elements; + for (i = 0; i < 3; i++) { + if (elements[i].type != ACPI_TYPE_INTEGER) + goto out; + + val = elements[i].integer.value; + if (val > 2) + goto out; + + /* Avoiding full matrix multiplication, we simply reorder the + * columns in the default_ont matrix according to the + * ordering provided by _ONT. + */ + final_ont[0][i] = default_ont[0][val]; + final_ont[1][i] = default_ont[1][val]; + final_ont[2][i] = default_ont[2][val]; + } + + /* The final 3 integers provide sign flip information. + * 0 means no change, 1 means flip. + * e.g. 0 0 1 means that Z data should be sign-flipped. + * This is applied after the axis reordering from above. + */ + elements += 3; + for (i = 0; i < 3; i++) { + if (elements[i].type != ACPI_TYPE_INTEGER) + goto out; + + val = elements[i].integer.value; + if (val != 0 && val != 1) + goto out; + if (!val) + continue; + + /* Flip the values in the indicated column */ + final_ont[0][i] *= -1; + final_ont[1][i] *= -1; + final_ont[2][i] *= -1; + } + + /* Convert our integer matrix to a string-based iio_mount_matrix */ + for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) { + int matrix_val = final_ont[i][j]; + char *str_value; + + switch (matrix_val) { + case -1: + str_value = "-1"; + break; + case 0: + str_value = "0"; + break; + case 1: + str_value = "1"; + break; + default: + goto out; + } + adata->mount_matrix.rotation[i * 3 + j] = str_value; + } + } + + ret = 0; + dev_info(parent, "computed mount matrix from ACPI\n"); + +out: + kfree(buffer.pointer); + if (ret) + dev_dbg(parent, + "failed to apply ACPI orientation data: %d\n", ret); + + return ret; +} +#else /* !CONFIG_ACPI */ +static int apply_acpi_orientation(struct iio_dev *indio_dev) +{ + return -EINVAL; +} +#endif + +/* + * st_accel_get_settings() - get sensor settings from device name + * @name: device name buffer reference. + * + * Return: valid reference on success, NULL otherwise. + */ +const struct st_sensor_settings *st_accel_get_settings(const char *name) +{ + int index = st_sensors_get_settings_index(name, + st_accel_sensors_settings, + ARRAY_SIZE(st_accel_sensors_settings)); + if (index < 0) + return NULL; + + return &st_accel_sensors_settings[index]; +} +EXPORT_SYMBOL_NS(st_accel_get_settings, "IIO_ST_SENSORS"); + +int st_accel_common_probe(struct iio_dev *indio_dev) +{ + struct st_sensor_data *adata = iio_priv(indio_dev); + struct device *parent = indio_dev->dev.parent; + struct st_sensors_platform_data *pdata = dev_get_platdata(parent); + int err; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &accel_info; + + err = st_sensors_verify_id(indio_dev); + if (err < 0) + return err; + + adata->num_data_channels = ST_ACCEL_NUMBER_DATA_CHANNELS; + indio_dev->channels = adata->sensor_settings->ch; + indio_dev->num_channels = ST_SENSORS_NUMBER_ALL_CHANNELS; + + /* + * First try specific ACPI methods to retrieve orientation then try the + * generic function. + */ + err = apply_acpi_orientation(indio_dev); + if (err) { + err = iio_read_mount_matrix(parent, &adata->mount_matrix); + if (err) + return err; + } + + adata->current_fullscale = &adata->sensor_settings->fs.fs_avl[0]; + adata->odr = adata->sensor_settings->odr.odr_avl[0].hz; + + if (!pdata) + pdata = (struct st_sensors_platform_data *)&default_accel_pdata; + + err = st_sensors_init_sensor(indio_dev, pdata); + if (err < 0) + return err; + + err = st_accel_allocate_ring(indio_dev); + if (err < 0) + return err; + + if (adata->irq > 0) { + err = st_sensors_allocate_trigger(indio_dev, + ST_ACCEL_TRIGGER_OPS); + if (err < 0) + return err; + } + + return devm_iio_device_register(parent, indio_dev); +} +EXPORT_SYMBOL_NS(st_accel_common_probe, "IIO_ST_SENSORS"); + +MODULE_AUTHOR("Denis Ciocca "); +MODULE_DESCRIPTION("STMicroelectronics accelerometers driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ST_SENSORS"); diff --git a/drivers/iio/accel/st_accel_i2c.c b/drivers/iio/accel/st_accel_i2c.c new file mode 100644 index 000000000..99522a885 --- /dev/null +++ b/drivers/iio/accel/st_accel_i2c.c @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics accelerometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include + +#include +#include "st_accel.h" + +static const struct of_device_id st_accel_of_match[] = { + { + /* An older compatible */ + .compatible = "st,lis3lv02d", + .data = LIS3LV02DL_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lis3lv02dl-accel", + .data = LIS3LV02DL_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lsm303dlh-accel", + .data = LSM303DLH_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lsm303dlhc-accel", + .data = LSM303DLHC_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lis3dh-accel", + .data = LIS3DH_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lsm330d-accel", + .data = LSM330D_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lsm330dl-accel", + .data = LSM330DL_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lsm330dlc-accel", + .data = LSM330DLC_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lis331dl-accel", + .data = LIS331DL_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lis331dlh-accel", + .data = LIS331DLH_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lsm303dl-accel", + .data = LSM303DL_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lsm303dlm-accel", + .data = LSM303DLM_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lsm330-accel", + .data = LSM330_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lsm303agr-accel", + .data = LSM303AGR_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lis2dh12-accel", + .data = LIS2DH12_ACCEL_DEV_NAME, + }, + { + .compatible = "st,h3lis331dl-accel", + .data = H3LIS331DL_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lis3l02dq", + .data = LIS3L02DQ_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lng2dm-accel", + .data = LNG2DM_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lis2dw12", + .data = LIS2DW12_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lis3de", + .data = LIS3DE_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lis2de12", + .data = LIS2DE12_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lis2ds12", + .data = LIS2DS12_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lis2hh12", + .data = LIS2HH12_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lis302dl", + .data = LIS302DL_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lsm303c-accel", + .data = LSM303C_ACCEL_DEV_NAME, + }, + { + .compatible = "silan,sc7a20", + .data = SC7A20_ACCEL_DEV_NAME, + }, + { + .compatible = "st,iis328dq", + .data = IIS328DQ_ACCEL_DEV_NAME, + }, + { } +}; +MODULE_DEVICE_TABLE(of, st_accel_of_match); + +static const struct acpi_device_id st_accel_acpi_match[] = { + {"SMO8840", (kernel_ulong_t)LIS2DH12_ACCEL_DEV_NAME}, + {"SMO8A90", (kernel_ulong_t)LNG2DM_ACCEL_DEV_NAME}, + { } +}; +MODULE_DEVICE_TABLE(acpi, st_accel_acpi_match); + +static const struct i2c_device_id st_accel_id_table[] = { + { .name = LSM303DLH_ACCEL_DEV_NAME }, + { .name = LSM303DLHC_ACCEL_DEV_NAME }, + { .name = LIS3DH_ACCEL_DEV_NAME }, + { .name = LSM330D_ACCEL_DEV_NAME }, + { .name = LSM330DL_ACCEL_DEV_NAME }, + { .name = LSM330DLC_ACCEL_DEV_NAME }, + { .name = LIS331DLH_ACCEL_DEV_NAME }, + { .name = LSM303DL_ACCEL_DEV_NAME }, + { .name = LSM303DLM_ACCEL_DEV_NAME }, + { .name = LSM330_ACCEL_DEV_NAME }, + { .name = LSM303AGR_ACCEL_DEV_NAME }, + { .name = LIS2DH12_ACCEL_DEV_NAME }, + { .name = LIS3L02DQ_ACCEL_DEV_NAME }, + { .name = LNG2DM_ACCEL_DEV_NAME }, + { .name = H3LIS331DL_ACCEL_DEV_NAME }, + { .name = LIS331DL_ACCEL_DEV_NAME }, + { .name = LIS3LV02DL_ACCEL_DEV_NAME }, + { .name = LIS2DW12_ACCEL_DEV_NAME }, + { .name = LIS3DE_ACCEL_DEV_NAME }, + { .name = LIS2DE12_ACCEL_DEV_NAME }, + { .name = LIS2DS12_ACCEL_DEV_NAME }, + { .name = LIS2HH12_ACCEL_DEV_NAME }, + { .name = LIS302DL_ACCEL_DEV_NAME }, + { .name = LSM303C_ACCEL_DEV_NAME }, + { .name = SC7A20_ACCEL_DEV_NAME }, + { .name = IIS328DQ_ACCEL_DEV_NAME }, + { } +}; +MODULE_DEVICE_TABLE(i2c, st_accel_id_table); + +static int st_accel_i2c_probe(struct i2c_client *client) +{ + const struct st_sensor_settings *settings; + struct st_sensor_data *adata; + struct iio_dev *indio_dev; + int ret; + + st_sensors_dev_name_probe(&client->dev, client->name, sizeof(client->name)); + + settings = st_accel_get_settings(client->name); + if (!settings) { + dev_err(&client->dev, "device name %s not recognized.\n", + client->name); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*adata)); + if (!indio_dev) + return -ENOMEM; + + adata = iio_priv(indio_dev); + adata->sensor_settings = (struct st_sensor_settings *)settings; + + ret = st_sensors_i2c_configure(indio_dev, client); + if (ret < 0) + return ret; + + ret = st_sensors_power_enable(indio_dev); + if (ret) + return ret; + + return st_accel_common_probe(indio_dev); +} + +static struct i2c_driver st_accel_driver = { + .driver = { + .name = "st-accel-i2c", + .of_match_table = st_accel_of_match, + .acpi_match_table = st_accel_acpi_match, + }, + .probe = st_accel_i2c_probe, + .id_table = st_accel_id_table, +}; +module_i2c_driver(st_accel_driver); + +MODULE_AUTHOR("Denis Ciocca "); +MODULE_DESCRIPTION("STMicroelectronics accelerometers i2c driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ST_SENSORS"); diff --git a/drivers/iio/accel/st_accel_spi.c b/drivers/iio/accel/st_accel_spi.c new file mode 100644 index 000000000..171f241c0 --- /dev/null +++ b/drivers/iio/accel/st_accel_spi.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics accelerometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include + +#include +#include +#include "st_accel.h" + +/* + * For new single-chip sensors use as compatible string. + * For old single-chip devices keep -accel to maintain + * compatibility + */ +static const struct of_device_id st_accel_of_match[] = { + { + /* An older compatible */ + .compatible = "st,lis302dl-spi", + .data = LIS3LV02DL_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lis3lv02dl-accel", + .data = LIS3LV02DL_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lis3dh-accel", + .data = LIS3DH_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lsm330d-accel", + .data = LSM330D_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lsm330dl-accel", + .data = LSM330DL_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lsm330dlc-accel", + .data = LSM330DLC_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lis331dlh-accel", + .data = LIS331DLH_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lsm330-accel", + .data = LSM330_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lsm303agr-accel", + .data = LSM303AGR_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lis2dh12-accel", + .data = LIS2DH12_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lis2ds12", + .data = LIS2DS12_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lis3l02dq", + .data = LIS3L02DQ_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lng2dm-accel", + .data = LNG2DM_ACCEL_DEV_NAME, + }, + { + .compatible = "st,h3lis331dl-accel", + .data = H3LIS331DL_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lis331dl-accel", + .data = LIS331DL_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lis2dw12", + .data = LIS2DW12_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lis3dhh", + .data = LIS3DHH_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lis3de", + .data = LIS3DE_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lis302dl", + .data = LIS302DL_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lsm303c-accel", + .data = LSM303C_ACCEL_DEV_NAME, + }, + { + .compatible = "st,iis328dq", + .data = IIS328DQ_ACCEL_DEV_NAME, + }, + { } +}; +MODULE_DEVICE_TABLE(of, st_accel_of_match); + +static int st_accel_spi_probe(struct spi_device *spi) +{ + const struct st_sensor_settings *settings; + struct st_sensor_data *adata; + struct iio_dev *indio_dev; + int err; + + st_sensors_dev_name_probe(&spi->dev, spi->modalias, sizeof(spi->modalias)); + + settings = st_accel_get_settings(spi->modalias); + if (!settings) { + dev_err(&spi->dev, "device name %s not recognized.\n", + spi->modalias); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adata)); + if (!indio_dev) + return -ENOMEM; + + adata = iio_priv(indio_dev); + adata->sensor_settings = (struct st_sensor_settings *)settings; + + err = st_sensors_spi_configure(indio_dev, spi); + if (err < 0) + return err; + + err = st_sensors_power_enable(indio_dev); + if (err) + return err; + + return st_accel_common_probe(indio_dev); +} + +static const struct spi_device_id st_accel_id_table[] = { + { .name = LIS3DH_ACCEL_DEV_NAME }, + { .name = LSM330D_ACCEL_DEV_NAME }, + { .name = LSM330DL_ACCEL_DEV_NAME }, + { .name = LSM330DLC_ACCEL_DEV_NAME }, + { .name = LIS331DLH_ACCEL_DEV_NAME }, + { .name = LSM330_ACCEL_DEV_NAME }, + { .name = LSM303AGR_ACCEL_DEV_NAME }, + { .name = LIS2DH12_ACCEL_DEV_NAME }, + { .name = LIS2DS12_ACCEL_DEV_NAME }, + { .name = LIS3L02DQ_ACCEL_DEV_NAME }, + { .name = LNG2DM_ACCEL_DEV_NAME }, + { .name = H3LIS331DL_ACCEL_DEV_NAME }, + { .name = LIS331DL_ACCEL_DEV_NAME }, + { .name = LIS3LV02DL_ACCEL_DEV_NAME }, + { .name = LIS2DW12_ACCEL_DEV_NAME }, + { .name = LIS3DHH_ACCEL_DEV_NAME }, + { .name = LIS3DE_ACCEL_DEV_NAME }, + { .name = LIS302DL_ACCEL_DEV_NAME }, + { .name = LSM303C_ACCEL_DEV_NAME }, + { .name = IIS328DQ_ACCEL_DEV_NAME }, + { } +}; +MODULE_DEVICE_TABLE(spi, st_accel_id_table); + +static struct spi_driver st_accel_driver = { + .driver = { + .name = "st-accel-spi", + .of_match_table = st_accel_of_match, + }, + .probe = st_accel_spi_probe, + .id_table = st_accel_id_table, +}; +module_spi_driver(st_accel_driver); + +MODULE_AUTHOR("Denis Ciocca "); +MODULE_DESCRIPTION("STMicroelectronics accelerometers spi driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ST_SENSORS"); diff --git a/drivers/iio/accel/stk8312.c b/drivers/iio/accel/stk8312.c new file mode 100644 index 000000000..50840c344 --- /dev/null +++ b/drivers/iio/accel/stk8312.c @@ -0,0 +1,658 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Sensortek STK8312 3-Axis Accelerometer + * + * Copyright (c) 2015, Intel Corporation. + * + * IIO driver for STK8312; 7-bit I2C address: 0x3D. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define STK8312_REG_XOUT 0x00 +#define STK8312_REG_YOUT 0x01 +#define STK8312_REG_ZOUT 0x02 +#define STK8312_REG_INTSU 0x06 +#define STK8312_REG_MODE 0x07 +#define STK8312_REG_SR 0x08 +#define STK8312_REG_STH 0x13 +#define STK8312_REG_RESET 0x20 +#define STK8312_REG_AFECTRL 0x24 +#define STK8312_REG_OTPADDR 0x3D +#define STK8312_REG_OTPDATA 0x3E +#define STK8312_REG_OTPCTRL 0x3F + +#define STK8312_MODE_ACTIVE BIT(0) +#define STK8312_MODE_STANDBY 0x00 +#define STK8312_MODE_INT_AH_PP 0xC0 /* active-high, push-pull */ +#define STK8312_DREADY_BIT BIT(4) +#define STK8312_RNG_6G 1 +#define STK8312_RNG_SHIFT 6 +#define STK8312_RNG_MASK GENMASK(7, 6) +#define STK8312_SR_MASK GENMASK(2, 0) +#define STK8312_SR_400HZ_IDX 0 +#define STK8312_ALL_CHANNEL_MASK GENMASK(2, 0) +#define STK8312_ALL_CHANNEL_SIZE 3 + +#define STK8312_DRIVER_NAME "stk8312" + +/* + * The accelerometer has two measurement ranges: + * + * -6g - +6g (8-bit, signed) + * -16g - +16g (8-bit, signed) + * + * scale1 = (6 + 6) * 9.81 / (2^8 - 1) = 0.4616 + * scale2 = (16 + 16) * 9.81 / (2^8 - 1) = 1.2311 + */ +#define STK8312_SCALE_AVAIL "0.4616 1.2311" + +static const int stk8312_scale_table[][2] = { + {0, 461600}, {1, 231100} +}; + +static const struct { + int val; + int val2; +} stk8312_samp_freq_table[] = { + {400, 0}, {200, 0}, {100, 0}, {50, 0}, {25, 0}, + {12, 500000}, {6, 250000}, {3, 125000} +}; + +#define STK8312_ACCEL_CHANNEL(index, reg, axis) { \ + .type = IIO_ACCEL, \ + .address = reg, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 8, \ + .storagebits = 8, \ + .endianness = IIO_CPU, \ + }, \ +} + +static const struct iio_chan_spec stk8312_channels[] = { + STK8312_ACCEL_CHANNEL(0, STK8312_REG_XOUT, X), + STK8312_ACCEL_CHANNEL(1, STK8312_REG_YOUT, Y), + STK8312_ACCEL_CHANNEL(2, STK8312_REG_ZOUT, Z), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +struct stk8312_data { + struct i2c_client *client; + struct mutex lock; + u8 range; + u8 sample_rate_idx; + u8 mode; + struct iio_trigger *dready_trig; + bool dready_trigger_on; + /* Ensure timestamp is naturally aligned */ + struct { + s8 chans[3]; + aligned_s64 timestamp; + } scan; +}; + +static IIO_CONST_ATTR(in_accel_scale_available, STK8312_SCALE_AVAIL); + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("3.125 6.25 12.5 25 50 100 200 400"); + +static struct attribute *stk8312_attributes[] = { + &iio_const_attr_in_accel_scale_available.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group stk8312_attribute_group = { + .attrs = stk8312_attributes +}; + +static int stk8312_otp_init(struct stk8312_data *data) +{ + int ret; + int count = 10; + struct i2c_client *client = data->client; + + ret = i2c_smbus_write_byte_data(client, STK8312_REG_OTPADDR, 0x70); + if (ret < 0) + goto exit_err; + ret = i2c_smbus_write_byte_data(client, STK8312_REG_OTPCTRL, 0x02); + if (ret < 0) + goto exit_err; + + do { + usleep_range(1000, 5000); + ret = i2c_smbus_read_byte_data(client, STK8312_REG_OTPCTRL); + if (ret < 0) + goto exit_err; + count--; + } while (!(ret & BIT(7)) && count > 0); + + if (count == 0) { + ret = -ETIMEDOUT; + goto exit_err; + } + + ret = i2c_smbus_read_byte_data(client, STK8312_REG_OTPDATA); + if (ret == 0) + ret = -EINVAL; + if (ret < 0) + goto exit_err; + + ret = i2c_smbus_write_byte_data(data->client, STK8312_REG_AFECTRL, ret); + if (ret < 0) + goto exit_err; + msleep(150); + + return 0; + +exit_err: + dev_err(&client->dev, "failed to initialize sensor\n"); + return ret; +} + +static int stk8312_set_mode(struct stk8312_data *data, u8 mode) +{ + int ret; + struct i2c_client *client = data->client; + + if (mode == data->mode) + return 0; + + ret = i2c_smbus_write_byte_data(client, STK8312_REG_MODE, mode); + if (ret < 0) { + dev_err(&client->dev, "failed to change sensor mode\n"); + return ret; + } + + data->mode = mode; + if (mode & STK8312_MODE_ACTIVE) { + /* Need to run OTP sequence before entering active mode */ + usleep_range(1000, 5000); + ret = stk8312_otp_init(data); + } + + return ret; +} + +static int stk8312_set_interrupts(struct stk8312_data *data, u8 int_mask) +{ + int ret; + u8 mode; + struct i2c_client *client = data->client; + + mode = data->mode; + /* We need to go in standby mode to modify registers */ + ret = stk8312_set_mode(data, STK8312_MODE_STANDBY); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(client, STK8312_REG_INTSU, int_mask); + if (ret < 0) { + dev_err(&client->dev, "failed to set interrupts\n"); + stk8312_set_mode(data, mode); + return ret; + } + + return stk8312_set_mode(data, mode); +} + +static int stk8312_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct stk8312_data *data = iio_priv(indio_dev); + int ret; + + if (state) + ret = stk8312_set_interrupts(data, STK8312_DREADY_BIT); + else + ret = stk8312_set_interrupts(data, 0x00); + + if (ret < 0) { + dev_err(&data->client->dev, "failed to set trigger state\n"); + return ret; + } + + data->dready_trigger_on = state; + + return 0; +} + +static const struct iio_trigger_ops stk8312_trigger_ops = { + .set_trigger_state = stk8312_data_rdy_trigger_set_state, +}; + +static int stk8312_set_sample_rate(struct stk8312_data *data, u8 rate) +{ + int ret; + u8 masked_reg; + u8 mode; + struct i2c_client *client = data->client; + + if (rate == data->sample_rate_idx) + return 0; + + mode = data->mode; + /* We need to go in standby mode to modify registers */ + ret = stk8312_set_mode(data, STK8312_MODE_STANDBY); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(client, STK8312_REG_SR); + if (ret < 0) + goto err_activate; + + masked_reg = (ret & (~STK8312_SR_MASK)) | rate; + + ret = i2c_smbus_write_byte_data(client, STK8312_REG_SR, masked_reg); + if (ret < 0) + goto err_activate; + + data->sample_rate_idx = rate; + + return stk8312_set_mode(data, mode); + +err_activate: + dev_err(&client->dev, "failed to set sampling rate\n"); + stk8312_set_mode(data, mode); + + return ret; +} + +static int stk8312_set_range(struct stk8312_data *data, u8 range) +{ + int ret; + u8 masked_reg; + u8 mode; + struct i2c_client *client = data->client; + + if (range != 1 && range != 2) + return -EINVAL; + else if (range == data->range) + return 0; + + mode = data->mode; + /* We need to go in standby mode to modify registers */ + ret = stk8312_set_mode(data, STK8312_MODE_STANDBY); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(client, STK8312_REG_STH); + if (ret < 0) + goto err_activate; + + masked_reg = ret & (~STK8312_RNG_MASK); + masked_reg |= range << STK8312_RNG_SHIFT; + + ret = i2c_smbus_write_byte_data(client, STK8312_REG_STH, masked_reg); + if (ret < 0) + goto err_activate; + + data->range = range; + + return stk8312_set_mode(data, mode); + +err_activate: + dev_err(&client->dev, "failed to change sensor range\n"); + stk8312_set_mode(data, mode); + + return ret; +} + +static int stk8312_read_accel(struct stk8312_data *data, u8 address) +{ + int ret; + struct i2c_client *client = data->client; + + if (address > 2) + return -EINVAL; + + ret = i2c_smbus_read_byte_data(client, address); + if (ret < 0) + dev_err(&client->dev, "register read failed\n"); + + return ret; +} + +static int stk8312_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct stk8312_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + mutex_lock(&data->lock); + ret = stk8312_set_mode(data, data->mode | STK8312_MODE_ACTIVE); + if (ret < 0) { + mutex_unlock(&data->lock); + return ret; + } + ret = stk8312_read_accel(data, chan->address); + if (ret < 0) { + stk8312_set_mode(data, + data->mode & (~STK8312_MODE_ACTIVE)); + mutex_unlock(&data->lock); + return ret; + } + *val = sign_extend32(ret, chan->scan_type.realbits - 1); + ret = stk8312_set_mode(data, + data->mode & (~STK8312_MODE_ACTIVE)); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = stk8312_scale_table[data->range - 1][0]; + *val2 = stk8312_scale_table[data->range - 1][1]; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = stk8312_samp_freq_table[data->sample_rate_idx].val; + *val2 = stk8312_samp_freq_table[data->sample_rate_idx].val2; + return IIO_VAL_INT_PLUS_MICRO; + } + + return -EINVAL; +} + +static int stk8312_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int i; + int index = -1; + int ret; + struct stk8312_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + for (i = 0; i < ARRAY_SIZE(stk8312_scale_table); i++) + if (val == stk8312_scale_table[i][0] && + val2 == stk8312_scale_table[i][1]) { + index = i + 1; + break; + } + if (index < 0) + return -EINVAL; + + mutex_lock(&data->lock); + ret = stk8312_set_range(data, index); + mutex_unlock(&data->lock); + + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + for (i = 0; i < ARRAY_SIZE(stk8312_samp_freq_table); i++) + if (val == stk8312_samp_freq_table[i].val && + val2 == stk8312_samp_freq_table[i].val2) { + index = i; + break; + } + if (index < 0) + return -EINVAL; + mutex_lock(&data->lock); + ret = stk8312_set_sample_rate(data, index); + mutex_unlock(&data->lock); + + return ret; + } + + return -EINVAL; +} + +static const struct iio_info stk8312_info = { + .read_raw = stk8312_read_raw, + .write_raw = stk8312_write_raw, + .attrs = &stk8312_attribute_group, +}; + +static irqreturn_t stk8312_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct stk8312_data *data = iio_priv(indio_dev); + int bit, ret, i = 0; + + mutex_lock(&data->lock); + /* + * Do a bulk read if all channels are requested, + * from 0x00 (XOUT) to 0x02 (ZOUT) + */ + if (*(indio_dev->active_scan_mask) == STK8312_ALL_CHANNEL_MASK) { + ret = i2c_smbus_read_i2c_block_data(data->client, + STK8312_REG_XOUT, + STK8312_ALL_CHANNEL_SIZE, + data->scan.chans); + if (ret < STK8312_ALL_CHANNEL_SIZE) { + dev_err(&data->client->dev, "register read failed\n"); + mutex_unlock(&data->lock); + goto err; + } + } else { + iio_for_each_active_channel(indio_dev, bit) { + ret = stk8312_read_accel(data, bit); + if (ret < 0) { + mutex_unlock(&data->lock); + goto err; + } + data->scan.chans[i++] = ret; + } + } + mutex_unlock(&data->lock); + + iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + pf->timestamp); +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static irqreturn_t stk8312_data_rdy_trig_poll(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct stk8312_data *data = iio_priv(indio_dev); + + if (data->dready_trigger_on) + iio_trigger_poll(data->dready_trig); + + return IRQ_HANDLED; +} + +static int stk8312_buffer_preenable(struct iio_dev *indio_dev) +{ + struct stk8312_data *data = iio_priv(indio_dev); + + return stk8312_set_mode(data, data->mode | STK8312_MODE_ACTIVE); +} + +static int stk8312_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct stk8312_data *data = iio_priv(indio_dev); + + return stk8312_set_mode(data, data->mode & (~STK8312_MODE_ACTIVE)); +} + +static const struct iio_buffer_setup_ops stk8312_buffer_setup_ops = { + .preenable = stk8312_buffer_preenable, + .postdisable = stk8312_buffer_postdisable, +}; + +static int stk8312_probe(struct i2c_client *client) +{ + int ret; + struct iio_dev *indio_dev; + struct stk8312_data *data; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + i2c_set_clientdata(client, indio_dev); + mutex_init(&data->lock); + + indio_dev->info = &stk8312_info; + indio_dev->name = STK8312_DRIVER_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = stk8312_channels; + indio_dev->num_channels = ARRAY_SIZE(stk8312_channels); + + /* A software reset is recommended at power-on */ + ret = i2c_smbus_write_byte_data(data->client, STK8312_REG_RESET, 0x00); + if (ret < 0) { + dev_err(&client->dev, "failed to reset sensor\n"); + return ret; + } + data->sample_rate_idx = STK8312_SR_400HZ_IDX; + ret = stk8312_set_range(data, STK8312_RNG_6G); + if (ret < 0) + return ret; + + ret = stk8312_set_mode(data, + STK8312_MODE_INT_AH_PP | STK8312_MODE_ACTIVE); + if (ret < 0) + return ret; + + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + stk8312_data_rdy_trig_poll, + NULL, + IRQF_TRIGGER_RISING | + IRQF_ONESHOT, + "stk8312_event", + indio_dev); + if (ret) + goto err_power_off; + + data->dready_trig = devm_iio_trigger_alloc(&client->dev, + "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->dready_trig) { + ret = -ENOMEM; + goto err_power_off; + } + + data->dready_trig->ops = &stk8312_trigger_ops; + iio_trigger_set_drvdata(data->dready_trig, indio_dev); + ret = iio_trigger_register(data->dready_trig); + if (ret) { + dev_err(&client->dev, "iio trigger register failed\n"); + goto err_power_off; + } + } + + ret = iio_triggered_buffer_setup(indio_dev, + iio_pollfunc_store_time, + stk8312_trigger_handler, + &stk8312_buffer_setup_ops); + if (ret < 0) { + dev_err(&client->dev, "iio triggered buffer setup failed\n"); + goto err_trigger_unregister; + } + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "device_register failed\n"); + goto err_buffer_cleanup; + } + + return 0; + +err_buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); +err_trigger_unregister: + if (data->dready_trig) + iio_trigger_unregister(data->dready_trig); +err_power_off: + stk8312_set_mode(data, STK8312_MODE_STANDBY); + return ret; +} + +static void stk8312_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct stk8312_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + + if (data->dready_trig) + iio_trigger_unregister(data->dready_trig); + + stk8312_set_mode(data, STK8312_MODE_STANDBY); +} + +static int stk8312_suspend(struct device *dev) +{ + struct stk8312_data *data; + + data = iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + return stk8312_set_mode(data, data->mode & (~STK8312_MODE_ACTIVE)); +} + +static int stk8312_resume(struct device *dev) +{ + struct stk8312_data *data; + + data = iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + return stk8312_set_mode(data, data->mode | STK8312_MODE_ACTIVE); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(stk8312_pm_ops, stk8312_suspend, + stk8312_resume); + +static const struct i2c_device_id stk8312_i2c_id[] = { + /* Deprecated in favour of lowercase form */ + { .name = "STK8312" }, + { .name = "stk8312" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, stk8312_i2c_id); + +static struct i2c_driver stk8312_driver = { + .driver = { + .name = STK8312_DRIVER_NAME, + .pm = pm_sleep_ptr(&stk8312_pm_ops), + }, + .probe = stk8312_probe, + .remove = stk8312_remove, + .id_table = stk8312_i2c_id, +}; + +module_i2c_driver(stk8312_driver); + +MODULE_AUTHOR("Tiberiu Breana "); +MODULE_DESCRIPTION("STK8312 3-Axis Accelerometer driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/stk8ba50.c b/drivers/iio/accel/stk8ba50.c new file mode 100644 index 000000000..6c1e286c0 --- /dev/null +++ b/drivers/iio/accel/stk8ba50.c @@ -0,0 +1,552 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Sensortek STK8BA50 3-Axis Accelerometer + * + * Copyright (c) 2015, Intel Corporation. + * + * STK8BA50 7-bit I2C address: 0x18. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define STK8BA50_REG_XOUT 0x02 +#define STK8BA50_REG_YOUT 0x04 +#define STK8BA50_REG_ZOUT 0x06 +#define STK8BA50_REG_RANGE 0x0F +#define STK8BA50_REG_BWSEL 0x10 +#define STK8BA50_REG_POWMODE 0x11 +#define STK8BA50_REG_SWRST 0x14 +#define STK8BA50_REG_INTEN2 0x17 +#define STK8BA50_REG_INTMAP2 0x1A + +#define STK8BA50_MODE_NORMAL 0 +#define STK8BA50_MODE_SUSPEND 1 +#define STK8BA50_MODE_POWERBIT BIT(7) +#define STK8BA50_DATA_SHIFT 6 +#define STK8BA50_RESET_CMD 0xB6 +#define STK8BA50_SR_1792HZ_IDX 7 +#define STK8BA50_DREADY_INT_MASK 0x10 +#define STK8BA50_DREADY_INT_MAP 0x81 +#define STK8BA50_ALL_CHANNEL_MASK 7 +#define STK8BA50_ALL_CHANNEL_SIZE 6 + +#define STK8BA50_DRIVER_NAME "stk8ba50" + +#define STK8BA50_SCALE_AVAIL "0.0384 0.0767 0.1534 0.3069" + +/* + * The accelerometer has four measurement ranges: + * +/-2g; +/-4g; +/-8g; +/-16g + * + * Acceleration values are 10-bit, 2's complement. + * Scales are calculated as following: + * + * scale1 = (2 + 2) * 9.81 / (2^10 - 1) = 0.0384 + * scale2 = (4 + 4) * 9.81 / (2^10 - 1) = 0.0767 + * etc. + * + * Scales are stored in this format: + * { , } + * + * Locally, the range is stored as a table index. + */ +static const struct { + u8 reg_val; + u32 scale_val; +} stk8ba50_scale_table[] = { + {3, 38400}, {5, 76700}, {8, 153400}, {12, 306900} +}; + +/* Sample rates are stored as { , } */ +static const struct { + u8 reg_val; + u16 samp_freq; +} stk8ba50_samp_freq_table[] = { + {0x08, 14}, {0x09, 25}, {0x0A, 56}, {0x0B, 112}, + {0x0C, 224}, {0x0D, 448}, {0x0E, 896}, {0x0F, 1792} +}; + +/* Used to map scan mask bits to their corresponding channel register. */ +static const int stk8ba50_channel_table[] = { + STK8BA50_REG_XOUT, + STK8BA50_REG_YOUT, + STK8BA50_REG_ZOUT +}; + +struct stk8ba50_data { + struct i2c_client *client; + struct mutex lock; + int range; + u8 sample_rate_idx; + struct iio_trigger *dready_trig; + bool dready_trigger_on; + /* Ensure timestamp is naturally aligned */ + struct { + s16 chans[3]; + aligned_s64 timetamp; + } scan; +}; + +#define STK8BA50_ACCEL_CHANNEL(index, reg, axis) { \ + .type = IIO_ACCEL, \ + .address = reg, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 10, \ + .storagebits = 16, \ + .shift = STK8BA50_DATA_SHIFT, \ + .endianness = IIO_CPU, \ + }, \ +} + +static const struct iio_chan_spec stk8ba50_channels[] = { + STK8BA50_ACCEL_CHANNEL(0, STK8BA50_REG_XOUT, X), + STK8BA50_ACCEL_CHANNEL(1, STK8BA50_REG_YOUT, Y), + STK8BA50_ACCEL_CHANNEL(2, STK8BA50_REG_ZOUT, Z), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static IIO_CONST_ATTR(in_accel_scale_available, STK8BA50_SCALE_AVAIL); + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("14 25 56 112 224 448 896 1792"); + +static struct attribute *stk8ba50_attributes[] = { + &iio_const_attr_in_accel_scale_available.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group stk8ba50_attribute_group = { + .attrs = stk8ba50_attributes +}; + +static int stk8ba50_read_accel(struct stk8ba50_data *data, u8 reg) +{ + int ret; + struct i2c_client *client = data->client; + + ret = i2c_smbus_read_word_data(client, reg); + if (ret < 0) { + dev_err(&client->dev, "register read failed\n"); + return ret; + } + + return ret; +} + +static int stk8ba50_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct stk8ba50_data *data = iio_priv(indio_dev); + int ret; + + if (state) + ret = i2c_smbus_write_byte_data(data->client, + STK8BA50_REG_INTEN2, STK8BA50_DREADY_INT_MASK); + else + ret = i2c_smbus_write_byte_data(data->client, + STK8BA50_REG_INTEN2, 0x00); + + if (ret < 0) + dev_err(&data->client->dev, "failed to set trigger state\n"); + else + data->dready_trigger_on = state; + + return ret; +} + +static const struct iio_trigger_ops stk8ba50_trigger_ops = { + .set_trigger_state = stk8ba50_data_rdy_trigger_set_state, +}; + +static int stk8ba50_set_power(struct stk8ba50_data *data, bool mode) +{ + int ret; + u8 masked_reg; + struct i2c_client *client = data->client; + + ret = i2c_smbus_read_byte_data(client, STK8BA50_REG_POWMODE); + if (ret < 0) + goto exit_err; + + if (mode) + masked_reg = ret | STK8BA50_MODE_POWERBIT; + else + masked_reg = ret & (~STK8BA50_MODE_POWERBIT); + + ret = i2c_smbus_write_byte_data(client, STK8BA50_REG_POWMODE, + masked_reg); + if (ret < 0) + goto exit_err; + + return ret; + +exit_err: + dev_err(&client->dev, "failed to change sensor mode\n"); + return ret; +} + +static int stk8ba50_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct stk8ba50_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + mutex_lock(&data->lock); + ret = stk8ba50_set_power(data, STK8BA50_MODE_NORMAL); + if (ret < 0) { + mutex_unlock(&data->lock); + return -EINVAL; + } + ret = stk8ba50_read_accel(data, chan->address); + if (ret < 0) { + stk8ba50_set_power(data, STK8BA50_MODE_SUSPEND); + mutex_unlock(&data->lock); + return -EINVAL; + } + *val = sign_extend32(ret >> chan->scan_type.shift, + chan->scan_type.realbits - 1); + stk8ba50_set_power(data, STK8BA50_MODE_SUSPEND); + mutex_unlock(&data->lock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = stk8ba50_scale_table[data->range].scale_val; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = stk8ba50_samp_freq_table + [data->sample_rate_idx].samp_freq; + *val2 = 0; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int stk8ba50_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret; + int i; + int index = -1; + struct stk8ba50_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (val != 0) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(stk8ba50_scale_table); i++) + if (val2 == stk8ba50_scale_table[i].scale_val) { + index = i; + break; + } + if (index < 0) + return -EINVAL; + + ret = i2c_smbus_write_byte_data(data->client, + STK8BA50_REG_RANGE, + stk8ba50_scale_table[index].reg_val); + if (ret < 0) + dev_err(&data->client->dev, + "failed to set measurement range\n"); + else + data->range = index; + + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + for (i = 0; i < ARRAY_SIZE(stk8ba50_samp_freq_table); i++) + if (val == stk8ba50_samp_freq_table[i].samp_freq) { + index = i; + break; + } + if (index < 0) + return -EINVAL; + + ret = i2c_smbus_write_byte_data(data->client, + STK8BA50_REG_BWSEL, + stk8ba50_samp_freq_table[index].reg_val); + if (ret < 0) + dev_err(&data->client->dev, + "failed to set sampling rate\n"); + else + data->sample_rate_idx = index; + + return ret; + } + + return -EINVAL; +} + +static const struct iio_info stk8ba50_info = { + .read_raw = stk8ba50_read_raw, + .write_raw = stk8ba50_write_raw, + .attrs = &stk8ba50_attribute_group, +}; + +static irqreturn_t stk8ba50_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct stk8ba50_data *data = iio_priv(indio_dev); + int bit, ret, i = 0; + + mutex_lock(&data->lock); + /* + * Do a bulk read if all channels are requested, + * from 0x02 (XOUT1) to 0x07 (ZOUT2) + */ + if (*(indio_dev->active_scan_mask) == STK8BA50_ALL_CHANNEL_MASK) { + ret = i2c_smbus_read_i2c_block_data(data->client, + STK8BA50_REG_XOUT, + STK8BA50_ALL_CHANNEL_SIZE, + (u8 *)data->scan.chans); + if (ret < STK8BA50_ALL_CHANNEL_SIZE) { + dev_err(&data->client->dev, "register read failed\n"); + goto err; + } + } else { + iio_for_each_active_channel(indio_dev, bit) { + ret = stk8ba50_read_accel(data, + stk8ba50_channel_table[bit]); + if (ret < 0) + goto err; + + data->scan.chans[i++] = ret; + } + } + iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + pf->timestamp); +err: + mutex_unlock(&data->lock); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static irqreturn_t stk8ba50_data_rdy_trig_poll(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct stk8ba50_data *data = iio_priv(indio_dev); + + if (data->dready_trigger_on) + iio_trigger_poll(data->dready_trig); + + return IRQ_HANDLED; +} + +static int stk8ba50_buffer_preenable(struct iio_dev *indio_dev) +{ + struct stk8ba50_data *data = iio_priv(indio_dev); + + return stk8ba50_set_power(data, STK8BA50_MODE_NORMAL); +} + +static int stk8ba50_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct stk8ba50_data *data = iio_priv(indio_dev); + + return stk8ba50_set_power(data, STK8BA50_MODE_SUSPEND); +} + +static const struct iio_buffer_setup_ops stk8ba50_buffer_setup_ops = { + .preenable = stk8ba50_buffer_preenable, + .postdisable = stk8ba50_buffer_postdisable, +}; + +static int stk8ba50_probe(struct i2c_client *client) +{ + int ret; + struct iio_dev *indio_dev; + struct stk8ba50_data *data; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + i2c_set_clientdata(client, indio_dev); + mutex_init(&data->lock); + + indio_dev->info = &stk8ba50_info; + indio_dev->name = STK8BA50_DRIVER_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = stk8ba50_channels; + indio_dev->num_channels = ARRAY_SIZE(stk8ba50_channels); + + /* Reset all registers on startup */ + ret = i2c_smbus_write_byte_data(client, + STK8BA50_REG_SWRST, STK8BA50_RESET_CMD); + if (ret < 0) { + dev_err(&client->dev, "failed to reset sensor\n"); + goto err_power_off; + } + + /* The default range is +/-2g */ + data->range = 0; + + /* The default sampling rate is 1792 Hz (maximum) */ + data->sample_rate_idx = STK8BA50_SR_1792HZ_IDX; + + /* Set up interrupts */ + ret = i2c_smbus_write_byte_data(client, + STK8BA50_REG_INTEN2, STK8BA50_DREADY_INT_MASK); + if (ret < 0) { + dev_err(&client->dev, "failed to set up interrupts\n"); + goto err_power_off; + } + ret = i2c_smbus_write_byte_data(client, + STK8BA50_REG_INTMAP2, STK8BA50_DREADY_INT_MAP); + if (ret < 0) { + dev_err(&client->dev, "failed to set up interrupts\n"); + goto err_power_off; + } + + if (client->irq > 0) { + ret = devm_request_irq(&client->dev, client->irq, + stk8ba50_data_rdy_trig_poll, + IRQF_TRIGGER_RISING | IRQF_NO_THREAD, + "stk8ba50_event", indio_dev); + if (ret) + goto err_power_off; + + data->dready_trig = devm_iio_trigger_alloc(&client->dev, + "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->dready_trig) { + ret = -ENOMEM; + goto err_power_off; + } + + data->dready_trig->ops = &stk8ba50_trigger_ops; + iio_trigger_set_drvdata(data->dready_trig, indio_dev); + ret = iio_trigger_register(data->dready_trig); + if (ret) { + dev_err(&client->dev, "iio trigger register failed\n"); + goto err_power_off; + } + } + + ret = iio_triggered_buffer_setup(indio_dev, + iio_pollfunc_store_time, + stk8ba50_trigger_handler, + &stk8ba50_buffer_setup_ops); + if (ret < 0) { + dev_err(&client->dev, "iio triggered buffer setup failed\n"); + goto err_trigger_unregister; + } + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "device_register failed\n"); + goto err_buffer_cleanup; + } + + return ret; + +err_buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); +err_trigger_unregister: + if (data->dready_trig) + iio_trigger_unregister(data->dready_trig); +err_power_off: + stk8ba50_set_power(data, STK8BA50_MODE_SUSPEND); + return ret; +} + +static void stk8ba50_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct stk8ba50_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + + if (data->dready_trig) + iio_trigger_unregister(data->dready_trig); + + stk8ba50_set_power(data, STK8BA50_MODE_SUSPEND); +} + +static int stk8ba50_suspend(struct device *dev) +{ + struct stk8ba50_data *data; + + data = iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + return stk8ba50_set_power(data, STK8BA50_MODE_SUSPEND); +} + +static int stk8ba50_resume(struct device *dev) +{ + struct stk8ba50_data *data; + + data = iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + return stk8ba50_set_power(data, STK8BA50_MODE_NORMAL); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(stk8ba50_pm_ops, stk8ba50_suspend, + stk8ba50_resume); + +static const struct i2c_device_id stk8ba50_i2c_id[] = { + { .name = "stk8ba50" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, stk8ba50_i2c_id); + +static const struct acpi_device_id stk8ba50_acpi_id[] = { + {"STK8BA50", 0}, + { } +}; + +MODULE_DEVICE_TABLE(acpi, stk8ba50_acpi_id); + +static struct i2c_driver stk8ba50_driver = { + .driver = { + .name = "stk8ba50", + .pm = pm_sleep_ptr(&stk8ba50_pm_ops), + .acpi_match_table = stk8ba50_acpi_id, + }, + .probe = stk8ba50_probe, + .remove = stk8ba50_remove, + .id_table = stk8ba50_i2c_id, +}; + +module_i2c_driver(stk8ba50_driver); + +MODULE_AUTHOR("Tiberiu Breana "); +MODULE_DESCRIPTION("STK8BA50 3-Axis Accelerometer driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/88pm886-gpadc.c b/drivers/iio/adc/88pm886-gpadc.c new file mode 100644 index 000000000..938ba43c2 --- /dev/null +++ b/drivers/iio/adc/88pm886-gpadc.c @@ -0,0 +1,392 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2025, Duje Mihanović + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +struct pm886_gpadc { + struct regmap *map; +}; + +enum pm886_gpadc_channel { + VSC_CHAN, + VCHG_PWR_CHAN, + VCF_OUT_CHAN, + VBAT_CHAN, + VBAT_SLP_CHAN, + VBUS_CHAN, + + GPADC0_CHAN, + GPADC1_CHAN, + GPADC2_CHAN, + GPADC3_CHAN, + + GND_DET1_CHAN, + GND_DET2_CHAN, + MIC_DET_CHAN, + + TINT_CHAN, +}; + +static const int pm886_gpadc_regs[] = { + [VSC_CHAN] = PM886_REG_GPADC_VSC, + [VCHG_PWR_CHAN] = PM886_REG_GPADC_VCHG_PWR, + [VCF_OUT_CHAN] = PM886_REG_GPADC_VCF_OUT, + [VBAT_CHAN] = PM886_REG_GPADC_VBAT, + [VBAT_SLP_CHAN] = PM886_REG_GPADC_VBAT_SLP, + [VBUS_CHAN] = PM886_REG_GPADC_VBUS, + + [GPADC0_CHAN] = PM886_REG_GPADC_GPADC0, + [GPADC1_CHAN] = PM886_REG_GPADC_GPADC1, + [GPADC2_CHAN] = PM886_REG_GPADC_GPADC2, + [GPADC3_CHAN] = PM886_REG_GPADC_GPADC3, + + [GND_DET1_CHAN] = PM886_REG_GPADC_GND_DET1, + [GND_DET2_CHAN] = PM886_REG_GPADC_GND_DET2, + [MIC_DET_CHAN] = PM886_REG_GPADC_MIC_DET, + + [TINT_CHAN] = PM886_REG_GPADC_TINT, +}; + +#define ADC_CHANNEL_VOLTAGE(index, lsb, name) \ +{ \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = index, \ + .address = lsb, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = name, \ +} + +#define ADC_CHANNEL_RESISTANCE(index, lsb, name) \ +{ \ + .type = IIO_RESISTANCE, \ + .indexed = 1, \ + .channel = index, \ + .address = lsb, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ + .datasheet_name = name, \ +} + +#define ADC_CHANNEL_TEMPERATURE(index, lsb, name) \ +{ \ + .type = IIO_TEMP, \ + .indexed = 1, \ + .channel = index, \ + .address = lsb, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .datasheet_name = name, \ +} + +static const struct iio_chan_spec pm886_gpadc_channels[] = { + ADC_CHANNEL_VOLTAGE(VSC_CHAN, 1367, "vsc"), + ADC_CHANNEL_VOLTAGE(VCHG_PWR_CHAN, 1709, "vchg_pwr"), + ADC_CHANNEL_VOLTAGE(VCF_OUT_CHAN, 1367, "vcf_out"), + ADC_CHANNEL_VOLTAGE(VBAT_CHAN, 1367, "vbat"), + ADC_CHANNEL_VOLTAGE(VBAT_SLP_CHAN, 1367, "vbat_slp"), + ADC_CHANNEL_VOLTAGE(VBUS_CHAN, 1709, "vbus"), + + ADC_CHANNEL_RESISTANCE(GPADC0_CHAN, 342, "gpadc0"), + ADC_CHANNEL_RESISTANCE(GPADC1_CHAN, 342, "gpadc1"), + ADC_CHANNEL_RESISTANCE(GPADC2_CHAN, 342, "gpadc2"), + ADC_CHANNEL_RESISTANCE(GPADC3_CHAN, 342, "gpadc3"), + + ADC_CHANNEL_VOLTAGE(GND_DET1_CHAN, 342, "gnddet1"), + ADC_CHANNEL_VOLTAGE(GND_DET2_CHAN, 342, "gnddet2"), + ADC_CHANNEL_VOLTAGE(MIC_DET_CHAN, 1367, "mic_det"), + + ADC_CHANNEL_TEMPERATURE(TINT_CHAN, 104, "tint"), +}; + +static const struct regmap_config pm886_gpadc_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = PM886_GPADC_MAX_REGISTER, +}; + +static int gpadc_get_raw(struct iio_dev *iio, enum pm886_gpadc_channel chan) +{ + struct pm886_gpadc *gpadc = iio_priv(iio); + __be16 buf; + int ret; + + ret = regmap_bulk_read(gpadc->map, pm886_gpadc_regs[chan], &buf, sizeof(buf)); + if (ret) + return ret; + + return be16_to_cpu(buf) >> 4; +} + +static int +gpadc_set_bias(struct pm886_gpadc *gpadc, enum pm886_gpadc_channel chan, bool on) +{ + unsigned int gpadc_num = chan - GPADC0_CHAN; + unsigned int bits = BIT(gpadc_num + 4) | BIT(gpadc_num); + + return regmap_assign_bits(gpadc->map, PM886_REG_GPADC_CONFIG(0x14), bits, on); +} + +static int +gpadc_find_bias_current(struct iio_dev *iio, struct iio_chan_spec const *chan, + unsigned int *raw_uV, unsigned int *raw_uA) +{ + struct pm886_gpadc *gpadc = iio_priv(iio); + unsigned int gpadc_num = chan->channel - GPADC0_CHAN; + unsigned int reg = PM886_REG_GPADC_CONFIG(0xb + gpadc_num); + unsigned long lsb = chan->address; + int ret; + + for (unsigned int i = 0; i < PM886_GPADC_BIAS_LEVELS; i++) { + ret = regmap_update_bits(gpadc->map, reg, GENMASK(3, 0), i); + if (ret) + return ret; + + /* Wait for the new bias level to apply. */ + fsleep(5 * USEC_PER_MSEC); + + *raw_uA = PM886_GPADC_INDEX_TO_BIAS_uA(i); + *raw_uV = gpadc_get_raw(iio, chan->channel) * lsb; + + /* + * Vendor kernel errors out above 1.25 V, but testing shows + * that the resistance of the battery detection channel (GPADC2 + * on coreprimevelte) reaches about 1.4 MΩ when the battery is + * removed, which can't be measured with such a low upper + * limit. Therefore, to be able to detect the battery without + * ugly externs as used in the vendor fuel gauge driver, + * increase this limit a bit. + */ + if (WARN_ON(*raw_uV > 1500 * (MICRO / MILLI))) + return -EIO; + + /* + * Vendor kernel errors out under 300 mV, but for the same + * reason as above (except the channel hovers around 3.5 kΩ + * with battery present) reduce this limit. + */ + if (*raw_uV < 200 * (MICRO / MILLI)) { + dev_dbg(&iio->dev, "bad bias for chan %d: %d uA @ %d uV\n", + chan->channel, *raw_uA, *raw_uV); + continue; + } + + dev_dbg(&iio->dev, "good bias for chan %d: %d uA @ %d uV\n", + chan->channel, *raw_uA, *raw_uV); + return 0; + } + + dev_err(&iio->dev, "failed to find good bias for chan %d\n", chan->channel); + return -EINVAL; +} + +static int +gpadc_get_resistance_ohm(struct iio_dev *iio, struct iio_chan_spec const *chan) +{ + struct pm886_gpadc *gpadc = iio_priv(iio); + unsigned int raw_uV, raw_uA; + int ret; + + ret = gpadc_set_bias(gpadc, chan->channel, true); + if (ret) + goto out; + + ret = gpadc_find_bias_current(iio, chan, &raw_uV, &raw_uA); + if (ret) + goto out; + + ret = DIV_ROUND_CLOSEST(raw_uV, raw_uA); +out: + gpadc_set_bias(gpadc, chan->channel, false); + return ret; +} + +static int +__pm886_gpadc_read_raw(struct iio_dev *iio, struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + unsigned long lsb = chan->address; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val = gpadc_get_raw(iio, chan->channel); + if (*val < 0) + return *val; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = lsb; + + if (chan->type == IIO_VOLTAGE) { + *val2 = MILLI; + return IIO_VAL_FRACTIONAL; + } else { + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_OFFSET: + /* Raw value is 104 millikelvin/LSB, convert it to 104 millicelsius/LSB */ + *val = ABSOLUTE_ZERO_MILLICELSIUS; + *val2 = lsb; + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_PROCESSED: + *val = gpadc_get_resistance_ohm(iio, chan); + if (*val < 0) + return *val; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int pm886_gpadc_read_raw(struct iio_dev *iio, struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct device *dev = iio->dev.parent; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = __pm886_gpadc_read_raw(iio, chan, val, val2, mask); + + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int pm886_gpadc_hw_enable(struct regmap *map) +{ + static const u8 config[] = { + PM886_GPADC_CONFIG1_EN_ALL, + PM886_GPADC_CONFIG2_EN_ALL, + PM886_GPADC_GND_DET2_EN, + }; + int ret; + + /* Enable the ADC block. */ + ret = regmap_set_bits(map, PM886_REG_GPADC_CONFIG(0x6), BIT(0)); + if (ret) + return ret; + + /* Enable all channels. */ + return regmap_bulk_write(map, PM886_REG_GPADC_CONFIG(0x1), config, ARRAY_SIZE(config)); +} + +static int pm886_gpadc_hw_disable(struct regmap *map) +{ + return regmap_clear_bits(map, PM886_REG_GPADC_CONFIG(0x6), BIT(0)); +} + +static const struct iio_info pm886_gpadc_iio_info = { + .read_raw = pm886_gpadc_read_raw, +}; + +static int pm886_gpadc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pm886_chip *chip = dev_get_drvdata(dev->parent); + struct i2c_client *client = chip->client; + struct pm886_gpadc *gpadc; + struct i2c_client *page; + struct iio_dev *iio; + int ret; + + iio = devm_iio_device_alloc(dev, sizeof(*gpadc)); + if (!iio) + return -ENOMEM; + + gpadc = iio_priv(iio); + dev_set_drvdata(dev, iio); + + page = devm_i2c_new_dummy_device(dev, client->adapter, + client->addr + PM886_PAGE_OFFSET_GPADC); + if (IS_ERR(page)) + return dev_err_probe(dev, PTR_ERR(page), "Failed to initialize GPADC page\n"); + + gpadc->map = devm_regmap_init_i2c(page, &pm886_gpadc_regmap_config); + if (IS_ERR(gpadc->map)) + return dev_err_probe(dev, PTR_ERR(gpadc->map), + "Failed to initialize GPADC regmap\n"); + + iio->name = "88pm886-gpadc"; + iio->modes = INDIO_DIRECT_MODE; + iio->info = &pm886_gpadc_iio_info; + iio->channels = pm886_gpadc_channels; + iio->num_channels = ARRAY_SIZE(pm886_gpadc_channels); + device_set_node(&iio->dev, dev_fwnode(dev->parent)); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable runtime PM\n"); + + pm_runtime_set_autosuspend_delay(dev, 50); + pm_runtime_use_autosuspend(dev); + ret = devm_iio_device_register(dev, iio); + if (ret) + return dev_err_probe(dev, ret, "Failed to register ADC\n"); + + return 0; +} + +static int pm886_gpadc_runtime_resume(struct device *dev) +{ + struct iio_dev *iio = dev_get_drvdata(dev); + struct pm886_gpadc *gpadc = iio_priv(iio); + + return pm886_gpadc_hw_enable(gpadc->map); +} + +static int pm886_gpadc_runtime_suspend(struct device *dev) +{ + struct iio_dev *iio = dev_get_drvdata(dev); + struct pm886_gpadc *gpadc = iio_priv(iio); + + return pm886_gpadc_hw_disable(gpadc->map); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(pm886_gpadc_pm_ops, + pm886_gpadc_runtime_suspend, + pm886_gpadc_runtime_resume, NULL); + +static const struct platform_device_id pm886_gpadc_id[] = { + { .name = "88pm886-gpadc" }, + { } +}; +MODULE_DEVICE_TABLE(platform, pm886_gpadc_id); + +static struct platform_driver pm886_gpadc_driver = { + .driver = { + .name = "88pm886-gpadc", + .pm = pm_ptr(&pm886_gpadc_pm_ops), + }, + .probe = pm886_gpadc_probe, + .id_table = pm886_gpadc_id, +}; +module_platform_driver(pm886_gpadc_driver); + +MODULE_AUTHOR("Duje Mihanović "); +MODULE_DESCRIPTION("Marvell 88PM886 GPADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig new file mode 100644 index 000000000..415e519ad --- /dev/null +++ b/drivers/iio/adc/Kconfig @@ -0,0 +1,2101 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# ADC drivers +# +# When adding new entries keep the list in alphabetical order + +menu "Analog to digital converters" + +config IIO_ADC_HELPER + tristate + +config 88PM886_GPADC + tristate "Marvell 88PM886 GPADC driver" + depends on MFD_88PM886_PMIC + default MFD_88PM886_PMIC + help + Say Y here to enable support for the GPADC (General Purpose ADC) + found on the Marvell 88PM886 PMIC. The GPADC measures various + internal voltages and temperatures, including (but not limited to) + system, battery and USB Vbus. + + To compile this driver as a module, choose M here: the module will be + called 88pm886-gpadc. + +config AB8500_GPADC + bool "ST-Ericsson AB8500 GPADC driver" + depends on AB8500_CORE && REGULATOR_AB8500 + default y + help + AB8500 Analog Baseband, mixed signal integrated circuit GPADC + (General Purpose Analog to Digital Converter) driver used to monitor + internal voltages, convert accessory and battery, AC (charger, mains) + and USB voltages integral to the U8500 platform. + +config AD_SIGMA_DELTA + tristate "Analog Devices Sigma-Delta Modulator support" if COMPILE_TEST + depends on SPI + select IIO_BUFFER + select IIO_BUFFER_DMAENGINE + select IIO_TRIGGERED_BUFFER + select SPI_OFFLOAD + +config AD4000 + tristate "Analog Devices AD4000 ADC Driver" + depends on SPI + select IIO_BUFFER + select IIO_BUFFER_DMAENGINE + select IIO_TRIGGERED_BUFFER + select SPI_OFFLOAD + help + Say yes here to build support for Analog Devices AD4000 high speed + SPI analog to digital converters (ADC). If intended to use with + SPI offloading support, it is recommended to enable + CONFIG_SPI_AXI_SPI_ENGINE, CONFIG_PWM_AXI_PWMGEN, and + CONFIG_SPI_OFFLOAD_TRIGGER_PWM. + + To compile this driver as a module, choose M here: the module will be + called ad4000. + +config AD4030 + tristate "Analog Devices AD4030 ADC Driver" + depends on SPI + depends on GPIOLIB + depends on PWM + select REGMAP + select IIO_BUFFER + select IIO_BUFFER_DMA + select IIO_BUFFER_DMAENGINE + select IIO_TRIGGERED_BUFFER + select SPI_OFFLOAD + select SPI_OFFLOAD_TRIGGER_PWM + help + Say yes here to build support for Analog Devices AD4030 and AD4630 high speed + SPI analog to digital converters (ADC). + + To compile this driver as a module, choose M here: the module will be + called ad4030. + +config AD4062 + tristate "Analog Devices AD4062 Driver" + depends on I3C + depends on GPIOLIB + select REGMAP_I3C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Analog Devices AD4062 I3C analog + to digital converters (ADC). + + To compile this driver as a module, choose M here: the module will be + called ad4062. + +config AD4080 + tristate "Analog Devices AD4080 high speed ADC" + depends on SPI + select REGMAP_SPI + select IIO_BACKEND + help + Say yes here to build support for Analog Devices AD4080 + high speed, low noise, low distortion, 20-bit, Easy Drive, + successive approximation register (SAR) analog-to-digital + converter (ADC). Supports iio_backended devices for AD4080. + + To compile this driver as a module, choose M here: the module will be + called ad4080. + +config AD4130 + tristate "Analog Device AD4130 ADC Driver" + depends on SPI + depends on GPIOLIB + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select IIO_KFIFO_BUF + select REGMAP_SPI + depends on COMMON_CLK + help + Say yes here to build support for Analog Devices AD4130-8 SPI analog + to digital converters (ADC). + + To compile this driver as a module, choose M here: the module will be + called ad4130. + +config AD4134 + tristate "Analog Device AD4134 ADC Driver" + depends on SPI + select REGMAP_SPI + select CRC8 + help + Say yes here to build support for Analog Devices AD4134 SPI analog to + digital converters (ADC). + + To compile this driver as a module, choose M here: the module will be + called ad4134_spi. + +config AD4170_4 + tristate "Analog Device AD4170-4 ADC Driver" + depends on SPI + select REGMAP_SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + depends on COMMON_CLK + depends on GPIOLIB + help + Say yes here to build support for Analog Devices AD4170-4 SPI analog + to digital converters (ADC). + + To compile this driver as a module, choose M here: the module will be + called ad4170-4. + +config AD4691 + tristate "Analog Devices AD4691 Family ADC Driver" + depends on SPI + depends on REGULATOR || COMPILE_TEST + select IIO_BUFFER + select IIO_BUFFER_DMAENGINE + select IIO_TRIGGERED_BUFFER + select REGMAP + select SPI_OFFLOAD + help + Say yes here to build support for Analog Devices AD4691 Family MuxSAR + SPI analog to digital converters (ADC). + + To compile this driver as a module, choose M here: the module will be + called ad4691. + +config AD4695 + tristate "Analog Device AD4695 ADC Driver" + depends on SPI + select IIO_BUFFER + select IIO_BUFFER_DMAENGINE + select IIO_TRIGGERED_BUFFER + select REGMAP + select SPI_OFFLOAD + help + Say yes here to build support for Analog Devices AD4695 and similar + analog to digital converters (ADC). + + To compile this driver as a module, choose M here: the module will be + called ad4695. + +config AD4851 + tristate "Analog Device AD4851 DAS Driver" + depends on SPI + depends on PWM + select REGMAP_SPI + select IIO_BACKEND + help + Say yes here to build support for Analog Devices AD4851, AD4852, + AD4853, AD4854, AD4855, AD4856, AD4857, AD4858, AD4858I high speed + data acquisition system (DAS). + + To compile this driver as a module, choose M here: the module will be + called ad4851. + +config AD7091R + tristate + +config AD7091R5 + tristate "Analog Devices AD7091R5 ADC Driver" + depends on I2C + select AD7091R + select REGMAP_I2C + help + Say yes here to build support for Analog Devices AD7091R-5 ADC. + +config AD7091R8 + tristate "Analog Devices AD7091R8 ADC Driver" + depends on SPI + select AD7091R + select REGMAP_SPI + help + Say yes here to build support for Analog Devices AD7091R-2, AD7091R-4, + and AD7091R-8 ADC. + + To compile this driver as a module, choose M here: the module will be + called ad7091r8. + +config AD7124 + tristate "Analog Devices AD7124 and similar sigma-delta ADCs driver" + depends on SPI_MASTER + select AD_SIGMA_DELTA + help + Say yes here to build support for Analog Devices AD7124-4 and AD7124-8 + SPI analog to digital converters (ADC). + + To compile this driver as a module, choose M here: the module will be + called ad7124. + +config AD7173 + tristate "Analog Devices AD7173 driver" + depends on SPI_MASTER + select AD_SIGMA_DELTA + select GPIOLIB + select GPIO_REGMAP + select REGMAP_SPI + help + Say yes here to build support for Analog Devices AD7173 and similar ADC + Currently supported models: + - AD7172-2 + - AD7173-8 + - AD7175-2 + - AD7176-2 + + To compile this driver as a module, choose M here: the module will be + called ad7173. + +config AD7191 + tristate "Analog Devices AD7191 ADC driver" + depends on SPI + select AD_SIGMA_DELTA + help + Say yes here to build support for Analog Devices AD7191. + + To compile this driver as a module, choose M here: the + module will be called ad7191. + +config AD7192 + tristate "Analog Devices AD7192 and similar ADC driver" + depends on SPI + select AD_SIGMA_DELTA + help + Say yes here to build support for Analog Devices SPI analog to digital + converters (ADC): + - AD7190 + - AD7192 + - AD7193 + - AD7194 + - AD7195 + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called ad7192. + +config AD7266 + tristate "Analog Devices AD7265/AD7266 ADC driver" + depends on SPI_MASTER + select IIO_BUFFER + select IIO_TRIGGER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Analog Devices AD7265 and AD7266 + ADCs. + + To compile this driver as a module, choose M here: the module will be + called ad7266. + +config AD7280 + tristate "Analog Devices AD7280A Lithium Ion Battery Monitoring System" + depends on SPI + select CRC8 + help + Say yes here to build support for Analog Devices AD7280A + Lithium Ion Battery Monitoring System. + + To compile this driver as a module, choose M here: the + module will be called ad7280a + +config AD7291 + tristate "Analog Devices AD7291 ADC driver" + depends on I2C + help + Say yes here to build support for Analog Devices AD7291 + 8 Channel ADC with temperature sensor. + + To compile this driver as a module, choose M here: the + module will be called ad7291. + +config AD7292 + tristate "Analog Devices AD7292 ADC driver" + depends on SPI + help + Say yes here to build support for Analog Devices AD7292 + 8 Channel ADC with temperature sensor. + + To compile this driver as a module, choose M here: the + module will be called ad7292. + +config AD7298 + tristate "Analog Devices AD7298 ADC driver" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Analog Devices AD7298 + 8 Channel ADC with temperature sensor. + + To compile this driver as a module, choose M here: the + module will be called ad7298. + +config AD7380 + tristate "Analog Devices AD7380 ADC driver" + depends on SPI_MASTER + select REGMAP + select SPI_OFFLOAD + select IIO_BUFFER + select IIO_BUFFER_DMAENGINE + select IIO_TRIGGER + select IIO_TRIGGERED_BUFFER + help + AD7380 is a family of simultaneous sampling ADCs that share the same + SPI register map and have similar pinouts. + + Say yes here to build support for Analog Devices AD7380 ADC and + similar chips. + + To compile this driver as a module, choose M here: the module will be + called ad7380. + +config AD7405 + tristate "Analog Device AD7405 ADC Driver" + depends on IIO_BACKEND + help + Say yes here to build support for Analog Devices AD7405, ADUM7701, + ADUM7702, ADUM7703 analog to digital converters (ADC). + + To compile this driver as a module, choose M here: the module will be + called ad7405. + +config AD7476 + tristate "Analog Devices AD7476 1-channel ADCs driver and other similar devices from AD and TI" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for the following SPI analog to + digital converters (ADCs): + Analog Devices: AD7273, AD7274, AD7276, AD7277, AD7278, AD7475, + AD7476, AD7477, AD7478, AD7466, AD7467, AD7468, AD7495, AD7910, + AD7920. + Texas Instruments: ADS7866, ADS7867, ADS7868. + + To compile this driver as a module, choose M here: the + module will be called ad7476. + +config AD7606 + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config AD7606_IFACE_PARALLEL + tristate "Analog Devices AD7606 ADC driver with parallel interface support" + depends on HAS_IOPORT + select AD7606 + select IIO_BACKEND + help + Say yes here to build parallel interface support for Analog Devices: + ad7605-4, ad7606, ad7606-6, ad7606-4 analog to digital converters (ADC). + It also support iio_backended devices for AD7606B. + + To compile this driver as a module, choose M here: the + module will be called ad7606_par. + +config AD7606_IFACE_SPI + tristate "Analog Devices AD7606 ADC driver with spi interface support" + depends on SPI + select AD7606 + select IIO_BUFFER_DMAENGINE + select SPI_OFFLOAD + help + Say yes here to build spi interface support for Analog Devices: + ad7605-4, ad7606, ad7606-6, ad7606-4 analog to digital converters (ADC). + + To compile this driver as a module, choose M here: the + module will be called ad7606_spi. + +config AD7625 + tristate "Analog Devices AD7625/AD7626 High Speed ADC driver" + depends on PWM + select IIO_BACKEND + help + Say yes here to build support for Analog Devices: + * AD7625 16-Bit, 6 MSPS PulSAR Analog-to-Digital Converter + * AD7626 16-Bit, 10 MSPS PulSAR Analog-to-Digital Converter + * AD7960 18-Bit, 5 MSPS PulSAR Analog-to-Digital Converter + * AD7961 16-Bit, 5 MSPS PulSAR Analog-to-Digital Converter + + The driver requires the assistance of the AXI ADC IP core to operate. + + To compile this driver as a module, choose M here: the module will be + called ad7625. + +config AD7766 + tristate "Analog Devices AD7766/AD7767 ADC driver" + depends on SPI_MASTER + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Analog Devices AD7766, AD7766-1, + AD7766-2, AD7767, AD7767-1, AD7767-2 SPI analog to digital converters. + + To compile this driver as a module, choose M here: the module will be + called ad7766. + +config AD7768_1 + tristate "Analog Devices AD7768-1 ADC driver" + depends on SPI + select GPIOLIB + select REGULATOR + select REGMAP_SPI + select RATIONAL + select IIO_BUFFER + select IIO_BUFFER_DMAENGINE + select IIO_TRIGGER + select IIO_TRIGGERED_BUFFER + select SPI_OFFLOAD + help + Say yes here to build support for Analog Devices AD7768-1 SPI + simultaneously sampling sigma-delta analog to digital converter (ADC). + + To compile this driver as a module, choose M here: the module will be + called ad7768-1. + +config AD7779 + tristate "Analog Devices AD7779 ADC driver" + depends on SPI + select CRC8 + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select IIO_BACKEND + help + Say yes here to build support for Analog Devices AD777X family + (AD7770, AD7771, AD7779) analog to digital converter (ADC). + + To compile this driver as a module, choose M here: the module will be + called ad7779. + +config AD7780 + tristate "Analog Devices AD7780 and similar ADCs driver" + depends on SPI + depends on GPIOLIB || COMPILE_TEST + select AD_SIGMA_DELTA + help + Say yes here to build support for Analog Devices AD7170, AD7171, + AD7780 and AD7781 SPI analog to digital converters (ADC). + + To compile this driver as a module, choose M here: the + module will be called ad7780. + +config AD7791 + tristate "Analog Devices AD7791 ADC driver" + depends on SPI + select AD_SIGMA_DELTA + help + Say yes here to build support for Analog Devices AD7787, AD7788, AD7789, + AD7790 and AD7791 SPI analog to digital converters (ADC). + + To compile this driver as a module, choose M here: the module will be + called ad7791. + +config AD7793 + tristate "Analog Devices AD7793 and similar ADCs driver" + depends on SPI + select AD_SIGMA_DELTA + help + Say yes here to build support for Analog Devices AD7785, AD7792, AD7793, + AD7794 and AD7795 SPI analog to digital converters (ADC). + + To compile this driver as a module, choose M here: the + module will be called AD7793. + +config AD7887 + tristate "Analog Devices AD7887 ADC driver" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Analog Devices + AD7887 SPI analog to digital converter (ADC). + + To compile this driver as a module, choose M here: the + module will be called ad7887. + +config AD7923 + tristate "Analog Devices AD7923 and similar ADCs driver" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Analog Devices + AD7904, AD7914, AD7923, AD7924 4 Channel ADCs. + + To compile this driver as a module, choose M here: the + module will be called ad7923. + +config AD7944 + tristate "Analog Devices AD7944 and similar ADCs driver" + depends on SPI + select SPI_OFFLOAD + select IIO_BUFFER + select IIO_BUFFER_DMAENGINE + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Analog Devices + AD7944, AD7985, AD7986 ADCs. + + To compile this driver as a module, choose M here: the + module will be called ad7944 + +config AD7949 + tristate "Analog Devices AD7949 and similar ADCs driver" + depends on SPI + help + Say yes here to build support for Analog Devices + AD7949, AD7682, AD7689 8 Channel ADCs. + + To compile this driver as a module, choose M here: the + module will be called ad7949. + +config AD799X + tristate "Analog Devices AD799x ADC driver" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Analog Devices: + ad7991, ad7995, ad7999, ad7992, ad7993, ad7994, ad7997, ad7998 + i2c analog to digital converters (ADC). Provides direct access + via sysfs. + + To compile this driver as a module, choose M here: the module will be + called ad799x. + +config AD9467 + tristate "Analog Devices AD9467 High Speed ADC driver" + depends on SPI + select IIO_BACKEND + help + Say yes here to build support for Analog Devices: + * AD9467 16-Bit, 200 MSPS/250 MSPS Analog-to-Digital Converter + + The driver requires the assistance of the AXI ADC IP core to operate, + since SPI is used for configuration only, while data has to be + streamed into memory via DMA. + + To compile this driver as a module, choose M here: the module will be + called ad9467. + +config ADE9000 + tristate "Analog Devices ADE9000 Multiphase Energy, and Power Quality Monitoring IC Driver" + depends on SPI + select REGMAP_SPI + select IIO_BUFFER + select IIO_KFIFO_BUF + help + Say yes here to build support for the Analog Devices ADE9000, + a highly accurate, multiphase energy and power quality monitoring + integrated circuit. + + The device features high-precision analog-to-digital converters + and digital signal processing to compute RMS values, power factor, + frequency, and harmonic analysis. It supports SPI communication + and provides buffered data output through the IIO framework. + + To compile this driver as a module, choose M here: the module will + be called ade9000. + +config ADI_AXI_ADC + tristate "Analog Devices Generic AXI ADC IP core driver" + depends on MICROBLAZE || NIOS2 || ARCH_ZYNQ || ARCH_ZYNQMP || ARCH_INTEL_SOCFPGA || COMPILE_TEST + select IIO_BUFFER + select IIO_BUFFER_HW_CONSUMER + select IIO_BUFFER_DMAENGINE + select REGMAP_MMIO + select IIO_BACKEND + help + Say yes here to build support for Analog Devices Generic + AXI ADC IP core. The IP core is used for interfacing with + analog-to-digital (ADC) converters that require either a high-speed + serial interface (JESD204B/C) or a source synchronous parallel + interface (LVDS/CMOS). + Typically (for such devices) SPI will be used for configuration only, + while this IP core handles the streaming of data into memory via DMA. + + Link: https://wiki.analog.com/resources/fpga/docs/axi_adc_ip + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called adi-axi-adc. + +config ASPEED_ADC + tristate "Aspeed ADC" + depends on ARCH_ASPEED || COMPILE_TEST + depends on COMMON_CLK + help + If you say yes here you get support for the ADC included in Aspeed + BMC SoCs. + + To compile this driver as a module, choose M here: the module will be + called aspeed_adc. + +config AT91_ADC + tristate "Atmel AT91 ADC" + depends on ARCH_AT91 || COMPILE_TEST + depends on INPUT && SYSFS && OF + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Atmel AT91 ADC. + + To compile this driver as a module, choose M here: the module will be + called at91_adc. + +config AT91_SAMA5D2_ADC + tristate "Atmel AT91 SAMA5D2 ADC" + depends on ARCH_AT91 || COMPILE_TEST + depends on HAS_IOMEM + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Atmel SAMA5D2 ADC which is + available on SAMA5D2 SoC family. + + To compile this driver as a module, choose M here: the module will be + called at91-sama5d2_adc. + +config AXP20X_ADC + tristate "X-Powers AXP20X and AXP22X ADC driver" + depends on MFD_AXP20X + help + Say yes here to have support for X-Powers power management IC (PMIC) + AXP20X and AXP22X ADC devices. + + To compile this driver as a module, choose M here: the module will be + called axp20x_adc. + +config AXP288_ADC + tristate "X-Powers AXP288 ADC driver" + depends on MFD_AXP20X + help + Say yes here to have support for X-Powers power management IC (PMIC) ADC + device. Depending on platform configuration, this general purpose ADC can + be used for sampling sensors such as thermal resistors. + + To compile this driver as a module, choose M here: the module will be + called axp288_adc. + +config BCM_IPROC_ADC + tristate "Broadcom IPROC ADC driver" + depends on (ARCH_BCM_IPROC && OF) || COMPILE_TEST + depends on MFD_SYSCON + default ARCH_BCM_CYGNUS + help + Say Y here if you want to add support for the Broadcom static + ADC driver. + + Broadcom iProc ADC driver. Broadcom iProc ADC controller has 8 + channels. The driver allows the user to read voltage values. + +config BERLIN2_ADC + tristate "Marvell Berlin2 ADC driver" + depends on ARCH_BERLIN || COMPILE_TEST + help + Marvell Berlin2 ADC driver. This ADC has 8 channels, with one used for + temperature measurement. + +config CC10001_ADC + tristate "Cosmic Circuits 10001 ADC driver" + depends on HAS_IOMEM && HAVE_CLK && REGULATOR + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Cosmic Circuits 10001 ADC. + + This driver can also be built as a module. If so, the module will be + called cc10001_adc. + +config CPCAP_ADC + tristate "Motorola CPCAP PMIC ADC driver" + depends on MFD_CPCAP + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Motorola CPCAP PMIC ADC. + + This driver can also be built as a module. If so, the module will be + called cpcap-adc. + +config DA9150_GPADC + tristate "Dialog DA9150 GPADC driver support" + depends on MFD_DA9150 + help + Say yes here to build support for Dialog DA9150 GPADC. + + This driver can also be built as a module. If chosen, the module name + will be da9150-gpadc. + + To compile this driver as a module, choose M here: the module will be + called berlin2-adc. + +config DLN2_ADC + tristate "Diolan DLN-2 ADC driver support" + depends on MFD_DLN2 + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Diolan DLN-2 ADC. + + This driver can also be built as a module. If so, the module will be + called adc_dln2. + +config ENVELOPE_DETECTOR + tristate "Envelope detector using a DAC and a comparator" + help + Say yes here to build support for an envelope detector using a DAC + and a comparator. + + To compile this driver as a module, choose M here: the module will be + called envelope-detector. + +config EP93XX_ADC + tristate "Cirrus Logic EP93XX ADC driver" + depends on ARCH_EP93XX || COMPILE_TEST + depends on HAS_IOMEM + help + Driver for the ADC module on the EP93XX series of SoC from Cirrus Logic. + It's recommended to switch on CONFIG_HIGH_RES_TIMERS option, in this + case driver will reduce its CPU usage by 90% in some use cases. + + To compile this driver as a module, choose M here: the module will be + called ep93xx_adc. + +config EXYNOS_ADC + tristate "Exynos ADC driver support" + depends on ARCH_EXYNOS || ARCH_S3C64XX || ARCH_S5PV210 || (OF && COMPILE_TEST) + depends on HAS_IOMEM + help + Driver for the ADC block found in the Samsung S3C6410, S5Pv210 and + Exynos SoCs. + Choose Y here only if you build for such Samsung SoC. + + To compile this driver as a module, choose M here: the module will be + called exynos_adc. + +config MXS_LRADC_ADC + tristate "Freescale i.MX23/i.MX28 LRADC ADC" + depends on MFD_MXS_LRADC + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for the ADC functions of the + i.MX23/i.MX28 LRADC. This includes general-purpose ADC readings, + battery voltage measurement, and die temperature measurement. + + This driver can also be built as a module. If so, the module will be + called mxs-lradc-adc. + +config FSL_MX25_ADC + tristate "Freescale MX25 ADC driver" + depends on MFD_MX25_TSADC + help + Generic Conversion Queue driver used for general purpose ADC in the + MX25. This driver supports single measurements using the MX25 ADC. + +config GEHC_PMC_ADC + tristate "GE HealthCare PMC ADC driver" + depends on I2C + help + Say yes here to build support for the GE HealthCare PMC 16-bit + 16-Channel ADC. + + To compile this driver as a module, choose M here: the module will be + called gehc-pmc-adc. + +config HI8435 + tristate "Holt Integrated Circuits HI-8435 threshold detector" + select IIO_TRIGGERED_EVENT + depends on SPI + help + If you say yes here you get support for Holt Integrated Circuits + HI-8435 chip. + + This driver can also be built as a module. If so, the module will be + called hi8435. + +config HX711 + tristate "AVIA HX711 ADC for weight cells" + depends on GPIOLIB + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for AVIA HX711 ADC which is used + for weigh cells + + This driver uses two GPIOs, one acts as the clock and controls the + channel selection and gain, the other one is used for the measurement + data + + Currently the raw value is read from the chip and delivered. + To get an actual weight one needs to subtract the + zero offset and multiply by a scale factor. + This should be done in userspace. + + This driver can also be built as a module. If so, the module will be + called hx711. + +config INA2XX_ADC + tristate "Texas Instruments INA2xx Power Monitors IIO driver" + depends on I2C && !SENSORS_INA2XX + select REGMAP_I2C + select IIO_BUFFER + select IIO_KFIFO_BUF + help + Say yes here to build support for TI INA2xx family of Power Monitors. + This driver is mutually exclusive with the HWMON version. + +config INGENIC_ADC + tristate "Ingenic JZ47xx SoCs ADC driver" + depends on MIPS || COMPILE_TEST + select IIO_BUFFER + help + Say yes here to build support for the Ingenic JZ47xx SoCs ADC unit. + + This driver can also be built as a module. If so, the module will be + called ingenic_adc. + +config INTEL_DC_TI_ADC + tristate "Intel Bay Trail / Cherry Trail Dollar Cove TI ADC driver" + depends on INTEL_SOC_PMIC_CHTDC_TI + help + Say yes here to have support for the Dollar Cove TI PMIC ADC device. + Depending on platform configuration, this general purpose ADC can be + used for sensors such as battery voltage and thermal resistors. + + To compile this driver as a module, choose M here: the module will be + called intel_dc_ti_adc. + +config INTEL_MRFLD_ADC + tristate "Intel Merrifield Basin Cove ADC driver" + depends on INTEL_SOC_PMIC_MRFLD + help + Say yes here to have support for Basin Cove power management IC (PMIC) ADC + device. Depending on platform configuration, this general purpose ADC can + be used for sampling sensors such as thermal resistors. + + To compile this driver as a module, choose M here: the module will be + called intel_mrfld_adc. + +config IMX7D_ADC + tristate "Freescale IMX7D ADC driver" + depends on ARCH_MXC || COMPILE_TEST + depends on HAS_IOMEM + help + Say yes here to build support for IMX7D ADC. + + This driver can also be built as a module. If so, the module will be + called imx7d_adc. + +config IMX8QXP_ADC + tristate "NXP IMX8QXP ADC driver" + depends on ARCH_MXC || COMPILE_TEST + depends on HAS_IOMEM + help + Say yes here to build support for IMX8QXP ADC. + + This driver can also be built as a module. If so, the module will be + called imx8qxp-adc. + +config IMX93_ADC + tristate "IMX93 ADC driver" + depends on ARCH_MXC || COMPILE_TEST + depends on HAS_IOMEM + help + Say yes here to build support for IMX93 ADC. + + This driver can also be built as a module. If so, the module will be + called imx93_adc. + +config LP8788_ADC + tristate "LP8788 ADC driver" + depends on MFD_LP8788 + help + Say yes here to build support for TI LP8788 ADC. + + To compile this driver as a module, choose M here: the module will be + called lp8788_adc. + +config LPC18XX_ADC + tristate "NXP LPC18xx ADC driver" + depends on ARCH_LPC18XX || COMPILE_TEST + depends on HAS_IOMEM + help + Say yes here to build support for NXP LPC18XX ADC. + + To compile this driver as a module, choose M here: the module will be + called lpc18xx_adc. + +config LPC32XX_ADC + tristate "NXP LPC32XX ADC" + depends on ARCH_LPC32XX || COMPILE_TEST + depends on HAS_IOMEM + help + Say yes here to build support for the integrated ADC inside the + LPC32XX SoC. Note that this feature uses the same hardware as the + touchscreen driver, so you should either select only one of the two + drivers (lpc32xx_adc or lpc32xx_ts) or, in the OpenFirmware case, + activate only one via device tree selection. Provides direct access + via sysfs. + +config LTC2309 + tristate "Linear Technology LTC2309 ADC driver" + depends on I2C + help + Say yes here to build support for Linear Technology LTC2309, a low + noise, low power, 8-channel, 12-bit SAR ADC + + This driver can also be built as a module. If so, the module will + be called ltc2309. + +config LTC2378 + tristate "Analog Devices LTC2378 ADC driver" + depends on SPI + depends on REGULATOR || COMPILE_TEST + depends on GPIOLIB + depends on PWM + select IIO_BUFFER + select IIO_BUFFER_DMA + select IIO_BUFFER_DMAENGINE + select IIO_TRIGGERED_BUFFER + select SPI_OFFLOAD + select SPI_OFFLOAD_TRIGGER_PWM + help + Say yes here to build support for Analog Devices LTC2378-20 and + similar analog to digital converters. + + This driver can also be built as a module. If so, the module will + be called ltc2378. + +config LTC2471 + tristate "Linear Technology LTC2471 and LTC2473 ADC driver" + depends on I2C + help + Say yes here to build support for Linear Technology LTC2471 and + LTC2473 16-bit I2C ADC. + + This driver can also be built as a module. If so, the module will + be called ltc2471. + +config LTC2485 + tristate "Linear Technology LTC2485 ADC driver" + depends on I2C + help + Say yes here to build support for Linear Technology LTC2485 ADC. + + To compile this driver as a module, choose M here: the module will be + called ltc2485. + +config LTC2496 + tristate "Linear Technology LTC2496 ADC driver" + depends on SPI + help + Say yes here to build support for Linear Technology LTC2496 + 16-Bit 8-/16-Channel Delta Sigma ADC. + + To compile this driver as a module, choose M here: the module will be + called ltc2496. + +config LTC2497 + tristate "Linear Technology LTC2497 ADC driver" + depends on I2C + help + Say yes here to build support for Linear Technology LTC2497 + 16-Bit 8-/16-Channel Delta Sigma ADC. + + To compile this driver as a module, choose M here: the module will be + called ltc2497. + +config MAX1027 + tristate "Maxim max1027 ADC driver" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Maxim SPI {10,12}-bit ADC models: + max1027, max1029, max1031, max1227, max1229 and max1231. + + To compile this driver as a module, choose M here: the module will be + called max1027. + +config MAX11100 + tristate "Maxim max11100 ADC driver" + depends on SPI_MASTER + help + Say yes here to build support for Maxim max11100 SPI ADC + + To compile this driver as a module, choose M here: the module will be + called max11100. + +config MAX1118 + tristate "Maxim max1117/max1118/max1119 ADCs driver" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Maxim max1117/max1118/max1119 + 8-bit, dual-channel ADCs. + + To compile this driver as a module, choose M here: the module will be + called max1118. + +config MAX11205 + tristate "Maxim max11205 ADC driver" + depends on SPI + select AD_SIGMA_DELTA + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + + help + Say yes here to build support for Maxim max11205 16-bit, single-channel + ultra-low power delta-sigma ADC. + + To compile this driver as a module, choose M here: the module will be + called max11205. + +config MAX11410 + tristate "Analog Devices MAX11410 ADC driver" + depends on SPI + select REGMAP_SPI + select IIO_BUFFER + select IIO_TRIGGER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Analog Devices MAX11410 ADCs. + + To compile this driver as a module, choose M here: the module will be + called max11410. + +config MAX1241 + tristate "Maxim max1241 ADC driver" + depends on SPI_MASTER + help + Say yes here to build support for Maxim max1241 12-bit, single-channel + ADC. + + To compile this driver as a module, choose M here: the module will be + called max1241. + +config MAX1363 + tristate "Maxim max1363 ADC driver" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for many Maxim i2c analog to digital + converters (ADC). (max1361, max1362, max1363, max1364, max1036, + max1037, max1038, max1039, max1136, max1136, max1137, max1138, + max1139, max1236, max1237, max11238, max1239, max11600, max11601, + max11602, max11603, max11604, max11605, max11606, max11607, + max11608, max11609, max11610, max11611, max11612, max11613, + max11614, max11615, max11616, max11617, max11644, max11645, + max11646, max11647) Provides direct access via sysfs and buffered + data via the iio dev interface. + + To compile this driver as a module, choose M here: the module will be + called max1363. + +config MAX14001 + tristate "Analog Devices MAX14001/MAX14002 ADC driver" + depends on SPI + select REGMAP + help + Say yes here to build support for Analog Devices MAX14001/MAX14002 + Configurable, Isolated 10-bit ADCs for Multi-Range Binary Inputs. + + To compile this driver as a module, choose M here: the module will be + called max14001. + +config MAX34408 + tristate "Maxim max34408/max344089 ADC driver" + depends on I2C + select REGMAP_I2C + help + Say yes here to build support for Maxim max34408/max34409 current sense + monitor with 8-bits ADC interface with overcurrent delay/threshold and + shutdown delay. + + To compile this driver as a module, choose M here: the module will be + called max34408. + +config MAX77541_ADC + tristate "Analog Devices MAX77541 ADC driver" + depends on MFD_MAX77541 + help + This driver controls a Analog Devices MAX77541 ADC + via I2C bus. This device has one adc. Say yes here to build + support for Analog Devices MAX77541 ADC interface. + + To compile this driver as a module, choose M here: + the module will be called max77541-adc. + +config MAX9611 + tristate "Maxim max9611/max9612 ADC driver" + depends on I2C + help + Say yes here to build support for Maxim max9611/max9612 current sense + amplifier with 12-bits ADC interface. + + To compile this driver as a module, choose M here: the module will be + called max9611. + +config MCP320X + tristate "Microchip Technology MCP3x01/02/04/08 and MCP3550/1/3" + depends on SPI + help + Say yes here to build support for Microchip Technology's + MCP3001, MCP3002, MCP3004, MCP3008, MCP3201, MCP3202, MCP3204, + MCP3208, MCP3301, MCP3550, MCP3551 and MCP3553 analog to digital + converters. + + This driver can also be built as a module. If so, the module will be + called mcp320x. + +config MCP3422 + tristate "Microchip Technology MCP3421/2/3/4/5/6/7/8 driver" + depends on I2C + help + Say yes here to build support for Microchip Technology's MCP3421 + MCP3422, MCP3423, MCP3424, MCP3425, MCP3426, MCP3427 or MCP3428 + analog to digital converters. + + This driver can also be built as a module. If so, the module will be + called mcp3422. + +config MCP3564 + tristate "Microchip Technology MCP3461/2/4/R, MCP3561/2/4/R driver" + depends on SPI + depends on IIO + help + Say yes here to build support for Microchip Technology's MCP3461, + MCP3462, MCP3464, MCP3461R, MCP3462R, MCP3464R, MCP3561, MCP3562, + MCP3564, MCP3561R, MCP3562R and MCP3564R analog to digital + converters. + + This driver can also be built as a module. If so, the module will be + called mcp3564. + +config MCP3911 + tristate "Microchip Technology MCP3911 driver" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for one of the following + Microchip Technology's analog to digital converters: + MCP3910, MCP3911, MCP3912, MCP3913, MCP3914, + MCP3918 and MCP3919. + + This driver can also be built as a module. If so, the module will be + called mcp3911. + +config MEDIATEK_MT6323_AUXADC + tristate "MediaTek MT6323 PMIC AUXADC driver" + depends on MFD_MT6397 + help + Say yes here to enable support for MediaTek MT6323 PMIC Auxiliary ADC. + This driver provides multiple channels for system monitoring, + such as battery voltage, PMIC temperature, and others. + + This driver can also be built as a module. If so, the module will be + called mt6323-auxadc. + +config MEDIATEK_MT6359_AUXADC + tristate "MediaTek MT6359 PMIC AUXADC driver" + depends on MFD_MT6397 + help + Say yes here to enable support for MediaTek MT6357, MT6358 and + MT6359 PMICs Auxiliary ADC. + This driver provides multiple channels for system monitoring, + such as battery voltage, PMIC temperature, and others. + + This driver can also be built as a module. If so, the module will be + called mt6359-auxadc. + +config MEDIATEK_MT6360_ADC + tristate "Mediatek MT6360 ADC driver" + depends on MFD_MT6360 + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here to enable MT6360 ADC support. + Integrated for System Monitoring includes + is used in smartphones and tablets and supports a 11 channel + general purpose ADC. + +config MEDIATEK_MT6370_ADC + tristate "MediaTek MT6370 ADC driver" + depends on MFD_MT6370 + help + Say yes here to enable MediaTek MT6370 ADC support. + + This ADC driver provides 9 channels for system monitoring (charger + current, voltage, and temperature). + + This driver can also be built as a module. If so, the module + will be called "mt6370-adc". + +config MEDIATEK_MT6577_AUXADC + tristate "MediaTek AUXADC driver" + depends on ARCH_MEDIATEK || COMPILE_TEST + depends on HAS_IOMEM + help + Say yes here to enable support for MediaTek mt65xx AUXADC. + + The driver supports immediate mode operation to read from one of sixteen + channels (external or internal). + + This driver can also be built as a module. If so, the module will be + called mt6577_auxadc. + +config MEN_Z188_ADC + tristate "MEN 16z188 ADC IP Core support" + depends on MCB + help + Say yes here to enable support for the MEN 16z188 ADC IP-Core on a MCB + carrier. + + This driver can also be built as a module. If so, the module will be + called men_z188_adc. + +config MESON_SARADC + tristate "Amlogic Meson SAR ADC driver" + default ARCH_MESON + depends on OF && COMMON_CLK && (ARCH_MESON || COMPILE_TEST) + select REGMAP_MMIO + help + Say yes here to build support for the SAR ADC found in Amlogic Meson + SoCs. + + To compile this driver as a module, choose M here: the + module will be called meson_saradc. + +config MP2629_ADC + tristate "Monolithic MP2629 ADC driver" + depends on MFD_MP2629 + help + Say yes to have support for battery charger IC MP2629 ADC device + accessed over I2C. + + This driver provides ADC conversion of system, input power supply + and battery voltage & current information. + +config NAU7802 + tristate "Nuvoton NAU7802 ADC driver" + depends on I2C + help + Say yes here to build support for Nuvoton NAU7802 ADC. + + To compile this driver as a module, choose M here: the + module will be called nau7802. + +config NCT7201 + tristate "Nuvoton Instruments NCT7201 and NCT7202 Power Monitor" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for the Nuvoton NCT7201 and + NCT7202 Voltage Monitor. + + This driver can also be built as a module. If so, the module + will be called nct7201. + +config NPCM_ADC + tristate "Nuvoton NPCM ADC driver" + depends on ARCH_NPCM || COMPILE_TEST + depends on HAS_IOMEM + help + Say yes here to build support for Nuvoton NPCM ADC. + + This driver can also be built as a module. If so, the module + will be called npcm_adc. + +config NXP_SAR_ADC + tristate "NXP S32G SAR-ADC driver" + depends on ARCH_S32 || COMPILE_TEST + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for S32G platforms + analog-to-digital converter. + + This driver can also be built as a module. If so, the module will be + called nxp_sar_adc. + +config PAC1921 + tristate "Microchip Technology PAC1921 driver" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Microchip Technology's PAC1921 + High-Side Power/Current Monitor with Analog Output. + + This driver can also be built as a module. If so, the module + will be called pac1921. + +config PAC1934 + tristate "Microchip Technology PAC1934 driver" + depends on I2C + help + Say yes here to build support for Microchip Technology's PAC1931, + PAC1932, PAC1933, PAC1934 Single/Multi-Channel Power Monitor with + Accumulator. + + This driver can also be built as a module. If so, the module + will be called pac1934. + +config PALMAS_GPADC + tristate "TI Palmas General Purpose ADC" + depends on MFD_PALMAS + help + Palmas series pmic chip by Texas Instruments (twl6035/6037) + is used in smartphones and tablets and supports a 16 channel + general purpose ADC. + +config QCOM_VADC_COMMON + tristate + +config QCOM_PM8XXX_XOADC + tristate "Qualcomm SSBI PM8xxx PMIC XOADCs" + depends on MFD_PM8XXX + select QCOM_VADC_COMMON + help + ADC driver for the XOADC portions of the Qualcomm PM8xxx PMICs + using SSBI transport: PM8018, PM8038, PM8058, PM8921. + + To compile this driver as a module, choose M here: the module + will be called qcom-pm8xxx-xoadc. + +config QCOM_SPMI_RRADC + tristate "Qualcomm SPMI RRADC" + depends on MFD_SPMI_PMIC + help + This is for the PMIC Round Robin ADC driver. + + This driver exposes the battery ID resistor, battery thermal, PMIC die + temperature, charger USB in and DC in voltage and current. + + To compile this driver as a module, choose M here: the module will + be called qcom-qpmi-rradc. + +config QCOM_SPMI_IADC + tristate "Qualcomm SPMI PMIC current ADC" + depends on SPMI + select REGMAP_SPMI + help + This is the IIO Current ADC driver for Qualcomm QPNP IADC Chip. + + The driver supports single mode operation to read from one of two + channels (external or internal). Hardware have additional + channels internally used for gain and offset calibration. + + To compile this driver as a module, choose M here: the module will + be called qcom-spmi-iadc. + +config QCOM_SPMI_VADC + tristate "Qualcomm SPMI PMIC voltage ADC" + depends on SPMI + select REGMAP_SPMI + select QCOM_VADC_COMMON + help + This is the IIO Voltage ADC driver for Qualcomm QPNP VADC Chip. + + The driver supports multiple channels read. The VADC is a 15-bit + sigma-delta ADC. Some of the channels are internally used for + calibration. + + To compile this driver as a module, choose M here: the module will + be called qcom-spmi-vadc. + +config QCOM_SPMI_ADC5 + tristate "Qualcomm SPMI PMIC5 ADC" + depends on SPMI + select REGMAP_SPMI + select QCOM_VADC_COMMON + help + This is the IIO Voltage PMIC5 ADC driver for Qualcomm Technologies Inc. + + The driver supports multiple channels read. The ADC is a 16-bit + sigma-delta ADC. The hardware supports calibrated results for + conversion requests and clients include reading voltage phone + power, on board system thermistors connected to the PMIC ADC, + PMIC die temperature, charger temperature, battery current, USB voltage + input, voltage signals connected to supported PMIC GPIO inputs. The + hardware supports internal pull-up for thermistors and can choose between + a 100k, 30k and 400k pull up using the ADC channels. + + To compile this driver as a module, choose M here: the module will + be called qcom-spmi-adc5. + +config QCOM_SPMI_ADC5_GEN3 + tristate "Qualcomm SPMI PMIC5 GEN3 ADC" + depends on SPMI && THERMAL + select REGMAP_SPMI + select QCOM_VADC_COMMON + select AUXILIARY_BUS + help + IIO Voltage PMIC5 Gen3 ADC driver for Qualcomm Technologies Inc. + + The driver supports reading multiple channels. The ADC is a 16-bit + sigma-delta ADC. The hardware supports calibrated results for + conversion requests and clients include reading phone power supply + voltage, on board system thermistors connected to the PMIC ADC, + PMIC die temperature, charger temperature, battery current, USB + voltage input and voltage signals connected to supported PMIC GPIO + pins. The hardware supports internal pull-up for thermistors and can + choose between a 30k, 100k or 400k ohm pull up using the ADC channels. + + In addition, the same driver supports ADC thermal monitoring devices + too. They appear as thermal zones with multiple trip points. A thermal + client sets threshold temperature for both warm and cool trips and + gets updated when a threshold is reached. + + To compile this driver as a module, choose M here: the module will + be called qcom-spmi-adc5-gen3. + +config RCAR_GYRO_ADC + tristate "Renesas R-Car GyroADC driver" + depends on ARCH_RCAR_GEN2 || COMPILE_TEST + help + Say yes here to build support for the GyroADC found in Renesas + R-Car Gen2 SoCs. This block is a simple SPI offload engine for + reading data out of attached compatible ADCs in a round-robin + fashion. Up to 4 or 8 ADC channels are supported by this block, + depending on which ADCs are attached. + + To compile this driver as a module, choose M here: the + module will be called rcar-gyroadc. + +config RN5T618_ADC + tristate "ADC for the RN5T618/RC5T619 family of chips" + depends on MFD_RN5T618 + help + Say yes here to build support for the integrated ADC inside the + RN5T618/619 series PMICs: + + This driver can also be built as a module. If so, the module + will be called rn5t618-adc. + +config ROHM_BD79112 + tristate "Rohm BD79112 ADC driver" + depends on SPI && GPIOLIB + select REGMAP_SPI + select IIO_ADC_HELPER + help + Say yes here to build support for the ROHM BD79112 ADC. The + ROHM BD79112 is a 12-bit, 32-channel, SAR ADC. Analog inputs + can also be used for GPIO. + +config ROHM_BD79124 + tristate "Rohm BD79124 ADC driver" + depends on I2C && GPIOLIB + select REGMAP_I2C + select IIO_ADC_HELPER + help + Say yes here to build support for the ROHM BD79124 ADC. The + ROHM BD79124 is a 12-bit, 8-channel, SAR ADC. The ADC supports + also an automatic measurement mode, with an alarm interrupt for + out-of-window measurements. The window is configurable for each + channel. + +config ROCKCHIP_SARADC + tristate "Rockchip SARADC driver" + depends on ARCH_ROCKCHIP || COMPILE_TEST + depends on RESET_CONTROLLER + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for the SARADC found in SoCs from + Rockchip. + + To compile this driver as a module, choose M here: the + module will be called rockchip_saradc. + +config RICHTEK_RTQ6056 + tristate "Richtek RTQ6056 Current and Power Monitor ADC" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to enable RQT6056 ADC support. + RTQ6056 is a high accuracy current-sense monitor with I2C and SMBus + compatible interface, and the device provides full information for + system by reading out the load current and power. + + This driver can also be built as a module. If so, the module will be + called rtq6056. + +config RZG2L_ADC + tristate "Renesas RZ/G2L ADC driver" + depends on ARCH_RZG2L || COMPILE_TEST + select IIO_ADC_HELPER + help + Say yes here to build support for the ADC found in Renesas + RZ/G2L family. + + To compile this driver as a module, choose M here: the + module will be called rzg2l_adc. + +config RZN1_ADC + tristate "Renesas RZ/N1 ADC driver" + depends on ARCH_RZN1 || COMPILE_TEST + help + Say yes here to build support for the ADC found in Renesas + RZ/N1 family. + + To compile this driver as a module, choose M here: the + module will be called rzn1-adc. + +config RZT2H_ADC + tristate "Renesas RZ/T2H / RZ/N2H ADC driver" + depends on ARCH_RENESAS || COMPILE_TEST + select IIO_ADC_HELPER + help + Say yes here to build support for the ADC found in Renesas + RZ/T2H / RZ/N2H SoCs. + + To compile this driver as a module, choose M here: the + module will be called rzt2h_adc. + +config SC27XX_ADC + tristate "Spreadtrum SC27xx series PMICs ADC" + depends on MFD_SC27XX_PMIC || COMPILE_TEST + help + Say yes here to build support for the integrated ADC inside the + Spreadtrum SC27xx series PMICs. + + This driver can also be built as a module. If so, the module + will be called sc27xx_adc. + +config SOPHGO_CV1800B_ADC + tristate "Sophgo CV1800B SARADC" + depends on ARCH_SOPHGO || COMPILE_TEST + help + Say yes here to build support for the SARADC integrated inside + the Sophgo CV1800B SoC. + + This driver can also be built as a module. If so, the module + will be called sophgo_cv1800b_adc. + +config SPEAR_ADC + tristate "ST SPEAr ADC" + depends on PLAT_SPEAR || COMPILE_TEST + depends on HAS_IOMEM + help + Say yes here to build support for the integrated ADC inside the + ST SPEAr SoC. Provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called spear_adc. + +config SD_ADC_MODULATOR + tristate "Generic sigma delta modulator" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select IIO_BACKEND + help + Select this option to enables sigma delta modulator. This driver can + support generic sigma delta modulators. + + This driver can also be built as a module. If so, the module + will be called sd_adc_modulator. + +config STM32_ADC_CORE + tristate "STMicroelectronics STM32 adc core" + depends on ARCH_STM32 || COMPILE_TEST + depends on OF + depends on REGULATOR + depends on HAS_IOMEM + select IIO_BUFFER + select MFD_STM32_TIMERS + select IIO_STM32_TIMER_TRIGGER + select IIO_TRIGGERED_BUFFER + help + Select this option to enable the core driver for STMicroelectronics + STM32 analog-to-digital converter (ADC). + + This driver can also be built as a module. If so, the module + will be called stm32-adc-core. + +config STM32_ADC + tristate "STMicroelectronics STM32 adc" + depends on STM32_ADC_CORE + help + Say yes here to build support for STMicroelectronics stm32 Analog + to Digital Converter (ADC). + + This driver can also be built as a module. If so, the module + will be called stm32-adc. + +config STM32_DFSDM_CORE + tristate "STMicroelectronics STM32 DFSDM core" + depends on (ARCH_STM32 && OF) || COMPILE_TEST + select REGMAP + select REGMAP_MMIO + help + Select this option to enable the driver for STMicroelectronics + STM32 digital filter for sigma delta converter. + + This driver can also be built as a module. If so, the module + will be called stm32-dfsdm-core. + +config STM32_DFSDM_ADC + tristate "STMicroelectronics STM32 dfsdm adc" + depends on (ARCH_STM32 && OF) || COMPILE_TEST + select STM32_DFSDM_CORE + select REGMAP_MMIO + select IIO_BUFFER + select IIO_BUFFER_HW_CONSUMER + select IIO_TRIGGERED_BUFFER + select IIO_BACKEND + help + Select this option to support ADCSigma delta modulator for + STMicroelectronics STM32 digital filter for sigma delta converter. + + This driver can also be built as a module. If so, the module + will be called stm32-dfsdm-adc. + +config STMPE_ADC + tristate "STMicroelectronics STMPE ADC driver" + depends on OF && MFD_STMPE + help + Say yes here to build support for ST Microelectronics STMPE + built-in ADC block (stmpe811). + +config SUN4I_GPADC + tristate "Allwinner A10/A13/A31 and similar GPADCs driver" + depends on IIO + depends on MFD_SUN4I_GPADC || MACH_SUN8I + depends on THERMAL || !THERMAL_OF + select REGMAP_IRQ + help + Say yes here to build support for Allwinner (A10, A13 and A31) SoCs + GPADC. This ADC provides 4 channels which can be used as an ADC or as + a touchscreen input and one channel for thermal sensor. + + The thermal sensor slows down ADC readings and can be disabled by + disabling CONFIG_THERMAL_OF. However, the thermal sensor should be + enabled by default since the SoC temperature is usually more critical + than ADC readings. + + To compile this driver as a module, choose M here: the module will be + called sun4i-gpadc-iio. + +config SUN20I_GPADC + tristate "Allwinner D1/T113s/T507/R329 and similar GPADCs driver" + depends on ARCH_SUNXI || COMPILE_TEST + select IIO_ADC_HELPER + help + Say yes here to build support for Allwinner (D1, T113, T507 and R329) + SoCs GPADC. This ADC provides up to 16 channels. + + To compile this driver as a module, choose M here: the module will be + called sun20i-gpadc-iio. + +config TI_ADC081C + tristate "Texas Instruments ADC081C/ADC101C/ADC121C family" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for Texas Instruments ADC081C, + ADC101C and ADC121C ADC chips. + + This driver can also be built as a module. If so, the module will be + called ti-adc081c. + +config TI_ADC0832 + tristate "Texas Instruments ADC0831/ADC0832/ADC0834/ADC0838" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for Texas Instruments ADC0831, + ADC0832, ADC0834, ADC0838 ADC chips. + + This driver can also be built as a module. If so, the module will be + called ti-adc0832. + +config TI_ADC084S021 + tristate "Texas Instruments ADC084S021" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for Texas Instruments ADC084S021 + chips. + + This driver can also be built as a module. If so, the module will be + called ti-adc084s021. + +config TI_ADC108S102 + tristate "Texas Instruments ADC108S102 and ADC128S102 driver" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Texas Instruments ADC108S102 and + ADC128S102 ADC. + + To compile this driver as a module, choose M here: the module will + be called ti-adc108s102. + +config TI_ADC12138 + tristate "Texas Instruments ADC12130/ADC12132/ADC12138" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for Texas Instruments ADC12130, + ADC12132 and ADC12138 chips. + + This driver can also be built as a module. If so, the module will be + called ti-adc12138. + +config TI_ADC128S052 + tristate "Texas Instruments ADC128S052/ADC122S021/ADC124S021" + depends on SPI + help + If you say yes here you get support for Texas Instruments ADC128S052, + ADC122S021, ADC124S021 and ROHM Semiconductor BD79104 chips. + + This driver can also be built as a module. If so, the module will be + called ti-adc128s052. + +config TI_ADC161S626 + tristate "Texas Instruments ADC161S626 1-channel differential ADC" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for Texas Instruments ADC141S626, + and ADC161S626 chips. + + This driver can also be built as a module. If so, the module will be + called ti-adc161s626. + +config TI_ADS1015 + tristate "Texas Instruments ADS1015 ADC" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for Texas Instruments ADS1015 + ADC chip. + + This driver can also be built as a module. If so, the module will be + called ti-ads1015. + +config TI_ADS1018 + tristate "Texas Instruments ADS1018 ADC" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for Texas Instruments ADS1018 and + ADS1118 ADC chips. + + This driver can also be built as a module. If so, the module will be + called ti-ads1018. + +config TI_ADS1100 + tristate "Texas Instruments ADS1100 and ADS1000 ADC" + depends on I2C + help + If you say yes here you get support for Texas Instruments ADS1100 and + ADS1000 ADC chips. + + This driver can also be built as a module. If so, the module will be + called ti-ads1100. + +config TI_ADS1119 + tristate "Texas Instruments ADS1119 ADC" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for Texas Instruments ADS1119 + ADC chip. + + This driver can also be built as a module. If so, the module will be + called ti-ads1119. + +config TI_ADS112C14 + tristate "Texas Instruments ADS112C14/ADS122C14" + depends on I2C + select CRC8 + select REGMAP + help + If you say yes here you get support for Texas Instruments ADS112C14, + ADS122C14 ADC chips. + + This driver can also be built as a module. If so, the module will be + called ti-ads112c14. + +config TI_ADS124S08 + tristate "Texas Instruments ADS124S08" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for Texas Instruments ADS124S08 + and ADS124S06 ADC chips + + This driver can also be built as a module. If so, the module will be + called ti-ads124s08. + +config TI_ADS1298 + tristate "Texas Instruments ADS1298" + depends on SPI + select IIO_BUFFER + select IIO_KFIFO_BUF + help + If you say yes here you get support for Texas Instruments ADS1298 + medical ADC chips + + This driver can also be built as a module. If so, the module will be + called ti-ads1298. + +config TI_ADS131E08 + tristate "Texas Instruments ADS131E08" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to get support for Texas Instruments ADS131E04, ADS131E06 + and ADS131E08 chips. + + This driver can also be built as a module. If so, the module will be + called ti-ads131e08. + +config TI_ADS131M02 + tristate "Texas Instruments ADS131M02" + depends on SPI && REGULATOR + select CRC_ITU_T + help + Say yes here to get support for Texas Instruments ADS131M02, ADS131M03, + ADS131M04, ADS131M06 and ADS131M08 chips. + + This driver can also be built as a module. If so, the module will be + called ti-ads131m02. + +config TI_ADS7138 + tristate "Texas Instruments ADS7128 and ADS7138 ADC driver" + depends on I2C + help + If you say yes here you get support for Texas Instruments ADS7128 and + ADS7138 8-channel A/D converters with 12-bit resolution. + + This driver can also be built as a module. If so, the module will be + called ti-ads7138. + +config TI_ADS7924 + tristate "Texas Instruments ADS7924 ADC" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for Texas Instruments ADS7924 + 4 channels, 12-bit I2C ADC chip. + + This driver can also be built as a module. If so, the module will be + called ti-ads7924. + +config TI_ADS7950 + tristate "Texas Instruments ADS7950 ADC driver" + depends on SPI && GPIOLIB + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Texas Instruments ADS7950, ADS7951, + ADS7952, ADS7953, ADS7954, ADS7955, ADS7956, ADS7957, ADS7958, ADS7959. + ADS7960, ADS7961. + + To compile this driver as a module, choose M here: the + module will be called ti-ads7950. + +config TI_ADS8344 + tristate "Texas Instruments ADS8344" + depends on SPI + help + If you say yes here you get support for Texas Instruments ADS8344 + ADC chips + + This driver can also be built as a module. If so, the module will be + called ti-ads8344. + +config TI_ADS8688 + tristate "Texas Instruments ADS8688" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for Texas Instruments ADS8684 and + and ADS8688 ADC chips + + This driver can also be built as a module. If so, the module will be + called ti-ads8688. + +config TI_AM335X_ADC + tristate "TI's AM335X ADC driver" + depends on MFD_TI_AM335X_TSCADC && HAS_DMA + select IIO_BUFFER + select IIO_KFIFO_BUF + help + Say yes here to build support for Texas Instruments ADC + driver which is also a MFD client. + + To compile this driver as a module, choose M here: the module will be + called ti_am335x_adc. + +config TI_LMP92064 + tristate "Texas Instruments LMP92064 ADC driver" + depends on SPI + select REGMAP_SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for the LMP92064 Precision Current and Voltage + sensor. + + This driver can also be built as a module. If so, the module will be called + ti-lmp92064. + +config TI_TLC4541 + tristate "Texas Instruments TLC4541 ADC driver" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Texas Instruments TLC4541 / TLC3541 + ADC chips. + + This driver can also be built as a module. If so, the module will be + called ti-tlc4541. + +config TI_TSC2046 + tristate "Texas Instruments TSC2046 ADC driver" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for ADC functionality of Texas + Instruments TSC2046 touch screen controller. + + This driver can also be built as a module. If so, the module will be + called ti-tsc2046. + +config TWL4030_MADC + tristate "TWL4030 MADC (Monitoring A/D Converter)" + depends on TWL4030_CORE + help + This driver provides support for Triton TWL4030-MADC. The + driver supports both RT and SW conversion methods. + + This driver can also be built as a module. If so, the module will be + called twl4030-madc. + +config TWL6030_GPADC + tristate "TWL6030 GPADC (General Purpose A/D Converter) Support" + depends on TWL4030_CORE + help + Say yes here if you want support for the TWL6030/TWL6032 General + Purpose A/D Converter. This will add support for battery type + detection, battery voltage and temperature measurement, die + temperature measurement, system supply voltage, audio accessory, + USB ID detection. + + This driver can also be built as a module. If so, the module will be + called twl6030-gpadc. + +config VERSAL_SYSMON_CORE + tristate + select REGMAP + +config VERSAL_SYSMON + tristate "AMD Versal SysMon driver" + depends on ARCH_ZYNQMP || COMPILE_TEST + depends on HAS_IOMEM + select VERSAL_SYSMON_CORE + help + Say yes here to have support for the AMD/Xilinx Versal System + Monitor (SysMon). This driver provides voltage and temperature + monitoring through the IIO subsystem. + + The SysMon measures up to 160 supply voltages and reads up to + 64 temperature satellites distributed across the SoC. + + To compile this driver as a module, choose M here: the module + will be called versal-sysmon. + +config VERSAL_SYSMON_I2C + tristate "AMD Versal SysMon I2C driver" + depends on I2C + select VERSAL_SYSMON_CORE + help + Say yes here to have support for the AMD/Xilinx Versal System + Monitor (SysMon) via I2C interface. This driver enables voltage + and temperature monitoring when the Versal chip has SysMon + configured with I2C access. + + To compile this driver as a module, choose M here: the module + will be called versal-sysmon-i2c. + +config VF610_ADC + tristate "Freescale vf610 ADC driver" + depends on HAS_IOMEM + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to support for Vybrid board analog-to-digital converter. + Since the IP is used for i.MX6SLX, the driver also support i.MX6SLX. + + This driver can also be built as a module. If so, the module will be + called vf610_adc. + +config VIPERBOARD_ADC + tristate "Viperboard ADC support" + depends on MFD_VIPERBOARD && USB + help + Say yes here to access the ADC part of the Nano River + Technologies Viperboard. + + To compile this driver as a module, choose M here: the module will be + called viperboard_adc. + +config XILINX_XADC + tristate "Xilinx XADC driver" + depends on HAS_IOMEM + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to have support for the Xilinx 7 Series XADC or + UltraScale/UltraScale+ System Management Wizard. + + For the 7 Series the driver does support both the ZYNQ interface + to the XADC as well as the AXI-XADC interface. + + The driver also support the Xilinx System Management Wizard IP core + that can be used to access the System Monitor ADC on the Xilinx + UltraScale and UltraScale+ FPGAs. + + The driver can also be build as a module. If so, the module will be called + xilinx-xadc. + +config XILINX_AMS + tristate "Xilinx AMS driver" + depends on ARCH_ZYNQMP || COMPILE_TEST + depends on HAS_IOMEM + help + Say yes here to have support for the Xilinx AMS for Ultrascale/Ultrascale+ + System Monitor. With this you can measure and monitor the Voltages and + Temperature values on the SOC. + + The driver supports Voltage and Temperature monitoring on Xilinx Ultrascale + devices. + + The driver can also be built as a module. If so, the module will be called + xilinx-ams. + +endmenu diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile new file mode 100644 index 000000000..dcec0abb0 --- /dev/null +++ b/drivers/iio/adc/Makefile @@ -0,0 +1,181 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for IIO ADC drivers +# + +obj-$(CONFIG_IIO_ADC_HELPER) += industrialio-adc.o + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_88PM886_GPADC) += 88pm886-gpadc.o +obj-$(CONFIG_AB8500_GPADC) += ab8500-gpadc.o +obj-$(CONFIG_AD_SIGMA_DELTA) += ad_sigma_delta.o +obj-$(CONFIG_AD4000) += ad4000.o +obj-$(CONFIG_AD4030) += ad4030.o +obj-$(CONFIG_AD4062) += ad4062.o +obj-$(CONFIG_AD4080) += ad4080.o +obj-$(CONFIG_AD4130) += ad4130.o +obj-$(CONFIG_AD4134) += ad4134.o +obj-$(CONFIG_AD4170_4) += ad4170-4.o +obj-$(CONFIG_AD4691) += ad4691.o +obj-$(CONFIG_AD4695) += ad4695.o +obj-$(CONFIG_AD4851) += ad4851.o +obj-$(CONFIG_AD7091R) += ad7091r-base.o +obj-$(CONFIG_AD7091R5) += ad7091r5.o +obj-$(CONFIG_AD7091R8) += ad7091r8.o +obj-$(CONFIG_AD7124) += ad7124.o +obj-$(CONFIG_AD7173) += ad7173.o +obj-$(CONFIG_AD7191) += ad7191.o +obj-$(CONFIG_AD7192) += ad7192.o +obj-$(CONFIG_AD7266) += ad7266.o +obj-$(CONFIG_AD7280) += ad7280a.o +obj-$(CONFIG_AD7291) += ad7291.o +obj-$(CONFIG_AD7292) += ad7292.o +obj-$(CONFIG_AD7298) += ad7298.o +obj-$(CONFIG_AD7380) += ad7380.o +obj-$(CONFIG_AD7405) += ad7405.o +obj-$(CONFIG_AD7476) += ad7476.o +obj-$(CONFIG_AD7606_IFACE_PARALLEL) += ad7606_par.o +obj-$(CONFIG_AD7606_IFACE_SPI) += ad7606_spi.o +obj-$(CONFIG_AD7606) += ad7606.o +obj-$(CONFIG_AD7625) += ad7625.o +obj-$(CONFIG_AD7766) += ad7766.o +obj-$(CONFIG_AD7768_1) += ad7768-1.o +obj-$(CONFIG_AD7779) += ad7779.o +obj-$(CONFIG_AD7780) += ad7780.o +obj-$(CONFIG_AD7791) += ad7791.o +obj-$(CONFIG_AD7793) += ad7793.o +obj-$(CONFIG_AD7887) += ad7887.o +obj-$(CONFIG_AD7923) += ad7923.o +obj-$(CONFIG_AD7944) += ad7944.o +obj-$(CONFIG_AD7949) += ad7949.o +obj-$(CONFIG_AD799X) += ad799x.o +obj-$(CONFIG_AD9467) += ad9467.o +obj-$(CONFIG_ADE9000) += ade9000.o +obj-$(CONFIG_ADI_AXI_ADC) += adi-axi-adc.o +obj-$(CONFIG_ASPEED_ADC) += aspeed_adc.o +obj-$(CONFIG_AT91_ADC) += at91_adc.o +obj-$(CONFIG_AT91_SAMA5D2_ADC) += at91-sama5d2_adc.o +obj-$(CONFIG_AXP20X_ADC) += axp20x_adc.o +obj-$(CONFIG_AXP288_ADC) += axp288_adc.o +obj-$(CONFIG_BCM_IPROC_ADC) += bcm_iproc_adc.o +obj-$(CONFIG_BERLIN2_ADC) += berlin2-adc.o +obj-$(CONFIG_CC10001_ADC) += cc10001_adc.o +obj-$(CONFIG_CPCAP_ADC) += cpcap-adc.o +obj-$(CONFIG_DA9150_GPADC) += da9150-gpadc.o +obj-$(CONFIG_DLN2_ADC) += dln2-adc.o +obj-$(CONFIG_ENVELOPE_DETECTOR) += envelope-detector.o +obj-$(CONFIG_EP93XX_ADC) += ep93xx_adc.o +obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o +obj-$(CONFIG_FSL_MX25_ADC) += fsl-imx25-gcq.o +obj-$(CONFIG_GEHC_PMC_ADC) += gehc-pmc-adc.o +obj-$(CONFIG_HI8435) += hi8435.o +obj-$(CONFIG_HX711) += hx711.o +obj-$(CONFIG_IMX7D_ADC) += imx7d_adc.o +obj-$(CONFIG_IMX8QXP_ADC) += imx8qxp-adc.o +obj-$(CONFIG_IMX93_ADC) += imx93_adc.o +obj-$(CONFIG_INA2XX_ADC) += ina2xx-adc.o +obj-$(CONFIG_INGENIC_ADC) += ingenic-adc.o +obj-$(CONFIG_INTEL_DC_TI_ADC) += intel_dc_ti_adc.o +obj-$(CONFIG_INTEL_MRFLD_ADC) += intel_mrfld_adc.o +obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o +obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o +obj-$(CONFIG_LPC32XX_ADC) += lpc32xx_adc.o +obj-$(CONFIG_LTC2309) += ltc2309.o +obj-$(CONFIG_LTC2378) += ltc2378.o +obj-$(CONFIG_LTC2471) += ltc2471.o +obj-$(CONFIG_LTC2485) += ltc2485.o +obj-$(CONFIG_LTC2496) += ltc2496.o ltc2497-core.o +obj-$(CONFIG_LTC2497) += ltc2497.o ltc2497-core.o +obj-$(CONFIG_MAX1027) += max1027.o +obj-$(CONFIG_MAX11100) += max11100.o +obj-$(CONFIG_MAX1118) += max1118.o +obj-$(CONFIG_MAX11205) += max11205.o +obj-$(CONFIG_MAX11410) += max11410.o +obj-$(CONFIG_MAX1241) += max1241.o +obj-$(CONFIG_MAX1363) += max1363.o +obj-$(CONFIG_MAX14001) += max14001.o +obj-$(CONFIG_MAX34408) += max34408.o +obj-$(CONFIG_MAX77541_ADC) += max77541-adc.o +obj-$(CONFIG_MAX9611) += max9611.o +obj-$(CONFIG_MCP320X) += mcp320x.o +obj-$(CONFIG_MCP3422) += mcp3422.o +obj-$(CONFIG_MCP3564) += mcp3564.o +obj-$(CONFIG_MCP3911) += mcp3911.o +obj-$(CONFIG_MEDIATEK_MT6323_AUXADC) += mt6323-auxadc.o +obj-$(CONFIG_MEDIATEK_MT6359_AUXADC) += mt6359-auxadc.o +obj-$(CONFIG_MEDIATEK_MT6360_ADC) += mt6360-adc.o +obj-$(CONFIG_MEDIATEK_MT6370_ADC) += mt6370-adc.o +obj-$(CONFIG_MEDIATEK_MT6577_AUXADC) += mt6577_auxadc.o +obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o +obj-$(CONFIG_MESON_SARADC) += meson_saradc.o +obj-$(CONFIG_MP2629_ADC) += mp2629_adc.o +obj-$(CONFIG_MXS_LRADC_ADC) += mxs-lradc-adc.o +obj-$(CONFIG_NAU7802) += nau7802.o +obj-$(CONFIG_NCT7201) += nct7201.o +obj-$(CONFIG_NPCM_ADC) += npcm_adc.o +obj-$(CONFIG_NXP_SAR_ADC) += nxp-sar-adc.o +obj-$(CONFIG_PAC1921) += pac1921.o +obj-$(CONFIG_PAC1934) += pac1934.o +obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o +obj-$(CONFIG_QCOM_PM8XXX_XOADC) += qcom-pm8xxx-xoadc.o +obj-$(CONFIG_QCOM_SPMI_ADC5) += qcom-spmi-adc5.o +obj-$(CONFIG_QCOM_SPMI_ADC5_GEN3) += qcom-spmi-adc5-gen3.o +obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o +obj-$(CONFIG_QCOM_SPMI_RRADC) += qcom-spmi-rradc.o +obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o +obj-$(CONFIG_QCOM_VADC_COMMON) += qcom-vadc-common.o +obj-$(CONFIG_RCAR_GYRO_ADC) += rcar-gyroadc.o +obj-$(CONFIG_RICHTEK_RTQ6056) += rtq6056.o +obj-$(CONFIG_RN5T618_ADC) += rn5t618-adc.o +obj-$(CONFIG_ROHM_BD79112) += rohm-bd79112.o +obj-$(CONFIG_ROHM_BD79124) += rohm-bd79124.o +obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o +obj-$(CONFIG_RZG2L_ADC) += rzg2l_adc.o +obj-$(CONFIG_RZN1_ADC) += rzn1-adc.o +obj-$(CONFIG_RZT2H_ADC) += rzt2h_adc.o +obj-$(CONFIG_SC27XX_ADC) += sc27xx_adc.o +obj-$(CONFIG_SD_ADC_MODULATOR) += sd_adc_modulator.o +obj-$(CONFIG_SOPHGO_CV1800B_ADC) += sophgo-cv1800b-adc.o +obj-$(CONFIG_SPEAR_ADC) += spear_adc.o +obj-$(CONFIG_STM32_ADC_CORE) += stm32-adc-core.o +obj-$(CONFIG_STM32_ADC) += stm32-adc.o +obj-$(CONFIG_STM32_DFSDM_ADC) += stm32-dfsdm-adc.o +obj-$(CONFIG_STM32_DFSDM_CORE) += stm32-dfsdm-core.o +obj-$(CONFIG_STMPE_ADC) += stmpe-adc.o +obj-$(CONFIG_SUN20I_GPADC) += sun20i-gpadc-iio.o +obj-$(CONFIG_SUN4I_GPADC) += sun4i-gpadc-iio.o +obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o +obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o +obj-$(CONFIG_TI_ADC084S021) += ti-adc084s021.o +obj-$(CONFIG_TI_ADC108S102) += ti-adc108s102.o +obj-$(CONFIG_TI_ADC12138) += ti-adc12138.o +obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o +obj-$(CONFIG_TI_ADC161S626) += ti-adc161s626.o +obj-$(CONFIG_TI_ADS1015) += ti-ads1015.o +obj-$(CONFIG_TI_ADS1018) += ti-ads1018.o +obj-$(CONFIG_TI_ADS1100) += ti-ads1100.o +obj-$(CONFIG_TI_ADS1119) += ti-ads1119.o +obj-$(CONFIG_TI_ADS112C14) += ti-ads112c14.o +obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o +obj-$(CONFIG_TI_ADS1298) += ti-ads1298.o +obj-$(CONFIG_TI_ADS131E08) += ti-ads131e08.o +obj-$(CONFIG_TI_ADS131M02) += ti-ads131m02.o +obj-$(CONFIG_TI_ADS7138) += ti-ads7138.o +obj-$(CONFIG_TI_ADS7924) += ti-ads7924.o +obj-$(CONFIG_TI_ADS7950) += ti-ads7950.o +obj-$(CONFIG_TI_ADS8344) += ti-ads8344.o +obj-$(CONFIG_TI_ADS8688) += ti-ads8688.o +obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o +obj-$(CONFIG_TI_LMP92064) += ti-lmp92064.o +obj-$(CONFIG_TI_TLC4541) += ti-tlc4541.o +obj-$(CONFIG_TI_TSC2046) += ti-tsc2046.o +obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o +obj-$(CONFIG_TWL6030_GPADC) += twl6030-gpadc.o +obj-$(CONFIG_VERSAL_SYSMON_CORE) += versal-sysmon-core.o +obj-$(CONFIG_VERSAL_SYSMON) += versal-sysmon.o +obj-$(CONFIG_VERSAL_SYSMON_I2C) += versal-sysmon-i2c.o +obj-$(CONFIG_VF610_ADC) += vf610_adc.o +obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o +obj-$(CONFIG_XILINX_AMS) += xilinx-ams.o +xilinx-xadc-y := xilinx-xadc-core.o xilinx-xadc-events.o +obj-$(CONFIG_XILINX_XADC) += xilinx-xadc.o diff --git a/drivers/iio/adc/ab8500-gpadc.c b/drivers/iio/adc/ab8500-gpadc.c new file mode 100644 index 000000000..8eaa1dd6a --- /dev/null +++ b/drivers/iio/adc/ab8500-gpadc.c @@ -0,0 +1,1202 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Arun R Murthy + * Author: Daniel Willerud + * Author: Johan Palsson + * Author: M'boumba Cedric Madianga + * Author: Linus Walleij + * + * AB8500 General Purpose ADC driver. The AB8500 uses reference voltages: + * VinVADC, and VADC relative to GND to do its job. It monitors main and backup + * battery voltages, AC (mains) voltage, USB cable voltage, as well as voltages + * representing the temperature of the chip die and battery, accessory + * detection by resistance measurements using relative voltages and GSM burst + * information. + * + * Some of the voltages are measured on external pins on the IC, such as + * battery temperature or "ADC aux" 1 and 2. Other voltages are internal rails + * from other parts of the ASIC such as main charger voltage, main and battery + * backup voltage or USB VBUS voltage. For this reason drivers for other + * parts of the system are required to obtain handles to the ADC to do work + * for them and the IIO driver provides arbitration among these consumers. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* GPADC register offsets and bit definitions */ + +#define AB8500_GPADC_CTRL1_REG 0x00 +/* GPADC control register 1 bits */ +#define AB8500_GPADC_CTRL1_DISABLE 0x00 +#define AB8500_GPADC_CTRL1_ENABLE BIT(0) +#define AB8500_GPADC_CTRL1_TRIG_ENA BIT(1) +#define AB8500_GPADC_CTRL1_START_SW_CONV BIT(2) +#define AB8500_GPADC_CTRL1_BTEMP_PULL_UP BIT(3) +/* 0 = use rising edge, 1 = use falling edge */ +#define AB8500_GPADC_CTRL1_TRIG_EDGE BIT(4) +/* 0 = use VTVOUT, 1 = use VRTC as pull-up supply for battery temp NTC */ +#define AB8500_GPADC_CTRL1_PUPSUPSEL BIT(5) +#define AB8500_GPADC_CTRL1_BUF_ENA BIT(6) +#define AB8500_GPADC_CTRL1_ICHAR_ENA BIT(7) + +#define AB8500_GPADC_CTRL2_REG 0x01 +#define AB8500_GPADC_CTRL3_REG 0x02 +/* + * GPADC control register 2 and 3 bits + * the bit layout is the same for SW and HW conversion set-up + */ +#define AB8500_GPADC_CTRL2_AVG_1 0x00 +#define AB8500_GPADC_CTRL2_AVG_4 BIT(5) +#define AB8500_GPADC_CTRL2_AVG_8 BIT(6) +#define AB8500_GPADC_CTRL2_AVG_16 (BIT(5) | BIT(6)) + +enum ab8500_gpadc_channel { + AB8500_GPADC_CHAN_UNUSED = 0x00, + AB8500_GPADC_CHAN_BAT_CTRL = 0x01, + AB8500_GPADC_CHAN_BAT_TEMP = 0x02, + /* This is not used on AB8505 */ + AB8500_GPADC_CHAN_MAIN_CHARGER = 0x03, + AB8500_GPADC_CHAN_ACC_DET_1 = 0x04, + AB8500_GPADC_CHAN_ACC_DET_2 = 0x05, + AB8500_GPADC_CHAN_ADC_AUX_1 = 0x06, + AB8500_GPADC_CHAN_ADC_AUX_2 = 0x07, + AB8500_GPADC_CHAN_VBAT_A = 0x08, + AB8500_GPADC_CHAN_VBUS = 0x09, + AB8500_GPADC_CHAN_MAIN_CHARGER_CURRENT = 0x0a, + AB8500_GPADC_CHAN_USB_CHARGER_CURRENT = 0x0b, + AB8500_GPADC_CHAN_BACKUP_BAT = 0x0c, + /* Only on AB8505 */ + AB8505_GPADC_CHAN_DIE_TEMP = 0x0d, + AB8500_GPADC_CHAN_ID = 0x0e, + AB8500_GPADC_CHAN_INTERNAL_TEST_1 = 0x0f, + AB8500_GPADC_CHAN_INTERNAL_TEST_2 = 0x10, + AB8500_GPADC_CHAN_INTERNAL_TEST_3 = 0x11, + /* FIXME: Applicable to all ASIC variants? */ + AB8500_GPADC_CHAN_XTAL_TEMP = 0x12, + AB8500_GPADC_CHAN_VBAT_TRUE_MEAS = 0x13, + /* FIXME: Doesn't seem to work with pure AB8500 */ + AB8500_GPADC_CHAN_BAT_CTRL_AND_IBAT = 0x1c, + AB8500_GPADC_CHAN_VBAT_MEAS_AND_IBAT = 0x1d, + AB8500_GPADC_CHAN_VBAT_TRUE_MEAS_AND_IBAT = 0x1e, + AB8500_GPADC_CHAN_BAT_TEMP_AND_IBAT = 0x1f, + /* + * Virtual channel used only for ibat conversion to ampere. + * Battery current conversion (ibat) cannot be requested as a + * single conversion but it is always requested in combination + * with other input requests. + */ + AB8500_GPADC_CHAN_IBAT_VIRTUAL = 0xFF, +}; + +#define AB8500_GPADC_AUTO_TIMER_REG 0x03 + +#define AB8500_GPADC_STAT_REG 0x04 +#define AB8500_GPADC_STAT_BUSY BIT(0) + +#define AB8500_GPADC_MANDATAL_REG 0x05 +#define AB8500_GPADC_MANDATAH_REG 0x06 +#define AB8500_GPADC_AUTODATAL_REG 0x07 +#define AB8500_GPADC_AUTODATAH_REG 0x08 +#define AB8500_GPADC_MUX_CTRL_REG 0x09 +#define AB8540_GPADC_MANDATA2L_REG 0x09 +#define AB8540_GPADC_MANDATA2H_REG 0x0A +#define AB8540_GPADC_APEAAX_REG 0x10 +#define AB8540_GPADC_APEAAT_REG 0x11 +#define AB8540_GPADC_APEAAM_REG 0x12 +#define AB8540_GPADC_APEAAH_REG 0x13 +#define AB8540_GPADC_APEAAL_REG 0x14 + +/* + * OTP register offsets + * Bank : 0x15 + */ +#define AB8500_GPADC_CAL_1 0x0F +#define AB8500_GPADC_CAL_2 0x10 +#define AB8500_GPADC_CAL_3 0x11 +#define AB8500_GPADC_CAL_4 0x12 +#define AB8500_GPADC_CAL_5 0x13 +#define AB8500_GPADC_CAL_6 0x14 +#define AB8500_GPADC_CAL_7 0x15 +/* New calibration for 8540 */ +#define AB8540_GPADC_OTP4_REG_7 0x38 +#define AB8540_GPADC_OTP4_REG_6 0x39 +#define AB8540_GPADC_OTP4_REG_5 0x3A + +#define AB8540_GPADC_DIS_ZERO 0x00 +#define AB8540_GPADC_EN_VBIAS_XTAL_TEMP 0x02 + +/* GPADC constants from AB8500 spec, UM0836 */ +#define AB8500_ADC_RESOLUTION 1024 +#define AB8500_ADC_CH_BTEMP_MIN 0 +#define AB8500_ADC_CH_BTEMP_MAX 1350 +#define AB8500_ADC_CH_DIETEMP_MIN 0 +#define AB8500_ADC_CH_DIETEMP_MAX 1350 +#define AB8500_ADC_CH_CHG_V_MIN 0 +#define AB8500_ADC_CH_CHG_V_MAX 20030 +#define AB8500_ADC_CH_ACCDET2_MIN 0 +#define AB8500_ADC_CH_ACCDET2_MAX 2500 +#define AB8500_ADC_CH_VBAT_MIN 2300 +#define AB8500_ADC_CH_VBAT_MAX 4800 +#define AB8500_ADC_CH_CHG_I_MIN 0 +#define AB8500_ADC_CH_CHG_I_MAX 1500 +#define AB8500_ADC_CH_BKBAT_MIN 0 +#define AB8500_ADC_CH_BKBAT_MAX 3200 + +/* GPADC constants from AB8540 spec */ +#define AB8500_ADC_CH_IBAT_MIN (-6000) /* mA range measured by ADC for ibat */ +#define AB8500_ADC_CH_IBAT_MAX 6000 +#define AB8500_ADC_CH_IBAT_MIN_V (-60) /* mV range measured by ADC for ibat */ +#define AB8500_ADC_CH_IBAT_MAX_V 60 +#define AB8500_GPADC_IBAT_VDROP_L (-56) /* mV */ +#define AB8500_GPADC_IBAT_VDROP_H 56 + +/* This is used to not lose precision when dividing to get gain and offset */ +#define AB8500_GPADC_CALIB_SCALE 1000 +/* + * Number of bits shift used to not lose precision + * when dividing to get ibat gain. + */ +#define AB8500_GPADC_CALIB_SHIFT_IBAT 20 + +/* Time in ms before disabling regulator */ +#define AB8500_GPADC_AUTOSUSPEND_DELAY 1 + +#define AB8500_GPADC_CONVERSION_TIME 500 /* ms */ + +enum ab8500_cal_channels { + AB8500_CAL_VMAIN = 0, + AB8500_CAL_BTEMP, + AB8500_CAL_VBAT, + AB8500_CAL_IBAT, + AB8500_CAL_NR, +}; + +/** + * struct ab8500_adc_cal_data - Table for storing gain and offset for the + * calibrated ADC channels + * @gain: Gain of the ADC channel + * @offset: Offset of the ADC channel + * @otp_calib_hi: Calibration from OTP + * @otp_calib_lo: Calibration from OTP + */ +struct ab8500_adc_cal_data { + s64 gain; + s64 offset; + u16 otp_calib_hi; + u16 otp_calib_lo; +}; + +/** + * struct ab8500_gpadc_chan_info - per-channel GPADC info + * @name: name of the channel + * @id: the internal AB8500 ID number for the channel + * @hardware_control: indicate that we want to use hardware ADC control + * on this channel, the default is software ADC control. Hardware control + * is normally only used to test the battery voltage during GSM bursts + * and needs a hardware trigger on the GPADCTrig pin of the ASIC. + * @falling_edge: indicate that we want to trigger on falling edge + * rather than rising edge, rising edge is the default + * @avg_sample: how many samples to average: must be 1, 4, 8 or 16. + * @trig_timer: how long to wait for the trigger, in 32kHz periods: + * 0 .. 255 periods + */ +struct ab8500_gpadc_chan_info { + const char *name; + u8 id; + bool hardware_control; + bool falling_edge; + u8 avg_sample; + u8 trig_timer; +}; + +/** + * struct ab8500_gpadc - AB8500 GPADC device information + * @dev: pointer to the containing device + * @ab8500: pointer to the parent AB8500 device + * @chans: internal per-channel information container + * @nchans: number of channels + * @complete: pointer to the completion that indicates + * the completion of an gpadc conversion cycle + * @vddadc: pointer to the regulator supplying VDDADC + * @irq_sw: interrupt number that is used by gpadc for software ADC conversion + * @irq_hw: interrupt number that is used by gpadc for hardware ADC conversion + * @cal_data: array of ADC calibration data structs + */ +struct ab8500_gpadc { + struct device *dev; + struct ab8500 *ab8500; + struct ab8500_gpadc_chan_info *chans; + unsigned int nchans; + struct completion complete; + struct regulator *vddadc; + int irq_sw; + int irq_hw; + struct ab8500_adc_cal_data cal_data[AB8500_CAL_NR]; +}; + +static struct ab8500_gpadc_chan_info * +ab8500_gpadc_get_channel(struct ab8500_gpadc *gpadc, u8 chan) +{ + struct ab8500_gpadc_chan_info *ch; + int i; + + for (i = 0; i < gpadc->nchans; i++) { + ch = &gpadc->chans[i]; + if (ch->id == chan) + break; + } + if (i == gpadc->nchans) + return NULL; + + return ch; +} + +/** + * ab8500_gpadc_ad_to_voltage() - Convert a raw ADC value to a voltage + * @gpadc: GPADC instance + * @ch: the sampled channel this raw value is coming from + * @ad_value: the raw value + */ +static int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, + enum ab8500_gpadc_channel ch, + int ad_value) +{ + int res; + + switch (ch) { + case AB8500_GPADC_CHAN_MAIN_CHARGER: + /* No calibration data available: just interpolate */ + if (!gpadc->cal_data[AB8500_CAL_VMAIN].gain) { + res = AB8500_ADC_CH_CHG_V_MIN + (AB8500_ADC_CH_CHG_V_MAX - + AB8500_ADC_CH_CHG_V_MIN) * ad_value / + AB8500_ADC_RESOLUTION; + break; + } + /* Here we can use calibration */ + res = (int) (ad_value * gpadc->cal_data[AB8500_CAL_VMAIN].gain + + gpadc->cal_data[AB8500_CAL_VMAIN].offset) / AB8500_GPADC_CALIB_SCALE; + break; + + case AB8500_GPADC_CHAN_BAT_CTRL: + case AB8500_GPADC_CHAN_BAT_TEMP: + case AB8500_GPADC_CHAN_ACC_DET_1: + case AB8500_GPADC_CHAN_ADC_AUX_1: + case AB8500_GPADC_CHAN_ADC_AUX_2: + case AB8500_GPADC_CHAN_XTAL_TEMP: + /* No calibration data available: just interpolate */ + if (!gpadc->cal_data[AB8500_CAL_BTEMP].gain) { + res = AB8500_ADC_CH_BTEMP_MIN + (AB8500_ADC_CH_BTEMP_MAX - + AB8500_ADC_CH_BTEMP_MIN) * ad_value / + AB8500_ADC_RESOLUTION; + break; + } + /* Here we can use calibration */ + res = (int) (ad_value * gpadc->cal_data[AB8500_CAL_BTEMP].gain + + gpadc->cal_data[AB8500_CAL_BTEMP].offset) / AB8500_GPADC_CALIB_SCALE; + break; + + case AB8500_GPADC_CHAN_VBAT_A: + case AB8500_GPADC_CHAN_VBAT_TRUE_MEAS: + /* No calibration data available: just interpolate */ + if (!gpadc->cal_data[AB8500_CAL_VBAT].gain) { + res = AB8500_ADC_CH_VBAT_MIN + (AB8500_ADC_CH_VBAT_MAX - + AB8500_ADC_CH_VBAT_MIN) * ad_value / + AB8500_ADC_RESOLUTION; + break; + } + /* Here we can use calibration */ + res = (int) (ad_value * gpadc->cal_data[AB8500_CAL_VBAT].gain + + gpadc->cal_data[AB8500_CAL_VBAT].offset) / AB8500_GPADC_CALIB_SCALE; + break; + + case AB8505_GPADC_CHAN_DIE_TEMP: + res = AB8500_ADC_CH_DIETEMP_MIN + + (AB8500_ADC_CH_DIETEMP_MAX - AB8500_ADC_CH_DIETEMP_MIN) * ad_value / + AB8500_ADC_RESOLUTION; + break; + + case AB8500_GPADC_CHAN_ACC_DET_2: + res = AB8500_ADC_CH_ACCDET2_MIN + + (AB8500_ADC_CH_ACCDET2_MAX - AB8500_ADC_CH_ACCDET2_MIN) * ad_value / + AB8500_ADC_RESOLUTION; + break; + + case AB8500_GPADC_CHAN_VBUS: + res = AB8500_ADC_CH_CHG_V_MIN + + (AB8500_ADC_CH_CHG_V_MAX - AB8500_ADC_CH_CHG_V_MIN) * ad_value / + AB8500_ADC_RESOLUTION; + break; + + case AB8500_GPADC_CHAN_MAIN_CHARGER_CURRENT: + case AB8500_GPADC_CHAN_USB_CHARGER_CURRENT: + res = AB8500_ADC_CH_CHG_I_MIN + + (AB8500_ADC_CH_CHG_I_MAX - AB8500_ADC_CH_CHG_I_MIN) * ad_value / + AB8500_ADC_RESOLUTION; + break; + + case AB8500_GPADC_CHAN_BACKUP_BAT: + res = AB8500_ADC_CH_BKBAT_MIN + + (AB8500_ADC_CH_BKBAT_MAX - AB8500_ADC_CH_BKBAT_MIN) * ad_value / + AB8500_ADC_RESOLUTION; + break; + + case AB8500_GPADC_CHAN_IBAT_VIRTUAL: + /* No calibration data available: just interpolate */ + if (!gpadc->cal_data[AB8500_CAL_IBAT].gain) { + res = AB8500_ADC_CH_IBAT_MIN + (AB8500_ADC_CH_IBAT_MAX - + AB8500_ADC_CH_IBAT_MIN) * ad_value / + AB8500_ADC_RESOLUTION; + break; + } + /* Here we can use calibration */ + res = (int) (ad_value * gpadc->cal_data[AB8500_CAL_IBAT].gain + + gpadc->cal_data[AB8500_CAL_IBAT].offset) + >> AB8500_GPADC_CALIB_SHIFT_IBAT; + break; + + default: + dev_err(gpadc->dev, + "unknown channel ID: %d, not possible to convert\n", + ch); + res = -EINVAL; + break; + + } + + return res; +} + +static int ab8500_gpadc_read(struct ab8500_gpadc *gpadc, + const struct ab8500_gpadc_chan_info *ch, + int *ibat) +{ + int ret; + int looplimit = 0; + unsigned long completion_timeout; + u8 val; + u8 low_data, high_data, low_data2, high_data2; + u8 ctrl1; + u8 ctrl23; + unsigned int delay_min = 0; + unsigned int delay_max = 0; + u8 data_low_addr, data_high_addr; + + if (!gpadc) + return -ENODEV; + + /* check if conversion is supported */ + if ((gpadc->irq_sw <= 0) && !ch->hardware_control) + return -ENOTSUPP; + if ((gpadc->irq_hw <= 0) && ch->hardware_control) + return -ENOTSUPP; + + /* Enable vddadc by grabbing PM runtime */ + pm_runtime_get_sync(gpadc->dev); + + /* Check if ADC is not busy, lock and proceed */ + do { + ret = abx500_get_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_STAT_REG, &val); + if (ret < 0) + goto out; + if (!(val & AB8500_GPADC_STAT_BUSY)) + break; + msleep(20); + } while (++looplimit < 10); + if (looplimit >= 10 && (val & AB8500_GPADC_STAT_BUSY)) { + dev_err(gpadc->dev, "gpadc_conversion: GPADC busy"); + ret = -EINVAL; + goto out; + } + + /* Enable GPADC */ + ctrl1 = AB8500_GPADC_CTRL1_ENABLE; + + /* Select the channel source and set average samples */ + switch (ch->avg_sample) { + case 1: + ctrl23 = ch->id | AB8500_GPADC_CTRL2_AVG_1; + break; + case 4: + ctrl23 = ch->id | AB8500_GPADC_CTRL2_AVG_4; + break; + case 8: + ctrl23 = ch->id | AB8500_GPADC_CTRL2_AVG_8; + break; + default: + ctrl23 = ch->id | AB8500_GPADC_CTRL2_AVG_16; + break; + } + + if (ch->hardware_control) { + ret = abx500_set_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_CTRL3_REG, ctrl23); + ctrl1 |= AB8500_GPADC_CTRL1_TRIG_ENA; + if (ch->falling_edge) + ctrl1 |= AB8500_GPADC_CTRL1_TRIG_EDGE; + } else { + ret = abx500_set_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_CTRL2_REG, ctrl23); + } + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc_conversion: set avg samples failed\n"); + goto out; + } + + /* + * Enable ADC, buffering, select rising edge and enable ADC path + * charging current sense if it needed, ABB 3.0 needs some special + * treatment too. + */ + switch (ch->id) { + case AB8500_GPADC_CHAN_MAIN_CHARGER_CURRENT: + case AB8500_GPADC_CHAN_USB_CHARGER_CURRENT: + ctrl1 |= AB8500_GPADC_CTRL1_BUF_ENA | + AB8500_GPADC_CTRL1_ICHAR_ENA; + break; + case AB8500_GPADC_CHAN_BAT_TEMP: + if (!is_ab8500_2p0_or_earlier(gpadc->ab8500)) { + ctrl1 |= AB8500_GPADC_CTRL1_BUF_ENA | + AB8500_GPADC_CTRL1_BTEMP_PULL_UP; + /* + * Delay might be needed for ABB8500 cut 3.0, if not, + * remove when hardware will be available + */ + delay_min = 1000; /* Delay in micro seconds */ + delay_max = 10000; /* large range optimises sleepmode */ + break; + } + fallthrough; + default: + ctrl1 |= AB8500_GPADC_CTRL1_BUF_ENA; + break; + } + + /* Write configuration to control register 1 */ + ret = abx500_set_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_CTRL1_REG, ctrl1); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc_conversion: set Control register failed\n"); + goto out; + } + + if (delay_min != 0) + usleep_range(delay_min, delay_max); + + if (ch->hardware_control) { + /* Set trigger delay timer */ + ret = abx500_set_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_AUTO_TIMER_REG, + ch->trig_timer); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc_conversion: trig timer failed\n"); + goto out; + } + completion_timeout = 2 * HZ; + data_low_addr = AB8500_GPADC_AUTODATAL_REG; + data_high_addr = AB8500_GPADC_AUTODATAH_REG; + } else { + /* Start SW conversion */ + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_CTRL1_REG, + AB8500_GPADC_CTRL1_START_SW_CONV, + AB8500_GPADC_CTRL1_START_SW_CONV); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc_conversion: start s/w conv failed\n"); + goto out; + } + completion_timeout = msecs_to_jiffies(AB8500_GPADC_CONVERSION_TIME); + data_low_addr = AB8500_GPADC_MANDATAL_REG; + data_high_addr = AB8500_GPADC_MANDATAH_REG; + } + + /* Wait for completion of conversion */ + if (!wait_for_completion_timeout(&gpadc->complete, + completion_timeout)) { + dev_err(gpadc->dev, + "timeout didn't receive GPADC conv interrupt\n"); + ret = -EINVAL; + goto out; + } + + /* Read the converted RAW data */ + ret = abx500_get_register_interruptible(gpadc->dev, + AB8500_GPADC, data_low_addr, &low_data); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc_conversion: read low data failed\n"); + goto out; + } + + ret = abx500_get_register_interruptible(gpadc->dev, + AB8500_GPADC, data_high_addr, &high_data); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc_conversion: read high data failed\n"); + goto out; + } + + /* Check if double conversion is required */ + if ((ch->id == AB8500_GPADC_CHAN_BAT_CTRL_AND_IBAT) || + (ch->id == AB8500_GPADC_CHAN_VBAT_MEAS_AND_IBAT) || + (ch->id == AB8500_GPADC_CHAN_VBAT_TRUE_MEAS_AND_IBAT) || + (ch->id == AB8500_GPADC_CHAN_BAT_TEMP_AND_IBAT)) { + + if (ch->hardware_control) { + /* not supported */ + ret = -ENOTSUPP; + dev_err(gpadc->dev, + "gpadc_conversion: only SW double conversion supported\n"); + goto out; + } else { + /* Read the converted RAW data 2 */ + ret = abx500_get_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8540_GPADC_MANDATA2L_REG, + &low_data2); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc_conversion: read sw low data 2 failed\n"); + goto out; + } + + ret = abx500_get_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8540_GPADC_MANDATA2H_REG, + &high_data2); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc_conversion: read sw high data 2 failed\n"); + goto out; + } + if (ibat != NULL) { + *ibat = (high_data2 << 8) | low_data2; + } else { + dev_warn(gpadc->dev, + "gpadc_conversion: ibat not stored\n"); + } + + } + } + + /* Disable GPADC */ + ret = abx500_set_register_interruptible(gpadc->dev, AB8500_GPADC, + AB8500_GPADC_CTRL1_REG, AB8500_GPADC_CTRL1_DISABLE); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc_conversion: disable gpadc failed\n"); + goto out; + } + + /* This eventually drops the regulator */ + pm_runtime_put_autosuspend(gpadc->dev); + + return (high_data << 8) | low_data; + +out: + /* + * It has shown to be needed to turn off the GPADC if an error occurs, + * otherwise we might have problem when waiting for the busy bit in the + * GPADC status register to go low. In V1.1 there wait_for_completion + * seems to timeout when waiting for an interrupt.. Not seen in V2.0 + */ + (void) abx500_set_register_interruptible(gpadc->dev, AB8500_GPADC, + AB8500_GPADC_CTRL1_REG, AB8500_GPADC_CTRL1_DISABLE); + pm_runtime_put(gpadc->dev); + dev_err(gpadc->dev, + "gpadc_conversion: Failed to AD convert channel %d\n", ch->id); + + return ret; +} + +/** + * ab8500_bm_gpadcconvend_handler() - isr for gpadc conversion completion + * @irq: irq number + * @data: pointer to the data passed during request irq + * + * This is a interrupt service routine for gpadc conversion completion. + * Notifies the gpadc completion is completed and the converted raw value + * can be read from the registers. + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_bm_gpadcconvend_handler(int irq, void *data) +{ + struct ab8500_gpadc *gpadc = data; + + complete(&gpadc->complete); + + return IRQ_HANDLED; +} + +static int otp_cal_regs[] = { + AB8500_GPADC_CAL_1, + AB8500_GPADC_CAL_2, + AB8500_GPADC_CAL_3, + AB8500_GPADC_CAL_4, + AB8500_GPADC_CAL_5, + AB8500_GPADC_CAL_6, + AB8500_GPADC_CAL_7, +}; + +static int otp4_cal_regs[] = { + AB8540_GPADC_OTP4_REG_7, + AB8540_GPADC_OTP4_REG_6, + AB8540_GPADC_OTP4_REG_5, +}; + +static void ab8500_gpadc_read_calibration_data(struct ab8500_gpadc *gpadc) +{ + int i; + int ret[ARRAY_SIZE(otp_cal_regs)]; + u8 gpadc_cal[ARRAY_SIZE(otp_cal_regs)]; + int ret_otp4[ARRAY_SIZE(otp4_cal_regs)]; + u8 gpadc_otp4[ARRAY_SIZE(otp4_cal_regs)]; + int vmain_high, vmain_low; + int btemp_high, btemp_low; + int vbat_high, vbat_low; + int ibat_high, ibat_low; + s64 V_gain, V_offset, V2A_gain, V2A_offset; + + /* First we read all OTP registers and store the error code */ + for (i = 0; i < ARRAY_SIZE(otp_cal_regs); i++) { + ret[i] = abx500_get_register_interruptible(gpadc->dev, + AB8500_OTP_EMUL, otp_cal_regs[i], &gpadc_cal[i]); + if (ret[i] < 0) { + /* Continue anyway: maybe the other registers are OK */ + dev_err(gpadc->dev, "%s: read otp reg 0x%02x failed\n", + __func__, otp_cal_regs[i]); + } else { + /* Put this in the entropy pool as device-unique */ + add_device_randomness(&ret[i], sizeof(ret[i])); + } + } + + /* + * The ADC calibration data is stored in OTP registers. + * The layout of the calibration data is outlined below and a more + * detailed description can be found in UM0836 + * + * vm_h/l = vmain_high/low + * bt_h/l = btemp_high/low + * vb_h/l = vbat_high/low + * + * Data bits 8500/9540: + * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | | vm_h9 | vm_h8 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | | vm_h7 | vm_h6 | vm_h5 | vm_h4 | vm_h3 | vm_h2 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | vm_h1 | vm_h0 | vm_l4 | vm_l3 | vm_l2 | vm_l1 | vm_l0 | bt_h9 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | bt_h8 | bt_h7 | bt_h6 | bt_h5 | bt_h4 | bt_h3 | bt_h2 | bt_h1 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | bt_h0 | bt_l4 | bt_l3 | bt_l2 | bt_l1 | bt_l0 | vb_h9 | vb_h8 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | vb_h7 | vb_h6 | vb_h5 | vb_h4 | vb_h3 | vb_h2 | vb_h1 | vb_h0 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | vb_l5 | vb_l4 | vb_l3 | vb_l2 | vb_l1 | vb_l0 | + * |.......|.......|.......|.......|.......|.......|.......|....... + * + * Data bits 8540: + * OTP2 + * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | + * |.......|.......|.......|.......|.......|.......|.......|....... + * | vm_h9 | vm_h8 | vm_h7 | vm_h6 | vm_h5 | vm_h4 | vm_h3 | vm_h2 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | vm_h1 | vm_h0 | vm_l4 | vm_l3 | vm_l2 | vm_l1 | vm_l0 | bt_h9 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | bt_h8 | bt_h7 | bt_h6 | bt_h5 | bt_h4 | bt_h3 | bt_h2 | bt_h1 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | bt_h0 | bt_l4 | bt_l3 | bt_l2 | bt_l1 | bt_l0 | vb_h9 | vb_h8 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | vb_h7 | vb_h6 | vb_h5 | vb_h4 | vb_h3 | vb_h2 | vb_h1 | vb_h0 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | vb_l5 | vb_l4 | vb_l3 | vb_l2 | vb_l1 | vb_l0 | + * |.......|.......|.......|.......|.......|.......|.......|....... + * + * Data bits 8540: + * OTP4 + * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | | ib_h9 | ib_h8 | ib_h7 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | ib_h6 | ib_h5 | ib_h4 | ib_h3 | ib_h2 | ib_h1 | ib_h0 | ib_l5 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | ib_l4 | ib_l3 | ib_l2 | ib_l1 | ib_l0 | + * + * + * Ideal output ADC codes corresponding to injected input voltages + * during manufacturing is: + * + * vmain_high: Vin = 19500mV / ADC ideal code = 997 + * vmain_low: Vin = 315mV / ADC ideal code = 16 + * btemp_high: Vin = 1300mV / ADC ideal code = 985 + * btemp_low: Vin = 21mV / ADC ideal code = 16 + * vbat_high: Vin = 4700mV / ADC ideal code = 982 + * vbat_low: Vin = 2380mV / ADC ideal code = 33 + */ + + if (is_ab8540(gpadc->ab8500)) { + /* Calculate gain and offset for VMAIN if all reads succeeded*/ + if (!(ret[1] < 0 || ret[2] < 0)) { + vmain_high = (((gpadc_cal[1] & 0xFF) << 2) | + ((gpadc_cal[2] & 0xC0) >> 6)); + vmain_low = ((gpadc_cal[2] & 0x3E) >> 1); + + gpadc->cal_data[AB8500_CAL_VMAIN].otp_calib_hi = + (u16)vmain_high; + gpadc->cal_data[AB8500_CAL_VMAIN].otp_calib_lo = + (u16)vmain_low; + + gpadc->cal_data[AB8500_CAL_VMAIN].gain = AB8500_GPADC_CALIB_SCALE * + (19500 - 315) / (vmain_high - vmain_low); + gpadc->cal_data[AB8500_CAL_VMAIN].offset = AB8500_GPADC_CALIB_SCALE * + 19500 - (AB8500_GPADC_CALIB_SCALE * (19500 - 315) / + (vmain_high - vmain_low)) * vmain_high; + } else { + gpadc->cal_data[AB8500_CAL_VMAIN].gain = 0; + } + + /* Read IBAT calibration Data */ + for (i = 0; i < ARRAY_SIZE(otp4_cal_regs); i++) { + ret_otp4[i] = abx500_get_register_interruptible( + gpadc->dev, AB8500_OTP_EMUL, + otp4_cal_regs[i], &gpadc_otp4[i]); + if (ret_otp4[i] < 0) + dev_err(gpadc->dev, + "%s: read otp4 reg 0x%02x failed\n", + __func__, otp4_cal_regs[i]); + } + + /* Calculate gain and offset for IBAT if all reads succeeded */ + if (!(ret_otp4[0] < 0 || ret_otp4[1] < 0 || ret_otp4[2] < 0)) { + ibat_high = (((gpadc_otp4[0] & 0x07) << 7) | + ((gpadc_otp4[1] & 0xFE) >> 1)); + ibat_low = (((gpadc_otp4[1] & 0x01) << 5) | + ((gpadc_otp4[2] & 0xF8) >> 3)); + + gpadc->cal_data[AB8500_CAL_IBAT].otp_calib_hi = + (u16)ibat_high; + gpadc->cal_data[AB8500_CAL_IBAT].otp_calib_lo = + (u16)ibat_low; + + V_gain = ((AB8500_GPADC_IBAT_VDROP_H - AB8500_GPADC_IBAT_VDROP_L) + << AB8500_GPADC_CALIB_SHIFT_IBAT) / (ibat_high - ibat_low); + + V_offset = (AB8500_GPADC_IBAT_VDROP_H << AB8500_GPADC_CALIB_SHIFT_IBAT) - + (((AB8500_GPADC_IBAT_VDROP_H - AB8500_GPADC_IBAT_VDROP_L) << + AB8500_GPADC_CALIB_SHIFT_IBAT) / (ibat_high - ibat_low)) + * ibat_high; + /* + * Result obtained is in mV (at a scale factor), + * we need to calculate gain and offset to get mA + */ + V2A_gain = (AB8500_ADC_CH_IBAT_MAX - AB8500_ADC_CH_IBAT_MIN)/ + (AB8500_ADC_CH_IBAT_MAX_V - AB8500_ADC_CH_IBAT_MIN_V); + V2A_offset = ((AB8500_ADC_CH_IBAT_MAX_V * AB8500_ADC_CH_IBAT_MIN - + AB8500_ADC_CH_IBAT_MAX * AB8500_ADC_CH_IBAT_MIN_V) + << AB8500_GPADC_CALIB_SHIFT_IBAT) + / (AB8500_ADC_CH_IBAT_MAX_V - AB8500_ADC_CH_IBAT_MIN_V); + + gpadc->cal_data[AB8500_CAL_IBAT].gain = + V_gain * V2A_gain; + gpadc->cal_data[AB8500_CAL_IBAT].offset = + V_offset * V2A_gain + V2A_offset; + } else { + gpadc->cal_data[AB8500_CAL_IBAT].gain = 0; + } + } else { + /* Calculate gain and offset for VMAIN if all reads succeeded */ + if (!(ret[0] < 0 || ret[1] < 0 || ret[2] < 0)) { + vmain_high = (((gpadc_cal[0] & 0x03) << 8) | + ((gpadc_cal[1] & 0x3F) << 2) | + ((gpadc_cal[2] & 0xC0) >> 6)); + vmain_low = ((gpadc_cal[2] & 0x3E) >> 1); + + gpadc->cal_data[AB8500_CAL_VMAIN].otp_calib_hi = + (u16)vmain_high; + gpadc->cal_data[AB8500_CAL_VMAIN].otp_calib_lo = + (u16)vmain_low; + + gpadc->cal_data[AB8500_CAL_VMAIN].gain = AB8500_GPADC_CALIB_SCALE * + (19500 - 315) / (vmain_high - vmain_low); + + gpadc->cal_data[AB8500_CAL_VMAIN].offset = AB8500_GPADC_CALIB_SCALE * + 19500 - (AB8500_GPADC_CALIB_SCALE * (19500 - 315) / + (vmain_high - vmain_low)) * vmain_high; + } else { + gpadc->cal_data[AB8500_CAL_VMAIN].gain = 0; + } + } + + /* Calculate gain and offset for BTEMP if all reads succeeded */ + if (!(ret[2] < 0 || ret[3] < 0 || ret[4] < 0)) { + btemp_high = (((gpadc_cal[2] & 0x01) << 9) | + (gpadc_cal[3] << 1) | ((gpadc_cal[4] & 0x80) >> 7)); + btemp_low = ((gpadc_cal[4] & 0x7C) >> 2); + + gpadc->cal_data[AB8500_CAL_BTEMP].otp_calib_hi = (u16)btemp_high; + gpadc->cal_data[AB8500_CAL_BTEMP].otp_calib_lo = (u16)btemp_low; + + gpadc->cal_data[AB8500_CAL_BTEMP].gain = + AB8500_GPADC_CALIB_SCALE * (1300 - 21) / (btemp_high - btemp_low); + gpadc->cal_data[AB8500_CAL_BTEMP].offset = AB8500_GPADC_CALIB_SCALE * 1300 - + (AB8500_GPADC_CALIB_SCALE * (1300 - 21) / (btemp_high - btemp_low)) + * btemp_high; + } else { + gpadc->cal_data[AB8500_CAL_BTEMP].gain = 0; + } + + /* Calculate gain and offset for VBAT if all reads succeeded */ + if (!(ret[4] < 0 || ret[5] < 0 || ret[6] < 0)) { + vbat_high = (((gpadc_cal[4] & 0x03) << 8) | gpadc_cal[5]); + vbat_low = ((gpadc_cal[6] & 0xFC) >> 2); + + gpadc->cal_data[AB8500_CAL_VBAT].otp_calib_hi = (u16)vbat_high; + gpadc->cal_data[AB8500_CAL_VBAT].otp_calib_lo = (u16)vbat_low; + + gpadc->cal_data[AB8500_CAL_VBAT].gain = AB8500_GPADC_CALIB_SCALE * + (4700 - 2380) / (vbat_high - vbat_low); + gpadc->cal_data[AB8500_CAL_VBAT].offset = AB8500_GPADC_CALIB_SCALE * 4700 - + (AB8500_GPADC_CALIB_SCALE * (4700 - 2380) / + (vbat_high - vbat_low)) * vbat_high; + } else { + gpadc->cal_data[AB8500_CAL_VBAT].gain = 0; + } +} + +static int ab8500_gpadc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ab8500_gpadc *gpadc = iio_priv(indio_dev); + const struct ab8500_gpadc_chan_info *ch; + int raw_val; + int processed; + + ch = ab8500_gpadc_get_channel(gpadc, chan->address); + if (!ch) { + dev_err(gpadc->dev, "no such channel %lu\n", + chan->address); + return -EINVAL; + } + + raw_val = ab8500_gpadc_read(gpadc, ch, NULL); + if (raw_val < 0) + return raw_val; + + if (mask == IIO_CHAN_INFO_RAW) { + *val = raw_val; + return IIO_VAL_INT; + } + + if (mask == IIO_CHAN_INFO_PROCESSED) { + processed = ab8500_gpadc_ad_to_voltage(gpadc, ch->id, raw_val); + if (processed < 0) + return processed; + + /* Return millivolt or milliamps or millicentigrades */ + *val = processed; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int ab8500_gpadc_fwnode_xlate(struct iio_dev *indio_dev, + const struct fwnode_reference_args *iiospec) +{ + int i; + + for (i = 0; i < indio_dev->num_channels; i++) + if (indio_dev->channels[i].channel == iiospec->args[0]) + return i; + + return -EINVAL; +} + +static const struct iio_info ab8500_gpadc_info = { + .fwnode_xlate = ab8500_gpadc_fwnode_xlate, + .read_raw = ab8500_gpadc_read_raw, +}; + +static int ab8500_gpadc_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ab8500_gpadc *gpadc = iio_priv(indio_dev); + + regulator_disable(gpadc->vddadc); + + return 0; +} + +static int ab8500_gpadc_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ab8500_gpadc *gpadc = iio_priv(indio_dev); + int ret; + + ret = regulator_enable(gpadc->vddadc); + if (ret) + dev_err(dev, "Failed to enable vddadc: %d\n", ret); + + return ret; +} + +/** + * ab8500_gpadc_parse_channel() - process devicetree channel configuration + * @dev: pointer to containing device + * @fwnode: fw node for the channel to configure + * @ch: channel info to fill in + * @iio_chan: IIO channel specification to fill in + * + * The devicetree will set up the channel for use with the specific device, + * and define usage for things like AUX GPADC inputs more precisely. + */ +static int ab8500_gpadc_parse_channel(struct device *dev, + struct fwnode_handle *fwnode, + struct ab8500_gpadc_chan_info *ch, + struct iio_chan_spec *iio_chan) +{ + const char *name = fwnode_get_name(fwnode); + u32 chan; + int ret; + + ret = fwnode_property_read_u32(fwnode, "reg", &chan); + if (ret) { + dev_err(dev, "invalid channel number %s\n", name); + return ret; + } + if (chan > AB8500_GPADC_CHAN_BAT_TEMP_AND_IBAT) { + dev_err(dev, "%s channel number out of range %d\n", name, chan); + return -EINVAL; + } + + iio_chan->channel = chan; + iio_chan->datasheet_name = name; + iio_chan->indexed = 1; + iio_chan->address = chan; + iio_chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_PROCESSED); + /* Most are voltages (also temperatures), some are currents */ + if ((chan == AB8500_GPADC_CHAN_MAIN_CHARGER_CURRENT) || + (chan == AB8500_GPADC_CHAN_USB_CHARGER_CURRENT)) + iio_chan->type = IIO_CURRENT; + else + iio_chan->type = IIO_VOLTAGE; + + ch->id = chan; + + /* Sensible defaults */ + ch->avg_sample = 16; + ch->hardware_control = false; + ch->falling_edge = false; + ch->trig_timer = 0; + + return 0; +} + +/** + * ab8500_gpadc_parse_channels() - Parse the GPADC channels from DT + * @gpadc: the GPADC to configure the channels for + * @chans_parsed: the IIO channels we parsed + * @nchans_parsed: the number of IIO channels we parsed + */ +static int ab8500_gpadc_parse_channels(struct ab8500_gpadc *gpadc, + struct iio_chan_spec **chans_parsed, + unsigned int *nchans_parsed) +{ + struct ab8500_gpadc_chan_info *ch; + struct iio_chan_spec *iio_chans; + unsigned int nchans; + int i; + + nchans = device_get_child_node_count(gpadc->dev); + if (!nchans) { + dev_err(gpadc->dev, "no channel children\n"); + return -ENODEV; + } + dev_info(gpadc->dev, "found %d ADC channels\n", nchans); + + iio_chans = devm_kcalloc(gpadc->dev, nchans, + sizeof(*iio_chans), GFP_KERNEL); + if (!iio_chans) + return -ENOMEM; + + gpadc->chans = devm_kcalloc(gpadc->dev, nchans, + sizeof(*gpadc->chans), GFP_KERNEL); + if (!gpadc->chans) + return -ENOMEM; + + i = 0; + device_for_each_child_node_scoped(gpadc->dev, child) { + struct iio_chan_spec *iio_chan; + int ret; + + ch = &gpadc->chans[i]; + iio_chan = &iio_chans[i]; + + ret = ab8500_gpadc_parse_channel(gpadc->dev, child, ch, + iio_chan); + if (ret) { + return ret; + } + i++; + } + gpadc->nchans = nchans; + *chans_parsed = iio_chans; + *nchans_parsed = nchans; + + return 0; +} + +static int ab8500_gpadc_probe(struct platform_device *pdev) +{ + struct ab8500_gpadc *gpadc; + struct iio_dev *indio_dev; + struct device *dev = &pdev->dev; + struct iio_chan_spec *iio_chans; + unsigned int n_iio_chans; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*gpadc)); + if (!indio_dev) + return -ENOMEM; + + platform_set_drvdata(pdev, indio_dev); + gpadc = iio_priv(indio_dev); + + gpadc->dev = dev; + gpadc->ab8500 = dev_get_drvdata(dev->parent); + + ret = ab8500_gpadc_parse_channels(gpadc, &iio_chans, &n_iio_chans); + if (ret) + return ret; + + gpadc->irq_sw = platform_get_irq_byname(pdev, "SW_CONV_END"); + if (gpadc->irq_sw < 0) + return gpadc->irq_sw; + + if (is_ab8500(gpadc->ab8500)) { + gpadc->irq_hw = platform_get_irq_byname(pdev, "HW_CONV_END"); + if (gpadc->irq_hw < 0) + return gpadc->irq_hw; + } else { + gpadc->irq_hw = 0; + } + + /* Initialize completion used to notify completion of conversion */ + init_completion(&gpadc->complete); + + /* Request interrupts */ + ret = devm_request_threaded_irq(dev, gpadc->irq_sw, NULL, + ab8500_bm_gpadcconvend_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT, + "ab8500-gpadc-sw", gpadc); + if (ret < 0) { + dev_err(dev, + "failed to request sw conversion irq %d\n", + gpadc->irq_sw); + return ret; + } + + if (gpadc->irq_hw) { + ret = devm_request_threaded_irq(dev, gpadc->irq_hw, NULL, + ab8500_bm_gpadcconvend_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT, + "ab8500-gpadc-hw", gpadc); + if (ret < 0) { + dev_err(dev, + "Failed to request hw conversion irq: %d\n", + gpadc->irq_hw); + return ret; + } + } + + /* The VTVout LDO used to power the AB8500 GPADC */ + gpadc->vddadc = devm_regulator_get(dev, "vddadc"); + if (IS_ERR(gpadc->vddadc)) + return dev_err_probe(dev, PTR_ERR(gpadc->vddadc), + "failed to get vddadc\n"); + + ret = regulator_enable(gpadc->vddadc); + if (ret) { + dev_err(dev, "failed to enable vddadc: %d\n", ret); + return ret; + } + + /* Enable runtime PM */ + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, AB8500_GPADC_AUTOSUSPEND_DELAY); + pm_runtime_use_autosuspend(dev); + + ab8500_gpadc_read_calibration_data(gpadc); + + pm_runtime_put(dev); + + indio_dev->name = "ab8500-gpadc"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ab8500_gpadc_info; + indio_dev->channels = iio_chans; + indio_dev->num_channels = n_iio_chans; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + goto out_dis_pm; + + return 0; + +out_dis_pm: + pm_runtime_get_sync(dev); + pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); + regulator_disable(gpadc->vddadc); + + return ret; +} + +static void ab8500_gpadc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct ab8500_gpadc *gpadc = iio_priv(indio_dev); + + pm_runtime_get_sync(gpadc->dev); + pm_runtime_put_noidle(gpadc->dev); + pm_runtime_disable(gpadc->dev); + regulator_disable(gpadc->vddadc); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(ab8500_gpadc_pm_ops, + ab8500_gpadc_runtime_suspend, + ab8500_gpadc_runtime_resume, NULL); + +static struct platform_driver ab8500_gpadc_driver = { + .probe = ab8500_gpadc_probe, + .remove = ab8500_gpadc_remove, + .driver = { + .name = "ab8500-gpadc", + .pm = pm_ptr(&ab8500_gpadc_pm_ops), + }, +}; +builtin_platform_driver(ab8500_gpadc_driver); diff --git a/drivers/iio/adc/ad4000.c b/drivers/iio/adc/ad4000.c new file mode 100644 index 000000000..a33896b2c --- /dev/null +++ b/drivers/iio/adc/ad4000.c @@ -0,0 +1,1263 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * AD4000 SPI ADC driver + * + * Copyright 2024 Analog Devices Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define AD4000_READ_COMMAND 0x54 +#define AD4000_WRITE_COMMAND 0x14 + +#define AD4000_CONFIG_REG_DEFAULT 0xE1 + +/* AD4000 Configuration Register programmable bits */ +#define AD4000_CFG_SPAN_COMP BIT(3) /* Input span compression */ +#define AD4000_CFG_HIGHZ BIT(2) /* High impedance mode */ +#define AD4000_CFG_TURBO BIT(1) /* Turbo mode */ + +#define AD4000_SCALE_OPTIONS 2 + +#define __AD4000_DIFF_CHANNEL(_sign, _real_bits, _storage_bits, _reg_access, _offl)\ +{ \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .differential = 1, \ + .channel = 0, \ + .channel2 = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + (_offl ? BIT(IIO_CHAN_INFO_SAMP_FREQ) : 0), \ + .info_mask_separate_available = _reg_access ? BIT(IIO_CHAN_INFO_SCALE) : 0,\ + .scan_index = 0, \ + .scan_type = { \ + .sign = _sign, \ + .realbits = _real_bits, \ + .storagebits = _storage_bits, \ + .shift = (_offl ? 0 : _storage_bits - _real_bits), \ + .endianness = _offl ? IIO_CPU : IIO_BE \ + }, \ +} + +#define AD4000_DIFF_CHANNEL(_sign, _real_bits, _reg_access, _offl) \ + __AD4000_DIFF_CHANNEL((_sign), (_real_bits), \ + (((_offl) || ((_real_bits) > 16)) ? 32 : 16), \ + (_reg_access), (_offl)) + +/* + * When SPI offload is configured, transfers are executed without CPU + * intervention so no soft timestamp can be recorded when transfers run. + * Because of that, the macros that set timestamp channel are only used when + * transfers are not offloaded. + */ +#define AD4000_DIFF_CHANNELS(_sign, _real_bits, _reg_access) \ +{ \ + AD4000_DIFF_CHANNEL(_sign, _real_bits, _reg_access, 0), \ + IIO_CHAN_SOFT_TIMESTAMP(1), \ +} + +#define __AD4000_PSEUDO_DIFF_CHANNEL(_sign, _real_bits, _storage_bits, \ + _reg_access, _offl) \ +{ \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + (_offl ? BIT(IIO_CHAN_INFO_SAMP_FREQ) : 0), \ + .info_mask_separate_available = _reg_access ? BIT(IIO_CHAN_INFO_SCALE) : 0,\ + .scan_index = 0, \ + .scan_type = { \ + .sign = _sign, \ + .realbits = _real_bits, \ + .storagebits = _storage_bits, \ + .shift = (_offl ? 0 : _storage_bits - _real_bits), \ + .endianness = _offl ? IIO_CPU : IIO_BE \ + }, \ +} + +#define AD4000_PSEUDO_DIFF_CHANNEL(_sign, _real_bits, _reg_access, _offl) \ + __AD4000_PSEUDO_DIFF_CHANNEL((_sign), (_real_bits), \ + (((_offl) || ((_real_bits) > 16)) ? 32 : 16),\ + (_reg_access), (_offl)) + +#define AD4000_PSEUDO_DIFF_CHANNELS(_sign, _real_bits, _reg_access) \ +{ \ + AD4000_PSEUDO_DIFF_CHANNEL(_sign, _real_bits, _reg_access, 0), \ + IIO_CHAN_SOFT_TIMESTAMP(1), \ +} + +static const char * const ad4000_power_supplies[] = { + "vdd", "vio" +}; + +enum ad4000_sdi { + AD4000_SDI_MOSI, + AD4000_SDI_VIO, + AD4000_SDI_CS, + AD4000_SDI_GND, +}; + +/* maps adi,sdi-pin property value to enum */ +static const char * const ad4000_sdi_pin[] = { + [AD4000_SDI_MOSI] = "sdi", + [AD4000_SDI_VIO] = "high", + [AD4000_SDI_CS] = "cs", + [AD4000_SDI_GND] = "low", +}; + +/* Gains stored as fractions of 1000 so they can be expressed by integers. */ +static const int ad4000_gains[] = { + 454, 909, 1000, 1900, +}; + +struct ad4000_time_spec { + int t_conv_ns; + int t_quiet2_ns; +}; + +/* + * Same timing specifications for all of AD4000, AD4001, ..., AD4008, AD4010, + * ADAQ4001, and ADAQ4003. + */ +static const struct ad4000_time_spec ad4000_t_spec = { + .t_conv_ns = 320, + .t_quiet2_ns = 60, +}; + +/* AD4020, AD4021, AD4022 */ +static const struct ad4000_time_spec ad4020_t_spec = { + .t_conv_ns = 350, + .t_quiet2_ns = 60, +}; + +/* AD7983, AD7984 */ +static const struct ad4000_time_spec ad7983_t_spec = { + .t_conv_ns = 500, + .t_quiet2_ns = 0, +}; + +/* AD7980, AD7982 */ +static const struct ad4000_time_spec ad7980_t_spec = { + .t_conv_ns = 800, + .t_quiet2_ns = 0, +}; + +/* AD7946, AD7686, AD7688, AD7988-5, AD7693 */ +static const struct ad4000_time_spec ad7686_t_spec = { + .t_conv_ns = 1600, + .t_quiet2_ns = 0, +}; + +/* AD7690 */ +static const struct ad4000_time_spec ad7690_t_spec = { + .t_conv_ns = 2100, + .t_quiet2_ns = 0, +}; + +/* AD7942, AD7685, AD7687 */ +static const struct ad4000_time_spec ad7687_t_spec = { + .t_conv_ns = 3200, + .t_quiet2_ns = 0, +}; + +/* AD7691 */ +static const struct ad4000_time_spec ad7691_t_spec = { + .t_conv_ns = 3700, + .t_quiet2_ns = 0, +}; + +/* AD7988-1 */ +static const struct ad4000_time_spec ad7988_1_t_spec = { + .t_conv_ns = 9500, + .t_quiet2_ns = 0, +}; + +struct ad4000_chip_info { + const char *dev_name; + struct iio_chan_spec chan_spec[2]; + struct iio_chan_spec reg_access_chan_spec[2]; + struct iio_chan_spec offload_chan_spec; + struct iio_chan_spec reg_access_offload_chan_spec; + const struct ad4000_time_spec *time_spec; + bool has_hardware_gain; + int max_rate_hz; +}; + +static const struct ad4000_chip_info ad4000_chip_info = { + .dev_name = "ad4000", + .chan_spec = AD4000_PSEUDO_DIFF_CHANNELS('u', 16, 0), + .reg_access_chan_spec = AD4000_PSEUDO_DIFF_CHANNELS('u', 16, 1), + .offload_chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 16, 0, 1), + .reg_access_offload_chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 16, 1, 1), + .time_spec = &ad4000_t_spec, + .max_rate_hz = 2 * MEGA, +}; + +static const struct ad4000_chip_info ad4001_chip_info = { + .dev_name = "ad4001", + .chan_spec = AD4000_DIFF_CHANNELS('s', 16, 0), + .reg_access_chan_spec = AD4000_DIFF_CHANNELS('s', 16, 1), + .offload_chan_spec = AD4000_DIFF_CHANNEL('s', 16, 0, 1), + .reg_access_offload_chan_spec = AD4000_DIFF_CHANNEL('s', 16, 1, 1), + .time_spec = &ad4000_t_spec, + .max_rate_hz = 2 * MEGA, +}; + +static const struct ad4000_chip_info ad4002_chip_info = { + .dev_name = "ad4002", + .chan_spec = AD4000_PSEUDO_DIFF_CHANNELS('u', 18, 0), + .reg_access_chan_spec = AD4000_PSEUDO_DIFF_CHANNELS('u', 18, 1), + .offload_chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 18, 0, 1), + .reg_access_offload_chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 18, 1, 1), + .time_spec = &ad4000_t_spec, + .max_rate_hz = 2 * MEGA, +}; + +static const struct ad4000_chip_info ad4003_chip_info = { + .dev_name = "ad4003", + .chan_spec = AD4000_DIFF_CHANNELS('s', 18, 0), + .reg_access_chan_spec = AD4000_DIFF_CHANNELS('s', 18, 1), + .offload_chan_spec = AD4000_DIFF_CHANNEL('s', 18, 0, 1), + .reg_access_offload_chan_spec = AD4000_DIFF_CHANNEL('s', 18, 1, 1), + .time_spec = &ad4000_t_spec, + .max_rate_hz = 2 * MEGA, +}; + +static const struct ad4000_chip_info ad4004_chip_info = { + .dev_name = "ad4004", + .chan_spec = AD4000_PSEUDO_DIFF_CHANNELS('u', 16, 0), + .reg_access_chan_spec = AD4000_PSEUDO_DIFF_CHANNELS('u', 16, 1), + .offload_chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 16, 0, 1), + .reg_access_offload_chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 16, 1, 1), + .time_spec = &ad4000_t_spec, + .max_rate_hz = 1 * MEGA, +}; + +static const struct ad4000_chip_info ad4005_chip_info = { + .dev_name = "ad4005", + .chan_spec = AD4000_DIFF_CHANNELS('s', 16, 0), + .reg_access_chan_spec = AD4000_DIFF_CHANNELS('s', 16, 1), + .offload_chan_spec = AD4000_DIFF_CHANNEL('s', 16, 0, 1), + .reg_access_offload_chan_spec = AD4000_DIFF_CHANNEL('s', 16, 1, 1), + .time_spec = &ad4000_t_spec, + .max_rate_hz = 1 * MEGA, +}; + +static const struct ad4000_chip_info ad4006_chip_info = { + .dev_name = "ad4006", + .chan_spec = AD4000_PSEUDO_DIFF_CHANNELS('u', 18, 0), + .reg_access_chan_spec = AD4000_PSEUDO_DIFF_CHANNELS('u', 18, 1), + .offload_chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 18, 0, 1), + .reg_access_offload_chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 18, 1, 1), + .time_spec = &ad4000_t_spec, + .max_rate_hz = 1 * MEGA, +}; + +static const struct ad4000_chip_info ad4007_chip_info = { + .dev_name = "ad4007", + .chan_spec = AD4000_DIFF_CHANNELS('s', 18, 0), + .reg_access_chan_spec = AD4000_DIFF_CHANNELS('s', 18, 1), + .offload_chan_spec = AD4000_DIFF_CHANNEL('s', 18, 0, 1), + .reg_access_offload_chan_spec = AD4000_DIFF_CHANNEL('s', 18, 1, 1), + .time_spec = &ad4000_t_spec, + .max_rate_hz = 1 * MEGA, +}; + +static const struct ad4000_chip_info ad4008_chip_info = { + .dev_name = "ad4008", + .chan_spec = AD4000_PSEUDO_DIFF_CHANNELS('u', 16, 0), + .reg_access_chan_spec = AD4000_PSEUDO_DIFF_CHANNELS('u', 16, 1), + .offload_chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 16, 0, 1), + .reg_access_offload_chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 16, 1, 1), + .time_spec = &ad4000_t_spec, + .max_rate_hz = 500 * KILO, +}; + +static const struct ad4000_chip_info ad4010_chip_info = { + .dev_name = "ad4010", + .chan_spec = AD4000_PSEUDO_DIFF_CHANNELS('u', 18, 0), + .reg_access_chan_spec = AD4000_PSEUDO_DIFF_CHANNELS('u', 18, 1), + .offload_chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 18, 0, 1), + .reg_access_offload_chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 18, 1, 1), + .time_spec = &ad4000_t_spec, + .max_rate_hz = 500 * KILO, +}; + +static const struct ad4000_chip_info ad4011_chip_info = { + .dev_name = "ad4011", + .chan_spec = AD4000_DIFF_CHANNELS('s', 18, 0), + .reg_access_chan_spec = AD4000_DIFF_CHANNELS('s', 18, 1), + .offload_chan_spec = AD4000_DIFF_CHANNEL('s', 18, 0, 1), + .reg_access_offload_chan_spec = AD4000_DIFF_CHANNEL('s', 18, 1, 1), + .time_spec = &ad4000_t_spec, + .max_rate_hz = 500 * KILO, +}; + +static const struct ad4000_chip_info ad4020_chip_info = { + .dev_name = "ad4020", + .chan_spec = AD4000_DIFF_CHANNELS('s', 20, 0), + .reg_access_chan_spec = AD4000_DIFF_CHANNELS('s', 20, 1), + .offload_chan_spec = AD4000_DIFF_CHANNEL('s', 20, 0, 1), + .reg_access_offload_chan_spec = AD4000_DIFF_CHANNEL('s', 20, 1, 1), + .time_spec = &ad4020_t_spec, + .max_rate_hz = 1800 * KILO, +}; + +static const struct ad4000_chip_info ad4021_chip_info = { + .dev_name = "ad4021", + .chan_spec = AD4000_DIFF_CHANNELS('s', 20, 0), + .reg_access_chan_spec = AD4000_DIFF_CHANNELS('s', 20, 1), + .offload_chan_spec = AD4000_DIFF_CHANNEL('s', 20, 0, 1), + .reg_access_offload_chan_spec = AD4000_DIFF_CHANNEL('s', 20, 1, 1), + .time_spec = &ad4020_t_spec, + .max_rate_hz = 1 * MEGA, +}; + +static const struct ad4000_chip_info ad4022_chip_info = { + .dev_name = "ad4022", + .chan_spec = AD4000_DIFF_CHANNELS('s', 20, 0), + .reg_access_chan_spec = AD4000_DIFF_CHANNELS('s', 20, 1), + .offload_chan_spec = AD4000_DIFF_CHANNEL('s', 20, 0, 1), + .reg_access_offload_chan_spec = AD4000_DIFF_CHANNEL('s', 20, 1, 1), + .time_spec = &ad4020_t_spec, + .max_rate_hz = 500 * KILO, +}; + +static const struct ad4000_chip_info adaq4001_chip_info = { + .dev_name = "adaq4001", + .chan_spec = AD4000_DIFF_CHANNELS('s', 16, 0), + .reg_access_chan_spec = AD4000_DIFF_CHANNELS('s', 16, 1), + .offload_chan_spec = AD4000_DIFF_CHANNEL('s', 16, 0, 1), + .reg_access_offload_chan_spec = AD4000_DIFF_CHANNEL('s', 16, 1, 1), + .time_spec = &ad4000_t_spec, + .has_hardware_gain = true, + .max_rate_hz = 2 * MEGA, +}; + +static const struct ad4000_chip_info adaq4003_chip_info = { + .dev_name = "adaq4003", + .chan_spec = AD4000_DIFF_CHANNELS('s', 18, 0), + .reg_access_chan_spec = AD4000_DIFF_CHANNELS('s', 18, 1), + .offload_chan_spec = AD4000_DIFF_CHANNEL('s', 18, 0, 1), + .reg_access_offload_chan_spec = AD4000_DIFF_CHANNEL('s', 18, 1, 1), + .time_spec = &ad4000_t_spec, + .has_hardware_gain = true, + .max_rate_hz = 2 * MEGA, +}; + +static const struct ad4000_chip_info ad7685_chip_info = { + .dev_name = "ad7685", + .chan_spec = AD4000_PSEUDO_DIFF_CHANNELS('u', 16, 0), + .offload_chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 16, 0, 1), + .time_spec = &ad7687_t_spec, + .max_rate_hz = 250 * KILO, +}; + +static const struct ad4000_chip_info ad7686_chip_info = { + .dev_name = "ad7686", + .chan_spec = AD4000_PSEUDO_DIFF_CHANNELS('u', 16, 0), + .offload_chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 16, 0, 1), + .time_spec = &ad7686_t_spec, + .max_rate_hz = 500 * KILO, +}; + +static const struct ad4000_chip_info ad7687_chip_info = { + .dev_name = "ad7687", + .chan_spec = AD4000_DIFF_CHANNELS('s', 16, 0), + .offload_chan_spec = AD4000_DIFF_CHANNEL('s', 16, 0, 1), + .time_spec = &ad7687_t_spec, + .max_rate_hz = 250 * KILO, +}; + +static const struct ad4000_chip_info ad7688_chip_info = { + .dev_name = "ad7688", + .chan_spec = AD4000_DIFF_CHANNELS('s', 16, 0), + .offload_chan_spec = AD4000_DIFF_CHANNEL('s', 16, 0, 1), + .time_spec = &ad7686_t_spec, + .max_rate_hz = 500 * KILO, +}; + +static const struct ad4000_chip_info ad7690_chip_info = { + .dev_name = "ad7690", + .chan_spec = AD4000_DIFF_CHANNELS('s', 18, 0), + .offload_chan_spec = AD4000_DIFF_CHANNEL('s', 18, 0, 1), + .time_spec = &ad7690_t_spec, + .max_rate_hz = 400 * KILO, +}; + +static const struct ad4000_chip_info ad7691_chip_info = { + .dev_name = "ad7691", + .chan_spec = AD4000_DIFF_CHANNELS('s', 18, 0), + .offload_chan_spec = AD4000_DIFF_CHANNEL('s', 18, 0, 1), + .time_spec = &ad7691_t_spec, + .max_rate_hz = 250 * KILO, +}; + +static const struct ad4000_chip_info ad7693_chip_info = { + .dev_name = "ad7693", + .chan_spec = AD4000_DIFF_CHANNELS('s', 16, 0), + .offload_chan_spec = AD4000_DIFF_CHANNEL('s', 16, 0, 1), + .time_spec = &ad7686_t_spec, + .max_rate_hz = 500 * KILO, +}; + +static const struct ad4000_chip_info ad7942_chip_info = { + .dev_name = "ad7942", + .chan_spec = AD4000_PSEUDO_DIFF_CHANNELS('u', 14, 0), + .offload_chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 14, 0, 1), + .time_spec = &ad7687_t_spec, + .max_rate_hz = 250 * KILO, +}; + +static const struct ad4000_chip_info ad7946_chip_info = { + .dev_name = "ad7946", + .chan_spec = AD4000_PSEUDO_DIFF_CHANNELS('u', 14, 0), + .offload_chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 14, 0, 1), + .time_spec = &ad7686_t_spec, + .max_rate_hz = 500 * KILO, +}; + +static const struct ad4000_chip_info ad7980_chip_info = { + .dev_name = "ad7980", + .chan_spec = AD4000_PSEUDO_DIFF_CHANNELS('u', 16, 0), + .offload_chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 16, 0, 1), + .time_spec = &ad7980_t_spec, + .max_rate_hz = 1 * MEGA, +}; + +static const struct ad4000_chip_info ad7982_chip_info = { + .dev_name = "ad7982", + .chan_spec = AD4000_DIFF_CHANNELS('s', 18, 0), + .offload_chan_spec = AD4000_DIFF_CHANNEL('s', 18, 0, 1), + .time_spec = &ad7980_t_spec, + .max_rate_hz = 1 * MEGA, +}; + +static const struct ad4000_chip_info ad7983_chip_info = { + .dev_name = "ad7983", + .chan_spec = AD4000_PSEUDO_DIFF_CHANNELS('u', 16, 0), + .offload_chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 16, 0, 1), + .time_spec = &ad7983_t_spec, + .max_rate_hz = 1 * MEGA + 333 * KILO + 333, +}; + +static const struct ad4000_chip_info ad7984_chip_info = { + .dev_name = "ad7984", + .chan_spec = AD4000_DIFF_CHANNELS('s', 18, 0), + .offload_chan_spec = AD4000_DIFF_CHANNEL('s', 18, 0, 1), + .time_spec = &ad7983_t_spec, + .max_rate_hz = 1 * MEGA + 333 * KILO + 333, +}; + +static const struct ad4000_chip_info ad7988_1_chip_info = { + .dev_name = "ad7988-1", + .chan_spec = AD4000_PSEUDO_DIFF_CHANNELS('u', 16, 0), + .offload_chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 16, 0, 1), + .time_spec = &ad7988_1_t_spec, + .max_rate_hz = 100 * KILO, +}; + +static const struct ad4000_chip_info ad7988_5_chip_info = { + .dev_name = "ad7988-5", + .chan_spec = AD4000_PSEUDO_DIFF_CHANNELS('u', 16, 0), + .offload_chan_spec = AD4000_PSEUDO_DIFF_CHANNEL('u', 16, 0, 1), + .time_spec = &ad7686_t_spec, + .max_rate_hz = 500 * KILO, +}; + +static const struct spi_offload_config ad4000_offload_config = { + .capability_flags = SPI_OFFLOAD_CAP_TRIGGER | + SPI_OFFLOAD_CAP_RX_STREAM_DMA, +}; + +struct ad4000_state { + struct spi_device *spi; + struct gpio_desc *cnv_gpio; + struct spi_transfer xfers[2]; + struct spi_message msg; + struct spi_transfer offload_xfer; + struct spi_message offload_msg; + struct spi_offload *offload; + struct spi_offload_trigger *offload_trigger; + bool using_offload; + unsigned long offload_trigger_hz; + int max_rate_hz; + struct mutex lock; /* Protect read modify write cycle */ + int vref_mv; + enum ad4000_sdi sdi_pin; + bool span_comp; + u16 gain_milli; + int scale_tbl[AD4000_SCALE_OPTIONS][2]; + const struct ad4000_time_spec *time_spec; + + /* + * DMA (thus cache coherency maintenance) requires the transfer buffers + * to live in their own cache lines. + */ + struct { + union { + __be16 sample_buf16_be; + __be32 sample_buf32_be; + u16 sample_buf16; + u32 sample_buf32; + } data; + aligned_s64 timestamp; + } scan __aligned(IIO_DMA_MINALIGN); + u8 tx_buf[2]; + u8 rx_buf[2]; +}; + +static void ad4000_fill_scale_tbl(struct ad4000_state *st, + struct iio_chan_spec const *chan) +{ + int val, tmp0, tmp1; + int scale_bits; + u64 tmp2; + + /* + * ADCs that output two's complement code have one less bit to express + * voltage magnitude. + */ + if (chan->scan_type.sign == 's') + scale_bits = chan->scan_type.realbits - 1; + else + scale_bits = chan->scan_type.realbits; + + /* + * The gain is stored as a fraction of 1000 and, as we need to + * divide vref_mv by the gain, we invert the gain/1000 fraction. + * Also multiply by an extra MILLI to preserve precision. + * Thus, we have MILLI * MILLI equals MICRO as fraction numerator. + */ + val = mult_frac(st->vref_mv, MICRO, st->gain_milli); + + /* Would multiply by NANO here but we multiplied by extra MILLI */ + tmp2 = (u64)val * MICRO >> scale_bits; + tmp0 = div_s64_rem(tmp2, NANO, &tmp1); + + /* Store scale for when span compression is disabled */ + st->scale_tbl[0][0] = tmp0; /* Integer part */ + st->scale_tbl[0][1] = abs(tmp1); /* Fractional part */ + + /* Store scale for when span compression is enabled */ + st->scale_tbl[1][0] = tmp0; + + /* The integer part is always zero so don't bother to divide it. */ + if (chan->differential) + st->scale_tbl[1][1] = DIV_ROUND_CLOSEST(abs(tmp1) * 4, 5); + else + st->scale_tbl[1][1] = DIV_ROUND_CLOSEST(abs(tmp1) * 9, 10); +} + +static int ad4000_write_reg(struct ad4000_state *st, uint8_t val) +{ + st->tx_buf[0] = AD4000_WRITE_COMMAND; + st->tx_buf[1] = val; + return spi_write(st->spi, st->tx_buf, ARRAY_SIZE(st->tx_buf)); +} + +static int ad4000_read_reg(struct ad4000_state *st, unsigned int *val) +{ + struct spi_transfer t = { + .tx_buf = st->tx_buf, + .rx_buf = st->rx_buf, + .len = 2, + }; + int ret; + + st->tx_buf[0] = AD4000_READ_COMMAND; + ret = spi_sync_transfer(st->spi, &t, 1); + if (ret < 0) + return ret; + + *val = st->rx_buf[1]; + return ret; +} + +static int ad4000_set_sampling_freq(struct ad4000_state *st, int freq) +{ + struct spi_offload_trigger_config config = { + .type = SPI_OFFLOAD_TRIGGER_PERIODIC, + .periodic = { + .frequency_hz = freq, + }, + }; + int ret; + + ret = spi_offload_trigger_validate(st->offload_trigger, &config); + if (ret) + return ret; + + st->offload_trigger_hz = config.periodic.frequency_hz; + + return 0; +} + +static int ad4000_convert_and_acquire(struct ad4000_state *st) +{ + int ret; + + /* + * In 4-wire mode, the CNV line is held high for the entire conversion + * and acquisition process. In other modes, the CNV GPIO is optional + * and, if provided, replaces controller CS. If CNV GPIO is not defined + * gpiod_set_value_cansleep() has no effect. + */ + gpiod_set_value_cansleep(st->cnv_gpio, 1); + ret = spi_sync(st->spi, &st->msg); + gpiod_set_value_cansleep(st->cnv_gpio, 0); + + return ret; +} + +static int ad4000_single_conversion(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val) +{ + struct ad4000_state *st = iio_priv(indio_dev); + u32 sample; + int ret; + + ret = ad4000_convert_and_acquire(st); + if (ret < 0) + return ret; + + if (chan->scan_type.endianness == IIO_BE) { + if (chan->scan_type.realbits > 16) + sample = be32_to_cpu(st->scan.data.sample_buf32_be); + else + sample = be16_to_cpu(st->scan.data.sample_buf16_be); + } else { + if (chan->scan_type.realbits > 16) + sample = st->scan.data.sample_buf32; + else + sample = st->scan.data.sample_buf16; + } + + sample >>= chan->scan_type.shift; + + if (chan->scan_type.sign == 's') + *val = sign_extend32(sample, chan->scan_type.realbits - 1); + else + *val = sample; + + return IIO_VAL_INT; +} + +static int ad4000_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long info) +{ + struct ad4000_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad4000_single_conversion(indio_dev, chan, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SCALE: + *val = st->scale_tbl[st->span_comp][0]; + *val2 = st->scale_tbl[st->span_comp][1]; + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + *val = 0; + if (st->span_comp) + *val = mult_frac(st->vref_mv, 1, 10); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = st->offload_trigger_hz; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad4000_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long info) +{ + struct ad4000_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SCALE: + *vals = (int *)st->scale_tbl; + *length = AD4000_SCALE_OPTIONS * 2; + *type = IIO_VAL_INT_PLUS_NANO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int ad4000_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT_PLUS_MICRO; + } +} + +static int __ad4000_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val2) +{ + struct ad4000_state *st = iio_priv(indio_dev); + unsigned int reg_val; + bool span_comp_en; + int ret; + + guard(mutex)(&st->lock); + + ret = ad4000_read_reg(st, ®_val); + if (ret < 0) + return ret; + + span_comp_en = val2 == st->scale_tbl[1][1]; + reg_val &= ~AD4000_CFG_SPAN_COMP; + reg_val |= FIELD_PREP(AD4000_CFG_SPAN_COMP, span_comp_en); + + ret = ad4000_write_reg(st, reg_val); + if (ret < 0) + return ret; + + st->span_comp = span_comp_en; + return 0; +} + +static int ad4000_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ad4000_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = __ad4000_write_raw(indio_dev, chan, val2); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + if (val < 1 || val > st->max_rate_hz) + return -EINVAL; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = ad4000_set_sampling_freq(st, val); + iio_device_release_direct(indio_dev); + return ret; + default: + return -EINVAL; + } +} + +static irqreturn_t ad4000_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad4000_state *st = iio_priv(indio_dev); + int ret; + + ret = ad4000_convert_and_acquire(st); + if (ret < 0) + goto err_out; + + iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan), + pf->timestamp); + +err_out: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static const struct iio_info ad4000_reg_access_info = { + .read_raw = &ad4000_read_raw, + .read_avail = &ad4000_read_avail, + .write_raw = &ad4000_write_raw, + .write_raw_get_fmt = &ad4000_write_raw_get_fmt, +}; + +static const struct iio_info ad4000_offload_info = { + .read_raw = &ad4000_read_raw, + .write_raw = &ad4000_write_raw, + .write_raw_get_fmt = &ad4000_write_raw_get_fmt, +}; + +static const struct iio_info ad4000_info = { + .read_raw = &ad4000_read_raw, +}; + +static int ad4000_offload_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad4000_state *st = iio_priv(indio_dev); + struct spi_offload_trigger_config config = { + .type = SPI_OFFLOAD_TRIGGER_PERIODIC, + .periodic = { + .frequency_hz = st->offload_trigger_hz, + }, + }; + + return spi_offload_trigger_enable(st->offload, st->offload_trigger, + &config); +} + +static int ad4000_offload_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad4000_state *st = iio_priv(indio_dev); + + spi_offload_trigger_disable(st->offload, st->offload_trigger); + + return 0; +} + +static const struct iio_buffer_setup_ops ad4000_offload_buffer_setup_ops = { + .postenable = &ad4000_offload_buffer_postenable, + .predisable = &ad4000_offload_buffer_predisable, +}; + +static int ad4000_spi_offload_setup(struct iio_dev *indio_dev, + struct ad4000_state *st) +{ + struct spi_device *spi = st->spi; + struct device *dev = &spi->dev; + struct dma_chan *rx_dma; + int ret; + + st->offload_trigger = devm_spi_offload_trigger_get(dev, st->offload, + SPI_OFFLOAD_TRIGGER_PERIODIC); + if (IS_ERR(st->offload_trigger)) + return dev_err_probe(dev, PTR_ERR(st->offload_trigger), + "Failed to get offload trigger\n"); + + ret = ad4000_set_sampling_freq(st, st->max_rate_hz); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set sampling frequency\n"); + + rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev, st->offload); + if (IS_ERR(rx_dma)) + return dev_err_probe(dev, PTR_ERR(rx_dma), + "Failed to get offload RX DMA\n"); + + ret = devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, rx_dma, + IIO_BUFFER_DIRECTION_IN); + if (ret) + return dev_err_probe(dev, ret, "Failed to setup DMA buffer\n"); + + return 0; +} + +/* + * This executes a data sample transfer when using SPI offloading. The device + * connections should be in "3-wire" mode, selected either when the adi,sdi-pin + * device tree property is absent or set to "high". Also, the ADC CNV pin must + * be connected to a SPI controller CS (it can't be connected to a GPIO). + * + * In order to achieve the maximum sample rate, we only do one transfer per + * SPI offload trigger. Because the ADC output has a one sample latency (delay) + * when the device is wired in "3-wire" mode and only one transfer per sample is + * being made in turbo mode, the first data sample is not valid because it + * contains the output of an earlier conversion result. We also set transfer + * `bits_per_word` to achieve higher throughput by using the minimum number of + * SCLK cycles. Also, a delay is added to make sure we meet the minimum quiet + * time before releasing the CS line. + * + * Note that, with `bits_per_word` set to the number of ADC precision bits, + * transfers use larger word sizes that get stored in 'in-memory wordsizes' that + * are always in native CPU byte order. Because of that, IIO buffer elements + * ought to be read in CPU endianness which requires setting IIO scan_type + * endianness accordingly (i.e. IIO_CPU). + */ +static int ad4000_prepare_offload_message(struct ad4000_state *st, + const struct iio_chan_spec *chan) +{ + struct spi_transfer *xfer = &st->offload_xfer; + + xfer->bits_per_word = chan->scan_type.realbits; + xfer->len = chan->scan_type.realbits > 16 ? 4 : 2; + xfer->delay.value = st->time_spec->t_quiet2_ns; + xfer->delay.unit = SPI_DELAY_UNIT_NSECS; + xfer->offload_flags = SPI_OFFLOAD_XFER_RX_STREAM; + + spi_message_init_with_transfers(&st->offload_msg, xfer, 1); + st->offload_msg.offload = st->offload; + + return devm_spi_optimize_message(&st->spi->dev, st->spi, &st->offload_msg); +} + +/* + * This executes a data sample transfer for when the device connections are + * in "3-wire" mode, selected when the adi,sdi-pin device tree property is + * absent or set to "high". In this connection mode, the ADC SDI pin is + * connected to MOSI or to VIO and ADC CNV pin is connected either to a SPI + * controller CS or to a GPIO. + * AD4000 series of devices initiate conversions on the rising edge of CNV pin. + * + * If the CNV pin is connected to an SPI controller CS line (which is by default + * active low), the ADC readings would have a latency (delay) of one read. + * Moreover, since we also do ADC sampling for filling the buffer on triggered + * buffer mode, the timestamps of buffer readings would be disarranged. + * To prevent the read latency and reduce the time discrepancy between the + * sample read request and the time of actual sampling by the ADC, do a + * preparatory transfer to pulse the CS/CNV line. + */ +static int ad4000_prepare_3wire_mode_message(struct ad4000_state *st, + const struct iio_chan_spec *chan) +{ + struct spi_transfer *xfers = st->xfers; + + xfers[0].cs_change = 1; + xfers[0].cs_change_delay.value = st->time_spec->t_conv_ns; + xfers[0].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS; + + xfers[1].rx_buf = &st->scan.data; + xfers[1].len = chan->scan_type.realbits > 16 ? 4 : 2; + + /* + * If the device is set up for SPI offloading, IIO channel scan_type is + * set to IIO_CPU. When that is the case, use larger SPI word sizes for + * single-shot reads too. Thus, sample data can be correctly handled in + * ad4000_single_conversion() according to scan_type endianness. + */ + if (chan->scan_type.endianness != IIO_BE) + xfers[1].bits_per_word = chan->scan_type.realbits; + xfers[1].delay.value = st->time_spec->t_quiet2_ns; + xfers[1].delay.unit = SPI_DELAY_UNIT_NSECS; + + spi_message_init_with_transfers(&st->msg, st->xfers, 2); + + return devm_spi_optimize_message(&st->spi->dev, st->spi, &st->msg); +} + +/* + * This executes a data sample transfer for when the device connections are + * in "4-wire" mode, selected when the adi,sdi-pin device tree property is + * set to "cs". In this connection mode, the controller CS pin is connected to + * ADC SDI pin and a GPIO is connected to ADC CNV pin. + * The GPIO connected to ADC CNV pin is set outside of the SPI transfer. + */ +static int ad4000_prepare_4wire_mode_message(struct ad4000_state *st, + const struct iio_chan_spec *chan) +{ + struct spi_transfer *xfers = st->xfers; + + /* + * Dummy transfer to cause enough delay between CNV going high and SDI + * going low. + */ + xfers[0].cs_off = 1; + xfers[0].delay.value = st->time_spec->t_conv_ns; + xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS; + + xfers[1].rx_buf = &st->scan.data; + xfers[1].len = BITS_TO_BYTES(chan->scan_type.storagebits); + + spi_message_init_with_transfers(&st->msg, st->xfers, 2); + + return devm_spi_optimize_message(&st->spi->dev, st->spi, &st->msg); +} + +static int ad4000_config(struct ad4000_state *st) +{ + unsigned int reg_val = AD4000_CONFIG_REG_DEFAULT; + + if (device_property_present(&st->spi->dev, "adi,high-z-input")) + reg_val |= FIELD_PREP(AD4000_CFG_HIGHZ, 1); + + if (st->using_offload) + reg_val |= FIELD_PREP(AD4000_CFG_TURBO, 1); + + return ad4000_write_reg(st, reg_val); +} + +static int ad4000_probe(struct spi_device *spi) +{ + const struct ad4000_chip_info *chip; + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ad4000_state *st; + int gain_idx, ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + chip = spi_get_device_match_data(spi); + if (!chip) + return -EINVAL; + + st = iio_priv(indio_dev); + st->spi = spi; + st->time_spec = chip->time_spec; + st->max_rate_hz = chip->max_rate_hz; + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(ad4000_power_supplies), + ad4000_power_supplies); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable power supplies\n"); + + ret = devm_regulator_get_enable_read_voltage(dev, "ref"); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to get ref regulator reference\n"); + st->vref_mv = ret / 1000; + + st->cnv_gpio = devm_gpiod_get_optional(dev, "cnv", GPIOD_OUT_HIGH); + if (IS_ERR(st->cnv_gpio)) + return dev_err_probe(dev, PTR_ERR(st->cnv_gpio), + "Failed to get CNV GPIO"); + + st->offload = devm_spi_offload_get(dev, spi, &ad4000_offload_config); + ret = PTR_ERR_OR_ZERO(st->offload); + if (ret && ret != -ENODEV) + return dev_err_probe(dev, ret, "Failed to get offload\n"); + + st->using_offload = !IS_ERR(st->offload); + if (st->using_offload) { + indio_dev->setup_ops = &ad4000_offload_buffer_setup_ops; + ret = ad4000_spi_offload_setup(indio_dev, st); + if (ret) + return ret; + } else { + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + &iio_pollfunc_store_time, + &ad4000_trigger_handler, + NULL); + if (ret) + return ret; + } + + ret = device_property_match_property_string(dev, "adi,sdi-pin", + ad4000_sdi_pin, + ARRAY_SIZE(ad4000_sdi_pin)); + if (ret < 0 && ret != -EINVAL) + return dev_err_probe(dev, ret, + "getting adi,sdi-pin property failed\n"); + + /* Default to usual SPI connections if pin properties are not present */ + st->sdi_pin = ret == -EINVAL ? AD4000_SDI_MOSI : ret; + switch (st->sdi_pin) { + case AD4000_SDI_MOSI: + indio_dev->info = &ad4000_reg_access_info; + + /* + * In "3-wire mode", the ADC SDI line must be kept high when + * data is not being clocked out of the controller. + * Request the SPI controller to make MOSI idle high. + */ + spi->mode |= SPI_MOSI_IDLE_HIGH; + ret = spi_setup(spi); + if (ret < 0) + return ret; + + if (st->using_offload) { + indio_dev->channels = &chip->reg_access_offload_chan_spec; + indio_dev->num_channels = 1; + ret = ad4000_prepare_offload_message(st, indio_dev->channels); + if (ret) + return dev_err_probe(dev, ret, + "Failed to optimize SPI msg\n"); + } else { + indio_dev->channels = chip->reg_access_chan_spec; + indio_dev->num_channels = ARRAY_SIZE(chip->reg_access_chan_spec); + } + + /* + * Call ad4000_prepare_3wire_mode_message() so single-shot read + * SPI messages are always initialized. + */ + ret = ad4000_prepare_3wire_mode_message(st, &indio_dev->channels[0]); + if (ret) + return dev_err_probe(dev, ret, + "Failed to optimize SPI msg\n"); + + ret = ad4000_config(st); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to config device\n"); + + break; + case AD4000_SDI_VIO: + if (st->using_offload) { + indio_dev->info = &ad4000_offload_info; + indio_dev->channels = &chip->offload_chan_spec; + indio_dev->num_channels = 1; + + ret = ad4000_prepare_offload_message(st, indio_dev->channels); + if (ret) + return dev_err_probe(dev, ret, + "Failed to optimize SPI msg\n"); + } else { + indio_dev->info = &ad4000_info; + indio_dev->channels = chip->chan_spec; + indio_dev->num_channels = ARRAY_SIZE(chip->chan_spec); + } + + ret = ad4000_prepare_3wire_mode_message(st, &indio_dev->channels[0]); + if (ret) + return dev_err_probe(dev, ret, + "Failed to optimize SPI msg\n"); + + break; + case AD4000_SDI_CS: + if (st->using_offload) + return dev_err_probe(dev, -EPROTONOSUPPORT, + "Unsupported sdi-pin + offload config\n"); + indio_dev->info = &ad4000_info; + indio_dev->channels = chip->chan_spec; + indio_dev->num_channels = ARRAY_SIZE(chip->chan_spec); + ret = ad4000_prepare_4wire_mode_message(st, &indio_dev->channels[0]); + if (ret) + return dev_err_probe(dev, ret, + "Failed to optimize SPI msg\n"); + + break; + case AD4000_SDI_GND: + return dev_err_probe(dev, -EPROTONOSUPPORT, + "Unsupported connection mode\n"); + + default: + return dev_err_probe(dev, -EINVAL, "Unrecognized connection mode\n"); + } + + indio_dev->name = chip->dev_name; + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + st->gain_milli = 1000; + if (chip->has_hardware_gain) { + ret = device_property_read_u16(dev, "adi,gain-milli", + &st->gain_milli); + if (!ret) { + /* Match gain value from dt to one of supported gains */ + gain_idx = find_closest(st->gain_milli, ad4000_gains, + ARRAY_SIZE(ad4000_gains)); + st->gain_milli = ad4000_gains[gain_idx]; + } else { + return dev_err_probe(dev, ret, + "Failed to read gain property\n"); + } + } + + ad4000_fill_scale_tbl(st, &indio_dev->channels[0]); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id ad4000_id[] = { + { .name = "ad4000", .driver_data = (kernel_ulong_t)&ad4000_chip_info }, + { .name = "ad4001", .driver_data = (kernel_ulong_t)&ad4001_chip_info }, + { .name = "ad4002", .driver_data = (kernel_ulong_t)&ad4002_chip_info }, + { .name = "ad4003", .driver_data = (kernel_ulong_t)&ad4003_chip_info }, + { .name = "ad4004", .driver_data = (kernel_ulong_t)&ad4004_chip_info }, + { .name = "ad4005", .driver_data = (kernel_ulong_t)&ad4005_chip_info }, + { .name = "ad4006", .driver_data = (kernel_ulong_t)&ad4006_chip_info }, + { .name = "ad4007", .driver_data = (kernel_ulong_t)&ad4007_chip_info }, + { .name = "ad4008", .driver_data = (kernel_ulong_t)&ad4008_chip_info }, + { .name = "ad4010", .driver_data = (kernel_ulong_t)&ad4010_chip_info }, + { .name = "ad4011", .driver_data = (kernel_ulong_t)&ad4011_chip_info }, + { .name = "ad4020", .driver_data = (kernel_ulong_t)&ad4020_chip_info }, + { .name = "ad4021", .driver_data = (kernel_ulong_t)&ad4021_chip_info }, + { .name = "ad4022", .driver_data = (kernel_ulong_t)&ad4022_chip_info }, + { .name = "adaq4001", .driver_data = (kernel_ulong_t)&adaq4001_chip_info }, + { .name = "adaq4003", .driver_data = (kernel_ulong_t)&adaq4003_chip_info }, + { .name = "ad7685", .driver_data = (kernel_ulong_t)&ad7685_chip_info }, + { .name = "ad7686", .driver_data = (kernel_ulong_t)&ad7686_chip_info }, + { .name = "ad7687", .driver_data = (kernel_ulong_t)&ad7687_chip_info }, + { .name = "ad7688", .driver_data = (kernel_ulong_t)&ad7688_chip_info }, + { .name = "ad7690", .driver_data = (kernel_ulong_t)&ad7690_chip_info }, + { .name = "ad7691", .driver_data = (kernel_ulong_t)&ad7691_chip_info }, + { .name = "ad7693", .driver_data = (kernel_ulong_t)&ad7693_chip_info }, + { .name = "ad7942", .driver_data = (kernel_ulong_t)&ad7942_chip_info }, + { .name = "ad7946", .driver_data = (kernel_ulong_t)&ad7946_chip_info }, + { .name = "ad7980", .driver_data = (kernel_ulong_t)&ad7980_chip_info }, + { .name = "ad7982", .driver_data = (kernel_ulong_t)&ad7982_chip_info }, + { .name = "ad7983", .driver_data = (kernel_ulong_t)&ad7983_chip_info }, + { .name = "ad7984", .driver_data = (kernel_ulong_t)&ad7984_chip_info }, + { .name = "ad7988-1", .driver_data = (kernel_ulong_t)&ad7988_1_chip_info }, + { .name = "ad7988-5", .driver_data = (kernel_ulong_t)&ad7988_5_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad4000_id); + +static const struct of_device_id ad4000_of_match[] = { + { .compatible = "adi,ad4000", .data = &ad4000_chip_info }, + { .compatible = "adi,ad4001", .data = &ad4001_chip_info }, + { .compatible = "adi,ad4002", .data = &ad4002_chip_info }, + { .compatible = "adi,ad4003", .data = &ad4003_chip_info }, + { .compatible = "adi,ad4004", .data = &ad4004_chip_info }, + { .compatible = "adi,ad4005", .data = &ad4005_chip_info }, + { .compatible = "adi,ad4006", .data = &ad4006_chip_info }, + { .compatible = "adi,ad4007", .data = &ad4007_chip_info }, + { .compatible = "adi,ad4008", .data = &ad4008_chip_info }, + { .compatible = "adi,ad4010", .data = &ad4010_chip_info }, + { .compatible = "adi,ad4011", .data = &ad4011_chip_info }, + { .compatible = "adi,ad4020", .data = &ad4020_chip_info }, + { .compatible = "adi,ad4021", .data = &ad4021_chip_info }, + { .compatible = "adi,ad4022", .data = &ad4022_chip_info }, + { .compatible = "adi,adaq4001", .data = &adaq4001_chip_info }, + { .compatible = "adi,adaq4003", .data = &adaq4003_chip_info }, + { .compatible = "adi,ad7685", .data = &ad7685_chip_info }, + { .compatible = "adi,ad7686", .data = &ad7686_chip_info }, + { .compatible = "adi,ad7687", .data = &ad7687_chip_info }, + { .compatible = "adi,ad7688", .data = &ad7688_chip_info }, + { .compatible = "adi,ad7690", .data = &ad7690_chip_info }, + { .compatible = "adi,ad7691", .data = &ad7691_chip_info }, + { .compatible = "adi,ad7693", .data = &ad7693_chip_info }, + { .compatible = "adi,ad7942", .data = &ad7942_chip_info }, + { .compatible = "adi,ad7946", .data = &ad7946_chip_info }, + { .compatible = "adi,ad7980", .data = &ad7980_chip_info }, + { .compatible = "adi,ad7982", .data = &ad7982_chip_info }, + { .compatible = "adi,ad7983", .data = &ad7983_chip_info }, + { .compatible = "adi,ad7984", .data = &ad7984_chip_info }, + { .compatible = "adi,ad7988-1", .data = &ad7988_1_chip_info }, + { .compatible = "adi,ad7988-5", .data = &ad7988_5_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad4000_of_match); + +static struct spi_driver ad4000_driver = { + .driver = { + .name = "ad4000", + .of_match_table = ad4000_of_match, + }, + .probe = ad4000_probe, + .id_table = ad4000_id, +}; +module_spi_driver(ad4000_driver); + +MODULE_AUTHOR("Marcelo Schmitt "); +MODULE_DESCRIPTION("Analog Devices AD4000 ADC driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER"); diff --git a/drivers/iio/adc/ad4030.c b/drivers/iio/adc/ad4030.c new file mode 100644 index 000000000..e97400a1a --- /dev/null +++ b/drivers/iio/adc/ad4030.c @@ -0,0 +1,1802 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices AD4030 and AD4630 ADC family driver. + * + * Copyright 2024 Analog Devices, Inc. + * Copyright 2024 BayLibre, SAS + * + * based on code from: + * Analog Devices, Inc. + * Sergiu Cuciurean + * Nuno Sa + * Marcelo Schmitt + * Liviu Adace + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define AD4030_REG_INTERFACE_CONFIG_A 0x00 +#define AD4030_REG_INTERFACE_CONFIG_A_SW_RESET (BIT(0) | BIT(7)) +#define AD4030_REG_INTERFACE_CONFIG_B 0x01 +#define AD4030_REG_DEVICE_CONFIG 0x02 +#define AD4030_REG_CHIP_TYPE 0x03 +#define AD4030_REG_PRODUCT_ID_L 0x04 +#define AD4030_REG_PRODUCT_ID_H 0x05 +#define AD4030_REG_CHIP_GRADE 0x06 +#define AD4030_REG_CHIP_GRADE_AD4030_24_GRADE 0x10 +#define AD4030_REG_CHIP_GRADE_AD4630_16_GRADE 0x03 +#define AD4030_REG_CHIP_GRADE_AD4630_24_GRADE 0x00 +#define AD4030_REG_CHIP_GRADE_AD4632_16_GRADE 0x05 +#define AD4030_REG_CHIP_GRADE_AD4632_24_GRADE 0x02 +#define AD4030_REG_CHIP_GRADE_ADAQ4216_GRADE 0x1E +#define AD4030_REG_CHIP_GRADE_ADAQ4224_GRADE 0x1C +#define AD4030_REG_CHIP_GRADE_MASK_CHIP_GRADE GENMASK(7, 3) +#define AD4030_REG_SCRATCH_PAD 0x0A +#define AD4030_REG_SPI_REVISION 0x0B +#define AD4030_REG_VENDOR_L 0x0C +#define AD4030_REG_VENDOR_H 0x0D +#define AD4030_REG_STREAM_MODE 0x0E +#define AD4030_REG_INTERFACE_CONFIG_C 0x10 +#define AD4030_REG_INTERFACE_STATUS_A 0x11 +#define AD4030_REG_EXIT_CFG_MODE 0x14 +#define AD4030_REG_EXIT_CFG_MODE_EXIT_MSK BIT(0) +#define AD4030_REG_AVG 0x15 +#define AD4030_REG_AVG_MASK_AVG_SYNC BIT(7) +#define AD4030_REG_AVG_MASK_AVG_VAL GENMASK(4, 0) +#define AD4030_REG_OFFSET_X0_0 0x16 +#define AD4030_REG_OFFSET_X0_1 0x17 +#define AD4030_REG_OFFSET_X0_2 0x18 +#define AD4030_REG_OFFSET_X1_0 0x19 +#define AD4030_REG_OFFSET_X1_1 0x1A +#define AD4030_REG_OFFSET_X1_2 0x1B +#define AD4030_REG_OFFSET_BYTES_NB 3 +#define AD4030_REG_OFFSET_CHAN(ch) \ + (AD4030_REG_OFFSET_X0_2 + (AD4030_REG_OFFSET_BYTES_NB * (ch))) +#define AD4030_REG_GAIN_X0_LSB 0x1C +#define AD4030_REG_GAIN_X0_MSB 0x1D +#define AD4030_REG_GAIN_X1_LSB 0x1E +#define AD4030_REG_GAIN_X1_MSB 0x1F +#define AD4030_REG_GAIN_MAX_GAIN 1999970 +#define AD4030_REG_GAIN_BYTES_NB 2 +#define AD4030_REG_GAIN_CHAN(ch) \ + (AD4030_REG_GAIN_X0_MSB + (AD4030_REG_GAIN_BYTES_NB * (ch))) +#define AD4030_REG_MODES 0x20 +#define AD4030_REG_MODES_MASK_OUT_DATA_MODE GENMASK(2, 0) +#define AD4030_REG_MODES_MASK_LANE_MODE GENMASK(7, 6) +#define AD4030_REG_OSCILATOR 0x21 +#define AD4030_REG_IO 0x22 +#define AD4030_REG_IO_MASK_IO2X BIT(1) +#define AD4030_REG_PAT0 0x23 +#define AD4030_REG_PAT1 0x24 +#define AD4030_REG_PAT2 0x25 +#define AD4030_REG_PAT3 0x26 +#define AD4030_REG_DIG_DIAG 0x34 +#define AD4030_REG_DIG_ERR 0x35 + +/* Sequence starting with "1 0 1" to enable reg access */ +#define AD4030_REG_ACCESS 0xA0 + +#define AD4030_MAX_IIO_SAMPLE_SIZE_BUFFERED BITS_TO_BYTES(64) +#define AD4030_MAX_HARDWARE_CHANNEL_NB 2 +#define AD4030_MAX_IIO_CHANNEL_NB 5 +#define AD4030_SINGLE_COMMON_BYTE_CHANNELS_MASK 0b10 +#define AD4030_DUAL_COMMON_BYTE_CHANNELS_MASK 0b1100 +#define AD4030_GAIN_MIDLE_POINT 0x8000 +/* + * This accounts for 1 sample per channel plus one s64 for the timestamp, + * aligned on a s64 boundary + */ +#define AD4030_MAXIMUM_RX_BUFFER_SIZE \ + (ALIGN(AD4030_MAX_IIO_SAMPLE_SIZE_BUFFERED * \ + AD4030_MAX_HARDWARE_CHANNEL_NB, \ + sizeof(s64)) + sizeof(s64)) + +#define AD4030_VREF_MIN_UV (4096 * MILLI) +#define AD4030_VREF_MAX_UV (5000 * MILLI) +#define AD4030_VIO_THRESHOLD_UV (1400 * MILLI) +#define AD4030_SPI_MAX_XFER_LEN 8 +#define AD4030_SPI_MAX_REG_XFER_SPEED (80 * MEGA) +#define AD4030_TCNVH_NS 10 +#define AD4030_TCNVL_NS 20 +#define AD4030_TCYC_NS 500 +#define AD4030_TCYC_ADJUSTED_NS (AD4030_TCYC_NS - AD4030_TCNVL_NS) +#define AD4030_TRESET_PW_NS 50 +#define AD4632_TCYC_NS 2000 +#define AD4632_TCYC_ADJUSTED_NS (AD4632_TCYC_NS - AD4030_TCNVL_NS) +#define AD4030_TRESET_COM_DELAY_MS 750 +/* Datasheet says 9.8ns, so use the closest integer value */ +#define AD4030_TQUIET_CNV_DELAY_NS 10 + +/* HARDWARE_GAIN */ +#define ADAQ4616_PGA_PINS 2 +#define ADAQ4616_PGA_GAIN_MAX_NANO (NANO * 2 / 3) + +enum ad4030_out_mode { + AD4030_OUT_DATA_MD_DIFF, + AD4030_OUT_DATA_MD_16_DIFF_8_COM, + AD4030_OUT_DATA_MD_24_DIFF_8_COM, + AD4030_OUT_DATA_MD_30_AVERAGED_DIFF, + AD4030_OUT_DATA_MD_32_PATTERN, +}; + +enum { + AD4030_LANE_MD_1_PER_CH, + AD4030_LANE_MD_2_PER_CH, + AD4030_LANE_MD_4_PER_CH, + AD4030_LANE_MD_INTERLEAVED, +}; + +enum { + AD4030_SCAN_TYPE_NORMAL, + AD4030_SCAN_TYPE_AVG, +}; + +/* + * Gains computed as fractions of 1000 so they can be expressed by integers. + */ +static const int adaq4216_hw_gains_vpv[] = { + 1 * MILLI / 3, /* 0.333 */ + 5 * MILLI / 9, /* 0.555 */ + 20 * MILLI / 9, /* 0.2222 */ + 20 * MILLI / 3, /* 0.6666 */ +}; + +static const int adaq4216_hw_gains_frac[][2] = { + { 1, 3 }, /* 1/3 V/V gain */ + { 5, 9 }, /* 5/9 V/V gain */ + { 20, 9 }, /* 20/9 V/V gain */ + { 20, 3 }, /* 20/3 V/V gain */ +}; + +struct ad4030_chip_info { + const char *name; + const unsigned long *available_masks; + const struct iio_chan_spec channels[AD4030_MAX_IIO_CHANNEL_NB]; + const struct iio_chan_spec offload_channels[AD4030_MAX_IIO_CHANNEL_NB]; + u8 grade; + u8 precision_bits; + bool has_pga; + /* Number of hardware channels */ + int num_voltage_inputs; + unsigned int tcyc_ns; + unsigned int max_sample_rate_hz; +}; + +struct ad4030_state { + struct spi_device *spi; + struct regmap *regmap; + const struct ad4030_chip_info *chip; + struct gpio_desc *cnv_gpio; + int vref_uv; + int vio_uv; + int offset_avail[3]; + unsigned int avg_log2; + enum ad4030_out_mode mode; + /* Offload sampling */ + struct spi_transfer offload_xfer; + struct spi_message offload_msg; + struct spi_offload *offload; + struct spi_offload_trigger *offload_trigger; + struct spi_offload_trigger_config offload_trigger_config; + struct pwm_device *cnv_trigger; + size_t scale_avail_size; + struct pwm_waveform cnv_wf; + unsigned int scale_avail[ARRAY_SIZE(adaq4216_hw_gains_vpv)][2]; + struct gpio_descs *pga_gpios; + unsigned int pga_index; + + /* + * DMA (thus cache coherency maintenance) requires the transfer buffers + * to live in their own cache lines. + */ + u8 tx_data[AD4030_SPI_MAX_XFER_LEN] __aligned(IIO_DMA_MINALIGN); + union { + u8 raw[AD4030_MAXIMUM_RX_BUFFER_SIZE]; + struct { + s32 diff; + u8 common; + } single; + struct { + s32 diff[2]; + u8 common[2]; + } dual; + } rx_data; +}; + +/* + * For a chip with 2 hardware channel this will be used to create 2 common-mode + * channels: + * - voltage4 + * - voltage5 + * As the common-mode channels are after the differential ones, we compute the + * channel number like this: + * - _idx is the scan_index (the order in the output buffer) + * - _ch is the hardware channel number this common-mode channel is related + * - _idx - _ch gives us the number of channel in the chip + * - _idx - _ch * 2 is the starting number of the common-mode channels, since + * for each differential channel there is a common-mode channel + * - _idx - _ch * 2 + _ch gives the channel number for this specific common-mode + * channel + */ +#define AD4030_CHAN_CMO(_idx, _ch) { \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .address = (_ch), \ + .channel = ((_idx) - (_ch)) * 2 + (_ch), \ + .scan_index = (_idx), \ + .scan_type = { \ + .sign = 'u', \ + .storagebits = 8, \ + .realbits = 8, \ + .endianness = IIO_BE, \ + }, \ +} + +/* + * For a chip with 2 hardware channel this will be used to create 2 differential + * channels: + * - voltage0-voltage1 + * - voltage2-voltage3 + */ +#define __AD4030_CHAN_DIFF(_idx, _scan_type, _offload, _pga) { \ + .info_mask_shared_by_all = \ + (_offload ? BIT(IIO_CHAN_INFO_SAMP_FREQ) : 0) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS) | \ + BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_separate_available = BIT(IIO_CHAN_INFO_CALIBBIAS) | \ + (_pga ? BIT(IIO_CHAN_INFO_SCALE) : 0) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE), \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .address = (_idx), \ + .channel = (_idx) * 2, \ + .channel2 = (_idx) * 2 + 1, \ + .scan_index = (_idx), \ + .differential = true, \ + .has_ext_scan_type = 1, \ + .ext_scan_type = _scan_type, \ + .num_ext_scan_type = ARRAY_SIZE(_scan_type), \ +} + +#define AD4030_CHAN_DIFF(_idx, _scan_type) \ + __AD4030_CHAN_DIFF(_idx, _scan_type, 0, 0) + +#define AD4030_OFFLOAD_CHAN_DIFF(_idx, _scan_type) \ + __AD4030_CHAN_DIFF(_idx, _scan_type, 1, 0) + +#define ADAQ4216_CHAN_DIFF(_idx, _scan_type) \ + __AD4030_CHAN_DIFF(_idx, _scan_type, 0, 1) + +#define ADAQ4216_OFFLOAD_CHAN_DIFF(_idx, _scan_type) \ + __AD4030_CHAN_DIFF(_idx, _scan_type, 1, 1) + +/* + * AD4030 can average over 2^N samples, where N = 1, 2, 3, ..., 16. + * We use N = 0 to mean no sample averaging. + */ +static const int ad4030_average_modes[] = { + BIT(0), /* No sampling average */ + BIT(1), BIT(2), BIT(3), BIT(4), + BIT(5), BIT(6), BIT(7), BIT(8), + BIT(9), BIT(10), BIT(11), BIT(12), + BIT(13), BIT(14), BIT(15), BIT(16), +}; + +static const struct spi_offload_config ad4030_offload_config = { + .capability_flags = SPI_OFFLOAD_CAP_TRIGGER | + SPI_OFFLOAD_CAP_RX_STREAM_DMA, +}; + +static int ad4030_enter_config_mode(struct ad4030_state *st) +{ + st->tx_data[0] = AD4030_REG_ACCESS; + + struct spi_transfer xfer = { + .tx_buf = st->tx_data, + .len = 1, + .speed_hz = AD4030_SPI_MAX_REG_XFER_SPEED, + }; + + return spi_sync_transfer(st->spi, &xfer, 1); +} + +static int ad4030_exit_config_mode(struct ad4030_state *st) +{ + st->tx_data[0] = 0; + st->tx_data[1] = AD4030_REG_EXIT_CFG_MODE; + st->tx_data[2] = AD4030_REG_EXIT_CFG_MODE_EXIT_MSK; + + struct spi_transfer xfer = { + .tx_buf = st->tx_data, + .len = 3, + .speed_hz = AD4030_SPI_MAX_REG_XFER_SPEED, + }; + + return spi_sync_transfer(st->spi, &xfer, 1); +} + +static int ad4030_spi_read(void *context, const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + int ret; + struct ad4030_state *st = context; + struct spi_transfer xfer = { + .tx_buf = st->tx_data, + .rx_buf = st->rx_data.raw, + .len = reg_size + val_size, + .speed_hz = AD4030_SPI_MAX_REG_XFER_SPEED, + }; + + if (xfer.len > sizeof(st->tx_data) || + xfer.len > sizeof(st->rx_data.raw)) + return -EINVAL; + + ret = ad4030_enter_config_mode(st); + if (ret) + return ret; + + memset(st->tx_data, 0, sizeof(st->tx_data)); + memcpy(st->tx_data, reg, reg_size); + + ret = spi_sync_transfer(st->spi, &xfer, 1); + if (ret) + return ret; + + memcpy(val, &st->rx_data.raw[reg_size], val_size); + + return ad4030_exit_config_mode(st); +} + +static int ad4030_spi_write(void *context, const void *data, size_t count) +{ + int ret; + struct ad4030_state *st = context; + bool is_reset = count >= 3 && + ((u8 *)data)[0] == 0 && + ((u8 *)data)[1] == 0 && + ((u8 *)data)[2] == 0x81; + struct spi_transfer xfer = { + .tx_buf = st->tx_data, + .len = count, + .speed_hz = AD4030_SPI_MAX_REG_XFER_SPEED, + }; + + if (count > sizeof(st->tx_data)) + return -EINVAL; + + ret = ad4030_enter_config_mode(st); + if (ret) + return ret; + + memcpy(st->tx_data, data, count); + + ret = spi_sync_transfer(st->spi, &xfer, 1); + if (ret) + return ret; + + /* + * From datasheet: "After a [...] reset, no SPI commands or conversions + * can be started for 750us" + * After a reset we are in conversion mode, no need to exit config mode + */ + if (is_reset) { + fsleep(750); + return 0; + } + + return ad4030_exit_config_mode(st); +} + +static const struct regmap_bus ad4030_regmap_bus = { + .read = ad4030_spi_read, + .write = ad4030_spi_write, + .reg_format_endian_default = REGMAP_ENDIAN_BIG, +}; + +static const struct regmap_range ad4030_regmap_rd_range[] = { + regmap_reg_range(AD4030_REG_INTERFACE_CONFIG_A, AD4030_REG_CHIP_GRADE), + regmap_reg_range(AD4030_REG_SCRATCH_PAD, AD4030_REG_STREAM_MODE), + regmap_reg_range(AD4030_REG_INTERFACE_CONFIG_C, + AD4030_REG_INTERFACE_STATUS_A), + regmap_reg_range(AD4030_REG_EXIT_CFG_MODE, AD4030_REG_PAT3), + regmap_reg_range(AD4030_REG_DIG_DIAG, AD4030_REG_DIG_ERR), +}; + +static const struct regmap_range ad4030_regmap_wr_range[] = { + regmap_reg_range(AD4030_REG_CHIP_TYPE, AD4030_REG_CHIP_GRADE), + regmap_reg_range(AD4030_REG_SPI_REVISION, AD4030_REG_VENDOR_H), +}; + +static const struct regmap_access_table ad4030_regmap_rd_table = { + .yes_ranges = ad4030_regmap_rd_range, + .n_yes_ranges = ARRAY_SIZE(ad4030_regmap_rd_range), +}; + +static const struct regmap_access_table ad4030_regmap_wr_table = { + .no_ranges = ad4030_regmap_wr_range, + .n_no_ranges = ARRAY_SIZE(ad4030_regmap_wr_range), +}; + +static const struct regmap_config ad4030_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .read_flag_mask = 0x80, + .rd_table = &ad4030_regmap_rd_table, + .wr_table = &ad4030_regmap_wr_table, + .max_register = AD4030_REG_DIG_ERR, +}; + +static void ad4030_fill_scale_avail(struct ad4030_state *st) +{ + unsigned int mag_bits, int_part, fract_part; + u64 range; + + /* + * The maximum precision of differential channels is retrieved from the + * chip properties. The output code of differential channels is in two's + * complement format (i.e. signed), so the MSB is the sign bit and only + * (precision_bits - 1) bits express voltage magnitude. + */ + mag_bits = st->chip->precision_bits - 1; + + for (unsigned int i = 0; i < ARRAY_SIZE(adaq4216_hw_gains_frac); i++) { + range = mult_frac(st->vref_uv, adaq4216_hw_gains_frac[i][1], + adaq4216_hw_gains_frac[i][0]); + /* + * If range were in mV, we would multiply it by NANO below. + * Though, range is in µV so multiply it by MICRO only so the + * result after right shift and division scales output codes to + * millivolts. + */ + int_part = div_u64_rem((range * MICRO) >> mag_bits, NANO, &fract_part); + st->scale_avail[i][0] = int_part; + st->scale_avail[i][1] = fract_part; + } +} + +static int ad4030_set_pga_gain(struct ad4030_state *st) +{ + DECLARE_BITMAP(bitmap, ADAQ4616_PGA_PINS) = { }; + + bitmap_write(bitmap, st->pga_index, 0, ADAQ4616_PGA_PINS); + + return gpiod_multi_set_value_cansleep(st->pga_gpios, bitmap); +} + +static int ad4030_set_pga(struct iio_dev *indio_dev, int gain_int, int gain_fract) +{ + struct ad4030_state *st = iio_priv(indio_dev); + unsigned int mag_bits = st->chip->precision_bits - 1; + unsigned int tmp; + u64 gain_nano; + + if (!st->pga_gpios) + return -EINVAL; + + gain_nano = gain_int * NANO + gain_fract; + if (!in_range(gain_nano, 1, ADAQ4616_PGA_GAIN_MAX_NANO)) + return -EINVAL; + + tmp = DIV_ROUND_CLOSEST_ULL(gain_nano << mag_bits, NANO); + gain_nano = DIV_ROUND_CLOSEST(st->vref_uv, tmp); + st->pga_index = find_closest(gain_nano, adaq4216_hw_gains_vpv, + ARRAY_SIZE(adaq4216_hw_gains_vpv)); + + return ad4030_set_pga_gain(st); +} + +static int ad4030_get_chan_scale(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2) +{ + struct ad4030_state *st = iio_priv(indio_dev); + const struct iio_scan_type *scan_type; + + scan_type = iio_get_current_scan_type(indio_dev, chan); + if (IS_ERR(scan_type)) + return PTR_ERR(scan_type); + + /* The LSB of the 8-bit common-mode data is always vref/256. */ + if (st->chip->has_pga && scan_type->realbits != 8) { + *val = st->scale_avail[st->pga_index][0]; + *val2 = st->scale_avail[st->pga_index][1]; + return IIO_VAL_INT_PLUS_NANO; + } + + if (chan->differential) + *val = (st->vref_uv * 2) / MILLI; + else + *val = st->vref_uv / MILLI; + + *val2 = scan_type->realbits; + + return IIO_VAL_FRACTIONAL_LOG2; +} + +static int ad4030_get_chan_calibscale(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2) +{ + struct ad4030_state *st = iio_priv(indio_dev); + u16 gain; + int ret; + + ret = regmap_bulk_read(st->regmap, AD4030_REG_GAIN_CHAN(chan->address), + st->rx_data.raw, AD4030_REG_GAIN_BYTES_NB); + if (ret) + return ret; + + gain = get_unaligned_be16(st->rx_data.raw); + + /* From datasheet: multiplied output = input × gain word/0x8000 */ + *val = gain / AD4030_GAIN_MIDLE_POINT; + *val2 = mul_u64_u32_div(gain % AD4030_GAIN_MIDLE_POINT, NANO, + AD4030_GAIN_MIDLE_POINT); + + return IIO_VAL_INT_PLUS_NANO; +} + +/* Returns the offset where 1 LSB = (VREF/2^precision_bits - 1)/gain */ +static int ad4030_get_chan_calibbias(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct ad4030_state *st = iio_priv(indio_dev); + int ret; + + ret = regmap_bulk_read(st->regmap, + AD4030_REG_OFFSET_CHAN(chan->address), + st->rx_data.raw, AD4030_REG_OFFSET_BYTES_NB); + if (ret) + return ret; + + switch (st->chip->precision_bits) { + case 16: + *val = sign_extend32(get_unaligned_be16(st->rx_data.raw), 15); + return IIO_VAL_INT; + + case 24: + *val = sign_extend32(get_unaligned_be24(st->rx_data.raw), 23); + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static void ad4030_get_sampling_freq(struct ad4030_state *st, int *freq) +{ + struct spi_offload_trigger_config *config = &st->offload_trigger_config; + + /* + * Conversion data is fetched from the device when the offload transfer + * is triggered. Thus, provide the SPI offload trigger frequency as the + * sampling frequency. + */ + *freq = config->periodic.frequency_hz; +} + +static int ad4030_update_conversion_rate(struct ad4030_state *st, + unsigned int freq_hz, unsigned int avg_log2) +{ + struct spi_offload_trigger_config *config = &st->offload_trigger_config; + unsigned int offload_period_ns, cnv_rate_hz; + struct pwm_waveform cnv_wf = { }; + u64 target = AD4030_TCNVH_NS; + u64 offload_offset_ns; + int ret; + + /* + * When averaging/oversampling over N samples, we fire the offload + * trigger once at every N pulses of the CNV signal. Conversely, the CNV + * signal needs to be N times faster than the offload trigger. Take that + * into account to correctly re-evaluate both the PWM waveform connected + * to CNV and the SPI offload trigger. + */ + cnv_rate_hz = freq_hz << avg_log2; + + cnv_wf.period_length_ns = DIV_ROUND_CLOSEST(NSEC_PER_SEC, cnv_rate_hz); + /* + * The datasheet lists a minimum time of 9.8 ns, but no maximum. If the + * rounded PWM's value is less than 10, increase the target value by 10 + * and attempt to round the waveform again, until the value is at least + * 10 ns. Use a separate variable to represent the target in case the + * rounding is severe enough to keep putting the first few results under + * the minimum 10ns condition checked by the while loop. + */ + do { + cnv_wf.duty_length_ns = target; + ret = pwm_round_waveform_might_sleep(st->cnv_trigger, &cnv_wf); + if (ret) + return ret; + target += AD4030_TCNVH_NS; + } while (cnv_wf.duty_length_ns < AD4030_TCNVH_NS); + + /* + * The CNV waveform period (period_length_ns) might get rounded down by + * pwm_round_waveform_might_sleep(). Check the resultant PWM period + * is not smaller than the minimum data conversion cycle time. + */ + if (!in_range(cnv_wf.period_length_ns, AD4030_TCYC_NS, INT_MAX)) + return -EINVAL; + + offload_period_ns = DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq_hz); + + config->periodic.frequency_hz = DIV_ROUND_UP(HZ_PER_GHZ, offload_period_ns); + + /* + * The hardware does the capture on zone 2 (when SPI trigger PWM + * is used). This means that the SPI trigger signal should happen at + * tsync + tquiet_con_delay being tsync the conversion signal period + * and tquiet_con_delay 9.8ns. Hence set the PWM phase accordingly. + * + * The PWM waveform API only supports nanosecond resolution right now, + * so round this setting to the closest available value. + */ + offload_offset_ns = AD4030_TQUIET_CNV_DELAY_NS; + do { + config->periodic.offset_ns = offload_offset_ns; + ret = spi_offload_trigger_validate(st->offload_trigger, config); + if (ret) + return ret; + offload_offset_ns += AD4030_TQUIET_CNV_DELAY_NS; + } while (config->periodic.offset_ns < AD4030_TQUIET_CNV_DELAY_NS); + + st->cnv_wf = cnv_wf; + + return 0; +} + +static int ad4030_set_sampling_freq(struct iio_dev *indio_dev, int freq_hz) +{ + struct ad4030_state *st = iio_priv(indio_dev); + + if (freq_hz == 0) + return -EINVAL; + + if (!in_range(freq_hz, 0, st->chip->max_sample_rate_hz)) + return -ERANGE; + + return ad4030_update_conversion_rate(st, freq_hz, st->avg_log2); +} + +static int ad4030_set_chan_calibscale(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int gain_int, + int gain_frac) +{ + struct ad4030_state *st = iio_priv(indio_dev); + u64 gain; + + if (gain_int < 0 || gain_frac < 0) + return -EINVAL; + + gain = mul_u32_u32(gain_int, MICRO) + gain_frac; + + if (gain > AD4030_REG_GAIN_MAX_GAIN) + return -EINVAL; + + put_unaligned_be16(DIV_ROUND_CLOSEST_ULL(gain * AD4030_GAIN_MIDLE_POINT, + MICRO), + st->tx_data); + + return regmap_bulk_write(st->regmap, + AD4030_REG_GAIN_CHAN(chan->address), + st->tx_data, AD4030_REG_GAIN_BYTES_NB); +} + +static int ad4030_set_chan_calibbias(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int offset) +{ + struct ad4030_state *st = iio_priv(indio_dev); + + if (offset < st->offset_avail[0] || offset > st->offset_avail[2]) + return -EINVAL; + + st->tx_data[2] = 0; + + switch (st->chip->precision_bits) { + case 16: + put_unaligned_be16(offset, st->tx_data); + break; + + case 24: + put_unaligned_be24(offset, st->tx_data); + break; + + default: + return -EINVAL; + } + + return regmap_bulk_write(st->regmap, + AD4030_REG_OFFSET_CHAN(chan->address), + st->tx_data, AD4030_REG_OFFSET_BYTES_NB); +} + +static int ad4030_set_avg_frame_len(struct iio_dev *dev, int avg_val) +{ + struct ad4030_state *st = iio_priv(dev); + unsigned int avg_log2 = ilog2(avg_val); + unsigned int last_avg_idx = ARRAY_SIZE(ad4030_average_modes) - 1; + int freq_hz; + int ret; + + if (avg_val < 0 || avg_val > ad4030_average_modes[last_avg_idx]) + return -EINVAL; + + if (st->offload_trigger) { + /* + * The sample averaging and sampling frequency configurations + * are mutually dependent on each other. That's because the + * effective data sample rate is fCNV / 2^N, where N is the + * number of samples being averaged. + * + * When SPI offload is supported and we have control over the + * sample rate, the conversion start signal (CNV) and the SPI + * offload trigger frequencies must be re-evaluated so data is + * fetched only after 'avg_val' conversions. + */ + ad4030_get_sampling_freq(st, &freq_hz); + ret = ad4030_update_conversion_rate(st, freq_hz, avg_log2); + if (ret) + return ret; + } + + ret = regmap_write(st->regmap, AD4030_REG_AVG, + AD4030_REG_AVG_MASK_AVG_SYNC | + FIELD_PREP(AD4030_REG_AVG_MASK_AVG_VAL, avg_log2)); + if (ret) + return ret; + + st->avg_log2 = avg_log2; + + return 0; +} + +static bool ad4030_is_common_byte_asked(struct ad4030_state *st, + unsigned int mask) +{ + return mask & (st->chip->num_voltage_inputs == 1 ? + AD4030_SINGLE_COMMON_BYTE_CHANNELS_MASK : + AD4030_DUAL_COMMON_BYTE_CHANNELS_MASK); +} + +static int ad4030_set_mode(struct iio_dev *indio_dev, unsigned long mask) +{ + struct ad4030_state *st = iio_priv(indio_dev); + + if (st->avg_log2 > 0) { + st->mode = AD4030_OUT_DATA_MD_30_AVERAGED_DIFF; + } else if (ad4030_is_common_byte_asked(st, mask)) { + switch (st->chip->precision_bits) { + case 16: + st->mode = AD4030_OUT_DATA_MD_16_DIFF_8_COM; + break; + + case 24: + st->mode = AD4030_OUT_DATA_MD_24_DIFF_8_COM; + break; + + default: + return -EINVAL; + } + } else { + st->mode = AD4030_OUT_DATA_MD_DIFF; + } + + return regmap_update_bits(st->regmap, AD4030_REG_MODES, + AD4030_REG_MODES_MASK_OUT_DATA_MODE, + st->mode); +} + +/* + * Descramble 2 32bits numbers out of a 64bits. The bits are interleaved: + * 1 bit for first number, 1 bit for the second, and so on... + */ +static void ad4030_extract_interleaved(u8 *src, u32 *ch0, u32 *ch1) +{ + u8 h0, h1, l0, l1; + u32 out0, out1; + u8 *out0_raw = (u8 *)&out0; + u8 *out1_raw = (u8 *)&out1; + + for (int i = 0; i < 4; i++) { + h0 = src[i * 2]; + l1 = src[i * 2 + 1]; + h1 = h0 << 1; + l0 = l1 >> 1; + + h0 &= 0xAA; + l0 &= 0x55; + h1 &= 0xAA; + l1 &= 0x55; + + h0 = (h0 | h0 << 001) & 0xCC; + h1 = (h1 | h1 << 001) & 0xCC; + l0 = (l0 | l0 >> 001) & 0x33; + l1 = (l1 | l1 >> 001) & 0x33; + h0 = (h0 | h0 << 002) & 0xF0; + h1 = (h1 | h1 << 002) & 0xF0; + l0 = (l0 | l0 >> 002) & 0x0F; + l1 = (l1 | l1 >> 002) & 0x0F; + + out0_raw[i] = h0 | l0; + out1_raw[i] = h1 | l1; + } + + *ch0 = out0; + *ch1 = out1; +} + +static int ad4030_conversion(struct iio_dev *indio_dev) +{ + struct ad4030_state *st = iio_priv(indio_dev); + const struct iio_scan_type *scan_type; + unsigned char diff_realbytes, diff_storagebytes; + unsigned int bytes_to_read; + unsigned long cnv_nb = BIT(st->avg_log2); + unsigned int i; + int ret; + + scan_type = iio_get_current_scan_type(indio_dev, st->chip->channels); + if (IS_ERR(scan_type)) + return PTR_ERR(scan_type); + + diff_realbytes = BITS_TO_BYTES(scan_type->realbits); + diff_storagebytes = BITS_TO_BYTES(scan_type->storagebits); + + /* Number of bytes for one differential channel */ + bytes_to_read = diff_realbytes; + /* Add one byte if we are using a differential + common byte mode */ + bytes_to_read += (st->mode == AD4030_OUT_DATA_MD_24_DIFF_8_COM || + st->mode == AD4030_OUT_DATA_MD_16_DIFF_8_COM) ? 1 : 0; + /* Multiply by the number of hardware channels */ + bytes_to_read *= st->chip->num_voltage_inputs; + + for (i = 0; i < cnv_nb; i++) { + gpiod_set_value_cansleep(st->cnv_gpio, 1); + ndelay(AD4030_TCNVH_NS); + gpiod_set_value_cansleep(st->cnv_gpio, 0); + ndelay(st->chip->tcyc_ns); + } + + ret = spi_read(st->spi, st->rx_data.raw, bytes_to_read); + if (ret) + return ret; + + if (st->chip->num_voltage_inputs == 2) + ad4030_extract_interleaved(st->rx_data.raw, + &st->rx_data.dual.diff[0], + &st->rx_data.dual.diff[1]); + + /* + * If no common mode voltage channel is enabled, we can use the raw + * data as is. Otherwise, we need to rearrange the data a bit to match + * the natural alignment of the IIO buffer. + */ + + if (st->mode != AD4030_OUT_DATA_MD_16_DIFF_8_COM && + st->mode != AD4030_OUT_DATA_MD_24_DIFF_8_COM) + return 0; + + if (st->chip->num_voltage_inputs == 1) { + st->rx_data.single.common = st->rx_data.raw[diff_realbytes]; + return 0; + } + + for (i = 0; i < st->chip->num_voltage_inputs; i++) + st->rx_data.dual.common[i] = + st->rx_data.raw[diff_storagebytes * i + diff_realbytes]; + + return 0; +} + +static int ad4030_single_conversion(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val) +{ + struct ad4030_state *st = iio_priv(indio_dev); + int ret; + + ret = ad4030_set_mode(indio_dev, BIT(chan->scan_index)); + if (ret) + return ret; + + ret = ad4030_conversion(indio_dev); + if (ret) + return ret; + + if (chan->differential) + if (st->chip->num_voltage_inputs == 1) + *val = st->rx_data.single.diff; + else + *val = st->rx_data.dual.diff[chan->address]; + else + if (st->chip->num_voltage_inputs == 1) + *val = st->rx_data.single.common; + else + *val = st->rx_data.dual.common[chan->address]; + + return IIO_VAL_INT; +} + +static irqreturn_t ad4030_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad4030_state *st = iio_priv(indio_dev); + int ret; + + ret = ad4030_conversion(indio_dev); + if (ret) + goto out; + + iio_push_to_buffers_with_ts(indio_dev, &st->rx_data, sizeof(st->rx_data), + pf->timestamp); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const int ad4030_gain_avail[3][2] = { + { 0, 0 }, + { 0, 30518 }, + { 1, 999969482 }, +}; + +static int ad4030_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, + const int **vals, int *type, + int *length, long mask) +{ + struct ad4030_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + *vals = st->offset_avail; + *type = IIO_VAL_INT; + return IIO_AVAIL_RANGE; + + case IIO_CHAN_INFO_CALIBSCALE: + *vals = (void *)ad4030_gain_avail; + *type = IIO_VAL_INT_PLUS_NANO; + return IIO_AVAIL_RANGE; + + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = ad4030_average_modes; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(ad4030_average_modes); + return IIO_AVAIL_LIST; + + case IIO_CHAN_INFO_SCALE: + if (st->scale_avail_size == 1) + *vals = (int *)st->scale_avail[st->pga_index]; + else + *vals = (int *)st->scale_avail; + *length = st->scale_avail_size * 2; /* print int and nano part */ + *type = IIO_VAL_INT_PLUS_NANO; + return IIO_AVAIL_LIST; + + default: + return -EINVAL; + } +} + +static int ad4030_read_raw_dispatch(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long info) +{ + struct ad4030_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_RAW: + return ad4030_single_conversion(indio_dev, chan, val); + + case IIO_CHAN_INFO_CALIBSCALE: + return ad4030_get_chan_calibscale(indio_dev, chan, val, val2); + + case IIO_CHAN_INFO_CALIBBIAS: + return ad4030_get_chan_calibbias(indio_dev, chan, val); + + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = BIT(st->avg_log2); + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SAMP_FREQ: + ad4030_get_sampling_freq(st, val); + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int ad4030_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long info) +{ + int ret; + + if (info == IIO_CHAN_INFO_SCALE) + return ad4030_get_chan_scale(indio_dev, chan, val, val2); + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad4030_read_raw_dispatch(indio_dev, chan, val, val2, info); + + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ad4030_write_raw_dispatch(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long info) +{ + switch (info) { + case IIO_CHAN_INFO_CALIBSCALE: + return ad4030_set_chan_calibscale(indio_dev, chan, val, val2); + + case IIO_CHAN_INFO_CALIBBIAS: + if (val2 != 0) + return -EINVAL; + return ad4030_set_chan_calibbias(indio_dev, chan, val); + + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return ad4030_set_avg_frame_len(indio_dev, val); + + case IIO_CHAN_INFO_SAMP_FREQ: + return ad4030_set_sampling_freq(indio_dev, val); + + case IIO_CHAN_INFO_SCALE: + return ad4030_set_pga(indio_dev, val, val2); + + default: + return -EINVAL; + } +} + +static int ad4030_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long info) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad4030_write_raw_dispatch(indio_dev, chan, val, val2, info); + + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ad4030_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT_PLUS_MICRO; + } +} + +static int ad4030_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + const struct ad4030_state *st = iio_priv(indio_dev); + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + if (readval) + ret = regmap_read(st->regmap, reg, readval); + else + ret = regmap_write(st->regmap, reg, writeval); + + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ad4030_read_label(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + char *label) +{ + if (chan->differential) + return sysfs_emit(label, "differential%lu\n", chan->address); + return sysfs_emit(label, "common-mode%lu\n", chan->address); +} + +static int ad4030_get_current_scan_type(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad4030_state *st = iio_priv(indio_dev); + + return st->avg_log2 ? AD4030_SCAN_TYPE_AVG : AD4030_SCAN_TYPE_NORMAL; +} + +static int ad4030_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + return ad4030_set_mode(indio_dev, *scan_mask); +} + +static const struct iio_info ad4030_iio_info = { + .read_avail = ad4030_read_avail, + .read_raw = ad4030_read_raw, + .write_raw = ad4030_write_raw, + .write_raw_get_fmt = &ad4030_write_raw_get_fmt, + .debugfs_reg_access = ad4030_reg_access, + .read_label = ad4030_read_label, + .get_current_scan_type = ad4030_get_current_scan_type, + .update_scan_mode = ad4030_update_scan_mode, +}; + +static bool ad4030_validate_scan_mask(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ad4030_state *st = iio_priv(indio_dev); + + /* Asking for both common channels and averaging */ + if (st->avg_log2 && ad4030_is_common_byte_asked(st, *scan_mask)) + return false; + + return true; +} + +static const struct iio_buffer_setup_ops ad4030_buffer_setup_ops = { + .validate_scan_mask = ad4030_validate_scan_mask, +}; + +static void ad4030_prepare_offload_msg(struct iio_dev *indio_dev) +{ + struct ad4030_state *st = iio_priv(indio_dev); + u8 offload_bpw; + + if (st->mode == AD4030_OUT_DATA_MD_30_AVERAGED_DIFF) + offload_bpw = 32; + else + offload_bpw = st->chip->precision_bits; + + st->offload_xfer.bits_per_word = offload_bpw; + st->offload_xfer.len = spi_bpw_to_bytes(offload_bpw); + st->offload_xfer.offload_flags = SPI_OFFLOAD_XFER_RX_STREAM; + spi_message_init_with_transfers(&st->offload_msg, &st->offload_xfer, 1); +} + +static int ad4030_offload_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad4030_state *st = iio_priv(indio_dev); + unsigned int reg_modes; + int ret; + + /* + * When data from 2 analog input channels is output through a single + * bus line (interleaved mode (LANE_MD == 0b11)) and gets pushed through + * DMA, extra hardware is required to do the de-interleaving. While we + * don't support such hardware configurations, disallow interleaved mode + * when using SPI offload. + */ + ret = regmap_read(st->regmap, AD4030_REG_MODES, ®_modes); + if (ret) + return ret; + + if (st->chip->num_voltage_inputs > 1 && + FIELD_GET(AD4030_REG_MODES_MASK_LANE_MODE, reg_modes) == AD4030_LANE_MD_INTERLEAVED) + return -EINVAL; + + ad4030_prepare_offload_msg(indio_dev); + st->offload_msg.offload = st->offload; + ret = spi_optimize_message(st->spi, &st->offload_msg); + if (ret) + return ret; + + ret = pwm_set_waveform_might_sleep(st->cnv_trigger, &st->cnv_wf, false); + if (ret) + goto out_unoptimize; + + ret = spi_offload_trigger_enable(st->offload, st->offload_trigger, + &st->offload_trigger_config); + if (ret) + goto out_pwm_disable; + + return 0; + +out_pwm_disable: + pwm_disable(st->cnv_trigger); +out_unoptimize: + spi_unoptimize_message(&st->offload_msg); + + return ret; +} + +static int ad4030_offload_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad4030_state *st = iio_priv(indio_dev); + + spi_offload_trigger_disable(st->offload, st->offload_trigger); + + pwm_disable(st->cnv_trigger); + + spi_unoptimize_message(&st->offload_msg); + + return 0; +} + +static const struct iio_buffer_setup_ops ad4030_offload_buffer_setup_ops = { + .postenable = &ad4030_offload_buffer_postenable, + .predisable = &ad4030_offload_buffer_predisable, +}; + +static int ad4030_regulators_get(struct ad4030_state *st) +{ + struct device *dev = &st->spi->dev; + static const char * const ids[] = { "vdd-5v", "vdd-1v8" }; + int ret; + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(ids), ids); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulators\n"); + + st->vio_uv = devm_regulator_get_enable_read_voltage(dev, "vio"); + if (st->vio_uv < 0) + return dev_err_probe(dev, st->vio_uv, + "Failed to enable and read vio voltage\n"); + + st->vref_uv = devm_regulator_get_enable_read_voltage(dev, "ref"); + if (st->vref_uv < 0) { + if (st->vref_uv != -ENODEV) + return dev_err_probe(dev, st->vref_uv, + "Failed to read ref voltage\n"); + + /* if not using optional REF, the REFIN must be used */ + st->vref_uv = devm_regulator_get_enable_read_voltage(dev, + "refin"); + if (st->vref_uv < 0) + return dev_err_probe(dev, st->vref_uv, + "Failed to read refin voltage\n"); + } + + return 0; +} + +static int ad4030_reset(struct ad4030_state *st) +{ + struct device *dev = &st->spi->dev; + struct gpio_desc *reset; + + reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(reset)) + return dev_err_probe(dev, PTR_ERR(reset), + "Failed to get reset GPIO\n"); + + if (reset) { + ndelay(50); + gpiod_set_value_cansleep(reset, 0); + return 0; + } + + return regmap_write(st->regmap, AD4030_REG_INTERFACE_CONFIG_A, + AD4030_REG_INTERFACE_CONFIG_A_SW_RESET); +} + +static int ad4030_detect_chip_info(const struct ad4030_state *st) +{ + unsigned int grade; + int ret; + + ret = regmap_read(st->regmap, AD4030_REG_CHIP_GRADE, &grade); + if (ret) + return ret; + + grade = FIELD_GET(AD4030_REG_CHIP_GRADE_MASK_CHIP_GRADE, grade); + if (grade != st->chip->grade) + dev_warn(&st->spi->dev, "Unknown grade(0x%x) for %s\n", grade, + st->chip->name); + + return 0; +} + +static int ad4030_pwm_get(struct ad4030_state *st) +{ + struct device *dev = &st->spi->dev; + + st->cnv_trigger = devm_pwm_get(dev, NULL); + if (IS_ERR(st->cnv_trigger)) + return dev_err_probe(dev, PTR_ERR(st->cnv_trigger), + "Failed to get CNV PWM\n"); + + /* + * Preemptively disable the PWM, since we only want to enable it with + * the buffer. + */ + pwm_disable(st->cnv_trigger); + + return 0; +} + +static int ad4030_config(struct ad4030_state *st) +{ + int ret; + u8 reg_modes; + + st->offset_avail[0] = (int)BIT(st->chip->precision_bits - 1) * -1; + st->offset_avail[1] = 1; + st->offset_avail[2] = BIT(st->chip->precision_bits - 1) - 1; + + if (st->chip->num_voltage_inputs > 1) + reg_modes = FIELD_PREP(AD4030_REG_MODES_MASK_LANE_MODE, + AD4030_LANE_MD_INTERLEAVED); + else + reg_modes = FIELD_PREP(AD4030_REG_MODES_MASK_LANE_MODE, + AD4030_LANE_MD_1_PER_CH); + + ret = regmap_write(st->regmap, AD4030_REG_MODES, reg_modes); + if (ret) + return ret; + + if (st->vio_uv < AD4030_VIO_THRESHOLD_UV) + return regmap_write(st->regmap, AD4030_REG_IO, + AD4030_REG_IO_MASK_IO2X); + + return 0; +} + +static int ad4030_spi_offload_setup(struct iio_dev *indio_dev, + struct ad4030_state *st) +{ + struct device *dev = &st->spi->dev; + struct dma_chan *rx_dma; + + indio_dev->setup_ops = &ad4030_offload_buffer_setup_ops; + + st->offload_trigger = devm_spi_offload_trigger_get(dev, st->offload, + SPI_OFFLOAD_TRIGGER_PERIODIC); + if (IS_ERR(st->offload_trigger)) + return dev_err_probe(dev, PTR_ERR(st->offload_trigger), + "failed to get offload trigger\n"); + + st->offload_trigger_config.type = SPI_OFFLOAD_TRIGGER_PERIODIC; + + rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev, st->offload); + if (IS_ERR(rx_dma)) + return dev_err_probe(dev, PTR_ERR(rx_dma), + "failed to get offload RX DMA\n"); + + return devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, rx_dma, + IIO_BUFFER_DIRECTION_IN); +} + +static int ad4030_setup_pga(struct device *dev, struct iio_dev *indio_dev, + struct ad4030_state *st) +{ + /* Setup GPIOs for PGA control */ + st->pga_gpios = devm_gpiod_get_array(dev, "pga", GPIOD_OUT_LOW); + if (IS_ERR(st->pga_gpios)) + return dev_err_probe(dev, PTR_ERR(st->pga_gpios), + "Failed to get PGA gpios.\n"); + + if (st->pga_gpios->ndescs != ADAQ4616_PGA_PINS) + return dev_err_probe(dev, -EINVAL, + "Expected %d GPIOs for PGA control.\n", + ADAQ4616_PGA_PINS); + + st->scale_avail_size = ARRAY_SIZE(adaq4216_hw_gains_vpv); + st->pga_index = 0; + + return 0; +} + +static int ad4030_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ad4030_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + + st->regmap = devm_regmap_init(dev, &ad4030_regmap_bus, st, + &ad4030_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "Failed to initialize regmap\n"); + + st->chip = spi_get_device_match_data(spi); + if (!st->chip) + return -EINVAL; + + ret = ad4030_regulators_get(st); + if (ret) + return ret; + + /* + * From datasheet: "Perform a reset no sooner than 3ms after the power + * supplies are valid and stable" + */ + fsleep(3000); + + ret = ad4030_reset(st); + if (ret) + return ret; + + ret = ad4030_detect_chip_info(st); + if (ret) + return ret; + + if (st->chip->has_pga) { + ret = ad4030_setup_pga(dev, indio_dev, st); + if (ret) + return ret; + + ad4030_fill_scale_avail(st); + } + + ret = ad4030_config(st); + if (ret) + return ret; + + st->cnv_gpio = devm_gpiod_get(dev, "cnv", GPIOD_OUT_LOW); + if (IS_ERR(st->cnv_gpio)) + return dev_err_probe(dev, PTR_ERR(st->cnv_gpio), + "Failed to get cnv gpio\n"); + + indio_dev->name = st->chip->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ad4030_iio_info; + indio_dev->available_scan_masks = st->chip->available_masks; + + st->offload = devm_spi_offload_get(dev, spi, &ad4030_offload_config); + ret = PTR_ERR_OR_ZERO(st->offload); + /* Fall back to low speed usage when no SPI offload is available. */ + if (ret == -ENODEV) { + /* + * One hardware channel is split in two software channels when + * using common byte mode. Add one more channel for the timestamp. + */ + indio_dev->num_channels = 2 * st->chip->num_voltage_inputs + 1; + indio_dev->channels = st->chip->channels; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + ad4030_trigger_handler, + &ad4030_buffer_setup_ops); + if (ret) + return dev_err_probe(dev, ret, + "Failed to setup triggered buffer\n"); + } else if (ret) { + return dev_err_probe(dev, ret, "failed to get offload\n"); + } else { + /* + * Offloaded SPI transfers can't support software timestamp so + * no additional timestamp channel is added. + */ + indio_dev->num_channels = st->chip->num_voltage_inputs; + indio_dev->channels = st->chip->offload_channels; + ret = ad4030_spi_offload_setup(indio_dev, st); + if (ret) + return dev_err_probe(dev, ret, + "Failed to setup SPI offload\n"); + + ret = ad4030_pwm_get(st); + if (ret) + return dev_err_probe(dev, ret, "Failed to get PWM\n"); + + /* + * Start with a slower sampling rate so there is some room for + * adjusting the sample averaging and the sampling frequency + * without hitting the maximum conversion rate. + */ + ret = ad4030_update_conversion_rate(st, st->chip->max_sample_rate_hz >> 4, + st->avg_log2); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set offload samp freq\n"); + } + + return devm_iio_device_register(dev, indio_dev); +} + +static const unsigned long ad4030_channel_masks[] = { + /* Differential only */ + BIT(0), + /* Differential and common-mode voltage */ + GENMASK(1, 0), + 0, +}; + +static const unsigned long ad4630_channel_masks[] = { + /* Differential only */ + BIT(1) | BIT(0), + /* Differential with common byte */ + GENMASK(3, 0), + 0, +}; + +static const struct iio_scan_type ad4030_24_scan_types[] = { + [AD4030_SCAN_TYPE_NORMAL] = { + .sign = 's', + .storagebits = 32, + .realbits = 24, + .shift = 8, + .endianness = IIO_BE, + }, + [AD4030_SCAN_TYPE_AVG] = { + .sign = 's', + .storagebits = 32, + .realbits = 30, + .shift = 2, + .endianness = IIO_BE, + }, +}; + +static const struct iio_scan_type ad4030_24_offload_scan_types[] = { + [AD4030_SCAN_TYPE_NORMAL] = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + .shift = 0, + .endianness = IIO_CPU, + }, + [AD4030_SCAN_TYPE_AVG] = { + .sign = 's', + .realbits = 30, + .storagebits = 32, + .shift = 2, + .endianness = IIO_CPU, + }, +}; + +static const struct iio_scan_type ad4030_16_scan_types[] = { + [AD4030_SCAN_TYPE_NORMAL] = { + .sign = 's', + .realbits = 16, + .storagebits = 32, + .shift = 16, + .endianness = IIO_BE, + }, + [AD4030_SCAN_TYPE_AVG] = { + .sign = 's', + .storagebits = 32, + .realbits = 30, + .shift = 2, + .endianness = IIO_BE, + } +}; + +static const struct iio_scan_type ad4030_16_offload_scan_types[] = { + [AD4030_SCAN_TYPE_NORMAL] = { + .sign = 's', + .realbits = 16, + .storagebits = 32, + .shift = 0, + .endianness = IIO_CPU, + }, + [AD4030_SCAN_TYPE_AVG] = { + .sign = 's', + .realbits = 30, + .storagebits = 32, + .shift = 2, + .endianness = IIO_CPU, + }, +}; + +static const struct ad4030_chip_info ad4030_24_chip_info = { + .name = "ad4030-24", + .available_masks = ad4030_channel_masks, + .channels = { + AD4030_CHAN_DIFF(0, ad4030_24_scan_types), + AD4030_CHAN_CMO(1, 0), + IIO_CHAN_SOFT_TIMESTAMP(2), + }, + .offload_channels = { + AD4030_OFFLOAD_CHAN_DIFF(0, ad4030_24_offload_scan_types), + }, + .grade = AD4030_REG_CHIP_GRADE_AD4030_24_GRADE, + .precision_bits = 24, + .num_voltage_inputs = 1, + .tcyc_ns = AD4030_TCYC_ADJUSTED_NS, + .max_sample_rate_hz = 2 * HZ_PER_MHZ, +}; + +static const struct ad4030_chip_info ad4630_16_chip_info = { + .name = "ad4630-16", + .available_masks = ad4630_channel_masks, + .channels = { + AD4030_CHAN_DIFF(0, ad4030_16_scan_types), + AD4030_CHAN_DIFF(1, ad4030_16_scan_types), + AD4030_CHAN_CMO(2, 0), + AD4030_CHAN_CMO(3, 1), + IIO_CHAN_SOFT_TIMESTAMP(4), + }, + .offload_channels = { + AD4030_OFFLOAD_CHAN_DIFF(0, ad4030_16_offload_scan_types), + AD4030_OFFLOAD_CHAN_DIFF(1, ad4030_16_offload_scan_types), + }, + .grade = AD4030_REG_CHIP_GRADE_AD4630_16_GRADE, + .precision_bits = 16, + .num_voltage_inputs = 2, + .tcyc_ns = AD4030_TCYC_ADJUSTED_NS, + .max_sample_rate_hz = 2 * HZ_PER_MHZ, +}; + +static const struct ad4030_chip_info ad4630_24_chip_info = { + .name = "ad4630-24", + .available_masks = ad4630_channel_masks, + .channels = { + AD4030_CHAN_DIFF(0, ad4030_24_scan_types), + AD4030_CHAN_DIFF(1, ad4030_24_scan_types), + AD4030_CHAN_CMO(2, 0), + AD4030_CHAN_CMO(3, 1), + IIO_CHAN_SOFT_TIMESTAMP(4), + }, + .offload_channels = { + AD4030_OFFLOAD_CHAN_DIFF(0, ad4030_24_offload_scan_types), + AD4030_OFFLOAD_CHAN_DIFF(1, ad4030_24_offload_scan_types), + }, + .grade = AD4030_REG_CHIP_GRADE_AD4630_24_GRADE, + .precision_bits = 24, + .num_voltage_inputs = 2, + .tcyc_ns = AD4030_TCYC_ADJUSTED_NS, + .max_sample_rate_hz = 2 * HZ_PER_MHZ, +}; + +static const struct ad4030_chip_info ad4632_16_chip_info = { + .name = "ad4632-16", + .available_masks = ad4630_channel_masks, + .channels = { + AD4030_CHAN_DIFF(0, ad4030_16_scan_types), + AD4030_CHAN_DIFF(1, ad4030_16_scan_types), + AD4030_CHAN_CMO(2, 0), + AD4030_CHAN_CMO(3, 1), + IIO_CHAN_SOFT_TIMESTAMP(4), + }, + .offload_channels = { + AD4030_OFFLOAD_CHAN_DIFF(0, ad4030_16_offload_scan_types), + AD4030_OFFLOAD_CHAN_DIFF(1, ad4030_16_offload_scan_types), + }, + .grade = AD4030_REG_CHIP_GRADE_AD4632_16_GRADE, + .precision_bits = 16, + .num_voltage_inputs = 2, + .tcyc_ns = AD4632_TCYC_ADJUSTED_NS, + .max_sample_rate_hz = 500 * HZ_PER_KHZ, +}; + +static const struct ad4030_chip_info ad4632_24_chip_info = { + .name = "ad4632-24", + .available_masks = ad4630_channel_masks, + .channels = { + AD4030_CHAN_DIFF(0, ad4030_24_scan_types), + AD4030_CHAN_DIFF(1, ad4030_24_scan_types), + AD4030_CHAN_CMO(2, 0), + AD4030_CHAN_CMO(3, 1), + IIO_CHAN_SOFT_TIMESTAMP(4), + }, + .offload_channels = { + AD4030_OFFLOAD_CHAN_DIFF(0, ad4030_24_offload_scan_types), + AD4030_OFFLOAD_CHAN_DIFF(1, ad4030_24_offload_scan_types), + }, + .grade = AD4030_REG_CHIP_GRADE_AD4632_24_GRADE, + .precision_bits = 24, + .num_voltage_inputs = 2, + .tcyc_ns = AD4632_TCYC_ADJUSTED_NS, + .max_sample_rate_hz = 500 * HZ_PER_KHZ, +}; + +static const struct ad4030_chip_info adaq4216_chip_info = { + .name = "adaq4216", + .available_masks = ad4030_channel_masks, + .channels = { + ADAQ4216_CHAN_DIFF(0, ad4030_16_scan_types), + AD4030_CHAN_CMO(1, 0), + IIO_CHAN_SOFT_TIMESTAMP(2), + }, + .offload_channels = { + ADAQ4216_OFFLOAD_CHAN_DIFF(0, ad4030_16_offload_scan_types), + }, + .grade = AD4030_REG_CHIP_GRADE_ADAQ4216_GRADE, + .precision_bits = 16, + .has_pga = true, + .num_voltage_inputs = 1, + .tcyc_ns = AD4030_TCYC_ADJUSTED_NS, + .max_sample_rate_hz = 2 * HZ_PER_MHZ, +}; + +static const struct ad4030_chip_info adaq4224_chip_info = { + .name = "adaq4224", + .available_masks = ad4030_channel_masks, + .channels = { + ADAQ4216_CHAN_DIFF(0, ad4030_24_scan_types), + AD4030_CHAN_CMO(1, 0), + IIO_CHAN_SOFT_TIMESTAMP(2), + }, + .offload_channels = { + ADAQ4216_OFFLOAD_CHAN_DIFF(0, ad4030_24_offload_scan_types), + }, + .grade = AD4030_REG_CHIP_GRADE_ADAQ4224_GRADE, + .precision_bits = 24, + .has_pga = true, + .num_voltage_inputs = 1, + .tcyc_ns = AD4030_TCYC_ADJUSTED_NS, + .max_sample_rate_hz = 2 * HZ_PER_MHZ, +}; + +static const struct spi_device_id ad4030_id_table[] = { + { .name = "ad4030-24", .driver_data = (kernel_ulong_t)&ad4030_24_chip_info }, + { .name = "ad4630-16", .driver_data = (kernel_ulong_t)&ad4630_16_chip_info }, + { .name = "ad4630-24", .driver_data = (kernel_ulong_t)&ad4630_24_chip_info }, + { .name = "ad4632-16", .driver_data = (kernel_ulong_t)&ad4632_16_chip_info }, + { .name = "ad4632-24", .driver_data = (kernel_ulong_t)&ad4632_24_chip_info }, + { .name = "adaq4216", .driver_data = (kernel_ulong_t)&adaq4216_chip_info }, + { .name = "adaq4224", .driver_data = (kernel_ulong_t)&adaq4224_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad4030_id_table); + +static const struct of_device_id ad4030_of_match[] = { + { .compatible = "adi,ad4030-24", .data = &ad4030_24_chip_info }, + { .compatible = "adi,ad4630-16", .data = &ad4630_16_chip_info }, + { .compatible = "adi,ad4630-24", .data = &ad4630_24_chip_info }, + { .compatible = "adi,ad4632-16", .data = &ad4632_16_chip_info }, + { .compatible = "adi,ad4632-24", .data = &ad4632_24_chip_info }, + { .compatible = "adi,adaq4216", .data = &adaq4216_chip_info }, + { .compatible = "adi,adaq4224", .data = &adaq4224_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad4030_of_match); + +static struct spi_driver ad4030_driver = { + .driver = { + .name = "ad4030", + .of_match_table = ad4030_of_match, + }, + .probe = ad4030_probe, + .id_table = ad4030_id_table, +}; +module_spi_driver(ad4030_driver); + +MODULE_AUTHOR("Esteban Blanc "); +MODULE_DESCRIPTION("Analog Devices AD4630 ADC family driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER"); diff --git a/drivers/iio/adc/ad4062.c b/drivers/iio/adc/ad4062.c new file mode 100644 index 000000000..8e5984055 --- /dev/null +++ b/drivers/iio/adc/ad4062.c @@ -0,0 +1,1614 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices AD4062 I3C ADC driver + * + * Copyright 2025 Analog Devices Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AD4062_REG_INTERFACE_CONFIG_A 0x00 +#define AD4062_REG_DEVICE_CONFIG 0x02 +#define AD4062_REG_DEVICE_CONFIG_POWER_MODE_MSK GENMASK(1, 0) +#define AD4062_REG_DEVICE_CONFIG_LOW_POWER_MODE 3 +#define AD4062_REG_PROD_ID_1 0x05 +#define AD4062_REG_DEVICE_GRADE 0x06 +#define AD4062_REG_SCRATCH_PAD 0x0A +#define AD4062_REG_VENDOR_H 0x0D +#define AD4062_REG_STREAM_MODE 0x0E +#define AD4062_REG_INTERFACE_STATUS 0x11 +#define AD4062_REG_MODE_SET 0x20 +#define AD4062_REG_MODE_SET_ENTER_ADC BIT(0) +#define AD4062_REG_ADC_MODES 0x21 +#define AD4062_REG_ADC_MODES_MODE_MSK GENMASK(1, 0) +#define AD4062_REG_ADC_CONFIG 0x22 +#define AD4062_REG_ADC_CONFIG_REF_EN_MSK BIT(5) +#define AD4062_REG_ADC_CONFIG_SCALE_EN_MSK BIT(4) +#define AD4062_REG_AVG_CONFIG 0x23 +#define AD4062_REG_GP_CONF 0x24 +#define AD4062_REG_GP_CONF_MODE_MSK_0 GENMASK(2, 0) +#define AD4062_REG_GP_CONF_MODE_MSK_1 GENMASK(6, 4) +#define AD4062_REG_INTR_CONF 0x25 +#define AD4062_REG_INTR_CONF_EN_MSK_0 GENMASK(1, 0) +#define AD4062_REG_INTR_CONF_EN_MSK_1 GENMASK(5, 4) +#define AD4062_REG_TIMER_CONFIG 0x27 +#define AD4062_REG_TIMER_CONFIG_FS_MASK GENMASK(7, 4) +#define AD4062_REG_MAX_LIMIT 0x29 +#define AD4062_REG_MIN_LIMIT 0x2B +#define AD4062_REG_MAX_HYST 0x2C +#define AD4062_REG_MIN_HYST 0x2D +#define AD4062_REG_MON_VAL 0x2F +#define AD4062_REG_ADC_IBI_EN 0x31 +#define AD4062_REG_ADC_IBI_EN_CONV_TRIGGER BIT(2) +#define AD4062_REG_ADC_IBI_EN_MAX BIT(1) +#define AD4062_REG_ADC_IBI_EN_MIN BIT(0) +#define AD4062_REG_FUSE_CRC 0x40 +#define AD4062_REG_DEVICE_STATUS 0x41 +#define AD4062_REG_DEVICE_STATUS_DEVICE_RESET BIT(6) +#define AD4062_REG_IBI_STATUS 0x48 +#define AD4062_REG_CONV_READ_LSB 0x50 +#define AD4062_REG_CONV_READ_16BITS 0x51 +#define AD4062_REG_CONV_READ_32BITS 0x53 +#define AD4062_REG_CONV_TRIGGER_16BITS 0x57 +#define AD4062_REG_CONV_TRIGGER_32BITS 0x59 +#define AD4062_REG_CONV_AUTO 0x61 +#define AD4062_MAX_REG AD4062_REG_CONV_AUTO + +#define AD4062_MON_VAL_MIDDLE_POINT 0x8000 + +#define AD4062_I3C_VENDOR 0x0177 +#define AD4062_SOFT_RESET 0x81 +#define AD4060_PROD_ID 0x7A +#define AD4062_PROD_ID 0x7C + +#define AD4062_GP_DISABLED 0x0 +#define AD4062_GP_INTR 0x1 +#define AD4062_GP_DRDY 0x2 +#define AD4062_GP_STATIC_LOW 0x5 +#define AD4062_GP_STATIC_HIGH 0x6 + +#define AD4062_LIMIT_BITS 12 + +#define AD4062_INTR_EN_NEITHER 0x0 +#define AD4062_INTR_EN_EITHER 0x3 + +#define AD4062_TCONV_NS 270 + +enum ad4062_operation_mode { + AD4062_SAMPLE_MODE = 0x0, + AD4062_BURST_AVERAGING_MODE = 0x1, + AD4062_MONITOR_MODE = 0x3, +}; + +struct ad4062_chip_info { + const struct iio_chan_spec channels[1]; + const char *name; + u16 prod_id; + u16 avg_max; +}; + +enum { + AD4062_SCAN_TYPE_SAMPLE, + AD4062_SCAN_TYPE_BURST_AVG, +}; + +static const struct iio_scan_type ad4062_scan_type_12_s[] = { + [AD4062_SCAN_TYPE_SAMPLE] = { + .sign = 's', + .realbits = 12, + .storagebits = 16, + .endianness = IIO_BE, + }, + [AD4062_SCAN_TYPE_BURST_AVG] = { + .sign = 's', + .realbits = 14, + .storagebits = 16, + .endianness = IIO_BE, + }, +}; + +static const struct iio_scan_type ad4062_scan_type_16_s[] = { + [AD4062_SCAN_TYPE_SAMPLE] = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, + }, + [AD4062_SCAN_TYPE_BURST_AVG] = { + .sign = 's', + .realbits = 20, + .storagebits = 32, + .endianness = IIO_BE, + }, +}; + +static const unsigned int ad4062_conversion_freqs[] = { + 2000000, 1000000, 300000, 100000, /* 0 - 3 */ + 33300, 10000, 3000, 500, /* 4 - 7 */ + 333, 250, 200, 166, /* 8 - 11 */ + 140, 124, 111, /* 12 - 15 */ +}; + +struct ad4062_state { + const struct ad4062_chip_info *chip; + const struct ad4062_bus_ops *ops; + enum ad4062_operation_mode mode; + struct work_struct trig_conv; + struct completion completion; + struct iio_trigger *trigger; + struct iio_dev *indio_dev; + struct i3c_device *i3cdev; + struct regmap *regmap; + bool wait_event; + int vref_uV; + unsigned int samp_freqs[ARRAY_SIZE(ad4062_conversion_freqs)]; + bool gpo_irq[2]; + u16 sampling_frequency; + u16 events_frequency; + u8 oversamp_ratio; + u8 conv_sizeof; + u8 conv_addr; + union { + __be32 be32; + __be16 be16; + } buf __aligned(IIO_DMA_MINALIGN); +}; + +static const struct regmap_range ad4062_regmap_rd_ranges[] = { + regmap_reg_range(AD4062_REG_INTERFACE_CONFIG_A, AD4062_REG_DEVICE_GRADE), + regmap_reg_range(AD4062_REG_SCRATCH_PAD, AD4062_REG_INTERFACE_STATUS), + regmap_reg_range(AD4062_REG_MODE_SET, AD4062_REG_ADC_IBI_EN), + regmap_reg_range(AD4062_REG_FUSE_CRC, AD4062_REG_IBI_STATUS), + regmap_reg_range(AD4062_REG_CONV_READ_LSB, AD4062_REG_CONV_AUTO), +}; + +static const struct regmap_access_table ad4062_regmap_rd_table = { + .yes_ranges = ad4062_regmap_rd_ranges, + .n_yes_ranges = ARRAY_SIZE(ad4062_regmap_rd_ranges), +}; + +static const struct regmap_range ad4062_regmap_wr_ranges[] = { + regmap_reg_range(AD4062_REG_INTERFACE_CONFIG_A, AD4062_REG_DEVICE_CONFIG), + regmap_reg_range(AD4062_REG_SCRATCH_PAD, AD4062_REG_SCRATCH_PAD), + regmap_reg_range(AD4062_REG_STREAM_MODE, AD4062_REG_INTERFACE_STATUS), + regmap_reg_range(AD4062_REG_MODE_SET, AD4062_REG_ADC_IBI_EN), + regmap_reg_range(AD4062_REG_FUSE_CRC, AD4062_REG_DEVICE_STATUS), +}; + +static const struct regmap_access_table ad4062_regmap_wr_table = { + .yes_ranges = ad4062_regmap_wr_ranges, + .n_yes_ranges = ARRAY_SIZE(ad4062_regmap_wr_ranges), +}; + +static const struct iio_event_spec ad4062_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_shared_by_all = BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_HYSTERESIS), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_HYSTERESIS), + }, +}; + +#define AD4062_CHAN(bits) { \ + .type = IIO_VOLTAGE, \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .indexed = 1, \ + .channel = 0, \ + .event_spec = ad4062_events, \ + .num_event_specs = ARRAY_SIZE(ad4062_events), \ + .has_ext_scan_type = 1, \ + .ext_scan_type = ad4062_scan_type_##bits##_s, \ + .num_ext_scan_type = ARRAY_SIZE(ad4062_scan_type_##bits##_s), \ +} + +static const struct ad4062_chip_info ad4060_chip_info = { + .name = "ad4060", + .channels = { AD4062_CHAN(12) }, + .prod_id = AD4060_PROD_ID, + .avg_max = 256, +}; + +static const struct ad4062_chip_info ad4062_chip_info = { + .name = "ad4062", + .channels = { AD4062_CHAN(16) }, + .prod_id = AD4062_PROD_ID, + .avg_max = 4096, +}; + +static ssize_t sampling_frequency_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ad4062_state *st = iio_priv(dev_to_iio_dev(dev)); + + return sysfs_emit(buf, "%d\n", ad4062_conversion_freqs[st->events_frequency]); +} + +static int sampling_frequency_store_dispatch(struct iio_dev *indio_dev, + const char *buf) +{ + struct ad4062_state *st = iio_priv(indio_dev); + int val, ret; + + if (st->wait_event) + return -EBUSY; + + ret = kstrtoint(buf, 10, &val); + if (ret) + return ret; + + st->events_frequency = find_closest_descending(val, ad4062_conversion_freqs, + ARRAY_SIZE(ad4062_conversion_freqs)); + return 0; +} + +static ssize_t sampling_frequency_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = sampling_frequency_store_dispatch(indio_dev, buf); + iio_device_release_direct(indio_dev); + return ret ?: len; +} + +static IIO_DEVICE_ATTR_RW(sampling_frequency, 0); + +static ssize_t sampling_frequency_available_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret = 0; + + for (u8 i = 0; i < ARRAY_SIZE(ad4062_conversion_freqs); i++) + ret += sysfs_emit_at(buf, ret, "%d%s", ad4062_conversion_freqs[i], + i != (ARRAY_SIZE(ad4062_conversion_freqs) - 1) ? " " : "\n"); + return ret; +} + +static IIO_DEVICE_ATTR_RO(sampling_frequency_available, 0); + +static struct attribute *ad4062_event_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group ad4062_event_attribute_group = { + .attrs = ad4062_event_attributes, +}; + +static int ad4062_set_oversampling_ratio(struct ad4062_state *st, int val, int val2) +{ + const u32 _max = st->chip->avg_max; + const u32 _min = 1; + int ret; + + if (!in_range(val, _min, _max) || val2 != 0) + return -EINVAL; + + /* 1 disables oversampling */ + val = ilog2(val); + if (val == 0) { + st->mode = AD4062_SAMPLE_MODE; + } else { + st->mode = AD4062_BURST_AVERAGING_MODE; + ret = regmap_write(st->regmap, AD4062_REG_AVG_CONFIG, val - 1); + if (ret) + return ret; + } + st->oversamp_ratio = val; + + return 0; +} + +static int ad4062_get_oversampling_ratio(struct ad4062_state *st, int *val) +{ + int ret, buf; + + if (st->mode == AD4062_SAMPLE_MODE) { + *val = 1; + return 0; + } + + ret = regmap_read(st->regmap, AD4062_REG_AVG_CONFIG, &buf); + if (ret) + return ret; + + *val = BIT(buf + 1); + return 0; +} + +static int ad4062_calc_sampling_frequency(unsigned int fosc, unsigned int oversamp_ratio) +{ + /* From datasheet p.31: (n_avg - 1)/fosc + tconv */ + u32 n_avg = BIT(oversamp_ratio) - 1; + u32 period_ns = NSEC_PER_SEC / fosc; + + /* Result is less than 1 Hz */ + if (n_avg >= fosc) + return 1; + + return NSEC_PER_SEC / (n_avg * period_ns + AD4062_TCONV_NS); +} + +static int ad4062_populate_sampling_frequency(struct ad4062_state *st) +{ + for (u8 i = 0; i < ARRAY_SIZE(ad4062_conversion_freqs); i++) + st->samp_freqs[i] = + ad4062_calc_sampling_frequency(ad4062_conversion_freqs[i], + st->oversamp_ratio); + return 0; +} + +static int ad4062_get_sampling_frequency(struct ad4062_state *st, int *val) +{ + int freq = ad4062_conversion_freqs[st->sampling_frequency]; + + *val = ad4062_calc_sampling_frequency(freq, st->oversamp_ratio); + return IIO_VAL_INT; +} + +static int ad4062_set_sampling_frequency(struct ad4062_state *st, int val, int val2) +{ + int ret; + + if (val2 != 0) + return -EINVAL; + + ret = ad4062_populate_sampling_frequency(st); + if (ret) + return ret; + + st->sampling_frequency = + find_closest_descending(val, st->samp_freqs, + ARRAY_SIZE(ad4062_conversion_freqs)); + return 0; +} + +static int ad4062_check_ids(struct ad4062_state *st) +{ + struct device *dev = &st->i3cdev->dev; + int ret; + u16 val; + + ret = regmap_bulk_read(st->regmap, AD4062_REG_PROD_ID_1, + &st->buf.be16, sizeof(st->buf.be16)); + if (ret) + return ret; + + val = be16_to_cpu(st->buf.be16); + if (val != st->chip->prod_id) + dev_warn(dev, "Production ID x%x does not match known values", val); + + ret = regmap_bulk_read(st->regmap, AD4062_REG_VENDOR_H, + &st->buf.be16, sizeof(st->buf.be16)); + if (ret) + return ret; + + val = be16_to_cpu(st->buf.be16); + if (val != AD4062_I3C_VENDOR) + return dev_err_probe(dev, -ENODEV, + "Vendor ID x%x does not match expected value\n", val); + + return 0; +} + +static int ad4062_conversion_frequency_set(struct ad4062_state *st, u8 val) +{ + return regmap_write(st->regmap, AD4062_REG_TIMER_CONFIG, + FIELD_PREP(AD4062_REG_TIMER_CONFIG_FS_MASK, val)); +} + +static int ad4062_set_operation_mode(struct ad4062_state *st, + enum ad4062_operation_mode mode) +{ + const unsigned int samp_freq = mode == AD4062_MONITOR_MODE ? + st->events_frequency : st->sampling_frequency; + int ret; + + ret = ad4062_conversion_frequency_set(st, samp_freq); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, AD4062_REG_ADC_MODES, + AD4062_REG_ADC_MODES_MODE_MSK, mode); + if (ret) + return ret; + + if (mode == AD4062_MONITOR_MODE) { + /* Change address pointer to enter monitor mode */ + struct i3c_xfer xfer_trigger = { + .data.out = &st->conv_addr, + .len = sizeof(st->conv_addr), + .rnw = false, + }; + st->conv_addr = AD4062_REG_CONV_TRIGGER_32BITS; + return i3c_device_do_xfers(st->i3cdev, &xfer_trigger, 1, I3C_SDR); + } + + return regmap_write(st->regmap, AD4062_REG_MODE_SET, + AD4062_REG_MODE_SET_ENTER_ADC); +} + +static int ad4062_soft_reset(struct ad4062_state *st) +{ + u8 val = AD4062_SOFT_RESET; + int ret; + + ret = regmap_write(st->regmap, AD4062_REG_INTERFACE_CONFIG_A, val); + if (ret) + return ret; + + /* Wait AD4062 treset time, datasheet p8 */ + ndelay(60); + + return 0; +} + +static int ad4062_setup(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + const bool *ref_sel) +{ + struct ad4062_state *st = iio_priv(indio_dev); + const struct iio_scan_type *scan_type; + int ret; + + scan_type = iio_get_current_scan_type(indio_dev, chan); + if (IS_ERR(scan_type)) + return PTR_ERR(scan_type); + + ret = regmap_update_bits(st->regmap, AD4062_REG_GP_CONF, + AD4062_REG_GP_CONF_MODE_MSK_0, + FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_0, + AD4062_GP_INTR)); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, AD4062_REG_GP_CONF, + AD4062_REG_GP_CONF_MODE_MSK_1, + FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_1, + AD4062_GP_DRDY)); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, AD4062_REG_ADC_CONFIG, + AD4062_REG_ADC_CONFIG_REF_EN_MSK, + FIELD_PREP(AD4062_REG_ADC_CONFIG_REF_EN_MSK, + *ref_sel)); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD4062_REG_DEVICE_STATUS, + AD4062_REG_DEVICE_STATUS_DEVICE_RESET); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, AD4062_REG_INTR_CONF, + AD4062_REG_INTR_CONF_EN_MSK_0, + FIELD_PREP(AD4062_REG_INTR_CONF_EN_MSK_0, + AD4062_INTR_EN_EITHER)); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, AD4062_REG_INTR_CONF, + AD4062_REG_INTR_CONF_EN_MSK_1, + FIELD_PREP(AD4062_REG_INTR_CONF_EN_MSK_1, + AD4062_INTR_EN_NEITHER)); + if (ret) + return ret; + + st->buf.be16 = cpu_to_be16(AD4062_MON_VAL_MIDDLE_POINT); + return regmap_bulk_write(st->regmap, AD4062_REG_MON_VAL, + &st->buf.be16, sizeof(st->buf.be16)); +} + +static irqreturn_t ad4062_irq_handler_thresh(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + + return IRQ_HANDLED; +} + +static irqreturn_t ad4062_irq_handler_drdy(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ad4062_state *st = iio_priv(indio_dev); + + if (iio_buffer_enabled(indio_dev) && iio_trigger_using_own(indio_dev)) + iio_trigger_poll(st->trigger); + else + complete(&st->completion); + + return IRQ_HANDLED; +} + +static void ad4062_ibi_handler(struct i3c_device *i3cdev, + const struct i3c_ibi_payload *payload) +{ + struct ad4062_state *st = i3cdev_get_drvdata(i3cdev); + + if (st->wait_event) { + iio_push_event(st->indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(st->indio_dev)); + return; + } + if (iio_buffer_enabled(st->indio_dev)) + iio_trigger_poll_nested(st->trigger); + else + complete(&st->completion); +} + +static void ad4062_trigger_work(struct work_struct *work) +{ + struct ad4062_state *st = + container_of(work, struct ad4062_state, trig_conv); + int ret; + + /* + * Read current conversion, if at reg CONV_READ, stop bit triggers + * next sample and does not need writing the address. + */ + struct i3c_xfer xfer_sample = { + .data.in = &st->buf.be32, + .len = st->conv_sizeof, + .rnw = true, + }; + struct i3c_xfer xfer_trigger = { + .data.out = &st->conv_addr, + .len = sizeof(st->conv_addr), + .rnw = false, + }; + + ret = i3c_device_do_xfers(st->i3cdev, &xfer_sample, 1, I3C_SDR); + if (ret) + return; + + iio_push_to_buffers_with_ts(st->indio_dev, &st->buf.be32, st->conv_sizeof, + iio_get_time_ns(st->indio_dev)); + if (st->gpo_irq[1]) + return; + + i3c_device_do_xfers(st->i3cdev, &xfer_trigger, 1, I3C_SDR); +} + +static irqreturn_t ad4062_poll_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad4062_state *st = iio_priv(indio_dev); + + iio_trigger_notify_done(indio_dev->trig); + schedule_work(&st->trig_conv); + + return IRQ_HANDLED; +} + +static void ad4062_disable_ibi(void *data) +{ + struct i3c_device *i3cdev = data; + + i3c_device_disable_ibi(i3cdev); +} + +static void ad4062_free_ibi(void *data) +{ + struct i3c_device *i3cdev = data; + + i3c_device_free_ibi(i3cdev); +} + +static int ad4062_request_ibi(struct i3c_device *i3cdev) +{ + const struct i3c_ibi_setup ibireq = { + .max_payload_len = 1, + .num_slots = 1, + .handler = ad4062_ibi_handler, + }; + int ret; + + ret = i3c_device_request_ibi(i3cdev, &ibireq); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&i3cdev->dev, ad4062_free_ibi, i3cdev); + if (ret) + return ret; + + ret = i3c_device_enable_ibi(i3cdev); + if (ret) + return ret; + + return devm_add_action_or_reset(&i3cdev->dev, ad4062_disable_ibi, i3cdev); +} + +static int ad4062_request_irq(struct iio_dev *indio_dev) +{ + struct ad4062_state *st = iio_priv(indio_dev); + struct device *dev = &st->i3cdev->dev; + int ret; + + ret = fwnode_irq_get_byname(dev_fwnode(&st->i3cdev->dev), "gp0"); + if (ret == -EPROBE_DEFER) + return ret; + + if (ret < 0) { + st->gpo_irq[0] = false; + ret = regmap_update_bits(st->regmap, AD4062_REG_ADC_IBI_EN, + AD4062_REG_ADC_IBI_EN_MAX | AD4062_REG_ADC_IBI_EN_MIN, + AD4062_REG_ADC_IBI_EN_MAX | AD4062_REG_ADC_IBI_EN_MIN); + if (ret) + return ret; + } else { + st->gpo_irq[0] = true; + ret = devm_request_threaded_irq(dev, ret, NULL, + ad4062_irq_handler_thresh, + IRQF_ONESHOT, indio_dev->name, + indio_dev); + if (ret) + return ret; + } + + ret = fwnode_irq_get_byname(dev_fwnode(&st->i3cdev->dev), "gp1"); + if (ret == -EPROBE_DEFER) + return ret; + + if (ret < 0) { + st->gpo_irq[1] = false; + return regmap_update_bits(st->regmap, AD4062_REG_ADC_IBI_EN, + AD4062_REG_ADC_IBI_EN_CONV_TRIGGER, + AD4062_REG_ADC_IBI_EN_CONV_TRIGGER); + } + st->gpo_irq[1] = true; + + return devm_request_irq(dev, ret, ad4062_irq_handler_drdy, + IRQF_NO_THREAD, indio_dev->name, indio_dev); +} + +static const struct iio_trigger_ops ad4062_trigger_ops = { + .validate_device = &iio_trigger_validate_own_device, +}; + +static int ad4062_request_trigger(struct iio_dev *indio_dev) +{ + struct ad4062_state *st = iio_priv(indio_dev); + struct device *dev = &st->i3cdev->dev; + int ret; + + st->trigger = devm_iio_trigger_alloc(dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!st->trigger) + return -ENOMEM; + + st->trigger->ops = &ad4062_trigger_ops; + iio_trigger_set_drvdata(st->trigger, indio_dev); + + ret = devm_iio_trigger_register(dev, st->trigger); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(st->trigger); + + return 0; +} + +static const int ad4062_oversampling_avail[] = { + 1, 2, 4, 8, 16, 32, 64, 128, /* 0 - 7 */ + 256, 512, 1024, 2048, 4096, /* 8 - 12 */ +}; + +static int ad4062_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, const int **vals, + int *type, int *len, long mask) +{ + struct ad4062_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = ad4062_oversampling_avail; + *len = ARRAY_SIZE(ad4062_oversampling_avail); + *len -= st->chip->avg_max == 256 ? 4 : 0; + *type = IIO_VAL_INT; + + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = ad4062_populate_sampling_frequency(st); + if (ret) + return ret; + *vals = st->samp_freqs; + *len = st->oversamp_ratio ? ARRAY_SIZE(ad4062_conversion_freqs) : 1; + *type = IIO_VAL_INT; + + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int ad4062_get_chan_scale(struct iio_dev *indio_dev, int *val, int *val2) +{ + struct ad4062_state *st = iio_priv(indio_dev); + const struct iio_scan_type *scan_type; + + /* + * In burst averaging mode the averaging filter accumulates resulting + * in a sample with increased precision. + */ + scan_type = iio_get_current_scan_type(indio_dev, st->chip->channels); + if (IS_ERR(scan_type)) + return PTR_ERR(scan_type); + + *val = (st->vref_uV * 2) / (MICRO / MILLI); /* signed */ + *val2 = scan_type->realbits - 1; + + return IIO_VAL_FRACTIONAL_LOG2; +} + +static int ad4062_get_chan_calibscale(struct ad4062_state *st, int *val, int *val2) +{ + int ret; + + ret = regmap_bulk_read(st->regmap, AD4062_REG_MON_VAL, + &st->buf.be16, sizeof(st->buf.be16)); + if (ret) + return ret; + + /* From datasheet: code out = code in × mon_val/0x8000 */ + *val = be16_to_cpu(st->buf.be16) * 2; + *val2 = 16; + + return IIO_VAL_FRACTIONAL_LOG2; +} + +static int ad4062_set_chan_calibscale(struct ad4062_state *st, int gain_int, + int gain_frac) +{ + /* Divide numerator and denumerator by known great common divider */ + const u32 mon_val = AD4062_MON_VAL_MIDDLE_POINT / 64; + const u32 micro = MICRO / 64; + const u32 gain_fp = gain_int * MICRO + gain_frac; + const u32 reg_val = DIV_ROUND_CLOSEST(gain_fp * mon_val, micro); + int ret; + + /* Checks if the gain is in range and the value fits the field */ + if (gain_int < 0 || gain_int > 1 || reg_val > BIT(16) - 1) + return -EINVAL; + + st->buf.be16 = cpu_to_be16(reg_val); + ret = regmap_bulk_write(st->regmap, AD4062_REG_MON_VAL, + &st->buf.be16, sizeof(st->buf.be16)); + if (ret) + return ret; + + /* Enable scale if gain is not equal to one */ + return regmap_update_bits(st->regmap, AD4062_REG_ADC_CONFIG, + AD4062_REG_ADC_CONFIG_SCALE_EN_MSK, + FIELD_PREP(AD4062_REG_ADC_CONFIG_SCALE_EN_MSK, + !(gain_int == 1 && gain_frac == 0))); +} + +static int ad4062_read_chan_raw(struct ad4062_state *st, int *val) +{ + struct i3c_device *i3cdev = st->i3cdev; + struct i3c_xfer xfer_trigger = { + .data.out = &st->conv_addr, + .len = sizeof(st->conv_addr), + .rnw = false, + }; + struct i3c_xfer xfer_sample = { + .data.in = &st->buf.be32, + .len = sizeof(st->buf.be32), + .rnw = true, + }; + int ret; + + PM_RUNTIME_ACQUIRE(&st->i3cdev->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + ret = ad4062_set_operation_mode(st, st->mode); + if (ret) + return ret; + + reinit_completion(&st->completion); + /* Change address pointer to trigger conversion */ + st->conv_addr = AD4062_REG_CONV_TRIGGER_32BITS; + ret = i3c_device_do_xfers(i3cdev, &xfer_trigger, 1, I3C_SDR); + if (ret) + return ret; + /* + * Single sample read should be used only for oversampling and + * sampling frequency pairs that take less than 1 sec. + */ + ret = wait_for_completion_timeout(&st->completion, + msecs_to_jiffies(1000)); + if (!ret) + return -ETIMEDOUT; + + ret = i3c_device_do_xfers(i3cdev, &xfer_sample, 1, I3C_SDR); + if (ret) + return ret; + *val = be32_to_cpu(st->buf.be32); + return 0; +} + +static int ad4062_read_raw_dispatch(struct ad4062_state *st, + int *val, int *val2, long info) +{ + if (st->wait_event) + return -EBUSY; + + switch (info) { + case IIO_CHAN_INFO_RAW: + return ad4062_read_chan_raw(st, val); + + case IIO_CHAN_INFO_CALIBSCALE: + return ad4062_get_chan_calibscale(st, val, val2); + + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return ad4062_get_oversampling_ratio(st, val); + + default: + return -EINVAL; + } +} + +static int ad4062_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad4062_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_SCALE: + return ad4062_get_chan_scale(indio_dev, val, val2); + + case IIO_CHAN_INFO_SAMP_FREQ: + return ad4062_get_sampling_frequency(st, val); + } + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad4062_read_raw_dispatch(st, val, val2, info); + iio_device_release_direct(indio_dev); + return ret ?: IIO_VAL_INT; +} + +static int ad4062_write_raw_dispatch(struct ad4062_state *st, int val, int val2, + long info) +{ + if (st->wait_event) + return -EBUSY; + + switch (info) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return ad4062_set_oversampling_ratio(st, val, val2); + + case IIO_CHAN_INFO_CALIBSCALE: + return ad4062_set_chan_calibscale(st, val, val2); + + default: + return -EINVAL; + } +} + +static int ad4062_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long info) +{ + struct ad4062_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + return ad4062_set_sampling_frequency(st, val, val2); + } + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad4062_write_raw_dispatch(st, val, val2, info); + iio_device_release_direct(indio_dev); + return ret; +} + +static int pm_ad4062_monitor_mode_enable(struct ad4062_state *st) +{ + int ret; + + PM_RUNTIME_ACQUIRE(&st->i3cdev->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + return ad4062_set_operation_mode(st, AD4062_MONITOR_MODE); +} + +static int ad4062_monitor_mode_enable(struct ad4062_state *st) +{ + int ret; + + ret = pm_ad4062_monitor_mode_enable(st); + if (ret) + return ret; + + pm_runtime_get_noresume(&st->i3cdev->dev); + return 0; +} + +static int ad4062_monitor_mode_disable(struct ad4062_state *st) +{ + pm_runtime_put_autosuspend(&st->i3cdev->dev); + return 0; +} + +static int ad4062_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct ad4062_state *st = iio_priv(indio_dev); + + return st->wait_event; +} + +static int ad4062_write_event_config_dispatch(struct iio_dev *indio_dev, + bool state) +{ + struct ad4062_state *st = iio_priv(indio_dev); + int ret; + + if (st->wait_event == state) + ret = 0; + else if (state) + ret = ad4062_monitor_mode_enable(st); + else + ret = ad4062_monitor_mode_disable(st); + if (ret) + return ret; + + st->wait_event = state; + return 0; +} + +static int ad4062_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad4062_write_event_config_dispatch(indio_dev, state); + iio_device_release_direct(indio_dev); + return ret; +} + +static int __ad4062_read_event_info_value(struct ad4062_state *st, + enum iio_event_direction dir, int *val) +{ + int ret; + u8 reg; + + if (dir == IIO_EV_DIR_RISING) + reg = AD4062_REG_MAX_LIMIT; + else + reg = AD4062_REG_MIN_LIMIT; + + ret = regmap_bulk_read(st->regmap, reg, &st->buf.be16, + sizeof(st->buf.be16)); + if (ret) + return ret; + + *val = sign_extend32(be16_to_cpu(st->buf.be16), AD4062_LIMIT_BITS - 1); + + return 0; +} + +static int __ad4062_read_event_info_hysteresis(struct ad4062_state *st, + enum iio_event_direction dir, int *val) +{ + u8 reg; + + if (dir == IIO_EV_DIR_RISING) + reg = AD4062_REG_MAX_HYST; + else + reg = AD4062_REG_MIN_HYST; + return regmap_read(st->regmap, reg, val); +} + +static int ad4062_read_event_config_dispatch(struct iio_dev *indio_dev, + enum iio_event_direction dir, + enum iio_event_info info, int *val) +{ + struct ad4062_state *st = iio_priv(indio_dev); + + if (st->wait_event) + return -EBUSY; + + switch (info) { + case IIO_EV_INFO_VALUE: + return __ad4062_read_event_info_value(st, dir, val); + case IIO_EV_INFO_HYSTERESIS: + return __ad4062_read_event_info_hysteresis(st, dir, val); + default: + return -EINVAL; + } +} + +static int ad4062_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, + int *val2) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad4062_read_event_config_dispatch(indio_dev, dir, info, val); + iio_device_release_direct(indio_dev); + return ret ?: IIO_VAL_INT; +} + +static int __ad4062_write_event_info_value(struct ad4062_state *st, + enum iio_event_direction dir, int val) +{ + u8 reg; + + if (val != sign_extend32(val, AD4062_LIMIT_BITS - 1)) + return -EINVAL; + if (dir == IIO_EV_DIR_RISING) + reg = AD4062_REG_MAX_LIMIT; + else + reg = AD4062_REG_MIN_LIMIT; + st->buf.be16 = cpu_to_be16(val); + + return regmap_bulk_write(st->regmap, reg, &st->buf.be16, + sizeof(st->buf.be16)); +} + +static int __ad4062_write_event_info_hysteresis(struct ad4062_state *st, + enum iio_event_direction dir, int val) +{ + u8 reg; + + if (val > BIT(7) - 1) + return -EINVAL; + if (dir == IIO_EV_DIR_RISING) + reg = AD4062_REG_MAX_HYST; + else + reg = AD4062_REG_MIN_HYST; + + return regmap_write(st->regmap, reg, val); +} + +static int ad4062_write_event_value_dispatch(struct iio_dev *indio_dev, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val) +{ + struct ad4062_state *st = iio_priv(indio_dev); + + if (st->wait_event) + return -EBUSY; + + switch (type) { + case IIO_EV_TYPE_THRESH: + switch (info) { + case IIO_EV_INFO_VALUE: + return __ad4062_write_event_info_value(st, dir, val); + case IIO_EV_INFO_HYSTERESIS: + return __ad4062_write_event_info_hysteresis(st, dir, val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ad4062_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, + int val2) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad4062_write_event_value_dispatch(indio_dev, type, dir, info, val); + iio_device_release_direct(indio_dev); + return ret; +} + +/* + * The AD4062 in burst averaging mode increases realbits from 16-bits to + * 20-bits, increasing the storagebits from 16-bits to 32-bits. + */ +static inline int ad4062_sizeof_storagebits(struct ad4062_state *st) +{ + const struct iio_scan_type *scan_type = + iio_get_current_scan_type(st->indio_dev, st->chip->channels); + + if (IS_ERR(scan_type)) + return PTR_ERR(scan_type); + + return BITS_TO_BYTES(scan_type->storagebits); +} + +/* Read registers only with realbits (no sign extension bytes) */ +static inline size_t ad4062_get_conv_addr(struct ad4062_state *st, size_t _sizeof) +{ + if (st->gpo_irq[1]) + return _sizeof == sizeof(u32) ? AD4062_REG_CONV_READ_32BITS : + AD4062_REG_CONV_READ_16BITS; + return _sizeof == sizeof(u32) ? AD4062_REG_CONV_TRIGGER_32BITS : + AD4062_REG_CONV_TRIGGER_16BITS; +} + +static int pm_ad4062_triggered_buffer_postenable(struct ad4062_state *st) +{ + int ret; + + PM_RUNTIME_ACQUIRE(&st->i3cdev->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + if (st->wait_event) + return -EBUSY; + + ret = ad4062_set_operation_mode(st, st->mode); + if (ret) + return ret; + + ret = ad4062_sizeof_storagebits(st); + if (ret < 0) + return ret; + + st->conv_sizeof = ret; + + st->conv_addr = ad4062_get_conv_addr(st, st->conv_sizeof); + /* CONV_READ requires read to trigger first sample. */ + struct i3c_xfer xfer_sample[2] = { + { + .data.out = &st->conv_addr, + .len = sizeof(st->conv_addr), + .rnw = false, + }, + { + .data.in = &st->buf.be32, + .len = sizeof(st->buf.be32), + .rnw = true, + } + }; + + return i3c_device_do_xfers(st->i3cdev, xfer_sample, + st->gpo_irq[1] ? 2 : 1, I3C_SDR); +} + +static int ad4062_triggered_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad4062_state *st = iio_priv(indio_dev); + int ret; + + ret = pm_ad4062_triggered_buffer_postenable(st); + if (ret) + return ret; + + pm_runtime_get_noresume(&st->i3cdev->dev); + return 0; +} + +static int ad4062_triggered_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad4062_state *st = iio_priv(indio_dev); + + pm_runtime_put_autosuspend(&st->i3cdev->dev); + return 0; +} + +static const struct iio_buffer_setup_ops ad4062_triggered_buffer_setup_ops = { + .postenable = &ad4062_triggered_buffer_postenable, + .predisable = &ad4062_triggered_buffer_predisable, +}; + +static int ad4062_debugfs_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct ad4062_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + else + return regmap_write(st->regmap, reg, writeval); +} + +static int ad4062_get_current_scan_type(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad4062_state *st = iio_priv(indio_dev); + + return st->mode == AD4062_BURST_AVERAGING_MODE ? + AD4062_SCAN_TYPE_BURST_AVG : + AD4062_SCAN_TYPE_SAMPLE; +} + +static const struct iio_info ad4062_info = { + .read_raw = ad4062_read_raw, + .write_raw = ad4062_write_raw, + .read_avail = ad4062_read_avail, + .read_event_config = ad4062_read_event_config, + .write_event_config = ad4062_write_event_config, + .read_event_value = ad4062_read_event_value, + .write_event_value = ad4062_write_event_value, + .event_attrs = &ad4062_event_attribute_group, + .get_current_scan_type = ad4062_get_current_scan_type, + .debugfs_reg_access = ad4062_debugfs_reg_access, +}; + +static const struct regmap_config ad4062_regmap_config = { + .name = "ad4062", + .reg_bits = 8, + .val_bits = 8, + .max_register = AD4062_MAX_REG, + .rd_table = &ad4062_regmap_rd_table, + .wr_table = &ad4062_regmap_wr_table, + .can_sleep = true, +}; + +static int ad4062_regulators_get(struct ad4062_state *st, bool *ref_sel) +{ + struct device *dev = &st->i3cdev->dev; + int ret; + + ret = devm_regulator_get_enable(dev, "vio"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable vio voltage\n"); + + st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "ref"); + *ref_sel = st->vref_uV == -ENODEV; + if (st->vref_uV < 0 && !*ref_sel) + return dev_err_probe(dev, st->vref_uV, + "Failed to enable and read ref voltage\n"); + + if (*ref_sel) { + st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "vdd"); + if (st->vref_uV < 0) + return dev_err_probe(dev, st->vref_uV, + "Failed to enable and read vdd voltage\n"); + } else { + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable vdd regulator\n"); + } + + return 0; +} + +static int ad4062_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + return GPIO_LINE_DIRECTION_OUT; +} + +static int ad4062_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + struct ad4062_state *st = gpiochip_get_data(gc); + unsigned int reg_val = value ? AD4062_GP_STATIC_HIGH : AD4062_GP_STATIC_LOW; + + if (offset) + return regmap_update_bits(st->regmap, AD4062_REG_GP_CONF, + AD4062_REG_GP_CONF_MODE_MSK_1, + FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_1, reg_val)); + else + return regmap_update_bits(st->regmap, AD4062_REG_GP_CONF, + AD4062_REG_GP_CONF_MODE_MSK_0, + FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_0, reg_val)); +} + +static int ad4062_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct ad4062_state *st = gpiochip_get_data(gc); + unsigned int reg_val; + int ret; + + ret = regmap_read(st->regmap, AD4062_REG_GP_CONF, ®_val); + if (ret) + return ret; + + if (offset) + reg_val = FIELD_GET(AD4062_REG_GP_CONF_MODE_MSK_1, reg_val); + else + reg_val = FIELD_GET(AD4062_REG_GP_CONF_MODE_MSK_0, reg_val); + + return reg_val == AD4062_GP_STATIC_HIGH; +} + +static void ad4062_gpio_disable(void *data) +{ + struct ad4062_state *st = data; + u8 val = FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_0, AD4062_GP_DISABLED) | + FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_1, AD4062_GP_DISABLED); + + regmap_update_bits(st->regmap, AD4062_REG_GP_CONF, + AD4062_REG_GP_CONF_MODE_MSK_1 | AD4062_REG_GP_CONF_MODE_MSK_0, + val); +} + +static int ad4062_gpio_init_valid_mask(struct gpio_chip *gc, + unsigned long *valid_mask, + unsigned int ngpios) +{ + struct ad4062_state *st = gpiochip_get_data(gc); + + bitmap_zero(valid_mask, ngpios); + + for (unsigned int i = 0; i < ARRAY_SIZE(st->gpo_irq); i++) + __assign_bit(i, valid_mask, !st->gpo_irq[i]); + + return 0; +} + +static int ad4062_gpio_init(struct ad4062_state *st) +{ + struct device *dev = &st->i3cdev->dev; + struct gpio_chip *gc; + u8 val, mask; + int ret; + + if (!device_property_read_bool(dev, "gpio-controller")) + return 0; + + gc = devm_kzalloc(dev, sizeof(*gc), GFP_KERNEL); + if (!gc) + return -ENOMEM; + + val = 0; + mask = 0; + if (!st->gpo_irq[0]) { + mask |= AD4062_REG_GP_CONF_MODE_MSK_0; + val |= FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_0, AD4062_GP_STATIC_LOW); + } + if (!st->gpo_irq[1]) { + mask |= AD4062_REG_GP_CONF_MODE_MSK_1; + val |= FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_1, AD4062_GP_STATIC_LOW); + } + + ret = regmap_update_bits(st->regmap, AD4062_REG_GP_CONF, + mask, val); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, ad4062_gpio_disable, st); + if (ret) + return ret; + + gc->parent = dev; + gc->label = st->chip->name; + gc->owner = THIS_MODULE; + gc->base = -1; + gc->ngpio = 2; + gc->init_valid_mask = ad4062_gpio_init_valid_mask; + gc->get_direction = ad4062_gpio_get_direction; + gc->set = ad4062_gpio_set; + gc->get = ad4062_gpio_get; + gc->can_sleep = true; + + ret = devm_gpiochip_add_data(dev, gc, st); + if (ret) + return dev_err_probe(dev, ret, "Unable to register GPIO chip\n"); + + return 0; +} + +static const struct i3c_device_id ad4062_id_table[] = { + I3C_DEVICE(AD4062_I3C_VENDOR, AD4060_PROD_ID, &ad4060_chip_info), + I3C_DEVICE(AD4062_I3C_VENDOR, AD4062_PROD_ID, &ad4062_chip_info), + { } +}; +MODULE_DEVICE_TABLE(i3c, ad4062_id_table); + +static int ad4062_probe(struct i3c_device *i3cdev) +{ + const struct i3c_device_id *id = i3c_device_match_id(i3cdev, ad4062_id_table); + const struct ad4062_chip_info *chip = id->data; + struct device *dev = &i3cdev->dev; + struct iio_dev *indio_dev; + struct ad4062_state *st; + bool ref_sel; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->i3cdev = i3cdev; + i3cdev_set_drvdata(i3cdev, st); + init_completion(&st->completion); + + ret = ad4062_regulators_get(st, &ref_sel); + if (ret) + return ret; + + st->regmap = devm_regmap_init_i3c(i3cdev, &ad4062_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "Failed to initialize regmap\n"); + + st->mode = AD4062_SAMPLE_MODE; + st->wait_event = false; + st->chip = chip; + st->sampling_frequency = 0; + st->events_frequency = 0; + st->oversamp_ratio = 0; + st->indio_dev = indio_dev; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = 1; + indio_dev->info = &ad4062_info; + indio_dev->name = chip->name; + indio_dev->channels = chip->channels; + + ret = ad4062_soft_reset(st); + if (ret) + return dev_err_probe(dev, ret, "AD4062 failed to soft reset\n"); + + ret = ad4062_check_ids(st); + if (ret) + return ret; + + ret = ad4062_setup(indio_dev, indio_dev->channels, &ref_sel); + if (ret) + return ret; + + ret = ad4062_request_irq(indio_dev); + if (ret) + return ret; + + ret = ad4062_request_trigger(indio_dev); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(&i3cdev->dev, indio_dev, + iio_pollfunc_store_time, + ad4062_poll_handler, + &ad4062_triggered_buffer_setup_ops); + if (ret) + return ret; + + pm_runtime_set_active(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable pm_runtime\n"); + + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + + ret = ad4062_request_ibi(i3cdev); + if (ret) + return dev_err_probe(dev, ret, "Failed to request i3c ibi\n"); + + ret = ad4062_gpio_init(st); + if (ret) + return ret; + + ret = devm_work_autocancel(dev, &st->trig_conv, ad4062_trigger_work); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static int ad4062_runtime_suspend(struct device *dev) +{ + struct ad4062_state *st = dev_get_drvdata(dev); + + return regmap_write(st->regmap, AD4062_REG_DEVICE_CONFIG, + FIELD_PREP(AD4062_REG_DEVICE_CONFIG_POWER_MODE_MSK, + AD4062_REG_DEVICE_CONFIG_LOW_POWER_MODE)); +} + +static int ad4062_runtime_resume(struct device *dev) +{ + struct ad4062_state *st = dev_get_drvdata(dev); + int ret; + + ret = regmap_clear_bits(st->regmap, AD4062_REG_DEVICE_CONFIG, + AD4062_REG_DEVICE_CONFIG_POWER_MODE_MSK); + if (ret) + return ret; + + /* Wait device functional blocks to power up */ + fsleep(3 * USEC_PER_MSEC); + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(ad4062_pm_ops, + ad4062_runtime_suspend, ad4062_runtime_resume, NULL); + +static struct i3c_driver ad4062_driver = { + .driver = { + .name = "ad4062", + .pm = pm_ptr(&ad4062_pm_ops), + }, + .probe = ad4062_probe, + .id_table = ad4062_id_table, +}; +module_i3c_driver(ad4062_driver); + +MODULE_AUTHOR("Jorge Marques "); +MODULE_DESCRIPTION("Analog Devices AD4062"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/ad4080.c b/drivers/iio/adc/ad4080.c new file mode 100644 index 000000000..04cd6628e --- /dev/null +++ b/drivers/iio/adc/ad4080.c @@ -0,0 +1,918 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices AD4080 SPI ADC driver + * + * Copyright 2025 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register Definition */ +#define AD4080_REG_INTERFACE_CONFIG_A 0x00 +#define AD4080_REG_INTERFACE_CONFIG_B 0x01 +#define AD4080_REG_DEVICE_CONFIG 0x02 +#define AD4080_REG_CHIP_TYPE 0x03 +#define AD4080_REG_PRODUCT_ID_L 0x04 +#define AD4080_REG_PRODUCT_ID_H 0x05 +#define AD4080_REG_CHIP_GRADE 0x06 +#define AD4080_REG_SCRATCH_PAD 0x0A +#define AD4080_REG_SPI_REVISION 0x0B +#define AD4080_REG_VENDOR_L 0x0C +#define AD4080_REG_VENDOR_H 0x0D +#define AD4080_REG_STREAM_MODE 0x0E +#define AD4080_REG_TRANSFER_CONFIG 0x0F +#define AD4080_REG_INTERFACE_CONFIG_C 0x10 +#define AD4080_REG_INTERFACE_STATUS_A 0x11 +#define AD4080_REG_DEVICE_STATUS 0x14 +#define AD4080_REG_ADC_DATA_INTF_CONFIG_A 0x15 +#define AD4080_REG_ADC_DATA_INTF_CONFIG_B 0x16 +#define AD4080_REG_ADC_DATA_INTF_CONFIG_C 0x17 +#define AD4080_REG_PWR_CTRL 0x18 +#define AD4080_REG_GPIO_CONFIG_A 0x19 +#define AD4080_REG_GPIO_CONFIG_B 0x1A +#define AD4080_REG_GPIO_CONFIG_C 0x1B +#define AD4080_REG_GENERAL_CONFIG 0x1C +#define AD4080_REG_FIFO_WATERMARK_LSB 0x1D +#define AD4080_REG_FIFO_WATERMARK_MSB 0x1E +#define AD4080_REG_EVENT_HYSTERESIS_LSB 0x1F +#define AD4080_REG_EVENT_HYSTERESIS_MSB 0x20 +#define AD4080_REG_EVENT_DETECTION_HI_LSB 0x21 +#define AD4080_REG_EVENT_DETECTION_HI_MSB 0x22 +#define AD4080_REG_EVENT_DETECTION_LO_LSB 0x23 +#define AD4080_REG_EVENT_DETECTION_LO_MSB 0x24 +#define AD4080_REG_OFFSET_LSB 0x25 +#define AD4080_REG_OFFSET_MSB 0x26 +#define AD4080_REG_GAIN_LSB 0x27 +#define AD4080_REG_GAIN_MSB 0x28 +#define AD4080_REG_FILTER_CONFIG 0x29 + +/* AD4080_REG_INTERFACE_CONFIG_A Bit Definition */ +#define AD4080_INTERFACE_CONFIG_A_SW_RESET (BIT(7) | BIT(0)) +#define AD4080_INTERFACE_CONFIG_A_ADDR_ASC BIT(5) +#define AD4080_INTERFACE_CONFIG_A_SDO_ENABLE BIT(4) + +/* AD4080_REG_INTERFACE_CONFIG_B Bit Definition */ +#define AD4080_INTERFACE_CONFIG_B_SINGLE_INST BIT(7) +#define AD4080_INTERFACE_CONFIG_B_SHORT_INST BIT(3) + +/* AD4080_REG_DEVICE_CONFIG Bit Definition */ +#define AD4080_DEVICE_CONFIG_OPERATING_MODES_MSK GENMASK(1, 0) + +/* AD4080_REG_TRANSFER_CONFIG Bit Definition */ +#define AD4080_TRANSFER_CONFIG_KEEP_STREAM_LENGTH_VAL BIT(2) + +/* AD4080_REG_INTERFACE_CONFIG_C Bit Definition */ +#define AD4080_INTERFACE_CONFIG_C_STRICT_REG_ACCESS BIT(5) + +/* AD4080_REG_ADC_DATA_INTF_CONFIG_A Bit Definition */ +#define AD4080_ADC_DATA_INTF_CONFIG_A_RESERVED_CONFIG_A BIT(6) +#define AD4080_ADC_DATA_INTF_CONFIG_A_INTF_CHK_EN BIT(4) +#define AD4080_ADC_DATA_INTF_CONFIG_A_SPI_LVDS_LANES BIT(2) +#define AD4080_ADC_DATA_INTF_CONFIG_A_DATA_INTF_MODE BIT(0) + +/* AD4080_REG_ADC_DATA_INTF_CONFIG_B Bit Definition */ +#define AD4080_ADC_DATA_INTF_CONFIG_B_LVDS_CNV_CLK_CNT_MSK GENMASK(7, 4) +#define AD4080_ADC_DATA_INTF_CONFIG_B_LVDS_SELF_CLK_MODE BIT(3) +#define AD4080_ADC_DATA_INTF_CONFIG_B_LVDS_CNV_EN BIT(0) + +/* AD4080_REG_ADC_DATA_INTF_CONFIG_C Bit Definition */ +#define AD4080_ADC_DATA_INTF_CONFIG_C_LVDS_VOD_MSK GENMASK(6, 4) + +/* AD4080_REG_PWR_CTRL Bit Definition */ +#define AD4080_PWR_CTRL_ANA_DIG_LDO_PD BIT(1) +#define AD4080_PWR_CTRL_INTF_LDO_PD BIT(0) + +/* AD4080_REG_GPIO_CONFIG_A Bit Definition */ +#define AD4080_GPIO_CONFIG_A_GPO_1_EN BIT(1) +#define AD4080_GPIO_CONFIG_A_GPO_0_EN BIT(0) + +/* AD4080_REG_GPIO_CONFIG_B Bit Definition */ +#define AD4080_GPIO_CONFIG_B_GPIO_1_SEL_MSK GENMASK(7, 4) +#define AD4080_GPIO_CONFIG_B_GPIO_0_SEL_MSK GENMASK(3, 0) +#define AD4080_GPIO_CONFIG_B_GPIO_SPI_SDO 0 +#define AD4080_GPIO_CONFIG_B_GPIO_FIFO_FULL 1 +#define AD4080_GPIO_CONFIG_B_GPIO_FIFO_READ_DONE 2 +#define AD4080_GPIO_CONFIG_B_GPIO_FILTER_RES_RDY 3 +#define AD4080_GPIO_CONFIG_B_GPIO_H_THRESH 4 +#define AD4080_GPIO_CONFIG_B_GPIO_L_THRESH 5 +#define AD4080_GPIO_CONFIG_B_GPIO_STATUS_ALERT 6 +#define AD4080_GPIO_CONFIG_B_GPIO_GPIO_DATA 7 +#define AD4080_GPIO_CONFIG_B_GPIO_FILTER_SYNC 8 +#define AD4080_GPIO_CONFIG_B_GPIO_EXTERNAL_EVENT 9 + +/* AD4080_REG_FIFO_CONFIG Bit Definition */ +#define AD4080_FIFO_CONFIG_FIFO_MODE_MSK GENMASK(1, 0) + +/* AD4080_REG_FILTER_CONFIG Bit Definition */ +#define AD4080_FILTER_CONFIG_SINC_DEC_RATE_MSK GENMASK(6, 3) +#define AD4080_FILTER_CONFIG_FILTER_SEL_MSK GENMASK(1, 0) + +/* Miscellaneous Definitions */ +#define AD4080_SPI_READ BIT(7) +#define AD4080_CHIP_ID 0x0050 +#define AD4081_CHIP_ID 0x0051 +#define AD4082_CHIP_ID 0x0052 +#define AD4083_CHIP_ID 0x0053 +#define AD4084_CHIP_ID 0x0054 +#define AD4085_CHIP_ID 0x0055 +#define AD4086_CHIP_ID 0x0056 +#define AD4087_CHIP_ID 0x0057 +#define AD4088_CHIP_ID 0x0058 +#define AD4880_CHIP_ID 0x0059 +#define AD4883_CHIP_ID 0x005B +#define AD4884_CHIP_ID 0x005C + +#define AD4080_MAX_CHANNELS 2 + +#define AD4080_LVDS_CNV_CLK_CNT_MAX 7 + +#define AD4080_MAX_SAMP_FREQ 40000000 +#define AD4080_MIN_SAMP_FREQ 1250000 + +enum ad4080_filter_type { + FILTER_NONE, + SINC_1, + SINC_5, + SINC_5_COMP +}; + +static const unsigned int ad4080_scale_table[][2] = { + { 6000, 0 }, +}; + +static const char *const ad4080_filter_type_iio_enum[] = { + [FILTER_NONE] = "none", + [SINC_1] = "sinc1", + [SINC_5] = "sinc5", + [SINC_5_COMP] = "sinc5+pf1", +}; + +static const int ad4080_dec_rate_avail[] = { + 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, +}; + +static const int ad4080_dec_rate_none[] = { 1 }; + +static const char * const ad4080_power_supplies[] = { + "vdd33", "vdd11", "vddldo", "iovdd", "vrefin", +}; + +struct ad4080_chip_info { + const char *name; + unsigned int product_id; + int num_scales; + const unsigned int (*scale_table)[2]; + const struct iio_chan_spec *channels; + unsigned int num_channels; + unsigned int lvds_cnv_clk_cnt_max; +}; + +struct ad4080_state { + struct spi_device *spi[AD4080_MAX_CHANNELS]; + struct regmap *regmap[AD4080_MAX_CHANNELS]; + struct iio_backend *back[AD4080_MAX_CHANNELS]; + const struct ad4080_chip_info *info; + /* + * Synchronize access to members the of driver state, and ensure + * atomicity of consecutive regmap operations. + */ + struct mutex lock; + unsigned int num_lanes; + unsigned long clk_rate; + enum ad4080_filter_type filter_type[AD4080_MAX_CHANNELS]; + bool lvds_cnv_en; +}; + +static const struct regmap_config ad4080_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .read_flag_mask = BIT(7), + .max_register = 0x29, +}; + +static int ad4080_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct ad4080_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap[0], reg, readval); + + return regmap_write(st->regmap[0], reg, writeval); +} + +static int ad4080_get_scale(struct ad4080_state *st, int *val, int *val2) +{ + unsigned int tmp; + + tmp = (st->info->scale_table[0][0] * 1000000ULL) >> + st->info->channels[0].scan_type.realbits; + *val = tmp / 1000000; + *val2 = tmp % 1000000; + + return IIO_VAL_INT_PLUS_NANO; +} + +static unsigned int ad4080_get_dec_rate(struct iio_dev *dev, + const struct iio_chan_spec *chan) +{ + struct ad4080_state *st = iio_priv(dev); + int ret; + unsigned int data; + unsigned int ch = chan->channel; + + ret = regmap_read(st->regmap[ch], AD4080_REG_FILTER_CONFIG, &data); + if (ret) + return ret; + + return 1 << (FIELD_GET(AD4080_FILTER_CONFIG_SINC_DEC_RATE_MSK, data) + 1); +} + +static int ad4080_set_dec_rate(struct iio_dev *dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct ad4080_state *st = iio_priv(dev); + unsigned int ch = chan->channel; + + guard(mutex)(&st->lock); + + if ((st->filter_type[ch] >= SINC_5 && mode >= 512) || mode < 2) + return -EINVAL; + + return regmap_update_bits(st->regmap[ch], AD4080_REG_FILTER_CONFIG, + AD4080_FILTER_CONFIG_SINC_DEC_RATE_MSK, + FIELD_PREP(AD4080_FILTER_CONFIG_SINC_DEC_RATE_MSK, + (ilog2(mode) - 1))); +} + +static int ad4080_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long m) +{ + struct ad4080_state *st = iio_priv(indio_dev); + int dec_rate; + + switch (m) { + case IIO_CHAN_INFO_SCALE: + return ad4080_get_scale(st, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + dec_rate = ad4080_get_dec_rate(indio_dev, chan); + if (dec_rate < 0) + return dec_rate; + if (st->filter_type[chan->channel] == SINC_5_COMP) + dec_rate *= 2; + if (st->filter_type[chan->channel]) + *val = DIV_ROUND_CLOSEST(st->clk_rate, dec_rate); + else + *val = st->clk_rate; + return IIO_VAL_INT; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + if (st->filter_type[chan->channel] == FILTER_NONE) { + *val = 1; + } else { + *val = ad4080_get_dec_rate(indio_dev, chan); + if (*val < 0) + return *val; + } + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad4080_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ad4080_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + if (st->filter_type[chan->channel] == FILTER_NONE && val > 1) + return -EINVAL; + + return ad4080_set_dec_rate(indio_dev, chan, val); + default: + return -EINVAL; + } +} + +static int ad4080_lvds_sync_write(struct ad4080_state *st, unsigned int ch) +{ + struct device *dev = regmap_get_device(st->regmap[ch]); + int ret; + + ret = regmap_set_bits(st->regmap[ch], AD4080_REG_ADC_DATA_INTF_CONFIG_A, + AD4080_ADC_DATA_INTF_CONFIG_A_INTF_CHK_EN); + if (ret) + return ret; + + ret = iio_backend_interface_data_align(st->back[ch], 10000); + if (ret) + return dev_err_probe(dev, ret, + "Data alignment process failed\n"); + + dev_dbg(dev, "Success: Pattern correct and Locked!\n"); + return regmap_clear_bits(st->regmap[ch], AD4080_REG_ADC_DATA_INTF_CONFIG_A, + AD4080_ADC_DATA_INTF_CONFIG_A_INTF_CHK_EN); +} + +static int ad4080_get_filter_type(struct iio_dev *dev, + const struct iio_chan_spec *chan) +{ + struct ad4080_state *st = iio_priv(dev); + unsigned int data; + unsigned int ch = chan->channel; + int ret; + + ret = regmap_read(st->regmap[ch], AD4080_REG_FILTER_CONFIG, &data); + if (ret) + return ret; + + return FIELD_GET(AD4080_FILTER_CONFIG_FILTER_SEL_MSK, data); +} + +static int ad4080_set_filter_type(struct iio_dev *dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct ad4080_state *st = iio_priv(dev); + unsigned int ch = chan->channel; + int dec_rate; + int ret; + + guard(mutex)(&st->lock); + + dec_rate = ad4080_get_dec_rate(dev, chan); + if (dec_rate < 0) + return dec_rate; + + if (mode >= SINC_5 && dec_rate >= 512) + return -EINVAL; + + ret = iio_backend_filter_type_set(st->back[ch], mode); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap[ch], AD4080_REG_FILTER_CONFIG, + AD4080_FILTER_CONFIG_FILTER_SEL_MSK, + FIELD_PREP(AD4080_FILTER_CONFIG_FILTER_SEL_MSK, + mode)); + if (ret) + return ret; + + st->filter_type[ch] = mode; + + return 0; +} + +static int ad4080_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct ad4080_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (st->filter_type[chan->channel]) { + case FILTER_NONE: + *vals = ad4080_dec_rate_none; + *length = ARRAY_SIZE(ad4080_dec_rate_none); + break; + default: + *vals = ad4080_dec_rate_avail; + *length = st->filter_type[chan->channel] >= SINC_5 ? + (ARRAY_SIZE(ad4080_dec_rate_avail) - 2) : + ARRAY_SIZE(ad4080_dec_rate_avail); + break; + } + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int ad4880_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ad4080_state *st = iio_priv(indio_dev); + int ret; + + for (unsigned int ch = 0; ch < st->info->num_channels; ch++) { + /* + * Each backend has a single channel (channel 0 from the + * backend's perspective), so always use channel index 0. + */ + if (test_bit(ch, scan_mask)) + ret = iio_backend_chan_enable(st->back[ch], 0); + else + ret = iio_backend_chan_disable(st->back[ch], 0); + if (ret) + return ret; + } + + return 0; +} + +static const struct iio_info ad4080_iio_info = { + .debugfs_reg_access = ad4080_reg_access, + .read_raw = ad4080_read_raw, + .write_raw = ad4080_write_raw, + .read_avail = ad4080_read_avail, +}; + +/* + * AD4880 needs update_scan_mode to enable/disable individual backend channels. + * Single-channel devices don't need this as their backends may not implement + * chan_enable/chan_disable operations. + */ +static const struct iio_info ad4880_iio_info = { + .debugfs_reg_access = ad4080_reg_access, + .read_raw = ad4080_read_raw, + .write_raw = ad4080_write_raw, + .read_avail = ad4080_read_avail, + .update_scan_mode = ad4880_update_scan_mode, +}; + +static const struct iio_enum ad4080_filter_type_enum = { + .items = ad4080_filter_type_iio_enum, + .num_items = ARRAY_SIZE(ad4080_filter_type_iio_enum), + .set = ad4080_set_filter_type, + .get = ad4080_get_filter_type, +}; + +static struct iio_chan_spec_ext_info ad4080_ext_info[] = { + IIO_ENUM("filter_type", IIO_SHARED_BY_ALL, &ad4080_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_ALL, + &ad4080_filter_type_enum), + { } +}; + +/* + * AD4880 needs per-channel filter configuration since each channel has + * its own independent ADC with separate SPI interface. + */ +static struct iio_chan_spec_ext_info ad4880_ext_info[] = { + IIO_ENUM("filter_type", IIO_SEPARATE, &ad4080_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SEPARATE, + &ad4080_filter_type_enum), + { } +}; + +#define AD4080_CHANNEL_DEFINE(bits, storage, idx) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .ext_info = ad4080_ext_info, \ + .scan_index = (idx), \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = (storage), \ + }, \ +} + +/* + * AD4880 has per-channel attributes (filter_type, oversampling_ratio, + * sampling_frequency) since each channel has its own independent ADC + * with separate SPI configuration interface. + */ +#define AD4880_CHANNEL_DEFINE(bits, storage, idx) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_separate_available = \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .ext_info = ad4880_ext_info, \ + .scan_index = (idx), \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = (storage), \ + }, \ +} + +static const struct iio_chan_spec ad4080_channel = AD4080_CHANNEL_DEFINE(20, 32, 0); + +static const struct iio_chan_spec ad4081_channel = AD4080_CHANNEL_DEFINE(20, 32, 0); + +static const struct iio_chan_spec ad4082_channel = AD4080_CHANNEL_DEFINE(20, 32, 0); + +static const struct iio_chan_spec ad4083_channel = AD4080_CHANNEL_DEFINE(16, 16, 0); + +static const struct iio_chan_spec ad4084_channel = AD4080_CHANNEL_DEFINE(16, 16, 0); + +static const struct iio_chan_spec ad4085_channel = AD4080_CHANNEL_DEFINE(16, 16, 0); + +static const struct iio_chan_spec ad4086_channel = AD4080_CHANNEL_DEFINE(14, 16, 0); + +static const struct iio_chan_spec ad4087_channel = AD4080_CHANNEL_DEFINE(14, 16, 0); + +static const struct iio_chan_spec ad4088_channel = AD4080_CHANNEL_DEFINE(14, 16, 0); + +static const struct iio_chan_spec ad4880_channels[] = { + AD4880_CHANNEL_DEFINE(20, 32, 0), + AD4880_CHANNEL_DEFINE(20, 32, 1), +}; + +static const struct iio_chan_spec ad4883_channels[] = { + AD4880_CHANNEL_DEFINE(16, 16, 0), + AD4880_CHANNEL_DEFINE(16, 16, 1), +}; + +static const struct iio_chan_spec ad4884_channels[] = { + AD4880_CHANNEL_DEFINE(16, 16, 0), + AD4880_CHANNEL_DEFINE(16, 16, 1), +}; + +static const struct ad4080_chip_info ad4080_chip_info = { + .name = "ad4080", + .product_id = AD4080_CHIP_ID, + .scale_table = ad4080_scale_table, + .num_scales = ARRAY_SIZE(ad4080_scale_table), + .num_channels = 1, + .channels = &ad4080_channel, + .lvds_cnv_clk_cnt_max = AD4080_LVDS_CNV_CLK_CNT_MAX, +}; + +static const struct ad4080_chip_info ad4081_chip_info = { + .name = "ad4081", + .product_id = AD4081_CHIP_ID, + .scale_table = ad4080_scale_table, + .num_scales = ARRAY_SIZE(ad4080_scale_table), + .num_channels = 1, + .channels = &ad4081_channel, + .lvds_cnv_clk_cnt_max = 2, +}; + +static const struct ad4080_chip_info ad4082_chip_info = { + .name = "ad4082", + .product_id = AD4082_CHIP_ID, + .scale_table = ad4080_scale_table, + .num_scales = ARRAY_SIZE(ad4080_scale_table), + .num_channels = 1, + .channels = &ad4082_channel, + .lvds_cnv_clk_cnt_max = 8, +}; + +static const struct ad4080_chip_info ad4083_chip_info = { + .name = "ad4083", + .product_id = AD4083_CHIP_ID, + .scale_table = ad4080_scale_table, + .num_scales = ARRAY_SIZE(ad4080_scale_table), + .num_channels = 1, + .channels = &ad4083_channel, + .lvds_cnv_clk_cnt_max = 5, +}; + +static const struct ad4080_chip_info ad4084_chip_info = { + .name = "ad4084", + .product_id = AD4084_CHIP_ID, + .scale_table = ad4080_scale_table, + .num_scales = ARRAY_SIZE(ad4080_scale_table), + .num_channels = 1, + .channels = &ad4084_channel, + .lvds_cnv_clk_cnt_max = 2, +}; + +static const struct ad4080_chip_info ad4085_chip_info = { + .name = "ad4085", + .product_id = AD4085_CHIP_ID, + .scale_table = ad4080_scale_table, + .num_scales = ARRAY_SIZE(ad4080_scale_table), + .num_channels = 1, + .channels = &ad4085_channel, + .lvds_cnv_clk_cnt_max = 8, +}; + +static const struct ad4080_chip_info ad4086_chip_info = { + .name = "ad4086", + .product_id = AD4086_CHIP_ID, + .scale_table = ad4080_scale_table, + .num_scales = ARRAY_SIZE(ad4080_scale_table), + .num_channels = 1, + .channels = &ad4086_channel, + .lvds_cnv_clk_cnt_max = 4, +}; + +static const struct ad4080_chip_info ad4087_chip_info = { + .name = "ad4087", + .product_id = AD4087_CHIP_ID, + .scale_table = ad4080_scale_table, + .num_scales = ARRAY_SIZE(ad4080_scale_table), + .num_channels = 1, + .channels = &ad4087_channel, + .lvds_cnv_clk_cnt_max = 1, +}; + +static const struct ad4080_chip_info ad4088_chip_info = { + .name = "ad4088", + .product_id = AD4088_CHIP_ID, + .scale_table = ad4080_scale_table, + .num_scales = ARRAY_SIZE(ad4080_scale_table), + .num_channels = 1, + .channels = &ad4088_channel, + .lvds_cnv_clk_cnt_max = 8, +}; + +static const struct ad4080_chip_info ad4880_chip_info = { + .name = "ad4880", + .product_id = AD4880_CHIP_ID, + .scale_table = ad4080_scale_table, + .num_scales = ARRAY_SIZE(ad4080_scale_table), + .num_channels = 2, + .channels = ad4880_channels, + .lvds_cnv_clk_cnt_max = AD4080_LVDS_CNV_CLK_CNT_MAX, +}; + +static const struct ad4080_chip_info ad4883_chip_info = { + .name = "ad4883", + .product_id = AD4883_CHIP_ID, + .scale_table = ad4080_scale_table, + .num_scales = ARRAY_SIZE(ad4080_scale_table), + .num_channels = 2, + .channels = ad4883_channels, + .lvds_cnv_clk_cnt_max = 5, +}; + +static const struct ad4080_chip_info ad4884_chip_info = { + .name = "ad4884", + .product_id = AD4884_CHIP_ID, + .scale_table = ad4080_scale_table, + .num_scales = ARRAY_SIZE(ad4080_scale_table), + .num_channels = 2, + .channels = ad4884_channels, + .lvds_cnv_clk_cnt_max = 2, +}; + +static int ad4080_setup_channel(struct ad4080_state *st, unsigned int ch) +{ + struct device *dev = regmap_get_device(st->regmap[ch]); + __le16 id_le; + u16 id; + int ret; + + ret = regmap_write(st->regmap[ch], AD4080_REG_INTERFACE_CONFIG_A, + AD4080_INTERFACE_CONFIG_A_SW_RESET); + if (ret) + return ret; + + ret = regmap_write(st->regmap[ch], AD4080_REG_INTERFACE_CONFIG_A, + AD4080_INTERFACE_CONFIG_A_SDO_ENABLE); + if (ret) + return ret; + + ret = regmap_bulk_read(st->regmap[ch], AD4080_REG_PRODUCT_ID_L, &id_le, + sizeof(id_le)); + if (ret) + return ret; + + id = le16_to_cpu(id_le); + if (id != st->info->product_id) + dev_info(dev, "Unrecognized CHIP_ID 0x%X\n", id); + + ret = regmap_set_bits(st->regmap[ch], AD4080_REG_GPIO_CONFIG_A, + AD4080_GPIO_CONFIG_A_GPO_1_EN); + if (ret) + return ret; + + ret = regmap_write(st->regmap[ch], AD4080_REG_GPIO_CONFIG_B, + FIELD_PREP(AD4080_GPIO_CONFIG_B_GPIO_1_SEL_MSK, + AD4080_GPIO_CONFIG_B_GPIO_FILTER_RES_RDY)); + if (ret) + return ret; + + ret = iio_backend_num_lanes_set(st->back[ch], st->num_lanes); + if (ret) + return ret; + + ret = iio_backend_data_size_set(st->back[ch], + st->info->channels[0].scan_type.realbits); + if (ret) + return ret; + + if (!st->lvds_cnv_en) + return 0; + + /* Set maximum LVDS Data Transfer Latency */ + ret = regmap_update_bits(st->regmap[ch], + AD4080_REG_ADC_DATA_INTF_CONFIG_B, + AD4080_ADC_DATA_INTF_CONFIG_B_LVDS_CNV_CLK_CNT_MSK, + FIELD_PREP(AD4080_ADC_DATA_INTF_CONFIG_B_LVDS_CNV_CLK_CNT_MSK, + st->info->lvds_cnv_clk_cnt_max)); + if (ret) + return ret; + + if (st->num_lanes > 1) { + ret = regmap_set_bits(st->regmap[ch], AD4080_REG_ADC_DATA_INTF_CONFIG_A, + AD4080_ADC_DATA_INTF_CONFIG_A_SPI_LVDS_LANES); + if (ret) + return ret; + } + + ret = regmap_set_bits(st->regmap[ch], + AD4080_REG_ADC_DATA_INTF_CONFIG_B, + AD4080_ADC_DATA_INTF_CONFIG_B_LVDS_CNV_EN); + if (ret) + return ret; + + return ad4080_lvds_sync_write(st, ch); +} + +static int ad4080_setup(struct iio_dev *indio_dev) +{ + struct ad4080_state *st = iio_priv(indio_dev); + int ret; + + for (unsigned int ch = 0; ch < st->info->num_channels; ch++) { + ret = ad4080_setup_channel(st, ch); + if (ret) + return ret; + } + + return 0; +} + +static int ad4080_properties_parse(struct ad4080_state *st, + struct device *dev) +{ + + st->lvds_cnv_en = device_property_read_bool(dev, "adi,lvds-cnv-enable"); + + st->num_lanes = 1; + device_property_read_u32(dev, "adi,num-lanes", &st->num_lanes); + if (!st->num_lanes || st->num_lanes > 2) + return dev_err_probe(dev, -EINVAL, + "Invalid 'adi,num-lanes' value: %u", + st->num_lanes); + + return 0; +} + +static int ad4080_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct device *dev = &spi->dev; + struct ad4080_state *st; + struct clk *clk; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + ret = devm_regulator_bulk_get_enable(dev, + ARRAY_SIZE(ad4080_power_supplies), + ad4080_power_supplies); + if (ret) + return dev_err_probe(dev, ret, + "failed to get and enable supplies\n"); + + /* Setup primary SPI device (channel 0) */ + st->spi[0] = spi; + st->regmap[0] = devm_regmap_init_spi(spi, &ad4080_regmap_config); + if (IS_ERR(st->regmap[0])) + return PTR_ERR(st->regmap[0]); + + st->info = spi_get_device_match_data(spi); + if (!st->info) + return -ENODEV; + + /* Setup ancillary SPI devices for additional channels */ + for (unsigned int ch = 1; ch < st->info->num_channels; ch++) { + st->spi[ch] = devm_spi_new_ancillary_device(spi, spi_get_chipselect(spi, ch)); + if (IS_ERR(st->spi[ch])) + return dev_err_probe(dev, PTR_ERR(st->spi[ch]), + "failed to register ancillary device\n"); + + st->regmap[ch] = devm_regmap_init_spi(st->spi[ch], &ad4080_regmap_config); + if (IS_ERR(st->regmap[ch])) + return PTR_ERR(st->regmap[ch]); + } + + ret = devm_mutex_init(dev, &st->lock); + 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 = st->info->num_channels > 1 ? + &ad4880_iio_info : &ad4080_iio_info; + + ret = ad4080_properties_parse(st, dev); + if (ret) + return ret; + + clk = devm_clk_get_enabled(&spi->dev, "cnv"); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + st->clk_rate = clk_get_rate(clk); + + /* Get backends for all channels */ + for (unsigned int ch = 0; ch < st->info->num_channels; ch++) { + st->back[ch] = devm_iio_backend_get_by_index(dev, ch); + if (IS_ERR(st->back[ch])) + return PTR_ERR(st->back[ch]); + + ret = devm_iio_backend_enable(dev, st->back[ch]); + if (ret) + return ret; + } + + /* + * Request buffer from the first backend only. For multi-channel + * devices (e.g., AD4880), the FPGA uses two axi_ad408x IP instances + * (one per ADC channel) whose outputs are combined by a packer block + * that interleaves all channel data into a single DMA stream routed + * through the first backend's clock domain. + */ + ret = devm_iio_backend_request_buffer(dev, st->back[0], indio_dev); + if (ret) + return ret; + + ret = ad4080_setup(indio_dev); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id ad4080_id[] = { + { .name = "ad4080", .driver_data = (kernel_ulong_t)&ad4080_chip_info }, + { .name = "ad4081", .driver_data = (kernel_ulong_t)&ad4081_chip_info }, + { .name = "ad4082", .driver_data = (kernel_ulong_t)&ad4082_chip_info }, + { .name = "ad4083", .driver_data = (kernel_ulong_t)&ad4083_chip_info }, + { .name = "ad4084", .driver_data = (kernel_ulong_t)&ad4084_chip_info }, + { .name = "ad4085", .driver_data = (kernel_ulong_t)&ad4085_chip_info }, + { .name = "ad4086", .driver_data = (kernel_ulong_t)&ad4086_chip_info }, + { .name = "ad4087", .driver_data = (kernel_ulong_t)&ad4087_chip_info }, + { .name = "ad4088", .driver_data = (kernel_ulong_t)&ad4088_chip_info }, + { .name = "ad4880", .driver_data = (kernel_ulong_t)&ad4880_chip_info }, + { .name = "ad4883", .driver_data = (kernel_ulong_t)&ad4883_chip_info }, + { .name = "ad4884", .driver_data = (kernel_ulong_t)&ad4884_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad4080_id); + +static const struct of_device_id ad4080_of_match[] = { + { .compatible = "adi,ad4080", &ad4080_chip_info }, + { .compatible = "adi,ad4081", &ad4081_chip_info }, + { .compatible = "adi,ad4082", &ad4082_chip_info }, + { .compatible = "adi,ad4083", &ad4083_chip_info }, + { .compatible = "adi,ad4084", &ad4084_chip_info }, + { .compatible = "adi,ad4085", &ad4085_chip_info }, + { .compatible = "adi,ad4086", &ad4086_chip_info }, + { .compatible = "adi,ad4087", &ad4087_chip_info }, + { .compatible = "adi,ad4088", &ad4088_chip_info }, + { .compatible = "adi,ad4880", &ad4880_chip_info }, + { .compatible = "adi,ad4883", &ad4883_chip_info }, + { .compatible = "adi,ad4884", &ad4884_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad4080_of_match); + +static struct spi_driver ad4080_driver = { + .driver = { + .name = "ad4080", + .of_match_table = ad4080_of_match, + }, + .probe = ad4080_probe, + .id_table = ad4080_id, +}; +module_spi_driver(ad4080_driver); + +MODULE_AUTHOR("Antoniu Miclaus + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define AD4130_NAME "ad4130" + +#define AD4130_COMMS_READ_MASK BIT(6) + +#define AD4130_STATUS_REG 0x00 + +#define AD4130_ADC_CONTROL_REG 0x01 +#define AD4130_ADC_CONTROL_BIPOLAR_MASK BIT(14) +#define AD4130_ADC_CONTROL_INT_REF_VAL_MASK BIT(13) +#define AD4130_ADC_CONTROL_CONT_READ_MASK BIT(11) +#define AD4130_INT_REF_2_5V 2500000 +#define AD4130_INT_REF_1_25V 1250000 +#define AD4130_ADC_CONTROL_CSB_EN_MASK BIT(9) +#define AD4130_ADC_CONTROL_INT_REF_EN_MASK BIT(8) +#define AD4130_ADC_CONTROL_MODE_MASK GENMASK(5, 2) +#define AD4130_ADC_CONTROL_MCLK_SEL_MASK GENMASK(1, 0) +#define AD4130_MCLK_FREQ_76_8KHZ 76800 +#define AD4130_MCLK_FREQ_153_6KHZ 153600 + +#define AD4130_DATA_REG 0x02 + +#define AD4130_IO_CONTROL_REG 0x03 +#define AD4130_IO_CONTROL_INT_PIN_SEL_MASK GENMASK(9, 8) +#define AD4130_IO_CONTROL_GPIO_DATA_MASK GENMASK(7, 4) +#define AD4130_4_IO_CONTROL_GPIO_DATA_MASK GENMASK(7, 6) +#define AD4130_IO_CONTROL_GPIO_CTRL_MASK GENMASK(3, 0) +#define AD4130_4_IO_CONTROL_GPIO_CTRL_MASK GENMASK(3, 2) + +#define AD4130_VBIAS_REG 0x04 + +#define AD4130_ID_REG 0x05 + +#define AD4130_ERROR_REG 0x06 + +#define AD4130_ERROR_EN_REG 0x07 + +#define AD4130_MCLK_COUNT_REG 0x08 + +#define AD4130_CHANNEL_X_REG(x) (0x09 + (x)) +#define AD4130_CHANNEL_EN_MASK BIT(23) +#define AD4130_CHANNEL_SETUP_MASK GENMASK(22, 20) +#define AD4130_CHANNEL_AINP_MASK GENMASK(17, 13) +#define AD4130_CHANNEL_AINM_MASK GENMASK(12, 8) +#define AD4130_CHANNEL_IOUT1_MASK GENMASK(7, 4) +#define AD4130_CHANNEL_IOUT2_MASK GENMASK(3, 0) + +#define AD4130_CONFIG_X_REG(x) (0x19 + (x)) +#define AD4130_CONFIG_IOUT1_VAL_MASK GENMASK(15, 13) +#define AD4130_CONFIG_IOUT2_VAL_MASK GENMASK(12, 10) +#define AD4130_CONFIG_BURNOUT_MASK GENMASK(9, 8) +#define AD4130_CONFIG_REF_BUFP_MASK BIT(7) +#define AD4130_CONFIG_REF_BUFM_MASK BIT(6) +#define AD4130_CONFIG_REF_SEL_MASK GENMASK(5, 4) +#define AD4130_CONFIG_PGA_MASK GENMASK(3, 1) + +#define AD4130_FILTER_X_REG(x) (0x21 + (x)) +#define AD4130_FILTER_MODE_MASK GENMASK(15, 12) +#define AD4130_FILTER_SELECT_MASK GENMASK(10, 0) +#define AD4130_FILTER_SELECT_MIN 1 + +#define AD4130_OFFSET_X_REG(x) (0x29 + (x)) + +#define AD4130_GAIN_X_REG(x) (0x31 + (x)) + +#define AD4130_MISC_REG 0x39 + +#define AD4130_FIFO_CONTROL_REG 0x3a +#define AD4130_FIFO_CONTROL_HEADER_MASK BIT(18) +#define AD4130_FIFO_CONTROL_MODE_MASK GENMASK(17, 16) +#define AD4130_FIFO_CONTROL_WM_INT_EN_MASK BIT(9) +#define AD4130_FIFO_CONTROL_WM_MASK GENMASK(7, 0) +#define AD4130_WATERMARK_256 0 + +#define AD4130_FIFO_STATUS_REG 0x3b + +#define AD4130_FIFO_THRESHOLD_REG 0x3c + +#define AD4130_FIFO_DATA_REG 0x3d +#define AD4130_FIFO_SIZE 256 +#define AD4130_FIFO_MAX_SAMPLE_SIZE 3 + +#define AD4130_MAX_ANALOG_PINS 16 +#define AD4130_MAX_CHANNELS 16 +#define AD4130_MAX_DIFF_INPUTS 30 +#define AD4130_MAX_GPIOS 4 +#define AD4130_MAX_ODR 2400 +#define AD4130_MAX_PGA 8 +#define AD4130_MAX_SETUPS 8 + +#define AD4130_AIN2_P1 0x2 +#define AD4130_AIN3_P2 0x3 + +#define AD4130_RESET_BUF_SIZE 8 +#define AD4130_RESET_SLEEP_US (160 * MICRO / AD4130_MCLK_FREQ_76_8KHZ) + +#define AD4130_INVALID_SLOT -1 + +static const unsigned int ad4129_reg_size[] = { + [AD4130_STATUS_REG] = 1, + [AD4130_ADC_CONTROL_REG] = 2, + [AD4130_DATA_REG] = 2, + [AD4130_IO_CONTROL_REG] = 2, + [AD4130_VBIAS_REG] = 2, + [AD4130_ID_REG] = 1, + [AD4130_ERROR_REG] = 2, + [AD4130_ERROR_EN_REG] = 2, + [AD4130_MCLK_COUNT_REG] = 1, + [AD4130_CHANNEL_X_REG(0) ... AD4130_CHANNEL_X_REG(AD4130_MAX_CHANNELS - 1)] = 3, + [AD4130_CONFIG_X_REG(0) ... AD4130_CONFIG_X_REG(AD4130_MAX_SETUPS - 1)] = 2, + [AD4130_FILTER_X_REG(0) ... AD4130_FILTER_X_REG(AD4130_MAX_SETUPS - 1)] = 3, + [AD4130_OFFSET_X_REG(0) ... AD4130_OFFSET_X_REG(AD4130_MAX_SETUPS - 1)] = 2, + [AD4130_GAIN_X_REG(0) ... AD4130_GAIN_X_REG(AD4130_MAX_SETUPS - 1)] = 2, + [AD4130_MISC_REG] = 2, + [AD4130_FIFO_CONTROL_REG] = 3, + [AD4130_FIFO_STATUS_REG] = 1, + [AD4130_FIFO_THRESHOLD_REG] = 3, + [AD4130_FIFO_DATA_REG] = 2, +}; + +static const unsigned int ad4130_reg_size[] = { + [AD4130_STATUS_REG] = 1, + [AD4130_ADC_CONTROL_REG] = 2, + [AD4130_DATA_REG] = 3, + [AD4130_IO_CONTROL_REG] = 2, + [AD4130_VBIAS_REG] = 2, + [AD4130_ID_REG] = 1, + [AD4130_ERROR_REG] = 2, + [AD4130_ERROR_EN_REG] = 2, + [AD4130_MCLK_COUNT_REG] = 1, + [AD4130_CHANNEL_X_REG(0) ... AD4130_CHANNEL_X_REG(AD4130_MAX_CHANNELS - 1)] = 3, + [AD4130_CONFIG_X_REG(0) ... AD4130_CONFIG_X_REG(AD4130_MAX_SETUPS - 1)] = 2, + [AD4130_FILTER_X_REG(0) ... AD4130_FILTER_X_REG(AD4130_MAX_SETUPS - 1)] = 3, + [AD4130_OFFSET_X_REG(0) ... AD4130_OFFSET_X_REG(AD4130_MAX_SETUPS - 1)] = 3, + [AD4130_GAIN_X_REG(0) ... AD4130_GAIN_X_REG(AD4130_MAX_SETUPS - 1)] = 3, + [AD4130_MISC_REG] = 2, + [AD4130_FIFO_CONTROL_REG] = 3, + [AD4130_FIFO_STATUS_REG] = 1, + [AD4130_FIFO_THRESHOLD_REG] = 3, + [AD4130_FIFO_DATA_REG] = 3, +}; + +static const unsigned int ad4131_reg_size[] = { + [AD4130_STATUS_REG] = 1, + [AD4130_ADC_CONTROL_REG] = 2, + [AD4130_DATA_REG] = 2, + [AD4130_IO_CONTROL_REG] = 2, + [AD4130_VBIAS_REG] = 2, + [AD4130_ID_REG] = 1, + [AD4130_ERROR_REG] = 2, + [AD4130_ERROR_EN_REG] = 2, + [AD4130_MCLK_COUNT_REG] = 1, + [AD4130_CHANNEL_X_REG(0) ... AD4130_CHANNEL_X_REG(AD4130_MAX_CHANNELS - 1)] = 3, + [AD4130_CONFIG_X_REG(0) ... AD4130_CONFIG_X_REG(AD4130_MAX_SETUPS - 1)] = 2, + [AD4130_FILTER_X_REG(0) ... AD4130_FILTER_X_REG(AD4130_MAX_SETUPS - 1)] = 3, + [AD4130_OFFSET_X_REG(0) ... AD4130_OFFSET_X_REG(AD4130_MAX_SETUPS - 1)] = 2, + [AD4130_GAIN_X_REG(0) ... AD4130_GAIN_X_REG(AD4130_MAX_SETUPS - 1)] = 2, + [AD4130_MISC_REG] = 2, +}; + +enum ad4130_int_ref_val { + AD4130_INT_REF_VAL_2_5V, + AD4130_INT_REF_VAL_1_25V, +}; + +enum ad4130_mclk_sel { + AD4130_MCLK_76_8KHZ, + AD4130_MCLK_76_8KHZ_OUT, + AD4130_MCLK_76_8KHZ_EXT, + AD4130_MCLK_153_6KHZ_EXT, +}; + +enum ad4130_int_pin_sel { + AD4130_INT_PIN_INT, + AD4130_INT_PIN_CLK, + AD4130_INT_PIN_P2, + AD4130_INT_PIN_DOUT, +}; + +enum ad4130_iout { + AD4130_IOUT_OFF, + AD4130_IOUT_10000NA, + AD4130_IOUT_20000NA, + AD4130_IOUT_50000NA, + AD4130_IOUT_100000NA, + AD4130_IOUT_150000NA, + AD4130_IOUT_200000NA, + AD4130_IOUT_100NA, + AD4130_IOUT_MAX +}; + +enum ad4130_burnout { + AD4130_BURNOUT_OFF, + AD4130_BURNOUT_500NA, + AD4130_BURNOUT_2000NA, + AD4130_BURNOUT_4000NA, + AD4130_BURNOUT_MAX +}; + +enum ad4130_ref_sel { + AD4130_REF_REFIN1, + AD4130_REF_REFIN2, + AD4130_REF_REFOUT_AVSS, + AD4130_REF_AVDD_AVSS, + AD4130_REF_SEL_MAX +}; + +enum ad4130_fifo_mode { + AD4130_FIFO_MODE_DISABLED = 0b00, + AD4130_FIFO_MODE_WM = 0b01, +}; + +enum ad4130_mode { + AD4130_MODE_CONTINUOUS = 0b0000, + AD4130_MODE_IDLE = 0b0100, +}; + +enum ad4130_filter_type { + AD4130_FILTER_SINC4, + AD4130_FILTER_SINC4_SINC1, + AD4130_FILTER_SINC3, + AD4130_FILTER_SINC3_REJ60, + AD4130_FILTER_SINC3_SINC1, + AD4130_FILTER_SINC3_PF1, + AD4130_FILTER_SINC3_PF2, + AD4130_FILTER_SINC3_PF3, + AD4130_FILTER_SINC3_PF4, +}; + +enum ad4130_pin_function { + AD4130_PIN_FN_NONE, + AD4130_PIN_FN_SPECIAL = BIT(0), + AD4130_PIN_FN_DIFF = BIT(1), + AD4130_PIN_FN_EXCITATION = BIT(2), + AD4130_PIN_FN_VBIAS = BIT(3), +}; + +/* Pin mapping for AIN0..AIN7, VBIAS_0..VBIAS_7 */ +static const u8 ad4130_4_pin_map[] = { + 0x00, 0x01, 0x04, 0x05, 0x0A, 0x0B, 0x0E, 0x0F, /* 0 - 7 */ +}; + +/* Pin mapping for AIN0..AIN15, VBIAS_0..VBIAS_15 */ +static const u8 ad4130_8_pin_map[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, /* 0 - 7 */ + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, /* 8 - 15 */ +}; + +struct ad4130_chip_info { + const char *name; + unsigned int max_analog_pins; + unsigned int num_gpios; + const struct iio_info *info; + const unsigned int *reg_size; + const unsigned int reg_size_length; + const u8 *pin_map; + bool has_fifo; +}; + +/* + * If you make adaptations in this struct, you most likely also have to adapt + * ad4130_setup_info_eq(), too. + */ +struct ad4130_setup_info { + unsigned int iout0_val; + unsigned int iout1_val; + unsigned int burnout; + unsigned int pga; + unsigned int fs; + u32 ref_sel; + enum ad4130_filter_type filter_type; + bool ref_bufp; + bool ref_bufm; +}; + +struct ad4130_slot_info { + struct ad4130_setup_info setup; + unsigned int enabled_channels; + unsigned int channels; +}; + +struct ad4130_chan_info { + struct ad4130_setup_info setup; + u32 iout0; + u32 iout1; + int slot; + bool enabled; + bool initialized; +}; + +struct ad4130_filter_config { + enum ad4130_filter_type filter_type; + unsigned int odr_div; + unsigned int fs_max; + enum iio_available_type samp_freq_avail_type; + int samp_freq_avail_len; + int samp_freq_avail[3][2]; +}; + +struct ad4130_state { + struct regmap *regmap; + struct spi_device *spi; + struct clk *mclk; + const struct ad4130_chip_info *chip_info; + struct regulator_bulk_data regulators[4]; + u32 irq_trigger; + u32 inv_irq_trigger; + + /* + * Synchronize access to members the of driver state, and ensure + * atomicity of consecutive regmap operations. + */ + struct mutex lock; + struct completion completion; + + struct iio_chan_spec chans[AD4130_MAX_CHANNELS]; + struct ad4130_chan_info chans_info[AD4130_MAX_CHANNELS]; + struct ad4130_slot_info slots_info[AD4130_MAX_SETUPS]; + enum ad4130_pin_function pins_fn[AD4130_MAX_ANALOG_PINS]; + u32 vbias_pins[AD4130_MAX_ANALOG_PINS]; + u32 num_vbias_pins; + int scale_tbls[AD4130_REF_SEL_MAX][AD4130_MAX_PGA][2]; + struct gpio_chip gc; + struct clk_hw int_clk_hw; + + u32 int_pin_sel; + u32 int_ref_uv; + u32 mclk_sel; + bool int_ref_en; + bool bipolar; + bool buffer_wait_for_irq; + + unsigned int num_enabled_channels; + unsigned int effective_watermark; + unsigned int watermark; + + struct spi_message fifo_msg; + struct spi_transfer fifo_xfer[2]; + struct iio_trigger *trig; + + /* + * DMA (thus cache coherency maintenance) requires any transfer + * buffers to live in their own cache lines. As the use of these + * buffers is synchronous, all of the buffers used for DMA in this + * driver may share a cache line. + */ + u8 reset_buf[AD4130_RESET_BUF_SIZE] __aligned(IIO_DMA_MINALIGN); + u8 reg_write_tx_buf[4]; + u8 reg_read_tx_buf[1]; + u8 reg_read_rx_buf[3]; + union { + struct { + u8 fifo_tx_buf[2]; + u8 fifo_rx_buf[AD4130_FIFO_SIZE * AD4130_FIFO_MAX_SAMPLE_SIZE]; + }; + IIO_DECLARE_BUFFER_WITH_TS(u32, scan_channels, AD4130_MAX_CHANNELS); + }; +}; + +static const char * const ad4130_int_pin_names[] = { + [AD4130_INT_PIN_INT] = "int", + [AD4130_INT_PIN_CLK] = "clk", + [AD4130_INT_PIN_P2] = "p2", + [AD4130_INT_PIN_DOUT] = "dout", +}; + +static const unsigned int ad4130_iout_current_na_tbl[AD4130_IOUT_MAX] = { + [AD4130_IOUT_OFF] = 0, + [AD4130_IOUT_100NA] = 100, + [AD4130_IOUT_10000NA] = 10000, + [AD4130_IOUT_20000NA] = 20000, + [AD4130_IOUT_50000NA] = 50000, + [AD4130_IOUT_100000NA] = 100000, + [AD4130_IOUT_150000NA] = 150000, + [AD4130_IOUT_200000NA] = 200000, +}; + +static const unsigned int ad4130_burnout_current_na_tbl[AD4130_BURNOUT_MAX] = { + [AD4130_BURNOUT_OFF] = 0, + [AD4130_BURNOUT_500NA] = 500, + [AD4130_BURNOUT_2000NA] = 2000, + [AD4130_BURNOUT_4000NA] = 4000, +}; + +#define AD4130_VARIABLE_ODR_CONFIG(_filter_type, _odr_div, _fs_max) \ +{ \ + .filter_type = (_filter_type), \ + .odr_div = (_odr_div), \ + .fs_max = (_fs_max), \ + .samp_freq_avail_type = IIO_AVAIL_RANGE, \ + .samp_freq_avail = { \ + { AD4130_MAX_ODR, (_odr_div) * (_fs_max) }, \ + { AD4130_MAX_ODR, (_odr_div) * (_fs_max) }, \ + { AD4130_MAX_ODR, (_odr_div) }, \ + }, \ +} + +#define AD4130_FIXED_ODR_CONFIG(_filter_type, _odr_div) \ +{ \ + .filter_type = (_filter_type), \ + .odr_div = (_odr_div), \ + .fs_max = AD4130_FILTER_SELECT_MIN, \ + .samp_freq_avail_type = IIO_AVAIL_LIST, \ + .samp_freq_avail_len = 1, \ + .samp_freq_avail = { \ + { AD4130_MAX_ODR, (_odr_div) }, \ + }, \ +} + +static const struct ad4130_filter_config ad4130_filter_configs[] = { + AD4130_VARIABLE_ODR_CONFIG(AD4130_FILTER_SINC4, 1, 10), + AD4130_VARIABLE_ODR_CONFIG(AD4130_FILTER_SINC4_SINC1, 11, 10), + AD4130_VARIABLE_ODR_CONFIG(AD4130_FILTER_SINC3, 1, 2047), + AD4130_VARIABLE_ODR_CONFIG(AD4130_FILTER_SINC3_REJ60, 1, 2047), + AD4130_VARIABLE_ODR_CONFIG(AD4130_FILTER_SINC3_SINC1, 10, 2047), + AD4130_FIXED_ODR_CONFIG(AD4130_FILTER_SINC3_PF1, 92), + AD4130_FIXED_ODR_CONFIG(AD4130_FILTER_SINC3_PF2, 100), + AD4130_FIXED_ODR_CONFIG(AD4130_FILTER_SINC3_PF3, 124), + AD4130_FIXED_ODR_CONFIG(AD4130_FILTER_SINC3_PF4, 148), +}; + +static const char * const ad4130_filter_types_str[] = { + [AD4130_FILTER_SINC4] = "sinc4", + [AD4130_FILTER_SINC4_SINC1] = "sinc4+sinc1", + [AD4130_FILTER_SINC3] = "sinc3", + [AD4130_FILTER_SINC3_REJ60] = "sinc3+rej60", + [AD4130_FILTER_SINC3_SINC1] = "sinc3+sinc1", + [AD4130_FILTER_SINC3_PF1] = "sinc3+pf1", + [AD4130_FILTER_SINC3_PF2] = "sinc3+pf2", + [AD4130_FILTER_SINC3_PF3] = "sinc3+pf3", + [AD4130_FILTER_SINC3_PF4] = "sinc3+pf4", +}; + +static int ad4130_get_reg_size(struct ad4130_state *st, unsigned int reg, + unsigned int *size) +{ + if (reg >= st->chip_info->reg_size_length) + return -EINVAL; + + *size = st->chip_info->reg_size[reg]; + + return 0; +} + +static unsigned int ad4130_data_reg_size(struct ad4130_state *st) +{ + unsigned int data_reg_size; + int ret; + + ret = ad4130_get_reg_size(st, AD4130_DATA_REG, &data_reg_size); + if (ret) + return 0; + + return data_reg_size; +} + +static unsigned int ad4130_resolution(struct ad4130_state *st) +{ + return ad4130_data_reg_size(st) * BITS_PER_BYTE; +} + +static int ad4130_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct ad4130_state *st = context; + unsigned int size; + int ret; + + ret = ad4130_get_reg_size(st, reg, &size); + if (ret) + return ret; + + st->reg_write_tx_buf[0] = reg; + + switch (size) { + case 3: + put_unaligned_be24(val, &st->reg_write_tx_buf[1]); + break; + case 2: + put_unaligned_be16(val, &st->reg_write_tx_buf[1]); + break; + case 1: + st->reg_write_tx_buf[1] = val; + break; + default: + return -EINVAL; + } + + return spi_write(st->spi, st->reg_write_tx_buf, size + 1); +} + +static int ad4130_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + struct ad4130_state *st = context; + struct spi_transfer t[] = { + { + .tx_buf = st->reg_read_tx_buf, + .len = sizeof(st->reg_read_tx_buf), + }, + { + .rx_buf = st->reg_read_rx_buf, + }, + }; + unsigned int size; + int ret; + + ret = ad4130_get_reg_size(st, reg, &size); + if (ret) + return ret; + + st->reg_read_tx_buf[0] = AD4130_COMMS_READ_MASK | reg; + t[1].len = size; + + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); + if (ret) + return ret; + + switch (size) { + case 3: + *val = get_unaligned_be24(st->reg_read_rx_buf); + break; + case 2: + *val = get_unaligned_be16(st->reg_read_rx_buf); + break; + case 1: + *val = st->reg_read_rx_buf[0]; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct regmap_config ad4130_regmap_config = { + .reg_read = ad4130_reg_read, + .reg_write = ad4130_reg_write, +}; + +static int ad4130_gpio_init_valid_mask(struct gpio_chip *gc, + unsigned long *valid_mask, + unsigned int ngpios) +{ + struct ad4130_state *st = gpiochip_get_data(gc); + unsigned int i; + + /* + * Output-only GPIO functionality is available on pins AIN2 through + * AIN5 for some parts and AIN2 through AIN3 for others. If these pins + * are used for anything else, do not expose them. + */ + for (i = 0; i < ngpios; i++) { + unsigned int pin = i + AD4130_AIN2_P1; + bool valid = st->pins_fn[pin] == AD4130_PIN_FN_NONE; + + __assign_bit(i, valid_mask, valid); + } + + return 0; +} + +static int ad4130_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + return GPIO_LINE_DIRECTION_OUT; +} + +static int ad4130_gpio_set(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct ad4130_state *st = gpiochip_get_data(gc); + unsigned int mask; + + if (st->chip_info->num_gpios == AD4130_MAX_GPIOS) + mask = FIELD_PREP(AD4130_IO_CONTROL_GPIO_DATA_MASK, + BIT(offset)); + else + mask = FIELD_PREP(AD4130_4_IO_CONTROL_GPIO_DATA_MASK, + BIT(offset)); + + return regmap_update_bits(st->regmap, AD4130_IO_CONTROL_REG, mask, + value ? mask : 0); +} + +static int ad4130_set_mode(struct ad4130_state *st, enum ad4130_mode mode) +{ + return regmap_update_bits(st->regmap, AD4130_ADC_CONTROL_REG, + AD4130_ADC_CONTROL_MODE_MASK, + FIELD_PREP(AD4130_ADC_CONTROL_MODE_MASK, mode)); +} + +static int ad4130_set_watermark_interrupt_en(struct ad4130_state *st, bool en) +{ + return regmap_update_bits(st->regmap, AD4130_FIFO_CONTROL_REG, + AD4130_FIFO_CONTROL_WM_INT_EN_MASK, + FIELD_PREP(AD4130_FIFO_CONTROL_WM_INT_EN_MASK, en)); +} + +static unsigned int ad4130_watermark_reg_val(unsigned int val) +{ + if (val == AD4130_FIFO_SIZE) + val = AD4130_WATERMARK_256; + + return val; +} + +static int ad4130_set_fifo_mode(struct ad4130_state *st, + enum ad4130_fifo_mode mode) +{ + return regmap_update_bits(st->regmap, AD4130_FIFO_CONTROL_REG, + AD4130_FIFO_CONTROL_MODE_MASK, + FIELD_PREP(AD4130_FIFO_CONTROL_MODE_MASK, mode)); +} + +static void ad4130_push_fifo_data(struct iio_dev *indio_dev) +{ + struct ad4130_state *st = iio_priv(indio_dev); + unsigned int data_reg_size = ad4130_data_reg_size(st); + unsigned int transfer_len = st->effective_watermark * data_reg_size; + unsigned int set_size = st->num_enabled_channels * data_reg_size; + unsigned int i; + int ret; + + st->fifo_tx_buf[1] = ad4130_watermark_reg_val(st->effective_watermark); + st->fifo_xfer[1].len = transfer_len; + + ret = spi_sync(st->spi, &st->fifo_msg); + if (ret) + return; + + for (i = 0; i < transfer_len; i += set_size) + iio_push_to_buffers(indio_dev, &st->fifo_rx_buf[i]); +} + +static irqreturn_t ad4130_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ad4130_state *st = iio_priv(indio_dev); + + if (iio_buffer_enabled(indio_dev)) { + if (st->chip_info->has_fifo) + ad4130_push_fifo_data(indio_dev); + else if (st->buffer_wait_for_irq) + complete(&st->completion); + else + iio_trigger_poll(st->trig); + } else { + complete(&st->completion); + } + + return IRQ_HANDLED; +} + +static irqreturn_t ad4130_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad4130_state *st = iio_priv(indio_dev); + unsigned int data_reg_size = ad4130_data_reg_size(st); + struct spi_transfer xfer = { }; + unsigned int num_en_chn; + int ret; + + num_en_chn = bitmap_weight(indio_dev->active_scan_mask, + iio_get_masklength(indio_dev)); + xfer.rx_buf = st->scan_channels; + xfer.len = data_reg_size * num_en_chn; + ret = spi_sync_transfer(st->spi, &xfer, 1); + if (ret < 0) + goto err_out; + + iio_push_to_buffers_with_timestamp(indio_dev, &st->scan_channels, + iio_get_time_ns(indio_dev)); + +err_out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static bool ad4130_setup_info_eq(struct ad4130_setup_info *a, + struct ad4130_setup_info *b) +{ + /* + * This is just to make sure that the comparison is adapted after + * struct ad4130_setup_info was changed. + */ + static_assert(sizeof(*a) == + sizeof(struct { + unsigned int iout0_val; + unsigned int iout1_val; + unsigned int burnout; + unsigned int pga; + unsigned int fs; + u32 ref_sel; + enum ad4130_filter_type filter_type; + bool ref_bufp; + bool ref_bufm; + })); + + if (a->iout0_val != b->iout0_val || + a->iout1_val != b->iout1_val || + a->burnout != b->burnout || + a->pga != b->pga || + a->fs != b->fs || + a->ref_sel != b->ref_sel || + a->filter_type != b->filter_type || + a->ref_bufp != b->ref_bufp || + a->ref_bufm != b->ref_bufm) + return false; + + return true; +} + +static int ad4130_find_slot(struct ad4130_state *st, + struct ad4130_setup_info *target_setup_info, + unsigned int *slot, bool *overwrite) +{ + unsigned int i; + + *slot = AD4130_INVALID_SLOT; + *overwrite = false; + + for (i = 0; i < AD4130_MAX_SETUPS; i++) { + struct ad4130_slot_info *slot_info = &st->slots_info[i]; + + /* Immediately accept a matching setup info. */ + if (ad4130_setup_info_eq(target_setup_info, &slot_info->setup)) { + *slot = i; + return 0; + } + + /* Ignore all setups which are used by enabled channels. */ + if (slot_info->enabled_channels) + continue; + + /* Find the least used slot. */ + if (*slot == AD4130_INVALID_SLOT || + slot_info->channels < st->slots_info[*slot].channels) + *slot = i; + } + + if (*slot == AD4130_INVALID_SLOT) + return -EINVAL; + + *overwrite = true; + + return 0; +} + +static void ad4130_unlink_channel(struct ad4130_state *st, unsigned int channel) +{ + struct ad4130_chan_info *chan_info = &st->chans_info[channel]; + struct ad4130_slot_info *slot_info = &st->slots_info[chan_info->slot]; + + chan_info->slot = AD4130_INVALID_SLOT; + slot_info->channels--; +} + +static int ad4130_unlink_slot(struct ad4130_state *st, unsigned int slot) +{ + unsigned int i; + + for (i = 0; i < AD4130_MAX_CHANNELS; i++) { + struct ad4130_chan_info *chan_info = &st->chans_info[i]; + + if (!chan_info->initialized || chan_info->slot != slot) + continue; + + ad4130_unlink_channel(st, i); + } + + return 0; +} + +static int ad4130_link_channel_slot(struct ad4130_state *st, + unsigned int channel, unsigned int slot) +{ + struct ad4130_slot_info *slot_info = &st->slots_info[slot]; + struct ad4130_chan_info *chan_info = &st->chans_info[channel]; + int ret; + + ret = regmap_update_bits(st->regmap, AD4130_CHANNEL_X_REG(channel), + AD4130_CHANNEL_SETUP_MASK, + FIELD_PREP(AD4130_CHANNEL_SETUP_MASK, slot)); + if (ret) + return ret; + + chan_info->slot = slot; + slot_info->channels++; + + return 0; +} + +static int ad4130_write_slot_setup(struct ad4130_state *st, + unsigned int slot, + struct ad4130_setup_info *setup_info) +{ + unsigned int val; + int ret; + + val = FIELD_PREP(AD4130_CONFIG_IOUT1_VAL_MASK, setup_info->iout0_val) | + FIELD_PREP(AD4130_CONFIG_IOUT1_VAL_MASK, setup_info->iout1_val) | + FIELD_PREP(AD4130_CONFIG_BURNOUT_MASK, setup_info->burnout) | + FIELD_PREP(AD4130_CONFIG_REF_BUFP_MASK, setup_info->ref_bufp) | + FIELD_PREP(AD4130_CONFIG_REF_BUFM_MASK, setup_info->ref_bufm) | + FIELD_PREP(AD4130_CONFIG_REF_SEL_MASK, setup_info->ref_sel) | + FIELD_PREP(AD4130_CONFIG_PGA_MASK, setup_info->pga); + + ret = regmap_write(st->regmap, AD4130_CONFIG_X_REG(slot), val); + if (ret) + return ret; + + val = FIELD_PREP(AD4130_FILTER_MODE_MASK, setup_info->filter_type) | + FIELD_PREP(AD4130_FILTER_SELECT_MASK, setup_info->fs); + + ret = regmap_write(st->regmap, AD4130_FILTER_X_REG(slot), val); + if (ret) + return ret; + + memcpy(&st->slots_info[slot].setup, setup_info, sizeof(*setup_info)); + + return 0; +} + +static int ad4130_write_channel_setup(struct ad4130_state *st, + unsigned int channel, bool on_enable) +{ + struct ad4130_chan_info *chan_info = &st->chans_info[channel]; + struct ad4130_setup_info *setup_info = &chan_info->setup; + bool overwrite; + int slot; + int ret; + + /* + * The following cases need to be handled. + * + * 1. Enabled and linked channel with setup changes: + * - Find a slot. If not possible, return error. + * - Unlink channel from current slot. + * - If the slot has channels linked to it, unlink all channels, and + * write the new setup to it. + * - Link channel to new slot. + * + * 2. Soon to be enabled and unlinked channel: + * - Find a slot. If not possible, return error. + * - If the slot has channels linked to it, unlink all channels, and + * write the new setup to it. + * - Link channel to the slot. + * + * 3. Disabled and linked channel with setup changes: + * - Unlink channel from current slot. + * + * 4. Soon to be enabled and linked channel: + * 5. Disabled and unlinked channel with setup changes: + * - Do nothing. + */ + + /* Case 4 */ + if (on_enable && chan_info->slot != AD4130_INVALID_SLOT) + return 0; + + if (!on_enable && !chan_info->enabled) { + if (chan_info->slot != AD4130_INVALID_SLOT) + /* Case 3 */ + ad4130_unlink_channel(st, channel); + + /* Cases 3 & 5 */ + return 0; + } + + /* Cases 1 & 2 */ + ret = ad4130_find_slot(st, setup_info, &slot, &overwrite); + if (ret) + return ret; + + if (chan_info->slot != AD4130_INVALID_SLOT) + /* Case 1 */ + ad4130_unlink_channel(st, channel); + + if (overwrite) { + ret = ad4130_unlink_slot(st, slot); + if (ret) + return ret; + + ret = ad4130_write_slot_setup(st, slot, setup_info); + if (ret) + return ret; + } + + return ad4130_link_channel_slot(st, channel, slot); +} + +static int ad4130_set_channel_enable(struct ad4130_state *st, + unsigned int channel, bool status) +{ + struct ad4130_chan_info *chan_info = &st->chans_info[channel]; + struct ad4130_slot_info *slot_info; + int ret; + + if (chan_info->enabled == status) + return 0; + + if (status) { + ret = ad4130_write_channel_setup(st, channel, true); + if (ret) + return ret; + } + + slot_info = &st->slots_info[chan_info->slot]; + + ret = regmap_update_bits(st->regmap, AD4130_CHANNEL_X_REG(channel), + AD4130_CHANNEL_EN_MASK, + FIELD_PREP(AD4130_CHANNEL_EN_MASK, status)); + if (ret) + return ret; + + slot_info->enabled_channels += status ? 1 : -1; + chan_info->enabled = status; + + return 0; +} + +/* + * Table 58. FILTER_MODE_n bits and Filter Types of the datasheet describes + * the relation between filter mode, ODR and FS. + * + * Notice that the max ODR of each filter mode is not necessarily the + * absolute max ODR supported by the chip. + * + * The ODR divider is not explicitly specified, but it can be deduced based + * on the ODR range of each filter mode. + * + * For example, for Sinc4+Sinc1, max ODR is 218.18. That means that the + * absolute max ODR is divided by 11 to achieve the max ODR of this filter + * mode. + * + * The formulas for converting between ODR and FS for a specific filter + * mode can be deduced from the same table. + * + * Notice that FS = 1 actually means max ODR, and that ODR decreases by + * (maximum ODR / maximum FS) for each increment of FS. + * + * odr = MAX_ODR / odr_div * (1 - (fs - 1) / fs_max) <=> + * odr = MAX_ODR * (1 - (fs - 1) / fs_max) / odr_div <=> + * odr = MAX_ODR * (1 - (fs - 1) / fs_max) / odr_div <=> + * odr = MAX_ODR * (fs_max - fs + 1) / (fs_max * odr_div) + * (used in ad4130_fs_to_freq) + * + * For the opposite formula, FS can be extracted from the last one. + * + * MAX_ODR * (fs_max - fs + 1) = fs_max * odr_div * odr <=> + * fs_max - fs + 1 = fs_max * odr_div * odr / MAX_ODR <=> + * fs = 1 + fs_max - fs_max * odr_div * odr / MAX_ODR + * (used in ad4130_fs_to_freq) + */ + +static void ad4130_freq_to_fs(enum ad4130_filter_type filter_type, + int val, int val2, unsigned int *fs) +{ + const struct ad4130_filter_config *filter_config = + &ad4130_filter_configs[filter_type]; + u64 dividend, divisor; + int temp; + + dividend = filter_config->fs_max * filter_config->odr_div * + ((u64)val * NANO + val2); + divisor = (u64)AD4130_MAX_ODR * NANO; + + temp = AD4130_FILTER_SELECT_MIN + filter_config->fs_max - + DIV64_U64_ROUND_CLOSEST(dividend, divisor); + + if (temp < AD4130_FILTER_SELECT_MIN) + temp = AD4130_FILTER_SELECT_MIN; + else if (temp > filter_config->fs_max) + temp = filter_config->fs_max; + + *fs = temp; +} + +static void ad4130_fs_to_freq(enum ad4130_filter_type filter_type, + unsigned int fs, int *val, int *val2) +{ + const struct ad4130_filter_config *filter_config = + &ad4130_filter_configs[filter_type]; + unsigned int dividend, divisor; + u64 temp; + + dividend = (filter_config->fs_max - fs + AD4130_FILTER_SELECT_MIN) * + AD4130_MAX_ODR; + divisor = filter_config->fs_max * filter_config->odr_div; + + temp = div_u64((u64)dividend * NANO, divisor); + *val = div_u64_rem(temp, NANO, val2); +} + +static int ad4130_set_filter_type(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int val) +{ + struct ad4130_state *st = iio_priv(indio_dev); + unsigned int channel = chan->scan_index; + struct ad4130_chan_info *chan_info = &st->chans_info[channel]; + struct ad4130_setup_info *setup_info = &chan_info->setup; + enum ad4130_filter_type old_filter_type; + int freq_val, freq_val2; + unsigned int old_fs; + int ret = 0; + + guard(mutex)(&st->lock); + if (setup_info->filter_type == val) + return 0; + + old_fs = setup_info->fs; + old_filter_type = setup_info->filter_type; + + /* + * When switching between filter modes, try to match the ODR as + * close as possible. To do this, convert the current FS into ODR + * using the old filter mode, then convert it back into FS using + * the new filter mode. + */ + ad4130_fs_to_freq(setup_info->filter_type, setup_info->fs, + &freq_val, &freq_val2); + + ad4130_freq_to_fs(val, freq_val, freq_val2, &setup_info->fs); + + setup_info->filter_type = val; + + ret = ad4130_write_channel_setup(st, channel, false); + if (ret) { + setup_info->fs = old_fs; + setup_info->filter_type = old_filter_type; + return ret; + } + + return 0; +} + +static int ad4130_get_filter_type(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad4130_state *st = iio_priv(indio_dev); + unsigned int channel = chan->scan_index; + struct ad4130_setup_info *setup_info = &st->chans_info[channel].setup; + enum ad4130_filter_type filter_type; + + guard(mutex)(&st->lock); + filter_type = setup_info->filter_type; + + return filter_type; +} + +static const struct iio_enum ad4130_filter_type_enum = { + .items = ad4130_filter_types_str, + .num_items = ARRAY_SIZE(ad4130_filter_types_str), + .set = ad4130_set_filter_type, + .get = ad4130_get_filter_type, +}; + +static const struct iio_chan_spec_ext_info ad4130_ext_info[] = { + /* + * `filter_type` is the standardized IIO ABI for digital filtering. + * `filter_mode` is just kept for backwards compatibility. + */ + IIO_ENUM("filter_mode", IIO_SEPARATE, &ad4130_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_mode", IIO_SHARED_BY_TYPE, + &ad4130_filter_type_enum), + IIO_ENUM("filter_type", IIO_SEPARATE, &ad4130_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_TYPE, + &ad4130_filter_type_enum), + { } +}; + +static const struct iio_chan_spec ad4130_channel_template = { + .type = IIO_VOLTAGE, + .indexed = 1, + .differential = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .ext_info = ad4130_ext_info, + .scan_type = { + .sign = 'u', + .endianness = IIO_BE, + }, +}; + +static int ad4130_set_channel_pga(struct ad4130_state *st, unsigned int channel, + int val, int val2) +{ + struct ad4130_chan_info *chan_info = &st->chans_info[channel]; + struct ad4130_setup_info *setup_info = &chan_info->setup; + unsigned int pga, old_pga; + int ret; + + for (pga = 0; pga < AD4130_MAX_PGA; pga++) + if (val == st->scale_tbls[setup_info->ref_sel][pga][0] && + val2 == st->scale_tbls[setup_info->ref_sel][pga][1]) + break; + + if (pga == AD4130_MAX_PGA) + return -EINVAL; + + guard(mutex)(&st->lock); + if (pga == setup_info->pga) + return 0; + + old_pga = setup_info->pga; + setup_info->pga = pga; + + ret = ad4130_write_channel_setup(st, channel, false); + if (ret) { + setup_info->pga = old_pga; + return ret; + } + + return 0; +} + +static int ad4130_set_channel_freq(struct ad4130_state *st, + unsigned int channel, int val, int val2) +{ + struct ad4130_chan_info *chan_info = &st->chans_info[channel]; + struct ad4130_setup_info *setup_info = &chan_info->setup; + unsigned int fs, old_fs; + int ret; + + guard(mutex)(&st->lock); + old_fs = setup_info->fs; + + ad4130_freq_to_fs(setup_info->filter_type, val, val2, &fs); + + if (fs == setup_info->fs) + return 0; + + setup_info->fs = fs; + + ret = ad4130_write_channel_setup(st, channel, false); + if (ret) { + setup_info->fs = old_fs; + return ret; + } + + return 0; +} + +static int _ad4130_read_sample(struct iio_dev *indio_dev, unsigned int channel, + int *val) +{ + struct ad4130_state *st = iio_priv(indio_dev); + int ret; + + ret = ad4130_set_channel_enable(st, channel, true); + if (ret) + return ret; + + reinit_completion(&st->completion); + + ret = ad4130_set_mode(st, AD4130_MODE_CONTINUOUS); + if (ret) + return ret; + + ret = wait_for_completion_timeout(&st->completion, + msecs_to_jiffies(1000)); + if (!ret) + return -ETIMEDOUT; + + ret = ad4130_set_mode(st, AD4130_MODE_IDLE); + if (ret) + return ret; + + ret = regmap_read(st->regmap, AD4130_DATA_REG, val); + if (ret) + return ret; + + ret = ad4130_set_channel_enable(st, channel, false); + if (ret) + return ret; + + return IIO_VAL_INT; +} + +static int ad4130_read_sample(struct iio_dev *indio_dev, unsigned int channel, + int *val) +{ + struct ad4130_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->lock); + + return _ad4130_read_sample(indio_dev, channel, val); +} + +static int ad4130_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad4130_state *st = iio_priv(indio_dev); + unsigned int channel = chan->scan_index; + struct ad4130_setup_info *setup_info = &st->chans_info[channel].setup; + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad4130_read_sample(indio_dev, channel, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SCALE: { + guard(mutex)(&st->lock); + *val = st->scale_tbls[setup_info->ref_sel][setup_info->pga][0]; + *val2 = st->scale_tbls[setup_info->ref_sel][setup_info->pga][1]; + + return IIO_VAL_INT_PLUS_NANO; + } + case IIO_CHAN_INFO_OFFSET: + *val = st->bipolar ? -BIT(chan->scan_type.realbits - 1) : 0; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: { + guard(mutex)(&st->lock); + ad4130_fs_to_freq(setup_info->filter_type, setup_info->fs, + val, val2); + + return IIO_VAL_INT_PLUS_NANO; + } + default: + return -EINVAL; + } +} + +static int ad4130_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long info) +{ + struct ad4130_state *st = iio_priv(indio_dev); + unsigned int channel = chan->scan_index; + struct ad4130_setup_info *setup_info = &st->chans_info[channel].setup; + const struct ad4130_filter_config *filter_config; + + switch (info) { + case IIO_CHAN_INFO_SCALE: + *vals = (int *)st->scale_tbls[setup_info->ref_sel]; + *length = ARRAY_SIZE(st->scale_tbls[setup_info->ref_sel]) * 2; + + *type = IIO_VAL_INT_PLUS_NANO; + + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + scoped_guard(mutex, &st->lock) { + filter_config = &ad4130_filter_configs[setup_info->filter_type]; + } + + *vals = (int *)filter_config->samp_freq_avail; + *length = filter_config->samp_freq_avail_len * 2; + *type = IIO_VAL_FRACTIONAL; + + return filter_config->samp_freq_avail_type; + default: + return -EINVAL; + } +} + +static int ad4130_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long info) +{ + switch (info) { + case IIO_CHAN_INFO_SCALE: + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static int ad4130_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct ad4130_state *st = iio_priv(indio_dev); + unsigned int channel = chan->scan_index; + + switch (info) { + case IIO_CHAN_INFO_SCALE: + return ad4130_set_channel_pga(st, channel, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return ad4130_set_channel_freq(st, channel, val, val2); + default: + return -EINVAL; + } +} + +static int ad4130_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct ad4130_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); +} + +static int ad4130_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ad4130_state *st = iio_priv(indio_dev); + unsigned int channel; + unsigned int val = 0; + int ret; + + guard(mutex)(&st->lock); + + for_each_set_bit(channel, scan_mask, indio_dev->num_channels) { + ret = ad4130_set_channel_enable(st, channel, true); + if (ret) + return ret; + + val++; + } + + st->num_enabled_channels = val; + + return 0; +} + +static int ad4130_set_fifo_watermark(struct iio_dev *indio_dev, unsigned int val) +{ + struct ad4130_state *st = iio_priv(indio_dev); + unsigned int eff; + int ret; + + if (val > AD4130_FIFO_SIZE) + return -EINVAL; + + eff = val * st->num_enabled_channels; + if (eff > AD4130_FIFO_SIZE) + /* + * Always set watermark to a multiple of the number of + * enabled channels to avoid making the FIFO unaligned. + */ + eff = rounddown(AD4130_FIFO_SIZE, st->num_enabled_channels); + + guard(mutex)(&st->lock); + + ret = regmap_update_bits(st->regmap, AD4130_FIFO_CONTROL_REG, + AD4130_FIFO_CONTROL_WM_MASK, + FIELD_PREP(AD4130_FIFO_CONTROL_WM_MASK, + ad4130_watermark_reg_val(eff))); + if (ret) + return ret; + + st->effective_watermark = eff; + st->watermark = val; + + return 0; +} + +static const struct iio_info ad4130_info = { + .read_raw = ad4130_read_raw, + .read_avail = ad4130_read_avail, + .write_raw_get_fmt = ad4130_write_raw_get_fmt, + .write_raw = ad4130_write_raw, + .update_scan_mode = ad4130_update_scan_mode, + .hwfifo_set_watermark = ad4130_set_fifo_watermark, + .debugfs_reg_access = ad4130_reg_access, +}; + +static const struct iio_info ad4131_info = { + .read_raw = ad4130_read_raw, + .read_avail = ad4130_read_avail, + .write_raw_get_fmt = ad4130_write_raw_get_fmt, + .write_raw = ad4130_write_raw, + .update_scan_mode = ad4130_update_scan_mode, + .debugfs_reg_access = ad4130_reg_access, +}; + +static const struct ad4130_chip_info ad4129_4_chip_info = { + .name = "ad4129-4", + .max_analog_pins = 8, + .num_gpios = 2, + .info = &ad4130_info, + .reg_size = ad4129_reg_size, + .reg_size_length = ARRAY_SIZE(ad4129_reg_size), + .has_fifo = true, + .pin_map = ad4130_4_pin_map, +}; + +static const struct ad4130_chip_info ad4129_8_chip_info = { + .name = "ad4129-8", + .max_analog_pins = 16, + .num_gpios = 4, + .info = &ad4130_info, + .reg_size = ad4129_reg_size, + .reg_size_length = ARRAY_SIZE(ad4129_reg_size), + .has_fifo = true, + .pin_map = ad4130_8_pin_map, +}; + +static const struct ad4130_chip_info ad4130_4_chip_info = { + .name = "ad4130-4", + .max_analog_pins = 16, + .num_gpios = 2, + .info = &ad4130_info, + .reg_size = ad4130_reg_size, + .reg_size_length = ARRAY_SIZE(ad4130_reg_size), + .has_fifo = true, + .pin_map = ad4130_4_pin_map, +}; + +static const struct ad4130_chip_info ad4130_8_chip_info = { + .name = "ad4130-8", + .max_analog_pins = 16, + .num_gpios = 4, + .info = &ad4130_info, + .reg_size = ad4130_reg_size, + .reg_size_length = ARRAY_SIZE(ad4130_reg_size), + .has_fifo = true, + .pin_map = ad4130_8_pin_map, +}; + +static const struct ad4130_chip_info ad4131_4_chip_info = { + .name = "ad4131-4", + .max_analog_pins = 8, + .num_gpios = 2, + .info = &ad4131_info, + .reg_size = ad4131_reg_size, + .reg_size_length = ARRAY_SIZE(ad4131_reg_size), + .pin_map = ad4130_4_pin_map, +}; + +static const struct ad4130_chip_info ad4131_8_chip_info = { + .name = "ad4131-8", + .max_analog_pins = 16, + .num_gpios = 4, + .info = &ad4131_info, + .reg_size = ad4131_reg_size, + .reg_size_length = ARRAY_SIZE(ad4131_reg_size), + .pin_map = ad4130_8_pin_map, +}; + +static int ad4130_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad4130_state *st = iio_priv(indio_dev); + int ret; + + guard(mutex)(&st->lock); + + if (st->chip_info->has_fifo) { + ret = ad4130_set_watermark_interrupt_en(st, true); + if (ret) + return ret; + + ret = irq_set_irq_type(st->spi->irq, st->inv_irq_trigger); + if (ret) + return ret; + + ret = ad4130_set_fifo_mode(st, AD4130_FIFO_MODE_WM); + if (ret) + return ret; + } + + ret = ad4130_set_mode(st, AD4130_MODE_CONTINUOUS); + if (ret) + return ret; + + /* + * When using triggered buffer, Entering continuous read mode must + * be the last command sent. No configuration changes are allowed until + * exiting this mode. + */ + if (!st->chip_info->has_fifo) { + ret = regmap_update_bits(st->regmap, AD4130_ADC_CONTROL_REG, + AD4130_ADC_CONTROL_CONT_READ_MASK, + FIELD_PREP(AD4130_ADC_CONTROL_CONT_READ_MASK, 1)); + if (ret) + return ret; + } + + return 0; +} + +static int ad4130_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad4130_state *st = iio_priv(indio_dev); + unsigned int i; + u32 temp; + int ret; + + guard(mutex)(&st->lock); + + if (!st->chip_info->has_fifo) { + temp = 0x42; + reinit_completion(&st->completion); + + /* + * In continuous read mode, when all samples are read, the data + * ready signal returns high until the next conversion result is + * ready. To exit this mode, the command must be sent when data + * ready is low. In order to ensure that condition, wait for the + * next interrupt (when the new conversion is finished), allowing + * data ready to return low before sending the exit command. + */ + st->buffer_wait_for_irq = true; + if (!wait_for_completion_timeout(&st->completion, msecs_to_jiffies(1000))) + dev_warn(&st->spi->dev, "Conversion timed out\n"); + st->buffer_wait_for_irq = false; + + /* Perform a read data command to exit continuous read mode (0x42) */ + ret = spi_write(st->spi, &temp, 1); + if (ret) + return ret; + } + + ret = ad4130_set_mode(st, AD4130_MODE_IDLE); + if (ret) + return ret; + + if (st->chip_info->has_fifo) { + ret = irq_set_irq_type(st->spi->irq, st->irq_trigger); + if (ret) + return ret; + + ret = ad4130_set_fifo_mode(st, AD4130_FIFO_MODE_DISABLED); + if (ret) + return ret; + + ret = ad4130_set_watermark_interrupt_en(st, false); + if (ret) + return ret; + } + + /* + * update_scan_mode() is not called in the disable path, disable all + * channels here. + */ + for (i = 0; i < indio_dev->num_channels; i++) { + ret = ad4130_set_channel_enable(st, i, false); + if (ret) + return ret; + } + + return 0; +} + +static const struct iio_buffer_setup_ops ad4130_buffer_ops = { + .postenable = ad4130_buffer_postenable, + .predisable = ad4130_buffer_predisable, +}; + +static ssize_t hwfifo_watermark_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ad4130_state *st = iio_priv(dev_to_iio_dev(dev)); + unsigned int val; + + guard(mutex)(&st->lock); + val = st->watermark; + + return sysfs_emit(buf, "%d\n", val); +} + +static ssize_t hwfifo_enabled_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ad4130_state *st = iio_priv(dev_to_iio_dev(dev)); + unsigned int val; + int ret; + + ret = regmap_read(st->regmap, AD4130_FIFO_CONTROL_REG, &val); + if (ret) + return ret; + + val = FIELD_GET(AD4130_FIFO_CONTROL_MODE_MASK, val); + + return sysfs_emit(buf, "%d\n", val != AD4130_FIFO_MODE_DISABLED); +} + +static ssize_t hwfifo_watermark_min_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%s\n", "1"); +} + +static ssize_t hwfifo_watermark_max_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%s\n", __stringify(AD4130_FIFO_SIZE)); +} + +static IIO_DEVICE_ATTR_RO(hwfifo_watermark_min, 0); +static IIO_DEVICE_ATTR_RO(hwfifo_watermark_max, 0); +static IIO_DEVICE_ATTR_RO(hwfifo_watermark, 0); +static IIO_DEVICE_ATTR_RO(hwfifo_enabled, 0); + +static const struct iio_dev_attr *ad4130_fifo_attributes[] = { + &iio_dev_attr_hwfifo_watermark_min, + &iio_dev_attr_hwfifo_watermark_max, + &iio_dev_attr_hwfifo_watermark, + &iio_dev_attr_hwfifo_enabled, + NULL +}; + +static const struct iio_trigger_ops ad4130_trigger_ops = { + .validate_device = iio_trigger_validate_own_device, +}; + +static int ad4130_triggered_buffer_setup(struct iio_dev *indio_dev) +{ + struct ad4130_state *st = iio_priv(indio_dev); + int ret; + + st->trig = devm_iio_trigger_alloc(indio_dev->dev.parent, "%s-dev%d", + indio_dev->name, iio_device_id(indio_dev)); + if (!st->trig) + return -ENOMEM; + + st->trig->ops = &ad4130_trigger_ops; + iio_trigger_set_drvdata(st->trig, indio_dev); + ret = devm_iio_trigger_register(indio_dev->dev.parent, st->trig); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(st->trig); + + return devm_iio_triggered_buffer_setup(indio_dev->dev.parent, indio_dev, + &iio_pollfunc_store_time, + &ad4130_trigger_handler, + &ad4130_buffer_ops); +} + +static int _ad4130_find_table_index(const unsigned int *tbl, size_t len, + unsigned int val) +{ + unsigned int i; + + for (i = 0; i < len; i++) + if (tbl[i] == val) + return i; + + return -EINVAL; +} + +#define ad4130_find_table_index(table, val) \ + _ad4130_find_table_index(table, ARRAY_SIZE(table), val) + +static int ad4130_get_ref_voltage(struct ad4130_state *st, + enum ad4130_ref_sel ref_sel) +{ + switch (ref_sel) { + case AD4130_REF_REFIN1: + return regulator_get_voltage(st->regulators[2].consumer); + case AD4130_REF_REFIN2: + return regulator_get_voltage(st->regulators[3].consumer); + case AD4130_REF_AVDD_AVSS: + return regulator_get_voltage(st->regulators[0].consumer); + case AD4130_REF_REFOUT_AVSS: + return st->int_ref_uv; + default: + return -EINVAL; + } +} + +static int ad4130_parse_fw_setup(struct ad4130_state *st, + struct fwnode_handle *child, + struct ad4130_setup_info *setup_info) +{ + struct device *dev = &st->spi->dev; + u32 tmp; + int ret; + + tmp = 0; + fwnode_property_read_u32(child, "adi,excitation-current-0-nanoamp", &tmp); + ret = ad4130_find_table_index(ad4130_iout_current_na_tbl, tmp); + if (ret < 0) + return dev_err_probe(dev, ret, + "Invalid excitation current %unA\n", tmp); + setup_info->iout0_val = ret; + + tmp = 0; + fwnode_property_read_u32(child, "adi,excitation-current-1-nanoamp", &tmp); + ret = ad4130_find_table_index(ad4130_iout_current_na_tbl, tmp); + if (ret < 0) + return dev_err_probe(dev, ret, + "Invalid excitation current %unA\n", tmp); + setup_info->iout1_val = ret; + + tmp = 0; + fwnode_property_read_u32(child, "adi,burnout-current-nanoamp", &tmp); + ret = ad4130_find_table_index(ad4130_burnout_current_na_tbl, tmp); + if (ret < 0) + return dev_err_probe(dev, ret, + "Invalid burnout current %unA\n", tmp); + setup_info->burnout = ret; + + setup_info->ref_bufp = fwnode_property_read_bool(child, "adi,buffered-positive"); + setup_info->ref_bufm = fwnode_property_read_bool(child, "adi,buffered-negative"); + + setup_info->ref_sel = AD4130_REF_REFIN1; + fwnode_property_read_u32(child, "adi,reference-select", + &setup_info->ref_sel); + if (setup_info->ref_sel >= AD4130_REF_SEL_MAX) + return dev_err_probe(dev, -EINVAL, + "Invalid reference selected %u\n", + setup_info->ref_sel); + + if (setup_info->ref_sel == AD4130_REF_REFOUT_AVSS) + st->int_ref_en = true; + + ret = ad4130_get_ref_voltage(st, setup_info->ref_sel); + if (ret < 0) + return dev_err_probe(dev, ret, "Cannot use reference %u\n", + setup_info->ref_sel); + + return 0; +} + +static unsigned int ad4130_translate_pin(struct ad4130_state *st, + unsigned int logical_pin) +{ + /* For analog input pins, use the chip-specific pin mapping */ + if (logical_pin < st->chip_info->max_analog_pins) + return st->chip_info->pin_map[logical_pin]; + + /* For internal channels, pass through unchanged */ + return logical_pin; +} + +static int ad4130_validate_diff_channel(struct ad4130_state *st, u32 pin) +{ + struct device *dev = &st->spi->dev; + + if (pin >= AD4130_MAX_DIFF_INPUTS) + return dev_err_probe(dev, -EINVAL, + "Invalid differential channel %u\n", pin); + + if (pin >= st->chip_info->max_analog_pins) + return 0; + + if (st->pins_fn[pin] == AD4130_PIN_FN_SPECIAL) + return dev_err_probe(dev, -EINVAL, + "Pin %u already used with fn %u\n", pin, + st->pins_fn[pin]); + + st->pins_fn[pin] |= AD4130_PIN_FN_DIFF; + + return 0; +} + +static int ad4130_validate_diff_channels(struct ad4130_state *st, + u32 *pins, unsigned int len) +{ + unsigned int i; + int ret; + + for (i = 0; i < len; i++) { + ret = ad4130_validate_diff_channel(st, pins[i]); + if (ret) + return ret; + } + + return 0; +} + +static int ad4130_validate_excitation_pin(struct ad4130_state *st, u32 pin) +{ + struct device *dev = &st->spi->dev; + + if (pin >= st->chip_info->max_analog_pins) + return dev_err_probe(dev, -EINVAL, + "Invalid excitation pin %u\n", pin); + + if (st->pins_fn[pin] == AD4130_PIN_FN_SPECIAL) + return dev_err_probe(dev, -EINVAL, + "Pin %u already used with fn %u\n", pin, + st->pins_fn[pin]); + + st->pins_fn[pin] |= AD4130_PIN_FN_EXCITATION; + + return 0; +} + +static int ad4130_validate_vbias_pin(struct ad4130_state *st, u32 pin) +{ + struct device *dev = &st->spi->dev; + + if (pin >= st->chip_info->max_analog_pins) + return dev_err_probe(dev, -EINVAL, "Invalid vbias pin %u\n", + pin); + + if (st->pins_fn[pin] == AD4130_PIN_FN_SPECIAL) + return dev_err_probe(dev, -EINVAL, + "Pin %u already used with fn %u\n", pin, + st->pins_fn[pin]); + + st->pins_fn[pin] |= AD4130_PIN_FN_VBIAS; + + return 0; +} + +static int ad4130_validate_vbias_pins(struct ad4130_state *st, + u32 *pins, unsigned int len) +{ + unsigned int i; + int ret; + + for (i = 0; i < st->num_vbias_pins; i++) { + ret = ad4130_validate_vbias_pin(st, pins[i]); + if (ret) + return ret; + } + + return 0; +} + +static int ad4130_parse_fw_channel(struct iio_dev *indio_dev, + struct fwnode_handle *child) +{ + struct ad4130_state *st = iio_priv(indio_dev); + unsigned int resolution = ad4130_resolution(st); + unsigned int index = indio_dev->num_channels++; + struct device *dev = &st->spi->dev; + struct ad4130_chan_info *chan_info; + struct iio_chan_spec *chan; + u32 pins[2]; + int ret; + + if (index >= AD4130_MAX_CHANNELS) + return dev_err_probe(dev, -EINVAL, "Too many channels\n"); + + chan = &st->chans[index]; + chan_info = &st->chans_info[index]; + + *chan = ad4130_channel_template; + chan->scan_type.realbits = resolution; + chan->scan_type.storagebits = resolution; + chan->scan_index = index; + + chan_info->slot = AD4130_INVALID_SLOT; + chan_info->setup.fs = AD4130_FILTER_SELECT_MIN; + chan_info->initialized = true; + + ret = fwnode_property_read_u32_array(child, "diff-channels", pins, + ARRAY_SIZE(pins)); + if (ret) + return ret; + + ret = ad4130_validate_diff_channels(st, pins, ARRAY_SIZE(pins)); + if (ret) + return ret; + + chan->channel = pins[0]; + chan->channel2 = pins[1]; + + ret = ad4130_parse_fw_setup(st, child, &chan_info->setup); + if (ret) + return ret; + + fwnode_property_read_u32(child, "adi,excitation-pin-0", + &chan_info->iout0); + if (chan_info->setup.iout0_val != AD4130_IOUT_OFF) { + ret = ad4130_validate_excitation_pin(st, chan_info->iout0); + if (ret) + return ret; + } + + fwnode_property_read_u32(child, "adi,excitation-pin-1", + &chan_info->iout1); + if (chan_info->setup.iout1_val != AD4130_IOUT_OFF) { + ret = ad4130_validate_excitation_pin(st, chan_info->iout1); + if (ret) + return ret; + } + + return 0; +} + +static int ad4130_parse_fw_children(struct iio_dev *indio_dev) +{ + struct ad4130_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + int ret; + + indio_dev->channels = st->chans; + + device_for_each_child_node_scoped(dev, child) { + ret = ad4130_parse_fw_channel(indio_dev, child); + if (ret) + return ret; + } + + return 0; +} + +static int ad4310_parse_fw(struct iio_dev *indio_dev) +{ + struct ad4130_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + u32 ext_clk_freq = AD4130_MCLK_FREQ_76_8KHZ; + unsigned int i; + int avdd_uv; + int irq; + int ret; + + st->mclk = devm_clk_get_optional(dev, "mclk"); + if (IS_ERR(st->mclk)) + return dev_err_probe(dev, PTR_ERR(st->mclk), + "Failed to get mclk\n"); + + st->int_pin_sel = AD4130_INT_PIN_INT; + + for (i = 0; i < ARRAY_SIZE(ad4130_int_pin_names); i++) { + irq = fwnode_irq_get_byname(dev_fwnode(dev), + ad4130_int_pin_names[i]); + if (irq > 0) { + st->int_pin_sel = i; + break; + } + } + + if (st->int_pin_sel == AD4130_INT_PIN_DOUT) + return dev_err_probe(dev, -EINVAL, + "Cannot use DOUT as interrupt pin\n"); + + if (st->int_pin_sel == AD4130_INT_PIN_P2) + st->pins_fn[AD4130_AIN3_P2] = AD4130_PIN_FN_SPECIAL; + + device_property_read_u32(dev, "adi,ext-clk-freq-hz", &ext_clk_freq); + if (ext_clk_freq != AD4130_MCLK_FREQ_153_6KHZ && + ext_clk_freq != AD4130_MCLK_FREQ_76_8KHZ) + return dev_err_probe(dev, -EINVAL, + "Invalid external clock frequency %u\n", + ext_clk_freq); + + if (st->mclk && ext_clk_freq == AD4130_MCLK_FREQ_153_6KHZ) + st->mclk_sel = AD4130_MCLK_153_6KHZ_EXT; + else if (st->mclk) + st->mclk_sel = AD4130_MCLK_76_8KHZ_EXT; + else + st->mclk_sel = AD4130_MCLK_76_8KHZ; + + if (st->int_pin_sel == AD4130_INT_PIN_CLK && + st->mclk_sel != AD4130_MCLK_76_8KHZ) + return dev_err_probe(dev, -EINVAL, + "Invalid clock %u for interrupt pin %u\n", + st->mclk_sel, st->int_pin_sel); + + st->int_ref_uv = AD4130_INT_REF_2_5V; + + /* + * When the AVDD supply is set to below 2.5V the internal reference of + * 1.25V should be selected. + * See datasheet page 37, section ADC REFERENCE. + */ + avdd_uv = regulator_get_voltage(st->regulators[0].consumer); + if (avdd_uv > 0 && avdd_uv < AD4130_INT_REF_2_5V) + st->int_ref_uv = AD4130_INT_REF_1_25V; + + st->bipolar = device_property_read_bool(dev, "adi,bipolar"); + + ret = device_property_count_u32(dev, "adi,vbias-pins"); + if (ret > 0) { + if (ret > st->chip_info->max_analog_pins) + return dev_err_probe(dev, -EINVAL, + "Too many vbias pins %u\n", ret); + + st->num_vbias_pins = ret; + + ret = device_property_read_u32_array(dev, "adi,vbias-pins", + st->vbias_pins, + st->num_vbias_pins); + if (ret) + return dev_err_probe(dev, ret, + "Failed to read vbias pins\n"); + + ret = ad4130_validate_vbias_pins(st, st->vbias_pins, + st->num_vbias_pins); + if (ret) + return ret; + } + + ret = ad4130_parse_fw_children(indio_dev); + if (ret) + return ret; + + return 0; +} + +static void ad4130_fill_scale_tbls(struct ad4130_state *st) +{ + unsigned int pow = ad4130_resolution(st) - st->bipolar; + unsigned int i, j; + + for (i = 0; i < AD4130_REF_SEL_MAX; i++) { + int ret; + u64 nv; + + ret = ad4130_get_ref_voltage(st, i); + if (ret < 0) + continue; + + nv = (u64)ret * NANO; + + for (j = 0; j < AD4130_MAX_PGA; j++) + st->scale_tbls[i][j][1] = div_u64(nv >> (pow + j), MILLI); + } +} + +static void ad4130_clk_disable_unprepare(void *clk) +{ + clk_disable_unprepare(clk); +} + +static int ad4130_set_mclk_sel(struct ad4130_state *st, + enum ad4130_mclk_sel mclk_sel) +{ + return regmap_update_bits(st->regmap, AD4130_ADC_CONTROL_REG, + AD4130_ADC_CONTROL_MCLK_SEL_MASK, + FIELD_PREP(AD4130_ADC_CONTROL_MCLK_SEL_MASK, + mclk_sel)); +} + +static unsigned long ad4130_int_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + return AD4130_MCLK_FREQ_76_8KHZ; +} + +static int ad4130_int_clk_is_enabled(struct clk_hw *hw) +{ + struct ad4130_state *st = container_of(hw, struct ad4130_state, int_clk_hw); + + return st->mclk_sel == AD4130_MCLK_76_8KHZ_OUT; +} + +static int ad4130_int_clk_prepare(struct clk_hw *hw) +{ + struct ad4130_state *st = container_of(hw, struct ad4130_state, int_clk_hw); + int ret; + + ret = ad4130_set_mclk_sel(st, AD4130_MCLK_76_8KHZ_OUT); + if (ret) + return ret; + + st->mclk_sel = AD4130_MCLK_76_8KHZ_OUT; + + return 0; +} + +static void ad4130_int_clk_unprepare(struct clk_hw *hw) +{ + struct ad4130_state *st = container_of(hw, struct ad4130_state, int_clk_hw); + int ret; + + ret = ad4130_set_mclk_sel(st, AD4130_MCLK_76_8KHZ); + if (ret) + return; + + st->mclk_sel = AD4130_MCLK_76_8KHZ; +} + +static const struct clk_ops ad4130_int_clk_ops = { + .recalc_rate = ad4130_int_clk_recalc_rate, + .is_enabled = ad4130_int_clk_is_enabled, + .prepare = ad4130_int_clk_prepare, + .unprepare = ad4130_int_clk_unprepare, +}; + +static int ad4130_setup_int_clk(struct ad4130_state *st) +{ + struct device *dev = &st->spi->dev; + struct device_node *of_node = dev_of_node(dev); + struct clk_init_data init = {}; + const char *clk_name; + int ret; + + if (st->int_pin_sel == AD4130_INT_PIN_CLK || + st->mclk_sel != AD4130_MCLK_76_8KHZ) + return 0; + + if (!of_node) + return 0; + + clk_name = of_node->name; + of_property_read_string(of_node, "clock-output-names", &clk_name); + + init.name = clk_name; + init.ops = &ad4130_int_clk_ops; + + st->int_clk_hw.init = &init; + ret = devm_clk_hw_register(dev, &st->int_clk_hw); + if (ret) + return ret; + + return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + &st->int_clk_hw); +} + +static int ad4130_setup(struct iio_dev *indio_dev) +{ + struct ad4130_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + unsigned int int_ref_val; + unsigned long rate = AD4130_MCLK_FREQ_76_8KHZ; + unsigned int val; + unsigned int i; + int ret; + + if (st->mclk_sel == AD4130_MCLK_153_6KHZ_EXT) + rate = AD4130_MCLK_FREQ_153_6KHZ; + + ret = clk_set_rate(st->mclk, rate); + if (ret) + return ret; + + ret = clk_prepare_enable(st->mclk); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, ad4130_clk_disable_unprepare, + st->mclk); + if (ret) + return ret; + + if (st->int_ref_uv == AD4130_INT_REF_2_5V) + int_ref_val = AD4130_INT_REF_VAL_2_5V; + else + int_ref_val = AD4130_INT_REF_VAL_1_25V; + + /* Switch to SPI 4-wire mode. */ + val = FIELD_PREP(AD4130_ADC_CONTROL_CSB_EN_MASK, 1); + val |= FIELD_PREP(AD4130_ADC_CONTROL_BIPOLAR_MASK, st->bipolar); + val |= FIELD_PREP(AD4130_ADC_CONTROL_INT_REF_EN_MASK, st->int_ref_en); + val |= FIELD_PREP(AD4130_ADC_CONTROL_MODE_MASK, AD4130_MODE_IDLE); + val |= FIELD_PREP(AD4130_ADC_CONTROL_MCLK_SEL_MASK, st->mclk_sel); + val |= FIELD_PREP(AD4130_ADC_CONTROL_INT_REF_VAL_MASK, int_ref_val); + + ret = regmap_write(st->regmap, AD4130_ADC_CONTROL_REG, val); + if (ret) + return ret; + + /* + * Configure unused GPIOs for output. If configured, the interrupt + * function of P2 takes priority over the GPIO out function. + */ + val = 0; + for (i = 0; i < st->chip_info->num_gpios; i++) { + if (st->pins_fn[i + AD4130_AIN2_P1] == AD4130_PIN_FN_NONE) { + if (st->chip_info->num_gpios == 2) + val |= FIELD_PREP(AD4130_4_IO_CONTROL_GPIO_CTRL_MASK, BIT(i)); + else + val |= FIELD_PREP(AD4130_IO_CONTROL_GPIO_CTRL_MASK, BIT(i)); + } + } + + val |= FIELD_PREP(AD4130_IO_CONTROL_INT_PIN_SEL_MASK, st->int_pin_sel); + + ret = regmap_write(st->regmap, AD4130_IO_CONTROL_REG, val); + if (ret) + return ret; + + val = 0; + for (i = 0; i < st->num_vbias_pins; i++) + val |= BIT(ad4130_translate_pin(st, st->vbias_pins[i])); + + ret = regmap_write(st->regmap, AD4130_VBIAS_REG, val); + if (ret) + return ret; + + if (st->chip_info->has_fifo) { + ret = regmap_clear_bits(st->regmap, AD4130_FIFO_CONTROL_REG, + AD4130_FIFO_CONTROL_HEADER_MASK); + if (ret) + return ret; + + /* FIFO watermark interrupt starts out as enabled, disable it. */ + ret = ad4130_set_watermark_interrupt_en(st, false); + if (ret) + return ret; + } + + /* Setup channels. */ + for (i = 0; i < indio_dev->num_channels; i++) { + struct ad4130_chan_info *chan_info = &st->chans_info[i]; + struct iio_chan_spec *chan = &st->chans[i]; + unsigned int val; + + val = FIELD_PREP(AD4130_CHANNEL_AINP_MASK, + ad4130_translate_pin(st, chan->channel)) | + FIELD_PREP(AD4130_CHANNEL_AINM_MASK, + ad4130_translate_pin(st, chan->channel2)) | + FIELD_PREP(AD4130_CHANNEL_IOUT1_MASK, + ad4130_translate_pin(st, chan_info->iout0)) | + FIELD_PREP(AD4130_CHANNEL_IOUT2_MASK, + ad4130_translate_pin(st, chan_info->iout1)); + + ret = regmap_write(st->regmap, AD4130_CHANNEL_X_REG(i), val); + if (ret) + return ret; + } + + return 0; +} + +static int ad4130_soft_reset(struct ad4130_state *st) +{ + int ret; + + ret = spi_write(st->spi, st->reset_buf, sizeof(st->reset_buf)); + if (ret) + return ret; + + fsleep(AD4130_RESET_SLEEP_US); + + return 0; +} + +static void ad4130_disable_regulators(void *data) +{ + struct ad4130_state *st = data; + + regulator_bulk_disable(ARRAY_SIZE(st->regulators), st->regulators); +} + +static int ad4130_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ad4130_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->chip_info = device_get_match_data(dev); + + memset(st->reset_buf, 0xff, sizeof(st->reset_buf)); + init_completion(&st->completion); + mutex_init(&st->lock); + st->spi = spi; + + if (st->chip_info->has_fifo) { + /* + * Xfer: [ XFR1 ] [ XFR2 ] + * Master: 0x7D N ...................... + * Slave: ...... DATA1 DATA2 ... DATAN + */ + st->fifo_tx_buf[0] = AD4130_COMMS_READ_MASK | AD4130_FIFO_DATA_REG; + st->fifo_xfer[0].tx_buf = st->fifo_tx_buf; + st->fifo_xfer[0].len = sizeof(st->fifo_tx_buf); + st->fifo_xfer[1].rx_buf = st->fifo_rx_buf; + spi_message_init_with_transfers(&st->fifo_msg, st->fifo_xfer, + ARRAY_SIZE(st->fifo_xfer)); + } + + indio_dev->name = st->chip_info->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = st->chip_info->info; + + st->regmap = devm_regmap_init(dev, NULL, st, &ad4130_regmap_config); + if (IS_ERR(st->regmap)) + return PTR_ERR(st->regmap); + + st->regulators[0].supply = "avdd"; + st->regulators[1].supply = "iovdd"; + st->regulators[2].supply = "refin1"; + st->regulators[3].supply = "refin2"; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(st->regulators), + st->regulators); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + ret = regulator_bulk_enable(ARRAY_SIZE(st->regulators), st->regulators); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulators\n"); + + ret = devm_add_action_or_reset(dev, ad4130_disable_regulators, st); + if (ret) + return ret; + + ret = ad4130_soft_reset(st); + if (ret) + return ret; + + ret = ad4310_parse_fw(indio_dev); + if (ret) + return ret; + + ret = ad4130_setup(indio_dev); + if (ret) + return ret; + + ret = ad4130_setup_int_clk(st); + if (ret) + return ret; + + ad4130_fill_scale_tbls(st); + + st->gc.owner = THIS_MODULE; + st->gc.label = st->chip_info->name; + st->gc.base = -1; + st->gc.ngpio = st->chip_info->num_gpios; + st->gc.parent = dev; + st->gc.can_sleep = true; + st->gc.init_valid_mask = ad4130_gpio_init_valid_mask; + st->gc.get_direction = ad4130_gpio_get_direction; + st->gc.set = ad4130_gpio_set; + + ret = devm_gpiochip_add_data(dev, &st->gc, st); + if (ret) + return ret; + + if (st->chip_info->has_fifo) + ret = devm_iio_kfifo_buffer_setup_ext(dev, indio_dev, + &ad4130_buffer_ops, + ad4130_fifo_attributes); + else + ret = ad4130_triggered_buffer_setup(indio_dev); + if (ret) + return ret; + + ret = devm_request_threaded_irq(dev, spi->irq, NULL, + ad4130_irq_handler, IRQF_ONESHOT, + indio_dev->name, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to request irq\n"); + + if (st->chip_info->has_fifo) { + /* + * When the chip enters FIFO mode, IRQ polarity is inverted. + * When the chip exits FIFO mode, IRQ polarity returns to normal. + * See datasheet pages: 65, FIFO Watermark Interrupt section, + * and 71, Bit Descriptions for STATUS Register, RDYB. + * Cache the normal and inverted IRQ triggers to set them when + * entering and exiting FIFO mode. + */ + st->irq_trigger = irq_get_trigger_type(spi->irq); + if (st->irq_trigger & IRQF_TRIGGER_RISING) + st->inv_irq_trigger = IRQF_TRIGGER_FALLING; + else if (st->irq_trigger & IRQF_TRIGGER_FALLING) + st->inv_irq_trigger = IRQF_TRIGGER_RISING; + else + return dev_err_probe(dev, -EINVAL, "Invalid irq flags: %u\n", + st->irq_trigger); + } + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ad4130_of_match[] = { + { + .compatible = "adi,ad4129-4", + .data = &ad4129_4_chip_info + }, + { + .compatible = "adi,ad4129-8", + .data = &ad4129_8_chip_info + }, + { + .compatible = "adi,ad4130-4", + .data = &ad4130_4_chip_info + }, + { + .compatible = "adi,ad4130", + .data = &ad4130_8_chip_info + }, + { + .compatible = "adi,ad4131-4", + .data = &ad4131_4_chip_info + }, + { + .compatible = "adi,ad4131-8", + .data = &ad4131_8_chip_info + }, + { } +}; +MODULE_DEVICE_TABLE(of, ad4130_of_match); + +static const struct spi_device_id ad4130_id_table[] = { + { .name = "ad4129-4", .driver_data = (kernel_ulong_t)&ad4129_4_chip_info }, + { .name = "ad4129-8", .driver_data = (kernel_ulong_t)&ad4129_8_chip_info }, + { .name = "ad4130-4", .driver_data = (kernel_ulong_t)&ad4130_4_chip_info }, + { .name = "ad4130", .driver_data = (kernel_ulong_t)&ad4130_8_chip_info }, + { .name = "ad4131-4", .driver_data = (kernel_ulong_t)&ad4131_4_chip_info }, + { .name = "ad4131-8", .driver_data = (kernel_ulong_t)&ad4131_8_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad4130_id_table); + +static struct spi_driver ad4130_driver = { + .driver = { + .name = AD4130_NAME, + .of_match_table = ad4130_of_match, + }, + .probe = ad4130_probe, + .id_table = ad4130_id_table, +}; +module_spi_driver(ad4130_driver); + +MODULE_AUTHOR("Cosmin Tanislav "); +MODULE_DESCRIPTION("Analog Devices AD4130 SPI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/ad4134.c b/drivers/iio/adc/ad4134.c new file mode 100644 index 000000000..0490218bb --- /dev/null +++ b/drivers/iio/adc/ad4134.c @@ -0,0 +1,499 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2026 Analog Devices, Inc. + * Author: Marcelo Schmitt + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AD4134_RESET_TIME_US (10 * USEC_PER_SEC) + +#define AD4134_REG_READ_MASK BIT(7) +#define AD4134_SPI_MAX_XFER_LEN 3 + +#define AD4134_EXT_CLOCK_MHZ (48 * HZ_PER_MHZ) + +#define AD4134_NUM_CHANNELS 4 +#define AD4134_CHAN_PRECISION_BITS 24 + +#define AD4134_IFACE_CONFIG_A_REG 0x00 +#define AD4134_IFACE_CONFIG_B_REG 0x01 +#define AD4134_IFACE_CONFIG_B_SINGLE_INSTR BIT(7) + +#define AD4134_DEVICE_CONFIG_REG 0x02 +#define AD4134_DEVICE_CONFIG_POWER_MODE_MASK BIT(0) +#define AD4134_POWER_MODE_HIGH_PERF 0x1 + +#define AD4134_SILICON_REV_REG 0x07 +#define AD4134_SCRATCH_PAD_REG 0x0A +#define AD4134_STREAM_MODE_REG 0x0E +#define AD4134_SDO_PIN_SRC_SEL_REG 0x10 +#define AD4134_SDO_PIN_SRC_SEL_SDO_SEL_MASK BIT(2) + +#define AD4134_DATA_PACKET_CONFIG_REG 0x11 +#define AD4134_DATA_PACKET_CONFIG_FRAME_MASK GENMASK(5, 4) +#define AD4134_DATA_PACKET_24BIT_FRAME 0x2 + +#define AD4134_DIG_IF_CFG_REG 0x12 +#define AD4134_DIF_IF_CFG_FORMAT_MASK GENMASK(1, 0) +#define AD4134_DATA_FORMAT_SINGLE_CH_MODE 0x0 + +#define AD4134_PW_DOWN_CTRL_REG 0x13 +#define AD4134_DEVICE_STATUS_REG 0x15 +#define AD4134_ODR_VAL_INT_LSB_REG 0x16 +#define AD4134_CH3_OFFSET_MSB_REG 0x3E +#define AD4134_AIN_OR_ERROR_REG 0x48 + +/* + * AD4134 register map ends at address 0x48 and there is no register for + * retrieving ADC sample data. Though, to make use of Linux regmap API both + * for register access and sample read, we define one virtual register for each + * ADC channel. AD4134_CH_VREG(x) maps a channel number to it's virtual register + * address while AD4134_VREG_CH(x) tells which channel given the address. + */ +#define AD4134_CH_VREG(x) ((x) + 0x50) +#define AD4134_VREG_CH(x) ((x) - 0x50) + +#define AD4134_SPI_CRC_POLYNOM 0x07 +#define AD4134_SPI_CRC_INIT_VALUE 0xA5 +static unsigned char ad4134_spi_crc_table[CRC8_TABLE_SIZE]; + +#define AD4134_CHANNEL(_index) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec ad4134_chan_set[] = { + AD4134_CHANNEL(0), + AD4134_CHANNEL(1), + AD4134_CHANNEL(2), + AD4134_CHANNEL(3), +}; + +struct ad4134_state { + struct spi_device *spi; + struct regmap *regmap; + unsigned long sys_clk_hz; + struct gpio_desc *odr_gpio; + int refin_mv; + /* + * DMA (thus cache coherency maintenance) requires the transfer buffers + * to live in their own cache lines. + */ + u8 rx_buf[AD4134_SPI_MAX_XFER_LEN] __aligned(IIO_DMA_MINALIGN); + u8 tx_buf[AD4134_SPI_MAX_XFER_LEN]; +}; + +static const struct regmap_range ad4134_regmap_rd_range[] = { + regmap_reg_range(AD4134_IFACE_CONFIG_A_REG, AD4134_SILICON_REV_REG), + regmap_reg_range(AD4134_SCRATCH_PAD_REG, AD4134_PW_DOWN_CTRL_REG), + regmap_reg_range(AD4134_DEVICE_STATUS_REG, AD4134_AIN_OR_ERROR_REG), + regmap_reg_range(AD4134_CH_VREG(0), AD4134_CH_VREG(AD4134_NUM_CHANNELS)), +}; + +static const struct regmap_range ad4134_regmap_wr_range[] = { + regmap_reg_range(AD4134_IFACE_CONFIG_A_REG, AD4134_DEVICE_CONFIG_REG), + regmap_reg_range(AD4134_SCRATCH_PAD_REG, AD4134_SCRATCH_PAD_REG), + regmap_reg_range(AD4134_STREAM_MODE_REG, AD4134_PW_DOWN_CTRL_REG), + regmap_reg_range(AD4134_ODR_VAL_INT_LSB_REG, AD4134_CH3_OFFSET_MSB_REG), +}; + +static const struct regmap_access_table ad4134_regmap_rd_table = { + .yes_ranges = ad4134_regmap_rd_range, + .n_yes_ranges = ARRAY_SIZE(ad4134_regmap_rd_range), +}; + +static const struct regmap_access_table ad4134_regmap_wr_table = { + .yes_ranges = ad4134_regmap_wr_range, + .n_yes_ranges = ARRAY_SIZE(ad4134_regmap_wr_range), +}; + +static int ad4134_calc_spi_crc(u8 inst, u8 data) +{ + u8 buf[] = { inst, data }; + + return crc8(ad4134_spi_crc_table, buf, ARRAY_SIZE(buf), + AD4134_SPI_CRC_INIT_VALUE); +} + +static void ad4134_prepare_spi_tx_buf(u8 inst, u8 data, u8 *buf) +{ + buf[0] = inst; + buf[1] = data; + buf[2] = ad4134_calc_spi_crc(inst, data); +} + +static int ad4134_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct ad4134_state *st = context; + struct spi_transfer xfer = { + .tx_buf = st->tx_buf, + .rx_buf = st->rx_buf, + .len = AD4134_SPI_MAX_XFER_LEN, + }; + int ret; + + ad4134_prepare_spi_tx_buf(reg, val, st->tx_buf); + + ret = spi_sync_transfer(st->spi, &xfer, 1); + if (ret) + return ret; + + if (st->rx_buf[2] != st->tx_buf[2]) + dev_dbg(&st->spi->dev, "reg write CRC check failed\n"); + + return 0; +} + +static int ad4134_data_read(struct ad4134_state *st, unsigned int reg, + unsigned int *val) +{ + unsigned int i; + int ret; + + /* + * To be able to read data from all 4 channels through a single line, we + * set DOUTx output format to 0 in the digital interface config register + * (0x12). With that, data from all four channels is serialized and + * output on DOUT0. During the probe, we also set SDO_PIN_SRC_SEL in + * DEVICE_CONFIG_1 register to duplicate DOUT0 on the SDO pin. Combined, + * those configurations enable ADC data read through a conventional SPI + * interface. Now we read data from all channels but keep only the bits + * from the requested one. + */ + for (i = 0; i < ARRAY_SIZE(ad4134_chan_set); i++) { + ret = spi_write_then_read(st->spi, NULL, 0, st->rx_buf, + BITS_TO_BYTES(AD4134_CHAN_PRECISION_BITS)); + if (ret) + return ret; + + /* + * AD4134 has a built-in feature that flags when data transfers + * don't run enough clock cycles to read the entire data frame. + * Clock out data from all channels to avoid that. + */ + if (i == AD4134_VREG_CH(reg)) + *val = get_unaligned_be24(st->rx_buf); + } + + return 0; +} + +static int ad4134_register_read(struct ad4134_state *st, unsigned int reg, + unsigned int *val) +{ + struct spi_transfer xfer = { + .tx_buf = st->tx_buf, + .rx_buf = st->rx_buf, + .len = AD4134_SPI_MAX_XFER_LEN, + }; + unsigned int inst; + int ret; + + inst = AD4134_REG_READ_MASK | reg; + ad4134_prepare_spi_tx_buf(inst, 0, st->tx_buf); + + ret = spi_sync_transfer(st->spi, &xfer, 1); + if (ret) + return ret; + + *val = st->rx_buf[1]; + + /* Check CRC */ + if (st->rx_buf[2] != st->tx_buf[2]) + dev_dbg(&st->spi->dev, "reg read CRC check failed\n"); + + return 0; +} + +static int ad4134_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + struct ad4134_state *st = context; + + if (reg >= AD4134_CH_VREG(0)) + return ad4134_data_read(st, reg, val); + + return ad4134_register_read(st, reg, val); +} + +static const struct regmap_config ad4134_regmap_config = { + .reg_read = ad4134_reg_read, + .reg_write = ad4134_reg_write, + .rd_table = &ad4134_regmap_rd_table, + .wr_table = &ad4134_regmap_wr_table, + .max_register = AD4134_CH_VREG(ARRAY_SIZE(ad4134_chan_set)), +}; + +static int ad4134_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad4134_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + gpiod_set_value_cansleep(st->odr_gpio, 1); + /* + * For slave mode gated DCLK (data sheet page 11), the minimum + * ODR high time is 3 * tDIGCLK. The internal digital clock + * period is tDIGCLK = 1/fDIGCLK = 2/fSYSCLK. + * The System clock frequency (fSYSCLK) is typically 48 MHz. + * Thus, ODR high time = 3 * (2 / (48 * HZ_PER_MHZ)) + * ODR high time = 0.000000125 s = 125 ns + * 1 micro second should be more than enough. Not worth it + * tweaking for shorter dealy since this is not a fast data path. + */ + fsleep(1); + gpiod_set_value_cansleep(st->odr_gpio, 0); + ret = regmap_read(st->regmap, AD4134_CH_VREG(chan->channel), val); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = st->refin_mv; + *val2 = AD4134_CHAN_PRECISION_BITS - 1; + + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static int ad4134_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + struct ad4134_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); +} + +static int ad4134_min_io_mode_setup(struct ad4134_state *st) +{ + struct device *dev = &st->spi->dev; + int ret; + + st->odr_gpio = devm_gpiod_get(dev, "odr", GPIOD_OUT_LOW); + if (IS_ERR(st->odr_gpio)) + return dev_err_probe(dev, PTR_ERR(st->odr_gpio), + "failed to get ODR GPIO\n"); + + ret = regmap_update_bits(st->regmap, AD4134_DIG_IF_CFG_REG, + AD4134_DIF_IF_CFG_FORMAT_MASK, + FIELD_PREP(AD4134_DIF_IF_CFG_FORMAT_MASK, + AD4134_DATA_FORMAT_SINGLE_CH_MODE)); + if (ret) + return dev_err_probe(dev, ret, + "failed to set single channel mode\n"); + + ret = regmap_set_bits(st->regmap, AD4134_SDO_PIN_SRC_SEL_REG, + AD4134_SDO_PIN_SRC_SEL_SDO_SEL_MASK); + if (ret) + return dev_err_probe(dev, ret, + "failed to set SDO source selection\n"); + + return regmap_set_bits(st->regmap, AD4134_IFACE_CONFIG_B_REG, + AD4134_IFACE_CONFIG_B_SINGLE_INSTR); +} + +static const struct iio_info ad4134_info = { + .read_raw = ad4134_read_raw, + .debugfs_reg_access = ad4134_debugfs_reg_access, +}; + +static const char * const ad4143_required_regulators[] = { + "avdd5", "dvdd5", "iovdd", +}; + +static const char * const ad4143_optional_regulators[] = { + "avdd1v8", "dvdd1v8", "clkvdd", +}; + +static int ad4134_regulator_setup(struct ad4134_state *st) +{ + struct device *dev = &st->spi->dev; + int ret; + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(ad4143_required_regulators), + ad4143_required_regulators); + if (ret) + return dev_err_probe(dev, ret, "failed to enable power supplies\n"); + + /* Required regulator that we need to read the voltage */ + ret = devm_regulator_get_enable_read_voltage(dev, "refin"); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get REFIN voltage.\n"); + + st->refin_mv = ret / (MICRO / MILLI); + + ret = devm_regulator_get_enable_optional(dev, "ldoin"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to enable ldoin supply\n"); + + /* If ldoin was provided, then use the use the internal LDO regulators */ + if (ret == 0) + return 0; + + /* + * If ldoin is not provided, then avdd1v8, dvdd1v8, and clkvdd are + * required. + */ + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(ad4143_optional_regulators), + ad4143_optional_regulators); + if (ret) + return dev_err_probe(dev, ret, "failed to enable 1V8 power supplies\n"); + + return 0; +} + +static int ad4134_clock_select(struct ad4134_state *st) +{ + struct device *dev = &st->spi->dev; + struct clk *xtal_clk, *clkin_clk; + + /* + * AD4134 requires one external clock source and only one external clock + * source can be provided at a time. Try to get a crystal provided clock. + * If that fails, try to get a CMOS clock. + */ + xtal_clk = devm_clk_get_optional_enabled(dev, "xtal"); + if (!xtal_clk) + xtal_clk = devm_clk_get_optional_enabled(dev, "xtal"); + if (IS_ERR(xtal_clk)) + return dev_err_probe(dev, PTR_ERR(xtal_clk), + "failed to get xtal\n"); + + clkin_clk = devm_clk_get_optional_enabled(dev, "clkin"); + if (!clkin_clk) + clkin_clk = devm_clk_get_optional_enabled(dev, "clkin"); + if (IS_ERR(clkin_clk)) + return dev_err_probe(dev, PTR_ERR(clkin_clk), + "failed to get clkin\n"); + + st->sys_clk_hz = clk_get_rate(xtal_clk) | clk_get_rate(clkin_clk); + if (st->sys_clk_hz != AD4134_EXT_CLOCK_MHZ) + dev_warn(dev, "invalid external clock frequency %lu\n", + st->sys_clk_hz); + + return 0; +} + +static int ad4134_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct reset_control *rst; + struct iio_dev *indio_dev; + struct ad4134_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + + indio_dev->name = "ad4134"; + indio_dev->channels = ad4134_chan_set; + indio_dev->num_channels = ARRAY_SIZE(ad4134_chan_set); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ad4134_info; + + ret = ad4134_regulator_setup(st); + if (ret) + return ret; + + ret = ad4134_clock_select(st); + if (ret) + return ret; + + rst = devm_reset_control_get_optional_exclusive_deasserted(dev, NULL); + if (IS_ERR(rst)) + return dev_err_probe(dev, PTR_ERR(rst), + "failed to get and deassert reset\n"); + + crc8_populate_msb(ad4134_spi_crc_table, AD4134_SPI_CRC_POLYNOM); + + st->regmap = devm_regmap_init(dev, NULL, st, &ad4134_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "failed to initialize regmap"); + + ret = ad4134_min_io_mode_setup(st); + if (ret) + return dev_err_probe(dev, ret, + "failed to setup minimum I/O mode\n"); + + /* Bump precision to 24-bit */ + ret = regmap_update_bits(st->regmap, AD4134_DATA_PACKET_CONFIG_REG, + AD4134_DATA_PACKET_CONFIG_FRAME_MASK, + FIELD_PREP(AD4134_DATA_PACKET_CONFIG_FRAME_MASK, + AD4134_DATA_PACKET_24BIT_FRAME)); + if (ret) + return ret; + + /* Set high performance power mode */ + ret = regmap_update_bits(st->regmap, AD4134_DEVICE_CONFIG_REG, + AD4134_DEVICE_CONFIG_POWER_MODE_MASK, + FIELD_PREP(AD4134_DEVICE_CONFIG_POWER_MODE_MASK, + AD4134_POWER_MODE_HIGH_PERF)); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id ad4134_id[] = { + { .name = "ad4134" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad4134_id); + +static const struct of_device_id ad4134_of_match[] = { + { .compatible = "adi,ad4134" }, + { } +}; +MODULE_DEVICE_TABLE(of, ad4134_of_match); + +static struct spi_driver ad4134_driver = { + .driver = { + .name = "ad4134", + .of_match_table = ad4134_of_match, + }, + .probe = ad4134_probe, + .id_table = ad4134_id, +}; +module_spi_driver(ad4134_driver); + +MODULE_AUTHOR("Marcelo Schmitt "); +MODULE_DESCRIPTION("Analog Devices AD4134 SPI driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_AD4134"); diff --git a/drivers/iio/adc/ad4170-4.c b/drivers/iio/adc/ad4170-4.c new file mode 100644 index 000000000..f7757b3a9 --- /dev/null +++ b/drivers/iio/adc/ad4170-4.c @@ -0,0 +1,3010 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Analog Devices AD4170-4 ADC driver + * + * Copyright (C) 2025 Analog Devices, Inc. + * Author: Ana-Maria Cusco + * Author: Marcelo Schmitt + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * AD4170 registers + * Multibyte register addresses point to the most significant byte which is the + * address to use to get the most significant byte first (address accessed is + * decremented by one for each data byte) + * + * Each register address define follows the AD4170__REG format. + * Each mask follows the AD4170__ format. + * E.g. AD4170_PIN_MUXING_DIG_AUX1_CTRL_MSK is for accessing DIG_AUX1_CTRL field + * of PIN_MUXING_REG. + * Each constant follows the AD4170___ format. + * E.g. AD4170_PIN_MUXING_DIG_AUX1_DISABLED is the value written to + * DIG_AUX1_CTRL field of PIN_MUXING register to disable DIG_AUX1 pin. + * Some register names and register field names are shortened versions of + * their datasheet counterpart names to provide better code readability. + */ +#define AD4170_CONFIG_A_REG 0x00 +#define AD4170_DATA_24B_REG 0x1E +#define AD4170_PIN_MUXING_REG 0x69 +#define AD4170_CLOCK_CTRL_REG 0x6B +#define AD4170_ADC_CTRL_REG 0x71 +#define AD4170_CHAN_EN_REG 0x79 +#define AD4170_CHAN_SETUP_REG(x) (0x81 + 4 * (x)) +#define AD4170_CHAN_MAP_REG(x) (0x83 + 4 * (x)) +#define AD4170_MISC_REG(x) (0xC1 + 14 * (x)) +#define AD4170_AFE_REG(x) (0xC3 + 14 * (x)) +#define AD4170_FILTER_REG(x) (0xC5 + 14 * (x)) +#define AD4170_FILTER_FS_REG(x) (0xC7 + 14 * (x)) +#define AD4170_OFFSET_REG(x) (0xCA + 14 * (x)) +#define AD4170_GAIN_REG(x) (0xCD + 14 * (x)) +#define AD4170_V_BIAS_REG 0x135 +#define AD4170_CURRENT_SRC_REG(x) (0x139 + 2 * (x)) +#define AD4170_GPIO_MODE_REG 0x191 +#define AD4170_GPIO_OUTPUT_REG 0x193 +#define AD4170_GPIO_INPUT_REG 0x195 +#define AD4170_ADC_CTRL_CONT_READ_EXIT_REG 0x200 /* virtual reg */ + +#define AD4170_REG_READ_MASK BIT(14) + +/* AD4170_CONFIG_A_REG - INTERFACE_CONFIG_A REGISTER */ +#define AD4170_SW_RESET_MSK (BIT(7) | BIT(0)) + +/* AD4170_PIN_MUXING_REG */ +#define AD4170_PIN_MUXING_DIG_AUX1_CTRL_MSK GENMASK(5, 4) + +/* AD4170_CLOCK_CTRL_REG */ +#define AD4170_CLOCK_CTRL_CLOCKSEL_MSK GENMASK(1, 0) + +/* AD4170_ADC_CTRL_REG */ +#define AD4170_ADC_CTRL_MULTI_DATA_REG_SEL_MSK BIT(7) +#define AD4170_ADC_CTRL_CONT_READ_MSK GENMASK(5, 4) +#define AD4170_ADC_CTRL_MODE_MSK GENMASK(3, 0) + +/* AD4170_CHAN_EN_REG */ +#define AD4170_CHAN_EN(ch) BIT(ch) + +/* AD4170_CHAN_SETUP_REG */ +#define AD4170_CHAN_SETUP_SETUP_MSK GENMASK(2, 0) + +/* AD4170_CHAN_MAP_REG */ +#define AD4170_CHAN_MAP_AINP_MSK GENMASK(12, 8) +#define AD4170_CHAN_MAP_AINM_MSK GENMASK(4, 0) + +/* AD4170_MISC_REG */ +#define AD4170_MISC_CHOP_IEXC_MSK GENMASK(15, 14) +#define AD4170_MISC_CHOP_ADC_MSK GENMASK(9, 8) + +/* AD4170_AFE_REG */ +#define AD4170_AFE_REF_BUF_M_MSK GENMASK(11, 10) +#define AD4170_AFE_REF_BUF_P_MSK GENMASK(9, 8) +#define AD4170_AFE_REF_SELECT_MSK GENMASK(6, 5) +#define AD4170_AFE_BIPOLAR_MSK BIT(4) +#define AD4170_AFE_PGA_GAIN_MSK GENMASK(3, 0) + +/* AD4170_FILTER_REG */ +#define AD4170_FILTER_FILTER_TYPE_MSK GENMASK(3, 0) + +/* AD4170_CURRENT_SRC_REG */ +#define AD4170_CURRENT_SRC_I_OUT_PIN_MSK GENMASK(12, 8) +#define AD4170_CURRENT_SRC_I_OUT_VAL_MSK GENMASK(2, 0) + +/* AD4170_GPIO_MODE_REG */ +#define AD4170_GPIO_MODE_GPIO0_MSK GENMASK(1, 0) +#define AD4170_GPIO_MODE_GPIO1_MSK GENMASK(3, 2) +#define AD4170_GPIO_MODE_GPIO2_MSK GENMASK(5, 4) +#define AD4170_GPIO_MODE_GPIO3_MSK GENMASK(7, 6) + +/* AD4170_GPIO_OUTPUT_REG */ +#define AD4170_GPIO_OUTPUT_GPIO_MSK(x) BIT(x) + +/* AD4170 register constants */ + +/* AD4170_CLOCK_CTRL_REG constants */ +#define AD4170_CLOCK_CTRL_CLOCKSEL_INT 0x0 +#define AD4170_CLOCK_CTRL_CLOCKSEL_INT_OUT 0x1 +#define AD4170_CLOCK_CTRL_CLOCKSEL_EXT 0x2 +#define AD4170_CLOCK_CTRL_CLOCKSEL_EXT_XTAL 0x3 + +/* AD4170_CHAN_MAP_REG constants */ +#define AD4170_CHAN_MAP_AIN(x) (x) +#define AD4170_CHAN_MAP_TEMP_SENSOR 17 +#define AD4170_CHAN_MAP_AVDD_AVSS_P 18 +#define AD4170_CHAN_MAP_AVDD_AVSS_N 18 +#define AD4170_CHAN_MAP_IOVDD_DGND_P 19 +#define AD4170_CHAN_MAP_IOVDD_DGND_N 19 +#define AD4170_CHAN_MAP_AVSS 23 +#define AD4170_CHAN_MAP_DGND 24 +#define AD4170_CHAN_MAP_REFIN1_P 25 +#define AD4170_CHAN_MAP_REFIN1_N 26 +#define AD4170_CHAN_MAP_REFIN2_P 27 +#define AD4170_CHAN_MAP_REFIN2_N 28 +#define AD4170_CHAN_MAP_REFOUT 29 + +/* AD4170_MISC_REG constants */ +#define AD4170_MISC_CHOP_IEXC_PAIR1 0x1 +#define AD4170_MISC_CHOP_IEXC_PAIR2 0x2 +#define AD4170_MISC_CHOP_IEXC_BOTH 0x3 + +/* AD4170_PIN_MUXING_REG constants */ +#define AD4170_PIN_MUXING_DIG_AUX1_DISABLED 0x0 +#define AD4170_PIN_MUXING_DIG_AUX1_RDY 0x1 + +/* AD4170_ADC_CTRL_REG constants */ +#define AD4170_ADC_CTRL_MODE_CONT 0x0 +#define AD4170_ADC_CTRL_MODE_SINGLE 0x4 +#define AD4170_ADC_CTRL_MODE_IDLE 0x7 + +#define AD4170_ADC_CTRL_CONT_READ_DISABLE 0x0 +#define AD4170_ADC_CTRL_CONT_READ_ENABLE 0x1 + +/* AD4170_FILTER_REG constants */ +#define AD4170_FILTER_FILTER_TYPE_SINC5_AVG 0x0 +#define AD4170_FILTER_FILTER_TYPE_SINC5 0x4 +#define AD4170_FILTER_FILTER_TYPE_SINC3 0x6 + +/* AD4170_CURRENT_SRC_REG constants */ +#define AD4170_CURRENT_SRC_I_OUT_PIN_AIN(x) (x) +#define AD4170_CURRENT_SRC_I_OUT_PIN_GPIO(x) ((x) + 17) + +/* AD4170_GPIO_MODE_REG constants */ +#define AD4170_GPIO_MODE_GPIO_INPUT 1 +#define AD4170_GPIO_MODE_GPIO_OUTPUT 2 + +/* Device properties and auxiliary constants */ + +#define AD4170_NUM_ANALOG_PINS 9 +#define AD4170_NUM_GPIO_PINS 4 +#define AD4170_MAX_ADC_CHANNELS 16 +#define AD4170_MAX_IIO_CHANNELS (AD4170_MAX_ADC_CHANNELS + 1) +#define AD4170_MAX_ANALOG_PINS 8 +#define AD4170_MAX_SETUPS 8 +#define AD4170_INVALID_SETUP 9 +#define AD4170_SPI_INST_PHASE_LEN 2 +#define AD4170_SPI_MAX_XFER_LEN 6 +#define AD4170_NUM_CURRENT_SRC 4 +#define AD4170_DEFAULT_SAMP_RATE (125 * HZ_PER_KHZ) + +#define AD4170_INT_REF_2_5V 2500000 + +/* Internal and external clock properties */ +#define AD4170_INT_CLOCK_16MHZ (16 * HZ_PER_MHZ) +#define AD4170_EXT_CLOCK_MHZ_MIN (1 * HZ_PER_MHZ) +#define AD4170_EXT_CLOCK_MHZ_MAX (17 * HZ_PER_MHZ) + +#define AD4170_NUM_PGA_OPTIONS 10 + +/* Digital filter properties */ +#define AD4170_SINC3_MIN_FS 4 +#define AD4170_SINC3_MAX_FS 65532 +#define AD4170_SINC5_MIN_FS 1 +#define AD4170_SINC5_MAX_FS 256 + +#define AD4170_GAIN_REG_DEFAULT 0x555555 + +#define AD4170_ADC_CTRL_CONT_READ_EXIT 0xA5 + +/* Analog pin functions */ +#define AD4170_PIN_UNASSIGNED 0x00 +#define AD4170_PIN_ANALOG_IN 0x01 +#define AD4170_PIN_CURRENT_OUT 0x02 +#define AD4170_PIN_VBIAS 0x04 + +/* GPIO pin functions */ +#define AD4170_GPIO_UNASSIGNED 0x00 +#define AD4170_GPIO_AC_EXCITATION 0x02 +#define AD4170_GPIO_OUTPUT 0x04 + +/* Current source */ +#define AD4170_CURRENT_SRC_DISABLED 0xFF + +static const unsigned int ad4170_reg_size[] = { + [AD4170_CONFIG_A_REG] = 1, + [AD4170_DATA_24B_REG] = 3, + [AD4170_PIN_MUXING_REG] = 2, + [AD4170_CLOCK_CTRL_REG] = 2, + [AD4170_ADC_CTRL_REG] = 2, + [AD4170_CHAN_EN_REG] = 2, + /* + * CHANNEL_SETUP and CHANNEL_MAP register are all 2 byte size each and + * their addresses are interleaved such that we have CHANNEL_SETUP0 + * address followed by CHANNEL_MAP0 address, followed by CHANNEL_SETUP1, + * and so on until CHANNEL_MAP15. + * Thus, initialize the register size for them only once. + */ + [AD4170_CHAN_SETUP_REG(0) ... AD4170_CHAN_MAP_REG(AD4170_MAX_ADC_CHANNELS - 1)] = 2, + /* + * MISC, AFE, FILTER, FILTER_FS, OFFSET, and GAIN register addresses are + * also interleaved but MISC, AFE, FILTER, FILTER_FS, OFFSET are 16-bit + * while OFFSET, GAIN are 24-bit registers so we can't init them all to + * the same size. + */ + [AD4170_MISC_REG(0) ... AD4170_FILTER_FS_REG(0)] = 2, + [AD4170_MISC_REG(1) ... AD4170_FILTER_FS_REG(1)] = 2, + [AD4170_MISC_REG(2) ... AD4170_FILTER_FS_REG(2)] = 2, + [AD4170_MISC_REG(3) ... AD4170_FILTER_FS_REG(3)] = 2, + [AD4170_MISC_REG(4) ... AD4170_FILTER_FS_REG(4)] = 2, + [AD4170_MISC_REG(5) ... AD4170_FILTER_FS_REG(5)] = 2, + [AD4170_MISC_REG(6) ... AD4170_FILTER_FS_REG(6)] = 2, + [AD4170_MISC_REG(7) ... AD4170_FILTER_FS_REG(7)] = 2, + [AD4170_OFFSET_REG(0) ... AD4170_GAIN_REG(0)] = 3, + [AD4170_OFFSET_REG(1) ... AD4170_GAIN_REG(1)] = 3, + [AD4170_OFFSET_REG(2) ... AD4170_GAIN_REG(2)] = 3, + [AD4170_OFFSET_REG(3) ... AD4170_GAIN_REG(3)] = 3, + [AD4170_OFFSET_REG(4) ... AD4170_GAIN_REG(4)] = 3, + [AD4170_OFFSET_REG(5) ... AD4170_GAIN_REG(5)] = 3, + [AD4170_OFFSET_REG(6) ... AD4170_GAIN_REG(6)] = 3, + [AD4170_OFFSET_REG(7) ... AD4170_GAIN_REG(7)] = 3, + [AD4170_V_BIAS_REG] = 2, + [AD4170_CURRENT_SRC_REG(0) ... AD4170_CURRENT_SRC_REG(3)] = 2, + [AD4170_GPIO_MODE_REG] = 2, + [AD4170_GPIO_OUTPUT_REG] = 2, + [AD4170_GPIO_INPUT_REG] = 2, + [AD4170_ADC_CTRL_CONT_READ_EXIT_REG] = 0, +}; + +enum ad4170_ref_buf { + AD4170_REF_BUF_PRE, /* Pre-charge reference buffer */ + AD4170_REF_BUF_FULL, /* Full reference buffering */ + AD4170_REF_BUF_BYPASS, /* Bypass reference buffering */ +}; + +/* maps adi,positive/negative-reference-buffer property values to enum */ +static const char * const ad4170_ref_buf_str[] = { + [AD4170_REF_BUF_PRE] = "precharge", + [AD4170_REF_BUF_FULL] = "full", + [AD4170_REF_BUF_BYPASS] = "disabled", +}; + +enum ad4170_ref_select { + AD4170_REF_REFIN1, + AD4170_REF_REFIN2, + AD4170_REF_REFOUT, + AD4170_REF_AVDD, +}; + +enum ad4170_filter_type { + AD4170_SINC5_AVG, + AD4170_SINC5, + AD4170_SINC3, +}; + +enum ad4170_regulator { + AD4170_AVDD_SUP, + AD4170_AVSS_SUP, + AD4170_IOVDD_SUP, + AD4170_REFIN1P_SUP, + AD4170_REFIN1N_SUP, + AD4170_REFIN2P_SUP, + AD4170_REFIN2N_SUP, + AD4170_MAX_SUP, +}; + +static const char *const ad4170_clk_sel[] = { + "ext-clk", "xtal", +}; + +enum ad4170_int_pin_sel { + AD4170_INT_PIN_SDO, + AD4170_INT_PIN_DIG_AUX1, +}; + +static const char * const ad4170_int_pin_names[] = { + [AD4170_INT_PIN_SDO] = "sdo", + [AD4170_INT_PIN_DIG_AUX1] = "dig_aux1", +}; + +static const unsigned int ad4170_sinc3_filt_fs_tbl[] = { + 4, 8, 12, 16, 20, 40, 48, 80, /* 0 - 7 */ + 100, 256, 500, 1000, 5000, 8332, 10000, 25000, /* 8 - 15 */ + 50000, 65532, /* 16 - 17 */ +}; + +#define AD4170_MAX_FS_TBL_SIZE ARRAY_SIZE(ad4170_sinc3_filt_fs_tbl) + +static const unsigned int ad4170_sinc5_filt_fs_tbl[] = { + 1, 2, 4, 8, 12, 16, 20, 40, 48, 80, 100, 256, +}; + +static const unsigned int ad4170_iout_pin_tbl[] = { + AD4170_CURRENT_SRC_I_OUT_PIN_AIN(0), + AD4170_CURRENT_SRC_I_OUT_PIN_AIN(1), + AD4170_CURRENT_SRC_I_OUT_PIN_AIN(2), + AD4170_CURRENT_SRC_I_OUT_PIN_AIN(3), + AD4170_CURRENT_SRC_I_OUT_PIN_AIN(4), + AD4170_CURRENT_SRC_I_OUT_PIN_AIN(5), + AD4170_CURRENT_SRC_I_OUT_PIN_AIN(6), + AD4170_CURRENT_SRC_I_OUT_PIN_AIN(7), + AD4170_CURRENT_SRC_I_OUT_PIN_AIN(8), + AD4170_CURRENT_SRC_I_OUT_PIN_GPIO(0), + AD4170_CURRENT_SRC_I_OUT_PIN_GPIO(1), + AD4170_CURRENT_SRC_I_OUT_PIN_GPIO(2), + AD4170_CURRENT_SRC_I_OUT_PIN_GPIO(3), +}; + +static const unsigned int ad4170_iout_current_ua_tbl[] = { + 0, 10, 50, 100, 250, 500, 1000, 1500, +}; + +enum ad4170_sensor_enum { + AD4170_ADC_SENSOR = 0, + AD4170_WEIGH_SCALE_SENSOR = 1, + AD4170_RTD_SENSOR = 2, + AD4170_THERMOCOUPLE_SENSOR = 3, +}; + +/* maps adi,sensor-type property value to enum */ +static const char * const ad4170_sensor_type[] = { + [AD4170_ADC_SENSOR] = "adc", + [AD4170_WEIGH_SCALE_SENSOR] = "weighscale", + [AD4170_RTD_SENSOR] = "rtd", + [AD4170_THERMOCOUPLE_SENSOR] = "thermocouple", +}; + +struct ad4170_chip_info { + const char *name; +}; + +static const struct ad4170_chip_info ad4170_chip_info = { + .name = "ad4170-4", +}; + +static const struct ad4170_chip_info ad4190_chip_info = { + .name = "ad4190-4", +}; + +static const struct ad4170_chip_info ad4195_chip_info = { + .name = "ad4195-4", +}; + +/* + * There are 8 of each MISC, AFE, FILTER, FILTER_FS, OFFSET, and GAIN + * configuration registers. That is, there are 8 miscellaneous registers, MISC0 + * to MISC7. Each MISC register is associated with a setup; MISCN is associated + * with setup number N. The other 5 above mentioned types of registers have + * analogous structure. A setup is a set of those registers. For example, + * setup 1 comprises of MISC1, AFE1, FILTER1, FILTER_FS1, OFFSET1, and GAIN1 + * registers. Also, there are 16 CHANNEL_SETUP registers (CHANNEL_SETUP0 to + * CHANNEL_SETUP15). Each channel setup is associated with one of the 8 possible + * setups. Thus, AD4170 can support up to 16 channels but, since there are only + * 8 available setups, channels must share settings if more than 8 channels are + * configured. + * + * If this struct is modified, ad4170_setup_eq() will probably need to be + * updated too. + */ +struct ad4170_setup { + u16 misc; + u16 afe; + u16 filter; + u16 filter_fs; + u32 offset; /* For calibration purposes */ + u32 gain; /* For calibration purposes */ +}; + +struct ad4170_setup_info { + struct ad4170_setup setup; + unsigned int enabled_channels; + unsigned int channels; +}; + +struct ad4170_chan_info { + unsigned int input_range_uv; + unsigned int setup_num; /* Index to access state setup_infos array */ + struct ad4170_setup setup; /* cached setup */ + int offset_tbl[10]; + u32 scale_tbl[10][2]; + bool initialized; + bool enabled; +}; + +static const char * const ad4170_filt_names[] = { + [AD4170_SINC5_AVG] = "sinc5+avg", + [AD4170_SINC5] = "sinc5", + [AD4170_SINC3] = "sinc3", +}; + +struct ad4170_state { + struct mutex lock; /* Protect read-modify-write and multi write sequences */ + int vrefs_uv[AD4170_MAX_SUP]; + u32 mclk_hz; + struct ad4170_setup_info setup_infos[AD4170_MAX_SETUPS]; + struct ad4170_chan_info chan_infos[AD4170_MAX_ADC_CHANNELS]; + struct completion completion; + struct iio_chan_spec chans[AD4170_MAX_IIO_CHANNELS]; + struct spi_device *spi; + struct regmap *regmap; + int sps_tbl[ARRAY_SIZE(ad4170_filt_names)][AD4170_MAX_FS_TBL_SIZE][2]; + __be32 bounce_buffer[AD4170_MAX_ADC_CHANNELS]; + struct spi_message msg; + struct spi_transfer xfer; + struct iio_trigger *trig; + struct clk_hw int_clk_hw; + unsigned int clock_ctrl; + unsigned int pins_fn[AD4170_NUM_ANALOG_PINS]; + int gpio_fn[AD4170_NUM_GPIO_PINS]; + unsigned int cur_src_pins[AD4170_NUM_CURRENT_SRC]; + struct gpio_chip gpiochip; + /* + * DMA (thus cache coherency maintenance) requires the transfer buffers + * to live in their own cache lines. + */ + u8 rx_buf[4] __aligned(IIO_DMA_MINALIGN); +}; + +static void ad4170_fill_sps_tbl(struct ad4170_state *st) +{ + unsigned int tmp0, tmp1, i; + + /* + * The ODR can be calculated the same way for sinc5+avg, sinc5, and + * sinc3 filter types with the exception that sinc5 filter has a + * narrowed range of allowed FILTER_FS values. + */ + for (i = 0; i < ARRAY_SIZE(ad4170_sinc3_filt_fs_tbl); i++) { + tmp0 = div_u64_rem(st->mclk_hz, 32 * ad4170_sinc3_filt_fs_tbl[i], + &tmp1); + tmp1 = mult_frac(tmp1, MICRO, 32 * ad4170_sinc3_filt_fs_tbl[i]); + /* Fill sinc5+avg filter SPS table */ + st->sps_tbl[AD4170_SINC5_AVG][i][0] = tmp0; /* Integer part */ + st->sps_tbl[AD4170_SINC5_AVG][i][1] = tmp1; /* Fractional part */ + + /* Fill sinc3 filter SPS table */ + st->sps_tbl[AD4170_SINC3][i][0] = tmp0; /* Integer part */ + st->sps_tbl[AD4170_SINC3][i][1] = tmp1; /* Fractional part */ + } + /* Sinc5 filter ODR doesn't use all FILTER_FS bits */ + for (i = 0; i < ARRAY_SIZE(ad4170_sinc5_filt_fs_tbl); i++) { + tmp0 = div_u64_rem(st->mclk_hz, 32 * ad4170_sinc5_filt_fs_tbl[i], + &tmp1); + tmp1 = mult_frac(tmp1, MICRO, 32 * ad4170_sinc5_filt_fs_tbl[i]); + /* Fill sinc5 filter SPS table */ + st->sps_tbl[AD4170_SINC5][i][0] = tmp0; /* Integer part */ + st->sps_tbl[AD4170_SINC5][i][1] = tmp1; /* Fractional part */ + } +} + +static int ad4170_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + struct ad4170_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); +} + +static int ad4170_get_reg_size(struct ad4170_state *st, unsigned int reg, + unsigned int *size) +{ + if (reg >= ARRAY_SIZE(ad4170_reg_size)) + return -EINVAL; + + *size = ad4170_reg_size[reg]; + + return 0; +} + +static int ad4170_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct ad4170_state *st = context; + u8 tx_buf[AD4170_SPI_MAX_XFER_LEN]; + unsigned int size; + int ret; + + ret = ad4170_get_reg_size(st, reg, &size); + if (ret) + return ret; + + put_unaligned_be16(reg, tx_buf); + switch (size) { + case 3: + put_unaligned_be24(val, &tx_buf[AD4170_SPI_INST_PHASE_LEN]); + break; + case 2: + put_unaligned_be16(val, &tx_buf[AD4170_SPI_INST_PHASE_LEN]); + break; + case 1: + tx_buf[AD4170_SPI_INST_PHASE_LEN] = val; + break; + case 0: + /* Write continuous read exit code */ + tx_buf[0] = AD4170_ADC_CTRL_CONT_READ_EXIT; + return spi_write_then_read(st->spi, tx_buf, 1, NULL, 0); + default: + return -EINVAL; + } + + return spi_write_then_read(st->spi, tx_buf, + AD4170_SPI_INST_PHASE_LEN + size, NULL, 0); +} + +static int ad4170_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + struct ad4170_state *st = context; + u8 tx_buf[AD4170_SPI_INST_PHASE_LEN]; + unsigned int size; + int ret; + + put_unaligned_be16(AD4170_REG_READ_MASK | reg, tx_buf); + + ret = ad4170_get_reg_size(st, reg, &size); + if (ret) + return ret; + + ret = spi_write_then_read(st->spi, tx_buf, ARRAY_SIZE(tx_buf), + st->rx_buf, size); + if (ret) + return ret; + + switch (size) { + case 3: + *val = get_unaligned_be24(st->rx_buf); + return 0; + case 2: + *val = get_unaligned_be16(st->rx_buf); + return 0; + case 1: + *val = st->rx_buf[0]; + return 0; + default: + return -EINVAL; + } +} + +static const struct regmap_config ad4170_regmap_config = { + .reg_read = ad4170_reg_read, + .reg_write = ad4170_reg_write, +}; + +static bool ad4170_setup_eq(struct ad4170_setup *a, struct ad4170_setup *b) +{ + if (a->misc != b->misc || + a->afe != b->afe || + a->filter != b->filter || + a->filter_fs != b->filter_fs || + a->offset != b->offset || + a->gain != b->gain) + return false; + + return true; +} + +static int ad4170_find_setup(struct ad4170_state *st, + struct ad4170_setup *target_setup, + unsigned int *setup_num, bool *overwrite) +{ + unsigned int i; + + *setup_num = AD4170_INVALID_SETUP; + *overwrite = false; + + for (i = 0; i < AD4170_MAX_SETUPS; i++) { + struct ad4170_setup_info *setup_info = &st->setup_infos[i]; + + /* Immediately accept a matching setup. */ + if (ad4170_setup_eq(target_setup, &setup_info->setup)) { + *setup_num = i; + return 0; + } + + /* Ignore all setups which are used by enabled channels. */ + if (setup_info->enabled_channels) + continue; + + /* Find the least used slot. */ + if (*setup_num == AD4170_INVALID_SETUP || + setup_info->channels < st->setup_infos[*setup_num].channels) + *setup_num = i; + } + + if (*setup_num == AD4170_INVALID_SETUP) + return -EINVAL; + + *overwrite = true; + return 0; +} + +static void ad4170_unlink_channel(struct ad4170_state *st, unsigned int channel) +{ + struct ad4170_chan_info *chan_info = &st->chan_infos[channel]; + struct ad4170_setup_info *setup_info = &st->setup_infos[chan_info->setup_num]; + + chan_info->setup_num = AD4170_INVALID_SETUP; + setup_info->channels--; +} + +static int ad4170_unlink_setup(struct ad4170_state *st, unsigned int setup_num) +{ + unsigned int i; + + for (i = 0; i < AD4170_MAX_ADC_CHANNELS; i++) { + struct ad4170_chan_info *chan_info = &st->chan_infos[i]; + + if (!chan_info->initialized || chan_info->setup_num != setup_num) + continue; + + ad4170_unlink_channel(st, i); + } + return 0; +} + +static int ad4170_link_channel_setup(struct ad4170_state *st, + unsigned int chan_addr, + unsigned int setup_num) +{ + struct ad4170_setup_info *setup_info = &st->setup_infos[setup_num]; + struct ad4170_chan_info *chan_info = &st->chan_infos[chan_addr]; + int ret; + + ret = regmap_update_bits(st->regmap, AD4170_CHAN_SETUP_REG(chan_addr), + AD4170_CHAN_SETUP_SETUP_MSK, + FIELD_PREP(AD4170_CHAN_SETUP_SETUP_MSK, setup_num)); + if (ret) + return ret; + + chan_info->setup_num = setup_num; + setup_info->channels++; + return 0; +} + +static int ad4170_write_setup(struct ad4170_state *st, unsigned int setup_num, + struct ad4170_setup *setup) +{ + int ret; + + /* + * It is recommended to place the ADC in standby mode or idle mode to + * write to OFFSET and GAIN registers. + */ + ret = regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG, + AD4170_ADC_CTRL_MODE_MSK, + FIELD_PREP(AD4170_ADC_CTRL_MODE_MSK, + AD4170_ADC_CTRL_MODE_IDLE)); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD4170_MISC_REG(setup_num), setup->misc); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD4170_AFE_REG(setup_num), setup->afe); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD4170_FILTER_REG(setup_num), + setup->filter); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD4170_FILTER_FS_REG(setup_num), + setup->filter_fs); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD4170_OFFSET_REG(setup_num), + setup->offset); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD4170_GAIN_REG(setup_num), setup->gain); + if (ret) + return ret; + + memcpy(&st->setup_infos[setup_num].setup, setup, sizeof(*setup)); + return 0; +} + +static int ad4170_write_channel_setup(struct ad4170_state *st, + unsigned int chan_addr, bool on_enable) +{ + struct ad4170_chan_info *chan_info = &st->chan_infos[chan_addr]; + bool overwrite; + int setup_num; + int ret; + + /* + * Similar to AD4130 driver, the following cases need to be handled. + * + * 1. Enabled and linked channel with setup changes: + * - Find a setup. If not possible, return error. + * - Unlink channel from current setup. + * - If the setup found has only disabled channels linked to it, + * unlink all channels, and write the new setup to it. + * - Link channel to new setup. + * + * 2. Soon to be enabled and unlinked channel: + * - Find a setup. If not possible, return error. + * - If the setup found has only disabled channels linked to it, + * unlink all channels, and write the new setup to it. + * - Link channel to the setup. + * + * 3. Disabled and linked channel with setup changes: + * - Unlink channel from current setup. + * + * 4. Soon to be enabled and linked channel: + * 5. Disabled and unlinked channel with setup changes: + * - Do nothing. + */ + + /* Cases 3, 4, and 5 */ + if (chan_info->setup_num != AD4170_INVALID_SETUP) { + /* Case 4 */ + if (on_enable) + return 0; + + /* Case 3 */ + if (!chan_info->enabled) { + ad4170_unlink_channel(st, chan_addr); + return 0; + } + } else if (!on_enable && !chan_info->enabled) { + /* Case 5 */ + return 0; + } + + /* Cases 1 & 2 */ + ret = ad4170_find_setup(st, &chan_info->setup, &setup_num, &overwrite); + if (ret) + return ret; + + if (chan_info->setup_num != AD4170_INVALID_SETUP) + /* Case 1 */ + ad4170_unlink_channel(st, chan_addr); + + if (overwrite) { + ret = ad4170_unlink_setup(st, setup_num); + if (ret) + return ret; + + ret = ad4170_write_setup(st, setup_num, &chan_info->setup); + if (ret) + return ret; + } + + return ad4170_link_channel_setup(st, chan_addr, setup_num); +} + +static int ad4170_set_channel_enable(struct ad4170_state *st, + unsigned int chan_addr, bool status) +{ + struct ad4170_chan_info *chan_info = &st->chan_infos[chan_addr]; + struct ad4170_setup_info *setup_info; + int ret; + + if (chan_info->enabled == status) + return 0; + + if (status) { + ret = ad4170_write_channel_setup(st, chan_addr, true); + if (ret) + return ret; + } + + setup_info = &st->setup_infos[chan_info->setup_num]; + + ret = regmap_update_bits(st->regmap, AD4170_CHAN_EN_REG, + AD4170_CHAN_EN(chan_addr), + status ? AD4170_CHAN_EN(chan_addr) : 0); + if (ret) + return ret; + + setup_info->enabled_channels += status ? 1 : -1; + chan_info->enabled = status; + return 0; +} + +static int __ad4170_get_filter_type(unsigned int filter) +{ + u16 f_conf = FIELD_GET(AD4170_FILTER_FILTER_TYPE_MSK, filter); + + switch (f_conf) { + case AD4170_FILTER_FILTER_TYPE_SINC5_AVG: + return AD4170_SINC5_AVG; + case AD4170_FILTER_FILTER_TYPE_SINC5: + return AD4170_SINC5; + case AD4170_FILTER_FILTER_TYPE_SINC3: + return AD4170_SINC3; + default: + return -EINVAL; + } +} + +static int ad4170_set_filter_type(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + unsigned int val) +{ + struct ad4170_state *st = iio_priv(indio_dev); + struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address]; + struct ad4170_setup *setup = &chan_info->setup; + unsigned int filter_type_conf; + int ret; + + switch (val) { + case AD4170_SINC5_AVG: + filter_type_conf = AD4170_FILTER_FILTER_TYPE_SINC5_AVG; + break; + case AD4170_SINC5: + filter_type_conf = AD4170_FILTER_FILTER_TYPE_SINC5; + break; + case AD4170_SINC3: + filter_type_conf = AD4170_FILTER_FILTER_TYPE_SINC3; + break; + default: + return -EINVAL; + } + + /* + * The filters provide the same ODR for a given filter_fs value but + * there are different minimum and maximum filter_fs limits for each + * filter. The filter_fs value will be adjusted if the current filter_fs + * is out of the limits of the just requested filter. Since the + * filter_fs value affects the ODR (sampling_frequency), changing the + * filter may lead to a change in the sampling frequency. + */ + scoped_guard(mutex, &st->lock) { + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + if (val == AD4170_SINC5_AVG || val == AD4170_SINC3) + setup->filter_fs = clamp(val, AD4170_SINC3_MIN_FS, + AD4170_SINC3_MAX_FS); + else + setup->filter_fs = clamp(val, AD4170_SINC5_MIN_FS, + AD4170_SINC5_MAX_FS); + + setup->filter &= ~AD4170_FILTER_FILTER_TYPE_MSK; + setup->filter |= FIELD_PREP(AD4170_FILTER_FILTER_TYPE_MSK, + filter_type_conf); + + ret = ad4170_write_channel_setup(st, chan->address, false); + iio_device_release_direct(indio_dev); + } + + return ret; +} + +static int ad4170_get_filter_type(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + struct ad4170_state *st = iio_priv(indio_dev); + struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address]; + struct ad4170_setup *setup = &chan_info->setup; + + return __ad4170_get_filter_type(setup->filter); +} + +static const struct iio_enum ad4170_filter_type_enum = { + .items = ad4170_filt_names, + .num_items = ARRAY_SIZE(ad4170_filt_names), + .get = ad4170_get_filter_type, + .set = ad4170_set_filter_type, +}; + +static const struct iio_chan_spec_ext_info ad4170_filter_type_ext_info[] = { + IIO_ENUM("filter_type", IIO_SEPARATE, &ad4170_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_TYPE, + &ad4170_filter_type_enum), + { } +}; + +static const struct iio_chan_spec ad4170_channel_template = { + .type = IIO_VOLTAGE, + .indexed = 1, + .differential = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_OFFSET), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .ext_info = ad4170_filter_type_ext_info, + .scan_type = { + .realbits = 24, + .storagebits = 32, + .shift = 8, + .endianness = IIO_BE, + }, +}; + +static const struct iio_chan_spec ad4170_temp_channel_template = { + .type = IIO_TEMP, + .indexed = 0, + .channel = 17, + .channel2 = 17, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + .shift = 8, + .endianness = IIO_BE, + }, +}; + +/* + * Receives the number of a multiplexed AD4170 input (ain_n), and stores the + * voltage (in µV) of the specified input into ain_voltage. If the input number + * is a ordinary analog input (AIN0 to AIN8), stores zero into ain_voltage. + * If a voltage regulator required by a special input is unavailable, return + * error code. Return 0 on success. + */ +static int ad4170_get_ain_voltage_uv(struct ad4170_state *st, int ain_n, + int *ain_voltage) +{ + struct device *dev = &st->spi->dev; + int v_diff; + + *ain_voltage = 0; + /* + * The voltage bias (vbias) sets the common-mode voltage of the channel + * to (AVDD + AVSS)/2. If provided, AVSS supply provides the magnitude + * (absolute value) of the negative voltage supplied to the AVSS pin. + * So, we do AVDD - AVSS to compute the DC voltage generated by the bias + * voltage generator. + */ + if (st->pins_fn[ain_n] & AD4170_PIN_VBIAS) { + int v_diff = st->vrefs_uv[AD4170_AVDD_SUP] - st->vrefs_uv[AD4170_AVSS_SUP]; + *ain_voltage = v_diff / 2; + return 0; + } + + if (ain_n <= AD4170_CHAN_MAP_TEMP_SENSOR) + return 0; + + switch (ain_n) { + case AD4170_CHAN_MAP_AVDD_AVSS_N: + v_diff = st->vrefs_uv[AD4170_AVDD_SUP] - st->vrefs_uv[AD4170_AVSS_SUP]; + *ain_voltage = v_diff / 5; + return 0; + case AD4170_CHAN_MAP_IOVDD_DGND_N: + *ain_voltage = st->vrefs_uv[AD4170_IOVDD_SUP] / 5; + return 0; + case AD4170_CHAN_MAP_AVSS: + *ain_voltage = st->vrefs_uv[AD4170_AVSS_SUP]; + return 0; + case AD4170_CHAN_MAP_DGND: + *ain_voltage = 0; + return 0; + case AD4170_CHAN_MAP_REFIN1_P: + if (st->vrefs_uv[AD4170_REFIN1P_SUP] == -ENODEV) + return dev_err_probe(dev, -ENODEV, + "input set to REFIN+ but ref not provided\n"); + + *ain_voltage = st->vrefs_uv[AD4170_REFIN1P_SUP]; + return 0; + case AD4170_CHAN_MAP_REFIN1_N: + if (st->vrefs_uv[AD4170_REFIN1N_SUP] == -ENODEV) + return dev_err_probe(dev, -ENODEV, + "input set to REFIN- but ref not provided\n"); + + *ain_voltage = st->vrefs_uv[AD4170_REFIN1N_SUP]; + return 0; + case AD4170_CHAN_MAP_REFIN2_P: + if (st->vrefs_uv[AD4170_REFIN2P_SUP] == -ENODEV) + return dev_err_probe(dev, -ENODEV, + "input set to REFIN2+ but ref not provided\n"); + + *ain_voltage = st->vrefs_uv[AD4170_REFIN2P_SUP]; + return 0; + case AD4170_CHAN_MAP_REFIN2_N: + if (st->vrefs_uv[AD4170_REFIN2N_SUP] == -ENODEV) + return dev_err_probe(dev, -ENODEV, + "input set to REFIN2- but ref not provided\n"); + + *ain_voltage = st->vrefs_uv[AD4170_REFIN2N_SUP]; + return 0; + case AD4170_CHAN_MAP_REFOUT: + /* REFOUT is 2.5V relative to AVSS so take that into account */ + *ain_voltage = st->vrefs_uv[AD4170_AVSS_SUP] + AD4170_INT_REF_2_5V; + return 0; + default: + return -EINVAL; + } +} + +static int ad4170_validate_analog_input(struct ad4170_state *st, int pin) +{ + if (pin <= AD4170_MAX_ANALOG_PINS) { + if (st->pins_fn[pin] & AD4170_PIN_CURRENT_OUT) + return dev_err_probe(&st->spi->dev, -EINVAL, + "Pin %d already used with fn %u.\n", + pin, st->pins_fn[pin]); + + st->pins_fn[pin] |= AD4170_PIN_ANALOG_IN; + } + return 0; +} + +static int ad4170_validate_channel_input(struct ad4170_state *st, int pin, bool com) +{ + /* Check common-mode input pin is mapped to a special input. */ + if (com && (pin < AD4170_CHAN_MAP_AVDD_AVSS_P || pin > AD4170_CHAN_MAP_REFOUT)) + return dev_err_probe(&st->spi->dev, -EINVAL, + "Invalid common-mode input pin number. %d\n", + pin); + + /* Check differential input pin is mapped to a analog input pin. */ + if (!com && pin > AD4170_MAX_ANALOG_PINS) + return dev_err_probe(&st->spi->dev, -EINVAL, + "Invalid analog input pin number. %d\n", + pin); + + return ad4170_validate_analog_input(st, pin); +} + +/* + * Verifies whether the channel input configuration is valid by checking the + * input numbers. + * Returns 0 on valid channel input configuration. -EINVAL otherwise. + */ +static int ad4170_validate_channel(struct ad4170_state *st, + struct iio_chan_spec const *chan) +{ + int ret; + + ret = ad4170_validate_channel_input(st, chan->channel, false); + if (ret) + return ret; + + return ad4170_validate_channel_input(st, chan->channel2, + !chan->differential); +} + +/* + * Verifies whether the channel configuration is valid by checking the provided + * input type, polarity, and voltage references result in a sane input range. + * Returns negative error code on failure. + */ +static int ad4170_get_input_range(struct ad4170_state *st, + struct iio_chan_spec const *chan, + unsigned int ch_reg, unsigned int ref_sel) +{ + bool bipolar = chan->scan_type.sign == 's'; + struct device *dev = &st->spi->dev; + int refp, refn, ain_voltage, ret; + + switch (ref_sel) { + case AD4170_REF_REFIN1: + if (st->vrefs_uv[AD4170_REFIN1P_SUP] == -ENODEV || + st->vrefs_uv[AD4170_REFIN1N_SUP] == -ENODEV) + return dev_err_probe(dev, -ENODEV, + "REFIN± selected but not provided\n"); + + refp = st->vrefs_uv[AD4170_REFIN1P_SUP]; + refn = st->vrefs_uv[AD4170_REFIN1N_SUP]; + break; + case AD4170_REF_REFIN2: + if (st->vrefs_uv[AD4170_REFIN2P_SUP] == -ENODEV || + st->vrefs_uv[AD4170_REFIN2N_SUP] == -ENODEV) + return dev_err_probe(dev, -ENODEV, + "REFIN2± selected but not provided\n"); + + refp = st->vrefs_uv[AD4170_REFIN2P_SUP]; + refn = st->vrefs_uv[AD4170_REFIN2N_SUP]; + break; + case AD4170_REF_AVDD: + refp = st->vrefs_uv[AD4170_AVDD_SUP]; + refn = st->vrefs_uv[AD4170_AVSS_SUP]; + break; + case AD4170_REF_REFOUT: + /* REFOUT is 2.5 V relative to AVSS */ + refp = st->vrefs_uv[AD4170_AVSS_SUP] + AD4170_INT_REF_2_5V; + refn = st->vrefs_uv[AD4170_AVSS_SUP]; + break; + default: + return -EINVAL; + } + + /* + * Find out the analog input range from the channel type, polarity, and + * voltage reference selection. + * AD4170 channels are either differential or pseudo-differential. + * Diff input voltage range: −VREF/gain to +VREF/gain (datasheet page 6) + * Pseudo-diff input voltage range: 0 to VREF/gain (datasheet page 6) + */ + if (chan->differential) { + if (!bipolar) + return dev_err_probe(dev, -EINVAL, + "Channel %u differential unipolar\n", + ch_reg); + + /* + * Differential bipolar channel. + * avss-supply is never above 0V. + * Assuming refin1n-supply not above 0V. + * Assuming refin2n-supply not above 0V. + */ + return refp + abs(refn); + } + /* + * Some configurations can lead to invalid setups. + * For example, if AVSS = -2.5V, REF_SELECT set to REFOUT (REFOUT/AVSS), + * and pseudo-diff channel configuration set, then the input range + * should go from 0V to +VREF (single-ended - datasheet pg 10), but + * REFOUT/AVSS range would be -2.5V to 0V. + * Check the positive reference is higher than 0V for pseudo-diff + * channels. + * Note that at this point in the code, refp can only be >= 0 since all + * error codes from reading the regulator voltage have been checked + * either at ad4170_regulator_setup() or above in this function. + */ + if (refp == 0) + return dev_err_probe(dev, -EINVAL, + "REF+ == GND for pseudo-diff chan %u\n", + ch_reg); + + if (bipolar) + return refp; + + /* + * Pseudo-differential unipolar channel. + * Input expected to swing from IN- to +VREF. + */ + ret = ad4170_get_ain_voltage_uv(st, chan->channel2, &ain_voltage); + if (ret) + return ret; + + if (refp - ain_voltage <= 0) + return dev_err_probe(dev, -EINVAL, + "Negative input >= REF+ for pseudo-diff chan %u\n", + ch_reg); + + return refp - ain_voltage; +} + +static int __ad4170_read_sample(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val) +{ + struct ad4170_state *st = iio_priv(indio_dev); + unsigned long settling_time_ms; + int ret; + + reinit_completion(&st->completion); + ret = regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG, + AD4170_ADC_CTRL_MODE_MSK, + FIELD_PREP(AD4170_ADC_CTRL_MODE_MSK, + AD4170_ADC_CTRL_MODE_SINGLE)); + if (ret) + return ret; + + /* + * When a channel is manually selected by the user, the ADC needs an + * extra time to provide the first stable conversion. The ADC settling + * time depends on the filter type, filter frequency, and ADC clock + * frequency (see datasheet page 53). The maximum settling time among + * all filter configurations is 6291164 / fCLK. Use that formula to wait + * for sufficient time whatever the filter configuration may be. + */ + settling_time_ms = DIV_ROUND_UP(6291164 * MILLI, st->mclk_hz); + ret = wait_for_completion_timeout(&st->completion, + msecs_to_jiffies(settling_time_ms)); + if (!ret) + dev_dbg(&st->spi->dev, + "No Data Ready signal. Reading after delay.\n"); + + ret = regmap_read(st->regmap, AD4170_DATA_24B_REG, val); + if (ret) + return ret; + + if (chan->scan_type.sign == 's') + *val = sign_extend32(*val, chan->scan_type.realbits - 1); + + return 0; +} + +static int ad4170_read_sample(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val) +{ + struct ad4170_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + int ret, ret2; + + /* + * The ADC sequences through all enabled channels. That can lead to + * incorrect channel being sampled if a previous read would have left a + * different channel enabled. Thus, always enable and disable the + * channel on single-shot read. + */ + ret = ad4170_set_channel_enable(st, chan->address, true); + if (ret) + return ret; + + ret = __ad4170_read_sample(indio_dev, chan, val); + if (ret) { + dev_err(dev, "failed to read sample: %d\n", ret); + + ret2 = ad4170_set_channel_enable(st, chan->address, false); + if (ret2) + dev_err(dev, "failed to disable channel: %d\n", ret2); + + return ret; + } + + ret = ad4170_set_channel_enable(st, chan->address, false); + if (ret) + return ret; + + return IIO_VAL_INT; +} + +static int ad4170_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad4170_state *st = iio_priv(indio_dev); + struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address]; + struct ad4170_setup *setup = &chan_info->setup; + enum ad4170_filter_type f_type; + unsigned int pga, fs_idx; + int ret; + + guard(mutex)(&st->lock); + switch (info) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad4170_read_sample(indio_dev, chan, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SCALE: + pga = FIELD_GET(AD4170_AFE_PGA_GAIN_MSK, setup->afe); + switch (chan->type) { + case IIO_VOLTAGE: + *val = chan_info->scale_tbl[pga][0]; + *val2 = chan_info->scale_tbl[pga][1]; + return IIO_VAL_INT_PLUS_NANO; + case IIO_TEMP: + /* + * The scale_tbl converts output codes to mV units so + * multiply by MILLI to make the factor convert to µV. + * Then, apply the temperature sensor change sensitivity + * of 477 μV/K. Finally, multiply the result by MILLI + * again to comply with milli degrees Celsius IIO ABI. + */ + *val = 0; + *val2 = DIV_ROUND_CLOSEST(chan_info->scale_tbl[pga][1] * MILLI, 477) * + MILLI; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + pga = FIELD_GET(AD4170_AFE_PGA_GAIN_MSK, setup->afe); + *val = chan_info->offset_tbl[pga]; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + f_type = __ad4170_get_filter_type(setup->filter); + switch (f_type) { + case AD4170_SINC5_AVG: + case AD4170_SINC3: + fs_idx = find_closest(setup->filter_fs, + ad4170_sinc3_filt_fs_tbl, + ARRAY_SIZE(ad4170_sinc3_filt_fs_tbl)); + *val = st->sps_tbl[f_type][fs_idx][0]; + *val2 = st->sps_tbl[f_type][fs_idx][1]; + return IIO_VAL_INT_PLUS_MICRO; + case AD4170_SINC5: + fs_idx = find_closest(setup->filter_fs, + ad4170_sinc5_filt_fs_tbl, + ARRAY_SIZE(ad4170_sinc5_filt_fs_tbl)); + *val = st->sps_tbl[f_type][fs_idx][0]; + *val2 = st->sps_tbl[f_type][fs_idx][1]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + *val = setup->offset; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + *val = setup->gain; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad4170_fill_scale_tbl(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + struct ad4170_state *st = iio_priv(indio_dev); + struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address]; + struct device *dev = &st->spi->dev; + int bipolar = chan->scan_type.sign == 's' ? 1 : 0; + int precision_bits = chan->scan_type.realbits; + int pga, ainm_voltage, ret; + unsigned long long offset; + + ainm_voltage = 0; + ret = ad4170_get_ain_voltage_uv(st, chan->channel2, &ainm_voltage); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to fill scale table\n"); + + for (pga = 0; pga < AD4170_NUM_PGA_OPTIONS; pga++) { + u64 nv; + unsigned int lshift, rshift; + + /* + * The PGA options are numbered from 0 to 9, with option 0 being + * a gain of 2^0 (no actual gain), and 7 meaning a gain of 2^7. + * Option 8, though, sets a gain of 0.5, so the input signal can + * be attenuated by 2 rather than amplified. Option 9, allows + * the signal to bypass the PGA circuitry (no gain). + * + * The scale factor to get ADC output codes to values in mV + * units is given by: + * _scale = (input_range / gain) / 2^precision + * AD4170 gain is a power of 2 so the above can be written as + * _scale = input_range / 2^(precision + gain) + * Keep the input range in µV to avoid truncating the less + * significant bits when right shifting it so to preserve scale + * precision. + */ + nv = (u64)chan_info->input_range_uv * NANO; + lshift = !!(pga & BIT(3)); /* handle PGA options 8 and 9 */ + rshift = precision_bits - bipolar + (pga & GENMASK(2, 0)) - lshift; + chan_info->scale_tbl[pga][0] = 0; + chan_info->scale_tbl[pga][1] = div_u64(nv >> rshift, MILLI); + + /* + * If the negative input is not at GND, the conversion result + * (which is relative to IN-) will be offset by the level at IN-. + * Use the scale factor the other way around to go from a known + * voltage to the corresponding ADC output code. + * With that, we are able to get to what would be the output + * code for the voltage at the negative input. + * If the negative input is not fixed, there is no offset. + */ + offset = ((unsigned long long)abs(ainm_voltage)) * MICRO; + offset = DIV_ROUND_CLOSEST_ULL(offset, chan_info->scale_tbl[pga][1]); + + /* + * After divided by the scale, offset will always fit into 31 + * bits. For _raw + _offset to be relative to GND, the value + * provided as _offset is of opposite sign than the real offset. + */ + if (ainm_voltage > 0) + chan_info->offset_tbl[pga] = -(int)(offset); + else + chan_info->offset_tbl[pga] = (int)(offset); + } + return 0; +} + +static int ad4170_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long info) +{ + struct ad4170_state *st = iio_priv(indio_dev); + struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address]; + enum ad4170_filter_type f_type; + + switch (info) { + case IIO_CHAN_INFO_SCALE: + *vals = (int *)chan_info->scale_tbl; + *length = ARRAY_SIZE(chan_info->scale_tbl) * 2; + *type = IIO_VAL_INT_PLUS_NANO; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *type = IIO_VAL_INT_PLUS_MICRO; + f_type = ad4170_get_filter_type(indio_dev, chan); + switch (f_type) { + case AD4170_SINC5_AVG: + case AD4170_SINC3: + /* Read sps_tbl here to ensure in bounds array access */ + *vals = (int *)st->sps_tbl[f_type]; + *length = ARRAY_SIZE(ad4170_sinc3_filt_fs_tbl) * 2; + return IIO_AVAIL_LIST; + case AD4170_SINC5: + /* Read sps_tbl here to ensure in bounds array access */ + *vals = (int *)st->sps_tbl[f_type]; + *length = ARRAY_SIZE(ad4170_sinc5_filt_fs_tbl) * 2; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ad4170_set_pga(struct ad4170_state *st, + struct iio_chan_spec const *chan, int val, int val2) +{ + struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address]; + struct ad4170_setup *setup = &chan_info->setup; + unsigned int pga; + + for (pga = 0; pga < AD4170_NUM_PGA_OPTIONS; pga++) { + if (val == chan_info->scale_tbl[pga][0] && + val2 == chan_info->scale_tbl[pga][1]) + break; + } + + if (pga == AD4170_NUM_PGA_OPTIONS) + return -EINVAL; + + guard(mutex)(&st->lock); + setup->afe &= ~AD4170_AFE_PGA_GAIN_MSK; + setup->afe |= FIELD_PREP(AD4170_AFE_PGA_GAIN_MSK, pga); + + return ad4170_write_channel_setup(st, chan->address, false); +} + +static int ad4170_set_channel_freq(struct ad4170_state *st, + struct iio_chan_spec const *chan, int val, + int val2) +{ + struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address]; + struct ad4170_setup *setup = &chan_info->setup; + enum ad4170_filter_type f_type = __ad4170_get_filter_type(setup->filter); + unsigned int filt_fs_tbl_size, i; + + switch (f_type) { + case AD4170_SINC5_AVG: + case AD4170_SINC3: + filt_fs_tbl_size = ARRAY_SIZE(ad4170_sinc3_filt_fs_tbl); + break; + case AD4170_SINC5: + filt_fs_tbl_size = ARRAY_SIZE(ad4170_sinc5_filt_fs_tbl); + break; + } + + for (i = 0; i < filt_fs_tbl_size; i++) { + if (st->sps_tbl[f_type][i][0] == val && + st->sps_tbl[f_type][i][1] == val2) + break; + } + if (i == filt_fs_tbl_size) + return -EINVAL; + + guard(mutex)(&st->lock); + if (f_type == AD4170_SINC5) + setup->filter_fs = ad4170_sinc5_filt_fs_tbl[i]; + else + setup->filter_fs = ad4170_sinc3_filt_fs_tbl[i]; + + return ad4170_write_channel_setup(st, chan->address, false); +} + +static int ad4170_set_calib_offset(struct ad4170_state *st, + struct iio_chan_spec const *chan, int val) +{ + struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address]; + struct ad4170_setup *setup = &chan_info->setup; + + guard(mutex)(&st->lock); + setup->offset = val; + + return ad4170_write_channel_setup(st, chan->address, false); +} + +static int ad4170_set_calib_gain(struct ad4170_state *st, + struct iio_chan_spec const *chan, int val) +{ + struct ad4170_chan_info *chan_info = &st->chan_infos[chan->address]; + struct ad4170_setup *setup = &chan_info->setup; + + guard(mutex)(&st->lock); + setup->gain = val; + + return ad4170_write_channel_setup(st, chan->address, false); +} + +static int __ad4170_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long info) +{ + struct ad4170_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SCALE: + return ad4170_set_pga(st, chan, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return ad4170_set_channel_freq(st, chan, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + return ad4170_set_calib_offset(st, chan, val); + case IIO_CHAN_INFO_CALIBSCALE: + return ad4170_set_calib_gain(st, chan, val); + default: + return -EINVAL; + } +} + +static int ad4170_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long info) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = __ad4170_write_raw(indio_dev, chan, val, val2, info); + iio_device_release_direct(indio_dev); + return ret; +} + +static int ad4170_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long info) +{ + switch (info) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBBIAS: + case IIO_CHAN_INFO_CALIBSCALE: + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad4170_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *active_scan_mask) +{ + struct ad4170_state *st = iio_priv(indio_dev); + unsigned int chan_index; + int ret; + + iio_for_each_active_channel(indio_dev, chan_index) { + ret = ad4170_set_channel_enable(st, chan_index, true); + if (ret) + return ret; + } + return 0; +} + +static const struct iio_info ad4170_info = { + .read_raw = ad4170_read_raw, + .read_avail = ad4170_read_avail, + .write_raw = ad4170_write_raw, + .write_raw_get_fmt = ad4170_write_raw_get_fmt, + .update_scan_mode = ad4170_update_scan_mode, + .debugfs_reg_access = ad4170_debugfs_reg_access, +}; + +static int ad4170_soft_reset(struct ad4170_state *st) +{ + int ret; + + ret = regmap_write(st->regmap, AD4170_CONFIG_A_REG, + AD4170_SW_RESET_MSK); + if (ret) + return ret; + + /* AD4170-4 requires 1 ms between reset and any register access. */ + fsleep(1 * USEC_PER_MSEC); + + return 0; +} + +static int ad4170_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct iio_dev *indio_dev = gpiochip_get_data(gc); + struct ad4170_state *st = iio_priv(indio_dev); + unsigned int val; + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = regmap_read(st->regmap, AD4170_GPIO_MODE_REG, &val); + if (ret) + goto err_release; + + /* + * If the GPIO is configured as an input, read the current value from + * AD4170_GPIO_INPUT_REG. Otherwise, read the input value from + * AD4170_GPIO_OUTPUT_REG. + */ + if (val & BIT(offset * 2)) + ret = regmap_read(st->regmap, AD4170_GPIO_INPUT_REG, &val); + else + ret = regmap_read(st->regmap, AD4170_GPIO_OUTPUT_REG, &val); + if (ret) + goto err_release; + + ret = !!(val & BIT(offset)); +err_release: + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ad4170_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + struct iio_dev *indio_dev = gpiochip_get_data(gc); + struct ad4170_state *st = iio_priv(indio_dev); + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = regmap_assign_bits(st->regmap, AD4170_GPIO_OUTPUT_REG, + BIT(offset), !!value); + + iio_device_release_direct(indio_dev); + return ret; +} + +static int ad4170_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + struct iio_dev *indio_dev = gpiochip_get_data(gc); + struct ad4170_state *st = iio_priv(indio_dev); + unsigned int val; + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = regmap_read(st->regmap, AD4170_GPIO_MODE_REG, &val); + if (ret) + goto err_release; + + if (val & BIT(offset * 2 + 1)) + ret = GPIO_LINE_DIRECTION_OUT; + else + ret = GPIO_LINE_DIRECTION_IN; + +err_release: + iio_device_release_direct(indio_dev); + + return ret; +} + +static const unsigned long gpio_masks[] = { + AD4170_GPIO_MODE_GPIO0_MSK, + AD4170_GPIO_MODE_GPIO1_MSK, + AD4170_GPIO_MODE_GPIO2_MSK, + AD4170_GPIO_MODE_GPIO3_MSK, +}; + +static int ad4170_gpio_direction_input(struct gpio_chip *gc, unsigned int offset) +{ + struct iio_dev *indio_dev = gpiochip_get_data(gc); + struct ad4170_state *st = iio_priv(indio_dev); + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + if (offset >= ARRAY_SIZE(gpio_masks)) { + ret = -EINVAL; + goto err_release; + } + + ret = regmap_update_bits(st->regmap, AD4170_GPIO_MODE_REG, + gpio_masks[offset], + AD4170_GPIO_MODE_GPIO_INPUT << (2 * offset)); + +err_release: + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ad4170_gpio_direction_output(struct gpio_chip *gc, + unsigned int offset, int value) +{ + struct iio_dev *indio_dev = gpiochip_get_data(gc); + struct ad4170_state *st = iio_priv(indio_dev); + int ret; + + ret = ad4170_gpio_set(gc, offset, value); + if (ret) + return ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + if (offset >= ARRAY_SIZE(gpio_masks)) { + ret = -EINVAL; + goto err_release; + } + + ret = regmap_update_bits(st->regmap, AD4170_GPIO_MODE_REG, + gpio_masks[offset], + AD4170_GPIO_MODE_GPIO_OUTPUT << (2 * offset)); + +err_release: + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ad4170_gpio_init_valid_mask(struct gpio_chip *gc, + unsigned long *valid_mask, + unsigned int ngpios) +{ + struct ad4170_state *st = gpiochip_get_data(gc); + unsigned int i; + + /* Only expose GPIOs that were not assigned any other function. */ + for (i = 0; i < ngpios; i++) { + bool valid = st->gpio_fn[i] == AD4170_GPIO_UNASSIGNED; + + __assign_bit(i, valid_mask, valid); + } + + return 0; +} + +static int ad4170_gpio_init(struct iio_dev *indio_dev) +{ + struct ad4170_state *st = iio_priv(indio_dev); + + st->gpiochip.label = "ad4170_gpios"; + st->gpiochip.base = -1; + st->gpiochip.ngpio = AD4170_NUM_GPIO_PINS; + st->gpiochip.parent = &st->spi->dev; + st->gpiochip.can_sleep = true; + st->gpiochip.init_valid_mask = ad4170_gpio_init_valid_mask; + st->gpiochip.get_direction = ad4170_gpio_get_direction; + st->gpiochip.direction_input = ad4170_gpio_direction_input; + st->gpiochip.direction_output = ad4170_gpio_direction_output; + st->gpiochip.get = ad4170_gpio_get; + st->gpiochip.set = ad4170_gpio_set; + st->gpiochip.owner = THIS_MODULE; + + return devm_gpiochip_add_data(&st->spi->dev, &st->gpiochip, indio_dev); +} + +static int ad4170_validate_excitation_pin(struct ad4170_state *st, u32 pin) +{ + struct device *dev = &st->spi->dev; + unsigned int i; + + /* Check the pin number is valid */ + for (i = 0; i < ARRAY_SIZE(ad4170_iout_pin_tbl); i++) + if (ad4170_iout_pin_tbl[i] == pin) + break; + + if (i == ARRAY_SIZE(ad4170_iout_pin_tbl)) + return dev_err_probe(dev, -EINVAL, + "Invalid excitation pin: %u\n", + pin); + + /* Check the pin is available */ + if (pin <= AD4170_MAX_ANALOG_PINS) { + if (st->pins_fn[pin] != AD4170_PIN_UNASSIGNED) + return dev_err_probe(dev, -EINVAL, + "Pin %u already used with fn %u\n", + pin, st->pins_fn[pin]); + + st->pins_fn[pin] |= AD4170_PIN_CURRENT_OUT; + } else { + unsigned int gpio = pin - AD4170_CURRENT_SRC_I_OUT_PIN_GPIO(0); + + if (st->gpio_fn[gpio] != AD4170_GPIO_UNASSIGNED) + return dev_err_probe(dev, -EINVAL, + "GPIO %u already used with fn %u\n", + gpio, st->gpio_fn[gpio]); + + st->gpio_fn[gpio] |= AD4170_GPIO_AC_EXCITATION; + } + + return 0; +} + +static int ad4170_validate_excitation_pins(struct ad4170_state *st, + u32 *exc_pins, int num_exc_pins) +{ + unsigned int i; + int ret; + + for (i = 0; i < num_exc_pins; i++) { + ret = ad4170_validate_excitation_pin(st, exc_pins[i]); + if (ret) + return ret; + } + return 0; +} + +static const char *const ad4170_i_out_pin_dt_props[] = { + "adi,excitation-pin-0", + "adi,excitation-pin-1", + "adi,excitation-pin-2", + "adi,excitation-pin-3", +}; + +static const char *const ad4170_i_out_val_dt_props[] = { + "adi,excitation-current-0-microamp", + "adi,excitation-current-1-microamp", + "adi,excitation-current-2-microamp", + "adi,excitation-current-3-microamp", +}; + +/* + * Parses firmware data describing output current source setup. There are 4 + * excitation currents (IOUT0 to IOUT3) that can be configured independently. + * Excitation currents are added if they are output on the same pin. + */ +static int ad4170_parse_exc_current(struct ad4170_state *st, + struct fwnode_handle *child, + unsigned int *exc_pins, + unsigned int *exc_curs, + unsigned int *num_exc_pins) +{ + struct device *dev = &st->spi->dev; + unsigned int num_pins, i, j; + u32 pin, val; + int ret; + + num_pins = 0; + for (i = 0; i < AD4170_NUM_CURRENT_SRC; i++) { + /* Parse excitation current output pin properties. */ + pin = AD4170_CURRENT_SRC_I_OUT_PIN_AIN(0); + ret = fwnode_property_read_u32(child, ad4170_i_out_pin_dt_props[i], + &pin); + if (ret) + continue; + + exc_pins[num_pins] = pin; + + /* Parse excitation current value properties. */ + val = ad4170_iout_current_ua_tbl[0]; + fwnode_property_read_u32(child, + ad4170_i_out_val_dt_props[i], &val); + + for (j = 0; j < ARRAY_SIZE(ad4170_iout_current_ua_tbl); j++) + if (ad4170_iout_current_ua_tbl[j] == val) + break; + + if (j == ARRAY_SIZE(ad4170_iout_current_ua_tbl)) + return dev_err_probe(dev, -EINVAL, "Invalid %s: %uuA\n", + ad4170_i_out_val_dt_props[i], val); + + exc_curs[num_pins] = j; + num_pins++; + } + *num_exc_pins = num_pins; + + return 0; +} + +static int ad4170_setup_current_src(struct ad4170_state *st, + struct fwnode_handle *child, + struct ad4170_setup *setup, u32 *exc_pins, + unsigned int *exc_curs, int num_exc_pins, + bool ac_excited) +{ + unsigned int exc_cur_pair, i, j; + int ret; + + for (i = 0; i < num_exc_pins; i++) { + unsigned int exc_cur = exc_curs[i]; + unsigned int pin = exc_pins[i]; + unsigned int current_src = 0; + + for (j = 0; j < AD4170_NUM_CURRENT_SRC; j++) + if (st->cur_src_pins[j] == AD4170_CURRENT_SRC_DISABLED) + break; + + if (j == AD4170_NUM_CURRENT_SRC) + return dev_err_probe(&st->spi->dev, -EINVAL, + "Too many excitation current sources\n"); + + current_src |= FIELD_PREP(AD4170_CURRENT_SRC_I_OUT_PIN_MSK, pin); + current_src |= FIELD_PREP(AD4170_CURRENT_SRC_I_OUT_VAL_MSK, exc_cur); + st->cur_src_pins[j] = pin; + ret = regmap_write(st->regmap, AD4170_CURRENT_SRC_REG(j), + current_src); + if (ret) + return ret; + } + + if (!ac_excited) + return 0; + + if (num_exc_pins < 2) + return dev_err_probe(&st->spi->dev, -EINVAL, + "Current chopping requested but only one pin provided: %u\n", + exc_pins[0]); + + /* + * Two use cases to handle here: + * - 2 pairs of excitation currents; + * - 1 pair of excitation currents. + */ + if (num_exc_pins == 4) { + for (i = 0; i < AD4170_NUM_CURRENT_SRC; i++) + if (st->cur_src_pins[i] != exc_pins[i]) + return dev_err_probe(&st->spi->dev, -EINVAL, + "Unable to use 4 exc pins\n"); + } else { + /* + * Excitation current chopping is configured in pairs. Current + * sources IOUT0 and IOUT1 form pair 1, IOUT2 and IOUT3 make up + * pair 2. So, if current chopping was requested, check if the + * first end of the first pair of excitation currents is + * available. Try the next pair if IOUT0 has already been + * configured for another channel. + */ + i = st->cur_src_pins[0] == exc_pins[0] ? 0 : 2; + + if (st->cur_src_pins[i] != exc_pins[0] || + st->cur_src_pins[i + 1] != exc_pins[1]) + return dev_err_probe(&st->spi->dev, -EINVAL, + "Failed to setup current chopping\n"); + + st->cur_src_pins[i] = exc_pins[0]; + st->cur_src_pins[i + 1] = exc_pins[1]; + + if (i == 0) + exc_cur_pair = AD4170_MISC_CHOP_IEXC_PAIR1; + else + exc_cur_pair = AD4170_MISC_CHOP_IEXC_PAIR2; + } + + /* + * Configure excitation current chopping. + * Chop both pairs if using four excitation pins. + */ + setup->misc |= FIELD_PREP(AD4170_MISC_CHOP_IEXC_MSK, + num_exc_pins == 2 ? + exc_cur_pair : + AD4170_MISC_CHOP_IEXC_BOTH); + + return 0; +} + +static int ad4170_setup_bridge(struct ad4170_state *st, + struct fwnode_handle *child, + struct ad4170_setup *setup, u32 *exc_pins, + unsigned int *exc_curs, int num_exc_pins, + bool ac_excited) +{ + unsigned long gpio_mask; + unsigned int i; + int ret; + + /* + * If a specific current is provided through + * adi,excitation-current-n-microamp, set excitation pins provided + * through adi,excitation-pin-n to excite the bridge circuit. + */ + for (i = 0; i < num_exc_pins; i++) + if (exc_curs[i] > 0) + return ad4170_setup_current_src(st, child, setup, exc_pins, + exc_curs, num_exc_pins, + ac_excited); + + /* + * Else, use predefined ACX1, ACX1 negated, ACX2, ACX2 negated signals + * to AC excite the bridge. Those signals are output on GPIO2, GPIO0, + * GPIO3, and GPIO1, respectively. If only two pins are specified for AC + * excitation, use ACX1 and ACX2 (GPIO2 and GPIO3). + * + * Also, to avoid any short-circuit condition when more than one channel + * is enabled, set GPIO2 and GPIO0 high, and set GPIO1 and GPIO3 low to + * DC excite the bridge whenever a channel without AC excitation is + * selected. That is needed because GPIO pins are controlled by the next + * highest priority GPIO function when a channel doesn't enable AC + * excitation. See datasheet Figure 113 Weigh Scale (AC Excitation) for + * the reference circuit diagram. + */ + if (num_exc_pins == 2) { + setup->misc |= FIELD_PREP(AD4170_MISC_CHOP_ADC_MSK, 0x3); + + gpio_mask = AD4170_GPIO_MODE_GPIO3_MSK | AD4170_GPIO_MODE_GPIO2_MSK; + ret = regmap_update_bits(st->regmap, AD4170_GPIO_MODE_REG, gpio_mask, + FIELD_PREP(AD4170_GPIO_MODE_GPIO3_MSK, + AD4170_GPIO_MODE_GPIO_OUTPUT) | + FIELD_PREP(AD4170_GPIO_MODE_GPIO2_MSK, + AD4170_GPIO_MODE_GPIO_OUTPUT)); + if (ret) + return ret; + + /* + * Set GPIO2 high and GPIO3 low to DC excite the bridge when + * a different channel is selected. + */ + gpio_mask = AD4170_GPIO_OUTPUT_GPIO_MSK(3) | + AD4170_GPIO_OUTPUT_GPIO_MSK(2); + ret = regmap_update_bits(st->regmap, AD4170_GPIO_OUTPUT_REG, gpio_mask, + FIELD_PREP(AD4170_GPIO_OUTPUT_GPIO_MSK(3), 0) | + FIELD_PREP(AD4170_GPIO_OUTPUT_GPIO_MSK(2), 1)); + if (ret) + return ret; + + st->gpio_fn[3] |= AD4170_GPIO_OUTPUT; + st->gpio_fn[2] |= AD4170_GPIO_OUTPUT; + } else { + setup->misc |= FIELD_PREP(AD4170_MISC_CHOP_ADC_MSK, 0x2); + + gpio_mask = AD4170_GPIO_MODE_GPIO3_MSK | AD4170_GPIO_MODE_GPIO2_MSK | + AD4170_GPIO_MODE_GPIO1_MSK | AD4170_GPIO_MODE_GPIO0_MSK; + ret = regmap_update_bits(st->regmap, AD4170_GPIO_MODE_REG, gpio_mask, + FIELD_PREP(AD4170_GPIO_MODE_GPIO3_MSK, + AD4170_GPIO_MODE_GPIO_OUTPUT) | + FIELD_PREP(AD4170_GPIO_MODE_GPIO2_MSK, + AD4170_GPIO_MODE_GPIO_OUTPUT) | + FIELD_PREP(AD4170_GPIO_MODE_GPIO1_MSK, + AD4170_GPIO_MODE_GPIO_OUTPUT) | + FIELD_PREP(AD4170_GPIO_MODE_GPIO0_MSK, + AD4170_GPIO_MODE_GPIO_OUTPUT)); + if (ret) + return ret; + + /* + * Set GPIO2 and GPIO0 high, and set GPIO1 and GPIO3 low to DC + * excite the bridge when a different channel is selected. + */ + gpio_mask = AD4170_GPIO_OUTPUT_GPIO_MSK(3) | + AD4170_GPIO_OUTPUT_GPIO_MSK(2) | + AD4170_GPIO_OUTPUT_GPIO_MSK(1) | + AD4170_GPIO_OUTPUT_GPIO_MSK(0); + ret = regmap_update_bits(st->regmap, AD4170_GPIO_OUTPUT_REG, gpio_mask, + FIELD_PREP(AD4170_GPIO_OUTPUT_GPIO_MSK(3), 0) | + FIELD_PREP(AD4170_GPIO_OUTPUT_GPIO_MSK(2), 1) | + FIELD_PREP(AD4170_GPIO_OUTPUT_GPIO_MSK(1), 0) | + FIELD_PREP(AD4170_GPIO_OUTPUT_GPIO_MSK(0), 1)); + if (ret) + return ret; + + st->gpio_fn[3] |= AD4170_GPIO_OUTPUT; + st->gpio_fn[2] |= AD4170_GPIO_OUTPUT; + st->gpio_fn[1] |= AD4170_GPIO_OUTPUT; + st->gpio_fn[0] |= AD4170_GPIO_OUTPUT; + } + + return 0; +} + +static int ad4170_setup_rtd(struct ad4170_state *st, + struct fwnode_handle *child, + struct ad4170_setup *setup, u32 *exc_pins, + unsigned int *exc_curs, int num_exc_pins, bool ac_excited) +{ + return ad4170_setup_current_src(st, child, setup, exc_pins, + exc_curs, num_exc_pins, ac_excited); +} + +static int ad4170_parse_external_sensor(struct ad4170_state *st, + struct fwnode_handle *child, + struct ad4170_setup *setup, + struct iio_chan_spec *chan, + unsigned int s_type) +{ + unsigned int num_exc_pins, reg_val; + struct device *dev = &st->spi->dev; + u32 pins[2], exc_pins[4], exc_curs[4]; + bool ac_excited; + int ret; + + ret = fwnode_property_read_u32_array(child, "diff-channels", pins, + ARRAY_SIZE(pins)); + if (ret) + return dev_err_probe(dev, ret, + "Failed to read sensor diff-channels\n"); + + chan->differential = true; + chan->channel = pins[0]; + chan->channel2 = pins[1]; + + ret = ad4170_parse_exc_current(st, child, exc_pins, exc_curs, &num_exc_pins); + if (ret) + return ret; + + /* The external sensor may not need excitation from the ADC chip. */ + if (num_exc_pins == 0) + return 0; + + ret = ad4170_validate_excitation_pins(st, exc_pins, num_exc_pins); + if (ret) + return ret; + + ac_excited = fwnode_property_read_bool(child, "adi,excitation-ac"); + + if (s_type == AD4170_THERMOCOUPLE_SENSOR) { + if (st->pins_fn[chan->channel2] & AD4170_PIN_VBIAS) { + reg_val = BIT(chan->channel2); + ret = regmap_write(st->regmap, AD4170_V_BIAS_REG, reg_val); + if (ret) + dev_err_probe(dev, ret, "Failed to set vbias\n"); + } + } + if (s_type == AD4170_WEIGH_SCALE_SENSOR) + ret = ad4170_setup_bridge(st, child, setup, exc_pins, exc_curs, + num_exc_pins, ac_excited); + else + ret = ad4170_setup_rtd(st, child, setup, exc_pins, exc_curs, + num_exc_pins, ac_excited); + + return ret; +} + +static int ad4170_parse_reference(struct ad4170_state *st, + struct fwnode_handle *child, + struct ad4170_setup *setup) +{ + struct device *dev = &st->spi->dev; + const char *propname; + u32 aux; + int ret; + + /* Optional positive reference buffering */ + propname = "adi,positive-reference-buffer"; + ret = device_property_match_property_string(dev, propname, + ad4170_ref_buf_str, + ARRAY_SIZE(ad4170_ref_buf_str)); + + /* Default to full precharge buffer enabled. */ + setup->afe |= FIELD_PREP(AD4170_AFE_REF_BUF_P_MSK, + ret >= 0 ? ret : AD4170_REF_BUF_FULL); + + /* Optional negative reference buffering */ + propname = "adi,negative-reference-buffer"; + ret = device_property_match_property_string(dev, propname, + ad4170_ref_buf_str, + ARRAY_SIZE(ad4170_ref_buf_str)); + + /* Default to full precharge buffer enabled. */ + setup->afe |= FIELD_PREP(AD4170_AFE_REF_BUF_M_MSK, + ret >= 0 ? ret : AD4170_REF_BUF_FULL); + + /* Optional voltage reference selection */ + propname = "adi,reference-select"; + aux = AD4170_REF_REFOUT; /* Default reference selection. */ + fwnode_property_read_u32(child, propname, &aux); + if (aux > AD4170_REF_AVDD) + return dev_err_probe(dev, -EINVAL, "Invalid %s: %u\n", + propname, aux); + + setup->afe |= FIELD_PREP(AD4170_AFE_REF_SELECT_MSK, aux); + + return 0; +} + +static int ad4170_parse_adc_channel_type(struct device *dev, + struct fwnode_handle *child, + struct iio_chan_spec *chan) +{ + const char *propname, *propname2; + int ret, ret2; + u32 pins[2]; + + propname = "single-channel"; + propname2 = "diff-channels"; + if (!fwnode_property_present(child, propname) && + !fwnode_property_present(child, propname2)) + return dev_err_probe(dev, -EINVAL, + "Channel must define one of %s or %s.\n", + propname, propname2); + + /* Parse differential channel configuration */ + ret = fwnode_property_read_u32_array(child, propname2, pins, + ARRAY_SIZE(pins)); + if (!ret) { + chan->differential = true; + chan->channel = pins[0]; + chan->channel2 = pins[1]; + return 0; + } + /* Failed to parse diff chan so try pseudo-diff chan props */ + + propname2 = "common-mode-channel"; + if (fwnode_property_present(child, propname) && + !fwnode_property_present(child, propname2)) + return dev_err_probe(dev, -EINVAL, + "When %s is defined, %s must be defined too\n", + propname, propname2); + + /* Parse pseudo-differential channel configuration */ + ret = fwnode_property_read_u32(child, propname, &pins[0]); + ret2 = fwnode_property_read_u32(child, propname2, &pins[1]); + + if (!ret && !ret2) { + chan->differential = false; + chan->channel = pins[0]; + chan->channel2 = pins[1]; + return 0; + } + return dev_err_probe(dev, -EINVAL, + "Failed to parse channel %lu input. %d, %d\n", + chan->address, ret, ret2); +} + +static int ad4170_parse_channel_node(struct iio_dev *indio_dev, + struct fwnode_handle *child, + unsigned int chan_num) +{ + struct ad4170_state *st = iio_priv(indio_dev); + unsigned int s_type = AD4170_ADC_SENSOR; + struct device *dev = &st->spi->dev; + struct ad4170_chan_info *chan_info; + struct ad4170_setup *setup; + struct iio_chan_spec *chan; + unsigned int ref_select; + unsigned int ch_reg; + bool bipolar; + int ret; + + ret = fwnode_property_read_u32(child, "reg", &ch_reg); + if (ret) + return dev_err_probe(dev, ret, "Failed to read channel reg\n"); + + if (ch_reg >= AD4170_MAX_ADC_CHANNELS) + return dev_err_probe(dev, -EINVAL, + "Channel idx greater than no of channels\n"); + + chan = &st->chans[chan_num]; + *chan = ad4170_channel_template; + + chan->address = ch_reg; + chan->scan_index = ch_reg; + chan_info = &st->chan_infos[chan->address]; + + chan_info->setup_num = AD4170_INVALID_SETUP; + chan_info->initialized = true; + + setup = &chan_info->setup; + ret = ad4170_parse_reference(st, child, setup); + if (ret) + return ret; + + ret = fwnode_property_match_property_string(child, "adi,sensor-type", + ad4170_sensor_type, + ARRAY_SIZE(ad4170_sensor_type)); + + /* Default to conventional ADC channel if sensor type not present */ + s_type = ret < 0 ? AD4170_ADC_SENSOR : ret; + switch (s_type) { + case AD4170_ADC_SENSOR: + ret = ad4170_parse_adc_channel_type(dev, child, chan); + if (ret) + return ret; + + break; + case AD4170_WEIGH_SCALE_SENSOR: + case AD4170_THERMOCOUPLE_SENSOR: + case AD4170_RTD_SENSOR: + ret = ad4170_parse_external_sensor(st, child, setup, chan, s_type); + if (ret) + return ret; + + break; + default: + return -EINVAL; + } + + bipolar = fwnode_property_read_bool(child, "bipolar"); + setup->afe |= FIELD_PREP(AD4170_AFE_BIPOLAR_MSK, bipolar); + if (bipolar) + chan->scan_type.sign = 's'; + else + chan->scan_type.sign = 'u'; + + ret = ad4170_validate_channel(st, chan); + if (ret) + return ret; + + ref_select = FIELD_GET(AD4170_AFE_REF_SELECT_MSK, setup->afe); + ret = ad4170_get_input_range(st, chan, ch_reg, ref_select); + if (ret < 0) + return dev_err_probe(dev, ret, "Invalid input config\n"); + + chan_info->input_range_uv = ret; + return 0; +} + +static int ad4170_parse_channels(struct iio_dev *indio_dev) +{ + struct ad4170_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + unsigned int num_channels; + unsigned int chan_num; + int ret; + + num_channels = device_get_child_node_count(dev); + + if (num_channels > AD4170_MAX_ADC_CHANNELS) + return dev_err_probe(dev, -EINVAL, "Too many channels\n"); + + /* Add one for temperature */ + num_channels = min(num_channels + 1, AD4170_MAX_ADC_CHANNELS); + + chan_num = 0; + device_for_each_child_node_scoped(dev, child) { + ret = ad4170_parse_channel_node(indio_dev, child, chan_num++); + if (ret) + return ret; + } + + /* + * Add internal temperature sensor channel if the maximum number of + * channels has not been reached. + */ + if (num_channels < AD4170_MAX_ADC_CHANNELS) { + struct ad4170_setup *setup = &st->chan_infos[chan_num].setup; + + st->chans[chan_num] = ad4170_temp_channel_template; + st->chans[chan_num].address = chan_num; + st->chans[chan_num].scan_index = chan_num; + + st->chan_infos[chan_num].setup_num = AD4170_INVALID_SETUP; + st->chan_infos[chan_num].initialized = true; + + setup->afe |= FIELD_PREP(AD4170_AFE_REF_SELECT_MSK, + AD4170_REF_AVDD); + + ret = ad4170_get_input_range(st, &st->chans[chan_num], chan_num, + AD4170_REF_AVDD); + if (ret < 0) + return dev_err_probe(dev, ret, "Invalid input config\n"); + + st->chan_infos[chan_num].input_range_uv = ret; + chan_num++; + } + + /* Add timestamp channel */ + struct iio_chan_spec ts_chan = IIO_CHAN_SOFT_TIMESTAMP(chan_num); + + st->chans[chan_num] = ts_chan; + num_channels = num_channels + 1; + + indio_dev->num_channels = num_channels; + indio_dev->channels = st->chans; + + return 0; +} + +static struct ad4170_state *clk_hw_to_ad4170(struct clk_hw *hw) +{ + return container_of(hw, struct ad4170_state, int_clk_hw); +} + +static unsigned long ad4170_sel_clk(struct ad4170_state *st, + unsigned int clk_sel) +{ + st->clock_ctrl &= ~AD4170_CLOCK_CTRL_CLOCKSEL_MSK; + st->clock_ctrl |= FIELD_PREP(AD4170_CLOCK_CTRL_CLOCKSEL_MSK, clk_sel); + return regmap_write(st->regmap, AD4170_CLOCK_CTRL_REG, st->clock_ctrl); +} + +static unsigned long ad4170_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + return AD4170_INT_CLOCK_16MHZ; +} + +static int ad4170_clk_output_is_enabled(struct clk_hw *hw) +{ + struct ad4170_state *st = clk_hw_to_ad4170(hw); + u32 clk_sel; + + clk_sel = FIELD_GET(AD4170_CLOCK_CTRL_CLOCKSEL_MSK, st->clock_ctrl); + return clk_sel == AD4170_CLOCK_CTRL_CLOCKSEL_INT_OUT; +} + +static int ad4170_clk_output_prepare(struct clk_hw *hw) +{ + struct ad4170_state *st = clk_hw_to_ad4170(hw); + + return ad4170_sel_clk(st, AD4170_CLOCK_CTRL_CLOCKSEL_INT_OUT); +} + +static void ad4170_clk_output_unprepare(struct clk_hw *hw) +{ + struct ad4170_state *st = clk_hw_to_ad4170(hw); + + ad4170_sel_clk(st, AD4170_CLOCK_CTRL_CLOCKSEL_INT); +} + +static const struct clk_ops ad4170_int_clk_ops = { + .recalc_rate = ad4170_clk_recalc_rate, + .is_enabled = ad4170_clk_output_is_enabled, + .prepare = ad4170_clk_output_prepare, + .unprepare = ad4170_clk_output_unprepare, +}; + +static int ad4170_register_clk_provider(struct iio_dev *indio_dev) +{ + struct ad4170_state *st = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + struct clk_init_data init = {}; + int ret; + + if (device_property_read_string(dev, "clock-output-names", &init.name)) { + init.name = devm_kasprintf(dev, GFP_KERNEL, "%pfw", + dev_fwnode(dev)); + if (!init.name) + return -ENOMEM; + } + + init.ops = &ad4170_int_clk_ops; + + st->int_clk_hw.init = &init; + ret = devm_clk_hw_register(dev, &st->int_clk_hw); + if (ret) + return ret; + + return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + &st->int_clk_hw); +} + +static int ad4170_clock_select(struct iio_dev *indio_dev) +{ + struct ad4170_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + struct clk *ext_clk; + int ret; + + ext_clk = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(ext_clk)) + return dev_err_probe(dev, PTR_ERR(ext_clk), + "Failed to get external clock\n"); + + if (!ext_clk) { + /* Use internal clock reference */ + st->mclk_hz = AD4170_INT_CLOCK_16MHZ; + st->clock_ctrl |= FIELD_PREP(AD4170_CLOCK_CTRL_CLOCKSEL_MSK, + AD4170_CLOCK_CTRL_CLOCKSEL_INT_OUT); + + if (!device_property_present(&st->spi->dev, "#clock-cells")) + return 0; + + return ad4170_register_clk_provider(indio_dev); + } + + /* Read optional clock-names prop to specify the external clock type */ + ret = device_property_match_property_string(dev, "clock-names", + ad4170_clk_sel, + ARRAY_SIZE(ad4170_clk_sel)); + + ret = ret < 0 ? 0 : ret; /* Default to external clock if no clock-names */ + st->clock_ctrl |= FIELD_PREP(AD4170_CLOCK_CTRL_CLOCKSEL_MSK, + AD4170_CLOCK_CTRL_CLOCKSEL_EXT + ret); + + st->mclk_hz = clk_get_rate(ext_clk); + if (st->mclk_hz < AD4170_EXT_CLOCK_MHZ_MIN || + st->mclk_hz > AD4170_EXT_CLOCK_MHZ_MAX) { + return dev_err_probe(dev, -EINVAL, + "Invalid external clock frequency %u\n", + st->mclk_hz); + } + + return 0; +} + +static int ad4170_parse_firmware(struct iio_dev *indio_dev) +{ + unsigned int vbias_pins[AD4170_MAX_ANALOG_PINS]; + struct ad4170_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + unsigned int num_vbias_pins; + int reg_data, ret; + u32 int_pin_sel; + unsigned int i; + + ret = ad4170_clock_select(indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to setup device clock\n"); + + ret = regmap_write(st->regmap, AD4170_CLOCK_CTRL_REG, st->clock_ctrl); + if (ret) + return ret; + + for (i = 0; i < AD4170_NUM_CURRENT_SRC; i++) + st->cur_src_pins[i] = AD4170_CURRENT_SRC_DISABLED; + + /* On power on, device defaults to using SDO pin for data ready signal */ + int_pin_sel = AD4170_INT_PIN_SDO; + ret = device_property_match_property_string(dev, "interrupt-names", + ad4170_int_pin_names, + ARRAY_SIZE(ad4170_int_pin_names)); + if (ret >= 0) + int_pin_sel = ret; + + reg_data = FIELD_PREP(AD4170_PIN_MUXING_DIG_AUX1_CTRL_MSK, + int_pin_sel == AD4170_INT_PIN_DIG_AUX1 ? + AD4170_PIN_MUXING_DIG_AUX1_RDY : + AD4170_PIN_MUXING_DIG_AUX1_DISABLED); + + ret = regmap_update_bits(st->regmap, AD4170_PIN_MUXING_REG, + AD4170_PIN_MUXING_DIG_AUX1_CTRL_MSK, reg_data); + if (ret) + return ret; + + ret = device_property_count_u32(dev, "adi,vbias-pins"); + if (ret > 0) { + if (ret > AD4170_MAX_ANALOG_PINS) + return dev_err_probe(dev, -EINVAL, + "Too many vbias pins %u\n", ret); + + num_vbias_pins = ret; + + ret = device_property_read_u32_array(dev, "adi,vbias-pins", + vbias_pins, + num_vbias_pins); + if (ret) + return dev_err_probe(dev, ret, + "Failed to read vbias pins\n"); + + for (i = 0; i < num_vbias_pins; i++) + st->pins_fn[vbias_pins[i]] |= AD4170_PIN_VBIAS; + } + + ret = ad4170_parse_channels(indio_dev); + if (ret) + return ret; + + /* Only create a GPIO chip if flagged for it */ + if (device_property_read_bool(dev, "gpio-controller")) { + ret = ad4170_gpio_init(indio_dev); + if (ret) + return ret; + } + + return 0; +} + +static int ad4170_initial_config(struct iio_dev *indio_dev) +{ + struct ad4170_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + unsigned int i; + int ret; + + ad4170_fill_sps_tbl(st); + + ret = regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG, + AD4170_ADC_CTRL_MODE_MSK, + FIELD_PREP(AD4170_ADC_CTRL_MODE_MSK, + AD4170_ADC_CTRL_MODE_IDLE)); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set ADC mode to idle\n"); + + for (i = 0; i < indio_dev->num_channels; i++) { + struct ad4170_chan_info *chan_info; + struct iio_chan_spec const *chan; + struct ad4170_setup *setup; + unsigned int val; + + chan = &indio_dev->channels[i]; + if (chan->type == IIO_TIMESTAMP) + continue; + + chan_info = &st->chan_infos[chan->address]; + + setup = &chan_info->setup; + setup->gain = AD4170_GAIN_REG_DEFAULT; + ret = ad4170_write_channel_setup(st, chan->address, false); + if (ret) + return dev_err_probe(dev, ret, + "Failed to write channel setup\n"); + + val = FIELD_PREP(AD4170_CHAN_MAP_AINP_MSK, chan->channel) | + FIELD_PREP(AD4170_CHAN_MAP_AINM_MSK, chan->channel2); + + ret = regmap_write(st->regmap, AD4170_CHAN_MAP_REG(i), val); + if (ret) + return dev_err_probe(dev, ret, + "Failed to write CHAN_MAP_REG\n"); + + ret = ad4170_set_channel_freq(st, chan, + AD4170_DEFAULT_SAMP_RATE, 0); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set channel freq\n"); + + ret = ad4170_fill_scale_tbl(indio_dev, chan); + if (ret) + return dev_err_probe(dev, ret, + "Failed to fill scale tbl\n"); + } + + /* Disable all channels to avoid reading from unexpected channel */ + ret = regmap_write(st->regmap, AD4170_CHAN_EN_REG, 0); + if (ret) + return dev_err_probe(dev, ret, + "Failed to disable channels\n"); + + /* + * Configure channels to share the same data output register, i.e. data + * can be read from the same register address regardless of channel + * number. + */ + return regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG, + AD4170_ADC_CTRL_MULTI_DATA_REG_SEL_MSK, + AD4170_ADC_CTRL_MULTI_DATA_REG_SEL_MSK); +} + +static int ad4170_prepare_spi_message(struct ad4170_state *st) +{ + /* + * Continuous data register read is enabled on buffer postenable so + * no instruction phase is needed meaning we don't need to send the + * register address to read data. Transfer only needs the read buffer. + */ + st->xfer.rx_buf = &st->rx_buf; + st->xfer.len = BITS_TO_BYTES(ad4170_channel_template.scan_type.realbits); + + spi_message_init_with_transfers(&st->msg, &st->xfer, 1); + + return devm_spi_optimize_message(&st->spi->dev, st->spi, &st->msg); +} + +static int ad4170_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad4170_state *st = iio_priv(indio_dev); + int ret; + + ret = regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG, + AD4170_ADC_CTRL_MODE_MSK, + FIELD_PREP(AD4170_ADC_CTRL_MODE_MSK, + AD4170_ADC_CTRL_MODE_CONT)); + if (ret) + return ret; + + /* + * This enables continuous read of the ADC data register. The ADC must + * be in continuous conversion mode. + */ + return regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG, + AD4170_ADC_CTRL_CONT_READ_MSK, + FIELD_PREP(AD4170_ADC_CTRL_CONT_READ_MSK, + AD4170_ADC_CTRL_CONT_READ_ENABLE)); +} + +static int ad4170_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad4170_state *st = iio_priv(indio_dev); + unsigned int i; + int ret; + + /* + * Use a high register address (virtual register) to request a write of + * 0xA5 to the ADC during the first 8 SCLKs of the ADC data read cycle, + * thus exiting continuous read. + */ + ret = regmap_write(st->regmap, AD4170_ADC_CTRL_CONT_READ_EXIT_REG, 0); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG, + AD4170_ADC_CTRL_CONT_READ_MSK, + FIELD_PREP(AD4170_ADC_CTRL_CONT_READ_MSK, + AD4170_ADC_CTRL_CONT_READ_DISABLE)); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, AD4170_ADC_CTRL_REG, + AD4170_ADC_CTRL_MODE_MSK, + FIELD_PREP(AD4170_ADC_CTRL_MODE_MSK, + AD4170_ADC_CTRL_MODE_IDLE)); + if (ret) + return ret; + + /* + * The ADC sequences through all the enabled channels (see datasheet + * page 95). That can lead to incorrect channel being read if a + * single-shot read (or buffered read with different active_scan_mask) + * is done after buffer disable. Disable all channels so only requested + * channels will be read. + */ + for (i = 0; i < indio_dev->num_channels; i++) { + if (indio_dev->channels[i].type == IIO_TIMESTAMP) + continue; + + ret = ad4170_set_channel_enable(st, i, false); + if (ret) + return ret; + } + + return 0; +} + +static bool ad4170_validate_scan_mask(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + unsigned int masklength = iio_get_masklength(indio_dev); + unsigned int enabled; + + /* + * The channel sequencer cycles through the enabled channels in + * sequential order, from channel 0 to channel 15, bypassing disabled + * channels. When more than one channel is enabled, channel 0 must + * always be enabled. See datasheet channel_en register description at + * page 95. + */ + enabled = bitmap_weight(scan_mask, masklength); + if (enabled > 1) + return test_bit(0, scan_mask); + + return enabled == 1; +} + +static const struct iio_buffer_setup_ops ad4170_buffer_ops = { + .postenable = ad4170_buffer_postenable, + .predisable = ad4170_buffer_predisable, + .validate_scan_mask = ad4170_validate_scan_mask, +}; + +static irqreturn_t ad4170_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad4170_state *st = iio_priv(indio_dev); + unsigned int chan_index; + unsigned int i = 0; + int ret; + + iio_for_each_active_channel(indio_dev, chan_index) { + ret = spi_sync(st->spi, &st->msg); + if (ret) + goto err_out; + + memcpy(&st->bounce_buffer[i++], st->rx_buf, ARRAY_SIZE(st->rx_buf)); + } + + iio_push_to_buffers_with_ts(indio_dev, st->bounce_buffer, + sizeof(st->bounce_buffer), + iio_get_time_ns(indio_dev)); +err_out: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static const struct iio_trigger_ops ad4170_trigger_ops = { + .validate_device = iio_trigger_validate_own_device, +}; + +static irqreturn_t ad4170_irq_handler(int irq, void *dev_id) +{ + struct iio_dev *indio_dev = dev_id; + struct ad4170_state *st = iio_priv(indio_dev); + + if (iio_buffer_enabled(indio_dev)) + iio_trigger_poll(st->trig); + else + complete(&st->completion); + + return IRQ_HANDLED; +}; + +static int ad4170_trigger_setup(struct iio_dev *indio_dev) +{ + struct ad4170_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + int ret; + + st->trig = devm_iio_trigger_alloc(dev, "%s-trig%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!st->trig) + return -ENOMEM; + + st->trig->ops = &ad4170_trigger_ops; + + iio_trigger_set_drvdata(st->trig, indio_dev); + ret = devm_iio_trigger_register(dev, st->trig); + if (ret) + return dev_err_probe(dev, ret, "Failed to register trigger\n"); + + indio_dev->trig = iio_trigger_get(st->trig); + + return 0; +} + +static int ad4170_regulator_setup(struct ad4170_state *st) +{ + struct device *dev = &st->spi->dev; + int ret; + + /* Required regulators */ + ret = devm_regulator_get_enable_read_voltage(dev, "avdd"); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get AVDD voltage.\n"); + + st->vrefs_uv[AD4170_AVDD_SUP] = ret; + + ret = devm_regulator_get_enable_read_voltage(dev, "iovdd"); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get IOVDD voltage.\n"); + + st->vrefs_uv[AD4170_IOVDD_SUP] = ret; + + /* Optional regulators */ + ret = devm_regulator_get_enable_read_voltage(dev, "avss"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "Failed to get AVSS voltage.\n"); + + /* + * Assume AVSS at GND (0V) if not provided. + * REVISIT: AVSS is never above system ground level (i.e. AVSS is either + * GND or a negative voltage). But we currently don't have support for + * reading negative voltages with the regulator framework. So, the + * current AD4170 support reads a positive value from the regulator, + * then inverts sign to make that negative. + */ + st->vrefs_uv[AD4170_AVSS_SUP] = ret == -ENODEV ? 0 : -ret; + + ret = devm_regulator_get_enable_read_voltage(dev, "refin1p"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "Failed to get REFIN+ voltage.\n"); + + st->vrefs_uv[AD4170_REFIN1P_SUP] = ret; + + ret = devm_regulator_get_enable_read_voltage(dev, "refin1n"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "Failed to get REFIN- voltage.\n"); + + /* + * Negative supplies are assumed to provide negative voltage. + * REVISIT when support for negative regulator voltage read be available + * in the regulator framework. + */ + st->vrefs_uv[AD4170_REFIN1N_SUP] = ret == -ENODEV ? -ENODEV : -ret; + + ret = devm_regulator_get_enable_read_voltage(dev, "refin2p"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "Failed to get REFIN2+ voltage.\n"); + + st->vrefs_uv[AD4170_REFIN2P_SUP] = ret; + + ret = devm_regulator_get_enable_read_voltage(dev, "refin2n"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "Failed to get REFIN2- voltage.\n"); + + /* + * Negative supplies are assumed to provide negative voltage. + * REVISIT when support for negative regulator voltage read be available + * in the regulator framework. + */ + st->vrefs_uv[AD4170_REFIN2N_SUP] = ret == -ENODEV ? -ENODEV : -ret; + + return 0; +} + +static int ad4170_probe(struct spi_device *spi) +{ + const struct ad4170_chip_info *chip; + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ad4170_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; + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + chip = spi_get_device_match_data(spi); + if (!chip) + return -EINVAL; + + indio_dev->name = chip->name; + indio_dev->info = &ad4170_info; + + st->regmap = devm_regmap_init(dev, NULL, st, &ad4170_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "Failed to initialize regmap\n"); + + ret = ad4170_regulator_setup(st); + if (ret) + return ret; + + ret = ad4170_soft_reset(st); + if (ret) + return ret; + + ret = ad4170_parse_firmware(indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to parse firmware\n"); + + ret = ad4170_initial_config(indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to setup device\n"); + + init_completion(&st->completion); + + if (spi->irq) { + ret = devm_request_irq(dev, spi->irq, &ad4170_irq_handler, + IRQF_NO_THREAD, indio_dev->name, indio_dev); + if (ret) + return ret; + + ret = ad4170_trigger_setup(indio_dev); + if (ret) + return ret; + } + + ret = ad4170_prepare_spi_message(st); + if (ret) + return dev_err_probe(dev, ret, "Failed to prepare SPI message\n"); + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + &ad4170_trigger_handler, + &ad4170_buffer_ops); + if (ret) + return dev_err_probe(dev, ret, "Failed to setup read buffer\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id ad4170_id_table[] = { + { .name = "ad4170-4", .driver_data = (kernel_ulong_t)&ad4170_chip_info }, + { .name = "ad4190-4", .driver_data = (kernel_ulong_t)&ad4190_chip_info }, + { .name = "ad4195-4", .driver_data = (kernel_ulong_t)&ad4195_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad4170_id_table); + +static const struct of_device_id ad4170_of_match[] = { + { .compatible = "adi,ad4170-4", .data = &ad4170_chip_info }, + { .compatible = "adi,ad4190-4", .data = &ad4190_chip_info }, + { .compatible = "adi,ad4195-4", .data = &ad4195_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad4170_of_match); + +static struct spi_driver ad4170_driver = { + .driver = { + .name = "ad4170-4", + .of_match_table = ad4170_of_match, + }, + .probe = ad4170_probe, + .id_table = ad4170_id_table, +}; +module_spi_driver(ad4170_driver); + +MODULE_AUTHOR("Ana-Maria Cusco "); +MODULE_AUTHOR("Marcelo Schmitt "); +MODULE_DESCRIPTION("Analog Devices AD4170 SPI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c new file mode 100644 index 000000000..f2f7c4c64 --- /dev/null +++ b/drivers/iio/adc/ad4691.c @@ -0,0 +1,2083 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2024-2026 Analog Devices, Inc. + * Author: Radu Sabau + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define AD4691_VREF_uV_MIN 2400000 +#define AD4691_VREF_uV_MAX 5250000 +#define AD4691_VREF_2P5_uV_MAX 2750000 +#define AD4691_VREF_3P0_uV_MAX 3250000 +#define AD4691_VREF_3P3_uV_MAX 3750000 +#define AD4691_VREF_4P096_uV_MAX 4500000 + +#define AD4691_CNV_DUTY_CYCLE_NS 380 +#define AD4691_CNV_HIGH_TIME_NS 430 +/* + * Conservative default for the manual offload periodic trigger. Low enough + * to work safely out of the box across all OSR and channel count combinations. + */ +#define AD4691_OFFLOAD_INITIAL_TRIGGER_HZ (100 * HZ_PER_KHZ) + +#define AD4691_SPI_CONFIG_A_REG 0x000 +#define AD4691_SW_RESET (BIT(7) | BIT(0)) + +#define AD4691_STATUS_REG 0x014 +#define AD4691_CLAMP_STATUS1_REG 0x01A +#define AD4691_CLAMP_STATUS2_REG 0x01B +#define AD4691_DEVICE_SETUP 0x020 +#define AD4691_MANUAL_MODE BIT(2) +#define AD4691_LDO_EN BIT(4) +#define AD4691_REF_CTRL 0x021 +#define AD4691_REF_CTRL_MASK GENMASK(4, 2) +#define AD4691_REFBUF_EN BIT(0) +#define AD4691_OSC_FREQ_REG 0x023 +#define AD4691_OSC_FREQ_MASK GENMASK(3, 0) +#define AD4691_STD_SEQ_CONFIG 0x025 +#define AD4691_SEQ_ALL_CHANNELS_OFF 0x00 +#define AD4691_SPARE_CONTROL 0x02A + +#define AD4691_MAX_CHANNELS 16 + +#define AD4691_NOOP 0x00 +#define AD4691_ADC_CHAN(ch) ((0x10 + (ch)) << 3) +#define AD4691_EXIT_COMMAND 0x5000 + +#define AD4691_OSC_EN_REG 0x180 +#define AD4691_STATE_RESET_REG 0x181 +#define AD4691_STATE_RESET_ALL BIT(0) +#define AD4691_ADC_SETUP 0x182 +#define AD4691_ADC_MODE_MASK GENMASK(1, 0) +#define AD4691_CNV_BURST_MODE 0x01 +#define AD4691_AUTONOMOUS_MODE 0x02 +/* + * ACC_MASK_REG covers both mask bytes via ADDR_DESCENDING SPI: writing a + * 16-bit BE value to 0x185 auto-decrements to 0x184 for the second byte. + */ +#define AD4691_ACC_MASK_REG 0x185 +#define AD4691_ACC_DEPTH_IN(n) (0x186 + (n)) +#define AD4691_GPIO_MODE1_REG 0x196 +#define AD4691_GPIO_MODE2_REG 0x197 +#define AD4691_GP_MODE_MASK GENMASK(3, 0) +#define AD4691_GP_MODE_DATA_READY 0x06 +#define AD4691_GPIO_READ 0x1A0 +#define AD4691_ACC_STATUS_FULL1_REG 0x1B0 +#define AD4691_ACC_STATUS_FULL2_REG 0x1B1 +#define AD4691_ACC_STATUS_OVERRUN1_REG 0x1B2 +#define AD4691_ACC_STATUS_OVERRUN2_REG 0x1B3 +#define AD4691_ACC_STATUS_SAT1_REG 0x1B4 +#define AD4691_ACC_STATUS_SAT2_REG 0x1BE +#define AD4691_ACC_SAT_OVR_REG(n) (0x1C0 + (n)) +#define AD4691_AVG_IN(n) (0x201 + (2 * (n))) +#define AD4691_AVG_STS_IN(n) (0x222 + (3 * (n))) +#define AD4691_ACC_IN(n) (0x252 + (3 * (n))) +#define AD4691_ACC_STS_DATA(n) (0x283 + (4 * (n))) + + +static const char * const ad4691_supplies[] = { "avdd", "vio" }; + +enum ad4691_ref_ctrl { + AD4691_VREF_2P5, + AD4691_VREF_3P0, + AD4691_VREF_3P3, + AD4691_VREF_4P096, + AD4691_VREF_5P0 +}; + +struct ad4691_channel_info { + const struct iio_chan_spec *channels __counted_by_ptr(num_channels); + const struct iio_chan_spec *manual_channels __counted_by_ptr(num_channels); + unsigned int num_channels; +}; + +struct ad4691_chip_info { + const char *name; + unsigned int max_rate; + const struct ad4691_channel_info *sw_info; + const struct ad4691_channel_info *offload_info; +}; + +/* CNV burst mode channel — exposes oversampling ratio. */ +#define AD4691_CHANNEL(ch) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE) \ + | BIT(IIO_CHAN_INFO_SAMP_FREQ) \ + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) \ + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .channel = ch, \ + .scan_index = ch, \ + .scan_type = { \ + .format = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + } + +/* + * Manual mode channel — no oversampling ratio attribute. OSR is not + * supported in manual mode; ACC_DEPTH_IN is not configured during manual + * buffer enable. + */ +#define AD4691_MANUAL_CHANNEL(ch) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE) \ + | BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .channel = ch, \ + .scan_index = ch, \ + .scan_type = { \ + .format = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + } + +/* + * Offload path (bits_per_word=16): the SPI Engine assembles received + * bits into native 16-bit words before DMA, so samples are in + * CPU-native byte order (IIO_CPU). storagebits=16 matches the 16-bit + * DMA word size. + * + * CNV burst offload configures ACC_DEPTH_IN per channel, so the + * oversampling_ratio attribute is exposed. Manual offload does not; + * use AD4691_OFFLOAD_MANUAL_CHANNEL for that path. + */ +#define AD4691_OFFLOAD_CHANNEL(ch) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE) \ + | BIT(IIO_CHAN_INFO_SAMP_FREQ) \ + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) \ + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .channel = ch, \ + .scan_index = ch, \ + .scan_type = { \ + .format = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + }, \ + } + +/* Manual offload — same IIO_CPU layout but no oversampling_ratio attribute. */ +#define AD4691_OFFLOAD_MANUAL_CHANNEL(ch) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE) \ + | BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .channel = ch, \ + .scan_index = ch, \ + .scan_type = { \ + .format = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + }, \ + } + +static const struct iio_chan_spec ad4691_channels[] = { + AD4691_CHANNEL(0), + AD4691_CHANNEL(1), + AD4691_CHANNEL(2), + AD4691_CHANNEL(3), + AD4691_CHANNEL(4), + AD4691_CHANNEL(5), + AD4691_CHANNEL(6), + AD4691_CHANNEL(7), + AD4691_CHANNEL(8), + AD4691_CHANNEL(9), + AD4691_CHANNEL(10), + AD4691_CHANNEL(11), + AD4691_CHANNEL(12), + AD4691_CHANNEL(13), + AD4691_CHANNEL(14), + AD4691_CHANNEL(15), + IIO_CHAN_SOFT_TIMESTAMP(16), +}; + +static const struct iio_chan_spec ad4693_channels[] = { + AD4691_CHANNEL(0), + AD4691_CHANNEL(1), + AD4691_CHANNEL(2), + AD4691_CHANNEL(3), + AD4691_CHANNEL(4), + AD4691_CHANNEL(5), + AD4691_CHANNEL(6), + AD4691_CHANNEL(7), + IIO_CHAN_SOFT_TIMESTAMP(8), +}; + +/* + * Offload channel arrays: no IIO_CHAN_SOFT_TIMESTAMP because DMA delivers + * data directly to userspace without a software timestamp. + */ +static const struct iio_chan_spec ad4691_offload_channels[] = { + AD4691_OFFLOAD_CHANNEL(0), + AD4691_OFFLOAD_CHANNEL(1), + AD4691_OFFLOAD_CHANNEL(2), + AD4691_OFFLOAD_CHANNEL(3), + AD4691_OFFLOAD_CHANNEL(4), + AD4691_OFFLOAD_CHANNEL(5), + AD4691_OFFLOAD_CHANNEL(6), + AD4691_OFFLOAD_CHANNEL(7), + AD4691_OFFLOAD_CHANNEL(8), + AD4691_OFFLOAD_CHANNEL(9), + AD4691_OFFLOAD_CHANNEL(10), + AD4691_OFFLOAD_CHANNEL(11), + AD4691_OFFLOAD_CHANNEL(12), + AD4691_OFFLOAD_CHANNEL(13), + AD4691_OFFLOAD_CHANNEL(14), + AD4691_OFFLOAD_CHANNEL(15), +}; + +static const struct iio_chan_spec ad4693_offload_channels[] = { + AD4691_OFFLOAD_CHANNEL(0), + AD4691_OFFLOAD_CHANNEL(1), + AD4691_OFFLOAD_CHANNEL(2), + AD4691_OFFLOAD_CHANNEL(3), + AD4691_OFFLOAD_CHANNEL(4), + AD4691_OFFLOAD_CHANNEL(5), + AD4691_OFFLOAD_CHANNEL(6), + AD4691_OFFLOAD_CHANNEL(7), +}; + +static const struct iio_chan_spec ad4691_manual_channels[] = { + AD4691_MANUAL_CHANNEL(0), + AD4691_MANUAL_CHANNEL(1), + AD4691_MANUAL_CHANNEL(2), + AD4691_MANUAL_CHANNEL(3), + AD4691_MANUAL_CHANNEL(4), + AD4691_MANUAL_CHANNEL(5), + AD4691_MANUAL_CHANNEL(6), + AD4691_MANUAL_CHANNEL(7), + AD4691_MANUAL_CHANNEL(8), + AD4691_MANUAL_CHANNEL(9), + AD4691_MANUAL_CHANNEL(10), + AD4691_MANUAL_CHANNEL(11), + AD4691_MANUAL_CHANNEL(12), + AD4691_MANUAL_CHANNEL(13), + AD4691_MANUAL_CHANNEL(14), + AD4691_MANUAL_CHANNEL(15), + IIO_CHAN_SOFT_TIMESTAMP(16), +}; + +static const struct iio_chan_spec ad4693_manual_channels[] = { + AD4691_MANUAL_CHANNEL(0), + AD4691_MANUAL_CHANNEL(1), + AD4691_MANUAL_CHANNEL(2), + AD4691_MANUAL_CHANNEL(3), + AD4691_MANUAL_CHANNEL(4), + AD4691_MANUAL_CHANNEL(5), + AD4691_MANUAL_CHANNEL(6), + AD4691_MANUAL_CHANNEL(7), + IIO_CHAN_SOFT_TIMESTAMP(8), +}; + +static const struct iio_chan_spec ad4691_offload_manual_channels[] = { + AD4691_OFFLOAD_MANUAL_CHANNEL(0), + AD4691_OFFLOAD_MANUAL_CHANNEL(1), + AD4691_OFFLOAD_MANUAL_CHANNEL(2), + AD4691_OFFLOAD_MANUAL_CHANNEL(3), + AD4691_OFFLOAD_MANUAL_CHANNEL(4), + AD4691_OFFLOAD_MANUAL_CHANNEL(5), + AD4691_OFFLOAD_MANUAL_CHANNEL(6), + AD4691_OFFLOAD_MANUAL_CHANNEL(7), + AD4691_OFFLOAD_MANUAL_CHANNEL(8), + AD4691_OFFLOAD_MANUAL_CHANNEL(9), + AD4691_OFFLOAD_MANUAL_CHANNEL(10), + AD4691_OFFLOAD_MANUAL_CHANNEL(11), + AD4691_OFFLOAD_MANUAL_CHANNEL(12), + AD4691_OFFLOAD_MANUAL_CHANNEL(13), + AD4691_OFFLOAD_MANUAL_CHANNEL(14), + AD4691_OFFLOAD_MANUAL_CHANNEL(15), +}; + +static const struct iio_chan_spec ad4693_offload_manual_channels[] = { + AD4691_OFFLOAD_MANUAL_CHANNEL(0), + AD4691_OFFLOAD_MANUAL_CHANNEL(1), + AD4691_OFFLOAD_MANUAL_CHANNEL(2), + AD4691_OFFLOAD_MANUAL_CHANNEL(3), + AD4691_OFFLOAD_MANUAL_CHANNEL(4), + AD4691_OFFLOAD_MANUAL_CHANNEL(5), + AD4691_OFFLOAD_MANUAL_CHANNEL(6), + AD4691_OFFLOAD_MANUAL_CHANNEL(7), +}; + +static const int ad4691_oversampling_ratios[] = { 1, 2, 4, 8, 16, 32 }; + +static const struct ad4691_channel_info ad4691_sw_info = { + .channels = ad4691_channels, + .manual_channels = ad4691_manual_channels, + .num_channels = ARRAY_SIZE(ad4691_channels), +}; + +static const struct ad4691_channel_info ad4693_sw_info = { + .channels = ad4693_channels, + .manual_channels = ad4693_manual_channels, + .num_channels = ARRAY_SIZE(ad4693_channels), +}; + +static const struct ad4691_channel_info ad4691_offload_info = { + .channels = ad4691_offload_channels, + .manual_channels = ad4691_offload_manual_channels, + .num_channels = ARRAY_SIZE(ad4691_offload_channels), +}; + +static const struct ad4691_channel_info ad4693_offload_info = { + .channels = ad4693_offload_channels, + .manual_channels = ad4693_offload_manual_channels, + .num_channels = ARRAY_SIZE(ad4693_offload_channels), +}; + +/* + * Internal oscillator frequency table. Index is the OSC_FREQ_REG[3:0] value. + * Index 0 (1 MHz) is only valid for AD4692/AD4694; AD4691/AD4693 support + * up to 500 kHz and use index 1 as their highest valid rate. + */ +static const int ad4691_osc_freqs_Hz[] = { + [0x0] = 1000000, + [0x1] = 500000, + [0x2] = 400000, + [0x3] = 250000, + [0x4] = 200000, + [0x5] = 167000, + [0x6] = 133000, + [0x7] = 125000, + [0x8] = 100000, + [0x9] = 50000, + [0xA] = 25000, + [0xB] = 12500, + [0xC] = 10000, + [0xD] = 5000, + [0xE] = 2500, + [0xF] = 1250, +}; + +static const char * const ad4691_gp_names[] = { "gp0", "gp1", "gp2", "gp3" }; + +static const struct ad4691_chip_info ad4691_chip_info = { + .name = "ad4691", + .max_rate = 500 * HZ_PER_KHZ, + .sw_info = &ad4691_sw_info, + .offload_info = &ad4691_offload_info, +}; + +static const struct ad4691_chip_info ad4692_chip_info = { + .name = "ad4692", + .max_rate = 1 * HZ_PER_MHZ, + .sw_info = &ad4691_sw_info, + .offload_info = &ad4691_offload_info, +}; + +static const struct ad4691_chip_info ad4693_chip_info = { + .name = "ad4693", + .max_rate = 500 * HZ_PER_KHZ, + .sw_info = &ad4693_sw_info, + .offload_info = &ad4693_offload_info, +}; + +static const struct ad4691_chip_info ad4694_chip_info = { + .name = "ad4694", + .max_rate = 1 * HZ_PER_MHZ, + .sw_info = &ad4693_sw_info, + .offload_info = &ad4693_offload_info, +}; + +struct ad4691_state { + const struct ad4691_chip_info *info; + struct regmap *regmap; + struct spi_device *spi; + + struct pwm_device *conv_trigger; + int irq; + int vref_uV; + u32 cnv_period_ns; + /* + * Snapped oscillator frequency (Hz) shared by all channels. Set when + * sampling_frequency or oversampling_ratio is written; written to + * OSC_FREQ_REG at buffer enable and single-shot time so both attributes + * can be set in any order. Reading in_voltage_sampling_frequency + * returns target_osc_freq_Hz / osr — the effective rate given the + * shared oversampling ratio. + */ + u32 target_osc_freq_Hz; + /* Shared oversampling ratio across all channels; always 1 in manual mode. */ + unsigned int osr; + /* + * Precomputed effective-rate lists, one row per entry in + * ad4691_oversampling_ratios[]. Populated at probe; read_avail picks + * the row for the current shared OSR. The tables are stable after + * probe so returning a pointer into them from read_avail is race-free. + */ + int samp_freq_avail[ARRAY_SIZE(ad4691_oversampling_ratios)][ARRAY_SIZE(ad4691_osc_freqs_Hz)]; + int samp_freq_avail_len[ARRAY_SIZE(ad4691_oversampling_ratios)]; + + bool manual_mode; + bool irq_enabled; + bool refbuf_en; + bool ldo_en; + /* + * Synchronize access to members of the driver state, and ensure + * atomicity of consecutive SPI operations. + */ + struct mutex lock; + /* NULL when no SPI offload hardware is present. */ + struct spi_offload *offload; + struct spi_offload_trigger *offload_trigger; + u64 trigger_hz; + /* + * Per-buffer-enable lifetime resources: + * Manual Mode - a pre-built SPI message that clocks out N+1 + * transfers in one go. + * CNV Burst Mode - a pre-built SPI message that clocks out 2*N + * transfers in one go. + */ + struct spi_message scan_msg; + /* + * max 16 + 1 NOOP (manual) or 2*16 + 1 state-reset (CNV burst). + */ + struct spi_transfer scan_xfers[34]; + /* + * CNV burst: 16 AVG_IN addresses = 16. Manual: 16 channel cmds + + * 1 NOOP = 17. Stored as native u16. The non-offload path fills slots + * with put_unaligned_be16() (bits_per_word=8, bytes go out in memory + * order). The offload path assigns native values directly + * (bits_per_word=bpw, SPI reads each slot as a native 16-bit word and + * shifts it out MSB-first). + */ + u16 scan_tx[17] __aligned(IIO_DMA_MINALIGN); + /* + * CNV burst state-reset: 4-byte write [addr_hi, addr_lo, + * STATE_RESET_ALL, OSC_EN=1]. CS is asserted throughout, so + * ADDR_DESCENDING writes byte[3]=1 to OSC_EN_REG (0x180) as a + * deliberate side-write, keeping the oscillator enabled. Shared + * with the offload path (mutually exclusive at probe). + */ + u8 scan_tx_reset[4] __aligned(IIO_DMA_MINALIGN); + /* + * Scan buffer: one BE16 slot per active channel, plus timestamp. + * DMA-aligned because scan_xfers point rx_buf directly into vals[]. + */ + IIO_DECLARE_DMA_BUFFER_WITH_TS(__be16, vals, 16); +}; + +/* + * Configure the given GP pin (0-3) as DATA_READY output. + * GP0/GP1 → GPIO_MODE1_REG, GP2/GP3 → GPIO_MODE2_REG. + * Even pins occupy bits [3:0], odd pins bits [7:4]. + */ +static int ad4691_gpio_setup(struct ad4691_state *st, unsigned int gp_num) +{ + unsigned int bit_off = gp_num % 2; + unsigned int reg_off = gp_num / 2; + unsigned int shift = 4 * bit_off; + + return regmap_update_bits(st->regmap, + AD4691_GPIO_MODE1_REG + reg_off, + AD4691_GP_MODE_MASK << shift, + AD4691_GP_MODE_DATA_READY << shift); +} + +static const struct spi_offload_config ad4691_offload_config = { + .capability_flags = SPI_OFFLOAD_CAP_TRIGGER | + SPI_OFFLOAD_CAP_RX_STREAM_DMA, +}; + +static bool ad4691_offload_trigger_match(struct spi_offload_trigger *trigger, + enum spi_offload_trigger_type type, + u64 *args, u32 nargs) +{ + return type == SPI_OFFLOAD_TRIGGER_DATA_READY && nargs == 1 && args[0] <= 3; +} + +static int ad4691_offload_trigger_request(struct spi_offload_trigger *trigger, + enum spi_offload_trigger_type type, + u64 *args, u32 nargs) +{ + struct ad4691_state *st = spi_offload_trigger_get_priv(trigger); + + if (nargs != 1 || args[0] > 3) + return -EINVAL; + + return ad4691_gpio_setup(st, args[0]); +} + +static int ad4691_offload_trigger_validate(struct spi_offload_trigger *trigger, + struct spi_offload_trigger_config *config) +{ + if (config->type != SPI_OFFLOAD_TRIGGER_DATA_READY) + return -EINVAL; + + return 0; +} + +static const struct spi_offload_trigger_ops ad4691_offload_trigger_ops = { + .match = ad4691_offload_trigger_match, + .request = ad4691_offload_trigger_request, + .validate = ad4691_offload_trigger_validate, +}; + +static int ad4691_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + struct spi_device *spi = context; + u8 tx[2], rx[4]; + int ret; + + /* Set bit 15 to mark the operation as READ. */ + put_unaligned_be16(0x8000 | reg, tx); + + switch (reg) { + case 0 ... AD4691_OSC_FREQ_REG: + case AD4691_SPARE_CONTROL ... AD4691_ACC_MASK_REG - 1: + case AD4691_ACC_MASK_REG + 1 ... AD4691_ACC_SAT_OVR_REG(15): + ret = spi_write_then_read(spi, tx, sizeof(tx), rx, 1); + if (ret) + return ret; + *val = rx[0]; + return 0; + case AD4691_ACC_MASK_REG: + case AD4691_STD_SEQ_CONFIG: + case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15): + ret = spi_write_then_read(spi, tx, sizeof(tx), rx, 2); + if (ret) + return ret; + *val = get_unaligned_be16(rx); + return 0; + case AD4691_AVG_STS_IN(0) ... AD4691_AVG_STS_IN(15): + case AD4691_ACC_IN(0) ... AD4691_ACC_IN(15): + ret = spi_write_then_read(spi, tx, sizeof(tx), rx, 3); + if (ret) + return ret; + *val = get_unaligned_be24(rx); + return 0; + case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15): + ret = spi_write_then_read(spi, tx, sizeof(tx), rx, 4); + if (ret) + return ret; + *val = get_unaligned_be32(rx); + return 0; + default: + return -EINVAL; + } +} + +static int ad4691_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct spi_device *spi = context; + u8 tx[4]; + + put_unaligned_be16(reg, tx); + + switch (reg) { + case 0 ... AD4691_OSC_FREQ_REG: + case AD4691_SPARE_CONTROL ... AD4691_ACC_MASK_REG - 1: + case AD4691_ACC_MASK_REG + 1 ... AD4691_GPIO_MODE2_REG: + if (val > U8_MAX) + return -EINVAL; + tx[2] = val; + return spi_write_then_read(spi, tx, 3, NULL, 0); + case AD4691_ACC_MASK_REG: + case AD4691_STD_SEQ_CONFIG: + if (val > U16_MAX) + return -EINVAL; + put_unaligned_be16(val, &tx[2]); + return spi_write_then_read(spi, tx, 4, NULL, 0); + default: + return -EINVAL; + } +} + +static bool ad4691_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AD4691_STATUS_REG: + case AD4691_CLAMP_STATUS1_REG: + case AD4691_CLAMP_STATUS2_REG: + case AD4691_GPIO_READ: + case AD4691_ACC_STATUS_FULL1_REG ... AD4691_ACC_STATUS_SAT2_REG: + case AD4691_ACC_SAT_OVR_REG(0) ... AD4691_ACC_SAT_OVR_REG(15): + case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15): + case AD4691_AVG_STS_IN(0) ... AD4691_AVG_STS_IN(15): + case AD4691_ACC_IN(0) ... AD4691_ACC_IN(15): + case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15): + return true; + default: + return false; + } +} + +static bool ad4691_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0 ... AD4691_OSC_FREQ_REG: + case AD4691_SPARE_CONTROL ... AD4691_ACC_SAT_OVR_REG(15): + case AD4691_STD_SEQ_CONFIG: + return true; + default: + break; + } + + /* + * Multi-byte result registers have non-unit strides; only the base + * address of each entry is a valid single-register read. + */ + if (reg >= AD4691_AVG_IN(0) && reg <= AD4691_AVG_IN(15)) + return (reg - AD4691_AVG_IN(0)) % 2 == 0; + if (reg >= AD4691_AVG_STS_IN(0) && reg <= AD4691_AVG_STS_IN(15)) + return (reg - AD4691_AVG_STS_IN(0)) % 3 == 0; + if (reg >= AD4691_ACC_IN(0) && reg <= AD4691_ACC_IN(15)) + return (reg - AD4691_ACC_IN(0)) % 3 == 0; + if (reg >= AD4691_ACC_STS_DATA(0) && reg <= AD4691_ACC_STS_DATA(15)) + return (reg - AD4691_ACC_STS_DATA(0)) % 4 == 0; + + return false; +} + +static bool ad4691_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0 ... AD4691_OSC_FREQ_REG: + case AD4691_STD_SEQ_CONFIG: + case AD4691_SPARE_CONTROL ... AD4691_GPIO_MODE2_REG: + return true; + default: + return false; + } +} + +static const struct regmap_config ad4691_regmap_config = { + .reg_bits = 16, + .val_bits = 32, + .reg_read = ad4691_reg_read, + .reg_write = ad4691_reg_write, + .volatile_reg = ad4691_volatile_reg, + .readable_reg = ad4691_readable_reg, + .writeable_reg = ad4691_writeable_reg, + .max_register = AD4691_ACC_STS_DATA(15), + .cache_type = REGCACHE_MAPLE, +}; + +/* + * Index 0 in ad4691_osc_freqs_Hz is 1 MHz — valid only for AD4692/AD4694 + * (max_rate == 1 MHz). AD4691/AD4693 cap at 500 kHz so their valid range + * starts at index 1. + */ +static unsigned int ad4691_samp_freq_start(const struct ad4691_chip_info *info) +{ + return (info->max_rate == 1 * HZ_PER_MHZ) ? 0 : 1; +} + +/* + * Find the largest oscillator table entry that is both <= needed_osc and + * evenly divisible by osr (guaranteeing an integer effective rate on + * read-back). Returns 0 if no such entry exists in the chip's valid range. + */ +static unsigned int ad4691_find_osc_freq(struct ad4691_state *st, + unsigned int needed_osc, + unsigned int osr) +{ + unsigned int start = ad4691_samp_freq_start(st->info); + + for (unsigned int i = start; i < ARRAY_SIZE(ad4691_osc_freqs_Hz); i++) { + if ((unsigned int)ad4691_osc_freqs_Hz[i] > needed_osc) + continue; + if (ad4691_osc_freqs_Hz[i] % osr) + continue; + return ad4691_osc_freqs_Hz[i]; + } + return 0; +} + +/* Write target_osc_freq_Hz to OSC_FREQ_REG. Called at use time. */ +static int ad4691_write_osc_freq(struct ad4691_state *st) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(ad4691_osc_freqs_Hz); i++) { + if (ad4691_osc_freqs_Hz[i] == st->target_osc_freq_Hz) + return regmap_write(st->regmap, AD4691_OSC_FREQ_REG, i); + } + return -EINVAL; +} + +/* Return the index of osr in ad4691_oversampling_ratios[], defaulting to 0. */ +static unsigned int ad4691_osr_index(unsigned int osr) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(ad4691_oversampling_ratios) - 1; i++) { + if ((unsigned int)ad4691_oversampling_ratios[i] == osr) + return i; + } + return ARRAY_SIZE(ad4691_oversampling_ratios) - 1; +} + +/* + * Precompute samp_freq_avail[][]: for each OSR value, list the oscillator + * table entries that divide evenly by that OSR, expressed as effective rates + * (osc_freq / osr). Called once at probe after st->info is set. + */ +static void ad4691_precompute_samp_freq_avail(struct ad4691_state *st) +{ + unsigned int start = ad4691_samp_freq_start(st->info); + + for (unsigned int i = 0; i < ARRAY_SIZE(ad4691_oversampling_ratios); i++) { + unsigned int osr = ad4691_oversampling_ratios[i]; + int n = 0; + + for (unsigned int j = start; j < ARRAY_SIZE(ad4691_osc_freqs_Hz); j++) { + if (ad4691_osc_freqs_Hz[j] % osr) + continue; + st->samp_freq_avail[i][n++] = ad4691_osc_freqs_Hz[j] / osr; + } + st->samp_freq_avail_len[i] = n; + } +} + +static int ad4691_set_sampling_freq(struct ad4691_state *st, int freq) +{ + unsigned int osr, found; + + /* + * Read osr under st->lock: osr and target_osc_freq_Hz are modified + * together under the lock; reading after acquiring it ensures we see + * a consistent snapshot with no concurrent write racing us. + */ + guard(mutex)(&st->lock); + osr = st->osr; + + if (freq <= 0 || (unsigned int)freq > st->info->max_rate / osr) + return -EINVAL; + + found = ad4691_find_osc_freq(st, (unsigned int)freq * osr, osr); + if (!found) + return -EINVAL; + + /* + * Store the snapped oscillator frequency; OSC_FREQ_REG is written at + * buffer enable and single-shot time so that sampling_frequency and + * oversampling_ratio can be set in any order. + */ + st->target_osc_freq_Hz = found; + return 0; +} + +static int ad4691_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, + int *length, long mask) +{ + struct ad4691_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: { + unsigned int osr_idx; + + /* + * The precomputed tables are stable after probe; only the + * current OSR needs to be read under the lock to pick the + * right row atomically. + */ + guard(mutex)(&st->lock); + osr_idx = ad4691_osr_index(st->osr); + *vals = st->samp_freq_avail[osr_idx]; + *type = IIO_VAL_INT; + *length = st->samp_freq_avail_len[osr_idx]; + return IIO_AVAIL_LIST; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = ad4691_oversampling_ratios; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(ad4691_oversampling_ratios); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int ad4691_single_shot_read(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val) +{ + struct ad4691_state *st = iio_priv(indio_dev); + unsigned int reg_val, period_us; + int ret; + + guard(mutex)(&st->lock); + + /* Use AUTONOMOUS mode for single-shot reads. */ + ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG, AD4691_STATE_RESET_ALL); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG, + BIT(chan->channel)); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD4691_ACC_MASK_REG, + ~BIT(chan->channel) & GENMASK(15, 0)); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(0), st->osr); + if (ret) + return ret; + + ret = ad4691_write_osc_freq(st); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 1); + if (ret) + return ret; + + /* + * Wait osr + 1 oscillator periods: osr for accumulation, +1 for the + * pipeline margin (one extra period ensures the final result is ready). + */ + period_us = DIV_ROUND_UP((st->osr + 1) * USEC_PER_SEC, + st->target_osc_freq_Hz); + fsleep(period_us); + + ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 0); + if (ret) + return ret; + + ret = regmap_read(st->regmap, AD4691_AVG_IN(chan->channel), ®_val); + if (ret) + return ret; + + *val = reg_val; + + ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG, AD4691_STATE_RESET_ALL); + if (ret) + return ret; + + return IIO_VAL_INT; +} + +static int ad4691_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long info) +{ + struct ad4691_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_RAW: { + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + return ad4691_single_shot_read(indio_dev, chan, val); + } + case IIO_CHAN_INFO_SAMP_FREQ: { + /* + * Read target_osc_freq_Hz and osr under st->lock to get a + * consistent snapshot: write_raw for SAMP_FREQ or OSR modifies + * both fields under the lock, so a concurrent read without the + * lock could observe a new oscillator frequency with the old OSR. + */ + guard(mutex)(&st->lock); + *val = st->target_osc_freq_Hz / st->osr; + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: { + guard(mutex)(&st->lock); + *val = st->osr; + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_SCALE: + *val = st->vref_uV / (MICRO / MILLI); + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static int ad4691_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ad4691_state *st = iio_priv(indio_dev); + + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return ad4691_set_sampling_freq(st, val); + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: { + unsigned int old_effective, found, osr_idx; + + osr_idx = ad4691_osr_index(val); + if (ad4691_oversampling_ratios[osr_idx] != val) + return -EINVAL; + + /* + * Hold st->lock while computing the new oscillator frequency + * and updating both target_osc_freq_Hz and osr atomically: + * read_raw for SAMP_FREQ reads both fields under the lock and + * must see a consistent pair (new osc ↔ new osr). + * + * Snap target_osc_freq_Hz to the largest table entry that is + * both <= old_effective * new_osr and evenly divisible by + * new_osr, preserving an integer read-back of + * in_voltage_sampling_frequency after the OSR change. + */ + guard(mutex)(&st->lock); + old_effective = st->target_osc_freq_Hz / st->osr; + found = ad4691_find_osc_freq(st, old_effective * (unsigned int)val, val); + if (!found) + return -EINVAL; + st->target_osc_freq_Hz = found; + st->osr = val; + return 0; + } + default: + return -EINVAL; + } +} + +static int ad4691_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct ad4691_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->lock); + + if (readval) + return regmap_read(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); +} + +static int ad4691_set_pwm_freq(struct ad4691_state *st, unsigned int freq) +{ + if (!freq) + return -EINVAL; + + st->cnv_period_ns = DIV_ROUND_UP(NSEC_PER_SEC, freq); + return 0; +} + +static int ad4691_sampling_enable(struct ad4691_state *st, bool enable) +{ + struct pwm_state conv_state = { + .period = st->cnv_period_ns, + .duty_cycle = AD4691_CNV_DUTY_CYCLE_NS, + .polarity = PWM_POLARITY_NORMAL, + .enabled = enable, + }; + + return pwm_apply_might_sleep(st->conv_trigger, &conv_state); +} + +/* + * ad4691_enter_conversion_mode - Switch the chip to its buffer conversion mode. + * + * Configures the ADC hardware registers for the mode selected at probe + * (CNV_BURST or MANUAL). Called from buffer preenable before starting + * sampling. The chip is in AUTONOMOUS mode during idle (for read_raw). + */ +static int ad4691_enter_conversion_mode(struct ad4691_state *st) +{ + int ret; + + if (st->manual_mode) + return regmap_update_bits(st->regmap, AD4691_DEVICE_SETUP, + AD4691_MANUAL_MODE, AD4691_MANUAL_MODE); + + ret = ad4691_write_osc_freq(st); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, AD4691_ADC_SETUP, + AD4691_ADC_MODE_MASK, AD4691_CNV_BURST_MODE); + if (ret) + return ret; + + return regmap_write(st->regmap, AD4691_STATE_RESET_REG, + AD4691_STATE_RESET_ALL); +} + +static int ad4691_transfer(struct ad4691_state *st, u16 cmd) +{ + u8 buf[2]; + + put_unaligned_be16(cmd, buf); + + return spi_write_then_read(st->spi, buf, sizeof(buf), NULL, 0); +} + +/* + * ad4691_exit_conversion_mode - Return the chip to AUTONOMOUS mode. + * + * Called from buffer postdisable to restore the chip to the + * idle state used by read_raw. Clears the sequencer and resets state. + */ +static int ad4691_exit_conversion_mode(struct ad4691_state *st) +{ + if (st->manual_mode) + return ad4691_transfer(st, AD4691_EXIT_COMMAND); + + return regmap_update_bits(st->regmap, AD4691_ADC_SETUP, + AD4691_ADC_MODE_MASK, AD4691_AUTONOMOUS_MODE); +} + +static int ad4691_manual_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ad4691_state *st = iio_priv(indio_dev); + unsigned int k, i; + int ret; + + memset(st->scan_xfers, 0, sizeof(st->scan_xfers)); + memset(st->scan_tx, 0, sizeof(st->scan_tx)); + + spi_message_init(&st->scan_msg); + + k = 0; + iio_for_each_active_channel(indio_dev, i) { + /* + * Channel-select command occupies the first (high) byte of the + * 16-bit DIN frame; the second byte is a don't-care zero pad. + * put_unaligned_be16() writes [cmd, 0x00] in memory so the + * SPI controller sends the command byte first on the wire. + */ + put_unaligned_be16((u16)(AD4691_ADC_CHAN(i) << 8), &st->scan_tx[k]); + st->scan_xfers[k].tx_buf = &st->scan_tx[k]; + /* + * The pipeline means xfer[0] receives the residual from the + * previous sequence, not a valid sample. Discard it (rx_buf=NULL) + * to avoid aliasing vals[0] across two concurrent DMA mappings. + * xfer[1] (or the NOOP when only one channel is active) writes + * the real ch[0] result to vals[0]. Subsequent transfers write + * into vals[k-1] so each result lands at the next dense slot. + */ + st->scan_xfers[k].rx_buf = (k == 0) ? NULL : &st->vals[k - 1]; + st->scan_xfers[k].len = sizeof(*st->scan_tx); + st->scan_xfers[k].cs_change = 1; + st->scan_xfers[k].cs_change_delay.value = AD4691_CNV_HIGH_TIME_NS; + st->scan_xfers[k].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS; + spi_message_add_tail(&st->scan_xfers[k], &st->scan_msg); + k++; + } + + /* Final NOOP transfer retrieves the last channel's result. */ + st->scan_xfers[k].tx_buf = &st->scan_tx[k]; /* scan_tx[k] == 0 == NOOP */ + st->scan_xfers[k].rx_buf = &st->vals[k - 1]; + st->scan_xfers[k].len = sizeof(*st->scan_tx); + spi_message_add_tail(&st->scan_xfers[k], &st->scan_msg); + + ret = spi_optimize_message(st->spi, &st->scan_msg); + if (ret) + return ret; + + ret = ad4691_enter_conversion_mode(st); + if (ret) { + spi_unoptimize_message(&st->scan_msg); + return ret; + } + + return 0; +} + +static int ad4691_manual_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ad4691_state *st = iio_priv(indio_dev); + int ret; + + ret = ad4691_exit_conversion_mode(st); + spi_unoptimize_message(&st->scan_msg); + return ret; +} + +static const struct iio_buffer_setup_ops ad4691_manual_buffer_setup_ops = { + .preenable = ad4691_manual_buffer_preenable, + .postdisable = ad4691_manual_buffer_postdisable, +}; + +static int ad4691_cnv_burst_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ad4691_state *st = iio_priv(indio_dev); + unsigned int acc_mask, std_seq_config; + unsigned int k, i; + int ret; + + memset(st->scan_xfers, 0, sizeof(st->scan_xfers)); + memset(st->scan_tx, 0, sizeof(st->scan_tx)); + + spi_message_init(&st->scan_msg); + + /* + * Each AVG_IN read needs two transfers: a 2-byte address write phase + * followed by a 2-byte data read phase. CS toggles between channels + * (cs_change=1 on the read phase of all but the last channel). + */ + k = 0; + iio_for_each_active_channel(indio_dev, i) { + put_unaligned_be16(0x8000 | AD4691_AVG_IN(i), &st->scan_tx[k]); + st->scan_xfers[2 * k].tx_buf = &st->scan_tx[k]; + st->scan_xfers[2 * k].len = sizeof(*st->scan_tx); + spi_message_add_tail(&st->scan_xfers[2 * k], &st->scan_msg); + st->scan_xfers[2 * k + 1].rx_buf = &st->vals[k]; + st->scan_xfers[2 * k + 1].len = sizeof(*st->scan_tx); + st->scan_xfers[2 * k + 1].cs_change = 1; + spi_message_add_tail(&st->scan_xfers[2 * k + 1], &st->scan_msg); + k++; + } + + /* + * Append a 4-byte state-reset transfer [addr_hi, addr_lo, + * STATE_RESET_ALL, OSC_EN=1]. CS is asserted throughout, so + * ADDR_DESCENDING writes byte[3]=1 to OSC_EN_REG (0x180) as a + * deliberate side-write, keeping the oscillator enabled. + * STATE_RESET_ALL starts the next burst; the hardware does not + * accumulate new conversions until after a STATE_RESET pulse, so + * no in-progress data is lost. No cs_change here — CS must + * deassert normally at end of message to frame the next command. + */ + put_unaligned_be16(AD4691_STATE_RESET_REG, st->scan_tx_reset); + st->scan_tx_reset[2] = AD4691_STATE_RESET_ALL; + st->scan_tx_reset[3] = 1; + st->scan_xfers[2 * k].tx_buf = st->scan_tx_reset; + st->scan_xfers[2 * k].len = sizeof(st->scan_tx_reset); + spi_message_add_tail(&st->scan_xfers[2 * k], &st->scan_msg); + + ret = spi_optimize_message(st->spi, &st->scan_msg); + if (ret) + return ret; + + std_seq_config = bitmap_read(indio_dev->active_scan_mask, 0, + iio_get_masklength(indio_dev)) & GENMASK(15, 0); + ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG, std_seq_config); + if (ret) + goto err_unoptimize; + + acc_mask = ~std_seq_config & GENMASK(15, 0); + ret = regmap_write(st->regmap, AD4691_ACC_MASK_REG, acc_mask); + if (ret) + goto err_unoptimize; + + ret = regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(0), st->osr); + if (ret) + goto err_unoptimize; + + ret = ad4691_enter_conversion_mode(st); + if (ret) + goto err_unoptimize; + + return 0; + +err_unoptimize: + spi_unoptimize_message(&st->scan_msg); + return ret; +} + +static int ad4691_cnv_burst_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad4691_state *st = iio_priv(indio_dev); + int ret; + + /* + * Start the PWM and unmask the IRQ here in postenable, not in + * preenable. The IIO core attaches the trigger poll function between + * preenable and postenable; enabling sampling or unmasking the IRQ + * before that point risks a DATA_READY assertion landing before the + * poll function is registered. iio_trigger_poll() would drop the + * event, disable_irq_nosync() would fire, and enable_irq() would + * never be called, leaving the IRQ permanently masked. + */ + ret = ad4691_sampling_enable(st, true); + if (ret) + return ret; + + enable_irq(st->irq); + st->irq_enabled = true; + return 0; +} + +static int ad4691_cnv_burst_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad4691_state *st = iio_priv(indio_dev); + + if (st->irq_enabled) { + disable_irq(st->irq); + st->irq_enabled = false; + } + return ad4691_sampling_enable(st, false); +} + +static int ad4691_cnv_burst_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ad4691_state *st = iio_priv(indio_dev); + int ret; + + ret = ad4691_exit_conversion_mode(st); + spi_unoptimize_message(&st->scan_msg); + return ret; +} + +static const struct iio_buffer_setup_ops ad4691_cnv_burst_buffer_setup_ops = { + .preenable = ad4691_cnv_burst_buffer_preenable, + .postenable = ad4691_cnv_burst_buffer_postenable, + .predisable = ad4691_cnv_burst_buffer_predisable, + .postdisable = ad4691_cnv_burst_buffer_postdisable, +}; + +static int ad4691_manual_offload_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad4691_state *st = iio_priv(indio_dev); + struct device *dev = regmap_get_device(st->regmap); + struct spi_device *spi = to_spi_device(dev); + struct spi_offload_trigger_config config = { + .type = SPI_OFFLOAD_TRIGGER_PERIODIC, + }; + unsigned int bpw = indio_dev->channels[0].scan_type.realbits; + unsigned int bit, k; + int ret; + + ret = ad4691_enter_conversion_mode(st); + if (ret) + return ret; + + memset(st->scan_xfers, 0, sizeof(st->scan_xfers)); + memset(st->scan_tx, 0, sizeof(st->scan_tx)); + + /* + * N+1 transfers for N channels. Each CS-low period triggers + * a conversion AND returns the previous result (pipelined). + * TX: [AD4691_ADC_CHAN(n), 0x00] + * RX: [data_hi, data_lo] (storagebits=16, shift=0) + * Transfer 0 RX is garbage; transfers 1..N carry real data. + * scan_tx is reused for TX commands (mutually exclusive with the + * non-offload triggered-buffer path). + * + * bits_per_word=bpw: the SPI controller reads tx_buf as a native + * 16-bit word and shifts it out MSB-first. Store the exact 16-bit + * value we want on the wire as a plain native u16 — no endianness + * macro — so the wire bytes are correct on both LE and BE hosts. + * The channel-select command is a single byte; shift it to the MSB + * position so SPI sends it first, with a zero pad in the LSB. + */ + k = 0; + iio_for_each_active_channel(indio_dev, bit) { + st->scan_tx[k] = AD4691_ADC_CHAN(bit) << 8; + st->scan_xfers[k].tx_buf = &st->scan_tx[k]; + st->scan_xfers[k].len = sizeof(*st->scan_tx); + st->scan_xfers[k].bits_per_word = bpw; + st->scan_xfers[k].cs_change = 1; + st->scan_xfers[k].cs_change_delay.value = AD4691_CNV_HIGH_TIME_NS; + st->scan_xfers[k].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS; + /* First transfer RX is garbage — skip it. */ + if (k > 0) + st->scan_xfers[k].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM; + k++; + } + + /* Final NOOP transfer retrieves the last channel's result. */ + st->scan_xfers[k].tx_buf = &st->scan_tx[k]; /* scan_tx[k] == 0 == NOOP */ + st->scan_xfers[k].len = sizeof(*st->scan_tx); + st->scan_xfers[k].bits_per_word = bpw; + st->scan_xfers[k].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM; + k++; + + spi_message_init_with_transfers(&st->scan_msg, st->scan_xfers, k); + st->scan_msg.offload = st->offload; + + ret = spi_optimize_message(spi, &st->scan_msg); + if (ret) + goto err_exit_conversion; + + config.periodic.frequency_hz = st->trigger_hz; + ret = spi_offload_trigger_enable(st->offload, st->offload_trigger, &config); + if (ret) + goto err_unoptimize; + + return 0; + +err_unoptimize: + spi_unoptimize_message(&st->scan_msg); +err_exit_conversion: + ad4691_exit_conversion_mode(st); + return ret; +} + +static int ad4691_manual_offload_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad4691_state *st = iio_priv(indio_dev); + + spi_offload_trigger_disable(st->offload, st->offload_trigger); + spi_unoptimize_message(&st->scan_msg); + + return ad4691_exit_conversion_mode(st); +} + +static const struct iio_buffer_setup_ops ad4691_manual_offload_buffer_setup_ops = { + .postenable = ad4691_manual_offload_buffer_postenable, + .predisable = ad4691_manual_offload_buffer_predisable, +}; + +static int ad4691_cnv_burst_offload_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad4691_state *st = iio_priv(indio_dev); + struct device *dev = regmap_get_device(st->regmap); + struct spi_device *spi = to_spi_device(dev); + struct spi_offload_trigger_config config = { + .type = SPI_OFFLOAD_TRIGGER_DATA_READY, + }; + unsigned int bpw = indio_dev->channels[0].scan_type.realbits; + unsigned int acc_mask, std_seq_config; + unsigned int bit, k; + int ret; + + std_seq_config = bitmap_read(indio_dev->active_scan_mask, 0, + iio_get_masklength(indio_dev)) & GENMASK(15, 0); + ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG, std_seq_config); + if (ret) + return ret; + + acc_mask = ~std_seq_config & GENMASK(15, 0); + ret = regmap_write(st->regmap, AD4691_ACC_MASK_REG, acc_mask); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(0), st->osr); + if (ret) + return ret; + + ret = ad4691_enter_conversion_mode(st); + if (ret) + return ret; + + memset(st->scan_xfers, 0, sizeof(st->scan_xfers)); + memset(st->scan_tx, 0, sizeof(st->scan_tx)); + + /* + * Each AVG_IN register read uses two transfers: + * TX: [reg_hi | 0x80, reg_lo] (address phase, CS stays asserted) + * RX: [data_hi, data_lo] (bpw-wide data phase, storagebits=16) + * Both TX and RX use bits_per_word=bpw: the SPI controller reads tx_buf + * as a native 16-bit word and shifts it out MSB-first. Store the exact + * 16-bit wire value as a plain native u16 — no endianness macro — so the + * wire bytes are correct on both LE and BE hosts. The read-address + * (0x8000 | reg) is already the 16-bit value we want on the wire. + * scan_tx is reused for TX addresses (mutually exclusive with the + * non-offload triggered-buffer path). + */ + k = 0; + iio_for_each_active_channel(indio_dev, bit) { + st->scan_tx[k] = 0x8000 | AD4691_AVG_IN(bit); + + /* TX: address phase, CS stays asserted into data phase */ + st->scan_xfers[2 * k].tx_buf = &st->scan_tx[k]; + st->scan_xfers[2 * k].len = sizeof(*st->scan_tx); + st->scan_xfers[2 * k].bits_per_word = bpw; + + /* RX: data phase, CS toggles after to delimit the next register op */ + st->scan_xfers[2 * k + 1].len = sizeof(*st->scan_tx); + st->scan_xfers[2 * k + 1].bits_per_word = bpw; + st->scan_xfers[2 * k + 1].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM; + st->scan_xfers[2 * k + 1].cs_change = 1; + k++; + } + + /* + * State reset: single 4-byte write [addr_hi, addr_lo, STATE_RESET_ALL, + * OSC_EN=1]. ADDR_DESCENDING writes byte[3]=1 to OSC_EN_REG (0x180) as + * a deliberate side-write, keeping the oscillator enabled. + * scan_tx_reset is shared with the non-offload path (len=4 here vs + * len=3 there) since the two paths are mutually exclusive at probe. + */ + put_unaligned_be16(AD4691_STATE_RESET_REG, st->scan_tx_reset); + st->scan_tx_reset[2] = AD4691_STATE_RESET_ALL; + st->scan_tx_reset[3] = 1; + st->scan_xfers[2 * k].tx_buf = st->scan_tx_reset; + st->scan_xfers[2 * k].len = sizeof(st->scan_tx_reset); + /* + * 4-byte u8 buffer assembled with put_unaligned_be16(); leave + * bits_per_word at the default (8) so bytes go out in memory order. + */ + + spi_message_init_with_transfers(&st->scan_msg, st->scan_xfers, 2 * k + 1); + st->scan_msg.offload = st->offload; + + ret = spi_optimize_message(spi, &st->scan_msg); + if (ret) + goto err_exit_conversion; + + ret = spi_offload_trigger_enable(st->offload, st->offload_trigger, &config); + if (ret) + goto err_unoptimize; + + ret = ad4691_sampling_enable(st, true); + if (ret) + goto err_disable_trigger; + + return 0; + +err_disable_trigger: + spi_offload_trigger_disable(st->offload, st->offload_trigger); +err_unoptimize: + spi_unoptimize_message(&st->scan_msg); +err_exit_conversion: + ad4691_exit_conversion_mode(st); + return ret; +} + +static int ad4691_cnv_burst_offload_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad4691_state *st = iio_priv(indio_dev); + + ad4691_sampling_enable(st, false); + spi_offload_trigger_disable(st->offload, st->offload_trigger); + spi_unoptimize_message(&st->scan_msg); + + return ad4691_exit_conversion_mode(st); +} + +static const struct iio_buffer_setup_ops ad4691_cnv_burst_offload_buffer_setup_ops = { + .postenable = ad4691_cnv_burst_offload_buffer_postenable, + .predisable = ad4691_cnv_burst_offload_buffer_predisable, +}; + +static ssize_t sampling_frequency_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad4691_state *st = iio_priv(indio_dev); + + if (st->manual_mode && st->offload) + return sysfs_emit(buf, "%llu\n", READ_ONCE(st->trigger_hz)); + + return sysfs_emit(buf, "%lu\n", NSEC_PER_SEC / st->cnv_period_ns); +} + +static ssize_t sampling_frequency_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad4691_state *st = iio_priv(indio_dev); + unsigned int freq; + int ret; + + ret = kstrtouint(buf, 10, &freq); + if (ret) + return ret; + + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + if (st->manual_mode && st->offload) { + struct spi_offload_trigger_config config = { + .type = SPI_OFFLOAD_TRIGGER_PERIODIC, + .periodic = { .frequency_hz = freq }, + }; + + ret = spi_offload_trigger_validate(st->offload_trigger, &config); + if (ret) + return ret; + + WRITE_ONCE(st->trigger_hz, config.periodic.frequency_hz); + return len; + } + + ret = ad4691_set_pwm_freq(st, freq); + if (ret) + return ret; + + return len; +} + +static IIO_DEVICE_ATTR_RW(sampling_frequency, 0); + +static const struct iio_dev_attr *ad4691_buffer_attrs[] = { + &iio_dev_attr_sampling_frequency, + NULL +}; + +static irqreturn_t ad4691_irq(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ad4691_state *st = iio_priv(indio_dev); + + /* + * Disable the IRQ before calling iio_trigger_poll(). The IRQ is + * re-enabled via the trigger .reenable callback, which the IIO core + * calls inside iio_trigger_notify_done() once use_count reaches zero. + * Re-enabling here (before notify_done) would race: a DATA_READY + * between enable_irq() and notify_done() calls iio_trigger_poll() + * while use_count > 0, dropping the event and permanently masking + * the IRQ. + */ + disable_irq_nosync(st->irq); + iio_trigger_poll(indio_dev->trig); + + return IRQ_HANDLED; +} + +static void ad4691_trigger_reenable(struct iio_trigger *trig) +{ + struct ad4691_state *st = iio_trigger_get_drvdata(trig); + + enable_irq(st->irq); +} + +static const struct iio_trigger_ops ad4691_trigger_ops = { + .reenable = ad4691_trigger_reenable, + .validate_device = iio_trigger_validate_own_device, +}; + +static void ad4691_read_scan(struct iio_dev *indio_dev, s64 ts) +{ + struct ad4691_state *st = iio_priv(indio_dev); + int ret; + + guard(mutex)(&st->lock); + + ret = spi_sync(st->spi, &st->scan_msg); + if (ret) { + dev_err_ratelimited(regmap_get_device(st->regmap), + "SPI scan failed: %d\n", ret); + return; + } + + /* + * rx_buf pointers in scan_xfers point directly into scan.vals, so no + * copy is needed. The scan_msg already includes a STATE_RESET at the + * end (appended in preenable), so no explicit reset is needed here. + */ + iio_push_to_buffers_with_ts(indio_dev, st->vals, sizeof(st->vals), ts); +} + +static irqreturn_t ad4691_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + + ad4691_read_scan(indio_dev, pf->timestamp); + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +/* + * CNV burst mode: only allow our own trigger (driven by DATA_READY IRQ). + * Manual mode: external triggers (e.g. iio-trig-hrtimer) must be allowed + * because manual mode has no DATA_READY IRQ to fire the internal trigger. + * iio_trigger_ops.validate_device = iio_trigger_validate_own_device is + * correct in both modes — it prevents other devices from hijacking our + * internal trigger; the distinction here is only for iio_info.validate_trigger. + */ +static const struct iio_info ad4691_cnv_burst_info = { + .read_raw = ad4691_read_raw, + .write_raw = ad4691_write_raw, + .read_avail = ad4691_read_avail, + .debugfs_reg_access = ad4691_reg_access, + .validate_trigger = iio_validate_own_trigger, +}; + +static const struct iio_info ad4691_manual_info = { + .read_raw = ad4691_read_raw, + .write_raw = ad4691_write_raw, + .read_avail = ad4691_read_avail, + .debugfs_reg_access = ad4691_reg_access, +}; + +static int ad4691_pwm_setup(struct ad4691_state *st) +{ + struct device *dev = regmap_get_device(st->regmap); + + st->conv_trigger = devm_pwm_get(dev, "cnv"); + if (IS_ERR(st->conv_trigger)) + return dev_err_probe(dev, PTR_ERR(st->conv_trigger), + "Failed to get CNV PWM\n"); + + return ad4691_set_pwm_freq(st, st->info->max_rate); +} + +static int ad4691_regulator_setup(struct ad4691_state *st) +{ + struct device *dev = regmap_get_device(st->regmap); + int ret; + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(ad4691_supplies), + ad4691_supplies); + if (ret) + return dev_err_probe(dev, ret, "Failed to get and enable supplies\n"); + + /* + * vdd-supply and ldo-in-supply are mutually exclusive: + * vdd-supply present → external 1.8V VDD; disable internal LDO. + * vdd-supply absent → enable internal LDO fed from ldo-in-supply. + * Having both simultaneously is strongly inadvisable per the datasheet. + */ + if (device_property_present(dev, "vdd-supply")) { + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get and enable VDD\n"); + } else if (device_property_present(dev, "ldo-in-supply")) { + ret = devm_regulator_get_enable(dev, "ldo-in"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get and enable LDO-IN\n"); + st->ldo_en = true; + } else { + return dev_err_probe(dev, -EINVAL, + "missing one of vdd-supply, ldo-in-supply\n"); + } + + if (device_property_present(dev, "ref-supply")) { + st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "ref"); + if (st->vref_uV < 0) + return dev_err_probe(dev, st->vref_uV, + "Failed to get REF supply voltage\n"); + } else if (device_property_present(dev, "refin-supply")) { + st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "refin"); + if (st->vref_uV < 0) + return dev_err_probe(dev, st->vref_uV, + "Failed to get REFIN supply voltage\n"); + st->refbuf_en = true; + } else { + return dev_err_probe(dev, -EINVAL, + "missing one of ref-supply, refin-supply\n"); + } + + if (st->vref_uV < AD4691_VREF_uV_MIN || st->vref_uV > AD4691_VREF_uV_MAX) + return dev_err_probe(dev, -EINVAL, + "vref(%d) must be in the range [%u...%u]\n", + st->vref_uV, AD4691_VREF_uV_MIN, + AD4691_VREF_uV_MAX); + + return 0; +} + +static int ad4691_reset(struct ad4691_state *st) +{ + struct device *dev = regmap_get_device(st->regmap); + struct reset_control *rst; + int ret; + + rst = devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(rst)) + return dev_err_probe(dev, PTR_ERR(rst), "Failed to get reset\n"); + + if (rst) { + /* + * Assert the reset line to guarantee a clean reset pulse on + * every probe, including driver reloads where the line may + * already be deasserted (reset_control_put() does not + * re-assert on release). tRESETL (minimum pulse width) = 10 ns + * (Table 5); kernel function-call overhead alone exceeds this, + * so no explicit delay is needed between assert and deassert. + */ + reset_control_assert(rst); + ret = reset_control_deassert(rst); + if (ret) + return ret; + } else { + /* No hardware reset available, fall back to software reset. */ + ret = regmap_write(st->regmap, AD4691_SPI_CONFIG_A_REG, + AD4691_SW_RESET); + if (ret) + return ret; + } + + /* + * Wait 300 µs (Table 5) for the device to complete its internal reset + * sequence before accepting SPI commands. + */ + fsleep(300); + return 0; +} + +static int ad4691_config(struct ad4691_state *st) +{ + struct device *dev = regmap_get_device(st->regmap); + enum ad4691_ref_ctrl ref_val; + unsigned int val; + int ret; + + /* + * Determine buffer conversion mode from DT: if a PWM is provided it + * drives the CNV pin (CNV_BURST_MODE); otherwise CNV is tied to CS + * and each SPI transfer triggers a conversion (MANUAL_MODE). + * Both modes idle in AUTONOMOUS mode so that read_raw can use the + * internal oscillator without disturbing the hardware configuration. + */ + if (device_property_present(dev, "pwms")) { + st->manual_mode = false; + ret = ad4691_pwm_setup(st); + if (ret) + return ret; + } else { + st->manual_mode = true; + } + + switch (st->vref_uV) { + case AD4691_VREF_uV_MIN ... AD4691_VREF_2P5_uV_MAX: + ref_val = AD4691_VREF_2P5; + break; + case AD4691_VREF_2P5_uV_MAX + 1 ... AD4691_VREF_3P0_uV_MAX: + ref_val = AD4691_VREF_3P0; + break; + case AD4691_VREF_3P0_uV_MAX + 1 ... AD4691_VREF_3P3_uV_MAX: + ref_val = AD4691_VREF_3P3; + break; + case AD4691_VREF_3P3_uV_MAX + 1 ... AD4691_VREF_4P096_uV_MAX: + ref_val = AD4691_VREF_4P096; + break; + case AD4691_VREF_4P096_uV_MAX + 1 ... AD4691_VREF_uV_MAX: + ref_val = AD4691_VREF_5P0; + break; + default: + return dev_err_probe(dev, -EINVAL, + "Unsupported vref voltage: %d uV\n", + st->vref_uV); + } + + val = FIELD_PREP(AD4691_REF_CTRL_MASK, ref_val); + if (st->refbuf_en) + val |= AD4691_REFBUF_EN; + + ret = regmap_write(st->regmap, AD4691_REF_CTRL, val); + if (ret) + return dev_err_probe(dev, ret, "Failed to write REF_CTRL\n"); + + ret = regmap_assign_bits(st->regmap, AD4691_DEVICE_SETUP, + AD4691_LDO_EN, st->ldo_en); + if (ret) + return dev_err_probe(dev, ret, "Failed to write DEVICE_SETUP\n"); + + /* + * Set the internal oscillator to the highest rate this chip supports. + * Index 0 (1 MHz) exceeds the 500 kHz max of AD4691/AD4693, so those + * chips start at index 1 (500 kHz). + */ + ret = regmap_write(st->regmap, AD4691_OSC_FREQ_REG, + ad4691_samp_freq_start(st->info)); + if (ret) + return dev_err_probe(dev, ret, "Failed to write OSC_FREQ\n"); + + st->target_osc_freq_Hz = ad4691_osc_freqs_Hz[ad4691_samp_freq_start(st->info)]; + + ret = regmap_update_bits(st->regmap, AD4691_ADC_SETUP, + AD4691_ADC_MODE_MASK, AD4691_AUTONOMOUS_MODE); + if (ret) + return dev_err_probe(dev, ret, "Failed to write ADC_SETUP\n"); + + ad4691_precompute_samp_freq_avail(st); + + return 0; +} + +static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev, + struct ad4691_state *st) +{ + struct device *dev = regmap_get_device(st->regmap); + struct iio_trigger *trig; + unsigned int i; + int irq, ret; + + /* + * Manual mode exposes channels without the oversampling_ratio attribute + * because ACC_DEPTH_IN is not configured in manual mode. + */ + if (st->manual_mode) + indio_dev->channels = st->info->sw_info->manual_channels; + else + indio_dev->channels = st->info->sw_info->channels; + indio_dev->num_channels = st->info->sw_info->num_channels; + indio_dev->info = st->manual_mode ? &ad4691_manual_info : &ad4691_cnv_burst_info; + + /* + * Manual mode relies on an external trigger (e.g. iio-trig-hrtimer); + * no internal trigger is needed or registered. + */ + if (st->manual_mode) + return devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + ad4691_trigger_handler, + &ad4691_manual_buffer_setup_ops); + + /* + * CNV burst mode: allocate an internal trigger driven by the + * DATA_READY IRQ on the GP pin. + */ + trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, + iio_device_id(indio_dev)); + if (!trig) + return -ENOMEM; + + trig->ops = &ad4691_trigger_ops; + iio_trigger_set_drvdata(trig, st); + + ret = devm_iio_trigger_register(dev, trig); + if (ret) + return dev_err_probe(dev, ret, "IIO trigger register failed\n"); + + indio_dev->trig = iio_trigger_get(trig); + + /* + * The GP pin named in interrupt-names asserts at end-of-conversion. + * The IRQ handler fires the IIO trigger so the trigger handler can + * read and push the sample to the buffer. The IRQ is kept disabled + * until the buffer is enabled. + */ + irq = -ENXIO; + for (i = 0; i < ARRAY_SIZE(ad4691_gp_names); i++) { + irq = fwnode_irq_get_byname(dev_fwnode(dev), + ad4691_gp_names[i]); + if (irq > 0 || irq == -EPROBE_DEFER) + break; + } + if (irq < 0) + return dev_err_probe(dev, irq, "failed to get GP interrupt\n"); + + st->irq = irq; + + ret = ad4691_gpio_setup(st, i); + if (ret) + return ret; + + /* + * The handler only calls disable_irq_nosync() and iio_trigger_poll(), + * both safe in hardirq context, so register as a hard IRQ handler. + * IRQF_NO_AUTOEN keeps it disabled until the buffer is enabled. + */ + ret = devm_request_irq(dev, irq, ad4691_irq, IRQF_NO_AUTOEN, + indio_dev->name, indio_dev); + if (ret) + return ret; + + return devm_iio_triggered_buffer_setup_ext(dev, indio_dev, + iio_pollfunc_store_time, + ad4691_trigger_handler, + IIO_BUFFER_DIRECTION_IN, + &ad4691_cnv_burst_buffer_setup_ops, + ad4691_buffer_attrs); +} + +static int ad4691_setup_offload(struct iio_dev *indio_dev, + struct ad4691_state *st, + struct spi_offload *spi_offload) +{ + struct device *dev = regmap_get_device(st->regmap); + struct dma_chan *rx_dma; + int ret; + + st->offload = spi_offload; + + /* + * CNV burst offload exposes oversampling_ratio (ACC_DEPTH_IN is + * configured per channel at buffer enable). Manual offload does not + * configure ACC_DEPTH_IN, so it uses a separate channel array + * without the oversampling_ratio attribute. Both paths use IIO_CPU + * (no .endianness annotation) because bits_per_word=16 causes the + * SPI Engine to produce native 16-bit DMA words. + */ + if (st->manual_mode) + indio_dev->channels = st->info->offload_info->manual_channels; + else + indio_dev->channels = st->info->offload_info->channels; + indio_dev->num_channels = st->info->offload_info->num_channels; + /* + * Offload path uses DMA directly; no IIO trigger is involved, so + * external triggers are not restricted (no validate_trigger). + */ + indio_dev->info = &ad4691_manual_info; + + if (st->manual_mode) { + st->offload_trigger = + devm_spi_offload_trigger_get(dev, st->offload, + SPI_OFFLOAD_TRIGGER_PERIODIC); + if (IS_ERR(st->offload_trigger)) + return dev_err_probe(dev, PTR_ERR(st->offload_trigger), + "Failed to get periodic offload trigger\n"); + + st->trigger_hz = AD4691_OFFLOAD_INITIAL_TRIGGER_HZ; + } else { + struct spi_offload_trigger_info trigger_info = { + .fwnode = dev_fwnode(dev), + .ops = &ad4691_offload_trigger_ops, + .priv = st, + }; + + ret = devm_spi_offload_trigger_register(dev, &trigger_info); + if (ret) + return dev_err_probe(dev, ret, + "Failed to register offload trigger\n"); + + st->offload_trigger = + devm_spi_offload_trigger_get(dev, st->offload, + SPI_OFFLOAD_TRIGGER_DATA_READY); + if (IS_ERR(st->offload_trigger)) + return dev_err_probe(dev, PTR_ERR(st->offload_trigger), + "Failed to get DATA_READY offload trigger\n"); + } + + rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev, st->offload); + if (IS_ERR(rx_dma)) + return dev_err_probe(dev, PTR_ERR(rx_dma), + "Failed to get offload RX DMA channel\n"); + + if (st->manual_mode) + indio_dev->setup_ops = &ad4691_manual_offload_buffer_setup_ops; + else + indio_dev->setup_ops = &ad4691_cnv_burst_offload_buffer_setup_ops; + + ret = devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, rx_dma, + IIO_BUFFER_DIRECTION_IN); + if (ret) + return ret; + + indio_dev->buffer->attrs = ad4691_buffer_attrs; + + return 0; +} + +static int ad4691_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct spi_offload *spi_offload; + struct iio_dev *indio_dev; + struct ad4691_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + st->info = spi_get_device_match_data(spi); + if (!st->info) + return -ENODEV; + st->osr = 1; + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + st->regmap = devm_regmap_init(dev, NULL, spi, &ad4691_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "Failed to initialize regmap\n"); + + ret = ad4691_regulator_setup(st); + if (ret) + return ret; + + ret = ad4691_reset(st); + if (ret) + return ret; + + ret = ad4691_config(st); + if (ret) + return ret; + + spi_offload = devm_spi_offload_get(dev, spi, &ad4691_offload_config); + ret = PTR_ERR_OR_ZERO(spi_offload); + if (ret == -ENODEV) + spi_offload = NULL; + else if (ret) + return dev_err_probe(dev, ret, "Failed to get SPI offload\n"); + + indio_dev->name = st->info->name; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (spi_offload) + ret = ad4691_setup_offload(indio_dev, st, spi_offload); + else + ret = ad4691_setup_triggered_buffer(indio_dev, st); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ad4691_of_match[] = { + { .compatible = "adi,ad4691", .data = &ad4691_chip_info }, + { .compatible = "adi,ad4692", .data = &ad4692_chip_info }, + { .compatible = "adi,ad4693", .data = &ad4693_chip_info }, + { .compatible = "adi,ad4694", .data = &ad4694_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad4691_of_match); + +static const struct spi_device_id ad4691_id[] = { + { .name = "ad4691", .driver_data = (kernel_ulong_t)&ad4691_chip_info }, + { .name = "ad4692", .driver_data = (kernel_ulong_t)&ad4692_chip_info }, + { .name = "ad4693", .driver_data = (kernel_ulong_t)&ad4693_chip_info }, + { .name = "ad4694", .driver_data = (kernel_ulong_t)&ad4694_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad4691_id); + +static struct spi_driver ad4691_driver = { + .driver = { + .name = "ad4691", + .of_match_table = ad4691_of_match, + }, + .probe = ad4691_probe, + .id_table = ad4691_id, +}; +module_spi_driver(ad4691_driver); + +MODULE_AUTHOR("Radu Sabau "); +MODULE_DESCRIPTION("Analog Devices AD4691 Family ADC Driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_DMA_BUFFER"); +MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER"); diff --git a/drivers/iio/adc/ad4695.c b/drivers/iio/adc/ad4695.c new file mode 100644 index 000000000..53642de73 --- /dev/null +++ b/drivers/iio/adc/ad4695.c @@ -0,0 +1,2019 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * SPI ADC driver for Analog Devices Inc. AD4695 and similar chips + * + * https://www.analog.com/en/products/ad4695.html + * https://www.analog.com/en/products/ad4696.html + * https://www.analog.com/en/products/ad4697.html + * https://www.analog.com/en/products/ad4698.html + * + * Copyright 2024 Analog Devices Inc. + * Copyright 2024 BayLibre, SAS + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* AD4695 registers */ +#define AD4695_REG_SPI_CONFIG_A 0x0000 +#define AD4695_REG_SPI_CONFIG_A_SW_RST (BIT(7) | BIT(0)) +#define AD4695_REG_SPI_CONFIG_A_ADDR_DIR BIT(5) +#define AD4695_REG_SPI_CONFIG_B 0x0001 +#define AD4695_REG_SPI_CONFIG_B_INST_MODE BIT(7) +#define AD4695_REG_DEVICE_TYPE 0x0003 +#define AD4695_REG_SCRATCH_PAD 0x000A +#define AD4695_REG_VENDOR_L 0x000C +#define AD4695_REG_VENDOR_H 0x000D +#define AD4695_REG_LOOP_MODE 0x000E +#define AD4695_REG_SPI_CONFIG_C 0x0010 +#define AD4695_REG_SPI_CONFIG_C_MB_STRICT BIT(7) +#define AD4695_REG_SPI_STATUS 0x0011 +#define AD4695_REG_STATUS 0x0014 +#define AD4695_REG_ALERT_STATUS1 0x0015 +#define AD4695_REG_ALERT_STATUS2 0x0016 +#define AD4695_REG_CLAMP_STATUS 0x001A +#define AD4695_REG_SETUP 0x0020 +#define AD4695_REG_SETUP_LDO_EN BIT(4) +#define AD4695_REG_SETUP_SPI_MODE BIT(2) +#define AD4695_REG_SETUP_SPI_CYC_CTRL BIT(1) +#define AD4695_REG_REF_CTRL 0x0021 +#define AD4695_REG_REF_CTRL_OV_MODE BIT(7) +#define AD4695_REG_REF_CTRL_VREF_SET GENMASK(4, 2) +#define AD4695_REG_REF_CTRL_REFHIZ_EN BIT(1) +#define AD4695_REG_REF_CTRL_REFBUF_EN BIT(0) +#define AD4695_REG_SEQ_CTRL 0x0022 +#define AD4695_REG_SEQ_CTRL_STD_SEQ_EN BIT(7) +#define AD4695_REG_SEQ_CTRL_NUM_SLOTS_AS GENMASK(6, 0) +#define AD4695_REG_AC_CTRL 0x0023 +#define AD4695_REG_STD_SEQ_CONFIG 0x0024 +#define AD4695_REG_GPIO_CTRL 0x0026 +#define AD4695_REG_GP_MODE 0x0027 +#define AD4695_REG_GP_MODE_BUSY_GP_SEL BIT(5) +#define AD4695_REG_GP_MODE_BUSY_GP_EN BIT(1) +#define AD4695_REG_TEMP_CTRL 0x0029 +#define AD4695_REG_TEMP_CTRL_TEMP_EN BIT(0) +#define AD4695_REG_CONFIG_IN(n) (0x0030 | (n)) +#define AD4695_REG_CONFIG_IN_MODE BIT(6) +#define AD4695_REG_CONFIG_IN_PAIR GENMASK(5, 4) +#define AD4695_REG_CONFIG_IN_AINHIGHZ_EN BIT(3) +#define AD4695_REG_CONFIG_IN_OSR_SET GENMASK(1, 0) +#define AD4695_REG_UPPER_IN(n) (0x0040 | (2 * (n))) +#define AD4695_REG_LOWER_IN(n) (0x0060 | (2 * (n))) +#define AD4695_REG_HYST_IN(n) (0x0080 | (2 * (n))) +#define AD4695_REG_OFFSET_IN(n) (0x00A0 | (2 * (n))) +#define AD4695_REG_GAIN_IN(n) (0x00C0 | (2 * (n))) +#define AD4695_REG_AS_SLOT(n) (0x0100 | (n)) +#define AD4695_REG_AS_SLOT_INX GENMASK(3, 0) + +/* Conversion mode commands */ +#define AD4695_CMD_EXIT_CNV_MODE 0x0A +#define AD4695_CMD_TEMP_CHAN 0x0F +#define AD4695_CMD_VOLTAGE_CHAN(n) (0x10 | (n)) + +/* timing specs */ +#define AD4695_T_CONVERT_NS 415 +#define AD4695_T_WAKEUP_HW_MS 3 +#define AD4695_T_WAKEUP_SW_MS 3 +#define AD4695_T_REFBUF_MS 100 +#define AD4695_T_REGCONFIG_NS 20 +#define AD4695_T_SCK_CNV_DELAY_NS 80 +#define AD4695_T_CNVL_NS 80 +#define AD4695_T_CNVH_NS 10 +#define AD4695_REG_ACCESS_SCLK_HZ (10 * MEGA) + +/* Max number of voltage input channels. */ +#define AD4695_MAX_VIN_CHANNELS 16 + +enum ad4695_in_pair { + AD4695_IN_PAIR_REFGND, + AD4695_IN_PAIR_COM, + AD4695_IN_PAIR_EVEN_ODD, +}; + +struct ad4695_chip_info { + const char *name; + int max_sample_rate; + u32 t_acq_ns; + u8 num_voltage_inputs; +}; + +struct ad4695_channel_config { + unsigned int channel; + bool highz_en; + bool bipolar; + enum ad4695_in_pair pin_pairing; + unsigned int common_mode_mv; + unsigned int oversampling_ratio; +}; + +struct ad4695_state { + struct spi_device *spi; + struct spi_offload *offload; + struct spi_offload_trigger *offload_trigger; + struct regmap *regmap; + struct regmap *regmap16; + struct gpio_desc *reset_gpio; + /* currently PWM CNV only supported with SPI offload use */ + struct pwm_device *cnv_pwm; + /* protects against concurrent use of cnv_pwm */ + struct mutex cnv_pwm_lock; + /* offload also requires separate gpio to manually control CNV */ + struct gpio_desc *cnv_gpio; + /* voltages channels plus temperature and timestamp */ + struct iio_chan_spec iio_chan[AD4695_MAX_VIN_CHANNELS + 2]; + struct ad4695_channel_config channels_cfg[AD4695_MAX_VIN_CHANNELS]; + const struct ad4695_chip_info *chip_info; + int sample_freq_range[3]; + /* Reference voltage. */ + unsigned int vref_mv; + /* Common mode input pin voltage. */ + unsigned int com_mv; + /* + * 2 per voltage and temperature chan plus 1 xfer to trigger 1st + * CNV. Excluding the trigger xfer, every 2nd xfer only serves + * to control CS and add a delay between the last SCLK and next + * CNV rising edges. + */ + struct spi_transfer buf_read_xfer[AD4695_MAX_VIN_CHANNELS * 2 + 3]; + struct spi_message buf_read_msg; + /* Raw conversion data received. */ + IIO_DECLARE_DMA_BUFFER_WITH_TS(u16, buf, AD4695_MAX_VIN_CHANNELS + 1); + u16 raw_data; + /* Commands to send for single conversion. */ + u16 cnv_cmd; + u8 cnv_cmd2; + /* Buffer for storing data from regmap bus reads/writes */ + u8 regmap_bus_data[4]; +}; + +static const struct regmap_range ad4695_regmap_rd_ranges[] = { + regmap_reg_range(AD4695_REG_SPI_CONFIG_A, AD4695_REG_SPI_CONFIG_B), + regmap_reg_range(AD4695_REG_DEVICE_TYPE, AD4695_REG_DEVICE_TYPE), + regmap_reg_range(AD4695_REG_SCRATCH_PAD, AD4695_REG_SCRATCH_PAD), + regmap_reg_range(AD4695_REG_VENDOR_L, AD4695_REG_LOOP_MODE), + regmap_reg_range(AD4695_REG_SPI_CONFIG_C, AD4695_REG_SPI_STATUS), + regmap_reg_range(AD4695_REG_STATUS, AD4695_REG_ALERT_STATUS2), + regmap_reg_range(AD4695_REG_CLAMP_STATUS, AD4695_REG_CLAMP_STATUS), + regmap_reg_range(AD4695_REG_SETUP, AD4695_REG_AC_CTRL), + regmap_reg_range(AD4695_REG_GPIO_CTRL, AD4695_REG_TEMP_CTRL), + regmap_reg_range(AD4695_REG_CONFIG_IN(0), AD4695_REG_CONFIG_IN(15)), + regmap_reg_range(AD4695_REG_AS_SLOT(0), AD4695_REG_AS_SLOT(127)), +}; + +static const struct regmap_access_table ad4695_regmap_rd_table = { + .yes_ranges = ad4695_regmap_rd_ranges, + .n_yes_ranges = ARRAY_SIZE(ad4695_regmap_rd_ranges), +}; + +static const struct regmap_range ad4695_regmap_wr_ranges[] = { + regmap_reg_range(AD4695_REG_SPI_CONFIG_A, AD4695_REG_SPI_CONFIG_B), + regmap_reg_range(AD4695_REG_SCRATCH_PAD, AD4695_REG_SCRATCH_PAD), + regmap_reg_range(AD4695_REG_LOOP_MODE, AD4695_REG_LOOP_MODE), + regmap_reg_range(AD4695_REG_SPI_CONFIG_C, AD4695_REG_SPI_STATUS), + regmap_reg_range(AD4695_REG_SETUP, AD4695_REG_AC_CTRL), + regmap_reg_range(AD4695_REG_GPIO_CTRL, AD4695_REG_TEMP_CTRL), + regmap_reg_range(AD4695_REG_CONFIG_IN(0), AD4695_REG_CONFIG_IN(15)), + regmap_reg_range(AD4695_REG_AS_SLOT(0), AD4695_REG_AS_SLOT(127)), +}; + +static const struct regmap_access_table ad4695_regmap_wr_table = { + .yes_ranges = ad4695_regmap_wr_ranges, + .n_yes_ranges = ARRAY_SIZE(ad4695_regmap_wr_ranges), +}; + +static const struct regmap_config ad4695_regmap_config = { + .name = "ad4695-8", + .reg_bits = 16, + .val_bits = 8, + .max_register = AD4695_REG_AS_SLOT(127), + .rd_table = &ad4695_regmap_rd_table, + .wr_table = &ad4695_regmap_wr_table, +}; + +static const struct regmap_range ad4695_regmap16_rd_ranges[] = { + regmap_reg_range(AD4695_REG_STD_SEQ_CONFIG, AD4695_REG_STD_SEQ_CONFIG), + regmap_reg_range(AD4695_REG_UPPER_IN(0), AD4695_REG_GAIN_IN(15)), +}; + +static const struct regmap_access_table ad4695_regmap16_rd_table = { + .yes_ranges = ad4695_regmap16_rd_ranges, + .n_yes_ranges = ARRAY_SIZE(ad4695_regmap16_rd_ranges), +}; + +static const struct regmap_range ad4695_regmap16_wr_ranges[] = { + regmap_reg_range(AD4695_REG_STD_SEQ_CONFIG, AD4695_REG_STD_SEQ_CONFIG), + regmap_reg_range(AD4695_REG_UPPER_IN(0), AD4695_REG_GAIN_IN(15)), +}; + +static const struct regmap_access_table ad4695_regmap16_wr_table = { + .yes_ranges = ad4695_regmap16_wr_ranges, + .n_yes_ranges = ARRAY_SIZE(ad4695_regmap16_wr_ranges), +}; + +static const struct regmap_config ad4695_regmap16_config = { + .name = "ad4695-16", + .reg_bits = 16, + .reg_stride = 2, + .val_bits = 16, + .val_format_endian = REGMAP_ENDIAN_LITTLE, + .max_register = AD4695_REG_GAIN_IN(15), + .rd_table = &ad4695_regmap16_rd_table, + .wr_table = &ad4695_regmap16_wr_table, +}; + +static int ad4695_regmap_bus_reg_write(void *context, const void *data, + size_t count) +{ + struct ad4695_state *st = context; + struct spi_transfer xfer = { + .speed_hz = AD4695_REG_ACCESS_SCLK_HZ, + .len = count, + .tx_buf = st->regmap_bus_data, + }; + + if (count > ARRAY_SIZE(st->regmap_bus_data)) + return -EINVAL; + + memcpy(st->regmap_bus_data, data, count); + + return spi_sync_transfer(st->spi, &xfer, 1); +} + +static int ad4695_regmap_bus_reg_read(void *context, const void *reg, + size_t reg_size, void *val, + size_t val_size) +{ + struct ad4695_state *st = context; + struct spi_transfer xfers[] = { + { + .speed_hz = AD4695_REG_ACCESS_SCLK_HZ, + .len = reg_size, + .tx_buf = &st->regmap_bus_data[0], + }, { + .speed_hz = AD4695_REG_ACCESS_SCLK_HZ, + .len = val_size, + .rx_buf = &st->regmap_bus_data[2], + }, + }; + int ret; + + if (reg_size > 2) + return -EINVAL; + + if (val_size > 2) + return -EINVAL; + + memcpy(&st->regmap_bus_data[0], reg, reg_size); + + ret = spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers)); + if (ret) + return ret; + + memcpy(val, &st->regmap_bus_data[2], val_size); + + return 0; +} + +static const struct regmap_bus ad4695_regmap_bus = { + .write = ad4695_regmap_bus_reg_write, + .read = ad4695_regmap_bus_reg_read, + .read_flag_mask = 0x80, + .reg_format_endian_default = REGMAP_ENDIAN_BIG, + .val_format_endian_default = REGMAP_ENDIAN_BIG, +}; + +enum { + AD4695_SCAN_TYPE_OSR_1, + AD4695_SCAN_TYPE_OSR_4, + AD4695_SCAN_TYPE_OSR_16, + AD4695_SCAN_TYPE_OSR_64, +}; + +static const struct iio_scan_type ad4695_scan_type_offload_u[] = { + [AD4695_SCAN_TYPE_OSR_1] = { + .sign = 'u', + .realbits = 16, + .shift = 3, + .storagebits = 32, + }, + [AD4695_SCAN_TYPE_OSR_4] = { + .sign = 'u', + .realbits = 17, + .shift = 2, + .storagebits = 32, + }, + [AD4695_SCAN_TYPE_OSR_16] = { + .sign = 'u', + .realbits = 18, + .shift = 1, + .storagebits = 32, + }, + [AD4695_SCAN_TYPE_OSR_64] = { + .sign = 'u', + .realbits = 19, + .storagebits = 32, + }, +}; + +static const struct iio_scan_type ad4695_scan_type_offload_s[] = { + [AD4695_SCAN_TYPE_OSR_1] = { + .sign = 's', + .realbits = 16, + .shift = 3, + .storagebits = 32, + }, + [AD4695_SCAN_TYPE_OSR_4] = { + .sign = 's', + .realbits = 17, + .shift = 2, + .storagebits = 32, + }, + [AD4695_SCAN_TYPE_OSR_16] = { + .sign = 's', + .realbits = 18, + .shift = 1, + .storagebits = 32, + }, + [AD4695_SCAN_TYPE_OSR_64] = { + .sign = 's', + .realbits = 19, + .storagebits = 32, + }, +}; + +static const struct iio_chan_spec ad4695_channel_template = { + .type = IIO_VOLTAGE, + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + }, +}; + +static const struct iio_chan_spec ad4695_temp_channel_template = { + .address = AD4695_CMD_TEMP_CHAN, + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + }, +}; + +static const struct iio_chan_spec ad4695_soft_timestamp_channel_template = + IIO_CHAN_SOFT_TIMESTAMP(0); + +static const char * const ad4695_power_supplies[] = { + "avdd", "vio" +}; + +static const int ad4695_oversampling_ratios[] = { + 1, 4, 16, 64, +}; + +static const struct ad4695_chip_info ad4695_chip_info = { + .name = "ad4695", + .max_sample_rate = 500 * KILO, + .t_acq_ns = 1715, + .num_voltage_inputs = 16, +}; + +static const struct ad4695_chip_info ad4696_chip_info = { + .name = "ad4696", + .max_sample_rate = 1 * MEGA, + .t_acq_ns = 715, + .num_voltage_inputs = 16, +}; + +static const struct ad4695_chip_info ad4697_chip_info = { + .name = "ad4697", + .max_sample_rate = 500 * KILO, + .t_acq_ns = 1715, + .num_voltage_inputs = 8, +}; + +static const struct ad4695_chip_info ad4698_chip_info = { + .name = "ad4698", + .max_sample_rate = 1 * MEGA, + .t_acq_ns = 715, + .num_voltage_inputs = 8, +}; + +static void ad4695_cnv_manual_trigger(struct ad4695_state *st) +{ + gpiod_set_value_cansleep(st->cnv_gpio, 1); + ndelay(10); + gpiod_set_value_cansleep(st->cnv_gpio, 0); +} + +/** + * ad4695_set_single_cycle_mode - Set the device in single cycle mode + * @st: The AD4695 state + * @channel: The first channel to read + * + * As per the datasheet, to enable single cycle mode, we need to set + * STD_SEQ_EN=0, NUM_SLOTS_AS=0 and CYC_CTRL=1 (Table 15). Setting SPI_MODE=1 + * triggers the first conversion using the channel in AS_SLOT0. + * + * Context: can sleep, must be called with iio_device_claim_direct held + * Return: 0 on success, a negative error code on failure + */ +static int ad4695_set_single_cycle_mode(struct ad4695_state *st, + unsigned int channel) +{ + int ret; + + ret = regmap_clear_bits(st->regmap, AD4695_REG_SEQ_CTRL, + AD4695_REG_SEQ_CTRL_STD_SEQ_EN | + AD4695_REG_SEQ_CTRL_NUM_SLOTS_AS); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD4695_REG_AS_SLOT(0), + FIELD_PREP(AD4695_REG_AS_SLOT_INX, channel)); + if (ret) + return ret; + + return regmap_set_bits(st->regmap, AD4695_REG_SETUP, + AD4695_REG_SETUP_SPI_MODE | + AD4695_REG_SETUP_SPI_CYC_CTRL); +} + +/** + * ad4695_enter_advanced_sequencer_mode - Put the ADC in advanced sequencer mode + * @st: The driver state + * @n: The number of slots to use - must be >= 2, <= 128 + * + * As per the datasheet, to enable advanced sequencer, we need to set + * STD_SEQ_EN=0, NUM_SLOTS_AS=n-1 and CYC_CTRL=0 (Table 15). Setting SPI_MODE=1 + * triggers the first conversion using the channel in AS_SLOT0. + * + * Return: 0 on success, a negative error code on failure + */ +static int ad4695_enter_advanced_sequencer_mode(struct ad4695_state *st, u32 n) +{ + int ret; + + ret = regmap_update_bits(st->regmap, AD4695_REG_SEQ_CTRL, + AD4695_REG_SEQ_CTRL_STD_SEQ_EN | + AD4695_REG_SEQ_CTRL_NUM_SLOTS_AS, + FIELD_PREP(AD4695_REG_SEQ_CTRL_STD_SEQ_EN, 0) | + FIELD_PREP(AD4695_REG_SEQ_CTRL_NUM_SLOTS_AS, n - 1)); + if (ret) + return ret; + + return regmap_update_bits(st->regmap, AD4695_REG_SETUP, + AD4695_REG_SETUP_SPI_MODE | AD4695_REG_SETUP_SPI_CYC_CTRL, + FIELD_PREP(AD4695_REG_SETUP_SPI_MODE, 1) | + FIELD_PREP(AD4695_REG_SETUP_SPI_CYC_CTRL, 0)); +} + +/** + * ad4695_exit_conversion_mode - Exit conversion mode + * @st: The AD4695 state + * + * Sends SPI command to exit conversion mode. + * + * Return: 0 on success, a negative error code on failure + */ +static int ad4695_exit_conversion_mode(struct ad4695_state *st) +{ + /* + * An extra transfer is needed to trigger a conversion here so + * that we can be 100% sure the command will be processed by the + * ADC, rather than relying on it to be in the correct state + * when this function is called (this chip has a quirk where the + * command only works when reading a conversion, and if the + * previous conversion was already read then it won't work). The + * actual conversion command is then run at the slower + * AD4695_REG_ACCESS_SCLK_HZ speed to guarantee this works. + */ + struct spi_transfer xfers[] = { + { + .delay.value = AD4695_T_CNVL_NS, + .delay.unit = SPI_DELAY_UNIT_NSECS, + .cs_change = 1, + .cs_change_delay.value = AD4695_T_CNVH_NS, + .cs_change_delay.unit = SPI_DELAY_UNIT_NSECS, + }, + { + .speed_hz = AD4695_REG_ACCESS_SCLK_HZ, + .tx_buf = &st->cnv_cmd2, + .len = 1, + .delay.value = AD4695_T_REGCONFIG_NS, + .delay.unit = SPI_DELAY_UNIT_NSECS, + }, + }; + + /* + * Technically, could do a 5-bit transfer, but shifting to start of + * 8 bits instead for better SPI controller support. + */ + st->cnv_cmd2 = AD4695_CMD_EXIT_CNV_MODE << 3; + + if (st->cnv_gpio) { + ad4695_cnv_manual_trigger(st); + + /* + * In this case, CNV is not connected to CS, so we don't need + * the extra CS toggle to trigger the conversion and toggling + * CS would have no effect. + */ + return spi_sync_transfer(st->spi, &xfers[1], 1); + } + + return spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers)); +} + +static int ad4695_set_ref_voltage(struct ad4695_state *st, int vref_mv) +{ + u8 val; + + if (vref_mv >= 2400 && vref_mv <= 2750) + val = 0; + else if (vref_mv > 2750 && vref_mv <= 3250) + val = 1; + else if (vref_mv > 3250 && vref_mv <= 3750) + val = 2; + else if (vref_mv > 3750 && vref_mv <= 4500) + val = 3; + else if (vref_mv > 4500 && vref_mv <= 5100) + val = 4; + else + return -EINVAL; + + return regmap_update_bits(st->regmap, AD4695_REG_REF_CTRL, + AD4695_REG_REF_CTRL_VREF_SET, + FIELD_PREP(AD4695_REG_REF_CTRL_VREF_SET, val)); +} + +/** + * ad4695_osr_to_regval - convert ratio to OSR register value + * @ratio: ratio to check + * + * Check if ratio is present in the list of available ratios and return + * the corresponding value that needs to be written to the register to + * select that ratio. + * + * Returns: register value (0 to 3) or -EINVAL if there is not an exact + * match + */ +static int ad4695_osr_to_regval(int ratio) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ad4695_oversampling_ratios); i++) { + if (ratio == ad4695_oversampling_ratios[i]) + return i; + } + + return -EINVAL; +} + +static int ad4695_write_chn_cfg(struct ad4695_state *st, + struct ad4695_channel_config *cfg) +{ + u32 mask, val; + + mask = AD4695_REG_CONFIG_IN_MODE; + val = FIELD_PREP(AD4695_REG_CONFIG_IN_MODE, cfg->bipolar ? 1 : 0); + + mask |= AD4695_REG_CONFIG_IN_PAIR; + val |= FIELD_PREP(AD4695_REG_CONFIG_IN_PAIR, cfg->pin_pairing); + + mask |= AD4695_REG_CONFIG_IN_AINHIGHZ_EN; + val |= FIELD_PREP(AD4695_REG_CONFIG_IN_AINHIGHZ_EN, + cfg->highz_en ? 1 : 0); + + return regmap_update_bits(st->regmap, + AD4695_REG_CONFIG_IN(cfg->channel), + mask, val); +} + +static int ad4695_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ad4695_state *st = iio_priv(indio_dev); + struct spi_transfer *xfer; + u8 temp_chan_bit = st->chip_info->num_voltage_inputs; + u32 bit, num_xfer, num_slots; + u32 temp_en = 0; + int ret, rx_buf_offset = 0; + + /* + * We are using the advanced sequencer since it is the only way to read + * multiple channels that allows individual configuration of each + * voltage input channel. Slot 0 in the advanced sequencer is used to + * account for the gap between trigger polls - we don't read data from + * this slot. Each enabled voltage channel is assigned a slot starting + * with slot 1. + */ + num_slots = 1; + + memset(st->buf_read_xfer, 0, sizeof(st->buf_read_xfer)); + + /* First xfer is only to trigger conversion of slot 1, so no rx. */ + xfer = &st->buf_read_xfer[0]; + xfer->cs_change = 1; + xfer->delay.value = st->chip_info->t_acq_ns; + xfer->delay.unit = SPI_DELAY_UNIT_NSECS; + xfer->cs_change_delay.value = AD4695_T_CONVERT_NS; + xfer->cs_change_delay.unit = SPI_DELAY_UNIT_NSECS; + num_xfer = 1; + + iio_for_each_active_channel(indio_dev, bit) { + xfer = &st->buf_read_xfer[num_xfer]; + xfer->bits_per_word = 16; + xfer->rx_buf = &st->buf[rx_buf_offset++]; + xfer->len = 2; + + if (bit == temp_chan_bit) { + temp_en = 1; + } else { + ret = regmap_write(st->regmap, + AD4695_REG_AS_SLOT(num_slots), + FIELD_PREP(AD4695_REG_AS_SLOT_INX, bit)); + if (ret) + return ret; + + num_slots++; + } + + num_xfer++; + + /* + * We need to add a blank xfer in data reads, to meet the timing + * requirement of a minimum delay between the last SCLK rising + * edge and the CS deassert. + */ + xfer = &st->buf_read_xfer[num_xfer]; + xfer->delay.value = AD4695_T_SCK_CNV_DELAY_NS; + xfer->delay.unit = SPI_DELAY_UNIT_NSECS; + xfer->cs_change = 1; + xfer->cs_change_delay.value = AD4695_T_CONVERT_NS; + xfer->cs_change_delay.unit = SPI_DELAY_UNIT_NSECS; + + num_xfer++; + } + + /* + * The advanced sequencer requires that at least 2 slots are enabled. + * Since slot 0 is always used for other purposes, we need only 1 + * enabled voltage channel to meet this requirement. If the temperature + * channel is the only enabled channel, we need to add one more slot in + * the sequence but not read from it. This is because the temperature + * sensor is sampled at the end of the channel sequence in advanced + * sequencer mode (see datasheet page 38). + * + * From the iio_for_each_active_channel() block above, we now have an + * xfer with data followed by a blank xfer to allow us to meet the + * timing spec, so move both of those up before adding an extra to + * handle the temperature-only case. + */ + if (num_slots < 2) { + /* Move last two xfers */ + st->buf_read_xfer[num_xfer] = st->buf_read_xfer[num_xfer - 1]; + st->buf_read_xfer[num_xfer - 1] = st->buf_read_xfer[num_xfer - 2]; + num_xfer++; + + /* Modify inserted xfer for extra slot. */ + xfer = &st->buf_read_xfer[num_xfer - 3]; + memset(xfer, 0, sizeof(*xfer)); + xfer->cs_change = 1; + xfer->delay.value = st->chip_info->t_acq_ns; + xfer->delay.unit = SPI_DELAY_UNIT_NSECS; + xfer->cs_change_delay.value = AD4695_T_CONVERT_NS; + xfer->cs_change_delay.unit = SPI_DELAY_UNIT_NSECS; + xfer++; + + /* and add the extra slot in the sequencer */ + ret = regmap_write(st->regmap, + AD4695_REG_AS_SLOT(num_slots), + FIELD_PREP(AD4695_REG_AS_SLOT_INX, 0)); + if (ret) + return ret; + + num_slots++; + + /* + * We still want to point at the last xfer when finished, so + * update the pointer. + */ + xfer = &st->buf_read_xfer[num_xfer - 1]; + } + + /* + * Don't keep CS asserted after last xfer. Also triggers conversion of + * slot 0. + */ + xfer->cs_change = 0; + + /* + * Temperature channel isn't included in the sequence, but rather + * controlled by setting a bit in the TEMP_CTRL register. + */ + + ret = regmap_update_bits(st->regmap, AD4695_REG_TEMP_CTRL, + AD4695_REG_TEMP_CTRL_TEMP_EN, + FIELD_PREP(AD4695_REG_TEMP_CTRL_TEMP_EN, temp_en)); + if (ret) + return ret; + + spi_message_init_with_transfers(&st->buf_read_msg, st->buf_read_xfer, + num_xfer); + + ret = spi_optimize_message(st->spi, &st->buf_read_msg); + if (ret) + return ret; + + /* This triggers conversion of slot 0. */ + ret = ad4695_enter_advanced_sequencer_mode(st, num_slots); + if (ret) + spi_unoptimize_message(&st->buf_read_msg); + + return ret; +} + +static int ad4695_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ad4695_state *st = iio_priv(indio_dev); + int ret; + + ret = ad4695_exit_conversion_mode(st); + if (ret) + return ret; + + spi_unoptimize_message(&st->buf_read_msg); + + return 0; +} + +static const struct iio_buffer_setup_ops ad4695_buffer_setup_ops = { + .preenable = ad4695_buffer_preenable, + .postdisable = ad4695_buffer_postdisable, +}; + +static irqreturn_t ad4695_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad4695_state *st = iio_priv(indio_dev); + int ret; + + ret = spi_sync(st->spi, &st->buf_read_msg); + if (ret) + goto out; + + iio_push_to_buffers_with_ts(indio_dev, st->buf, sizeof(st->buf), + pf->timestamp); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ad4695_offload_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad4695_state *st = iio_priv(indio_dev); + struct spi_offload_trigger_config config = { + .type = SPI_OFFLOAD_TRIGGER_DATA_READY, + }; + struct spi_transfer *xfer = &st->buf_read_xfer[0]; + struct pwm_state state; + u8 temp_chan_bit = st->chip_info->num_voltage_inputs; + u8 num_slots = 0; + u8 temp_en = 0; + unsigned int bit; + int ret; + + iio_for_each_active_channel(indio_dev, bit) { + if (bit == temp_chan_bit) { + temp_en = 1; + continue; + } + + ret = regmap_write(st->regmap, AD4695_REG_AS_SLOT(num_slots), + FIELD_PREP(AD4695_REG_AS_SLOT_INX, bit)); + if (ret) + return ret; + + num_slots++; + } + + /* + * For non-offload, we could discard data to work around this + * restriction, but with offload, that is not possible. + */ + if (num_slots < 2) { + dev_err(&st->spi->dev, + "At least two voltage channels must be enabled.\n"); + return -EINVAL; + } + + ret = regmap_update_bits(st->regmap, AD4695_REG_TEMP_CTRL, + AD4695_REG_TEMP_CTRL_TEMP_EN, + FIELD_PREP(AD4695_REG_TEMP_CTRL_TEMP_EN, + temp_en)); + if (ret) + return ret; + + /* Each BUSY event means just one sample for one channel is ready. */ + memset(xfer, 0, sizeof(*xfer)); + xfer->offload_flags = SPI_OFFLOAD_XFER_RX_STREAM; + /* Using 19 bits per word to allow for possible oversampling */ + xfer->bits_per_word = 19; + xfer->len = 4; + + spi_message_init_with_transfers(&st->buf_read_msg, xfer, 1); + st->buf_read_msg.offload = st->offload; + + ret = spi_optimize_message(st->spi, &st->buf_read_msg); + if (ret) + return ret; + + /* + * NB: technically, this is part the SPI offload trigger enable, but it + * doesn't work to call it from the offload trigger enable callback + * because it requires accessing the SPI bus. Calling it from the + * trigger enable callback could cause a deadlock. + */ + ret = regmap_set_bits(st->regmap, AD4695_REG_GP_MODE, + AD4695_REG_GP_MODE_BUSY_GP_EN); + if (ret) + goto err_unoptimize_message; + + ret = ad4695_enter_advanced_sequencer_mode(st, num_slots); + if (ret) + goto err_disable_busy_output; + + ret = spi_offload_trigger_enable(st->offload, st->offload_trigger, + &config); + if (ret) + goto err_exit_conversion_mode; + + mutex_lock(&st->cnv_pwm_lock); + pwm_get_state(st->cnv_pwm, &state); + /* + * PWM subsystem generally rounds down, so requesting 2x minimum high + * time ensures that we meet the minimum high time in any case. + */ + state.duty_cycle = AD4695_T_CNVH_NS * 2; + ret = pwm_apply_might_sleep(st->cnv_pwm, &state); + mutex_unlock(&st->cnv_pwm_lock); + if (ret) + goto err_offload_trigger_disable; + + return 0; + +err_offload_trigger_disable: + spi_offload_trigger_disable(st->offload, st->offload_trigger); + +err_exit_conversion_mode: + ad4695_exit_conversion_mode(st); + +err_disable_busy_output: + regmap_clear_bits(st->regmap, AD4695_REG_GP_MODE, + AD4695_REG_GP_MODE_BUSY_GP_EN); + +err_unoptimize_message: + spi_unoptimize_message(&st->buf_read_msg); + + return ret; +} + +static int ad4695_offload_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad4695_state *st = iio_priv(indio_dev); + struct pwm_state state; + int ret; + + scoped_guard(mutex, &st->cnv_pwm_lock) { + pwm_get_state(st->cnv_pwm, &state); + state.duty_cycle = 0; + ret = pwm_apply_might_sleep(st->cnv_pwm, &state); + if (ret) + return ret; + } + + spi_offload_trigger_disable(st->offload, st->offload_trigger); + + /* + * ad4695_exit_conversion_mode() triggers a conversion, so it has to be + * done after spi_offload_trigger_disable(). + */ + ret = ad4695_exit_conversion_mode(st); + if (ret) + return ret; + + ret = regmap_clear_bits(st->regmap, AD4695_REG_GP_MODE, + AD4695_REG_GP_MODE_BUSY_GP_EN); + if (ret) + return ret; + + spi_unoptimize_message(&st->buf_read_msg); + + return 0; +} + +static const struct iio_buffer_setup_ops ad4695_offload_buffer_setup_ops = { + .postenable = ad4695_offload_buffer_postenable, + .predisable = ad4695_offload_buffer_predisable, +}; + +/** + * ad4695_read_one_sample - Read a single sample using single-cycle mode + * @st: The AD4695 state + * @address: The address of the channel to read + * + * Upon successful return, the sample will be stored in `st->raw_data`. + * + * Context: can sleep, must be called with iio_device_claim_direct held + * Return: 0 on success, a negative error code on failure + */ +static int ad4695_read_one_sample(struct ad4695_state *st, unsigned int address) +{ + struct spi_transfer xfers[2] = { + { + .speed_hz = AD4695_REG_ACCESS_SCLK_HZ, + .bits_per_word = 16, + .tx_buf = &st->cnv_cmd, + .len = 2, + }, + { + /* Required delay between last SCLK and CNV/CS */ + .delay.value = AD4695_T_SCK_CNV_DELAY_NS, + .delay.unit = SPI_DELAY_UNIT_NSECS, + } + }; + int ret; + + ret = ad4695_set_single_cycle_mode(st, address); + if (ret) + return ret; + + /* + * If CNV is connected to CS, the previous function will have triggered + * the conversion, otherwise, we do it manually. + */ + if (st->cnv_gpio) + ad4695_cnv_manual_trigger(st); + + /* + * Setting the first channel to the temperature channel isn't supported + * in single-cycle mode, so we have to do an extra conversion to read + * the temperature. + */ + if (address == AD4695_CMD_TEMP_CHAN) { + st->cnv_cmd = AD4695_CMD_TEMP_CHAN << 11; + + ret = spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers)); + if (ret) + return ret; + + /* + * If CNV is connected to CS, the previous function will have + * triggered the conversion, otherwise, we do it manually. + */ + if (st->cnv_gpio) + ad4695_cnv_manual_trigger(st); + } + + /* Then read the result and exit conversion mode. */ + st->cnv_cmd = AD4695_CMD_EXIT_CNV_MODE << 11; + xfers[0].rx_buf = &st->raw_data; + + return spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers)); +} + +static int __ad4695_read_info_raw(struct ad4695_state *st, + struct iio_chan_spec const *chan, + int *val) +{ + u8 realbits = chan->scan_type.realbits; + int ret; + + ret = ad4695_read_one_sample(st, chan->address); + if (ret) + return ret; + + if (chan->scan_type.sign == 's') + *val = sign_extend32(st->raw_data, realbits - 1); + else + *val = st->raw_data; + + return IIO_VAL_INT; +} + +static int ad4695_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ad4695_state *st = iio_priv(indio_dev); + const struct iio_scan_type *scan_type; + struct ad4695_channel_config *cfg; + unsigned int reg_val; + int ret, tmp; + u8 realbits; + + if (chan->type == IIO_VOLTAGE) + cfg = &st->channels_cfg[chan->scan_index]; + + scan_type = iio_get_current_scan_type(indio_dev, chan); + if (IS_ERR(scan_type)) + return PTR_ERR(scan_type); + + realbits = scan_type->realbits; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = __ad4695_read_info_raw(st, chan, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + *val = st->vref_mv; + *val2 = realbits; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_TEMP: + /* T_scale (°C) = raw * V_REF (mV) / (-1.8 mV/°C * 2^16) */ + *val = st->vref_mv * -556; + *val2 = 16; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_VOLTAGE: + if (cfg->pin_pairing == AD4695_IN_PAIR_COM) + *val = st->com_mv * (1 << realbits) / st->vref_mv; + else if (cfg->pin_pairing == AD4695_IN_PAIR_EVEN_ODD) + *val = cfg->common_mode_mv * (1 << realbits) / st->vref_mv; + else + *val = 0; + + return IIO_VAL_INT; + case IIO_TEMP: + /* T_offset (°C) = -725 mV / (-1.8 mV/°C) */ + /* T_offset (raw) = T_offset (°C) * (-1.8 mV/°C) * 2^16 / V_REF (mV) */ + *val = -47513600; + *val2 = st->vref_mv; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBSCALE: + switch (chan->type) { + case IIO_VOLTAGE: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = regmap_read(st->regmap16, + AD4695_REG_GAIN_IN(chan->scan_index), + ®_val); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + *val = reg_val; + *val2 = 15; + + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) + case IIO_VOLTAGE: { + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = regmap_read(st->regmap16, + AD4695_REG_OFFSET_IN(chan->scan_index), + ®_val); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + + tmp = sign_extend32(reg_val, 15); + + switch (cfg->oversampling_ratio) { + case 1: + *val = tmp / 4; + *val2 = abs(tmp) % 4 * MICRO / 4; + break; + case 4: + *val = tmp / 2; + *val2 = abs(tmp) % 2 * MICRO / 2; + break; + case 16: + *val = tmp; + *val2 = 0; + break; + case 64: + *val = tmp * 2; + *val2 = 0; + break; + default: + return -EINVAL; + } + + if (tmp < 0 && *val2) { + *val *= -1; + *val2 *= -1; + } + + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_VOLTAGE: + *val = cfg->oversampling_ratio; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: { + struct pwm_state state; + unsigned int osr = 1; + + if (chan->type == IIO_VOLTAGE) + osr = cfg->oversampling_ratio; + + ret = pwm_get_state_hw(st->cnv_pwm, &state); + if (ret) + return ret; + + /* + * The effective sampling frequency for a channel is the input + * frequency divided by the channel's OSR value. + */ + *val = DIV_ROUND_UP_ULL(NSEC_PER_SEC, state.period * osr); + + return IIO_VAL_INT; + } + default: + return -EINVAL; + } +} + +static int ad4695_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT; + default: + return IIO_VAL_INT_PLUS_MICRO; + } +} + +static int ad4695_set_osr_val(struct ad4695_state *st, + struct iio_chan_spec const *chan, + int val) +{ + int osr = ad4695_osr_to_regval(val); + + if (osr < 0) + return osr; + + switch (chan->type) { + case IIO_VOLTAGE: + st->channels_cfg[chan->scan_index].oversampling_ratio = val; + return regmap_update_bits(st->regmap, + AD4695_REG_CONFIG_IN(chan->scan_index), + AD4695_REG_CONFIG_IN_OSR_SET, + FIELD_PREP(AD4695_REG_CONFIG_IN_OSR_SET, osr)); + default: + return -EINVAL; + } +} + +static unsigned int ad4695_get_calibbias(int val, int val2, int osr) +{ + int val_calc, scale; + + switch (osr) { + case 4: + scale = 4; + break; + case 16: + scale = 2; + break; + case 64: + scale = 1; + break; + default: + scale = 8; + break; + } + + val = clamp_t(int, val, S32_MIN / 8, S32_MAX / 8); + + /* val2 range is (-MICRO, MICRO) if val == 0, otherwise [0, MICRO) */ + if (val < 0) + val_calc = val * scale - val2 * scale / MICRO; + else if (val2 < 0) + /* if val2 < 0 then val == 0 */ + val_calc = val2 * scale / (int)MICRO; + else + val_calc = val * scale + val2 * scale / MICRO; + + val_calc /= 2; + + return clamp_t(int, val_calc, S16_MIN, S16_MAX); +} + +static int __ad4695_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ad4695_state *st = iio_priv(indio_dev); + unsigned int reg_val; + unsigned int osr = 1; + + if (chan->type == IIO_VOLTAGE) + osr = st->channels_cfg[chan->scan_index].oversampling_ratio; + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + switch (chan->type) { + case IIO_VOLTAGE: + if (val < 0 || val2 < 0) + reg_val = 0; + else if (val > 1) + reg_val = U16_MAX; + else + reg_val = (val * (1 << 16) + + mul_u64_u32_div(val2, 1 << 16, + MICRO)) / 2; + + return regmap_write(st->regmap16, + AD4695_REG_GAIN_IN(chan->scan_index), + reg_val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_VOLTAGE: + reg_val = ad4695_get_calibbias(val, val2, osr); + return regmap_write(st->regmap16, + AD4695_REG_OFFSET_IN(chan->scan_index), + reg_val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: { + struct pwm_state state; + /* + * Limit the maximum acceptable sample rate according to + * the channel's oversampling ratio. + */ + u64 max_osr_rate = DIV_ROUND_UP_ULL(st->chip_info->max_sample_rate, + osr); + + if (val <= 0 || val > max_osr_rate) + return -EINVAL; + + guard(mutex)(&st->cnv_pwm_lock); + pwm_get_state(st->cnv_pwm, &state); + /* + * The required sample frequency for a given OSR is the + * input frequency multiplied by it. + */ + state.period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, val * osr); + return pwm_apply_might_sleep(st->cnv_pwm, &state); + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return ad4695_set_osr_val(st, chan, val); + default: + return -EINVAL; + } +} + +static int ad4695_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = __ad4695_write_raw(indio_dev, chan, val, val2, mask); + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ad4695_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + int ret; + static const int ad4695_calibscale_available[6] = { + /* Range of 0 (inclusive) to 2 (exclusive) */ + 0, 15, 1, 15, U16_MAX, 15 + }; + static const int ad4695_calibbias_available[4][6] = { + /* + * Datasheet says FSR/8 which translates to signed/4. The step + * depends on oversampling ratio, so we need four different + * ranges to select from. + */ + { + S16_MIN / 4, 0, + 0, MICRO / 4, + S16_MAX / 4, S16_MAX % 4 * MICRO / 4 + }, + { + S16_MIN / 2, 0, + 0, MICRO / 2, + S16_MAX / 2, S16_MAX % 2 * MICRO / 2, + }, + { + S16_MIN, 0, + 1, 0, + S16_MAX, 0, + }, + { + S16_MIN * 2, 0, + 2, 0, + S16_MAX * 2, 0, + }, + }; + struct ad4695_state *st = iio_priv(indio_dev); + unsigned int osr = 1; + + if (chan->type == IIO_VOLTAGE) + osr = st->channels_cfg[chan->scan_index].oversampling_ratio; + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + switch (chan->type) { + case IIO_VOLTAGE: + *vals = ad4695_calibscale_available; + *type = IIO_VAL_FRACTIONAL_LOG2; + return IIO_AVAIL_RANGE; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_VOLTAGE: + ret = ad4695_osr_to_regval(osr); + if (ret < 0) + return ret; + /* + * Select the appropriate calibbias array based on the + * OSR value in the register. + */ + *vals = ad4695_calibbias_available[ret]; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_RANGE; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + /* Max sample rate for the channel depends on OSR */ + st->sample_freq_range[2] = + DIV_ROUND_UP_ULL(st->chip_info->max_sample_rate, osr); + *vals = st->sample_freq_range; + *type = IIO_VAL_INT; + return IIO_AVAIL_RANGE; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_VOLTAGE: + *vals = ad4695_oversampling_ratios; + *length = ARRAY_SIZE(ad4695_oversampling_ratios); + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ad4695_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct ad4695_state *st = iio_priv(indio_dev); + int ret = -EINVAL; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + if (readval) { + if (regmap_check_range_table(st->regmap, reg, + &ad4695_regmap_rd_table)) + ret = regmap_read(st->regmap, reg, readval); + if (regmap_check_range_table(st->regmap16, reg, + &ad4695_regmap16_rd_table)) + ret = regmap_read(st->regmap16, reg, readval); + } else { + if (regmap_check_range_table(st->regmap, reg, + &ad4695_regmap_wr_table)) + ret = regmap_write(st->regmap, reg, writeval); + if (regmap_check_range_table(st->regmap16, reg, + &ad4695_regmap16_wr_table)) + ret = regmap_write(st->regmap16, reg, writeval); + } + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ad4695_get_current_scan_type(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad4695_state *st = iio_priv(indio_dev); + unsigned int osr = st->channels_cfg[chan->scan_index].oversampling_ratio; + + switch (osr) { + case 1: + return AD4695_SCAN_TYPE_OSR_1; + case 4: + return AD4695_SCAN_TYPE_OSR_4; + case 16: + return AD4695_SCAN_TYPE_OSR_16; + case 64: + return AD4695_SCAN_TYPE_OSR_64; + default: + return -EINVAL; + } +} + +static const struct iio_info ad4695_info = { + .read_raw = &ad4695_read_raw, + .write_raw_get_fmt = &ad4695_write_raw_get_fmt, + .write_raw = &ad4695_write_raw, + .read_avail = &ad4695_read_avail, + .debugfs_reg_access = &ad4695_debugfs_reg_access, +}; + +static const struct iio_info ad4695_offload_info = { + .read_raw = &ad4695_read_raw, + .write_raw_get_fmt = &ad4695_write_raw_get_fmt, + .write_raw = &ad4695_write_raw, + .get_current_scan_type = &ad4695_get_current_scan_type, + .read_avail = &ad4695_read_avail, + .debugfs_reg_access = &ad4695_debugfs_reg_access, +}; + +static int ad4695_parse_channel_cfg(struct ad4695_state *st) +{ + struct device *dev = &st->spi->dev; + struct ad4695_channel_config *chan_cfg; + struct iio_chan_spec *iio_chan; + int ret, i; + + /* populate defaults */ + for (i = 0; i < st->chip_info->num_voltage_inputs; i++) { + chan_cfg = &st->channels_cfg[i]; + iio_chan = &st->iio_chan[i]; + + chan_cfg->highz_en = true; + chan_cfg->channel = i; + + /* This is the default OSR after reset */ + chan_cfg->oversampling_ratio = 1; + + *iio_chan = ad4695_channel_template; + iio_chan->channel = i; + iio_chan->scan_index = i; + iio_chan->address = AD4695_CMD_VOLTAGE_CHAN(i); + } + + /* modify based on firmware description */ + device_for_each_child_node_scoped(dev, child) { + u32 reg, val; + + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret) + return dev_err_probe(dev, ret, + "failed to read reg property (%s)\n", + fwnode_get_name(child)); + + if (reg >= st->chip_info->num_voltage_inputs) + return dev_err_probe(dev, -EINVAL, + "reg out of range (%s)\n", + fwnode_get_name(child)); + + iio_chan = &st->iio_chan[reg]; + chan_cfg = &st->channels_cfg[reg]; + + chan_cfg->highz_en = + !fwnode_property_read_bool(child, "adi,no-high-z"); + chan_cfg->bipolar = fwnode_property_read_bool(child, "bipolar"); + + ret = fwnode_property_read_u32(child, "common-mode-channel", + &val); + if (ret && ret != -EINVAL) + return dev_err_probe(dev, ret, + "failed to read common-mode-channel (%s)\n", + fwnode_get_name(child)); + + if (ret == -EINVAL || val == AD4695_COMMON_MODE_REFGND) + chan_cfg->pin_pairing = AD4695_IN_PAIR_REFGND; + else if (val == AD4695_COMMON_MODE_COM) + chan_cfg->pin_pairing = AD4695_IN_PAIR_COM; + else + chan_cfg->pin_pairing = AD4695_IN_PAIR_EVEN_ODD; + + if (chan_cfg->pin_pairing == AD4695_IN_PAIR_EVEN_ODD && + val % 2 == 0) + return dev_err_probe(dev, -EINVAL, + "common-mode-channel must be odd number (%s)\n", + fwnode_get_name(child)); + + if (chan_cfg->pin_pairing == AD4695_IN_PAIR_EVEN_ODD && + val != reg + 1) + return dev_err_probe(dev, -EINVAL, + "common-mode-channel must be next consecutive channel (%s)\n", + fwnode_get_name(child)); + + if (chan_cfg->pin_pairing == AD4695_IN_PAIR_EVEN_ODD) { + char name[5]; + + snprintf(name, sizeof(name), "in%d", reg + 1); + + ret = devm_regulator_get_enable_read_voltage(dev, name); + if (ret < 0) + return dev_err_probe(dev, ret, + "failed to get %s voltage (%s)\n", + name, fwnode_get_name(child)); + + chan_cfg->common_mode_mv = ret / 1000; + } + + if (chan_cfg->bipolar && + chan_cfg->pin_pairing == AD4695_IN_PAIR_REFGND) + return dev_err_probe(dev, -EINVAL, + "bipolar mode is not available for inputs paired with REFGND (%s).\n", + fwnode_get_name(child)); + + if (chan_cfg->bipolar) + iio_chan->scan_type.sign = 's'; + + ret = ad4695_write_chn_cfg(st, chan_cfg); + if (ret) + return ret; + } + + /* Temperature channel must be next scan index after voltage channels. */ + st->iio_chan[i] = ad4695_temp_channel_template; + st->iio_chan[i].scan_index = i; + i++; + + st->iio_chan[i] = ad4695_soft_timestamp_channel_template; + st->iio_chan[i].scan_index = i; + + return 0; +} + +static bool ad4695_offload_trigger_match(struct spi_offload_trigger *trigger, + enum spi_offload_trigger_type type, + u64 *args, u32 nargs) +{ + if (type != SPI_OFFLOAD_TRIGGER_DATA_READY) + return false; + + /* + * Requires 2 args: + * args[0] is the trigger event. + * args[1] is the GPIO pin number. + */ + if (nargs != 2 || args[0] != AD4695_TRIGGER_EVENT_BUSY) + return false; + + return true; +} + +static int ad4695_offload_trigger_request(struct spi_offload_trigger *trigger, + enum spi_offload_trigger_type type, + u64 *args, u32 nargs) +{ + struct ad4695_state *st = spi_offload_trigger_get_priv(trigger); + + /* Should already be validated by match, but just in case. */ + if (nargs != 2) + return -EINVAL; + + /* DT tells us if BUSY event uses GP0 or GP3. */ + if (args[1] == AD4695_TRIGGER_PIN_GP3) + return regmap_set_bits(st->regmap, AD4695_REG_GP_MODE, + AD4695_REG_GP_MODE_BUSY_GP_SEL); + + return regmap_clear_bits(st->regmap, AD4695_REG_GP_MODE, + AD4695_REG_GP_MODE_BUSY_GP_SEL); +} + +static int +ad4695_offload_trigger_validate(struct spi_offload_trigger *trigger, + struct spi_offload_trigger_config *config) +{ + if (config->type != SPI_OFFLOAD_TRIGGER_DATA_READY) + return -EINVAL; + + return 0; +} + +/* + * NB: There are no enable/disable callbacks here due to requiring a SPI + * message to enable or disable the BUSY output on the ADC. + */ +static const struct spi_offload_trigger_ops ad4695_offload_trigger_ops = { + .match = ad4695_offload_trigger_match, + .request = ad4695_offload_trigger_request, + .validate = ad4695_offload_trigger_validate, +}; + +static void ad4695_pwm_disable(void *pwm) +{ + pwm_disable(pwm); +} + +static int ad4695_probe_spi_offload(struct iio_dev *indio_dev, + struct ad4695_state *st) +{ + struct device *dev = &st->spi->dev; + struct spi_offload_trigger_info trigger_info = { + .fwnode = dev_fwnode(dev), + .ops = &ad4695_offload_trigger_ops, + .priv = st, + }; + struct pwm_state pwm_state; + struct dma_chan *rx_dma; + int ret, i; + + indio_dev->info = &ad4695_offload_info; + indio_dev->num_channels = st->chip_info->num_voltage_inputs + 1; + indio_dev->setup_ops = &ad4695_offload_buffer_setup_ops; + + if (!st->cnv_gpio) + return dev_err_probe(dev, -ENODEV, + "CNV GPIO is required for SPI offload\n"); + + ret = devm_spi_offload_trigger_register(dev, &trigger_info); + if (ret) + return dev_err_probe(dev, ret, + "failed to register offload trigger\n"); + + st->offload_trigger = devm_spi_offload_trigger_get(dev, st->offload, + SPI_OFFLOAD_TRIGGER_DATA_READY); + if (IS_ERR(st->offload_trigger)) + return dev_err_probe(dev, PTR_ERR(st->offload_trigger), + "failed to get offload trigger\n"); + + ret = devm_mutex_init(dev, &st->cnv_pwm_lock); + if (ret) + return ret; + + st->cnv_pwm = devm_pwm_get(dev, NULL); + if (IS_ERR(st->cnv_pwm)) + return dev_err_probe(dev, PTR_ERR(st->cnv_pwm), + "failed to get CNV PWM\n"); + + pwm_init_state(st->cnv_pwm, &pwm_state); + + /* If firmware didn't provide default rate, use 10kHz (arbitrary). */ + if (pwm_state.period == 0) + pwm_state.period = 100 * MILLI; + + pwm_state.enabled = true; + + ret = pwm_apply_might_sleep(st->cnv_pwm, &pwm_state); + if (ret) + return dev_err_probe(dev, ret, "failed to apply CNV PWM\n"); + + ret = devm_add_action_or_reset(dev, ad4695_pwm_disable, st->cnv_pwm); + if (ret) + return ret; + + rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev, st->offload); + if (IS_ERR(rx_dma)) + return dev_err_probe(dev, PTR_ERR(rx_dma), + "failed to get offload RX DMA\n"); + + for (i = 0; i < indio_dev->num_channels; i++) { + struct iio_chan_spec *chan = &st->iio_chan[i]; + struct ad4695_channel_config *cfg; + + /* + * NB: When using offload support, all channels need to have the + * same bits_per_word because they all use the same SPI message + * for reading one sample. In order to prevent breaking + * userspace in the future when oversampling support is added, + * all channels are set read 19 bits with a shift of 3 to mask + * out the extra bits even though we currently only support 16 + * bit samples (oversampling ratio == 1). + */ + chan->scan_type.shift = 3; + chan->scan_type.storagebits = 32; + /* add sample frequency for PWM CNV trigger */ + chan->info_mask_separate |= BIT(IIO_CHAN_INFO_SAMP_FREQ); + chan->info_mask_separate_available |= BIT(IIO_CHAN_INFO_SAMP_FREQ); + + /* Add the oversampling properties only for voltage channels */ + if (chan->type != IIO_VOLTAGE) + continue; + + cfg = &st->channels_cfg[i]; + + chan->info_mask_separate |= BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO); + chan->info_mask_separate_available |= + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO); + chan->has_ext_scan_type = 1; + if (cfg->bipolar) { + chan->ext_scan_type = ad4695_scan_type_offload_s; + chan->num_ext_scan_type = + ARRAY_SIZE(ad4695_scan_type_offload_s); + } else { + chan->ext_scan_type = ad4695_scan_type_offload_u; + chan->num_ext_scan_type = + ARRAY_SIZE(ad4695_scan_type_offload_u); + } + } + + return devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, + rx_dma, IIO_BUFFER_DIRECTION_IN); +} + +static const struct spi_offload_config ad4695_spi_offload_config = { + .capability_flags = SPI_OFFLOAD_CAP_TRIGGER | + SPI_OFFLOAD_CAP_RX_STREAM_DMA, +}; + +static int ad4695_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct ad4695_state *st; + struct iio_dev *indio_dev; + bool use_internal_ldo_supply; + bool use_internal_ref_buffer; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + + st->chip_info = spi_get_device_match_data(spi); + if (!st->chip_info) + return -EINVAL; + + st->sample_freq_range[0] = 1; /* min */ + st->sample_freq_range[1] = 1; /* step */ + st->sample_freq_range[2] = st->chip_info->max_sample_rate; /* max */ + + st->regmap = devm_regmap_init(dev, &ad4695_regmap_bus, st, + &ad4695_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "Failed to initialize regmap\n"); + + st->regmap16 = devm_regmap_init(dev, &ad4695_regmap_bus, st, + &ad4695_regmap16_config); + if (IS_ERR(st->regmap16)) + return dev_err_probe(dev, PTR_ERR(st->regmap16), + "Failed to initialize regmap16\n"); + + st->cnv_gpio = devm_gpiod_get_optional(dev, "cnv", GPIOD_OUT_LOW); + if (IS_ERR(st->cnv_gpio)) + return dev_err_probe(dev, PTR_ERR(st->cnv_gpio), + "Failed to get CNV GPIO\n"); + + ret = devm_regulator_bulk_get_enable(dev, + ARRAY_SIZE(ad4695_power_supplies), + ad4695_power_supplies); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable power supplies\n"); + + /* If LDO_IN supply is present, then we are using internal LDO. */ + ret = devm_regulator_get_enable_optional(dev, "ldo-in"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, + "Failed to enable LDO_IN supply\n"); + + use_internal_ldo_supply = ret == 0; + + if (!use_internal_ldo_supply) { + /* Otherwise we need an external VDD supply. */ + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to enable VDD supply\n"); + } + + /* If REFIN supply is given, then we are using internal buffer */ + ret = devm_regulator_get_enable_read_voltage(dev, "refin"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "Failed to get REFIN voltage\n"); + + if (ret != -ENODEV) { + st->vref_mv = ret / 1000; + use_internal_ref_buffer = true; + } else { + /* Otherwise, we need an external reference. */ + ret = devm_regulator_get_enable_read_voltage(dev, "ref"); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to get REF voltage\n"); + + st->vref_mv = ret / 1000; + use_internal_ref_buffer = false; + } + + ret = devm_regulator_get_enable_read_voltage(dev, "com"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "Failed to get COM voltage\n"); + + st->com_mv = ret == -ENODEV ? 0 : ret / 1000; + + /* + * Reset the device using hardware reset if available or fall back to + * software reset. + */ + + st->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(st->reset_gpio)) + return PTR_ERR(st->reset_gpio); + + if (st->reset_gpio) { + gpiod_set_value(st->reset_gpio, 0); + msleep(AD4695_T_WAKEUP_HW_MS); + } else { + ret = regmap_write(st->regmap, AD4695_REG_SPI_CONFIG_A, + AD4695_REG_SPI_CONFIG_A_SW_RST); + if (ret) + return ret; + + msleep(AD4695_T_WAKEUP_SW_MS); + } + + /* Needed for regmap16 to be able to work correctly. */ + ret = regmap_set_bits(st->regmap, AD4695_REG_SPI_CONFIG_A, + AD4695_REG_SPI_CONFIG_A_ADDR_DIR); + if (ret) + return ret; + + /* Disable internal LDO if it isn't needed. */ + ret = regmap_update_bits(st->regmap, AD4695_REG_SETUP, + AD4695_REG_SETUP_LDO_EN, + FIELD_PREP(AD4695_REG_SETUP_LDO_EN, + use_internal_ldo_supply ? 1 : 0)); + if (ret) + return ret; + + /* configure reference supply */ + + if (device_property_present(dev, "adi,no-ref-current-limit")) { + ret = regmap_set_bits(st->regmap, AD4695_REG_REF_CTRL, + AD4695_REG_REF_CTRL_OV_MODE); + if (ret) + return ret; + } + + if (device_property_present(dev, "adi,no-ref-high-z")) { + if (use_internal_ref_buffer) + return dev_err_probe(dev, -EINVAL, + "Cannot disable high-Z mode for internal reference buffer\n"); + + ret = regmap_clear_bits(st->regmap, AD4695_REG_REF_CTRL, + AD4695_REG_REF_CTRL_REFHIZ_EN); + if (ret) + return ret; + } + + ret = ad4695_set_ref_voltage(st, st->vref_mv); + if (ret) + return ret; + + if (use_internal_ref_buffer) { + ret = regmap_set_bits(st->regmap, AD4695_REG_REF_CTRL, + AD4695_REG_REF_CTRL_REFBUF_EN); + if (ret) + return ret; + + /* Give the capacitor some time to charge up. */ + msleep(AD4695_T_REFBUF_MS); + } + + ret = ad4695_parse_channel_cfg(st); + if (ret) + return ret; + + indio_dev->name = st->chip_info->name; + indio_dev->info = &ad4695_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->iio_chan; + indio_dev->num_channels = st->chip_info->num_voltage_inputs + 2; + + st->offload = devm_spi_offload_get(dev, spi, &ad4695_spi_offload_config); + ret = PTR_ERR_OR_ZERO(st->offload); + if (ret && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to get SPI offload\n"); + + /* If no SPI offload, fall back to low speed usage. */ + if (ret == -ENODEV) { + /* Driver currently requires CNV pin to be connected to SPI CS */ + if (st->cnv_gpio) + return dev_err_probe(dev, -EINVAL, + "CNV GPIO is not supported\n"); + + indio_dev->num_channels = st->chip_info->num_voltage_inputs + 2; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + ad4695_trigger_handler, + &ad4695_buffer_setup_ops); + if (ret) + return ret; + } else { + ret = ad4695_probe_spi_offload(indio_dev, st); + if (ret) + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id ad4695_spi_id_table[] = { + { .name = "ad4695", .driver_data = (kernel_ulong_t)&ad4695_chip_info }, + { .name = "ad4696", .driver_data = (kernel_ulong_t)&ad4696_chip_info }, + { .name = "ad4697", .driver_data = (kernel_ulong_t)&ad4697_chip_info }, + { .name = "ad4698", .driver_data = (kernel_ulong_t)&ad4698_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad4695_spi_id_table); + +static const struct of_device_id ad4695_of_match_table[] = { + { .compatible = "adi,ad4695", .data = &ad4695_chip_info, }, + { .compatible = "adi,ad4696", .data = &ad4696_chip_info, }, + { .compatible = "adi,ad4697", .data = &ad4697_chip_info, }, + { .compatible = "adi,ad4698", .data = &ad4698_chip_info, }, + { } +}; +MODULE_DEVICE_TABLE(of, ad4695_of_match_table); + +static struct spi_driver ad4695_driver = { + .driver = { + .name = "ad4695", + .of_match_table = ad4695_of_match_table, + }, + .probe = ad4695_probe, + .id_table = ad4695_spi_id_table, +}; +module_spi_driver(ad4695_driver); + +MODULE_AUTHOR("Ramona Gradinariu "); +MODULE_AUTHOR("David Lechner "); +MODULE_DESCRIPTION("Analog Devices AD4695 ADC driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER"); diff --git a/drivers/iio/adc/ad4851.c b/drivers/iio/adc/ad4851.c new file mode 100644 index 000000000..480d1798f --- /dev/null +++ b/drivers/iio/adc/ad4851.c @@ -0,0 +1,1316 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices AD4851 DAS driver + * + * Copyright 2024 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define AD4851_REG_INTERFACE_CONFIG_A 0x00 +#define AD4851_REG_INTERFACE_CONFIG_B 0x01 +#define AD4851_REG_PRODUCT_ID_L 0x04 +#define AD4851_REG_PRODUCT_ID_H 0x05 +#define AD4851_REG_DEVICE_CTRL 0x25 +#define AD4851_REG_PACKET 0x26 +#define AD4851_REG_OVERSAMPLE 0x27 + +#define AD4851_REG_CH_CONFIG_BASE 0x2A +#define AD4851_REG_CHX_SOFTSPAN(ch) ((0x12 * (ch)) + AD4851_REG_CH_CONFIG_BASE) +#define AD4851_REG_CHX_OFFSET(ch) (AD4851_REG_CHX_SOFTSPAN(ch) + 0x01) +#define AD4851_REG_CHX_OFFSET_LSB(ch) AD4851_REG_CHX_OFFSET(ch) +#define AD4851_REG_CHX_OFFSET_MID(ch) (AD4851_REG_CHX_OFFSET_LSB(ch) + 0x01) +#define AD4851_REG_CHX_OFFSET_MSB(ch) (AD4851_REG_CHX_OFFSET_MID(ch) + 0x01) +#define AD4851_REG_CHX_GAIN(ch) (AD4851_REG_CHX_OFFSET(ch) + 0x03) +#define AD4851_REG_CHX_GAIN_LSB(ch) AD4851_REG_CHX_GAIN(ch) +#define AD4851_REG_CHX_GAIN_MSB(ch) (AD4851_REG_CHX_GAIN(ch) + 0x01) +#define AD4851_REG_CHX_PHASE(ch) (AD4851_REG_CHX_GAIN(ch) + 0x02) +#define AD4851_REG_CHX_PHASE_LSB(ch) AD4851_REG_CHX_PHASE(ch) +#define AD4851_REG_CHX_PHASE_MSB(ch) (AD4851_REG_CHX_PHASE_LSB(ch) + 0x01) + +#define AD4851_REG_TESTPAT_0(c) (0x38 + (c) * 0x12) +#define AD4851_REG_TESTPAT_1(c) (0x39 + (c) * 0x12) +#define AD4851_REG_TESTPAT_2(c) (0x3A + (c) * 0x12) +#define AD4851_REG_TESTPAT_3(c) (0x3B + (c) * 0x12) + +#define AD4851_SW_RESET (BIT(7) | BIT(0)) +#define AD4851_SDO_ENABLE BIT(4) +#define AD4851_SINGLE_INSTRUCTION BIT(7) +#define AD4851_REFBUF BIT(2) +#define AD4851_REFSEL BIT(1) +#define AD4851_ECHO_CLOCK_MODE BIT(0) + +#define AD4851_PACKET_FORMAT_0 0 +#define AD4851_PACKET_FORMAT_1 1 +#define AD4851_PACKET_FORMAT_MASK GENMASK(1, 0) + +#define AD4851_OS_EN_MSK BIT(7) +#define AD4851_OS_RATIO_MSK GENMASK(3, 0) + +#define AD4851_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 AD4851_TESTPAT_0_DEFAULT 0x2A +#define AD4851_TESTPAT_1_DEFAULT 0x3C +#define AD4851_TESTPAT_2_DEFAULT 0xCE +#define AD4851_TESTPAT_3_DEFAULT(c) (0x0A + (0x10 * (c))) + +#define AD4851_SOFTSPAN_0V_2V5 0 +#define AD4851_SOFTSPAN_N2V5_2V5 1 +#define AD4851_SOFTSPAN_0V_5V 2 +#define AD4851_SOFTSPAN_N5V_5V 3 +#define AD4851_SOFTSPAN_0V_6V25 4 +#define AD4851_SOFTSPAN_N6V25_6V25 5 +#define AD4851_SOFTSPAN_0V_10V 6 +#define AD4851_SOFTSPAN_N10V_10V 7 +#define AD4851_SOFTSPAN_0V_12V5 8 +#define AD4851_SOFTSPAN_N12V5_12V5 9 +#define AD4851_SOFTSPAN_0V_20V 10 +#define AD4851_SOFTSPAN_N20V_20V 11 +#define AD4851_SOFTSPAN_0V_25V 12 +#define AD4851_SOFTSPAN_N25V_25V 13 +#define AD4851_SOFTSPAN_0V_40V 14 +#define AD4851_SOFTSPAN_N40V_40V 15 + +#define AD4851_MAX_LANES 8 +#define AD4851_MAX_IODELAY 32 + +#define AD4851_T_CNVH_NS 40 +#define AD4851_T_CNVH_NS_MARGIN 10 + +#define AD4841_MAX_SCALE_AVAIL 8 + +#define AD4851_MAX_CH_NR 8 +#define AD4851_CH_START 0 + +struct ad4851_scale { + unsigned int scale_val; + u8 reg_val; +}; + +static const struct ad4851_scale ad4851_scale_table_unipolar[] = { + { 2500, 0x0 }, + { 5000, 0x2 }, + { 6250, 0x4 }, + { 10000, 0x6 }, + { 12500, 0x8 }, + { 20000, 0xA }, + { 25000, 0xC }, + { 40000, 0xE }, +}; + +static const struct ad4851_scale ad4851_scale_table_bipolar[] = { + { 5000, 0x1 }, + { 10000, 0x3 }, + { 12500, 0x5 }, + { 20000, 0x7 }, + { 25000, 0x9 }, + { 40000, 0xB }, + { 50000, 0xD }, + { 80000, 0xF }, +}; + +static const unsigned int ad4851_scale_avail_unipolar[] = { + 2500, + 5000, + 6250, + 10000, + 12500, + 20000, + 25000, + 40000, +}; + +static const unsigned int ad4851_scale_avail_bipolar[] = { + 5000, + 10000, + 12500, + 20000, + 25000, + 40000, + 50000, + 80000, +}; + +struct ad4851_chip_info { + const char *name; + unsigned int product_id; + int num_scales; + unsigned long max_sample_rate_hz; + unsigned int resolution; + unsigned int max_channels; + int (*parse_channels)(struct iio_dev *indio_dev); +}; + +enum { + AD4851_SCAN_TYPE_NORMAL, + AD4851_SCAN_TYPE_RESOLUTION_BOOST, +}; + +struct ad4851_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 ad4851_chip_info *info; + struct gpio_desc *pd_gpio; + bool resolution_boost_enabled; + unsigned long cnv_trigger_rate_hz; + unsigned int osr; + bool vrefbuf_en; + bool vrefio_en; + bool bipolar_ch[AD4851_MAX_CH_NR]; + unsigned int scales_unipolar[AD4841_MAX_SCALE_AVAIL][2]; + unsigned int scales_bipolar[AD4841_MAX_SCALE_AVAIL][2]; +}; + +static int ad4851_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct ad4851_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); +} + +static int ad4851_set_sampling_freq(struct ad4851_state *st, unsigned int freq) +{ + struct pwm_state cnv_state = { + .duty_cycle = AD4851_T_CNVH_NS + AD4851_T_CNVH_NS_MARGIN, + .enabled = true, + }; + int ret; + + freq = clamp(freq, 1, st->info->max_sample_rate_hz); + + cnv_state.period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, freq); + + ret = pwm_apply_might_sleep(st->cnv, &cnv_state); + if (ret) + return ret; + + st->cnv_trigger_rate_hz = freq; + + return 0; +} + +static const int ad4851_oversampling_ratios[] = { + 1, 2, 4, 8, 16, 32, 64, 128, + 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, + 65536, +}; + +static int ad4851_osr_to_regval(unsigned int ratio) +{ + int i; + + for (i = 1; i < ARRAY_SIZE(ad4851_oversampling_ratios); i++) + if (ratio == ad4851_oversampling_ratios[i]) + return i - 1; + + return -EINVAL; +} + +static int __ad4851_get_scale(struct iio_dev *indio_dev, int scale_tbl, + unsigned int *val, unsigned int *val2) +{ + const struct iio_scan_type *scan_type; + unsigned int tmp; + + scan_type = iio_get_current_scan_type(indio_dev, &indio_dev->channels[0]); + if (IS_ERR(scan_type)) + return PTR_ERR(scan_type); + + tmp = ((u64)scale_tbl * MICRO) >> scan_type->realbits; + *val = tmp / MICRO; + *val2 = tmp % MICRO; + + return 0; +} + +static int ad4851_scale_fill(struct iio_dev *indio_dev) +{ + struct ad4851_state *st = iio_priv(indio_dev); + unsigned int i, val1, val2; + int ret; + + for (i = 0; i < ARRAY_SIZE(ad4851_scale_avail_unipolar); i++) { + ret = __ad4851_get_scale(indio_dev, + ad4851_scale_avail_unipolar[i], + &val1, &val2); + if (ret) + return ret; + + st->scales_unipolar[i][0] = val1; + st->scales_unipolar[i][1] = val2; + } + + for (i = 0; i < ARRAY_SIZE(ad4851_scale_avail_bipolar); i++) { + ret = __ad4851_get_scale(indio_dev, + ad4851_scale_avail_bipolar[i], + &val1, &val2); + if (ret) + return ret; + + st->scales_bipolar[i][0] = val1; + st->scales_bipolar[i][1] = val2; + } + + return 0; +} + +static int ad4851_set_oversampling_ratio(struct iio_dev *indio_dev, + unsigned int osr) +{ + struct ad4851_state *st = iio_priv(indio_dev); + int val, ret; + + guard(mutex)(&st->lock); + + if (osr == 1) { + ret = regmap_clear_bits(st->regmap, AD4851_REG_OVERSAMPLE, + AD4851_OS_EN_MSK); + if (ret) + return ret; + } else { + val = ad4851_osr_to_regval(osr); + if (val < 0) + return -EINVAL; + + ret = regmap_update_bits(st->regmap, AD4851_REG_OVERSAMPLE, + AD4851_OS_EN_MSK | + AD4851_OS_RATIO_MSK, + FIELD_PREP(AD4851_OS_EN_MSK, 1) | + FIELD_PREP(AD4851_OS_RATIO_MSK, val)); + if (ret) + return ret; + } + + /* Channel is ignored by the backend being used here */ + ret = iio_backend_oversampling_ratio_set(st->back, 0, osr); + if (ret) + return ret; + + switch (st->info->resolution) { + case 20: + switch (osr) { + case 0: + return -EINVAL; + case 1: + val = 20; + break; + default: + val = 24; + break; + } + break; + case 16: + val = 16; + break; + default: + return -EINVAL; + } + + ret = iio_backend_data_size_set(st->back, val); + if (ret) + return ret; + + if (osr == 1 || st->info->resolution == 16) { + ret = regmap_clear_bits(st->regmap, AD4851_REG_PACKET, + AD4851_PACKET_FORMAT_MASK); + if (ret) + return ret; + + st->resolution_boost_enabled = false; + } else { + ret = regmap_update_bits(st->regmap, AD4851_REG_PACKET, + AD4851_PACKET_FORMAT_MASK, + FIELD_PREP(AD4851_PACKET_FORMAT_MASK, 1)); + if (ret) + return ret; + + st->resolution_boost_enabled = true; + } + + if (st->osr != osr) { + ret = ad4851_scale_fill(indio_dev); + if (ret) + return ret; + + st->osr = osr; + } + + return 0; +} + +static int ad4851_get_oversampling_ratio(struct ad4851_state *st, unsigned int *val) +{ + unsigned int osr; + int ret; + + guard(mutex)(&st->lock); + + ret = regmap_read(st->regmap, AD4851_REG_OVERSAMPLE, &osr); + if (ret) + return ret; + + if (!FIELD_GET(AD4851_OS_EN_MSK, osr)) + *val = 1; + else + *val = ad4851_oversampling_ratios[FIELD_GET(AD4851_OS_RATIO_MSK, osr) + 1]; + + st->osr = *val; + + return IIO_VAL_INT; +} + +static void ad4851_pwm_disable(void *data) +{ + pwm_disable(data); +} + +static int ad4851_setup(struct ad4851_state *st) +{ + unsigned int product_id; + int ret; + + if (st->pd_gpio) { + /* To initiate a global reset, bring the PD pin high twice */ + gpiod_set_value(st->pd_gpio, 1); + fsleep(1); + gpiod_set_value(st->pd_gpio, 0); + fsleep(1); + gpiod_set_value(st->pd_gpio, 1); + fsleep(1); + gpiod_set_value(st->pd_gpio, 0); + fsleep(1000); + } else { + ret = regmap_set_bits(st->regmap, AD4851_REG_INTERFACE_CONFIG_A, + AD4851_SW_RESET); + if (ret) + return ret; + } + + if (st->vrefbuf_en) { + ret = regmap_set_bits(st->regmap, AD4851_REG_DEVICE_CTRL, + AD4851_REFBUF); + if (ret) + return ret; + } + + if (st->vrefio_en) { + ret = regmap_set_bits(st->regmap, AD4851_REG_DEVICE_CTRL, + AD4851_REFSEL); + if (ret) + return ret; + } + + ret = regmap_write(st->regmap, AD4851_REG_INTERFACE_CONFIG_B, + AD4851_SINGLE_INSTRUCTION); + if (ret) + return ret; + + if (!(st->spi->mode & SPI_3WIRE)) { + ret = regmap_write(st->regmap, AD4851_REG_INTERFACE_CONFIG_A, + AD4851_SDO_ENABLE); + if (ret) + return ret; + } + + ret = regmap_read(st->regmap, AD4851_REG_PRODUCT_ID_L, &product_id); + if (ret) + return ret; + + if (product_id != st->info->product_id) + dev_info(&st->spi->dev, "Unknown product ID: 0x%02X\n", + product_id); + + ret = regmap_set_bits(st->regmap, AD4851_REG_DEVICE_CTRL, + AD4851_ECHO_CLOCK_MODE); + if (ret) + return ret; + + return regmap_write(st->regmap, AD4851_REG_PACKET, 0); +} + +/* + * Find the longest consecutive sequence of false values from field + * and return starting index. + */ +static int ad4851_find_opt(const unsigned long *field, unsigned int start, + unsigned int nbits, unsigned int *val) +{ + unsigned int bit = start, end, start_cnt, cnt = 0; + + for_each_clear_bitrange_from(bit, end, field, start + nbits) { + if (end - bit > cnt) { + cnt = end - bit; + start_cnt = bit - start; + } + } + + if (!cnt) + return -ENOENT; + + *val = start_cnt; + + return cnt; +} + +static int ad4851_calibrate(struct iio_dev *indio_dev) +{ + struct ad4851_state *st = iio_priv(indio_dev); + unsigned int opt_delay, num_lanes, delay, i, s; + enum iio_backend_interface_type interface_type; + DECLARE_BITMAP(pn_status, AD4851_MAX_LANES * AD4851_MAX_IODELAY); + bool status; + int c, ret; + + ret = iio_backend_interface_type_get(st->back, &interface_type); + if (ret) + return ret; + + switch (interface_type) { + case IIO_BACKEND_INTERFACE_SERIAL_CMOS: + num_lanes = indio_dev->num_channels; + break; + case IIO_BACKEND_INTERFACE_SERIAL_LVDS: + num_lanes = 1; + break; + default: + return -EINVAL; + } + + if (st->info->resolution == 16) { + ret = iio_backend_data_size_set(st->back, 24); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD4851_REG_PACKET, + AD4851_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, AD4851_REG_PACKET, + AD4851_TEST_PAT | AD4858_PACKET_SIZE_32); + if (ret) + return ret; + } + + for (i = 0; i < indio_dev->num_channels; i++) { + ret = regmap_write(st->regmap, AD4851_REG_TESTPAT_0(i), + AD4851_TESTPAT_0_DEFAULT); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD4851_REG_TESTPAT_1(i), + AD4851_TESTPAT_1_DEFAULT); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD4851_REG_TESTPAT_2(i), + AD4851_TESTPAT_2_DEFAULT); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD4851_REG_TESTPAT_3(i), + AD4851_TESTPAT_3_DEFAULT(i)); + if (ret) + return ret; + + ret = iio_backend_chan_enable(st->back, + indio_dev->channels[i].channel); + if (ret) + return ret; + } + + for (i = 0; i < num_lanes; i++) { + for (delay = 0; delay < AD4851_MAX_IODELAY; delay++) { + ret = iio_backend_iodelay_set(st->back, i, delay); + if (ret) + return ret; + + ret = iio_backend_chan_status(st->back, i, &status); + if (ret) + return ret; + + __assign_bit(i * AD4851_MAX_IODELAY + delay, pn_status, + status); + } + } + + for (i = 0; i < num_lanes; i++) { + c = ad4851_find_opt(pn_status, i * AD4851_MAX_IODELAY, + AD4851_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 < indio_dev->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, AD4851_REG_PACKET, 0); +} + +static int ad4851_get_calibscale(struct ad4851_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, AD4851_REG_CHX_GAIN_MSB(ch), ®_val); + if (ret) + return ret; + + gain = reg_val << 8; + + ret = regmap_read(st->regmap, AD4851_REG_CHX_GAIN_LSB(ch), ®_val); + if (ret) + return ret; + + gain |= reg_val; + + *val = gain; + *val2 = 15; + + return IIO_VAL_FRACTIONAL_LOG2; +} + +static int ad4851_set_calibscale(struct ad4851_state *st, int ch, int val, + int val2) +{ + u64 gain; + u8 buf[2]; + int ret; + + if (val < 0 || val2 < 0) + return -EINVAL; + + gain = val * MICRO + val2; + gain = DIV_U64_ROUND_CLOSEST(gain * 32768, MICRO); + + put_unaligned_be16(gain, buf); + + guard(mutex)(&st->lock); + + ret = regmap_write(st->regmap, AD4851_REG_CHX_GAIN_MSB(ch), buf[0]); + if (ret) + return ret; + + return regmap_write(st->regmap, AD4851_REG_CHX_GAIN_LSB(ch), buf[1]); +} + +static int ad4851_get_calibbias(struct ad4851_state *st, int ch, int *val) +{ + unsigned int lsb, mid, msb; + int ret; + + guard(mutex)(&st->lock); + /* + * After testing, the bulk_write operations doesn't work as expected + * here since the cs needs to be raised after each byte transaction. + */ + ret = regmap_read(st->regmap, AD4851_REG_CHX_OFFSET_MSB(ch), &msb); + if (ret) + return ret; + + ret = regmap_read(st->regmap, AD4851_REG_CHX_OFFSET_MID(ch), &mid); + if (ret) + return ret; + + ret = regmap_read(st->regmap, AD4851_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 ad4851_set_calibbias(struct ad4851_state *st, int ch, int val) +{ + u8 buf[3]; + int ret; + + if (val < 0) + return -EINVAL; + + if (st->info->resolution == 16) + put_unaligned_be16(val, buf); + else + put_unaligned_be24(val << 4, buf); + + guard(mutex)(&st->lock); + /* + * After testing, the bulk_write operations doesn't work as expected + * here since the cs needs to be raised after each byte transaction. + */ + ret = regmap_write(st->regmap, AD4851_REG_CHX_OFFSET_LSB(ch), buf[2]); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD4851_REG_CHX_OFFSET_MID(ch), buf[1]); + if (ret) + return ret; + + return regmap_write(st->regmap, AD4851_REG_CHX_OFFSET_MSB(ch), buf[0]); +} + +static int ad4851_set_scale(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int val, int val2) +{ + struct ad4851_state *st = iio_priv(indio_dev); + unsigned int scale_val[2]; + unsigned int i; + const struct ad4851_scale *scale_table; + size_t table_size; + int ret; + + if (st->bipolar_ch[chan->channel]) { + scale_table = ad4851_scale_table_bipolar; + table_size = ARRAY_SIZE(ad4851_scale_table_bipolar); + } else { + scale_table = ad4851_scale_table_unipolar; + table_size = ARRAY_SIZE(ad4851_scale_table_unipolar); + } + + for (i = 0; i < table_size; i++) { + ret = __ad4851_get_scale(indio_dev, scale_table[i].scale_val, + &scale_val[0], &scale_val[1]); + if (ret) + return ret; + + if (scale_val[0] != val || scale_val[1] != val2) + continue; + + return regmap_write(st->regmap, + AD4851_REG_CHX_SOFTSPAN(chan->channel), + scale_table[i].reg_val); + } + + return -EINVAL; +} + +static int ad4851_get_scale(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, + int *val2) +{ + struct ad4851_state *st = iio_priv(indio_dev); + const struct ad4851_scale *scale_table; + size_t table_size; + u32 softspan_val; + int i, ret; + + if (st->bipolar_ch[chan->channel]) { + scale_table = ad4851_scale_table_bipolar; + table_size = ARRAY_SIZE(ad4851_scale_table_bipolar); + } else { + scale_table = ad4851_scale_table_unipolar; + table_size = ARRAY_SIZE(ad4851_scale_table_unipolar); + } + + ret = regmap_read(st->regmap, AD4851_REG_CHX_SOFTSPAN(chan->channel), + &softspan_val); + if (ret) + return ret; + + for (i = 0; i < table_size; i++) { + if (softspan_val == scale_table[i].reg_val) + break; + } + + if (i == table_size) + return -EIO; + + ret = __ad4851_get_scale(indio_dev, scale_table[i].scale_val, val, + val2); + if (ret) + return ret; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int ad4851_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long info) +{ + struct ad4851_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + *val = st->cnv_trigger_rate_hz; + *val2 = st->osr; + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_CALIBSCALE: + return ad4851_get_calibscale(st, chan->channel, val, val2); + case IIO_CHAN_INFO_SCALE: + return ad4851_get_scale(indio_dev, chan, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + return ad4851_get_calibbias(st, chan->channel, val); + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return ad4851_get_oversampling_ratio(st, val); + default: + return -EINVAL; + } +} + +static int ad4851_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct ad4851_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (val < 0 || val2 < 0) + return -EINVAL; + return ad4851_set_sampling_freq(st, val * st->osr + val2 * st->osr / MICRO); + case IIO_CHAN_INFO_SCALE: + return ad4851_set_scale(indio_dev, chan, val, val2); + case IIO_CHAN_INFO_CALIBSCALE: + return ad4851_set_calibscale(st, chan->channel, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + return ad4851_set_calibbias(st, chan->channel, val); + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return ad4851_set_oversampling_ratio(indio_dev, val); + default: + return -EINVAL; + } +} + +static int ad4851_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ad4851_state *st = iio_priv(indio_dev); + unsigned int c; + int ret; + + for (c = 0; c < indio_dev->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 ad4851_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct ad4851_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (st->bipolar_ch[chan->channel]) { + *vals = (const int *)st->scales_bipolar; + *type = IIO_VAL_INT_PLUS_MICRO; + /* Values are stored in a 2D matrix */ + *length = ARRAY_SIZE(ad4851_scale_avail_bipolar) * 2; + } else { + *vals = (const int *)st->scales_unipolar; + *type = IIO_VAL_INT_PLUS_MICRO; + /* Values are stored in a 2D matrix */ + *length = ARRAY_SIZE(ad4851_scale_avail_unipolar) * 2; + } + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = ad4851_oversampling_ratios; + *length = ARRAY_SIZE(ad4851_oversampling_ratios); + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const struct iio_scan_type ad4851_scan_type_20_u[] = { + [AD4851_SCAN_TYPE_NORMAL] = { + .sign = 'u', + .realbits = 20, + .storagebits = 32, + }, + [AD4851_SCAN_TYPE_RESOLUTION_BOOST] = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + }, +}; + +static const struct iio_scan_type ad4851_scan_type_20_b[] = { + [AD4851_SCAN_TYPE_NORMAL] = { + .sign = 's', + .realbits = 20, + .storagebits = 32, + }, + [AD4851_SCAN_TYPE_RESOLUTION_BOOST] = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + }, +}; + +static int ad4851_get_current_scan_type(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad4851_state *st = iio_priv(indio_dev); + + return st->resolution_boost_enabled ? AD4851_SCAN_TYPE_RESOLUTION_BOOST + : AD4851_SCAN_TYPE_NORMAL; +} + +#define AD4851_IIO_CHANNEL \ + .type = IIO_VOLTAGE, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .indexed = 1 + +/* + * In case of AD4858_IIO_CHANNEL the scan_type is handled dynamically during the + * parse_channels function. + */ +#define AD4858_IIO_CHANNEL \ +{ \ + AD4851_IIO_CHANNEL \ +} + +#define AD4857_IIO_CHANNEL \ +{ \ + AD4851_IIO_CHANNEL, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + }, \ +} + +static int ad4851_parse_channels_common(struct iio_dev *indio_dev, + struct iio_chan_spec **chans, + const struct iio_chan_spec ad4851_chan) +{ + struct ad4851_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + struct iio_chan_spec *channels, *chan_start; + unsigned int num_channels, reg; + unsigned int index = 0; + int ret; + + num_channels = device_get_child_node_count(dev); + if (num_channels > AD4851_MAX_CH_NR) + return dev_err_probe(dev, -EINVAL, "Too many channels: %u\n", + num_channels); + + channels = devm_kcalloc(dev, num_channels, sizeof(*channels), GFP_KERNEL); + if (!channels) + return -ENOMEM; + + chan_start = channels; + + device_for_each_child_node_scoped(dev, child) { + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret) + return dev_err_probe(dev, ret, + "Missing channel number\n"); + if (reg >= AD4851_MAX_CH_NR) + return dev_err_probe(dev, -EINVAL, + "Invalid channel number\n"); + *channels = ad4851_chan; + channels->scan_index = index++; + channels->channel = reg; + + if (fwnode_property_present(child, "diff-channels")) { + channels->channel2 = reg + st->info->max_channels; + channels->differential = 1; + } + + st->bipolar_ch[reg] = fwnode_property_read_bool(child, "bipolar"); + + if (st->bipolar_ch[reg]) { + channels->scan_type.sign = 's'; + } else { + ret = regmap_write(st->regmap, AD4851_REG_CHX_SOFTSPAN(reg), + AD4851_SOFTSPAN_0V_40V); + if (ret) + return ret; + } + + channels++; + } + + *chans = chan_start; + + return num_channels; +} + +static int ad4857_parse_channels(struct iio_dev *indio_dev) +{ + struct iio_chan_spec *ad4851_channels; + const struct iio_chan_spec ad4851_chan = AD4857_IIO_CHANNEL; + int ret; + + ret = ad4851_parse_channels_common(indio_dev, &ad4851_channels, + ad4851_chan); + if (ret < 0) + return ret; + + indio_dev->channels = ad4851_channels; + indio_dev->num_channels = ret; + + return 0; +} + +static int ad4858_parse_channels(struct iio_dev *indio_dev) +{ + struct ad4851_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + struct iio_chan_spec *ad4851_channels; + const struct iio_chan_spec ad4851_chan = AD4858_IIO_CHANNEL; + int ret, i = 0; + + ret = ad4851_parse_channels_common(indio_dev, &ad4851_channels, + ad4851_chan); + if (ret < 0) + return ret; + + device_for_each_child_node_scoped(dev, child) { + ad4851_channels[i].has_ext_scan_type = 1; + if (fwnode_property_read_bool(child, "bipolar")) { + ad4851_channels[i].ext_scan_type = ad4851_scan_type_20_b; + ad4851_channels[i].num_ext_scan_type = ARRAY_SIZE(ad4851_scan_type_20_b); + } else { + ad4851_channels[i].ext_scan_type = ad4851_scan_type_20_u; + ad4851_channels[i].num_ext_scan_type = ARRAY_SIZE(ad4851_scan_type_20_u); + } + i++; + } + + indio_dev->channels = ad4851_channels; + indio_dev->num_channels = ret; + + return 0; +} + +/* + * parse_channels() function handles the rest of the channel related attributes + * that are usually are stored in the chip info structure. + */ +static const struct ad4851_chip_info ad4851_info = { + .name = "ad4851", + .product_id = 0x67, + .max_sample_rate_hz = 250 * KILO, + .resolution = 16, + .max_channels = AD4851_MAX_CH_NR, + .parse_channels = ad4857_parse_channels, +}; + +static const struct ad4851_chip_info ad4852_info = { + .name = "ad4852", + .product_id = 0x66, + .max_sample_rate_hz = 250 * KILO, + .resolution = 20, + .max_channels = AD4851_MAX_CH_NR, + .parse_channels = ad4858_parse_channels, +}; + +static const struct ad4851_chip_info ad4853_info = { + .name = "ad4853", + .product_id = 0x65, + .max_sample_rate_hz = 1 * MEGA, + .resolution = 16, + .max_channels = AD4851_MAX_CH_NR, + .parse_channels = ad4857_parse_channels, +}; + +static const struct ad4851_chip_info ad4854_info = { + .name = "ad4854", + .product_id = 0x64, + .max_sample_rate_hz = 1 * MEGA, + .resolution = 20, + .max_channels = AD4851_MAX_CH_NR, + .parse_channels = ad4858_parse_channels, +}; + +static const struct ad4851_chip_info ad4855_info = { + .name = "ad4855", + .product_id = 0x63, + .max_sample_rate_hz = 250 * KILO, + .resolution = 16, + .max_channels = AD4851_MAX_CH_NR, + .parse_channels = ad4857_parse_channels, +}; + +static const struct ad4851_chip_info ad4856_info = { + .name = "ad4856", + .product_id = 0x62, + .max_sample_rate_hz = 250 * KILO, + .resolution = 20, + .max_channels = AD4851_MAX_CH_NR, + .parse_channels = ad4858_parse_channels, +}; + +static const struct ad4851_chip_info ad4857_info = { + .name = "ad4857", + .product_id = 0x61, + .max_sample_rate_hz = 1 * MEGA, + .resolution = 16, + .max_channels = AD4851_MAX_CH_NR, + .parse_channels = ad4857_parse_channels, +}; + +static const struct ad4851_chip_info ad4858_info = { + .name = "ad4858", + .product_id = 0x60, + .max_sample_rate_hz = 1 * MEGA, + .resolution = 20, + .max_channels = AD4851_MAX_CH_NR, + .parse_channels = ad4858_parse_channels, +}; + +static const struct ad4851_chip_info ad4858i_info = { + .name = "ad4858i", + .product_id = 0x6F, + .max_sample_rate_hz = 1 * MEGA, + .resolution = 20, + .max_channels = AD4851_MAX_CH_NR, + .parse_channels = ad4858_parse_channels, +}; + +static const struct iio_info ad4851_iio_info = { + .debugfs_reg_access = ad4851_reg_access, + .read_raw = ad4851_read_raw, + .write_raw = ad4851_write_raw, + .update_scan_mode = ad4851_update_scan_mode, + .get_current_scan_type = ad4851_get_current_scan_type, + .read_avail = ad4851_read_avail, +}; + +static const struct regmap_config regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .read_flag_mask = BIT(7), +}; + +static const char * const ad4851_power_supplies[] = { + "vcc", "vdd", "vee", "vio", +}; + +static int ad4851_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct device *dev = &spi->dev; + struct ad4851_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + ret = devm_regulator_bulk_get_enable(dev, + ARRAY_SIZE(ad4851_power_supplies), + ad4851_power_supplies); + if (ret) + return dev_err_probe(dev, ret, + "failed to get and enable supplies\n"); + + ret = devm_regulator_get_enable_optional(dev, "vddh"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to enable vddh voltage\n"); + + ret = devm_regulator_get_enable_optional(dev, "vddl"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to enable vddl voltage\n"); + + ret = devm_regulator_get_enable_optional(dev, "vrefbuf"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to enable vrefbuf voltage\n"); + + st->vrefbuf_en = ret != -ENODEV; + + ret = devm_regulator_get_enable_optional(dev, "vrefio"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to enable vrefio voltage\n"); + + st->vrefio_en = ret != -ENODEV; + + st->pd_gpio = devm_gpiod_get_optional(dev, "pd", GPIOD_OUT_LOW); + if (IS_ERR(st->pd_gpio)) + return dev_err_probe(dev, PTR_ERR(st->pd_gpio), + "Error on requesting pd GPIO\n"); + + st->cnv = devm_pwm_get(dev, NULL); + if (IS_ERR(st->cnv)) + return dev_err_probe(dev, PTR_ERR(st->cnv), + "Error on requesting pwm\n"); + + 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 = ad4851_set_sampling_freq(st, HZ_PER_MHZ); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&st->spi->dev, ad4851_pwm_disable, + st->cnv); + if (ret) + return ret; + + ret = ad4851_setup(st); + if (ret) + return ret; + + indio_dev->name = st->info->name; + indio_dev->info = &ad4851_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = st->info->parse_channels(indio_dev); + if (ret) + return ret; + + ret = ad4851_scale_fill(indio_dev); + if (ret) + return ret; + + st->back = devm_iio_backend_get(dev, NULL); + if (IS_ERR(st->back)) + return PTR_ERR(st->back); + + ret = devm_iio_backend_request_buffer(dev, st->back, indio_dev); + if (ret) + return ret; + + ret = devm_iio_backend_enable(dev, st->back); + if (ret) + return ret; + + ret = ad4851_calibrate(indio_dev); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ad4851_of_match[] = { + { .compatible = "adi,ad4851", .data = &ad4851_info, }, + { .compatible = "adi,ad4852", .data = &ad4852_info, }, + { .compatible = "adi,ad4853", .data = &ad4853_info, }, + { .compatible = "adi,ad4854", .data = &ad4854_info, }, + { .compatible = "adi,ad4855", .data = &ad4855_info, }, + { .compatible = "adi,ad4856", .data = &ad4856_info, }, + { .compatible = "adi,ad4857", .data = &ad4857_info, }, + { .compatible = "adi,ad4858", .data = &ad4858_info, }, + { .compatible = "adi,ad4858i", .data = &ad4858i_info, }, + { } +}; + +static const struct spi_device_id ad4851_spi_id[] = { + { .name = "ad4851", .driver_data = (kernel_ulong_t)&ad4851_info }, + { .name = "ad4852", .driver_data = (kernel_ulong_t)&ad4852_info }, + { .name = "ad4853", .driver_data = (kernel_ulong_t)&ad4853_info }, + { .name = "ad4854", .driver_data = (kernel_ulong_t)&ad4854_info }, + { .name = "ad4855", .driver_data = (kernel_ulong_t)&ad4855_info }, + { .name = "ad4856", .driver_data = (kernel_ulong_t)&ad4856_info }, + { .name = "ad4857", .driver_data = (kernel_ulong_t)&ad4857_info }, + { .name = "ad4858", .driver_data = (kernel_ulong_t)&ad4858_info }, + { .name = "ad4858i", .driver_data = (kernel_ulong_t)&ad4858i_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad4851_spi_id); + +static struct spi_driver ad4851_driver = { + .probe = ad4851_probe, + .driver = { + .name = "ad4851", + .of_match_table = ad4851_of_match, + }, + .id_table = ad4851_spi_id, +}; +module_spi_driver(ad4851_driver); + +MODULE_AUTHOR("Sergiu Cuciurean "); +MODULE_AUTHOR("Dragos Bogdan "); +MODULE_AUTHOR("Antoniu Miclaus "); +MODULE_DESCRIPTION("Analog Devices AD4851 DAS driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_BACKEND"); diff --git a/drivers/iio/adc/ad7091r-base.c b/drivers/iio/adc/ad7091r-base.c new file mode 100644 index 000000000..647a7852d --- /dev/null +++ b/drivers/iio/adc/ad7091r-base.c @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AD7091RX Analog to Digital converter driver + * + * Copyright 2014-2019 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ad7091r-base.h" + +const struct iio_event_spec ad7091r_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_HYSTERESIS), + }, +}; +EXPORT_SYMBOL_NS_GPL(ad7091r_events, "IIO_AD7091R"); + +static int ad7091r_set_channel(struct ad7091r_state *st, unsigned int channel) +{ + unsigned int dummy; + int ret; + + /* AD7091R_REG_CHANNEL specified which channels to be converted */ + ret = regmap_write(st->map, AD7091R_REG_CHANNEL, + BIT(channel) | (BIT(channel) << 8)); + if (ret) + return ret; + + /* + * There is a latency of one conversion before the channel conversion + * sequence is updated + */ + return regmap_read(st->map, AD7091R_REG_RESULT, &dummy); +} + +static int ad7091r_read_one(struct iio_dev *iio_dev, + unsigned int channel, unsigned int *read_val) +{ + struct ad7091r_state *st = iio_priv(iio_dev); + unsigned int val; + int ret; + + ret = ad7091r_set_channel(st, channel); + if (ret) + return ret; + + ret = regmap_read(st->map, AD7091R_REG_RESULT, &val); + if (ret) + return ret; + + if (st->chip_info->reg_result_chan_id(val) != channel) + return -EIO; + + *read_val = AD7091R_REG_RESULT_CONV_RESULT(val); + + return 0; +} + +static int ad7091r_read_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long m) +{ + struct ad7091r_state *st = iio_priv(iio_dev); + unsigned int read_val; + int ret; + + guard(mutex)(&st->lock); + + switch (m) { + case IIO_CHAN_INFO_RAW: + if (st->mode != AD7091R_MODE_COMMAND) + return -EBUSY; + + ret = ad7091r_read_one(iio_dev, chan->channel, &read_val); + if (ret) + return ret; + + *val = read_val; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + if (st->vref) { + ret = regulator_get_voltage(st->vref); + if (ret < 0) + return ret; + + *val = ret / 1000; + } else { + *val = st->chip_info->vref_mV; + } + + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } +} + +static int ad7091r_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct ad7091r_state *st = iio_priv(indio_dev); + int val, ret; + + switch (dir) { + case IIO_EV_DIR_RISING: + ret = regmap_read(st->map, + AD7091R_REG_CH_HIGH_LIMIT(chan->channel), + &val); + if (ret) + return ret; + return val != AD7091R_HIGH_LIMIT; + case IIO_EV_DIR_FALLING: + ret = regmap_read(st->map, + AD7091R_REG_CH_LOW_LIMIT(chan->channel), + &val); + if (ret) + return ret; + return val != AD7091R_LOW_LIMIT; + default: + return -EINVAL; + } +} + +static int ad7091r_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct ad7091r_state *st = iio_priv(indio_dev); + + if (state) { + return regmap_set_bits(st->map, AD7091R_REG_CONF, + AD7091R_REG_CONF_ALERT_EN); + } else { + /* + * Set thresholds either to 0 or to 2^12 - 1 as appropriate to + * prevent alerts and thus disable event generation. + */ + switch (dir) { + case IIO_EV_DIR_RISING: + return regmap_write(st->map, + AD7091R_REG_CH_HIGH_LIMIT(chan->channel), + AD7091R_HIGH_LIMIT); + case IIO_EV_DIR_FALLING: + return regmap_write(st->map, + AD7091R_REG_CH_LOW_LIMIT(chan->channel), + AD7091R_LOW_LIMIT); + default: + return -EINVAL; + } + } +} + +static int ad7091r_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, int *val2) +{ + struct ad7091r_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + ret = regmap_read(st->map, + AD7091R_REG_CH_HIGH_LIMIT(chan->channel), + val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_EV_DIR_FALLING: + ret = regmap_read(st->map, + AD7091R_REG_CH_LOW_LIMIT(chan->channel), + val); + if (ret) + return ret; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_EV_INFO_HYSTERESIS: + ret = regmap_read(st->map, + AD7091R_REG_CH_HYSTERESIS(chan->channel), + val); + if (ret) + return ret; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad7091r_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, int val2) +{ + struct ad7091r_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + return regmap_write(st->map, + AD7091R_REG_CH_HIGH_LIMIT(chan->channel), + val); + case IIO_EV_DIR_FALLING: + return regmap_write(st->map, + AD7091R_REG_CH_LOW_LIMIT(chan->channel), + val); + default: + return -EINVAL; + } + case IIO_EV_INFO_HYSTERESIS: + return regmap_write(st->map, + AD7091R_REG_CH_HYSTERESIS(chan->channel), + val); + default: + return -EINVAL; + } +} + +static const struct iio_info ad7091r_info = { + .read_raw = ad7091r_read_raw, + .read_event_config = &ad7091r_read_event_config, + .write_event_config = &ad7091r_write_event_config, + .read_event_value = &ad7091r_read_event_value, + .write_event_value = &ad7091r_write_event_value, +}; + +static irqreturn_t ad7091r_event_handler(int irq, void *private) +{ + struct iio_dev *iio_dev = private; + struct ad7091r_state *st = iio_priv(iio_dev); + unsigned int i, read_val; + int ret; + s64 timestamp = iio_get_time_ns(iio_dev); + + ret = regmap_read(st->map, AD7091R_REG_ALERT, &read_val); + if (ret) + return IRQ_HANDLED; + + for (i = 0; i < st->chip_info->num_channels; i++) { + if (read_val & BIT(i * 2)) + iio_push_event(iio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, i, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), timestamp); + if (read_val & BIT(i * 2 + 1)) + iio_push_event(iio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, i, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), timestamp); + } + + return IRQ_HANDLED; +} + +static void ad7091r_remove(void *data) +{ + struct ad7091r_state *st = data; + + regulator_disable(st->vref); +} + +int ad7091r_probe(struct device *dev, const struct ad7091r_init_info *init_info, + int irq) +{ + struct iio_dev *iio_dev; + struct ad7091r_state *st; + int ret; + + iio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!iio_dev) + return -ENOMEM; + + st = iio_priv(iio_dev); + st->dev = dev; + init_info->init_adc_regmap(st, init_info->regmap_config); + if (IS_ERR(st->map)) + return dev_err_probe(st->dev, PTR_ERR(st->map), + "Error initializing regmap\n"); + + iio_dev->info = &ad7091r_info; + iio_dev->modes = INDIO_DIRECT_MODE; + + if (init_info->setup) { + ret = init_info->setup(st); + if (ret < 0) + return ret; + } + + if (irq) { + st->chip_info = init_info->info_irq; + ret = regmap_update_bits(st->map, AD7091R_REG_CONF, + AD7091R_REG_CONF_ALERT_EN, BIT(4)); + if (ret) + return ret; + + ret = devm_request_threaded_irq(dev, irq, NULL, + ad7091r_event_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + st->chip_info->name, iio_dev); + if (ret) + return ret; + } else { + st->chip_info = init_info->info_no_irq; + } + + iio_dev->name = st->chip_info->name; + iio_dev->num_channels = st->chip_info->num_channels; + iio_dev->channels = st->chip_info->channels; + + st->vref = devm_regulator_get_optional(dev, "vref"); + if (IS_ERR(st->vref)) { + if (PTR_ERR(st->vref) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + st->vref = NULL; + /* Enable internal vref */ + ret = regmap_set_bits(st->map, AD7091R_REG_CONF, + AD7091R_REG_CONF_INT_VREF); + if (ret) + return dev_err_probe(st->dev, ret, + "Error on enable internal reference\n"); + } else { + ret = regulator_enable(st->vref); + if (ret) + return ret; + ret = devm_add_action_or_reset(dev, ad7091r_remove, st); + if (ret) + return ret; + } + + /* Use command mode by default to convert only desired channels*/ + ret = st->chip_info->set_mode(st, AD7091R_MODE_COMMAND); + if (ret) + return ret; + + return devm_iio_device_register(dev, iio_dev); +} +EXPORT_SYMBOL_NS_GPL(ad7091r_probe, "IIO_AD7091R"); + +bool ad7091r_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AD7091R_REG_RESULT: + case AD7091R_REG_ALERT: + return false; + default: + return true; + } +} +EXPORT_SYMBOL_NS_GPL(ad7091r_writeable_reg, "IIO_AD7091R"); + +bool ad7091r_volatile_reg(struct device *dev, unsigned int reg) +{ + /* The volatile ad7091r registers are also the only RO ones. */ + return !ad7091r_writeable_reg(dev, reg); +} +EXPORT_SYMBOL_NS_GPL(ad7091r_volatile_reg, "IIO_AD7091R"); + +MODULE_AUTHOR("Beniamin Bia "); +MODULE_DESCRIPTION("Analog Devices AD7091Rx multi-channel converters"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ad7091r-base.h b/drivers/iio/adc/ad7091r-base.h new file mode 100644 index 000000000..092ddea0f --- /dev/null +++ b/drivers/iio/adc/ad7091r-base.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * AD7091RX Analog to Digital converter driver + * + * Copyright 2014-2019 Analog Devices Inc. + */ + +#ifndef __DRIVERS_IIO_ADC_AD7091R_BASE_H__ +#define __DRIVERS_IIO_ADC_AD7091R_BASE_H__ + +#include + +#define AD7091R_REG_RESULT 0 +#define AD7091R_REG_CHANNEL 1 +#define AD7091R_REG_CONF 2 +#define AD7091R_REG_ALERT 3 +#define AD7091R_REG_CH_LOW_LIMIT(ch) ((ch) * 3 + 4) +#define AD7091R_REG_CH_HIGH_LIMIT(ch) ((ch) * 3 + 5) +#define AD7091R_REG_CH_HYSTERESIS(ch) ((ch) * 3 + 6) + +/* AD7091R_REG_RESULT */ +#define AD7091R5_REG_RESULT_CH_ID(x) (((x) >> 13) & 0x3) +#define AD7091R8_REG_RESULT_CH_ID(x) (((x) >> 13) & 0x7) +#define AD7091R_REG_RESULT_CONV_RESULT(x) ((x) & 0xfff) + +/* AD7091R_REG_CONF */ +#define AD7091R_REG_CONF_INT_VREF BIT(0) +#define AD7091R_REG_CONF_ALERT_EN BIT(4) +#define AD7091R_REG_CONF_AUTO BIT(8) +#define AD7091R_REG_CONF_CMD BIT(10) + +#define AD7091R_REG_CONF_MODE_MASK \ + (AD7091R_REG_CONF_AUTO | AD7091R_REG_CONF_CMD) + +/* AD7091R_REG_CH_LIMIT */ +#define AD7091R_HIGH_LIMIT 0xFFF +#define AD7091R_LOW_LIMIT 0x0 + +#define AD7091R_CHANNEL(idx, bits, ev, num_ev) { \ + .type = IIO_VOLTAGE, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .indexed = 1, \ + .channel = idx, \ + .event_spec = ev, \ + .num_event_specs = num_ev, \ + .scan_type.storagebits = 16, \ + .scan_type.realbits = bits, \ +} + +struct device; +struct gpio_desc; + +enum ad7091r_mode { + AD7091R_MODE_SAMPLE, + AD7091R_MODE_COMMAND, + AD7091R_MODE_AUTOCYCLE, +}; + +struct ad7091r_state { + struct device *dev; + struct regmap *map; + struct gpio_desc *convst_gpio; + struct gpio_desc *reset_gpio; + struct regulator *vref; + const struct ad7091r_chip_info *chip_info; + enum ad7091r_mode mode; + struct mutex lock; /*lock to prevent concurrent reads */ + __be16 tx_buf __aligned(IIO_DMA_MINALIGN); + __be16 rx_buf; +}; + +struct ad7091r_chip_info { + const char *name; + unsigned int num_channels; + const struct iio_chan_spec *channels; + unsigned int vref_mV; + unsigned int (*reg_result_chan_id)(unsigned int val); + int (*set_mode)(struct ad7091r_state *st, enum ad7091r_mode mode); +}; + +struct ad7091r_init_info { + const struct ad7091r_chip_info *info_irq; + const struct ad7091r_chip_info *info_no_irq; + const struct regmap_config *regmap_config; + void (*init_adc_regmap)(struct ad7091r_state *st, + const struct regmap_config *regmap_conf); + int (*setup)(struct ad7091r_state *st); +}; + +extern const struct iio_event_spec ad7091r_events[3]; + +int ad7091r_probe(struct device *dev, const struct ad7091r_init_info *init_info, + int irq); + +bool ad7091r_volatile_reg(struct device *dev, unsigned int reg); +bool ad7091r_writeable_reg(struct device *dev, unsigned int reg); + +#endif /* __DRIVERS_IIO_ADC_AD7091R_BASE_H__ */ diff --git a/drivers/iio/adc/ad7091r5.c b/drivers/iio/adc/ad7091r5.c new file mode 100644 index 000000000..7bf7d538a --- /dev/null +++ b/drivers/iio/adc/ad7091r5.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AD7091R5 Analog to Digital converter driver + * + * Copyright 2014-2019 Analog Devices Inc. + */ + +#include +#include +#include +#include + +#include "ad7091r-base.h" + +static const struct iio_chan_spec ad7091r5_channels_irq[] = { + AD7091R_CHANNEL(0, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)), + AD7091R_CHANNEL(1, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)), + AD7091R_CHANNEL(2, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)), + AD7091R_CHANNEL(3, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)), +}; + +static const struct iio_chan_spec ad7091r5_channels_noirq[] = { + AD7091R_CHANNEL(0, 12, NULL, 0), + AD7091R_CHANNEL(1, 12, NULL, 0), + AD7091R_CHANNEL(2, 12, NULL, 0), + AD7091R_CHANNEL(3, 12, NULL, 0), +}; + +static int ad7091r5_set_mode(struct ad7091r_state *st, enum ad7091r_mode mode) +{ + int ret, conf; + + switch (mode) { + case AD7091R_MODE_SAMPLE: + conf = 0; + break; + case AD7091R_MODE_COMMAND: + conf = AD7091R_REG_CONF_CMD; + break; + case AD7091R_MODE_AUTOCYCLE: + conf = AD7091R_REG_CONF_AUTO; + break; + default: + return -EINVAL; + } + + ret = regmap_update_bits(st->map, AD7091R_REG_CONF, + AD7091R_REG_CONF_MODE_MASK, conf); + if (ret) + return ret; + + st->mode = mode; + + return 0; +} + +static unsigned int ad7091r5_reg_result_chan_id(unsigned int val) +{ + return AD7091R5_REG_RESULT_CH_ID(val); +} + +static const struct ad7091r_chip_info ad7091r5_chip_info_irq = { + .name = "ad7091r-5", + .channels = ad7091r5_channels_irq, + .num_channels = ARRAY_SIZE(ad7091r5_channels_irq), + .vref_mV = 2500, + .reg_result_chan_id = &ad7091r5_reg_result_chan_id, + .set_mode = &ad7091r5_set_mode, +}; + +static const struct ad7091r_chip_info ad7091r5_chip_info_noirq = { + .name = "ad7091r-5", + .channels = ad7091r5_channels_noirq, + .num_channels = ARRAY_SIZE(ad7091r5_channels_noirq), + .vref_mV = 2500, + .reg_result_chan_id = &ad7091r5_reg_result_chan_id, + .set_mode = &ad7091r5_set_mode, +}; + +static const struct regmap_config ad7091r_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .writeable_reg = ad7091r_writeable_reg, + .volatile_reg = ad7091r_volatile_reg, +}; + +static void ad7091r5_regmap_init(struct ad7091r_state *st, + const struct regmap_config *regmap_conf) +{ + struct i2c_client *i2c = container_of(st->dev, struct i2c_client, dev); + + st->map = devm_regmap_init_i2c(i2c, regmap_conf); +} + +static const struct ad7091r_init_info ad7091r5_init_info = { + .info_irq = &ad7091r5_chip_info_irq, + .info_no_irq = &ad7091r5_chip_info_noirq, + .regmap_config = &ad7091r_regmap_config, + .init_adc_regmap = &ad7091r5_regmap_init +}; + +static int ad7091r5_i2c_probe(struct i2c_client *i2c) +{ + return ad7091r_probe(&i2c->dev, &ad7091r5_init_info, i2c->irq); +} + +static const struct of_device_id ad7091r5_dt_ids[] = { + { .compatible = "adi,ad7091r5" }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7091r5_dt_ids); + +static const struct i2c_device_id ad7091r5_i2c_ids[] = { + { .name = "ad7091r5" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ad7091r5_i2c_ids); + +static struct i2c_driver ad7091r5_driver = { + .driver = { + .name = "ad7091r5", + .of_match_table = ad7091r5_dt_ids, + }, + .probe = ad7091r5_i2c_probe, + .id_table = ad7091r5_i2c_ids, +}; +module_i2c_driver(ad7091r5_driver); + +MODULE_AUTHOR("Beniamin Bia "); +MODULE_DESCRIPTION("Analog Devices AD7091R5 multi-channel ADC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_AD7091R"); diff --git a/drivers/iio/adc/ad7091r8.c b/drivers/iio/adc/ad7091r8.c new file mode 100644 index 000000000..465a8ba01 --- /dev/null +++ b/drivers/iio/adc/ad7091r8.c @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices AD7091R8 12-bit SAR ADC driver + * + * Copyright 2023 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include + +#include "ad7091r-base.h" + +#define AD7091R8_REG_ADDR_MSK GENMASK(15, 11) +#define AD7091R8_RD_WR_FLAG_MSK BIT(10) +#define AD7091R8_REG_DATA_MSK GENMASK(9, 0) + +#define AD7091R_SPI_REGMAP_CONFIG(n) { \ + .reg_bits = 8, \ + .val_bits = 16, \ + .volatile_reg = ad7091r_volatile_reg, \ + .writeable_reg = ad7091r_writeable_reg, \ + .max_register = AD7091R_REG_CH_HYSTERESIS(n), \ +} + +static int ad7091r8_set_mode(struct ad7091r_state *st, enum ad7091r_mode mode) +{ + /* AD7091R-2/-4/-8 don't set sample/command/autocycle mode in conf reg */ + st->mode = mode; + return 0; +} + +static unsigned int ad7091r8_reg_result_chan_id(unsigned int val) +{ + return AD7091R8_REG_RESULT_CH_ID(val); +} + +#define AD7091R_SPI_CHIP_INFO(_n, _name) { \ + .name = _name, \ + .channels = ad7091r##_n##_channels, \ + .num_channels = ARRAY_SIZE(ad7091r##_n##_channels), \ + .vref_mV = 2500, \ + .reg_result_chan_id = &ad7091r8_reg_result_chan_id, \ + .set_mode = &ad7091r8_set_mode, \ +} + +#define AD7091R_SPI_CHIP_INFO_IRQ(_n, _name) { \ + .name = _name, \ + .channels = ad7091r##_n##_channels_irq, \ + .num_channels = ARRAY_SIZE(ad7091r##_n##_channels_irq), \ + .vref_mV = 2500, \ + .reg_result_chan_id = &ad7091r8_reg_result_chan_id, \ + .set_mode = &ad7091r8_set_mode, \ +} + +enum ad7091r8_info_ids { + AD7091R2_INFO, + AD7091R4_INFO, + AD7091R4_INFO_IRQ, + AD7091R8_INFO, + AD7091R8_INFO_IRQ, +}; + +static const struct iio_chan_spec ad7091r2_channels[] = { + AD7091R_CHANNEL(0, 12, NULL, 0), + AD7091R_CHANNEL(1, 12, NULL, 0), +}; + +static const struct iio_chan_spec ad7091r4_channels[] = { + AD7091R_CHANNEL(0, 12, NULL, 0), + AD7091R_CHANNEL(1, 12, NULL, 0), + AD7091R_CHANNEL(2, 12, NULL, 0), + AD7091R_CHANNEL(3, 12, NULL, 0), +}; + +static const struct iio_chan_spec ad7091r4_channels_irq[] = { + AD7091R_CHANNEL(0, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)), + AD7091R_CHANNEL(1, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)), + AD7091R_CHANNEL(2, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)), + AD7091R_CHANNEL(3, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)), +}; + +static const struct iio_chan_spec ad7091r8_channels[] = { + AD7091R_CHANNEL(0, 12, NULL, 0), + AD7091R_CHANNEL(1, 12, NULL, 0), + AD7091R_CHANNEL(2, 12, NULL, 0), + AD7091R_CHANNEL(3, 12, NULL, 0), + AD7091R_CHANNEL(4, 12, NULL, 0), + AD7091R_CHANNEL(5, 12, NULL, 0), + AD7091R_CHANNEL(6, 12, NULL, 0), + AD7091R_CHANNEL(7, 12, NULL, 0), +}; + +static const struct iio_chan_spec ad7091r8_channels_irq[] = { + AD7091R_CHANNEL(0, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)), + AD7091R_CHANNEL(1, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)), + AD7091R_CHANNEL(2, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)), + AD7091R_CHANNEL(3, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)), + AD7091R_CHANNEL(4, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)), + AD7091R_CHANNEL(5, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)), + AD7091R_CHANNEL(6, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)), + AD7091R_CHANNEL(7, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)), +}; + +static void ad7091r_pulse_convst(struct ad7091r_state *st) +{ + gpiod_set_value_cansleep(st->convst_gpio, 1); + gpiod_set_value_cansleep(st->convst_gpio, 0); +} + +static int ad7091r_regmap_bus_reg_read(void *context, unsigned int reg, + unsigned int *val) +{ + struct ad7091r_state *st = context; + struct spi_device *spi = container_of(st->dev, struct spi_device, dev); + int ret; + + struct spi_transfer t[] = { + { + .tx_buf = &st->tx_buf, + .len = 2, + .cs_change = 1, + }, { + .rx_buf = &st->rx_buf, + .len = 2, + } + }; + + if (reg == AD7091R_REG_RESULT) + ad7091r_pulse_convst(st); + + st->tx_buf = cpu_to_be16(reg << 11); + + ret = spi_sync_transfer(spi, t, ARRAY_SIZE(t)); + if (ret < 0) + return ret; + + *val = be16_to_cpu(st->rx_buf); + return 0; +} + +static int ad7091r_regmap_bus_reg_write(void *context, unsigned int reg, + unsigned int val) +{ + struct ad7091r_state *st = context; + struct spi_device *spi = container_of(st->dev, struct spi_device, dev); + + /* + * AD7091R-2/-4/-8 protocol (datasheet page 31) is to do a single SPI + * transfer with reg address set in bits B15:B11 and value set in B9:B0. + */ + st->tx_buf = cpu_to_be16(FIELD_PREP(AD7091R8_REG_DATA_MSK, val) | + FIELD_PREP(AD7091R8_RD_WR_FLAG_MSK, 1) | + FIELD_PREP(AD7091R8_REG_ADDR_MSK, reg)); + + return spi_write(spi, &st->tx_buf, 2); +} + +static const struct regmap_bus ad7091r8_regmap_bus = { + .reg_read = ad7091r_regmap_bus_reg_read, + .reg_write = ad7091r_regmap_bus_reg_write, + .reg_format_endian_default = REGMAP_ENDIAN_BIG, + .val_format_endian_default = REGMAP_ENDIAN_BIG, +}; + +static const struct ad7091r_chip_info ad7091r8_infos[] = { + [AD7091R2_INFO] = AD7091R_SPI_CHIP_INFO(2, "ad7091r-2"), + [AD7091R4_INFO] = AD7091R_SPI_CHIP_INFO(4, "ad7091r-4"), + [AD7091R4_INFO_IRQ] = AD7091R_SPI_CHIP_INFO_IRQ(4, "ad7091r-4"), + [AD7091R8_INFO] = AD7091R_SPI_CHIP_INFO(8, "ad7091r-8"), + [AD7091R8_INFO_IRQ] = AD7091R_SPI_CHIP_INFO_IRQ(8, "ad7091r-8") +}; + +static const struct regmap_config ad7091r2_reg_conf = AD7091R_SPI_REGMAP_CONFIG(2); +static const struct regmap_config ad7091r4_reg_conf = AD7091R_SPI_REGMAP_CONFIG(4); +static const struct regmap_config ad7091r8_reg_conf = AD7091R_SPI_REGMAP_CONFIG(8); + +static void ad7091r8_regmap_init(struct ad7091r_state *st, + const struct regmap_config *regmap_conf) +{ + st->map = devm_regmap_init(st->dev, &ad7091r8_regmap_bus, st, + regmap_conf); +} + +static int ad7091r8_gpio_setup(struct ad7091r_state *st) +{ + st->convst_gpio = devm_gpiod_get(st->dev, "convst", GPIOD_OUT_LOW); + if (IS_ERR(st->convst_gpio)) + return dev_err_probe(st->dev, PTR_ERR(st->convst_gpio), + "Error getting convst GPIO\n"); + + st->reset_gpio = devm_gpiod_get_optional(st->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(st->reset_gpio)) + return dev_err_probe(st->dev, PTR_ERR(st->reset_gpio), + "Error on requesting reset GPIO\n"); + + if (st->reset_gpio) { + fsleep(20); + gpiod_set_value_cansleep(st->reset_gpio, 0); + } + + return 0; +} + +static const struct ad7091r_init_info ad7091r2_init_info = { + .info_no_irq = &ad7091r8_infos[AD7091R2_INFO], + .regmap_config = &ad7091r2_reg_conf, + .init_adc_regmap = &ad7091r8_regmap_init, + .setup = &ad7091r8_gpio_setup +}; + +static const struct ad7091r_init_info ad7091r4_init_info = { + .info_no_irq = &ad7091r8_infos[AD7091R4_INFO], + .info_irq = &ad7091r8_infos[AD7091R4_INFO_IRQ], + .regmap_config = &ad7091r4_reg_conf, + .init_adc_regmap = &ad7091r8_regmap_init, + .setup = &ad7091r8_gpio_setup +}; + +static const struct ad7091r_init_info ad7091r8_init_info = { + .info_no_irq = &ad7091r8_infos[AD7091R8_INFO], + .info_irq = &ad7091r8_infos[AD7091R8_INFO_IRQ], + .regmap_config = &ad7091r8_reg_conf, + .init_adc_regmap = &ad7091r8_regmap_init, + .setup = &ad7091r8_gpio_setup +}; + +static int ad7091r8_spi_probe(struct spi_device *spi) +{ + const struct ad7091r_init_info *init_info; + + init_info = spi_get_device_match_data(spi); + if (!init_info) + return -EINVAL; + + return ad7091r_probe(&spi->dev, init_info, spi->irq); +} + +static const struct of_device_id ad7091r8_of_match[] = { + { .compatible = "adi,ad7091r2", .data = &ad7091r2_init_info }, + { .compatible = "adi,ad7091r4", .data = &ad7091r4_init_info }, + { .compatible = "adi,ad7091r8", .data = &ad7091r8_init_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7091r8_of_match); + +static const struct spi_device_id ad7091r8_spi_id[] = { + { .name = "ad7091r2", .driver_data = (kernel_ulong_t)&ad7091r2_init_info }, + { .name = "ad7091r4", .driver_data = (kernel_ulong_t)&ad7091r4_init_info }, + { .name = "ad7091r8", .driver_data = (kernel_ulong_t)&ad7091r8_init_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7091r8_spi_id); + +static struct spi_driver ad7091r8_driver = { + .driver = { + .name = "ad7091r8", + .of_match_table = ad7091r8_of_match, + }, + .probe = ad7091r8_spi_probe, + .id_table = ad7091r8_spi_id, +}; +module_spi_driver(ad7091r8_driver); + +MODULE_AUTHOR("Marcelo Schmitt "); +MODULE_DESCRIPTION("Analog Devices AD7091R8 ADC driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_AD7091R"); diff --git a/drivers/iio/adc/ad7124.c b/drivers/iio/adc/ad7124.c new file mode 100644 index 000000000..fc5dbc624 --- /dev/null +++ b/drivers/iio/adc/ad7124.c @@ -0,0 +1,1705 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * AD7124 SPI ADC driver + * + * Copyright 2018 Analog Devices Inc. + * Copyright 2025 BayLibre, SAS + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* AD7124 registers */ +#define AD7124_COMMS 0x00 +#define AD7124_STATUS 0x00 +#define AD7124_ADC_CONTROL 0x01 +#define AD7124_DATA 0x02 +#define AD7124_IO_CONTROL_1 0x03 +#define AD7124_IO_CONTROL_2 0x04 +#define AD7124_ID 0x05 +#define AD7124_ERROR 0x06 +#define AD7124_ERROR_EN 0x07 +#define AD7124_MCLK_COUNT 0x08 +#define AD7124_CHANNEL(x) (0x09 + (x)) +#define AD7124_CONFIG(x) (0x19 + (x)) +#define AD7124_FILTER(x) (0x21 + (x)) +#define AD7124_OFFSET(x) (0x29 + (x)) +#define AD7124_GAIN(x) (0x31 + (x)) + +/* AD7124_STATUS */ +#define AD7124_STATUS_POR_FLAG BIT(4) + +/* AD7124_ADC_CONTROL */ +#define AD7124_ADC_CONTROL_CLK_SEL GENMASK(1, 0) +#define AD7124_ADC_CONTROL_CLK_SEL_INT 0 +#define AD7124_ADC_CONTROL_CLK_SEL_INT_OUT 1 +#define AD7124_ADC_CONTROL_CLK_SEL_EXT 2 +#define AD7124_ADC_CONTROL_CLK_SEL_EXT_DIV4 3 +#define AD7124_ADC_CONTROL_MODE GENMASK(5, 2) +#define AD7124_ADC_CONTROL_MODE_CONTINUOUS 0 +#define AD7124_ADC_CONTROL_MODE_SINGLE 1 +#define AD7124_ADC_CONTROL_MODE_STANDBY 2 +#define AD7124_ADC_CONTROL_MODE_POWERDOWN 3 +#define AD7124_ADC_CONTROL_MODE_IDLE 4 +#define AD7124_ADC_CONTROL_MODE_INT_OFFSET_CALIB 5 /* Internal Zero-Scale Calibration */ +#define AD7124_ADC_CONTROL_MODE_INT_GAIN_CALIB 6 /* Internal Full-Scale Calibration */ +#define AD7124_ADC_CONTROL_MODE_SYS_OFFSET_CALIB 7 /* System Zero-Scale Calibration */ +#define AD7124_ADC_CONTROL_MODE_SYS_GAIN_CALIB 8 /* System Full-Scale Calibration */ +#define AD7124_ADC_CONTROL_POWER_MODE GENMASK(7, 6) +#define AD7124_ADC_CONTROL_POWER_MODE_LOW 0 +#define AD7124_ADC_CONTROL_POWER_MODE_MID 1 +#define AD7124_ADC_CONTROL_POWER_MODE_FULL 2 +#define AD7124_ADC_CONTROL_REF_EN BIT(8) +#define AD7124_ADC_CONTROL_DATA_STATUS BIT(10) + +/* AD7124_ID */ +#define AD7124_ID_SILICON_REVISION GENMASK(3, 0) +#define AD7124_ID_DEVICE_ID GENMASK(7, 4) +#define AD7124_ID_DEVICE_ID_AD7124_4 0x0 +#define AD7124_ID_DEVICE_ID_AD7124_8 0x1 + +/* AD7124_CHANNEL_X */ +#define AD7124_CHANNEL_ENABLE BIT(15) +#define AD7124_CHANNEL_SETUP GENMASK(14, 12) +#define AD7124_CHANNEL_AINP GENMASK(9, 5) +#define AD7124_CHANNEL_AINM GENMASK(4, 0) +#define AD7124_CHANNEL_AINx_TEMPSENSOR 16 +#define AD7124_CHANNEL_AINx_AVSS 17 + +/* AD7124_CONFIG_X */ +#define AD7124_CONFIG_BIPOLAR BIT(11) +#define AD7124_CONFIG_IN_BUFF GENMASK(6, 5) +#define AD7124_CONFIG_AIN_BUFP BIT(6) +#define AD7124_CONFIG_AIN_BUFM BIT(5) +#define AD7124_CONFIG_REF_SEL GENMASK(4, 3) +#define AD7124_CONFIG_PGA GENMASK(2, 0) + +/* AD7124_FILTER_X */ +#define AD7124_FILTER_FILTER GENMASK(23, 21) +#define AD7124_FILTER_FILTER_SINC4 0 +#define AD7124_FILTER_FILTER_SINC3 2 +#define AD7124_FILTER_FILTER_SINC4_SINC1 4 +#define AD7124_FILTER_FILTER_SINC3_SINC1 5 +#define AD7124_FILTER_FILTER_SINC3_PF 7 +#define AD7124_FILTER_REJ60 BIT(20) +#define AD7124_FILTER_POST_FILTER GENMASK(19, 17) +#define AD7124_FILTER_POST_FILTER_47dB 2 +#define AD7124_FILTER_POST_FILTER_62dB 3 +#define AD7124_FILTER_POST_FILTER_86dB 5 +#define AD7124_FILTER_POST_FILTER_92dB 6 +#define AD7124_FILTER_SINGLE_CYCLE BIT(16) +#define AD7124_FILTER_FS GENMASK(10, 0) + +#define AD7124_CFG_SLOT_UNASSIGNED ~0U + +#define AD7124_MAX_CONFIGS 8 +#define AD7124_MAX_CHANNELS 16 + +#define AD7124_INT_CLK_HZ 614400 + +/* AD7124 input sources */ + +enum ad7124_ref_sel { + AD7124_REFIN1, + AD7124_REFIN2, + AD7124_INT_REF, + AD7124_AVDD_REF, +}; + +enum ad7124_power_mode { + AD7124_LOW_POWER, + AD7124_MID_POWER, + AD7124_FULL_POWER, +}; + +static const unsigned int ad7124_gain[8] = { + 1, 2, 4, 8, 16, 32, 64, 128 +}; + +static const unsigned int ad7124_reg_size[] = { + 1, 2, 3, 3, 2, 1, 3, 3, 1, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3 +}; + +static const int ad7124_master_clk_freq_hz[3] = { + [AD7124_LOW_POWER] = AD7124_INT_CLK_HZ / 8, + [AD7124_MID_POWER] = AD7124_INT_CLK_HZ / 4, + [AD7124_FULL_POWER] = AD7124_INT_CLK_HZ, +}; + +static const char * const ad7124_ref_names[] = { + [AD7124_REFIN1] = "refin1", + [AD7124_REFIN2] = "refin2", + [AD7124_INT_REF] = "int", + [AD7124_AVDD_REF] = "avdd", +}; + +struct ad7124_chip_info { + const char *name; + unsigned int chip_id; + unsigned int num_inputs; +}; + +enum ad7124_filter_type { + AD7124_FILTER_TYPE_SINC3, + AD7124_FILTER_TYPE_SINC3_PF1, + AD7124_FILTER_TYPE_SINC3_PF2, + AD7124_FILTER_TYPE_SINC3_PF3, + AD7124_FILTER_TYPE_SINC3_PF4, + AD7124_FILTER_TYPE_SINC3_REJ60, + AD7124_FILTER_TYPE_SINC3_SINC1, + AD7124_FILTER_TYPE_SINC4, + AD7124_FILTER_TYPE_SINC4_REJ60, + AD7124_FILTER_TYPE_SINC4_SINC1, +}; + +struct ad7124_channel_config { + unsigned int cfg_slot; + unsigned int requested_odr; + unsigned int requested_odr_micro; + /* + * Following fields are used to compare for equality. If you + * make adaptations in it, you most likely also have to adapt + * ad7124_config_equal(), too. + */ + struct_group(config_props, + enum ad7124_ref_sel refsel; + bool bipolar; + bool buf_positive; + bool buf_negative; + unsigned int vref_mv; + unsigned int pga_bits; + unsigned int odr_sel_bits; + enum ad7124_filter_type filter_type; + unsigned int calibration_offset; + unsigned int calibration_gain; + ); +}; + +struct ad7124_channel { + struct ad7124_channel_config cfg; + unsigned int ain; + unsigned int slot; + u8 syscalib_mode; +}; + +struct ad7124_state { + const struct ad7124_chip_info *chip_info; + struct ad_sigma_delta sd; + struct ad7124_channel *channels; + struct regulator *vref[4]; + u32 clk_hz; + unsigned int adc_control; + unsigned int num_channels; + struct mutex cfgs_lock; /* lock for configs access */ + u8 cfg_slot_use_count[AD7124_MAX_CONFIGS]; + + /* + * Stores the power-on reset value for the GAIN(x) registers which are + * needed for measurements at gain 1 (i.e. CONFIG(x).PGA == 0) + */ + unsigned int gain_default; + bool enable_single_cycle; +}; + +static const struct ad7124_chip_info ad7124_4_chip_info = { + .name = "ad7124-4", + .chip_id = AD7124_ID_DEVICE_ID_AD7124_4, + .num_inputs = 8, +}; + +static const struct ad7124_chip_info ad7124_8_chip_info = { + .name = "ad7124-8", + .chip_id = AD7124_ID_DEVICE_ID_AD7124_8, + .num_inputs = 16, +}; + +static int ad7124_find_closest_match(const int *array, + unsigned int size, int val) +{ + int i, idx; + unsigned int diff_new, diff_old; + + diff_old = U32_MAX; + idx = 0; + + for (i = 0; i < size; i++) { + diff_new = abs(val - array[i]); + if (diff_new < diff_old) { + diff_old = diff_new; + idx = i; + } + } + + return idx; +} + +static int ad7124_spi_write_mask(struct ad7124_state *st, + unsigned int addr, + unsigned long mask, + unsigned int val, + unsigned int bytes) +{ + unsigned int readval; + int ret; + + ret = ad_sd_read_reg(&st->sd, addr, bytes, &readval); + if (ret < 0) + return ret; + + readval &= ~mask; + readval |= val; + + return ad_sd_write_reg(&st->sd, addr, bytes, readval); +} + +static int ad7124_set_mode(struct ad_sigma_delta *sd, + enum ad_sigma_delta_mode mode) +{ + struct ad7124_state *st = container_of(sd, struct ad7124_state, sd); + + st->adc_control &= ~AD7124_ADC_CONTROL_MODE; + st->adc_control |= FIELD_PREP(AD7124_ADC_CONTROL_MODE, mode); + + return ad_sd_write_reg(&st->sd, AD7124_ADC_CONTROL, 2, st->adc_control); +} + +static u32 ad7124_get_fclk_hz(struct ad7124_state *st) +{ + enum ad7124_power_mode power_mode; + u32 fclk_hz; + + power_mode = FIELD_GET(AD7124_ADC_CONTROL_POWER_MODE, st->adc_control); + fclk_hz = st->clk_hz; + + switch (power_mode) { + case AD7124_LOW_POWER: + fclk_hz /= 8; + break; + case AD7124_MID_POWER: + fclk_hz /= 4; + break; + default: + break; + } + + return fclk_hz; +} + +static u32 ad7124_get_fs_factor(struct ad7124_state *st, unsigned int channel) +{ + enum ad7124_power_mode power_mode = + FIELD_GET(AD7124_ADC_CONTROL_POWER_MODE, st->adc_control); + u32 avg = power_mode == AD7124_LOW_POWER ? 8 : 16; + + /* + * These are the "zero-latency" factors from the data sheet. For the + * sinc1 filters, these aren't documented, but derived by taking the + * single-channel formula from the sinc1 section of the data sheet and + * multiplying that by the sinc3/4 factor from the corresponding zero- + * latency sections. + */ + switch (st->channels[channel].cfg.filter_type) { + case AD7124_FILTER_TYPE_SINC4: + case AD7124_FILTER_TYPE_SINC4_REJ60: + return 4 * 32; + case AD7124_FILTER_TYPE_SINC4_SINC1: + return 4 * avg * 32; + case AD7124_FILTER_TYPE_SINC3_SINC1: + return 3 * avg * 32; + default: + return 3 * 32; + } +} + +static u32 ad7124_get_fadc_divisor(struct ad7124_state *st, unsigned int channel) +{ + u32 factor = ad7124_get_fs_factor(st, channel); + + /* + * The output data rate (f_ADC) is f_CLK / divisor. We are returning + * the divisor. + */ + return st->channels[channel].cfg.odr_sel_bits * factor; +} + +static void ad7124_set_channel_odr(struct ad7124_state *st, unsigned int channel) +{ + struct ad7124_channel_config *cfg = &st->channels[channel].cfg; + unsigned int fclk, factor, divisor, odr_sel_bits; + + fclk = ad7124_get_fclk_hz(st); + factor = ad7124_get_fs_factor(st, channel); + + /* + * FS[10:0] = fCLK / (fADC x 32 * N) where: + * fADC is the output data rate + * fCLK is the master clock frequency + * N is number of conversions per sample (depends on filter type) + * FS[10:0] are the bits in the filter register + * FS[10:0] can have a value from 1 to 2047 + */ + divisor = cfg->requested_odr * factor + + cfg->requested_odr_micro * factor / MICRO; + odr_sel_bits = clamp(DIV_ROUND_CLOSEST(fclk, divisor), 1, 2047); + + st->channels[channel].cfg.odr_sel_bits = odr_sel_bits; +} + +static int ad7124_get_3db_filter_factor(struct ad7124_state *st, + unsigned int channel) +{ + struct ad7124_channel_config *cfg = &st->channels[channel].cfg; + + /* + * 3dB point is the f_CLK rate times some factor. This functions returns + * the factor times 1000. + */ + switch (cfg->filter_type) { + case AD7124_FILTER_TYPE_SINC3: + case AD7124_FILTER_TYPE_SINC3_REJ60: + case AD7124_FILTER_TYPE_SINC3_SINC1: + return 272; + case AD7124_FILTER_TYPE_SINC4: + case AD7124_FILTER_TYPE_SINC4_REJ60: + case AD7124_FILTER_TYPE_SINC4_SINC1: + return 230; + case AD7124_FILTER_TYPE_SINC3_PF1: + return 633; + case AD7124_FILTER_TYPE_SINC3_PF2: + return 605; + case AD7124_FILTER_TYPE_SINC3_PF3: + return 669; + case AD7124_FILTER_TYPE_SINC3_PF4: + return 759; + default: + return -EINVAL; + } +} + +/* Only called during probe, so dev_err_probe() can be used */ +static int ad7124_init_config_vref(struct ad7124_state *st, struct ad7124_channel_config *cfg) +{ + struct device *dev = &st->sd.spi->dev; + unsigned int refsel = cfg->refsel; + + switch (refsel) { + case AD7124_REFIN1: + case AD7124_REFIN2: + case AD7124_AVDD_REF: + if (IS_ERR(st->vref[refsel])) + return dev_err_probe(dev, PTR_ERR(st->vref[refsel]), + "Error, trying to use external voltage reference without a %s regulator.\n", + ad7124_ref_names[refsel]); + + cfg->vref_mv = regulator_get_voltage(st->vref[refsel]); + /* Conversion from uV to mV */ + cfg->vref_mv /= 1000; + return 0; + case AD7124_INT_REF: + cfg->vref_mv = 2500; + st->adc_control |= AD7124_ADC_CONTROL_REF_EN; + return 0; + default: + return dev_err_probe(dev, -EINVAL, "Invalid reference %d\n", refsel); + } +} + +static bool ad7124_config_equal(struct ad7124_channel_config *a, + struct ad7124_channel_config *b) +{ + return a->refsel == b->refsel && + a->bipolar == b->bipolar && + a->buf_positive == b->buf_positive && + a->buf_negative == b->buf_negative && + a->vref_mv == b->vref_mv && + a->pga_bits == b->pga_bits && + a->odr_sel_bits == b->odr_sel_bits && + a->filter_type == b->filter_type && + a->calibration_offset == b->calibration_offset && + a->calibration_gain == b->calibration_gain; +} + +static int ad7124_write_config(struct ad7124_state *st, struct ad7124_channel_config *cfg, + unsigned int cfg_slot) +{ + unsigned int val, filter; + unsigned int rej60 = 0; + unsigned int post = 0; + int ret; + + ret = ad_sd_write_reg(&st->sd, AD7124_OFFSET(cfg_slot), 3, + cfg->calibration_offset); + if (ret) + return ret; + + ret = ad_sd_write_reg(&st->sd, AD7124_GAIN(cfg_slot), 3, + cfg->calibration_gain); + if (ret) + return ret; + + val = FIELD_PREP(AD7124_CONFIG_BIPOLAR, cfg->bipolar) | + FIELD_PREP(AD7124_CONFIG_REF_SEL, cfg->refsel) | + (cfg->buf_positive ? AD7124_CONFIG_AIN_BUFP : 0) | + (cfg->buf_negative ? AD7124_CONFIG_AIN_BUFM : 0) | + FIELD_PREP(AD7124_CONFIG_PGA, cfg->pga_bits); + + ret = ad_sd_write_reg(&st->sd, AD7124_CONFIG(cfg_slot), 2, val); + if (ret < 0) + return ret; + + switch (cfg->filter_type) { + case AD7124_FILTER_TYPE_SINC3: + filter = AD7124_FILTER_FILTER_SINC3; + break; + case AD7124_FILTER_TYPE_SINC3_PF1: + filter = AD7124_FILTER_FILTER_SINC3_PF; + post = AD7124_FILTER_POST_FILTER_47dB; + break; + case AD7124_FILTER_TYPE_SINC3_PF2: + filter = AD7124_FILTER_FILTER_SINC3_PF; + post = AD7124_FILTER_POST_FILTER_62dB; + break; + case AD7124_FILTER_TYPE_SINC3_PF3: + filter = AD7124_FILTER_FILTER_SINC3_PF; + post = AD7124_FILTER_POST_FILTER_86dB; + break; + case AD7124_FILTER_TYPE_SINC3_PF4: + filter = AD7124_FILTER_FILTER_SINC3_PF; + post = AD7124_FILTER_POST_FILTER_92dB; + break; + case AD7124_FILTER_TYPE_SINC3_REJ60: + filter = AD7124_FILTER_FILTER_SINC3; + rej60 = 1; + break; + case AD7124_FILTER_TYPE_SINC3_SINC1: + filter = AD7124_FILTER_FILTER_SINC3_SINC1; + break; + case AD7124_FILTER_TYPE_SINC4: + filter = AD7124_FILTER_FILTER_SINC4; + break; + case AD7124_FILTER_TYPE_SINC4_REJ60: + filter = AD7124_FILTER_FILTER_SINC4; + rej60 = 1; + break; + case AD7124_FILTER_TYPE_SINC4_SINC1: + filter = AD7124_FILTER_FILTER_SINC4_SINC1; + break; + default: + return -EINVAL; + } + + /* + * NB: AD7124_FILTER_SINGLE_CYCLE is always set so that we get the same + * sampling frequency even when only one channel is enabled in a + * buffered read. If it was not set, the N in ad7124_set_channel_odr() + * would be 1 and we would get a faster sampling frequency than what + * was requested. It may only be disabled through debugfs for testing + * purposes. + */ + return ad_sd_write_reg(&st->sd, AD7124_FILTER(cfg_slot), 3, + FIELD_PREP(AD7124_FILTER_FILTER, filter) | + FIELD_PREP(AD7124_FILTER_REJ60, rej60) | + FIELD_PREP(AD7124_FILTER_POST_FILTER, post) | + FIELD_PREP(AD7124_FILTER_SINGLE_CYCLE, + st->enable_single_cycle) | + FIELD_PREP(AD7124_FILTER_FS, cfg->odr_sel_bits)); +} + +/** + * ad7124_request_config_slot() - Request a config slot for a given config + * @st: Driver instance + * @channel: Channel to request a slot for + * + * Tries to find a matching config already in use, otherwise finds a free + * slot. If this function returns successfully, the use count for the slot is + * increased and the slot number is stored in cfg->cfg_slot. + * + * The slot must be released again with ad7124_release_config_slot() when no + * longer needed. + * + * Returns: 0 if a slot was successfully assigned, -EUSERS if no slot is + * available or other error if SPI communication fails. + */ +static int ad7124_request_config_slot(struct ad7124_state *st, u8 channel) +{ + unsigned int other, slot; + int last_used_slot = -1; + + /* Find another channel with a matching config, if any. */ + for (other = 0; other < st->num_channels; other++) { + if (other == channel) + continue; + + if (st->channels[other].cfg.cfg_slot == AD7124_CFG_SLOT_UNASSIGNED) + continue; + + last_used_slot = max_t(int, last_used_slot, + st->channels[other].cfg.cfg_slot); + + if (!ad7124_config_equal(&st->channels[other].cfg, + &st->channels[channel].cfg)) + continue; + + /* Found a match, re-use that slot. */ + slot = st->channels[other].cfg.cfg_slot; + st->cfg_slot_use_count[slot]++; + st->channels[channel].cfg.cfg_slot = slot; + + return 0; + } + + /* No match, use next free slot. */ + slot = last_used_slot + 1; + if (slot >= AD7124_MAX_CONFIGS) + return -EUSERS; + + st->cfg_slot_use_count[slot]++; + st->channels[channel].cfg.cfg_slot = slot; + + return ad7124_write_config(st, &st->channels[channel].cfg, slot); +} + +static void ad7124_release_config_slot(struct ad7124_state *st, u8 channel) +{ + unsigned int slot; + + /* + * All of these early return conditions can happen at probe when all + * channels are disabled. Otherwise, they should not happen normally. + */ + if (channel >= st->num_channels) + return; + + slot = st->channels[channel].cfg.cfg_slot; + + if (slot == AD7124_CFG_SLOT_UNASSIGNED || + st->cfg_slot_use_count[slot] == 0) + return; + + st->cfg_slot_use_count[slot]--; + st->channels[channel].cfg.cfg_slot = AD7124_CFG_SLOT_UNASSIGNED; +} + +static int ad7124_prepare_read(struct ad7124_state *st, int address) +{ + struct ad7124_channel_config *cfg = &st->channels[address].cfg; + int ret; + + ret = ad7124_request_config_slot(st, address); + if (ret) + return ret; + + /* point channel to the config slot and enable */ + return ad_sd_write_reg(&st->sd, AD7124_CHANNEL(address), 2, + st->channels[address].ain | + FIELD_PREP(AD7124_CHANNEL_SETUP, cfg->cfg_slot) | + AD7124_CHANNEL_ENABLE); +} + +static int ad7124_set_channel(struct ad_sigma_delta *sd, unsigned int channel) +{ + struct ad7124_state *st = container_of(sd, struct ad7124_state, sd); + int ret; + + mutex_lock(&st->cfgs_lock); + ret = ad7124_prepare_read(st, channel); + mutex_unlock(&st->cfgs_lock); + + return ret; +} + +static int ad7124_append_status(struct ad_sigma_delta *sd, bool append) +{ + struct ad7124_state *st = container_of(sd, struct ad7124_state, sd); + unsigned int adc_control = st->adc_control; + int ret; + + if (append) + adc_control |= AD7124_ADC_CONTROL_DATA_STATUS; + else + adc_control &= ~AD7124_ADC_CONTROL_DATA_STATUS; + + ret = ad_sd_write_reg(&st->sd, AD7124_ADC_CONTROL, 2, adc_control); + if (ret < 0) + return ret; + + st->adc_control = adc_control; + + return 0; +} + +static int ad7124_disable_one(struct ad_sigma_delta *sd, unsigned int chan) +{ + struct ad7124_state *st = container_of(sd, struct ad7124_state, sd); + + ad7124_release_config_slot(st, chan); + + /* The relevant thing here is that AD7124_CHANNEL_ENABLE is cleared. */ + return ad_sd_write_reg(&st->sd, AD7124_CHANNEL(chan), 2, 0); +} + +static int ad7124_disable_all(struct ad_sigma_delta *sd) +{ + int ret; + int i; + + for (i = 0; i < AD7124_MAX_CHANNELS; i++) { + ret = ad7124_disable_one(sd, i); + if (ret < 0) + return ret; + } + + return 0; +} + +static const struct ad_sigma_delta_info ad7124_sigma_delta_info = { + .set_channel = ad7124_set_channel, + .append_status = ad7124_append_status, + .disable_all = ad7124_disable_all, + .disable_one = ad7124_disable_one, + .set_mode = ad7124_set_mode, + .has_registers = true, + .addr_shift = 0, + .read_mask = BIT(6), + .status_ch_mask = GENMASK(3, 0), + .data_reg = AD7124_DATA, + .num_slots = 8, + .irq_flags = IRQF_TRIGGER_FALLING, + .num_resetclks = 64, +}; + +static const int ad7124_voltage_scales[][2] = { + { 0, 1164 }, + { 0, 2328 }, + { 0, 4656 }, + { 0, 9313 }, + { 0, 18626 }, + { 0, 37252 }, + { 0, 74505 }, + { 0, 149011 }, + { 0, 298023 }, +}; + +static int ad7124_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, long info) +{ + switch (info) { + case IIO_CHAN_INFO_SCALE: + *vals = (const int *)ad7124_voltage_scales; + *type = IIO_VAL_INT_PLUS_NANO; + *length = ARRAY_SIZE(ad7124_voltage_scales) * 2; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int ad7124_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad7124_state *st = iio_priv(indio_dev); + int idx, ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + ret = ad_sigma_delta_single_conversion(indio_dev, chan, val); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + mutex_lock(&st->cfgs_lock); + + idx = st->channels[chan->address].cfg.pga_bits; + *val = st->channels[chan->address].cfg.vref_mv; + if (st->channels[chan->address].cfg.bipolar) + *val2 = chan->scan_type.realbits - 1 + idx; + else + *val2 = chan->scan_type.realbits + idx; + + mutex_unlock(&st->cfgs_lock); + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_TEMP: + /* + * According to the data sheet + * Temperature (°C) + * = ((Conversion − 0x800000)/13584) − 272.5 + * = (Conversion − 0x800000 - 13584 * 272.5) / 13584 + * = (Conversion − 12090248) / 13584 + * So scale with 1000/13584 to yield °mC. Reduce by 8 to + * 125/1698. + */ + *val = 125; + *val2 = 1698; + return IIO_VAL_FRACTIONAL; + + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_VOLTAGE: + mutex_lock(&st->cfgs_lock); + if (st->channels[chan->address].cfg.bipolar) + *val = -(1 << (chan->scan_type.realbits - 1)); + else + *val = 0; + + mutex_unlock(&st->cfgs_lock); + return IIO_VAL_INT; + + case IIO_TEMP: + /* see calculation above */ + *val = -12090248; + return IIO_VAL_INT; + + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_SAMP_FREQ: { + struct ad7124_channel_config *cfg = &st->channels[chan->address].cfg; + + guard(mutex)(&st->cfgs_lock); + + switch (cfg->filter_type) { + case AD7124_FILTER_TYPE_SINC3: + case AD7124_FILTER_TYPE_SINC3_REJ60: + case AD7124_FILTER_TYPE_SINC3_SINC1: + case AD7124_FILTER_TYPE_SINC4: + case AD7124_FILTER_TYPE_SINC4_REJ60: + case AD7124_FILTER_TYPE_SINC4_SINC1: + *val = ad7124_get_fclk_hz(st); + *val2 = ad7124_get_fadc_divisor(st, chan->address); + return IIO_VAL_FRACTIONAL; + /* + * Post filters force the chip to a fixed rate. These are the + * single-channel rates from the data sheet divided by 3 for + * the multi-channel case (data sheet doesn't explicitly state + * this but confirmed through testing). + */ + case AD7124_FILTER_TYPE_SINC3_PF1: + *val = 300; + *val2 = 33; + return IIO_VAL_FRACTIONAL; + case AD7124_FILTER_TYPE_SINC3_PF2: + *val = 25; + *val2 = 3; + return IIO_VAL_FRACTIONAL; + case AD7124_FILTER_TYPE_SINC3_PF3: + *val = 20; + *val2 = 3; + return IIO_VAL_FRACTIONAL; + case AD7124_FILTER_TYPE_SINC3_PF4: + *val = 50; + *val2 = 9; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + } + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: { + guard(mutex)(&st->cfgs_lock); + + ret = ad7124_get_3db_filter_factor(st, chan->address); + if (ret < 0) + return ret; + + /* 3dB point is the f_CLK rate times a fractional value */ + *val = ret * ad7124_get_fclk_hz(st); + *val2 = MILLI * ad7124_get_fadc_divisor(st, chan->address); + return IIO_VAL_FRACTIONAL; + } + default: + return -EINVAL; + } +} + +static int ad7124_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct ad7124_state *st = iio_priv(indio_dev); + struct ad7124_channel_config *cfg = &st->channels[chan->address].cfg; + unsigned int res, gain, full_scale, vref; + + guard(mutex)(&st->cfgs_lock); + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (val2 < 0 || val < 0 || (val2 == 0 && val == 0)) + return -EINVAL; + + cfg->requested_odr = val; + cfg->requested_odr_micro = val2; + ad7124_set_channel_odr(st, chan->address); + + return 0; + case IIO_CHAN_INFO_SCALE: + if (val != 0) + return -EINVAL; + + if (st->channels[chan->address].cfg.bipolar) + full_scale = 1 << (chan->scan_type.realbits - 1); + else + full_scale = 1 << chan->scan_type.realbits; + + vref = st->channels[chan->address].cfg.vref_mv * 1000000LL; + res = DIV_ROUND_CLOSEST(vref, full_scale); + gain = DIV_ROUND_CLOSEST(res, val2); + res = ad7124_find_closest_match(ad7124_gain, ARRAY_SIZE(ad7124_gain), gain); + + st->channels[chan->address].cfg.pga_bits = res; + return 0; + default: + return -EINVAL; + } +} + +static int ad7124_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct ad7124_state *st = iio_priv(indio_dev); + int ret; + + if (reg >= ARRAY_SIZE(ad7124_reg_size)) + return -EINVAL; + + if (readval) + ret = ad_sd_read_reg(&st->sd, reg, ad7124_reg_size[reg], + readval); + else + ret = ad_sd_write_reg(&st->sd, reg, ad7124_reg_size[reg], + writeval); + + return ret; +} + +static int ad7124_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ad7124_state *st = iio_priv(indio_dev); + bool bit_set; + int ret; + int i; + + guard(mutex)(&st->cfgs_lock); + + for (i = 0; i < st->num_channels; i++) { + bit_set = test_bit(i, scan_mask); + if (bit_set) + ret = ad7124_prepare_read(st, i); + else + ret = ad7124_spi_write_mask(st, AD7124_CHANNEL(i), AD7124_CHANNEL_ENABLE, + 0, 2); + if (ret < 0) + return ret; + } + + return 0; +} + +static const struct iio_info ad7124_info = { + .read_avail = ad7124_read_avail, + .read_raw = ad7124_read_raw, + .write_raw = ad7124_write_raw, + .debugfs_reg_access = &ad7124_reg_access, + .validate_trigger = ad_sd_validate_trigger, + .update_scan_mode = ad7124_update_scan_mode, +}; + +/* Only called during probe, so dev_err_probe() can be used */ +static int ad7124_soft_reset(struct ad7124_state *st) +{ + struct device *dev = &st->sd.spi->dev; + unsigned int readval, timeout; + int ret; + + ret = ad_sd_reset(&st->sd); + if (ret < 0) + return ret; + + fsleep(200); + timeout = 100; + do { + ret = ad_sd_read_reg(&st->sd, AD7124_STATUS, 1, &readval); + if (ret < 0) + return dev_err_probe(dev, ret, "Error reading status register\n"); + + if (!(readval & AD7124_STATUS_POR_FLAG)) + break; + + /* The AD7124 requires typically 2ms to power up and settle */ + usleep_range(100, 2000); + } while (--timeout); + + if (readval & AD7124_STATUS_POR_FLAG) + return dev_err_probe(dev, -EIO, "Soft reset failed\n"); + + ret = ad_sd_read_reg(&st->sd, AD7124_GAIN(0), 3, &st->gain_default); + if (ret < 0) + return dev_err_probe(dev, ret, "Error reading gain register\n"); + + dev_dbg(dev, "Reset value of GAIN register is 0x%x\n", st->gain_default); + + return 0; +} + +static int ad7124_check_chip_id(struct ad7124_state *st) +{ + struct device *dev = &st->sd.spi->dev; + unsigned int readval, chip_id, silicon_rev; + int ret; + + ret = ad_sd_read_reg(&st->sd, AD7124_ID, 1, &readval); + if (ret < 0) + return dev_err_probe(dev, ret, "Failure to read ID register\n"); + + chip_id = FIELD_GET(AD7124_ID_DEVICE_ID, readval); + silicon_rev = FIELD_GET(AD7124_ID_SILICON_REVISION, readval); + + if (chip_id != st->chip_info->chip_id) + return dev_err_probe(dev, -ENODEV, + "Chip ID mismatch: expected %u, got %u\n", + st->chip_info->chip_id, chip_id); + + if (silicon_rev == 0) + return dev_err_probe(dev, -ENODEV, + "Silicon revision empty. Chip may not be present\n"); + + return 0; +} + +enum { + AD7124_SYSCALIB_ZERO_SCALE, + AD7124_SYSCALIB_FULL_SCALE, +}; + +static int ad7124_syscalib_locked(struct ad7124_state *st, const struct iio_chan_spec *chan) +{ + struct device *dev = &st->sd.spi->dev; + struct ad7124_channel *ch = &st->channels[chan->address]; + int ret; + + if (ch->syscalib_mode == AD7124_SYSCALIB_ZERO_SCALE) { + ch->cfg.calibration_offset = 0x800000; + + ret = ad_sd_calibrate(&st->sd, AD7124_ADC_CONTROL_MODE_SYS_OFFSET_CALIB, + chan->address); + if (ret < 0) + return ret; + + /* + * Making the assumption that a single conversion will always + * use configuration slot 0 for the OFFSET/GAIN registers. + */ + ret = ad_sd_read_reg(&st->sd, AD7124_OFFSET(0), 3, + &ch->cfg.calibration_offset); + if (ret < 0) + return ret; + + dev_dbg(dev, "offset for channel %lu after zero-scale calibration: 0x%x\n", + chan->address, ch->cfg.calibration_offset); + } else { + ch->cfg.calibration_gain = st->gain_default; + + ret = ad_sd_calibrate(&st->sd, AD7124_ADC_CONTROL_MODE_SYS_GAIN_CALIB, + chan->address); + if (ret < 0) + return ret; + + ret = ad_sd_read_reg(&st->sd, AD7124_GAIN(0), 3, + &ch->cfg.calibration_gain); + if (ret < 0) + return ret; + + dev_dbg(dev, "gain for channel %lu after full-scale calibration: 0x%x\n", + chan->address, ch->cfg.calibration_gain); + } + + return 0; +} + +static ssize_t ad7124_write_syscalib(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ad7124_state *st = iio_priv(indio_dev); + bool sys_calib; + int ret; + + ret = kstrtobool(buf, &sys_calib); + if (ret) + return ret; + + if (!sys_calib) + return len; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad7124_syscalib_locked(st, chan); + + iio_device_release_direct(indio_dev); + + return ret ?: len; +} + +static const char * const ad7124_syscalib_modes[] = { + [AD7124_SYSCALIB_ZERO_SCALE] = "zero_scale", + [AD7124_SYSCALIB_FULL_SCALE] = "full_scale", +}; + +static int ad7124_set_syscalib_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct ad7124_state *st = iio_priv(indio_dev); + + st->channels[chan->address].syscalib_mode = mode; + + return 0; +} + +static int ad7124_get_syscalib_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad7124_state *st = iio_priv(indio_dev); + + return st->channels[chan->address].syscalib_mode; +} + +static const struct iio_enum ad7124_syscalib_mode_enum = { + .items = ad7124_syscalib_modes, + .num_items = ARRAY_SIZE(ad7124_syscalib_modes), + .set = ad7124_set_syscalib_mode, + .get = ad7124_get_syscalib_mode +}; + +static const char * const ad7124_filter_types[] = { + [AD7124_FILTER_TYPE_SINC3] = "sinc3", + [AD7124_FILTER_TYPE_SINC3_PF1] = "sinc3+pf1", + [AD7124_FILTER_TYPE_SINC3_PF2] = "sinc3+pf2", + [AD7124_FILTER_TYPE_SINC3_PF3] = "sinc3+pf3", + [AD7124_FILTER_TYPE_SINC3_PF4] = "sinc3+pf4", + [AD7124_FILTER_TYPE_SINC3_REJ60] = "sinc3+rej60", + [AD7124_FILTER_TYPE_SINC3_SINC1] = "sinc3+sinc1", + [AD7124_FILTER_TYPE_SINC4] = "sinc4", + [AD7124_FILTER_TYPE_SINC4_REJ60] = "sinc4+rej60", + [AD7124_FILTER_TYPE_SINC4_SINC1] = "sinc4+sinc1", +}; + +static int ad7124_set_filter_type_attr(struct iio_dev *dev, + const struct iio_chan_spec *chan, + unsigned int value) +{ + struct ad7124_state *st = iio_priv(dev); + struct ad7124_channel_config *cfg = &st->channels[chan->address].cfg; + + guard(mutex)(&st->cfgs_lock); + + cfg->filter_type = value; + ad7124_set_channel_odr(st, chan->address); + + return 0; +} + +static int ad7124_get_filter_type_attr(struct iio_dev *dev, + const struct iio_chan_spec *chan) +{ + struct ad7124_state *st = iio_priv(dev); + + guard(mutex)(&st->cfgs_lock); + + return st->channels[chan->address].cfg.filter_type; +} + +static const struct iio_enum ad7124_filter_type_enum = { + .items = ad7124_filter_types, + .num_items = ARRAY_SIZE(ad7124_filter_types), + .set = ad7124_set_filter_type_attr, + .get = ad7124_get_filter_type_attr, +}; + +static const struct iio_chan_spec_ext_info ad7124_calibsys_ext_info[] = { + { + .name = "sys_calibration", + .write = ad7124_write_syscalib, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("sys_calibration_mode", IIO_SEPARATE, + &ad7124_syscalib_mode_enum), + IIO_ENUM_AVAILABLE("sys_calibration_mode", IIO_SHARED_BY_TYPE, + &ad7124_syscalib_mode_enum), + IIO_ENUM("filter_type", IIO_SEPARATE, &ad7124_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_TYPE, + &ad7124_filter_type_enum), + { } +}; + +static const struct iio_chan_spec ad7124_channel_template = { + .type = IIO_VOLTAGE, + .indexed = 1, + .differential = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_BE, + }, + .ext_info = ad7124_calibsys_ext_info, +}; + +/* + * Input specifiers 8 - 15 are explicitly reserved for ad7124-4 + * while they are fine for ad7124-8. Values above 31 don't fit + * into the register field and so are invalid for sure. + */ +static bool ad7124_valid_input_select(unsigned int ain, const struct ad7124_chip_info *info) +{ + if (ain >= info->num_inputs && ain < 16) + return false; + + return ain <= FIELD_MAX(AD7124_CHANNEL_AINM); +} + +static int ad7124_parse_channel_config(struct iio_dev *indio_dev, + struct device *dev) +{ + struct ad7124_state *st = iio_priv(indio_dev); + struct ad7124_channel_config *cfg; + struct ad7124_channel *channels; + struct iio_chan_spec *chan; + unsigned int ain[2], channel = 0, tmp; + unsigned int num_channels; + int ret; + + num_channels = device_get_child_node_count(dev); + + /* + * The driver assigns each logical channel defined in the device tree + * statically one channel register. So only accept 16 such logical + * channels to not treat CONFIG_0 (i.e. the register following + * CHANNEL_15) as an additional channel register. The driver could be + * improved to lift this limitation. + */ + if (num_channels > AD7124_MAX_CHANNELS) + return dev_err_probe(dev, -EINVAL, "Too many channels defined\n"); + + /* Add one for temperature */ + st->num_channels = min(num_channels + 1, AD7124_MAX_CHANNELS); + + chan = devm_kcalloc(dev, st->num_channels, + sizeof(*chan), GFP_KERNEL); + if (!chan) + return -ENOMEM; + + channels = devm_kcalloc(dev, st->num_channels, sizeof(*channels), + GFP_KERNEL); + if (!channels) + return -ENOMEM; + + indio_dev->channels = chan; + indio_dev->num_channels = st->num_channels; + st->channels = channels; + + device_for_each_child_node_scoped(dev, child) { + ret = fwnode_property_read_u32(child, "reg", &channel); + if (ret) + return dev_err_probe(dev, ret, + "Failed to parse reg property of %pfwP\n", child); + + if (channel >= num_channels) + return dev_err_probe(dev, -EINVAL, + "Channel index >= number of channels in %pfwP\n", child); + + ret = fwnode_property_read_u32_array(child, "diff-channels", + ain, 2); + if (ret) + return dev_err_probe(dev, ret, + "Failed to parse diff-channels property of %pfwP\n", child); + + if (!ad7124_valid_input_select(ain[0], st->chip_info) || + !ad7124_valid_input_select(ain[1], st->chip_info)) + return dev_err_probe(dev, -EINVAL, + "diff-channels property of %pfwP contains invalid data\n", child); + + st->channels[channel].ain = FIELD_PREP(AD7124_CHANNEL_AINP, ain[0]) | + FIELD_PREP(AD7124_CHANNEL_AINM, ain[1]); + + cfg = &st->channels[channel].cfg; + cfg->bipolar = fwnode_property_read_bool(child, "bipolar"); + + ret = fwnode_property_read_u32(child, "adi,reference-select", &tmp); + if (ret) + cfg->refsel = AD7124_INT_REF; + else + cfg->refsel = tmp; + + cfg->buf_positive = + fwnode_property_read_bool(child, "adi,buffered-positive"); + cfg->buf_negative = + fwnode_property_read_bool(child, "adi,buffered-negative"); + + chan[channel] = ad7124_channel_template; + chan[channel].address = channel; + chan[channel].scan_index = channel; + chan[channel].channel = ain[0]; + chan[channel].channel2 = ain[1]; + } + + if (num_channels < AD7124_MAX_CHANNELS) { + st->channels[num_channels] = (struct ad7124_channel) { + .ain = FIELD_PREP(AD7124_CHANNEL_AINP, AD7124_CHANNEL_AINx_TEMPSENSOR) | + FIELD_PREP(AD7124_CHANNEL_AINM, AD7124_CHANNEL_AINx_AVSS), + .cfg = { + .bipolar = true, + }, + }; + + chan[num_channels] = (struct iio_chan_spec) { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_type = { + /* + * You might find it strange that a bipolar + * measurement yields an unsigned value, but + * this matches the device's manual. + */ + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_BE, + }, + .address = num_channels, + .scan_index = num_channels, + .ext_info = ad7124_calibsys_ext_info, + }; + } + + return 0; +} + +static int ad7124_setup(struct ad7124_state *st) +{ + struct device *dev = &st->sd.spi->dev; + unsigned int power_mode, clk_sel; + struct clk *mclk; + int i, ret; + + /* + * Always use full power mode for max performance. If needed, the driver + * could be adapted to use a dynamic power mode based on the requested + * output data rate. + */ + power_mode = AD7124_ADC_CONTROL_POWER_MODE_FULL; + + /* + * This "mclk" business is needed for backwards compatibility with old + * devicetrees that specified a fake clock named "mclk" to select the + * power mode. + */ + mclk = devm_clk_get_optional_enabled(dev, "mclk"); + if (IS_ERR(mclk)) + return dev_err_probe(dev, PTR_ERR(mclk), "Failed to get mclk\n"); + + if (mclk) { + unsigned long mclk_hz; + + mclk_hz = clk_get_rate(mclk); + if (!mclk_hz) + return dev_err_probe(dev, -EINVAL, + "Failed to get mclk rate\n"); + + /* + * This logic is a bit backwards, which is why it is only here + * for backwards compatibility. The driver should be able to set + * the power mode as it sees fit and the f_clk/mclk rate should + * be dynamic accordingly. But here, we are selecting a fixed + * power mode based on the given "mclk" rate. + */ + power_mode = ad7124_find_closest_match(ad7124_master_clk_freq_hz, + ARRAY_SIZE(ad7124_master_clk_freq_hz), mclk_hz); + + if (mclk_hz != ad7124_master_clk_freq_hz[power_mode]) { + ret = clk_set_rate(mclk, mclk_hz); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set mclk rate\n"); + } + + clk_sel = AD7124_ADC_CONTROL_CLK_SEL_INT; + st->clk_hz = AD7124_INT_CLK_HZ; + } else if (!device_property_present(dev, "clocks") && + device_property_present(dev, "#clock-cells")) { +#ifdef CONFIG_COMMON_CLK + struct clk_hw *clk_hw; + + const char *name __free(kfree) = kasprintf(GFP_KERNEL, "%pfwP-clk", + dev_fwnode(dev)); + if (!name) + return -ENOMEM; + + clk_hw = devm_clk_hw_register_fixed_rate(dev, name, NULL, 0, + AD7124_INT_CLK_HZ); + if (IS_ERR(clk_hw)) + return dev_err_probe(dev, PTR_ERR(clk_hw), + "Failed to register clock provider\n"); + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + clk_hw); + if (ret) + return dev_err_probe(dev, ret, + "Failed to add clock provider\n"); +#endif + + /* + * Treat the clock as always on. This way we don't have to deal + * with someone trying to enable/disable the clock while we are + * reading samples. + */ + clk_sel = AD7124_ADC_CONTROL_CLK_SEL_INT_OUT; + st->clk_hz = AD7124_INT_CLK_HZ; + } else { + struct clk *clk; + + clk = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), + "Failed to get external clock\n"); + + if (clk) { + unsigned long clk_hz; + + clk_hz = clk_get_rate(clk); + if (!clk_hz) + return dev_err_probe(dev, -EINVAL, + "Failed to get external clock rate\n"); + + /* + * The external clock may be 4x the nominal clock rate, + * in which case the ADC needs to be configured to + * divide it by 4. Using MEGA is a bit arbitrary, but + * the expected clock rates are either 614.4 kHz or + * 2.4576 MHz, so this should work. + */ + if (clk_hz > (1 * HZ_PER_MHZ)) { + clk_sel = AD7124_ADC_CONTROL_CLK_SEL_EXT_DIV4; + st->clk_hz = clk_hz / 4; + } else { + clk_sel = AD7124_ADC_CONTROL_CLK_SEL_EXT; + st->clk_hz = clk_hz; + } + } else { + clk_sel = AD7124_ADC_CONTROL_CLK_SEL_INT; + st->clk_hz = AD7124_INT_CLK_HZ; + } + } + + st->adc_control &= ~AD7124_ADC_CONTROL_CLK_SEL; + st->adc_control |= FIELD_PREP(AD7124_ADC_CONTROL_CLK_SEL, clk_sel); + + st->adc_control &= ~AD7124_ADC_CONTROL_POWER_MODE; + st->adc_control |= FIELD_PREP(AD7124_ADC_CONTROL_POWER_MODE, power_mode); + + st->adc_control &= ~AD7124_ADC_CONTROL_MODE; + st->adc_control |= FIELD_PREP(AD7124_ADC_CONTROL_MODE, AD_SD_MODE_IDLE); + + ret = devm_mutex_init(dev, &st->cfgs_lock); + if (ret) + return ret; + + for (i = 0; i < st->num_channels; i++) { + struct ad7124_channel_config *cfg = &st->channels[i].cfg; + + ret = ad7124_init_config_vref(st, cfg); + if (ret < 0) + return ret; + + cfg->cfg_slot = AD7124_CFG_SLOT_UNASSIGNED; + + /* Default filter type on the ADC after reset. */ + cfg->filter_type = AD7124_FILTER_TYPE_SINC4; + + /* + * 9.38 SPS is the minimum output data rate supported + * regardless of the selected power mode. Round it up to 10 and + * set all channels to this default value. + */ + cfg->requested_odr = 10; + ad7124_set_channel_odr(st, i); + } + + ad7124_disable_all(&st->sd); + + ret = ad_sd_write_reg(&st->sd, AD7124_ADC_CONTROL, 2, st->adc_control); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to setup CONTROL register\n"); + + return ret; +} + +static int __ad7124_calibrate_all(struct ad7124_state *st, struct iio_dev *indio_dev) +{ + struct device *dev = &st->sd.spi->dev; + int ret, i; + + for (i = 0; i < st->num_channels; i++) { + /* + * For calibration the OFFSET register should hold its reset default + * value. For the GAIN register there is no such requirement but + * for gain 1 it should hold the reset default value, too. So to + * simplify matters use the reset default value for both. + */ + st->channels[i].cfg.calibration_offset = 0x800000; + st->channels[i].cfg.calibration_gain = st->gain_default; + + /* + * Only the main voltage input channels are important enough + * to be automatically calibrated here. For everything else, + * just use the default values set above. + */ + if (indio_dev->channels[i].type != IIO_VOLTAGE) + continue; + + /* + * Full-scale calibration isn't supported at gain 1, so skip in + * that case. Note that untypically full-scale calibration has + * to happen before zero-scale calibration. This only applies to + * the internal calibration. For system calibration it's as + * usual: first zero-scale then full-scale calibration. + */ + if (st->channels[i].cfg.pga_bits > 0) { + ret = ad_sd_calibrate(&st->sd, AD7124_ADC_CONTROL_MODE_INT_GAIN_CALIB, i); + if (ret < 0) + return ret; + + /* + * read out the resulting value of GAIN + * after full-scale calibration because the next + * ad_sd_calibrate() call overwrites this via + * ad_sigma_delta_set_channel() -> ad7124_set_channel() + * -> ad7124_prepare_read(). + */ + ret = ad_sd_read_reg(&st->sd, AD7124_GAIN(0), 3, + &st->channels[i].cfg.calibration_gain); + if (ret < 0) + return ret; + } + + ret = ad_sd_calibrate(&st->sd, AD7124_ADC_CONTROL_MODE_INT_OFFSET_CALIB, i); + if (ret < 0) + return ret; + + /* + * Making the assumption that a single conversion will always + * use configuration slot 0 for the OFFSET/GAIN registers. + */ + ret = ad_sd_read_reg(&st->sd, AD7124_OFFSET(0), 3, + &st->channels[i].cfg.calibration_offset); + if (ret < 0) + return ret; + + dev_dbg(dev, "offset and gain for channel %d = 0x%x + 0x%x\n", i, + st->channels[i].cfg.calibration_offset, + st->channels[i].cfg.calibration_gain); + } + + return 0; +} + +static int ad7124_calibrate_all(struct ad7124_state *st, struct iio_dev *indio_dev) +{ + int ret; + unsigned int adc_control = st->adc_control; + + /* + * Calibration isn't supported at full power, so speed down a bit. + * Setting .adc_control is enough here because the control register is + * written as part of ad_sd_calibrate() -> ad_sigma_delta_set_mode(). + * The resulting calibration is then also valid for high-speed, so just + * restore adc_control afterwards. + */ + if (FIELD_GET(AD7124_ADC_CONTROL_POWER_MODE, adc_control) >= AD7124_FULL_POWER) { + st->adc_control &= ~AD7124_ADC_CONTROL_POWER_MODE; + st->adc_control |= FIELD_PREP(AD7124_ADC_CONTROL_POWER_MODE, AD7124_MID_POWER); + } + + ret = __ad7124_calibrate_all(st, indio_dev); + + st->adc_control = adc_control; + + return ret; +} + +static void ad7124_reg_disable(void *r) +{ + regulator_disable(r); +} + +static void ad7124_debugfs_init(struct iio_dev *indio_dev) +{ + struct dentry *dentry = iio_get_debugfs_dentry(indio_dev); + struct ad7124_state *st = iio_priv(indio_dev); + + if (!IS_ENABLED(CONFIG_DEBUG_FS)) + return; + + debugfs_create_bool("enable_single_cycle", 0644, dentry, + &st->enable_single_cycle); +} + +static int ad7124_probe(struct spi_device *spi) +{ + const struct ad7124_chip_info *info; + struct device *dev = &spi->dev; + struct ad7124_state *st; + struct iio_dev *indio_dev; + int i, ret; + + info = spi_get_device_match_data(spi); + if (!info) + return dev_err_probe(dev, -ENODEV, "Failed to get match data\n"); + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->chip_info = info; + + /* Only disabled for debug/testing purposes. */ + st->enable_single_cycle = true; + + indio_dev->name = st->chip_info->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ad7124_info; + + ret = ad_sd_init(&st->sd, indio_dev, spi, &ad7124_sigma_delta_info); + if (ret < 0) + return ret; + + ret = ad7124_parse_channel_config(indio_dev, &spi->dev); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(st->vref); i++) { + if (i == AD7124_INT_REF) + continue; + + st->vref[i] = devm_regulator_get_optional(&spi->dev, + ad7124_ref_names[i]); + if (PTR_ERR(st->vref[i]) == -ENODEV) + continue; + else if (IS_ERR(st->vref[i])) + return PTR_ERR(st->vref[i]); + + ret = regulator_enable(st->vref[i]); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulator #%d\n", i); + + ret = devm_add_action_or_reset(&spi->dev, ad7124_reg_disable, + st->vref[i]); + if (ret) + return ret; + } + + ret = ad7124_soft_reset(st); + if (ret < 0) + return ret; + + ret = ad7124_check_chip_id(st); + if (ret) + return ret; + + ret = ad7124_setup(st); + if (ret < 0) + return ret; + + ret = devm_ad_sd_setup_buffer_and_trigger(&spi->dev, indio_dev); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to setup triggers\n"); + + ret = ad7124_calibrate_all(st, indio_dev); + if (ret) + return ret; + + ret = devm_iio_device_register(&spi->dev, indio_dev); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to register iio device\n"); + + ad7124_debugfs_init(indio_dev); + + return 0; +} + +static const struct of_device_id ad7124_of_match[] = { + { .compatible = "adi,ad7124-4", .data = &ad7124_4_chip_info }, + { .compatible = "adi,ad7124-8", .data = &ad7124_8_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7124_of_match); + +static const struct spi_device_id ad71124_ids[] = { + { .name = "ad7124-4", .driver_data = (kernel_ulong_t)&ad7124_4_chip_info }, + { .name = "ad7124-8", .driver_data = (kernel_ulong_t)&ad7124_8_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad71124_ids); + +static struct spi_driver ad71124_driver = { + .driver = { + .name = "ad7124", + .of_match_table = ad7124_of_match, + }, + .probe = ad7124_probe, + .id_table = ad71124_ids, +}; +module_spi_driver(ad71124_driver); + +MODULE_AUTHOR("Stefan Popa "); +MODULE_DESCRIPTION("Analog Devices AD7124 SPI driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_AD_SIGMA_DELTA"); diff --git a/drivers/iio/adc/ad7173.c b/drivers/iio/adc/ad7173.c new file mode 100644 index 000000000..eb47175a0 --- /dev/null +++ b/drivers/iio/adc/ad7173.c @@ -0,0 +1,2099 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * AD717x and AD411x family SPI ADC driver + * + * Supported devices: + * AD4111/AD4112/AD4113/AD4114/AD4115/AD4116 + * AD7172-2/AD7172-4/AD7173-8/AD7175-2 + * AD7175-8/AD7176-2/AD7177-2 + * + * Copyright (C) 2015, 2024 Analog Devices, Inc. + * Copyright (C) 2025 BayLibre, SAS + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#define AD7173_REG_COMMS 0x00 +#define AD7173_REG_ADC_MODE 0x01 +#define AD7173_REG_INTERFACE_MODE 0x02 +#define AD7173_REG_CRC 0x03 +#define AD7173_REG_DATA 0x04 +#define AD7173_REG_GPIO 0x06 +#define AD7173_REG_ID 0x07 +#define AD7173_REG_CH(x) (0x10 + (x)) +#define AD7173_REG_SETUP(x) (0x20 + (x)) +#define AD7173_REG_FILTER(x) (0x28 + (x)) +#define AD7173_REG_OFFSET(x) (0x30 + (x)) +#define AD7173_REG_GAIN(x) (0x38 + (x)) + +#define AD7173_RESET_LENGTH BITS_TO_BYTES(64) + +#define AD7173_CH_ENABLE BIT(15) +#define AD7173_CH_SETUP_SEL_MASK GENMASK(14, 12) +#define AD7173_CH_SETUP_AINPOS_MASK GENMASK(9, 5) +#define AD7173_CH_SETUP_AINNEG_MASK GENMASK(4, 0) + +#define AD7173_NO_AINS_PER_CHANNEL 2 +#define AD7173_CH_ADDRESS(pos, neg) \ + (FIELD_PREP(AD7173_CH_SETUP_AINPOS_MASK, pos) | \ + FIELD_PREP(AD7173_CH_SETUP_AINNEG_MASK, neg)) +#define AD7173_AIN_TEMP_POS 17 +#define AD7173_AIN_TEMP_NEG 18 +#define AD7173_AIN_POW_MON_POS 19 +#define AD7173_AIN_POW_MON_NEG 20 +#define AD7173_AIN_REF_POS 21 +#define AD7173_AIN_REF_NEG 22 + +#define AD7173_IS_REF_INPUT(x) ((x) == AD7173_AIN_REF_POS || \ + (x) == AD7173_AIN_REF_NEG) + +#define AD7172_2_ID 0x00d0 +#define AD7176_ID 0x0c90 +#define AD7175_ID 0x0cd0 +#define AD7175_2_ID 0x0cd0 +#define AD7172_4_ID 0x2050 +#define AD7173_ID 0x30d0 +#define AD4111_ID AD7173_ID +#define AD4112_ID AD7173_ID +#define AD4114_ID AD7173_ID +#define AD4113_ID 0x31d0 +#define AD4116_ID 0x34d0 +#define AD4115_ID 0x38d0 +#define AD7175_8_ID 0x3cd0 +#define AD7177_ID 0x4fd0 +#define AD7173_ID_MASK GENMASK(15, 4) + +#define AD7173_ADC_MODE_REF_EN BIT(15) +#define AD7173_ADC_MODE_SING_CYC BIT(13) +#define AD7173_ADC_MODE_MODE_MASK GENMASK(6, 4) +#define AD7173_ADC_MODE_CLOCKSEL_MASK GENMASK(3, 2) +#define AD7173_ADC_MODE_CLOCKSEL_INT 0x0 +#define AD7173_ADC_MODE_CLOCKSEL_INT_OUTPUT 0x1 +#define AD7173_ADC_MODE_CLOCKSEL_EXT 0x2 +#define AD7173_ADC_MODE_CLOCKSEL_XTAL 0x3 + +#define AD7173_GPIO_PDSW BIT(14) +#define AD7173_GPIO_OP_EN2_3 BIT(13) +#define AD4111_GPIO_GP_OW_EN BIT(12) +#define AD7173_GPIO_MUX_IO BIT(12) +#define AD7173_GPIO_SYNC_EN BIT(11) +#define AD7173_GPIO_ERR_EN BIT(10) +#define AD7173_GPIO_ERR_DAT BIT(9) +#define AD7173_GPIO_GP_DATA3 BIT(7) +#define AD7173_GPIO_GP_DATA2 BIT(6) +#define AD7173_GPIO_IP_EN1 BIT(5) +#define AD7173_GPIO_IP_EN0 BIT(4) +#define AD7173_GPIO_OP_EN1 BIT(3) +#define AD7173_GPIO_OP_EN0 BIT(2) +#define AD7173_GPIO_GP_DATA1 BIT(1) +#define AD7173_GPIO_GP_DATA0 BIT(0) + +#define AD7173_GPO12_DATA(x) BIT((x) + 0) +#define AD7173_GPO23_DATA(x) BIT((x) + 4) +#define AD4111_GPO01_DATA(x) BIT((x) + 6) +#define AD7173_GPO_DATA(x) ((x) < 2 ? AD7173_GPO12_DATA(x) : AD7173_GPO23_DATA(x)) + +#define AD7173_INTERFACE_DATA_STAT BIT(6) +#define AD7173_INTERFACE_DATA_STAT_EN(x) \ + FIELD_PREP(AD7173_INTERFACE_DATA_STAT, x) + +#define AD7173_SETUP_BIPOLAR BIT(12) +#define AD7173_SETUP_AREF_BUF_MASK GENMASK(11, 10) +#define AD7173_SETUP_AIN_BUF_MASK GENMASK(9, 8) + +#define AD7173_SETUP_REF_SEL_MASK GENMASK(5, 4) +#define AD7173_SETUP_REF_SEL_AVDD1_AVSS 0x3 +#define AD7173_SETUP_REF_SEL_INT_REF 0x2 +#define AD7173_SETUP_REF_SEL_EXT_REF2 0x1 +#define AD7173_SETUP_REF_SEL_EXT_REF 0x0 +#define AD7173_VOLTAGE_INT_REF_uV 2500000 +#define AD7173_TEMP_SENSIIVITY_uV_per_C 477 +#define AD7177_ODR_START_VALUE 0x07 +#define AD4111_SHUNT_RESISTOR_OHM 50 +#define AD4111_DIVIDER_RATIO 10 +#define AD4111_CURRENT_CHAN_CUTOFF 16 +#define AD4111_VINCOM_INPUT 0x10 + +/* pin < num_voltage_in is a normal voltage input */ +/* pin >= num_voltage_in_div is a voltage input without a divider */ +#define AD4111_IS_VINCOM_MISMATCH(pin1, pin2) ((pin1) == AD4111_VINCOM_INPUT && \ + (pin2) < st->info->num_voltage_in && \ + (pin2) >= st->info->num_voltage_in_div) + +#define AD7173_FILTER_SINC3_MAP BIT(15) +#define AD7173_FILTER_SINC3_MAP_DIV GENMASK(14, 0) +#define AD7173_FILTER_ENHFILTEN BIT(11) +#define AD7173_FILTER_ENHFILT_MASK GENMASK(10, 8) +#define AD7173_FILTER_ORDER BIT(6) +#define AD7173_FILTER_ODR_MASK GENMASK(5, 0) +#define AD7173_MAX_CONFIGS 8 +#define AD4111_OW_DET_THRSH_MV 300 + +#define AD7173_MODE_CAL_INT_ZERO 0x4 /* Internal Zero-Scale Calibration */ +#define AD7173_MODE_CAL_INT_FULL 0x5 /* Internal Full-Scale Calibration */ +#define AD7173_MODE_CAL_SYS_ZERO 0x6 /* System Zero-Scale Calibration */ +#define AD7173_MODE_CAL_SYS_FULL 0x7 /* System Full-Scale Calibration */ + +struct ad7173_device_info { + const unsigned int *sinc5_data_rates; + unsigned int num_sinc5_data_rates; + unsigned int odr_start_value; + /* + * AD4116 has both inputs with a voltage divider and without. + * These inputs cannot be mixed in the channel configuration. + * Does not include the VINCOM input. + */ + unsigned int num_voltage_in_div; + unsigned int num_channels; + unsigned int num_configs; + unsigned int num_voltage_in; + unsigned int clock; + unsigned int id; + char *name; + const struct ad_sigma_delta_info *sd_info; + bool has_current_inputs; + bool has_vincom_input; + bool has_temp; + /* ((AVDD1 − AVSS)/5) */ + bool has_pow_supply_monitoring; + bool data_reg_only_16bit; + bool has_input_buf; + bool has_int_ref; + bool has_ref2; + bool has_internal_fs_calibration; + bool has_openwire_det; + bool higher_gpio_bits; + u8 num_gpios; +}; + +enum ad7173_filter_type { + AD7173_FILTER_SINC3, + AD7173_FILTER_SINC5_SINC1, + AD7173_FILTER_SINC5_SINC1_PF1, + AD7173_FILTER_SINC5_SINC1_PF2, + AD7173_FILTER_SINC5_SINC1_PF3, + AD7173_FILTER_SINC5_SINC1_PF4, +}; + +struct ad7173_channel_config { + /* Openwire detection threshold */ + unsigned int openwire_thrsh_raw; + int openwire_comp_chan; + u8 cfg_slot; + bool live; + + /* + * Following fields are used to compare equality. If you + * make adaptations in it, you most likely also have to adapt + * ad7173_is_setup_equal(), too. + */ + struct_group(config_props, + bool bipolar; + bool input_buf; + u16 sinc3_odr_div; + u8 sinc5_odr_index; + u8 ref_sel; + enum ad7173_filter_type filter_type; + ); +}; + +struct ad7173_channel { + unsigned int ain; + struct ad7173_channel_config cfg; + u8 syscalib_mode; + bool openwire_det_en; +}; + +struct ad7173_state { + struct ad_sigma_delta sd; + const struct ad7173_device_info *info; + struct ad7173_channel *channels; + struct regulator_bulk_data regulators[3]; + unsigned int adc_mode; + unsigned int interface_mode; + unsigned int num_channels; + struct ida cfg_slots_status; + unsigned long long config_usage_counter; + unsigned long long *config_cnts; + struct clk_hw int_clk_hw; + struct regmap *reg_gpiocon_regmap; + struct gpio_regmap *gpio_regmap; +}; + +static unsigned int ad4115_sinc5_data_rates[] = { + 24845000, 24845000, 20725000, 20725000, /* 0-3 */ + 15564000, 13841000, 10390000, 10390000, /* 4-7 */ + 4994000, 2499000, 1000000, 500000, /* 8-11 */ + 395500, 200000, 100000, 59890, /* 12-15 */ + 49920, 20000, 16660, 10000, /* 16-19 */ + 5000, 2500, 2500, /* 20-22 */ +}; + +static unsigned int ad4116_sinc5_data_rates[] = { + 12422360, 12422360, 12422360, 12422360, /* 0-3 */ + 10362690, 10362690, 7782100, 6290530, /* 4-7 */ + 5194800, 2496900, 1007600, 499900, /* 8-11 */ + 390600, 200300, 100000, 59750, /* 12-15 */ + 49840, 20000, 16650, 10000, /* 16-19 */ + 5000, 2500, 1250, /* 20-22 */ +}; + +static const unsigned int ad7173_sinc5_data_rates[] = { + 6211000, 6211000, 6211000, 6211000, 6211000, 6211000, 5181000, 4444000, /* 0-7 */ + 3115000, 2597000, 1007000, 503800, 381000, 200300, 100500, 59520, /* 8-15 */ + 49680, 20010, 16333, 10000, 5000, 2500, 1250, /* 16-22 */ +}; + +static const unsigned int ad7175_sinc5_data_rates[] = { + 50000000, 41667000, 31250000, 27778000, /* 0-3 */ + 20833000, 17857000, 12500000, 10000000, /* 4-7 */ + 5000000, 2500000, 1000000, 500000, /* 8-11 */ + 397500, 200000, 100000, 59920, /* 12-15 */ + 49960, 20000, 16666, 10000, /* 16-19 */ + 5000, /* 20 */ +}; + +/** + * ad7173_sinc3_odr_div_from_odr() - Convert ODR to divider value + * @odr_millihz: ODR (sampling_frequency) in milliHz + * Returns: Divider value for SINC3 filter to pass. + */ +static u16 ad7173_sinc3_odr_div_from_odr(u32 odr_millihz) +{ + /* + * Divider is f_MOD (1 MHz) / 32 / ODR. ODR freq is in milliHz, so + * we need to convert f_MOD to the same units. When SING_CYC=1 or + * multiple channels are enabled (currently always the case), there + * is an additional factor of 3. + */ + u32 div = DIV_ROUND_CLOSEST(MEGA * MILLI, odr_millihz * 32 * 3); + /* Avoid divide by 0 and limit to register field size. */ + return clamp(div, 1U, AD7173_FILTER_SINC3_MAP_DIV); +} + +static unsigned int ad4111_current_channel_config[] = { + /* Ain sel: pos neg */ + 0x1E8, /* 15:IIN0+ 8:IIN0− */ + 0x1C9, /* 14:IIN1+ 9:IIN1− */ + 0x1AA, /* 13:IIN2+ 10:IIN2− */ + 0x18B, /* 12:IIN3+ 11:IIN3− */ +}; + +static const char *const ad7173_ref_sel_str[] = { + [AD7173_SETUP_REF_SEL_EXT_REF] = "vref", + [AD7173_SETUP_REF_SEL_EXT_REF2] = "vref2", + [AD7173_SETUP_REF_SEL_INT_REF] = "refout-avss", + [AD7173_SETUP_REF_SEL_AVDD1_AVSS] = "avdd", +}; + +static const char *const ad7173_clk_sel[] = { + "ext-clk", "xtal" +}; + +static const struct regmap_range ad7173_range_gpio[] = { + regmap_reg_range(AD7173_REG_GPIO, AD7173_REG_GPIO), +}; + +static const struct regmap_access_table ad7173_access_table = { + .yes_ranges = ad7173_range_gpio, + .n_yes_ranges = ARRAY_SIZE(ad7173_range_gpio), +}; + +static const struct regmap_config ad7173_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .rd_table = &ad7173_access_table, + .wr_table = &ad7173_access_table, + .read_flag_mask = BIT(6), +}; + +enum { + AD7173_SYSCALIB_ZERO_SCALE, + AD7173_SYSCALIB_FULL_SCALE, +}; + +static const char * const ad7173_syscalib_modes[] = { + [AD7173_SYSCALIB_ZERO_SCALE] = "zero_scale", + [AD7173_SYSCALIB_FULL_SCALE] = "full_scale", +}; + +static int ad7173_set_syscalib_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct ad7173_state *st = iio_priv(indio_dev); + + st->channels[chan->address].syscalib_mode = mode; + + return 0; +} + +static int ad7173_get_syscalib_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad7173_state *st = iio_priv(indio_dev); + + return st->channels[chan->address].syscalib_mode; +} + +static ssize_t ad7173_write_syscalib(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ad7173_state *st = iio_priv(indio_dev); + bool sys_calib; + int ret, mode; + + ret = kstrtobool(buf, &sys_calib); + if (ret) + return ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + mode = st->channels[chan->address].syscalib_mode; + if (sys_calib) { + if (mode == AD7173_SYSCALIB_ZERO_SCALE) + ret = ad_sd_calibrate(&st->sd, AD7173_MODE_CAL_SYS_ZERO, + chan->address); + else + ret = ad_sd_calibrate(&st->sd, AD7173_MODE_CAL_SYS_FULL, + chan->address); + } + + iio_device_release_direct(indio_dev); + + return ret ? : len; +} + +static const struct iio_enum ad7173_syscalib_mode_enum = { + .items = ad7173_syscalib_modes, + .num_items = ARRAY_SIZE(ad7173_syscalib_modes), + .set = ad7173_set_syscalib_mode, + .get = ad7173_get_syscalib_mode +}; + +static const char * const ad7173_filter_types_str[] = { + [AD7173_FILTER_SINC3] = "sinc3", + [AD7173_FILTER_SINC5_SINC1] = "sinc5+sinc1", + [AD7173_FILTER_SINC5_SINC1_PF1] = "sinc5+sinc1+pf1", + [AD7173_FILTER_SINC5_SINC1_PF2] = "sinc5+sinc1+pf2", + [AD7173_FILTER_SINC5_SINC1_PF3] = "sinc5+sinc1+pf3", + [AD7173_FILTER_SINC5_SINC1_PF4] = "sinc5+sinc1+pf4", +}; + +static int ad7173_set_filter_type(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int val) +{ + struct ad7173_state *st = iio_priv(indio_dev); + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + st->channels[chan->address].cfg.filter_type = val; + st->channels[chan->address].cfg.live = false; + + iio_device_release_direct(indio_dev); + + return 0; +} + +static int ad7173_get_filter_type(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad7173_state *st = iio_priv(indio_dev); + + return st->channels[chan->address].cfg.filter_type; +} + +static const struct iio_enum ad7173_filter_type_enum = { + .items = ad7173_filter_types_str, + .num_items = ARRAY_SIZE(ad7173_filter_types_str), + .set = ad7173_set_filter_type, + .get = ad7173_get_filter_type, +}; + +static const struct iio_chan_spec_ext_info ad7173_chan_spec_ext_info[] = { + { + .name = "sys_calibration", + .write = ad7173_write_syscalib, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("sys_calibration_mode", IIO_SEPARATE, + &ad7173_syscalib_mode_enum), + IIO_ENUM_AVAILABLE("sys_calibration_mode", IIO_SHARED_BY_TYPE, + &ad7173_syscalib_mode_enum), + IIO_ENUM("filter_type", IIO_SEPARATE, &ad7173_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_TYPE, + &ad7173_filter_type_enum), + { } +}; + +static const struct iio_chan_spec_ext_info ad7173_temp_chan_spec_ext_info[] = { + IIO_ENUM("filter_type", IIO_SEPARATE, &ad7173_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_TYPE, + &ad7173_filter_type_enum), + { } +}; + +static int ad7173_calibrate_all(struct ad7173_state *st, struct iio_dev *indio_dev) +{ + int ret; + int i; + + for (i = 0; i < st->num_channels; i++) { + if (indio_dev->channels[i].type != IIO_VOLTAGE) + continue; + + ret = ad_sd_calibrate(&st->sd, AD7173_MODE_CAL_INT_ZERO, i); + if (ret < 0) + return ret; + + if (st->info->has_internal_fs_calibration) { + ret = ad_sd_calibrate(&st->sd, AD7173_MODE_CAL_INT_FULL, i); + if (ret < 0) + return ret; + } + } + + return 0; +} + +/* + * Associative array of channel pairs for open wire detection + * The array is indexed by ain and gives the associated channel pair + * to perform the open wire detection with + * the channel pair [0] is for non differential and pair [1] + * is for differential inputs + */ +static int openwire_ain_to_channel_pair[][2][2] = { +/* AIN Single Differential */ + [0] = { { 0, 15 }, { 1, 2 } }, + [1] = { { 1, 2 }, { 2, 1 } }, + [2] = { { 3, 4 }, { 5, 6 } }, + [3] = { { 5, 6 }, { 6, 5 } }, + [4] = { { 7, 8 }, { 9, 10 } }, + [5] = { { 9, 10 }, { 10, 9 } }, + [6] = { { 11, 12 }, { 13, 14 } }, + [7] = { { 13, 14 }, { 14, 13 } }, +}; + +/* + * Openwire detection on ad4111 works by running the same input measurement + * on two different channels and compare if the difference between the two + * measurements exceeds a certain value (typical 300mV) + */ +static int ad4111_openwire_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad7173_state *st = iio_priv(indio_dev); + struct ad7173_channel *adchan = &st->channels[chan->address]; + struct ad7173_channel_config *cfg = &adchan->cfg; + int ret, val1, val2; + + ret = regmap_set_bits(st->reg_gpiocon_regmap, AD7173_REG_GPIO, + AD4111_GPIO_GP_OW_EN); + if (ret) + return ret; + + adchan->cfg.openwire_comp_chan = + openwire_ain_to_channel_pair[chan->channel][chan->differential][0]; + + ret = ad_sigma_delta_single_conversion(indio_dev, chan, &val1); + if (ret < 0) { + dev_err(&indio_dev->dev, + "Error running ad_sigma_delta single conversion: %d", ret); + goto out; + } + + adchan->cfg.openwire_comp_chan = + openwire_ain_to_channel_pair[chan->channel][chan->differential][1]; + + ret = ad_sigma_delta_single_conversion(indio_dev, chan, &val2); + if (ret < 0) { + dev_err(&indio_dev->dev, + "Error running ad_sigma_delta single conversion: %d", ret); + goto out; + } + + if (abs(val1 - val2) > cfg->openwire_thrsh_raw) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, chan->address, + IIO_EV_TYPE_FAULT, IIO_EV_DIR_FAULT_OPENWIRE), + iio_get_time_ns(indio_dev)); + +out: + adchan->cfg.openwire_comp_chan = -1; + regmap_clear_bits(st->reg_gpiocon_regmap, AD7173_REG_GPIO, + AD4111_GPIO_GP_OW_EN); + return ret; +} + +static int ad7173_mask_xlate(struct gpio_regmap *gpio, enum gpio_regmap_operation op, + unsigned int base, unsigned int offset, unsigned int *reg, + unsigned int *mask) +{ + *mask = AD7173_GPO_DATA(offset); + *reg = base; + return 0; +} + +static int ad4111_mask_xlate(struct gpio_regmap *gpio, enum gpio_regmap_operation op, + unsigned int base, unsigned int offset, unsigned int *reg, + unsigned int *mask) +{ + *mask = AD4111_GPO01_DATA(offset); + *reg = base; + return 0; +} + +static void ad7173_gpio_disable(void *data) +{ + struct ad7173_state *st = data; + unsigned int mask; + + mask = AD7173_GPIO_OP_EN0 | AD7173_GPIO_OP_EN1 | AD7173_GPIO_OP_EN2_3; + regmap_update_bits(st->reg_gpiocon_regmap, AD7173_REG_GPIO, mask, ~mask); +} + +static int ad7173_gpio_init(struct ad7173_state *st) +{ + struct gpio_regmap_config gpio_regmap = {}; + struct device *dev = &st->sd.spi->dev; + unsigned int mask; + int ret; + + st->reg_gpiocon_regmap = devm_regmap_init_spi(st->sd.spi, &ad7173_regmap_config); + ret = PTR_ERR_OR_ZERO(st->reg_gpiocon_regmap); + if (ret) + return dev_err_probe(dev, ret, "Unable to init regmap\n"); + + mask = AD7173_GPIO_OP_EN0 | AD7173_GPIO_OP_EN1 | AD7173_GPIO_OP_EN2_3; + regmap_update_bits(st->reg_gpiocon_regmap, AD7173_REG_GPIO, mask, mask); + + ret = devm_add_action_or_reset(dev, ad7173_gpio_disable, st); + if (ret) + return ret; + + gpio_regmap.parent = dev; + gpio_regmap.regmap = st->reg_gpiocon_regmap; + gpio_regmap.ngpio = st->info->num_gpios; + gpio_regmap.reg_set_base = AD7173_REG_GPIO; + if (st->info->higher_gpio_bits) + gpio_regmap.reg_mask_xlate = ad4111_mask_xlate; + else + gpio_regmap.reg_mask_xlate = ad7173_mask_xlate; + + st->gpio_regmap = devm_gpio_regmap_register(dev, &gpio_regmap); + ret = PTR_ERR_OR_ZERO(st->gpio_regmap); + if (ret) + return dev_err_probe(dev, ret, "Unable to init gpio-regmap\n"); + + return 0; +} + +static struct ad7173_state *ad_sigma_delta_to_ad7173(struct ad_sigma_delta *sd) +{ + return container_of(sd, struct ad7173_state, sd); +} + +static struct ad7173_state *clk_hw_to_ad7173(struct clk_hw *hw) +{ + return container_of(hw, struct ad7173_state, int_clk_hw); +} + +static void ad7173_ida_destroy(void *data) +{ + struct ad7173_state *st = data; + + ida_destroy(&st->cfg_slots_status); +} + +static void ad7173_reset_usage_cnts(struct ad7173_state *st) +{ + memset64(st->config_cnts, 0, st->info->num_configs); + st->config_usage_counter = 0; +} + +/** + * ad7173_is_setup_equal - Compare two channel setups + * @cfg1: First channel configuration + * @cfg2: Second channel configuration + * + * Compares all configuration options that affect the registers connected to + * SETUP_SEL, namely CONFIGx, FILTERx, GAINx and OFFSETx. + * + * Returns: true if the setups are identical, false otherwise + */ +static bool ad7173_is_setup_equal(const struct ad7173_channel_config *cfg1, + const struct ad7173_channel_config *cfg2) +{ + /* + * This is just to make sure that the comparison is adapted after + * struct ad7173_channel_config was changed. + */ + static_assert(sizeof_field(struct ad7173_channel_config, config_props) == + sizeof(struct { + bool bipolar; + bool input_buf; + u16 sinc3_odr_div; + u8 sinc5_odr_index; + u8 ref_sel; + enum ad7173_filter_type filter_type; + })); + + return cfg1->bipolar == cfg2->bipolar && + cfg1->input_buf == cfg2->input_buf && + cfg1->sinc3_odr_div == cfg2->sinc3_odr_div && + cfg1->sinc5_odr_index == cfg2->sinc5_odr_index && + cfg1->ref_sel == cfg2->ref_sel && + cfg1->filter_type == cfg2->filter_type; +} + +static struct ad7173_channel_config * +ad7173_find_live_config(struct ad7173_state *st, struct ad7173_channel_config *cfg) +{ + struct ad7173_channel_config *cfg_aux; + int i; + + for (i = 0; i < st->num_channels; i++) { + cfg_aux = &st->channels[i].cfg; + + if (cfg_aux->live && ad7173_is_setup_equal(cfg, cfg_aux)) + return cfg_aux; + } + return NULL; +} + +/* Could be replaced with a generic LRU implementation */ +static int ad7173_free_config_slot_lru(struct ad7173_state *st) +{ + int i, lru_position = 0; + + for (i = 1; i < st->info->num_configs; i++) + if (st->config_cnts[i] < st->config_cnts[lru_position]) + lru_position = i; + + for (i = 0; i < st->num_channels; i++) + if (st->channels[i].cfg.cfg_slot == lru_position) + st->channels[i].cfg.live = false; + + ida_free(&st->cfg_slots_status, lru_position); + return ida_alloc(&st->cfg_slots_status, GFP_KERNEL); +} + +/* Could be replaced with a generic LRU implementation */ +static int ad7173_load_config(struct ad7173_state *st, + struct ad7173_channel_config *cfg) +{ + unsigned int config; + int free_cfg_slot, ret; + u8 post_filter_enable, post_filter_select; + + free_cfg_slot = ida_alloc_range(&st->cfg_slots_status, 0, + st->info->num_configs - 1, GFP_KERNEL); + if (free_cfg_slot < 0) + free_cfg_slot = ad7173_free_config_slot_lru(st); + + cfg->cfg_slot = free_cfg_slot; + config = FIELD_PREP(AD7173_SETUP_REF_SEL_MASK, cfg->ref_sel); + + if (cfg->bipolar) + config |= AD7173_SETUP_BIPOLAR; + + if (cfg->input_buf) + config |= AD7173_SETUP_AIN_BUF_MASK; + + ret = ad_sd_write_reg(&st->sd, AD7173_REG_SETUP(free_cfg_slot), 2, config); + if (ret) + return ret; + + /* + * When SINC3_MAP flag is enabled, the rest of the register has a + * different meaning. We are using this option to allow the most + * possible sampling frequencies with SINC3 filter. + */ + if (cfg->filter_type == AD7173_FILTER_SINC3) + return ad_sd_write_reg(&st->sd, AD7173_REG_FILTER(free_cfg_slot), 2, + FIELD_PREP(AD7173_FILTER_SINC3_MAP, 1) | + FIELD_PREP(AD7173_FILTER_SINC3_MAP_DIV, + cfg->sinc3_odr_div)); + + switch (cfg->filter_type) { + case AD7173_FILTER_SINC5_SINC1_PF1: + post_filter_enable = 1; + post_filter_select = 2; + break; + case AD7173_FILTER_SINC5_SINC1_PF2: + post_filter_enable = 1; + post_filter_select = 3; + break; + case AD7173_FILTER_SINC5_SINC1_PF3: + post_filter_enable = 1; + post_filter_select = 5; + break; + case AD7173_FILTER_SINC5_SINC1_PF4: + post_filter_enable = 1; + post_filter_select = 6; + break; + default: + post_filter_enable = 0; + post_filter_select = 0; + break; + } + + return ad_sd_write_reg(&st->sd, AD7173_REG_FILTER(free_cfg_slot), 2, + FIELD_PREP(AD7173_FILTER_SINC3_MAP, 0) | + FIELD_PREP(AD7173_FILTER_ENHFILT_MASK, + post_filter_enable) | + FIELD_PREP(AD7173_FILTER_ENHFILTEN, + post_filter_select) | + FIELD_PREP(AD7173_FILTER_ORDER, 0) | + FIELD_PREP(AD7173_FILTER_ODR_MASK, + cfg->sinc5_odr_index)); +} + +static int ad7173_config_channel(struct ad7173_state *st, int addr) +{ + struct ad7173_channel_config *cfg = &st->channels[addr].cfg; + struct ad7173_channel_config *live_cfg; + int ret; + + if (!cfg->live) { + live_cfg = ad7173_find_live_config(st, cfg); + if (live_cfg) { + cfg->cfg_slot = live_cfg->cfg_slot; + } else { + ret = ad7173_load_config(st, cfg); + if (ret) + return ret; + cfg->live = true; + } + } + + if (st->config_usage_counter == U64_MAX) + ad7173_reset_usage_cnts(st); + + st->config_usage_counter++; + st->config_cnts[cfg->cfg_slot] = st->config_usage_counter; + + return 0; +} + +static int ad7173_set_channel(struct ad_sigma_delta *sd, unsigned int channel) +{ + struct ad7173_state *st = ad_sigma_delta_to_ad7173(sd); + unsigned int val; + int ret; + + ret = ad7173_config_channel(st, channel); + if (ret) + return ret; + + val = AD7173_CH_ENABLE | + FIELD_PREP(AD7173_CH_SETUP_SEL_MASK, st->channels[channel].cfg.cfg_slot) | + st->channels[channel].ain; + + if (st->channels[channel].cfg.openwire_comp_chan >= 0) + channel = st->channels[channel].cfg.openwire_comp_chan; + + return ad_sd_write_reg(&st->sd, AD7173_REG_CH(channel), 2, val); +} + +static int ad7173_set_mode(struct ad_sigma_delta *sd, + enum ad_sigma_delta_mode mode) +{ + struct ad7173_state *st = ad_sigma_delta_to_ad7173(sd); + + st->adc_mode &= ~AD7173_ADC_MODE_MODE_MASK; + st->adc_mode |= FIELD_PREP(AD7173_ADC_MODE_MODE_MASK, mode); + + return ad_sd_write_reg(&st->sd, AD7173_REG_ADC_MODE, 2, st->adc_mode); +} + +static int ad7173_append_status(struct ad_sigma_delta *sd, bool append) +{ + struct ad7173_state *st = ad_sigma_delta_to_ad7173(sd); + unsigned int interface_mode = st->interface_mode; + int ret; + + interface_mode &= ~AD7173_INTERFACE_DATA_STAT; + interface_mode |= AD7173_INTERFACE_DATA_STAT_EN(append); + ret = ad_sd_write_reg(&st->sd, AD7173_REG_INTERFACE_MODE, 2, interface_mode); + if (ret) + return ret; + + st->interface_mode = interface_mode; + + return 0; +} + +static int ad7173_disable_all(struct ad_sigma_delta *sd) +{ + struct ad7173_state *st = ad_sigma_delta_to_ad7173(sd); + int ret; + int i; + + for (i = 0; i < st->num_channels; i++) { + ret = ad_sd_write_reg(sd, AD7173_REG_CH(i), 2, 0); + if (ret < 0) + return ret; + } + + return 0; +} + +static int ad7173_disable_one(struct ad_sigma_delta *sd, unsigned int chan) +{ + struct ad7173_state *st = ad_sigma_delta_to_ad7173(sd); + + if (st->channels[chan].cfg.openwire_comp_chan >= 0) + chan = st->channels[chan].cfg.openwire_comp_chan; + + return ad_sd_write_reg(sd, AD7173_REG_CH(chan), 2, 0); +} + +static const struct ad_sigma_delta_info ad7173_sigma_delta_info_4_slots = { + .set_channel = ad7173_set_channel, + .append_status = ad7173_append_status, + .disable_all = ad7173_disable_all, + .disable_one = ad7173_disable_one, + .set_mode = ad7173_set_mode, + .has_registers = true, + .has_named_irqs = true, + .supports_spi_offload = true, + .addr_shift = 0, + .read_mask = BIT(6), + .status_ch_mask = GENMASK(3, 0), + .data_reg = AD7173_REG_DATA, + .num_resetclks = 64, + .num_slots = 4, +}; + +static const struct ad_sigma_delta_info ad7173_sigma_delta_info_8_slots = { + .set_channel = ad7173_set_channel, + .append_status = ad7173_append_status, + .disable_all = ad7173_disable_all, + .disable_one = ad7173_disable_one, + .set_mode = ad7173_set_mode, + .has_registers = true, + .has_named_irqs = true, + .supports_spi_offload = true, + .addr_shift = 0, + .read_mask = BIT(6), + .status_ch_mask = GENMASK(3, 0), + .data_reg = AD7173_REG_DATA, + .num_resetclks = 64, + .num_slots = 8, +}; + +static const struct ad_sigma_delta_info ad7173_sigma_delta_info_16_slots = { + .set_channel = ad7173_set_channel, + .append_status = ad7173_append_status, + .disable_all = ad7173_disable_all, + .disable_one = ad7173_disable_one, + .set_mode = ad7173_set_mode, + .has_registers = true, + .has_named_irqs = true, + .supports_spi_offload = true, + .addr_shift = 0, + .read_mask = BIT(6), + .status_ch_mask = GENMASK(3, 0), + .data_reg = AD7173_REG_DATA, + .num_resetclks = 64, + .num_slots = 16, +}; + +static const struct ad7173_device_info ad4111_device_info = { + .name = "ad4111", + .id = AD4111_ID, + .sd_info = &ad7173_sigma_delta_info_16_slots, + .num_voltage_in_div = 8, + .num_channels = 16, + .num_configs = 8, + .num_voltage_in = 8, + .num_gpios = 2, + .higher_gpio_bits = true, + .has_temp = true, + .has_vincom_input = true, + .has_input_buf = true, + .has_current_inputs = true, + .has_int_ref = true, + .has_internal_fs_calibration = true, + .has_openwire_det = true, + .clock = 2 * HZ_PER_MHZ, + .sinc5_data_rates = ad7173_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates), +}; + +static const struct ad7173_device_info ad4112_device_info = { + .name = "ad4112", + .id = AD4112_ID, + .sd_info = &ad7173_sigma_delta_info_16_slots, + .num_voltage_in_div = 8, + .num_channels = 16, + .num_configs = 8, + .num_voltage_in = 8, + .num_gpios = 2, + .higher_gpio_bits = true, + .has_vincom_input = true, + .has_temp = true, + .has_input_buf = true, + .has_current_inputs = true, + .has_int_ref = true, + .has_internal_fs_calibration = true, + .clock = 2 * HZ_PER_MHZ, + .sinc5_data_rates = ad7173_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates), +}; + +static const struct ad7173_device_info ad4113_device_info = { + .name = "ad4113", + .id = AD4113_ID, + .sd_info = &ad7173_sigma_delta_info_16_slots, + .num_voltage_in_div = 8, + .num_channels = 16, + .num_configs = 8, + .num_voltage_in = 8, + .num_gpios = 2, + .data_reg_only_16bit = true, + .higher_gpio_bits = true, + .has_vincom_input = true, + .has_input_buf = true, + .has_int_ref = true, + .clock = 2 * HZ_PER_MHZ, + .sinc5_data_rates = ad7173_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates), +}; + +static const struct ad7173_device_info ad4114_device_info = { + .name = "ad4114", + .id = AD4114_ID, + .sd_info = &ad7173_sigma_delta_info_16_slots, + .num_voltage_in_div = 16, + .num_channels = 16, + .num_configs = 8, + .num_voltage_in = 16, + .num_gpios = 4, + .has_vincom_input = true, + .has_temp = true, + .has_input_buf = true, + .has_int_ref = true, + .has_internal_fs_calibration = true, + .clock = 2 * HZ_PER_MHZ, + .sinc5_data_rates = ad7173_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates), +}; + +static const struct ad7173_device_info ad4115_device_info = { + .name = "ad4115", + .id = AD4115_ID, + .sd_info = &ad7173_sigma_delta_info_16_slots, + .num_voltage_in_div = 16, + .num_channels = 16, + .num_configs = 8, + .num_voltage_in = 16, + .num_gpios = 4, + .has_vincom_input = true, + .has_temp = true, + .has_input_buf = true, + .has_int_ref = true, + .has_internal_fs_calibration = true, + .clock = 8 * HZ_PER_MHZ, + .sinc5_data_rates = ad4115_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad4115_sinc5_data_rates), +}; + +static const struct ad7173_device_info ad4116_device_info = { + .name = "ad4116", + .id = AD4116_ID, + .sd_info = &ad7173_sigma_delta_info_16_slots, + .num_voltage_in_div = 11, + .num_channels = 16, + .num_configs = 8, + .num_voltage_in = 16, + .num_gpios = 4, + .has_vincom_input = true, + .has_temp = true, + .has_input_buf = true, + .has_int_ref = true, + .has_internal_fs_calibration = true, + .clock = 4 * HZ_PER_MHZ, + .sinc5_data_rates = ad4116_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad4116_sinc5_data_rates), +}; + +static const struct ad7173_device_info ad7172_2_device_info = { + .name = "ad7172-2", + .id = AD7172_2_ID, + .sd_info = &ad7173_sigma_delta_info_4_slots, + .num_voltage_in = 5, + .num_channels = 4, + .num_configs = 4, + .num_gpios = 2, + .has_temp = true, + .has_input_buf = true, + .has_int_ref = true, + .has_pow_supply_monitoring = true, + .clock = 2 * HZ_PER_MHZ, + .sinc5_data_rates = ad7173_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates), +}; + +static const struct ad7173_device_info ad7172_4_device_info = { + .name = "ad7172-4", + .id = AD7172_4_ID, + .sd_info = &ad7173_sigma_delta_info_8_slots, + .num_voltage_in = 9, + .num_channels = 8, + .num_configs = 8, + .num_gpios = 4, + .has_input_buf = true, + .has_ref2 = true, + .has_pow_supply_monitoring = true, + .clock = 2 * HZ_PER_MHZ, + .sinc5_data_rates = ad7173_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates), +}; + +static const struct ad7173_device_info ad7173_8_device_info = { + .name = "ad7173-8", + .id = AD7173_ID, + .sd_info = &ad7173_sigma_delta_info_16_slots, + .num_voltage_in = 17, + .num_channels = 16, + .num_configs = 8, + .num_gpios = 4, + .has_temp = true, + .has_input_buf = true, + .has_int_ref = true, + .has_ref2 = true, + .clock = 2 * HZ_PER_MHZ, + .sinc5_data_rates = ad7173_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates), +}; + +static const struct ad7173_device_info ad7175_2_device_info = { + .name = "ad7175-2", + .id = AD7175_2_ID, + .sd_info = &ad7173_sigma_delta_info_4_slots, + .num_voltage_in = 5, + .num_channels = 4, + .num_configs = 4, + .num_gpios = 2, + .has_temp = true, + .has_input_buf = true, + .has_int_ref = true, + .has_pow_supply_monitoring = true, + .clock = 16 * HZ_PER_MHZ, + .sinc5_data_rates = ad7175_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7175_sinc5_data_rates), +}; + +static const struct ad7173_device_info ad7175_8_device_info = { + .name = "ad7175-8", + .id = AD7175_8_ID, + .sd_info = &ad7173_sigma_delta_info_16_slots, + .num_voltage_in = 17, + .num_channels = 16, + .num_configs = 8, + .num_gpios = 4, + .has_temp = true, + .has_input_buf = true, + .has_int_ref = true, + .has_ref2 = true, + .has_pow_supply_monitoring = true, + .clock = 16 * HZ_PER_MHZ, + .sinc5_data_rates = ad7175_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7175_sinc5_data_rates), +}; + +static const struct ad7173_device_info ad7176_2_device_info = { + .name = "ad7176-2", + .id = AD7176_ID, + .sd_info = &ad7173_sigma_delta_info_4_slots, + .num_voltage_in = 5, + .num_channels = 4, + .num_configs = 4, + .num_gpios = 2, + .has_int_ref = true, + .clock = 16 * HZ_PER_MHZ, + .sinc5_data_rates = ad7175_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7175_sinc5_data_rates), +}; + +static const struct ad7173_device_info ad7177_2_device_info = { + .name = "ad7177-2", + .id = AD7177_ID, + .sd_info = &ad7173_sigma_delta_info_4_slots, + .num_voltage_in = 5, + .num_channels = 4, + .num_configs = 4, + .num_gpios = 2, + .has_temp = true, + .has_input_buf = true, + .has_int_ref = true, + .has_pow_supply_monitoring = true, + .clock = 16 * HZ_PER_MHZ, + .odr_start_value = AD7177_ODR_START_VALUE, + .sinc5_data_rates = ad7175_sinc5_data_rates, + .num_sinc5_data_rates = ARRAY_SIZE(ad7175_sinc5_data_rates), +}; + +static int ad7173_setup(struct iio_dev *indio_dev) +{ + struct ad7173_state *st = iio_priv(indio_dev); + struct device *dev = &st->sd.spi->dev; + u8 buf[AD7173_RESET_LENGTH]; + unsigned int id; + int ret; + + /* reset the serial interface */ + memset(buf, 0xff, AD7173_RESET_LENGTH); + ret = spi_write_then_read(st->sd.spi, buf, sizeof(buf), NULL, 0); + if (ret < 0) + return ret; + + /* datasheet recommends a delay of at least 500us after reset */ + fsleep(500); + + ret = ad_sd_read_reg(&st->sd, AD7173_REG_ID, 2, &id); + if (ret) + return ret; + + id &= AD7173_ID_MASK; + if (id != st->info->id) + dev_warn(dev, "Unexpected device id: 0x%04X, expected: 0x%04X\n", + id, st->info->id); + + st->adc_mode |= AD7173_ADC_MODE_SING_CYC; + st->interface_mode = 0x0; + + st->config_usage_counter = 0; + st->config_cnts = devm_kcalloc(dev, st->info->num_configs, + sizeof(*st->config_cnts), GFP_KERNEL); + if (!st->config_cnts) + return -ENOMEM; + + ret = ad7173_calibrate_all(st, indio_dev); + if (ret) + return ret; + + /* All channels are enabled by default after a reset */ + return ad7173_disable_all(&st->sd); +} + +static unsigned int ad7173_get_ref_voltage_milli(struct ad7173_state *st, + u8 reference_select) +{ + int vref; + + switch (reference_select) { + case AD7173_SETUP_REF_SEL_EXT_REF: + vref = regulator_get_voltage(st->regulators[0].consumer); + break; + + case AD7173_SETUP_REF_SEL_EXT_REF2: + vref = regulator_get_voltage(st->regulators[1].consumer); + break; + + case AD7173_SETUP_REF_SEL_INT_REF: + vref = AD7173_VOLTAGE_INT_REF_uV; + break; + + case AD7173_SETUP_REF_SEL_AVDD1_AVSS: + vref = regulator_get_voltage(st->regulators[2].consumer); + break; + + default: + return -EINVAL; + } + + if (vref < 0) + return vref; + + return vref / (MICRO / MILLI); +} + +static int ad7173_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad7173_state *st = iio_priv(indio_dev); + struct ad7173_channel *ch = &st->channels[chan->address]; + unsigned int reg; + u64 temp; + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + ret = ad_sigma_delta_single_conversion(indio_dev, chan, val); + if (ret < 0) + return ret; + + if (ch->openwire_det_en) { + ret = ad4111_openwire_event(indio_dev, chan); + if (ret < 0) + return ret; + } + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + + switch (chan->type) { + case IIO_TEMP: + temp = AD7173_VOLTAGE_INT_REF_uV * MILLI; + temp /= AD7173_TEMP_SENSIIVITY_uV_per_C; + *val = temp; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_VOLTAGE: + *val = ad7173_get_ref_voltage_milli(st, ch->cfg.ref_sel); + *val2 = chan->scan_type.realbits - !!(ch->cfg.bipolar); + + if (chan->channel < st->info->num_voltage_in_div) + *val *= AD4111_DIVIDER_RATIO; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CURRENT: + *val = ad7173_get_ref_voltage_milli(st, ch->cfg.ref_sel); + *val /= AD4111_SHUNT_RESISTOR_OHM; + *val2 = chan->scan_type.realbits - ch->cfg.bipolar; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + + switch (chan->type) { + case IIO_TEMP: + /* 0 Kelvin -> raw sample */ + temp = -ABSOLUTE_ZERO_MILLICELSIUS; + temp *= AD7173_TEMP_SENSIIVITY_uV_per_C; + temp <<= chan->scan_type.realbits; + temp = DIV_U64_ROUND_CLOSEST(temp, + AD7173_VOLTAGE_INT_REF_uV * + MILLI); + *val = -temp; + return IIO_VAL_INT; + case IIO_VOLTAGE: + case IIO_CURRENT: + *val = -BIT(chan->scan_type.realbits - 1); + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + if (st->channels[chan->address].cfg.filter_type == AD7173_FILTER_SINC3) { + /* Inverse operation of ad7173_sinc3_odr_div_from_odr() */ + *val = MEGA; + *val2 = 3 * 32 * st->channels[chan->address].cfg.sinc3_odr_div; + return IIO_VAL_FRACTIONAL; + } + + reg = st->channels[chan->address].cfg.sinc5_odr_index; + + *val = st->info->sinc5_data_rates[reg] / MILLI; + *val2 = (st->info->sinc5_data_rates[reg] % MILLI) * (MICRO / MILLI); + + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int ad7173_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct ad7173_state *st = iio_priv(indio_dev); + struct ad7173_channel_config *cfg; + unsigned int freq, i; + int ret = 0; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + switch (info) { + /* + * This attribute sets the sampling frequency for each channel individually. + * There are no issues for raw or buffered reads of an individual channel. + * + * When multiple channels are enabled in buffered mode, the effective + * sampling rate of a channel is lowered in correlation to the number + * of channels enabled and the sampling rate of the other channels. + * + * Example: 3 channels enabled with rates CH1:6211sps CH2,CH3:10sps + * While the reading of CH1 takes only 0.16ms, the reading of CH2 and CH3 + * will take 100ms each. + * + * This will cause the reading of CH1 to be actually done once every + * 200.16ms, an effective rate of 4.99sps. + * + * Both the sinc5 and sinc3 rates are set here so that if the filter + * type is changed, the requested rate will still be set (aside from + * rounding differences). + */ + case IIO_CHAN_INFO_SAMP_FREQ: + freq = val * MILLI + val2 / MILLI; + for (i = st->info->odr_start_value; i < st->info->num_sinc5_data_rates - 1; i++) + if (freq >= st->info->sinc5_data_rates[i]) + break; + + cfg = &st->channels[chan->address].cfg; + cfg->sinc5_odr_index = i; + cfg->sinc3_odr_div = ad7173_sinc3_odr_div_from_odr(freq); + cfg->live = false; + break; + + default: + ret = -EINVAL; + break; + } + + iio_device_release_direct(indio_dev); + return ret; +} + +static int ad7173_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ad7173_state *st = iio_priv(indio_dev); + u16 sinc3_count = 0; + u16 sinc3_div = 0; + int i, j, k, ret; + + for (i = 0; i < indio_dev->num_channels; i++) { + const struct ad7173_channel_config *cfg = &st->channels[i].cfg; + + if (test_bit(i, scan_mask)) { + if (cfg->filter_type == AD7173_FILTER_SINC3) { + sinc3_count++; + + if (sinc3_div == 0) { + sinc3_div = cfg->sinc3_odr_div; + } else if (sinc3_div != cfg->sinc3_odr_div) { + dev_err(&st->sd.spi->dev, + "All enabled channels must have the same sampling_frequency for sinc3 filter_type\n"); + return -EINVAL; + } + } + + ret = ad7173_set_channel(&st->sd, i); + } else { + ret = ad_sd_write_reg(&st->sd, AD7173_REG_CH(i), 2, 0); + } + if (ret < 0) + return ret; + } + + if (sinc3_count && sinc3_count < bitmap_weight(scan_mask, indio_dev->num_channels)) { + dev_err(&st->sd.spi->dev, + "All enabled channels must have sinc3 filter_type\n"); + return -EINVAL; + } + + /* + * On some chips, there are more channels that setups, so if there were + * more unique setups requested than the number of available slots, + * ad7173_set_channel() will have written over some of the slots. We + * can detect this by making sure each assigned cfg_slot matches the + * requested configuration. If it doesn't, we know that the slot was + * overwritten by a different channel. + */ + for_each_set_bit(i, scan_mask, indio_dev->num_channels) { + const struct ad7173_channel_config *cfg1, *cfg2; + + cfg1 = &st->channels[i].cfg; + + for_each_set_bit(j, scan_mask, indio_dev->num_channels) { + cfg2 = &st->channels[j].cfg; + + /* + * Only compare configs that are assigned to the same + * SETUP_SEL slot and don't compare channel to itself. + */ + if (i == j || cfg1->cfg_slot != cfg2->cfg_slot) + continue; + + /* + * If we find two different configs trying to use the + * same SETUP_SEL slot, then we know that the that we + * have too many unique configurations requested for + * the available slots and at least one was overwritten. + */ + if (!ad7173_is_setup_equal(cfg1, cfg2)) { + /* + * At this point, there isn't a way to tell + * which setups are actually programmed in the + * ADC anymore, so we could read them back to + * see, but it is simpler to just turn off all + * of the live flags so that everything gets + * reprogramed on the next attempt read a sample. + */ + for (k = 0; k < st->num_channels; k++) + st->channels[k].cfg.live = false; + + dev_err(&st->sd.spi->dev, + "Too many unique channel configurations requested for scan\n"); + return -EINVAL; + } + } + } + + return 0; +} + +static int ad7173_debug_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct ad7173_state *st = iio_priv(indio_dev); + u8 reg_size; + + if (reg == AD7173_REG_COMMS) + reg_size = 1; + else if (reg == AD7173_REG_CRC || reg == AD7173_REG_DATA || + reg >= AD7173_REG_OFFSET(0)) + reg_size = 3; + else + reg_size = 2; + + if (readval) + return ad_sd_read_reg(&st->sd, reg, reg_size, readval); + + return ad_sd_write_reg(&st->sd, reg, reg_size, writeval); +} + +static int ad7173_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct ad7173_state *st = iio_priv(indio_dev); + struct ad7173_channel *adchan = &st->channels[chan->address]; + + switch (type) { + case IIO_EV_TYPE_FAULT: + adchan->openwire_det_en = state; + return 0; + default: + return -EINVAL; + } +} + +static int ad7173_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct ad7173_state *st = iio_priv(indio_dev); + struct ad7173_channel *adchan = &st->channels[chan->address]; + + switch (type) { + case IIO_EV_TYPE_FAULT: + return adchan->openwire_det_en; + default: + return -EINVAL; + } +} + +static const struct iio_event_spec ad4111_events[] = { + { + .type = IIO_EV_TYPE_FAULT, + .dir = IIO_EV_DIR_FAULT_OPENWIRE, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + .mask_shared_by_all = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_info ad7173_info = { + .read_raw = &ad7173_read_raw, + .write_raw = &ad7173_write_raw, + .debugfs_reg_access = &ad7173_debug_reg_access, + .validate_trigger = ad_sd_validate_trigger, + .update_scan_mode = ad7173_update_scan_mode, + .write_event_config = ad7173_write_event_config, + .read_event_config = ad7173_read_event_config, +}; + +static const struct iio_scan_type ad4113_scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, +}; + +static const struct iio_chan_spec ad7173_channel_template = { + .type = IIO_VOLTAGE, + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_BE, + }, + .ext_info = ad7173_chan_spec_ext_info, +}; + +static const struct iio_chan_spec ad7173_temp_iio_channel_template = { + .type = IIO_TEMP, + .channel = AD7173_AIN_TEMP_POS, + .channel2 = AD7173_AIN_TEMP_NEG, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_BE, + }, + .ext_info = ad7173_temp_chan_spec_ext_info, +}; + +static void ad7173_disable_regulators(void *data) +{ + struct ad7173_state *st = data; + + regulator_bulk_disable(ARRAY_SIZE(st->regulators), st->regulators); +} + +static unsigned long ad7173_sel_clk(struct ad7173_state *st, + unsigned int clk_sel) +{ + int ret; + + st->adc_mode &= ~AD7173_ADC_MODE_CLOCKSEL_MASK; + st->adc_mode |= FIELD_PREP(AD7173_ADC_MODE_CLOCKSEL_MASK, clk_sel); + ret = ad_sd_write_reg(&st->sd, AD7173_REG_ADC_MODE, 0x2, st->adc_mode); + + return ret; +} + +static unsigned long ad7173_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ad7173_state *st = clk_hw_to_ad7173(hw); + + return st->info->clock / HZ_PER_KHZ; +} + +static int ad7173_clk_output_is_enabled(struct clk_hw *hw) +{ + struct ad7173_state *st = clk_hw_to_ad7173(hw); + u32 clk_sel; + + clk_sel = FIELD_GET(AD7173_ADC_MODE_CLOCKSEL_MASK, st->adc_mode); + return clk_sel == AD7173_ADC_MODE_CLOCKSEL_INT_OUTPUT; +} + +static int ad7173_clk_output_prepare(struct clk_hw *hw) +{ + struct ad7173_state *st = clk_hw_to_ad7173(hw); + + return ad7173_sel_clk(st, AD7173_ADC_MODE_CLOCKSEL_INT_OUTPUT); +} + +static void ad7173_clk_output_unprepare(struct clk_hw *hw) +{ + struct ad7173_state *st = clk_hw_to_ad7173(hw); + + ad7173_sel_clk(st, AD7173_ADC_MODE_CLOCKSEL_INT); +} + +static const struct clk_ops ad7173_int_clk_ops = { + .recalc_rate = ad7173_clk_recalc_rate, + .is_enabled = ad7173_clk_output_is_enabled, + .prepare = ad7173_clk_output_prepare, + .unprepare = ad7173_clk_output_unprepare, +}; + +static int ad7173_register_clk_provider(struct iio_dev *indio_dev) +{ + struct ad7173_state *st = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + struct fwnode_handle *fwnode = dev_fwnode(dev); + struct clk_init_data init = {}; + int ret; + + if (!IS_ENABLED(CONFIG_COMMON_CLK)) + return 0; + + init.name = fwnode_get_name(fwnode); + init.ops = &ad7173_int_clk_ops; + + st->int_clk_hw.init = &init; + ret = devm_clk_hw_register(dev, &st->int_clk_hw); + if (ret) + return ret; + + return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + &st->int_clk_hw); +} + +static int ad4111_validate_current_ain(struct ad7173_state *st, + const unsigned int ain[AD7173_NO_AINS_PER_CHANNEL]) +{ + struct device *dev = &st->sd.spi->dev; + + if (!st->info->has_current_inputs) + return dev_err_probe(dev, -EINVAL, + "Model %s does not support current channels\n", + st->info->name); + + if (ain[0] >= ARRAY_SIZE(ad4111_current_channel_config)) + return dev_err_probe(dev, -EINVAL, + "For current channels single-channel must be <[0-3]>\n"); + + return 0; +} + +static int ad7173_validate_voltage_ain_inputs(struct ad7173_state *st, + unsigned int ain0, unsigned int ain1) +{ + struct device *dev = &st->sd.spi->dev; + bool special_input0, special_input1; + + /* (AVDD1-AVSS)/5 power supply monitoring */ + if (ain0 == AD7173_AIN_POW_MON_POS && ain1 == AD7173_AIN_POW_MON_NEG && + st->info->has_pow_supply_monitoring) + return 0; + + special_input0 = AD7173_IS_REF_INPUT(ain0) || + (ain0 == AD4111_VINCOM_INPUT && st->info->has_vincom_input); + special_input1 = AD7173_IS_REF_INPUT(ain1) || + (ain1 == AD4111_VINCOM_INPUT && st->info->has_vincom_input); + + if ((ain0 >= st->info->num_voltage_in && !special_input0) || + (ain1 >= st->info->num_voltage_in && !special_input1)) { + if (ain0 == AD4111_VINCOM_INPUT || ain1 == AD4111_VINCOM_INPUT) + return dev_err_probe(dev, -EINVAL, + "VINCOM not supported for %s\n", st->info->name); + + return dev_err_probe(dev, -EINVAL, + "Input pin number out of range for pair (%d %d).\n", + ain0, ain1); + } + + if (AD4111_IS_VINCOM_MISMATCH(ain0, ain1) || + AD4111_IS_VINCOM_MISMATCH(ain1, ain0)) + return dev_err_probe(dev, -EINVAL, + "VINCOM must be paired with inputs having divider.\n"); + + if (!special_input0 && !special_input1 && + ((ain0 >= st->info->num_voltage_in_div) != + (ain1 >= st->info->num_voltage_in_div))) + return dev_err_probe(dev, -EINVAL, + "Both inputs must either have a voltage divider or not have: (%d %d).\n", + ain0, ain1); + + return 0; +} + +static int ad7173_validate_reference(struct ad7173_state *st, int ref_sel) +{ + struct device *dev = &st->sd.spi->dev; + int ret; + + if (ref_sel == AD7173_SETUP_REF_SEL_INT_REF && !st->info->has_int_ref) + return dev_err_probe(dev, -EINVAL, + "Internal reference is not available on current model.\n"); + + if (ref_sel == AD7173_SETUP_REF_SEL_EXT_REF2 && !st->info->has_ref2) + return dev_err_probe(dev, -EINVAL, + "External reference 2 is not available on current model.\n"); + + ret = ad7173_get_ref_voltage_milli(st, ref_sel); + if (ret < 0) + return dev_err_probe(dev, ret, "Cannot use reference %u\n", + ref_sel); + + return 0; +} + +static int ad7173_validate_openwire_ain_inputs(struct ad7173_state *st, + bool differential, + unsigned int ain0, + unsigned int ain1) +{ + /* + * If the channel is configured as differential, + * the ad4111 requires specific ains to be used together + */ + if (differential) + return (ain0 % 2) ? (ain0 - 1) == ain1 : (ain0 + 1) == ain1; + + return ain1 == AD4111_VINCOM_INPUT; +} + +static unsigned int ad7173_calc_openwire_thrsh_raw(struct ad7173_state *st, + struct iio_chan_spec *chan, + struct ad7173_channel *chan_st_priv, + unsigned int thrsh_mv) +{ + unsigned int thrsh_raw; + + thrsh_raw = + BIT(chan->scan_type.realbits - !!(chan_st_priv->cfg.bipolar)) + * thrsh_mv + / ad7173_get_ref_voltage_milli(st, chan_st_priv->cfg.ref_sel); + if (chan->channel < st->info->num_voltage_in_div) + thrsh_raw /= AD4111_DIVIDER_RATIO; + + return thrsh_raw; +} + +static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev) +{ + struct ad7173_channel *chans_st_arr, *chan_st_priv; + struct ad7173_state *st = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + struct iio_chan_spec *chan_arr, *chan; + unsigned int ain[AD7173_NO_AINS_PER_CHANNEL], chan_index = 0; + int ref_sel, ret, num_channels; + + num_channels = device_get_child_node_count(dev); + + if (st->info->has_temp) + num_channels++; + + if (num_channels == 0) + return dev_err_probe(dev, -ENODATA, "No channels specified\n"); + + if (num_channels > st->info->num_channels) + return dev_err_probe(dev, -EINVAL, + "Too many channels specified. Maximum is %d, not including temperature channel if supported.\n", + st->info->num_channels); + + indio_dev->num_channels = num_channels; + st->num_channels = num_channels; + + chan_arr = devm_kcalloc(dev, sizeof(*indio_dev->channels), + st->num_channels, GFP_KERNEL); + if (!chan_arr) + return -ENOMEM; + + chans_st_arr = devm_kcalloc(dev, st->num_channels, sizeof(*st->channels), + GFP_KERNEL); + if (!chans_st_arr) + return -ENOMEM; + + indio_dev->channels = chan_arr; + st->channels = chans_st_arr; + + if (st->info->has_temp) { + chan_arr[chan_index] = ad7173_temp_iio_channel_template; + chan_st_priv = &chans_st_arr[chan_index]; + chan_st_priv->ain = + AD7173_CH_ADDRESS(chan_arr[chan_index].channel, + chan_arr[chan_index].channel2); + chan_st_priv->cfg.bipolar = false; + chan_st_priv->cfg.input_buf = st->info->has_input_buf; + chan_st_priv->cfg.ref_sel = AD7173_SETUP_REF_SEL_INT_REF; + chan_st_priv->cfg.sinc3_odr_div = ad7173_sinc3_odr_div_from_odr( + st->info->sinc5_data_rates[st->info->odr_start_value] + ); + chan_st_priv->cfg.sinc5_odr_index = st->info->odr_start_value; + chan_st_priv->cfg.filter_type = AD7173_FILTER_SINC5_SINC1; + chan_st_priv->cfg.openwire_comp_chan = -1; + st->adc_mode |= AD7173_ADC_MODE_REF_EN; + if (st->info->data_reg_only_16bit) + chan_arr[chan_index].scan_type = ad4113_scan_type; + + if (ad_sigma_delta_has_spi_offload(&st->sd)) { + chan_arr[chan_index].scan_type.storagebits = 32; + chan_arr[chan_index].scan_type.endianness = IIO_CPU; + } + + chan_index++; + } + + device_for_each_child_node_scoped(dev, child) { + bool is_current_chan = false; + + chan = &chan_arr[chan_index]; + *chan = ad7173_channel_template; + chan_st_priv = &chans_st_arr[chan_index]; + ret = fwnode_property_read_u32_array(child, "diff-channels", + ain, ARRAY_SIZE(ain)); + if (ret) { + ret = fwnode_property_read_u32(child, "single-channel", + ain); + if (ret) + return dev_err_probe(dev, ret, + "Channel must define one of diff-channels or single-channel.\n"); + + is_current_chan = fwnode_property_read_bool(child, "adi,current-channel"); + } else { + chan->differential = true; + } + + if (is_current_chan) { + ret = ad4111_validate_current_ain(st, ain); + if (ret) + return ret; + } else { + if (!chan->differential) { + ret = fwnode_property_read_u32(child, + "common-mode-channel", ain + 1); + if (ret) + return dev_err_probe(dev, ret, + "common-mode-channel must be defined for single-ended channels.\n"); + } + ret = ad7173_validate_voltage_ain_inputs(st, ain[0], ain[1]); + if (ret) + return ret; + } + + ret = fwnode_property_match_property_string(child, + "adi,reference-select", + ad7173_ref_sel_str, + ARRAY_SIZE(ad7173_ref_sel_str)); + if (ret < 0) + ref_sel = AD7173_SETUP_REF_SEL_INT_REF; + else + ref_sel = ret; + + ret = ad7173_validate_reference(st, ref_sel); + if (ret) + return ret; + + if (ref_sel == AD7173_SETUP_REF_SEL_INT_REF) + st->adc_mode |= AD7173_ADC_MODE_REF_EN; + chan_st_priv->cfg.ref_sel = ref_sel; + + chan->address = chan_index; + chan->scan_index = chan_index; + chan->channel = ain[0]; + chan_st_priv->cfg.input_buf = st->info->has_input_buf; + chan_st_priv->cfg.sinc3_odr_div = ad7173_sinc3_odr_div_from_odr( + st->info->sinc5_data_rates[st->info->odr_start_value] + ); + chan_st_priv->cfg.sinc5_odr_index = st->info->odr_start_value; + chan_st_priv->cfg.filter_type = AD7173_FILTER_SINC5_SINC1; + chan_st_priv->cfg.openwire_comp_chan = -1; + + chan_st_priv->cfg.bipolar = fwnode_property_read_bool(child, "bipolar"); + if (chan_st_priv->cfg.bipolar) + chan->info_mask_separate |= BIT(IIO_CHAN_INFO_OFFSET); + + if (is_current_chan) { + chan->type = IIO_CURRENT; + chan->differential = false; + chan->channel2 = 0; + chan_st_priv->ain = ad4111_current_channel_config[ain[0]]; + } else { + chan_st_priv->cfg.input_buf = st->info->has_input_buf; + chan->channel2 = ain[1]; + chan_st_priv->ain = AD7173_CH_ADDRESS(ain[0], ain[1]); + if (st->info->has_openwire_det && + ad7173_validate_openwire_ain_inputs(st, chan->differential, ain[0], ain[1])) { + chan->event_spec = ad4111_events; + chan->num_event_specs = ARRAY_SIZE(ad4111_events); + chan_st_priv->cfg.openwire_thrsh_raw = + ad7173_calc_openwire_thrsh_raw(st, chan, chan_st_priv, + AD4111_OW_DET_THRSH_MV); + } + } + + if (st->info->data_reg_only_16bit) + chan_arr[chan_index].scan_type = ad4113_scan_type; + + /* Assuming SPI offload is ad411x_ad717x HDL project. */ + if (ad_sigma_delta_has_spi_offload(&st->sd)) { + chan_arr[chan_index].scan_type.storagebits = 32; + chan_arr[chan_index].scan_type.endianness = IIO_CPU; + } + + chan_index++; + } + return 0; +} + +static int ad7173_fw_parse_device_config(struct iio_dev *indio_dev) +{ + struct ad7173_state *st = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + int ret; + + st->regulators[0].supply = ad7173_ref_sel_str[AD7173_SETUP_REF_SEL_EXT_REF]; + st->regulators[1].supply = ad7173_ref_sel_str[AD7173_SETUP_REF_SEL_EXT_REF2]; + st->regulators[2].supply = ad7173_ref_sel_str[AD7173_SETUP_REF_SEL_AVDD1_AVSS]; + + /* + * If a regulator is not available, it will be set to a dummy regulator. + * Each channel reference is checked with regulator_get_voltage() before + * setting attributes so if any channel uses a dummy supply the driver + * probe will fail. + */ + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(st->regulators), + st->regulators); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + ret = regulator_bulk_enable(ARRAY_SIZE(st->regulators), st->regulators); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulators\n"); + + ret = devm_add_action_or_reset(dev, ad7173_disable_regulators, st); + if (ret) + return ret; + + ret = device_property_match_property_string(dev, "clock-names", + ad7173_clk_sel, + ARRAY_SIZE(ad7173_clk_sel)); + if (ret < 0) { + st->adc_mode |= FIELD_PREP(AD7173_ADC_MODE_CLOCKSEL_MASK, + AD7173_ADC_MODE_CLOCKSEL_INT); + ad7173_register_clk_provider(indio_dev); + } else { + struct clk *clk; + + st->adc_mode |= FIELD_PREP(AD7173_ADC_MODE_CLOCKSEL_MASK, + AD7173_ADC_MODE_CLOCKSEL_EXT + ret); + clk = devm_clk_get_enabled(dev, ad7173_clk_sel[ret]); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), + "Failed to get external clock\n"); + } + + return ad7173_fw_parse_channel_config(indio_dev); +} + +static int ad7173_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct ad7173_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->info = spi_get_device_match_data(spi); + if (!st->info) + return -ENODEV; + + ida_init(&st->cfg_slots_status); + ret = devm_add_action_or_reset(dev, ad7173_ida_destroy, st); + if (ret) + return ret; + + indio_dev->name = st->info->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ad7173_info; + + spi->mode = SPI_MODE_3; + ret = spi_setup(spi); + if (ret) + return ret; + + ret = ad_sd_init(&st->sd, indio_dev, spi, st->info->sd_info); + if (ret) + return ret; + + ret = ad7173_fw_parse_device_config(indio_dev); + if (ret) + return ret; + + ret = devm_ad_sd_setup_buffer_and_trigger(dev, indio_dev); + if (ret) + return ret; + + ret = ad7173_setup(indio_dev); + if (ret) + return ret; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ret; + + return ad7173_gpio_init(st); +} + +static const struct of_device_id ad7173_of_match[] = { + { .compatible = "adi,ad4111", .data = &ad4111_device_info }, + { .compatible = "adi,ad4112", .data = &ad4112_device_info }, + { .compatible = "adi,ad4113", .data = &ad4113_device_info }, + { .compatible = "adi,ad4114", .data = &ad4114_device_info }, + { .compatible = "adi,ad4115", .data = &ad4115_device_info }, + { .compatible = "adi,ad4116", .data = &ad4116_device_info }, + { .compatible = "adi,ad7172-2", .data = &ad7172_2_device_info }, + { .compatible = "adi,ad7172-4", .data = &ad7172_4_device_info }, + { .compatible = "adi,ad7173-8", .data = &ad7173_8_device_info }, + { .compatible = "adi,ad7175-2", .data = &ad7175_2_device_info }, + { .compatible = "adi,ad7175-8", .data = &ad7175_8_device_info }, + { .compatible = "adi,ad7176-2", .data = &ad7176_2_device_info }, + { .compatible = "adi,ad7177-2", .data = &ad7177_2_device_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7173_of_match); + +static const struct spi_device_id ad7173_id_table[] = { + { .name = "ad4111", .driver_data = (kernel_ulong_t)&ad4111_device_info }, + { .name = "ad4112", .driver_data = (kernel_ulong_t)&ad4112_device_info }, + { .name = "ad4113", .driver_data = (kernel_ulong_t)&ad4113_device_info }, + { .name = "ad4114", .driver_data = (kernel_ulong_t)&ad4114_device_info }, + { .name = "ad4115", .driver_data = (kernel_ulong_t)&ad4115_device_info }, + { .name = "ad4116", .driver_data = (kernel_ulong_t)&ad4116_device_info }, + { .name = "ad7172-2", .driver_data = (kernel_ulong_t)&ad7172_2_device_info }, + { .name = "ad7172-4", .driver_data = (kernel_ulong_t)&ad7172_4_device_info }, + { .name = "ad7173-8", .driver_data = (kernel_ulong_t)&ad7173_8_device_info }, + { .name = "ad7175-2", .driver_data = (kernel_ulong_t)&ad7175_2_device_info }, + { .name = "ad7175-8", .driver_data = (kernel_ulong_t)&ad7175_8_device_info }, + { .name = "ad7176-2", .driver_data = (kernel_ulong_t)&ad7176_2_device_info }, + { .name = "ad7177-2", .driver_data = (kernel_ulong_t)&ad7177_2_device_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7173_id_table); + +static struct spi_driver ad7173_driver = { + .driver = { + .name = "ad7173", + .of_match_table = ad7173_of_match, + }, + .probe = ad7173_probe, + .id_table = ad7173_id_table, +}; +module_spi_driver(ad7173_driver); + +MODULE_IMPORT_NS("IIO_AD_SIGMA_DELTA"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_AUTHOR("Dumitru Ceclan "); +MODULE_DESCRIPTION("Analog Devices AD7173 and similar ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/ad7191.c b/drivers/iio/adc/ad7191.c new file mode 100644 index 000000000..071d26191 --- /dev/null +++ b/drivers/iio/adc/ad7191.c @@ -0,0 +1,556 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD7191 ADC driver + * + * Copyright 2025 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define ad_sigma_delta_to_ad7191(sigmad) \ + container_of((sigmad), struct ad7191_state, sd) + +#define AD7191_TEMP_CODES_PER_DEGREE 2815 + +#define AD7191_CHAN_MASK BIT(0) +#define AD7191_TEMP_MASK BIT(1) + +enum ad7191_channel { + AD7191_CH_AIN1_AIN2, + AD7191_CH_AIN3_AIN4, + AD7191_CH_TEMP, +}; + +/* + * NOTE: + * The AD7191 features a dual-use data out ready DOUT/RDY output. + * In order to avoid contentions on the SPI bus, it's therefore necessary + * to use SPI bus locking. + * + * The DOUT/RDY output must also be wired to an interrupt-capable GPIO. + * + * The SPI controller's chip select must be connected to the PDOWN pin + * of the ADC. When CS (PDOWN) is high, it powers down the device and + * resets the internal circuitry. + */ + +struct ad7191_state { + struct ad_sigma_delta sd; + struct mutex lock; /* Protect device state */ + + struct gpio_descs *odr_gpios; + struct gpio_descs *pga_gpios; + struct gpio_desc *temp_gpio; + struct gpio_desc *chan_gpio; + + u16 int_vref_mv; + const u32 (*scale_avail)[2]; + size_t scale_avail_size; + u32 scale_index; + const u32 *samp_freq_avail; + size_t samp_freq_avail_size; + u32 samp_freq_index; + + struct clk *mclk; +}; + +static int ad7191_set_channel(struct ad_sigma_delta *sd, unsigned int address) +{ + struct ad7191_state *st = ad_sigma_delta_to_ad7191(sd); + u8 temp_gpio_val, chan_gpio_val; + + if (!FIELD_FIT(AD7191_CHAN_MASK | AD7191_TEMP_MASK, address)) + return -EINVAL; + + chan_gpio_val = FIELD_GET(AD7191_CHAN_MASK, address); + temp_gpio_val = FIELD_GET(AD7191_TEMP_MASK, address); + + gpiod_set_value(st->chan_gpio, chan_gpio_val); + gpiod_set_value(st->temp_gpio, temp_gpio_val); + + return 0; +} + +static int ad7191_set_cs(struct ad_sigma_delta *sigma_delta, int assert) +{ + struct spi_transfer t = { + .len = 0, + .cs_change = assert, + }; + struct spi_message m; + + spi_message_init_with_transfers(&m, &t, 1); + + return spi_sync_locked(sigma_delta->spi, &m); +} + +static int ad7191_set_mode(struct ad_sigma_delta *sd, + enum ad_sigma_delta_mode mode) +{ + struct ad7191_state *st = ad_sigma_delta_to_ad7191(sd); + + switch (mode) { + case AD_SD_MODE_CONTINUOUS: + case AD_SD_MODE_SINGLE: + return ad7191_set_cs(&st->sd, 1); + case AD_SD_MODE_IDLE: + return ad7191_set_cs(&st->sd, 0); + default: + return -EINVAL; + } +} + +static const struct ad_sigma_delta_info ad7191_sigma_delta_info = { + .set_channel = ad7191_set_channel, + .set_mode = ad7191_set_mode, + .has_registers = false, +}; + +static int ad7191_init_regulators(struct iio_dev *indio_dev) +{ + struct ad7191_state *st = iio_priv(indio_dev); + struct device *dev = &st->sd.spi->dev; + int ret; + + ret = devm_regulator_get_enable(dev, "avdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable specified AVdd supply\n"); + + ret = devm_regulator_get_enable(dev, "dvdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable specified DVdd supply\n"); + + ret = devm_regulator_get_enable_read_voltage(dev, "vref"); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get Vref voltage\n"); + + st->int_vref_mv = ret / 1000; + + return 0; +} + +static int ad7191_config_setup(struct iio_dev *indio_dev) +{ + struct ad7191_state *st = iio_priv(indio_dev); + struct device *dev = &st->sd.spi->dev; + /* Sampling frequencies in Hz, see Table 5 */ + static const u32 samp_freq[4] = { 120, 60, 50, 10 }; + /* Gain options, see Table 7 */ + const u32 gain[4] = { 1, 8, 64, 128 }; + static u32 scale_buffer[4][2]; + int odr_value, odr_index = 0, pga_value, pga_index = 0, i, ret; + const char *propname; + u64 scale_uv; + + st->samp_freq_index = 0; + st->scale_index = 0; + + propname = "adi,odr-value"; + if (device_property_present(dev, propname)) { + ret = device_property_read_u32(dev, propname, &odr_value); + if (ret) + return dev_err_probe(dev, ret, "Failed to get %s.\n", propname); + + for (i = 0; i < ARRAY_SIZE(samp_freq); i++) { + if (odr_value != samp_freq[i]) + continue; + odr_index = i; + break; + } + + st->samp_freq_avail = &samp_freq[odr_index]; + st->samp_freq_avail_size = 1; + + st->odr_gpios = NULL; + } else { + st->odr_gpios = devm_gpiod_get_array(dev, "odr", GPIOD_OUT_LOW); + if (IS_ERR(st->odr_gpios)) + return dev_err_probe(dev, PTR_ERR(st->odr_gpios), + "Failed to get odr gpios.\n"); + + if (st->odr_gpios->ndescs != 2) + return dev_err_probe(dev, -EINVAL, "Expected 2 odr gpio pins.\n"); + + st->samp_freq_avail = samp_freq; + st->samp_freq_avail_size = ARRAY_SIZE(samp_freq); + } + + mutex_lock(&st->lock); + + for (i = 0; i < ARRAY_SIZE(scale_buffer); i++) { + scale_uv = ((u64)st->int_vref_mv * NANO) >> + (indio_dev->channels[0].scan_type.realbits - 1); + do_div(scale_uv, gain[i]); + scale_buffer[i][1] = do_div(scale_uv, NANO); + scale_buffer[i][0] = scale_uv; + } + + mutex_unlock(&st->lock); + + propname = "adi,pga-value"; + if (device_property_present(dev, propname)) { + ret = device_property_read_u32(dev, propname, &pga_value); + if (ret) + return dev_err_probe(dev, ret, "Failed to get %s.\n", propname); + + for (i = 0; i < ARRAY_SIZE(gain); i++) { + if (pga_value != gain[i]) + continue; + pga_index = i; + break; + } + + st->scale_avail = &scale_buffer[pga_index]; + st->scale_avail_size = 1; + + st->pga_gpios = NULL; + } else { + st->pga_gpios = devm_gpiod_get_array(dev, "pga", GPIOD_OUT_LOW); + if (IS_ERR(st->pga_gpios)) + return dev_err_probe(dev, PTR_ERR(st->pga_gpios), + "Failed to get pga gpios.\n"); + + if (st->pga_gpios->ndescs != 2) + return dev_err_probe(dev, -EINVAL, "Expected 2 pga gpio pins.\n"); + + st->scale_avail = scale_buffer; + st->scale_avail_size = ARRAY_SIZE(scale_buffer); + } + + st->temp_gpio = devm_gpiod_get(dev, "temp", GPIOD_OUT_LOW); + if (IS_ERR(st->temp_gpio)) + return dev_err_probe(dev, PTR_ERR(st->temp_gpio), + "Failed to get temp gpio.\n"); + + st->chan_gpio = devm_gpiod_get(dev, "chan", GPIOD_OUT_LOW); + if (IS_ERR(st->chan_gpio)) + return dev_err_probe(dev, PTR_ERR(st->chan_gpio), + "Failed to get chan gpio.\n"); + + return 0; +} + +static int ad7191_clock_setup(struct ad7191_state *st) +{ + struct device *dev = &st->sd.spi->dev; + + st->mclk = devm_clk_get_optional_enabled(dev, "mclk"); + if (IS_ERR(st->mclk)) + return dev_err_probe(dev, PTR_ERR(st->mclk), + "Failed to get mclk.\n"); + + return 0; +} + +static int ad7191_setup(struct iio_dev *indio_dev) +{ + struct ad7191_state *st = iio_priv(indio_dev); + int ret; + + ret = ad7191_init_regulators(indio_dev); + if (ret) + return ret; + + ret = ad7191_config_setup(indio_dev); + if (ret) + return ret; + + return ad7191_clock_setup(st); +} + +static int ad7191_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long m) +{ + struct ad7191_state *st = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + return ad_sigma_delta_single_conversion(indio_dev, chan, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: { + guard(mutex)(&st->lock); + *val = st->scale_avail[st->scale_index][0]; + *val2 = st->scale_avail[st->scale_index][1]; + return IIO_VAL_INT_PLUS_NANO; + } + case IIO_TEMP: + *val = 0; + *val2 = NANO / AD7191_TEMP_CODES_PER_DEGREE; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + *val = -(1 << (chan->scan_type.realbits - 1)); + switch (chan->type) { + case IIO_VOLTAGE: + return IIO_VAL_INT; + case IIO_TEMP: + *val -= 273 * AD7191_TEMP_CODES_PER_DEGREE; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + *val = st->samp_freq_avail[st->samp_freq_index]; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad7191_set_gain(struct ad7191_state *st, int gain_index) +{ + DECLARE_BITMAP(bitmap, 2) = { }; + + st->scale_index = gain_index; + + bitmap_write(bitmap, gain_index, 0, 2); + + return gpiod_multi_set_value_cansleep(st->pga_gpios, bitmap); +} + +static int ad7191_set_samp_freq(struct ad7191_state *st, int samp_freq_index) +{ + DECLARE_BITMAP(bitmap, 2) = {}; + + st->samp_freq_index = samp_freq_index; + + bitmap_write(bitmap, samp_freq_index, 0, 2); + + return gpiod_multi_set_value_cansleep(st->odr_gpios, bitmap); +} + +static int __ad7191_write_raw(struct ad7191_state *st, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int i; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: { + if (!st->pga_gpios) + return -EPERM; + guard(mutex)(&st->lock); + for (i = 0; i < st->scale_avail_size; i++) { + if (val2 == st->scale_avail[i][1]) + return ad7191_set_gain(st, i); + } + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: { + if (!st->odr_gpios) + return -EPERM; + guard(mutex)(&st->lock); + for (i = 0; i < st->samp_freq_avail_size; i++) { + if (val == st->samp_freq_avail[i]) + return ad7191_set_samp_freq(st, i); + } + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ad7191_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, + long mask) +{ + struct ad7191_state *st = iio_priv(indio_dev); + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = __ad7191_write_raw(st, chan, val, val2, mask); + + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ad7191_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad7191_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, const int **vals, + int *type, int *length, long mask) +{ + struct ad7191_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = (int *)st->scale_avail; + *type = IIO_VAL_INT_PLUS_NANO; + *length = st->scale_avail_size * 2; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = (int *)st->samp_freq_avail; + *type = IIO_VAL_INT; + *length = st->samp_freq_avail_size; + return IIO_AVAIL_LIST; + } + + return -EINVAL; +} + +static const struct iio_info ad7191_info = { + .read_raw = ad7191_read_raw, + .write_raw = ad7191_write_raw, + .write_raw_get_fmt = ad7191_write_raw_get_fmt, + .read_avail = ad7191_read_avail, + .validate_trigger = ad_sd_validate_trigger, +}; + +static const struct iio_chan_spec ad7191_channels[] = { + { + .type = IIO_TEMP, + .address = AD7191_CH_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_BE, + }, + }, + { + .type = IIO_VOLTAGE, + .differential = 1, + .indexed = 1, + .channel = 1, + .channel2 = 2, + .address = AD7191_CH_AIN1_AIN2, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 1, + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_BE, + }, + }, + { + .type = IIO_VOLTAGE, + .differential = 1, + .indexed = 1, + .channel = 3, + .channel2 = 4, + .address = AD7191_CH_AIN3_AIN4, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 2, + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_BE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static int ad7191_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct ad7191_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + indio_dev->name = "ad7191"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad7191_channels; + indio_dev->num_channels = ARRAY_SIZE(ad7191_channels); + indio_dev->info = &ad7191_info; + + ret = ad_sd_init(&st->sd, indio_dev, spi, &ad7191_sigma_delta_info); + if (ret) + return ret; + + ret = devm_ad_sd_setup_buffer_and_trigger(dev, indio_dev); + if (ret) + return ret; + + ret = ad7191_setup(indio_dev); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ad7191_of_match[] = { + { .compatible = "adi,ad7191", }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7191_of_match); + +static const struct spi_device_id ad7191_id_table[] = { + { .name = "ad7191" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7191_id_table); + +static struct spi_driver ad7191_driver = { + .driver = { + .name = "ad7191", + .of_match_table = ad7191_of_match, + }, + .probe = ad7191_probe, + .id_table = ad7191_id_table, +}; +module_spi_driver(ad7191_driver); + +MODULE_AUTHOR("Alisa-Dariana Roman "); +MODULE_DESCRIPTION("Analog Devices AD7191 ADC"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_AD_SIGMA_DELTA"); diff --git a/drivers/iio/adc/ad7192.c b/drivers/iio/adc/ad7192.c new file mode 100644 index 000000000..d8f5886fc --- /dev/null +++ b/drivers/iio/adc/ad7192.c @@ -0,0 +1,1473 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AD7192 and similar SPI ADC driver + * + * Copyright 2011-2015 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* Registers */ +#define AD7192_REG_COMM 0 /* Communications Register (WO, 8-bit) */ +#define AD7192_REG_STAT 0 /* Status Register (RO, 8-bit) */ +#define AD7192_REG_MODE 1 /* Mode Register (RW, 24-bit */ +#define AD7192_REG_CONF 2 /* Configuration Register (RW, 24-bit) */ +#define AD7192_REG_DATA 3 /* Data Register (RO, 24/32-bit) */ +#define AD7192_REG_ID 4 /* ID Register (RO, 8-bit) */ +#define AD7192_REG_GPOCON 5 /* GPOCON Register (RW, 8-bit) */ +#define AD7192_REG_OFFSET 6 /* Offset Register (RW, 16-bit */ + /* (AD7792)/24-bit (AD7192)) */ +#define AD7192_REG_FULLSALE 7 /* Full-Scale Register */ + /* (RW, 16-bit (AD7792)/24-bit (AD7192)) */ + +/* Communications Register Bit Designations (AD7192_REG_COMM) */ +#define AD7192_COMM_WEN BIT(7) /* Write Enable */ +#define AD7192_COMM_WRITE 0 /* Write Operation */ +#define AD7192_COMM_READ BIT(6) /* Read Operation */ +#define AD7192_COMM_ADDR_MASK GENMASK(5, 3) /* Register Address Mask */ +#define AD7192_COMM_CREAD BIT(2) /* Continuous Read of Data Register */ + +/* Status Register Bit Designations (AD7192_REG_STAT) */ +#define AD7192_STAT_RDY BIT(7) /* Ready */ +#define AD7192_STAT_ERR BIT(6) /* Error (Overrange, Underrange) */ +#define AD7192_STAT_NOREF BIT(5) /* Error no external reference */ +#define AD7192_STAT_PARITY BIT(4) /* Parity */ +#define AD7192_STAT_CH3 BIT(2) /* Channel 3 */ +#define AD7192_STAT_CH2 BIT(1) /* Channel 2 */ +#define AD7192_STAT_CH1 BIT(0) /* Channel 1 */ + +/* Mode Register Bit Designations (AD7192_REG_MODE) */ +#define AD7192_MODE_SEL_MASK GENMASK(23, 21) /* Operation Mode Select Mask */ +#define AD7192_MODE_STA_MASK BIT(20) /* Status Register transmission Mask */ +#define AD7192_MODE_CLKSRC_MASK GENMASK(19, 18) /* Clock Source Select Mask */ +#define AD7192_MODE_AVG_MASK GENMASK(17, 16) + /* Fast Settling Filter Average Select Mask (AD7193 only) */ +#define AD7192_MODE_SINC3 BIT(15) /* SINC3 Filter Select */ +#define AD7192_MODE_ENPAR BIT(13) /* Parity Enable */ +#define AD7192_MODE_CLKDIV BIT(12) /* Clock divide by 2 (AD7190/2 only)*/ +#define AD7192_MODE_SCYCLE BIT(11) /* Single cycle conversion */ +#define AD7192_MODE_REJ60 BIT(10) /* 50/60Hz notch filter */ + /* Filter Update Rate Select Mask */ +#define AD7192_MODE_RATE_MASK GENMASK(9, 0) + +/* Mode Register: AD7192_MODE_SEL options */ +#define AD7192_MODE_CONT 0 /* Continuous Conversion Mode */ +#define AD7192_MODE_SINGLE 1 /* Single Conversion Mode */ +#define AD7192_MODE_IDLE 2 /* Idle Mode */ +#define AD7192_MODE_PWRDN 3 /* Power-Down Mode */ +#define AD7192_MODE_CAL_INT_ZERO 4 /* Internal Zero-Scale Calibration */ +#define AD7192_MODE_CAL_INT_FULL 5 /* Internal Full-Scale Calibration */ +#define AD7192_MODE_CAL_SYS_ZERO 6 /* System Zero-Scale Calibration */ +#define AD7192_MODE_CAL_SYS_FULL 7 /* System Full-Scale Calibration */ + +/* Mode Register: AD7192_MODE_CLKSRC options */ +#define AD7192_CLK_EXT_MCLK1_2 0 /* External 4.92 MHz Clock connected*/ + /* from MCLK1 to MCLK2 */ +#define AD7192_CLK_EXT_MCLK2 1 /* External Clock applied to MCLK2 */ +#define AD7192_CLK_INT 2 /* Internal 4.92 MHz Clock not */ + /* available at the MCLK2 pin */ +#define AD7192_CLK_INT_CO 3 /* Internal 4.92 MHz Clock available*/ + /* at the MCLK2 pin */ + +/* Configuration Register Bit Designations (AD7192_REG_CONF) */ + +#define AD7192_CONF_CHOP BIT(23) /* CHOP enable */ +#define AD7192_CONF_ACX BIT(22) /* AC excitation enable(AD7195 only) */ +#define AD7192_CONF_REFSEL BIT(20) /* REFIN1/REFIN2 Reference Select */ +#define AD7192_CONF_CHAN_MASK GENMASK(18, 8) /* Channel select mask */ +#define AD7192_CONF_BURN BIT(7) /* Burnout current enable */ +#define AD7192_CONF_REFDET BIT(6) /* Reference detect enable */ +#define AD7192_CONF_BUF BIT(4) /* Buffered Mode Enable */ +#define AD7192_CONF_UNIPOLAR BIT(3) /* Unipolar/Bipolar Enable */ +#define AD7192_CONF_GAIN_MASK GENMASK(2, 0) /* Gain Select */ + +#define AD7192_CH_AIN1P_AIN2M BIT(0) /* AIN1(+) - AIN2(-) */ +#define AD7192_CH_AIN3P_AIN4M BIT(1) /* AIN3(+) - AIN4(-) */ +#define AD7192_CH_TEMP BIT(2) /* Temp Sensor */ +#define AD7192_CH_AIN2P_AIN2M BIT(3) /* AIN2(+) - AIN2(-) */ +#define AD7192_CH_AIN1 BIT(4) /* AIN1 - AINCOM */ +#define AD7192_CH_AIN2 BIT(5) /* AIN2 - AINCOM */ +#define AD7192_CH_AIN3 BIT(6) /* AIN3 - AINCOM */ +#define AD7192_CH_AIN4 BIT(7) /* AIN4 - AINCOM */ + +#define AD7193_CH_AIN1P_AIN2M 0x001 /* AIN1(+) - AIN2(-) */ +#define AD7193_CH_AIN3P_AIN4M 0x002 /* AIN3(+) - AIN4(-) */ +#define AD7193_CH_AIN5P_AIN6M 0x004 /* AIN5(+) - AIN6(-) */ +#define AD7193_CH_AIN7P_AIN8M 0x008 /* AIN7(+) - AIN8(-) */ +#define AD7193_CH_TEMP 0x100 /* Temp senseor */ +#define AD7193_CH_AIN2P_AIN2M 0x200 /* AIN2(+) - AIN2(-) */ +#define AD7193_CH_AIN1 0x401 /* AIN1 - AINCOM */ +#define AD7193_CH_AIN2 0x402 /* AIN2 - AINCOM */ +#define AD7193_CH_AIN3 0x404 /* AIN3 - AINCOM */ +#define AD7193_CH_AIN4 0x408 /* AIN4 - AINCOM */ +#define AD7193_CH_AIN5 0x410 /* AIN5 - AINCOM */ +#define AD7193_CH_AIN6 0x420 /* AIN6 - AINCOM */ +#define AD7193_CH_AIN7 0x440 /* AIN7 - AINCOM */ +#define AD7193_CH_AIN8 0x480 /* AIN7 - AINCOM */ +#define AD7193_CH_AINCOM 0x600 /* AINCOM - AINCOM */ + +#define AD7194_CH_POS(x) (((x) - 1) << 4) +#define AD7194_CH_NEG(x) ((x) - 1) + +/* 10th bit corresponds to CON18(Pseudo) */ +#define AD7194_CH(p) (BIT(10) | AD7194_CH_POS(p)) + +#define AD7194_DIFF_CH(p, n) (AD7194_CH_POS(p) | AD7194_CH_NEG(n)) +#define AD7194_CH_TEMP 0x100 +#define AD7194_CH_BASE_NR 2 +#define AD7194_CH_AIN_START 1 +#define AD7194_CH_AIN_NR 16 +#define AD7194_CH_MAX_NR 272 + +/* ID Register Bit Designations (AD7192_REG_ID) */ +#define CHIPID_AD7190 0x4 +#define CHIPID_AD7192 0x0 +#define CHIPID_AD7193 0x2 +#define CHIPID_AD7194 0x3 +#define CHIPID_AD7195 0x6 +#define AD7192_ID_MASK GENMASK(3, 0) + +/* GPOCON Register Bit Designations (AD7192_REG_GPOCON) */ +#define AD7192_GPOCON_BPDSW BIT(6) /* Bridge power-down switch enable */ +#define AD7192_GPOCON_GP32EN BIT(5) /* Digital Output P3 and P2 enable */ +#define AD7192_GPOCON_GP10EN BIT(4) /* Digital Output P1 and P0 enable */ +#define AD7192_GPOCON_P3DAT BIT(3) /* P3 state */ +#define AD7192_GPOCON_P2DAT BIT(2) /* P2 state */ +#define AD7192_GPOCON_P1DAT BIT(1) /* P1 state */ +#define AD7192_GPOCON_P0DAT BIT(0) /* P0 state */ + +#define AD7192_EXT_FREQ_MHZ_MIN 2457600 +#define AD7192_EXT_FREQ_MHZ_MAX 5120000 +#define AD7192_INT_FREQ_MHZ 4915200 + +#define AD7192_NO_SYNC_FILTER 1 +#define AD7192_SYNC3_FILTER 3 +#define AD7192_SYNC4_FILTER 4 + +/* NOTE: + * The AD7190/2/5 features a dual use data out ready DOUT/RDY output. + * In order to avoid contentions on the SPI bus, it's therefore necessary + * to use spi bus locking. + * + * The DOUT/RDY output must also be wired to an interrupt capable GPIO. + */ + +enum { + AD7192_SYSCALIB_ZERO_SCALE, + AD7192_SYSCALIB_FULL_SCALE, +}; + +enum { + ID_AD7190, + ID_AD7192, + ID_AD7193, + ID_AD7194, + ID_AD7195, +}; + +struct ad7192_chip_info { + unsigned int chip_id; + const char *name; + const struct iio_chan_spec *channels; + u8 num_channels; + const struct ad_sigma_delta_info *sigma_delta_info; + const struct iio_info *info; + int (*parse_channels)(struct iio_dev *indio_dev); +}; + +struct ad7192_state { + const struct ad7192_chip_info *chip_info; + struct clk *mclk; + struct clk_hw int_clk_hw; + u16 int_vref_mv; + u32 aincom_mv; + u32 fclk; + u32 mode; + u32 conf; + u32 scale_avail[8][2]; + u32 filter_freq_avail[4][2]; + u32 oversampling_ratio_avail[4]; + u8 gpocon; + u8 clock_sel; + struct mutex lock; /* protect sensor state */ + u8 syscalib_mode[8]; + + struct ad_sigma_delta sd; +}; + +static const char * const ad7192_syscalib_modes[] = { + [AD7192_SYSCALIB_ZERO_SCALE] = "zero_scale", + [AD7192_SYSCALIB_FULL_SCALE] = "full_scale", +}; + +static int ad7192_set_syscalib_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct ad7192_state *st = iio_priv(indio_dev); + + st->syscalib_mode[chan->channel] = mode; + + return 0; +} + +static int ad7192_get_syscalib_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad7192_state *st = iio_priv(indio_dev); + + return st->syscalib_mode[chan->channel]; +} + +static ssize_t ad7192_write_syscalib(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ad7192_state *st = iio_priv(indio_dev); + bool sys_calib; + int ret, temp; + + ret = kstrtobool(buf, &sys_calib); + if (ret) + return ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + temp = st->syscalib_mode[chan->channel]; + if (sys_calib) { + if (temp == AD7192_SYSCALIB_ZERO_SCALE) + ret = ad_sd_calibrate(&st->sd, AD7192_MODE_CAL_SYS_ZERO, + chan->address); + else + ret = ad_sd_calibrate(&st->sd, AD7192_MODE_CAL_SYS_FULL, + chan->address); + } + + iio_device_release_direct(indio_dev); + + return ret ? ret : len; +} + +static const struct iio_enum ad7192_syscalib_mode_enum = { + .items = ad7192_syscalib_modes, + .num_items = ARRAY_SIZE(ad7192_syscalib_modes), + .set = ad7192_set_syscalib_mode, + .get = ad7192_get_syscalib_mode +}; + +static const struct iio_chan_spec_ext_info ad7192_calibsys_ext_info[] = { + { + .name = "sys_calibration", + .write = ad7192_write_syscalib, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("sys_calibration_mode", IIO_SEPARATE, + &ad7192_syscalib_mode_enum), + IIO_ENUM_AVAILABLE("sys_calibration_mode", IIO_SHARED_BY_TYPE, + &ad7192_syscalib_mode_enum), + { } +}; + +static struct ad7192_state *ad_sigma_delta_to_ad7192(struct ad_sigma_delta *sd) +{ + return container_of(sd, struct ad7192_state, sd); +} + +static int ad7192_set_channel(struct ad_sigma_delta *sd, unsigned int channel) +{ + struct ad7192_state *st = ad_sigma_delta_to_ad7192(sd); + + st->conf &= ~AD7192_CONF_CHAN_MASK; + st->conf |= FIELD_PREP(AD7192_CONF_CHAN_MASK, channel); + + return ad_sd_write_reg(&st->sd, AD7192_REG_CONF, 3, st->conf); +} + +static int ad7192_set_mode(struct ad_sigma_delta *sd, + enum ad_sigma_delta_mode mode) +{ + struct ad7192_state *st = ad_sigma_delta_to_ad7192(sd); + + st->mode &= ~AD7192_MODE_SEL_MASK; + st->mode |= FIELD_PREP(AD7192_MODE_SEL_MASK, mode); + + return ad_sd_write_reg(&st->sd, AD7192_REG_MODE, 3, st->mode); +} + +static int ad7192_append_status(struct ad_sigma_delta *sd, bool append) +{ + struct ad7192_state *st = ad_sigma_delta_to_ad7192(sd); + unsigned int mode = st->mode; + int ret; + + mode &= ~AD7192_MODE_STA_MASK; + mode |= FIELD_PREP(AD7192_MODE_STA_MASK, append); + + ret = ad_sd_write_reg(&st->sd, AD7192_REG_MODE, 3, mode); + if (ret < 0) + return ret; + + st->mode = mode; + + return 0; +} + +static int ad7192_disable_all(struct ad_sigma_delta *sd) +{ + struct ad7192_state *st = ad_sigma_delta_to_ad7192(sd); + u32 conf = st->conf; + int ret; + + conf &= ~AD7192_CONF_CHAN_MASK; + + ret = ad_sd_write_reg(&st->sd, AD7192_REG_CONF, 3, conf); + if (ret < 0) + return ret; + + st->conf = conf; + + return 0; +} + +static const struct ad_sigma_delta_info ad7192_sigma_delta_info = { + .set_channel = ad7192_set_channel, + .append_status = ad7192_append_status, + .disable_all = ad7192_disable_all, + .set_mode = ad7192_set_mode, + .has_registers = true, + .addr_shift = 3, + .read_mask = BIT(6), + .status_ch_mask = GENMASK(3, 0), + .num_slots = 4, + .irq_flags = IRQF_TRIGGER_FALLING, + .num_resetclks = 40, +}; + +static const struct ad_sigma_delta_info ad7194_sigma_delta_info = { + .set_channel = ad7192_set_channel, + .append_status = ad7192_append_status, + .disable_all = ad7192_disable_all, + .set_mode = ad7192_set_mode, + .has_registers = true, + .addr_shift = 3, + .read_mask = BIT(6), + .status_ch_mask = GENMASK(3, 0), + .irq_flags = IRQF_TRIGGER_FALLING, + .num_resetclks = 40, +}; + +static const struct ad_sd_calib_data ad7192_calib_arr[8] = { + {AD7192_MODE_CAL_INT_ZERO, AD7192_CH_AIN1}, + {AD7192_MODE_CAL_INT_FULL, AD7192_CH_AIN1}, + {AD7192_MODE_CAL_INT_ZERO, AD7192_CH_AIN2}, + {AD7192_MODE_CAL_INT_FULL, AD7192_CH_AIN2}, + {AD7192_MODE_CAL_INT_ZERO, AD7192_CH_AIN3}, + {AD7192_MODE_CAL_INT_FULL, AD7192_CH_AIN3}, + {AD7192_MODE_CAL_INT_ZERO, AD7192_CH_AIN4}, + {AD7192_MODE_CAL_INT_FULL, AD7192_CH_AIN4} +}; + +static int ad7192_calibrate_all(struct ad7192_state *st) +{ + return ad_sd_calibrate_all(&st->sd, ad7192_calib_arr, + ARRAY_SIZE(ad7192_calib_arr)); +} + +static inline bool ad7192_valid_external_frequency(u32 freq) +{ + return (freq >= AD7192_EXT_FREQ_MHZ_MIN && + freq <= AD7192_EXT_FREQ_MHZ_MAX); +} + +/* + * Position 0 of ad7192_clock_names, xtal, corresponds to clock source + * configuration AD7192_CLK_EXT_MCLK1_2 and position 1, mclk, corresponds to + * AD7192_CLK_EXT_MCLK2 + */ +static const char *const ad7192_clock_names[] = { + "xtal", + "mclk" +}; + +static struct ad7192_state *clk_hw_to_ad7192(struct clk_hw *hw) +{ + return container_of(hw, struct ad7192_state, int_clk_hw); +} + +static unsigned long ad7192_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + return AD7192_INT_FREQ_MHZ; +} + +static int ad7192_clk_output_is_enabled(struct clk_hw *hw) +{ + struct ad7192_state *st = clk_hw_to_ad7192(hw); + + return st->clock_sel == AD7192_CLK_INT_CO; +} + +static int ad7192_clk_prepare(struct clk_hw *hw) +{ + struct ad7192_state *st = clk_hw_to_ad7192(hw); + int ret; + + st->mode &= ~AD7192_MODE_CLKSRC_MASK; + st->mode |= AD7192_CLK_INT_CO; + + ret = ad_sd_write_reg(&st->sd, AD7192_REG_MODE, 3, st->mode); + if (ret) + return ret; + + st->clock_sel = AD7192_CLK_INT_CO; + + return 0; +} + +static void ad7192_clk_unprepare(struct clk_hw *hw) +{ + struct ad7192_state *st = clk_hw_to_ad7192(hw); + int ret; + + st->mode &= ~AD7192_MODE_CLKSRC_MASK; + st->mode |= AD7192_CLK_INT; + + ret = ad_sd_write_reg(&st->sd, AD7192_REG_MODE, 3, st->mode); + if (ret) + return; + + st->clock_sel = AD7192_CLK_INT; +} + +static const struct clk_ops ad7192_int_clk_ops = { + .recalc_rate = ad7192_clk_recalc_rate, + .is_enabled = ad7192_clk_output_is_enabled, + .prepare = ad7192_clk_prepare, + .unprepare = ad7192_clk_unprepare, +}; + +static int ad7192_register_clk_provider(struct ad7192_state *st) +{ + struct device *dev = &st->sd.spi->dev; + struct clk_init_data init = {}; + int ret; + + if (!IS_ENABLED(CONFIG_COMMON_CLK)) + return 0; + + if (!device_property_present(dev, "#clock-cells")) + return 0; + + init.name = devm_kasprintf(dev, GFP_KERNEL, "%s-clk", + fwnode_get_name(dev_fwnode(dev))); + if (!init.name) + return -ENOMEM; + + init.ops = &ad7192_int_clk_ops; + + st->int_clk_hw.init = &init; + ret = devm_clk_hw_register(dev, &st->int_clk_hw); + if (ret) + return ret; + + return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + &st->int_clk_hw); +} + +static int ad7192_clock_setup(struct ad7192_state *st) +{ + struct device *dev = &st->sd.spi->dev; + int ret; + + /* + * The following two if branches are kept for backward compatibility but + * the use of the two devicetree properties is highly discouraged. Clock + * configuration should be done according to the bindings. + */ + + if (device_property_read_bool(dev, "adi,int-clock-output-enable")) { + st->clock_sel = AD7192_CLK_INT_CO; + st->fclk = AD7192_INT_FREQ_MHZ; + dev_warn(dev, "Property adi,int-clock-output-enable is deprecated! Check bindings!\n"); + return 0; + } + + if (device_property_read_bool(dev, "adi,clock-xtal")) { + st->clock_sel = AD7192_CLK_EXT_MCLK1_2; + st->mclk = devm_clk_get_enabled(dev, "mclk"); + if (IS_ERR(st->mclk)) + return dev_err_probe(dev, PTR_ERR(st->mclk), + "Failed to get mclk\n"); + + st->fclk = clk_get_rate(st->mclk); + if (!ad7192_valid_external_frequency(st->fclk)) + return dev_err_probe(dev, -EINVAL, + "External clock frequency out of bounds\n"); + + dev_warn(dev, "Property adi,clock-xtal is deprecated! Check bindings!\n"); + return 0; + } + + ret = device_property_match_property_string(dev, "clock-names", + ad7192_clock_names, + ARRAY_SIZE(ad7192_clock_names)); + if (ret < 0) { + st->clock_sel = AD7192_CLK_INT; + st->fclk = AD7192_INT_FREQ_MHZ; + + ret = ad7192_register_clk_provider(st); + if (ret) + return dev_err_probe(dev, ret, + "Failed to register clock provider\n"); + return 0; + } + + st->clock_sel = AD7192_CLK_EXT_MCLK1_2 + ret; + + st->mclk = devm_clk_get_enabled(dev, ad7192_clock_names[ret]); + if (IS_ERR(st->mclk)) + return dev_err_probe(dev, PTR_ERR(st->mclk), + "Failed to get clock source\n"); + + st->fclk = clk_get_rate(st->mclk); + if (!ad7192_valid_external_frequency(st->fclk)) + return dev_err_probe(dev, -EINVAL, + "External clock frequency out of bounds\n"); + + return 0; +} + +static int ad7192_setup(struct iio_dev *indio_dev, struct device *dev) +{ + struct ad7192_state *st = iio_priv(indio_dev); + bool rej60_en, refin2_en; + bool buf_en, bipolar, burnout_curr_en; + unsigned long long scale_uv; + int i, ret, id; + + /* reset the serial interface */ + ret = ad_sd_reset(&st->sd); + if (ret < 0) + return ret; + + /* + * Per AD7192 datasheet (Rev. A, page 34, RESET section), allow + * 500 us after a reset before accessing on-chip registers. + */ + fsleep(500); + + /* write/read test for device presence */ + ret = ad_sd_read_reg(&st->sd, AD7192_REG_ID, 1, &id); + if (ret) + return ret; + + id = FIELD_GET(AD7192_ID_MASK, id); + + if (id != st->chip_info->chip_id) + dev_warn(dev, "device ID query failed (0x%X != 0x%X)\n", + id, st->chip_info->chip_id); + + st->mode = FIELD_PREP(AD7192_MODE_SEL_MASK, AD7192_MODE_IDLE) | + FIELD_PREP(AD7192_MODE_CLKSRC_MASK, st->clock_sel) | + FIELD_PREP(AD7192_MODE_RATE_MASK, 480); + + st->conf = FIELD_PREP(AD7192_CONF_GAIN_MASK, 0); + + rej60_en = device_property_read_bool(dev, "adi,rejection-60-Hz-enable"); + if (rej60_en) + st->mode |= AD7192_MODE_REJ60; + + refin2_en = device_property_read_bool(dev, "adi,refin2-pins-enable"); + if (refin2_en && st->chip_info->chip_id != CHIPID_AD7195) + st->conf |= AD7192_CONF_REFSEL; + + st->conf &= ~AD7192_CONF_CHOP; + + buf_en = device_property_read_bool(dev, "adi,buffer-enable"); + if (buf_en) + st->conf |= AD7192_CONF_BUF; + + bipolar = device_property_read_bool(dev, "bipolar"); + if (!bipolar) + st->conf |= AD7192_CONF_UNIPOLAR; + + burnout_curr_en = device_property_read_bool(dev, + "adi,burnout-currents-enable"); + if (burnout_curr_en && buf_en) { + st->conf |= AD7192_CONF_BURN; + } else if (burnout_curr_en) { + dev_warn(dev, + "Can't enable burnout currents: see CHOP or buffer\n"); + } + + ret = ad_sd_write_reg(&st->sd, AD7192_REG_MODE, 3, st->mode); + if (ret) + return ret; + + ret = ad_sd_write_reg(&st->sd, AD7192_REG_CONF, 3, st->conf); + if (ret) + return ret; + + ret = ad7192_calibrate_all(st); + if (ret) + return ret; + + /* Populate available ADC input ranges */ + for (i = 0; i < ARRAY_SIZE(st->scale_avail); i++) { + scale_uv = ((u64)st->int_vref_mv * 100000000) + >> (indio_dev->channels[0].scan_type.realbits - + !FIELD_GET(AD7192_CONF_UNIPOLAR, st->conf)); + scale_uv >>= i; + + st->scale_avail[i][1] = do_div(scale_uv, 100000000) * 10; + st->scale_avail[i][0] = scale_uv; + } + + st->oversampling_ratio_avail[0] = 1; + st->oversampling_ratio_avail[1] = 2; + st->oversampling_ratio_avail[2] = 8; + st->oversampling_ratio_avail[3] = 16; + + st->filter_freq_avail[0][0] = 600; + st->filter_freq_avail[1][0] = 800; + st->filter_freq_avail[2][0] = 2300; + st->filter_freq_avail[3][0] = 2720; + + st->filter_freq_avail[0][1] = 1000; + st->filter_freq_avail[1][1] = 1000; + st->filter_freq_avail[2][1] = 1000; + st->filter_freq_avail[3][1] = 1000; + + return 0; +} + +static ssize_t ad7192_show_ac_excitation(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7192_state *st = iio_priv(indio_dev); + + return sysfs_emit(buf, "%ld\n", FIELD_GET(AD7192_CONF_ACX, st->conf)); +} + +static ssize_t ad7192_show_bridge_switch(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7192_state *st = iio_priv(indio_dev); + + return sysfs_emit(buf, "%ld\n", + FIELD_GET(AD7192_GPOCON_BPDSW, st->gpocon)); +} + +static ssize_t ad7192_set(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7192_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + bool val; + + ret = kstrtobool(buf, &val); + if (ret < 0) + return ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + switch ((u32)this_attr->address) { + case AD7192_REG_GPOCON: + if (val) + st->gpocon |= AD7192_GPOCON_BPDSW; + else + st->gpocon &= ~AD7192_GPOCON_BPDSW; + + ad_sd_write_reg(&st->sd, AD7192_REG_GPOCON, 1, st->gpocon); + break; + case AD7192_REG_CONF: + if (val) + st->conf |= AD7192_CONF_ACX; + else + st->conf &= ~AD7192_CONF_ACX; + + ad_sd_write_reg(&st->sd, AD7192_REG_CONF, 3, st->conf); + break; + default: + ret = -EINVAL; + } + + iio_device_release_direct(indio_dev); + + return ret ? ret : len; +} + +static int ad7192_compute_f_order(struct ad7192_state *st, bool sinc3_en, bool chop_en) +{ + u8 avg_factor_selected, oversampling_ratio; + + avg_factor_selected = FIELD_GET(AD7192_MODE_AVG_MASK, st->mode); + + if (!avg_factor_selected && !chop_en) + return 1; + + oversampling_ratio = st->oversampling_ratio_avail[avg_factor_selected]; + + if (sinc3_en) + return AD7192_SYNC3_FILTER + oversampling_ratio - 1; + + return AD7192_SYNC4_FILTER + oversampling_ratio - 1; +} + +static int ad7192_get_f_order(struct ad7192_state *st) +{ + bool sinc3_en, chop_en; + + sinc3_en = FIELD_GET(AD7192_MODE_SINC3, st->mode); + chop_en = FIELD_GET(AD7192_CONF_CHOP, st->conf); + + return ad7192_compute_f_order(st, sinc3_en, chop_en); +} + +static int ad7192_compute_f_adc(struct ad7192_state *st, bool sinc3_en, + bool chop_en) +{ + unsigned int f_order = ad7192_compute_f_order(st, sinc3_en, chop_en); + + return DIV_ROUND_CLOSEST(st->fclk, + f_order * FIELD_GET(AD7192_MODE_RATE_MASK, st->mode)); +} + +static int ad7192_get_f_adc(struct ad7192_state *st) +{ + unsigned int f_order = ad7192_get_f_order(st); + + return DIV_ROUND_CLOSEST(st->fclk, + f_order * FIELD_GET(AD7192_MODE_RATE_MASK, st->mode)); +} + +static void ad7192_update_filter_freq_avail(struct ad7192_state *st) +{ + unsigned int fadc; + + /* Formulas for filter at page 25 of the datasheet */ + fadc = ad7192_compute_f_adc(st, false, true); + st->filter_freq_avail[0][0] = DIV_ROUND_CLOSEST(fadc * 240, 1024); + + fadc = ad7192_compute_f_adc(st, true, true); + st->filter_freq_avail[1][0] = DIV_ROUND_CLOSEST(fadc * 240, 1024); + + fadc = ad7192_compute_f_adc(st, false, false); + st->filter_freq_avail[2][0] = DIV_ROUND_CLOSEST(fadc * 230, 1024); + + fadc = ad7192_compute_f_adc(st, true, false); + st->filter_freq_avail[3][0] = DIV_ROUND_CLOSEST(fadc * 272, 1024); +} + +static IIO_DEVICE_ATTR(bridge_switch_en, 0644, + ad7192_show_bridge_switch, ad7192_set, + AD7192_REG_GPOCON); + +static IIO_DEVICE_ATTR(ac_excitation_en, 0644, + ad7192_show_ac_excitation, ad7192_set, + AD7192_REG_CONF); + +static struct attribute *ad7192_attributes[] = { + &iio_dev_attr_bridge_switch_en.dev_attr.attr, + NULL +}; + +static const struct attribute_group ad7192_attribute_group = { + .attrs = ad7192_attributes, +}; + +static struct attribute *ad7195_attributes[] = { + &iio_dev_attr_bridge_switch_en.dev_attr.attr, + &iio_dev_attr_ac_excitation_en.dev_attr.attr, + NULL +}; + +static const struct attribute_group ad7195_attribute_group = { + .attrs = ad7195_attributes, +}; + +static unsigned int ad7192_get_temp_scale(bool unipolar) +{ + return unipolar ? 2815 * 2 : 2815; +} + +static int ad7192_set_3db_filter_freq(struct ad7192_state *st, + int val, int val2) +{ + int i, ret, freq; + unsigned int diff_new, diff_old; + int idx = 0; + + diff_old = U32_MAX; + freq = val * 1000 + val2; + + for (i = 0; i < ARRAY_SIZE(st->filter_freq_avail); i++) { + diff_new = abs(freq - st->filter_freq_avail[i][0]); + if (diff_new < diff_old) { + diff_old = diff_new; + idx = i; + } + } + + switch (idx) { + case 0: + st->mode &= ~AD7192_MODE_SINC3; + + st->conf |= AD7192_CONF_CHOP; + break; + case 1: + st->mode |= AD7192_MODE_SINC3; + + st->conf |= AD7192_CONF_CHOP; + break; + case 2: + st->mode &= ~AD7192_MODE_SINC3; + + st->conf &= ~AD7192_CONF_CHOP; + break; + case 3: + st->mode |= AD7192_MODE_SINC3; + + st->conf &= ~AD7192_CONF_CHOP; + break; + } + + ret = ad_sd_write_reg(&st->sd, AD7192_REG_MODE, 3, st->mode); + if (ret < 0) + return ret; + + return ad_sd_write_reg(&st->sd, AD7192_REG_CONF, 3, st->conf); +} + +static int ad7192_get_3db_filter_freq(struct ad7192_state *st) +{ + unsigned int fadc; + + fadc = ad7192_get_f_adc(st); + + if (FIELD_GET(AD7192_CONF_CHOP, st->conf)) + return DIV_ROUND_CLOSEST(fadc * 240, 1024); + if (FIELD_GET(AD7192_MODE_SINC3, st->mode)) + return DIV_ROUND_CLOSEST(fadc * 272, 1024); + else + return DIV_ROUND_CLOSEST(fadc * 230, 1024); +} + +static int ad7192_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad7192_state *st = iio_priv(indio_dev); + bool unipolar = FIELD_GET(AD7192_CONF_UNIPOLAR, st->conf); + u8 gain = FIELD_GET(AD7192_CONF_GAIN_MASK, st->conf); + + switch (m) { + case IIO_CHAN_INFO_RAW: + return ad_sigma_delta_single_conversion(indio_dev, chan, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + mutex_lock(&st->lock); + *val = st->scale_avail[gain][0]; + *val2 = st->scale_avail[gain][1]; + mutex_unlock(&st->lock); + return IIO_VAL_INT_PLUS_NANO; + case IIO_TEMP: + *val = 0; + *val2 = 1000000000 / ad7192_get_temp_scale(unipolar); + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + if (!unipolar) + *val = -(1 << (chan->scan_type.realbits - 1)); + else + *val = 0; + + switch (chan->type) { + case IIO_VOLTAGE: + /* + * Only applies to pseudo-differential inputs. + * AINCOM voltage has to be converted to "raw" units. + */ + if (st->aincom_mv && !chan->differential) + *val += DIV_ROUND_CLOSEST_ULL((u64)st->aincom_mv * NANO, + st->scale_avail[gain][1]); + return IIO_VAL_INT; + /* Kelvin to Celsius */ + case IIO_TEMP: + *val -= 273 * ad7192_get_temp_scale(unipolar); + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + *val = DIV_ROUND_CLOSEST(ad7192_get_f_adc(st), 1024); + return IIO_VAL_INT; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + *val = ad7192_get_3db_filter_freq(st); + *val2 = 1000; + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = st->oversampling_ratio_avail[FIELD_GET(AD7192_MODE_AVG_MASK, st->mode)]; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int __ad7192_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad7192_state *st = iio_priv(indio_dev); + int i, div; + unsigned int tmp; + + guard(mutex)(&st->lock); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + for (i = 0; i < ARRAY_SIZE(st->scale_avail); i++) { + if (val2 != st->scale_avail[i][1]) + continue; + + tmp = st->conf; + st->conf &= ~AD7192_CONF_GAIN_MASK; + st->conf |= FIELD_PREP(AD7192_CONF_GAIN_MASK, i); + if (tmp == st->conf) + return 0; + ad_sd_write_reg(&st->sd, AD7192_REG_CONF, 3, st->conf); + ad7192_calibrate_all(st); + return 0; + } + return -EINVAL; + case IIO_CHAN_INFO_SAMP_FREQ: + if (!val) + return -EINVAL; + + div = st->fclk / (val * ad7192_get_f_order(st) * 1024); + if (div < 1 || div > 1023) + return -EINVAL; + + st->mode &= ~AD7192_MODE_RATE_MASK; + st->mode |= FIELD_PREP(AD7192_MODE_RATE_MASK, div); + ad_sd_write_reg(&st->sd, AD7192_REG_MODE, 3, st->mode); + ad7192_update_filter_freq_avail(st); + return 0; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return ad7192_set_3db_filter_freq(st, val, val2 / 1000); + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + for (i = 0; i < ARRAY_SIZE(st->oversampling_ratio_avail); i++) { + if (val != st->oversampling_ratio_avail[i]) + continue; + + tmp = st->mode; + st->mode &= ~AD7192_MODE_AVG_MASK; + st->mode |= FIELD_PREP(AD7192_MODE_AVG_MASK, i); + if (tmp == st->mode) + return 0; + ad_sd_write_reg(&st->sd, AD7192_REG_MODE, 3, st->mode); + ad7192_update_filter_freq_avail(st); + return 0; + } + return -EINVAL; + default: + return -EINVAL; + } +} + +static int ad7192_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = __ad7192_write_raw(indio_dev, chan, val, val2, mask); + + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ad7192_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad7192_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct ad7192_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = (int *)st->scale_avail; + *type = IIO_VAL_INT_PLUS_NANO; + /* Values are stored in a 2D matrix */ + *length = ARRAY_SIZE(st->scale_avail) * 2; + + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + *vals = (int *)st->filter_freq_avail; + *type = IIO_VAL_FRACTIONAL; + *length = ARRAY_SIZE(st->filter_freq_avail) * 2; + + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = (int *)st->oversampling_ratio_avail; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(st->oversampling_ratio_avail); + + return IIO_AVAIL_LIST; + } + + return -EINVAL; +} + +static int ad7192_update_scan_mode(struct iio_dev *indio_dev, const unsigned long *scan_mask) +{ + struct ad7192_state *st = iio_priv(indio_dev); + u32 conf = st->conf; + int ret; + int i; + + conf &= ~AD7192_CONF_CHAN_MASK; + for_each_set_bit(i, scan_mask, 8) + conf |= FIELD_PREP(AD7192_CONF_CHAN_MASK, BIT(i)); + + ret = ad_sd_write_reg(&st->sd, AD7192_REG_CONF, 3, conf); + if (ret < 0) + return ret; + + st->conf = conf; + + return 0; +} + +static const struct iio_info ad7192_info = { + .read_raw = ad7192_read_raw, + .write_raw = ad7192_write_raw, + .write_raw_get_fmt = ad7192_write_raw_get_fmt, + .read_avail = ad7192_read_avail, + .attrs = &ad7192_attribute_group, + .validate_trigger = ad_sd_validate_trigger, + .update_scan_mode = ad7192_update_scan_mode, +}; + +static const struct iio_info ad7194_info = { + .read_raw = ad7192_read_raw, + .write_raw = ad7192_write_raw, + .write_raw_get_fmt = ad7192_write_raw_get_fmt, + .read_avail = ad7192_read_avail, + .validate_trigger = ad_sd_validate_trigger, +}; + +static const struct iio_info ad7195_info = { + .read_raw = ad7192_read_raw, + .write_raw = ad7192_write_raw, + .write_raw_get_fmt = ad7192_write_raw_get_fmt, + .read_avail = ad7192_read_avail, + .attrs = &ad7195_attribute_group, + .validate_trigger = ad_sd_validate_trigger, + .update_scan_mode = ad7192_update_scan_mode, +}; + +#define __AD719x_CHANNEL(_si, _channel1, _channel2, _address, _type, \ + _mask_all, _mask_type_av, _mask_all_av, _ext_info) \ + { \ + .type = (_type), \ + .differential = ((_channel2) == -1 ? 0 : 1), \ + .indexed = 1, \ + .channel = (_channel1), \ + .channel2 = (_channel2), \ + .address = (_address), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \ + (_mask_all), \ + .info_mask_shared_by_type_available = (_mask_type_av), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \ + (_mask_all_av), \ + .ext_info = (_ext_info), \ + .scan_index = (_si), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 24, \ + .storagebits = 32, \ + .endianness = IIO_BE, \ + }, \ + } + +#define AD719x_DIFF_CHANNEL(_si, _channel1, _channel2, _address) \ + __AD719x_CHANNEL(_si, _channel1, _channel2, _address, IIO_VOLTAGE, 0, \ + BIT(IIO_CHAN_INFO_SCALE), 0, ad7192_calibsys_ext_info) + +#define AD719x_CHANNEL(_si, _channel1, _address) \ + __AD719x_CHANNEL(_si, _channel1, -1, _address, IIO_VOLTAGE, 0, \ + BIT(IIO_CHAN_INFO_SCALE), 0, ad7192_calibsys_ext_info) + +#define AD719x_TEMP_CHANNEL(_si, _address) \ + __AD719x_CHANNEL(_si, 0, -1, _address, IIO_TEMP, 0, 0, 0, NULL) + +#define AD7193_DIFF_CHANNEL(_si, _channel1, _channel2, _address) \ + __AD719x_CHANNEL(_si, _channel1, _channel2, _address, \ + IIO_VOLTAGE, \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + BIT(IIO_CHAN_INFO_SCALE), \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + ad7192_calibsys_ext_info) + +#define AD7193_CHANNEL(_si, _channel1, _address) \ + AD7193_DIFF_CHANNEL(_si, _channel1, -1, _address) + +static const struct iio_chan_spec ad7192_channels[] = { + AD719x_DIFF_CHANNEL(0, 1, 2, AD7192_CH_AIN1P_AIN2M), + AD719x_DIFF_CHANNEL(1, 3, 4, AD7192_CH_AIN3P_AIN4M), + AD719x_TEMP_CHANNEL(2, AD7192_CH_TEMP), + AD719x_DIFF_CHANNEL(3, 2, 2, AD7192_CH_AIN2P_AIN2M), + AD719x_CHANNEL(4, 1, AD7192_CH_AIN1), + AD719x_CHANNEL(5, 2, AD7192_CH_AIN2), + AD719x_CHANNEL(6, 3, AD7192_CH_AIN3), + AD719x_CHANNEL(7, 4, AD7192_CH_AIN4), + IIO_CHAN_SOFT_TIMESTAMP(8), +}; + +static const struct iio_chan_spec ad7193_channels[] = { + AD7193_DIFF_CHANNEL(0, 1, 2, AD7193_CH_AIN1P_AIN2M), + AD7193_DIFF_CHANNEL(1, 3, 4, AD7193_CH_AIN3P_AIN4M), + AD7193_DIFF_CHANNEL(2, 5, 6, AD7193_CH_AIN5P_AIN6M), + AD7193_DIFF_CHANNEL(3, 7, 8, AD7193_CH_AIN7P_AIN8M), + AD719x_TEMP_CHANNEL(4, AD7193_CH_TEMP), + AD7193_DIFF_CHANNEL(5, 2, 2, AD7193_CH_AIN2P_AIN2M), + AD7193_CHANNEL(6, 1, AD7193_CH_AIN1), + AD7193_CHANNEL(7, 2, AD7193_CH_AIN2), + AD7193_CHANNEL(8, 3, AD7193_CH_AIN3), + AD7193_CHANNEL(9, 4, AD7193_CH_AIN4), + AD7193_CHANNEL(10, 5, AD7193_CH_AIN5), + AD7193_CHANNEL(11, 6, AD7193_CH_AIN6), + AD7193_CHANNEL(12, 7, AD7193_CH_AIN7), + AD7193_CHANNEL(13, 8, AD7193_CH_AIN8), + IIO_CHAN_SOFT_TIMESTAMP(14), +}; + +static bool ad7194_validate_ain_channel(struct device *dev, u32 ain) +{ + return in_range(ain, AD7194_CH_AIN_START, AD7194_CH_AIN_NR); +} + +static int ad7194_parse_channels(struct iio_dev *indio_dev) +{ + struct device *dev = indio_dev->dev.parent; + struct iio_chan_spec *ad7194_channels; + const struct iio_chan_spec ad7194_chan = AD7193_CHANNEL(0, 0, 0); + const struct iio_chan_spec ad7194_chan_diff = AD7193_DIFF_CHANNEL(0, 0, 0, 0); + const struct iio_chan_spec ad7194_chan_temp = AD719x_TEMP_CHANNEL(0, 0); + const struct iio_chan_spec ad7194_chan_timestamp = IIO_CHAN_SOFT_TIMESTAMP(0); + unsigned int num_channels, index = 0; + u32 ain[2]; + int ret; + + num_channels = device_get_child_node_count(dev); + if (num_channels > AD7194_CH_MAX_NR) + return dev_err_probe(dev, -EINVAL, "Too many channels: %u\n", + num_channels); + + num_channels += AD7194_CH_BASE_NR; + + ad7194_channels = devm_kcalloc(dev, num_channels, + sizeof(*ad7194_channels), GFP_KERNEL); + if (!ad7194_channels) + return -ENOMEM; + + indio_dev->channels = ad7194_channels; + indio_dev->num_channels = num_channels; + + device_for_each_child_node_scoped(dev, child) { + ret = fwnode_property_read_u32_array(child, "diff-channels", + ain, ARRAY_SIZE(ain)); + if (ret == 0) { + if (!ad7194_validate_ain_channel(dev, ain[0])) + return dev_err_probe(dev, -EINVAL, + "Invalid AIN channel: %u\n", + ain[0]); + + if (!ad7194_validate_ain_channel(dev, ain[1])) + return dev_err_probe(dev, -EINVAL, + "Invalid AIN channel: %u\n", + ain[1]); + + *ad7194_channels = ad7194_chan_diff; + ad7194_channels->scan_index = index++; + ad7194_channels->channel = ain[0]; + ad7194_channels->channel2 = ain[1]; + ad7194_channels->address = AD7194_DIFF_CH(ain[0], ain[1]); + } else { + ret = fwnode_property_read_u32(child, "single-channel", + &ain[0]); + if (ret) + return dev_err_probe(dev, ret, + "Missing channel property\n"); + + if (!ad7194_validate_ain_channel(dev, ain[0])) + return dev_err_probe(dev, -EINVAL, + "Invalid AIN channel: %u\n", + ain[0]); + + *ad7194_channels = ad7194_chan; + ad7194_channels->scan_index = index++; + ad7194_channels->channel = ain[0]; + ad7194_channels->address = AD7194_CH(ain[0]); + } + ad7194_channels++; + } + + *ad7194_channels = ad7194_chan_temp; + ad7194_channels->scan_index = index++; + ad7194_channels->address = AD7194_CH_TEMP; + ad7194_channels++; + + *ad7194_channels = ad7194_chan_timestamp; + ad7194_channels->scan_index = index; + + return 0; +} + +static const struct ad7192_chip_info ad7192_chip_info_tbl[] = { + [ID_AD7190] = { + .chip_id = CHIPID_AD7190, + .name = "ad7190", + .channels = ad7192_channels, + .num_channels = ARRAY_SIZE(ad7192_channels), + .sigma_delta_info = &ad7192_sigma_delta_info, + .info = &ad7192_info, + }, + [ID_AD7192] = { + .chip_id = CHIPID_AD7192, + .name = "ad7192", + .channels = ad7192_channels, + .num_channels = ARRAY_SIZE(ad7192_channels), + .sigma_delta_info = &ad7192_sigma_delta_info, + .info = &ad7192_info, + }, + [ID_AD7193] = { + .chip_id = CHIPID_AD7193, + .name = "ad7193", + .channels = ad7193_channels, + .num_channels = ARRAY_SIZE(ad7193_channels), + .sigma_delta_info = &ad7192_sigma_delta_info, + .info = &ad7192_info, + }, + [ID_AD7194] = { + .chip_id = CHIPID_AD7194, + .name = "ad7194", + .info = &ad7194_info, + .sigma_delta_info = &ad7194_sigma_delta_info, + .parse_channels = ad7194_parse_channels, + }, + [ID_AD7195] = { + .chip_id = CHIPID_AD7195, + .name = "ad7195", + .channels = ad7192_channels, + .num_channels = ARRAY_SIZE(ad7192_channels), + .sigma_delta_info = &ad7192_sigma_delta_info, + .info = &ad7195_info, + }, +}; + +static int ad7192_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct ad7192_state *st; + struct iio_dev *indio_dev; + int ret, avdd_mv; + + if (!spi->irq) + return dev_err_probe(dev, -ENODEV, "Failed to get IRQ\n"); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + mutex_init(&st->lock); + + /* + * Regulator aincom is optional to maintain compatibility with older DT. + * Newer firmware should provide a zero volt fixed supply if wired to + * ground. + */ + ret = devm_regulator_get_enable_read_voltage(dev, "aincom"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "Failed to get AINCOM voltage\n"); + + st->aincom_mv = ret == -ENODEV ? 0 : ret / MILLI; + + /* AVDD can optionally be used as reference voltage */ + ret = devm_regulator_get_enable_read_voltage(dev, "avdd"); + if (ret == -ENODEV || ret == -EINVAL) { + int ret2; + + /* + * We get -EINVAL if avdd is a supply with unknown voltage. We + * still need to enable it since it is also a power supply. + */ + ret2 = devm_regulator_get_enable(dev, "avdd"); + if (ret2) + return dev_err_probe(dev, ret2, + "Failed to enable AVDD supply\n"); + } else if (ret < 0) { + return dev_err_probe(dev, ret, "Failed to get AVDD voltage\n"); + } + + avdd_mv = ret == -ENODEV || ret == -EINVAL ? 0 : ret / MILLI; + + ret = devm_regulator_get_enable(dev, "dvdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable specified DVdd supply\n"); + + /* + * This is either REFIN1 or REFIN2 depending on adi,refin2-pins-enable. + * If this supply is not present, fall back to AVDD as reference. + */ + ret = devm_regulator_get_enable_read_voltage(dev, "vref"); + if (ret == -ENODEV) { + if (avdd_mv == 0) + return dev_err_probe(dev, -ENODEV, + "No reference voltage available\n"); + } else if (ret < 0) { + return ret; + } + + st->int_vref_mv = ret == -ENODEV ? avdd_mv : ret / MILLI; + + st->chip_info = spi_get_device_match_data(spi); + indio_dev->name = st->chip_info->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = st->chip_info->info; + if (st->chip_info->parse_channels) { + ret = st->chip_info->parse_channels(indio_dev); + if (ret) + return ret; + } else { + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + } + + ret = ad_sd_init(&st->sd, indio_dev, spi, st->chip_info->sigma_delta_info); + if (ret) + return ret; + + ret = devm_ad_sd_setup_buffer_and_trigger(dev, indio_dev); + if (ret) + return ret; + + ret = ad7192_clock_setup(st); + if (ret) + return ret; + + ret = ad7192_setup(indio_dev, dev); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ad7192_of_match[] = { + { .compatible = "adi,ad7190", .data = &ad7192_chip_info_tbl[ID_AD7190] }, + { .compatible = "adi,ad7192", .data = &ad7192_chip_info_tbl[ID_AD7192] }, + { .compatible = "adi,ad7193", .data = &ad7192_chip_info_tbl[ID_AD7193] }, + { .compatible = "adi,ad7194", .data = &ad7192_chip_info_tbl[ID_AD7194] }, + { .compatible = "adi,ad7195", .data = &ad7192_chip_info_tbl[ID_AD7195] }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7192_of_match); + +static const struct spi_device_id ad7192_ids[] = { + { .name = "ad7190", .driver_data = (kernel_ulong_t)&ad7192_chip_info_tbl[ID_AD7190] }, + { .name = "ad7192", .driver_data = (kernel_ulong_t)&ad7192_chip_info_tbl[ID_AD7192] }, + { .name = "ad7193", .driver_data = (kernel_ulong_t)&ad7192_chip_info_tbl[ID_AD7193] }, + { .name = "ad7194", .driver_data = (kernel_ulong_t)&ad7192_chip_info_tbl[ID_AD7194] }, + { .name = "ad7195", .driver_data = (kernel_ulong_t)&ad7192_chip_info_tbl[ID_AD7195] }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7192_ids); + +static struct spi_driver ad7192_driver = { + .driver = { + .name = "ad7192", + .of_match_table = ad7192_of_match, + }, + .probe = ad7192_probe, + .id_table = ad7192_ids, +}; +module_spi_driver(ad7192_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7192 and similar ADC"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_AD_SIGMA_DELTA"); diff --git a/drivers/iio/adc/ad7266.c b/drivers/iio/adc/ad7266.c new file mode 100644 index 000000000..594a070c3 --- /dev/null +++ b/drivers/iio/adc/ad7266.c @@ -0,0 +1,473 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD7266/65 SPI ADC driver + * + * Copyright 2012 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#define AD7266_INTERNAL_REF_MV 2500 + +struct ad7266_state { + struct spi_device *spi; + unsigned long vref_mv; + + struct spi_transfer single_xfer[3]; + struct spi_message single_msg; + + enum ad7266_range range; + enum ad7266_mode mode; + bool fixed_addr; + struct gpio_desc *gpios[3]; + + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + * The buffer needs to be large enough to hold two samples (4 bytes) and + * the naturally aligned timestamp (8 bytes). + */ + struct { + __be16 sample[2]; + aligned_s64 timestamp; + } data __aligned(IIO_DMA_MINALIGN); +}; + +static int ad7266_wakeup(struct ad7266_state *st) +{ + /* Any read with >= 2 bytes will wake the device */ + return spi_read(st->spi, &st->data.sample[0], 2); +} + +static int ad7266_powerdown(struct ad7266_state *st) +{ + /* Any read with < 2 bytes will powerdown the device */ + return spi_read(st->spi, &st->data.sample[0], 1); +} + +static int ad7266_preenable(struct iio_dev *indio_dev) +{ + struct ad7266_state *st = iio_priv(indio_dev); + return ad7266_wakeup(st); +} + +static int ad7266_postdisable(struct iio_dev *indio_dev) +{ + struct ad7266_state *st = iio_priv(indio_dev); + return ad7266_powerdown(st); +} + +static const struct iio_buffer_setup_ops iio_triggered_buffer_setup_ops = { + .preenable = &ad7266_preenable, + .postdisable = &ad7266_postdisable, +}; + +static irqreturn_t ad7266_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad7266_state *st = iio_priv(indio_dev); + int ret; + + ret = spi_read(st->spi, st->data.sample, 4); + if (ret == 0) + iio_push_to_buffers_with_ts(indio_dev, &st->data, sizeof(st->data), + pf->timestamp); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static void ad7266_select_input(struct ad7266_state *st, unsigned int nr) +{ + unsigned int i; + + if (st->fixed_addr) + return; + + switch (st->mode) { + case AD7266_MODE_SINGLE_ENDED: + nr >>= 1; + break; + case AD7266_MODE_PSEUDO_DIFF: + nr |= 1; + break; + case AD7266_MODE_DIFF: + nr &= ~1; + break; + } + + for (i = 0; i < 3; ++i) + gpiod_set_value(st->gpios[i], (bool)(nr & BIT(i))); +} + +static int ad7266_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ad7266_state *st = iio_priv(indio_dev); + unsigned int nr = find_first_bit(scan_mask, + iio_get_masklength(indio_dev)); + + ad7266_select_input(st, nr); + + return 0; +} + +static int ad7266_read_single(struct ad7266_state *st, int *val, + unsigned int address) +{ + int ret; + + ad7266_select_input(st, address); + + ret = spi_sync(st->spi, &st->single_msg); + *val = be16_to_cpu(st->data.sample[address % 2]); + + return ret; +} + +static int ad7266_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long m) +{ + struct ad7266_state *st = iio_priv(indio_dev); + unsigned long scale_mv; + int ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = ad7266_read_single(st, val, chan->address); + iio_device_release_direct(indio_dev); + + if (ret < 0) + return ret; + *val = (*val >> 2) & 0xfff; + if (chan->scan_type.sign == 's') + *val = sign_extend32(*val, + chan->scan_type.realbits - 1); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + scale_mv = st->vref_mv; + if (st->mode == AD7266_MODE_DIFF) + scale_mv *= 2; + if (st->range == AD7266_RANGE_2VREF) + scale_mv *= 2; + + *val = scale_mv; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: + if (st->range == AD7266_RANGE_2VREF && + st->mode != AD7266_MODE_DIFF) + *val = 2048; + else + *val = 0; + return IIO_VAL_INT; + } + return -EINVAL; +} + +#define AD7266_CHAN(_chan, _sign) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_chan), \ + .address = (_chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \ + | BIT(IIO_CHAN_INFO_OFFSET), \ + .scan_index = (_chan), \ + .scan_type = { \ + .sign = (_sign), \ + .realbits = 12, \ + .storagebits = 16, \ + .shift = 2, \ + .endianness = IIO_BE, \ + }, \ +} + +#define AD7266_DECLARE_SINGLE_ENDED_CHANNELS(_name, _sign) \ +const struct iio_chan_spec ad7266_channels_##_name[] = { \ + AD7266_CHAN(0, (_sign)), \ + AD7266_CHAN(1, (_sign)), \ + AD7266_CHAN(2, (_sign)), \ + AD7266_CHAN(3, (_sign)), \ + AD7266_CHAN(4, (_sign)), \ + AD7266_CHAN(5, (_sign)), \ + AD7266_CHAN(6, (_sign)), \ + AD7266_CHAN(7, (_sign)), \ + AD7266_CHAN(8, (_sign)), \ + AD7266_CHAN(9, (_sign)), \ + AD7266_CHAN(10, (_sign)), \ + AD7266_CHAN(11, (_sign)), \ + IIO_CHAN_SOFT_TIMESTAMP(13), \ +} + +#define AD7266_DECLARE_SINGLE_ENDED_CHANNELS_FIXED(_name, _sign) \ +const struct iio_chan_spec ad7266_channels_##_name##_fixed[] = { \ + AD7266_CHAN(0, (_sign)), \ + AD7266_CHAN(1, (_sign)), \ + IIO_CHAN_SOFT_TIMESTAMP(2), \ +} + +static AD7266_DECLARE_SINGLE_ENDED_CHANNELS(u, 'u'); +static AD7266_DECLARE_SINGLE_ENDED_CHANNELS(s, 's'); +static AD7266_DECLARE_SINGLE_ENDED_CHANNELS_FIXED(u, 'u'); +static AD7266_DECLARE_SINGLE_ENDED_CHANNELS_FIXED(s, 's'); + +#define AD7266_CHAN_DIFF(_chan, _sign) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_chan) * 2, \ + .channel2 = (_chan) * 2 + 1, \ + .address = (_chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \ + | BIT(IIO_CHAN_INFO_OFFSET), \ + .scan_index = (_chan), \ + .scan_type = { \ + .sign = _sign, \ + .realbits = 12, \ + .storagebits = 16, \ + .shift = 2, \ + .endianness = IIO_BE, \ + }, \ + .differential = 1, \ +} + +#define AD7266_DECLARE_DIFF_CHANNELS(_name, _sign) \ +const struct iio_chan_spec ad7266_channels_diff_##_name[] = { \ + AD7266_CHAN_DIFF(0, (_sign)), \ + AD7266_CHAN_DIFF(1, (_sign)), \ + AD7266_CHAN_DIFF(2, (_sign)), \ + AD7266_CHAN_DIFF(3, (_sign)), \ + AD7266_CHAN_DIFF(4, (_sign)), \ + AD7266_CHAN_DIFF(5, (_sign)), \ + IIO_CHAN_SOFT_TIMESTAMP(6), \ +} + +static AD7266_DECLARE_DIFF_CHANNELS(s, 's'); +static AD7266_DECLARE_DIFF_CHANNELS(u, 'u'); + +#define AD7266_DECLARE_DIFF_CHANNELS_FIXED(_name, _sign) \ +const struct iio_chan_spec ad7266_channels_diff_fixed_##_name[] = { \ + AD7266_CHAN_DIFF(0, (_sign)), \ + AD7266_CHAN_DIFF(1, (_sign)), \ + IIO_CHAN_SOFT_TIMESTAMP(2), \ +} + +static AD7266_DECLARE_DIFF_CHANNELS_FIXED(s, 's'); +static AD7266_DECLARE_DIFF_CHANNELS_FIXED(u, 'u'); + +static const struct iio_info ad7266_info = { + .read_raw = &ad7266_read_raw, + .update_scan_mode = &ad7266_update_scan_mode, +}; + +static const unsigned long ad7266_available_scan_masks[] = { + 0x003, + 0x00c, + 0x030, + 0x0c0, + 0x300, + 0xc00, + 0x000, +}; + +static const unsigned long ad7266_available_scan_masks_diff[] = { + 0x003, + 0x00c, + 0x030, + 0x000, +}; + +static const unsigned long ad7266_available_scan_masks_fixed[] = { + 0x003, + 0x000, +}; + +struct ad7266_chan_info { + const struct iio_chan_spec *channels; + unsigned int num_channels; + const unsigned long *scan_masks; +}; + +#define AD7266_CHAN_INFO_INDEX(_differential, _signed, _fixed) \ + (((_differential) << 2) | ((_signed) << 1) | ((_fixed) << 0)) + +static const struct ad7266_chan_info ad7266_chan_infos[] = { + [AD7266_CHAN_INFO_INDEX(0, 0, 0)] = { + .channels = ad7266_channels_u, + .num_channels = ARRAY_SIZE(ad7266_channels_u), + .scan_masks = ad7266_available_scan_masks, + }, + [AD7266_CHAN_INFO_INDEX(0, 0, 1)] = { + .channels = ad7266_channels_u_fixed, + .num_channels = ARRAY_SIZE(ad7266_channels_u_fixed), + .scan_masks = ad7266_available_scan_masks_fixed, + }, + [AD7266_CHAN_INFO_INDEX(0, 1, 0)] = { + .channels = ad7266_channels_s, + .num_channels = ARRAY_SIZE(ad7266_channels_s), + .scan_masks = ad7266_available_scan_masks, + }, + [AD7266_CHAN_INFO_INDEX(0, 1, 1)] = { + .channels = ad7266_channels_s_fixed, + .num_channels = ARRAY_SIZE(ad7266_channels_s_fixed), + .scan_masks = ad7266_available_scan_masks_fixed, + }, + [AD7266_CHAN_INFO_INDEX(1, 0, 0)] = { + .channels = ad7266_channels_diff_u, + .num_channels = ARRAY_SIZE(ad7266_channels_diff_u), + .scan_masks = ad7266_available_scan_masks_diff, + }, + [AD7266_CHAN_INFO_INDEX(1, 0, 1)] = { + .channels = ad7266_channels_diff_fixed_u, + .num_channels = ARRAY_SIZE(ad7266_channels_diff_fixed_u), + .scan_masks = ad7266_available_scan_masks_fixed, + }, + [AD7266_CHAN_INFO_INDEX(1, 1, 0)] = { + .channels = ad7266_channels_diff_s, + .num_channels = ARRAY_SIZE(ad7266_channels_diff_s), + .scan_masks = ad7266_available_scan_masks_diff, + }, + [AD7266_CHAN_INFO_INDEX(1, 1, 1)] = { + .channels = ad7266_channels_diff_fixed_s, + .num_channels = ARRAY_SIZE(ad7266_channels_diff_fixed_s), + .scan_masks = ad7266_available_scan_masks_fixed, + }, +}; + +static void ad7266_init_channels(struct iio_dev *indio_dev) +{ + struct ad7266_state *st = iio_priv(indio_dev); + bool is_differential, is_signed; + const struct ad7266_chan_info *chan_info; + int i; + + is_differential = st->mode != AD7266_MODE_SINGLE_ENDED; + is_signed = (st->range == AD7266_RANGE_2VREF) | + (st->mode == AD7266_MODE_DIFF); + + i = AD7266_CHAN_INFO_INDEX(is_differential, is_signed, st->fixed_addr); + chan_info = &ad7266_chan_infos[i]; + + indio_dev->channels = chan_info->channels; + indio_dev->num_channels = chan_info->num_channels; + indio_dev->available_scan_masks = chan_info->scan_masks; +} + +static const char * const ad7266_gpio_labels[] = { + "ad0", "ad1", "ad2", +}; + +static int ad7266_probe(struct spi_device *spi) +{ + const struct ad7266_platform_data *pdata = dev_get_platdata(&spi->dev); + struct iio_dev *indio_dev; + struct ad7266_state *st; + unsigned int i; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + ret = devm_regulator_get_enable_read_voltage(&spi->dev, "vref"); + if (ret < 0 && ret != -ENODEV) + return ret; + + st->vref_mv = ret == -ENODEV ? AD7266_INTERNAL_REF_MV : ret / 1000; + + if (pdata) { + st->fixed_addr = pdata->fixed_addr; + st->mode = pdata->mode; + st->range = pdata->range; + + if (!st->fixed_addr) { + for (i = 0; i < ARRAY_SIZE(st->gpios); ++i) { + st->gpios[i] = devm_gpiod_get(&spi->dev, + ad7266_gpio_labels[i], + GPIOD_OUT_LOW); + if (IS_ERR(st->gpios[i])) + return PTR_ERR(st->gpios[i]); + } + } + } else { + st->fixed_addr = true; + st->range = AD7266_RANGE_VREF; + st->mode = AD7266_MODE_DIFF; + } + + st->spi = spi; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ad7266_info; + + ad7266_init_channels(indio_dev); + + /* wakeup */ + st->single_xfer[0].rx_buf = &st->data.sample[0]; + st->single_xfer[0].len = 2; + st->single_xfer[0].cs_change = 1; + /* conversion */ + st->single_xfer[1].rx_buf = st->data.sample; + st->single_xfer[1].len = 4; + st->single_xfer[1].cs_change = 1; + /* powerdown */ + st->single_xfer[2].tx_buf = &st->data.sample[0]; + st->single_xfer[2].len = 1; + + spi_message_init(&st->single_msg); + spi_message_add_tail(&st->single_xfer[0], &st->single_msg); + spi_message_add_tail(&st->single_xfer[1], &st->single_msg); + spi_message_add_tail(&st->single_xfer[2], &st->single_msg); + + ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, &iio_pollfunc_store_time, + &ad7266_trigger_handler, &iio_triggered_buffer_setup_ops); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id ad7266_id[] = { + { .name = "ad7265" }, + { .name = "ad7266" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7266_id); + +static struct spi_driver ad7266_driver = { + .driver = { + .name = "ad7266", + }, + .probe = ad7266_probe, + .id_table = ad7266_id, +}; +module_spi_driver(ad7266_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Analog Devices AD7266/65 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ad7280a.c b/drivers/iio/adc/ad7280a.c new file mode 100644 index 000000000..5ae0d694d --- /dev/null +++ b/drivers/iio/adc/ad7280a.c @@ -0,0 +1,1101 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AD7280A Lithium Ion Battery Monitoring System + * + * Copyright 2011 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* Registers */ + +#define AD7280A_CELL_VOLTAGE_1_REG 0x0 /* D11 to D0, Read only */ +#define AD7280A_CELL_VOLTAGE_2_REG 0x1 /* D11 to D0, Read only */ +#define AD7280A_CELL_VOLTAGE_3_REG 0x2 /* D11 to D0, Read only */ +#define AD7280A_CELL_VOLTAGE_4_REG 0x3 /* D11 to D0, Read only */ +#define AD7280A_CELL_VOLTAGE_5_REG 0x4 /* D11 to D0, Read only */ +#define AD7280A_CELL_VOLTAGE_6_REG 0x5 /* D11 to D0, Read only */ +#define AD7280A_AUX_ADC_1_REG 0x6 /* D11 to D0, Read only */ +#define AD7280A_AUX_ADC_2_REG 0x7 /* D11 to D0, Read only */ +#define AD7280A_AUX_ADC_3_REG 0x8 /* D11 to D0, Read only */ +#define AD7280A_AUX_ADC_4_REG 0x9 /* D11 to D0, Read only */ +#define AD7280A_AUX_ADC_5_REG 0xA /* D11 to D0, Read only */ +#define AD7280A_AUX_ADC_6_REG 0xB /* D11 to D0, Read only */ +#define AD7280A_SELF_TEST_REG 0xC /* D11 to D0, Read only */ + +#define AD7280A_CTRL_HB_REG 0xD /* D15 to D8, Read/write */ +#define AD7280A_CTRL_HB_CONV_INPUT_MSK GENMASK(7, 6) +#define AD7280A_CTRL_HB_CONV_INPUT_ALL 0 +#define AD7280A_CTRL_HB_CONV_INPUT_6CELL_AUX1_3_5 1 +#define AD7280A_CTRL_HB_CONV_INPUT_6CELL 2 +#define AD7280A_CTRL_HB_CONV_INPUT_SELF_TEST 3 +#define AD7280A_CTRL_HB_CONV_RREAD_MSK GENMASK(5, 4) +#define AD7280A_CTRL_HB_CONV_RREAD_ALL 0 +#define AD7280A_CTRL_HB_CONV_RREAD_6CELL_AUX1_3_5 1 +#define AD7280A_CTRL_HB_CONV_RREAD_6CELL 2 +#define AD7280A_CTRL_HB_CONV_RREAD_NO 3 +#define AD7280A_CTRL_HB_CONV_START_MSK BIT(3) +#define AD7280A_CTRL_HB_CONV_START_CNVST 0 +#define AD7280A_CTRL_HB_CONV_START_CS 1 +#define AD7280A_CTRL_HB_CONV_AVG_MSK GENMASK(2, 1) +#define AD7280A_CTRL_HB_CONV_AVG_DIS 0 +#define AD7280A_CTRL_HB_CONV_AVG_2 1 +#define AD7280A_CTRL_HB_CONV_AVG_4 2 +#define AD7280A_CTRL_HB_CONV_AVG_8 3 +#define AD7280A_CTRL_HB_PWRDN_SW BIT(0) + +#define AD7280A_CTRL_LB_REG 0xE /* D7 to D0, Read/write */ +#define AD7280A_CTRL_LB_SWRST_MSK BIT(7) +#define AD7280A_CTRL_LB_ACQ_TIME_MSK GENMASK(6, 5) +#define AD7280A_CTRL_LB_ACQ_TIME_400ns 0 +#define AD7280A_CTRL_LB_ACQ_TIME_800ns 1 +#define AD7280A_CTRL_LB_ACQ_TIME_1200ns 2 +#define AD7280A_CTRL_LB_ACQ_TIME_1600ns 3 +#define AD7280A_CTRL_LB_MUST_SET BIT(4) +#define AD7280A_CTRL_LB_THERMISTOR_MSK BIT(3) +#define AD7280A_CTRL_LB_LOCK_DEV_ADDR_MSK BIT(2) +#define AD7280A_CTRL_LB_INC_DEV_ADDR_MSK BIT(1) +#define AD7280A_CTRL_LB_DAISY_CHAIN_RB_MSK BIT(0) + +#define AD7280A_CELL_OVERVOLTAGE_REG 0xF /* D7 to D0, Read/write */ +#define AD7280A_CELL_UNDERVOLTAGE_REG 0x10 /* D7 to D0, Read/write */ +#define AD7280A_AUX_ADC_OVERVOLTAGE_REG 0x11 /* D7 to D0, Read/write */ +#define AD7280A_AUX_ADC_UNDERVOLTAGE_REG 0x12 /* D7 to D0, Read/write */ + +#define AD7280A_ALERT_REG 0x13 /* D7 to D0, Read/write */ +#define AD7280A_ALERT_REMOVE_MSK GENMASK(3, 0) +#define AD7280A_ALERT_REMOVE_AUX5 BIT(0) +#define AD7280A_ALERT_REMOVE_AUX3_AUX5 BIT(1) +#define AD7280A_ALERT_REMOVE_VIN5 BIT(2) +#define AD7280A_ALERT_REMOVE_VIN4_VIN5 BIT(3) +#define AD7280A_ALERT_GEN_STATIC_HIGH BIT(6) +#define AD7280A_ALERT_RELAY_SIG_CHAIN_DOWN (BIT(7) | BIT(6)) + +#define AD7280A_CELL_BALANCE_REG 0x14 /* D7 to D0, Read/write */ +#define AD7280A_CELL_BALANCE_CHAN_BITMAP_MSK GENMASK(7, 2) +#define AD7280A_CB1_TIMER_REG 0x15 /* D7 to D0, Read/write */ +#define AD7280A_CB_TIMER_VAL_MSK GENMASK(7, 3) +#define AD7280A_CB2_TIMER_REG 0x16 /* D7 to D0, Read/write */ +#define AD7280A_CB3_TIMER_REG 0x17 /* D7 to D0, Read/write */ +#define AD7280A_CB4_TIMER_REG 0x18 /* D7 to D0, Read/write */ +#define AD7280A_CB5_TIMER_REG 0x19 /* D7 to D0, Read/write */ +#define AD7280A_CB6_TIMER_REG 0x1A /* D7 to D0, Read/write */ +#define AD7280A_PD_TIMER_REG 0x1B /* D7 to D0, Read/write */ +#define AD7280A_READ_REG 0x1C /* D7 to D0, Read/write */ +#define AD7280A_READ_ADDR_MSK GENMASK(7, 2) +#define AD7280A_CNVST_CTRL_REG 0x1D /* D7 to D0, Read/write */ + +/* Transfer fields */ +#define AD7280A_TRANS_WRITE_DEVADDR_MSK GENMASK(31, 27) +#define AD7280A_TRANS_WRITE_ADDR_MSK GENMASK(26, 21) +#define AD7280A_TRANS_WRITE_VAL_MSK GENMASK(20, 13) +#define AD7280A_TRANS_WRITE_ALL_MSK BIT(12) +#define AD7280A_TRANS_WRITE_CRC_MSK GENMASK(10, 3) +#define AD7280A_TRANS_WRITE_RES_PATTERN 0x2 + +/* Layouts differ for channel vs other registers */ +#define AD7280A_TRANS_READ_DEVADDR_MSK GENMASK(31, 27) +#define AD7280A_TRANS_READ_CONV_CHANADDR_MSK GENMASK(26, 23) +#define AD7280A_TRANS_READ_CONV_DATA_MSK GENMASK(22, 11) +#define AD7280A_TRANS_READ_REG_REGADDR_MSK GENMASK(26, 21) +#define AD7280A_TRANS_READ_REG_DATA_MSK GENMASK(20, 13) +#define AD7280A_TRANS_READ_WRITE_ACK_MSK BIT(10) +#define AD7280A_TRANS_READ_CRC_MSK GENMASK(9, 2) + +/* Magic value used to indicate this special case */ +#define AD7280A_ALL_CELLS (0xAD << 16) + +#define AD7280A_MAX_SPI_CLK_HZ 700000 /* < 1MHz */ +#define AD7280A_MAX_CHAIN 8 +#define AD7280A_CELLS_PER_DEV 6 +#define AD7280A_BITS 12 +#define AD7280A_NUM_CH (AD7280A_AUX_ADC_6_REG - \ + AD7280A_CELL_VOLTAGE_1_REG + 1) + +#define AD7280A_CALC_VOLTAGE_CHAN_NUM(d, c) (((d) * AD7280A_CELLS_PER_DEV) + \ + (c)) +#define AD7280A_CALC_TEMP_CHAN_NUM(d, c) (((d) * AD7280A_CELLS_PER_DEV) + \ + (c) - AD7280A_CELLS_PER_DEV) + +#define AD7280A_DEVADDR_MASTER 0 +#define AD7280A_DEVADDR_ALL 0x1F + +static const unsigned short ad7280a_n_avg[4] = {1, 2, 4, 8}; +static const unsigned short ad7280a_t_acq_ns[4] = {470, 1030, 1510, 1945}; + +/* 5-bit device address is sent LSB first */ +static unsigned int ad7280a_devaddr(unsigned int addr) +{ + return ((addr & 0x1) << 4) | + ((addr & 0x2) << 2) | + (addr & 0x4) | + ((addr & 0x8) >> 2) | + ((addr & 0x10) >> 4); +} + +/* + * During a read a valid write is mandatory. + * So writing to the highest available address (Address 0x1F) and setting the + * address all parts bit to 0 is recommended. + * So the TXVAL is AD7280A_DEVADDR_ALL + CRC + */ +#define AD7280A_READ_TXVAL 0xF800030A + +/* + * AD7280 CRC + * + * P(x) = x^8 + x^5 + x^3 + x^2 + x^1 + x^0 = 0b100101111 => 0x2F + */ +#define POLYNOM 0x2F + +struct ad7280_state { + struct spi_device *spi; + struct iio_chan_spec *channels; + unsigned int chain_last_alert_ignore; + bool thermistor_term_en; + int slave_num; + int scan_cnt; + int readback_delay_us; + unsigned char crc_tab[CRC8_TABLE_SIZE]; + u8 oversampling_ratio; + u8 acquisition_time; + unsigned char ctrl_lb; + unsigned char cell_threshhigh; + unsigned char cell_threshlow; + unsigned char aux_threshhigh; + unsigned char aux_threshlow; + unsigned char cb_mask[AD7280A_MAX_CHAIN]; + struct mutex lock; /* protect sensor state */ + + __be32 tx __aligned(IIO_DMA_MINALIGN); + __be32 rx; +}; + +static unsigned char ad7280_calc_crc8(unsigned char *crc_tab, unsigned int val) +{ + unsigned char crc; + + crc = crc_tab[val >> 16 & 0xFF]; + crc = crc_tab[crc ^ (val >> 8 & 0xFF)]; + + return crc ^ (val & 0xFF); +} + +static int ad7280_check_crc(struct ad7280_state *st, unsigned int val) +{ + unsigned char crc = ad7280_calc_crc8(st->crc_tab, val >> 10); + + if (crc != ((val >> 2) & 0xFF)) + return -EIO; + + return 0; +} + +/* + * After initiating a conversion sequence we need to wait until the conversion + * is done. The delay is typically in the range of 15..30us however depending on + * the number of devices in the daisy chain, the number of averages taken, + * conversion delays and acquisition time options it may take up to 250us, in + * this case we better sleep instead of busy wait. + */ + +static void ad7280_delay(struct ad7280_state *st) +{ + if (st->readback_delay_us < 50) + udelay(st->readback_delay_us); + else + usleep_range(250, 500); +} + +static int __ad7280_read32(struct ad7280_state *st, unsigned int *val) +{ + int ret; + struct spi_transfer t = { + .tx_buf = &st->tx, + .rx_buf = &st->rx, + .len = sizeof(st->tx), + }; + + st->tx = cpu_to_be32(AD7280A_READ_TXVAL); + + ret = spi_sync_transfer(st->spi, &t, 1); + if (ret) + return ret; + + *val = be32_to_cpu(st->rx); + + return 0; +} + +static int ad7280_write(struct ad7280_state *st, unsigned int devaddr, + unsigned int addr, bool all, unsigned int val) +{ + unsigned int reg = FIELD_PREP(AD7280A_TRANS_WRITE_DEVADDR_MSK, devaddr) | + FIELD_PREP(AD7280A_TRANS_WRITE_ADDR_MSK, addr) | + FIELD_PREP(AD7280A_TRANS_WRITE_VAL_MSK, val) | + FIELD_PREP(AD7280A_TRANS_WRITE_ALL_MSK, all); + + reg |= FIELD_PREP(AD7280A_TRANS_WRITE_CRC_MSK, + ad7280_calc_crc8(st->crc_tab, reg >> 11)); + /* Reserved b010 pattern not included crc calc */ + reg |= AD7280A_TRANS_WRITE_RES_PATTERN; + + st->tx = cpu_to_be32(reg); + + return spi_write(st->spi, &st->tx, sizeof(st->tx)); +} + +static int ad7280_read_reg(struct ad7280_state *st, unsigned int devaddr, + unsigned int addr) +{ + int ret; + unsigned int tmp; + + /* turns off the read operation on all parts */ + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_CTRL_HB_REG, 1, + FIELD_PREP(AD7280A_CTRL_HB_CONV_INPUT_MSK, + AD7280A_CTRL_HB_CONV_INPUT_ALL) | + FIELD_PREP(AD7280A_CTRL_HB_CONV_RREAD_MSK, + AD7280A_CTRL_HB_CONV_RREAD_NO) | + FIELD_PREP(AD7280A_CTRL_HB_CONV_AVG_MSK, + st->oversampling_ratio)); + if (ret) + return ret; + + /* turns on the read operation on the addressed part */ + ret = ad7280_write(st, devaddr, AD7280A_CTRL_HB_REG, 0, + FIELD_PREP(AD7280A_CTRL_HB_CONV_INPUT_MSK, + AD7280A_CTRL_HB_CONV_INPUT_ALL) | + FIELD_PREP(AD7280A_CTRL_HB_CONV_RREAD_MSK, + AD7280A_CTRL_HB_CONV_RREAD_ALL) | + FIELD_PREP(AD7280A_CTRL_HB_CONV_AVG_MSK, + st->oversampling_ratio)); + if (ret) + return ret; + + /* Set register address on the part to be read from */ + ret = ad7280_write(st, devaddr, AD7280A_READ_REG, 0, + FIELD_PREP(AD7280A_READ_ADDR_MSK, addr)); + if (ret) + return ret; + + ret = __ad7280_read32(st, &tmp); + if (ret) + return ret; + + if (ad7280_check_crc(st, tmp)) + return -EIO; + + if ((FIELD_GET(AD7280A_TRANS_READ_DEVADDR_MSK, tmp) != devaddr) || + (FIELD_GET(AD7280A_TRANS_READ_REG_REGADDR_MSK, tmp) != addr)) + return -EFAULT; + + return FIELD_GET(AD7280A_TRANS_READ_REG_DATA_MSK, tmp); +} + +static int ad7280_read_channel(struct ad7280_state *st, unsigned int devaddr, + unsigned int addr) +{ + int ret; + unsigned int tmp; + + ret = ad7280_write(st, devaddr, AD7280A_READ_REG, 0, + FIELD_PREP(AD7280A_READ_ADDR_MSK, addr)); + if (ret) + return ret; + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_CTRL_HB_REG, 1, + FIELD_PREP(AD7280A_CTRL_HB_CONV_INPUT_MSK, + AD7280A_CTRL_HB_CONV_INPUT_ALL) | + FIELD_PREP(AD7280A_CTRL_HB_CONV_RREAD_MSK, + AD7280A_CTRL_HB_CONV_RREAD_NO) | + FIELD_PREP(AD7280A_CTRL_HB_CONV_AVG_MSK, + st->oversampling_ratio)); + if (ret) + return ret; + + ret = ad7280_write(st, devaddr, AD7280A_CTRL_HB_REG, 0, + FIELD_PREP(AD7280A_CTRL_HB_CONV_INPUT_MSK, + AD7280A_CTRL_HB_CONV_INPUT_ALL) | + FIELD_PREP(AD7280A_CTRL_HB_CONV_RREAD_MSK, + AD7280A_CTRL_HB_CONV_RREAD_ALL) | + FIELD_PREP(AD7280A_CTRL_HB_CONV_START_MSK, + AD7280A_CTRL_HB_CONV_START_CS) | + FIELD_PREP(AD7280A_CTRL_HB_CONV_AVG_MSK, + st->oversampling_ratio)); + if (ret) + return ret; + + ad7280_delay(st); + + ret = __ad7280_read32(st, &tmp); + if (ret) + return ret; + + if (ad7280_check_crc(st, tmp)) + return -EIO; + + if ((FIELD_GET(AD7280A_TRANS_READ_DEVADDR_MSK, tmp) != devaddr) || + (FIELD_GET(AD7280A_TRANS_READ_CONV_CHANADDR_MSK, tmp) != addr)) + return -EFAULT; + + return FIELD_GET(AD7280A_TRANS_READ_CONV_DATA_MSK, tmp); +} + +static int ad7280_read_all_channels(struct ad7280_state *st, unsigned int cnt, + unsigned int *array) +{ + int i, ret; + unsigned int tmp, sum = 0; + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_READ_REG, 1, + AD7280A_CELL_VOLTAGE_1_REG << 2); + if (ret) + return ret; + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_CTRL_HB_REG, 1, + FIELD_PREP(AD7280A_CTRL_HB_CONV_INPUT_MSK, + AD7280A_CTRL_HB_CONV_INPUT_ALL) | + FIELD_PREP(AD7280A_CTRL_HB_CONV_RREAD_MSK, + AD7280A_CTRL_HB_CONV_RREAD_ALL) | + FIELD_PREP(AD7280A_CTRL_HB_CONV_START_MSK, + AD7280A_CTRL_HB_CONV_START_CS) | + FIELD_PREP(AD7280A_CTRL_HB_CONV_AVG_MSK, + st->oversampling_ratio)); + if (ret) + return ret; + + ad7280_delay(st); + + for (i = 0; i < cnt; i++) { + ret = __ad7280_read32(st, &tmp); + if (ret) + return ret; + + if (ad7280_check_crc(st, tmp)) + return -EIO; + + if (array) + array[i] = tmp; + /* only sum cell voltages */ + if (FIELD_GET(AD7280A_TRANS_READ_CONV_CHANADDR_MSK, tmp) <= + AD7280A_CELL_VOLTAGE_6_REG) + sum += FIELD_GET(AD7280A_TRANS_READ_CONV_DATA_MSK, tmp); + } + + return sum; +} + +static void ad7280_sw_power_down(void *data) +{ + struct ad7280_state *st = data; + + ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_CTRL_HB_REG, 1, + AD7280A_CTRL_HB_PWRDN_SW | + FIELD_PREP(AD7280A_CTRL_HB_CONV_AVG_MSK, st->oversampling_ratio)); +} + +static int ad7280_chain_setup(struct ad7280_state *st) +{ + unsigned int val, n; + int ret; + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_CTRL_LB_REG, 1, + FIELD_PREP(AD7280A_CTRL_LB_DAISY_CHAIN_RB_MSK, 1) | + FIELD_PREP(AD7280A_CTRL_LB_LOCK_DEV_ADDR_MSK, 1) | + AD7280A_CTRL_LB_MUST_SET | + FIELD_PREP(AD7280A_CTRL_LB_SWRST_MSK, 1) | + st->ctrl_lb); + if (ret) + return ret; + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_CTRL_LB_REG, 1, + FIELD_PREP(AD7280A_CTRL_LB_DAISY_CHAIN_RB_MSK, 1) | + FIELD_PREP(AD7280A_CTRL_LB_LOCK_DEV_ADDR_MSK, 1) | + AD7280A_CTRL_LB_MUST_SET | + FIELD_PREP(AD7280A_CTRL_LB_SWRST_MSK, 0) | + st->ctrl_lb); + if (ret) + goto error_power_down; + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_READ_REG, 1, + FIELD_PREP(AD7280A_READ_ADDR_MSK, AD7280A_CTRL_LB_REG)); + if (ret) + goto error_power_down; + + for (n = 0; n <= AD7280A_MAX_CHAIN; n++) { + ret = __ad7280_read32(st, &val); + if (ret) + goto error_power_down; + + if (val == 0) + return n - 1; + + if (ad7280_check_crc(st, val)) { + ret = -EIO; + goto error_power_down; + } + + if (n != ad7280a_devaddr(FIELD_GET(AD7280A_TRANS_READ_DEVADDR_MSK, val))) { + ret = -EIO; + goto error_power_down; + } + } + ret = -EFAULT; + +error_power_down: + ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_CTRL_HB_REG, 1, + AD7280A_CTRL_HB_PWRDN_SW | + FIELD_PREP(AD7280A_CTRL_HB_CONV_AVG_MSK, st->oversampling_ratio)); + + return ret; +} + +static ssize_t ad7280_show_balance_sw(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, char *buf) +{ + struct ad7280_state *st = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", + !!(st->cb_mask[chan->address >> 8] & + BIT(chan->address & 0xFF))); +} + +static ssize_t ad7280_store_balance_sw(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ad7280_state *st = iio_priv(indio_dev); + unsigned int devaddr, ch; + bool readin; + int ret; + + ret = kstrtobool(buf, &readin); + if (ret) + return ret; + + devaddr = chan->address >> 8; + ch = chan->address & 0xFF; + + guard(mutex)(&st->lock); + + if (readin) + st->cb_mask[devaddr] |= BIT(ch); + else + st->cb_mask[devaddr] &= ~BIT(ch); + + ret = ad7280_write(st, devaddr, AD7280A_CELL_BALANCE_REG, 0, + FIELD_PREP(AD7280A_CELL_BALANCE_CHAN_BITMAP_MSK, + st->cb_mask[devaddr])); + + return ret ? ret : len; +} + +static ssize_t ad7280_show_balance_timer(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ad7280_state *st = iio_priv(indio_dev); + u8 devaddr = chan->address >> 8; + u8 ch = chan->address & 0xFF; + unsigned int msecs; + int ret; + + scoped_guard(mutex, &st->lock) + ret = ad7280_read_reg(st, devaddr, ch + AD7280A_CB1_TIMER_REG); + if (ret < 0) + return ret; + + msecs = FIELD_GET(AD7280A_CB_TIMER_VAL_MSK, ret) * 71500; + + return sysfs_emit(buf, "%u.%u\n", msecs / 1000, msecs % 1000); +} + +static ssize_t ad7280_store_balance_timer(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ad7280_state *st = iio_priv(indio_dev); + u8 devaddr = chan->address >> 8; + u8 ch = chan->address & 0xFF; + int val, val2; + int ret; + + ret = iio_str_to_fixpoint(buf, 100, &val, &val2); + if (ret) + return ret; + + val = val * 1000 + val2; + val /= 71500; + + if (val > 31) + return -EINVAL; + + guard(mutex)(&st->lock); + + ret = ad7280_write(st, devaddr, ch + AD7280A_CB1_TIMER_REG, 0, + FIELD_PREP(AD7280A_CB_TIMER_VAL_MSK, val)); + + return ret ? ret : len; +} + +static const struct iio_chan_spec_ext_info ad7280_cell_ext_info[] = { + { + .name = "balance_switch_en", + .read = ad7280_show_balance_sw, + .write = ad7280_store_balance_sw, + .shared = IIO_SEPARATE, + }, { + .name = "balance_switch_timer", + .read = ad7280_show_balance_timer, + .write = ad7280_store_balance_timer, + .shared = IIO_SEPARATE, + }, + { } +}; + +static const struct iio_event_spec ad7280_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE), + }, +}; + +static void ad7280_voltage_channel_init(struct iio_chan_spec *chan, int i, + bool irq_present) +{ + chan->type = IIO_VOLTAGE; + chan->differential = 1; + chan->channel = i; + chan->channel2 = chan->channel + 1; + if (irq_present) { + chan->event_spec = ad7280_events; + chan->num_event_specs = ARRAY_SIZE(ad7280_events); + } + chan->ext_info = ad7280_cell_ext_info; +} + +static void ad7280_temp_channel_init(struct iio_chan_spec *chan, int i, + bool irq_present) +{ + chan->type = IIO_TEMP; + chan->channel = i; + if (irq_present) { + chan->event_spec = ad7280_events; + chan->num_event_specs = ARRAY_SIZE(ad7280_events); + } +} + +static void ad7280_common_fields_init(struct iio_chan_spec *chan, int addr, + int cnt) +{ + chan->indexed = 1; + chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); + chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE); + chan->info_mask_shared_by_all = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO); + chan->address = addr; + chan->scan_index = cnt; + chan->scan_type.sign = 'u'; + chan->scan_type.realbits = 12; + chan->scan_type.storagebits = 32; +} + +static void ad7280_total_voltage_channel_init(struct iio_chan_spec *chan, + int cnt, int dev) +{ + chan->type = IIO_VOLTAGE; + chan->differential = 1; + chan->channel = 0; + chan->channel2 = dev * AD7280A_CELLS_PER_DEV; + chan->address = AD7280A_ALL_CELLS; + chan->indexed = 1; + chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); + chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE); + chan->scan_index = cnt; + chan->scan_type.sign = 'u'; + chan->scan_type.realbits = 32; + chan->scan_type.storagebits = 32; +} + +static void ad7280_init_dev_channels(struct ad7280_state *st, int dev, int *cnt, + bool irq_present) +{ + int addr, ch, i; + struct iio_chan_spec *chan; + + for (ch = AD7280A_CELL_VOLTAGE_1_REG; ch <= AD7280A_AUX_ADC_6_REG; ch++) { + chan = &st->channels[*cnt]; + + if (ch < AD7280A_AUX_ADC_1_REG) { + i = AD7280A_CALC_VOLTAGE_CHAN_NUM(dev, ch); + ad7280_voltage_channel_init(chan, i, irq_present); + } else { + i = AD7280A_CALC_TEMP_CHAN_NUM(dev, ch); + ad7280_temp_channel_init(chan, i, irq_present); + } + + addr = ad7280a_devaddr(dev) << 8 | ch; + ad7280_common_fields_init(chan, addr, *cnt); + + (*cnt)++; + } +} + +static int ad7280_channel_init(struct ad7280_state *st, bool irq_present) +{ + int dev, cnt = 0; + + st->channels = devm_kcalloc(&st->spi->dev, (st->slave_num + 1) * 12 + 1, + sizeof(*st->channels), GFP_KERNEL); + if (!st->channels) + return -ENOMEM; + + for (dev = 0; dev <= st->slave_num; dev++) + ad7280_init_dev_channels(st, dev, &cnt, irq_present); + + ad7280_total_voltage_channel_init(&st->channels[cnt], cnt, dev); + + return cnt + 1; +} + +static int ad7280a_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, int *val2) +{ + struct ad7280_state *st = iio_priv(indio_dev); + + switch (chan->type) { + case IIO_VOLTAGE: + switch (dir) { + case IIO_EV_DIR_RISING: + *val = 1000 + (st->cell_threshhigh * 1568L) / 100; + return IIO_VAL_INT; + case IIO_EV_DIR_FALLING: + *val = 1000 + (st->cell_threshlow * 1568L) / 100; + return IIO_VAL_INT; + default: + return -EINVAL; + } + break; + case IIO_TEMP: + switch (dir) { + case IIO_EV_DIR_RISING: + *val = ((st->aux_threshhigh) * 196L) / 10; + return IIO_VAL_INT; + case IIO_EV_DIR_FALLING: + *val = (st->aux_threshlow * 196L) / 10; + return IIO_VAL_INT; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } +} + +static int ad7280a_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct ad7280_state *st = iio_priv(indio_dev); + unsigned int addr; + long value; + int ret; + + if (val2 != 0) + return -EINVAL; + + guard(mutex)(&st->lock); + + switch (chan->type) { + case IIO_VOLTAGE: + value = ((val - 1000) * 100) / 1568; /* LSB 15.68mV */ + value = clamp(value, 0L, 0xFFL); + switch (dir) { + case IIO_EV_DIR_RISING: + addr = AD7280A_CELL_OVERVOLTAGE_REG; + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, addr, + 1, value); + if (ret) + return ret; + st->cell_threshhigh = value; + return 0; + case IIO_EV_DIR_FALLING: + addr = AD7280A_CELL_UNDERVOLTAGE_REG; + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, addr, + 1, value); + if (ret) + return ret; + st->cell_threshlow = value; + return 0; + default: + return -EINVAL; + } + case IIO_TEMP: + value = (val * 10) / 196; /* LSB 19.6mV */ + value = clamp(value, 0L, 0xFFL); + switch (dir) { + case IIO_EV_DIR_RISING: + addr = AD7280A_AUX_ADC_OVERVOLTAGE_REG; + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, addr, + 1, value); + if (ret) + return ret; + st->aux_threshhigh = value; + return 0; + case IIO_EV_DIR_FALLING: + addr = AD7280A_AUX_ADC_UNDERVOLTAGE_REG; + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, addr, + 1, value); + if (ret) + return ret; + st->aux_threshlow = value; + return 0; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static irqreturn_t ad7280_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ad7280_state *st = iio_priv(indio_dev); + int i, ret; + + unsigned int *channels __free(kfree) = kzalloc_objs(*channels, + st->scan_cnt); + if (!channels) + return IRQ_HANDLED; + + ret = ad7280_read_all_channels(st, st->scan_cnt, channels); + if (ret < 0) + return IRQ_HANDLED; + + for (i = 0; i < st->scan_cnt; i++) { + unsigned int val; + + val = FIELD_GET(AD7280A_TRANS_READ_CONV_DATA_MSK, channels[i]); + if (FIELD_GET(AD7280A_TRANS_READ_CONV_CHANADDR_MSK, channels[i]) <= + AD7280A_CELL_VOLTAGE_6_REG) { + if (val >= st->cell_threshhigh) { + u64 tmp = IIO_DIFF_EVENT_CODE(IIO_VOLTAGE, 0, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING); + iio_push_event(indio_dev, tmp, + iio_get_time_ns(indio_dev)); + } else if (val <= st->cell_threshlow) { + u64 tmp = IIO_DIFF_EVENT_CODE(IIO_VOLTAGE, 0, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING); + iio_push_event(indio_dev, tmp, + iio_get_time_ns(indio_dev)); + } + } else { + if (val >= st->aux_threshhigh) { + u64 tmp = IIO_UNMOD_EVENT_CODE(IIO_TEMP, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING); + iio_push_event(indio_dev, tmp, + iio_get_time_ns(indio_dev)); + } else if (val <= st->aux_threshlow) { + u64 tmp = IIO_UNMOD_EVENT_CODE(IIO_TEMP, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING); + iio_push_event(indio_dev, tmp, + iio_get_time_ns(indio_dev)); + } + } + } + + return IRQ_HANDLED; +} + +static void ad7280_update_delay(struct ad7280_state *st) +{ + /* + * Total Conversion Time = ((tACQ + tCONV) * + * (Number of Conversions per Part)) − + * tACQ + ((N - 1) * tDELAY) + * + * Readback Delay = Total Conversion Time + tWAIT + */ + + st->readback_delay_us = + ((ad7280a_t_acq_ns[st->acquisition_time & 0x3] + 720) * + (AD7280A_NUM_CH * ad7280a_n_avg[st->oversampling_ratio & 0x3])) - + ad7280a_t_acq_ns[st->acquisition_time & 0x3] + st->slave_num * 250; + + /* Convert to usecs */ + st->readback_delay_us = DIV_ROUND_UP(st->readback_delay_us, 1000); + st->readback_delay_us += 5; /* Add tWAIT */ +} + +static int ad7280_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad7280_state *st = iio_priv(indio_dev); + u8 devaddr = chan->address >> 8; + u8 ch = chan->address & 0xFF; + int ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: { + guard(mutex)(&st->lock); + + if (chan->address == AD7280A_ALL_CELLS) + ret = ad7280_read_all_channels(st, st->scan_cnt, NULL); + else + ret = ad7280_read_channel(st, devaddr, ch); + + if (ret < 0) + return ret; + + *val = ret; + + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_SCALE: + if (ch <= AD7280A_CELL_VOLTAGE_6_REG) + *val = 4000; + else + *val = 5000; + + *val2 = AD7280A_BITS; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = ad7280a_n_avg[st->oversampling_ratio]; + return IIO_VAL_INT; + } + return -EINVAL; +} + +static int ad7280_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ad7280_state *st = iio_priv(indio_dev); + int i; + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + if (val2 != 0) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(ad7280a_n_avg); i++) { + if (val == ad7280a_n_avg[i]) { + st->oversampling_ratio = i; + ad7280_update_delay(st); + return 0; + } + } + return -EINVAL; + default: + return -EINVAL; + } +} + +static const struct iio_info ad7280_info = { + .read_raw = ad7280_read_raw, + .write_raw = ad7280_write_raw, + .read_event_value = &ad7280a_read_thresh, + .write_event_value = &ad7280a_write_thresh, +}; + +static const struct iio_info ad7280_info_no_irq = { + .read_raw = ad7280_read_raw, + .write_raw = ad7280_write_raw, +}; + +static int ad7280_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct ad7280_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); + spi_set_drvdata(spi, indio_dev); + st->spi = spi; + mutex_init(&st->lock); + + st->thermistor_term_en = + device_property_read_bool(dev, "adi,thermistor-termination"); + + if (device_property_present(dev, "adi,acquisition-time-ns")) { + u32 val; + + ret = device_property_read_u32(dev, "adi,acquisition-time-ns", &val); + if (ret) + return ret; + + switch (val) { + case 400: + st->acquisition_time = AD7280A_CTRL_LB_ACQ_TIME_400ns; + break; + case 800: + st->acquisition_time = AD7280A_CTRL_LB_ACQ_TIME_800ns; + break; + case 1200: + st->acquisition_time = AD7280A_CTRL_LB_ACQ_TIME_1200ns; + break; + case 1600: + st->acquisition_time = AD7280A_CTRL_LB_ACQ_TIME_1600ns; + break; + default: + return dev_err_probe(dev, -EINVAL, + "Firmware provided acquisition time is invalid\n"); + } + } else { + st->acquisition_time = AD7280A_CTRL_LB_ACQ_TIME_400ns; + } + + /* Alert masks are intended for when particular inputs are not wired up */ + if (device_property_present(dev, "adi,voltage-alert-last-chan")) { + u32 val; + + ret = device_property_read_u32(dev, "adi,voltage-alert-last-chan", &val); + if (ret) + return ret; + + switch (val) { + case 3: + st->chain_last_alert_ignore |= AD7280A_ALERT_REMOVE_VIN4_VIN5; + break; + case 4: + st->chain_last_alert_ignore |= AD7280A_ALERT_REMOVE_VIN5; + break; + case 5: + break; + default: + dev_err(dev, + "Firmware provided last voltage alert channel invalid\n"); + break; + } + } + crc8_populate_msb(st->crc_tab, POLYNOM); + + st->spi->max_speed_hz = AD7280A_MAX_SPI_CLK_HZ; + st->spi->mode = SPI_MODE_1; + ret = spi_setup(st->spi); + if (ret < 0) + return ret; + + st->ctrl_lb = FIELD_PREP(AD7280A_CTRL_LB_ACQ_TIME_MSK, st->acquisition_time) | + FIELD_PREP(AD7280A_CTRL_LB_THERMISTOR_MSK, st->thermistor_term_en); + st->oversampling_ratio = 0; /* No oversampling */ + + ret = ad7280_chain_setup(st); + if (ret < 0) + return ret; + + st->slave_num = ret; + st->scan_cnt = (st->slave_num + 1) * AD7280A_NUM_CH; + st->cell_threshhigh = 0xFF; + st->aux_threshhigh = 0xFF; + + ret = devm_add_action_or_reset(dev, ad7280_sw_power_down, st); + if (ret) + return ret; + + ad7280_update_delay(st); + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = ad7280_channel_init(st, spi->irq > 0); + if (ret < 0) + return ret; + + indio_dev->num_channels = ret; + indio_dev->channels = st->channels; + if (spi->irq > 0) { + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, + AD7280A_ALERT_REG, 1, + AD7280A_ALERT_RELAY_SIG_CHAIN_DOWN); + if (ret) + return ret; + + ret = ad7280_write(st, ad7280a_devaddr(st->slave_num), + AD7280A_ALERT_REG, 0, + AD7280A_ALERT_GEN_STATIC_HIGH | + FIELD_PREP(AD7280A_ALERT_REMOVE_MSK, + st->chain_last_alert_ignore)); + if (ret) + return ret; + + ret = devm_request_threaded_irq(dev, spi->irq, + NULL, + ad7280_event_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + indio_dev->name, + indio_dev); + if (ret) + return ret; + + indio_dev->info = &ad7280_info; + } else { + indio_dev->info = &ad7280_info_no_irq; + } + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id ad7280_id[] = { + { .name = "ad7280a" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7280_id); + +static struct spi_driver ad7280_driver = { + .driver = { + .name = "ad7280", + }, + .probe = ad7280_probe, + .id_table = ad7280_id, +}; +module_spi_driver(ad7280_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7280A"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ad7291.c b/drivers/iio/adc/ad7291.c new file mode 100644 index 000000000..6a1493e27 --- /dev/null +++ b/drivers/iio/adc/ad7291.c @@ -0,0 +1,563 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * AD7291 8-Channel, I2C, 12-Bit SAR ADC with Temperature Sensor + * + * Copyright 2010-2011 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* + * Simplified handling + * + * If no events enabled - single polled channel read + * If event enabled direct reads disable unless channel + * is in the read mask. + * + * The noise-delayed bit as per datasheet suggestion is always enabled. + */ + +/* + * AD7291 registers definition + */ +#define AD7291_COMMAND 0x00 +#define AD7291_VOLTAGE 0x01 +#define AD7291_T_SENSE 0x02 +#define AD7291_T_AVERAGE 0x03 +#define AD7291_DATA_HIGH(x) ((x) * 3 + 0x4) +#define AD7291_DATA_LOW(x) ((x) * 3 + 0x5) +#define AD7291_HYST(x) ((x) * 3 + 0x6) +#define AD7291_VOLTAGE_ALERT_STATUS 0x1F +#define AD7291_T_ALERT_STATUS 0x20 + +#define AD7291_BITS 12 +#define AD7291_VOLTAGE_LIMIT_COUNT 8 + + +/* + * AD7291 command + */ +#define AD7291_AUTOCYCLE BIT(0) +#define AD7291_RESET BIT(1) +#define AD7291_ALERT_CLEAR BIT(2) +#define AD7291_ALERT_POLARITY BIT(3) +#define AD7291_EXT_REF BIT(4) +#define AD7291_NOISE_DELAY BIT(5) +#define AD7291_T_SENSE_MASK BIT(7) +#define AD7291_VOLTAGE_MASK GENMASK(15, 8) +#define AD7291_VOLTAGE_OFFSET 8 + +/* + * AD7291 value masks + */ +#define AD7291_VALUE_MASK GENMASK(11, 0) + +/* + * AD7291 alert register bits + */ +#define AD7291_T_LOW BIT(0) +#define AD7291_T_HIGH BIT(1) +#define AD7291_T_AVG_LOW BIT(2) +#define AD7291_T_AVG_HIGH BIT(3) +#define AD7291_V_LOW(x) BIT((x) * 2) +#define AD7291_V_HIGH(x) BIT((x) * 2 + 1) + + +struct ad7291_chip_info { + struct i2c_client *client; + struct regulator *reg; + u16 command; + u16 c_mask; /* Active voltage channels for events */ + struct mutex state_lock; +}; + +static int ad7291_i2c_read(struct ad7291_chip_info *chip, u8 reg, u16 *data) +{ + struct i2c_client *client = chip->client; + int ret = 0; + + ret = i2c_smbus_read_word_swapped(client, reg); + if (ret < 0) { + dev_err(&client->dev, "I2C read error\n"); + return ret; + } + + *data = ret; + + return 0; +} + +static int ad7291_i2c_write(struct ad7291_chip_info *chip, u8 reg, u16 data) +{ + return i2c_smbus_write_word_swapped(chip->client, reg, data); +} + +static irqreturn_t ad7291_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ad7291_chip_info *chip = iio_priv(private); + u16 t_status, v_status; + u16 command; + int i; + s64 timestamp = iio_get_time_ns(indio_dev); + + if (ad7291_i2c_read(chip, AD7291_T_ALERT_STATUS, &t_status)) + return IRQ_HANDLED; + + if (ad7291_i2c_read(chip, AD7291_VOLTAGE_ALERT_STATUS, &v_status)) + return IRQ_HANDLED; + + if (!(t_status || v_status)) + return IRQ_HANDLED; + + command = chip->command | AD7291_ALERT_CLEAR; + ad7291_i2c_write(chip, AD7291_COMMAND, command); + + command = chip->command & ~AD7291_ALERT_CLEAR; + ad7291_i2c_write(chip, AD7291_COMMAND, command); + + /* For now treat t_sense and t_sense_average the same */ + if ((t_status & AD7291_T_LOW) || (t_status & AD7291_T_AVG_LOW)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + if ((t_status & AD7291_T_HIGH) || (t_status & AD7291_T_AVG_HIGH)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + + for (i = 0; i < AD7291_VOLTAGE_LIMIT_COUNT; i++) { + if (v_status & AD7291_V_LOW(i)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + i, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + if (v_status & AD7291_V_HIGH(i)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + i, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + } + + return IRQ_HANDLED; +} + +static unsigned int ad7291_threshold_reg(const struct iio_chan_spec *chan, + enum iio_event_direction dir, + enum iio_event_info info) +{ + unsigned int offset; + + switch (chan->type) { + case IIO_VOLTAGE: + offset = chan->channel; + break; + case IIO_TEMP: + offset = AD7291_VOLTAGE_OFFSET; + break; + default: + return 0; + } + + switch (info) { + case IIO_EV_INFO_VALUE: + if (dir == IIO_EV_DIR_FALLING) + return AD7291_DATA_HIGH(offset); + else + return AD7291_DATA_LOW(offset); + case IIO_EV_INFO_HYSTERESIS: + return AD7291_HYST(offset); + default: + break; + } + return 0; +} + +static int ad7291_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct ad7291_chip_info *chip = iio_priv(indio_dev); + int ret; + u16 uval; + + ret = ad7291_i2c_read(chip, ad7291_threshold_reg(chan, dir, info), + &uval); + if (ret < 0) + return ret; + + if (info == IIO_EV_INFO_HYSTERESIS || chan->type == IIO_VOLTAGE) + *val = uval & AD7291_VALUE_MASK; + + else + *val = sign_extend32(uval, 11); + + return IIO_VAL_INT; +} + +static int ad7291_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct ad7291_chip_info *chip = iio_priv(indio_dev); + + if (info == IIO_EV_INFO_HYSTERESIS || chan->type == IIO_VOLTAGE) { + if (val > AD7291_VALUE_MASK || val < 0) + return -EINVAL; + } else { + if (val > 2047 || val < -2048) + return -EINVAL; + } + + return ad7291_i2c_write(chip, ad7291_threshold_reg(chan, dir, info), + val); +} + +static int ad7291_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct ad7291_chip_info *chip = iio_priv(indio_dev); + /* + * To be enabled the channel must simply be on. If any are enabled + * we are in continuous sampling mode + */ + + switch (chan->type) { + case IIO_VOLTAGE: + return !!(chip->c_mask & BIT(15 - chan->channel)); + case IIO_TEMP: + /* always on */ + return 1; + default: + return -EINVAL; + } + +} + +static int ad7291_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + int ret = 0; + struct ad7291_chip_info *chip = iio_priv(indio_dev); + unsigned int mask; + u16 regval; + + mutex_lock(&chip->state_lock); + regval = chip->command; + /* + * To be enabled the channel must simply be on. If any are enabled + * use continuous sampling mode. + * Possible to disable temp as well but that makes single read tricky. + */ + + mask = BIT(15 - chan->channel); + + switch (chan->type) { + case IIO_VOLTAGE: + if ((!state) && (chip->c_mask & mask)) + chip->c_mask &= ~mask; + else if (state && (!(chip->c_mask & mask))) + chip->c_mask |= mask; + else + break; + + regval &= ~AD7291_AUTOCYCLE; + regval |= chip->c_mask; + if (chip->c_mask) /* Enable autocycle? */ + regval |= AD7291_AUTOCYCLE; + + ret = ad7291_i2c_write(chip, AD7291_COMMAND, regval); + if (ret < 0) + goto error_ret; + + chip->command = regval; + break; + default: + ret = -EINVAL; + } + +error_ret: + mutex_unlock(&chip->state_lock); + return ret; +} + +static int ad7291_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + int ret; + struct ad7291_chip_info *chip = iio_priv(indio_dev); + u16 regval; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_VOLTAGE: + mutex_lock(&chip->state_lock); + /* If in autocycle mode drop through */ + if (chip->command & AD7291_AUTOCYCLE) { + mutex_unlock(&chip->state_lock); + return -EBUSY; + } + /* Enable this channel alone */ + regval = chip->command & (~AD7291_VOLTAGE_MASK); + regval |= BIT(15 - chan->channel); + ret = ad7291_i2c_write(chip, AD7291_COMMAND, regval); + if (ret < 0) { + mutex_unlock(&chip->state_lock); + return ret; + } + /* Read voltage */ + ret = i2c_smbus_read_word_swapped(chip->client, + AD7291_VOLTAGE); + if (ret < 0) { + mutex_unlock(&chip->state_lock); + return ret; + } + *val = ret & AD7291_VALUE_MASK; + mutex_unlock(&chip->state_lock); + return IIO_VAL_INT; + case IIO_TEMP: + /* Assumes tsense bit of command register always set */ + ret = i2c_smbus_read_word_swapped(chip->client, + AD7291_T_SENSE); + if (ret < 0) + return ret; + *val = sign_extend32(ret, 11); + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_AVERAGE_RAW: + ret = i2c_smbus_read_word_swapped(chip->client, + AD7291_T_AVERAGE); + if (ret < 0) + return ret; + *val = sign_extend32(ret, 11); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + if (chip->reg) { + int vref; + + vref = regulator_get_voltage(chip->reg); + if (vref < 0) + return vref; + *val = vref / 1000; + } else { + *val = 2500; + } + *val2 = AD7291_BITS; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_TEMP: + /* + * One LSB of the ADC corresponds to 0.25 deg C. + * The temperature reading is in 12-bit twos + * complement format + */ + *val = 250; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static const struct iio_event_spec ad7291_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_HYSTERESIS), + }, +}; + +#define AD7291_VOLTAGE_CHAN(_chan) \ +{ \ + .type = IIO_VOLTAGE, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .indexed = 1, \ + .channel = _chan, \ + .event_spec = ad7291_events, \ + .num_event_specs = ARRAY_SIZE(ad7291_events), \ +} + +static const struct iio_chan_spec ad7291_channels[] = { + AD7291_VOLTAGE_CHAN(0), + AD7291_VOLTAGE_CHAN(1), + AD7291_VOLTAGE_CHAN(2), + AD7291_VOLTAGE_CHAN(3), + AD7291_VOLTAGE_CHAN(4), + AD7291_VOLTAGE_CHAN(5), + AD7291_VOLTAGE_CHAN(6), + AD7291_VOLTAGE_CHAN(7), + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_AVERAGE_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .indexed = 1, + .channel = 0, + .event_spec = ad7291_events, + .num_event_specs = ARRAY_SIZE(ad7291_events), + } +}; + +static const struct iio_info ad7291_info = { + .read_raw = &ad7291_read_raw, + .read_event_config = &ad7291_read_event_config, + .write_event_config = &ad7291_write_event_config, + .read_event_value = &ad7291_read_event_value, + .write_event_value = &ad7291_write_event_value, +}; + +static void ad7291_reg_disable(void *reg) +{ + regulator_disable(reg); +} + +static int ad7291_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct ad7291_chip_info *chip; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + chip = iio_priv(indio_dev); + + mutex_init(&chip->state_lock); + + chip->client = client; + + chip->command = AD7291_NOISE_DELAY | + AD7291_T_SENSE_MASK | /* Tsense always enabled */ + AD7291_ALERT_POLARITY; /* set irq polarity low level */ + + chip->reg = devm_regulator_get_optional(&client->dev, "vref"); + if (IS_ERR(chip->reg)) { + if (PTR_ERR(chip->reg) != -ENODEV) + return PTR_ERR(chip->reg); + + chip->reg = NULL; + } + + if (chip->reg) { + ret = regulator_enable(chip->reg); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&client->dev, ad7291_reg_disable, + chip->reg); + if (ret) + return ret; + + chip->command |= AD7291_EXT_REF; + } + + indio_dev->name = id->name; + indio_dev->channels = ad7291_channels; + indio_dev->num_channels = ARRAY_SIZE(ad7291_channels); + + indio_dev->info = &ad7291_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = ad7291_i2c_write(chip, AD7291_COMMAND, AD7291_RESET); + if (ret) + return -EIO; + + ret = ad7291_i2c_write(chip, AD7291_COMMAND, chip->command); + if (ret) + return -EIO; + + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, + &ad7291_event_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + id->name, + indio_dev); + if (ret) + return ret; + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id ad7291_id[] = { + { .name = "ad7291" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, ad7291_id); + +static const struct of_device_id ad7291_of_match[] = { + { .compatible = "adi,ad7291" }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7291_of_match); + +static struct i2c_driver ad7291_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = ad7291_of_match, + }, + .probe = ad7291_probe, + .id_table = ad7291_id, +}; +module_i2c_driver(ad7291_driver); + +MODULE_AUTHOR("Sonic Zhang "); +MODULE_DESCRIPTION("Analog Devices AD7291 ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ad7292.c b/drivers/iio/adc/ad7292.c new file mode 100644 index 000000000..1d7bf46b8 --- /dev/null +++ b/drivers/iio/adc/ad7292.c @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices AD7292 SPI ADC driver + * + * Copyright 2019 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#define ADI_VENDOR_ID 0x0018 + +#define AD7292_INTERNAL_REF_MV 1250 + +/* AD7292 registers definition */ +#define AD7292_REG_VENDOR_ID 0x00 +#define AD7292_REG_CONF_BANK 0x05 +#define AD7292_REG_CONV_COMM 0x0E +#define AD7292_REG_ADC_CH(x) (0x10 + (x)) + +/* AD7292 configuration bank subregisters definition */ +#define AD7292_BANK_REG_VIN_RNG0 0x10 +#define AD7292_BANK_REG_VIN_RNG1 0x11 +#define AD7292_BANK_REG_SAMP_MODE 0x12 + +#define AD7292_RD_FLAG_MSK(x) (BIT(7) | ((x) & 0x3F)) + +/* AD7292_REG_ADC_CONVERSION */ +#define AD7292_ADC_DATA_MASK GENMASK(15, 6) +#define AD7292_ADC_DATA(x) FIELD_GET(AD7292_ADC_DATA_MASK, x) + +/* AD7292_CHANNEL_SAMPLING_MODE */ +#define AD7292_CH_SAMP_MODE(reg, ch) (((reg) >> 8) & BIT(ch)) + +/* AD7292_CHANNEL_VIN_RANGE */ +#define AD7292_CH_VIN_RANGE(reg, ch) ((reg) & BIT(ch)) + +#define AD7292_VOLTAGE_CHAN(_chan) \ +{ \ + .type = IIO_VOLTAGE, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .indexed = 1, \ + .channel = _chan, \ +} + +static const struct iio_chan_spec ad7292_channels[] = { + AD7292_VOLTAGE_CHAN(0), + AD7292_VOLTAGE_CHAN(1), + AD7292_VOLTAGE_CHAN(2), + AD7292_VOLTAGE_CHAN(3), + AD7292_VOLTAGE_CHAN(4), + AD7292_VOLTAGE_CHAN(5), + AD7292_VOLTAGE_CHAN(6), + AD7292_VOLTAGE_CHAN(7) +}; + +static const struct iio_chan_spec ad7292_channels_diff[] = { + { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .indexed = 1, + .differential = 1, + .channel = 0, + .channel2 = 1, + }, + AD7292_VOLTAGE_CHAN(2), + AD7292_VOLTAGE_CHAN(3), + AD7292_VOLTAGE_CHAN(4), + AD7292_VOLTAGE_CHAN(5), + AD7292_VOLTAGE_CHAN(6), + AD7292_VOLTAGE_CHAN(7) +}; + +struct ad7292_state { + struct spi_device *spi; + unsigned short vref_mv; + + __be16 d16 __aligned(IIO_DMA_MINALIGN); + u8 d8[2]; +}; + +static int ad7292_spi_reg_read(struct ad7292_state *st, unsigned int addr) +{ + int ret; + + st->d8[0] = AD7292_RD_FLAG_MSK(addr); + + ret = spi_write_then_read(st->spi, st->d8, 1, &st->d16, 2); + if (ret < 0) + return ret; + + return be16_to_cpu(st->d16); +} + +static int ad7292_spi_subreg_read(struct ad7292_state *st, unsigned int addr, + unsigned int sub_addr, unsigned int len) +{ + unsigned int shift = 16 - (8 * len); + int ret; + + st->d8[0] = AD7292_RD_FLAG_MSK(addr); + st->d8[1] = sub_addr; + + ret = spi_write_then_read(st->spi, st->d8, 2, &st->d16, len); + if (ret < 0) + return ret; + + return (be16_to_cpu(st->d16) >> shift); +} + +static int ad7292_single_conversion(struct ad7292_state *st, + unsigned int chan_addr) +{ + int ret; + + struct spi_transfer t[] = { + { + .tx_buf = &st->d8, + .len = 4, + .delay = { + .value = 6, + .unit = SPI_DELAY_UNIT_USECS + }, + }, { + .rx_buf = &st->d16, + .len = 2, + }, + }; + + st->d8[0] = chan_addr; + st->d8[1] = AD7292_RD_FLAG_MSK(AD7292_REG_CONV_COMM); + + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); + + if (ret < 0) + return ret; + + return be16_to_cpu(st->d16); +} + +static int ad7292_vin_range_multiplier(struct ad7292_state *st, int channel) +{ + int samp_mode, range0, range1, factor = 1; + + /* + * Every AD7292 ADC channel may have its input range adjusted according + * to the settings at the ADC sampling mode and VIN range subregisters. + * For a given channel, the minimum input range is equal to Vref, and it + * may be increased by a multiplier factor of 2 or 4 according to the + * following rule: + * If channel is being sampled with respect to AGND: + * factor = 4 if VIN range0 and VIN range1 equal 0 + * factor = 2 if only one of VIN ranges equal 1 + * factor = 1 if both VIN range0 and VIN range1 equal 1 + * If channel is being sampled with respect to AVDD: + * factor = 4 if VIN range0 and VIN range1 equal 0 + * Behavior is undefined if any of VIN range doesn't equal 0 + */ + + samp_mode = ad7292_spi_subreg_read(st, AD7292_REG_CONF_BANK, + AD7292_BANK_REG_SAMP_MODE, 2); + + if (samp_mode < 0) + return samp_mode; + + range0 = ad7292_spi_subreg_read(st, AD7292_REG_CONF_BANK, + AD7292_BANK_REG_VIN_RNG0, 2); + + if (range0 < 0) + return range0; + + range1 = ad7292_spi_subreg_read(st, AD7292_REG_CONF_BANK, + AD7292_BANK_REG_VIN_RNG1, 2); + + if (range1 < 0) + return range1; + + if (AD7292_CH_SAMP_MODE(samp_mode, channel)) { + /* Sampling with respect to AGND */ + if (!AD7292_CH_VIN_RANGE(range0, channel)) + factor *= 2; + + if (!AD7292_CH_VIN_RANGE(range1, channel)) + factor *= 2; + + } else { + /* Sampling with respect to AVDD */ + if (AD7292_CH_VIN_RANGE(range0, channel) || + AD7292_CH_VIN_RANGE(range1, channel)) + return -EPERM; + + factor = 4; + } + + return factor; +} + +static int ad7292_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long info) +{ + struct ad7292_state *st = iio_priv(indio_dev); + unsigned int ch_addr; + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + ch_addr = AD7292_REG_ADC_CH(chan->channel); + ret = ad7292_single_conversion(st, ch_addr); + if (ret < 0) + return ret; + + *val = AD7292_ADC_DATA(ret); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* + * To convert a raw value to standard units, the IIO defines + * this formula: Scaled value = (raw + offset) * scale. + * For the scale to be a correct multiplier for (raw + offset), + * it must be calculated as the input range divided by the + * number of possible distinct input values. Given the ADC data + * is 10 bit long, it may assume 2^10 distinct values. + * Hence, scale = range / 2^10. The IIO_VAL_FRACTIONAL_LOG2 + * return type indicates to the IIO API to divide *val by 2 to + * the power of *val2 when returning from read_raw. + */ + + ret = ad7292_vin_range_multiplier(st, chan->channel); + if (ret < 0) + return ret; + + *val = st->vref_mv * ret; + *val2 = 10; + return IIO_VAL_FRACTIONAL_LOG2; + default: + break; + } + return -EINVAL; +} + +static const struct iio_info ad7292_info = { + .read_raw = ad7292_read_raw, +}; + +static int ad7292_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct ad7292_state *st; + struct iio_dev *indio_dev; + bool diff_channels = false; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + + ret = ad7292_spi_reg_read(st, AD7292_REG_VENDOR_ID); + if (ret != ADI_VENDOR_ID) + return dev_err_probe(dev, -EINVAL, + "Wrong vendor id 0x%x\n", ret); + + ret = devm_regulator_get_enable_read_voltage(dev, "vref"); + if (ret < 0 && ret != -ENODEV) + return ret; + + st->vref_mv = ret == -ENODEV ? AD7292_INTERNAL_REF_MV : ret / 1000; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ad7292_info; + + device_for_each_child_node_scoped(dev, child) { + diff_channels = fwnode_property_read_bool(child, + "diff-channels"); + if (diff_channels) + break; + } + + if (diff_channels) { + indio_dev->num_channels = ARRAY_SIZE(ad7292_channels_diff); + indio_dev->channels = ad7292_channels_diff; + } else { + indio_dev->num_channels = ARRAY_SIZE(ad7292_channels); + indio_dev->channels = ad7292_channels; + } + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id ad7292_id_table[] = { + { .name = "ad7292" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7292_id_table); + +static const struct of_device_id ad7292_of_match[] = { + { .compatible = "adi,ad7292" }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7292_of_match); + +static struct spi_driver ad7292_driver = { + .driver = { + .name = "ad7292", + .of_match_table = ad7292_of_match, + }, + .probe = ad7292_probe, + .id_table = ad7292_id_table, +}; +module_spi_driver(ad7292_driver); + +MODULE_AUTHOR("Marcelo Schmitt "); +MODULE_DESCRIPTION("Analog Devices AD7292 ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ad7298.c b/drivers/iio/adc/ad7298.c new file mode 100644 index 000000000..c669022b5 --- /dev/null +++ b/drivers/iio/adc/ad7298.c @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD7298 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define AD7298_WRITE BIT(15) /* write to the control register */ +#define AD7298_REPEAT BIT(14) /* repeated conversion enable */ +#define AD7298_CH(x) BIT(13 - (x)) /* channel select */ +#define AD7298_TSENSE BIT(5) /* temperature conversion enable */ +#define AD7298_EXTREF BIT(2) /* external reference enable */ +#define AD7298_TAVG BIT(1) /* temperature sensor averaging enable */ +#define AD7298_PDD BIT(0) /* partial power down enable */ + +#define AD7298_MAX_CHAN 8 +#define AD7298_INTREF_mV 2500 + +#define AD7298_CH_TEMP 9 + +struct ad7298_state { + struct spi_device *spi; + struct regulator *reg; + unsigned ext_ref; + struct spi_transfer ring_xfer[10]; + struct spi_transfer scan_single_xfer[3]; + struct spi_message ring_msg; + struct spi_message scan_single_msg; + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + __be16 rx_buf[12] __aligned(IIO_DMA_MINALIGN); + __be16 tx_buf[2]; +}; + +#define AD7298_V_CHAN(index) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .address = index, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 12, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + } + +static const struct iio_chan_spec ad7298_channels[] = { + { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = AD7298_CH_TEMP, + .scan_index = -1, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + }, + }, + AD7298_V_CHAN(0), + AD7298_V_CHAN(1), + AD7298_V_CHAN(2), + AD7298_V_CHAN(3), + AD7298_V_CHAN(4), + AD7298_V_CHAN(5), + AD7298_V_CHAN(6), + AD7298_V_CHAN(7), + IIO_CHAN_SOFT_TIMESTAMP(8), +}; + +/* + * ad7298_update_scan_mode() setup the spi transfer buffer for the new scan mask + */ +static int ad7298_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *active_scan_mask) +{ + struct ad7298_state *st = iio_priv(indio_dev); + int i, m; + unsigned short command; + int scan_count; + + /* Now compute overall size */ + scan_count = bitmap_weight(active_scan_mask, + iio_get_masklength(indio_dev)); + + command = AD7298_WRITE | st->ext_ref; + + for (i = 0, m = AD7298_CH(0); i < AD7298_MAX_CHAN; i++, m >>= 1) + if (test_bit(i, active_scan_mask)) + command |= m; + + st->tx_buf[0] = cpu_to_be16(command); + + /* build spi ring message */ + st->ring_xfer[0].tx_buf = &st->tx_buf[0]; + st->ring_xfer[0].len = 2; + st->ring_xfer[0].cs_change = 1; + st->ring_xfer[1].tx_buf = &st->tx_buf[1]; + st->ring_xfer[1].len = 2; + st->ring_xfer[1].cs_change = 1; + + spi_message_init(&st->ring_msg); + spi_message_add_tail(&st->ring_xfer[0], &st->ring_msg); + spi_message_add_tail(&st->ring_xfer[1], &st->ring_msg); + + for (i = 0; i < scan_count; i++) { + st->ring_xfer[i + 2].rx_buf = &st->rx_buf[i]; + st->ring_xfer[i + 2].len = 2; + st->ring_xfer[i + 2].cs_change = 1; + spi_message_add_tail(&st->ring_xfer[i + 2], &st->ring_msg); + } + /* make sure last transfer cs_change is not set */ + st->ring_xfer[i + 1].cs_change = 0; + + return 0; +} + +static irqreturn_t ad7298_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad7298_state *st = iio_priv(indio_dev); + int b_sent; + + b_sent = spi_sync(st->spi, &st->ring_msg); + if (b_sent) + goto done; + + iio_push_to_buffers_with_ts(indio_dev, st->rx_buf, sizeof(st->rx_buf), + iio_get_time_ns(indio_dev)); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ad7298_scan_direct(struct ad7298_state *st, unsigned ch) +{ + int ret; + st->tx_buf[0] = cpu_to_be16(AD7298_WRITE | st->ext_ref | + (AD7298_CH(0) >> ch)); + + ret = spi_sync(st->spi, &st->scan_single_msg); + if (ret) + return ret; + + return be16_to_cpu(st->rx_buf[0]); +} + +static int ad7298_scan_temp(struct ad7298_state *st, int *val) +{ + int ret; + __be16 buf; + + buf = cpu_to_be16(AD7298_WRITE | AD7298_TSENSE | + AD7298_TAVG | st->ext_ref); + + ret = spi_write(st->spi, (u8 *)&buf, 2); + if (ret) + return ret; + + buf = cpu_to_be16(0); + + ret = spi_write(st->spi, (u8 *)&buf, 2); + if (ret) + return ret; + + usleep_range(101, 1000); /* sleep > 100us */ + + ret = spi_read(st->spi, (u8 *)&buf, 2); + if (ret) + return ret; + + *val = sign_extend32(be16_to_cpu(buf), 11); + + return 0; +} + +static int ad7298_get_ref_voltage(struct ad7298_state *st) +{ + int vref; + + if (st->reg) { + vref = regulator_get_voltage(st->reg); + if (vref < 0) + return vref; + + return vref / 1000; + } else { + return AD7298_INTREF_mV; + } +} + +static int ad7298_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret; + struct ad7298_state *st = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + if (chan->address == AD7298_CH_TEMP) + ret = ad7298_scan_temp(st, val); + else + ret = ad7298_scan_direct(st, chan->address); + + iio_device_release_direct(indio_dev); + + if (ret < 0) + return ret; + + if (chan->address != AD7298_CH_TEMP) + *val = ret & GENMASK(chan->scan_type.realbits - 1, 0); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + *val = ad7298_get_ref_voltage(st); + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_TEMP: + *val = ad7298_get_ref_voltage(st); + *val2 = 10; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + *val = 1093 - 2732500 / ad7298_get_ref_voltage(st); + return IIO_VAL_INT; + } + return -EINVAL; +} + +static const struct iio_info ad7298_info = { + .read_raw = &ad7298_read_raw, + .update_scan_mode = ad7298_update_scan_mode, +}; + +static void ad7298_reg_disable(void *data) +{ + struct regulator *reg = data; + + regulator_disable(reg); +} + +static int ad7298_probe(struct spi_device *spi) +{ + struct ad7298_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->reg = devm_regulator_get_optional(&spi->dev, "vref"); + if (!IS_ERR(st->reg)) { + st->ext_ref = AD7298_EXTREF; + } else { + ret = PTR_ERR(st->reg); + if (ret != -ENODEV) + return ret; + + st->reg = NULL; + } + + if (st->reg) { + ret = regulator_enable(st->reg); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&spi->dev, ad7298_reg_disable, + st->reg); + if (ret) + return ret; + } + + st->spi = spi; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad7298_channels; + indio_dev->num_channels = ARRAY_SIZE(ad7298_channels); + indio_dev->info = &ad7298_info; + + /* Setup default message */ + + st->scan_single_xfer[0].tx_buf = &st->tx_buf[0]; + st->scan_single_xfer[0].len = 2; + st->scan_single_xfer[0].cs_change = 1; + st->scan_single_xfer[1].tx_buf = &st->tx_buf[1]; + st->scan_single_xfer[1].len = 2; + st->scan_single_xfer[1].cs_change = 1; + st->scan_single_xfer[2].rx_buf = &st->rx_buf[0]; + st->scan_single_xfer[2].len = 2; + + spi_message_init(&st->scan_single_msg); + spi_message_add_tail(&st->scan_single_xfer[0], &st->scan_single_msg); + spi_message_add_tail(&st->scan_single_xfer[1], &st->scan_single_msg); + spi_message_add_tail(&st->scan_single_xfer[2], &st->scan_single_msg); + + ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, NULL, + &ad7298_trigger_handler, NULL); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct acpi_device_id ad7298_acpi_ids[] = { + { "INT3494", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, ad7298_acpi_ids); + +static const struct spi_device_id ad7298_id[] = { + { .name = "ad7298" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7298_id); + +static struct spi_driver ad7298_driver = { + .driver = { + .name = "ad7298", + .acpi_match_table = ad7298_acpi_ids, + }, + .probe = ad7298_probe, + .id_table = ad7298_id, +}; +module_spi_driver(ad7298_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7298 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ad7380.c b/drivers/iio/adc/ad7380.c new file mode 100644 index 000000000..2e90f125a --- /dev/null +++ b/drivers/iio/adc/ad7380.c @@ -0,0 +1,2154 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices AD738x Simultaneous Sampling SAR ADCs + * + * Copyright 2017 Analog Devices Inc. + * Copyright 2024 BayLibre, SAS + * + * Datasheets of supported parts: + * ad7380/1 : https://www.analog.com/media/en/technical-documentation/data-sheets/AD7380-7381.pdf + * ad7383/4 : https://www.analog.com/media/en/technical-documentation/data-sheets/ad7383-7384.pdf + * ad7386/7/8 : https://www.analog.com/media/en/technical-documentation/data-sheets/AD7386-7387-7388.pdf + * ad7380-4 : https://www.analog.com/media/en/technical-documentation/data-sheets/ad7380-4.pdf + * ad7381-4 : https://www.analog.com/media/en/technical-documentation/data-sheets/ad7381-4.pdf + * ad7383/4-4 : https://www.analog.com/media/en/technical-documentation/data-sheets/ad7383-4-ad7384-4.pdf + * ad7386/7/8-4 : https://www.analog.com/media/en/technical-documentation/data-sheets/ad7386-4-7387-4-7388-4.pdf + * ad7389-4 : https://www.analog.com/media/en/technical-documentation/data-sheets/ad7389-4.pdf + * adaq4370-4 : https://www.analog.com/media/en/technical-documentation/data-sheets/adaq4370-4.pdf + * adaq4380-4 : https://www.analog.com/media/en/technical-documentation/data-sheets/adaq4380-4.pdf + * adaq4381-4 : https://www.analog.com/media/en/technical-documentation/data-sheets/adaq4381-4.pdf + * + * HDL ad738x_fmc: https://analogdevicesinc.github.io/hdl/projects/ad738x_fmc/index.html + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define MAX_NUM_CHANNELS 8 +/* 2.5V internal reference voltage */ +#define AD7380_INTERNAL_REF_MV 2500 +/* 3.3V internal reference voltage for ADAQ */ +#define ADAQ4380_INTERNAL_REF_MV 3300 + +/* reading and writing registers is more reliable at lower than max speed */ +#define AD7380_REG_WR_SPEED_HZ 10000000 + +#define AD7380_REG_WR BIT(15) +#define AD7380_REG_REGADDR GENMASK(14, 12) +#define AD7380_REG_DATA GENMASK(11, 0) + +#define AD7380_REG_ADDR_NOP 0x0 +#define AD7380_REG_ADDR_CONFIG1 0x1 +#define AD7380_REG_ADDR_CONFIG2 0x2 +#define AD7380_REG_ADDR_ALERT 0x3 +#define AD7380_REG_ADDR_ALERT_LOW_TH 0x4 +#define AD7380_REG_ADDR_ALERT_HIGH_TH 0x5 + +#define AD7380_CONFIG1_CH BIT(11) +#define AD7380_CONFIG1_SEQ BIT(10) +#define AD7380_CONFIG1_OS_MODE BIT(9) +#define AD7380_CONFIG1_OSR GENMASK(8, 6) +#define AD7380_CONFIG1_CRC_W BIT(5) +#define AD7380_CONFIG1_CRC_R BIT(4) +#define AD7380_CONFIG1_ALERTEN BIT(3) +#define AD7380_CONFIG1_RES BIT(2) +#define AD7380_CONFIG1_REFSEL BIT(1) +#define AD7380_CONFIG1_PMODE BIT(0) + +#define AD7380_CONFIG2_SDO GENMASK(9, 8) +#define AD7380_CONFIG2_RESET GENMASK(7, 0) + +#define AD7380_CONFIG2_RESET_SOFT 0x3C +#define AD7380_CONFIG2_RESET_HARD 0xFF + +#define AD7380_ALERT_LOW_TH GENMASK(11, 0) +#define AD7380_ALERT_HIGH_TH GENMASK(11, 0) + +#define T_CONVERT_NS 190 /* conversion time */ +#define T_CONVERT_0_NS 10 /* 1st conversion start time (oversampling) */ +#define T_CONVERT_X_NS 500 /* xth conversion start time (oversampling) */ +#define T_POWERUP_US 5000 /* Power up */ + +#define AD7380_DEFAULT_GAIN_MILLI 1000 + +/* + * Using SPI offload, storagebits is always 32, so can't be used to compute struct + * spi_transfer.len. Using realbits instead. + */ +#define AD7380_SPI_BYTES(scan_type) ((scan_type)->realbits > 16 ? 4 : 2) + +struct ad7380_timing_specs { + const unsigned int t_csh_ns; /* CS minimum high time */ +}; + +struct ad7380_chip_info { + const char *name; + const struct iio_chan_spec *channels; + const struct iio_chan_spec *offload_channels; + unsigned int num_channels; + unsigned int num_simult_channels; + bool has_hardware_gain; + bool has_mux; + const char * const *supplies; + unsigned int num_supplies; + bool external_ref_only; + bool internal_ref_only; + unsigned int internal_ref_mv; + const char * const *vcm_supplies; + unsigned int num_vcm_supplies; + const unsigned long *available_scan_masks; + const struct ad7380_timing_specs *timing_specs; + u32 max_conversion_rate_hz; +}; + +static const struct iio_event_spec ad7380_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_dir = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_shared_by_dir = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_shared_by_all = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +enum { + AD7380_SCAN_TYPE_NORMAL, + AD7380_SCAN_TYPE_RESOLUTION_BOOST, +}; + +/* Extended scan types for 12-bit unsigned chips. */ +static const struct iio_scan_type ad7380_scan_type_12_u[] = { + [AD7380_SCAN_TYPE_NORMAL] = { + .sign = 'u', + .realbits = 12, + .storagebits = 16, + .endianness = IIO_CPU, + }, + [AD7380_SCAN_TYPE_RESOLUTION_BOOST] = { + .sign = 'u', + .realbits = 14, + .storagebits = 16, + .endianness = IIO_CPU, + }, +}; + +/* Extended scan types for 14-bit signed chips. */ +static const struct iio_scan_type ad7380_scan_type_14_s[] = { + [AD7380_SCAN_TYPE_NORMAL] = { + .sign = 's', + .realbits = 14, + .storagebits = 16, + .endianness = IIO_CPU, + }, + [AD7380_SCAN_TYPE_RESOLUTION_BOOST] = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, +}; + +/* Extended scan types for 14-bit unsigned chips. */ +static const struct iio_scan_type ad7380_scan_type_14_u[] = { + [AD7380_SCAN_TYPE_NORMAL] = { + .sign = 'u', + .realbits = 14, + .storagebits = 16, + .endianness = IIO_CPU, + }, + [AD7380_SCAN_TYPE_RESOLUTION_BOOST] = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, +}; + +/* Extended scan types for 16-bit signed_chips. */ +static const struct iio_scan_type ad7380_scan_type_16_s[] = { + [AD7380_SCAN_TYPE_NORMAL] = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + [AD7380_SCAN_TYPE_RESOLUTION_BOOST] = { + .sign = 's', + .realbits = 18, + .storagebits = 32, + .endianness = IIO_CPU, + }, +}; + +/* Extended scan types for 16-bit unsigned chips. */ +static const struct iio_scan_type ad7380_scan_type_16_u[] = { + [AD7380_SCAN_TYPE_NORMAL] = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + [AD7380_SCAN_TYPE_RESOLUTION_BOOST] = { + .sign = 'u', + .realbits = 18, + .storagebits = 32, + .endianness = IIO_CPU, + }, +}; + +/* + * Defining here scan types for offload mode, since with current available HDL + * only a value of 32 for storagebits is supported. + */ + +/* Extended scan types for 12-bit unsigned chips, offload support. */ +static const struct iio_scan_type ad7380_scan_type_12_u_offload[] = { + [AD7380_SCAN_TYPE_NORMAL] = { + .sign = 'u', + .realbits = 12, + .storagebits = 32, + .endianness = IIO_CPU, + }, + [AD7380_SCAN_TYPE_RESOLUTION_BOOST] = { + .sign = 'u', + .realbits = 14, + .storagebits = 32, + .endianness = IIO_CPU, + }, +}; + +/* Extended scan types for 14-bit signed chips, offload support. */ +static const struct iio_scan_type ad7380_scan_type_14_s_offload[] = { + [AD7380_SCAN_TYPE_NORMAL] = { + .sign = 's', + .realbits = 14, + .storagebits = 32, + .endianness = IIO_CPU, + }, + [AD7380_SCAN_TYPE_RESOLUTION_BOOST] = { + .sign = 's', + .realbits = 16, + .storagebits = 32, + .endianness = IIO_CPU, + }, +}; + +/* Extended scan types for 14-bit unsigned chips, offload support. */ +static const struct iio_scan_type ad7380_scan_type_14_u_offload[] = { + [AD7380_SCAN_TYPE_NORMAL] = { + .sign = 'u', + .realbits = 14, + .storagebits = 32, + .endianness = IIO_CPU, + }, + [AD7380_SCAN_TYPE_RESOLUTION_BOOST] = { + .sign = 'u', + .realbits = 16, + .storagebits = 32, + .endianness = IIO_CPU, + }, +}; + +/* Extended scan types for 16-bit signed_chips, offload support. */ +static const struct iio_scan_type ad7380_scan_type_16_s_offload[] = { + [AD7380_SCAN_TYPE_NORMAL] = { + .sign = 's', + .realbits = 16, + .storagebits = 32, + .endianness = IIO_CPU, + }, + [AD7380_SCAN_TYPE_RESOLUTION_BOOST] = { + .sign = 's', + .realbits = 18, + .storagebits = 32, + .endianness = IIO_CPU, + }, +}; + +/* Extended scan types for 16-bit unsigned chips, offload support. */ +static const struct iio_scan_type ad7380_scan_type_16_u_offload[] = { + [AD7380_SCAN_TYPE_NORMAL] = { + .sign = 'u', + .realbits = 16, + .storagebits = 32, + .endianness = IIO_CPU, + }, + [AD7380_SCAN_TYPE_RESOLUTION_BOOST] = { + .sign = 'u', + .realbits = 18, + .storagebits = 32, + .endianness = IIO_CPU, + }, +}; + +#define _AD7380_CHANNEL(index, bits, diff, sign, gain) { \ + .type = IIO_VOLTAGE, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + ((gain) ? BIT(IIO_CHAN_INFO_SCALE) : 0) | \ + ((diff) ? 0 : BIT(IIO_CHAN_INFO_OFFSET)), \ + .info_mask_shared_by_type = ((gain) ? 0 : BIT(IIO_CHAN_INFO_SCALE)) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .indexed = 1, \ + .differential = (diff), \ + .channel = (diff) ? (2 * (index)) : (index), \ + .channel2 = (diff) ? (2 * (index) + 1) : 0, \ + .scan_index = (index), \ + .has_ext_scan_type = 1, \ + .ext_scan_type = ad7380_scan_type_##bits##_##sign, \ + .num_ext_scan_type = ARRAY_SIZE(ad7380_scan_type_##bits##_##sign), \ + .event_spec = ad7380_events, \ + .num_event_specs = ARRAY_SIZE(ad7380_events), \ +} + +#define _AD7380_OFFLOAD_CHANNEL(index, bits, diff, sign, gain) { \ + .type = IIO_VOLTAGE, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + ((gain) ? BIT(IIO_CHAN_INFO_SCALE) : 0) | \ + ((diff) ? 0 : BIT(IIO_CHAN_INFO_OFFSET)), \ + .info_mask_shared_by_type = ((gain) ? 0 : BIT(IIO_CHAN_INFO_SCALE)) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .indexed = 1, \ + .differential = (diff), \ + .channel = (diff) ? (2 * (index)) : (index), \ + .channel2 = (diff) ? (2 * (index) + 1) : 0, \ + .scan_index = (index), \ + .has_ext_scan_type = 1, \ + .ext_scan_type = ad7380_scan_type_##bits##_##sign##_offload, \ + .num_ext_scan_type = \ + ARRAY_SIZE(ad7380_scan_type_##bits##_##sign##_offload), \ + .event_spec = ad7380_events, \ + .num_event_specs = ARRAY_SIZE(ad7380_events), \ +} + +/* + * Notes on the offload channels: + * - There is no soft timestamp since everything is done in hardware. + * - There is a sampling frequency attribute added. This controls the SPI + * offload trigger. + * - The storagebits value depends on the SPI offload provider. Currently there + * is only one supported provider, namely the ADI PULSAR ADC HDL project, + * which always uses 32-bit words for data values, even for <= 16-bit ADCs. + * So the value is just hardcoded to 32 for now. + */ + +#define AD7380_CHANNEL(index, bits, diff, sign) \ + _AD7380_CHANNEL(index, bits, diff, sign, false) + +#define ADAQ4380_CHANNEL(index, bits, diff, sign) \ + _AD7380_CHANNEL(index, bits, diff, sign, true) + +#define DEFINE_AD7380_2_CHANNEL(name, bits, diff, sign) \ +static const struct iio_chan_spec name[] = { \ + AD7380_CHANNEL(0, bits, diff, sign), \ + AD7380_CHANNEL(1, bits, diff, sign), \ + IIO_CHAN_SOFT_TIMESTAMP(2), \ +} + +#define DEFINE_AD7380_4_CHANNEL(name, bits, diff, sign) \ +static const struct iio_chan_spec name[] = { \ + AD7380_CHANNEL(0, bits, diff, sign), \ + AD7380_CHANNEL(1, bits, diff, sign), \ + AD7380_CHANNEL(2, bits, diff, sign), \ + AD7380_CHANNEL(3, bits, diff, sign), \ + IIO_CHAN_SOFT_TIMESTAMP(4), \ +} + +#define DEFINE_ADAQ4380_4_CHANNEL(name, bits, diff, sign) \ +static const struct iio_chan_spec name[] = { \ + ADAQ4380_CHANNEL(0, bits, diff, sign), \ + ADAQ4380_CHANNEL(1, bits, diff, sign), \ + ADAQ4380_CHANNEL(2, bits, diff, sign), \ + ADAQ4380_CHANNEL(3, bits, diff, sign), \ + IIO_CHAN_SOFT_TIMESTAMP(4), \ +} + +#define DEFINE_AD7380_8_CHANNEL(name, bits, diff, sign) \ +static const struct iio_chan_spec name[] = { \ + AD7380_CHANNEL(0, bits, diff, sign), \ + AD7380_CHANNEL(1, bits, diff, sign), \ + AD7380_CHANNEL(2, bits, diff, sign), \ + AD7380_CHANNEL(3, bits, diff, sign), \ + AD7380_CHANNEL(4, bits, diff, sign), \ + AD7380_CHANNEL(5, bits, diff, sign), \ + AD7380_CHANNEL(6, bits, diff, sign), \ + AD7380_CHANNEL(7, bits, diff, sign), \ + IIO_CHAN_SOFT_TIMESTAMP(8), \ +} + +#define AD7380_OFFLOAD_CHANNEL(index, bits, diff, sign) \ +_AD7380_OFFLOAD_CHANNEL(index, bits, diff, sign, false) + +#define ADAQ4380_OFFLOAD_CHANNEL(index, bits, diff, sign) \ +_AD7380_OFFLOAD_CHANNEL(index, bits, diff, sign, true) + +#define DEFINE_AD7380_2_OFFLOAD_CHANNEL(name, bits, diff, sign) \ +static const struct iio_chan_spec name[] = { \ + AD7380_OFFLOAD_CHANNEL(0, bits, diff, sign), \ + AD7380_OFFLOAD_CHANNEL(1, bits, diff, sign), \ +} + +#define DEFINE_AD7380_4_OFFLOAD_CHANNEL(name, bits, diff, sign) \ +static const struct iio_chan_spec name[] = { \ + AD7380_OFFLOAD_CHANNEL(0, bits, diff, sign), \ + AD7380_OFFLOAD_CHANNEL(1, bits, diff, sign), \ + AD7380_OFFLOAD_CHANNEL(2, bits, diff, sign), \ + AD7380_OFFLOAD_CHANNEL(3, bits, diff, sign), \ +} + +#define DEFINE_ADAQ4380_4_OFFLOAD_CHANNEL(name, bits, diff, sign) \ +static const struct iio_chan_spec name[] = { \ + AD7380_OFFLOAD_CHANNEL(0, bits, diff, sign), \ + AD7380_OFFLOAD_CHANNEL(1, bits, diff, sign), \ + AD7380_OFFLOAD_CHANNEL(2, bits, diff, sign), \ + AD7380_OFFLOAD_CHANNEL(3, bits, diff, sign), \ +} + +#define DEFINE_AD7380_8_OFFLOAD_CHANNEL(name, bits, diff, sign) \ +static const struct iio_chan_spec name[] = { \ + AD7380_OFFLOAD_CHANNEL(0, bits, diff, sign), \ + AD7380_OFFLOAD_CHANNEL(1, bits, diff, sign), \ + AD7380_OFFLOAD_CHANNEL(2, bits, diff, sign), \ + AD7380_OFFLOAD_CHANNEL(3, bits, diff, sign), \ + AD7380_OFFLOAD_CHANNEL(4, bits, diff, sign), \ + AD7380_OFFLOAD_CHANNEL(5, bits, diff, sign), \ + AD7380_OFFLOAD_CHANNEL(6, bits, diff, sign), \ + AD7380_OFFLOAD_CHANNEL(7, bits, diff, sign), \ +} + +/* fully differential */ +DEFINE_AD7380_2_CHANNEL(ad7380_channels, 16, 1, s); +DEFINE_AD7380_2_CHANNEL(ad7381_channels, 14, 1, s); +DEFINE_AD7380_4_CHANNEL(ad7380_4_channels, 16, 1, s); +DEFINE_AD7380_4_CHANNEL(ad7381_4_channels, 14, 1, s); +DEFINE_ADAQ4380_4_CHANNEL(adaq4380_4_channels, 16, 1, s); +DEFINE_ADAQ4380_4_CHANNEL(adaq4381_4_channels, 14, 1, s); +/* pseudo differential */ +DEFINE_AD7380_2_CHANNEL(ad7383_channels, 16, 0, s); +DEFINE_AD7380_2_CHANNEL(ad7384_channels, 14, 0, s); +DEFINE_AD7380_4_CHANNEL(ad7383_4_channels, 16, 0, s); +DEFINE_AD7380_4_CHANNEL(ad7384_4_channels, 14, 0, s); + +/* Single ended */ +DEFINE_AD7380_4_CHANNEL(ad7386_channels, 16, 0, u); +DEFINE_AD7380_4_CHANNEL(ad7387_channels, 14, 0, u); +DEFINE_AD7380_4_CHANNEL(ad7388_channels, 12, 0, u); +DEFINE_AD7380_8_CHANNEL(ad7386_4_channels, 16, 0, u); +DEFINE_AD7380_8_CHANNEL(ad7387_4_channels, 14, 0, u); +DEFINE_AD7380_8_CHANNEL(ad7388_4_channels, 12, 0, u); + +/* offload channels */ +DEFINE_AD7380_2_OFFLOAD_CHANNEL(ad7380_offload_channels, 16, 1, s); +DEFINE_AD7380_2_OFFLOAD_CHANNEL(ad7381_offload_channels, 14, 1, s); +DEFINE_AD7380_4_OFFLOAD_CHANNEL(ad7380_4_offload_channels, 16, 1, s); +DEFINE_AD7380_4_OFFLOAD_CHANNEL(ad7381_4_offload_channels, 14, 1, s); +DEFINE_ADAQ4380_4_OFFLOAD_CHANNEL(adaq4380_4_offload_channels, 16, 1, s); +DEFINE_ADAQ4380_4_OFFLOAD_CHANNEL(adaq4381_4_offload_channels, 14, 1, s); + +/* pseudo differential */ +DEFINE_AD7380_2_OFFLOAD_CHANNEL(ad7383_offload_channels, 16, 0, s); +DEFINE_AD7380_2_OFFLOAD_CHANNEL(ad7384_offload_channels, 14, 0, s); +DEFINE_AD7380_4_OFFLOAD_CHANNEL(ad7383_4_offload_channels, 16, 0, s); +DEFINE_AD7380_4_OFFLOAD_CHANNEL(ad7384_4_offload_channels, 14, 0, s); + +/* Single ended */ +DEFINE_AD7380_4_OFFLOAD_CHANNEL(ad7386_offload_channels, 16, 0, u); +DEFINE_AD7380_4_OFFLOAD_CHANNEL(ad7387_offload_channels, 14, 0, u); +DEFINE_AD7380_4_OFFLOAD_CHANNEL(ad7388_offload_channels, 12, 0, u); +DEFINE_AD7380_8_OFFLOAD_CHANNEL(ad7386_4_offload_channels, 16, 0, u); +DEFINE_AD7380_8_OFFLOAD_CHANNEL(ad7387_4_offload_channels, 14, 0, u); +DEFINE_AD7380_8_OFFLOAD_CHANNEL(ad7388_4_offload_channels, 12, 0, u); + +static const char * const ad7380_supplies[] = { + "vcc", "vlogic", +}; + +static const char * const adaq4380_supplies[] = { + "ldo", "vcc", "vlogic", "vs-p", "vs-n", "refin", +}; + +static const char * const ad7380_2_channel_vcm_supplies[] = { + "aina", "ainb", +}; + +static const char * const ad7380_4_channel_vcm_supplies[] = { + "aina", "ainb", "ainc", "aind", +}; + +/* Since this is simultaneous sampling, we don't allow individual channels. */ +static const unsigned long ad7380_2_channel_scan_masks[] = { + GENMASK(1, 0), + 0 +}; + +static const unsigned long ad7380_4_channel_scan_masks[] = { + GENMASK(3, 0), + 0 +}; + +/* + * Single ended parts have a 2:1 multiplexer in front of each ADC. + * + * From an IIO point of view, all inputs are exported, i.e ad7386/7/8 + * export 4 channels and ad7386-4/7-4/8-4 export 8 channels. + * + * Inputs AinX0 of multiplexers correspond to the first half of IIO channels + * (i.e 0-1 or 0-3) and inputs AinX1 correspond to second half (i.e 2-3 or + * 4-7). Example for AD7386/7/8 (2 channels parts): + * + * IIO | AD7386/7/8 + * | +---------------------------- + * | | _____ ______ + * | | | | | | + * voltage0 | AinA0 --|--->| | | | + * | | | mux |----->| ADCA |--- + * voltage2 | AinA1 --|--->| | | | + * | | |_____| |_____ | + * | | _____ ______ + * | | | | | | + * voltage1 | AinB0 --|--->| | | | + * | | | mux |----->| ADCB |--- + * voltage3 | AinB1 --|--->| | | | + * | | |_____| |______| + * | | + * | +---------------------------- + * + * Since this is simultaneous sampling for AinX0 OR AinX1 we have two separate + * scan masks. + * When sequencer mode is enabled, chip automatically cycles through + * AinX0 and AinX1 channels. From an IIO point of view, we ca enable all + * channels, at the cost of an extra read, thus dividing the maximum rate by + * two. + */ +enum { + AD7380_SCAN_MASK_CH_0, + AD7380_SCAN_MASK_CH_1, + AD7380_SCAN_MASK_SEQ, +}; + +static const unsigned long ad7380_2x2_channel_scan_masks[] = { + [AD7380_SCAN_MASK_CH_0] = GENMASK(1, 0), + [AD7380_SCAN_MASK_CH_1] = GENMASK(3, 2), + [AD7380_SCAN_MASK_SEQ] = GENMASK(3, 0), + 0 +}; + +static const unsigned long ad7380_2x4_channel_scan_masks[] = { + [AD7380_SCAN_MASK_CH_0] = GENMASK(3, 0), + [AD7380_SCAN_MASK_CH_1] = GENMASK(7, 4), + [AD7380_SCAN_MASK_SEQ] = GENMASK(7, 0), + 0 +}; + +static const struct ad7380_timing_specs ad7380_timing = { + .t_csh_ns = 10, +}; + +static const struct ad7380_timing_specs ad7380_4_timing = { + .t_csh_ns = 20, +}; + +/* + * Available oversampling ratios. The indices correspond with the bit value + * expected by the chip. The available ratios depend on the averaging mode, + * only normal averaging is supported for now. + */ +static const int ad7380_oversampling_ratios[] = { + 1, 2, 4, 8, 16, 32, +}; + +/* Gains stored as fractions of 1000 so they can be expressed by integers. */ +static const int ad7380_gains[] = { + 300, 600, 1000, 1600, +}; + +static const struct ad7380_chip_info ad7380_chip_info = { + .name = "ad7380", + .channels = ad7380_channels, + .offload_channels = ad7380_offload_channels, + .num_channels = ARRAY_SIZE(ad7380_channels), + .num_simult_channels = 2, + .supplies = ad7380_supplies, + .num_supplies = ARRAY_SIZE(ad7380_supplies), + .internal_ref_mv = AD7380_INTERNAL_REF_MV, + .available_scan_masks = ad7380_2_channel_scan_masks, + .timing_specs = &ad7380_timing, + .max_conversion_rate_hz = 4 * MEGA, +}; + +static const struct ad7380_chip_info ad7381_chip_info = { + .name = "ad7381", + .channels = ad7381_channels, + .offload_channels = ad7381_offload_channels, + .num_channels = ARRAY_SIZE(ad7381_channels), + .num_simult_channels = 2, + .supplies = ad7380_supplies, + .num_supplies = ARRAY_SIZE(ad7380_supplies), + .internal_ref_mv = AD7380_INTERNAL_REF_MV, + .available_scan_masks = ad7380_2_channel_scan_masks, + .timing_specs = &ad7380_timing, + .max_conversion_rate_hz = 4 * MEGA, +}; + +static const struct ad7380_chip_info ad7383_chip_info = { + .name = "ad7383", + .channels = ad7383_channels, + .offload_channels = ad7383_offload_channels, + .num_channels = ARRAY_SIZE(ad7383_channels), + .num_simult_channels = 2, + .supplies = ad7380_supplies, + .num_supplies = ARRAY_SIZE(ad7380_supplies), + .vcm_supplies = ad7380_2_channel_vcm_supplies, + .num_vcm_supplies = ARRAY_SIZE(ad7380_2_channel_vcm_supplies), + .internal_ref_mv = AD7380_INTERNAL_REF_MV, + .available_scan_masks = ad7380_2_channel_scan_masks, + .timing_specs = &ad7380_timing, + .max_conversion_rate_hz = 4 * MEGA, +}; + +static const struct ad7380_chip_info ad7384_chip_info = { + .name = "ad7384", + .channels = ad7384_channels, + .offload_channels = ad7384_offload_channels, + .num_channels = ARRAY_SIZE(ad7384_channels), + .num_simult_channels = 2, + .supplies = ad7380_supplies, + .num_supplies = ARRAY_SIZE(ad7380_supplies), + .vcm_supplies = ad7380_2_channel_vcm_supplies, + .num_vcm_supplies = ARRAY_SIZE(ad7380_2_channel_vcm_supplies), + .internal_ref_mv = AD7380_INTERNAL_REF_MV, + .available_scan_masks = ad7380_2_channel_scan_masks, + .timing_specs = &ad7380_timing, + .max_conversion_rate_hz = 4 * MEGA, +}; + +static const struct ad7380_chip_info ad7386_chip_info = { + .name = "ad7386", + .channels = ad7386_channels, + .offload_channels = ad7386_offload_channels, + .num_channels = ARRAY_SIZE(ad7386_channels), + .num_simult_channels = 2, + .supplies = ad7380_supplies, + .num_supplies = ARRAY_SIZE(ad7380_supplies), + .internal_ref_mv = AD7380_INTERNAL_REF_MV, + .has_mux = true, + .available_scan_masks = ad7380_2x2_channel_scan_masks, + .timing_specs = &ad7380_timing, + .max_conversion_rate_hz = 4 * MEGA, +}; + +static const struct ad7380_chip_info ad7387_chip_info = { + .name = "ad7387", + .channels = ad7387_channels, + .offload_channels = ad7387_offload_channels, + .num_channels = ARRAY_SIZE(ad7387_channels), + .num_simult_channels = 2, + .supplies = ad7380_supplies, + .num_supplies = ARRAY_SIZE(ad7380_supplies), + .internal_ref_mv = AD7380_INTERNAL_REF_MV, + .has_mux = true, + .available_scan_masks = ad7380_2x2_channel_scan_masks, + .timing_specs = &ad7380_timing, + .max_conversion_rate_hz = 4 * MEGA, +}; + +static const struct ad7380_chip_info ad7388_chip_info = { + .name = "ad7388", + .channels = ad7388_channels, + .offload_channels = ad7388_offload_channels, + .num_channels = ARRAY_SIZE(ad7388_channels), + .num_simult_channels = 2, + .supplies = ad7380_supplies, + .num_supplies = ARRAY_SIZE(ad7380_supplies), + .internal_ref_mv = AD7380_INTERNAL_REF_MV, + .has_mux = true, + .available_scan_masks = ad7380_2x2_channel_scan_masks, + .timing_specs = &ad7380_timing, + .max_conversion_rate_hz = 4 * MEGA, +}; + +static const struct ad7380_chip_info ad7380_4_chip_info = { + .name = "ad7380-4", + .channels = ad7380_4_channels, + .offload_channels = ad7380_4_offload_channels, + .num_channels = ARRAY_SIZE(ad7380_4_channels), + .num_simult_channels = 4, + .supplies = ad7380_supplies, + .num_supplies = ARRAY_SIZE(ad7380_supplies), + .external_ref_only = true, + .available_scan_masks = ad7380_4_channel_scan_masks, + .timing_specs = &ad7380_4_timing, + .max_conversion_rate_hz = 4 * MEGA, +}; + +static const struct ad7380_chip_info ad7381_4_chip_info = { + .name = "ad7381-4", + .channels = ad7381_4_channels, + .offload_channels = ad7381_4_offload_channels, + .num_channels = ARRAY_SIZE(ad7381_4_channels), + .num_simult_channels = 4, + .supplies = ad7380_supplies, + .num_supplies = ARRAY_SIZE(ad7380_supplies), + .internal_ref_mv = AD7380_INTERNAL_REF_MV, + .available_scan_masks = ad7380_4_channel_scan_masks, + .timing_specs = &ad7380_4_timing, + .max_conversion_rate_hz = 4 * MEGA, +}; + +static const struct ad7380_chip_info ad7383_4_chip_info = { + .name = "ad7383-4", + .channels = ad7383_4_channels, + .offload_channels = ad7383_4_offload_channels, + .num_channels = ARRAY_SIZE(ad7383_4_channels), + .num_simult_channels = 4, + .supplies = ad7380_supplies, + .num_supplies = ARRAY_SIZE(ad7380_supplies), + .internal_ref_mv = AD7380_INTERNAL_REF_MV, + .vcm_supplies = ad7380_4_channel_vcm_supplies, + .num_vcm_supplies = ARRAY_SIZE(ad7380_4_channel_vcm_supplies), + .available_scan_masks = ad7380_4_channel_scan_masks, + .timing_specs = &ad7380_4_timing, + .max_conversion_rate_hz = 4 * MEGA, +}; + +static const struct ad7380_chip_info ad7384_4_chip_info = { + .name = "ad7384-4", + .channels = ad7384_4_channels, + .offload_channels = ad7384_4_offload_channels, + .num_channels = ARRAY_SIZE(ad7384_4_channels), + .num_simult_channels = 4, + .supplies = ad7380_supplies, + .num_supplies = ARRAY_SIZE(ad7380_supplies), + .internal_ref_mv = AD7380_INTERNAL_REF_MV, + .vcm_supplies = ad7380_4_channel_vcm_supplies, + .num_vcm_supplies = ARRAY_SIZE(ad7380_4_channel_vcm_supplies), + .available_scan_masks = ad7380_4_channel_scan_masks, + .timing_specs = &ad7380_4_timing, + .max_conversion_rate_hz = 4 * MEGA, +}; + +static const struct ad7380_chip_info ad7386_4_chip_info = { + .name = "ad7386-4", + .channels = ad7386_4_channels, + .offload_channels = ad7386_4_offload_channels, + .num_channels = ARRAY_SIZE(ad7386_4_channels), + .num_simult_channels = 4, + .supplies = ad7380_supplies, + .num_supplies = ARRAY_SIZE(ad7380_supplies), + .internal_ref_mv = AD7380_INTERNAL_REF_MV, + .has_mux = true, + .available_scan_masks = ad7380_2x4_channel_scan_masks, + .timing_specs = &ad7380_4_timing, + .max_conversion_rate_hz = 4 * MEGA, +}; + +static const struct ad7380_chip_info ad7387_4_chip_info = { + .name = "ad7387-4", + .channels = ad7387_4_channels, + .offload_channels = ad7387_4_offload_channels, + .num_channels = ARRAY_SIZE(ad7387_4_channels), + .num_simult_channels = 4, + .supplies = ad7380_supplies, + .num_supplies = ARRAY_SIZE(ad7380_supplies), + .internal_ref_mv = AD7380_INTERNAL_REF_MV, + .has_mux = true, + .available_scan_masks = ad7380_2x4_channel_scan_masks, + .timing_specs = &ad7380_4_timing, + .max_conversion_rate_hz = 4 * MEGA, +}; + +static const struct ad7380_chip_info ad7388_4_chip_info = { + .name = "ad7388-4", + .channels = ad7388_4_channels, + .offload_channels = ad7388_4_offload_channels, + .num_channels = ARRAY_SIZE(ad7388_4_channels), + .num_simult_channels = 4, + .supplies = ad7380_supplies, + .num_supplies = ARRAY_SIZE(ad7380_supplies), + .internal_ref_mv = AD7380_INTERNAL_REF_MV, + .has_mux = true, + .available_scan_masks = ad7380_2x4_channel_scan_masks, + .timing_specs = &ad7380_4_timing, + .max_conversion_rate_hz = 4 * MEGA, +}; + +static const struct ad7380_chip_info ad7389_4_chip_info = { + .name = "ad7389-4", + .channels = ad7380_4_channels, + .offload_channels = ad7380_4_offload_channels, + .num_channels = ARRAY_SIZE(ad7380_4_channels), + .num_simult_channels = 4, + .supplies = ad7380_supplies, + .num_supplies = ARRAY_SIZE(ad7380_supplies), + .internal_ref_only = true, + .internal_ref_mv = AD7380_INTERNAL_REF_MV, + .available_scan_masks = ad7380_4_channel_scan_masks, + .timing_specs = &ad7380_4_timing, + .max_conversion_rate_hz = 4 * MEGA, +}; + +static const struct ad7380_chip_info adaq4370_4_chip_info = { + .name = "adaq4370-4", + .channels = adaq4380_4_channels, + .offload_channels = adaq4380_4_offload_channels, + .num_channels = ARRAY_SIZE(adaq4380_4_channels), + .num_simult_channels = 4, + .supplies = adaq4380_supplies, + .num_supplies = ARRAY_SIZE(adaq4380_supplies), + .internal_ref_only = true, + .internal_ref_mv = ADAQ4380_INTERNAL_REF_MV, + .has_hardware_gain = true, + .available_scan_masks = ad7380_4_channel_scan_masks, + .timing_specs = &ad7380_4_timing, + .max_conversion_rate_hz = 2 * MEGA, +}; + +static const struct ad7380_chip_info adaq4380_4_chip_info = { + .name = "adaq4380-4", + .channels = adaq4380_4_channels, + .offload_channels = adaq4380_4_offload_channels, + .num_channels = ARRAY_SIZE(adaq4380_4_channels), + .num_simult_channels = 4, + .supplies = adaq4380_supplies, + .num_supplies = ARRAY_SIZE(adaq4380_supplies), + .internal_ref_only = true, + .internal_ref_mv = ADAQ4380_INTERNAL_REF_MV, + .has_hardware_gain = true, + .available_scan_masks = ad7380_4_channel_scan_masks, + .timing_specs = &ad7380_4_timing, + .max_conversion_rate_hz = 4 * MEGA, +}; + +static const struct ad7380_chip_info adaq4381_4_chip_info = { + .name = "adaq4381-4", + .channels = adaq4381_4_channels, + .offload_channels = adaq4381_4_offload_channels, + .num_channels = ARRAY_SIZE(adaq4381_4_channels), + .num_simult_channels = 4, + .supplies = adaq4380_supplies, + .num_supplies = ARRAY_SIZE(adaq4380_supplies), + .internal_ref_only = true, + .internal_ref_mv = ADAQ4380_INTERNAL_REF_MV, + .has_hardware_gain = true, + .available_scan_masks = ad7380_4_channel_scan_masks, + .timing_specs = &ad7380_4_timing, + .max_conversion_rate_hz = 4 * MEGA, +}; + +static const struct spi_offload_config ad7380_offload_config = { + .capability_flags = SPI_OFFLOAD_CAP_TRIGGER | + SPI_OFFLOAD_CAP_RX_STREAM_DMA, +}; + +struct ad7380_state { + const struct ad7380_chip_info *chip_info; + struct spi_device *spi; + struct regmap *regmap; + bool resolution_boost_enabled; + unsigned int ch; + bool seq; + /* How many SDO lines are wired up. */ + u8 num_sdo_lines; + unsigned int vref_mv; + unsigned int vcm_mv[MAX_NUM_CHANNELS]; + unsigned int gain_milli[MAX_NUM_CHANNELS]; + /* xfers, message an buffer for reading sample data */ + struct spi_transfer normal_xfer[2]; + struct spi_message normal_msg; + struct spi_transfer seq_xfer[4]; + struct spi_message seq_msg; + struct spi_transfer offload_xfer; + struct spi_message offload_msg; + struct spi_offload *offload; + struct spi_offload_trigger *offload_trigger; + unsigned long offload_trigger_hz; + + int sample_freq_range[3]; + /* + * DMA (thus cache coherency maintenance) requires the transfer buffers + * to live in their own cache lines. + * + * Make the buffer large enough for MAX_NUM_CHANNELS 32-bit samples and + * one 64-bit aligned 64-bit timestamp. + */ + IIO_DECLARE_DMA_BUFFER_WITH_TS(u8, scan_data, MAX_NUM_CHANNELS * sizeof(u32)); + /* buffers for reading/writing registers */ + u16 tx; + u16 rx; +}; + +static int ad7380_regmap_reg_write(void *context, unsigned int reg, + unsigned int val) +{ + struct ad7380_state *st = context; + struct spi_transfer xfer = { + .speed_hz = AD7380_REG_WR_SPEED_HZ, + .bits_per_word = 16, + .len = 2, + .tx_buf = &st->tx, + }; + + st->tx = FIELD_PREP(AD7380_REG_WR, 1) | + FIELD_PREP(AD7380_REG_REGADDR, reg) | + FIELD_PREP(AD7380_REG_DATA, val); + + return spi_sync_transfer(st->spi, &xfer, 1); +} + +static int ad7380_regmap_reg_read(void *context, unsigned int reg, + unsigned int *val) +{ + struct ad7380_state *st = context; + struct spi_transfer xfers[] = { + { + .speed_hz = AD7380_REG_WR_SPEED_HZ, + .bits_per_word = 16, + .len = 2, + .tx_buf = &st->tx, + .cs_change = 1, + .cs_change_delay = { + .value = st->chip_info->timing_specs->t_csh_ns, + .unit = SPI_DELAY_UNIT_NSECS, + }, + }, { + .speed_hz = AD7380_REG_WR_SPEED_HZ, + .bits_per_word = 16, + .len = 2, + .rx_buf = &st->rx, + }, + }; + int ret; + + st->tx = FIELD_PREP(AD7380_REG_WR, 0) | + FIELD_PREP(AD7380_REG_REGADDR, reg) | + FIELD_PREP(AD7380_REG_DATA, 0); + + ret = spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers)); + if (ret < 0) + return ret; + + *val = FIELD_GET(AD7380_REG_DATA, st->rx); + + return 0; +} + +static const struct reg_default ad7380_reg_defaults[] = { + { AD7380_REG_ADDR_ALERT_LOW_TH, 0x800 }, + { AD7380_REG_ADDR_ALERT_HIGH_TH, 0x7FF }, +}; + +static const struct regmap_range ad7380_volatile_reg_ranges[] = { + regmap_reg_range(AD7380_REG_ADDR_CONFIG2, AD7380_REG_ADDR_ALERT), +}; + +static const struct regmap_access_table ad7380_volatile_regs = { + .yes_ranges = ad7380_volatile_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(ad7380_volatile_reg_ranges), +}; + +static const struct regmap_config ad7380_regmap_config = { + .reg_bits = 3, + .val_bits = 12, + .reg_read = ad7380_regmap_reg_read, + .reg_write = ad7380_regmap_reg_write, + .max_register = AD7380_REG_ADDR_ALERT_HIGH_TH, + .can_sleep = true, + .reg_defaults = ad7380_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(ad7380_reg_defaults), + .volatile_table = &ad7380_volatile_regs, + .cache_type = REGCACHE_MAPLE, +}; + +static int ad7380_debugfs_reg_access(struct iio_dev *indio_dev, u32 reg, + u32 writeval, u32 *readval) +{ + struct ad7380_state *st = iio_priv(indio_dev); + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + if (readval) + ret = regmap_read(st->regmap, reg, readval); + else + ret = regmap_write(st->regmap, reg, writeval); + + iio_device_release_direct(indio_dev); + + return ret; +} + +/** + * ad7380_regval_to_osr - convert OSR register value to ratio + * @regval: register value to check + * + * Returns: the ratio corresponding to the OSR register. If regval is not in + * bound, return 1 (oversampling disabled) + * + */ +static int ad7380_regval_to_osr(unsigned int regval) +{ + if (regval >= ARRAY_SIZE(ad7380_oversampling_ratios)) + return 1; + + return ad7380_oversampling_ratios[regval]; +} + +static int ad7380_get_osr(struct ad7380_state *st, int *val) +{ + u32 tmp; + int ret; + + ret = regmap_read(st->regmap, AD7380_REG_ADDR_CONFIG1, &tmp); + if (ret) + return ret; + + *val = ad7380_regval_to_osr(FIELD_GET(AD7380_CONFIG1_OSR, tmp)); + + return 0; +} + +/* + * When switching channel, the ADC require an additional settling time. + * According to the datasheet, data is value on the third CS low. We already + * have an extra toggle before each read (either direct reads or buffered reads) + * to sample correct data, so we just add a single CS toggle at the end of the + * register write. + */ +static int ad7380_set_ch(struct ad7380_state *st, unsigned int ch) +{ + struct spi_transfer xfer = { + .delay = { + .value = T_CONVERT_NS, + .unit = SPI_DELAY_UNIT_NSECS, + } + }; + int oversampling_ratio, ret; + + if (st->ch == ch) + return 0; + + ret = ad7380_get_osr(st, &oversampling_ratio); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, + AD7380_REG_ADDR_CONFIG1, + AD7380_CONFIG1_CH, + FIELD_PREP(AD7380_CONFIG1_CH, ch)); + + if (ret) + return ret; + + st->ch = ch; + + if (oversampling_ratio > 1) + xfer.delay.value = T_CONVERT_0_NS + + T_CONVERT_X_NS * (oversampling_ratio - 1) * + st->chip_info->num_simult_channels / st->num_sdo_lines; + + return spi_sync_transfer(st->spi, &xfer, 1); +} + +/** + * ad7380_update_xfers - update the SPI transfers base on the current scan type + * @st: device instance specific state + * @scan_type: current scan type + */ +static int ad7380_update_xfers(struct ad7380_state *st, + const struct iio_scan_type *scan_type) +{ + struct spi_transfer *xfer = st->seq ? st->seq_xfer : st->normal_xfer; + unsigned int t_convert = T_CONVERT_NS; + int oversampling_ratio, ret; + + /* + * In the case of oversampling, conversion time is higher than in normal + * mode. Technically T_CONVERT_X_NS is lower for some chips, but we use + * the maximum value for simplicity for now. + */ + ret = ad7380_get_osr(st, &oversampling_ratio); + if (ret) + return ret; + + if (oversampling_ratio > 1) + t_convert = T_CONVERT_0_NS + T_CONVERT_X_NS * + (oversampling_ratio - 1) * + st->chip_info->num_simult_channels / st->num_sdo_lines; + + if (st->seq) { + xfer[0].delay.value = xfer[1].delay.value = t_convert; + xfer[0].delay.unit = xfer[1].delay.unit = SPI_DELAY_UNIT_NSECS; + xfer[2].bits_per_word = xfer[3].bits_per_word = + scan_type->realbits; + xfer[2].len = xfer[3].len = + AD7380_SPI_BYTES(scan_type) * + st->chip_info->num_simult_channels; + xfer[3].rx_buf = xfer[2].rx_buf + xfer[2].len; + /* Additional delay required here when oversampling is enabled */ + if (oversampling_ratio > 1) + xfer[2].delay.value = t_convert; + else + xfer[2].delay.value = 0; + xfer[2].delay.unit = SPI_DELAY_UNIT_NSECS; + } else { + xfer[0].delay.value = t_convert; + xfer[0].delay.unit = SPI_DELAY_UNIT_NSECS; + xfer[1].bits_per_word = scan_type->realbits; + xfer[1].len = AD7380_SPI_BYTES(scan_type) * + st->chip_info->num_simult_channels; + } + + return 0; +} + +static int ad7380_set_sample_freq(struct ad7380_state *st, int val) +{ + struct spi_offload_trigger_config config = { + .type = SPI_OFFLOAD_TRIGGER_PERIODIC, + .periodic = { + .frequency_hz = val, + }, + }; + int ret; + + ret = spi_offload_trigger_validate(st->offload_trigger, &config); + if (ret) + return ret; + + st->offload_trigger_hz = config.periodic.frequency_hz; + + return 0; +} + +static int ad7380_init_offload_msg(struct ad7380_state *st, + struct iio_dev *indio_dev) +{ + struct spi_transfer *xfer = &st->offload_xfer; + struct device *dev = &st->spi->dev; + const struct iio_scan_type *scan_type; + int ret; + + scan_type = iio_get_current_scan_type(indio_dev, + &indio_dev->channels[0]); + if (IS_ERR(scan_type)) + return PTR_ERR(scan_type); + + if (st->chip_info->has_mux) { + int index; + + ret = iio_active_scan_mask_index(indio_dev); + if (ret < 0) + return ret; + + index = ret; + if (index == AD7380_SCAN_MASK_SEQ) { + ret = regmap_set_bits(st->regmap, AD7380_REG_ADDR_CONFIG1, + AD7380_CONFIG1_SEQ); + if (ret) + return ret; + + st->seq = true; + } else { + ret = ad7380_set_ch(st, index); + if (ret) + return ret; + } + } + + xfer->bits_per_word = scan_type->realbits; + xfer->offload_flags = SPI_OFFLOAD_XFER_RX_STREAM; + xfer->len = AD7380_SPI_BYTES(scan_type) * st->chip_info->num_simult_channels; + if (st->num_sdo_lines > 1) + xfer->multi_lane_mode = SPI_MULTI_LANE_MODE_STRIPE; + + spi_message_init_with_transfers(&st->offload_msg, xfer, 1); + st->offload_msg.offload = st->offload; + + ret = spi_optimize_message(st->spi, &st->offload_msg); + if (ret) { + dev_err(dev, "failed to prepare offload msg, err: %d\n", + ret); + return ret; + } + + return 0; +} + +static int ad7380_offload_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad7380_state *st = iio_priv(indio_dev); + struct spi_offload_trigger_config config = { + .type = SPI_OFFLOAD_TRIGGER_PERIODIC, + .periodic = { + .frequency_hz = st->offload_trigger_hz, + }, + }; + int ret; + + ret = ad7380_init_offload_msg(st, indio_dev); + if (ret) + return ret; + + /* + * When the sequencer is required to read all channels, we need to + * trigger twice per sample period in order to read one complete set + * of samples. + */ + if (st->seq) + config.periodic.frequency_hz *= 2; + + ret = spi_offload_trigger_enable(st->offload, st->offload_trigger, &config); + if (ret) + spi_unoptimize_message(&st->offload_msg); + + return ret; +} + +static int ad7380_offload_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad7380_state *st = iio_priv(indio_dev); + int ret; + + spi_offload_trigger_disable(st->offload, st->offload_trigger); + spi_unoptimize_message(&st->offload_msg); + + if (st->seq) { + ret = regmap_update_bits(st->regmap, + AD7380_REG_ADDR_CONFIG1, + AD7380_CONFIG1_SEQ, + FIELD_PREP(AD7380_CONFIG1_SEQ, 0)); + if (ret) + return ret; + + st->seq = false; + } + + return 0; +} + +static const struct iio_buffer_setup_ops ad7380_offload_buffer_setup_ops = { + .postenable = ad7380_offload_buffer_postenable, + .predisable = ad7380_offload_buffer_predisable, +}; + +static int ad7380_triggered_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ad7380_state *st = iio_priv(indio_dev); + const struct iio_scan_type *scan_type; + struct spi_message *msg = &st->normal_msg; + int ret; + + /* + * Currently, we always read all channels at the same time. The scan_type + * is the same for all channels, so we just pass the first channel. + */ + scan_type = iio_get_current_scan_type(indio_dev, &indio_dev->channels[0]); + if (IS_ERR(scan_type)) + return PTR_ERR(scan_type); + + if (st->chip_info->has_mux) { + unsigned int index; + + /* + * Depending on the requested scan_mask and current state, + * we need to either change CH bit, or enable sequencer mode + * to sample correct data. + * Sequencer mode is enabled if active mask corresponds to all + * IIO channels enabled. Otherwise, CH bit is set. + */ + ret = iio_active_scan_mask_index(indio_dev); + if (ret < 0) + return ret; + + index = ret; + if (index == AD7380_SCAN_MASK_SEQ) { + ret = regmap_update_bits(st->regmap, + AD7380_REG_ADDR_CONFIG1, + AD7380_CONFIG1_SEQ, + FIELD_PREP(AD7380_CONFIG1_SEQ, 1)); + if (ret) + return ret; + msg = &st->seq_msg; + st->seq = true; + } else { + ret = ad7380_set_ch(st, index); + if (ret) + return ret; + } + + } + + ret = ad7380_update_xfers(st, scan_type); + if (ret) + return ret; + + return spi_optimize_message(st->spi, msg); +} + +static int ad7380_triggered_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ad7380_state *st = iio_priv(indio_dev); + struct spi_message *msg = &st->normal_msg; + int ret; + + if (st->seq) { + ret = regmap_update_bits(st->regmap, + AD7380_REG_ADDR_CONFIG1, + AD7380_CONFIG1_SEQ, + FIELD_PREP(AD7380_CONFIG1_SEQ, 0)); + if (ret) + return ret; + + msg = &st->seq_msg; + st->seq = false; + } + + spi_unoptimize_message(msg); + + return 0; +} + +static const struct iio_buffer_setup_ops ad7380_buffer_setup_ops = { + .preenable = ad7380_triggered_buffer_preenable, + .postdisable = ad7380_triggered_buffer_postdisable, +}; + +static irqreturn_t ad7380_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad7380_state *st = iio_priv(indio_dev); + struct spi_message *msg = st->seq ? &st->seq_msg : &st->normal_msg; + int ret; + + ret = spi_sync(st->spi, msg); + if (ret) + goto out; + + iio_push_to_buffers_with_ts(indio_dev, &st->scan_data, sizeof(st->scan_data), + pf->timestamp); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ad7380_read_direct(struct ad7380_state *st, unsigned int scan_index, + const struct iio_scan_type *scan_type, int *val) +{ + unsigned int index = scan_index; + int ret; + + if (st->chip_info->has_mux) { + unsigned int ch = 0; + + if (index >= st->chip_info->num_simult_channels) { + index -= st->chip_info->num_simult_channels; + ch = 1; + } + + ret = ad7380_set_ch(st, ch); + if (ret) + return ret; + } + + ret = ad7380_update_xfers(st, scan_type); + if (ret) + return ret; + + ret = spi_sync(st->spi, &st->normal_msg); + if (ret < 0) + return ret; + + if (scan_type->realbits > 16) { + if (scan_type->sign == 's') + *val = sign_extend32(*(u32 *)(st->scan_data + 4 * index), + scan_type->realbits - 1); + else + *val = *(u32 *)(st->scan_data + 4 * index) & + GENMASK(scan_type->realbits - 1, 0); + } else { + if (scan_type->sign == 's') + *val = sign_extend32(*(u16 *)(st->scan_data + 2 * index), + scan_type->realbits - 1); + else + *val = *(u16 *)(st->scan_data + 2 * index) & + GENMASK(scan_type->realbits - 1, 0); + } + + return IIO_VAL_INT; +} + +static int ad7380_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad7380_state *st = iio_priv(indio_dev); + const struct iio_scan_type *scan_type; + int ret; + + scan_type = iio_get_current_scan_type(indio_dev, chan); + + if (IS_ERR(scan_type)) + return PTR_ERR(scan_type); + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad7380_read_direct(st, chan->scan_index, + scan_type, val); + + iio_device_release_direct(indio_dev); + + return ret; + case IIO_CHAN_INFO_SCALE: + /* + * According to the datasheet, the LSB size is: + * * (2 × VREF) / 2^N, for differential chips + * * VREF / 2^N, for pseudo-differential chips + * where N is the ADC resolution (i.e realbits) + * + * The gain is stored as a fraction of 1000 and, as we need to + * divide vref_mv by the gain, we invert the gain/1000 fraction. + */ + if (st->chip_info->has_hardware_gain) + *val = mult_frac(st->vref_mv, MILLI, + st->gain_milli[chan->scan_index]); + else + *val = st->vref_mv; + *val2 = scan_type->realbits - chan->differential; + + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: + /* + * According to IIO ABI, offset is applied before scale, + * so offset is: vcm_mv / scale + */ + *val = st->vcm_mv[chan->channel] * (1 << scan_type->realbits) + / st->vref_mv; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad7380_get_osr(st, val); + + iio_device_release_direct(indio_dev); + + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = st->offload_trigger_hz; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad7380_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct ad7380_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = ad7380_oversampling_ratios; + *length = ARRAY_SIZE(ad7380_oversampling_ratios); + *type = IIO_VAL_INT; + + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = st->sample_freq_range; + *type = IIO_VAL_INT; + return IIO_AVAIL_RANGE; + default: + return -EINVAL; + } +} + +/** + * ad7380_osr_to_regval - convert ratio to OSR register value + * @ratio: ratio to check + * + * Check if ratio is present in the list of available ratios and return the + * corresponding value that needs to be written to the register to select that + * ratio. + * + * Returns: register value (0 to 7) or -EINVAL if there is not an exact match + */ +static int ad7380_osr_to_regval(int ratio) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ad7380_oversampling_ratios); i++) { + if (ratio == ad7380_oversampling_ratios[i]) + return i; + } + + return -EINVAL; +} + +static int ad7380_set_oversampling_ratio(struct ad7380_state *st, int val) +{ + int ret, osr, boost; + + osr = ad7380_osr_to_regval(val); + if (osr < 0) + return osr; + + /* always enable resolution boost when oversampling is enabled */ + boost = osr > 0 ? 1 : 0; + + ret = regmap_update_bits(st->regmap, + AD7380_REG_ADDR_CONFIG1, + AD7380_CONFIG1_OSR | AD7380_CONFIG1_RES, + FIELD_PREP(AD7380_CONFIG1_OSR, osr) | + FIELD_PREP(AD7380_CONFIG1_RES, boost)); + + if (ret) + return ret; + + st->resolution_boost_enabled = boost; + + /* + * Perform a soft reset. This will flush the oversampling + * block and FIFO but will maintain the content of the + * configurable registers. + */ + ret = regmap_update_bits(st->regmap, + AD7380_REG_ADDR_CONFIG2, + AD7380_CONFIG2_RESET, + FIELD_PREP(AD7380_CONFIG2_RESET, + AD7380_CONFIG2_RESET_SOFT)); + return ret; +} +static int ad7380_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct ad7380_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (val < 1) + return -EINVAL; + return ad7380_set_sample_freq(st, val); + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad7380_set_oversampling_ratio(st, val); + + iio_device_release_direct(indio_dev); + + return ret; + default: + return -EINVAL; + } +} + +static int ad7380_get_current_scan_type(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad7380_state *st = iio_priv(indio_dev); + + return st->resolution_boost_enabled ? AD7380_SCAN_TYPE_RESOLUTION_BOOST + : AD7380_SCAN_TYPE_NORMAL; +} + +static int ad7380_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct ad7380_state *st = iio_priv(indio_dev); + int tmp, ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = regmap_read(st->regmap, AD7380_REG_ADDR_CONFIG1, &tmp); + + iio_device_release_direct(indio_dev); + + if (ret) + return ret; + + return FIELD_GET(AD7380_CONFIG1_ALERTEN, tmp); +} + +static int ad7380_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct ad7380_state *st = iio_priv(indio_dev); + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = regmap_update_bits(st->regmap, + AD7380_REG_ADDR_CONFIG1, + AD7380_CONFIG1_ALERTEN, + FIELD_PREP(AD7380_CONFIG1_ALERTEN, state)); + + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ad7380_get_alert_th(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_direction dir, + int *val) +{ + struct ad7380_state *st = iio_priv(indio_dev); + const struct iio_scan_type *scan_type; + int ret, tmp, shift; + + scan_type = iio_get_current_scan_type(indio_dev, chan); + if (IS_ERR(scan_type)) + return PTR_ERR(scan_type); + + /* + * The register value is 12-bits and is compared to the most significant + * bits of raw value, therefore a shift is required to convert this to + * the same scale as the raw value. + */ + shift = scan_type->realbits - 12; + + switch (dir) { + case IIO_EV_DIR_RISING: + ret = regmap_read(st->regmap, + AD7380_REG_ADDR_ALERT_HIGH_TH, + &tmp); + if (ret) + return ret; + + *val = FIELD_GET(AD7380_ALERT_HIGH_TH, tmp) << shift; + return IIO_VAL_INT; + case IIO_EV_DIR_FALLING: + ret = regmap_read(st->regmap, + AD7380_REG_ADDR_ALERT_LOW_TH, + &tmp); + if (ret) + return ret; + + *val = FIELD_GET(AD7380_ALERT_LOW_TH, tmp) << shift; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad7380_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad7380_get_alert_th(indio_dev, chan, dir, val); + + iio_device_release_direct(indio_dev); + return ret; + default: + return -EINVAL; + } +} + +static int ad7380_set_alert_th(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_direction dir, + int val) +{ + struct ad7380_state *st = iio_priv(indio_dev); + const struct iio_scan_type *scan_type; + u16 th; + + /* + * According to the datasheet, + * AD7380_REG_ADDR_ALERT_HIGH_TH[11:0] are the 12 MSB of the + * 16-bits internal alert high register. LSB are set to 0xf. + * AD7380_REG_ADDR_ALERT_LOW_TH[11:0] are the 12 MSB of the + * 16 bits internal alert low register. LSB are set to 0x0. + * + * When alert is enabled the conversion from the adc is compared + * immediately to the alert high/low thresholds, before any + * oversampling. This means that the thresholds are the same for + * normal mode and oversampling mode. + */ + + /* Extract the 12 MSB of val */ + scan_type = iio_get_current_scan_type(indio_dev, chan); + if (IS_ERR(scan_type)) + return PTR_ERR(scan_type); + + th = val >> (scan_type->realbits - 12); + + switch (dir) { + case IIO_EV_DIR_RISING: + return regmap_write(st->regmap, + AD7380_REG_ADDR_ALERT_HIGH_TH, + th); + case IIO_EV_DIR_FALLING: + return regmap_write(st->regmap, + AD7380_REG_ADDR_ALERT_LOW_TH, + th); + default: + return -EINVAL; + } +} + +static int ad7380_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad7380_set_alert_th(indio_dev, chan, dir, val); + + iio_device_release_direct(indio_dev); + return ret; + default: + return -EINVAL; + } +} + +static const struct iio_info ad7380_info = { + .read_raw = &ad7380_read_raw, + .read_avail = &ad7380_read_avail, + .write_raw = &ad7380_write_raw, + .get_current_scan_type = &ad7380_get_current_scan_type, + .debugfs_reg_access = &ad7380_debugfs_reg_access, + .read_event_config = &ad7380_read_event_config, + .write_event_config = &ad7380_write_event_config, + .read_event_value = &ad7380_read_event_value, + .write_event_value = &ad7380_write_event_value, +}; + +static int ad7380_init(struct ad7380_state *st, bool external_ref_en) +{ + u32 sdo; + int ret; + + /* perform hard reset */ + ret = regmap_update_bits(st->regmap, AD7380_REG_ADDR_CONFIG2, + AD7380_CONFIG2_RESET, + FIELD_PREP(AD7380_CONFIG2_RESET, + AD7380_CONFIG2_RESET_HARD)); + if (ret < 0) + return ret; + + if (external_ref_en) { + /* select external reference voltage */ + ret = regmap_set_bits(st->regmap, AD7380_REG_ADDR_CONFIG1, + AD7380_CONFIG1_REFSEL); + if (ret < 0) + return ret; + } + + /* This is the default value after reset. */ + st->ch = 0; + st->seq = false; + + /* SDO field has an irregular mapping. */ + switch (st->num_sdo_lines) { + case 1: + sdo = 1; + break; + case 2: + sdo = 0; + break; + case 4: + sdo = 2; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(st->regmap, AD7380_REG_ADDR_CONFIG2, + AD7380_CONFIG2_SDO, + FIELD_PREP(AD7380_CONFIG2_SDO, sdo)); +} + +static int ad7380_probe_spi_offload(struct iio_dev *indio_dev, + struct ad7380_state *st) +{ + struct spi_device *spi = st->spi; + struct device *dev = &spi->dev; + struct dma_chan *rx_dma; + int sample_rate, ret; + + indio_dev->setup_ops = &ad7380_offload_buffer_setup_ops; + indio_dev->channels = st->chip_info->offload_channels; + /* Just removing the timestamp channel. */ + indio_dev->num_channels--; + + st->offload_trigger = devm_spi_offload_trigger_get(dev, st->offload, + SPI_OFFLOAD_TRIGGER_PERIODIC); + if (IS_ERR(st->offload_trigger)) + return dev_err_probe(dev, PTR_ERR(st->offload_trigger), + "failed to get offload trigger\n"); + + sample_rate = st->chip_info->max_conversion_rate_hz * + st->num_sdo_lines / st->chip_info->num_simult_channels; + + st->sample_freq_range[0] = 1; /* min */ + st->sample_freq_range[1] = 1; /* step */ + st->sample_freq_range[2] = sample_rate; /* max */ + + /* + * Starting with a quite low frequency, to allow oversampling x32, + * user is then responsible to adjust the frequency for the specific case. + */ + ret = ad7380_set_sample_freq(st, sample_rate / 32); + if (ret) + return ret; + + rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev, st->offload); + if (IS_ERR(rx_dma)) + return dev_err_probe(dev, PTR_ERR(rx_dma), + "failed to get offload RX DMA\n"); + + ret = devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, + rx_dma, IIO_BUFFER_DIRECTION_IN); + if (ret) + return dev_err_probe(dev, ret, "cannot setup dma buffer\n"); + + return 0; +} + +static int ad7380_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ad7380_state *st; + bool external_ref_en; + int ret, i; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + st->chip_info = spi_get_device_match_data(spi); + if (!st->chip_info) + return dev_err_probe(dev, -EINVAL, "missing match data\n"); + + st->num_sdo_lines = spi->num_rx_lanes; + + if (st->num_sdo_lines < 1 || st->num_sdo_lines > st->chip_info->num_simult_channels) + return dev_err_probe(dev, -EINVAL, + "invalid number of SDO lines (%d)\n", + st->num_sdo_lines); + + ret = devm_regulator_bulk_get_enable(dev, st->chip_info->num_supplies, + st->chip_info->supplies); + + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable power supplies\n"); + fsleep(T_POWERUP_US); + + if (st->chip_info->internal_ref_only) { + /* + * ADAQ chips use fixed internal reference but still + * require a specific reference supply to power it. + * "refin" is already enabled with other power supplies + * in bulk_get_enable(). + */ + + st->vref_mv = st->chip_info->internal_ref_mv; + + /* these chips don't have a register bit for this */ + external_ref_en = false; + } else if (st->chip_info->external_ref_only) { + ret = devm_regulator_get_enable_read_voltage(dev, "refin"); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to get refin regulator\n"); + + st->vref_mv = ret / 1000; + + /* these chips don't have a register bit for this */ + external_ref_en = false; + } else { + /* + * If there is no REFIO supply, then it means that we are using + * the internal reference, otherwise REFIO is reference voltage. + */ + ret = devm_regulator_get_enable_read_voltage(dev, "refio"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, + "Failed to get refio regulator\n"); + + external_ref_en = ret != -ENODEV; + st->vref_mv = external_ref_en ? ret / 1000 + : st->chip_info->internal_ref_mv; + } + + if (st->chip_info->num_vcm_supplies > ARRAY_SIZE(st->vcm_mv)) + return dev_err_probe(dev, -EINVAL, + "invalid number of VCM supplies\n"); + + /* + * pseudo-differential chips have common mode supplies for the negative + * input pin. + */ + for (i = 0; i < st->chip_info->num_vcm_supplies; i++) { + const char *vcm = st->chip_info->vcm_supplies[i]; + + ret = devm_regulator_get_enable_read_voltage(dev, vcm); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to get %s regulator\n", + vcm); + + st->vcm_mv[i] = ret / 1000; + } + + for (i = 0; i < MAX_NUM_CHANNELS; i++) + st->gain_milli[i] = AD7380_DEFAULT_GAIN_MILLI; + + if (st->chip_info->has_hardware_gain) { + device_for_each_child_node_scoped(dev, node) { + unsigned int channel; + int gain_idx; + u16 gain; + + ret = fwnode_property_read_u32(node, "reg", &channel); + if (ret) + return dev_err_probe(dev, ret, + "Failed to read reg property\n"); + + if (channel >= st->chip_info->num_channels - 1) + return dev_err_probe(dev, -EINVAL, + "Invalid channel number %i\n", + channel); + + ret = fwnode_property_read_u16(node, "adi,gain-milli", + &gain); + if (ret && ret != -EINVAL) + return dev_err_probe(dev, ret, + "Failed to read gain for channel %i\n", + channel); + if (ret != -EINVAL) { + /* + * Match gain value from dt to one of supported + * gains + */ + gain_idx = find_closest(gain, ad7380_gains, + ARRAY_SIZE(ad7380_gains)); + st->gain_milli[channel] = ad7380_gains[gain_idx]; + } + } + } + + st->regmap = devm_regmap_init(dev, NULL, st, &ad7380_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "failed to allocate register map\n"); + + /* + * Setting up xfer structures for both normal and sequence mode. These + * struct are used for both direct read and triggered buffer. Additional + * fields will be set up in ad7380_update_xfers() based on the current + * state of the driver at the time of the read. + */ + + /* + * In normal mode a read is composed of two steps: + * - first, toggle CS (no data xfer) to trigger a conversion + * - then, read data + */ + st->normal_xfer[0].cs_change = 1; + st->normal_xfer[0].cs_change_delay.value = st->chip_info->timing_specs->t_csh_ns; + st->normal_xfer[0].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS; + st->normal_xfer[1].rx_buf = st->scan_data; + if (st->num_sdo_lines > 1) + st->normal_xfer[1].multi_lane_mode = SPI_MULTI_LANE_MODE_STRIPE; + + spi_message_init_with_transfers(&st->normal_msg, st->normal_xfer, + ARRAY_SIZE(st->normal_xfer)); + /* + * In sequencer mode a read is composed of four steps: + * - CS toggle (no data xfer) to get the right point in the sequence + * - CS toggle (no data xfer) to trigger a conversion of AinX0 and + * acquisition of AinX1 + * - 2 data reads, to read AinX0 and AinX1 + */ + st->seq_xfer[0].cs_change = 1; + st->seq_xfer[0].cs_change_delay.value = st->chip_info->timing_specs->t_csh_ns; + st->seq_xfer[0].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS; + st->seq_xfer[1].cs_change = 1; + st->seq_xfer[1].cs_change_delay.value = st->chip_info->timing_specs->t_csh_ns; + st->seq_xfer[1].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS; + + st->seq_xfer[2].rx_buf = st->scan_data; + st->seq_xfer[2].cs_change = 1; + st->seq_xfer[2].cs_change_delay.value = st->chip_info->timing_specs->t_csh_ns; + st->seq_xfer[2].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS; + if (st->num_sdo_lines > 1) { + st->seq_xfer[2].multi_lane_mode = SPI_MULTI_LANE_MODE_STRIPE; + st->seq_xfer[3].multi_lane_mode = SPI_MULTI_LANE_MODE_STRIPE; + } + + spi_message_init_with_transfers(&st->seq_msg, st->seq_xfer, + ARRAY_SIZE(st->seq_xfer)); + + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + indio_dev->name = st->chip_info->name; + indio_dev->info = &ad7380_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->available_scan_masks = st->chip_info->available_scan_masks; + + st->offload = devm_spi_offload_get(dev, spi, &ad7380_offload_config); + ret = PTR_ERR_OR_ZERO(st->offload); + if (ret && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to get offload\n"); + + /* If no SPI offload, fall back to low speed usage. */ + if (ret == -ENODEV) { + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + ad7380_trigger_handler, + &ad7380_buffer_setup_ops); + if (ret) + return ret; + } else { + ret = ad7380_probe_spi_offload(indio_dev, st); + if (ret) + return ret; + } + + ret = ad7380_init(st, external_ref_en); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ad7380_of_match_table[] = { + { .compatible = "adi,ad7380", .data = &ad7380_chip_info }, + { .compatible = "adi,ad7381", .data = &ad7381_chip_info }, + { .compatible = "adi,ad7383", .data = &ad7383_chip_info }, + { .compatible = "adi,ad7384", .data = &ad7384_chip_info }, + { .compatible = "adi,ad7386", .data = &ad7386_chip_info }, + { .compatible = "adi,ad7387", .data = &ad7387_chip_info }, + { .compatible = "adi,ad7388", .data = &ad7388_chip_info }, + { .compatible = "adi,ad7380-4", .data = &ad7380_4_chip_info }, + { .compatible = "adi,ad7381-4", .data = &ad7381_4_chip_info }, + { .compatible = "adi,ad7383-4", .data = &ad7383_4_chip_info }, + { .compatible = "adi,ad7384-4", .data = &ad7384_4_chip_info }, + { .compatible = "adi,ad7386-4", .data = &ad7386_4_chip_info }, + { .compatible = "adi,ad7387-4", .data = &ad7387_4_chip_info }, + { .compatible = "adi,ad7388-4", .data = &ad7388_4_chip_info }, + { .compatible = "adi,ad7389-4", .data = &ad7389_4_chip_info }, + { .compatible = "adi,adaq4370-4", .data = &adaq4370_4_chip_info }, + { .compatible = "adi,adaq4380-4", .data = &adaq4380_4_chip_info }, + { .compatible = "adi,adaq4381-4", .data = &adaq4381_4_chip_info }, + { } +}; + +static const struct spi_device_id ad7380_id_table[] = { + { .name = "ad7380", .driver_data = (kernel_ulong_t)&ad7380_chip_info }, + { .name = "ad7381", .driver_data = (kernel_ulong_t)&ad7381_chip_info }, + { .name = "ad7383", .driver_data = (kernel_ulong_t)&ad7383_chip_info }, + { .name = "ad7384", .driver_data = (kernel_ulong_t)&ad7384_chip_info }, + { .name = "ad7386", .driver_data = (kernel_ulong_t)&ad7386_chip_info }, + { .name = "ad7387", .driver_data = (kernel_ulong_t)&ad7387_chip_info }, + { .name = "ad7388", .driver_data = (kernel_ulong_t)&ad7388_chip_info }, + { .name = "ad7380-4", .driver_data = (kernel_ulong_t)&ad7380_4_chip_info }, + { .name = "ad7381-4", .driver_data = (kernel_ulong_t)&ad7381_4_chip_info }, + { .name = "ad7383-4", .driver_data = (kernel_ulong_t)&ad7383_4_chip_info }, + { .name = "ad7384-4", .driver_data = (kernel_ulong_t)&ad7384_4_chip_info }, + { .name = "ad7386-4", .driver_data = (kernel_ulong_t)&ad7386_4_chip_info }, + { .name = "ad7387-4", .driver_data = (kernel_ulong_t)&ad7387_4_chip_info }, + { .name = "ad7388-4", .driver_data = (kernel_ulong_t)&ad7388_4_chip_info }, + { .name = "ad7389-4", .driver_data = (kernel_ulong_t)&ad7389_4_chip_info }, + { .name = "adaq4370-4", .driver_data = (kernel_ulong_t)&adaq4370_4_chip_info }, + { .name = "adaq4380-4", .driver_data = (kernel_ulong_t)&adaq4380_4_chip_info }, + { .name = "adaq4381-4", .driver_data = (kernel_ulong_t)&adaq4381_4_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7380_id_table); + +static struct spi_driver ad7380_driver = { + .driver = { + .name = "ad7380", + .of_match_table = ad7380_of_match_table, + }, + .probe = ad7380_probe, + .id_table = ad7380_id_table, +}; +module_spi_driver(ad7380_driver); + +MODULE_AUTHOR("Stefan Popa "); +MODULE_DESCRIPTION("Analog Devices AD738x ADC driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER"); diff --git a/drivers/iio/adc/ad7405.c b/drivers/iio/adc/ad7405.c new file mode 100644 index 000000000..0996f48d7 --- /dev/null +++ b/drivers/iio/adc/ad7405.c @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices AD7405 driver + * + * Copyright 2025 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static const unsigned int ad7405_dec_rates_range[] = { + 32, 1, 4096, +}; + +struct ad7405_chip_info { + const char *name; + const unsigned int full_scale_mv; +}; + +struct ad7405_state { + struct iio_backend *back; + const struct ad7405_chip_info *info; + unsigned int ref_frequency; + unsigned int dec_rate; +}; + +static int ad7405_set_dec_rate(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int dec_rate) +{ + struct ad7405_state *st = iio_priv(indio_dev); + int ret; + + if (dec_rate > 4096 || dec_rate < 32) + return -EINVAL; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = iio_backend_oversampling_ratio_set(st->back, chan->scan_index, dec_rate); + iio_device_release_direct(indio_dev); + + if (ret < 0) + return ret; + + st->dec_rate = dec_rate; + + return 0; +} + +static int ad7405_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, + int *val2, long info) +{ + struct ad7405_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SCALE: + *val = st->info->full_scale_mv; + *val2 = indio_dev->channels[0].scan_type.realbits - 1; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = st->dec_rate; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = DIV_ROUND_CLOSEST_ULL(st->ref_frequency, st->dec_rate); + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + *val = -(1 << (indio_dev->channels[0].scan_type.realbits - 1)); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad7405_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long info) +{ + switch (info) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + if (val < 0) + return -EINVAL; + return ad7405_set_dec_rate(indio_dev, chan, val); + default: + return -EINVAL; + } +} + +static int ad7405_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long info) +{ + switch (info) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = ad7405_dec_rates_range; + *type = IIO_VAL_INT; + return IIO_AVAIL_RANGE; + default: + return -EINVAL; + } +} + +static const struct iio_info ad7405_iio_info = { + .read_raw = &ad7405_read_raw, + .write_raw = &ad7405_write_raw, + .read_avail = &ad7405_read_avail, +}; + +static const struct iio_chan_spec ad7405_channel = { + .type = IIO_VOLTAGE, + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .info_mask_shared_by_all = IIO_CHAN_INFO_SAMP_FREQ | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .indexed = 1, + .channel = 0, + .channel2 = 1, + .differential = 1, + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + }, +}; + +static const struct ad7405_chip_info ad7405_chip_info = { + .name = "ad7405", + .full_scale_mv = 320, +}; + +static const struct ad7405_chip_info adum7701_chip_info = { + .name = "adum7701", + .full_scale_mv = 320, +}; + +static const struct ad7405_chip_info adum7702_chip_info = { + .name = "adum7702", + .full_scale_mv = 64, +}; + +static const struct ad7405_chip_info adum7703_chip_info = { + .name = "adum7703", + .full_scale_mv = 320, +}; + +static const char * const ad7405_power_supplies[] = { + "vdd1", "vdd2", +}; + +static int ad7405_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct ad7405_state *st; + struct clk *clk; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->info = device_get_match_data(dev); + if (!st->info) + return dev_err_probe(dev, -EINVAL, "no chip info\n"); + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(ad7405_power_supplies), + ad7405_power_supplies); + if (ret) + return dev_err_probe(dev, ret, "failed to get and enable supplies"); + + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + st->ref_frequency = clk_get_rate(clk); + if (!st->ref_frequency) + return -EINVAL; + + indio_dev->name = st->info->name; + indio_dev->channels = &ad7405_channel; + indio_dev->num_channels = 1; + indio_dev->info = &ad7405_iio_info; + + st->back = devm_iio_backend_get(dev, NULL); + if (IS_ERR(st->back)) + return dev_err_probe(dev, PTR_ERR(st->back), + "failed to get IIO backend"); + + ret = iio_backend_chan_enable(st->back, 0); + if (ret) + return ret; + + ret = devm_iio_backend_request_buffer(dev, st->back, indio_dev); + if (ret) + return ret; + + ret = devm_iio_backend_enable(dev, st->back); + if (ret) + return ret; + + /* + * Set 256 decimation rate. The default value in the AXI_ADC register + * is 0, so we set the register with a decimation rate value that is + * functional for all parts. + */ + ret = ad7405_set_dec_rate(indio_dev, &indio_dev->channels[0], 256); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ad7405_of_match[] = { + { .compatible = "adi,ad7405", .data = &ad7405_chip_info, }, + { .compatible = "adi,adum7701", .data = &adum7701_chip_info, }, + { .compatible = "adi,adum7702", .data = &adum7702_chip_info, }, + { .compatible = "adi,adum7703", .data = &adum7703_chip_info, }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7405_of_match); + +static struct platform_driver ad7405_driver = { + .driver = { + .name = "ad7405", + .of_match_table = ad7405_of_match, + }, + .probe = ad7405_probe, +}; +module_platform_driver(ad7405_driver); + +MODULE_AUTHOR("Dragos Bogdan "); +MODULE_AUTHOR("Pop Ioan Daniel "); +MODULE_DESCRIPTION("Analog Devices AD7405 driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_BACKEND"); diff --git a/drivers/iio/adc/ad7476.c b/drivers/iio/adc/ad7476.c new file mode 100644 index 000000000..c007f453a --- /dev/null +++ b/drivers/iio/adc/ad7476.c @@ -0,0 +1,465 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices AD7466/7/8 AD7476/5/7/8 (A) SPI ADC driver + * TI ADC081S/ADC101S/ADC121S 8/10/12-bit SPI ADC driver + * + * Copyright 2010 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +struct ad7476_state; + +struct ad7476_chip_info { + unsigned int int_vref_mv; + struct iio_chan_spec channel[2]; + void (*reset)(struct ad7476_state *); + void (*conversion_pre_op)(struct ad7476_state *st); + void (*conversion_post_op)(struct ad7476_state *st); + bool has_vref; + bool has_vdrive; + bool convstart_required; +}; + +struct ad7476_state { + struct spi_device *spi; + const struct ad7476_chip_info *chip_info; + struct gpio_desc *convst_gpio; + struct spi_transfer xfer; + struct spi_message msg; + struct iio_chan_spec channel[2]; + int scale_mv; + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + * Make the buffer large enough for one 16 bit sample and one 64 bit + * aligned 64 bit timestamp. + */ + unsigned char data[ALIGN(2, sizeof(s64)) + sizeof(s64)] __aligned(IIO_DMA_MINALIGN); +}; + +static void ad7091_convst(struct ad7476_state *st) +{ + if (!st->convst_gpio) + return; + + gpiod_set_value_cansleep(st->convst_gpio, 0); + udelay(1); /* CONVST pulse width: 10 ns min */ + gpiod_set_value_cansleep(st->convst_gpio, 1); + udelay(1); /* Conversion time: 650 ns max */ +} + +static void bd79105_convst_disable(struct ad7476_state *st) +{ + gpiod_set_value_cansleep(st->convst_gpio, 0); +} + +static void bd79105_convst_enable(struct ad7476_state *st) +{ + gpiod_set_value_cansleep(st->convst_gpio, 1); + /* Worst case, 2790 ns required for conversion */ + ndelay(2790); +} + +static irqreturn_t ad7476_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad7476_state *st = iio_priv(indio_dev); + int b_sent; + + if (st->chip_info->conversion_pre_op) + st->chip_info->conversion_pre_op(st); + + b_sent = spi_sync(st->spi, &st->msg); + if (b_sent < 0) + goto done; + + iio_push_to_buffers_with_ts(indio_dev, st->data, sizeof(st->data), + iio_get_time_ns(indio_dev)); +done: + if (st->chip_info->conversion_post_op) + st->chip_info->conversion_post_op(st); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static void ad7091_reset(struct ad7476_state *st) +{ + /* Any transfers with 8 scl cycles will reset the device */ + spi_read(st->spi, st->data, 1); +} + +static int ad7476_scan_direct(struct ad7476_state *st) +{ + int ret; + + if (st->chip_info->conversion_pre_op) + st->chip_info->conversion_pre_op(st); + + ret = spi_sync(st->spi, &st->msg); + if (ret) + return ret; + + if (st->chip_info->conversion_post_op) + st->chip_info->conversion_post_op(st); + + return be16_to_cpup((__be16 *)st->data); +} + +static int ad7476_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret; + struct ad7476_state *st = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = ad7476_scan_direct(st); + iio_device_release_direct(indio_dev); + + if (ret < 0) + return ret; + *val = (ret >> chan->scan_type.shift) & + GENMASK(chan->scan_type.realbits - 1, 0); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = st->scale_mv; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + +#define _AD7476_CHAN(bits, _shift, _info_mask_sep) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .info_mask_separate = _info_mask_sep, \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = (_shift), \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADC081S_CHAN(bits) _AD7476_CHAN((bits), 12 - (bits), \ + BIT(IIO_CHAN_INFO_RAW)) +#define AD7476_CHAN(bits) _AD7476_CHAN((bits), 13 - (bits), \ + BIT(IIO_CHAN_INFO_RAW)) +#define AD7940_CHAN(bits) _AD7476_CHAN((bits), 15 - (bits), \ + BIT(IIO_CHAN_INFO_RAW)) +#define AD7091R_CHAN(bits) _AD7476_CHAN((bits), 16 - (bits), 0) +#define ADS786X_CHAN(bits) _AD7476_CHAN((bits), 12 - (bits), \ + BIT(IIO_CHAN_INFO_RAW)) + +static const struct ad7476_chip_info ad7091_chip_info = { + .channel[0] = AD7091R_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + .conversion_pre_op = ad7091_convst, + .reset = ad7091_reset, +}; + +static const struct ad7476_chip_info ad7091r_chip_info = { + .channel[0] = AD7091R_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + .conversion_pre_op = ad7091_convst, + .int_vref_mv = 2500, + .has_vref = true, + .reset = ad7091_reset, +}; + +static const struct ad7476_chip_info ad7273_chip_info = { + .channel[0] = AD7940_CHAN(10), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + .has_vref = true, +}; + +static const struct ad7476_chip_info ad7274_chip_info = { + .channel[0] = AD7940_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + .has_vref = true, +}; + +static const struct ad7476_chip_info ad7276_chip_info = { + .channel[0] = AD7940_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ad7277_chip_info = { + .channel[0] = AD7940_CHAN(10), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ad7278_chip_info = { + .channel[0] = AD7940_CHAN(8), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ad7466_chip_info = { + .channel[0] = AD7476_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ad7467_chip_info = { + .channel[0] = AD7476_CHAN(10), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ad7468_chip_info = { + .channel[0] = AD7476_CHAN(8), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ad7475_chip_info = { + .channel[0] = AD7476_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + .has_vref = true, + .has_vdrive = true, +}; + +static const struct ad7476_chip_info ad7495_chip_info = { + .channel[0] = AD7476_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + .int_vref_mv = 2500, + .has_vdrive = true, +}; + +static const struct ad7476_chip_info ad7940_chip_info = { + .channel[0] = AD7940_CHAN(14), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info adc081s_chip_info = { + .channel[0] = ADC081S_CHAN(8), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info adc101s_chip_info = { + .channel[0] = ADC081S_CHAN(10), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info adc121s_chip_info = { + .channel[0] = ADC081S_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ads7866_chip_info = { + .channel[0] = ADS786X_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ads7867_chip_info = { + .channel[0] = ADS786X_CHAN(10), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ads7868_chip_info = { + .channel[0] = ADS786X_CHAN(8), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ltc2314_14_chip_info = { + .channel[0] = AD7940_CHAN(14), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + .has_vref = true, +}; + +static const struct ad7476_chip_info bd79105_chip_info = { + .channel[0] = AD7091R_CHAN(16), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + /* + * The BD79105 starts ADC data conversion when the CONVSTART line is + * set HIGH. The CONVSTART must be kept HIGH until the data has been + * read from the ADC. + */ + .conversion_pre_op = bd79105_convst_enable, + .conversion_post_op = bd79105_convst_disable, + /* BD79105 won't do conversion without convstart */ + .convstart_required = true, + .has_vref = true, + .has_vdrive = true, +}; + +static const struct iio_info ad7476_info = { + .read_raw = &ad7476_read_raw, +}; + +static int ad7476_probe(struct spi_device *spi) +{ + struct ad7476_state *st; + struct iio_dev *indio_dev; + unsigned int i; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->chip_info = spi_get_device_match_data(spi); + if (!st->chip_info) + return -ENODEV; + + /* Use VCC for reference voltage if vref / internal vref aren't used */ + if (!st->chip_info->int_vref_mv && !st->chip_info->has_vref) { + ret = devm_regulator_get_enable_read_voltage(&spi->dev, "vcc"); + if (ret < 0) + return ret; + st->scale_mv = ret / 1000; + } else { + ret = devm_regulator_get_enable(&spi->dev, "vcc"); + if (ret < 0) + return ret; + } + + if (st->chip_info->has_vref) { + ret = devm_regulator_get_enable_read_voltage(&spi->dev, "vref"); + if (ret < 0) { + /* Vref is optional if a device has an internal reference */ + if (!st->chip_info->int_vref_mv || ret != -ENODEV) + return ret; + } else { + st->scale_mv = ret / 1000; + } + } + + if (!st->scale_mv) + st->scale_mv = st->chip_info->int_vref_mv; + + if (st->chip_info->has_vdrive) { + ret = devm_regulator_get_enable(&spi->dev, "vdrive"); + if (ret) + return ret; + } + + st->convst_gpio = devm_gpiod_get_optional(&spi->dev, + "adi,conversion-start", + GPIOD_OUT_LOW); + if (IS_ERR(st->convst_gpio)) + return PTR_ERR(st->convst_gpio); + + if (st->chip_info->convstart_required && !st->convst_gpio) + return dev_err_probe(&spi->dev, -EINVAL, "No convstart GPIO\n"); + + /* + * This will never happen. Unless someone changes the channel specs + * in this driver. And if someone does, without changing the loop + * below, then we'd better immediately produce a big fat error, before + * the change proceeds from that developer's table. + */ + static_assert(ARRAY_SIZE(st->channel) == ARRAY_SIZE(st->chip_info->channel)); + for (i = 0; i < ARRAY_SIZE(st->channel); i++) { + st->channel[i] = st->chip_info->channel[i]; + if (st->convst_gpio) + __set_bit(IIO_CHAN_INFO_RAW, + &st->channel[i].info_mask_separate); + } + + st->spi = spi; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->channel; + indio_dev->num_channels = ARRAY_SIZE(st->channel); + indio_dev->info = &ad7476_info; + + /* Setup default message */ + + st->xfer.rx_buf = &st->data; + st->xfer.len = indio_dev->channels[0].scan_type.storagebits / 8; + + spi_message_init(&st->msg); + spi_message_add_tail(&st->xfer, &st->msg); + + ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, NULL, + &ad7476_trigger_handler, NULL); + if (ret) + return ret; + + if (st->chip_info->reset) + st->chip_info->reset(st); + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id ad7476_id[] = { + { .name = "ad7091", .driver_data = (kernel_ulong_t)&ad7091_chip_info }, + { .name = "ad7091r", .driver_data = (kernel_ulong_t)&ad7091r_chip_info }, + { .name = "ad7273", .driver_data = (kernel_ulong_t)&ad7273_chip_info }, + { .name = "ad7274", .driver_data = (kernel_ulong_t)&ad7274_chip_info }, + { .name = "ad7276", .driver_data = (kernel_ulong_t)&ad7276_chip_info }, + { .name = "ad7277", .driver_data = (kernel_ulong_t)&ad7277_chip_info }, + { .name = "ad7278", .driver_data = (kernel_ulong_t)&ad7278_chip_info }, + { .name = "ad7466", .driver_data = (kernel_ulong_t)&ad7466_chip_info }, + { .name = "ad7467", .driver_data = (kernel_ulong_t)&ad7467_chip_info }, + { .name = "ad7468", .driver_data = (kernel_ulong_t)&ad7468_chip_info }, + { .name = "ad7475", .driver_data = (kernel_ulong_t)&ad7475_chip_info }, + { .name = "ad7476", .driver_data = (kernel_ulong_t)&ad7466_chip_info }, + { .name = "ad7476a", .driver_data = (kernel_ulong_t)&ad7466_chip_info }, + { .name = "ad7477", .driver_data = (kernel_ulong_t)&ad7467_chip_info }, + { .name = "ad7477a", .driver_data = (kernel_ulong_t)&ad7467_chip_info }, + { .name = "ad7478", .driver_data = (kernel_ulong_t)&ad7468_chip_info }, + { .name = "ad7478a", .driver_data = (kernel_ulong_t)&ad7468_chip_info }, + { .name = "ad7495", .driver_data = (kernel_ulong_t)&ad7495_chip_info }, + { .name = "ad7910", .driver_data = (kernel_ulong_t)&ad7467_chip_info }, + { .name = "ad7920", .driver_data = (kernel_ulong_t)&ad7466_chip_info }, + { .name = "ad7940", .driver_data = (kernel_ulong_t)&ad7940_chip_info }, + { .name = "adc081s", .driver_data = (kernel_ulong_t)&adc081s_chip_info }, + { .name = "adc101s", .driver_data = (kernel_ulong_t)&adc101s_chip_info }, + { .name = "adc121s", .driver_data = (kernel_ulong_t)&adc121s_chip_info }, + { .name = "ads7866", .driver_data = (kernel_ulong_t)&ads7866_chip_info }, + { .name = "ads7867", .driver_data = (kernel_ulong_t)&ads7867_chip_info }, + { .name = "ads7868", .driver_data = (kernel_ulong_t)&ads7868_chip_info }, + { .name = "bd79105", .driver_data = (kernel_ulong_t)&bd79105_chip_info }, + /* + * The ROHM BU79100G is identical to the TI's ADS7866 from the software + * point of view. The binding document mandates the ADS7866 to be + * marked as a fallback for the BU79100G, but we still need the SPI ID + * here to make the module loading work. + */ + { .name = "bu79100g", .driver_data = (kernel_ulong_t)&ads7866_chip_info }, + { .name = "ltc2314-14", .driver_data = (kernel_ulong_t)<c2314_14_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7476_id); + +static struct spi_driver ad7476_driver = { + .driver = { + .name = "ad7476", + }, + .probe = ad7476_probe, + .id_table = ad7476_id, +}; +module_spi_driver(ad7476_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7476 and similar 1-channel ADCs"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ad7606.c b/drivers/iio/adc/ad7606.c new file mode 100644 index 000000000..cebb8ed8d --- /dev/null +++ b/drivers/iio/adc/ad7606.c @@ -0,0 +1,1712 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AD7606 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "ad7606.h" + +#define AD7606_CALIB_GAIN_MIN 0 +#define AD7606_CALIB_GAIN_STEP 1024 +#define AD7606_CALIB_GAIN_MAX (63 * AD7606_CALIB_GAIN_STEP) + +/* + * Scales are computed as 5000/32768 and 10000/32768 respectively, + * so that when applied to the raw values they provide mV values. + * The scale arrays are kept as IIO_VAL_INT_PLUS_MICRO, so index + * X is the integer part and X + 1 is the fractional part. + */ +static const unsigned int ad7606_16bit_hw_scale_avail[2][2] = { + { 0, 152588 }, { 0, 305176 } +}; + +static const unsigned int ad7606_18bit_hw_scale_avail[2][2] = { + { 0, 38147 }, { 0, 76294 } +}; + +static const unsigned int ad7606c_16bit_single_ended_unipolar_scale_avail[3][2] = { + { 0, 76294 }, { 0, 152588 }, { 0, 190735 } +}; + +static const unsigned int ad7606c_16bit_single_ended_bipolar_scale_avail[5][2] = { + { 0, 76294 }, { 0, 152588 }, { 0, 190735 }, { 0, 305176 }, { 0, 381470 } +}; + +static const unsigned int ad7606c_16bit_differential_bipolar_scale_avail[4][2] = { + { 0, 152588 }, { 0, 305176 }, { 0, 381470 }, { 0, 610352 } +}; + +static const unsigned int ad7606c_18bit_single_ended_unipolar_scale_avail[3][2] = { + { 0, 19073 }, { 0, 38147 }, { 0, 47684 } +}; + +static const unsigned int ad7606c_18bit_single_ended_bipolar_scale_avail[5][2] = { + { 0, 19073 }, { 0, 38147 }, { 0, 47684 }, { 0, 76294 }, { 0, 95367 } +}; + +static const unsigned int ad7606c_18bit_differential_bipolar_scale_avail[4][2] = { + { 0, 38147 }, { 0, 76294 }, { 0, 95367 }, { 0, 152588 } +}; + +static const unsigned int ad7606_16bit_sw_scale_avail[3][2] = { + { 0, 76293 }, { 0, 152588 }, { 0, 305176 } +}; + +static const unsigned int ad7607_hw_scale_avail[2][2] = { + { 0, 610352 }, { 1, 220703 } +}; + +static const unsigned int ad7609_hw_scale_avail[2][2] = { + { 0, 152588 }, { 0, 305176 } +}; + +static const unsigned int ad7606_oversampling_avail[7] = { + 1, 2, 4, 8, 16, 32, 64, +}; + +static const unsigned int ad7606b_oversampling_avail[9] = { + 1, 2, 4, 8, 16, 32, 64, 128, 256, +}; + +static const unsigned int ad7616_oversampling_avail[8] = { + 1, 2, 4, 8, 16, 32, 64, 128, +}; + +static const int ad7606_calib_offset_avail[3] = { + -128, 1, 127, +}; + +static const int ad7606c_18bit_calib_offset_avail[3] = { + -512, 4, 508, +}; + +static const int ad7606b_calib_phase_avail[][2] = { + { 0, 0 }, { 0, 1250 }, { 0, 318750 }, +}; + +static const int ad7606c_calib_phase_avail[][2] = { + { 0, 0 }, { 0, 1000 }, { 0, 255000 }, +}; + +static int ad7606c_18bit_chan_scale_setup(struct iio_dev *indio_dev, + struct iio_chan_spec *chan); +static int ad7606c_16bit_chan_scale_setup(struct iio_dev *indio_dev, + struct iio_chan_spec *chan); +static int ad7606_16bit_chan_scale_setup(struct iio_dev *indio_dev, + struct iio_chan_spec *chan); +static int ad7607_chan_scale_setup(struct iio_dev *indio_dev, + struct iio_chan_spec *chan); +static int ad7608_chan_scale_setup(struct iio_dev *indio_dev, + struct iio_chan_spec *chan); +static int ad7609_chan_scale_setup(struct iio_dev *indio_dev, + struct iio_chan_spec *chan); +static int ad7616_sw_mode_setup(struct iio_dev *indio_dev); +static int ad7606b_sw_mode_setup(struct iio_dev *indio_dev); + +const struct ad7606_chip_info ad7605_4_info = { + .max_samplerate = 300 * KILO, + .name = "ad7605-4", + .bits = 16, + .num_adc_channels = 4, + .scale_setup_cb = ad7606_16bit_chan_scale_setup, +}; +EXPORT_SYMBOL_NS_GPL(ad7605_4_info, "IIO_AD7606"); + +const struct ad7606_chip_info ad7606_8_info = { + .max_samplerate = 200 * KILO, + .name = "ad7606-8", + .bits = 16, + .num_adc_channels = 8, + .oversampling_avail = ad7606_oversampling_avail, + .oversampling_num = ARRAY_SIZE(ad7606_oversampling_avail), + .scale_setup_cb = ad7606_16bit_chan_scale_setup, +}; +EXPORT_SYMBOL_NS_GPL(ad7606_8_info, "IIO_AD7606"); + +const struct ad7606_chip_info ad7606_6_info = { + .max_samplerate = 200 * KILO, + .name = "ad7606-6", + .bits = 16, + .num_adc_channels = 6, + .oversampling_avail = ad7606_oversampling_avail, + .oversampling_num = ARRAY_SIZE(ad7606_oversampling_avail), + .scale_setup_cb = ad7606_16bit_chan_scale_setup, + .offload_storagebits = 32, +}; +EXPORT_SYMBOL_NS_GPL(ad7606_6_info, "IIO_AD7606"); + +const struct ad7606_chip_info ad7606_4_info = { + .max_samplerate = 200 * KILO, + .name = "ad7606-4", + .bits = 16, + .num_adc_channels = 4, + .oversampling_avail = ad7606_oversampling_avail, + .oversampling_num = ARRAY_SIZE(ad7606_oversampling_avail), + .scale_setup_cb = ad7606_16bit_chan_scale_setup, + .offload_storagebits = 32, +}; +EXPORT_SYMBOL_NS_GPL(ad7606_4_info, "IIO_AD7606"); + +const struct ad7606_chip_info ad7606b_info = { + .max_samplerate = 800 * KILO, + .name = "ad7606b", + .bits = 16, + .num_adc_channels = 8, + .oversampling_avail = ad7606_oversampling_avail, + .oversampling_num = ARRAY_SIZE(ad7606_oversampling_avail), + .scale_setup_cb = ad7606_16bit_chan_scale_setup, + .sw_setup_cb = ad7606b_sw_mode_setup, + .offload_storagebits = 32, + .calib_gain_avail = true, + .calib_offset_avail = ad7606_calib_offset_avail, + .calib_phase_avail = ad7606b_calib_phase_avail, +}; +EXPORT_SYMBOL_NS_GPL(ad7606b_info, "IIO_AD7606"); + +const struct ad7606_chip_info ad7606c_16_info = { + .max_samplerate = 1 * MEGA, + .name = "ad7606c16", + .bits = 16, + .num_adc_channels = 8, + .oversampling_avail = ad7606_oversampling_avail, + .oversampling_num = ARRAY_SIZE(ad7606_oversampling_avail), + .scale_setup_cb = ad7606c_16bit_chan_scale_setup, + .sw_setup_cb = ad7606b_sw_mode_setup, + .offload_storagebits = 32, + .calib_gain_avail = true, + .calib_offset_avail = ad7606_calib_offset_avail, + .calib_phase_avail = ad7606c_calib_phase_avail, +}; +EXPORT_SYMBOL_NS_GPL(ad7606c_16_info, "IIO_AD7606"); + +const struct ad7606_chip_info ad7607_info = { + .max_samplerate = 200 * KILO, + .name = "ad7607", + .bits = 14, + .num_adc_channels = 8, + .oversampling_avail = ad7606_oversampling_avail, + .oversampling_num = ARRAY_SIZE(ad7606_oversampling_avail), + .scale_setup_cb = ad7607_chan_scale_setup, + .offload_storagebits = 32, +}; +EXPORT_SYMBOL_NS_GPL(ad7607_info, "IIO_AD7606"); + +const struct ad7606_chip_info ad7608_info = { + .max_samplerate = 200 * KILO, + .name = "ad7608", + .bits = 18, + .num_adc_channels = 8, + .oversampling_avail = ad7606_oversampling_avail, + .oversampling_num = ARRAY_SIZE(ad7606_oversampling_avail), + .scale_setup_cb = ad7608_chan_scale_setup, + .offload_storagebits = 32, +}; +EXPORT_SYMBOL_NS_GPL(ad7608_info, "IIO_AD7606"); + +const struct ad7606_chip_info ad7609_info = { + .max_samplerate = 200 * KILO, + .name = "ad7609", + .bits = 18, + .num_adc_channels = 8, + .oversampling_avail = ad7606_oversampling_avail, + .oversampling_num = ARRAY_SIZE(ad7606_oversampling_avail), + .scale_setup_cb = ad7609_chan_scale_setup, + .offload_storagebits = 32, +}; +EXPORT_SYMBOL_NS_GPL(ad7609_info, "IIO_AD7606"); + +const struct ad7606_chip_info ad7606c_18_info = { + .max_samplerate = 1 * MEGA, + .name = "ad7606c18", + .bits = 18, + .num_adc_channels = 8, + .oversampling_avail = ad7606_oversampling_avail, + .oversampling_num = ARRAY_SIZE(ad7606_oversampling_avail), + .scale_setup_cb = ad7606c_18bit_chan_scale_setup, + .sw_setup_cb = ad7606b_sw_mode_setup, + .offload_storagebits = 32, + .calib_gain_avail = true, + .calib_offset_avail = ad7606c_18bit_calib_offset_avail, + .calib_phase_avail = ad7606c_calib_phase_avail, +}; +EXPORT_SYMBOL_NS_GPL(ad7606c_18_info, "IIO_AD7606"); + +const struct ad7606_chip_info ad7616_info = { + .max_samplerate = 1 * MEGA, + .init_delay_ms = 15, + .name = "ad7616", + .bits = 16, + .num_adc_channels = 16, + .oversampling_avail = ad7616_oversampling_avail, + .oversampling_num = ARRAY_SIZE(ad7616_oversampling_avail), + .os_req_reset = true, + .scale_setup_cb = ad7606_16bit_chan_scale_setup, + .sw_setup_cb = ad7616_sw_mode_setup, + .offload_storagebits = 16, +}; +EXPORT_SYMBOL_NS_GPL(ad7616_info, "IIO_AD7606"); + +int ad7606_reset(struct ad7606_state *st) +{ + if (st->gpio_reset) { + gpiod_set_value(st->gpio_reset, 1); + ndelay(100); /* t_reset >= 100ns */ + gpiod_set_value(st->gpio_reset, 0); + return 0; + } + + return -ENODEV; +} +EXPORT_SYMBOL_NS_GPL(ad7606_reset, "IIO_AD7606"); + +static int ad7606_16bit_chan_scale_setup(struct iio_dev *indio_dev, + struct iio_chan_spec *chan) +{ + struct ad7606_state *st = iio_priv(indio_dev); + struct ad7606_chan_info *ci = &st->chan_info[chan->scan_index]; + + if (!st->sw_mode_en) { + /* tied to logic low, analog input range is +/- 5V */ + ci->range = 0; + ci->scale_avail = ad7606_16bit_hw_scale_avail; + ci->num_scales = ARRAY_SIZE(ad7606_16bit_hw_scale_avail); + return 0; + } + + /* Scale of 0.076293 is only available in sw mode */ + /* After reset, in software mode, ±10 V is set by default */ + ci->range = 2; + ci->scale_avail = ad7606_16bit_sw_scale_avail; + ci->num_scales = ARRAY_SIZE(ad7606_16bit_sw_scale_avail); + + return 0; +} + +static int ad7606_get_chan_config(struct iio_dev *indio_dev, int ch, + bool *bipolar, bool *differential) +{ + struct ad7606_state *st = iio_priv(indio_dev); + struct ad7606_chan_info *ci; + unsigned int num_channels = st->chip_info->num_adc_channels; + struct device *dev = st->dev; + int ret; + + *bipolar = false; + *differential = false; + + device_for_each_child_node_scoped(dev, child) { + u32 pins[2]; + int reg; + + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret) + return ret; + + /* channel number (here) is from 1 to num_channels */ + if (reg < 1 || reg > num_channels) + return -EINVAL; + + /* Loop until we are in the right channel. */ + if (reg != (ch + 1)) + continue; + + *bipolar = fwnode_property_read_bool(child, "bipolar"); + + ret = fwnode_property_read_u32_array(child, "diff-channels", + pins, ARRAY_SIZE(pins)); + /* Channel is differential, if pins are the same as 'reg' */ + if (ret == 0 && (pins[0] != reg || pins[1] != reg)) { + dev_err(dev, + "Differential pins must be the same as 'reg'"); + return -EINVAL; + } + + *differential = (ret == 0); + + if (*differential && !*bipolar) { + dev_err(dev, + "'bipolar' must be added for diff channel %d\n", + reg); + return -EINVAL; + } + + ci = &st->chan_info[reg - 1]; + + ci->r_gain = 0; + ret = fwnode_property_read_u32(child, "adi,rfilter-ohms", + &ci->r_gain); + if (ret == 0 && ci->r_gain > AD7606_CALIB_GAIN_MAX) + return -EINVAL; + + return 0; + } + + return 0; +} + +static int ad7606c_18bit_chan_scale_setup(struct iio_dev *indio_dev, + struct iio_chan_spec *chan) +{ + struct ad7606_state *st = iio_priv(indio_dev); + struct ad7606_chan_info *ci = &st->chan_info[chan->scan_index]; + bool bipolar, differential; + int ret; + + if (!st->sw_mode_en) { + ci->range = 0; + ci->scale_avail = ad7606_18bit_hw_scale_avail; + ci->num_scales = ARRAY_SIZE(ad7606_18bit_hw_scale_avail); + return 0; + } + + ret = ad7606_get_chan_config(indio_dev, chan->scan_index, &bipolar, + &differential); + if (ret) + return ret; + + if (differential) { + ci->scale_avail = ad7606c_18bit_differential_bipolar_scale_avail; + ci->num_scales = + ARRAY_SIZE(ad7606c_18bit_differential_bipolar_scale_avail); + /* Bipolar differential ranges start at 8 (b1000) */ + ci->reg_offset = 8; + ci->range = 1; + chan->differential = 1; + chan->channel2 = chan->channel; + + return 0; + } + + chan->differential = 0; + + if (bipolar) { + ci->scale_avail = ad7606c_18bit_single_ended_bipolar_scale_avail; + ci->num_scales = + ARRAY_SIZE(ad7606c_18bit_single_ended_bipolar_scale_avail); + /* Bipolar single-ended ranges start at 0 (b0000) */ + ci->reg_offset = 0; + ci->range = 3; + chan->scan_type.sign = 's'; + + return 0; + } + + ci->scale_avail = ad7606c_18bit_single_ended_unipolar_scale_avail; + ci->num_scales = + ARRAY_SIZE(ad7606c_18bit_single_ended_unipolar_scale_avail); + /* Unipolar single-ended ranges start at 5 (b0101) */ + ci->reg_offset = 5; + ci->range = 1; + chan->scan_type.sign = 'u'; + + return 0; +} + +static int ad7606c_16bit_chan_scale_setup(struct iio_dev *indio_dev, + struct iio_chan_spec *chan) +{ + struct ad7606_state *st = iio_priv(indio_dev); + struct ad7606_chan_info *ci = &st->chan_info[chan->scan_index]; + bool bipolar, differential; + int ret; + + if (!st->sw_mode_en) { + ci->range = 0; + ci->scale_avail = ad7606_16bit_hw_scale_avail; + ci->num_scales = ARRAY_SIZE(ad7606_16bit_hw_scale_avail); + return 0; + } + + ret = ad7606_get_chan_config(indio_dev, chan->scan_index, &bipolar, + &differential); + if (ret) + return ret; + + if (differential) { + ci->scale_avail = ad7606c_16bit_differential_bipolar_scale_avail; + ci->num_scales = + ARRAY_SIZE(ad7606c_16bit_differential_bipolar_scale_avail); + /* Bipolar differential ranges start at 8 (b1000) */ + ci->reg_offset = 8; + ci->range = 1; + chan->differential = 1; + chan->channel2 = chan->channel; + chan->scan_type.sign = 's'; + + return 0; + } + + chan->differential = 0; + + if (bipolar) { + ci->scale_avail = ad7606c_16bit_single_ended_bipolar_scale_avail; + ci->num_scales = + ARRAY_SIZE(ad7606c_16bit_single_ended_bipolar_scale_avail); + /* Bipolar single-ended ranges start at 0 (b0000) */ + ci->reg_offset = 0; + ci->range = 3; + chan->scan_type.sign = 's'; + + return 0; + } + + ci->scale_avail = ad7606c_16bit_single_ended_unipolar_scale_avail; + ci->num_scales = + ARRAY_SIZE(ad7606c_16bit_single_ended_unipolar_scale_avail); + /* Unipolar single-ended ranges start at 5 (b0101) */ + ci->reg_offset = 5; + ci->range = 1; + chan->scan_type.sign = 'u'; + + return 0; +} + +static int ad7607_chan_scale_setup(struct iio_dev *indio_dev, + struct iio_chan_spec *chan) +{ + struct ad7606_state *st = iio_priv(indio_dev); + struct ad7606_chan_info *ci = &st->chan_info[chan->scan_index]; + + ci->range = 0; + ci->scale_avail = ad7607_hw_scale_avail; + ci->num_scales = ARRAY_SIZE(ad7607_hw_scale_avail); + return 0; +} + +static int ad7608_chan_scale_setup(struct iio_dev *indio_dev, + struct iio_chan_spec *chan) +{ + struct ad7606_state *st = iio_priv(indio_dev); + struct ad7606_chan_info *ci = &st->chan_info[chan->scan_index]; + + ci->range = 0; + ci->scale_avail = ad7606_18bit_hw_scale_avail; + ci->num_scales = ARRAY_SIZE(ad7606_18bit_hw_scale_avail); + return 0; +} + +static int ad7609_chan_scale_setup(struct iio_dev *indio_dev, + struct iio_chan_spec *chan) +{ + struct ad7606_state *st = iio_priv(indio_dev); + struct ad7606_chan_info *ci = &st->chan_info[chan->scan_index]; + + ci->range = 0; + ci->scale_avail = ad7609_hw_scale_avail; + ci->num_scales = ARRAY_SIZE(ad7609_hw_scale_avail); + 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; + + guard(mutex)(&st->lock); + + if (readval) { + ret = st->bops->reg_read(st, reg); + if (ret < 0) + return ret; + *readval = ret; + return 0; + } else { + return st->bops->reg_write(st, reg, writeval); + } +} + +static int ad7606_pwm_set_high(struct ad7606_state *st) +{ + struct pwm_state cnvst_pwm_state; + int ret; + + pwm_get_state(st->cnvst_pwm, &cnvst_pwm_state); + cnvst_pwm_state.enabled = true; + cnvst_pwm_state.duty_cycle = cnvst_pwm_state.period; + + ret = pwm_apply_might_sleep(st->cnvst_pwm, &cnvst_pwm_state); + + return ret; +} + +int ad7606_pwm_set_low(struct ad7606_state *st) +{ + struct pwm_state cnvst_pwm_state; + int ret; + + pwm_get_state(st->cnvst_pwm, &cnvst_pwm_state); + cnvst_pwm_state.enabled = true; + cnvst_pwm_state.duty_cycle = 0; + + ret = pwm_apply_might_sleep(st->cnvst_pwm, &cnvst_pwm_state); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(ad7606_pwm_set_low, "IIO_AD7606"); + +int ad7606_pwm_set_swing(struct ad7606_state *st) +{ + struct pwm_state cnvst_pwm_state; + + pwm_get_state(st->cnvst_pwm, &cnvst_pwm_state); + cnvst_pwm_state.enabled = true; + cnvst_pwm_state.duty_cycle = cnvst_pwm_state.period / 2; + + return pwm_apply_might_sleep(st->cnvst_pwm, &cnvst_pwm_state); +} +EXPORT_SYMBOL_NS_GPL(ad7606_pwm_set_swing, "IIO_AD7606"); + +static bool ad7606_pwm_is_swinging(struct ad7606_state *st) +{ + struct pwm_state cnvst_pwm_state; + + pwm_get_state(st->cnvst_pwm, &cnvst_pwm_state); + + return cnvst_pwm_state.duty_cycle != cnvst_pwm_state.period && + cnvst_pwm_state.duty_cycle != 0; +} + +static int ad7606_set_sampling_freq(struct ad7606_state *st, unsigned long freq) +{ + struct pwm_state cnvst_pwm_state; + bool is_swinging = ad7606_pwm_is_swinging(st); + bool is_high; + + if (freq == 0) + return -EINVAL; + + /* Retrieve the previous state. */ + pwm_get_state(st->cnvst_pwm, &cnvst_pwm_state); + is_high = cnvst_pwm_state.duty_cycle == cnvst_pwm_state.period; + + cnvst_pwm_state.period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, freq); + cnvst_pwm_state.polarity = PWM_POLARITY_NORMAL; + if (is_high) + cnvst_pwm_state.duty_cycle = cnvst_pwm_state.period; + else if (is_swinging) + cnvst_pwm_state.duty_cycle = cnvst_pwm_state.period / 2; + else + cnvst_pwm_state.duty_cycle = 0; + + return pwm_apply_might_sleep(st->cnvst_pwm, &cnvst_pwm_state); +} + +static int ad7606_read_samples(struct ad7606_state *st) +{ + unsigned int num = st->chip_info->num_adc_channels; + + return st->bops->read_block(st->dev, num, &st->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); + int ret; + + guard(mutex)(&st->lock); + + ret = ad7606_read_samples(st); + if (ret) + goto error_ret; + + iio_push_to_buffers_with_ts(indio_dev, &st->data, sizeof(st->data), + iio_get_time_ns(indio_dev)); +error_ret: + iio_trigger_notify_done(indio_dev->trig); + /* The rising edge of the CONVST signal starts a new conversion. */ + gpiod_set_value(st->gpio_convst, 1); + + return IRQ_HANDLED; +} + +static int ad7606_scan_direct(struct iio_dev *indio_dev, unsigned int ch, + int *val) +{ + struct ad7606_state *st = iio_priv(indio_dev); + const struct iio_chan_spec *chan; + unsigned int realbits; + int ret; + + if (st->gpio_convst) { + gpiod_set_value(st->gpio_convst, 1); + } else { + ret = ad7606_pwm_set_high(st); + if (ret < 0) + return ret; + } + + /* + * If no backend, wait for the interruption on busy pin, otherwise just add + * a delay to leave time for the data to be available. For now, the latter + * will not happen because IIO_CHAN_INFO_RAW is not supported for the backend. + * TODO: Add support for reading a single value when the backend is used. + */ + if (st->trig) { + ret = wait_for_completion_timeout(&st->completion, + msecs_to_jiffies(1000)); + if (!ret) { + ret = -ETIMEDOUT; + goto error_ret; + } + } else { + /* + * If the BUSY interrupt is not available, wait enough time for + * the longest possible conversion (max for the whole family is + * around 350us). + */ + fsleep(400); + } + + ret = ad7606_read_samples(st); + if (ret) + goto error_ret; + + chan = &indio_dev->channels[ch]; + realbits = chan->scan_type.realbits; + + if (realbits > 16) + *val = st->data.buf32[ch]; + else + *val = st->data.buf16[ch]; + + *val &= GENMASK(realbits - 1, 0); + + if (chan->scan_type.sign == 's') + *val = sign_extend32(*val, realbits - 1); + +error_ret: + if (!st->gpio_convst) { + ret = ad7606_pwm_set_low(st); + if (ret < 0) + return ret; + } + gpiod_set_value(st->gpio_convst, 0); + + return ret; +} + +static int ad7606_get_calib_offset(struct ad7606_state *st, int ch, int *val) +{ + int ret; + + ret = st->bops->reg_read(st, AD7606_CALIB_OFFSET(ch)); + if (ret < 0) + return ret; + + *val = st->chip_info->calib_offset_avail[0] + + ret * st->chip_info->calib_offset_avail[1]; + + return 0; +} + +static int ad7606_get_calib_phase(struct ad7606_state *st, int ch, int *val, + int *val2) +{ + int ret; + + ret = st->bops->reg_read(st, AD7606_CALIB_PHASE(ch)); + if (ret < 0) + return ret; + + *val = 0; + + /* + * ad7606b: phase delay from 0 to 318.75 μs in steps of 1.25 μs. + * ad7606c-16/18: phase delay from 0 µs to 255 µs in steps of 1 µs. + */ + *val2 = ret * st->chip_info->calib_phase_avail[1][1]; + + return 0; +} + +static int ad7606_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret, ch = 0; + struct ad7606_state *st = iio_priv(indio_dev); + struct ad7606_chan_info *ci; + struct pwm_state cnvst_pwm_state; + + switch (m) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = ad7606_scan_direct(indio_dev, chan->scan_index, val); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (st->sw_mode_en) + ch = chan->scan_index; + ci = &st->chan_info[ch]; + *val = ci->scale_avail[ci->range][0]; + *val2 = ci->scale_avail[ci->range][1]; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = st->oversampling; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + pwm_get_state(st->cnvst_pwm, &cnvst_pwm_state); + *val = DIV_ROUND_CLOSEST_ULL(NSEC_PER_SEC, cnvst_pwm_state.period); + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = ad7606_get_calib_offset(st, chan->scan_index, val); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CONVDELAY: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = ad7606_get_calib_phase(st, chan->scan_index, val, val2); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + return IIO_VAL_INT_PLUS_NANO; + } + return -EINVAL; +} + +static ssize_t in_voltage_scale_available_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7606_state *st = iio_priv(indio_dev); + struct ad7606_chan_info *ci = &st->chan_info[0]; + const unsigned int (*vals)[2] = ci->scale_avail; + unsigned int i; + size_t len = 0; + + for (i = 0; i < ci->num_scales; i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "%u.%06u ", + vals[i][0], vals[i][1]); + buf[len - 1] = '\n'; + + return len; +} + +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; +} + +static int ad7606_write_os_hw(struct iio_dev *indio_dev, int val) +{ + struct ad7606_state *st = iio_priv(indio_dev); + DECLARE_BITMAP(values, 3); + + values[0] = val & GENMASK(2, 0); + + gpiod_multi_set_value_cansleep(st->gpio_os, values); + + /* AD7616 requires a reset to update value */ + if (st->chip_info->os_req_reset) + ad7606_reset(st); + + return 0; +} + +static int ad7606_set_calib_offset(struct ad7606_state *st, int ch, int val) +{ + int start_val, step_val, stop_val; + int offset; + + start_val = st->chip_info->calib_offset_avail[0]; + step_val = st->chip_info->calib_offset_avail[1]; + stop_val = st->chip_info->calib_offset_avail[2]; + + if (val < start_val || val > stop_val) + return -EINVAL; + + offset = (val - start_val) / step_val; + + return st->bops->reg_write(st, AD7606_CALIB_OFFSET(ch), offset); +} + +static int ad7606_set_calib_phase(struct ad7606_state *st, int ch, int val, + int val2) +{ + int wreg, start_ns, step_ns, stop_ns; + + if (val != 0) + return -EINVAL; + + start_ns = st->chip_info->calib_phase_avail[0][1]; + step_ns = st->chip_info->calib_phase_avail[1][1]; + stop_ns = st->chip_info->calib_phase_avail[2][1]; + + /* + * ad7606b: phase delay from 0 to 318.75 μs in steps of 1.25 μs. + * ad7606c-16/18: phase delay from 0 µs to 255 µs in steps of 1 µs. + */ + if (val2 < start_ns || val2 > stop_ns) + return -EINVAL; + + wreg = val2 / step_ns; + + return st->bops->reg_write(st, AD7606_CALIB_PHASE(ch), wreg); +} + +static int ad7606_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long info) +{ + switch (info) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + case IIO_CHAN_INFO_CALIBBIAS: + return IIO_VAL_INT; + case IIO_CHAN_INFO_CONVDELAY: + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static int ad7606_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad7606_state *st = iio_priv(indio_dev); + unsigned int scale_avail_uv[AD760X_MAX_SCALES]; + struct ad7606_chan_info *ci; + int i, ret, ch = 0; + + guard(mutex)(&st->lock); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (st->sw_mode_en) + ch = chan->scan_index; + ci = &st->chan_info[ch]; + for (i = 0; i < ci->num_scales; i++) { + scale_avail_uv[i] = ci->scale_avail[i][0] * MICRO + + ci->scale_avail[i][1]; + } + val = (val * MICRO) + val2; + i = find_closest(val, scale_avail_uv, ci->num_scales); + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = st->write_scale(indio_dev, ch, i + ci->reg_offset); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + ci->range = i; + + return 0; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + if (val2) + return -EINVAL; + i = find_closest(val, st->oversampling_avail, + st->num_os_ratios); + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = st->write_os(indio_dev, i); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + st->oversampling = st->oversampling_avail[i]; + + return 0; + case IIO_CHAN_INFO_SAMP_FREQ: + if (val < 0 && val2 != 0) + return -EINVAL; + return ad7606_set_sampling_freq(st, val); + case IIO_CHAN_INFO_CALIBBIAS: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = ad7606_set_calib_offset(st, chan->scan_index, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_CONVDELAY: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = ad7606_set_calib_phase(st, chan->scan_index, val, val2); + iio_device_release_direct(indio_dev); + return ret; + default: + return -EINVAL; + } +} + +static ssize_t ad7606_oversampling_ratio_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7606_state *st = iio_priv(indio_dev); + const unsigned int *vals = st->oversampling_avail; + unsigned int i; + size_t len = 0; + + for (i = 0; i < st->num_os_ratios; i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "%u ", vals[i]); + buf[len - 1] = '\n'; + + return len; +} + +static IIO_DEVICE_ATTR(oversampling_ratio_available, 0444, + ad7606_oversampling_ratio_avail, NULL, 0); + +static struct attribute *ad7606_attributes_os_and_range[] = { + &iio_dev_attr_in_voltage_scale_available.dev_attr.attr, + &iio_dev_attr_oversampling_ratio_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad7606_attribute_group_os_and_range = { + .attrs = ad7606_attributes_os_and_range, +}; + +static struct attribute *ad7606_attributes_os[] = { + &iio_dev_attr_oversampling_ratio_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad7606_attribute_group_os = { + .attrs = ad7606_attributes_os, +}; + +static struct attribute *ad7606_attributes_range[] = { + &iio_dev_attr_in_voltage_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad7606_attribute_group_range = { + .attrs = ad7606_attributes_range, +}; + +static int ad7606_request_gpios(struct ad7606_state *st) +{ + struct device *dev = st->dev; + + st->gpio_convst = devm_gpiod_get_optional(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); + + st->gpio_range = devm_gpiod_get_optional(dev, "adi,range", + GPIOD_OUT_LOW); + if (IS_ERR(st->gpio_range)) + return PTR_ERR(st->gpio_range); + + st->gpio_standby = devm_gpiod_get_optional(dev, "standby", + GPIOD_OUT_LOW); + if (IS_ERR(st->gpio_standby)) + return PTR_ERR(st->gpio_standby); + + st->gpio_frstdata = devm_gpiod_get_optional(dev, "adi,first-data", + GPIOD_IN); + if (IS_ERR(st->gpio_frstdata)) + return PTR_ERR(st->gpio_frstdata); + + if (!st->chip_info->oversampling_num) + return 0; + + st->gpio_os = devm_gpiod_get_array_optional(dev, + "adi,oversampling-ratio", + GPIOD_OUT_LOW); + return PTR_ERR_OR_ZERO(st->gpio_os); +} + +/* + * The BUSY signal indicates when conversions are in progress, so when a rising + * edge of CONVST is applied, BUSY goes logic high and transitions low at the + * end of the entire conversion process. The falling edge of the BUSY signal + * triggers this interrupt. + */ +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); + int ret; + + if (iio_buffer_enabled(indio_dev)) { + if (st->gpio_convst) { + gpiod_set_value(st->gpio_convst, 0); + } else { + ret = ad7606_pwm_set_low(st); + if (ret < 0) { + dev_err(st->dev, "PWM set low failed"); + goto done; + } + } + iio_trigger_poll_nested(st->trig); + } else { + complete(&st->completion); + } + +done: + return IRQ_HANDLED; +}; + +static int ad7606_validate_trigger(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + struct ad7606_state *st = iio_priv(indio_dev); + + if (st->trig != trig) + return -EINVAL; + + return 0; +} + +static int ad7606_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad7606_state *st = iio_priv(indio_dev); + + gpiod_set_value(st->gpio_convst, 1); + + return 0; +} + +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 int ad7606_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long info) +{ + struct ad7606_state *st = iio_priv(indio_dev); + struct ad7606_chan_info *ci; + unsigned int ch = 0; + + switch (info) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = st->oversampling_avail; + *length = st->num_os_ratios; + *type = IIO_VAL_INT; + + return IIO_AVAIL_LIST; + + case IIO_CHAN_INFO_SCALE: + if (st->sw_mode_en) + ch = chan->scan_index; + + ci = &st->chan_info[ch]; + *vals = (int *)ci->scale_avail; + *length = ci->num_scales * 2; + *type = IIO_VAL_INT_PLUS_MICRO; + + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_CALIBBIAS: + *vals = st->chip_info->calib_offset_avail; + *type = IIO_VAL_INT; + return IIO_AVAIL_RANGE; + case IIO_CHAN_INFO_CONVDELAY: + *vals = (const int *)st->chip_info->calib_phase_avail; + *type = IIO_VAL_INT_PLUS_NANO; + return IIO_AVAIL_RANGE; + } + return -EINVAL; +} + +static int ad7606_backend_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad7606_state *st = iio_priv(indio_dev); + + return ad7606_pwm_set_swing(st); +} + +static int ad7606_backend_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad7606_state *st = iio_priv(indio_dev); + + return ad7606_pwm_set_low(st); +} + +static int ad7606_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ad7606_state *st = iio_priv(indio_dev); + + /* + * The update scan mode is only for iio backend compatible drivers. + * If the specific update_scan_mode is not defined in the bus ops, + * just do nothing and return 0. + */ + if (!st->bops->update_scan_mode) + return 0; + + return st->bops->update_scan_mode(indio_dev, scan_mask); +} + +static const struct iio_buffer_setup_ops ad7606_buffer_ops = { + .postenable = &ad7606_buffer_postenable, + .predisable = &ad7606_buffer_predisable, +}; + +static const struct iio_buffer_setup_ops ad7606_backend_buffer_ops = { + .postenable = &ad7606_backend_buffer_postenable, + .predisable = &ad7606_backend_buffer_predisable, +}; + +static const struct iio_info ad7606_info_no_os_or_range = { + .read_raw = &ad7606_read_raw, + .validate_trigger = &ad7606_validate_trigger, + .update_scan_mode = &ad7606_update_scan_mode, +}; + +static const struct iio_info ad7606_info_os_and_range = { + .read_raw = &ad7606_read_raw, + .write_raw = &ad7606_write_raw, + .attrs = &ad7606_attribute_group_os_and_range, + .validate_trigger = &ad7606_validate_trigger, + .update_scan_mode = &ad7606_update_scan_mode, +}; + +static const struct iio_info ad7606_info_sw_mode = { + .read_raw = &ad7606_read_raw, + .write_raw = &ad7606_write_raw, + .read_avail = &ad7606_read_avail, + .write_raw_get_fmt = ad7606_write_raw_get_fmt, + .debugfs_reg_access = &ad7606_reg_access, + .validate_trigger = &ad7606_validate_trigger, + .update_scan_mode = &ad7606_update_scan_mode, +}; + +static const struct iio_info ad7606_info_os = { + .read_raw = &ad7606_read_raw, + .write_raw = &ad7606_write_raw, + .attrs = &ad7606_attribute_group_os, + .validate_trigger = &ad7606_validate_trigger, + .update_scan_mode = &ad7606_update_scan_mode, +}; + +static const struct iio_info ad7606_info_range = { + .read_raw = &ad7606_read_raw, + .write_raw = &ad7606_write_raw, + .attrs = &ad7606_attribute_group_range, + .validate_trigger = &ad7606_validate_trigger, + .update_scan_mode = &ad7606_update_scan_mode, +}; + +static const struct iio_trigger_ops ad7606_trigger_ops = { + .validate_device = iio_trigger_validate_own_device, +}; + +static int ad7606_write_mask(struct ad7606_state *st, unsigned int addr, + unsigned long mask, unsigned int val) +{ + int readval; + + readval = st->bops->reg_read(st, addr); + if (readval < 0) + return readval; + + readval &= ~mask; + readval |= val; + + return st->bops->reg_write(st, addr, readval); +} + +static int ad7616_write_scale_sw(struct iio_dev *indio_dev, int ch, int val) +{ + struct ad7606_state *st = iio_priv(indio_dev); + unsigned int ch_addr, mode, ch_index; + + /* + * Ad7616 has 16 channels divided in group A and group B. + * The range of channels from A are stored in registers with address 4 + * while channels from B are stored in register with address 6. + * The last bit from channels determines if it is from group A or B + * because the order of channels in iio is 0A, 0B, 1A, 1B... + */ + ch_index = ch >> 1; + + ch_addr = AD7616_RANGE_CH_ADDR(ch_index); + + if ((ch & 0x1) == 0) /* channel A */ + ch_addr += AD7616_RANGE_CH_A_ADDR_OFF; + else /* channel B */ + ch_addr += AD7616_RANGE_CH_B_ADDR_OFF; + + /* 0b01 for 2.5v, 0b10 for 5v and 0b11 for 10v */ + mode = AD7616_RANGE_CH_MODE(ch_index, ((val + 1) & 0b11)); + + return ad7606_write_mask(st, ch_addr, AD7616_RANGE_CH_MSK(ch_index), + mode); +} + +static int ad7616_write_os_sw(struct iio_dev *indio_dev, int val) +{ + struct ad7606_state *st = iio_priv(indio_dev); + + return ad7606_write_mask(st, AD7616_CONFIGURATION_REGISTER, + AD7616_OS_MASK, val << 2); +} + +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_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 st->bops->reg_write(st, AD7606_OS_MODE, val); +} + +static int ad7616_sw_mode_setup(struct iio_dev *indio_dev) +{ + struct ad7606_state *st = iio_priv(indio_dev); + int ret; + + /* + * Scale can be configured individually for each channel + * in software mode. + */ + + st->write_scale = ad7616_write_scale_sw; + st->write_os = &ad7616_write_os_sw; + + if (st->bops->sw_mode_config) { + ret = st->bops->sw_mode_config(indio_dev); + if (ret) + return ret; + } + + /* Activate Burst mode and SEQEN MODE */ + return ad7606_write_mask(st, AD7616_CONFIGURATION_REGISTER, + AD7616_BURST_MODE | AD7616_SEQEN_MODE, + AD7616_BURST_MODE | AD7616_SEQEN_MODE); +} + +static int ad7606b_sw_mode_setup(struct iio_dev *indio_dev) +{ + struct ad7606_state *st = iio_priv(indio_dev); + DECLARE_BITMAP(os, 3); + + bitmap_fill(os, 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) + gpiod_multi_set_value_cansleep(st->gpio_os, os); + + /* OS of 128 and 256 are available only in software mode */ + st->oversampling_avail = ad7606b_oversampling_avail; + st->num_os_ratios = ARRAY_SIZE(ad7606b_oversampling_avail); + + st->write_scale = ad7606_write_scale_sw; + st->write_os = &ad7606_write_os_sw; + + if (!st->bops->sw_mode_config) + return 0; + + return st->bops->sw_mode_config(indio_dev); +} + +static int ad7606_set_gain_calib(struct ad7606_state *st) +{ + struct ad7606_chan_info *ci; + int i, ret; + + for (i = 0; i < st->chip_info->num_adc_channels; i++) { + ci = &st->chan_info[i]; + ret = st->bops->reg_write(st, AD7606_CALIB_GAIN(i), + DIV_ROUND_CLOSEST(ci->r_gain, + AD7606_CALIB_GAIN_STEP)); + if (ret) + return ret; + } + + return 0; +} + +static int ad7606_probe_channels(struct iio_dev *indio_dev) +{ + struct ad7606_state *st = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + struct iio_chan_spec *channels; + bool slow_bus; + int ret, i; + + slow_bus = !(st->bops->iio_backend_config || st->offload_en); + indio_dev->num_channels = st->chip_info->num_adc_channels; + + /* Slow buses also get 1 more channel for soft timestamp */ + if (slow_bus) + indio_dev->num_channels++; + + channels = devm_kcalloc(dev, indio_dev->num_channels, sizeof(*channels), + GFP_KERNEL); + if (!channels) + return -ENOMEM; + + for (i = 0; i < st->chip_info->num_adc_channels; i++) { + struct iio_chan_spec *chan = &channels[i]; + + chan->type = IIO_VOLTAGE; + chan->indexed = 1; + chan->channel = i; + chan->scan_index = i; + chan->scan_type.sign = 's'; + chan->scan_type.realbits = st->chip_info->bits; + /* + * If in SPI offload mode, storagebits are set based + * on the spi-engine hw implementation. + */ + chan->scan_type.storagebits = st->offload_en ? + st->chip_info->offload_storagebits : + (st->chip_info->bits > 16 ? 32 : 16); + + chan->scan_type.endianness = IIO_CPU; + + if (indio_dev->modes & INDIO_DIRECT_MODE) + chan->info_mask_separate |= BIT(IIO_CHAN_INFO_RAW); + + if (st->sw_mode_en) { + chan->info_mask_separate |= BIT(IIO_CHAN_INFO_SCALE); + chan->info_mask_separate_available |= + BIT(IIO_CHAN_INFO_SCALE); + + if (st->chip_info->calib_offset_avail) { + chan->info_mask_separate |= + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_CONVDELAY); + chan->info_mask_separate_available |= + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_CONVDELAY); + } + + /* + * All chips with software mode support oversampling, + * so we skip the oversampling_available check. And the + * shared_by_type instead of shared_by_all on slow + * buses is for backward compatibility. + */ + if (slow_bus) + chan->info_mask_shared_by_type |= + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO); + else + chan->info_mask_shared_by_all |= + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO); + + chan->info_mask_shared_by_all_available |= + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO); + } else { + chan->info_mask_shared_by_type |= + BIT(IIO_CHAN_INFO_SCALE); + + if (st->chip_info->oversampling_avail) + chan->info_mask_shared_by_all |= + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO); + } + + if (!slow_bus) + chan->info_mask_shared_by_all |= + BIT(IIO_CHAN_INFO_SAMP_FREQ); + + ret = st->chip_info->scale_setup_cb(indio_dev, chan); + if (ret) + return ret; + } + + if (slow_bus) + channels[i] = IIO_CHAN_SOFT_TIMESTAMP(i); + + indio_dev->channels = channels; + + return 0; +} + +static void ad7606_pwm_disable(void *data) +{ + pwm_disable(data); +} + +int ad7606_probe(struct device *dev, int irq, void __iomem *base_address, + const struct ad7606_chip_info *chip_info, + const struct ad7606_bus_ops *bops) +{ + 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); + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + st->dev = dev; + st->bops = bops; + st->base_address = base_address; + st->oversampling = 1; + st->sw_mode_en = device_property_read_bool(dev, "adi,sw-mode"); + + if (st->sw_mode_en && !chip_info->sw_setup_cb) + return dev_err_probe(dev, -EINVAL, + "Software mode is not supported for this chip\n"); + + ret = devm_regulator_get_enable(dev, "avcc"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable specified AVcc supply\n"); + + ret = devm_regulator_get_enable(dev, "vdrive"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable Vdrive supply\n"); + + ret = devm_regulator_get_enable_optional(dev, "refin"); + if (ret && ret != -ENODEV) + return dev_err_probe(dev, ret, + "Failed to enable REFIN supply\n"); + + st->chip_info = chip_info; + + if (st->chip_info->oversampling_num) { + st->oversampling_avail = st->chip_info->oversampling_avail; + st->num_os_ratios = st->chip_info->oversampling_num; + } + + ret = ad7606_request_gpios(st); + if (ret) + return ret; + + if (st->gpio_os) { + if (st->gpio_range) + indio_dev->info = &ad7606_info_os_and_range; + else + indio_dev->info = &ad7606_info_os; + } else { + if (st->gpio_range) + indio_dev->info = &ad7606_info_range; + else + indio_dev->info = &ad7606_info_no_os_or_range; + } + + /* AXI ADC backend doesn't support single read. */ + indio_dev->modes = st->bops->iio_backend_config ? 0 : INDIO_DIRECT_MODE; + indio_dev->name = chip_info->name; + + /* Using spi-engine with offload support ? */ + if (st->bops->offload_config) { + ret = st->bops->offload_config(dev, indio_dev); + if (ret) + return ret; + } + + ret = ad7606_probe_channels(indio_dev); + if (ret) + return ret; + + ret = ad7606_reset(st); + if (ret) + dev_warn(st->dev, "failed to RESET: no RESET GPIO specified\n"); + + /* AD7616 requires al least 15ms to reconfigure after a reset */ + if (st->chip_info->init_delay_ms) { + if (msleep_interruptible(st->chip_info->init_delay_ms)) + return -ERESTARTSYS; + } + + /* If convst pin is not defined, setup PWM. */ + if (!st->gpio_convst || st->offload_en) { + st->cnvst_pwm = devm_pwm_get(dev, NULL); + if (IS_ERR(st->cnvst_pwm)) + return PTR_ERR(st->cnvst_pwm); + + /* The PWM is initialized at 1MHz to have a fast enough GPIO emulation. */ + ret = ad7606_set_sampling_freq(st, 1 * MEGA); + if (ret) + return ret; + + ret = ad7606_pwm_set_low(st); + if (ret) + return ret; + + /* + * PWM is not disabled when sampling stops, but instead its duty cycle is set + * to 0% to be sure we have a "low" state. After we unload the driver, let's + * disable the PWM. + */ + ret = devm_add_action_or_reset(dev, ad7606_pwm_disable, + st->cnvst_pwm); + if (ret) + return ret; + } + + if (st->bops->iio_backend_config) { + /* + * If there is a backend, the PWM should not overpass the maximum sampling + * frequency the chip supports. + */ + ret = ad7606_set_sampling_freq(st, chip_info->max_samplerate); + if (ret) + return ret; + + ret = st->bops->iio_backend_config(dev, indio_dev); + if (ret) + return ret; + + indio_dev->setup_ops = &ad7606_backend_buffer_ops; + } else if (!st->offload_en) { + /* Reserve the PWM use only for backend (force gpio_convst definition) */ + if (!st->gpio_convst) + return dev_err_probe(dev, -EINVAL, + "No backend, connect convst to a GPIO"); + + init_completion(&st->completion); + st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!st->trig) + return -ENOMEM; + + st->trig->ops = &ad7606_trigger_ops; + 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, + chip_info->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; + } + + st->write_scale = ad7606_write_scale_hw; + st->write_os = ad7606_write_os_hw; + + /* Offload needs 1 DOUT line, applying this setting in sw_setup_cb. */ + if (st->sw_mode_en || st->offload_en) { + indio_dev->info = &ad7606_info_sw_mode; + st->chip_info->sw_setup_cb(indio_dev); + } + + if (st->sw_mode_en && st->chip_info->calib_gain_avail) { + ret = ad7606_set_gain_calib(st); + if (ret) + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS_GPL(ad7606_probe, "IIO_AD7606"); + +#ifdef CONFIG_PM_SLEEP + +static int ad7606_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7606_state *st = iio_priv(indio_dev); + + if (st->gpio_standby) { + gpiod_set_value(st->gpio_range, 1); + gpiod_set_value(st->gpio_standby, 1); + } + + return 0; +} + +static int ad7606_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7606_state *st = iio_priv(indio_dev); + + if (st->gpio_standby) { + gpiod_set_value(st->gpio_range, st->chan_info[0].range); + gpiod_set_value(st->gpio_standby, 1); + ad7606_reset(st); + } + + return 0; +} + +SIMPLE_DEV_PM_OPS(ad7606_pm_ops, ad7606_suspend, ad7606_resume); +EXPORT_SYMBOL_NS_GPL(ad7606_pm_ops, "IIO_AD7606"); + +#endif + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7606 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ad7606.h b/drivers/iio/adc/ad7606.h new file mode 100644 index 000000000..2951bb731 --- /dev/null +++ b/drivers/iio/adc/ad7606.h @@ -0,0 +1,251 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * AD7606 ADC driver + * + * Copyright 2011 Analog Devices Inc. + */ + +#ifndef IIO_ADC_AD7606_H_ +#define IIO_ADC_AD7606_H_ + +#define AD760X_MAX_CHANNELS 16 + +#define AD7616_CONFIGURATION_REGISTER 0x02 +#define AD7616_OS_MASK GENMASK(4, 2) +#define AD7616_BURST_MODE BIT(6) +#define AD7616_SEQEN_MODE BIT(5) +#define AD7616_RANGE_CH_A_ADDR_OFF 0x04 +#define AD7616_RANGE_CH_B_ADDR_OFF 0x06 +/* + * Range of channels from a group are stored in 2 registers. + * 0, 1, 2, 3 in a register followed by 4, 5, 6, 7 in second register. + * For channels from second group(8-15) the order is the same, only with + * an offset of 2 for register address. + */ +#define AD7616_RANGE_CH_ADDR(ch) ((ch) >> 2) +/* The range of the channel is stored in 2 bits */ +#define AD7616_RANGE_CH_MSK(ch) (0b11 << (((ch) & 0b11) * 2)) +#define AD7616_RANGE_CH_MODE(ch, mode) ((mode) << ((((ch) & 0b11)) * 2)) + +#define AD7606_CONFIGURATION_REGISTER 0x02 +#define AD7606_SINGLE_DOUT 0x00 + +/* + * Range for AD7606B channels are stored in registers starting with address 0x3. + * Each register stores range for 2 channels(4 bits per channel). + */ +#define AD7606_RANGE_CH_MSK(ch) (GENMASK(3, 0) << (4 * ((ch) & 0x1))) +#define AD7606_RANGE_CH_MODE(ch, mode) \ + ((GENMASK(3, 0) & (mode)) << (4 * ((ch) & 0x1))) +#define AD7606_RANGE_CH_ADDR(ch) (0x03 + ((ch) >> 1)) +#define AD7606_OS_MODE 0x08 + +#define AD7606_CALIB_GAIN(ch) (0x09 + (ch)) +#define AD7606_CALIB_GAIN_MASK GENMASK(5, 0) +#define AD7606_CALIB_OFFSET(ch) (0x11 + (ch)) +#define AD7606_CALIB_PHASE(ch) (0x19 + (ch)) + +struct ad7606_state; + +typedef int (*ad7606_scale_setup_cb_t)(struct iio_dev *indio_dev, + struct iio_chan_spec *chan); +typedef int (*ad7606_sw_setup_cb_t)(struct iio_dev *indio_dev); + +/** + * struct ad7606_chip_info - chip specific information + * @max_samplerate: maximum supported sample rate + * @name: device name + * @bits: data width in bits + * @num_adc_channels: the number of physical voltage inputs + * @scale_setup_cb: callback to setup the scales for each channel + * @sw_setup_cb: callback to setup the software mode if available. + * @oversampling_avail: pointer to the array which stores the available + * oversampling ratios. + * @oversampling_num: number of elements stored in oversampling_avail array + * @os_req_reset: some devices require a reset to update oversampling + * @init_delay_ms: required delay in milliseconds for initialization + * after a restart + * @offload_storagebits: storage bits used by the offload hw implementation + * @calib_gain_avail: chip supports gain calibration + * @calib_offset_avail: pointer to offset calibration range/limits array + * @calib_phase_avail: pointer to phase calibration range/limits array + */ +struct ad7606_chip_info { + unsigned int max_samplerate; + const char *name; + unsigned int bits; + unsigned int num_adc_channels; + ad7606_scale_setup_cb_t scale_setup_cb; + ad7606_sw_setup_cb_t sw_setup_cb; + const unsigned int *oversampling_avail; + unsigned int oversampling_num; + bool os_req_reset; + unsigned long init_delay_ms; + u8 offload_storagebits; + bool calib_gain_avail; + const int *calib_offset_avail; + const int (*calib_phase_avail)[2]; +}; + +/** + * struct ad7606_chan_info - channel configuration + * @scale_avail: pointer to the array which stores the available scales + * @num_scales: number of elements stored in the scale_avail array + * @range: voltage range selection, selects which scale to apply + * @reg_offset: offset for the register value, to be applied when + * writing the value of 'range' to the register value + * @r_gain: gain resistor value in ohms, to be set to match the + * external r_filter value + */ +struct ad7606_chan_info { +#define AD760X_MAX_SCALES 16 + const unsigned int (*scale_avail)[2]; + unsigned int num_scales; + unsigned int range; + unsigned int reg_offset; + unsigned int r_gain; +}; + +/** + * struct ad7606_state - driver instance specific data + * @dev: pointer to kernel device + * @chip_info: entry in the table of chips that describes this device + * @bops: bus operations (SPI or parallel) + * @chan_info: scale configuration for channels + * @oversampling: oversampling selection + * @cnvst_pwm: pointer to the PWM device connected to the cnvst pin + * @base_address: address from where to read data in parallel operation + * @sw_mode_en: software mode enabled + * @oversampling_avail: pointer to the array which stores the available + * oversampling ratios. + * @num_os_ratios: number of elements stored in oversampling_avail array + * @back: pointer to the iio_backend structure, if used + * @write_scale: pointer to the function which writes the scale + * @write_os: pointer to the function which writes the os + * @lock: protect sensor state from concurrent accesses to GPIOs + * @gpio_convst: GPIO descriptor for conversion start signal (CONVST) + * @gpio_reset: GPIO descriptor for device hard-reset + * @gpio_range: GPIO descriptor for range selection + * @gpio_standby: GPIO descriptor for stand-by signal (STBY), + * controls power-down mode of device + * @gpio_frstdata: GPIO descriptor for reading from device when data + * is being read on the first channel + * @gpio_os: GPIO descriptors to control oversampling on the device + * @trig: The IIO trigger associated with the device. + * @completion: completion to indicate end of conversion + * @data: buffer for reading data from the device + * @offload_en: SPI offload enabled + * @bus_data: bus-specific variables + * @d16: be16 buffer for reading data from the device + */ +struct ad7606_state { + struct device *dev; + const struct ad7606_chip_info *chip_info; + const struct ad7606_bus_ops *bops; + struct ad7606_chan_info chan_info[AD760X_MAX_CHANNELS]; + unsigned int oversampling; + struct pwm_device *cnvst_pwm; + void __iomem *base_address; + bool sw_mode_en; + const unsigned int *oversampling_avail; + unsigned int num_os_ratios; + struct iio_backend *back; + int (*write_scale)(struct iio_dev *indio_dev, int ch, int val); + int (*write_os)(struct iio_dev *indio_dev, int val); + + struct mutex lock; /* protect sensor state */ + struct gpio_desc *gpio_convst; + struct gpio_desc *gpio_reset; + struct gpio_desc *gpio_range; + struct gpio_desc *gpio_standby; + struct gpio_desc *gpio_frstdata; + struct gpio_descs *gpio_os; + struct iio_trigger *trig; + struct completion completion; + + bool offload_en; + void *bus_data; + + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + * 16 * 16-bit samples for AD7616 + * 8 * 32-bit samples for AD7616C-18 (and similar) + */ + struct { + union { + u16 buf16[16]; + u32 buf32[8]; + }; + aligned_s64 timestamp; + } data __aligned(IIO_DMA_MINALIGN); + __be16 d16[2]; +}; + +/** + * struct ad7606_bus_ops - driver bus operations + * @iio_backend_config: function pointer for configuring the iio_backend for + * the compatibles that use it + * @read_block: function pointer for reading blocks of data + * @sw_mode_config: pointer to a function which configured the device + * for software mode + * @offload_config: function pointer for configuring offload support, + * where any + * @reg_read: function pointer for reading spi register + * @reg_write: function pointer for writing spi register + * @update_scan_mode: function pointer for handling the calls to iio_info's + * update_scan mode when enabling/disabling channels. + * @rd_wr_cmd: pointer to the function which calculates the spi address + */ +struct ad7606_bus_ops { + /* more methods added in future? */ + int (*iio_backend_config)(struct device *dev, struct iio_dev *indio_dev); + int (*offload_config)(struct device *dev, struct iio_dev *indio_dev); + int (*read_block)(struct device *dev, int num, void *data); + int (*sw_mode_config)(struct iio_dev *indio_dev); + int (*reg_read)(struct ad7606_state *st, unsigned int addr); + int (*reg_write)(struct ad7606_state *st, + unsigned int addr, + unsigned int val); + int (*update_scan_mode)(struct iio_dev *indio_dev, const unsigned long *scan_mask); + u16 (*rd_wr_cmd)(int addr, char is_write_op); +}; + +/** + * struct ad7606_bus_info - aggregate ad7606_chip_info and ad7606_bus_ops + * @chip_info: entry in the table of chips that describes this device + * @bops: bus operations (SPI or parallel) + */ +struct ad7606_bus_info { + const struct ad7606_chip_info *chip_info; + const struct ad7606_bus_ops *bops; +}; + +int ad7606_probe(struct device *dev, int irq, void __iomem *base_address, + const struct ad7606_chip_info *info, + const struct ad7606_bus_ops *bops); + +int ad7606_reset(struct ad7606_state *st); +int ad7606_pwm_set_swing(struct ad7606_state *st); +int ad7606_pwm_set_low(struct ad7606_state *st); + +extern const struct ad7606_chip_info ad7605_4_info; +extern const struct ad7606_chip_info ad7606_8_info; +extern const struct ad7606_chip_info ad7606_6_info; +extern const struct ad7606_chip_info ad7606_4_info; +extern const struct ad7606_chip_info ad7606b_info; +extern const struct ad7606_chip_info ad7606c_16_info; +extern const struct ad7606_chip_info ad7606c_18_info; +extern const struct ad7606_chip_info ad7607_info; +extern const struct ad7606_chip_info ad7608_info; +extern const struct ad7606_chip_info ad7609_info; +extern const struct ad7606_chip_info ad7616_info; + +#ifdef CONFIG_PM_SLEEP +extern const struct dev_pm_ops ad7606_pm_ops; +#define AD7606_PM_OPS (&ad7606_pm_ops) +#else +#define AD7606_PM_OPS NULL +#endif + +#endif /* IIO_ADC_AD7606_H_ */ diff --git a/drivers/iio/adc/ad7606_bus_iface.h b/drivers/iio/adc/ad7606_bus_iface.h new file mode 100644 index 000000000..f2c979a9b --- /dev/null +++ b/drivers/iio/adc/ad7606_bus_iface.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2010-2024 Analog Devices Inc. + * Copyright (c) 2025 Baylibre, SAS + */ +#ifndef __LINUX_PLATFORM_DATA_AD7606_H__ +#define __LINUX_PLATFORM_DATA_AD7606_H__ + +struct iio_backend; + +struct ad7606_platform_data { + int (*bus_reg_read)(struct iio_backend *back, u32 reg, u32 *val); + int (*bus_reg_write)(struct iio_backend *back, u32 reg, u32 val); +}; + +#endif /* __LINUX_PLATFORM_DATA_AD7606_H__ */ diff --git a/drivers/iio/adc/ad7606_par.c b/drivers/iio/adc/ad7606_par.c new file mode 100644 index 000000000..5b137e947 --- /dev/null +++ b/drivers/iio/adc/ad7606_par.c @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AD7606 Parallel Interface ADC driver + * + * Copyright 2011 - 2024 Analog Devices Inc. + * Copyright 2024 BayLibre SAS. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "ad7606.h" +#include "ad7606_bus_iface.h" + +static int ad7606_par_bus_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ad7606_state *st = iio_priv(indio_dev); + unsigned int c, ret; + + for (c = 0; c < indio_dev->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 ad7606_par_bus_setup_iio_backend(struct device *dev, + struct iio_dev *indio_dev) +{ + struct ad7606_state *st = iio_priv(indio_dev); + unsigned int c; + int ret; + struct iio_backend_data_fmt data = { + .sign_extend = true, + .enable = true, + }; + + st->back = devm_iio_backend_get(dev, NULL); + if (IS_ERR(st->back)) + return PTR_ERR(st->back); + + /* If the device is iio_backend powered the PWM is mandatory */ + if (!st->cnvst_pwm) + return dev_err_probe(st->dev, -EINVAL, + "A PWM is mandatory when using backend.\n"); + + ret = devm_iio_backend_request_buffer(dev, st->back, indio_dev); + if (ret) + return ret; + + ret = devm_iio_backend_enable(dev, st->back); + if (ret) + return ret; + + for (c = 0; c < indio_dev->num_channels; c++) { + ret = iio_backend_data_format_set(st->back, c, &data); + if (ret) + return ret; + } + + return 0; +} + +static int ad7606_par_bus_reg_read(struct ad7606_state *st, unsigned int addr) +{ + struct ad7606_platform_data *pdata = st->dev->platform_data; + int val, ret; + + ret = pdata->bus_reg_read(st->back, addr, &val); + if (ret) + return ret; + + return val; +} + +static int ad7606_par_bus_reg_write(struct ad7606_state *st, unsigned int addr, + unsigned int val) +{ + struct ad7606_platform_data *pdata = st->dev->platform_data; + + return pdata->bus_reg_write(st->back, addr, val); +} + +static const struct ad7606_bus_ops ad7606_bi_bops = { + .iio_backend_config = ad7606_par_bus_setup_iio_backend, + .update_scan_mode = ad7606_par_bus_update_scan_mode, + .reg_read = ad7606_par_bus_reg_read, + .reg_write = ad7606_par_bus_reg_write, +}; + +static int ad7606_par16_read_block(struct device *dev, + int count, void *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7606_state *st = iio_priv(indio_dev); + + + /* + * On the parallel interface, 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. + */ + int num = count; + u16 *_buf = buf; + + if (st->gpio_frstdata) { + insw((unsigned long)st->base_address, _buf, 1); + if (!gpiod_get_value(st->gpio_frstdata)) { + ad7606_reset(st); + return -EIO; + } + _buf++; + num--; + } + insw((unsigned long)st->base_address, _buf, num); + return 0; +} + +static const struct ad7606_bus_ops ad7606_par16_bops = { + .read_block = ad7606_par16_read_block, +}; + +static int ad7606_par8_read_block(struct device *dev, + int count, void *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7606_state *st = iio_priv(indio_dev); + /* + * On the parallel interface, 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. + */ + int num = count; + u16 *_buf = buf; + + if (st->gpio_frstdata) { + insb((unsigned long)st->base_address, _buf, 2); + if (!gpiod_get_value(st->gpio_frstdata)) { + ad7606_reset(st); + return -EIO; + } + _buf++; + num--; + } + insb((unsigned long)st->base_address, _buf, num * 2); + + return 0; +} + +static const struct ad7606_bus_ops ad7606_par8_bops = { + .read_block = ad7606_par8_read_block, +}; + +static int ad7606_par_probe(struct platform_device *pdev) +{ + const struct ad7606_chip_info *chip_info; + const struct platform_device_id *id; + struct resource *res; + void __iomem *addr; + resource_size_t remap_size; + int irq; + + /* + * If a firmware node is available (ACPI or DT), platform_device_id is null + * and we must use get_match_data. + */ + if (dev_fwnode(&pdev->dev)) { + chip_info = device_get_match_data(&pdev->dev); + if (device_property_present(&pdev->dev, "io-backends")) + /* + * If a backend is available ,call the core probe with backend + * bops, otherwise use the former bops. + */ + return ad7606_probe(&pdev->dev, 0, NULL, + chip_info, + &ad7606_bi_bops); + } else { + id = platform_get_device_id(pdev); + chip_info = (const struct ad7606_chip_info *)id->driver_data; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + addr = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(addr)) + return PTR_ERR(addr); + + remap_size = resource_size(res); + + return ad7606_probe(&pdev->dev, irq, addr, chip_info, + remap_size > 1 ? &ad7606_par16_bops : + &ad7606_par8_bops); +} + +static const struct platform_device_id ad7606_driver_ids[] = { + { .name = "ad7605-4", .driver_data = (kernel_ulong_t)&ad7605_4_info, }, + { .name = "ad7606-4", .driver_data = (kernel_ulong_t)&ad7606_4_info, }, + { .name = "ad7606-6", .driver_data = (kernel_ulong_t)&ad7606_6_info, }, + { .name = "ad7606-8", .driver_data = (kernel_ulong_t)&ad7606_8_info, }, + { .name = "ad7606b", .driver_data = (kernel_ulong_t)&ad7606b_info, }, + { .name = "ad7606c-16", .driver_data = (kernel_ulong_t)&ad7606c_16_info }, + { .name = "ad7606c-18", .driver_data = (kernel_ulong_t)&ad7606c_18_info }, + { .name = "ad7607", .driver_data = (kernel_ulong_t)&ad7607_info, }, + { .name = "ad7608", .driver_data = (kernel_ulong_t)&ad7608_info, }, + { .name = "ad7609", .driver_data = (kernel_ulong_t)&ad7609_info, }, + { } +}; +MODULE_DEVICE_TABLE(platform, ad7606_driver_ids); + +static const struct of_device_id ad7606_of_match[] = { + { .compatible = "adi,ad7605-4", .data = &ad7605_4_info }, + { .compatible = "adi,ad7606-4", .data = &ad7606_4_info }, + { .compatible = "adi,ad7606-6", .data = &ad7606_6_info }, + { .compatible = "adi,ad7606-8", .data = &ad7606_8_info }, + { .compatible = "adi,ad7606b", .data = &ad7606b_info }, + { .compatible = "adi,ad7606c-16", .data = &ad7606c_16_info }, + { .compatible = "adi,ad7606c-18", .data = &ad7606c_18_info }, + { .compatible = "adi,ad7607", .data = &ad7607_info }, + { .compatible = "adi,ad7608", .data = &ad7608_info }, + { .compatible = "adi,ad7609", .data = &ad7609_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7606_of_match); + +static struct platform_driver ad7606_driver = { + .probe = ad7606_par_probe, + .id_table = ad7606_driver_ids, + .driver = { + .name = "ad7606", + .pm = AD7606_PM_OPS, + .of_match_table = ad7606_of_match, + }, +}; +module_platform_driver(ad7606_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7606 ADC"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_AD7606"); +MODULE_IMPORT_NS("IIO_BACKEND"); diff --git a/drivers/iio/adc/ad7606_spi.c b/drivers/iio/adc/ad7606_spi.c new file mode 100644 index 000000000..1ebdfb858 --- /dev/null +++ b/drivers/iio/adc/ad7606_spi.c @@ -0,0 +1,512 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AD7606 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "ad7606.h" + +#define MAX_SPI_FREQ_HZ 23500000 /* VDRIVE above 4.75 V */ + +struct spi_bus_data { + struct spi_offload *offload; + struct spi_offload_trigger *offload_trigger; + struct spi_transfer offload_xfer; + struct spi_message offload_msg; +}; + +static u16 ad7616_spi_rd_wr_cmd(int addr, char is_write_op) +{ + /* + * The address of register consist of one w/r bit + * 6 bits of address followed by one reserved bit. + */ + return ((addr & 0x7F) << 1) | ((is_write_op & 0x1) << 7); +} + +static u16 ad7606b_spi_rd_wr_cmd(int addr, char is_write_op) +{ + /* + * The address of register consists of one bit which + * specifies a read command placed in bit 6, followed by + * 6 bits of address. + */ + return (addr & 0x3F) | (((~is_write_op) & 0x1) << 6); +} + +static int ad7606_spi_read_block(struct device *dev, + 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"); + return ret; + } + + for (i = 0; i < count; i++) + data[i] = be16_to_cpu(bdata[i]); + + return 0; +} + +static int ad7606_spi_read_block14to16(struct device *dev, + int count, void *buf) +{ + struct spi_device *spi = to_spi_device(dev); + struct spi_transfer xfer = { + .bits_per_word = 14, + .len = count * sizeof(u16), + .rx_buf = buf, + }; + + return spi_sync_transfer(spi, &xfer, 1); +} + +static int ad7606_spi_read_block18to32(struct device *dev, + int count, void *buf) +{ + struct spi_device *spi = to_spi_device(dev); + struct spi_transfer xfer = { + .bits_per_word = 18, + .len = count * sizeof(u32), + .rx_buf = buf, + }; + + return spi_sync_transfer(spi, &xfer, 1); +} + +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[] = { + { + .tx_buf = &st->d16[0], + .len = 2, + .cs_change = 1, + }, { + .rx_buf = &st->d16[1], + .len = 2, + }, + }; + int ret; + + st->d16[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->d16[1]); +} + +static int ad7606_spi_reg_write(struct ad7606_state *st, + unsigned int addr, + unsigned int val) +{ + struct spi_device *spi = to_spi_device(st->dev); + + st->d16[0] = cpu_to_be16((st->bops->rd_wr_cmd(addr, 1) << 8) | + (val & 0xFF)); + + return spi_write(spi, &st->d16[0], sizeof(st->d16[0])); +} + +static int ad7606b_sw_mode_config(struct iio_dev *indio_dev) +{ + struct ad7606_state *st = iio_priv(indio_dev); + + /* Configure device spi to output on a single channel */ + return st->bops->reg_write(st, AD7606_CONFIGURATION_REGISTER, + AD7606_SINGLE_DOUT); +} + +static const struct spi_offload_config ad7606_spi_offload_config = { + .capability_flags = SPI_OFFLOAD_CAP_TRIGGER | + SPI_OFFLOAD_CAP_RX_STREAM_DMA, +}; + +static int ad7606_spi_offload_buffer_postenable(struct iio_dev *indio_dev) +{ + const struct iio_scan_type *scan_type; + struct ad7606_state *st = iio_priv(indio_dev); + struct spi_bus_data *bus_data = st->bus_data; + struct spi_transfer *xfer = &bus_data->offload_xfer; + struct spi_device *spi = to_spi_device(st->dev); + struct spi_offload_trigger_config config = { + .type = SPI_OFFLOAD_TRIGGER_DATA_READY, + }; + int ret; + + scan_type = &indio_dev->channels[0].scan_type; + + xfer->bits_per_word = scan_type->realbits; + xfer->offload_flags = SPI_OFFLOAD_XFER_RX_STREAM; + /* + * Using SPI offload, storagebits are related to the spi-engine + * hw implementation, can be 16 or 32, so can't be used to compute + * struct spi_transfer.len. Using realbits instead. + */ + xfer->len = (scan_type->realbits > 16 ? 4 : 2) * + st->chip_info->num_adc_channels; + + spi_message_init_with_transfers(&bus_data->offload_msg, xfer, 1); + bus_data->offload_msg.offload = bus_data->offload; + + ret = spi_optimize_message(spi, &bus_data->offload_msg); + if (ret) { + dev_err(st->dev, "failed to prepare offload, err: %d\n", ret); + return ret; + } + + ret = spi_offload_trigger_enable(bus_data->offload, + bus_data->offload_trigger, + &config); + if (ret) + goto err_unoptimize_message; + + ret = ad7606_pwm_set_swing(st); + if (ret) + goto err_offload_exit_conversion_mode; + + return 0; + +err_offload_exit_conversion_mode: + spi_offload_trigger_disable(bus_data->offload, + bus_data->offload_trigger); + +err_unoptimize_message: + spi_unoptimize_message(&bus_data->offload_msg); + + return ret; +} + +static int ad7606_spi_offload_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad7606_state *st = iio_priv(indio_dev); + struct spi_bus_data *bus_data = st->bus_data; + int ret; + + ret = ad7606_pwm_set_low(st); + if (ret) + return ret; + + spi_offload_trigger_disable(bus_data->offload, + bus_data->offload_trigger); + spi_unoptimize_message(&bus_data->offload_msg); + + return 0; +} + +static const struct iio_buffer_setup_ops ad7606_offload_buffer_setup_ops = { + .postenable = ad7606_spi_offload_buffer_postenable, + .predisable = ad7606_spi_offload_buffer_predisable, +}; + +static bool ad7606_spi_offload_trigger_match( + struct spi_offload_trigger *trigger, + enum spi_offload_trigger_type type, + u64 *args, u32 nargs) +{ + if (type != SPI_OFFLOAD_TRIGGER_DATA_READY) + return false; + + /* + * Requires 1 arg: + * args[0] is the trigger event. + */ + if (nargs != 1 || args[0] != AD7606_TRIGGER_EVENT_BUSY) + return false; + + return true; +} + +static int ad7606_spi_offload_trigger_request( + struct spi_offload_trigger *trigger, + enum spi_offload_trigger_type type, + u64 *args, u32 nargs) +{ + /* Should already be validated by match, but just in case. */ + if (nargs != 1) + return -EINVAL; + + return 0; +} + +static int ad7606_spi_offload_trigger_validate( + struct spi_offload_trigger *trigger, + struct spi_offload_trigger_config *config) +{ + if (config->type != SPI_OFFLOAD_TRIGGER_DATA_READY) + return -EINVAL; + + return 0; +} + +static const struct spi_offload_trigger_ops ad7606_offload_trigger_ops = { + .match = ad7606_spi_offload_trigger_match, + .request = ad7606_spi_offload_trigger_request, + .validate = ad7606_spi_offload_trigger_validate, +}; + +static int ad7606_spi_offload_probe(struct device *dev, + struct iio_dev *indio_dev) +{ + struct ad7606_state *st = iio_priv(indio_dev); + struct spi_device *spi = to_spi_device(dev); + struct spi_bus_data *bus_data; + struct dma_chan *rx_dma; + struct spi_offload_trigger_info trigger_info = { + .fwnode = dev_fwnode(dev), + .ops = &ad7606_offload_trigger_ops, + .priv = st, + }; + int ret; + + bus_data = devm_kzalloc(dev, sizeof(*bus_data), GFP_KERNEL); + if (!bus_data) + return -ENOMEM; + st->bus_data = bus_data; + + bus_data->offload = devm_spi_offload_get(dev, spi, + &ad7606_spi_offload_config); + ret = PTR_ERR_OR_ZERO(bus_data->offload); + if (ret && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to get SPI offload\n"); + /* Allow main ad7606_probe function to continue. */ + if (ret == -ENODEV) + return 0; + + ret = devm_spi_offload_trigger_register(dev, &trigger_info); + if (ret) + return dev_err_probe(dev, ret, + "failed to register offload trigger\n"); + + bus_data->offload_trigger = devm_spi_offload_trigger_get(dev, + bus_data->offload, SPI_OFFLOAD_TRIGGER_DATA_READY); + if (IS_ERR(bus_data->offload_trigger)) + return dev_err_probe(dev, PTR_ERR(bus_data->offload_trigger), + "failed to get offload trigger\n"); + + /* TODO: PWM setup should be ok, done for the backend. PWM mutex ? */ + rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev, + bus_data->offload); + if (IS_ERR(rx_dma)) + return dev_err_probe(dev, PTR_ERR(rx_dma), + "failed to get offload RX DMA\n"); + + ret = devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, + rx_dma, IIO_BUFFER_DIRECTION_IN); + if (ret) + return dev_err_probe(dev, ret, + "failed to setup offload RX DMA\n"); + + /* Use offload ops. */ + indio_dev->setup_ops = &ad7606_offload_buffer_setup_ops; + + st->offload_en = true; + + return 0; +} + +static int ad7606_spi_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ad7606_state *st = iio_priv(indio_dev); + + if (st->offload_en) { + unsigned int num_adc_ch = st->chip_info->num_adc_channels; + + /* + * SPI offload requires that all channels are enabled since + * there isn't a way to selectively disable channels that get + * read (this is simultaneous sampling ADC) and the DMA buffer + * has no way of demuxing the data to filter out unwanted + * channels. + */ + if (!bitmap_full(scan_mask, num_adc_ch)) + return -EINVAL; + } + + return 0; +} + +static const struct ad7606_bus_ops ad7606_spi_bops = { + .offload_config = ad7606_spi_offload_probe, + .read_block = ad7606_spi_read_block, + .update_scan_mode = ad7606_spi_update_scan_mode, +}; + +static const struct ad7606_bus_ops ad7607_spi_bops = { + .offload_config = ad7606_spi_offload_probe, + .read_block = ad7606_spi_read_block14to16, + .update_scan_mode = ad7606_spi_update_scan_mode, +}; + +static const struct ad7606_bus_ops ad7608_spi_bops = { + .offload_config = ad7606_spi_offload_probe, + .read_block = ad7606_spi_read_block18to32, + .update_scan_mode = ad7606_spi_update_scan_mode, +}; + +static const struct ad7606_bus_ops ad7616_spi_bops = { + .offload_config = ad7606_spi_offload_probe, + .read_block = ad7606_spi_read_block, + .reg_read = ad7606_spi_reg_read, + .reg_write = ad7606_spi_reg_write, + .rd_wr_cmd = ad7616_spi_rd_wr_cmd, + .update_scan_mode = ad7606_spi_update_scan_mode, +}; + +static const struct ad7606_bus_ops ad7606b_spi_bops = { + .offload_config = ad7606_spi_offload_probe, + .read_block = ad7606_spi_read_block, + .reg_read = ad7606_spi_reg_read, + .reg_write = ad7606_spi_reg_write, + .rd_wr_cmd = ad7606b_spi_rd_wr_cmd, + .sw_mode_config = ad7606b_sw_mode_config, + .update_scan_mode = ad7606_spi_update_scan_mode, +}; + +static const struct ad7606_bus_ops ad7606c_18_spi_bops = { + .offload_config = ad7606_spi_offload_probe, + .read_block = ad7606_spi_read_block18to32, + .reg_read = ad7606_spi_reg_read, + .reg_write = ad7606_spi_reg_write, + .rd_wr_cmd = ad7606b_spi_rd_wr_cmd, + .sw_mode_config = ad7606b_sw_mode_config, + .update_scan_mode = ad7606_spi_update_scan_mode, +}; + +static const struct ad7606_bus_info ad7605_4_bus_info = { + .chip_info = &ad7605_4_info, + .bops = &ad7606_spi_bops, +}; + +static const struct ad7606_bus_info ad7606_8_bus_info = { + .chip_info = &ad7606_8_info, + .bops = &ad7606_spi_bops, +}; + +static const struct ad7606_bus_info ad7606_6_bus_info = { + .chip_info = &ad7606_6_info, + .bops = &ad7606_spi_bops, +}; + +static const struct ad7606_bus_info ad7606_4_bus_info = { + .chip_info = &ad7606_4_info, + .bops = &ad7606_spi_bops, +}; + +static const struct ad7606_bus_info ad7606b_bus_info = { + .chip_info = &ad7606b_info, + .bops = &ad7606b_spi_bops, +}; + +static const struct ad7606_bus_info ad7606c_16_bus_info = { + .chip_info = &ad7606c_16_info, + .bops = &ad7606b_spi_bops, +}; + +static const struct ad7606_bus_info ad7606c_18_bus_info = { + .chip_info = &ad7606c_18_info, + .bops = &ad7606c_18_spi_bops, +}; + +static const struct ad7606_bus_info ad7607_bus_info = { + .chip_info = &ad7607_info, + .bops = &ad7607_spi_bops, +}; + +static const struct ad7606_bus_info ad7608_bus_info = { + .chip_info = &ad7608_info, + .bops = &ad7608_spi_bops, +}; + +static const struct ad7606_bus_info ad7609_bus_info = { + .chip_info = &ad7609_info, + .bops = &ad7608_spi_bops, +}; + +static const struct ad7606_bus_info ad7616_bus_info = { + .chip_info = &ad7616_info, + .bops = &ad7616_spi_bops, +}; + +static int ad7606_spi_probe(struct spi_device *spi) +{ + const struct ad7606_bus_info *bus_info = spi_get_device_match_data(spi); + + return ad7606_probe(&spi->dev, spi->irq, NULL, + bus_info->chip_info, bus_info->bops); +} + +static const struct spi_device_id ad7606_id_table[] = { + { .name = "ad7605-4", .driver_data = (kernel_ulong_t)&ad7605_4_bus_info }, + { .name = "ad7606-4", .driver_data = (kernel_ulong_t)&ad7606_4_bus_info }, + { .name = "ad7606-6", .driver_data = (kernel_ulong_t)&ad7606_6_bus_info }, + { .name = "ad7606-8", .driver_data = (kernel_ulong_t)&ad7606_8_bus_info }, + { .name = "ad7606b", .driver_data = (kernel_ulong_t)&ad7606b_bus_info }, + { .name = "ad7606c-16", .driver_data = (kernel_ulong_t)&ad7606c_16_bus_info }, + { .name = "ad7606c-18", .driver_data = (kernel_ulong_t)&ad7606c_18_bus_info }, + { .name = "ad7607", .driver_data = (kernel_ulong_t)&ad7607_bus_info }, + { .name = "ad7608", .driver_data = (kernel_ulong_t)&ad7608_bus_info }, + { .name = "ad7609", .driver_data = (kernel_ulong_t)&ad7609_bus_info }, + { .name = "ad7616", .driver_data = (kernel_ulong_t)&ad7616_bus_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7606_id_table); + +static const struct of_device_id ad7606_of_match[] = { + { .compatible = "adi,ad7605-4", .data = &ad7605_4_bus_info }, + { .compatible = "adi,ad7606-4", .data = &ad7606_4_bus_info }, + { .compatible = "adi,ad7606-6", .data = &ad7606_6_bus_info }, + { .compatible = "adi,ad7606-8", .data = &ad7606_8_bus_info }, + { .compatible = "adi,ad7606b", .data = &ad7606b_bus_info }, + { .compatible = "adi,ad7606c-16", .data = &ad7606c_16_bus_info }, + { .compatible = "adi,ad7606c-18", .data = &ad7606c_18_bus_info }, + { .compatible = "adi,ad7607", .data = &ad7607_bus_info }, + { .compatible = "adi,ad7608", .data = &ad7608_bus_info }, + { .compatible = "adi,ad7609", .data = &ad7609_bus_info }, + { .compatible = "adi,ad7616", .data = &ad7616_bus_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7606_of_match); + +static struct spi_driver ad7606_driver = { + .driver = { + .name = "ad7606", + .of_match_table = ad7606_of_match, + .pm = AD7606_PM_OPS, + }, + .probe = ad7606_spi_probe, + .id_table = ad7606_id_table, +}; +module_spi_driver(ad7606_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7606 ADC"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_AD7606"); +MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER"); diff --git a/drivers/iio/adc/ad7625.c b/drivers/iio/adc/ad7625.c new file mode 100644 index 000000000..e73a5c9e7 --- /dev/null +++ b/drivers/iio/adc/ad7625.c @@ -0,0 +1,686 @@ +// SPDX-License-Identifier: (GPL-2.0-only) +/* + * Analog Devices Inc. AD7625 ADC driver + * + * Copyright 2024 Analog Devices Inc. + * Copyright 2024 BayLibre, SAS + * + * Note that this driver requires the AXI ADC IP block configured for + * LVDS to function. See Documentation/iio/ad7625.rst for more + * information. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AD7625_INTERNAL_REF_MV 4096 +#define AD7960_MAX_NBW_FREQ (2 * MEGA) + +struct ad7625_timing_spec { + /* Max conversion high time (t_{CNVH}). */ + unsigned int conv_high_ns; + /* Max conversion to MSB delay (t_{MSB}). */ + unsigned int conv_msb_ns; +}; + +struct ad7625_chip_info { + const char *name; + const unsigned int max_sample_freq_hz; + const struct ad7625_timing_spec *timing_spec; + const struct iio_chan_spec chan_spec; + const bool has_power_down_state; + const bool has_bandwidth_control; + const bool has_internal_vref; +}; + +/* AD7625_CHAN_SPEC - Define a chan spec structure for a specific chip */ +#define AD7625_CHAN_SPEC(_bits) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .differential = 1, \ + .channel = 0, \ + .channel2 = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = 0, \ + .scan_type.sign = 's', \ + .scan_type.storagebits = (_bits) > 16 ? 32 : 16, \ + .scan_type.realbits = (_bits), \ +} + +struct ad7625_state { + const struct ad7625_chip_info *info; + struct iio_backend *back; + /* rate of the clock gated by the "clk_gate" PWM */ + u32 ref_clk_rate_hz; + /* PWM burst signal for transferring acquired data to the host */ + struct pwm_device *clk_gate_pwm; + /* + * PWM control signal for initiating data conversion. Analog + * inputs are sampled beginning on this signal's rising edge. + */ + struct pwm_device *cnv_pwm; + /* + * Waveforms containing the last-requested and rounded + * properties for the clk_gate and cnv PWMs + */ + struct pwm_waveform clk_gate_wf; + struct pwm_waveform cnv_wf; + unsigned int vref_mv; + u32 sampling_freq_hz; + /* + * Optional GPIOs for controlling device state. EN0 and EN1 + * determine voltage reference configuration and on/off state. + * EN2 controls the device -3dB bandwidth (and by extension, max + * sample rate). EN3 controls the VCM reference output. EN2 and + * EN3 are only present for the AD796x devices. + */ + struct gpio_desc *en_gpios[4]; + bool can_power_down; + bool can_refin; + bool can_ref_4v096; + /* + * Indicate whether the bandwidth can be narrow (9MHz). + * When true, device sample rate must also be < 2MSPS. + */ + bool can_narrow_bandwidth; + /* Indicate whether the bandwidth can be wide (28MHz). */ + bool can_wide_bandwidth; + bool can_ref_5v; + bool can_snooze; + bool can_test_pattern; + /* Indicate whether there is a REFIN supply connected */ + bool have_refin; +}; + +static const struct ad7625_timing_spec ad7625_timing_spec = { + .conv_high_ns = 40, + .conv_msb_ns = 145, +}; + +static const struct ad7625_timing_spec ad7626_timing_spec = { + .conv_high_ns = 40, + .conv_msb_ns = 80, +}; + +/* + * conv_msb_ns is set to 0 instead of the datasheet maximum of 200ns to + * avoid exceeding the minimum conversion time, i.e. it is effectively + * modulo 200 and offset by a full period. Values greater than or equal + * to the period would be rejected by the PWM API. + */ +static const struct ad7625_timing_spec ad7960_timing_spec = { + .conv_high_ns = 80, + .conv_msb_ns = 0, +}; + +static const struct ad7625_chip_info ad7625_chip_info = { + .name = "ad7625", + .max_sample_freq_hz = 6 * MEGA, + .timing_spec = &ad7625_timing_spec, + .chan_spec = AD7625_CHAN_SPEC(16), + .has_power_down_state = false, + .has_bandwidth_control = false, + .has_internal_vref = true, +}; + +static const struct ad7625_chip_info ad7626_chip_info = { + .name = "ad7626", + .max_sample_freq_hz = 10 * MEGA, + .timing_spec = &ad7626_timing_spec, + .chan_spec = AD7625_CHAN_SPEC(16), + .has_power_down_state = true, + .has_bandwidth_control = false, + .has_internal_vref = true, +}; + +static const struct ad7625_chip_info ad7960_chip_info = { + .name = "ad7960", + .max_sample_freq_hz = 5 * MEGA, + .timing_spec = &ad7960_timing_spec, + .chan_spec = AD7625_CHAN_SPEC(18), + .has_power_down_state = true, + .has_bandwidth_control = true, + .has_internal_vref = false, +}; + +static const struct ad7625_chip_info ad7961_chip_info = { + .name = "ad7961", + .max_sample_freq_hz = 5 * MEGA, + .timing_spec = &ad7960_timing_spec, + .chan_spec = AD7625_CHAN_SPEC(16), + .has_power_down_state = true, + .has_bandwidth_control = true, + .has_internal_vref = false, +}; + +enum ad7960_mode { + AD7960_MODE_POWER_DOWN, + AD7960_MODE_SNOOZE, + AD7960_MODE_NARROW_BANDWIDTH, + AD7960_MODE_WIDE_BANDWIDTH, + AD7960_MODE_TEST_PATTERN, +}; + +static int ad7625_set_sampling_freq(struct ad7625_state *st, u32 freq) +{ + struct pwm_waveform clk_gate_wf = { }, cnv_wf = { }; + unsigned long target; + int ret; + + target = DIV_ROUND_UP(NSEC_PER_SEC, freq); + cnv_wf.period_length_ns = clamp(target, 100UL, 10UL * NSEC_PER_USEC); + + /* + * Use the maximum conversion time t_CNVH from the datasheet as + * the duty_cycle for ref_clk, cnv, and clk_gate + */ + cnv_wf.duty_length_ns = st->info->timing_spec->conv_high_ns; + + ret = pwm_round_waveform_might_sleep(st->cnv_pwm, &cnv_wf); + if (ret) + return ret; + + /* + * Set up the burst signal for transferring data. period and + * offset should mirror the CNV signal + */ + clk_gate_wf.period_length_ns = cnv_wf.period_length_ns; + + clk_gate_wf.duty_length_ns = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * + st->info->chan_spec.scan_type.realbits, + st->ref_clk_rate_hz); + + /* max t_MSB from datasheet */ + clk_gate_wf.duty_offset_ns = st->info->timing_spec->conv_msb_ns; + + ret = pwm_round_waveform_might_sleep(st->clk_gate_pwm, &clk_gate_wf); + if (ret) + return ret; + + st->cnv_wf = cnv_wf; + st->clk_gate_wf = clk_gate_wf; + + /* TODO: Add a rounding API for PWMs that can simplify this */ + target = DIV_ROUND_CLOSEST(st->ref_clk_rate_hz, freq); + st->sampling_freq_hz = DIV_ROUND_CLOSEST(st->ref_clk_rate_hz, + target); + + return 0; +} + +static int ad7625_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long info) +{ + struct ad7625_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + *val = st->sampling_freq_hz; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = st->vref_mv; + *val2 = chan->scan_type.realbits - 1; + + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } +} + +static int ad7625_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct ad7625_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = ad7625_set_sampling_freq(st, val); + iio_device_release_direct(indio_dev); + return ret; + default: + return -EINVAL; + } +} + +static int ad7625_parse_mode(struct device *dev, struct ad7625_state *st, + int num_gpios) +{ + bool en_always_on[4], en_always_off[4]; + bool en_may_be_on[4], en_may_be_off[4]; + char en_gpio_buf[4]; + char always_on_buf[18]; + int i; + + for (i = 0; i < num_gpios; i++) { + snprintf(en_gpio_buf, sizeof(en_gpio_buf), "en%d", i); + snprintf(always_on_buf, sizeof(always_on_buf), + "adi,en%d-always-on", i); + /* Set the device to 0b0000 (power-down mode) by default */ + st->en_gpios[i] = devm_gpiod_get_optional(dev, en_gpio_buf, + GPIOD_OUT_LOW); + if (IS_ERR(st->en_gpios[i])) + return dev_err_probe(dev, PTR_ERR(st->en_gpios[i]), + "failed to get EN%d GPIO\n", i); + + en_always_on[i] = device_property_read_bool(dev, always_on_buf); + if (st->en_gpios[i] && en_always_on[i]) + return dev_err_probe(dev, -EINVAL, + "cannot have adi,en%d-always-on and en%d-gpios\n", i, i); + + en_may_be_off[i] = !en_always_on[i]; + en_may_be_on[i] = en_always_on[i] || st->en_gpios[i]; + en_always_off[i] = !en_always_on[i] && !st->en_gpios[i]; + } + + /* + * Power down is mode 0bXX00, but not all devices have a valid + * power down state. + */ + st->can_power_down = en_may_be_off[1] && en_may_be_off[0] && + st->info->has_power_down_state; + /* + * The REFIN pin can take a 1.2V (AD762x) or 2.048V (AD796x) + * external reference when the mode is 0bXX01. + */ + st->can_refin = en_may_be_off[1] && en_may_be_on[0]; + /* 4.096V can be applied to REF when the EN mode is 0bXX10. */ + st->can_ref_4v096 = en_may_be_on[1] && en_may_be_off[0]; + + /* Avoid AD796x-specific setup if the part is an AD762x */ + if (num_gpios == 2) + return 0; + + /* mode 0b1100 (AD796x) is invalid */ + if (en_always_on[3] && en_always_on[2] && + en_always_off[1] && en_always_off[0]) + return dev_err_probe(dev, -EINVAL, + "EN GPIOs set to invalid mode 0b1100\n"); + /* + * 5V can be applied to the AD796x REF pin when the EN mode is + * the same (0bX001 or 0bX101) as for can_refin, and REFIN is + * 0V. + */ + st->can_ref_5v = st->can_refin; + /* + * Bandwidth (AD796x) is controlled solely by EN2. If it's + * specified and not hard-wired, then we can configure it to + * change the bandwidth between 28MHz and 9MHz. + */ + st->can_narrow_bandwidth = en_may_be_on[2]; + /* Wide bandwidth mode is possible if EN2 can be 0. */ + st->can_wide_bandwidth = en_may_be_off[2]; + /* Snooze mode (AD796x) is 0bXX11 when REFIN = 0V. */ + st->can_snooze = en_may_be_on[1] && en_may_be_on[0]; + /* Test pattern mode (AD796x) is 0b0100. */ + st->can_test_pattern = en_may_be_off[3] && en_may_be_on[2] && + en_may_be_off[1] && en_may_be_off[0]; + + return 0; +} + +/* Set EN1 and EN0 based on reference voltage source */ +static void ad7625_set_en_gpios_for_vref(struct ad7625_state *st, + bool have_refin, int ref_mv) +{ + if (have_refin || ref_mv == 5000) { + gpiod_set_value_cansleep(st->en_gpios[1], 0); + gpiod_set_value_cansleep(st->en_gpios[0], 1); + } else if (ref_mv == 4096) { + gpiod_set_value_cansleep(st->en_gpios[1], 1); + gpiod_set_value_cansleep(st->en_gpios[0], 0); + } else { + /* + * Unreachable by AD796x, since the driver will error if + * neither REF nor REFIN is provided + */ + gpiod_set_value_cansleep(st->en_gpios[1], 1); + gpiod_set_value_cansleep(st->en_gpios[0], 1); + } +} + +static int ad7960_set_mode(struct ad7625_state *st, enum ad7960_mode mode, + bool have_refin, int ref_mv) +{ + switch (mode) { + case AD7960_MODE_POWER_DOWN: + if (!st->can_power_down) + return -EINVAL; + + gpiod_set_value_cansleep(st->en_gpios[2], 0); + gpiod_set_value_cansleep(st->en_gpios[1], 0); + gpiod_set_value_cansleep(st->en_gpios[0], 0); + + return 0; + + case AD7960_MODE_SNOOZE: + if (!st->can_snooze) + return -EINVAL; + + gpiod_set_value_cansleep(st->en_gpios[1], 1); + gpiod_set_value_cansleep(st->en_gpios[0], 1); + + return 0; + + case AD7960_MODE_NARROW_BANDWIDTH: + if (!st->can_narrow_bandwidth) + return -EINVAL; + + gpiod_set_value_cansleep(st->en_gpios[2], 1); + ad7625_set_en_gpios_for_vref(st, have_refin, ref_mv); + + return 0; + + case AD7960_MODE_WIDE_BANDWIDTH: + if (!st->can_wide_bandwidth) + return -EINVAL; + + gpiod_set_value_cansleep(st->en_gpios[2], 0); + ad7625_set_en_gpios_for_vref(st, have_refin, ref_mv); + + return 0; + + case AD7960_MODE_TEST_PATTERN: + if (!st->can_test_pattern) + return -EINVAL; + + gpiod_set_value_cansleep(st->en_gpios[3], 0); + gpiod_set_value_cansleep(st->en_gpios[2], 1); + gpiod_set_value_cansleep(st->en_gpios[1], 0); + gpiod_set_value_cansleep(st->en_gpios[0], 0); + + return 0; + + default: + return -EINVAL; + } +} + +static int ad7625_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ad7625_state *st = iio_priv(indio_dev); + int ret; + + ret = pwm_set_waveform_might_sleep(st->cnv_pwm, &st->cnv_wf, false); + if (ret) + return ret; + + ret = pwm_set_waveform_might_sleep(st->clk_gate_pwm, + &st->clk_gate_wf, false); + if (ret) { + /* Disable cnv PWM if clk_gate setup failed */ + pwm_disable(st->cnv_pwm); + return ret; + } + + return 0; +} + +static int ad7625_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ad7625_state *st = iio_priv(indio_dev); + + pwm_disable(st->clk_gate_pwm); + pwm_disable(st->cnv_pwm); + + return 0; +} + +static const struct iio_info ad7625_info = { + .read_raw = ad7625_read_raw, + .write_raw = ad7625_write_raw, +}; + +static const struct iio_buffer_setup_ops ad7625_buffer_setup_ops = { + .preenable = &ad7625_buffer_preenable, + .postdisable = &ad7625_buffer_postdisable, +}; + +static int devm_ad7625_pwm_get(struct device *dev, + struct ad7625_state *st) +{ + struct clk *ref_clk; + u32 ref_clk_rate_hz; + + st->cnv_pwm = devm_pwm_get(dev, "cnv"); + if (IS_ERR(st->cnv_pwm)) + return dev_err_probe(dev, PTR_ERR(st->cnv_pwm), + "failed to get cnv pwm\n"); + + /* Preemptively disable the PWM in case it was enabled at boot */ + pwm_disable(st->cnv_pwm); + + st->clk_gate_pwm = devm_pwm_get(dev, "clk_gate"); + if (IS_ERR(st->clk_gate_pwm)) + return dev_err_probe(dev, PTR_ERR(st->clk_gate_pwm), + "failed to get clk_gate pwm\n"); + + /* Preemptively disable the PWM in case it was enabled at boot */ + pwm_disable(st->clk_gate_pwm); + + ref_clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(ref_clk)) + return dev_err_probe(dev, PTR_ERR(ref_clk), + "failed to get ref_clk\n"); + + ref_clk_rate_hz = clk_get_rate(ref_clk); + if (!ref_clk_rate_hz) + return dev_err_probe(dev, -EINVAL, + "failed to get ref_clk rate\n"); + + st->ref_clk_rate_hz = ref_clk_rate_hz; + + return 0; +} + +/* + * There are three required input voltages for each device, plus two + * conditionally-optional (depending on part) REF and REFIN voltages + * where their validity depends upon the EN pin configuration. + * + * Power-up info for the device says to bring up vio, then vdd2, then + * vdd1, so list them in that order in the regulator_names array. + * + * The reference voltage source is determined like so: + * - internal reference: neither REF or REFIN is connected (invalid for + * AD796x) + * - internal buffer, external reference: REF not connected, REFIN + * connected + * - external reference: REF connected, REFIN not connected + */ +static int devm_ad7625_regulator_setup(struct device *dev, + struct ad7625_state *st) +{ + static const char * const regulator_names[] = { "vio", "vdd2", "vdd1" }; + int ret, ref_mv; + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulator_names), + regulator_names); + if (ret) + return ret; + + ret = devm_regulator_get_enable_read_voltage(dev, "ref"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to get REF voltage\n"); + + ref_mv = ret == -ENODEV ? 0 : ret / 1000; + + ret = devm_regulator_get_enable_optional(dev, "refin"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to get REFIN voltage\n"); + + st->have_refin = ret != -ENODEV; + + if (st->have_refin && !st->can_refin) + return dev_err_probe(dev, -EINVAL, + "REFIN provided in unsupported mode\n"); + + if (!st->info->has_internal_vref && !st->have_refin && !ref_mv) + return dev_err_probe(dev, -EINVAL, + "Need either REFIN or REF\n"); + + if (st->have_refin && ref_mv) + return dev_err_probe(dev, -EINVAL, + "cannot have both REFIN and REF supplies\n"); + + if (ref_mv == 4096 && !st->can_ref_4v096) + return dev_err_probe(dev, -EINVAL, + "REF is 4.096V in unsupported mode\n"); + + if (ref_mv == 5000 && !st->can_ref_5v) + return dev_err_probe(dev, -EINVAL, + "REF is 5V in unsupported mode\n"); + + st->vref_mv = ref_mv ?: AD7625_INTERNAL_REF_MV; + + return 0; +} + +static int ad7625_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct ad7625_state *st; + int ret; + u32 default_sample_freq; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->info = device_get_match_data(dev); + if (!st->info) + return dev_err_probe(dev, -EINVAL, "no chip info\n"); + + if (device_property_read_bool(dev, "adi,no-dco")) + return dev_err_probe(dev, -EINVAL, + "self-clocked mode not supported\n"); + + if (st->info->has_bandwidth_control) + ret = ad7625_parse_mode(dev, st, 4); + else + ret = ad7625_parse_mode(dev, st, 2); + + if (ret) + return ret; + + ret = devm_ad7625_regulator_setup(dev, st); + if (ret) + return ret; + + /* Set the device mode based on detected EN configuration. */ + if (!st->info->has_bandwidth_control) { + ad7625_set_en_gpios_for_vref(st, st->have_refin, st->vref_mv); + } else { + /* + * If neither sampling mode is available, then report an error, + * since the other modes are not useful defaults. + */ + if (st->can_wide_bandwidth) { + ret = ad7960_set_mode(st, AD7960_MODE_WIDE_BANDWIDTH, + st->have_refin, st->vref_mv); + } else if (st->can_narrow_bandwidth) { + ret = ad7960_set_mode(st, AD7960_MODE_NARROW_BANDWIDTH, + st->have_refin, st->vref_mv); + } else { + return dev_err_probe(dev, -EINVAL, + "couldn't set device to wide or narrow bandwidth modes\n"); + } + + if (ret) + return dev_err_probe(dev, -EINVAL, + "failed to set EN pins\n"); + } + + ret = devm_ad7625_pwm_get(dev, st); + if (ret) + return ret; + + indio_dev->channels = &st->info->chan_spec; + indio_dev->num_channels = 1; + indio_dev->name = st->info->name; + indio_dev->info = &ad7625_info; + indio_dev->setup_ops = &ad7625_buffer_setup_ops; + + st->back = devm_iio_backend_get(dev, NULL); + if (IS_ERR(st->back)) + return dev_err_probe(dev, PTR_ERR(st->back), + "failed to get IIO backend\n"); + + ret = devm_iio_backend_request_buffer(dev, st->back, indio_dev); + if (ret) + return ret; + + ret = devm_iio_backend_enable(dev, st->back); + if (ret) + return ret; + + /* + * Set the initial sampling frequency to the maximum, unless the + * AD796x device is limited to narrow bandwidth by EN2 == 1, in + * which case the sampling frequency should be limited to 2MSPS + */ + default_sample_freq = st->info->max_sample_freq_hz; + if (st->info->has_bandwidth_control && !st->can_wide_bandwidth) + default_sample_freq = AD7960_MAX_NBW_FREQ; + + ret = ad7625_set_sampling_freq(st, default_sample_freq); + if (ret) + dev_err_probe(dev, ret, + "failed to set valid sampling frequency\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ad7625_of_match[] = { + { .compatible = "adi,ad7625", .data = &ad7625_chip_info }, + { .compatible = "adi,ad7626", .data = &ad7626_chip_info }, + { .compatible = "adi,ad7960", .data = &ad7960_chip_info }, + { .compatible = "adi,ad7961", .data = &ad7961_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7625_of_match); + +static const struct platform_device_id ad7625_device_ids[] = { + { .name = "ad7625", .driver_data = (kernel_ulong_t)&ad7625_chip_info }, + { .name = "ad7626", .driver_data = (kernel_ulong_t)&ad7626_chip_info }, + { .name = "ad7960", .driver_data = (kernel_ulong_t)&ad7960_chip_info }, + { .name = "ad7961", .driver_data = (kernel_ulong_t)&ad7961_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(platform, ad7625_device_ids); + +static struct platform_driver ad7625_driver = { + .probe = ad7625_probe, + .driver = { + .name = "ad7625", + .of_match_table = ad7625_of_match, + }, + .id_table = ad7625_device_ids, +}; +module_platform_driver(ad7625_driver); + +MODULE_AUTHOR("Trevor Gamblin "); +MODULE_DESCRIPTION("Analog Devices AD7625 ADC"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_BACKEND"); diff --git a/drivers/iio/adc/ad7766.c b/drivers/iio/adc/ad7766.c new file mode 100644 index 000000000..92957196d --- /dev/null +++ b/drivers/iio/adc/ad7766.c @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * AD7766/AD7767 SPI ADC driver + * + * Copyright 2016 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +struct ad7766_chip_info { + unsigned int decimation_factor; +}; + +enum { + AD7766_SUPPLY_AVDD = 0, + AD7766_SUPPLY_DVDD = 1, + AD7766_SUPPLY_VREF = 2, + AD7766_NUM_SUPPLIES = 3 +}; + +struct ad7766 { + const struct ad7766_chip_info *chip_info; + struct spi_device *spi; + struct clk *mclk; + struct gpio_desc *pd_gpio; + struct regulator_bulk_data reg[AD7766_NUM_SUPPLIES]; + + struct iio_trigger *trig; + + struct spi_transfer xfer; + struct spi_message msg; + + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + * Make the buffer large enough for one 24 bit sample and one 64 bit + * aligned 64 bit timestamp. + */ + unsigned char data[ALIGN(3, sizeof(s64)) + sizeof(s64)] __aligned(IIO_DMA_MINALIGN); +}; + +/* + * AD7766 and AD7767 variations are interface compatible, the main difference is + * analog performance. Both parts will use the same ID. + */ +enum ad7766_device_ids { + ID_AD7766, + ID_AD7766_1, + ID_AD7766_2, +}; + +static irqreturn_t ad7766_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad7766 *ad7766 = iio_priv(indio_dev); + int ret; + + ret = spi_sync(ad7766->spi, &ad7766->msg); + if (ret < 0) + goto done; + + iio_push_to_buffers_with_timestamp(indio_dev, ad7766->data, + pf->timestamp); +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ad7766_preenable(struct iio_dev *indio_dev) +{ + struct ad7766 *ad7766 = iio_priv(indio_dev); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(ad7766->reg), ad7766->reg); + if (ret < 0) { + dev_err(&ad7766->spi->dev, "Failed to enable supplies: %d\n", + ret); + return ret; + } + + ret = clk_prepare_enable(ad7766->mclk); + if (ret < 0) { + dev_err(&ad7766->spi->dev, "Failed to enable MCLK: %d\n", ret); + regulator_bulk_disable(ARRAY_SIZE(ad7766->reg), ad7766->reg); + return ret; + } + + gpiod_set_value(ad7766->pd_gpio, 0); + + return 0; +} + +static int ad7766_postdisable(struct iio_dev *indio_dev) +{ + struct ad7766 *ad7766 = iio_priv(indio_dev); + + gpiod_set_value(ad7766->pd_gpio, 1); + + /* + * The PD pin is synchronous to the clock, so give it some time to + * notice the change before we disable the clock. + */ + msleep(20); + + clk_disable_unprepare(ad7766->mclk); + regulator_bulk_disable(ARRAY_SIZE(ad7766->reg), ad7766->reg); + + return 0; +} + +static int ad7766_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, int *val2, long info) +{ + struct ad7766 *ad7766 = iio_priv(indio_dev); + struct regulator *vref = ad7766->reg[AD7766_SUPPLY_VREF].consumer; + int scale_uv; + + switch (info) { + case IIO_CHAN_INFO_SCALE: + scale_uv = regulator_get_voltage(vref); + if (scale_uv < 0) + return scale_uv; + *val = scale_uv / 1000; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = clk_get_rate(ad7766->mclk) / + ad7766->chip_info->decimation_factor; + return IIO_VAL_INT; + } + return -EINVAL; +} + +static const struct iio_chan_spec ad7766_channels[] = { + { + .type = IIO_VOLTAGE, + .indexed = 1, + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_BE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7766_chip_info ad7766_chip_info[] = { + [ID_AD7766] = { + .decimation_factor = 8, + }, + [ID_AD7766_1] = { + .decimation_factor = 16, + }, + [ID_AD7766_2] = { + .decimation_factor = 32, + }, +}; + +static const struct iio_buffer_setup_ops ad7766_buffer_setup_ops = { + .preenable = &ad7766_preenable, + .postdisable = &ad7766_postdisable, +}; + +static const struct iio_info ad7766_info = { + .read_raw = &ad7766_read_raw, +}; + +static int ad7766_set_trigger_state(struct iio_trigger *trig, bool enable) +{ + struct ad7766 *ad7766 = iio_trigger_get_drvdata(trig); + + if (enable) + enable_irq(ad7766->spi->irq); + else + disable_irq(ad7766->spi->irq); + + return 0; +} + +static const struct iio_trigger_ops ad7766_trigger_ops = { + .set_trigger_state = ad7766_set_trigger_state, + .validate_device = iio_trigger_validate_own_device, +}; + +static int ad7766_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct iio_dev *indio_dev; + struct ad7766 *ad7766; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*ad7766)); + if (!indio_dev) + return -ENOMEM; + + ad7766 = iio_priv(indio_dev); + ad7766->chip_info = &ad7766_chip_info[id->driver_data]; + + ad7766->mclk = devm_clk_get(&spi->dev, "mclk"); + if (IS_ERR(ad7766->mclk)) + return PTR_ERR(ad7766->mclk); + + ad7766->reg[AD7766_SUPPLY_AVDD].supply = "avdd"; + ad7766->reg[AD7766_SUPPLY_DVDD].supply = "dvdd"; + ad7766->reg[AD7766_SUPPLY_VREF].supply = "vref"; + + ret = devm_regulator_bulk_get(&spi->dev, ARRAY_SIZE(ad7766->reg), + ad7766->reg); + if (ret) + return ret; + + ad7766->pd_gpio = devm_gpiod_get_optional(&spi->dev, "powerdown", + GPIOD_OUT_HIGH); + if (IS_ERR(ad7766->pd_gpio)) + return PTR_ERR(ad7766->pd_gpio); + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad7766_channels; + indio_dev->num_channels = ARRAY_SIZE(ad7766_channels); + indio_dev->info = &ad7766_info; + + if (spi->irq > 0) { + ad7766->trig = devm_iio_trigger_alloc(&spi->dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!ad7766->trig) + return -ENOMEM; + + ad7766->trig->ops = &ad7766_trigger_ops; + iio_trigger_set_drvdata(ad7766->trig, ad7766); + + /* + * The device generates interrupts as long as it is powered up. + * Some platforms might not allow the option to power it down so + * don't enable the interrupt to avoid extra load on the system + */ + ret = devm_request_irq(&spi->dev, spi->irq, iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_FALLING | IRQF_NO_AUTOEN | IRQF_NO_THREAD, + dev_name(&spi->dev), + ad7766->trig); + if (ret < 0) + return ret; + + ret = devm_iio_trigger_register(&spi->dev, ad7766->trig); + if (ret) + return ret; + } + + ad7766->spi = spi; + + /* First byte always 0 */ + ad7766->xfer.rx_buf = &ad7766->data[1]; + ad7766->xfer.len = 3; + + spi_message_init(&ad7766->msg); + spi_message_add_tail(&ad7766->xfer, &ad7766->msg); + + ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, + &iio_pollfunc_store_time, &ad7766_trigger_handler, + &ad7766_buffer_setup_ops); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id ad7766_id[] = { + { .name = "ad7766", .driver_data = ID_AD7766 }, + { .name = "ad7766-1", .driver_data = ID_AD7766_1 }, + { .name = "ad7766-2", .driver_data = ID_AD7766_2 }, + { .name = "ad7767", .driver_data = ID_AD7766 }, + { .name = "ad7767-1", .driver_data = ID_AD7766_1 }, + { .name = "ad7767-2", .driver_data = ID_AD7766_2 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7766_id); + +static struct spi_driver ad7766_driver = { + .driver = { + .name = "ad7766", + }, + .probe = ad7766_probe, + .id_table = ad7766_id, +}; +module_spi_driver(ad7766_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Analog Devices AD7766 and AD7767 ADCs driver support"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ad7768-1.c b/drivers/iio/adc/ad7768-1.c new file mode 100644 index 000000000..e9060c1bb --- /dev/null +++ b/drivers/iio/adc/ad7768-1.c @@ -0,0 +1,1951 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices AD7768-1 SPI ADC driver + * + * Copyright 2017 Analog Devices Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +/* AD7768 registers definition */ +#define AD7768_REG_CHIP_TYPE 0x3 +#define AD7768_REG_PROD_ID_L 0x4 +#define AD7768_REG_PROD_ID_H 0x5 +#define AD7768_REG_CHIP_GRADE 0x6 +#define AD7768_REG_SCRATCH_PAD 0x0A +#define AD7768_REG_VENDOR_L 0x0C +#define AD7768_REG_VENDOR_H 0x0D +#define AD7768_REG_INTERFACE_FORMAT 0x14 +#define AD7768_REG_POWER_CLOCK 0x15 +#define AD7768_REG_ANALOG 0x16 +#define AD7768_REG_ANALOG2 0x17 +#define AD7768_REG_CONVERSION 0x18 +#define AD7768_REG_DIGITAL_FILTER 0x19 +#define AD7768_REG_SINC3_DEC_RATE_MSB 0x1A +#define AD7768_REG_SINC3_DEC_RATE_LSB 0x1B +#define AD7768_REG_DUTY_CYCLE_RATIO 0x1C +#define AD7768_REG_SYNC_RESET 0x1D +#define AD7768_REG_GPIO_CONTROL 0x1E +#define AD7768_REG_GPIO_WRITE 0x1F +#define AD7768_REG_GPIO_READ 0x20 +#define AD7768_REG_OFFSET_HI 0x21 +#define AD7768_REG_OFFSET_MID 0x22 +#define AD7768_REG_OFFSET_LO 0x23 +#define AD7768_REG_GAIN_HI 0x24 +#define AD7768_REG_GAIN_MID 0x25 +#define AD7768_REG_GAIN_LO 0x26 +#define AD7768_REG_SPI_DIAG_ENABLE 0x28 +#define AD7768_REG_ADC_DIAG_ENABLE 0x29 +#define AD7768_REG_DIG_DIAG_ENABLE 0x2A +#define AD7768_REG24_ADC_DATA 0x2C +#define AD7768_REG_MASTER_STATUS 0x2D +#define AD7768_REG_SPI_DIAG_STATUS 0x2E +#define AD7768_REG_ADC_DIAG_STATUS 0x2F +#define AD7768_REG_DIG_DIAG_STATUS 0x30 +#define AD7768_REG_MCLK_COUNTER 0x31 +#define AD7768_REG_COEFF_CONTROL 0x32 +#define AD7768_REG24_COEFF_DATA 0x33 +#define AD7768_REG_ACCESS_KEY 0x34 + +/* AD7768_REG_POWER_CLOCK */ +#define AD7768_PWR_MCLK_DIV_MSK GENMASK(5, 4) +#define AD7768_PWR_MCLK_DIV(x) FIELD_PREP(AD7768_PWR_MCLK_DIV_MSK, x) +#define AD7768_PWR_PWRMODE_MSK GENMASK(1, 0) +#define AD7768_PWR_PWRMODE(x) FIELD_PREP(AD7768_PWR_PWRMODE_MSK, x) + +/* AD7768_REG_DIGITAL_FILTER */ +#define AD7768_DIG_FIL_EN_60HZ_REJ BIT(7) +#define AD7768_DIG_FIL_FIL_MSK GENMASK(6, 4) +#define AD7768_DIG_FIL_FIL(x) FIELD_PREP(AD7768_DIG_FIL_FIL_MSK, x) +#define AD7768_DIG_FIL_DEC_MSK GENMASK(2, 0) +#define AD7768_DIG_FIL_DEC_RATE(x) FIELD_PREP(AD7768_DIG_FIL_DEC_MSK, x) + +/* AD7768_REG_CONVERSION */ +#define AD7768_CONV_MODE_MSK GENMASK(2, 0) +#define AD7768_CONV_MODE(x) FIELD_PREP(AD7768_CONV_MODE_MSK, x) + +/* AD7768_REG_ANALOG2 */ +#define AD7768_REG_ANALOG2_VCM_MSK GENMASK(2, 0) +#define AD7768_REG_ANALOG2_VCM(x) FIELD_PREP(AD7768_REG_ANALOG2_VCM_MSK, (x)) + +/* AD7768_REG_GPIO_CONTROL */ +#define AD7768_GPIO_UNIVERSAL_EN BIT(7) +#define AD7768_GPIO_CONTROL_MSK GENMASK(3, 0) + +/* AD7768_REG_GPIO_WRITE */ +#define AD7768_GPIO_WRITE_MSK GENMASK(3, 0) + +/* AD7768_REG_GPIO_READ */ +#define AD7768_GPIO_READ_MSK GENMASK(3, 0) + +#define AD7768_VCM_OFF 0x07 + +#define ADAQ776X_GAIN_MAX_NANO (128 * NANO) +#define ADAQ776X_MAX_GAIN_MODES 8 + +#define AD7768_TRIGGER_SOURCE_SYNC_IDX 0 + +#define AD7768_MAX_CHANNELS 1 + +#define ADAQ7768_PGA_PINS 3 + +enum ad7768_conv_mode { + AD7768_CONTINUOUS, + AD7768_ONE_SHOT, + AD7768_SINGLE, + AD7768_PERIODIC, + AD7768_STANDBY +}; + +enum ad7768_pwrmode { + AD7768_ECO_MODE = 0, + AD7768_MED_MODE = 2, + AD7768_FAST_MODE = 3 +}; + +enum ad7768_mclk_div { + AD7768_MCLK_DIV_16, + AD7768_MCLK_DIV_8, + AD7768_MCLK_DIV_4, + AD7768_MCLK_DIV_2 +}; + +enum ad7768_filter_type { + AD7768_FILTER_SINC5, + AD7768_FILTER_SINC3, + AD7768_FILTER_WIDEBAND, + AD7768_FILTER_SINC3_REJ60, +}; + +enum ad7768_filter_regval { + AD7768_FILTER_REGVAL_SINC5 = 0, + AD7768_FILTER_REGVAL_SINC5_X8 = 1, + AD7768_FILTER_REGVAL_SINC5_X16 = 2, + AD7768_FILTER_REGVAL_SINC3 = 3, + AD7768_FILTER_REGVAL_WIDEBAND = 4, + AD7768_FILTER_REGVAL_SINC3_REJ60 = 11, +}; + +enum ad7768_scan_type { + AD7768_SCAN_TYPE_NORMAL, + AD7768_SCAN_TYPE_HIGH_SPEED, + AD7768_SCAN_TYPE_OFFLOAD_NORMAL, + AD7768_SCAN_TYPE_OFFLOAD_HIGH_SPEED, +}; + +enum { + AD7768_PGA_GAIN_0, + AD7768_PGA_GAIN_1, + AD7768_PGA_GAIN_2, + AD7768_PGA_GAIN_3, + AD7768_PGA_GAIN_4, + AD7768_PGA_GAIN_5, + AD7768_PGA_GAIN_6, + AD7768_PGA_GAIN_7, +}; + +enum { + AD7768_AAF_IN1, + AD7768_AAF_IN2, + AD7768_AAF_IN3, +}; + +/* PGA and AAF gains in V/V */ +static const int adaq7768_gains[] = { + [AD7768_PGA_GAIN_0] = 325, /* 0.325 */ + [AD7768_PGA_GAIN_1] = 650, /* 0.650 */ + [AD7768_PGA_GAIN_2] = 1300, /* 1.300 */ + [AD7768_PGA_GAIN_3] = 2600, /* 2.600 */ + [AD7768_PGA_GAIN_4] = 5200, /* 5.200 */ + [AD7768_PGA_GAIN_5] = 10400, /* 10.400 */ + [AD7768_PGA_GAIN_6] = 20800, /* 20.800 */ +}; + +static const int adaq7769_gains[] = { + [AD7768_PGA_GAIN_0] = 1000, /* 1.000 */ + [AD7768_PGA_GAIN_1] = 2000, /* 2.000 */ + [AD7768_PGA_GAIN_2] = 4000, /* 4.000 */ + [AD7768_PGA_GAIN_3] = 8000, /* 8.000 */ + [AD7768_PGA_GAIN_4] = 16000, /* 16.000 */ + [AD7768_PGA_GAIN_5] = 32000, /* 32.000 */ + [AD7768_PGA_GAIN_6] = 64000, /* 64.000 */ + [AD7768_PGA_GAIN_7] = 128000, /* 128.000 */ +}; + +static const int ad7768_aaf_gains_bp[] = { + [AD7768_AAF_IN1] = 10000, /* 1.000 */ + [AD7768_AAF_IN2] = 3640, /* 0.364 */ + [AD7768_AAF_IN3] = 1430, /* 0.143 */ +}; + +/* -3dB cutoff frequency multipliers (relative to ODR) for each filter type. */ +static const int ad7768_filter_3db_odr_multiplier[] = { + [AD7768_FILTER_SINC5] = 204, /* 0.204 */ + [AD7768_FILTER_SINC3] = 262, /* 0.2617 */ + [AD7768_FILTER_SINC3_REJ60] = 262, /* 0.2617 */ + [AD7768_FILTER_WIDEBAND] = 433, /* 0.433 */ +}; + +static const int ad7768_mclk_div_rates[] = { + 16, 8, 4, 2, +}; + +static const int ad7768_dec_rate_values[8] = { + 8, 16, 32, 64, 128, 256, 512, 1024, +}; + +/* Decimation rate range for sinc3 filter */ +static const int ad7768_sinc3_dec_rate_range[3] = { + 32, 32, 163840, +}; + +/* + * The AD7768-1 supports three primary filter types: + * Sinc5, Sinc3, and Wideband. + * However, the filter register values can also encode additional parameters + * such as decimation rates and 60Hz rejection. This utility array separates + * the filter type from these parameters. + */ +static const int ad7768_filter_regval_to_type[] = { + [AD7768_FILTER_REGVAL_SINC5] = AD7768_FILTER_SINC5, + [AD7768_FILTER_REGVAL_SINC5_X8] = AD7768_FILTER_SINC5, + [AD7768_FILTER_REGVAL_SINC5_X16] = AD7768_FILTER_SINC5, + [AD7768_FILTER_REGVAL_SINC3] = AD7768_FILTER_SINC3, + [AD7768_FILTER_REGVAL_WIDEBAND] = AD7768_FILTER_WIDEBAND, + [AD7768_FILTER_REGVAL_SINC3_REJ60] = AD7768_FILTER_SINC3_REJ60, +}; + +static const char * const ad7768_filter_enum[] = { + [AD7768_FILTER_SINC5] = "sinc5", + [AD7768_FILTER_SINC3] = "sinc3", + [AD7768_FILTER_WIDEBAND] = "wideband", + [AD7768_FILTER_SINC3_REJ60] = "sinc3+rej60", +}; + +static const struct iio_scan_type ad7768_scan_type[] = { + [AD7768_SCAN_TYPE_NORMAL] = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + .shift = 8, + .endianness = IIO_BE, + }, + [AD7768_SCAN_TYPE_HIGH_SPEED] = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, + }, + [AD7768_SCAN_TYPE_OFFLOAD_NORMAL] = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_CPU, + }, + [AD7768_SCAN_TYPE_OFFLOAD_HIGH_SPEED] = { + .sign = 's', + .realbits = 16, + .storagebits = 32, + .endianness = IIO_CPU, + }, +}; + +struct ad7768_chip_info { + const char *name; + const struct iio_chan_spec *channel_spec; + int num_channels; + const int *pga_gains; + int num_pga_modes; + int default_pga_mode; + int pgia_mode2pin_offset; + bool has_pga; + bool has_variable_aaf; + bool has_vcm_regulator; +}; + +struct ad7768_state { + struct spi_device *spi; + struct spi_offload *offload; + struct spi_offload_trigger *offload_trigger; + struct regmap *regmap; + struct regmap *regmap24; + int vref_uv; + struct regulator_dev *vcm_rdev; + unsigned int vcm_output_sel; + struct clk *mclk; + unsigned int mclk_freq; + unsigned int oversampling_ratio; + enum ad7768_filter_type filter_type; + unsigned int samp_freq; + unsigned int samp_freq_avail[ARRAY_SIZE(ad7768_mclk_div_rates)]; + unsigned int samp_freq_avail_len; + unsigned int pga_gain_mode; + unsigned int aaf_gain; + int scale_tbl[ADAQ776X_MAX_GAIN_MODES][2]; + struct completion completion; + struct iio_trigger *trig; + struct gpio_descs *pga_gpios; + struct gpio_desc *gpio_sync_in; + struct gpio_desc *gpio_reset; + const char *labels[AD7768_MAX_CHANNELS]; + struct gpio_chip gpiochip; + struct spi_transfer offload_xfer; + struct spi_message offload_msg; + const struct ad7768_chip_info *chip; + bool en_spi_sync; + struct mutex pga_lock; /* protect device internal state (PGA) */ + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + */ + union { + struct { + __be32 chan; + aligned_s64 timestamp; + } scan; + __be32 d32; + u8 d8[2]; + } data __aligned(IIO_DMA_MINALIGN); +}; + +static const struct regmap_range ad7768_regmap_rd_ranges[] = { + regmap_reg_range(AD7768_REG_CHIP_TYPE, AD7768_REG_CHIP_GRADE), + regmap_reg_range(AD7768_REG_SCRATCH_PAD, AD7768_REG_SCRATCH_PAD), + regmap_reg_range(AD7768_REG_VENDOR_L, AD7768_REG_VENDOR_H), + regmap_reg_range(AD7768_REG_INTERFACE_FORMAT, AD7768_REG_GAIN_LO), + regmap_reg_range(AD7768_REG_SPI_DIAG_ENABLE, AD7768_REG_DIG_DIAG_ENABLE), + regmap_reg_range(AD7768_REG_MASTER_STATUS, AD7768_REG_COEFF_CONTROL), + regmap_reg_range(AD7768_REG_ACCESS_KEY, AD7768_REG_ACCESS_KEY), +}; + +static const struct regmap_access_table ad7768_regmap_rd_table = { + .yes_ranges = ad7768_regmap_rd_ranges, + .n_yes_ranges = ARRAY_SIZE(ad7768_regmap_rd_ranges), +}; + +static const struct regmap_range ad7768_regmap_wr_ranges[] = { + regmap_reg_range(AD7768_REG_SCRATCH_PAD, AD7768_REG_SCRATCH_PAD), + regmap_reg_range(AD7768_REG_INTERFACE_FORMAT, AD7768_REG_GPIO_WRITE), + regmap_reg_range(AD7768_REG_OFFSET_HI, AD7768_REG_GAIN_LO), + regmap_reg_range(AD7768_REG_SPI_DIAG_ENABLE, AD7768_REG_DIG_DIAG_ENABLE), + regmap_reg_range(AD7768_REG_SPI_DIAG_STATUS, AD7768_REG_SPI_DIAG_STATUS), + regmap_reg_range(AD7768_REG_COEFF_CONTROL, AD7768_REG_COEFF_CONTROL), + regmap_reg_range(AD7768_REG_ACCESS_KEY, AD7768_REG_ACCESS_KEY), +}; + +static const struct regmap_access_table ad7768_regmap_wr_table = { + .yes_ranges = ad7768_regmap_wr_ranges, + .n_yes_ranges = ARRAY_SIZE(ad7768_regmap_wr_ranges), +}; + +static const struct regmap_config ad7768_regmap_config = { + .name = "ad7768-1-8", + .reg_bits = 8, + .val_bits = 8, + .read_flag_mask = BIT(6), + .rd_table = &ad7768_regmap_rd_table, + .wr_table = &ad7768_regmap_wr_table, + .max_register = AD7768_REG_ACCESS_KEY, + .use_single_write = true, + .use_single_read = true, +}; + +static const struct regmap_range ad7768_regmap24_rd_ranges[] = { + regmap_reg_range(AD7768_REG24_ADC_DATA, AD7768_REG24_ADC_DATA), + regmap_reg_range(AD7768_REG24_COEFF_DATA, AD7768_REG24_COEFF_DATA), +}; + +static const struct regmap_access_table ad7768_regmap24_rd_table = { + .yes_ranges = ad7768_regmap24_rd_ranges, + .n_yes_ranges = ARRAY_SIZE(ad7768_regmap24_rd_ranges), +}; + +static const struct regmap_range ad7768_regmap24_wr_ranges[] = { + regmap_reg_range(AD7768_REG24_COEFF_DATA, AD7768_REG24_COEFF_DATA), +}; + +static const struct regmap_access_table ad7768_regmap24_wr_table = { + .yes_ranges = ad7768_regmap24_wr_ranges, + .n_yes_ranges = ARRAY_SIZE(ad7768_regmap24_wr_ranges), +}; + +static const struct regmap_config ad7768_regmap24_config = { + .name = "ad7768-1-24", + .reg_bits = 8, + .val_bits = 24, + .read_flag_mask = BIT(6), + .rd_table = &ad7768_regmap24_rd_table, + .wr_table = &ad7768_regmap24_wr_table, + .max_register = AD7768_REG24_COEFF_DATA, +}; + +static int ad7768_send_sync_pulse(struct ad7768_state *st) +{ + if (st->en_spi_sync) + return regmap_write(st->regmap, AD7768_REG_SYNC_RESET, 0x00); + + /* + * The datasheet specifies a minimum SYNC_IN pulse width of 1.5 × Tmclk, + * where Tmclk is the MCLK period. The supported MCLK frequencies range + * from 0.6 MHz to 17 MHz, which corresponds to a minimum SYNC_IN pulse + * width of approximately 2.5 µs in the worst-case scenario (0.6 MHz). + * + * Add a delay to ensure the pulse width is always sufficient to + * trigger synchronization. + */ + gpiod_set_value_cansleep(st->gpio_sync_in, 1); + fsleep(3); + gpiod_set_value_cansleep(st->gpio_sync_in, 0); + + return 0; +} + +static void ad7768_fill_samp_freq_tbl(struct ad7768_state *st) +{ + unsigned int i, samp_freq_avail, freq_filtered; + unsigned int len = 0; + + freq_filtered = DIV_ROUND_CLOSEST(st->mclk_freq, st->oversampling_ratio); + for (i = 0; i < ARRAY_SIZE(ad7768_mclk_div_rates); i++) { + samp_freq_avail = DIV_ROUND_CLOSEST(freq_filtered, ad7768_mclk_div_rates[i]); + /* Sampling frequency cannot be lower than the minimum of 50 SPS */ + if (samp_freq_avail < 50) + continue; + + st->samp_freq_avail[len++] = samp_freq_avail; + } + + st->samp_freq_avail_len = len; +} + +static int ad7768_set_mclk_div(struct ad7768_state *st, unsigned int mclk_div) +{ + unsigned int mclk_div_value; + + mclk_div_value = AD7768_PWR_MCLK_DIV(mclk_div); + /* + * Set power mode based on mclk_div value. + * ECO_MODE is only recommended for MCLK_DIV = 16. + */ + mclk_div_value |= mclk_div > AD7768_MCLK_DIV_16 ? + AD7768_PWR_PWRMODE(AD7768_FAST_MODE) : + AD7768_PWR_PWRMODE(AD7768_ECO_MODE); + + return regmap_update_bits(st->regmap, AD7768_REG_POWER_CLOCK, + AD7768_PWR_MCLK_DIV_MSK | AD7768_PWR_PWRMODE_MSK, + mclk_div_value); +} + +static int ad7768_set_mode(struct ad7768_state *st, + enum ad7768_conv_mode mode) +{ + return regmap_update_bits(st->regmap, AD7768_REG_CONVERSION, + AD7768_CONV_MODE_MSK, AD7768_CONV_MODE(mode)); +} + +static int ad7768_scan_direct(struct iio_dev *indio_dev) +{ + struct ad7768_state *st = iio_priv(indio_dev); + int readval, ret; + + reinit_completion(&st->completion); + enable_irq(st->spi->irq); + + ret = wait_for_completion_timeout(&st->completion, + msecs_to_jiffies(1000)); + disable_irq(st->spi->irq); + if (!ret) + return -ETIMEDOUT; + + ret = regmap_read(st->regmap24, AD7768_REG24_ADC_DATA, &readval); + if (ret) + return ret; + + /* + * When the decimation rate is set to x8, the ADC data precision is + * reduced from 24 bits to 16 bits. Since the AD7768_REG_ADC_DATA + * register provides 24-bit data, the precision is reduced by + * right-shifting the read value by 8 bits. + */ + if (st->oversampling_ratio == 8) + readval >>= 8; + + return readval; +} + +static int ad7768_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct ad7768_state *st = iio_priv(indio_dev); + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = -EINVAL; + if (readval) { + if (regmap_check_range_table(st->regmap, reg, &ad7768_regmap_rd_table)) + ret = regmap_read(st->regmap, reg, readval); + + if (regmap_check_range_table(st->regmap24, reg, &ad7768_regmap24_rd_table)) + ret = regmap_read(st->regmap24, reg, readval); + + } else { + if (regmap_check_range_table(st->regmap, reg, &ad7768_regmap_wr_table)) + ret = regmap_write(st->regmap, reg, writeval); + + if (regmap_check_range_table(st->regmap24, reg, &ad7768_regmap24_wr_table)) + ret = regmap_write(st->regmap24, reg, writeval); + + } + + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ad7768_fill_scale_tbl(struct iio_dev *dev) +{ + struct ad7768_state *st = iio_priv(dev); + const struct iio_scan_type *scan_type; + int val, val2, tmp0, tmp1, i; + struct u32_fract fract; + unsigned long n, d; + u64 tmp2; + + scan_type = iio_get_current_scan_type(dev, &dev->channels[0]); + if (IS_ERR(scan_type)) { + dev_err(&st->spi->dev, "Failed to get scan type.\n"); + return PTR_ERR(scan_type); + } + + if (scan_type->sign == 's') + val2 = scan_type->realbits - 1; + else + val2 = scan_type->realbits; + + for (i = 0; i < st->chip->num_pga_modes; i++) { + /* Convert gain to a fraction format */ + fract.numerator = st->chip->pga_gains[i]; + fract.denominator = MILLI; + if (st->chip->has_variable_aaf) { + fract.numerator *= ad7768_aaf_gains_bp[st->aaf_gain]; + fract.denominator *= PERMYRIAD; + } + + rational_best_approximation(fract.numerator, fract.denominator, + INT_MAX, INT_MAX, &n, &d); + + val = mult_frac(st->vref_uv, d, n); + /* Would multiply by NANO here, but value is already in milli */ + tmp2 = ((u64)val * MICRO) >> val2; + tmp0 = div_u64_rem(tmp2, NANO, &tmp1); + st->scale_tbl[i][0] = tmp0; /* Integer part */ + st->scale_tbl[i][1] = abs(tmp1); /* Fractional part */ + } + + return 0; +} + +static int ad7768_set_sinc3_dec_rate(struct ad7768_state *st, + unsigned int dec_rate) +{ + unsigned int max_dec_rate; + u8 dec_rate_reg[2]; + u16 regval; + int ret; + + /* + * Maximum dec_rate is limited by the MCLK_DIV value and by the ODR. + * The edge case is for MCLK_DIV = 2, ODR = 50 SPS. + * max_dec_rate <= MCLK / (2 * 50) + */ + max_dec_rate = st->mclk_freq / 100; + dec_rate = clamp(dec_rate, 32, max_dec_rate); + /* + * Calculate the equivalent value to sinc3 decimation ratio + * to be written on the SINC3_DEC_RATE register: + * Value = (DEC_RATE / 32) - 1 + */ + dec_rate = DIV_ROUND_UP(dec_rate, 32) - 1; + + /* + * The SINC3_DEC_RATE value is a 13-bit value split across two + * registers: MSB [12:8] and LSB [7:0]. Prepare the 13-bit value using + * FIELD_PREP() and store it with the right endianness in dec_rate_reg. + */ + regval = FIELD_PREP(GENMASK(12, 0), dec_rate); + put_unaligned_be16(regval, dec_rate_reg); + ret = regmap_bulk_write(st->regmap, AD7768_REG_SINC3_DEC_RATE_MSB, + dec_rate_reg, 2); + if (ret) + return ret; + + st->oversampling_ratio = (dec_rate + 1) * 32; + + return 0; +} + +static int ad7768_configure_dig_fil(struct iio_dev *dev, + enum ad7768_filter_type filter_type, + unsigned int dec_rate) +{ + struct ad7768_state *st = iio_priv(dev); + unsigned int dec_rate_idx, dig_filter_regval; + int ret; + + switch (filter_type) { + case AD7768_FILTER_SINC3: + dig_filter_regval = AD7768_DIG_FIL_FIL(AD7768_FILTER_REGVAL_SINC3); + break; + case AD7768_FILTER_SINC3_REJ60: + dig_filter_regval = AD7768_DIG_FIL_FIL(AD7768_FILTER_REGVAL_SINC3) | + AD7768_DIG_FIL_EN_60HZ_REJ; + break; + case AD7768_FILTER_WIDEBAND: + /* Skip decimations 8 and 16, not supported by the wideband filter */ + dec_rate_idx = find_closest(dec_rate, &ad7768_dec_rate_values[2], + ARRAY_SIZE(ad7768_dec_rate_values) - 2); + dig_filter_regval = AD7768_DIG_FIL_FIL(AD7768_FILTER_REGVAL_WIDEBAND) | + AD7768_DIG_FIL_DEC_RATE(dec_rate_idx); + /* Correct the index offset */ + dec_rate_idx += 2; + break; + case AD7768_FILTER_SINC5: + dec_rate_idx = find_closest(dec_rate, ad7768_dec_rate_values, + ARRAY_SIZE(ad7768_dec_rate_values)); + + /* + * Decimations 8 (idx 0) and 16 (idx 1) are set in the + * FILTER[6:4] field. The other decimations are set in the + * DEC_RATE[2:0] field, and the idx needs to be offsetted by two. + */ + if (dec_rate_idx == 0) + dig_filter_regval = AD7768_DIG_FIL_FIL(AD7768_FILTER_REGVAL_SINC5_X8); + else if (dec_rate_idx == 1) + dig_filter_regval = AD7768_DIG_FIL_FIL(AD7768_FILTER_REGVAL_SINC5_X16); + else + dig_filter_regval = AD7768_DIG_FIL_FIL(AD7768_FILTER_REGVAL_SINC5) | + AD7768_DIG_FIL_DEC_RATE(dec_rate_idx - 2); + break; + } + + ret = regmap_write(st->regmap, AD7768_REG_DIGITAL_FILTER, dig_filter_regval); + if (ret) + return ret; + + st->filter_type = filter_type; + /* + * The decimation for SINC3 filters are configured in different + * registers. + */ + if (filter_type == AD7768_FILTER_SINC3 || + filter_type == AD7768_FILTER_SINC3_REJ60) { + ret = ad7768_set_sinc3_dec_rate(st, dec_rate); + if (ret) + return ret; + } else { + st->oversampling_ratio = ad7768_dec_rate_values[dec_rate_idx]; + } + + /* Update scale table: scale values vary according to the precision */ + ret = ad7768_fill_scale_tbl(dev); + if (ret) + return ret; + + ad7768_fill_samp_freq_tbl(st); + + /* A sync-in pulse is required after every configuration change */ + return ad7768_send_sync_pulse(st); +} + +static int ad7768_setup_pga(struct device *dev, struct ad7768_state *st) +{ + st->pga_gpios = devm_gpiod_get_array(dev, "pga", GPIOD_OUT_LOW); + if (IS_ERR(st->pga_gpios)) + return dev_err_probe(dev, PTR_ERR(st->pga_gpios), + "Failed to get PGA gpios.\n"); + + if (st->pga_gpios->ndescs != ADAQ7768_PGA_PINS) + return dev_err_probe(dev, -EINVAL, + "Expected %d GPIOs for PGA control.\n", + ADAQ7768_PGA_PINS); + return 0; +} + +static int ad7768_calc_pga_gain(struct ad7768_state *st, int gain_int, + int gain_fract, int precision) +{ + u64 gain_nano; + u32 tmp; + + gain_nano = gain_int * NANO + gain_fract; + gain_nano = clamp(gain_nano, 0, ADAQ776X_GAIN_MAX_NANO); + tmp = DIV_ROUND_CLOSEST_ULL(gain_nano << precision, NANO); + gain_nano = DIV_ROUND_CLOSEST(st->vref_uv, tmp); + if (st->chip->has_variable_aaf) + gain_nano = DIV_ROUND_CLOSEST_ULL(gain_nano * PERMYRIAD, + ad7768_aaf_gains_bp[st->aaf_gain]); + + return find_closest(gain_nano, st->chip->pga_gains, + (int)st->chip->num_pga_modes); +} + +static int ad7768_set_pga_gain(struct ad7768_state *st, + int gain_mode) +{ + int pgia_pins_value = abs(gain_mode - st->chip->pgia_mode2pin_offset); + DECLARE_BITMAP(bitmap, ADAQ7768_PGA_PINS) = { }; + int ret; + + guard(mutex)(&st->pga_lock); + + bitmap_write(bitmap, pgia_pins_value, 0, ADAQ7768_PGA_PINS); + ret = gpiod_multi_set_value_cansleep(st->pga_gpios, bitmap); + if (ret) + return ret; + + st->pga_gain_mode = gain_mode; + + return 0; +} + +static int ad7768_gpio_direction_input(struct gpio_chip *chip, unsigned int offset) +{ + struct iio_dev *indio_dev = gpiochip_get_data(chip); + struct ad7768_state *st = iio_priv(indio_dev); + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = regmap_clear_bits(st->regmap, AD7768_REG_GPIO_CONTROL, + BIT(offset)); + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ad7768_gpio_direction_output(struct gpio_chip *chip, + unsigned int offset, int value) +{ + struct iio_dev *indio_dev = gpiochip_get_data(chip); + struct ad7768_state *st = iio_priv(indio_dev); + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = regmap_set_bits(st->regmap, AD7768_REG_GPIO_CONTROL, + BIT(offset)); + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ad7768_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct iio_dev *indio_dev = gpiochip_get_data(chip); + struct ad7768_state *st = iio_priv(indio_dev); + unsigned int val; + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = regmap_read(st->regmap, AD7768_REG_GPIO_CONTROL, &val); + if (ret) + goto err_release; + + /* + * If the GPIO is configured as an output, read the current value from + * AD7768_REG_GPIO_WRITE. Otherwise, read the input value from + * AD7768_REG_GPIO_READ. + */ + if (val & BIT(offset)) + ret = regmap_read(st->regmap, AD7768_REG_GPIO_WRITE, &val); + else + ret = regmap_read(st->regmap, AD7768_REG_GPIO_READ, &val); + if (ret) + goto err_release; + + ret = !!(val & BIT(offset)); +err_release: + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ad7768_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) +{ + struct iio_dev *indio_dev = gpiochip_get_data(chip); + struct ad7768_state *st = iio_priv(indio_dev); + unsigned int val; + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = regmap_read(st->regmap, AD7768_REG_GPIO_CONTROL, &val); + if (ret) + goto err_release; + + if (val & BIT(offset)) + ret = regmap_assign_bits(st->regmap, AD7768_REG_GPIO_WRITE, + BIT(offset), value); + +err_release: + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ad7768_gpio_init(struct iio_dev *indio_dev) +{ + struct ad7768_state *st = iio_priv(indio_dev); + int ret; + + ret = regmap_write(st->regmap, AD7768_REG_GPIO_CONTROL, + AD7768_GPIO_UNIVERSAL_EN); + if (ret) + return ret; + + st->gpiochip = (struct gpio_chip) { + .label = "ad7768_1_gpios", + .base = -1, + .ngpio = 4, + .parent = &st->spi->dev, + .can_sleep = true, + .direction_input = ad7768_gpio_direction_input, + .direction_output = ad7768_gpio_direction_output, + .get = ad7768_gpio_get, + .set = ad7768_gpio_set, + .owner = THIS_MODULE, + }; + + return devm_gpiochip_add_data(&st->spi->dev, &st->gpiochip, indio_dev); +} + +static int ad7768_set_freq(struct ad7768_state *st, + unsigned int freq) +{ + unsigned int idx, mclk_div; + int ret; + + freq = clamp(freq, 50, 1024000); + + mclk_div = DIV_ROUND_CLOSEST(st->mclk_freq, freq * st->oversampling_ratio); + /* Find the closest match for the desired sampling frequency */ + idx = find_closest_descending(mclk_div, ad7768_mclk_div_rates, + ARRAY_SIZE(ad7768_mclk_div_rates)); + /* Set both the mclk_div and pwrmode */ + ret = ad7768_set_mclk_div(st, idx); + if (ret) + return ret; + + st->samp_freq = DIV_ROUND_CLOSEST(st->mclk_freq, + ad7768_mclk_div_rates[idx] * st->oversampling_ratio); + + /* A sync-in pulse is required after every configuration change */ + return ad7768_send_sync_pulse(st); +} + +static int ad7768_set_filter_type_attr(struct iio_dev *dev, + const struct iio_chan_spec *chan, + unsigned int filter) +{ + struct ad7768_state *st = iio_priv(dev); + int ret; + + ret = ad7768_configure_dig_fil(dev, filter, st->oversampling_ratio); + if (ret) + return ret; + + /* Update sampling frequency */ + return ad7768_set_freq(st, st->samp_freq); +} + +static int ad7768_get_filter_type_attr(struct iio_dev *dev, + const struct iio_chan_spec *chan) +{ + struct ad7768_state *st = iio_priv(dev); + int ret; + unsigned int mode, mask; + + ret = regmap_read(st->regmap, AD7768_REG_DIGITAL_FILTER, &mode); + if (ret) + return ret; + + mask = AD7768_DIG_FIL_EN_60HZ_REJ | AD7768_DIG_FIL_FIL_MSK; + /* From the register value, get the corresponding filter type */ + return ad7768_filter_regval_to_type[FIELD_GET(mask, mode)]; +} + +static int ad7768_update_dec_rate(struct iio_dev *dev, unsigned int dec_rate) +{ + struct ad7768_state *st = iio_priv(dev); + int ret; + + ret = ad7768_configure_dig_fil(dev, st->filter_type, dec_rate); + if (ret) + return ret; + + /* Update sampling frequency */ + return ad7768_set_freq(st, st->samp_freq); +} + +static const struct iio_enum ad7768_filter_type_iio_enum = { + .items = ad7768_filter_enum, + .num_items = ARRAY_SIZE(ad7768_filter_enum), + .set = ad7768_set_filter_type_attr, + .get = ad7768_get_filter_type_attr, +}; + +static const struct iio_chan_spec_ext_info ad7768_ext_info[] = { + IIO_ENUM("filter_type", IIO_SHARED_BY_ALL, &ad7768_filter_type_iio_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_ALL, &ad7768_filter_type_iio_enum), + { } +}; + +#define AD7768_CHAN(_idx, _msk_avail) \ +{ \ + .type = IIO_VOLTAGE, \ + .info_mask_separate_available = _msk_avail, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .ext_info = ad7768_ext_info, \ + .indexed = 1, \ + .channel = _idx, \ + .scan_index = _idx, \ + .has_ext_scan_type = 1, \ + .ext_scan_type = ad7768_scan_type, \ + .num_ext_scan_type = ARRAY_SIZE(ad7768_scan_type), \ +} + +static const struct iio_chan_spec ad7768_channels[] = { + AD7768_CHAN(0, 0), +}; + +static const struct iio_chan_spec adaq776x_channels[] = { + AD7768_CHAN(0, BIT(IIO_CHAN_INFO_SCALE)), +}; + +static int ad7768_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad7768_state *st = iio_priv(indio_dev); + const struct iio_scan_type *scan_type; + int ret, temp; + + scan_type = iio_get_current_scan_type(indio_dev, chan); + if (IS_ERR(scan_type)) + return PTR_ERR(scan_type); + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad7768_scan_direct(indio_dev); + + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + *val = sign_extend32(ret, scan_type->realbits - 1); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + if (st->chip->has_pga) { + guard(mutex)(&st->pga_lock); + + *val = st->scale_tbl[st->pga_gain_mode][0]; + *val2 = st->scale_tbl[st->pga_gain_mode][1]; + return IIO_VAL_INT_PLUS_NANO; + } + + temp = (st->vref_uv * 2) / 1000; + if (st->chip->has_variable_aaf) + temp = (temp * PERMYRIAD) / ad7768_aaf_gains_bp[st->aaf_gain]; + + *val = temp; + *val2 = scan_type->realbits; + + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = st->samp_freq; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = st->oversampling_ratio; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + temp = st->samp_freq * ad7768_filter_3db_odr_multiplier[st->filter_type]; + *val = DIV_ROUND_CLOSEST(temp, MILLI); + + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int ad7768_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long info) +{ + struct ad7768_state *st = iio_priv(indio_dev); + unsigned int shift; + + switch (info) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + /* + * Sinc3 filter allows a wider range of OSR values, so show + * the available values in range format. + */ + if (st->filter_type == AD7768_FILTER_SINC3 || + st->filter_type == AD7768_FILTER_SINC3_REJ60) { + *vals = (int *)ad7768_sinc3_dec_rate_range; + *type = IIO_VAL_INT; + return IIO_AVAIL_RANGE; + } + + shift = st->filter_type == AD7768_FILTER_SINC5 ? 0 : 2; + *vals = (int *)&ad7768_dec_rate_values[shift]; + *length = ARRAY_SIZE(ad7768_dec_rate_values) - shift; + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = (int *)st->samp_freq_avail; + *length = st->samp_freq_avail_len; + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SCALE: + *vals = (int *)st->scale_tbl; + *length = st->chip->num_pga_modes * 2; + *type = IIO_VAL_INT_PLUS_NANO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int ad7768_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT_PLUS_MICRO; + } +} + +static int ad7768_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct ad7768_state *st = iio_priv(indio_dev); + const struct iio_scan_type *scan_type; + int ret; + + scan_type = iio_get_current_scan_type(indio_dev, chan); + if (IS_ERR(scan_type)) + return PTR_ERR(scan_type); + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad7768_set_freq(st, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad7768_update_dec_rate(indio_dev, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SCALE: { + int gain_mode; + + if (!st->chip->has_pga) + return -EOPNOTSUPP; + + if (scan_type->sign == 's') + gain_mode = ad7768_calc_pga_gain(st, val, val2, + scan_type->realbits - 1); + else + gain_mode = ad7768_calc_pga_gain(st, val, val2, + scan_type->realbits); + + return ad7768_set_pga_gain(st, gain_mode); + } + default: + return -EINVAL; + } +} + +static int ad7768_read_label(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, char *label) +{ + struct ad7768_state *st = iio_priv(indio_dev); + + return sysfs_emit(label, "%s\n", st->labels[chan->channel]); +} + +static int ad7768_get_current_scan_type(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad7768_state *st = iio_priv(indio_dev); + + if (st->offload) + return st->oversampling_ratio == 8 ? + AD7768_SCAN_TYPE_OFFLOAD_HIGH_SPEED : AD7768_SCAN_TYPE_OFFLOAD_NORMAL; + + return st->oversampling_ratio == 8 ? + AD7768_SCAN_TYPE_HIGH_SPEED : AD7768_SCAN_TYPE_NORMAL; +} + +static const struct iio_info ad7768_info = { + .read_raw = &ad7768_read_raw, + .read_avail = &ad7768_read_avail, + .write_raw = &ad7768_write_raw, + .write_raw_get_fmt = &ad7768_write_raw_get_fmt, + .read_label = ad7768_read_label, + .get_current_scan_type = &ad7768_get_current_scan_type, + .debugfs_reg_access = &ad7768_reg_access, +}; + +static struct fwnode_handle * +ad7768_fwnode_find_reference_args(const struct fwnode_handle *fwnode, + const char *name, const char *nargs_prop, + unsigned int nargs, unsigned int index, + struct fwnode_reference_args *args) +{ + int ret; + + ret = fwnode_property_get_reference_args(fwnode, name, nargs_prop, + nargs, index, args); + return ret ? ERR_PTR(ret) : args->fwnode; +} + +static int ad7768_trigger_sources_sync_setup(struct device *dev, + struct fwnode_handle *fwnode, + struct ad7768_state *st) +{ + struct fwnode_reference_args args; + + struct fwnode_handle *ref __free(fwnode_handle) = + ad7768_fwnode_find_reference_args(fwnode, "trigger-sources", + "#trigger-source-cells", 0, + AD7768_TRIGGER_SOURCE_SYNC_IDX, + &args); + if (IS_ERR(ref)) + return PTR_ERR(ref); + + ref = args.fwnode; + /* First, try getting the GPIO trigger source */ + if (fwnode_device_is_compatible(ref, "gpio-trigger")) { + st->gpio_sync_in = devm_fwnode_gpiod_get_index(dev, ref, NULL, 0, + GPIOD_OUT_LOW, + "sync-in"); + return PTR_ERR_OR_ZERO(st->gpio_sync_in); + } + + /* + * TODO: Support the other cases when we have a trigger subsystem + * to reliably handle other types of devices as trigger sources. + * + * For now, return an error message. For self triggering, omit the + * trigger-sources property. + */ + return dev_err_probe(dev, -EOPNOTSUPP, "Invalid synchronization trigger source\n"); +} + +static int ad7768_trigger_sources_get_sync(struct device *dev, + struct ad7768_state *st) +{ + struct fwnode_handle *fwnode = dev_fwnode(dev); + + /* + * The AD7768-1 allows two primary methods for driving the SYNC_IN pin + * to synchronize one or more devices: + * 1. Using an external GPIO. + * 2. Using a SPI command, where the SYNC_OUT pin generates a + * synchronization pulse that drives the SYNC_IN pin. + */ + if (fwnode_property_present(fwnode, "trigger-sources")) + return ad7768_trigger_sources_sync_setup(dev, fwnode, st); + + /* + * In the absence of trigger-sources property, enable self + * synchronization over SPI (SYNC_OUT). + */ + st->en_spi_sync = true; + + return 0; +} + +static int ad7768_setup(struct iio_dev *indio_dev) +{ + struct ad7768_state *st = iio_priv(indio_dev); + int ret; + + st->gpio_reset = devm_gpiod_get_optional(&st->spi->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(st->gpio_reset)) + return PTR_ERR(st->gpio_reset); + + if (st->gpio_reset) { + fsleep(10); + gpiod_set_value_cansleep(st->gpio_reset, 0); + fsleep(200); + } else { + /* + * Two writes to the SPI_RESET[1:0] bits are required to initiate + * a software reset. The bits must first be set to 11, and then + * to 10. When the sequence is detected, the reset occurs. + * See the datasheet, page 70. + */ + ret = regmap_write(st->regmap, AD7768_REG_SYNC_RESET, 0x3); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD7768_REG_SYNC_RESET, 0x2); + if (ret) + return ret; + } + + ret = ad7768_set_mode(st, AD7768_CONTINUOUS); + if (ret) + return ret; + + /* For backwards compatibility, try the adi,sync-in-gpios property */ + st->gpio_sync_in = devm_gpiod_get_optional(&st->spi->dev, "adi,sync-in", + GPIOD_OUT_LOW); + if (IS_ERR(st->gpio_sync_in)) + return PTR_ERR(st->gpio_sync_in); + + /* + * If the synchronization is not defined by adi,sync-in-gpios, try the + * trigger-sources. + */ + if (!st->gpio_sync_in) { + ret = ad7768_trigger_sources_get_sync(&st->spi->dev, st); + if (ret) + return ret; + } + + /* Only create a Chip GPIO if flagged for it */ + if (device_property_read_bool(&st->spi->dev, "gpio-controller")) { + ret = ad7768_gpio_init(indio_dev); + if (ret) + return ret; + } + + /* + * Set Default Digital Filter configuration: + * SINC5 filter with x32 Decimation rate + */ + ret = ad7768_configure_dig_fil(indio_dev, AD7768_FILTER_SINC5, 32); + if (ret) + return ret; + + /* Set the default sampling frequency to 32000 kSPS */ + return ad7768_set_freq(st, 32000); +} + +static irqreturn_t ad7768_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad7768_state *st = iio_priv(indio_dev); + const struct iio_scan_type *scan_type; + int ret; + + scan_type = iio_get_current_scan_type(indio_dev, &indio_dev->channels[0]); + if (IS_ERR(scan_type)) + goto out; + + ret = spi_read(st->spi, &st->data.scan.chan, + BITS_TO_BYTES(scan_type->realbits)); + if (ret < 0) + goto out; + + iio_push_to_buffers_with_ts(indio_dev, &st->data.scan, + sizeof(st->data.scan), + iio_get_time_ns(indio_dev)); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static irqreturn_t ad7768_interrupt(int irq, void *dev_id) +{ + struct iio_dev *indio_dev = dev_id; + struct ad7768_state *st = iio_priv(indio_dev); + + if (iio_buffer_enabled(indio_dev)) + iio_trigger_poll(st->trig); + else + complete(&st->completion); + + return IRQ_HANDLED; +}; + +static int ad7768_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad7768_state *st = iio_priv(indio_dev); + + /* + * Write a 1 to the LSB of the INTERFACE_FORMAT register to enter + * continuous read mode. Subsequent data reads do not require an + * initial 8-bit write to query the ADC_DATA register. + */ + return regmap_write(st->regmap, AD7768_REG_INTERFACE_FORMAT, 0x01); +} + +static int ad7768_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad7768_state *st = iio_priv(indio_dev); + unsigned int unused; + + /* + * To exit continuous read mode, perform a single read of the ADC_DATA + * reg (0x2C), which allows further configuration of the device. + */ + return regmap_read(st->regmap24, AD7768_REG24_ADC_DATA, &unused); +} + +static const struct iio_buffer_setup_ops ad7768_buffer_ops = { + .postenable = &ad7768_buffer_postenable, + .predisable = &ad7768_buffer_predisable, +}; + +static int ad7768_offload_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad7768_state *st = iio_priv(indio_dev); + struct spi_offload_trigger_config config = { + .type = SPI_OFFLOAD_TRIGGER_DATA_READY, + }; + const struct iio_scan_type *scan_type; + unsigned int unused; + int ret; + + scan_type = iio_get_current_scan_type(indio_dev, &indio_dev->channels[0]); + if (IS_ERR(scan_type)) + return PTR_ERR(scan_type); + + st->offload_xfer.len = spi_bpw_to_bytes(scan_type->realbits); + st->offload_xfer.bits_per_word = scan_type->realbits; + st->offload_xfer.offload_flags = SPI_OFFLOAD_XFER_RX_STREAM; + + spi_message_init_with_transfers(&st->offload_msg, &st->offload_xfer, 1); + st->offload_msg.offload = st->offload; + + ret = spi_optimize_message(st->spi, &st->offload_msg); + if (ret) { + dev_err(&st->spi->dev, "failed to prepare offload, err: %d\n", ret); + return ret; + } + + /* + * Write a 1 to the LSB of the INTERFACE_FORMAT register to enter + * continuous read mode. Subsequent data reads do not require an + * initial 8-bit write to query the ADC_DATA register. + */ + ret = regmap_write(st->regmap, AD7768_REG_INTERFACE_FORMAT, 0x01); + if (ret) + goto err_unoptimize_message; + + ret = spi_offload_trigger_enable(st->offload, st->offload_trigger, + &config); + if (ret) + goto err_exit_continuous_read_mode; + + return 0; + +err_exit_continuous_read_mode: + regmap_read(st->regmap24, AD7768_REG24_ADC_DATA, &unused); + +err_unoptimize_message: + spi_unoptimize_message(&st->offload_msg); + + return ret; +} + +static int ad7768_offload_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad7768_state *st = iio_priv(indio_dev); + unsigned int unused; + + spi_offload_trigger_disable(st->offload, st->offload_trigger); + spi_unoptimize_message(&st->offload_msg); + + /* + * To exit continuous read mode, perform a single read of the ADC_DATA + * reg (0x2C), which allows further configuration of the device. + */ + return regmap_read(st->regmap24, AD7768_REG24_ADC_DATA, &unused); +} + +static const struct iio_buffer_setup_ops ad7768_offload_buffer_ops = { + .postenable = ad7768_offload_buffer_postenable, + .predisable = ad7768_offload_buffer_predisable, +}; + +static int ad7768_set_trigger_state(struct iio_trigger *trig, bool enable) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct ad7768_state *st = iio_priv(indio_dev); + + if (enable) + enable_irq(st->spi->irq); + else + disable_irq(st->spi->irq); + + return 0; +} + +static const struct iio_trigger_ops ad7768_trigger_ops = { + .validate_device = iio_trigger_validate_own_device, + .set_trigger_state = ad7768_set_trigger_state, +}; + +static int ad7768_set_channel_label(struct iio_dev *indio_dev, + int num_channels) +{ + struct ad7768_state *st = iio_priv(indio_dev); + struct device *device = indio_dev->dev.parent; + const char *label; + int crt_ch = 0; + + device_for_each_child_node_scoped(device, child) { + if (fwnode_property_read_u32(child, "reg", &crt_ch)) + continue; + + if (crt_ch >= num_channels) + continue; + + if (fwnode_property_read_string(child, "label", &label)) + continue; + + st->labels[crt_ch] = label; + } + + return 0; +} + +static int ad7768_triggered_buffer_alloc(struct iio_dev *indio_dev) +{ + struct ad7768_state *st = iio_priv(indio_dev); + int ret; + + st->trig = devm_iio_trigger_alloc(indio_dev->dev.parent, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!st->trig) + return -ENOMEM; + + st->trig->ops = &ad7768_trigger_ops; + iio_trigger_set_drvdata(st->trig, indio_dev); + ret = devm_iio_trigger_register(indio_dev->dev.parent, st->trig); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(st->trig); + + return devm_iio_triggered_buffer_setup(indio_dev->dev.parent, indio_dev, + &iio_pollfunc_store_time, + &ad7768_trigger_handler, + &ad7768_buffer_ops); +} + +static int ad7768_vcm_enable(struct regulator_dev *rdev) +{ + struct iio_dev *indio_dev = rdev_get_drvdata(rdev); + struct ad7768_state *st = iio_priv(indio_dev); + int ret, regval; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + /* To enable, set the last selected output */ + regval = AD7768_REG_ANALOG2_VCM(st->vcm_output_sel + 1); + ret = regmap_update_bits(st->regmap, AD7768_REG_ANALOG2, + AD7768_REG_ANALOG2_VCM_MSK, regval); + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ad7768_vcm_disable(struct regulator_dev *rdev) +{ + struct iio_dev *indio_dev = rdev_get_drvdata(rdev); + struct ad7768_state *st = iio_priv(indio_dev); + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = regmap_update_bits(st->regmap, AD7768_REG_ANALOG2, + AD7768_REG_ANALOG2_VCM_MSK, AD7768_VCM_OFF); + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ad7768_vcm_is_enabled(struct regulator_dev *rdev) +{ + struct iio_dev *indio_dev = rdev_get_drvdata(rdev); + struct ad7768_state *st = iio_priv(indio_dev); + int ret, val; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = regmap_read(st->regmap, AD7768_REG_ANALOG2, &val); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + + return FIELD_GET(AD7768_REG_ANALOG2_VCM_MSK, val) != AD7768_VCM_OFF; +} + +static int ad7768_set_voltage_sel(struct regulator_dev *rdev, + unsigned int selector) +{ + unsigned int regval = AD7768_REG_ANALOG2_VCM(selector + 1); + struct iio_dev *indio_dev = rdev_get_drvdata(rdev); + struct ad7768_state *st = iio_priv(indio_dev); + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = regmap_update_bits(st->regmap, AD7768_REG_ANALOG2, + AD7768_REG_ANALOG2_VCM_MSK, regval); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + + st->vcm_output_sel = selector; + + return 0; +} + +static int ad7768_get_voltage_sel(struct regulator_dev *rdev) +{ + struct iio_dev *indio_dev = rdev_get_drvdata(rdev); + struct ad7768_state *st = iio_priv(indio_dev); + int ret, val; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = regmap_read(st->regmap, AD7768_REG_ANALOG2, &val); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + + val = FIELD_GET(AD7768_REG_ANALOG2_VCM_MSK, val); + + return clamp(val, 1, rdev->desc->n_voltages) - 1; +} + +static const struct regulator_ops vcm_regulator_ops = { + .enable = ad7768_vcm_enable, + .disable = ad7768_vcm_disable, + .is_enabled = ad7768_vcm_is_enabled, + .list_voltage = regulator_list_voltage_table, + .set_voltage_sel = ad7768_set_voltage_sel, + .get_voltage_sel = ad7768_get_voltage_sel, +}; + +static const unsigned int vcm_voltage_table[] = { + 2500000, + 2050000, + 1650000, + 1900000, + 1100000, + 900000, +}; + +static const struct regulator_desc vcm_desc = { + .name = "ad7768-1-vcm", + .of_match = "vcm-output", + .regulators_node = "regulators", + .n_voltages = ARRAY_SIZE(vcm_voltage_table), + .volt_table = vcm_voltage_table, + .ops = &vcm_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, +}; + +static int ad7768_register_vcm_regulator(struct device *dev, + struct ad7768_state *st, + struct iio_dev *indio_dev) +{ + struct regulator_config config = { + .dev = dev, + .driver_data = indio_dev, + }; + int ret; + + /* Disable the regulator before registering it */ + ret = regmap_update_bits(st->regmap, AD7768_REG_ANALOG2, + AD7768_REG_ANALOG2_VCM_MSK, AD7768_VCM_OFF); + if (ret) + return ret; + + st->vcm_rdev = devm_regulator_register(dev, &vcm_desc, &config); + if (IS_ERR(st->vcm_rdev)) + return dev_err_probe(dev, PTR_ERR(st->vcm_rdev), + "failed to register VCM regulator\n"); + + return 0; +} + +static int ad7768_parse_aaf_gain(struct device *dev, struct ad7768_state *st) +{ + u32 val; + int ret; + + ret = device_property_read_u32(dev, "adi,aaf-gain-bp", &val); + if (ret == -EINVAL) { + /* If controllable, use default */ + if (st->chip->has_variable_aaf) + st->aaf_gain = AD7768_AAF_IN1; + return 0; + } + if (ret) + return dev_err_probe(dev, ret, "Failed to get AAF gain value\n"); + + if (!st->chip->has_variable_aaf) + return dev_err_probe(dev, -EOPNOTSUPP, + "AAF gain provided, but not supported for %s\n", st->chip->name); + + switch (val) { + case 10000: + st->aaf_gain = AD7768_AAF_IN1; + break; + case 3640: + st->aaf_gain = AD7768_AAF_IN2; + break; + case 1430: + st->aaf_gain = AD7768_AAF_IN3; + break; + default: + return dev_err_probe(dev, -EINVAL, "Invalid firmware provided AAF gain\n"); + } + + return 0; +} + +static bool ad7768_offload_trigger_match(struct spi_offload_trigger *trigger, + enum spi_offload_trigger_type type, + u64 *args, u32 nargs) +{ + if (type != SPI_OFFLOAD_TRIGGER_DATA_READY) + return false; + + /* Up to 2 args are allowed, but only 1 is used */ + if (nargs == 0 || nargs > 2 || args[0] != AD7768_TRIGGER_SOURCE_DRDY) + return false; + + return true; +} + +static int ad7768_offload_trigger_request(struct spi_offload_trigger *trigger, + enum spi_offload_trigger_type type, + u64 *args, u32 nargs) +{ + /* Should already be validated by match, but just in case */ + if (nargs == 0 || nargs > 2) + return -EINVAL; + + return 0; +} + +static const struct spi_offload_trigger_ops ad7768_offload_trigger_ops = { + .match = ad7768_offload_trigger_match, + .request = ad7768_offload_trigger_request, +}; + +static const struct ad7768_chip_info ad7768_chip_info = { + .name = "ad7768-1", + .channel_spec = ad7768_channels, + .num_channels = ARRAY_SIZE(ad7768_channels), + .has_vcm_regulator = true, +}; + +static const struct ad7768_chip_info adaq7767_chip_info = { + .name = "adaq7767-1", + .channel_spec = ad7768_channels, + .num_channels = ARRAY_SIZE(ad7768_channels), + .has_variable_aaf = true, +}; + +static const struct ad7768_chip_info adaq7768_chip_info = { + .name = "adaq7768-1", + .channel_spec = adaq776x_channels, + .num_channels = ARRAY_SIZE(adaq776x_channels), + .pga_gains = adaq7768_gains, + .default_pga_mode = AD7768_PGA_GAIN_2, + .num_pga_modes = ARRAY_SIZE(adaq7768_gains), + .pgia_mode2pin_offset = 6, + .has_pga = true, +}; + +static const struct ad7768_chip_info adaq7769_chip_info = { + .name = "adaq7769-1", + .channel_spec = adaq776x_channels, + .num_channels = ARRAY_SIZE(adaq776x_channels), + .pga_gains = adaq7769_gains, + .default_pga_mode = AD7768_PGA_GAIN_0, + .num_pga_modes = ARRAY_SIZE(adaq7769_gains), + .pgia_mode2pin_offset = 0, + .has_pga = true, + .has_variable_aaf = true, +}; + +static const struct spi_offload_config ad7768_spi_offload_config = { + .capability_flags = SPI_OFFLOAD_CAP_TRIGGER | SPI_OFFLOAD_CAP_RX_STREAM_DMA, +}; + +static int ad7768_spi_offload_probe(struct iio_dev *indio_dev, + struct ad7768_state *st) +{ + struct device *dev = &st->spi->dev; + struct spi_offload_trigger_info trigger_info = { + .fwnode = dev_fwnode(dev), + .ops = &ad7768_offload_trigger_ops, + .priv = st, + }; + struct dma_chan *rx_dma; + int ret; + + ret = devm_spi_offload_trigger_register(dev, &trigger_info); + if (ret) + return dev_err_probe(dev, ret, "failed to register offload trigger\n"); + + st->offload_trigger = devm_spi_offload_trigger_get(dev, st->offload, + SPI_OFFLOAD_TRIGGER_DATA_READY); + if (IS_ERR(st->offload_trigger)) + return dev_err_probe(dev, PTR_ERR(st->offload_trigger), + "failed to get offload trigger\n"); + + rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev, st->offload); + if (IS_ERR(rx_dma)) + return dev_err_probe(dev, PTR_ERR(rx_dma), "failed to get offload RX DMA\n"); + + ret = devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, rx_dma, + IIO_BUFFER_DIRECTION_IN); + if (ret) + return dev_err_probe(dev, ret, "failed to setup offload RX DMA\n"); + + indio_dev->setup_ops = &ad7768_offload_buffer_ops; + + return 0; +} + +static int ad7768_probe(struct spi_device *spi) +{ + struct ad7768_state *st; + struct iio_dev *indio_dev; + struct device *dev = &spi->dev; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + /* + * Datasheet recommends SDI line to be kept high when data is not being + * clocked out of the controller and the spi clock is free running, + * to prevent accidental reset. + * Since many controllers do not support the SPI_MOSI_IDLE_HIGH flag + * yet, only request the MOSI idle state to enable if the controller + * supports it. + */ + if (spi->controller->mode_bits & SPI_MOSI_IDLE_HIGH) { + spi->mode |= SPI_MOSI_IDLE_HIGH; + ret = spi_setup(spi); + if (ret < 0) + return ret; + } + + st->chip = spi_get_device_match_data(spi); + st->spi = spi; + + st->regmap = devm_regmap_init_spi(spi, &ad7768_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(&spi->dev, PTR_ERR(st->regmap), + "Failed to initialize regmap"); + + st->regmap24 = devm_regmap_init_spi(spi, &ad7768_regmap24_config); + if (IS_ERR(st->regmap24)) + return dev_err_probe(&spi->dev, PTR_ERR(st->regmap24), + "Failed to initialize regmap24"); + + ret = devm_regulator_get_enable_read_voltage(&spi->dev, "vref"); + if (ret < 0) + return dev_err_probe(&spi->dev, ret, + "Failed to get VREF voltage\n"); + st->vref_uv = ret; + + st->mclk = devm_clk_get_enabled(&spi->dev, "mclk"); + if (IS_ERR(st->mclk)) + return PTR_ERR(st->mclk); + + st->mclk_freq = clk_get_rate(st->mclk); + + indio_dev->channels = st->chip->channel_spec; + indio_dev->num_channels = st->chip->num_channels; + indio_dev->name = st->chip->name; + indio_dev->info = &ad7768_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + /* Register VCM output regulator */ + if (st->chip->has_vcm_regulator) { + ret = ad7768_register_vcm_regulator(&spi->dev, st, indio_dev); + if (ret) + return ret; + } + + ret = ad7768_parse_aaf_gain(&spi->dev, st); + if (ret) + return ret; + + ret = ad7768_setup(indio_dev); + if (ret < 0) + return dev_err_probe(dev, ret, "AD7768 setup failed\n"); + + init_completion(&st->completion); + ret = devm_mutex_init(&spi->dev, &st->pga_lock); + if (ret) + return ret; + + if (st->chip->has_pga) { + ret = ad7768_setup_pga(&spi->dev, st); + if (ret) + return ret; + + ret = ad7768_set_pga_gain(st, st->chip->default_pga_mode); + if (ret) + return ret; + } + + ret = ad7768_set_channel_label(indio_dev, st->chip->num_channels); + if (ret) + return ret; + + ret = devm_request_irq(&spi->dev, spi->irq, &ad7768_interrupt, + IRQF_TRIGGER_RISING | IRQF_NO_THREAD | IRQF_NO_AUTOEN, + indio_dev->name, indio_dev); + if (ret) + return ret; + + st->offload = devm_spi_offload_get(dev, spi, &ad7768_spi_offload_config); + ret = PTR_ERR_OR_ZERO(st->offload); + if (ret == -ENODEV) { + /* If not using SPI offload, fall back to low speed usage */ + ret = ad7768_triggered_buffer_alloc(indio_dev); + if (ret) + return ret; + } else if (ret) { + return dev_err_probe(dev, ret, "failed to get SPI offload\n"); + } else { + ret = ad7768_spi_offload_probe(indio_dev, st); + if (ret) + return ret; + } + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id ad7768_id_table[] = { + { .name = "ad7768-1", .driver_data = (kernel_ulong_t)&ad7768_chip_info }, + { .name = "adaq7767-1", .driver_data = (kernel_ulong_t)&adaq7767_chip_info }, + { .name = "adaq7768-1", .driver_data = (kernel_ulong_t)&adaq7768_chip_info }, + { .name = "adaq7769-1", .driver_data = (kernel_ulong_t)&adaq7769_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7768_id_table); + +static const struct of_device_id ad7768_of_match[] = { + { .compatible = "adi,ad7768-1", .data = &ad7768_chip_info }, + { .compatible = "adi,adaq7767-1", .data = &adaq7767_chip_info }, + { .compatible = "adi,adaq7768-1", .data = &adaq7768_chip_info }, + { .compatible = "adi,adaq7769-1", .data = &adaq7769_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7768_of_match); + +static struct spi_driver ad7768_driver = { + .driver = { + .name = "ad7768-1", + .of_match_table = ad7768_of_match, + }, + .probe = ad7768_probe, + .id_table = ad7768_id_table, +}; +module_spi_driver(ad7768_driver); + +MODULE_AUTHOR("Stefan Popa "); +MODULE_DESCRIPTION("Analog Devices AD7768-1 ADC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER"); diff --git a/drivers/iio/adc/ad7779.c b/drivers/iio/adc/ad7779.c new file mode 100644 index 000000000..79fb38d0f --- /dev/null +++ b/drivers/iio/adc/ad7779.c @@ -0,0 +1,1045 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * AD7770, AD7771, AD7779 ADC + * + * Copyright 2023-2024 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define AD7779_SPI_READ_CMD BIT(7) + +#define AD7779_DISABLE_SD BIT(7) + +#define AD7779_REG_CH_DISABLE 0x08 +#define AD7779_REG_CH_SYNC_OFFSET(ch) (0x09 + (ch)) +#define AD7779_REG_CH_CONFIG(ch) (0x00 + (ch)) +#define AD7779_REG_GENERAL_USER_CONFIG_1 0x11 +#define AD7779_REG_GENERAL_USER_CONFIG_2 0x12 +#define AD7779_REG_GENERAL_USER_CONFIG_3 0x13 +#define AD7779_REG_DOUT_FORMAT 0x14 +#define AD7779_REG_ADC_MUX_CONFIG 0x15 +#define AD7779_REG_GPIO_CONFIG 0x17 +#define AD7779_REG_BUFFER_CONFIG_1 0x19 +#define AD7779_REG_GLOBAL_MUX_CONFIG 0x16 +#define AD7779_REG_BUFFER_CONFIG_2 0x1A +#define AD7779_REG_GPIO_DATA 0x18 +#define AD7779_REG_CH_OFFSET_UPPER_BYTE(ch) (0x1C + (ch) * 6) +#define AD7779_REG_CH_OFFSET_LOWER_BYTE(ch) (0x1E + (ch) * 6) +#define AD7779_REG_CH_GAIN_UPPER_BYTE(ch) (0x1F + (ch) * 6) +#define AD7779_REG_CH_OFFSET_MID_BYTE(ch) (0x1D + (ch) * 6) +#define AD7779_REG_CH_GAIN_MID_BYTE(ch) (0x20 + (ch) * 6) +#define AD7779_REG_CH_ERR_REG(ch) (0x4C + (ch)) +#define AD7779_REG_CH0_1_SAT_ERR 0x54 +#define AD7779_REG_CH_GAIN_LOWER_BYTE(ch) (0x21 + (ch) * 6) +#define AD7779_REG_CH2_3_SAT_ERR 0x55 +#define AD7779_REG_CH4_5_SAT_ERR 0x56 +#define AD7779_REG_CH6_7_SAT_ERR 0x57 +#define AD7779_REG_CHX_ERR_REG_EN 0x58 +#define AD7779_REG_GEN_ERR_REG_1 0x59 +#define AD7779_REG_GEN_ERR_REG_1_EN 0x5A +#define AD7779_REG_GEN_ERR_REG_2 0x5B +#define AD7779_REG_GEN_ERR_REG_2_EN 0x5C +#define AD7779_REG_STATUS_REG_1 0x5D +#define AD7779_REG_STATUS_REG_2 0x5E +#define AD7779_REG_STATUS_REG_3 0x5F +#define AD7779_REG_SRC_N_MSB 0x60 +#define AD7779_REG_SRC_N_LSB 0x61 +#define AD7779_REG_SRC_IF_MSB 0x62 +#define AD7779_REG_SRC_IF_LSB 0x63 +#define AD7779_REG_SRC_UPDATE 0x64 + +#define AD7779_FILTER_MSK BIT(6) +#define AD7779_MOD_POWERMODE_MSK BIT(6) +#define AD7779_MOD_PDB_REFOUT_MSK BIT(4) +#define AD7779_MOD_SPI_EN_MSK BIT(4) +#define AD7779_USRMOD_INIT_MSK GENMASK(6, 4) + +/* AD7779_REG_DOUT_FORMAT */ +#define AD7779_DOUT_FORMAT_MSK GENMASK(7, 6) +#define AD7779_DOUT_HEADER_FORMAT BIT(5) +#define AD7779_DCLK_CLK_DIV_MSK GENMASK(3, 1) + +#define AD7779_REFMUX_CTRL_MSK GENMASK(7, 6) +#define AD7779_SPI_CRC_EN_MSK BIT(0) + +#define AD7779_MAXCLK_LOWPOWER (4096 * HZ_PER_KHZ) +#define AD7779_NUM_CHANNELS 8 +#define AD7779_RESET_BUF_SIZE 8 +#define AD7779_CHAN_DATA_SIZE 4 + +#define AD7779_LOWPOWER_DIV 512 +#define AD7779_HIGHPOWER_DIV 2048 + +#define AD7779_SINC3_MAXFREQ (16 * HZ_PER_KHZ) +#define AD7779_SINC5_MAXFREQ (128 * HZ_PER_KHZ) + +#define AD7779_DEFAULT_SAMPLING_FREQ (8 * HZ_PER_KHZ) +#define AD7779_DEFAULT_SAMPLING_2LINE (4 * HZ_PER_KHZ) +#define AD7779_DEFAULT_SAMPLING_1LINE (2 * HZ_PER_KHZ) + +#define AD7779_SPIMODE_MAX_SAMP_FREQ (16 * HZ_PER_KHZ) + +#define GAIN_REL 0x555555 +#define AD7779_FREQ_MSB_MSK GENMASK(15, 8) +#define AD7779_FREQ_LSB_MSK GENMASK(7, 0) +#define AD7779_UPPER GENMASK(23, 16) +#define AD7779_MID GENMASK(15, 8) +#define AD7779_LOWER GENMASK(7, 0) + +#define AD7779_REG_MSK GENMASK(6, 0) + +#define AD7779_CRC8_POLY 0x07 +DECLARE_CRC8_TABLE(ad7779_crc8_table); + +enum ad7779_filter { + AD7779_SINC3, + AD7779_SINC5, +}; + +enum ad7779_variant { + ad7770, + ad7771, + ad7779, +}; + +enum ad7779_power_mode { + AD7779_LOW_POWER, + AD7779_HIGH_POWER, +}; + +struct ad7779_chip_info { + const char *name; + struct iio_chan_spec const *channels; +}; + +struct ad7779_state { + struct spi_device *spi; + const struct ad7779_chip_info *chip_info; + struct clk *mclk; + struct iio_trigger *trig; + unsigned int sampling_freq; + enum ad7779_filter filter_enabled; + struct iio_backend *back; + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + struct { + u32 chans[8]; + aligned_s64 timestamp; + } data __aligned(IIO_DMA_MINALIGN); + u32 spidata_tx[8]; + u8 reg_rx_buf[3]; + u8 reg_tx_buf[3]; + u8 reset_buf[8]; +}; + +static const char * const ad7779_filter_type[] = { + [AD7779_SINC3] = "sinc3", + [AD7779_SINC5] = "sinc5", +}; + +static const char * const ad7779_power_supplies[] = { + "avdd1", "avdd2", "avdd4", +}; + +static int ad7779_spi_read(struct ad7779_state *st, u8 reg, u8 *rbuf) +{ + int ret; + u8 crc_buf[2]; + u8 exp_crc; + struct spi_transfer t = { + .tx_buf = st->reg_tx_buf, + .rx_buf = st->reg_rx_buf, + }; + + st->reg_tx_buf[0] = AD7779_SPI_READ_CMD | FIELD_GET(AD7779_REG_MSK, reg); + st->reg_tx_buf[1] = 0; + + if (reg == AD7779_REG_GEN_ERR_REG_1_EN) { + t.len = 2; + } else { + t.len = 3; + st->reg_tx_buf[2] = crc8(ad7779_crc8_table, st->reg_tx_buf, + t.len - 1, 0); + } + + ret = spi_sync_transfer(st->spi, &t, 1); + if (ret) + return ret; + + crc_buf[0] = AD7779_SPI_READ_CMD | FIELD_GET(AD7779_REG_MSK, reg); + crc_buf[1] = st->reg_rx_buf[1]; + exp_crc = crc8(ad7779_crc8_table, crc_buf, ARRAY_SIZE(crc_buf), 0); + if (reg != AD7779_REG_GEN_ERR_REG_1_EN && exp_crc != st->reg_rx_buf[2]) { + dev_err(&st->spi->dev, "Bad CRC %x, expected %x", + st->reg_rx_buf[2], exp_crc); + return -EINVAL; + } + *rbuf = st->reg_rx_buf[1]; + + return 0; +} + +static int ad7779_spi_write(struct ad7779_state *st, u8 reg, u8 val) +{ + u8 length = 3; + + st->reg_tx_buf[0] = FIELD_GET(AD7779_REG_MSK, reg); + st->reg_tx_buf[1] = val; + if (reg == AD7779_REG_GEN_ERR_REG_1_EN) + length = 2; + else + st->reg_tx_buf[2] = crc8(ad7779_crc8_table, st->reg_tx_buf, + length - 1, 0); + + return spi_write(st->spi, st->reg_tx_buf, length); +} + +static int ad7779_spi_write_mask(struct ad7779_state *st, u8 reg, u8 mask, + u8 val) +{ + int ret; + u8 regval, data; + + ret = ad7779_spi_read(st, reg, &data); + if (ret) + return ret; + + regval = (data & ~mask) | (val & mask); + + if (regval == data) + return 0; + + return ad7779_spi_write(st, reg, regval); +} + +static int ad7779_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct ad7779_state *st = iio_priv(indio_dev); + u8 rval; + int ret; + + if (readval) { + ret = ad7779_spi_read(st, reg, &rval); + *readval = rval; + return ret; + } + + return ad7779_spi_write(st, reg, writeval); +} + +static int ad7779_set_sampling_frequency(struct ad7779_state *st, + unsigned int sampling_freq) +{ + int ret; + unsigned int dec; + unsigned int frac; + unsigned int div; + unsigned int decimal; + unsigned int freq_khz; + + if (st->filter_enabled == AD7779_SINC3 && + sampling_freq > AD7779_SINC3_MAXFREQ) + return -EINVAL; + + if (st->filter_enabled == AD7779_SINC5 && + sampling_freq > AD7779_SINC5_MAXFREQ) + return -EINVAL; + + if (sampling_freq > AD7779_SPIMODE_MAX_SAMP_FREQ) + return -EINVAL; + + div = AD7779_HIGHPOWER_DIV; + + freq_khz = sampling_freq / HZ_PER_KHZ; + dec = div / freq_khz; + frac = div % freq_khz; + + ret = ad7779_spi_write(st, AD7779_REG_SRC_N_MSB, + FIELD_GET(AD7779_FREQ_MSB_MSK, dec)); + if (ret) + return ret; + ret = ad7779_spi_write(st, AD7779_REG_SRC_N_LSB, + FIELD_GET(AD7779_FREQ_LSB_MSK, dec)); + if (ret) + return ret; + + if (frac) { + /* + * In order to obtain the first three decimals of the decimation + * the initial number is multiplied with 10^3 prior to the + * division, then the original division result is subtracted and + * the number is divided by 10^3. + */ + decimal = ((mult_frac(div, KILO, freq_khz) - dec * KILO) << 16) + / KILO; + ret = ad7779_spi_write(st, AD7779_REG_SRC_N_MSB, + FIELD_GET(AD7779_FREQ_MSB_MSK, decimal)); + if (ret) + return ret; + ret = ad7779_spi_write(st, AD7779_REG_SRC_N_LSB, + FIELD_GET(AD7779_FREQ_LSB_MSK, decimal)); + if (ret) + return ret; + } else { + ret = ad7779_spi_write(st, AD7779_REG_SRC_N_MSB, + FIELD_GET(AD7779_FREQ_MSB_MSK, 0x0)); + if (ret) + return ret; + ret = ad7779_spi_write(st, AD7779_REG_SRC_N_LSB, + FIELD_GET(AD7779_FREQ_LSB_MSK, 0x0)); + if (ret) + return ret; + } + ret = ad7779_spi_write(st, AD7779_REG_SRC_UPDATE, BIT(0)); + if (ret) + return ret; + + /* SRC update settling time */ + fsleep(15); + + ret = ad7779_spi_write(st, AD7779_REG_SRC_UPDATE, 0x0); + if (ret) + return ret; + + /* SRC update settling time */ + fsleep(15); + + st->sampling_freq = sampling_freq; + + return 0; +} + +static int ad7779_get_filter(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + struct ad7779_state *st = iio_priv(indio_dev); + u8 temp; + int ret; + + ret = ad7779_spi_read(st, AD7779_REG_GENERAL_USER_CONFIG_2, &temp); + if (ret) + return ret; + + return FIELD_GET(AD7779_FILTER_MSK, temp); +} + +static int ad7779_set_filter(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + unsigned int mode) +{ + struct ad7779_state *st = iio_priv(indio_dev); + int ret; + + ret = ad7779_spi_write_mask(st, + AD7779_REG_GENERAL_USER_CONFIG_2, + AD7779_FILTER_MSK, + FIELD_PREP(AD7779_FILTER_MSK, mode)); + if (ret) + return ret; + + ret = ad7779_set_sampling_frequency(st, st->sampling_freq); + if (ret) + return ret; + + st->filter_enabled = mode; + + return 0; +} + +static int ad7779_get_calibscale(struct ad7779_state *st, int channel) +{ + int ret; + u8 calibscale[3]; + + ret = ad7779_spi_read(st, AD7779_REG_CH_GAIN_LOWER_BYTE(channel), + &calibscale[0]); + if (ret) + return ret; + + ret = ad7779_spi_read(st, AD7779_REG_CH_GAIN_MID_BYTE(channel), + &calibscale[1]); + if (ret) + return ret; + + ret = ad7779_spi_read(st, AD7779_REG_CH_GAIN_UPPER_BYTE(channel), + &calibscale[2]); + if (ret) + return ret; + + return get_unaligned_be24(calibscale); +} + +static int ad7779_set_calibscale(struct ad7779_state *st, int channel, int val) +{ + int ret; + unsigned int gain; + u8 gain_bytes[3]; + + /* + * The gain value is relative to 0x555555, which represents a gain of 1 + */ + gain = DIV_ROUND_CLOSEST_ULL((u64)val * 5592405LL, MEGA); + put_unaligned_be24(gain, gain_bytes); + ret = ad7779_spi_write(st, AD7779_REG_CH_GAIN_UPPER_BYTE(channel), + gain_bytes[0]); + if (ret) + return ret; + + ret = ad7779_spi_write(st, AD7779_REG_CH_GAIN_MID_BYTE(channel), + gain_bytes[1]); + if (ret) + return ret; + + return ad7779_spi_write(st, AD7779_REG_CH_GAIN_LOWER_BYTE(channel), + gain_bytes[2]); +} + +static int ad7779_get_calibbias(struct ad7779_state *st, int channel) +{ + int ret; + u8 calibbias[3]; + + ret = ad7779_spi_read(st, AD7779_REG_CH_OFFSET_LOWER_BYTE(channel), + &calibbias[0]); + if (ret) + return ret; + + ret = ad7779_spi_read(st, AD7779_REG_CH_OFFSET_MID_BYTE(channel), + &calibbias[1]); + if (ret) + return ret; + + ret = ad7779_spi_read(st, AD7779_REG_CH_OFFSET_UPPER_BYTE(channel), + &calibbias[2]); + if (ret) + return ret; + + return get_unaligned_be24(calibbias); +} + +static int ad7779_set_calibbias(struct ad7779_state *st, int channel, int val) +{ + int ret; + u8 calibbias[3]; + + put_unaligned_be24(val, calibbias); + ret = ad7779_spi_write(st, AD7779_REG_CH_OFFSET_UPPER_BYTE(channel), + calibbias[0]); + if (ret) + return ret; + + ret = ad7779_spi_write(st, AD7779_REG_CH_OFFSET_MID_BYTE(channel), + calibbias[1]); + if (ret) + return ret; + + return ad7779_spi_write(st, AD7779_REG_CH_OFFSET_LOWER_BYTE(channel), + calibbias[2]); +} + +static int __ad7779_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct ad7779_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + ret = ad7779_get_calibscale(st, chan->channel); + if (ret < 0) + return ret; + *val = ret; + *val2 = GAIN_REL; + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_CALIBBIAS: + ret = ad7779_get_calibbias(st, chan->channel); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = st->sampling_freq; + if (*val < 0) + return -EINVAL; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad7779_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = __ad7779_read_raw(indio_dev, chan, val, val2, mask); + iio_device_release_direct(indio_dev); + return ret; +} + +static int __ad7779_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, + long mask) +{ + struct ad7779_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + return ad7779_set_calibscale(st, chan->channel, val2); + case IIO_CHAN_INFO_CALIBBIAS: + return ad7779_set_calibbias(st, chan->channel, val); + case IIO_CHAN_INFO_SAMP_FREQ: + return ad7779_set_sampling_frequency(st, val); + default: + return -EINVAL; + } +} + +static int ad7779_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, + long mask) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = __ad7779_write_raw(indio_dev, chan, val, val2, mask); + iio_device_release_direct(indio_dev); + return ret; +} + +static int ad7779_buffer_preenable(struct iio_dev *indio_dev) +{ + int ret; + struct ad7779_state *st = iio_priv(indio_dev); + + ret = ad7779_spi_write_mask(st, + AD7779_REG_GENERAL_USER_CONFIG_3, + AD7779_MOD_SPI_EN_MSK, + FIELD_PREP(AD7779_MOD_SPI_EN_MSK, 1)); + if (ret) + return ret; + + /* + * DRDY output cannot be disabled at device level therefore we mask + * the irq at host end. + */ + enable_irq(st->spi->irq); + + return 0; +} + +static int ad7779_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ad7779_state *st = iio_priv(indio_dev); + + disable_irq(st->spi->irq); + + return ad7779_spi_write(st, AD7779_REG_GENERAL_USER_CONFIG_3, + AD7779_DISABLE_SD); +} + +static irqreturn_t ad7779_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad7779_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer t = { + .rx_buf = st->data.chans, + .tx_buf = st->spidata_tx, + .len = AD7779_NUM_CHANNELS * AD7779_CHAN_DATA_SIZE, + }; + + st->spidata_tx[0] = AD7779_SPI_READ_CMD; + ret = spi_sync_transfer(st->spi, &t, 1); + if (ret) { + dev_err(&st->spi->dev, "SPI transfer error in IRQ handler"); + goto exit_handler; + } + + iio_push_to_buffers_with_ts(indio_dev, &st->data, sizeof(st->data), + pf->timestamp); + +exit_handler: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int ad7779_reset(struct iio_dev *indio_dev, struct gpio_desc *reset_gpio) +{ + struct ad7779_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer t = { + .tx_buf = st->reset_buf, + .len = 8, + }; + + if (reset_gpio) { + gpiod_set_value(reset_gpio, 1); + /* Delay for reset to occur is 225 microseconds */ + fsleep(230); + ret = 0; + } else { + memset(st->reset_buf, 0xff, sizeof(st->reset_buf)); + ret = spi_sync_transfer(st->spi, &t, 1); + if (ret) + return ret; + } + + /* Delay for reset to occur is 225 microseconds */ + fsleep(230); + + return ret; +} + +static int ad7779_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ad7779_state *st = iio_priv(indio_dev); + unsigned int c; + int ret; + + for (c = 0; c < AD7779_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 const struct iio_info ad7779_info = { + .read_raw = ad7779_read_raw, + .write_raw = ad7779_write_raw, + .debugfs_reg_access = &ad7779_reg_access, +}; + +static const struct iio_info ad7779_info_data = { + .read_raw = ad7779_read_raw, + .write_raw = ad7779_write_raw, + .debugfs_reg_access = &ad7779_reg_access, + .update_scan_mode = &ad7779_update_scan_mode, +}; + +static const struct iio_enum ad7779_filter_enum = { + .items = ad7779_filter_type, + .num_items = ARRAY_SIZE(ad7779_filter_type), + .get = ad7779_get_filter, + .set = ad7779_set_filter, +}; + +static const struct iio_chan_spec_ext_info ad7779_ext_filter[] = { + IIO_ENUM("filter_type", IIO_SHARED_BY_ALL, &ad7779_filter_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_ALL, + &ad7779_filter_enum), + { } +}; + +#define AD777x_CHAN_S(index, _ext_info) \ + { \ + .type = IIO_VOLTAGE, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\ + .address = (index), \ + .indexed = 1, \ + .channel = (index), \ + .scan_index = (index), \ + .ext_info = (_ext_info), \ + .scan_type = { \ + .sign = 's', \ + .realbits = 24, \ + .storagebits = 32, \ + .endianness = IIO_BE, \ + }, \ + } + +#define AD777x_CHAN_NO_FILTER_S(index) \ + AD777x_CHAN_S(index, NULL) + +#define AD777x_CHAN_FILTER_S(index) \ + AD777x_CHAN_S(index, ad7779_ext_filter) +static const struct iio_chan_spec ad7779_channels[] = { + AD777x_CHAN_NO_FILTER_S(0), + AD777x_CHAN_NO_FILTER_S(1), + AD777x_CHAN_NO_FILTER_S(2), + AD777x_CHAN_NO_FILTER_S(3), + AD777x_CHAN_NO_FILTER_S(4), + AD777x_CHAN_NO_FILTER_S(5), + AD777x_CHAN_NO_FILTER_S(6), + AD777x_CHAN_NO_FILTER_S(7), + IIO_CHAN_SOFT_TIMESTAMP(8), +}; + +static const struct iio_chan_spec ad7779_channels_filter[] = { + AD777x_CHAN_FILTER_S(0), + AD777x_CHAN_FILTER_S(1), + AD777x_CHAN_FILTER_S(2), + AD777x_CHAN_FILTER_S(3), + AD777x_CHAN_FILTER_S(4), + AD777x_CHAN_FILTER_S(5), + AD777x_CHAN_FILTER_S(6), + AD777x_CHAN_FILTER_S(7), + IIO_CHAN_SOFT_TIMESTAMP(8), +}; + +static const struct iio_buffer_setup_ops ad7779_buffer_setup_ops = { + .preenable = ad7779_buffer_preenable, + .postdisable = ad7779_buffer_postdisable, +}; + +static const struct iio_trigger_ops ad7779_trigger_ops = { + .validate_device = iio_trigger_validate_own_device, +}; + +static int ad7779_conf(struct ad7779_state *st, struct gpio_desc *start_gpio) +{ + int ret; + + ret = ad7779_spi_write_mask(st, AD7779_REG_GEN_ERR_REG_1_EN, + AD7779_SPI_CRC_EN_MSK, + FIELD_PREP(AD7779_SPI_CRC_EN_MSK, 1)); + if (ret) + return ret; + + ret = ad7779_spi_write_mask(st, AD7779_REG_GENERAL_USER_CONFIG_1, + AD7779_USRMOD_INIT_MSK, + FIELD_PREP(AD7779_USRMOD_INIT_MSK, 5)); + if (ret) + return ret; + + ret = ad7779_spi_write_mask(st, AD7779_REG_DOUT_FORMAT, + AD7779_DCLK_CLK_DIV_MSK, + FIELD_PREP(AD7779_DCLK_CLK_DIV_MSK, 1)); + if (ret) + return ret; + + ret = ad7779_spi_write_mask(st, AD7779_REG_ADC_MUX_CONFIG, + AD7779_REFMUX_CTRL_MSK, + FIELD_PREP(AD7779_REFMUX_CTRL_MSK, 1)); + if (ret) + return ret; + + ret = ad7779_set_sampling_frequency(st, AD7779_DEFAULT_SAMPLING_FREQ); + if (ret) + return ret; + + gpiod_set_value(start_gpio, 0); + /* Start setup time */ + fsleep(15); + gpiod_set_value(start_gpio, 1); + /* Start setup time */ + fsleep(15); + gpiod_set_value(start_gpio, 0); + /* Start setup time */ + fsleep(15); + + return 0; +} + +static int ad7779_set_data_lines(struct iio_dev *indio_dev, u32 num_lanes) +{ + struct ad7779_state *st = iio_priv(indio_dev); + int ret; + + if (num_lanes != 1 && num_lanes != 2 && num_lanes != 4) + return -EINVAL; + + ret = ad7779_set_sampling_frequency(st, num_lanes * AD7779_DEFAULT_SAMPLING_1LINE); + if (ret) + return ret; + + ret = iio_backend_num_lanes_set(st->back, num_lanes); + if (ret) + return ret; + + return ad7779_spi_write_mask(st, AD7779_REG_DOUT_FORMAT, + AD7779_DOUT_FORMAT_MSK, + FIELD_PREP(AD7779_DOUT_FORMAT_MSK, 2 - ilog2(num_lanes))); +} + +static int ad7779_setup_channels(struct iio_dev *indio_dev, const struct ad7779_state *st) +{ + struct iio_chan_spec *channels; + struct device *dev = &st->spi->dev; + + channels = devm_kmemdup_array(dev, st->chip_info->channels, + ARRAY_SIZE(ad7779_channels), + sizeof(*channels), GFP_KERNEL); + if (!channels) + return -ENOMEM; + + for (unsigned int i = 0; i < ARRAY_SIZE(ad7779_channels); i++) + channels[i].scan_type.endianness = IIO_CPU; + + indio_dev->channels = channels; + indio_dev->num_channels = ARRAY_SIZE(ad7779_channels); + + return 0; +} + +static int ad7779_setup_without_backend(struct ad7779_state *st, struct iio_dev *indio_dev) +{ + int ret; + struct device *dev = &st->spi->dev; + + indio_dev->info = &ad7779_info; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = ARRAY_SIZE(ad7779_channels); + + st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, + iio_device_id(indio_dev)); + if (!st->trig) + return -ENOMEM; + + st->trig->ops = &ad7779_trigger_ops; + + iio_trigger_set_drvdata(st->trig, st); + + ret = devm_request_irq(dev, st->spi->irq, iio_trigger_generic_data_rdy_poll, + IRQF_NO_THREAD | IRQF_NO_AUTOEN, indio_dev->name, + st->trig); + if (ret) + return ret; + + ret = devm_iio_trigger_register(dev, st->trig); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(st->trig); + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + &iio_pollfunc_store_time, + &ad7779_trigger_handler, + &ad7779_buffer_setup_ops); + if (ret) + return ret; + + return ad7779_spi_write_mask(st, AD7779_REG_DOUT_FORMAT, + AD7779_DCLK_CLK_DIV_MSK, + FIELD_PREP(AD7779_DCLK_CLK_DIV_MSK, 7)); +} + +static int ad7779_setup_backend(struct ad7779_state *st, struct iio_dev *indio_dev) +{ + struct device *dev = &st->spi->dev; + int ret; + u32 num_lanes; + + indio_dev->info = &ad7779_info_data; + + ret = ad7779_setup_channels(indio_dev, st); + if (ret) + return ret; + + st->back = devm_iio_backend_get(dev, NULL); + if (IS_ERR(st->back)) + return dev_err_probe(dev, PTR_ERR(st->back), + "failed to get iio backend"); + + ret = devm_iio_backend_request_buffer(dev, st->back, indio_dev); + if (ret) + return ret; + + ret = devm_iio_backend_enable(dev, st->back); + if (ret) + return ret; + + num_lanes = 4; + ret = device_property_read_u32(dev, "adi,num-lanes", &num_lanes); + if (ret && ret != -EINVAL) + return ret; + + return ad7779_set_data_lines(indio_dev, num_lanes); +} + +static int ad7779_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct ad7779_state *st; + struct gpio_desc *reset_gpio, *start_gpio; + struct device *dev = &spi->dev; + int ret = -EINVAL; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + ret = devm_regulator_bulk_get_enable(dev, + ARRAY_SIZE(ad7779_power_supplies), + ad7779_power_supplies); + if (ret) + return dev_err_probe(dev, ret, + "failed to get and enable supplies\n"); + + st->mclk = devm_clk_get_enabled(dev, "mclk"); + if (IS_ERR(st->mclk)) + return PTR_ERR(st->mclk); + + reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(reset_gpio)) + return PTR_ERR(reset_gpio); + + start_gpio = devm_gpiod_get(dev, "start", GPIOD_OUT_HIGH); + if (IS_ERR(start_gpio)) + return PTR_ERR(start_gpio); + + crc8_populate_msb(ad7779_crc8_table, AD7779_CRC8_POLY); + st->spi = spi; + + st->chip_info = spi_get_device_match_data(spi); + if (!st->chip_info) + return -ENODEV; + + ret = ad7779_reset(indio_dev, reset_gpio); + if (ret) + return ret; + + ret = ad7779_conf(st, start_gpio); + if (ret) + return ret; + + indio_dev->name = st->chip_info->name; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (device_property_present(dev, "io-backends")) + ret = ad7779_setup_backend(st, indio_dev); + else + ret = ad7779_setup_without_backend(st, indio_dev); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static int ad7779_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7779_state *st = iio_priv(indio_dev); + + return ad7779_spi_write_mask(st, AD7779_REG_GENERAL_USER_CONFIG_1, + AD7779_MOD_POWERMODE_MSK, + FIELD_PREP(AD7779_MOD_POWERMODE_MSK, + AD7779_LOW_POWER)); +} + +static int ad7779_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7779_state *st = iio_priv(indio_dev); + + return ad7779_spi_write_mask(st, AD7779_REG_GENERAL_USER_CONFIG_1, + AD7779_MOD_POWERMODE_MSK, + FIELD_PREP(AD7779_MOD_POWERMODE_MSK, + AD7779_HIGH_POWER)); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ad7779_pm_ops, ad7779_suspend, ad7779_resume); + +static const struct ad7779_chip_info ad7770_chip_info = { + .name = "ad7770", + .channels = ad7779_channels, +}; + +static const struct ad7779_chip_info ad7771_chip_info = { + .name = "ad7771", + .channels = ad7779_channels_filter, +}; + +static const struct ad7779_chip_info ad7779_chip_info = { + .name = "ad7779", + .channels = ad7779_channels, +}; + +static const struct spi_device_id ad7779_id[] = { + { + .name = "ad7770", + .driver_data = (kernel_ulong_t)&ad7770_chip_info, + }, + { + .name = "ad7771", + .driver_data = (kernel_ulong_t)&ad7771_chip_info, + }, + { + .name = "ad7779", + .driver_data = (kernel_ulong_t)&ad7779_chip_info, + }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7779_id); + +static const struct of_device_id ad7779_of_table[] = { + { + .compatible = "adi,ad7770", + .data = &ad7770_chip_info, + }, + { + .compatible = "adi,ad7771", + .data = &ad7771_chip_info, + }, + { + .compatible = "adi,ad7779", + .data = &ad7779_chip_info, + }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7779_of_table); + +static struct spi_driver ad7779_driver = { + .driver = { + .name = "ad7779", + .pm = pm_sleep_ptr(&ad7779_pm_ops), + .of_match_table = ad7779_of_table, + }, + .probe = ad7779_probe, + .id_table = ad7779_id, +}; +module_spi_driver(ad7779_driver); + +MODULE_AUTHOR("Ramona Alexandra Nechita "); +MODULE_DESCRIPTION("Analog Devices AD7779 ADC"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_BACKEND"); diff --git a/drivers/iio/adc/ad7780.c b/drivers/iio/adc/ad7780.c new file mode 100644 index 000000000..1cfad7032 --- /dev/null +++ b/drivers/iio/adc/ad7780.c @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AD7170/AD7171 and AD7780/AD7781 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + * Copyright 2019 Renato Lui Geh + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define AD7780_RDY BIT(7) +#define AD7780_FILTER BIT(6) +#define AD7780_ERR BIT(5) +#define AD7780_ID1 BIT(4) +#define AD7780_ID0 BIT(3) +#define AD7780_GAIN BIT(2) + +#define AD7170_ID 0 +#define AD7171_ID 1 +#define AD7780_ID 1 +#define AD7781_ID 0 + +#define AD7780_ID_MASK (AD7780_ID0 | AD7780_ID1) + +#define AD7780_PATTERN_GOOD 1 +#define AD7780_PATTERN_MASK GENMASK(1, 0) + +#define AD7170_PATTERN_GOOD 5 +#define AD7170_PATTERN_MASK GENMASK(2, 0) + +#define AD7780_GAIN_MIDPOINT 64 +#define AD7780_FILTER_MIDPOINT 13350 + +static const unsigned int ad778x_gain[2] = { 1, 128 }; +static const unsigned int ad778x_odr_avail[2] = { 10000, 16700 }; + +struct ad7780_chip_info { + struct iio_chan_spec channel; + unsigned int pattern_mask; + unsigned int pattern; + bool is_ad778x; +}; + +struct ad7780_state { + const struct ad7780_chip_info *chip_info; + struct regulator *reg; + struct gpio_desc *powerdown_gpio; + struct gpio_desc *gain_gpio; + struct gpio_desc *filter_gpio; + unsigned int gain; + unsigned int odr; + unsigned int int_vref_mv; + + struct ad_sigma_delta sd; +}; + +enum ad7780_supported_device_ids { + ID_AD7170, + ID_AD7171, + ID_AD7780, + ID_AD7781, +}; + +static struct ad7780_state *ad_sigma_delta_to_ad7780(struct ad_sigma_delta *sd) +{ + return container_of(sd, struct ad7780_state, sd); +} + +static int ad7780_set_mode(struct ad_sigma_delta *sigma_delta, + enum ad_sigma_delta_mode mode) +{ + struct ad7780_state *st = ad_sigma_delta_to_ad7780(sigma_delta); + unsigned int val; + + switch (mode) { + case AD_SD_MODE_SINGLE: + case AD_SD_MODE_CONTINUOUS: + val = 1; + break; + default: + val = 0; + break; + } + + gpiod_set_value(st->powerdown_gpio, val); + + return 0; +} + +static int ad7780_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad7780_state *st = iio_priv(indio_dev); + int voltage_uv; + + switch (m) { + case IIO_CHAN_INFO_RAW: + return ad_sigma_delta_single_conversion(indio_dev, chan, val); + case IIO_CHAN_INFO_SCALE: + voltage_uv = regulator_get_voltage(st->reg); + if (voltage_uv < 0) + return voltage_uv; + voltage_uv /= 1000; + *val = voltage_uv * st->gain; + *val2 = chan->scan_type.realbits - 1; + st->int_vref_mv = voltage_uv; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: + *val = -(1 << (chan->scan_type.realbits - 1)); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = st->odr; + return IIO_VAL_INT; + default: + break; + } + + return -EINVAL; +} + +static int ad7780_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long m) +{ + struct ad7780_state *st = iio_priv(indio_dev); + const struct ad7780_chip_info *chip_info = st->chip_info; + unsigned long long vref; + unsigned int full_scale, gain; + + if (!chip_info->is_ad778x) + return -EINVAL; + + switch (m) { + case IIO_CHAN_INFO_SCALE: + if (val != 0 || val2 == 0) + return -EINVAL; + + vref = st->int_vref_mv * 1000000LL; + full_scale = 1 << (chip_info->channel.scan_type.realbits - 1); + gain = DIV_ROUND_CLOSEST_ULL(vref, full_scale); + gain = DIV_ROUND_CLOSEST(gain, val2); + st->gain = gain; + if (gain < AD7780_GAIN_MIDPOINT) + gain = 0; + else + gain = 1; + gpiod_set_value(st->gain_gpio, gain); + break; + case IIO_CHAN_INFO_SAMP_FREQ: + if (1000*val + val2/1000 < AD7780_FILTER_MIDPOINT) + val = 0; + else + val = 1; + st->odr = ad778x_odr_avail[val]; + gpiod_set_value(st->filter_gpio, val); + break; + default: + break; + } + + return 0; +} + +static int ad7780_postprocess_sample(struct ad_sigma_delta *sigma_delta, + unsigned int raw_sample) +{ + struct ad7780_state *st = ad_sigma_delta_to_ad7780(sigma_delta); + const struct ad7780_chip_info *chip_info = st->chip_info; + + if ((raw_sample & AD7780_ERR) || + ((raw_sample & chip_info->pattern_mask) != chip_info->pattern)) + return -EIO; + + if (chip_info->is_ad778x) { + st->gain = ad778x_gain[raw_sample & AD7780_GAIN]; + st->odr = ad778x_odr_avail[raw_sample & AD7780_FILTER]; + } + + return 0; +} + +static const struct ad_sigma_delta_info ad7780_sigma_delta_info = { + .set_mode = ad7780_set_mode, + .postprocess_sample = ad7780_postprocess_sample, + .has_registers = false, + .irq_flags = IRQF_TRIGGER_FALLING, +}; + +#define _AD7780_CHANNEL(_bits, _wordsize, _mask_all) \ +{ \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = _mask_all, \ + .scan_index = 1, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (_bits), \ + .storagebits = 32, \ + .shift = (_wordsize) - (_bits), \ + .endianness = IIO_BE, \ + }, \ +} + +#define AD7780_CHANNEL(_bits, _wordsize) \ + _AD7780_CHANNEL(_bits, _wordsize, BIT(IIO_CHAN_INFO_SAMP_FREQ)) +#define AD7170_CHANNEL(_bits, _wordsize) \ + _AD7780_CHANNEL(_bits, _wordsize, 0) + +static const struct ad7780_chip_info ad7780_chip_info_tbl[] = { + [ID_AD7170] = { + .channel = AD7170_CHANNEL(12, 24), + .pattern = AD7170_PATTERN_GOOD, + .pattern_mask = AD7170_PATTERN_MASK, + .is_ad778x = false, + }, + [ID_AD7171] = { + .channel = AD7170_CHANNEL(16, 24), + .pattern = AD7170_PATTERN_GOOD, + .pattern_mask = AD7170_PATTERN_MASK, + .is_ad778x = false, + }, + [ID_AD7780] = { + .channel = AD7780_CHANNEL(24, 32), + .pattern = AD7780_PATTERN_GOOD, + .pattern_mask = AD7780_PATTERN_MASK, + .is_ad778x = true, + }, + [ID_AD7781] = { + .channel = AD7780_CHANNEL(20, 32), + .pattern = AD7780_PATTERN_GOOD, + .pattern_mask = AD7780_PATTERN_MASK, + .is_ad778x = true, + }, +}; + +static const struct iio_info ad7780_info = { + .read_raw = ad7780_read_raw, + .write_raw = ad7780_write_raw, +}; + +static int ad7780_init_gpios(struct device *dev, struct ad7780_state *st) +{ + st->powerdown_gpio = devm_gpiod_get_optional(dev, + "powerdown", + GPIOD_OUT_LOW); + if (IS_ERR(st->powerdown_gpio)) + return dev_err_probe(dev, PTR_ERR(st->powerdown_gpio), + "Failed to request powerdown GPIO\n"); + + if (!st->chip_info->is_ad778x) + return 0; + + + st->gain_gpio = devm_gpiod_get_optional(dev, + "adi,gain", + GPIOD_OUT_HIGH); + if (IS_ERR(st->gain_gpio)) + return dev_err_probe(dev, PTR_ERR(st->gain_gpio), + "Failed to request gain GPIO\n"); + + st->filter_gpio = devm_gpiod_get_optional(dev, + "adi,filter", + GPIOD_OUT_HIGH); + if (IS_ERR(st->filter_gpio)) + return dev_err_probe(dev, PTR_ERR(st->filter_gpio), + "Failed to request filter GPIO\n"); + + return 0; +} + +static void ad7780_reg_disable(void *reg) +{ + regulator_disable(reg); +} + +static int ad7780_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct ad7780_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->gain = 1; + + ad_sd_init(&st->sd, indio_dev, spi, &ad7780_sigma_delta_info); + + st->chip_info = + &ad7780_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = &st->chip_info->channel; + indio_dev->num_channels = 1; + indio_dev->info = &ad7780_info; + + ret = ad7780_init_gpios(dev, st); + if (ret) + return ret; + + st->reg = devm_regulator_get(dev, "avdd"); + if (IS_ERR(st->reg)) + return PTR_ERR(st->reg); + + ret = regulator_enable(st->reg); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable specified AVdd supply\n"); + + ret = devm_add_action_or_reset(dev, ad7780_reg_disable, st->reg); + if (ret) + return ret; + + ret = devm_ad_sd_setup_buffer_and_trigger(dev, indio_dev); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id ad7780_id[] = { + { .name = "ad7170", .driver_data = ID_AD7170 }, + { .name = "ad7171", .driver_data = ID_AD7171 }, + { .name = "ad7780", .driver_data = ID_AD7780 }, + { .name = "ad7781", .driver_data = ID_AD7781 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7780_id); + +static struct spi_driver ad7780_driver = { + .driver = { + .name = "ad7780", + }, + .probe = ad7780_probe, + .id_table = ad7780_id, +}; +module_spi_driver(ad7780_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7780 and similar ADCs"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_AD_SIGMA_DELTA"); diff --git a/drivers/iio/adc/ad7791.c b/drivers/iio/adc/ad7791.c new file mode 100644 index 000000000..c2688307b --- /dev/null +++ b/drivers/iio/adc/ad7791.c @@ -0,0 +1,482 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD7787/AD7788/AD7789/AD7790/AD7791 SPI ADC driver + * + * Copyright 2012 Analog Devices Inc. + * Author: Lars-Peter Clausen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define AD7791_REG_COMM 0x0 /* For writes */ +#define AD7791_REG_STATUS 0x0 /* For reads */ +#define AD7791_REG_MODE 0x1 +#define AD7791_REG_FILTER 0x2 +#define AD7791_REG_DATA 0x3 + +#define AD7791_MODE_CONTINUOUS 0x00 +#define AD7791_MODE_SINGLE 0x02 +#define AD7791_MODE_POWERDOWN 0x03 + +#define AD7791_CH_AIN1P_AIN1N 0x00 +#define AD7791_CH_AIN2 0x01 +#define AD7791_CH_AIN1N_AIN1N 0x02 +#define AD7791_CH_AVDD_MONITOR 0x03 + +#define AD7791_FILTER_CLK_DIV_1 (0x0 << 4) +#define AD7791_FILTER_CLK_DIV_2 (0x1 << 4) +#define AD7791_FILTER_CLK_DIV_4 (0x2 << 4) +#define AD7791_FILTER_CLK_DIV_8 (0x3 << 4) +#define AD7791_FILTER_CLK_MASK (0x3 << 4) +#define AD7791_FILTER_RATE_120 0x0 +#define AD7791_FILTER_RATE_100 0x1 +#define AD7791_FILTER_RATE_33_3 0x2 +#define AD7791_FILTER_RATE_20 0x3 +#define AD7791_FILTER_RATE_16_6 0x4 +#define AD7791_FILTER_RATE_16_7 0x5 +#define AD7791_FILTER_RATE_13_3 0x6 +#define AD7791_FILTER_RATE_9_5 0x7 +#define AD7791_FILTER_RATE_MASK 0x7 + +#define AD7791_MODE_BUFFER BIT(1) +#define AD7791_MODE_UNIPOLAR BIT(2) +#define AD7791_MODE_BURNOUT BIT(3) +#define AD7791_MODE_SEL_MASK (0x3 << 6) +#define AD7791_MODE_SEL(x) ((x) << 6) + +#define __AD7991_CHANNEL(_si, _channel1, _channel2, _address, _bits, \ + _storagebits, _shift, _extend_name, _type, _mask_all) \ + { \ + .type = (_type), \ + .differential = (_channel2 == -1 ? 0 : 1), \ + .indexed = 1, \ + .channel = (_channel1), \ + .channel2 = (_channel2), \ + .address = (_address), \ + .extend_name = (_extend_name), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = _mask_all, \ + .scan_index = (_si), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (_bits), \ + .storagebits = (_storagebits), \ + .shift = (_shift), \ + .endianness = IIO_BE, \ + }, \ + } + +#define AD7991_SHORTED_CHANNEL(_si, _channel, _address, _bits, \ + _storagebits, _shift) \ + __AD7991_CHANNEL(_si, _channel, _channel, _address, _bits, \ + _storagebits, _shift, "shorted", IIO_VOLTAGE, \ + BIT(IIO_CHAN_INFO_SAMP_FREQ)) + +#define AD7991_CHANNEL(_si, _channel, _address, _bits, \ + _storagebits, _shift) \ + __AD7991_CHANNEL(_si, _channel, -1, _address, _bits, \ + _storagebits, _shift, NULL, IIO_VOLTAGE, \ + BIT(IIO_CHAN_INFO_SAMP_FREQ)) + +#define AD7991_DIFF_CHANNEL(_si, _channel1, _channel2, _address, _bits, \ + _storagebits, _shift) \ + __AD7991_CHANNEL(_si, _channel1, _channel2, _address, _bits, \ + _storagebits, _shift, NULL, IIO_VOLTAGE, \ + BIT(IIO_CHAN_INFO_SAMP_FREQ)) + +#define AD7991_SUPPLY_CHANNEL(_si, _channel, _address, _bits, _storagebits, \ + _shift) \ + __AD7991_CHANNEL(_si, _channel, -1, _address, _bits, \ + _storagebits, _shift, "supply", IIO_VOLTAGE, \ + BIT(IIO_CHAN_INFO_SAMP_FREQ)) + +#define DECLARE_AD7787_CHANNELS(name, bits, storagebits) \ +const struct iio_chan_spec name[] = { \ + AD7991_DIFF_CHANNEL(0, 0, 0, AD7791_CH_AIN1P_AIN1N, \ + (bits), (storagebits), 0), \ + AD7991_CHANNEL(1, 1, AD7791_CH_AIN2, (bits), (storagebits), 0), \ + AD7991_SHORTED_CHANNEL(2, 0, AD7791_CH_AIN1N_AIN1N, \ + (bits), (storagebits), 0), \ + AD7991_SUPPLY_CHANNEL(3, 2, AD7791_CH_AVDD_MONITOR, \ + (bits), (storagebits), 0), \ + IIO_CHAN_SOFT_TIMESTAMP(4), \ +} + +#define DECLARE_AD7791_CHANNELS(name, bits, storagebits) \ +const struct iio_chan_spec name[] = { \ + AD7991_DIFF_CHANNEL(0, 0, 0, AD7791_CH_AIN1P_AIN1N, \ + (bits), (storagebits), 0), \ + AD7991_SHORTED_CHANNEL(1, 0, AD7791_CH_AIN1N_AIN1N, \ + (bits), (storagebits), 0), \ + AD7991_SUPPLY_CHANNEL(2, 1, AD7791_CH_AVDD_MONITOR, \ + (bits), (storagebits), 0), \ + IIO_CHAN_SOFT_TIMESTAMP(3), \ +} + +static DECLARE_AD7787_CHANNELS(ad7787_channels, 24, 32); +static DECLARE_AD7791_CHANNELS(ad7790_channels, 16, 16); +static DECLARE_AD7791_CHANNELS(ad7791_channels, 24, 32); + +enum { + AD7787, + AD7788, + AD7789, + AD7790, + AD7791, +}; + +enum ad7791_chip_info_flags { + AD7791_FLAG_HAS_FILTER = (1 << 0), + AD7791_FLAG_HAS_BUFFER = (1 << 1), + AD7791_FLAG_HAS_UNIPOLAR = (1 << 2), + AD7791_FLAG_HAS_BURNOUT = (1 << 3), +}; + +struct ad7791_chip_info { + const struct iio_chan_spec *channels; + unsigned int num_channels; + enum ad7791_chip_info_flags flags; +}; + +static const struct ad7791_chip_info ad7791_chip_infos[] = { + [AD7787] = { + .channels = ad7787_channels, + .num_channels = ARRAY_SIZE(ad7787_channels), + .flags = AD7791_FLAG_HAS_FILTER | AD7791_FLAG_HAS_BUFFER | + AD7791_FLAG_HAS_UNIPOLAR | AD7791_FLAG_HAS_BURNOUT, + }, + [AD7788] = { + .channels = ad7790_channels, + .num_channels = ARRAY_SIZE(ad7790_channels), + .flags = AD7791_FLAG_HAS_UNIPOLAR, + }, + [AD7789] = { + .channels = ad7791_channels, + .num_channels = ARRAY_SIZE(ad7791_channels), + .flags = AD7791_FLAG_HAS_UNIPOLAR, + }, + [AD7790] = { + .channels = ad7790_channels, + .num_channels = ARRAY_SIZE(ad7790_channels), + .flags = AD7791_FLAG_HAS_FILTER | AD7791_FLAG_HAS_BUFFER | + AD7791_FLAG_HAS_BURNOUT, + }, + [AD7791] = { + .channels = ad7791_channels, + .num_channels = ARRAY_SIZE(ad7791_channels), + .flags = AD7791_FLAG_HAS_FILTER | AD7791_FLAG_HAS_BUFFER | + AD7791_FLAG_HAS_UNIPOLAR | AD7791_FLAG_HAS_BURNOUT, + }, +}; + +struct ad7791_state { + struct ad_sigma_delta sd; + uint8_t mode; + uint8_t filter; + + struct regulator *reg; + const struct ad7791_chip_info *info; +}; + +static const int ad7791_sample_freq_avail[8][2] = { + [AD7791_FILTER_RATE_120] = { 120, 0 }, + [AD7791_FILTER_RATE_100] = { 100, 0 }, + [AD7791_FILTER_RATE_33_3] = { 33, 300000 }, + [AD7791_FILTER_RATE_20] = { 20, 0 }, + [AD7791_FILTER_RATE_16_6] = { 16, 600000 }, + [AD7791_FILTER_RATE_16_7] = { 16, 700000 }, + [AD7791_FILTER_RATE_13_3] = { 13, 300000 }, + [AD7791_FILTER_RATE_9_5] = { 9, 500000 }, +}; + +static struct ad7791_state *ad_sigma_delta_to_ad7791(struct ad_sigma_delta *sd) +{ + return container_of(sd, struct ad7791_state, sd); +} + +static int ad7791_set_channel(struct ad_sigma_delta *sd, unsigned int channel) +{ + ad_sd_set_comm(sd, channel); + + return 0; +} + +static int ad7791_set_mode(struct ad_sigma_delta *sd, + enum ad_sigma_delta_mode mode) +{ + struct ad7791_state *st = ad_sigma_delta_to_ad7791(sd); + + switch (mode) { + case AD_SD_MODE_CONTINUOUS: + mode = AD7791_MODE_CONTINUOUS; + break; + case AD_SD_MODE_SINGLE: + mode = AD7791_MODE_SINGLE; + break; + case AD_SD_MODE_IDLE: + case AD_SD_MODE_POWERDOWN: + mode = AD7791_MODE_POWERDOWN; + break; + } + + st->mode &= ~AD7791_MODE_SEL_MASK; + st->mode |= AD7791_MODE_SEL(mode); + + return ad_sd_write_reg(sd, AD7791_REG_MODE, sizeof(st->mode), st->mode); +} + +static const struct ad_sigma_delta_info ad7791_sigma_delta_info = { + .set_channel = ad7791_set_channel, + .set_mode = ad7791_set_mode, + .has_registers = true, + .addr_shift = 4, + .read_mask = BIT(3), + .irq_flags = IRQF_TRIGGER_FALLING, + .num_resetclks = 32, +}; + +static int ad7791_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, int *val2, long info) +{ + struct ad7791_state *st = iio_priv(indio_dev); + bool unipolar = !!(st->mode & AD7791_MODE_UNIPOLAR); + unsigned int rate; + + switch (info) { + case IIO_CHAN_INFO_RAW: + return ad_sigma_delta_single_conversion(indio_dev, chan, val); + case IIO_CHAN_INFO_OFFSET: + /** + * Unipolar: 0 to VREF + * Bipolar -VREF to VREF + **/ + if (unipolar) + *val = 0; + else + *val = -(1 << (chan->scan_type.realbits - 1)); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* The monitor channel uses an internal reference. */ + if (chan->address == AD7791_CH_AVDD_MONITOR) { + /* + * The signal is attenuated by a factor of 5 and + * compared against a 1.17V internal reference. + */ + *val = 1170 * 5; + } else { + int voltage_uv; + + voltage_uv = regulator_get_voltage(st->reg); + if (voltage_uv < 0) + return voltage_uv; + + *val = voltage_uv / 1000; + } + if (unipolar) + *val2 = chan->scan_type.realbits; + else + *val2 = chan->scan_type.realbits - 1; + + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_SAMP_FREQ: + rate = st->filter & AD7791_FILTER_RATE_MASK; + *val = ad7791_sample_freq_avail[rate][0]; + *val2 = ad7791_sample_freq_avail[rate][1]; + return IIO_VAL_INT_PLUS_MICRO; + } + + return -EINVAL; +} + +static int __ad7791_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + struct ad7791_state *st = iio_priv(indio_dev); + int i; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + for (i = 0; i < ARRAY_SIZE(ad7791_sample_freq_avail); i++) { + if (ad7791_sample_freq_avail[i][0] == val && + ad7791_sample_freq_avail[i][1] == val2) + break; + } + + if (i == ARRAY_SIZE(ad7791_sample_freq_avail)) + return -EINVAL; + + st->filter &= ~AD7791_FILTER_RATE_MASK; + st->filter |= i; + ad_sd_write_reg(&st->sd, AD7791_REG_FILTER, + sizeof(st->filter), + st->filter); + return 0; + default: + return -EINVAL; + } +} + +static int ad7791_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = __ad7791_write_raw(indio_dev, chan, val, val2, mask); + + iio_device_release_direct(indio_dev); + return ret; +} + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("120 100 33.3 20 16.7 16.6 13.3 9.5"); + +static struct attribute *ad7791_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group ad7791_attribute_group = { + .attrs = ad7791_attributes, +}; + +static const struct iio_info ad7791_info = { + .read_raw = &ad7791_read_raw, + .write_raw = &ad7791_write_raw, + .attrs = &ad7791_attribute_group, + .validate_trigger = ad_sd_validate_trigger, +}; + +static const struct iio_info ad7791_no_filter_info = { + .read_raw = &ad7791_read_raw, + .write_raw = &ad7791_write_raw, + .validate_trigger = ad_sd_validate_trigger, +}; + +static int ad7791_setup(struct ad7791_state *st, + const struct ad7791_platform_data *pdata) +{ + /* Set to poweron-reset default values */ + st->mode = AD7791_MODE_BUFFER; + st->filter = AD7791_FILTER_RATE_16_6; + + if (!pdata) + return 0; + + if ((st->info->flags & AD7791_FLAG_HAS_BUFFER) && !pdata->buffered) + st->mode &= ~AD7791_MODE_BUFFER; + + if ((st->info->flags & AD7791_FLAG_HAS_BURNOUT) && + pdata->burnout_current) + st->mode |= AD7791_MODE_BURNOUT; + + if ((st->info->flags & AD7791_FLAG_HAS_UNIPOLAR) && pdata->unipolar) + st->mode |= AD7791_MODE_UNIPOLAR; + + return ad_sd_write_reg(&st->sd, AD7791_REG_MODE, sizeof(st->mode), + st->mode); +} + +static void ad7791_reg_disable(void *reg) +{ + regulator_disable(reg); +} + +static int ad7791_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + const struct ad7791_platform_data *pdata = dev_get_platdata(dev); + struct iio_dev *indio_dev; + struct ad7791_state *st; + int ret; + + if (!spi->irq) + return dev_err_probe(dev, -ENXIO, "Missing IRQ.\n"); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->reg = devm_regulator_get(dev, "refin"); + if (IS_ERR(st->reg)) + return PTR_ERR(st->reg); + + ret = regulator_enable(st->reg); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, ad7791_reg_disable, st->reg); + if (ret) + return ret; + + st->info = &ad7791_chip_infos[spi_get_device_id(spi)->driver_data]; + ad_sd_init(&st->sd, indio_dev, spi, &ad7791_sigma_delta_info); + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->info->channels; + indio_dev->num_channels = st->info->num_channels; + if (st->info->flags & AD7791_FLAG_HAS_FILTER) + indio_dev->info = &ad7791_info; + else + indio_dev->info = &ad7791_no_filter_info; + + ret = devm_ad_sd_setup_buffer_and_trigger(dev, indio_dev); + if (ret) + return ret; + + ret = ad7791_setup(st, pdata); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id ad7791_spi_ids[] = { + { .name = "ad7787", .driver_data = AD7787 }, + { .name = "ad7788", .driver_data = AD7788 }, + { .name = "ad7789", .driver_data = AD7789 }, + { .name = "ad7790", .driver_data = AD7790 }, + { .name = "ad7791", .driver_data = AD7791 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7791_spi_ids); + +static struct spi_driver ad7791_driver = { + .driver = { + .name = "ad7791", + }, + .probe = ad7791_probe, + .id_table = ad7791_spi_ids, +}; +module_spi_driver(ad7791_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Analog Devices AD7787/AD7788/AD7789/AD7790/AD7791 ADC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_AD_SIGMA_DELTA"); diff --git a/drivers/iio/adc/ad7793.c b/drivers/iio/adc/ad7793.c new file mode 100644 index 000000000..b0f44c144 --- /dev/null +++ b/drivers/iio/adc/ad7793.c @@ -0,0 +1,857 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD7785/AD7792/AD7793/AD7794/AD7795 SPI ADC driver + * + * Copyright 2011-2012 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Registers */ +#define AD7793_REG_COMM 0 /* Communications Register (WO, 8-bit) */ +#define AD7793_REG_STAT 0 /* Status Register (RO, 8-bit) */ +#define AD7793_REG_MODE 1 /* Mode Register (RW, 16-bit */ +#define AD7793_REG_CONF 2 /* Configuration Register (RW, 16-bit) */ +#define AD7793_REG_DATA 3 /* Data Register (RO, 16-/24-bit) */ +#define AD7793_REG_ID 4 /* ID Register (RO, 8-bit) */ +#define AD7793_REG_IO 5 /* IO Register (RO, 8-bit) */ +#define AD7793_REG_OFFSET 6 /* Offset Register (RW, 16-bit + * (AD7792)/24-bit (AD7793)) */ +#define AD7793_REG_FULLSALE 7 /* Full-Scale Register + * (RW, 16-bit (AD7792)/24-bit (AD7793)) */ + +/* Communications Register Bit Designations (AD7793_REG_COMM) */ +#define AD7793_COMM_WEN (1 << 7) /* Write Enable */ +#define AD7793_COMM_WRITE (0 << 6) /* Write Operation */ +#define AD7793_COMM_READ (1 << 6) /* Read Operation */ +#define AD7793_COMM_ADDR(x) (((x) & 0x7) << 3) /* Register Address */ +#define AD7793_COMM_CREAD (1 << 2) /* Continuous Read of Data Register */ + +/* Status Register Bit Designations (AD7793_REG_STAT) */ +#define AD7793_STAT_RDY (1 << 7) /* Ready */ +#define AD7793_STAT_ERR (1 << 6) /* Error (Overrange, Underrange) */ +#define AD7793_STAT_CH3 (1 << 2) /* Channel 3 */ +#define AD7793_STAT_CH2 (1 << 1) /* Channel 2 */ +#define AD7793_STAT_CH1 (1 << 0) /* Channel 1 */ + +/* Mode Register Bit Designations (AD7793_REG_MODE) */ +#define AD7793_MODE_SEL(x) (((x) & 0x7) << 13) /* Operation Mode Select */ +#define AD7793_MODE_SEL_MASK (0x7 << 13) /* Operation Mode Select mask */ +#define AD7793_MODE_CLKSRC(x) (((x) & 0x3) << 6) /* ADC Clock Source Select */ +#define AD7793_MODE_RATE(x) ((x) & 0xF) /* Filter Update Rate Select */ + +#define AD7793_MODE_CONT 0 /* Continuous Conversion Mode */ +#define AD7793_MODE_SINGLE 1 /* Single Conversion Mode */ +#define AD7793_MODE_IDLE 2 /* Idle Mode */ +#define AD7793_MODE_PWRDN 3 /* Power-Down Mode */ +#define AD7793_MODE_CAL_INT_ZERO 4 /* Internal Zero-Scale Calibration */ +#define AD7793_MODE_CAL_INT_FULL 5 /* Internal Full-Scale Calibration */ +#define AD7793_MODE_CAL_SYS_ZERO 6 /* System Zero-Scale Calibration */ +#define AD7793_MODE_CAL_SYS_FULL 7 /* System Full-Scale Calibration */ + +#define AD7793_CLK_INT 0 /* Internal 64 kHz Clock not + * available at the CLK pin */ +#define AD7793_CLK_INT_CO 1 /* Internal 64 kHz Clock available + * at the CLK pin */ +#define AD7793_CLK_EXT 2 /* External 64 kHz Clock */ +#define AD7793_CLK_EXT_DIV2 3 /* External Clock divided by 2 */ + +/* Configuration Register Bit Designations (AD7793_REG_CONF) */ +#define AD7793_CONF_VBIAS(x) (((x) & 0x3) << 14) /* Bias Voltage + * Generator Enable */ +#define AD7793_CONF_BO_EN (1 << 13) /* Burnout Current Enable */ +#define AD7793_CONF_UNIPOLAR (1 << 12) /* Unipolar/Bipolar Enable */ +#define AD7793_CONF_BOOST (1 << 11) /* Boost Enable */ +#define AD7793_CONF_GAIN(x) (((x) & 0x7) << 8) /* Gain Select */ +#define AD7793_CONF_REFSEL(x) ((x) << 6) /* INT/EXT Reference Select */ +#define AD7793_CONF_BUF (1 << 4) /* Buffered Mode Enable */ +#define AD7793_CONF_CHAN(x) ((x) & 0xf) /* Channel select */ +#define AD7793_CONF_CHAN_MASK 0xf /* Channel select mask */ + +#define AD7793_CH_AIN1P_AIN1M 0 /* AIN1(+) - AIN1(-) */ +#define AD7793_CH_AIN2P_AIN2M 1 /* AIN2(+) - AIN2(-) */ +#define AD7793_CH_AIN3P_AIN3M 2 /* AIN3(+) - AIN3(-) */ +#define AD7793_CH_AIN1M_AIN1M 3 /* AIN1(-) - AIN1(-) */ +#define AD7793_CH_TEMP 6 /* Temp Sensor */ +#define AD7793_CH_AVDD_MONITOR 7 /* AVDD Monitor */ + +#define AD7795_CH_AIN4P_AIN4M 4 /* AIN4(+) - AIN4(-) */ +#define AD7795_CH_AIN5P_AIN5M 5 /* AIN5(+) - AIN5(-) */ +#define AD7795_CH_AIN6P_AIN6M 6 /* AIN6(+) - AIN6(-) */ +#define AD7795_CH_AIN1M_AIN1M 8 /* AIN1(-) - AIN1(-) */ + +/* ID Register Bit Designations (AD7793_REG_ID) */ +#define AD7785_ID 0x3 +#define AD7792_ID 0xA +#define AD7793_ID 0xB +#define AD7794_ID 0xF +#define AD7795_ID 0xF +#define AD7796_ID 0xA +#define AD7797_ID 0xB +#define AD7798_ID 0x8 +#define AD7799_ID 0x9 +#define AD7793_ID_MASK 0xF + +/* IO (Excitation Current Sources) Register Bit Designations (AD7793_REG_IO) */ +#define AD7793_IO_IEXC1_IOUT1_IEXC2_IOUT2 0 /* IEXC1 connect to IOUT1, + * IEXC2 connect to IOUT2 */ +#define AD7793_IO_IEXC1_IOUT2_IEXC2_IOUT1 1 /* IEXC1 connect to IOUT2, + * IEXC2 connect to IOUT1 */ +#define AD7793_IO_IEXC1_IEXC2_IOUT1 2 /* Both current sources + * IEXC1,2 connect to IOUT1 */ +#define AD7793_IO_IEXC1_IEXC2_IOUT2 3 /* Both current sources + * IEXC1,2 connect to IOUT2 */ + +#define AD7793_IO_IXCEN_10uA (1 << 0) /* Excitation Current 10uA */ +#define AD7793_IO_IXCEN_210uA (2 << 0) /* Excitation Current 210uA */ +#define AD7793_IO_IXCEN_1mA (3 << 0) /* Excitation Current 1mA */ + +/* NOTE: + * The AD7792/AD7793 features a dual use data out ready DOUT/RDY output. + * In order to avoid contentions on the SPI bus, it's therefore necessary + * to use spi bus locking. + * + * The DOUT/RDY output must also be wired to an interrupt capable GPIO. + */ + +#define AD7793_FLAG_HAS_CLKSEL BIT(0) +#define AD7793_FLAG_HAS_REFSEL BIT(1) +#define AD7793_FLAG_HAS_VBIAS BIT(2) +#define AD7793_HAS_EXITATION_CURRENT BIT(3) +#define AD7793_FLAG_HAS_GAIN BIT(4) +#define AD7793_FLAG_HAS_BUFFER BIT(5) + +struct ad7793_chip_info { + unsigned int id; + const struct iio_chan_spec *channels; + unsigned int num_channels; + unsigned int flags; + + const struct iio_info *iio_info; + const u16 *sample_freq_avail; +}; + +struct ad7793_state { + const struct ad7793_chip_info *chip_info; + u16 mode; + u16 conf; + u32 scale_avail[8][2]; + + struct ad_sigma_delta sd; + +}; + +enum ad7793_supported_device_ids { + ID_AD7785, + ID_AD7792, + ID_AD7793, + ID_AD7794, + ID_AD7795, + ID_AD7796, + ID_AD7797, + ID_AD7798, + ID_AD7799, +}; + +static struct ad7793_state *ad_sigma_delta_to_ad7793(struct ad_sigma_delta *sd) +{ + return container_of(sd, struct ad7793_state, sd); +} + +static int ad7793_set_channel(struct ad_sigma_delta *sd, unsigned int channel) +{ + struct ad7793_state *st = ad_sigma_delta_to_ad7793(sd); + + st->conf &= ~AD7793_CONF_CHAN_MASK; + st->conf |= AD7793_CONF_CHAN(channel); + + return ad_sd_write_reg(&st->sd, AD7793_REG_CONF, 2, st->conf); +} + +static int ad7793_set_mode(struct ad_sigma_delta *sd, + enum ad_sigma_delta_mode mode) +{ + struct ad7793_state *st = ad_sigma_delta_to_ad7793(sd); + + st->mode &= ~AD7793_MODE_SEL_MASK; + st->mode |= AD7793_MODE_SEL(mode); + + return ad_sd_write_reg(&st->sd, AD7793_REG_MODE, 2, st->mode); +} + +static const struct ad_sigma_delta_info ad7793_sigma_delta_info = { + .set_channel = ad7793_set_channel, + .set_mode = ad7793_set_mode, + .has_registers = true, + .addr_shift = 3, + .read_mask = BIT(6), + .irq_flags = IRQF_TRIGGER_FALLING, + .num_resetclks = 32, +}; + +static const struct ad_sd_calib_data ad7793_calib_arr[6] = { + {AD7793_MODE_CAL_INT_ZERO, AD7793_CH_AIN1P_AIN1M}, + {AD7793_MODE_CAL_INT_FULL, AD7793_CH_AIN1P_AIN1M}, + {AD7793_MODE_CAL_INT_ZERO, AD7793_CH_AIN2P_AIN2M}, + {AD7793_MODE_CAL_INT_FULL, AD7793_CH_AIN2P_AIN2M}, + {AD7793_MODE_CAL_INT_ZERO, AD7793_CH_AIN3P_AIN3M}, + {AD7793_MODE_CAL_INT_FULL, AD7793_CH_AIN3P_AIN3M} +}; + +static int ad7793_calibrate_all(struct ad7793_state *st) +{ + return ad_sd_calibrate_all(&st->sd, ad7793_calib_arr, + ARRAY_SIZE(ad7793_calib_arr)); +} + +static int ad7793_check_platform_data(struct ad7793_state *st, + const struct ad7793_platform_data *pdata) +{ + if ((pdata->current_source_direction == AD7793_IEXEC1_IEXEC2_IOUT1 || + pdata->current_source_direction == AD7793_IEXEC1_IEXEC2_IOUT2) && + ((pdata->exitation_current != AD7793_IX_10uA) && + (pdata->exitation_current != AD7793_IX_210uA))) + return -EINVAL; + + if (!(st->chip_info->flags & AD7793_FLAG_HAS_CLKSEL) && + pdata->clock_src != AD7793_CLK_SRC_INT) + return -EINVAL; + + if (!(st->chip_info->flags & AD7793_FLAG_HAS_REFSEL) && + pdata->refsel != AD7793_REFSEL_REFIN1) + return -EINVAL; + + if (!(st->chip_info->flags & AD7793_FLAG_HAS_VBIAS) && + pdata->bias_voltage != AD7793_BIAS_VOLTAGE_DISABLED) + return -EINVAL; + + if (!(st->chip_info->flags & AD7793_HAS_EXITATION_CURRENT) && + pdata->exitation_current != AD7793_IX_DISABLED) + return -EINVAL; + + return 0; +} + +static int ad7793_setup(struct iio_dev *indio_dev, + const struct ad7793_platform_data *pdata, + unsigned int vref_mv) +{ + struct ad7793_state *st = iio_priv(indio_dev); + int i, ret; + unsigned long long scale_uv; + u32 id; + + ret = ad7793_check_platform_data(st, pdata); + if (ret) + return ret; + + /* reset the serial interface */ + ret = ad_sd_reset(&st->sd); + if (ret < 0) + goto out; + + /* + * Per AD7792/AD7793 datasheet (Rev. B, page 25, RESET section), + * allow 500 us after a reset before accessing on-chip registers. + */ + fsleep(500); + + /* write/read test for device presence */ + ret = ad_sd_read_reg(&st->sd, AD7793_REG_ID, 1, &id); + if (ret) + goto out; + + id &= AD7793_ID_MASK; + + if (id != st->chip_info->id) { + ret = dev_err_probe(&st->sd.spi->dev, -ENODEV, + "device ID query failed\n"); + goto out; + } + + st->mode = AD7793_MODE_RATE(1); + st->conf = 0; + + if (st->chip_info->flags & AD7793_FLAG_HAS_CLKSEL) + st->mode |= AD7793_MODE_CLKSRC(pdata->clock_src); + if (st->chip_info->flags & AD7793_FLAG_HAS_REFSEL) + st->conf |= AD7793_CONF_REFSEL(pdata->refsel); + if (st->chip_info->flags & AD7793_FLAG_HAS_VBIAS) + st->conf |= AD7793_CONF_VBIAS(pdata->bias_voltage); + if (pdata->buffered || !(st->chip_info->flags & AD7793_FLAG_HAS_BUFFER)) + st->conf |= AD7793_CONF_BUF; + if (pdata->boost_enable && + (st->chip_info->flags & AD7793_FLAG_HAS_VBIAS)) + st->conf |= AD7793_CONF_BOOST; + if (pdata->burnout_current) + st->conf |= AD7793_CONF_BO_EN; + if (pdata->unipolar) + st->conf |= AD7793_CONF_UNIPOLAR; + + if (!(st->chip_info->flags & AD7793_FLAG_HAS_GAIN)) + st->conf |= AD7793_CONF_GAIN(7); + + ret = ad7793_set_mode(&st->sd, AD_SD_MODE_IDLE); + if (ret) + goto out; + + ret = ad7793_set_channel(&st->sd, 0); + if (ret) + goto out; + + if (st->chip_info->flags & AD7793_HAS_EXITATION_CURRENT) { + ret = ad_sd_write_reg(&st->sd, AD7793_REG_IO, 1, + pdata->exitation_current | + (pdata->current_source_direction << 2)); + if (ret) + goto out; + } + + ret = ad7793_calibrate_all(st); + if (ret) + goto out; + + /* Populate available ADC input ranges */ + for (i = 0; i < ARRAY_SIZE(st->scale_avail); i++) { + scale_uv = ((u64)vref_mv * 100000000) + >> (st->chip_info->channels[0].scan_type.realbits - + (!!(st->conf & AD7793_CONF_UNIPOLAR) ? 0 : 1)); + scale_uv >>= i; + + st->scale_avail[i][1] = do_div(scale_uv, 100000000) * 10; + st->scale_avail[i][0] = scale_uv; + } + + return 0; +out: + return dev_err_probe(&st->sd.spi->dev, ret, "setup failed\n"); +} + +static const u16 ad7793_sample_freq_avail[16] = {0, 470, 242, 123, 62, 50, 39, + 33, 19, 17, 16, 12, 10, 8, 6, 4}; + +static const u16 ad7797_sample_freq_avail[16] = {0, 0, 0, 123, 62, 50, 0, + 33, 0, 17, 16, 12, 10, 8, 6, 4}; + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL( + "470 242 123 62 50 39 33 19 17 16 12 10 8 6 4"); + +static IIO_CONST_ATTR_NAMED(sampling_frequency_available_ad7797, + sampling_frequency_available, "123 62 50 33 17 16 12 10 8 6 4"); + +static int ad7793_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct ad7793_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = (int *)st->scale_avail; + *type = IIO_VAL_INT_PLUS_NANO; + /* Values are stored in a 2D matrix */ + *length = ARRAY_SIZE(st->scale_avail) * 2; + + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static struct attribute *ad7793_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group ad7793_attribute_group = { + .attrs = ad7793_attributes, +}; + +static struct attribute *ad7797_attributes[] = { + &iio_const_attr_sampling_frequency_available_ad7797.dev_attr.attr, + NULL +}; + +static const struct attribute_group ad7797_attribute_group = { + .attrs = ad7797_attributes, +}; + +static int ad7793_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad7793_state *st = iio_priv(indio_dev); + int ret; + unsigned long long scale_uv; + bool unipolar = !!(st->conf & AD7793_CONF_UNIPOLAR); + + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = ad_sigma_delta_single_conversion(indio_dev, chan, val); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->differential) { + *val = st-> + scale_avail[(st->conf >> 8) & 0x7][0]; + *val2 = st-> + scale_avail[(st->conf >> 8) & 0x7][1]; + return IIO_VAL_INT_PLUS_NANO; + } + /* 1170mV / 2^23 * 6 */ + scale_uv = (1170ULL * 1000000000ULL * 6ULL); + break; + case IIO_TEMP: + /* 1170mV / 0.81 mV/C / 2^23 */ + scale_uv = 1444444444444444ULL; + break; + default: + return -EINVAL; + } + + scale_uv >>= (chan->scan_type.realbits - (unipolar ? 0 : 1)); + *val = 0; + *val2 = scale_uv; + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + if (!unipolar) + *val = -(1 << (chan->scan_type.realbits - 1)); + else + *val = 0; + + /* Kelvin to Celsius */ + if (chan->type == IIO_TEMP) { + unsigned long long offset; + unsigned int shift; + + shift = chan->scan_type.realbits - (unipolar ? 0 : 1); + offset = 273ULL << shift; + do_div(offset, 1444); + *val -= offset; + } + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = st->chip_info + ->sample_freq_avail[AD7793_MODE_RATE(st->mode)]; + return IIO_VAL_INT; + } + return -EINVAL; +} + +static int __ad7793_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ad7793_state *st = iio_priv(indio_dev); + int i; + unsigned int tmp; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + for (i = 0; i < ARRAY_SIZE(st->scale_avail); i++) { + if (val2 != st->scale_avail[i][1]) + continue; + + tmp = st->conf; + st->conf &= ~AD7793_CONF_GAIN(-1); + st->conf |= AD7793_CONF_GAIN(i); + + if (tmp == st->conf) + return 0; + + ad_sd_write_reg(&st->sd, AD7793_REG_CONF, + sizeof(st->conf), st->conf); + ad7793_calibrate_all(st); + + return 0; + } + return -EINVAL; + case IIO_CHAN_INFO_SAMP_FREQ: + if (!val) + return -EINVAL; + + for (i = 0; i < 16; i++) + if (val == st->chip_info->sample_freq_avail[i]) + break; + + if (i == 16) + return -EINVAL; + + st->mode &= ~AD7793_MODE_RATE(-1); + st->mode |= AD7793_MODE_RATE(i); + ad_sd_write_reg(&st->sd, AD7793_REG_MODE, sizeof(st->mode), + st->mode); + return 0; + default: + return -EINVAL; + } +} + +static int ad7793_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = __ad7793_write_raw(indio_dev, chan, val, val2, mask); + + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ad7793_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + return IIO_VAL_INT_PLUS_NANO; +} + +static const struct iio_info ad7793_info = { + .read_raw = &ad7793_read_raw, + .write_raw = &ad7793_write_raw, + .write_raw_get_fmt = &ad7793_write_raw_get_fmt, + .read_avail = ad7793_read_avail, + .attrs = &ad7793_attribute_group, + .validate_trigger = ad_sd_validate_trigger, +}; + +static const struct iio_info ad7797_info = { + .read_raw = &ad7793_read_raw, + .write_raw = &ad7793_write_raw, + .write_raw_get_fmt = &ad7793_write_raw_get_fmt, + .attrs = &ad7797_attribute_group, + .validate_trigger = ad_sd_validate_trigger, +}; + +#define __AD7793_CHANNEL(_si, _channel1, _channel2, _address, _bits, \ + _storagebits, _shift, _extend_name, _type, _mask_type_av, _mask_all) \ + { \ + .type = (_type), \ + .differential = (_channel2 == -1 ? 0 : 1), \ + .indexed = 1, \ + .channel = (_channel1), \ + .channel2 = (_channel2), \ + .address = (_address), \ + .extend_name = (_extend_name), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = (_mask_type_av), \ + .info_mask_shared_by_all = _mask_all, \ + .scan_index = (_si), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (_bits), \ + .storagebits = (_storagebits), \ + .shift = (_shift), \ + .endianness = IIO_BE, \ + }, \ + } + +#define AD7793_DIFF_CHANNEL(_si, _channel1, _channel2, _address, _bits, \ + _storagebits, _shift) \ + __AD7793_CHANNEL(_si, _channel1, _channel2, _address, _bits, \ + _storagebits, _shift, NULL, IIO_VOLTAGE, \ + BIT(IIO_CHAN_INFO_SCALE), \ + BIT(IIO_CHAN_INFO_SAMP_FREQ)) + +#define AD7793_SHORTED_CHANNEL(_si, _channel, _address, _bits, \ + _storagebits, _shift) \ + __AD7793_CHANNEL(_si, _channel, _channel, _address, _bits, \ + _storagebits, _shift, "shorted", IIO_VOLTAGE, \ + BIT(IIO_CHAN_INFO_SCALE), \ + BIT(IIO_CHAN_INFO_SAMP_FREQ)) + +#define AD7793_TEMP_CHANNEL(_si, _address, _bits, _storagebits, _shift) \ + __AD7793_CHANNEL(_si, 0, -1, _address, _bits, \ + _storagebits, _shift, NULL, IIO_TEMP, \ + 0, \ + BIT(IIO_CHAN_INFO_SAMP_FREQ)) + +#define AD7793_SUPPLY_CHANNEL(_si, _channel, _address, _bits, _storagebits, \ + _shift) \ + __AD7793_CHANNEL(_si, _channel, -1, _address, _bits, \ + _storagebits, _shift, "supply", IIO_VOLTAGE, \ + 0, \ + BIT(IIO_CHAN_INFO_SAMP_FREQ)) + +#define AD7797_DIFF_CHANNEL(_si, _channel1, _channel2, _address, _bits, \ + _storagebits, _shift) \ + __AD7793_CHANNEL(_si, _channel1, _channel2, _address, _bits, \ + _storagebits, _shift, NULL, IIO_VOLTAGE, \ + 0, \ + BIT(IIO_CHAN_INFO_SAMP_FREQ)) + +#define AD7797_SHORTED_CHANNEL(_si, _channel, _address, _bits, \ + _storagebits, _shift) \ + __AD7793_CHANNEL(_si, _channel, _channel, _address, _bits, \ + _storagebits, _shift, "shorted", IIO_VOLTAGE, \ + 0, \ + BIT(IIO_CHAN_INFO_SAMP_FREQ)) + +#define DECLARE_AD7793_CHANNELS(_name, _b, _sb, _s) \ +const struct iio_chan_spec _name##_channels[] = { \ + AD7793_DIFF_CHANNEL(0, 0, 0, AD7793_CH_AIN1P_AIN1M, (_b), (_sb), (_s)), \ + AD7793_DIFF_CHANNEL(1, 1, 1, AD7793_CH_AIN2P_AIN2M, (_b), (_sb), (_s)), \ + AD7793_DIFF_CHANNEL(2, 2, 2, AD7793_CH_AIN3P_AIN3M, (_b), (_sb), (_s)), \ + AD7793_SHORTED_CHANNEL(3, 0, AD7793_CH_AIN1M_AIN1M, (_b), (_sb), (_s)), \ + AD7793_TEMP_CHANNEL(4, AD7793_CH_TEMP, (_b), (_sb), (_s)), \ + AD7793_SUPPLY_CHANNEL(5, 3, AD7793_CH_AVDD_MONITOR, (_b), (_sb), (_s)), \ + IIO_CHAN_SOFT_TIMESTAMP(6), \ +} + +#define DECLARE_AD7795_CHANNELS(_name, _b, _sb) \ +const struct iio_chan_spec _name##_channels[] = { \ + AD7793_DIFF_CHANNEL(0, 0, 0, AD7793_CH_AIN1P_AIN1M, (_b), (_sb), 0), \ + AD7793_DIFF_CHANNEL(1, 1, 1, AD7793_CH_AIN2P_AIN2M, (_b), (_sb), 0), \ + AD7793_DIFF_CHANNEL(2, 2, 2, AD7793_CH_AIN3P_AIN3M, (_b), (_sb), 0), \ + AD7793_DIFF_CHANNEL(3, 3, 3, AD7795_CH_AIN4P_AIN4M, (_b), (_sb), 0), \ + AD7793_DIFF_CHANNEL(4, 4, 4, AD7795_CH_AIN5P_AIN5M, (_b), (_sb), 0), \ + AD7793_DIFF_CHANNEL(5, 5, 5, AD7795_CH_AIN6P_AIN6M, (_b), (_sb), 0), \ + AD7793_SHORTED_CHANNEL(6, 0, AD7795_CH_AIN1M_AIN1M, (_b), (_sb), 0), \ + AD7793_TEMP_CHANNEL(7, AD7793_CH_TEMP, (_b), (_sb), 0), \ + AD7793_SUPPLY_CHANNEL(8, 3, AD7793_CH_AVDD_MONITOR, (_b), (_sb), 0), \ + IIO_CHAN_SOFT_TIMESTAMP(9), \ +} + +#define DECLARE_AD7797_CHANNELS(_name, _b, _sb) \ +const struct iio_chan_spec _name##_channels[] = { \ + AD7797_DIFF_CHANNEL(0, 0, 0, AD7793_CH_AIN1P_AIN1M, (_b), (_sb), 0), \ + AD7797_SHORTED_CHANNEL(1, 0, AD7793_CH_AIN1M_AIN1M, (_b), (_sb), 0), \ + AD7793_TEMP_CHANNEL(2, AD7793_CH_TEMP, (_b), (_sb), 0), \ + AD7793_SUPPLY_CHANNEL(3, 3, AD7793_CH_AVDD_MONITOR, (_b), (_sb), 0), \ + IIO_CHAN_SOFT_TIMESTAMP(4), \ +} + +#define DECLARE_AD7799_CHANNELS(_name, _b, _sb) \ +const struct iio_chan_spec _name##_channels[] = { \ + AD7793_DIFF_CHANNEL(0, 0, 0, AD7793_CH_AIN1P_AIN1M, (_b), (_sb), 0), \ + AD7793_DIFF_CHANNEL(1, 1, 1, AD7793_CH_AIN2P_AIN2M, (_b), (_sb), 0), \ + AD7793_DIFF_CHANNEL(2, 2, 2, AD7793_CH_AIN3P_AIN3M, (_b), (_sb), 0), \ + AD7793_SHORTED_CHANNEL(3, 0, AD7793_CH_AIN1M_AIN1M, (_b), (_sb), 0), \ + AD7793_SUPPLY_CHANNEL(4, 3, AD7793_CH_AVDD_MONITOR, (_b), (_sb), 0), \ + IIO_CHAN_SOFT_TIMESTAMP(5), \ +} + +static DECLARE_AD7793_CHANNELS(ad7785, 20, 32, 4); +static DECLARE_AD7793_CHANNELS(ad7792, 16, 32, 0); +static DECLARE_AD7793_CHANNELS(ad7793, 24, 32, 0); +static DECLARE_AD7795_CHANNELS(ad7794, 16, 32); +static DECLARE_AD7795_CHANNELS(ad7795, 24, 32); +static DECLARE_AD7797_CHANNELS(ad7796, 16, 16); +static DECLARE_AD7797_CHANNELS(ad7797, 24, 32); +static DECLARE_AD7799_CHANNELS(ad7798, 16, 16); +static DECLARE_AD7799_CHANNELS(ad7799, 24, 32); + +static const struct ad7793_chip_info ad7793_chip_info_tbl[] = { + [ID_AD7785] = { + .id = AD7785_ID, + .channels = ad7785_channels, + .num_channels = ARRAY_SIZE(ad7785_channels), + .iio_info = &ad7793_info, + .sample_freq_avail = ad7793_sample_freq_avail, + .flags = AD7793_FLAG_HAS_CLKSEL | + AD7793_FLAG_HAS_REFSEL | + AD7793_FLAG_HAS_VBIAS | + AD7793_HAS_EXITATION_CURRENT | + AD7793_FLAG_HAS_GAIN | + AD7793_FLAG_HAS_BUFFER, + }, + [ID_AD7792] = { + .id = AD7792_ID, + .channels = ad7792_channels, + .num_channels = ARRAY_SIZE(ad7792_channels), + .iio_info = &ad7793_info, + .sample_freq_avail = ad7793_sample_freq_avail, + .flags = AD7793_FLAG_HAS_CLKSEL | + AD7793_FLAG_HAS_REFSEL | + AD7793_FLAG_HAS_VBIAS | + AD7793_HAS_EXITATION_CURRENT | + AD7793_FLAG_HAS_GAIN | + AD7793_FLAG_HAS_BUFFER, + }, + [ID_AD7793] = { + .id = AD7793_ID, + .channels = ad7793_channels, + .num_channels = ARRAY_SIZE(ad7793_channels), + .iio_info = &ad7793_info, + .sample_freq_avail = ad7793_sample_freq_avail, + .flags = AD7793_FLAG_HAS_CLKSEL | + AD7793_FLAG_HAS_REFSEL | + AD7793_FLAG_HAS_VBIAS | + AD7793_HAS_EXITATION_CURRENT | + AD7793_FLAG_HAS_GAIN | + AD7793_FLAG_HAS_BUFFER, + }, + [ID_AD7794] = { + .id = AD7794_ID, + .channels = ad7794_channels, + .num_channels = ARRAY_SIZE(ad7794_channels), + .iio_info = &ad7793_info, + .sample_freq_avail = ad7793_sample_freq_avail, + .flags = AD7793_FLAG_HAS_CLKSEL | + AD7793_FLAG_HAS_REFSEL | + AD7793_FLAG_HAS_VBIAS | + AD7793_HAS_EXITATION_CURRENT | + AD7793_FLAG_HAS_GAIN | + AD7793_FLAG_HAS_BUFFER, + }, + [ID_AD7795] = { + .id = AD7795_ID, + .channels = ad7795_channels, + .num_channels = ARRAY_SIZE(ad7795_channels), + .iio_info = &ad7793_info, + .sample_freq_avail = ad7793_sample_freq_avail, + .flags = AD7793_FLAG_HAS_CLKSEL | + AD7793_FLAG_HAS_REFSEL | + AD7793_FLAG_HAS_VBIAS | + AD7793_HAS_EXITATION_CURRENT | + AD7793_FLAG_HAS_GAIN | + AD7793_FLAG_HAS_BUFFER, + }, + [ID_AD7796] = { + .id = AD7796_ID, + .channels = ad7796_channels, + .num_channels = ARRAY_SIZE(ad7796_channels), + .iio_info = &ad7797_info, + .sample_freq_avail = ad7797_sample_freq_avail, + .flags = AD7793_FLAG_HAS_CLKSEL, + }, + [ID_AD7797] = { + .id = AD7797_ID, + .channels = ad7797_channels, + .num_channels = ARRAY_SIZE(ad7797_channels), + .iio_info = &ad7797_info, + .sample_freq_avail = ad7797_sample_freq_avail, + .flags = AD7793_FLAG_HAS_CLKSEL, + }, + [ID_AD7798] = { + .id = AD7798_ID, + .channels = ad7798_channels, + .num_channels = ARRAY_SIZE(ad7798_channels), + .iio_info = &ad7793_info, + .sample_freq_avail = ad7793_sample_freq_avail, + .flags = AD7793_FLAG_HAS_GAIN | + AD7793_FLAG_HAS_BUFFER, + }, + [ID_AD7799] = { + .id = AD7799_ID, + .channels = ad7799_channels, + .num_channels = ARRAY_SIZE(ad7799_channels), + .iio_info = &ad7793_info, + .sample_freq_avail = ad7793_sample_freq_avail, + .flags = AD7793_FLAG_HAS_GAIN | + AD7793_FLAG_HAS_BUFFER, + }, +}; + +static int ad7793_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + const struct ad7793_platform_data *pdata = dev_get_platdata(dev); + struct ad7793_state *st; + struct iio_dev *indio_dev; + int ret, vref_mv = 0; + + if (!pdata) + return dev_err_probe(dev, -ENODEV, "no platform data?\n"); + + if (!spi->irq) + return dev_err_probe(dev, -ENODEV, "no IRQ?\n"); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + ad_sd_init(&st->sd, indio_dev, spi, &ad7793_sigma_delta_info); + + if (pdata->refsel != AD7793_REFSEL_INTERNAL) { + ret = devm_regulator_get_enable_read_voltage(dev, "refin"); + if (ret < 0) + return ret; + + vref_mv = ret / 1000; + } else { + vref_mv = 1170; /* Built-in ref */ + } + + st->chip_info = + &ad7793_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + indio_dev->info = st->chip_info->iio_info; + + ret = devm_ad_sd_setup_buffer_and_trigger(dev, indio_dev); + if (ret) + return ret; + + ret = ad7793_setup(indio_dev, pdata, vref_mv); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id ad7793_id[] = { + { .name = "ad7785", .driver_data = ID_AD7785 }, + { .name = "ad7792", .driver_data = ID_AD7792 }, + { .name = "ad7793", .driver_data = ID_AD7793 }, + { .name = "ad7794", .driver_data = ID_AD7794 }, + { .name = "ad7795", .driver_data = ID_AD7795 }, + { .name = "ad7796", .driver_data = ID_AD7796 }, + { .name = "ad7797", .driver_data = ID_AD7797 }, + { .name = "ad7798", .driver_data = ID_AD7798 }, + { .name = "ad7799", .driver_data = ID_AD7799 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7793_id); + +static struct spi_driver ad7793_driver = { + .driver = { + .name = "ad7793", + }, + .probe = ad7793_probe, + .id_table = ad7793_id, +}; +module_spi_driver(ad7793_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7793 and similar ADCs"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_AD_SIGMA_DELTA"); diff --git a/drivers/iio/adc/ad7887.c b/drivers/iio/adc/ad7887.c new file mode 100644 index 000000000..62aaedbce --- /dev/null +++ b/drivers/iio/adc/ad7887.c @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD7887 SPI ADC driver + * + * Copyright 2010-2011 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +#define AD7887_REF_DIS BIT(5) /* on-chip reference disable */ +#define AD7887_DUAL BIT(4) /* dual-channel mode */ +#define AD7887_CH_AIN1 BIT(3) /* convert on channel 1, DUAL=1 */ +#define AD7887_CH_AIN0 0 /* convert on channel 0, DUAL=0,1 */ +#define AD7887_PM_MODE1 0 /* CS based shutdown */ +#define AD7887_PM_MODE2 1 /* full on */ +#define AD7887_PM_MODE3 2 /* auto shutdown after conversion */ +#define AD7887_PM_MODE4 3 /* standby mode */ + +enum ad7887_channels { + AD7887_CH0, + AD7887_CH0_CH1, + AD7887_CH1, +}; + +/** + * struct ad7887_chip_info - chip specific information + * @int_vref_mv: the internal reference voltage + * @channels: channels specification + * @num_channels: number of channels + * @dual_channels: channels specification in dual mode + * @num_dual_channels: number of channels in dual mode + */ +struct ad7887_chip_info { + u16 int_vref_mv; + const struct iio_chan_spec *channels; + unsigned int num_channels; + const struct iio_chan_spec *dual_channels; + unsigned int num_dual_channels; +}; + +struct ad7887_state { + struct spi_device *spi; + const struct ad7887_chip_info *chip_info; + struct regulator *reg; + struct spi_transfer xfer[4]; + struct spi_message msg[3]; + struct spi_message *ring_msg; + unsigned char tx_cmd_buf[4]; + + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + * Buffer needs to be large enough to hold two 16 bit samples and a + * 64 bit aligned 64 bit timestamp. + */ + unsigned char data[ALIGN(4, sizeof(s64)) + sizeof(s64)] __aligned(IIO_DMA_MINALIGN); +}; + +enum ad7887_supported_device_ids { + ID_AD7887 +}; + +static int ad7887_ring_preenable(struct iio_dev *indio_dev) +{ + struct ad7887_state *st = iio_priv(indio_dev); + + /* We know this is a single long so can 'cheat' */ + switch (*indio_dev->active_scan_mask) { + case (1 << 0): + st->ring_msg = &st->msg[AD7887_CH0]; + break; + case (1 << 1): + st->ring_msg = &st->msg[AD7887_CH1]; + /* Dummy read: push CH1 setting down to hardware */ + spi_sync(st->spi, st->ring_msg); + break; + case ((1 << 1) | (1 << 0)): + st->ring_msg = &st->msg[AD7887_CH0_CH1]; + break; + } + + return 0; +} + +static int ad7887_ring_postdisable(struct iio_dev *indio_dev) +{ + struct ad7887_state *st = iio_priv(indio_dev); + + /* dummy read: restore default CH0 settings */ + return spi_sync(st->spi, &st->msg[AD7887_CH0]); +} + +static irqreturn_t ad7887_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad7887_state *st = iio_priv(indio_dev); + int b_sent; + + b_sent = spi_sync(st->spi, st->ring_msg); + if (b_sent) + goto done; + + iio_push_to_buffers_with_timestamp(indio_dev, st->data, + iio_get_time_ns(indio_dev)); +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_buffer_setup_ops ad7887_ring_setup_ops = { + .preenable = &ad7887_ring_preenable, + .postdisable = &ad7887_ring_postdisable, +}; + +static int ad7887_scan_direct(struct ad7887_state *st, unsigned ch) +{ + int ret = spi_sync(st->spi, &st->msg[ch]); + if (ret) + return ret; + + return (st->data[(ch * 2)] << 8) | st->data[(ch * 2) + 1]; +} + +static int ad7887_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret; + struct ad7887_state *st = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = ad7887_scan_direct(st, chan->address); + iio_device_release_direct(indio_dev); + + if (ret < 0) + return ret; + *val = ret >> chan->scan_type.shift; + *val &= GENMASK(chan->scan_type.realbits - 1, 0); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (st->reg) { + *val = regulator_get_voltage(st->reg); + if (*val < 0) + return *val; + *val /= 1000; + } else { + *val = st->chip_info->int_vref_mv; + } + + *val2 = chan->scan_type.realbits; + + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + +#define AD7887_CHANNEL(x) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (x), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .address = (x), \ + .scan_index = (x), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 12, \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +static const struct iio_chan_spec ad7887_channels[] = { + AD7887_CHANNEL(0), + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct iio_chan_spec ad7887_dual_channels[] = { + AD7887_CHANNEL(0), + AD7887_CHANNEL(1), + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static const struct ad7887_chip_info ad7887_chip_info_tbl[] = { + /* + * More devices added in future + */ + [ID_AD7887] = { + .channels = ad7887_channels, + .num_channels = ARRAY_SIZE(ad7887_channels), + .dual_channels = ad7887_dual_channels, + .num_dual_channels = ARRAY_SIZE(ad7887_dual_channels), + .int_vref_mv = 2500, + }, +}; + +static const struct iio_info ad7887_info = { + .read_raw = &ad7887_read_raw, +}; + +static void ad7887_reg_disable(void *data) +{ + struct regulator *reg = data; + + regulator_disable(reg); +} + +static int ad7887_probe(struct spi_device *spi) +{ + const struct ad7887_platform_data *pdata = dev_get_platdata(&spi->dev); + struct ad7887_state *st; + struct iio_dev *indio_dev; + uint8_t mode; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->reg = devm_regulator_get_optional(&spi->dev, "vref"); + if (IS_ERR(st->reg)) { + if (PTR_ERR(st->reg) != -ENODEV) + return PTR_ERR(st->reg); + + st->reg = NULL; + } + + if (st->reg) { + ret = regulator_enable(st->reg); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&spi->dev, ad7887_reg_disable, st->reg); + if (ret) + return ret; + } + + st->chip_info = + &ad7887_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + st->spi = spi; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &ad7887_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + /* Setup default message */ + + mode = AD7887_PM_MODE4; + if (!st->reg) + mode |= AD7887_REF_DIS; + if (pdata && pdata->en_dual) + mode |= AD7887_DUAL; + + st->tx_cmd_buf[0] = AD7887_CH_AIN0 | mode; + + st->xfer[0].rx_buf = &st->data[0]; + st->xfer[0].tx_buf = &st->tx_cmd_buf[0]; + st->xfer[0].len = 2; + + spi_message_init(&st->msg[AD7887_CH0]); + spi_message_add_tail(&st->xfer[0], &st->msg[AD7887_CH0]); + + if (pdata && pdata->en_dual) { + st->tx_cmd_buf[2] = AD7887_CH_AIN1 | mode; + + st->xfer[1].rx_buf = &st->data[0]; + st->xfer[1].tx_buf = &st->tx_cmd_buf[2]; + st->xfer[1].len = 2; + + st->xfer[2].rx_buf = &st->data[2]; + st->xfer[2].tx_buf = &st->tx_cmd_buf[0]; + st->xfer[2].len = 2; + + spi_message_init(&st->msg[AD7887_CH0_CH1]); + spi_message_add_tail(&st->xfer[1], &st->msg[AD7887_CH0_CH1]); + spi_message_add_tail(&st->xfer[2], &st->msg[AD7887_CH0_CH1]); + + st->xfer[3].rx_buf = &st->data[2]; + st->xfer[3].tx_buf = &st->tx_cmd_buf[2]; + st->xfer[3].len = 2; + + spi_message_init(&st->msg[AD7887_CH1]); + spi_message_add_tail(&st->xfer[3], &st->msg[AD7887_CH1]); + + indio_dev->channels = st->chip_info->dual_channels; + indio_dev->num_channels = st->chip_info->num_dual_channels; + } else { + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + } + + ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, + &iio_pollfunc_store_time, + &ad7887_trigger_handler, &ad7887_ring_setup_ops); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id ad7887_id[] = { + { .name = "ad7887", .driver_data = ID_AD7887 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7887_id); + +static struct spi_driver ad7887_driver = { + .driver = { + .name = "ad7887", + }, + .probe = ad7887_probe, + .id_table = ad7887_id, +}; +module_spi_driver(ad7887_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7887 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ad7923.c b/drivers/iio/adc/ad7923.c new file mode 100644 index 000000000..44e014da9 --- /dev/null +++ b/drivers/iio/adc/ad7923.c @@ -0,0 +1,399 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD7904/AD7914/AD7923/AD7924/AD7908/AD7918/AD7928 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc (from AD7923 Driver) + * Copyright 2012 CS Systemes d'Information + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define AD7923_WRITE_CR BIT(11) /* write control register */ +#define AD7923_RANGE BIT(1) /* range to REFin */ +#define AD7923_CODING BIT(0) /* coding is straight binary */ +#define AD7923_PM_MODE_AS (1) /* auto shutdown */ +#define AD7923_PM_MODE_FS (2) /* full shutdown */ +#define AD7923_PM_MODE_OPS (3) /* normal operation */ +#define AD7923_SEQUENCE_OFF (0) /* no sequence function */ +#define AD7923_SEQUENCE_PROTECT (2) /* no interrupt write cycle */ +#define AD7923_SEQUENCE_ON (3) /* continuous sequence */ + + +#define AD7923_PM_MODE_WRITE(mode) ((mode) << 4) /* write mode */ +#define AD7923_CHANNEL_WRITE(channel) ((channel) << 6) /* write channel */ +#define AD7923_SEQUENCE_WRITE(sequence) ((((sequence) & 1) << 3) \ + + (((sequence) & 2) << 9)) + /* write sequence function */ +/* left shift for CR : bit 11 transmit in first */ +#define AD7923_SHIFT_REGISTER 4 + +/* val = value, dec = left shift, bits = number of bits of the mask */ +#define EXTRACT(val, dec, bits) (((val) >> (dec)) & ((1 << (bits)) - 1)) + +struct ad7923_state { + struct spi_device *spi; + struct spi_transfer ring_xfer[9]; + struct spi_transfer scan_single_xfer[2]; + struct spi_message ring_msg; + struct spi_message scan_single_msg; + + struct regulator *reg; + + unsigned int settings; + + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + * Ensure rx_buf can be directly used in iio_push_to_buffers_with_timetamp + * Length = 8 channels + 4 extra for 8 byte timestamp + */ + __be16 rx_buf[12] __aligned(IIO_DMA_MINALIGN); + __be16 tx_buf[8]; +}; + +struct ad7923_chip_info { + const struct iio_chan_spec *channels; + unsigned int num_channels; +}; + +enum ad7923_id { + AD7904, + AD7914, + AD7924, + AD7908, + AD7918, + AD7928 +}; + +#define AD7923_V_CHAN(index, bits) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .address = index, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 12 - (bits), \ + .endianness = IIO_BE, \ + }, \ + } + +#define DECLARE_AD7923_CHANNELS(name, bits) \ +const struct iio_chan_spec name ## _channels[] = { \ + AD7923_V_CHAN(0, bits), \ + AD7923_V_CHAN(1, bits), \ + AD7923_V_CHAN(2, bits), \ + AD7923_V_CHAN(3, bits), \ + IIO_CHAN_SOFT_TIMESTAMP(4), \ +} + +#define DECLARE_AD7908_CHANNELS(name, bits) \ +const struct iio_chan_spec name ## _channels[] = { \ + AD7923_V_CHAN(0, bits), \ + AD7923_V_CHAN(1, bits), \ + AD7923_V_CHAN(2, bits), \ + AD7923_V_CHAN(3, bits), \ + AD7923_V_CHAN(4, bits), \ + AD7923_V_CHAN(5, bits), \ + AD7923_V_CHAN(6, bits), \ + AD7923_V_CHAN(7, bits), \ + IIO_CHAN_SOFT_TIMESTAMP(8), \ +} + +static DECLARE_AD7923_CHANNELS(ad7904, 8); +static DECLARE_AD7923_CHANNELS(ad7914, 10); +static DECLARE_AD7923_CHANNELS(ad7924, 12); +static DECLARE_AD7908_CHANNELS(ad7908, 8); +static DECLARE_AD7908_CHANNELS(ad7918, 10); +static DECLARE_AD7908_CHANNELS(ad7928, 12); + +static const struct ad7923_chip_info ad7923_chip_info[] = { + [AD7904] = { + .channels = ad7904_channels, + .num_channels = ARRAY_SIZE(ad7904_channels), + }, + [AD7914] = { + .channels = ad7914_channels, + .num_channels = ARRAY_SIZE(ad7914_channels), + }, + [AD7924] = { + .channels = ad7924_channels, + .num_channels = ARRAY_SIZE(ad7924_channels), + }, + [AD7908] = { + .channels = ad7908_channels, + .num_channels = ARRAY_SIZE(ad7908_channels), + }, + [AD7918] = { + .channels = ad7918_channels, + .num_channels = ARRAY_SIZE(ad7918_channels), + }, + [AD7928] = { + .channels = ad7928_channels, + .num_channels = ARRAY_SIZE(ad7928_channels), + }, +}; + +/* + * ad7923_update_scan_mode() setup the spi transfer buffer for the new scan mask + */ +static int ad7923_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *active_scan_mask) +{ + struct ad7923_state *st = iio_priv(indio_dev); + int i, cmd, len; + + len = 0; + /* + * For this driver the last channel is always the software timestamp so + * skip that one. + */ + for_each_set_bit(i, active_scan_mask, indio_dev->num_channels - 1) { + cmd = AD7923_WRITE_CR | AD7923_CHANNEL_WRITE(i) | + AD7923_SEQUENCE_WRITE(AD7923_SEQUENCE_OFF) | + st->settings; + cmd <<= AD7923_SHIFT_REGISTER; + st->tx_buf[len++] = cpu_to_be16(cmd); + } + /* build spi ring message */ + st->ring_xfer[0].tx_buf = &st->tx_buf[0]; + st->ring_xfer[0].len = len; + st->ring_xfer[0].cs_change = 1; + + spi_message_init(&st->ring_msg); + spi_message_add_tail(&st->ring_xfer[0], &st->ring_msg); + + for (i = 0; i < len; i++) { + st->ring_xfer[i + 1].rx_buf = &st->rx_buf[i]; + st->ring_xfer[i + 1].len = 2; + st->ring_xfer[i + 1].cs_change = 1; + spi_message_add_tail(&st->ring_xfer[i + 1], &st->ring_msg); + } + /* make sure last transfer cs_change is not set */ + st->ring_xfer[i + 1].cs_change = 0; + + return 0; +} + +static irqreturn_t ad7923_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad7923_state *st = iio_priv(indio_dev); + int b_sent; + + b_sent = spi_sync(st->spi, &st->ring_msg); + if (b_sent) + goto done; + + iio_push_to_buffers_with_ts(indio_dev, st->rx_buf, sizeof(st->rx_buf), + iio_get_time_ns(indio_dev)); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ad7923_scan_direct(struct ad7923_state *st, unsigned int ch) +{ + int ret, cmd; + + cmd = AD7923_WRITE_CR | AD7923_CHANNEL_WRITE(ch) | + AD7923_SEQUENCE_WRITE(AD7923_SEQUENCE_OFF) | + st->settings; + cmd <<= AD7923_SHIFT_REGISTER; + st->tx_buf[0] = cpu_to_be16(cmd); + + ret = spi_sync(st->spi, &st->scan_single_msg); + if (ret) + return ret; + + return be16_to_cpu(st->rx_buf[0]); +} + +static int ad7923_get_range(struct ad7923_state *st) +{ + int vref; + + vref = regulator_get_voltage(st->reg); + if (vref < 0) + return vref; + + vref /= 1000; + + if (!(st->settings & AD7923_RANGE)) + vref *= 2; + + return vref; +} + +static int ad7923_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret; + struct ad7923_state *st = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = ad7923_scan_direct(st, chan->address); + iio_device_release_direct(indio_dev); + + if (ret < 0) + return ret; + + if (chan->address == EXTRACT(ret, 12, 4)) + *val = EXTRACT(ret, chan->scan_type.shift, + chan->scan_type.realbits); + else + return -EIO; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = ad7923_get_range(st); + if (ret < 0) + return ret; + *val = ret; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + +static const struct iio_info ad7923_info = { + .read_raw = &ad7923_read_raw, + .update_scan_mode = ad7923_update_scan_mode, +}; + +static void ad7923_regulator_disable(void *data) +{ + struct ad7923_state *st = data; + + regulator_disable(st->reg); +} + +static int ad7923_probe(struct spi_device *spi) +{ + u32 ad7923_range = AD7923_RANGE; + struct ad7923_state *st; + struct iio_dev *indio_dev; + const struct ad7923_chip_info *info; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + if (device_property_read_bool(&spi->dev, "adi,range-double")) + ad7923_range = 0; + + st->spi = spi; + st->settings = AD7923_CODING | ad7923_range | + AD7923_PM_MODE_WRITE(AD7923_PM_MODE_OPS); + + info = &ad7923_chip_info[spi_get_device_id(spi)->driver_data]; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = info->channels; + indio_dev->num_channels = info->num_channels; + indio_dev->info = &ad7923_info; + + /* Setup default message */ + + st->scan_single_xfer[0].tx_buf = &st->tx_buf[0]; + st->scan_single_xfer[0].len = 2; + st->scan_single_xfer[0].cs_change = 1; + st->scan_single_xfer[1].rx_buf = &st->rx_buf[0]; + st->scan_single_xfer[1].len = 2; + + spi_message_init(&st->scan_single_msg); + spi_message_add_tail(&st->scan_single_xfer[0], &st->scan_single_msg); + spi_message_add_tail(&st->scan_single_xfer[1], &st->scan_single_msg); + + st->reg = devm_regulator_get(&spi->dev, "refin"); + if (IS_ERR(st->reg)) + return PTR_ERR(st->reg); + + ret = regulator_enable(st->reg); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&spi->dev, ad7923_regulator_disable, st); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, NULL, + &ad7923_trigger_handler, NULL); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id ad7923_id[] = { + { .name = "ad7904", .driver_data = AD7904 }, + { .name = "ad7914", .driver_data = AD7914 }, + { .name = "ad7923", .driver_data = AD7924 }, + { .name = "ad7924", .driver_data = AD7924 }, + { .name = "ad7908", .driver_data = AD7908 }, + { .name = "ad7918", .driver_data = AD7918 }, + { .name = "ad7928", .driver_data = AD7928 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7923_id); + +static const struct of_device_id ad7923_of_match[] = { + { .compatible = "adi,ad7904", }, + { .compatible = "adi,ad7914", }, + { .compatible = "adi,ad7923", }, + { .compatible = "adi,ad7924", }, + { .compatible = "adi,ad7908", }, + { .compatible = "adi,ad7918", }, + { .compatible = "adi,ad7928", }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7923_of_match); + +static struct spi_driver ad7923_driver = { + .driver = { + .name = "ad7923", + .of_match_table = ad7923_of_match, + }, + .probe = ad7923_probe, + .id_table = ad7923_id, +}; +module_spi_driver(ad7923_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_AUTHOR("Patrick Vasseur "); +MODULE_DESCRIPTION("Analog Devices AD7923 and similar ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ad7944.c b/drivers/iio/adc/ad7944.c new file mode 100644 index 000000000..12656d3cd --- /dev/null +++ b/drivers/iio/adc/ad7944.c @@ -0,0 +1,889 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices AD7944/85/86 PulSAR ADC family driver. + * + * Copyright 2024 Analog Devices, Inc. + * Copyright 2024 BayLibre, SAS + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define AD7944_INTERNAL_REF_MV 4096 + +struct ad7944_timing_spec { + /* Normal mode max conversion time (t_{CONV}). */ + unsigned int conv_ns; + /* TURBO mode max conversion time (t_{CONV}). */ + unsigned int turbo_conv_ns; +}; + +enum ad7944_spi_mode { + /* datasheet calls this "4-wire mode" */ + AD7944_SPI_MODE_DEFAULT, + /* datasheet calls this "3-wire mode" (not related to SPI_3WIRE!) */ + AD7944_SPI_MODE_SINGLE, + /* datasheet calls this "chain mode" */ + AD7944_SPI_MODE_CHAIN, +}; + +/* maps adi,spi-mode property value to enum */ +static const char * const ad7944_spi_modes[] = { + [AD7944_SPI_MODE_DEFAULT] = "", + [AD7944_SPI_MODE_SINGLE] = "single", + [AD7944_SPI_MODE_CHAIN] = "chain", +}; + +struct ad7944_adc { + struct spi_device *spi; + enum ad7944_spi_mode spi_mode; + struct spi_transfer xfers[3]; + struct spi_message msg; + struct spi_transfer offload_xfers[2]; + struct spi_message offload_msg; + struct spi_offload *offload; + struct spi_offload_trigger *offload_trigger; + unsigned long offload_trigger_hz; + int sample_freq_range[3]; + void *chain_mode_buf; + /* Chip-specific timing specifications. */ + const struct ad7944_timing_spec *timing_spec; + /* GPIO connected to CNV pin. */ + struct gpio_desc *cnv; + /* Optional GPIO to enable turbo mode. */ + struct gpio_desc *turbo; + /* Indicates TURBO is hard-wired to be always enabled. */ + bool always_turbo; + /* Reference voltage (millivolts). */ + unsigned int ref_mv; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + struct { + union { + u16 u16; + u32 u32; + } raw; + aligned_s64 timestamp; + } sample __aligned(IIO_DMA_MINALIGN); +}; + +/* quite time before CNV rising edge */ +#define AD7944_T_QUIET_NS 20 +/* minimum CNV high time to trigger conversion */ +#define AD7944_T_CNVH_NS 10 + +static const struct ad7944_timing_spec ad7944_timing_spec = { + .conv_ns = 420, + .turbo_conv_ns = 320, +}; + +static const struct ad7944_timing_spec ad7986_timing_spec = { + .conv_ns = 500, + .turbo_conv_ns = 400, +}; + +struct ad7944_chip_info { + const char *name; + const struct ad7944_timing_spec *timing_spec; + u32 max_sample_rate_hz; + const struct iio_chan_spec channels[2]; + const struct iio_chan_spec offload_channels[1]; +}; + +/* get number of bytes for SPI xfer */ +#define AD7944_SPI_BYTES(scan_type) ((scan_type).realbits > 16 ? 4 : 2) + +/* + * AD7944_DEFINE_CHIP_INFO - Define a chip info structure for a specific chip + * @_name: The name of the chip + * @_ts: The timing specification for the chip + * @_max: The maximum sample rate in Hz + * @_bits: The number of bits in the conversion result + * @_diff: Whether the chip is true differential or not + */ +#define AD7944_DEFINE_CHIP_INFO(_name, _ts, _max, _bits, _diff) \ +static const struct ad7944_chip_info _name##_chip_info = { \ + .name = #_name, \ + .timing_spec = &_ts##_timing_spec, \ + .max_sample_rate_hz = _max, \ + .channels = { \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .differential = _diff, \ + .channel = 0, \ + .channel2 = _diff ? 1 : 0, \ + .scan_index = 0, \ + .scan_type.sign = _diff ? 's' : 'u', \ + .scan_type.realbits = _bits, \ + .scan_type.storagebits = _bits > 16 ? 32 : 16, \ + .scan_type.endianness = IIO_CPU, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \ + | BIT(IIO_CHAN_INFO_SCALE), \ + }, \ + IIO_CHAN_SOFT_TIMESTAMP(1), \ + }, \ + .offload_channels = { \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .differential = _diff, \ + .channel = 0, \ + .channel2 = _diff ? 1 : 0, \ + .scan_index = 0, \ + .scan_type.sign = _diff ? 's' : 'u', \ + .scan_type.realbits = _bits, \ + .scan_type.storagebits = 32, \ + .scan_type.endianness = IIO_CPU, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \ + | BIT(IIO_CHAN_INFO_SCALE) \ + | BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_separate_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + }, \ + }, \ +} + +/* + * Notes on the offload channels: + * - There is no soft timestamp since everything is done in hardware. + * - There is a sampling frequency attribute added. This controls the SPI + * offload trigger. + * - The storagebits value depends on the SPI offload provider. Currently there + * is only one supported provider, namely the ADI PULSAR ADC HDL project, + * which always uses 32-bit words for data values, even for <= 16-bit ADCs. + * So the value is just hardcoded to 32 for now. + */ + +/* pseudo-differential with ground sense */ +AD7944_DEFINE_CHIP_INFO(ad7944, ad7944, 2.5 * MEGA, 14, 0); +AD7944_DEFINE_CHIP_INFO(ad7985, ad7944, 2.5 * MEGA, 16, 0); +/* fully differential */ +AD7944_DEFINE_CHIP_INFO(ad7986, ad7986, 2 * MEGA, 18, 1); + +static int ad7944_3wire_cs_mode_init_msg(struct device *dev, struct ad7944_adc *adc, + const struct iio_chan_spec *chan) +{ + unsigned int t_conv_ns = adc->always_turbo ? adc->timing_spec->turbo_conv_ns + : adc->timing_spec->conv_ns; + struct spi_transfer *xfers = adc->xfers; + + /* + * CS is tied to CNV and we need a low to high transition to start the + * conversion, so place CNV low for t_QUIET to prepare for this. + */ + xfers[0].delay.value = AD7944_T_QUIET_NS; + xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS; + + /* + * CS has to be high for full conversion time to avoid triggering the + * busy indication. + */ + xfers[1].cs_off = 1; + xfers[1].delay.value = t_conv_ns; + xfers[1].delay.unit = SPI_DELAY_UNIT_NSECS; + + /* Then we can read the data during the acquisition phase */ + xfers[2].rx_buf = &adc->sample.raw; + xfers[2].len = AD7944_SPI_BYTES(chan->scan_type); + xfers[2].bits_per_word = chan->scan_type.realbits; + + spi_message_init_with_transfers(&adc->msg, xfers, 3); + + return devm_spi_optimize_message(dev, adc->spi, &adc->msg); +} + +static int ad7944_4wire_mode_init_msg(struct device *dev, struct ad7944_adc *adc, + const struct iio_chan_spec *chan) +{ + unsigned int t_conv_ns = adc->always_turbo ? adc->timing_spec->turbo_conv_ns + : adc->timing_spec->conv_ns; + struct spi_transfer *xfers = adc->xfers; + + /* + * CS has to be high for full conversion time to avoid triggering the + * busy indication. + */ + xfers[0].cs_off = 1; + xfers[0].delay.value = t_conv_ns; + xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS; + + xfers[1].rx_buf = &adc->sample.raw; + xfers[1].len = AD7944_SPI_BYTES(chan->scan_type); + xfers[1].bits_per_word = chan->scan_type.realbits; + + spi_message_init_with_transfers(&adc->msg, xfers, 2); + + return devm_spi_optimize_message(dev, adc->spi, &adc->msg); +} + +static int ad7944_chain_mode_init_msg(struct device *dev, struct ad7944_adc *adc, + const struct iio_chan_spec *chan, + u32 n_chain_dev) +{ + struct spi_transfer *xfers = adc->xfers; + + /* + * NB: SCLK has to be low before we toggle CS to avoid triggering the + * busy indication. + */ + if (adc->spi->mode & SPI_CPOL) + return dev_err_probe(dev, -EINVAL, + "chain mode requires ~SPI_CPOL\n"); + + /* + * We only support CNV connected to CS in chain mode and we need CNV + * to be high during the transfer to trigger the conversion. + */ + if (!(adc->spi->mode & SPI_CS_HIGH)) + return dev_err_probe(dev, -EINVAL, + "chain mode requires SPI_CS_HIGH\n"); + + /* CNV has to be high for full conversion time before reading data. */ + xfers[0].delay.value = adc->timing_spec->conv_ns; + xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS; + + xfers[1].rx_buf = adc->chain_mode_buf; + xfers[1].len = AD7944_SPI_BYTES(chan->scan_type) * n_chain_dev; + xfers[1].bits_per_word = chan->scan_type.realbits; + + spi_message_init_with_transfers(&adc->msg, xfers, 2); + + return devm_spi_optimize_message(dev, adc->spi, &adc->msg); +} + +/* + * Unlike ad7944_3wire_cs_mode_init_msg(), this creates a message that reads + * during the conversion phase instead of the acquisition phase when reading + * a sample from the ADC. This is needed to be able to read at the maximum + * sample rate. It requires the SPI controller to have offload support and a + * high enough SCLK rate to read the sample during the conversion phase. + */ +static int ad7944_3wire_cs_mode_init_offload_msg(struct device *dev, + struct ad7944_adc *adc, + const struct iio_chan_spec *chan) +{ + struct spi_transfer *xfers = adc->offload_xfers; + int ret; + + /* + * CS is tied to CNV and we need a low to high transition to start the + * conversion, so place CNV low for t_QUIET to prepare for this. + */ + xfers[0].delay.value = AD7944_T_QUIET_NS; + xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS; + /* CNV has to be high for a minimum time to trigger conversion. */ + xfers[0].cs_change = 1; + xfers[0].cs_change_delay.value = AD7944_T_CNVH_NS; + xfers[0].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS; + + /* Then we can read the previous sample during the conversion phase */ + xfers[1].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM; + xfers[1].len = AD7944_SPI_BYTES(chan->scan_type); + xfers[1].bits_per_word = chan->scan_type.realbits; + + spi_message_init_with_transfers(&adc->offload_msg, xfers, + ARRAY_SIZE(adc->offload_xfers)); + + adc->offload_msg.offload = adc->offload; + + ret = devm_spi_optimize_message(dev, adc->spi, &adc->offload_msg); + if (ret) + return dev_err_probe(dev, ret, "failed to prepare offload msg\n"); + + return 0; +} + +/** + * ad7944_convert_and_acquire - Perform a single conversion and acquisition + * @adc: The ADC device structure + * Return: 0 on success, a negative error code on failure + * + * Perform a conversion and acquisition of a single sample using the + * pre-optimized adc->msg. + * + * Upon successful return adc->sample.raw will contain the conversion result + * (or adc->chain_mode_buf if the device is using chain mode). + */ +static int ad7944_convert_and_acquire(struct ad7944_adc *adc) +{ + int ret; + + /* + * In 4-wire mode, the CNV line is held high for the entire conversion + * and acquisition process. In other modes adc->cnv is NULL and is + * ignored (CS is wired to CNV in those cases). + */ + gpiod_set_value_cansleep(adc->cnv, 1); + ret = spi_sync(adc->spi, &adc->msg); + gpiod_set_value_cansleep(adc->cnv, 0); + + return ret; +} + +static int ad7944_single_conversion(struct ad7944_adc *adc, + const struct iio_chan_spec *chan, + int *val) +{ + int ret; + + ret = ad7944_convert_and_acquire(adc); + if (ret) + return ret; + + if (adc->spi_mode == AD7944_SPI_MODE_CHAIN) { + if (chan->scan_type.realbits > 16) + *val = ((u32 *)adc->chain_mode_buf)[chan->scan_index]; + else + *val = ((u16 *)adc->chain_mode_buf)[chan->scan_index]; + } else { + if (chan->scan_type.realbits > 16) + *val = adc->sample.raw.u32; + else + *val = adc->sample.raw.u16; + } + + if (chan->scan_type.sign == 's') + *val = sign_extend32(*val, chan->scan_type.realbits - 1); + else + *val &= GENMASK(chan->scan_type.realbits - 1, 0); + + return IIO_VAL_INT; +} + +static int ad7944_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct ad7944_adc *adc = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = adc->sample_freq_range; + *type = IIO_VAL_INT; + return IIO_AVAIL_RANGE; + default: + return -EINVAL; + } +} + +static int ad7944_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long info) +{ + struct ad7944_adc *adc = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad7944_single_conversion(adc, chan, val); + iio_device_release_direct(indio_dev); + return ret; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + *val = adc->ref_mv; + + if (chan->scan_type.sign == 's') + *val2 = chan->scan_type.realbits - 1; + else + *val2 = chan->scan_type.realbits; + + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = adc->offload_trigger_hz; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int ad7944_set_sample_freq(struct ad7944_adc *adc, int val) +{ + struct spi_offload_trigger_config config = { + .type = SPI_OFFLOAD_TRIGGER_PERIODIC, + .periodic = { + .frequency_hz = val, + }, + }; + int ret; + + ret = spi_offload_trigger_validate(adc->offload_trigger, &config); + if (ret) + return ret; + + adc->offload_trigger_hz = config.periodic.frequency_hz; + + return 0; +} + +static int ad7944_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int val, int val2, long info) +{ + struct ad7944_adc *adc = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (val < 1 || val > adc->sample_freq_range[2]) + return -EINVAL; + + return ad7944_set_sample_freq(adc, val); + default: + return -EINVAL; + } +} + +static int ad7944_write_raw_get_fmt(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT; + default: + return IIO_VAL_INT_PLUS_MICRO; + } +} + +static const struct iio_info ad7944_iio_info = { + .read_avail = &ad7944_read_avail, + .read_raw = &ad7944_read_raw, + .write_raw = &ad7944_write_raw, + .write_raw_get_fmt = &ad7944_write_raw_get_fmt, +}; + +static int ad7944_offload_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad7944_adc *adc = iio_priv(indio_dev); + struct spi_offload_trigger_config config = { + .type = SPI_OFFLOAD_TRIGGER_PERIODIC, + .periodic = { + .frequency_hz = adc->offload_trigger_hz, + }, + }; + int ret; + + gpiod_set_value_cansleep(adc->turbo, 1); + + ret = spi_offload_trigger_enable(adc->offload, adc->offload_trigger, + &config); + if (ret) + gpiod_set_value_cansleep(adc->turbo, 0); + + return ret; +} + +static int ad7944_offload_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad7944_adc *adc = iio_priv(indio_dev); + + spi_offload_trigger_disable(adc->offload, adc->offload_trigger); + gpiod_set_value_cansleep(adc->turbo, 0); + + return 0; +} + +static const struct iio_buffer_setup_ops ad7944_offload_buffer_setup_ops = { + .postenable = &ad7944_offload_buffer_postenable, + .predisable = &ad7944_offload_buffer_predisable, +}; + +static irqreturn_t ad7944_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad7944_adc *adc = iio_priv(indio_dev); + int ret; + + ret = ad7944_convert_and_acquire(adc); + if (ret) + goto out; + + if (adc->spi_mode == AD7944_SPI_MODE_CHAIN) + iio_push_to_buffers_with_timestamp(indio_dev, adc->chain_mode_buf, + pf->timestamp); + else + iio_push_to_buffers_with_timestamp(indio_dev, &adc->sample.raw, + pf->timestamp); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +/** + * ad7944_chain_mode_alloc - allocate and initialize channel specs and buffers + * for daisy-chained devices + * @dev: The device for devm_ functions + * @chan_template: The channel template for the devices (array of 2 channels + * voltage and timestamp) + * @n_chain_dev: The number of devices in the chain + * @chain_chan: Pointer to receive the allocated channel specs + * @chain_mode_buf: Pointer to receive the allocated rx buffer + * @chain_scan_masks: Pointer to receive the allocated scan masks + * Return: 0 on success, a negative error code on failure + */ +static int ad7944_chain_mode_alloc(struct device *dev, + const struct iio_chan_spec *chan_template, + u32 n_chain_dev, + struct iio_chan_spec **chain_chan, + void **chain_mode_buf, + unsigned long **chain_scan_masks) +{ + struct iio_chan_spec *chan; + size_t chain_mode_buf_size; + unsigned long *scan_masks; + void *buf; + int i; + + /* 1 channel for each device in chain plus 1 for soft timestamp */ + + chan = devm_kcalloc(dev, n_chain_dev + 1, sizeof(*chan), GFP_KERNEL); + if (!chan) + return -ENOMEM; + + for (i = 0; i < n_chain_dev; i++) { + chan[i] = chan_template[0]; + + if (chan_template[0].differential) { + chan[i].channel = 2 * i; + chan[i].channel2 = 2 * i + 1; + } else { + chan[i].channel = i; + } + + chan[i].scan_index = i; + } + + /* soft timestamp */ + chan[i] = chan_template[1]; + chan[i].scan_index = i; + + *chain_chan = chan; + + /* 1 word for each voltage channel + aligned u64 for timestamp */ + + chain_mode_buf_size = ALIGN(n_chain_dev * + AD7944_SPI_BYTES(chan[0].scan_type), sizeof(u64)) + sizeof(u64); + buf = devm_kzalloc(dev, chain_mode_buf_size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + *chain_mode_buf = buf; + + /* + * Have to limit n_chain_dev due to current implementation of + * available_scan_masks. + */ + if (n_chain_dev > BITS_PER_LONG) + return dev_err_probe(dev, -EINVAL, + "chain is limited to 32 devices\n"); + + scan_masks = devm_kcalloc(dev, 2, sizeof(*scan_masks), GFP_KERNEL); + if (!scan_masks) + return -ENOMEM; + + /* + * Scan mask is needed since we always have to read all devices in the + * chain in one SPI transfer. + */ + scan_masks[0] = GENMASK(n_chain_dev - 1, 0); + + *chain_scan_masks = scan_masks; + + return 0; +} + +static const char * const ad7944_power_supplies[] = { + "avdd", "dvdd", "bvdd", "vio" +}; + +static const struct spi_offload_config ad7944_offload_config = { + .capability_flags = SPI_OFFLOAD_CAP_TRIGGER | + SPI_OFFLOAD_CAP_RX_STREAM_DMA, +}; + +static int ad7944_probe(struct spi_device *spi) +{ + const struct ad7944_chip_info *chip_info; + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ad7944_adc *adc; + bool have_refin; + struct iio_chan_spec *chain_chan; + unsigned long *chain_scan_masks; + u32 n_chain_dev; + int ret, ref_mv; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->spi = spi; + + chip_info = spi_get_device_match_data(spi); + if (!chip_info) + return dev_err_probe(dev, -EINVAL, "no chip info\n"); + + adc->timing_spec = chip_info->timing_spec; + + adc->sample_freq_range[0] = 1; /* min */ + adc->sample_freq_range[1] = 1; /* step */ + adc->sample_freq_range[2] = chip_info->max_sample_rate_hz; /* max */ + + ret = device_property_match_property_string(dev, "adi,spi-mode", + ad7944_spi_modes, + ARRAY_SIZE(ad7944_spi_modes)); + /* absence of adi,spi-mode property means default mode */ + if (ret == -EINVAL) + adc->spi_mode = AD7944_SPI_MODE_DEFAULT; + else if (ret < 0) + return dev_err_probe(dev, ret, + "getting adi,spi-mode property failed\n"); + else + adc->spi_mode = ret; + + /* + * Some chips use unusual word sizes, so check now instead of waiting + * for the first xfer. + */ + if (!spi_is_bpw_supported(spi, chip_info->channels[0].scan_type.realbits)) + return dev_err_probe(dev, -EINVAL, + "SPI host does not support %d bits per word\n", + chip_info->channels[0].scan_type.realbits); + + ret = devm_regulator_bulk_get_enable(dev, + ARRAY_SIZE(ad7944_power_supplies), + ad7944_power_supplies); + if (ret) + return dev_err_probe(dev, ret, + "failed to get and enable supplies\n"); + + /* + * Sort out what is being used for the reference voltage. Options are: + * - internal reference: neither REF or REFIN is connected + * - internal reference with external buffer: REF not connected, REFIN + * is connected + * - external reference: REF is connected, REFIN is not connected + */ + + ret = devm_regulator_get_enable_read_voltage(dev, "ref"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to get REF voltage\n"); + + ref_mv = ret == -ENODEV ? 0 : ret / 1000; + + ret = devm_regulator_get_enable_optional(dev, "refin"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to get REFIN voltage\n"); + + have_refin = ret != -ENODEV; + + if (have_refin && ref_mv) + return dev_err_probe(dev, -EINVAL, + "cannot have both refin and ref supplies\n"); + + adc->ref_mv = ref_mv ?: AD7944_INTERNAL_REF_MV; + + adc->cnv = devm_gpiod_get_optional(dev, "cnv", GPIOD_OUT_LOW); + if (IS_ERR(adc->cnv)) + return dev_err_probe(dev, PTR_ERR(adc->cnv), + "failed to get CNV GPIO\n"); + + if (!adc->cnv && adc->spi_mode == AD7944_SPI_MODE_DEFAULT) + return dev_err_probe(&spi->dev, -EINVAL, "CNV GPIO is required\n"); + if (adc->cnv && adc->spi_mode != AD7944_SPI_MODE_DEFAULT) + return dev_err_probe(&spi->dev, -EINVAL, + "CNV GPIO in single and chain mode is not currently supported\n"); + + adc->turbo = devm_gpiod_get_optional(dev, "turbo", GPIOD_OUT_LOW); + if (IS_ERR(adc->turbo)) + return dev_err_probe(dev, PTR_ERR(adc->turbo), + "failed to get TURBO GPIO\n"); + + adc->always_turbo = device_property_present(dev, "adi,always-turbo"); + + if (adc->turbo && adc->always_turbo) + return dev_err_probe(dev, -EINVAL, + "cannot have both turbo-gpios and adi,always-turbo\n"); + + if (adc->spi_mode == AD7944_SPI_MODE_CHAIN && adc->always_turbo) + return dev_err_probe(dev, -EINVAL, + "cannot have both chain mode and always turbo\n"); + + switch (adc->spi_mode) { + case AD7944_SPI_MODE_DEFAULT: + ret = ad7944_4wire_mode_init_msg(dev, adc, &chip_info->channels[0]); + if (ret) + return ret; + + break; + case AD7944_SPI_MODE_SINGLE: + ret = ad7944_3wire_cs_mode_init_msg(dev, adc, &chip_info->channels[0]); + if (ret) + return ret; + + break; + case AD7944_SPI_MODE_CHAIN: + ret = device_property_read_u32(dev, "#daisy-chained-devices", + &n_chain_dev); + if (ret) + return dev_err_probe(dev, ret, + "failed to get #daisy-chained-devices\n"); + + ret = ad7944_chain_mode_alloc(dev, chip_info->channels, + n_chain_dev, &chain_chan, + &adc->chain_mode_buf, + &chain_scan_masks); + if (ret) + return ret; + + ret = ad7944_chain_mode_init_msg(dev, adc, &chain_chan[0], + n_chain_dev); + if (ret) + return ret; + + break; + } + + indio_dev->name = chip_info->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ad7944_iio_info; + + adc->offload = devm_spi_offload_get(dev, spi, &ad7944_offload_config); + ret = PTR_ERR_OR_ZERO(adc->offload); + if (ret && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to get offload\n"); + + /* Fall back to low speed usage when no SPI offload available. */ + if (ret == -ENODEV) { + if (adc->spi_mode == AD7944_SPI_MODE_CHAIN) { + indio_dev->available_scan_masks = chain_scan_masks; + indio_dev->channels = chain_chan; + indio_dev->num_channels = n_chain_dev + 1; + } else { + indio_dev->channels = chip_info->channels; + indio_dev->num_channels = ARRAY_SIZE(chip_info->channels); + } + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + ad7944_trigger_handler, + NULL); + if (ret) + return ret; + } else { + struct dma_chan *rx_dma; + + if (adc->spi_mode != AD7944_SPI_MODE_SINGLE) + return dev_err_probe(dev, -EINVAL, + "offload only supported in single mode\n"); + + indio_dev->setup_ops = &ad7944_offload_buffer_setup_ops; + indio_dev->channels = chip_info->offload_channels; + indio_dev->num_channels = ARRAY_SIZE(chip_info->offload_channels); + + adc->offload_trigger = devm_spi_offload_trigger_get(dev, + adc->offload, SPI_OFFLOAD_TRIGGER_PERIODIC); + if (IS_ERR(adc->offload_trigger)) + return dev_err_probe(dev, PTR_ERR(adc->offload_trigger), + "failed to get offload trigger\n"); + + ret = ad7944_set_sample_freq(adc, 2 * MEGA); + if (ret) + return dev_err_probe(dev, ret, + "failed to init sample rate\n"); + + rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev, + adc->offload); + if (IS_ERR(rx_dma)) + return dev_err_probe(dev, PTR_ERR(rx_dma), + "failed to get offload RX DMA\n"); + + /* + * REVISIT: ideally, we would confirm that the offload RX DMA + * buffer layout is the same as what is hard-coded in + * offload_channels. Right now, the only supported offload + * is the pulsar_adc project which always uses 32-bit word + * size for data values, regardless of the SPI bits per word. + */ + + ret = devm_iio_dmaengine_buffer_setup_with_handle(dev, + indio_dev, rx_dma, IIO_BUFFER_DIRECTION_IN); + if (ret) + return ret; + + ret = ad7944_3wire_cs_mode_init_offload_msg(dev, adc, + &chip_info->offload_channels[0]); + if (ret) + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ad7944_of_match[] = { + { .compatible = "adi,ad7944", .data = &ad7944_chip_info }, + { .compatible = "adi,ad7985", .data = &ad7985_chip_info }, + { .compatible = "adi,ad7986", .data = &ad7986_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7944_of_match); + +static const struct spi_device_id ad7944_spi_id[] = { + { .name = "ad7944", .driver_data = (kernel_ulong_t)&ad7944_chip_info }, + { .name = "ad7985", .driver_data = (kernel_ulong_t)&ad7985_chip_info }, + { .name = "ad7986", .driver_data = (kernel_ulong_t)&ad7986_chip_info }, + { } + +}; +MODULE_DEVICE_TABLE(spi, ad7944_spi_id); + +static struct spi_driver ad7944_driver = { + .driver = { + .name = "ad7944", + .of_match_table = ad7944_of_match, + }, + .probe = ad7944_probe, + .id_table = ad7944_spi_id, +}; +module_spi_driver(ad7944_driver); + +MODULE_AUTHOR("David Lechner "); +MODULE_DESCRIPTION("Analog Devices AD7944 PulSAR ADC family driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER"); diff --git a/drivers/iio/adc/ad7949.c b/drivers/iio/adc/ad7949.c new file mode 100644 index 000000000..93a57a3d5 --- /dev/null +++ b/drivers/iio/adc/ad7949.c @@ -0,0 +1,432 @@ +// SPDX-License-Identifier: GPL-2.0 +/* ad7949.c - Analog Devices ADC driver 14/16 bits 4/8 channels + * + * Copyright (C) 2018 CMC NV + * + * https://www.analog.com/media/en/technical-documentation/data-sheets/AD7949.pdf + */ + +#include +#include +#include +#include +#include +#include + +#define AD7949_CFG_MASK_TOTAL GENMASK(13, 0) + +/* CFG: Configuration Update */ +#define AD7949_CFG_MASK_OVERWRITE BIT(13) + +/* INCC: Input Channel Configuration */ +#define AD7949_CFG_MASK_INCC GENMASK(12, 10) +#define AD7949_CFG_VAL_INCC_UNIPOLAR_GND 7 +#define AD7949_CFG_VAL_INCC_UNIPOLAR_COMM 6 +#define AD7949_CFG_VAL_INCC_UNIPOLAR_DIFF 4 +#define AD7949_CFG_VAL_INCC_TEMP 3 +#define AD7949_CFG_VAL_INCC_BIPOLAR 2 +#define AD7949_CFG_VAL_INCC_BIPOLAR_DIFF 0 + +/* INX: Input channel Selection in a binary fashion */ +#define AD7949_CFG_MASK_INX GENMASK(9, 7) + +/* BW: select bandwidth for low-pass filter. Full or Quarter */ +#define AD7949_CFG_MASK_BW_FULL BIT(6) + +/* REF: reference/buffer selection */ +#define AD7949_CFG_MASK_REF GENMASK(5, 3) +#define AD7949_CFG_VAL_REF_EXT_TEMP_BUF 3 +#define AD7949_CFG_VAL_REF_EXT_TEMP 2 +#define AD7949_CFG_VAL_REF_INT_4096 1 +#define AD7949_CFG_VAL_REF_INT_2500 0 +#define AD7949_CFG_VAL_REF_EXTERNAL BIT(1) + +/* SEQ: channel sequencer. Allows for scanning channels */ +#define AD7949_CFG_MASK_SEQ GENMASK(2, 1) + +/* RB: Read back the CFG register */ +#define AD7949_CFG_MASK_RBN BIT(0) + +enum { + ID_AD7949 = 0, + ID_AD7682, + ID_AD7689, +}; + +struct ad7949_adc_spec { + u8 num_channels; + u8 resolution; +}; + +static const struct ad7949_adc_spec ad7949_adc_spec[] = { + [ID_AD7949] = { .num_channels = 8, .resolution = 14 }, + [ID_AD7682] = { .num_channels = 4, .resolution = 16 }, + [ID_AD7689] = { .num_channels = 8, .resolution = 16 }, +}; + +/** + * struct ad7949_adc_chip - AD ADC chip + * @lock: protects write sequences + * @vref: regulator generating Vref + * @indio_dev: reference to iio structure + * @spi: reference to spi structure + * @refsel: reference selection + * @resolution: resolution of the chip + * @cfg: copy of the configuration register + * @current_channel: current channel in use + * @buffer: buffer to send / receive data to / from device + * @buf8b: be16 buffer to exchange data with the device in 8-bit transfers + */ +struct ad7949_adc_chip { + struct mutex lock; + struct regulator *vref; + struct iio_dev *indio_dev; + struct spi_device *spi; + u32 refsel; + u8 resolution; + u16 cfg; + unsigned int current_channel; + u16 buffer __aligned(IIO_DMA_MINALIGN); + __be16 buf8b; +}; + +static int ad7949_spi_write_cfg(struct ad7949_adc_chip *ad7949_adc, u16 val, + u16 mask) +{ + int ret; + + ad7949_adc->cfg = (val & mask) | (ad7949_adc->cfg & ~mask); + + switch (ad7949_adc->spi->bits_per_word) { + case 16: + ad7949_adc->buffer = ad7949_adc->cfg << 2; + ret = spi_write(ad7949_adc->spi, &ad7949_adc->buffer, 2); + break; + case 14: + ad7949_adc->buffer = ad7949_adc->cfg; + ret = spi_write(ad7949_adc->spi, &ad7949_adc->buffer, 2); + break; + case 8: + /* Here, type is big endian as it must be sent in two transfers */ + ad7949_adc->buf8b = cpu_to_be16(ad7949_adc->cfg << 2); + ret = spi_write(ad7949_adc->spi, &ad7949_adc->buf8b, 2); + break; + default: + dev_err(&ad7949_adc->indio_dev->dev, "unsupported BPW\n"); + return -EINVAL; + } + + /* + * This delay is to avoid a new request before the required time to + * send a new command to the device + */ + udelay(2); + return ret; +} + +static int ad7949_spi_read_channel(struct ad7949_adc_chip *ad7949_adc, int *val, + unsigned int channel) +{ + int ret; + int i; + + /* + * 1: write CFG for sample N and read old data (sample N-2) + * 2: if CFG was not changed since sample N-1 then we'll get good data + * at the next xfer, so we bail out now, otherwise we write something + * and we read garbage (sample N-1 configuration). + */ + for (i = 0; i < 2; i++) { + ret = ad7949_spi_write_cfg(ad7949_adc, + FIELD_PREP(AD7949_CFG_MASK_INX, channel), + AD7949_CFG_MASK_INX); + if (ret) + return ret; + if (channel == ad7949_adc->current_channel) + break; + } + + /* 3: write something and read actual data */ + if (ad7949_adc->spi->bits_per_word == 8) + ret = spi_read(ad7949_adc->spi, &ad7949_adc->buf8b, 2); + else + ret = spi_read(ad7949_adc->spi, &ad7949_adc->buffer, 2); + + if (ret) + return ret; + + /* + * This delay is to avoid a new request before the required time to + * send a new command to the device + */ + udelay(2); + + ad7949_adc->current_channel = channel; + + switch (ad7949_adc->spi->bits_per_word) { + case 16: + *val = ad7949_adc->buffer; + /* Shift-out padding bits */ + *val >>= 16 - ad7949_adc->resolution; + break; + case 14: + *val = ad7949_adc->buffer & GENMASK(13, 0); + break; + case 8: + /* Here, type is big endian as data was sent in two transfers */ + *val = be16_to_cpu(ad7949_adc->buf8b); + /* Shift-out padding bits */ + *val >>= 16 - ad7949_adc->resolution; + break; + default: + dev_err(&ad7949_adc->indio_dev->dev, "unsupported BPW\n"); + return -EINVAL; + } + + return 0; +} + +#define AD7949_ADC_CHANNEL(chan) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec ad7949_adc_channels[] = { + AD7949_ADC_CHANNEL(0), + AD7949_ADC_CHANNEL(1), + AD7949_ADC_CHANNEL(2), + AD7949_ADC_CHANNEL(3), + AD7949_ADC_CHANNEL(4), + AD7949_ADC_CHANNEL(5), + AD7949_ADC_CHANNEL(6), + AD7949_ADC_CHANNEL(7), +}; + +static int ad7949_spi_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ad7949_adc_chip *ad7949_adc = iio_priv(indio_dev); + int ret; + + if (!val) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&ad7949_adc->lock); + ret = ad7949_spi_read_channel(ad7949_adc, val, chan->channel); + mutex_unlock(&ad7949_adc->lock); + + if (ret < 0) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + switch (ad7949_adc->refsel) { + case AD7949_CFG_VAL_REF_INT_2500: + *val = 2500; + break; + case AD7949_CFG_VAL_REF_INT_4096: + *val = 4096; + break; + case AD7949_CFG_VAL_REF_EXT_TEMP: + case AD7949_CFG_VAL_REF_EXT_TEMP_BUF: + ret = regulator_get_voltage(ad7949_adc->vref); + if (ret < 0) + return ret; + + /* convert value back to mV */ + *val = ret / 1000; + break; + } + + *val2 = (1 << ad7949_adc->resolution) - 1; + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; +} + +static int ad7949_spi_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + struct ad7949_adc_chip *ad7949_adc = iio_priv(indio_dev); + int ret = 0; + + if (readval) + *readval = ad7949_adc->cfg; + else + ret = ad7949_spi_write_cfg(ad7949_adc, writeval, + AD7949_CFG_MASK_TOTAL); + + return ret; +} + +static const struct iio_info ad7949_spi_info = { + .read_raw = ad7949_spi_read_raw, + .debugfs_reg_access = ad7949_spi_reg_access, +}; + +static int ad7949_spi_init(struct ad7949_adc_chip *ad7949_adc) +{ + int ret; + int val; + u16 cfg; + + ad7949_adc->current_channel = 0; + + cfg = FIELD_PREP(AD7949_CFG_MASK_OVERWRITE, 1) | + FIELD_PREP(AD7949_CFG_MASK_INCC, AD7949_CFG_VAL_INCC_UNIPOLAR_GND) | + FIELD_PREP(AD7949_CFG_MASK_INX, ad7949_adc->current_channel) | + FIELD_PREP(AD7949_CFG_MASK_BW_FULL, 1) | + FIELD_PREP(AD7949_CFG_MASK_REF, ad7949_adc->refsel) | + FIELD_PREP(AD7949_CFG_MASK_SEQ, 0x0) | + FIELD_PREP(AD7949_CFG_MASK_RBN, 1); + + ret = ad7949_spi_write_cfg(ad7949_adc, cfg, AD7949_CFG_MASK_TOTAL); + + /* + * Do two dummy conversions to apply the first configuration setting. + * Required only after the start up of the device. + */ + ad7949_spi_read_channel(ad7949_adc, &val, ad7949_adc->current_channel); + ad7949_spi_read_channel(ad7949_adc, &val, ad7949_adc->current_channel); + + return ret; +} + +static void ad7949_disable_reg(void *reg) +{ + regulator_disable(reg); +} + +static int ad7949_spi_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + const struct ad7949_adc_spec *spec; + struct ad7949_adc_chip *ad7949_adc; + struct iio_dev *indio_dev; + u32 tmp; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*ad7949_adc)); + if (!indio_dev) + return -ENOMEM; + + indio_dev->info = &ad7949_spi_info; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad7949_adc_channels; + spi_set_drvdata(spi, indio_dev); + + ad7949_adc = iio_priv(indio_dev); + ad7949_adc->indio_dev = indio_dev; + ad7949_adc->spi = spi; + + spec = &ad7949_adc_spec[spi_get_device_id(spi)->driver_data]; + indio_dev->num_channels = spec->num_channels; + ad7949_adc->resolution = spec->resolution; + + /* Set SPI bits per word */ + if (spi_is_bpw_supported(spi, ad7949_adc->resolution)) { + spi->bits_per_word = ad7949_adc->resolution; + } else if (spi_is_bpw_supported(spi, 16)) { + spi->bits_per_word = 16; + } else if (spi_is_bpw_supported(spi, 8)) { + spi->bits_per_word = 8; + } else { + return dev_err_probe(dev, -EINVAL, + "unable to find common BPW with spi controller\n"); + } + + /* Setup internal voltage reference */ + tmp = 4096000; + device_property_read_u32(dev, "adi,internal-ref-microvolt", &tmp); + + switch (tmp) { + case 2500000: + ad7949_adc->refsel = AD7949_CFG_VAL_REF_INT_2500; + break; + case 4096000: + ad7949_adc->refsel = AD7949_CFG_VAL_REF_INT_4096; + break; + default: + return dev_err_probe(dev, -EINVAL, + "unsupported internal voltage reference\n"); + } + + /* Setup external voltage reference, buffered? */ + ad7949_adc->vref = devm_regulator_get_optional(dev, "vrefin"); + if (IS_ERR(ad7949_adc->vref)) { + ret = PTR_ERR(ad7949_adc->vref); + if (ret != -ENODEV) + return ret; + /* unbuffered? */ + ad7949_adc->vref = devm_regulator_get_optional(dev, "vref"); + if (IS_ERR(ad7949_adc->vref)) { + ret = PTR_ERR(ad7949_adc->vref); + if (ret != -ENODEV) + return ret; + } else { + ad7949_adc->refsel = AD7949_CFG_VAL_REF_EXT_TEMP; + } + } else { + ad7949_adc->refsel = AD7949_CFG_VAL_REF_EXT_TEMP_BUF; + } + + if (ad7949_adc->refsel & AD7949_CFG_VAL_REF_EXTERNAL) { + ret = regulator_enable(ad7949_adc->vref); + if (ret < 0) + return dev_err_probe(dev, ret, + "fail to enable regulator\n"); + + ret = devm_add_action_or_reset(dev, ad7949_disable_reg, + ad7949_adc->vref); + if (ret) + return ret; + } + + mutex_init(&ad7949_adc->lock); + + ret = ad7949_spi_init(ad7949_adc); + if (ret) + return dev_err_probe(dev, ret, "fail to init this device\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ad7949_spi_of_id[] = { + { .compatible = "adi,ad7949" }, + { .compatible = "adi,ad7682" }, + { .compatible = "adi,ad7689" }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7949_spi_of_id); + +static const struct spi_device_id ad7949_spi_id[] = { + { .name = "ad7949", .driver_data = ID_AD7949 }, + { .name = "ad7682", .driver_data = ID_AD7682 }, + { .name = "ad7689", .driver_data = ID_AD7689 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7949_spi_id); + +static struct spi_driver ad7949_spi_driver = { + .driver = { + .name = "ad7949", + .of_match_table = ad7949_spi_of_id, + }, + .probe = ad7949_spi_probe, + .id_table = ad7949_spi_id, +}; +module_spi_driver(ad7949_spi_driver); + +MODULE_AUTHOR("Charles-Antoine Couret "); +MODULE_DESCRIPTION("Analog Devices 14/16-bit 8-channel ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ad799x.c b/drivers/iio/adc/ad799x.c new file mode 100644 index 000000000..bb076d8f5 --- /dev/null +++ b/drivers/iio/adc/ad799x.c @@ -0,0 +1,968 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * iio/adc/ad799x.c + * Copyright (C) 2010-2011 Michael Hennerich, Analog Devices Inc. + * + * based on iio/adc/max1363 + * Copyright (C) 2008-2010 Jonathan Cameron + * + * based on linux/drivers/i2c/chips/max123x + * Copyright (C) 2002-2004 Stefan Eletzhofer + * + * based on linux/drivers/acron/char/pcf8583.c + * Copyright (C) 2000 Russell King + * + * ad799x.c + * + * Support for ad7991, ad7995, ad7999, ad7992, ad7993, ad7994, ad7997, + * ad7998 and similar chips. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define AD799X_CHANNEL_SHIFT 4 +#define AD799X_MAX_CHANNELS 8 + +/* + * AD7991, AD7995 and AD7999 defines + */ + +#define AD7991_REF_SEL 0x08 +#define AD7991_FLTR 0x04 +#define AD7991_BIT_TRIAL_DELAY 0x02 +#define AD7991_SAMPLE_DELAY 0x01 + +/* + * AD7992, AD7993, AD7994, AD7997 and AD7998 defines + */ + +#define AD7998_FLTR BIT(3) +#define AD7998_ALERT_EN BIT(2) +#define AD7998_BUSY_ALERT BIT(1) +#define AD7998_BUSY_ALERT_POL BIT(0) + +#define AD7998_CONV_RES_REG 0x0 +#define AD7998_ALERT_STAT_REG 0x1 +#define AD7998_CONF_REG 0x2 +#define AD7998_CYCLE_TMR_REG 0x3 + +#define AD7998_DATALOW_REG(x) ((x) * 3 + 0x4) +#define AD7998_DATAHIGH_REG(x) ((x) * 3 + 0x5) +#define AD7998_HYST_REG(x) ((x) * 3 + 0x6) + +#define AD7998_CYC_MASK GENMASK(2, 0) +#define AD7998_CYC_DIS 0x0 +#define AD7998_CYC_TCONF_32 0x1 +#define AD7998_CYC_TCONF_64 0x2 +#define AD7998_CYC_TCONF_128 0x3 +#define AD7998_CYC_TCONF_256 0x4 +#define AD7998_CYC_TCONF_512 0x5 +#define AD7998_CYC_TCONF_1024 0x6 +#define AD7998_CYC_TCONF_2048 0x7 + +#define AD7998_ALERT_STAT_CLEAR 0xFF + +/* + * AD7997 and AD7997 defines + */ + +#define AD7997_8_READ_SINGLE BIT(7) +#define AD7997_8_READ_SEQUENCE (BIT(6) | BIT(5) | BIT(4)) + +enum { + ad7991, + ad7995, + ad7999, + ad7992, + ad7993, + ad7994, + ad7997, + ad7998 +}; + +/** + * struct ad799x_chip_config - chip specific information + * @channel: channel specification + * @default_config: device default configuration + * @info: pointer to iio_info struct + */ +struct ad799x_chip_config { + const struct iio_chan_spec channel[9]; + u16 default_config; + const struct iio_info *info; +}; + +/** + * struct ad799x_chip_info - chip specific information + * @num_channels: number of channels + * @noirq_config: device configuration w/o IRQ + * @irq_config: device configuration w/IRQ + * @has_vref: device supports external reference voltage + */ +struct ad799x_chip_info { + int num_channels; + const struct ad799x_chip_config noirq_config; + const struct ad799x_chip_config irq_config; + bool has_vref; +}; + +struct ad799x_state { + struct i2c_client *client; + const struct ad799x_chip_config *chip_config; + struct regulator *reg; + struct regulator *vref; + /* lock to protect against multiple access to the device */ + struct mutex lock; + unsigned int id; + u16 config; + + unsigned int transfer_size; + + int vref_uV; + + IIO_DECLARE_BUFFER_WITH_TS(__be16, rx_buf, AD799X_MAX_CHANNELS); +}; + +static int ad799x_write_config(struct ad799x_state *st, u16 val) +{ + switch (st->id) { + case ad7997: + case ad7998: + return i2c_smbus_write_word_swapped(st->client, AD7998_CONF_REG, + val); + case ad7992: + case ad7993: + case ad7994: + return i2c_smbus_write_byte_data(st->client, AD7998_CONF_REG, + val); + default: + /* Will be written when doing a conversion */ + st->config = val; + return 0; + } +} + +static int ad799x_read_config(struct ad799x_state *st) +{ + switch (st->id) { + case ad7997: + case ad7998: + return i2c_smbus_read_word_swapped(st->client, AD7998_CONF_REG); + case ad7992: + case ad7993: + case ad7994: + return i2c_smbus_read_byte_data(st->client, AD7998_CONF_REG); + default: + /* No readback support */ + return st->config; + } +} + +static int ad799x_update_config(struct ad799x_state *st, u16 config) +{ + int ret; + + ret = ad799x_write_config(st, config); + if (ret < 0) + return ret; + ret = ad799x_read_config(st); + if (ret < 0) + return ret; + st->config = ret; + + return 0; +} + +static irqreturn_t ad799x_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad799x_state *st = iio_priv(indio_dev); + int b_sent; + u8 cmd; + + switch (st->id) { + case ad7991: + case ad7995: + case ad7999: + cmd = st->config | + (*indio_dev->active_scan_mask << AD799X_CHANNEL_SHIFT); + break; + case ad7992: + case ad7993: + case ad7994: + cmd = (*indio_dev->active_scan_mask << AD799X_CHANNEL_SHIFT) | + AD7998_CONV_RES_REG; + break; + case ad7997: + case ad7998: + cmd = AD7997_8_READ_SEQUENCE | AD7998_CONV_RES_REG; + break; + default: + cmd = 0; + } + + b_sent = i2c_smbus_read_i2c_block_data(st->client, + cmd, st->transfer_size, + (u8 *)st->rx_buf); + if (b_sent < 0) + goto out; + + iio_push_to_buffers_with_timestamp(indio_dev, st->rx_buf, + iio_get_time_ns(indio_dev)); +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ad799x_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ad799x_state *st = iio_priv(indio_dev); + + st->transfer_size = bitmap_weight(scan_mask, + iio_get_masklength(indio_dev)) * 2; + + switch (st->id) { + case ad7992: + case ad7993: + case ad7994: + case ad7997: + case ad7998: + st->config &= ~(GENMASK(7, 0) << AD799X_CHANNEL_SHIFT); + st->config |= (*scan_mask << AD799X_CHANNEL_SHIFT); + return ad799x_write_config(st, st->config); + default: + return 0; + } +} + +static int ad799x_scan_direct(struct ad799x_state *st, unsigned int ch) +{ + u8 cmd; + + switch (st->id) { + case ad7991: + case ad7995: + case ad7999: + cmd = st->config | (BIT(ch) << AD799X_CHANNEL_SHIFT); + break; + case ad7992: + case ad7993: + case ad7994: + cmd = BIT(ch) << AD799X_CHANNEL_SHIFT; + break; + case ad7997: + case ad7998: + cmd = (ch << AD799X_CHANNEL_SHIFT) | AD7997_8_READ_SINGLE; + break; + default: + return -EINVAL; + } + + return i2c_smbus_read_word_swapped(st->client, cmd); +} + +static int ad799x_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret; + struct ad799x_state *st = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + mutex_lock(&st->lock); + ret = ad799x_scan_direct(st, chan->scan_index); + mutex_unlock(&st->lock); + iio_device_release_direct(indio_dev); + + if (ret < 0) + return ret; + *val = (ret >> chan->scan_type.shift) & + GENMASK(chan->scan_type.realbits - 1, 0); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = st->vref_uV / (MICRO / MILLI); + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} +static const unsigned int ad7998_frequencies[] = { + [AD7998_CYC_DIS] = 0, + [AD7998_CYC_TCONF_32] = 15625, + [AD7998_CYC_TCONF_64] = 7812, + [AD7998_CYC_TCONF_128] = 3906, + [AD7998_CYC_TCONF_512] = 976, + [AD7998_CYC_TCONF_1024] = 488, + [AD7998_CYC_TCONF_2048] = 244, +}; + +static ssize_t ad799x_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad799x_state *st = iio_priv(indio_dev); + + int ret = i2c_smbus_read_byte_data(st->client, AD7998_CYCLE_TMR_REG); + + if (ret < 0) + return ret; + + return sprintf(buf, "%u\n", ad7998_frequencies[ret & AD7998_CYC_MASK]); +} + +static ssize_t ad799x_write_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad799x_state *st = iio_priv(indio_dev); + + long val; + int ret, i; + + ret = kstrtol(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&st->lock); + + ret = i2c_smbus_read_byte_data(st->client, AD7998_CYCLE_TMR_REG); + if (ret < 0) + goto error_ret_mutex; + /* Wipe the bits clean */ + ret &= ~AD7998_CYC_MASK; + + for (i = 0; i < ARRAY_SIZE(ad7998_frequencies); i++) + if (val == ad7998_frequencies[i]) + break; + if (i == ARRAY_SIZE(ad7998_frequencies)) { + ret = -EINVAL; + goto error_ret_mutex; + } + + ret = i2c_smbus_write_byte_data(st->client, AD7998_CYCLE_TMR_REG, + ret | i); + if (ret < 0) + goto error_ret_mutex; + ret = len; + +error_ret_mutex: + mutex_unlock(&st->lock); + + return ret; +} + +static int ad799x_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct ad799x_state *st = iio_priv(indio_dev); + + if (!(st->config & AD7998_ALERT_EN)) + return 0; + + if ((st->config >> AD799X_CHANNEL_SHIFT) & BIT(chan->scan_index)) + return 1; + + return 0; +} + +static int ad799x_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct ad799x_state *st = iio_priv(indio_dev); + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + mutex_lock(&st->lock); + + if (state) + st->config |= BIT(chan->scan_index) << AD799X_CHANNEL_SHIFT; + else + st->config &= ~(BIT(chan->scan_index) << AD799X_CHANNEL_SHIFT); + + if (st->config >> AD799X_CHANNEL_SHIFT) + st->config |= AD7998_ALERT_EN; + else + st->config &= ~AD7998_ALERT_EN; + + ret = ad799x_write_config(st, st->config); + mutex_unlock(&st->lock); + iio_device_release_direct(indio_dev); + return ret; +} + +static unsigned int ad799x_threshold_reg(const struct iio_chan_spec *chan, + enum iio_event_direction dir, + enum iio_event_info info) +{ + switch (info) { + case IIO_EV_INFO_VALUE: + if (dir == IIO_EV_DIR_FALLING) + return AD7998_DATALOW_REG(chan->channel); + else + return AD7998_DATAHIGH_REG(chan->channel); + case IIO_EV_INFO_HYSTERESIS: + return AD7998_HYST_REG(chan->channel); + default: + return -EINVAL; + } + + return 0; +} + +static int ad799x_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + int ret; + struct ad799x_state *st = iio_priv(indio_dev); + + if (val < 0 || val > GENMASK(chan->scan_type.realbits - 1, 0)) + return -EINVAL; + + ret = i2c_smbus_write_word_swapped(st->client, + ad799x_threshold_reg(chan, dir, info), + val << chan->scan_type.shift); + + return ret; +} + +static int ad799x_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + int ret; + struct ad799x_state *st = iio_priv(indio_dev); + + ret = i2c_smbus_read_word_swapped(st->client, + ad799x_threshold_reg(chan, dir, info)); + if (ret < 0) + return ret; + *val = (ret >> chan->scan_type.shift) & + GENMASK(chan->scan_type.realbits - 1, 0); + + return IIO_VAL_INT; +} + +static irqreturn_t ad799x_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ad799x_state *st = iio_priv(private); + int i, ret; + + ret = i2c_smbus_read_byte_data(st->client, AD7998_ALERT_STAT_REG); + if (ret <= 0) + goto done; + + if (i2c_smbus_write_byte_data(st->client, AD7998_ALERT_STAT_REG, + AD7998_ALERT_STAT_CLEAR) < 0) + goto done; + + for (i = 0; i < 8; i++) { + if (ret & BIT(i)) + iio_push_event(indio_dev, + i & 0x1 ? + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + (i >> 1), + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING) : + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + (i >> 1), + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns(indio_dev)); + } + +done: + return IRQ_HANDLED; +} + +static IIO_DEV_ATTR_SAMP_FREQ(0644, + ad799x_read_frequency, + ad799x_write_frequency); +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("15625 7812 3906 1953 976 488 244 0"); + +static struct attribute *ad799x_event_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad799x_event_attrs_group = { + .attrs = ad799x_event_attributes, +}; + +static const struct iio_info ad7991_info = { + .read_raw = &ad799x_read_raw, + .update_scan_mode = ad799x_update_scan_mode, +}; + +static const struct iio_info ad7993_4_7_8_noirq_info = { + .read_raw = &ad799x_read_raw, + .update_scan_mode = ad799x_update_scan_mode, +}; + +static const struct iio_info ad7993_4_7_8_irq_info = { + .read_raw = &ad799x_read_raw, + .event_attrs = &ad799x_event_attrs_group, + .read_event_config = &ad799x_read_event_config, + .write_event_config = &ad799x_write_event_config, + .read_event_value = &ad799x_read_event_value, + .write_event_value = &ad799x_write_event_value, + .update_scan_mode = ad799x_update_scan_mode, +}; + +static const struct iio_event_spec ad799x_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_HYSTERESIS), + }, +}; + +#define _AD799X_CHANNEL(_index, _realbits, _ev_spec, _num_ev_spec) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = (_index), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (_realbits), \ + .storagebits = 16, \ + .shift = 12 - (_realbits), \ + .endianness = IIO_BE, \ + }, \ + .event_spec = _ev_spec, \ + .num_event_specs = _num_ev_spec, \ +} + +#define AD799X_CHANNEL(_index, _realbits) \ + _AD799X_CHANNEL(_index, _realbits, NULL, 0) + +#define AD799X_CHANNEL_WITH_EVENTS(_index, _realbits) \ + _AD799X_CHANNEL(_index, _realbits, ad799x_events, \ + ARRAY_SIZE(ad799x_events)) + +static const struct ad799x_chip_info ad799x_chip_info_tbl[] = { + [ad7991] = { + .num_channels = 5, + .has_vref = true, + .noirq_config = { + .channel = { + AD799X_CHANNEL(0, 12), + AD799X_CHANNEL(1, 12), + AD799X_CHANNEL(2, 12), + AD799X_CHANNEL(3, 12), + IIO_CHAN_SOFT_TIMESTAMP(4), + }, + .info = &ad7991_info, + }, + }, + [ad7995] = { + .num_channels = 5, + .has_vref = true, + .noirq_config = { + .channel = { + AD799X_CHANNEL(0, 10), + AD799X_CHANNEL(1, 10), + AD799X_CHANNEL(2, 10), + AD799X_CHANNEL(3, 10), + IIO_CHAN_SOFT_TIMESTAMP(4), + }, + .info = &ad7991_info, + }, + }, + [ad7999] = { + .num_channels = 5, + .has_vref = true, + .noirq_config = { + .channel = { + AD799X_CHANNEL(0, 8), + AD799X_CHANNEL(1, 8), + AD799X_CHANNEL(2, 8), + AD799X_CHANNEL(3, 8), + IIO_CHAN_SOFT_TIMESTAMP(4), + }, + .info = &ad7991_info, + }, + }, + [ad7992] = { + .num_channels = 3, + .noirq_config = { + .channel = { + AD799X_CHANNEL(0, 12), + AD799X_CHANNEL(1, 12), + IIO_CHAN_SOFT_TIMESTAMP(3), + }, + .info = &ad7993_4_7_8_noirq_info, + }, + .irq_config = { + .channel = { + AD799X_CHANNEL_WITH_EVENTS(0, 12), + AD799X_CHANNEL_WITH_EVENTS(1, 12), + IIO_CHAN_SOFT_TIMESTAMP(3), + }, + .default_config = AD7998_ALERT_EN | AD7998_BUSY_ALERT, + .info = &ad7993_4_7_8_irq_info, + }, + }, + [ad7993] = { + .num_channels = 5, + .noirq_config = { + .channel = { + AD799X_CHANNEL(0, 10), + AD799X_CHANNEL(1, 10), + AD799X_CHANNEL(2, 10), + AD799X_CHANNEL(3, 10), + IIO_CHAN_SOFT_TIMESTAMP(4), + }, + .info = &ad7993_4_7_8_noirq_info, + }, + .irq_config = { + .channel = { + AD799X_CHANNEL_WITH_EVENTS(0, 10), + AD799X_CHANNEL_WITH_EVENTS(1, 10), + AD799X_CHANNEL_WITH_EVENTS(2, 10), + AD799X_CHANNEL_WITH_EVENTS(3, 10), + IIO_CHAN_SOFT_TIMESTAMP(4), + }, + .default_config = AD7998_ALERT_EN | AD7998_BUSY_ALERT, + .info = &ad7993_4_7_8_irq_info, + }, + }, + [ad7994] = { + .num_channels = 5, + .has_vref = true, + .noirq_config = { + .channel = { + AD799X_CHANNEL(0, 12), + AD799X_CHANNEL(1, 12), + AD799X_CHANNEL(2, 12), + AD799X_CHANNEL(3, 12), + IIO_CHAN_SOFT_TIMESTAMP(4), + }, + .info = &ad7993_4_7_8_noirq_info, + }, + .irq_config = { + .channel = { + AD799X_CHANNEL_WITH_EVENTS(0, 12), + AD799X_CHANNEL_WITH_EVENTS(1, 12), + AD799X_CHANNEL_WITH_EVENTS(2, 12), + AD799X_CHANNEL_WITH_EVENTS(3, 12), + IIO_CHAN_SOFT_TIMESTAMP(4), + }, + .default_config = AD7998_ALERT_EN | AD7998_BUSY_ALERT, + .info = &ad7993_4_7_8_irq_info, + }, + }, + [ad7997] = { + .num_channels = 9, + .noirq_config = { + .channel = { + AD799X_CHANNEL(0, 10), + AD799X_CHANNEL(1, 10), + AD799X_CHANNEL(2, 10), + AD799X_CHANNEL(3, 10), + AD799X_CHANNEL(4, 10), + AD799X_CHANNEL(5, 10), + AD799X_CHANNEL(6, 10), + AD799X_CHANNEL(7, 10), + IIO_CHAN_SOFT_TIMESTAMP(8), + }, + .info = &ad7993_4_7_8_noirq_info, + }, + .irq_config = { + .channel = { + AD799X_CHANNEL_WITH_EVENTS(0, 10), + AD799X_CHANNEL_WITH_EVENTS(1, 10), + AD799X_CHANNEL_WITH_EVENTS(2, 10), + AD799X_CHANNEL_WITH_EVENTS(3, 10), + AD799X_CHANNEL(4, 10), + AD799X_CHANNEL(5, 10), + AD799X_CHANNEL(6, 10), + AD799X_CHANNEL(7, 10), + IIO_CHAN_SOFT_TIMESTAMP(8), + }, + .default_config = AD7998_ALERT_EN | AD7998_BUSY_ALERT, + .info = &ad7993_4_7_8_irq_info, + }, + }, + [ad7998] = { + .num_channels = 9, + .noirq_config = { + .channel = { + AD799X_CHANNEL(0, 12), + AD799X_CHANNEL(1, 12), + AD799X_CHANNEL(2, 12), + AD799X_CHANNEL(3, 12), + AD799X_CHANNEL(4, 12), + AD799X_CHANNEL(5, 12), + AD799X_CHANNEL(6, 12), + AD799X_CHANNEL(7, 12), + IIO_CHAN_SOFT_TIMESTAMP(8), + }, + .info = &ad7993_4_7_8_noirq_info, + }, + .irq_config = { + .channel = { + AD799X_CHANNEL_WITH_EVENTS(0, 12), + AD799X_CHANNEL_WITH_EVENTS(1, 12), + AD799X_CHANNEL_WITH_EVENTS(2, 12), + AD799X_CHANNEL_WITH_EVENTS(3, 12), + AD799X_CHANNEL(4, 12), + AD799X_CHANNEL(5, 12), + AD799X_CHANNEL(6, 12), + AD799X_CHANNEL(7, 12), + IIO_CHAN_SOFT_TIMESTAMP(8), + }, + .default_config = AD7998_ALERT_EN | AD7998_BUSY_ALERT, + .info = &ad7993_4_7_8_irq_info, + }, + }, +}; + +static void ad799x_reg_disable(void *reg) +{ + regulator_disable(reg); +} + +static int ad799x_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + const struct i2c_device_id *id = i2c_client_get_device_id(client); + int ret; + int extra_config = 0; + struct ad799x_state *st; + struct iio_dev *indio_dev; + const struct ad799x_chip_info *chip_info = + &ad799x_chip_info_tbl[id->driver_data]; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + /* this is only used for device removal purposes */ + i2c_set_clientdata(client, indio_dev); + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + st->id = id->driver_data; + if (client->irq > 0 && chip_info->irq_config.info) + st->chip_config = &chip_info->irq_config; + else + st->chip_config = &chip_info->noirq_config; + + /* TODO: Add pdata options for filtering and bit delay */ + + st->reg = devm_regulator_get(dev, "vcc"); + if (IS_ERR(st->reg)) + return PTR_ERR(st->reg); + ret = regulator_enable(st->reg); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, ad799x_reg_disable, st->reg); + if (ret) + return ret; + + /* check if an external reference is supplied */ + if (chip_info->has_vref) { + st->vref = devm_regulator_get_optional(dev, "vref"); + ret = PTR_ERR_OR_ZERO(st->vref); + if (ret == -ENODEV) { + st->vref = NULL; + dev_info(dev, "Using VCC reference voltage\n"); + } else if (ret) { + return ret; + } + + if (st->vref) { + dev_info(dev, "Using external reference voltage\n"); + extra_config |= AD7991_REF_SEL; + ret = regulator_enable(st->vref); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, ad799x_reg_disable, st->vref); + if (ret) + return ret; + + ret = regulator_get_voltage(st->vref); + if (ret < 0) + return ret; + st->vref_uV = ret; + } + } + + if (!st->vref) { + ret = regulator_get_voltage(st->reg); + if (ret < 0) + return ret; + st->vref_uV = ret; + } + + st->client = client; + + indio_dev->name = id->name; + indio_dev->info = st->chip_config->info; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->chip_config->channel; + indio_dev->num_channels = chip_info->num_channels; + + ret = ad799x_update_config(st, st->chip_config->default_config | extra_config); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + &ad799x_trigger_handler, NULL); + if (ret) + return ret; + + if (client->irq > 0) { + ret = devm_request_threaded_irq(dev, + client->irq, + NULL, + ad799x_event_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + client->name, + indio_dev); + if (ret) + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} + +static int ad799x_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct ad799x_state *st = iio_priv(indio_dev); + + if (st->vref) + regulator_disable(st->vref); + regulator_disable(st->reg); + + return 0; +} + +static int ad799x_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct ad799x_state *st = iio_priv(indio_dev); + int ret; + + ret = regulator_enable(st->reg); + if (ret) { + dev_err(dev, "Unable to enable vcc regulator\n"); + return ret; + } + + if (st->vref) { + ret = regulator_enable(st->vref); + if (ret) { + regulator_disable(st->reg); + dev_err(dev, "Unable to enable vref regulator\n"); + return ret; + } + } + + /* resync config */ + ret = ad799x_update_config(st, st->config); + if (ret) { + if (st->vref) + regulator_disable(st->vref); + regulator_disable(st->reg); + return ret; + } + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ad799x_pm_ops, ad799x_suspend, ad799x_resume); + +static const struct i2c_device_id ad799x_id[] = { + { .name = "ad7991", .driver_data = ad7991 }, + { .name = "ad7995", .driver_data = ad7995 }, + { .name = "ad7999", .driver_data = ad7999 }, + { .name = "ad7992", .driver_data = ad7992 }, + { .name = "ad7993", .driver_data = ad7993 }, + { .name = "ad7994", .driver_data = ad7994 }, + { .name = "ad7997", .driver_data = ad7997 }, + { .name = "ad7998", .driver_data = ad7998 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, ad799x_id); + +static struct i2c_driver ad799x_driver = { + .driver = { + .name = "ad799x", + .pm = pm_sleep_ptr(&ad799x_pm_ops), + }, + .probe = ad799x_probe, + .id_table = ad799x_id, +}; +module_i2c_driver(ad799x_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD799x ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ad9467.c b/drivers/iio/adc/ad9467.c new file mode 100644 index 000000000..34b7b1256 --- /dev/null +++ b/drivers/iio/adc/ad9467.c @@ -0,0 +1,1433 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices AD9467 SPI ADC driver + * + * Copyright 2012-2020 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* + * ADI High-Speed ADC common spi interface registers + * See Application-Note AN-877: + * https://www.analog.com/media/en/technical-documentation/application-notes/AN-877.pdf + */ + +#define AN877_ADC_REG_CHIP_PORT_CONF 0x00 +#define AN877_ADC_REG_CHIP_ID 0x01 +#define AN877_ADC_REG_CHIP_GRADE 0x02 +#define AN877_ADC_REG_CHAN_INDEX 0x05 +#define AN877_ADC_REG_TRANSFER 0xFF +#define AN877_ADC_REG_MODES 0x08 +#define AN877_ADC_REG_TEST_IO 0x0D +#define AN877_ADC_REG_ADC_INPUT 0x0F +#define AN877_ADC_REG_OFFSET 0x10 +#define AN877_ADC_REG_OUTPUT_MODE 0x14 +#define AN877_ADC_REG_OUTPUT_ADJUST 0x15 +#define AN877_ADC_REG_OUTPUT_PHASE 0x16 +#define AN877_ADC_REG_OUTPUT_DELAY 0x17 +#define AN877_ADC_REG_VREF 0x18 +#define AN877_ADC_REG_ANALOG_INPUT 0x2C + +/* AN877_ADC_REG_TEST_IO */ +#define AN877_ADC_TESTMODE_OFF 0x0 +#define AN877_ADC_TESTMODE_MIDSCALE_SHORT 0x1 +#define AN877_ADC_TESTMODE_POS_FULLSCALE 0x2 +#define AN877_ADC_TESTMODE_NEG_FULLSCALE 0x3 +#define AN877_ADC_TESTMODE_ALT_CHECKERBOARD 0x4 +#define AN877_ADC_TESTMODE_PN23_SEQ 0x5 +#define AN877_ADC_TESTMODE_PN9_SEQ 0x6 +#define AN877_ADC_TESTMODE_ONE_ZERO_TOGGLE 0x7 +#define AN877_ADC_TESTMODE_USER 0x8 +#define AN877_ADC_TESTMODE_BIT_TOGGLE 0x9 +#define AN877_ADC_TESTMODE_SYNC 0xA +#define AN877_ADC_TESTMODE_ONE_BIT_HIGH 0xB +#define AN877_ADC_TESTMODE_MIXED_BIT_FREQUENCY 0xC +#define AN877_ADC_TESTMODE_RAMP 0xF + +/* AN877_ADC_REG_TRANSFER */ +#define AN877_ADC_TRANSFER_SYNC 0x1 + +/* AN877_ADC_REG_OUTPUT_MODE */ +#define AN877_ADC_OUTPUT_MODE_OFFSET_BINARY 0x0 +#define AN877_ADC_OUTPUT_MODE_TWOS_COMPLEMENT 0x1 +#define AN877_ADC_OUTPUT_MODE_GRAY_CODE 0x2 +#define AN877_ADC_OUTPUT_MODE_MASK GENMASK(1, 0) + +/* AN877_ADC_REG_OUTPUT_PHASE */ +#define AN877_ADC_OUTPUT_EVEN_ODD_MODE_EN 0x20 +#define AN877_ADC_INVERT_DCO_CLK 0x80 + +/* AN877_ADC_REG_OUTPUT_DELAY */ +#define AN877_ADC_DCO_DELAY_ENABLE 0x80 + +/* + * Analog Devices AD9211 10-Bit, 200/250/300 MSPS ADC + */ + +#define CHIPID_AD9211 0x06 +#define AD9211_DEF_OUTPUT_MODE 0x01 +#define AD9211_REG_VREF_MASK GENMASK(4, 0) + +/* + * Analog Devices AD9265 16-Bit, 125/105/80 MSPS ADC + */ + +#define CHIPID_AD9265 0x64 +#define AD9265_DEF_OUTPUT_MODE 0x41 +#define AD9265_REG_VREF_MASK 0xC0 + +/* + * Analog Devices AD9434 12-Bit, 370/500 MSPS ADC + */ + +#define CHIPID_AD9434 0x6A +#define AD9434_DEF_OUTPUT_MODE 0x01 +#define AD9434_REG_VREF_MASK GENMASK(4, 0) + +/* + * Analog Devices AD9467 16-Bit, 200/250 MSPS ADC + */ + +#define CHIPID_AD9467 0x50 +#define AD9467_DEF_OUTPUT_MODE 0x09 +#define AD9467_REG_VREF_MASK 0x0F + +/* + * Analog Devices AD9643 14-Bit, 170/210/250 MSPS ADC + */ + +#define CHIPID_AD9643 0x82 +#define AD9643_DEF_OUTPUT_MODE 0x01 +#define AD9643_REG_VREF_MASK 0x1F + +/* + * Analog Devices AD9652 16-bit 310 MSPS ADC + */ + +#define CHIPID_AD9652 0xC1 +#define AD9652_DEF_OUTPUT_MODE 0x01 +#define AD9652_REG_VREF_MASK 0xC0 + +/* + * Analog Devices AD9649 14-bit 20/40/65/80 MSPS ADC + */ + +#define CHIPID_AD9649 0x6F +#define AD9649_DEF_OUTPUT_MODE 0x01 +#define AD9649_TEST_POINTS 8 + +#define AD9647_MAX_TEST_POINTS 32 +#define AD9467_CAN_INVERT(st) \ + (!(st)->info->has_dco || (st)->info->has_dco_invert) + +struct ad9467_chip_info { + const char *name; + unsigned int id; + const struct iio_chan_spec *channels; + unsigned int num_channels; + const unsigned int (*scale_table)[2]; + int num_scales; + unsigned long test_mask; + unsigned int test_mask_len; + unsigned long max_rate; + unsigned int default_output_mode; + unsigned int vref_mask; + unsigned int num_lanes; + unsigned int dco_en; + unsigned int test_points; + const int *offset_range; + /* data clock output */ + bool has_dco; + bool has_dco_invert; +}; + +struct ad9467_chan_test_mode { + struct ad9467_state *st; + unsigned int idx; + u8 mode; +}; + +struct ad9467_state { + const struct ad9467_chip_info *info; + struct iio_backend *back; + struct spi_device *spi; + struct clk *clk; + /* used for debugfs */ + struct ad9467_chan_test_mode *chan_test; + unsigned int (*scales)[2]; + /* + * Times 2 because we may also invert the signal polarity and run the + * calibration again. For some reference on the test points (ad9265) see: + * https://www.analog.com/media/en/technical-documentation/data-sheets/ad9265.pdf + * at page 38 for the dco output delay. On devices as ad9467, the + * calibration is done at the backend level. For the ADI axi-adc: + * https://wiki.analog.com/resources/fpga/docs/axi_adc_ip + * at the io delay control section. + */ + DECLARE_BITMAP(calib_map, AD9647_MAX_TEST_POINTS * 2); + /* number of bits of the map */ + unsigned int calib_map_size; + struct gpio_desc *pwrdown_gpio; + /* ensure consistent state obtained on multiple related accesses */ + struct mutex lock; + u8 buf[3] __aligned(IIO_DMA_MINALIGN); +}; + +static int ad9467_spi_read(struct ad9467_state *st, unsigned int reg) +{ + unsigned char tbuf[2], rbuf[1]; + int ret; + + tbuf[0] = 0x80 | (reg >> 8); + tbuf[1] = reg & 0xFF; + + ret = spi_write_then_read(st->spi, + tbuf, ARRAY_SIZE(tbuf), + rbuf, ARRAY_SIZE(rbuf)); + + if (ret < 0) + return ret; + + return rbuf[0]; +} + +static int ad9467_spi_write(struct ad9467_state *st, unsigned int reg, + unsigned int val) +{ + st->buf[0] = reg >> 8; + st->buf[1] = reg & 0xFF; + st->buf[2] = val; + + return spi_write(st->spi, st->buf, ARRAY_SIZE(st->buf)); +} + +static int ad9467_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct ad9467_state *st = iio_priv(indio_dev); + int ret; + + if (!readval) { + guard(mutex)(&st->lock); + ret = ad9467_spi_write(st, reg, writeval); + if (ret) + return ret; + return ad9467_spi_write(st, AN877_ADC_REG_TRANSFER, + AN877_ADC_TRANSFER_SYNC); + } + + ret = ad9467_spi_read(st, reg); + if (ret < 0) + return ret; + *readval = ret; + + return 0; +} + +static const int ad9434_offset_range[] = { + -128, 1, 127, +}; + +static const unsigned int ad9211_scale_table[][2] = { + {980, 0x10}, {1000, 0x11}, {1020, 0x12}, {1040, 0x13}, + {1060, 0x14}, {1080, 0x15}, {1100, 0x16}, {1120, 0x17}, + {1140, 0x18}, {1160, 0x19}, {1180, 0x1A}, {1190, 0x1B}, + {1200, 0x1C}, {1210, 0x1D}, {1220, 0x1E}, {1230, 0x1F}, + {1250, 0x0}, {1270, 0x1}, {1290, 0x2}, {1310, 0x3}, + {1330, 0x4}, {1350, 0x5}, {1370, 0x6}, {1390, 0x7}, + {1410, 0x8}, {1430, 0x9}, {1450, 0xA}, {1460, 0xB}, + {1470, 0xC}, {1480, 0xD}, {1490, 0xE}, {1500, 0xF}, +}; + +static const unsigned int ad9265_scale_table[][2] = { + {1250, 0x00}, {1500, 0x40}, {1750, 0x80}, {2000, 0xC0}, +}; + +static const unsigned int ad9434_scale_table[][2] = { + {1600, 0x1C}, {1580, 0x1D}, {1550, 0x1E}, {1520, 0x1F}, {1500, 0x00}, + {1470, 0x01}, {1440, 0x02}, {1420, 0x03}, {1390, 0x04}, {1360, 0x05}, + {1340, 0x06}, {1310, 0x07}, {1280, 0x08}, {1260, 0x09}, {1230, 0x0A}, + {1200, 0x0B}, {1180, 0x0C}, +}; + +static const unsigned int ad9467_scale_table[][2] = { + {2000, 0}, {2100, 6}, {2200, 7}, + {2300, 8}, {2400, 9}, {2500, 10}, +}; + +static const unsigned int ad9643_scale_table[][2] = { + {2087, 0x0F}, {2065, 0x0E}, {2042, 0x0D}, {2020, 0x0C}, {1997, 0x0B}, + {1975, 0x0A}, {1952, 0x09}, {1930, 0x08}, {1907, 0x07}, {1885, 0x06}, + {1862, 0x05}, {1840, 0x04}, {1817, 0x03}, {1795, 0x02}, {1772, 0x01}, + {1750, 0x00}, {1727, 0x1F}, {1704, 0x1E}, {1681, 0x1D}, {1658, 0x1C}, + {1635, 0x1B}, {1612, 0x1A}, {1589, 0x19}, {1567, 0x18}, {1544, 0x17}, + {1521, 0x16}, {1498, 0x15}, {1475, 0x14}, {1452, 0x13}, {1429, 0x12}, + {1406, 0x11}, {1383, 0x10}, +}; + +static const unsigned int ad9649_scale_table[][2] = { + {2000, 0}, +}; + +static const unsigned int ad9652_scale_table[][2] = { + {1250, 0}, {1125, 1}, {1200, 2}, {1250, 3}, {1000, 5}, +}; + +static void __ad9467_get_scale(struct ad9467_state *st, int index, + unsigned int *val, unsigned int *val2) +{ + const struct ad9467_chip_info *info = st->info; + const struct iio_chan_spec *chan = &info->channels[0]; + unsigned int tmp; + + tmp = (info->scale_table[index][0] * 1000000ULL) >> + chan->scan_type.realbits; + *val = tmp / 1000000; + *val2 = tmp % 1000000; +} + +#define AD9467_CHAN(_chan, avai_mask, _si, _bits, _sign) \ +{ \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _chan, \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available = avai_mask, \ + .scan_index = _si, \ + .scan_type = { \ + .sign = _sign, \ + .realbits = _bits, \ + .storagebits = 16, \ + }, \ +} + +static const struct iio_chan_spec ad9211_channels[] = { + AD9467_CHAN(0, BIT(IIO_CHAN_INFO_SCALE), 0, 10, 's'), +}; + +static const struct iio_chan_spec ad9434_channels[] = { + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .info_mask_shared_by_type = + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_shared_by_type_available = + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 12, + .storagebits = 16, + }, + }, +}; + +static const struct iio_chan_spec ad9467_channels[] = { + AD9467_CHAN(0, BIT(IIO_CHAN_INFO_SCALE), 0, 16, 's'), +}; + +static const struct iio_chan_spec ad9643_channels[] = { + AD9467_CHAN(0, BIT(IIO_CHAN_INFO_SCALE), 0, 14, 's'), + AD9467_CHAN(1, BIT(IIO_CHAN_INFO_SCALE), 1, 14, 's'), +}; + +static const struct iio_chan_spec ad9649_channels[] = { + AD9467_CHAN(0, 0, 0, 14, 's'), +}; + +static const struct iio_chan_spec ad9652_channels[] = { + AD9467_CHAN(0, BIT(IIO_CHAN_INFO_SCALE), 0, 16, 's'), + AD9467_CHAN(1, BIT(IIO_CHAN_INFO_SCALE), 1, 16, 's'), +}; + +static const char * const ad9467_test_modes[] = { + [AN877_ADC_TESTMODE_OFF] = "off", + [AN877_ADC_TESTMODE_MIDSCALE_SHORT] = "midscale_short", + [AN877_ADC_TESTMODE_POS_FULLSCALE] = "pos_fullscale", + [AN877_ADC_TESTMODE_NEG_FULLSCALE] = "neg_fullscale", + [AN877_ADC_TESTMODE_ALT_CHECKERBOARD] = "checkerboard", + [AN877_ADC_TESTMODE_PN23_SEQ] = "prbs23", + [AN877_ADC_TESTMODE_PN9_SEQ] = "prbs9", + [AN877_ADC_TESTMODE_ONE_ZERO_TOGGLE] = "one_zero_toggle", + [AN877_ADC_TESTMODE_USER] = "user", + [AN877_ADC_TESTMODE_BIT_TOGGLE] = "bit_toggle", + [AN877_ADC_TESTMODE_SYNC] = "sync", + [AN877_ADC_TESTMODE_ONE_BIT_HIGH] = "one_bit_high", + [AN877_ADC_TESTMODE_MIXED_BIT_FREQUENCY] = "mixed_bit_frequency", + [AN877_ADC_TESTMODE_RAMP] = "ramp", +}; + +static const struct ad9467_chip_info ad9467_chip_tbl = { + .name = "ad9467", + .id = CHIPID_AD9467, + .max_rate = 250000000UL, + .scale_table = ad9467_scale_table, + .num_scales = ARRAY_SIZE(ad9467_scale_table), + .channels = ad9467_channels, + .num_channels = ARRAY_SIZE(ad9467_channels), + .test_points = AD9647_MAX_TEST_POINTS, + .test_mask = GENMASK(AN877_ADC_TESTMODE_ONE_ZERO_TOGGLE, + AN877_ADC_TESTMODE_OFF), + .test_mask_len = AN877_ADC_TESTMODE_ONE_ZERO_TOGGLE + 1, + .default_output_mode = AD9467_DEF_OUTPUT_MODE, + .vref_mask = AD9467_REG_VREF_MASK, + .num_lanes = 8, +}; + +static const struct ad9467_chip_info ad9434_chip_tbl = { + .name = "ad9434", + .id = CHIPID_AD9434, + .max_rate = 500000000UL, + .scale_table = ad9434_scale_table, + .num_scales = ARRAY_SIZE(ad9434_scale_table), + .channels = ad9434_channels, + .num_channels = ARRAY_SIZE(ad9434_channels), + .test_points = AD9647_MAX_TEST_POINTS, + .test_mask = GENMASK(AN877_ADC_TESTMODE_USER, AN877_ADC_TESTMODE_OFF), + .test_mask_len = AN877_ADC_TESTMODE_USER + 1, + .default_output_mode = AD9434_DEF_OUTPUT_MODE, + .vref_mask = AD9434_REG_VREF_MASK, + .num_lanes = 6, + .offset_range = ad9434_offset_range, +}; + +static const struct ad9467_chip_info ad9211_chip_tbl = { + .name = "ad9211", + .id = CHIPID_AD9211, + .max_rate = 300 * HZ_PER_MHZ, + .scale_table = ad9211_scale_table, + .num_scales = ARRAY_SIZE(ad9211_scale_table), + .channels = ad9211_channels, + .num_channels = ARRAY_SIZE(ad9211_channels), + .test_points = AD9647_MAX_TEST_POINTS, + .test_mask = GENMASK(AN877_ADC_TESTMODE_ONE_ZERO_TOGGLE, + AN877_ADC_TESTMODE_OFF), + .test_mask_len = AN877_ADC_TESTMODE_ONE_ZERO_TOGGLE + 1, + .default_output_mode = AD9211_DEF_OUTPUT_MODE, + .vref_mask = AD9211_REG_VREF_MASK, + .has_dco = true, +}; + +static const struct ad9467_chip_info ad9265_chip_tbl = { + .name = "ad9265", + .id = CHIPID_AD9265, + .max_rate = 125000000UL, + .scale_table = ad9265_scale_table, + .num_scales = ARRAY_SIZE(ad9265_scale_table), + .channels = ad9467_channels, + .num_channels = ARRAY_SIZE(ad9467_channels), + .test_points = AD9647_MAX_TEST_POINTS, + .test_mask = GENMASK(AN877_ADC_TESTMODE_ONE_ZERO_TOGGLE, + AN877_ADC_TESTMODE_OFF), + .test_mask_len = AN877_ADC_TESTMODE_ONE_ZERO_TOGGLE + 1, + .default_output_mode = AD9265_DEF_OUTPUT_MODE, + .vref_mask = AD9265_REG_VREF_MASK, + .has_dco = true, + .has_dco_invert = true, +}; + +static const struct ad9467_chip_info ad9643_chip_tbl = { + .name = "ad9643", + .id = CHIPID_AD9643, + .max_rate = 250000000UL, + .scale_table = ad9643_scale_table, + .num_scales = ARRAY_SIZE(ad9643_scale_table), + .channels = ad9643_channels, + .num_channels = ARRAY_SIZE(ad9643_channels), + .test_points = AD9647_MAX_TEST_POINTS, + .test_mask = BIT(AN877_ADC_TESTMODE_RAMP) | + GENMASK(AN877_ADC_TESTMODE_MIXED_BIT_FREQUENCY, AN877_ADC_TESTMODE_OFF), + .test_mask_len = AN877_ADC_TESTMODE_RAMP + 1, + .default_output_mode = AD9643_DEF_OUTPUT_MODE, + .vref_mask = AD9643_REG_VREF_MASK, + .has_dco = true, + .has_dco_invert = true, + .dco_en = AN877_ADC_DCO_DELAY_ENABLE, +}; + +static const struct ad9467_chip_info ad9649_chip_tbl = { + .name = "ad9649", + .id = CHIPID_AD9649, + .max_rate = 80000000UL, + .scale_table = ad9649_scale_table, + .num_scales = ARRAY_SIZE(ad9649_scale_table), + .channels = ad9649_channels, + .num_channels = ARRAY_SIZE(ad9649_channels), + .test_points = AD9649_TEST_POINTS, + .test_mask = GENMASK(AN877_ADC_TESTMODE_MIXED_BIT_FREQUENCY, + AN877_ADC_TESTMODE_OFF), + .test_mask_len = AN877_ADC_TESTMODE_MIXED_BIT_FREQUENCY + 1, + .default_output_mode = AD9649_DEF_OUTPUT_MODE, + .has_dco = true, + .has_dco_invert = true, + .dco_en = AN877_ADC_DCO_DELAY_ENABLE, +}; + +static const struct ad9467_chip_info ad9652_chip_tbl = { + .name = "ad9652", + .id = CHIPID_AD9652, + .max_rate = 310000000UL, + .scale_table = ad9652_scale_table, + .num_scales = ARRAY_SIZE(ad9652_scale_table), + .channels = ad9652_channels, + .num_channels = ARRAY_SIZE(ad9652_channels), + .test_points = AD9647_MAX_TEST_POINTS, + .test_mask = GENMASK(AN877_ADC_TESTMODE_ONE_ZERO_TOGGLE, + AN877_ADC_TESTMODE_OFF), + .test_mask_len = AN877_ADC_TESTMODE_ONE_ZERO_TOGGLE + 1, + .default_output_mode = AD9652_DEF_OUTPUT_MODE, + .vref_mask = AD9652_REG_VREF_MASK, + .has_dco = true, +}; + +static int ad9467_get_scale(struct ad9467_state *st, int *val, int *val2) +{ + const struct ad9467_chip_info *info = st->info; + unsigned int vref_val; + unsigned int i = 0; + int ret; + + /* nothing to read if we only have one possible scale */ + if (info->num_scales == 1) + goto out_get_scale; + + ret = ad9467_spi_read(st, AN877_ADC_REG_VREF); + if (ret < 0) + return ret; + + vref_val = ret & info->vref_mask; + + for (i = 0; i < info->num_scales; i++) { + if (vref_val == info->scale_table[i][1]) + break; + } + + if (i == info->num_scales) + return -ERANGE; + +out_get_scale: + __ad9467_get_scale(st, i, val, val2); + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int ad9467_set_scale(struct ad9467_state *st, int val, int val2) +{ + const struct ad9467_chip_info *info = st->info; + unsigned int scale_val[2]; + unsigned int i; + int ret; + + if (val != 0) + return -EINVAL; + if (info->num_scales == 1) + return -EOPNOTSUPP; + + for (i = 0; i < info->num_scales; i++) { + __ad9467_get_scale(st, i, &scale_val[0], &scale_val[1]); + if (scale_val[0] != val || scale_val[1] != val2) + continue; + + guard(mutex)(&st->lock); + ret = ad9467_spi_write(st, AN877_ADC_REG_VREF, + info->scale_table[i][1]); + if (ret < 0) + return ret; + + return ad9467_spi_write(st, AN877_ADC_REG_TRANSFER, + AN877_ADC_TRANSFER_SYNC); + } + + return -EINVAL; +} + +static int ad9467_get_offset(struct ad9467_state *st, int *val) +{ + int ret; + + ret = ad9467_spi_read(st, AN877_ADC_REG_OFFSET); + if (ret < 0) + return ret; + *val = ret; + + return IIO_VAL_INT; +} + +static int ad9467_set_offset(struct ad9467_state *st, int val) +{ + int ret; + + if (val < st->info->offset_range[0] || val > st->info->offset_range[2]) + return -EINVAL; + + ret = ad9467_spi_write(st, AN877_ADC_REG_OFFSET, val); + if (ret < 0) + return ret; + + return ad9467_spi_write(st, AN877_ADC_REG_TRANSFER, + AN877_ADC_TRANSFER_SYNC); +} + +static int ad9467_outputmode_set(struct ad9467_state *st, unsigned int mode) +{ + int ret; + + ret = ad9467_spi_write(st, AN877_ADC_REG_OUTPUT_MODE, mode); + if (ret < 0) + return ret; + + return ad9467_spi_write(st, AN877_ADC_REG_TRANSFER, + AN877_ADC_TRANSFER_SYNC); +} + +static int ad9467_testmode_set(struct ad9467_state *st, unsigned int chan, + unsigned int test_mode) +{ + int ret; + + if (st->info->num_channels > 1) { + /* so that the test mode is only applied to one channel */ + ret = ad9467_spi_write(st, AN877_ADC_REG_CHAN_INDEX, BIT(chan)); + if (ret) + return ret; + } + + ret = ad9467_spi_write(st, AN877_ADC_REG_TEST_IO, test_mode); + if (ret) + return ret; + + if (st->info->num_channels > 1) { + /* go to default state where all channels get write commands */ + ret = ad9467_spi_write(st, AN877_ADC_REG_CHAN_INDEX, + GENMASK(st->info->num_channels - 1, 0)); + if (ret) + return ret; + } + + return ad9467_spi_write(st, AN877_ADC_REG_TRANSFER, + AN877_ADC_TRANSFER_SYNC); +} + +static int ad9467_backend_testmode_on(struct ad9467_state *st, + unsigned int chan, + enum iio_backend_test_pattern pattern) +{ + struct iio_backend_data_fmt data = { + .enable = false, + }; + int ret; + + ret = iio_backend_data_format_set(st->back, chan, &data); + if (ret) + return ret; + + ret = iio_backend_test_pattern_set(st->back, chan, pattern); + if (ret) + return ret; + + return iio_backend_chan_enable(st->back, chan); +} + +static int ad9467_backend_testmode_off(struct ad9467_state *st, + unsigned int chan) +{ + struct iio_backend_data_fmt data = { + .enable = true, + .sign_extend = true, + }; + int ret; + + ret = iio_backend_chan_disable(st->back, chan); + if (ret) + return ret; + + ret = iio_backend_test_pattern_set(st->back, chan, + IIO_BACKEND_NO_TEST_PATTERN); + if (ret) + return ret; + + return iio_backend_data_format_set(st->back, chan, &data); +} + +static int ad9647_calibrate_prepare(struct ad9467_state *st) +{ + unsigned int cmode; + unsigned int c; + int ret; + + cmode = st->info->default_output_mode; + FIELD_MODIFY(AN877_ADC_OUTPUT_MODE_MASK, &cmode, + AN877_ADC_OUTPUT_MODE_OFFSET_BINARY); + ret = ad9467_outputmode_set(st, cmode); + if (ret) + return ret; + + for (c = 0; c < st->info->num_channels; c++) { + ret = ad9467_testmode_set(st, c, AN877_ADC_TESTMODE_PN9_SEQ); + if (ret) + return ret; + + ret = ad9467_backend_testmode_on(st, c, + IIO_BACKEND_ADI_PRBS_9A); + if (ret) + return ret; + } + + return 0; +} + +static int ad9647_calibrate_polarity_set(struct ad9467_state *st, + bool invert) +{ + enum iio_backend_sample_trigger trigger; + + if (st->info->has_dco) { + unsigned int phase = AN877_ADC_OUTPUT_EVEN_ODD_MODE_EN; + + if (invert) + phase |= AN877_ADC_INVERT_DCO_CLK; + + return ad9467_spi_write(st, AN877_ADC_REG_OUTPUT_PHASE, + phase); + } + + if (invert) + trigger = IIO_BACKEND_SAMPLE_TRIGGER_EDGE_FALLING; + else + trigger = IIO_BACKEND_SAMPLE_TRIGGER_EDGE_RISING; + + return iio_backend_data_sample_trigger(st->back, trigger); +} + +/* + * The idea is pretty simple. Find the max number of successful points in a row + * and get the one in the middle. + */ +static unsigned int ad9467_find_optimal_point(const unsigned long *calib_map, + unsigned int start, + unsigned int nbits, + unsigned int *val) +{ + unsigned int bit = start, end, start_cnt, cnt = 0; + + for_each_clear_bitrange_from(bit, end, calib_map, nbits + start) { + if (end - bit > cnt) { + cnt = end - bit; + start_cnt = bit; + } + } + + if (cnt) + *val = start_cnt + cnt / 2; + + return cnt; +} + +static int ad9467_calibrate_apply(struct ad9467_state *st, unsigned int val) +{ + unsigned int lane; + int ret; + + if (st->info->has_dco) { + ret = ad9467_spi_write(st, AN877_ADC_REG_OUTPUT_DELAY, + val | st->info->dco_en); + if (ret) + return ret; + + return ad9467_spi_write(st, AN877_ADC_REG_TRANSFER, + AN877_ADC_TRANSFER_SYNC); + } + + for (lane = 0; lane < st->info->num_lanes; lane++) { + ret = iio_backend_iodelay_set(st->back, lane, val); + if (ret) + return ret; + } + + return 0; +} + +static int ad9647_calibrate_stop(struct ad9467_state *st) +{ + unsigned int c, mode; + int ret; + + for (c = 0; c < st->info->num_channels; c++) { + ret = ad9467_backend_testmode_off(st, c); + if (ret) + return ret; + + ret = ad9467_testmode_set(st, c, AN877_ADC_TESTMODE_OFF); + if (ret) + return ret; + } + + mode = st->info->default_output_mode; + return ad9467_outputmode_set(st, mode); +} + +static int ad9467_calibrate(struct ad9467_state *st) +{ + unsigned int point, val, inv_val, cnt, inv_cnt = 0, c; + /* + * Half of the bitmap is for the inverted signal. The number of test + * points is the same though... + */ + unsigned int test_points = st->info->test_points; + unsigned long sample_rate = clk_get_rate(st->clk); + struct device *dev = &st->spi->dev; + bool invert = false, stat; + int ret; + + /* all points invalid */ + bitmap_fill(st->calib_map, st->calib_map_size); + + ret = ad9647_calibrate_prepare(st); + if (ret) + return ret; +retune: + ret = ad9647_calibrate_polarity_set(st, invert); + if (ret) + return ret; + + for (point = 0; point < st->info->test_points; point++) { + ret = ad9467_calibrate_apply(st, point); + if (ret) + return ret; + + for (c = 0; c < st->info->num_channels; c++) { + ret = iio_backend_chan_status(st->back, c, &stat); + if (ret) + return ret; + + /* + * A point is considered valid if all channels report no + * error. If one reports an error, then we consider the + * point as invalid and we can break the loop right away. + */ + if (stat) { + dev_dbg(dev, "Invalid point(%u, inv:%u) for CH:%u\n", + point, invert, c); + break; + } + + if (c == st->info->num_channels - 1) + __clear_bit(point + invert * test_points, + st->calib_map); + } + } + + if (!invert) { + cnt = ad9467_find_optimal_point(st->calib_map, 0, test_points, + &val); + /* + * We're happy if we find, at least, three good test points in + * a row. + */ + if (cnt < 3) { + if (AD9467_CAN_INVERT(st)) { + invert = true; + goto retune; + } + + if (!cnt) + return -EIO; + } + } else { + inv_cnt = ad9467_find_optimal_point(st->calib_map, test_points, + test_points, &inv_val); + if (!inv_cnt && !cnt) + return -EIO; + } + + if (inv_cnt < cnt) { + ret = ad9647_calibrate_polarity_set(st, false); + if (ret) + return ret; + } else { + /* + * polarity inverted is the last test to run. Hence, there's no + * need to re-do any configuration. We just need to "normalize" + * the selected value. + */ + val = inv_val - test_points; + } + + if (st->info->has_dco) + dev_dbg(dev, "%sDCO 0x%X CLK %lu Hz\n", inv_cnt >= cnt ? "INVERT " : "", + val, sample_rate); + else + dev_dbg(dev, "%sIDELAY 0x%x\n", inv_cnt >= cnt ? "INVERT " : "", + val); + + ret = ad9467_calibrate_apply(st, val); + if (ret) + return ret; + + /* finally apply the optimal value */ + return ad9647_calibrate_stop(st); +} + +static int ad9467_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long m) +{ + struct ad9467_state *st = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_CALIBBIAS: + return ad9467_get_offset(st, val); + case IIO_CHAN_INFO_SCALE: + return ad9467_get_scale(st, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + *val = clk_get_rate(st->clk); + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int __ad9467_update_clock(struct ad9467_state *st, long r_clk) +{ + int ret; + + ret = clk_set_rate(st->clk, r_clk); + if (ret) + return ret; + + guard(mutex)(&st->lock); + + if (iio_backend_has_caps(st->back, IIO_BACKEND_CAP_CALIBRATION)) + return ad9467_calibrate(st); + + return 0; +} + +static int ad9467_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ad9467_state *st = iio_priv(indio_dev); + const struct ad9467_chip_info *info = st->info; + unsigned long sample_rate; + long r_clk; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + return ad9467_set_offset(st, val); + case IIO_CHAN_INFO_SCALE: + return ad9467_set_scale(st, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + r_clk = clk_round_rate(st->clk, val); + if (r_clk < 0 || r_clk > info->max_rate) { + dev_warn(&st->spi->dev, + "Error setting ADC sample rate %ld", r_clk); + return -EINVAL; + } + + sample_rate = clk_get_rate(st->clk); + /* + * clk_set_rate() would also do this but since we would still + * need it for avoiding an unnecessary calibration, do it now. + */ + if (sample_rate == r_clk) + return 0; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = __ad9467_update_clock(st, r_clk); + iio_device_release_direct(indio_dev); + return ret; + default: + return -EINVAL; + } +} + +static int ad9467_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct ad9467_state *st = iio_priv(indio_dev); + const struct ad9467_chip_info *info = st->info; + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + *type = IIO_VAL_INT; + *vals = info->offset_range; + return IIO_AVAIL_RANGE; + case IIO_CHAN_INFO_SCALE: + *vals = (const int *)st->scales; + *type = IIO_VAL_INT_PLUS_MICRO; + /* Values are stored in a 2D matrix */ + *length = info->num_scales * 2; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int ad9467_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ad9467_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 const struct iio_info ad9467_info = { + .read_raw = ad9467_read_raw, + .write_raw = ad9467_write_raw, + .update_scan_mode = ad9467_update_scan_mode, + .debugfs_reg_access = ad9467_reg_access, + .read_avail = ad9467_read_avail, +}; + +/* Same as above, but without .read_avail */ +static const struct iio_info ad9467_info_no_read_avail = { + .read_raw = ad9467_read_raw, + .write_raw = ad9467_write_raw, + .update_scan_mode = ad9467_update_scan_mode, + .debugfs_reg_access = ad9467_reg_access, +}; + +static int ad9467_scale_fill(struct ad9467_state *st) +{ + const struct ad9467_chip_info *info = st->info; + unsigned int i, val1, val2; + + st->scales = devm_kmalloc_array(&st->spi->dev, info->num_scales, + sizeof(*st->scales), GFP_KERNEL); + if (!st->scales) + return -ENOMEM; + + for (i = 0; i < info->num_scales; i++) { + __ad9467_get_scale(st, i, &val1, &val2); + st->scales[i][0] = val1; + st->scales[i][1] = val2; + } + + return 0; +} + +static int ad9467_reset(struct device *dev) +{ + struct gpio_desc *gpio; + + gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR_OR_NULL(gpio)) + return PTR_ERR_OR_ZERO(gpio); + + fsleep(1); + gpiod_set_value_cansleep(gpio, 0); + fsleep(10 * USEC_PER_MSEC); + + return 0; +} + +static int ad9467_iio_backend_get(struct ad9467_state *st) +{ + struct device *dev = &st->spi->dev; + struct device_node *__back; + + st->back = devm_iio_backend_get(dev, NULL); + if (!IS_ERR(st->back)) + return 0; + /* If not found, don't error out as we might have legacy DT property */ + if (PTR_ERR(st->back) != -ENOENT) + return PTR_ERR(st->back); + + /* + * if we don't get the backend using the normal API's, use the legacy + * 'adi,adc-dev' property. So we get all nodes with that property, and + * look for the one pointing at us. Then we directly lookup that fwnode + * on the backend list of registered devices. This is done so we don't + * make io-backends mandatory which would break DT ABI. + */ + for_each_node_with_property(__back, "adi,adc-dev") { + struct device_node *__me; + + __me = of_parse_phandle(__back, "adi,adc-dev", 0); + if (!__me) + continue; + + if (!device_match_of_node(dev, __me)) { + of_node_put(__me); + continue; + } + + of_node_put(__me); + st->back = __devm_iio_backend_get_from_fwnode_lookup(dev, + of_fwnode_handle(__back)); + of_node_put(__back); + return PTR_ERR_OR_ZERO(st->back); + } + + return -ENODEV; +} + +static int ad9467_test_mode_available_show(struct seq_file *s, void *ignored) +{ + struct ad9467_state *st = s->private; + unsigned int bit; + + for_each_set_bit(bit, &st->info->test_mask, st->info->test_mask_len) + seq_printf(s, "%s\n", ad9467_test_modes[bit]); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ad9467_test_mode_available); + +static ssize_t ad9467_chan_test_mode_read(struct file *file, + char __user *userbuf, size_t count, + loff_t *ppos) +{ + struct ad9467_chan_test_mode *chan = file->private_data; + struct ad9467_state *st = chan->st; + char buf[128] = {0}; + size_t len; + int ret; + + if (chan->mode == AN877_ADC_TESTMODE_PN9_SEQ || + chan->mode == AN877_ADC_TESTMODE_PN23_SEQ) { + len = scnprintf(buf, sizeof(buf), "Running \"%s\" Test:\n\t", + ad9467_test_modes[chan->mode]); + + if (iio_backend_has_caps(st->back, IIO_BACKEND_CAP_CALIBRATION)) { + ret = iio_backend_debugfs_print_chan_status(st->back, + chan->idx, + buf + len, + sizeof(buf) - len); + if (ret < 0) + return ret; + len += ret; + } + } else if (chan->mode == AN877_ADC_TESTMODE_OFF) { + len = scnprintf(buf, sizeof(buf), "No test Running...\n"); + } else { + len = scnprintf(buf, sizeof(buf), "Running \"%s\" Test on CH:%u\n", + ad9467_test_modes[chan->mode], chan->idx); + } + + return simple_read_from_buffer(userbuf, count, ppos, buf, len); +} + +static ssize_t ad9467_chan_test_mode_write(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct ad9467_chan_test_mode *chan = file->private_data; + struct ad9467_state *st = chan->st; + char test_mode[32] = {0}; + unsigned int mode; + int ret; + + ret = simple_write_to_buffer(test_mode, sizeof(test_mode) - 1, ppos, + userbuf, count); + if (ret < 0) + return ret; + + for_each_set_bit(mode, &st->info->test_mask, st->info->test_mask_len) { + if (sysfs_streq(test_mode, ad9467_test_modes[mode])) + break; + } + + if (mode == st->info->test_mask_len) + return -EINVAL; + + guard(mutex)(&st->lock); + + if (mode == AN877_ADC_TESTMODE_OFF) { + unsigned int out_mode; + + if (iio_backend_has_caps(st->back, IIO_BACKEND_CAP_CALIBRATION)) { + if (chan->mode == AN877_ADC_TESTMODE_PN9_SEQ || + chan->mode == AN877_ADC_TESTMODE_PN23_SEQ) { + ret = ad9467_backend_testmode_off(st, chan->idx); + if (ret) + return ret; + } + } + + ret = ad9467_testmode_set(st, chan->idx, mode); + if (ret) + return ret; + + out_mode = st->info->default_output_mode; + ret = ad9467_outputmode_set(st, out_mode); + if (ret) + return ret; + } else { + unsigned int cmode; + + cmode = st->info->default_output_mode; + FIELD_MODIFY(AN877_ADC_OUTPUT_MODE_MASK, &cmode, + AN877_ADC_OUTPUT_MODE_OFFSET_BINARY); + ret = ad9467_outputmode_set(st, cmode); + if (ret) + return ret; + + ret = ad9467_testmode_set(st, chan->idx, mode); + if (ret) + return ret; + + /* some patterns have a backend matching monitoring block */ + if (iio_backend_has_caps(st->back, IIO_BACKEND_CAP_CALIBRATION)) { + if (mode == AN877_ADC_TESTMODE_PN9_SEQ) { + ret = ad9467_backend_testmode_on(st, chan->idx, + IIO_BACKEND_ADI_PRBS_9A); + if (ret) + return ret; + } else if (mode == AN877_ADC_TESTMODE_PN23_SEQ) { + ret = ad9467_backend_testmode_on(st, chan->idx, + IIO_BACKEND_ADI_PRBS_23A); + if (ret) + return ret; + } + } + } + + chan->mode = mode; + + return count; +} + +static const struct file_operations ad9467_chan_test_mode_fops = { + .open = simple_open, + .read = ad9467_chan_test_mode_read, + .write = ad9467_chan_test_mode_write, + .llseek = default_llseek, + .owner = THIS_MODULE, +}; + +static ssize_t ad9467_dump_calib_table(struct file *file, + char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct ad9467_state *st = file->private_data; + unsigned int bit; + /* +2 for the newline and +1 for the string termination */ + unsigned char map[AD9647_MAX_TEST_POINTS * 2 + 3]; + ssize_t len = 0; + + guard(mutex)(&st->lock); + if (*ppos) + goto out_read; + + for (bit = 0; bit < st->calib_map_size; bit++) { + if (AD9467_CAN_INVERT(st) && bit == st->calib_map_size / 2) + len += scnprintf(map + len, sizeof(map) - len, "\n"); + + len += scnprintf(map + len, sizeof(map) - len, "%c", + test_bit(bit, st->calib_map) ? 'x' : 'o'); + } + + len += scnprintf(map + len, sizeof(map) - len, "\n"); +out_read: + return simple_read_from_buffer(userbuf, count, ppos, map, len); +} + +static const struct file_operations ad9467_calib_table_fops = { + .open = simple_open, + .read = ad9467_dump_calib_table, + .llseek = default_llseek, + .owner = THIS_MODULE, +}; + +static void ad9467_debugfs_init(struct iio_dev *indio_dev) +{ + struct dentry *d = iio_get_debugfs_dentry(indio_dev); + struct ad9467_state *st = iio_priv(indio_dev); + char attr_name[32]; + unsigned int chan; + + if (!IS_ENABLED(CONFIG_DEBUG_FS)) + return; + + st->chan_test = devm_kcalloc(&st->spi->dev, st->info->num_channels, + sizeof(*st->chan_test), GFP_KERNEL); + if (!st->chan_test) + return; + + if (iio_backend_has_caps(st->back, IIO_BACKEND_CAP_CALIBRATION)) + debugfs_create_file("calibration_table_dump", 0400, d, st, + &ad9467_calib_table_fops); + + for (chan = 0; chan < st->info->num_channels; chan++) { + snprintf(attr_name, sizeof(attr_name), "in_voltage%u_test_mode", + chan); + st->chan_test[chan].idx = chan; + st->chan_test[chan].st = st; + debugfs_create_file(attr_name, 0600, d, &st->chan_test[chan], + &ad9467_chan_test_mode_fops); + } + + debugfs_create_file("in_voltage_test_mode_available", 0400, d, st, + &ad9467_test_mode_available_fops); + + iio_backend_debugfs_add(st->back, indio_dev); +} + +static int ad9467_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ad9467_state *st; + unsigned int id; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + + st->info = spi_get_device_match_data(spi); + if (!st->info) + return -ENODEV; + + st->calib_map_size = st->info->test_points; + if (AD9467_CAN_INVERT(st)) + st->calib_map_size *= 2; + + st->clk = devm_clk_get_enabled(dev, "adc-clk"); + if (IS_ERR(st->clk)) + return PTR_ERR(st->clk); + + st->pwrdown_gpio = devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_LOW); + if (IS_ERR(st->pwrdown_gpio)) + return PTR_ERR(st->pwrdown_gpio); + + ret = ad9467_reset(dev); + if (ret) + return ret; + + ret = ad9467_scale_fill(st); + if (ret) + return ret; + + id = ad9467_spi_read(st, AN877_ADC_REG_CHIP_ID); + if (id != st->info->id) + return dev_err_probe(dev, -ENODEV, + "Mismatch CHIP_ID, got 0x%X, expected 0x%X\n", + id, st->info->id); + + if (st->info->num_scales > 1) + indio_dev->info = &ad9467_info; + else + indio_dev->info = &ad9467_info_no_read_avail; + indio_dev->name = st->info->name; + indio_dev->channels = st->info->channels; + indio_dev->num_channels = st->info->num_channels; + + ret = ad9467_iio_backend_get(st); + if (ret) + return ret; + + if (iio_backend_has_caps(st->back, IIO_BACKEND_CAP_BUFFER)) { + ret = devm_iio_backend_request_buffer(dev, st->back, indio_dev); + if (ret) + return ret; + } + + if (iio_backend_has_caps(st->back, IIO_BACKEND_CAP_ENABLE)) { + ret = devm_iio_backend_enable(dev, st->back); + if (ret) + return ret; + } + + if (iio_backend_has_caps(st->back, IIO_BACKEND_CAP_CALIBRATION)) { + ret = ad9467_calibrate(st); + if (ret) + return ret; + } + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ret; + + ad9467_debugfs_init(indio_dev); + + return 0; +} + +static const struct of_device_id ad9467_of_match[] = { + { .compatible = "adi,ad9211", .data = &ad9211_chip_tbl, }, + { .compatible = "adi,ad9265", .data = &ad9265_chip_tbl, }, + { .compatible = "adi,ad9434", .data = &ad9434_chip_tbl, }, + { .compatible = "adi,ad9467", .data = &ad9467_chip_tbl, }, + { .compatible = "adi,ad9643", .data = &ad9643_chip_tbl, }, + { .compatible = "adi,ad9649", .data = &ad9649_chip_tbl, }, + { .compatible = "adi,ad9652", .data = &ad9652_chip_tbl, }, + { } +}; +MODULE_DEVICE_TABLE(of, ad9467_of_match); + +static const struct spi_device_id ad9467_ids[] = { + { .name = "ad9211", .driver_data = (kernel_ulong_t)&ad9211_chip_tbl }, + { .name = "ad9265", .driver_data = (kernel_ulong_t)&ad9265_chip_tbl }, + { .name = "ad9434", .driver_data = (kernel_ulong_t)&ad9434_chip_tbl }, + { .name = "ad9467", .driver_data = (kernel_ulong_t)&ad9467_chip_tbl }, + { .name = "ad9643", .driver_data = (kernel_ulong_t)&ad9643_chip_tbl }, + { .name = "ad9649", .driver_data = (kernel_ulong_t)&ad9649_chip_tbl }, + { .name = "ad9652", .driver_data = (kernel_ulong_t)&ad9652_chip_tbl }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad9467_ids); + +static struct spi_driver ad9467_driver = { + .driver = { + .name = "ad9467", + .of_match_table = ad9467_of_match, + }, + .probe = ad9467_probe, + .id_table = ad9467_ids, +}; +module_spi_driver(ad9467_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD9467 ADC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_BACKEND"); diff --git a/drivers/iio/adc/ad_sigma_delta.c b/drivers/iio/adc/ad_sigma_delta.c new file mode 100644 index 000000000..1b410291d --- /dev/null +++ b/drivers/iio/adc/ad_sigma_delta.c @@ -0,0 +1,950 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Support code for Analog Devices Sigma-Delta ADCs + * + * Copyright 2012 Analog Devices Inc. + * Author: Lars-Peter Clausen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define AD_SD_COMM_CHAN_MASK 0x3 + +#define AD_SD_REG_COMM 0x00 +#define AD_SD_REG_STATUS 0x00 +#define AD_SD_REG_DATA 0x03 + +#define AD_SD_REG_STATUS_RDY 0x80 + +/** + * ad_sd_set_comm() - Set communications register + * + * @sigma_delta: The sigma delta device + * @comm: New value for the communications register + */ +void ad_sd_set_comm(struct ad_sigma_delta *sigma_delta, u8 comm) +{ + /* + * Some variants use the lower two bits of the communications register + * to select the channel. + */ + sigma_delta->comm = comm & AD_SD_COMM_CHAN_MASK; +} +EXPORT_SYMBOL_NS_GPL(ad_sd_set_comm, "IIO_AD_SIGMA_DELTA"); + +/** + * ad_sd_write_reg() - Write a register + * + * @sigma_delta: The sigma delta device + * @reg: Address of the register + * @size: Size of the register (0-3) + * @val: Value to write to the register + * + * Returns 0 on success, an error code otherwise. + **/ +int ad_sd_write_reg(struct ad_sigma_delta *sigma_delta, unsigned int reg, + unsigned int size, unsigned int val) +{ + u8 *data = sigma_delta->tx_buf; + struct spi_transfer t = { + .tx_buf = data, + .len = size + 1, + .cs_change = sigma_delta->keep_cs_asserted, + }; + struct spi_message m; + int ret; + + data[0] = (reg << sigma_delta->info->addr_shift) | sigma_delta->comm; + + switch (size) { + case 3: + put_unaligned_be24(val, &data[1]); + break; + case 2: + put_unaligned_be16(val, &data[1]); + break; + case 1: + data[1] = val; + break; + case 0: + break; + default: + return -EINVAL; + } + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + if (sigma_delta->bus_locked) + ret = spi_sync_locked(sigma_delta->spi, &m); + else + ret = spi_sync(sigma_delta->spi, &m); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(ad_sd_write_reg, "IIO_AD_SIGMA_DELTA"); + +static void ad_sd_set_read_reg_addr(struct ad_sigma_delta *sigma_delta, u8 reg, + u8 *data) +{ + data[0] = reg << sigma_delta->info->addr_shift; + data[0] |= sigma_delta->info->read_mask; + data[0] |= sigma_delta->comm; +} + +static int ad_sd_read_reg_raw(struct ad_sigma_delta *sigma_delta, + unsigned int reg, unsigned int size, u8 *val) +{ + u8 *data = sigma_delta->tx_buf; + int ret; + struct spi_transfer t[] = { + { + .tx_buf = data, + .len = 1, + }, { + .rx_buf = val, + .len = size, + .cs_change = sigma_delta->keep_cs_asserted, + }, + }; + struct spi_message m; + + spi_message_init(&m); + + if (sigma_delta->info->has_registers) { + ad_sd_set_read_reg_addr(sigma_delta, reg, data); + spi_message_add_tail(&t[0], &m); + } + spi_message_add_tail(&t[1], &m); + + if (sigma_delta->bus_locked) + ret = spi_sync_locked(sigma_delta->spi, &m); + else + ret = spi_sync(sigma_delta->spi, &m); + + return ret; +} + +/** + * ad_sd_read_reg() - Read a register + * + * @sigma_delta: The sigma delta device + * @reg: Address of the register + * @size: Size of the register (1-4) + * @val: Read value + * + * Returns 0 on success, an error code otherwise. + **/ +int ad_sd_read_reg(struct ad_sigma_delta *sigma_delta, + unsigned int reg, unsigned int size, unsigned int *val) +{ + int ret; + + ret = ad_sd_read_reg_raw(sigma_delta, reg, size, sigma_delta->rx_buf); + if (ret < 0) + goto out; + + switch (size) { + case 4: + *val = get_unaligned_be32(sigma_delta->rx_buf); + break; + case 3: + *val = get_unaligned_be24(sigma_delta->rx_buf); + break; + case 2: + *val = get_unaligned_be16(sigma_delta->rx_buf); + break; + case 1: + *val = sigma_delta->rx_buf[0]; + break; + default: + ret = -EINVAL; + break; + } + +out: + return ret; +} +EXPORT_SYMBOL_NS_GPL(ad_sd_read_reg, "IIO_AD_SIGMA_DELTA"); + +/** + * ad_sd_reset() - Reset the serial interface + * + * @sigma_delta: The sigma delta device + * + * Returns 0 on success, an error code otherwise. + **/ +int ad_sd_reset(struct ad_sigma_delta *sigma_delta) +{ + unsigned int reset_length = sigma_delta->info->num_resetclks; + unsigned int size; + u8 *buf; + int ret; + + size = BITS_TO_BYTES(reset_length); + buf = kcalloc(size, sizeof(*buf), GFP_KERNEL); + if (!buf) + return -ENOMEM; + + memset(buf, 0xff, size); + ret = spi_write(sigma_delta->spi, buf, size); + kfree(buf); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(ad_sd_reset, "IIO_AD_SIGMA_DELTA"); + +static bool ad_sd_disable_irq(struct ad_sigma_delta *sigma_delta) +{ + guard(spinlock_irqsave)(&sigma_delta->irq_lock); + + /* It's already off, return false to indicate nothing was changed */ + if (sigma_delta->irq_dis) + return false; + + sigma_delta->irq_dis = true; + disable_irq_nosync(sigma_delta->irq_line); + return true; +} + +static void ad_sd_enable_irq(struct ad_sigma_delta *sigma_delta) +{ + guard(spinlock_irqsave)(&sigma_delta->irq_lock); + + sigma_delta->irq_dis = false; + enable_irq(sigma_delta->irq_line); +} + +#define AD_SD_CLEAR_DATA_BUFLEN 9 + +/* Called with `sigma_delta->bus_locked == true` only. */ +static int ad_sigma_delta_clear_pending_event(struct ad_sigma_delta *sigma_delta) +{ + bool pending_event; + unsigned int data_read_len = BITS_TO_BYTES(sigma_delta->info->num_resetclks); + u8 *data; + struct spi_transfer t[] = { + { + .len = 1, + }, { + .len = data_read_len, + } + }; + struct spi_message m; + int ret; + + /* + * Read R̅D̅Y̅ pin (if possible) or status register to check if there is an + * old event. For devices with neither an RDY GPIO nor registers, + * ad_sd_read_reg() transmits no address byte and clocks raw MISO bytes, + * which is indistinguishable from reading conversion data and would + * partially consume a pending result. Skip the check for such devices. + * + * This is safe for all current registerless devices: ad7191 and ad7780 + * (with powerdown GPIO) are reset between conversions by CS deassertion, + * so there is no stale result to drain; ad7780 (without powerdown GPIO) + * and max11205 are continuously-converting and cycle ~DRDY at the output + * data rate regardless of whether the previous result was read, so the + * next falling edge fires naturally. + * + * A future registerless device that holds ~DRDY asserted until data is + * read would be broken by this early return and would need either + * num_resetclks set or a rdy-gpio. + */ + if (sigma_delta->rdy_gpiod) { + pending_event = gpiod_get_value(sigma_delta->rdy_gpiod); + } else if (sigma_delta->info->has_registers) { + unsigned int status_reg; + + ret = ad_sd_read_reg(sigma_delta, AD_SD_REG_STATUS, 1, &status_reg); + if (ret) + return ret; + + pending_event = !(status_reg & AD_SD_REG_STATUS_RDY); + } else { + return 0; + } + + if (!pending_event) + return 0; + + /* + * With num_resetclks = 0, data_read_len is 0 and the drain sequence + * below would compute memset(data + 2, 0xff, 0 - 1), underflowing to + * SIZE_MAX and corrupting the heap. There is no safe way to drain the + * stale result without knowing the data register size; it will be + * consumed by the first ad_sd_read_reg() call in + * ad_sigma_delta_single_conversion(). + */ + if (!data_read_len) + return 0; + + /* + * In general the size of the data register is unknown. It varies from + * device to device, might be one byte longer if CONTROL.DATA_STATUS is + * set and even varies on some devices depending on which input is + * selected. So send one byte to start reading the data register and + * then just clock for some bytes with DIN (aka MOSI) high to not + * confuse the register access state machine after the data register was + * completely read. Note however that the sequence length must be + * shorter than the reset procedure. + */ + + data = kzalloc(data_read_len + 1, GFP_KERNEL); + if (!data) + return -ENOMEM; + + spi_message_init(&m); + if (sigma_delta->info->has_registers) { + unsigned int data_reg = sigma_delta->info->data_reg ?: AD_SD_REG_DATA; + + ad_sd_set_read_reg_addr(sigma_delta, data_reg, data); + t[0].tx_buf = data; + spi_message_add_tail(&t[0], &m); + } + + /* + * The first transferred byte is part of the real data register, + * so this doesn't need to be 0xff. In the remaining + * `data_read_len - 1` bytes are less than $num_resetclks ones. + */ + t[1].tx_buf = data + 1; + data[1] = 0x00; + memset(data + 2, 0xff, data_read_len - 1); + spi_message_add_tail(&t[1], &m); + + ret = spi_sync_locked(sigma_delta->spi, &m); + + kfree(data); + + return ret; +} + +int ad_sd_calibrate(struct ad_sigma_delta *sigma_delta, + unsigned int mode, unsigned int channel) +{ + int ret; + unsigned long time_left; + + ret = ad_sigma_delta_set_channel(sigma_delta, channel); + if (ret) + return ret; + + spi_bus_lock(sigma_delta->spi->controller); + sigma_delta->bus_locked = true; + sigma_delta->keep_cs_asserted = true; + reinit_completion(&sigma_delta->completion); + + ret = ad_sigma_delta_clear_pending_event(sigma_delta); + if (ret) + goto out; + + ret = ad_sigma_delta_set_mode(sigma_delta, mode); + if (ret < 0) + goto out; + + ad_sd_enable_irq(sigma_delta); + time_left = wait_for_completion_timeout(&sigma_delta->completion, 2 * HZ); + if (time_left == 0) { + ad_sd_disable_irq(sigma_delta); + ret = -EIO; + } else { + ret = 0; + } +out: + sigma_delta->keep_cs_asserted = false; + ad_sigma_delta_set_mode(sigma_delta, AD_SD_MODE_IDLE); + ad_sigma_delta_disable_one(sigma_delta, channel); + sigma_delta->bus_locked = false; + spi_bus_unlock(sigma_delta->spi->controller); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(ad_sd_calibrate, "IIO_AD_SIGMA_DELTA"); + +/** + * ad_sd_calibrate_all() - Performs channel calibration + * @sigma_delta: The sigma delta device + * @cb: Array of channels and calibration type to perform + * @n: Number of items in cb + * + * Returns 0 on success, an error code otherwise. + **/ +int ad_sd_calibrate_all(struct ad_sigma_delta *sigma_delta, + const struct ad_sd_calib_data *cb, unsigned int n) +{ + unsigned int i; + int ret; + + for (i = 0; i < n; i++) { + ret = ad_sd_calibrate(sigma_delta, cb[i].mode, cb[i].channel); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ad_sd_calibrate_all, "IIO_AD_SIGMA_DELTA"); + +/** + * ad_sigma_delta_single_conversion() - Performs a single data conversion + * @indio_dev: The IIO device + * @chan: The conversion is done for this channel + * @val: Pointer to the location where to store the read value + * + * Returns: 0 on success, an error value otherwise. + */ +int ad_sigma_delta_single_conversion(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val) +{ + struct ad_sigma_delta *sigma_delta = iio_device_get_drvdata(indio_dev); + unsigned int sample, raw_sample; + unsigned int data_reg; + int ret = 0; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad_sigma_delta_set_channel(sigma_delta, chan->address); + if (ret) + goto out_release; + + spi_bus_lock(sigma_delta->spi->controller); + sigma_delta->bus_locked = true; + sigma_delta->keep_cs_asserted = true; + reinit_completion(&sigma_delta->completion); + + ret = ad_sigma_delta_clear_pending_event(sigma_delta); + if (ret) + goto out_unlock; + + ad_sigma_delta_set_mode(sigma_delta, AD_SD_MODE_SINGLE); + + ad_sd_enable_irq(sigma_delta); + ret = wait_for_completion_interruptible_timeout( + &sigma_delta->completion, HZ); + + if (ret == 0) + ret = -EIO; + if (ret < 0) + goto out; + + if (sigma_delta->info->data_reg != 0) + data_reg = sigma_delta->info->data_reg; + else + data_reg = AD_SD_REG_DATA; + + ret = ad_sd_read_reg(sigma_delta, data_reg, + BITS_TO_BYTES(chan->scan_type.realbits + chan->scan_type.shift), + &raw_sample); + +out: + ad_sd_disable_irq(sigma_delta); + +out_unlock: + sigma_delta->keep_cs_asserted = false; + ad_sigma_delta_set_mode(sigma_delta, AD_SD_MODE_IDLE); + ad_sigma_delta_disable_one(sigma_delta, chan->address); + sigma_delta->bus_locked = false; + spi_bus_unlock(sigma_delta->spi->controller); +out_release: + iio_device_release_direct(indio_dev); + + if (ret) + return ret; + + sample = raw_sample >> chan->scan_type.shift; + sample &= (1 << chan->scan_type.realbits) - 1; + *val = sample; + + ret = ad_sigma_delta_postprocess_sample(sigma_delta, raw_sample); + if (ret) + return ret; + + return IIO_VAL_INT; +} +EXPORT_SYMBOL_NS_GPL(ad_sigma_delta_single_conversion, "IIO_AD_SIGMA_DELTA"); + +static int ad_sd_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad_sigma_delta *sigma_delta = iio_device_get_drvdata(indio_dev); + const struct iio_scan_type *scan_type = &indio_dev->channels[0].scan_type; + struct spi_transfer *xfer = sigma_delta->sample_xfer; + unsigned int i, slot, channel; + u8 *samples_buf; + int ret; + + if (sigma_delta->num_slots == 1) { + channel = find_first_bit(indio_dev->active_scan_mask, + iio_get_masklength(indio_dev)); + ret = ad_sigma_delta_set_channel(sigma_delta, + indio_dev->channels[channel].address); + if (ret) + return ret; + slot = 1; + } else { + /* + * At this point update_scan_mode already enabled the required channels. + * For sigma-delta sequencer drivers with multiple slots, an update_scan_mode + * implementation is mandatory. + */ + slot = 0; + iio_for_each_active_channel(indio_dev, i) { + sigma_delta->slots[slot] = indio_dev->channels[i].address; + slot++; + } + } + + sigma_delta->active_slots = slot; + sigma_delta->current_slot = 0; + + if (ad_sigma_delta_has_spi_offload(sigma_delta)) { + xfer[1].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM; + xfer[1].bits_per_word = scan_type->realbits; + xfer[1].len = spi_bpw_to_bytes(scan_type->realbits); + } else { + unsigned int samples_buf_size, scan_size; + + if (sigma_delta->active_slots > 1) { + ret = ad_sigma_delta_append_status(sigma_delta, true); + if (ret) + return ret; + } + + samples_buf_size = + ALIGN(slot * BITS_TO_BYTES(scan_type->storagebits), + sizeof(s64)); + samples_buf_size += sizeof(s64); + samples_buf = devm_krealloc(&sigma_delta->spi->dev, + sigma_delta->samples_buf, + samples_buf_size, GFP_KERNEL); + if (!samples_buf) + return -ENOMEM; + + sigma_delta->samples_buf = samples_buf; + scan_size = BITS_TO_BYTES(scan_type->realbits + scan_type->shift); + /* For 24-bit data, there is an extra byte of padding. */ + xfer[1].rx_buf = &sigma_delta->rx_buf[scan_size == 3 ? 1 : 0]; + xfer[1].len = scan_size + (sigma_delta->status_appended ? 1 : 0); + } + xfer[1].cs_change = 1; + + if (sigma_delta->info->has_registers) { + xfer[0].tx_buf = &sigma_delta->sample_addr; + xfer[0].len = 1; + + ad_sd_set_read_reg_addr(sigma_delta, + sigma_delta->info->data_reg ?: AD_SD_REG_DATA, + &sigma_delta->sample_addr); + spi_message_init_with_transfers(&sigma_delta->sample_msg, xfer, 2); + } else { + spi_message_init_with_transfers(&sigma_delta->sample_msg, + &xfer[1], 1); + } + + sigma_delta->sample_msg.offload = sigma_delta->offload; + + ret = spi_optimize_message(sigma_delta->spi, &sigma_delta->sample_msg); + if (ret) + return ret; + + spi_bus_lock(sigma_delta->spi->controller); + sigma_delta->bus_locked = true; + sigma_delta->keep_cs_asserted = true; + + ret = ad_sigma_delta_clear_pending_event(sigma_delta); + if (ret) + goto err_unlock; + + ret = ad_sigma_delta_set_mode(sigma_delta, AD_SD_MODE_CONTINUOUS); + if (ret) + goto err_unlock; + + if (ad_sigma_delta_has_spi_offload(sigma_delta)) { + struct spi_offload_trigger_config config = { + .type = SPI_OFFLOAD_TRIGGER_DATA_READY, + }; + + ret = spi_offload_trigger_enable(sigma_delta->offload, + sigma_delta->offload_trigger, + &config); + if (ret) + goto err_unlock; + } else { + ad_sd_enable_irq(sigma_delta); + } + + return 0; + +err_unlock: + sigma_delta->keep_cs_asserted = false; + ad_sigma_delta_set_mode(sigma_delta, AD_SD_MODE_IDLE); + sigma_delta->bus_locked = false; + spi_bus_unlock(sigma_delta->spi->controller); + spi_unoptimize_message(&sigma_delta->sample_msg); + + return ret; +} + +static int ad_sd_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad_sigma_delta *sigma_delta = iio_device_get_drvdata(indio_dev); + + if (ad_sigma_delta_has_spi_offload(sigma_delta)) { + spi_offload_trigger_disable(sigma_delta->offload, + sigma_delta->offload_trigger); + } else { + reinit_completion(&sigma_delta->completion); + wait_for_completion_timeout(&sigma_delta->completion, HZ); + + ad_sd_disable_irq(sigma_delta); + } + + sigma_delta->keep_cs_asserted = false; + ad_sigma_delta_set_mode(sigma_delta, AD_SD_MODE_IDLE); + + if (sigma_delta->status_appended) + ad_sigma_delta_append_status(sigma_delta, false); + + ad_sigma_delta_disable_all(sigma_delta); + sigma_delta->bus_locked = false; + spi_bus_unlock(sigma_delta->spi->controller); + spi_unoptimize_message(&sigma_delta->sample_msg); + + return 0; +} + +static irqreturn_t ad_sd_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + const struct iio_scan_type *scan_type = &indio_dev->channels[0].scan_type; + struct ad_sigma_delta *sigma_delta = iio_device_get_drvdata(indio_dev); + u8 *data = sigma_delta->rx_buf; + unsigned int sample_size; + unsigned int sample_pos; + unsigned int status_pos; + unsigned int reg_size; + int ret; + + reg_size = BITS_TO_BYTES(scan_type->realbits + scan_type->shift); + /* For 24-bit data, there is an extra byte of padding. */ + status_pos = reg_size + (reg_size == 3 ? 1 : 0); + + ret = spi_sync_locked(sigma_delta->spi, &sigma_delta->sample_msg); + if (ret) + goto irq_handled; + + /* + * For devices sampling only one channel at + * once, there is no need for sample number tracking. + */ + if (sigma_delta->active_slots == 1) { + iio_push_to_buffers_with_timestamp(indio_dev, data, pf->timestamp); + goto irq_handled; + } + + if (sigma_delta->status_appended) { + u8 converted_channel; + + converted_channel = data[status_pos] & sigma_delta->info->status_ch_mask; + if (converted_channel != sigma_delta->slots[sigma_delta->current_slot]) { + /* + * Desync occurred during continuous sampling of multiple channels. + * Drop this incomplete sample and start from first channel again. + */ + + sigma_delta->current_slot = 0; + goto irq_handled; + } + } + + sample_size = BITS_TO_BYTES(scan_type->storagebits); + sample_pos = sample_size * sigma_delta->current_slot; + memcpy(&sigma_delta->samples_buf[sample_pos], data, sample_size); + sigma_delta->current_slot++; + + if (sigma_delta->current_slot == sigma_delta->active_slots) { + sigma_delta->current_slot = 0; + iio_push_to_buffers_with_timestamp(indio_dev, sigma_delta->samples_buf, + pf->timestamp); + } + +irq_handled: + iio_trigger_notify_done(indio_dev->trig); + ad_sd_enable_irq(sigma_delta); + + return IRQ_HANDLED; +} + +static bool ad_sd_validate_scan_mask(struct iio_dev *indio_dev, const unsigned long *mask) +{ + struct ad_sigma_delta *sigma_delta = iio_device_get_drvdata(indio_dev); + + return bitmap_weight(mask, iio_get_masklength(indio_dev)) <= sigma_delta->num_slots; +} + +static const struct iio_buffer_setup_ops ad_sd_buffer_setup_ops = { + .postenable = &ad_sd_buffer_postenable, + .predisable = &ad_sd_buffer_predisable, + .validate_scan_mask = &ad_sd_validate_scan_mask, +}; + +static irqreturn_t ad_sd_data_rdy_trig_poll(int irq, void *private) +{ + struct ad_sigma_delta *sigma_delta = private; + + /* + * AD7124 and a few others use the same physical line for interrupt + * reporting (R̅D̅Y̅) and MISO. + * As MISO toggles when reading a register, this likely results in a + * pending interrupt. This has two consequences: a) The irq might + * trigger immediately after it's enabled even though the conversion + * isn't done yet; and b) checking the STATUS register's R̅D̅Y̅ flag is + * off-limits as reading that would trigger another irq event. + * + * So read the MOSI line as GPIO (if available) and only trigger the irq + * if the line is active. Without such a GPIO assume this is a valid + * interrupt. + * + * Also as disable_irq_nosync() is used to disable the irq, only act if + * the irq wasn't disabled before. + */ + if ((!sigma_delta->rdy_gpiod || gpiod_get_value(sigma_delta->rdy_gpiod)) && + ad_sd_disable_irq(sigma_delta)) { + complete(&sigma_delta->completion); + if (sigma_delta->trig) + iio_trigger_poll(sigma_delta->trig); + + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +/** + * ad_sd_validate_trigger() - validate_trigger callback for ad_sigma_delta devices + * @indio_dev: The IIO device + * @trig: The new trigger + * + * Returns: 0 if the 'trig' matches the trigger registered by the ad_sigma_delta + * device, -EINVAL otherwise. + */ +int ad_sd_validate_trigger(struct iio_dev *indio_dev, struct iio_trigger *trig) +{ + struct ad_sigma_delta *sigma_delta = iio_device_get_drvdata(indio_dev); + + if (sigma_delta->trig != trig) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ad_sd_validate_trigger, "IIO_AD_SIGMA_DELTA"); + +static int devm_ad_sd_probe_trigger(struct device *dev, struct iio_dev *indio_dev) +{ + struct ad_sigma_delta *sigma_delta = iio_device_get_drvdata(indio_dev); + unsigned long irq_flags = irq_get_trigger_type(sigma_delta->irq_line); + int ret; + + init_completion(&sigma_delta->completion); + + sigma_delta->irq_dis = true; + + /* the IRQ core clears IRQ_DISABLE_UNLAZY flag when freeing an IRQ */ + irq_set_status_flags(sigma_delta->irq_line, IRQ_DISABLE_UNLAZY); + + /* Allow overwriting the flags from firmware */ + if (!irq_flags) + irq_flags = sigma_delta->info->irq_flags; + + ret = devm_request_irq(dev, sigma_delta->irq_line, + ad_sd_data_rdy_trig_poll, + irq_flags | IRQF_NO_AUTOEN, + indio_dev->name, + sigma_delta); + if (ret) + return ret; + + if (ad_sigma_delta_has_spi_offload(sigma_delta)) { + sigma_delta->offload_trigger = + devm_spi_offload_trigger_get(dev, sigma_delta->offload, + SPI_OFFLOAD_TRIGGER_DATA_READY); + if (IS_ERR(sigma_delta->offload_trigger)) + return dev_err_probe(dev, PTR_ERR(sigma_delta->offload_trigger), + "Failed to get SPI offload trigger\n"); + } else { + if (dev != &sigma_delta->spi->dev) + return dev_err_probe(dev, -EFAULT, + "Trigger parent should be '%s', got '%s'\n", + dev_name(dev), dev_name(&sigma_delta->spi->dev)); + + sigma_delta->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", + indio_dev->name, iio_device_id(indio_dev)); + if (!sigma_delta->trig) + return -ENOMEM; + + iio_trigger_set_drvdata(sigma_delta->trig, sigma_delta); + + ret = devm_iio_trigger_register(dev, sigma_delta->trig); + if (ret) + return ret; + + /* select default trigger */ + indio_dev->trig = iio_trigger_get(sigma_delta->trig); + } + + return 0; +} + +/** + * devm_ad_sd_setup_buffer_and_trigger() - Device-managed buffer & trigger setup + * @dev: Device object to which to bind the life-time of the resources attached + * @indio_dev: The IIO device + */ +int devm_ad_sd_setup_buffer_and_trigger(struct device *dev, struct iio_dev *indio_dev) +{ + struct ad_sigma_delta *sigma_delta = iio_device_get_drvdata(indio_dev); + int ret; + + sigma_delta->slots = devm_kcalloc(dev, sigma_delta->num_slots, + sizeof(*sigma_delta->slots), GFP_KERNEL); + if (!sigma_delta->slots) + return -ENOMEM; + + if (ad_sigma_delta_has_spi_offload(sigma_delta)) { + struct dma_chan *rx_dma; + + rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev, + sigma_delta->offload); + if (IS_ERR(rx_dma)) + return dev_err_probe(dev, PTR_ERR(rx_dma), + "Failed to get RX DMA channel\n"); + + ret = devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, + rx_dma, IIO_BUFFER_DIRECTION_IN); + if (ret) + return dev_err_probe(dev, ret, "Cannot setup DMA buffer\n"); + + indio_dev->setup_ops = &ad_sd_buffer_setup_ops; + } else { + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + &iio_pollfunc_store_time, + &ad_sd_trigger_handler, + &ad_sd_buffer_setup_ops); + if (ret) + return ret; + } + + return devm_ad_sd_probe_trigger(dev, indio_dev); +} +EXPORT_SYMBOL_NS_GPL(devm_ad_sd_setup_buffer_and_trigger, "IIO_AD_SIGMA_DELTA"); + +/** + * ad_sd_init() - Initializes a ad_sigma_delta struct + * @sigma_delta: The ad_sigma_delta device + * @indio_dev: The IIO device which the Sigma Delta device is used for + * @spi: The SPI device for the ad_sigma_delta device + * @info: Device specific callbacks and options + * + * This function needs to be called before any other operations are performed on + * the ad_sigma_delta struct. + */ +int ad_sd_init(struct ad_sigma_delta *sigma_delta, struct iio_dev *indio_dev, + struct spi_device *spi, const struct ad_sigma_delta_info *info) +{ + sigma_delta->spi = spi; + sigma_delta->info = info; + + /* If the field is unset in ad_sigma_delta_info, assume there can only be 1 slot. */ + if (!info->num_slots) + sigma_delta->num_slots = 1; + else + sigma_delta->num_slots = info->num_slots; + + if (sigma_delta->num_slots > 1) { + if (!indio_dev->info->update_scan_mode) { + dev_err(&spi->dev, "iio_dev lacks update_scan_mode().\n"); + return -EINVAL; + } + + if (!info->disable_all) { + dev_err(&spi->dev, "ad_sigma_delta_info lacks disable_all().\n"); + return -EINVAL; + } + } + + spin_lock_init(&sigma_delta->irq_lock); + + if (info->has_named_irqs) { + sigma_delta->irq_line = fwnode_irq_get_byname(dev_fwnode(&spi->dev), + "rdy"); + if (sigma_delta->irq_line < 0) + return dev_err_probe(&spi->dev, sigma_delta->irq_line, + "Interrupt 'rdy' is required\n"); + } else { + sigma_delta->irq_line = spi->irq; + } + + sigma_delta->rdy_gpiod = devm_gpiod_get_optional(&spi->dev, "rdy", GPIOD_IN); + if (IS_ERR(sigma_delta->rdy_gpiod)) + return dev_err_probe(&spi->dev, PTR_ERR(sigma_delta->rdy_gpiod), + "Failed to find rdy gpio\n"); + + if (sigma_delta->rdy_gpiod && !sigma_delta->irq_line) { + sigma_delta->irq_line = gpiod_to_irq(sigma_delta->rdy_gpiod); + if (sigma_delta->irq_line < 0) + return sigma_delta->irq_line; + } + + if (info->supports_spi_offload) { + struct spi_offload_config offload_config = { + .capability_flags = SPI_OFFLOAD_CAP_TRIGGER | + SPI_OFFLOAD_CAP_RX_STREAM_DMA, + }; + int ret; + + sigma_delta->offload = devm_spi_offload_get(&spi->dev, spi, + &offload_config); + ret = PTR_ERR_OR_ZERO(sigma_delta->offload); + if (ret && ret != -ENODEV) + return dev_err_probe(&spi->dev, ret, "Failed to get SPI offload\n"); + } + + iio_device_set_drvdata(indio_dev, sigma_delta); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ad_sd_init, "IIO_AD_SIGMA_DELTA"); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Analog Devices Sigma-Delta ADCs"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER"); diff --git a/drivers/iio/adc/ade9000.c b/drivers/iio/adc/ade9000.c new file mode 100644 index 000000000..c6c3ea953 --- /dev/null +++ b/drivers/iio/adc/ade9000.c @@ -0,0 +1,1794 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADE9000 driver + * + * Copyright 2025 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Address of ADE9000 registers */ +#define ADE9000_REG_AIGAIN 0x000 +#define ADE9000_REG_AVGAIN 0x00B +#define ADE9000_REG_AIRMSOS 0x00C +#define ADE9000_REG_AVRMSOS 0x00D +#define ADE9000_REG_APGAIN 0x00E +#define ADE9000_REG_AWATTOS 0x00F +#define ADE9000_REG_AVAROS 0x010 +#define ADE9000_REG_AFVAROS 0x012 +#define ADE9000_REG_CONFIG0 0x060 +#define ADE9000_REG_DICOEFF 0x072 +#define ADE9000_REG_AI_PCF 0x20A +#define ADE9000_REG_AV_PCF 0x20B +#define ADE9000_REG_AIRMS 0x20C +#define ADE9000_REG_AVRMS 0x20D +#define ADE9000_REG_AWATT 0x210 +#define ADE9000_REG_AVAR 0x211 +#define ADE9000_REG_AVA 0x212 +#define ADE9000_REG_AFVAR 0x214 +#define ADE9000_REG_APF 0x216 +#define ADE9000_REG_BI_PCF 0x22A +#define ADE9000_REG_BV_PCF 0x22B +#define ADE9000_REG_BIRMS 0x22C +#define ADE9000_REG_BVRMS 0x22D +#define ADE9000_REG_CI_PCF 0x24A +#define ADE9000_REG_CV_PCF 0x24B +#define ADE9000_REG_CIRMS 0x24C +#define ADE9000_REG_CVRMS 0x24D +#define ADE9000_REG_AWATT_ACC 0x2E5 +#define ADE9000_REG_AWATTHR_LO 0x2E6 +#define ADE9000_REG_AVAHR_LO 0x2FA +#define ADE9000_REG_AFVARHR_LO 0x30E +#define ADE9000_REG_BWATTHR_LO 0x322 +#define ADE9000_REG_BVAHR_LO 0x336 +#define ADE9000_REG_BFVARHR_LO 0x34A +#define ADE9000_REG_CWATTHR_LO 0x35E +#define ADE9000_REG_CVAHR_LO 0x372 +#define ADE9000_REG_CFVARHR_LO 0x386 +#define ADE9000_REG_STATUS0 0x402 +#define ADE9000_REG_STATUS1 0x403 +#define ADE9000_REG_MASK0 0x405 +#define ADE9000_REG_MASK1 0x406 +#define ADE9000_REG_EVENT_MASK 0x407 +#define ADE9000_REG_VLEVEL 0x40F +#define ADE9000_REG_DIP_LVL 0x410 +#define ADE9000_REG_DIPA 0x411 +#define ADE9000_REG_DIPB 0x412 +#define ADE9000_REG_DIPC 0x413 +#define ADE9000_REG_SWELL_LVL 0x414 +#define ADE9000_REG_SWELLA 0x415 +#define ADE9000_REG_SWELLB 0x416 +#define ADE9000_REG_SWELLC 0x417 +#define ADE9000_REG_APERIOD 0x418 +#define ADE9000_REG_BPERIOD 0x419 +#define ADE9000_REG_CPERIOD 0x41A +#define ADE9000_REG_RUN 0x480 +#define ADE9000_REG_CONFIG1 0x481 +#define ADE9000_REG_ACCMODE 0x492 +#define ADE9000_REG_CONFIG3 0x493 +#define ADE9000_REG_ZXTOUT 0x498 +#define ADE9000_REG_ZX_LP_SEL 0x49A +#define ADE9000_REG_WFB_CFG 0x4A0 +#define ADE9000_REG_WFB_PG_IRQEN 0x4A1 +#define ADE9000_REG_WFB_TRG_CFG 0x4A2 +#define ADE9000_REG_WFB_TRG_STAT 0x4A3 +#define ADE9000_REG_CONFIG2 0x4AF +#define ADE9000_REG_EP_CFG 0x4B0 +#define ADE9000_REG_EGY_TIME 0x4B2 +#define ADE9000_REG_PGA_GAIN 0x4B9 +#define ADE9000_REG_VERSION 0x4FE +#define ADE9000_REG_WF_BUFF 0x800 +#define ADE9000_REG_WF_HALF_BUFF 0xC00 + +#define ADE9000_REG_ADDR_MASK GENMASK(15, 4) +#define ADE9000_REG_READ_BIT_MASK BIT(3) + +#define ADE9000_WF_CAP_EN_MASK BIT(4) +#define ADE9000_WF_CAP_SEL_MASK BIT(5) +#define ADE9000_WF_MODE_MASK GENMASK(7, 6) +#define ADE9000_WF_SRC_MASK GENMASK(9, 8) +#define ADE9000_WF_IN_EN_MASK BIT(12) + +/* External reference selection bit in CONFIG1 */ +#define ADE9000_EXT_REF_MASK BIT(15) + +/* + * Configuration registers + */ +#define ADE9000_PGA_GAIN 0x0000 + +/* Default configuration */ + +#define ADE9000_CONFIG0 0x00000000 + +/* CF3/ZX pin outputs Zero crossing, CF4 = DREADY */ +#define ADE9000_CONFIG1 0x000E + +/* Default High pass corner frequency of 1.25Hz */ +#define ADE9000_CONFIG2 0x0A00 + +/* Peak and overcurrent detection disabled */ +#define ADE9000_CONFIG3 0x0000 + +/* + * 50Hz operation, 3P4W Wye configuration, signed accumulation + * 3P4W Wye = 3-Phase 4-Wire star configuration (3 phases + neutral wire) + * Clear bit 8 i.e. ACCMODE=0x00xx for 50Hz operation + * ACCMODE=0x0x9x for 3Wire delta when phase B is used as reference + * 3Wire delta = 3-Phase 3-Wire triangle configuration (3 phases, no neutral) + */ +#define ADE9000_ACCMODE 0x0000 +#define ADE9000_ACCMODE_60HZ 0x0100 + +/*Line period and zero crossing obtained from VA */ +#define ADE9000_ZX_LP_SEL 0x0000 + +/* Interrupt mask values for initialization */ +#define ADE9000_MASK0_ALL_INT_DIS 0 +#define ADE9000_MASK1_ALL_INT_DIS 0x00000000 + +/* Events disabled */ +#define ADE9000_EVENT_DISABLE 0x00000000 + +/* + * Assuming Vnom=1/2 of full scale. + * Refer to Technical reference manual for detailed calculations. + */ +#define ADE9000_VLEVEL 0x0022EA28 + +/* Set DICOEFF= 0xFFFFE000 when integrator is enabled */ +#define ADE9000_DICOEFF 0x00000000 + +/* DSP ON */ +#define ADE9000_RUN_ON 0xFFFFFFFF + +/* + * Energy Accumulation Settings + * Enable energy accumulation, accumulate samples at 8ksps + * latch energy accumulation after EGYRDY + * If accumulation is changed to half line cycle mode, change EGY_TIME + */ +#define ADE9000_EP_CFG 0x0011 + +/* Accumulate 4000 samples */ +#define ADE9000_EGY_TIME 7999 + +/* + * Constant Definitions + * ADE9000 FDSP: 8000sps, ADE9000 FDSP: 4000sps + */ +#define ADE9000_FDSP 4000 +#define ADE9000_DEFAULT_CLK_FREQ_HZ 24576000 +#define ADE9000_WFB_CFG 0x03E9 +#define ADE9000_WFB_PAGE_SIZE 128 +#define ADE9000_WFB_NR_OF_PAGES 16 +#define ADE9000_WFB_MAX_CHANNELS 8 +#define ADE9000_WFB_BYTES_IN_SAMPLE 4 +#define ADE9000_WFB_SAMPLES_IN_PAGE \ + (ADE9000_WFB_PAGE_SIZE / ADE9000_WFB_MAX_CHANNELS) +#define ADE9000_WFB_MAX_SAMPLES_CHAN \ + (ADE9000_WFB_SAMPLES_IN_PAGE * ADE9000_WFB_NR_OF_PAGES) +#define ADE9000_WFB_FULL_BUFF_NR_SAMPLES \ + (ADE9000_WFB_PAGE_SIZE * ADE9000_WFB_NR_OF_PAGES) +#define ADE9000_WFB_FULL_BUFF_SIZE \ + (ADE9000_WFB_FULL_BUFF_NR_SAMPLES * ADE9000_WFB_BYTES_IN_SAMPLE) + +#define ADE9000_SWRST_BIT BIT(0) + +/* Status and Mask register bits*/ +#define ADE9000_ST0_WFB_TRIG_BIT BIT(16) +#define ADE9000_ST0_PAGE_FULL_BIT BIT(17) +#define ADE9000_ST0_EGYRDY BIT(0) + +#define ADE9000_ST1_ZXTOVA_BIT BIT(6) +#define ADE9000_ST1_ZXTOVB_BIT BIT(7) +#define ADE9000_ST1_ZXTOVC_BIT BIT(8) +#define ADE9000_ST1_ZXVA_BIT BIT(9) +#define ADE9000_ST1_ZXVB_BIT BIT(10) +#define ADE9000_ST1_ZXVC_BIT BIT(11) +#define ADE9000_ST1_ZXIA_BIT BIT(13) +#define ADE9000_ST1_ZXIB_BIT BIT(14) +#define ADE9000_ST1_ZXIC_BIT BIT(15) +#define ADE9000_ST1_RSTDONE_BIT BIT(16) +#define ADE9000_ST1_SEQERR_BIT BIT(18) +#define ADE9000_ST1_SWELLA_BIT BIT(20) +#define ADE9000_ST1_SWELLB_BIT BIT(21) +#define ADE9000_ST1_SWELLC_BIT BIT(22) +#define ADE9000_ST1_DIPA_BIT BIT(23) +#define ADE9000_ST1_DIPB_BIT BIT(24) +#define ADE9000_ST1_DIPC_BIT BIT(25) +#define ADE9000_ST1_ERROR0_BIT BIT(28) +#define ADE9000_ST1_ERROR1_BIT BIT(29) +#define ADE9000_ST1_ERROR2_BIT BIT(30) +#define ADE9000_ST1_ERROR3_BIT BIT(31) +#define ADE9000_ST1_CROSSING_FIRST 6 +#define ADE9000_ST1_CROSSING_DEPTH 25 + +#define ADE9000_WFB_TRG_DIP_BIT BIT(0) +#define ADE9000_WFB_TRG_SWELL_BIT BIT(1) +#define ADE9000_WFB_TRG_ZXIA_BIT BIT(3) +#define ADE9000_WFB_TRG_ZXIB_BIT BIT(4) +#define ADE9000_WFB_TRG_ZXIC_BIT BIT(5) +#define ADE9000_WFB_TRG_ZXVA_BIT BIT(6) +#define ADE9000_WFB_TRG_ZXVB_BIT BIT(7) +#define ADE9000_WFB_TRG_ZXVC_BIT BIT(8) + +/* Stop when waveform buffer is full */ +#define ADE9000_WFB_FULL_MODE 0x0 +/* Continuous fill—stop only on enabled trigger events */ +#define ADE9000_WFB_EN_TRIG_MODE 0x1 +/* Continuous filling—center capture around enabled trigger events */ +#define ADE9000_WFB_C_EN_TRIG_MODE 0x2 +/* Continuous fill—used as streaming mode for continuous data output */ +#define ADE9000_WFB_STREAMING_MODE 0x3 + +#define ADE9000_LAST_PAGE_BIT BIT(15) +#define ADE9000_MIDDLE_PAGE_BIT BIT(7) + +/* + * Full scale Codes referred from Datasheet. Respective digital codes are + * produced when ADC inputs are at full scale. + */ +#define ADE9000_RMS_FULL_SCALE_CODES 52866837 +#define ADE9000_WATT_FULL_SCALE_CODES 20694066 +#define ADE9000_PCF_FULL_SCALE_CODES 74770000 + +/* Phase and channel definitions */ +#define ADE9000_PHASE_A_NR 0 +#define ADE9000_PHASE_B_NR 1 +#define ADE9000_PHASE_C_NR 2 + +#define ADE9000_SCAN_POS_IA BIT(0) +#define ADE9000_SCAN_POS_VA BIT(1) +#define ADE9000_SCAN_POS_IB BIT(2) +#define ADE9000_SCAN_POS_VB BIT(3) +#define ADE9000_SCAN_POS_IC BIT(4) +#define ADE9000_SCAN_POS_VC BIT(5) + +/* Waveform buffer configuration values */ +enum ade9000_wfb_cfg { + ADE9000_WFB_CFG_ALL_CHAN = 0x0, + ADE9000_WFB_CFG_IA_VA = 0x1, + ADE9000_WFB_CFG_IB_VB = 0x2, + ADE9000_WFB_CFG_IC_VC = 0x3, + ADE9000_WFB_CFG_IA = 0x8, + ADE9000_WFB_CFG_VA = 0x9, + ADE9000_WFB_CFG_IB = 0xA, + ADE9000_WFB_CFG_VB = 0xB, + ADE9000_WFB_CFG_IC = 0xC, + ADE9000_WFB_CFG_VC = 0xD, +}; + +#define ADE9000_PHASE_B_POS_BIT BIT(5) +#define ADE9000_PHASE_C_POS_BIT BIT(6) + +#define ADE9000_MAX_PHASE_NR 3 + +/* + * Calculate register address for multi-phase device. + * Phase A (chan 0): base address + 0x00 + * Phase B (chan 1): base address + 0x20 + * Phase C (chan 2): base address + 0x40 + */ +#define ADE9000_ADDR_ADJUST(addr, chan) \ + (((chan) == 0 ? 0 : (chan) == 1 ? 2 : 4) << 4 | (addr)) + +struct ade9000_state { + struct completion reset_completion; + struct mutex lock; /* Protects SPI transactions */ + u8 wf_src; + u32 wfb_trg; + u8 wfb_nr_activ_chan; + u32 wfb_nr_samples; + struct spi_device *spi; + struct clk *clkin; + struct spi_transfer xfer[2]; + struct spi_message spi_msg; + struct regmap *regmap; + union{ + u8 byte[ADE9000_WFB_FULL_BUFF_SIZE]; + __be32 word[ADE9000_WFB_FULL_BUFF_NR_SAMPLES]; + } rx_buff __aligned(IIO_DMA_MINALIGN); + u8 tx_buff[2] __aligned(IIO_DMA_MINALIGN); + unsigned int bulk_read_buf[2]; +}; + +struct ade9000_irq1_event { + u32 bit_mask; + enum iio_chan_type chan_type; + u32 channel; + enum iio_event_type event_type; + enum iio_event_direction event_dir; +}; + +static const struct ade9000_irq1_event ade9000_irq1_events[] = { + { ADE9000_ST1_ZXVA_BIT, IIO_VOLTAGE, ADE9000_PHASE_A_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER }, + { ADE9000_ST1_ZXIA_BIT, IIO_CURRENT, ADE9000_PHASE_A_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER }, + { ADE9000_ST1_ZXVB_BIT, IIO_VOLTAGE, ADE9000_PHASE_B_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER }, + { ADE9000_ST1_ZXIB_BIT, IIO_CURRENT, ADE9000_PHASE_B_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER }, + { ADE9000_ST1_ZXVC_BIT, IIO_VOLTAGE, ADE9000_PHASE_C_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER }, + { ADE9000_ST1_ZXIC_BIT, IIO_CURRENT, ADE9000_PHASE_C_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER }, + { ADE9000_ST1_SWELLA_BIT, IIO_ALTVOLTAGE, ADE9000_PHASE_A_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING }, + { ADE9000_ST1_SWELLB_BIT, IIO_ALTVOLTAGE, ADE9000_PHASE_B_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING }, + { ADE9000_ST1_SWELLC_BIT, IIO_ALTVOLTAGE, ADE9000_PHASE_C_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING }, + { ADE9000_ST1_DIPA_BIT, IIO_ALTVOLTAGE, ADE9000_PHASE_A_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING }, + { ADE9000_ST1_DIPB_BIT, IIO_ALTVOLTAGE, ADE9000_PHASE_B_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING }, + { ADE9000_ST1_DIPC_BIT, IIO_ALTVOLTAGE, ADE9000_PHASE_C_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING }, +}; + +/* Voltage events (zero crossing on instantaneous voltage) */ +static const struct iio_event_spec ade9000_voltage_events[] = { + { + /* Zero crossing detection - datasheet: ZXV interrupts */ + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +/* Current events (zero crossing on instantaneous current) */ +static const struct iio_event_spec ade9000_current_events[] = { + { + /* Zero crossing detection - datasheet: ZXI interrupts */ + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +/* RMS voltage events (swell/sag detection on RMS values) */ +static const struct iio_event_spec ade9000_rms_voltage_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, /* RMS swell detection */ + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, /* RMS sag/dip detection */ + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | BIT(IIO_EV_INFO_VALUE), + }, +}; + +static const char * const ade9000_filter_type_items[] = { + "sinc4", "sinc4+lp", +}; + +static const int ade9000_filter_type_values[] = { + 0, 2, +}; + +static int ade9000_filter_type_get(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ade9000_state *st = iio_priv(indio_dev); + u32 val; + int ret; + unsigned int i; + + ret = regmap_read(st->regmap, ADE9000_REG_WFB_CFG, &val); + if (ret) + return ret; + + val = FIELD_GET(ADE9000_WF_SRC_MASK, val); + + for (i = 0; i < ARRAY_SIZE(ade9000_filter_type_values); i++) { + if (ade9000_filter_type_values[i] == val) + return i; + } + + return -EINVAL; +} + +static int ade9000_filter_type_set(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int index) +{ + struct ade9000_state *st = iio_priv(indio_dev); + int ret, val; + + if (index >= ARRAY_SIZE(ade9000_filter_type_values)) + return -EINVAL; + + val = ade9000_filter_type_values[index]; + + /* Update the WFB_CFG register with the new filter type */ + ret = regmap_update_bits(st->regmap, ADE9000_REG_WFB_CFG, + ADE9000_WF_SRC_MASK, + FIELD_PREP(ADE9000_WF_SRC_MASK, val)); + if (ret) + return ret; + + /* Update cached value */ + st->wf_src = val; + + return 0; +} + +static const struct iio_enum ade9000_filter_type_enum = { + .items = ade9000_filter_type_items, + .num_items = ARRAY_SIZE(ade9000_filter_type_items), + .get = ade9000_filter_type_get, + .set = ade9000_filter_type_set, +}; + +static const struct iio_chan_spec_ext_info ade9000_ext_info[] = { + IIO_ENUM("filter_type", IIO_SHARED_BY_ALL, &ade9000_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_ALL, &ade9000_filter_type_enum), + { } +}; + +#define ADE9000_CURRENT_CHANNEL(num) { \ + .type = IIO_CURRENT, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AI_PCF, num), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE), \ + .event_spec = ade9000_current_events, \ + .num_event_specs = ARRAY_SIZE(ade9000_current_events), \ + .scan_index = num, \ + .indexed = 1, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 32, \ + .storagebits = 32, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADE9000_VOLTAGE_CHANNEL(num) { \ + .type = IIO_VOLTAGE, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AV_PCF, num), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_FREQUENCY), \ + .event_spec = ade9000_voltage_events, \ + .num_event_specs = ARRAY_SIZE(ade9000_voltage_events), \ + .scan_index = num + 1, /* interleave with current channels */ \ + .indexed = 1, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 32, \ + .storagebits = 32, \ + .endianness = IIO_BE, \ + }, \ + .ext_info = ade9000_ext_info, \ +} + +#define ADE9000_ALTCURRENT_RMS_CHANNEL(num) { \ + .type = IIO_ALTCURRENT, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AIRMS, num), \ + .channel2 = IIO_MOD_RMS, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .scan_index = -1 \ +} + +#define ADE9000_ALTVOLTAGE_RMS_CHANNEL(num) { \ + .type = IIO_ALTVOLTAGE, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AVRMS, num), \ + .channel2 = IIO_MOD_RMS, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .event_spec = ade9000_rms_voltage_events, \ + .num_event_specs = ARRAY_SIZE(ade9000_rms_voltage_events), \ + .scan_index = -1 \ +} + +#define ADE9000_POWER_ACTIVE_CHANNEL(num) { \ + .type = IIO_POWER, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AWATT, num), \ + .channel2 = IIO_MOD_ACTIVE, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE), \ + .scan_index = -1 \ +} + +#define ADE9000_POWER_REACTIVE_CHANNEL(num) { \ + .type = IIO_POWER, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AVAR, num), \ + .channel2 = IIO_MOD_REACTIVE, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .scan_index = -1 \ +} + +#define ADE9000_POWER_APPARENT_CHANNEL(num) { \ + .type = IIO_POWER, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AVA, num), \ + .channel2 = IIO_MOD_APPARENT, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = -1 \ +} + + #define ADE9000_ENERGY_ACTIVE_CHANNEL(num, addr) { \ + .type = IIO_ENERGY, \ + .channel = num, \ + .address = addr, \ + .channel2 = IIO_MOD_ACTIVE, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .scan_index = -1 \ +} + +#define ADE9000_ENERGY_APPARENT_CHANNEL(num, addr) { \ + .type = IIO_ENERGY, \ + .channel = num, \ + .address = addr, \ + .channel2 = IIO_MOD_APPARENT, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .scan_index = -1 \ +} + +#define ADE9000_ENERGY_REACTIVE_CHANNEL(num, addr) { \ + .type = IIO_ENERGY, \ + .channel = num, \ + .address = addr, \ + .channel2 = IIO_MOD_REACTIVE, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .scan_index = -1 \ +} + +#define ADE9000_POWER_FACTOR_CHANNEL(num) { \ + .type = IIO_POWER, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_APF, num), \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_POWERFACTOR), \ + .scan_index = -1 \ +} + +static const struct iio_chan_spec ade9000_channels[] = { + /* Phase A channels */ + ADE9000_CURRENT_CHANNEL(ADE9000_PHASE_A_NR), + ADE9000_VOLTAGE_CHANNEL(ADE9000_PHASE_A_NR), + ADE9000_ALTCURRENT_RMS_CHANNEL(ADE9000_PHASE_A_NR), + ADE9000_ALTVOLTAGE_RMS_CHANNEL(ADE9000_PHASE_A_NR), + ADE9000_POWER_ACTIVE_CHANNEL(ADE9000_PHASE_A_NR), + ADE9000_POWER_REACTIVE_CHANNEL(ADE9000_PHASE_A_NR), + ADE9000_POWER_APPARENT_CHANNEL(ADE9000_PHASE_A_NR), + ADE9000_ENERGY_ACTIVE_CHANNEL(ADE9000_PHASE_A_NR, ADE9000_REG_AWATTHR_LO), + ADE9000_ENERGY_APPARENT_CHANNEL(ADE9000_PHASE_A_NR, ADE9000_REG_AVAHR_LO), + ADE9000_ENERGY_REACTIVE_CHANNEL(ADE9000_PHASE_A_NR, ADE9000_REG_AFVARHR_LO), + ADE9000_POWER_FACTOR_CHANNEL(ADE9000_PHASE_A_NR), + /* Phase B channels */ + ADE9000_CURRENT_CHANNEL(ADE9000_PHASE_B_NR), + ADE9000_VOLTAGE_CHANNEL(ADE9000_PHASE_B_NR), + ADE9000_ALTCURRENT_RMS_CHANNEL(ADE9000_PHASE_B_NR), + ADE9000_ALTVOLTAGE_RMS_CHANNEL(ADE9000_PHASE_B_NR), + ADE9000_POWER_ACTIVE_CHANNEL(ADE9000_PHASE_B_NR), + ADE9000_POWER_REACTIVE_CHANNEL(ADE9000_PHASE_B_NR), + ADE9000_POWER_APPARENT_CHANNEL(ADE9000_PHASE_B_NR), + ADE9000_ENERGY_ACTIVE_CHANNEL(ADE9000_PHASE_B_NR, ADE9000_REG_BWATTHR_LO), + ADE9000_ENERGY_APPARENT_CHANNEL(ADE9000_PHASE_B_NR, ADE9000_REG_BVAHR_LO), + ADE9000_ENERGY_REACTIVE_CHANNEL(ADE9000_PHASE_B_NR, ADE9000_REG_BFVARHR_LO), + ADE9000_POWER_FACTOR_CHANNEL(ADE9000_PHASE_B_NR), + /* Phase C channels */ + ADE9000_CURRENT_CHANNEL(ADE9000_PHASE_C_NR), + ADE9000_VOLTAGE_CHANNEL(ADE9000_PHASE_C_NR), + ADE9000_ALTCURRENT_RMS_CHANNEL(ADE9000_PHASE_C_NR), + ADE9000_ALTVOLTAGE_RMS_CHANNEL(ADE9000_PHASE_C_NR), + ADE9000_POWER_ACTIVE_CHANNEL(ADE9000_PHASE_C_NR), + ADE9000_POWER_REACTIVE_CHANNEL(ADE9000_PHASE_C_NR), + ADE9000_POWER_APPARENT_CHANNEL(ADE9000_PHASE_C_NR), + ADE9000_ENERGY_ACTIVE_CHANNEL(ADE9000_PHASE_C_NR, ADE9000_REG_CWATTHR_LO), + ADE9000_ENERGY_APPARENT_CHANNEL(ADE9000_PHASE_C_NR, ADE9000_REG_CVAHR_LO), + ADE9000_ENERGY_REACTIVE_CHANNEL(ADE9000_PHASE_C_NR, ADE9000_REG_CFVARHR_LO), + ADE9000_POWER_FACTOR_CHANNEL(ADE9000_PHASE_C_NR), +}; + +static const struct reg_sequence ade9000_initialization_sequence[] = { + { ADE9000_REG_PGA_GAIN, ADE9000_PGA_GAIN }, + { ADE9000_REG_CONFIG0, ADE9000_CONFIG0 }, + { ADE9000_REG_CONFIG1, ADE9000_CONFIG1 }, + { ADE9000_REG_CONFIG2, ADE9000_CONFIG2 }, + { ADE9000_REG_CONFIG3, ADE9000_CONFIG3 }, + { ADE9000_REG_ACCMODE, ADE9000_ACCMODE }, + { ADE9000_REG_ZX_LP_SEL, ADE9000_ZX_LP_SEL }, + { ADE9000_REG_MASK0, ADE9000_MASK0_ALL_INT_DIS }, + { ADE9000_REG_MASK1, ADE9000_MASK1_ALL_INT_DIS }, + { ADE9000_REG_EVENT_MASK, ADE9000_EVENT_DISABLE }, + { ADE9000_REG_WFB_CFG, ADE9000_WFB_CFG }, + { ADE9000_REG_VLEVEL, ADE9000_VLEVEL }, + { ADE9000_REG_DICOEFF, ADE9000_DICOEFF }, + { ADE9000_REG_EGY_TIME, ADE9000_EGY_TIME }, + { ADE9000_REG_EP_CFG, ADE9000_EP_CFG }, + /* Clear all pending status bits by writing 1s */ + { ADE9000_REG_STATUS0, GENMASK(31, 0) }, + { ADE9000_REG_STATUS1, GENMASK(31, 0) }, + { ADE9000_REG_RUN, ADE9000_RUN_ON } +}; + +static int ade9000_spi_write_reg(void *context, unsigned int reg, + unsigned int val) +{ + struct ade9000_state *st = context; + u8 tx_buf[6]; + u16 addr; + int ret, len; + + guard(mutex)(&st->lock); + + addr = FIELD_PREP(ADE9000_REG_ADDR_MASK, reg); + put_unaligned_be16(addr, tx_buf); + + if (reg > ADE9000_REG_RUN && reg < ADE9000_REG_VERSION) { + put_unaligned_be16(val, &tx_buf[2]); + len = 4; + } else { + put_unaligned_be32(val, &tx_buf[2]); + len = 6; + } + + ret = spi_write_then_read(st->spi, tx_buf, len, NULL, 0); + if (ret) + dev_err(&st->spi->dev, "problem when writing register 0x%x\n", reg); + + return ret; +} + +static int ade9000_spi_read_reg(void *context, unsigned int reg, + unsigned int *val) +{ + struct ade9000_state *st = context; + u8 tx_buf[2]; + u8 rx_buf[4]; + u16 addr; + int ret, rx_len; + + guard(mutex)(&st->lock); + + addr = FIELD_PREP(ADE9000_REG_ADDR_MASK, reg) | + ADE9000_REG_READ_BIT_MASK; + + put_unaligned_be16(addr, tx_buf); + + /* Skip CRC bytes - only read actual data */ + if (reg > ADE9000_REG_RUN && reg < ADE9000_REG_VERSION) + rx_len = 2; + else + rx_len = 4; + + ret = spi_write_then_read(st->spi, tx_buf, 2, rx_buf, rx_len); + if (ret) { + dev_err(&st->spi->dev, "error reading register 0x%x\n", reg); + return ret; + } + + if (reg > ADE9000_REG_RUN && reg < ADE9000_REG_VERSION) + *val = get_unaligned_be16(rx_buf); + else + *val = get_unaligned_be32(rx_buf); + + return 0; +} + +static bool ade9000_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + /* Interrupt/error status registers - volatile */ + case ADE9000_REG_STATUS0: + case ADE9000_REG_STATUS1: + return true; + default: + /* All other registers are non-volatile */ + return false; + } +} + +static void ade9000_configure_scan(struct iio_dev *indio_dev, u32 wfb_addr) +{ + struct ade9000_state *st = iio_priv(indio_dev); + u16 addr; + + addr = FIELD_PREP(ADE9000_REG_ADDR_MASK, wfb_addr) | + ADE9000_REG_READ_BIT_MASK; + + put_unaligned_be16(addr, st->tx_buff); + + st->xfer[0].tx_buf = &st->tx_buff[0]; + st->xfer[0].len = 2; + + st->xfer[1].rx_buf = st->rx_buff.byte; + + /* Always use streaming mode */ + st->xfer[1].len = (st->wfb_nr_samples / 2) * 4; + + spi_message_init_with_transfers(&st->spi_msg, st->xfer, ARRAY_SIZE(st->xfer)); +} + +static int ade9000_iio_push_streaming(struct iio_dev *indio_dev) +{ + struct ade9000_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + u32 current_page, i; + int ret; + + guard(mutex)(&st->lock); + + ret = spi_sync(st->spi, &st->spi_msg); + if (ret) { + dev_err_ratelimited(dev, "SPI fail in trigger handler\n"); + return ret; + } + + /* In streaming mode, only half the buffer is filled per interrupt */ + for (i = 0; i < st->wfb_nr_samples / 2; i += st->wfb_nr_activ_chan) + iio_push_to_buffers(indio_dev, &st->rx_buff.word[i]); + + ret = regmap_read(st->regmap, ADE9000_REG_WFB_PG_IRQEN, ¤t_page); + if (ret) { + dev_err_ratelimited(dev, "IRQ0 WFB read fail\n"); + return ret; + } + + if (current_page & ADE9000_MIDDLE_PAGE_BIT) { + ret = regmap_write(st->regmap, ADE9000_REG_WFB_PG_IRQEN, + ADE9000_LAST_PAGE_BIT); + if (ret) { + dev_err_ratelimited(dev, "IRQ0 WFB write fail\n"); + return ret; + } + + ade9000_configure_scan(indio_dev, + ADE9000_REG_WF_HALF_BUFF); + } else { + ret = regmap_write(st->regmap, ADE9000_REG_WFB_PG_IRQEN, + ADE9000_MIDDLE_PAGE_BIT); + if (ret) { + dev_err_ratelimited(dev, "IRQ0 WFB write fail"); + return ret; + } + + ade9000_configure_scan(indio_dev, ADE9000_REG_WF_BUFF); + } + + return 0; +} + +static int ade9000_iio_push_buffer(struct iio_dev *indio_dev) +{ + struct ade9000_state *st = iio_priv(indio_dev); + int ret; + u32 i; + + guard(mutex)(&st->lock); + + ret = spi_sync(st->spi, &st->spi_msg); + if (ret) { + dev_err_ratelimited(&st->spi->dev, + "SPI fail in trigger handler\n"); + return ret; + } + + for (i = 0; i < st->wfb_nr_samples; i += st->wfb_nr_activ_chan) + iio_push_to_buffers(indio_dev, &st->rx_buff.word[i]); + + return 0; +} + +static irqreturn_t ade9000_irq0_thread(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct ade9000_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + u32 handled_irq = 0; + u32 interrupts, status; + int ret; + + ret = regmap_read(st->regmap, ADE9000_REG_STATUS0, &status); + if (ret) { + dev_err_ratelimited(dev, "IRQ0 read status fail\n"); + return IRQ_HANDLED; + } + + ret = regmap_read(st->regmap, ADE9000_REG_MASK0, &interrupts); + if (ret) { + dev_err_ratelimited(dev, "IRQ0 read mask fail\n"); + return IRQ_HANDLED; + } + + if ((status & ADE9000_ST0_PAGE_FULL_BIT) && + (interrupts & ADE9000_ST0_PAGE_FULL_BIT)) { + /* Always use streaming mode */ + ret = ade9000_iio_push_streaming(indio_dev); + if (ret) { + dev_err_ratelimited(dev, "IRQ0 IIO push fail\n"); + return IRQ_HANDLED; + } + + handled_irq |= ADE9000_ST0_PAGE_FULL_BIT; + } + + if ((status & ADE9000_ST0_WFB_TRIG_BIT) && + (interrupts & ADE9000_ST0_WFB_TRIG_BIT)) { + ret = regmap_update_bits(st->regmap, ADE9000_REG_WFB_CFG, + ADE9000_WF_CAP_EN_MASK, 0); + if (ret) { + dev_err_ratelimited(dev, "IRQ0 WFB fail\n"); + return IRQ_HANDLED; + } + + if (iio_buffer_enabled(indio_dev)) { + ret = ade9000_iio_push_buffer(indio_dev); + if (ret) { + dev_err_ratelimited(dev, + "IRQ0 IIO push fail @ WFB TRIG\n"); + return IRQ_HANDLED; + } + } + + handled_irq |= ADE9000_ST0_WFB_TRIG_BIT; + } + + ret = regmap_write(st->regmap, ADE9000_REG_STATUS0, handled_irq); + if (ret) + dev_err_ratelimited(dev, "IRQ0 write status fail\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t ade9000_irq1_thread(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct ade9000_state *st = iio_priv(indio_dev); + unsigned int bit = ADE9000_ST1_CROSSING_FIRST; + s64 timestamp = iio_get_time_ns(indio_dev); + u32 handled_irq = 0; + u32 interrupts, result, status, tmp; + DECLARE_BITMAP(interrupt_bits, ADE9000_ST1_CROSSING_DEPTH); + const struct ade9000_irq1_event *event; + int ret, i; + + if (!completion_done(&st->reset_completion)) { + ret = regmap_read(st->regmap, ADE9000_REG_STATUS1, &result); + if (ret) { + dev_err_ratelimited(&st->spi->dev, "IRQ1 read status fail\n"); + return IRQ_HANDLED; + } + + if (result & ADE9000_ST1_RSTDONE_BIT) { + complete(&st->reset_completion); + /* Clear the reset done status bit */ + ret = regmap_write(st->regmap, ADE9000_REG_STATUS1, ADE9000_ST1_RSTDONE_BIT); + if (ret) + dev_err_ratelimited(&st->spi->dev, + "IRQ1 clear reset status fail\n"); + } else { + dev_err_ratelimited(&st->spi->dev, + "Error testing reset done\n"); + } + + return IRQ_HANDLED; + } + + ret = regmap_read(st->regmap, ADE9000_REG_STATUS1, &status); + if (ret) { + dev_err_ratelimited(&st->spi->dev, "IRQ1 read status fail\n"); + return IRQ_HANDLED; + } + + ret = regmap_read(st->regmap, ADE9000_REG_MASK1, &interrupts); + if (ret) { + dev_err_ratelimited(&st->spi->dev, "IRQ1 read mask fail\n"); + return IRQ_HANDLED; + } + + bitmap_from_arr32(interrupt_bits, &interrupts, ADE9000_ST1_CROSSING_DEPTH); + for_each_set_bit_from(bit, interrupt_bits, + ADE9000_ST1_CROSSING_DEPTH) { + tmp = status & BIT(bit); + if (!tmp) + continue; + + event = NULL; + + /* Find corresponding event in lookup table */ + for (i = 0; i < ARRAY_SIZE(ade9000_irq1_events); i++) { + if (ade9000_irq1_events[i].bit_mask == tmp) { + event = &ade9000_irq1_events[i]; + break; + } + } + + if (event) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(event->chan_type, + event->channel, + event->event_type, + event->event_dir), + timestamp); + } + handled_irq |= tmp; + } + + ret = regmap_write(st->regmap, ADE9000_REG_STATUS1, handled_irq); + if (ret) + dev_err_ratelimited(&st->spi->dev, "IRQ1 write status fail\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t ade9000_dready_thread(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + + /* Handle data ready interrupt from C4/EVENT/DREADY pin */ + if (iio_device_try_claim_buffer_mode(indio_dev)) { + ade9000_iio_push_buffer(indio_dev); + iio_device_release_buffer_mode(indio_dev); + } + + return IRQ_HANDLED; +} + +static int ade9000_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct ade9000_state *st = iio_priv(indio_dev); + unsigned int measured; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_FREQUENCY: + if (chan->type == IIO_VOLTAGE) { + int period_reg; + int period; + + switch (chan->channel) { + case ADE9000_PHASE_A_NR: + period_reg = ADE9000_REG_APERIOD; + break; + case ADE9000_PHASE_B_NR: + period_reg = ADE9000_REG_BPERIOD; + break; + case ADE9000_PHASE_C_NR: + period_reg = ADE9000_REG_CPERIOD; + break; + default: + return -EINVAL; + } + ret = regmap_read(st->regmap, period_reg, &period); + if (ret) + return ret; + /* + * Frequency = (4MHz * 65536) / (PERIOD + 1) + * 4MHz = ADC sample rate, 65536 = 2^16 period register scaling + * See ADE9000 datasheet section on period measurement + */ + *val = 4000 * 65536; + *val2 = period + 1; + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_ENERGY) { + u16 lo_reg = chan->address; + + ret = regmap_bulk_read(st->regmap, lo_reg, + st->bulk_read_buf, 2); + if (ret) + return ret; + + *val = st->bulk_read_buf[0]; /* Lower 32 bits */ + *val2 = st->bulk_read_buf[1]; /* Upper 32 bits */ + return IIO_VAL_INT_64; + } + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = regmap_read(st->regmap, chan->address, &measured); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + + *val = measured; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_POWERFACTOR: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = regmap_read(st->regmap, chan->address, &measured); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + + *val = measured; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_CURRENT: + case IIO_VOLTAGE: + case IIO_ALTVOLTAGE: + case IIO_ALTCURRENT: + switch (chan->address) { + case ADE9000_REG_AI_PCF: + case ADE9000_REG_AV_PCF: + case ADE9000_REG_BI_PCF: + case ADE9000_REG_BV_PCF: + case ADE9000_REG_CI_PCF: + case ADE9000_REG_CV_PCF: + *val = 1; + *val2 = ADE9000_PCF_FULL_SCALE_CODES; + return IIO_VAL_FRACTIONAL; + case ADE9000_REG_AIRMS: + case ADE9000_REG_AVRMS: + case ADE9000_REG_BIRMS: + case ADE9000_REG_BVRMS: + case ADE9000_REG_CIRMS: + case ADE9000_REG_CVRMS: + *val = 1; + *val2 = ADE9000_RMS_FULL_SCALE_CODES; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_POWER: + *val = 1; + *val2 = ADE9000_WATT_FULL_SCALE_CODES; + return IIO_VAL_FRACTIONAL; + default: + break; + } + + return -EINVAL; + default: + return -EINVAL; + } +} + +static int ade9000_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ade9000_state *st = iio_priv(indio_dev); + u32 tmp; + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_CURRENT: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AIRMSOS, + chan->channel), val); + case IIO_VOLTAGE: + case IIO_ALTVOLTAGE: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AVRMSOS, + chan->channel), val); + case IIO_POWER: + tmp = chan->address; + tmp &= ~ADE9000_PHASE_B_POS_BIT; + tmp &= ~ADE9000_PHASE_C_POS_BIT; + + switch (tmp) { + case ADE9000_REG_AWATT: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AWATTOS, + chan->channel), val); + case ADE9000_REG_AVAR: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AVAROS, + chan->channel), val); + case ADE9000_REG_AFVAR: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AFVAROS, + chan->channel), val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBSCALE: + /* + * Calibration gain registers for fine-tuning measurements. + * These are separate from PGA gain and applied in the digital domain. + */ + switch (chan->type) { + case IIO_CURRENT: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AIGAIN, + chan->channel), val); + case IIO_VOLTAGE: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AVGAIN, + chan->channel), val); + case IIO_POWER: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_APGAIN, + chan->channel), val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + /* Per-channel scales are read-only */ + return -EINVAL; + default: + return -EINVAL; + } +} + +static int ade9000_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int tx_val, + unsigned int *rx_val) +{ + struct ade9000_state *st = iio_priv(indio_dev); + + if (rx_val) + return regmap_read(st->regmap, reg, rx_val); + + return regmap_write(st->regmap, reg, tx_val); +} + +static int ade9000_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct ade9000_state *st = iio_priv(indio_dev); + u32 interrupts1; + int ret; + + /* All events use MASK1 register */ + ret = regmap_read(st->regmap, ADE9000_REG_MASK1, &interrupts1); + if (ret) + return ret; + + switch (chan->channel) { + case ADE9000_PHASE_A_NR: + if (chan->type == IIO_VOLTAGE && dir == IIO_EV_DIR_EITHER) + return !!(interrupts1 & ADE9000_ST1_ZXVA_BIT); + else if (chan->type == IIO_CURRENT && dir == IIO_EV_DIR_EITHER) + return !!(interrupts1 & ADE9000_ST1_ZXIA_BIT); + else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_RISING) + return !!(interrupts1 & ADE9000_ST1_SWELLA_BIT); + else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_FALLING) + return !!(interrupts1 & ADE9000_ST1_DIPA_BIT); + dev_err_ratelimited(&indio_dev->dev, + "Invalid channel type %d or direction %d for phase A\n", chan->type, dir); + return -EINVAL; + case ADE9000_PHASE_B_NR: + if (chan->type == IIO_VOLTAGE && dir == IIO_EV_DIR_EITHER) + return !!(interrupts1 & ADE9000_ST1_ZXVB_BIT); + else if (chan->type == IIO_CURRENT && dir == IIO_EV_DIR_EITHER) + return !!(interrupts1 & ADE9000_ST1_ZXIB_BIT); + else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_RISING) + return !!(interrupts1 & ADE9000_ST1_SWELLB_BIT); + else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_FALLING) + return !!(interrupts1 & ADE9000_ST1_DIPB_BIT); + dev_err_ratelimited(&indio_dev->dev, + "Invalid channel type %d or direction %d for phase B\n", chan->type, dir); + return -EINVAL; + case ADE9000_PHASE_C_NR: + if (chan->type == IIO_VOLTAGE && dir == IIO_EV_DIR_EITHER) + return !!(interrupts1 & ADE9000_ST1_ZXVC_BIT); + else if (chan->type == IIO_CURRENT && dir == IIO_EV_DIR_EITHER) + return !!(interrupts1 & ADE9000_ST1_ZXIC_BIT); + else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_RISING) + return !!(interrupts1 & ADE9000_ST1_SWELLC_BIT); + else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_FALLING) + return !!(interrupts1 & ADE9000_ST1_DIPC_BIT); + dev_err_ratelimited(&indio_dev->dev, + "Invalid channel type %d or direction %d for phase C\n", chan->type, dir); + return -EINVAL; + default: + return -EINVAL; + } +} + +static int ade9000_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct ade9000_state *st = iio_priv(indio_dev); + u32 bit_mask; + int ret; + + /* Clear all pending events in STATUS1 register (write 1 to clear) */ + ret = regmap_write(st->regmap, ADE9000_REG_STATUS1, GENMASK(31, 0)); + if (ret) + return ret; + + /* Determine which interrupt bit to enable/disable */ + switch (chan->channel) { + case ADE9000_PHASE_A_NR: + if (chan->type == IIO_VOLTAGE && dir == IIO_EV_DIR_EITHER) { + bit_mask = ADE9000_ST1_ZXVA_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_ZXVA_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_ZXVA_BIT; + } else if (chan->type == IIO_CURRENT && dir == IIO_EV_DIR_EITHER) { + bit_mask = ADE9000_ST1_ZXIA_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_ZXIA_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_ZXIA_BIT; + } else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_RISING) { + bit_mask = ADE9000_ST1_SWELLA_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_SWELL_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_SWELL_BIT; + } else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_FALLING) { + bit_mask = ADE9000_ST1_DIPA_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_DIP_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_DIP_BIT; + } else { + dev_err_ratelimited(&indio_dev->dev, "Invalid channel type %d or direction %d for phase A\n", + chan->type, dir); + return -EINVAL; + } + break; + case ADE9000_PHASE_B_NR: + if (chan->type == IIO_VOLTAGE && dir == IIO_EV_DIR_EITHER) { + bit_mask = ADE9000_ST1_ZXVB_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_ZXVB_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_ZXVB_BIT; + } else if (chan->type == IIO_CURRENT && dir == IIO_EV_DIR_EITHER) { + bit_mask = ADE9000_ST1_ZXIB_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_ZXIB_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_ZXIB_BIT; + } else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_RISING) { + bit_mask = ADE9000_ST1_SWELLB_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_SWELL_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_SWELL_BIT; + } else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_FALLING) { + bit_mask = ADE9000_ST1_DIPB_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_DIP_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_DIP_BIT; + } else { + dev_err_ratelimited(&indio_dev->dev, + "Invalid channel type %d or direction %d for phase B\n", + chan->type, dir); + return -EINVAL; + } + break; + case ADE9000_PHASE_C_NR: + if (chan->type == IIO_VOLTAGE && dir == IIO_EV_DIR_EITHER) { + bit_mask = ADE9000_ST1_ZXVC_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_ZXVC_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_ZXVC_BIT; + } else if (chan->type == IIO_CURRENT && dir == IIO_EV_DIR_EITHER) { + bit_mask = ADE9000_ST1_ZXIC_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_ZXIC_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_ZXIC_BIT; + } else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_RISING) { + bit_mask = ADE9000_ST1_SWELLC_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_SWELL_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_SWELL_BIT; + } else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_FALLING) { + bit_mask = ADE9000_ST1_DIPC_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_DIP_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_DIP_BIT; + } else { + dev_err_ratelimited(&indio_dev->dev, + "Invalid channel type %d or direction %d for phase C\n", + chan->type, dir); + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + /* Set bits if enabling event, clear bits if disabling */ + return regmap_assign_bits(st->regmap, ADE9000_REG_MASK1, bit_mask, state ? bit_mask : 0); +} + +static int ade9000_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct ade9000_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_FALLING: + return regmap_write(st->regmap, ADE9000_REG_DIP_LVL, val); + case IIO_EV_DIR_RISING: + return regmap_write(st->regmap, ADE9000_REG_SWELL_LVL, val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ade9000_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct ade9000_state *st = iio_priv(indio_dev); + unsigned int data; + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_FALLING: + ret = regmap_read(st->regmap, ADE9000_REG_DIP_LVL, &data); + if (ret) + return ret; + *val = data; + return IIO_VAL_INT; + case IIO_EV_DIR_RISING: + ret = regmap_read(st->regmap, ADE9000_REG_SWELL_LVL, &data); + if (ret) + return ret; + *val = data; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ade9000_waveform_buffer_config(struct iio_dev *indio_dev) +{ + struct ade9000_state *st = iio_priv(indio_dev); + u32 wfb_cfg_val; + u32 active_scans; + + bitmap_to_arr32(&active_scans, indio_dev->active_scan_mask, + iio_get_masklength(indio_dev)); + + switch (active_scans) { + case ADE9000_SCAN_POS_IA | ADE9000_SCAN_POS_VA: + wfb_cfg_val = ADE9000_WFB_CFG_IA_VA; + st->wfb_nr_activ_chan = 2; + break; + case ADE9000_SCAN_POS_IB | ADE9000_SCAN_POS_VB: + wfb_cfg_val = ADE9000_WFB_CFG_IB_VB; + st->wfb_nr_activ_chan = 2; + break; + case ADE9000_SCAN_POS_IC | ADE9000_SCAN_POS_VC: + wfb_cfg_val = ADE9000_WFB_CFG_IC_VC; + st->wfb_nr_activ_chan = 2; + break; + case ADE9000_SCAN_POS_IA: + wfb_cfg_val = ADE9000_WFB_CFG_IA; + st->wfb_nr_activ_chan = 1; + break; + case ADE9000_SCAN_POS_VA: + wfb_cfg_val = ADE9000_WFB_CFG_VA; + st->wfb_nr_activ_chan = 1; + break; + case ADE9000_SCAN_POS_IB: + wfb_cfg_val = ADE9000_WFB_CFG_IB; + st->wfb_nr_activ_chan = 1; + break; + case ADE9000_SCAN_POS_VB: + wfb_cfg_val = ADE9000_WFB_CFG_VB; + st->wfb_nr_activ_chan = 1; + break; + case ADE9000_SCAN_POS_IC: + wfb_cfg_val = ADE9000_WFB_CFG_IC; + st->wfb_nr_activ_chan = 1; + break; + case ADE9000_SCAN_POS_VC: + wfb_cfg_val = ADE9000_WFB_CFG_VC; + st->wfb_nr_activ_chan = 1; + break; + case (ADE9000_SCAN_POS_IA | ADE9000_SCAN_POS_VA | ADE9000_SCAN_POS_IB | + ADE9000_SCAN_POS_VB | ADE9000_SCAN_POS_IC | ADE9000_SCAN_POS_VC): + wfb_cfg_val = ADE9000_WFB_CFG_ALL_CHAN; + st->wfb_nr_activ_chan = 6; + break; + default: + dev_err(&st->spi->dev, "Unsupported combination of scans\n"); + return -EINVAL; + } + + wfb_cfg_val |= FIELD_PREP(ADE9000_WF_SRC_MASK, st->wf_src); + + return regmap_write(st->regmap, ADE9000_REG_WFB_CFG, wfb_cfg_val); +} + +static int ade9000_waveform_buffer_interrupt_setup(struct ade9000_state *st) +{ + int ret; + + ret = regmap_write(st->regmap, ADE9000_REG_WFB_TRG_CFG, 0x0); + if (ret) + return ret; + + /* Always use streaming mode setup */ + ret = regmap_write(st->regmap, ADE9000_REG_WFB_PG_IRQEN, + ADE9000_MIDDLE_PAGE_BIT); + if (ret) + return ret; + + ret = regmap_write(st->regmap, ADE9000_REG_STATUS0, GENMASK(31, 0)); + if (ret) + return ret; + + return regmap_set_bits(st->regmap, ADE9000_REG_MASK0, + ADE9000_ST0_PAGE_FULL_BIT); +} + +static int ade9000_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ade9000_state *st = iio_priv(indio_dev); + int ret; + + ret = ade9000_waveform_buffer_config(indio_dev); + if (ret) + return ret; + + st->wfb_nr_samples = ADE9000_WFB_MAX_SAMPLES_CHAN * st->wfb_nr_activ_chan; + + ade9000_configure_scan(indio_dev, ADE9000_REG_WF_BUFF); + + ret = ade9000_waveform_buffer_interrupt_setup(st); + if (ret) + return ret; + + ret = regmap_set_bits(st->regmap, ADE9000_REG_WFB_CFG, + ADE9000_WF_CAP_EN_MASK); + if (ret) { + dev_err(&st->spi->dev, "Post-enable waveform buffer enable fail\n"); + return ret; + } + + return 0; +} + +static int ade9000_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ade9000_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + u32 interrupts; + int ret; + + ret = regmap_clear_bits(st->regmap, ADE9000_REG_WFB_CFG, + ADE9000_WF_CAP_EN_MASK); + if (ret) { + dev_err(dev, "Post-disable waveform buffer disable fail\n"); + return ret; + } + + ret = regmap_write(st->regmap, ADE9000_REG_WFB_TRG_CFG, 0x0); + if (ret) + return ret; + + interrupts = ADE9000_ST0_WFB_TRIG_BIT | ADE9000_ST0_PAGE_FULL_BIT; + + ret = regmap_clear_bits(st->regmap, ADE9000_REG_MASK0, interrupts); + if (ret) { + dev_err(dev, "Post-disable update mask0 fail\n"); + return ret; + } + + return regmap_write(st->regmap, ADE9000_REG_STATUS0, GENMASK(31, 0)); +} + +static const struct iio_buffer_setup_ops ade9000_buffer_ops = { + .preenable = &ade9000_buffer_preenable, + .postdisable = &ade9000_buffer_postdisable, +}; + +static int ade9000_reset(struct ade9000_state *st) +{ + struct device *dev = &st->spi->dev; + struct gpio_desc *gpio_reset; + int ret; + + gpio_reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(gpio_reset)) + return PTR_ERR(gpio_reset); + + /* Software reset via register if no GPIO available */ + if (!gpio_reset) { + ret = regmap_set_bits(st->regmap, ADE9000_REG_CONFIG1, + ADE9000_SWRST_BIT); + if (ret) + return ret; + fsleep(90); + return 0; + } + + /* Hardware reset via GPIO */ + fsleep(10); + gpiod_set_value_cansleep(gpio_reset, 0); + fsleep(50000); + + /* Only wait for completion if IRQ1 is available to signal reset done */ + if (fwnode_irq_get_byname(dev_fwnode(dev), "irq1") >= 0) { + if (!wait_for_completion_timeout(&st->reset_completion, + msecs_to_jiffies(1000))) + return dev_err_probe(dev, -ETIMEDOUT, + "Reset timeout after 1s\n"); + } + /* If no IRQ available, reset is already complete after the 50ms delay above */ + + return 0; +} + +static int ade9000_setup(struct ade9000_state *st) +{ + struct device *dev = &st->spi->dev; + int ret; + + ret = regmap_multi_reg_write(st->regmap, ade9000_initialization_sequence, + ARRAY_SIZE(ade9000_initialization_sequence)); + if (ret) + return dev_err_probe(dev, ret, "Failed to write register sequence"); + + fsleep(2000); + + return 0; +} + +static const struct iio_info ade9000_info = { + .read_raw = ade9000_read_raw, + .write_raw = ade9000_write_raw, + .debugfs_reg_access = ade9000_reg_access, + .write_event_config = ade9000_write_event_config, + .read_event_config = ade9000_read_event_config, + .write_event_value = ade9000_write_event_value, + .read_event_value = ade9000_read_event_value, +}; + +static const struct regmap_config ade9000_regmap_config = { + .reg_bits = 16, + .val_bits = 32, + .max_register = 0x6bc, + .zero_flag_mask = true, + .cache_type = REGCACHE_MAPLE, + .reg_read = ade9000_spi_read_reg, + .reg_write = ade9000_spi_write_reg, + .volatile_reg = ade9000_is_volatile_reg, +}; + +static int ade9000_setup_clkout(struct device *dev, struct ade9000_state *st) +{ + struct clk_hw *clkout_hw; + int ret; + + if (!IS_ENABLED(CONFIG_COMMON_CLK)) + return 0; + + /* + * Only provide clock output when using external CMOS clock. + * When using crystal, CLKOUT is connected to crystal and shouldn't + * be used as clock provider for other devices. + */ + if (!device_property_present(dev, "#clock-cells") || !st->clkin) + return 0; + + /* CLKOUT passes through CLKIN with divider of 1 */ + clkout_hw = devm_clk_hw_register_divider(dev, "clkout", __clk_get_name(st->clkin), + CLK_SET_RATE_PARENT, NULL, 0, 1, 0, NULL); + if (IS_ERR(clkout_hw)) + return dev_err_probe(dev, PTR_ERR(clkout_hw), "Failed to register clkout"); + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, clkout_hw); + if (ret) + return dev_err_probe(dev, ret, "Failed to add clock provider"); + + return 0; +} + +static int ade9000_request_irq(struct device *dev, const char *name, + irq_handler_t handler, void *dev_id) +{ + int irq, ret; + + irq = fwnode_irq_get_byname(dev_fwnode(dev), name); + if (irq == -EINVAL) + return 0; /* interrupts are optional */ + if (irq < 0) + return dev_err_probe(dev, irq, "Failed to get %s irq", name); + + ret = devm_request_threaded_irq(dev, irq, NULL, handler, + IRQF_ONESHOT, KBUILD_MODNAME, dev_id); + if (ret) + return dev_err_probe(dev, ret, "Failed to request %s irq", name); + + return 0; +} + +static int ade9000_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ade9000_state *st; + struct regmap *regmap; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + regmap = devm_regmap_init(dev, NULL, st, &ade9000_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Unable to allocate ADE9000 regmap"); + + st->regmap = regmap; + st->spi = spi; + + init_completion(&st->reset_completion); + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + ret = ade9000_request_irq(dev, "irq0", ade9000_irq0_thread, indio_dev); + if (ret) + return ret; + + ret = ade9000_request_irq(dev, "irq1", ade9000_irq1_thread, indio_dev); + if (ret) + return ret; + + ret = ade9000_request_irq(dev, "dready", ade9000_dready_thread, indio_dev); + if (ret) + return ret; + + /* External CMOS clock input (optional - crystal can be used instead) */ + st->clkin = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(st->clkin)) + return dev_err_probe(dev, PTR_ERR(st->clkin), "Failed to get and enable clkin"); + + ret = ade9000_setup_clkout(dev, st); + if (ret) + return ret; + + indio_dev->name = "ade9000"; + indio_dev->info = &ade9000_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->setup_ops = &ade9000_buffer_ops; + + ret = devm_regulator_get_enable(&spi->dev, "vdd"); + if (ret) + return dev_err_probe(&spi->dev, ret, + "Failed to get and enable vdd regulator\n"); + + indio_dev->channels = ade9000_channels; + indio_dev->num_channels = ARRAY_SIZE(ade9000_channels); + + ret = devm_iio_kfifo_buffer_setup(dev, indio_dev, + &ade9000_buffer_ops); + if (ret) + return dev_err_probe(dev, ret, "Failed to setup IIO buffer"); + + ret = ade9000_reset(st); + if (ret) + return ret; + + /* Configure reference selection if vref regulator is available */ + ret = devm_regulator_get_enable_optional(dev, "vref"); + if (ret != -ENODEV && ret >= 0) { + ret = regmap_set_bits(st->regmap, ADE9000_REG_CONFIG1, + ADE9000_EXT_REF_MASK); + if (ret) + return ret; + } else if (ret < 0 && ret != -ENODEV) { + return dev_err_probe(dev, ret, + "Failed to get and enable vref regulator\n"); + } + + ret = ade9000_setup(st); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +}; + +static const struct spi_device_id ade9000_id[] = { + { .name = "ade9000" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ade9000_id); + +static const struct of_device_id ade9000_of_match[] = { + { .compatible = "adi,ade9000" }, + { } +}; +MODULE_DEVICE_TABLE(of, ade9000_of_match); + +static struct spi_driver ade9000_driver = { + .driver = { + .name = "ade9000", + .of_match_table = ade9000_of_match, + }, + .probe = ade9000_probe, + .id_table = ade9000_id, +}; +module_spi_driver(ade9000_driver); + +MODULE_AUTHOR("Antoniu Miclaus "); +MODULE_DESCRIPTION("Analog Devices ADE9000"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c new file mode 100644 index 000000000..589f03618 --- /dev/null +++ b/drivers/iio/adc/adi-axi-adc.c @@ -0,0 +1,842 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices Generic AXI ADC IP core + * Link: https://wiki.analog.com/resources/fpga/docs/axi_adc_ip + * + * Copyright 2012-2020 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ad7606_bus_iface.h" +/* + * Register definitions: + * https://wiki.analog.com/resources/fpga/docs/axi_adc_ip#register_map + */ + +/* ADC controls */ + +#define ADI_AXI_REG_RSTN 0x0040 +#define ADI_AXI_REG_RSTN_CE_N BIT(2) +#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_NUM_LANES_MSK GENMASK(12, 8) +#define ADI_AXI_ADC_CTRL_SYNC_MSK BIT(3) +#define ADI_AXI_ADC_CTRL_DDR_EDGESEL_MASK BIT(1) + +#define ADI_AXI_ADC_REG_CNTRL_3 0x004c +#define AXI_AD485X_CNTRL_3_OS_EN_MSK BIT(2) +#define AXI_AD485X_CNTRL_3_PACKET_FORMAT_MSK GENMASK(1, 0) +#define AXI_AD485X_PACKET_FORMAT_20BIT 0x0 +#define AXI_AD485X_PACKET_FORMAT_24BIT 0x1 +#define AXI_AD485X_PACKET_FORMAT_32BIT 0x2 +#define AXI_AD408X_CNTRL_3_FILTER_EN_MSK BIT(0) +#define AXI_AD408X_CNTRL_3_PACKET_FORMAT_MSK GENMASK(3, 2) +#define AXI_AD408X_PACKET_FORMAT_20BIT 0x0 +#define AXI_AD408X_PACKET_FORMAT_16BIT 0x1 +#define AXI_AD408X_PACKET_FORMAT_14BIT 0x2 + +#define ADI_AXI_ADC_REG_SYNC_STATUS 0x0068 +#define ADI_AXI_ADC_SYNC_STATUS_ADC_SYNC_MSK BIT(0) + +#define ADI_AXI_ADC_REG_DRP_STATUS 0x0074 +#define ADI_AXI_ADC_DRP_LOCKED BIT(17) + +/* ADC Channel controls */ + +#define ADI_AXI_REG_CHAN_CTRL(c) (0x0400 + (c) * 0x40) +#define ADI_AXI_REG_CHAN_CTRL_LB_OWR BIT(11) +#define ADI_AXI_REG_CHAN_CTRL_PN_SEL_OWR BIT(10) +#define ADI_AXI_REG_CHAN_CTRL_IQCOR_EN BIT(9) +#define ADI_AXI_REG_CHAN_CTRL_DCFILT_EN BIT(8) +#define ADI_AXI_REG_CHAN_CTRL_FMT_MASK GENMASK(6, 4) +#define ADI_AXI_REG_CHAN_CTRL_FMT_SIGNEXT BIT(6) +#define ADI_AXI_REG_CHAN_CTRL_FMT_TYPE BIT(5) +#define ADI_AXI_REG_CHAN_CTRL_FMT_EN BIT(4) +#define ADI_AXI_REG_CHAN_CTRL_PN_TYPE_OWR BIT(1) +#define ADI_AXI_REG_CHAN_CTRL_ENABLE BIT(0) + +#define ADI_AXI_ADC_REG_CHAN_STATUS(c) (0x0404 + (c) * 0x40) +#define ADI_AXI_ADC_CHAN_STAT_PN_MASK GENMASK(2, 1) +/* out of sync */ +#define ADI_AXI_ADC_CHAN_STAT_PN_OOS BIT(1) +/* spurious out of sync */ +#define ADI_AXI_ADC_CHAN_STAT_PN_ERR BIT(2) + +#define ADI_AXI_ADC_REG_CHAN_CTRL_3(c) (0x0418 + (c) * 0x40) +#define ADI_AXI_ADC_CHAN_PN_SEL_MASK GENMASK(19, 16) + +#define ADI_AXI_ADC_REG_CHAN_USR_CTRL_2(c) (0x0424 + (c) * 0x40) +#define ADI_AXI_ADC_CHAN_USR_CTRL_2_DEC_RATE_N_MASK GENMASK(15, 0) + +/* IO Delays */ +#define ADI_AXI_ADC_REG_DELAY(l) (0x0800 + (l) * 0x4) +#define AXI_ADC_DELAY_CTRL_MASK GENMASK(4, 0) + +#define ADI_AXI_REG_CONFIG_WR 0x0080 +#define ADI_AXI_REG_CONFIG_RD 0x0084 +#define ADI_AXI_REG_CONFIG_CTRL 0x008c +#define ADI_AXI_REG_CONFIG_CTRL_READ 0x03 +#define ADI_AXI_REG_CONFIG_CTRL_WRITE 0x01 + +#define ADI_AXI_ADC_MAX_IO_NUM_LANES 15 + +#define ADI_AXI_REG_CHAN_CTRL_DEFAULTS \ + (ADI_AXI_REG_CHAN_CTRL_FMT_SIGNEXT | \ + ADI_AXI_REG_CHAN_CTRL_FMT_EN | \ + ADI_AXI_REG_CHAN_CTRL_ENABLE) + +#define ADI_AXI_REG_READ_BIT 0x8000 +#define ADI_AXI_REG_ADDRESS_MASK 0xff00 +#define ADI_AXI_REG_VALUE_MASK 0x00ff + +struct axi_adc_info { + unsigned int version; + const struct iio_backend_info *backend_info; + bool has_child_nodes; + const void *pdata; + unsigned int pdata_sz; +}; + +struct adi_axi_adc_state { + const struct axi_adc_info *info; + struct regmap *regmap; + struct device *dev; + /* lock to protect multiple accesses to the device registers */ + struct mutex lock; +}; + +static int axi_adc_enable(struct iio_backend *back) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + unsigned int __val; + int ret; + + guard(mutex)(&st->lock); + ret = regmap_set_bits(st->regmap, ADI_AXI_REG_RSTN, + ADI_AXI_REG_RSTN_MMCM_RSTN); + if (ret) + return ret; + + /* + * Make sure the DRP (Dynamic Reconfiguration Port) is locked. Not all + * designs really use it but if they don't we still get the lock bit + * set. So let's do it all the time so the code is generic. + */ + ret = regmap_read_poll_timeout(st->regmap, ADI_AXI_ADC_REG_DRP_STATUS, + __val, __val & ADI_AXI_ADC_DRP_LOCKED, + 100, 1000); + if (ret) + return ret; + + return regmap_set_bits(st->regmap, ADI_AXI_REG_RSTN, + ADI_AXI_REG_RSTN_RSTN | ADI_AXI_REG_RSTN_MMCM_RSTN); +} + +static void axi_adc_disable(struct iio_backend *back) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + + guard(mutex)(&st->lock); + regmap_write(st->regmap, ADI_AXI_REG_RSTN, 0); +} + +static int axi_adc_data_format_set(struct iio_backend *back, unsigned int chan, + const struct iio_backend_data_fmt *data) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + u32 val; + + if (!data->enable) + return regmap_clear_bits(st->regmap, + ADI_AXI_REG_CHAN_CTRL(chan), + ADI_AXI_REG_CHAN_CTRL_FMT_EN); + + val = FIELD_PREP(ADI_AXI_REG_CHAN_CTRL_FMT_EN, true); + if (data->sign_extend) + val |= FIELD_PREP(ADI_AXI_REG_CHAN_CTRL_FMT_SIGNEXT, true); + if (data->type == IIO_BACKEND_OFFSET_BINARY) + val |= FIELD_PREP(ADI_AXI_REG_CHAN_CTRL_FMT_TYPE, true); + + return regmap_update_bits(st->regmap, ADI_AXI_REG_CHAN_CTRL(chan), + ADI_AXI_REG_CHAN_CTRL_FMT_MASK, val); +} + +static int axi_adc_data_sample_trigger(struct iio_backend *back, + enum iio_backend_sample_trigger trigger) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + + switch (trigger) { + case IIO_BACKEND_SAMPLE_TRIGGER_EDGE_RISING: + return regmap_clear_bits(st->regmap, ADI_AXI_ADC_REG_CTRL, + ADI_AXI_ADC_CTRL_DDR_EDGESEL_MASK); + case IIO_BACKEND_SAMPLE_TRIGGER_EDGE_FALLING: + return regmap_set_bits(st->regmap, ADI_AXI_ADC_REG_CTRL, + ADI_AXI_ADC_CTRL_DDR_EDGESEL_MASK); + default: + return -EINVAL; + } +} + +static int axi_adc_iodelays_set(struct iio_backend *back, unsigned int lane, + unsigned int tap) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + int ret; + u32 val; + + if (tap > FIELD_MAX(AXI_ADC_DELAY_CTRL_MASK)) + return -EINVAL; + if (lane > ADI_AXI_ADC_MAX_IO_NUM_LANES) + return -EINVAL; + + guard(mutex)(&st->lock); + ret = regmap_write(st->regmap, ADI_AXI_ADC_REG_DELAY(lane), tap); + if (ret) + return ret; + /* + * If readback is ~0, that means there are issues with the + * delay_clk. + */ + ret = regmap_read(st->regmap, ADI_AXI_ADC_REG_DELAY(lane), &val); + if (ret) + return ret; + if (val == U32_MAX) + return -EIO; + + return 0; +} + +static int axi_adc_test_pattern_set(struct iio_backend *back, + unsigned int chan, + enum iio_backend_test_pattern pattern) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + + switch (pattern) { + case IIO_BACKEND_NO_TEST_PATTERN: + /* nothing to do */ + return 0; + case IIO_BACKEND_ADI_PRBS_9A: + return regmap_update_bits(st->regmap, ADI_AXI_ADC_REG_CHAN_CTRL_3(chan), + ADI_AXI_ADC_CHAN_PN_SEL_MASK, + FIELD_PREP(ADI_AXI_ADC_CHAN_PN_SEL_MASK, 0)); + case IIO_BACKEND_ADI_PRBS_23A: + return regmap_update_bits(st->regmap, ADI_AXI_ADC_REG_CHAN_CTRL_3(chan), + ADI_AXI_ADC_CHAN_PN_SEL_MASK, + FIELD_PREP(ADI_AXI_ADC_CHAN_PN_SEL_MASK, 1)); + default: + return -EINVAL; + } +} + +static int axi_adc_oversampling_ratio_set(struct iio_backend *back, + unsigned int chan, + unsigned int rate) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + + return regmap_update_bits(st->regmap, + ADI_AXI_ADC_REG_CHAN_USR_CTRL_2(chan), + ADI_AXI_ADC_CHAN_USR_CTRL_2_DEC_RATE_N_MASK, + FIELD_PREP(ADI_AXI_ADC_CHAN_USR_CTRL_2_DEC_RATE_N_MASK, + rate)); +} + +static int axi_adc_read_chan_status(struct adi_axi_adc_state *st, unsigned int chan, + unsigned int *status) +{ + int ret; + + guard(mutex)(&st->lock); + /* reset test bits by setting them */ + ret = regmap_write(st->regmap, ADI_AXI_ADC_REG_CHAN_STATUS(chan), + ADI_AXI_ADC_CHAN_STAT_PN_MASK); + if (ret) + return ret; + + /* let's give enough time to validate or erroring the incoming pattern */ + fsleep(1000); + + return regmap_read(st->regmap, ADI_AXI_ADC_REG_CHAN_STATUS(chan), + status); +} + +static int axi_adc_chan_status(struct iio_backend *back, unsigned int chan, + bool *error) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + u32 val; + int ret; + + ret = axi_adc_read_chan_status(st, chan, &val); + if (ret) + return ret; + + if (ADI_AXI_ADC_CHAN_STAT_PN_MASK & val) + *error = true; + else + *error = false; + + return 0; +} + +static int axi_adc_debugfs_print_chan_status(struct iio_backend *back, + unsigned int chan, char *buf, + size_t len) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + u32 val; + int ret; + + ret = axi_adc_read_chan_status(st, chan, &val); + if (ret) + return ret; + + /* + * PN_ERR is cleared in case out of sync is set. Hence, no point in + * checking both bits. + */ + if (val & ADI_AXI_ADC_CHAN_STAT_PN_OOS) + return scnprintf(buf, len, "CH%u: Out of Sync.\n", chan); + if (val & ADI_AXI_ADC_CHAN_STAT_PN_ERR) + return scnprintf(buf, len, "CH%u: Spurious Out of Sync.\n", chan); + + return scnprintf(buf, len, "CH%u: OK.\n", chan); +} + +static int axi_adc_chan_enable(struct iio_backend *back, unsigned int chan) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + + return regmap_set_bits(st->regmap, ADI_AXI_REG_CHAN_CTRL(chan), + ADI_AXI_REG_CHAN_CTRL_ENABLE); +} + +static int axi_adc_chan_disable(struct iio_backend *back, unsigned int chan) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + + return regmap_clear_bits(st->regmap, ADI_AXI_REG_CHAN_CTRL(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_SERIAL_CMOS; + else + *type = IIO_BACKEND_INTERFACE_SERIAL_LVDS; + + return 0; +} + +static int axi_adc_ad485x_data_size_set(struct iio_backend *back, + unsigned int size) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + unsigned int val; + + switch (size) { + /* + * There are two different variants of the AXI AXI_AD485X IP block, a + * 16-bit and a 20-bit variant. + * The 0x0 value (AXI_AD485X_PACKET_FORMAT_20BIT) is corresponding also + * to the 16-bit variant of the IP block. + */ + case 16: + case 20: + val = AXI_AD485X_PACKET_FORMAT_20BIT; + break; + case 24: + val = AXI_AD485X_PACKET_FORMAT_24BIT; + break; + /* + * The 0x2 (AXI_AD485X_PACKET_FORMAT_32BIT) corresponds only to the + * 20-bit variant of the IP block. Setting this value properly is + * ensured by the upper layers of the drivers calling the axi-adc + * functions. + * Also, for 16-bit IP block, the 0x2 (AXI_AD485X_PACKET_FORMAT_32BIT) + * value is handled as maximum size available which is 24-bit for this + * configuration. + */ + case 32: + val = AXI_AD485X_PACKET_FORMAT_32BIT; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(st->regmap, ADI_AXI_ADC_REG_CNTRL_3, + AXI_AD485X_CNTRL_3_PACKET_FORMAT_MSK, + FIELD_PREP(AXI_AD485X_CNTRL_3_PACKET_FORMAT_MSK, val)); +} + +static int axi_adc_ad485x_oversampling_ratio_set(struct iio_backend *back, + unsigned int chan, + unsigned int ratio) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + + /* The current state of the function enables or disables the + * oversampling in REG_CNTRL_3 register. A ratio equal to 1 implies no + * oversampling, while a value greater than 1 implies oversampling being + * enabled. + */ + switch (ratio) { + case 0: + return -EINVAL; + case 1: + return regmap_clear_bits(st->regmap, ADI_AXI_ADC_REG_CNTRL_3, + AXI_AD485X_CNTRL_3_OS_EN_MSK); + default: + return regmap_set_bits(st->regmap, ADI_AXI_ADC_REG_CNTRL_3, + AXI_AD485X_CNTRL_3_OS_EN_MSK); + } +} + +static int axi_adc_ad408x_filter_type_set(struct iio_backend *back, + enum iio_backend_filter_type type) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + + if (type) + return regmap_set_bits(st->regmap, ADI_AXI_ADC_REG_CNTRL_3, + AXI_AD408X_CNTRL_3_FILTER_EN_MSK); + + return regmap_clear_bits(st->regmap, ADI_AXI_ADC_REG_CNTRL_3, + AXI_AD408X_CNTRL_3_FILTER_EN_MSK); +} + +static int axi_adc_ad408x_data_size_set(struct iio_backend *back, + unsigned int size) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + unsigned int val; + + switch (size) { + case 20: + val = AXI_AD408X_PACKET_FORMAT_20BIT; + break; + case 16: + val = AXI_AD408X_PACKET_FORMAT_16BIT; + break; + case 14: + val = AXI_AD408X_PACKET_FORMAT_14BIT; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(st->regmap, ADI_AXI_ADC_REG_CNTRL_3, + AXI_AD408X_CNTRL_3_PACKET_FORMAT_MSK, + FIELD_PREP(AXI_AD408X_CNTRL_3_PACKET_FORMAT_MSK, val)); +} + +static int axi_adc_ad408x_interface_data_align(struct iio_backend *back, + u32 timeout_us) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + u32 val; + int ret; + + ret = regmap_set_bits(st->regmap, ADI_AXI_ADC_REG_CTRL, + ADI_AXI_ADC_CTRL_SYNC_MSK); + if (ret) + return ret; + + return regmap_read_poll_timeout(st->regmap, ADI_AXI_ADC_REG_SYNC_STATUS, + val, + FIELD_GET(ADI_AXI_ADC_SYNC_STATUS_ADC_SYNC_MSK, val), + 1, timeout_us); +} + +static int axi_adc_num_lanes_set(struct iio_backend *back, + unsigned int num_lanes) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + + if (!num_lanes) + return -EINVAL; + + return regmap_update_bits(st->regmap, ADI_AXI_ADC_REG_CTRL, + ADI_AXI_ADC_CTRL_NUM_LANES_MSK, + FIELD_PREP(ADI_AXI_ADC_CTRL_NUM_LANES_MSK, num_lanes)); +} + +static struct iio_buffer *axi_adc_request_buffer(struct iio_backend *back, + struct iio_dev *indio_dev) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + const char *dma_name; + + if (device_property_read_string(st->dev, "dma-names", &dma_name)) + dma_name = "rx"; + + return iio_dmaengine_buffer_setup(st->dev, indio_dev, dma_name); +} + +static int axi_adc_raw_write(struct iio_backend *back, u32 val) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + + regmap_write(st->regmap, ADI_AXI_REG_CONFIG_WR, val); + regmap_write(st->regmap, ADI_AXI_REG_CONFIG_CTRL, + ADI_AXI_REG_CONFIG_CTRL_WRITE); + fsleep(100); + regmap_write(st->regmap, ADI_AXI_REG_CONFIG_CTRL, 0x00); + fsleep(100); + + return 0; +} + +static int axi_adc_raw_read(struct iio_backend *back, u32 *val) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + + regmap_write(st->regmap, ADI_AXI_REG_CONFIG_CTRL, + ADI_AXI_REG_CONFIG_CTRL_READ); + fsleep(100); + regmap_read(st->regmap, ADI_AXI_REG_CONFIG_RD, val); + regmap_write(st->regmap, ADI_AXI_REG_CONFIG_CTRL, 0x00); + fsleep(100); + + return 0; +} + +static int ad7606_bus_reg_read(struct iio_backend *back, u32 reg, u32 *val) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + u32 addr, reg_val; + + guard(mutex)(&st->lock); + + /* + * The address is written on the highest weight byte, and the MSB set + * at 1 indicates a read operation. + */ + addr = FIELD_PREP(ADI_AXI_REG_ADDRESS_MASK, reg) | ADI_AXI_REG_READ_BIT; + axi_adc_raw_write(back, addr); + axi_adc_raw_read(back, ®_val); + + *val = FIELD_GET(ADI_AXI_REG_VALUE_MASK, reg_val); + + /* Write 0x0 on the bus to get back to ADC mode */ + axi_adc_raw_write(back, 0); + + return 0; +} + +static int ad7606_bus_reg_write(struct iio_backend *back, u32 reg, u32 val) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + u32 buf; + + guard(mutex)(&st->lock); + + /* Write any register to switch to register mode */ + axi_adc_raw_write(back, 0xaf00); + + buf = FIELD_PREP(ADI_AXI_REG_ADDRESS_MASK, reg) | + FIELD_PREP(ADI_AXI_REG_VALUE_MASK, val); + axi_adc_raw_write(back, buf); + + /* Write 0x0 on the bus to get back to ADC mode */ + axi_adc_raw_write(back, 0); + + return 0; +} + +static void axi_adc_free_buffer(struct iio_backend *back, + struct iio_buffer *buffer) +{ + iio_dmaengine_buffer_teardown(buffer); +} + +static int axi_adc_reg_access(struct iio_backend *back, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct adi_axi_adc_state *st = iio_backend_get_priv(back); + + if (readval) + return regmap_read(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); +} + +static const struct regmap_config axi_adc_regmap_config = { + .val_bits = 32, + .reg_bits = 32, + .reg_stride = 4, +}; + +static void axi_adc_child_remove(void *data) +{ + platform_device_unregister(data); +} + +static int axi_adc_create_platform_device(struct adi_axi_adc_state *st, + struct fwnode_handle *child) +{ + struct platform_device_info pi = { + .parent = st->dev, + .name = fwnode_get_name(child), + .id = PLATFORM_DEVID_AUTO, + .fwnode = child, + .data = st->info->pdata, + .size_data = st->info->pdata_sz, + }; + struct platform_device *pdev; + + pdev = platform_device_register_full(&pi); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + return devm_add_action_or_reset(st->dev, axi_adc_child_remove, pdev); +} + +static const struct iio_backend_ops adi_axi_adc_ops = { + .enable = axi_adc_enable, + .disable = axi_adc_disable, + .data_format_set = axi_adc_data_format_set, + .chan_enable = axi_adc_chan_enable, + .chan_disable = axi_adc_chan_disable, + .request_buffer = axi_adc_request_buffer, + .free_buffer = axi_adc_free_buffer, + .data_sample_trigger = axi_adc_data_sample_trigger, + .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, + .oversampling_ratio_set = axi_adc_oversampling_ratio_set, + .num_lanes_set = axi_adc_num_lanes_set, + .debugfs_reg_access = iio_backend_debugfs_ptr(axi_adc_reg_access), + .debugfs_print_chan_status = iio_backend_debugfs_ptr(axi_adc_debugfs_print_chan_status), +}; + +static const struct iio_backend_info adi_axi_adc_generic = { + .name = "axi-adc", + .ops = &adi_axi_adc_ops, + .caps = IIO_BACKEND_CAP_CALIBRATION | IIO_BACKEND_CAP_BUFFER | + IIO_BACKEND_CAP_ENABLE, +}; + +static const struct iio_backend_ops adi_ad485x_ops = { + .enable = axi_adc_enable, + .disable = axi_adc_disable, + .data_format_set = axi_adc_data_format_set, + .chan_enable = axi_adc_chan_enable, + .chan_disable = axi_adc_chan_disable, + .request_buffer = axi_adc_request_buffer, + .free_buffer = axi_adc_free_buffer, + .data_sample_trigger = axi_adc_data_sample_trigger, + .iodelay_set = axi_adc_iodelays_set, + .chan_status = axi_adc_chan_status, + .interface_type_get = axi_adc_interface_type_get, + .data_size_set = axi_adc_ad485x_data_size_set, + .oversampling_ratio_set = axi_adc_ad485x_oversampling_ratio_set, + .debugfs_reg_access = iio_backend_debugfs_ptr(axi_adc_reg_access), + .debugfs_print_chan_status = + iio_backend_debugfs_ptr(axi_adc_debugfs_print_chan_status), +}; + +static const struct iio_backend_info axi_ad485x = { + .name = "axi-ad485x", + .ops = &adi_ad485x_ops, + .caps = IIO_BACKEND_CAP_CALIBRATION | IIO_BACKEND_CAP_BUFFER | + IIO_BACKEND_CAP_ENABLE, +}; + +static const struct iio_backend_ops adi_ad408x_ops = { + .enable = axi_adc_enable, + .disable = axi_adc_disable, + .chan_enable = axi_adc_chan_enable, + .chan_disable = axi_adc_chan_disable, + .request_buffer = axi_adc_request_buffer, + .free_buffer = axi_adc_free_buffer, + .data_sample_trigger = axi_adc_data_sample_trigger, + .filter_type_set = axi_adc_ad408x_filter_type_set, + .data_size_set = axi_adc_ad408x_data_size_set, + .interface_data_align = axi_adc_ad408x_interface_data_align, + .num_lanes_set = axi_adc_num_lanes_set, + .debugfs_reg_access = iio_backend_debugfs_ptr(axi_adc_reg_access), + .debugfs_print_chan_status = iio_backend_debugfs_ptr(axi_adc_debugfs_print_chan_status), +}; + +static const struct iio_backend_info axi_ad408x = { + .name = "axi-ad408x", + .ops = &adi_ad408x_ops, + .caps = IIO_BACKEND_CAP_BUFFER | IIO_BACKEND_CAP_ENABLE, +}; + +static int adi_axi_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct adi_axi_adc_state *st; + void __iomem *base; + unsigned int ver; + struct clk *clk; + int ret; + + st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL); + if (!st) + return -ENOMEM; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + st->dev = dev; + st->regmap = devm_regmap_init_mmio(dev, base, &axi_adc_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "failed to init register map\n"); + + st->info = device_get_match_data(dev); + if (!st->info) + return -ENODEV; + + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), + "failed to get clock\n"); + + /* + * Force disable the core. Up to the frontend to enable us. And we can + * still read/write registers... + */ + ret = regmap_write(st->regmap, ADI_AXI_REG_RSTN, 0); + if (ret) + return ret; + + ret = regmap_read(st->regmap, ADI_AXI_REG_VERSION, &ver); + if (ret) + return ret; + + if (ADI_AXI_PCORE_VER_MAJOR(ver) != ADI_AXI_PCORE_VER_MAJOR(st->info->version)) + return dev_err_probe(dev, -ENODEV, + "Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n", + ADI_AXI_PCORE_VER_MAJOR(st->info->version), + ADI_AXI_PCORE_VER_MINOR(st->info->version), + ADI_AXI_PCORE_VER_PATCH(st->info->version), + ADI_AXI_PCORE_VER_MAJOR(ver), + ADI_AXI_PCORE_VER_MINOR(ver), + ADI_AXI_PCORE_VER_PATCH(ver)); + + ret = devm_iio_backend_register(dev, st->info->backend_info, st); + if (ret) + return dev_err_probe(dev, ret, "failed to register iio backend\n"); + + device_for_each_child_node_scoped(dev, child) { + int val; + + if (!st->info->has_child_nodes) + return dev_err_probe(dev, -EINVAL, + "invalid fdt axi-dac compatible."); + + /* Processing only reg 0 node */ + ret = fwnode_property_read_u32(child, "reg", &val); + if (ret) + return dev_err_probe(dev, ret, "invalid reg property."); + if (val != 0) + return dev_err_probe(dev, -EINVAL, + "invalid node address."); + + ret = axi_adc_create_platform_device(st, child); + if (ret) + return dev_err_probe(dev, -EINVAL, + "cannot create device."); + } + + dev_info(dev, "AXI ADC IP core (%d.%.2d.%c) probed\n", + ADI_AXI_PCORE_VER_MAJOR(ver), + ADI_AXI_PCORE_VER_MINOR(ver), + ADI_AXI_PCORE_VER_PATCH(ver)); + + return 0; +} + +static const struct axi_adc_info adc_generic = { + .version = ADI_AXI_PCORE_VER(10, 0, 'a'), + .backend_info = &adi_axi_adc_generic, +}; + +static const struct axi_adc_info adi_axi_ad485x = { + .version = ADI_AXI_PCORE_VER(10, 0, 'a'), + .backend_info = &axi_ad485x, +}; + +static const struct ad7606_platform_data ad7606_pdata = { + .bus_reg_read = ad7606_bus_reg_read, + .bus_reg_write = ad7606_bus_reg_write, +}; + +static const struct axi_adc_info adc_ad7606 = { + .version = ADI_AXI_PCORE_VER(10, 0, 'a'), + .backend_info = &adi_axi_adc_generic, + .pdata = &ad7606_pdata, + .pdata_sz = sizeof(ad7606_pdata), + .has_child_nodes = true, +}; + +static const struct axi_adc_info adi_axi_ad408x = { + .version = ADI_AXI_PCORE_VER(10, 0, 'a'), + .backend_info = &axi_ad408x, +}; + +/* Match table for of_platform binding */ +static const struct of_device_id adi_axi_adc_of_match[] = { + { .compatible = "adi,axi-adc-10.0.a", .data = &adc_generic }, + { .compatible = "adi,axi-ad408x", .data = &adi_axi_ad408x }, + { .compatible = "adi,axi-ad485x", .data = &adi_axi_ad485x }, + { .compatible = "adi,axi-ad7606x", .data = &adc_ad7606 }, + { } +}; +MODULE_DEVICE_TABLE(of, adi_axi_adc_of_match); + +static struct platform_driver adi_axi_adc_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = adi_axi_adc_of_match, + }, + .probe = adi_axi_adc_probe, +}; +module_platform_driver(adi_axi_adc_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices Generic AXI ADC IP core driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER"); +MODULE_IMPORT_NS("IIO_BACKEND"); diff --git a/drivers/iio/adc/aspeed_adc.c b/drivers/iio/adc/aspeed_adc.c new file mode 100644 index 000000000..6169ce282 --- /dev/null +++ b/drivers/iio/adc/aspeed_adc.c @@ -0,0 +1,789 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Aspeed AST2400/2500/2600 ADC + * + * Copyright (C) 2017 Google, Inc. + * Copyright (C) 2021 Aspeed Technology Inc. + * + * ADC clock formula: + * Ast2400/Ast2500: + * clock period = period of PCLK * 2 * (ADC0C[31:17] + 1) * (ADC0C[9:0] + 1) + * Ast2600: + * clock period = period of PCLK * 2 * (ADC0C[15:0] + 1) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define ASPEED_RESOLUTION_BITS 10 +#define ASPEED_CLOCKS_PER_SAMPLE 12 + +#define ASPEED_REG_ENGINE_CONTROL 0x00 +#define ASPEED_REG_INTERRUPT_CONTROL 0x04 +#define ASPEED_REG_VGA_DETECT_CONTROL 0x08 +#define ASPEED_REG_CLOCK_CONTROL 0x0C +#define ASPEED_REG_COMPENSATION_TRIM 0xC4 +/* + * The register offset between 0xC8~0xCC can be read and won't affect the + * hardware logic in each version of ADC. + */ +#define ASPEED_REG_MAX 0xD0 + +#define ASPEED_ADC_ENGINE_ENABLE BIT(0) +#define ASPEED_ADC_OP_MODE GENMASK(3, 1) +#define ASPEED_ADC_OP_MODE_PWR_DOWN 0 +#define ASPEED_ADC_OP_MODE_STANDBY 1 +#define ASPEED_ADC_OP_MODE_NORMAL 7 +#define ASPEED_ADC_CTRL_COMPENSATION BIT(4) +#define ASPEED_ADC_AUTO_COMPENSATION BIT(5) +/* + * Bit 6 determines not only the reference voltage range but also the dividing + * circuit for battery sensing. + */ +#define ASPEED_ADC_REF_VOLTAGE GENMASK(7, 6) +#define ASPEED_ADC_REF_VOLTAGE_2500mV 0 +#define ASPEED_ADC_REF_VOLTAGE_1200mV 1 +#define ASPEED_ADC_REF_VOLTAGE_EXT_HIGH 2 +#define ASPEED_ADC_REF_VOLTAGE_EXT_LOW 3 +#define ASPEED_ADC_BAT_SENSING_DIV BIT(6) +#define ASPEED_ADC_BAT_SENSING_DIV_2_3 0 +#define ASPEED_ADC_BAT_SENSING_DIV_1_3 1 +#define ASPEED_ADC_CTRL_INIT_RDY BIT(8) +#define ASPEED_ADC_CH7_MODE BIT(12) +#define ASPEED_ADC_CH7_NORMAL 0 +#define ASPEED_ADC_CH7_BAT 1 +#define ASPEED_ADC_BAT_SENSING_ENABLE BIT(13) +#define ASPEED_ADC_CTRL_CHANNEL GENMASK(31, 16) +#define ASPEED_ADC_CTRL_CHANNEL_ENABLE(ch) FIELD_PREP(ASPEED_ADC_CTRL_CHANNEL, BIT(ch)) + +#define ASPEED_ADC_INIT_POLLING_TIME 500 +#define ASPEED_ADC_INIT_TIMEOUT 500000 +/* Battery sensing is typically on the last channel */ +#define ASPEED_ADC_BATTERY_CHANNEL 7 +/* + * When the sampling rate is too high, the ADC may not have enough charging + * time, resulting in a low voltage value. Thus, the default uses a slow + * sampling rate for most use cases. + */ +#define ASPEED_ADC_DEF_SAMPLING_RATE 65000 + +struct aspeed_adc_trim_locate { + const unsigned int offset; + const unsigned int field; +}; + +struct aspeed_adc_model_data { + const char *model_name; + unsigned int min_sampling_rate; // Hz + unsigned int max_sampling_rate; // Hz + unsigned int vref_fixed_mv; + bool wait_init_sequence; + bool need_prescaler; + bool bat_sense_sup; + u8 scaler_bit_width; + unsigned int num_channels; + const struct aspeed_adc_trim_locate *trim_locate; +}; + +struct adc_gain { + u8 mult; + u8 div; +}; + +struct aspeed_adc_data { + struct device *dev; + const struct aspeed_adc_model_data *model_data; + void __iomem *base; + spinlock_t clk_lock; + struct clk_hw *fixed_div_clk; + struct clk_hw *clk_prescaler; + struct clk_hw *clk_scaler; + struct reset_control *rst; + int vref_mv; + u32 sample_period_ns; + int cv; + bool battery_sensing; + struct adc_gain battery_mode_gain; +}; + +/* + * Enable multiple consecutive channels starting from channel 0. + * This creates a bitmask for channels 0 to (num_channels - 1). + * For example: num_channels=3 creates mask 0x0007 (channels 0,1,2) + */ +static inline u32 aspeed_adc_channels_mask(unsigned int num_channels) +{ + if (num_channels > 16) + return GENMASK(15, 0); + + return BIT(num_channels) - 1; +} + +static inline unsigned int aspeed_adc_get_active_channels(const struct aspeed_adc_data *data) +{ + /* + * For controllers with battery sensing capability, the last channel + * is reserved for battery sensing and should not be included in + * normal channel operations. + */ + if (data->model_data->bat_sense_sup) + return data->model_data->num_channels - 1; + return data->model_data->num_channels; +} + +#define ASPEED_CHAN(_idx, _data_reg_addr) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_idx), \ + .address = (_data_reg_addr), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ +} + +static const struct iio_chan_spec aspeed_adc_iio_channels[] = { + ASPEED_CHAN(0, 0x10), + ASPEED_CHAN(1, 0x12), + ASPEED_CHAN(2, 0x14), + ASPEED_CHAN(3, 0x16), + ASPEED_CHAN(4, 0x18), + ASPEED_CHAN(5, 0x1A), + ASPEED_CHAN(6, 0x1C), + ASPEED_CHAN(7, 0x1E), + ASPEED_CHAN(8, 0x20), + ASPEED_CHAN(9, 0x22), + ASPEED_CHAN(10, 0x24), + ASPEED_CHAN(11, 0x26), + ASPEED_CHAN(12, 0x28), + ASPEED_CHAN(13, 0x2A), + ASPEED_CHAN(14, 0x2C), + ASPEED_CHAN(15, 0x2E), +}; + +#define ASPEED_BAT_CHAN(_idx, _data_reg_addr) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_idx), \ + .address = (_data_reg_addr), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ +} +static const struct iio_chan_spec aspeed_adc_iio_bat_channels[] = { + ASPEED_CHAN(0, 0x10), + ASPEED_CHAN(1, 0x12), + ASPEED_CHAN(2, 0x14), + ASPEED_CHAN(3, 0x16), + ASPEED_CHAN(4, 0x18), + ASPEED_CHAN(5, 0x1A), + ASPEED_CHAN(6, 0x1C), + ASPEED_BAT_CHAN(7, 0x1E), +}; + +static int aspeed_adc_set_trim_data(struct iio_dev *indio_dev) +{ + struct device_node *syscon; + struct regmap *scu; + u32 scu_otp, trimming_val; + struct aspeed_adc_data *data = iio_priv(indio_dev); + + syscon = of_find_node_by_name(NULL, "syscon"); + if (syscon == NULL) { + dev_warn(data->dev, "Couldn't find syscon node\n"); + return -EOPNOTSUPP; + } + scu = syscon_node_to_regmap(syscon); + of_node_put(syscon); + if (IS_ERR(scu)) { + dev_warn(data->dev, "Failed to get syscon regmap\n"); + return -EOPNOTSUPP; + } + if (data->model_data->trim_locate) { + if (regmap_read(scu, data->model_data->trim_locate->offset, + &scu_otp)) { + dev_warn(data->dev, + "Failed to get adc trimming data\n"); + trimming_val = 0x8; + } else { + trimming_val = + ((scu_otp) & + (data->model_data->trim_locate->field)) >> + __ffs(data->model_data->trim_locate->field); + if (!trimming_val) + trimming_val = 0x8; + } + dev_dbg(data->dev, + "trimming val = %d, offset = %08x, fields = %08x\n", + trimming_val, data->model_data->trim_locate->offset, + data->model_data->trim_locate->field); + writel(trimming_val, data->base + ASPEED_REG_COMPENSATION_TRIM); + } + return 0; +} + +static int aspeed_adc_compensation(struct iio_dev *indio_dev) +{ + struct aspeed_adc_data *data = iio_priv(indio_dev); + u32 index, adc_raw = 0; + u32 adc_engine_control_reg_val; + + adc_engine_control_reg_val = + readl(data->base + ASPEED_REG_ENGINE_CONTROL); + adc_engine_control_reg_val &= ~ASPEED_ADC_OP_MODE; + adc_engine_control_reg_val |= + (FIELD_PREP(ASPEED_ADC_OP_MODE, ASPEED_ADC_OP_MODE_NORMAL) | + ASPEED_ADC_ENGINE_ENABLE); + /* + * Enable compensating sensing: + * After that, the input voltage of ADC will force to half of the reference + * voltage. So the expected reading raw data will become half of the max + * value. We can get compensating value = 0x200 - ADC read raw value. + * It is recommended to average at least 10 samples to get a final CV. + */ + writel(adc_engine_control_reg_val | ASPEED_ADC_CTRL_COMPENSATION | + ASPEED_ADC_CTRL_CHANNEL_ENABLE(0), + data->base + ASPEED_REG_ENGINE_CONTROL); + /* + * After enable compensating sensing mode need to wait some time for the + * ADC stablize. Experiment result is 1ms. + */ + fsleep(1000); + + for (index = 0; index < 16; index++) { + /* + * Waiting for the sampling period ensures that the value acquired + * is fresh each time. + */ + ndelay(data->sample_period_ns); + adc_raw += readw(data->base + aspeed_adc_iio_channels[0].address); + } + adc_raw >>= 4; + data->cv = BIT(ASPEED_RESOLUTION_BITS - 1) - adc_raw; + writel(adc_engine_control_reg_val, + data->base + ASPEED_REG_ENGINE_CONTROL); + dev_dbg(data->dev, "Compensating value = %d\n", data->cv); + + return 0; +} + +static int aspeed_adc_set_sampling_rate(struct iio_dev *indio_dev, u32 rate) +{ + struct aspeed_adc_data *data = iio_priv(indio_dev); + + if (rate < data->model_data->min_sampling_rate || + rate > data->model_data->max_sampling_rate) + return -EINVAL; + /* Each sampling needs 12 clocks to convert.*/ + clk_set_rate(data->clk_scaler->clk, rate * ASPEED_CLOCKS_PER_SAMPLE); + rate = clk_get_rate(data->clk_scaler->clk); + data->sample_period_ns = DIV_ROUND_UP_ULL( + (u64)NSEC_PER_SEC * ASPEED_CLOCKS_PER_SAMPLE, rate); + dev_dbg(data->dev, "Adc clock = %d sample period = %d ns", rate, + data->sample_period_ns); + + return 0; +} + +static int aspeed_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct aspeed_adc_data *data = iio_priv(indio_dev); + u32 adc_engine_control_reg_val; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + adc_engine_control_reg_val = readl(data->base + ASPEED_REG_ENGINE_CONTROL); + /* + * For battery sensing capable controllers, we need to enable + * the specific channel before reading. This is required because + * the battery channel may not be enabled by default. + */ + if (data->model_data->bat_sense_sup && + chan->channel == ASPEED_ADC_BATTERY_CHANNEL) { + u32 ctrl_reg = adc_engine_control_reg_val & ~ASPEED_ADC_CTRL_CHANNEL; + + ctrl_reg |= ASPEED_ADC_CTRL_CHANNEL_ENABLE(chan->channel); + writel(ctrl_reg, data->base + ASPEED_REG_ENGINE_CONTROL); + /* + * After enable a new channel need to wait some time for ADC stable + * Experiment result is 1ms. + */ + fsleep(1000); + } + + if (data->battery_sensing && chan->channel == ASPEED_ADC_BATTERY_CHANNEL) { + writel(adc_engine_control_reg_val | + FIELD_PREP(ASPEED_ADC_CH7_MODE, + ASPEED_ADC_CH7_BAT) | + ASPEED_ADC_BAT_SENSING_ENABLE, + data->base + ASPEED_REG_ENGINE_CONTROL); + /* + * After enable battery sensing mode need to wait some time for ADC stable + * Experiment result is 1ms. + */ + fsleep(1000); + *val = readw(data->base + chan->address); + *val = (*val * data->battery_mode_gain.mult) / + data->battery_mode_gain.div; + } else + *val = readw(data->base + chan->address); + /* Restore control register value */ + writel(adc_engine_control_reg_val, + data->base + ASPEED_REG_ENGINE_CONTROL); + return IIO_VAL_INT; + + case IIO_CHAN_INFO_OFFSET: + if (data->battery_sensing && chan->channel == ASPEED_ADC_BATTERY_CHANNEL) + *val = (data->cv * data->battery_mode_gain.mult) / + data->battery_mode_gain.div; + else + *val = data->cv; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = data->vref_mv; + *val2 = ASPEED_RESOLUTION_BITS; + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = clk_get_rate(data->clk_scaler->clk) / + ASPEED_CLOCKS_PER_SAMPLE; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int aspeed_adc_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return aspeed_adc_set_sampling_rate(indio_dev, val); + + case IIO_CHAN_INFO_SCALE: + case IIO_CHAN_INFO_RAW: + /* + * Technically, these could be written but the only reasons + * for doing so seem better handled in userspace. EPERM is + * returned to signal this is a policy choice rather than a + * hardware limitation. + */ + return -EPERM; + + default: + return -EINVAL; + } +} + +static int aspeed_adc_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + struct aspeed_adc_data *data = iio_priv(indio_dev); + + if (!readval || reg % 4 || reg > ASPEED_REG_MAX) + return -EINVAL; + + *readval = readl(data->base + reg); + + return 0; +} + +static const struct iio_info aspeed_adc_iio_info = { + .read_raw = aspeed_adc_read_raw, + .write_raw = aspeed_adc_write_raw, + .debugfs_reg_access = aspeed_adc_reg_access, +}; + +static void aspeed_adc_unregister_fixed_divider(void *data) +{ + struct clk_hw *clk = data; + + clk_hw_unregister_fixed_factor(clk); +} + +static void aspeed_adc_reset_assert(void *data) +{ + struct reset_control *rst = data; + + reset_control_assert(rst); +} + +static void aspeed_adc_clk_disable_unprepare(void *data) +{ + struct clk *clk = data; + + clk_disable_unprepare(clk); +} + +static void aspeed_adc_power_down(void *data) +{ + struct aspeed_adc_data *priv_data = data; + + writel(FIELD_PREP(ASPEED_ADC_OP_MODE, ASPEED_ADC_OP_MODE_PWR_DOWN), + priv_data->base + ASPEED_REG_ENGINE_CONTROL); +} + +static int aspeed_adc_vref_config(struct iio_dev *indio_dev) +{ + struct aspeed_adc_data *data = iio_priv(indio_dev); + int ret; + u32 adc_engine_control_reg_val; + + if (data->model_data->vref_fixed_mv) { + data->vref_mv = data->model_data->vref_fixed_mv; + return 0; + } + adc_engine_control_reg_val = + readl(data->base + ASPEED_REG_ENGINE_CONTROL); + adc_engine_control_reg_val &= ~ASPEED_ADC_REF_VOLTAGE; + + ret = devm_regulator_get_enable_read_voltage(data->dev, "vref"); + if (ret < 0 && ret != -ENODEV) + return ret; + + if (ret != -ENODEV) { + data->vref_mv = ret / 1000; + + if ((data->vref_mv >= 1550) && (data->vref_mv <= 2700)) + writel(adc_engine_control_reg_val | + FIELD_PREP( + ASPEED_ADC_REF_VOLTAGE, + ASPEED_ADC_REF_VOLTAGE_EXT_HIGH), + data->base + ASPEED_REG_ENGINE_CONTROL); + else if ((data->vref_mv >= 900) && (data->vref_mv <= 1650)) + writel(adc_engine_control_reg_val | + FIELD_PREP( + ASPEED_ADC_REF_VOLTAGE, + ASPEED_ADC_REF_VOLTAGE_EXT_LOW), + data->base + ASPEED_REG_ENGINE_CONTROL); + else { + dev_err(data->dev, "Regulator voltage %d not support", + data->vref_mv); + return -EOPNOTSUPP; + } + } else { + data->vref_mv = 2500000; + of_property_read_u32(data->dev->of_node, + "aspeed,int-vref-microvolt", + &data->vref_mv); + /* Conversion from uV to mV */ + data->vref_mv /= 1000; + if (data->vref_mv == 2500) + writel(adc_engine_control_reg_val | + FIELD_PREP(ASPEED_ADC_REF_VOLTAGE, + ASPEED_ADC_REF_VOLTAGE_2500mV), + data->base + ASPEED_REG_ENGINE_CONTROL); + else if (data->vref_mv == 1200) + writel(adc_engine_control_reg_val | + FIELD_PREP(ASPEED_ADC_REF_VOLTAGE, + ASPEED_ADC_REF_VOLTAGE_1200mV), + data->base + ASPEED_REG_ENGINE_CONTROL); + else { + dev_err(data->dev, "Voltage %d not support", data->vref_mv); + return -EOPNOTSUPP; + } + } + + return 0; +} + +static int aspeed_adc_probe(struct platform_device *pdev) +{ + struct iio_dev *indio_dev; + struct aspeed_adc_data *data; + int ret; + u32 adc_engine_control_reg_val; + struct device *dev = &pdev->dev; + struct device_node *np = dev_of_node(dev); + unsigned long scaler_flags = 0; + char clk_name[32], clk_parent_name[32]; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->dev = dev; + data->model_data = of_device_get_match_data(dev); + platform_set_drvdata(pdev, indio_dev); + + data->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(data->base)) + return PTR_ERR(data->base); + + /* Register ADC clock prescaler with source specified by device tree. */ + spin_lock_init(&data->clk_lock); + snprintf(clk_parent_name, ARRAY_SIZE(clk_parent_name), "%s", + of_clk_get_parent_name(np, 0)); + snprintf(clk_name, ARRAY_SIZE(clk_name), "%s-fixed-div", + data->model_data->model_name); + data->fixed_div_clk = clk_hw_register_fixed_factor(dev, clk_name, + clk_parent_name, 0, 1, 2); + if (IS_ERR(data->fixed_div_clk)) + return PTR_ERR(data->fixed_div_clk); + + ret = devm_add_action_or_reset(dev, aspeed_adc_unregister_fixed_divider, + data->fixed_div_clk); + if (ret) + return ret; + snprintf(clk_parent_name, ARRAY_SIZE(clk_parent_name), clk_name); + + if (data->model_data->need_prescaler) { + snprintf(clk_name, ARRAY_SIZE(clk_name), "%s-prescaler", + data->model_data->model_name); + data->clk_prescaler = devm_clk_hw_register_divider( + dev, clk_name, clk_parent_name, 0, + data->base + ASPEED_REG_CLOCK_CONTROL, 17, 15, 0, + &data->clk_lock); + if (IS_ERR(data->clk_prescaler)) + return PTR_ERR(data->clk_prescaler); + snprintf(clk_parent_name, ARRAY_SIZE(clk_parent_name), + clk_name); + scaler_flags = CLK_SET_RATE_PARENT; + } + /* + * Register ADC clock scaler downstream from the prescaler. Allow rate + * setting to adjust the prescaler as well. + */ + snprintf(clk_name, ARRAY_SIZE(clk_name), "%s-scaler", + data->model_data->model_name); + data->clk_scaler = devm_clk_hw_register_divider( + dev, clk_name, clk_parent_name, scaler_flags, + data->base + ASPEED_REG_CLOCK_CONTROL, 0, + data->model_data->scaler_bit_width, + data->model_data->need_prescaler ? CLK_DIVIDER_ONE_BASED : 0, + &data->clk_lock); + if (IS_ERR(data->clk_scaler)) + return PTR_ERR(data->clk_scaler); + + data->rst = devm_reset_control_get_shared(dev, NULL); + if (IS_ERR(data->rst)) + return dev_err_probe(dev, PTR_ERR(data->rst), + "invalid or missing reset controller device tree entry"); + + reset_control_deassert(data->rst); + + ret = devm_add_action_or_reset(dev, aspeed_adc_reset_assert, data->rst); + if (ret) + return ret; + + ret = aspeed_adc_vref_config(indio_dev); + if (ret) + return ret; + + ret = aspeed_adc_set_trim_data(indio_dev); + if (ret) + return ret; + + if (of_property_present(np, "aspeed,battery-sensing")) { + if (data->model_data->bat_sense_sup) { + data->battery_sensing = 1; + if (readl(data->base + ASPEED_REG_ENGINE_CONTROL) & + ASPEED_ADC_BAT_SENSING_DIV) { + data->battery_mode_gain.mult = 3; + data->battery_mode_gain.div = 1; + } else { + data->battery_mode_gain.mult = 3; + data->battery_mode_gain.div = 2; + } + } else + dev_warn(dev, "Failed to enable battery-sensing mode\n"); + } + + ret = clk_prepare_enable(data->clk_scaler->clk); + if (ret) + return ret; + ret = devm_add_action_or_reset(dev, aspeed_adc_clk_disable_unprepare, + data->clk_scaler->clk); + if (ret) + return ret; + ret = aspeed_adc_set_sampling_rate(indio_dev, + ASPEED_ADC_DEF_SAMPLING_RATE); + if (ret) + return ret; + + adc_engine_control_reg_val = + readl(data->base + ASPEED_REG_ENGINE_CONTROL); + adc_engine_control_reg_val |= + FIELD_PREP(ASPEED_ADC_OP_MODE, ASPEED_ADC_OP_MODE_NORMAL) | + ASPEED_ADC_ENGINE_ENABLE; + /* Enable engine in normal mode. */ + writel(adc_engine_control_reg_val, + data->base + ASPEED_REG_ENGINE_CONTROL); + + ret = devm_add_action_or_reset(dev, aspeed_adc_power_down, data); + if (ret) + return ret; + + if (data->model_data->wait_init_sequence) { + /* Wait for initial sequence complete. */ + ret = readl_poll_timeout(data->base + ASPEED_REG_ENGINE_CONTROL, + adc_engine_control_reg_val, + adc_engine_control_reg_val & + ASPEED_ADC_CTRL_INIT_RDY, + ASPEED_ADC_INIT_POLLING_TIME, + ASPEED_ADC_INIT_TIMEOUT); + if (ret) + return ret; + } + + aspeed_adc_compensation(indio_dev); + /* Start all channels in normal mode. */ + adc_engine_control_reg_val = + readl(data->base + ASPEED_REG_ENGINE_CONTROL); + FIELD_MODIFY(ASPEED_ADC_CTRL_CHANNEL, &adc_engine_control_reg_val, + aspeed_adc_channels_mask(aspeed_adc_get_active_channels(data))); + + writel(adc_engine_control_reg_val, + data->base + ASPEED_REG_ENGINE_CONTROL); + + indio_dev->name = data->model_data->model_name; + indio_dev->info = &aspeed_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = data->battery_sensing ? + aspeed_adc_iio_bat_channels : + aspeed_adc_iio_channels; + indio_dev->num_channels = data->model_data->num_channels; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct aspeed_adc_trim_locate ast2500_adc_trim = { + .offset = 0x154, + .field = GENMASK(31, 28), +}; + +static const struct aspeed_adc_trim_locate ast2600_adc0_trim = { + .offset = 0x5d0, + .field = GENMASK(3, 0), +}; + +static const struct aspeed_adc_trim_locate ast2600_adc1_trim = { + .offset = 0x5d0, + .field = GENMASK(7, 4), +}; + +static const struct aspeed_adc_trim_locate ast2700_adc0_trim = { + .offset = 0x820, + .field = GENMASK(3, 0), +}; + +static const struct aspeed_adc_trim_locate ast2700_adc1_trim = { + .offset = 0x820, + .field = GENMASK(7, 4), +}; + +static const struct aspeed_adc_model_data ast2400_model_data = { + .model_name = "ast2400-adc", + .vref_fixed_mv = 2500, + .min_sampling_rate = 10000, + .max_sampling_rate = 500000, + .need_prescaler = true, + .scaler_bit_width = 10, + .num_channels = 16, +}; + +static const struct aspeed_adc_model_data ast2500_model_data = { + .model_name = "ast2500-adc", + .vref_fixed_mv = 1800, + .min_sampling_rate = 1, + .max_sampling_rate = 1000000, + .wait_init_sequence = true, + .need_prescaler = true, + .scaler_bit_width = 10, + .num_channels = 16, + .trim_locate = &ast2500_adc_trim, +}; + +static const struct aspeed_adc_model_data ast2600_adc0_model_data = { + .model_name = "ast2600-adc0", + .min_sampling_rate = 10000, + .max_sampling_rate = 500000, + .wait_init_sequence = true, + .bat_sense_sup = true, + .scaler_bit_width = 16, + .num_channels = 8, + .trim_locate = &ast2600_adc0_trim, +}; + +static const struct aspeed_adc_model_data ast2600_adc1_model_data = { + .model_name = "ast2600-adc1", + .min_sampling_rate = 10000, + .max_sampling_rate = 500000, + .wait_init_sequence = true, + .bat_sense_sup = true, + .scaler_bit_width = 16, + .num_channels = 8, + .trim_locate = &ast2600_adc1_trim, +}; + +static const struct aspeed_adc_model_data ast2700_adc0_model_data = { + .model_name = "ast2700-adc0", + .min_sampling_rate = 10000, + .max_sampling_rate = 500000, + .wait_init_sequence = true, + .bat_sense_sup = true, + .scaler_bit_width = 16, + .num_channels = 8, + .trim_locate = &ast2700_adc0_trim, +}; + +static const struct aspeed_adc_model_data ast2700_adc1_model_data = { + .model_name = "ast2700-adc1", + .min_sampling_rate = 10000, + .max_sampling_rate = 500000, + .wait_init_sequence = true, + .bat_sense_sup = true, + .scaler_bit_width = 16, + .num_channels = 8, + .trim_locate = &ast2700_adc1_trim, +}; + +static const struct of_device_id aspeed_adc_matches[] = { + { .compatible = "aspeed,ast2400-adc", .data = &ast2400_model_data }, + { .compatible = "aspeed,ast2500-adc", .data = &ast2500_model_data }, + { .compatible = "aspeed,ast2600-adc0", .data = &ast2600_adc0_model_data }, + { .compatible = "aspeed,ast2600-adc1", .data = &ast2600_adc1_model_data }, + { .compatible = "aspeed,ast2700-adc0", .data = &ast2700_adc0_model_data }, + { .compatible = "aspeed,ast2700-adc1", .data = &ast2700_adc1_model_data }, + { } +}; +MODULE_DEVICE_TABLE(of, aspeed_adc_matches); + +static struct platform_driver aspeed_adc_driver = { + .probe = aspeed_adc_probe, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = aspeed_adc_matches, + } +}; + +module_platform_driver(aspeed_adc_driver); + +MODULE_AUTHOR("Rick Altherr "); +MODULE_DESCRIPTION("Aspeed AST2400/2500/2600 ADC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/at91-sama5d2_adc.c b/drivers/iio/adc/at91-sama5d2_adc.c new file mode 100644 index 000000000..e8a5285bb --- /dev/null +++ b/drivers/iio/adc/at91-sama5d2_adc.c @@ -0,0 +1,2629 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Atmel ADC driver for SAMA5D2 devices and compatible. + * + * Copyright (C) 2015 Atmel, + * 2015 Ludovic Desroches + * 2021 Microchip Technology, Inc. and its subsidiaries + * 2021 Eugen Hristev + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct at91_adc_reg_layout { +/* Control Register */ + u16 CR; +/* Software Reset */ +#define AT91_SAMA5D2_CR_SWRST BIT(0) +/* Start Conversion */ +#define AT91_SAMA5D2_CR_START BIT(1) +/* Touchscreen Calibration */ +#define AT91_SAMA5D2_CR_TSCALIB BIT(2) +/* Comparison Restart */ +#define AT91_SAMA5D2_CR_CMPRST BIT(4) + +/* Mode Register */ + u16 MR; +/* Trigger Selection */ +#define AT91_SAMA5D2_MR_TRGSEL(v) ((v) << 1) +/* ADTRG */ +#define AT91_SAMA5D2_MR_TRGSEL_TRIG0 0 +/* TIOA0 */ +#define AT91_SAMA5D2_MR_TRGSEL_TRIG1 1 +/* TIOA1 */ +#define AT91_SAMA5D2_MR_TRGSEL_TRIG2 2 +/* TIOA2 */ +#define AT91_SAMA5D2_MR_TRGSEL_TRIG3 3 +/* PWM event line 0 */ +#define AT91_SAMA5D2_MR_TRGSEL_TRIG4 4 +/* PWM event line 1 */ +#define AT91_SAMA5D2_MR_TRGSEL_TRIG5 5 +/* TIOA3 */ +#define AT91_SAMA5D2_MR_TRGSEL_TRIG6 6 +/* RTCOUT0 */ +#define AT91_SAMA5D2_MR_TRGSEL_TRIG7 7 +/* Sleep Mode */ +#define AT91_SAMA5D2_MR_SLEEP BIT(5) +/* Fast Wake Up */ +#define AT91_SAMA5D2_MR_FWUP BIT(6) +/* Prescaler Rate Selection */ +#define AT91_SAMA5D2_MR_PRESCAL(v) ((v) << AT91_SAMA5D2_MR_PRESCAL_OFFSET) +#define AT91_SAMA5D2_MR_PRESCAL_OFFSET 8 +#define AT91_SAMA5D2_MR_PRESCAL_MAX 0xff +#define AT91_SAMA5D2_MR_PRESCAL_MASK GENMASK(15, 8) +/* Startup Time */ +#define AT91_SAMA5D2_MR_STARTUP(v) ((v) << 16) +#define AT91_SAMA5D2_MR_STARTUP_MASK GENMASK(19, 16) +/* Minimum startup time for temperature sensor */ +#define AT91_SAMA5D2_MR_STARTUP_TS_MIN (50) +/* Analog Change */ +#define AT91_SAMA5D2_MR_ANACH BIT(23) +/* Tracking Time */ +#define AT91_SAMA5D2_MR_TRACKTIM(v) ((v) << 24) +#define AT91_SAMA5D2_MR_TRACKTIM_TS 6 +#define AT91_SAMA5D2_MR_TRACKTIM_MAX 0xf +/* Transfer Time */ +#define AT91_SAMA5D2_MR_TRANSFER(v) ((v) << 28) +#define AT91_SAMA5D2_MR_TRANSFER_MAX 0x3 +/* Use Sequence Enable */ +#define AT91_SAMA5D2_MR_USEQ BIT(31) + +/* Channel Sequence Register 1 */ + u16 SEQR1; +/* Channel Sequence Register 2 */ + u16 SEQR2; +/* Channel Enable Register */ + u16 CHER; +/* Channel Disable Register */ + u16 CHDR; +/* Channel Status Register */ + u16 CHSR; +/* Last Converted Data Register */ + u16 LCDR; +/* Interrupt Enable Register */ + u16 IER; +/* Interrupt Enable Register - TS X measurement ready */ +#define AT91_SAMA5D2_IER_XRDY BIT(20) +/* Interrupt Enable Register - TS Y measurement ready */ +#define AT91_SAMA5D2_IER_YRDY BIT(21) +/* Interrupt Enable Register - TS pressure measurement ready */ +#define AT91_SAMA5D2_IER_PRDY BIT(22) +/* Interrupt Enable Register - Data ready */ +#define AT91_SAMA5D2_IER_DRDY BIT(24) +/* Interrupt Enable Register - general overrun error */ +#define AT91_SAMA5D2_IER_GOVRE BIT(25) +/* Interrupt Enable Register - Pen detect */ +#define AT91_SAMA5D2_IER_PEN BIT(29) +/* Interrupt Enable Register - No pen detect */ +#define AT91_SAMA5D2_IER_NOPEN BIT(30) + +/* Interrupt Disable Register */ + u16 IDR; +/* Interrupt Mask Register */ + u16 IMR; +/* Interrupt Status Register */ + u16 ISR; +/* End of Conversion Interrupt Enable Register */ + u16 EOC_IER; +/* End of Conversion Interrupt Disable Register */ + u16 EOC_IDR; +/* End of Conversion Interrupt Mask Register */ + u16 EOC_IMR; +/* End of Conversion Interrupt Status Register */ + u16 EOC_ISR; +/* Interrupt Status Register - Pen touching sense status */ +#define AT91_SAMA5D2_ISR_PENS BIT(31) +/* Last Channel Trigger Mode Register */ + u16 LCTMR; +/* Last Channel Compare Window Register */ + u16 LCCWR; +/* Overrun Status Register */ + u16 OVER; +/* Extended Mode Register */ + u16 EMR; +/* Extended Mode Register - Oversampling rate */ +#define AT91_SAMA5D2_EMR_OSR(V, M) (((V) << 16) & (M)) +#define AT91_SAMA5D2_EMR_OSR_1SAMPLES 0 +#define AT91_SAMA5D2_EMR_OSR_4SAMPLES 1 +#define AT91_SAMA5D2_EMR_OSR_16SAMPLES 2 +#define AT91_SAMA5D2_EMR_OSR_64SAMPLES 3 +#define AT91_SAMA5D2_EMR_OSR_256SAMPLES 4 + +/* Extended Mode Register - TRACKX */ +#define AT91_SAMA5D2_TRACKX_MASK GENMASK(23, 22) +#define AT91_SAMA5D2_TRACKX(x) (((x) << 22) & \ + AT91_SAMA5D2_TRACKX_MASK) +/* TRACKX for temperature sensor. */ +#define AT91_SAMA5D2_TRACKX_TS (1) + +/* Extended Mode Register - Averaging on single trigger event */ +#define AT91_SAMA5D2_EMR_ASTE(V) ((V) << 20) + +/* Compare Window Register */ + u16 CWR; +/* Channel Gain Register */ + u16 CGR; +/* Channel Offset Register */ + u16 COR; +/* Channel Offset Register differential offset - constant, not a register */ + u16 COR_diff_offset; +/* Analog Control Register */ + u16 ACR; +/* Analog Control Register - Pen detect sensitivity mask */ +#define AT91_SAMA5D2_ACR_PENDETSENS_MASK GENMASK(1, 0) +/* Analog Control Register - Source last channel */ +#define AT91_SAMA5D2_ACR_SRCLCH BIT(16) + +/* Touchscreen Mode Register */ + u16 TSMR; +/* Touchscreen Mode Register - No touch mode */ +#define AT91_SAMA5D2_TSMR_TSMODE_NONE 0 +/* Touchscreen Mode Register - 4 wire screen, no pressure measurement */ +#define AT91_SAMA5D2_TSMR_TSMODE_4WIRE_NO_PRESS 1 +/* Touchscreen Mode Register - 4 wire screen, pressure measurement */ +#define AT91_SAMA5D2_TSMR_TSMODE_4WIRE_PRESS 2 +/* Touchscreen Mode Register - 5 wire screen */ +#define AT91_SAMA5D2_TSMR_TSMODE_5WIRE 3 +/* Touchscreen Mode Register - Average samples mask */ +#define AT91_SAMA5D2_TSMR_TSAV_MASK GENMASK(5, 4) +/* Touchscreen Mode Register - Average samples */ +#define AT91_SAMA5D2_TSMR_TSAV(x) ((x) << 4) +/* Touchscreen Mode Register - Touch/trigger frequency ratio mask */ +#define AT91_SAMA5D2_TSMR_TSFREQ_MASK GENMASK(11, 8) +/* Touchscreen Mode Register - Touch/trigger frequency ratio */ +#define AT91_SAMA5D2_TSMR_TSFREQ(x) ((x) << 8) +/* Touchscreen Mode Register - Pen Debounce Time mask */ +#define AT91_SAMA5D2_TSMR_PENDBC_MASK GENMASK(31, 28) +/* Touchscreen Mode Register - Pen Debounce Time */ +#define AT91_SAMA5D2_TSMR_PENDBC(x) ((x) << 28) +/* Touchscreen Mode Register - No DMA for touch measurements */ +#define AT91_SAMA5D2_TSMR_NOTSDMA BIT(22) +/* Touchscreen Mode Register - Disable pen detection */ +#define AT91_SAMA5D2_TSMR_PENDET_DIS (0 << 24) +/* Touchscreen Mode Register - Enable pen detection */ +#define AT91_SAMA5D2_TSMR_PENDET_ENA BIT(24) + +/* Touchscreen X Position Register */ + u16 XPOSR; +/* Touchscreen Y Position Register */ + u16 YPOSR; +/* Touchscreen Pressure Register */ + u16 PRESSR; +/* Trigger Register */ + u16 TRGR; +/* Mask for TRGMOD field of TRGR register */ +#define AT91_SAMA5D2_TRGR_TRGMOD_MASK GENMASK(2, 0) +/* No trigger, only software trigger can start conversions */ +#define AT91_SAMA5D2_TRGR_TRGMOD_NO_TRIGGER 0 +/* Trigger Mode external trigger rising edge */ +#define AT91_SAMA5D2_TRGR_TRGMOD_EXT_TRIG_RISE 1 +/* Trigger Mode external trigger falling edge */ +#define AT91_SAMA5D2_TRGR_TRGMOD_EXT_TRIG_FALL 2 +/* Trigger Mode external trigger any edge */ +#define AT91_SAMA5D2_TRGR_TRGMOD_EXT_TRIG_ANY 3 +/* Trigger Mode internal periodic */ +#define AT91_SAMA5D2_TRGR_TRGMOD_PERIODIC 5 +/* Trigger Mode - trigger period mask */ +#define AT91_SAMA5D2_TRGR_TRGPER_MASK GENMASK(31, 16) +/* Trigger Mode - trigger period */ +#define AT91_SAMA5D2_TRGR_TRGPER(x) ((x) << 16) + +/* Correction Select Register */ + u16 COSR; +/* Correction Value Register */ + u16 CVR; +/* Channel Error Correction Register */ + u16 CECR; +/* Write Protection Mode Register */ + u16 WPMR; +/* Write Protection Status Register */ + u16 WPSR; +/* Version Register */ + u16 VERSION; +/* Temperature Sensor Mode Register */ + u16 TEMPMR; +/* Temperature Sensor Mode - Temperature sensor on */ +#define AT91_SAMA5D2_TEMPMR_TEMPON BIT(0) +}; + +static const struct at91_adc_reg_layout sama5d2_layout = { + .CR = 0x00, + .MR = 0x04, + .SEQR1 = 0x08, + .SEQR2 = 0x0c, + .CHER = 0x10, + .CHDR = 0x14, + .CHSR = 0x18, + .LCDR = 0x20, + .IER = 0x24, + .IDR = 0x28, + .IMR = 0x2c, + .ISR = 0x30, + .LCTMR = 0x34, + .LCCWR = 0x38, + .OVER = 0x3c, + .EMR = 0x40, + .CWR = 0x44, + .CGR = 0x48, + .COR = 0x4c, + .COR_diff_offset = 16, + .ACR = 0x94, + .TSMR = 0xb0, + .XPOSR = 0xb4, + .YPOSR = 0xb8, + .PRESSR = 0xbc, + .TRGR = 0xc0, + .COSR = 0xd0, + .CVR = 0xd4, + .CECR = 0xd8, + .WPMR = 0xe4, + .WPSR = 0xe8, + .VERSION = 0xfc, +}; + +static const struct at91_adc_reg_layout sama7g5_layout = { + .CR = 0x00, + .MR = 0x04, + .SEQR1 = 0x08, + .SEQR2 = 0x0c, + .CHER = 0x10, + .CHDR = 0x14, + .CHSR = 0x18, + .LCDR = 0x20, + .IER = 0x24, + .IDR = 0x28, + .IMR = 0x2c, + .ISR = 0x30, + .EOC_IER = 0x34, + .EOC_IDR = 0x38, + .EOC_IMR = 0x3c, + .EOC_ISR = 0x40, + .TEMPMR = 0x44, + .OVER = 0x4c, + .EMR = 0x50, + .CWR = 0x54, + .COR = 0x5c, + .COR_diff_offset = 0, + .ACR = 0xe0, + .TRGR = 0x100, + .COSR = 0x104, + .CVR = 0x108, + .CECR = 0x10c, + .WPMR = 0x118, + .WPSR = 0x11c, + .VERSION = 0x130, +}; + +#define AT91_SAMA5D2_TOUCH_SAMPLE_PERIOD_US 2000 /* 2ms */ +#define AT91_SAMA5D2_TOUCH_PEN_DETECT_DEBOUNCE_US 200 + +#define AT91_SAMA5D2_XYZ_MASK GENMASK(11, 0) + +#define AT91_SAMA5D2_MAX_POS_BITS 12 + +#define AT91_HWFIFO_MAX_SIZE_STR "128" +#define AT91_HWFIFO_MAX_SIZE 128 + +#define AT91_SAMA_CHAN_SINGLE(index, num, addr, rbits) \ + { \ + .type = IIO_VOLTAGE, \ + .channel = num, \ + .address = addr, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = rbits, \ + .storagebits = 16, \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ)|\ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .datasheet_name = "CH"#num, \ + .indexed = 1, \ + } + +#define AT91_SAMA5D2_CHAN_SINGLE(index, num, addr) \ + AT91_SAMA_CHAN_SINGLE(index, num, addr, 14) + +#define AT91_SAMA7G5_CHAN_SINGLE(index, num, addr) \ + AT91_SAMA_CHAN_SINGLE(index, num, addr, 16) + +#define AT91_SAMA_CHAN_DIFF(index, num, num2, addr, rbits) \ + { \ + .type = IIO_VOLTAGE, \ + .differential = 1, \ + .channel = num, \ + .channel2 = num2, \ + .address = addr, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = rbits, \ + .storagebits = 16, \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ)|\ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .datasheet_name = "CH"#num"-CH"#num2, \ + .indexed = 1, \ + } + +#define AT91_SAMA5D2_CHAN_DIFF(index, num, num2, addr) \ + AT91_SAMA_CHAN_DIFF(index, num, num2, addr, 14) + +#define AT91_SAMA7G5_CHAN_DIFF(index, num, num2, addr) \ + AT91_SAMA_CHAN_DIFF(index, num, num2, addr, 16) + +#define AT91_SAMA5D2_CHAN_TOUCH(num, name, mod) \ + { \ + .type = IIO_POSITIONRELATIVE, \ + .modified = 1, \ + .channel = num, \ + .channel2 = mod, \ + .scan_index = num, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 12, \ + .storagebits = 16, \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ)|\ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .datasheet_name = name, \ + } +#define AT91_SAMA5D2_CHAN_PRESSURE(num, name) \ + { \ + .type = IIO_PRESSURE, \ + .channel = num, \ + .scan_index = num, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 12, \ + .storagebits = 16, \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ)|\ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .datasheet_name = name, \ + } + +#define AT91_SAMA5D2_CHAN_TEMP(num, name, addr) \ + { \ + .type = IIO_TEMP, \ + .channel = num, \ + .address = addr, \ + .scan_index = num, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ + .info_mask_shared_by_all = \ + BIT(IIO_CHAN_INFO_PROCESSED) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .datasheet_name = name, \ + } + +#define at91_adc_readl(st, reg) \ + readl_relaxed((st)->base + (st)->soc_info.platform->layout->reg) +#define at91_adc_read_chan(st, reg) \ + readl_relaxed((st)->base + reg) +#define at91_adc_writel(st, reg, val) \ + writel_relaxed(val, (st)->base + (st)->soc_info.platform->layout->reg) + +/** + * struct at91_adc_platform - at91-sama5d2 platform information struct + * @layout: pointer to the reg layout struct + * @adc_channels: pointer to an array of channels for registering in + * the iio subsystem + * @nr_channels: number of physical channels available + * @touch_chan_x: index of the touchscreen X channel + * @touch_chan_y: index of the touchscreen Y channel + * @touch_chan_p: index of the touchscreen P channel + * @max_channels: number of total channels + * @max_index: highest channel index (highest index may be higher + * than the total channel number) + * @hw_trig_cnt: number of possible hardware triggers + * @osr_mask: oversampling ratio bitmask on EMR register + * @oversampling_avail: available oversampling values + * @oversampling_avail_no: number of available oversampling values + * @chan_realbits: realbits for registered channels + * @temp_chan: temperature channel index + * @temp_sensor: temperature sensor supported + */ +struct at91_adc_platform { + const struct at91_adc_reg_layout *layout; + const struct iio_chan_spec (*adc_channels)[]; + unsigned int nr_channels; + unsigned int touch_chan_x; + unsigned int touch_chan_y; + unsigned int touch_chan_p; + unsigned int max_channels; + unsigned int max_index; + unsigned int hw_trig_cnt; + unsigned int osr_mask; + unsigned int oversampling_avail[5]; + unsigned int oversampling_avail_no; + unsigned int chan_realbits; + unsigned int temp_chan; + bool temp_sensor; +}; + +/** + * struct at91_adc_temp_sensor_clb - at91-sama5d2 temperature sensor + * calibration data structure + * @p1: P1 calibration temperature + * @p4: P4 calibration voltage + * @p6: P6 calibration voltage + */ +struct at91_adc_temp_sensor_clb { + u32 p1; + u32 p4; + u32 p6; +}; + +/** + * enum at91_adc_ts_clb_idx - calibration indexes in NVMEM buffer + * @AT91_ADC_TS_CLB_IDX_P1: index for P1 + * @AT91_ADC_TS_CLB_IDX_P4: index for P4 + * @AT91_ADC_TS_CLB_IDX_P6: index for P6 + * @AT91_ADC_TS_CLB_IDX_MAX: max index for temperature calibration packet in OTP + */ +enum at91_adc_ts_clb_idx { + AT91_ADC_TS_CLB_IDX_P1 = 2, + AT91_ADC_TS_CLB_IDX_P4 = 5, + AT91_ADC_TS_CLB_IDX_P6 = 7, + AT91_ADC_TS_CLB_IDX_MAX = 19, +}; + +/* Temperature sensor calibration - Vtemp voltage sensitivity to temperature. */ +#define AT91_ADC_TS_VTEMP_DT (2080U) + +/** + * struct at91_adc_soc_info - at91-sama5d2 soc information struct + * @startup_time: device startup time + * @min_sample_rate: minimum sample rate in Hz + * @max_sample_rate: maximum sample rate in Hz + * @platform: pointer to the platform structure + * @temp_sensor_clb: temperature sensor calibration data structure + */ +struct at91_adc_soc_info { + unsigned startup_time; + unsigned min_sample_rate; + unsigned max_sample_rate; + const struct at91_adc_platform *platform; + struct at91_adc_temp_sensor_clb temp_sensor_clb; +}; + +struct at91_adc_trigger { + char *name; + unsigned int trgmod_value; + unsigned int edge_type; + bool hw_trig; +}; + +/** + * struct at91_adc_dma - at91-sama5d2 dma information struct + * @dma_chan: the dma channel acquired + * @rx_buf: dma coherent allocated area + * @rx_dma_buf: dma handler for the buffer + * @phys_addr: physical address of the ADC base register + * @buf_idx: index inside the dma buffer where reading was last done + * @rx_buf_sz: size of buffer used by DMA operation + * @watermark: number of conversions to copy before DMA triggers irq + * @dma_ts: hold the start timestamp of dma operation + */ +struct at91_adc_dma { + struct dma_chan *dma_chan; + u8 *rx_buf; + dma_addr_t rx_dma_buf; + phys_addr_t phys_addr; + int buf_idx; + int rx_buf_sz; + int watermark; + s64 dma_ts; +}; + +/** + * struct at91_adc_touch - at91-sama5d2 touchscreen information struct + * @sample_period_val: the value for periodic trigger interval + * @touching: is the pen touching the screen or not + * @x_pos: temporary placeholder for pressure computation + * @channels_bitmask: bitmask with the touchscreen channels enabled + * @workq: workqueue for buffer data pushing + */ +struct at91_adc_touch { + u16 sample_period_val; + bool touching; + u16 x_pos; + unsigned long channels_bitmask; + struct work_struct workq; +}; + +/** + * struct at91_adc_temp - at91-sama5d2 temperature information structure + * @sample_period_val: sample period value + * @saved_sample_rate: saved sample rate + * @saved_oversampling: saved oversampling + */ +struct at91_adc_temp { + u16 sample_period_val; + u16 saved_sample_rate; + u16 saved_oversampling; +}; + +struct at91_adc_state { + void __iomem *base; + int irq; + struct clk *per_clk; + struct regulator *reg; + struct regulator *vref; + int vref_uv; + unsigned int current_sample_rate; + struct iio_trigger *trig; + const struct at91_adc_trigger *selected_trig; + const struct iio_chan_spec *chan; + bool conversion_done; + u32 conversion_value; + unsigned int oversampling_ratio; + struct at91_adc_soc_info soc_info; + wait_queue_head_t wq_data_available; + struct at91_adc_dma dma_st; + struct at91_adc_touch touch_st; + struct at91_adc_temp temp_st; + struct iio_dev *indio_dev; + struct device *dev; + /* We assume 32 channels for now, has to be increased if needed. */ + IIO_DECLARE_BUFFER_WITH_TS(u16, buffer, 32); + /* + * lock to prevent concurrent 'single conversion' requests through + * sysfs. + */ + struct mutex lock; +}; + +static const struct at91_adc_trigger at91_adc_trigger_list[] = { + { + .name = "external_rising", + .trgmod_value = AT91_SAMA5D2_TRGR_TRGMOD_EXT_TRIG_RISE, + .edge_type = IRQ_TYPE_EDGE_RISING, + .hw_trig = true, + }, + { + .name = "external_falling", + .trgmod_value = AT91_SAMA5D2_TRGR_TRGMOD_EXT_TRIG_FALL, + .edge_type = IRQ_TYPE_EDGE_FALLING, + .hw_trig = true, + }, + { + .name = "external_any", + .trgmod_value = AT91_SAMA5D2_TRGR_TRGMOD_EXT_TRIG_ANY, + .edge_type = IRQ_TYPE_EDGE_BOTH, + .hw_trig = true, + }, + { + .name = "software", + .trgmod_value = AT91_SAMA5D2_TRGR_TRGMOD_NO_TRIGGER, + .edge_type = IRQ_TYPE_NONE, + .hw_trig = false, + }, +}; + +static const struct iio_chan_spec at91_sama5d2_adc_channels[] = { + AT91_SAMA5D2_CHAN_SINGLE(0, 0, 0x50), + AT91_SAMA5D2_CHAN_SINGLE(1, 1, 0x54), + AT91_SAMA5D2_CHAN_SINGLE(2, 2, 0x58), + AT91_SAMA5D2_CHAN_SINGLE(3, 3, 0x5c), + AT91_SAMA5D2_CHAN_SINGLE(4, 4, 0x60), + AT91_SAMA5D2_CHAN_SINGLE(5, 5, 0x64), + AT91_SAMA5D2_CHAN_SINGLE(6, 6, 0x68), + AT91_SAMA5D2_CHAN_SINGLE(7, 7, 0x6c), + AT91_SAMA5D2_CHAN_SINGLE(8, 8, 0x70), + AT91_SAMA5D2_CHAN_SINGLE(9, 9, 0x74), + AT91_SAMA5D2_CHAN_SINGLE(10, 10, 0x78), + AT91_SAMA5D2_CHAN_SINGLE(11, 11, 0x7c), + /* original ABI has the differential channels with a gap in between */ + AT91_SAMA5D2_CHAN_DIFF(12, 0, 1, 0x50), + AT91_SAMA5D2_CHAN_DIFF(14, 2, 3, 0x58), + AT91_SAMA5D2_CHAN_DIFF(16, 4, 5, 0x60), + AT91_SAMA5D2_CHAN_DIFF(18, 6, 7, 0x68), + AT91_SAMA5D2_CHAN_DIFF(20, 8, 9, 0x70), + AT91_SAMA5D2_CHAN_DIFF(22, 10, 11, 0x78), + IIO_CHAN_SOFT_TIMESTAMP(23), + AT91_SAMA5D2_CHAN_TOUCH(24, "x", IIO_MOD_X), + AT91_SAMA5D2_CHAN_TOUCH(25, "y", IIO_MOD_Y), + AT91_SAMA5D2_CHAN_PRESSURE(26, "pressure"), +}; + +static const struct iio_chan_spec at91_sama7g5_adc_channels[] = { + AT91_SAMA7G5_CHAN_SINGLE(0, 0, 0x60), + AT91_SAMA7G5_CHAN_SINGLE(1, 1, 0x64), + AT91_SAMA7G5_CHAN_SINGLE(2, 2, 0x68), + AT91_SAMA7G5_CHAN_SINGLE(3, 3, 0x6c), + AT91_SAMA7G5_CHAN_SINGLE(4, 4, 0x70), + AT91_SAMA7G5_CHAN_SINGLE(5, 5, 0x74), + AT91_SAMA7G5_CHAN_SINGLE(6, 6, 0x78), + AT91_SAMA7G5_CHAN_SINGLE(7, 7, 0x7c), + AT91_SAMA7G5_CHAN_SINGLE(8, 8, 0x80), + AT91_SAMA7G5_CHAN_SINGLE(9, 9, 0x84), + AT91_SAMA7G5_CHAN_SINGLE(10, 10, 0x88), + AT91_SAMA7G5_CHAN_SINGLE(11, 11, 0x8c), + AT91_SAMA7G5_CHAN_SINGLE(12, 12, 0x90), + AT91_SAMA7G5_CHAN_SINGLE(13, 13, 0x94), + AT91_SAMA7G5_CHAN_SINGLE(14, 14, 0x98), + AT91_SAMA7G5_CHAN_SINGLE(15, 15, 0x9c), + AT91_SAMA7G5_CHAN_DIFF(16, 0, 1, 0x60), + AT91_SAMA7G5_CHAN_DIFF(17, 2, 3, 0x68), + AT91_SAMA7G5_CHAN_DIFF(18, 4, 5, 0x70), + AT91_SAMA7G5_CHAN_DIFF(19, 6, 7, 0x78), + AT91_SAMA7G5_CHAN_DIFF(20, 8, 9, 0x80), + AT91_SAMA7G5_CHAN_DIFF(21, 10, 11, 0x88), + AT91_SAMA7G5_CHAN_DIFF(22, 12, 13, 0x90), + AT91_SAMA7G5_CHAN_DIFF(23, 14, 15, 0x98), + IIO_CHAN_SOFT_TIMESTAMP(24), + AT91_SAMA5D2_CHAN_TEMP(AT91_SAMA7G5_ADC_TEMP_CHANNEL, "temp", 0xdc), +}; + +static const struct at91_adc_platform sama5d2_platform = { + .layout = &sama5d2_layout, + .adc_channels = &at91_sama5d2_adc_channels, +#define AT91_SAMA5D2_SINGLE_CHAN_CNT 12 +#define AT91_SAMA5D2_DIFF_CHAN_CNT 6 + .nr_channels = AT91_SAMA5D2_SINGLE_CHAN_CNT + + AT91_SAMA5D2_DIFF_CHAN_CNT, +#define AT91_SAMA5D2_TOUCH_X_CHAN_IDX (AT91_SAMA5D2_SINGLE_CHAN_CNT + \ + AT91_SAMA5D2_DIFF_CHAN_CNT * 2) + .touch_chan_x = AT91_SAMA5D2_TOUCH_X_CHAN_IDX, +#define AT91_SAMA5D2_TOUCH_Y_CHAN_IDX (AT91_SAMA5D2_TOUCH_X_CHAN_IDX + 1) + .touch_chan_y = AT91_SAMA5D2_TOUCH_Y_CHAN_IDX, +#define AT91_SAMA5D2_TOUCH_P_CHAN_IDX (AT91_SAMA5D2_TOUCH_Y_CHAN_IDX + 1) + .touch_chan_p = AT91_SAMA5D2_TOUCH_P_CHAN_IDX, +#define AT91_SAMA5D2_MAX_CHAN_IDX AT91_SAMA5D2_TOUCH_P_CHAN_IDX + .max_channels = ARRAY_SIZE(at91_sama5d2_adc_channels), + .max_index = AT91_SAMA5D2_MAX_CHAN_IDX, +#define AT91_SAMA5D2_HW_TRIG_CNT 3 + .hw_trig_cnt = AT91_SAMA5D2_HW_TRIG_CNT, + .osr_mask = GENMASK(17, 16), + .oversampling_avail = { 1, 4, 16, }, + .oversampling_avail_no = 3, + .chan_realbits = 14, +}; + +static const struct at91_adc_platform sama7g5_platform = { + .layout = &sama7g5_layout, + .adc_channels = &at91_sama7g5_adc_channels, +#define AT91_SAMA7G5_SINGLE_CHAN_CNT 16 +#define AT91_SAMA7G5_DIFF_CHAN_CNT 8 +#define AT91_SAMA7G5_TEMP_CHAN_CNT 1 + .nr_channels = AT91_SAMA7G5_SINGLE_CHAN_CNT + + AT91_SAMA7G5_DIFF_CHAN_CNT + + AT91_SAMA7G5_TEMP_CHAN_CNT, +#define AT91_SAMA7G5_MAX_CHAN_IDX (AT91_SAMA7G5_SINGLE_CHAN_CNT + \ + AT91_SAMA7G5_DIFF_CHAN_CNT + \ + AT91_SAMA7G5_TEMP_CHAN_CNT) + .max_channels = ARRAY_SIZE(at91_sama7g5_adc_channels), + .max_index = AT91_SAMA7G5_MAX_CHAN_IDX, +#define AT91_SAMA7G5_HW_TRIG_CNT 3 + .hw_trig_cnt = AT91_SAMA7G5_HW_TRIG_CNT, + .osr_mask = GENMASK(18, 16), + .oversampling_avail = { 1, 4, 16, 64, 256, }, + .oversampling_avail_no = 5, + .chan_realbits = 16, + .temp_sensor = true, + .temp_chan = AT91_SAMA7G5_ADC_TEMP_CHANNEL, +}; + +static int at91_adc_chan_xlate(struct iio_dev *indio_dev, int chan) +{ + int i; + + for (i = 0; i < indio_dev->num_channels; i++) { + if (indio_dev->channels[i].scan_index == chan) + return i; + } + return -EINVAL; +} + +static inline struct iio_chan_spec const * +at91_adc_chan_get(struct iio_dev *indio_dev, int chan) +{ + int index = at91_adc_chan_xlate(indio_dev, chan); + + if (index < 0) + return NULL; + return indio_dev->channels + index; +} + +static inline int at91_adc_fwnode_xlate(struct iio_dev *indio_dev, + const struct fwnode_reference_args *iiospec) +{ + return at91_adc_chan_xlate(indio_dev, iiospec->args[0]); +} + +static unsigned int at91_adc_active_scan_mask_to_reg(struct iio_dev *indio_dev) +{ + u32 mask = 0; + u8 bit; + struct at91_adc_state *st = iio_priv(indio_dev); + + for_each_set_bit(bit, indio_dev->active_scan_mask, + indio_dev->num_channels) { + struct iio_chan_spec const *chan = + at91_adc_chan_get(indio_dev, bit); + mask |= BIT(chan->channel); + } + + return mask & GENMASK(st->soc_info.platform->nr_channels, 0); +} + +static void at91_adc_cor(struct at91_adc_state *st, + struct iio_chan_spec const *chan) +{ + u32 cor, cur_cor; + + cor = BIT(chan->channel) | BIT(chan->channel2); + + cur_cor = at91_adc_readl(st, COR); + cor <<= st->soc_info.platform->layout->COR_diff_offset; + if (chan->differential) + at91_adc_writel(st, COR, cur_cor | cor); + else + at91_adc_writel(st, COR, cur_cor & ~cor); +} + +static void at91_adc_irq_status(struct at91_adc_state *st, u32 *status, + u32 *eoc) +{ + *status = at91_adc_readl(st, ISR); + if (st->soc_info.platform->layout->EOC_ISR) + *eoc = at91_adc_readl(st, EOC_ISR); + else + *eoc = *status; +} + +static void at91_adc_irq_mask(struct at91_adc_state *st, u32 *status, u32 *eoc) +{ + *status = at91_adc_readl(st, IMR); + if (st->soc_info.platform->layout->EOC_IMR) + *eoc = at91_adc_readl(st, EOC_IMR); + else + *eoc = *status; +} + +static void at91_adc_eoc_dis(struct at91_adc_state *st, unsigned int channel) +{ + /* + * On some products having the EOC bits in a separate register, + * errata recommends not writing this register (EOC_IDR). + * On products having the EOC bits in the IDR register, it's fine to write it. + */ + if (!st->soc_info.platform->layout->EOC_IDR) + at91_adc_writel(st, IDR, BIT(channel)); +} + +static void at91_adc_eoc_ena(struct at91_adc_state *st, unsigned int channel) +{ + if (!st->soc_info.platform->layout->EOC_IDR) + at91_adc_writel(st, IER, BIT(channel)); + else + at91_adc_writel(st, EOC_IER, BIT(channel)); +} + +static int at91_adc_config_emr(struct at91_adc_state *st, + u32 oversampling_ratio, u32 trackx) +{ + /* configure the extended mode register */ + unsigned int emr, osr; + unsigned int osr_mask = st->soc_info.platform->osr_mask; + int i, ret; + + /* Check against supported oversampling values. */ + for (i = 0; i < st->soc_info.platform->oversampling_avail_no; i++) { + if (oversampling_ratio == st->soc_info.platform->oversampling_avail[i]) + break; + } + if (i == st->soc_info.platform->oversampling_avail_no) + return -EINVAL; + + /* select oversampling ratio from configuration */ + switch (oversampling_ratio) { + case 1: + osr = AT91_SAMA5D2_EMR_OSR(AT91_SAMA5D2_EMR_OSR_1SAMPLES, + osr_mask); + break; + case 4: + osr = AT91_SAMA5D2_EMR_OSR(AT91_SAMA5D2_EMR_OSR_4SAMPLES, + osr_mask); + break; + case 16: + osr = AT91_SAMA5D2_EMR_OSR(AT91_SAMA5D2_EMR_OSR_16SAMPLES, + osr_mask); + break; + case 64: + osr = AT91_SAMA5D2_EMR_OSR(AT91_SAMA5D2_EMR_OSR_64SAMPLES, + osr_mask); + break; + case 256: + osr = AT91_SAMA5D2_EMR_OSR(AT91_SAMA5D2_EMR_OSR_256SAMPLES, + osr_mask); + break; + } + + ret = pm_runtime_resume_and_get(st->dev); + if (ret < 0) + return ret; + + emr = at91_adc_readl(st, EMR); + /* select oversampling per single trigger event */ + emr |= AT91_SAMA5D2_EMR_ASTE(1); + /* delete leftover content if it's the case */ + emr &= ~(osr_mask | AT91_SAMA5D2_TRACKX_MASK); + /* Update osr and trackx. */ + emr |= osr | AT91_SAMA5D2_TRACKX(trackx); + at91_adc_writel(st, EMR, emr); + + pm_runtime_put_autosuspend(st->dev); + + st->oversampling_ratio = oversampling_ratio; + + return 0; +} + +static int at91_adc_adjust_val_osr(struct at91_adc_state *st, int *val) +{ + int nbits, diff; + + if (st->oversampling_ratio == 1) + nbits = 12; + else if (st->oversampling_ratio == 4) + nbits = 13; + else if (st->oversampling_ratio == 16) + nbits = 14; + else if (st->oversampling_ratio == 64) + nbits = 15; + else if (st->oversampling_ratio == 256) + nbits = 16; + else + /* Should not happen. */ + return -EINVAL; + + /* + * We have nbits of real data and channel is registered as + * st->soc_info.platform->chan_realbits, so shift left diff bits. + */ + diff = st->soc_info.platform->chan_realbits - nbits; + *val <<= diff; + + return IIO_VAL_INT; +} + +static void at91_adc_adjust_val_osr_array(struct at91_adc_state *st, void *buf, + int len) +{ + int i = 0, val; + u16 *buf_u16 = (u16 *) buf; + + /* + * We are converting each two bytes (each sample). + * First convert the byte based array to u16, and convert each sample + * separately. + * Each value is two bytes in an array of chars, so to not shift + * more than we need, save the value separately. + * len is in bytes, so divide by two to get number of samples. + */ + while (i < len / 2) { + val = buf_u16[i]; + at91_adc_adjust_val_osr(st, &val); + buf_u16[i] = val; + i++; + } +} + +static int at91_adc_configure_touch(struct at91_adc_state *st, bool state) +{ + u32 clk_khz = st->current_sample_rate / 1000; + int i = 0, ret; + u16 pendbc; + u32 tsmr, acr; + + if (state) { + ret = pm_runtime_resume_and_get(st->dev); + if (ret < 0) + return ret; + } else { + /* disabling touch IRQs and setting mode to no touch enabled */ + at91_adc_writel(st, IDR, + AT91_SAMA5D2_IER_PEN | AT91_SAMA5D2_IER_NOPEN); + at91_adc_writel(st, TSMR, 0); + + pm_runtime_put_autosuspend(st->dev); + return 0; + } + /* + * debounce time is in microseconds, we need it in milliseconds to + * multiply with kilohertz, so, divide by 1000, but after the multiply. + * round up to make sure pendbc is at least 1 + */ + pendbc = round_up(AT91_SAMA5D2_TOUCH_PEN_DETECT_DEBOUNCE_US * + clk_khz / 1000, 1); + + /* get the required exponent */ + while (pendbc >> i++) + ; + + pendbc = i; + + tsmr = AT91_SAMA5D2_TSMR_TSMODE_4WIRE_PRESS; + + tsmr |= AT91_SAMA5D2_TSMR_TSAV(2) & AT91_SAMA5D2_TSMR_TSAV_MASK; + tsmr |= AT91_SAMA5D2_TSMR_PENDBC(pendbc) & + AT91_SAMA5D2_TSMR_PENDBC_MASK; + tsmr |= AT91_SAMA5D2_TSMR_NOTSDMA; + tsmr |= AT91_SAMA5D2_TSMR_PENDET_ENA; + tsmr |= AT91_SAMA5D2_TSMR_TSFREQ(2) & AT91_SAMA5D2_TSMR_TSFREQ_MASK; + + at91_adc_writel(st, TSMR, tsmr); + + acr = at91_adc_readl(st, ACR); + acr &= ~AT91_SAMA5D2_ACR_PENDETSENS_MASK; + acr |= 0x02 & AT91_SAMA5D2_ACR_PENDETSENS_MASK; + at91_adc_writel(st, ACR, acr); + + /* Sample Period Time = (TRGPER + 1) / ADCClock */ + st->touch_st.sample_period_val = + round_up((AT91_SAMA5D2_TOUCH_SAMPLE_PERIOD_US * + clk_khz / 1000) - 1, 1); + /* enable pen detect IRQ */ + at91_adc_writel(st, IER, AT91_SAMA5D2_IER_PEN); + + return 0; +} + +static u16 at91_adc_touch_pos(struct at91_adc_state *st, int reg) +{ + u32 val = 0; + u32 scale, result, pos; + + /* + * to obtain the actual position we must divide by scale + * and multiply with max, where + * max = 2^AT91_SAMA5D2_MAX_POS_BITS - 1 + */ + /* first half of register is the x or y, second half is the scale */ + if (reg == st->soc_info.platform->layout->XPOSR) + val = at91_adc_readl(st, XPOSR); + else if (reg == st->soc_info.platform->layout->YPOSR) + val = at91_adc_readl(st, YPOSR); + + if (!val) + dev_dbg(&st->indio_dev->dev, "pos is 0\n"); + + pos = val & AT91_SAMA5D2_XYZ_MASK; + result = (pos << AT91_SAMA5D2_MAX_POS_BITS) - pos; + scale = (val >> 16) & AT91_SAMA5D2_XYZ_MASK; + if (scale == 0) { + dev_err(&st->indio_dev->dev, "scale is 0\n"); + return 0; + } + result /= scale; + + return result; +} + +static u16 at91_adc_touch_x_pos(struct at91_adc_state *st) +{ + st->touch_st.x_pos = at91_adc_touch_pos(st, st->soc_info.platform->layout->XPOSR); + return st->touch_st.x_pos; +} + +static u16 at91_adc_touch_y_pos(struct at91_adc_state *st) +{ + return at91_adc_touch_pos(st, st->soc_info.platform->layout->YPOSR); +} + +static u16 at91_adc_touch_pressure(struct at91_adc_state *st) +{ + u32 val; + u32 z1, z2; + u32 pres; + u32 rxp = 1; + u32 factor = 1000; + + /* calculate the pressure */ + val = at91_adc_readl(st, PRESSR); + z1 = val & AT91_SAMA5D2_XYZ_MASK; + z2 = (val >> 16) & AT91_SAMA5D2_XYZ_MASK; + + if (z1 != 0) + pres = rxp * (st->touch_st.x_pos * factor / 1024) * + (z2 * factor / z1 - factor) / + factor; + else + pres = 0xFFFF; /* no pen contact */ + + /* + * The pressure from device grows down, minimum is 0xFFFF, maximum 0x0. + * We compute it this way, but let's return it in the expected way, + * growing from 0 to 0xFFFF. + */ + return 0xFFFF - pres; +} + +static int at91_adc_read_position(struct at91_adc_state *st, int chan, u16 *val) +{ + *val = 0; + if (!st->touch_st.touching) + return -ENODATA; + if (chan == st->soc_info.platform->touch_chan_x) + *val = at91_adc_touch_x_pos(st); + else if (chan == st->soc_info.platform->touch_chan_y) + *val = at91_adc_touch_y_pos(st); + else + return -ENODATA; + + return IIO_VAL_INT; +} + +static int at91_adc_read_pressure(struct at91_adc_state *st, int chan, u16 *val) +{ + *val = 0; + if (!st->touch_st.touching) + return -ENODATA; + if (chan == st->soc_info.platform->touch_chan_p) + *val = at91_adc_touch_pressure(st); + else + return -ENODATA; + + return IIO_VAL_INT; +} + +static void at91_adc_configure_trigger_registers(struct at91_adc_state *st, + bool state) +{ + u32 status = at91_adc_readl(st, TRGR); + + /* clear TRGMOD */ + status &= ~AT91_SAMA5D2_TRGR_TRGMOD_MASK; + + if (state) + status |= st->selected_trig->trgmod_value; + + /* set/unset hw trigger */ + at91_adc_writel(st, TRGR, status); +} + +static int at91_adc_configure_trigger(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio = iio_trigger_get_drvdata(trig); + struct at91_adc_state *st = iio_priv(indio); + int ret; + + if (state) { + ret = pm_runtime_resume_and_get(st->dev); + if (ret < 0) + return ret; + } + + at91_adc_configure_trigger_registers(st, state); + + if (!state) + pm_runtime_put_autosuspend(st->dev); + + return 0; +} + +static void at91_adc_reenable_trigger(struct iio_trigger *trig) +{ + struct iio_dev *indio = iio_trigger_get_drvdata(trig); + struct at91_adc_state *st = iio_priv(indio); + + /* if we are using DMA, we must not reenable irq after each trigger */ + if (st->dma_st.dma_chan) + return; + + enable_irq(st->irq); + + /* Needed to ACK the DRDY interruption */ + at91_adc_readl(st, LCDR); +} + +static const struct iio_trigger_ops at91_adc_trigger_ops = { + .set_trigger_state = &at91_adc_configure_trigger, + .reenable = &at91_adc_reenable_trigger, + .validate_device = iio_trigger_validate_own_device, +}; + +static int at91_adc_dma_size_done(struct at91_adc_state *st) +{ + struct dma_tx_state state; + enum dma_status status; + int i, size; + + status = dmaengine_tx_status(st->dma_st.dma_chan, + st->dma_st.dma_chan->cookie, + &state); + if (status != DMA_IN_PROGRESS) + return 0; + + /* Transferred length is size in bytes from end of buffer */ + i = st->dma_st.rx_buf_sz - state.residue; + + /* Return available bytes */ + if (i >= st->dma_st.buf_idx) + size = i - st->dma_st.buf_idx; + else + size = st->dma_st.rx_buf_sz + i - st->dma_st.buf_idx; + return size; +} + +static void at91_dma_buffer_done(void *data) +{ + struct iio_dev *indio_dev = data; + + iio_trigger_poll_nested(indio_dev->trig); +} + +static int at91_adc_dma_start(struct iio_dev *indio_dev) +{ + struct at91_adc_state *st = iio_priv(indio_dev); + struct dma_async_tx_descriptor *desc; + dma_cookie_t cookie; + int ret; + u8 bit; + + if (!st->dma_st.dma_chan) + return 0; + + /* we start a new DMA, so set buffer index to start */ + st->dma_st.buf_idx = 0; + + /* + * compute buffer size w.r.t. watermark and enabled channels. + * scan_bytes is aligned so we need an exact size for DMA + */ + st->dma_st.rx_buf_sz = 0; + + for_each_set_bit(bit, indio_dev->active_scan_mask, + indio_dev->num_channels) { + struct iio_chan_spec const *chan = + at91_adc_chan_get(indio_dev, bit); + + if (!chan) + continue; + + st->dma_st.rx_buf_sz += chan->scan_type.storagebits / 8; + } + st->dma_st.rx_buf_sz *= st->dma_st.watermark; + + /* Prepare a DMA cyclic transaction */ + desc = dmaengine_prep_dma_cyclic(st->dma_st.dma_chan, + st->dma_st.rx_dma_buf, + st->dma_st.rx_buf_sz, + st->dma_st.rx_buf_sz / 2, + DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT); + + if (!desc) { + dev_err(&indio_dev->dev, "cannot prepare DMA cyclic\n"); + return -EBUSY; + } + + desc->callback = at91_dma_buffer_done; + desc->callback_param = indio_dev; + + cookie = dmaengine_submit(desc); + ret = dma_submit_error(cookie); + if (ret) { + dev_err(&indio_dev->dev, "cannot submit DMA cyclic\n"); + dmaengine_terminate_async(st->dma_st.dma_chan); + return ret; + } + + /* enable general overrun error signaling */ + at91_adc_writel(st, IER, AT91_SAMA5D2_IER_GOVRE); + /* Issue pending DMA requests */ + dma_async_issue_pending(st->dma_st.dma_chan); + + /* consider current time as DMA start time for timestamps */ + st->dma_st.dma_ts = iio_get_time_ns(indio_dev); + + dev_dbg(&indio_dev->dev, "DMA cyclic started\n"); + + return 0; +} + +static bool at91_adc_buffer_check_use_irq(struct iio_dev *indio, + struct at91_adc_state *st) +{ + /* if using DMA, we do not use our own IRQ (we use DMA-controller) */ + if (st->dma_st.dma_chan) + return false; + /* if the trigger is not ours, then it has its own IRQ */ + if (iio_trigger_validate_own_device(indio->trig, indio)) + return false; + return true; +} + +static bool at91_adc_current_chan_is_touch(struct iio_dev *indio_dev) +{ + struct at91_adc_state *st = iio_priv(indio_dev); + + return !!bitmap_subset(indio_dev->active_scan_mask, + &st->touch_st.channels_bitmask, + st->soc_info.platform->max_index + 1); +} + +static int at91_adc_buffer_prepare(struct iio_dev *indio_dev) +{ + int ret; + u8 bit; + struct at91_adc_state *st = iio_priv(indio_dev); + + /* check if we are enabling triggered buffer or the touchscreen */ + if (at91_adc_current_chan_is_touch(indio_dev)) + return at91_adc_configure_touch(st, true); + + /* if we are not in triggered mode, we cannot enable the buffer. */ + if (!(iio_device_get_current_mode(indio_dev) & INDIO_ALL_TRIGGERED_MODES)) + return -EINVAL; + + ret = pm_runtime_resume_and_get(st->dev); + if (ret < 0) + return ret; + + /* we continue with the triggered buffer */ + ret = at91_adc_dma_start(indio_dev); + if (ret) { + dev_err(&indio_dev->dev, "buffer prepare failed\n"); + goto pm_runtime_put; + } + + for_each_set_bit(bit, indio_dev->active_scan_mask, + indio_dev->num_channels) { + struct iio_chan_spec const *chan = + at91_adc_chan_get(indio_dev, bit); + if (!chan) + continue; + /* these channel types cannot be handled by this trigger */ + if (chan->type == IIO_POSITIONRELATIVE || + chan->type == IIO_PRESSURE || + chan->type == IIO_TEMP) + continue; + + at91_adc_cor(st, chan); + + at91_adc_writel(st, CHER, BIT(chan->channel)); + } + + if (at91_adc_buffer_check_use_irq(indio_dev, st)) + at91_adc_writel(st, IER, AT91_SAMA5D2_IER_DRDY); + +pm_runtime_put: + pm_runtime_put_autosuspend(st->dev); + return ret; +} + +static int at91_adc_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct at91_adc_state *st = iio_priv(indio_dev); + int ret; + u8 bit; + + /* check if we are disabling triggered buffer or the touchscreen */ + if (at91_adc_current_chan_is_touch(indio_dev)) + return at91_adc_configure_touch(st, false); + + /* if we are not in triggered mode, nothing to do here */ + if (!(iio_device_get_current_mode(indio_dev) & INDIO_ALL_TRIGGERED_MODES)) + return -EINVAL; + + ret = pm_runtime_resume_and_get(st->dev); + if (ret < 0) + return ret; + + /* + * For each enable channel we must disable it in hardware. + * In the case of DMA, we must read the last converted value + * to clear EOC status and not get a possible interrupt later. + * This value is being read by DMA from LCDR anyway, so it's not lost. + */ + for_each_set_bit(bit, indio_dev->active_scan_mask, + indio_dev->num_channels) { + struct iio_chan_spec const *chan = + at91_adc_chan_get(indio_dev, bit); + + if (!chan) + continue; + /* these channel types are virtual, no need to do anything */ + if (chan->type == IIO_POSITIONRELATIVE || + chan->type == IIO_PRESSURE || + chan->type == IIO_TEMP) + continue; + + at91_adc_writel(st, CHDR, BIT(chan->channel)); + + if (st->dma_st.dma_chan) + at91_adc_read_chan(st, chan->address); + } + + if (at91_adc_buffer_check_use_irq(indio_dev, st)) + at91_adc_writel(st, IDR, AT91_SAMA5D2_IER_DRDY); + + /* read overflow register to clear possible overflow status */ + at91_adc_readl(st, OVER); + + /* if we are using DMA we must clear registers and end DMA */ + if (st->dma_st.dma_chan) + dmaengine_terminate_sync(st->dma_st.dma_chan); + + pm_runtime_put_autosuspend(st->dev); + + return 0; +} + +static const struct iio_buffer_setup_ops at91_buffer_setup_ops = { + .postdisable = &at91_adc_buffer_postdisable, +}; + +static struct iio_trigger *at91_adc_allocate_trigger(struct iio_dev *indio, + char *trigger_name) +{ + struct iio_trigger *trig; + int ret; + + trig = devm_iio_trigger_alloc(&indio->dev, "%s-dev%d-%s", indio->name, + iio_device_id(indio), trigger_name); + if (!trig) + return ERR_PTR(-ENOMEM); + + trig->dev.parent = indio->dev.parent; + iio_trigger_set_drvdata(trig, indio); + trig->ops = &at91_adc_trigger_ops; + + ret = devm_iio_trigger_register(&indio->dev, trig); + if (ret) + return ERR_PTR(ret); + + return trig; +} + +static void at91_adc_trigger_handler_nodma(struct iio_dev *indio_dev, + struct iio_poll_func *pf) +{ + struct at91_adc_state *st = iio_priv(indio_dev); + int i = 0; + int val; + u8 bit; + u32 mask = at91_adc_active_scan_mask_to_reg(indio_dev); + unsigned int timeout = 50; + u32 status, imr, eoc = 0, eoc_imr; + + /* + * Check if the conversion is ready. If not, wait a little bit, and + * in case of timeout exit with an error. + */ + while (((eoc & mask) != mask) && timeout) { + at91_adc_irq_status(st, &status, &eoc); + at91_adc_irq_mask(st, &imr, &eoc_imr); + usleep_range(50, 100); + timeout--; + } + + /* Cannot read data, not ready. Continue without reporting data */ + if (!timeout) + return; + + for_each_set_bit(bit, indio_dev->active_scan_mask, + indio_dev->num_channels) { + struct iio_chan_spec const *chan = + at91_adc_chan_get(indio_dev, bit); + + if (!chan) + continue; + /* + * Our external trigger only supports the voltage channels. + * In case someone requested a different type of channel + * just put zeroes to buffer. + * This should not happen because we check the scan mode + * and scan mask when we enable the buffer, and we don't allow + * the buffer to start with a mixed mask (voltage and something + * else). + * Thus, emit a warning. + */ + if (chan->type == IIO_VOLTAGE) { + val = at91_adc_read_chan(st, chan->address); + at91_adc_adjust_val_osr(st, &val); + st->buffer[i] = val; + } else { + st->buffer[i] = 0; + WARN(true, "This trigger cannot handle this type of channel"); + } + i++; + } + iio_push_to_buffers_with_timestamp(indio_dev, st->buffer, + pf->timestamp); +} + +static void at91_adc_trigger_handler_dma(struct iio_dev *indio_dev) +{ + struct at91_adc_state *st = iio_priv(indio_dev); + int transferred_len = at91_adc_dma_size_done(st); + s64 ns = iio_get_time_ns(indio_dev); + s64 interval; + int sample_index = 0, sample_count, sample_size; + + u32 status = at91_adc_readl(st, ISR); + /* if we reached this point, we cannot sample faster */ + if (status & AT91_SAMA5D2_IER_GOVRE) + pr_info_ratelimited("%s: conversion overrun detected\n", + indio_dev->name); + + sample_size = div_s64(st->dma_st.rx_buf_sz, st->dma_st.watermark); + + sample_count = div_s64(transferred_len, sample_size); + + /* + * interval between samples is total time since last transfer handling + * divided by the number of samples (total size divided by sample size) + */ + interval = div_s64((ns - st->dma_st.dma_ts), sample_count); + + while (transferred_len >= sample_size) { + /* + * for all the values in the current sample, + * adjust the values inside the buffer for oversampling + */ + at91_adc_adjust_val_osr_array(st, + &st->dma_st.rx_buf[st->dma_st.buf_idx], + sample_size); + + iio_push_to_buffers_with_timestamp(indio_dev, + (st->dma_st.rx_buf + st->dma_st.buf_idx), + (st->dma_st.dma_ts + interval * sample_index)); + /* adjust remaining length */ + transferred_len -= sample_size; + /* adjust buffer index */ + st->dma_st.buf_idx += sample_size; + /* in case of reaching end of buffer, reset index */ + if (st->dma_st.buf_idx >= st->dma_st.rx_buf_sz) + st->dma_st.buf_idx = 0; + sample_index++; + } + /* adjust saved time for next transfer handling */ + st->dma_st.dma_ts = iio_get_time_ns(indio_dev); +} + +static irqreturn_t at91_adc_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct at91_adc_state *st = iio_priv(indio_dev); + + /* + * If it's not our trigger, start a conversion now, as we are + * actually polling the trigger now. + */ + if (iio_trigger_validate_own_device(indio_dev->trig, indio_dev)) + at91_adc_writel(st, CR, AT91_SAMA5D2_CR_START); + + if (st->dma_st.dma_chan) + at91_adc_trigger_handler_dma(indio_dev); + else + at91_adc_trigger_handler_nodma(indio_dev, pf); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static unsigned at91_adc_startup_time(unsigned startup_time_min, + unsigned adc_clk_khz) +{ + static const unsigned int startup_lookup[] = { + 0, 8, 16, 24, + 64, 80, 96, 112, + 512, 576, 640, 704, + 768, 832, 896, 960 + }; + unsigned ticks_min, i; + + /* + * Since the adc frequency is checked before, there is no reason + * to not meet the startup time constraint. + */ + + ticks_min = startup_time_min * adc_clk_khz / 1000; + for (i = 0; i < ARRAY_SIZE(startup_lookup); i++) + if (startup_lookup[i] > ticks_min) + break; + + return i; +} + +static void at91_adc_setup_samp_freq(struct iio_dev *indio_dev, unsigned freq, + unsigned int startup_time, + unsigned int tracktim) +{ + struct at91_adc_state *st = iio_priv(indio_dev); + unsigned f_per, prescal, startup, mr; + int ret; + + f_per = clk_get_rate(st->per_clk); + prescal = (f_per / (2 * freq)) - 1; + + startup = at91_adc_startup_time(startup_time, freq / 1000); + + ret = pm_runtime_resume_and_get(st->dev); + if (ret < 0) + return; + + mr = at91_adc_readl(st, MR); + mr &= ~(AT91_SAMA5D2_MR_STARTUP_MASK | AT91_SAMA5D2_MR_PRESCAL_MASK); + mr |= AT91_SAMA5D2_MR_STARTUP(startup); + mr |= AT91_SAMA5D2_MR_PRESCAL(prescal); + mr |= AT91_SAMA5D2_MR_TRACKTIM(tracktim); + at91_adc_writel(st, MR, mr); + + pm_runtime_put_autosuspend(st->dev); + + dev_dbg(&indio_dev->dev, "freq: %u, startup: %u, prescal: %u, tracktim=%u\n", + freq, startup, prescal, tracktim); + st->current_sample_rate = freq; +} + +static inline unsigned at91_adc_get_sample_freq(struct at91_adc_state *st) +{ + return st->current_sample_rate; +} + +static void at91_adc_touch_data_handler(struct iio_dev *indio_dev) +{ + struct at91_adc_state *st = iio_priv(indio_dev); + u8 bit; + u16 val; + int i = 0; + + for_each_set_bit(bit, indio_dev->active_scan_mask, + st->soc_info.platform->max_index + 1) { + struct iio_chan_spec const *chan = + at91_adc_chan_get(indio_dev, bit); + + if (chan->type == IIO_POSITIONRELATIVE) + at91_adc_read_position(st, chan->channel, &val); + else if (chan->type == IIO_PRESSURE) + at91_adc_read_pressure(st, chan->channel, &val); + else + continue; + st->buffer[i] = val; + i++; + } + /* + * Schedule work to push to buffers. + * This is intended to push to the callback buffer that another driver + * registered. We are still in a handler from our IRQ. If we push + * directly, it means the other driver has it's callback called + * from our IRQ context. Which is something we better avoid. + * Let's schedule it after our IRQ is completed. + */ + schedule_work(&st->touch_st.workq); +} + +static void at91_adc_pen_detect_interrupt(struct at91_adc_state *st) +{ + at91_adc_writel(st, IDR, AT91_SAMA5D2_IER_PEN); + at91_adc_writel(st, IER, AT91_SAMA5D2_IER_NOPEN | + AT91_SAMA5D2_IER_XRDY | AT91_SAMA5D2_IER_YRDY | + AT91_SAMA5D2_IER_PRDY); + at91_adc_writel(st, TRGR, AT91_SAMA5D2_TRGR_TRGMOD_PERIODIC | + AT91_SAMA5D2_TRGR_TRGPER(st->touch_st.sample_period_val)); + st->touch_st.touching = true; +} + +static void at91_adc_no_pen_detect_interrupt(struct iio_dev *indio_dev) +{ + struct at91_adc_state *st = iio_priv(indio_dev); + + at91_adc_writel(st, TRGR, AT91_SAMA5D2_TRGR_TRGMOD_NO_TRIGGER); + at91_adc_writel(st, IDR, AT91_SAMA5D2_IER_NOPEN | + AT91_SAMA5D2_IER_XRDY | AT91_SAMA5D2_IER_YRDY | + AT91_SAMA5D2_IER_PRDY); + st->touch_st.touching = false; + + at91_adc_touch_data_handler(indio_dev); + + at91_adc_writel(st, IER, AT91_SAMA5D2_IER_PEN); +} + +static void at91_adc_workq_handler(struct work_struct *workq) +{ + struct at91_adc_touch *touch_st = container_of(workq, + struct at91_adc_touch, workq); + struct at91_adc_state *st = container_of(touch_st, + struct at91_adc_state, touch_st); + struct iio_dev *indio_dev = st->indio_dev; + + iio_push_to_buffers(indio_dev, st->buffer); +} + +static irqreturn_t at91_adc_interrupt(int irq, void *private) +{ + struct iio_dev *indio = private; + struct at91_adc_state *st = iio_priv(indio); + u32 status, eoc, imr, eoc_imr; + u32 rdy_mask = AT91_SAMA5D2_IER_XRDY | AT91_SAMA5D2_IER_YRDY | + AT91_SAMA5D2_IER_PRDY; + + at91_adc_irq_status(st, &status, &eoc); + at91_adc_irq_mask(st, &imr, &eoc_imr); + + if (!(status & imr) && !(eoc & eoc_imr)) + return IRQ_NONE; + if (status & AT91_SAMA5D2_IER_PEN) { + /* pen detected IRQ */ + at91_adc_pen_detect_interrupt(st); + } else if ((status & AT91_SAMA5D2_IER_NOPEN)) { + /* nopen detected IRQ */ + at91_adc_no_pen_detect_interrupt(indio); + } else if ((status & AT91_SAMA5D2_ISR_PENS) && + ((status & rdy_mask) == rdy_mask)) { + /* periodic trigger IRQ - during pen sense */ + at91_adc_touch_data_handler(indio); + } else if (status & AT91_SAMA5D2_ISR_PENS) { + /* + * touching, but the measurements are not ready yet. + * read and ignore. + */ + status = at91_adc_readl(st, XPOSR); + status = at91_adc_readl(st, YPOSR); + status = at91_adc_readl(st, PRESSR); + } else if (iio_buffer_enabled(indio) && + (status & AT91_SAMA5D2_IER_DRDY)) { + /* triggered buffer without DMA */ + disable_irq_nosync(irq); + iio_trigger_poll(indio->trig); + } else if (iio_buffer_enabled(indio) && st->dma_st.dma_chan) { + /* triggered buffer with DMA - should not happen */ + disable_irq_nosync(irq); + WARN(true, "Unexpected irq occurred\n"); + } else if (!iio_buffer_enabled(indio)) { + /* software requested conversion */ + st->conversion_value = at91_adc_read_chan(st, st->chan->address); + st->conversion_done = true; + wake_up_interruptible(&st->wq_data_available); + } + return IRQ_HANDLED; +} + +/* This needs to be called with direct mode claimed and st->lock locked. */ +static int at91_adc_read_info_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val) +{ + struct at91_adc_state *st = iio_priv(indio_dev); + u16 tmp_val; + int ret; + + ret = pm_runtime_resume_and_get(st->dev); + if (ret < 0) + return ret; + + /* + * Keep in mind that we cannot use software trigger or touchscreen + * if external trigger is enabled + */ + if (chan->type == IIO_POSITIONRELATIVE) { + ret = at91_adc_read_position(st, chan->channel, + &tmp_val); + *val = tmp_val; + if (ret > 0) + ret = at91_adc_adjust_val_osr(st, val); + + goto pm_runtime_put; + } + if (chan->type == IIO_PRESSURE) { + ret = at91_adc_read_pressure(st, chan->channel, + &tmp_val); + *val = tmp_val; + if (ret > 0) + ret = at91_adc_adjust_val_osr(st, val); + + goto pm_runtime_put; + } + + /* in this case we have a voltage or temperature channel */ + + st->chan = chan; + + at91_adc_cor(st, chan); + at91_adc_writel(st, CHER, BIT(chan->channel)); + /* + * TEMPMR.TEMPON needs to update after CHER otherwise if none + * of the channels are enabled and TEMPMR.TEMPON = 1 will + * trigger DRDY interruption while preparing for temperature read. + */ + if (chan->type == IIO_TEMP) + at91_adc_writel(st, TEMPMR, AT91_SAMA5D2_TEMPMR_TEMPON); + at91_adc_eoc_ena(st, chan->channel); + at91_adc_writel(st, CR, AT91_SAMA5D2_CR_START); + + ret = wait_event_interruptible_timeout(st->wq_data_available, + st->conversion_done, + msecs_to_jiffies(1000)); + if (ret == 0) + ret = -ETIMEDOUT; + + if (ret > 0) { + *val = st->conversion_value; + ret = at91_adc_adjust_val_osr(st, val); + if (chan->scan_type.sign == 's') + *val = sign_extend32(*val, + chan->scan_type.realbits - 1); + st->conversion_done = false; + } + + at91_adc_eoc_dis(st, st->chan->channel); + if (chan->type == IIO_TEMP) + at91_adc_writel(st, TEMPMR, 0U); + at91_adc_writel(st, CHDR, BIT(chan->channel)); + + /* Needed to ACK the DRDY interruption */ + at91_adc_readl(st, LCDR); + +pm_runtime_put: + pm_runtime_put_autosuspend(st->dev); + return ret; +} + +static int at91_adc_read_info_locked(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val) +{ + struct at91_adc_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->lock); + + return at91_adc_read_info_raw(indio_dev, chan, val); +} + +static void at91_adc_temp_sensor_configure(struct at91_adc_state *st, + bool start) +{ + u32 sample_rate, oversampling_ratio; + u32 startup_time, tracktim, trackx; + + if (start) { + /* + * Configure the sensor for best accuracy: 10MHz frequency, + * oversampling rate of 256, tracktim=0xf and trackx=1. + */ + sample_rate = 10 * MEGA; + oversampling_ratio = 256; + startup_time = AT91_SAMA5D2_MR_STARTUP_TS_MIN; + tracktim = AT91_SAMA5D2_MR_TRACKTIM_TS; + trackx = AT91_SAMA5D2_TRACKX_TS; + + st->temp_st.saved_sample_rate = st->current_sample_rate; + st->temp_st.saved_oversampling = st->oversampling_ratio; + } else { + /* Go back to previous settings. */ + sample_rate = st->temp_st.saved_sample_rate; + oversampling_ratio = st->temp_st.saved_oversampling; + startup_time = st->soc_info.startup_time; + tracktim = 0; + trackx = 0; + } + + at91_adc_setup_samp_freq(st->indio_dev, sample_rate, startup_time, + tracktim); + at91_adc_config_emr(st, oversampling_ratio, trackx); +} + +static int at91_adc_read_temp(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val) +{ + struct at91_adc_state *st = iio_priv(indio_dev); + struct at91_adc_temp_sensor_clb *clb = &st->soc_info.temp_sensor_clb; + u64 div1, div2; + u32 tmp; + int ret, vbg, vtemp; + + guard(mutex)(&st->lock); + + ret = pm_runtime_resume_and_get(st->dev); + if (ret < 0) + return ret; + + at91_adc_temp_sensor_configure(st, true); + + /* Read VBG. */ + tmp = at91_adc_readl(st, ACR); + tmp |= AT91_SAMA5D2_ACR_SRCLCH; + at91_adc_writel(st, ACR, tmp); + ret = at91_adc_read_info_raw(indio_dev, chan, &vbg); + if (ret < 0) + goto restore_config; + + /* Read VTEMP. */ + tmp &= ~AT91_SAMA5D2_ACR_SRCLCH; + at91_adc_writel(st, ACR, tmp); + ret = at91_adc_read_info_raw(indio_dev, chan, &vtemp); + +restore_config: + /* Revert previous settings. */ + at91_adc_temp_sensor_configure(st, false); + pm_runtime_put_autosuspend(st->dev); + if (ret < 0) + return ret; + + /* + * Temp[milli] = p1[milli] + (vtemp * clb->p6 - clb->p4 * vbg)/ + * (vbg * AT91_ADC_TS_VTEMP_DT) + */ + div1 = DIV_ROUND_CLOSEST_ULL(((u64)vtemp * clb->p6), vbg); + div1 = DIV_ROUND_CLOSEST_ULL((div1 * 1000), AT91_ADC_TS_VTEMP_DT); + div2 = DIV_ROUND_CLOSEST_ULL((u64)clb->p4, AT91_ADC_TS_VTEMP_DT); + div2 *= 1000; + *val = clb->p1 + (int)div1 - (int)div2; + + return ret; +} + +static int at91_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct at91_adc_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = at91_adc_read_info_locked(indio_dev, chan, val); + iio_device_release_direct(indio_dev); + return ret; + + case IIO_CHAN_INFO_SCALE: + *val = st->vref_uv / 1000; + if (chan->differential) + *val *= 2; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_PROCESSED: + if (chan->type != IIO_TEMP) + return -EINVAL; + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = at91_adc_read_temp(indio_dev, chan, val); + iio_device_release_direct(indio_dev); + + return ret; + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = at91_adc_get_sample_freq(st); + return IIO_VAL_INT; + + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = st->oversampling_ratio; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int at91_adc_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct at91_adc_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + /* if no change, optimize out */ + if (val == st->oversampling_ratio) + return 0; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + mutex_lock(&st->lock); + /* update ratio */ + ret = at91_adc_config_emr(st, val, 0); + mutex_unlock(&st->lock); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + if (val < st->soc_info.min_sample_rate || + val > st->soc_info.max_sample_rate) + return -EINVAL; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + mutex_lock(&st->lock); + at91_adc_setup_samp_freq(indio_dev, val, + st->soc_info.startup_time, 0); + mutex_unlock(&st->lock); + iio_device_release_direct(indio_dev); + return 0; + default: + return -EINVAL; + } +} + +static int at91_adc_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct at91_adc_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = (int *)st->soc_info.platform->oversampling_avail; + *type = IIO_VAL_INT; + *length = st->soc_info.platform->oversampling_avail_no; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static void at91_adc_dma_init(struct at91_adc_state *st) +{ + struct device *dev = &st->indio_dev->dev; + struct dma_slave_config config = {0}; + /* we have 2 bytes for each channel */ + unsigned int sample_size = st->soc_info.platform->nr_channels * 2; + /* + * We make the buffer double the size of the fifo, + * such that DMA uses one half of the buffer (full fifo size) + * and the software uses the other half to read/write. + */ + unsigned int pages = DIV_ROUND_UP(AT91_HWFIFO_MAX_SIZE * + sample_size * 2, PAGE_SIZE); + + if (st->dma_st.dma_chan) + return; + + st->dma_st.dma_chan = dma_request_chan(dev, "rx"); + if (IS_ERR(st->dma_st.dma_chan)) { + dev_info(dev, "can't get DMA channel\n"); + st->dma_st.dma_chan = NULL; + goto dma_exit; + } + + st->dma_st.rx_buf = dma_alloc_coherent(st->dma_st.dma_chan->device->dev, + pages * PAGE_SIZE, + &st->dma_st.rx_dma_buf, + GFP_KERNEL); + if (!st->dma_st.rx_buf) { + dev_info(dev, "can't allocate coherent DMA area\n"); + goto dma_chan_disable; + } + + /* Configure DMA channel to read data register */ + config.direction = DMA_DEV_TO_MEM; + config.src_addr = (phys_addr_t)(st->dma_st.phys_addr + + st->soc_info.platform->layout->LCDR); + config.src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + config.src_maxburst = 1; + config.dst_maxburst = 1; + + if (dmaengine_slave_config(st->dma_st.dma_chan, &config)) { + dev_info(dev, "can't configure DMA slave\n"); + goto dma_free_area; + } + + dev_info(dev, "using %s for rx DMA transfers\n", + dma_chan_name(st->dma_st.dma_chan)); + + return; + +dma_free_area: + dma_free_coherent(st->dma_st.dma_chan->device->dev, pages * PAGE_SIZE, + st->dma_st.rx_buf, st->dma_st.rx_dma_buf); +dma_chan_disable: + dma_release_channel(st->dma_st.dma_chan); + st->dma_st.dma_chan = NULL; +dma_exit: + dev_info(dev, "continuing without DMA support\n"); +} + +static void at91_adc_dma_disable(struct at91_adc_state *st) +{ + struct device *dev = &st->indio_dev->dev; + /* we have 2 bytes for each channel */ + unsigned int sample_size = st->soc_info.platform->nr_channels * 2; + unsigned int pages = DIV_ROUND_UP(AT91_HWFIFO_MAX_SIZE * + sample_size * 2, PAGE_SIZE); + + /* if we are not using DMA, just return */ + if (!st->dma_st.dma_chan) + return; + + /* wait for all transactions to be terminated first*/ + dmaengine_terminate_sync(st->dma_st.dma_chan); + + dma_free_coherent(st->dma_st.dma_chan->device->dev, pages * PAGE_SIZE, + st->dma_st.rx_buf, st->dma_st.rx_dma_buf); + dma_release_channel(st->dma_st.dma_chan); + st->dma_st.dma_chan = NULL; + + dev_info(dev, "continuing without DMA support\n"); +} + +static int at91_adc_set_watermark(struct iio_dev *indio_dev, unsigned int val) +{ + struct at91_adc_state *st = iio_priv(indio_dev); + int ret; + + if (val > AT91_HWFIFO_MAX_SIZE) + val = AT91_HWFIFO_MAX_SIZE; + + if (!st->selected_trig->hw_trig) { + dev_dbg(&indio_dev->dev, "we need hw trigger for DMA\n"); + return 0; + } + + dev_dbg(&indio_dev->dev, "new watermark is %u\n", val); + st->dma_st.watermark = val; + + /* + * The logic here is: if we have watermark 1, it means we do + * each conversion with it's own IRQ, thus we don't need DMA. + * If the watermark is higher, we do DMA to do all the transfers in bulk + */ + + if (val == 1) + at91_adc_dma_disable(st); + else if (val > 1) + at91_adc_dma_init(st); + + /* + * We can start the DMA only after setting the watermark and + * having the DMA initialization completed + */ + ret = at91_adc_buffer_prepare(indio_dev); + if (ret) + at91_adc_dma_disable(st); + + return ret; +} + +static int at91_adc_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct at91_adc_state *st = iio_priv(indio_dev); + + if (bitmap_subset(scan_mask, &st->touch_st.channels_bitmask, + st->soc_info.platform->max_index + 1)) + return 0; + /* + * if the new bitmap is a combination of touchscreen and regular + * channels, then we are not fine + */ + if (bitmap_intersects(&st->touch_st.channels_bitmask, scan_mask, + st->soc_info.platform->max_index + 1)) + return -EINVAL; + return 0; +} + +static void at91_adc_hw_init(struct iio_dev *indio_dev) +{ + struct at91_adc_state *st = iio_priv(indio_dev); + + at91_adc_writel(st, CR, AT91_SAMA5D2_CR_SWRST); + if (st->soc_info.platform->layout->EOC_IDR) + at91_adc_writel(st, EOC_IDR, 0xffffffff); + at91_adc_writel(st, IDR, 0xffffffff); + /* + * Transfer field must be set to 2 according to the datasheet and + * allows different analog settings for each channel. + */ + at91_adc_writel(st, MR, + AT91_SAMA5D2_MR_TRANSFER(2) | AT91_SAMA5D2_MR_ANACH); + + at91_adc_setup_samp_freq(indio_dev, st->soc_info.min_sample_rate, + st->soc_info.startup_time, 0); + + /* configure extended mode register */ + at91_adc_config_emr(st, st->oversampling_ratio, 0); +} + +static ssize_t at91_adc_get_fifo_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct at91_adc_state *st = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", !!st->dma_st.dma_chan); +} + +static ssize_t at91_adc_get_watermark(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct at91_adc_state *st = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", st->dma_st.watermark); +} + +static IIO_DEVICE_ATTR(hwfifo_enabled, 0444, + at91_adc_get_fifo_state, NULL, 0); +static IIO_DEVICE_ATTR(hwfifo_watermark, 0444, + at91_adc_get_watermark, NULL, 0); + +IIO_STATIC_CONST_DEVICE_ATTR(hwfifo_watermark_min, "2"); +IIO_STATIC_CONST_DEVICE_ATTR(hwfifo_watermark_max, AT91_HWFIFO_MAX_SIZE_STR); + +static const struct iio_dev_attr *at91_adc_fifo_attributes[] = { + &iio_dev_attr_hwfifo_watermark_min, + &iio_dev_attr_hwfifo_watermark_max, + &iio_dev_attr_hwfifo_watermark, + &iio_dev_attr_hwfifo_enabled, + NULL, +}; + +static const struct iio_info at91_adc_info = { + .read_avail = &at91_adc_read_avail, + .read_raw = &at91_adc_read_raw, + .write_raw = &at91_adc_write_raw, + .update_scan_mode = &at91_adc_update_scan_mode, + .fwnode_xlate = &at91_adc_fwnode_xlate, + .hwfifo_set_watermark = &at91_adc_set_watermark, +}; + +static int at91_adc_buffer_and_trigger_init(struct device *dev, + struct iio_dev *indio) +{ + struct at91_adc_state *st = iio_priv(indio); + const struct iio_dev_attr **fifo_attrs; + int ret; + + if (st->selected_trig->hw_trig) + fifo_attrs = at91_adc_fifo_attributes; + else + fifo_attrs = NULL; + + ret = devm_iio_triggered_buffer_setup_ext(&indio->dev, indio, + &iio_pollfunc_store_time, &at91_adc_trigger_handler, + IIO_BUFFER_DIRECTION_IN, &at91_buffer_setup_ops, fifo_attrs); + if (ret < 0) { + dev_err(dev, "couldn't initialize the buffer.\n"); + return ret; + } + + if (!st->selected_trig->hw_trig) + return 0; + + st->trig = at91_adc_allocate_trigger(indio, st->selected_trig->name); + if (IS_ERR(st->trig)) { + dev_err(dev, "could not allocate trigger\n"); + return PTR_ERR(st->trig); + } + + /* + * Initially the iio buffer has a length of 2 and + * a watermark of 1 + */ + st->dma_st.watermark = 1; + + return 0; +} + +static int at91_adc_temp_sensor_init(struct at91_adc_state *st, + struct device *dev) +{ + struct at91_adc_temp_sensor_clb *clb = &st->soc_info.temp_sensor_clb; + struct nvmem_cell *temp_calib; + u32 *buf; + size_t len; + int ret = 0; + + if (!st->soc_info.platform->temp_sensor) + return 0; + + /* Get the calibration data from NVMEM. */ + temp_calib = nvmem_cell_get(dev, "temperature_calib"); + if (IS_ERR(temp_calib)) { + ret = PTR_ERR(temp_calib); + if (ret != -ENOENT) + dev_err(dev, "Failed to get temperature_calib cell!\n"); + return ret; + } + + buf = nvmem_cell_read(temp_calib, &len); + nvmem_cell_put(temp_calib); + if (IS_ERR(buf)) { + dev_err(dev, "Failed to read calibration data!\n"); + return PTR_ERR(buf); + } + if (len < AT91_ADC_TS_CLB_IDX_MAX * 4) { + dev_err(dev, "Invalid calibration data!\n"); + ret = -EINVAL; + goto free_buf; + } + + /* Store calibration data for later use. */ + clb->p1 = buf[AT91_ADC_TS_CLB_IDX_P1]; + clb->p4 = buf[AT91_ADC_TS_CLB_IDX_P4]; + clb->p6 = buf[AT91_ADC_TS_CLB_IDX_P6]; + + /* + * We prepare here the conversion to milli to avoid doing it on hotpath. + */ + clb->p1 = clb->p1 * 1000; + +free_buf: + kfree(buf); + return ret; +} + +static int at91_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct at91_adc_state *st; + struct resource *res; + int ret, i, num_channels; + u32 edge_type = IRQ_TYPE_NONE; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->indio_dev = indio_dev; + + st->soc_info.platform = device_get_match_data(dev); + + ret = at91_adc_temp_sensor_init(st, &pdev->dev); + /* Don't register temperature channel if initialization failed. */ + if (ret) + num_channels = st->soc_info.platform->max_channels - 1; + else + num_channels = st->soc_info.platform->max_channels; + + indio_dev->name = dev_name(&pdev->dev); + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + indio_dev->info = &at91_adc_info; + indio_dev->channels = *st->soc_info.platform->adc_channels; + indio_dev->num_channels = num_channels; + + bitmap_set(&st->touch_st.channels_bitmask, + st->soc_info.platform->touch_chan_x, 1); + bitmap_set(&st->touch_st.channels_bitmask, + st->soc_info.platform->touch_chan_y, 1); + bitmap_set(&st->touch_st.channels_bitmask, + st->soc_info.platform->touch_chan_p, 1); + + st->oversampling_ratio = 1; + + ret = device_property_read_u32(dev, "atmel,min-sample-rate-hz", + &st->soc_info.min_sample_rate); + if (ret) { + dev_err(&pdev->dev, + "invalid or missing value for atmel,min-sample-rate-hz\n"); + return ret; + } + + ret = device_property_read_u32(dev, "atmel,max-sample-rate-hz", + &st->soc_info.max_sample_rate); + if (ret) { + dev_err(&pdev->dev, + "invalid or missing value for atmel,max-sample-rate-hz\n"); + return ret; + } + + ret = device_property_read_u32(dev, "atmel,startup-time-ms", + &st->soc_info.startup_time); + if (ret) { + dev_err(&pdev->dev, + "invalid or missing value for atmel,startup-time-ms\n"); + return ret; + } + + ret = device_property_read_u32(dev, "atmel,trigger-edge-type", + &edge_type); + if (ret) { + dev_dbg(&pdev->dev, + "atmel,trigger-edge-type not specified, only software trigger available\n"); + } + + st->selected_trig = NULL; + + /* find the right trigger, or no trigger at all */ + for (i = 0; i < st->soc_info.platform->hw_trig_cnt + 1; i++) + if (at91_adc_trigger_list[i].edge_type == edge_type) { + st->selected_trig = &at91_adc_trigger_list[i]; + break; + } + + if (!st->selected_trig) { + dev_err(&pdev->dev, "invalid external trigger edge value\n"); + return -EINVAL; + } + + init_waitqueue_head(&st->wq_data_available); + mutex_init(&st->lock); + INIT_WORK(&st->touch_st.workq, at91_adc_workq_handler); + + st->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(st->base)) + return PTR_ERR(st->base); + + /* if we plan to use DMA, we need the physical address of the regs */ + st->dma_st.phys_addr = res->start; + + st->irq = platform_get_irq(pdev, 0); + if (st->irq < 0) + return st->irq; + + st->per_clk = devm_clk_get(&pdev->dev, "adc_clk"); + if (IS_ERR(st->per_clk)) + return PTR_ERR(st->per_clk); + + st->reg = devm_regulator_get(&pdev->dev, "vddana"); + if (IS_ERR(st->reg)) + return PTR_ERR(st->reg); + + st->vref = devm_regulator_get(&pdev->dev, "vref"); + if (IS_ERR(st->vref)) + return PTR_ERR(st->vref); + + ret = devm_request_irq(&pdev->dev, st->irq, at91_adc_interrupt, 0, + pdev->dev.driver->name, indio_dev); + if (ret) + return ret; + + ret = regulator_enable(st->reg); + if (ret) + return ret; + + ret = regulator_enable(st->vref); + if (ret) + goto reg_disable; + + st->vref_uv = regulator_get_voltage(st->vref); + if (st->vref_uv <= 0) { + ret = -EINVAL; + goto vref_disable; + } + + ret = clk_prepare_enable(st->per_clk); + if (ret) + goto vref_disable; + + platform_set_drvdata(pdev, indio_dev); + st->dev = &pdev->dev; + pm_runtime_set_autosuspend_delay(st->dev, 500); + pm_runtime_use_autosuspend(st->dev); + pm_runtime_set_active(st->dev); + pm_runtime_enable(st->dev); + pm_runtime_get_noresume(st->dev); + + at91_adc_hw_init(indio_dev); + + ret = at91_adc_buffer_and_trigger_init(&pdev->dev, indio_dev); + if (ret < 0) + goto err_pm_disable; + + if (dma_coerce_mask_and_coherent(&indio_dev->dev, DMA_BIT_MASK(32))) + dev_info(&pdev->dev, "cannot set DMA mask to 32-bit\n"); + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto dma_disable; + + if (st->selected_trig->hw_trig) + dev_info(&pdev->dev, "setting up trigger as %s\n", + st->selected_trig->name); + + dev_info(&pdev->dev, "version: %x\n", + readl_relaxed(st->base + st->soc_info.platform->layout->VERSION)); + + pm_runtime_put_autosuspend(st->dev); + + return 0; + +dma_disable: + at91_adc_dma_disable(st); +err_pm_disable: + pm_runtime_put_noidle(st->dev); + pm_runtime_disable(st->dev); + pm_runtime_set_suspended(st->dev); + pm_runtime_dont_use_autosuspend(st->dev); + clk_disable_unprepare(st->per_clk); +vref_disable: + regulator_disable(st->vref); +reg_disable: + regulator_disable(st->reg); + return ret; +} + +static void at91_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct at91_adc_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + cancel_work_sync(&st->touch_st.workq); + + at91_adc_dma_disable(st); + + pm_runtime_disable(st->dev); + pm_runtime_set_suspended(st->dev); + clk_disable_unprepare(st->per_clk); + + regulator_disable(st->vref); + regulator_disable(st->reg); +} + +static int at91_adc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct at91_adc_state *st = iio_priv(indio_dev); + int ret; + + ret = pm_runtime_resume_and_get(st->dev); + if (ret < 0) + return ret; + + if (iio_buffer_enabled(indio_dev)) + at91_adc_buffer_postdisable(indio_dev); + + /* + * Do a software reset of the ADC before we go to suspend. + * this will ensure that all pins are free from being muxed by the ADC + * and can be used by for other devices. + * Otherwise, ADC will hog them and we can't go to suspend mode. + */ + at91_adc_writel(st, CR, AT91_SAMA5D2_CR_SWRST); + + pm_runtime_mark_last_busy(st->dev); + pm_runtime_put_noidle(st->dev); + clk_disable_unprepare(st->per_clk); + regulator_disable(st->vref); + regulator_disable(st->reg); + + return pinctrl_pm_select_sleep_state(dev); +} + +static int at91_adc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct at91_adc_state *st = iio_priv(indio_dev); + int ret; + + ret = pinctrl_pm_select_default_state(dev); + if (ret) + goto resume_failed; + + ret = regulator_enable(st->reg); + if (ret) + goto resume_failed; + + ret = regulator_enable(st->vref); + if (ret) + goto reg_disable_resume; + + ret = clk_prepare_enable(st->per_clk); + if (ret) + goto vref_disable_resume; + + pm_runtime_get_noresume(st->dev); + + at91_adc_hw_init(indio_dev); + + /* reconfiguring trigger hardware state */ + if (iio_buffer_enabled(indio_dev)) { + ret = at91_adc_buffer_prepare(indio_dev); + if (ret) + goto pm_runtime_put; + + at91_adc_configure_trigger_registers(st, true); + } + + pm_runtime_put_autosuspend(st->dev); + + return 0; + +pm_runtime_put: + pm_runtime_mark_last_busy(st->dev); + pm_runtime_put_noidle(st->dev); + clk_disable_unprepare(st->per_clk); +vref_disable_resume: + regulator_disable(st->vref); +reg_disable_resume: + regulator_disable(st->reg); +resume_failed: + dev_err(&indio_dev->dev, "failed to resume\n"); + return ret; +} + +static int at91_adc_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct at91_adc_state *st = iio_priv(indio_dev); + + clk_disable(st->per_clk); + + return 0; +} + +static int at91_adc_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct at91_adc_state *st = iio_priv(indio_dev); + + return clk_enable(st->per_clk); +} + +static const struct dev_pm_ops at91_adc_pm_ops = { + SYSTEM_SLEEP_PM_OPS(at91_adc_suspend, at91_adc_resume) + RUNTIME_PM_OPS(at91_adc_runtime_suspend, at91_adc_runtime_resume, + NULL) +}; + +static const struct of_device_id at91_adc_dt_match[] = { + { + .compatible = "atmel,sama5d2-adc", + .data = (const void *)&sama5d2_platform, + }, { + .compatible = "microchip,sama7g5-adc", + .data = (const void *)&sama7g5_platform, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, at91_adc_dt_match); + +static struct platform_driver at91_adc_driver = { + .probe = at91_adc_probe, + .remove = at91_adc_remove, + .driver = { + .name = "at91-sama5d2_adc", + .of_match_table = at91_adc_dt_match, + .pm = pm_ptr(&at91_adc_pm_ops), + }, +}; +module_platform_driver(at91_adc_driver) + +MODULE_AUTHOR("Ludovic Desroches "); +MODULE_AUTHOR("Eugen Hristev +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* Registers */ +#define AT91_ADC_CR 0x00 /* Control Register */ +#define AT91_ADC_SWRST (1 << 0) /* Software Reset */ +#define AT91_ADC_START (1 << 1) /* Start Conversion */ + +#define AT91_ADC_MR 0x04 /* Mode Register */ +#define AT91_ADC_TSAMOD (3 << 0) /* ADC mode */ +#define AT91_ADC_TSAMOD_ADC_ONLY_MODE (0 << 0) /* ADC Mode */ +#define AT91_ADC_TSAMOD_TS_ONLY_MODE (1 << 0) /* Touch Screen Only Mode */ +#define AT91_ADC_TRGEN (1 << 0) /* Trigger Enable */ +#define AT91_ADC_TRGSEL (7 << 1) /* Trigger Selection */ +#define AT91_ADC_TRGSEL_TC0 (0 << 1) +#define AT91_ADC_TRGSEL_TC1 (1 << 1) +#define AT91_ADC_TRGSEL_TC2 (2 << 1) +#define AT91_ADC_TRGSEL_EXTERNAL (6 << 1) +#define AT91_ADC_LOWRES (1 << 4) /* Low Resolution */ +#define AT91_ADC_SLEEP (1 << 5) /* Sleep Mode */ +#define AT91_ADC_PENDET (1 << 6) /* Pen contact detection enable */ +#define AT91_ADC_PRESCAL_9260 (0x3f << 8) /* Prescalar Rate Selection */ +#define AT91_ADC_PRESCAL_9G45 (0xff << 8) +#define AT91_ADC_PRESCAL_(x) ((x) << 8) +#define AT91_ADC_STARTUP_9260 (0x1f << 16) /* Startup Up Time */ +#define AT91_ADC_STARTUP_9G45 (0x7f << 16) +#define AT91_ADC_STARTUP_9X5 (0xf << 16) +#define AT91_ADC_STARTUP_(x) ((x) << 16) +#define AT91_ADC_SHTIM (0xf << 24) /* Sample & Hold Time */ +#define AT91_ADC_SHTIM_(x) ((x) << 24) +#define AT91_ADC_PENDBC (0x0f << 28) /* Pen Debounce time */ +#define AT91_ADC_PENDBC_(x) ((x) << 28) + +#define AT91_ADC_TSR 0x0C +#define AT91_ADC_TSR_SHTIM (0xf << 24) /* Sample & Hold Time */ +#define AT91_ADC_TSR_SHTIM_(x) ((x) << 24) + +#define AT91_ADC_CHER 0x10 /* Channel Enable Register */ +#define AT91_ADC_CHDR 0x14 /* Channel Disable Register */ +#define AT91_ADC_CHSR 0x18 /* Channel Status Register */ +#define AT91_ADC_CH(n) (1 << (n)) /* Channel Number */ + +#define AT91_ADC_SR 0x1C /* Status Register */ +#define AT91_ADC_EOC(n) (1 << (n)) /* End of Conversion on Channel N */ +#define AT91_ADC_OVRE(n) (1 << ((n) + 8))/* Overrun Error on Channel N */ +#define AT91_ADC_DRDY (1 << 16) /* Data Ready */ +#define AT91_ADC_GOVRE (1 << 17) /* General Overrun Error */ +#define AT91_ADC_ENDRX (1 << 18) /* End of RX Buffer */ +#define AT91_ADC_RXFUFF (1 << 19) /* RX Buffer Full */ + +#define AT91_ADC_SR_9X5 0x30 /* Status Register for 9x5 */ +#define AT91_ADC_SR_DRDY_9X5 (1 << 24) /* Data Ready */ + +#define AT91_ADC_LCDR 0x20 /* Last Converted Data Register */ +#define AT91_ADC_LDATA (0x3ff) + +#define AT91_ADC_IER 0x24 /* Interrupt Enable Register */ +#define AT91_ADC_IDR 0x28 /* Interrupt Disable Register */ +#define AT91_ADC_IMR 0x2C /* Interrupt Mask Register */ +#define AT91RL_ADC_IER_PEN (1 << 20) +#define AT91RL_ADC_IER_NOPEN (1 << 21) +#define AT91_ADC_IER_PEN (1 << 29) +#define AT91_ADC_IER_NOPEN (1 << 30) +#define AT91_ADC_IER_XRDY (1 << 20) +#define AT91_ADC_IER_YRDY (1 << 21) +#define AT91_ADC_IER_PRDY (1 << 22) +#define AT91_ADC_ISR_PENS (1 << 31) + +#define AT91_ADC_CHR(n) (0x30 + ((n) * 4)) /* Channel Data Register N */ +#define AT91_ADC_DATA (0x3ff) + +#define AT91_ADC_CDR0_9X5 (0x50) /* Channel Data Register 0 for 9X5 */ + +#define AT91_ADC_ACR 0x94 /* Analog Control Register */ +#define AT91_ADC_ACR_PENDETSENS (0x3 << 0) /* pull-up resistor */ + +#define AT91_ADC_TSMR 0xB0 +#define AT91_ADC_TSMR_TSMODE (3 << 0) /* Touch Screen Mode */ +#define AT91_ADC_TSMR_TSMODE_NONE (0 << 0) +#define AT91_ADC_TSMR_TSMODE_4WIRE_NO_PRESS (1 << 0) +#define AT91_ADC_TSMR_TSMODE_4WIRE_PRESS (2 << 0) +#define AT91_ADC_TSMR_TSMODE_5WIRE (3 << 0) +#define AT91_ADC_TSMR_TSAV (3 << 4) /* Averages samples */ +#define AT91_ADC_TSMR_TSAV_(x) ((x) << 4) +#define AT91_ADC_TSMR_SCTIM (0x0f << 16) /* Switch closure time */ +#define AT91_ADC_TSMR_SCTIM_(x) ((x) << 16) +#define AT91_ADC_TSMR_PENDBC (0x0f << 28) /* Pen Debounce time */ +#define AT91_ADC_TSMR_PENDBC_(x) ((x) << 28) +#define AT91_ADC_TSMR_NOTSDMA (1 << 22) /* No Touchscreen DMA */ +#define AT91_ADC_TSMR_PENDET_DIS (0 << 24) /* Pen contact detection disable */ +#define AT91_ADC_TSMR_PENDET_ENA (1 << 24) /* Pen contact detection enable */ + +#define AT91_ADC_TSXPOSR 0xB4 +#define AT91_ADC_TSYPOSR 0xB8 +#define AT91_ADC_TSPRESSR 0xBC + +#define AT91_ADC_TRGR_9260 AT91_ADC_MR +#define AT91_ADC_TRGR_9G45 0x08 +#define AT91_ADC_TRGR_9X5 0xC0 + +/* Trigger Register bit field */ +#define AT91_ADC_TRGR_TRGPER (0xffff << 16) +#define AT91_ADC_TRGR_TRGPER_(x) ((x) << 16) +#define AT91_ADC_TRGR_TRGMOD (0x7 << 0) +#define AT91_ADC_TRGR_NONE (0 << 0) +#define AT91_ADC_TRGR_MOD_PERIOD_TRIG (5 << 0) + +#define AT91_ADC_CHAN(st, ch) \ + (st->registers->channel_base + (ch * 4)) +#define at91_adc_readl(st, reg) \ + (readl_relaxed(st->reg_base + reg)) +#define at91_adc_writel(st, reg, val) \ + (writel_relaxed(val, st->reg_base + reg)) + +#define DRIVER_NAME "at91_adc" +#define MAX_POS_BITS 12 + +#define TOUCH_SAMPLE_PERIOD_US 2000 /* 2ms */ +#define TOUCH_PEN_DETECT_DEBOUNCE_US 200 + +#define MAX_RLPOS_BITS 10 +#define TOUCH_SAMPLE_PERIOD_US_RL 10000 /* 10ms, the SoC can't keep up with 2ms */ +#define TOUCH_SHTIM 0xa +#define TOUCH_SCTIM_US 10 /* 10us for the Touchscreen Switches Closure Time */ + +enum atmel_adc_ts_type { + ATMEL_ADC_TOUCHSCREEN_NONE = 0, + ATMEL_ADC_TOUCHSCREEN_4WIRE = 4, + ATMEL_ADC_TOUCHSCREEN_5WIRE = 5, +}; + +/** + * struct at91_adc_trigger - description of triggers + * @name: name of the trigger advertised to the user + * @value: value to set in the ADC's trigger setup register + * to enable the trigger + * @is_external: Does the trigger rely on an external pin? + */ +struct at91_adc_trigger { + const char *name; + u8 value; + bool is_external; +}; + +/** + * struct at91_adc_reg_desc - Various information relative to registers + * @channel_base: Base offset for the channel data registers + * @drdy_mask: Mask of the DRDY field in the relevant registers + * (Interruptions registers mostly) + * @status_register: Offset of the Interrupt Status Register + * @trigger_register: Offset of the Trigger setup register + * @mr_prescal_mask: Mask of the PRESCAL field in the adc MR register + * @mr_startup_mask: Mask of the STARTUP field in the adc MR register + */ +struct at91_adc_reg_desc { + u8 channel_base; + u32 drdy_mask; + u8 status_register; + u8 trigger_register; + u32 mr_prescal_mask; + u32 mr_startup_mask; +}; + +struct at91_adc_caps { + bool has_ts; /* Support touch screen */ + bool has_tsmr; /* only at91sam9x5, sama5d3 have TSMR reg */ + /* + * Numbers of sampling data will be averaged. Can be 0~3. + * Hardware can average (2 ^ ts_filter_average) sample data. + */ + u8 ts_filter_average; + /* Pen Detection input pull-up resistor, can be 0~3 */ + u8 ts_pen_detect_sensitivity; + + /* startup time calculate function */ + u32 (*calc_startup_ticks)(u32 startup_time, u32 adc_clk_khz); + + u8 num_channels; + + u8 low_res_bits; + u8 high_res_bits; + u32 trigger_number; + const struct at91_adc_trigger *triggers; + struct at91_adc_reg_desc registers; +}; + +struct at91_adc_state { + struct clk *adc_clk; + u16 *buffer; + unsigned long channels_mask; + struct clk *clk; + bool done; + int irq; + u16 last_value; + int chnb; + struct mutex lock; + u8 num_channels; + void __iomem *reg_base; + const struct at91_adc_reg_desc *registers; + u32 startup_time; + u8 sample_hold_time; + bool sleep_mode; + struct iio_trigger **trig; + bool use_external; + u32 vref_mv; + u32 res; /* resolution used for conversions */ + wait_queue_head_t wq_data_avail; + const struct at91_adc_caps *caps; + + /* + * Following ADC channels are shared by touchscreen: + * + * CH0 -- Touch screen XP/UL + * CH1 -- Touch screen XM/UR + * CH2 -- Touch screen YP/LL + * CH3 -- Touch screen YM/Sense + * CH4 -- Touch screen LR(5-wire only) + * + * The bitfields below represents the reserved channel in the + * touchscreen mode. + */ +#define CHAN_MASK_TOUCHSCREEN_4WIRE (0xf << 0) +#define CHAN_MASK_TOUCHSCREEN_5WIRE (0x1f << 0) + enum atmel_adc_ts_type touchscreen_type; + struct input_dev *ts_input; + + u16 ts_sample_period_val; + u32 ts_pressure_threshold; + u16 ts_pendbc; + + bool ts_bufferedmeasure; + u32 ts_prev_absx; + u32 ts_prev_absy; +}; + +static irqreturn_t at91_adc_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *idev = pf->indio_dev; + struct at91_adc_state *st = iio_priv(idev); + struct iio_chan_spec const *chan; + int i, j = 0; + + iio_for_each_active_channel(idev, i) { + chan = idev->channels + i; + st->buffer[j] = at91_adc_readl(st, AT91_ADC_CHAN(st, chan->channel)); + j++; + } + + iio_push_to_buffers_with_timestamp(idev, st->buffer, pf->timestamp); + + iio_trigger_notify_done(idev->trig); + + /* Needed to ACK the DRDY interruption */ + at91_adc_readl(st, AT91_ADC_LCDR); + + enable_irq(st->irq); + + return IRQ_HANDLED; +} + +/* Handler for classic adc channel eoc trigger */ +static void handle_adc_eoc_trigger(int irq, struct iio_dev *idev) +{ + struct at91_adc_state *st = iio_priv(idev); + + if (iio_buffer_enabled(idev)) { + disable_irq_nosync(irq); + iio_trigger_poll(idev->trig); + } else { + st->last_value = at91_adc_readl(st, AT91_ADC_CHAN(st, st->chnb)); + /* Needed to ACK the DRDY interruption */ + at91_adc_readl(st, AT91_ADC_LCDR); + st->done = true; + wake_up_interruptible(&st->wq_data_avail); + } +} + +static void at91_ts_sample(struct iio_dev *idev) +{ + struct at91_adc_state *st = iio_priv(idev); + unsigned int xscale, yscale, reg, z1, z2; + unsigned int x, y, pres, xpos, ypos; + unsigned int rxp = 1; + unsigned int factor = 1000; + + unsigned int xyz_mask_bits = st->res; + unsigned int xyz_mask = (1 << xyz_mask_bits) - 1; + + /* calculate position */ + /* x position = (x / xscale) * max, max = 2^MAX_POS_BITS - 1 */ + reg = at91_adc_readl(st, AT91_ADC_TSXPOSR); + xpos = reg & xyz_mask; + x = (xpos << MAX_POS_BITS) - xpos; + xscale = (reg >> 16) & xyz_mask; + if (xscale == 0) { + dev_err(&idev->dev, "Error: xscale == 0!\n"); + return; + } + x /= xscale; + + /* y position = (y / yscale) * max, max = 2^MAX_POS_BITS - 1 */ + reg = at91_adc_readl(st, AT91_ADC_TSYPOSR); + ypos = reg & xyz_mask; + y = (ypos << MAX_POS_BITS) - ypos; + yscale = (reg >> 16) & xyz_mask; + if (yscale == 0) { + dev_err(&idev->dev, "Error: yscale == 0!\n"); + return; + } + y /= yscale; + + /* calculate the pressure */ + reg = at91_adc_readl(st, AT91_ADC_TSPRESSR); + z1 = reg & xyz_mask; + z2 = (reg >> 16) & xyz_mask; + + if (z1 != 0) + pres = rxp * (x * factor / 1024) * (z2 * factor / z1 - factor) + / factor; + else + pres = st->ts_pressure_threshold; /* no pen contacted */ + + dev_dbg(&idev->dev, "xpos = %d, xscale = %d, ypos = %d, yscale = %d, z1 = %d, z2 = %d, press = %d\n", + xpos, xscale, ypos, yscale, z1, z2, pres); + + if (pres < st->ts_pressure_threshold) { + dev_dbg(&idev->dev, "x = %d, y = %d, pressure = %d\n", + x, y, pres / factor); + input_report_abs(st->ts_input, ABS_X, x); + input_report_abs(st->ts_input, ABS_Y, y); + input_report_abs(st->ts_input, ABS_PRESSURE, pres); + input_report_key(st->ts_input, BTN_TOUCH, 1); + input_sync(st->ts_input); + } else { + dev_dbg(&idev->dev, "pressure too low: not reporting\n"); + } +} + +static irqreturn_t at91_adc_rl_interrupt(int irq, void *private) +{ + struct iio_dev *idev = private; + struct at91_adc_state *st = iio_priv(idev); + u32 status = at91_adc_readl(st, st->registers->status_register); + unsigned int reg; + + status &= at91_adc_readl(st, AT91_ADC_IMR); + if (status & GENMASK(st->num_channels - 1, 0)) + handle_adc_eoc_trigger(irq, idev); + + if (status & AT91RL_ADC_IER_PEN) { + /* Disabling pen debounce is required to get a NOPEN irq */ + reg = at91_adc_readl(st, AT91_ADC_MR); + reg &= ~AT91_ADC_PENDBC; + at91_adc_writel(st, AT91_ADC_MR, reg); + + at91_adc_writel(st, AT91_ADC_IDR, AT91RL_ADC_IER_PEN); + at91_adc_writel(st, AT91_ADC_IER, AT91RL_ADC_IER_NOPEN + | AT91_ADC_EOC(3)); + /* Set up period trigger for sampling */ + at91_adc_writel(st, st->registers->trigger_register, + AT91_ADC_TRGR_MOD_PERIOD_TRIG | + AT91_ADC_TRGR_TRGPER_(st->ts_sample_period_val)); + } else if (status & AT91RL_ADC_IER_NOPEN) { + reg = at91_adc_readl(st, AT91_ADC_MR); + reg |= AT91_ADC_PENDBC_(st->ts_pendbc) & AT91_ADC_PENDBC; + at91_adc_writel(st, AT91_ADC_MR, reg); + at91_adc_writel(st, st->registers->trigger_register, + AT91_ADC_TRGR_NONE); + + at91_adc_writel(st, AT91_ADC_IDR, AT91RL_ADC_IER_NOPEN + | AT91_ADC_EOC(3)); + at91_adc_writel(st, AT91_ADC_IER, AT91RL_ADC_IER_PEN); + st->ts_bufferedmeasure = false; + input_report_key(st->ts_input, BTN_TOUCH, 0); + input_sync(st->ts_input); + } else if (status & AT91_ADC_EOC(3) && st->ts_input) { + /* Conversion finished and we've a touchscreen */ + if (st->ts_bufferedmeasure) { + /* + * Last measurement is always discarded, since it can + * be erroneous. + * Always report previous measurement + */ + input_report_abs(st->ts_input, ABS_X, st->ts_prev_absx); + input_report_abs(st->ts_input, ABS_Y, st->ts_prev_absy); + input_report_key(st->ts_input, BTN_TOUCH, 1); + input_sync(st->ts_input); + } else + st->ts_bufferedmeasure = true; + + /* Now make new measurement */ + st->ts_prev_absx = at91_adc_readl(st, AT91_ADC_CHAN(st, 3)) + << MAX_RLPOS_BITS; + st->ts_prev_absx /= at91_adc_readl(st, AT91_ADC_CHAN(st, 2)); + + st->ts_prev_absy = at91_adc_readl(st, AT91_ADC_CHAN(st, 1)) + << MAX_RLPOS_BITS; + st->ts_prev_absy /= at91_adc_readl(st, AT91_ADC_CHAN(st, 0)); + } + + return IRQ_HANDLED; +} + +static irqreturn_t at91_adc_9x5_interrupt(int irq, void *private) +{ + struct iio_dev *idev = private; + struct at91_adc_state *st = iio_priv(idev); + u32 status = at91_adc_readl(st, st->registers->status_register); + const uint32_t ts_data_irq_mask = + AT91_ADC_IER_XRDY | + AT91_ADC_IER_YRDY | + AT91_ADC_IER_PRDY; + + if (status & GENMASK(st->num_channels - 1, 0)) + handle_adc_eoc_trigger(irq, idev); + + if (status & AT91_ADC_IER_PEN) { + at91_adc_writel(st, AT91_ADC_IDR, AT91_ADC_IER_PEN); + at91_adc_writel(st, AT91_ADC_IER, AT91_ADC_IER_NOPEN | + ts_data_irq_mask); + /* Set up period trigger for sampling */ + at91_adc_writel(st, st->registers->trigger_register, + AT91_ADC_TRGR_MOD_PERIOD_TRIG | + AT91_ADC_TRGR_TRGPER_(st->ts_sample_period_val)); + } else if (status & AT91_ADC_IER_NOPEN) { + at91_adc_writel(st, st->registers->trigger_register, 0); + at91_adc_writel(st, AT91_ADC_IDR, AT91_ADC_IER_NOPEN | + ts_data_irq_mask); + at91_adc_writel(st, AT91_ADC_IER, AT91_ADC_IER_PEN); + + input_report_key(st->ts_input, BTN_TOUCH, 0); + input_sync(st->ts_input); + } else if ((status & ts_data_irq_mask) == ts_data_irq_mask) { + /* Now all touchscreen data is ready */ + + if (status & AT91_ADC_ISR_PENS) { + /* validate data by pen contact */ + at91_ts_sample(idev); + } else { + /* triggered by event that is no pen contact, just read + * them to clean the interrupt and discard all. + */ + at91_adc_readl(st, AT91_ADC_TSXPOSR); + at91_adc_readl(st, AT91_ADC_TSYPOSR); + at91_adc_readl(st, AT91_ADC_TSPRESSR); + } + } + + return IRQ_HANDLED; +} + +static int at91_adc_channel_init(struct iio_dev *idev) +{ + struct at91_adc_state *st = iio_priv(idev); + struct iio_chan_spec *chan_array; + int bit, idx = 0; + unsigned long rsvd_mask = 0; + + /* If touchscreen is enable, then reserve the adc channels */ + if (st->touchscreen_type == ATMEL_ADC_TOUCHSCREEN_4WIRE) + rsvd_mask = CHAN_MASK_TOUCHSCREEN_4WIRE; + else if (st->touchscreen_type == ATMEL_ADC_TOUCHSCREEN_5WIRE) + rsvd_mask = CHAN_MASK_TOUCHSCREEN_5WIRE; + + /* set up the channel mask to reserve touchscreen channels */ + st->channels_mask &= ~rsvd_mask; + + idev->num_channels = bitmap_weight(&st->channels_mask, + st->num_channels) + 1; + + chan_array = devm_kzalloc(&idev->dev, + ((idev->num_channels + 1) * + sizeof(struct iio_chan_spec)), + GFP_KERNEL); + + if (!chan_array) + return -ENOMEM; + + for_each_set_bit(bit, &st->channels_mask, st->num_channels) { + struct iio_chan_spec *chan = chan_array + idx; + + chan->type = IIO_VOLTAGE; + chan->indexed = 1; + chan->channel = bit; + chan->scan_index = idx; + chan->scan_type.sign = 'u'; + chan->scan_type.realbits = st->res; + chan->scan_type.storagebits = 16; + chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE); + chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); + idx++; + } + + chan_array[idx] = IIO_CHAN_SOFT_TIMESTAMP(idx); + + idev->channels = chan_array; + return idev->num_channels; +} + +static int at91_adc_get_trigger_value_by_name(struct iio_dev *idev, + const struct at91_adc_trigger *triggers, + const char *trigger_name) +{ + struct at91_adc_state *st = iio_priv(idev); + int i; + + for (i = 0; i < st->caps->trigger_number; i++) { + char *name __free(kfree) = kasprintf(GFP_KERNEL, "%s-dev%d-%s", + idev->name, + iio_device_id(idev), + triggers[i].name); + if (!name) + return -ENOMEM; + + if (strcmp(trigger_name, name) == 0) { + if (triggers[i].value == 0) + return -EINVAL; + return triggers[i].value; + } + } + + return -EINVAL; +} + +static int at91_adc_configure_trigger(struct iio_trigger *trig, bool state) +{ + struct iio_dev *idev = iio_trigger_get_drvdata(trig); + struct at91_adc_state *st = iio_priv(idev); + const struct at91_adc_reg_desc *reg = st->registers; + u32 status = at91_adc_readl(st, reg->trigger_register); + int value; + u8 bit; + + value = at91_adc_get_trigger_value_by_name(idev, + st->caps->triggers, + idev->trig->name); + if (value < 0) + return value; + + if (state) { + st->buffer = kmalloc(idev->scan_bytes, GFP_KERNEL); + if (st->buffer == NULL) + return -ENOMEM; + + at91_adc_writel(st, reg->trigger_register, + status | value); + + for_each_set_bit(bit, idev->active_scan_mask, + st->num_channels) { + struct iio_chan_spec const *chan = idev->channels + bit; + at91_adc_writel(st, AT91_ADC_CHER, + AT91_ADC_CH(chan->channel)); + } + + at91_adc_writel(st, AT91_ADC_IER, reg->drdy_mask); + + } else { + at91_adc_writel(st, AT91_ADC_IDR, reg->drdy_mask); + + at91_adc_writel(st, reg->trigger_register, + status & ~value); + + for_each_set_bit(bit, idev->active_scan_mask, + st->num_channels) { + struct iio_chan_spec const *chan = idev->channels + bit; + at91_adc_writel(st, AT91_ADC_CHDR, + AT91_ADC_CH(chan->channel)); + } + kfree(st->buffer); + } + + return 0; +} + +static const struct iio_trigger_ops at91_adc_trigger_ops = { + .set_trigger_state = &at91_adc_configure_trigger, +}; + +static struct iio_trigger *at91_adc_allocate_trigger(struct iio_dev *idev, + const struct at91_adc_trigger *trigger) +{ + struct iio_trigger *trig; + int ret; + + trig = iio_trigger_alloc(idev->dev.parent, "%s-dev%d-%s", idev->name, + iio_device_id(idev), trigger->name); + if (trig == NULL) + return NULL; + + iio_trigger_set_drvdata(trig, idev); + trig->ops = &at91_adc_trigger_ops; + + ret = iio_trigger_register(trig); + if (ret) { + iio_trigger_free(trig); + return NULL; + } + + return trig; +} + +static int at91_adc_trigger_init(struct iio_dev *idev) +{ + struct at91_adc_state *st = iio_priv(idev); + int i, ret; + + st->trig = devm_kcalloc(&idev->dev, + st->caps->trigger_number, sizeof(*st->trig), + GFP_KERNEL); + + if (st->trig == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + for (i = 0; i < st->caps->trigger_number; i++) { + if (st->caps->triggers[i].is_external && !(st->use_external)) + continue; + + st->trig[i] = at91_adc_allocate_trigger(idev, + st->caps->triggers + i); + if (st->trig[i] == NULL) { + dev_err(&idev->dev, + "Could not allocate trigger %d\n", i); + ret = -ENOMEM; + goto error_trigger; + } + } + + return 0; + +error_trigger: + for (i--; i >= 0; i--) { + iio_trigger_unregister(st->trig[i]); + iio_trigger_free(st->trig[i]); + } +error_ret: + return ret; +} + +static void at91_adc_trigger_remove(struct iio_dev *idev) +{ + struct at91_adc_state *st = iio_priv(idev); + int i; + + for (i = 0; i < st->caps->trigger_number; i++) { + iio_trigger_unregister(st->trig[i]); + iio_trigger_free(st->trig[i]); + } +} + +static int at91_adc_buffer_init(struct iio_dev *idev) +{ + return iio_triggered_buffer_setup(idev, &iio_pollfunc_store_time, + &at91_adc_trigger_handler, NULL); +} + +static void at91_adc_buffer_remove(struct iio_dev *idev) +{ + iio_triggered_buffer_cleanup(idev); +} + +static int at91_adc_read_raw(struct iio_dev *idev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct at91_adc_state *st = iio_priv(idev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&st->lock); + + st->chnb = chan->channel; + at91_adc_writel(st, AT91_ADC_CHER, + AT91_ADC_CH(chan->channel)); + at91_adc_writel(st, AT91_ADC_IER, BIT(chan->channel)); + at91_adc_writel(st, AT91_ADC_CR, AT91_ADC_START); + + ret = wait_event_interruptible_timeout(st->wq_data_avail, + st->done, + msecs_to_jiffies(1000)); + + /* Disable interrupts, regardless if adc conversion was + * successful or not + */ + at91_adc_writel(st, AT91_ADC_CHDR, + AT91_ADC_CH(chan->channel)); + at91_adc_writel(st, AT91_ADC_IDR, BIT(chan->channel)); + + if (ret > 0) { + /* a valid conversion took place */ + *val = st->last_value; + st->last_value = 0; + st->done = false; + ret = IIO_VAL_INT; + } else if (ret == 0) { + /* conversion timeout */ + dev_err(&idev->dev, "ADC Channel %d timeout.\n", + chan->channel); + ret = -ETIMEDOUT; + } + + mutex_unlock(&st->lock); + return ret; + + case IIO_CHAN_INFO_SCALE: + *val = st->vref_mv; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + default: + break; + } + return -EINVAL; +} + + +static u32 calc_startup_ticks_9260(u32 startup_time, u32 adc_clk_khz) +{ + /* + * Number of ticks needed to cover the startup time of the ADC + * as defined in the electrical characteristics of the board, + * divided by 8. The formula thus is : + * Startup Time = (ticks + 1) * 8 / ADC Clock + */ + return round_up((startup_time * adc_clk_khz / 1000) - 1, 8) / 8; +} + +static u32 calc_startup_ticks_9x5(u32 startup_time, u32 adc_clk_khz) +{ + /* + * For sama5d3x and at91sam9x5, the formula changes to: + * Startup Time = / ADC Clock + */ + static const int startup_lookup[] = { + 0, 8, 16, 24, + 64, 80, 96, 112, + 512, 576, 640, 704, + 768, 832, 896, 960 + }; + int i, size = ARRAY_SIZE(startup_lookup); + unsigned int ticks; + + ticks = startup_time * adc_clk_khz / 1000; + for (i = 0; i < size; i++) + if (ticks < startup_lookup[i]) + break; + + ticks = i; + if (ticks == size) + /* Reach the end of lookup table */ + ticks = size - 1; + + return ticks; +} + +static int at91_adc_probe_dt_ts(struct device_node *node, + struct at91_adc_state *st, struct device *dev) +{ + int ret; + u32 prop; + + ret = of_property_read_u32(node, "atmel,adc-ts-wires", &prop); + if (ret) { + dev_info(dev, "ADC Touch screen is disabled.\n"); + return 0; + } + + switch (prop) { + case 4: + case 5: + st->touchscreen_type = prop; + break; + default: + dev_err(dev, "Unsupported number of touchscreen wires (%d). Should be 4 or 5.\n", prop); + return -EINVAL; + } + + if (!st->caps->has_tsmr) + return 0; + prop = 0; + of_property_read_u32(node, "atmel,adc-ts-pressure-threshold", &prop); + st->ts_pressure_threshold = prop; + if (st->ts_pressure_threshold) { + return 0; + } else { + dev_err(dev, "Invalid pressure threshold for the touchscreen\n"); + return -EINVAL; + } +} + +static const struct iio_info at91_adc_info = { + .read_raw = &at91_adc_read_raw, +}; + +/* Touchscreen related functions */ +static int atmel_ts_open(struct input_dev *dev) +{ + struct at91_adc_state *st = input_get_drvdata(dev); + + if (st->caps->has_tsmr) + at91_adc_writel(st, AT91_ADC_IER, AT91_ADC_IER_PEN); + else + at91_adc_writel(st, AT91_ADC_IER, AT91RL_ADC_IER_PEN); + return 0; +} + +static void atmel_ts_close(struct input_dev *dev) +{ + struct at91_adc_state *st = input_get_drvdata(dev); + + if (st->caps->has_tsmr) + at91_adc_writel(st, AT91_ADC_IDR, AT91_ADC_IER_PEN); + else + at91_adc_writel(st, AT91_ADC_IDR, AT91RL_ADC_IER_PEN); +} + +static int at91_ts_hw_init(struct iio_dev *idev, u32 adc_clk_khz) +{ + struct at91_adc_state *st = iio_priv(idev); + u32 reg = 0; + u32 tssctim = 0; + int i = 0; + + /* a Pen Detect Debounce Time is necessary for the ADC Touch to avoid + * pen detect noise. + * The formula is : Pen Detect Debounce Time = (2 ^ pendbc) / ADCClock + */ + st->ts_pendbc = round_up(TOUCH_PEN_DETECT_DEBOUNCE_US * adc_clk_khz / + 1000, 1); + + while (st->ts_pendbc >> ++i) + ; /* Empty! Find the shift offset */ + if (abs(st->ts_pendbc - (1 << i)) < abs(st->ts_pendbc - (1 << (i - 1)))) + st->ts_pendbc = i; + else + st->ts_pendbc = i - 1; + + if (!st->caps->has_tsmr) { + reg = at91_adc_readl(st, AT91_ADC_MR); + reg |= AT91_ADC_TSAMOD_TS_ONLY_MODE | AT91_ADC_PENDET; + + reg |= AT91_ADC_PENDBC_(st->ts_pendbc) & AT91_ADC_PENDBC; + at91_adc_writel(st, AT91_ADC_MR, reg); + + reg = AT91_ADC_TSR_SHTIM_(TOUCH_SHTIM) & AT91_ADC_TSR_SHTIM; + at91_adc_writel(st, AT91_ADC_TSR, reg); + + st->ts_sample_period_val = round_up((TOUCH_SAMPLE_PERIOD_US_RL * + adc_clk_khz / 1000) - 1, 1); + + return 0; + } + + /* Touchscreen Switches Closure time needed for allowing the value to + * stabilize. + * Switch Closure Time = (TSSCTIM * 4) ADCClock periods + */ + tssctim = DIV_ROUND_UP(TOUCH_SCTIM_US * adc_clk_khz / 1000, 4); + dev_dbg(&idev->dev, "adc_clk at: %d KHz, tssctim at: %d\n", + adc_clk_khz, tssctim); + + if (st->touchscreen_type == ATMEL_ADC_TOUCHSCREEN_4WIRE) + reg = AT91_ADC_TSMR_TSMODE_4WIRE_PRESS; + else + reg = AT91_ADC_TSMR_TSMODE_5WIRE; + + reg |= AT91_ADC_TSMR_SCTIM_(tssctim) & AT91_ADC_TSMR_SCTIM; + reg |= AT91_ADC_TSMR_TSAV_(st->caps->ts_filter_average) + & AT91_ADC_TSMR_TSAV; + reg |= AT91_ADC_TSMR_PENDBC_(st->ts_pendbc) & AT91_ADC_TSMR_PENDBC; + reg |= AT91_ADC_TSMR_NOTSDMA; + reg |= AT91_ADC_TSMR_PENDET_ENA; + reg |= 0x03 << 8; /* TSFREQ, needs to be bigger than TSAV */ + + at91_adc_writel(st, AT91_ADC_TSMR, reg); + + /* Change adc internal resistor value for better pen detection, + * default value is 100 kOhm. + * 0 = 200 kOhm, 1 = 150 kOhm, 2 = 100 kOhm, 3 = 50 kOhm + * option only available on ES2 and higher + */ + at91_adc_writel(st, AT91_ADC_ACR, st->caps->ts_pen_detect_sensitivity + & AT91_ADC_ACR_PENDETSENS); + + /* Sample Period Time = (TRGPER + 1) / ADCClock */ + st->ts_sample_period_val = round_up((TOUCH_SAMPLE_PERIOD_US * + adc_clk_khz / 1000) - 1, 1); + + return 0; +} + +static int at91_ts_register(struct iio_dev *idev, + struct platform_device *pdev) +{ + struct at91_adc_state *st = iio_priv(idev); + struct input_dev *input; + int ret; + + input = input_allocate_device(); + if (!input) { + dev_err(&idev->dev, "Failed to allocate TS device!\n"); + return -ENOMEM; + } + + input->name = DRIVER_NAME; + input->id.bustype = BUS_HOST; + input->dev.parent = &pdev->dev; + input->open = atmel_ts_open; + input->close = atmel_ts_close; + + __set_bit(EV_ABS, input->evbit); + __set_bit(EV_KEY, input->evbit); + __set_bit(BTN_TOUCH, input->keybit); + if (st->caps->has_tsmr) { + input_set_abs_params(input, ABS_X, 0, (1 << MAX_POS_BITS) - 1, + 0, 0); + input_set_abs_params(input, ABS_Y, 0, (1 << MAX_POS_BITS) - 1, + 0, 0); + input_set_abs_params(input, ABS_PRESSURE, 0, 0xffffff, 0, 0); + } else { + if (st->touchscreen_type != ATMEL_ADC_TOUCHSCREEN_4WIRE) { + dev_err(&pdev->dev, + "This touchscreen controller only support 4 wires\n"); + ret = -EINVAL; + goto err; + } + + input_set_abs_params(input, ABS_X, 0, (1 << MAX_RLPOS_BITS) - 1, + 0, 0); + input_set_abs_params(input, ABS_Y, 0, (1 << MAX_RLPOS_BITS) - 1, + 0, 0); + } + + st->ts_input = input; + input_set_drvdata(input, st); + + ret = input_register_device(input); + if (ret) + goto err; + + return ret; + +err: + input_free_device(input); + return ret; +} + +static void at91_ts_unregister(struct at91_adc_state *st) +{ + input_unregister_device(st->ts_input); +} + +static int at91_adc_probe(struct platform_device *pdev) +{ + unsigned int prsc, mstrclk, ticks, adc_clk, adc_clk_khz, shtim; + struct device_node *node = pdev->dev.of_node; + int ret; + struct iio_dev *idev; + struct at91_adc_state *st; + u32 reg, prop; + const char *s; + + idev = devm_iio_device_alloc(&pdev->dev, sizeof(struct at91_adc_state)); + if (!idev) + return -ENOMEM; + + st = iio_priv(idev); + + st->caps = of_device_get_match_data(&pdev->dev); + + st->use_external = of_property_read_bool(node, "atmel,adc-use-external-triggers"); + + if (of_property_read_u32(node, "atmel,adc-channels-used", &prop)) + return dev_err_probe(&idev->dev, -EINVAL, + "Missing adc-channels-used property in the DT.\n"); + st->channels_mask = prop; + + st->sleep_mode = of_property_read_bool(node, "atmel,adc-sleep-mode"); + + if (of_property_read_u32(node, "atmel,adc-startup-time", &prop)) + return dev_err_probe(&idev->dev, -EINVAL, + "Missing adc-startup-time property in the DT.\n"); + st->startup_time = prop; + + prop = 0; + of_property_read_u32(node, "atmel,adc-sample-hold-time", &prop); + st->sample_hold_time = prop; + + if (of_property_read_u32(node, "atmel,adc-vref", &prop)) + return dev_err_probe(&idev->dev, -EINVAL, + "Missing adc-vref property in the DT.\n"); + st->vref_mv = prop; + + st->res = st->caps->high_res_bits; + if (st->caps->low_res_bits && + !of_property_read_string(node, "atmel,adc-use-res", &s) + && !strcmp(s, "lowres")) + st->res = st->caps->low_res_bits; + + dev_info(&idev->dev, "Resolution used: %u bits\n", st->res); + + st->registers = &st->caps->registers; + st->num_channels = st->caps->num_channels; + + /* Check if touchscreen is supported. */ + if (st->caps->has_ts) { + ret = at91_adc_probe_dt_ts(node, st, &idev->dev); + if (ret) + return ret; + } + + platform_set_drvdata(pdev, idev); + + idev->name = dev_name(&pdev->dev); + idev->modes = INDIO_DIRECT_MODE; + idev->info = &at91_adc_info; + + st->irq = platform_get_irq(pdev, 0); + if (st->irq < 0) + return -ENODEV; + + st->reg_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(st->reg_base)) + return PTR_ERR(st->reg_base); + + /* + * Disable all IRQs before setting up the handler + */ + at91_adc_writel(st, AT91_ADC_CR, AT91_ADC_SWRST); + at91_adc_writel(st, AT91_ADC_IDR, 0xFFFFFFFF); + + if (st->caps->has_tsmr) + ret = devm_request_irq(&pdev->dev, st->irq, + at91_adc_9x5_interrupt, 0, + pdev->dev.driver->name, idev); + else + ret = devm_request_irq(&pdev->dev, st->irq, + at91_adc_rl_interrupt, 0, + pdev->dev.driver->name, idev); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to allocate IRQ.\n"); + + st->clk = devm_clk_get_enabled(&pdev->dev, "adc_clk"); + if (IS_ERR(st->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(st->clk), + "Could not prepare or enable the clock.\n"); + + st->adc_clk = devm_clk_get_enabled(&pdev->dev, "adc_op_clk"); + if (IS_ERR(st->adc_clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(st->adc_clk), + "Could not prepare or enable the ADC clock.\n"); + + /* + * Prescaler rate computation using the formula from the Atmel's + * datasheet : ADC Clock = MCK / ((Prescaler + 1) * 2), ADC Clock being + * specified by the electrical characteristics of the board. + */ + mstrclk = clk_get_rate(st->clk); + adc_clk = clk_get_rate(st->adc_clk); + adc_clk_khz = adc_clk / 1000; + + dev_dbg(&pdev->dev, "Master clock is set as: %d Hz, adc_clk should set as: %d Hz\n", + mstrclk, adc_clk); + + prsc = (mstrclk / (2 * adc_clk)) - 1; + + if (!st->startup_time) + return dev_err_probe(&pdev->dev, -EINVAL, + "No startup time available.\n"); + ticks = (*st->caps->calc_startup_ticks)(st->startup_time, adc_clk_khz); + + /* + * a minimal Sample and Hold Time is necessary for the ADC to guarantee + * the best converted final value between two channels selection + * The formula thus is : Sample and Hold Time = (shtim + 1) / ADCClock + */ + if (st->sample_hold_time > 0) + shtim = round_up((st->sample_hold_time * adc_clk_khz / 1000) + - 1, 1); + else + shtim = 0; + + reg = AT91_ADC_PRESCAL_(prsc) & st->registers->mr_prescal_mask; + reg |= AT91_ADC_STARTUP_(ticks) & st->registers->mr_startup_mask; + if (st->res == st->caps->low_res_bits) + reg |= AT91_ADC_LOWRES; + if (st->sleep_mode) + reg |= AT91_ADC_SLEEP; + reg |= AT91_ADC_SHTIM_(shtim) & AT91_ADC_SHTIM; + at91_adc_writel(st, AT91_ADC_MR, reg); + + /* Setup the ADC channels available on the board */ + ret = at91_adc_channel_init(idev); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, + "Couldn't initialize the channels.\n"); + + init_waitqueue_head(&st->wq_data_avail); + mutex_init(&st->lock); + + /* + * Since touch screen will set trigger register as period trigger. So + * when touch screen is enabled, then we have to disable hardware + * trigger for classic adc. + */ + if (!st->touchscreen_type) { + ret = at91_adc_buffer_init(idev); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, + "Couldn't initialize the buffer.\n"); + + ret = at91_adc_trigger_init(idev); + if (ret < 0) { + dev_err(&pdev->dev, "Couldn't setup the triggers.\n"); + at91_adc_buffer_remove(idev); + return ret; + } + } else { + ret = at91_ts_register(idev, pdev); + if (ret) + return ret; + + at91_ts_hw_init(idev, adc_clk_khz); + } + + ret = iio_device_register(idev); + if (ret < 0) { + dev_err(&pdev->dev, "Couldn't register the device.\n"); + goto error_iio_device_register; + } + + return 0; + +error_iio_device_register: + if (!st->touchscreen_type) { + at91_adc_trigger_remove(idev); + at91_adc_buffer_remove(idev); + } else { + at91_ts_unregister(st); + } + return ret; +} + +static void at91_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *idev = platform_get_drvdata(pdev); + struct at91_adc_state *st = iio_priv(idev); + + iio_device_unregister(idev); + if (!st->touchscreen_type) { + at91_adc_trigger_remove(idev); + at91_adc_buffer_remove(idev); + } else { + at91_ts_unregister(st); + } +} + +static int at91_adc_suspend(struct device *dev) +{ + struct iio_dev *idev = dev_get_drvdata(dev); + struct at91_adc_state *st = iio_priv(idev); + + pinctrl_pm_select_sleep_state(dev); + clk_disable_unprepare(st->clk); + + return 0; +} + +static int at91_adc_resume(struct device *dev) +{ + struct iio_dev *idev = dev_get_drvdata(dev); + struct at91_adc_state *st = iio_priv(idev); + + clk_prepare_enable(st->clk); + pinctrl_pm_select_default_state(dev); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(at91_adc_pm_ops, at91_adc_suspend, + at91_adc_resume); + +static const struct at91_adc_trigger at91sam9260_triggers[] = { + { .name = "timer-counter-0", .value = 0x1 }, + { .name = "timer-counter-1", .value = 0x3 }, + { .name = "timer-counter-2", .value = 0x5 }, + { .name = "external", .value = 0xd, .is_external = true }, +}; + +static const struct at91_adc_caps at91sam9260_caps = { + .calc_startup_ticks = calc_startup_ticks_9260, + .num_channels = 4, + .low_res_bits = 8, + .high_res_bits = 10, + .registers = { + .channel_base = AT91_ADC_CHR(0), + .drdy_mask = AT91_ADC_DRDY, + .status_register = AT91_ADC_SR, + .trigger_register = AT91_ADC_TRGR_9260, + .mr_prescal_mask = AT91_ADC_PRESCAL_9260, + .mr_startup_mask = AT91_ADC_STARTUP_9260, + }, + .triggers = at91sam9260_triggers, + .trigger_number = ARRAY_SIZE(at91sam9260_triggers), +}; + +static const struct at91_adc_trigger at91sam9x5_triggers[] = { + { .name = "external-rising", .value = 0x1, .is_external = true }, + { .name = "external-falling", .value = 0x2, .is_external = true }, + { .name = "external-any", .value = 0x3, .is_external = true }, + { .name = "continuous", .value = 0x6 }, +}; + +static const struct at91_adc_caps at91sam9rl_caps = { + .has_ts = true, + .calc_startup_ticks = calc_startup_ticks_9260, /* same as 9260 */ + .num_channels = 6, + .low_res_bits = 8, + .high_res_bits = 10, + .registers = { + .channel_base = AT91_ADC_CHR(0), + .drdy_mask = AT91_ADC_DRDY, + .status_register = AT91_ADC_SR, + .trigger_register = AT91_ADC_TRGR_9G45, + .mr_prescal_mask = AT91_ADC_PRESCAL_9260, + .mr_startup_mask = AT91_ADC_STARTUP_9G45, + }, + .triggers = at91sam9x5_triggers, + .trigger_number = ARRAY_SIZE(at91sam9x5_triggers), +}; + +static const struct at91_adc_caps at91sam9g45_caps = { + .has_ts = true, + .calc_startup_ticks = calc_startup_ticks_9260, /* same as 9260 */ + .num_channels = 8, + .low_res_bits = 8, + .high_res_bits = 10, + .registers = { + .channel_base = AT91_ADC_CHR(0), + .drdy_mask = AT91_ADC_DRDY, + .status_register = AT91_ADC_SR, + .trigger_register = AT91_ADC_TRGR_9G45, + .mr_prescal_mask = AT91_ADC_PRESCAL_9G45, + .mr_startup_mask = AT91_ADC_STARTUP_9G45, + }, + .triggers = at91sam9x5_triggers, + .trigger_number = ARRAY_SIZE(at91sam9x5_triggers), +}; + +static const struct at91_adc_caps at91sam9x5_caps = { + .has_ts = true, + .has_tsmr = true, + .ts_filter_average = 3, + .ts_pen_detect_sensitivity = 2, + .calc_startup_ticks = calc_startup_ticks_9x5, + .num_channels = 12, + .low_res_bits = 8, + .high_res_bits = 10, + .registers = { + .channel_base = AT91_ADC_CDR0_9X5, + .drdy_mask = AT91_ADC_SR_DRDY_9X5, + .status_register = AT91_ADC_SR_9X5, + .trigger_register = AT91_ADC_TRGR_9X5, + /* prescal mask is same as 9G45 */ + .mr_prescal_mask = AT91_ADC_PRESCAL_9G45, + .mr_startup_mask = AT91_ADC_STARTUP_9X5, + }, + .triggers = at91sam9x5_triggers, + .trigger_number = ARRAY_SIZE(at91sam9x5_triggers), +}; + +static const struct at91_adc_caps sama5d3_caps = { + .has_ts = true, + .has_tsmr = true, + .ts_filter_average = 3, + .ts_pen_detect_sensitivity = 2, + .calc_startup_ticks = calc_startup_ticks_9x5, + .num_channels = 12, + .low_res_bits = 0, + .high_res_bits = 12, + .registers = { + .channel_base = AT91_ADC_CDR0_9X5, + .drdy_mask = AT91_ADC_SR_DRDY_9X5, + .status_register = AT91_ADC_SR_9X5, + .trigger_register = AT91_ADC_TRGR_9X5, + .mr_prescal_mask = AT91_ADC_PRESCAL_9G45, + .mr_startup_mask = AT91_ADC_STARTUP_9X5, + }, + .triggers = at91sam9x5_triggers, + .trigger_number = ARRAY_SIZE(at91sam9x5_triggers), +}; + +static const struct of_device_id at91_adc_dt_ids[] = { + { .compatible = "atmel,at91sam9260-adc", .data = &at91sam9260_caps }, + { .compatible = "atmel,at91sam9rl-adc", .data = &at91sam9rl_caps }, + { .compatible = "atmel,at91sam9g45-adc", .data = &at91sam9g45_caps }, + { .compatible = "atmel,at91sam9x5-adc", .data = &at91sam9x5_caps }, + { .compatible = "atmel,sama5d3-adc", .data = &sama5d3_caps }, + { } +}; +MODULE_DEVICE_TABLE(of, at91_adc_dt_ids); + +static struct platform_driver at91_adc_driver = { + .probe = at91_adc_probe, + .remove = at91_adc_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = at91_adc_dt_ids, + .pm = pm_sleep_ptr(&at91_adc_pm_ops), + }, +}; + +module_platform_driver(at91_adc_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Atmel AT91 ADC Driver"); +MODULE_AUTHOR("Maxime Ripard "); diff --git a/drivers/iio/adc/axp20x_adc.c b/drivers/iio/adc/axp20x_adc.c new file mode 100644 index 000000000..d9016fe1a --- /dev/null +++ b/drivers/iio/adc/axp20x_adc.c @@ -0,0 +1,1192 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* ADC driver for AXP20X and AXP22X PMICs + * + * Copyright (c) 2016 Free Electrons NextThing Co. + * Quentin Schulz + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define AXP192_ADC_EN1_MASK GENMASK(7, 0) +#define AXP192_ADC_EN2_MASK (GENMASK(3, 0) | BIT(7)) + +#define AXP20X_ADC_EN1_MASK GENMASK(7, 0) +#define AXP20X_ADC_EN2_MASK (GENMASK(3, 2) | BIT(7)) + +#define AXP22X_ADC_EN1_MASK (GENMASK(7, 5) | BIT(0)) + +#define AXP717_ADC_EN1_MASK GENMASK(7, 0) + +#define AXP192_GPIO30_IN_RANGE_GPIO0 BIT(0) +#define AXP192_GPIO30_IN_RANGE_GPIO1 BIT(1) +#define AXP192_GPIO30_IN_RANGE_GPIO2 BIT(2) +#define AXP192_GPIO30_IN_RANGE_GPIO3 BIT(3) + +#define AXP20X_GPIO10_IN_RANGE_GPIO0 BIT(0) +#define AXP20X_GPIO10_IN_RANGE_GPIO1 BIT(1) + +#define AXP20X_ADC_RATE_MASK GENMASK(7, 6) +#define AXP20X_ADC_RATE_HZ(x) ((ilog2((x) / 25) << 6) & AXP20X_ADC_RATE_MASK) + +#define AXP22X_ADC_RATE_HZ(x) ((ilog2((x) / 100) << 6) & AXP20X_ADC_RATE_MASK) + +#define AXP717_ADC_DATA_TS 0x00 +#define AXP717_ADC_DATA_TEMP 0x01 +#define AXP717_ADC_DATA_VMID 0x02 +#define AXP717_ADC_DATA_BKUP_BATT 0x03 + +#define AXP717_ADC_DATA_MASK GENMASK(13, 0) + +#define AXP813_V_I_ADC_RATE_MASK GENMASK(5, 4) +#define AXP813_ADC_RATE_MASK (AXP20X_ADC_RATE_MASK | AXP813_V_I_ADC_RATE_MASK) +#define AXP813_TS_GPIO0_ADC_RATE_HZ(x) AXP20X_ADC_RATE_HZ(x) +#define AXP813_V_I_ADC_RATE_HZ(x) ((ilog2((x) / 100) << 4) & AXP813_V_I_ADC_RATE_MASK) +#define AXP813_ADC_RATE_HZ(x) (AXP20X_ADC_RATE_HZ(x) | AXP813_V_I_ADC_RATE_HZ(x)) + +#define AXP20X_ADC_CHANNEL(_channel, _name, _type, _reg) \ + { \ + .type = _type, \ + .indexed = 1, \ + .channel = _channel, \ + .address = _reg, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = _name, \ + } + +#define AXP20X_ADC_CHANNEL_OFFSET(_channel, _name, _type, _reg) \ + { \ + .type = _type, \ + .indexed = 1, \ + .channel = _channel, \ + .address = _reg, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) |\ + BIT(IIO_CHAN_INFO_OFFSET),\ + .datasheet_name = _name, \ + } + +struct axp_data; + +struct axp20x_adc_iio { + struct regmap *regmap; + const struct axp_data *data; +}; + +enum axp192_adc_channel_v { + AXP192_ACIN_V = 0, + AXP192_VBUS_V, + AXP192_TS_IN, + AXP192_GPIO0_V, + AXP192_GPIO1_V, + AXP192_GPIO2_V, + AXP192_GPIO3_V, + AXP192_IPSOUT_V, + AXP192_BATT_V, +}; + +enum axp192_adc_channel_i { + AXP192_ACIN_I = 0, + AXP192_VBUS_I, + AXP192_BATT_CHRG_I, + AXP192_BATT_DISCHRG_I, +}; + +enum axp20x_adc_channel_v { + AXP20X_ACIN_V = 0, + AXP20X_VBUS_V, + AXP20X_TS_IN, + AXP20X_GPIO0_V, + AXP20X_GPIO1_V, + AXP20X_IPSOUT_V, + AXP20X_BATT_V, +}; + +enum axp20x_adc_channel_i { + AXP20X_ACIN_I = 0, + AXP20X_VBUS_I, + AXP20X_BATT_CHRG_I, + AXP20X_BATT_DISCHRG_I, +}; + +enum axp22x_adc_channel_v { + AXP22X_TS_IN = 0, + AXP22X_BATT_V, +}; + +enum axp22x_adc_channel_i { + AXP22X_BATT_CHRG_I = 1, + AXP22X_BATT_DISCHRG_I, +}; + +enum axp717_adc_channel_v { + AXP717_BATT_V = 0, + AXP717_TS_IN, + AXP717_VBUS_V, + AXP717_VSYS_V, + AXP717_DIE_TEMP_V, + AXP717_VMID_V = 6, + AXP717_BKUP_BATT_V, +}; + +enum axp717_adc_channel_i { + AXP717_BATT_CHRG_I = 5, +}; + +enum axp813_adc_channel_v { + AXP813_TS_IN = 0, + AXP813_GPIO0_V, + AXP813_BATT_V, +}; + +static const struct iio_map axp20x_maps[] = { + IIO_MAP("vbus_v", "axp20x-usb-power-supply", "vbus_v"), + IIO_MAP("vbus_i", "axp20x-usb-power-supply", "vbus_i"), + IIO_MAP("acin_v", "axp20x-ac-power-supply", "acin_v"), + IIO_MAP("acin_i", "axp20x-ac-power-supply", "acin_i"), + IIO_MAP("batt_v", "axp20x-battery-power-supply", "batt_v"), + IIO_MAP("batt_chrg_i", "axp20x-battery-power-supply", "batt_chrg_i"), + IIO_MAP("batt_dischrg_i", "axp20x-battery-power-supply", "batt_dischrg_i"), + { } +}; + +static const struct iio_map axp22x_maps[] = { + IIO_MAP("batt_v", "axp20x-battery-power-supply", "batt_v"), + IIO_MAP("batt_chrg_i", "axp20x-battery-power-supply", "batt_chrg_i"), + IIO_MAP("batt_dischrg_i", "axp20x-battery-power-supply", "batt_dischrg_i"), + { } +}; + +static const struct iio_map axp717_maps[] = { + { + .consumer_dev_name = "axp20x-usb-power-supply", + .consumer_channel = "vbus_v", + .adc_channel_label = "vbus_v", + }, { + .consumer_dev_name = "axp20x-battery-power-supply", + .consumer_channel = "batt_v", + .adc_channel_label = "batt_v", + }, { + .consumer_dev_name = "axp20x-battery-power-supply", + .consumer_channel = "batt_chrg_i", + .adc_channel_label = "batt_chrg_i", + }, + { } +}; + +/* + * Channels are mapped by physical system. Their channels share the same index. + * i.e. acin_i is in_current0_raw and acin_v is in_voltage0_raw. + * The only exception is for the battery. batt_v will be in_voltage6_raw and + * charge current in_current6_raw and discharge current will be in_current7_raw. + */ +static const struct iio_chan_spec axp192_adc_channels[] = { + AXP20X_ADC_CHANNEL(AXP192_ACIN_V, "acin_v", IIO_VOLTAGE, + AXP20X_ACIN_V_ADC_H), + AXP20X_ADC_CHANNEL(AXP192_ACIN_I, "acin_i", IIO_CURRENT, + AXP20X_ACIN_I_ADC_H), + AXP20X_ADC_CHANNEL(AXP192_VBUS_V, "vbus_v", IIO_VOLTAGE, + AXP20X_VBUS_V_ADC_H), + AXP20X_ADC_CHANNEL(AXP192_VBUS_I, "vbus_i", IIO_CURRENT, + AXP20X_VBUS_I_ADC_H), + { + .type = IIO_TEMP, + .address = AXP20X_TEMP_ADC_H, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .datasheet_name = "pmic_temp", + }, + AXP20X_ADC_CHANNEL_OFFSET(AXP192_GPIO0_V, "gpio0_v", IIO_VOLTAGE, + AXP20X_GPIO0_V_ADC_H), + AXP20X_ADC_CHANNEL_OFFSET(AXP192_GPIO1_V, "gpio1_v", IIO_VOLTAGE, + AXP20X_GPIO1_V_ADC_H), + AXP20X_ADC_CHANNEL_OFFSET(AXP192_GPIO2_V, "gpio2_v", IIO_VOLTAGE, + AXP192_GPIO2_V_ADC_H), + AXP20X_ADC_CHANNEL_OFFSET(AXP192_GPIO3_V, "gpio3_v", IIO_VOLTAGE, + AXP192_GPIO3_V_ADC_H), + AXP20X_ADC_CHANNEL(AXP192_IPSOUT_V, "ipsout_v", IIO_VOLTAGE, + AXP20X_IPSOUT_V_HIGH_H), + AXP20X_ADC_CHANNEL(AXP192_BATT_V, "batt_v", IIO_VOLTAGE, + AXP20X_BATT_V_H), + AXP20X_ADC_CHANNEL(AXP192_BATT_CHRG_I, "batt_chrg_i", IIO_CURRENT, + AXP20X_BATT_CHRG_I_H), + AXP20X_ADC_CHANNEL(AXP192_BATT_DISCHRG_I, "batt_dischrg_i", IIO_CURRENT, + AXP20X_BATT_DISCHRG_I_H), + AXP20X_ADC_CHANNEL(AXP192_TS_IN, "ts_v", IIO_VOLTAGE, + AXP20X_TS_IN_H), +}; + +static const struct iio_chan_spec axp20x_adc_channels[] = { + AXP20X_ADC_CHANNEL(AXP20X_ACIN_V, "acin_v", IIO_VOLTAGE, + AXP20X_ACIN_V_ADC_H), + AXP20X_ADC_CHANNEL(AXP20X_ACIN_I, "acin_i", IIO_CURRENT, + AXP20X_ACIN_I_ADC_H), + AXP20X_ADC_CHANNEL(AXP20X_VBUS_V, "vbus_v", IIO_VOLTAGE, + AXP20X_VBUS_V_ADC_H), + AXP20X_ADC_CHANNEL(AXP20X_VBUS_I, "vbus_i", IIO_CURRENT, + AXP20X_VBUS_I_ADC_H), + { + .type = IIO_TEMP, + .address = AXP20X_TEMP_ADC_H, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .datasheet_name = "pmic_temp", + }, + AXP20X_ADC_CHANNEL_OFFSET(AXP20X_GPIO0_V, "gpio0_v", IIO_VOLTAGE, + AXP20X_GPIO0_V_ADC_H), + AXP20X_ADC_CHANNEL_OFFSET(AXP20X_GPIO1_V, "gpio1_v", IIO_VOLTAGE, + AXP20X_GPIO1_V_ADC_H), + AXP20X_ADC_CHANNEL(AXP20X_IPSOUT_V, "ipsout_v", IIO_VOLTAGE, + AXP20X_IPSOUT_V_HIGH_H), + AXP20X_ADC_CHANNEL(AXP20X_BATT_V, "batt_v", IIO_VOLTAGE, + AXP20X_BATT_V_H), + AXP20X_ADC_CHANNEL(AXP20X_BATT_CHRG_I, "batt_chrg_i", IIO_CURRENT, + AXP20X_BATT_CHRG_I_H), + AXP20X_ADC_CHANNEL(AXP20X_BATT_DISCHRG_I, "batt_dischrg_i", IIO_CURRENT, + AXP20X_BATT_DISCHRG_I_H), + AXP20X_ADC_CHANNEL(AXP20X_TS_IN, "ts_v", IIO_VOLTAGE, + AXP20X_TS_IN_H), +}; + +static const struct iio_chan_spec axp22x_adc_channels[] = { + { + .type = IIO_TEMP, + .address = AXP22X_PMIC_TEMP_H, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .datasheet_name = "pmic_temp", + }, + AXP20X_ADC_CHANNEL(AXP22X_BATT_V, "batt_v", IIO_VOLTAGE, + AXP20X_BATT_V_H), + AXP20X_ADC_CHANNEL(AXP22X_BATT_CHRG_I, "batt_chrg_i", IIO_CURRENT, + AXP20X_BATT_CHRG_I_H), + AXP20X_ADC_CHANNEL(AXP22X_BATT_DISCHRG_I, "batt_dischrg_i", IIO_CURRENT, + AXP20X_BATT_DISCHRG_I_H), + AXP20X_ADC_CHANNEL(AXP22X_TS_IN, "ts_v", IIO_VOLTAGE, + AXP22X_TS_ADC_H), +}; + +/* + * Scale and offset is unknown for temp, ts, batt_chrg_i, vmid_v, and + * bkup_batt_v channels. Leaving scale and offset undefined for now. + */ +static const struct iio_chan_spec axp717_adc_channels[] = { + AXP20X_ADC_CHANNEL(AXP717_BATT_V, "batt_v", IIO_VOLTAGE, + AXP717_BATT_V_H), + AXP20X_ADC_CHANNEL(AXP717_TS_IN, "ts_v", IIO_VOLTAGE, + AXP717_ADC_DATA_H), + AXP20X_ADC_CHANNEL(AXP717_VBUS_V, "vbus_v", IIO_VOLTAGE, + AXP717_VBUS_V_H), + AXP20X_ADC_CHANNEL(AXP717_VSYS_V, "vsys_v", IIO_VOLTAGE, + AXP717_VSYS_V_H), + AXP20X_ADC_CHANNEL(AXP717_DIE_TEMP_V, "pmic_temp", IIO_TEMP, + AXP717_ADC_DATA_H), + AXP20X_ADC_CHANNEL(AXP717_BATT_CHRG_I, "batt_chrg_i", IIO_CURRENT, + AXP717_BATT_CHRG_I_H), + AXP20X_ADC_CHANNEL(AXP717_VMID_V, "vmid_v", IIO_VOLTAGE, + AXP717_ADC_DATA_H), + AXP20X_ADC_CHANNEL(AXP717_BKUP_BATT_V, "bkup_batt_v", IIO_VOLTAGE, + AXP717_ADC_DATA_H), +}; + +static const struct iio_chan_spec axp813_adc_channels[] = { + { + .type = IIO_TEMP, + .address = AXP22X_PMIC_TEMP_H, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .datasheet_name = "pmic_temp", + }, + AXP20X_ADC_CHANNEL(AXP813_GPIO0_V, "gpio0_v", IIO_VOLTAGE, + AXP288_GP_ADC_H), + AXP20X_ADC_CHANNEL(AXP813_BATT_V, "batt_v", IIO_VOLTAGE, + AXP20X_BATT_V_H), + AXP20X_ADC_CHANNEL(AXP22X_BATT_CHRG_I, "batt_chrg_i", IIO_CURRENT, + AXP20X_BATT_CHRG_I_H), + AXP20X_ADC_CHANNEL(AXP22X_BATT_DISCHRG_I, "batt_dischrg_i", IIO_CURRENT, + AXP20X_BATT_DISCHRG_I_H), + AXP20X_ADC_CHANNEL(AXP813_TS_IN, "ts_v", IIO_VOLTAGE, + AXP288_TS_ADC_H), +}; + +static int axp192_adc_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val) +{ + struct axp20x_adc_iio *info = iio_priv(indio_dev); + int ret, size; + + if (chan->type == IIO_CURRENT && + (chan->channel == AXP192_BATT_CHRG_I || + chan->channel == AXP192_BATT_DISCHRG_I)) + size = 13; + else + size = 12; + + ret = axp20x_read_variable_width(info->regmap, chan->address, size); + if (ret < 0) + return ret; + + *val = ret; + return IIO_VAL_INT; +} + +static int axp20x_adc_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val) +{ + struct axp20x_adc_iio *info = iio_priv(indio_dev); + int ret, size; + + /* + * N.B.: Unlike the Chinese datasheets tell, the charging current is + * stored on 12 bits, not 13 bits. Only discharging current is on 13 + * bits. + */ + if (chan->type == IIO_CURRENT && chan->channel == AXP20X_BATT_DISCHRG_I) + size = 13; + else + size = 12; + + ret = axp20x_read_variable_width(info->regmap, chan->address, size); + if (ret < 0) + return ret; + + *val = ret; + return IIO_VAL_INT; +} + +static int axp22x_adc_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val) +{ + struct axp20x_adc_iio *info = iio_priv(indio_dev); + int ret; + + ret = axp20x_read_variable_width(info->regmap, chan->address, 12); + if (ret < 0) + return ret; + + *val = ret; + return IIO_VAL_INT; +} + +static int axp717_adc_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val) +{ + struct axp20x_adc_iio *info = iio_priv(indio_dev); + u8 bulk_reg[2]; + int ret; + + /* + * A generic "ADC data" channel is used for TS, tdie, vmid, + * and vbackup. This channel must both first be enabled and + * also selected before it can be read. + */ + switch (chan->channel) { + case AXP717_TS_IN: + regmap_write(info->regmap, AXP717_ADC_DATA_SEL, + AXP717_ADC_DATA_TS); + break; + case AXP717_DIE_TEMP_V: + regmap_write(info->regmap, AXP717_ADC_DATA_SEL, + AXP717_ADC_DATA_TEMP); + break; + case AXP717_VMID_V: + regmap_write(info->regmap, AXP717_ADC_DATA_SEL, + AXP717_ADC_DATA_VMID); + break; + case AXP717_BKUP_BATT_V: + regmap_write(info->regmap, AXP717_ADC_DATA_SEL, + AXP717_ADC_DATA_BKUP_BATT); + break; + default: + break; + } + + /* + * All channels are 14 bits, with the first 2 bits on the high + * register reserved and the remaining bits as the ADC value. + */ + ret = regmap_bulk_read(info->regmap, chan->address, bulk_reg, 2); + if (ret < 0) + return ret; + + *val = FIELD_GET(AXP717_ADC_DATA_MASK, get_unaligned_be16(bulk_reg)); + return IIO_VAL_INT; +} + +static int axp813_adc_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val) +{ + struct axp20x_adc_iio *info = iio_priv(indio_dev); + int ret; + + ret = axp20x_read_variable_width(info->regmap, chan->address, 12); + if (ret < 0) + return ret; + + *val = ret; + return IIO_VAL_INT; +} + +static int axp192_adc_scale_voltage(int channel, int *val, int *val2) +{ + switch (channel) { + case AXP192_ACIN_V: + case AXP192_VBUS_V: + *val = 1; + *val2 = 700000; + return IIO_VAL_INT_PLUS_MICRO; + + case AXP192_GPIO0_V: + case AXP192_GPIO1_V: + case AXP192_GPIO2_V: + case AXP192_GPIO3_V: + *val = 0; + *val2 = 500000; + return IIO_VAL_INT_PLUS_MICRO; + + case AXP192_BATT_V: + *val = 1; + *val2 = 100000; + return IIO_VAL_INT_PLUS_MICRO; + + case AXP192_IPSOUT_V: + *val = 1; + *val2 = 400000; + return IIO_VAL_INT_PLUS_MICRO; + + case AXP192_TS_IN: + /* 0.8 mV per LSB */ + *val = 0; + *val2 = 800000; + return IIO_VAL_INT_PLUS_MICRO; + + default: + return -EINVAL; + } +} + +static int axp20x_adc_scale_voltage(int channel, int *val, int *val2) +{ + switch (channel) { + case AXP20X_ACIN_V: + case AXP20X_VBUS_V: + *val = 1; + *val2 = 700000; + return IIO_VAL_INT_PLUS_MICRO; + + case AXP20X_GPIO0_V: + case AXP20X_GPIO1_V: + *val = 0; + *val2 = 500000; + return IIO_VAL_INT_PLUS_MICRO; + + case AXP20X_BATT_V: + *val = 1; + *val2 = 100000; + return IIO_VAL_INT_PLUS_MICRO; + + case AXP20X_IPSOUT_V: + *val = 1; + *val2 = 400000; + return IIO_VAL_INT_PLUS_MICRO; + + case AXP20X_TS_IN: + /* 0.8 mV per LSB */ + *val = 0; + *val2 = 800000; + return IIO_VAL_INT_PLUS_MICRO; + + default: + return -EINVAL; + } +} + +static int axp22x_adc_scale_voltage(int channel, int *val, int *val2) +{ + switch (channel) { + case AXP22X_BATT_V: + /* 1.1 mV per LSB */ + *val = 1; + *val2 = 100000; + return IIO_VAL_INT_PLUS_MICRO; + + case AXP22X_TS_IN: + /* 0.8 mV per LSB */ + *val = 0; + *val2 = 800000; + return IIO_VAL_INT_PLUS_MICRO; + + default: + return -EINVAL; + } +} +static int axp813_adc_scale_voltage(int channel, int *val, int *val2) +{ + switch (channel) { + case AXP813_GPIO0_V: + *val = 0; + *val2 = 800000; + return IIO_VAL_INT_PLUS_MICRO; + + case AXP813_BATT_V: + *val = 1; + *val2 = 100000; + return IIO_VAL_INT_PLUS_MICRO; + + case AXP813_TS_IN: + /* 0.8 mV per LSB */ + *val = 0; + *val2 = 800000; + return IIO_VAL_INT_PLUS_MICRO; + + default: + return -EINVAL; + } +} + +static int axp20x_adc_scale_current(int channel, int *val, int *val2) +{ + switch (channel) { + case AXP20X_ACIN_I: + *val = 0; + *val2 = 625000; + return IIO_VAL_INT_PLUS_MICRO; + + case AXP20X_VBUS_I: + *val = 0; + *val2 = 375000; + return IIO_VAL_INT_PLUS_MICRO; + + case AXP20X_BATT_DISCHRG_I: + case AXP20X_BATT_CHRG_I: + *val = 0; + *val2 = 500000; + return IIO_VAL_INT_PLUS_MICRO; + + default: + return -EINVAL; + } +} + +static int axp192_adc_scale(struct iio_chan_spec const *chan, int *val, + int *val2) +{ + switch (chan->type) { + case IIO_VOLTAGE: + return axp192_adc_scale_voltage(chan->channel, val, val2); + + case IIO_CURRENT: + /* + * AXP192 current channels are identical to the AXP20x, + * therefore we can re-use the scaling function. + */ + return axp20x_adc_scale_current(chan->channel, val, val2); + + case IIO_TEMP: + *val = 100; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int axp20x_adc_scale(struct iio_chan_spec const *chan, int *val, + int *val2) +{ + switch (chan->type) { + case IIO_VOLTAGE: + return axp20x_adc_scale_voltage(chan->channel, val, val2); + + case IIO_CURRENT: + return axp20x_adc_scale_current(chan->channel, val, val2); + + case IIO_TEMP: + *val = 100; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int axp22x_adc_scale(struct iio_chan_spec const *chan, int *val, + int *val2) +{ + switch (chan->type) { + case IIO_VOLTAGE: + return axp22x_adc_scale_voltage(chan->channel, val, val2); + + case IIO_CURRENT: + *val = 1; + return IIO_VAL_INT; + + case IIO_TEMP: + *val = 100; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int axp717_adc_scale(struct iio_chan_spec const *chan, int *val, + int *val2) +{ + switch (chan->type) { + case IIO_VOLTAGE: + *val = 1; + return IIO_VAL_INT; + + case IIO_CURRENT: + *val = 1; + return IIO_VAL_INT; + + case IIO_TEMP: + *val = 100; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int axp813_adc_scale(struct iio_chan_spec const *chan, int *val, + int *val2) +{ + switch (chan->type) { + case IIO_VOLTAGE: + return axp813_adc_scale_voltage(chan->channel, val, val2); + + case IIO_CURRENT: + *val = 1; + return IIO_VAL_INT; + + case IIO_TEMP: + *val = 100; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int axp192_adc_offset_voltage(struct iio_dev *indio_dev, int channel, + int *val) +{ + struct axp20x_adc_iio *info = iio_priv(indio_dev); + unsigned int regval; + int ret; + + ret = regmap_read(info->regmap, AXP192_GPIO30_IN_RANGE, ®val); + if (ret < 0) + return ret; + + switch (channel) { + case AXP192_GPIO0_V: + regval = FIELD_GET(AXP192_GPIO30_IN_RANGE_GPIO0, regval); + break; + + case AXP192_GPIO1_V: + regval = FIELD_GET(AXP192_GPIO30_IN_RANGE_GPIO1, regval); + break; + + case AXP192_GPIO2_V: + regval = FIELD_GET(AXP192_GPIO30_IN_RANGE_GPIO2, regval); + break; + + case AXP192_GPIO3_V: + regval = FIELD_GET(AXP192_GPIO30_IN_RANGE_GPIO3, regval); + break; + + default: + return -EINVAL; + } + + *val = regval ? 700000 : 0; + return IIO_VAL_INT; +} + +static int axp20x_adc_offset_voltage(struct iio_dev *indio_dev, int channel, + int *val) +{ + struct axp20x_adc_iio *info = iio_priv(indio_dev); + unsigned int regval; + int ret; + + ret = regmap_read(info->regmap, AXP20X_GPIO10_IN_RANGE, ®val); + if (ret < 0) + return ret; + + switch (channel) { + case AXP20X_GPIO0_V: + regval = FIELD_GET(AXP20X_GPIO10_IN_RANGE_GPIO0, regval); + break; + + case AXP20X_GPIO1_V: + regval = FIELD_GET(AXP20X_GPIO10_IN_RANGE_GPIO1, regval); + break; + + default: + return -EINVAL; + } + + *val = regval ? 700000 : 0; + return IIO_VAL_INT; +} + +static int axp192_adc_offset(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val) +{ + switch (chan->type) { + case IIO_VOLTAGE: + return axp192_adc_offset_voltage(indio_dev, chan->channel, val); + + case IIO_TEMP: + *val = -1447; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int axp20x_adc_offset(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val) +{ + switch (chan->type) { + case IIO_VOLTAGE: + return axp20x_adc_offset_voltage(indio_dev, chan->channel, val); + + case IIO_TEMP: + *val = -1447; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int axp192_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_OFFSET: + return axp192_adc_offset(indio_dev, chan, val); + + case IIO_CHAN_INFO_SCALE: + return axp192_adc_scale(chan, val, val2); + + case IIO_CHAN_INFO_RAW: + return axp192_adc_raw(indio_dev, chan, val); + + default: + return -EINVAL; + } +} + +static int axp20x_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_OFFSET: + return axp20x_adc_offset(indio_dev, chan, val); + + case IIO_CHAN_INFO_SCALE: + return axp20x_adc_scale(chan, val, val2); + + case IIO_CHAN_INFO_RAW: + return axp20x_adc_raw(indio_dev, chan, val); + + default: + return -EINVAL; + } +} + +static int axp22x_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_OFFSET: + /* For PMIC temp only */ + *val = -2677; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + return axp22x_adc_scale(chan, val, val2); + + case IIO_CHAN_INFO_RAW: + return axp22x_adc_raw(indio_dev, chan, val); + + default: + return -EINVAL; + } +} + +static int axp717_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return axp717_adc_scale(chan, val, val2); + + case IIO_CHAN_INFO_RAW: + return axp717_adc_raw(indio_dev, chan, val); + + default: + return -EINVAL; + } +} + +static int axp813_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_OFFSET: + *val = -2667; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + return axp813_adc_scale(chan, val, val2); + + case IIO_CHAN_INFO_RAW: + return axp813_adc_raw(indio_dev, chan, val); + + default: + return -EINVAL; + } +} + +static int axp192_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, + long mask) +{ + struct axp20x_adc_iio *info = iio_priv(indio_dev); + unsigned int regmask, regval; + + /* + * The AXP192 PMIC allows the user to choose between 0V and 0.7V offsets + * for (independently) GPIO0-3 when in ADC mode. + */ + if (mask != IIO_CHAN_INFO_OFFSET) + return -EINVAL; + + if (val != 0 && val != 700000) + return -EINVAL; + + switch (chan->channel) { + case AXP192_GPIO0_V: + regmask = AXP192_GPIO30_IN_RANGE_GPIO0; + regval = FIELD_PREP(AXP192_GPIO30_IN_RANGE_GPIO0, !!val); + break; + + case AXP192_GPIO1_V: + regmask = AXP192_GPIO30_IN_RANGE_GPIO1; + regval = FIELD_PREP(AXP192_GPIO30_IN_RANGE_GPIO1, !!val); + break; + + case AXP192_GPIO2_V: + regmask = AXP192_GPIO30_IN_RANGE_GPIO2; + regval = FIELD_PREP(AXP192_GPIO30_IN_RANGE_GPIO2, !!val); + break; + + case AXP192_GPIO3_V: + regmask = AXP192_GPIO30_IN_RANGE_GPIO3; + regval = FIELD_PREP(AXP192_GPIO30_IN_RANGE_GPIO3, !!val); + break; + + default: + return -EINVAL; + } + + return regmap_update_bits(info->regmap, AXP192_GPIO30_IN_RANGE, regmask, regval); +} + +static int axp20x_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, + long mask) +{ + struct axp20x_adc_iio *info = iio_priv(indio_dev); + unsigned int regmask, regval; + + /* + * The AXP20X PMIC allows the user to choose between 0V and 0.7V offsets + * for (independently) GPIO0 and GPIO1 when in ADC mode. + */ + if (mask != IIO_CHAN_INFO_OFFSET) + return -EINVAL; + + if (val != 0 && val != 700000) + return -EINVAL; + + switch (chan->channel) { + case AXP20X_GPIO0_V: + regmask = AXP20X_GPIO10_IN_RANGE_GPIO0; + regval = FIELD_PREP(AXP20X_GPIO10_IN_RANGE_GPIO0, !!val); + break; + + case AXP20X_GPIO1_V: + regmask = AXP20X_GPIO10_IN_RANGE_GPIO1; + regval = FIELD_PREP(AXP20X_GPIO10_IN_RANGE_GPIO1, !!val); + break; + + default: + return -EINVAL; + } + + return regmap_update_bits(info->regmap, AXP20X_GPIO10_IN_RANGE, regmask, regval); +} + +static const struct iio_info axp192_adc_iio_info = { + .read_raw = axp192_read_raw, + .write_raw = axp192_write_raw, +}; + +static const struct iio_info axp20x_adc_iio_info = { + .read_raw = axp20x_read_raw, + .write_raw = axp20x_write_raw, +}; + +static const struct iio_info axp22x_adc_iio_info = { + .read_raw = axp22x_read_raw, +}; + +static const struct iio_info axp717_adc_iio_info = { + .read_raw = axp717_read_raw, +}; + +static const struct iio_info axp813_adc_iio_info = { + .read_raw = axp813_read_raw, +}; + +static int axp20x_adc_rate(struct axp20x_adc_iio *info, int rate) +{ + return regmap_update_bits(info->regmap, AXP20X_ADC_RATE, + AXP20X_ADC_RATE_MASK, + AXP20X_ADC_RATE_HZ(rate)); +} + +static int axp22x_adc_rate(struct axp20x_adc_iio *info, int rate) +{ + return regmap_update_bits(info->regmap, AXP20X_ADC_RATE, + AXP20X_ADC_RATE_MASK, + AXP22X_ADC_RATE_HZ(rate)); +} + +static int axp813_adc_rate(struct axp20x_adc_iio *info, int rate) +{ + return regmap_update_bits(info->regmap, AXP813_ADC_RATE, + AXP813_ADC_RATE_MASK, + AXP813_ADC_RATE_HZ(rate)); +} + +struct axp_data { + const struct iio_info *iio_info; + int num_channels; + struct iio_chan_spec const *channels; + unsigned long adc_en1; + unsigned long adc_en1_mask; + unsigned long adc_en2; + unsigned long adc_en2_mask; + int (*adc_rate)(struct axp20x_adc_iio *info, + int rate); + const struct iio_map *maps; +}; + +static const struct axp_data axp192_data = { + .iio_info = &axp192_adc_iio_info, + .num_channels = ARRAY_SIZE(axp192_adc_channels), + .channels = axp192_adc_channels, + .adc_en1_mask = AXP192_ADC_EN1_MASK, + .adc_en2_mask = AXP192_ADC_EN2_MASK, + .adc_rate = axp20x_adc_rate, + .maps = axp20x_maps, +}; + +static const struct axp_data axp20x_data = { + .iio_info = &axp20x_adc_iio_info, + .num_channels = ARRAY_SIZE(axp20x_adc_channels), + .channels = axp20x_adc_channels, + .adc_en1 = AXP20X_ADC_EN1, + .adc_en1_mask = AXP20X_ADC_EN1_MASK, + .adc_en2 = AXP20X_ADC_EN2, + .adc_en2_mask = AXP20X_ADC_EN2_MASK, + .adc_rate = axp20x_adc_rate, + .maps = axp20x_maps, +}; + +static const struct axp_data axp22x_data = { + .iio_info = &axp22x_adc_iio_info, + .num_channels = ARRAY_SIZE(axp22x_adc_channels), + .channels = axp22x_adc_channels, + .adc_en1 = AXP20X_ADC_EN1, + .adc_en1_mask = AXP22X_ADC_EN1_MASK, + .adc_rate = axp22x_adc_rate, + .maps = axp22x_maps, +}; + +static const struct axp_data axp717_data = { + .iio_info = &axp717_adc_iio_info, + .num_channels = ARRAY_SIZE(axp717_adc_channels), + .channels = axp717_adc_channels, + .adc_en1 = AXP717_ADC_CH_EN_CONTROL, + .adc_en1_mask = AXP717_ADC_EN1_MASK, + .maps = axp717_maps, +}; + +static const struct axp_data axp813_data = { + .iio_info = &axp813_adc_iio_info, + .num_channels = ARRAY_SIZE(axp813_adc_channels), + .channels = axp813_adc_channels, + .adc_en1 = AXP20X_ADC_EN1, + .adc_en1_mask = AXP22X_ADC_EN1_MASK, + .adc_rate = axp813_adc_rate, + .maps = axp22x_maps, +}; + +static const struct of_device_id axp20x_adc_of_match[] = { + { .compatible = "x-powers,axp192-adc", .data = (void *)&axp192_data, }, + { .compatible = "x-powers,axp209-adc", .data = (void *)&axp20x_data, }, + { .compatible = "x-powers,axp221-adc", .data = (void *)&axp22x_data, }, + { .compatible = "x-powers,axp717-adc", .data = (void *)&axp717_data, }, + { .compatible = "x-powers,axp813-adc", .data = (void *)&axp813_data, }, + { } +}; +MODULE_DEVICE_TABLE(of, axp20x_adc_of_match); + +static const struct platform_device_id axp20x_adc_id_match[] = { + { .name = "axp192-adc", .driver_data = (kernel_ulong_t)&axp192_data, }, + { .name = "axp20x-adc", .driver_data = (kernel_ulong_t)&axp20x_data, }, + { .name = "axp22x-adc", .driver_data = (kernel_ulong_t)&axp22x_data, }, + { .name = "axp717-adc", .driver_data = (kernel_ulong_t)&axp717_data, }, + { .name = "axp813-adc", .driver_data = (kernel_ulong_t)&axp813_data, }, + { } +}; +MODULE_DEVICE_TABLE(platform, axp20x_adc_id_match); + +static int axp20x_probe(struct platform_device *pdev) +{ + struct axp20x_adc_iio *info; + struct iio_dev *indio_dev; + struct axp20x_dev *axp20x_dev; + int ret; + + axp20x_dev = dev_get_drvdata(pdev->dev.parent); + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + platform_set_drvdata(pdev, indio_dev); + + info->regmap = axp20x_dev->regmap; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (!dev_fwnode(&pdev->dev)) { + const struct platform_device_id *id; + + id = platform_get_device_id(pdev); + info->data = (const struct axp_data *)id->driver_data; + } else { + struct device *dev = &pdev->dev; + + info->data = device_get_match_data(dev); + } + + indio_dev->name = platform_get_device_id(pdev)->name; + indio_dev->info = info->data->iio_info; + indio_dev->num_channels = info->data->num_channels; + indio_dev->channels = info->data->channels; + + /* Enable the ADCs on IP */ + regmap_write(info->regmap, info->data->adc_en1, + info->data->adc_en1_mask); + + if (info->data->adc_en2_mask) + regmap_set_bits(info->regmap, info->data->adc_en2, + info->data->adc_en2_mask); + + /* Configure ADCs rate */ + if (info->data->adc_rate) + info->data->adc_rate(info, 100); + + ret = iio_map_array_register(indio_dev, info->data->maps); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register IIO maps: %d\n", ret); + goto fail_map; + } + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&pdev->dev, "could not register the device\n"); + goto fail_register; + } + + return 0; + +fail_register: + iio_map_array_unregister(indio_dev); + +fail_map: + regmap_write(info->regmap, info->data->adc_en1, 0); + + if (info->data->adc_en2_mask) + regmap_write(info->regmap, info->data->adc_en2, 0); + + return ret; +} + +static void axp20x_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct axp20x_adc_iio *info = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_map_array_unregister(indio_dev); + + regmap_write(info->regmap, info->data->adc_en1, 0); + + if (info->data->adc_en2_mask) + regmap_write(info->regmap, info->data->adc_en2, 0); +} + +static struct platform_driver axp20x_adc_driver = { + .driver = { + .name = "axp20x-adc", + .of_match_table = axp20x_adc_of_match, + }, + .id_table = axp20x_adc_id_match, + .probe = axp20x_probe, + .remove = axp20x_remove, +}; + +module_platform_driver(axp20x_adc_driver); + +MODULE_DESCRIPTION("ADC driver for AXP20X and AXP22X PMICs"); +MODULE_AUTHOR("Quentin Schulz "); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/axp288_adc.c b/drivers/iio/adc/axp288_adc.c new file mode 100644 index 000000000..c8283279c --- /dev/null +++ b/drivers/iio/adc/axp288_adc.c @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * axp288_adc.c - X-Powers AXP288 PMIC ADC Driver + * + * Copyright (C) 2014 Intel Corporation + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* + * This mask enables all ADCs except for the battery temp-sensor (TS), that is + * left as-is to avoid breaking charging on devices without a temp-sensor. + */ +#define AXP288_ADC_EN_MASK 0xF0 +#define AXP288_ADC_TS_ENABLE 0x01 + +#define AXP288_ADC_TS_BIAS_MASK GENMASK(5, 4) +#define AXP288_ADC_TS_BIAS_20UA (0 << 4) +#define AXP288_ADC_TS_BIAS_40UA (1 << 4) +#define AXP288_ADC_TS_BIAS_60UA (2 << 4) +#define AXP288_ADC_TS_BIAS_80UA (3 << 4) +#define AXP288_ADC_TS_CURRENT_ON_OFF_MASK GENMASK(1, 0) +#define AXP288_ADC_TS_CURRENT_OFF (0 << 0) +#define AXP288_ADC_TS_CURRENT_ON_WHEN_CHARGING (1 << 0) +#define AXP288_ADC_TS_CURRENT_ON_ONDEMAND (2 << 0) +#define AXP288_ADC_TS_CURRENT_ON (3 << 0) + +enum axp288_adc_id { + AXP288_ADC_TS, + AXP288_ADC_PMIC, + AXP288_ADC_GP, + AXP288_ADC_BATT_CHRG_I, + AXP288_ADC_BATT_DISCHRG_I, + AXP288_ADC_BATT_V, + AXP288_ADC_NR_CHAN, +}; + +struct axp288_adc_info { + int irq; + struct regmap *regmap; + /* lock to protect against multiple access to the device */ + struct mutex lock; + bool ts_enabled; +}; + +static const struct iio_chan_spec axp288_adc_channels[] = { + { + .indexed = 1, + .type = IIO_TEMP, + .channel = 0, + .address = AXP288_TS_ADC_H, + .datasheet_name = "TS_PIN", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .indexed = 1, + .type = IIO_TEMP, + .channel = 1, + .address = AXP288_PMIC_ADC_H, + .datasheet_name = "PMIC_TEMP", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .indexed = 1, + .type = IIO_TEMP, + .channel = 2, + .address = AXP288_GP_ADC_H, + .datasheet_name = "GPADC", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .indexed = 1, + .type = IIO_CURRENT, + .channel = 3, + .address = AXP20X_BATT_CHRG_I_H, + .datasheet_name = "BATT_CHG_I", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .indexed = 1, + .type = IIO_CURRENT, + .channel = 4, + .address = AXP20X_BATT_DISCHRG_I_H, + .datasheet_name = "BATT_DISCHRG_I", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .indexed = 1, + .type = IIO_VOLTAGE, + .channel = 5, + .address = AXP20X_BATT_V_H, + .datasheet_name = "BATT_V", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, +}; + +/* for consumer drivers */ +static const struct iio_map axp288_adc_default_maps[] = { + IIO_MAP("TS_PIN", "axp288-batt", "axp288-batt-temp"), + IIO_MAP("PMIC_TEMP", "axp288-pmic", "axp288-pmic-temp"), + IIO_MAP("GPADC", "axp288-gpadc", "axp288-system-temp"), + IIO_MAP("BATT_CHG_I", "axp288-chrg", "axp288-chrg-curr"), + IIO_MAP("BATT_DISCHRG_I", "axp288-chrg", "axp288-chrg-d-curr"), + IIO_MAP("BATT_V", "axp288-batt", "axp288-batt-volt"), + { } +}; + +static int axp288_adc_read_channel(int *val, unsigned long address, + struct regmap *regmap) +{ + u8 buf[2]; + + if (regmap_bulk_read(regmap, address, buf, 2)) + return -EIO; + *val = (buf[0] << 4) + ((buf[1] >> 4) & 0x0F); + + return IIO_VAL_INT; +} + +/* + * The current-source used for the battery temp-sensor (TS) is shared + * with the GPADC. For proper fuel-gauge and charger operation the TS + * current-source needs to be permanently on. But to read the GPADC we + * need to temporary switch the TS current-source to ondemand, so that + * the GPADC can use it, otherwise we will always read an all 0 value. + */ +static int axp288_adc_set_ts(struct axp288_adc_info *info, + unsigned int mode, unsigned long address) +{ + int ret; + + /* No need to switch the current-source if the TS pin is disabled */ + if (!info->ts_enabled) + return 0; + + /* Channels other than GPADC do not need the current source */ + if (address != AXP288_GP_ADC_H) + return 0; + + ret = regmap_update_bits(info->regmap, AXP288_ADC_TS_PIN_CTRL, + AXP288_ADC_TS_CURRENT_ON_OFF_MASK, mode); + if (ret) + return ret; + + /* When switching to the GPADC pin give things some time to settle */ + if (mode == AXP288_ADC_TS_CURRENT_ON_ONDEMAND) + usleep_range(6000, 10000); + + return 0; +} + +static int axp288_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct axp288_adc_info *info = iio_priv(indio_dev); + + mutex_lock(&info->lock); + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (axp288_adc_set_ts(info, AXP288_ADC_TS_CURRENT_ON_ONDEMAND, + chan->address)) { + dev_err(&indio_dev->dev, "GPADC mode\n"); + ret = -EINVAL; + break; + } + ret = axp288_adc_read_channel(val, chan->address, info->regmap); + if (axp288_adc_set_ts(info, AXP288_ADC_TS_CURRENT_ON, + chan->address)) + dev_err(&indio_dev->dev, "TS pin restore\n"); + break; + default: + ret = -EINVAL; + } + mutex_unlock(&info->lock); + + return ret; +} + +/* + * We rely on the machine's firmware to correctly setup the TS pin bias current + * at boot. This lists systems with broken fw where we need to set it ourselves. + */ +static const struct dmi_system_id axp288_adc_ts_bias_override[] = { + { + /* Lenovo Ideapad 100S (11 inch) */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 100S-11IBY"), + }, + .driver_data = (void *)(uintptr_t)AXP288_ADC_TS_BIAS_80UA, + }, + { + /* Nuvision Solo 10 Draw */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TMAX"), + DMI_MATCH(DMI_PRODUCT_NAME, "TM101W610L"), + }, + .driver_data = (void *)(uintptr_t)AXP288_ADC_TS_BIAS_80UA, + }, + { } +}; + +static int axp288_adc_initialize(struct axp288_adc_info *info) +{ + const struct dmi_system_id *bias_override; + int ret, adc_enable_val; + + bias_override = dmi_first_match(axp288_adc_ts_bias_override); + if (bias_override) { + ret = regmap_update_bits(info->regmap, AXP288_ADC_TS_PIN_CTRL, + AXP288_ADC_TS_BIAS_MASK, + (uintptr_t)bias_override->driver_data); + if (ret) + return ret; + } + + /* + * Determine if the TS pin is enabled and set the TS current-source + * accordingly. + */ + ret = regmap_read(info->regmap, AXP20X_ADC_EN1, &adc_enable_val); + if (ret) + return ret; + + if (adc_enable_val & AXP288_ADC_TS_ENABLE) { + info->ts_enabled = true; + ret = regmap_update_bits(info->regmap, AXP288_ADC_TS_PIN_CTRL, + AXP288_ADC_TS_CURRENT_ON_OFF_MASK, + AXP288_ADC_TS_CURRENT_ON); + } else { + info->ts_enabled = false; + ret = regmap_update_bits(info->regmap, AXP288_ADC_TS_PIN_CTRL, + AXP288_ADC_TS_CURRENT_ON_OFF_MASK, + AXP288_ADC_TS_CURRENT_OFF); + } + if (ret) + return ret; + + /* Turn on the ADC for all channels except TS, leave TS as is */ + return regmap_set_bits(info->regmap, AXP20X_ADC_EN1, + AXP288_ADC_EN_MASK); +} + +static const struct iio_info axp288_adc_iio_info = { + .read_raw = &axp288_adc_read_raw, +}; + +static int axp288_adc_probe(struct platform_device *pdev) +{ + int ret; + struct axp288_adc_info *info; + struct iio_dev *indio_dev; + struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + info->irq = platform_get_irq(pdev, 0); + if (info->irq < 0) + return info->irq; + + info->regmap = axp20x->regmap; + /* + * Set ADC to enabled state at all time, including system suspend. + * otherwise internal fuel gauge functionality may be affected. + */ + ret = axp288_adc_initialize(info); + if (ret) { + dev_err(&pdev->dev, "unable to enable ADC device\n"); + return ret; + } + + indio_dev->name = pdev->name; + indio_dev->channels = axp288_adc_channels; + indio_dev->num_channels = ARRAY_SIZE(axp288_adc_channels); + indio_dev->info = &axp288_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_iio_map_array_register(&pdev->dev, indio_dev, axp288_adc_default_maps); + if (ret < 0) + return ret; + + mutex_init(&info->lock); + + return devm_iio_device_register(&pdev->dev, indio_dev); +} + +static const struct platform_device_id axp288_adc_id_table[] = { + { .name = "axp288_adc" }, + { } +}; + +static struct platform_driver axp288_adc_driver = { + .probe = axp288_adc_probe, + .id_table = axp288_adc_id_table, + .driver = { + .name = "axp288_adc", + }, +}; + +MODULE_DEVICE_TABLE(platform, axp288_adc_id_table); + +module_platform_driver(axp288_adc_driver); + +MODULE_AUTHOR("Jacob Pan "); +MODULE_DESCRIPTION("X-Powers AXP288 ADC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/bcm_iproc_adc.c b/drivers/iio/adc/bcm_iproc_adc.c new file mode 100644 index 000000000..cf4738b16 --- /dev/null +++ b/drivers/iio/adc/bcm_iproc_adc.c @@ -0,0 +1,622 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2016 Broadcom + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Below Register's are common to IPROC ADC and Touchscreen IP */ +#define IPROC_REGCTL1 0x00 +#define IPROC_REGCTL2 0x04 +#define IPROC_INTERRUPT_THRES 0x08 +#define IPROC_INTERRUPT_MASK 0x0c +#define IPROC_INTERRUPT_STATUS 0x10 +#define IPROC_ANALOG_CONTROL 0x1c +#define IPROC_CONTROLLER_STATUS 0x14 +#define IPROC_AUX_DATA 0x20 +#define IPROC_SOFT_BYPASS_CONTROL 0x38 +#define IPROC_SOFT_BYPASS_DATA 0x3C + +/* IPROC ADC Channel register offsets */ +#define IPROC_ADC_CHANNEL_REGCTL1 0x800 +#define IPROC_ADC_CHANNEL_REGCTL2 0x804 +#define IPROC_ADC_CHANNEL_STATUS 0x808 +#define IPROC_ADC_CHANNEL_INTERRUPT_STATUS 0x80c +#define IPROC_ADC_CHANNEL_INTERRUPT_MASK 0x810 +#define IPROC_ADC_CHANNEL_DATA 0x814 +#define IPROC_ADC_CHANNEL_OFFSET 0x20 + +/* Bit definitions for IPROC_REGCTL2 */ +#define IPROC_ADC_AUXIN_SCAN_ENA BIT(0) +#define IPROC_ADC_PWR_LDO BIT(5) +#define IPROC_ADC_PWR_ADC BIT(4) +#define IPROC_ADC_PWR_BG BIT(3) +#define IPROC_ADC_CONTROLLER_EN BIT(17) + +/* Bit definitions for IPROC_INTERRUPT_MASK and IPROC_INTERRUPT_STATUS */ +#define IPROC_ADC_AUXDATA_RDY_INTR BIT(3) +#define IPROC_ADC_INTR 9 +#define IPROC_ADC_INTR_MASK (0xFF << IPROC_ADC_INTR) + +/* Bit definitions for IPROC_ANALOG_CONTROL */ +#define IPROC_ADC_CHANNEL_SEL 11 +#define IPROC_ADC_CHANNEL_SEL_MASK (0x7 << IPROC_ADC_CHANNEL_SEL) + +/* Bit definitions for IPROC_ADC_CHANNEL_REGCTL1 */ +#define IPROC_ADC_CHANNEL_ROUNDS 0x2 +#define IPROC_ADC_CHANNEL_ROUNDS_MASK (0x3F << IPROC_ADC_CHANNEL_ROUNDS) +#define IPROC_ADC_CHANNEL_MODE 0x1 +#define IPROC_ADC_CHANNEL_MODE_MASK (0x1 << IPROC_ADC_CHANNEL_MODE) +#define IPROC_ADC_CHANNEL_MODE_TDM 0x1 +#define IPROC_ADC_CHANNEL_MODE_SNAPSHOT 0x0 +#define IPROC_ADC_CHANNEL_ENABLE 0x0 +#define IPROC_ADC_CHANNEL_ENABLE_MASK 0x1 + +/* Bit definitions for IPROC_ADC_CHANNEL_REGCTL2 */ +#define IPROC_ADC_CHANNEL_WATERMARK 0x0 +#define IPROC_ADC_CHANNEL_WATERMARK_MASK \ + (0x3F << IPROC_ADC_CHANNEL_WATERMARK) + +#define IPROC_ADC_WATER_MARK_LEVEL 0x1 + +/* Bit definitions for IPROC_ADC_CHANNEL_STATUS */ +#define IPROC_ADC_CHANNEL_DATA_LOST 0x0 +#define IPROC_ADC_CHANNEL_DATA_LOST_MASK \ + (0x0 << IPROC_ADC_CHANNEL_DATA_LOST) +#define IPROC_ADC_CHANNEL_VALID_ENTERIES 0x1 +#define IPROC_ADC_CHANNEL_VALID_ENTERIES_MASK \ + (0xFF << IPROC_ADC_CHANNEL_VALID_ENTERIES) +#define IPROC_ADC_CHANNEL_TOTAL_ENTERIES 0x9 +#define IPROC_ADC_CHANNEL_TOTAL_ENTERIES_MASK \ + (0xFF << IPROC_ADC_CHANNEL_TOTAL_ENTERIES) + +/* Bit definitions for IPROC_ADC_CHANNEL_INTERRUPT_MASK */ +#define IPROC_ADC_CHANNEL_WTRMRK_INTR 0x0 +#define IPROC_ADC_CHANNEL_WTRMRK_INTR_MASK \ + (0x1 << IPROC_ADC_CHANNEL_WTRMRK_INTR) +#define IPROC_ADC_CHANNEL_FULL_INTR 0x1 +#define IPROC_ADC_CHANNEL_FULL_INTR_MASK \ + (0x1 << IPROC_ADC_IPROC_ADC_CHANNEL_FULL_INTR) +#define IPROC_ADC_CHANNEL_EMPTY_INTR 0x2 +#define IPROC_ADC_CHANNEL_EMPTY_INTR_MASK \ + (0x1 << IPROC_ADC_CHANNEL_EMPTY_INTR) + +#define IPROC_ADC_WATER_MARK_INTR_ENABLE 0x1 + +/* Number of time to retry a set of the interrupt mask reg */ +#define IPROC_ADC_INTMASK_RETRY_ATTEMPTS 10 + +#define IPROC_ADC_READ_TIMEOUT (HZ*2) + +#define iproc_adc_dbg_reg(dev, priv, reg) \ +do { \ + u32 val; \ + regmap_read(priv->regmap, reg, &val); \ + dev_dbg(dev, "%20s= 0x%08x\n", #reg, val); \ +} while (0) + +struct iproc_adc_priv { + struct regmap *regmap; + struct clk *adc_clk; + struct mutex mutex; + int irqno; + int chan_val; + int chan_id; + struct completion completion; +}; + +static void iproc_adc_reg_dump(struct iio_dev *indio_dev) +{ + struct device *dev = &indio_dev->dev; + struct iproc_adc_priv *adc_priv = iio_priv(indio_dev); + + iproc_adc_dbg_reg(dev, adc_priv, IPROC_REGCTL1); + iproc_adc_dbg_reg(dev, adc_priv, IPROC_REGCTL2); + iproc_adc_dbg_reg(dev, adc_priv, IPROC_INTERRUPT_THRES); + iproc_adc_dbg_reg(dev, adc_priv, IPROC_INTERRUPT_MASK); + iproc_adc_dbg_reg(dev, adc_priv, IPROC_INTERRUPT_STATUS); + iproc_adc_dbg_reg(dev, adc_priv, IPROC_CONTROLLER_STATUS); + iproc_adc_dbg_reg(dev, adc_priv, IPROC_ANALOG_CONTROL); + iproc_adc_dbg_reg(dev, adc_priv, IPROC_AUX_DATA); + iproc_adc_dbg_reg(dev, adc_priv, IPROC_SOFT_BYPASS_CONTROL); + iproc_adc_dbg_reg(dev, adc_priv, IPROC_SOFT_BYPASS_DATA); +} + +static irqreturn_t iproc_adc_interrupt_thread(int irq, void *data) +{ + u32 channel_intr_status; + u32 intr_status; + u32 intr_mask; + struct iio_dev *indio_dev = data; + struct iproc_adc_priv *adc_priv = iio_priv(indio_dev); + + /* + * This interrupt is shared with the touchscreen driver. + * Make sure this interrupt is intended for us. + * Handle only ADC channel specific interrupts. + */ + regmap_read(adc_priv->regmap, IPROC_INTERRUPT_STATUS, &intr_status); + regmap_read(adc_priv->regmap, IPROC_INTERRUPT_MASK, &intr_mask); + intr_status = intr_status & intr_mask; + channel_intr_status = (intr_status & IPROC_ADC_INTR_MASK) >> + IPROC_ADC_INTR; + if (channel_intr_status) + return IRQ_WAKE_THREAD; + + return IRQ_NONE; +} + +static irqreturn_t iproc_adc_interrupt_handler(int irq, void *data) +{ + irqreturn_t retval = IRQ_NONE; + struct iproc_adc_priv *adc_priv; + struct iio_dev *indio_dev = data; + unsigned int valid_entries; + u32 intr_status; + u32 intr_channels; + u32 channel_status; + u32 ch_intr_status; + + adc_priv = iio_priv(indio_dev); + + regmap_read(adc_priv->regmap, IPROC_INTERRUPT_STATUS, &intr_status); + dev_dbg(&indio_dev->dev, "iproc_adc_interrupt_handler(),INTRPT_STS:%x\n", + intr_status); + + intr_channels = (intr_status & IPROC_ADC_INTR_MASK) >> IPROC_ADC_INTR; + if (intr_channels) { + regmap_read(adc_priv->regmap, + IPROC_ADC_CHANNEL_INTERRUPT_STATUS + + IPROC_ADC_CHANNEL_OFFSET * adc_priv->chan_id, + &ch_intr_status); + + if (ch_intr_status & IPROC_ADC_CHANNEL_WTRMRK_INTR_MASK) { + regmap_read(adc_priv->regmap, + IPROC_ADC_CHANNEL_STATUS + + IPROC_ADC_CHANNEL_OFFSET * + adc_priv->chan_id, + &channel_status); + + valid_entries = ((channel_status & + IPROC_ADC_CHANNEL_VALID_ENTERIES_MASK) >> + IPROC_ADC_CHANNEL_VALID_ENTERIES); + if (valid_entries >= 1) { + regmap_read(adc_priv->regmap, + IPROC_ADC_CHANNEL_DATA + + IPROC_ADC_CHANNEL_OFFSET * + adc_priv->chan_id, + &adc_priv->chan_val); + complete(&adc_priv->completion); + } else { + dev_err(&indio_dev->dev, + "No data rcvd on channel %d\n", + adc_priv->chan_id); + } + regmap_write(adc_priv->regmap, + IPROC_ADC_CHANNEL_INTERRUPT_MASK + + IPROC_ADC_CHANNEL_OFFSET * + adc_priv->chan_id, + (ch_intr_status & + ~(IPROC_ADC_CHANNEL_WTRMRK_INTR_MASK))); + } + regmap_write(adc_priv->regmap, + IPROC_ADC_CHANNEL_INTERRUPT_STATUS + + IPROC_ADC_CHANNEL_OFFSET * adc_priv->chan_id, + ch_intr_status); + regmap_write(adc_priv->regmap, IPROC_INTERRUPT_STATUS, + intr_channels); + retval = IRQ_HANDLED; + } + + return retval; +} + +static int iproc_adc_do_read(struct iio_dev *indio_dev, + int channel, + u16 *p_adc_data) +{ + int read_len = 0; + u32 val; + u32 mask; + u32 val_check; + int failed_cnt = 0; + struct iproc_adc_priv *adc_priv = iio_priv(indio_dev); + + mutex_lock(&adc_priv->mutex); + + /* + * After a read is complete the ADC interrupts will be disabled so + * we can assume this section of code is safe from interrupts. + */ + adc_priv->chan_val = -1; + adc_priv->chan_id = channel; + + reinit_completion(&adc_priv->completion); + /* Clear any pending interrupt */ + regmap_update_bits(adc_priv->regmap, IPROC_INTERRUPT_STATUS, + IPROC_ADC_INTR_MASK | IPROC_ADC_AUXDATA_RDY_INTR, + ((0x0 << channel) << IPROC_ADC_INTR) | + IPROC_ADC_AUXDATA_RDY_INTR); + + /* Configure channel for snapshot mode and enable */ + val = (BIT(IPROC_ADC_CHANNEL_ROUNDS) | + (IPROC_ADC_CHANNEL_MODE_SNAPSHOT << IPROC_ADC_CHANNEL_MODE) | + (0x1 << IPROC_ADC_CHANNEL_ENABLE)); + + mask = IPROC_ADC_CHANNEL_ROUNDS_MASK | IPROC_ADC_CHANNEL_MODE_MASK | + IPROC_ADC_CHANNEL_ENABLE_MASK; + regmap_update_bits(adc_priv->regmap, (IPROC_ADC_CHANNEL_REGCTL1 + + IPROC_ADC_CHANNEL_OFFSET * channel), + mask, val); + + /* Set the Watermark for a channel */ + regmap_update_bits(adc_priv->regmap, (IPROC_ADC_CHANNEL_REGCTL2 + + IPROC_ADC_CHANNEL_OFFSET * channel), + IPROC_ADC_CHANNEL_WATERMARK_MASK, + 0x1); + + /* Enable water mark interrupt */ + regmap_update_bits(adc_priv->regmap, (IPROC_ADC_CHANNEL_INTERRUPT_MASK + + IPROC_ADC_CHANNEL_OFFSET * + channel), + IPROC_ADC_CHANNEL_WTRMRK_INTR_MASK, + IPROC_ADC_WATER_MARK_INTR_ENABLE); + regmap_read(adc_priv->regmap, IPROC_INTERRUPT_MASK, &val); + + /* Enable ADC interrupt for a channel */ + val |= (BIT(channel) << IPROC_ADC_INTR); + regmap_write(adc_priv->regmap, IPROC_INTERRUPT_MASK, val); + + /* + * There seems to be a very rare issue where writing to this register + * does not take effect. To work around the issue we will try multiple + * writes. In total we will spend about 10*10 = 100 us attempting this. + * Testing has shown that this may loop a few time, but we have never + * hit the full count. + */ + regmap_read(adc_priv->regmap, IPROC_INTERRUPT_MASK, &val_check); + while (val_check != val) { + failed_cnt++; + + if (failed_cnt > IPROC_ADC_INTMASK_RETRY_ATTEMPTS) + break; + + udelay(10); + regmap_update_bits(adc_priv->regmap, IPROC_INTERRUPT_MASK, + IPROC_ADC_INTR_MASK, + ((0x1 << channel) << + IPROC_ADC_INTR)); + + regmap_read(adc_priv->regmap, IPROC_INTERRUPT_MASK, &val_check); + } + + if (failed_cnt) { + dev_dbg(&indio_dev->dev, + "IntMask failed (%d times)", failed_cnt); + if (failed_cnt > IPROC_ADC_INTMASK_RETRY_ATTEMPTS) { + dev_err(&indio_dev->dev, + "IntMask set failed. Read will likely fail."); + read_len = -EIO; + goto adc_err; + } + } + regmap_read(adc_priv->regmap, IPROC_INTERRUPT_MASK, &val_check); + + if (wait_for_completion_timeout(&adc_priv->completion, + IPROC_ADC_READ_TIMEOUT) > 0) { + + /* Only the lower 16 bits are relevant */ + *p_adc_data = adc_priv->chan_val & 0xFFFF; + read_len = sizeof(*p_adc_data); + + } else { + /* + * We never got the interrupt, something went wrong. + * Perhaps the interrupt may still be coming, we do not want + * that now. Lets disable the ADC interrupt, and clear the + * status to put it back in to normal state. + */ + read_len = -ETIMEDOUT; + goto adc_err; + } + mutex_unlock(&adc_priv->mutex); + + return read_len; + +adc_err: + regmap_update_bits(adc_priv->regmap, IPROC_INTERRUPT_MASK, + IPROC_ADC_INTR_MASK, + ((0x0 << channel) << IPROC_ADC_INTR)); + + regmap_update_bits(adc_priv->regmap, IPROC_INTERRUPT_STATUS, + IPROC_ADC_INTR_MASK, + ((0x0 << channel) << IPROC_ADC_INTR)); + + dev_err(&indio_dev->dev, "Timed out waiting for ADC data!\n"); + iproc_adc_reg_dump(indio_dev); + mutex_unlock(&adc_priv->mutex); + + return read_len; +} + +static int iproc_adc_enable(struct iio_dev *indio_dev) +{ + u32 val; + u32 channel_id; + struct iproc_adc_priv *adc_priv = iio_priv(indio_dev); + int ret; + + /* Set i_amux = 3b'000, select channel 0 */ + ret = regmap_clear_bits(adc_priv->regmap, IPROC_ANALOG_CONTROL, + IPROC_ADC_CHANNEL_SEL_MASK); + if (ret) { + dev_err(&indio_dev->dev, + "failed to write IPROC_ANALOG_CONTROL %d\n", ret); + return ret; + } + adc_priv->chan_val = -1; + + /* + * PWR up LDO, ADC, and Band Gap (0 to enable) + * Also enable ADC controller (set high) + */ + ret = regmap_read(adc_priv->regmap, IPROC_REGCTL2, &val); + if (ret) { + dev_err(&indio_dev->dev, + "failed to read IPROC_REGCTL2 %d\n", ret); + return ret; + } + + val &= ~(IPROC_ADC_PWR_LDO | IPROC_ADC_PWR_ADC | IPROC_ADC_PWR_BG); + + ret = regmap_write(adc_priv->regmap, IPROC_REGCTL2, val); + if (ret) { + dev_err(&indio_dev->dev, + "failed to write IPROC_REGCTL2 %d\n", ret); + return ret; + } + + ret = regmap_read(adc_priv->regmap, IPROC_REGCTL2, &val); + if (ret) { + dev_err(&indio_dev->dev, + "failed to read IPROC_REGCTL2 %d\n", ret); + return ret; + } + + val |= IPROC_ADC_CONTROLLER_EN; + ret = regmap_write(adc_priv->regmap, IPROC_REGCTL2, val); + if (ret) { + dev_err(&indio_dev->dev, + "failed to write IPROC_REGCTL2 %d\n", ret); + return ret; + } + + for (channel_id = 0; channel_id < indio_dev->num_channels; + channel_id++) { + ret = regmap_write(adc_priv->regmap, + IPROC_ADC_CHANNEL_INTERRUPT_MASK + + IPROC_ADC_CHANNEL_OFFSET * channel_id, 0); + if (ret) { + dev_err(&indio_dev->dev, + "failed to write ADC_CHANNEL_INTERRUPT_MASK %d\n", + ret); + return ret; + } + + ret = regmap_write(adc_priv->regmap, + IPROC_ADC_CHANNEL_INTERRUPT_STATUS + + IPROC_ADC_CHANNEL_OFFSET * channel_id, 0); + if (ret) { + dev_err(&indio_dev->dev, + "failed to write ADC_CHANNEL_INTERRUPT_STATUS %d\n", + ret); + return ret; + } + } + + return 0; +} + +static void iproc_adc_disable(struct iio_dev *indio_dev) +{ + u32 val; + int ret; + struct iproc_adc_priv *adc_priv = iio_priv(indio_dev); + + ret = regmap_read(adc_priv->regmap, IPROC_REGCTL2, &val); + if (ret) { + dev_err(&indio_dev->dev, + "failed to read IPROC_REGCTL2 %d\n", ret); + return; + } + + val &= ~IPROC_ADC_CONTROLLER_EN; + ret = regmap_write(adc_priv->regmap, IPROC_REGCTL2, val); + if (ret) { + dev_err(&indio_dev->dev, + "failed to write IPROC_REGCTL2 %d\n", ret); + return; + } +} + +static int iproc_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + u16 adc_data; + int err; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + err = iproc_adc_do_read(indio_dev, chan->channel, &adc_data); + if (err < 0) + return err; + *val = adc_data; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + *val = 1800; + *val2 = 10; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static const struct iio_info iproc_adc_iio_info = { + .read_raw = &iproc_adc_read_raw, +}; + +#define IPROC_ADC_CHANNEL(_index, _id) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = _id, \ +} + +static const struct iio_chan_spec iproc_adc_iio_channels[] = { + IPROC_ADC_CHANNEL(0, "adc0"), + IPROC_ADC_CHANNEL(1, "adc1"), + IPROC_ADC_CHANNEL(2, "adc2"), + IPROC_ADC_CHANNEL(3, "adc3"), + IPROC_ADC_CHANNEL(4, "adc4"), + IPROC_ADC_CHANNEL(5, "adc5"), + IPROC_ADC_CHANNEL(6, "adc6"), + IPROC_ADC_CHANNEL(7, "adc7"), +}; + +static int iproc_adc_probe(struct platform_device *pdev) +{ + struct iproc_adc_priv *adc_priv; + struct iio_dev *indio_dev = NULL; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, + sizeof(*adc_priv)); + if (!indio_dev) + return -ENOMEM; + + adc_priv = iio_priv(indio_dev); + platform_set_drvdata(pdev, indio_dev); + + mutex_init(&adc_priv->mutex); + + init_completion(&adc_priv->completion); + + adc_priv->regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "adc-syscon"); + if (IS_ERR(adc_priv->regmap)) { + dev_err(&pdev->dev, "failed to get handle for tsc syscon\n"); + ret = PTR_ERR(adc_priv->regmap); + return ret; + } + + adc_priv->adc_clk = devm_clk_get(&pdev->dev, "tsc_clk"); + if (IS_ERR(adc_priv->adc_clk)) { + dev_err(&pdev->dev, + "failed getting clock tsc_clk\n"); + ret = PTR_ERR(adc_priv->adc_clk); + return ret; + } + + adc_priv->irqno = platform_get_irq(pdev, 0); + if (adc_priv->irqno < 0) + return adc_priv->irqno; + + ret = regmap_clear_bits(adc_priv->regmap, IPROC_REGCTL2, + IPROC_ADC_AUXIN_SCAN_ENA); + if (ret) { + dev_err(&pdev->dev, "failed to write IPROC_REGCTL2 %d\n", ret); + return ret; + } + + ret = devm_request_threaded_irq(&pdev->dev, adc_priv->irqno, + iproc_adc_interrupt_handler, + iproc_adc_interrupt_thread, + IRQF_SHARED, "iproc-adc", indio_dev); + if (ret) { + dev_err(&pdev->dev, "request_irq error %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(adc_priv->adc_clk); + if (ret) { + dev_err(&pdev->dev, + "clk_prepare_enable failed %d\n", ret); + return ret; + } + + ret = iproc_adc_enable(indio_dev); + if (ret) { + dev_err(&pdev->dev, "failed to enable adc %d\n", ret); + goto err_adc_enable; + } + + indio_dev->name = "iproc-static-adc"; + indio_dev->info = &iproc_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = iproc_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(iproc_adc_iio_channels); + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "iio_device_register failed:err %d\n", ret); + goto err_clk; + } + + return 0; + +err_clk: + iproc_adc_disable(indio_dev); +err_adc_enable: + clk_disable_unprepare(adc_priv->adc_clk); + + return ret; +} + +static void iproc_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct iproc_adc_priv *adc_priv = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iproc_adc_disable(indio_dev); + clk_disable_unprepare(adc_priv->adc_clk); +} + +static const struct of_device_id iproc_adc_of_match[] = { + {.compatible = "brcm,iproc-static-adc", }, + { } +}; +MODULE_DEVICE_TABLE(of, iproc_adc_of_match); + +static struct platform_driver iproc_adc_driver = { + .probe = iproc_adc_probe, + .remove = iproc_adc_remove, + .driver = { + .name = "iproc-static-adc", + .of_match_table = iproc_adc_of_match, + }, +}; +module_platform_driver(iproc_adc_driver); + +MODULE_DESCRIPTION("Broadcom iProc ADC controller driver"); +MODULE_AUTHOR("Raveendra Padasalagi "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/berlin2-adc.c b/drivers/iio/adc/berlin2-adc.c new file mode 100644 index 000000000..a67edf0bd --- /dev/null +++ b/drivers/iio/adc/berlin2-adc.c @@ -0,0 +1,368 @@ +/* + * Marvell Berlin2 ADC driver + * + * Copyright (C) 2015 Marvell Technology Group Ltd. + * + * Antoine Tenart + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BERLIN2_SM_CTRL 0x14 +#define BERLIN2_SM_CTRL_SM_SOC_INT BIT(1) +#define BERLIN2_SM_CTRL_SOC_SM_INT BIT(2) +#define BERLIN2_SM_CTRL_ADC_SEL(x) ((x) << 5) /* 0-15 */ +#define BERLIN2_SM_CTRL_ADC_SEL_MASK GENMASK(8, 5) +#define BERLIN2_SM_CTRL_ADC_POWER BIT(9) +#define BERLIN2_SM_CTRL_ADC_CLKSEL_DIV2 (0x0 << 10) +#define BERLIN2_SM_CTRL_ADC_CLKSEL_DIV3 (0x1 << 10) +#define BERLIN2_SM_CTRL_ADC_CLKSEL_DIV4 (0x2 << 10) +#define BERLIN2_SM_CTRL_ADC_CLKSEL_DIV8 (0x3 << 10) +#define BERLIN2_SM_CTRL_ADC_CLKSEL_MASK GENMASK(11, 10) +#define BERLIN2_SM_CTRL_ADC_START BIT(12) +#define BERLIN2_SM_CTRL_ADC_RESET BIT(13) +#define BERLIN2_SM_CTRL_ADC_BANDGAP_RDY BIT(14) +#define BERLIN2_SM_CTRL_ADC_CONT_SINGLE (0x0 << 15) +#define BERLIN2_SM_CTRL_ADC_CONT_CONTINUOUS (0x1 << 15) +#define BERLIN2_SM_CTRL_ADC_BUFFER_EN BIT(16) +#define BERLIN2_SM_CTRL_ADC_VREF_EXT (0x0 << 17) +#define BERLIN2_SM_CTRL_ADC_VREF_INT (0x1 << 17) +#define BERLIN2_SM_CTRL_ADC_ROTATE BIT(19) +#define BERLIN2_SM_CTRL_TSEN_EN BIT(20) +#define BERLIN2_SM_CTRL_TSEN_CLK_SEL_125 (0x0 << 21) /* 1.25 MHz */ +#define BERLIN2_SM_CTRL_TSEN_CLK_SEL_250 (0x1 << 21) /* 2.5 MHz */ +#define BERLIN2_SM_CTRL_TSEN_MODE_0_125 (0x0 << 22) /* 0-125 C */ +#define BERLIN2_SM_CTRL_TSEN_MODE_10_50 (0x1 << 22) /* 10-50 C */ +#define BERLIN2_SM_CTRL_TSEN_RESET BIT(29) +#define BERLIN2_SM_ADC_DATA 0x20 +#define BERLIN2_SM_ADC_MASK GENMASK(9, 0) +#define BERLIN2_SM_ADC_STATUS 0x1c +#define BERLIN2_SM_ADC_STATUS_DATA_RDY(x) BIT(x) /* 0-15 */ +#define BERLIN2_SM_ADC_STATUS_DATA_RDY_MASK GENMASK(15, 0) +#define BERLIN2_SM_ADC_STATUS_INT_EN(x) (BIT(x) << 16) /* 0-15 */ +#define BERLIN2_SM_ADC_STATUS_INT_EN_MASK GENMASK(31, 16) +#define BERLIN2_SM_TSEN_STATUS 0x24 +#define BERLIN2_SM_TSEN_STATUS_DATA_RDY BIT(0) +#define BERLIN2_SM_TSEN_STATUS_INT_EN BIT(1) +#define BERLIN2_SM_TSEN_DATA 0x28 +#define BERLIN2_SM_TSEN_MASK GENMASK(9, 0) +#define BERLIN2_SM_TSEN_CTRL 0x74 +#define BERLIN2_SM_TSEN_CTRL_START BIT(8) +#define BERLIN2_SM_TSEN_CTRL_SETTLING_4 (0x0 << 21) /* 4 us */ +#define BERLIN2_SM_TSEN_CTRL_SETTLING_12 (0x1 << 21) /* 12 us */ +#define BERLIN2_SM_TSEN_CTRL_SETTLING_MASK BIT(21) +#define BERLIN2_SM_TSEN_CTRL_TRIM(x) ((x) << 22) +#define BERLIN2_SM_TSEN_CTRL_TRIM_MASK GENMASK(25, 22) + +struct berlin2_adc_priv { + struct regmap *regmap; + struct mutex lock; + wait_queue_head_t wq; + bool data_available; + int data; +}; + +#define BERLIN2_ADC_CHANNEL(n, t) \ + { \ + .channel = n, \ + .datasheet_name = "channel"#n, \ + .type = t, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + } + +static const struct iio_chan_spec berlin2_adc_channels[] = { + BERLIN2_ADC_CHANNEL(0, IIO_VOLTAGE), /* external input */ + BERLIN2_ADC_CHANNEL(1, IIO_VOLTAGE), /* external input */ + BERLIN2_ADC_CHANNEL(2, IIO_VOLTAGE), /* external input */ + BERLIN2_ADC_CHANNEL(3, IIO_VOLTAGE), /* external input */ + BERLIN2_ADC_CHANNEL(4, IIO_VOLTAGE), /* reserved */ + BERLIN2_ADC_CHANNEL(5, IIO_VOLTAGE), /* reserved */ + { /* temperature sensor */ + .channel = 6, + .datasheet_name = "channel6", + .type = IIO_TEMP, + .indexed = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, + BERLIN2_ADC_CHANNEL(7, IIO_VOLTAGE), /* reserved */ + IIO_CHAN_SOFT_TIMESTAMP(8), /* timestamp */ +}; + +static int berlin2_adc_read(struct iio_dev *indio_dev, int channel) +{ + struct berlin2_adc_priv *priv = iio_priv(indio_dev); + int data, ret; + + mutex_lock(&priv->lock); + + /* Enable the interrupts */ + regmap_write(priv->regmap, BERLIN2_SM_ADC_STATUS, + BERLIN2_SM_ADC_STATUS_INT_EN(channel)); + + /* Configure the ADC */ + regmap_update_bits(priv->regmap, BERLIN2_SM_CTRL, + BERLIN2_SM_CTRL_ADC_RESET | + BERLIN2_SM_CTRL_ADC_SEL_MASK | + BERLIN2_SM_CTRL_ADC_START, + BERLIN2_SM_CTRL_ADC_SEL(channel) | + BERLIN2_SM_CTRL_ADC_START); + + ret = wait_event_interruptible_timeout(priv->wq, priv->data_available, + msecs_to_jiffies(1000)); + + /* Disable the interrupts */ + regmap_clear_bits(priv->regmap, BERLIN2_SM_ADC_STATUS, + BERLIN2_SM_ADC_STATUS_INT_EN(channel)); + + if (ret == 0) + ret = -ETIMEDOUT; + if (ret < 0) { + mutex_unlock(&priv->lock); + return ret; + } + + regmap_clear_bits(priv->regmap, BERLIN2_SM_CTRL, + BERLIN2_SM_CTRL_ADC_START); + + data = priv->data; + priv->data_available = false; + + mutex_unlock(&priv->lock); + + return data; +} + +static int berlin2_adc_tsen_read(struct iio_dev *indio_dev) +{ + struct berlin2_adc_priv *priv = iio_priv(indio_dev); + int data, ret; + + mutex_lock(&priv->lock); + + /* Enable interrupts */ + regmap_write(priv->regmap, BERLIN2_SM_TSEN_STATUS, + BERLIN2_SM_TSEN_STATUS_INT_EN); + + /* Configure the ADC */ + regmap_update_bits(priv->regmap, BERLIN2_SM_CTRL, + BERLIN2_SM_CTRL_TSEN_RESET | + BERLIN2_SM_CTRL_ADC_ROTATE, + BERLIN2_SM_CTRL_ADC_ROTATE); + + /* Configure the temperature sensor */ + regmap_update_bits(priv->regmap, BERLIN2_SM_TSEN_CTRL, + BERLIN2_SM_TSEN_CTRL_TRIM_MASK | + BERLIN2_SM_TSEN_CTRL_SETTLING_MASK | + BERLIN2_SM_TSEN_CTRL_START, + BERLIN2_SM_TSEN_CTRL_TRIM(3) | + BERLIN2_SM_TSEN_CTRL_SETTLING_12 | + BERLIN2_SM_TSEN_CTRL_START); + + ret = wait_event_interruptible_timeout(priv->wq, priv->data_available, + msecs_to_jiffies(1000)); + + /* Disable interrupts */ + regmap_clear_bits(priv->regmap, BERLIN2_SM_TSEN_STATUS, + BERLIN2_SM_TSEN_STATUS_INT_EN); + + if (ret == 0) + ret = -ETIMEDOUT; + if (ret < 0) { + mutex_unlock(&priv->lock); + return ret; + } + + regmap_clear_bits(priv->regmap, BERLIN2_SM_TSEN_CTRL, + BERLIN2_SM_TSEN_CTRL_START); + + data = priv->data; + priv->data_available = false; + + mutex_unlock(&priv->lock); + + return data; +} + +static int berlin2_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int temp; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type != IIO_VOLTAGE) + return -EINVAL; + + *val = berlin2_adc_read(indio_dev, chan->channel); + if (*val < 0) + return *val; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_PROCESSED: + if (chan->type != IIO_TEMP) + return -EINVAL; + + temp = berlin2_adc_tsen_read(indio_dev); + if (temp < 0) + return temp; + + if (temp > 2047) + temp -= 4096; + + /* Convert to milli Celsius */ + *val = ((temp * 100000) / 264 - 270000); + return IIO_VAL_INT; + default: + break; + } + + return -EINVAL; +} + +static irqreturn_t berlin2_adc_irq(int irq, void *private) +{ + struct berlin2_adc_priv *priv = iio_priv(private); + unsigned val; + + regmap_read(priv->regmap, BERLIN2_SM_ADC_STATUS, &val); + if (val & BERLIN2_SM_ADC_STATUS_DATA_RDY_MASK) { + regmap_read(priv->regmap, BERLIN2_SM_ADC_DATA, &priv->data); + priv->data &= BERLIN2_SM_ADC_MASK; + + val &= ~BERLIN2_SM_ADC_STATUS_DATA_RDY_MASK; + regmap_write(priv->regmap, BERLIN2_SM_ADC_STATUS, val); + + priv->data_available = true; + wake_up_interruptible(&priv->wq); + } + + return IRQ_HANDLED; +} + +static irqreturn_t berlin2_adc_tsen_irq(int irq, void *private) +{ + struct berlin2_adc_priv *priv = iio_priv(private); + unsigned val; + + regmap_read(priv->regmap, BERLIN2_SM_TSEN_STATUS, &val); + if (val & BERLIN2_SM_TSEN_STATUS_DATA_RDY) { + regmap_read(priv->regmap, BERLIN2_SM_TSEN_DATA, &priv->data); + priv->data &= BERLIN2_SM_TSEN_MASK; + + val &= ~BERLIN2_SM_TSEN_STATUS_DATA_RDY; + regmap_write(priv->regmap, BERLIN2_SM_TSEN_STATUS, val); + + priv->data_available = true; + wake_up_interruptible(&priv->wq); + } + + return IRQ_HANDLED; +} + +static const struct iio_info berlin2_adc_info = { + .read_raw = berlin2_adc_read_raw, +}; + +static void berlin2_adc_powerdown(void *regmap) +{ + regmap_clear_bits(regmap, BERLIN2_SM_CTRL, BERLIN2_SM_CTRL_ADC_POWER); + +} + +static int berlin2_adc_probe(struct platform_device *pdev) +{ + struct iio_dev *indio_dev; + struct berlin2_adc_priv *priv; + struct device_node *parent_np = of_get_parent(pdev->dev.of_node); + int irq, tsen_irq; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*priv)); + if (!indio_dev) { + of_node_put(parent_np); + return -ENOMEM; + } + + priv = iio_priv(indio_dev); + + priv->regmap = syscon_node_to_regmap(parent_np); + of_node_put(parent_np); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + irq = platform_get_irq_byname(pdev, "adc"); + if (irq < 0) + return irq; + + tsen_irq = platform_get_irq_byname(pdev, "tsen"); + if (tsen_irq < 0) + return tsen_irq; + + ret = devm_request_irq(&pdev->dev, irq, berlin2_adc_irq, 0, + pdev->dev.driver->name, indio_dev); + if (ret) + return ret; + + ret = devm_request_irq(&pdev->dev, tsen_irq, berlin2_adc_tsen_irq, + 0, pdev->dev.driver->name, indio_dev); + if (ret) + return ret; + + init_waitqueue_head(&priv->wq); + mutex_init(&priv->lock); + + indio_dev->name = dev_name(&pdev->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &berlin2_adc_info; + + indio_dev->channels = berlin2_adc_channels; + indio_dev->num_channels = ARRAY_SIZE(berlin2_adc_channels); + + /* Power up the ADC */ + regmap_set_bits(priv->regmap, BERLIN2_SM_CTRL, + BERLIN2_SM_CTRL_ADC_POWER); + + ret = devm_add_action_or_reset(&pdev->dev, berlin2_adc_powerdown, + priv->regmap); + if (ret) + return ret; + + return devm_iio_device_register(&pdev->dev, indio_dev); +} + +static const struct of_device_id berlin2_adc_match[] = { + { .compatible = "marvell,berlin2-adc", }, + { } +}; +MODULE_DEVICE_TABLE(of, berlin2_adc_match); + +static struct platform_driver berlin2_adc_driver = { + .driver = { + .name = "berlin2-adc", + .of_match_table = berlin2_adc_match, + }, + .probe = berlin2_adc_probe, +}; +module_platform_driver(berlin2_adc_driver); + +MODULE_AUTHOR("Antoine Tenart "); +MODULE_DESCRIPTION("Marvell Berlin2 ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/cc10001_adc.c b/drivers/iio/adc/cc10001_adc.c new file mode 100644 index 000000000..d42b74732 --- /dev/null +++ b/drivers/iio/adc/cc10001_adc.c @@ -0,0 +1,409 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014-2015 Imagination Technologies Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* Registers */ +#define CC10001_ADC_CONFIG 0x00 +#define CC10001_ADC_START_CONV BIT(4) +#define CC10001_ADC_MODE_SINGLE_CONV BIT(5) + +#define CC10001_ADC_DDATA_OUT 0x04 +#define CC10001_ADC_EOC 0x08 +#define CC10001_ADC_EOC_SET BIT(0) + +#define CC10001_ADC_CHSEL_SAMPLED 0x0c +#define CC10001_ADC_POWER_DOWN 0x10 +#define CC10001_ADC_POWER_DOWN_SET BIT(0) + +#define CC10001_ADC_DEBUG 0x14 +#define CC10001_ADC_DATA_COUNT 0x20 + +#define CC10001_ADC_DATA_MASK GENMASK(9, 0) +#define CC10001_ADC_NUM_CHANNELS 8 +#define CC10001_ADC_CH_MASK GENMASK(2, 0) + +#define CC10001_INVALID_SAMPLED 0xffff +#define CC10001_MAX_POLL_COUNT 20 + +/* + * As per device specification, wait six clock cycles after power-up to + * activate START. Since adding two more clock cycles delay does not + * impact the performance too much, we are adding two additional cycles delay + * intentionally here. + */ +#define CC10001_WAIT_CYCLES 8 + +struct cc10001_adc_device { + void __iomem *reg_base; + struct clk *adc_clk; + struct regulator *reg; + u16 *buf; + + bool shared; + struct mutex lock; + unsigned int start_delay_ns; + unsigned int eoc_delay_ns; +}; + +static inline void cc10001_adc_write_reg(struct cc10001_adc_device *adc_dev, + u32 reg, u32 val) +{ + writel(val, adc_dev->reg_base + reg); +} + +static inline u32 cc10001_adc_read_reg(struct cc10001_adc_device *adc_dev, + u32 reg) +{ + return readl(adc_dev->reg_base + reg); +} + +static void cc10001_adc_power_up(struct cc10001_adc_device *adc_dev) +{ + cc10001_adc_write_reg(adc_dev, CC10001_ADC_POWER_DOWN, 0); + ndelay(adc_dev->start_delay_ns); +} + +static void cc10001_adc_power_down(struct cc10001_adc_device *adc_dev) +{ + cc10001_adc_write_reg(adc_dev, CC10001_ADC_POWER_DOWN, + CC10001_ADC_POWER_DOWN_SET); +} + +static void cc10001_adc_start(struct cc10001_adc_device *adc_dev, + unsigned int channel) +{ + u32 val; + + /* Channel selection and mode of operation */ + val = (channel & CC10001_ADC_CH_MASK) | CC10001_ADC_MODE_SINGLE_CONV; + cc10001_adc_write_reg(adc_dev, CC10001_ADC_CONFIG, val); + + udelay(1); + val = cc10001_adc_read_reg(adc_dev, CC10001_ADC_CONFIG); + val = val | CC10001_ADC_START_CONV; + cc10001_adc_write_reg(adc_dev, CC10001_ADC_CONFIG, val); +} + +static u16 cc10001_adc_poll_done(struct iio_dev *indio_dev, + unsigned int channel, + unsigned int delay) +{ + struct cc10001_adc_device *adc_dev = iio_priv(indio_dev); + unsigned int poll_count = 0; + + while (!(cc10001_adc_read_reg(adc_dev, CC10001_ADC_EOC) & + CC10001_ADC_EOC_SET)) { + + ndelay(delay); + if (poll_count++ == CC10001_MAX_POLL_COUNT) + return CC10001_INVALID_SAMPLED; + } + + poll_count = 0; + while ((cc10001_adc_read_reg(adc_dev, CC10001_ADC_CHSEL_SAMPLED) & + CC10001_ADC_CH_MASK) != channel) { + + ndelay(delay); + if (poll_count++ == CC10001_MAX_POLL_COUNT) + return CC10001_INVALID_SAMPLED; + } + + /* Read the 10 bit output register */ + return cc10001_adc_read_reg(adc_dev, CC10001_ADC_DDATA_OUT) & + CC10001_ADC_DATA_MASK; +} + +static irqreturn_t cc10001_adc_trigger_h(int irq, void *p) +{ + struct cc10001_adc_device *adc_dev; + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev; + unsigned int delay_ns; + unsigned int channel; + unsigned int scan_idx; + bool sample_invalid; + u16 *data; + int i; + + indio_dev = pf->indio_dev; + adc_dev = iio_priv(indio_dev); + data = adc_dev->buf; + + mutex_lock(&adc_dev->lock); + + if (!adc_dev->shared) + cc10001_adc_power_up(adc_dev); + + /* Calculate delay step for eoc and sampled data */ + delay_ns = adc_dev->eoc_delay_ns / CC10001_MAX_POLL_COUNT; + + i = 0; + sample_invalid = false; + iio_for_each_active_channel(indio_dev, scan_idx) { + channel = indio_dev->channels[scan_idx].channel; + cc10001_adc_start(adc_dev, channel); + + data[i] = cc10001_adc_poll_done(indio_dev, channel, delay_ns); + if (data[i] == CC10001_INVALID_SAMPLED) { + dev_warn(&indio_dev->dev, + "invalid sample on channel %d\n", channel); + sample_invalid = true; + goto done; + } + i++; + } + +done: + if (!adc_dev->shared) + cc10001_adc_power_down(adc_dev); + + mutex_unlock(&adc_dev->lock); + + if (!sample_invalid) + iio_push_to_buffers_with_timestamp(indio_dev, data, + iio_get_time_ns(indio_dev)); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static u16 cc10001_adc_read_raw_voltage(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + struct cc10001_adc_device *adc_dev = iio_priv(indio_dev); + unsigned int delay_ns; + u16 val; + + if (!adc_dev->shared) + cc10001_adc_power_up(adc_dev); + + /* Calculate delay step for eoc and sampled data */ + delay_ns = adc_dev->eoc_delay_ns / CC10001_MAX_POLL_COUNT; + + cc10001_adc_start(adc_dev, chan->channel); + + val = cc10001_adc_poll_done(indio_dev, chan->channel, delay_ns); + + if (!adc_dev->shared) + cc10001_adc_power_down(adc_dev); + + return val; +} + +static int cc10001_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cc10001_adc_device *adc_dev = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + mutex_lock(&adc_dev->lock); + *val = cc10001_adc_read_raw_voltage(indio_dev, chan); + mutex_unlock(&adc_dev->lock); + + if (*val == CC10001_INVALID_SAMPLED) + return -EIO; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + ret = regulator_get_voltage(adc_dev->reg); + if (ret < 0) + return ret; + + *val = ret / 1000; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } +} + +static int cc10001_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct cc10001_adc_device *adc_dev = iio_priv(indio_dev); + + kfree(adc_dev->buf); + adc_dev->buf = kmalloc(indio_dev->scan_bytes, GFP_KERNEL); + if (!adc_dev->buf) + return -ENOMEM; + + return 0; +} + +static const struct iio_info cc10001_adc_info = { + .read_raw = &cc10001_adc_read_raw, + .update_scan_mode = &cc10001_update_scan_mode, +}; + +static int cc10001_adc_channel_init(struct iio_dev *indio_dev, + unsigned long channel_map) +{ + struct iio_chan_spec *chan_array; + unsigned int bit, idx = 0; + + indio_dev->num_channels = bitmap_weight(&channel_map, + CC10001_ADC_NUM_CHANNELS) + 1; + + chan_array = devm_kcalloc(&indio_dev->dev, indio_dev->num_channels, + sizeof(struct iio_chan_spec), + GFP_KERNEL); + if (!chan_array) + return -ENOMEM; + + for_each_set_bit(bit, &channel_map, CC10001_ADC_NUM_CHANNELS) { + struct iio_chan_spec *chan = &chan_array[idx]; + + chan->type = IIO_VOLTAGE; + chan->indexed = 1; + chan->channel = bit; + chan->scan_index = idx; + chan->scan_type.sign = 'u'; + chan->scan_type.realbits = 10; + chan->scan_type.storagebits = 16; + chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE); + chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); + idx++; + } + + chan_array[idx] = IIO_CHAN_SOFT_TIMESTAMP(idx); + + indio_dev->channels = chan_array; + + return 0; +} + +static void cc10001_reg_disable(void *priv) +{ + regulator_disable(priv); +} + +static void cc10001_pd_cb(void *priv) +{ + cc10001_adc_power_down(priv); +} + +static int cc10001_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct cc10001_adc_device *adc_dev; + unsigned long adc_clk_rate; + struct iio_dev *indio_dev; + unsigned long channel_map; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*adc_dev)); + if (indio_dev == NULL) + return -ENOMEM; + + adc_dev = iio_priv(indio_dev); + + channel_map = GENMASK(CC10001_ADC_NUM_CHANNELS - 1, 0); + if (!of_property_read_u32(node, "adc-reserved-channels", &ret)) { + adc_dev->shared = true; + channel_map &= ~ret; + } + + adc_dev->reg = devm_regulator_get(dev, "vref"); + if (IS_ERR(adc_dev->reg)) + return PTR_ERR(adc_dev->reg); + + ret = regulator_enable(adc_dev->reg); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, cc10001_reg_disable, adc_dev->reg); + if (ret) + return ret; + + indio_dev->name = dev_name(dev); + indio_dev->info = &cc10001_adc_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + adc_dev->reg_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(adc_dev->reg_base)) + return PTR_ERR(adc_dev->reg_base); + + adc_dev->adc_clk = devm_clk_get_enabled(dev, "adc"); + if (IS_ERR(adc_dev->adc_clk)) { + dev_err(dev, "failed to get/enable the clock\n"); + return PTR_ERR(adc_dev->adc_clk); + } + + adc_clk_rate = clk_get_rate(adc_dev->adc_clk); + if (!adc_clk_rate) { + dev_err(dev, "null clock rate!\n"); + return -EINVAL; + } + + adc_dev->eoc_delay_ns = NSEC_PER_SEC / adc_clk_rate; + adc_dev->start_delay_ns = adc_dev->eoc_delay_ns * CC10001_WAIT_CYCLES; + + /* + * There is only one register to power-up/power-down the AUX ADC. + * If the ADC is shared among multiple CPUs, always power it up here. + * If the ADC is used only by the MIPS, power-up/power-down at runtime. + */ + if (adc_dev->shared) + cc10001_adc_power_up(adc_dev); + + ret = devm_add_action_or_reset(dev, cc10001_pd_cb, adc_dev); + if (ret) + return ret; + /* Setup the ADC channels available on the device */ + ret = cc10001_adc_channel_init(indio_dev, channel_map); + if (ret < 0) + return ret; + + mutex_init(&adc_dev->lock); + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + &cc10001_adc_trigger_h, NULL); + if (ret < 0) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id cc10001_adc_dt_ids[] = { + { .compatible = "cosmic,10001-adc", }, + { } +}; +MODULE_DEVICE_TABLE(of, cc10001_adc_dt_ids); + +static struct platform_driver cc10001_adc_driver = { + .driver = { + .name = "cc10001-adc", + .of_match_table = cc10001_adc_dt_ids, + }, + .probe = cc10001_adc_probe, +}; +module_platform_driver(cc10001_adc_driver); + +MODULE_AUTHOR("Phani Movva "); +MODULE_DESCRIPTION("Cosmic Circuits ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/cpcap-adc.c b/drivers/iio/adc/cpcap-adc.c new file mode 100644 index 000000000..223e2737c --- /dev/null +++ b/drivers/iio/adc/cpcap-adc.c @@ -0,0 +1,1035 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2017 Tony Lindgren + * + * Rewritten for Linux IIO framework with some code based on + * earlier driver found in the Motorola Linux kernel: + * + * Copyright (C) 2009-2010 Motorola, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* Register CPCAP_REG_ADCC1 bits */ +#define CPCAP_BIT_ADEN_AUTO_CLR BIT(15) /* Currently unused */ +#define CPCAP_BIT_CAL_MODE BIT(14) /* Set with BIT_RAND0 */ +#define CPCAP_BIT_ADC_CLK_SEL1 BIT(13) /* Currently unused */ +#define CPCAP_BIT_ADC_CLK_SEL0 BIT(12) /* Currently unused */ +#define CPCAP_BIT_ATOX BIT(11) +#define CPCAP_BIT_ATO3 BIT(10) +#define CPCAP_BIT_ATO2 BIT(9) +#define CPCAP_BIT_ATO1 BIT(8) +#define CPCAP_BIT_ATO0 BIT(7) +#define CPCAP_BIT_ADA2 BIT(6) +#define CPCAP_BIT_ADA1 BIT(5) +#define CPCAP_BIT_ADA0 BIT(4) +#define CPCAP_BIT_AD_SEL1 BIT(3) /* Set for bank1 */ +#define CPCAP_BIT_RAND1 BIT(2) /* Set for channel 16 & 17 */ +#define CPCAP_BIT_RAND0 BIT(1) /* Set with CAL_MODE */ +#define CPCAP_BIT_ADEN BIT(0) /* Currently unused */ + +#define CPCAP_REG_ADCC1_DEFAULTS (CPCAP_BIT_ADEN_AUTO_CLR | \ + CPCAP_BIT_ADC_CLK_SEL0 | \ + CPCAP_BIT_RAND1) + +/* Register CPCAP_REG_ADCC2 bits */ +#define CPCAP_BIT_CAL_FACTOR_ENABLE BIT(15) /* Currently unused */ +#define CPCAP_BIT_BATDETB_EN BIT(14) /* Currently unused */ +#define CPCAP_BIT_ADTRIG_ONESHOT BIT(13) /* Set for !TIMING_IMM */ +#define CPCAP_BIT_ASC BIT(12) /* Set for TIMING_IMM */ +#define CPCAP_BIT_ATOX_PS_FACTOR BIT(11) +#define CPCAP_BIT_ADC_PS_FACTOR1 BIT(10) +#define CPCAP_BIT_ADC_PS_FACTOR0 BIT(9) +#define CPCAP_BIT_AD4_SELECT BIT(8) /* Currently unused */ +#define CPCAP_BIT_ADC_BUSY BIT(7) /* Currently unused */ +#define CPCAP_BIT_THERMBIAS_EN BIT(6) /* Bias for AD0_BATTDETB */ +#define CPCAP_BIT_ADTRIG_DIS BIT(5) /* Disable interrupt */ +#define CPCAP_BIT_LIADC BIT(4) /* Currently unused */ +#define CPCAP_BIT_TS_REFEN BIT(3) /* Currently unused */ +#define CPCAP_BIT_TS_M2 BIT(2) /* Currently unused */ +#define CPCAP_BIT_TS_M1 BIT(1) /* Currently unused */ +#define CPCAP_BIT_TS_M0 BIT(0) /* Currently unused */ + +#define CPCAP_REG_ADCC2_DEFAULTS (CPCAP_BIT_AD4_SELECT | \ + CPCAP_BIT_ADTRIG_DIS | \ + CPCAP_BIT_LIADC | \ + CPCAP_BIT_TS_M2 | \ + CPCAP_BIT_TS_M1) + +#define CPCAP_MAX_TEMP_LVL 27 +#define CPCAP_FOUR_POINT_TWO_ADC 801 +#define ST_ADC_CAL_CHRGI_HIGH_THRESHOLD 530 +#define ST_ADC_CAL_CHRGI_LOW_THRESHOLD 494 +#define ST_ADC_CAL_BATTI_HIGH_THRESHOLD 530 +#define ST_ADC_CAL_BATTI_LOW_THRESHOLD 494 +#define ST_ADC_CALIBRATE_DIFF_THRESHOLD 3 + +#define CPCAP_ADC_MAX_RETRIES 5 /* Calibration */ + +/* + * struct cpcap_adc_ato - timing settings for cpcap adc + * + * Unfortunately no cpcap documentation available, please document when + * using these. + */ +struct cpcap_adc_ato { + unsigned short ato_in; + unsigned short atox_in; + unsigned short adc_ps_factor_in; + unsigned short atox_ps_factor_in; + unsigned short ato_out; + unsigned short atox_out; + unsigned short adc_ps_factor_out; + unsigned short atox_ps_factor_out; +}; + +/** + * struct cpcap_adc - cpcap adc device driver data + * @reg: cpcap regmap + * @dev: struct device + * @vendor: cpcap vendor + * @irq: interrupt + * @lock: mutex + * @ato: request timings + * @wq_data_avail: work queue + * @done: work done + */ +struct cpcap_adc { + struct regmap *reg; + struct device *dev; + u16 vendor; + int irq; + struct mutex lock; /* ADC register access lock */ + const struct cpcap_adc_ato *ato; + wait_queue_head_t wq_data_avail; + bool done; +}; + +/* + * enum cpcap_adc_channel - cpcap adc channels + */ +enum cpcap_adc_channel { + /* Bank0 channels */ + CPCAP_ADC_AD0, /* Battery temperature */ + CPCAP_ADC_BATTP, /* Battery voltage */ + CPCAP_ADC_VBUS, /* USB VBUS voltage */ + CPCAP_ADC_AD3, /* Die temperature when charging */ + CPCAP_ADC_BPLUS_AD4, /* Another battery or system voltage */ + CPCAP_ADC_CHG_ISENSE, /* Calibrated charge current */ + CPCAP_ADC_BATTI, /* Calibrated system current */ + CPCAP_ADC_USB_ID, /* USB OTG ID, unused on droid 4? */ + + /* Bank1 channels */ + CPCAP_ADC_AD8, /* Seems unused */ + CPCAP_ADC_AD9, /* Seems unused */ + CPCAP_ADC_LICELL, /* Maybe system voltage? Always 3V */ + CPCAP_ADC_HV_BATTP, /* Another battery detection? */ + CPCAP_ADC_TSX1_AD12, /* Seems unused, for touchscreen? */ + CPCAP_ADC_TSX2_AD13, /* Seems unused, for touchscreen? */ + CPCAP_ADC_TSY1_AD14, /* Seems unused, for touchscreen? */ + CPCAP_ADC_TSY2_AD15, /* Seems unused, for touchscreen? */ + + /* Remuxed channels using bank0 entries */ + CPCAP_ADC_BATTP_PI16, /* Alternative mux mode for BATTP */ + CPCAP_ADC_BATTI_PI17, /* Alternative mux mode for BATTI */ + + CPCAP_ADC_CHANNEL_NUM, +}; + +/* + * enum cpcap_adc_timing - cpcap adc timing options + * + * CPCAP_ADC_TIMING_IMM seems to be immediate with no timings. + * Please document when using. + */ +enum cpcap_adc_timing { + CPCAP_ADC_TIMING_IMM, + CPCAP_ADC_TIMING_IN, + CPCAP_ADC_TIMING_OUT, +}; + +/** + * struct cpcap_adc_phasing_tbl - cpcap phasing table + * @offset: offset in the phasing table + * @multiplier: multiplier in the phasing table + * @divider: divider in the phasing table + * @min: minimum value + * @max: maximum value + */ +struct cpcap_adc_phasing_tbl { + short offset; + unsigned short multiplier; + unsigned short divider; + short min; + short max; +}; + +/** + * struct cpcap_adc_conversion_tbl - cpcap conversion table + * @conv_type: conversion type + * @align_offset: align offset + * @conv_offset: conversion offset + * @cal_offset: calibration offset + * @multiplier: conversion multiplier + * @divider: conversion divider + */ +struct cpcap_adc_conversion_tbl { + enum iio_chan_info_enum conv_type; + int align_offset; + int conv_offset; + int cal_offset; + int multiplier; + int divider; +}; + +/** + * struct cpcap_adc_request - cpcap adc request + * @channel: request channel + * @phase_tbl: channel phasing table + * @conv_tbl: channel conversion table + * @bank_index: channel index within the bank + * @timing: timing settings + * @result: result + */ +struct cpcap_adc_request { + int channel; + const struct cpcap_adc_phasing_tbl *phase_tbl; + const struct cpcap_adc_conversion_tbl *conv_tbl; + int bank_index; + enum cpcap_adc_timing timing; + int result; +}; + +/* Phasing table for channels. Note that channels 16 & 17 use BATTP and BATTI */ +static const struct cpcap_adc_phasing_tbl bank_phasing[] = { + /* Bank0 */ + [CPCAP_ADC_AD0] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_BATTP] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_VBUS] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_AD3] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_BPLUS_AD4] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_CHG_ISENSE] = {0, 0x80, 0x80, -512, 511}, + [CPCAP_ADC_BATTI] = {0, 0x80, 0x80, -512, 511}, + [CPCAP_ADC_USB_ID] = {0, 0x80, 0x80, 0, 1023}, + + /* Bank1 */ + [CPCAP_ADC_AD8] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_AD9] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_LICELL] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_HV_BATTP] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_TSX1_AD12] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_TSX2_AD13] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_TSY1_AD14] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_TSY2_AD15] = {0, 0x80, 0x80, 0, 1023}, +}; + +/* + * Conversion table for channels. Updated during init based on calibration. + * Here too channels 16 & 17 use BATTP and BATTI. + */ +static struct cpcap_adc_conversion_tbl bank_conversion[] = { + /* Bank0 */ + [CPCAP_ADC_AD0] = { + IIO_CHAN_INFO_PROCESSED, 0, 0, 0, 1, 1, + }, + [CPCAP_ADC_BATTP] = { + IIO_CHAN_INFO_PROCESSED, 0, 2400, 0, 2300, 1023, + }, + [CPCAP_ADC_VBUS] = { + IIO_CHAN_INFO_PROCESSED, 0, 0, 0, 10000, 1023, + }, + [CPCAP_ADC_AD3] = { + IIO_CHAN_INFO_PROCESSED, 0, 0, 0, 1, 1, + }, + [CPCAP_ADC_BPLUS_AD4] = { + IIO_CHAN_INFO_PROCESSED, 0, 2400, 0, 2300, 1023, + }, + [CPCAP_ADC_CHG_ISENSE] = { + IIO_CHAN_INFO_PROCESSED, -512, 2, 0, 5000, 1023, + }, + [CPCAP_ADC_BATTI] = { + IIO_CHAN_INFO_PROCESSED, -512, 2, 0, 5000, 1023, + }, + [CPCAP_ADC_USB_ID] = { + IIO_CHAN_INFO_RAW, 0, 0, 0, 1, 1, + }, + + /* Bank1 */ + [CPCAP_ADC_AD8] = { + IIO_CHAN_INFO_RAW, 0, 0, 0, 1, 1, + }, + [CPCAP_ADC_AD9] = { + IIO_CHAN_INFO_RAW, 0, 0, 0, 1, 1, + }, + [CPCAP_ADC_LICELL] = { + IIO_CHAN_INFO_PROCESSED, 0, 0, 0, 3400, 1023, + }, + [CPCAP_ADC_HV_BATTP] = { + IIO_CHAN_INFO_RAW, 0, 0, 0, 1, 1, + }, + [CPCAP_ADC_TSX1_AD12] = { + IIO_CHAN_INFO_RAW, 0, 0, 0, 1, 1, + }, + [CPCAP_ADC_TSX2_AD13] = { + IIO_CHAN_INFO_RAW, 0, 0, 0, 1, 1, + }, + [CPCAP_ADC_TSY1_AD14] = { + IIO_CHAN_INFO_RAW, 0, 0, 0, 1, 1, + }, + [CPCAP_ADC_TSY2_AD15] = { + IIO_CHAN_INFO_RAW, 0, 0, 0, 1, 1, + }, +}; + +/* + * Temperature lookup table of register values to milliCelcius. + * REVISIT: Check the duplicate 0x3ff entry in a freezer + */ +static const int temp_map[CPCAP_MAX_TEMP_LVL][2] = { + { 0x03ff, -40000 }, + { 0x03ff, -35000 }, + { 0x03ef, -30000 }, + { 0x03b2, -25000 }, + { 0x036c, -20000 }, + { 0x0320, -15000 }, + { 0x02d0, -10000 }, + { 0x027f, -5000 }, + { 0x022f, 0 }, + { 0x01e4, 5000 }, + { 0x019f, 10000 }, + { 0x0161, 15000 }, + { 0x012b, 20000 }, + { 0x00fc, 25000 }, + { 0x00d4, 30000 }, + { 0x00b2, 35000 }, + { 0x0095, 40000 }, + { 0x007d, 45000 }, + { 0x0069, 50000 }, + { 0x0059, 55000 }, + { 0x004b, 60000 }, + { 0x003f, 65000 }, + { 0x0036, 70000 }, + { 0x002e, 75000 }, + { 0x0027, 80000 }, + { 0x0022, 85000 }, + { 0x001d, 90000 }, +}; + +#define CPCAP_CHAN(_type, _index, _address, _datasheet_name) { \ + .type = (_type), \ + .address = (_address), \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_PROCESSED), \ + .scan_index = (_index), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 10, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ + .datasheet_name = (_datasheet_name), \ +} + +/* + * The datasheet names are from Motorola mapphone Linux kernel except + * for the last two which might be uncalibrated charge voltage and + * current. + */ +static const struct iio_chan_spec cpcap_adc_channels[] = { + /* Bank0 */ + CPCAP_CHAN(IIO_TEMP, 0, CPCAP_REG_ADCD0, "battdetb"), + CPCAP_CHAN(IIO_VOLTAGE, 1, CPCAP_REG_ADCD1, "battp"), + CPCAP_CHAN(IIO_VOLTAGE, 2, CPCAP_REG_ADCD2, "vbus"), + CPCAP_CHAN(IIO_TEMP, 3, CPCAP_REG_ADCD3, "ad3"), + CPCAP_CHAN(IIO_VOLTAGE, 4, CPCAP_REG_ADCD4, "ad4"), + CPCAP_CHAN(IIO_CURRENT, 5, CPCAP_REG_ADCD5, "chg_isense"), + CPCAP_CHAN(IIO_CURRENT, 6, CPCAP_REG_ADCD6, "batti"), + CPCAP_CHAN(IIO_VOLTAGE, 7, CPCAP_REG_ADCD7, "usb_id"), + + /* Bank1 */ + CPCAP_CHAN(IIO_CURRENT, 8, CPCAP_REG_ADCD0, "ad8"), + CPCAP_CHAN(IIO_VOLTAGE, 9, CPCAP_REG_ADCD1, "ad9"), + CPCAP_CHAN(IIO_VOLTAGE, 10, CPCAP_REG_ADCD2, "licell"), + CPCAP_CHAN(IIO_VOLTAGE, 11, CPCAP_REG_ADCD3, "hv_battp"), + CPCAP_CHAN(IIO_VOLTAGE, 12, CPCAP_REG_ADCD4, "tsx1_ad12"), + CPCAP_CHAN(IIO_VOLTAGE, 13, CPCAP_REG_ADCD5, "tsx2_ad13"), + CPCAP_CHAN(IIO_VOLTAGE, 14, CPCAP_REG_ADCD6, "tsy1_ad14"), + CPCAP_CHAN(IIO_VOLTAGE, 15, CPCAP_REG_ADCD7, "tsy2_ad15"), + + /* There are two registers with multiplexed functionality */ + CPCAP_CHAN(IIO_VOLTAGE, 16, CPCAP_REG_ADCD0, "chg_vsense"), + CPCAP_CHAN(IIO_CURRENT, 17, CPCAP_REG_ADCD1, "batti2"), +}; + +static irqreturn_t cpcap_adc_irq_thread(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct cpcap_adc *ddata = iio_priv(indio_dev); + int error; + + error = regmap_set_bits(ddata->reg, CPCAP_REG_ADCC2, + CPCAP_BIT_ADTRIG_DIS); + if (error) + return IRQ_NONE; + + ddata->done = true; + wake_up_interruptible(&ddata->wq_data_avail); + + return IRQ_HANDLED; +} + +/* ADC calibration functions */ +static void cpcap_adc_setup_calibrate(struct cpcap_adc *ddata, + enum cpcap_adc_channel chan) +{ + unsigned int value = 0; + unsigned long timeout = jiffies + msecs_to_jiffies(3000); + int error; + + if ((chan != CPCAP_ADC_CHG_ISENSE) && + (chan != CPCAP_ADC_BATTI)) + return; + + value |= CPCAP_BIT_CAL_MODE | CPCAP_BIT_RAND0; + value |= ((chan << 4) & + (CPCAP_BIT_ADA2 | CPCAP_BIT_ADA1 | CPCAP_BIT_ADA0)); + + error = regmap_update_bits(ddata->reg, CPCAP_REG_ADCC1, + CPCAP_BIT_CAL_MODE | CPCAP_BIT_ATOX | + CPCAP_BIT_ATO3 | CPCAP_BIT_ATO2 | + CPCAP_BIT_ATO1 | CPCAP_BIT_ATO0 | + CPCAP_BIT_ADA2 | CPCAP_BIT_ADA1 | + CPCAP_BIT_ADA0 | CPCAP_BIT_AD_SEL1 | + CPCAP_BIT_RAND1 | CPCAP_BIT_RAND0, + value); + if (error) + return; + + error = regmap_clear_bits(ddata->reg, CPCAP_REG_ADCC2, + CPCAP_BIT_ATOX_PS_FACTOR | + CPCAP_BIT_ADC_PS_FACTOR1 | + CPCAP_BIT_ADC_PS_FACTOR0); + if (error) + return; + + error = regmap_set_bits(ddata->reg, CPCAP_REG_ADCC2, + CPCAP_BIT_ADTRIG_DIS); + if (error) + return; + + error = regmap_set_bits(ddata->reg, CPCAP_REG_ADCC2, CPCAP_BIT_ASC); + if (error) + return; + + do { + schedule_timeout_uninterruptible(1); + error = regmap_read(ddata->reg, CPCAP_REG_ADCC2, &value); + if (error) + return; + } while ((value & CPCAP_BIT_ASC) && time_before(jiffies, timeout)); + + if (value & CPCAP_BIT_ASC) + dev_err(ddata->dev, + "Timeout waiting for calibration to complete\n"); + + error = regmap_clear_bits(ddata->reg, CPCAP_REG_ADCC1, + CPCAP_BIT_CAL_MODE); + if (error) + return; +} + +static int cpcap_adc_calibrate_one(struct cpcap_adc *ddata, + int channel, + u16 calibration_register, + int lower_threshold, + int upper_threshold) +{ + unsigned int calibration_data[2]; + unsigned short cal_data_diff; + int i, error; + + for (i = 0; i < CPCAP_ADC_MAX_RETRIES; i++) { + calibration_data[0] = 0; + calibration_data[1] = 0; + + cpcap_adc_setup_calibrate(ddata, channel); + error = regmap_read(ddata->reg, calibration_register, + &calibration_data[0]); + if (error) + return error; + cpcap_adc_setup_calibrate(ddata, channel); + error = regmap_read(ddata->reg, calibration_register, + &calibration_data[1]); + if (error) + return error; + + if (calibration_data[0] > calibration_data[1]) + cal_data_diff = + calibration_data[0] - calibration_data[1]; + else + cal_data_diff = + calibration_data[1] - calibration_data[0]; + + if (((calibration_data[1] >= lower_threshold) && + (calibration_data[1] <= upper_threshold) && + (cal_data_diff <= ST_ADC_CALIBRATE_DIFF_THRESHOLD)) || + (ddata->vendor == CPCAP_VENDOR_TI)) { + bank_conversion[channel].cal_offset = + ((short)calibration_data[1] * -1) + 512; + dev_dbg(ddata->dev, "ch%i calibration complete: %i\n", + channel, bank_conversion[channel].cal_offset); + break; + } + usleep_range(5000, 10000); + } + + return 0; +} + +static int cpcap_adc_calibrate(struct cpcap_adc *ddata) +{ + int error; + + error = cpcap_adc_calibrate_one(ddata, CPCAP_ADC_CHG_ISENSE, + CPCAP_REG_ADCAL1, + ST_ADC_CAL_CHRGI_LOW_THRESHOLD, + ST_ADC_CAL_CHRGI_HIGH_THRESHOLD); + if (error) + return error; + + error = cpcap_adc_calibrate_one(ddata, CPCAP_ADC_BATTI, + CPCAP_REG_ADCAL2, + ST_ADC_CAL_BATTI_LOW_THRESHOLD, + ST_ADC_CAL_BATTI_HIGH_THRESHOLD); + if (error) + return error; + + return 0; +} + +/* ADC setup, read and scale functions */ +static void cpcap_adc_setup_bank(struct cpcap_adc *ddata, + struct cpcap_adc_request *req) +{ + const struct cpcap_adc_ato *ato = ddata->ato; + unsigned short value1 = 0; + unsigned short value2 = 0; + int error; + + if (!ato) + return; + + switch (req->channel) { + case CPCAP_ADC_AD0: + value2 |= CPCAP_BIT_THERMBIAS_EN; + error = regmap_update_bits(ddata->reg, CPCAP_REG_ADCC2, + CPCAP_BIT_THERMBIAS_EN, + value2); + if (error) + return; + usleep_range(800, 1000); + break; + case CPCAP_ADC_AD8 ... CPCAP_ADC_TSY2_AD15: + value1 |= CPCAP_BIT_AD_SEL1; + break; + case CPCAP_ADC_BATTP_PI16 ... CPCAP_ADC_BATTI_PI17: + value1 |= CPCAP_BIT_RAND1; + break; + default: + break; + } + + switch (req->timing) { + case CPCAP_ADC_TIMING_IN: + value1 |= ato->ato_in; + value1 |= ato->atox_in; + value2 |= ato->adc_ps_factor_in; + value2 |= ato->atox_ps_factor_in; + break; + case CPCAP_ADC_TIMING_OUT: + value1 |= ato->ato_out; + value1 |= ato->atox_out; + value2 |= ato->adc_ps_factor_out; + value2 |= ato->atox_ps_factor_out; + break; + + case CPCAP_ADC_TIMING_IMM: + default: + break; + } + + error = regmap_update_bits(ddata->reg, CPCAP_REG_ADCC1, + CPCAP_BIT_CAL_MODE | CPCAP_BIT_ATOX | + CPCAP_BIT_ATO3 | CPCAP_BIT_ATO2 | + CPCAP_BIT_ATO1 | CPCAP_BIT_ATO0 | + CPCAP_BIT_ADA2 | CPCAP_BIT_ADA1 | + CPCAP_BIT_ADA0 | CPCAP_BIT_AD_SEL1 | + CPCAP_BIT_RAND1 | CPCAP_BIT_RAND0, + value1); + if (error) + return; + + error = regmap_update_bits(ddata->reg, CPCAP_REG_ADCC2, + CPCAP_BIT_ATOX_PS_FACTOR | + CPCAP_BIT_ADC_PS_FACTOR1 | + CPCAP_BIT_ADC_PS_FACTOR0 | + CPCAP_BIT_THERMBIAS_EN, + value2); + if (error) + return; + + if (req->timing == CPCAP_ADC_TIMING_IMM) { + error = regmap_set_bits(ddata->reg, CPCAP_REG_ADCC2, + CPCAP_BIT_ADTRIG_DIS); + if (error) + return; + + error = regmap_set_bits(ddata->reg, CPCAP_REG_ADCC2, + CPCAP_BIT_ASC); + if (error) + return; + } else { + error = regmap_set_bits(ddata->reg, CPCAP_REG_ADCC2, + CPCAP_BIT_ADTRIG_ONESHOT); + if (error) + return; + + error = regmap_clear_bits(ddata->reg, CPCAP_REG_ADCC2, + CPCAP_BIT_ADTRIG_DIS); + if (error) + return; + } +} + +static int cpcap_adc_start_bank(struct cpcap_adc *ddata, + struct cpcap_adc_request *req) +{ + int i, error; + + req->timing = CPCAP_ADC_TIMING_IMM; + ddata->done = false; + + for (i = 0; i < CPCAP_ADC_MAX_RETRIES; i++) { + cpcap_adc_setup_bank(ddata, req); + error = wait_event_interruptible_timeout(ddata->wq_data_avail, + ddata->done, + msecs_to_jiffies(50)); + if (error > 0) + return 0; + + if (error == 0) { + error = -ETIMEDOUT; + continue; + } + + if (error < 0) + return error; + } + + return error; +} + +static int cpcap_adc_stop_bank(struct cpcap_adc *ddata) +{ + int error; + + error = regmap_update_bits(ddata->reg, CPCAP_REG_ADCC1, + 0xffff, + CPCAP_REG_ADCC1_DEFAULTS); + if (error) + return error; + + return regmap_update_bits(ddata->reg, CPCAP_REG_ADCC2, + 0xffff, + CPCAP_REG_ADCC2_DEFAULTS); +} + +static void cpcap_adc_phase(struct cpcap_adc_request *req) +{ + const struct cpcap_adc_conversion_tbl *conv_tbl = req->conv_tbl; + const struct cpcap_adc_phasing_tbl *phase_tbl = req->phase_tbl; + int index = req->channel; + + /* Remuxed channels 16 and 17 use BATTP and BATTI entries */ + switch (req->channel) { + case CPCAP_ADC_BATTP: + case CPCAP_ADC_BATTP_PI16: + index = req->bank_index; + req->result -= phase_tbl[index].offset; + req->result -= CPCAP_FOUR_POINT_TWO_ADC; + req->result *= phase_tbl[index].multiplier; + if (phase_tbl[index].divider == 0) + return; + req->result /= phase_tbl[index].divider; + req->result += CPCAP_FOUR_POINT_TWO_ADC; + break; + case CPCAP_ADC_BATTI_PI17: + index = req->bank_index; + fallthrough; + default: + req->result += conv_tbl[index].cal_offset; + req->result += conv_tbl[index].align_offset; + req->result *= phase_tbl[index].multiplier; + if (phase_tbl[index].divider == 0) + return; + req->result /= phase_tbl[index].divider; + req->result += phase_tbl[index].offset; + break; + } + + if (req->result < phase_tbl[index].min) + req->result = phase_tbl[index].min; + else if (req->result > phase_tbl[index].max) + req->result = phase_tbl[index].max; +} + +/* Looks up temperatures in a table and calculates averages if needed */ +static int cpcap_adc_table_to_millicelcius(unsigned short value) +{ + int i, result = 0, alpha; + + if (value <= temp_map[CPCAP_MAX_TEMP_LVL - 1][0]) + return temp_map[CPCAP_MAX_TEMP_LVL - 1][1]; + + if (value >= temp_map[0][0]) + return temp_map[0][1]; + + for (i = 0; i < CPCAP_MAX_TEMP_LVL - 1; i++) { + if ((value <= temp_map[i][0]) && + (value >= temp_map[i + 1][0])) { + if (value == temp_map[i][0]) { + result = temp_map[i][1]; + } else if (value == temp_map[i + 1][0]) { + result = temp_map[i + 1][1]; + } else { + alpha = ((value - temp_map[i][0]) * 1000) / + (temp_map[i + 1][0] - temp_map[i][0]); + + result = temp_map[i][1] + + ((alpha * (temp_map[i + 1][1] - + temp_map[i][1])) / 1000); + } + break; + } + } + + return result; +} + +static void cpcap_adc_convert(struct cpcap_adc_request *req) +{ + const struct cpcap_adc_conversion_tbl *conv_tbl = req->conv_tbl; + int index = req->channel; + + /* Remuxed channels 16 and 17 use BATTP and BATTI entries */ + switch (req->channel) { + case CPCAP_ADC_BATTP_PI16: + index = CPCAP_ADC_BATTP; + break; + case CPCAP_ADC_BATTI_PI17: + index = CPCAP_ADC_BATTI; + break; + default: + break; + } + + /* No conversion for raw channels */ + if (conv_tbl[index].conv_type == IIO_CHAN_INFO_RAW) + return; + + /* Temperatures use a lookup table instead of conversion table */ + if ((req->channel == CPCAP_ADC_AD0) || + (req->channel == CPCAP_ADC_AD3)) { + req->result = + cpcap_adc_table_to_millicelcius(req->result); + + return; + } + + /* All processed channels use a conversion table */ + req->result *= conv_tbl[index].multiplier; + if (conv_tbl[index].divider == 0) + return; + req->result /= conv_tbl[index].divider; + req->result += conv_tbl[index].conv_offset; +} + +/* + * REVISIT: Check if timed sampling can use multiple channels at the + * same time. If not, replace channel_mask with just channel. + */ +static int cpcap_adc_read_bank_scaled(struct cpcap_adc *ddata, + struct cpcap_adc_request *req) +{ + int calibration_data, error, addr; + + if (ddata->vendor == CPCAP_VENDOR_TI) { + error = regmap_read(ddata->reg, CPCAP_REG_ADCAL1, + &calibration_data); + if (error) + return error; + bank_conversion[CPCAP_ADC_CHG_ISENSE].cal_offset = + ((short)calibration_data * -1) + 512; + + error = regmap_read(ddata->reg, CPCAP_REG_ADCAL2, + &calibration_data); + if (error) + return error; + bank_conversion[CPCAP_ADC_BATTI].cal_offset = + ((short)calibration_data * -1) + 512; + } + + addr = CPCAP_REG_ADCD0 + req->bank_index * 4; + + error = regmap_read(ddata->reg, addr, &req->result); + if (error) + return error; + + req->result &= 0x3ff; + cpcap_adc_phase(req); + cpcap_adc_convert(req); + + return 0; +} + +static int cpcap_adc_init_request(struct cpcap_adc_request *req, + int channel) +{ + req->channel = channel; + req->phase_tbl = bank_phasing; + req->conv_tbl = bank_conversion; + + switch (channel) { + case CPCAP_ADC_AD0 ... CPCAP_ADC_USB_ID: + req->bank_index = channel; + break; + case CPCAP_ADC_AD8 ... CPCAP_ADC_TSY2_AD15: + req->bank_index = channel - 8; + break; + case CPCAP_ADC_BATTP_PI16: + req->bank_index = CPCAP_ADC_BATTP; + break; + case CPCAP_ADC_BATTI_PI17: + req->bank_index = CPCAP_ADC_BATTI; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int cpcap_adc_read_st_die_temp(struct cpcap_adc *ddata, + int addr, int *val) +{ + int error; + + error = regmap_read(ddata->reg, addr, val); + if (error) + return error; + + *val -= 282; + *val *= 114; + *val += 25000; + + return 0; +} + +static int cpcap_adc_read(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cpcap_adc *ddata = iio_priv(indio_dev); + struct cpcap_adc_request req; + int error; + + error = cpcap_adc_init_request(&req, chan->channel); + if (error) + return error; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&ddata->lock); + error = cpcap_adc_start_bank(ddata, &req); + if (error) + goto err_unlock; + error = regmap_read(ddata->reg, chan->address, val); + if (error) + goto err_unlock; + error = cpcap_adc_stop_bank(ddata); + if (error) + goto err_unlock; + mutex_unlock(&ddata->lock); + break; + case IIO_CHAN_INFO_PROCESSED: + mutex_lock(&ddata->lock); + error = cpcap_adc_start_bank(ddata, &req); + if (error) + goto err_unlock; + if ((ddata->vendor == CPCAP_VENDOR_ST) && + (chan->channel == CPCAP_ADC_AD3)) { + error = cpcap_adc_read_st_die_temp(ddata, + chan->address, + &req.result); + if (error) + goto err_unlock; + } else { + error = cpcap_adc_read_bank_scaled(ddata, &req); + if (error) + goto err_unlock; + } + error = cpcap_adc_stop_bank(ddata); + if (error) + goto err_unlock; + mutex_unlock(&ddata->lock); + *val = req.result; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; + +err_unlock: + mutex_unlock(&ddata->lock); + dev_err(ddata->dev, "error reading ADC: %i\n", error); + + return error; +} + +static const struct iio_info cpcap_adc_info = { + .read_raw = &cpcap_adc_read, +}; + +/* + * Configuration for Motorola mapphone series such as droid 4. + * Copied from the Motorola mapphone kernel tree. + */ +static const struct cpcap_adc_ato mapphone_adc = { + .ato_in = 0x0480, + .atox_in = 0, + .adc_ps_factor_in = 0x0200, + .atox_ps_factor_in = 0, + .ato_out = 0, + .atox_out = 0, + .adc_ps_factor_out = 0, + .atox_ps_factor_out = 0, +}; + +static const struct cpcap_adc_ato mot_adc = { + .ato_in = 0x0300, + .atox_in = 0, + .adc_ps_factor_in = 0x0200, + .atox_ps_factor_in = 0, + .ato_out = 0x0780, + .atox_out = 0, + .adc_ps_factor_out = 0x0600, + .atox_ps_factor_out = 0, +}; + +static const struct of_device_id cpcap_adc_id_table[] = { + { + .compatible = "motorola,cpcap-adc", + }, + { + .compatible = "motorola,mapphone-cpcap-adc", + .data = &mapphone_adc, + }, + { + .compatible = "motorola,mot-cpcap-adc", + .data = &mot_adc, + }, + { } +}; +MODULE_DEVICE_TABLE(of, cpcap_adc_id_table); + +static int cpcap_adc_probe(struct platform_device *pdev) +{ + struct cpcap_adc *ddata; + struct iio_dev *indio_dev; + int error; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*ddata)); + if (!indio_dev) + return -ENOMEM; + + ddata = iio_priv(indio_dev); + ddata->ato = device_get_match_data(&pdev->dev); + if (!ddata->ato) + return -ENODEV; + ddata->dev = &pdev->dev; + + mutex_init(&ddata->lock); + init_waitqueue_head(&ddata->wq_data_avail); + + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + indio_dev->channels = cpcap_adc_channels; + indio_dev->num_channels = ARRAY_SIZE(cpcap_adc_channels); + indio_dev->name = dev_name(&pdev->dev); + indio_dev->info = &cpcap_adc_info; + + ddata->reg = dev_get_regmap(pdev->dev.parent, NULL); + if (!ddata->reg) + return -ENODEV; + + error = cpcap_get_vendor(ddata->dev, ddata->reg, &ddata->vendor); + if (error) + return error; + + platform_set_drvdata(pdev, indio_dev); + + ddata->irq = platform_get_irq_byname(pdev, "adcdone"); + if (ddata->irq < 0) + return -ENODEV; + + error = devm_request_threaded_irq(&pdev->dev, ddata->irq, NULL, + cpcap_adc_irq_thread, + IRQF_TRIGGER_NONE | IRQF_ONESHOT, + "cpcap-adc", indio_dev); + if (error) { + dev_err(&pdev->dev, "could not get irq: %i\n", + error); + + return error; + } + + error = cpcap_adc_calibrate(ddata); + if (error) + return error; + + dev_info(&pdev->dev, "CPCAP ADC device probed\n"); + + return devm_iio_device_register(&pdev->dev, indio_dev); +} + +static struct platform_driver cpcap_adc_driver = { + .driver = { + .name = "cpcap_adc", + .of_match_table = cpcap_adc_id_table, + }, + .probe = cpcap_adc_probe, +}; + +module_platform_driver(cpcap_adc_driver); + +MODULE_ALIAS("platform:cpcap_adc"); +MODULE_DESCRIPTION("CPCAP ADC driver"); +MODULE_AUTHOR("Tony Lindgren + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Channels */ +enum da9150_gpadc_hw_channel { + DA9150_GPADC_HW_CHAN_GPIOA_2V = 0, + DA9150_GPADC_HW_CHAN_GPIOA_2V_, + DA9150_GPADC_HW_CHAN_GPIOB_2V, + DA9150_GPADC_HW_CHAN_GPIOB_2V_, + DA9150_GPADC_HW_CHAN_GPIOC_2V, + DA9150_GPADC_HW_CHAN_GPIOC_2V_, + DA9150_GPADC_HW_CHAN_GPIOD_2V, + DA9150_GPADC_HW_CHAN_GPIOD_2V_, + DA9150_GPADC_HW_CHAN_IBUS_SENSE, + DA9150_GPADC_HW_CHAN_IBUS_SENSE_, + DA9150_GPADC_HW_CHAN_VBUS_DIV, + DA9150_GPADC_HW_CHAN_VBUS_DIV_, + DA9150_GPADC_HW_CHAN_ID, + DA9150_GPADC_HW_CHAN_ID_, + DA9150_GPADC_HW_CHAN_VSYS, + DA9150_GPADC_HW_CHAN_VSYS_, + DA9150_GPADC_HW_CHAN_GPIOA_6V, + DA9150_GPADC_HW_CHAN_GPIOA_6V_, + DA9150_GPADC_HW_CHAN_GPIOB_6V, + DA9150_GPADC_HW_CHAN_GPIOB_6V_, + DA9150_GPADC_HW_CHAN_GPIOC_6V, + DA9150_GPADC_HW_CHAN_GPIOC_6V_, + DA9150_GPADC_HW_CHAN_GPIOD_6V, + DA9150_GPADC_HW_CHAN_GPIOD_6V_, + DA9150_GPADC_HW_CHAN_VBAT, + DA9150_GPADC_HW_CHAN_VBAT_, + DA9150_GPADC_HW_CHAN_TBAT, + DA9150_GPADC_HW_CHAN_TBAT_, + DA9150_GPADC_HW_CHAN_TJUNC_CORE, + DA9150_GPADC_HW_CHAN_TJUNC_CORE_, + DA9150_GPADC_HW_CHAN_TJUNC_OVP, + DA9150_GPADC_HW_CHAN_TJUNC_OVP_, +}; + +enum da9150_gpadc_channel { + DA9150_GPADC_CHAN_GPIOA = 0, + DA9150_GPADC_CHAN_GPIOB, + DA9150_GPADC_CHAN_GPIOC, + DA9150_GPADC_CHAN_GPIOD, + DA9150_GPADC_CHAN_IBUS, + DA9150_GPADC_CHAN_VBUS, + DA9150_GPADC_CHAN_VSYS, + DA9150_GPADC_CHAN_VBAT, + DA9150_GPADC_CHAN_TBAT, + DA9150_GPADC_CHAN_TJUNC_CORE, + DA9150_GPADC_CHAN_TJUNC_OVP, +}; + +/* Private data */ +struct da9150_gpadc { + struct da9150 *da9150; + struct device *dev; + + struct mutex lock; + struct completion complete; +}; + + +static irqreturn_t da9150_gpadc_irq(int irq, void *data) +{ + + struct da9150_gpadc *gpadc = data; + + complete(&gpadc->complete); + + return IRQ_HANDLED; +} + +static int da9150_gpadc_read_adc(struct da9150_gpadc *gpadc, int hw_chan) +{ + u8 result_regs[2]; + int result; + + mutex_lock(&gpadc->lock); + + /* Set channel & enable measurement */ + da9150_reg_write(gpadc->da9150, DA9150_GPADC_MAN, + (DA9150_GPADC_EN_MASK | + hw_chan << DA9150_GPADC_MUX_SHIFT)); + + /* Consume left-over completion from a previous timeout */ + try_wait_for_completion(&gpadc->complete); + + /* Check for actual completion */ + wait_for_completion_timeout(&gpadc->complete, msecs_to_jiffies(5)); + + /* Read result and status from device */ + da9150_bulk_read(gpadc->da9150, DA9150_GPADC_RES_A, 2, result_regs); + + mutex_unlock(&gpadc->lock); + + /* Check to make sure device really has completed reading */ + if (result_regs[1] & DA9150_GPADC_RUN_MASK) { + dev_err(gpadc->dev, "Timeout on channel %d of GPADC\n", + hw_chan); + return -ETIMEDOUT; + } + + /* LSBs - 2 bits */ + result = (result_regs[1] & DA9150_GPADC_RES_L_MASK) >> + DA9150_GPADC_RES_L_SHIFT; + /* MSBs - 8 bits */ + result |= result_regs[0] << DA9150_GPADC_RES_L_BITS; + + return result; +} + +static inline int da9150_gpadc_gpio_6v_voltage_now(int raw_val) +{ + /* Convert to mV */ + return (6 * ((raw_val * 1000) + 500)) / 1024; +} + +static inline int da9150_gpadc_ibus_current_avg(int raw_val) +{ + /* Convert to mA */ + return (4 * ((raw_val * 1000) + 500)) / 2048; +} + +static inline int da9150_gpadc_vbus_21v_voltage_now(int raw_val) +{ + /* Convert to mV */ + return (21 * ((raw_val * 1000) + 500)) / 1024; +} + +static inline int da9150_gpadc_vsys_6v_voltage_now(int raw_val) +{ + /* Convert to mV */ + return (3 * ((raw_val * 1000) + 500)) / 512; +} + +static int da9150_gpadc_read_processed(struct da9150_gpadc *gpadc, int channel, + int hw_chan, int *val) +{ + int raw_val; + + raw_val = da9150_gpadc_read_adc(gpadc, hw_chan); + if (raw_val < 0) + return raw_val; + + switch (channel) { + case DA9150_GPADC_CHAN_GPIOA: + case DA9150_GPADC_CHAN_GPIOB: + case DA9150_GPADC_CHAN_GPIOC: + case DA9150_GPADC_CHAN_GPIOD: + *val = da9150_gpadc_gpio_6v_voltage_now(raw_val); + break; + case DA9150_GPADC_CHAN_IBUS: + *val = da9150_gpadc_ibus_current_avg(raw_val); + break; + case DA9150_GPADC_CHAN_VBUS: + *val = da9150_gpadc_vbus_21v_voltage_now(raw_val); + break; + case DA9150_GPADC_CHAN_VSYS: + *val = da9150_gpadc_vsys_6v_voltage_now(raw_val); + break; + default: + /* No processing for other channels so return raw value */ + *val = raw_val; + break; + } + + return IIO_VAL_INT; +} + +static int da9150_gpadc_read_scale(int channel, int *val, int *val2) +{ + switch (channel) { + case DA9150_GPADC_CHAN_VBAT: + *val = 2932; + *val2 = 1000; + return IIO_VAL_FRACTIONAL; + case DA9150_GPADC_CHAN_TJUNC_CORE: + case DA9150_GPADC_CHAN_TJUNC_OVP: + *val = 1000000; + *val2 = 4420; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } +} + +static int da9150_gpadc_read_offset(int channel, int *val) +{ + switch (channel) { + case DA9150_GPADC_CHAN_VBAT: + *val = 1500000 / 2932; + return IIO_VAL_INT; + case DA9150_GPADC_CHAN_TJUNC_CORE: + case DA9150_GPADC_CHAN_TJUNC_OVP: + *val = -144; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int da9150_gpadc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct da9150_gpadc *gpadc = iio_priv(indio_dev); + + if ((chan->channel < DA9150_GPADC_CHAN_GPIOA) || + (chan->channel > DA9150_GPADC_CHAN_TJUNC_OVP)) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + return da9150_gpadc_read_processed(gpadc, chan->channel, + chan->address, val); + case IIO_CHAN_INFO_SCALE: + return da9150_gpadc_read_scale(chan->channel, val, val2); + case IIO_CHAN_INFO_OFFSET: + return da9150_gpadc_read_offset(chan->channel, val); + default: + return -EINVAL; + } +} + +static const struct iio_info da9150_gpadc_info = { + .read_raw = &da9150_gpadc_read_raw, +}; + +#define DA9150_GPADC_CHANNEL(_id, _hw_id, _type, chan_info, \ + _ext_name) { \ + .type = _type, \ + .indexed = 1, \ + .channel = DA9150_GPADC_CHAN_##_id, \ + .address = DA9150_GPADC_HW_CHAN_##_hw_id, \ + .info_mask_separate = chan_info, \ + .extend_name = _ext_name, \ + .datasheet_name = #_id, \ +} + +#define DA9150_GPADC_CHANNEL_RAW(_id, _hw_id, _type, _ext_name) \ + DA9150_GPADC_CHANNEL(_id, _hw_id, _type, \ + BIT(IIO_CHAN_INFO_RAW), _ext_name) + +#define DA9150_GPADC_CHANNEL_SCALED(_id, _hw_id, _type, _ext_name) \ + DA9150_GPADC_CHANNEL(_id, _hw_id, _type, \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + _ext_name) + +#define DA9150_GPADC_CHANNEL_PROCESSED(_id, _hw_id, _type, _ext_name) \ + DA9150_GPADC_CHANNEL(_id, _hw_id, _type, \ + BIT(IIO_CHAN_INFO_PROCESSED), _ext_name) + +/* Supported channels */ +static const struct iio_chan_spec da9150_gpadc_channels[] = { + DA9150_GPADC_CHANNEL_PROCESSED(GPIOA, GPIOA_6V, IIO_VOLTAGE, NULL), + DA9150_GPADC_CHANNEL_PROCESSED(GPIOB, GPIOB_6V, IIO_VOLTAGE, NULL), + DA9150_GPADC_CHANNEL_PROCESSED(GPIOC, GPIOC_6V, IIO_VOLTAGE, NULL), + DA9150_GPADC_CHANNEL_PROCESSED(GPIOD, GPIOD_6V, IIO_VOLTAGE, NULL), + DA9150_GPADC_CHANNEL_PROCESSED(IBUS, IBUS_SENSE, IIO_CURRENT, "ibus"), + DA9150_GPADC_CHANNEL_PROCESSED(VBUS, VBUS_DIV_, IIO_VOLTAGE, "vbus"), + DA9150_GPADC_CHANNEL_PROCESSED(VSYS, VSYS, IIO_VOLTAGE, "vsys"), + DA9150_GPADC_CHANNEL_SCALED(VBAT, VBAT, IIO_VOLTAGE, "vbat"), + DA9150_GPADC_CHANNEL_RAW(TBAT, TBAT, IIO_VOLTAGE, "tbat"), + DA9150_GPADC_CHANNEL_SCALED(TJUNC_CORE, TJUNC_CORE, IIO_TEMP, + "tjunc_core"), + DA9150_GPADC_CHANNEL_SCALED(TJUNC_OVP, TJUNC_OVP, IIO_TEMP, + "tjunc_ovp"), +}; + +/* Default maps used by da9150-charger */ +static const struct iio_map da9150_gpadc_default_maps[] = { + IIO_MAP("IBUS", "da9150-charger", "CHAN_IBUS"), + IIO_MAP("VBUS", "da9150-charger", "CHAN_VBUS"), + IIO_MAP("TJUNC_CORE", "da9150-charger", "CHAN_TJUNC"), + IIO_MAP("VBAT", "da9150-charger", "CHAN_VBAT"), + { } +}; + +static int da9150_gpadc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct da9150 *da9150 = dev_get_drvdata(dev->parent); + struct da9150_gpadc *gpadc; + struct iio_dev *indio_dev; + int irq, ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*gpadc)); + if (!indio_dev) + return -ENOMEM; + + gpadc = iio_priv(indio_dev); + + gpadc->da9150 = da9150; + gpadc->dev = dev; + mutex_init(&gpadc->lock); + init_completion(&gpadc->complete); + + irq = platform_get_irq_byname(pdev, "GPADC"); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(dev, irq, NULL, da9150_gpadc_irq, + IRQF_ONESHOT, "GPADC", gpadc); + if (ret) { + dev_err(dev, "Failed to request IRQ %d: %d\n", irq, ret); + return ret; + } + + ret = devm_iio_map_array_register(&pdev->dev, indio_dev, da9150_gpadc_default_maps); + if (ret) { + dev_err(dev, "Failed to register IIO maps: %d\n", ret); + return ret; + } + + indio_dev->name = dev_name(dev); + indio_dev->info = &da9150_gpadc_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = da9150_gpadc_channels; + indio_dev->num_channels = ARRAY_SIZE(da9150_gpadc_channels); + + return devm_iio_device_register(&pdev->dev, indio_dev); +} + +static struct platform_driver da9150_gpadc_driver = { + .driver = { + .name = "da9150-gpadc", + }, + .probe = da9150_gpadc_probe, +}; + +module_platform_driver(da9150_gpadc_driver); + +MODULE_DESCRIPTION("GPADC Driver for DA9150"); +MODULE_AUTHOR("Adam Thomson "); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/dln2-adc.c b/drivers/iio/adc/dln2-adc.c new file mode 100644 index 000000000..b01c6f5a7 --- /dev/null +++ b/drivers/iio/adc/dln2-adc.c @@ -0,0 +1,678 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for the Diolan DLN-2 USB-ADC adapter + * + * Copyright (c) 2017 Jack Andersen + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define DLN2_ADC_MOD_NAME "dln2-adc" + +#define DLN2_ADC_ID 0x06 + +#define DLN2_ADC_GET_CHANNEL_COUNT DLN2_CMD(0x01, DLN2_ADC_ID) +#define DLN2_ADC_ENABLE DLN2_CMD(0x02, DLN2_ADC_ID) +#define DLN2_ADC_DISABLE DLN2_CMD(0x03, DLN2_ADC_ID) +#define DLN2_ADC_CHANNEL_ENABLE DLN2_CMD(0x05, DLN2_ADC_ID) +#define DLN2_ADC_CHANNEL_DISABLE DLN2_CMD(0x06, DLN2_ADC_ID) +#define DLN2_ADC_SET_RESOLUTION DLN2_CMD(0x08, DLN2_ADC_ID) +#define DLN2_ADC_CHANNEL_GET_VAL DLN2_CMD(0x0A, DLN2_ADC_ID) +#define DLN2_ADC_CHANNEL_GET_ALL_VAL DLN2_CMD(0x0B, DLN2_ADC_ID) +#define DLN2_ADC_CHANNEL_SET_CFG DLN2_CMD(0x0C, DLN2_ADC_ID) +#define DLN2_ADC_CHANNEL_GET_CFG DLN2_CMD(0x0D, DLN2_ADC_ID) +#define DLN2_ADC_CONDITION_MET_EV DLN2_CMD(0x10, DLN2_ADC_ID) + +#define DLN2_ADC_EVENT_NONE 0 +#define DLN2_ADC_EVENT_BELOW 1 +#define DLN2_ADC_EVENT_LEVEL_ABOVE 2 +#define DLN2_ADC_EVENT_OUTSIDE 3 +#define DLN2_ADC_EVENT_INSIDE 4 +#define DLN2_ADC_EVENT_ALWAYS 5 + +#define DLN2_ADC_MAX_CHANNELS 8 +#define DLN2_ADC_DATA_BITS 10 + +/* + * Plays similar role to iio_demux_table in subsystem core; except allocated + * in a fixed 8-element array. + */ +struct dln2_adc_demux_table { + unsigned int from; + unsigned int to; + unsigned int length; +}; + +struct dln2_adc { + struct platform_device *pdev; + struct iio_chan_spec iio_channels[DLN2_ADC_MAX_CHANNELS + 1]; + int port, trigger_chan; + struct iio_trigger *trig; + struct mutex mutex; + /* Cached sample period in milliseconds */ + unsigned int sample_period; + /* Demux table */ + unsigned int demux_count; + struct dln2_adc_demux_table demux[DLN2_ADC_MAX_CHANNELS]; +}; + +struct dln2_adc_port_chan { + u8 port; + u8 chan; +}; + +struct dln2_adc_get_all_vals { + __le16 channel_mask; + __le16 values[DLN2_ADC_MAX_CHANNELS]; +}; + +static void dln2_adc_add_demux(struct dln2_adc *dln2, + unsigned int in_loc, unsigned int out_loc, + unsigned int length) +{ + struct dln2_adc_demux_table *p = dln2->demux_count ? + &dln2->demux[dln2->demux_count - 1] : NULL; + + if (p && p->from + p->length == in_loc && + p->to + p->length == out_loc) { + p->length += length; + } else if (dln2->demux_count < DLN2_ADC_MAX_CHANNELS) { + p = &dln2->demux[dln2->demux_count++]; + p->from = in_loc; + p->to = out_loc; + p->length = length; + } +} + +static void dln2_adc_update_demux(struct dln2_adc *dln2) +{ + int in_ind = -1, out_ind; + unsigned int in_loc = 0, out_loc = 0; + struct iio_dev *indio_dev = platform_get_drvdata(dln2->pdev); + + /* Clear out any old demux */ + dln2->demux_count = 0; + + /* Optimize all 8-channels case */ + if (iio_get_masklength(indio_dev) && + (*indio_dev->active_scan_mask & 0xff) == 0xff) { + dln2_adc_add_demux(dln2, 0, 0, 16); + return; + } + + /* Build demux table from fixed 8-channels to active_scan_mask */ + iio_for_each_active_channel(indio_dev, out_ind) { + /* Handle timestamp separately */ + if (out_ind == DLN2_ADC_MAX_CHANNELS) + break; + for (++in_ind; in_ind != out_ind; ++in_ind) + in_loc += 2; + dln2_adc_add_demux(dln2, in_loc, out_loc, 2); + out_loc += 2; + in_loc += 2; + } +} + +static int dln2_adc_get_chan_count(struct dln2_adc *dln2) +{ + int ret; + u8 port = dln2->port; + u8 count; + int olen = sizeof(count); + + ret = dln2_transfer(dln2->pdev, DLN2_ADC_GET_CHANNEL_COUNT, + &port, sizeof(port), &count, &olen); + if (ret < 0) { + dev_dbg(&dln2->pdev->dev, "Problem in %s\n", __func__); + return ret; + } + if (olen < sizeof(count)) + return -EPROTO; + + return count; +} + +static int dln2_adc_set_port_resolution(struct dln2_adc *dln2) +{ + int ret; + struct dln2_adc_port_chan port_chan = { + .port = dln2->port, + .chan = DLN2_ADC_DATA_BITS, + }; + + ret = dln2_transfer_tx(dln2->pdev, DLN2_ADC_SET_RESOLUTION, + &port_chan, sizeof(port_chan)); + if (ret < 0) + dev_dbg(&dln2->pdev->dev, "Problem in %s\n", __func__); + + return ret; +} + +static int dln2_adc_set_chan_enabled(struct dln2_adc *dln2, + int channel, bool enable) +{ + int ret; + struct dln2_adc_port_chan port_chan = { + .port = dln2->port, + .chan = channel, + }; + u16 cmd = enable ? DLN2_ADC_CHANNEL_ENABLE : DLN2_ADC_CHANNEL_DISABLE; + + ret = dln2_transfer_tx(dln2->pdev, cmd, &port_chan, sizeof(port_chan)); + if (ret < 0) + dev_dbg(&dln2->pdev->dev, "Problem in %s\n", __func__); + + return ret; +} + +static int dln2_adc_set_port_enabled(struct dln2_adc *dln2, bool enable, + u16 *conflict_out) +{ + int ret; + u8 port = dln2->port; + __le16 conflict; + int olen = sizeof(conflict); + u16 cmd = enable ? DLN2_ADC_ENABLE : DLN2_ADC_DISABLE; + + if (conflict_out) + *conflict_out = 0; + + ret = dln2_transfer(dln2->pdev, cmd, &port, sizeof(port), + &conflict, &olen); + if (ret < 0) { + dev_dbg(&dln2->pdev->dev, "Problem in %s(%d)\n", + __func__, (int)enable); + if (conflict_out && enable && olen >= sizeof(conflict)) + *conflict_out = le16_to_cpu(conflict); + return ret; + } + if (enable && olen < sizeof(conflict)) + return -EPROTO; + + return ret; +} + +static int dln2_adc_set_chan_period(struct dln2_adc *dln2, + unsigned int channel, unsigned int period) +{ + int ret; + struct { + struct dln2_adc_port_chan port_chan; + __u8 type; + __le16 period; + __le16 low; + __le16 high; + } __packed set_cfg = { + .port_chan.port = dln2->port, + .port_chan.chan = channel, + .type = period ? DLN2_ADC_EVENT_ALWAYS : DLN2_ADC_EVENT_NONE, + .period = cpu_to_le16(period) + }; + + ret = dln2_transfer_tx(dln2->pdev, DLN2_ADC_CHANNEL_SET_CFG, + &set_cfg, sizeof(set_cfg)); + if (ret < 0) + dev_dbg(&dln2->pdev->dev, "Problem in %s\n", __func__); + + return ret; +} + +static int dln2_adc_read(struct dln2_adc *dln2, unsigned int channel) +{ + int ret, i; + u16 conflict; + __le16 value; + int olen = sizeof(value); + struct dln2_adc_port_chan port_chan = { + .port = dln2->port, + .chan = channel, + }; + + ret = dln2_adc_set_chan_enabled(dln2, channel, true); + if (ret < 0) + return ret; + + ret = dln2_adc_set_port_enabled(dln2, true, &conflict); + if (ret < 0) { + if (conflict) { + dev_err(&dln2->pdev->dev, + "ADC pins conflict with mask %04X\n", + (int)conflict); + ret = -EBUSY; + } + goto disable_chan; + } + + /* + * Call GET_VAL twice due to initial zero-return immediately after + * enabling channel. + */ + for (i = 0; i < 2; ++i) { + ret = dln2_transfer(dln2->pdev, DLN2_ADC_CHANNEL_GET_VAL, + &port_chan, sizeof(port_chan), + &value, &olen); + if (ret < 0) { + dev_dbg(&dln2->pdev->dev, "Problem in %s\n", __func__); + goto disable_port; + } + if (olen < sizeof(value)) { + ret = -EPROTO; + goto disable_port; + } + } + + ret = le16_to_cpu(value); + +disable_port: + dln2_adc_set_port_enabled(dln2, false, NULL); +disable_chan: + dln2_adc_set_chan_enabled(dln2, channel, false); + + return ret; +} + +static int dln2_adc_read_all(struct dln2_adc *dln2, + struct dln2_adc_get_all_vals *get_all_vals) +{ + int ret; + __u8 port = dln2->port; + int olen = sizeof(*get_all_vals); + + ret = dln2_transfer(dln2->pdev, DLN2_ADC_CHANNEL_GET_ALL_VAL, + &port, sizeof(port), get_all_vals, &olen); + if (ret < 0) { + dev_dbg(&dln2->pdev->dev, "Problem in %s\n", __func__); + return ret; + } + if (olen < sizeof(*get_all_vals)) + return -EPROTO; + + return ret; +} + +static int dln2_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + int ret; + unsigned int microhertz; + struct dln2_adc *dln2 = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + mutex_lock(&dln2->mutex); + ret = dln2_adc_read(dln2, chan->channel); + mutex_unlock(&dln2->mutex); + + iio_device_release_direct(indio_dev); + + if (ret < 0) + return ret; + + *val = ret; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + /* + * Voltage reference is fixed at 3.3v + * 3.3 / (1 << 10) * 1000000000 + */ + *val = 0; + *val2 = 3222656; + return IIO_VAL_INT_PLUS_NANO; + + case IIO_CHAN_INFO_SAMP_FREQ: + if (dln2->sample_period) { + microhertz = 1000000000 / dln2->sample_period; + *val = microhertz / 1000000; + *val2 = microhertz % 1000000; + } else { + *val = 0; + *val2 = 0; + } + + return IIO_VAL_INT_PLUS_MICRO; + + default: + return -EINVAL; + } +} + +static int dln2_adc_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + int ret; + unsigned int microhertz; + struct dln2_adc *dln2 = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + microhertz = 1000000 * val + val2; + + mutex_lock(&dln2->mutex); + + dln2->sample_period = + microhertz ? 1000000000 / microhertz : UINT_MAX; + if (dln2->sample_period > 65535) { + dln2->sample_period = 65535; + dev_warn(&dln2->pdev->dev, + "clamping period to 65535ms\n"); + } + + /* + * The first requested channel is arbitrated as a shared + * trigger source, so only one event is registered with the + * DLN. The event handler will then read all enabled channel + * values using DLN2_ADC_CHANNEL_GET_ALL_VAL to maintain + * synchronization between ADC readings. + */ + if (dln2->trigger_chan != -1) + ret = dln2_adc_set_chan_period(dln2, + dln2->trigger_chan, dln2->sample_period); + else + ret = 0; + + mutex_unlock(&dln2->mutex); + + return ret; + + default: + return -EINVAL; + } +} + +static int dln2_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct dln2_adc *dln2 = iio_priv(indio_dev); + int chan_count = indio_dev->num_channels - 1; + int ret, i, j; + + mutex_lock(&dln2->mutex); + + for (i = 0; i < chan_count; ++i) { + ret = dln2_adc_set_chan_enabled(dln2, i, + test_bit(i, scan_mask)); + if (ret < 0) { + for (j = 0; j < i; ++j) + dln2_adc_set_chan_enabled(dln2, j, false); + mutex_unlock(&dln2->mutex); + dev_err(&dln2->pdev->dev, + "Unable to enable ADC channel %d\n", i); + return -EBUSY; + } + } + + dln2_adc_update_demux(dln2); + + mutex_unlock(&dln2->mutex); + + return 0; +} + +#define DLN2_ADC_CHAN(lval, idx) { \ + lval.type = IIO_VOLTAGE; \ + lval.channel = idx; \ + lval.indexed = 1; \ + lval.info_mask_separate = BIT(IIO_CHAN_INFO_RAW); \ + lval.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ); \ + lval.scan_index = idx; \ + lval.scan_type.sign = 'u'; \ + lval.scan_type.realbits = DLN2_ADC_DATA_BITS; \ + lval.scan_type.storagebits = 16; \ + lval.scan_type.endianness = IIO_LE; \ +} + +static const struct iio_info dln2_adc_info = { + .read_raw = dln2_adc_read_raw, + .write_raw = dln2_adc_write_raw, + .update_scan_mode = dln2_update_scan_mode, +}; + +static irqreturn_t dln2_adc_trigger_h(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct { + __le16 values[DLN2_ADC_MAX_CHANNELS]; + aligned_s64 timestamp_space; + } data = { }; + struct dln2_adc_get_all_vals dev_data; + struct dln2_adc *dln2 = iio_priv(indio_dev); + const struct dln2_adc_demux_table *t; + int ret, i; + + mutex_lock(&dln2->mutex); + ret = dln2_adc_read_all(dln2, &dev_data); + mutex_unlock(&dln2->mutex); + if (ret < 0) + goto done; + + /* Demux operation */ + for (i = 0; i < dln2->demux_count; ++i) { + t = &dln2->demux[i]; + memcpy((void *)data.values + t->to, + (void *)dev_data.values + t->from, t->length); + } + + iio_push_to_buffers_with_ts(indio_dev, &data, sizeof(data), + iio_get_time_ns(indio_dev)); + +done: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int dln2_adc_triggered_buffer_postenable(struct iio_dev *indio_dev) +{ + int ret; + struct dln2_adc *dln2 = iio_priv(indio_dev); + u16 conflict; + unsigned int trigger_chan; + + mutex_lock(&dln2->mutex); + + /* Enable ADC */ + ret = dln2_adc_set_port_enabled(dln2, true, &conflict); + if (ret < 0) { + mutex_unlock(&dln2->mutex); + dev_dbg(&dln2->pdev->dev, "Problem in %s\n", __func__); + if (conflict) { + dev_err(&dln2->pdev->dev, + "ADC pins conflict with mask %04X\n", + (int)conflict); + ret = -EBUSY; + } + return ret; + } + + /* Assign trigger channel based on first enabled channel */ + trigger_chan = find_first_bit(indio_dev->active_scan_mask, + iio_get_masklength(indio_dev)); + if (trigger_chan < DLN2_ADC_MAX_CHANNELS) { + dln2->trigger_chan = trigger_chan; + ret = dln2_adc_set_chan_period(dln2, dln2->trigger_chan, + dln2->sample_period); + mutex_unlock(&dln2->mutex); + if (ret < 0) { + dev_dbg(&dln2->pdev->dev, "Problem in %s\n", __func__); + return ret; + } + } else { + dln2->trigger_chan = -1; + mutex_unlock(&dln2->mutex); + } + + return 0; +} + +static int dln2_adc_triggered_buffer_predisable(struct iio_dev *indio_dev) +{ + int ret; + struct dln2_adc *dln2 = iio_priv(indio_dev); + + mutex_lock(&dln2->mutex); + + /* Disable trigger channel */ + if (dln2->trigger_chan != -1) { + dln2_adc_set_chan_period(dln2, dln2->trigger_chan, 0); + dln2->trigger_chan = -1; + } + + /* Disable ADC */ + ret = dln2_adc_set_port_enabled(dln2, false, NULL); + + mutex_unlock(&dln2->mutex); + if (ret < 0) + dev_dbg(&dln2->pdev->dev, "Problem in %s\n", __func__); + + return ret; +} + +static const struct iio_buffer_setup_ops dln2_adc_buffer_setup_ops = { + .postenable = dln2_adc_triggered_buffer_postenable, + .predisable = dln2_adc_triggered_buffer_predisable, +}; + +static void dln2_adc_event(struct platform_device *pdev, u16 echo, + const void *data, int len) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct dln2_adc *dln2 = iio_priv(indio_dev); + + /* Called via URB completion handler */ + iio_trigger_poll(dln2->trig); +} + +static int dln2_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dln2_adc *dln2; + struct dln2_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct iio_dev *indio_dev; + int i, ret, chans; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*dln2)); + if (!indio_dev) + return -ENOMEM; + + dln2 = iio_priv(indio_dev); + dln2->pdev = pdev; + dln2->port = pdata->port; + dln2->trigger_chan = -1; + mutex_init(&dln2->mutex); + + platform_set_drvdata(pdev, indio_dev); + + ret = dln2_adc_set_port_resolution(dln2); + if (ret < 0) { + dev_err(dev, "failed to set ADC resolution to 10 bits\n"); + return ret; + } + + chans = dln2_adc_get_chan_count(dln2); + if (chans < 0) { + dev_err(dev, "failed to get channel count: %d\n", chans); + return chans; + } + if (chans > DLN2_ADC_MAX_CHANNELS) { + chans = DLN2_ADC_MAX_CHANNELS; + dev_warn(dev, "clamping channels to %d\n", + DLN2_ADC_MAX_CHANNELS); + } + + for (i = 0; i < chans; ++i) + DLN2_ADC_CHAN(dln2->iio_channels[i], i) + dln2->iio_channels[i] = IIO_CHAN_SOFT_TIMESTAMP(i); + + indio_dev->name = DLN2_ADC_MOD_NAME; + indio_dev->info = &dln2_adc_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = dln2->iio_channels; + indio_dev->num_channels = chans + 1; + indio_dev->setup_ops = &dln2_adc_buffer_setup_ops; + + dln2->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!dln2->trig) + return -ENOMEM; + + iio_trigger_set_drvdata(dln2->trig, dln2); + ret = devm_iio_trigger_register(dev, dln2->trig); + if (ret) { + dev_err(dev, "failed to register trigger: %d\n", ret); + return ret; + } + iio_trigger_set_immutable(indio_dev, dln2->trig); + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + dln2_adc_trigger_h, + &dln2_adc_buffer_setup_ops); + if (ret) { + dev_err(dev, "failed to allocate triggered buffer: %d\n", ret); + return ret; + } + + ret = dln2_register_event_cb(pdev, DLN2_ADC_CONDITION_MET_EV, + dln2_adc_event); + if (ret) { + dev_err(dev, "failed to setup DLN2 periodic event: %d\n", ret); + return ret; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(dev, "failed to register iio device: %d\n", ret); + goto unregister_event; + } + + return ret; + +unregister_event: + dln2_unregister_event_cb(pdev, DLN2_ADC_CONDITION_MET_EV); + + return ret; +} + +static void dln2_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + + iio_device_unregister(indio_dev); + dln2_unregister_event_cb(pdev, DLN2_ADC_CONDITION_MET_EV); +} + +static struct platform_driver dln2_adc_driver = { + .driver.name = DLN2_ADC_MOD_NAME, + .probe = dln2_adc_probe, + .remove = dln2_adc_remove, +}; + +module_platform_driver(dln2_adc_driver); + +MODULE_AUTHOR("Jack Andersen + */ + +/* + * The DAC is used to find the peak level of an alternating voltage input + * signal by a binary search using the output of a comparator wired to + * an interrupt pin. Like so: + * _ + * | \ + * input +------>-------|+ \ + * | \ + * .-------. | }---. + * | | | / | + * | dac|-->--|- / | + * | | |_/ | + * | | | + * | | | + * | irq|------<-------' + * | | + * '-------' + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct envelope { + spinlock_t comp_lock; /* protects comp */ + int comp; + + struct mutex read_lock; /* protects everything else */ + + int comp_irq; + u32 comp_irq_trigger; + u32 comp_irq_trigger_inv; + + struct iio_channel *dac; + struct delayed_work comp_timeout; + + unsigned int comp_interval; + bool invert; + u32 dac_max; + + int high; + int level; + int low; + + struct completion done; +}; + +/* + * The envelope_detector_comp_latch function works together with the compare + * interrupt service routine below (envelope_detector_comp_isr) as a latch + * (one-bit memory) for if the interrupt has triggered since last calling + * this function. + * The ..._comp_isr function disables the interrupt so that the cpu does not + * need to service a possible interrupt flood from the comparator when no-one + * cares anyway, and this ..._comp_latch function reenables them again if + * needed. + */ +static int envelope_detector_comp_latch(struct envelope *env) +{ + int comp; + + spin_lock_irq(&env->comp_lock); + comp = env->comp; + env->comp = 0; + spin_unlock_irq(&env->comp_lock); + + if (!comp) + return 0; + + /* + * The irq was disabled, and is reenabled just now. + * But there might have been a pending irq that + * happened while the irq was disabled that fires + * just as the irq is reenabled. That is not what + * is desired. + */ + enable_irq(env->comp_irq); + + /* So, synchronize this possibly pending irq... */ + synchronize_irq(env->comp_irq); + + /* ...and redo the whole dance. */ + spin_lock_irq(&env->comp_lock); + comp = env->comp; + env->comp = 0; + spin_unlock_irq(&env->comp_lock); + + if (comp) + enable_irq(env->comp_irq); + + return 1; +} + +static irqreturn_t envelope_detector_comp_isr(int irq, void *ctx) +{ + struct envelope *env = ctx; + + spin_lock(&env->comp_lock); + env->comp = 1; + disable_irq_nosync(env->comp_irq); + spin_unlock(&env->comp_lock); + + return IRQ_HANDLED; +} + +static void envelope_detector_setup_compare(struct envelope *env) +{ + int ret; + + /* + * Do a binary search for the peak input level, and stop + * when that level is "trapped" between two adjacent DAC + * values. + * When invert is active, use the midpoint floor so that + * env->level ends up as env->low when the termination + * criteria below is fulfilled, and use the midpoint + * ceiling when invert is not active so that env->level + * ends up as env->high in that case. + */ + env->level = (env->high + env->low + !env->invert) / 2; + + if (env->high == env->low + 1) { + complete(&env->done); + return; + } + + /* Set a "safe" DAC level (if there is such a thing)... */ + ret = iio_write_channel_raw(env->dac, env->invert ? 0 : env->dac_max); + if (ret < 0) + goto err; + + /* ...clear the comparison result... */ + envelope_detector_comp_latch(env); + + /* ...set the real DAC level... */ + ret = iio_write_channel_raw(env->dac, env->level); + if (ret < 0) + goto err; + + /* ...and wait for a bit to see if the latch catches anything. */ + schedule_delayed_work(&env->comp_timeout, + msecs_to_jiffies(env->comp_interval)); + return; + +err: + env->level = ret; + complete(&env->done); +} + +static void envelope_detector_timeout(struct work_struct *work) +{ + struct envelope *env = container_of(work, struct envelope, + comp_timeout.work); + + /* Adjust low/high depending on the latch content... */ + if (!envelope_detector_comp_latch(env) ^ !env->invert) + env->low = env->level; + else + env->high = env->level; + + /* ...and continue the search. */ + envelope_detector_setup_compare(env); +} + +static int envelope_detector_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct envelope *env = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* + * When invert is active, start with high=max+1 and low=0 + * since we will end up with the low value when the + * termination criteria is fulfilled (rounding down). And + * start with high=max and low=-1 when invert is not active + * since we will end up with the high value in that case. + * This ensures that the returned value in both cases are + * in the same range as the DAC and is a value that has not + * triggered the comparator. + */ + mutex_lock(&env->read_lock); + env->high = env->dac_max + env->invert; + env->low = -1 + env->invert; + envelope_detector_setup_compare(env); + wait_for_completion(&env->done); + if (env->level < 0) { + ret = env->level; + goto err_unlock; + } + *val = env->invert ? env->dac_max - env->level : env->level; + mutex_unlock(&env->read_lock); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + return iio_read_channel_scale(env->dac, val, val2); + } + + return -EINVAL; + +err_unlock: + mutex_unlock(&env->read_lock); + return ret; +} + +static ssize_t envelope_show_invert(struct iio_dev *indio_dev, + uintptr_t private, + struct iio_chan_spec const *ch, char *buf) +{ + struct envelope *env = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", env->invert); +} + +static ssize_t envelope_store_invert(struct iio_dev *indio_dev, + uintptr_t private, + struct iio_chan_spec const *ch, + const char *buf, size_t len) +{ + struct envelope *env = iio_priv(indio_dev); + unsigned long invert; + int ret; + u32 trigger; + + ret = kstrtoul(buf, 0, &invert); + if (ret < 0) + return ret; + if (invert > 1) + return -EINVAL; + + trigger = invert ? env->comp_irq_trigger_inv : env->comp_irq_trigger; + + mutex_lock(&env->read_lock); + if (invert != env->invert) + ret = irq_set_irq_type(env->comp_irq, trigger); + if (!ret) { + env->invert = invert; + ret = len; + } + mutex_unlock(&env->read_lock); + + return ret; +} + +static ssize_t envelope_show_comp_interval(struct iio_dev *indio_dev, + uintptr_t private, + struct iio_chan_spec const *ch, + char *buf) +{ + struct envelope *env = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", env->comp_interval); +} + +static ssize_t envelope_store_comp_interval(struct iio_dev *indio_dev, + uintptr_t private, + struct iio_chan_spec const *ch, + const char *buf, size_t len) +{ + struct envelope *env = iio_priv(indio_dev); + unsigned long interval; + int ret; + + ret = kstrtoul(buf, 0, &interval); + if (ret < 0) + return ret; + if (interval > 1000) + return -EINVAL; + + mutex_lock(&env->read_lock); + env->comp_interval = interval; + mutex_unlock(&env->read_lock); + + return len; +} + +static const struct iio_chan_spec_ext_info envelope_detector_ext_info[] = { + { .name = "invert", + .read = envelope_show_invert, + .write = envelope_store_invert, }, + { .name = "compare_interval", + .read = envelope_show_comp_interval, + .write = envelope_store_comp_interval, }, + { } +}; + +static const struct iio_chan_spec envelope_detector_iio_channel = { + .type = IIO_ALTVOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) + | BIT(IIO_CHAN_INFO_SCALE), + .ext_info = envelope_detector_ext_info, + .indexed = 1, +}; + +static const struct iio_info envelope_detector_info = { + .read_raw = &envelope_detector_read_raw, +}; + +static int envelope_detector_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct envelope *env; + enum iio_chan_type type; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*env)); + if (!indio_dev) + return -ENOMEM; + + platform_set_drvdata(pdev, indio_dev); + env = iio_priv(indio_dev); + env->comp_interval = 50; /* some sensible default? */ + + spin_lock_init(&env->comp_lock); + mutex_init(&env->read_lock); + init_completion(&env->done); + INIT_DELAYED_WORK(&env->comp_timeout, envelope_detector_timeout); + + indio_dev->name = dev_name(dev); + indio_dev->info = &envelope_detector_info; + indio_dev->channels = &envelope_detector_iio_channel; + indio_dev->num_channels = 1; + + env->dac = devm_iio_channel_get(dev, "dac"); + if (IS_ERR(env->dac)) + return dev_err_probe(dev, PTR_ERR(env->dac), + "failed to get dac input channel\n"); + + env->comp_irq = platform_get_irq_byname(pdev, "comp"); + if (env->comp_irq < 0) + return env->comp_irq; + + ret = devm_request_irq(dev, env->comp_irq, envelope_detector_comp_isr, + 0, "envelope-detector", env); + if (ret) + return dev_err_probe(dev, ret, "failed to request interrupt\n"); + + env->comp_irq_trigger = irq_get_trigger_type(env->comp_irq); + if (env->comp_irq_trigger & IRQF_TRIGGER_RISING) + env->comp_irq_trigger_inv |= IRQF_TRIGGER_FALLING; + if (env->comp_irq_trigger & IRQF_TRIGGER_FALLING) + env->comp_irq_trigger_inv |= IRQF_TRIGGER_RISING; + if (env->comp_irq_trigger & IRQF_TRIGGER_HIGH) + env->comp_irq_trigger_inv |= IRQF_TRIGGER_LOW; + if (env->comp_irq_trigger & IRQF_TRIGGER_LOW) + env->comp_irq_trigger_inv |= IRQF_TRIGGER_HIGH; + + ret = iio_get_channel_type(env->dac, &type); + if (ret < 0) + return ret; + + if (type != IIO_VOLTAGE) { + dev_err(dev, "dac is of the wrong type\n"); + return -EINVAL; + } + + ret = iio_read_max_channel_raw(env->dac, &env->dac_max); + if (ret < 0) { + dev_err(dev, "dac does not indicate its raw maximum value\n"); + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id envelope_detector_match[] = { + { .compatible = "axentia,tse850-envelope-detector", }, + { } +}; +MODULE_DEVICE_TABLE(of, envelope_detector_match); + +static struct platform_driver envelope_detector_driver = { + .probe = envelope_detector_probe, + .driver = { + .name = "iio-envelope-detector", + .of_match_table = envelope_detector_match, + }, +}; +module_platform_driver(envelope_detector_driver); + +MODULE_DESCRIPTION("Envelope detector using a DAC and a comparator"); +MODULE_AUTHOR("Peter Rosin "); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_CONSUMER"); diff --git a/drivers/iio/adc/ep93xx_adc.c b/drivers/iio/adc/ep93xx_adc.c new file mode 100644 index 000000000..a3e9c697e --- /dev/null +++ b/drivers/iio/adc/ep93xx_adc.c @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for ADC module on the Cirrus Logic EP93xx series of SoCs + * + * Copyright (C) 2015 Alexander Sverdlin + * + * The driver uses polling to get the conversion status. According to EP93xx + * datasheets, reading ADCResult register starts the conversion, but user is also + * responsible for ensuring that delay between adjacent conversion triggers is + * long enough so that maximum allowed conversion rate is not exceeded. This + * basically renders IRQ mode unusable. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * This code could benefit from real HR Timers, but jiffy granularity would + * lower ADC conversion rate down to CONFIG_HZ, so we fallback to busy wait + * in such case. + * + * HR Timers-based version loads CPU only up to 10% during back to back ADC + * conversion, while busy wait-based version consumes whole CPU power. + */ +#ifdef CONFIG_HIGH_RES_TIMERS +#define ep93xx_adc_delay(usmin, usmax) usleep_range(usmin, usmax) +#else +#define ep93xx_adc_delay(usmin, usmax) udelay(usmin) +#endif + +#define EP93XX_ADC_RESULT 0x08 +#define EP93XX_ADC_SDR BIT(31) +#define EP93XX_ADC_SWITCH 0x18 +#define EP93XX_ADC_SW_LOCK 0x20 + +struct ep93xx_adc_priv { + struct clk *clk; + void __iomem *base; + int lastch; + struct mutex lock; +}; + +#define EP93XX_ADC_CH(index, dname, swcfg) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = index, \ + .address = swcfg, \ + .datasheet_name = dname, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ +} + +/* + * Numbering scheme for channels 0..4 is defined in EP9301 and EP9302 datasheets. + * EP9307, EP9312 and EP9312 have 3 channels more (total 8), but the numbering is + * not defined. So the last three are numbered randomly, let's say. + */ +static const struct iio_chan_spec ep93xx_adc_channels[8] = { + EP93XX_ADC_CH(0, "YM", 0x608), + EP93XX_ADC_CH(1, "SXP", 0x680), + EP93XX_ADC_CH(2, "SXM", 0x640), + EP93XX_ADC_CH(3, "SYP", 0x620), + EP93XX_ADC_CH(4, "SYM", 0x610), + EP93XX_ADC_CH(5, "XP", 0x601), + EP93XX_ADC_CH(6, "XM", 0x602), + EP93XX_ADC_CH(7, "YP", 0x604), +}; + +static int ep93xx_read_raw(struct iio_dev *iiodev, + struct iio_chan_spec const *channel, int *value, + int *shift, long mask) +{ + struct ep93xx_adc_priv *priv = iio_priv(iiodev); + unsigned long timeout; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&priv->lock); + if (priv->lastch != channel->channel) { + priv->lastch = channel->channel; + /* + * Switch register is software-locked, unlocking must be + * immediately followed by write + */ + local_irq_disable(); + writel_relaxed(0xAA, priv->base + EP93XX_ADC_SW_LOCK); + writel_relaxed(channel->address, + priv->base + EP93XX_ADC_SWITCH); + local_irq_enable(); + /* + * Settling delay depends on module clock and could be + * 2ms or 500us + */ + ep93xx_adc_delay(2000, 2000); + } + /* Start the conversion, eventually discarding old result */ + readl_relaxed(priv->base + EP93XX_ADC_RESULT); + /* Ensure maximum conversion rate is not exceeded */ + ep93xx_adc_delay(DIV_ROUND_UP(1000000, 925), + DIV_ROUND_UP(1000000, 925)); + /* At this point conversion must be completed, but anyway... */ + ret = IIO_VAL_INT; + timeout = jiffies + msecs_to_jiffies(1) + 1; + while (1) { + u32 t; + + t = readl_relaxed(priv->base + EP93XX_ADC_RESULT); + if (t & EP93XX_ADC_SDR) { + *value = sign_extend32(t, 15); + break; + } + + if (time_after(jiffies, timeout)) { + dev_err(&iiodev->dev, "Conversion timeout\n"); + ret = -ETIMEDOUT; + break; + } + + cpu_relax(); + } + mutex_unlock(&priv->lock); + return ret; + + case IIO_CHAN_INFO_OFFSET: + /* According to datasheet, range is -25000..25000 */ + *value = 25000; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + /* Typical supply voltage is 3.3v */ + *value = (1ULL << 32) * 3300 / 50000; + *shift = 32; + return IIO_VAL_FRACTIONAL_LOG2; + } + + return -EINVAL; +} + +static const struct iio_info ep93xx_adc_info = { + .read_raw = ep93xx_read_raw, +}; + +static int ep93xx_adc_probe(struct platform_device *pdev) +{ + int ret; + struct iio_dev *iiodev; + struct ep93xx_adc_priv *priv; + struct clk *pclk; + + iiodev = devm_iio_device_alloc(&pdev->dev, sizeof(*priv)); + if (!iiodev) + return -ENOMEM; + priv = iio_priv(iiodev); + + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + iiodev->name = dev_name(&pdev->dev); + iiodev->modes = INDIO_DIRECT_MODE; + iiodev->info = &ep93xx_adc_info; + iiodev->num_channels = ARRAY_SIZE(ep93xx_adc_channels); + iiodev->channels = ep93xx_adc_channels; + + priv->lastch = -1; + mutex_init(&priv->lock); + + platform_set_drvdata(pdev, iiodev); + + priv->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(priv->clk)) { + dev_err(&pdev->dev, "Cannot obtain clock\n"); + return PTR_ERR(priv->clk); + } + + pclk = clk_get_parent(priv->clk); + if (!pclk) { + dev_warn(&pdev->dev, "Cannot obtain parent clock\n"); + } else { + /* + * This is actually a place for improvement: + * EP93xx ADC supports two clock divisors -- 4 and 16, + * resulting in conversion rates 3750 and 925 samples per second + * with 500us or 2ms settling time respectively. + * One might find this interesting enough to be configurable. + */ + ret = clk_set_rate(priv->clk, clk_get_rate(pclk) / 16); + if (ret) + dev_warn(&pdev->dev, "Cannot set clock rate\n"); + /* + * We can tolerate rate setting failure because the module should + * work in any case. + */ + } + + ret = clk_prepare_enable(priv->clk); + if (ret) { + dev_err(&pdev->dev, "Cannot enable clock\n"); + return ret; + } + + ret = iio_device_register(iiodev); + if (ret) + clk_disable_unprepare(priv->clk); + + return ret; +} + +static void ep93xx_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *iiodev = platform_get_drvdata(pdev); + struct ep93xx_adc_priv *priv = iio_priv(iiodev); + + iio_device_unregister(iiodev); + clk_disable_unprepare(priv->clk); +} + +static const struct of_device_id ep93xx_adc_of_ids[] = { + { .compatible = "cirrus,ep9301-adc" }, + { } +}; +MODULE_DEVICE_TABLE(of, ep93xx_adc_of_ids); + +static struct platform_driver ep93xx_adc_driver = { + .driver = { + .name = "ep93xx-adc", + .of_match_table = ep93xx_adc_of_ids, + }, + .probe = ep93xx_adc_probe, + .remove = ep93xx_adc_remove, +}; +module_platform_driver(ep93xx_adc_driver); + +MODULE_AUTHOR("Alexander Sverdlin "); +MODULE_DESCRIPTION("Cirrus Logic EP93XX ADC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ep93xx-adc"); diff --git a/drivers/iio/adc/exynos_adc.c b/drivers/iio/adc/exynos_adc.c new file mode 100644 index 000000000..c1a0061b1 --- /dev/null +++ b/drivers/iio/adc/exynos_adc.c @@ -0,0 +1,721 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * exynos_adc.c - Support for ADC in EXYNOS SoCs + * + * 8 ~ 10 channel, 10/12-bit ADC + * + * Copyright (C) 2013 Naveen Krishna Chatradhi + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* S3C/EXYNOS4412/5250 ADC_V1 registers definitions */ +#define ADC_V1_CON(x) ((x) + 0x00) +#define ADC_V1_DLY(x) ((x) + 0x08) +#define ADC_V1_DATX(x) ((x) + 0x0C) +#define ADC_V1_DATY(x) ((x) + 0x10) +#define ADC_V1_UPDN(x) ((x) + 0x14) +#define ADC_V1_INTCLR(x) ((x) + 0x18) +#define ADC_V1_MUX(x) ((x) + 0x1c) + +/* Future ADC_V2 registers definitions */ +#define ADC_V2_CON1(x) ((x) + 0x00) +#define ADC_V2_CON2(x) ((x) + 0x04) +#define ADC_V2_STAT(x) ((x) + 0x08) +#define ADC_V2_INT_EN(x) ((x) + 0x10) +#define ADC_V2_INT_ST(x) ((x) + 0x14) +#define ADC_V2_VER(x) ((x) + 0x20) + +/* Bit definitions for ADC_V1 */ +#define ADC_V1_CON_RES (1u << 16) +#define ADC_V1_CON_PRSCEN (1u << 14) +#define ADC_V1_CON_PRSCLV(x) (((x) & 0xFF) << 6) +#define ADC_V1_CON_STANDBY (1u << 2) + +/* Bit definitions for S3C2410 / S3C6410 ADC */ +#define ADC_S3C2410_CON_SELMUX(x) (((x) & 7) << 3) + +/* ADCTSC Register Bits */ +#define ADC_S3C2443_TSC_UD_SEN (1u << 8) +#define ADC_S3C2410_TSC_YM_SEN (1u << 7) +#define ADC_S3C2410_TSC_YP_SEN (1u << 6) +#define ADC_S3C2410_TSC_XM_SEN (1u << 5) +#define ADC_S3C2410_TSC_XP_SEN (1u << 4) +#define ADC_S3C2410_TSC_XY_PST(x) (((x) & 0x3) << 0) + +#define ADC_TSC_WAIT4INT (ADC_S3C2410_TSC_YM_SEN | \ + ADC_S3C2410_TSC_YP_SEN | \ + ADC_S3C2410_TSC_XP_SEN | \ + ADC_S3C2410_TSC_XY_PST(3)) + +/* Bit definitions for ADC_V2 */ +#define ADC_V2_CON1_SOFT_RESET (1u << 2) + +#define ADC_V2_CON2_OSEL (1u << 10) +#define ADC_V2_CON2_ESEL (1u << 9) +#define ADC_V2_CON2_HIGHF (1u << 8) +#define ADC_V2_CON2_C_TIME(x) (((x) & 7) << 4) +#define ADC_V2_CON2_ACH_SEL(x) (((x) & 0xF) << 0) +#define ADC_V2_CON2_ACH_MASK 0xF + +#define MAX_ADC_V2_CHANNELS 10 +#define MAX_ADC_V1_CHANNELS 8 +#define MAX_EXYNOS3250_ADC_CHANNELS 2 +#define MAX_EXYNOS4212_ADC_CHANNELS 4 +#define MAX_S5PV210_ADC_CHANNELS 10 + +/* Bit definitions common for ADC_V1 and ADC_V2 */ +#define ADC_CON_EN_START (1u << 0) +#define ADC_CON_EN_START_MASK (0x3 << 0) +#define ADC_DATX_PRESSED (1u << 15) +#define ADC_DATX_MASK 0xFFF +#define ADC_DATY_MASK 0xFFF + +#define EXYNOS_ADC_TIMEOUT (msecs_to_jiffies(100)) + +#define EXYNOS_ADCV1_PHY_OFFSET 0x0718 +#define EXYNOS_ADCV2_PHY_OFFSET 0x0720 + +struct exynos_adc { + struct exynos_adc_data *data; + struct device *dev; + void __iomem *regs; + struct regmap *pmu_map; + struct clk *clk; + struct clk *sclk; + unsigned int irq; + struct regulator *vdd; + + struct completion completion; + + u32 value; + unsigned int version; + + /* + * Lock to protect from potential concurrent access to the + * completion callback during a manual conversion. For this driver + * a wait-callback is used to wait for the conversion result, + * so in the meantime no other read request (or conversion start) + * must be performed, otherwise it would interfere with the + * current conversion result. + */ + struct mutex lock; +}; + +struct exynos_adc_data { + int num_channels; + bool needs_sclk; + bool needs_adc_phy; + int phy_offset; + u32 mask; + + void (*init_hw)(struct exynos_adc *info); + void (*exit_hw)(struct exynos_adc *info); + void (*clear_irq)(struct exynos_adc *info); + void (*start_conv)(struct exynos_adc *info, unsigned long addr); +}; + +static void exynos_adc_unprepare_clk(struct exynos_adc *info) +{ + if (info->data->needs_sclk) + clk_unprepare(info->sclk); + clk_unprepare(info->clk); +} + +static int exynos_adc_prepare_clk(struct exynos_adc *info) +{ + int ret; + + ret = clk_prepare(info->clk); + if (ret) { + dev_err(info->dev, "failed preparing adc clock: %d\n", ret); + return ret; + } + + if (info->data->needs_sclk) { + ret = clk_prepare(info->sclk); + if (ret) { + clk_unprepare(info->clk); + dev_err(info->dev, + "failed preparing sclk_adc clock: %d\n", ret); + return ret; + } + } + + return 0; +} + +static void exynos_adc_disable_clk(struct exynos_adc *info) +{ + if (info->data->needs_sclk) + clk_disable(info->sclk); + clk_disable(info->clk); +} + +static int exynos_adc_enable_clk(struct exynos_adc *info) +{ + int ret; + + ret = clk_enable(info->clk); + if (ret) { + dev_err(info->dev, "failed enabling adc clock: %d\n", ret); + return ret; + } + + if (info->data->needs_sclk) { + ret = clk_enable(info->sclk); + if (ret) { + clk_disable(info->clk); + dev_err(info->dev, + "failed enabling sclk_adc clock: %d\n", ret); + return ret; + } + } + + return 0; +} + +static void exynos_adc_v1_init_hw(struct exynos_adc *info) +{ + u32 con1; + + if (info->data->needs_adc_phy) + regmap_write(info->pmu_map, info->data->phy_offset, 1); + + /* set default prescaler values and Enable prescaler */ + con1 = ADC_V1_CON_PRSCLV(49) | ADC_V1_CON_PRSCEN; + + /* Enable 12-bit ADC resolution */ + con1 |= ADC_V1_CON_RES; + writel(con1, ADC_V1_CON(info->regs)); + + /* set touchscreen delay */ + writel(10000, ADC_V1_DLY(info->regs)); +} + +static void exynos_adc_v1_exit_hw(struct exynos_adc *info) +{ + u32 con; + + if (info->data->needs_adc_phy) + regmap_write(info->pmu_map, info->data->phy_offset, 0); + + con = readl(ADC_V1_CON(info->regs)); + con |= ADC_V1_CON_STANDBY; + writel(con, ADC_V1_CON(info->regs)); +} + +static void exynos_adc_v1_clear_irq(struct exynos_adc *info) +{ + writel(1, ADC_V1_INTCLR(info->regs)); +} + +static void exynos_adc_v1_start_conv(struct exynos_adc *info, + unsigned long addr) +{ + u32 con1; + + writel(addr, ADC_V1_MUX(info->regs)); + + con1 = readl(ADC_V1_CON(info->regs)); + writel(con1 | ADC_CON_EN_START, ADC_V1_CON(info->regs)); +} + +/* Exynos4212 and 4412 is like ADCv1 but with four channels only */ +static const struct exynos_adc_data exynos4212_adc_data = { + .num_channels = MAX_EXYNOS4212_ADC_CHANNELS, + .mask = ADC_DATX_MASK, /* 12 bit ADC resolution */ + .needs_adc_phy = true, + .phy_offset = EXYNOS_ADCV1_PHY_OFFSET, + + .init_hw = exynos_adc_v1_init_hw, + .exit_hw = exynos_adc_v1_exit_hw, + .clear_irq = exynos_adc_v1_clear_irq, + .start_conv = exynos_adc_v1_start_conv, +}; + +static const struct exynos_adc_data exynos_adc_v1_data = { + .num_channels = MAX_ADC_V1_CHANNELS, + .mask = ADC_DATX_MASK, /* 12 bit ADC resolution */ + .needs_adc_phy = true, + .phy_offset = EXYNOS_ADCV1_PHY_OFFSET, + + .init_hw = exynos_adc_v1_init_hw, + .exit_hw = exynos_adc_v1_exit_hw, + .clear_irq = exynos_adc_v1_clear_irq, + .start_conv = exynos_adc_v1_start_conv, +}; + +static const struct exynos_adc_data exynos_adc_s5pv210_data = { + .num_channels = MAX_S5PV210_ADC_CHANNELS, + .mask = ADC_DATX_MASK, /* 12 bit ADC resolution */ + + .init_hw = exynos_adc_v1_init_hw, + .exit_hw = exynos_adc_v1_exit_hw, + .clear_irq = exynos_adc_v1_clear_irq, + .start_conv = exynos_adc_v1_start_conv, +}; + +static void exynos_adc_s3c64xx_start_conv(struct exynos_adc *info, + unsigned long addr) +{ + u32 con1; + + con1 = readl(ADC_V1_CON(info->regs)); + con1 &= ~ADC_S3C2410_CON_SELMUX(0x7); + con1 |= ADC_S3C2410_CON_SELMUX(addr); + writel(con1 | ADC_CON_EN_START, ADC_V1_CON(info->regs)); +} + +static struct exynos_adc_data const exynos_adc_s3c64xx_data = { + .num_channels = MAX_ADC_V1_CHANNELS, + .mask = ADC_DATX_MASK, /* 12 bit ADC resolution */ + + .init_hw = exynos_adc_v1_init_hw, + .exit_hw = exynos_adc_v1_exit_hw, + .clear_irq = exynos_adc_v1_clear_irq, + .start_conv = exynos_adc_s3c64xx_start_conv, +}; + +static void exynos_adc_v2_init_hw(struct exynos_adc *info) +{ + u32 con1, con2; + + if (info->data->needs_adc_phy) + regmap_write(info->pmu_map, info->data->phy_offset, 1); + + con1 = ADC_V2_CON1_SOFT_RESET; + writel(con1, ADC_V2_CON1(info->regs)); + + con2 = ADC_V2_CON2_OSEL | ADC_V2_CON2_ESEL | + ADC_V2_CON2_HIGHF | ADC_V2_CON2_C_TIME(0); + writel(con2, ADC_V2_CON2(info->regs)); + + /* Enable interrupts */ + writel(1, ADC_V2_INT_EN(info->regs)); +} + +static void exynos_adc_v2_exit_hw(struct exynos_adc *info) +{ + u32 con; + + if (info->data->needs_adc_phy) + regmap_write(info->pmu_map, info->data->phy_offset, 0); + + con = readl(ADC_V2_CON1(info->regs)); + con &= ~ADC_CON_EN_START; + writel(con, ADC_V2_CON1(info->regs)); +} + +static void exynos_adc_v2_clear_irq(struct exynos_adc *info) +{ + writel(1, ADC_V2_INT_ST(info->regs)); +} + +static void exynos_adc_v2_start_conv(struct exynos_adc *info, + unsigned long addr) +{ + u32 con1, con2; + + con2 = readl(ADC_V2_CON2(info->regs)); + con2 &= ~ADC_V2_CON2_ACH_MASK; + con2 |= ADC_V2_CON2_ACH_SEL(addr); + writel(con2, ADC_V2_CON2(info->regs)); + + con1 = readl(ADC_V2_CON1(info->regs)); + writel(con1 | ADC_CON_EN_START, ADC_V2_CON1(info->regs)); +} + +static const struct exynos_adc_data exynos_adc_v2_data = { + .num_channels = MAX_ADC_V2_CHANNELS, + .mask = ADC_DATX_MASK, /* 12 bit ADC resolution */ + .needs_adc_phy = true, + .phy_offset = EXYNOS_ADCV2_PHY_OFFSET, + + .init_hw = exynos_adc_v2_init_hw, + .exit_hw = exynos_adc_v2_exit_hw, + .clear_irq = exynos_adc_v2_clear_irq, + .start_conv = exynos_adc_v2_start_conv, +}; + +static const struct exynos_adc_data exynos3250_adc_data = { + .num_channels = MAX_EXYNOS3250_ADC_CHANNELS, + .mask = ADC_DATX_MASK, /* 12 bit ADC resolution */ + .needs_sclk = true, + .needs_adc_phy = true, + .phy_offset = EXYNOS_ADCV1_PHY_OFFSET, + + .init_hw = exynos_adc_v2_init_hw, + .exit_hw = exynos_adc_v2_exit_hw, + .clear_irq = exynos_adc_v2_clear_irq, + .start_conv = exynos_adc_v2_start_conv, +}; + +static void exynos_adc_exynos7_init_hw(struct exynos_adc *info) +{ + u32 con1, con2; + + con1 = ADC_V2_CON1_SOFT_RESET; + writel(con1, ADC_V2_CON1(info->regs)); + + con2 = readl(ADC_V2_CON2(info->regs)); + con2 &= ~ADC_V2_CON2_C_TIME(7); + con2 |= ADC_V2_CON2_C_TIME(0); + writel(con2, ADC_V2_CON2(info->regs)); + + /* Enable interrupts */ + writel(1, ADC_V2_INT_EN(info->regs)); +} + +static const struct exynos_adc_data exynos7_adc_data = { + .num_channels = MAX_ADC_V1_CHANNELS, + .mask = ADC_DATX_MASK, /* 12 bit ADC resolution */ + + .init_hw = exynos_adc_exynos7_init_hw, + .exit_hw = exynos_adc_v2_exit_hw, + .clear_irq = exynos_adc_v2_clear_irq, + .start_conv = exynos_adc_v2_start_conv, +}; + +static const struct of_device_id exynos_adc_match[] = { + { + .compatible = "samsung,s3c6410-adc", + .data = &exynos_adc_s3c64xx_data, + }, { + .compatible = "samsung,s5pv210-adc", + .data = &exynos_adc_s5pv210_data, + }, { + .compatible = "samsung,exynos4212-adc", + .data = &exynos4212_adc_data, + }, { + .compatible = "samsung,exynos-adc-v1", + .data = &exynos_adc_v1_data, + }, { + .compatible = "samsung,exynos-adc-v2", + .data = &exynos_adc_v2_data, + }, { + .compatible = "samsung,exynos3250-adc", + .data = &exynos3250_adc_data, + }, { + .compatible = "samsung,exynos7-adc", + .data = &exynos7_adc_data, + }, + { } +}; +MODULE_DEVICE_TABLE(of, exynos_adc_match); + +static struct exynos_adc_data *exynos_adc_get_data(struct platform_device *pdev) +{ + const struct of_device_id *match; + + match = of_match_node(exynos_adc_match, pdev->dev.of_node); + return (struct exynos_adc_data *)match->data; +} + +static int exynos_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct exynos_adc *info = iio_priv(indio_dev); + unsigned long time_left; + int ret; + + if (mask == IIO_CHAN_INFO_SCALE) { + ret = regulator_get_voltage(info->vdd); + if (ret < 0) + return ret; + + /* Regulator voltage is in uV, but need mV */ + *val = ret / 1000; + *val2 = info->data->mask; + + return IIO_VAL_FRACTIONAL; + } else if (mask != IIO_CHAN_INFO_RAW) { + return -EINVAL; + } + + mutex_lock(&info->lock); + reinit_completion(&info->completion); + + /* Select the channel to be used and Trigger conversion */ + if (info->data->start_conv) + info->data->start_conv(info, chan->address); + + time_left = wait_for_completion_timeout(&info->completion, + EXYNOS_ADC_TIMEOUT); + if (time_left == 0) { + dev_warn(&indio_dev->dev, "Conversion timed out! Resetting\n"); + if (info->data->init_hw) + info->data->init_hw(info); + ret = -ETIMEDOUT; + } else { + *val = info->value; + *val2 = 0; + ret = IIO_VAL_INT; + } + + mutex_unlock(&info->lock); + + return ret; +} + +static irqreturn_t exynos_adc_isr(int irq, void *dev_id) +{ + struct exynos_adc *info = dev_id; + u32 mask = info->data->mask; + + /* Read value */ + info->value = readl(ADC_V1_DATX(info->regs)) & mask; + + /* clear irq */ + if (info->data->clear_irq) + info->data->clear_irq(info); + + complete(&info->completion); + + return IRQ_HANDLED; +} + +static int exynos_adc_reg_access(struct iio_dev *indio_dev, + unsigned reg, unsigned writeval, + unsigned *readval) +{ + struct exynos_adc *info = iio_priv(indio_dev); + + if (readval == NULL) + return -EINVAL; + + *readval = readl(info->regs + reg); + + return 0; +} + +static const struct iio_info exynos_adc_iio_info = { + .read_raw = &exynos_read_raw, + .debugfs_reg_access = &exynos_adc_reg_access, +}; + +#define ADC_CHANNEL(_index, _id) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _index, \ + .address = _index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = _id, \ +} + +static const struct iio_chan_spec exynos_adc_iio_channels[] = { + ADC_CHANNEL(0, "adc0"), + ADC_CHANNEL(1, "adc1"), + ADC_CHANNEL(2, "adc2"), + ADC_CHANNEL(3, "adc3"), + ADC_CHANNEL(4, "adc4"), + ADC_CHANNEL(5, "adc5"), + ADC_CHANNEL(6, "adc6"), + ADC_CHANNEL(7, "adc7"), + ADC_CHANNEL(8, "adc8"), + ADC_CHANNEL(9, "adc9"), +}; + +static int exynos_adc_probe(struct platform_device *pdev) +{ + struct exynos_adc *info = NULL; + struct device *dev = &pdev->dev; + struct device_node *np = pdev->dev.of_node; + struct iio_dev *indio_dev = NULL; + int ret; + int irq; + + indio_dev = devm_iio_device_alloc(dev, sizeof(struct exynos_adc)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + + info->data = exynos_adc_get_data(pdev); + if (!info->data) + return dev_err_probe(dev, -EINVAL, "failed getting exynos_adc_data\n"); + + info->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(info->regs)) + return PTR_ERR(info->regs); + + + if (info->data->needs_adc_phy) { + info->pmu_map = syscon_regmap_lookup_by_phandle(np, "samsung,syscon-phandle"); + if (IS_ERR(info->pmu_map)) + return dev_err_probe(dev, PTR_ERR(info->pmu_map), + "syscon regmap lookup failed.\n"); + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + info->irq = irq; + info->dev = dev; + + init_completion(&info->completion); + + info->clk = devm_clk_get(dev, "adc"); + if (IS_ERR(info->clk)) + return dev_err_probe(dev, PTR_ERR(info->clk), "failed getting clock\n"); + + if (info->data->needs_sclk) { + info->sclk = devm_clk_get(dev, "sclk"); + if (IS_ERR(info->sclk)) + return dev_err_probe(dev, PTR_ERR(info->sclk), + "failed getting sclk clock\n"); + } + + info->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(info->vdd)) + return dev_err_probe(dev, PTR_ERR(info->vdd), "failed getting regulator"); + + ret = regulator_enable(info->vdd); + if (ret) + return ret; + + ret = exynos_adc_prepare_clk(info); + if (ret) + goto err_disable_reg; + + ret = exynos_adc_enable_clk(info); + if (ret) + goto err_unprepare_clk; + + platform_set_drvdata(pdev, indio_dev); + + indio_dev->name = dev_name(dev); + indio_dev->info = &exynos_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = exynos_adc_iio_channels; + indio_dev->num_channels = info->data->num_channels; + + mutex_init(&info->lock); + + ret = request_irq(info->irq, exynos_adc_isr, 0, dev_name(dev), info); + if (ret < 0) { + dev_err(dev, "failed requesting irq, irq = %d\n", info->irq); + goto err_disable_clk; + } + + ret = iio_device_register(indio_dev); + if (ret) + goto err_irq; + + if (info->data->init_hw) + info->data->init_hw(info); + + ret = of_platform_populate(np, exynos_adc_match, NULL, &indio_dev->dev); + if (ret < 0) { + dev_err(dev, "failed adding child nodes\n"); + goto err_of_populate; + } + + return 0; + +err_of_populate: + of_platform_depopulate(&indio_dev->dev); + iio_device_unregister(indio_dev); +err_irq: + free_irq(info->irq, info); +err_disable_clk: + if (info->data->exit_hw) + info->data->exit_hw(info); + exynos_adc_disable_clk(info); +err_unprepare_clk: + exynos_adc_unprepare_clk(info); +err_disable_reg: + regulator_disable(info->vdd); + return ret; +} + +static void exynos_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct exynos_adc *info = iio_priv(indio_dev); + + of_platform_depopulate(&indio_dev->dev); + iio_device_unregister(indio_dev); + free_irq(info->irq, info); + if (info->data->exit_hw) + info->data->exit_hw(info); + exynos_adc_disable_clk(info); + exynos_adc_unprepare_clk(info); + regulator_disable(info->vdd); +} + +static int exynos_adc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct exynos_adc *info = iio_priv(indio_dev); + + if (info->data->exit_hw) + info->data->exit_hw(info); + exynos_adc_disable_clk(info); + regulator_disable(info->vdd); + + return 0; +} + +static int exynos_adc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct exynos_adc *info = iio_priv(indio_dev); + int ret; + + ret = regulator_enable(info->vdd); + if (ret) + return ret; + + ret = exynos_adc_enable_clk(info); + if (ret) + return ret; + + if (info->data->init_hw) + info->data->init_hw(info); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(exynos_adc_pm_ops, exynos_adc_suspend, + exynos_adc_resume); + +static struct platform_driver exynos_adc_driver = { + .probe = exynos_adc_probe, + .remove = exynos_adc_remove, + .driver = { + .name = "exynos-adc", + .of_match_table = exynos_adc_match, + .pm = pm_sleep_ptr(&exynos_adc_pm_ops), + }, +}; + +module_platform_driver(exynos_adc_driver); + +MODULE_AUTHOR("Naveen Krishna Chatradhi "); +MODULE_DESCRIPTION("Samsung EXYNOS5 ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/fsl-imx25-gcq.c b/drivers/iio/adc/fsl-imx25-gcq.c new file mode 100644 index 000000000..dc310ed61 --- /dev/null +++ b/drivers/iio/adc/fsl-imx25-gcq.c @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2014-2015 Pengutronix, Markus Pargmann + * + * This is the driver for the imx25 GCQ (Generic Conversion Queue) + * connected to the imx25 ADC. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MX25_GCQ_TIMEOUT (msecs_to_jiffies(2000)) + +static const char * const driver_name = "mx25-gcq"; + +enum mx25_gcq_cfgs { + MX25_CFG_XP = 0, + MX25_CFG_YP, + MX25_CFG_XN, + MX25_CFG_YN, + MX25_CFG_WIPER, + MX25_CFG_INAUX0, + MX25_CFG_INAUX1, + MX25_CFG_INAUX2, + MX25_NUM_CFGS, +}; + +struct mx25_gcq_priv { + struct regmap *regs; + struct completion completed; + struct clk *clk; + int irq; + struct regulator *vref[4]; + u32 channel_vref_mv[MX25_NUM_CFGS]; + /* + * Lock to protect the device state during a potential concurrent + * read access from userspace. Reading a raw value requires a sequence + * of register writes, then a wait for a completion callback, + * and finally a register read, during which userspace could issue + * another read request. This lock protects a read access from + * occurring before another one has finished. + */ + struct mutex lock; +}; + +#define MX25_CQG_CHAN(chan, id) {\ + .type = IIO_VOLTAGE,\ + .indexed = 1,\ + .channel = chan,\ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE),\ + .datasheet_name = id,\ +} + +static const struct iio_chan_spec mx25_gcq_channels[MX25_NUM_CFGS] = { + MX25_CQG_CHAN(MX25_CFG_XP, "xp"), + MX25_CQG_CHAN(MX25_CFG_YP, "yp"), + MX25_CQG_CHAN(MX25_CFG_XN, "xn"), + MX25_CQG_CHAN(MX25_CFG_YN, "yn"), + MX25_CQG_CHAN(MX25_CFG_WIPER, "wiper"), + MX25_CQG_CHAN(MX25_CFG_INAUX0, "inaux0"), + MX25_CQG_CHAN(MX25_CFG_INAUX1, "inaux1"), + MX25_CQG_CHAN(MX25_CFG_INAUX2, "inaux2"), +}; + +static const char * const mx25_gcq_refp_names[] = { + [MX25_ADC_REFP_YP] = "yp", + [MX25_ADC_REFP_XP] = "xp", + [MX25_ADC_REFP_INT] = "int", + [MX25_ADC_REFP_EXT] = "ext", +}; + +static irqreturn_t mx25_gcq_irq(int irq, void *data) +{ + struct mx25_gcq_priv *priv = data; + u32 stats; + + regmap_read(priv->regs, MX25_ADCQ_SR, &stats); + + if (stats & MX25_ADCQ_SR_EOQ) { + regmap_set_bits(priv->regs, MX25_ADCQ_MR, + MX25_ADCQ_MR_EOQ_IRQ); + complete(&priv->completed); + } + + /* Disable conversion queue run */ + regmap_clear_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_FQS); + + /* Acknowledge all possible irqs */ + regmap_write(priv->regs, MX25_ADCQ_SR, MX25_ADCQ_SR_FRR | + MX25_ADCQ_SR_FUR | MX25_ADCQ_SR_FOR | + MX25_ADCQ_SR_EOQ | MX25_ADCQ_SR_PD); + + return IRQ_HANDLED; +} + +static int mx25_gcq_get_raw_value(struct device *dev, + struct iio_chan_spec const *chan, + struct mx25_gcq_priv *priv, + int *val) +{ + long time_left; + u32 data; + + /* Setup the configuration we want to use */ + regmap_write(priv->regs, MX25_ADCQ_ITEM_7_0, + MX25_ADCQ_ITEM(0, chan->channel)); + + regmap_clear_bits(priv->regs, MX25_ADCQ_MR, MX25_ADCQ_MR_EOQ_IRQ); + + /* Trigger queue for one run */ + regmap_set_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_FQS); + + time_left = wait_for_completion_interruptible_timeout( + &priv->completed, MX25_GCQ_TIMEOUT); + if (time_left < 0) { + dev_err(dev, "ADC wait for measurement failed\n"); + return time_left; + } else if (time_left == 0) { + dev_err(dev, "ADC timed out\n"); + return -ETIMEDOUT; + } + + regmap_read(priv->regs, MX25_ADCQ_FIFO, &data); + + *val = MX25_ADCQ_FIFO_DATA(data); + + return IIO_VAL_INT; +} + +static int mx25_gcq_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct mx25_gcq_priv *priv = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&priv->lock); + ret = mx25_gcq_get_raw_value(&indio_dev->dev, chan, priv, val); + mutex_unlock(&priv->lock); + return ret; + + case IIO_CHAN_INFO_SCALE: + *val = priv->channel_vref_mv[chan->channel]; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } +} + +static const struct iio_info mx25_gcq_iio_info = { + .read_raw = mx25_gcq_read_raw, +}; + +static const struct regmap_config mx25_gcq_regconfig = { + .max_register = 0x5c, + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +static int mx25_gcq_ext_regulator_setup(struct device *dev, + struct mx25_gcq_priv *priv, u32 refp) +{ + char reg_name[12]; + int ret; + + if (priv->vref[refp]) + return 0; + + ret = snprintf(reg_name, sizeof(reg_name), "vref-%s", + mx25_gcq_refp_names[refp]); + if (ret < 0) + return ret; + + priv->vref[refp] = devm_regulator_get_optional(dev, reg_name); + if (IS_ERR(priv->vref[refp])) + return dev_err_probe(dev, PTR_ERR(priv->vref[refp]), + "Error, trying to use external voltage reference without a %s regulator.", + reg_name); + + return 0; +} + +static int mx25_gcq_setup_cfgs(struct platform_device *pdev, + struct mx25_gcq_priv *priv) +{ + struct device *dev = &pdev->dev; + int ret, i; + + /* + * Setup all configurations registers with a default conversion + * configuration for each input + */ + for (i = 0; i < MX25_NUM_CFGS; ++i) + regmap_write(priv->regs, MX25_ADCQ_CFG(i), + MX25_ADCQ_CFG_YPLL_OFF | + MX25_ADCQ_CFG_XNUR_OFF | + MX25_ADCQ_CFG_XPUL_OFF | + MX25_ADCQ_CFG_REFP_INT | + MX25_ADCQ_CFG_IN(i) | + MX25_ADCQ_CFG_REFN_NGND2); + + device_for_each_child_node_scoped(dev, child) { + u32 reg; + u32 refp = MX25_ADCQ_CFG_REFP_INT; + u32 refn = MX25_ADCQ_CFG_REFN_NGND2; + + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get reg property\n"); + + if (reg >= MX25_NUM_CFGS) + return dev_err_probe(dev, -EINVAL, + "reg value is greater than the number of available configuration registers\n"); + + fwnode_property_read_u32(child, "fsl,adc-refp", &refp); + fwnode_property_read_u32(child, "fsl,adc-refn", &refn); + + switch (refp) { + case MX25_ADC_REFP_EXT: + case MX25_ADC_REFP_XP: + case MX25_ADC_REFP_YP: + ret = mx25_gcq_ext_regulator_setup(&pdev->dev, priv, refp); + if (ret) + return ret; + priv->channel_vref_mv[reg] = + regulator_get_voltage(priv->vref[refp]); + /* Conversion from uV to mV */ + priv->channel_vref_mv[reg] /= 1000; + break; + case MX25_ADC_REFP_INT: + priv->channel_vref_mv[reg] = 2500; + break; + default: + return dev_err_probe(dev, -EINVAL, + "Invalid positive reference %d\n", refp); + } + + /* + * Shift the read values to the correct positions within the + * register. + */ + refp = MX25_ADCQ_CFG_REFP(refp); + refn = MX25_ADCQ_CFG_REFN(refn); + + if ((refp & MX25_ADCQ_CFG_REFP_MASK) != refp) + return dev_err_probe(dev, -EINVAL, + "Invalid fsl,adc-refp property value\n"); + + if ((refn & MX25_ADCQ_CFG_REFN_MASK) != refn) + return dev_err_probe(dev, -EINVAL, + "Invalid fsl,adc-refn property value\n"); + + regmap_update_bits(priv->regs, MX25_ADCQ_CFG(reg), + MX25_ADCQ_CFG_REFP_MASK | + MX25_ADCQ_CFG_REFN_MASK, + refp | refn); + } + regmap_set_bits(priv->regs, MX25_ADCQ_CR, + MX25_ADCQ_CR_FRST | MX25_ADCQ_CR_QRST); + + regmap_write(priv->regs, MX25_ADCQ_CR, + MX25_ADCQ_CR_PDMSK | MX25_ADCQ_CR_QSM_FQS); + + return 0; +} + +static void mx25_gcq_reg_disable(void *reg) +{ + regulator_disable(reg); +} + +/* Custom handling needed as this driver doesn't own the clock */ +static void mx25_gcq_clk_disable(void *clk) +{ + clk_disable_unprepare(clk); +} + +static int mx25_gcq_probe(struct platform_device *pdev) +{ + struct iio_dev *indio_dev; + struct mx25_gcq_priv *priv; + struct mx25_tsadc *tsadc = dev_get_drvdata(pdev->dev.parent); + struct device *dev = &pdev->dev; + void __iomem *mem; + int ret; + int i; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + priv = iio_priv(indio_dev); + + mem = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mem)) + return PTR_ERR(mem); + + priv->regs = devm_regmap_init_mmio(dev, mem, &mx25_gcq_regconfig); + if (IS_ERR(priv->regs)) + return dev_err_probe(dev, PTR_ERR(priv->regs), + "Failed to initialize regmap\n"); + + mutex_init(&priv->lock); + + init_completion(&priv->completed); + + ret = mx25_gcq_setup_cfgs(pdev, priv); + if (ret) + return ret; + + for (i = 0; i != 4; ++i) { + if (!priv->vref[i]) + continue; + + ret = regulator_enable(priv->vref[i]); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, mx25_gcq_reg_disable, + priv->vref[i]); + if (ret) + return ret; + } + + priv->clk = tsadc->clk; + ret = clk_prepare_enable(priv->clk); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable clock\n"); + + ret = devm_add_action_or_reset(dev, mx25_gcq_clk_disable, + priv->clk); + if (ret) + return ret; + + ret = platform_get_irq(pdev, 0); + if (ret < 0) + return ret; + + priv->irq = ret; + ret = devm_request_irq(dev, priv->irq, mx25_gcq_irq, 0, pdev->name, + priv); + if (ret) + return dev_err_probe(dev, ret, "Failed requesting IRQ\n"); + + indio_dev->channels = mx25_gcq_channels; + indio_dev->num_channels = ARRAY_SIZE(mx25_gcq_channels); + indio_dev->info = &mx25_gcq_iio_info; + indio_dev->name = driver_name; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to register iio device\n"); + + return 0; +} + +static const struct of_device_id mx25_gcq_ids[] = { + { .compatible = "fsl,imx25-gcq", }, + { } +}; +MODULE_DEVICE_TABLE(of, mx25_gcq_ids); + +static struct platform_driver mx25_gcq_driver = { + .driver = { + .name = "mx25-gcq", + .of_match_table = mx25_gcq_ids, + }, + .probe = mx25_gcq_probe, +}; +module_platform_driver(mx25_gcq_driver); + +MODULE_DESCRIPTION("ADC driver for Freescale mx25"); +MODULE_AUTHOR("Markus Pargmann "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/gehc-pmc-adc.c b/drivers/iio/adc/gehc-pmc-adc.c new file mode 100644 index 000000000..24193e941 --- /dev/null +++ b/drivers/iio/adc/gehc-pmc-adc.c @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * The GE HealthCare PMC ADC is a 16-Channel (Voltage and current), 16-Bit + * ADC with an I2C Interface. + * + * Copyright (C) 2024, GE HealthCare + * + * Authors: + * Herve Codina + */ +#include +#include +#include +#include +#include +#include +#include +#include + +struct pmc_adc { + struct i2c_client *client; +}; + +#define PMC_ADC_CMD_REQUEST_PROTOCOL_VERSION 0x01 +#define PMC_ADC_CMD_READ_VOLTAGE(_ch) (0x10 | (_ch)) +#define PMC_ADC_CMD_READ_CURRENT(_ch) (0x20 | (_ch)) + +#define PMC_ADC_VOLTAGE_CHANNEL(_ch, _ds_name) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_ch), \ + .address = PMC_ADC_CMD_READ_VOLTAGE(_ch), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ + .datasheet_name = (_ds_name), \ +} + +#define PMC_ADC_CURRENT_CHANNEL(_ch, _ds_name) { \ + .type = IIO_CURRENT, \ + .indexed = 1, \ + .channel = (_ch), \ + .address = PMC_ADC_CMD_READ_CURRENT(_ch), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ + .datasheet_name = (_ds_name), \ +} + +static const struct iio_chan_spec pmc_adc_channels[] = { + PMC_ADC_VOLTAGE_CHANNEL(0, "CH0_V"), + PMC_ADC_VOLTAGE_CHANNEL(1, "CH1_V"), + PMC_ADC_VOLTAGE_CHANNEL(2, "CH2_V"), + PMC_ADC_VOLTAGE_CHANNEL(3, "CH3_V"), + PMC_ADC_VOLTAGE_CHANNEL(4, "CH4_V"), + PMC_ADC_VOLTAGE_CHANNEL(5, "CH5_V"), + PMC_ADC_VOLTAGE_CHANNEL(6, "CH6_V"), + PMC_ADC_VOLTAGE_CHANNEL(7, "CH7_V"), + PMC_ADC_VOLTAGE_CHANNEL(8, "CH8_V"), + PMC_ADC_VOLTAGE_CHANNEL(9, "CH9_V"), + PMC_ADC_VOLTAGE_CHANNEL(10, "CH10_V"), + PMC_ADC_VOLTAGE_CHANNEL(11, "CH11_V"), + PMC_ADC_VOLTAGE_CHANNEL(12, "CH12_V"), + PMC_ADC_VOLTAGE_CHANNEL(13, "CH13_V"), + PMC_ADC_VOLTAGE_CHANNEL(14, "CH14_V"), + PMC_ADC_VOLTAGE_CHANNEL(15, "CH15_V"), + + PMC_ADC_CURRENT_CHANNEL(0, "CH0_I"), + PMC_ADC_CURRENT_CHANNEL(1, "CH1_I"), + PMC_ADC_CURRENT_CHANNEL(2, "CH2_I"), + PMC_ADC_CURRENT_CHANNEL(3, "CH3_I"), + PMC_ADC_CURRENT_CHANNEL(4, "CH4_I"), + PMC_ADC_CURRENT_CHANNEL(5, "CH5_I"), + PMC_ADC_CURRENT_CHANNEL(6, "CH6_I"), + PMC_ADC_CURRENT_CHANNEL(7, "CH7_I"), + PMC_ADC_CURRENT_CHANNEL(8, "CH8_I"), + PMC_ADC_CURRENT_CHANNEL(9, "CH9_I"), + PMC_ADC_CURRENT_CHANNEL(10, "CH10_I"), + PMC_ADC_CURRENT_CHANNEL(11, "CH11_I"), + PMC_ADC_CURRENT_CHANNEL(12, "CH12_I"), + PMC_ADC_CURRENT_CHANNEL(13, "CH13_I"), + PMC_ADC_CURRENT_CHANNEL(14, "CH14_I"), + PMC_ADC_CURRENT_CHANNEL(15, "CH15_I"), +}; + +static int pmc_adc_read_raw_ch(struct pmc_adc *pmc_adc, u8 cmd, int *val) +{ + s32 ret; + + ret = i2c_smbus_read_word_swapped(pmc_adc->client, cmd); + if (ret < 0) { + dev_err(&pmc_adc->client->dev, "i2c read word failed (%d)\n", ret); + return ret; + } + + *val = sign_extend32(ret, 15); + return 0; +} + +static int pmc_adc_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct pmc_adc *pmc_adc = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + /* Values are directly read in mV or mA */ + ret = pmc_adc_read_raw_ch(pmc_adc, chan->address, val); + if (ret) + return ret; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int pmc_adc_fwnode_xlate(struct iio_dev *indio_dev, + const struct fwnode_reference_args *iiospec) +{ + enum iio_chan_type expected_type; + unsigned int i; + + /* + * args[0]: Acquisition type (i.e. voltage or current) + * args[1]: PMC ADC channel number + */ + if (iiospec->nargs != 2) + return -EINVAL; + + switch (iiospec->args[0]) { + case GEHC_PMC_ADC_VOLTAGE: + expected_type = IIO_VOLTAGE; + break; + case GEHC_PMC_ADC_CURRENT: + expected_type = IIO_CURRENT; + break; + default: + dev_err(&indio_dev->dev, "Invalid channel type %llu\n", + iiospec->args[0]); + return -EINVAL; + } + + for (i = 0; i < indio_dev->num_channels; i++) + if (indio_dev->channels[i].type == expected_type && + indio_dev->channels[i].channel == iiospec->args[1]) + return i; + + dev_err(&indio_dev->dev, "Invalid channel type %llu number %llu\n", + iiospec->args[0], iiospec->args[1]); + return -EINVAL; +} + +static const struct iio_info pmc_adc_info = { + .read_raw = pmc_adc_read_raw, + .fwnode_xlate = pmc_adc_fwnode_xlate, +}; + +static const char *const pmc_adc_regulator_names[] = { + "vdd", + "vdda", + "vddio", + "vref", +}; + +static int pmc_adc_probe(struct i2c_client *client) +{ + struct iio_dev *indio_dev; + struct pmc_adc *pmc_adc; + struct clk *clk; + s32 val; + int ret; + + ret = devm_regulator_bulk_get_enable(&client->dev, ARRAY_SIZE(pmc_adc_regulator_names), + pmc_adc_regulator_names); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to get regulators\n"); + + clk = devm_clk_get_optional_enabled(&client->dev, "osc"); + if (IS_ERR(clk)) + return dev_err_probe(&client->dev, PTR_ERR(clk), "Failed to get osc clock\n"); + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*pmc_adc)); + if (!indio_dev) + return -ENOMEM; + + pmc_adc = iio_priv(indio_dev); + pmc_adc->client = client; + + val = i2c_smbus_read_byte_data(pmc_adc->client, PMC_ADC_CMD_REQUEST_PROTOCOL_VERSION); + if (val < 0) + return dev_err_probe(&client->dev, val, "Failed to get protocol version\n"); + + if (val != 0x01) + return dev_err_probe(&client->dev, -EINVAL, + "Unsupported protocol version 0x%02x\n", val); + + indio_dev->name = "pmc_adc"; + indio_dev->info = &pmc_adc_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = pmc_adc_channels; + indio_dev->num_channels = ARRAY_SIZE(pmc_adc_channels); + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct of_device_id pmc_adc_of_match[] = { + { .compatible = "gehc,pmc-adc"}, + { } +}; +MODULE_DEVICE_TABLE(of, pmc_adc_of_match); + +static const struct i2c_device_id pmc_adc_id_table[] = { + { .name = "pmc-adc" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pmc_adc_id_table); + +static struct i2c_driver pmc_adc_i2c_driver = { + .driver = { + .name = "pmc-adc", + .of_match_table = pmc_adc_of_match, + }, + .id_table = pmc_adc_id_table, + .probe = pmc_adc_probe, +}; + +module_i2c_driver(pmc_adc_i2c_driver); + +MODULE_AUTHOR("Herve Codina "); +MODULE_DESCRIPTION("GE HealthCare PMC ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/hi8435.c b/drivers/iio/adc/hi8435.c new file mode 100644 index 000000000..9fa63de4b --- /dev/null +++ b/drivers/iio/adc/hi8435.c @@ -0,0 +1,546 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Holt Integrated Circuits HI-8435 threshold detector driver + * + * Copyright (C) 2015 Zodiac Inflight Innovations + * Copyright (C) 2015 Cogent Embedded, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register offsets for HI-8435 */ +#define HI8435_CTRL_REG 0x02 +#define HI8435_PSEN_REG 0x04 +#define HI8435_TMDATA_REG 0x1E +#define HI8435_GOCENHYS_REG 0x3A +#define HI8435_SOCENHYS_REG 0x3C +#define HI8435_SO7_0_REG 0x10 +#define HI8435_SO15_8_REG 0x12 +#define HI8435_SO23_16_REG 0x14 +#define HI8435_SO31_24_REG 0x16 +#define HI8435_SO31_0_REG 0x78 + +#define HI8435_WRITE_OPCODE 0x00 +#define HI8435_READ_OPCODE 0x80 + +/* CTRL register bits */ +#define HI8435_CTRL_TEST 0x01 +#define HI8435_CTRL_SRST 0x02 + +struct hi8435_priv { + struct spi_device *spi; + struct mutex lock; + + unsigned long event_scan_mask; /* soft mask/unmask channels events */ + unsigned int event_prev_val; + + unsigned threshold_lo[2]; /* GND-Open and Supply-Open thresholds */ + unsigned threshold_hi[2]; /* GND-Open and Supply-Open thresholds */ + u8 reg_buffer[3] __aligned(IIO_DMA_MINALIGN); +}; + +static int hi8435_readb(struct hi8435_priv *priv, u8 reg, u8 *val) +{ + reg |= HI8435_READ_OPCODE; + return spi_write_then_read(priv->spi, ®, 1, val, 1); +} + +static int hi8435_readw(struct hi8435_priv *priv, u8 reg, u16 *val) +{ + int ret; + __be16 be_val; + + reg |= HI8435_READ_OPCODE; + ret = spi_write_then_read(priv->spi, ®, 1, &be_val, 2); + *val = be16_to_cpu(be_val); + + return ret; +} + +static int hi8435_readl(struct hi8435_priv *priv, u8 reg, u32 *val) +{ + int ret; + __be32 be_val; + + reg |= HI8435_READ_OPCODE; + ret = spi_write_then_read(priv->spi, ®, 1, &be_val, 4); + *val = be32_to_cpu(be_val); + + return ret; +} + +static int hi8435_writeb(struct hi8435_priv *priv, u8 reg, u8 val) +{ + priv->reg_buffer[0] = reg | HI8435_WRITE_OPCODE; + priv->reg_buffer[1] = val; + + return spi_write(priv->spi, priv->reg_buffer, 2); +} + +static int hi8435_writew(struct hi8435_priv *priv, u8 reg, u16 val) +{ + priv->reg_buffer[0] = reg | HI8435_WRITE_OPCODE; + priv->reg_buffer[1] = (val >> 8) & 0xff; + priv->reg_buffer[2] = val & 0xff; + + return spi_write(priv->spi, priv->reg_buffer, 3); +} + +static int hi8435_read_raw(struct iio_dev *idev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct hi8435_priv *priv = iio_priv(idev); + u32 tmp; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = hi8435_readl(priv, HI8435_SO31_0_REG, &tmp); + if (ret < 0) + return ret; + *val = !!(tmp & BIT(chan->channel)); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int hi8435_read_event_config(struct iio_dev *idev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct hi8435_priv *priv = iio_priv(idev); + + return !!(priv->event_scan_mask & BIT(chan->channel)); +} + +static int hi8435_write_event_config(struct iio_dev *idev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct hi8435_priv *priv = iio_priv(idev); + int ret; + u32 tmp; + + if (state) { + ret = hi8435_readl(priv, HI8435_SO31_0_REG, &tmp); + if (ret < 0) + return ret; + if (tmp & BIT(chan->channel)) + priv->event_prev_val |= BIT(chan->channel); + else + priv->event_prev_val &= ~BIT(chan->channel); + + priv->event_scan_mask |= BIT(chan->channel); + } else + priv->event_scan_mask &= ~BIT(chan->channel); + + return 0; +} + +static int hi8435_read_event_value(struct iio_dev *idev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct hi8435_priv *priv = iio_priv(idev); + int ret; + u8 mode, psen; + u16 reg; + + ret = hi8435_readb(priv, HI8435_PSEN_REG, &psen); + if (ret < 0) + return ret; + + /* Supply-Open or GND-Open sensing mode */ + mode = !!(psen & BIT(chan->channel / 8)); + + ret = hi8435_readw(priv, mode ? HI8435_SOCENHYS_REG : + HI8435_GOCENHYS_REG, ®); + if (ret < 0) + return ret; + + if (dir == IIO_EV_DIR_FALLING) + *val = ((reg & 0xff) - (reg >> 8)) / 2; + else if (dir == IIO_EV_DIR_RISING) + *val = ((reg & 0xff) + (reg >> 8)) / 2; + + return IIO_VAL_INT; +} + +static int hi8435_write_event_value(struct iio_dev *idev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct hi8435_priv *priv = iio_priv(idev); + int ret; + u8 mode, psen; + u16 reg; + + ret = hi8435_readb(priv, HI8435_PSEN_REG, &psen); + if (ret < 0) + return ret; + + /* Supply-Open or GND-Open sensing mode */ + mode = !!(psen & BIT(chan->channel / 8)); + + ret = hi8435_readw(priv, mode ? HI8435_SOCENHYS_REG : + HI8435_GOCENHYS_REG, ®); + if (ret < 0) + return ret; + + if (dir == IIO_EV_DIR_FALLING) { + /* falling threshold range 2..21V, hysteresis minimum 2V */ + if (val < 2 || val > 21 || (val + 2) > priv->threshold_hi[mode]) + return -EINVAL; + + if (val == priv->threshold_lo[mode]) + return 0; + + priv->threshold_lo[mode] = val; + + /* hysteresis must not be odd */ + if ((priv->threshold_hi[mode] - priv->threshold_lo[mode]) % 2) + priv->threshold_hi[mode]--; + } else if (dir == IIO_EV_DIR_RISING) { + /* rising threshold range 3..22V, hysteresis minimum 2V */ + if (val < 3 || val > 22 || val < (priv->threshold_lo[mode] + 2)) + return -EINVAL; + + if (val == priv->threshold_hi[mode]) + return 0; + + priv->threshold_hi[mode] = val; + + /* hysteresis must not be odd */ + if ((priv->threshold_hi[mode] - priv->threshold_lo[mode]) % 2) + priv->threshold_lo[mode]++; + } + + /* program thresholds */ + mutex_lock(&priv->lock); + + ret = hi8435_readw(priv, mode ? HI8435_SOCENHYS_REG : + HI8435_GOCENHYS_REG, ®); + if (ret < 0) { + mutex_unlock(&priv->lock); + return ret; + } + + /* hysteresis */ + reg = priv->threshold_hi[mode] - priv->threshold_lo[mode]; + reg <<= 8; + /* threshold center */ + reg |= (priv->threshold_hi[mode] + priv->threshold_lo[mode]); + + ret = hi8435_writew(priv, mode ? HI8435_SOCENHYS_REG : + HI8435_GOCENHYS_REG, reg); + + mutex_unlock(&priv->lock); + + return ret; +} + +static int hi8435_debugfs_reg_access(struct iio_dev *idev, + unsigned reg, unsigned writeval, + unsigned *readval) +{ + struct hi8435_priv *priv = iio_priv(idev); + int ret; + u8 val; + + if (readval != NULL) { + ret = hi8435_readb(priv, reg, &val); + *readval = val; + } else { + val = (u8)writeval; + ret = hi8435_writeb(priv, reg, val); + } + + return ret; +} + +static const struct iio_event_spec hi8435_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static int hi8435_get_sensing_mode(struct iio_dev *idev, + const struct iio_chan_spec *chan) +{ + struct hi8435_priv *priv = iio_priv(idev); + int ret; + u8 reg; + + ret = hi8435_readb(priv, HI8435_PSEN_REG, ®); + if (ret < 0) + return ret; + + return !!(reg & BIT(chan->channel / 8)); +} + +static int hi8435_set_sensing_mode(struct iio_dev *idev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct hi8435_priv *priv = iio_priv(idev); + int ret; + u8 reg; + + mutex_lock(&priv->lock); + + ret = hi8435_readb(priv, HI8435_PSEN_REG, ®); + if (ret < 0) { + mutex_unlock(&priv->lock); + return ret; + } + + reg &= ~BIT(chan->channel / 8); + if (mode) + reg |= BIT(chan->channel / 8); + + ret = hi8435_writeb(priv, HI8435_PSEN_REG, reg); + + mutex_unlock(&priv->lock); + + return ret; +} + +static const char * const hi8435_sensing_modes[] = { "GND-Open", + "Supply-Open" }; + +static const struct iio_enum hi8435_sensing_mode = { + .items = hi8435_sensing_modes, + .num_items = ARRAY_SIZE(hi8435_sensing_modes), + .get = hi8435_get_sensing_mode, + .set = hi8435_set_sensing_mode, +}; + +static const struct iio_chan_spec_ext_info hi8435_ext_info[] = { + IIO_ENUM("sensing_mode", IIO_SEPARATE, &hi8435_sensing_mode), + IIO_ENUM_AVAILABLE("sensing_mode", IIO_SHARED_BY_TYPE, &hi8435_sensing_mode), + { } +}; + +#define HI8435_VOLTAGE_CHANNEL(num) \ +{ \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = num, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .event_spec = hi8435_events, \ + .num_event_specs = ARRAY_SIZE(hi8435_events), \ + .ext_info = hi8435_ext_info, \ +} + +static const struct iio_chan_spec hi8435_channels[] = { + HI8435_VOLTAGE_CHANNEL(0), + HI8435_VOLTAGE_CHANNEL(1), + HI8435_VOLTAGE_CHANNEL(2), + HI8435_VOLTAGE_CHANNEL(3), + HI8435_VOLTAGE_CHANNEL(4), + HI8435_VOLTAGE_CHANNEL(5), + HI8435_VOLTAGE_CHANNEL(6), + HI8435_VOLTAGE_CHANNEL(7), + HI8435_VOLTAGE_CHANNEL(8), + HI8435_VOLTAGE_CHANNEL(9), + HI8435_VOLTAGE_CHANNEL(10), + HI8435_VOLTAGE_CHANNEL(11), + HI8435_VOLTAGE_CHANNEL(12), + HI8435_VOLTAGE_CHANNEL(13), + HI8435_VOLTAGE_CHANNEL(14), + HI8435_VOLTAGE_CHANNEL(15), + HI8435_VOLTAGE_CHANNEL(16), + HI8435_VOLTAGE_CHANNEL(17), + HI8435_VOLTAGE_CHANNEL(18), + HI8435_VOLTAGE_CHANNEL(19), + HI8435_VOLTAGE_CHANNEL(20), + HI8435_VOLTAGE_CHANNEL(21), + HI8435_VOLTAGE_CHANNEL(22), + HI8435_VOLTAGE_CHANNEL(23), + HI8435_VOLTAGE_CHANNEL(24), + HI8435_VOLTAGE_CHANNEL(25), + HI8435_VOLTAGE_CHANNEL(26), + HI8435_VOLTAGE_CHANNEL(27), + HI8435_VOLTAGE_CHANNEL(28), + HI8435_VOLTAGE_CHANNEL(29), + HI8435_VOLTAGE_CHANNEL(30), + HI8435_VOLTAGE_CHANNEL(31), + IIO_CHAN_SOFT_TIMESTAMP(32), +}; + +static const struct iio_info hi8435_info = { + .read_raw = hi8435_read_raw, + .read_event_config = hi8435_read_event_config, + .write_event_config = hi8435_write_event_config, + .read_event_value = hi8435_read_event_value, + .write_event_value = hi8435_write_event_value, + .debugfs_reg_access = hi8435_debugfs_reg_access, +}; + +static void hi8435_iio_push_event(struct iio_dev *idev, unsigned int val) +{ + struct hi8435_priv *priv = iio_priv(idev); + enum iio_event_direction dir; + unsigned int i; + unsigned int status = priv->event_prev_val ^ val; + + if (!status) + return; + + for_each_set_bit(i, &priv->event_scan_mask, 32) { + if (status & BIT(i)) { + dir = val & BIT(i) ? IIO_EV_DIR_RISING : + IIO_EV_DIR_FALLING; + iio_push_event(idev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, i, + IIO_EV_TYPE_THRESH, dir), + iio_get_time_ns(idev)); + } + } + + priv->event_prev_val = val; +} + +static irqreturn_t hi8435_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *idev = pf->indio_dev; + struct hi8435_priv *priv = iio_priv(idev); + u32 val; + int ret; + + ret = hi8435_readl(priv, HI8435_SO31_0_REG, &val); + if (ret < 0) + goto err_read; + + hi8435_iio_push_event(idev, val); + +err_read: + iio_trigger_notify_done(idev->trig); + + return IRQ_HANDLED; +} + +static void hi8435_triggered_event_cleanup(void *data) +{ + iio_triggered_event_cleanup(data); +} + +static int hi8435_probe(struct spi_device *spi) +{ + struct iio_dev *idev; + struct hi8435_priv *priv; + struct gpio_desc *reset_gpio; + int ret; + + idev = devm_iio_device_alloc(&spi->dev, sizeof(*priv)); + if (!idev) + return -ENOMEM; + + priv = iio_priv(idev); + priv->spi = spi; + + reset_gpio = devm_gpiod_get(&spi->dev, NULL, GPIOD_OUT_LOW); + if (IS_ERR(reset_gpio)) { + /* chip s/w reset if h/w reset failed */ + hi8435_writeb(priv, HI8435_CTRL_REG, HI8435_CTRL_SRST); + hi8435_writeb(priv, HI8435_CTRL_REG, 0); + } else { + udelay(5); + gpiod_set_value_cansleep(reset_gpio, 1); + } + + mutex_init(&priv->lock); + + idev->name = spi_get_device_id(spi)->name; + idev->modes = INDIO_DIRECT_MODE; + idev->info = &hi8435_info; + idev->channels = hi8435_channels; + idev->num_channels = ARRAY_SIZE(hi8435_channels); + + /* unmask all events */ + priv->event_scan_mask = ~(0); + /* + * There is a restriction in the chip - the hysteresis can not be odd. + * If the hysteresis is set to odd value then chip gets into lock state + * and not functional anymore. + * After chip reset the thresholds are in undefined state, so we need to + * initialize thresholds to some initial values and then prevent + * userspace setting odd hysteresis. + * + * Set threshold low voltage to 2V, threshold high voltage to 4V + * for both GND-Open and Supply-Open sensing modes. + */ + priv->threshold_lo[0] = priv->threshold_lo[1] = 2; + priv->threshold_hi[0] = priv->threshold_hi[1] = 4; + hi8435_writew(priv, HI8435_GOCENHYS_REG, 0x206); + hi8435_writew(priv, HI8435_SOCENHYS_REG, 0x206); + + ret = iio_triggered_event_setup(idev, NULL, hi8435_trigger_handler); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&spi->dev, + hi8435_triggered_event_cleanup, + idev); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, idev); +} + +static const struct of_device_id hi8435_dt_ids[] = { + { .compatible = "holt,hi8435" }, + { } +}; +MODULE_DEVICE_TABLE(of, hi8435_dt_ids); + +static const struct spi_device_id hi8435_id[] = { + { .name = "hi8435" }, + { } +}; +MODULE_DEVICE_TABLE(spi, hi8435_id); + +static struct spi_driver hi8435_driver = { + .driver = { + .name = "hi8435", + .of_match_table = hi8435_dt_ids, + }, + .probe = hi8435_probe, + .id_table = hi8435_id, +}; +module_spi_driver(hi8435_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_DESCRIPTION("HI-8435 threshold detector"); diff --git a/drivers/iio/adc/hx711.c b/drivers/iio/adc/hx711.c new file mode 100644 index 000000000..00adafa7c --- /dev/null +++ b/drivers/iio/adc/hx711.c @@ -0,0 +1,629 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HX711: analog to digital converter for weight sensor module + * + * Copyright (c) 2016 Andreas Klinger + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define HX711_DATA_BITS 24 + +/* gain to pulse and scale conversion */ +#define HX711_GAIN_MAX 3 +#define HX711_RESET_GAIN 128 + +struct hx711_gain_to_scale { + int gain; + int gain_pulse; + int channel; +}; + +/* + * .scale depends on AVDD which in turn is known as soon as the regulator + * is available; it is stored per device in hx711_data.gain_scale[] + * + * channel A in documentation is channel 0 in source code + * channel B in documentation is channel 1 in source code + */ +static const struct hx711_gain_to_scale hx711_gain_to_scale[HX711_GAIN_MAX] = { + { 128, 1, 0 }, + { 32, 2, 1 }, + { 64, 3, 0 }, +}; + +static int hx711_get_gain_to_pulse(int gain) +{ + int i; + + for (i = 0; i < HX711_GAIN_MAX; i++) + if (hx711_gain_to_scale[i].gain == gain) + return hx711_gain_to_scale[i].gain_pulse; + return 1; +} + +static int hx711_get_gain_to_scale(const int *gain_scale, int gain) +{ + int i; + + for (i = 0; i < HX711_GAIN_MAX; i++) + if (hx711_gain_to_scale[i].gain == gain) + return gain_scale[i]; + return 0; +} + +static int hx711_get_scale_to_gain(const int *gain_scale, int scale) +{ + int i; + + for (i = 0; i < HX711_GAIN_MAX; i++) + if (gain_scale[i] == scale) + return hx711_gain_to_scale[i].gain; + return -EINVAL; +} + +/** + * struct hx711_chip_info - per-variant static configuration + * @name: IIO device name + * @channels: channel specification array + * @num_channels: number of entries in @channels + * @iio_info: IIO info ops for this variant + */ +struct hx711_chip_info { + const char *name; + const struct iio_chan_spec *channels __counted_by_ptr(num_channels); + unsigned int num_channels; + const struct iio_info *iio_info; +}; + +struct hx711_data { + struct device *dev; + struct gpio_desc *gpiod_pd_sck; + struct gpio_desc *gpiod_dout; + int gain_set; /* gain set on device */ + int gain_chan_a; /* gain for channel A */ + int gain_scale[HX711_GAIN_MAX]; + const struct hx711_chip_info *chip_info; + struct mutex lock; + /* + * triggered buffer + * 2x32-bit channel + 64-bit naturally aligned timestamp + */ + struct { + u32 channel[2]; + aligned_s64 timestamp; + } buffer; + /* + * delay after a rising edge on SCK until the data is ready DOUT + * this is dependent on the hx711 where the datasheet tells a + * maximum value of 100 ns + * but also on potential parasitic capacities on the wiring + */ + u32 data_ready_delay_ns; + u32 clock_frequency; +}; + +static int hx711_cycle(struct hx711_data *hx711_data) +{ + unsigned long flags; + + /* + * if preempted for more then 60us while PD_SCK is high: + * hx711 is going in reset + * ==> measuring is false + */ + local_irq_save(flags); + gpiod_set_value(hx711_data->gpiod_pd_sck, 1); + + /* + * wait until DOUT is ready + * it turned out that parasitic capacities are extending the time + * until DOUT has reached it's value + */ + ndelay(hx711_data->data_ready_delay_ns); + + /* + * here we are not waiting for 0.2 us as suggested by the datasheet, + * because the oscilloscope showed in a test scenario + * at least 1.15 us for PD_SCK high (T3 in datasheet) + * and 0.56 us for PD_SCK low on TI Sitara with 800 MHz + */ + gpiod_set_value(hx711_data->gpiod_pd_sck, 0); + local_irq_restore(flags); + + /* + * make it a square wave for addressing cases with capacitance on + * PC_SCK + */ + ndelay(hx711_data->data_ready_delay_ns); + + /* sample as late as possible */ + return gpiod_get_value(hx711_data->gpiod_dout); +} + +static int hx711_read(struct hx711_data *hx711_data, int trailing_pulses) +{ + int value = 0; + int val; + int ret; + + /* we double check if it's really down */ + val = gpiod_get_value(hx711_data->gpiod_dout); + if (val) + return -EIO; + + for (int i = 0; i < HX711_DATA_BITS; i++) { + value <<= 1; + ret = hx711_cycle(hx711_data); + if (ret) + value++; + } + + value ^= 0x800000; + + for (int i = 0; i < trailing_pulses; i++) + hx711_cycle(hx711_data); + + return value; +} + +static int hx711_wait_for_ready(struct hx711_data *hx711_data) +{ + int i, val; + + /* + * in some rare cases the reset takes quite a long time + * especially when the channel is changed. + * Allow up to one second for it + */ + for (i = 0; i < 100; i++) { + val = gpiod_get_value(hx711_data->gpiod_dout); + if (!val) + break; + /* sleep at least 10 ms */ + msleep(10); + } + if (val) + return -EIO; + + return 0; +} + +static int hx711_reset(struct hx711_data *hx711_data) +{ + int val; + + val = hx711_wait_for_ready(hx711_data); + if (val) { + /* + * an examination with the oszilloscope indicated + * that the first value read after the reset is not stable + * if we reset too short; + * the shorter the reset cycle + * the less reliable the first value after reset is; + * there were no problems encountered with a value + * of 10 ms or higher + */ + gpiod_set_value(hx711_data->gpiod_pd_sck, 1); + msleep(10); + gpiod_set_value(hx711_data->gpiod_pd_sck, 0); + + val = hx711_wait_for_ready(hx711_data); + + /* after a reset the gain is 128 */ + hx711_data->gain_set = HX711_RESET_GAIN; + } + + return val; +} + +static int hx711_set_gain_for_channel(struct hx711_data *hx711_data, int chan) +{ + int ret; + + if (chan == 0) { + if (hx711_data->gain_set == 32) { + hx711_data->gain_set = hx711_data->gain_chan_a; + + ret = hx711_read(hx711_data, + hx711_get_gain_to_pulse(hx711_data->gain_set)); + if (ret < 0) + return ret; + + ret = hx711_wait_for_ready(hx711_data); + if (ret) + return ret; + } + } else { + if (hx711_data->gain_set != 32) { + hx711_data->gain_set = 32; + + ret = hx711_read(hx711_data, + hx711_get_gain_to_pulse(hx711_data->gain_set)); + if (ret < 0) + return ret; + + ret = hx711_wait_for_ready(hx711_data); + if (ret) + return ret; + } + } + + return 0; +} + +static int hx711_set_hx711_channel(struct hx711_data *hx711_data, + const struct iio_chan_spec *chan, + int *trailing_pulses) +{ + int ret; + + ret = hx711_set_gain_for_channel(hx711_data, chan->channel); + if (ret < 0) + return ret; + + *trailing_pulses = hx711_get_gain_to_pulse(hx711_data->gain_set); + + return 0; +} + +static int hx711_reset_read(struct hx711_data *hx711_data, + const struct iio_chan_spec *chan) +{ + int trailing_pulses; + int ret; + + /* + * hx711_reset() must be called from here + * because it could be calling hx711_read() by itself + */ + if (hx711_reset(hx711_data)) { + dev_err(hx711_data->dev, "reset failed!"); + return -EIO; + } + + ret = hx711_set_hx711_channel(hx711_data, chan, &trailing_pulses); + if (ret < 0) + return ret; + + return hx711_read(hx711_data, trailing_pulses); +} + +static int hx711_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct hx711_data *hx711_data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&hx711_data->lock); + + *val = hx711_reset_read(hx711_data, chan); + + mutex_unlock(&hx711_data->lock); + + if (*val < 0) + return *val; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + mutex_lock(&hx711_data->lock); + + *val2 = hx711_get_gain_to_scale(hx711_data->gain_scale, + hx711_data->gain_set); + + mutex_unlock(&hx711_data->lock); + + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static int hx711_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct hx711_data *hx711_data = iio_priv(indio_dev); + int ret; + int gain; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + /* + * a scale greater than 1 mV per LSB is not possible + * with the HX711, therefore val must be 0 + */ + if (val != 0) + return -EINVAL; + + mutex_lock(&hx711_data->lock); + + gain = hx711_get_scale_to_gain(hx711_data->gain_scale, val2); + if (gain < 0) { + mutex_unlock(&hx711_data->lock); + return gain; + } + + if (gain != hx711_data->gain_set) { + hx711_data->gain_set = gain; + if (gain != 32) + hx711_data->gain_chan_a = gain; + + ret = hx711_read(hx711_data, + hx711_get_gain_to_pulse(hx711_data->gain_set)); + if (ret < 0) { + mutex_unlock(&hx711_data->lock); + return ret; + } + } + + mutex_unlock(&hx711_data->lock); + return 0; + default: + return -EINVAL; + } + + return 0; +} + +static int hx711_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + return IIO_VAL_INT_PLUS_NANO; +} + +static irqreturn_t hx711_trigger(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct hx711_data *hx711_data = iio_priv(indio_dev); + int i, j = 0; + + mutex_lock(&hx711_data->lock); + + memset(&hx711_data->buffer, 0, sizeof(hx711_data->buffer)); + + iio_for_each_active_channel(indio_dev, i) { + hx711_data->buffer.channel[j] = + hx711_reset_read(hx711_data, &indio_dev->channels[i]); + j++; + } + + iio_push_to_buffers_with_timestamp(indio_dev, &hx711_data->buffer, + pf->timestamp); + + mutex_unlock(&hx711_data->lock); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static ssize_t hx711_scale_available_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hx711_data *hx711_data = iio_priv(dev_to_iio_dev(dev)); + struct iio_dev_attr *iio_attr = to_iio_dev_attr(attr); + int channel = iio_attr->address; + int i, len = 0; + + for (i = 0; i < HX711_GAIN_MAX; i++) + if (hx711_gain_to_scale[i].channel == channel) + len += sprintf(buf + len, "0.%09d ", + hx711_data->gain_scale[i]); + + len += sprintf(buf + len, "\n"); + + return len; +} + +static IIO_DEVICE_ATTR(in_voltage0_scale_available, S_IRUGO, + hx711_scale_available_show, NULL, 0); + +static IIO_DEVICE_ATTR(in_voltage1_scale_available, S_IRUGO, + hx711_scale_available_show, NULL, 1); + +static struct attribute *hx711_attributes[] = { + &iio_dev_attr_in_voltage0_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage1_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group hx711_attribute_group = { + .attrs = hx711_attributes, +}; + +static const struct iio_info hx711_iio_info = { + .read_raw = hx711_read_raw, + .write_raw = hx711_write_raw, + .write_raw_get_fmt = hx711_write_raw_get_fmt, + .attrs = &hx711_attribute_group, +}; + +static const struct iio_chan_spec hx711_chan_spec[] = { + { + .type = IIO_VOLTAGE, + .channel = 0, + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_VOLTAGE, + .channel = 1, + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 1, + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static const struct hx711_chip_info hx711_chip = { + .name = "hx711", + .channels = hx711_chan_spec, + .iio_info = &hx711_iio_info, + .num_channels = ARRAY_SIZE(hx711_chan_spec), +}; + +static int hx711_probe(struct platform_device *pdev) +{ + const struct hx711_chip_info *chip_info; + struct device *dev = &pdev->dev; + struct hx711_data *hx711_data; + struct iio_dev *indio_dev; + int ret; + int i; + + indio_dev = devm_iio_device_alloc(dev, sizeof(struct hx711_data)); + if (!indio_dev) + return -ENOMEM; + + hx711_data = iio_priv(indio_dev); + hx711_data->dev = dev; + + mutex_init(&hx711_data->lock); + + chip_info = device_get_match_data(dev); + if (!chip_info) + return dev_err_probe(dev, -ENODEV, "missing driver data\n"); + + hx711_data->chip_info = chip_info; + + /* + * PD_SCK stands for power down and serial clock input of HX711 + * in the driver it is an output + */ + hx711_data->gpiod_pd_sck = devm_gpiod_get(dev, "sck", GPIOD_OUT_LOW); + if (IS_ERR(hx711_data->gpiod_pd_sck)) + return dev_err_probe(dev, PTR_ERR(hx711_data->gpiod_pd_sck), + "failed to get sck-gpiod\n"); + + /* + * DOUT stands for serial data output of HX711 + * for the driver it is an input + */ + hx711_data->gpiod_dout = devm_gpiod_get(dev, "dout", GPIOD_IN); + if (IS_ERR(hx711_data->gpiod_dout)) + return dev_err_probe(dev, PTR_ERR(hx711_data->gpiod_dout), + "failed to get dout-gpiod\n"); + + ret = devm_regulator_get_enable_read_voltage(dev, "avdd"); + if (ret < 0) + return ret; + + /* + * with + * full scale differential input range: AVDD / GAIN + * full scale output data: 2^24 + * we can say: + * AVDD / GAIN = 2^24 + * therefore: + * 1 LSB = AVDD / GAIN / 2^24 + * AVDD is in uV, but we need 10^-9 mV + * approximately to fit into a 32 bit number: + * 1 LSB = (AVDD * 100) / GAIN / 1678 [10^-9 mV] + */ + + /* we need 10^-9 mV */ + ret *= 100; + + for (i = 0; i < HX711_GAIN_MAX; i++) + hx711_data->gain_scale[i] = + ret / hx711_gain_to_scale[i].gain / 1678; + + hx711_data->gain_set = 128; + hx711_data->gain_chan_a = 128; + + hx711_data->clock_frequency = 400000; + ret = device_property_read_u32(&pdev->dev, "clock-frequency", + &hx711_data->clock_frequency); + + /* + * datasheet says the high level of PD_SCK has a maximum duration + * of 50 microseconds + */ + if (hx711_data->clock_frequency < 20000) { + dev_warn(dev, "clock-frequency too low - assuming 400 kHz\n"); + hx711_data->clock_frequency = 400000; + } + + hx711_data->data_ready_delay_ns = + 1000000000 / hx711_data->clock_frequency; + + indio_dev->name = chip_info->name; + indio_dev->info = chip_info->iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = chip_info->channels; + indio_dev->num_channels = chip_info->num_channels; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + hx711_trigger, NULL); + if (ret < 0) + return dev_err_probe(dev, ret, + "setup of iio triggered buffer failed\n"); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret < 0) + return dev_err_probe(dev, ret, "Couldn't register the device\n"); + + return 0; +} + +static const struct of_device_id of_hx711_match[] = { + { .compatible = "avia,hx711", .data = &hx711_chip }, + { } +}; + +MODULE_DEVICE_TABLE(of, of_hx711_match); + +static struct platform_driver hx711_driver = { + .probe = hx711_probe, + .driver = { + .name = "hx711-gpio", + .of_match_table = of_hx711_match, + }, +}; + +module_platform_driver(hx711_driver); + +MODULE_AUTHOR("Andreas Klinger "); +MODULE_DESCRIPTION("HX711 bitbanging driver - ADC for weight cells"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:hx711-gpio"); + diff --git a/drivers/iio/adc/imx7d_adc.c b/drivers/iio/adc/imx7d_adc.c new file mode 100644 index 000000000..e9a509dc1 --- /dev/null +++ b/drivers/iio/adc/imx7d_adc.c @@ -0,0 +1,560 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Freescale i.MX7D ADC driver + * + * Copyright (C) 2015 Freescale Semiconductor, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* ADC register */ +#define IMX7D_REG_ADC_CH_A_CFG1 0x00 +#define IMX7D_REG_ADC_CH_A_CFG2 0x10 +#define IMX7D_REG_ADC_CH_B_CFG1 0x20 +#define IMX7D_REG_ADC_CH_B_CFG2 0x30 +#define IMX7D_REG_ADC_CH_C_CFG1 0x40 +#define IMX7D_REG_ADC_CH_C_CFG2 0x50 +#define IMX7D_REG_ADC_CH_D_CFG1 0x60 +#define IMX7D_REG_ADC_CH_D_CFG2 0x70 +#define IMX7D_REG_ADC_CH_SW_CFG 0x80 +#define IMX7D_REG_ADC_TIMER_UNIT 0x90 +#define IMX7D_REG_ADC_DMA_FIFO 0xa0 +#define IMX7D_REG_ADC_FIFO_STATUS 0xb0 +#define IMX7D_REG_ADC_INT_SIG_EN 0xc0 +#define IMX7D_REG_ADC_INT_EN 0xd0 +#define IMX7D_REG_ADC_INT_STATUS 0xe0 +#define IMX7D_REG_ADC_CHA_B_CNV_RSLT 0xf0 +#define IMX7D_REG_ADC_CHC_D_CNV_RSLT 0x100 +#define IMX7D_REG_ADC_CH_SW_CNV_RSLT 0x110 +#define IMX7D_REG_ADC_DMA_FIFO_DAT 0x120 +#define IMX7D_REG_ADC_ADC_CFG 0x130 + +#define IMX7D_REG_ADC_CHANNEL_CFG2_BASE 0x10 +#define IMX7D_EACH_CHANNEL_REG_OFFSET 0x20 + +#define IMX7D_REG_ADC_CH_CFG1_CHANNEL_EN (0x1 << 31) +#define IMX7D_REG_ADC_CH_CFG1_CHANNEL_SINGLE BIT(30) +#define IMX7D_REG_ADC_CH_CFG1_CHANNEL_AVG_EN BIT(29) +#define IMX7D_REG_ADC_CH_CFG1_CHANNEL_SEL(x) ((x) << 24) + +#define IMX7D_REG_ADC_CH_CFG2_AVG_NUM_4 (0x0 << 12) +#define IMX7D_REG_ADC_CH_CFG2_AVG_NUM_8 (0x1 << 12) +#define IMX7D_REG_ADC_CH_CFG2_AVG_NUM_16 (0x2 << 12) +#define IMX7D_REG_ADC_CH_CFG2_AVG_NUM_32 (0x3 << 12) + +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_4 (0x0 << 29) +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_8 (0x1 << 29) +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_16 (0x2 << 29) +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_32 (0x3 << 29) +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_64 (0x4 << 29) +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_128 (0x5 << 29) + +#define IMX7D_REG_ADC_ADC_CFG_ADC_CLK_DOWN BIT(31) +#define IMX7D_REG_ADC_ADC_CFG_ADC_POWER_DOWN BIT(1) +#define IMX7D_REG_ADC_ADC_CFG_ADC_EN BIT(0) + +#define IMX7D_REG_ADC_INT_CHA_COV_INT_EN BIT(8) +#define IMX7D_REG_ADC_INT_CHB_COV_INT_EN BIT(9) +#define IMX7D_REG_ADC_INT_CHC_COV_INT_EN BIT(10) +#define IMX7D_REG_ADC_INT_CHD_COV_INT_EN BIT(11) +#define IMX7D_REG_ADC_INT_CHANNEL_INT_EN \ + (IMX7D_REG_ADC_INT_CHA_COV_INT_EN | \ + IMX7D_REG_ADC_INT_CHB_COV_INT_EN | \ + IMX7D_REG_ADC_INT_CHC_COV_INT_EN | \ + IMX7D_REG_ADC_INT_CHD_COV_INT_EN) +#define IMX7D_REG_ADC_INT_STATUS_CHANNEL_INT_STATUS 0xf00 +#define IMX7D_REG_ADC_INT_STATUS_CHANNEL_CONV_TIME_OUT 0xf0000 + +#define IMX7D_ADC_TIMEOUT msecs_to_jiffies(100) +#define IMX7D_ADC_INPUT_CLK 24000000 + +enum imx7d_adc_clk_pre_div { + IMX7D_ADC_ANALOG_CLK_PRE_DIV_4, + IMX7D_ADC_ANALOG_CLK_PRE_DIV_8, + IMX7D_ADC_ANALOG_CLK_PRE_DIV_16, + IMX7D_ADC_ANALOG_CLK_PRE_DIV_32, + IMX7D_ADC_ANALOG_CLK_PRE_DIV_64, + IMX7D_ADC_ANALOG_CLK_PRE_DIV_128, +}; + +enum imx7d_adc_average_num { + IMX7D_ADC_AVERAGE_NUM_4, + IMX7D_ADC_AVERAGE_NUM_8, + IMX7D_ADC_AVERAGE_NUM_16, + IMX7D_ADC_AVERAGE_NUM_32, +}; + +struct imx7d_adc_feature { + enum imx7d_adc_clk_pre_div clk_pre_div; + enum imx7d_adc_average_num avg_num; + + u32 core_time_unit; /* impact the sample rate */ +}; + +struct imx7d_adc { + struct device *dev; + void __iomem *regs; + struct clk *clk; + /* lock to protect against multiple access to the device */ + struct mutex lock; + u32 vref_uv; + u32 value; + u32 channel; + u32 pre_div_num; + + struct regulator *vref; + struct imx7d_adc_feature adc_feature; + + struct completion completion; +}; + +struct imx7d_adc_analogue_core_clk { + u32 pre_div; + u32 reg_config; +}; + +#define IMX7D_ADC_ANALOGUE_CLK_CONFIG(_pre_div, _reg_conf) { \ + .pre_div = (_pre_div), \ + .reg_config = (_reg_conf), \ +} + +static const struct imx7d_adc_analogue_core_clk imx7d_adc_analogue_clk[] = { + IMX7D_ADC_ANALOGUE_CLK_CONFIG(4, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_4), + IMX7D_ADC_ANALOGUE_CLK_CONFIG(8, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_8), + IMX7D_ADC_ANALOGUE_CLK_CONFIG(16, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_16), + IMX7D_ADC_ANALOGUE_CLK_CONFIG(32, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_32), + IMX7D_ADC_ANALOGUE_CLK_CONFIG(64, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_64), + IMX7D_ADC_ANALOGUE_CLK_CONFIG(128, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_128), +}; + +#define IMX7D_ADC_CHAN(_idx) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ +} + +static const struct iio_chan_spec imx7d_adc_iio_channels[] = { + IMX7D_ADC_CHAN(0), + IMX7D_ADC_CHAN(1), + IMX7D_ADC_CHAN(2), + IMX7D_ADC_CHAN(3), + IMX7D_ADC_CHAN(4), + IMX7D_ADC_CHAN(5), + IMX7D_ADC_CHAN(6), + IMX7D_ADC_CHAN(7), + IMX7D_ADC_CHAN(8), + IMX7D_ADC_CHAN(9), + IMX7D_ADC_CHAN(10), + IMX7D_ADC_CHAN(11), + IMX7D_ADC_CHAN(12), + IMX7D_ADC_CHAN(13), + IMX7D_ADC_CHAN(14), + IMX7D_ADC_CHAN(15), +}; + +static const u32 imx7d_adc_average_num[] = { + IMX7D_REG_ADC_CH_CFG2_AVG_NUM_4, + IMX7D_REG_ADC_CH_CFG2_AVG_NUM_8, + IMX7D_REG_ADC_CH_CFG2_AVG_NUM_16, + IMX7D_REG_ADC_CH_CFG2_AVG_NUM_32, +}; + +static void imx7d_adc_feature_config(struct imx7d_adc *info) +{ + info->adc_feature.clk_pre_div = IMX7D_ADC_ANALOG_CLK_PRE_DIV_4; + info->adc_feature.avg_num = IMX7D_ADC_AVERAGE_NUM_32; + info->adc_feature.core_time_unit = 1; +} + +static void imx7d_adc_sample_rate_set(struct imx7d_adc *info) +{ + struct imx7d_adc_feature *adc_feature = &info->adc_feature; + struct imx7d_adc_analogue_core_clk adc_analogure_clk; + u32 i; + u32 tmp_cfg1; + u32 sample_rate = 0; + + /* + * Before sample set, disable channel A,B,C,D. Here we + * clear the bit 31 of register REG_ADC_CH_A\B\C\D_CFG1. + */ + for (i = 0; i < 4; i++) { + tmp_cfg1 = + readl(info->regs + i * IMX7D_EACH_CHANNEL_REG_OFFSET); + tmp_cfg1 &= ~IMX7D_REG_ADC_CH_CFG1_CHANNEL_EN; + writel(tmp_cfg1, + info->regs + i * IMX7D_EACH_CHANNEL_REG_OFFSET); + } + + adc_analogure_clk = imx7d_adc_analogue_clk[adc_feature->clk_pre_div]; + sample_rate |= adc_analogure_clk.reg_config; + info->pre_div_num = adc_analogure_clk.pre_div; + + sample_rate |= adc_feature->core_time_unit; + writel(sample_rate, info->regs + IMX7D_REG_ADC_TIMER_UNIT); +} + +static void imx7d_adc_hw_init(struct imx7d_adc *info) +{ + u32 cfg; + + /* power up and enable adc analogue core */ + cfg = readl(info->regs + IMX7D_REG_ADC_ADC_CFG); + cfg &= ~(IMX7D_REG_ADC_ADC_CFG_ADC_CLK_DOWN | + IMX7D_REG_ADC_ADC_CFG_ADC_POWER_DOWN); + cfg |= IMX7D_REG_ADC_ADC_CFG_ADC_EN; + writel(cfg, info->regs + IMX7D_REG_ADC_ADC_CFG); + + /* enable channel A,B,C,D interrupt */ + writel(IMX7D_REG_ADC_INT_CHANNEL_INT_EN, + info->regs + IMX7D_REG_ADC_INT_SIG_EN); + writel(IMX7D_REG_ADC_INT_CHANNEL_INT_EN, + info->regs + IMX7D_REG_ADC_INT_EN); + + imx7d_adc_sample_rate_set(info); +} + +static void imx7d_adc_channel_set(struct imx7d_adc *info) +{ + u32 cfg1 = 0; + u32 cfg2; + u32 channel; + + channel = info->channel; + + /* the channel choose single conversion, and enable average mode */ + cfg1 |= (IMX7D_REG_ADC_CH_CFG1_CHANNEL_EN | + IMX7D_REG_ADC_CH_CFG1_CHANNEL_SINGLE | + IMX7D_REG_ADC_CH_CFG1_CHANNEL_AVG_EN); + + /* + * physical channel 0 chose logical channel A + * physical channel 1 chose logical channel B + * physical channel 2 chose logical channel C + * physical channel 3 chose logical channel D + */ + cfg1 |= IMX7D_REG_ADC_CH_CFG1_CHANNEL_SEL(channel); + + /* + * read register REG_ADC_CH_A\B\C\D_CFG2, according to the + * channel chosen + */ + cfg2 = readl(info->regs + IMX7D_EACH_CHANNEL_REG_OFFSET * channel + + IMX7D_REG_ADC_CHANNEL_CFG2_BASE); + + cfg2 |= imx7d_adc_average_num[info->adc_feature.avg_num]; + + /* + * write the register REG_ADC_CH_A\B\C\D_CFG2, according to + * the channel chosen + */ + writel(cfg2, info->regs + IMX7D_EACH_CHANNEL_REG_OFFSET * channel + + IMX7D_REG_ADC_CHANNEL_CFG2_BASE); + writel(cfg1, info->regs + IMX7D_EACH_CHANNEL_REG_OFFSET * channel); +} + +static u32 imx7d_adc_get_sample_rate(struct imx7d_adc *info) +{ + u32 analogue_core_clk; + u32 core_time_unit = info->adc_feature.core_time_unit; + u32 tmp; + + analogue_core_clk = IMX7D_ADC_INPUT_CLK / info->pre_div_num; + tmp = (core_time_unit + 1) * 6; + + return analogue_core_clk / tmp; +} + +static int imx7d_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct imx7d_adc *info = iio_priv(indio_dev); + + u32 channel; + long ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&info->lock); + reinit_completion(&info->completion); + + channel = chan->channel & 0x03; + info->channel = channel; + imx7d_adc_channel_set(info); + + ret = wait_for_completion_interruptible_timeout + (&info->completion, IMX7D_ADC_TIMEOUT); + if (ret == 0) { + mutex_unlock(&info->lock); + return -ETIMEDOUT; + } + if (ret < 0) { + mutex_unlock(&info->lock); + return ret; + } + + *val = info->value; + mutex_unlock(&info->lock); + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + info->vref_uv = regulator_get_voltage(info->vref); + *val = info->vref_uv / 1000; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = imx7d_adc_get_sample_rate(info); + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int imx7d_adc_read_data(struct imx7d_adc *info) +{ + u32 channel; + u32 value; + + channel = info->channel & 0x03; + + /* + * channel A and B conversion result share one register, + * bit[27~16] is the channel B conversion result, + * bit[11~0] is the channel A conversion result. + * channel C and D is the same. + */ + if (channel < 2) + value = readl(info->regs + IMX7D_REG_ADC_CHA_B_CNV_RSLT); + else + value = readl(info->regs + IMX7D_REG_ADC_CHC_D_CNV_RSLT); + if (channel & 0x1) /* channel B or D */ + value = (value >> 16) & 0xFFF; + else /* channel A or C */ + value &= 0xFFF; + + return value; +} + +static irqreturn_t imx7d_adc_isr(int irq, void *dev_id) +{ + struct imx7d_adc *info = dev_id; + int status; + + status = readl(info->regs + IMX7D_REG_ADC_INT_STATUS); + if (status & IMX7D_REG_ADC_INT_STATUS_CHANNEL_INT_STATUS) { + info->value = imx7d_adc_read_data(info); + complete(&info->completion); + + /* + * The register IMX7D_REG_ADC_INT_STATUS can't clear + * itself after read operation, need software to write + * 0 to the related bit. Here we clear the channel A/B/C/D + * conversion finished flag. + */ + status &= ~IMX7D_REG_ADC_INT_STATUS_CHANNEL_INT_STATUS; + writel(status, info->regs + IMX7D_REG_ADC_INT_STATUS); + } + + /* + * If the channel A/B/C/D conversion timeout, report it and clear these + * timeout flags. + */ + if (status & IMX7D_REG_ADC_INT_STATUS_CHANNEL_CONV_TIME_OUT) { + dev_err(info->dev, + "ADC got conversion time out interrupt: 0x%08x\n", + status); + status &= ~IMX7D_REG_ADC_INT_STATUS_CHANNEL_CONV_TIME_OUT; + writel(status, info->regs + IMX7D_REG_ADC_INT_STATUS); + } + + return IRQ_HANDLED; +} + +static int imx7d_adc_reg_access(struct iio_dev *indio_dev, + unsigned reg, unsigned writeval, + unsigned *readval) +{ + struct imx7d_adc *info = iio_priv(indio_dev); + + if (!readval || reg % 4 || reg > IMX7D_REG_ADC_ADC_CFG) + return -EINVAL; + + *readval = readl(info->regs + reg); + + return 0; +} + +static const struct iio_info imx7d_adc_iio_info = { + .read_raw = &imx7d_adc_read_raw, + .debugfs_reg_access = &imx7d_adc_reg_access, +}; + +static const struct of_device_id imx7d_adc_match[] = { + { .compatible = "fsl,imx7d-adc", }, + { } +}; +MODULE_DEVICE_TABLE(of, imx7d_adc_match); + +static void imx7d_adc_power_down(struct imx7d_adc *info) +{ + u32 adc_cfg; + + adc_cfg = readl(info->regs + IMX7D_REG_ADC_ADC_CFG); + adc_cfg |= IMX7D_REG_ADC_ADC_CFG_ADC_CLK_DOWN | + IMX7D_REG_ADC_ADC_CFG_ADC_POWER_DOWN; + adc_cfg &= ~IMX7D_REG_ADC_ADC_CFG_ADC_EN; + writel(adc_cfg, info->regs + IMX7D_REG_ADC_ADC_CFG); +} + +static int imx7d_adc_enable(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct imx7d_adc *info = iio_priv(indio_dev); + int ret; + + ret = regulator_enable(info->vref); + if (ret) { + dev_err(info->dev, + "Can't enable adc reference top voltage, err = %d\n", + ret); + return ret; + } + + ret = clk_prepare_enable(info->clk); + if (ret) { + dev_err(info->dev, + "Could not prepare or enable clock.\n"); + regulator_disable(info->vref); + return ret; + } + + imx7d_adc_hw_init(info); + + return 0; +} + +static int imx7d_adc_disable(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct imx7d_adc *info = iio_priv(indio_dev); + + imx7d_adc_power_down(info); + + clk_disable_unprepare(info->clk); + regulator_disable(info->vref); + + return 0; +} + +static void __imx7d_adc_disable(void *data) +{ + imx7d_adc_disable(data); +} + +static int imx7d_adc_probe(struct platform_device *pdev) +{ + struct imx7d_adc *info; + struct iio_dev *indio_dev; + struct device *dev = &pdev->dev; + int irq; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*info)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + info->dev = dev; + + info->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(info->regs)) + return PTR_ERR(info->regs); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + info->clk = devm_clk_get(dev, "adc"); + if (IS_ERR(info->clk)) + return dev_err_probe(dev, PTR_ERR(info->clk), "Failed getting clock\n"); + + info->vref = devm_regulator_get(dev, "vref"); + if (IS_ERR(info->vref)) + return dev_err_probe(dev, PTR_ERR(info->vref), + "Failed getting reference voltage\n"); + + platform_set_drvdata(pdev, indio_dev); + + init_completion(&info->completion); + + indio_dev->name = dev_name(dev); + indio_dev->info = &imx7d_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = imx7d_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(imx7d_adc_iio_channels); + + ret = devm_request_irq(dev, irq, imx7d_adc_isr, 0, dev_name(dev), info); + if (ret < 0) { + dev_err(dev, "Failed requesting irq, irq = %d\n", irq); + return ret; + } + + imx7d_adc_feature_config(info); + + ret = imx7d_adc_enable(dev); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, __imx7d_adc_disable, dev); + if (ret) + return ret; + + mutex_init(&info->lock); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) { + dev_err(&pdev->dev, "Couldn't register the device.\n"); + return ret; + } + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(imx7d_adc_pm_ops, imx7d_adc_disable, + imx7d_adc_enable); + +static struct platform_driver imx7d_adc_driver = { + .probe = imx7d_adc_probe, + .driver = { + .name = "imx7d_adc", + .of_match_table = imx7d_adc_match, + .pm = pm_sleep_ptr(&imx7d_adc_pm_ops), + }, +}; + +module_platform_driver(imx7d_adc_driver); + +MODULE_AUTHOR("Haibo Chen "); +MODULE_DESCRIPTION("Freescale IMX7D ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/imx8qxp-adc.c b/drivers/iio/adc/imx8qxp-adc.c new file mode 100644 index 000000000..d7cc97743 --- /dev/null +++ b/drivers/iio/adc/imx8qxp-adc.c @@ -0,0 +1,496 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * NXP i.MX8QXP ADC driver + * + * Based on the work of Haibo Chen + * The initial developer of the original code is Haibo Chen. + * Portions created by Haibo Chen are Copyright (C) 2018 NXP. + * All Rights Reserved. + * + * Copyright (C) 2018 NXP + * Copyright (C) 2021 Cai Huoqing + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define ADC_DRIVER_NAME "imx8qxp-adc" + +/* Register map definition */ +#define IMX8QXP_ADR_ADC_CTRL 0x10 +#define IMX8QXP_ADR_ADC_STAT 0x14 +#define IMX8QXP_ADR_ADC_IE 0x18 +#define IMX8QXP_ADR_ADC_DE 0x1c +#define IMX8QXP_ADR_ADC_CFG 0x20 +#define IMX8QXP_ADR_ADC_FCTRL 0x30 +#define IMX8QXP_ADR_ADC_SWTRIG 0x34 +#define IMX8QXP_ADR_ADC_TCTRL(tid) (0xc0 + (tid) * 4) +#define IMX8QXP_ADR_ADC_CMDL(cid) (0x100 + (cid) * 8) +#define IMX8QXP_ADR_ADC_CMDH(cid) (0x104 + (cid) * 8) +#define IMX8QXP_ADR_ADC_RESFIFO 0x300 +#define IMX8QXP_ADR_ADC_TST 0xffc + +/* ADC bit shift */ +#define IMX8QXP_ADC_IE_FWMIE_MASK GENMASK(1, 0) +#define IMX8QXP_ADC_CTRL_FIFO_RESET_MASK BIT(8) +#define IMX8QXP_ADC_CTRL_SOFTWARE_RESET_MASK BIT(1) +#define IMX8QXP_ADC_CTRL_ADC_EN_MASK BIT(0) +#define IMX8QXP_ADC_TCTRL_TCMD_MASK GENMASK(31, 24) +#define IMX8QXP_ADC_TCTRL_TDLY_MASK GENMASK(23, 16) +#define IMX8QXP_ADC_TCTRL_TPRI_MASK GENMASK(15, 8) +#define IMX8QXP_ADC_TCTRL_HTEN_MASK GENMASK(7, 0) +#define IMX8QXP_ADC_CMDL_CSCALE_MASK GENMASK(13, 8) +#define IMX8QXP_ADC_CMDL_MODE_MASK BIT(7) +#define IMX8QXP_ADC_CMDL_DIFF_MASK BIT(6) +#define IMX8QXP_ADC_CMDL_ABSEL_MASK BIT(5) +#define IMX8QXP_ADC_CMDL_ADCH_MASK GENMASK(2, 0) +#define IMX8QXP_ADC_CMDH_NEXT_MASK GENMASK(31, 24) +#define IMX8QXP_ADC_CMDH_LOOP_MASK GENMASK(23, 16) +#define IMX8QXP_ADC_CMDH_AVGS_MASK GENMASK(15, 12) +#define IMX8QXP_ADC_CMDH_STS_MASK BIT(8) +#define IMX8QXP_ADC_CMDH_LWI_MASK GENMASK(7, 7) +#define IMX8QXP_ADC_CMDH_CMPEN_MASK GENMASK(0, 0) +#define IMX8QXP_ADC_CFG_PWREN_MASK BIT(28) +#define IMX8QXP_ADC_CFG_PUDLY_MASK GENMASK(23, 16) +#define IMX8QXP_ADC_CFG_REFSEL_MASK GENMASK(7, 6) +#define IMX8QXP_ADC_CFG_PWRSEL_MASK GENMASK(5, 4) +#define IMX8QXP_ADC_CFG_TPRICTRL_MASK GENMASK(3, 0) +#define IMX8QXP_ADC_FCTRL_FWMARK_MASK GENMASK(20, 16) +#define IMX8QXP_ADC_FCTRL_FCOUNT_MASK GENMASK(4, 0) +#define IMX8QXP_ADC_RESFIFO_VAL_MASK GENMASK(18, 3) + +/* ADC PARAMETER*/ +#define IMX8QXP_ADC_CMDL_CHANNEL_SCALE_FULL GENMASK(5, 0) +#define IMX8QXP_ADC_CMDL_SEL_A_A_B_CHANNEL 0 +#define IMX8QXP_ADC_CMDL_STANDARD_RESOLUTION 0 +#define IMX8QXP_ADC_CMDL_MODE_SINGLE 0 +#define IMX8QXP_ADC_CMDH_LWI_INCREMENT_DIS 0 +#define IMX8QXP_ADC_CMDH_CMPEN_DIS 0 +#define IMX8QXP_ADC_PAUSE_EN BIT(31) +#define IMX8QXP_ADC_TCTRL_TPRI_PRIORITY_HIGH 0 + +#define IMX8QXP_ADC_TCTRL_HTEN_HW_TIRG_DIS 0 + +#define IMX8QXP_ADC_TIMEOUT msecs_to_jiffies(100) + +#define IMX8QXP_ADC_MAX_FIFO_SIZE 16 + +struct imx8qxp_adc { + struct device *dev; + void __iomem *regs; + struct clk *clk; + struct clk *ipg_clk; + struct regulator *vref; + /* Serialise ADC channel reads */ + struct mutex lock; + struct completion completion; + u32 fifo[IMX8QXP_ADC_MAX_FIFO_SIZE]; +}; + +#define IMX8QXP_ADC_CHAN(_idx) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ +} + +static const struct iio_chan_spec imx8qxp_adc_iio_channels[] = { + IMX8QXP_ADC_CHAN(0), + IMX8QXP_ADC_CHAN(1), + IMX8QXP_ADC_CHAN(2), + IMX8QXP_ADC_CHAN(3), + IMX8QXP_ADC_CHAN(4), + IMX8QXP_ADC_CHAN(5), + IMX8QXP_ADC_CHAN(6), + IMX8QXP_ADC_CHAN(7), +}; + +static void imx8qxp_adc_reset(struct imx8qxp_adc *adc) +{ + u32 ctrl; + + /*software reset, need to clear the set bit*/ + ctrl = readl(adc->regs + IMX8QXP_ADR_ADC_CTRL); + ctrl |= FIELD_PREP(IMX8QXP_ADC_CTRL_SOFTWARE_RESET_MASK, 1); + writel(ctrl, adc->regs + IMX8QXP_ADR_ADC_CTRL); + udelay(10); + ctrl &= ~FIELD_PREP(IMX8QXP_ADC_CTRL_SOFTWARE_RESET_MASK, 1); + writel(ctrl, adc->regs + IMX8QXP_ADR_ADC_CTRL); + + /* reset the fifo */ + ctrl |= FIELD_PREP(IMX8QXP_ADC_CTRL_FIFO_RESET_MASK, 1); + writel(ctrl, adc->regs + IMX8QXP_ADR_ADC_CTRL); +} + +static void imx8qxp_adc_reg_config(struct imx8qxp_adc *adc, int channel) +{ + u32 adc_cfg, adc_tctrl, adc_cmdl, adc_cmdh; + + /* ADC configuration */ + adc_cfg = FIELD_PREP(IMX8QXP_ADC_CFG_PWREN_MASK, 1) | + FIELD_PREP(IMX8QXP_ADC_CFG_PUDLY_MASK, 0x80)| + FIELD_PREP(IMX8QXP_ADC_CFG_REFSEL_MASK, 0) | + FIELD_PREP(IMX8QXP_ADC_CFG_PWRSEL_MASK, 3) | + FIELD_PREP(IMX8QXP_ADC_CFG_TPRICTRL_MASK, 0); + writel(adc_cfg, adc->regs + IMX8QXP_ADR_ADC_CFG); + + /* config the trigger control */ + adc_tctrl = FIELD_PREP(IMX8QXP_ADC_TCTRL_TCMD_MASK, 1) | + FIELD_PREP(IMX8QXP_ADC_TCTRL_TDLY_MASK, 0) | + FIELD_PREP(IMX8QXP_ADC_TCTRL_TPRI_MASK, IMX8QXP_ADC_TCTRL_TPRI_PRIORITY_HIGH) | + FIELD_PREP(IMX8QXP_ADC_TCTRL_HTEN_MASK, IMX8QXP_ADC_TCTRL_HTEN_HW_TIRG_DIS); + writel(adc_tctrl, adc->regs + IMX8QXP_ADR_ADC_TCTRL(0)); + + /* config the cmd */ + adc_cmdl = FIELD_PREP(IMX8QXP_ADC_CMDL_CSCALE_MASK, IMX8QXP_ADC_CMDL_CHANNEL_SCALE_FULL) | + FIELD_PREP(IMX8QXP_ADC_CMDL_MODE_MASK, IMX8QXP_ADC_CMDL_STANDARD_RESOLUTION) | + FIELD_PREP(IMX8QXP_ADC_CMDL_DIFF_MASK, IMX8QXP_ADC_CMDL_MODE_SINGLE) | + FIELD_PREP(IMX8QXP_ADC_CMDL_ABSEL_MASK, IMX8QXP_ADC_CMDL_SEL_A_A_B_CHANNEL) | + FIELD_PREP(IMX8QXP_ADC_CMDL_ADCH_MASK, channel); + writel(adc_cmdl, adc->regs + IMX8QXP_ADR_ADC_CMDL(0)); + + adc_cmdh = FIELD_PREP(IMX8QXP_ADC_CMDH_NEXT_MASK, 0) | + FIELD_PREP(IMX8QXP_ADC_CMDH_LOOP_MASK, 0) | + FIELD_PREP(IMX8QXP_ADC_CMDH_AVGS_MASK, 7) | + FIELD_PREP(IMX8QXP_ADC_CMDH_STS_MASK, 0) | + FIELD_PREP(IMX8QXP_ADC_CMDH_LWI_MASK, IMX8QXP_ADC_CMDH_LWI_INCREMENT_DIS) | + FIELD_PREP(IMX8QXP_ADC_CMDH_CMPEN_MASK, IMX8QXP_ADC_CMDH_CMPEN_DIS); + writel(adc_cmdh, adc->regs + IMX8QXP_ADR_ADC_CMDH(0)); +} + +static void imx8qxp_adc_fifo_config(struct imx8qxp_adc *adc) +{ + u32 fifo_ctrl, interrupt_en; + + fifo_ctrl = readl(adc->regs + IMX8QXP_ADR_ADC_FCTRL); + fifo_ctrl &= ~IMX8QXP_ADC_FCTRL_FWMARK_MASK; + /* set the watermark level to 1 */ + fifo_ctrl |= FIELD_PREP(IMX8QXP_ADC_FCTRL_FWMARK_MASK, 0); + writel(fifo_ctrl, adc->regs + IMX8QXP_ADR_ADC_FCTRL); + + /* FIFO Watermark Interrupt Enable */ + interrupt_en = readl(adc->regs + IMX8QXP_ADR_ADC_IE); + interrupt_en |= FIELD_PREP(IMX8QXP_ADC_IE_FWMIE_MASK, 1); + writel(interrupt_en, adc->regs + IMX8QXP_ADR_ADC_IE); +} + +static void imx8qxp_adc_disable(struct imx8qxp_adc *adc) +{ + u32 ctrl; + + ctrl = readl(adc->regs + IMX8QXP_ADR_ADC_CTRL); + ctrl &= ~FIELD_PREP(IMX8QXP_ADC_CTRL_ADC_EN_MASK, 1); + writel(ctrl, adc->regs + IMX8QXP_ADR_ADC_CTRL); +} + +static int imx8qxp_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct imx8qxp_adc *adc = iio_priv(indio_dev); + struct device *dev = adc->dev; + + u32 ctrl; + long ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + pm_runtime_get_sync(dev); + + mutex_lock(&adc->lock); + reinit_completion(&adc->completion); + + imx8qxp_adc_reg_config(adc, chan->channel); + + imx8qxp_adc_fifo_config(adc); + + /* adc enable */ + ctrl = readl(adc->regs + IMX8QXP_ADR_ADC_CTRL); + ctrl |= FIELD_PREP(IMX8QXP_ADC_CTRL_ADC_EN_MASK, 1); + writel(ctrl, adc->regs + IMX8QXP_ADR_ADC_CTRL); + /* adc start */ + writel(1, adc->regs + IMX8QXP_ADR_ADC_SWTRIG); + + ret = wait_for_completion_interruptible_timeout(&adc->completion, + IMX8QXP_ADC_TIMEOUT); + + pm_runtime_put_sync_autosuspend(dev); + + if (ret == 0) { + mutex_unlock(&adc->lock); + return -ETIMEDOUT; + } + if (ret < 0) { + mutex_unlock(&adc->lock); + return ret; + } + + *val = adc->fifo[0]; + + mutex_unlock(&adc->lock); + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + ret = regulator_get_voltage(adc->vref); + if (ret < 0) + return ret; + *val = ret / 1000; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = clk_get_rate(adc->clk) / 3; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static irqreturn_t imx8qxp_adc_isr(int irq, void *dev_id) +{ + struct imx8qxp_adc *adc = dev_id; + u32 fifo_count; + int i; + + fifo_count = FIELD_GET(IMX8QXP_ADC_FCTRL_FCOUNT_MASK, + readl(adc->regs + IMX8QXP_ADR_ADC_FCTRL)); + + for (i = 0; i < fifo_count; i++) + adc->fifo[i] = FIELD_GET(IMX8QXP_ADC_RESFIFO_VAL_MASK, + readl_relaxed(adc->regs + IMX8QXP_ADR_ADC_RESFIFO)); + + if (fifo_count) + complete(&adc->completion); + + return IRQ_HANDLED; +} + +static int imx8qxp_adc_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct imx8qxp_adc *adc = iio_priv(indio_dev); + struct device *dev = adc->dev; + + if (!readval || reg % 4 || reg > IMX8QXP_ADR_ADC_TST) + return -EINVAL; + + pm_runtime_get_sync(dev); + + *readval = readl(adc->regs + reg); + + pm_runtime_put_sync_autosuspend(dev); + + return 0; +} + +static const struct iio_info imx8qxp_adc_iio_info = { + .read_raw = &imx8qxp_adc_read_raw, + .debugfs_reg_access = &imx8qxp_adc_reg_access, +}; + +static int imx8qxp_adc_probe(struct platform_device *pdev) +{ + struct imx8qxp_adc *adc; + struct iio_dev *indio_dev; + struct device *dev = &pdev->dev; + int irq; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->dev = dev; + + mutex_init(&adc->lock); + adc->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(adc->regs)) + return PTR_ERR(adc->regs); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + adc->clk = devm_clk_get(dev, "per"); + if (IS_ERR(adc->clk)) + return dev_err_probe(dev, PTR_ERR(adc->clk), "Failed getting clock\n"); + + adc->ipg_clk = devm_clk_get(dev, "ipg"); + if (IS_ERR(adc->ipg_clk)) + return dev_err_probe(dev, PTR_ERR(adc->ipg_clk), "Failed getting clock\n"); + + adc->vref = devm_regulator_get(dev, "vref"); + if (IS_ERR(adc->vref)) + return dev_err_probe(dev, PTR_ERR(adc->vref), "Failed getting reference voltage\n"); + + ret = regulator_enable(adc->vref); + if (ret) { + dev_err(dev, "Can't enable adc reference top voltage\n"); + return ret; + } + + platform_set_drvdata(pdev, indio_dev); + + init_completion(&adc->completion); + + indio_dev->name = ADC_DRIVER_NAME; + indio_dev->info = &imx8qxp_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = imx8qxp_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(imx8qxp_adc_iio_channels); + + ret = clk_prepare_enable(adc->clk); + if (ret) { + dev_err(&pdev->dev, "Could not prepare or enable the clock.\n"); + goto error_regulator_disable; + } + + ret = clk_prepare_enable(adc->ipg_clk); + if (ret) { + dev_err(&pdev->dev, "Could not prepare or enable the clock.\n"); + goto error_adc_clk_disable; + } + + ret = devm_request_irq(dev, irq, imx8qxp_adc_isr, 0, ADC_DRIVER_NAME, adc); + if (ret < 0) { + dev_err(dev, "Failed requesting irq, irq = %d\n", irq); + goto error_ipg_clk_disable; + } + + imx8qxp_adc_reset(adc); + + ret = iio_device_register(indio_dev); + if (ret) { + imx8qxp_adc_disable(adc); + dev_err(dev, "Couldn't register the device.\n"); + goto error_ipg_clk_disable; + } + + pm_runtime_set_active(dev); + pm_runtime_set_autosuspend_delay(dev, 50); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + + return 0; + +error_ipg_clk_disable: + clk_disable_unprepare(adc->ipg_clk); +error_adc_clk_disable: + clk_disable_unprepare(adc->clk); +error_regulator_disable: + regulator_disable(adc->vref); + + return ret; +} + +static void imx8qxp_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct imx8qxp_adc *adc = iio_priv(indio_dev); + struct device *dev = adc->dev; + + pm_runtime_get_sync(dev); + + iio_device_unregister(indio_dev); + + imx8qxp_adc_disable(adc); + + clk_disable_unprepare(adc->clk); + clk_disable_unprepare(adc->ipg_clk); + regulator_disable(adc->vref); + + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); +} + +static int imx8qxp_adc_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct imx8qxp_adc *adc = iio_priv(indio_dev); + + imx8qxp_adc_disable(adc); + + clk_disable_unprepare(adc->clk); + clk_disable_unprepare(adc->ipg_clk); + regulator_disable(adc->vref); + + return 0; +} + +static int imx8qxp_adc_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct imx8qxp_adc *adc = iio_priv(indio_dev); + int ret; + + ret = regulator_enable(adc->vref); + if (ret) { + dev_err(dev, "Can't enable adc reference top voltage, err = %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(adc->clk); + if (ret) { + dev_err(dev, "Could not prepare or enable clock.\n"); + goto err_disable_reg; + } + + ret = clk_prepare_enable(adc->ipg_clk); + if (ret) { + dev_err(dev, "Could not prepare or enable clock.\n"); + goto err_unprepare_clk; + } + + imx8qxp_adc_reset(adc); + + return 0; + +err_unprepare_clk: + clk_disable_unprepare(adc->clk); + +err_disable_reg: + regulator_disable(adc->vref); + + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(imx8qxp_adc_pm_ops, + imx8qxp_adc_runtime_suspend, + imx8qxp_adc_runtime_resume, NULL); + +static const struct of_device_id imx8qxp_adc_match[] = { + { .compatible = "nxp,imx8qxp-adc", }, + { } +}; +MODULE_DEVICE_TABLE(of, imx8qxp_adc_match); + +static struct platform_driver imx8qxp_adc_driver = { + .probe = imx8qxp_adc_probe, + .remove = imx8qxp_adc_remove, + .driver = { + .name = ADC_DRIVER_NAME, + .of_match_table = imx8qxp_adc_match, + .pm = pm_ptr(&imx8qxp_adc_pm_ops), + }, +}; + +module_platform_driver(imx8qxp_adc_driver); + +MODULE_DESCRIPTION("i.MX8QuadXPlus ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/imx93_adc.c b/drivers/iio/adc/imx93_adc.c new file mode 100644 index 000000000..797adaf37 --- /dev/null +++ b/drivers/iio/adc/imx93_adc.c @@ -0,0 +1,494 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * NXP i.MX93 ADC driver + * + * Copyright 2023 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IMX93_ADC_DRIVER_NAME "imx93-adc" + +/* Register map definition */ +#define IMX93_ADC_MCR 0x00 +#define IMX93_ADC_MSR 0x04 +#define IMX93_ADC_ISR 0x10 +#define IMX93_ADC_IMR 0x20 +#define IMX93_ADC_CIMR0 0x24 +#define IMX93_ADC_CTR0 0x94 +#define IMX93_ADC_NCMR0 0xA4 +#define IMX93_ADC_PCDR0 0x100 +#define IMX93_ADC_PCDR1 0x104 +#define IMX93_ADC_PCDR2 0x108 +#define IMX93_ADC_PCDR3 0x10C +#define IMX93_ADC_PCDR4 0x110 +#define IMX93_ADC_PCDR5 0x114 +#define IMX93_ADC_PCDR6 0x118 +#define IMX93_ADC_PCDR7 0x11C +#define IMX93_ADC_CALSTAT 0x39C +#define IMX93_ADC_CALCFG0 0x3A0 + +/* ADC bit shift */ +#define IMX93_ADC_MCR_MODE_MASK BIT(29) +#define IMX93_ADC_MCR_NSTART_MASK BIT(24) +#define IMX93_ADC_MCR_CALSTART_MASK BIT(14) +#define IMX93_ADC_MCR_ADCLKSE_MASK BIT(8) +#define IMX93_ADC_MCR_PWDN_MASK BIT(0) +#define IMX93_ADC_MSR_CALFAIL_MASK BIT(30) +#define IMX93_ADC_MSR_CALBUSY_MASK BIT(29) +#define IMX93_ADC_MSR_ADCSTATUS_MASK GENMASK(2, 0) +#define IMX93_ADC_ISR_ECH_MASK BIT(0) +#define IMX93_ADC_ISR_EOC_MASK BIT(1) +#define IMX93_ADC_ISR_EOC_ECH_MASK (IMX93_ADC_ISR_EOC_MASK | \ + IMX93_ADC_ISR_ECH_MASK) +#define IMX93_ADC_IMR_JEOC_MASK BIT(3) +#define IMX93_ADC_IMR_JECH_MASK BIT(2) +#define IMX93_ADC_IMR_EOC_MASK BIT(1) +#define IMX93_ADC_IMR_ECH_MASK BIT(0) +#define IMX93_ADC_PCDR_CDATA_MASK GENMASK(11, 0) + +#define IMX93_ADC_CALCFG0_LDFAIL_MASK BIT(4) + +/* ADC status */ +#define IMX93_ADC_MSR_ADCSTATUS_IDLE 0 +#define IMX93_ADC_MSR_ADCSTATUS_POWER_DOWN 1 +#define IMX93_ADC_MSR_ADCSTATUS_WAIT_STATE 2 +#define IMX93_ADC_MSR_ADCSTATUS_BUSY_IN_CALIBRATION 3 +#define IMX93_ADC_MSR_ADCSTATUS_SAMPLE 4 +#define IMX93_ADC_MSR_ADCSTATUS_CONVERSION 6 + +#define IMX93_ADC_TIMEOUT msecs_to_jiffies(100) + +struct imx93_adc { + struct device *dev; + void __iomem *regs; + struct clk *ipg_clk; + int irq; + struct regulator *vref; + /* lock to protect against multiple access to the device */ + struct mutex lock; + struct completion completion; +}; + +#define IMX93_ADC_CHAN(_idx) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ +} + +static const struct iio_chan_spec imx93_adc_iio_channels[] = { + IMX93_ADC_CHAN(0), + IMX93_ADC_CHAN(1), + IMX93_ADC_CHAN(2), + IMX93_ADC_CHAN(3), + IMX93_ADC_CHAN(4), + IMX93_ADC_CHAN(5), + IMX93_ADC_CHAN(6), + IMX93_ADC_CHAN(7), +}; + +static void imx93_adc_power_down(struct imx93_adc *adc) +{ + u32 mcr, msr; + int ret; + + mcr = readl(adc->regs + IMX93_ADC_MCR); + mcr |= FIELD_PREP(IMX93_ADC_MCR_PWDN_MASK, 1); + writel(mcr, adc->regs + IMX93_ADC_MCR); + + ret = readl_poll_timeout(adc->regs + IMX93_ADC_MSR, msr, + ((msr & IMX93_ADC_MSR_ADCSTATUS_MASK) == + IMX93_ADC_MSR_ADCSTATUS_POWER_DOWN), + 1, 50); + if (ret == -ETIMEDOUT) + dev_warn(adc->dev, + "ADC do not in power down mode, current MSR is %x\n", + msr); +} + +static void imx93_adc_power_up(struct imx93_adc *adc) +{ + u32 mcr; + + /* bring ADC out of power down state, in idle state */ + mcr = readl(adc->regs + IMX93_ADC_MCR); + mcr &= ~FIELD_PREP(IMX93_ADC_MCR_PWDN_MASK, 1); + writel(mcr, adc->regs + IMX93_ADC_MCR); +} + +static void imx93_adc_config_ad_clk(struct imx93_adc *adc) +{ + u32 mcr; + + /* put adc in power down mode */ + imx93_adc_power_down(adc); + + /* config the AD_CLK equal to bus clock */ + mcr = readl(adc->regs + IMX93_ADC_MCR); + mcr |= FIELD_PREP(IMX93_ADC_MCR_ADCLKSE_MASK, 1); + writel(mcr, adc->regs + IMX93_ADC_MCR); + + imx93_adc_power_up(adc); +} + +static int imx93_adc_calibration(struct imx93_adc *adc) +{ + u32 mcr, msr, calcfg; + int ret; + + /* make sure ADC in power down mode */ + imx93_adc_power_down(adc); + + /* config SAR controller operating clock */ + mcr = readl(adc->regs + IMX93_ADC_MCR); + mcr &= ~FIELD_PREP(IMX93_ADC_MCR_ADCLKSE_MASK, 1); + writel(mcr, adc->regs + IMX93_ADC_MCR); + + imx93_adc_power_up(adc); + + /* Enable loading of calibrated values even in fail condition */ + calcfg = readl(adc->regs + IMX93_ADC_CALCFG0); + calcfg |= IMX93_ADC_CALCFG0_LDFAIL_MASK; + writel(calcfg, adc->regs + IMX93_ADC_CALCFG0); + + /* + * TODO: we use the default TSAMP/NRSMPL/AVGEN in MCR, + * can add the setting of these bit if need in future. + */ + + /* run calibration */ + mcr = readl(adc->regs + IMX93_ADC_MCR); + mcr |= FIELD_PREP(IMX93_ADC_MCR_CALSTART_MASK, 1); + writel(mcr, adc->regs + IMX93_ADC_MCR); + + /* wait calibration to be finished */ + ret = readl_poll_timeout(adc->regs + IMX93_ADC_MSR, msr, + !(msr & IMX93_ADC_MSR_CALBUSY_MASK), 1000, 2000000); + if (ret == -ETIMEDOUT) { + dev_warn(adc->dev, "ADC do not finish calibration in 2 min!\n"); + imx93_adc_power_down(adc); + return ret; + } + + /* check whether calbration is success or not */ + msr = readl(adc->regs + IMX93_ADC_MSR); + if (msr & IMX93_ADC_MSR_CALFAIL_MASK) { + /* + * Only give warning here, this means the noise of the + * reference voltage do not meet the requirement: + * ADC reference voltage Noise < 1.8V * 1/2^ENOB + * And the resault of ADC is not that accurate. + */ + dev_warn(adc->dev, "ADC calibration failed!\n"); + } + + return 0; +} + +static int imx93_adc_read_channel_conversion(struct imx93_adc *adc, + int channel_number, + int *result) +{ + u32 channel; + u32 imr, mcr, pcda; + long ret; + + reinit_completion(&adc->completion); + + /* config channel mask register */ + channel = 1 << channel_number; + writel(channel, adc->regs + IMX93_ADC_NCMR0); + + /* TODO: can config desired sample time in CTRn if need */ + + /* config interrupt mask */ + imr = FIELD_PREP(IMX93_ADC_IMR_EOC_MASK, 1); + writel(imr, adc->regs + IMX93_ADC_IMR); + writel(channel, adc->regs + IMX93_ADC_CIMR0); + + /* config one-shot mode */ + mcr = readl(adc->regs + IMX93_ADC_MCR); + mcr &= ~FIELD_PREP(IMX93_ADC_MCR_MODE_MASK, 1); + writel(mcr, adc->regs + IMX93_ADC_MCR); + + /* start normal conversion */ + mcr = readl(adc->regs + IMX93_ADC_MCR); + mcr |= FIELD_PREP(IMX93_ADC_MCR_NSTART_MASK, 1); + writel(mcr, adc->regs + IMX93_ADC_MCR); + + ret = wait_for_completion_interruptible_timeout(&adc->completion, + IMX93_ADC_TIMEOUT); + if (ret == 0) + return -ETIMEDOUT; + + if (ret < 0) + return ret; + + pcda = readl(adc->regs + IMX93_ADC_PCDR0 + channel_number * 4); + + *result = FIELD_GET(IMX93_ADC_PCDR_CDATA_MASK, pcda); + + return ret; +} + +static int imx93_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct imx93_adc *adc = iio_priv(indio_dev); + struct device *dev = adc->dev; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + pm_runtime_get_sync(dev); + mutex_lock(&adc->lock); + ret = imx93_adc_read_channel_conversion(adc, chan->channel, val); + mutex_unlock(&adc->lock); + pm_runtime_put_sync_autosuspend(dev); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + ret = regulator_get_voltage(adc->vref); + if (ret < 0) + return ret; + *val = ret / 1000; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = clk_get_rate(adc->ipg_clk); + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static irqreturn_t imx93_adc_isr(int irq, void *dev_id) +{ + struct imx93_adc *adc = dev_id; + u32 isr, eoc, unexpected; + + isr = readl(adc->regs + IMX93_ADC_ISR); + + if (FIELD_GET(IMX93_ADC_ISR_EOC_ECH_MASK, isr)) { + eoc = isr & IMX93_ADC_ISR_EOC_ECH_MASK; + writel(eoc, adc->regs + IMX93_ADC_ISR); + complete(&adc->completion); + } + + unexpected = isr & ~IMX93_ADC_ISR_EOC_ECH_MASK; + if (unexpected) { + writel(unexpected, adc->regs + IMX93_ADC_ISR); + dev_err(adc->dev, "Unexpected interrupt 0x%08x.\n", unexpected); + return IRQ_NONE; + } + + return IRQ_HANDLED; +} + +static const struct iio_info imx93_adc_iio_info = { + .read_raw = &imx93_adc_read_raw, +}; + +static int imx93_adc_probe(struct platform_device *pdev) +{ + struct imx93_adc *adc; + struct iio_dev *indio_dev; + struct device *dev = &pdev->dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->dev = dev; + + mutex_init(&adc->lock); + adc->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(adc->regs)) + return dev_err_probe(dev, PTR_ERR(adc->regs), + "Failed getting ioremap resource\n"); + + /* The third irq is for ADC conversion usage */ + adc->irq = platform_get_irq(pdev, 2); + if (adc->irq < 0) + return adc->irq; + + adc->ipg_clk = devm_clk_get(dev, "ipg"); + if (IS_ERR(adc->ipg_clk)) + return dev_err_probe(dev, PTR_ERR(adc->ipg_clk), + "Failed getting clock.\n"); + + adc->vref = devm_regulator_get(dev, "vref"); + if (IS_ERR(adc->vref)) + return dev_err_probe(dev, PTR_ERR(adc->vref), + "Failed getting reference voltage.\n"); + + ret = regulator_enable(adc->vref); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable reference voltage.\n"); + + platform_set_drvdata(pdev, indio_dev); + + init_completion(&adc->completion); + + indio_dev->name = "imx93-adc"; + indio_dev->info = &imx93_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = imx93_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(imx93_adc_iio_channels); + + ret = clk_prepare_enable(adc->ipg_clk); + if (ret) { + dev_err_probe(dev, ret, + "Failed to enable ipg clock.\n"); + goto error_regulator_disable; + } + + ret = request_irq(adc->irq, imx93_adc_isr, 0, IMX93_ADC_DRIVER_NAME, adc); + if (ret < 0) { + dev_err_probe(dev, ret, + "Failed requesting irq, irq = %d\n", adc->irq); + goto error_ipg_clk_disable; + } + + ret = imx93_adc_calibration(adc); + if (ret < 0) + goto error_free_adc_irq; + + imx93_adc_config_ad_clk(adc); + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err_probe(dev, ret, + "Failed to register this iio device.\n"); + goto error_adc_power_down; + } + + pm_runtime_set_active(dev); + pm_runtime_set_autosuspend_delay(dev, 50); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + + return 0; + +error_adc_power_down: + imx93_adc_power_down(adc); +error_free_adc_irq: + free_irq(adc->irq, adc); +error_ipg_clk_disable: + clk_disable_unprepare(adc->ipg_clk); +error_regulator_disable: + regulator_disable(adc->vref); + + return ret; +} + +static void imx93_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct imx93_adc *adc = iio_priv(indio_dev); + struct device *dev = adc->dev; + + /* adc power down need clock on */ + pm_runtime_get_sync(dev); + + pm_runtime_disable(dev); + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_put_noidle(dev); + + iio_device_unregister(indio_dev); + imx93_adc_power_down(adc); + free_irq(adc->irq, adc); + clk_disable_unprepare(adc->ipg_clk); + regulator_disable(adc->vref); +} + +static int imx93_adc_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct imx93_adc *adc = iio_priv(indio_dev); + + imx93_adc_power_down(adc); + clk_disable_unprepare(adc->ipg_clk); + regulator_disable(adc->vref); + + return 0; +} + +static int imx93_adc_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct imx93_adc *adc = iio_priv(indio_dev); + int ret; + + ret = regulator_enable(adc->vref); + if (ret) { + dev_err(dev, + "Can't enable adc reference top voltage, err = %d\n", + ret); + return ret; + } + + ret = clk_prepare_enable(adc->ipg_clk); + if (ret) { + dev_err(dev, "Could not prepare or enable clock.\n"); + goto err_disable_reg; + } + + imx93_adc_power_up(adc); + + return 0; + +err_disable_reg: + regulator_disable(adc->vref); + + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(imx93_adc_pm_ops, + imx93_adc_runtime_suspend, + imx93_adc_runtime_resume, NULL); + +static const struct of_device_id imx93_adc_match[] = { + { .compatible = "nxp,imx93-adc", }, + { } +}; +MODULE_DEVICE_TABLE(of, imx93_adc_match); + +static struct platform_driver imx93_adc_driver = { + .probe = imx93_adc_probe, + .remove = imx93_adc_remove, + .driver = { + .name = IMX93_ADC_DRIVER_NAME, + .of_match_table = imx93_adc_match, + .pm = pm_ptr(&imx93_adc_pm_ops), + }, +}; + +module_platform_driver(imx93_adc_driver); + +MODULE_DESCRIPTION("NXP i.MX93 ADC driver"); +MODULE_AUTHOR("Haibo Chen "); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/ina2xx-adc.c b/drivers/iio/adc/ina2xx-adc.c new file mode 100644 index 000000000..c412f5ed4 --- /dev/null +++ b/drivers/iio/adc/ina2xx-adc.c @@ -0,0 +1,1133 @@ +/* + * INA2XX Current and Power Monitors + * + * Copyright 2015 Baylibre SAS. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Based on linux/drivers/iio/adc/ad7291.c + * Copyright 2010-2011 Analog Devices Inc. + * + * Based on linux/drivers/hwmon/ina2xx.c + * Copyright 2012 Lothar Felten + * + * Licensed under the GPL-2 or later. + * + * IIO driver for INA219-220-226-230-231 + * + * Configurable 7-bit I2C slave address from 0x40 to 0x4F + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* INA2XX registers definition */ +#define INA2XX_CONFIG 0x00 +#define INA2XX_SHUNT_VOLTAGE 0x01 /* readonly */ +#define INA2XX_BUS_VOLTAGE 0x02 /* readonly */ +#define INA2XX_POWER 0x03 /* readonly */ +#define INA2XX_CURRENT 0x04 /* readonly */ +#define INA2XX_CALIBRATION 0x05 + +#define INA226_MASK_ENABLE 0x06 +#define INA226_CVRF BIT(3) + +#define INA2XX_MAX_REGISTERS 8 + +/* settings - depend on use case */ +#define INA219_CONFIG_DEFAULT 0x399F /* PGA=1/8, BRNG=32V */ +#define INA219_DEFAULT_IT 532 +#define INA219_DEFAULT_BRNG 1 /* 32V */ +#define INA219_DEFAULT_PGA 125 /* 1000/8 */ +#define INA226_CONFIG_DEFAULT 0x4327 +#define INA226_DEFAULT_AVG 4 +#define INA226_DEFAULT_IT 1110 + +#define INA2XX_RSHUNT_DEFAULT 10000 + +/* + * bit masks for reading the settings in the configuration register + * FIXME: use regmap_fields. + */ +#define INA2XX_MODE_MASK GENMASK(3, 0) + +/* Gain for VShunt: 1/8 (default), 1/4, 1/2, 1 */ +#define INA219_PGA_MASK GENMASK(12, 11) +#define INA219_SHIFT_PGA(val) ((val) << 11) + +/* VBus range: 32V (default), 16V */ +#define INA219_BRNG_MASK BIT(13) +#define INA219_SHIFT_BRNG(val) ((val) << 13) + +/* Averaging for VBus/VShunt/Power */ +#define INA226_AVG_MASK GENMASK(11, 9) +#define INA226_SHIFT_AVG(val) ((val) << 9) + +/* Integration time for VBus */ +#define INA219_ITB_MASK GENMASK(10, 7) +#define INA219_SHIFT_ITB(val) ((val) << 7) +#define INA226_ITB_MASK GENMASK(8, 6) +#define INA226_SHIFT_ITB(val) ((val) << 6) + +/* Integration time for VShunt */ +#define INA219_ITS_MASK GENMASK(6, 3) +#define INA219_SHIFT_ITS(val) ((val) << 3) +#define INA226_ITS_MASK GENMASK(5, 3) +#define INA226_SHIFT_ITS(val) ((val) << 3) + +/* INA219 Bus voltage register, low bits are flags */ +#define INA219_OVF BIT(0) +#define INA219_CNVR BIT(1) +#define INA219_BUS_VOLTAGE_SHIFT 3 + +/* Cosmetic macro giving the sampling period for a full P=UxI cycle */ +#define SAMPLING_PERIOD(c) ((c->int_time_vbus + c->int_time_vshunt) \ + * c->avg) + +static bool ina2xx_is_writeable_reg(struct device *dev, unsigned int reg) +{ + return (reg == INA2XX_CONFIG) || (reg > INA2XX_CURRENT); +} + +static bool ina2xx_is_volatile_reg(struct device *dev, unsigned int reg) +{ + return (reg != INA2XX_CONFIG); +} + +static inline bool is_signed_reg(unsigned int reg) +{ + return (reg == INA2XX_SHUNT_VOLTAGE) || (reg == INA2XX_CURRENT); +} + +static const struct regmap_config ina2xx_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = INA2XX_MAX_REGISTERS, + .writeable_reg = ina2xx_is_writeable_reg, + .volatile_reg = ina2xx_is_volatile_reg, +}; + +enum ina2xx_ids { ina219, ina226, ina236 }; + +struct ina2xx_config { + const char *name; + u16 config_default; + int calibration_value; + int shunt_voltage_lsb; /* nV */ + int bus_voltage_shift; /* position of lsb */ + int bus_voltage_lsb; /* uV */ + /* fixed relation between current and power lsb, uW/uA */ + int power_lsb_factor; + enum ina2xx_ids chip_id; +}; + +struct ina2xx_chip_info { + struct regmap *regmap; + struct task_struct *task; + const struct ina2xx_config *config; + struct mutex state_lock; + unsigned int shunt_resistor_uohm; + int avg; + int int_time_vbus; /* Bus voltage integration time uS */ + int int_time_vshunt; /* Shunt voltage integration time uS */ + int range_vbus; /* Bus voltage maximum in V */ + int pga_gain_vshunt; /* Shunt voltage PGA gain */ + bool allow_async_readout; + /* data buffer needs space for channel data and timestamp */ + struct { + u16 chan[4]; + aligned_s64 ts; + } scan; +}; + +static const struct ina2xx_config ina2xx_config[] = { + [ina219] = { + .name = "ina219", + .config_default = INA219_CONFIG_DEFAULT, + .calibration_value = 4096, + .shunt_voltage_lsb = 10000, + .bus_voltage_shift = INA219_BUS_VOLTAGE_SHIFT, + .bus_voltage_lsb = 4000, + .power_lsb_factor = 20, + .chip_id = ina219, + }, + [ina226] = { + .name = "ina226", + .config_default = INA226_CONFIG_DEFAULT, + .calibration_value = 2048, + .shunt_voltage_lsb = 2500, + .bus_voltage_shift = 0, + .bus_voltage_lsb = 1250, + .power_lsb_factor = 25, + .chip_id = ina226, + }, + [ina236] = { + .name = "ina236", + .config_default = INA226_CONFIG_DEFAULT, + .calibration_value = 2048, + .shunt_voltage_lsb = 2500, + .bus_voltage_shift = 0, + .bus_voltage_lsb = 1600, + .power_lsb_factor = 32, + .chip_id = ina236, + }, +}; + +static int ina2xx_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + unsigned int regval; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(chip->regmap, chan->address, ®val); + if (ret) + return ret; + + if (is_signed_reg(chan->address)) + *val = (s16) regval; + else + *val = regval; + + if (chan->address == INA2XX_BUS_VOLTAGE) + *val >>= chip->config->bus_voltage_shift; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = chip->avg; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + if (chan->address == INA2XX_SHUNT_VOLTAGE) + *val2 = chip->int_time_vshunt; + else + *val2 = chip->int_time_vbus; + + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_CHAN_INFO_SAMP_FREQ: + /* + * Sample freq is read only, it is a consequence of + * 1/AVG*(CT_bus+CT_shunt). + */ + *val = DIV_ROUND_CLOSEST(1000000, SAMPLING_PERIOD(chip)); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + switch (chan->address) { + case INA2XX_SHUNT_VOLTAGE: + /* processed (mV) = raw * lsb(nV) / 1000000 */ + *val = chip->config->shunt_voltage_lsb; + *val2 = 1000000; + return IIO_VAL_FRACTIONAL; + + case INA2XX_BUS_VOLTAGE: + /* processed (mV) = raw * lsb (uV) / 1000 */ + *val = chip->config->bus_voltage_lsb; + *val2 = 1000; + return IIO_VAL_FRACTIONAL; + + case INA2XX_CURRENT: + /* + * processed (mA) = raw * current_lsb (mA) + * current_lsb (mA) = shunt_voltage_lsb (nV) / + * shunt_resistor (uOhm) + */ + *val = chip->config->shunt_voltage_lsb; + *val2 = chip->shunt_resistor_uohm; + return IIO_VAL_FRACTIONAL; + + case INA2XX_POWER: + /* + * processed (mW) = raw * power_lsb (mW) + * power_lsb (mW) = power_lsb_factor (mW/mA) * + * current_lsb (mA) + */ + *val = chip->config->power_lsb_factor * + chip->config->shunt_voltage_lsb; + *val2 = chip->shunt_resistor_uohm; + return IIO_VAL_FRACTIONAL; + } + return -EINVAL; + + case IIO_CHAN_INFO_HARDWAREGAIN: + switch (chan->address) { + case INA2XX_SHUNT_VOLTAGE: + *val = chip->pga_gain_vshunt; + *val2 = 1000; + return IIO_VAL_FRACTIONAL; + + case INA2XX_BUS_VOLTAGE: + *val = chip->range_vbus == 32 ? 1 : 2; + return IIO_VAL_INT; + } + return -EINVAL; + } + + return -EINVAL; +} + +/* + * Available averaging rates for ina226. The indices correspond with + * the bit values expected by the chip (according to the ina226 datasheet, + * table 3 AVG bit settings, found at + * https://www.ti.com/lit/ds/symlink/ina226.pdf. + */ +static const int ina226_avg_tab[] = { 1, 4, 16, 64, 128, 256, 512, 1024 }; + +static int ina226_set_average(struct ina2xx_chip_info *chip, unsigned int val, + unsigned int *config) +{ + int bits; + + if (val > 1024 || val < 1) + return -EINVAL; + + bits = find_closest(val, ina226_avg_tab, + ARRAY_SIZE(ina226_avg_tab)); + + chip->avg = ina226_avg_tab[bits]; + + *config &= ~INA226_AVG_MASK; + *config |= INA226_SHIFT_AVG(bits) & INA226_AVG_MASK; + + return 0; +} + +/* Conversion times in uS */ +static const int ina226_conv_time_tab[] = { 140, 204, 332, 588, 1100, + 2116, 4156, 8244 }; + +static int ina226_set_int_time_vbus(struct ina2xx_chip_info *chip, + unsigned int val_us, unsigned int *config) +{ + int bits; + + if (val_us > 8244 || val_us < 140) + return -EINVAL; + + bits = find_closest(val_us, ina226_conv_time_tab, + ARRAY_SIZE(ina226_conv_time_tab)); + + chip->int_time_vbus = ina226_conv_time_tab[bits]; + + *config &= ~INA226_ITB_MASK; + *config |= INA226_SHIFT_ITB(bits) & INA226_ITB_MASK; + + return 0; +} + +static int ina226_set_int_time_vshunt(struct ina2xx_chip_info *chip, + unsigned int val_us, unsigned int *config) +{ + int bits; + + if (val_us > 8244 || val_us < 140) + return -EINVAL; + + bits = find_closest(val_us, ina226_conv_time_tab, + ARRAY_SIZE(ina226_conv_time_tab)); + + chip->int_time_vshunt = ina226_conv_time_tab[bits]; + + *config &= ~INA226_ITS_MASK; + *config |= INA226_SHIFT_ITS(bits) & INA226_ITS_MASK; + + return 0; +} + +/* Conversion times in uS. */ +static const int ina219_conv_time_tab_subsample[] = { 84, 148, 276, 532 }; +static const int ina219_conv_time_tab_average[] = { 532, 1060, 2130, 4260, + 8510, 17020, 34050, 68100}; + +static int ina219_lookup_int_time(unsigned int *val_us, int *bits) +{ + if (*val_us > 68100 || *val_us < 84) + return -EINVAL; + + if (*val_us <= 532) { + *bits = find_closest(*val_us, ina219_conv_time_tab_subsample, + ARRAY_SIZE(ina219_conv_time_tab_subsample)); + *val_us = ina219_conv_time_tab_subsample[*bits]; + } else { + *bits = find_closest(*val_us, ina219_conv_time_tab_average, + ARRAY_SIZE(ina219_conv_time_tab_average)); + *val_us = ina219_conv_time_tab_average[*bits]; + *bits |= 0x8; + } + + return 0; +} + +static int ina219_set_int_time_vbus(struct ina2xx_chip_info *chip, + unsigned int val_us, unsigned int *config) +{ + int bits, ret; + unsigned int val_us_best = val_us; + + ret = ina219_lookup_int_time(&val_us_best, &bits); + if (ret) + return ret; + + chip->int_time_vbus = val_us_best; + + *config &= ~INA219_ITB_MASK; + *config |= INA219_SHIFT_ITB(bits) & INA219_ITB_MASK; + + return 0; +} + +static int ina219_set_int_time_vshunt(struct ina2xx_chip_info *chip, + unsigned int val_us, unsigned int *config) +{ + int bits, ret; + unsigned int val_us_best = val_us; + + ret = ina219_lookup_int_time(&val_us_best, &bits); + if (ret) + return ret; + + chip->int_time_vshunt = val_us_best; + + *config &= ~INA219_ITS_MASK; + *config |= INA219_SHIFT_ITS(bits) & INA219_ITS_MASK; + + return 0; +} + +static const int ina219_vbus_range_tab[] = { 1, 2 }; +static int ina219_set_vbus_range_denom(struct ina2xx_chip_info *chip, + unsigned int range, + unsigned int *config) +{ + if (range == 1) + chip->range_vbus = 32; + else if (range == 2) + chip->range_vbus = 16; + else + return -EINVAL; + + *config &= ~INA219_BRNG_MASK; + *config |= INA219_SHIFT_BRNG(range == 1 ? 1 : 0) & INA219_BRNG_MASK; + + return 0; +} + +static const int ina219_vshunt_gain_tab[] = { 125, 250, 500, 1000 }; +static const int ina219_vshunt_gain_frac[] = { + 125, 1000, 250, 1000, 500, 1000, 1000, 1000 }; + +static int ina219_set_vshunt_pga_gain(struct ina2xx_chip_info *chip, + unsigned int gain, + unsigned int *config) +{ + int bits; + + if (gain < 125 || gain > 1000) + return -EINVAL; + + bits = find_closest(gain, ina219_vshunt_gain_tab, + ARRAY_SIZE(ina219_vshunt_gain_tab)); + + chip->pga_gain_vshunt = ina219_vshunt_gain_tab[bits]; + bits = 3 - bits; + + *config &= ~INA219_PGA_MASK; + *config |= INA219_SHIFT_PGA(bits) & INA219_PGA_MASK; + + return 0; +} + +static int ina2xx_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + switch (chan->address) { + case INA2XX_SHUNT_VOLTAGE: + *type = IIO_VAL_FRACTIONAL; + *length = sizeof(ina219_vshunt_gain_frac) / sizeof(int); + *vals = ina219_vshunt_gain_frac; + return IIO_AVAIL_LIST; + + case INA2XX_BUS_VOLTAGE: + *type = IIO_VAL_INT; + *length = sizeof(ina219_vbus_range_tab) / sizeof(int); + *vals = ina219_vbus_range_tab; + return IIO_AVAIL_LIST; + } + } + + return -EINVAL; +} + +static int ina2xx_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + unsigned int config, tmp; + int ret; + + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + + mutex_lock(&chip->state_lock); + + ret = regmap_read(chip->regmap, INA2XX_CONFIG, &config); + if (ret) + goto err; + + tmp = config; + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + ret = ina226_set_average(chip, val, &tmp); + break; + + case IIO_CHAN_INFO_INT_TIME: + switch (chip->config->chip_id) { + case ina226: + case ina236: + if (chan->address == INA2XX_SHUNT_VOLTAGE) + ret = ina226_set_int_time_vshunt(chip, val2, + &tmp); + else + ret = ina226_set_int_time_vbus(chip, val2, + &tmp); + break; + case ina219: + if (chan->address == INA2XX_SHUNT_VOLTAGE) + ret = ina219_set_int_time_vshunt(chip, val2, + &tmp); + else + ret = ina219_set_int_time_vbus(chip, val2, + &tmp); + break; + default: + ret = -EINVAL; + } + break; + + case IIO_CHAN_INFO_HARDWAREGAIN: + if (chan->address == INA2XX_SHUNT_VOLTAGE) + ret = ina219_set_vshunt_pga_gain(chip, val * 1000 + + val2 / 1000, &tmp); + else + ret = ina219_set_vbus_range_denom(chip, val, &tmp); + break; + + default: + ret = -EINVAL; + } + + if (!ret && (tmp != config)) + ret = regmap_write(chip->regmap, INA2XX_CONFIG, tmp); +err: + mutex_unlock(&chip->state_lock); + + return ret; +} + +static ssize_t ina2xx_allow_async_readout_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ina2xx_chip_info *chip = iio_priv(dev_to_iio_dev(dev)); + + return sysfs_emit(buf, "%d\n", chip->allow_async_readout); +} + +static ssize_t ina2xx_allow_async_readout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct ina2xx_chip_info *chip = iio_priv(dev_to_iio_dev(dev)); + bool val; + int ret; + + ret = kstrtobool(buf, &val); + if (ret) + return ret; + + chip->allow_async_readout = val; + + return len; +} + +/* + * Calibration register is set to the best value, which eliminates + * truncation errors on calculating current register in hardware. + * According to datasheet (INA 226: eq. 3, INA219: eq. 4) the best values + * are 2048 for ina226 and 4096 for ina219. They are hardcoded as + * calibration_value. + */ +static int ina2xx_set_calibration(struct ina2xx_chip_info *chip) +{ + return regmap_write(chip->regmap, INA2XX_CALIBRATION, + chip->config->calibration_value); +} + +static int set_shunt_resistor(struct ina2xx_chip_info *chip, unsigned int val) +{ + if (val == 0 || val > INT_MAX) + return -EINVAL; + + chip->shunt_resistor_uohm = val; + + return 0; +} + +static ssize_t ina2xx_shunt_resistor_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ina2xx_chip_info *chip = iio_priv(dev_to_iio_dev(dev)); + int vals[2] = { chip->shunt_resistor_uohm, 1000000 }; + + return iio_format_value(buf, IIO_VAL_FRACTIONAL, 1, vals); +} + +static ssize_t ina2xx_shunt_resistor_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct ina2xx_chip_info *chip = iio_priv(dev_to_iio_dev(dev)); + int val, val_fract, ret; + + ret = iio_str_to_fixpoint(buf, 100000, &val, &val_fract); + if (ret) + return ret; + + ret = set_shunt_resistor(chip, val * 1000000 + val_fract); + if (ret) + return ret; + + return len; +} + +#define INA219_CHAN(_type, _index, _address) { \ + .type = (_type), \ + .address = (_address), \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = (_index), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + } \ +} + +#define INA226_CHAN(_type, _index, _address) { \ + .type = (_type), \ + .address = (_address), \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .scan_index = (_index), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + } \ +} + +/* + * Sampling Freq is a consequence of the integration times of + * the Voltage channels. + */ +#define INA219_CHAN_VOLTAGE(_index, _address, _shift) { \ + .type = IIO_VOLTAGE, \ + .address = (_address), \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_INT_TIME) | \ + BIT(IIO_CHAN_INFO_HARDWAREGAIN), \ + .info_mask_separate_available = \ + BIT(IIO_CHAN_INFO_HARDWAREGAIN), \ + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = (_index), \ + .scan_type = { \ + .sign = 'u', \ + .shift = _shift, \ + .realbits = 16 - _shift, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + } \ +} + +#define INA226_CHAN_VOLTAGE(_index, _address) { \ + .type = IIO_VOLTAGE, \ + .address = (_address), \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .scan_index = (_index), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + } \ +} + + +static const struct iio_chan_spec ina226_channels[] = { + INA226_CHAN_VOLTAGE(0, INA2XX_SHUNT_VOLTAGE), + INA226_CHAN_VOLTAGE(1, INA2XX_BUS_VOLTAGE), + INA226_CHAN(IIO_POWER, 2, INA2XX_POWER), + INA226_CHAN(IIO_CURRENT, 3, INA2XX_CURRENT), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const struct iio_chan_spec ina219_channels[] = { + INA219_CHAN_VOLTAGE(0, INA2XX_SHUNT_VOLTAGE, 0), + INA219_CHAN_VOLTAGE(1, INA2XX_BUS_VOLTAGE, INA219_BUS_VOLTAGE_SHIFT), + INA219_CHAN(IIO_POWER, 2, INA2XX_POWER), + INA219_CHAN(IIO_CURRENT, 3, INA2XX_CURRENT), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static int ina2xx_conversion_ready(struct iio_dev *indio_dev) +{ + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + int ret; + unsigned int alert; + + /* + * Because the timer thread and the chip conversion clock + * are asynchronous, the period difference will eventually + * result in reading V[k-1] again, or skip V[k] at time Tk. + * In order to resync the timer with the conversion process + * we check the ConVersionReadyFlag. + * On hardware that supports using the ALERT pin to toggle a + * GPIO a triggered buffer could be used instead. + * For now, we do an extra read of the MASK_ENABLE register (INA226) + * resp. the BUS_VOLTAGE register (INA219). + */ + switch (chip->config->chip_id) { + case ina226: + case ina236: + ret = regmap_read(chip->regmap, + INA226_MASK_ENABLE, &alert); + if (ret < 0) + return ret; + + alert &= INA226_CVRF; + break; + case ina219: + ret = regmap_read(chip->regmap, + INA2XX_BUS_VOLTAGE, &alert); + if (ret < 0) + return ret; + alert &= INA219_CNVR; + break; + default: + return -EINVAL; + } + + return !!alert; +} + +static int ina2xx_work_buffer(struct iio_dev *indio_dev) +{ + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + int bit, ret, i = 0; + s64 time; + + time = iio_get_time_ns(indio_dev); + + /* + * Single register reads: bulk_read will not work with ina226/219 + * as there is no auto-increment of the register pointer. + */ + iio_for_each_active_channel(indio_dev, bit) { + unsigned int val; + + ret = regmap_read(chip->regmap, + INA2XX_SHUNT_VOLTAGE + bit, &val); + if (ret < 0) + return ret; + + chip->scan.chan[i++] = val; + } + + iio_push_to_buffers_with_ts(indio_dev, &chip->scan, sizeof(chip->scan), time); + + return 0; +}; + +static int ina2xx_capture_thread(void *data) +{ + struct iio_dev *indio_dev = data; + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + int sampling_us = SAMPLING_PERIOD(chip); + int ret; + struct timespec64 next, now, delta; + s64 delay_us; + + /* + * Poll a bit faster than the chip internal Fs, in case + * we wish to sync with the conversion ready flag. + */ + if (!chip->allow_async_readout) + sampling_us -= 200; + + ktime_get_ts64(&next); + + do { + while (!chip->allow_async_readout) { + ret = ina2xx_conversion_ready(indio_dev); + if (ret < 0) + return ret; + + /* + * If the conversion was not yet finished, + * reset the reference timestamp. + */ + if (ret == 0) + ktime_get_ts64(&next); + else + break; + } + + ret = ina2xx_work_buffer(indio_dev); + if (ret < 0) + return ret; + + ktime_get_ts64(&now); + + /* + * Advance the timestamp for the next poll by one sampling + * interval, and sleep for the remainder (next - now) + * In case "next" has already passed, the interval is added + * multiple times, i.e. samples are dropped. + */ + do { + timespec64_add_ns(&next, 1000 * sampling_us); + delta = timespec64_sub(next, now); + delay_us = div_s64(timespec64_to_ns(&delta), 1000); + } while (delay_us <= 0); + + usleep_range(delay_us, (delay_us * 3) >> 1); + + } while (!kthread_should_stop()); + + return 0; +} + +static int ina2xx_buffer_enable(struct iio_dev *indio_dev) +{ + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + unsigned int sampling_us = SAMPLING_PERIOD(chip); + struct task_struct *task; + + dev_dbg(&indio_dev->dev, "Enabling buffer w/ scan_mask %02x, freq = %d, avg =%u\n", + (unsigned int)(*indio_dev->active_scan_mask), + 1000000 / sampling_us, chip->avg); + + dev_dbg(&indio_dev->dev, "Expected work period: %u us\n", sampling_us); + dev_dbg(&indio_dev->dev, "Async readout mode: %d\n", + chip->allow_async_readout); + + task = kthread_run(ina2xx_capture_thread, (void *)indio_dev, + "%s:%d-%uus", indio_dev->name, + iio_device_id(indio_dev), + sampling_us); + if (IS_ERR(task)) + return PTR_ERR(task); + + chip->task = task; + + return 0; +} + +static int ina2xx_buffer_disable(struct iio_dev *indio_dev) +{ + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + + if (chip->task) { + kthread_stop(chip->task); + chip->task = NULL; + } + + return 0; +} + +static const struct iio_buffer_setup_ops ina2xx_setup_ops = { + .postenable = &ina2xx_buffer_enable, + .predisable = &ina2xx_buffer_disable, +}; + +static int ina2xx_debug_reg(struct iio_dev *indio_dev, + unsigned reg, unsigned writeval, unsigned *readval) +{ + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + + if (!readval) + return regmap_write(chip->regmap, reg, writeval); + + return regmap_read(chip->regmap, reg, readval); +} + +/* Possible integration times for vshunt and vbus */ +static IIO_CONST_ATTR_NAMED(ina219_integration_time_available, + integration_time_available, + "0.000084 0.000148 0.000276 0.000532 0.001060 0.002130 0.004260 0.008510 0.017020 0.034050 0.068100"); + +static IIO_CONST_ATTR_NAMED(ina226_integration_time_available, + integration_time_available, + "0.000140 0.000204 0.000332 0.000588 0.001100 0.002116 0.004156 0.008244"); + +static IIO_DEVICE_ATTR(in_allow_async_readout, S_IRUGO | S_IWUSR, + ina2xx_allow_async_readout_show, + ina2xx_allow_async_readout_store, 0); + +static IIO_DEVICE_ATTR(in_shunt_resistor, S_IRUGO | S_IWUSR, + ina2xx_shunt_resistor_show, + ina2xx_shunt_resistor_store, 0); + +static struct attribute *ina219_attributes[] = { + &iio_dev_attr_in_allow_async_readout.dev_attr.attr, + &iio_const_attr_ina219_integration_time_available.dev_attr.attr, + &iio_dev_attr_in_shunt_resistor.dev_attr.attr, + NULL, +}; + +static struct attribute *ina226_attributes[] = { + &iio_dev_attr_in_allow_async_readout.dev_attr.attr, + &iio_const_attr_ina226_integration_time_available.dev_attr.attr, + &iio_dev_attr_in_shunt_resistor.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ina219_attribute_group = { + .attrs = ina219_attributes, +}; + +static const struct attribute_group ina226_attribute_group = { + .attrs = ina226_attributes, +}; + +static const struct iio_info ina219_info = { + .attrs = &ina219_attribute_group, + .read_raw = ina2xx_read_raw, + .read_avail = ina2xx_read_avail, + .write_raw = ina2xx_write_raw, + .debugfs_reg_access = ina2xx_debug_reg, +}; + +static const struct iio_info ina226_info = { + .attrs = &ina226_attribute_group, + .read_raw = ina2xx_read_raw, + .write_raw = ina2xx_write_raw, + .debugfs_reg_access = ina2xx_debug_reg, +}; + +/* Initialize the configuration and calibration registers. */ +static int ina2xx_init(struct ina2xx_chip_info *chip, unsigned int config) +{ + int ret = regmap_write(chip->regmap, INA2XX_CONFIG, config); + if (ret) + return ret; + + return ina2xx_set_calibration(chip); +} + +static int ina2xx_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct ina2xx_chip_info *chip; + struct iio_dev *indio_dev; + unsigned int val; + enum ina2xx_ids type; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + + /* This is only used for device removal purposes. */ + i2c_set_clientdata(client, indio_dev); + + chip->regmap = devm_regmap_init_i2c(client, &ina2xx_regmap_config); + if (IS_ERR(chip->regmap)) { + dev_err(&client->dev, "failed to allocate register map\n"); + return PTR_ERR(chip->regmap); + } + + if (client->dev.of_node) + type = (uintptr_t)of_device_get_match_data(&client->dev); + else + type = id->driver_data; + chip->config = &ina2xx_config[type]; + + mutex_init(&chip->state_lock); + + if (of_property_read_u32(client->dev.of_node, "shunt-resistor", &val) < 0) + val = INA2XX_RSHUNT_DEFAULT; + + ret = set_shunt_resistor(chip, val); + if (ret) + return ret; + + /* Patch the current config register with default. */ + val = chip->config->config_default; + + switch (type) { + case ina226: + case ina236: + ina226_set_average(chip, INA226_DEFAULT_AVG, &val); + ina226_set_int_time_vbus(chip, INA226_DEFAULT_IT, &val); + ina226_set_int_time_vshunt(chip, INA226_DEFAULT_IT, &val); + break; + case ina219: + chip->avg = 1; + ina219_set_int_time_vbus(chip, INA219_DEFAULT_IT, &val); + ina219_set_int_time_vshunt(chip, INA219_DEFAULT_IT, &val); + ina219_set_vbus_range_denom(chip, INA219_DEFAULT_BRNG, &val); + ina219_set_vshunt_pga_gain(chip, INA219_DEFAULT_PGA, &val); + break; + default: + return -EINVAL; + } + + ret = ina2xx_init(chip, val); + if (ret) { + dev_err(&client->dev, "error configuring the device\n"); + return ret; + } + + indio_dev->modes = INDIO_DIRECT_MODE; + switch (type) { + case ina226: + case ina236: + indio_dev->channels = ina226_channels; + indio_dev->num_channels = ARRAY_SIZE(ina226_channels); + indio_dev->info = &ina226_info; + break; + case ina219: + indio_dev->channels = ina219_channels; + indio_dev->num_channels = ARRAY_SIZE(ina219_channels); + indio_dev->info = &ina219_info; + break; + default: + return -EINVAL; + } + indio_dev->name = id ? id->name : chip->config->name; + + ret = devm_iio_kfifo_buffer_setup(&client->dev, indio_dev, + &ina2xx_setup_ops); + if (ret) + return ret; + + return iio_device_register(indio_dev); +} + +static void ina2xx_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + int ret; + + iio_device_unregister(indio_dev); + + /* Powerdown */ + ret = regmap_clear_bits(chip->regmap, INA2XX_CONFIG, INA2XX_MODE_MASK); + if (ret) + dev_warn(&client->dev, "Failed to power down device (%pe)\n", + ERR_PTR(ret)); +} + +static const struct i2c_device_id ina2xx_id[] = { + { .name = "ina219", .driver_data = ina219 }, + { .name = "ina220", .driver_data = ina219 }, + { .name = "ina226", .driver_data = ina226 }, + { .name = "ina230", .driver_data = ina226 }, + { .name = "ina231", .driver_data = ina226 }, + { .name = "ina236", .driver_data = ina236 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ina2xx_id); + +static const struct of_device_id ina2xx_of_match[] = { + { + .compatible = "ti,ina219", + .data = (void *)ina219 + }, + { + .compatible = "ti,ina220", + .data = (void *)ina219 + }, + { + .compatible = "ti,ina226", + .data = (void *)ina226 + }, + { + .compatible = "ti,ina230", + .data = (void *)ina226 + }, + { + .compatible = "ti,ina231", + .data = (void *)ina226 + }, + { + .compatible = "ti,ina236", + .data = (void *)ina236 + }, + { } +}; +MODULE_DEVICE_TABLE(of, ina2xx_of_match); + +static struct i2c_driver ina2xx_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = ina2xx_of_match, + }, + .probe = ina2xx_probe, + .remove = ina2xx_remove, + .id_table = ina2xx_id, +}; +module_i2c_driver(ina2xx_driver); + +MODULE_AUTHOR("Marc Titinger "); +MODULE_DESCRIPTION("Texas Instruments INA2XX ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/industrialio-adc.c b/drivers/iio/adc/industrialio-adc.c new file mode 100644 index 000000000..b4057230e --- /dev/null +++ b/drivers/iio/adc/industrialio-adc.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Helpers for parsing common ADC information from a firmware node. + * + * Copyright (c) 2025 Matti Vaittinen + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +/** + * devm_iio_adc_device_alloc_chaninfo_se - allocate and fill iio_chan_spec for ADC + * + * Scan the device node for single-ended ADC channel information. Channel ID is + * expected to be found from the "reg" property. Allocate and populate the + * iio_chan_spec structure corresponding to channels that are found. The memory + * for iio_chan_spec structure will be freed upon device detach. + * + * @dev: Pointer to the ADC device. + * @template: Template iio_chan_spec from which the fields of all + * found and allocated channels are initialized. + * @max_chan_id: Maximum value of a channel ID. Use negative value if no + * checking is required. + * @cs: Location where pointer to allocated iio_chan_spec + * should be stored. + * + * Return: Number of found channels on success. Negative value to indicate + * failure. Specifically, -ENOENT if no channel nodes were found. + */ +int devm_iio_adc_device_alloc_chaninfo_se(struct device *dev, + const struct iio_chan_spec *template, + int max_chan_id, + struct iio_chan_spec **cs) +{ + struct iio_chan_spec *chan_array, *chan; + int num_chan, ret; + + num_chan = iio_adc_device_num_channels(dev); + if (num_chan < 0) + return num_chan; + + if (!num_chan) + return -ENOENT; + + chan_array = devm_kcalloc(dev, num_chan, sizeof(*chan_array), + GFP_KERNEL); + if (!chan_array) + return -ENOMEM; + + chan = &chan_array[0]; + + device_for_each_named_child_node_scoped(dev, child, "channel") { + u32 ch; + + ret = fwnode_property_read_u32(child, "reg", &ch); + if (ret) + return ret; + + if (max_chan_id >= 0 && ch > max_chan_id) + return -ERANGE; + + *chan = *template; + chan->channel = ch; + chan++; + } + + *cs = chan_array; + + return num_chan; +} +EXPORT_SYMBOL_NS_GPL(devm_iio_adc_device_alloc_chaninfo_se, "IIO_DRIVER"); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Matti Vaittinen "); +MODULE_DESCRIPTION("IIO ADC fwnode parsing helpers"); diff --git a/drivers/iio/adc/ingenic-adc.c b/drivers/iio/adc/ingenic-adc.c new file mode 100644 index 000000000..71fcdfedb --- /dev/null +++ b/drivers/iio/adc/ingenic-adc.c @@ -0,0 +1,925 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ADC driver for the Ingenic JZ47xx SoCs + * Copyright (c) 2019 Artur Rojek + * + * based on drivers/mfd/jz4740-adc.c + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define JZ_ADC_REG_ENABLE 0x00 +#define JZ_ADC_REG_CFG 0x04 +#define JZ_ADC_REG_CTRL 0x08 +#define JZ_ADC_REG_STATUS 0x0c +#define JZ_ADC_REG_ADSAME 0x10 +#define JZ_ADC_REG_ADWAIT 0x14 +#define JZ_ADC_REG_ADTCH 0x18 +#define JZ_ADC_REG_ADBDAT 0x1c +#define JZ_ADC_REG_ADSDAT 0x20 +#define JZ_ADC_REG_ADCMD 0x24 +#define JZ_ADC_REG_ADCLK 0x28 + +#define JZ_ADC_REG_ENABLE_PD BIT(7) +#define JZ_ADC_REG_CFG_AUX_MD (BIT(0) | BIT(1)) +#define JZ_ADC_REG_CFG_BAT_MD BIT(4) +#define JZ_ADC_REG_CFG_SAMPLE_NUM(n) ((n) << 10) +#define JZ_ADC_REG_CFG_PULL_UP(n) ((n) << 16) +#define JZ_ADC_REG_CFG_CMD_SEL BIT(22) +#define JZ_ADC_REG_CFG_VBAT_SEL BIT(30) +#define JZ_ADC_REG_CFG_TOUCH_OPS_MASK (BIT(31) | GENMASK(23, 10)) +#define JZ_ADC_REG_ADCLK_CLKDIV_LSB 0 +#define JZ4725B_ADC_REG_ADCLK_CLKDIV10US_LSB 16 +#define JZ4770_ADC_REG_ADCLK_CLKDIV10US_LSB 8 +#define JZ4770_ADC_REG_ADCLK_CLKDIVMS_LSB 16 + +#define JZ_ADC_REG_ADCMD_YNADC BIT(7) +#define JZ_ADC_REG_ADCMD_YPADC BIT(8) +#define JZ_ADC_REG_ADCMD_XNADC BIT(9) +#define JZ_ADC_REG_ADCMD_XPADC BIT(10) +#define JZ_ADC_REG_ADCMD_VREFPYP BIT(11) +#define JZ_ADC_REG_ADCMD_VREFPXP BIT(12) +#define JZ_ADC_REG_ADCMD_VREFPXN BIT(13) +#define JZ_ADC_REG_ADCMD_VREFPAUX BIT(14) +#define JZ_ADC_REG_ADCMD_VREFPVDD33 BIT(15) +#define JZ_ADC_REG_ADCMD_VREFNYN BIT(16) +#define JZ_ADC_REG_ADCMD_VREFNXP BIT(17) +#define JZ_ADC_REG_ADCMD_VREFNXN BIT(18) +#define JZ_ADC_REG_ADCMD_VREFAUX BIT(19) +#define JZ_ADC_REG_ADCMD_YNGRU BIT(20) +#define JZ_ADC_REG_ADCMD_XNGRU BIT(21) +#define JZ_ADC_REG_ADCMD_XPGRU BIT(22) +#define JZ_ADC_REG_ADCMD_YPSUP BIT(23) +#define JZ_ADC_REG_ADCMD_XNSUP BIT(24) +#define JZ_ADC_REG_ADCMD_XPSUP BIT(25) + +#define JZ_ADC_AUX_VREF 3300 +#define JZ_ADC_AUX_VREF_BITS 12 +#define JZ_ADC_BATTERY_LOW_VREF 2500 +#define JZ_ADC_BATTERY_LOW_VREF_BITS 12 +#define JZ4725B_ADC_BATTERY_HIGH_VREF 7500 +#define JZ4725B_ADC_BATTERY_HIGH_VREF_BITS 10 +#define JZ4740_ADC_BATTERY_HIGH_VREF (7500 * 0.986) +#define JZ4740_ADC_BATTERY_HIGH_VREF_BITS 12 +#define JZ4760_ADC_BATTERY_VREF 2500 +#define JZ4770_ADC_BATTERY_VREF 1200 +#define JZ4770_ADC_BATTERY_VREF_BITS 12 + +#define JZ_ADC_IRQ_AUX BIT(0) +#define JZ_ADC_IRQ_BATTERY BIT(1) +#define JZ_ADC_IRQ_TOUCH BIT(2) +#define JZ_ADC_IRQ_PEN_DOWN BIT(3) +#define JZ_ADC_IRQ_PEN_UP BIT(4) +#define JZ_ADC_IRQ_PEN_DOWN_SLEEP BIT(5) +#define JZ_ADC_IRQ_SLEEP BIT(7) + +struct ingenic_adc; + +struct ingenic_adc_soc_data { + unsigned int battery_high_vref; + unsigned int battery_high_vref_bits; + const int *battery_raw_avail; + size_t battery_raw_avail_size; + const int *battery_scale_avail; + size_t battery_scale_avail_size; + unsigned int battery_vref_mode: 1; + unsigned int has_aux_md: 1; + const struct iio_chan_spec *channels; + unsigned int num_channels; + int (*init_clk_div)(struct device *dev, struct ingenic_adc *adc); +}; + +struct ingenic_adc { + void __iomem *base; + struct clk *clk; + struct mutex lock; + struct mutex aux_lock; + const struct ingenic_adc_soc_data *soc_data; + bool low_vref_mode; +}; + +static void ingenic_adc_set_adcmd(struct iio_dev *iio_dev, unsigned long mask) +{ + struct ingenic_adc *adc = iio_priv(iio_dev); + + guard(mutex)(&adc->lock); + + /* Init ADCMD */ + readl(adc->base + JZ_ADC_REG_ADCMD); + + if (mask & 0x3) { + /* Second channel (INGENIC_ADC_TOUCH_YP): sample YP vs. GND */ + writel(JZ_ADC_REG_ADCMD_XNGRU + | JZ_ADC_REG_ADCMD_VREFNXN | JZ_ADC_REG_ADCMD_VREFPVDD33 + | JZ_ADC_REG_ADCMD_YPADC, + adc->base + JZ_ADC_REG_ADCMD); + + /* First channel (INGENIC_ADC_TOUCH_XP): sample XP vs. GND */ + writel(JZ_ADC_REG_ADCMD_YNGRU + | JZ_ADC_REG_ADCMD_VREFNYN | JZ_ADC_REG_ADCMD_VREFPVDD33 + | JZ_ADC_REG_ADCMD_XPADC, + adc->base + JZ_ADC_REG_ADCMD); + } + + if (mask & 0xc) { + /* Fourth channel (INGENIC_ADC_TOUCH_YN): sample YN vs. GND */ + writel(JZ_ADC_REG_ADCMD_XNGRU + | JZ_ADC_REG_ADCMD_VREFNXN | JZ_ADC_REG_ADCMD_VREFPVDD33 + | JZ_ADC_REG_ADCMD_YNADC, + adc->base + JZ_ADC_REG_ADCMD); + + /* Third channel (INGENIC_ADC_TOUCH_XN): sample XN vs. GND */ + writel(JZ_ADC_REG_ADCMD_YNGRU + | JZ_ADC_REG_ADCMD_VREFNYN | JZ_ADC_REG_ADCMD_VREFPVDD33 + | JZ_ADC_REG_ADCMD_XNADC, + adc->base + JZ_ADC_REG_ADCMD); + } + + if (mask & 0x30) { + /* Sixth channel (INGENIC_ADC_TOUCH_YD): sample YP vs. YN */ + writel(JZ_ADC_REG_ADCMD_VREFNYN | JZ_ADC_REG_ADCMD_VREFPVDD33 + | JZ_ADC_REG_ADCMD_YPADC, + adc->base + JZ_ADC_REG_ADCMD); + + /* Fifth channel (INGENIC_ADC_TOUCH_XD): sample XP vs. XN */ + writel(JZ_ADC_REG_ADCMD_VREFNXN | JZ_ADC_REG_ADCMD_VREFPVDD33 + | JZ_ADC_REG_ADCMD_XPADC, + adc->base + JZ_ADC_REG_ADCMD); + } + + /* We're done */ + writel(0, adc->base + JZ_ADC_REG_ADCMD); +} + +static void ingenic_adc_set_config(struct ingenic_adc *adc, + uint32_t mask, + uint32_t val) +{ + uint32_t cfg; + + guard(mutex)(&adc->lock); + + cfg = readl(adc->base + JZ_ADC_REG_CFG) & ~mask; + cfg |= val; + writel(cfg, adc->base + JZ_ADC_REG_CFG); +} + +static void __ingenic_adc_enable(struct ingenic_adc *adc, int engine, + bool enabled) +{ + u8 val; + + val = readb(adc->base + JZ_ADC_REG_ENABLE); + + if (enabled) + val |= BIT(engine); + else + val &= ~BIT(engine); + + writeb(val, adc->base + JZ_ADC_REG_ENABLE); +} + +static void ingenic_adc_enable(struct ingenic_adc *adc, + int engine, + bool enabled) +{ + guard(mutex)(&adc->lock); + __ingenic_adc_enable(adc, engine, enabled); +} + +static int ingenic_adc_capture(struct ingenic_adc *adc, + int engine) +{ + u32 cfg; + u8 val; + int ret; + + /* + * Disable CMD_SEL temporarily, because it causes wrong VBAT readings, + * probably due to the switch of VREF. We must keep the lock here to + * avoid races with the buffer enable/disable functions. + */ + guard(mutex)(&adc->lock); + cfg = readl(adc->base + JZ_ADC_REG_CFG); + writel(cfg & ~JZ_ADC_REG_CFG_CMD_SEL, adc->base + JZ_ADC_REG_CFG); + + __ingenic_adc_enable(adc, engine, true); + ret = readb_poll_timeout(adc->base + JZ_ADC_REG_ENABLE, val, + !(val & BIT(engine)), 250, 1000); + if (ret) + __ingenic_adc_enable(adc, engine, false); + + writel(cfg, adc->base + JZ_ADC_REG_CFG); + + return ret; +} + +static int ingenic_adc_write_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long m) +{ + struct ingenic_adc *adc = iio_priv(iio_dev); + struct device *dev = iio_dev->dev.parent; + int ret; + + switch (m) { + case IIO_CHAN_INFO_SCALE: + switch (chan->channel) { + case INGENIC_ADC_BATTERY: + if (!adc->soc_data->battery_vref_mode) + return -EINVAL; + + ret = clk_enable(adc->clk); + if (ret) { + dev_err(dev, "Failed to enable clock: %d\n", + ret); + return ret; + } + + if (val > JZ_ADC_BATTERY_LOW_VREF) { + ingenic_adc_set_config(adc, + JZ_ADC_REG_CFG_BAT_MD, + 0); + adc->low_vref_mode = false; + } else { + ingenic_adc_set_config(adc, + JZ_ADC_REG_CFG_BAT_MD, + JZ_ADC_REG_CFG_BAT_MD); + adc->low_vref_mode = true; + } + + clk_disable(adc->clk); + + return 0; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static const int jz4725b_adc_battery_raw_avail[] = { + 0, 1, (1 << JZ_ADC_BATTERY_LOW_VREF_BITS) - 1, +}; + +static const int jz4725b_adc_battery_scale_avail[] = { + JZ4725B_ADC_BATTERY_HIGH_VREF, JZ4725B_ADC_BATTERY_HIGH_VREF_BITS, + JZ_ADC_BATTERY_LOW_VREF, JZ_ADC_BATTERY_LOW_VREF_BITS, +}; + +static const int jz4740_adc_battery_raw_avail[] = { + 0, 1, (1 << JZ_ADC_BATTERY_LOW_VREF_BITS) - 1, +}; + +static const int jz4740_adc_battery_scale_avail[] = { + JZ4740_ADC_BATTERY_HIGH_VREF, JZ4740_ADC_BATTERY_HIGH_VREF_BITS, + JZ_ADC_BATTERY_LOW_VREF, JZ_ADC_BATTERY_LOW_VREF_BITS, +}; + +static const int jz4760_adc_battery_scale_avail[] = { + JZ4760_ADC_BATTERY_VREF, JZ4770_ADC_BATTERY_VREF_BITS, +}; + +static const int jz4770_adc_battery_raw_avail[] = { + 0, 1, (1 << JZ4770_ADC_BATTERY_VREF_BITS) - 1, +}; + +static const int jz4770_adc_battery_scale_avail[] = { + JZ4770_ADC_BATTERY_VREF, JZ4770_ADC_BATTERY_VREF_BITS, +}; + +static int jz4725b_adc_init_clk_div(struct device *dev, struct ingenic_adc *adc) +{ + struct clk *parent_clk; + unsigned long parent_rate, rate; + unsigned int div_main, div_10us; + + parent_clk = clk_get_parent(adc->clk); + if (!parent_clk) { + dev_err(dev, "ADC clock has no parent\n"); + return -ENODEV; + } + parent_rate = clk_get_rate(parent_clk); + + /* + * The JZ4725B ADC works at 500 kHz to 8 MHz. + * We pick the highest rate possible. + * In practice we typically get 6 MHz, half of the 12 MHz EXT clock. + */ + div_main = DIV_ROUND_UP(parent_rate, 8000000); + div_main = clamp(div_main, 1u, 64u); + rate = parent_rate / div_main; + if (rate < 500000 || rate > 8000000) { + dev_err(dev, "No valid divider for ADC main clock\n"); + return -EINVAL; + } + + /* We also need a divider that produces a 10us clock. */ + div_10us = DIV_ROUND_UP(rate, 100000); + + writel(((div_10us - 1) << JZ4725B_ADC_REG_ADCLK_CLKDIV10US_LSB) | + (div_main - 1) << JZ_ADC_REG_ADCLK_CLKDIV_LSB, + adc->base + JZ_ADC_REG_ADCLK); + + return 0; +} + +static int jz4770_adc_init_clk_div(struct device *dev, struct ingenic_adc *adc) +{ + struct clk *parent_clk; + unsigned long parent_rate, rate; + unsigned int div_main, div_ms, div_10us; + + parent_clk = clk_get_parent(adc->clk); + if (!parent_clk) { + dev_err(dev, "ADC clock has no parent\n"); + return -ENODEV; + } + parent_rate = clk_get_rate(parent_clk); + + /* + * The JZ4770 ADC works at 20 kHz to 200 kHz. + * We pick the highest rate possible. + */ + div_main = DIV_ROUND_UP(parent_rate, 200000); + div_main = clamp(div_main, 1u, 256u); + rate = parent_rate / div_main; + if (rate < 20000 || rate > 200000) { + dev_err(dev, "No valid divider for ADC main clock\n"); + return -EINVAL; + } + + /* We also need a divider that produces a 10us clock. */ + div_10us = DIV_ROUND_UP(rate, 10000); + /* And another, which produces a 1ms clock. */ + div_ms = DIV_ROUND_UP(rate, 1000); + + writel(((div_ms - 1) << JZ4770_ADC_REG_ADCLK_CLKDIVMS_LSB) | + ((div_10us - 1) << JZ4770_ADC_REG_ADCLK_CLKDIV10US_LSB) | + (div_main - 1) << JZ_ADC_REG_ADCLK_CLKDIV_LSB, + adc->base + JZ_ADC_REG_ADCLK); + + return 0; +} + +static const struct iio_chan_spec jz4740_channels[] = { + { + .extend_name = "aux", + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .indexed = 1, + .channel = INGENIC_ADC_AUX, + .scan_index = -1, + }, + { + .extend_name = "battery", + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .indexed = 1, + .channel = INGENIC_ADC_BATTERY, + .scan_index = -1, + }, +}; + +static const struct iio_chan_spec jz4760_channels[] = { + { + .extend_name = "aux", + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .indexed = 1, + .channel = INGENIC_ADC_AUX0, + .scan_index = -1, + }, + { + .extend_name = "aux1", + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .indexed = 1, + .channel = INGENIC_ADC_AUX, + .scan_index = -1, + }, + { + .extend_name = "aux2", + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .indexed = 1, + .channel = INGENIC_ADC_AUX2, + .scan_index = -1, + }, + { + .extend_name = "battery", + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .indexed = 1, + .channel = INGENIC_ADC_BATTERY, + .scan_index = -1, + }, +}; + +static const struct iio_chan_spec jz4770_channels[] = { + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = INGENIC_ADC_TOUCH_XP, + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 12, + .storagebits = 16, + }, + }, + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = INGENIC_ADC_TOUCH_YP, + .scan_index = 1, + .scan_type = { + .sign = 'u', + .realbits = 12, + .storagebits = 16, + }, + }, + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = INGENIC_ADC_TOUCH_XN, + .scan_index = 2, + .scan_type = { + .sign = 'u', + .realbits = 12, + .storagebits = 16, + }, + }, + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = INGENIC_ADC_TOUCH_YN, + .scan_index = 3, + .scan_type = { + .sign = 'u', + .realbits = 12, + .storagebits = 16, + }, + }, + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = INGENIC_ADC_TOUCH_XD, + .scan_index = 4, + .scan_type = { + .sign = 'u', + .realbits = 12, + .storagebits = 16, + }, + }, + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = INGENIC_ADC_TOUCH_YD, + .scan_index = 5, + .scan_type = { + .sign = 'u', + .realbits = 12, + .storagebits = 16, + }, + }, + { + .extend_name = "aux", + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .indexed = 1, + .channel = INGENIC_ADC_AUX, + .scan_index = -1, + }, + { + .extend_name = "battery", + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .indexed = 1, + .channel = INGENIC_ADC_BATTERY, + .scan_index = -1, + }, + { + .extend_name = "aux2", + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .indexed = 1, + .channel = INGENIC_ADC_AUX2, + .scan_index = -1, + }, +}; + +static const struct ingenic_adc_soc_data jz4725b_adc_soc_data = { + .battery_high_vref = JZ4725B_ADC_BATTERY_HIGH_VREF, + .battery_high_vref_bits = JZ4725B_ADC_BATTERY_HIGH_VREF_BITS, + .battery_raw_avail = jz4725b_adc_battery_raw_avail, + .battery_raw_avail_size = ARRAY_SIZE(jz4725b_adc_battery_raw_avail), + .battery_scale_avail = jz4725b_adc_battery_scale_avail, + .battery_scale_avail_size = ARRAY_SIZE(jz4725b_adc_battery_scale_avail), + .battery_vref_mode = true, + .has_aux_md = false, + .channels = jz4740_channels, + .num_channels = ARRAY_SIZE(jz4740_channels), + .init_clk_div = jz4725b_adc_init_clk_div, +}; + +static const struct ingenic_adc_soc_data jz4740_adc_soc_data = { + .battery_high_vref = JZ4740_ADC_BATTERY_HIGH_VREF, + .battery_high_vref_bits = JZ4740_ADC_BATTERY_HIGH_VREF_BITS, + .battery_raw_avail = jz4740_adc_battery_raw_avail, + .battery_raw_avail_size = ARRAY_SIZE(jz4740_adc_battery_raw_avail), + .battery_scale_avail = jz4740_adc_battery_scale_avail, + .battery_scale_avail_size = ARRAY_SIZE(jz4740_adc_battery_scale_avail), + .battery_vref_mode = true, + .has_aux_md = false, + .channels = jz4740_channels, + .num_channels = ARRAY_SIZE(jz4740_channels), + .init_clk_div = NULL, /* no ADCLK register on JZ4740 */ +}; + +static const struct ingenic_adc_soc_data jz4760_adc_soc_data = { + .battery_high_vref = JZ4760_ADC_BATTERY_VREF, + .battery_high_vref_bits = JZ4770_ADC_BATTERY_VREF_BITS, + .battery_raw_avail = jz4770_adc_battery_raw_avail, + .battery_raw_avail_size = ARRAY_SIZE(jz4770_adc_battery_raw_avail), + .battery_scale_avail = jz4760_adc_battery_scale_avail, + .battery_scale_avail_size = ARRAY_SIZE(jz4760_adc_battery_scale_avail), + .battery_vref_mode = false, + .has_aux_md = true, + .channels = jz4760_channels, + .num_channels = ARRAY_SIZE(jz4760_channels), + .init_clk_div = jz4770_adc_init_clk_div, +}; + +static const struct ingenic_adc_soc_data jz4770_adc_soc_data = { + .battery_high_vref = JZ4770_ADC_BATTERY_VREF, + .battery_high_vref_bits = JZ4770_ADC_BATTERY_VREF_BITS, + .battery_raw_avail = jz4770_adc_battery_raw_avail, + .battery_raw_avail_size = ARRAY_SIZE(jz4770_adc_battery_raw_avail), + .battery_scale_avail = jz4770_adc_battery_scale_avail, + .battery_scale_avail_size = ARRAY_SIZE(jz4770_adc_battery_scale_avail), + .battery_vref_mode = false, + .has_aux_md = true, + .channels = jz4770_channels, + .num_channels = ARRAY_SIZE(jz4770_channels), + .init_clk_div = jz4770_adc_init_clk_div, +}; + +static int ingenic_adc_read_avail(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + const int **vals, + int *type, + int *length, + long m) +{ + struct ingenic_adc *adc = iio_priv(iio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + *type = IIO_VAL_INT; + *length = adc->soc_data->battery_raw_avail_size; + *vals = adc->soc_data->battery_raw_avail; + return IIO_AVAIL_RANGE; + case IIO_CHAN_INFO_SCALE: + *type = IIO_VAL_FRACTIONAL_LOG2; + *length = adc->soc_data->battery_scale_avail_size; + *vals = adc->soc_data->battery_scale_avail; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int __ingenic_adc_read_chan(struct ingenic_adc *adc, + struct iio_chan_spec const *chan, + int *val) +{ + int cmd, ret, engine = (chan->channel == INGENIC_ADC_BATTERY); + + if (adc->soc_data->has_aux_md && engine == 0) { + switch (chan->channel) { + case INGENIC_ADC_AUX0: + cmd = 0; + break; + case INGENIC_ADC_AUX: + cmd = 1; + break; + case INGENIC_ADC_AUX2: + cmd = 2; + break; + } + + ingenic_adc_set_config(adc, JZ_ADC_REG_CFG_AUX_MD, cmd); + } + + ret = ingenic_adc_capture(adc, engine); + if (ret) + return ret; + + switch (chan->channel) { + case INGENIC_ADC_AUX0: + case INGENIC_ADC_AUX: + case INGENIC_ADC_AUX2: + *val = readw(adc->base + JZ_ADC_REG_ADSDAT); + break; + case INGENIC_ADC_BATTERY: + *val = readw(adc->base + JZ_ADC_REG_ADBDAT); + break; + } + + return IIO_VAL_INT; +} + +static int ingenic_adc_read_chan_info_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct ingenic_adc *adc = iio_priv(iio_dev); + int ret; + + ret = clk_enable(adc->clk); + if (ret) { + dev_err(iio_dev->dev.parent, "Failed to enable clock: %d\n", ret); + return ret; + } + + /* We cannot sample the aux channels in parallel. */ + scoped_guard(mutex, &adc->aux_lock) + ret = __ingenic_adc_read_chan(adc, chan, val); + + clk_disable(adc->clk); + + return ret; +} + +static int ingenic_adc_read_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ingenic_adc *adc = iio_priv(iio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + return ingenic_adc_read_chan_info_raw(iio_dev, chan, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->channel) { + case INGENIC_ADC_AUX0: + case INGENIC_ADC_AUX: + case INGENIC_ADC_AUX2: + *val = JZ_ADC_AUX_VREF; + *val2 = JZ_ADC_AUX_VREF_BITS; + break; + case INGENIC_ADC_BATTERY: + if (adc->low_vref_mode) { + *val = JZ_ADC_BATTERY_LOW_VREF; + *val2 = JZ_ADC_BATTERY_LOW_VREF_BITS; + } else { + *val = adc->soc_data->battery_high_vref; + *val2 = adc->soc_data->battery_high_vref_bits; + } + break; + } + + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static int ingenic_adc_fwnode_xlate(struct iio_dev *iio_dev, + const struct fwnode_reference_args *iiospec) +{ + int i; + + if (!iiospec->nargs) + return -EINVAL; + + for (i = 0; i < iio_dev->num_channels; ++i) + if (iio_dev->channels[i].channel == iiospec->args[0]) + return i; + + return -EINVAL; +} + +static const struct iio_info ingenic_adc_info = { + .write_raw = ingenic_adc_write_raw, + .read_raw = ingenic_adc_read_raw, + .read_avail = ingenic_adc_read_avail, + .fwnode_xlate = ingenic_adc_fwnode_xlate, +}; + +static int ingenic_adc_buffer_enable(struct iio_dev *iio_dev) +{ + struct ingenic_adc *adc = iio_priv(iio_dev); + int ret; + + ret = clk_enable(adc->clk); + if (ret) { + dev_err(iio_dev->dev.parent, "Failed to enable clock: %d\n", + ret); + return ret; + } + + /* It takes significant time for the touchscreen hw to stabilize. */ + msleep(50); + ingenic_adc_set_config(adc, JZ_ADC_REG_CFG_TOUCH_OPS_MASK, + JZ_ADC_REG_CFG_SAMPLE_NUM(4) | + JZ_ADC_REG_CFG_PULL_UP(4)); + + writew(80, adc->base + JZ_ADC_REG_ADWAIT); + writew(2, adc->base + JZ_ADC_REG_ADSAME); + writeb((u8)~JZ_ADC_IRQ_TOUCH, adc->base + JZ_ADC_REG_CTRL); + writel(0, adc->base + JZ_ADC_REG_ADTCH); + + ingenic_adc_set_config(adc, JZ_ADC_REG_CFG_CMD_SEL, + JZ_ADC_REG_CFG_CMD_SEL); + ingenic_adc_set_adcmd(iio_dev, iio_dev->active_scan_mask[0]); + + ingenic_adc_enable(adc, 2, true); + + return 0; +} + +static int ingenic_adc_buffer_disable(struct iio_dev *iio_dev) +{ + struct ingenic_adc *adc = iio_priv(iio_dev); + + ingenic_adc_enable(adc, 2, false); + + ingenic_adc_set_config(adc, JZ_ADC_REG_CFG_CMD_SEL, 0); + + writeb(0xff, adc->base + JZ_ADC_REG_CTRL); + writeb(0xff, adc->base + JZ_ADC_REG_STATUS); + ingenic_adc_set_config(adc, JZ_ADC_REG_CFG_TOUCH_OPS_MASK, 0); + writew(0, adc->base + JZ_ADC_REG_ADSAME); + writew(0, adc->base + JZ_ADC_REG_ADWAIT); + clk_disable(adc->clk); + + return 0; +} + +static const struct iio_buffer_setup_ops ingenic_buffer_setup_ops = { + .postenable = &ingenic_adc_buffer_enable, + .predisable = &ingenic_adc_buffer_disable +}; + +static irqreturn_t ingenic_adc_irq(int irq, void *data) +{ + struct iio_dev *iio_dev = data; + struct ingenic_adc *adc = iio_priv(iio_dev); + unsigned long mask = iio_dev->active_scan_mask[0]; + unsigned int i; + u32 tdat[3]; + + for (i = 0; i < ARRAY_SIZE(tdat); mask >>= 2, i++) { + if (mask & 0x3) + tdat[i] = readl(adc->base + JZ_ADC_REG_ADTCH); + else + tdat[i] = 0; + } + + iio_push_to_buffers(iio_dev, tdat); + writeb(JZ_ADC_IRQ_TOUCH, adc->base + JZ_ADC_REG_STATUS); + + return IRQ_HANDLED; +} + +static int ingenic_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *iio_dev; + struct ingenic_adc *adc; + const struct ingenic_adc_soc_data *soc_data; + int irq, ret; + + soc_data = device_get_match_data(dev); + if (!soc_data) + return -EINVAL; + + iio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); + if (!iio_dev) + return -ENOMEM; + + adc = iio_priv(iio_dev); + mutex_init(&adc->lock); + mutex_init(&adc->aux_lock); + adc->soc_data = soc_data; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, ingenic_adc_irq, 0, + dev_name(dev), iio_dev); + if (ret < 0) { + dev_err(dev, "Failed to request irq: %d\n", ret); + return ret; + } + + adc->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(adc->base)) + return PTR_ERR(adc->base); + + adc->clk = devm_clk_get_prepared(dev, "adc"); + if (IS_ERR(adc->clk)) { + dev_err(dev, "Unable to get clock\n"); + return PTR_ERR(adc->clk); + } + + ret = clk_enable(adc->clk); + if (ret) { + dev_err(dev, "Failed to enable clock\n"); + return ret; + } + + /* Set clock dividers. */ + if (soc_data->init_clk_div) { + ret = soc_data->init_clk_div(dev, adc); + if (ret) { + clk_disable_unprepare(adc->clk); + return ret; + } + } + + /* Put hardware in a known passive state. */ + writeb(0x00, adc->base + JZ_ADC_REG_ENABLE); + writeb(0xff, adc->base + JZ_ADC_REG_CTRL); + + /* JZ4760B specific */ + if (device_property_present(dev, "ingenic,use-internal-divider")) + ingenic_adc_set_config(adc, JZ_ADC_REG_CFG_VBAT_SEL, + JZ_ADC_REG_CFG_VBAT_SEL); + else + ingenic_adc_set_config(adc, JZ_ADC_REG_CFG_VBAT_SEL, 0); + + usleep_range(2000, 3000); /* Must wait at least 2ms. */ + clk_disable(adc->clk); + + iio_dev->name = "jz-adc"; + iio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + iio_dev->setup_ops = &ingenic_buffer_setup_ops; + iio_dev->channels = soc_data->channels; + iio_dev->num_channels = soc_data->num_channels; + iio_dev->info = &ingenic_adc_info; + + ret = devm_iio_device_register(dev, iio_dev); + if (ret) + dev_err(dev, "Unable to register IIO device\n"); + + return ret; +} + +static const struct of_device_id ingenic_adc_of_match[] = { + { .compatible = "ingenic,jz4725b-adc", .data = &jz4725b_adc_soc_data, }, + { .compatible = "ingenic,jz4740-adc", .data = &jz4740_adc_soc_data, }, + { .compatible = "ingenic,jz4760-adc", .data = &jz4760_adc_soc_data, }, + { .compatible = "ingenic,jz4760b-adc", .data = &jz4760_adc_soc_data, }, + { .compatible = "ingenic,jz4770-adc", .data = &jz4770_adc_soc_data, }, + { } +}; +MODULE_DEVICE_TABLE(of, ingenic_adc_of_match); + +static struct platform_driver ingenic_adc_driver = { + .driver = { + .name = "ingenic-adc", + .of_match_table = ingenic_adc_of_match, + }, + .probe = ingenic_adc_probe, +}; +module_platform_driver(ingenic_adc_driver); +MODULE_DESCRIPTION("ADC driver for the Ingenic JZ47xx SoCs"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/intel_dc_ti_adc.c b/drivers/iio/adc/intel_dc_ti_adc.c new file mode 100644 index 000000000..698a2a304 --- /dev/null +++ b/drivers/iio/adc/intel_dc_ti_adc.c @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Intel Dollar Cove TI PMIC GPADC Driver + * + * Copyright (C) 2014 Intel Corporation (Ramakrishna Pallala ) + * Copyright (C) 2024 - 2025 Hans de Goede + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define DC_TI_ADC_CNTL_REG 0x50 +#define DC_TI_ADC_START BIT(0) +#define DC_TI_ADC_CH_SEL GENMASK(2, 1) +#define DC_TI_ADC_EN BIT(5) +#define DC_TI_ADC_EN_EXT_BPTH_BIAS BIT(6) + +#define DC_TI_VBAT_ZSE_GE_REG 0x53 +#define DC_TI_VBAT_GE GENMASK(3, 0) +#define DC_TI_VBAT_ZSE GENMASK(7, 4) + +/* VBAT GE gain correction is in 0.0015 increments, ZSE is in 1.0 increments */ +#define DC_TI_VBAT_GE_STEP 15 +#define DC_TI_VBAT_GE_DIV 10000 + +#define DC_TI_ADC_DATA_REG_CH(x) (0x54 + 2 * (x)) + +enum dc_ti_adc_id { + DC_TI_ADC_VBAT, + DC_TI_ADC_PMICTEMP, + DC_TI_ADC_BATTEMP, + DC_TI_ADC_SYSTEMP0, +}; + +struct dc_ti_adc_info { + struct mutex lock; /* Protects against concurrent accesses to the ADC */ + wait_queue_head_t wait; + struct device *dev; + struct regmap *regmap; + int vbat_zse; + int vbat_ge; + bool conversion_done; +}; + +static const struct iio_chan_spec dc_ti_adc_channels[] = { + { + .indexed = 1, + .type = IIO_VOLTAGE, + .channel = DC_TI_ADC_VBAT, + .address = DC_TI_ADC_DATA_REG_CH(0), + .datasheet_name = "CH0", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_PROCESSED), + }, { + .indexed = 1, + .type = IIO_TEMP, + .channel = DC_TI_ADC_PMICTEMP, + .address = DC_TI_ADC_DATA_REG_CH(1), + .datasheet_name = "CH1", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .indexed = 1, + .type = IIO_TEMP, + .channel = DC_TI_ADC_BATTEMP, + .address = DC_TI_ADC_DATA_REG_CH(2), + .datasheet_name = "CH2", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .indexed = 1, + .type = IIO_TEMP, + .channel = DC_TI_ADC_SYSTEMP0, + .address = DC_TI_ADC_DATA_REG_CH(3), + .datasheet_name = "CH3", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + } +}; + +static struct iio_map dc_ti_adc_default_maps[] = { + IIO_MAP("CH0", "chtdc_ti_battery", "VBAT"), + IIO_MAP("CH1", "chtdc_ti_battery", "PMICTEMP"), + IIO_MAP("CH2", "chtdc_ti_battery", "BATTEMP"), + IIO_MAP("CH3", "chtdc_ti_battery", "SYSTEMP0"), + { } +}; + +static irqreturn_t dc_ti_adc_isr(int irq, void *data) +{ + struct dc_ti_adc_info *info = data; + + info->conversion_done = true; + wake_up(&info->wait); + return IRQ_HANDLED; +} + +static int dc_ti_adc_scale(struct dc_ti_adc_info *info, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + if (chan->channel != DC_TI_ADC_VBAT) + return -EINVAL; + + /* Vbat ADC scale is 4.6875 mV / unit */ + *val = 4; + *val2 = 687500; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int dc_ti_adc_raw_to_processed(struct dc_ti_adc_info *info, + struct iio_chan_spec const *chan, + int raw, int *val, int *val2) +{ + if (chan->channel != DC_TI_ADC_VBAT) + return -EINVAL; + + /* Apply calibration */ + raw -= info->vbat_zse; + raw = raw * (DC_TI_VBAT_GE_DIV - info->vbat_ge * DC_TI_VBAT_GE_STEP) / + DC_TI_VBAT_GE_DIV; + /* Vbat ADC scale is 4.6875 mV / unit */ + raw *= 46875; + + /* raw is now in 10000 units / mV, convert to milli + milli/1e6 */ + *val = raw / 10000; + *val2 = (raw % 10000) * 100; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int dc_ti_adc_sample(struct dc_ti_adc_info *info, + struct iio_chan_spec const *chan, int *val) +{ + int ret, ch = chan->channel; + __be16 buf; + + info->conversion_done = false; + + /* + * As per TI (PMIC Vendor), the ADC enable and ADC start commands should + * not be sent together. Hence send the commands separately. + */ + ret = regmap_set_bits(info->regmap, DC_TI_ADC_CNTL_REG, DC_TI_ADC_EN); + if (ret) + return ret; + + ret = regmap_update_bits(info->regmap, DC_TI_ADC_CNTL_REG, + DC_TI_ADC_CH_SEL, + FIELD_PREP(DC_TI_ADC_CH_SEL, ch)); + if (ret) + return ret; + + /* + * As per PMIC Vendor, a minimum of 50 ųs delay is required between ADC + * Enable and ADC START commands. This is also recommended by Intel + * Hardware team after the timing analysis of GPADC signals. Since the + * I2C Write transaction to set the channel number also imparts 25 ųs + * delay, we need to wait for another 25 ųs before issuing ADC START. + */ + fsleep(25); + + ret = regmap_set_bits(info->regmap, DC_TI_ADC_CNTL_REG, + DC_TI_ADC_START); + if (ret) + return ret; + + /* TI (PMIC Vendor) recommends 5 s timeout for conversion */ + ret = wait_event_timeout(info->wait, info->conversion_done, 5 * HZ); + if (ret == 0) { + ret = -ETIMEDOUT; + goto disable_adc; + } + + ret = regmap_bulk_read(info->regmap, chan->address, &buf, sizeof(buf)); + if (ret) + goto disable_adc; + + /* The ADC values are 10 bits wide */ + *val = be16_to_cpu(buf) & GENMASK(9, 0); + +disable_adc: + regmap_clear_bits(info->regmap, DC_TI_ADC_CNTL_REG, + DC_TI_ADC_START | DC_TI_ADC_EN); + return ret; +} + +static int dc_ti_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct dc_ti_adc_info *info = iio_priv(indio_dev); + int ret; + + if (mask == IIO_CHAN_INFO_SCALE) + return dc_ti_adc_scale(info, chan, val, val2); + + guard(mutex)(&info->lock); + + /* + * If channel BPTHERM has been selected, first enable the BPTHERM BIAS + * which provides the VREF Voltage reference to convert BPTHERM Input + * voltage to temperature. + */ + if (chan->channel == DC_TI_ADC_BATTEMP) { + ret = regmap_set_bits(info->regmap, DC_TI_ADC_CNTL_REG, + DC_TI_ADC_EN_EXT_BPTH_BIAS); + if (ret) + return ret; + /* + * As per PMIC Vendor specifications, BPTHERM BIAS should be + * enabled 35 ms before ADC_EN command. + */ + msleep(35); + } + + ret = dc_ti_adc_sample(info, chan, val); + + if (chan->channel == DC_TI_ADC_BATTEMP) + regmap_clear_bits(info->regmap, DC_TI_ADC_CNTL_REG, + DC_TI_ADC_EN_EXT_BPTH_BIAS); + + if (ret) + return ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return IIO_VAL_INT; + case IIO_CHAN_INFO_PROCESSED: + return dc_ti_adc_raw_to_processed(info, chan, *val, val, val2); + } + + return -EINVAL; +} + +static const struct iio_info dc_ti_adc_iio_info = { + .read_raw = dc_ti_adc_read_raw, +}; + +static int dc_ti_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct intel_soc_pmic *pmic = dev_get_drvdata(dev->parent); + struct dc_ti_adc_info *info; + struct iio_dev *indio_dev; + unsigned int val; + int irq, ret; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*info)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + + ret = devm_mutex_init(dev, &info->lock); + if (ret) + return ret; + + init_waitqueue_head(&info->wait); + + info->dev = dev; + info->regmap = pmic->regmap; + + indio_dev->name = "dc_ti_adc"; + indio_dev->channels = dc_ti_adc_channels; + indio_dev->num_channels = ARRAY_SIZE(dc_ti_adc_channels); + indio_dev->info = &dc_ti_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = regmap_read(info->regmap, DC_TI_VBAT_ZSE_GE_REG, &val); + if (ret) + return ret; + + info->vbat_zse = FIELD_GET_SIGNED(DC_TI_VBAT_ZSE, val); + info->vbat_ge = FIELD_GET_SIGNED(DC_TI_VBAT_GE, val); + + dev_dbg(dev, "vbat-zse %d vbat-ge %d\n", info->vbat_zse, info->vbat_ge); + + ret = devm_iio_map_array_register(dev, indio_dev, dc_ti_adc_default_maps); + if (ret) + return ret; + + ret = devm_request_threaded_irq(dev, irq, NULL, dc_ti_adc_isr, + IRQF_ONESHOT, indio_dev->name, info); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct platform_device_id dc_ti_adc_ids[] = { + { .name = "chtdc_ti_adc" }, + { } +}; +MODULE_DEVICE_TABLE(platform, dc_ti_adc_ids); + +static struct platform_driver dc_ti_adc_driver = { + .driver = { + .name = "dc_ti_adc", + }, + .probe = dc_ti_adc_probe, + .id_table = dc_ti_adc_ids, +}; +module_platform_driver(dc_ti_adc_driver); + +MODULE_AUTHOR("Ramakrishna Pallala (Intel)"); +MODULE_AUTHOR("Hans de Goede "); +MODULE_DESCRIPTION("Intel Dollar Cove (TI) GPADC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/intel_mrfld_adc.c b/drivers/iio/adc/intel_mrfld_adc.c new file mode 100644 index 000000000..ff34e5979 --- /dev/null +++ b/drivers/iio/adc/intel_mrfld_adc.c @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ADC driver for Basin Cove PMIC + * + * Copyright (C) 2012 Intel Corporation + * Author: Bin Yang + * + * Rewritten for upstream by: + * Vincent Pelletier + * Andy Shevchenko + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#define BCOVE_GPADCREQ 0xDC +#define BCOVE_GPADCREQ_BUSY BIT(0) +#define BCOVE_GPADCREQ_IRQEN BIT(1) + +#define BCOVE_ADCIRQ_ALL ( \ + BCOVE_ADCIRQ_BATTEMP | \ + BCOVE_ADCIRQ_SYSTEMP | \ + BCOVE_ADCIRQ_BATTID | \ + BCOVE_ADCIRQ_VIBATT | \ + BCOVE_ADCIRQ_CCTICK) + +#define BCOVE_ADC_TIMEOUT msecs_to_jiffies(1000) + +static const u8 mrfld_adc_requests[] = { + BCOVE_ADCIRQ_VIBATT, + BCOVE_ADCIRQ_BATTID, + BCOVE_ADCIRQ_VIBATT, + BCOVE_ADCIRQ_SYSTEMP, + BCOVE_ADCIRQ_BATTEMP, + BCOVE_ADCIRQ_BATTEMP, + BCOVE_ADCIRQ_SYSTEMP, + BCOVE_ADCIRQ_SYSTEMP, + BCOVE_ADCIRQ_SYSTEMP, +}; + +struct mrfld_adc { + struct regmap *regmap; + struct completion completion; + /* Lock to protect the IPC transfers */ + struct mutex lock; +}; + +static irqreturn_t mrfld_adc_thread_isr(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct mrfld_adc *adc = iio_priv(indio_dev); + + complete(&adc->completion); + return IRQ_HANDLED; +} + +static int mrfld_adc_single_conv(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *result) +{ + struct mrfld_adc *adc = iio_priv(indio_dev); + struct regmap *regmap = adc->regmap; + unsigned int req; + long time_left; + __be16 value; + int ret; + + reinit_completion(&adc->completion); + + regmap_clear_bits(regmap, BCOVE_MADCIRQ, BCOVE_ADCIRQ_ALL); + regmap_clear_bits(regmap, BCOVE_MIRQLVL1, BCOVE_LVL1_ADC); + + ret = regmap_read_poll_timeout(regmap, BCOVE_GPADCREQ, req, + !(req & BCOVE_GPADCREQ_BUSY), + 2000, 1000000); + if (ret) + goto done; + + req = mrfld_adc_requests[chan->channel]; + ret = regmap_write(regmap, BCOVE_GPADCREQ, BCOVE_GPADCREQ_IRQEN | req); + if (ret) + goto done; + + time_left = wait_for_completion_interruptible_timeout(&adc->completion, + BCOVE_ADC_TIMEOUT); + if (time_left < 0) { + ret = time_left; + goto done; + } + if (time_left == 0) { + ret = -ETIMEDOUT; + goto done; + } + + ret = regmap_bulk_read(regmap, chan->address, &value, sizeof(value)); + if (ret) + goto done; + + *result = be16_to_cpu(value); + ret = IIO_VAL_INT; + +done: + regmap_update_bits(regmap, BCOVE_MIRQLVL1, BCOVE_LVL1_ADC, 0xff); + regmap_update_bits(regmap, BCOVE_MADCIRQ, BCOVE_ADCIRQ_ALL, 0xff); + + return ret; +} + +static int mrfld_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mrfld_adc *adc = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&adc->lock); + ret = mrfld_adc_single_conv(indio_dev, chan, val); + mutex_unlock(&adc->lock); + return ret; + default: + return -EINVAL; + } +} + +static const struct iio_info mrfld_adc_iio_info = { + .read_raw = &mrfld_adc_read_raw, +}; + +#define BCOVE_ADC_CHANNEL(_type, _channel, _datasheet_name, _address) \ + { \ + .indexed = 1, \ + .type = _type, \ + .channel = _channel, \ + .address = _address, \ + .datasheet_name = _datasheet_name, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + } + +static const struct iio_chan_spec mrfld_adc_channels[] = { + BCOVE_ADC_CHANNEL(IIO_VOLTAGE, 0, "CH0", 0xE9), + BCOVE_ADC_CHANNEL(IIO_RESISTANCE, 1, "CH1", 0xEB), + BCOVE_ADC_CHANNEL(IIO_CURRENT, 2, "CH2", 0xED), + BCOVE_ADC_CHANNEL(IIO_TEMP, 3, "CH3", 0xCC), + BCOVE_ADC_CHANNEL(IIO_TEMP, 4, "CH4", 0xC8), + BCOVE_ADC_CHANNEL(IIO_TEMP, 5, "CH5", 0xCA), + BCOVE_ADC_CHANNEL(IIO_TEMP, 6, "CH6", 0xC2), + BCOVE_ADC_CHANNEL(IIO_TEMP, 7, "CH7", 0xC4), + BCOVE_ADC_CHANNEL(IIO_TEMP, 8, "CH8", 0xC6), +}; + +static const struct iio_map iio_maps[] = { + IIO_MAP("CH0", "bcove-battery", "VBATRSLT"), + IIO_MAP("CH1", "bcove-battery", "BATTID"), + IIO_MAP("CH2", "bcove-battery", "IBATRSLT"), + IIO_MAP("CH3", "bcove-temp", "PMICTEMP"), + IIO_MAP("CH4", "bcove-temp", "BATTEMP0"), + IIO_MAP("CH5", "bcove-temp", "BATTEMP1"), + IIO_MAP("CH6", "bcove-temp", "SYSTEMP0"), + IIO_MAP("CH7", "bcove-temp", "SYSTEMP1"), + IIO_MAP("CH8", "bcove-temp", "SYSTEMP2"), + { } +}; + +static int mrfld_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct intel_soc_pmic *pmic = dev_get_drvdata(dev->parent); + struct iio_dev *indio_dev; + struct mrfld_adc *adc; + int irq; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(struct mrfld_adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + + mutex_init(&adc->lock); + init_completion(&adc->completion); + adc->regmap = pmic->regmap; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(dev, irq, NULL, mrfld_adc_thread_isr, + IRQF_ONESHOT | IRQF_SHARED, pdev->name, + indio_dev); + if (ret) + return ret; + + indio_dev->name = pdev->name; + + indio_dev->channels = mrfld_adc_channels; + indio_dev->num_channels = ARRAY_SIZE(mrfld_adc_channels); + indio_dev->info = &mrfld_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_iio_map_array_register(dev, indio_dev, iio_maps); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct platform_device_id mrfld_adc_id_table[] = { + { .name = "mrfld_bcove_adc" }, + { } +}; +MODULE_DEVICE_TABLE(platform, mrfld_adc_id_table); + +static struct platform_driver mrfld_adc_driver = { + .driver = { + .name = "mrfld_bcove_adc", + }, + .probe = mrfld_adc_probe, + .id_table = mrfld_adc_id_table, +}; +module_platform_driver(mrfld_adc_driver); + +MODULE_AUTHOR("Bin Yang "); +MODULE_AUTHOR("Vincent Pelletier "); +MODULE_AUTHOR("Andy Shevchenko "); +MODULE_DESCRIPTION("ADC driver for Basin Cove PMIC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/lp8788_adc.c b/drivers/iio/adc/lp8788_adc.c new file mode 100644 index 000000000..33bf8aef7 --- /dev/null +++ b/drivers/iio/adc/lp8788_adc.c @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * TI LP8788 MFD - ADC driver + * + * Copyright 2012 Texas Instruments + * + * Author: Milo(Woogyom) Kim + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* register address */ +#define LP8788_ADC_CONF 0x60 +#define LP8788_ADC_RAW 0x61 +#define LP8788_ADC_DONE 0x63 + +#define ADC_CONV_START 1 + +struct lp8788_adc { + struct lp8788 *lp; + const struct iio_map *map; + struct mutex lock; +}; + +static const int lp8788_scale[LPADC_MAX] = { + [LPADC_VBATT_5P5] = 1343101, + [LPADC_VIN_CHG] = 3052503, + [LPADC_IBATT] = 610500, + [LPADC_IC_TEMP] = 61050, + [LPADC_VBATT_6P0] = 1465201, + [LPADC_VBATT_5P0] = 1221001, + [LPADC_ADC1] = 610500, + [LPADC_ADC2] = 610500, + [LPADC_VDD] = 1025641, + [LPADC_VCOIN] = 757020, + [LPADC_ADC3] = 610500, + [LPADC_ADC4] = 610500, +}; + +static int lp8788_get_adc_result(struct lp8788_adc *adc, enum lp8788_adc_id id, + int *val) +{ + unsigned int msb; + unsigned int lsb; + unsigned int result; + u8 data; + u8 rawdata[2]; + int size = ARRAY_SIZE(rawdata); + int retry = 5; + int ret; + + data = (id << 1) | ADC_CONV_START; + ret = lp8788_write_byte(adc->lp, LP8788_ADC_CONF, data); + if (ret) + goto err_io; + + /* retry until adc conversion is done */ + data = 0; + while (retry--) { + usleep_range(100, 200); + + ret = lp8788_read_byte(adc->lp, LP8788_ADC_DONE, &data); + if (ret) + goto err_io; + + /* conversion done */ + if (data) + break; + } + + ret = lp8788_read_multi_bytes(adc->lp, LP8788_ADC_RAW, rawdata, size); + if (ret) + goto err_io; + + msb = (rawdata[0] << 4) & 0x00000ff0; + lsb = (rawdata[1] >> 4) & 0x0000000f; + result = msb | lsb; + *val = result; + + return 0; + +err_io: + return ret; +} + +static int lp8788_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct lp8788_adc *adc = iio_priv(indio_dev); + enum lp8788_adc_id id = chan->channel; + int ret; + + mutex_lock(&adc->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = lp8788_get_adc_result(adc, id, val) ? -EIO : IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + *val = lp8788_scale[id] / 1000000; + *val2 = lp8788_scale[id] % 1000000; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + ret = -EINVAL; + break; + } + + mutex_unlock(&adc->lock); + + return ret; +} + +static const struct iio_info lp8788_adc_info = { + .read_raw = &lp8788_adc_read_raw, +}; + +#define LP8788_CHAN(_id, _type) { \ + .type = _type, \ + .indexed = 1, \ + .channel = LPADC_##_id, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = #_id, \ +} + +static const struct iio_chan_spec lp8788_adc_channels[] = { + [LPADC_VBATT_5P5] = LP8788_CHAN(VBATT_5P5, IIO_VOLTAGE), + [LPADC_VIN_CHG] = LP8788_CHAN(VIN_CHG, IIO_VOLTAGE), + [LPADC_IBATT] = LP8788_CHAN(IBATT, IIO_CURRENT), + [LPADC_IC_TEMP] = LP8788_CHAN(IC_TEMP, IIO_TEMP), + [LPADC_VBATT_6P0] = LP8788_CHAN(VBATT_6P0, IIO_VOLTAGE), + [LPADC_VBATT_5P0] = LP8788_CHAN(VBATT_5P0, IIO_VOLTAGE), + [LPADC_ADC1] = LP8788_CHAN(ADC1, IIO_VOLTAGE), + [LPADC_ADC2] = LP8788_CHAN(ADC2, IIO_VOLTAGE), + [LPADC_VDD] = LP8788_CHAN(VDD, IIO_VOLTAGE), + [LPADC_VCOIN] = LP8788_CHAN(VCOIN, IIO_VOLTAGE), + [LPADC_ADC3] = LP8788_CHAN(ADC3, IIO_VOLTAGE), + [LPADC_ADC4] = LP8788_CHAN(ADC4, IIO_VOLTAGE), +}; + +/* default maps used by iio consumer (lp8788-charger driver) */ +static const struct iio_map lp8788_default_iio_maps[] = { + IIO_MAP("VBATT_5P0", "lp8788-charger", "lp8788_vbatt_5p0"), + IIO_MAP("ADC1", "lp8788-charger", "lp8788_adc1"), + { } +}; + +static int lp8788_iio_map_register(struct device *dev, + struct iio_dev *indio_dev, + struct lp8788_platform_data *pdata, + struct lp8788_adc *adc) +{ + const struct iio_map *map; + int ret; + + map = (!pdata || !pdata->adc_pdata) ? + lp8788_default_iio_maps : pdata->adc_pdata; + + ret = devm_iio_map_array_register(dev, indio_dev, map); + if (ret) { + dev_err(&indio_dev->dev, "iio map err: %d\n", ret); + return ret; + } + + adc->map = map; + return 0; +} + +static int lp8788_adc_probe(struct platform_device *pdev) +{ + struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent); + struct iio_dev *indio_dev; + struct lp8788_adc *adc; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->lp = lp; + + ret = lp8788_iio_map_register(&pdev->dev, indio_dev, lp->pdata, adc); + if (ret) + return ret; + + mutex_init(&adc->lock); + + indio_dev->name = pdev->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &lp8788_adc_info; + indio_dev->channels = lp8788_adc_channels; + indio_dev->num_channels = ARRAY_SIZE(lp8788_adc_channels); + + return devm_iio_device_register(&pdev->dev, indio_dev); +} + +static struct platform_driver lp8788_adc_driver = { + .probe = lp8788_adc_probe, + .driver = { + .name = LP8788_DEV_ADC, + }, +}; +module_platform_driver(lp8788_adc_driver); + +MODULE_DESCRIPTION("Texas Instruments LP8788 ADC Driver"); +MODULE_AUTHOR("Milo Kim"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:lp8788-adc"); diff --git a/drivers/iio/adc/lpc18xx_adc.c b/drivers/iio/adc/lpc18xx_adc.c new file mode 100644 index 000000000..2cf2519f5 --- /dev/null +++ b/drivers/iio/adc/lpc18xx_adc.c @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * IIO ADC driver for NXP LPC18xx ADC + * + * Copyright (C) 2016 Joachim Eastwood + * + * UNSUPPORTED hardware features: + * - Hardware triggers + * - Burst mode + * - Interrupts + * - DMA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* LPC18XX ADC registers and bits */ +#define LPC18XX_ADC_CR 0x000 +#define LPC18XX_ADC_CR_CLKDIV_SHIFT 8 +#define LPC18XX_ADC_CR_PDN BIT(21) +#define LPC18XX_ADC_CR_START_NOW (0x1 << 24) +#define LPC18XX_ADC_GDR 0x004 + +/* Data register bits */ +#define LPC18XX_ADC_SAMPLE_SHIFT 6 +#define LPC18XX_ADC_SAMPLE_MASK 0x3ff +#define LPC18XX_ADC_CONV_DONE BIT(31) + +/* Clock should be 4.5 MHz or less */ +#define LPC18XX_ADC_CLK_TARGET 4500000 + +struct lpc18xx_adc { + struct regulator *vref; + void __iomem *base; + struct device *dev; + struct mutex lock; + struct clk *clk; + u32 cr_reg; +}; + +#define LPC18XX_ADC_CHAN(_idx) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _idx, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec lpc18xx_adc_iio_channels[] = { + LPC18XX_ADC_CHAN(0), + LPC18XX_ADC_CHAN(1), + LPC18XX_ADC_CHAN(2), + LPC18XX_ADC_CHAN(3), + LPC18XX_ADC_CHAN(4), + LPC18XX_ADC_CHAN(5), + LPC18XX_ADC_CHAN(6), + LPC18XX_ADC_CHAN(7), +}; + +static int lpc18xx_adc_read_chan(struct lpc18xx_adc *adc, unsigned int ch) +{ + int ret; + u32 reg; + + reg = adc->cr_reg | BIT(ch) | LPC18XX_ADC_CR_START_NOW; + writel(reg, adc->base + LPC18XX_ADC_CR); + + ret = readl_poll_timeout(adc->base + LPC18XX_ADC_GDR, reg, + reg & LPC18XX_ADC_CONV_DONE, 3, 9); + if (ret) { + dev_warn(adc->dev, "adc read timed out\n"); + return ret; + } + + return (reg >> LPC18XX_ADC_SAMPLE_SHIFT) & LPC18XX_ADC_SAMPLE_MASK; +} + +static int lpc18xx_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct lpc18xx_adc *adc = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&adc->lock); + *val = lpc18xx_adc_read_chan(adc, chan->channel); + mutex_unlock(&adc->lock); + if (*val < 0) + return *val; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = regulator_get_voltage(adc->vref) / 1000; + *val2 = 10; + + return IIO_VAL_FRACTIONAL_LOG2; + } + + return -EINVAL; +} + +static const struct iio_info lpc18xx_adc_info = { + .read_raw = lpc18xx_adc_read_raw, +}; + +static void lpc18xx_clear_cr_reg(void *data) +{ + struct lpc18xx_adc *adc = data; + + writel(0, adc->base + LPC18XX_ADC_CR); +} + +static void lpc18xx_regulator_disable(void *vref) +{ + regulator_disable(vref); +} + +static int lpc18xx_adc_probe(struct platform_device *pdev) +{ + struct iio_dev *indio_dev; + struct lpc18xx_adc *adc; + unsigned int clkdiv; + unsigned long rate; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->dev = &pdev->dev; + mutex_init(&adc->lock); + + adc->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(adc->base)) + return PTR_ERR(adc->base); + + adc->clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(adc->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(adc->clk), + "error getting clock\n"); + + adc->vref = devm_regulator_get(&pdev->dev, "vref"); + if (IS_ERR(adc->vref)) + return dev_err_probe(&pdev->dev, PTR_ERR(adc->vref), + "error getting regulator\n"); + + indio_dev->name = dev_name(&pdev->dev); + indio_dev->info = &lpc18xx_adc_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = lpc18xx_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(lpc18xx_adc_iio_channels); + + ret = regulator_enable(adc->vref); + if (ret) { + dev_err(&pdev->dev, "unable to enable regulator\n"); + return ret; + } + + ret = devm_add_action_or_reset(&pdev->dev, lpc18xx_regulator_disable, adc->vref); + if (ret) + return ret; + + rate = clk_get_rate(adc->clk); + clkdiv = DIV_ROUND_UP(rate, LPC18XX_ADC_CLK_TARGET); + + adc->cr_reg = (clkdiv << LPC18XX_ADC_CR_CLKDIV_SHIFT) | + LPC18XX_ADC_CR_PDN; + writel(adc->cr_reg, adc->base + LPC18XX_ADC_CR); + + ret = devm_add_action_or_reset(&pdev->dev, lpc18xx_clear_cr_reg, adc); + if (ret) + return ret; + + return devm_iio_device_register(&pdev->dev, indio_dev); +} + +static const struct of_device_id lpc18xx_adc_match[] = { + { .compatible = "nxp,lpc1850-adc" }, + { } +}; +MODULE_DEVICE_TABLE(of, lpc18xx_adc_match); + +static struct platform_driver lpc18xx_adc_driver = { + .probe = lpc18xx_adc_probe, + .driver = { + .name = "lpc18xx-adc", + .of_match_table = lpc18xx_adc_match, + }, +}; +module_platform_driver(lpc18xx_adc_driver); + +MODULE_DESCRIPTION("LPC18xx ADC driver"); +MODULE_AUTHOR("Joachim Eastwood "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/lpc32xx_adc.c b/drivers/iio/adc/lpc32xx_adc.c new file mode 100644 index 000000000..32a15d193 --- /dev/null +++ b/drivers/iio/adc/lpc32xx_adc.c @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * lpc32xx_adc.c - Support for ADC in LPC32XX + * + * 3-channel, 10-bit ADC + * + * Copyright (C) 2011, 2012 Roland Stigge + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * LPC32XX registers definitions + */ +#define LPC32XXAD_SELECT(x) ((x) + 0x04) +#define LPC32XXAD_CTRL(x) ((x) + 0x08) +#define LPC32XXAD_VALUE(x) ((x) + 0x48) + +/* Bit definitions for LPC32XXAD_SELECT: */ +/* constant, always write this value! */ +#define LPC32XXAD_REFm 0x00000200 +/* constant, always write this value! */ +#define LPC32XXAD_REFp 0x00000080 + /* multiple of this is the channel number: 0, 1, 2 */ +#define LPC32XXAD_IN 0x00000010 +/* constant, always write this value! */ +#define LPC32XXAD_INTERNAL 0x00000004 + +/* Bit definitions for LPC32XXAD_CTRL: */ +#define LPC32XXAD_STROBE 0x00000002 +#define LPC32XXAD_PDN_CTRL 0x00000004 + +/* Bit definitions for LPC32XXAD_VALUE: */ +#define LPC32XXAD_VALUE_MASK 0x000003FF + +#define LPC32XXAD_NAME "lpc32xx-adc" + +struct lpc32xx_adc_state { + void __iomem *adc_base; + struct clk *clk; + struct completion completion; + struct regulator *vref; + /* lock to protect against multiple access to the device */ + struct mutex lock; + + u32 value; +}; + +static int lpc32xx_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct lpc32xx_adc_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&st->lock); + ret = clk_prepare_enable(st->clk); + if (ret) { + mutex_unlock(&st->lock); + return ret; + } + /* Measurement setup */ + __raw_writel(LPC32XXAD_INTERNAL | (chan->address) | + LPC32XXAD_REFp | LPC32XXAD_REFm, + LPC32XXAD_SELECT(st->adc_base)); + /* Trigger conversion */ + __raw_writel(LPC32XXAD_PDN_CTRL | LPC32XXAD_STROBE, + LPC32XXAD_CTRL(st->adc_base)); + wait_for_completion(&st->completion); /* set by ISR */ + clk_disable_unprepare(st->clk); + *val = st->value; + mutex_unlock(&st->lock); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = regulator_get_voltage(st->vref) / 1000; + *val2 = 10; + + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static const struct iio_info lpc32xx_adc_iio_info = { + .read_raw = &lpc32xx_read_raw, +}; + +#define LPC32XX_ADC_CHANNEL_BASE(_index) \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .address = LPC32XXAD_IN * _index, \ + .scan_index = _index, + +#define LPC32XX_ADC_CHANNEL(_index) { \ + LPC32XX_ADC_CHANNEL_BASE(_index) \ +} + +#define LPC32XX_ADC_SCALE_CHANNEL(_index) { \ + LPC32XX_ADC_CHANNEL_BASE(_index) \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \ +} + +static const struct iio_chan_spec lpc32xx_adc_iio_channels[] = { + LPC32XX_ADC_CHANNEL(0), + LPC32XX_ADC_CHANNEL(1), + LPC32XX_ADC_CHANNEL(2), +}; + +static const struct iio_chan_spec lpc32xx_adc_iio_scale_channels[] = { + LPC32XX_ADC_SCALE_CHANNEL(0), + LPC32XX_ADC_SCALE_CHANNEL(1), + LPC32XX_ADC_SCALE_CHANNEL(2), +}; + +static irqreturn_t lpc32xx_adc_isr(int irq, void *dev_id) +{ + struct lpc32xx_adc_state *st = dev_id; + + /* Read value and clear irq */ + st->value = __raw_readl(LPC32XXAD_VALUE(st->adc_base)) & + LPC32XXAD_VALUE_MASK; + complete(&st->completion); + + return IRQ_HANDLED; +} + +static int lpc32xx_adc_probe(struct platform_device *pdev) +{ + struct lpc32xx_adc_state *st = NULL; + struct resource *res; + int retval = -ENODEV; + struct iio_dev *iodev = NULL; + int irq; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "failed to get platform I/O memory\n"); + return -ENXIO; + } + + iodev = devm_iio_device_alloc(&pdev->dev, sizeof(*st)); + if (!iodev) + return -ENOMEM; + + st = iio_priv(iodev); + + st->adc_base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!st->adc_base) { + dev_err(&pdev->dev, "failed mapping memory\n"); + return -EBUSY; + } + + st->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(st->clk)) { + dev_err(&pdev->dev, "failed getting clock\n"); + return PTR_ERR(st->clk); + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + init_completion(&st->completion); + + retval = devm_request_irq(&pdev->dev, irq, lpc32xx_adc_isr, 0, + LPC32XXAD_NAME, st); + if (retval < 0) { + dev_err(&pdev->dev, "failed requesting interrupt\n"); + return retval; + } + + st->vref = devm_regulator_get(&pdev->dev, "vref"); + if (IS_ERR(st->vref)) { + iodev->channels = lpc32xx_adc_iio_channels; + dev_info(&pdev->dev, + "Missing vref regulator: No scaling available\n"); + } else { + iodev->channels = lpc32xx_adc_iio_scale_channels; + } + + platform_set_drvdata(pdev, iodev); + + iodev->name = LPC32XXAD_NAME; + iodev->info = &lpc32xx_adc_iio_info; + iodev->modes = INDIO_DIRECT_MODE; + iodev->num_channels = ARRAY_SIZE(lpc32xx_adc_iio_channels); + + mutex_init(&st->lock); + + retval = devm_iio_device_register(&pdev->dev, iodev); + if (retval) + return retval; + + dev_info(&pdev->dev, "LPC32XX ADC driver loaded, IRQ %d\n", irq); + + return 0; +} + +static const struct of_device_id lpc32xx_adc_match[] = { + { .compatible = "nxp,lpc3220-adc" }, + { } +}; +MODULE_DEVICE_TABLE(of, lpc32xx_adc_match); + +static struct platform_driver lpc32xx_adc_driver = { + .probe = lpc32xx_adc_probe, + .driver = { + .name = LPC32XXAD_NAME, + .of_match_table = lpc32xx_adc_match, + }, +}; + +module_platform_driver(lpc32xx_adc_driver); + +MODULE_AUTHOR("Roland Stigge "); +MODULE_DESCRIPTION("LPC32XX ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/ltc2309.c b/drivers/iio/adc/ltc2309.c new file mode 100644 index 000000000..1ad533270 --- /dev/null +++ b/drivers/iio/adc/ltc2309.c @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * The LTC2305 is a 2-Channel, 12-Bit SAR ADC with an I2C Interface. + * The LTC2309 is an 8-Channel, 12-Bit SAR ADC with an I2C Interface. + * + * Datasheet: + * https://www.analog.com/media/en/technical-documentation/data-sheets/23015fb.pdf + * https://www.analog.com/media/en/technical-documentation/data-sheets/2309fd.pdf + * + * Copyright (c) 2023, Liam Beguin + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LTC2309_ADC_RESOLUTION 12 +#define LTC2309_INTERNAL_REF_MV 4096 + +#define LTC2309_DIN_CH_MASK GENMASK(7, 4) +#define LTC2309_DIN_SDN BIT(7) +#define LTC2309_DIN_OSN BIT(6) +#define LTC2309_DIN_S1 BIT(5) +#define LTC2309_DIN_S0 BIT(4) +#define LTC2309_DIN_UNI BIT(3) +#define LTC2309_DIN_SLEEP BIT(2) + +/** + * struct ltc2309 - internal device data structure + * @dev: Device reference + * @client: I2C reference + * @lock: Lock to serialize data access + * @vref_mv: Internal voltage reference + * @read_delay_us: Chip-specific read delay in microseconds + */ +struct ltc2309 { + struct device *dev; + struct i2c_client *client; + struct mutex lock; /* serialize data access */ + int vref_mv; + unsigned int read_delay_us; +}; + +/* Order matches expected channel address, See datasheet Table 1. */ +enum ltc2305_channels { + LTC2305_CH0_CH1 = 0x0, + LTC2305_CH1_CH0 = 0x4, + LTC2305_CH0 = 0x8, + LTC2305_CH1 = 0xc, +}; + +enum ltc2309_channels { + LTC2309_CH0_CH1 = 0x0, + LTC2309_CH2_CH3 = 0x1, + LTC2309_CH4_CH5 = 0x2, + LTC2309_CH6_CH7 = 0x3, + LTC2309_CH1_CH0 = 0x4, + LTC2309_CH3_CH2 = 0x5, + LTC2309_CH5_CH4 = 0x6, + LTC2309_CH7_CH6 = 0x7, + LTC2309_CH0 = 0x8, + LTC2309_CH2 = 0x9, + LTC2309_CH4 = 0xa, + LTC2309_CH6 = 0xb, + LTC2309_CH1 = 0xc, + LTC2309_CH3 = 0xd, + LTC2309_CH5 = 0xe, + LTC2309_CH7 = 0xf, +}; + +#define LTC2309_CHAN(_chan, _addr) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .address = _addr, \ + .channel = _chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +#define LTC2309_DIFF_CHAN(_chan, _chan2, _addr) { \ + .type = IIO_VOLTAGE, \ + .differential = 1, \ + .indexed = 1, \ + .address = _addr, \ + .channel = _chan, \ + .channel2 = _chan2, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec ltc2305_channels[] = { + LTC2309_CHAN(0, LTC2305_CH0), + LTC2309_CHAN(1, LTC2305_CH1), + LTC2309_DIFF_CHAN(0, 1, LTC2305_CH0_CH1), + LTC2309_DIFF_CHAN(1, 0, LTC2305_CH1_CH0), +}; + +static const struct iio_chan_spec ltc2309_channels[] = { + LTC2309_CHAN(0, LTC2309_CH0), + LTC2309_CHAN(1, LTC2309_CH1), + LTC2309_CHAN(2, LTC2309_CH2), + LTC2309_CHAN(3, LTC2309_CH3), + LTC2309_CHAN(4, LTC2309_CH4), + LTC2309_CHAN(5, LTC2309_CH5), + LTC2309_CHAN(6, LTC2309_CH6), + LTC2309_CHAN(7, LTC2309_CH7), + LTC2309_DIFF_CHAN(0, 1, LTC2309_CH0_CH1), + LTC2309_DIFF_CHAN(2, 3, LTC2309_CH2_CH3), + LTC2309_DIFF_CHAN(4, 5, LTC2309_CH4_CH5), + LTC2309_DIFF_CHAN(6, 7, LTC2309_CH6_CH7), + LTC2309_DIFF_CHAN(1, 0, LTC2309_CH1_CH0), + LTC2309_DIFF_CHAN(3, 2, LTC2309_CH3_CH2), + LTC2309_DIFF_CHAN(5, 4, LTC2309_CH5_CH4), + LTC2309_DIFF_CHAN(7, 6, LTC2309_CH7_CH6), +}; + +struct ltc2309_chip_info { + const char *name; + unsigned int read_delay_us; + int num_channels; + const struct iio_chan_spec *channels __counted_by_ptr(num_channels); +}; + +static const struct ltc2309_chip_info ltc2305_chip_info = { + .name = "ltc2305", + .read_delay_us = 2, + .num_channels = ARRAY_SIZE(ltc2305_channels), + .channels = ltc2305_channels, +}; + +static const struct ltc2309_chip_info ltc2309_chip_info = { + .name = "ltc2309", + .num_channels = ARRAY_SIZE(ltc2309_channels), + .channels = ltc2309_channels, +}; + +static int ltc2309_read_raw_channel(struct ltc2309 *ltc2309, + unsigned long address, int *val) +{ + int ret; + __be16 buf; + u8 din; + + din = FIELD_PREP(LTC2309_DIN_CH_MASK, address & 0x0f) | + FIELD_PREP(LTC2309_DIN_UNI, 1) | + FIELD_PREP(LTC2309_DIN_SLEEP, 0); + + ret = i2c_smbus_write_byte(ltc2309->client, din); + if (ret < 0) { + dev_err(ltc2309->dev, "i2c command failed: %pe\n", + ERR_PTR(ret)); + return ret; + } + + if (ltc2309->read_delay_us) + fsleep(ltc2309->read_delay_us); + + ret = i2c_master_recv(ltc2309->client, (char *)&buf, 2); + if (ret < 0) { + dev_err(ltc2309->dev, "i2c read failed: %pe\n", ERR_PTR(ret)); + return ret; + } + + *val = be16_to_cpu(buf) >> 4; + + return ret; +} + +static int ltc2309_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct ltc2309 *ltc2309 = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(<c2309->lock); + ret = ltc2309_read_raw_channel(ltc2309, chan->address, val); + mutex_unlock(<c2309->lock); + if (ret < 0) + return -EINVAL; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = ltc2309->vref_mv; + *val2 = LTC2309_ADC_RESOLUTION; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static const struct iio_info ltc2309_info = { + .read_raw = ltc2309_read_raw, +}; + +static int ltc2309_probe(struct i2c_client *client) +{ + const struct ltc2309_chip_info *chip_info; + struct iio_dev *indio_dev; + struct ltc2309 *ltc2309; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*ltc2309)); + if (!indio_dev) + return -ENOMEM; + + ltc2309 = iio_priv(indio_dev); + chip_info = i2c_get_match_data(client); + + ltc2309->dev = &indio_dev->dev; + ltc2309->client = client; + ltc2309->read_delay_us = chip_info->read_delay_us; + + indio_dev->name = chip_info->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = chip_info->channels; + indio_dev->num_channels = chip_info->num_channels; + indio_dev->info = <c2309_info; + + ret = devm_regulator_get_enable_read_voltage(&client->dev, "vref"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(ltc2309->dev, ret, + "failed to get vref voltage\n"); + + ltc2309->vref_mv = ret == -ENODEV ? LTC2309_INTERNAL_REF_MV : ret / 1000; + + mutex_init(<c2309->lock); + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct of_device_id ltc2309_of_match[] = { + { .compatible = "lltc,ltc2305", .data = <c2305_chip_info }, + { .compatible = "lltc,ltc2309", .data = <c2309_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ltc2309_of_match); + +static const struct i2c_device_id ltc2309_id[] = { + { .name = "ltc2305", .driver_data = (kernel_ulong_t)<c2305_chip_info }, + { .name = "ltc2309", .driver_data = (kernel_ulong_t)<c2309_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltc2309_id); + +static struct i2c_driver ltc2309_driver = { + .driver = { + .name = "ltc2309", + .of_match_table = ltc2309_of_match, + }, + .probe = ltc2309_probe, + .id_table = ltc2309_id, +}; +module_i2c_driver(ltc2309_driver); + +MODULE_AUTHOR("Liam Beguin "); +MODULE_DESCRIPTION("Linear Technology LTC2309 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ltc2378.c b/drivers/iio/adc/ltc2378.c new file mode 100644 index 000000000..b0ff2be1a --- /dev/null +++ b/drivers/iio/adc/ltc2378.c @@ -0,0 +1,857 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Analog Devices LTC2378 ADC series driver + * + * Copyright (C) 2026 Analog Devices Inc. + * Author: Marcelo Schmitt + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define LTC2378_TDSDOBUSYL_NS 5 +#define LTC2378_TBUSYLH_NS 13 +#define LTC2378_TCNV_HIGH_NS 20 +#define LTC2378_MAX_DATA_WAIT_US 4 /* max(TBUSYLH + TCONV + TDSDOBUSYL) */ + +#define LTC2378_CHANNEL(_sign, _real_bits, _storage_bits) \ +{ \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .differential = _sign, \ + .channel = 0, \ + .channel2 = _sign ? 1 : 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = 0, \ + .scan_type = { \ + .format = _sign ? IIO_SCAN_FORMAT_SIGNED_INT : \ + IIO_SCAN_FORMAT_UNSIGNED_INT, \ + .realbits = _real_bits, \ + .storagebits = _storage_bits, \ + .shift = _storage_bits - _real_bits, \ + .endianness = IIO_BE, \ + }, \ +} + +#define LTC2378_DIFF_CHANNEL(_real_bits) \ + LTC2378_CHANNEL(1, _real_bits, (((_real_bits) > 16) ? 32 : 16)) + +#define LTC2378_PSEUDO_DIFF_CHANNEL(_real_bits) \ + LTC2378_CHANNEL(0, _real_bits, (((_real_bits) > 16) ? 32 : 16)) + +#define LTC2378_OFFLOAD_CHANNEL(_sign, _real_bits, _storage_bits) \ +{ \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .differential = _sign, \ + .channel = 0, \ + .channel2 = _sign ? 1 : 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = 0, \ + .scan_type = { \ + .format = _sign ? IIO_SCAN_FORMAT_SIGNED_INT : \ + IIO_SCAN_FORMAT_UNSIGNED_INT, \ + .realbits = _real_bits, \ + .storagebits = _storage_bits, \ + .shift = 0, \ + .endianness = IIO_CPU, \ + }, \ +} + +/* + * Currently, the available offload hardware + DMA configuration only supports + * pushing 32-bit data elements to DMA IIO buffers in CPU endianness. For 16-bit + * precision parts, those 32-bit elements (in CPU endianness) contain 2 bytes + * with data and 2 bytes always zeroed out. Nevertheless, for the offload use + * case, the IIO buffer is configured for 32 storage bits in CPU endianness so + * data is correctly aligned in user space despite 2 out of the 4 bytes being + * zeros. + */ +#define LTC2378_OFFLOAD_DIFF_CHANNEL(_real_bits) \ + LTC2378_OFFLOAD_CHANNEL(1, (_real_bits), 32) + +#define LTC2378_OFFLOAD_PSEUDO_DIFF_CHANNEL(_real_bits) \ + LTC2378_OFFLOAD_CHANNEL(0, (_real_bits), 32) + +struct ltc2378_chip_info { + const char *name; + unsigned int internal_ref_uV; + struct u32_fract internal_div; + struct iio_chan_spec chan[2]; /* 1 physical chan + 1 timestamp chan */ + struct iio_chan_spec offload_chan; + unsigned int max_sample_rate_Hz; + unsigned int tconv_ns; +}; + +struct ltc2378_state { + const struct ltc2378_chip_info *info; + struct gpio_desc *cnv_gpio; + struct spi_device *spi; + struct mutex lock; /* Protect data acquisition cycle */ + int ref_uV; + struct spi_transfer xfer; + struct spi_transfer offload_xfer; + struct spi_offload *offload; + struct spi_offload_trigger *offload_trigger; + struct pwm_waveform cnv_wf; + struct spi_message offload_msg; + struct spi_offload_trigger_config offload_trigger_config; + struct pwm_device *cnv_trigger; + unsigned int cnv_Hz; + unsigned int sample_freq_range[3]; + + /* + * DMA (thus cache coherency maintenance) requires the transfer buffers + * to live in their own cache lines. + */ + struct { + union { + __be16 sample_buf16_be; + __be32 sample_buf32_be; + u16 sample_buf16; + u32 sample_buf32; + } data; + aligned_s64 timestamp; + } scan __aligned(IIO_DMA_MINALIGN); +}; + +static const struct ltc2378_chip_info ltc2338_18_chip_info = { + .name = "ltc2338-18", + .internal_ref_uV = 2048000, + .internal_div = { .numerator = 5, .denominator = 2 }, + .chan = { LTC2378_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) }, + .offload_chan = LTC2378_OFFLOAD_DIFF_CHANNEL(18), + .max_sample_rate_Hz = 1 * HZ_PER_MHZ, + .tconv_ns = 527, +}; + +static const struct ltc2378_chip_info ltc2364_16_chip_info = { + .name = "ltc2364-16", + .chan = { LTC2378_PSEUDO_DIFF_CHANNEL(16), IIO_CHAN_SOFT_TIMESTAMP(1) }, + .offload_chan = LTC2378_OFFLOAD_PSEUDO_DIFF_CHANNEL(16), + .max_sample_rate_Hz = 250 * HZ_PER_KHZ, + .tconv_ns = 3000, +}; + +static const struct ltc2378_chip_info ltc2364_18_chip_info = { + .name = "ltc2364-18", + .chan = { LTC2378_PSEUDO_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) }, + .offload_chan = LTC2378_OFFLOAD_PSEUDO_DIFF_CHANNEL(18), + .max_sample_rate_Hz = 250 * HZ_PER_KHZ, + .tconv_ns = 3000, +}; + +static const struct ltc2378_chip_info ltc2367_16_chip_info = { + .name = "ltc2367-16", + .chan = { LTC2378_PSEUDO_DIFF_CHANNEL(16), IIO_CHAN_SOFT_TIMESTAMP(1) }, + .offload_chan = LTC2378_OFFLOAD_PSEUDO_DIFF_CHANNEL(16), + .max_sample_rate_Hz = 500 * HZ_PER_KHZ, + .tconv_ns = 1500, +}; + +static const struct ltc2378_chip_info ltc2367_18_chip_info = { + .name = "ltc2367-18", + .chan = { LTC2378_PSEUDO_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) }, + .offload_chan = LTC2378_OFFLOAD_PSEUDO_DIFF_CHANNEL(18), + .max_sample_rate_Hz = 500 * HZ_PER_KHZ, + .tconv_ns = 1500, +}; + +static const struct ltc2378_chip_info ltc2368_16_chip_info = { + .name = "ltc2368-16", + .chan = { LTC2378_PSEUDO_DIFF_CHANNEL(16), IIO_CHAN_SOFT_TIMESTAMP(1) }, + .offload_chan = LTC2378_OFFLOAD_PSEUDO_DIFF_CHANNEL(16), + .max_sample_rate_Hz = 1 * HZ_PER_MHZ, + .tconv_ns = 527, +}; + +static const struct ltc2378_chip_info ltc2368_18_chip_info = { + .name = "ltc2368-18", + .chan = { LTC2378_PSEUDO_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) }, + .offload_chan = LTC2378_OFFLOAD_PSEUDO_DIFF_CHANNEL(18), + .max_sample_rate_Hz = 1 * HZ_PER_MHZ, + .tconv_ns = 527, +}; + +static const struct ltc2378_chip_info ltc2369_18_chip_info = { + .name = "ltc2369-18", + .chan = { LTC2378_PSEUDO_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) }, + .offload_chan = LTC2378_OFFLOAD_PSEUDO_DIFF_CHANNEL(18), + .max_sample_rate_Hz = 1600 * HZ_PER_KHZ, + .tconv_ns = 412, +}; + +static const struct ltc2378_chip_info ltc2370_16_chip_info = { + .name = "ltc2370-16", + .chan = { LTC2378_PSEUDO_DIFF_CHANNEL(16), IIO_CHAN_SOFT_TIMESTAMP(1) }, + .offload_chan = LTC2378_OFFLOAD_PSEUDO_DIFF_CHANNEL(16), + .max_sample_rate_Hz = 2 * HZ_PER_MHZ, + .tconv_ns = 322, +}; + +static const struct ltc2378_chip_info ltc2376_16_chip_info = { + .name = "ltc2376-16", + .chan = { LTC2378_DIFF_CHANNEL(16), IIO_CHAN_SOFT_TIMESTAMP(1) }, + .offload_chan = LTC2378_OFFLOAD_DIFF_CHANNEL(16), + .max_sample_rate_Hz = 250 * HZ_PER_KHZ, + .tconv_ns = 3000, +}; + +static const struct ltc2378_chip_info ltc2376_18_chip_info = { + .name = "ltc2376-18", + .chan = { LTC2378_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) }, + .offload_chan = LTC2378_OFFLOAD_DIFF_CHANNEL(18), + .max_sample_rate_Hz = 250 * HZ_PER_KHZ, + .tconv_ns = 3000, +}; + +static const struct ltc2378_chip_info ltc2376_20_chip_info = { + .name = "ltc2376-20", + .chan = { LTC2378_DIFF_CHANNEL(20), IIO_CHAN_SOFT_TIMESTAMP(1) }, + .offload_chan = LTC2378_OFFLOAD_DIFF_CHANNEL(20), + .max_sample_rate_Hz = 250 * HZ_PER_KHZ, + .tconv_ns = 3000, +}; + +static const struct ltc2378_chip_info ltc2377_16_chip_info = { + .name = "ltc2377-16", + .chan = { LTC2378_DIFF_CHANNEL(16), IIO_CHAN_SOFT_TIMESTAMP(1) }, + .offload_chan = LTC2378_OFFLOAD_DIFF_CHANNEL(16), + .max_sample_rate_Hz = 500 * HZ_PER_KHZ, + .tconv_ns = 1500, +}; + +static const struct ltc2378_chip_info ltc2377_18_chip_info = { + .name = "ltc2377-18", + .chan = { LTC2378_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) }, + .offload_chan = LTC2378_OFFLOAD_DIFF_CHANNEL(18), + .max_sample_rate_Hz = 500 * HZ_PER_KHZ, + .tconv_ns = 1500, +}; + +static const struct ltc2378_chip_info ltc2377_20_chip_info = { + .name = "ltc2377-20", + .chan = { LTC2378_DIFF_CHANNEL(20), IIO_CHAN_SOFT_TIMESTAMP(1) }, + .offload_chan = LTC2378_OFFLOAD_DIFF_CHANNEL(20), + .max_sample_rate_Hz = 500 * HZ_PER_KHZ, + .tconv_ns = 1500, +}; + +static const struct ltc2378_chip_info ltc2378_16_chip_info = { + .name = "ltc2378-16", + .chan = { LTC2378_DIFF_CHANNEL(16), IIO_CHAN_SOFT_TIMESTAMP(1) }, + .offload_chan = LTC2378_OFFLOAD_DIFF_CHANNEL(16), + .max_sample_rate_Hz = 1 * HZ_PER_MHZ, + .tconv_ns = 527, +}; + +static const struct ltc2378_chip_info ltc2378_18_chip_info = { + .name = "ltc2378-18", + .chan = { LTC2378_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) }, + .offload_chan = LTC2378_OFFLOAD_DIFF_CHANNEL(18), + .max_sample_rate_Hz = 1 * HZ_PER_MHZ, + .tconv_ns = 527, +}; + +static const struct ltc2378_chip_info ltc2378_20_chip_info = { + .name = "ltc2378-20", + .chan = { LTC2378_DIFF_CHANNEL(20), IIO_CHAN_SOFT_TIMESTAMP(1) }, + .offload_chan = LTC2378_OFFLOAD_DIFF_CHANNEL(20), + .max_sample_rate_Hz = 1 * HZ_PER_MHZ, + .tconv_ns = 675, +}; + +static const struct ltc2378_chip_info ltc2379_18_chip_info = { + .name = "ltc2379-18", + .chan = { LTC2378_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) }, + .offload_chan = LTC2378_OFFLOAD_DIFF_CHANNEL(18), + .max_sample_rate_Hz = 1600 * HZ_PER_KHZ, + .tconv_ns = 412, +}; + +static const struct ltc2378_chip_info ltc2380_16_chip_info = { + .name = "ltc2380-16", + .chan = { LTC2378_DIFF_CHANNEL(16), IIO_CHAN_SOFT_TIMESTAMP(1) }, + .offload_chan = LTC2378_OFFLOAD_DIFF_CHANNEL(16), + .max_sample_rate_Hz = 2 * HZ_PER_MHZ, + .tconv_ns = 322, +}; + +static int ltc2378_convert_and_acquire(struct ltc2378_state *st) +{ + int ret; + + /* Cause a rising edge of CNV to initiate a new ADC conversion */ + gpiod_set_value_cansleep(st->cnv_gpio, 1); + fsleep(LTC2378_MAX_DATA_WAIT_US); + ret = spi_sync_transfer(st->spi, &st->xfer, 1); + gpiod_set_value_cansleep(st->cnv_gpio, 0); + + return ret; +} + +static irqreturn_t ltc2378_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ltc2378_state *st = iio_priv(indio_dev); + int ret; + + ret = ltc2378_convert_and_acquire(st); + if (ret < 0) + goto err_out; + + iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan), + pf->timestamp); + +err_out: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int ltc2378_channel_single_read(const struct iio_chan_spec *chan, + struct ltc2378_state *st, int *val) +{ + const struct iio_scan_type *scan_type = &chan->scan_type; + u32 sample; + int ret; + + guard(mutex)(&st->lock); + ret = ltc2378_convert_and_acquire(st); + if (ret) + return ret; + + if (chan->scan_type.endianness == IIO_BE) { + if (chan->scan_type.realbits > 16) + sample = be32_to_cpu(st->scan.data.sample_buf32_be); + else + sample = be16_to_cpu(st->scan.data.sample_buf16_be); + } else { /* IIO_CPU */ + if (chan->scan_type.realbits > 16) + sample = st->scan.data.sample_buf32; + else + sample = st->scan.data.sample_buf16; + } + + sample >>= chan->scan_type.shift; + + if (scan_type->format == IIO_SCAN_FORMAT_SIGNED_INT) + *val = sign_extend32(sample, scan_type->realbits - 1); + else + *val = sample; + + return 0; +} + +static int ltc2378_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct ltc2378_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + ret = ltc2378_channel_single_read(chan, st, val); + if (ret) + return ret; + + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_SCALE: { + struct u32_fract fract = st->info->internal_div; + *val = st->ref_uV / MILLI; + if (fract.numerator && fract.denominator) + *val = mult_frac(*val, fract.numerator, fract.denominator); + /* + * For all LTC2378-like devices, the amount of bits that express + * voltage magnitude depend on the polarity / output code format: + * - straight binary: All precision/resolution bits are used. + * - 2's complement: One of the precision bits is used for sign. + */ + if (chan->scan_type.format == IIO_SCAN_FORMAT_SIGNED_INT) + *val2 = chan->scan_type.realbits - 1; + else + *val2 = chan->scan_type.realbits; + + return IIO_VAL_FRACTIONAL_LOG2; + } + case IIO_CHAN_INFO_SAMP_FREQ: + *val = st->cnv_Hz; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ltc2378_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, long mask) +{ + struct ltc2378_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = st->sample_freq_range; + *type = IIO_VAL_INT; + return IIO_AVAIL_RANGE; + default: + return -EINVAL; + } +} + +/* + * SPI offload wiring schema + * + * +-------------+ +-------------+ + * | CNV |<-----+--| GPIO | + * | | +--| PWM0 | + * | | | | + * | | +--| PWM1 | + * | | | +-------------+ + * | | +->| TRIGGER | + * | | | | + * | ADC | | SPI | + * | | | controller | + * | | | | + * | SDI |<--------| SDO | + * | SDO |-------->| SDI | + * | SCLK |<--------| SCLK | + * +-------------+ +-------------+ + * + */ +static int ltc2378_update_conversion_rate(struct ltc2378_state *st, int freq_Hz) +{ + struct spi_offload_trigger_config config = st->offload_trigger_config; + unsigned int min_read_offset, offload_period_ns; + struct pwm_waveform cnv_wf = { }; + u64 target = LTC2378_TCNV_HIGH_NS; + unsigned int count; + u64 offload_offset_ns; + int ret; + + if (freq_Hz == 0) + return -EINVAL; + + if (!in_range(freq_Hz, 1, st->info->max_sample_rate_Hz)) + return -ERANGE; + + /* Configure CNV PWM waveform */ + cnv_wf.period_length_ns = DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq_Hz); + + /* + * Ensure CNV high time meets minimum requirement (20ns). The PWM + * hardware may round the duty cycle, so iterate until we get at least + * the minimum required high time (or reach a try count limit). + */ + count = 100; + do { + cnv_wf.duty_length_ns = target; + ret = pwm_round_waveform_might_sleep(st->cnv_trigger, &cnv_wf); + if (ret) + return ret; + target += 10; /* Increment by PWM duty cycle period */ + } while (count-- && cnv_wf.duty_length_ns < LTC2378_TCNV_HIGH_NS); + + /* Check the minimum CNV high time is met */ + if (cnv_wf.duty_length_ns < LTC2378_TCNV_HIGH_NS) + return -EDOM; + + /* + * Configure SPI offload PWM trigger. + * The trigger should fire after tBUSYLH + tCONV + tDSDOBUSYL. + * Minimum time needed: TBUSYLH (13ns) + TCONV (part-specific) + TDSDOBUSYL (5ns) + * + * Use the same period as CNV PWM to avoid timing issues. + * Convert back from period to frequency for the SPI offload API. + */ + offload_period_ns = cnv_wf.period_length_ns; + config.periodic.frequency_hz = div_u64(HZ_PER_GHZ, offload_period_ns); + min_read_offset = LTC2378_TBUSYLH_NS + st->info->tconv_ns + LTC2378_TDSDOBUSYL_NS; + offload_offset_ns = min_read_offset; + count = 100; + do { + config.periodic.offset_ns = offload_offset_ns; + ret = spi_offload_trigger_validate(st->offload_trigger, &config); + if (ret) + return ret; + offload_offset_ns += 10; + } while (count-- && config.periodic.offset_ns < min_read_offset); + + /* Check the minimum CNV to SCLK delay is met */ + if (config.periodic.offset_ns < min_read_offset) + return -EDOM; + + /* Check the PWM periods remain the same */ + offload_period_ns = div64_u64(HZ_PER_GHZ, config.periodic.frequency_hz); + if (cnv_wf.period_length_ns != offload_period_ns) + return -EDOM; + + st->offload_trigger_config = config; + st->cnv_wf = cnv_wf; + st->cnv_Hz = DIV_ROUND_CLOSEST_ULL(HZ_PER_GHZ, cnv_wf.period_length_ns); + + return 0; +} + +static int ltc2378_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ltc2378_state *st = iio_priv(indio_dev); + + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return ltc2378_update_conversion_rate(st, val); + default: + return -EINVAL; + } +} + +static const struct iio_info ltc2378_iio_info = { + .read_raw = <c2378_read_raw, +}; + +static const struct iio_info ltc2378_offload_iio_info = { + .read_raw = <c2378_read_raw, + .read_avail = <c2378_read_avail, + .write_raw = <c2378_write_raw, +}; + +static int ltc2378_offload_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ltc2378_state *st = iio_priv(indio_dev); + int ret; + + ret = pwm_set_waveform_might_sleep(st->cnv_trigger, &st->cnv_wf, true); + if (ret) + return ret; + + ret = spi_offload_trigger_enable(st->offload, st->offload_trigger, + &st->offload_trigger_config); + if (ret) + goto out_pwm_disable; + + return 0; + +out_pwm_disable: + pwm_disable(st->cnv_trigger); + return ret; +} + +static int ltc2378_offload_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ltc2378_state *st = iio_priv(indio_dev); + + spi_offload_trigger_disable(st->offload, st->offload_trigger); + pwm_disable(st->cnv_trigger); + + return 0; +} + +static const struct iio_buffer_setup_ops ltc2378_offload_buffer_ops = { + .postenable = <c2378_offload_buffer_postenable, + .predisable = <c2378_offload_buffer_predisable, +}; + +static int ltc2378_prepare_offload_message(struct device *dev, + struct ltc2378_state *st) +{ + unsigned int resolution = st->info->offload_chan.scan_type.realbits; + + st->offload_xfer.bits_per_word = resolution; + st->offload_xfer.len = spi_bpw_to_bytes(resolution); + st->offload_xfer.offload_flags = SPI_OFFLOAD_XFER_RX_STREAM; + + /* Initialize message with offload */ + spi_message_init_with_transfers(&st->offload_msg, &st->offload_xfer, 1); + st->offload_msg.offload = st->offload; + + return devm_spi_optimize_message(dev, st->spi, &st->offload_msg); +} + +static int ltc2378_spi_offload_setup(struct iio_dev *indio_dev, + struct ltc2378_state *st) +{ + struct device *dev = &st->spi->dev; + struct dma_chan *rx_dma; + + indio_dev->setup_ops = <c2378_offload_buffer_ops; + + st->offload_trigger = devm_spi_offload_trigger_get(dev, st->offload, + SPI_OFFLOAD_TRIGGER_PERIODIC); + if (IS_ERR(st->offload_trigger)) + return dev_err_probe(dev, PTR_ERR(st->offload_trigger), + "failed to get offload trigger\n"); + + st->offload_trigger_config.type = SPI_OFFLOAD_TRIGGER_PERIODIC; + + rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev, st->offload); + if (IS_ERR(rx_dma)) + return dev_err_probe(dev, PTR_ERR(rx_dma), "failed to get offload RX DMA\n"); + + return devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, rx_dma, + IIO_BUFFER_DIRECTION_IN); +} + +static int ltc2378_pwm_get(struct ltc2378_state *st) +{ + struct device *dev = &st->spi->dev; + + st->cnv_trigger = devm_pwm_get(dev, NULL); + if (IS_ERR(st->cnv_trigger)) + return dev_err_probe(dev, PTR_ERR(st->cnv_trigger), + "failed to get cnv pwm\n"); + + /* + * Disable the PWM connected to CNV in case it was left running by + * something else. + */ + pwm_disable(st->cnv_trigger); + + return 0; +} + +static const struct spi_offload_config ltc2378_offload_config = { + .capability_flags = SPI_OFFLOAD_CAP_TRIGGER | + SPI_OFFLOAD_CAP_RX_STREAM_DMA, +}; + +static int ltc2378_refin_setup(struct device *dev, struct ltc2378_state *st) +{ + int ret; + + /* + * The internal reference buffer amplifies both the internal reference + * and REFIN by a factor of 2. + */ + ret = devm_regulator_get_enable_read_voltage(dev, "refin"); + if (ret == -ENODEV) { /* refin is optional */ + st->ref_uV = st->info->internal_ref_uV * 2; + return 0; + } + + if (ret < 0) + return dev_err_probe(dev, ret, "failed to read refin regulator\n"); + + st->ref_uV = ret * 2; + + return 0; +} + +static int ltc2378_ref_setup(struct device *dev, struct ltc2378_state *st) +{ + int ret; + + ret = devm_regulator_get_enable_read_voltage(dev, "ref"); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to read ref regulator\n"); + + st->ref_uV = ret; + + return 0; +} + +static int ltc2378_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ltc2378_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; + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + st->info = spi_get_device_match_data(spi); + if (!st->info) + return -EINVAL; + + if (st->info->internal_ref_uV) + ret = ltc2378_refin_setup(dev, st); + else + ret = ltc2378_ref_setup(dev, st); + if (ret) + return ret; + + indio_dev->name = st->info->name; + indio_dev->modes = INDIO_DIRECT_MODE; + + st->cnv_gpio = devm_gpiod_get(dev, "cnv", GPIOD_OUT_LOW); + if (IS_ERR(st->cnv_gpio)) + return dev_err_probe(dev, PTR_ERR(st->cnv_gpio), + "failed to get CNV GPIO"); + + st->offload = devm_spi_offload_get(dev, spi, <c2378_offload_config); + ret = PTR_ERR_OR_ZERO(st->offload); + /* Fall back to low speed usage when no SPI offload is available. */ + if (ret == -ENODEV) { + indio_dev->info = <c2378_iio_info; + indio_dev->channels = st->info->chan; + indio_dev->num_channels = ARRAY_SIZE(st->info->chan); + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + ltc2378_trigger_handler, + NULL); + if (ret) + return ret; + } else if (ret) { + return dev_err_probe(dev, ret, "failed to get offload\n"); + } else { + indio_dev->info = <c2378_offload_iio_info; + indio_dev->channels = &st->info->offload_chan; + indio_dev->num_channels = 1; + ret = ltc2378_spi_offload_setup(indio_dev, st); + if (ret) + return dev_err_probe(dev, ret, + "failed to setup SPI offload\n"); + + ret = ltc2378_pwm_get(st); + if (ret) + return dev_err_probe(dev, ret, "failed to get PWM\n"); + + st->sample_freq_range[0] = 1; /* min */ + st->sample_freq_range[1] = 1; /* step */ + st->sample_freq_range[2] = st->info->max_sample_rate_Hz; /* max */ + + /* + * Start with a slower sampling rate so there is some room for + * adjusting the sample averaging and the sampling frequency + * without hitting the maximum conversion rate. + */ + ret = ltc2378_update_conversion_rate(st, st->info->max_sample_rate_Hz >> 4); + if (ret) + return dev_err_probe(dev, ret, + "failed to set offload samp freq\n"); + + ret = ltc2378_prepare_offload_message(&spi->dev, st); + if (ret) + return dev_err_probe(dev, ret, "failed to optimize SPI message\n"); + + /* + * Set single-read transfer bits_per_word so the SPI subsystem + * rearranges data to CPU endianness, enabling us to reuse + * offload_chan specifications for single-shot reads. + */ + st->xfer.bits_per_word = st->info->offload_chan.scan_type.realbits; + } + + st->xfer.rx_buf = &st->scan.data; + st->xfer.len = spi_bpw_to_bytes(indio_dev->channels[0].scan_type.realbits); + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct of_device_id ltc2378_of_match[] = { + { .compatible = "adi,ltc2338-18", .data = <c2338_18_chip_info }, + { .compatible = "adi,ltc2364-16", .data = <c2364_16_chip_info }, + { .compatible = "adi,ltc2364-18", .data = <c2364_18_chip_info }, + { .compatible = "adi,ltc2367-16", .data = <c2367_16_chip_info }, + { .compatible = "adi,ltc2367-18", .data = <c2367_18_chip_info }, + { .compatible = "adi,ltc2368-16", .data = <c2368_16_chip_info }, + { .compatible = "adi,ltc2368-18", .data = <c2368_18_chip_info }, + { .compatible = "adi,ltc2369-18", .data = <c2369_18_chip_info }, + { .compatible = "adi,ltc2370-16", .data = <c2370_16_chip_info }, + { .compatible = "adi,ltc2376-16", .data = <c2376_16_chip_info }, + { .compatible = "adi,ltc2376-18", .data = <c2376_18_chip_info }, + { .compatible = "adi,ltc2376-20", .data = <c2376_20_chip_info }, + { .compatible = "adi,ltc2377-16", .data = <c2377_16_chip_info }, + { .compatible = "adi,ltc2377-18", .data = <c2377_18_chip_info }, + { .compatible = "adi,ltc2377-20", .data = <c2377_20_chip_info }, + { .compatible = "adi,ltc2378-16", .data = <c2378_16_chip_info }, + { .compatible = "adi,ltc2378-18", .data = <c2378_18_chip_info }, + { .compatible = "adi,ltc2378-20", .data = <c2378_20_chip_info }, + { .compatible = "adi,ltc2379-18", .data = <c2379_18_chip_info }, + { .compatible = "adi,ltc2380-16", .data = <c2380_16_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ltc2378_of_match); + +static const struct spi_device_id ltc2378_spi_id[] = { + { .name = "ltc2338-18", .driver_data = (kernel_ulong_t)<c2338_18_chip_info }, + { .name = "ltc2364-16", .driver_data = (kernel_ulong_t)<c2364_16_chip_info }, + { .name = "ltc2364-18", .driver_data = (kernel_ulong_t)<c2364_18_chip_info }, + { .name = "ltc2367-16", .driver_data = (kernel_ulong_t)<c2367_16_chip_info }, + { .name = "ltc2367-18", .driver_data = (kernel_ulong_t)<c2367_18_chip_info }, + { .name = "ltc2368-16", .driver_data = (kernel_ulong_t)<c2368_16_chip_info }, + { .name = "ltc2368-18", .driver_data = (kernel_ulong_t)<c2368_18_chip_info }, + { .name = "ltc2369-18", .driver_data = (kernel_ulong_t)<c2369_18_chip_info }, + { .name = "ltc2370-16", .driver_data = (kernel_ulong_t)<c2370_16_chip_info }, + { .name = "ltc2376-16", .driver_data = (kernel_ulong_t)<c2376_16_chip_info }, + { .name = "ltc2376-18", .driver_data = (kernel_ulong_t)<c2376_18_chip_info }, + { .name = "ltc2376-20", .driver_data = (kernel_ulong_t)<c2376_20_chip_info }, + { .name = "ltc2377-16", .driver_data = (kernel_ulong_t)<c2377_16_chip_info }, + { .name = "ltc2377-18", .driver_data = (kernel_ulong_t)<c2377_18_chip_info }, + { .name = "ltc2377-20", .driver_data = (kernel_ulong_t)<c2377_20_chip_info }, + { .name = "ltc2378-16", .driver_data = (kernel_ulong_t)<c2378_16_chip_info }, + { .name = "ltc2378-18", .driver_data = (kernel_ulong_t)<c2378_18_chip_info }, + { .name = "ltc2378-20", .driver_data = (kernel_ulong_t)<c2378_20_chip_info }, + { .name = "ltc2379-18", .driver_data = (kernel_ulong_t)<c2379_18_chip_info }, + { .name = "ltc2380-16", .driver_data = (kernel_ulong_t)<c2380_16_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ltc2378_spi_id); + +static struct spi_driver ltc2378_driver = { + .driver = { + .name = "ltc2378", + .of_match_table = ltc2378_of_match + }, + .probe = ltc2378_probe, + .id_table = ltc2378_spi_id, +}; +module_spi_driver(ltc2378_driver); + +MODULE_AUTHOR("Marcelo Schmitt "); +MODULE_DESCRIPTION("Analog Devices LTC2378 ADC series driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER"); +MODULE_IMPORT_NS("SPI_OFFLOAD"); diff --git a/drivers/iio/adc/ltc2471.c b/drivers/iio/adc/ltc2471.c new file mode 100644 index 000000000..369af7009 --- /dev/null +++ b/drivers/iio/adc/ltc2471.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Linear Technology LTC2471 and LTC2473 voltage monitors + * The LTC2473 is identical to the 2471, but reports a differential signal. + * + * Copyright (C) 2017 Topic Embedded Products + * Author: Mike Looijmans + */ + +#include +#include +#include +#include +#include +#include + +enum ltc2471_chips { + ltc2471, + ltc2473, +}; + +struct ltc2471_data { + struct i2c_client *client; +}; + +/* Reference voltage is 1.25V */ +#define LTC2471_VREF 1250 + +/* Read two bytes from the I2C bus to obtain the ADC result */ +static int ltc2471_get_value(struct i2c_client *client) +{ + int ret; + __be16 buf; + + ret = i2c_master_recv(client, (char *)&buf, sizeof(buf)); + if (ret < 0) + return ret; + if (ret != sizeof(buf)) + return -EIO; + + /* MSB first */ + return be16_to_cpu(buf); +} + +static int ltc2471_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ltc2471_data *data = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + ret = ltc2471_get_value(data->client); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + if (chan->differential) + /* Output ranges from -VREF to +VREF */ + *val = 2 * LTC2471_VREF; + else + /* Output ranges from 0 to VREF */ + *val = LTC2471_VREF; + *val2 = 16; /* 16 data bits */ + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_OFFSET: + /* Only differential chip has this property */ + *val = -LTC2471_VREF; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static const struct iio_chan_spec ltc2471_channel[] = { + { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static const struct iio_chan_spec ltc2473_channel[] = { + { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .differential = 1, + }, +}; + +static const struct iio_info ltc2471_info = { + .read_raw = ltc2471_read_raw, +}; + +static int ltc2471_i2c_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct iio_dev *indio_dev; + struct ltc2471_data *data; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -EOPNOTSUPP; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + + indio_dev->name = id->name; + indio_dev->info = <c2471_info; + indio_dev->modes = INDIO_DIRECT_MODE; + if (id->driver_data == ltc2473) + indio_dev->channels = ltc2473_channel; + else + indio_dev->channels = ltc2471_channel; + indio_dev->num_channels = 1; + + /* Trigger once to start conversion and check if chip is there */ + ret = ltc2471_get_value(client); + if (ret < 0) { + dev_err(&client->dev, "Cannot read from device.\n"); + return ret; + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id ltc2471_i2c_id[] = { + { .name = "ltc2471", .driver_data = ltc2471 }, + { .name = "ltc2473", .driver_data = ltc2473 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltc2471_i2c_id); + +static struct i2c_driver ltc2471_i2c_driver = { + .driver = { + .name = "ltc2471", + }, + .probe = ltc2471_i2c_probe, + .id_table = ltc2471_i2c_id, +}; + +module_i2c_driver(ltc2471_i2c_driver); + +MODULE_DESCRIPTION("LTC2471/LTC2473 ADC driver"); +MODULE_AUTHOR("Topic Embedded Products"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ltc2485.c b/drivers/iio/adc/ltc2485.c new file mode 100644 index 000000000..8c3806e06 --- /dev/null +++ b/drivers/iio/adc/ltc2485.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ltc2485.c - Driver for Linear Technology LTC2485 ADC + * + * Copyright (C) 2016 Alison Schofield + * + * Datasheet: http://cds.linear.com/docs/en/datasheet/2485fd.pdf + */ + +#include +#include +#include + +#include +#include + +/* Power-on configuration: rejects both 50/60Hz, operates at 1x speed */ +#define LTC2485_CONFIG_DEFAULT 0 + +struct ltc2485_data { + struct i2c_client *client; + ktime_t time_prev; /* last conversion */ +}; + +static void ltc2485_wait_conv(struct ltc2485_data *data) +{ + const unsigned int conv_time = 147; /* conversion time ms */ + unsigned int time_elapsed; + + /* delay if conversion time not passed since last read or write */ + time_elapsed = ktime_ms_delta(ktime_get(), data->time_prev); + + if (time_elapsed < conv_time) + msleep(conv_time - time_elapsed); +} + +static int ltc2485_read(struct ltc2485_data *data, int *val) +{ + struct i2c_client *client = data->client; + __be32 buf = 0; + int ret; + + ltc2485_wait_conv(data); + + ret = i2c_master_recv(client, (char *)&buf, 4); + if (ret < 0) { + dev_err(&client->dev, "i2c_master_recv failed\n"); + return ret; + } + data->time_prev = ktime_get(); + *val = sign_extend32(be32_to_cpu(buf) >> 6, 24); + + return ret; +} + +static int ltc2485_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ltc2485_data *data = iio_priv(indio_dev); + int ret; + + if (mask == IIO_CHAN_INFO_RAW) { + ret = ltc2485_read(data, val); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + + } else if (mask == IIO_CHAN_INFO_SCALE) { + *val = 5000; /* on board vref millivolts */ + *val2 = 25; /* 25 (24 + sign) data bits */ + return IIO_VAL_FRACTIONAL_LOG2; + + } else { + return -EINVAL; + } +} + +static const struct iio_chan_spec ltc2485_channel[] = { + { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) + }, +}; + +static const struct iio_info ltc2485_info = { + .read_raw = ltc2485_read_raw, +}; + +static int ltc2485_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct iio_dev *indio_dev; + struct ltc2485_data *data; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C | + I2C_FUNC_SMBUS_WRITE_BYTE)) + return -EOPNOTSUPP; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + indio_dev->name = id->name; + indio_dev->info = <c2485_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ltc2485_channel; + indio_dev->num_channels = ARRAY_SIZE(ltc2485_channel); + + ret = i2c_smbus_write_byte(data->client, LTC2485_CONFIG_DEFAULT); + if (ret < 0) + return ret; + + data->time_prev = ktime_get(); + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id ltc2485_id[] = { + { .name = "ltc2485" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltc2485_id); + +static struct i2c_driver ltc2485_driver = { + .driver = { + .name = "ltc2485", + }, + .probe = ltc2485_probe, + .id_table = ltc2485_id, +}; +module_i2c_driver(ltc2485_driver); + +MODULE_AUTHOR("Alison Schofield "); +MODULE_DESCRIPTION("Linear Technology LTC2485 ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ltc2496.c b/drivers/iio/adc/ltc2496.c new file mode 100644 index 000000000..5b5b6ab28 --- /dev/null +++ b/drivers/iio/adc/ltc2496.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ltc2496.c - Driver for Analog Devices/Linear Technology LTC2496 ADC + * + * Based on ltc2497.c which has + * Copyright (C) 2017 Analog Devices Inc. + * + * Licensed under the GPL-2. + * + * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/2496fc.pdf + */ + +#include +#include +#include +#include +#include + +#include "ltc2497.h" + +struct ltc2496_driverdata { + /* this must be the first member */ + struct ltc2497core_driverdata common_ddata; + struct spi_device *spi; + + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + */ + unsigned char rxbuf[3] __aligned(IIO_DMA_MINALIGN); + unsigned char txbuf[3]; +}; + +static int ltc2496_result_and_measure(struct ltc2497core_driverdata *ddata, + u8 address, int *val) +{ + struct ltc2496_driverdata *st = + container_of(ddata, struct ltc2496_driverdata, common_ddata); + struct spi_transfer t = { + .tx_buf = st->txbuf, + .rx_buf = st->rxbuf, + .len = sizeof(st->txbuf), + }; + int ret; + + st->txbuf[0] = LTC2497_ENABLE | address; + + ret = spi_sync_transfer(st->spi, &t, 1); + if (ret < 0) { + dev_err(&st->spi->dev, "spi_sync_transfer failed: %pe\n", + ERR_PTR(ret)); + return ret; + } + + if (val) + *val = ((st->rxbuf[0] & 0x3f) << 12 | + st->rxbuf[1] << 4 | st->rxbuf[2] >> 4) - + (1 << 17); + + return 0; +} + +static int ltc2496_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct ltc2496_driverdata *st; + struct device *dev = &spi->dev; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + st->spi = spi; + st->common_ddata.result_and_measure = ltc2496_result_and_measure; + st->common_ddata.chip_info = device_get_match_data(dev); + + return ltc2497core_probe(dev, indio_dev); +} + +static void ltc2496_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + + ltc2497core_remove(indio_dev); +} + +static const struct ltc2497_chip_info ltc2496_info = { + .resolution = 16, + .name = NULL, +}; + +static const struct of_device_id ltc2496_of_match[] = { + { .compatible = "lltc,ltc2496", .data = <c2496_info, }, + { } +}; +MODULE_DEVICE_TABLE(of, ltc2496_of_match); + +static struct spi_driver ltc2496_driver = { + .driver = { + .name = "ltc2496", + .of_match_table = ltc2496_of_match, + }, + .probe = ltc2496_probe, + .remove = ltc2496_remove, +}; +module_spi_driver(ltc2496_driver); + +MODULE_AUTHOR("Uwe Kleine-König "); +MODULE_DESCRIPTION("Linear Technology LTC2496 ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ltc2497-core.c b/drivers/iio/adc/ltc2497-core.c new file mode 100644 index 000000000..2dc5c7044 --- /dev/null +++ b/drivers/iio/adc/ltc2497-core.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ltc2497-core.c - Common code for Analog Devices/Linear Technology + * LTC2496 and LTC2497 ADCs + * + * Copyright (C) 2017 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include + +#include "ltc2497.h" + +#define LTC2497_SGL BIT(4) +#define LTC2497_DIFF 0 +#define LTC2497_SIGN BIT(3) + +static int ltc2497core_wait_conv(struct ltc2497core_driverdata *ddata) +{ + s64 time_elapsed; + + time_elapsed = ktime_ms_delta(ktime_get(), ddata->time_prev); + + if (time_elapsed < LTC2497_CONVERSION_TIME_MS) { + /* delay if conversion time not passed + * since last read or write + */ + if (msleep_interruptible( + LTC2497_CONVERSION_TIME_MS - time_elapsed)) + return -ERESTARTSYS; + + return 0; + } + + if (time_elapsed - LTC2497_CONVERSION_TIME_MS <= 0) { + /* We're in automatic mode - + * so the last reading is still not outdated + */ + return 0; + } + + return 1; +} + +static int ltc2497core_read(struct ltc2497core_driverdata *ddata, u8 address, int *val) +{ + int ret; + + ret = ltc2497core_wait_conv(ddata); + if (ret < 0) + return ret; + + if (ret || ddata->addr_prev != address) { + ret = ddata->result_and_measure(ddata, address, NULL); + if (ret < 0) + return ret; + ddata->addr_prev = address; + + if (msleep_interruptible(LTC2497_CONVERSION_TIME_MS)) + return -ERESTARTSYS; + } + + ret = ddata->result_and_measure(ddata, address, val); + if (ret < 0) + return ret; + + ddata->time_prev = ktime_get(); + + return ret; +} + +static int ltc2497core_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ltc2497core_driverdata *ddata = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&ddata->lock); + ret = ltc2497core_read(ddata, chan->address, val); + mutex_unlock(&ddata->lock); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + ret = regulator_get_voltage(ddata->ref); + if (ret < 0) + return ret; + + *val = ret / 1000; + *val2 = ddata->chip_info->resolution + 1; + + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } +} + +#define LTC2497_CHAN(_chan, _addr, _ds_name) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_chan), \ + .address = (_addr | (_chan / 2) | ((_chan & 1) ? LTC2497_SIGN : 0)), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = (_ds_name), \ +} + +#define LTC2497_CHAN_DIFF(_chan, _addr) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_chan) * 2 + ((_addr) & LTC2497_SIGN ? 1 : 0), \ + .channel2 = (_chan) * 2 + ((_addr) & LTC2497_SIGN ? 0 : 1),\ + .address = (_addr | _chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .differential = 1, \ +} + +static const struct iio_chan_spec ltc2497core_channel[] = { + LTC2497_CHAN(0, LTC2497_SGL, "CH0"), + LTC2497_CHAN(1, LTC2497_SGL, "CH1"), + LTC2497_CHAN(2, LTC2497_SGL, "CH2"), + LTC2497_CHAN(3, LTC2497_SGL, "CH3"), + LTC2497_CHAN(4, LTC2497_SGL, "CH4"), + LTC2497_CHAN(5, LTC2497_SGL, "CH5"), + LTC2497_CHAN(6, LTC2497_SGL, "CH6"), + LTC2497_CHAN(7, LTC2497_SGL, "CH7"), + LTC2497_CHAN(8, LTC2497_SGL, "CH8"), + LTC2497_CHAN(9, LTC2497_SGL, "CH9"), + LTC2497_CHAN(10, LTC2497_SGL, "CH10"), + LTC2497_CHAN(11, LTC2497_SGL, "CH11"), + LTC2497_CHAN(12, LTC2497_SGL, "CH12"), + LTC2497_CHAN(13, LTC2497_SGL, "CH13"), + LTC2497_CHAN(14, LTC2497_SGL, "CH14"), + LTC2497_CHAN(15, LTC2497_SGL, "CH15"), + LTC2497_CHAN_DIFF(0, LTC2497_DIFF), + LTC2497_CHAN_DIFF(1, LTC2497_DIFF), + LTC2497_CHAN_DIFF(2, LTC2497_DIFF), + LTC2497_CHAN_DIFF(3, LTC2497_DIFF), + LTC2497_CHAN_DIFF(4, LTC2497_DIFF), + LTC2497_CHAN_DIFF(5, LTC2497_DIFF), + LTC2497_CHAN_DIFF(6, LTC2497_DIFF), + LTC2497_CHAN_DIFF(7, LTC2497_DIFF), + LTC2497_CHAN_DIFF(0, LTC2497_DIFF | LTC2497_SIGN), + LTC2497_CHAN_DIFF(1, LTC2497_DIFF | LTC2497_SIGN), + LTC2497_CHAN_DIFF(2, LTC2497_DIFF | LTC2497_SIGN), + LTC2497_CHAN_DIFF(3, LTC2497_DIFF | LTC2497_SIGN), + LTC2497_CHAN_DIFF(4, LTC2497_DIFF | LTC2497_SIGN), + LTC2497_CHAN_DIFF(5, LTC2497_DIFF | LTC2497_SIGN), + LTC2497_CHAN_DIFF(6, LTC2497_DIFF | LTC2497_SIGN), + LTC2497_CHAN_DIFF(7, LTC2497_DIFF | LTC2497_SIGN), +}; + +static const struct iio_info ltc2497core_info = { + .read_raw = ltc2497core_read_raw, +}; + +int ltc2497core_probe(struct device *dev, struct iio_dev *indio_dev) +{ + struct ltc2497core_driverdata *ddata = iio_priv(indio_dev); + struct iio_map *plat_data = dev_get_platdata(dev); + int ret; + + /* + * Keep using dev_name() for the iio_dev's name on some of the parts, + * since updating it would result in a ABI breakage. + */ + if (ddata->chip_info->name) + indio_dev->name = ddata->chip_info->name; + else + indio_dev->name = dev_name(dev); + + indio_dev->info = <c2497core_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ltc2497core_channel; + indio_dev->num_channels = ARRAY_SIZE(ltc2497core_channel); + + ret = ddata->result_and_measure(ddata, LTC2497_CONFIG_DEFAULT, NULL); + if (ret < 0) + return ret; + + ddata->ref = devm_regulator_get(dev, "vref"); + if (IS_ERR(ddata->ref)) + return dev_err_probe(dev, PTR_ERR(ddata->ref), + "Failed to get vref regulator\n"); + + ret = regulator_enable(ddata->ref); + if (ret < 0) { + dev_err(dev, "Failed to enable vref regulator: %pe\n", + ERR_PTR(ret)); + return ret; + } + + ret = iio_map_array_register(indio_dev, plat_data); + if (ret) { + dev_err(&indio_dev->dev, "iio map err: %d\n", ret); + goto err_regulator_disable; + } + + ddata->addr_prev = LTC2497_CONFIG_DEFAULT; + ddata->time_prev = ktime_get(); + + mutex_init(&ddata->lock); + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto err_array_unregister; + + return 0; + +err_array_unregister: + iio_map_array_unregister(indio_dev); + +err_regulator_disable: + regulator_disable(ddata->ref); + + return ret; +} +EXPORT_SYMBOL_NS(ltc2497core_probe, "LTC2497"); + +void ltc2497core_remove(struct iio_dev *indio_dev) +{ + struct ltc2497core_driverdata *ddata = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + iio_map_array_unregister(indio_dev); + + regulator_disable(ddata->ref); +} +EXPORT_SYMBOL_NS(ltc2497core_remove, "LTC2497"); + +MODULE_DESCRIPTION("common code for LTC2496/LTC2497 drivers"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ltc2497.c b/drivers/iio/adc/ltc2497.c new file mode 100644 index 000000000..c1668b5a3 --- /dev/null +++ b/drivers/iio/adc/ltc2497.c @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ltc2497.c - Driver for Analog Devices/Linear Technology LTC2497 ADC + * + * Copyright (C) 2017 Analog Devices Inc. + * + * Datasheet: http://cds.linear.com/docs/en/datasheet/2497fd.pdf + */ + +#include +#include +#include +#include +#include + +#include + +#include "ltc2497.h" + +enum ltc2497_chip_type { + TYPE_LTC2497, + TYPE_LTC2499, +}; + +struct ltc2497_driverdata { + /* this must be the first member */ + struct ltc2497core_driverdata common_ddata; + struct i2c_client *client; + u32 recv_size; + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + */ + union { + __be32 d32; + u8 d8[3]; + } data __aligned(IIO_DMA_MINALIGN); +}; + +static int ltc2497_result_and_measure(struct ltc2497core_driverdata *ddata, + u8 address, int *val) +{ + struct ltc2497_driverdata *st = + container_of(ddata, struct ltc2497_driverdata, common_ddata); + int ret; + + if (val) { + if (st->recv_size == 3) + ret = i2c_master_recv(st->client, (char *)&st->data.d8, + st->recv_size); + else + ret = i2c_master_recv(st->client, (char *)&st->data.d32, + st->recv_size); + if (ret < 0) { + dev_err(&st->client->dev, "i2c_master_recv failed\n"); + return ret; + } + + /* + * The data format is 16/24 bit 2s complement, but with an upper sign bit on the + * resolution + 1 position, which is set for positive values only. Given this + * bit's value, subtracting BIT(resolution + 1) from the ADC's result is + * equivalent to a sign extension. + */ + if (st->recv_size == 3) { + *val = (get_unaligned_be24(st->data.d8) >> 6) + - BIT(ddata->chip_info->resolution + 1); + } else { + *val = (be32_to_cpu(st->data.d32) >> 6) + - BIT(ddata->chip_info->resolution + 1); + } + + /* + * The part started a new conversion at the end of the above i2c + * transfer, so if the address didn't change since the last call + * everything is fine and we can return early. + * If not (which should only happen when some sort of bulk + * conversion is implemented) we have to program the new + * address. Note that this probably fails as the conversion that + * was triggered above is like not complete yet and the two + * operations have to be done in a single transfer. + */ + if (ddata->addr_prev == address) + return 0; + } + + ret = i2c_smbus_write_byte(st->client, + LTC2497_ENABLE | address); + if (ret) + dev_err(&st->client->dev, "i2c transfer failed: %pe\n", + ERR_PTR(ret)); + return ret; +} + +static int ltc2497_probe(struct i2c_client *client) +{ + const struct ltc2497_chip_info *chip_info; + struct iio_dev *indio_dev; + struct ltc2497_driverdata *st; + struct device *dev = &client->dev; + u32 resolution; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C | + I2C_FUNC_SMBUS_WRITE_BYTE)) + return -EOPNOTSUPP; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + st->client = client; + st->common_ddata.result_and_measure = ltc2497_result_and_measure; + + chip_info = i2c_get_match_data(client); + st->common_ddata.chip_info = chip_info; + + resolution = chip_info->resolution; + st->recv_size = BITS_TO_BYTES(resolution) + 1; + + return ltc2497core_probe(dev, indio_dev); +} + +static void ltc2497_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + ltc2497core_remove(indio_dev); +} + +static const struct ltc2497_chip_info ltc2497_info[] = { + [TYPE_LTC2497] = { + .resolution = 16, + .name = NULL, + }, + [TYPE_LTC2499] = { + .resolution = 24, + .name = "ltc2499", + }, +}; + +static const struct i2c_device_id ltc2497_id[] = { + { .name = "ltc2497", .driver_data = (kernel_ulong_t)<c2497_info[TYPE_LTC2497] }, + { .name = "ltc2499", .driver_data = (kernel_ulong_t)<c2497_info[TYPE_LTC2499] }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltc2497_id); + +static const struct of_device_id ltc2497_of_match[] = { + { .compatible = "lltc,ltc2497", .data = <c2497_info[TYPE_LTC2497] }, + { .compatible = "lltc,ltc2499", .data = <c2497_info[TYPE_LTC2499] }, + { } +}; +MODULE_DEVICE_TABLE(of, ltc2497_of_match); + +static struct i2c_driver ltc2497_driver = { + .driver = { + .name = "ltc2497", + .of_match_table = ltc2497_of_match, + }, + .probe = ltc2497_probe, + .remove = ltc2497_remove, + .id_table = ltc2497_id, +}; +module_i2c_driver(ltc2497_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Linear Technology LTC2497 ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ltc2497.h b/drivers/iio/adc/ltc2497.h new file mode 100644 index 000000000..64e81c95a --- /dev/null +++ b/drivers/iio/adc/ltc2497.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#define LTC2497_ENABLE 0xA0 +#define LTC2497_CONFIG_DEFAULT LTC2497_ENABLE +#define LTC2497_CONVERSION_TIME_MS 150ULL + +struct ltc2497_chip_info { + u32 resolution; + const char *name; +}; + +struct ltc2497core_driverdata { + struct regulator *ref; + ktime_t time_prev; + /* lock to protect against multiple access to the device */ + struct mutex lock; + const struct ltc2497_chip_info *chip_info; + u8 addr_prev; + int (*result_and_measure)(struct ltc2497core_driverdata *ddata, + u8 address, int *val); +}; + +int ltc2497core_probe(struct device *dev, struct iio_dev *indio_dev); +void ltc2497core_remove(struct iio_dev *indio_dev); + +MODULE_IMPORT_NS("LTC2497"); diff --git a/drivers/iio/adc/max1027.c b/drivers/iio/adc/max1027.c new file mode 100644 index 000000000..fb6f12316 --- /dev/null +++ b/drivers/iio/adc/max1027.c @@ -0,0 +1,635 @@ +// SPDX-License-Identifier: GPL-2.0-only + /* + * iio/adc/max1027.c + * Copyright (C) 2014 Philippe Reynes + * + * based on linux/drivers/iio/ad7923.c + * Copyright 2011 Analog Devices Inc (from AD7923 Driver) + * Copyright 2012 CS Systemes d'Information + * + * max1027.c + * + * Partial support for max1027 and similar chips. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define MAX1027_CONV_REG BIT(7) +#define MAX1027_SETUP_REG BIT(6) +#define MAX1027_AVG_REG BIT(5) +#define MAX1027_RST_REG BIT(4) + +/* conversion register */ +#define MAX1027_TEMP BIT(0) +#define MAX1027_SCAN_0_N (0x00 << 1) +#define MAX1027_SCAN_N_M (0x01 << 1) +#define MAX1027_SCAN_N (0x02 << 1) +#define MAX1027_NOSCAN (0x03 << 1) +#define MAX1027_CHAN(n) ((n) << 3) + +/* setup register */ +#define MAX1027_UNIPOLAR 0x02 +#define MAX1027_BIPOLAR 0x03 +#define MAX1027_REF_MODE0 (0x00 << 2) +#define MAX1027_REF_MODE1 (0x01 << 2) +#define MAX1027_REF_MODE2 (0x02 << 2) +#define MAX1027_REF_MODE3 (0x03 << 2) +#define MAX1027_CKS_MODE0 (0x00 << 4) +#define MAX1027_CKS_MODE1 (0x01 << 4) +#define MAX1027_CKS_MODE2 (0x02 << 4) +#define MAX1027_CKS_MODE3 (0x03 << 4) + +/* averaging register */ +#define MAX1027_NSCAN_4 0x00 +#define MAX1027_NSCAN_8 0x01 +#define MAX1027_NSCAN_12 0x02 +#define MAX1027_NSCAN_16 0x03 +#define MAX1027_NAVG_4 (0x00 << 2) +#define MAX1027_NAVG_8 (0x01 << 2) +#define MAX1027_NAVG_16 (0x02 << 2) +#define MAX1027_NAVG_32 (0x03 << 2) +#define MAX1027_AVG_EN BIT(4) + +/* Device can achieve 300ksps so we assume a 3.33us conversion delay */ +#define MAX1027_CONVERSION_UDELAY 4 + +enum max1027_id { + max1027, + max1029, + max1031, + max1227, + max1229, + max1231, +}; + +static const struct spi_device_id max1027_id[] = { + { .name = "max1027", .driver_data = max1027 }, + { .name = "max1029", .driver_data = max1029 }, + { .name = "max1031", .driver_data = max1031 }, + { .name = "max1227", .driver_data = max1227 }, + { .name = "max1229", .driver_data = max1229 }, + { .name = "max1231", .driver_data = max1231 }, + { } +}; +MODULE_DEVICE_TABLE(spi, max1027_id); + +static const struct of_device_id max1027_adc_dt_ids[] = { + { .compatible = "maxim,max1027" }, + { .compatible = "maxim,max1029" }, + { .compatible = "maxim,max1031" }, + { .compatible = "maxim,max1227" }, + { .compatible = "maxim,max1229" }, + { .compatible = "maxim,max1231" }, + { } +}; +MODULE_DEVICE_TABLE(of, max1027_adc_dt_ids); + +#define MAX1027_V_CHAN(index, depth) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = index + 1, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = depth, \ + .storagebits = 16, \ + .shift = (depth == 10) ? 2 : 0, \ + .endianness = IIO_BE, \ + }, \ + } + +#define MAX1027_T_CHAN \ + { \ + .type = IIO_TEMP, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = 0, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 12, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + } + +#define MAX1X27_CHANNELS(depth) \ + MAX1027_T_CHAN, \ + MAX1027_V_CHAN(0, depth), \ + MAX1027_V_CHAN(1, depth), \ + MAX1027_V_CHAN(2, depth), \ + MAX1027_V_CHAN(3, depth), \ + MAX1027_V_CHAN(4, depth), \ + MAX1027_V_CHAN(5, depth), \ + MAX1027_V_CHAN(6, depth), \ + MAX1027_V_CHAN(7, depth) + +#define MAX1X29_CHANNELS(depth) \ + MAX1X27_CHANNELS(depth), \ + MAX1027_V_CHAN(8, depth), \ + MAX1027_V_CHAN(9, depth), \ + MAX1027_V_CHAN(10, depth), \ + MAX1027_V_CHAN(11, depth) + +#define MAX1X31_CHANNELS(depth) \ + MAX1X29_CHANNELS(depth), \ + MAX1027_V_CHAN(12, depth), \ + MAX1027_V_CHAN(13, depth), \ + MAX1027_V_CHAN(14, depth), \ + MAX1027_V_CHAN(15, depth) + +static const struct iio_chan_spec max1027_channels[] = { + MAX1X27_CHANNELS(10), +}; + +static const struct iio_chan_spec max1029_channels[] = { + MAX1X29_CHANNELS(10), +}; + +static const struct iio_chan_spec max1031_channels[] = { + MAX1X31_CHANNELS(10), +}; + +static const struct iio_chan_spec max1227_channels[] = { + MAX1X27_CHANNELS(12), +}; + +static const struct iio_chan_spec max1229_channels[] = { + MAX1X29_CHANNELS(12), +}; + +static const struct iio_chan_spec max1231_channels[] = { + MAX1X31_CHANNELS(12), +}; + +/* + * These devices are able to scan from 0 to N, N being the highest voltage + * channel requested by the user. The temperature can be included or not, + * but cannot be retrieved alone. Based on the below + * ->available_scan_masks, the core will select the most appropriate + * ->active_scan_mask and the "minimum" number of channels will be + * scanned and pushed to the buffers. + * + * For example, if the user wants channels 1, 4 and 5, all channels from + * 0 to 5 will be scanned and pushed to the IIO buffers. The core will then + * filter out the unneeded samples based on the ->active_scan_mask that has + * been selected and only channels 1, 4 and 5 will be available to the user + * in the shared buffer. + */ +#define MAX1X27_SCAN_MASK_TEMP BIT(0) + +#define MAX1X27_SCAN_MASKS(temp) \ + GENMASK(1, 1 - (temp)), GENMASK(2, 1 - (temp)), \ + GENMASK(3, 1 - (temp)), GENMASK(4, 1 - (temp)), \ + GENMASK(5, 1 - (temp)), GENMASK(6, 1 - (temp)), \ + GENMASK(7, 1 - (temp)), GENMASK(8, 1 - (temp)) + +#define MAX1X29_SCAN_MASKS(temp) \ + MAX1X27_SCAN_MASKS(temp), \ + GENMASK(9, 1 - (temp)), GENMASK(10, 1 - (temp)), \ + GENMASK(11, 1 - (temp)), GENMASK(12, 1 - (temp)) + +#define MAX1X31_SCAN_MASKS(temp) \ + MAX1X29_SCAN_MASKS(temp), \ + GENMASK(13, 1 - (temp)), GENMASK(14, 1 - (temp)), \ + GENMASK(15, 1 - (temp)), GENMASK(16, 1 - (temp)) + +static const unsigned long max1027_available_scan_masks[] = { + MAX1X27_SCAN_MASKS(0), + MAX1X27_SCAN_MASKS(1), + 0x00000000, +}; + +static const unsigned long max1029_available_scan_masks[] = { + MAX1X29_SCAN_MASKS(0), + MAX1X29_SCAN_MASKS(1), + 0x00000000, +}; + +static const unsigned long max1031_available_scan_masks[] = { + MAX1X31_SCAN_MASKS(0), + MAX1X31_SCAN_MASKS(1), + 0x00000000, +}; + +struct max1027_chip_info { + const struct iio_chan_spec *channels; + unsigned int num_channels; + const unsigned long *available_scan_masks; +}; + +static const struct max1027_chip_info max1027_chip_info_tbl[] = { + [max1027] = { + .channels = max1027_channels, + .num_channels = ARRAY_SIZE(max1027_channels), + .available_scan_masks = max1027_available_scan_masks, + }, + [max1029] = { + .channels = max1029_channels, + .num_channels = ARRAY_SIZE(max1029_channels), + .available_scan_masks = max1029_available_scan_masks, + }, + [max1031] = { + .channels = max1031_channels, + .num_channels = ARRAY_SIZE(max1031_channels), + .available_scan_masks = max1031_available_scan_masks, + }, + [max1227] = { + .channels = max1227_channels, + .num_channels = ARRAY_SIZE(max1227_channels), + .available_scan_masks = max1027_available_scan_masks, + }, + [max1229] = { + .channels = max1229_channels, + .num_channels = ARRAY_SIZE(max1229_channels), + .available_scan_masks = max1029_available_scan_masks, + }, + [max1231] = { + .channels = max1231_channels, + .num_channels = ARRAY_SIZE(max1231_channels), + .available_scan_masks = max1031_available_scan_masks, + }, +}; + +struct max1027_state { + const struct max1027_chip_info *info; + struct spi_device *spi; + struct iio_trigger *trig; + __be16 *buffer; + struct mutex lock; + struct completion complete; + + u8 reg __aligned(IIO_DMA_MINALIGN); +}; + +static int max1027_wait_eoc(struct iio_dev *indio_dev) +{ + struct max1027_state *st = iio_priv(indio_dev); + unsigned int conversion_time = MAX1027_CONVERSION_UDELAY; + int ret; + + if (st->spi->irq) { + ret = wait_for_completion_timeout(&st->complete, + msecs_to_jiffies(1000)); + reinit_completion(&st->complete); + if (!ret) + return -ETIMEDOUT; + } else { + if (indio_dev->active_scan_mask) + conversion_time *= hweight32(*indio_dev->active_scan_mask); + + usleep_range(conversion_time, conversion_time * 2); + } + + return 0; +} + +/* Scan from chan 0 to the highest requested channel. Include temperature on demand. */ +static int max1027_configure_chans_and_start(struct iio_dev *indio_dev) +{ + struct max1027_state *st = iio_priv(indio_dev); + + st->reg = MAX1027_CONV_REG | MAX1027_SCAN_0_N; + st->reg |= MAX1027_CHAN(fls(*indio_dev->active_scan_mask) - 2); + if (*indio_dev->active_scan_mask & MAX1X27_SCAN_MASK_TEMP) + st->reg |= MAX1027_TEMP; + + return spi_write(st->spi, &st->reg, 1); +} + +static int max1027_enable_trigger(struct iio_dev *indio_dev, bool enable) +{ + struct max1027_state *st = iio_priv(indio_dev); + + st->reg = MAX1027_SETUP_REG | MAX1027_REF_MODE2; + + /* + * Start acquisition on: + * MODE0: external hardware trigger wired to the cnvst input pin + * MODE2: conversion register write + */ + if (enable) + st->reg |= MAX1027_CKS_MODE0; + else + st->reg |= MAX1027_CKS_MODE2; + + return spi_write(st->spi, &st->reg, 1); +} + +static int max1027_read_single_value(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + int ret; + struct max1027_state *st = iio_priv(indio_dev); + + /* Configure conversion register with the requested chan */ + st->reg = MAX1027_CONV_REG | MAX1027_CHAN(chan->channel) | + MAX1027_NOSCAN; + if (chan->type == IIO_TEMP) + st->reg |= MAX1027_TEMP; + ret = spi_write(st->spi, &st->reg, 1); + if (ret < 0) { + dev_err(&indio_dev->dev, + "Failed to configure conversion register\n"); + return ret; + } + + /* + * For an unknown reason, when we use the mode "10" (write + * conversion register), the interrupt doesn't occur every time. + * So we just wait the maximum conversion time and deliver the value. + */ + ret = max1027_wait_eoc(indio_dev); + if (ret) + return ret; + + /* Read result */ + ret = spi_read(st->spi, st->buffer, (chan->type == IIO_TEMP) ? 4 : 2); + if (ret < 0) + return ret; + + *val = be16_to_cpu(st->buffer[0]); + + return IIO_VAL_INT; +} + +static int max1027_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret = 0; + struct max1027_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = max1027_read_single_value(indio_dev, chan, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + *val = 1; + *val2 = 8; + return IIO_VAL_FRACTIONAL; + case IIO_VOLTAGE: + *val = 2500; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int max1027_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + struct max1027_state *st = iio_priv(indio_dev); + u8 *val = (u8 *)st->buffer; + + if (readval) { + int ret = spi_read(st->spi, val, 2); + *readval = be16_to_cpu(st->buffer[0]); + return ret; + } + + *val = (u8)writeval; + return spi_write(st->spi, val, 1); +} + +static int max1027_set_cnvst_trigger_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + int ret; + + /* + * In order to disable the convst trigger, start acquisition on + * conversion register write, which basically disables triggering + * conversions upon cnvst changes and thus has the effect of disabling + * the external hardware trigger. + */ + ret = max1027_enable_trigger(indio_dev, state); + if (ret) + return ret; + + if (state) { + ret = max1027_configure_chans_and_start(indio_dev); + if (ret) + return ret; + } + + return 0; +} + +static int max1027_read_scan(struct iio_dev *indio_dev) +{ + struct max1027_state *st = iio_priv(indio_dev); + unsigned int scanned_chans; + int ret; + + scanned_chans = fls(*indio_dev->active_scan_mask) - 1; + if (*indio_dev->active_scan_mask & MAX1X27_SCAN_MASK_TEMP) + scanned_chans++; + + /* fill buffer with all channel */ + ret = spi_read(st->spi, st->buffer, scanned_chans * 2); + if (ret < 0) + return ret; + + iio_push_to_buffers(indio_dev, st->buffer); + + return 0; +} + +static irqreturn_t max1027_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct max1027_state *st = iio_priv(indio_dev); + + /* + * If buffers are disabled (raw read) or when using external triggers, + * we just need to unlock the waiters which will then handle the data. + * + * When using the internal trigger, we must hand-off the choice of the + * handler to the core which will then lookup through the interrupt tree + * for the right handler registered with iio_triggered_buffer_setup() + * to execute, as this trigger might very well be used in conjunction + * with another device. The core will then call the relevant handler to + * perform the data processing step. + */ + if (!iio_buffer_enabled(indio_dev)) + complete(&st->complete); + else + iio_trigger_poll(indio_dev->trig); + + return IRQ_HANDLED; +} + +static irqreturn_t max1027_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + int ret; + + if (!iio_trigger_using_own(indio_dev)) { + ret = max1027_configure_chans_and_start(indio_dev); + if (ret) + goto out; + + /* This is a threaded handler, it is fine to wait for an IRQ */ + ret = max1027_wait_eoc(indio_dev); + if (ret) + goto out; + } + + ret = max1027_read_scan(indio_dev); +out: + if (ret) + dev_err(&indio_dev->dev, + "Cannot read scanned values (%d)\n", ret); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_trigger_ops max1027_trigger_ops = { + .validate_device = &iio_trigger_validate_own_device, + .set_trigger_state = &max1027_set_cnvst_trigger_state, +}; + +static const struct iio_info max1027_info = { + .read_raw = &max1027_read_raw, + .debugfs_reg_access = &max1027_debugfs_reg_access, +}; + +static int max1027_probe(struct spi_device *spi) +{ + int ret; + struct iio_dev *indio_dev; + struct max1027_state *st; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) { + pr_err("Can't allocate iio device\n"); + return -ENOMEM; + } + + st = iio_priv(indio_dev); + st->spi = spi; + st->info = &max1027_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + mutex_init(&st->lock); + init_completion(&st->complete); + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &max1027_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->info->channels; + indio_dev->num_channels = st->info->num_channels; + indio_dev->available_scan_masks = st->info->available_scan_masks; + + st->buffer = devm_kmalloc_array(&indio_dev->dev, + indio_dev->num_channels, 2, + GFP_KERNEL); + if (!st->buffer) + return -ENOMEM; + + /* Enable triggered buffers */ + ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, + &iio_pollfunc_store_time, + &max1027_trigger_handler, + NULL); + if (ret < 0) { + dev_err(&indio_dev->dev, "Failed to setup buffer\n"); + return ret; + } + + /* If there is an EOC interrupt, register the cnvst hardware trigger */ + if (spi->irq) { + st->trig = devm_iio_trigger_alloc(&spi->dev, "%s-trigger", + indio_dev->name); + if (!st->trig) { + ret = -ENOMEM; + dev_err(&indio_dev->dev, + "Failed to allocate iio trigger\n"); + return ret; + } + + st->trig->ops = &max1027_trigger_ops; + iio_trigger_set_drvdata(st->trig, indio_dev); + ret = devm_iio_trigger_register(&indio_dev->dev, + st->trig); + if (ret < 0) { + dev_err(&indio_dev->dev, + "Failed to register iio trigger\n"); + return ret; + } + + ret = devm_request_irq(&spi->dev, spi->irq, max1027_handler, + IRQF_TRIGGER_FALLING, + spi->dev.driver->name, indio_dev); + if (ret < 0) { + dev_err(&indio_dev->dev, "Failed to allocate IRQ.\n"); + return ret; + } + } + + /* Internal reset */ + st->reg = MAX1027_RST_REG; + ret = spi_write(st->spi, &st->reg, 1); + if (ret < 0) { + dev_err(&indio_dev->dev, "Failed to reset the ADC\n"); + return ret; + } + + /* Disable averaging */ + st->reg = MAX1027_AVG_REG; + ret = spi_write(st->spi, &st->reg, 1); + if (ret < 0) { + dev_err(&indio_dev->dev, "Failed to configure averaging register\n"); + return ret; + } + + /* Assume conversion on register write for now */ + ret = max1027_enable_trigger(indio_dev, false); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static struct spi_driver max1027_driver = { + .driver = { + .name = "max1027", + .of_match_table = max1027_adc_dt_ids, + }, + .probe = max1027_probe, + .id_table = max1027_id, +}; +module_spi_driver(max1027_driver); + +MODULE_AUTHOR("Philippe Reynes "); +MODULE_DESCRIPTION("MAX1X27/MAX1X29/MAX1X31 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/max11100.c b/drivers/iio/adc/max11100.c new file mode 100644 index 000000000..549dea6bf --- /dev/null +++ b/drivers/iio/adc/max11100.c @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * iio/adc/max11100.c + * Maxim max11100 ADC Driver with IIO interface + * + * Copyright (C) 2016-17 Renesas Electronics Corporation + * Copyright (C) 2016-17 Jacopo Mondi + */ +#include +#include +#include +#include +#include +#include + +#include +#include + +/* + * LSB is the ADC single digital step + * 1 LSB = (vref_mv / 2 ^ 16) + * + * LSB is used to calculate analog voltage value + * from the number of ADC steps count + * + * Ain = (count * LSB) + */ +#define MAX11100_LSB_DIV (1 << 16) + +struct max11100_state { + struct regulator *vref_reg; + struct spi_device *spi; + + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + */ + u8 buffer[3] __aligned(IIO_DMA_MINALIGN); +}; + +static const struct iio_chan_spec max11100_channels[] = { + { /* [0] */ + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static int max11100_read_single(struct iio_dev *indio_dev, int *val) +{ + int ret; + struct max11100_state *state = iio_priv(indio_dev); + + ret = spi_read(state->spi, state->buffer, sizeof(state->buffer)); + if (ret) { + dev_err(&indio_dev->dev, "SPI transfer failed\n"); + return ret; + } + + /* the first 8 bits sent out from ADC must be 0s */ + if (state->buffer[0]) { + dev_err(&indio_dev->dev, "Invalid value: buffer[0] != 0\n"); + return -EINVAL; + } + + *val = get_unaligned_be16(&state->buffer[1]); + + return 0; +} + +static int max11100_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + int ret, vref_uv; + struct max11100_state *state = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_RAW: + ret = max11100_read_single(indio_dev, val); + if (ret) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + vref_uv = regulator_get_voltage(state->vref_reg); + if (vref_uv < 0) + /* dummy regulator "get_voltage" returns -EINVAL */ + return -EINVAL; + + *val = vref_uv / 1000; + *val2 = MAX11100_LSB_DIV; + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; +} + +static const struct iio_info max11100_info = { + .read_raw = max11100_read_raw, +}; + +static void max11100_regulator_disable(void *reg) +{ + regulator_disable(reg); +} + +static int max11100_probe(struct spi_device *spi) +{ + int ret; + struct iio_dev *indio_dev; + struct max11100_state *state; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*state)); + if (!indio_dev) + return -ENOMEM; + + state = iio_priv(indio_dev); + state->spi = spi; + + indio_dev->name = "max11100"; + indio_dev->info = &max11100_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = max11100_channels; + indio_dev->num_channels = ARRAY_SIZE(max11100_channels); + + state->vref_reg = devm_regulator_get(&spi->dev, "vref"); + if (IS_ERR(state->vref_reg)) + return PTR_ERR(state->vref_reg); + + ret = regulator_enable(state->vref_reg); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&spi->dev, max11100_regulator_disable, + state->vref_reg); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct of_device_id max11100_ids[] = { + { .compatible = "maxim,max11100" }, + { } +}; +MODULE_DEVICE_TABLE(of, max11100_ids); + +static struct spi_driver max11100_driver = { + .driver = { + .name = "max11100", + .of_match_table = max11100_ids, + }, + .probe = max11100_probe, +}; + +module_spi_driver(max11100_driver); + +MODULE_AUTHOR("Jacopo Mondi "); +MODULE_DESCRIPTION("Maxim max11100 ADC Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/max1118.c b/drivers/iio/adc/max1118.c new file mode 100644 index 000000000..75e51ce30 --- /dev/null +++ b/drivers/iio/adc/max1118.c @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MAX1117/MAX1118/MAX1119 8-bit, dual-channel ADCs driver + * + * Copyright (c) 2017 Akinobu Mita + * + * Datasheet: https://datasheets.maximintegrated.com/en/ds/MAX1117-MAX1119.pdf + * + * SPI interface connections + * + * SPI MAXIM + * Master Direction MAX1117/8/9 + * ------ --------- ----------- + * nCS --> CNVST + * SCK --> SCLK + * MISO <-- DOUT + * ------ --------- ----------- + */ + +#include +#include +#include +#include +#include +#include +#include + +enum max1118_id { + max1117, + max1118, + max1119, +}; + +struct max1118 { + struct spi_device *spi; + struct mutex lock; + struct regulator *reg; + /* Ensure natural alignment of buffer elements */ + struct { + u8 channels[2]; + aligned_s64 ts; + } scan; + + u8 data __aligned(IIO_DMA_MINALIGN); +}; + +#define MAX1118_CHANNEL(ch) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (ch), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = ch, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 8, \ + .storagebits = 8, \ + }, \ + } + +static const struct iio_chan_spec max1118_channels[] = { + MAX1118_CHANNEL(0), + MAX1118_CHANNEL(1), + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static int max1118_read(struct iio_dev *indio_dev, int channel) +{ + struct max1118 *adc = iio_priv(indio_dev); + struct spi_transfer xfers[] = { + /* + * To select CH1 for conversion, CNVST pin must be brought high + * and low for a second time. + */ + { + .len = 0, + .delay = { /* > CNVST Low Time 100 ns */ + .value = 1, + .unit = SPI_DELAY_UNIT_USECS + }, + .cs_change = 1, + }, + /* + * The acquisition interval begins with the falling edge of + * CNVST. The total acquisition and conversion process takes + * <7.5us. + */ + { + .len = 0, + .delay = { + .value = 8, + .unit = SPI_DELAY_UNIT_USECS + }, + }, + { + .rx_buf = &adc->data, + .len = 1, + }, + }; + int ret; + + if (channel == 0) + ret = spi_sync_transfer(adc->spi, xfers + 1, 2); + else + ret = spi_sync_transfer(adc->spi, xfers, 3); + + if (ret) + return ret; + + return adc->data; +} + +static int max1118_get_vref_mV(struct iio_dev *indio_dev) +{ + struct max1118 *adc = iio_priv(indio_dev); + const struct spi_device_id *id = spi_get_device_id(adc->spi); + int vref_uV; + + switch (id->driver_data) { + case max1117: + return 2048; + case max1119: + return 4096; + case max1118: + vref_uV = regulator_get_voltage(adc->reg); + if (vref_uV < 0) + return vref_uV; + return vref_uV / 1000; + } + + return -ENODEV; +} + +static int max1118_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max1118 *adc = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&adc->lock); + *val = max1118_read(indio_dev, chan->channel); + mutex_unlock(&adc->lock); + if (*val < 0) + return *val; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = max1118_get_vref_mV(indio_dev); + if (*val < 0) + return *val; + *val2 = 8; + + return IIO_VAL_FRACTIONAL_LOG2; + } + + return -EINVAL; +} + +static const struct iio_info max1118_info = { + .read_raw = max1118_read_raw, +}; + +static irqreturn_t max1118_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct max1118 *adc = iio_priv(indio_dev); + int scan_index; + int i = 0; + + mutex_lock(&adc->lock); + + iio_for_each_active_channel(indio_dev, scan_index) { + const struct iio_chan_spec *scan_chan = + &indio_dev->channels[scan_index]; + int ret = max1118_read(indio_dev, scan_chan->channel); + + if (ret < 0) { + dev_warn(&adc->spi->dev, + "failed to get conversion data\n"); + goto out; + } + + adc->scan.channels[i] = ret; + i++; + } + iio_push_to_buffers_with_ts(indio_dev, &adc->scan, sizeof(adc->scan), + iio_get_time_ns(indio_dev)); +out: + mutex_unlock(&adc->lock); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static void max1118_reg_disable(void *reg) +{ + regulator_disable(reg); +} + +static int max1118_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct max1118 *adc; + const struct spi_device_id *id = spi_get_device_id(spi); + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->spi = spi; + mutex_init(&adc->lock); + + if (id->driver_data == max1118) { + adc->reg = devm_regulator_get(&spi->dev, "vref"); + if (IS_ERR(adc->reg)) + return dev_err_probe(&spi->dev, PTR_ERR(adc->reg), + "failed to get vref regulator\n"); + ret = regulator_enable(adc->reg); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&spi->dev, max1118_reg_disable, + adc->reg); + if (ret) + return ret; + + } + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &max1118_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = max1118_channels; + indio_dev->num_channels = ARRAY_SIZE(max1118_channels); + + /* + * To reinitiate a conversion on CH0, it is necessary to allow for a + * conversion to be complete and all of the data to be read out. Once + * a conversion has been completed, the MAX1117/MAX1118/MAX1119 will go + * into AutoShutdown mode until the next conversion is initiated. + */ + max1118_read(indio_dev, 0); + + ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, NULL, + max1118_trigger_handler, NULL); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id max1118_id[] = { + { .name = "max1117", .driver_data = max1117 }, + { .name = "max1118", .driver_data = max1118 }, + { .name = "max1119", .driver_data = max1119 }, + { } +}; +MODULE_DEVICE_TABLE(spi, max1118_id); + +static const struct of_device_id max1118_dt_ids[] = { + { .compatible = "maxim,max1117" }, + { .compatible = "maxim,max1118" }, + { .compatible = "maxim,max1119" }, + { } +}; +MODULE_DEVICE_TABLE(of, max1118_dt_ids); + +static struct spi_driver max1118_spi_driver = { + .driver = { + .name = "max1118", + .of_match_table = max1118_dt_ids, + }, + .probe = max1118_probe, + .id_table = max1118_id, +}; +module_spi_driver(max1118_spi_driver); + +MODULE_AUTHOR("Akinobu Mita "); +MODULE_DESCRIPTION("MAXIM MAX1117/MAX1118/MAX1119 ADCs driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/max11205.c b/drivers/iio/adc/max11205.c new file mode 100644 index 000000000..63b6561dc --- /dev/null +++ b/drivers/iio/adc/max11205.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Maxim MAX11205 16-Bit Delta-Sigma ADC + * + * Datasheet: https://datasheets.maximintegrated.com/en/ds/MAX1240-max11205.pdf + * Copyright (C) 2022 Analog Devices, Inc. + * Author: Ramona Bolboaca + */ + +#include +#include +#include +#include + +#include +#include + +#define MAX11205_BIT_SCALE 15 +#define MAX11205A_OUT_DATA_RATE 116 +#define MAX11205B_OUT_DATA_RATE 13 + +enum max11205_chip_type { + TYPE_MAX11205A, + TYPE_MAX11205B, +}; + +struct max11205_chip_info { + unsigned int out_data_rate; + const char *name; +}; + +struct max11205_state { + const struct max11205_chip_info *chip_info; + struct regulator *vref; + struct ad_sigma_delta sd; +}; + +static const struct ad_sigma_delta_info max11205_sigma_delta_info = { + .has_registers = false, +}; + +static int max11205_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max11205_state *st = iio_priv(indio_dev); + int reg_mv; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return ad_sigma_delta_single_conversion(indio_dev, chan, val); + case IIO_CHAN_INFO_SCALE: + reg_mv = regulator_get_voltage(st->vref); + if (reg_mv < 0) + return reg_mv; + reg_mv /= 1000; + *val = reg_mv; + *val2 = MAX11205_BIT_SCALE; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = st->chip_info->out_data_rate; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static const struct iio_info max11205_iio_info = { + .read_raw = max11205_read_raw, + .validate_trigger = ad_sd_validate_trigger, +}; + +static const struct iio_chan_spec max11205_channels[] = { + { + .type = IIO_VOLTAGE, + .indexed = 1, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, + }, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static const struct max11205_chip_info max11205_chip_info[] = { + [TYPE_MAX11205A] = { + .out_data_rate = MAX11205A_OUT_DATA_RATE, + .name = "max11205a", + }, + [TYPE_MAX11205B] = { + .out_data_rate = MAX11205B_OUT_DATA_RATE, + .name = "max11205b", + }, +}; + +static void max11205_reg_disable(void *reg) +{ + regulator_disable(reg); +} + +static int max11205_probe(struct spi_device *spi) +{ + struct max11205_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + ad_sd_init(&st->sd, indio_dev, spi, &max11205_sigma_delta_info); + + st->chip_info = spi_get_device_match_data(spi); + + indio_dev->name = st->chip_info->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = max11205_channels; + indio_dev->num_channels = 1; + indio_dev->info = &max11205_iio_info; + + st->vref = devm_regulator_get(&spi->dev, "vref"); + if (IS_ERR(st->vref)) + return dev_err_probe(&spi->dev, PTR_ERR(st->vref), + "Failed to get vref regulator\n"); + + ret = regulator_enable(st->vref); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&spi->dev, max11205_reg_disable, st->vref); + if (ret) + return ret; + + ret = devm_ad_sd_setup_buffer_and_trigger(&spi->dev, indio_dev); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id max11205_spi_ids[] = { + { .name = "max11205a", .driver_data = (kernel_ulong_t)&max11205_chip_info[TYPE_MAX11205A] }, + { .name = "max11205b", .driver_data = (kernel_ulong_t)&max11205_chip_info[TYPE_MAX11205B] }, + { } +}; +MODULE_DEVICE_TABLE(spi, max11205_spi_ids); + +static const struct of_device_id max11205_dt_ids[] = { + { + .compatible = "maxim,max11205a", + .data = &max11205_chip_info[TYPE_MAX11205A], + }, + { + .compatible = "maxim,max11205b", + .data = &max11205_chip_info[TYPE_MAX11205B], + }, + { } +}; +MODULE_DEVICE_TABLE(of, max11205_dt_ids); + +static struct spi_driver max11205_spi_driver = { + .driver = { + .name = "max11205", + .of_match_table = max11205_dt_ids, + }, + .probe = max11205_probe, + .id_table = max11205_spi_ids, +}; +module_spi_driver(max11205_spi_driver); + +MODULE_AUTHOR("Ramona Bolboaca "); +MODULE_DESCRIPTION("MAX11205 ADC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_AD_SIGMA_DELTA"); diff --git a/drivers/iio/adc/max11410.c b/drivers/iio/adc/max11410.c new file mode 100644 index 000000000..23ae684cf --- /dev/null +++ b/drivers/iio/adc/max11410.c @@ -0,0 +1,1046 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MAX11410 SPI ADC driver + * + * Copyright 2022 Analog Devices Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#define MAX11410_REG_CONV_START 0x01 +#define MAX11410_CONV_TYPE_SINGLE 0x00 +#define MAX11410_CONV_TYPE_CONTINUOUS 0x01 +#define MAX11410_REG_CAL_START 0x03 +#define MAX11410_CAL_START_SELF 0x00 +#define MAX11410_CAL_START_PGA 0x01 +#define MAX11410_REG_GPIO_CTRL(ch) ((ch) ? 0x05 : 0x04) +#define MAX11410_GPIO_INTRB 0xC1 +#define MAX11410_REG_FILTER 0x08 +#define MAX11410_FILTER_RATE_MASK GENMASK(3, 0) +#define MAX11410_FILTER_RATE_MAX 0x0F +#define MAX11410_FILTER_LINEF_MASK GENMASK(5, 4) +#define MAX11410_FILTER_50HZ BIT(5) +#define MAX11410_FILTER_60HZ BIT(4) +#define MAX11410_REG_CTRL 0x09 +#define MAX11410_CTRL_REFSEL_MASK GENMASK(2, 0) +#define MAX11410_CTRL_VREFN_BUF_BIT BIT(3) +#define MAX11410_CTRL_VREFP_BUF_BIT BIT(4) +#define MAX11410_CTRL_FORMAT_BIT BIT(5) +#define MAX11410_CTRL_UNIPOLAR_BIT BIT(6) +#define MAX11410_REG_MUX_CTRL0 0x0B +#define MAX11410_REG_PGA 0x0E +#define MAX11410_PGA_GAIN_MASK GENMASK(2, 0) +#define MAX11410_PGA_SIG_PATH_MASK GENMASK(5, 4) +#define MAX11410_PGA_SIG_PATH_BUFFERED 0x00 +#define MAX11410_PGA_SIG_PATH_BYPASS 0x01 +#define MAX11410_PGA_SIG_PATH_PGA 0x02 +#define MAX11410_REG_DATA0 0x30 +#define MAX11410_REG_STATUS 0x38 +#define MAX11410_STATUS_CONV_READY_BIT BIT(0) +#define MAX11410_STATUS_CAL_READY_BIT BIT(2) + +#define MAX11410_REFSEL_AVDD_AGND 0x03 +#define MAX11410_REFSEL_MAX 0x06 +#define MAX11410_SIG_PATH_MAX 0x02 +#define MAX11410_CHANNEL_INDEX_MAX 0x0A +#define MAX11410_AINP_AVDD 0x0A +#define MAX11410_AINN_GND 0x0A + +#define MAX11410_CONVERSION_TIMEOUT_MS 2000 +#define MAX11410_CALIB_TIMEOUT_MS 2000 + +#define MAX11410_SCALE_AVAIL_SIZE 8 + +enum max11410_filter { + MAX11410_FILTER_FIR5060, + MAX11410_FILTER_FIR50, + MAX11410_FILTER_FIR60, + MAX11410_FILTER_SINC4, +}; + +static const u8 max11410_sampling_len[] = { + [MAX11410_FILTER_FIR5060] = 5, + [MAX11410_FILTER_FIR50] = 6, + [MAX11410_FILTER_FIR60] = 6, + [MAX11410_FILTER_SINC4] = 10, +}; + +static const int max11410_sampling_rates[4][10][2] = { + [MAX11410_FILTER_FIR5060] = { + { 1, 100000 }, + { 2, 100000 }, + { 4, 200000 }, + { 8, 400000 }, + { 16, 800000 } + }, + [MAX11410_FILTER_FIR50] = { + { 1, 300000 }, + { 2, 700000 }, + { 5, 300000 }, + { 10, 700000 }, + { 21, 300000 }, + { 40 } + }, + [MAX11410_FILTER_FIR60] = { + { 1, 300000 }, + { 2, 700000 }, + { 5, 300000 }, + { 10, 700000 }, + { 21, 300000 }, + { 40 } + }, + [MAX11410_FILTER_SINC4] = { + { 4 }, + { 10 }, + { 20 }, + { 40 }, + { 60 }, + { 120 }, + { 240 }, + { 480 }, + { 960 }, + { 1920 } + } +}; + +struct max11410_channel_config { + u32 settling_time_us; + u32 *scale_avail; + u8 refsel; + u8 sig_path; + u8 gain; + bool bipolar; + bool buffered_vrefp; + bool buffered_vrefn; +}; + +struct max11410_state { + struct spi_device *spi_dev; + struct iio_trigger *trig; + struct completion completion; + struct mutex lock; /* Prevent changing channel config during sampling */ + struct regmap *regmap; + struct regulator *avdd; + struct regulator *vrefp[3]; + struct regulator *vrefn[3]; + struct max11410_channel_config *channels; + int irq; + struct { + u32 data __aligned(IIO_DMA_MINALIGN); + aligned_s64 ts; + } scan; +}; + +static const struct iio_chan_spec chanspec_template = { + .type = IIO_VOLTAGE, + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + 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_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_LE, + }, +}; + +static unsigned int max11410_reg_size(unsigned int reg) +{ + /* Registers from 0x00 to 0x10 are 1 byte, the rest are 3 bytes long. */ + return reg <= 0x10 ? 1 : 3; +} + +static int max11410_write_reg(struct max11410_state *st, unsigned int reg, + unsigned int val) +{ + /* This driver only needs to write 8-bit registers */ + if (max11410_reg_size(reg) != 1) + return -EINVAL; + + return regmap_write(st->regmap, reg, val); +} + +static int max11410_read_reg(struct max11410_state *st, unsigned int reg, + int *val) +{ + int ret; + + if (max11410_reg_size(reg) == 3) { + ret = regmap_bulk_read(st->regmap, reg, &st->scan.data, 3); + if (ret) + return ret; + + *val = get_unaligned_be24(&st->scan.data); + return 0; + } + + return regmap_read(st->regmap, reg, val); +} + +static struct regulator *max11410_get_vrefp(struct max11410_state *st, + u8 refsel) +{ + refsel = refsel % 4; + if (refsel == 3) + return st->avdd; + + return st->vrefp[refsel]; +} + +static struct regulator *max11410_get_vrefn(struct max11410_state *st, + u8 refsel) +{ + if (refsel > 2) + return NULL; + + return st->vrefn[refsel]; +} + +static const struct regmap_config regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x39, +}; + +static ssize_t max11410_notch_en_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct max11410_state *state = iio_priv(indio_dev); + struct iio_dev_attr *iio_attr = to_iio_dev_attr(devattr); + unsigned int val; + int ret; + + ret = max11410_read_reg(state, MAX11410_REG_FILTER, &val); + if (ret) + return ret; + + switch (iio_attr->address) { + case 0: + val = !FIELD_GET(MAX11410_FILTER_50HZ, val); + break; + case 1: + val = !FIELD_GET(MAX11410_FILTER_60HZ, val); + break; + case 2: + val = FIELD_GET(MAX11410_FILTER_LINEF_MASK, val) == 3; + break; + default: + return -EINVAL; + } + + return sysfs_emit(buf, "%d\n", val); +} + +static ssize_t max11410_notch_en_store(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct iio_dev_attr *iio_attr = to_iio_dev_attr(devattr); + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct max11410_state *state = iio_priv(indio_dev); + unsigned int filter_bits; + bool enable; + int ret; + + ret = kstrtobool(buf, &enable); + if (ret) + return ret; + + switch (iio_attr->address) { + case 0: + filter_bits = MAX11410_FILTER_50HZ; + break; + case 1: + filter_bits = MAX11410_FILTER_60HZ; + break; + case 2: + default: + filter_bits = MAX11410_FILTER_50HZ | MAX11410_FILTER_60HZ; + enable = !enable; + break; + } + + if (enable) + ret = regmap_clear_bits(state->regmap, MAX11410_REG_FILTER, + filter_bits); + else + ret = regmap_set_bits(state->regmap, MAX11410_REG_FILTER, + filter_bits); + + if (ret) + return ret; + + return count; +} + +static ssize_t in_voltage_filter2_notch_center_show(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct max11410_state *state = iio_priv(indio_dev); + int ret, reg, rate, filter; + + ret = regmap_read(state->regmap, MAX11410_REG_FILTER, ®); + if (ret) + return ret; + + rate = FIELD_GET(MAX11410_FILTER_RATE_MASK, reg); + rate = clamp_val(rate, 0, + max11410_sampling_len[MAX11410_FILTER_SINC4] - 1); + filter = max11410_sampling_rates[MAX11410_FILTER_SINC4][rate][0]; + + return sysfs_emit(buf, "%d\n", filter); +} + +static IIO_CONST_ATTR(in_voltage_filter0_notch_center, "50"); +static IIO_CONST_ATTR(in_voltage_filter1_notch_center, "60"); +static IIO_DEVICE_ATTR_RO(in_voltage_filter2_notch_center, 2); + +static IIO_DEVICE_ATTR(in_voltage_filter0_notch_en, 0644, + max11410_notch_en_show, max11410_notch_en_store, 0); +static IIO_DEVICE_ATTR(in_voltage_filter1_notch_en, 0644, + max11410_notch_en_show, max11410_notch_en_store, 1); +static IIO_DEVICE_ATTR(in_voltage_filter2_notch_en, 0644, + max11410_notch_en_show, max11410_notch_en_store, 2); + +static struct attribute *max11410_attributes[] = { + &iio_const_attr_in_voltage_filter0_notch_center.dev_attr.attr, + &iio_const_attr_in_voltage_filter1_notch_center.dev_attr.attr, + &iio_dev_attr_in_voltage_filter2_notch_center.dev_attr.attr, + &iio_dev_attr_in_voltage_filter0_notch_en.dev_attr.attr, + &iio_dev_attr_in_voltage_filter1_notch_en.dev_attr.attr, + &iio_dev_attr_in_voltage_filter2_notch_en.dev_attr.attr, + NULL +}; + +static const struct attribute_group max11410_attribute_group = { + .attrs = max11410_attributes, +}; + +static int max11410_set_input_mux(struct max11410_state *st, u8 ainp, u8 ainn) +{ + if (ainp > MAX11410_CHANNEL_INDEX_MAX || + ainn > MAX11410_CHANNEL_INDEX_MAX) + return -EINVAL; + + return max11410_write_reg(st, MAX11410_REG_MUX_CTRL0, + (ainp << 4) | ainn); +} + +static int max11410_configure_channel(struct max11410_state *st, + struct iio_chan_spec const *chan) +{ + struct max11410_channel_config cfg = st->channels[chan->address]; + unsigned int regval; + int ret; + + if (chan->differential) + ret = max11410_set_input_mux(st, chan->channel, chan->channel2); + else + ret = max11410_set_input_mux(st, chan->channel, + MAX11410_AINN_GND); + + if (ret) + return ret; + + regval = FIELD_PREP(MAX11410_CTRL_VREFP_BUF_BIT, cfg.buffered_vrefp) | + FIELD_PREP(MAX11410_CTRL_VREFN_BUF_BIT, cfg.buffered_vrefn) | + FIELD_PREP(MAX11410_CTRL_REFSEL_MASK, cfg.refsel) | + FIELD_PREP(MAX11410_CTRL_UNIPOLAR_BIT, cfg.bipolar ? 0 : 1); + ret = regmap_update_bits(st->regmap, MAX11410_REG_CTRL, + MAX11410_CTRL_REFSEL_MASK | + MAX11410_CTRL_VREFP_BUF_BIT | + MAX11410_CTRL_VREFN_BUF_BIT | + MAX11410_CTRL_UNIPOLAR_BIT, regval); + if (ret) + return ret; + + regval = FIELD_PREP(MAX11410_PGA_SIG_PATH_MASK, cfg.sig_path) | + FIELD_PREP(MAX11410_PGA_GAIN_MASK, cfg.gain); + ret = regmap_write(st->regmap, MAX11410_REG_PGA, regval); + if (ret) + return ret; + + if (cfg.settling_time_us) + fsleep(cfg.settling_time_us); + + return 0; +} + +static int max11410_sample(struct max11410_state *st, int *sample_raw, + struct iio_chan_spec const *chan) +{ + int val, ret; + + ret = max11410_configure_channel(st, chan); + if (ret) + return ret; + + if (st->irq > 0) + reinit_completion(&st->completion); + + /* Start Conversion */ + ret = max11410_write_reg(st, MAX11410_REG_CONV_START, + MAX11410_CONV_TYPE_SINGLE); + if (ret) + return ret; + + if (st->irq > 0) { + /* Wait for an interrupt. */ + ret = wait_for_completion_timeout(&st->completion, + msecs_to_jiffies(MAX11410_CONVERSION_TIMEOUT_MS)); + if (!ret) + return -ETIMEDOUT; + } else { + int ret2; + + /* Wait for status register Conversion Ready flag */ + ret = read_poll_timeout(max11410_read_reg, ret2, + ret2 || (val & MAX11410_STATUS_CONV_READY_BIT), + 5000, MAX11410_CONVERSION_TIMEOUT_MS * 1000, + true, st, MAX11410_REG_STATUS, &val); + if (ret) + return ret; + if (ret2) + return ret2; + } + + /* Read ADC Data */ + return max11410_read_reg(st, MAX11410_REG_DATA0, sample_raw); +} + +static int max11410_get_scale(struct max11410_state *state, + struct max11410_channel_config cfg) +{ + struct regulator *vrefp, *vrefn; + int scale; + + vrefp = max11410_get_vrefp(state, cfg.refsel); + + scale = regulator_get_voltage(vrefp) / 1000; + vrefn = max11410_get_vrefn(state, cfg.refsel); + if (vrefn) + scale -= regulator_get_voltage(vrefn) / 1000; + + if (cfg.bipolar) + scale *= 2; + + return scale >> cfg.gain; +} + +static int max11410_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct max11410_state *state = iio_priv(indio_dev); + struct max11410_channel_config cfg = state->channels[chan->address]; + int ret, reg_val, filter, rate; + + switch (info) { + case IIO_CHAN_INFO_SCALE: + *val = max11410_get_scale(state, cfg); + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: + if (cfg.bipolar) + *val = -BIT(chan->scan_type.realbits - 1); + else + *val = 0; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + mutex_lock(&state->lock); + + ret = max11410_sample(state, ®_val, chan); + + mutex_unlock(&state->lock); + + iio_device_release_direct(indio_dev); + + if (ret) + return ret; + + *val = reg_val; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = regmap_read(state->regmap, MAX11410_REG_FILTER, ®_val); + if (ret) + return ret; + + filter = FIELD_GET(MAX11410_FILTER_LINEF_MASK, reg_val); + rate = reg_val & MAX11410_FILTER_RATE_MASK; + if (rate >= max11410_sampling_len[filter]) + rate = max11410_sampling_len[filter] - 1; + + *val = max11410_sampling_rates[filter][rate][0]; + *val2 = max11410_sampling_rates[filter][rate][1]; + + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static int __max11410_write_samp_freq(struct max11410_state *st, + int val, int val2) +{ + int ret, i, reg_val, filter; + + guard(mutex)(&st->lock); + + ret = regmap_read(st->regmap, MAX11410_REG_FILTER, ®_val); + if (ret) + return ret; + + filter = FIELD_GET(MAX11410_FILTER_LINEF_MASK, reg_val); + + for (i = 0; i < max11410_sampling_len[filter]; ++i) { + if (val == max11410_sampling_rates[filter][i][0] && + val2 == max11410_sampling_rates[filter][i][1]) + break; + } + if (i == max11410_sampling_len[filter]) + return -EINVAL; + + return regmap_write_bits(st->regmap, MAX11410_REG_FILTER, + MAX11410_FILTER_RATE_MASK, i); +} + +static int max11410_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct max11410_state *st = iio_priv(indio_dev); + int ret, gain; + u32 *scale_avail; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + scale_avail = st->channels[chan->address].scale_avail; + if (!scale_avail) + return -EOPNOTSUPP; + + /* Accept values in range 0.000001 <= scale < 1.000000 */ + if (val != 0 || val2 == 0) + return -EINVAL; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + /* Convert from INT_PLUS_MICRO to FRACTIONAL_LOG2 */ + val2 = val2 * DIV_ROUND_CLOSEST(BIT(24), 1000000); + val2 = DIV_ROUND_CLOSEST(scale_avail[0], val2); + gain = order_base_2(val2); + + st->channels[chan->address].gain = clamp_val(gain, 0, 7); + + iio_device_release_direct(indio_dev); + + return 0; + case IIO_CHAN_INFO_SAMP_FREQ: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = __max11410_write_samp_freq(st, val, val2); + iio_device_release_direct(indio_dev); + + return ret; + default: + return -EINVAL; + } +} + +static int max11410_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long info) +{ + struct max11410_state *st = iio_priv(indio_dev); + struct max11410_channel_config cfg; + int ret, reg_val, filter; + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = regmap_read(st->regmap, MAX11410_REG_FILTER, ®_val); + if (ret) + return ret; + + filter = FIELD_GET(MAX11410_FILTER_LINEF_MASK, reg_val); + + *vals = (const int *)max11410_sampling_rates[filter]; + *length = max11410_sampling_len[filter] * 2; + *type = IIO_VAL_INT_PLUS_MICRO; + + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SCALE: + cfg = st->channels[chan->address]; + + if (!cfg.scale_avail) + return -EINVAL; + + *vals = cfg.scale_avail; + *length = MAX11410_SCALE_AVAIL_SIZE * 2; + *type = IIO_VAL_FRACTIONAL_LOG2; + + return IIO_AVAIL_LIST; + } + return -EINVAL; +} + +static const struct iio_info max11410_info = { + .read_raw = max11410_read_raw, + .write_raw = max11410_write_raw, + .read_avail = max11410_read_avail, + .attrs = &max11410_attribute_group, +}; + +static irqreturn_t max11410_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct max11410_state *st = iio_priv(indio_dev); + int ret; + + ret = max11410_read_reg(st, MAX11410_REG_DATA0, &st->scan.data); + if (ret) { + dev_err(&indio_dev->dev, "cannot read data\n"); + goto out; + } + + iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan), + iio_get_time_ns(indio_dev)); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int max11410_buffer_postenable(struct iio_dev *indio_dev) +{ + struct max11410_state *st = iio_priv(indio_dev); + int scan_ch, ret; + + scan_ch = ffs(*indio_dev->active_scan_mask) - 1; + + ret = max11410_configure_channel(st, &indio_dev->channels[scan_ch]); + if (ret) + return ret; + + /* Start continuous conversion. */ + return max11410_write_reg(st, MAX11410_REG_CONV_START, + MAX11410_CONV_TYPE_CONTINUOUS); +} + +static int max11410_buffer_predisable(struct iio_dev *indio_dev) +{ + struct max11410_state *st = iio_priv(indio_dev); + + /* Stop continuous conversion. */ + return max11410_write_reg(st, MAX11410_REG_CONV_START, + MAX11410_CONV_TYPE_SINGLE); +} + +static const struct iio_buffer_setup_ops max11410_buffer_ops = { + .postenable = &max11410_buffer_postenable, + .predisable = &max11410_buffer_predisable, + .validate_scan_mask = &iio_validate_scan_mask_onehot, +}; + +static const struct iio_trigger_ops max11410_trigger_ops = { + .validate_device = iio_trigger_validate_own_device, +}; + +static irqreturn_t max11410_interrupt(int irq, void *dev_id) +{ + struct iio_dev *indio_dev = dev_id; + struct max11410_state *st = iio_priv(indio_dev); + + if (iio_buffer_enabled(indio_dev)) + iio_trigger_poll_nested(st->trig); + else + complete(&st->completion); + + return IRQ_HANDLED; +}; + +static int max11410_parse_channels(struct max11410_state *st, + struct iio_dev *indio_dev) +{ + struct iio_chan_spec chanspec = chanspec_template; + struct device *dev = &st->spi_dev->dev; + struct max11410_channel_config *cfg; + struct iio_chan_spec *channels; + u32 reference, sig_path; + const char *node_name; + u32 inputs[2], scale; + unsigned int num_ch; + int chan_idx = 0; + int ret, i; + + num_ch = device_get_child_node_count(dev); + if (num_ch == 0) + return dev_err_probe(&indio_dev->dev, -ENODEV, + "FW has no channels defined\n"); + + /* Reserve space for soft timestamp channel */ + num_ch++; + channels = devm_kcalloc(dev, num_ch, sizeof(*channels), GFP_KERNEL); + if (!channels) + return -ENOMEM; + + st->channels = devm_kcalloc(dev, num_ch, sizeof(*st->channels), + GFP_KERNEL); + if (!st->channels) + return -ENOMEM; + + device_for_each_child_node_scoped(dev, child) { + node_name = fwnode_get_name(child); + if (fwnode_property_present(child, "diff-channels")) { + ret = fwnode_property_read_u32_array(child, + "diff-channels", + inputs, + ARRAY_SIZE(inputs)); + + chanspec.differential = 1; + } else { + ret = fwnode_property_read_u32(child, "reg", &inputs[0]); + + inputs[1] = 0; + chanspec.differential = 0; + } + if (ret) + return ret; + + if (inputs[0] > MAX11410_CHANNEL_INDEX_MAX || + inputs[1] > MAX11410_CHANNEL_INDEX_MAX) + return dev_err_probe(&indio_dev->dev, -EINVAL, + "Invalid channel index for %s, should be less than %d\n", + node_name, + MAX11410_CHANNEL_INDEX_MAX + 1); + + cfg = &st->channels[chan_idx]; + + reference = MAX11410_REFSEL_AVDD_AGND; + fwnode_property_read_u32(child, "adi,reference", &reference); + if (reference > MAX11410_REFSEL_MAX) + return dev_err_probe(&indio_dev->dev, -EINVAL, + "Invalid adi,reference value for %s, should be less than %d.\n", + node_name, MAX11410_REFSEL_MAX + 1); + + if (!max11410_get_vrefp(st, reference) || + (!max11410_get_vrefn(st, reference) && reference <= 2)) + return dev_err_probe(&indio_dev->dev, -EINVAL, + "Invalid VREF configuration for %s, either specify corresponding VREF regulators or change adi,reference property.\n", + node_name); + + sig_path = MAX11410_PGA_SIG_PATH_BUFFERED; + fwnode_property_read_u32(child, "adi,input-mode", &sig_path); + if (sig_path > MAX11410_SIG_PATH_MAX) + return dev_err_probe(&indio_dev->dev, -EINVAL, + "Invalid adi,input-mode value for %s, should be less than %d.\n", + node_name, MAX11410_SIG_PATH_MAX + 1); + + fwnode_property_read_u32(child, "settling-time-us", + &cfg->settling_time_us); + cfg->bipolar = fwnode_property_read_bool(child, "bipolar"); + cfg->buffered_vrefp = fwnode_property_read_bool(child, "adi,buffered-vrefp"); + cfg->buffered_vrefn = fwnode_property_read_bool(child, "adi,buffered-vrefn"); + cfg->refsel = reference; + cfg->sig_path = sig_path; + cfg->gain = 0; + + /* Enable scale_available property if input mode is PGA */ + if (sig_path == MAX11410_PGA_SIG_PATH_PGA) { + __set_bit(IIO_CHAN_INFO_SCALE, + &chanspec.info_mask_separate_available); + cfg->scale_avail = devm_kcalloc(dev, MAX11410_SCALE_AVAIL_SIZE * 2, + sizeof(*cfg->scale_avail), + GFP_KERNEL); + if (!cfg->scale_avail) + return -ENOMEM; + + scale = max11410_get_scale(st, *cfg); + for (i = 0; i < MAX11410_SCALE_AVAIL_SIZE; i++) { + cfg->scale_avail[2 * i] = scale >> i; + cfg->scale_avail[2 * i + 1] = chanspec.scan_type.realbits; + } + } else { + __clear_bit(IIO_CHAN_INFO_SCALE, + &chanspec.info_mask_separate_available); + } + + chanspec.address = chan_idx; + chanspec.scan_index = chan_idx; + chanspec.channel = inputs[0]; + chanspec.channel2 = inputs[1]; + + channels[chan_idx] = chanspec; + chan_idx++; + } + + channels[chan_idx] = IIO_CHAN_SOFT_TIMESTAMP(chan_idx); + + indio_dev->num_channels = chan_idx + 1; + indio_dev->channels = channels; + + return 0; +} + +static void max11410_disable_reg(void *reg) +{ + regulator_disable(reg); +} + +static int max11410_init_vref(struct device *dev, + struct regulator **vref, + const char *id) +{ + struct regulator *reg; + int ret; + + reg = devm_regulator_get_optional(dev, id); + if (PTR_ERR(reg) == -ENODEV) { + *vref = NULL; + return 0; + } else if (IS_ERR(reg)) { + return PTR_ERR(reg); + } + ret = regulator_enable(reg); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable regulator %s\n", id); + + *vref = reg; + return devm_add_action_or_reset(dev, max11410_disable_reg, reg); +} + +static int max11410_calibrate(struct max11410_state *st, u32 cal_type) +{ + int ret, ret2, val; + + ret = max11410_write_reg(st, MAX11410_REG_CAL_START, cal_type); + if (ret) + return ret; + + /* Wait for status register Calibration Ready flag */ + ret = read_poll_timeout(max11410_read_reg, ret2, + ret2 || (val & MAX11410_STATUS_CAL_READY_BIT), + 50000, MAX11410_CALIB_TIMEOUT_MS * 1000, true, + st, MAX11410_REG_STATUS, &val); + if (ret) + return ret; + + return ret2; +} + +static int max11410_self_calibrate(struct max11410_state *st) +{ + int ret, i; + + ret = regmap_write_bits(st->regmap, MAX11410_REG_FILTER, + MAX11410_FILTER_RATE_MASK, + FIELD_PREP(MAX11410_FILTER_RATE_MASK, + MAX11410_FILTER_RATE_MAX)); + if (ret) + return ret; + + ret = max11410_calibrate(st, MAX11410_CAL_START_SELF); + if (ret) + return ret; + + ret = regmap_write_bits(st->regmap, MAX11410_REG_PGA, + MAX11410_PGA_SIG_PATH_MASK, + FIELD_PREP(MAX11410_PGA_SIG_PATH_MASK, + MAX11410_PGA_SIG_PATH_PGA)); + if (ret) + return ret; + + /* PGA calibrations */ + for (i = 1; i < 8; ++i) { + ret = regmap_write_bits(st->regmap, MAX11410_REG_PGA, + MAX11410_PGA_GAIN_MASK, i); + if (ret) + return ret; + + ret = max11410_calibrate(st, MAX11410_CAL_START_PGA); + if (ret) + return ret; + } + + /* Cleanup */ + ret = regmap_write_bits(st->regmap, MAX11410_REG_PGA, + MAX11410_PGA_GAIN_MASK, 0); + if (ret) + return ret; + + ret = regmap_write_bits(st->regmap, MAX11410_REG_FILTER, + MAX11410_FILTER_RATE_MASK, 0); + if (ret) + return ret; + + return regmap_write_bits(st->regmap, MAX11410_REG_PGA, + MAX11410_PGA_SIG_PATH_MASK, + FIELD_PREP(MAX11410_PGA_SIG_PATH_MASK, + MAX11410_PGA_SIG_PATH_BUFFERED)); +} + +static int max11410_probe(struct spi_device *spi) +{ + static const char * const vrefp_regs[] = { "vref0p", "vref1p", "vref2p" }; + static const char * const vrefn_regs[] = { "vref0n", "vref1n", "vref2n" }; + struct device *dev = &spi->dev; + struct max11410_state *st; + struct iio_dev *indio_dev; + int ret, irqs[2]; + int i; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi_dev = spi; + init_completion(&st->completion); + mutex_init(&st->lock); + + indio_dev->name = "max11410"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &max11410_info; + + st->regmap = devm_regmap_init_spi(spi, ®map_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "regmap initialization failed\n"); + + ret = max11410_init_vref(dev, &st->avdd, "avdd"); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(vrefp_regs); i++) { + ret = max11410_init_vref(dev, &st->vrefp[i], vrefp_regs[i]); + if (ret) + return ret; + + ret = max11410_init_vref(dev, &st->vrefn[i], vrefn_regs[i]); + if (ret) + return ret; + } + + /* + * Regulators must be configured before parsing channels for + * validating "adi,reference" property of each channel. + */ + ret = max11410_parse_channels(st, indio_dev); + if (ret) + return ret; + + irqs[0] = fwnode_irq_get_byname(dev_fwnode(dev), "gpio0"); + irqs[1] = fwnode_irq_get_byname(dev_fwnode(dev), "gpio1"); + + if (irqs[0] > 0) { + st->irq = irqs[0]; + ret = regmap_write(st->regmap, MAX11410_REG_GPIO_CTRL(0), + MAX11410_GPIO_INTRB); + } else if (irqs[1] > 0) { + st->irq = irqs[1]; + ret = regmap_write(st->regmap, MAX11410_REG_GPIO_CTRL(1), + MAX11410_GPIO_INTRB); + } else if (spi->irq > 0) { + return dev_err_probe(dev, -ENODEV, + "no interrupt name specified"); + } + + if (ret) + return ret; + + ret = regmap_set_bits(st->regmap, MAX11410_REG_CTRL, + MAX11410_CTRL_FORMAT_BIT); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + &max11410_trigger_handler, + &max11410_buffer_ops); + if (ret) + return ret; + + if (st->irq > 0) { + st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!st->trig) + return -ENOMEM; + + st->trig->ops = &max11410_trigger_ops; + ret = devm_iio_trigger_register(dev, st->trig); + if (ret) + return ret; + + ret = devm_request_threaded_irq(dev, st->irq, NULL, + &max11410_interrupt, + IRQF_ONESHOT, "max11410", + indio_dev); + if (ret) + return ret; + } + + ret = max11410_self_calibrate(st); + if (ret) + return dev_err_probe(dev, ret, + "cannot perform device self calibration\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id max11410_spi_of_id[] = { + { .compatible = "adi,max11410" }, + { } +}; +MODULE_DEVICE_TABLE(of, max11410_spi_of_id); + +static const struct spi_device_id max11410_id[] = { + { .name = "max11410" }, + { } +}; +MODULE_DEVICE_TABLE(spi, max11410_id); + +static struct spi_driver max11410_driver = { + .driver = { + .name = "max11410", + .of_match_table = max11410_spi_of_id, + }, + .probe = max11410_probe, + .id_table = max11410_id, +}; +module_spi_driver(max11410_driver); + +MODULE_AUTHOR("David Jung "); +MODULE_AUTHOR("Ibrahim Tilki "); +MODULE_DESCRIPTION("Analog Devices MAX11410 ADC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/max1241.c b/drivers/iio/adc/max1241.c new file mode 100644 index 000000000..b3cecbf9a --- /dev/null +++ b/drivers/iio/adc/max1241.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MAX1241 low-power, 12-bit serial ADC + * + * Datasheet: https://datasheets.maximintegrated.com/en/ds/MAX1240-MAX1241.pdf + */ + +#include +#include +#include +#include +#include +#include + +#define MAX1241_VAL_MASK GENMASK(11, 0) +#define MAX1241_SHUTDOWN_DELAY_USEC 4 + +struct max1241 { + struct spi_device *spi; + struct mutex lock; + struct regulator *vref; + struct gpio_desc *shutdown; + + __be16 data __aligned(IIO_DMA_MINALIGN); +}; + +static const struct iio_chan_spec max1241_channels[] = { + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static int max1241_read(struct max1241 *adc) +{ + struct spi_transfer xfers[] = { + /* + * Begin conversion by bringing /CS low for at least + * tconv us. + */ + { + .len = 0, + .delay.value = 8, + .delay.unit = SPI_DELAY_UNIT_USECS, + }, + /* + * Then read two bytes of data in our RX buffer. + */ + { + .rx_buf = &adc->data, + .len = 2, + }, + }; + + return spi_sync_transfer(adc->spi, xfers, ARRAY_SIZE(xfers)); +} + +static int max1241_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret, vref_uV; + struct max1241 *adc = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&adc->lock); + + if (adc->shutdown) { + gpiod_set_value(adc->shutdown, 0); + udelay(MAX1241_SHUTDOWN_DELAY_USEC); + ret = max1241_read(adc); + gpiod_set_value(adc->shutdown, 1); + } else + ret = max1241_read(adc); + + if (ret) { + mutex_unlock(&adc->lock); + return ret; + } + + *val = (be16_to_cpu(adc->data) >> 3) & MAX1241_VAL_MASK; + + mutex_unlock(&adc->lock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + vref_uV = regulator_get_voltage(adc->vref); + + if (vref_uV < 0) + return vref_uV; + + *val = vref_uV / 1000; + *val2 = 12; + + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static const struct iio_info max1241_info = { + .read_raw = max1241_read_raw, +}; + +static void max1241_disable_vref_action(void *data) +{ + struct max1241 *adc = data; + struct device *dev = &adc->spi->dev; + int err; + + err = regulator_disable(adc->vref); + if (err) + dev_err(dev, "could not disable vref regulator.\n"); +} + +static int max1241_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct max1241 *adc; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->spi = spi; + mutex_init(&adc->lock); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, + "failed to get/enable vdd regulator\n"); + + adc->vref = devm_regulator_get(dev, "vref"); + if (IS_ERR(adc->vref)) + return dev_err_probe(dev, PTR_ERR(adc->vref), + "failed to get vref regulator\n"); + + ret = regulator_enable(adc->vref); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, max1241_disable_vref_action, adc); + if (ret) { + dev_err(dev, "could not set up vref regulator cleanup action\n"); + return ret; + } + + adc->shutdown = devm_gpiod_get_optional(dev, "shutdown", + GPIOD_OUT_HIGH); + if (IS_ERR(adc->shutdown)) + return dev_err_probe(dev, PTR_ERR(adc->shutdown), + "cannot get shutdown gpio\n"); + + if (adc->shutdown) + dev_dbg(dev, "shutdown pin passed, low-power mode enabled"); + else + dev_dbg(dev, "no shutdown pin passed, low-power mode disabled"); + + indio_dev->name = "max1241"; + indio_dev->info = &max1241_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = max1241_channels; + indio_dev->num_channels = ARRAY_SIZE(max1241_channels); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id max1241_id[] = { + { .name = "max1241" }, + { } +}; +MODULE_DEVICE_TABLE(spi, max1241_id); + +static const struct of_device_id max1241_dt_ids[] = { + { .compatible = "maxim,max1241" }, + { } +}; +MODULE_DEVICE_TABLE(of, max1241_dt_ids); + +static struct spi_driver max1241_spi_driver = { + .driver = { + .name = "max1241", + .of_match_table = max1241_dt_ids, + }, + .probe = max1241_probe, + .id_table = max1241_id, +}; +module_spi_driver(max1241_spi_driver); + +MODULE_AUTHOR("Alexandru Lazar "); +MODULE_DESCRIPTION("MAX1241 ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/max1363.c b/drivers/iio/adc/max1363.c new file mode 100644 index 000000000..65a2d92bb --- /dev/null +++ b/drivers/iio/adc/max1363.c @@ -0,0 +1,1691 @@ +// SPDX-License-Identifier: GPL-2.0-only + /* + * iio/adc/max1363.c + * Copyright (C) 2008-2010 Jonathan Cameron + * + * based on linux/drivers/i2c/chips/max123x + * Copyright (C) 2002-2004 Stefan Eletzhofer + * + * based on linux/drivers/acron/char/pcf8583.c + * Copyright (C) 2000 Russell King + * + * Driver for max1363 and similar chips. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define MAX1363_SETUP_BYTE(a) ((a) | 0x80) + +/* There is a fair bit more defined here than currently + * used, but the intention is to support everything these + * chips do in the long run */ + +/* see data sheets */ +/* max1363 and max1236, max1237, max1238, max1239 */ +#define MAX1363_SETUP_AIN3_IS_AIN3_REF_IS_VDD 0x00 +#define MAX1363_SETUP_AIN3_IS_REF_EXT_TO_REF 0x20 +#define MAX1363_SETUP_AIN3_IS_AIN3_REF_IS_INT 0x40 +#define MAX1363_SETUP_AIN3_IS_REF_REF_IS_INT 0x60 +#define MAX1363_SETUP_POWER_UP_INT_REF 0x10 +#define MAX1363_SETUP_POWER_DOWN_INT_REF 0x00 + +/* think about including max11600 etc - more settings */ +#define MAX1363_SETUP_EXT_CLOCK 0x08 +#define MAX1363_SETUP_INT_CLOCK 0x00 +#define MAX1363_SETUP_UNIPOLAR 0x00 +#define MAX1363_SETUP_BIPOLAR 0x04 +#define MAX1363_SETUP_RESET 0x00 +#define MAX1363_SETUP_NORESET 0x02 +/* max1363 only - though don't care on others. + * For now monitor modes are not implemented as the relevant + * line is not connected on my test board. + * The definitions are here as I intend to add this soon. + */ +#define MAX1363_SETUP_MONITOR_SETUP 0x01 + +/* Specific to the max1363 */ +#define MAX1363_MON_RESET_CHAN(a) (1 << ((a) + 4)) +#define MAX1363_MON_INT_ENABLE 0x01 + +/* defined for readability reasons */ +/* All chips */ +#define MAX1363_CONFIG_BYTE(a) ((a)) + +#define MAX1363_CONFIG_SE 0x01 +#define MAX1363_CONFIG_DE 0x00 +#define MAX1363_CONFIG_SCAN_TO_CS 0x00 +#define MAX1363_CONFIG_SCAN_SINGLE_8 0x20 +#define MAX1363_CONFIG_SCAN_MONITOR_MODE 0x40 +#define MAX1363_CONFIG_SCAN_SINGLE_1 0x60 +/* max123{6-9} only */ +#define MAX1236_SCAN_MID_TO_CHANNEL 0x40 + +/* max1363 only - merely part of channel selects or don't care for others */ +#define MAX1363_CONFIG_EN_MON_MODE_READ 0x18 + +#define MAX1363_CHANNEL_SEL(a) ((a) << 1) + +/* max1363 strictly 0x06 - but doesn't matter */ +#define MAX1363_CHANNEL_SEL_MASK 0x1E +#define MAX1363_SCAN_MASK 0x60 +#define MAX1363_SE_DE_MASK 0x01 + +#define MAX1363_MAX_CHANNELS 25 +/** + * struct max1363_mode - scan mode information + * @conf: The corresponding value of the configuration register + * @modemask: Bit mask corresponding to channels enabled in this mode + */ +struct max1363_mode { + int8_t conf; + DECLARE_BITMAP(modemask, MAX1363_MAX_CHANNELS); +}; + +/* This must be maintained along side the max1363_mode_table in max1363_core */ +enum max1363_modes { + /* Single read of a single channel */ + _s0, _s1, _s2, _s3, _s4, _s5, _s6, _s7, _s8, _s9, _s10, _s11, + /* Differential single read */ + d0m1, d2m3, d4m5, d6m7, d8m9, d10m11, + d1m0, d3m2, d5m4, d7m6, d9m8, d11m10, + /* Scan to channel and mid to channel where overlapping */ + s0to1, s0to2, s2to3, s0to3, s0to4, s0to5, s0to6, + s6to7, s0to7, s6to8, s0to8, s6to9, + s0to9, s6to10, s0to10, s6to11, s0to11, + /* Differential scan to channel and mid to channel where overlapping */ + d0m1to2m3, d0m1to4m5, d0m1to6m7, d6m7to8m9, + d0m1to8m9, d6m7to10m11, d0m1to10m11, d1m0to3m2, + d1m0to5m4, d1m0to7m6, d7m6to9m8, d1m0to9m8, + d7m6to11m10, d1m0to11m10, +}; + +/** + * struct max1363_chip_info - chip specific information + * @info: iio core function callbacks structure + * @channels: channel specification + * @num_channels: number of channels + * @mode_list: array of available scan modes + * @default_mode: the scan mode in which the chip starts up + * @int_vref_mv: the internal reference voltage + * @num_modes: number of modes + * @bits: accuracy of the adc in bits + */ +struct max1363_chip_info { + const struct iio_info *info; + const struct iio_chan_spec *channels; + int num_channels; + const enum max1363_modes *mode_list; + enum max1363_modes default_mode; + u16 int_vref_mv; + u8 num_modes; + u8 bits; +}; + +/** + * struct max1363_state - driver instance specific data + * @client: i2c_client + * @setupbyte: cache of current device setup byte + * @configbyte: cache of current device config byte + * @chip_info: chip model specific constants, available modes, etc. + * @current_mode: the scan mode of this chip + * @lock: lock to ensure state is consistent + * @monitor_on: whether monitor mode is enabled + * @monitor_speed: parameter corresponding to device monitor speed setting + * @mask_high: bitmask for enabled high thresholds + * @mask_low: bitmask for enabled low thresholds + * @thresh_high: high threshold values + * @thresh_low: low threshold values + * @vref: Reference voltage regulator + * @vref_uv: Actual (external or internal) reference voltage + * @send: function used to send data to the chip + * @recv: function used to receive data from the chip + * @data: buffer to store channel data and timestamp + */ +struct max1363_state { + struct i2c_client *client; + u8 setupbyte; + u8 configbyte; + const struct max1363_chip_info *chip_info; + const struct max1363_mode *current_mode; + struct mutex lock; + + /* Using monitor modes and buffer at the same time is + currently not supported */ + bool monitor_on; + unsigned int monitor_speed:3; + u8 mask_high; + u8 mask_low; + /* 4x unipolar first then the fours bipolar ones */ + s16 thresh_high[8]; + s16 thresh_low[8]; + struct regulator *vref; + u32 vref_uv; + int (*send)(const struct i2c_client *client, + const char *buf, int count); + int (*recv)(const struct i2c_client *client, + char *buf, int count); + struct { + u8 buf[MAX1363_MAX_CHANNELS * 2]; + aligned_s64 ts; + } data; +}; + +#define MAX1363_MODE_SINGLE(_num, _mask) { \ + .conf = MAX1363_CHANNEL_SEL(_num) \ + | MAX1363_CONFIG_SCAN_SINGLE_1 \ + | MAX1363_CONFIG_SE, \ + .modemask[0] = _mask, \ + } + +#define MAX1363_MODE_SCAN_TO_CHANNEL(_num, _mask) { \ + .conf = MAX1363_CHANNEL_SEL(_num) \ + | MAX1363_CONFIG_SCAN_TO_CS \ + | MAX1363_CONFIG_SE, \ + .modemask[0] = _mask, \ + } + +/* note not available for max1363 hence naming */ +#define MAX1236_MODE_SCAN_MID_TO_CHANNEL(_mid, _num, _mask) { \ + .conf = MAX1363_CHANNEL_SEL(_num) \ + | MAX1236_SCAN_MID_TO_CHANNEL \ + | MAX1363_CONFIG_SE, \ + .modemask[0] = _mask \ +} + +#define MAX1363_MODE_DIFF_SINGLE(_nump, _numm, _mask) { \ + .conf = MAX1363_CHANNEL_SEL(_nump) \ + | MAX1363_CONFIG_SCAN_SINGLE_1 \ + | MAX1363_CONFIG_DE, \ + .modemask[0] = _mask \ + } + +/* Can't think how to automate naming so specify for now */ +#define MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(_num, _numvals, _mask) { \ + .conf = MAX1363_CHANNEL_SEL(_num) \ + | MAX1363_CONFIG_SCAN_TO_CS \ + | MAX1363_CONFIG_DE, \ + .modemask[0] = _mask \ + } + +/* note only available for max1363 hence naming */ +#define MAX1236_MODE_DIFF_SCAN_MID_TO_CHANNEL(_num, _numvals, _mask) { \ + .conf = MAX1363_CHANNEL_SEL(_num) \ + | MAX1236_SCAN_MID_TO_CHANNEL \ + | MAX1363_CONFIG_SE, \ + .modemask[0] = _mask \ +} + +static const struct max1363_mode max1363_mode_table[] = { + /* All of the single channel options first */ + MAX1363_MODE_SINGLE(0, 1 << 0), + MAX1363_MODE_SINGLE(1, 1 << 1), + MAX1363_MODE_SINGLE(2, 1 << 2), + MAX1363_MODE_SINGLE(3, 1 << 3), + MAX1363_MODE_SINGLE(4, 1 << 4), + MAX1363_MODE_SINGLE(5, 1 << 5), + MAX1363_MODE_SINGLE(6, 1 << 6), + MAX1363_MODE_SINGLE(7, 1 << 7), + MAX1363_MODE_SINGLE(8, 1 << 8), + MAX1363_MODE_SINGLE(9, 1 << 9), + MAX1363_MODE_SINGLE(10, 1 << 10), + MAX1363_MODE_SINGLE(11, 1 << 11), + + MAX1363_MODE_DIFF_SINGLE(0, 1, 1 << 12), + MAX1363_MODE_DIFF_SINGLE(2, 3, 1 << 13), + MAX1363_MODE_DIFF_SINGLE(4, 5, 1 << 14), + MAX1363_MODE_DIFF_SINGLE(6, 7, 1 << 15), + MAX1363_MODE_DIFF_SINGLE(8, 9, 1 << 16), + MAX1363_MODE_DIFF_SINGLE(10, 11, 1 << 17), + MAX1363_MODE_DIFF_SINGLE(1, 0, 1 << 18), + MAX1363_MODE_DIFF_SINGLE(3, 2, 1 << 19), + MAX1363_MODE_DIFF_SINGLE(5, 4, 1 << 20), + MAX1363_MODE_DIFF_SINGLE(7, 6, 1 << 21), + MAX1363_MODE_DIFF_SINGLE(9, 8, 1 << 22), + MAX1363_MODE_DIFF_SINGLE(11, 10, 1 << 23), + + /* The multichannel scans next */ + MAX1363_MODE_SCAN_TO_CHANNEL(1, 0x003), + MAX1363_MODE_SCAN_TO_CHANNEL(2, 0x007), + MAX1236_MODE_SCAN_MID_TO_CHANNEL(2, 3, 0x00C), + MAX1363_MODE_SCAN_TO_CHANNEL(3, 0x00F), + MAX1363_MODE_SCAN_TO_CHANNEL(4, 0x01F), + MAX1363_MODE_SCAN_TO_CHANNEL(5, 0x03F), + MAX1363_MODE_SCAN_TO_CHANNEL(6, 0x07F), + MAX1236_MODE_SCAN_MID_TO_CHANNEL(6, 7, 0x0C0), + MAX1363_MODE_SCAN_TO_CHANNEL(7, 0x0FF), + MAX1236_MODE_SCAN_MID_TO_CHANNEL(6, 8, 0x1C0), + MAX1363_MODE_SCAN_TO_CHANNEL(8, 0x1FF), + MAX1236_MODE_SCAN_MID_TO_CHANNEL(6, 9, 0x3C0), + MAX1363_MODE_SCAN_TO_CHANNEL(9, 0x3FF), + MAX1236_MODE_SCAN_MID_TO_CHANNEL(6, 10, 0x7C0), + MAX1363_MODE_SCAN_TO_CHANNEL(10, 0x7FF), + MAX1236_MODE_SCAN_MID_TO_CHANNEL(6, 11, 0xFC0), + MAX1363_MODE_SCAN_TO_CHANNEL(11, 0xFFF), + + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(2, 2, 0x003000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(4, 3, 0x007000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(6, 4, 0x00F000), + MAX1236_MODE_DIFF_SCAN_MID_TO_CHANNEL(8, 2, 0x018000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(8, 5, 0x01F000), + MAX1236_MODE_DIFF_SCAN_MID_TO_CHANNEL(10, 3, 0x038000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(10, 6, 0x3F000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(3, 2, 0x0C0000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(5, 3, 0x1C0000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(7, 4, 0x3C0000), + MAX1236_MODE_DIFF_SCAN_MID_TO_CHANNEL(9, 2, 0x600000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(9, 5, 0x7C0000), + MAX1236_MODE_DIFF_SCAN_MID_TO_CHANNEL(11, 3, 0xE00000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(11, 6, 0xFC0000), +}; + +static const struct max1363_mode +*max1363_match_mode(const unsigned long *mask, + const struct max1363_chip_info *ci) +{ + int i; + if (mask) + for (i = 0; i < ci->num_modes; i++) + if (bitmap_subset(mask, + max1363_mode_table[ci->mode_list[i]]. + modemask, + MAX1363_MAX_CHANNELS)) + return &max1363_mode_table[ci->mode_list[i]]; + return NULL; +} + +static int max1363_smbus_send(const struct i2c_client *client, const char *buf, + int count) +{ + int i, err; + + for (i = err = 0; err == 0 && i < count; ++i) + err = i2c_smbus_write_byte(client, buf[i]); + + return err ? err : count; +} + +static int max1363_smbus_recv(const struct i2c_client *client, char *buf, + int count) +{ + int i, ret; + + for (i = 0; i < count; ++i) { + ret = i2c_smbus_read_byte(client); + if (ret < 0) + return ret; + buf[i] = ret; + } + + return count; +} + +static int max1363_write_basic_config(struct max1363_state *st) +{ + u8 tx_buf[2] = { st->setupbyte, st->configbyte }; + + return st->send(st->client, tx_buf, 2); +} + +static int max1363_set_scan_mode(struct max1363_state *st) +{ + st->configbyte &= ~(MAX1363_CHANNEL_SEL_MASK + | MAX1363_SCAN_MASK + | MAX1363_SE_DE_MASK); + st->configbyte |= st->current_mode->conf; + + return max1363_write_basic_config(st); +} + +static int max1363_read_single_chan(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + long m) +{ + s32 data; + u8 rxbuf[2]; + struct max1363_state *st = iio_priv(indio_dev); + struct i2c_client *client = st->client; + + guard(mutex)(&st->lock); + + /* + * If monitor mode is enabled, the method for reading a single + * channel will have to be rather different and has not yet + * been implemented. + * + * Also, cannot read directly if buffered capture enabled. + */ + if (st->monitor_on) + return -EBUSY; + + /* Check to see if current scan mode is correct */ + if (st->current_mode != &max1363_mode_table[chan->address]) { + int ret; + + /* Update scan mode if needed */ + st->current_mode = &max1363_mode_table[chan->address]; + ret = max1363_set_scan_mode(st); + if (ret < 0) + return ret; + } + if (st->chip_info->bits != 8) { + /* Get reading */ + data = st->recv(client, rxbuf, 2); + if (data < 0) + return data; + + data = get_unaligned_be16(rxbuf) & + ((1 << st->chip_info->bits) - 1); + } else { + /* Get reading */ + data = st->recv(client, rxbuf, 1); + if (data < 0) + return data; + + data = rxbuf[0]; + } + *val = data; + + return 0; +} + +static int max1363_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct max1363_state *st = iio_priv(indio_dev); + int ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = max1363_read_single_chan(indio_dev, chan, val, m); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = st->vref_uv / 1000; + *val2 = st->chip_info->bits; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } + return 0; +} + +/* Applies to max1363 */ +static const enum max1363_modes max1363_mode_list[] = { + _s0, _s1, _s2, _s3, + s0to1, s0to2, s0to3, + d0m1, d2m3, d1m0, d3m2, + d0m1to2m3, d1m0to3m2, +}; + +static const struct iio_event_spec max1363_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +#define MAX1363_CHAN_U(num, addr, si, bits, ev_spec, num_ev_spec) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = num, \ + .address = addr, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = "AIN"#num, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = bits, \ + .storagebits = (bits > 8) ? 16 : 8, \ + .endianness = IIO_BE, \ + }, \ + .scan_index = si, \ + .event_spec = ev_spec, \ + .num_event_specs = num_ev_spec, \ + } + +/* bipolar channel */ +#define MAX1363_CHAN_B(num, num2, addr, si, bits, ev_spec, num_ev_spec) \ + { \ + .type = IIO_VOLTAGE, \ + .differential = 1, \ + .indexed = 1, \ + .channel = num, \ + .channel2 = num2, \ + .address = addr, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = "AIN"#num"-AIN"#num2, \ + .scan_type = { \ + .sign = 's', \ + .realbits = bits, \ + .storagebits = (bits > 8) ? 16 : 8, \ + .endianness = IIO_BE, \ + }, \ + .scan_index = si, \ + .event_spec = ev_spec, \ + .num_event_specs = num_ev_spec, \ + } + +#define MAX1363_4X_CHANS(bits, ev_spec, num_ev_spec) { \ + MAX1363_CHAN_U(0, _s0, 0, bits, ev_spec, num_ev_spec), \ + MAX1363_CHAN_U(1, _s1, 1, bits, ev_spec, num_ev_spec), \ + MAX1363_CHAN_U(2, _s2, 2, bits, ev_spec, num_ev_spec), \ + MAX1363_CHAN_U(3, _s3, 3, bits, ev_spec, num_ev_spec), \ + MAX1363_CHAN_B(0, 1, d0m1, 12, bits, ev_spec, num_ev_spec), \ + MAX1363_CHAN_B(2, 3, d2m3, 13, bits, ev_spec, num_ev_spec), \ + MAX1363_CHAN_B(1, 0, d1m0, 18, bits, ev_spec, num_ev_spec), \ + MAX1363_CHAN_B(3, 2, d3m2, 19, bits, ev_spec, num_ev_spec), \ + IIO_CHAN_SOFT_TIMESTAMP(8) \ + } + +static const struct iio_chan_spec max1036_channels[] = + MAX1363_4X_CHANS(8, NULL, 0); +static const struct iio_chan_spec max1136_channels[] = + MAX1363_4X_CHANS(10, NULL, 0); +static const struct iio_chan_spec max1236_channels[] = + MAX1363_4X_CHANS(12, NULL, 0); +static const struct iio_chan_spec max1361_channels[] = + MAX1363_4X_CHANS(10, max1363_events, ARRAY_SIZE(max1363_events)); +static const struct iio_chan_spec max1363_channels[] = + MAX1363_4X_CHANS(12, max1363_events, ARRAY_SIZE(max1363_events)); + +/* Applies to max1236, max1237 */ +static const enum max1363_modes max1236_mode_list[] = { + _s0, _s1, _s2, _s3, + s0to1, s0to2, s2to3, s0to3, + d0m1, d2m3, d1m0, d3m2, + d0m1to2m3, d1m0to3m2, +}; + +/* Applies to max1238, max1239 */ +static const enum max1363_modes max1238_mode_list[] = { + _s0, _s1, _s2, _s3, _s4, _s5, _s6, _s7, _s8, _s9, _s10, _s11, + s0to1, s0to2, s0to3, s0to4, s0to5, s0to6, + s6to7, s6to8, s6to9, s6to10, s6to11, + s0to7, s0to8, s0to9, s0to10, s0to11, + d0m1, d2m3, d4m5, d6m7, d8m9, d10m11, + d1m0, d3m2, d5m4, d7m6, d9m8, d11m10, + d0m1to2m3, d0m1to4m5, d0m1to6m7, d6m7to8m9, + d0m1to8m9, d6m7to10m11, d0m1to10m11, d1m0to3m2, + d1m0to5m4, d1m0to7m6, d7m6to9m8, d1m0to9m8, + d7m6to11m10, d1m0to11m10, +}; + +#define MAX1363_12X_CHANS(bits) { \ + MAX1363_CHAN_U(0, _s0, 0, bits, NULL, 0), \ + MAX1363_CHAN_U(1, _s1, 1, bits, NULL, 0), \ + MAX1363_CHAN_U(2, _s2, 2, bits, NULL, 0), \ + MAX1363_CHAN_U(3, _s3, 3, bits, NULL, 0), \ + MAX1363_CHAN_U(4, _s4, 4, bits, NULL, 0), \ + MAX1363_CHAN_U(5, _s5, 5, bits, NULL, 0), \ + MAX1363_CHAN_U(6, _s6, 6, bits, NULL, 0), \ + MAX1363_CHAN_U(7, _s7, 7, bits, NULL, 0), \ + MAX1363_CHAN_U(8, _s8, 8, bits, NULL, 0), \ + MAX1363_CHAN_U(9, _s9, 9, bits, NULL, 0), \ + MAX1363_CHAN_U(10, _s10, 10, bits, NULL, 0), \ + MAX1363_CHAN_U(11, _s11, 11, bits, NULL, 0), \ + MAX1363_CHAN_B(0, 1, d0m1, 12, bits, NULL, 0), \ + MAX1363_CHAN_B(2, 3, d2m3, 13, bits, NULL, 0), \ + MAX1363_CHAN_B(4, 5, d4m5, 14, bits, NULL, 0), \ + MAX1363_CHAN_B(6, 7, d6m7, 15, bits, NULL, 0), \ + MAX1363_CHAN_B(8, 9, d8m9, 16, bits, NULL, 0), \ + MAX1363_CHAN_B(10, 11, d10m11, 17, bits, NULL, 0), \ + MAX1363_CHAN_B(1, 0, d1m0, 18, bits, NULL, 0), \ + MAX1363_CHAN_B(3, 2, d3m2, 19, bits, NULL, 0), \ + MAX1363_CHAN_B(5, 4, d5m4, 20, bits, NULL, 0), \ + MAX1363_CHAN_B(7, 6, d7m6, 21, bits, NULL, 0), \ + MAX1363_CHAN_B(9, 8, d9m8, 22, bits, NULL, 0), \ + MAX1363_CHAN_B(11, 10, d11m10, 23, bits, NULL, 0), \ + IIO_CHAN_SOFT_TIMESTAMP(24) \ + } +static const struct iio_chan_spec max1038_channels[] = MAX1363_12X_CHANS(8); +static const struct iio_chan_spec max1138_channels[] = MAX1363_12X_CHANS(10); +static const struct iio_chan_spec max1238_channels[] = MAX1363_12X_CHANS(12); + +static const enum max1363_modes max11607_mode_list[] = { + _s0, _s1, _s2, _s3, + s0to1, s0to2, s2to3, + s0to3, + d0m1, d2m3, d1m0, d3m2, + d0m1to2m3, d1m0to3m2, +}; + +static const enum max1363_modes max11608_mode_list[] = { + _s0, _s1, _s2, _s3, _s4, _s5, _s6, _s7, + s0to1, s0to2, s0to3, s0to4, s0to5, s0to6, s6to7, s0to7, + d0m1, d2m3, d4m5, d6m7, + d1m0, d3m2, d5m4, d7m6, + d0m1to2m3, d0m1to4m5, d0m1to6m7, + d1m0to3m2, d1m0to5m4, d1m0to7m6, +}; + +#define MAX1363_8X_CHANS(bits) { \ + MAX1363_CHAN_U(0, _s0, 0, bits, NULL, 0), \ + MAX1363_CHAN_U(1, _s1, 1, bits, NULL, 0), \ + MAX1363_CHAN_U(2, _s2, 2, bits, NULL, 0), \ + MAX1363_CHAN_U(3, _s3, 3, bits, NULL, 0), \ + MAX1363_CHAN_U(4, _s4, 4, bits, NULL, 0), \ + MAX1363_CHAN_U(5, _s5, 5, bits, NULL, 0), \ + MAX1363_CHAN_U(6, _s6, 6, bits, NULL, 0), \ + MAX1363_CHAN_U(7, _s7, 7, bits, NULL, 0), \ + MAX1363_CHAN_B(0, 1, d0m1, 12, bits, NULL, 0), \ + MAX1363_CHAN_B(2, 3, d2m3, 13, bits, NULL, 0), \ + MAX1363_CHAN_B(4, 5, d4m5, 14, bits, NULL, 0), \ + MAX1363_CHAN_B(6, 7, d6m7, 15, bits, NULL, 0), \ + MAX1363_CHAN_B(1, 0, d1m0, 18, bits, NULL, 0), \ + MAX1363_CHAN_B(3, 2, d3m2, 19, bits, NULL, 0), \ + MAX1363_CHAN_B(5, 4, d5m4, 20, bits, NULL, 0), \ + MAX1363_CHAN_B(7, 6, d7m6, 21, bits, NULL, 0), \ + IIO_CHAN_SOFT_TIMESTAMP(16) \ +} +static const struct iio_chan_spec max11602_channels[] = MAX1363_8X_CHANS(8); +static const struct iio_chan_spec max11608_channels[] = MAX1363_8X_CHANS(10); +static const struct iio_chan_spec max11614_channels[] = MAX1363_8X_CHANS(12); + +static const enum max1363_modes max11644_mode_list[] = { + _s0, _s1, s0to1, d0m1, d1m0, +}; + +#define MAX1363_2X_CHANS(bits) { \ + MAX1363_CHAN_U(0, _s0, 0, bits, NULL, 0), \ + MAX1363_CHAN_U(1, _s1, 1, bits, NULL, 0), \ + MAX1363_CHAN_B(0, 1, d0m1, 2, bits, NULL, 0), \ + MAX1363_CHAN_B(1, 0, d1m0, 3, bits, NULL, 0), \ + IIO_CHAN_SOFT_TIMESTAMP(4) \ + } + +static const struct iio_chan_spec max11646_channels[] = MAX1363_2X_CHANS(10); +static const struct iio_chan_spec max11644_channels[] = MAX1363_2X_CHANS(12); + +enum { + max1361, + max1362, + max1363, + max1364, + max1036, + max1037, + max1038, + max1039, + max1136, + max1137, + max1138, + max1139, + max1236, + max1237, + max1238, + max1239, + max11600, + max11601, + max11602, + max11603, + max11604, + max11605, + max11606, + max11607, + max11608, + max11609, + max11610, + max11611, + max11612, + max11613, + max11614, + max11615, + max11616, + max11617, + max11644, + max11645, + max11646, + max11647, +}; + +static const int max1363_monitor_speeds[] = { + 133000, 665000, 33300, 16600, + 8300, 4200, 2000, 1000, +}; + +static ssize_t max1363_monitor_show_freq(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct max1363_state *st = iio_priv(dev_to_iio_dev(dev)); + return sprintf(buf, "%d\n", max1363_monitor_speeds[st->monitor_speed]); +} + +static ssize_t max1363_monitor_store_freq(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct max1363_state *st = iio_priv(indio_dev); + int i, ret; + unsigned long val; + bool found = false; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(max1363_monitor_speeds); i++) + if (val == max1363_monitor_speeds[i]) { + found = true; + break; + } + if (!found) + return -EINVAL; + + scoped_guard(mutex, &st->lock) + st->monitor_speed = i; + + return 0; +} + +static IIO_DEV_ATTR_SAMP_FREQ(S_IRUGO | S_IWUSR, + max1363_monitor_show_freq, + max1363_monitor_store_freq); + +static IIO_CONST_ATTR(sampling_frequency_available, + "133000 665000 33300 16600 8300 4200 2000 1000"); + +static int max1363_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, int *val, + int *val2) +{ + struct max1363_state *st = iio_priv(indio_dev); + if (dir == IIO_EV_DIR_FALLING) + *val = st->thresh_low[chan->channel]; + else + *val = st->thresh_high[chan->channel]; + return IIO_VAL_INT; +} + +static int max1363_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, int val, + int val2) +{ + struct max1363_state *st = iio_priv(indio_dev); + /* make it handle signed correctly as well */ + switch (st->chip_info->bits) { + case 10: + if (val > 0x3FF) + return -EINVAL; + break; + case 12: + if (val > 0xFFF) + return -EINVAL; + break; + } + + switch (dir) { + case IIO_EV_DIR_FALLING: + st->thresh_low[chan->channel] = val; + break; + case IIO_EV_DIR_RISING: + st->thresh_high[chan->channel] = val; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const u64 max1363_event_codes[] = { + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 0, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 1, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 2, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 3, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 0, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 1, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 2, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 3, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), +}; + +static irqreturn_t max1363_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct max1363_state *st = iio_priv(indio_dev); + s64 timestamp = iio_get_time_ns(indio_dev); + unsigned long mask, loc; + u8 rx; + u8 tx[2] = { st->setupbyte, + MAX1363_MON_INT_ENABLE | (st->monitor_speed << 1) | 0xF0 }; + + st->recv(st->client, &rx, 1); + mask = rx; + for_each_set_bit(loc, &mask, 8) + iio_push_event(indio_dev, max1363_event_codes[loc], timestamp); + st->send(st->client, tx, 2); + + return IRQ_HANDLED; +} + +static int max1363_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir) +{ + struct max1363_state *st = iio_priv(indio_dev); + int val; + int number = chan->channel; + + guard(mutex)(&st->lock); + if (dir == IIO_EV_DIR_FALLING) + val = (1 << number) & st->mask_low; + else + val = (1 << number) & st->mask_high; + + return val; +} + +static int max1363_monitor_mode_update(struct max1363_state *st, int enabled) +{ + int ret, i = 3, j; + unsigned long numelements; + int len; + const long *modemask; + + if (!enabled) { + /* transition to buffered capture is not currently supported */ + st->setupbyte &= ~MAX1363_SETUP_MONITOR_SETUP; + st->configbyte &= ~MAX1363_SCAN_MASK; + st->monitor_on = false; + return max1363_write_basic_config(st); + } + + /* Ensure we are in the relevant mode */ + st->setupbyte |= MAX1363_SETUP_MONITOR_SETUP; + st->configbyte &= ~(MAX1363_CHANNEL_SEL_MASK + | MAX1363_SCAN_MASK + | MAX1363_SE_DE_MASK); + st->configbyte |= MAX1363_CONFIG_SCAN_MONITOR_MODE; + if ((st->mask_low | st->mask_high) & 0x0F) { + st->configbyte |= max1363_mode_table[s0to3].conf; + modemask = max1363_mode_table[s0to3].modemask; + } else if ((st->mask_low | st->mask_high) & 0x30) { + st->configbyte |= max1363_mode_table[d0m1to2m3].conf; + modemask = max1363_mode_table[d0m1to2m3].modemask; + } else { + st->configbyte |= max1363_mode_table[d1m0to3m2].conf; + modemask = max1363_mode_table[d1m0to3m2].modemask; + } + numelements = bitmap_weight(modemask, MAX1363_MAX_CHANNELS); + len = 3 * numelements + 3; + u8 *tx_buf __free(kfree) = kmalloc(len, GFP_KERNEL); + if (!tx_buf) + return -ENOMEM; + + tx_buf[0] = st->configbyte; + tx_buf[1] = st->setupbyte; + tx_buf[2] = (st->monitor_speed << 1); + + /* + * So we need to do yet another bit of nefarious scan mode + * setup to match what we need. + */ + for (j = 0; j < 8; j++) + if (test_bit(j, modemask)) { + /* Establish the mode is in the scan */ + if (st->mask_low & (1 << j)) { + tx_buf[i] = (st->thresh_low[j] >> 4) & 0xFF; + tx_buf[i + 1] = (st->thresh_low[j] << 4) & 0xF0; + } else if (j < 4) { + tx_buf[i] = 0; + tx_buf[i + 1] = 0; + } else { + tx_buf[i] = 0x80; + tx_buf[i + 1] = 0; + } + if (st->mask_high & (1 << j)) { + tx_buf[i + 1] |= + (st->thresh_high[j] >> 8) & 0x0F; + tx_buf[i + 2] = st->thresh_high[j] & 0xFF; + } else if (j < 4) { + tx_buf[i + 1] |= 0x0F; + tx_buf[i + 2] = 0xFF; + } else { + tx_buf[i + 1] |= 0x07; + tx_buf[i + 2] = 0xFF; + } + i += 3; + } + + + ret = st->send(st->client, tx_buf, len); + if (ret < 0) + return ret; + if (ret != len) + return -EIO; + + /* + * Now that we hopefully have sensible thresholds in place it is + * time to turn the interrupts on. + * It is unclear from the data sheet if this should be necessary + * (i.e. whether monitor mode setup is atomic) but it appears to + * be in practice. + */ + tx_buf[0] = st->setupbyte; + tx_buf[1] = MAX1363_MON_INT_ENABLE | (st->monitor_speed << 1) | 0xF0; + ret = st->send(st->client, tx_buf, 2); + if (ret < 0) + return ret; + if (ret != 2) + return -EIO; + + st->monitor_on = true; + + return 0; +} + +/* + * To keep this manageable we always use one of 3 scan modes. + * Scan 0...3, 0-1,2-3 and 1-0,3-2 + */ + +static inline int __max1363_check_event_mask(int thismask, int checkmask) +{ + int ret = 0; + /* Is it unipolar */ + if (thismask < 4) { + if (checkmask & ~0x0F) { + ret = -EBUSY; + goto error_ret; + } + } else if (thismask < 6) { + if (checkmask & ~0x30) { + ret = -EBUSY; + goto error_ret; + } + } else if (checkmask & ~0xC0) + ret = -EBUSY; +error_ret: + return ret; +} + +static int __max1363_write_event_config(struct max1363_state *st, + const struct iio_chan_spec *chan, + enum iio_event_direction dir, bool state) +{ + int number = chan->channel; + u16 unifiedmask; + int ret; + + guard(mutex)(&st->lock); + + unifiedmask = st->mask_low | st->mask_high; + if (dir == IIO_EV_DIR_FALLING) { + + if (state == 0) + st->mask_low &= ~(1 << number); + else { + ret = __max1363_check_event_mask((1 << number), + unifiedmask); + if (ret) + return ret; + st->mask_low |= (1 << number); + } + } else { + if (state == 0) + st->mask_high &= ~(1 << number); + else { + ret = __max1363_check_event_mask((1 << number), + unifiedmask); + if (ret) + return ret; + st->mask_high |= (1 << number); + } + } + + return 0; + +} +static int max1363_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct max1363_state *st = iio_priv(indio_dev); + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = __max1363_write_event_config(st, chan, dir, state); + iio_device_release_direct(indio_dev); + max1363_monitor_mode_update(st, !!(st->mask_high | st->mask_low)); + + return ret; +} + +/* + * As with scan_elements, only certain sets of these can + * be combined. + */ +static struct attribute *max1363_event_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group max1363_event_attribute_group = { + .attrs = max1363_event_attributes, +}; + +static int max1363_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct max1363_state *st = iio_priv(indio_dev); + + /* + * Need to figure out the current mode based upon the requested + * scan mask in iio_dev + */ + st->current_mode = max1363_match_mode(scan_mask, st->chip_info); + if (!st->current_mode) + return -EINVAL; + max1363_set_scan_mode(st); + return 0; +} + +static const struct iio_info max1238_info = { + .read_raw = &max1363_read_raw, + .update_scan_mode = &max1363_update_scan_mode, +}; + +static const struct iio_info max1363_info = { + .read_event_value = &max1363_read_thresh, + .write_event_value = &max1363_write_thresh, + .read_event_config = &max1363_read_event_config, + .write_event_config = &max1363_write_event_config, + .read_raw = &max1363_read_raw, + .update_scan_mode = &max1363_update_scan_mode, + .event_attrs = &max1363_event_attribute_group, +}; + +/* max1363 and max1368 tested - rest from data sheet */ +static const struct max1363_chip_info max1363_chip_info_tbl[] = { + [max1361] = { + .bits = 10, + .int_vref_mv = 2048, + .mode_list = max1363_mode_list, + .num_modes = ARRAY_SIZE(max1363_mode_list), + .default_mode = s0to3, + .channels = max1361_channels, + .num_channels = ARRAY_SIZE(max1361_channels), + .info = &max1363_info, + }, + [max1362] = { + .bits = 10, + .int_vref_mv = 4096, + .mode_list = max1363_mode_list, + .num_modes = ARRAY_SIZE(max1363_mode_list), + .default_mode = s0to3, + .channels = max1361_channels, + .num_channels = ARRAY_SIZE(max1361_channels), + .info = &max1363_info, + }, + [max1363] = { + .bits = 12, + .int_vref_mv = 2048, + .mode_list = max1363_mode_list, + .num_modes = ARRAY_SIZE(max1363_mode_list), + .default_mode = s0to3, + .channels = max1363_channels, + .num_channels = ARRAY_SIZE(max1363_channels), + .info = &max1363_info, + }, + [max1364] = { + .bits = 12, + .int_vref_mv = 4096, + .mode_list = max1363_mode_list, + .num_modes = ARRAY_SIZE(max1363_mode_list), + .default_mode = s0to3, + .channels = max1363_channels, + .num_channels = ARRAY_SIZE(max1363_channels), + .info = &max1363_info, + }, + [max1036] = { + .bits = 8, + .int_vref_mv = 4096, + .mode_list = max1236_mode_list, + .num_modes = ARRAY_SIZE(max1236_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1036_channels, + .num_channels = ARRAY_SIZE(max1036_channels), + }, + [max1037] = { + .bits = 8, + .int_vref_mv = 2048, + .mode_list = max1236_mode_list, + .num_modes = ARRAY_SIZE(max1236_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1036_channels, + .num_channels = ARRAY_SIZE(max1036_channels), + }, + [max1038] = { + .bits = 8, + .int_vref_mv = 4096, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1038_channels, + .num_channels = ARRAY_SIZE(max1038_channels), + }, + [max1039] = { + .bits = 8, + .int_vref_mv = 2048, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1038_channels, + .num_channels = ARRAY_SIZE(max1038_channels), + }, + [max1136] = { + .bits = 10, + .int_vref_mv = 4096, + .mode_list = max1236_mode_list, + .num_modes = ARRAY_SIZE(max1236_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1136_channels, + .num_channels = ARRAY_SIZE(max1136_channels), + }, + [max1137] = { + .bits = 10, + .int_vref_mv = 2048, + .mode_list = max1236_mode_list, + .num_modes = ARRAY_SIZE(max1236_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1136_channels, + .num_channels = ARRAY_SIZE(max1136_channels), + }, + [max1138] = { + .bits = 10, + .int_vref_mv = 4096, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1138_channels, + .num_channels = ARRAY_SIZE(max1138_channels), + }, + [max1139] = { + .bits = 10, + .int_vref_mv = 2048, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1138_channels, + .num_channels = ARRAY_SIZE(max1138_channels), + }, + [max1236] = { + .bits = 12, + .int_vref_mv = 4096, + .mode_list = max1236_mode_list, + .num_modes = ARRAY_SIZE(max1236_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1236_channels, + .num_channels = ARRAY_SIZE(max1236_channels), + }, + [max1237] = { + .bits = 12, + .int_vref_mv = 2048, + .mode_list = max1236_mode_list, + .num_modes = ARRAY_SIZE(max1236_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1236_channels, + .num_channels = ARRAY_SIZE(max1236_channels), + }, + [max1238] = { + .bits = 12, + .int_vref_mv = 4096, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1238_channels, + .num_channels = ARRAY_SIZE(max1238_channels), + }, + [max1239] = { + .bits = 12, + .int_vref_mv = 2048, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1238_channels, + .num_channels = ARRAY_SIZE(max1238_channels), + }, + [max11600] = { + .bits = 8, + .int_vref_mv = 4096, + .mode_list = max11607_mode_list, + .num_modes = ARRAY_SIZE(max11607_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1036_channels, + .num_channels = ARRAY_SIZE(max1036_channels), + }, + [max11601] = { + .bits = 8, + .int_vref_mv = 2048, + .mode_list = max11607_mode_list, + .num_modes = ARRAY_SIZE(max11607_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1036_channels, + .num_channels = ARRAY_SIZE(max1036_channels), + }, + [max11602] = { + .bits = 8, + .int_vref_mv = 4096, + .mode_list = max11608_mode_list, + .num_modes = ARRAY_SIZE(max11608_mode_list), + .default_mode = s0to7, + .info = &max1238_info, + .channels = max11602_channels, + .num_channels = ARRAY_SIZE(max11602_channels), + }, + [max11603] = { + .bits = 8, + .int_vref_mv = 2048, + .mode_list = max11608_mode_list, + .num_modes = ARRAY_SIZE(max11608_mode_list), + .default_mode = s0to7, + .info = &max1238_info, + .channels = max11602_channels, + .num_channels = ARRAY_SIZE(max11602_channels), + }, + [max11604] = { + .bits = 8, + .int_vref_mv = 4096, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1038_channels, + .num_channels = ARRAY_SIZE(max1038_channels), + }, + [max11605] = { + .bits = 8, + .int_vref_mv = 2048, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1038_channels, + .num_channels = ARRAY_SIZE(max1038_channels), + }, + [max11606] = { + .bits = 10, + .int_vref_mv = 4096, + .mode_list = max11607_mode_list, + .num_modes = ARRAY_SIZE(max11607_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1136_channels, + .num_channels = ARRAY_SIZE(max1136_channels), + }, + [max11607] = { + .bits = 10, + .int_vref_mv = 2048, + .mode_list = max11607_mode_list, + .num_modes = ARRAY_SIZE(max11607_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1136_channels, + .num_channels = ARRAY_SIZE(max1136_channels), + }, + [max11608] = { + .bits = 10, + .int_vref_mv = 4096, + .mode_list = max11608_mode_list, + .num_modes = ARRAY_SIZE(max11608_mode_list), + .default_mode = s0to7, + .info = &max1238_info, + .channels = max11608_channels, + .num_channels = ARRAY_SIZE(max11608_channels), + }, + [max11609] = { + .bits = 10, + .int_vref_mv = 2048, + .mode_list = max11608_mode_list, + .num_modes = ARRAY_SIZE(max11608_mode_list), + .default_mode = s0to7, + .info = &max1238_info, + .channels = max11608_channels, + .num_channels = ARRAY_SIZE(max11608_channels), + }, + [max11610] = { + .bits = 10, + .int_vref_mv = 4096, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1138_channels, + .num_channels = ARRAY_SIZE(max1138_channels), + }, + [max11611] = { + .bits = 10, + .int_vref_mv = 2048, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1138_channels, + .num_channels = ARRAY_SIZE(max1138_channels), + }, + [max11612] = { + .bits = 12, + .int_vref_mv = 4096, + .mode_list = max11607_mode_list, + .num_modes = ARRAY_SIZE(max11607_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1363_channels, + .num_channels = ARRAY_SIZE(max1363_channels), + }, + [max11613] = { + .bits = 12, + .int_vref_mv = 2048, + .mode_list = max11607_mode_list, + .num_modes = ARRAY_SIZE(max11607_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1363_channels, + .num_channels = ARRAY_SIZE(max1363_channels), + }, + [max11614] = { + .bits = 12, + .int_vref_mv = 4096, + .mode_list = max11608_mode_list, + .num_modes = ARRAY_SIZE(max11608_mode_list), + .default_mode = s0to7, + .info = &max1238_info, + .channels = max11614_channels, + .num_channels = ARRAY_SIZE(max11614_channels), + }, + [max11615] = { + .bits = 12, + .int_vref_mv = 2048, + .mode_list = max11608_mode_list, + .num_modes = ARRAY_SIZE(max11608_mode_list), + .default_mode = s0to7, + .info = &max1238_info, + .channels = max11614_channels, + .num_channels = ARRAY_SIZE(max11614_channels), + }, + [max11616] = { + .bits = 12, + .int_vref_mv = 4096, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1238_channels, + .num_channels = ARRAY_SIZE(max1238_channels), + }, + [max11617] = { + .bits = 12, + .int_vref_mv = 2048, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1238_channels, + .num_channels = ARRAY_SIZE(max1238_channels), + }, + [max11644] = { + .bits = 12, + .int_vref_mv = 4096, + .mode_list = max11644_mode_list, + .num_modes = ARRAY_SIZE(max11644_mode_list), + .default_mode = s0to1, + .info = &max1238_info, + .channels = max11644_channels, + .num_channels = ARRAY_SIZE(max11644_channels), + }, + [max11645] = { + .bits = 12, + .int_vref_mv = 2048, + .mode_list = max11644_mode_list, + .num_modes = ARRAY_SIZE(max11644_mode_list), + .default_mode = s0to1, + .info = &max1238_info, + .channels = max11644_channels, + .num_channels = ARRAY_SIZE(max11644_channels), + }, + [max11646] = { + .bits = 10, + .int_vref_mv = 4096, + .mode_list = max11644_mode_list, + .num_modes = ARRAY_SIZE(max11644_mode_list), + .default_mode = s0to1, + .info = &max1238_info, + .channels = max11646_channels, + .num_channels = ARRAY_SIZE(max11646_channels), + }, + [max11647] = { + .bits = 10, + .int_vref_mv = 2048, + .mode_list = max11644_mode_list, + .num_modes = ARRAY_SIZE(max11644_mode_list), + .default_mode = s0to1, + .info = &max1238_info, + .channels = max11646_channels, + .num_channels = ARRAY_SIZE(max11646_channels), + }, +}; + +static int max1363_initial_setup(struct max1363_state *st) +{ + st->setupbyte = MAX1363_SETUP_INT_CLOCK + | MAX1363_SETUP_UNIPOLAR + | MAX1363_SETUP_NORESET; + + if (st->vref) + st->setupbyte |= MAX1363_SETUP_AIN3_IS_REF_EXT_TO_REF; + else + st->setupbyte |= MAX1363_SETUP_POWER_UP_INT_REF + | MAX1363_SETUP_AIN3_IS_AIN3_REF_IS_INT; + + /* Set scan mode writes the config anyway so wait until then */ + st->setupbyte = MAX1363_SETUP_BYTE(st->setupbyte); + st->current_mode = &max1363_mode_table[st->chip_info->default_mode]; + st->configbyte = MAX1363_CONFIG_BYTE(st->configbyte); + + return max1363_set_scan_mode(st); +} + +static int max1363_alloc_scan_masks(struct iio_dev *indio_dev) +{ + struct max1363_state *st = iio_priv(indio_dev); + unsigned long *masks; + int i; + + masks = devm_kzalloc(&indio_dev->dev, + array3_size(BITS_TO_LONGS(MAX1363_MAX_CHANNELS), + sizeof(long), + st->chip_info->num_modes + 1), + GFP_KERNEL); + if (!masks) + return -ENOMEM; + + for (i = 0; i < st->chip_info->num_modes; i++) + bitmap_copy(masks + BITS_TO_LONGS(MAX1363_MAX_CHANNELS)*i, + max1363_mode_table[st->chip_info->mode_list[i]] + .modemask, MAX1363_MAX_CHANNELS); + + indio_dev->available_scan_masks = masks; + + return 0; +} + +static irqreturn_t max1363_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct max1363_state *st = iio_priv(indio_dev); + int b_sent; + unsigned long numvals = bitmap_weight(st->current_mode->modemask, + MAX1363_MAX_CHANNELS); + + /* Monitor mode prevents reading. Whilst not currently implemented + * might as well have this test in here in the meantime as it does + * no harm. + */ + if (numvals == 0) + goto done; + + if (st->chip_info->bits != 8) + b_sent = st->recv(st->client, st->data.buf, numvals * 2); + else + b_sent = st->recv(st->client, st->data.buf, numvals); + if (b_sent < 0) + goto done; + + iio_push_to_buffers_with_ts(indio_dev, &st->data, sizeof(st->data), + iio_get_time_ns(indio_dev)); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +#define MAX1363_COMPATIBLE(of_compatible, cfg) { \ + .compatible = of_compatible, \ + .data = &max1363_chip_info_tbl[cfg], \ +} + +static const struct of_device_id max1363_of_match[] = { + MAX1363_COMPATIBLE("maxim,max1361", max1361), + MAX1363_COMPATIBLE("maxim,max1362", max1362), + MAX1363_COMPATIBLE("maxim,max1363", max1363), + MAX1363_COMPATIBLE("maxim,max1364", max1364), + MAX1363_COMPATIBLE("maxim,max1036", max1036), + MAX1363_COMPATIBLE("maxim,max1037", max1037), + MAX1363_COMPATIBLE("maxim,max1038", max1038), + MAX1363_COMPATIBLE("maxim,max1039", max1039), + MAX1363_COMPATIBLE("maxim,max1136", max1136), + MAX1363_COMPATIBLE("maxim,max1137", max1137), + MAX1363_COMPATIBLE("maxim,max1138", max1138), + MAX1363_COMPATIBLE("maxim,max1139", max1139), + MAX1363_COMPATIBLE("maxim,max1236", max1236), + MAX1363_COMPATIBLE("maxim,max1237", max1237), + MAX1363_COMPATIBLE("maxim,max1238", max1238), + MAX1363_COMPATIBLE("maxim,max1239", max1239), + MAX1363_COMPATIBLE("maxim,max11600", max11600), + MAX1363_COMPATIBLE("maxim,max11601", max11601), + MAX1363_COMPATIBLE("maxim,max11602", max11602), + MAX1363_COMPATIBLE("maxim,max11603", max11603), + MAX1363_COMPATIBLE("maxim,max11604", max11604), + MAX1363_COMPATIBLE("maxim,max11605", max11605), + MAX1363_COMPATIBLE("maxim,max11606", max11606), + MAX1363_COMPATIBLE("maxim,max11607", max11607), + MAX1363_COMPATIBLE("maxim,max11608", max11608), + MAX1363_COMPATIBLE("maxim,max11609", max11609), + MAX1363_COMPATIBLE("maxim,max11610", max11610), + MAX1363_COMPATIBLE("maxim,max11611", max11611), + MAX1363_COMPATIBLE("maxim,max11612", max11612), + MAX1363_COMPATIBLE("maxim,max11613", max11613), + MAX1363_COMPATIBLE("maxim,max11614", max11614), + MAX1363_COMPATIBLE("maxim,max11615", max11615), + MAX1363_COMPATIBLE("maxim,max11616", max11616), + MAX1363_COMPATIBLE("maxim,max11617", max11617), + MAX1363_COMPATIBLE("maxim,max11644", max11644), + MAX1363_COMPATIBLE("maxim,max11645", max11645), + MAX1363_COMPATIBLE("maxim,max11646", max11646), + MAX1363_COMPATIBLE("maxim,max11647", max11647), + { } +}; +MODULE_DEVICE_TABLE(of, max1363_of_match); + +static int max1363_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + int ret; + struct max1363_state *st; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(&client->dev, + sizeof(struct max1363_state)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + mutex_init(&st->lock); + ret = devm_regulator_get_enable(&client->dev, "vcc"); + if (ret) + return ret; + + st->chip_info = i2c_get_match_data(client); + st->client = client; + + ret = devm_regulator_get_enable_read_voltage(&client->dev, "vref"); + if (ret < 0 && ret != -ENODEV) + return ret; + + + st->vref_uv = ret == -ENODEV ? st->chip_info->int_vref_mv * 1000 : ret; + + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + st->send = i2c_master_send; + st->recv = i2c_master_recv; + } else if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE) + && st->chip_info->bits == 8) { + st->send = max1363_smbus_send; + st->recv = max1363_smbus_recv; + } else { + return -EOPNOTSUPP; + } + + ret = max1363_alloc_scan_masks(indio_dev); + if (ret) + return ret; + + indio_dev->name = id->name; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + indio_dev->info = st->chip_info->info; + indio_dev->modes = INDIO_DIRECT_MODE; + ret = max1363_initial_setup(st); + if (ret < 0) + return ret; + + ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, NULL, + &max1363_trigger_handler, NULL); + if (ret) + return ret; + + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, st->client->irq, + NULL, + &max1363_event_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "max1363_event", + indio_dev); + + if (ret) + return ret; + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +#define MAX1363_ID_TABLE(_name, cfg) { \ + .name = _name, \ + .driver_data = (kernel_ulong_t)&max1363_chip_info_tbl[cfg], \ +} + +static const struct i2c_device_id max1363_id[] = { + MAX1363_ID_TABLE("max1361", max1361), + MAX1363_ID_TABLE("max1362", max1362), + MAX1363_ID_TABLE("max1363", max1363), + MAX1363_ID_TABLE("max1364", max1364), + MAX1363_ID_TABLE("max1036", max1036), + MAX1363_ID_TABLE("max1037", max1037), + MAX1363_ID_TABLE("max1038", max1038), + MAX1363_ID_TABLE("max1039", max1039), + MAX1363_ID_TABLE("max1136", max1136), + MAX1363_ID_TABLE("max1137", max1137), + MAX1363_ID_TABLE("max1138", max1138), + MAX1363_ID_TABLE("max1139", max1139), + MAX1363_ID_TABLE("max1236", max1236), + MAX1363_ID_TABLE("max1237", max1237), + MAX1363_ID_TABLE("max1238", max1238), + MAX1363_ID_TABLE("max1239", max1239), + MAX1363_ID_TABLE("max11600", max11600), + MAX1363_ID_TABLE("max11601", max11601), + MAX1363_ID_TABLE("max11602", max11602), + MAX1363_ID_TABLE("max11603", max11603), + MAX1363_ID_TABLE("max11604", max11604), + MAX1363_ID_TABLE("max11605", max11605), + MAX1363_ID_TABLE("max11606", max11606), + MAX1363_ID_TABLE("max11607", max11607), + MAX1363_ID_TABLE("max11608", max11608), + MAX1363_ID_TABLE("max11609", max11609), + MAX1363_ID_TABLE("max11610", max11610), + MAX1363_ID_TABLE("max11611", max11611), + MAX1363_ID_TABLE("max11612", max11612), + MAX1363_ID_TABLE("max11613", max11613), + MAX1363_ID_TABLE("max11614", max11614), + MAX1363_ID_TABLE("max11615", max11615), + MAX1363_ID_TABLE("max11616", max11616), + MAX1363_ID_TABLE("max11617", max11617), + MAX1363_ID_TABLE("max11644", max11644), + MAX1363_ID_TABLE("max11645", max11645), + MAX1363_ID_TABLE("max11646", max11646), + MAX1363_ID_TABLE("max11647", max11647), + { } +}; + +MODULE_DEVICE_TABLE(i2c, max1363_id); + +static struct i2c_driver max1363_driver = { + .driver = { + .name = "max1363", + .of_match_table = max1363_of_match, + }, + .probe = max1363_probe, + .id_table = max1363_id, +}; +module_i2c_driver(max1363_driver); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("Maxim 1363 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/max14001.c b/drivers/iio/adc/max14001.c new file mode 100644 index 000000000..58adeac62 --- /dev/null +++ b/drivers/iio/adc/max14001.c @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +/* + * Analog Devices MAX14001/MAX14002 ADC driver + * + * Copyright (C) 2023-2025 Analog Devices Inc. + * Copyright (C) 2023 Kim Seer Paller + * Copyright (c) 2025 Marilene Andrade Garcia + * + * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/MAX14001-MAX14002.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* MAX14001 Registers Address */ +#define MAX14001_REG_ADC 0x00 +#define MAX14001_REG_FADC 0x01 +#define MAX14001_REG_FLAGS 0x02 +#define MAX14001_REG_FLTEN 0x03 +#define MAX14001_REG_THL 0x04 +#define MAX14001_REG_THU 0x05 +#define MAX14001_REG_INRR 0x06 +#define MAX14001_REG_INRT 0x07 +#define MAX14001_REG_INRP 0x08 +#define MAX14001_REG_CFG 0x09 +#define MAX14001_REG_ENBL 0x0A +#define MAX14001_REG_ACT 0x0B +#define MAX14001_REG_WEN 0x0C + +#define MAX14001_REG_VERIFICATION(x) ((x) + 0x10) + +#define MAX14001_REG_CFG_BIT_EXRF BIT(5) + +#define MAX14001_REG_WEN_VALUE_WRITE 0x294 + +#define MAX14001_MASK_ADDR GENMASK(15, 11) +#define MAX14001_MASK_WR BIT(10) +#define MAX14001_MASK_DATA GENMASK(9, 0) + +struct max14001_state { + const struct max14001_chip_info *chip_info; + struct spi_device *spi; + struct regmap *regmap; + int vref_mV; + bool spi_hw_has_lsb_first; + + /* + * The following buffers will be bit-reversed during device + * communication, because the device transmits and receives data + * LSB-first. + * DMA (thus cache coherency maintenance) requires the transfer + * buffers to live in their own cache lines. + */ + union { + __be16 be; + __le16 le; + } spi_tx_buffer __aligned(IIO_DMA_MINALIGN); + + union { + __be16 be; + __le16 le; + } spi_rx_buffer; +}; + +struct max14001_chip_info { + const char *name; +}; + +static int max14001_read(void *context, unsigned int reg, unsigned int *val) +{ + struct max14001_state *st = context; + struct spi_transfer xfers[] = { + { + .tx_buf = &st->spi_tx_buffer, + .len = sizeof(st->spi_tx_buffer), + .cs_change = 1, + }, { + .rx_buf = &st->spi_rx_buffer, + .len = sizeof(st->spi_rx_buffer), + }, + }; + int ret; + unsigned int addr, data; + + /* + * Prepare SPI transmit buffer 16 bit-value and reverse bit order + * to align with the LSB-first input on SDI port in order to meet + * the device communication requirements. If the controller supports + * SPI_LSB_FIRST, this step will be handled by the SPI controller. + */ + addr = FIELD_PREP(MAX14001_MASK_ADDR, reg); + + if (st->spi_hw_has_lsb_first) + st->spi_tx_buffer.le = cpu_to_le16(addr); + else + st->spi_tx_buffer.be = cpu_to_be16(bitrev16(addr)); + + ret = spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers)); + if (ret) + return ret; + + /* + * Convert received 16-bit value to cpu-endian format and reverse + * bit order. If the controller supports SPI_LSB_FIRST, this step + * will be handled by the SPI controller. + */ + if (st->spi_hw_has_lsb_first) + data = le16_to_cpu(st->spi_rx_buffer.le); + else + data = bitrev16(be16_to_cpu(st->spi_rx_buffer.be)); + + *val = FIELD_GET(MAX14001_MASK_DATA, data); + + return 0; +} + +static int max14001_write(struct max14001_state *st, unsigned int reg, unsigned int val) +{ + unsigned int addr; + + /* + * Prepare SPI transmit buffer 16 bit-value and reverse bit order + * to align with the LSB-first input on SDI port in order to meet + * the device communication requirements. If the controller supports + * SPI_LSB_FIRST, this step will be handled by the SPI controller. + */ + addr = FIELD_PREP(MAX14001_MASK_ADDR, reg) | + FIELD_PREP(MAX14001_MASK_WR, 1) | + FIELD_PREP(MAX14001_MASK_DATA, val); + + if (st->spi_hw_has_lsb_first) + st->spi_tx_buffer.le = cpu_to_le16(addr); + else + st->spi_tx_buffer.be = cpu_to_be16(bitrev16(addr)); + + return spi_write(st->spi, &st->spi_tx_buffer, sizeof(st->spi_tx_buffer)); +} + +static int max14001_write_single_reg(void *context, unsigned int reg, unsigned int val) +{ + struct max14001_state *st = context; + int ret; + + /* Enable writing to the SPI register. */ + ret = max14001_write(st, MAX14001_REG_WEN, MAX14001_REG_WEN_VALUE_WRITE); + if (ret) + return ret; + + /* Writing data into SPI register. */ + ret = max14001_write(st, reg, val); + if (ret) + return ret; + + /* Disable writing to the SPI register. */ + return max14001_write(st, MAX14001_REG_WEN, 0); +} + +static int max14001_write_verification_reg(struct max14001_state *st, unsigned int reg) +{ + unsigned int val; + int ret; + + ret = regmap_read(st->regmap, reg, &val); + if (ret) + return ret; + + return max14001_write(st, MAX14001_REG_VERIFICATION(reg), val); +} + +static int max14001_disable_mv_fault(struct max14001_state *st) +{ + unsigned int reg; + int ret; + + /* Enable writing to the SPI registers. */ + ret = max14001_write(st, MAX14001_REG_WEN, MAX14001_REG_WEN_VALUE_WRITE); + if (ret) + return ret; + + /* + * Reads all registers and writes the values to their appropriate + * verification registers to clear the Memory Validation fault. + */ + for (reg = MAX14001_REG_FLTEN; reg <= MAX14001_REG_ENBL; reg++) { + ret = max14001_write_verification_reg(st, reg); + if (ret) + return ret; + } + + /* Disable writing to the SPI registers. */ + return max14001_write(st, MAX14001_REG_WEN, 0); +} + +static int max14001_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + struct max14001_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); +} + +static int max14001_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max14001_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(st->regmap, MAX14001_REG_ADC, val); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = st->vref_mV; + *val2 = 10; + + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static const struct regmap_range max14001_regmap_rd_range[] = { + regmap_reg_range(MAX14001_REG_ADC, MAX14001_REG_ENBL), + regmap_reg_range(MAX14001_REG_WEN, MAX14001_REG_WEN), + regmap_reg_range(MAX14001_REG_VERIFICATION(MAX14001_REG_FLTEN), + MAX14001_REG_VERIFICATION(MAX14001_REG_ENBL)), +}; + +static const struct regmap_access_table max14001_regmap_rd_table = { + .yes_ranges = max14001_regmap_rd_range, + .n_yes_ranges = ARRAY_SIZE(max14001_regmap_rd_range), +}; + +static const struct regmap_range max14001_regmap_wr_range[] = { + regmap_reg_range(MAX14001_REG_FLTEN, MAX14001_REG_WEN), + regmap_reg_range(MAX14001_REG_VERIFICATION(MAX14001_REG_FLTEN), + MAX14001_REG_VERIFICATION(MAX14001_REG_ENBL)), +}; + +static const struct regmap_access_table max14001_regmap_wr_table = { + .yes_ranges = max14001_regmap_wr_range, + .n_yes_ranges = ARRAY_SIZE(max14001_regmap_wr_range), +}; + +static const struct regmap_config max14001_regmap_config = { + .reg_read = max14001_read, + .reg_write = max14001_write_single_reg, + .max_register = MAX14001_REG_VERIFICATION(MAX14001_REG_ENBL), + .rd_table = &max14001_regmap_rd_table, + .wr_table = &max14001_regmap_wr_table, +}; + +static const struct iio_info max14001_info = { + .read_raw = max14001_read_raw, + .debugfs_reg_access = max14001_debugfs_reg_access, +}; + +static const struct iio_chan_spec max14001_channel[] = { + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static int max14001_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct max14001_state *st; + int ret; + bool use_ext_vrefin = false; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + st->spi_hw_has_lsb_first = spi->mode & SPI_LSB_FIRST; + st->chip_info = spi_get_device_match_data(spi); + if (!st->chip_info) + return -EINVAL; + + indio_dev->name = st->chip_info->name; + indio_dev->info = &max14001_info; + indio_dev->channels = max14001_channel; + indio_dev->num_channels = ARRAY_SIZE(max14001_channel); + indio_dev->modes = INDIO_DIRECT_MODE; + + st->regmap = devm_regmap_init(dev, NULL, st, &max14001_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), "Failed to initialize regmap\n"); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable Vdd supply\n"); + + ret = devm_regulator_get_enable(dev, "vddl"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable Vddl supply\n"); + + ret = devm_regulator_get_enable_read_voltage(dev, "refin"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "Failed to get REFIN voltage\n"); + + if (ret == -ENODEV) + ret = 1250000; + else + use_ext_vrefin = true; + st->vref_mV = ret / (MICRO / MILLI); + + if (use_ext_vrefin) { + /* + * Configure the MAX14001/MAX14002 to use an external voltage + * reference source by setting the bit 5 of the configuration register. + */ + ret = regmap_set_bits(st->regmap, MAX14001_REG_CFG, + MAX14001_REG_CFG_BIT_EXRF); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set External REFIN in Configuration Register\n"); + } + + ret = max14001_disable_mv_fault(st); + if (ret) + return dev_err_probe(dev, ret, "Failed to disable MV Fault\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static struct max14001_chip_info max14001_chip_info = { + .name = "max14001", +}; + +static struct max14001_chip_info max14002_chip_info = { + .name = "max14002", +}; + +static const struct spi_device_id max14001_id_table[] = { + { .name = "max14001", .driver_data = (kernel_ulong_t)&max14001_chip_info }, + { .name = "max14002", .driver_data = (kernel_ulong_t)&max14002_chip_info }, + { } +}; + +static const struct of_device_id max14001_of_match[] = { + { .compatible = "adi,max14001", .data = &max14001_chip_info }, + { .compatible = "adi,max14002", .data = &max14002_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, max14001_of_match); + +static struct spi_driver max14001_driver = { + .driver = { + .name = "max14001", + .of_match_table = max14001_of_match, + }, + .probe = max14001_probe, + .id_table = max14001_id_table, +}; +module_spi_driver(max14001_driver); + +MODULE_AUTHOR("Kim Seer Paller "); +MODULE_AUTHOR("Marilene Andrade Garcia "); +MODULE_DESCRIPTION("Analog Devices MAX14001/MAX14002 ADCs driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/max34408.c b/drivers/iio/adc/max34408.c new file mode 100644 index 000000000..c96dfed63 --- /dev/null +++ b/drivers/iio/adc/max34408.c @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * IIO driver for Maxim MAX34409/34408 ADC, 4-Channels/2-Channels, 8bits, I2C + * + * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/MAX34408-MAX34409.pdf + * + * TODO: ALERT interrupt, Overcurrent delay, Shutdown delay + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define MAX34408_STATUS_REG 0x0 +#define MAX34408_CONTROL_REG 0x1 +#define MAX34408_OCDELAY_REG 0x2 +#define MAX34408_SDDELAY_REG 0x3 + +#define MAX34408_ADC1_REG 0x4 +#define MAX34408_ADC2_REG 0x5 +/* ADC3 & ADC4 always returns 0x0 on 34408 */ +#define MAX34409_ADC3_REG 0x6 +#define MAX34409_ADC4_REG 0x7 + +#define MAX34408_OCT1_REG 0x8 +#define MAX34408_OCT2_REG 0x9 +#define MAX34409_OCT3_REG 0xA +#define MAX34409_OCT4_REG 0xB + +#define MAX34408_DID_REG 0xC +#define MAX34408_DCYY_REG 0xD +#define MAX34408_DCWW_REG 0xE + +/* Bit masks for status register */ +#define MAX34408_STATUS_OC_MSK GENMASK(1, 0) +#define MAX34409_STATUS_OC_MSK GENMASK(3, 0) +#define MAX34408_STATUS_SHTDN BIT(4) +#define MAX34408_STATUS_ENA BIT(5) + +/* Bit masks for control register */ +#define MAX34408_CONTROL_AVG0 BIT(0) +#define MAX34408_CONTROL_AVG1 BIT(1) +#define MAX34408_CONTROL_AVG2 BIT(2) +#define MAX34408_CONTROL_ALERT BIT(3) + +#define MAX34408_DEFAULT_AVG 0x4 + +/* Bit masks for over current delay */ +#define MAX34408_OCDELAY_OCD_MSK GENMASK(6, 0) +#define MAX34408_OCDELAY_RESET BIT(7) + +/* Bit masks for shutdown delay */ +#define MAX34408_SDDELAY_SHD_MSK GENMASK(6, 0) +#define MAX34408_SDDELAY_RESET BIT(7) + +#define MAX34408_DEFAULT_RSENSE 1000 + +/** + * struct max34408_data - max34408/max34409 specific data. + * @regmap: device register map. + * @dev: max34408 device. + * @lock: lock for protecting access to device hardware registers, mostly + * for read modify write cycles for control registers. + * @input_rsense: Rsense values in uOhm, will be overwritten by + * values from channel nodes. + */ +struct max34408_data { + struct regmap *regmap; + struct device *dev; + struct mutex lock; + u32 input_rsense[4]; +}; + +static const struct regmap_config max34408_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX34408_DCWW_REG, +}; + +struct max34408_adc_model_data { + const char *model_name; + const struct iio_chan_spec *channels; + const int num_channels; +}; + +#define MAX34008_CHANNEL(_index, _address) \ + { \ + .type = IIO_CURRENT, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .channel = (_index), \ + .address = (_address), \ + .indexed = 1, \ + } + +static const struct iio_chan_spec max34408_channels[] = { + MAX34008_CHANNEL(0, MAX34408_ADC1_REG), + MAX34008_CHANNEL(1, MAX34408_ADC2_REG), +}; + +static const struct iio_chan_spec max34409_channels[] = { + MAX34008_CHANNEL(0, MAX34408_ADC1_REG), + MAX34008_CHANNEL(1, MAX34408_ADC2_REG), + MAX34008_CHANNEL(2, MAX34409_ADC3_REG), + MAX34008_CHANNEL(3, MAX34409_ADC4_REG), +}; + +static int max34408_read_adc_avg(struct max34408_data *max34408, + const struct iio_chan_spec *chan, int *val) +{ + unsigned int ctrl; + int rc; + + guard(mutex)(&max34408->lock); + rc = regmap_read(max34408->regmap, MAX34408_CONTROL_REG, (u32 *)&ctrl); + if (rc) + return rc; + + /* set averaging (0b100) default values*/ + rc = regmap_write(max34408->regmap, MAX34408_CONTROL_REG, + MAX34408_DEFAULT_AVG); + if (rc) { + dev_err(max34408->dev, + "Error (%d) writing control register\n", rc); + return rc; + } + + rc = regmap_read(max34408->regmap, chan->address, val); + if (rc) + return rc; + + /* back to old values */ + rc = regmap_write(max34408->regmap, MAX34408_CONTROL_REG, ctrl); + if (rc) + dev_err(max34408->dev, + "Error (%d) writing control register\n", rc); + + return rc; +} + +static int max34408_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max34408_data *max34408 = iio_priv(indio_dev); + int rc; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + rc = max34408_read_adc_avg(max34408, chan, val); + if (rc) + return rc; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* + * calculate current for 8bit ADC with Rsense + * value. + * 10 mV * 1000 / Rsense uOhm = max current + * (max current * adc val * 1000) / (2^8 - 1) mA + */ + *val = 10000 / max34408->input_rsense[chan->channel]; + *val2 = 8; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static const struct iio_info max34408_info = { + .read_raw = max34408_read_raw, +}; + +static const struct max34408_adc_model_data max34408_model_data = { + .model_name = "max34408", + .channels = max34408_channels, + .num_channels = 2, +}; + +static const struct max34408_adc_model_data max34409_model_data = { + .model_name = "max34409", + .channels = max34409_channels, + .num_channels = 4, +}; + +static int max34408_probe(struct i2c_client *client) +{ + const struct max34408_adc_model_data *model_data; + struct device *dev = &client->dev; + struct max34408_data *max34408; + struct fwnode_handle *node; + struct iio_dev *indio_dev; + struct regmap *regmap; + int rc, i = 0; + + model_data = i2c_get_match_data(client); + if (!model_data) + return -EINVAL; + + regmap = devm_regmap_init_i2c(client, &max34408_regmap_config); + if (IS_ERR(regmap)) { + dev_err_probe(dev, PTR_ERR(regmap), + "regmap_init failed\n"); + return PTR_ERR(regmap); + } + + indio_dev = devm_iio_device_alloc(dev, sizeof(*max34408)); + if (!indio_dev) + return -ENOMEM; + + max34408 = iio_priv(indio_dev); + max34408->regmap = regmap; + max34408->dev = dev; + mutex_init(&max34408->lock); + + device_for_each_child_node(dev, node) { + fwnode_property_read_u32(node, "maxim,rsense-val-micro-ohms", + &max34408->input_rsense[i]); + i++; + } + + /* disable ALERT and averaging */ + rc = regmap_write(max34408->regmap, MAX34408_CONTROL_REG, 0x0); + if (rc) + return rc; + + indio_dev->channels = model_data->channels; + indio_dev->num_channels = model_data->num_channels; + indio_dev->name = model_data->model_name; + + indio_dev->info = &max34408_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id max34408_of_match[] = { + { + .compatible = "maxim,max34408", + .data = &max34408_model_data, + }, + { + .compatible = "maxim,max34409", + .data = &max34409_model_data, + }, + { } +}; +MODULE_DEVICE_TABLE(of, max34408_of_match); + +static const struct i2c_device_id max34408_id[] = { + { .name = "max34408", .driver_data = (kernel_ulong_t)&max34408_model_data }, + { .name = "max34409", .driver_data = (kernel_ulong_t)&max34409_model_data }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max34408_id); + +static struct i2c_driver max34408_driver = { + .driver = { + .name = "max34408", + .of_match_table = max34408_of_match, + }, + .probe = max34408_probe, + .id_table = max34408_id, +}; +module_i2c_driver(max34408_driver); + +MODULE_AUTHOR("Ivan Mikhaylov "); +MODULE_DESCRIPTION("Maxim MAX34408/34409 ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/max77541-adc.c b/drivers/iio/adc/max77541-adc.c new file mode 100644 index 000000000..6e68cad5b --- /dev/null +++ b/drivers/iio/adc/max77541-adc.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022 Analog Devices, Inc. + * ADI MAX77541 ADC Driver with IIO interface + */ + +#include +#include +#include +#include +#include + +#include + +enum max77541_adc_range { + LOW_RANGE, + MID_RANGE, + HIGH_RANGE, +}; + +enum max77541_adc_channel { + MAX77541_ADC_VSYS_V, + MAX77541_ADC_VOUT1_V, + MAX77541_ADC_VOUT2_V, + MAX77541_ADC_TEMP, +}; + +static int max77541_adc_offset(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + switch (chan->channel) { + case MAX77541_ADC_TEMP: + *val = DIV_ROUND_CLOSEST(ABSOLUTE_ZERO_MILLICELSIUS, 1725); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int max77541_adc_scale(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + struct regmap **regmap = iio_priv(indio_dev); + unsigned int reg_val; + int ret; + + switch (chan->channel) { + case MAX77541_ADC_VSYS_V: + *val = 25; + return IIO_VAL_INT; + case MAX77541_ADC_VOUT1_V: + case MAX77541_ADC_VOUT2_V: + ret = regmap_read(*regmap, MAX77541_REG_M2_CFG1, ®_val); + if (ret) + return ret; + + reg_val = FIELD_GET(MAX77541_BITS_MX_CFG1_RNG, reg_val); + switch (reg_val) { + case LOW_RANGE: + *val = 6; + *val2 = 250000; + break; + case MID_RANGE: + *val = 12; + *val2 = 500000; + break; + case HIGH_RANGE: + *val = 25; + return IIO_VAL_INT; + default: + return -EINVAL; + } + + return IIO_VAL_INT_PLUS_MICRO; + case MAX77541_ADC_TEMP: + *val = 1725; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int max77541_adc_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct regmap **regmap = iio_priv(indio_dev); + int ret; + + ret = regmap_read(*regmap, chan->address, val); + if (ret) + return ret; + + return IIO_VAL_INT; +} + +#define MAX77541_ADC_CHANNEL_V(_channel, _name, _type, _reg) \ + { \ + .type = _type, \ + .indexed = 1, \ + .channel = _channel, \ + .address = _reg, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = _name, \ + } + +#define MAX77541_ADC_CHANNEL_TEMP(_channel, _name, _type, _reg) \ + { \ + .type = _type, \ + .indexed = 1, \ + .channel = _channel, \ + .address = _reg, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) |\ + BIT(IIO_CHAN_INFO_OFFSET),\ + .datasheet_name = _name, \ + } + +static const struct iio_chan_spec max77541_adc_channels[] = { + MAX77541_ADC_CHANNEL_V(MAX77541_ADC_VSYS_V, "vsys_v", IIO_VOLTAGE, + MAX77541_REG_ADC_DATA_CH1), + MAX77541_ADC_CHANNEL_V(MAX77541_ADC_VOUT1_V, "vout1_v", IIO_VOLTAGE, + MAX77541_REG_ADC_DATA_CH2), + MAX77541_ADC_CHANNEL_V(MAX77541_ADC_VOUT2_V, "vout2_v", IIO_VOLTAGE, + MAX77541_REG_ADC_DATA_CH3), + MAX77541_ADC_CHANNEL_TEMP(MAX77541_ADC_TEMP, "temp", IIO_TEMP, + MAX77541_REG_ADC_DATA_CH6), +}; + +static int max77541_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_OFFSET: + return max77541_adc_offset(indio_dev, chan, val, val2); + case IIO_CHAN_INFO_SCALE: + return max77541_adc_scale(indio_dev, chan, val, val2); + case IIO_CHAN_INFO_RAW: + return max77541_adc_raw(indio_dev, chan, val); + default: + return -EINVAL; + } +} + +static const struct iio_info max77541_adc_info = { + .read_raw = max77541_adc_read_raw, +}; + +static int max77541_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct regmap **regmap; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*regmap)); + if (!indio_dev) + return -ENOMEM; + + regmap = iio_priv(indio_dev); + + *regmap = dev_get_regmap(dev->parent, NULL); + indio_dev->modes = INDIO_DIRECT_MODE; + + indio_dev->name = "max77541"; + indio_dev->info = &max77541_adc_info; + indio_dev->channels = max77541_adc_channels; + indio_dev->num_channels = ARRAY_SIZE(max77541_adc_channels); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct platform_device_id max77541_adc_platform_id[] = { + { .name = "max77541-adc" }, + { } +}; +MODULE_DEVICE_TABLE(platform, max77541_adc_platform_id); + +static struct platform_driver max77541_adc_driver = { + .driver = { + .name = "max77541-adc", + }, + .probe = max77541_adc_probe, + .id_table = max77541_adc_platform_id, +}; +module_platform_driver(max77541_adc_driver); + +MODULE_AUTHOR("Okan Sahin "); +MODULE_DESCRIPTION("MAX77541 ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/max9611.c b/drivers/iio/adc/max9611.c new file mode 100644 index 000000000..45cea84c5 --- /dev/null +++ b/drivers/iio/adc/max9611.c @@ -0,0 +1,562 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * iio/adc/max9611.c + * + * Maxim max9611/max9612 high side current sense amplifier with + * 12-bit ADC interface. + * + * Copyright (C) 2017 Jacopo Mondi + */ + +/* + * This driver supports input common-mode voltage, current-sense + * amplifier with programmable gains and die temperature reading from + * Maxim max9611/max9612. + * + * Op-amp, analog comparator, and watchdog functionalities are not + * supported by this driver. + */ + +#include +#include +#include +#include +#include +#include + +/* max9611 register addresses */ +#define MAX9611_REG_CSA_DATA 0x00 +#define MAX9611_REG_RS_DATA 0x02 +#define MAX9611_REG_TEMP_DATA 0x08 +#define MAX9611_REG_CTRL1 0x0a +#define MAX9611_REG_CTRL2 0x0b + +/* max9611 REG1 mux configuration options */ +#define MAX9611_MUX_MASK GENMASK(3, 0) +#define MAX9611_MUX_SENSE_1x 0x00 +#define MAX9611_MUX_SENSE_4x 0x01 +#define MAX9611_MUX_SENSE_8x 0x02 +#define MAX9611_INPUT_VOLT 0x03 +#define MAX9611_MUX_TEMP 0x06 + +/* max9611 voltage (both csa and input) helper macros */ +#define MAX9611_VOLTAGE_SHIFT 0x04 +#define MAX9611_VOLTAGE_RAW(_r) ((_r) >> MAX9611_VOLTAGE_SHIFT) + +/* + * max9611 current sense amplifier voltage output: + * LSB and offset values depends on selected gain (1x, 4x, 8x) + * + * GAIN LSB (nV) OFFSET (LSB steps) + * 1x 107500 1 + * 4x 26880 1 + * 8x 13440 3 + * + * The complete formula to calculate current sense voltage is: + * (((adc_read >> 4) - offset) / ((1 / LSB) * 10^-3) + */ +#define MAX9611_CSA_1X_LSB_nV 107500 +#define MAX9611_CSA_4X_LSB_nV 26880 +#define MAX9611_CSA_8X_LSB_nV 13440 + +#define MAX9611_CSA_1X_OFFS_RAW 1 +#define MAX9611_CSA_4X_OFFS_RAW 1 +#define MAX9611_CSA_8X_OFFS_RAW 3 + +/* + * max9611 common input mode (CIM): LSB is 14mV, with 14mV offset at 25 C + * + * The complete formula to calculate input common voltage is: + * (((adc_read >> 4) * 1000) - offset) / (1 / 14 * 1000) + */ +#define MAX9611_CIM_LSB_mV 14 +#define MAX9611_CIM_OFFSET_RAW 1 + +/* + * max9611 temperature reading: LSB is 480 milli degrees Celsius + * + * The complete formula to calculate temperature is: + * ((adc_read >> 7) * 1000) / (1 / 480 * 1000) + */ +#define MAX9611_TEMP_MAX_POS 0x7f80 +#define MAX9611_TEMP_MAX_NEG 0xff80 +#define MAX9611_TEMP_MIN_NEG 0xd980 +#define MAX9611_TEMP_MASK GENMASK(15, 7) +#define MAX9611_TEMP_SHIFT 0x07 +#define MAX9611_TEMP_RAW(_r) ((_r) >> MAX9611_TEMP_SHIFT) +#define MAX9611_TEMP_SCALE_NUM 1000000 +#define MAX9611_TEMP_SCALE_DIV 2083 + +/* + * Conversion time is 2 ms (typically) at Ta=25 degreeC + * No maximum value is known, so play it safe. + */ +#define MAX9611_CONV_TIME_US_RANGE 3000, 3300 + +struct max9611_dev { + struct device *dev; + struct i2c_client *i2c_client; + struct mutex lock; + unsigned int shunt_resistor_uohm; +}; + +enum max9611_conf_ids { + CONF_SENSE_1x, + CONF_SENSE_4x, + CONF_SENSE_8x, + CONF_IN_VOLT, + CONF_TEMP, +}; + +/* + * max9611_mux_conf - associate ADC mux configuration with register address + * where data shall be read from + */ +static const unsigned int max9611_mux_conf[][2] = { + [CONF_SENSE_1x] = { MAX9611_MUX_SENSE_1x, MAX9611_REG_CSA_DATA }, + [CONF_SENSE_4x] = { MAX9611_MUX_SENSE_4x, MAX9611_REG_CSA_DATA }, + [CONF_SENSE_8x] = { MAX9611_MUX_SENSE_8x, MAX9611_REG_CSA_DATA }, + [CONF_IN_VOLT] = { MAX9611_INPUT_VOLT, MAX9611_REG_RS_DATA }, + [CONF_TEMP] = { MAX9611_MUX_TEMP, MAX9611_REG_TEMP_DATA }, +}; + +enum max9611_csa_gain { + CSA_GAIN_1x = CONF_SENSE_1x, + CSA_GAIN_4x = CONF_SENSE_4x, + CSA_GAIN_8x = CONF_SENSE_8x, +}; + +enum max9611_csa_gain_params { + CSA_GAIN_LSB_nV, + CSA_GAIN_OFFS_RAW, +}; + +/* + * max9611_csa_gain_conf - associate gain multiplier with LSB and + * offset values. + * + * Group together parameters associated with configurable gain + * on current sense amplifier path to ADC interface. + * Current sense read routine adjusts gain until it gets a meaningful + * value; use this structure to retrieve the correct LSB and offset values. + */ +static const unsigned int max9611_gain_conf[][2] = { + [CSA_GAIN_1x] = { MAX9611_CSA_1X_LSB_nV, MAX9611_CSA_1X_OFFS_RAW, }, + [CSA_GAIN_4x] = { MAX9611_CSA_4X_LSB_nV, MAX9611_CSA_4X_OFFS_RAW, }, + [CSA_GAIN_8x] = { MAX9611_CSA_8X_LSB_nV, MAX9611_CSA_8X_OFFS_RAW, }, +}; + +enum max9611_chan_addrs { + MAX9611_CHAN_VOLTAGE_INPUT, + MAX9611_CHAN_VOLTAGE_SENSE, + MAX9611_CHAN_TEMPERATURE, + MAX9611_CHAN_CURRENT_LOAD, + MAX9611_CHAN_POWER_LOAD, +}; + +static const struct iio_chan_spec max9611_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .address = MAX9611_CHAN_TEMPERATURE, + }, + { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = MAX9611_CHAN_VOLTAGE_SENSE, + .indexed = 1, + .channel = 0, + }, + { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = MAX9611_CHAN_VOLTAGE_INPUT, + .indexed = 1, + .channel = 1, + }, + { + .type = IIO_CURRENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = MAX9611_CHAN_CURRENT_LOAD, + }, + { + .type = IIO_POWER, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = MAX9611_CHAN_POWER_LOAD + }, +}; + +/** + * max9611_read_single() - read a single value from ADC interface + * + * Data registers are 16 bit long, spread between two 8 bit registers + * with consecutive addresses. + * Configure ADC mux first, then read register at address "reg_addr". + * The smbus_read_word routine asks for 16 bits and the ADC is kind enough + * to return values from "reg_addr" and "reg_addr + 1" consecutively. + * Data are transmitted with big-endian ordering: MSB arrives first. + * + * @max9611: max9611 device + * @selector: index for mux and register configuration + * @raw_val: the value returned from ADC + */ +static int max9611_read_single(struct max9611_dev *max9611, + enum max9611_conf_ids selector, + u16 *raw_val) +{ + int ret; + + u8 mux_conf = max9611_mux_conf[selector][0] & MAX9611_MUX_MASK; + u8 reg_addr = max9611_mux_conf[selector][1]; + + /* + * Keep mutex lock held during read-write to avoid mux register + * (CTRL1) re-configuration. + */ + mutex_lock(&max9611->lock); + ret = i2c_smbus_write_byte_data(max9611->i2c_client, + MAX9611_REG_CTRL1, mux_conf); + if (ret) { + dev_err(max9611->dev, "i2c write byte failed: 0x%2x - 0x%2x\n", + MAX9611_REG_CTRL1, mux_conf); + mutex_unlock(&max9611->lock); + return ret; + } + + /* need a delay here to make register configuration stabilize. */ + + usleep_range(MAX9611_CONV_TIME_US_RANGE); + + ret = i2c_smbus_read_word_swapped(max9611->i2c_client, reg_addr); + if (ret < 0) { + dev_err(max9611->dev, "i2c read word from 0x%2x failed\n", + reg_addr); + mutex_unlock(&max9611->lock); + return ret; + } + + *raw_val = ret; + mutex_unlock(&max9611->lock); + + return 0; +} + +/** + * max9611_read_csa_voltage() - read current sense amplifier output voltage + * + * Current sense amplifier output voltage is read through a configurable + * 1x, 4x or 8x gain. + * Start with plain 1x gain, and adjust gain control properly until a + * meaningful value is read from ADC output. + * + * @max9611: max9611 device + * @adc_raw: raw value read from ADC output + * @csa_gain: gain configuration option selector + */ +static int max9611_read_csa_voltage(struct max9611_dev *max9611, + u16 *adc_raw, + enum max9611_csa_gain *csa_gain) +{ + enum max9611_conf_ids gain_selectors[] = { + CONF_SENSE_1x, + CONF_SENSE_4x, + CONF_SENSE_8x + }; + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(gain_selectors); ++i) { + ret = max9611_read_single(max9611, gain_selectors[i], adc_raw); + if (ret) + return ret; + + if (*adc_raw > 0) { + *csa_gain = (enum max9611_csa_gain)gain_selectors[i]; + return 0; + } + } + + return -EIO; +} + +static int max9611_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max9611_dev *dev = iio_priv(indio_dev); + enum max9611_csa_gain gain_selector; + const unsigned int *csa_gain; + u16 adc_data; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + + switch (chan->address) { + case MAX9611_CHAN_TEMPERATURE: + ret = max9611_read_single(dev, CONF_TEMP, + &adc_data); + if (ret) + return -EINVAL; + + *val = MAX9611_TEMP_RAW(adc_data); + return IIO_VAL_INT; + + case MAX9611_CHAN_VOLTAGE_INPUT: + ret = max9611_read_single(dev, CONF_IN_VOLT, + &adc_data); + if (ret) + return -EINVAL; + + *val = MAX9611_VOLTAGE_RAW(adc_data); + return IIO_VAL_INT; + } + + break; + + case IIO_CHAN_INFO_OFFSET: + /* MAX9611_CHAN_VOLTAGE_INPUT */ + *val = MAX9611_CIM_OFFSET_RAW; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + + switch (chan->address) { + case MAX9611_CHAN_TEMPERATURE: + *val = MAX9611_TEMP_SCALE_NUM; + *val2 = MAX9611_TEMP_SCALE_DIV; + + return IIO_VAL_FRACTIONAL; + + case MAX9611_CHAN_VOLTAGE_INPUT: + *val = MAX9611_CIM_LSB_mV; + + return IIO_VAL_INT; + } + + break; + + case IIO_CHAN_INFO_PROCESSED: + + switch (chan->address) { + case MAX9611_CHAN_VOLTAGE_SENSE: + /* + * processed (mV): (raw - offset) * LSB (nV) / 10^6 + * + * Even if max9611 can output raw csa voltage readings, + * use a produced value as scale depends on gain. + */ + ret = max9611_read_csa_voltage(dev, &adc_data, + &gain_selector); + if (ret) + return -EINVAL; + + csa_gain = max9611_gain_conf[gain_selector]; + + adc_data -= csa_gain[CSA_GAIN_OFFS_RAW]; + *val = MAX9611_VOLTAGE_RAW(adc_data) * + csa_gain[CSA_GAIN_LSB_nV]; + *val2 = 1000000; + + return IIO_VAL_FRACTIONAL; + + case MAX9611_CHAN_CURRENT_LOAD: + /* processed (mA): Vcsa (nV) / Rshunt (uOhm) */ + ret = max9611_read_csa_voltage(dev, &adc_data, + &gain_selector); + if (ret) + return -EINVAL; + + csa_gain = max9611_gain_conf[gain_selector]; + + adc_data -= csa_gain[CSA_GAIN_OFFS_RAW]; + *val = MAX9611_VOLTAGE_RAW(adc_data) * + csa_gain[CSA_GAIN_LSB_nV]; + *val2 = dev->shunt_resistor_uohm; + + return IIO_VAL_FRACTIONAL; + + case MAX9611_CHAN_POWER_LOAD: + /* + * processed (mW): Vin (mV) * Vcsa (uV) / + * Rshunt (uOhm) + */ + ret = max9611_read_single(dev, CONF_IN_VOLT, + &adc_data); + if (ret) + return -EINVAL; + + adc_data -= MAX9611_CIM_OFFSET_RAW; + *val = MAX9611_VOLTAGE_RAW(adc_data) * + MAX9611_CIM_LSB_mV; + + ret = max9611_read_csa_voltage(dev, &adc_data, + &gain_selector); + if (ret) + return -EINVAL; + + csa_gain = max9611_gain_conf[gain_selector]; + + /* divide by 10^3 here to avoid 32bit overflow */ + adc_data -= csa_gain[CSA_GAIN_OFFS_RAW]; + *val *= MAX9611_VOLTAGE_RAW(adc_data) * + csa_gain[CSA_GAIN_LSB_nV] / 1000; + *val2 = dev->shunt_resistor_uohm; + + return IIO_VAL_FRACTIONAL; + } + + break; + } + + return -EINVAL; +} + +static ssize_t max9611_shunt_resistor_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct max9611_dev *max9611 = iio_priv(dev_to_iio_dev(dev)); + unsigned int i, r; + + i = max9611->shunt_resistor_uohm / 1000000; + r = max9611->shunt_resistor_uohm % 1000000; + + return sysfs_emit(buf, "%u.%06u\n", i, r); +} + +static IIO_DEVICE_ATTR(in_power_shunt_resistor, 0444, + max9611_shunt_resistor_show, NULL, 0); +static IIO_DEVICE_ATTR(in_current_shunt_resistor, 0444, + max9611_shunt_resistor_show, NULL, 0); + +static struct attribute *max9611_attributes[] = { + &iio_dev_attr_in_power_shunt_resistor.dev_attr.attr, + &iio_dev_attr_in_current_shunt_resistor.dev_attr.attr, + NULL, +}; + +static const struct attribute_group max9611_attribute_group = { + .attrs = max9611_attributes, +}; + +static const struct iio_info indio_info = { + .read_raw = max9611_read_raw, + .attrs = &max9611_attribute_group, +}; + +static int max9611_init(struct max9611_dev *max9611) +{ + struct i2c_client *client = max9611->i2c_client; + u16 regval; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_BYTE | + I2C_FUNC_SMBUS_READ_WORD_DATA)) { + dev_err(max9611->dev, + "I2c adapter does not support smbus write_byte or read_word functionalities: aborting probe.\n"); + return -EINVAL; + } + + /* Make sure die temperature is in range to test communications. */ + ret = max9611_read_single(max9611, CONF_TEMP, ®val); + if (ret) + return ret; + + regval &= MAX9611_TEMP_MASK; + + if ((regval > MAX9611_TEMP_MAX_POS && + regval < MAX9611_TEMP_MIN_NEG) || + regval > MAX9611_TEMP_MAX_NEG) { + dev_err(max9611->dev, + "Invalid value received from ADC 0x%4x: aborting\n", + regval); + return -EIO; + } + + /* Mux shall be zeroed back before applying other configurations */ + ret = i2c_smbus_write_byte_data(max9611->i2c_client, + MAX9611_REG_CTRL1, 0); + if (ret) { + dev_err(max9611->dev, "i2c write byte failed: 0x%2x - 0x%2x\n", + MAX9611_REG_CTRL1, 0); + return ret; + } + + ret = i2c_smbus_write_byte_data(max9611->i2c_client, + MAX9611_REG_CTRL2, 0); + if (ret) { + dev_err(max9611->dev, "i2c write byte failed: 0x%2x - 0x%2x\n", + MAX9611_REG_CTRL2, 0); + return ret; + } + usleep_range(MAX9611_CONV_TIME_US_RANGE); + + return 0; +} + +static const struct of_device_id max9611_of_table[] = { + { .compatible = "maxim,max9611", .data = "max9611" }, + { .compatible = "maxim,max9612", .data = "max9612" }, + { } +}; + +MODULE_DEVICE_TABLE(of, max9611_of_table); +static int max9611_probe(struct i2c_client *client) +{ + const char * const shunt_res_prop = "shunt-resistor-micro-ohms"; + struct max9611_dev *max9611; + struct iio_dev *indio_dev; + struct device *dev = &client->dev; + unsigned int of_shunt; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*max9611)); + if (!indio_dev) + return -ENOMEM; + + i2c_set_clientdata(client, indio_dev); + + max9611 = iio_priv(indio_dev); + max9611->dev = dev; + max9611->i2c_client = client; + mutex_init(&max9611->lock); + + ret = device_property_read_u32(dev, shunt_res_prop, &of_shunt); + if (ret) { + dev_err(dev, "Missing %s property for %pfw node\n", + shunt_res_prop, dev_fwnode(dev)); + return ret; + } + max9611->shunt_resistor_uohm = of_shunt; + + ret = max9611_init(max9611); + if (ret) + return ret; + + indio_dev->name = device_get_match_data(dev); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &indio_info; + indio_dev->channels = max9611_channels; + indio_dev->num_channels = ARRAY_SIZE(max9611_channels); + + return devm_iio_device_register(dev, indio_dev); +} + +static struct i2c_driver max9611_driver = { + .driver = { + .name = "max9611", + .of_match_table = max9611_of_table, + }, + .probe = max9611_probe, +}; +module_i2c_driver(max9611_driver); + +MODULE_AUTHOR("Jacopo Mondi "); +MODULE_DESCRIPTION("Maxim max9611/12 current sense amplifier with 12bit ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/mcp320x.c b/drivers/iio/adc/mcp320x.c new file mode 100644 index 000000000..18cb957e7 --- /dev/null +++ b/drivers/iio/adc/mcp320x.c @@ -0,0 +1,508 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 Oskar Andero + * Copyright (C) 2014 Rose Technology + * Allan Bendorff Jensen + * Soren Andersen + * + * Driver for following ADC chips from Microchip Technology's: + * 10 Bit converter + * MCP3001 + * MCP3002 + * MCP3004 + * MCP3008 + * ------------ + * 12 bit converter + * MCP3201 + * MCP3202 + * MCP3204 + * MCP3208 + * ------------ + * 13 bit converter + * MCP3301 + * ------------ + * 22 bit converter + * MCP3550 + * MCP3551 + * MCP3553 + * + * Datasheet can be found here: + * https://ww1.microchip.com/downloads/en/DeviceDoc/21293C.pdf mcp3001 + * https://ww1.microchip.com/downloads/en/DeviceDoc/21294E.pdf mcp3002 + * https://ww1.microchip.com/downloads/en/DeviceDoc/21295d.pdf mcp3004/08 + * http://ww1.microchip.com/downloads/en/DeviceDoc/21290D.pdf mcp3201 + * http://ww1.microchip.com/downloads/en/DeviceDoc/21034D.pdf mcp3202 + * http://ww1.microchip.com/downloads/en/DeviceDoc/21298c.pdf mcp3204/08 + * https://ww1.microchip.com/downloads/en/DeviceDoc/21700E.pdf mcp3301 + * http://ww1.microchip.com/downloads/en/DeviceDoc/21950D.pdf mcp3550/1/3 + */ + +#include +#include +#include +#include +#include +#include + +enum { + mcp3001, + mcp3002, + mcp3004, + mcp3008, + mcp3201, + mcp3202, + mcp3204, + mcp3208, + mcp3301, + mcp3550_50, + mcp3550_60, + mcp3551, + mcp3553, +}; + +struct mcp320x_chip_info { + const struct iio_chan_spec *channels; + unsigned int num_channels; + unsigned int resolution; + unsigned int conv_time; /* usec */ +}; + +/** + * struct mcp320x - Microchip SPI ADC instance + * @spi: SPI slave (parent of the IIO device) + * @msg: SPI message to select a channel and receive a value from the ADC + * @transfer: SPI transfers used by @msg + * @start_conv_msg: SPI message to start a conversion by briefly asserting CS + * @start_conv_transfer: SPI transfer used by @start_conv_msg + * @reg: regulator generating Vref + * @lock: protects read sequences + * @chip_info: ADC properties + * @tx_buf: buffer for @transfer[0] (not used on single-channel converters) + * @rx_buf: buffer for @transfer[1] + */ +struct mcp320x { + struct spi_device *spi; + struct spi_message msg; + struct spi_transfer transfer[2]; + struct spi_message start_conv_msg; + struct spi_transfer start_conv_transfer; + + struct regulator *reg; + struct mutex lock; + const struct mcp320x_chip_info *chip_info; + + u8 tx_buf __aligned(IIO_DMA_MINALIGN); + u8 rx_buf[4]; +}; + +static int mcp320x_channel_to_tx_data(int device_index, + const unsigned int channel, bool differential) +{ + int start_bit = 1; + + switch (device_index) { + case mcp3002: + case mcp3202: + return ((start_bit << 4) | (!differential << 3) | + (channel << 2)); + case mcp3004: + case mcp3204: + case mcp3008: + case mcp3208: + return ((start_bit << 6) | (!differential << 5) | + (channel << 2)); + default: + return -EINVAL; + } +} + +static int mcp320x_adc_conversion(struct mcp320x *adc, u8 channel, + bool differential, int device_index, int *val) +{ + int ret; + + if (adc->chip_info->conv_time) { + ret = spi_sync(adc->spi, &adc->start_conv_msg); + if (ret < 0) + return ret; + + usleep_range(adc->chip_info->conv_time, + adc->chip_info->conv_time + 100); + } + + memset(&adc->rx_buf, 0, sizeof(adc->rx_buf)); + if (adc->chip_info->num_channels > 1) + adc->tx_buf = mcp320x_channel_to_tx_data(device_index, channel, + differential); + + ret = spi_sync(adc->spi, &adc->msg); + if (ret < 0) + return ret; + + switch (device_index) { + case mcp3001: + *val = (adc->rx_buf[0] << 5 | adc->rx_buf[1] >> 3); + return 0; + case mcp3002: + case mcp3004: + case mcp3008: + *val = (adc->rx_buf[0] << 2 | adc->rx_buf[1] >> 6); + return 0; + case mcp3201: + *val = (adc->rx_buf[0] << 7 | adc->rx_buf[1] >> 1); + return 0; + case mcp3202: + case mcp3204: + case mcp3208: + *val = (adc->rx_buf[0] << 4 | adc->rx_buf[1] >> 4); + return 0; + case mcp3301: + *val = sign_extend32((adc->rx_buf[0] & 0x1f) << 8 + | adc->rx_buf[1], 12); + return 0; + case mcp3550_50: + case mcp3550_60: + case mcp3551: + case mcp3553: { + u32 raw = be32_to_cpup((__be32 *)adc->rx_buf); + + if (!(adc->spi->mode & SPI_CPOL)) + raw <<= 1; /* strip Data Ready bit in SPI mode 0,0 */ + + /* + * If the input is within -vref and vref, bit 21 is the sign. + * Up to 12% overrange or underrange are allowed, in which case + * bit 23 is the sign and bit 0 to 21 is the value. + */ + raw >>= 8; + if (raw & BIT(22) && raw & BIT(23)) + return -EIO; /* cannot have overrange AND underrange */ + else if (raw & BIT(22)) + raw &= ~BIT(22); /* overrange */ + else if (raw & BIT(23) || raw & BIT(21)) + raw |= GENMASK(31, 22); /* underrange or negative */ + + *val = (s32)raw; + return 0; + } + default: + return -EINVAL; + } +} + +static int mcp320x_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct mcp320x *adc = iio_priv(indio_dev); + int ret = -EINVAL; + int device_index = 0; + + mutex_lock(&adc->lock); + + device_index = spi_get_device_id(adc->spi)->driver_data; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = mcp320x_adc_conversion(adc, channel->address, + channel->differential, device_index, val); + if (ret < 0) + goto out; + + ret = IIO_VAL_INT; + break; + + case IIO_CHAN_INFO_SCALE: + ret = regulator_get_voltage(adc->reg); + if (ret < 0) + goto out; + + /* convert regulator output voltage to mV */ + *val = ret / 1000; + *val2 = adc->chip_info->resolution; + ret = IIO_VAL_FRACTIONAL_LOG2; + break; + } + +out: + mutex_unlock(&adc->lock); + + return ret; +} + +#define MCP320X_VOLTAGE_CHANNEL(num) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (num), \ + .address = (num), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \ + } + +#define MCP320X_VOLTAGE_CHANNEL_DIFF(chan1, chan2) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (chan1), \ + .channel2 = (chan2), \ + .address = (chan1), \ + .differential = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \ + } + +static const struct iio_chan_spec mcp3201_channels[] = { + MCP320X_VOLTAGE_CHANNEL_DIFF(0, 1), +}; + +static const struct iio_chan_spec mcp3202_channels[] = { + MCP320X_VOLTAGE_CHANNEL(0), + MCP320X_VOLTAGE_CHANNEL(1), + MCP320X_VOLTAGE_CHANNEL_DIFF(0, 1), + MCP320X_VOLTAGE_CHANNEL_DIFF(1, 0), +}; + +static const struct iio_chan_spec mcp3204_channels[] = { + MCP320X_VOLTAGE_CHANNEL(0), + MCP320X_VOLTAGE_CHANNEL(1), + MCP320X_VOLTAGE_CHANNEL(2), + MCP320X_VOLTAGE_CHANNEL(3), + MCP320X_VOLTAGE_CHANNEL_DIFF(0, 1), + MCP320X_VOLTAGE_CHANNEL_DIFF(1, 0), + MCP320X_VOLTAGE_CHANNEL_DIFF(2, 3), + MCP320X_VOLTAGE_CHANNEL_DIFF(3, 2), +}; + +static const struct iio_chan_spec mcp3208_channels[] = { + MCP320X_VOLTAGE_CHANNEL(0), + MCP320X_VOLTAGE_CHANNEL(1), + MCP320X_VOLTAGE_CHANNEL(2), + MCP320X_VOLTAGE_CHANNEL(3), + MCP320X_VOLTAGE_CHANNEL(4), + MCP320X_VOLTAGE_CHANNEL(5), + MCP320X_VOLTAGE_CHANNEL(6), + MCP320X_VOLTAGE_CHANNEL(7), + MCP320X_VOLTAGE_CHANNEL_DIFF(0, 1), + MCP320X_VOLTAGE_CHANNEL_DIFF(1, 0), + MCP320X_VOLTAGE_CHANNEL_DIFF(2, 3), + MCP320X_VOLTAGE_CHANNEL_DIFF(3, 2), + MCP320X_VOLTAGE_CHANNEL_DIFF(4, 5), + MCP320X_VOLTAGE_CHANNEL_DIFF(5, 4), + MCP320X_VOLTAGE_CHANNEL_DIFF(6, 7), + MCP320X_VOLTAGE_CHANNEL_DIFF(7, 6), +}; + +static const struct iio_info mcp320x_info = { + .read_raw = mcp320x_read_raw, +}; + +static const struct mcp320x_chip_info mcp320x_chip_infos[] = { + [mcp3001] = { + .channels = mcp3201_channels, + .num_channels = ARRAY_SIZE(mcp3201_channels), + .resolution = 10 + }, + [mcp3002] = { + .channels = mcp3202_channels, + .num_channels = ARRAY_SIZE(mcp3202_channels), + .resolution = 10 + }, + [mcp3004] = { + .channels = mcp3204_channels, + .num_channels = ARRAY_SIZE(mcp3204_channels), + .resolution = 10 + }, + [mcp3008] = { + .channels = mcp3208_channels, + .num_channels = ARRAY_SIZE(mcp3208_channels), + .resolution = 10 + }, + [mcp3201] = { + .channels = mcp3201_channels, + .num_channels = ARRAY_SIZE(mcp3201_channels), + .resolution = 12 + }, + [mcp3202] = { + .channels = mcp3202_channels, + .num_channels = ARRAY_SIZE(mcp3202_channels), + .resolution = 12 + }, + [mcp3204] = { + .channels = mcp3204_channels, + .num_channels = ARRAY_SIZE(mcp3204_channels), + .resolution = 12 + }, + [mcp3208] = { + .channels = mcp3208_channels, + .num_channels = ARRAY_SIZE(mcp3208_channels), + .resolution = 12 + }, + [mcp3301] = { + .channels = mcp3201_channels, + .num_channels = ARRAY_SIZE(mcp3201_channels), + .resolution = 13 + }, + [mcp3550_50] = { + .channels = mcp3201_channels, + .num_channels = ARRAY_SIZE(mcp3201_channels), + .resolution = 21, + /* 2% max deviation + 144 clock periods to exit shutdown */ + .conv_time = 80000 * 1.02 + 144000 / 102.4, + }, + [mcp3550_60] = { + .channels = mcp3201_channels, + .num_channels = ARRAY_SIZE(mcp3201_channels), + .resolution = 21, + .conv_time = 66670 * 1.02 + 144000 / 122.88, + }, + [mcp3551] = { + .channels = mcp3201_channels, + .num_channels = ARRAY_SIZE(mcp3201_channels), + .resolution = 21, + .conv_time = 73100 * 1.02 + 144000 / 112.64, + }, + [mcp3553] = { + .channels = mcp3201_channels, + .num_channels = ARRAY_SIZE(mcp3201_channels), + .resolution = 21, + .conv_time = 16670 * 1.02 + 144000 / 122.88, + }, +}; + +static void mcp320x_regulator_disable(void *reg) +{ + regulator_disable(reg); +} + +static int mcp320x_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct mcp320x *adc; + const struct mcp320x_chip_info *chip_info; + int ret, device_index; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->spi = spi; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &mcp320x_info; + + device_index = spi_get_device_id(spi)->driver_data; + chip_info = &mcp320x_chip_infos[device_index]; + indio_dev->channels = chip_info->channels; + indio_dev->num_channels = chip_info->num_channels; + + adc->chip_info = chip_info; + + adc->transfer[0].tx_buf = &adc->tx_buf; + adc->transfer[0].len = sizeof(adc->tx_buf); + adc->transfer[1].rx_buf = adc->rx_buf; + adc->transfer[1].len = DIV_ROUND_UP(chip_info->resolution, 8); + + if (chip_info->num_channels == 1) + /* single-channel converters are rx only (no MOSI pin) */ + spi_message_init_with_transfers(&adc->msg, + &adc->transfer[1], 1); + else + spi_message_init_with_transfers(&adc->msg, adc->transfer, + ARRAY_SIZE(adc->transfer)); + + switch (device_index) { + case mcp3550_50: + case mcp3550_60: + case mcp3551: + case mcp3553: + /* rx len increases from 24 to 25 bit in SPI mode 0,0 */ + if (!(spi->mode & SPI_CPOL)) + adc->transfer[1].len++; + + /* conversions are started by asserting CS pin for 8 usec */ + adc->start_conv_transfer.delay.value = 8; + adc->start_conv_transfer.delay.unit = SPI_DELAY_UNIT_USECS; + spi_message_init_with_transfers(&adc->start_conv_msg, + &adc->start_conv_transfer, 1); + + /* + * If CS was previously kept low (continuous conversion mode) + * and then changed to high, the chip is in shutdown. + * Sometimes it fails to wake from shutdown and clocks out + * only 0xffffff. The magic sequence of performing two + * conversions without delay between them resets the chip + * and ensures all subsequent conversions succeed. + */ + mcp320x_adc_conversion(adc, 0, 1, device_index, &ret); + mcp320x_adc_conversion(adc, 0, 1, device_index, &ret); + } + + adc->reg = devm_regulator_get(&spi->dev, "vref"); + if (IS_ERR(adc->reg)) + return PTR_ERR(adc->reg); + + ret = regulator_enable(adc->reg); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(&spi->dev, mcp320x_regulator_disable, adc->reg); + if (ret < 0) + return ret; + + mutex_init(&adc->lock); + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct of_device_id mcp320x_dt_ids[] = { + { .compatible = "microchip,mcp3001" }, + { .compatible = "microchip,mcp3002" }, + { .compatible = "microchip,mcp3004" }, + { .compatible = "microchip,mcp3008" }, + { .compatible = "microchip,mcp3201" }, + { .compatible = "microchip,mcp3202" }, + { .compatible = "microchip,mcp3204" }, + { .compatible = "microchip,mcp3208" }, + { .compatible = "microchip,mcp3301" }, + { .compatible = "microchip,mcp3550-50" }, + { .compatible = "microchip,mcp3550-60" }, + { .compatible = "microchip,mcp3551" }, + { .compatible = "microchip,mcp3553" }, + { } +}; +MODULE_DEVICE_TABLE(of, mcp320x_dt_ids); + +static const struct spi_device_id mcp320x_id[] = { + { .name = "mcp3001", .driver_data = mcp3001 }, + { .name = "mcp3002", .driver_data = mcp3002 }, + { .name = "mcp3004", .driver_data = mcp3004 }, + { .name = "mcp3008", .driver_data = mcp3008 }, + { .name = "mcp3201", .driver_data = mcp3201 }, + { .name = "mcp3202", .driver_data = mcp3202 }, + { .name = "mcp3204", .driver_data = mcp3204 }, + { .name = "mcp3208", .driver_data = mcp3208 }, + { .name = "mcp3301", .driver_data = mcp3301 }, + { .name = "mcp3550-50", .driver_data = mcp3550_50 }, + { .name = "mcp3550-60", .driver_data = mcp3550_60 }, + { .name = "mcp3551", .driver_data = mcp3551 }, + { .name = "mcp3553", .driver_data = mcp3553 }, + { } +}; +MODULE_DEVICE_TABLE(spi, mcp320x_id); + +static struct spi_driver mcp320x_driver = { + .driver = { + .name = "mcp320x", + .of_match_table = mcp320x_dt_ids, + }, + .probe = mcp320x_probe, + .id_table = mcp320x_id, +}; +module_spi_driver(mcp320x_driver); + +MODULE_AUTHOR("Oskar Andero "); +MODULE_DESCRIPTION("Microchip Technology MCP3x01/02/04/08 and MCP3550/1/3"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/mcp3422.c b/drivers/iio/adc/mcp3422.c new file mode 100644 index 000000000..36ba00edf --- /dev/null +++ b/drivers/iio/adc/mcp3422.c @@ -0,0 +1,415 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * mcp3422.c - driver for the Microchip mcp3421/2/3/4/5/6/7/8 chip family + * + * Copyright (C) 2013, Angelo Compagnucci + * Author: Angelo Compagnucci + * + * Datasheet: http://ww1.microchip.com/downloads/en/devicedoc/22088b.pdf + * https://ww1.microchip.com/downloads/en/DeviceDoc/22226a.pdf + * https://ww1.microchip.com/downloads/en/DeviceDoc/22072b.pdf + * + * This driver exports the value of analog input voltage to sysfs, the + * voltage unit is nV. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define MCP3422_CHANNEL_MASK GENMASK(6, 5) +#define MCP3422_SRATE_MASK GENMASK(3, 2) +#define MCP3422_PGA_MASK GENMASK(1, 0) + +#define MCP3422_SRATE_240 0x0 +#define MCP3422_SRATE_60 0x1 +#define MCP3422_SRATE_15 0x2 +#define MCP3422_SRATE_3 0x3 +#define MCP3422_PGA_1 0 +#define MCP3422_PGA_2 1 +#define MCP3422_PGA_4 2 +#define MCP3422_PGA_8 3 +#define MCP3422_CONT_SAMPLING BIT(4) + +#define MCP3422_CHAN(_index) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \ + | BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + } + +static const int mcp3422_scales[4][4] = { + { 1000000, 500000, 250000, 125000 }, + { 250000, 125000, 62500, 31250 }, + { 62500, 31250, 15625, 7812 }, + { 15625, 7812, 3906, 1953 } }; + +/* Constant msleep times for data acquisitions */ +static const int mcp3422_read_times[4] = { + [MCP3422_SRATE_240] = 1000 / 240, + [MCP3422_SRATE_60] = 1000 / 60, + [MCP3422_SRATE_15] = 1000 / 15, + [MCP3422_SRATE_3] = 1000 / 3 }; + +/* sample rates to integer conversion table */ +static const int mcp3422_sample_rates[4] = { + [MCP3422_SRATE_240] = 240, + [MCP3422_SRATE_60] = 60, + [MCP3422_SRATE_15] = 15, + [MCP3422_SRATE_3] = 3 }; + +/* sample rates to sign extension table */ +static const int mcp3422_sign_extend[4] = { + [MCP3422_SRATE_240] = 11, + [MCP3422_SRATE_60] = 13, + [MCP3422_SRATE_15] = 15, + [MCP3422_SRATE_3] = 17 }; + +/* Client data (each client gets its own) */ +struct mcp3422 { + struct i2c_client *i2c; + u8 id; + u8 config; + u8 pga[4]; + struct mutex lock; +}; + +static int mcp3422_update_config(struct mcp3422 *adc, u8 newconfig) +{ + int ret; + + ret = i2c_master_send(adc->i2c, &newconfig, 1); + if (ret > 0) { + adc->config = newconfig; + ret = 0; + } + + return ret; +} + +static int mcp3422_read(struct mcp3422 *adc, int *value, u8 *config) +{ + int ret = 0; + u8 sample_rate = FIELD_GET(MCP3422_SRATE_MASK, adc->config); + u8 buf[4] = {0, 0, 0, 0}; + u32 temp; + + if (sample_rate == MCP3422_SRATE_3) { + ret = i2c_master_recv(adc->i2c, buf, 4); + temp = get_unaligned_be24(&buf[0]); + *config = buf[3]; + } else { + ret = i2c_master_recv(adc->i2c, buf, 3); + temp = get_unaligned_be16(&buf[0]); + *config = buf[2]; + } + + *value = sign_extend32(temp, mcp3422_sign_extend[sample_rate]); + + return ret; +} + +static int mcp3422_read_channel(struct mcp3422 *adc, + struct iio_chan_spec const *channel, int *value) +{ + int ret; + u8 config; + u8 req_channel = channel->channel; + + mutex_lock(&adc->lock); + + if (req_channel != FIELD_GET(MCP3422_CHANNEL_MASK, adc->config)) { + config = adc->config; + + FIELD_MODIFY(MCP3422_CHANNEL_MASK, &config, req_channel); + FIELD_MODIFY(MCP3422_PGA_MASK, &config, adc->pga[req_channel]); + + ret = mcp3422_update_config(adc, config); + if (ret < 0) { + mutex_unlock(&adc->lock); + return ret; + } + msleep(mcp3422_read_times[FIELD_GET(MCP3422_SRATE_MASK, adc->config)]); + } + + ret = mcp3422_read(adc, value, &config); + + mutex_unlock(&adc->lock); + + return ret; +} + +static int mcp3422_read_raw(struct iio_dev *iio, + struct iio_chan_spec const *channel, int *val1, + int *val2, long mask) +{ + struct mcp3422 *adc = iio_priv(iio); + int err; + u8 sample_rate = FIELD_GET(MCP3422_SRATE_MASK, adc->config); + u8 pga = FIELD_GET(MCP3422_PGA_MASK, adc->config); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + err = mcp3422_read_channel(adc, channel, val1); + if (err < 0) + return -EINVAL; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + + *val1 = 0; + *val2 = mcp3422_scales[sample_rate][pga]; + return IIO_VAL_INT_PLUS_NANO; + + case IIO_CHAN_INFO_SAMP_FREQ: + *val1 = mcp3422_sample_rates[FIELD_GET(MCP3422_SRATE_MASK, adc->config)]; + return IIO_VAL_INT; + + default: + break; + } + + return -EINVAL; +} + +static int mcp3422_write_raw(struct iio_dev *iio, + struct iio_chan_spec const *channel, int val1, + int val2, long mask) +{ + struct mcp3422 *adc = iio_priv(iio); + u8 temp; + u8 config = adc->config; + u8 req_channel = channel->channel; + u8 sample_rate = FIELD_GET(MCP3422_SRATE_MASK, config); + u8 i; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (val1 != 0) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(mcp3422_scales[0]); i++) { + if (val2 == mcp3422_scales[sample_rate][i]) { + adc->pga[req_channel] = i; + + FIELD_MODIFY(MCP3422_CHANNEL_MASK, &config, req_channel); + FIELD_MODIFY(MCP3422_PGA_MASK, &config, adc->pga[req_channel]); + + return mcp3422_update_config(adc, config); + } + } + return -EINVAL; + + case IIO_CHAN_INFO_SAMP_FREQ: + switch (val1) { + case 240: + temp = MCP3422_SRATE_240; + break; + case 60: + temp = MCP3422_SRATE_60; + break; + case 15: + temp = MCP3422_SRATE_15; + break; + case 3: + if (adc->id > 4) + return -EINVAL; + temp = MCP3422_SRATE_3; + break; + default: + return -EINVAL; + } + + FIELD_MODIFY(MCP3422_CHANNEL_MASK, &config, req_channel); + FIELD_MODIFY(MCP3422_SRATE_MASK, &config, temp); + + return mcp3422_update_config(adc, config); + + default: + break; + } + + return -EINVAL; +} + +static int mcp3422_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static ssize_t mcp3422_show_samp_freqs(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mcp3422 *adc = iio_priv(dev_to_iio_dev(dev)); + + if (adc->id > 4) + return sprintf(buf, "240 60 15\n"); + + return sprintf(buf, "240 60 15 3\n"); +} + +static ssize_t mcp3422_show_scales(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mcp3422 *adc = iio_priv(dev_to_iio_dev(dev)); + u8 sample_rate = FIELD_GET(MCP3422_SRATE_MASK, adc->config); + + return sprintf(buf, "0.%09u 0.%09u 0.%09u 0.%09u\n", + mcp3422_scales[sample_rate][0], + mcp3422_scales[sample_rate][1], + mcp3422_scales[sample_rate][2], + mcp3422_scales[sample_rate][3]); +} + +static IIO_DEVICE_ATTR(sampling_frequency_available, S_IRUGO, + mcp3422_show_samp_freqs, NULL, 0); +static IIO_DEVICE_ATTR(in_voltage_scale_available, S_IRUGO, + mcp3422_show_scales, NULL, 0); + +static struct attribute *mcp3422_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_voltage_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group mcp3422_attribute_group = { + .attrs = mcp3422_attributes, +}; + +static const struct iio_chan_spec mcp3421_channels[] = { + MCP3422_CHAN(0), +}; + +static const struct iio_chan_spec mcp3422_channels[] = { + MCP3422_CHAN(0), + MCP3422_CHAN(1), +}; + +static const struct iio_chan_spec mcp3424_channels[] = { + MCP3422_CHAN(0), + MCP3422_CHAN(1), + MCP3422_CHAN(2), + MCP3422_CHAN(3), +}; + +static const struct iio_info mcp3422_info = { + .read_raw = mcp3422_read_raw, + .write_raw = mcp3422_write_raw, + .write_raw_get_fmt = mcp3422_write_raw_get_fmt, + .attrs = &mcp3422_attribute_group, +}; + +static int mcp3422_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct iio_dev *indio_dev; + struct mcp3422 *adc; + int err; + u8 config; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -EOPNOTSUPP; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->i2c = client; + adc->id = (u8)(id->driver_data); + + mutex_init(&adc->lock); + + indio_dev->name = dev_name(&client->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &mcp3422_info; + + switch (adc->id) { + case 1: + case 5: + indio_dev->channels = mcp3421_channels; + indio_dev->num_channels = ARRAY_SIZE(mcp3421_channels); + break; + case 2: + case 3: + case 6: + case 7: + indio_dev->channels = mcp3422_channels; + indio_dev->num_channels = ARRAY_SIZE(mcp3422_channels); + break; + case 4: + case 8: + indio_dev->channels = mcp3424_channels; + indio_dev->num_channels = ARRAY_SIZE(mcp3424_channels); + break; + } + + /* meaningful default configuration */ + config = MCP3422_CONT_SAMPLING | + FIELD_PREP(MCP3422_CHANNEL_MASK, 0) | + FIELD_PREP(MCP3422_PGA_MASK, MCP3422_PGA_1) | + FIELD_PREP(MCP3422_SRATE_MASK, MCP3422_SRATE_240); + err = mcp3422_update_config(adc, config); + if (err < 0) + return err; + + err = devm_iio_device_register(&client->dev, indio_dev); + if (err < 0) + return err; + + i2c_set_clientdata(client, indio_dev); + + return 0; +} + +static const struct i2c_device_id mcp3422_id[] = { + { .name = "mcp3421", .driver_data = 1 }, + { .name = "mcp3422", .driver_data = 2 }, + { .name = "mcp3423", .driver_data = 3 }, + { .name = "mcp3424", .driver_data = 4 }, + { .name = "mcp3425", .driver_data = 5 }, + { .name = "mcp3426", .driver_data = 6 }, + { .name = "mcp3427", .driver_data = 7 }, + { .name = "mcp3428", .driver_data = 8 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mcp3422_id); + +static const struct of_device_id mcp3422_of_match[] = { + { .compatible = "mcp3422" }, + { } +}; +MODULE_DEVICE_TABLE(of, mcp3422_of_match); + +static struct i2c_driver mcp3422_driver = { + .driver = { + .name = "mcp3422", + .of_match_table = mcp3422_of_match, + }, + .probe = mcp3422_probe, + .id_table = mcp3422_id, +}; +module_i2c_driver(mcp3422_driver); + +MODULE_AUTHOR("Angelo Compagnucci "); +MODULE_DESCRIPTION("Microchip mcp3421/2/3/4/5/6/7/8 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/mcp3564.c b/drivers/iio/adc/mcp3564.c new file mode 100644 index 000000000..259ac4ebd --- /dev/null +++ b/drivers/iio/adc/mcp3564.c @@ -0,0 +1,1483 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * IIO driver for MCP356X/MCP356XR and MCP346X/MCP346XR series ADC chip family + * + * Copyright (C) 2022-2023 Microchip Technology Inc. and its subsidiaries + * + * Author: Marius Cristea + * + * Datasheet for MCP3561, MCP3562, MCP3564 can be found here: + * https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP3561-2-4-Family-Data-Sheet-DS20006181C.pdf + * Datasheet for MCP3561R, MCP3562R, MCP3564R can be found here: + * https://ww1.microchip.com/downloads/aemDocuments/documents/APID/ProductDocuments/DataSheets/MCP3561_2_4R-Data-Sheet-DS200006391C.pdf + * Datasheet for MCP3461, MCP3462, MCP3464 can be found here: + * https://ww1.microchip.com/downloads/aemDocuments/documents/APID/ProductDocuments/DataSheets/MCP3461-2-4-Two-Four-Eight-Channel-153.6-ksps-Low-Noise-16-Bit-Delta-Sigma-ADC-Data-Sheet-20006180D.pdf + * Datasheet for MCP3461R, MCP3462R, MCP3464R can be found here: + * https://ww1.microchip.com/downloads/aemDocuments/documents/APID/ProductDocuments/DataSheets/MCP3461-2-4R-Family-Data-Sheet-DS20006404C.pdf + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define MCP3564_ADCDATA_REG 0x00 + +#define MCP3564_CONFIG0_REG 0x01 +#define MCP3564_CONFIG0_ADC_MODE_MASK GENMASK(1, 0) +/* Current Source/Sink Selection Bits for Sensor Bias */ +#define MCP3564_CONFIG0_CS_SEL_MASK GENMASK(3, 2) +/* Internal clock is selected and AMCLK is present on the analog master clock output pin */ +#define MCP3564_CONFIG0_USE_INT_CLK_OUTPUT_EN 0x03 +/* Internal clock is selected and no clock output is present on the CLK pin */ +#define MCP3564_CONFIG0_USE_INT_CLK 0x02 +/* External digital clock */ +#define MCP3564_CONFIG0_USE_EXT_CLK 0x01 +/* External digital clock (default) */ +#define MCP3564_CONFIG0_USE_EXT_CLK_DEFAULT 0x00 +#define MCP3564_CONFIG0_CLK_SEL_MASK GENMASK(5, 4) +#define MCP3456_CONFIG0_BIT6_DEFAULT BIT(6) +#define MCP3456_CONFIG0_VREF_MASK BIT(7) + +#define MCP3564_CONFIG1_REG 0x02 +#define MCP3564_CONFIG1_OVERSPL_RATIO_MASK GENMASK(5, 2) + +#define MCP3564_CONFIG2_REG 0x03 +#define MCP3564_CONFIG2_AZ_REF_MASK BIT(1) +#define MCP3564_CONFIG2_AZ_MUX_MASK BIT(2) + +#define MCP3564_CONFIG2_HARDWARE_GAIN_MASK GENMASK(5, 3) +#define MCP3564_DEFAULT_HARDWARE_GAIN 0x01 +#define MCP3564_CONFIG2_BOOST_CURRENT_MASK GENMASK(7, 6) + +#define MCP3564_CONFIG3_REG 0x04 +#define MCP3464_CONFIG3_EN_GAINCAL_MASK BIT(0) +#define MCP3464_CONFIG3_EN_OFFCAL_MASK BIT(1) +#define MCP3464_CONFIG3_EN_CRCCOM_MASK BIT(2) +#define MCP3464_CONFIG3_CRC_FORMAT_MASK BIT(3) +/* + * ADC Output Data Format 32-bit (25-bit right justified data + Channel ID): + * CHID[3:0] + SGN extension (4 bits) + 24-bit ADC data. + * It allows overrange with the SGN extension. + */ +#define MCP3464_CONFIG3_DATA_FMT_32B_WITH_CH_ID 3 +/* + * ADC Output Data Format 32-bit (25-bit right justified data): + * SGN extension (8-bit) + 24-bit ADC data. + * It allows overrange with the SGN extension. + */ +#define MCP3464_CONFIG3_DATA_FMT_32B_SGN_EXT 2 +/* + * ADC Output Data Format 32-bit (24-bit left justified data): + * 24-bit ADC data + 0x00 (8-bit). + * It does not allow overrange (ADC code locked to 0xFFFFFF or 0x800000). + */ +#define MCP3464_CONFIG3_DATA_FMT_32B_LEFT_JUSTIFIED 1 +/* + * ADC Output Data Format 24-bit (default ADC coding): + * 24-bit ADC data. + * It does not allow overrange (ADC code locked to 0xFFFFFF or 0x800000). + */ +#define MCP3464_CONFIG3_DATA_FMT_24B 0 +#define MCP3464_CONFIG3_DATA_FORMAT_MASK GENMASK(5, 4) + +/* Continuous Conversion mode or continuous conversion cycle in SCAN mode. */ +#define MCP3464_CONFIG3_CONV_MODE_CONTINUOUS 3 +/* + * One-shot conversion or one-shot cycle in SCAN mode. It sets ADC_MODE[1:0] to ‘10’ + * (standby) at the end of the conversion or at the end of the conversion cycle in SCAN mode. + */ +#define MCP3464_CONFIG3_CONV_MODE_ONE_SHOT_STANDBY 2 +/* + * One-shot conversion or one-shot cycle in SCAN mode. It sets ADC_MODE[1:0] to ‘0x’ (ADC + * Shutdown) at the end of the conversion or at the end of the conversion cycle in SCAN + * mode (default). + */ +#define MCP3464_CONFIG3_CONV_MODE_ONE_SHOT_SHUTDOWN 0 +#define MCP3464_CONFIG3_CONV_MODE_MASK GENMASK(7, 6) + +#define MCP3564_IRQ_REG 0x05 +#define MCP3464_EN_STP_MASK BIT(0) +#define MCP3464_EN_FASTCMD_MASK BIT(1) +#define MCP3464_IRQ_MODE_0_MASK BIT(2) +#define MCP3464_IRQ_MODE_1_MASK BIT(3) +#define MCP3564_POR_STATUS_MASK BIT(4) +#define MCP3564_CRCCFG_STATUS_MASK BIT(5) +#define MCP3564_DATA_READY_MASK BIT(6) + +#define MCP3564_MUX_REG 0x06 +#define MCP3564_MUX_VIN_P_MASK GENMASK(7, 4) +#define MCP3564_MUX_VIN_N_MASK GENMASK(3, 0) +#define MCP3564_MUX_SET(x, y) (FIELD_PREP(MCP3564_MUX_VIN_P_MASK, (x)) | \ + FIELD_PREP(MCP3564_MUX_VIN_N_MASK, (y))) + +#define MCP3564_SCAN_REG 0x07 +#define MCP3564_SCAN_CH_SEL_MASK GENMASK(15, 0) +#define MCP3564_SCAN_CH_SEL_SET(x) FIELD_PREP(MCP3564_SCAN_CH_SEL_MASK, (x)) +#define MCP3564_SCAN_DELAY_TIME_MASK GENMASK(23, 21) +#define MCP3564_SCAN_DELAY_TIME_SET(x) FIELD_PREP(MCP3564_SCAN_DELAY_TIME_MASK, (x)) +#define MCP3564_SCAN_DEFAULT_VALUE 0 + +#define MCP3564_TIMER_REG 0x08 +#define MCP3564_TIMER_DEFAULT_VALUE 0 + +#define MCP3564_OFFSETCAL_REG 0x09 +#define MCP3564_DEFAULT_OFFSETCAL 0 + +#define MCP3564_GAINCAL_REG 0x0A +#define MCP3564_DEFAULT_GAINCAL 0x00800000 + +#define MCP3564_RESERVED_B_REG 0x0B + +#define MCP3564_RESERVED_C_REG 0x0C +#define MCP3564_C_REG_DEFAULT 0x50 +#define MCP3564R_C_REG_DEFAULT 0x30 + +#define MCP3564_LOCK_REG 0x0D +#define MCP3564_LOCK_WRITE_ACCESS_PASSWORD 0xA5 +#define MCP3564_RESERVED_E_REG 0x0E +#define MCP3564_CRCCFG_REG 0x0F + +#define MCP3564_CMD_HW_ADDR_MASK GENMASK(7, 6) +#define MCP3564_CMD_ADDR_MASK GENMASK(5, 2) + +#define MCP3564_HW_ADDR_MASK GENMASK(1, 0) + +#define MCP3564_FASTCMD_START 0x0A +#define MCP3564_FASTCMD_RESET 0x0E + +#define MCP3461_HW_ID 0x0008 +#define MCP3462_HW_ID 0x0009 +#define MCP3464_HW_ID 0x000B + +#define MCP3561_HW_ID 0x000C +#define MCP3562_HW_ID 0x000D +#define MCP3564_HW_ID 0x000F +#define MCP3564_HW_ID_MASK GENMASK(3, 0) + +#define MCP3564R_INT_VREF_MV 2400 + +#define MCP3564_DATA_READY_TIMEOUT_MS 2000 + +#define MCP3564_MAX_PGA 8 +#define MCP3564_MAX_BURNOUT_IDX 4 +#define MCP3564_MAX_CHANNELS 66 + +enum mcp3564_ids { + mcp3461, + mcp3462, + mcp3464, + mcp3561, + mcp3562, + mcp3564, + mcp3461r, + mcp3462r, + mcp3464r, + mcp3561r, + mcp3562r, + mcp3564r, +}; + +enum mcp3564_delay_time { + MCP3564_NO_DELAY, + MCP3564_DELAY_8_DMCLK, + MCP3564_DELAY_16_DMCLK, + MCP3564_DELAY_32_DMCLK, + MCP3564_DELAY_64_DMCLK, + MCP3564_DELAY_128_DMCLK, + MCP3564_DELAY_256_DMCLK, + MCP3564_DELAY_512_DMCLK +}; + +enum mcp3564_adc_conversion_mode { + MCP3564_ADC_MODE_DEFAULT, + MCP3564_ADC_MODE_SHUTDOWN, + MCP3564_ADC_MODE_STANDBY, + MCP3564_ADC_MODE_CONVERSION +}; + +enum mcp3564_adc_bias_current { + MCP3564_BOOST_CURRENT_x0_50, + MCP3564_BOOST_CURRENT_x0_66, + MCP3564_BOOST_CURRENT_x1_00, + MCP3564_BOOST_CURRENT_x2_00 +}; + +enum mcp3564_burnout { + MCP3564_CONFIG0_CS_SEL_0_0_uA, + MCP3564_CONFIG0_CS_SEL_0_9_uA, + MCP3564_CONFIG0_CS_SEL_3_7_uA, + MCP3564_CONFIG0_CS_SEL_15_uA +}; + +enum mcp3564_channel_names { + MCP3564_CH0, + MCP3564_CH1, + MCP3564_CH2, + MCP3564_CH3, + MCP3564_CH4, + MCP3564_CH5, + MCP3564_CH6, + MCP3564_CH7, + MCP3564_AGND, + MCP3564_AVDD, + MCP3564_RESERVED, /* do not use */ + MCP3564_REFIN_POZ, + MCP3564_REFIN_NEG, + MCP3564_TEMP_DIODE_P, + MCP3564_TEMP_DIODE_M, + MCP3564_INTERNAL_VCM, +}; + +enum mcp3564_oversampling { + MCP3564_OVERSAMPLING_RATIO_32, + MCP3564_OVERSAMPLING_RATIO_64, + MCP3564_OVERSAMPLING_RATIO_128, + MCP3564_OVERSAMPLING_RATIO_256, + MCP3564_OVERSAMPLING_RATIO_512, + MCP3564_OVERSAMPLING_RATIO_1024, + MCP3564_OVERSAMPLING_RATIO_2048, + MCP3564_OVERSAMPLING_RATIO_4096, + MCP3564_OVERSAMPLING_RATIO_8192, + MCP3564_OVERSAMPLING_RATIO_16384, + MCP3564_OVERSAMPLING_RATIO_20480, + MCP3564_OVERSAMPLING_RATIO_24576, + MCP3564_OVERSAMPLING_RATIO_40960, + MCP3564_OVERSAMPLING_RATIO_49152, + MCP3564_OVERSAMPLING_RATIO_81920, + MCP3564_OVERSAMPLING_RATIO_98304 +}; + +static const unsigned int mcp3564_oversampling_avail[] = { + [MCP3564_OVERSAMPLING_RATIO_32] = 32, + [MCP3564_OVERSAMPLING_RATIO_64] = 64, + [MCP3564_OVERSAMPLING_RATIO_128] = 128, + [MCP3564_OVERSAMPLING_RATIO_256] = 256, + [MCP3564_OVERSAMPLING_RATIO_512] = 512, + [MCP3564_OVERSAMPLING_RATIO_1024] = 1024, + [MCP3564_OVERSAMPLING_RATIO_2048] = 2048, + [MCP3564_OVERSAMPLING_RATIO_4096] = 4096, + [MCP3564_OVERSAMPLING_RATIO_8192] = 8192, + [MCP3564_OVERSAMPLING_RATIO_16384] = 16384, + [MCP3564_OVERSAMPLING_RATIO_20480] = 20480, + [MCP3564_OVERSAMPLING_RATIO_24576] = 24576, + [MCP3564_OVERSAMPLING_RATIO_40960] = 40960, + [MCP3564_OVERSAMPLING_RATIO_49152] = 49152, + [MCP3564_OVERSAMPLING_RATIO_81920] = 81920, + [MCP3564_OVERSAMPLING_RATIO_98304] = 98304 +}; + +/* + * Current Source/Sink Selection Bits for Sensor Bias (source on VIN+/sink on VIN-) + */ +static const int mcp3564_burnout_avail[][2] = { + [MCP3564_CONFIG0_CS_SEL_0_0_uA] = { 0, 0 }, + [MCP3564_CONFIG0_CS_SEL_0_9_uA] = { 0, 900 }, + [MCP3564_CONFIG0_CS_SEL_3_7_uA] = { 0, 3700 }, + [MCP3564_CONFIG0_CS_SEL_15_uA] = { 0, 15000 } +}; + +/* + * BOOST[1:0]: ADC Bias Current Selection + */ +static const char * const mcp3564_boost_current_avail[] = { + [MCP3564_BOOST_CURRENT_x0_50] = "0.5", + [MCP3564_BOOST_CURRENT_x0_66] = "0.66", + [MCP3564_BOOST_CURRENT_x1_00] = "1", + [MCP3564_BOOST_CURRENT_x2_00] = "2", +}; + +/* + * Calibration bias values + */ +static const int mcp3564_calib_bias[] = { + -8388608, /* min: -2^23 */ + 1, /* step: 1 */ + 8388607 /* max: 2^23 - 1 */ +}; + +/* + * Calibration scale values + * The Gain Error Calibration register (GAINCAL) is an + * unsigned 24-bit register that holds the digital gain error + * calibration value, GAINCAL which could be calculated by + * GAINCAL (V/V) = (GAINCAL[23:0])/8388608 + * The gain error calibration value range in equivalent voltage is [0; 2-2^(-23)] + */ +static const unsigned int mcp3564_calib_scale[] = { + 0, /* min: 0 */ + 1, /* step: 1/8388608 */ + 16777215 /* max: 2 - 2^(-23) */ +}; + +/* Programmable hardware gain x1/3, x1, x2, x4, x8, x16, x32, x64 */ +static const int mcp3564_hwgain_frac[] = { + 3, 10, + 1, 1, + 2, 1, + 4, 1, + 8, 1, + 16, 1, + 32, 1, + 64, 1 +}; + +static const char *mcp3564_channel_labels[2] = { + "burnout_current", "temperature", +}; + +/** + * struct mcp3564_chip_info - chip specific data + * @name: device name + * @num_channels: number of channels + * @resolution: ADC resolution + * @have_vref: does the hardware have an internal voltage reference? + */ +struct mcp3564_chip_info { + const char *name; + unsigned int num_channels; + unsigned int resolution; + bool have_vref; +}; + +/** + * struct mcp3564_state - working data for a ADC device + * @chip_info: chip specific data + * @spi: SPI device structure + * @vref_mv: voltage reference value in millivolts + * @lock: synchronize access to driver's state members + * @dev_addr: hardware device address + * @oversampling: the index inside oversampling list of the ADC + * @hwgain: the index inside hardware gain list of the ADC + * @scale_tbls: table with precalculated scale + * @calib_bias: calibration bias value + * @calib_scale: calibration scale value + * @current_boost_mode: the index inside current boost list of the ADC + * @burnout_mode: the index inside current bias list of the ADC + * @auto_zeroing_mux: set if ADC auto-zeroing algorithm is enabled + * @auto_zeroing_ref: set if ADC auto-Zeroing Reference Buffer Setting is enabled + * @have_vref: does the ADC have an internal voltage reference? + * @labels: table with channels labels + */ +struct mcp3564_state { + const struct mcp3564_chip_info *chip_info; + struct spi_device *spi; + unsigned short vref_mv; + struct mutex lock; /* Synchronize access to driver's state members */ + u8 dev_addr; + enum mcp3564_oversampling oversampling; + unsigned int hwgain; + unsigned int scale_tbls[MCP3564_MAX_PGA][2]; + int calib_bias; + int calib_scale; + unsigned int current_boost_mode; + enum mcp3564_burnout burnout_mode; + bool auto_zeroing_mux; + bool auto_zeroing_ref; + bool have_vref; + const char *labels[MCP3564_MAX_CHANNELS]; +}; + +static inline u8 mcp3564_cmd_write(u8 chip_addr, u8 reg) +{ + return FIELD_PREP(MCP3564_CMD_HW_ADDR_MASK, chip_addr) | + FIELD_PREP(MCP3564_CMD_ADDR_MASK, reg) | + BIT(1); +} + +static inline u8 mcp3564_cmd_read(u8 chip_addr, u8 reg) +{ + return FIELD_PREP(MCP3564_CMD_HW_ADDR_MASK, chip_addr) | + FIELD_PREP(MCP3564_CMD_ADDR_MASK, reg) | + BIT(0); +} + +static int mcp3564_read_8bits(struct mcp3564_state *adc, u8 reg, u8 *val) +{ + int ret; + u8 tx_buf; + u8 rx_buf; + + tx_buf = mcp3564_cmd_read(adc->dev_addr, reg); + + ret = spi_write_then_read(adc->spi, &tx_buf, sizeof(tx_buf), + &rx_buf, sizeof(rx_buf)); + *val = rx_buf; + + return ret; +} + +static int mcp3564_read_16bits(struct mcp3564_state *adc, u8 reg, u16 *val) +{ + int ret; + u8 tx_buf; + __be16 rx_buf; + + tx_buf = mcp3564_cmd_read(adc->dev_addr, reg); + + ret = spi_write_then_read(adc->spi, &tx_buf, sizeof(tx_buf), + &rx_buf, sizeof(rx_buf)); + *val = be16_to_cpu(rx_buf); + + return ret; +} + +static int mcp3564_read_32bits(struct mcp3564_state *adc, u8 reg, u32 *val) +{ + int ret; + u8 tx_buf; + __be32 rx_buf; + + tx_buf = mcp3564_cmd_read(adc->dev_addr, reg); + + ret = spi_write_then_read(adc->spi, &tx_buf, sizeof(tx_buf), + &rx_buf, sizeof(rx_buf)); + *val = be32_to_cpu(rx_buf); + + return ret; +} + +static int mcp3564_write_8bits(struct mcp3564_state *adc, u8 reg, u8 val) +{ + u8 tx_buf[2]; + + tx_buf[0] = mcp3564_cmd_write(adc->dev_addr, reg); + tx_buf[1] = val; + + return spi_write_then_read(adc->spi, tx_buf, sizeof(tx_buf), NULL, 0); +} + +static int mcp3564_write_24bits(struct mcp3564_state *adc, u8 reg, u32 val) +{ + __be32 val_be; + + val |= (mcp3564_cmd_write(adc->dev_addr, reg) << 24); + val_be = cpu_to_be32(val); + + return spi_write_then_read(adc->spi, &val_be, sizeof(val_be), NULL, 0); +} + +static int mcp3564_fast_cmd(struct mcp3564_state *adc, u8 fast_cmd) +{ + u8 val; + + val = FIELD_PREP(MCP3564_CMD_HW_ADDR_MASK, adc->dev_addr) | + FIELD_PREP(MCP3564_CMD_ADDR_MASK, fast_cmd); + + return spi_write_then_read(adc->spi, &val, 1, NULL, 0); +} + +static int mcp3564_update_8bits(struct mcp3564_state *adc, u8 reg, u32 mask, u8 val) +{ + u8 tmp; + int ret; + + val &= mask; + + ret = mcp3564_read_8bits(adc, reg, &tmp); + if (ret < 0) + return ret; + + tmp &= ~mask; + tmp |= val; + + return mcp3564_write_8bits(adc, reg, tmp); +} + +static int mcp3564_set_current_boost_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct mcp3564_state *adc = iio_priv(indio_dev); + int ret; + + dev_dbg(&indio_dev->dev, "%s: %d\n", __func__, mode); + + mutex_lock(&adc->lock); + ret = mcp3564_update_8bits(adc, MCP3564_CONFIG2_REG, MCP3564_CONFIG2_BOOST_CURRENT_MASK, + FIELD_PREP(MCP3564_CONFIG2_BOOST_CURRENT_MASK, mode)); + + if (ret) + dev_err(&indio_dev->dev, "Failed to configure CONFIG2 register\n"); + else + adc->current_boost_mode = mode; + + mutex_unlock(&adc->lock); + + return ret; +} + +static int mcp3564_get_current_boost_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct mcp3564_state *adc = iio_priv(indio_dev); + + return adc->current_boost_mode; +} + +static const struct iio_enum mcp3564_current_boost_mode_enum = { + .items = mcp3564_boost_current_avail, + .num_items = ARRAY_SIZE(mcp3564_boost_current_avail), + .set = mcp3564_set_current_boost_mode, + .get = mcp3564_get_current_boost_mode, +}; + +static const struct iio_chan_spec_ext_info mcp3564_ext_info[] = { + IIO_ENUM("boost_current_gain", IIO_SHARED_BY_ALL, &mcp3564_current_boost_mode_enum), + { + .name = "boost_current_gain_available", + .shared = IIO_SHARED_BY_ALL, + .read = iio_enum_available_read, + .private = (uintptr_t)&mcp3564_current_boost_mode_enum, + }, + { } +}; + +static ssize_t mcp3564_auto_zeroing_mux_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct mcp3564_state *adc = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", adc->auto_zeroing_mux); +} + +static ssize_t mcp3564_auto_zeroing_mux_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct mcp3564_state *adc = iio_priv(indio_dev); + bool auto_zero; + int ret; + + ret = kstrtobool(buf, &auto_zero); + if (ret) + return ret; + + mutex_lock(&adc->lock); + ret = mcp3564_update_8bits(adc, MCP3564_CONFIG2_REG, MCP3564_CONFIG2_AZ_MUX_MASK, + FIELD_PREP(MCP3564_CONFIG2_AZ_MUX_MASK, auto_zero)); + + if (ret) + dev_err(&indio_dev->dev, "Failed to update CONFIG2 register\n"); + else + adc->auto_zeroing_mux = auto_zero; + + mutex_unlock(&adc->lock); + + return ret ? ret : len; +} + +static ssize_t mcp3564_auto_zeroing_ref_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct mcp3564_state *adc = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", adc->auto_zeroing_ref); +} + +static ssize_t mcp3564_auto_zeroing_ref_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct mcp3564_state *adc = iio_priv(indio_dev); + bool auto_zero; + int ret; + + ret = kstrtobool(buf, &auto_zero); + if (ret) + return ret; + + mutex_lock(&adc->lock); + ret = mcp3564_update_8bits(adc, MCP3564_CONFIG2_REG, MCP3564_CONFIG2_AZ_REF_MASK, + FIELD_PREP(MCP3564_CONFIG2_AZ_REF_MASK, auto_zero)); + + if (ret) + dev_err(&indio_dev->dev, "Failed to update CONFIG2 register\n"); + else + adc->auto_zeroing_ref = auto_zero; + + mutex_unlock(&adc->lock); + + return ret ? ret : len; +} + +static const struct iio_chan_spec mcp3564_channel_template = { + .type = IIO_VOLTAGE, + .indexed = 1, + .differential = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .ext_info = mcp3564_ext_info, +}; + +static const struct iio_chan_spec mcp3564_temp_channel_template = { + .type = IIO_TEMP, + .channel = 0, + .address = ((MCP3564_TEMP_DIODE_P << 4) | MCP3564_TEMP_DIODE_M), + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), +}; + +static const struct iio_chan_spec mcp3564_burnout_channel_template = { + .type = IIO_CURRENT, + .output = true, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW), +}; + +/* + * Number of channels could be calculated: + * num_channels = single_ended_input + differential_input + temperature + burnout + * Eg. for MCP3561 (only 2 channels available: CH0 and CH1) + * single_ended_input = (CH0 - GND), (CH1 - GND) = 2 + * differential_input = (CH0 - CH1), (CH0 - CH0) = 2 + * num_channels = 2 + 2 + 2 + * Generic formula is: + * num_channels = P^R(Number_of_single_ended_channels, 2) + 2 (temperature + burnout channels) + * P^R(Number_of_single_ended_channels, 2) is Permutations with Replacement of + * Number_of_single_ended_channels taken by 2 + */ +static const struct mcp3564_chip_info mcp3564_chip_infos_tbl[] = { + [mcp3461] = { + .name = "mcp3461", + .num_channels = 6, + .resolution = 16, + .have_vref = false, + }, + [mcp3462] = { + .name = "mcp3462", + .num_channels = 18, + .resolution = 16, + .have_vref = false, + }, + [mcp3464] = { + .name = "mcp3464", + .num_channels = 66, + .resolution = 16, + .have_vref = false, + }, + [mcp3561] = { + .name = "mcp3561", + .num_channels = 6, + .resolution = 24, + .have_vref = false, + }, + [mcp3562] = { + .name = "mcp3562", + .num_channels = 18, + .resolution = 24, + .have_vref = false, + }, + [mcp3564] = { + .name = "mcp3564", + .num_channels = 66, + .resolution = 24, + .have_vref = false, + }, + [mcp3461r] = { + .name = "mcp3461r", + .num_channels = 6, + .resolution = 16, + .have_vref = false, + }, + [mcp3462r] = { + .name = "mcp3462r", + .num_channels = 18, + .resolution = 16, + .have_vref = true, + }, + [mcp3464r] = { + .name = "mcp3464r", + .num_channels = 66, + .resolution = 16, + .have_vref = true, + }, + [mcp3561r] = { + .name = "mcp3561r", + .num_channels = 6, + .resolution = 24, + .have_vref = true, + }, + [mcp3562r] = { + .name = "mcp3562r", + .num_channels = 18, + .resolution = 24, + .have_vref = true, + }, + [mcp3564r] = { + .name = "mcp3564r", + .num_channels = 66, + .resolution = 24, + .have_vref = true, + }, +}; + +static int mcp3564_read_single_value(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, + int *val) +{ + struct mcp3564_state *adc = iio_priv(indio_dev); + int ret; + u8 tmp; + int ret_read = 0; + + ret = mcp3564_write_8bits(adc, MCP3564_MUX_REG, channel->address); + if (ret) + return ret; + + /* Start ADC Conversion using fast command (overwrites ADC_MODE[1:0] = 11) */ + ret = mcp3564_fast_cmd(adc, MCP3564_FASTCMD_START); + if (ret) + return ret; + + /* + * Check if the conversion is ready. If not, wait a little bit, and + * in case of timeout exit with an error. + */ + ret = read_poll_timeout(mcp3564_read_8bits, ret_read, + ret_read || !(tmp & MCP3564_DATA_READY_MASK), + 20000, MCP3564_DATA_READY_TIMEOUT_MS * 1000, true, + adc, MCP3564_IRQ_REG, &tmp); + + /* failed to read status register */ + if (ret_read) + return ret_read; + + if (ret) + return ret; + + if (tmp & MCP3564_DATA_READY_MASK) + /* failing to finish conversion */ + return -EBUSY; + + return mcp3564_read_32bits(adc, MCP3564_ADCDATA_REG, val); +} + +static int mcp3564_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, + const int **vals, int *type, + int *length, long mask) +{ + struct mcp3564_state *adc = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!channel->output) + return -EINVAL; + + *vals = mcp3564_burnout_avail[0]; + *length = ARRAY_SIZE(mcp3564_burnout_avail) * 2; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = mcp3564_oversampling_avail; + *length = ARRAY_SIZE(mcp3564_oversampling_avail); + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SCALE: + *vals = (int *)adc->scale_tbls; + *length = ARRAY_SIZE(adc->scale_tbls) * 2; + *type = IIO_VAL_INT_PLUS_NANO; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_CALIBBIAS: + *vals = mcp3564_calib_bias; + *type = IIO_VAL_INT; + return IIO_AVAIL_RANGE; + case IIO_CHAN_INFO_CALIBSCALE: + *vals = mcp3564_calib_scale; + *type = IIO_VAL_INT; + return IIO_AVAIL_RANGE; + default: + return -EINVAL; + } +} + +static int mcp3564_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, + int *val, int *val2, long mask) +{ + struct mcp3564_state *adc = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (channel->output) { + mutex_lock(&adc->lock); + *val = mcp3564_burnout_avail[adc->burnout_mode][0]; + *val2 = mcp3564_burnout_avail[adc->burnout_mode][1]; + mutex_unlock(&adc->lock); + return IIO_VAL_INT_PLUS_MICRO; + } + + ret = mcp3564_read_single_value(indio_dev, channel, val); + if (ret) + return -EINVAL; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + mutex_lock(&adc->lock); + *val = adc->scale_tbls[adc->hwgain][0]; + *val2 = adc->scale_tbls[adc->hwgain][1]; + mutex_unlock(&adc->lock); + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = mcp3564_oversampling_avail[adc->oversampling]; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + *val = adc->calib_bias; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + *val = adc->calib_scale; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int mcp3564_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long info) +{ + switch (info) { + case IIO_CHAN_INFO_RAW: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBBIAS: + case IIO_CHAN_INFO_CALIBSCALE: + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static int mcp3564_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int val, + int val2, long mask) +{ + struct mcp3564_state *adc = iio_priv(indio_dev); + int tmp; + unsigned int hwgain; + enum mcp3564_burnout burnout; + int ret = 0; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!channel->output) + return -EINVAL; + + for (burnout = 0; burnout < MCP3564_MAX_BURNOUT_IDX; burnout++) + if (val == mcp3564_burnout_avail[burnout][0] && + val2 == mcp3564_burnout_avail[burnout][1]) + break; + + if (burnout == MCP3564_MAX_BURNOUT_IDX) + return -EINVAL; + + if (burnout == adc->burnout_mode) + return ret; + + mutex_lock(&adc->lock); + ret = mcp3564_update_8bits(adc, MCP3564_CONFIG0_REG, + MCP3564_CONFIG0_CS_SEL_MASK, + FIELD_PREP(MCP3564_CONFIG0_CS_SEL_MASK, burnout)); + + if (ret) + dev_err(&indio_dev->dev, "Failed to configure burnout current\n"); + else + adc->burnout_mode = burnout; + mutex_unlock(&adc->lock); + return ret; + case IIO_CHAN_INFO_CALIBBIAS: + if (val < mcp3564_calib_bias[0] || val > mcp3564_calib_bias[2]) + return -EINVAL; + + mutex_lock(&adc->lock); + ret = mcp3564_write_24bits(adc, MCP3564_OFFSETCAL_REG, val); + if (!ret) + adc->calib_bias = val; + mutex_unlock(&adc->lock); + return ret; + case IIO_CHAN_INFO_CALIBSCALE: + if (val < mcp3564_calib_scale[0] || val > mcp3564_calib_scale[2]) + return -EINVAL; + + if (adc->calib_scale == val) + return ret; + + mutex_lock(&adc->lock); + ret = mcp3564_write_24bits(adc, MCP3564_GAINCAL_REG, val); + if (!ret) + adc->calib_scale = val; + mutex_unlock(&adc->lock); + return ret; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + if (val < 0) + return -EINVAL; + + tmp = find_closest(val, mcp3564_oversampling_avail, + ARRAY_SIZE(mcp3564_oversampling_avail)); + + if (adc->oversampling == tmp) + return ret; + + mutex_lock(&adc->lock); + ret = mcp3564_update_8bits(adc, MCP3564_CONFIG1_REG, + MCP3564_CONFIG1_OVERSPL_RATIO_MASK, + FIELD_PREP(MCP3564_CONFIG1_OVERSPL_RATIO_MASK, + adc->oversampling)); + if (!ret) + adc->oversampling = tmp; + mutex_unlock(&adc->lock); + return ret; + case IIO_CHAN_INFO_SCALE: + for (hwgain = 0; hwgain < MCP3564_MAX_PGA; hwgain++) + if (val == adc->scale_tbls[hwgain][0] && + val2 == adc->scale_tbls[hwgain][1]) + break; + + if (hwgain == MCP3564_MAX_PGA) + return -EINVAL; + + if (hwgain == adc->hwgain) + return ret; + + mutex_lock(&adc->lock); + ret = mcp3564_update_8bits(adc, MCP3564_CONFIG2_REG, + MCP3564_CONFIG2_HARDWARE_GAIN_MASK, + FIELD_PREP(MCP3564_CONFIG2_HARDWARE_GAIN_MASK, hwgain)); + if (!ret) + adc->hwgain = hwgain; + + mutex_unlock(&adc->lock); + return ret; + default: + return -EINVAL; + } +} + +static int mcp3564_read_label(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, char *label) +{ + struct mcp3564_state *adc = iio_priv(indio_dev); + + return sysfs_emit(label, "%s\n", adc->labels[chan->scan_index]); +} + +static int mcp3564_parse_fw_children(struct iio_dev *indio_dev) +{ + struct mcp3564_state *adc = iio_priv(indio_dev); + struct device *dev = &adc->spi->dev; + struct iio_chan_spec *channels; + struct iio_chan_spec chanspec = mcp3564_channel_template; + struct iio_chan_spec temp_chanspec = mcp3564_temp_channel_template; + struct iio_chan_spec burnout_chanspec = mcp3564_burnout_channel_template; + int chan_idx = 0; + unsigned int num_ch; + u32 inputs[2]; + const char *node_name; + const char *label; + int ret; + + num_ch = device_get_child_node_count(dev); + if (num_ch == 0) + return dev_err_probe(&indio_dev->dev, -ENODEV, + "FW has no channels defined\n"); + + /* Reserve space for burnout and temperature channel */ + num_ch += 2; + + if (num_ch > adc->chip_info->num_channels) + return dev_err_probe(dev, -EINVAL, "Too many channels %d > %d\n", + num_ch, adc->chip_info->num_channels); + + channels = devm_kcalloc(dev, num_ch, sizeof(*channels), GFP_KERNEL); + if (!channels) + return -ENOMEM; + + device_for_each_child_node_scoped(dev, child) { + node_name = fwnode_get_name(child); + + if (fwnode_property_present(child, "diff-channels")) { + ret = fwnode_property_read_u32_array(child, + "diff-channels", + inputs, + ARRAY_SIZE(inputs)); + if (ret) + return ret; + + chanspec.differential = 1; + } else { + ret = fwnode_property_read_u32(child, "reg", &inputs[0]); + if (ret) + return ret; + + chanspec.differential = 0; + inputs[1] = MCP3564_AGND; + } + + if (inputs[0] > MCP3564_INTERNAL_VCM || + inputs[1] > MCP3564_INTERNAL_VCM) + return dev_err_probe(&indio_dev->dev, -EINVAL, + "Channel index > %d, for %s\n", + MCP3564_INTERNAL_VCM + 1, + node_name); + + chanspec.address = (inputs[0] << 4) | inputs[1]; + chanspec.channel = inputs[0]; + chanspec.channel2 = inputs[1]; + chanspec.scan_index = chan_idx; + + if (fwnode_property_present(child, "label")) { + fwnode_property_read_string(child, "label", &label); + adc->labels[chan_idx] = label; + } + + channels[chan_idx] = chanspec; + chan_idx++; + } + + /* Add burnout current channel */ + burnout_chanspec.scan_index = chan_idx; + channels[chan_idx] = burnout_chanspec; + adc->labels[chan_idx] = mcp3564_channel_labels[0]; + chanspec.scan_index = chan_idx; + chan_idx++; + + /* Add temperature channel */ + temp_chanspec.scan_index = chan_idx; + channels[chan_idx] = temp_chanspec; + adc->labels[chan_idx] = mcp3564_channel_labels[1]; + chan_idx++; + + indio_dev->num_channels = chan_idx; + indio_dev->channels = channels; + + return 0; +} + +static void mcp3564_fill_scale_tbls(struct mcp3564_state *adc) +{ + unsigned int pow = adc->chip_info->resolution - 1; + int ref; + unsigned int i; + int tmp0; + u64 tmp1; + + for (i = 0; i < MCP3564_MAX_PGA; i++) { + ref = adc->vref_mv; + tmp1 = ((u64)ref * NANO) >> pow; + div_u64_rem(tmp1, NANO, &tmp0); + + tmp1 = tmp1 * mcp3564_hwgain_frac[(2 * i) + 1]; + tmp0 = (int)div_u64(tmp1, mcp3564_hwgain_frac[2 * i]); + + adc->scale_tbls[i][1] = tmp0; + } +} + +static int mcp3564_config(struct iio_dev *indio_dev, bool *use_internal_vref_attr) +{ + struct mcp3564_state *adc = iio_priv(indio_dev); + struct device *dev = &adc->spi->dev; + u8 tmp_reg; + u16 tmp_u16; + enum mcp3564_ids ids; + int ret = 0; + unsigned int tmp = 0x01; + bool internal_vref; + bool err = false; + + /* + * The address is set on a per-device basis by fuses in the factory, + * configured on request. If not requested, the fuses are set for 0x1. + * The device address is part of the device markings to avoid + * potential confusion. This address is coded on two bits, so four possible + * addresses are available when multiple devices are present on the same + * SPI bus with only one Chip Select line for all devices. + */ + device_property_read_u32(dev, "microchip,hw-device-address", &tmp); + + if (tmp > 3) { + dev_err_probe(dev, tmp, + "invalid device address. Must be in range 0-3.\n"); + return -EINVAL; + } + + adc->dev_addr = FIELD_GET(MCP3564_HW_ADDR_MASK, tmp); + + dev_dbg(dev, "use HW device address %i\n", adc->dev_addr); + + ret = mcp3564_read_8bits(adc, MCP3564_RESERVED_C_REG, &tmp_reg); + if (ret < 0) + return ret; + + switch (tmp_reg) { + case MCP3564_C_REG_DEFAULT: + adc->have_vref = false; + break; + case MCP3564R_C_REG_DEFAULT: + adc->have_vref = true; + break; + default: + dev_info(dev, "Unknown chip found: %d\n", tmp_reg); + err = true; + } + + if (!err) { + ret = mcp3564_read_16bits(adc, MCP3564_RESERVED_E_REG, &tmp_u16); + if (ret < 0) + return ret; + + switch (tmp_u16 & MCP3564_HW_ID_MASK) { + case MCP3461_HW_ID: + if (adc->have_vref) + ids = mcp3461r; + else + ids = mcp3461; + break; + case MCP3462_HW_ID: + if (adc->have_vref) + ids = mcp3462r; + else + ids = mcp3462; + break; + case MCP3464_HW_ID: + if (adc->have_vref) + ids = mcp3464r; + else + ids = mcp3464; + break; + case MCP3561_HW_ID: + if (adc->have_vref) + ids = mcp3561r; + else + ids = mcp3561; + break; + case MCP3562_HW_ID: + if (adc->have_vref) + ids = mcp3562r; + else + ids = mcp3562; + break; + case MCP3564_HW_ID: + if (adc->have_vref) + ids = mcp3564r; + else + ids = mcp3564; + break; + default: + dev_info(dev, "Unknown chip found: %d\n", tmp_u16); + err = true; + } + } + + if (err) { + /* + * If failed to identify the hardware based on internal registers, + * try using fallback compatible in device tree to deal with some newer part number. + */ + adc->chip_info = spi_get_device_match_data(adc->spi); + adc->have_vref = adc->chip_info->have_vref; + } else { + adc->chip_info = &mcp3564_chip_infos_tbl[ids]; + } + + dev_dbg(dev, "Found %s chip\n", adc->chip_info->name); + + ret = devm_regulator_get_enable_read_voltage(dev, "vref"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "Failed to get vref voltage\n"); + + internal_vref = ret == -ENODEV; + adc->vref_mv = internal_vref ? MCP3564R_INT_VREF_MV : ret / MILLI; + *use_internal_vref_attr = internal_vref; + + if (internal_vref) { + /* Check if chip has internal vref */ + if (!adc->have_vref) + return dev_err_probe(dev, -ENODEV, "Unknown Vref\n"); + + dev_dbg(dev, "%s: Using internal Vref\n", __func__); + } else { + dev_dbg(dev, "%s: Using External Vref\n", __func__); + } + + ret = mcp3564_parse_fw_children(indio_dev); + if (ret) + return ret; + + /* + * Command sequence that ensures a recovery with the desired settings + * in any cases of loss-of-power scenario (Full Chip Reset): + * - Write LOCK register to 0xA5 + * - Write IRQ register to 0x03 + * - Send "Device Full Reset" fast command + * - Wait 1ms for "Full Reset" to complete + */ + ret = mcp3564_write_8bits(adc, MCP3564_LOCK_REG, MCP3564_LOCK_WRITE_ACCESS_PASSWORD); + if (ret) + return ret; + + ret = mcp3564_write_8bits(adc, MCP3564_IRQ_REG, 0x03); + if (ret) + return ret; + + ret = mcp3564_fast_cmd(adc, MCP3564_FASTCMD_RESET); + if (ret) + return ret; + + /* + * After Full reset wait some time to be able to fully reset the part and place + * it back in a default configuration. + * From datasheet: POR (Power On Reset Time) is ~1us + * 1ms should be enough. + */ + mdelay(1); + + /* set a gain of 1x for GAINCAL */ + ret = mcp3564_write_24bits(adc, MCP3564_GAINCAL_REG, MCP3564_DEFAULT_GAINCAL); + if (ret) + return ret; + + adc->calib_scale = MCP3564_DEFAULT_GAINCAL; + + ret = mcp3564_write_24bits(adc, MCP3564_OFFSETCAL_REG, MCP3564_DEFAULT_OFFSETCAL); + if (ret) + return ret; + + ret = mcp3564_write_24bits(adc, MCP3564_TIMER_REG, MCP3564_TIMER_DEFAULT_VALUE); + if (ret) + return ret; + + ret = mcp3564_write_24bits(adc, MCP3564_SCAN_REG, + MCP3564_SCAN_DELAY_TIME_SET(MCP3564_NO_DELAY) | + MCP3564_SCAN_CH_SEL_SET(MCP3564_SCAN_DEFAULT_VALUE)); + if (ret) + return ret; + + ret = mcp3564_write_8bits(adc, MCP3564_MUX_REG, MCP3564_MUX_SET(MCP3564_CH0, MCP3564_CH1)); + if (ret) + return ret; + + ret = mcp3564_write_8bits(adc, MCP3564_IRQ_REG, + FIELD_PREP(MCP3464_EN_FASTCMD_MASK, 1) | + FIELD_PREP(MCP3464_EN_STP_MASK, 1)); + if (ret) + return ret; + + tmp_reg = FIELD_PREP(MCP3464_CONFIG3_CONV_MODE_MASK, + MCP3464_CONFIG3_CONV_MODE_ONE_SHOT_STANDBY); + tmp_reg |= FIELD_PREP(MCP3464_CONFIG3_DATA_FORMAT_MASK, + MCP3464_CONFIG3_DATA_FMT_32B_SGN_EXT); + tmp_reg |= MCP3464_CONFIG3_EN_OFFCAL_MASK; + tmp_reg |= MCP3464_CONFIG3_EN_GAINCAL_MASK; + + ret = mcp3564_write_8bits(adc, MCP3564_CONFIG3_REG, tmp_reg); + if (ret) + return ret; + + tmp_reg = FIELD_PREP(MCP3564_CONFIG2_BOOST_CURRENT_MASK, MCP3564_BOOST_CURRENT_x1_00); + tmp_reg |= FIELD_PREP(MCP3564_CONFIG2_HARDWARE_GAIN_MASK, 0x01); + tmp_reg |= FIELD_PREP(MCP3564_CONFIG2_AZ_MUX_MASK, 1); + + ret = mcp3564_write_8bits(adc, MCP3564_CONFIG2_REG, tmp_reg); + if (ret) + return ret; + + adc->hwgain = 0x01; + adc->auto_zeroing_mux = true; + adc->auto_zeroing_ref = false; + adc->current_boost_mode = MCP3564_BOOST_CURRENT_x1_00; + + tmp_reg = FIELD_PREP(MCP3564_CONFIG1_OVERSPL_RATIO_MASK, MCP3564_OVERSAMPLING_RATIO_98304); + + ret = mcp3564_write_8bits(adc, MCP3564_CONFIG1_REG, tmp_reg); + if (ret) + return ret; + + adc->oversampling = MCP3564_OVERSAMPLING_RATIO_98304; + + tmp_reg = FIELD_PREP(MCP3564_CONFIG0_ADC_MODE_MASK, MCP3564_ADC_MODE_STANDBY); + tmp_reg |= FIELD_PREP(MCP3564_CONFIG0_CS_SEL_MASK, MCP3564_CONFIG0_CS_SEL_0_0_uA); + tmp_reg |= FIELD_PREP(MCP3564_CONFIG0_CLK_SEL_MASK, MCP3564_CONFIG0_USE_INT_CLK); + tmp_reg |= MCP3456_CONFIG0_BIT6_DEFAULT; + + if (internal_vref) + tmp_reg |= FIELD_PREP(MCP3456_CONFIG0_VREF_MASK, 1); + + ret = mcp3564_write_8bits(adc, MCP3564_CONFIG0_REG, tmp_reg); + + adc->burnout_mode = MCP3564_CONFIG0_CS_SEL_0_0_uA; + + return ret; +} + +static IIO_DEVICE_ATTR(auto_zeroing_ref_enable, 0644, + mcp3564_auto_zeroing_ref_show, + mcp3564_auto_zeroing_ref_store, 0); + +static IIO_DEVICE_ATTR(auto_zeroing_mux_enable, 0644, + mcp3564_auto_zeroing_mux_show, + mcp3564_auto_zeroing_mux_store, 0); + +static struct attribute *mcp3564_attributes[] = { + &iio_dev_attr_auto_zeroing_mux_enable.dev_attr.attr, + NULL +}; + +static struct attribute *mcp3564r_attributes[] = { + &iio_dev_attr_auto_zeroing_mux_enable.dev_attr.attr, + &iio_dev_attr_auto_zeroing_ref_enable.dev_attr.attr, + NULL +}; + +static struct attribute_group mcp3564_attribute_group = { + .attrs = mcp3564_attributes, +}; + +static struct attribute_group mcp3564r_attribute_group = { + .attrs = mcp3564r_attributes, +}; + +static const struct iio_info mcp3564_info = { + .read_raw = mcp3564_read_raw, + .read_avail = mcp3564_read_avail, + .write_raw = mcp3564_write_raw, + .write_raw_get_fmt = mcp3564_write_raw_get_fmt, + .read_label = mcp3564_read_label, + .attrs = &mcp3564_attribute_group, +}; + +static const struct iio_info mcp3564r_info = { + .read_raw = mcp3564_read_raw, + .read_avail = mcp3564_read_avail, + .write_raw = mcp3564_write_raw, + .write_raw_get_fmt = mcp3564_write_raw_get_fmt, + .read_label = mcp3564_read_label, + .attrs = &mcp3564r_attribute_group, +}; + +static int mcp3564_probe(struct spi_device *spi) +{ + int ret; + struct iio_dev *indio_dev; + struct mcp3564_state *adc; + bool use_internal_vref_attr; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->spi = spi; + + dev_dbg(&spi->dev, "%s: probe(spi = 0x%p)\n", __func__, spi); + + /* + * Do any chip specific initialization, e.g: + * read/write some registers + * enable/disable certain channels + * change the sampling rate to the requested value + */ + ret = mcp3564_config(indio_dev, &use_internal_vref_attr); + if (ret) + return dev_err_probe(&spi->dev, ret, + "Can't configure MCP356X device\n"); + + dev_dbg(&spi->dev, "%s: Vref (mV): %d\n", __func__, adc->vref_mv); + + mcp3564_fill_scale_tbls(adc); + + indio_dev->name = adc->chip_info->name; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (use_internal_vref_attr) + indio_dev->info = &mcp3564r_info; + else + indio_dev->info = &mcp3564_info; + + mutex_init(&adc->lock); + + ret = devm_iio_device_register(&spi->dev, indio_dev); + if (ret) + return dev_err_probe(&spi->dev, ret, + "Can't register IIO device\n"); + + return 0; +} + +static const struct of_device_id mcp3564_dt_ids[] = { + { .compatible = "microchip,mcp3461", .data = &mcp3564_chip_infos_tbl[mcp3461] }, + { .compatible = "microchip,mcp3462", .data = &mcp3564_chip_infos_tbl[mcp3462] }, + { .compatible = "microchip,mcp3464", .data = &mcp3564_chip_infos_tbl[mcp3464] }, + { .compatible = "microchip,mcp3561", .data = &mcp3564_chip_infos_tbl[mcp3561] }, + { .compatible = "microchip,mcp3562", .data = &mcp3564_chip_infos_tbl[mcp3562] }, + { .compatible = "microchip,mcp3564", .data = &mcp3564_chip_infos_tbl[mcp3564] }, + { .compatible = "microchip,mcp3461r", .data = &mcp3564_chip_infos_tbl[mcp3461r] }, + { .compatible = "microchip,mcp3462r", .data = &mcp3564_chip_infos_tbl[mcp3462r] }, + { .compatible = "microchip,mcp3464r", .data = &mcp3564_chip_infos_tbl[mcp3464r] }, + { .compatible = "microchip,mcp3561r", .data = &mcp3564_chip_infos_tbl[mcp3561r] }, + { .compatible = "microchip,mcp3562r", .data = &mcp3564_chip_infos_tbl[mcp3562r] }, + { .compatible = "microchip,mcp3564r", .data = &mcp3564_chip_infos_tbl[mcp3564r] }, + { } +}; +MODULE_DEVICE_TABLE(of, mcp3564_dt_ids); + +static const struct spi_device_id mcp3564_id[] = { + { .name = "mcp3461", .driver_data = (kernel_ulong_t)&mcp3564_chip_infos_tbl[mcp3461] }, + { .name = "mcp3462", .driver_data = (kernel_ulong_t)&mcp3564_chip_infos_tbl[mcp3462] }, + { .name = "mcp3464", .driver_data = (kernel_ulong_t)&mcp3564_chip_infos_tbl[mcp3464] }, + { .name = "mcp3561", .driver_data = (kernel_ulong_t)&mcp3564_chip_infos_tbl[mcp3561] }, + { .name = "mcp3562", .driver_data = (kernel_ulong_t)&mcp3564_chip_infos_tbl[mcp3562] }, + { .name = "mcp3564", .driver_data = (kernel_ulong_t)&mcp3564_chip_infos_tbl[mcp3564] }, + { .name = "mcp3461r", .driver_data = (kernel_ulong_t)&mcp3564_chip_infos_tbl[mcp3461r] }, + { .name = "mcp3462r", .driver_data = (kernel_ulong_t)&mcp3564_chip_infos_tbl[mcp3462r] }, + { .name = "mcp3464r", .driver_data = (kernel_ulong_t)&mcp3564_chip_infos_tbl[mcp3464r] }, + { .name = "mcp3561r", .driver_data = (kernel_ulong_t)&mcp3564_chip_infos_tbl[mcp3561r] }, + { .name = "mcp3562r", .driver_data = (kernel_ulong_t)&mcp3564_chip_infos_tbl[mcp3562r] }, + { .name = "mcp3564r", .driver_data = (kernel_ulong_t)&mcp3564_chip_infos_tbl[mcp3564r] }, + { } +}; +MODULE_DEVICE_TABLE(spi, mcp3564_id); + +static struct spi_driver mcp3564_driver = { + .driver = { + .name = "mcp3564", + .of_match_table = mcp3564_dt_ids, + }, + .probe = mcp3564_probe, + .id_table = mcp3564_id, +}; + +module_spi_driver(mcp3564_driver); + +MODULE_AUTHOR("Marius Cristea "); +MODULE_DESCRIPTION("Microchip MCP346x/MCP346xR and MCP356x/MCP356xR ADCs"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/mcp3911.c b/drivers/iio/adc/mcp3911.c new file mode 100644 index 000000000..797424f59 --- /dev/null +++ b/drivers/iio/adc/mcp3911.c @@ -0,0 +1,954 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Microchip MCP3911, Two-channel Analog Front End + * + * Copyright (C) 2018 Marcus Folkesson + * Copyright (C) 2018 Kent Gustavsson + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#define MCP3911_REG_CHANNEL0 0x00 +#define MCP3911_REG_CHANNEL1 0x03 +#define MCP3911_REG_MOD 0x06 +#define MCP3911_REG_PHASE 0x07 +#define MCP3911_REG_GAIN 0x09 +#define MCP3911_GAIN_MASK(ch) (GENMASK(2, 0) << 3 * (ch)) +#define MCP3911_GAIN_VAL(ch, val) ((val << 3 * (ch)) & MCP3911_GAIN_MASK(ch)) + +#define MCP3911_REG_STATUSCOM 0x0a +#define MCP3911_STATUSCOM_DRHIZ BIT(12) +#define MCP3911_STATUSCOM_READ GENMASK(7, 6) +#define MCP3911_STATUSCOM_CH1_24WIDTH BIT(4) +#define MCP3911_STATUSCOM_CH0_24WIDTH BIT(3) +#define MCP3911_STATUSCOM_EN_OFFCAL BIT(2) +#define MCP3911_STATUSCOM_EN_GAINCAL BIT(1) + +#define MCP3911_REG_CONFIG 0x0c +#define MCP3911_CONFIG_CLKEXT BIT(1) +#define MCP3911_CONFIG_VREFEXT BIT(2) +#define MCP3911_CONFIG_OSR GENMASK(13, 11) + +#define MCP3911_REG_OFFCAL_CH0 0x0e +#define MCP3911_REG_GAINCAL_CH0 0x11 +#define MCP3911_REG_OFFCAL_CH1 0x14 +#define MCP3911_REG_GAINCAL_CH1 0x17 +#define MCP3911_REG_VREFCAL 0x1a + +#define MCP3911_CHANNEL(ch) (MCP3911_REG_CHANNEL0 + (ch) * 3) +#define MCP3911_OFFCAL(ch) (MCP3911_REG_OFFCAL_CH0 + (ch) * 6) + +/* Internal voltage reference in mV */ +#define MCP3911_INT_VREF_MV 1200 + +#define MCP3911_REG_READ(reg, id) ((((reg) << 1) | ((id) << 6) | (1 << 0)) & 0xff) +#define MCP3911_REG_WRITE(reg, id) ((((reg) << 1) | ((id) << 6) | (0 << 0)) & 0xff) +#define MCP3911_REG_MASK GENMASK(4, 1) + +#define MCP3911_NUM_SCALES 6 + +/* Registers compatible with MCP3910 */ +#define MCP3910_REG_STATUSCOM 0x0c +#define MCP3910_STATUSCOM_READ GENMASK(23, 22) +#define MCP3910_STATUSCOM_DRHIZ BIT(20) + +#define MCP3910_REG_GAIN 0x0b + +#define MCP3910_REG_CONFIG0 0x0d +#define MCP3910_CONFIG0_EN_OFFCAL BIT(23) +#define MCP3910_CONFIG0_OSR GENMASK(15, 13) + +#define MCP3910_REG_CONFIG1 0x0e +#define MCP3910_CONFIG1_CLKEXT BIT(6) +#define MCP3910_CONFIG1_VREFEXT BIT(7) + +#define MCP3910_CHANNEL(ch) (MCP3911_REG_CHANNEL0 + (ch)) + +#define MCP3910_REG_OFFCAL_CH0 0x0f +#define MCP3910_OFFCAL(ch) (MCP3910_REG_OFFCAL_CH0 + (ch) * 6) + +/* Maximal number of channels used by the MCP39XX family */ +#define MCP39XX_MAX_NUM_CHANNELS 8 + +static const int mcp3911_osr_table[] = { 32, 64, 128, 256, 512, 1024, 2048, 4096 }; +static u32 mcp3911_scale_table[MCP3911_NUM_SCALES][2]; + +enum mcp3911_id { + MCP3910, + MCP3911, + MCP3912, + MCP3913, + MCP3914, + MCP3918, + MCP3919, +}; + +struct mcp3911; +struct mcp3911_chip_info { + const struct iio_chan_spec *channels; + unsigned int num_channels; + + int (*config)(struct mcp3911 *adc, bool external_vref); + int (*get_osr)(struct mcp3911 *adc, u32 *val); + int (*set_osr)(struct mcp3911 *adc, u32 val); + int (*enable_offset)(struct mcp3911 *adc, bool enable); + int (*get_offset)(struct mcp3911 *adc, int channel, int *val); + int (*set_offset)(struct mcp3911 *adc, int channel, int val); + int (*set_scale)(struct mcp3911 *adc, int channel, u32 val); + int (*get_raw)(struct mcp3911 *adc, int channel, int *val); +}; + +struct mcp3911 { + struct spi_device *spi; + struct mutex lock; + struct clk *clki; + u32 dev_addr; + struct iio_trigger *trig; + u32 gain[MCP39XX_MAX_NUM_CHANNELS]; + const struct mcp3911_chip_info *chip; + struct { + u32 channels[MCP39XX_MAX_NUM_CHANNELS]; + aligned_s64 ts; + } scan; + + u8 tx_buf __aligned(IIO_DMA_MINALIGN); + u8 rx_buf[MCP39XX_MAX_NUM_CHANNELS * 3]; +}; + +static int mcp3911_read(struct mcp3911 *adc, u8 reg, u32 *val, u8 len) +{ + int ret; + + reg = MCP3911_REG_READ(reg, adc->dev_addr); + ret = spi_write_then_read(adc->spi, ®, 1, val, len); + if (ret < 0) + return ret; + + be32_to_cpus(val); + *val >>= ((4 - len) * 8); + dev_dbg(&adc->spi->dev, "reading 0x%x from register 0x%lx\n", *val, + FIELD_GET(MCP3911_REG_MASK, reg)); + return ret; +} + +static int mcp3911_write(struct mcp3911 *adc, u8 reg, u32 val, u8 len) +{ + dev_dbg(&adc->spi->dev, "writing 0x%x to register 0x%x\n", val, reg); + + val <<= (3 - len) * 8; + cpu_to_be32s(&val); + val |= MCP3911_REG_WRITE(reg, adc->dev_addr); + + return spi_write(adc->spi, &val, len + 1); +} + +static int mcp3911_update(struct mcp3911 *adc, u8 reg, u32 mask, u32 val, u8 len) +{ + u32 tmp; + int ret; + + ret = mcp3911_read(adc, reg, &tmp, len); + if (ret) + return ret; + + val &= mask; + val |= tmp & ~mask; + return mcp3911_write(adc, reg, val, len); +} + +static int mcp3911_read_s24(struct mcp3911 *const adc, u8 const reg, s32 *const val) +{ + u32 uval; + int const ret = mcp3911_read(adc, reg, &uval, 3); + + if (ret) + return ret; + + *val = sign_extend32(uval, 23); + return ret; +} + +static int mcp3910_enable_offset(struct mcp3911 *adc, bool enable) +{ + unsigned int mask = MCP3910_CONFIG0_EN_OFFCAL; + unsigned int value = enable ? mask : 0; + + return mcp3911_update(adc, MCP3910_REG_CONFIG0, mask, value, 3); +} + +static int mcp3910_get_offset(struct mcp3911 *adc, int channel, int *val) +{ + return mcp3911_read(adc, MCP3910_OFFCAL(channel), val, 3); +} + +static int mcp3910_set_offset(struct mcp3911 *adc, int channel, int val) +{ + int ret; + + ret = mcp3911_write(adc, MCP3910_OFFCAL(channel), val, 3); + if (ret) + return ret; + + return adc->chip->enable_offset(adc, 1); +} + +static int mcp3910_get_raw(struct mcp3911 *adc, int channel, s32 *val) +{ + return mcp3911_read_s24(adc, MCP3910_CHANNEL(channel), val); +} + +static int mcp3911_enable_offset(struct mcp3911 *adc, bool enable) +{ + unsigned int mask = MCP3911_STATUSCOM_EN_OFFCAL; + unsigned int value = enable ? mask : 0; + + return mcp3911_update(adc, MCP3911_REG_STATUSCOM, mask, value, 2); +} + +static int mcp3911_get_offset(struct mcp3911 *adc, int channel, int *val) +{ + return mcp3911_read(adc, MCP3911_OFFCAL(channel), val, 3); +} + +static int mcp3911_set_offset(struct mcp3911 *adc, int channel, int val) +{ + int ret; + + ret = mcp3911_write(adc, MCP3911_OFFCAL(channel), val, 3); + if (ret) + return ret; + + return adc->chip->enable_offset(adc, 1); +} + +static int mcp3911_get_raw(struct mcp3911 *adc, int channel, s32 *val) +{ + return mcp3911_read_s24(adc, MCP3911_CHANNEL(channel), val); +} + +static int mcp3910_get_osr(struct mcp3911 *adc, u32 *val) +{ + int ret; + unsigned int osr; + + ret = mcp3911_read(adc, MCP3910_REG_CONFIG0, val, 3); + if (ret) + return ret; + + osr = FIELD_GET(MCP3910_CONFIG0_OSR, *val); + *val = 32 << osr; + return 0; +} + +static int mcp3910_set_osr(struct mcp3911 *adc, u32 val) +{ + unsigned int osr = FIELD_PREP(MCP3910_CONFIG0_OSR, val); + unsigned int mask = MCP3910_CONFIG0_OSR; + + return mcp3911_update(adc, MCP3910_REG_CONFIG0, mask, osr, 3); +} + +static int mcp3911_set_osr(struct mcp3911 *adc, u32 val) +{ + unsigned int osr = FIELD_PREP(MCP3911_CONFIG_OSR, val); + unsigned int mask = MCP3911_CONFIG_OSR; + + return mcp3911_update(adc, MCP3911_REG_CONFIG, mask, osr, 2); +} + +static int mcp3911_get_osr(struct mcp3911 *adc, u32 *val) +{ + int ret; + unsigned int osr; + + ret = mcp3911_read(adc, MCP3911_REG_CONFIG, val, 2); + if (ret) + return ret; + + osr = FIELD_GET(MCP3911_CONFIG_OSR, *val); + *val = 32 << osr; + return ret; +} + +static int mcp3910_set_scale(struct mcp3911 *adc, int channel, u32 val) +{ + return mcp3911_update(adc, MCP3910_REG_GAIN, + MCP3911_GAIN_MASK(channel), + MCP3911_GAIN_VAL(channel, val), 3); +} + +static int mcp3911_set_scale(struct mcp3911 *adc, int channel, u32 val) +{ + return mcp3911_update(adc, MCP3911_REG_GAIN, + MCP3911_GAIN_MASK(channel), + MCP3911_GAIN_VAL(channel, val), 1); +} + +static int mcp3911_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return IIO_VAL_INT; + default: + return IIO_VAL_INT_PLUS_NANO; + } +} + +static int mcp3911_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long info) +{ + switch (info) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *type = IIO_VAL_INT; + *vals = mcp3911_osr_table; + *length = ARRAY_SIZE(mcp3911_osr_table); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SCALE: + *type = IIO_VAL_INT_PLUS_NANO; + *vals = (int *)mcp3911_scale_table; + *length = ARRAY_SIZE(mcp3911_scale_table) * 2; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int mcp3911_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct mcp3911 *adc = iio_priv(indio_dev); + int ret; + + guard(mutex)(&adc->lock); + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = adc->chip->get_raw(adc, channel->channel, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + ret = adc->chip->get_offset(adc, channel->channel, val); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + ret = adc->chip->get_osr(adc, val); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = mcp3911_scale_table[ilog2(adc->gain[channel->channel])][0]; + *val2 = mcp3911_scale_table[ilog2(adc->gain[channel->channel])][1]; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static int mcp3911_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int val, + int val2, long mask) +{ + struct mcp3911 *adc = iio_priv(indio_dev); + + guard(mutex)(&adc->lock); + switch (mask) { + case IIO_CHAN_INFO_SCALE: + for (int i = 0; i < MCP3911_NUM_SCALES; i++) { + if (val == mcp3911_scale_table[i][0] && + val2 == mcp3911_scale_table[i][1]) { + + adc->gain[channel->channel] = BIT(i); + return adc->chip->set_scale(adc, channel->channel, i); + } + } + return -EINVAL; + case IIO_CHAN_INFO_OFFSET: + if (val2 != 0) + return -EINVAL; + + return adc->chip->set_offset(adc, channel->channel, val); + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + for (int i = 0; i < ARRAY_SIZE(mcp3911_osr_table); i++) { + if (val == mcp3911_osr_table[i]) { + return adc->chip->set_osr(adc, i); + } + } + return -EINVAL; + default: + return -EINVAL; + } +} + +static int mcp3911_calc_scale_table(u32 vref_mv) +{ + u32 div; + u64 tmp; + + /* + * For 24-bit Conversion + * Raw = ((Voltage)/(Vref) * 2^23 * Gain * 1.5 + * Voltage = Raw * (Vref)/(2^23 * Gain * 1.5) + * + * ref = Reference voltage + * div = (2^23 * 1.5 * gain) = 12582912 * gain + */ + for (int i = 0; i < MCP3911_NUM_SCALES; i++) { + div = 12582912 * BIT(i); + tmp = div_s64((s64)vref_mv * 1000000000LL, div); + + mcp3911_scale_table[i][0] = 0; + mcp3911_scale_table[i][1] = tmp; + } + + return 0; +} + +#define MCP3911_CHAN(idx) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = idx, \ + .scan_index = idx, \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_separate_available = \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_type = { \ + .sign = 's', \ + .realbits = 24, \ + .storagebits = 32, \ + .endianness = IIO_BE, \ + }, \ +} + +static const struct iio_chan_spec mcp3910_channels[] = { + MCP3911_CHAN(0), + MCP3911_CHAN(1), + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static const struct iio_chan_spec mcp3911_channels[] = { + MCP3911_CHAN(0), + MCP3911_CHAN(1), + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static const struct iio_chan_spec mcp3912_channels[] = { + MCP3911_CHAN(0), + MCP3911_CHAN(1), + MCP3911_CHAN(2), + MCP3911_CHAN(3), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const struct iio_chan_spec mcp3913_channels[] = { + MCP3911_CHAN(0), + MCP3911_CHAN(1), + MCP3911_CHAN(2), + MCP3911_CHAN(3), + MCP3911_CHAN(4), + MCP3911_CHAN(5), + IIO_CHAN_SOFT_TIMESTAMP(6), +}; + +static const struct iio_chan_spec mcp3914_channels[] = { + MCP3911_CHAN(0), + MCP3911_CHAN(1), + MCP3911_CHAN(2), + MCP3911_CHAN(3), + MCP3911_CHAN(4), + MCP3911_CHAN(5), + MCP3911_CHAN(6), + MCP3911_CHAN(7), + IIO_CHAN_SOFT_TIMESTAMP(8), +}; + +static const struct iio_chan_spec mcp3918_channels[] = { + MCP3911_CHAN(0), + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct iio_chan_spec mcp3919_channels[] = { + MCP3911_CHAN(0), + MCP3911_CHAN(1), + MCP3911_CHAN(2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static irqreturn_t mcp3911_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct mcp3911 *adc = iio_priv(indio_dev); + struct device *dev = &adc->spi->dev; + struct spi_transfer xfer[] = { + { + .tx_buf = &adc->tx_buf, + .len = 1, + }, { + .rx_buf = adc->rx_buf, + .len = (adc->chip->num_channels - 1) * 3, + }, + }; + int scan_index; + int i = 0; + int ret; + + guard(mutex)(&adc->lock); + adc->tx_buf = MCP3911_REG_READ(MCP3911_CHANNEL(0), adc->dev_addr); + ret = spi_sync_transfer(adc->spi, xfer, ARRAY_SIZE(xfer)); + if (ret < 0) { + dev_warn(dev, "failed to get conversion data\n"); + goto out; + } + + iio_for_each_active_channel(indio_dev, scan_index) { + const struct iio_chan_spec *scan_chan = &indio_dev->channels[scan_index]; + + adc->scan.channels[i] = get_unaligned_be24(&adc->rx_buf[scan_chan->channel * 3]); + i++; + } + iio_push_to_buffers_with_ts(indio_dev, &adc->scan, sizeof(adc->scan), + iio_get_time_ns(indio_dev)); +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_info mcp3911_info = { + .read_raw = mcp3911_read_raw, + .write_raw = mcp3911_write_raw, + .read_avail = mcp3911_read_avail, + .write_raw_get_fmt = mcp3911_write_raw_get_fmt, +}; + +static int mcp3911_config(struct mcp3911 *adc, bool external_vref) +{ + struct device *dev = &adc->spi->dev; + u32 regval; + int ret; + + ret = mcp3911_read(adc, MCP3911_REG_CONFIG, ®val, 2); + if (ret) + return ret; + + regval &= ~MCP3911_CONFIG_VREFEXT; + if (external_vref) { + dev_dbg(dev, "use external voltage reference\n"); + regval |= FIELD_PREP(MCP3911_CONFIG_VREFEXT, 1); + } else { + dev_dbg(dev, "use internal voltage reference (1.2V)\n"); + regval |= FIELD_PREP(MCP3911_CONFIG_VREFEXT, 0); + } + + regval &= ~MCP3911_CONFIG_CLKEXT; + if (adc->clki) { + dev_dbg(dev, "use external clock as clocksource\n"); + regval |= FIELD_PREP(MCP3911_CONFIG_CLKEXT, 1); + } else { + dev_dbg(dev, "use crystal oscillator as clocksource\n"); + regval |= FIELD_PREP(MCP3911_CONFIG_CLKEXT, 0); + } + + ret = mcp3911_write(adc, MCP3911_REG_CONFIG, regval, 2); + if (ret) + return ret; + + ret = mcp3911_read(adc, MCP3911_REG_STATUSCOM, ®val, 2); + if (ret) + return ret; + + /* Address counter incremented, cycle through register types */ + regval &= ~MCP3911_STATUSCOM_READ; + regval |= FIELD_PREP(MCP3911_STATUSCOM_READ, 0x02); + + regval &= ~MCP3911_STATUSCOM_DRHIZ; + if (device_property_read_bool(dev, "microchip,data-ready-hiz")) + regval |= FIELD_PREP(MCP3911_STATUSCOM_DRHIZ, 0); + else + regval |= FIELD_PREP(MCP3911_STATUSCOM_DRHIZ, 1); + + /* Disable offset to ignore any old values in offset register */ + regval &= ~MCP3911_STATUSCOM_EN_OFFCAL; + + ret = mcp3911_write(adc, MCP3911_REG_STATUSCOM, regval, 2); + if (ret) + return ret; + + /* Set gain to 1 for all channels */ + ret = mcp3911_read(adc, MCP3911_REG_GAIN, ®val, 1); + if (ret) + return ret; + + for (int i = 0; i < adc->chip->num_channels - 1; i++) { + adc->gain[i] = 1; + regval &= ~MCP3911_GAIN_MASK(i); + } + + return mcp3911_write(adc, MCP3911_REG_GAIN, regval, 1); +} + +static int mcp3910_config(struct mcp3911 *adc, bool external_vref) +{ + struct device *dev = &adc->spi->dev; + u32 regval; + int ret; + + ret = mcp3911_read(adc, MCP3910_REG_CONFIG1, ®val, 3); + if (ret) + return ret; + + regval &= ~MCP3910_CONFIG1_VREFEXT; + if (external_vref) { + dev_dbg(dev, "use external voltage reference\n"); + regval |= FIELD_PREP(MCP3910_CONFIG1_VREFEXT, 1); + } else { + dev_dbg(dev, "use internal voltage reference (1.2V)\n"); + regval |= FIELD_PREP(MCP3910_CONFIG1_VREFEXT, 0); + } + + regval &= ~MCP3910_CONFIG1_CLKEXT; + if (adc->clki) { + dev_dbg(dev, "use external clock as clocksource\n"); + regval |= FIELD_PREP(MCP3910_CONFIG1_CLKEXT, 1); + } else { + dev_dbg(dev, "use crystal oscillator as clocksource\n"); + regval |= FIELD_PREP(MCP3910_CONFIG1_CLKEXT, 0); + } + + ret = mcp3911_write(adc, MCP3910_REG_CONFIG1, regval, 3); + if (ret) + return ret; + + ret = mcp3911_read(adc, MCP3910_REG_STATUSCOM, ®val, 3); + if (ret) + return ret; + + /* Address counter incremented, cycle through register types */ + regval &= ~MCP3910_STATUSCOM_READ; + regval |= FIELD_PREP(MCP3910_STATUSCOM_READ, 0x02); + + regval &= ~MCP3910_STATUSCOM_DRHIZ; + if (device_property_read_bool(dev, "microchip,data-ready-hiz")) + regval |= FIELD_PREP(MCP3910_STATUSCOM_DRHIZ, 0); + else + regval |= FIELD_PREP(MCP3910_STATUSCOM_DRHIZ, 1); + + ret = mcp3911_write(adc, MCP3910_REG_STATUSCOM, regval, 3); + if (ret) + return ret; + + /* Set gain to 1 for all channels */ + ret = mcp3911_read(adc, MCP3910_REG_GAIN, ®val, 3); + if (ret) + return ret; + + for (int i = 0; i < adc->chip->num_channels - 1; i++) { + adc->gain[i] = 1; + regval &= ~MCP3911_GAIN_MASK(i); + } + ret = mcp3911_write(adc, MCP3910_REG_GAIN, regval, 3); + if (ret) + return ret; + + /* Disable offset to ignore any old values in offset register */ + return adc->chip->enable_offset(adc, 0); +} + +static int mcp3911_set_trigger_state(struct iio_trigger *trig, bool enable) +{ + struct mcp3911 *adc = iio_trigger_get_drvdata(trig); + + if (enable) + enable_irq(adc->spi->irq); + else + disable_irq(adc->spi->irq); + + return 0; +} + +static const struct iio_trigger_ops mcp3911_trigger_ops = { + .validate_device = iio_trigger_validate_own_device, + .set_trigger_state = mcp3911_set_trigger_state, +}; + +static int mcp3911_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct gpio_desc *gpio_reset; + struct iio_dev *indio_dev; + struct mcp3911 *adc; + bool external_vref; + u32 vref_mv; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->spi = spi; + adc->chip = spi_get_device_match_data(spi); + + ret = devm_regulator_get_enable_read_voltage(dev, "vref"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to get vref voltage\n"); + + external_vref = ret != -ENODEV; + vref_mv = external_vref ? ret / 1000 : MCP3911_INT_VREF_MV; + + adc->clki = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(adc->clki)) { + if (PTR_ERR(adc->clki) == -ENOENT) { + adc->clki = NULL; + } else { + return dev_err_probe(dev, PTR_ERR(adc->clki), "failed to get adc clk\n"); + } + } + + /* + * Fallback to "device-addr" due to historical mismatch between + * dt-bindings and implementation. + */ + ret = device_property_read_u32(dev, "microchip,device-addr", &adc->dev_addr); + if (ret) + device_property_read_u32(dev, "device-addr", &adc->dev_addr); + if (adc->dev_addr > 3) { + return dev_err_probe(dev, -EINVAL, + "invalid device address (%i). Must be in range 0-3.\n", + adc->dev_addr); + } + dev_dbg(dev, "use device address %i\n", adc->dev_addr); + + gpio_reset = devm_gpiod_get_optional(&spi->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(gpio_reset)) + return dev_err_probe(dev, PTR_ERR(gpio_reset), + "Cannot get reset GPIO\n"); + + if (gpio_reset) { + gpiod_set_value_cansleep(gpio_reset, 0); + + /* + * Settling time after Hard Reset Mode (determined experimentally): + * 330 micro-seconds are too few; 470 micro-seconds are sufficient. + * Just in case, we add some safety factor... + */ + fsleep(600); + } + + ret = adc->chip->config(adc, external_vref); + if (ret) + return ret; + + ret = mcp3911_calc_scale_table(vref_mv); + if (ret) + return ret; + + /* Set gain to 1 for all channels */ + for (int i = 0; i < adc->chip->num_channels - 1; i++) { + adc->gain[i] = 1; + ret = mcp3911_update(adc, MCP3911_REG_GAIN, + MCP3911_GAIN_MASK(i), + MCP3911_GAIN_VAL(i, 0), 1); + if (ret) + return ret; + } + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &mcp3911_info; + spi_set_drvdata(spi, indio_dev); + + indio_dev->channels = adc->chip->channels; + indio_dev->num_channels = adc->chip->num_channels; + + mutex_init(&adc->lock); + + if (spi->irq > 0) { + adc->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, + iio_device_id(indio_dev)); + if (!adc->trig) + return -ENOMEM; + + adc->trig->ops = &mcp3911_trigger_ops; + iio_trigger_set_drvdata(adc->trig, adc); + ret = devm_iio_trigger_register(dev, adc->trig); + if (ret) + return ret; + + /* + * The device generates interrupts as long as it is powered up. + * Some platforms might not allow the option to power it down so + * don't enable the interrupt to avoid extra load on the system. + */ + ret = devm_request_irq(dev, spi->irq, &iio_trigger_generic_data_rdy_poll, + IRQF_NO_AUTOEN | IRQF_NO_THREAD, + indio_dev->name, adc->trig); + if (ret) + return ret; + } + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + mcp3911_trigger_handler, NULL); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct mcp3911_chip_info mcp3911_chip_info[] = { + [MCP3910] = { + .channels = mcp3910_channels, + .num_channels = ARRAY_SIZE(mcp3910_channels), + .config = mcp3910_config, + .get_osr = mcp3910_get_osr, + .set_osr = mcp3910_set_osr, + .enable_offset = mcp3910_enable_offset, + .get_offset = mcp3910_get_offset, + .set_offset = mcp3910_set_offset, + .set_scale = mcp3910_set_scale, + .get_raw = mcp3910_get_raw, + }, + [MCP3911] = { + .channels = mcp3911_channels, + .num_channels = ARRAY_SIZE(mcp3911_channels), + .config = mcp3911_config, + .get_osr = mcp3911_get_osr, + .set_osr = mcp3911_set_osr, + .enable_offset = mcp3911_enable_offset, + .get_offset = mcp3911_get_offset, + .set_offset = mcp3911_set_offset, + .set_scale = mcp3911_set_scale, + .get_raw = mcp3911_get_raw, + }, + [MCP3912] = { + .channels = mcp3912_channels, + .num_channels = ARRAY_SIZE(mcp3912_channels), + .config = mcp3910_config, + .get_osr = mcp3910_get_osr, + .set_osr = mcp3910_set_osr, + .enable_offset = mcp3910_enable_offset, + .get_offset = mcp3910_get_offset, + .set_offset = mcp3910_set_offset, + .set_scale = mcp3910_set_scale, + .get_raw = mcp3910_get_raw, + }, + [MCP3913] = { + .channels = mcp3913_channels, + .num_channels = ARRAY_SIZE(mcp3913_channels), + .config = mcp3910_config, + .get_osr = mcp3910_get_osr, + .set_osr = mcp3910_set_osr, + .enable_offset = mcp3910_enable_offset, + .get_offset = mcp3910_get_offset, + .set_offset = mcp3910_set_offset, + .set_scale = mcp3910_set_scale, + .get_raw = mcp3910_get_raw, + }, + [MCP3914] = { + .channels = mcp3914_channels, + .num_channels = ARRAY_SIZE(mcp3914_channels), + .config = mcp3910_config, + .get_osr = mcp3910_get_osr, + .set_osr = mcp3910_set_osr, + .enable_offset = mcp3910_enable_offset, + .get_offset = mcp3910_get_offset, + .set_offset = mcp3910_set_offset, + .set_scale = mcp3910_set_scale, + .get_raw = mcp3910_get_raw, + }, + [MCP3918] = { + .channels = mcp3918_channels, + .num_channels = ARRAY_SIZE(mcp3918_channels), + .config = mcp3910_config, + .get_osr = mcp3910_get_osr, + .set_osr = mcp3910_set_osr, + .enable_offset = mcp3910_enable_offset, + .get_offset = mcp3910_get_offset, + .set_offset = mcp3910_set_offset, + .set_scale = mcp3910_set_scale, + .get_raw = mcp3910_get_raw, + }, + [MCP3919] = { + .channels = mcp3919_channels, + .num_channels = ARRAY_SIZE(mcp3919_channels), + .config = mcp3910_config, + .get_osr = mcp3910_get_osr, + .set_osr = mcp3910_set_osr, + .enable_offset = mcp3910_enable_offset, + .get_offset = mcp3910_get_offset, + .set_offset = mcp3910_set_offset, + .set_scale = mcp3910_set_scale, + .get_raw = mcp3910_get_raw, + }, +}; +static const struct of_device_id mcp3911_dt_ids[] = { + { .compatible = "microchip,mcp3910", .data = &mcp3911_chip_info[MCP3910] }, + { .compatible = "microchip,mcp3911", .data = &mcp3911_chip_info[MCP3911] }, + { .compatible = "microchip,mcp3912", .data = &mcp3911_chip_info[MCP3912] }, + { .compatible = "microchip,mcp3913", .data = &mcp3911_chip_info[MCP3913] }, + { .compatible = "microchip,mcp3914", .data = &mcp3911_chip_info[MCP3914] }, + { .compatible = "microchip,mcp3918", .data = &mcp3911_chip_info[MCP3918] }, + { .compatible = "microchip,mcp3919", .data = &mcp3911_chip_info[MCP3919] }, + { } +}; +MODULE_DEVICE_TABLE(of, mcp3911_dt_ids); + +static const struct spi_device_id mcp3911_id[] = { + { .name = "mcp3910", .driver_data = (kernel_ulong_t)&mcp3911_chip_info[MCP3910] }, + { .name = "mcp3911", .driver_data = (kernel_ulong_t)&mcp3911_chip_info[MCP3911] }, + { .name = "mcp3912", .driver_data = (kernel_ulong_t)&mcp3911_chip_info[MCP3912] }, + { .name = "mcp3913", .driver_data = (kernel_ulong_t)&mcp3911_chip_info[MCP3913] }, + { .name = "mcp3914", .driver_data = (kernel_ulong_t)&mcp3911_chip_info[MCP3914] }, + { .name = "mcp3918", .driver_data = (kernel_ulong_t)&mcp3911_chip_info[MCP3918] }, + { .name = "mcp3919", .driver_data = (kernel_ulong_t)&mcp3911_chip_info[MCP3919] }, + { } +}; +MODULE_DEVICE_TABLE(spi, mcp3911_id); + +static struct spi_driver mcp3911_driver = { + .driver = { + .name = "mcp3911", + .of_match_table = mcp3911_dt_ids, + }, + .probe = mcp3911_probe, + .id_table = mcp3911_id, +}; +module_spi_driver(mcp3911_driver); + +MODULE_AUTHOR("Marcus Folkesson "); +MODULE_AUTHOR("Kent Gustavsson "); +MODULE_DESCRIPTION("Microchip Technology MCP3911"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/men_z188_adc.c b/drivers/iio/adc/men_z188_adc.c new file mode 100644 index 000000000..5bd334ec5 --- /dev/null +++ b/drivers/iio/adc/men_z188_adc.c @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MEN 16z188 Analog to Digital Converter + * + * Copyright (C) 2014 MEN Mikroelektronik GmbH (www.men.de) + * Author: Johannes Thumshirn + */ + +#include +#include +#include +#include +#include + +#define Z188_ADC_MAX_CHAN 8 +#define Z188_ADC_GAIN 0x0700000 +#define Z188_MODE_VOLTAGE BIT(27) +#define Z188_CFG_AUTO 0x1 +#define Z188_CTRL_REG 0x40 + +#define ADC_DATA(x) (((x) >> 2) & 0x7ffffc) +#define ADC_OVR(x) ((x) & 0x1) + +struct z188_adc { + struct resource *mem; + void __iomem *base; +}; + +#define Z188_ADC_CHANNEL(idx) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ +} + +static const struct iio_chan_spec z188_adc_iio_channels[] = { + Z188_ADC_CHANNEL(0), + Z188_ADC_CHANNEL(1), + Z188_ADC_CHANNEL(2), + Z188_ADC_CHANNEL(3), + Z188_ADC_CHANNEL(4), + Z188_ADC_CHANNEL(5), + Z188_ADC_CHANNEL(6), + Z188_ADC_CHANNEL(7), +}; + +static int z188_iio_read_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long info) +{ + struct z188_adc *adc = iio_priv(iio_dev); + int ret; + u16 tmp; + + switch (info) { + case IIO_CHAN_INFO_RAW: + tmp = readw(adc->base + chan->channel * 4); + + if (ADC_OVR(tmp)) { + dev_info(&iio_dev->dev, + "Oversampling error on ADC channel %d\n", + chan->channel); + return -EIO; + } + *val = ADC_DATA(tmp); + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct iio_info z188_adc_info = { + .read_raw = &z188_iio_read_raw, +}; + +static void men_z188_config_channels(void __iomem *addr) +{ + int i; + u32 cfg; + u32 ctl; + + ctl = readl(addr + Z188_CTRL_REG); + ctl |= Z188_CFG_AUTO; + writel(ctl, addr + Z188_CTRL_REG); + + for (i = 0; i < Z188_ADC_MAX_CHAN; i++) { + cfg = readl(addr + i); + cfg &= ~Z188_ADC_GAIN; + cfg |= Z188_MODE_VOLTAGE; + writel(cfg, addr + i); + } +} + +static int men_z188_probe(struct mcb_device *dev, + const struct mcb_device_id *id) +{ + struct z188_adc *adc; + struct iio_dev *indio_dev; + struct resource *mem; + int ret; + + indio_dev = devm_iio_device_alloc(&dev->dev, sizeof(struct z188_adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + indio_dev->name = "z188-adc"; + indio_dev->info = &z188_adc_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = z188_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(z188_adc_iio_channels); + + mem = mcb_request_mem(dev, "z188-adc"); + if (IS_ERR(mem)) + return PTR_ERR(mem); + + adc->base = ioremap(mem->start, resource_size(mem)); + if (adc->base == NULL) + goto err; + + men_z188_config_channels(adc->base); + + adc->mem = mem; + mcb_set_drvdata(dev, indio_dev); + + ret = iio_device_register(indio_dev); + if (ret) + goto err_unmap; + + return 0; + +err_unmap: + iounmap(adc->base); +err: + mcb_release_mem(mem); + return -ENXIO; +} + +static void men_z188_remove(struct mcb_device *dev) +{ + struct iio_dev *indio_dev = mcb_get_drvdata(dev); + struct z188_adc *adc = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iounmap(adc->base); + mcb_release_mem(adc->mem); +} + +static const struct mcb_device_id men_z188_ids[] = { + { .device = 0xbc }, + { } +}; +MODULE_DEVICE_TABLE(mcb, men_z188_ids); + +static struct mcb_driver men_z188_driver = { + .driver = { + .name = "z188-adc", + }, + .probe = men_z188_probe, + .remove = men_z188_remove, + .id_table = men_z188_ids, +}; +module_mcb_driver(men_z188_driver); + +MODULE_AUTHOR("Johannes Thumshirn "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("IIO ADC driver for MEN 16z188 ADC Core"); +MODULE_IMPORT_NS("MCB"); diff --git a/drivers/iio/adc/meson_saradc.c b/drivers/iio/adc/meson_saradc.c new file mode 100644 index 000000000..000e39ca5 --- /dev/null +++ b/drivers/iio/adc/meson_saradc.c @@ -0,0 +1,1530 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Amlogic Meson Successive Approximation Register (SAR) A/D Converter + * + * Copyright (C) 2017 Martin Blumenstingl + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MESON_SAR_ADC_REG0 0x00 + #define MESON_SAR_ADC_REG0_PANEL_DETECT BIT(31) + #define MESON_SAR_ADC_REG0_BUSY_MASK GENMASK(30, 28) + #define MESON_SAR_ADC_REG0_DELTA_BUSY BIT(30) + #define MESON_SAR_ADC_REG0_AVG_BUSY BIT(29) + #define MESON_SAR_ADC_REG0_SAMPLE_BUSY BIT(28) + #define MESON_SAR_ADC_REG0_FIFO_FULL BIT(27) + #define MESON_SAR_ADC_REG0_FIFO_EMPTY BIT(26) + #define MESON_SAR_ADC_REG0_FIFO_COUNT_MASK GENMASK(25, 21) + #define MESON_SAR_ADC_REG0_ADC_BIAS_CTRL_MASK GENMASK(20, 19) + #define MESON_SAR_ADC_REG0_CURR_CHAN_ID_MASK GENMASK(18, 16) + #define MESON_SAR_ADC_REG0_ADC_TEMP_SEN_SEL BIT(15) + #define MESON_SAR_ADC_REG0_SAMPLING_STOP BIT(14) + #define MESON_SAR_ADC_REG0_CHAN_DELTA_EN_MASK GENMASK(13, 12) + #define MESON_SAR_ADC_REG0_DETECT_IRQ_POL BIT(10) + #define MESON_SAR_ADC_REG0_DETECT_IRQ_EN BIT(9) + #define MESON_SAR_ADC_REG0_FIFO_CNT_IRQ_MASK GENMASK(8, 4) + #define MESON_SAR_ADC_REG0_FIFO_IRQ_EN BIT(3) + #define MESON_SAR_ADC_REG0_SAMPLING_START BIT(2) + #define MESON_SAR_ADC_REG0_CONTINUOUS_EN BIT(1) + #define MESON_SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE BIT(0) + +#define MESON_SAR_ADC_CHAN_LIST 0x04 + #define MESON_SAR_ADC_CHAN_LIST_MAX_INDEX_MASK GENMASK(26, 24) + #define MESON_SAR_ADC_CHAN_LIST_ENTRY_MASK(_chan) \ + (GENMASK(2, 0) << ((_chan) * 3)) + +#define MESON_SAR_ADC_AVG_CNTL 0x08 + #define MESON_SAR_ADC_AVG_CNTL_AVG_MODE_SHIFT(_chan) \ + (16 + ((_chan) * 2)) + #define MESON_SAR_ADC_AVG_CNTL_AVG_MODE_MASK(_chan) \ + (GENMASK(17, 16) << ((_chan) * 2)) + #define MESON_SAR_ADC_AVG_CNTL_NUM_SAMPLES_SHIFT(_chan) \ + (0 + ((_chan) * 2)) + #define MESON_SAR_ADC_AVG_CNTL_NUM_SAMPLES_MASK(_chan) \ + (GENMASK(1, 0) << ((_chan) * 2)) + +#define MESON_SAR_ADC_REG3 0x0c + #define MESON_SAR_ADC_REG3_CNTL_USE_SC_DLY BIT(31) + #define MESON_SAR_ADC_REG3_CLK_EN BIT(30) + #define MESON_SAR_ADC_REG3_BL30_INITIALIZED BIT(28) + #define MESON_SAR_ADC_REG3_CTRL_CONT_RING_COUNTER_EN BIT(27) + #define MESON_SAR_ADC_REG3_CTRL_SAMPLING_CLOCK_PHASE BIT(26) + #define MESON_SAR_ADC_REG3_CTRL_CHAN7_MUX_SEL_MASK GENMASK(25, 23) + #define MESON_SAR_ADC_REG3_DETECT_EN BIT(22) + #define MESON_SAR_ADC_REG3_ADC_EN BIT(21) + #define MESON_SAR_ADC_REG3_PANEL_DETECT_COUNT_MASK GENMASK(20, 18) + #define MESON_SAR_ADC_REG3_PANEL_DETECT_FILTER_TB_MASK GENMASK(17, 16) + #define MESON_SAR_ADC_REG3_ADC_CLK_DIV_SHIFT 10 + #define MESON_SAR_ADC_REG3_ADC_CLK_DIV_WIDTH 6 + #define MESON_SAR_ADC_REG3_BLOCK_DLY_SEL_MASK GENMASK(9, 8) + #define MESON_SAR_ADC_REG3_BLOCK_DLY_MASK GENMASK(7, 0) + +#define MESON_SAR_ADC_DELAY 0x10 + #define MESON_SAR_ADC_DELAY_INPUT_DLY_SEL_MASK GENMASK(25, 24) + #define MESON_SAR_ADC_DELAY_BL30_BUSY BIT(15) + #define MESON_SAR_ADC_DELAY_KERNEL_BUSY BIT(14) + #define MESON_SAR_ADC_DELAY_INPUT_DLY_CNT_MASK GENMASK(23, 16) + #define MESON_SAR_ADC_DELAY_SAMPLE_DLY_SEL_MASK GENMASK(9, 8) + #define MESON_SAR_ADC_DELAY_SAMPLE_DLY_CNT_MASK GENMASK(7, 0) + +#define MESON_SAR_ADC_LAST_RD 0x14 + #define MESON_SAR_ADC_LAST_RD_LAST_CHANNEL1_MASK GENMASK(23, 16) + #define MESON_SAR_ADC_LAST_RD_LAST_CHANNEL0_MASK GENMASK(9, 0) + +#define MESON_SAR_ADC_FIFO_RD 0x18 + #define MESON_SAR_ADC_FIFO_RD_CHAN_ID_MASK GENMASK(14, 12) + #define MESON_SAR_ADC_FIFO_RD_SAMPLE_VALUE_MASK GENMASK(11, 0) + +#define MESON_SAR_ADC_AUX_SW 0x1c + #define MESON_SAR_ADC_AUX_SW_MUX_SEL_CHAN_SHIFT(_chan) \ + (8 + (((_chan) - 2) * 3)) + #define MESON_SAR_ADC_AUX_SW_VREF_P_MUX BIT(6) + #define MESON_SAR_ADC_AUX_SW_VREF_N_MUX BIT(5) + #define MESON_SAR_ADC_AUX_SW_MODE_SEL BIT(4) + #define MESON_SAR_ADC_AUX_SW_YP_DRIVE_SW BIT(3) + #define MESON_SAR_ADC_AUX_SW_XP_DRIVE_SW BIT(2) + #define MESON_SAR_ADC_AUX_SW_YM_DRIVE_SW BIT(1) + #define MESON_SAR_ADC_AUX_SW_XM_DRIVE_SW BIT(0) + +#define MESON_SAR_ADC_CHAN_10_SW 0x20 + #define MESON_SAR_ADC_CHAN_10_SW_CHAN1_MUX_SEL_MASK GENMASK(25, 23) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN1_VREF_P_MUX BIT(22) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN1_VREF_N_MUX BIT(21) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN1_MODE_SEL BIT(20) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN1_YP_DRIVE_SW BIT(19) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN1_XP_DRIVE_SW BIT(18) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN1_YM_DRIVE_SW BIT(17) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN1_XM_DRIVE_SW BIT(16) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN0_MUX_SEL_MASK GENMASK(9, 7) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN0_VREF_P_MUX BIT(6) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN0_VREF_N_MUX BIT(5) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN0_MODE_SEL BIT(4) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN0_YP_DRIVE_SW BIT(3) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN0_XP_DRIVE_SW BIT(2) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN0_YM_DRIVE_SW BIT(1) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN0_XM_DRIVE_SW BIT(0) + +#define MESON_SAR_ADC_DETECT_IDLE_SW 0x24 + #define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_SW_EN BIT(26) + #define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_MUX_MASK GENMASK(25, 23) + #define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_VREF_P_MUX BIT(22) + #define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_VREF_N_MUX BIT(21) + #define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_SEL BIT(20) + #define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_YP_DRIVE_SW BIT(19) + #define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_XP_DRIVE_SW BIT(18) + #define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_YM_DRIVE_SW BIT(17) + #define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_XM_DRIVE_SW BIT(16) + #define MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_MUX_SEL_MASK GENMASK(9, 7) + #define MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_VREF_P_MUX BIT(6) + #define MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_VREF_N_MUX BIT(5) + #define MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_SEL BIT(4) + #define MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_YP_DRIVE_SW BIT(3) + #define MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_XP_DRIVE_SW BIT(2) + #define MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_YM_DRIVE_SW BIT(1) + #define MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_XM_DRIVE_SW BIT(0) + +#define MESON_SAR_ADC_DELTA_10 0x28 + #define MESON_SAR_ADC_DELTA_10_TEMP_SEL BIT(27) + #define MESON_SAR_ADC_DELTA_10_TS_REVE1 BIT(26) + #define MESON_SAR_ADC_DELTA_10_CHAN1_DELTA_VALUE_MASK GENMASK(25, 16) + #define MESON_SAR_ADC_DELTA_10_TS_REVE0 BIT(15) + #define MESON_SAR_ADC_DELTA_10_TS_C_MASK GENMASK(14, 11) + #define MESON_SAR_ADC_DELTA_10_TS_VBG_EN BIT(10) + #define MESON_SAR_ADC_DELTA_10_CHAN0_DELTA_VALUE_MASK GENMASK(9, 0) + +/* + * NOTE: registers from here are undocumented (the vendor Linux kernel driver + * and u-boot source served as reference). These only seem to be relevant on + * GXBB and newer. + */ +#define MESON_SAR_ADC_REG11 0x2c + #define MESON_SAR_ADC_REG11_BANDGAP_EN BIT(13) + #define MESON_SAR_ADC_REG11_CMV_SEL BIT(6) + #define MESON_SAR_ADC_REG11_VREF_VOLTAGE BIT(5) + #define MESON_SAR_ADC_REG11_EOC BIT(1) + #define MESON_SAR_ADC_REG11_VREF_SEL BIT(0) + +#define MESON_SAR_ADC_REG12 0x30 + #define MESON_SAR_ADC_REG12_MPLL0_UNKNOWN BIT(0) + #define MESON_SAR_ADC_REG12_MPLL1_UNKNOWN BIT(1) + #define MESON_SAR_ADC_REG12_MPLL2_UNKNOWN BIT(2) + +#define MESON_SAR_ADC_REG13 0x34 + #define MESON_SAR_ADC_REG13_12BIT_CALIBRATION_MASK GENMASK(13, 8) + +#define MESON_SAR_ADC_MAX_FIFO_SIZE 32 +#define MESON_SAR_ADC_TIMEOUT 100 /* ms */ +#define MESON_SAR_ADC_VOLTAGE_AND_TEMP_CHANNEL 6 +#define MESON_SAR_ADC_VOLTAGE_AND_MUX_CHANNEL 7 +#define MESON_SAR_ADC_TEMP_OFFSET 27 + +/* temperature sensor calibration information in eFuse */ +#define MESON_SAR_ADC_EFUSE_BYTES 4 +#define MESON_SAR_ADC_EFUSE_BYTE3_UPPER_ADC_VAL GENMASK(6, 0) +#define MESON_SAR_ADC_EFUSE_BYTE3_IS_CALIBRATED BIT(7) + +#define MESON_HHI_DPLL_TOP_0 0x318 +#define MESON_HHI_DPLL_TOP_0_TSC_BIT4 BIT(9) + +/* for use with IIO_VAL_INT_PLUS_MICRO */ +#define MILLION 1000000 + +#define MESON_SAR_ADC_CHAN(_chan) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _chan, \ + .address = _chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_AVERAGE_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_CALIBBIAS) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE), \ + .datasheet_name = "SAR_ADC_CH"#_chan, \ +} + +#define MESON_SAR_ADC_TEMP_CHAN(_chan) { \ + .type = IIO_TEMP, \ + .channel = _chan, \ + .address = MESON_SAR_ADC_VOLTAGE_AND_TEMP_CHANNEL, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_AVERAGE_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_CALIBBIAS) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE), \ + .datasheet_name = "TEMP_SENSOR", \ +} + +#define MESON_SAR_ADC_MUX(_chan, _sel) { \ + .type = IIO_VOLTAGE, \ + .channel = _chan, \ + .indexed = 1, \ + .address = MESON_SAR_ADC_VOLTAGE_AND_MUX_CHANNEL, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_AVERAGE_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_CALIBBIAS) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE), \ + .datasheet_name = "SAR_ADC_MUX_"#_sel, \ +} + +enum meson_sar_adc_vref_sel { + VREF_CALIBATION_VOLTAGE = 0, + VREF_VDDA = 1, +}; + +enum meson_sar_adc_avg_mode { + NO_AVERAGING = 0x0, + MEAN_AVERAGING = 0x1, + MEDIAN_AVERAGING = 0x2, +}; + +enum meson_sar_adc_num_samples { + ONE_SAMPLE = 0x0, + TWO_SAMPLES = 0x1, + FOUR_SAMPLES = 0x2, + EIGHT_SAMPLES = 0x3, +}; + +enum meson_sar_adc_chan7_mux_sel { + CHAN7_MUX_VSS = 0x0, + CHAN7_MUX_VDD_DIV4 = 0x1, + CHAN7_MUX_VDD_DIV2 = 0x2, + CHAN7_MUX_VDD_MUL3_DIV4 = 0x3, + CHAN7_MUX_VDD = 0x4, + CHAN7_MUX_CH7_INPUT = 0x7, +}; + +enum meson_sar_adc_channel_index { + NUM_CHAN_0, + NUM_CHAN_1, + NUM_CHAN_2, + NUM_CHAN_3, + NUM_CHAN_4, + NUM_CHAN_5, + NUM_CHAN_6, + NUM_CHAN_7, + NUM_CHAN_TEMP, + NUM_MUX_0_VSS, + NUM_MUX_1_VDD_DIV4, + NUM_MUX_2_VDD_DIV2, + NUM_MUX_3_VDD_MUL3_DIV4, + NUM_MUX_4_VDD, +}; + +static enum meson_sar_adc_chan7_mux_sel chan7_mux_values[] = { + CHAN7_MUX_VSS, + CHAN7_MUX_VDD_DIV4, + CHAN7_MUX_VDD_DIV2, + CHAN7_MUX_VDD_MUL3_DIV4, + CHAN7_MUX_VDD, +}; + +static const char * const chan7_mux_names[] = { + [CHAN7_MUX_VSS] = "gnd", + [CHAN7_MUX_VDD_DIV4] = "0.25vdd", + [CHAN7_MUX_VDD_DIV2] = "0.5vdd", + [CHAN7_MUX_VDD_MUL3_DIV4] = "0.75vdd", + [CHAN7_MUX_VDD] = "vdd", +}; + +static const struct iio_chan_spec meson_sar_adc_iio_channels[] = { + MESON_SAR_ADC_CHAN(NUM_CHAN_0), + MESON_SAR_ADC_CHAN(NUM_CHAN_1), + MESON_SAR_ADC_CHAN(NUM_CHAN_2), + MESON_SAR_ADC_CHAN(NUM_CHAN_3), + MESON_SAR_ADC_CHAN(NUM_CHAN_4), + MESON_SAR_ADC_CHAN(NUM_CHAN_5), + MESON_SAR_ADC_CHAN(NUM_CHAN_6), + MESON_SAR_ADC_CHAN(NUM_CHAN_7), + MESON_SAR_ADC_MUX(NUM_MUX_0_VSS, 0), + MESON_SAR_ADC_MUX(NUM_MUX_1_VDD_DIV4, 1), + MESON_SAR_ADC_MUX(NUM_MUX_2_VDD_DIV2, 2), + MESON_SAR_ADC_MUX(NUM_MUX_3_VDD_MUL3_DIV4, 3), + MESON_SAR_ADC_MUX(NUM_MUX_4_VDD, 4), +}; + +static const struct iio_chan_spec meson_sar_adc_and_temp_iio_channels[] = { + MESON_SAR_ADC_CHAN(NUM_CHAN_0), + MESON_SAR_ADC_CHAN(NUM_CHAN_1), + MESON_SAR_ADC_CHAN(NUM_CHAN_2), + MESON_SAR_ADC_CHAN(NUM_CHAN_3), + MESON_SAR_ADC_CHAN(NUM_CHAN_4), + MESON_SAR_ADC_CHAN(NUM_CHAN_5), + MESON_SAR_ADC_CHAN(NUM_CHAN_6), + MESON_SAR_ADC_CHAN(NUM_CHAN_7), + MESON_SAR_ADC_TEMP_CHAN(NUM_CHAN_TEMP), + MESON_SAR_ADC_MUX(NUM_MUX_0_VSS, 0), + MESON_SAR_ADC_MUX(NUM_MUX_1_VDD_DIV4, 1), + MESON_SAR_ADC_MUX(NUM_MUX_2_VDD_DIV2, 2), + MESON_SAR_ADC_MUX(NUM_MUX_3_VDD_MUL3_DIV4, 3), + MESON_SAR_ADC_MUX(NUM_MUX_4_VDD, 4), +}; + +struct meson_sar_adc_param { + bool has_bl30_integration; + unsigned long clock_rate; + unsigned int resolution; + const struct regmap_config *regmap_config; + u8 temperature_trimming_bits; + unsigned int temperature_multiplier; + unsigned int temperature_divider; + u8 disable_ring_counter; + bool has_vref_select; + u8 vref_select; + u8 cmv_select; + u8 adc_eoc; + enum meson_sar_adc_vref_sel vref_voltage; + bool enable_mpll_clock_workaround; +}; + +struct meson_sar_adc_data { + const struct meson_sar_adc_param *param; + const char *name; +}; + +struct meson_sar_adc_priv { + struct regmap *regmap; + struct regulator *vref; + const struct meson_sar_adc_param *param; + struct clk *clkin; + struct clk *core_clk; + struct clk *adc_sel_clk; + struct clk *adc_clk; + struct clk_gate clk_gate; + struct clk *adc_div_clk; + struct clk_divider clk_div; + struct completion done; + /* lock to protect against multiple access to the device */ + struct mutex lock; + int calibbias; + int calibscale; + struct regmap *tsc_regmap; + bool temperature_sensor_calibrated; + u8 temperature_sensor_coefficient; + u16 temperature_sensor_adc_val; + enum meson_sar_adc_chan7_mux_sel chan7_mux_sel; +}; + +static const struct regmap_config meson_sar_adc_regmap_config_gxbb = { + .reg_bits = 8, + .val_bits = 32, + .reg_stride = 4, + .max_register = MESON_SAR_ADC_REG13, +}; + +static const struct regmap_config meson_sar_adc_regmap_config_meson8 = { + .reg_bits = 8, + .val_bits = 32, + .reg_stride = 4, + .max_register = MESON_SAR_ADC_DELTA_10, +}; + +static const struct iio_chan_spec * +find_channel_by_num(struct iio_dev *indio_dev, int num) +{ + int i; + + for (i = 0; i < indio_dev->num_channels; i++) + if (indio_dev->channels[i].channel == num) + return &indio_dev->channels[i]; + return NULL; +} + +static unsigned int meson_sar_adc_get_fifo_count(struct iio_dev *indio_dev) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + u32 regval; + + regmap_read(priv->regmap, MESON_SAR_ADC_REG0, ®val); + + return FIELD_GET(MESON_SAR_ADC_REG0_FIFO_COUNT_MASK, regval); +} + +static int meson_sar_adc_calib_val(struct iio_dev *indio_dev, int val) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + int tmp; + + /* use val_calib = scale * val_raw + offset calibration function */ + tmp = div_s64((s64)val * priv->calibscale, MILLION) + priv->calibbias; + + return clamp(tmp, 0, (1 << priv->param->resolution) - 1); +} + +static int meson_sar_adc_wait_busy_clear(struct iio_dev *indio_dev) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + int val; + + /* + * NOTE: we need a small delay before reading the status, otherwise + * the sample engine may not have started internally (which would + * seem to us that sampling is already finished). + */ + udelay(1); + return regmap_read_poll_timeout_atomic(priv->regmap, MESON_SAR_ADC_REG0, val, + !FIELD_GET(MESON_SAR_ADC_REG0_BUSY_MASK, val), + 1, 10000); +} + +static void meson_sar_adc_set_chan7_mux(struct iio_dev *indio_dev, + enum meson_sar_adc_chan7_mux_sel sel) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + u32 regval; + + regval = FIELD_PREP(MESON_SAR_ADC_REG3_CTRL_CHAN7_MUX_SEL_MASK, sel); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG3, + MESON_SAR_ADC_REG3_CTRL_CHAN7_MUX_SEL_MASK, regval); + + usleep_range(10, 20); + + priv->chan7_mux_sel = sel; +} + +static int meson_sar_adc_read_raw_sample(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + int regval, fifo_chan, fifo_val, count; + + if (!wait_for_completion_timeout(&priv->done, + msecs_to_jiffies(MESON_SAR_ADC_TIMEOUT))) + return -ETIMEDOUT; + + count = meson_sar_adc_get_fifo_count(indio_dev); + if (count != 1) { + dev_err(dev, "ADC FIFO has %d element(s) instead of one\n", count); + return -EINVAL; + } + + regmap_read(priv->regmap, MESON_SAR_ADC_FIFO_RD, ®val); + fifo_chan = FIELD_GET(MESON_SAR_ADC_FIFO_RD_CHAN_ID_MASK, regval); + if (fifo_chan != chan->address) { + dev_err(dev, "ADC FIFO entry belongs to channel %d instead of %lu\n", + fifo_chan, chan->address); + return -EINVAL; + } + + fifo_val = FIELD_GET(MESON_SAR_ADC_FIFO_RD_SAMPLE_VALUE_MASK, regval); + fifo_val &= GENMASK(priv->param->resolution - 1, 0); + *val = meson_sar_adc_calib_val(indio_dev, fifo_val); + + return 0; +} + +static void meson_sar_adc_set_averaging(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum meson_sar_adc_avg_mode mode, + enum meson_sar_adc_num_samples samples) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + int val, address = chan->address; + + val = samples << MESON_SAR_ADC_AVG_CNTL_NUM_SAMPLES_SHIFT(address); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_AVG_CNTL, + MESON_SAR_ADC_AVG_CNTL_NUM_SAMPLES_MASK(address), + val); + + val = mode << MESON_SAR_ADC_AVG_CNTL_AVG_MODE_SHIFT(address); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_AVG_CNTL, + MESON_SAR_ADC_AVG_CNTL_AVG_MODE_MASK(address), val); +} + +static void meson_sar_adc_enable_channel(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + u32 regval; + + /* + * the SAR ADC engine allows sampling multiple channels at the same + * time. to keep it simple we're only working with one *internal* + * channel, which starts counting at index 0 (which means: count = 1). + */ + regval = FIELD_PREP(MESON_SAR_ADC_CHAN_LIST_MAX_INDEX_MASK, 0); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_CHAN_LIST, + MESON_SAR_ADC_CHAN_LIST_MAX_INDEX_MASK, regval); + + /* map channel index 0 to the channel which we want to read */ + regval = FIELD_PREP(MESON_SAR_ADC_CHAN_LIST_ENTRY_MASK(0), + chan->address); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_CHAN_LIST, + MESON_SAR_ADC_CHAN_LIST_ENTRY_MASK(0), regval); + + regval = FIELD_PREP(MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_MUX_MASK, + chan->address); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_DETECT_IDLE_SW, + MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_MUX_MASK, + regval); + + regval = FIELD_PREP(MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_MUX_SEL_MASK, + chan->address); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_DETECT_IDLE_SW, + MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_MUX_SEL_MASK, + regval); + + if (chan->address == MESON_SAR_ADC_VOLTAGE_AND_TEMP_CHANNEL) { + if (chan->type == IIO_TEMP) + regval = MESON_SAR_ADC_DELTA_10_TEMP_SEL; + else + regval = 0; + + regmap_update_bits(priv->regmap, + MESON_SAR_ADC_DELTA_10, + MESON_SAR_ADC_DELTA_10_TEMP_SEL, regval); + } else if (chan->address == MESON_SAR_ADC_VOLTAGE_AND_MUX_CHANNEL) { + enum meson_sar_adc_chan7_mux_sel sel; + + if (chan->channel == NUM_CHAN_7) + sel = CHAN7_MUX_CH7_INPUT; + else + sel = chan7_mux_values[chan->channel - NUM_MUX_0_VSS]; + if (sel != priv->chan7_mux_sel) + meson_sar_adc_set_chan7_mux(indio_dev, sel); + } +} + +static void meson_sar_adc_start_sample_engine(struct iio_dev *indio_dev) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + + reinit_completion(&priv->done); + + regmap_set_bits(priv->regmap, MESON_SAR_ADC_REG0, + MESON_SAR_ADC_REG0_FIFO_IRQ_EN); + + regmap_set_bits(priv->regmap, MESON_SAR_ADC_REG0, + MESON_SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE); + + regmap_set_bits(priv->regmap, MESON_SAR_ADC_REG0, + MESON_SAR_ADC_REG0_SAMPLING_START); +} + +static void meson_sar_adc_stop_sample_engine(struct iio_dev *indio_dev) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + + regmap_clear_bits(priv->regmap, MESON_SAR_ADC_REG0, + MESON_SAR_ADC_REG0_FIFO_IRQ_EN); + + regmap_set_bits(priv->regmap, MESON_SAR_ADC_REG0, + MESON_SAR_ADC_REG0_SAMPLING_STOP); + + /* wait until all modules are stopped */ + meson_sar_adc_wait_busy_clear(indio_dev); + + regmap_clear_bits(priv->regmap, MESON_SAR_ADC_REG0, + MESON_SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE); +} + +static int meson_sar_adc_lock(struct iio_dev *indio_dev) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + int val, ret; + + mutex_lock(&priv->lock); + + if (priv->param->has_bl30_integration) { + /* prevent BL30 from using the SAR ADC while we are using it */ + regmap_set_bits(priv->regmap, MESON_SAR_ADC_DELAY, + MESON_SAR_ADC_DELAY_KERNEL_BUSY); + + udelay(1); + + /* + * wait until BL30 releases it's lock (so we can use the SAR + * ADC) + */ + ret = regmap_read_poll_timeout_atomic(priv->regmap, MESON_SAR_ADC_DELAY, val, + !(val & MESON_SAR_ADC_DELAY_BL30_BUSY), + 1, 10000); + if (ret) { + mutex_unlock(&priv->lock); + return ret; + } + } + + return 0; +} + +static void meson_sar_adc_unlock(struct iio_dev *indio_dev) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + + if (priv->param->has_bl30_integration) + /* allow BL30 to use the SAR ADC again */ + regmap_clear_bits(priv->regmap, MESON_SAR_ADC_DELAY, + MESON_SAR_ADC_DELAY_KERNEL_BUSY); + + mutex_unlock(&priv->lock); +} + +static void meson_sar_adc_clear_fifo(struct iio_dev *indio_dev) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + unsigned int count, tmp; + + for (count = 0; count < MESON_SAR_ADC_MAX_FIFO_SIZE; count++) { + if (!meson_sar_adc_get_fifo_count(indio_dev)) + break; + + regmap_read(priv->regmap, MESON_SAR_ADC_FIFO_RD, &tmp); + } +} + +static int meson_sar_adc_get_sample(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum meson_sar_adc_avg_mode avg_mode, + enum meson_sar_adc_num_samples avg_samples, + int *val) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + int ret; + + if (chan->type == IIO_TEMP && !priv->temperature_sensor_calibrated) + return -ENOTSUPP; + + ret = meson_sar_adc_lock(indio_dev); + if (ret) + return ret; + + /* clear the FIFO to make sure we're not reading old values */ + meson_sar_adc_clear_fifo(indio_dev); + + meson_sar_adc_set_averaging(indio_dev, chan, avg_mode, avg_samples); + + meson_sar_adc_enable_channel(indio_dev, chan); + + meson_sar_adc_start_sample_engine(indio_dev); + ret = meson_sar_adc_read_raw_sample(indio_dev, chan, val); + meson_sar_adc_stop_sample_engine(indio_dev); + + meson_sar_adc_unlock(indio_dev); + + if (ret) { + dev_warn(dev, "failed to read sample for channel %lu: %d\n", + chan->address, ret); + return ret; + } + + return IIO_VAL_INT; +} + +static int meson_sar_adc_iio_info_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return meson_sar_adc_get_sample(indio_dev, chan, NO_AVERAGING, + ONE_SAMPLE, val); + + case IIO_CHAN_INFO_AVERAGE_RAW: + return meson_sar_adc_get_sample(indio_dev, chan, + MEAN_AVERAGING, EIGHT_SAMPLES, + val); + + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_VOLTAGE) { + ret = regulator_get_voltage(priv->vref); + if (ret < 0) { + dev_err(dev, "failed to get vref voltage: %d\n", ret); + return ret; + } + + *val = ret / 1000; + *val2 = priv->param->resolution; + return IIO_VAL_FRACTIONAL_LOG2; + } else if (chan->type == IIO_TEMP) { + /* SoC specific multiplier and divider */ + *val = priv->param->temperature_multiplier; + *val2 = priv->param->temperature_divider; + + /* celsius to millicelsius */ + *val *= 1000; + + return IIO_VAL_FRACTIONAL; + } else { + return -EINVAL; + } + + case IIO_CHAN_INFO_CALIBBIAS: + *val = priv->calibbias; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_CALIBSCALE: + *val = priv->calibscale / MILLION; + *val2 = priv->calibscale % MILLION; + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_CHAN_INFO_OFFSET: + *val = DIV_ROUND_CLOSEST(MESON_SAR_ADC_TEMP_OFFSET * + priv->param->temperature_divider, + priv->param->temperature_multiplier); + *val -= priv->temperature_sensor_adc_val; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int meson_sar_adc_clk_init(struct iio_dev *indio_dev, + void __iomem *base) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + struct clk_init_data init; + const char *clk_parents[1]; + + init.name = devm_kasprintf(dev, GFP_KERNEL, "%s#adc_div", dev_name(dev)); + if (!init.name) + return -ENOMEM; + + init.flags = 0; + init.ops = &clk_divider_ops; + clk_parents[0] = __clk_get_name(priv->clkin); + init.parent_names = clk_parents; + init.num_parents = 1; + + priv->clk_div.reg = base + MESON_SAR_ADC_REG3; + priv->clk_div.shift = MESON_SAR_ADC_REG3_ADC_CLK_DIV_SHIFT; + priv->clk_div.width = MESON_SAR_ADC_REG3_ADC_CLK_DIV_WIDTH; + priv->clk_div.hw.init = &init; + priv->clk_div.flags = 0; + + priv->adc_div_clk = devm_clk_register(dev, &priv->clk_div.hw); + if (WARN_ON(IS_ERR(priv->adc_div_clk))) + return PTR_ERR(priv->adc_div_clk); + + init.name = devm_kasprintf(dev, GFP_KERNEL, "%s#adc_en", dev_name(dev)); + if (!init.name) + return -ENOMEM; + + init.flags = CLK_SET_RATE_PARENT; + init.ops = &clk_gate_ops; + clk_parents[0] = __clk_get_name(priv->adc_div_clk); + init.parent_names = clk_parents; + init.num_parents = 1; + + priv->clk_gate.reg = base + MESON_SAR_ADC_REG3; + priv->clk_gate.bit_idx = __ffs(MESON_SAR_ADC_REG3_CLK_EN); + priv->clk_gate.hw.init = &init; + + priv->adc_clk = devm_clk_register(dev, &priv->clk_gate.hw); + if (WARN_ON(IS_ERR(priv->adc_clk))) + return PTR_ERR(priv->adc_clk); + + return 0; +} + +static int meson_sar_adc_temp_sensor_init(struct iio_dev *indio_dev) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + u8 *buf, trimming_bits, trimming_mask, upper_adc_val; + struct device *dev = indio_dev->dev.parent; + struct nvmem_cell *temperature_calib; + size_t read_len; + int ret; + + temperature_calib = nvmem_cell_get(dev, "temperature_calib"); + if (IS_ERR(temperature_calib)) { + ret = PTR_ERR(temperature_calib); + + /* + * leave the temperature sensor disabled if no calibration data + * was passed via nvmem-cells. + */ + if (ret == -ENODEV) + return 0; + + return dev_err_probe(dev, ret, "failed to get temperature_calib cell\n"); + } + + read_len = MESON_SAR_ADC_EFUSE_BYTES; + buf = nvmem_cell_read(temperature_calib, &read_len); + nvmem_cell_put(temperature_calib); + if (IS_ERR(buf)) + return dev_err_probe(dev, PTR_ERR(buf), "failed to read temperature_calib cell\n"); + if (read_len != MESON_SAR_ADC_EFUSE_BYTES) { + kfree(buf); + return dev_err_probe(dev, -EINVAL, "invalid read size of temperature_calib cell\n"); + } + + priv->tsc_regmap = syscon_regmap_lookup_by_phandle(dev->of_node, "amlogic,hhi-sysctrl"); + if (IS_ERR(priv->tsc_regmap)) { + kfree(buf); + return dev_err_probe(dev, PTR_ERR(priv->tsc_regmap), + "failed to get amlogic,hhi-sysctrl regmap\n"); + } + + trimming_bits = priv->param->temperature_trimming_bits; + trimming_mask = BIT(trimming_bits) - 1; + + priv->temperature_sensor_calibrated = + buf[3] & MESON_SAR_ADC_EFUSE_BYTE3_IS_CALIBRATED; + priv->temperature_sensor_coefficient = buf[2] & trimming_mask; + + upper_adc_val = FIELD_GET(MESON_SAR_ADC_EFUSE_BYTE3_UPPER_ADC_VAL, + buf[3]); + + priv->temperature_sensor_adc_val = buf[2]; + priv->temperature_sensor_adc_val |= upper_adc_val << BITS_PER_BYTE; + priv->temperature_sensor_adc_val >>= trimming_bits; + + kfree(buf); + + return 0; +} + +static int meson_sar_adc_init(struct iio_dev *indio_dev) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + int regval, i, ret; + + /* + * make sure we start at CH7 input since the other muxes are only used + * for internal calibration. + */ + meson_sar_adc_set_chan7_mux(indio_dev, CHAN7_MUX_CH7_INPUT); + + if (priv->param->has_bl30_integration) { + /* + * leave sampling delay and the input clocks as configured by + * BL30 to make sure BL30 gets the values it expects when + * reading the temperature sensor. + */ + regmap_read(priv->regmap, MESON_SAR_ADC_REG3, ®val); + if (regval & MESON_SAR_ADC_REG3_BL30_INITIALIZED) + return 0; + } + + meson_sar_adc_stop_sample_engine(indio_dev); + + /* + * disable this bit as seems to be only relevant for Meson6 (based + * on the vendor driver), which we don't support at the moment. + */ + regmap_clear_bits(priv->regmap, MESON_SAR_ADC_REG0, + MESON_SAR_ADC_REG0_ADC_TEMP_SEN_SEL); + + /* disable all channels by default */ + regmap_write(priv->regmap, MESON_SAR_ADC_CHAN_LIST, 0x0); + + regmap_clear_bits(priv->regmap, MESON_SAR_ADC_REG3, + MESON_SAR_ADC_REG3_CTRL_SAMPLING_CLOCK_PHASE); + regmap_set_bits(priv->regmap, MESON_SAR_ADC_REG3, + MESON_SAR_ADC_REG3_CNTL_USE_SC_DLY); + + /* delay between two samples = (10+1) * 1uS */ + regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELAY, + MESON_SAR_ADC_DELAY_INPUT_DLY_CNT_MASK, + FIELD_PREP(MESON_SAR_ADC_DELAY_SAMPLE_DLY_CNT_MASK, + 10)); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELAY, + MESON_SAR_ADC_DELAY_SAMPLE_DLY_SEL_MASK, + FIELD_PREP(MESON_SAR_ADC_DELAY_SAMPLE_DLY_SEL_MASK, + 0)); + + /* delay between two samples = (10+1) * 1uS */ + regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELAY, + MESON_SAR_ADC_DELAY_INPUT_DLY_CNT_MASK, + FIELD_PREP(MESON_SAR_ADC_DELAY_INPUT_DLY_CNT_MASK, + 10)); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELAY, + MESON_SAR_ADC_DELAY_INPUT_DLY_SEL_MASK, + FIELD_PREP(MESON_SAR_ADC_DELAY_INPUT_DLY_SEL_MASK, + 1)); + + /* + * set up the input channel muxes in MESON_SAR_ADC_CHAN_10_SW + * (0 = SAR_ADC_CH0, 1 = SAR_ADC_CH1) + */ + regval = FIELD_PREP(MESON_SAR_ADC_CHAN_10_SW_CHAN0_MUX_SEL_MASK, 0); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_CHAN_10_SW, + MESON_SAR_ADC_CHAN_10_SW_CHAN0_MUX_SEL_MASK, + regval); + regval = FIELD_PREP(MESON_SAR_ADC_CHAN_10_SW_CHAN1_MUX_SEL_MASK, 1); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_CHAN_10_SW, + MESON_SAR_ADC_CHAN_10_SW_CHAN1_MUX_SEL_MASK, + regval); + + regmap_set_bits(priv->regmap, MESON_SAR_ADC_CHAN_10_SW, + MESON_SAR_ADC_CHAN_10_SW_CHAN0_XP_DRIVE_SW); + + regmap_set_bits(priv->regmap, MESON_SAR_ADC_CHAN_10_SW, + MESON_SAR_ADC_CHAN_10_SW_CHAN0_YP_DRIVE_SW); + + regmap_set_bits(priv->regmap, MESON_SAR_ADC_CHAN_10_SW, + MESON_SAR_ADC_CHAN_10_SW_CHAN1_XP_DRIVE_SW); + + regmap_set_bits(priv->regmap, MESON_SAR_ADC_CHAN_10_SW, + MESON_SAR_ADC_CHAN_10_SW_CHAN1_YP_DRIVE_SW); + + /* + * set up the input channel muxes in MESON_SAR_ADC_AUX_SW + * (2 = SAR_ADC_CH2, 3 = SAR_ADC_CH3, ...) and enable + * MESON_SAR_ADC_AUX_SW_YP_DRIVE_SW and + * MESON_SAR_ADC_AUX_SW_XP_DRIVE_SW like the vendor driver. + */ + regval = 0; + for (i = 2; i <= 7; i++) + regval |= i << MESON_SAR_ADC_AUX_SW_MUX_SEL_CHAN_SHIFT(i); + regval |= MESON_SAR_ADC_AUX_SW_YP_DRIVE_SW; + regval |= MESON_SAR_ADC_AUX_SW_XP_DRIVE_SW; + regmap_write(priv->regmap, MESON_SAR_ADC_AUX_SW, regval); + + if (priv->temperature_sensor_calibrated) { + regmap_set_bits(priv->regmap, MESON_SAR_ADC_DELTA_10, + MESON_SAR_ADC_DELTA_10_TS_REVE1); + regmap_set_bits(priv->regmap, MESON_SAR_ADC_DELTA_10, + MESON_SAR_ADC_DELTA_10_TS_REVE0); + + /* + * set bits [3:0] of the TSC (temperature sensor coefficient) + * to get the correct values when reading the temperature. + */ + regval = FIELD_PREP(MESON_SAR_ADC_DELTA_10_TS_C_MASK, + priv->temperature_sensor_coefficient); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELTA_10, + MESON_SAR_ADC_DELTA_10_TS_C_MASK, regval); + + if (priv->param->temperature_trimming_bits == 5) { + if (priv->temperature_sensor_coefficient & BIT(4)) + regval = MESON_HHI_DPLL_TOP_0_TSC_BIT4; + else + regval = 0; + + /* + * bit [4] (the 5th bit when starting to count at 1) + * of the TSC is located in the HHI register area. + */ + regmap_update_bits(priv->tsc_regmap, + MESON_HHI_DPLL_TOP_0, + MESON_HHI_DPLL_TOP_0_TSC_BIT4, + regval); + } + } else { + regmap_clear_bits(priv->regmap, MESON_SAR_ADC_DELTA_10, + MESON_SAR_ADC_DELTA_10_TS_REVE1); + regmap_clear_bits(priv->regmap, MESON_SAR_ADC_DELTA_10, + MESON_SAR_ADC_DELTA_10_TS_REVE0); + } + + regval = FIELD_PREP(MESON_SAR_ADC_REG3_CTRL_CONT_RING_COUNTER_EN, + priv->param->disable_ring_counter); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG3, + MESON_SAR_ADC_REG3_CTRL_CONT_RING_COUNTER_EN, + regval); + + if (priv->param->regmap_config->max_register >= MESON_SAR_ADC_REG11) { + regval = FIELD_PREP(MESON_SAR_ADC_REG11_EOC, priv->param->adc_eoc); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG11, + MESON_SAR_ADC_REG11_EOC, regval); + + if (priv->param->has_vref_select) { + regval = FIELD_PREP(MESON_SAR_ADC_REG11_VREF_SEL, + priv->param->vref_select); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG11, + MESON_SAR_ADC_REG11_VREF_SEL, regval); + } + + regval = FIELD_PREP(MESON_SAR_ADC_REG11_VREF_VOLTAGE, + priv->param->vref_voltage); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG11, + MESON_SAR_ADC_REG11_VREF_VOLTAGE, regval); + + regval = FIELD_PREP(MESON_SAR_ADC_REG11_CMV_SEL, + priv->param->cmv_select); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG11, + MESON_SAR_ADC_REG11_CMV_SEL, regval); + + if (priv->param->enable_mpll_clock_workaround) { + dev_warn(dev, + "Enabling unknown bits to make the MPLL clocks work. This may change so always update dtbs and kernel together\n"); + regmap_write(priv->regmap, MESON_SAR_ADC_REG12, + MESON_SAR_ADC_REG12_MPLL0_UNKNOWN | + MESON_SAR_ADC_REG12_MPLL1_UNKNOWN | + MESON_SAR_ADC_REG12_MPLL2_UNKNOWN); + } + } + + ret = clk_set_parent(priv->adc_sel_clk, priv->clkin); + if (ret) + return dev_err_probe(dev, ret, "failed to set adc parent to clkin\n"); + + ret = clk_set_rate(priv->adc_clk, priv->param->clock_rate); + if (ret) + return dev_err_probe(dev, ret, "failed to set adc clock rate\n"); + + return 0; +} + +static void meson_sar_adc_set_bandgap(struct iio_dev *indio_dev, bool on_off) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + + if (priv->param->regmap_config->max_register >= MESON_SAR_ADC_REG11) + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG11, + MESON_SAR_ADC_REG11_BANDGAP_EN, + on_off ? MESON_SAR_ADC_REG11_BANDGAP_EN : 0); + else + regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELTA_10, + MESON_SAR_ADC_DELTA_10_TS_VBG_EN, + on_off ? MESON_SAR_ADC_DELTA_10_TS_VBG_EN : 0); +} + +static int meson_sar_adc_hw_enable(struct iio_dev *indio_dev) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + int ret; + u32 regval; + + ret = meson_sar_adc_lock(indio_dev); + if (ret) { + dev_err(dev, "failed to lock adc\n"); + goto err_lock; + } + + ret = regulator_enable(priv->vref); + if (ret < 0) { + dev_err(dev, "failed to enable vref regulator\n"); + goto err_vref; + } + + regval = FIELD_PREP(MESON_SAR_ADC_REG0_FIFO_CNT_IRQ_MASK, 1); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0, + MESON_SAR_ADC_REG0_FIFO_CNT_IRQ_MASK, regval); + + meson_sar_adc_set_bandgap(indio_dev, true); + + regmap_set_bits(priv->regmap, MESON_SAR_ADC_REG3, + MESON_SAR_ADC_REG3_ADC_EN); + + udelay(5); + + ret = clk_prepare_enable(priv->adc_clk); + if (ret) { + dev_err(dev, "failed to enable adc clk\n"); + goto err_adc_clk; + } + + meson_sar_adc_unlock(indio_dev); + + return 0; + +err_adc_clk: + regmap_clear_bits(priv->regmap, MESON_SAR_ADC_REG3, + MESON_SAR_ADC_REG3_ADC_EN); + meson_sar_adc_set_bandgap(indio_dev, false); + regulator_disable(priv->vref); +err_vref: + meson_sar_adc_unlock(indio_dev); +err_lock: + return ret; +} + +static void meson_sar_adc_hw_disable(struct iio_dev *indio_dev) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + int ret; + + /* + * If taking the lock fails we have to assume that BL30 is broken. The + * best we can do then is to release the resources anyhow. + */ + ret = meson_sar_adc_lock(indio_dev); + if (ret) + dev_err(indio_dev->dev.parent, "Failed to lock ADC (%pE)\n", ERR_PTR(ret)); + + clk_disable_unprepare(priv->adc_clk); + + regmap_clear_bits(priv->regmap, MESON_SAR_ADC_REG3, + MESON_SAR_ADC_REG3_ADC_EN); + + meson_sar_adc_set_bandgap(indio_dev, false); + + regulator_disable(priv->vref); + + if (!ret) + meson_sar_adc_unlock(indio_dev); +} + +static irqreturn_t meson_sar_adc_irq(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + unsigned int cnt, threshold; + u32 regval; + + regmap_read(priv->regmap, MESON_SAR_ADC_REG0, ®val); + cnt = FIELD_GET(MESON_SAR_ADC_REG0_FIFO_COUNT_MASK, regval); + threshold = FIELD_GET(MESON_SAR_ADC_REG0_FIFO_CNT_IRQ_MASK, regval); + + if (cnt < threshold) + return IRQ_NONE; + + complete(&priv->done); + + return IRQ_HANDLED; +} + +static int meson_sar_adc_calib(struct iio_dev *indio_dev) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + int ret, nominal0, nominal1, value0, value1; + + /* use points 25% and 75% for calibration */ + nominal0 = (1 << priv->param->resolution) / 4; + nominal1 = (1 << priv->param->resolution) * 3 / 4; + + meson_sar_adc_set_chan7_mux(indio_dev, CHAN7_MUX_VDD_DIV4); + usleep_range(10, 20); + ret = meson_sar_adc_get_sample(indio_dev, + find_channel_by_num(indio_dev, + NUM_MUX_1_VDD_DIV4), + MEAN_AVERAGING, EIGHT_SAMPLES, &value0); + if (ret < 0) + goto out; + + meson_sar_adc_set_chan7_mux(indio_dev, CHAN7_MUX_VDD_MUL3_DIV4); + usleep_range(10, 20); + ret = meson_sar_adc_get_sample(indio_dev, + find_channel_by_num(indio_dev, + NUM_MUX_3_VDD_MUL3_DIV4), + MEAN_AVERAGING, EIGHT_SAMPLES, &value1); + if (ret < 0) + goto out; + + if (value1 <= value0) { + ret = -EINVAL; + goto out; + } + + priv->calibscale = div_s64((nominal1 - nominal0) * (s64)MILLION, + value1 - value0); + priv->calibbias = nominal0 - div_s64((s64)value0 * priv->calibscale, + MILLION); + ret = 0; +out: + meson_sar_adc_set_chan7_mux(indio_dev, CHAN7_MUX_CH7_INPUT); + + return ret; +} + +static int read_label(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + char *label) +{ + if (chan->type == IIO_TEMP) + return sysfs_emit(label, "temp-sensor\n"); + if (chan->type == IIO_VOLTAGE && chan->channel >= NUM_MUX_0_VSS) + return sysfs_emit(label, "%s\n", + chan7_mux_names[chan->channel - NUM_MUX_0_VSS]); + if (chan->type == IIO_VOLTAGE) + return sysfs_emit(label, "channel-%d\n", chan->channel); + return 0; +} + +static const struct iio_info meson_sar_adc_iio_info = { + .read_raw = meson_sar_adc_iio_info_read_raw, + .read_label = read_label, +}; + +static const struct meson_sar_adc_param meson_sar_adc_meson8_param = { + .has_bl30_integration = false, + .clock_rate = 1150000, + .regmap_config = &meson_sar_adc_regmap_config_meson8, + .resolution = 10, + .temperature_trimming_bits = 4, + .temperature_multiplier = 18 * 10000, + .temperature_divider = 1024 * 10 * 85, +}; + +static const struct meson_sar_adc_param meson_sar_adc_meson8b_param = { + .has_bl30_integration = false, + .clock_rate = 1150000, + .regmap_config = &meson_sar_adc_regmap_config_meson8, + .resolution = 10, + .temperature_trimming_bits = 5, + .temperature_multiplier = 10, + .temperature_divider = 32, +}; + +static const struct meson_sar_adc_param meson_sar_adc_gxbb_param = { + .has_bl30_integration = true, + .clock_rate = 1200000, + .regmap_config = &meson_sar_adc_regmap_config_gxbb, + .resolution = 10, + .vref_voltage = 1, + .cmv_select = 1, +}; + +static const struct meson_sar_adc_param meson_sar_adc_gxl_param = { + .has_bl30_integration = true, + .clock_rate = 1200000, + .regmap_config = &meson_sar_adc_regmap_config_gxbb, + .resolution = 12, + .disable_ring_counter = 1, + .vref_voltage = 1, + .cmv_select = 1, +}; + +static const struct meson_sar_adc_param meson_sar_adc_gxlx_param = { + .has_bl30_integration = true, + .clock_rate = 1200000, + .regmap_config = &meson_sar_adc_regmap_config_gxbb, + .resolution = 12, + .disable_ring_counter = 1, + .vref_voltage = 1, + .cmv_select = true, + .enable_mpll_clock_workaround = true, +}; + +static const struct meson_sar_adc_param meson_sar_adc_axg_param = { + .has_bl30_integration = true, + .clock_rate = 1200000, + .regmap_config = &meson_sar_adc_regmap_config_gxbb, + .resolution = 12, + .disable_ring_counter = 1, + .vref_voltage = 1, + .has_vref_select = true, + .vref_select = VREF_VDDA, + .cmv_select = 1, +}; + +static const struct meson_sar_adc_param meson_sar_adc_g12a_param = { + .has_bl30_integration = false, + .clock_rate = 1200000, + .regmap_config = &meson_sar_adc_regmap_config_gxbb, + .resolution = 12, + .disable_ring_counter = 1, + .adc_eoc = 1, + .has_vref_select = true, + .vref_select = VREF_VDDA, +}; + +static const struct meson_sar_adc_data meson_sar_adc_meson8_data = { + .param = &meson_sar_adc_meson8_param, + .name = "meson-meson8-saradc", +}; + +static const struct meson_sar_adc_data meson_sar_adc_meson8b_data = { + .param = &meson_sar_adc_meson8b_param, + .name = "meson-meson8b-saradc", +}; + +static const struct meson_sar_adc_data meson_sar_adc_meson8m2_data = { + .param = &meson_sar_adc_meson8b_param, + .name = "meson-meson8m2-saradc", +}; + +static const struct meson_sar_adc_data meson_sar_adc_gxbb_data = { + .param = &meson_sar_adc_gxbb_param, + .name = "meson-gxbb-saradc", +}; + +static const struct meson_sar_adc_data meson_sar_adc_gxl_data = { + .param = &meson_sar_adc_gxl_param, + .name = "meson-gxl-saradc", +}; + +static const struct meson_sar_adc_data meson_sar_adc_gxlx_data = { + .param = &meson_sar_adc_gxlx_param, + .name = "meson-gxlx-saradc", +}; + +static const struct meson_sar_adc_data meson_sar_adc_gxm_data = { + .param = &meson_sar_adc_gxl_param, + .name = "meson-gxm-saradc", +}; + +static const struct meson_sar_adc_data meson_sar_adc_axg_data = { + .param = &meson_sar_adc_axg_param, + .name = "meson-axg-saradc", +}; + +static const struct meson_sar_adc_data meson_sar_adc_g12a_data = { + .param = &meson_sar_adc_g12a_param, + .name = "meson-g12a-saradc", +}; + +static const struct meson_sar_adc_data meson_sar_adc_s4_data = { + .param = &meson_sar_adc_g12a_param, + .name = "meson-s4-saradc", +}; + +static const struct of_device_id meson_sar_adc_of_match[] = { + { + .compatible = "amlogic,meson8-saradc", + .data = &meson_sar_adc_meson8_data, + }, { + .compatible = "amlogic,meson8b-saradc", + .data = &meson_sar_adc_meson8b_data, + }, { + .compatible = "amlogic,meson8m2-saradc", + .data = &meson_sar_adc_meson8m2_data, + }, { + .compatible = "amlogic,meson-gxbb-saradc", + .data = &meson_sar_adc_gxbb_data, + }, { + .compatible = "amlogic,meson-gxl-saradc", + .data = &meson_sar_adc_gxl_data, + }, { + .compatible = "amlogic,meson-gxlx-saradc", + .data = &meson_sar_adc_gxlx_data, + }, { + .compatible = "amlogic,meson-gxm-saradc", + .data = &meson_sar_adc_gxm_data, + }, { + .compatible = "amlogic,meson-axg-saradc", + .data = &meson_sar_adc_axg_data, + }, { + .compatible = "amlogic,meson-g12a-saradc", + .data = &meson_sar_adc_g12a_data, + }, { + .compatible = "amlogic,meson-s4-saradc", + .data = &meson_sar_adc_s4_data, + }, + { } +}; +MODULE_DEVICE_TABLE(of, meson_sar_adc_of_match); + +static int meson_sar_adc_probe(struct platform_device *pdev) +{ + const struct meson_sar_adc_data *match_data; + struct meson_sar_adc_priv *priv; + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + void __iomem *base; + int irq, ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + priv = iio_priv(indio_dev); + init_completion(&priv->done); + + match_data = of_device_get_match_data(dev); + if (!match_data) + return dev_err_probe(dev, -ENODEV, "failed to get match data\n"); + + priv->param = match_data->param; + + indio_dev->name = match_data->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &meson_sar_adc_iio_info; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->regmap = devm_regmap_init_mmio(dev, base, priv->param->regmap_config); + if (IS_ERR(priv->regmap)) + return dev_err_probe(dev, PTR_ERR(priv->regmap), "failed to init regmap\n"); + + irq = irq_of_parse_and_map(dev->of_node, 0); + if (!irq) + return dev_err_probe(dev, -EINVAL, "failed to get irq\n"); + + ret = devm_request_irq(dev, irq, meson_sar_adc_irq, IRQF_SHARED, dev_name(dev), indio_dev); + if (ret) + return dev_err_probe(dev, ret, "failed to request irq\n"); + + priv->clkin = devm_clk_get(dev, "clkin"); + if (IS_ERR(priv->clkin)) + return dev_err_probe(dev, PTR_ERR(priv->clkin), "failed to get clkin\n"); + + priv->core_clk = devm_clk_get_enabled(dev, "core"); + if (IS_ERR(priv->core_clk)) + return dev_err_probe(dev, PTR_ERR(priv->core_clk), "failed to get core clk\n"); + + priv->adc_clk = devm_clk_get_optional(dev, "adc_clk"); + if (IS_ERR(priv->adc_clk)) + return dev_err_probe(dev, PTR_ERR(priv->adc_clk), "failed to get adc clk\n"); + + priv->adc_sel_clk = devm_clk_get_optional(dev, "adc_sel"); + if (IS_ERR(priv->adc_sel_clk)) + return dev_err_probe(dev, PTR_ERR(priv->adc_sel_clk), "failed to get adc_sel clk\n"); + + /* on pre-GXBB SoCs the SAR ADC itself provides the ADC clock: */ + if (!priv->adc_clk) { + ret = meson_sar_adc_clk_init(indio_dev, base); + if (ret) + return dev_err_probe(dev, ret, "failed to init internal clk\n"); + } + + priv->vref = devm_regulator_get(dev, "vref"); + if (IS_ERR(priv->vref)) + return dev_err_probe(dev, PTR_ERR(priv->vref), "failed to get vref regulator\n"); + + priv->calibscale = MILLION; + + if (priv->param->temperature_trimming_bits) { + ret = meson_sar_adc_temp_sensor_init(indio_dev); + if (ret) + return ret; + } + + if (priv->temperature_sensor_calibrated) { + indio_dev->channels = meson_sar_adc_and_temp_iio_channels; + indio_dev->num_channels = + ARRAY_SIZE(meson_sar_adc_and_temp_iio_channels); + } else { + indio_dev->channels = meson_sar_adc_iio_channels; + indio_dev->num_channels = + ARRAY_SIZE(meson_sar_adc_iio_channels); + } + + ret = meson_sar_adc_init(indio_dev); + if (ret) + goto err; + + mutex_init(&priv->lock); + + ret = meson_sar_adc_hw_enable(indio_dev); + if (ret) + goto err; + + ret = meson_sar_adc_calib(indio_dev); + if (ret) + dev_warn(dev, "calibration failed\n"); + + platform_set_drvdata(pdev, indio_dev); + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err_probe(dev, ret, "failed to register iio device\n"); + goto err_hw; + } + + return 0; + +err_hw: + meson_sar_adc_hw_disable(indio_dev); +err: + return ret; +} + +static void meson_sar_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + + iio_device_unregister(indio_dev); + + meson_sar_adc_hw_disable(indio_dev); +} + +static int meson_sar_adc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + + meson_sar_adc_hw_disable(indio_dev); + + clk_disable_unprepare(priv->core_clk); + + return 0; +} + +static int meson_sar_adc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + int ret; + + ret = clk_prepare_enable(priv->core_clk); + if (ret) { + dev_err(dev, "failed to enable core clk\n"); + return ret; + } + + return meson_sar_adc_hw_enable(indio_dev); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(meson_sar_adc_pm_ops, + meson_sar_adc_suspend, meson_sar_adc_resume); + +static struct platform_driver meson_sar_adc_driver = { + .probe = meson_sar_adc_probe, + .remove = meson_sar_adc_remove, + .driver = { + .name = "meson-saradc", + .of_match_table = meson_sar_adc_of_match, + .pm = pm_sleep_ptr(&meson_sar_adc_pm_ops), + }, +}; + +module_platform_driver(meson_sar_adc_driver); + +MODULE_AUTHOR("Martin Blumenstingl "); +MODULE_DESCRIPTION("Amlogic Meson SAR ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/mp2629_adc.c b/drivers/iio/adc/mp2629_adc.c new file mode 100644 index 000000000..c03f89ddb --- /dev/null +++ b/drivers/iio/adc/mp2629_adc.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MP2629 Driver for ADC + * + * Copyright 2020 Monolithic Power Systems, Inc + * + * Author: Saravanan Sekar + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MP2629_REG_ADC_CTRL 0x03 +#define MP2629_REG_BATT_VOLT 0x0e +#define MP2629_REG_SYSTEM_VOLT 0x0f +#define MP2629_REG_INPUT_VOLT 0x11 +#define MP2629_REG_BATT_CURRENT 0x12 +#define MP2629_REG_INPUT_CURRENT 0x13 + +#define MP2629_ADC_START BIT(7) +#define MP2629_ADC_CONTINUOUS BIT(6) + +#define MP2629_MAP(_mp, _mpc) IIO_MAP(#_mp, "mp2629_charger", "mp2629-"_mpc) + +#define MP2629_ADC_CHAN(_ch, _type) { \ + .type = _type, \ + .indexed = 1, \ + .datasheet_name = #_ch, \ + .channel = MP2629_ ## _ch, \ + .address = MP2629_REG_ ## _ch, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +struct mp2629_adc { + struct regmap *regmap; + struct device *dev; +}; + +static const struct iio_chan_spec mp2629_channels[] = { + MP2629_ADC_CHAN(BATT_VOLT, IIO_VOLTAGE), + MP2629_ADC_CHAN(SYSTEM_VOLT, IIO_VOLTAGE), + MP2629_ADC_CHAN(INPUT_VOLT, IIO_VOLTAGE), + MP2629_ADC_CHAN(BATT_CURRENT, IIO_CURRENT), + MP2629_ADC_CHAN(INPUT_CURRENT, IIO_CURRENT) +}; + +static const struct iio_map mp2629_adc_maps[] = { + MP2629_MAP(BATT_VOLT, "batt-volt"), + MP2629_MAP(SYSTEM_VOLT, "system-volt"), + MP2629_MAP(INPUT_VOLT, "input-volt"), + MP2629_MAP(BATT_CURRENT, "batt-current"), + MP2629_MAP(INPUT_CURRENT, "input-current"), + { } +}; + +static int mp2629_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mp2629_adc *info = iio_priv(indio_dev); + unsigned int rval; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(info->regmap, chan->address, &rval); + if (ret) + return ret; + + if (chan->channel == MP2629_INPUT_VOLT) + rval &= GENMASK(6, 0); + *val = rval; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + switch (chan->channel) { + case MP2629_BATT_VOLT: + case MP2629_SYSTEM_VOLT: + *val = 20; + return IIO_VAL_INT; + + case MP2629_INPUT_VOLT: + *val = 60; + return IIO_VAL_INT; + + case MP2629_BATT_CURRENT: + *val = 175; + *val2 = 10; + return IIO_VAL_FRACTIONAL; + + case MP2629_INPUT_CURRENT: + *val = 133; + *val2 = 10; + return IIO_VAL_FRACTIONAL; + + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +static const struct iio_info mp2629_adc_info = { + .read_raw = &mp2629_read_raw, +}; + +static int mp2629_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mp2629_data *ddata = dev_get_drvdata(dev->parent); + struct mp2629_adc *info; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*info)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + info->regmap = ddata->regmap; + info->dev = dev; + platform_set_drvdata(pdev, indio_dev); + + ret = regmap_set_bits(info->regmap, MP2629_REG_ADC_CTRL, + MP2629_ADC_START | MP2629_ADC_CONTINUOUS); + if (ret) { + dev_err(dev, "adc enable fail: %d\n", ret); + return ret; + } + + ret = iio_map_array_register(indio_dev, mp2629_adc_maps); + if (ret) { + dev_err(dev, "IIO maps register fail: %d\n", ret); + goto fail_disable; + } + + indio_dev->name = "mp2629-adc"; + indio_dev->channels = mp2629_channels; + indio_dev->num_channels = ARRAY_SIZE(mp2629_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &mp2629_adc_info; + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(dev, "IIO device register fail: %d\n", ret); + goto fail_map_unregister; + } + + return 0; + +fail_map_unregister: + iio_map_array_unregister(indio_dev); + +fail_disable: + regmap_clear_bits(info->regmap, MP2629_REG_ADC_CTRL, + MP2629_ADC_CONTINUOUS); + regmap_clear_bits(info->regmap, MP2629_REG_ADC_CTRL, MP2629_ADC_START); + + return ret; +} + +static void mp2629_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct mp2629_adc *info = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + iio_map_array_unregister(indio_dev); + + regmap_clear_bits(info->regmap, MP2629_REG_ADC_CTRL, + MP2629_ADC_CONTINUOUS); + regmap_clear_bits(info->regmap, MP2629_REG_ADC_CTRL, MP2629_ADC_START); +} + +static const struct of_device_id mp2629_adc_of_match[] = { + { .compatible = "mps,mp2629_adc" }, + { } +}; +MODULE_DEVICE_TABLE(of, mp2629_adc_of_match); + +static struct platform_driver mp2629_adc_driver = { + .driver = { + .name = "mp2629_adc", + .of_match_table = mp2629_adc_of_match, + }, + .probe = mp2629_adc_probe, + .remove = mp2629_adc_remove, +}; +module_platform_driver(mp2629_adc_driver); + +MODULE_AUTHOR("Saravanan Sekar "); +MODULE_DESCRIPTION("MP2629 ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/mt6323-auxadc.c b/drivers/iio/adc/mt6323-auxadc.c new file mode 100644 index 000000000..4f73ac42a --- /dev/null +++ b/drivers/iio/adc/mt6323-auxadc.c @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2026 Roman Vivchar + * + * Based on drivers/iio/adc/mt6359-auxadc.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#define AUXADC_STRUP_CON10_RSTB_SEL BIT(7) +#define AUXADC_STRUP_CON10_RSTB_SW BIT(5) + +#define AUXADC_TOP_CKPDN2_CTL_CK BIT(5) + +#define AUXADC_TRIM_CH2_MASK GENMASK(11, 10) +#define AUXADC_TRIM_CH4_MASK GENMASK(9, 8) +#define AUXADC_TRIM_CH5_MASK GENMASK(5, 4) +#define AUXADC_TRIM_CH6_MASK GENMASK(3, 2) + +#define AUXADC_CON27_VREF18_ENB_MD BIT(15) +#define AUXADC_CON27_MD_STATUS BIT(0) + +#define AUXADC_CON19_GPS_STATUS BIT(1) + +#define AUXADC_CON26_VREF18_SELB BIT(1) +#define AUXADC_CON26_DECI_GDLY_SEL BIT(0) + +#define AUXADC_CON11_VBUF_EN BIT(4) + +#define AUXADC_CON19_DECI_GDLY_MASK GENMASK(15, 14) +#define AUXADC_ADC19_BUSY_MASK GENMASK(15, 1) +#define AUXADC_READY_MASK BIT(15) +#define AUXADC_DATA_MASK GENMASK(14, 0) + +#define AUXADC_CON9_OSR_MASK GENMASK(12, 10) +#define AUXADC_DEFAULT_OSR 3 + +#define MTK_PMIC_IIO_CHAN(_name, _chan, _addr) \ +{ \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _chan, \ + .address = _addr, \ + .datasheet_name = __stringify(_name), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ +} + +/* + * AUXADC reports everything in mV, including temperature and + * current channels. Channel macros are mapped such that their + * ID matches their respective hardware bit position in CON22. + */ +static const struct iio_chan_spec mt6323_auxadc_channels[] = { + MTK_PMIC_IIO_CHAN(baton2, MT6323_AUXADC_BATON2, MT6323_AUXADC_ADC6), + MTK_PMIC_IIO_CHAN(ch6, MT6323_AUXADC_CH6, MT6323_AUXADC_ADC11), + MTK_PMIC_IIO_CHAN(bat_temp, MT6323_AUXADC_BAT_TEMP, MT6323_AUXADC_ADC5), + MTK_PMIC_IIO_CHAN(chip_temp, MT6323_AUXADC_CHIP_TEMP, MT6323_AUXADC_ADC4), + MTK_PMIC_IIO_CHAN(vcdt, MT6323_AUXADC_VCDT, MT6323_AUXADC_ADC2), + MTK_PMIC_IIO_CHAN(baton1, MT6323_AUXADC_BATON1, MT6323_AUXADC_ADC3), + MTK_PMIC_IIO_CHAN(isense, MT6323_AUXADC_ISENSE, MT6323_AUXADC_ADC1), + MTK_PMIC_IIO_CHAN(batsns, MT6323_AUXADC_BATSNS, MT6323_AUXADC_ADC0), + MTK_PMIC_IIO_CHAN(accdet, MT6323_AUXADC_ACCDET, MT6323_AUXADC_ADC7), +}; + +/* + * The MediaTek MT6323 (as well as a lot of other PMICs) has the following hierarchy: + * PMIC AUXADC <- PMIC MFD <- SoC PWRAP (wrapper for PWRAP FSM) + * + * Therefore, PWRAP regmap should be obtained using dev->parent->parent. + */ +struct mt6323_auxadc { + struct regmap *regmap; + /* AUXADC doesn't support reading multiple channels simultaneously. */ + struct mutex lock; +}; + +static int mt6323_auxadc_prepare_channel(struct mt6323_auxadc *auxadc) +{ + struct regmap *map = auxadc->regmap; + u32 val; + int ret; + + ret = regmap_read(map, MT6323_AUXADC_CON19, &val); + if (ret) + return ret; + + /* The ADC is idle. */ + if (!(val & AUXADC_CON19_DECI_GDLY_MASK)) + return 0; + + ret = regmap_read_poll_timeout(map, MT6323_AUXADC_ADC19, + val, !(val & AUXADC_ADC19_BUSY_MASK), + 10, 500); + if (ret) + return ret; + + return regmap_clear_bits(map, MT6323_AUXADC_CON19, + AUXADC_CON19_DECI_GDLY_MASK); +} + +static int mt6323_auxadc_request(struct mt6323_auxadc *auxadc, + unsigned long channel) +{ + struct regmap *map = auxadc->regmap; + int ret; + + ret = regmap_set_bits(map, MT6323_AUXADC_CON11, AUXADC_CON11_VBUF_EN); + if (ret) + return ret; + + return regmap_set_bits(map, MT6323_AUXADC_CON22, BIT(channel)); +} + +static int mt6323_auxadc_release(struct mt6323_auxadc *auxadc, + unsigned long channel) +{ + struct regmap *map = auxadc->regmap; + int ret; + + ret = regmap_clear_bits(map, MT6323_AUXADC_CON22, BIT(channel)); + if (ret) + return ret; + + return regmap_clear_bits(map, MT6323_AUXADC_CON11, AUXADC_CON11_VBUF_EN); +} + +static int mt6323_auxadc_read(struct mt6323_auxadc *auxadc, + const struct iio_chan_spec *chan, int *out) +{ + struct regmap *map = auxadc->regmap; + u32 val; + int ret; + + ret = regmap_read_poll_timeout(map, chan->address, + val, (val & AUXADC_READY_MASK), + 1 * USEC_PER_MSEC, 100 * USEC_PER_MSEC); + if (ret) + return ret; + + *out = FIELD_GET(AUXADC_DATA_MASK, val); + + return 0; +} + +static int mt6323_auxadc_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct mt6323_auxadc *auxadc = iio_priv(indio_dev); + int ret, mult; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (chan->channel == MT6323_AUXADC_ISENSE || + chan->channel == MT6323_AUXADC_BATSNS) + mult = 4; + else + mult = 1; + + /* 1800mV full range with 15-bit resolution. */ + *val = mult * 1800; + *val2 = 15; + + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_RAW: { + guard(mutex)(&auxadc->lock); + + ret = mt6323_auxadc_prepare_channel(auxadc); + if (ret) + return ret; + + ret = mt6323_auxadc_request(auxadc, chan->channel); + if (ret) + return ret; + + /* Hardware limitation: the AUXADC needs a delay to become ready. */ + fsleep(300); + + ret = mt6323_auxadc_read(auxadc, chan, val); + + if (mt6323_auxadc_release(auxadc, chan->channel)) + dev_err(&indio_dev->dev, + "failed to release channel %d\n", chan->channel); + + if (ret) + return ret; + + return IIO_VAL_INT; + } + default: + return -EINVAL; + } +} + +static int mt6323_auxadc_init(struct mt6323_auxadc *auxadc) +{ + struct regmap *map = auxadc->regmap; + int ret; + + ret = regmap_set_bits(map, MT6323_STRUP_CON10, + AUXADC_STRUP_CON10_RSTB_SW | + AUXADC_STRUP_CON10_RSTB_SEL); + if (ret) + return ret; + + ret = regmap_set_bits(map, MT6323_TOP_CKPDN2, AUXADC_TOP_CKPDN2_CTL_CK); + if (ret) + return ret; + + ret = regmap_update_bits(map, MT6323_AUXADC_CON10, + AUXADC_TRIM_CH2_MASK | AUXADC_TRIM_CH4_MASK | + AUXADC_TRIM_CH5_MASK | AUXADC_TRIM_CH6_MASK, + FIELD_PREP(AUXADC_TRIM_CH2_MASK, 1) | + FIELD_PREP(AUXADC_TRIM_CH4_MASK, 1) | + FIELD_PREP(AUXADC_TRIM_CH5_MASK, 1) | + FIELD_PREP(AUXADC_TRIM_CH6_MASK, 1)); + if (ret) + return ret; + + ret = regmap_set_bits(map, MT6323_AUXADC_CON27, + AUXADC_CON27_VREF18_ENB_MD | + AUXADC_CON27_MD_STATUS); + if (ret) + return ret; + + ret = regmap_set_bits(map, MT6323_AUXADC_CON19, AUXADC_CON19_GPS_STATUS); + if (ret) + return ret; + + ret = regmap_set_bits(map, MT6323_AUXADC_CON26, + AUXADC_CON26_VREF18_SELB | + AUXADC_CON26_DECI_GDLY_SEL); + if (ret) + return ret; + + return regmap_update_bits(map, MT6323_AUXADC_CON9, AUXADC_CON9_OSR_MASK, + FIELD_PREP(AUXADC_CON9_OSR_MASK, AUXADC_DEFAULT_OSR)); +} + +static const struct iio_info mt6323_auxadc_iio_info = { + .read_raw = mt6323_auxadc_read_raw, +}; + +static int mt6323_auxadc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mt6323_auxadc *auxadc; + struct regmap *regmap; + struct iio_dev *iio; + int ret; + + regmap = dev_get_regmap(dev->parent->parent, NULL); + if (!regmap) + return dev_err_probe(dev, -ENODEV, "failed to get regmap\n"); + + iio = devm_iio_device_alloc(dev, sizeof(*auxadc)); + if (!iio) + return -ENOMEM; + + auxadc = iio_priv(iio); + auxadc->regmap = regmap; + + ret = devm_mutex_init(dev, &auxadc->lock); + if (ret) + return ret; + + ret = mt6323_auxadc_init(auxadc); + if (ret) + return dev_err_probe(dev, ret, "failed to initialize auxadc\n"); + + iio->name = "mt6323-auxadc"; + iio->info = &mt6323_auxadc_iio_info; + iio->modes = INDIO_DIRECT_MODE; + iio->channels = mt6323_auxadc_channels; + iio->num_channels = ARRAY_SIZE(mt6323_auxadc_channels); + + return devm_iio_device_register(dev, iio); +} + +static const struct of_device_id mt6323_auxadc_of_match[] = { + { .compatible = "mediatek,mt6323-auxadc" }, + { } +}; +MODULE_DEVICE_TABLE(of, mt6323_auxadc_of_match); + +static struct platform_driver mt6323_auxadc_driver = { + .driver = { + .name = "mt6323-auxadc", + .of_match_table = mt6323_auxadc_of_match, + }, + .probe = mt6323_auxadc_probe, +}; +module_platform_driver(mt6323_auxadc_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("MediaTek MT6323 PMIC AUXADC Driver"); diff --git a/drivers/iio/adc/mt6359-auxadc.c b/drivers/iio/adc/mt6359-auxadc.c new file mode 100644 index 000000000..88b0e2630 --- /dev/null +++ b/drivers/iio/adc/mt6359-auxadc.c @@ -0,0 +1,908 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MediaTek MT6359 PMIC AUXADC IIO driver + * + * Copyright (c) 2021 MediaTek Inc. + * Copyright (c) 2024 Collabora Ltd + * Author: AngeloGioacchino Del Regno + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include + +#define AUXADC_AVG_TIME_US 10 +#define AUXADC_POLL_DELAY_US 100 +#define AUXADC_TIMEOUT_US 32000 +#define IMP_STOP_DELAY_US 150 +#define IMP_POLL_DELAY_US 1000 + +/* For PMIC_RG_RESET_VAL and MT6358_IMP0_CLEAR, the bits specific purpose is unknown. */ +#define PMIC_RG_RESET_VAL (BIT(0) | BIT(3)) +#define PMIC_AUXADC_RDY_BIT BIT(15) +#define MT6357_IMP_ADC_NUM 30 +#define MT6358_IMP_ADC_NUM 28 + +#define MT6358_DCM_CK_SW_EN GENMASK(1, 0) +#define MT6358_IMP0_CLEAR (BIT(14) | BIT(7)) +#define MT6358_IMP0_IRQ_RDY BIT(8) +#define MT6358_IMP1_AUTOREPEAT_EN BIT(15) + +#define MT6359_IMP0_CONV_EN BIT(0) +#define MT6359_IMP1_IRQ_RDY BIT(15) + +#define MT6363_EXT_CHAN_MASK GENMASK(2, 0) +#define MT6363_EXT_PURES_MASK GENMASK(4, 3) + #define MT6363_PULLUP_RES_100K 0 + #define MT6363_PULLUP_RES_30K 1 + #define MT6363_PULLUP_RES_OPEN 3 + +enum mtk_pmic_auxadc_regs { + PMIC_AUXADC_ADC0, + PMIC_AUXADC_DCM_CON, + PMIC_AUXADC_IMP0, + PMIC_AUXADC_IMP1, + PMIC_AUXADC_IMP3, + PMIC_AUXADC_RQST0, + PMIC_AUXADC_RQST1, + PMIC_AUXADC_RQST3, + PMIC_AUXADC_SDMADC_CON0, + PMIC_HK_TOP_WKEY, + PMIC_HK_TOP_RST_CON0, + PMIC_FGADC_R_CON0, + PMIC_AUXADC_REGS_MAX +}; + +enum mtk_pmic_auxadc_channels { + PMIC_AUXADC_CHAN_BATADC, + PMIC_AUXADC_CHAN_ISENSE, + PMIC_AUXADC_CHAN_VCDT, + PMIC_AUXADC_CHAN_BAT_TEMP, + PMIC_AUXADC_CHAN_BATID, + PMIC_AUXADC_CHAN_CHIP_TEMP, + PMIC_AUXADC_CHAN_VCORE_TEMP, + PMIC_AUXADC_CHAN_VPROC_TEMP, + PMIC_AUXADC_CHAN_VGPU_TEMP, + PMIC_AUXADC_CHAN_ACCDET, + PMIC_AUXADC_CHAN_VDCXO, + PMIC_AUXADC_CHAN_TSX_TEMP, + PMIC_AUXADC_CHAN_HPOFS_CAL, + PMIC_AUXADC_CHAN_DCXO_TEMP, + PMIC_AUXADC_CHAN_VTREF, + PMIC_AUXADC_CHAN_VBIF, + PMIC_AUXADC_CHAN_VSYSSNS, + PMIC_AUXADC_CHAN_VIN1, + PMIC_AUXADC_CHAN_VIN2, + PMIC_AUXADC_CHAN_VIN3, + PMIC_AUXADC_CHAN_VIN4, + PMIC_AUXADC_CHAN_VIN5, + PMIC_AUXADC_CHAN_VIN6, + PMIC_AUXADC_CHAN_VIN7, + PMIC_AUXADC_CHAN_IBAT, + PMIC_AUXADC_CHAN_VBAT, + PMIC_AUXADC_CHAN_MAX +}; + +/** + * struct mt6359_auxadc - Main driver structure + * @dev: Device pointer + * @regmap: Regmap from SoC PMIC Wrapper + * @chip_info: PMIC specific chip info + * @lock: Mutex to serialize AUXADC reading vs configuration + * @timed_out: Signals whether the last read timed out + */ +struct mt6359_auxadc { + struct device *dev; + struct regmap *regmap; + const struct mtk_pmic_auxadc_info *chip_info; + struct mutex lock; + bool timed_out; +}; + +/** + * struct mtk_pmic_auxadc_chan - PMIC AUXADC channel data + * @req_idx: Request register number + * @req_mask: Bitmask to activate a channel + * @rdy_idx: Readiness register number + * @rdy_mask: Bitmask to determine channel readiness + * @ext_sel_idx: PMIC GPIO channel register number + * @ext_sel_ch: PMIC GPIO number + * @ext_sel_pu: PMIC GPIO channel pullup resistor selector + * @num_samples: Number of AUXADC samples for averaging + * @r_ratio: Resistance ratio fractional + */ +struct mtk_pmic_auxadc_chan { + u8 req_idx; + u16 req_mask; + u8 rdy_idx; + u16 rdy_mask; + s8 ext_sel_idx; + u8 ext_sel_ch; + u8 ext_sel_pu; + u16 num_samples; + struct u8_fract r_ratio; +}; + +/** + * struct mtk_pmic_auxadc_info - PMIC specific chip info + * @model_name: PMIC model name + * @channels: IIO specification of ADC channels + * @num_channels: Number of ADC channels + * @desc: PMIC AUXADC channel data + * @regs: List of PMIC specific registers + * @sec_unlock_key: Security unlock key for HK_TOP writes + * @vref_mV: AUXADC Reference Voltage (VREF) in millivolts + * @imp_adc_num: ADC channel for battery impedance readings + * @is_spmi: Defines whether this PMIC communicates over SPMI + * @no_reset: If true, this PMIC does not support ADC reset + * @read_imp: Callback to read impedance channels + */ +struct mtk_pmic_auxadc_info { + const char *model_name; + const struct iio_chan_spec *channels; + u8 num_channels; + const struct mtk_pmic_auxadc_chan *desc; + const u16 *regs; + u16 sec_unlock_key; + u32 vref_mV; + u8 imp_adc_num; + bool is_spmi; + bool no_reset; + int (*read_imp)(struct mt6359_auxadc *adc_dev, + const struct iio_chan_spec *chan, int *vbat, int *ibat); +}; + +#define MTK_PMIC_ADC_EXT_CHAN(_ch_idx, _req_idx, _req_bit, _rdy_idx, _rdy_bit, \ + _ext_sel_idx, _ext_sel_ch, _ext_sel_pu, \ + _samples, _rnum, _rdiv) \ + [PMIC_AUXADC_CHAN_##_ch_idx] = { \ + .req_idx = _req_idx, \ + .req_mask = BIT(_req_bit), \ + .rdy_idx = _rdy_idx, \ + .rdy_mask = BIT(_rdy_bit), \ + .ext_sel_idx = _ext_sel_idx, \ + .ext_sel_ch = _ext_sel_ch, \ + .ext_sel_pu = _ext_sel_pu, \ + .num_samples = _samples, \ + .r_ratio = { _rnum, _rdiv } \ + } + +#define MTK_PMIC_ADC_CHAN(_ch_idx, _req_idx, _req_bit, _rdy_idx, _rdy_bit, \ + _samples, _rnum, _rdiv) \ + MTK_PMIC_ADC_EXT_CHAN(_ch_idx, _req_idx, _req_bit, _rdy_idx, _rdy_bit, \ + -1, 0, 0, _samples, _rnum, _rdiv) + +#define MTK_PMIC_IIO_CHAN(_model, _name, _ch_idx, _adc_idx, _nbits, _ch_type) \ +{ \ + .type = _ch_type, \ + .channel = _model##_AUXADC_##_ch_idx, \ + .address = _adc_idx, \ + .scan_index = PMIC_AUXADC_CHAN_##_ch_idx, \ + .datasheet_name = __stringify(_name), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = _nbits, \ + .storagebits = 16, \ + .endianness = IIO_CPU \ + }, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE) \ +} + +static const struct iio_chan_spec mt6357_auxadc_channels[] = { + MTK_PMIC_IIO_CHAN(MT6357, bat_adc, BATADC, 0, 15, IIO_RESISTANCE), + MTK_PMIC_IIO_CHAN(MT6357, isense, ISENSE, 1, 12, IIO_CURRENT), + MTK_PMIC_IIO_CHAN(MT6357, cdt_v, VCDT, 2, 12, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6357, batt_temp, BAT_TEMP, 3, 12, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6357, chip_temp, CHIP_TEMP, 4, 12, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6357, acc_det, ACCDET, 5, 12, IIO_RESISTANCE), + MTK_PMIC_IIO_CHAN(MT6357, dcxo_v, VDCXO, 6, 12, IIO_VOLTAGE), + MTK_PMIC_IIO_CHAN(MT6357, tsx_temp, TSX_TEMP, 7, 15, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6357, hp_ofs_cal, HPOFS_CAL, 9, 15, IIO_RESISTANCE), + MTK_PMIC_IIO_CHAN(MT6357, dcxo_temp, DCXO_TEMP, 36, 15, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6357, vcore_temp, VCORE_TEMP, 40, 12, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6357, vproc_temp, VPROC_TEMP, 41, 12, IIO_TEMP), + + /* Battery impedance channels */ + MTK_PMIC_IIO_CHAN(MT6357, batt_v, VBAT, 0, 15, IIO_VOLTAGE), +}; + +static const struct mtk_pmic_auxadc_chan mt6357_auxadc_ch_desc[] = { + MTK_PMIC_ADC_CHAN(BATADC, PMIC_AUXADC_RQST0, 0, PMIC_AUXADC_IMP0, 8, 128, 3, 1), + MTK_PMIC_ADC_CHAN(ISENSE, PMIC_AUXADC_RQST0, 0, PMIC_AUXADC_IMP0, 8, 128, 3, 1), + MTK_PMIC_ADC_CHAN(VCDT, PMIC_AUXADC_RQST0, 0, PMIC_AUXADC_IMP0, 8, 8, 1, 1), + MTK_PMIC_ADC_CHAN(BAT_TEMP, PMIC_AUXADC_RQST0, 3, PMIC_AUXADC_IMP0, 8, 8, 1, 1), + MTK_PMIC_ADC_CHAN(CHIP_TEMP, PMIC_AUXADC_RQST0, 4, PMIC_AUXADC_IMP0, 8, 8, 1, 1), + MTK_PMIC_ADC_CHAN(ACCDET, PMIC_AUXADC_RQST0, 5, PMIC_AUXADC_IMP0, 8, 8, 1, 1), + MTK_PMIC_ADC_CHAN(TSX_TEMP, PMIC_AUXADC_RQST0, 7, PMIC_AUXADC_IMP0, 8, 128, 1, 1), + MTK_PMIC_ADC_CHAN(HPOFS_CAL, PMIC_AUXADC_RQST0, 9, PMIC_AUXADC_IMP0, 8, 256, 1, 1), + MTK_PMIC_ADC_CHAN(DCXO_TEMP, PMIC_AUXADC_RQST0, 10, PMIC_AUXADC_IMP0, 8, 16, 1, 1), + MTK_PMIC_ADC_CHAN(VBIF, PMIC_AUXADC_RQST0, 11, PMIC_AUXADC_IMP0, 8, 8, 1, 1), + MTK_PMIC_ADC_CHAN(VCORE_TEMP, PMIC_AUXADC_RQST1, 5, PMIC_AUXADC_IMP0, 8, 8, 1, 1), + MTK_PMIC_ADC_CHAN(VPROC_TEMP, PMIC_AUXADC_RQST1, 6, PMIC_AUXADC_IMP0, 8, 8, 1, 1), + + /* Battery impedance channels */ + MTK_PMIC_ADC_CHAN(VBAT, 0, 0, PMIC_AUXADC_IMP0, 8, 128, 3, 1), +}; + +static const u16 mt6357_auxadc_regs[] = { + [PMIC_HK_TOP_RST_CON0] = 0x0f90, + [PMIC_AUXADC_DCM_CON] = 0x122e, + [PMIC_AUXADC_ADC0] = 0x1088, + [PMIC_AUXADC_IMP0] = 0x119c, + [PMIC_AUXADC_IMP1] = 0x119e, + [PMIC_AUXADC_RQST0] = 0x110e, + [PMIC_AUXADC_RQST1] = 0x1114, +}; + +static const struct iio_chan_spec mt6358_auxadc_channels[] = { + MTK_PMIC_IIO_CHAN(MT6358, bat_adc, BATADC, 0, 15, IIO_RESISTANCE), + MTK_PMIC_IIO_CHAN(MT6358, cdt_v, VCDT, 2, 12, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6358, batt_temp, BAT_TEMP, 3, 12, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6358, chip_temp, CHIP_TEMP, 4, 12, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6358, acc_det, ACCDET, 5, 12, IIO_RESISTANCE), + MTK_PMIC_IIO_CHAN(MT6358, dcxo_v, VDCXO, 6, 12, IIO_VOLTAGE), + MTK_PMIC_IIO_CHAN(MT6358, tsx_temp, TSX_TEMP, 7, 15, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6358, hp_ofs_cal, HPOFS_CAL, 9, 15, IIO_RESISTANCE), + MTK_PMIC_IIO_CHAN(MT6358, dcxo_temp, DCXO_TEMP, 10, 15, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6358, bif_v, VBIF, 11, 12, IIO_VOLTAGE), + MTK_PMIC_IIO_CHAN(MT6358, vcore_temp, VCORE_TEMP, 38, 12, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6358, vproc_temp, VPROC_TEMP, 39, 12, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6358, vgpu_temp, VGPU_TEMP, 40, 12, IIO_TEMP), + + /* Battery impedance channels */ + MTK_PMIC_IIO_CHAN(MT6358, batt_v, VBAT, 0, 15, IIO_VOLTAGE), +}; + +static const struct mtk_pmic_auxadc_chan mt6358_auxadc_ch_desc[] = { + MTK_PMIC_ADC_CHAN(BATADC, PMIC_AUXADC_RQST0, 0, PMIC_AUXADC_IMP0, 8, 128, 3, 1), + MTK_PMIC_ADC_CHAN(VCDT, PMIC_AUXADC_RQST0, 0, PMIC_AUXADC_IMP0, 8, 8, 1, 1), + MTK_PMIC_ADC_CHAN(BAT_TEMP, PMIC_AUXADC_RQST0, 3, PMIC_AUXADC_IMP0, 8, 8, 2, 1), + MTK_PMIC_ADC_CHAN(CHIP_TEMP, PMIC_AUXADC_RQST0, 4, PMIC_AUXADC_IMP0, 8, 8, 1, 1), + MTK_PMIC_ADC_CHAN(ACCDET, PMIC_AUXADC_RQST0, 5, PMIC_AUXADC_IMP0, 8, 8, 1, 1), + MTK_PMIC_ADC_CHAN(VDCXO, PMIC_AUXADC_RQST0, 6, PMIC_AUXADC_IMP0, 8, 8, 3, 2), + MTK_PMIC_ADC_CHAN(TSX_TEMP, PMIC_AUXADC_RQST0, 7, PMIC_AUXADC_IMP0, 8, 128, 1, 1), + MTK_PMIC_ADC_CHAN(HPOFS_CAL, PMIC_AUXADC_RQST0, 9, PMIC_AUXADC_IMP0, 8, 256, 1, 1), + MTK_PMIC_ADC_CHAN(DCXO_TEMP, PMIC_AUXADC_RQST0, 10, PMIC_AUXADC_IMP0, 8, 16, 1, 1), + MTK_PMIC_ADC_CHAN(VBIF, PMIC_AUXADC_RQST0, 11, PMIC_AUXADC_IMP0, 8, 8, 2, 1), + MTK_PMIC_ADC_CHAN(VCORE_TEMP, PMIC_AUXADC_RQST1, 8, PMIC_AUXADC_IMP0, 8, 8, 1, 1), + MTK_PMIC_ADC_CHAN(VPROC_TEMP, PMIC_AUXADC_RQST1, 9, PMIC_AUXADC_IMP0, 8, 8, 1, 1), + MTK_PMIC_ADC_CHAN(VGPU_TEMP, PMIC_AUXADC_RQST1, 10, PMIC_AUXADC_IMP0, 8, 8, 1, 1), + + /* Battery impedance channels */ + MTK_PMIC_ADC_CHAN(VBAT, 0, 0, PMIC_AUXADC_IMP0, 8, 128, 7, 2), +}; + +static const u16 mt6358_auxadc_regs[] = { + [PMIC_HK_TOP_RST_CON0] = 0x0f90, + [PMIC_AUXADC_DCM_CON] = 0x1260, + [PMIC_AUXADC_ADC0] = 0x1088, + [PMIC_AUXADC_IMP0] = 0x1208, + [PMIC_AUXADC_IMP1] = 0x120a, + [PMIC_AUXADC_RQST0] = 0x1108, + [PMIC_AUXADC_RQST1] = 0x110a, +}; + +static const struct iio_chan_spec mt6359_auxadc_channels[] = { + MTK_PMIC_IIO_CHAN(MT6359, bat_adc, BATADC, 0, 15, IIO_RESISTANCE), + MTK_PMIC_IIO_CHAN(MT6359, batt_temp, BAT_TEMP, 3, 12, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6359, chip_temp, CHIP_TEMP, 4, 12, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6359, acc_det, ACCDET, 5, 12, IIO_RESISTANCE), + MTK_PMIC_IIO_CHAN(MT6359, dcxo_v, VDCXO, 6, 12, IIO_VOLTAGE), + MTK_PMIC_IIO_CHAN(MT6359, tsx_temp, TSX_TEMP, 7, 15, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6359, hp_ofs_cal, HPOFS_CAL, 9, 15, IIO_RESISTANCE), + MTK_PMIC_IIO_CHAN(MT6359, dcxo_temp, DCXO_TEMP, 10, 15, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6359, bif_v, VBIF, 11, 12, IIO_VOLTAGE), + MTK_PMIC_IIO_CHAN(MT6359, vcore_temp, VCORE_TEMP, 30, 12, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6359, vproc_temp, VPROC_TEMP, 31, 12, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6359, vgpu_temp, VGPU_TEMP, 32, 12, IIO_TEMP), + + /* Battery impedance channels */ + MTK_PMIC_IIO_CHAN(MT6359, batt_v, VBAT, 0, 15, IIO_VOLTAGE), + MTK_PMIC_IIO_CHAN(MT6359, batt_i, IBAT, 0, 15, IIO_CURRENT), +}; + +static const struct mtk_pmic_auxadc_chan mt6359_auxadc_ch_desc[] = { + MTK_PMIC_ADC_CHAN(BATADC, PMIC_AUXADC_RQST0, 0, PMIC_AUXADC_IMP1, 15, 128, 7, 2), + MTK_PMIC_ADC_CHAN(BAT_TEMP, PMIC_AUXADC_RQST0, 3, PMIC_AUXADC_IMP1, 15, 8, 5, 2), + MTK_PMIC_ADC_CHAN(CHIP_TEMP, PMIC_AUXADC_RQST0, 4, PMIC_AUXADC_IMP1, 15, 8, 1, 1), + MTK_PMIC_ADC_CHAN(ACCDET, PMIC_AUXADC_RQST0, 5, PMIC_AUXADC_IMP1, 15, 8, 1, 1), + MTK_PMIC_ADC_CHAN(VDCXO, PMIC_AUXADC_RQST0, 6, PMIC_AUXADC_IMP1, 15, 8, 3, 2), + MTK_PMIC_ADC_CHAN(TSX_TEMP, PMIC_AUXADC_RQST0, 7, PMIC_AUXADC_IMP1, 15, 128, 1, 1), + MTK_PMIC_ADC_CHAN(HPOFS_CAL, PMIC_AUXADC_RQST0, 9, PMIC_AUXADC_IMP1, 15, 256, 1, 1), + MTK_PMIC_ADC_CHAN(DCXO_TEMP, PMIC_AUXADC_RQST0, 10, PMIC_AUXADC_IMP1, 15, 16, 1, 1), + MTK_PMIC_ADC_CHAN(VBIF, PMIC_AUXADC_RQST0, 11, PMIC_AUXADC_IMP1, 15, 8, 5, 2), + MTK_PMIC_ADC_CHAN(VCORE_TEMP, PMIC_AUXADC_RQST1, 8, PMIC_AUXADC_IMP1, 15, 8, 1, 1), + MTK_PMIC_ADC_CHAN(VPROC_TEMP, PMIC_AUXADC_RQST1, 9, PMIC_AUXADC_IMP1, 15, 8, 1, 1), + MTK_PMIC_ADC_CHAN(VGPU_TEMP, PMIC_AUXADC_RQST1, 10, PMIC_AUXADC_IMP1, 15, 8, 1, 1), + + /* Battery impedance channels */ + MTK_PMIC_ADC_CHAN(VBAT, 0, 0, PMIC_AUXADC_IMP1, 15, 128, 7, 2), + MTK_PMIC_ADC_CHAN(IBAT, 0, 0, PMIC_AUXADC_IMP1, 15, 128, 7, 2), +}; + +static const u16 mt6359_auxadc_regs[] = { + [PMIC_FGADC_R_CON0] = 0x0d88, + [PMIC_HK_TOP_WKEY] = 0x0fb4, + [PMIC_HK_TOP_RST_CON0] = 0x0f90, + [PMIC_AUXADC_RQST0] = 0x1108, + [PMIC_AUXADC_RQST1] = 0x110a, + [PMIC_AUXADC_ADC0] = 0x1088, + [PMIC_AUXADC_IMP0] = 0x1208, + [PMIC_AUXADC_IMP1] = 0x120a, + [PMIC_AUXADC_IMP3] = 0x120e, +}; + +static const struct iio_chan_spec mt6363_auxadc_channels[] = { + MTK_PMIC_IIO_CHAN(MT6363, bat_adc, BATADC, 0, 15, IIO_RESISTANCE), + MTK_PMIC_IIO_CHAN(MT6363, cdt_v, VCDT, 2, 12, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6363, batt_temp, BAT_TEMP, 3, 12, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6363, chip_temp, CHIP_TEMP, 4, 12, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6363, sys_sns_v, VSYSSNS, 6, 15, IIO_VOLTAGE), + MTK_PMIC_IIO_CHAN(MT6363, tref_v, VTREF, 11, 12, IIO_VOLTAGE), + MTK_PMIC_IIO_CHAN(MT6363, vcore_temp, VCORE_TEMP, 38, 12, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6363, vproc_temp, VPROC_TEMP, 39, 12, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6363, vgpu_temp, VGPU_TEMP, 40, 12, IIO_TEMP), + + /* For VIN, ADC12 holds the result depending on which GPIO was activated */ + MTK_PMIC_IIO_CHAN(MT6363, in1_v, VIN1, 45, 15, IIO_VOLTAGE), + MTK_PMIC_IIO_CHAN(MT6363, in2_v, VIN2, 45, 15, IIO_VOLTAGE), + MTK_PMIC_IIO_CHAN(MT6363, in3_v, VIN3, 45, 15, IIO_VOLTAGE), + MTK_PMIC_IIO_CHAN(MT6363, in4_v, VIN4, 45, 15, IIO_VOLTAGE), + MTK_PMIC_IIO_CHAN(MT6363, in5_v, VIN5, 45, 15, IIO_VOLTAGE), + MTK_PMIC_IIO_CHAN(MT6363, in6_v, VIN6, 45, 15, IIO_VOLTAGE), + MTK_PMIC_IIO_CHAN(MT6363, in7_v, VIN7, 45, 15, IIO_VOLTAGE), +}; + +static const struct mtk_pmic_auxadc_chan mt6363_auxadc_ch_desc[] = { + MTK_PMIC_ADC_CHAN(BATADC, PMIC_AUXADC_RQST0, 0, PMIC_AUXADC_ADC0, 15, 64, 4, 1), + MTK_PMIC_ADC_CHAN(VCDT, PMIC_AUXADC_RQST0, 2, PMIC_AUXADC_ADC0, 15, 32, 1, 1), + MTK_PMIC_ADC_CHAN(BAT_TEMP, PMIC_AUXADC_RQST0, 3, PMIC_AUXADC_ADC0, 15, 32, 3, 2), + MTK_PMIC_ADC_CHAN(CHIP_TEMP, PMIC_AUXADC_RQST0, 4, PMIC_AUXADC_ADC0, 15, 32, 1, 1), + MTK_PMIC_ADC_CHAN(VSYSSNS, PMIC_AUXADC_RQST1, 6, PMIC_AUXADC_ADC0, 15, 64, 3, 1), + MTK_PMIC_ADC_CHAN(VTREF, PMIC_AUXADC_RQST1, 3, PMIC_AUXADC_ADC0, 15, 32, 3, 2), + MTK_PMIC_ADC_CHAN(VCORE_TEMP, PMIC_AUXADC_RQST3, 0, PMIC_AUXADC_ADC0, 15, 32, 1, 1), + MTK_PMIC_ADC_CHAN(VPROC_TEMP, PMIC_AUXADC_RQST3, 1, PMIC_AUXADC_ADC0, 15, 32, 1, 1), + MTK_PMIC_ADC_CHAN(VGPU_TEMP, PMIC_AUXADC_RQST3, 2, PMIC_AUXADC_ADC0, 15, 32, 1, 1), + + MTK_PMIC_ADC_EXT_CHAN(VIN1, + PMIC_AUXADC_RQST1, 4, PMIC_AUXADC_ADC0, 15, + PMIC_AUXADC_SDMADC_CON0, 1, MT6363_PULLUP_RES_100K, 32, 1, 1), + MTK_PMIC_ADC_EXT_CHAN(VIN2, + PMIC_AUXADC_RQST1, 4, PMIC_AUXADC_ADC0, 15, + PMIC_AUXADC_SDMADC_CON0, 2, MT6363_PULLUP_RES_100K, 32, 1, 1), + MTK_PMIC_ADC_EXT_CHAN(VIN3, + PMIC_AUXADC_RQST1, 4, PMIC_AUXADC_ADC0, 15, + PMIC_AUXADC_SDMADC_CON0, 3, MT6363_PULLUP_RES_100K, 32, 1, 1), + MTK_PMIC_ADC_EXT_CHAN(VIN4, + PMIC_AUXADC_RQST1, 4, PMIC_AUXADC_ADC0, 15, + PMIC_AUXADC_SDMADC_CON0, 4, MT6363_PULLUP_RES_100K, 32, 1, 1), + MTK_PMIC_ADC_EXT_CHAN(VIN5, + PMIC_AUXADC_RQST1, 4, PMIC_AUXADC_ADC0, 15, + PMIC_AUXADC_SDMADC_CON0, 5, MT6363_PULLUP_RES_100K, 32, 1, 1), + MTK_PMIC_ADC_EXT_CHAN(VIN6, + PMIC_AUXADC_RQST1, 4, PMIC_AUXADC_ADC0, 15, + PMIC_AUXADC_SDMADC_CON0, 6, MT6363_PULLUP_RES_100K, 32, 1, 1), + MTK_PMIC_ADC_EXT_CHAN(VIN7, + PMIC_AUXADC_RQST1, 4, PMIC_AUXADC_ADC0, 15, + PMIC_AUXADC_SDMADC_CON0, 7, MT6363_PULLUP_RES_100K, 32, 1, 1), +}; + +static const u16 mt6363_auxadc_regs[] = { + [PMIC_AUXADC_RQST0] = 0x1108, + [PMIC_AUXADC_RQST1] = 0x1109, + [PMIC_AUXADC_RQST3] = 0x110c, + [PMIC_AUXADC_ADC0] = 0x1088, + [PMIC_AUXADC_IMP0] = 0x1208, + [PMIC_AUXADC_IMP1] = 0x1209, +}; + +static const struct iio_chan_spec mt6373_auxadc_channels[] = { + MTK_PMIC_IIO_CHAN(MT6363, chip_temp, CHIP_TEMP, 4, 12, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6363, vcore_temp, VCORE_TEMP, 38, 12, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6363, vproc_temp, VPROC_TEMP, 39, 12, IIO_TEMP), + MTK_PMIC_IIO_CHAN(MT6363, vgpu_temp, VGPU_TEMP, 40, 12, IIO_TEMP), + + /* For VIN, ADC12 holds the result depending on which GPIO was activated */ + MTK_PMIC_IIO_CHAN(MT6363, in1_v, VIN1, 45, 15, IIO_VOLTAGE), + MTK_PMIC_IIO_CHAN(MT6363, in2_v, VIN2, 45, 15, IIO_VOLTAGE), + MTK_PMIC_IIO_CHAN(MT6363, in3_v, VIN3, 45, 15, IIO_VOLTAGE), + MTK_PMIC_IIO_CHAN(MT6363, in4_v, VIN4, 45, 15, IIO_VOLTAGE), + MTK_PMIC_IIO_CHAN(MT6363, in5_v, VIN5, 45, 15, IIO_VOLTAGE), +}; + +static const struct mtk_pmic_auxadc_chan mt6373_auxadc_ch_desc[] = { + MTK_PMIC_ADC_CHAN(CHIP_TEMP, PMIC_AUXADC_RQST0, 4, PMIC_AUXADC_ADC0, 15, 32, 1, 1), + MTK_PMIC_ADC_CHAN(VCORE_TEMP, PMIC_AUXADC_RQST3, 0, PMIC_AUXADC_ADC0, 15, 32, 1, 1), + MTK_PMIC_ADC_CHAN(VPROC_TEMP, PMIC_AUXADC_RQST3, 1, PMIC_AUXADC_ADC0, 15, 32, 1, 1), + MTK_PMIC_ADC_CHAN(VGPU_TEMP, PMIC_AUXADC_RQST3, 2, PMIC_AUXADC_ADC0, 15, 32, 1, 1), + + MTK_PMIC_ADC_EXT_CHAN(VIN1, + PMIC_AUXADC_RQST1, 4, PMIC_AUXADC_ADC0, 15, + PMIC_AUXADC_SDMADC_CON0, 1, MT6363_PULLUP_RES_30K, 32, 1, 1), + MTK_PMIC_ADC_EXT_CHAN(VIN2, + PMIC_AUXADC_RQST1, 4, PMIC_AUXADC_ADC0, 15, + PMIC_AUXADC_SDMADC_CON0, 2, MT6363_PULLUP_RES_OPEN, 32, 1, 1), + MTK_PMIC_ADC_EXT_CHAN(VIN3, + PMIC_AUXADC_RQST1, 4, PMIC_AUXADC_ADC0, 15, + PMIC_AUXADC_SDMADC_CON0, 3, MT6363_PULLUP_RES_OPEN, 32, 1, 1), + MTK_PMIC_ADC_EXT_CHAN(VIN4, + PMIC_AUXADC_RQST1, 4, PMIC_AUXADC_ADC0, 15, + PMIC_AUXADC_SDMADC_CON0, 4, MT6363_PULLUP_RES_OPEN, 32, 1, 1), + MTK_PMIC_ADC_EXT_CHAN(VIN5, + PMIC_AUXADC_RQST1, 4, PMIC_AUXADC_ADC0, 15, + PMIC_AUXADC_SDMADC_CON0, 5, MT6363_PULLUP_RES_OPEN, 32, 1, 1), +}; + +static void mt6358_stop_imp_conv(struct mt6359_auxadc *adc_dev) +{ + const struct mtk_pmic_auxadc_info *cinfo = adc_dev->chip_info; + struct regmap *regmap = adc_dev->regmap; + + regmap_set_bits(regmap, cinfo->regs[PMIC_AUXADC_IMP0], MT6358_IMP0_CLEAR); + regmap_clear_bits(regmap, cinfo->regs[PMIC_AUXADC_IMP0], MT6358_IMP0_CLEAR); + regmap_clear_bits(regmap, cinfo->regs[PMIC_AUXADC_IMP1], MT6358_IMP1_AUTOREPEAT_EN); + regmap_clear_bits(regmap, cinfo->regs[PMIC_AUXADC_DCM_CON], MT6358_DCM_CK_SW_EN); +} + +static int mt6358_start_imp_conv(struct mt6359_auxadc *adc_dev, const struct iio_chan_spec *chan) +{ + const struct mtk_pmic_auxadc_info *cinfo = adc_dev->chip_info; + const struct mtk_pmic_auxadc_chan *desc = &cinfo->desc[chan->scan_index]; + struct regmap *regmap = adc_dev->regmap; + u32 val; + int ret; + + regmap_set_bits(regmap, cinfo->regs[PMIC_AUXADC_DCM_CON], MT6358_DCM_CK_SW_EN); + regmap_set_bits(regmap, cinfo->regs[PMIC_AUXADC_IMP1], MT6358_IMP1_AUTOREPEAT_EN); + + ret = regmap_read_poll_timeout(regmap, cinfo->regs[desc->rdy_idx], + val, val & desc->rdy_mask, + IMP_POLL_DELAY_US, AUXADC_TIMEOUT_US); + if (ret) { + mt6358_stop_imp_conv(adc_dev); + return ret; + } + + return 0; +} + +static int mt6358_read_imp(struct mt6359_auxadc *adc_dev, + const struct iio_chan_spec *chan, int *vbat, int *ibat) +{ + const struct mtk_pmic_auxadc_info *cinfo = adc_dev->chip_info; + struct regmap *regmap = adc_dev->regmap; + u16 reg_adc0 = cinfo->regs[PMIC_AUXADC_ADC0]; + u32 val_v; + int ret; + + ret = mt6358_start_imp_conv(adc_dev, chan); + if (ret) + return ret; + + /* Read the params before stopping */ + val_v = 0; + regmap_read(regmap, reg_adc0 + (cinfo->imp_adc_num << 1), &val_v); + + mt6358_stop_imp_conv(adc_dev); + + if (vbat) + *vbat = val_v; + if (ibat) + *ibat = 0; + + return 0; +} + +static int mt6359_read_imp(struct mt6359_auxadc *adc_dev, + const struct iio_chan_spec *chan, int *vbat, int *ibat) +{ + const struct mtk_pmic_auxadc_info *cinfo = adc_dev->chip_info; + const struct mtk_pmic_auxadc_chan *desc = &cinfo->desc[chan->scan_index]; + struct regmap *regmap = adc_dev->regmap; + u32 val, val_v, val_i; + int ret; + + /* Start conversion */ + regmap_write(regmap, cinfo->regs[PMIC_AUXADC_IMP0], MT6359_IMP0_CONV_EN); + ret = regmap_read_poll_timeout(regmap, cinfo->regs[desc->rdy_idx], + val, val & desc->rdy_mask, + IMP_POLL_DELAY_US, AUXADC_TIMEOUT_US); + + /* Stop conversion regardless of the result */ + regmap_write(regmap, cinfo->regs[PMIC_AUXADC_IMP0], 0); + if (ret) + return ret; + + /* If it succeeded, wait for the registers to be populated */ + fsleep(IMP_STOP_DELAY_US); + + ret = regmap_read(regmap, cinfo->regs[PMIC_AUXADC_IMP3], &val_v); + if (ret) + return ret; + + ret = regmap_read(regmap, cinfo->regs[PMIC_FGADC_R_CON0], &val_i); + if (ret) + return ret; + + if (vbat) + *vbat = val_v; + if (ibat) + *ibat = val_i; + + return 0; +} + +static const struct mtk_pmic_auxadc_info mt6357_chip_info = { + .model_name = "MT6357", + .channels = mt6357_auxadc_channels, + .num_channels = ARRAY_SIZE(mt6357_auxadc_channels), + .desc = mt6357_auxadc_ch_desc, + .regs = mt6357_auxadc_regs, + .imp_adc_num = MT6357_IMP_ADC_NUM, + .read_imp = mt6358_read_imp, + .vref_mV = 1800, +}; + +static const struct mtk_pmic_auxadc_info mt6358_chip_info = { + .model_name = "MT6358", + .channels = mt6358_auxadc_channels, + .num_channels = ARRAY_SIZE(mt6358_auxadc_channels), + .desc = mt6358_auxadc_ch_desc, + .regs = mt6358_auxadc_regs, + .imp_adc_num = MT6358_IMP_ADC_NUM, + .read_imp = mt6358_read_imp, + .vref_mV = 1800, +}; + +static const struct mtk_pmic_auxadc_info mt6359_chip_info = { + .model_name = "MT6359", + .channels = mt6359_auxadc_channels, + .num_channels = ARRAY_SIZE(mt6359_auxadc_channels), + .desc = mt6359_auxadc_ch_desc, + .regs = mt6359_auxadc_regs, + .sec_unlock_key = 0x6359, + .read_imp = mt6359_read_imp, + .vref_mV = 1800, +}; + +static const struct mtk_pmic_auxadc_info mt6363_chip_info = { + .model_name = "MT6363", + .channels = mt6363_auxadc_channels, + .num_channels = ARRAY_SIZE(mt6363_auxadc_channels), + .desc = mt6363_auxadc_ch_desc, + .regs = mt6363_auxadc_regs, + .is_spmi = true, + .no_reset = true, + .vref_mV = 1840, +}; + +static const struct mtk_pmic_auxadc_info mt6373_chip_info = { + .model_name = "MT6373", + .channels = mt6373_auxadc_channels, + .num_channels = ARRAY_SIZE(mt6373_auxadc_channels), + .desc = mt6373_auxadc_ch_desc, + .regs = mt6363_auxadc_regs, + .is_spmi = true, + .no_reset = true, + .vref_mV = 1840, +}; + +static void mt6359_auxadc_reset(struct mt6359_auxadc *adc_dev) +{ + const struct mtk_pmic_auxadc_info *cinfo = adc_dev->chip_info; + struct regmap *regmap = adc_dev->regmap; + + /* Some PMICs do not support reset */ + if (cinfo->no_reset) + return; + + /* Unlock HK_TOP writes */ + if (cinfo->sec_unlock_key) + regmap_write(regmap, cinfo->regs[PMIC_HK_TOP_WKEY], cinfo->sec_unlock_key); + + /* Assert ADC reset */ + regmap_set_bits(regmap, cinfo->regs[PMIC_HK_TOP_RST_CON0], PMIC_RG_RESET_VAL); + + /* De-assert ADC reset. No wait required, as pwrap takes care of that for us. */ + regmap_clear_bits(regmap, cinfo->regs[PMIC_HK_TOP_RST_CON0], PMIC_RG_RESET_VAL); + + /* Lock HK_TOP writes again */ + if (cinfo->sec_unlock_key) + regmap_write(regmap, cinfo->regs[PMIC_HK_TOP_WKEY], 0); +} + +/** + * mt6359_auxadc_sample_adc_val() - Start ADC channel sampling and read value + * @adc_dev: Main driver structure + * @chan: IIO Channel spec for requested ADC + * @out: Preallocated variable to store the value read from HW + * + * This function starts the sampling for an ADC channel, waits until all + * of the samples are averaged and then reads the value from the HW. + * + * Note that the caller must stop the ADC sampling on its own, as this + * function *never* stops it. + * + * Return: + * Negative number for error; + * Upon success returns zero and writes the read value to *out. + */ +static int mt6359_auxadc_sample_adc_val(struct mt6359_auxadc *adc_dev, + const struct iio_chan_spec *chan, u32 *out) +{ + const struct mtk_pmic_auxadc_info *cinfo = adc_dev->chip_info; + const struct mtk_pmic_auxadc_chan *desc = &cinfo->desc[chan->scan_index]; + struct regmap *regmap = adc_dev->regmap; + u32 reg, rdy_mask, val, lval; + int ret; + + /* Request to start sampling for ADC channel */ + ret = regmap_write(regmap, cinfo->regs[desc->req_idx], desc->req_mask); + if (ret) + return ret; + + /* Wait until all samples are averaged */ + fsleep(desc->num_samples * AUXADC_AVG_TIME_US); + + reg = cinfo->regs[PMIC_AUXADC_ADC0] + (chan->address << 1); + rdy_mask = PMIC_AUXADC_RDY_BIT; + + /* + * Even though for both PWRAP and SPMI cases the ADC HW signals that + * the data is ready by setting AUXADC_RDY_BIT, for SPMI the register + * read is only 8 bits long: for this case, the check has to be done + * on the ADC(x)_H register (high bits) and the rdy_mask needs to be + * shifted to the right by the same 8 bits. + */ + if (cinfo->is_spmi) { + rdy_mask >>= 8; + reg += 1; + } + + ret = regmap_read_poll_timeout(regmap, reg, val, val & rdy_mask, + AUXADC_POLL_DELAY_US, AUXADC_TIMEOUT_US); + if (ret) { + dev_dbg(adc_dev->dev, "ADC read timeout for chan %lu\n", chan->address); + return ret; + } + + if (cinfo->is_spmi) { + ret = regmap_read(regmap, reg - 1, &lval); + if (ret) + return ret; + + val = (val << 8) | lval; + } + + *out = val; + return 0; +} + +static int mt6359_auxadc_read_adc(struct mt6359_auxadc *adc_dev, + const struct iio_chan_spec *chan, int *out) +{ + const struct mtk_pmic_auxadc_info *cinfo = adc_dev->chip_info; + const struct mtk_pmic_auxadc_chan *desc = &cinfo->desc[chan->scan_index]; + struct regmap *regmap = adc_dev->regmap; + int ret, adc_stop_err; + u8 ext_sel; + u32 val; + + if (desc->ext_sel_idx >= 0) { + ext_sel = FIELD_PREP(MT6363_EXT_PURES_MASK, desc->ext_sel_pu); + ext_sel |= FIELD_PREP(MT6363_EXT_CHAN_MASK, desc->ext_sel_ch); + + ret = regmap_update_bits(regmap, cinfo->regs[desc->ext_sel_idx], + MT6363_EXT_PURES_MASK | MT6363_EXT_CHAN_MASK, + ext_sel); + if (ret) + return ret; + } + + /* + * Get sampled value, then stop sampling unconditionally; the gathered + * value is good regardless of if the ADC could be stopped. + * + * Note that if the ADC cannot be stopped but sampling was ok, this + * function will not return any error, but will set the timed_out + * status: this is not critical, as the ADC may auto recover and auto + * stop after some time (depending on the PMIC model); if not, the next + * read attempt will return -ETIMEDOUT and, for models that support it, + * reset will be triggered. + */ + ret = mt6359_auxadc_sample_adc_val(adc_dev, chan, &val); + + adc_stop_err = regmap_write(regmap, cinfo->regs[desc->req_idx], 0); + if (adc_stop_err) { + dev_warn(adc_dev->dev, "Could not stop the ADC: %d\n,", adc_stop_err); + adc_dev->timed_out = true; + } + + /* If any sampling error occurred, the retrieved value is invalid */ + if (ret) + return ret; + + /* ...and deactivate the ADC GPIO if previously done */ + if (desc->ext_sel_idx >= 0) { + ext_sel = FIELD_PREP(MT6363_EXT_PURES_MASK, MT6363_PULLUP_RES_OPEN); + + ret = regmap_update_bits(regmap, cinfo->regs[desc->ext_sel_idx], + MT6363_EXT_PURES_MASK, ext_sel); + if (ret) + return ret; + } + + /* Everything went fine, give back the ADC reading */ + *out = val & GENMASK(chan->scan_type.realbits - 1, 0); + return 0; +} + +static int mt6359_auxadc_read_label(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, char *label) +{ + return sysfs_emit(label, "%s\n", chan->datasheet_name); +} + +static int mt6359_auxadc_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct mt6359_auxadc *adc_dev = iio_priv(indio_dev); + const struct mtk_pmic_auxadc_info *cinfo = adc_dev->chip_info; + const struct mtk_pmic_auxadc_chan *desc = &cinfo->desc[chan->scan_index]; + int ret; + + if (mask == IIO_CHAN_INFO_SCALE) { + *val = desc->r_ratio.numerator * cinfo->vref_mV; + + if (desc->r_ratio.denominator > 1) { + *val2 = desc->r_ratio.denominator; + return IIO_VAL_FRACTIONAL; + } + + return IIO_VAL_INT; + } + + scoped_guard(mutex, &adc_dev->lock) { + switch (chan->scan_index) { + case PMIC_AUXADC_CHAN_IBAT: + if (!adc_dev->chip_info->read_imp) + return -EOPNOTSUPP; + + ret = adc_dev->chip_info->read_imp(adc_dev, chan, NULL, val); + break; + case PMIC_AUXADC_CHAN_VBAT: + if (!adc_dev->chip_info->read_imp) + return -EOPNOTSUPP; + + ret = adc_dev->chip_info->read_imp(adc_dev, chan, val, NULL); + break; + default: + ret = mt6359_auxadc_read_adc(adc_dev, chan, val); + break; + } + } + + if (ret) { + /* + * If we get more than one timeout, it's possible that the + * AUXADC is stuck: perform a full reset to recover it. + */ + if (ret == -ETIMEDOUT) { + if (adc_dev->timed_out) { + dev_warn(adc_dev->dev, "Resetting stuck ADC!\r\n"); + mt6359_auxadc_reset(adc_dev); + } + adc_dev->timed_out = true; + } + return ret; + } + adc_dev->timed_out = false; + + return IIO_VAL_INT; +} + +static const struct iio_info mt6359_auxadc_iio_info = { + .read_label = mt6359_auxadc_read_label, + .read_raw = mt6359_auxadc_read_raw, +}; + +static int mt6359_auxadc_probe(struct platform_device *pdev) +{ + const struct mtk_pmic_auxadc_info *chip_info; + struct device *dev = &pdev->dev; + struct device *mfd_dev = dev->parent; + struct mt6359_auxadc *adc_dev; + struct iio_dev *indio_dev; + struct device *regmap_dev; + struct regmap *regmap; + int ret; + + chip_info = device_get_match_data(dev); + if (!chip_info) + return -EINVAL; + /* + * The regmap for this device has to be acquired differently for + * SoC PMIC Wrapper and SPMI PMIC cases: + * + * If this is under SPMI, the regmap comes from the direct parent of + * this driver: this_device->parent(mfd). + * ... or ... + * If this is under the SoC PMIC Wrapper, the regmap comes from the + * parent of the MT6397 MFD: this_device->parent(mfd)->parent(pwrap) + */ + if (chip_info->is_spmi) + regmap_dev = mfd_dev; + else + regmap_dev = mfd_dev->parent; + + + /* Regmap is from SoC PMIC Wrapper, parent of the mt6397 MFD */ + regmap = dev_get_regmap(regmap_dev, NULL); + if (!regmap) + return dev_err_probe(dev, -ENODEV, "Failed to get regmap\n"); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*adc_dev)); + if (!indio_dev) + return -ENOMEM; + + adc_dev = iio_priv(indio_dev); + adc_dev->regmap = regmap; + adc_dev->dev = dev; + adc_dev->chip_info = chip_info; + + mutex_init(&adc_dev->lock); + + mt6359_auxadc_reset(adc_dev); + + indio_dev->name = adc_dev->chip_info->model_name; + indio_dev->info = &mt6359_auxadc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = adc_dev->chip_info->channels; + indio_dev->num_channels = adc_dev->chip_info->num_channels; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "failed to register iio device\n"); + + return 0; +} + +static const struct of_device_id mt6359_auxadc_of_match[] = { + { .compatible = "mediatek,mt6357-auxadc", .data = &mt6357_chip_info }, + { .compatible = "mediatek,mt6358-auxadc", .data = &mt6358_chip_info }, + { .compatible = "mediatek,mt6359-auxadc", .data = &mt6359_chip_info }, + { .compatible = "mediatek,mt6363-auxadc", .data = &mt6363_chip_info }, + { .compatible = "mediatek,mt6373-auxadc", .data = &mt6373_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, mt6359_auxadc_of_match); + +static struct platform_driver mt6359_auxadc_driver = { + .driver = { + .name = "mt6359-auxadc", + .of_match_table = mt6359_auxadc_of_match, + }, + .probe = mt6359_auxadc_probe, +}; +module_platform_driver(mt6359_auxadc_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("AngeloGioacchino Del Regno "); +MODULE_DESCRIPTION("MediaTek MT6359 PMIC AUXADC Driver"); diff --git a/drivers/iio/adc/mt6360-adc.c b/drivers/iio/adc/mt6360-adc.c new file mode 100644 index 000000000..e59b13e88 --- /dev/null +++ b/drivers/iio/adc/mt6360-adc.c @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define MT6360_REG_PMUCHGCTRL3 0x313 +#define MT6360_REG_PMUADCCFG 0x356 +#define MT6360_REG_PMUADCIDLET 0x358 +#define MT6360_REG_PMUADCRPT1 0x35A + +/* PMUCHGCTRL3 0x313 */ +#define MT6360_AICR_MASK GENMASK(7, 2) +#define MT6360_AICR_SHFT 2 +#define MT6360_AICR_400MA 0x6 +/* PMUADCCFG 0x356 */ +#define MT6360_ADCEN_MASK BIT(15) +/* PMUADCRPT1 0x35A */ +#define MT6360_PREFERCH_MASK GENMASK(7, 4) +#define MT6360_PREFERCH_SHFT 4 +#define MT6360_RPTCH_MASK GENMASK(3, 0) +#define MT6360_NO_PREFER 15 + +/* Time in ms */ +#define ADC_WAIT_TIME_MS 25 +#define ADC_CONV_TIMEOUT_MS 100 +#define ADC_LOOP_TIME_US 2000 + +enum { + MT6360_CHAN_USBID = 0, + MT6360_CHAN_VBUSDIV5, + MT6360_CHAN_VBUSDIV2, + MT6360_CHAN_VSYS, + MT6360_CHAN_VBAT, + MT6360_CHAN_IBUS, + MT6360_CHAN_IBAT, + MT6360_CHAN_CHG_VDDP, + MT6360_CHAN_TEMP_JC, + MT6360_CHAN_VREF_TS, + MT6360_CHAN_TS, + MT6360_CHAN_MAX +}; + +struct mt6360_adc_data { + struct device *dev; + struct regmap *regmap; + /* Due to only one set of ADC control, this lock is used to prevent the race condition */ + struct mutex adc_lock; + ktime_t last_off_timestamps[MT6360_CHAN_MAX]; +}; + +static int mt6360_adc_read_channel(struct mt6360_adc_data *mad, int channel, int *val) +{ + __be16 adc_enable; + u8 rpt[3]; + ktime_t predict_end_t, timeout; + unsigned int pre_wait_time; + int ret; + + mutex_lock(&mad->adc_lock); + + /* Select the preferred ADC channel */ + ret = regmap_update_bits(mad->regmap, MT6360_REG_PMUADCRPT1, MT6360_PREFERCH_MASK, + channel << MT6360_PREFERCH_SHFT); + if (ret) + goto out_adc_lock; + + adc_enable = cpu_to_be16(MT6360_ADCEN_MASK | BIT(channel)); + ret = regmap_raw_write(mad->regmap, MT6360_REG_PMUADCCFG, &adc_enable, sizeof(adc_enable)); + if (ret) + goto out_adc_lock; + + predict_end_t = ktime_add_ms(mad->last_off_timestamps[channel], 2 * ADC_WAIT_TIME_MS); + + if (ktime_after(ktime_get(), predict_end_t)) + pre_wait_time = ADC_WAIT_TIME_MS; + else + pre_wait_time = 3 * ADC_WAIT_TIME_MS; + + if (msleep_interruptible(pre_wait_time)) { + ret = -ERESTARTSYS; + goto out_adc_conv; + } + + timeout = ktime_add_ms(ktime_get(), ADC_CONV_TIMEOUT_MS); + while (true) { + ret = regmap_raw_read(mad->regmap, MT6360_REG_PMUADCRPT1, rpt, sizeof(rpt)); + if (ret) + goto out_adc_conv; + + /* + * There are two functions, ZCV and TypeC OTP, running ADC VBAT and TS in + * background, and ADC samples are taken on a fixed frequency no matter read the + * previous one or not. + * To avoid conflict, We set minimum time threshold after enable ADC and + * check report channel is the same. + * The worst case is run the same ADC twice and background function is also running, + * ADC conversion sequence is desire channel before start ADC, background ADC, + * desire channel after start ADC. + * So the minimum correct data is three times of typical conversion time. + */ + if ((rpt[0] & MT6360_RPTCH_MASK) == channel) + break; + + if (ktime_compare(ktime_get(), timeout) > 0) { + ret = -ETIMEDOUT; + goto out_adc_conv; + } + + usleep_range(ADC_LOOP_TIME_US / 2, ADC_LOOP_TIME_US); + } + + *val = get_unaligned_be16(&rpt[1]); + ret = IIO_VAL_INT; + +out_adc_conv: + /* Only keep ADC enable */ + adc_enable = cpu_to_be16(MT6360_ADCEN_MASK); + regmap_raw_write(mad->regmap, MT6360_REG_PMUADCCFG, &adc_enable, sizeof(adc_enable)); + mad->last_off_timestamps[channel] = ktime_get(); + /* Config prefer channel to NO_PREFER */ + regmap_update_bits(mad->regmap, MT6360_REG_PMUADCRPT1, MT6360_PREFERCH_MASK, + MT6360_NO_PREFER << MT6360_PREFERCH_SHFT); +out_adc_lock: + mutex_unlock(&mad->adc_lock); + + return ret; +} + +static int mt6360_adc_read_scale(struct mt6360_adc_data *mad, int channel, int *val, int *val2) +{ + unsigned int regval; + int ret; + + switch (channel) { + case MT6360_CHAN_USBID: + case MT6360_CHAN_VSYS: + case MT6360_CHAN_VBAT: + case MT6360_CHAN_CHG_VDDP: + case MT6360_CHAN_VREF_TS: + case MT6360_CHAN_TS: + *val = 1250; + return IIO_VAL_INT; + case MT6360_CHAN_VBUSDIV5: + *val = 6250; + return IIO_VAL_INT; + case MT6360_CHAN_VBUSDIV2: + case MT6360_CHAN_IBUS: + case MT6360_CHAN_IBAT: + *val = 2500; + + if (channel == MT6360_CHAN_IBUS) { + /* IBUS will be affected by input current limit for the different Ron */ + /* Check whether the config is <400mA or not */ + ret = regmap_read(mad->regmap, MT6360_REG_PMUCHGCTRL3, ®val); + if (ret) + return ret; + + regval = (regval & MT6360_AICR_MASK) >> MT6360_AICR_SHFT; + if (regval < MT6360_AICR_400MA) + *val = 1900; + } + + return IIO_VAL_INT; + case MT6360_CHAN_TEMP_JC: + *val = 105; + *val2 = 100; + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; +} + +static int mt6360_adc_read_offset(struct mt6360_adc_data *mad, int channel, int *val) +{ + *val = (channel == MT6360_CHAN_TEMP_JC) ? -80 : 0; + return IIO_VAL_INT; +} + +static int mt6360_adc_read_raw(struct iio_dev *iio_dev, const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct mt6360_adc_data *mad = iio_priv(iio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return mt6360_adc_read_channel(mad, chan->channel, val); + case IIO_CHAN_INFO_SCALE: + return mt6360_adc_read_scale(mad, chan->channel, val, val2); + case IIO_CHAN_INFO_OFFSET: + return mt6360_adc_read_offset(mad, chan->channel, val); + } + + return -EINVAL; +} + +static const char *mt6360_channel_labels[MT6360_CHAN_MAX] = { + "usbid", "vbusdiv5", "vbusdiv2", "vsys", "vbat", "ibus", "ibat", "chg_vddp", + "temp_jc", "vref_ts", "ts", +}; + +static int mt6360_adc_read_label(struct iio_dev *iio_dev, const struct iio_chan_spec *chan, + char *label) +{ + return sysfs_emit(label, "%s\n", mt6360_channel_labels[chan->channel]); +} + +static const struct iio_info mt6360_adc_iio_info = { + .read_raw = mt6360_adc_read_raw, + .read_label = mt6360_adc_read_label, +}; + +#define MT6360_ADC_CHAN(_idx, _type) { \ + .type = _type, \ + .channel = MT6360_CHAN_##_idx, \ + .scan_index = MT6360_CHAN_##_idx, \ + .datasheet_name = #_idx, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ +} + +static const struct iio_chan_spec mt6360_adc_channels[] = { + MT6360_ADC_CHAN(USBID, IIO_VOLTAGE), + MT6360_ADC_CHAN(VBUSDIV5, IIO_VOLTAGE), + MT6360_ADC_CHAN(VBUSDIV2, IIO_VOLTAGE), + MT6360_ADC_CHAN(VSYS, IIO_VOLTAGE), + MT6360_ADC_CHAN(VBAT, IIO_VOLTAGE), + MT6360_ADC_CHAN(IBUS, IIO_CURRENT), + MT6360_ADC_CHAN(IBAT, IIO_CURRENT), + MT6360_ADC_CHAN(CHG_VDDP, IIO_VOLTAGE), + MT6360_ADC_CHAN(TEMP_JC, IIO_TEMP), + MT6360_ADC_CHAN(VREF_TS, IIO_VOLTAGE), + MT6360_ADC_CHAN(TS, IIO_VOLTAGE), + IIO_CHAN_SOFT_TIMESTAMP(MT6360_CHAN_MAX), +}; + +static irqreturn_t mt6360_adc_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct mt6360_adc_data *mad = iio_priv(indio_dev); + struct { + u16 values[MT6360_CHAN_MAX]; + aligned_s64 timestamp; + } data = { }; + int i = 0, bit, val, ret; + + iio_for_each_active_channel(indio_dev, bit) { + ret = mt6360_adc_read_channel(mad, bit, &val); + if (ret < 0) { + dev_warn(&indio_dev->dev, "Failed to get channel %d conversion val\n", bit); + goto out; + } + + data.values[i++] = val; + } + iio_push_to_buffers_with_timestamp(indio_dev, &data, iio_get_time_ns(indio_dev)); +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static inline int mt6360_adc_reset(struct mt6360_adc_data *info) +{ + __be16 adc_enable; + ktime_t all_off_time; + int i, ret; + + /* Clear ADC idle wait time to 0 */ + ret = regmap_write(info->regmap, MT6360_REG_PMUADCIDLET, 0); + if (ret) + return ret; + + /* Only keep ADC enable, but keep all channels off */ + adc_enable = cpu_to_be16(MT6360_ADCEN_MASK); + ret = regmap_raw_write(info->regmap, MT6360_REG_PMUADCCFG, &adc_enable, sizeof(adc_enable)); + if (ret) + return ret; + + /* Reset all channel off time to the current one */ + all_off_time = ktime_get(); + for (i = 0; i < MT6360_CHAN_MAX; i++) + info->last_off_timestamps[i] = all_off_time; + + return 0; +} + +static int mt6360_adc_probe(struct platform_device *pdev) +{ + struct mt6360_adc_data *mad; + struct regmap *regmap; + struct iio_dev *indio_dev; + int ret; + + regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!regmap) { + dev_err(&pdev->dev, "Failed to get parent regmap\n"); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*mad)); + if (!indio_dev) + return -ENOMEM; + + mad = iio_priv(indio_dev); + mad->dev = &pdev->dev; + mad->regmap = regmap; + mutex_init(&mad->adc_lock); + + ret = mt6360_adc_reset(mad); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to reset adc\n"); + return ret; + } + + indio_dev->name = dev_name(&pdev->dev); + indio_dev->info = &mt6360_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = mt6360_adc_channels; + indio_dev->num_channels = ARRAY_SIZE(mt6360_adc_channels); + + ret = devm_iio_triggered_buffer_setup(&pdev->dev, indio_dev, NULL, + mt6360_adc_trigger_handler, NULL); + if (ret) { + dev_err(&pdev->dev, "Failed to allocate iio trigger buffer\n"); + return ret; + } + + return devm_iio_device_register(&pdev->dev, indio_dev); +} + +static const struct of_device_id mt6360_adc_of_id[] = { + { .compatible = "mediatek,mt6360-adc", }, + { } +}; +MODULE_DEVICE_TABLE(of, mt6360_adc_of_id); + +static struct platform_driver mt6360_adc_driver = { + .driver = { + .name = "mt6360-adc", + .of_match_table = mt6360_adc_of_id, + }, + .probe = mt6360_adc_probe, +}; +module_platform_driver(mt6360_adc_driver); + +MODULE_AUTHOR("Gene Chen "); +MODULE_DESCRIPTION("MT6360 ADC Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/mt6370-adc.c b/drivers/iio/adc/mt6370-adc.c new file mode 100644 index 000000000..c250385f9 --- /dev/null +++ b/drivers/iio/adc/mt6370-adc.c @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 Richtek Technology Corp. + * + * Author: ChiaEn Wu + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define MT6370_REG_DEV_INFO 0x100 +#define MT6370_REG_CHG_CTRL3 0x113 +#define MT6370_REG_CHG_CTRL7 0x117 +#define MT6370_REG_CHG_ADC 0x121 +#define MT6370_REG_ADC_DATA_H 0x14C + +#define MT6370_ADC_START_MASK BIT(0) +#define MT6370_ADC_IN_SEL_MASK GENMASK(7, 4) +#define MT6370_AICR_ICHG_MASK GENMASK(7, 2) +#define MT6370_VENID_MASK GENMASK(7, 4) + +#define MT6370_AICR_100_mA 0x0 +#define MT6370_AICR_150_mA 0x1 +#define MT6370_AICR_200_mA 0x2 +#define MT6370_AICR_250_mA 0x3 +#define MT6370_AICR_300_mA 0x4 +#define MT6370_AICR_350_mA 0x5 + +#define MT6370_ICHG_100_mA 0x0 +#define MT6370_ICHG_200_mA 0x1 +#define MT6370_ICHG_300_mA 0x2 +#define MT6370_ICHG_400_mA 0x3 +#define MT6370_ICHG_500_mA 0x4 +#define MT6370_ICHG_600_mA 0x5 +#define MT6370_ICHG_700_mA 0x6 +#define MT6370_ICHG_800_mA 0x7 + +#define ADC_CONV_TIME_MS 35 +#define ADC_CONV_POLLING_TIME_US 1000 + +#define MT6370_VID_RT5081 0x8 +#define MT6370_VID_RT5081A 0xA +#define MT6370_VID_MT6370 0xE + +struct mt6370_adc_data { + struct device *dev; + struct regmap *regmap; + /* + * This mutex lock is for preventing the different ADC channels + * from being read at the same time. + */ + struct mutex adc_lock; + unsigned int vid; +}; + +static int mt6370_adc_read_channel(struct mt6370_adc_data *priv, int chan, + unsigned long addr, int *val) +{ + unsigned int reg_val; + __be16 be_val; + int ret; + + mutex_lock(&priv->adc_lock); + + reg_val = MT6370_ADC_START_MASK | + FIELD_PREP(MT6370_ADC_IN_SEL_MASK, addr); + ret = regmap_write(priv->regmap, MT6370_REG_CHG_ADC, reg_val); + if (ret) + goto adc_unlock; + + msleep(ADC_CONV_TIME_MS); + + ret = regmap_read_poll_timeout(priv->regmap, + MT6370_REG_CHG_ADC, reg_val, + !(reg_val & MT6370_ADC_START_MASK), + ADC_CONV_POLLING_TIME_US, + ADC_CONV_TIME_MS * MILLI * 3); + if (ret) { + dev_err(priv->dev, "Failed to read ADC register (%d)\n", ret); + goto adc_unlock; + } + + ret = regmap_raw_read(priv->regmap, MT6370_REG_ADC_DATA_H, + &be_val, sizeof(be_val)); + if (ret) + goto adc_unlock; + + *val = be16_to_cpu(be_val); + ret = IIO_VAL_INT; + +adc_unlock: + mutex_unlock(&priv->adc_lock); + + return ret; +} + +static int mt6370_adc_get_ibus_scale(struct mt6370_adc_data *priv) +{ + switch (priv->vid) { + case MT6370_VID_RT5081: + case MT6370_VID_RT5081A: + case MT6370_VID_MT6370: + return 3350; + default: + return 3875; + } +} + +static int mt6370_adc_get_ibat_scale(struct mt6370_adc_data *priv) +{ + switch (priv->vid) { + case MT6370_VID_RT5081: + case MT6370_VID_RT5081A: + case MT6370_VID_MT6370: + return 2680; + default: + return 3870; + } +} + +static int mt6370_adc_read_scale(struct mt6370_adc_data *priv, + int chan, int *val1, int *val2) +{ + unsigned int reg_val; + int ret; + + switch (chan) { + case MT6370_CHAN_VBAT: + case MT6370_CHAN_VSYS: + case MT6370_CHAN_CHG_VDDP: + *val1 = 5; + return IIO_VAL_INT; + case MT6370_CHAN_IBUS: + ret = regmap_read(priv->regmap, MT6370_REG_CHG_CTRL3, ®_val); + if (ret) + return ret; + + reg_val = FIELD_GET(MT6370_AICR_ICHG_MASK, reg_val); + switch (reg_val) { + case MT6370_AICR_100_mA: + case MT6370_AICR_150_mA: + case MT6370_AICR_200_mA: + case MT6370_AICR_250_mA: + case MT6370_AICR_300_mA: + case MT6370_AICR_350_mA: + *val1 = mt6370_adc_get_ibus_scale(priv); + break; + default: + *val1 = 5000; + break; + } + + *val2 = 100; + + return IIO_VAL_FRACTIONAL; + case MT6370_CHAN_IBAT: + ret = regmap_read(priv->regmap, MT6370_REG_CHG_CTRL7, ®_val); + if (ret) + return ret; + + reg_val = FIELD_GET(MT6370_AICR_ICHG_MASK, reg_val); + switch (reg_val) { + case MT6370_ICHG_100_mA: + case MT6370_ICHG_200_mA: + case MT6370_ICHG_300_mA: + case MT6370_ICHG_400_mA: + *val1 = 2375; + break; + case MT6370_ICHG_500_mA: + case MT6370_ICHG_600_mA: + case MT6370_ICHG_700_mA: + case MT6370_ICHG_800_mA: + *val1 = mt6370_adc_get_ibat_scale(priv); + break; + default: + *val1 = 5000; + break; + } + + *val2 = 100; + + return IIO_VAL_FRACTIONAL; + case MT6370_CHAN_VBUSDIV5: + *val1 = 25; + return IIO_VAL_INT; + case MT6370_CHAN_VBUSDIV2: + *val1 = 10; + return IIO_VAL_INT; + case MT6370_CHAN_TS_BAT: + *val1 = 25; + *val2 = 10000; + return IIO_VAL_FRACTIONAL; + case MT6370_CHAN_TEMP_JC: + *val1 = 2000; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int mt6370_adc_read_offset(struct mt6370_adc_data *priv, + int chan, int *val) +{ + *val = -20; + + return IIO_VAL_INT; +} + +static int mt6370_adc_read_raw(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct mt6370_adc_data *priv = iio_priv(iio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return mt6370_adc_read_channel(priv, chan->channel, + chan->address, val); + case IIO_CHAN_INFO_SCALE: + return mt6370_adc_read_scale(priv, chan->channel, val, val2); + case IIO_CHAN_INFO_OFFSET: + return mt6370_adc_read_offset(priv, chan->channel, val); + default: + return -EINVAL; + } +} + +static const char * const mt6370_channel_labels[MT6370_CHAN_MAX] = { + [MT6370_CHAN_VBUSDIV5] = "vbusdiv5", + [MT6370_CHAN_VBUSDIV2] = "vbusdiv2", + [MT6370_CHAN_VSYS] = "vsys", + [MT6370_CHAN_VBAT] = "vbat", + [MT6370_CHAN_TS_BAT] = "ts_bat", + [MT6370_CHAN_IBUS] = "ibus", + [MT6370_CHAN_IBAT] = "ibat", + [MT6370_CHAN_CHG_VDDP] = "chg_vddp", + [MT6370_CHAN_TEMP_JC] = "temp_jc", +}; + +static int mt6370_adc_read_label(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, char *label) +{ + return sysfs_emit(label, "%s\n", mt6370_channel_labels[chan->channel]); +} + +static const struct iio_info mt6370_adc_iio_info = { + .read_raw = mt6370_adc_read_raw, + .read_label = mt6370_adc_read_label, +}; + +#define MT6370_ADC_CHAN(_idx, _type, _addr, _extra_info) { \ + .type = _type, \ + .channel = MT6370_CHAN_##_idx, \ + .address = _addr, \ + .scan_index = MT6370_CHAN_##_idx, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + _extra_info, \ +} + +static const struct iio_chan_spec mt6370_adc_channels[] = { + MT6370_ADC_CHAN(VBUSDIV5, IIO_VOLTAGE, 1, 0), + MT6370_ADC_CHAN(VBUSDIV2, IIO_VOLTAGE, 2, 0), + MT6370_ADC_CHAN(VSYS, IIO_VOLTAGE, 3, 0), + MT6370_ADC_CHAN(VBAT, IIO_VOLTAGE, 4, 0), + MT6370_ADC_CHAN(TS_BAT, IIO_VOLTAGE, 6, 0), + MT6370_ADC_CHAN(IBUS, IIO_CURRENT, 8, 0), + MT6370_ADC_CHAN(IBAT, IIO_CURRENT, 9, 0), + MT6370_ADC_CHAN(CHG_VDDP, IIO_VOLTAGE, 11, 0), + MT6370_ADC_CHAN(TEMP_JC, IIO_TEMP, 12, BIT(IIO_CHAN_INFO_OFFSET)), +}; + +static int mt6370_get_vendor_info(struct mt6370_adc_data *priv) +{ + unsigned int dev_info; + int ret; + + ret = regmap_read(priv->regmap, MT6370_REG_DEV_INFO, &dev_info); + if (ret) + return ret; + + priv->vid = FIELD_GET(MT6370_VENID_MASK, dev_info); + + return 0; +} + +static int mt6370_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mt6370_adc_data *priv; + struct iio_dev *indio_dev; + struct regmap *regmap; + int ret; + + regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!regmap) + return dev_err_probe(dev, -ENODEV, "Failed to get regmap\n"); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + priv = iio_priv(indio_dev); + priv->dev = dev; + priv->regmap = regmap; + mutex_init(&priv->adc_lock); + + ret = mt6370_get_vendor_info(priv); + if (ret) + return dev_err_probe(dev, ret, "Failed to get vid\n"); + + ret = regmap_write(priv->regmap, MT6370_REG_CHG_ADC, 0); + if (ret) + return dev_err_probe(dev, ret, "Failed to reset ADC\n"); + + indio_dev->name = "mt6370-adc"; + indio_dev->info = &mt6370_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = mt6370_adc_channels; + indio_dev->num_channels = ARRAY_SIZE(mt6370_adc_channels); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id mt6370_adc_of_id[] = { + { .compatible = "mediatek,mt6370-adc", }, + { } +}; +MODULE_DEVICE_TABLE(of, mt6370_adc_of_id); + +static struct platform_driver mt6370_adc_driver = { + .driver = { + .name = "mt6370-adc", + .of_match_table = mt6370_adc_of_id, + }, + .probe = mt6370_adc_probe, +}; +module_platform_driver(mt6370_adc_driver); + +MODULE_AUTHOR("ChiaEn Wu "); +MODULE_DESCRIPTION("MT6370 ADC Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/mt6577_auxadc.c b/drivers/iio/adc/mt6577_auxadc.c new file mode 100644 index 000000000..ecbae90ac --- /dev/null +++ b/drivers/iio/adc/mt6577_auxadc.c @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: Zhiyong Tao + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register definitions */ +#define MT6577_AUXADC_CON0 0x00 +#define MT6577_AUXADC_CON1 0x04 +#define MT6577_AUXADC_CON2 0x10 +#define MT6577_AUXADC_STA BIT(0) + +#define MT6577_AUXADC_DAT0 0x14 +#define MT6577_AUXADC_RDY0 BIT(12) + +#define MT6577_AUXADC_MISC 0x94 +#define MT6577_AUXADC_PDN_EN BIT(14) + +#define MT6577_AUXADC_DAT_MASK 0xfff +#define MT6577_AUXADC_SLEEP_US 1000 +#define MT6577_AUXADC_TIMEOUT_US 10000 +#define MT6577_AUXADC_POWER_READY_MS 1 +#define MT6577_AUXADC_SAMPLE_READY_US 25 + +struct mtk_auxadc_compatible { + bool sample_data_cali; + bool check_global_idle; +}; + +struct mt6577_auxadc_device { + void __iomem *reg_base; + struct clk *adc_clk; + struct mutex lock; + const struct mtk_auxadc_compatible *dev_comp; +}; + +static const struct mtk_auxadc_compatible mt8186_compat = { + .sample_data_cali = false, + .check_global_idle = false, +}; + +static const struct mtk_auxadc_compatible mt8173_compat = { + .sample_data_cali = false, + .check_global_idle = true, +}; + +static const struct mtk_auxadc_compatible mt6765_compat = { + .sample_data_cali = true, + .check_global_idle = false, +}; + +#define MT6577_AUXADC_CHANNEL(idx) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ +} + +static const struct iio_chan_spec mt6577_auxadc_iio_channels[] = { + MT6577_AUXADC_CHANNEL(0), + MT6577_AUXADC_CHANNEL(1), + MT6577_AUXADC_CHANNEL(2), + MT6577_AUXADC_CHANNEL(3), + MT6577_AUXADC_CHANNEL(4), + MT6577_AUXADC_CHANNEL(5), + MT6577_AUXADC_CHANNEL(6), + MT6577_AUXADC_CHANNEL(7), + MT6577_AUXADC_CHANNEL(8), + MT6577_AUXADC_CHANNEL(9), + MT6577_AUXADC_CHANNEL(10), + MT6577_AUXADC_CHANNEL(11), + MT6577_AUXADC_CHANNEL(12), + MT6577_AUXADC_CHANNEL(13), + MT6577_AUXADC_CHANNEL(14), + MT6577_AUXADC_CHANNEL(15), +}; + +/* For Voltage calculation */ +#define VOLTAGE_FULL_RANGE 1500 /* VA voltage */ +#define AUXADC_PRECISE 4096 /* 12 bits */ + +static int mt_auxadc_get_cali_data(int rawdata, bool enable_cali) +{ + return rawdata; +} + +static inline void mt6577_auxadc_mod_reg(void __iomem *reg, + u32 or_mask, u32 and_mask) +{ + u32 val; + + val = readl(reg); + val |= or_mask; + val &= ~and_mask; + writel(val, reg); +} + +static int mt6577_auxadc_read(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + u32 val; + void __iomem *reg_channel; + int ret; + struct mt6577_auxadc_device *adc_dev = iio_priv(indio_dev); + + reg_channel = adc_dev->reg_base + MT6577_AUXADC_DAT0 + + chan->channel * 0x04; + + mutex_lock(&adc_dev->lock); + + mt6577_auxadc_mod_reg(adc_dev->reg_base + MT6577_AUXADC_CON1, + 0, 1 << chan->channel); + + /* read channel and make sure old ready bit == 0 */ + ret = readl_poll_timeout(reg_channel, val, + ((val & MT6577_AUXADC_RDY0) == 0), + MT6577_AUXADC_SLEEP_US, + MT6577_AUXADC_TIMEOUT_US); + if (ret < 0) { + dev_err(indio_dev->dev.parent, + "wait for channel[%d] ready bit clear time out\n", + chan->channel); + goto err_timeout; + } + + /* set bit to trigger sample */ + mt6577_auxadc_mod_reg(adc_dev->reg_base + MT6577_AUXADC_CON1, + 1 << chan->channel, 0); + + /* we must delay here for hardware sample channel data */ + udelay(MT6577_AUXADC_SAMPLE_READY_US); + + if (adc_dev->dev_comp->check_global_idle) { + /* check MTK_AUXADC_CON2 if auxadc is idle */ + ret = readl_poll_timeout(adc_dev->reg_base + MT6577_AUXADC_CON2, + val, ((val & MT6577_AUXADC_STA) == 0), + MT6577_AUXADC_SLEEP_US, + MT6577_AUXADC_TIMEOUT_US); + if (ret < 0) { + dev_err(indio_dev->dev.parent, + "wait for auxadc idle time out\n"); + goto err_timeout; + } + } + + /* read channel and make sure ready bit == 1 */ + ret = readl_poll_timeout(reg_channel, val, + ((val & MT6577_AUXADC_RDY0) != 0), + MT6577_AUXADC_SLEEP_US, + MT6577_AUXADC_TIMEOUT_US); + if (ret < 0) { + dev_err(indio_dev->dev.parent, + "wait for channel[%d] data ready time out\n", + chan->channel); + goto err_timeout; + } + + /* read data */ + val = readl(reg_channel) & MT6577_AUXADC_DAT_MASK; + + mutex_unlock(&adc_dev->lock); + + return val; + +err_timeout: + + mutex_unlock(&adc_dev->lock); + + return -ETIMEDOUT; +} + +static int mt6577_auxadc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long info) +{ + struct mt6577_auxadc_device *adc_dev = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_PROCESSED: + *val = mt6577_auxadc_read(indio_dev, chan); + if (*val < 0) { + dev_err(indio_dev->dev.parent, + "failed to sample data on channel[%d]\n", + chan->channel); + return *val; + } + if (adc_dev->dev_comp->sample_data_cali) + *val = mt_auxadc_get_cali_data(*val, true); + + /* Convert adc raw data to voltage: 0 - 1500 mV */ + *val = *val * VOLTAGE_FULL_RANGE / AUXADC_PRECISE; + + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static const struct iio_info mt6577_auxadc_info = { + .read_raw = &mt6577_auxadc_read_raw, +}; + +static int mt6577_auxadc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct mt6577_auxadc_device *adc_dev = iio_priv(indio_dev); + int ret; + + ret = clk_prepare_enable(adc_dev->adc_clk); + if (ret) { + pr_err("failed to enable auxadc clock\n"); + return ret; + } + + mt6577_auxadc_mod_reg(adc_dev->reg_base + MT6577_AUXADC_MISC, + MT6577_AUXADC_PDN_EN, 0); + mdelay(MT6577_AUXADC_POWER_READY_MS); + + return 0; +} + +static int mt6577_auxadc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct mt6577_auxadc_device *adc_dev = iio_priv(indio_dev); + + mt6577_auxadc_mod_reg(adc_dev->reg_base + MT6577_AUXADC_MISC, + 0, MT6577_AUXADC_PDN_EN); + clk_disable_unprepare(adc_dev->adc_clk); + + return 0; +} + +static void mt6577_power_off(void *data) +{ + struct mt6577_auxadc_device *adc_dev = data; + + mt6577_auxadc_mod_reg(adc_dev->reg_base + MT6577_AUXADC_MISC, + 0, MT6577_AUXADC_PDN_EN); +} + +static int mt6577_auxadc_probe(struct platform_device *pdev) +{ + struct mt6577_auxadc_device *adc_dev; + unsigned long adc_clk_rate; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc_dev)); + if (!indio_dev) + return -ENOMEM; + + adc_dev = iio_priv(indio_dev); + indio_dev->name = dev_name(&pdev->dev); + indio_dev->info = &mt6577_auxadc_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = mt6577_auxadc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(mt6577_auxadc_iio_channels); + + adc_dev->reg_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(adc_dev->reg_base)) + return dev_err_probe(&pdev->dev, PTR_ERR(adc_dev->reg_base), + "failed to get auxadc base address\n"); + + adc_dev->adc_clk = devm_clk_get_enabled(&pdev->dev, "main"); + if (IS_ERR(adc_dev->adc_clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(adc_dev->adc_clk), + "failed to enable auxadc clock\n"); + + adc_clk_rate = clk_get_rate(adc_dev->adc_clk); + if (!adc_clk_rate) + return dev_err_probe(&pdev->dev, -EINVAL, "null clock rate\n"); + + adc_dev->dev_comp = device_get_match_data(&pdev->dev); + + mutex_init(&adc_dev->lock); + + mt6577_auxadc_mod_reg(adc_dev->reg_base + MT6577_AUXADC_MISC, + MT6577_AUXADC_PDN_EN, 0); + mdelay(MT6577_AUXADC_POWER_READY_MS); + platform_set_drvdata(pdev, indio_dev); + + ret = devm_add_action_or_reset(&pdev->dev, mt6577_power_off, adc_dev); + if (ret) + return ret; + + ret = devm_iio_device_register(&pdev->dev, indio_dev); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "failed to register iio device\n"); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(mt6577_auxadc_pm_ops, + mt6577_auxadc_suspend, + mt6577_auxadc_resume); + +static const struct of_device_id mt6577_auxadc_of_match[] = { + { .compatible = "mediatek,mt2701-auxadc", .data = &mt8173_compat }, + { .compatible = "mediatek,mt2712-auxadc", .data = &mt8173_compat }, + { .compatible = "mediatek,mt7622-auxadc", .data = &mt8173_compat }, + { .compatible = "mediatek,mt8173-auxadc", .data = &mt8173_compat }, + { .compatible = "mediatek,mt8186-auxadc", .data = &mt8186_compat }, + { .compatible = "mediatek,mt6765-auxadc", .data = &mt6765_compat }, + { } +}; +MODULE_DEVICE_TABLE(of, mt6577_auxadc_of_match); + +static struct platform_driver mt6577_auxadc_driver = { + .driver = { + .name = "mt6577-auxadc", + .of_match_table = mt6577_auxadc_of_match, + .pm = pm_sleep_ptr(&mt6577_auxadc_pm_ops), + }, + .probe = mt6577_auxadc_probe, +}; +module_platform_driver(mt6577_auxadc_driver); + +MODULE_AUTHOR("Zhiyong Tao "); +MODULE_DESCRIPTION("MTK AUXADC Device Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/mxs-lradc-adc.c b/drivers/iio/adc/mxs-lradc-adc.c new file mode 100644 index 000000000..dda5182a5 --- /dev/null +++ b/drivers/iio/adc/mxs-lradc-adc.c @@ -0,0 +1,829 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Freescale MXS LRADC ADC driver + * + * Copyright (c) 2012 DENX Software Engineering, GmbH. + * Copyright (c) 2017 Ksenija Stanojevic + * + * Authors: + * Marek Vasut + * Ksenija Stanojevic + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* + * Make this runtime configurable if necessary. Currently, if the buffered mode + * is enabled, the LRADC takes LRADC_DELAY_TIMER_LOOP samples of data before + * triggering IRQ. The sampling happens every (LRADC_DELAY_TIMER_PER / 2000) + * seconds. The result is that the samples arrive every 500mS. + */ +#define LRADC_DELAY_TIMER_PER 200 +#define LRADC_DELAY_TIMER_LOOP 5 + +#define VREF_MV_BASE 1850 + +static const char *mx23_lradc_adc_irq_names[] = { + "mxs-lradc-channel0", + "mxs-lradc-channel1", + "mxs-lradc-channel2", + "mxs-lradc-channel3", + "mxs-lradc-channel4", + "mxs-lradc-channel5", +}; + +static const char *mx28_lradc_adc_irq_names[] = { + "mxs-lradc-thresh0", + "mxs-lradc-thresh1", + "mxs-lradc-channel0", + "mxs-lradc-channel1", + "mxs-lradc-channel2", + "mxs-lradc-channel3", + "mxs-lradc-channel4", + "mxs-lradc-channel5", + "mxs-lradc-button0", + "mxs-lradc-button1", +}; + +static const u32 mxs_lradc_adc_vref_mv[][LRADC_MAX_TOTAL_CHANS] = { + [IMX23_LRADC] = { + VREF_MV_BASE, /* CH0 */ + VREF_MV_BASE, /* CH1 */ + VREF_MV_BASE, /* CH2 */ + VREF_MV_BASE, /* CH3 */ + VREF_MV_BASE, /* CH4 */ + VREF_MV_BASE, /* CH5 */ + VREF_MV_BASE * 2, /* CH6 VDDIO */ + VREF_MV_BASE * 4, /* CH7 VBATT */ + VREF_MV_BASE, /* CH8 Temp sense 0 */ + VREF_MV_BASE, /* CH9 Temp sense 1 */ + VREF_MV_BASE, /* CH10 */ + VREF_MV_BASE, /* CH11 */ + VREF_MV_BASE, /* CH12 USB_DP */ + VREF_MV_BASE, /* CH13 USB_DN */ + VREF_MV_BASE, /* CH14 VBG */ + VREF_MV_BASE * 4, /* CH15 VDD5V */ + }, + [IMX28_LRADC] = { + VREF_MV_BASE, /* CH0 */ + VREF_MV_BASE, /* CH1 */ + VREF_MV_BASE, /* CH2 */ + VREF_MV_BASE, /* CH3 */ + VREF_MV_BASE, /* CH4 */ + VREF_MV_BASE, /* CH5 */ + VREF_MV_BASE, /* CH6 */ + VREF_MV_BASE * 4, /* CH7 VBATT */ + VREF_MV_BASE, /* CH8 Temp sense 0 */ + VREF_MV_BASE, /* CH9 Temp sense 1 */ + VREF_MV_BASE * 2, /* CH10 VDDIO */ + VREF_MV_BASE, /* CH11 VTH */ + VREF_MV_BASE * 2, /* CH12 VDDA */ + VREF_MV_BASE, /* CH13 VDDD */ + VREF_MV_BASE, /* CH14 VBG */ + VREF_MV_BASE * 4, /* CH15 VDD5V */ + }, +}; + +enum mxs_lradc_divbytwo { + MXS_LRADC_DIV_DISABLED = 0, + MXS_LRADC_DIV_ENABLED, +}; + +struct mxs_lradc_scale { + unsigned int integer; + unsigned int nano; +}; + +struct mxs_lradc_adc { + struct mxs_lradc *lradc; + struct device *dev; + + void __iomem *base; + /* Maximum of 8 channels + 8 byte ts */ + u32 buffer[10] __aligned(8); + struct iio_trigger *trig; + struct completion completion; + spinlock_t lock; + + const u32 *vref_mv; + struct mxs_lradc_scale scale_avail[LRADC_MAX_TOTAL_CHANS][2]; + unsigned long is_divided; +}; + + +/* Raw I/O operations */ +static int mxs_lradc_adc_read_single(struct iio_dev *iio_dev, int chan, + int *val) +{ + struct mxs_lradc_adc *adc = iio_priv(iio_dev); + struct mxs_lradc *lradc = adc->lradc; + int ret; + + /* + * See if there is no buffered operation in progress. If there is simply + * bail out. This can be improved to support both buffered and raw IO at + * the same time, yet the code becomes horribly complicated. Therefore I + * applied KISS principle here. + */ + if (!iio_device_claim_direct(iio_dev)) + return -EBUSY; + + reinit_completion(&adc->completion); + + /* + * No buffered operation in progress, map the channel and trigger it. + * Virtual channel 0 is always used here as the others are always not + * used if doing raw sampling. + */ + if (lradc->soc == IMX28_LRADC) + writel(LRADC_CTRL1_LRADC_IRQ_EN(0), + adc->base + LRADC_CTRL1 + STMP_OFFSET_REG_CLR); + writel(0x1, adc->base + LRADC_CTRL0 + STMP_OFFSET_REG_CLR); + + /* Enable / disable the divider per requirement */ + if (test_bit(chan, &adc->is_divided)) + writel(1 << LRADC_CTRL2_DIVIDE_BY_TWO_OFFSET, + adc->base + LRADC_CTRL2 + STMP_OFFSET_REG_SET); + else + writel(1 << LRADC_CTRL2_DIVIDE_BY_TWO_OFFSET, + adc->base + LRADC_CTRL2 + STMP_OFFSET_REG_CLR); + + /* Clean the slot's previous content, then set new one. */ + writel(LRADC_CTRL4_LRADCSELECT_MASK(0), + adc->base + LRADC_CTRL4 + STMP_OFFSET_REG_CLR); + writel(chan, adc->base + LRADC_CTRL4 + STMP_OFFSET_REG_SET); + + writel(0, adc->base + LRADC_CH(0)); + + /* Enable the IRQ and start sampling the channel. */ + writel(LRADC_CTRL1_LRADC_IRQ_EN(0), + adc->base + LRADC_CTRL1 + STMP_OFFSET_REG_SET); + writel(BIT(0), adc->base + LRADC_CTRL0 + STMP_OFFSET_REG_SET); + + /* Wait for completion on the channel, 1 second max. */ + ret = wait_for_completion_killable_timeout(&adc->completion, HZ); + if (!ret) + ret = -ETIMEDOUT; + if (ret < 0) + goto err; + + /* Read the data. */ + *val = readl(adc->base + LRADC_CH(0)) & LRADC_CH_VALUE_MASK; + ret = IIO_VAL_INT; + +err: + writel(LRADC_CTRL1_LRADC_IRQ_EN(0), + adc->base + LRADC_CTRL1 + STMP_OFFSET_REG_CLR); + + iio_device_release_direct(iio_dev); + + return ret; +} + +static int mxs_lradc_adc_read_temp(struct iio_dev *iio_dev, int *val) +{ + int ret, min, max; + + ret = mxs_lradc_adc_read_single(iio_dev, 8, &min); + if (ret != IIO_VAL_INT) + return ret; + + ret = mxs_lradc_adc_read_single(iio_dev, 9, &max); + if (ret != IIO_VAL_INT) + return ret; + + *val = max - min; + + return IIO_VAL_INT; +} + +static int mxs_lradc_adc_read_raw(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long m) +{ + struct mxs_lradc_adc *adc = iio_priv(iio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_TEMP) + return mxs_lradc_adc_read_temp(iio_dev, val); + + return mxs_lradc_adc_read_single(iio_dev, chan->channel, val); + + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_TEMP) { + /* + * From the datasheet, we have to multiply by 1.012 and + * divide by 4 + */ + *val = 0; + *val2 = 253000; + return IIO_VAL_INT_PLUS_MICRO; + } + + *val = adc->vref_mv[chan->channel]; + *val2 = chan->scan_type.realbits - + test_bit(chan->channel, &adc->is_divided); + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_OFFSET: + if (chan->type == IIO_TEMP) { + /* + * The calculated value from the ADC is in Kelvin, we + * want Celsius for hwmon so the offset is -273.15 + * The offset is applied before scaling so it is + * actually -213.15 * 4 / 1.012 = -1079.644268 + */ + *val = -1079; + *val2 = 644268; + + return IIO_VAL_INT_PLUS_MICRO; + } + + return -EINVAL; + + default: + break; + } + + return -EINVAL; +} + +static int mxs_lradc_adc_write_raw(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + int val, int val2, long m) +{ + struct mxs_lradc_adc *adc = iio_priv(iio_dev); + struct mxs_lradc_scale *scale_avail = + adc->scale_avail[chan->channel]; + int ret; + + if (!iio_device_claim_direct(iio_dev)) + return -EBUSY; + + switch (m) { + case IIO_CHAN_INFO_SCALE: + ret = -EINVAL; + if (val == scale_avail[MXS_LRADC_DIV_DISABLED].integer && + val2 == scale_avail[MXS_LRADC_DIV_DISABLED].nano) { + /* divider by two disabled */ + clear_bit(chan->channel, &adc->is_divided); + ret = 0; + } else if (val == scale_avail[MXS_LRADC_DIV_ENABLED].integer && + val2 == scale_avail[MXS_LRADC_DIV_ENABLED].nano) { + /* divider by two enabled */ + set_bit(chan->channel, &adc->is_divided); + ret = 0; + } + + break; + default: + ret = -EINVAL; + break; + } + + iio_device_release_direct(iio_dev); + + return ret; +} + +static int mxs_lradc_adc_write_raw_get_fmt(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + long m) +{ + return IIO_VAL_INT_PLUS_NANO; +} + +static ssize_t mxs_lradc_adc_show_scale_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *iio = dev_to_iio_dev(dev); + struct mxs_lradc_adc *adc = iio_priv(iio); + struct iio_dev_attr *iio_attr = to_iio_dev_attr(attr); + int i, ch, len = 0; + + ch = iio_attr->address; + for (i = 0; i < ARRAY_SIZE(adc->scale_avail[ch]); i++) + len += sprintf(buf + len, "%u.%09u ", + adc->scale_avail[ch][i].integer, + adc->scale_avail[ch][i].nano); + + len += sprintf(buf + len, "\n"); + + return len; +} + +#define SHOW_SCALE_AVAILABLE_ATTR(ch)\ + IIO_DEVICE_ATTR(in_voltage##ch##_scale_available, 0444,\ + mxs_lradc_adc_show_scale_avail, NULL, ch) + +static SHOW_SCALE_AVAILABLE_ATTR(0); +static SHOW_SCALE_AVAILABLE_ATTR(1); +static SHOW_SCALE_AVAILABLE_ATTR(2); +static SHOW_SCALE_AVAILABLE_ATTR(3); +static SHOW_SCALE_AVAILABLE_ATTR(4); +static SHOW_SCALE_AVAILABLE_ATTR(5); +static SHOW_SCALE_AVAILABLE_ATTR(6); +static SHOW_SCALE_AVAILABLE_ATTR(7); +static SHOW_SCALE_AVAILABLE_ATTR(10); +static SHOW_SCALE_AVAILABLE_ATTR(11); +static SHOW_SCALE_AVAILABLE_ATTR(12); +static SHOW_SCALE_AVAILABLE_ATTR(13); +static SHOW_SCALE_AVAILABLE_ATTR(14); +static SHOW_SCALE_AVAILABLE_ATTR(15); + +static struct attribute *mxs_lradc_adc_attributes[] = { + &iio_dev_attr_in_voltage0_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage1_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage2_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage3_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage4_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage5_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage6_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage7_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage10_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage11_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage12_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage13_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage14_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage15_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group mxs_lradc_adc_attribute_group = { + .attrs = mxs_lradc_adc_attributes, +}; + +static const struct iio_info mxs_lradc_adc_iio_info = { + .read_raw = mxs_lradc_adc_read_raw, + .write_raw = mxs_lradc_adc_write_raw, + .write_raw_get_fmt = mxs_lradc_adc_write_raw_get_fmt, + .attrs = &mxs_lradc_adc_attribute_group, +}; + +/* IRQ Handling */ +static irqreturn_t mxs_lradc_adc_handle_irq(int irq, void *data) +{ + struct iio_dev *iio = data; + struct mxs_lradc_adc *adc = iio_priv(iio); + struct mxs_lradc *lradc = adc->lradc; + unsigned long reg = readl(adc->base + LRADC_CTRL1); + unsigned long flags; + + if (!(reg & mxs_lradc_irq_mask(lradc))) + return IRQ_NONE; + + if (iio_buffer_enabled(iio)) { + if (reg & lradc->buffer_vchans) { + spin_lock_irqsave(&adc->lock, flags); + iio_trigger_poll(iio->trig); + spin_unlock_irqrestore(&adc->lock, flags); + } + } else if (reg & LRADC_CTRL1_LRADC_IRQ(0)) { + complete(&adc->completion); + } + + writel(reg & mxs_lradc_irq_mask(lradc), + adc->base + LRADC_CTRL1 + STMP_OFFSET_REG_CLR); + + return IRQ_HANDLED; +} + + +/* Trigger handling */ +static irqreturn_t mxs_lradc_adc_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *iio = pf->indio_dev; + struct mxs_lradc_adc *adc = iio_priv(iio); + const u32 chan_value = LRADC_CH_ACCUMULATE | + ((LRADC_DELAY_TIMER_LOOP - 1) << LRADC_CH_NUM_SAMPLES_OFFSET); + unsigned int i, j = 0; + + for_each_set_bit(i, iio->active_scan_mask, LRADC_MAX_TOTAL_CHANS) { + adc->buffer[j] = readl(adc->base + LRADC_CH(j)); + writel(chan_value, adc->base + LRADC_CH(j)); + adc->buffer[j] &= LRADC_CH_VALUE_MASK; + adc->buffer[j] /= LRADC_DELAY_TIMER_LOOP; + j++; + } + + iio_push_to_buffers_with_ts(iio, adc->buffer, sizeof(adc->buffer), + pf->timestamp); + + iio_trigger_notify_done(iio->trig); + + return IRQ_HANDLED; +} + +static int mxs_lradc_adc_configure_trigger(struct iio_trigger *trig, bool state) +{ + struct iio_dev *iio = iio_trigger_get_drvdata(trig); + struct mxs_lradc_adc *adc = iio_priv(iio); + const u32 st = state ? STMP_OFFSET_REG_SET : STMP_OFFSET_REG_CLR; + + writel(LRADC_DELAY_KICK, adc->base + (LRADC_DELAY(0) + st)); + + return 0; +} + +static const struct iio_trigger_ops mxs_lradc_adc_trigger_ops = { + .set_trigger_state = &mxs_lradc_adc_configure_trigger, +}; + +static int mxs_lradc_adc_trigger_init(struct iio_dev *iio) +{ + int ret; + struct iio_trigger *trig; + struct mxs_lradc_adc *adc = iio_priv(iio); + + trig = devm_iio_trigger_alloc(&iio->dev, "%s-dev%i", iio->name, + iio_device_id(iio)); + if (!trig) + return -ENOMEM; + + trig->dev.parent = adc->dev; + iio_trigger_set_drvdata(trig, iio); + trig->ops = &mxs_lradc_adc_trigger_ops; + + ret = iio_trigger_register(trig); + if (ret) + return ret; + + adc->trig = trig; + + return 0; +} + +static void mxs_lradc_adc_trigger_remove(struct iio_dev *iio) +{ + struct mxs_lradc_adc *adc = iio_priv(iio); + + iio_trigger_unregister(adc->trig); +} + +static int mxs_lradc_adc_buffer_preenable(struct iio_dev *iio) +{ + struct mxs_lradc_adc *adc = iio_priv(iio); + struct mxs_lradc *lradc = adc->lradc; + int chan, ofs = 0; + unsigned long enable = 0; + u32 ctrl4_set = 0; + u32 ctrl4_clr = 0; + u32 ctrl1_irq = 0; + const u32 chan_value = LRADC_CH_ACCUMULATE | + ((LRADC_DELAY_TIMER_LOOP - 1) << LRADC_CH_NUM_SAMPLES_OFFSET); + + if (lradc->soc == IMX28_LRADC) + writel(lradc->buffer_vchans << LRADC_CTRL1_LRADC_IRQ_EN_OFFSET, + adc->base + LRADC_CTRL1 + STMP_OFFSET_REG_CLR); + writel(lradc->buffer_vchans, + adc->base + LRADC_CTRL0 + STMP_OFFSET_REG_CLR); + + for_each_set_bit(chan, iio->active_scan_mask, LRADC_MAX_TOTAL_CHANS) { + ctrl4_set |= chan << LRADC_CTRL4_LRADCSELECT_OFFSET(ofs); + ctrl4_clr |= LRADC_CTRL4_LRADCSELECT_MASK(ofs); + ctrl1_irq |= LRADC_CTRL1_LRADC_IRQ_EN(ofs); + writel(chan_value, adc->base + LRADC_CH(ofs)); + bitmap_set(&enable, ofs, 1); + ofs++; + } + + writel(LRADC_DELAY_TRIGGER_LRADCS_MASK | LRADC_DELAY_KICK, + adc->base + LRADC_DELAY(0) + STMP_OFFSET_REG_CLR); + writel(ctrl4_clr, adc->base + LRADC_CTRL4 + STMP_OFFSET_REG_CLR); + writel(ctrl4_set, adc->base + LRADC_CTRL4 + STMP_OFFSET_REG_SET); + writel(ctrl1_irq, adc->base + LRADC_CTRL1 + STMP_OFFSET_REG_SET); + writel(enable << LRADC_DELAY_TRIGGER_LRADCS_OFFSET, + adc->base + LRADC_DELAY(0) + STMP_OFFSET_REG_SET); + + return 0; +} + +static int mxs_lradc_adc_buffer_postdisable(struct iio_dev *iio) +{ + struct mxs_lradc_adc *adc = iio_priv(iio); + struct mxs_lradc *lradc = adc->lradc; + + writel(LRADC_DELAY_TRIGGER_LRADCS_MASK | LRADC_DELAY_KICK, + adc->base + LRADC_DELAY(0) + STMP_OFFSET_REG_CLR); + + writel(lradc->buffer_vchans, + adc->base + LRADC_CTRL0 + STMP_OFFSET_REG_CLR); + if (lradc->soc == IMX28_LRADC) + writel(lradc->buffer_vchans << LRADC_CTRL1_LRADC_IRQ_EN_OFFSET, + adc->base + LRADC_CTRL1 + STMP_OFFSET_REG_CLR); + + return 0; +} + +static bool mxs_lradc_adc_validate_scan_mask(struct iio_dev *iio, + const unsigned long *mask) +{ + struct mxs_lradc_adc *adc = iio_priv(iio); + struct mxs_lradc *lradc = adc->lradc; + const int map_chans = bitmap_weight(mask, LRADC_MAX_TOTAL_CHANS); + int rsvd_chans = 0; + unsigned long rsvd_mask = 0; + + if (lradc->use_touchbutton) + rsvd_mask |= CHAN_MASK_TOUCHBUTTON; + if (lradc->touchscreen_wire == MXS_LRADC_TOUCHSCREEN_4WIRE) + rsvd_mask |= CHAN_MASK_TOUCHSCREEN_4WIRE; + if (lradc->touchscreen_wire == MXS_LRADC_TOUCHSCREEN_5WIRE) + rsvd_mask |= CHAN_MASK_TOUCHSCREEN_5WIRE; + + if (lradc->use_touchbutton) + rsvd_chans++; + if (lradc->touchscreen_wire) + rsvd_chans += 2; + + /* Test for attempts to map channels with special mode of operation. */ + if (bitmap_intersects(mask, &rsvd_mask, LRADC_MAX_TOTAL_CHANS)) + return false; + + /* Test for attempts to map more channels then available slots. */ + if (map_chans + rsvd_chans > LRADC_MAX_MAPPED_CHANS) + return false; + + return true; +} + +static const struct iio_buffer_setup_ops mxs_lradc_adc_buffer_ops = { + .preenable = &mxs_lradc_adc_buffer_preenable, + .postdisable = &mxs_lradc_adc_buffer_postdisable, + .validate_scan_mask = &mxs_lradc_adc_validate_scan_mask, +}; + +/* Driver initialization */ +#define MXS_ADC_CHAN(idx, chan_type, name) { \ + .type = (chan_type), \ + .indexed = 1, \ + .scan_index = (idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .channel = (idx), \ + .address = (idx), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = LRADC_RESOLUTION, \ + .storagebits = 32, \ + }, \ + .datasheet_name = (name), \ +} + +static const struct iio_chan_spec mx23_lradc_chan_spec[] = { + MXS_ADC_CHAN(0, IIO_VOLTAGE, "LRADC0"), + MXS_ADC_CHAN(1, IIO_VOLTAGE, "LRADC1"), + MXS_ADC_CHAN(2, IIO_VOLTAGE, "LRADC2"), + MXS_ADC_CHAN(3, IIO_VOLTAGE, "LRADC3"), + MXS_ADC_CHAN(4, IIO_VOLTAGE, "LRADC4"), + MXS_ADC_CHAN(5, IIO_VOLTAGE, "LRADC5"), + MXS_ADC_CHAN(6, IIO_VOLTAGE, "VDDIO"), + MXS_ADC_CHAN(7, IIO_VOLTAGE, "VBATT"), + /* Combined Temperature sensors */ + { + .type = IIO_TEMP, + .indexed = 1, + .scan_index = 8, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .channel = 8, + .scan_type = {.sign = 'u', .realbits = 18, .storagebits = 32,}, + .datasheet_name = "TEMP_DIE", + }, + /* Hidden channel to keep indexes */ + { + .type = IIO_TEMP, + .indexed = 1, + .scan_index = -1, + .channel = 9, + }, + MXS_ADC_CHAN(10, IIO_VOLTAGE, NULL), + MXS_ADC_CHAN(11, IIO_VOLTAGE, NULL), + MXS_ADC_CHAN(12, IIO_VOLTAGE, "USB_DP"), + MXS_ADC_CHAN(13, IIO_VOLTAGE, "USB_DN"), + MXS_ADC_CHAN(14, IIO_VOLTAGE, "VBG"), + MXS_ADC_CHAN(15, IIO_VOLTAGE, "VDD5V"), +}; + +static const struct iio_chan_spec mx28_lradc_chan_spec[] = { + MXS_ADC_CHAN(0, IIO_VOLTAGE, "LRADC0"), + MXS_ADC_CHAN(1, IIO_VOLTAGE, "LRADC1"), + MXS_ADC_CHAN(2, IIO_VOLTAGE, "LRADC2"), + MXS_ADC_CHAN(3, IIO_VOLTAGE, "LRADC3"), + MXS_ADC_CHAN(4, IIO_VOLTAGE, "LRADC4"), + MXS_ADC_CHAN(5, IIO_VOLTAGE, "LRADC5"), + MXS_ADC_CHAN(6, IIO_VOLTAGE, "LRADC6"), + MXS_ADC_CHAN(7, IIO_VOLTAGE, "VBATT"), + /* Combined Temperature sensors */ + { + .type = IIO_TEMP, + .indexed = 1, + .scan_index = 8, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .channel = 8, + .scan_type = {.sign = 'u', .realbits = 18, .storagebits = 32,}, + .datasheet_name = "TEMP_DIE", + }, + /* Hidden channel to keep indexes */ + { + .type = IIO_TEMP, + .indexed = 1, + .scan_index = -1, + .channel = 9, + }, + MXS_ADC_CHAN(10, IIO_VOLTAGE, "VDDIO"), + MXS_ADC_CHAN(11, IIO_VOLTAGE, "VTH"), + MXS_ADC_CHAN(12, IIO_VOLTAGE, "VDDA"), + MXS_ADC_CHAN(13, IIO_VOLTAGE, "VDDD"), + MXS_ADC_CHAN(14, IIO_VOLTAGE, "VBG"), + MXS_ADC_CHAN(15, IIO_VOLTAGE, "VDD5V"), +}; + +static void mxs_lradc_adc_hw_init(struct mxs_lradc_adc *adc) +{ + /* The ADC always uses DELAY CHANNEL 0. */ + const u32 adc_cfg = + (1 << (LRADC_DELAY_TRIGGER_DELAYS_OFFSET + 0)) | + (LRADC_DELAY_TIMER_PER << LRADC_DELAY_DELAY_OFFSET); + + /* Configure DELAY CHANNEL 0 for generic ADC sampling. */ + writel(adc_cfg, adc->base + LRADC_DELAY(0)); + + /* + * Start internal temperature sensing by clearing bit + * HW_LRADC_CTRL2_TEMPSENSE_PWD. This bit can be left cleared + * after power up. + */ + writel(0, adc->base + LRADC_CTRL2); +} + +static void mxs_lradc_adc_hw_stop(struct mxs_lradc_adc *adc) +{ + writel(0, adc->base + LRADC_DELAY(0)); +} + +static int mxs_lradc_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mxs_lradc *lradc = dev_get_drvdata(dev->parent); + struct mxs_lradc_adc *adc; + struct iio_dev *iio; + struct resource *iores; + int ret, irq, virq, i, s, n; + u64 scale_uv; + const char **irq_name; + + /* Allocate the IIO device. */ + iio = devm_iio_device_alloc(dev, sizeof(*adc)); + if (!iio) + return -ENOMEM; + + adc = iio_priv(iio); + adc->lradc = lradc; + adc->dev = dev; + + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!iores) + return -EINVAL; + + adc->base = devm_ioremap(dev, iores->start, resource_size(iores)); + if (!adc->base) + return -ENOMEM; + + init_completion(&adc->completion); + spin_lock_init(&adc->lock); + + platform_set_drvdata(pdev, iio); + + iio->name = pdev->name; + iio->dev.of_node = dev->parent->of_node; + iio->info = &mxs_lradc_adc_iio_info; + iio->modes = INDIO_DIRECT_MODE; + + if (lradc->soc == IMX23_LRADC) { + iio->channels = mx23_lradc_chan_spec; + iio->num_channels = ARRAY_SIZE(mx23_lradc_chan_spec); + irq_name = mx23_lradc_adc_irq_names; + n = ARRAY_SIZE(mx23_lradc_adc_irq_names); + } else { + iio->channels = mx28_lradc_chan_spec; + iio->num_channels = ARRAY_SIZE(mx28_lradc_chan_spec); + irq_name = mx28_lradc_adc_irq_names; + n = ARRAY_SIZE(mx28_lradc_adc_irq_names); + } + + ret = stmp_reset_block(adc->base); + if (ret) + return ret; + + for (i = 0; i < n; i++) { + irq = platform_get_irq_byname(pdev, irq_name[i]); + if (irq < 0) + return irq; + + virq = irq_of_parse_and_map(dev->parent->of_node, irq); + + ret = devm_request_irq(dev, virq, mxs_lradc_adc_handle_irq, + 0, irq_name[i], iio); + if (ret) + return ret; + } + + ret = mxs_lradc_adc_trigger_init(iio); + if (ret) + return ret; + + ret = iio_triggered_buffer_setup(iio, &iio_pollfunc_store_time, + &mxs_lradc_adc_trigger_handler, + &mxs_lradc_adc_buffer_ops); + if (ret) + goto err_trig; + + adc->vref_mv = mxs_lradc_adc_vref_mv[lradc->soc]; + + /* Populate available ADC input ranges */ + for (i = 0; i < LRADC_MAX_TOTAL_CHANS; i++) { + for (s = 0; s < ARRAY_SIZE(adc->scale_avail[i]); s++) { + /* + * [s=0] = optional divider by two disabled (default) + * [s=1] = optional divider by two enabled + * + * The scale is calculated by doing: + * Vref >> (realbits - s) + * which multiplies by two on the second component + * of the array. + */ + scale_uv = ((u64)adc->vref_mv[i] * 100000000) >> + (LRADC_RESOLUTION - s); + adc->scale_avail[i][s].nano = + do_div(scale_uv, 100000000) * 10; + adc->scale_avail[i][s].integer = scale_uv; + } + } + + /* Configure the hardware. */ + mxs_lradc_adc_hw_init(adc); + + /* Register IIO device. */ + ret = iio_device_register(iio); + if (ret) { + dev_err(dev, "Failed to register IIO device\n"); + goto err_dev; + } + + return 0; + +err_dev: + mxs_lradc_adc_hw_stop(adc); + iio_triggered_buffer_cleanup(iio); +err_trig: + mxs_lradc_adc_trigger_remove(iio); + return ret; +} + +static void mxs_lradc_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *iio = platform_get_drvdata(pdev); + struct mxs_lradc_adc *adc = iio_priv(iio); + + iio_device_unregister(iio); + mxs_lradc_adc_hw_stop(adc); + iio_triggered_buffer_cleanup(iio); + mxs_lradc_adc_trigger_remove(iio); +} + +static struct platform_driver mxs_lradc_adc_driver = { + .driver = { + .name = "mxs-lradc-adc", + }, + .probe = mxs_lradc_adc_probe, + .remove = mxs_lradc_adc_remove, +}; +module_platform_driver(mxs_lradc_adc_driver); + +MODULE_AUTHOR("Marek Vasut "); +MODULE_DESCRIPTION("Freescale MXS LRADC driver general purpose ADC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:mxs-lradc-adc"); diff --git a/drivers/iio/adc/nau7802.c b/drivers/iio/adc/nau7802.c new file mode 100644 index 000000000..dccf11fbf --- /dev/null +++ b/drivers/iio/adc/nau7802.c @@ -0,0 +1,558 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the Nuvoton NAU7802 ADC + * + * Copyright 2013 Free Electrons + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define NAU7802_REG_PUCTRL 0x00 +#define NAU7802_PUCTRL_RR(x) (x << 0) +#define NAU7802_PUCTRL_RR_BIT NAU7802_PUCTRL_RR(1) +#define NAU7802_PUCTRL_PUD(x) (x << 1) +#define NAU7802_PUCTRL_PUD_BIT NAU7802_PUCTRL_PUD(1) +#define NAU7802_PUCTRL_PUA(x) (x << 2) +#define NAU7802_PUCTRL_PUA_BIT NAU7802_PUCTRL_PUA(1) +#define NAU7802_PUCTRL_PUR(x) (x << 3) +#define NAU7802_PUCTRL_PUR_BIT NAU7802_PUCTRL_PUR(1) +#define NAU7802_PUCTRL_CS(x) (x << 4) +#define NAU7802_PUCTRL_CS_BIT NAU7802_PUCTRL_CS(1) +#define NAU7802_PUCTRL_CR(x) (x << 5) +#define NAU7802_PUCTRL_CR_BIT NAU7802_PUCTRL_CR(1) +#define NAU7802_PUCTRL_AVDDS(x) (x << 7) +#define NAU7802_PUCTRL_AVDDS_BIT NAU7802_PUCTRL_AVDDS(1) +#define NAU7802_REG_CTRL1 0x01 +#define NAU7802_CTRL1_VLDO(x) (x << 3) +#define NAU7802_CTRL1_GAINS(x) (x) +#define NAU7802_CTRL1_GAINS_BITS 0x07 +#define NAU7802_REG_CTRL2 0x02 +#define NAU7802_CTRL2_CHS(x) (x << 7) +#define NAU7802_CTRL2_CRS(x) (x << 4) +#define NAU7802_SAMP_FREQ_320 0x07 +#define NAU7802_CTRL2_CHS_BIT NAU7802_CTRL2_CHS(1) +#define NAU7802_REG_ADC_B2 0x12 +#define NAU7802_REG_ADC_B1 0x13 +#define NAU7802_REG_ADC_B0 0x14 +#define NAU7802_REG_ADC_CTRL 0x15 + +#define NAU7802_MIN_CONVERSIONS 6 + +struct nau7802_state { + struct i2c_client *client; + s32 last_value; + struct mutex lock; + struct mutex data_lock; + u32 vref_mv; + u32 conversion_count; + u8 sample_rate; + u32 scale_avail[8]; + struct completion value_ok; +}; + +#define NAU7802_CHANNEL(chan) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (chan), \ + .scan_index = (chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) \ +} + +static const struct iio_chan_spec nau7802_chan_array[] = { + NAU7802_CHANNEL(0), + NAU7802_CHANNEL(1), +}; + +static const u16 nau7802_sample_freq_avail[] = {10, 20, 40, 80, + 10, 10, 10, 320}; + +static ssize_t nau7802_show_scales(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct nau7802_state *st = iio_priv(dev_to_iio_dev(dev)); + int i, len = 0; + + for (i = 0; i < ARRAY_SIZE(st->scale_avail); i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "0.%09d ", + st->scale_avail[i]); + + buf[len-1] = '\n'; + + return len; +} + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("10 40 80 320"); + +static IIO_DEVICE_ATTR(in_voltage_scale_available, S_IRUGO, nau7802_show_scales, + NULL, 0); + +static struct attribute *nau7802_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_voltage_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group nau7802_attribute_group = { + .attrs = nau7802_attributes, +}; + +static int nau7802_set_gain(struct nau7802_state *st, int gain) +{ + int ret; + + mutex_lock(&st->lock); + st->conversion_count = 0; + + ret = i2c_smbus_read_byte_data(st->client, NAU7802_REG_CTRL1); + if (ret < 0) + goto nau7802_sysfs_set_gain_out; + ret = i2c_smbus_write_byte_data(st->client, NAU7802_REG_CTRL1, + (ret & (~NAU7802_CTRL1_GAINS_BITS)) | + gain); + +nau7802_sysfs_set_gain_out: + mutex_unlock(&st->lock); + + return ret; +} + +static int nau7802_read_conversion(struct nau7802_state *st) +{ + int data; + + mutex_lock(&st->data_lock); + data = i2c_smbus_read_byte_data(st->client, NAU7802_REG_ADC_B2); + if (data < 0) + goto nau7802_read_conversion_out; + st->last_value = data << 16; + + data = i2c_smbus_read_byte_data(st->client, NAU7802_REG_ADC_B1); + if (data < 0) + goto nau7802_read_conversion_out; + st->last_value |= data << 8; + + data = i2c_smbus_read_byte_data(st->client, NAU7802_REG_ADC_B0); + if (data < 0) + goto nau7802_read_conversion_out; + st->last_value |= data; + + st->last_value = sign_extend32(st->last_value, 23); + +nau7802_read_conversion_out: + mutex_unlock(&st->data_lock); + + return data; +} + +/* + * Conversions are synchronised on the rising edge of NAU7802_PUCTRL_CS_BIT + */ +static int nau7802_sync(struct nau7802_state *st) +{ + int ret; + + ret = i2c_smbus_read_byte_data(st->client, NAU7802_REG_PUCTRL); + if (ret < 0) + return ret; + ret = i2c_smbus_write_byte_data(st->client, NAU7802_REG_PUCTRL, + ret | NAU7802_PUCTRL_CS_BIT); + + return ret; +} + +static irqreturn_t nau7802_eoc_trigger(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct nau7802_state *st = iio_priv(indio_dev); + int status; + + status = i2c_smbus_read_byte_data(st->client, NAU7802_REG_PUCTRL); + if (status < 0) + return IRQ_HANDLED; + + if (!(status & NAU7802_PUCTRL_CR_BIT)) + return IRQ_NONE; + + if (nau7802_read_conversion(st) < 0) + return IRQ_HANDLED; + + /* + * Because there is actually only one ADC for both channels, we have to + * wait for enough conversions to happen before getting a significant + * value when changing channels and the values are far apart. + */ + if (st->conversion_count < NAU7802_MIN_CONVERSIONS) + st->conversion_count++; + if (st->conversion_count >= NAU7802_MIN_CONVERSIONS) + complete(&st->value_ok); + + return IRQ_HANDLED; +} + +static int nau7802_read_irq(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct nau7802_state *st = iio_priv(indio_dev); + int ret; + + reinit_completion(&st->value_ok); + enable_irq(st->client->irq); + + nau7802_sync(st); + + /* read registers to ensure we flush everything */ + ret = nau7802_read_conversion(st); + if (ret < 0) + goto read_chan_info_failure; + + /* Wait for a conversion to finish */ + ret = wait_for_completion_interruptible_timeout(&st->value_ok, + msecs_to_jiffies(1000)); + if (ret == 0) + ret = -ETIMEDOUT; + + if (ret < 0) + goto read_chan_info_failure; + + disable_irq(st->client->irq); + + *val = st->last_value; + + return IIO_VAL_INT; + +read_chan_info_failure: + disable_irq(st->client->irq); + + return ret; +} + +static int nau7802_read_poll(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct nau7802_state *st = iio_priv(indio_dev); + int ret; + + nau7802_sync(st); + + /* read registers to ensure we flush everything */ + ret = nau7802_read_conversion(st); + if (ret < 0) + return ret; + + /* + * Because there is actually only one ADC for both channels, we have to + * wait for enough conversions to happen before getting a significant + * value when changing channels and the values are far apart. + */ + do { + ret = i2c_smbus_read_byte_data(st->client, NAU7802_REG_PUCTRL); + if (ret < 0) + return ret; + + while (!(ret & NAU7802_PUCTRL_CR_BIT)) { + if (st->sample_rate != NAU7802_SAMP_FREQ_320) + msleep(20); + else + mdelay(4); + ret = i2c_smbus_read_byte_data(st->client, + NAU7802_REG_PUCTRL); + if (ret < 0) + return ret; + } + + ret = nau7802_read_conversion(st); + if (ret < 0) + return ret; + if (st->conversion_count < NAU7802_MIN_CONVERSIONS) + st->conversion_count++; + } while (st->conversion_count < NAU7802_MIN_CONVERSIONS); + + *val = st->last_value; + + return IIO_VAL_INT; +} + +static int nau7802_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct nau7802_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&st->lock); + /* + * Select the channel to use + * - Channel 1 is value 0 in the CHS register + * - Channel 2 is value 1 in the CHS register + */ + ret = i2c_smbus_read_byte_data(st->client, NAU7802_REG_CTRL2); + if (ret < 0) { + mutex_unlock(&st->lock); + return ret; + } + + if (((ret & NAU7802_CTRL2_CHS_BIT) && !chan->channel) || + (!(ret & NAU7802_CTRL2_CHS_BIT) && + chan->channel)) { + st->conversion_count = 0; + ret = i2c_smbus_write_byte_data(st->client, + NAU7802_REG_CTRL2, + NAU7802_CTRL2_CHS(chan->channel) | + NAU7802_CTRL2_CRS(st->sample_rate)); + + if (ret < 0) { + mutex_unlock(&st->lock); + return ret; + } + } + + if (st->client->irq) + ret = nau7802_read_irq(indio_dev, chan, val); + else + ret = nau7802_read_poll(indio_dev, chan, val); + + mutex_unlock(&st->lock); + return ret; + + case IIO_CHAN_INFO_SCALE: + ret = i2c_smbus_read_byte_data(st->client, NAU7802_REG_CTRL1); + if (ret < 0) + return ret; + + /* + * We have 24 bits of signed data, that means 23 bits of data + * plus the sign bit + */ + *val = st->vref_mv; + *val2 = 23 + (ret & NAU7802_CTRL1_GAINS_BITS); + + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = nau7802_sample_freq_avail[st->sample_rate]; + *val2 = 0; + return IIO_VAL_INT; + + default: + break; + } + + return -EINVAL; +} + +static int nau7802_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct nau7802_state *st = iio_priv(indio_dev); + int i, ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + for (i = 0; i < ARRAY_SIZE(st->scale_avail); i++) + if (val2 == st->scale_avail[i]) + return nau7802_set_gain(st, i); + + break; + + case IIO_CHAN_INFO_SAMP_FREQ: + for (i = 0; i < ARRAY_SIZE(nau7802_sample_freq_avail); i++) + if (val == nau7802_sample_freq_avail[i]) { + mutex_lock(&st->lock); + st->sample_rate = i; + st->conversion_count = 0; + ret = i2c_smbus_write_byte_data(st->client, + NAU7802_REG_CTRL2, + NAU7802_CTRL2_CRS(st->sample_rate)); + mutex_unlock(&st->lock); + return ret; + } + + break; + + default: + break; + } + + return -EINVAL; +} + +static int nau7802_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + return IIO_VAL_INT_PLUS_NANO; +} + +static const struct iio_info nau7802_info = { + .read_raw = &nau7802_read_raw, + .write_raw = &nau7802_write_raw, + .write_raw_get_fmt = nau7802_write_raw_get_fmt, + .attrs = &nau7802_attribute_group, +}; + +static int nau7802_probe(struct i2c_client *client) +{ + struct iio_dev *indio_dev; + struct nau7802_state *st; + int i, ret; + u8 data; + u32 tmp = 0; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + indio_dev->name = dev_name(&client->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &nau7802_info; + + st->client = client; + + /* Reset the device */ + ret = i2c_smbus_write_byte_data(st->client, NAU7802_REG_PUCTRL, + NAU7802_PUCTRL_RR_BIT); + if (ret < 0) + return ret; + + /* Enter normal operation mode */ + ret = i2c_smbus_write_byte_data(st->client, NAU7802_REG_PUCTRL, + NAU7802_PUCTRL_PUD_BIT); + if (ret < 0) + return ret; + + /* + * After about 200 usecs, the device should be ready and then + * the Power Up bit will be set to 1. If not, wait for it. + */ + udelay(210); + ret = i2c_smbus_read_byte_data(st->client, NAU7802_REG_PUCTRL); + if (ret < 0) + return ret; + if (!(ret & NAU7802_PUCTRL_PUR_BIT)) + return ret; + + device_property_read_u32(&client->dev, "nuvoton,vldo", &tmp); + st->vref_mv = tmp; + + data = NAU7802_PUCTRL_PUD_BIT | NAU7802_PUCTRL_PUA_BIT | + NAU7802_PUCTRL_CS_BIT; + if (tmp >= 2400) + data |= NAU7802_PUCTRL_AVDDS_BIT; + + ret = i2c_smbus_write_byte_data(st->client, NAU7802_REG_PUCTRL, data); + if (ret < 0) + return ret; + ret = i2c_smbus_write_byte_data(st->client, NAU7802_REG_ADC_CTRL, 0x30); + if (ret < 0) + return ret; + + if (tmp >= 2400) { + data = NAU7802_CTRL1_VLDO((4500 - tmp) / 300); + ret = i2c_smbus_write_byte_data(st->client, NAU7802_REG_CTRL1, + data); + if (ret < 0) + return ret; + } + + /* Populate available ADC input ranges */ + for (i = 0; i < ARRAY_SIZE(st->scale_avail); i++) + st->scale_avail[i] = (((u64)st->vref_mv) * 1000000000ULL) + >> (23 + i); + + init_completion(&st->value_ok); + + /* + * The ADC fires continuously and we can't do anything about + * it. So we need to have the IRQ disabled by default, and we + * will enable them back when we will need them.. + */ + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, + nau7802_eoc_trigger, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT | + IRQF_NO_AUTOEN, + client->dev.driver->name, + indio_dev); + if (ret) { + /* + * What may happen here is that our IRQ controller is + * not able to get level interrupt but this is required + * by this ADC as when going over 40 sample per second, + * the interrupt line may stay high between conversions. + * So, we continue no matter what but we switch to + * polling mode. + */ + dev_info(&client->dev, + "Failed to allocate IRQ, using polling mode\n"); + client->irq = 0; + } + } + + if (!client->irq) { + /* + * We are polling, use the fastest sample rate by + * default + */ + st->sample_rate = NAU7802_SAMP_FREQ_320; + ret = i2c_smbus_write_byte_data(st->client, NAU7802_REG_CTRL2, + NAU7802_CTRL2_CRS(st->sample_rate)); + if (ret) + return ret; + } + + /* Setup the ADC channels available on the board */ + indio_dev->num_channels = ARRAY_SIZE(nau7802_chan_array); + indio_dev->channels = nau7802_chan_array; + + mutex_init(&st->lock); + mutex_init(&st->data_lock); + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id nau7802_i2c_id[] = { + { .name = "nau7802" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, nau7802_i2c_id); + +static const struct of_device_id nau7802_dt_ids[] = { + { .compatible = "nuvoton,nau7802" }, + { } +}; +MODULE_DEVICE_TABLE(of, nau7802_dt_ids); + +static struct i2c_driver nau7802_driver = { + .probe = nau7802_probe, + .id_table = nau7802_i2c_id, + .driver = { + .name = "nau7802", + .of_match_table = nau7802_dt_ids, + }, +}; + +module_i2c_driver(nau7802_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Nuvoton NAU7802 ADC Driver"); +MODULE_AUTHOR("Maxime Ripard "); +MODULE_AUTHOR("Alexandre Belloni "); diff --git a/drivers/iio/adc/nct7201.c b/drivers/iio/adc/nct7201.c new file mode 100644 index 000000000..bea88a9b9 --- /dev/null +++ b/drivers/iio/adc/nct7201.c @@ -0,0 +1,500 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Nuvoton nct7201 and nct7202 power monitor chips. + * + * Copyright (c) 2024-2025 Nuvoton Technology corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define NCT7201_REG_INTERRUPT_STATUS 0x0C +#define NCT7201_REG_VOLT_LOW_BYTE 0x0F +#define NCT7201_REG_CONFIGURATION 0x10 +#define NCT7201_BIT_CONFIGURATION_START BIT(0) +#define NCT7201_BIT_CONFIGURATION_ALERT_MSK BIT(1) +#define NCT7201_BIT_CONFIGURATION_CONV_RATE BIT(2) +#define NCT7201_BIT_CONFIGURATION_RESET BIT(7) + +#define NCT7201_REG_ADVANCED_CONFIGURATION 0x11 +#define NCT7201_BIT_ADVANCED_CONF_MOD_ALERT BIT(0) +#define NCT7201_BIT_ADVANCED_CONF_MOD_STS BIT(1) +#define NCT7201_BIT_ADVANCED_CONF_FAULT_QUEUE BIT(2) +#define NCT7201_BIT_ADVANCED_CONF_EN_DEEP_SHUTDOWN BIT(4) +#define NCT7201_BIT_ADVANCED_CONF_EN_SMB_TIMEOUT BIT(5) +#define NCT7201_BIT_ADVANCED_CONF_MOD_RSTIN BIT(7) + +#define NCT7201_REG_CHANNEL_INPUT_MODE 0x12 +#define NCT7201_REG_CHANNEL_ENABLE 0x13 +#define NCT7201_REG_INTERRUPT_MASK_1 0x15 +#define NCT7201_REG_INTERRUPT_MASK_2 0x16 +#define NCT7201_REG_BUSY_STATUS 0x1E +#define NCT7201_BIT_BUSY BIT(0) +#define NCT7201_BIT_PWR_UP BIT(1) +#define NCT7201_REG_ONE_SHOT 0x1F +#define NCT7201_REG_SMUS_ADDRESS 0xFC +#define NCT7201_REG_VIN_MASK GENMASK(15, 3) + +#define NCT7201_REG_VIN(i) (0x00 + i) +#define NCT7201_REG_VIN_HIGH_LIMIT(i) (0x20 + (i) * 2) +#define NCT7201_REG_VIN_LOW_LIMIT(i) (0x21 + (i) * 2) +#define NCT7201_MAX_CHANNEL 12 + +static const struct regmap_range nct7201_read_reg_range[] = { + regmap_reg_range(NCT7201_REG_INTERRUPT_STATUS, NCT7201_REG_BUSY_STATUS), + regmap_reg_range(NCT7201_REG_SMUS_ADDRESS, NCT7201_REG_SMUS_ADDRESS), +}; + +static const struct regmap_access_table nct7201_readable_regs_tbl = { + .yes_ranges = nct7201_read_reg_range, + .n_yes_ranges = ARRAY_SIZE(nct7201_read_reg_range), +}; + +static const struct regmap_range nct7201_write_reg_range[] = { + regmap_reg_range(NCT7201_REG_CONFIGURATION, NCT7201_REG_INTERRUPT_MASK_2), + regmap_reg_range(NCT7201_REG_ONE_SHOT, NCT7201_REG_ONE_SHOT), +}; + +static const struct regmap_access_table nct7201_writeable_regs_tbl = { + .yes_ranges = nct7201_write_reg_range, + .n_yes_ranges = ARRAY_SIZE(nct7201_write_reg_range), +}; + +static const struct regmap_range nct7201_read_vin_reg_range[] = { + regmap_reg_range(NCT7201_REG_VIN(0), NCT7201_REG_VIN(NCT7201_MAX_CHANNEL - 1)), + regmap_reg_range(NCT7201_REG_VIN_HIGH_LIMIT(0), + NCT7201_REG_VIN_LOW_LIMIT(NCT7201_MAX_CHANNEL - 1)), +}; + +static const struct regmap_access_table nct7201_readable_vin_regs_tbl = { + .yes_ranges = nct7201_read_vin_reg_range, + .n_yes_ranges = ARRAY_SIZE(nct7201_read_vin_reg_range), +}; + +static const struct regmap_range nct7201_write_vin_reg_range[] = { + regmap_reg_range(NCT7201_REG_VIN_HIGH_LIMIT(0), + NCT7201_REG_VIN_LOW_LIMIT(NCT7201_MAX_CHANNEL - 1)), +}; + +static const struct regmap_access_table nct7201_writeable_vin_regs_tbl = { + .yes_ranges = nct7201_write_vin_reg_range, + .n_yes_ranges = ARRAY_SIZE(nct7201_write_vin_reg_range), +}; + +static const struct regmap_config nct7201_regmap8_config = { + .name = "vin-data-read-byte", + .reg_bits = 8, + .val_bits = 8, + .use_single_read = true, + .use_single_write = true, + .max_register = 0xff, + .rd_table = &nct7201_readable_regs_tbl, + .wr_table = &nct7201_writeable_regs_tbl, +}; + +static const struct regmap_config nct7201_regmap16_config = { + .name = "vin-data-read-word", + .reg_bits = 8, + .val_bits = 16, + .max_register = 0xff, + .rd_table = &nct7201_readable_vin_regs_tbl, + .wr_table = &nct7201_writeable_vin_regs_tbl, +}; + +struct nct7201_chip_info { + struct regmap *regmap; + struct regmap *regmap16; + int num_vin_channels; + __le16 vin_mask; +}; + +struct nct7201_adc_model_data { + const char *model_name; + const struct iio_chan_spec *channels; + unsigned int num_channels; + int num_vin_channels; +}; + +static const struct iio_event_spec nct7201_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +#define NCT7201_VOLTAGE_CHANNEL(num) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = num + 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .address = num, \ + .event_spec = nct7201_events, \ + .num_event_specs = ARRAY_SIZE(nct7201_events), \ + } + +static const struct iio_chan_spec nct7201_channels[] = { + NCT7201_VOLTAGE_CHANNEL(0), + NCT7201_VOLTAGE_CHANNEL(1), + NCT7201_VOLTAGE_CHANNEL(2), + NCT7201_VOLTAGE_CHANNEL(3), + NCT7201_VOLTAGE_CHANNEL(4), + NCT7201_VOLTAGE_CHANNEL(5), + NCT7201_VOLTAGE_CHANNEL(6), + NCT7201_VOLTAGE_CHANNEL(7), +}; + +static const struct iio_chan_spec nct7202_channels[] = { + NCT7201_VOLTAGE_CHANNEL(0), + NCT7201_VOLTAGE_CHANNEL(1), + NCT7201_VOLTAGE_CHANNEL(2), + NCT7201_VOLTAGE_CHANNEL(3), + NCT7201_VOLTAGE_CHANNEL(4), + NCT7201_VOLTAGE_CHANNEL(5), + NCT7201_VOLTAGE_CHANNEL(6), + NCT7201_VOLTAGE_CHANNEL(7), + NCT7201_VOLTAGE_CHANNEL(8), + NCT7201_VOLTAGE_CHANNEL(9), + NCT7201_VOLTAGE_CHANNEL(10), + NCT7201_VOLTAGE_CHANNEL(11), +}; + +static int nct7201_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct nct7201_chip_info *chip = iio_priv(indio_dev); + unsigned int value; + int err; + + if (chan->type != IIO_VOLTAGE) + return -EOPNOTSUPP; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + err = regmap_read(chip->regmap16, NCT7201_REG_VIN(chan->address), &value); + if (err) + return err; + *val = FIELD_GET(NCT7201_REG_VIN_MASK, value); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* From the datasheet, we have to multiply by 0.0004995 */ + *val = 0; + *val2 = 499500; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static int nct7201_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct nct7201_chip_info *chip = iio_priv(indio_dev); + unsigned int value; + int err; + + if (chan->type != IIO_VOLTAGE) + return -EOPNOTSUPP; + + if (info != IIO_EV_INFO_VALUE) + return -EINVAL; + + if (dir == IIO_EV_DIR_FALLING) + err = regmap_read(chip->regmap16, NCT7201_REG_VIN_LOW_LIMIT(chan->address), + &value); + else + err = regmap_read(chip->regmap16, NCT7201_REG_VIN_HIGH_LIMIT(chan->address), + &value); + if (err) + return err; + + *val = FIELD_GET(NCT7201_REG_VIN_MASK, value); + + return IIO_VAL_INT; +} + +static int nct7201_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct nct7201_chip_info *chip = iio_priv(indio_dev); + int err; + + if (chan->type != IIO_VOLTAGE) + return -EOPNOTSUPP; + + if (info != IIO_EV_INFO_VALUE) + return -EOPNOTSUPP; + + if (dir == IIO_EV_DIR_FALLING) + err = regmap_write(chip->regmap16, NCT7201_REG_VIN_LOW_LIMIT(chan->address), + FIELD_PREP(NCT7201_REG_VIN_MASK, val)); + else + err = regmap_write(chip->regmap16, NCT7201_REG_VIN_HIGH_LIMIT(chan->address), + FIELD_PREP(NCT7201_REG_VIN_MASK, val)); + + return err; +} + +static int nct7201_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct nct7201_chip_info *chip = iio_priv(indio_dev); + + if (chan->type != IIO_VOLTAGE) + return -EOPNOTSUPP; + + return !!(le16_to_cpu(chip->vin_mask) & BIT(chan->address)); +} + +static int nct7201_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct nct7201_chip_info *chip = iio_priv(indio_dev); + __le16 mask = cpu_to_le16(BIT(chan->address)); + int err; + + if (chan->type != IIO_VOLTAGE) + return -EOPNOTSUPP; + + if (state) + chip->vin_mask |= mask; + else + chip->vin_mask &= ~mask; + + if (chip->num_vin_channels <= 8) + err = regmap_write(chip->regmap, NCT7201_REG_CHANNEL_ENABLE, + le16_to_cpu(chip->vin_mask)); + else + err = regmap_bulk_write(chip->regmap, NCT7201_REG_CHANNEL_ENABLE, + &chip->vin_mask, sizeof(chip->vin_mask)); + + return err; +} + +static const struct iio_info nct7201_info = { + .read_raw = nct7201_read_raw, + .read_event_config = nct7201_read_event_config, + .write_event_config = nct7201_write_event_config, + .read_event_value = nct7201_read_event_value, + .write_event_value = nct7201_write_event_value, +}; + +static const struct iio_info nct7201_info_no_irq = { + .read_raw = nct7201_read_raw, +}; + +static const struct nct7201_adc_model_data nct7201_model_data = { + .model_name = "nct7201", + .channels = nct7201_channels, + .num_channels = ARRAY_SIZE(nct7201_channels), + .num_vin_channels = 8, +}; + +static const struct nct7201_adc_model_data nct7202_model_data = { + .model_name = "nct7202", + .channels = nct7202_channels, + .num_channels = ARRAY_SIZE(nct7202_channels), + .num_vin_channels = 12, +}; + +static int nct7201_init_chip(struct nct7201_chip_info *chip) +{ + struct device *dev = regmap_get_device(chip->regmap); + __le16 data = cpu_to_le16(GENMASK(chip->num_vin_channels - 1, 0)); + unsigned int value; + int err; + + err = regmap_write(chip->regmap, NCT7201_REG_CONFIGURATION, + NCT7201_BIT_CONFIGURATION_RESET); + if (err) + return dev_err_probe(dev, err, "Failed to reset chip\n"); + + /* + * After about 25 msecs, the device should be ready and then the power-up + * bit will be set to 1. + */ + fsleep(25 * USEC_PER_MSEC); + + err = regmap_read(chip->regmap, NCT7201_REG_BUSY_STATUS, &value); + if (err) + return dev_err_probe(dev, err, "Failed to read busy status\n"); + if (!(value & NCT7201_BIT_PWR_UP)) + return dev_err_probe(dev, -EIO, "Failed to power up after reset\n"); + + /* Enable Channels */ + if (chip->num_vin_channels <= 8) + err = regmap_write(chip->regmap, NCT7201_REG_CHANNEL_ENABLE, + le16_to_cpu(data)); + else + err = regmap_bulk_write(chip->regmap, NCT7201_REG_CHANNEL_ENABLE, + &data, sizeof(data)); + if (err) + return dev_err_probe(dev, err, "Failed to enable channels\n"); + + err = regmap_bulk_read(chip->regmap, NCT7201_REG_CHANNEL_ENABLE, + &chip->vin_mask, sizeof(chip->vin_mask)); + if (err) + return dev_err_probe(dev, err, + "Failed to read channel enable register\n"); + + /* Start monitoring if needed */ + err = regmap_set_bits(chip->regmap, NCT7201_REG_CONFIGURATION, + NCT7201_BIT_CONFIGURATION_START); + if (err) + return dev_err_probe(dev, err, "Failed to start monitoring\n"); + + return 0; +} + +static irqreturn_t nct7201_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct nct7201_chip_info *chip = iio_priv(indio_dev); + __le16 data; + int err; + + err = regmap_bulk_read(chip->regmap, NCT7201_REG_INTERRUPT_STATUS, + &data, sizeof(data)); + if (err) + return IRQ_NONE; + + if (data) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + + return IRQ_HANDLED; +} + +static int nct7201_probe(struct i2c_client *client) +{ + const struct nct7201_adc_model_data *model_data; + struct device *dev = &client->dev; + struct nct7201_chip_info *chip; + struct iio_dev *indio_dev; + int ret; + + model_data = i2c_get_match_data(client); + if (!model_data) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + chip = iio_priv(indio_dev); + + chip->regmap = devm_regmap_init_i2c(client, &nct7201_regmap8_config); + if (IS_ERR(chip->regmap)) + return dev_err_probe(dev, PTR_ERR(chip->regmap), + "Failed to init regmap\n"); + + chip->regmap16 = devm_regmap_init_i2c(client, &nct7201_regmap16_config); + if (IS_ERR(chip->regmap16)) + return dev_err_probe(dev, PTR_ERR(chip->regmap16), + "Failed to init regmap16\n"); + + chip->num_vin_channels = model_data->num_vin_channels; + + ret = nct7201_init_chip(chip); + if (ret) + return ret; + + indio_dev->name = model_data->model_name; + indio_dev->channels = model_data->channels; + indio_dev->num_channels = model_data->num_channels; + if (client->irq) { + /* Enable alert function */ + ret = regmap_clear_bits(chip->regmap, NCT7201_REG_CONFIGURATION, + NCT7201_BIT_CONFIGURATION_ALERT_MSK); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable alert function\n"); + + ret = devm_request_threaded_irq(dev, client->irq, + NULL, nct7201_irq_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + client->name, indio_dev); + if (ret) + return dev_err_probe(dev, ret, + "Failed to assign interrupt.\n"); + + indio_dev->info = &nct7201_info; + } else { + indio_dev->info = &nct7201_info_no_irq; + } + indio_dev->modes = INDIO_DIRECT_MODE; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct i2c_device_id nct7201_id[] = { + { .name = "nct7201", .driver_data = (kernel_ulong_t)&nct7201_model_data }, + { .name = "nct7202", .driver_data = (kernel_ulong_t)&nct7202_model_data }, + { } +}; +MODULE_DEVICE_TABLE(i2c, nct7201_id); + +static const struct of_device_id nct7201_of_match[] = { + { + .compatible = "nuvoton,nct7201", + .data = &nct7201_model_data, + }, + { + .compatible = "nuvoton,nct7202", + .data = &nct7202_model_data, + }, + { } +}; +MODULE_DEVICE_TABLE(of, nct7201_of_match); + +static struct i2c_driver nct7201_driver = { + .driver = { + .name = "nct7201", + .of_match_table = nct7201_of_match, + }, + .probe = nct7201_probe, + .id_table = nct7201_id, +}; +module_i2c_driver(nct7201_driver); + +MODULE_AUTHOR("Eason Yang "); +MODULE_DESCRIPTION("Nuvoton NCT7201 voltage monitor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/npcm_adc.c b/drivers/iio/adc/npcm_adc.c new file mode 100644 index 000000000..a25c15b38 --- /dev/null +++ b/drivers/iio/adc/npcm_adc.c @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2019 Nuvoton Technology corporation. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct npcm_adc_info { + u32 data_mask; + u32 internal_vref; + u32 res_bits; +}; + +struct npcm_adc { + bool int_status; + u32 adc_sample_hz; + struct device *dev; + void __iomem *regs; + struct clk *adc_clk; + wait_queue_head_t wq; + struct regulator *vref; + struct reset_control *reset; + /* + * Lock to protect the device state during a potential concurrent + * read access from userspace. Reading a raw value requires a sequence + * of register writes, then a wait for a event and finally a register + * read, during which userspace could issue another read request. + * This lock protects a read access from occurring before another one + * has finished. + */ + struct mutex lock; + const struct npcm_adc_info *data; +}; + +/* ADC registers */ +#define NPCM_ADCCON 0x00 +#define NPCM_ADCDATA 0x04 + +/* ADCCON Register Bits */ +#define NPCM_ADCCON_ADC_INT_EN BIT(21) +#define NPCM_ADCCON_REFSEL BIT(19) +#define NPCM_ADCCON_ADC_INT_ST BIT(18) +#define NPCM_ADCCON_ADC_EN BIT(17) +#define NPCM_ADCCON_ADC_RST BIT(16) +#define NPCM_ADCCON_ADC_CONV BIT(13) + +#define NPCM_ADCCON_CH_MASK GENMASK(27, 24) +#define NPCM_ADCCON_CH(x) ((x) << 24) +#define NPCM_ADCCON_DIV_SHIFT 1 +#define NPCM_ADCCON_DIV_MASK GENMASK(8, 1) + +#define NPCM_ADC_ENABLE (NPCM_ADCCON_ADC_EN | NPCM_ADCCON_ADC_INT_EN) + +/* ADC General Definition */ +static const struct npcm_adc_info npxm7xx_adc_info = { + .data_mask = GENMASK(9, 0), + .internal_vref = 2048, + .res_bits = 10, +}; + +static const struct npcm_adc_info npxm8xx_adc_info = { + .data_mask = GENMASK(11, 0), + .internal_vref = 1229, + .res_bits = 12, +}; + +#define NPCM_ADC_CHAN(ch) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = ch, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ +} + +static const struct iio_chan_spec npcm_adc_iio_channels[] = { + NPCM_ADC_CHAN(0), + NPCM_ADC_CHAN(1), + NPCM_ADC_CHAN(2), + NPCM_ADC_CHAN(3), + NPCM_ADC_CHAN(4), + NPCM_ADC_CHAN(5), + NPCM_ADC_CHAN(6), + NPCM_ADC_CHAN(7), +}; + +static irqreturn_t npcm_adc_isr(int irq, void *data) +{ + u32 regtemp; + struct iio_dev *indio_dev = data; + struct npcm_adc *info = iio_priv(indio_dev); + + regtemp = ioread32(info->regs + NPCM_ADCCON); + if (regtemp & NPCM_ADCCON_ADC_INT_ST) { + iowrite32(regtemp, info->regs + NPCM_ADCCON); + wake_up_interruptible(&info->wq); + info->int_status = true; + } + + return IRQ_HANDLED; +} + +static int npcm_adc_read(struct npcm_adc *info, int *val, u8 channel) +{ + int ret; + u32 regtemp; + + /* Select ADC channel */ + regtemp = ioread32(info->regs + NPCM_ADCCON); + regtemp &= ~NPCM_ADCCON_CH_MASK; + info->int_status = false; + iowrite32(regtemp | NPCM_ADCCON_CH(channel) | + NPCM_ADCCON_ADC_CONV, info->regs + NPCM_ADCCON); + + ret = wait_event_interruptible_timeout(info->wq, info->int_status, + msecs_to_jiffies(10)); + if (ret == 0) { + regtemp = ioread32(info->regs + NPCM_ADCCON); + if (regtemp & NPCM_ADCCON_ADC_CONV) { + /* if conversion failed - reset ADC module */ + reset_control_assert(info->reset); + msleep(100); + reset_control_deassert(info->reset); + msleep(100); + + /* Enable ADC and start conversion module */ + iowrite32(NPCM_ADC_ENABLE | NPCM_ADCCON_ADC_CONV, + info->regs + NPCM_ADCCON); + dev_err(info->dev, "RESET ADC Complete\n"); + } + return -ETIMEDOUT; + } + if (ret < 0) + return ret; + + *val = ioread32(info->regs + NPCM_ADCDATA); + *val &= info->data->data_mask; + + return 0; +} + +static int npcm_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int ret; + int vref_uv; + struct npcm_adc *info = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&info->lock); + ret = npcm_adc_read(info, val, chan->channel); + mutex_unlock(&info->lock); + if (ret) { + dev_err(info->dev, "NPCM ADC read failed\n"); + return ret; + } + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (!IS_ERR(info->vref)) { + vref_uv = regulator_get_voltage(info->vref); + *val = vref_uv / 1000; + } else { + *val = info->data->internal_vref; + } + *val2 = info->data->res_bits; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = info->adc_sample_hz; + return IIO_VAL_INT; + default: + return -EINVAL; + } + + return 0; +} + +static const struct iio_info npcm_adc_iio_info = { + .read_raw = &npcm_adc_read_raw, +}; + +static const struct of_device_id npcm_adc_match[] = { + { .compatible = "nuvoton,npcm750-adc", .data = &npxm7xx_adc_info}, + { .compatible = "nuvoton,npcm845-adc", .data = &npxm8xx_adc_info}, + { } +}; +MODULE_DEVICE_TABLE(of, npcm_adc_match); + +static int npcm_adc_probe(struct platform_device *pdev) +{ + int ret; + int irq; + u32 div; + u32 reg_con; + struct npcm_adc *info; + struct iio_dev *indio_dev; + struct device *dev = &pdev->dev; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info)); + if (!indio_dev) + return -ENOMEM; + info = iio_priv(indio_dev); + + info->data = device_get_match_data(dev); + if (!info->data) + return -EINVAL; + + mutex_init(&info->lock); + + info->dev = &pdev->dev; + + info->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(info->regs)) + return PTR_ERR(info->regs); + + info->reset = devm_reset_control_get(&pdev->dev, NULL); + if (IS_ERR(info->reset)) + return PTR_ERR(info->reset); + + info->adc_clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(info->adc_clk)) { + dev_warn(&pdev->dev, "ADC clock failed: can't read clk\n"); + return PTR_ERR(info->adc_clk); + } + + /* calculate ADC clock sample rate */ + reg_con = ioread32(info->regs + NPCM_ADCCON); + div = reg_con & NPCM_ADCCON_DIV_MASK; + div = div >> NPCM_ADCCON_DIV_SHIFT; + info->adc_sample_hz = clk_get_rate(info->adc_clk) / ((div + 1) * 2); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(&pdev->dev, irq, npcm_adc_isr, 0, + "NPCM_ADC", indio_dev); + if (ret < 0) + return ret; + + reg_con = ioread32(info->regs + NPCM_ADCCON); + info->vref = devm_regulator_get_optional(&pdev->dev, "vref"); + if (!IS_ERR(info->vref)) { + ret = regulator_enable(info->vref); + if (ret) { + dev_err(&pdev->dev, "Can't enable ADC reference voltage\n"); + return ret; + } + + iowrite32(reg_con & ~NPCM_ADCCON_REFSEL, + info->regs + NPCM_ADCCON); + } else { + /* + * Any error which is not ENODEV indicates the regulator + * has been specified and so is a failure case. + */ + if (PTR_ERR(info->vref) != -ENODEV) + return PTR_ERR(info->vref); + + /* Use internal reference */ + iowrite32(reg_con | NPCM_ADCCON_REFSEL, + info->regs + NPCM_ADCCON); + } + + init_waitqueue_head(&info->wq); + + reg_con = ioread32(info->regs + NPCM_ADCCON); + reg_con |= NPCM_ADC_ENABLE; + + /* Enable the ADC Module */ + iowrite32(reg_con, info->regs + NPCM_ADCCON); + + /* Start ADC conversion */ + iowrite32(reg_con | NPCM_ADCCON_ADC_CONV, info->regs + NPCM_ADCCON); + + platform_set_drvdata(pdev, indio_dev); + indio_dev->name = dev_name(&pdev->dev); + indio_dev->info = &npcm_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = npcm_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(npcm_adc_iio_channels); + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "Couldn't register the device.\n"); + goto err_iio_register; + } + + pr_info("NPCM ADC driver probed\n"); + + return 0; + +err_iio_register: + iowrite32(reg_con & ~NPCM_ADCCON_ADC_EN, info->regs + NPCM_ADCCON); + if (!IS_ERR(info->vref)) + regulator_disable(info->vref); + + return ret; +} + +static void npcm_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct npcm_adc *info = iio_priv(indio_dev); + u32 regtemp; + + iio_device_unregister(indio_dev); + + regtemp = ioread32(info->regs + NPCM_ADCCON); + iowrite32(regtemp & ~NPCM_ADCCON_ADC_EN, info->regs + NPCM_ADCCON); + if (!IS_ERR(info->vref)) + regulator_disable(info->vref); +} + +static struct platform_driver npcm_adc_driver = { + .probe = npcm_adc_probe, + .remove = npcm_adc_remove, + .driver = { + .name = "npcm_adc", + .of_match_table = npcm_adc_match, + }, +}; + +module_platform_driver(npcm_adc_driver); + +MODULE_DESCRIPTION("Nuvoton NPCM ADC Driver"); +MODULE_AUTHOR("Tomer Maimon "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/nxp-sar-adc.c b/drivers/iio/adc/nxp-sar-adc.c new file mode 100644 index 000000000..894d3211b --- /dev/null +++ b/drivers/iio/adc/nxp-sar-adc.c @@ -0,0 +1,1030 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP SAR-ADC driver (adapted from Freescale Vybrid vf610 ADC driver + * by Fugang Duan ) + * + * Copyright 2013 Freescale Semiconductor, Inc. + * Copyright 2017, 2020-2025 NXP + * Copyright 2025, Linaro Ltd + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* SAR ADC registers. */ +#define NXP_SAR_ADC_CDR(__base, __channel) (((__base) + 0x100) + ((__channel) * 0x4)) + +#define NXP_SAR_ADC_CDR_CDATA_MASK GENMASK(11, 0) +#define NXP_SAR_ADC_CDR_VALID BIT(19) + +/* Main Configuration Register */ +#define NXP_SAR_ADC_MCR(__base) ((__base) + 0x00) + +#define NXP_SAR_ADC_MCR_PWDN BIT(0) +#define NXP_SAR_ADC_MCR_ACKO BIT(5) +#define NXP_SAR_ADC_MCR_ADCLKSEL BIT(8) +#define NXP_SAR_ADC_MCR_TSAMP_MASK GENMASK(10, 9) +#define NXP_SAR_ADC_MCR_NRSMPL_MASK GENMASK(12, 11) +#define NXP_SAR_ADC_MCR_AVGEN BIT(13) +#define NXP_SAR_ADC_MCR_CALSTART BIT(14) +#define NXP_SAR_ADC_MCR_NSTART BIT(24) +#define NXP_SAR_ADC_MCR_MODE BIT(29) +#define NXP_SAR_ADC_MCR_OWREN BIT(31) + +/* Main Status Register */ +#define NXP_SAR_ADC_MSR(__base) ((__base) + 0x04) + +#define NXP_SAR_ADC_MSR_CALBUSY BIT(29) +#define NXP_SAR_ADC_MSR_CALFAIL BIT(30) + +/* Interrupt Status Register */ +#define NXP_SAR_ADC_ISR(__base) ((__base) + 0x10) + +#define NXP_SAR_ADC_ISR_ECH BIT(0) + +/* Channel Pending Register */ +#define NXP_SAR_ADC_CEOCFR0(__base) ((__base) + 0x14) +#define NXP_SAR_ADC_CEOCFR1(__base) ((__base) + 0x18) + +#define NXP_SAR_ADC_EOC_CH(c) BIT(c) + +/* Interrupt Mask Register */ +#define NXP_SAR_ADC_IMR(__base) ((__base) + 0x20) + +/* Channel Interrupt Mask Register */ +#define NXP_SAR_ADC_CIMR0(__base) ((__base) + 0x24) +#define NXP_SAR_ADC_CIMR1(__base) ((__base) + 0x28) + +/* DMA Setting Register */ +#define NXP_SAR_ADC_DMAE(__base) ((__base) + 0x40) + +#define NXP_SAR_ADC_DMAE_DMAEN BIT(0) +#define NXP_SAR_ADC_DMAE_DCLR BIT(1) + +/* DMA Control register */ +#define NXP_SAR_ADC_DMAR0(__base) ((__base) + 0x44) +#define NXP_SAR_ADC_DMAR1(__base) ((__base) + 0x48) + +/* Conversion Timing Register */ +#define NXP_SAR_ADC_CTR0(__base) ((__base) + 0x94) +#define NXP_SAR_ADC_CTR1(__base) ((__base) + 0x98) + +#define NXP_SAR_ADC_CTR_INPSAMP_MIN 0x08 +#define NXP_SAR_ADC_CTR_INPSAMP_MAX 0xff + +/* Normal Conversion Mask Register */ +#define NXP_SAR_ADC_NCMR0(__base) ((__base) + 0xa4) +#define NXP_SAR_ADC_NCMR1(__base) ((__base) + 0xa8) + +/* Normal Conversion Mask Register field define */ +#define NXP_SAR_ADC_CH_MASK GENMASK(7, 0) + +/* Other field define */ +#define NXP_SAR_ADC_CONV_TIMEOUT (msecs_to_jiffies(100)) +#define NXP_SAR_ADC_CAL_TIMEOUT_US (100 * USEC_PER_MSEC) +#define NXP_SAR_ADC_WAIT_US (2 * USEC_PER_MSEC) +#define NXP_SAR_ADC_RESOLUTION 12 + +/* Duration of conversion phases */ +#define NXP_SAR_ADC_TPT 2 +#define NXP_SAR_ADC_DP 2 +#define NXP_SAR_ADC_CT ((NXP_SAR_ADC_RESOLUTION + 2) * 4) +#define NXP_SAR_ADC_CONV_TIME (NXP_SAR_ADC_TPT + NXP_SAR_ADC_CT + NXP_SAR_ADC_DP) + +#define NXP_SAR_ADC_NR_CHANNELS 8 + +#define NXP_PAGE_SIZE SZ_4K +#define NXP_SAR_ADC_DMA_SAMPLE_SZ DMA_SLAVE_BUSWIDTH_4_BYTES +#define NXP_SAR_ADC_DMA_BUFF_SZ (NXP_PAGE_SIZE * NXP_SAR_ADC_DMA_SAMPLE_SZ) +#define NXP_SAR_ADC_DMA_SAMPLE_CNT (NXP_SAR_ADC_DMA_BUFF_SZ / NXP_SAR_ADC_DMA_SAMPLE_SZ) + +struct nxp_sar_adc { + void __iomem *regs; + phys_addr_t regs_phys; + u8 current_channel; + u8 channels_used; + u16 value; + u32 vref_mV; + + /* Save and restore context. */ + u32 inpsamp; + u32 pwdn; + + struct clk *clk; + struct dma_chan *dma_chan; + struct completion completion; + struct circ_buf dma_buf; + + dma_addr_t rx_dma_buf; + dma_cookie_t cookie; + + /* Protect circular buffers access. */ + spinlock_t lock; + + /* Array of enabled channels. */ + u16 buffered_chan[NXP_SAR_ADC_NR_CHANNELS]; + + /* Buffer to be filled by the DMA. */ + IIO_DECLARE_BUFFER_WITH_TS(u16, buffer, NXP_SAR_ADC_NR_CHANNELS); +}; + +struct nxp_sar_adc_data { + u32 vref_mV; + const char *model; +}; + +#define ADC_CHAN(_idx, _chan_type) { \ + .type = (_chan_type), \ + .indexed = 1, \ + .channel = (_idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = (_idx), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 12, \ + .storagebits = 16, \ + }, \ +} + +static const struct iio_chan_spec nxp_sar_adc_iio_channels[] = { + ADC_CHAN(0, IIO_VOLTAGE), + ADC_CHAN(1, IIO_VOLTAGE), + ADC_CHAN(2, IIO_VOLTAGE), + ADC_CHAN(3, IIO_VOLTAGE), + ADC_CHAN(4, IIO_VOLTAGE), + ADC_CHAN(5, IIO_VOLTAGE), + ADC_CHAN(6, IIO_VOLTAGE), + ADC_CHAN(7, IIO_VOLTAGE), + /* + * The NXP SAR ADC documentation marks the channels 8 to 31 as + * "Reserved". Reflect the same in the driver in case new ADC + * variants comes with more channels. + */ + IIO_CHAN_SOFT_TIMESTAMP(32), +}; + +static void nxp_sar_adc_irq_cfg(struct nxp_sar_adc *info, bool enable) +{ + if (enable) + writel(NXP_SAR_ADC_ISR_ECH, NXP_SAR_ADC_IMR(info->regs)); + else + writel(0, NXP_SAR_ADC_IMR(info->regs)); +} + +static void nxp_sar_adc_wait_for(struct nxp_sar_adc *info, u64 cycles) +{ + u64 rate; + + rate = clk_get_rate(info->clk); + if (rate) + ndelay(div64_u64(NSEC_PER_SEC * cycles, rate)); +} + +static bool nxp_sar_adc_set_enabled(struct nxp_sar_adc *info, bool enable) +{ + u32 mcr; + bool pwdn; + + mcr = readl(NXP_SAR_ADC_MCR(info->regs)); + + /* + * Get the current state and return it later. This is used for + * suspend/resume to get the power state + */ + pwdn = FIELD_GET(NXP_SAR_ADC_MCR_PWDN, mcr); + + /* When the enabled flag is not set, we set the power down bit */ + FIELD_MODIFY(NXP_SAR_ADC_MCR_PWDN, &mcr, !enable); + + writel(mcr, NXP_SAR_ADC_MCR(info->regs)); + + /* + * Ensure there are at least three cycles between the + * configuration of NCMR and the setting of NSTART. + */ + if (enable) + nxp_sar_adc_wait_for(info, 3); + + return pwdn; +} + +static inline bool nxp_sar_adc_enable(struct nxp_sar_adc *info) +{ + return nxp_sar_adc_set_enabled(info, true); +} + +static inline bool nxp_sar_adc_disable(struct nxp_sar_adc *info) +{ + return nxp_sar_adc_set_enabled(info, false); +} + +static inline void nxp_sar_adc_calibration_start(void __iomem *base) +{ + u32 mcr = readl(NXP_SAR_ADC_MCR(base)); + + FIELD_MODIFY(NXP_SAR_ADC_MCR_CALSTART, &mcr, 0x1); + + writel(mcr, NXP_SAR_ADC_MCR(base)); +} + +static inline int nxp_sar_adc_calibration_wait(void __iomem *base) +{ + u32 msr; + int ret; + + ret = readl_poll_timeout(NXP_SAR_ADC_MSR(base), msr, + !FIELD_GET(NXP_SAR_ADC_MSR_CALBUSY, msr), + NXP_SAR_ADC_WAIT_US, + NXP_SAR_ADC_CAL_TIMEOUT_US); + if (ret) + return ret; + + if (FIELD_GET(NXP_SAR_ADC_MSR_CALFAIL, msr)) { + /* + * If the calibration fails, the status register bit must be + * cleared. + */ + FIELD_MODIFY(NXP_SAR_ADC_MSR_CALFAIL, &msr, 0x0); + writel(msr, NXP_SAR_ADC_MSR(base)); + + return -EAGAIN; + } + + return 0; +} + +static int nxp_sar_adc_calibration(struct nxp_sar_adc *info) +{ + int ret; + + /* Calibration works only if the ADC is powered up. */ + nxp_sar_adc_enable(info); + + /* The calibration operation starts. */ + nxp_sar_adc_calibration_start(info->regs); + + ret = nxp_sar_adc_calibration_wait(info->regs); + + /* + * Calibration works only if the ADC is powered up. However + * the calibration is called from the probe function where the + * iio is not enabled, so we disable after the calibration. + */ + nxp_sar_adc_disable(info); + + return ret; +} + +static void nxp_sar_adc_conversion_timing_set(struct nxp_sar_adc *info, u32 inpsamp) +{ + inpsamp = clamp(inpsamp, NXP_SAR_ADC_CTR_INPSAMP_MIN, NXP_SAR_ADC_CTR_INPSAMP_MAX); + + writel(inpsamp, NXP_SAR_ADC_CTR0(info->regs)); +} + +static u32 nxp_sar_adc_conversion_timing_get(struct nxp_sar_adc *info) +{ + return readl(NXP_SAR_ADC_CTR0(info->regs)); +} + +static void nxp_sar_adc_read_notify(struct nxp_sar_adc *info) +{ + writel(NXP_SAR_ADC_CH_MASK, NXP_SAR_ADC_CEOCFR0(info->regs)); + writel(NXP_SAR_ADC_CH_MASK, NXP_SAR_ADC_CEOCFR1(info->regs)); +} + +static int nxp_sar_adc_read_data(struct nxp_sar_adc *info, unsigned int chan) +{ + u32 ceocfr, cdr; + + ceocfr = readl(NXP_SAR_ADC_CEOCFR0(info->regs)); + + if (!field_get(NXP_SAR_ADC_EOC_CH(chan), ceocfr)) + return -EIO; + + cdr = readl(NXP_SAR_ADC_CDR(info->regs, chan)); + if (!(FIELD_GET(NXP_SAR_ADC_CDR_VALID, cdr))) + return -EIO; + + return FIELD_GET(NXP_SAR_ADC_CDR_CDATA_MASK, cdr); +} + +static void nxp_sar_adc_isr_buffer(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + unsigned int i; + int ret; + + for (i = 0; i < info->channels_used; i++) { + ret = nxp_sar_adc_read_data(info, info->buffered_chan[i]); + if (ret < 0) { + nxp_sar_adc_read_notify(info); + iio_trigger_notify_done(indio_dev->trig); + return; + } + + info->buffer[i] = ret; + } + + nxp_sar_adc_read_notify(info); + + iio_push_to_buffers_with_ts(indio_dev, info->buffer, sizeof(info->buffer), + iio_get_time_ns(indio_dev)); + + iio_trigger_notify_done(indio_dev->trig); +} + +static void nxp_sar_adc_isr_read_raw(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + int ret; + + ret = nxp_sar_adc_read_data(info, info->current_channel); + nxp_sar_adc_read_notify(info); + if (ret < 0) + return; + + info->value = ret; + complete(&info->completion); +} + +static irqreturn_t nxp_sar_adc_isr(int irq, void *dev_id) +{ + struct iio_dev *indio_dev = dev_id; + struct nxp_sar_adc *info = iio_priv(indio_dev); + int isr; + + isr = readl(NXP_SAR_ADC_ISR(info->regs)); + if (!(FIELD_GET(NXP_SAR_ADC_ISR_ECH, isr))) + return IRQ_NONE; + + if (iio_buffer_enabled(indio_dev)) + nxp_sar_adc_isr_buffer(indio_dev); + else + nxp_sar_adc_isr_read_raw(indio_dev); + + writel(NXP_SAR_ADC_ISR_ECH, NXP_SAR_ADC_ISR(info->regs)); + + return IRQ_HANDLED; +} + +static void nxp_sar_adc_channels_disable(struct nxp_sar_adc *info, u32 mask) +{ + u32 ncmr, cimr; + + ncmr = readl(NXP_SAR_ADC_NCMR0(info->regs)); + cimr = readl(NXP_SAR_ADC_CIMR0(info->regs)); + + /* FIELD_MODIFY() can not be used because the mask is not constant */ + ncmr &= ~mask; + cimr &= ~mask; + + writel(ncmr, NXP_SAR_ADC_NCMR0(info->regs)); + writel(cimr, NXP_SAR_ADC_CIMR0(info->regs)); +} + +static void nxp_sar_adc_channels_enable(struct nxp_sar_adc *info, u32 mask) +{ + u32 ncmr, cimr; + + ncmr = readl(NXP_SAR_ADC_NCMR0(info->regs)); + cimr = readl(NXP_SAR_ADC_CIMR0(info->regs)); + + ncmr |= mask; + cimr |= mask; + + writel(ncmr, NXP_SAR_ADC_NCMR0(info->regs)); + writel(cimr, NXP_SAR_ADC_CIMR0(info->regs)); +} + +static void nxp_sar_adc_dma_channels_enable(struct nxp_sar_adc *info, u32 mask) +{ + u32 dmar; + + dmar = readl(NXP_SAR_ADC_DMAR0(info->regs)); + + dmar |= mask; + + writel(dmar, NXP_SAR_ADC_DMAR0(info->regs)); +} + +static void nxp_sar_adc_dma_channels_disable(struct nxp_sar_adc *info, u32 mask) +{ + u32 dmar; + + dmar = readl(NXP_SAR_ADC_DMAR0(info->regs)); + + dmar &= ~mask; + + writel(dmar, NXP_SAR_ADC_DMAR0(info->regs)); +} + +static void nxp_sar_adc_dma_cfg(struct nxp_sar_adc *info, bool enable) +{ + u32 dmae; + + dmae = readl(NXP_SAR_ADC_DMAE(info->regs)); + + FIELD_MODIFY(NXP_SAR_ADC_DMAE_DMAEN, &dmae, enable); + + writel(dmae, NXP_SAR_ADC_DMAE(info->regs)); +} + +static void nxp_sar_adc_stop_conversion(struct nxp_sar_adc *info) +{ + u32 mcr; + + mcr = readl(NXP_SAR_ADC_MCR(info->regs)); + + FIELD_MODIFY(NXP_SAR_ADC_MCR_NSTART, &mcr, 0x0); + + writel(mcr, NXP_SAR_ADC_MCR(info->regs)); + + /* + * On disable, we have to wait for the transaction to finish. + * ADC does not abort the transaction if a chain conversion is + * in progress. Wait for the worst case scenario - 80 ADC clk + * cycles. The clock rate is 80MHz, this routine is called + * only when the capture finishes. The delay will be very + * short, usec-ish, which is acceptable in the atomic context. + */ + nxp_sar_adc_wait_for(info, 80); +} + +static int nxp_sar_adc_start_conversion(struct nxp_sar_adc *info, bool raw) +{ + u32 mcr; + + mcr = readl(NXP_SAR_ADC_MCR(info->regs)); + + FIELD_MODIFY(NXP_SAR_ADC_MCR_NSTART, &mcr, 0x1); + FIELD_MODIFY(NXP_SAR_ADC_MCR_MODE, &mcr, raw ? 0 : 1); + + writel(mcr, NXP_SAR_ADC_MCR(info->regs)); + + return 0; +} + +static int nxp_sar_adc_read_channel(struct nxp_sar_adc *info, int channel) +{ + int ret; + + info->current_channel = channel; + nxp_sar_adc_channels_enable(info, BIT(channel)); + nxp_sar_adc_irq_cfg(info, true); + nxp_sar_adc_enable(info); + + reinit_completion(&info->completion); + ret = nxp_sar_adc_start_conversion(info, true); + if (ret < 0) + goto out_disable; + + if (!wait_for_completion_interruptible_timeout(&info->completion, + NXP_SAR_ADC_CONV_TIMEOUT)) + ret = -ETIMEDOUT; + + nxp_sar_adc_stop_conversion(info); + +out_disable: + nxp_sar_adc_channels_disable(info, BIT(channel)); + nxp_sar_adc_irq_cfg(info, false); + nxp_sar_adc_disable(info); + + return ret; +} + +static int nxp_sar_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + u32 inpsamp; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = nxp_sar_adc_read_channel(info, chan->channel); + + iio_device_release_direct(indio_dev); + + if (ret) + return ret; + + *val = info->value; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = info->vref_mV; + *val2 = NXP_SAR_ADC_RESOLUTION; + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_SAMP_FREQ: + inpsamp = nxp_sar_adc_conversion_timing_get(info); + *val = clk_get_rate(info->clk) / (inpsamp + NXP_SAR_ADC_CONV_TIME); + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int nxp_sar_adc_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + u32 inpsamp; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (val <= 0) + return -EINVAL; + + /* + * Configures the sample period duration in terms of the SAR + * controller clock. The minimum acceptable value is 8. + * Configuring it to a value lower than 8 sets the sample period + * to 8 cycles. We read the clock value and divide by the + * sampling timing which gives us the number of cycles expected. + * The value is 8-bit wide, consequently the max value is 0xFF. + */ + inpsamp = clk_get_rate(info->clk) / val; + if (inpsamp < NXP_SAR_ADC_CONV_TIME) + return -EINVAL; + + inpsamp -= NXP_SAR_ADC_CONV_TIME; + nxp_sar_adc_conversion_timing_set(info, inpsamp); + return 0; + + default: + return -EINVAL; + } +} + +static void nxp_sar_adc_dma_cb(void *data) +{ + struct iio_dev *indio_dev = data; + struct nxp_sar_adc *info = iio_priv(indio_dev); + struct dma_tx_state state; + struct circ_buf *dma_buf; + struct device *dev_dma; + u32 *dma_samples; + s64 timestamp; + int idx, ret; + + guard(spinlock_irqsave)(&info->lock); + + dma_buf = &info->dma_buf; + dma_samples = (u32 *)dma_buf->buf; + dev_dma = info->dma_chan->device->dev; + + /* + * DMA in some corner cases might have already be charged for + * the next transfer. Potentially there can be a race where + * the residue changes while the dma engine updates the + * buffer. That could be handled by using the + * callback_result() instead of callback() because the residue + * will be passed as a parameter to the function. However this + * new callback is pretty new and the backend does not update + * the residue. So let's stick to the version other drivers do + * which has proven running well in production since several + * years. + */ + dmaengine_tx_status(info->dma_chan, info->cookie, &state); + + dma_sync_single_for_cpu(dev_dma, info->rx_dma_buf, + NXP_SAR_ADC_DMA_BUFF_SZ, DMA_FROM_DEVICE); + + /* Current head position. */ + dma_buf->head = (NXP_SAR_ADC_DMA_BUFF_SZ - state.residue) / + NXP_SAR_ADC_DMA_SAMPLE_SZ; + + /* If everything was transferred, avoid an off by one error. */ + if (!state.residue) + dma_buf->head--; + + /* Something went wrong and nothing transferred. */ + if (state.residue != NXP_SAR_ADC_DMA_BUFF_SZ) { + /* Make sure that head is multiple of info->channels_used. */ + dma_buf->head -= dma_buf->head % info->channels_used; + + /* + * dma_buf->tail != dma_buf->head condition will become false + * because dma_buf->tail will be incremented with 1. + */ + while (dma_buf->tail != dma_buf->head) { + idx = dma_buf->tail % info->channels_used; + info->buffer[idx] = dma_samples[dma_buf->tail]; + dma_buf->tail = (dma_buf->tail + 1) % NXP_SAR_ADC_DMA_SAMPLE_CNT; + if (idx != info->channels_used - 1) + continue; + + /* + * iio_push_to_buffers_with_ts() should not be + * called with dma_samples as parameter. The samples + * will be smashed if timestamp is enabled. + */ + timestamp = iio_get_time_ns(indio_dev); + ret = iio_push_to_buffers_with_ts(indio_dev, info->buffer, + sizeof(info->buffer), + timestamp); + if (ret < 0 && ret != -EBUSY) + dev_err_ratelimited(&indio_dev->dev, + "failed to push iio buffer: %d", + ret); + } + + dma_buf->tail = dma_buf->head; + } + + dma_sync_single_for_device(dev_dma, info->rx_dma_buf, + NXP_SAR_ADC_DMA_BUFF_SZ, DMA_FROM_DEVICE); +} + +static int nxp_sar_adc_start_cyclic_dma(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + struct dma_slave_config config = { }; + struct dma_async_tx_descriptor *desc; + int ret; + + info->dma_buf.head = 0; + info->dma_buf.tail = 0; + + config.direction = DMA_DEV_TO_MEM; + config.src_addr_width = NXP_SAR_ADC_DMA_SAMPLE_SZ; + config.src_addr = NXP_SAR_ADC_CDR(info->regs_phys, info->buffered_chan[0]); + config.src_port_window_size = info->channels_used; + config.src_maxburst = info->channels_used; + ret = dmaengine_slave_config(info->dma_chan, &config); + if (ret < 0) + return ret; + + desc = dmaengine_prep_dma_cyclic(info->dma_chan, + info->rx_dma_buf, + NXP_SAR_ADC_DMA_BUFF_SZ, + NXP_SAR_ADC_DMA_BUFF_SZ / 2, + DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT); + if (!desc) + return -EINVAL; + + desc->callback = nxp_sar_adc_dma_cb; + desc->callback_param = indio_dev; + info->cookie = dmaengine_submit(desc); + ret = dma_submit_error(info->cookie); + if (ret) { + dmaengine_terminate_async(info->dma_chan); + return ret; + } + + dma_async_issue_pending(info->dma_chan); + + return 0; +} + +static void nxp_sar_adc_buffer_software_do_predisable(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + + /* + * The ADC DMAEN bit should be cleared before DMA transaction + * is canceled. + */ + nxp_sar_adc_stop_conversion(info); + dmaengine_terminate_sync(info->dma_chan); + nxp_sar_adc_dma_cfg(info, false); + nxp_sar_adc_dma_channels_disable(info, *indio_dev->active_scan_mask); + + dma_release_channel(info->dma_chan); +} + +static int nxp_sar_adc_buffer_software_do_postenable(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + int ret; + + info->dma_chan = dma_request_chan(indio_dev->dev.parent, "rx"); + if (IS_ERR(info->dma_chan)) + return PTR_ERR(info->dma_chan); + + nxp_sar_adc_dma_channels_enable(info, *indio_dev->active_scan_mask); + + nxp_sar_adc_dma_cfg(info, true); + + ret = nxp_sar_adc_start_cyclic_dma(indio_dev); + if (ret) + goto out_dma_channels_disable; + + ret = nxp_sar_adc_start_conversion(info, false); + if (ret) + goto out_stop_cyclic_dma; + + return 0; + +out_stop_cyclic_dma: + dmaengine_terminate_sync(info->dma_chan); + +out_dma_channels_disable: + nxp_sar_adc_dma_cfg(info, false); + nxp_sar_adc_dma_channels_disable(info, *indio_dev->active_scan_mask); + dma_release_channel(info->dma_chan); + + return ret; +} + +static void nxp_sar_adc_buffer_trigger_do_predisable(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + + nxp_sar_adc_irq_cfg(info, false); +} + +static int nxp_sar_adc_buffer_trigger_do_postenable(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + + nxp_sar_adc_irq_cfg(info, true); + + return 0; +} + +static int nxp_sar_adc_buffer_postenable(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + int current_mode = iio_device_get_current_mode(indio_dev); + unsigned long channel; + int ret; + + info->channels_used = 0; + + /* + * The SAR-ADC has two groups of channels. + * + * - Group #0: + * * bit 0-7 : channel 0 -> channel 7 + * * bit 8-31 : reserved + * + * - Group #32: + * * bit 0-7 : Internal + * * bit 8-31 : reserved + * + * The 8 channels from group #0 are used in this driver for + * ADC as described when declaring the IIO device and the + * mapping is the same. That means the active_scan_mask can be + * used directly to write the channel interrupt mask. + */ + nxp_sar_adc_channels_enable(info, *indio_dev->active_scan_mask); + + for_each_set_bit(channel, indio_dev->active_scan_mask, NXP_SAR_ADC_NR_CHANNELS) + info->buffered_chan[info->channels_used++] = channel; + + nxp_sar_adc_enable(info); + + if (current_mode == INDIO_BUFFER_SOFTWARE) + ret = nxp_sar_adc_buffer_software_do_postenable(indio_dev); + else + ret = nxp_sar_adc_buffer_trigger_do_postenable(indio_dev); + if (ret) + goto out_postenable; + + return 0; + +out_postenable: + nxp_sar_adc_disable(info); + nxp_sar_adc_channels_disable(info, *indio_dev->active_scan_mask); + + return ret; +} + +static int nxp_sar_adc_buffer_predisable(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + int currentmode = iio_device_get_current_mode(indio_dev); + + if (currentmode == INDIO_BUFFER_SOFTWARE) + nxp_sar_adc_buffer_software_do_predisable(indio_dev); + else + nxp_sar_adc_buffer_trigger_do_predisable(indio_dev); + + nxp_sar_adc_disable(info); + + nxp_sar_adc_channels_disable(info, *indio_dev->active_scan_mask); + + return 0; +} + +static irqreturn_t nxp_sar_adc_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct nxp_sar_adc *info = iio_priv(indio_dev); + int ret; + + ret = nxp_sar_adc_start_conversion(info, true); + if (ret < 0) + dev_dbg(&indio_dev->dev, "Failed to start conversion\n"); + + return IRQ_HANDLED; +} + +static const struct iio_buffer_setup_ops iio_triggered_buffer_setup_ops = { + .postenable = nxp_sar_adc_buffer_postenable, + .predisable = nxp_sar_adc_buffer_predisable, +}; + +static const struct iio_info nxp_sar_adc_iio_info = { + .read_raw = nxp_sar_adc_read_raw, + .write_raw = nxp_sar_adc_write_raw, +}; + +static int nxp_sar_adc_dma_probe(struct device *dev, struct nxp_sar_adc *info) +{ + u8 *rx_buf; + + rx_buf = dmam_alloc_coherent(dev, NXP_SAR_ADC_DMA_BUFF_SZ, + &info->rx_dma_buf, GFP_KERNEL); + if (!rx_buf) + return -ENOMEM; + + info->dma_buf.buf = rx_buf; + + return 0; +} + +/* + * The documentation describes the reset values for the registers. + * However some registers do not have these values after a reset. It + * is not a desirable situation. In some other SoC family + * documentation NXP recommends not assuming the default values are + * set and to initialize the registers conforming to the documentation + * reset information to prevent this situation. Assume the same rule + * applies here as there is a discrepancy between what is read from + * the registers at reset time and the documentation. + */ +static void nxp_sar_adc_set_default_values(struct nxp_sar_adc *info) +{ + writel(0x00003901, NXP_SAR_ADC_MCR(info->regs)); + writel(0x00000001, NXP_SAR_ADC_MSR(info->regs)); + writel(0x00000014, NXP_SAR_ADC_CTR0(info->regs)); + writel(0x00000014, NXP_SAR_ADC_CTR1(info->regs)); + writel(0x00000000, NXP_SAR_ADC_CIMR0(info->regs)); + writel(0x00000000, NXP_SAR_ADC_CIMR1(info->regs)); + writel(0x00000000, NXP_SAR_ADC_NCMR0(info->regs)); + writel(0x00000000, NXP_SAR_ADC_NCMR1(info->regs)); +} + +static int nxp_sar_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct nxp_sar_adc_data *data = device_get_match_data(dev); + struct nxp_sar_adc *info; + struct iio_dev *indio_dev; + struct resource *mem; + int irq, ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*info)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + info->vref_mV = data->vref_mV; + spin_lock_init(&info->lock); + info->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &mem); + if (IS_ERR(info->regs)) + return dev_err_probe(dev, PTR_ERR(info->regs), + "Failed to get and remap resource"); + + info->regs_phys = mem->start; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, nxp_sar_adc_isr, 0, dev_name(dev), + indio_dev); + if (ret < 0) + return ret; + + info->clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(info->clk)) + return dev_err_probe(dev, PTR_ERR(info->clk), + "Failed to get the clock\n"); + + platform_set_drvdata(pdev, indio_dev); + + init_completion(&info->completion); + + indio_dev->name = data->model; + indio_dev->info = &nxp_sar_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + indio_dev->channels = nxp_sar_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(nxp_sar_adc_iio_channels); + + nxp_sar_adc_set_default_values(info); + + ret = nxp_sar_adc_calibration(info); + if (ret) + dev_err_probe(dev, ret, "Calibration failed\n"); + + ret = nxp_sar_adc_dma_probe(dev, info); + if (ret) + return dev_err_probe(dev, ret, "Failed to initialize the DMA\n"); + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + &iio_pollfunc_store_time, + &nxp_sar_adc_trigger_handler, + &iio_triggered_buffer_setup_ops); + if (ret < 0) + return dev_err_probe(dev, ret, "Couldn't initialise the buffer\n"); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Couldn't register the device\n"); + + return 0; +} + +static int nxp_sar_adc_suspend(struct device *dev) +{ + struct nxp_sar_adc *info = iio_priv(dev_get_drvdata(dev)); + + info->pwdn = nxp_sar_adc_disable(info); + info->inpsamp = nxp_sar_adc_conversion_timing_get(info); + + clk_disable_unprepare(info->clk); + + return 0; +} + +static int nxp_sar_adc_resume(struct device *dev) +{ + struct nxp_sar_adc *info = iio_priv(dev_get_drvdata(dev)); + int ret; + + ret = clk_prepare_enable(info->clk); + if (ret) + return ret; + + nxp_sar_adc_conversion_timing_set(info, info->inpsamp); + + if (!info->pwdn) + nxp_sar_adc_enable(info); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(nxp_sar_adc_pm_ops, nxp_sar_adc_suspend, + nxp_sar_adc_resume); + +static const struct nxp_sar_adc_data s32g2_sar_adc_data = { + .vref_mV = 1800, + .model = "s32g2-sar-adc", +}; + +static const struct of_device_id nxp_sar_adc_match[] = { + { .compatible = "nxp,s32g2-sar-adc", .data = &s32g2_sar_adc_data }, + { } +}; +MODULE_DEVICE_TABLE(of, nxp_sar_adc_match); + +static struct platform_driver nxp_sar_adc_driver = { + .probe = nxp_sar_adc_probe, + .driver = { + .name = "nxp-sar-adc", + .of_match_table = nxp_sar_adc_match, + .pm = pm_sleep_ptr(&nxp_sar_adc_pm_ops), + }, +}; +module_platform_driver(nxp_sar_adc_driver); + +MODULE_AUTHOR("NXP"); +MODULE_DESCRIPTION("NXP SAR-ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/pac1921.c b/drivers/iio/adc/pac1921.c new file mode 100644 index 000000000..f0bab9446 --- /dev/null +++ b/drivers/iio/adc/pac1921.c @@ -0,0 +1,1345 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * IIO driver for PAC1921 High-Side Power/Current Monitor + * + * Copyright (C) 2024 Matteo Martelli + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* pac1921 registers */ +#define PAC1921_REG_GAIN_CFG 0x00 +#define PAC1921_REG_INT_CFG 0x01 +#define PAC1921_REG_CONTROL 0x02 +#define PAC1921_REG_VBUS 0x10 +#define PAC1921_REG_VSENSE 0x12 +#define PAC1921_REG_OVERFLOW_STS 0x1C +#define PAC1921_REG_VPOWER 0x1D + +/* pac1921 gain configuration bits */ +#define PAC1921_GAIN_DI_GAIN_MASK GENMASK(5, 3) +#define PAC1921_GAIN_DV_GAIN_MASK GENMASK(2, 0) + +/* pac1921 integration configuration bits */ +#define PAC1921_INT_CFG_SMPL_MASK GENMASK(7, 4) +#define PAC1921_INT_CFG_VSFEN BIT(3) +#define PAC1921_INT_CFG_VBFEN BIT(2) +#define PAC1921_INT_CFG_RIOV BIT(1) +#define PAC1921_INT_CFG_INTEN BIT(0) + +/* pac1921 control bits */ +#define PAC1921_CONTROL_MXSL_MASK GENMASK(7, 6) +enum pac1921_mxsl { + PAC1921_MXSL_VPOWER_PIN = 0, + PAC1921_MXSL_VSENSE_FREE_RUN = 1, + PAC1921_MXSL_VBUS_FREE_RUN = 2, + PAC1921_MXSL_VPOWER_FREE_RUN = 3, +}; +#define PAC1921_CONTROL_SLEEP BIT(2) + +/* pac1921 result registers mask and resolution */ +#define PAC1921_RES_MASK GENMASK(15, 6) +#define PAC1921_RES_RESOLUTION 1023 + +/* pac1921 overflow status bits */ +#define PAC1921_OVERFLOW_VSOV BIT(2) +#define PAC1921_OVERFLOW_VBOV BIT(1) +#define PAC1921_OVERFLOW_VPOV BIT(0) + +/* pac1921 constants */ +#define PAC1921_MAX_VSENSE_MV 100 +#define PAC1921_MAX_VBUS_V 32 +/* Time to first communication after power up (tINT_T) */ +#define PAC1921_POWERUP_TIME_MS 20 +/* Time from Sleep State to Start of Integration Period (tSLEEP_TO_INT) */ +#define PAC1921_SLEEP_TO_INT_TIME_US 86 + +/* pac1921 defaults */ +#define PAC1921_DEFAULT_DV_GAIN 0 /* 2^(value): 1x gain (HW default) */ +#define PAC1921_DEFAULT_DI_GAIN 0 /* 2^(value): 1x gain (HW default) */ +#define PAC1921_DEFAULT_NUM_SAMPLES 0 /* 2^(value): 1 sample (HW default) */ + +#define PAC1921_ACPI_GET_uOHMS_VALS 0 +#define PAC1921_ACPI_GET_LABEL 1 + +/* f7bb9932-86ee-4516-a236-7a7a742e55cb */ +static const guid_t pac1921_guid = + GUID_INIT(0xf7bb9932, 0x86ee, 0x4516, 0xa2, + 0x36, 0x7a, 0x7a, 0x74, 0x2e, 0x55, 0xcb); + +/* + * Pre-computed scale factors for BUS voltage + * format: IIO_VAL_INT_PLUS_NANO + * unit: mV + * + * Vbus scale (mV) = max_vbus (mV) / dv_gain / resolution + */ +static const int pac1921_vbus_scales[][2] = { + { 31, 280547409 }, /* dv_gain x1 */ + { 15, 640273704 }, /* dv_gain x2 */ + { 7, 820136852 }, /* dv_gain x4 */ + { 3, 910068426 }, /* dv_gain x8 */ + { 1, 955034213 }, /* dv_gain x16 */ + { 0, 977517106 }, /* dv_gain x32 */ +}; + +/* + * Pre-computed scales for SENSE voltage + * format: IIO_VAL_INT_PLUS_NANO + * unit: mV + * + * Vsense scale (mV) = max_vsense (mV) / di_gain / resolution + */ +static const int pac1921_vsense_scales[][2] = { + { 0, 97751710 }, /* di_gain x1 */ + { 0, 48875855 }, /* di_gain x2 */ + { 0, 24437927 }, /* di_gain x4 */ + { 0, 12218963 }, /* di_gain x8 */ + { 0, 6109481 }, /* di_gain x16 */ + { 0, 3054740 }, /* di_gain x32 */ + { 0, 1527370 }, /* di_gain x64 */ + { 0, 763685 }, /* di_gain x128 */ +}; + +/* + * Numbers of samples used to integrate measurements at the end of an + * integration period. + * + * Changing the number of samples affects the integration period: higher the + * number of samples, longer the integration period. + * + * These correspond to the oversampling ratios available exposed to userspace. + */ +static const int pac1921_int_num_samples[] = { + 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048 +}; + +/* + * The integration period depends on the configuration of number of integration + * samples, measurement resolution and post filters. The following array + * contains integration periods, in microsecs unit, based on table 4-5 from + * datasheet considering power integration mode, 14-Bit resolution and post + * filters on. Each index corresponds to a specific number of samples from 1 + * to 2048. + */ +static const unsigned int pac1921_int_periods_usecs[] = { + 2720, /* 1 sample */ + 4050, /* 2 samples */ + 6790, /* 4 samples */ + 12200, /* 8 samples */ + 23000, /* 16 samples */ + 46000, /* 32 samples */ + 92000, /* 64 samples */ + 184000, /* 128 samples */ + 368000, /* 256 samples */ + 736000, /* 512 samples */ + 1471000, /* 1024 samples */ + 2941000 /* 2048 samples */ +}; + +/* pac1921 regmap configuration */ +static const struct regmap_range pac1921_regmap_wr_ranges[] = { + regmap_reg_range(PAC1921_REG_GAIN_CFG, PAC1921_REG_CONTROL), +}; + +static const struct regmap_access_table pac1921_regmap_wr_table = { + .yes_ranges = pac1921_regmap_wr_ranges, + .n_yes_ranges = ARRAY_SIZE(pac1921_regmap_wr_ranges), +}; + +static const struct regmap_range pac1921_regmap_rd_ranges[] = { + regmap_reg_range(PAC1921_REG_GAIN_CFG, PAC1921_REG_CONTROL), + regmap_reg_range(PAC1921_REG_VBUS, PAC1921_REG_VPOWER + 1), +}; + +static const struct regmap_access_table pac1921_regmap_rd_table = { + .yes_ranges = pac1921_regmap_rd_ranges, + .n_yes_ranges = ARRAY_SIZE(pac1921_regmap_rd_ranges), +}; + +static const struct regmap_config pac1921_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .rd_table = &pac1921_regmap_rd_table, + .wr_table = &pac1921_regmap_wr_table, +}; + +enum pac1921_channels { + PAC1921_CHAN_VBUS = 0, + PAC1921_CHAN_VSENSE = 1, + PAC1921_CHAN_CURRENT = 2, + PAC1921_CHAN_POWER = 3, +}; +#define PAC1921_NUM_MEAS_CHANS 4 + +struct pac1921_priv { + struct i2c_client *client; + struct regmap *regmap; + struct regulator *vdd; + struct iio_info iio_info; + + /* + * Synchronize access to private members, and ensure atomicity of + * consecutive regmap operations. + */ + struct mutex lock; + + u32 rshunt_uohm; /* uOhm */ + u8 dv_gain; + u8 di_gain; + u8 n_samples; + u8 prev_ovf_flags; + u8 ovf_enabled_events; + + bool first_integr_started; + bool first_integr_done; + unsigned long integr_started_time_jiffies; + unsigned int integr_period_usecs; + + int current_scales[ARRAY_SIZE(pac1921_vsense_scales)][2]; + + struct { + u16 chan[PAC1921_NUM_MEAS_CHANS]; + aligned_s64 timestamp; + } scan; +}; + +/* + * Check if first integration after configuration update has completed. + * + * Must be called with lock held. + */ +static bool pac1921_data_ready(struct pac1921_priv *priv) +{ + if (!priv->first_integr_started) + return false; + + if (!priv->first_integr_done) { + unsigned long t_ready; + + /* + * Data valid after the device entered into integration state, + * considering worst case where the device was in sleep state, + * and completed the first integration period. + */ + t_ready = priv->integr_started_time_jiffies + + usecs_to_jiffies(PAC1921_SLEEP_TO_INT_TIME_US) + + usecs_to_jiffies(priv->integr_period_usecs); + + if (time_before(jiffies, t_ready)) + return false; + + priv->first_integr_done = true; + } + + return true; +} + +static inline void pac1921_calc_scale(int dividend, int divisor, int *val, + int *val2) +{ + s64 tmp; + + tmp = div_s64(dividend * (s64)NANO, divisor); + *val = div_s64_rem(tmp, NANO, val2); +} + +/* + * Fill the table of scale factors for current + * format: IIO_VAL_INT_PLUS_NANO + * unit: mA + * + * Vsense LSB (nV) = max_vsense (nV) * di_gain / resolution + * Current scale (mA) = Vsense LSB (nV) / shunt (uOhm) + * + * Must be called with held lock when updating after first initialization. + */ +static void pac1921_calc_current_scales(struct pac1921_priv *priv) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(priv->current_scales); i++) { + int max = (PAC1921_MAX_VSENSE_MV * MICRO) >> i; + int vsense_lsb = DIV_ROUND_CLOSEST(max, PAC1921_RES_RESOLUTION); + + pac1921_calc_scale(vsense_lsb, priv->rshunt_uohm, + &priv->current_scales[i][0], + &priv->current_scales[i][1]); + } +} + +/* + * Check if overflow occurred and if so, push the corresponding events. + * + * Must be called with lock held. + */ +static int pac1921_check_push_overflow(struct iio_dev *indio_dev, s64 timestamp) +{ + struct pac1921_priv *priv = iio_priv(indio_dev); + unsigned int flags; + int ret; + + ret = regmap_read(priv->regmap, PAC1921_REG_OVERFLOW_STS, &flags); + if (ret) + return ret; + + if (flags & PAC1921_OVERFLOW_VBOV && + !(priv->prev_ovf_flags & PAC1921_OVERFLOW_VBOV) && + priv->ovf_enabled_events & PAC1921_OVERFLOW_VBOV) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE( + IIO_VOLTAGE, PAC1921_CHAN_VBUS, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + timestamp); + } + if (flags & PAC1921_OVERFLOW_VSOV && + !(priv->prev_ovf_flags & PAC1921_OVERFLOW_VSOV) && + priv->ovf_enabled_events & PAC1921_OVERFLOW_VSOV) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE( + IIO_VOLTAGE, PAC1921_CHAN_VSENSE, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + timestamp); + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE( + IIO_CURRENT, PAC1921_CHAN_CURRENT, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + timestamp); + } + if (flags & PAC1921_OVERFLOW_VPOV && + !(priv->prev_ovf_flags & PAC1921_OVERFLOW_VPOV) && + priv->ovf_enabled_events & PAC1921_OVERFLOW_VPOV) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE( + IIO_POWER, PAC1921_CHAN_POWER, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + timestamp); + } + + priv->prev_ovf_flags = flags; + + return 0; +} + +/* + * Read the value from a result register + * + * Result registers contain the most recent averaged values of Vbus, Vsense and + * Vpower. Each value is 10 bits wide and spread across two consecutive 8 bit + * registers, with 6 bit LSB zero padding. + */ +static int pac1921_read_res(struct pac1921_priv *priv, unsigned long reg, + u16 *val) +{ + int ret = regmap_bulk_read(priv->regmap, reg, val, sizeof(*val)); + if (ret) + return ret; + + *val = FIELD_GET(PAC1921_RES_MASK, get_unaligned_be16(val)); + + return 0; +} + +static int pac1921_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct pac1921_priv *priv = iio_priv(indio_dev); + + guard(mutex)(&priv->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + s64 ts; + u16 res_val; + int ret; + + if (!pac1921_data_ready(priv)) + return -EBUSY; + + ts = iio_get_time_ns(indio_dev); + + ret = pac1921_check_push_overflow(indio_dev, ts); + if (ret) + return ret; + + ret = pac1921_read_res(priv, chan->address, &res_val); + if (ret) + return ret; + + *val = res_val; + + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->channel) { + case PAC1921_CHAN_VBUS: + *val = pac1921_vbus_scales[priv->dv_gain][0]; + *val2 = pac1921_vbus_scales[priv->dv_gain][1]; + return IIO_VAL_INT_PLUS_NANO; + + case PAC1921_CHAN_VSENSE: + *val = pac1921_vsense_scales[priv->di_gain][0]; + *val2 = pac1921_vsense_scales[priv->di_gain][1]; + return IIO_VAL_INT_PLUS_NANO; + + case PAC1921_CHAN_CURRENT: + *val = priv->current_scales[priv->di_gain][0]; + *val2 = priv->current_scales[priv->di_gain][1]; + return IIO_VAL_INT_PLUS_NANO; + + case PAC1921_CHAN_POWER: { + /* + * Power scale factor in mW: + * Current scale (mA) * max_vbus (V) / dv_gain + */ + + /* Get current scale based on di_gain */ + int *curr_scale = priv->current_scales[priv->di_gain]; + + /* Convert current_scale from INT_PLUS_NANO to INT */ + s64 tmp = curr_scale[0] * (s64)NANO + curr_scale[1]; + + /* Multiply by max_vbus (V) / dv_gain */ + tmp *= PAC1921_MAX_VBUS_V >> priv->dv_gain; + + /* Convert back to INT_PLUS_NANO */ + *val = div_s64_rem(tmp, NANO, val2); + + return IIO_VAL_INT_PLUS_NANO; + } + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = pac1921_int_num_samples[priv->n_samples]; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SAMP_FREQ: + /* + * The sampling frequency (Hz) is read-only and corresponds to + * how often the device provides integrated measurements into + * the result registers, thus it's 1/integration_period. + * The integration period depends on the number of integration + * samples, measurement resolution and post filters. + * + * 1/(integr_period_usecs/MICRO) = MICRO/integr_period_usecs + */ + *val = MICRO; + *val2 = priv->integr_period_usecs; + return IIO_VAL_FRACTIONAL; + + default: + return -EINVAL; + } +} + +static int pac1921_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *type = IIO_VAL_INT; + *vals = pac1921_int_num_samples; + *length = ARRAY_SIZE(pac1921_int_num_samples); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +/* + * Perform configuration update sequence: set the device into read state, then + * write the config register and set the device back into integration state. + * Also reset integration start time and mark first integration to be yet + * completed. + * + * Must be called with lock held. + */ +static int pac1921_update_cfg_reg(struct pac1921_priv *priv, unsigned int reg, + unsigned int mask, unsigned int val) +{ + /* Enter READ state before configuration */ + int ret = regmap_update_bits(priv->regmap, PAC1921_REG_INT_CFG, + PAC1921_INT_CFG_INTEN, 0); + if (ret) + return ret; + + /* Update configuration value */ + ret = regmap_update_bits(priv->regmap, reg, mask, val); + if (ret) + return ret; + + /* Re-enable integration */ + ret = regmap_update_bits(priv->regmap, PAC1921_REG_INT_CFG, + PAC1921_INT_CFG_INTEN, PAC1921_INT_CFG_INTEN); + if (ret) + return ret; + + /* + * Reset integration started time and mark this integration period as + * the first one so that new measurements will be considered as valid + * only at the end of this integration period. + */ + priv->integr_started_time_jiffies = jiffies; + priv->first_integr_done = false; + + return 0; +} + +/* + * Retrieve the index of the given scale (represented by scale_val and + * scale_val2) from scales_tbl. The returned index (if found) is the log2 of + * the gain corresponding to the given scale. + * + * Must be called with lock held if the scales_tbl can change runtime (e.g. for + * the current scales table) + */ +static int pac1921_lookup_scale(const int (*const scales_tbl)[2], size_t size, + int scale_val, int scale_val2) +{ + for (unsigned int i = 0; i < size; i++) + if (scales_tbl[i][0] == scale_val && + scales_tbl[i][1] == scale_val2) + return i; + + return -EINVAL; +} + +/* + * Configure device with the given gain (only if changed) + * + * Must be called with lock held. + */ +static int pac1921_update_gain(struct pac1921_priv *priv, u8 *priv_val, u8 gain, + unsigned int mask) +{ + unsigned int reg_val; + int ret; + + if (*priv_val == gain) + return 0; + + reg_val = (gain << __ffs(mask)) & mask; + ret = pac1921_update_cfg_reg(priv, PAC1921_REG_GAIN_CFG, mask, reg_val); + if (ret) + return ret; + + *priv_val = gain; + + return 0; +} + +/* + * Given a scale factor represented by scale_val and scale_val2 with format + * IIO_VAL_INT_PLUS_NANO, find the corresponding gain value and write it to the + * device. + * + * Must be called with lock held. + */ +static int pac1921_update_gain_from_scale(struct pac1921_priv *priv, + struct iio_chan_spec const *chan, + int scale_val, int scale_val2) +{ + int ret; + + switch (chan->channel) { + case PAC1921_CHAN_VBUS: + ret = pac1921_lookup_scale(pac1921_vbus_scales, + ARRAY_SIZE(pac1921_vbus_scales), + scale_val, scale_val2); + if (ret < 0) + return ret; + + return pac1921_update_gain(priv, &priv->dv_gain, ret, + PAC1921_GAIN_DV_GAIN_MASK); + case PAC1921_CHAN_VSENSE: + ret = pac1921_lookup_scale(pac1921_vsense_scales, + ARRAY_SIZE(pac1921_vsense_scales), + scale_val, scale_val2); + if (ret < 0) + return ret; + + return pac1921_update_gain(priv, &priv->di_gain, ret, + PAC1921_GAIN_DI_GAIN_MASK); + case PAC1921_CHAN_CURRENT: + ret = pac1921_lookup_scale(priv->current_scales, + ARRAY_SIZE(priv->current_scales), + scale_val, scale_val2); + if (ret < 0) + return ret; + + return pac1921_update_gain(priv, &priv->di_gain, ret, + PAC1921_GAIN_DI_GAIN_MASK); + default: + return -EINVAL; + } +} + +/* + * Retrieve the index of the given number of samples from the constant table. + * The returned index (if found) is the log2 of the given num_samples. + */ +static int pac1921_lookup_int_num_samples(int num_samples) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(pac1921_int_num_samples); i++) + if (pac1921_int_num_samples[i] == num_samples) + return i; + + return -EINVAL; +} + +/* + * Update the device with the given number of integration samples. + * + * Must be called with lock held. + */ +static int pac1921_update_int_num_samples(struct pac1921_priv *priv, + int num_samples) +{ + unsigned int reg_val; + u8 n_samples; + int ret; + + ret = pac1921_lookup_int_num_samples(num_samples); + if (ret < 0) + return ret; + + n_samples = ret; + + if (priv->n_samples == n_samples) + return 0; + + reg_val = FIELD_PREP(PAC1921_INT_CFG_SMPL_MASK, n_samples); + + ret = pac1921_update_cfg_reg(priv, PAC1921_REG_INT_CFG, + PAC1921_INT_CFG_SMPL_MASK, reg_val); + if (ret) + return ret; + + priv->n_samples = n_samples; + + priv->integr_period_usecs = pac1921_int_periods_usecs[priv->n_samples]; + + return 0; +} + +static int pac1921_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long info) +{ + switch (info) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int pac1921_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct pac1921_priv *priv = iio_priv(indio_dev); + + guard(mutex)(&priv->lock); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return pac1921_update_gain_from_scale(priv, chan, val, val2); + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return pac1921_update_int_num_samples(priv, val); + default: + return -EINVAL; + } +} + +static int pac1921_read_label(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, char *label) +{ + switch (chan->channel) { + case PAC1921_CHAN_VBUS: + return sysfs_emit(label, "vbus\n"); + case PAC1921_CHAN_VSENSE: + return sysfs_emit(label, "vsense\n"); + case PAC1921_CHAN_CURRENT: + return sysfs_emit(label, "current\n"); + case PAC1921_CHAN_POWER: + return sysfs_emit(label, "power\n"); + default: + return -EINVAL; + } +} + +static int pac1921_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct pac1921_priv *priv = iio_priv(indio_dev); + + guard(mutex)(&priv->lock); + + switch (chan->channel) { + case PAC1921_CHAN_VBUS: + return !!(priv->ovf_enabled_events & PAC1921_OVERFLOW_VBOV); + case PAC1921_CHAN_VSENSE: + case PAC1921_CHAN_CURRENT: + return !!(priv->ovf_enabled_events & PAC1921_OVERFLOW_VSOV); + case PAC1921_CHAN_POWER: + return !!(priv->ovf_enabled_events & PAC1921_OVERFLOW_VPOV); + default: + return -EINVAL; + } +} + +static int pac1921_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct pac1921_priv *priv = iio_priv(indio_dev); + u8 ovf_bit; + + guard(mutex)(&priv->lock); + + switch (chan->channel) { + case PAC1921_CHAN_VBUS: + ovf_bit = PAC1921_OVERFLOW_VBOV; + break; + case PAC1921_CHAN_VSENSE: + case PAC1921_CHAN_CURRENT: + ovf_bit = PAC1921_OVERFLOW_VSOV; + break; + case PAC1921_CHAN_POWER: + ovf_bit = PAC1921_OVERFLOW_VPOV; + break; + default: + return -EINVAL; + } + + if (state) + priv->ovf_enabled_events |= ovf_bit; + else + priv->ovf_enabled_events &= ~ovf_bit; + + return 0; +} + +static int pac1921_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, + int *val2) +{ + switch (info) { + case IIO_EV_INFO_VALUE: + *val = PAC1921_RES_RESOLUTION; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static const struct iio_info pac1921_iio = { + .read_raw = pac1921_read_raw, + .read_avail = pac1921_read_avail, + .write_raw = pac1921_write_raw, + .write_raw_get_fmt = pac1921_write_raw_get_fmt, + .read_label = pac1921_read_label, + .read_event_config = pac1921_read_event_config, + .write_event_config = pac1921_write_event_config, + .read_event_value = pac1921_read_event_value, +}; + +static ssize_t pac1921_read_shunt_resistor(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct pac1921_priv *priv = iio_priv(indio_dev); + int vals[2]; + + if (chan->channel != PAC1921_CHAN_CURRENT) + return -EINVAL; + + guard(mutex)(&priv->lock); + + vals[0] = priv->rshunt_uohm; + vals[1] = MICRO; + + return iio_format_value(buf, IIO_VAL_FRACTIONAL, 1, vals); +} + +static ssize_t pac1921_write_shunt_resistor(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct pac1921_priv *priv = iio_priv(indio_dev); + u32 rshunt_uohm; + int val, val_fract; + int ret; + + if (chan->channel != PAC1921_CHAN_CURRENT) + return -EINVAL; + + ret = iio_str_to_fixpoint(buf, 100000, &val, &val_fract); + if (ret) + return ret; + + /* + * This check validates the shunt is not zero and does not surpass + * INT_MAX. The check is done before calculating in order to avoid + * val * MICRO overflowing. + */ + if ((!val && !val_fract) || val > INT_MAX / MICRO || + (val == INT_MAX / MICRO && val_fract > INT_MAX % MICRO)) + return -EINVAL; + + rshunt_uohm = val * MICRO + val_fract; + + guard(mutex)(&priv->lock); + + priv->rshunt_uohm = rshunt_uohm; + + pac1921_calc_current_scales(priv); + + return len; +} + +/* + * Emit on sysfs the list of available scales contained in scales_tbl + * + * TODO:: this function can be replaced with iio_format_avail_list() if the + * latter will ever be exported. + * + * Must be called with lock held if the scales_tbl can change runtime (e.g. for + * the current scales table) + */ +static ssize_t pac1921_format_scale_avail(const int (*const scales_tbl)[2], + size_t size, char *buf) +{ + ssize_t len = 0; + + for (unsigned int i = 0; i < size; i++) { + if (i != 0) { + len += sysfs_emit_at(buf, len, " "); + if (len >= PAGE_SIZE) + return -EFBIG; + } + len += sysfs_emit_at(buf, len, "%d.%09d", scales_tbl[i][0], + scales_tbl[i][1]); + if (len >= PAGE_SIZE) + return -EFBIG; + } + + len += sysfs_emit_at(buf, len, "\n"); + return len; +} + +/* + * Read available scales for a specific channel + * + * NOTE: using extended info instead of iio.read_avail() because access to + * current scales must be locked as they depend on shunt resistor which may + * change runtime. Caller of iio.read_avail() would access the table unlocked + * instead. + */ +static ssize_t pac1921_read_scale_avail(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct pac1921_priv *priv = iio_priv(indio_dev); + const int (*scales_tbl)[2]; + size_t size; + + switch (chan->channel) { + case PAC1921_CHAN_VBUS: + scales_tbl = pac1921_vbus_scales; + size = ARRAY_SIZE(pac1921_vbus_scales); + return pac1921_format_scale_avail(scales_tbl, size, buf); + + case PAC1921_CHAN_VSENSE: + scales_tbl = pac1921_vsense_scales; + size = ARRAY_SIZE(pac1921_vsense_scales); + return pac1921_format_scale_avail(scales_tbl, size, buf); + + case PAC1921_CHAN_CURRENT: { + guard(mutex)(&priv->lock); + scales_tbl = priv->current_scales; + size = ARRAY_SIZE(priv->current_scales); + return pac1921_format_scale_avail(scales_tbl, size, buf); + } + default: + return -EINVAL; + } +} + +#define PAC1921_EXT_INFO_SCALE_AVAIL { \ + .name = "scale_available", \ + .read = pac1921_read_scale_avail, \ + .shared = IIO_SEPARATE, \ +} + +static const struct iio_chan_spec_ext_info pac1921_ext_info_voltage[] = { + PAC1921_EXT_INFO_SCALE_AVAIL, + { } +}; + +static const struct iio_chan_spec_ext_info pac1921_ext_info_current[] = { + PAC1921_EXT_INFO_SCALE_AVAIL, + { + .name = "shunt_resistor", + .read = pac1921_read_shunt_resistor, + .write = pac1921_write_shunt_resistor, + .shared = IIO_SEPARATE, + }, + { } +}; + +static const struct iio_event_spec pac1921_overflow_event[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE), + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec pac1921_channels[] = { + { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .channel = PAC1921_CHAN_VBUS, + .address = PAC1921_REG_VBUS, + .scan_index = PAC1921_CHAN_VBUS, + .scan_type = { + .sign = 'u', + .realbits = 10, + .storagebits = 16, + .endianness = IIO_CPU + }, + .indexed = 1, + .event_spec = pac1921_overflow_event, + .num_event_specs = ARRAY_SIZE(pac1921_overflow_event), + .ext_info = pac1921_ext_info_voltage, + }, + { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .channel = PAC1921_CHAN_VSENSE, + .address = PAC1921_REG_VSENSE, + .scan_index = PAC1921_CHAN_VSENSE, + .scan_type = { + .sign = 'u', + .realbits = 10, + .storagebits = 16, + .endianness = IIO_CPU + }, + .indexed = 1, + .event_spec = pac1921_overflow_event, + .num_event_specs = ARRAY_SIZE(pac1921_overflow_event), + .ext_info = pac1921_ext_info_voltage, + }, + { + .type = IIO_CURRENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .channel = PAC1921_CHAN_CURRENT, + .address = PAC1921_REG_VSENSE, + .scan_index = PAC1921_CHAN_CURRENT, + .scan_type = { + .sign = 'u', + .realbits = 10, + .storagebits = 16, + .endianness = IIO_CPU + }, + .event_spec = pac1921_overflow_event, + .num_event_specs = ARRAY_SIZE(pac1921_overflow_event), + .ext_info = pac1921_ext_info_current, + }, + { + .type = IIO_POWER, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .channel = PAC1921_CHAN_POWER, + .address = PAC1921_REG_VPOWER, + .scan_index = PAC1921_CHAN_POWER, + .scan_type = { + .sign = 'u', + .realbits = 10, + .storagebits = 16, + .endianness = IIO_CPU + }, + .event_spec = pac1921_overflow_event, + .num_event_specs = ARRAY_SIZE(pac1921_overflow_event), + }, + IIO_CHAN_SOFT_TIMESTAMP(PAC1921_NUM_MEAS_CHANS), +}; + +static irqreturn_t pac1921_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *idev = pf->indio_dev; + struct pac1921_priv *priv = iio_priv(idev); + int ret; + int bit; + int ch = 0; + + guard(mutex)(&priv->lock); + + if (!pac1921_data_ready(priv)) + goto done; + + ret = pac1921_check_push_overflow(idev, pf->timestamp); + if (ret) + goto done; + + iio_for_each_active_channel(idev, bit) { + u16 val; + + ret = pac1921_read_res(priv, idev->channels[bit].address, &val); + if (ret) + goto done; + + priv->scan.chan[ch++] = val; + } + + iio_push_to_buffers_with_ts(idev, &priv->scan, sizeof(priv->scan), + pf->timestamp); + +done: + iio_trigger_notify_done(idev->trig); + + return IRQ_HANDLED; +} + +/* + * Initialize device by writing initial configuration and putting it into + * integration state. + * + * Must be called with lock held when called after first initialization + * (e.g. from pm resume) + */ +static int pac1921_init(struct pac1921_priv *priv) +{ + unsigned int val; + int ret; + + /* Enter READ state before configuration */ + ret = regmap_update_bits(priv->regmap, PAC1921_REG_INT_CFG, + PAC1921_INT_CFG_INTEN, 0); + if (ret) + return ret; + + /* Configure gains, use 14-bits measurement resolution (HW default) */ + val = FIELD_PREP(PAC1921_GAIN_DI_GAIN_MASK, priv->di_gain) | + FIELD_PREP(PAC1921_GAIN_DV_GAIN_MASK, priv->dv_gain); + ret = regmap_write(priv->regmap, PAC1921_REG_GAIN_CFG, val); + if (ret) + return ret; + + /* + * Configure integration: + * - num of integration samples + * - filters enabled (HW default) + * - set READ/INT pin override (RIOV) to control operation mode via + * register instead of pin + */ + val = FIELD_PREP(PAC1921_INT_CFG_SMPL_MASK, priv->n_samples) | + PAC1921_INT_CFG_VSFEN | PAC1921_INT_CFG_VBFEN | + PAC1921_INT_CFG_RIOV; + ret = regmap_write(priv->regmap, PAC1921_REG_INT_CFG, val); + if (ret) + return ret; + + /* + * Init control register: + * - VPower free run integration mode + * - OUT pin full scale range: 3V (HW default) + * - no timeout, no sleep, no sleep override, no recalc (HW defaults) + */ + val = FIELD_PREP(PAC1921_CONTROL_MXSL_MASK, + PAC1921_MXSL_VPOWER_FREE_RUN); + ret = regmap_write(priv->regmap, PAC1921_REG_CONTROL, val); + if (ret) + return ret; + + /* Enable integration */ + ret = regmap_update_bits(priv->regmap, PAC1921_REG_INT_CFG, + PAC1921_INT_CFG_INTEN, PAC1921_INT_CFG_INTEN); + if (ret) + return ret; + + priv->first_integr_started = true; + priv->integr_started_time_jiffies = jiffies; + priv->integr_period_usecs = pac1921_int_periods_usecs[priv->n_samples]; + + return 0; +} + +static int pac1921_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct pac1921_priv *priv = iio_priv(indio_dev); + int ret; + + guard(mutex)(&priv->lock); + + priv->first_integr_started = false; + priv->first_integr_done = false; + + ret = regmap_update_bits(priv->regmap, PAC1921_REG_INT_CFG, + PAC1921_INT_CFG_INTEN, 0); + if (ret) + return ret; + + ret = regmap_update_bits(priv->regmap, PAC1921_REG_CONTROL, + PAC1921_CONTROL_SLEEP, PAC1921_CONTROL_SLEEP); + if (ret) + return ret; + + return regulator_disable(priv->vdd); + +} + +static int pac1921_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct pac1921_priv *priv = iio_priv(indio_dev); + int ret; + + guard(mutex)(&priv->lock); + + ret = regulator_enable(priv->vdd); + if (ret) + return ret; + + msleep(PAC1921_POWERUP_TIME_MS); + + return pac1921_init(priv); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(pac1921_pm_ops, pac1921_suspend, + pac1921_resume); + +static void pac1921_regulator_disable(void *data) +{ + struct regulator *regulator = data; + + regulator_disable(regulator); +} + +/* + * Documentation related to the ACPI device definition + * https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ApplicationNotes/ApplicationNotes/PAC193X-Integration-Notes-for-Microsoft-Windows-10-and-Windows-11-Driver-Support-DS00002534.pdf + */ +static int pac1921_match_acpi_device(struct iio_dev *indio_dev) +{ + acpi_handle handle; + union acpi_object *status; + char *label; + struct pac1921_priv *priv = iio_priv(indio_dev); + struct device *dev = &priv->client->dev; + + handle = ACPI_HANDLE(dev); + + status = acpi_evaluate_dsm(handle, &pac1921_guid, 1, + PAC1921_ACPI_GET_uOHMS_VALS, NULL); + if (!status) + return dev_err_probe(dev, -EINVAL, + "Could not read shunt from ACPI table\n"); + + priv->rshunt_uohm = status->package.elements[0].integer.value; + ACPI_FREE(status); + + status = acpi_evaluate_dsm(handle, &pac1921_guid, 1, + PAC1921_ACPI_GET_LABEL, NULL); + if (!status) + return dev_err_probe(dev, -EINVAL, + "Could not read label from ACPI table\n"); + + label = devm_kstrdup(dev, status->package.elements[0].string.pointer, + GFP_KERNEL); + ACPI_FREE(status); + if (!label) + return -ENOMEM; + + indio_dev->label = label; + + return 0; +} + +static int pac1921_parse_of_fw(struct iio_dev *indio_dev) +{ + int ret; + struct pac1921_priv *priv = iio_priv(indio_dev); + struct device *dev = &priv->client->dev; + + ret = device_property_read_u32(dev, "shunt-resistor-micro-ohms", + &priv->rshunt_uohm); + if (ret) + return dev_err_probe(dev, ret, + "Cannot read shunt resistor property\n"); + + return 0; +} + +static int pac1921_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct pac1921_priv *priv; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + priv = iio_priv(indio_dev); + priv->client = client; + i2c_set_clientdata(client, indio_dev); + + priv->regmap = devm_regmap_init_i2c(client, &pac1921_regmap_config); + if (IS_ERR(priv->regmap)) + return dev_err_probe(dev, PTR_ERR(priv->regmap), + "Cannot initialize register map\n"); + + ret = devm_mutex_init(dev, &priv->lock); + if (ret) + return ret; + + priv->dv_gain = PAC1921_DEFAULT_DV_GAIN; + priv->di_gain = PAC1921_DEFAULT_DI_GAIN; + priv->n_samples = PAC1921_DEFAULT_NUM_SAMPLES; + + if (is_acpi_device_node(dev->fwnode)) + ret = pac1921_match_acpi_device(indio_dev); + else + ret = pac1921_parse_of_fw(indio_dev); + if (ret) + return dev_err_probe(dev, ret, + "Parameter parsing error\n"); + + if (priv->rshunt_uohm == 0 || priv->rshunt_uohm > INT_MAX) + return dev_err_probe(dev, -EINVAL, + "Invalid shunt resistor: %u\n", + priv->rshunt_uohm); + + pac1921_calc_current_scales(priv); + + priv->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(priv->vdd)) + return dev_err_probe(dev, PTR_ERR(priv->vdd), + "Cannot get vdd regulator\n"); + + ret = regulator_enable(priv->vdd); + if (ret) + return dev_err_probe(dev, ret, "Cannot enable vdd regulator\n"); + + ret = devm_add_action_or_reset(dev, pac1921_regulator_disable, + priv->vdd); + if (ret) + return ret; + + msleep(PAC1921_POWERUP_TIME_MS); + + ret = pac1921_init(priv); + if (ret) + return dev_err_probe(dev, ret, "Cannot initialize device\n"); + + priv->iio_info = pac1921_iio; + + indio_dev->name = "pac1921"; + indio_dev->info = &priv->iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = pac1921_channels; + indio_dev->num_channels = ARRAY_SIZE(pac1921_channels); + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + &iio_pollfunc_store_time, + &pac1921_trigger_handler, NULL); + if (ret) + return dev_err_probe(dev, ret, + "Cannot setup IIO triggered buffer\n"); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Cannot register IIO device\n"); + + return 0; +} + +static const struct i2c_device_id pac1921_id[] = { + { .name = "pac1921" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pac1921_id); + +static const struct of_device_id pac1921_of_match[] = { + { .compatible = "microchip,pac1921" }, + { } +}; +MODULE_DEVICE_TABLE(of, pac1921_of_match); + +static const struct acpi_device_id pac1921_acpi_match[] = { + { "MCHP1921" }, + { } +}; +MODULE_DEVICE_TABLE(acpi, pac1921_acpi_match); + +static struct i2c_driver pac1921_driver = { + .driver = { + .name = "pac1921", + .pm = pm_sleep_ptr(&pac1921_pm_ops), + .of_match_table = pac1921_of_match, + .acpi_match_table = pac1921_acpi_match, + }, + .probe = pac1921_probe, + .id_table = pac1921_id, +}; + +module_i2c_driver(pac1921_driver); + +MODULE_AUTHOR("Matteo Martelli "); +MODULE_DESCRIPTION("IIO driver for PAC1921 High-Side Power/Current Monitor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/pac1934.c b/drivers/iio/adc/pac1934.c new file mode 100644 index 000000000..23055405a --- /dev/null +++ b/drivers/iio/adc/pac1934.c @@ -0,0 +1,1632 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * IIO driver for PAC1934 Multi-Channel DC Power/Energy Monitor + * + * Copyright (C) 2017-2024 Microchip Technology Inc. and its subsidiaries + * + * Author: Bogdan Bolocan + * Author: Victor Tudose + * Author: Marius Cristea + * + * Datasheet for PAC1931, PAC1932, PAC1933 and PAC1934 can be found here: + * https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/PAC1931-Family-Data-Sheet-DS20005850E.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * maximum accumulation time should be (17 * 60 * 1000) around 17 minutes@1024 sps + * till PAC1934 accumulation registers starts to saturate + */ +#define PAC1934_MAX_RFSH_LIMIT_MS 60000 +/* 50msec is the timeout for validity of the cached registers */ +#define PAC1934_MIN_POLLING_TIME_MS 50 +/* + * 1000usec is the minimum wait time for normal conversions when sample + * rate doesn't change + */ +#define PAC1934_MIN_UPDATE_WAIT_TIME_US 1000 + +/* 32000mV */ +#define PAC1934_VOLTAGE_MILLIVOLTS_MAX 32000 +/* voltage bits resolution when set for unsigned values */ +#define PAC1934_VOLTAGE_U_RES 16 +/* voltage bits resolution when set for signed values */ +#define PAC1934_VOLTAGE_S_RES 15 + +/* + * max signed value that can be stored on 32 bits and 8 digits fractional value + * (2^31 - 1) * 10^8 + 99999999 + */ +#define PAC_193X_MAX_POWER_ACC 214748364799999999LL +/* + * min signed value that can be stored on 32 bits and 8 digits fractional value + * -(2^31) * 10^8 - 99999999 + */ +#define PAC_193X_MIN_POWER_ACC -214748364899999999LL + +#define PAC1934_MAX_NUM_CHANNELS 4 + +#define PAC1934_MEAS_REG_LEN 76 +#define PAC1934_CTRL_REG_LEN 12 + +#define PAC1934_DEFAULT_CHIP_SAMP_SPEED_HZ 1024 + +/* I2C address map */ +#define PAC1934_REFRESH_REG_ADDR 0x00 +#define PAC1934_CTRL_REG_ADDR 0x01 +#define PAC1934_ACC_COUNT_REG_ADDR 0x02 +#define PAC1934_VPOWER_ACC_1_ADDR 0x03 +#define PAC1934_VPOWER_ACC_2_ADDR 0x04 +#define PAC1934_VPOWER_ACC_3_ADDR 0x05 +#define PAC1934_VPOWER_ACC_4_ADDR 0x06 +#define PAC1934_VBUS_1_ADDR 0x07 +#define PAC1934_VBUS_2_ADDR 0x08 +#define PAC1934_VBUS_3_ADDR 0x09 +#define PAC1934_VBUS_4_ADDR 0x0A +#define PAC1934_VSENSE_1_ADDR 0x0B +#define PAC1934_VSENSE_2_ADDR 0x0C +#define PAC1934_VSENSE_3_ADDR 0x0D +#define PAC1934_VSENSE_4_ADDR 0x0E +#define PAC1934_VBUS_AVG_1_ADDR 0x0F +#define PAC1934_VBUS_AVG_2_ADDR 0x10 +#define PAC1934_VBUS_AVG_3_ADDR 0x11 +#define PAC1934_VBUS_AVG_4_ADDR 0x12 +#define PAC1934_VSENSE_AVG_1_ADDR 0x13 +#define PAC1934_VSENSE_AVG_2_ADDR 0x14 +#define PAC1934_VSENSE_AVG_3_ADDR 0x15 +#define PAC1934_VSENSE_AVG_4_ADDR 0x16 +#define PAC1934_VPOWER_1_ADDR 0x17 +#define PAC1934_VPOWER_2_ADDR 0x18 +#define PAC1934_VPOWER_3_ADDR 0x19 +#define PAC1934_VPOWER_4_ADDR 0x1A +#define PAC1934_REFRESH_V_REG_ADDR 0x1F +#define PAC1934_SLOW_REG_ADDR 0x20 +#define PAC1934_CTRL_STAT_REGS_ADDR 0x1C +#define PAC1934_PID_REG_ADDR 0xFD +#define PAC1934_MID_REG_ADDR 0xFE +#define PAC1934_RID_REG_ADDR 0xFF + +/* PRODUCT ID REGISTER + MANUFACTURER ID REGISTER + REVISION ID REGISTER */ +#define PAC1934_ID_REG_LEN 3 +#define PAC1934_PID_IDX 0 +#define PAC1934_MID_IDX 1 +#define PAC1934_RID_IDX 2 + +#define PAC1934_ACPI_GET_NAMES_AND_MOHMS_VALS 1 +#define PAC1934_ACPI_GET_UOHMS_VALS 2 +#define PAC1934_ACPI_GET_BIPOLAR_SETTINGS 4 +#define PAC1934_ACPI_GET_SAMP 5 + +#define PAC1934_SAMPLE_RATE_SHIFT 6 + +#define PAC1934_VBUS_SENSE_REG_LEN 2 +#define PAC1934_ACC_REG_LEN 3 +#define PAC1934_VPOWER_REG_LEN 4 +#define PAC1934_VPOWER_ACC_REG_LEN 6 +#define PAC1934_MAX_REGISTER_LENGTH 6 + +#define PAC1934_CUSTOM_ATTR_FOR_CHANNEL 1 + +/* + * relative offsets when using multi-byte reads/writes even though these + * bytes are read one after the other, they are not at adjacent memory + * locations within the I2C memory map. The chip can skip some addresses + */ +#define PAC1934_CHANNEL_DIS_REG_OFF 0 +#define PAC1934_NEG_PWR_REG_OFF 1 + +/* + * when reading/writing multiple bytes from offset PAC1934_CHANNEL_DIS_REG_OFF, + * the chip jumps over the 0x1E (REFRESH_G) and 0x1F (REFRESH_V) offsets + */ +#define PAC1934_SLOW_REG_OFF 2 +#define PAC1934_CTRL_ACT_REG_OFF 3 +#define PAC1934_CHANNEL_DIS_ACT_REG_OFF 4 +#define PAC1934_NEG_PWR_ACT_REG_OFF 5 +#define PAC1934_CTRL_LAT_REG_OFF 6 +#define PAC1934_CHANNEL_DIS_LAT_REG_OFF 7 +#define PAC1934_NEG_PWR_LAT_REG_OFF 8 +#define PAC1934_PID_REG_OFF 9 +#define PAC1934_MID_REG_OFF 10 +#define PAC1934_REV_REG_OFF 11 +#define PAC1934_CTRL_STATUS_INFO_LEN 12 + +#define PAC1934_MID 0x5D +#define PAC1931_PID 0x58 +#define PAC1932_PID 0x59 +#define PAC1933_PID 0x5A +#define PAC1934_PID 0x5B + +/* Scale constant = (10^3 * 3.2 * 10^9 / 2^28) for mili Watt-second */ +#define PAC1934_SCALE_CONSTANT 11921 + +#define PAC1934_MAX_VPOWER_RSHIFTED_BY_28B 11921 +#define PAC1934_MAX_VSENSE_RSHIFTED_BY_16B 1525 + +#define PAC1934_DEV_ATTR(name) (&iio_dev_attr_##name.dev_attr.attr) + +#define PAC1934_CRTL_SAMPLE_RATE_MASK GENMASK(7, 6) +#define PAC1934_CHAN_SLEEP_MASK BIT(5) +#define PAC1934_CHAN_SLEEP_SET BIT(5) +#define PAC1934_CHAN_SINGLE_MASK BIT(4) +#define PAC1934_CHAN_SINGLE_SHOT_SET BIT(4) +#define PAC1934_CHAN_ALERT_MASK BIT(3) +#define PAC1934_CHAN_ALERT_EN BIT(3) +#define PAC1934_CHAN_ALERT_CC_MASK BIT(2) +#define PAC1934_CHAN_ALERT_CC_EN BIT(2) +#define PAC1934_CHAN_OVF_ALERT_MASK BIT(1) +#define PAC1934_CHAN_OVF_ALERT_EN BIT(1) +#define PAC1934_CHAN_OVF_MASK BIT(0) + +#define PAC1934_CHAN_DIS_CH1_OFF_MASK BIT(7) +#define PAC1934_CHAN_DIS_CH2_OFF_MASK BIT(6) +#define PAC1934_CHAN_DIS_CH3_OFF_MASK BIT(5) +#define PAC1934_CHAN_DIS_CH4_OFF_MASK BIT(4) +#define PAC1934_SMBUS_TIMEOUT_MASK BIT(3) +#define PAC1934_SMBUS_BYTECOUNT_MASK BIT(2) +#define PAC1934_SMBUS_NO_SKIP_MASK BIT(1) + +#define PAC1934_NEG_PWR_CH1_BIDI_MASK BIT(7) +#define PAC1934_NEG_PWR_CH2_BIDI_MASK BIT(6) +#define PAC1934_NEG_PWR_CH3_BIDI_MASK BIT(5) +#define PAC1934_NEG_PWR_CH4_BIDI_MASK BIT(4) +#define PAC1934_NEG_PWR_CH1_BIDV_MASK BIT(3) +#define PAC1934_NEG_PWR_CH2_BIDV_MASK BIT(2) +#define PAC1934_NEG_PWR_CH3_BIDV_MASK BIT(1) +#define PAC1934_NEG_PWR_CH4_BIDV_MASK BIT(0) + +/* + * Universal Unique Identifier (UUID), + * 033771E0-1705-47B4-9535-D1BBE14D9A09, + * is reserved to Microchip for the PAC1934. + */ +#define PAC1934_DSM_UUID "033771E0-1705-47B4-9535-D1BBE14D9A09" + +enum pac1934_ids { + PAC1931, + PAC1932, + PAC1933, + PAC1934 +}; + +enum pac1934_samps { + PAC1934_SAMP_1024SPS, + PAC1934_SAMP_256SPS, + PAC1934_SAMP_64SPS, + PAC1934_SAMP_8SPS +}; + +/* + * these indexes are exactly describing the element order within a single + * PAC1934 phys channel IIO channel descriptor; see the static const struct + * iio_chan_spec pac1934_single_channel[] declaration + */ +enum pac1934_ch_idx { + PAC1934_CH_ENERGY, + PAC1934_CH_POWER, + PAC1934_CH_VOLTAGE, + PAC1934_CH_CURRENT, + PAC1934_CH_VOLTAGE_AVERAGE, + PAC1934_CH_CURRENT_AVERAGE +}; + +/** + * struct pac1934_features - features of a pac1934 instance + * @phys_channels: number of physical channels supported by the chip + * @name: chip's name + */ +struct pac1934_features { + u8 phys_channels; + const char *name; +}; + +static const unsigned int samp_rate_map_tbl[] = { + [PAC1934_SAMP_1024SPS] = 1024, + [PAC1934_SAMP_256SPS] = 256, + [PAC1934_SAMP_64SPS] = 64, + [PAC1934_SAMP_8SPS] = 8, +}; + +static const struct pac1934_features pac1934_chip_config[] = { + [PAC1931] = { + .phys_channels = 1, + .name = "pac1931", + }, + [PAC1932] = { + .phys_channels = 2, + .name = "pac1932", + }, + [PAC1933] = { + .phys_channels = 3, + .name = "pac1933", + }, + [PAC1934] = { + .phys_channels = 4, + .name = "pac1934", + }, +}; + +/** + * struct reg_data - data from the registers + * @meas_regs: snapshot of raw measurements registers + * @ctrl_regs: snapshot of control registers + * @energy_sec_acc: snapshot of energy values + * @vpower_acc: accumulated vpower values + * @vpower: snapshot of vpower registers + * @vbus: snapshot of vbus registers + * @vbus_avg: averages of vbus registers + * @vsense: snapshot of vsense registers + * @vsense_avg: averages of vsense registers + * @num_enabled_channels: count of how many chip channels are currently enabled + */ +struct reg_data { + u8 meas_regs[PAC1934_MEAS_REG_LEN]; + u8 ctrl_regs[PAC1934_CTRL_REG_LEN]; + s64 energy_sec_acc[PAC1934_MAX_NUM_CHANNELS]; + s64 vpower_acc[PAC1934_MAX_NUM_CHANNELS]; + s32 vpower[PAC1934_MAX_NUM_CHANNELS]; + s32 vbus[PAC1934_MAX_NUM_CHANNELS]; + s32 vbus_avg[PAC1934_MAX_NUM_CHANNELS]; + s32 vsense[PAC1934_MAX_NUM_CHANNELS]; + s32 vsense_avg[PAC1934_MAX_NUM_CHANNELS]; + u8 num_enabled_channels; +}; + +/** + * struct pac1934_chip_info - information about the chip + * @client: the i2c-client attached to the device + * @lock: synchronize access to driver's state members + * @work_chip_rfsh: work queue used for refresh commands + * @phys_channels: phys channels count + * @active_channels: array of values, true means that channel is active + * @enable_energy: array of values, true means that channel energy is measured + * @bi_dir: array of bools, true means that channel is bidirectional + * @chip_variant: chip variant + * @chip_revision: chip revision + * @shunts: shunts + * @chip_reg_data: chip reg data + * @sample_rate_value: sampling frequency + * @labels: table with channels labels + * @iio_info: iio_info + * @tstamp: chip's uptime + */ +struct pac1934_chip_info { + struct i2c_client *client; + struct mutex lock; /* synchronize access to driver's state members */ + struct delayed_work work_chip_rfsh; + u8 phys_channels; + bool active_channels[PAC1934_MAX_NUM_CHANNELS]; + bool enable_energy[PAC1934_MAX_NUM_CHANNELS]; + bool bi_dir[PAC1934_MAX_NUM_CHANNELS]; + u8 chip_variant; + u8 chip_revision; + u32 shunts[PAC1934_MAX_NUM_CHANNELS]; + struct reg_data chip_reg_data; + s32 sample_rate_value; + char *labels[PAC1934_MAX_NUM_CHANNELS]; + struct iio_info iio_info; + unsigned long tstamp; +}; + +#define TO_PAC1934_CHIP_INFO(d) container_of(d, struct pac1934_chip_info, work_chip_rfsh) + +#define PAC1934_VPOWER_ACC_CHANNEL(_index, _si, _address) { \ + .type = IIO_ENERGY, \ + .address = (_address), \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_ENABLE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = (_si), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 48, \ + .storagebits = 64, \ + .endianness = IIO_CPU, \ + } \ +} + +#define PAC1934_VBUS_CHANNEL(_index, _si, _address) { \ + .type = IIO_VOLTAGE, \ + .address = (_address), \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = (_si), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + } \ +} + +#define PAC1934_VBUS_AVG_CHANNEL(_index, _si, _address) { \ + .type = IIO_VOLTAGE, \ + .address = (_address), \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_AVERAGE_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = (_si), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + } \ +} + +#define PAC1934_VSENSE_CHANNEL(_index, _si, _address) { \ + .type = IIO_CURRENT, \ + .address = (_address), \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = (_si), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + } \ +} + +#define PAC1934_VSENSE_AVG_CHANNEL(_index, _si, _address) { \ + .type = IIO_CURRENT, \ + .address = (_address), \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_AVERAGE_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = (_si), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + } \ +} + +#define PAC1934_VPOWER_CHANNEL(_index, _si, _address) { \ + .type = IIO_POWER, \ + .address = (_address), \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = (_si), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 28, \ + .storagebits = 32, \ + .shift = 4, \ + .endianness = IIO_CPU, \ + } \ +} + +static const struct iio_chan_spec pac1934_single_channel[] = { + PAC1934_VPOWER_ACC_CHANNEL(0, 0, PAC1934_VPOWER_ACC_1_ADDR), + PAC1934_VPOWER_CHANNEL(0, 0, PAC1934_VPOWER_1_ADDR), + PAC1934_VBUS_CHANNEL(0, 0, PAC1934_VBUS_1_ADDR), + PAC1934_VSENSE_CHANNEL(0, 0, PAC1934_VSENSE_1_ADDR), + PAC1934_VBUS_AVG_CHANNEL(0, 0, PAC1934_VBUS_AVG_1_ADDR), + PAC1934_VSENSE_AVG_CHANNEL(0, 0, PAC1934_VSENSE_AVG_1_ADDR), +}; + +/* Low-level I2c functions used to transfer up to 76 bytes at once */ +static int pac1934_i2c_read(struct i2c_client *client, u8 reg_addr, + void *databuf, u8 len) +{ + int ret; + struct i2c_msg msgs[2] = { + { + .addr = client->addr, + .len = 1, + .buf = (u8 *)®_addr, + }, + { + .addr = client->addr, + .len = len, + .buf = databuf, + .flags = I2C_M_RD + } + }; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + return ret; + + return 0; +} + +static int pac1934_get_samp_rate_idx(struct pac1934_chip_info *info, + u32 new_samp_rate) +{ + int cnt; + + for (cnt = 0; cnt < ARRAY_SIZE(samp_rate_map_tbl); cnt++) + if (new_samp_rate == samp_rate_map_tbl[cnt]) + return cnt; + + /* not a valid sample rate value */ + return -EINVAL; +} + +static ssize_t pac1934_shunt_value_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct pac1934_chip_info *info = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + return sysfs_emit(buf, "%u\n", info->shunts[this_attr->address]); +} + +static ssize_t pac1934_shunt_value_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct pac1934_chip_info *info = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int sh_val; + + if (kstrtouint(buf, 10, &sh_val)) { + dev_err(dev, "Shunt value is not valid\n"); + return -EINVAL; + } + + scoped_guard(mutex, &info->lock) + info->shunts[this_attr->address] = sh_val; + + return count; +} + +static int pac1934_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, + const int **vals, int *type, int *length, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *type = IIO_VAL_INT; + *vals = samp_rate_map_tbl; + *length = ARRAY_SIZE(samp_rate_map_tbl); + return IIO_AVAIL_LIST; + } + + return -EINVAL; +} + +static int pac1934_send_refresh(struct pac1934_chip_info *info, + u8 refresh_cmd, u32 wait_time) +{ + /* this function only sends REFRESH or REFRESH_V */ + struct i2c_client *client = info->client; + int ret; + u8 bidir_reg; + bool revision_bug = false; + + if (info->chip_revision == 2 || info->chip_revision == 3) { + /* + * chip rev 2 and 3 bug workaround + * see: PAC1934 Family Data Sheet Errata DS80000836A.pdf + */ + revision_bug = true; + + bidir_reg = + FIELD_PREP(PAC1934_NEG_PWR_CH1_BIDI_MASK, info->bi_dir[0]) | + FIELD_PREP(PAC1934_NEG_PWR_CH2_BIDI_MASK, info->bi_dir[1]) | + FIELD_PREP(PAC1934_NEG_PWR_CH3_BIDI_MASK, info->bi_dir[2]) | + FIELD_PREP(PAC1934_NEG_PWR_CH4_BIDI_MASK, info->bi_dir[3]) | + FIELD_PREP(PAC1934_NEG_PWR_CH1_BIDV_MASK, info->bi_dir[0]) | + FIELD_PREP(PAC1934_NEG_PWR_CH2_BIDV_MASK, info->bi_dir[1]) | + FIELD_PREP(PAC1934_NEG_PWR_CH3_BIDV_MASK, info->bi_dir[2]) | + FIELD_PREP(PAC1934_NEG_PWR_CH4_BIDV_MASK, info->bi_dir[3]); + + ret = i2c_smbus_write_byte_data(client, + PAC1934_CTRL_STAT_REGS_ADDR + + PAC1934_NEG_PWR_REG_OFF, + bidir_reg); + if (ret) + return ret; + } + + ret = i2c_smbus_write_byte(client, refresh_cmd); + if (ret) { + dev_err(&client->dev, "%s - cannot send 0x%02X\n", + __func__, refresh_cmd); + return ret; + } + + if (revision_bug) { + /* + * chip rev 2 and 3 bug workaround - write again the same + * register write the updated registers back + */ + ret = i2c_smbus_write_byte_data(client, + PAC1934_CTRL_STAT_REGS_ADDR + + PAC1934_NEG_PWR_REG_OFF, bidir_reg); + if (ret) + return ret; + } + + /* register data retrieval timestamp */ + info->tstamp = jiffies; + + /* wait till the data is available */ + usleep_range(wait_time, wait_time + 100); + + return ret; +} + +static int pac1934_reg_snapshot(struct pac1934_chip_info *info, + bool do_refresh, u8 refresh_cmd, u32 wait_time) +{ + int ret; + struct i2c_client *client = info->client; + u8 samp_shift, ctrl_regs_tmp; + u8 *offset_reg_data_p; + u16 tmp_value; + u32 samp_rate, cnt, tmp; + s64 curr_energy, inc; + u64 tmp_energy; + struct reg_data *reg_data; + + guard(mutex)(&info->lock); + + if (do_refresh) { + ret = pac1934_send_refresh(info, refresh_cmd, wait_time); + if (ret < 0) { + dev_err(&client->dev, + "%s - cannot send refresh\n", + __func__); + return ret; + } + } + + ret = i2c_smbus_read_i2c_block_data(client, PAC1934_CTRL_STAT_REGS_ADDR, + PAC1934_CTRL_REG_LEN, + (u8 *)info->chip_reg_data.ctrl_regs); + if (ret < 0) { + dev_err(&client->dev, + "%s - cannot read ctrl/status registers\n", + __func__); + return ret; + } + + reg_data = &info->chip_reg_data; + + /* read the data registers */ + ret = pac1934_i2c_read(client, PAC1934_ACC_COUNT_REG_ADDR, + (u8 *)reg_data->meas_regs, PAC1934_MEAS_REG_LEN); + if (ret) { + dev_err(&client->dev, + "%s - cannot read ACC_COUNT register: %d:%d\n", + __func__, ret, PAC1934_MEAS_REG_LEN); + return ret; + } + + /* see how much shift is required by the sample rate */ + samp_rate = samp_rate_map_tbl[((reg_data->ctrl_regs[PAC1934_CTRL_LAT_REG_OFF]) >> 6)]; + samp_shift = get_count_order(samp_rate); + + ctrl_regs_tmp = reg_data->ctrl_regs[PAC1934_CHANNEL_DIS_LAT_REG_OFF]; + offset_reg_data_p = ®_data->meas_regs[PAC1934_ACC_REG_LEN]; + + /* start with VPOWER_ACC */ + for (cnt = 0; cnt < info->phys_channels; cnt++) { + /* check if the channel is active, skip all fields if disabled */ + if ((ctrl_regs_tmp << cnt) & 0x80) + continue; + + /* skip if the energy accumulation is disabled */ + if (info->enable_energy[cnt]) { + curr_energy = info->chip_reg_data.energy_sec_acc[cnt]; + + tmp_energy = get_unaligned_be48(offset_reg_data_p); + + if (info->bi_dir[cnt]) + reg_data->vpower_acc[cnt] = sign_extend64(tmp_energy, 47); + else + reg_data->vpower_acc[cnt] = tmp_energy; + + /* + * compute the scaled to 1 second accumulated energy value; + * energy accumulator scaled to 1sec = VPOWER_ACC/2^samp_shift + * the chip's sampling rate is 2^samp_shift samples/sec + */ + inc = (reg_data->vpower_acc[cnt] >> samp_shift); + + /* add the power_acc field */ + curr_energy += inc; + + reg_data->energy_sec_acc[cnt] = clamp(curr_energy, + PAC_193X_MIN_POWER_ACC, + PAC_193X_MAX_POWER_ACC); + } + + offset_reg_data_p += PAC1934_VPOWER_ACC_REG_LEN; + } + + /* continue with VBUS */ + for (cnt = 0; cnt < info->phys_channels; cnt++) { + if ((ctrl_regs_tmp << cnt) & 0x80) + continue; + + tmp_value = get_unaligned_be16(offset_reg_data_p); + + if (info->bi_dir[cnt]) + reg_data->vbus[cnt] = sign_extend32((u32)(tmp_value), 15); + else + reg_data->vbus[cnt] = tmp_value; + + offset_reg_data_p += PAC1934_VBUS_SENSE_REG_LEN; + } + + /* VSENSE */ + for (cnt = 0; cnt < info->phys_channels; cnt++) { + if ((ctrl_regs_tmp << cnt) & 0x80) + continue; + + tmp_value = get_unaligned_be16(offset_reg_data_p); + + if (info->bi_dir[cnt]) + reg_data->vsense[cnt] = sign_extend32((u32)(tmp_value), 15); + else + reg_data->vsense[cnt] = tmp_value; + + offset_reg_data_p += PAC1934_VBUS_SENSE_REG_LEN; + } + + /* VBUS_AVG */ + for (cnt = 0; cnt < info->phys_channels; cnt++) { + if ((ctrl_regs_tmp << cnt) & 0x80) + continue; + + tmp_value = get_unaligned_be16(offset_reg_data_p); + + if (info->bi_dir[cnt]) + reg_data->vbus_avg[cnt] = sign_extend32((u32)(tmp_value), 15); + else + reg_data->vbus_avg[cnt] = tmp_value; + + offset_reg_data_p += PAC1934_VBUS_SENSE_REG_LEN; + } + + /* VSENSE_AVG */ + for (cnt = 0; cnt < info->phys_channels; cnt++) { + if ((ctrl_regs_tmp << cnt) & 0x80) + continue; + + tmp_value = get_unaligned_be16(offset_reg_data_p); + + if (info->bi_dir[cnt]) + reg_data->vsense_avg[cnt] = sign_extend32((u32)(tmp_value), 15); + else + reg_data->vsense_avg[cnt] = tmp_value; + + offset_reg_data_p += PAC1934_VBUS_SENSE_REG_LEN; + } + + /* VPOWER */ + for (cnt = 0; cnt < info->phys_channels; cnt++) { + if ((ctrl_regs_tmp << cnt) & 0x80) + continue; + + tmp = get_unaligned_be32(offset_reg_data_p) >> 4; + + if (info->bi_dir[cnt]) + reg_data->vpower[cnt] = sign_extend32(tmp, 27); + else + reg_data->vpower[cnt] = tmp; + + offset_reg_data_p += PAC1934_VPOWER_REG_LEN; + } + + return 0; +} + +static int pac1934_retrieve_data(struct pac1934_chip_info *info, + u32 wait_time) +{ + int ret = 0; + + /* + * check if the minimal elapsed time has passed and if so, + * re-read the chip, otherwise the cached info is just fine + */ + if (time_after(jiffies, info->tstamp + msecs_to_jiffies(PAC1934_MIN_POLLING_TIME_MS))) { + ret = pac1934_reg_snapshot(info, true, PAC1934_REFRESH_REG_ADDR, + wait_time); + + /* + * Re-schedule the work for the read registers on timeout + * (to prevent chip registers saturation) + */ + mod_delayed_work(system_percpu_wq, &info->work_chip_rfsh, + msecs_to_jiffies(PAC1934_MAX_RFSH_LIMIT_MS)); + } + + return ret; +} + +static int pac1934_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct pac1934_chip_info *info = iio_priv(indio_dev); + s64 curr_energy; + int ret, channel = chan->channel - 1; + + /* + * For AVG the index should be between 5 to 8. + * To calculate PAC1934_CH_VOLTAGE_AVERAGE, + * respectively PAC1934_CH_CURRENT real index, we need + * to remove the added offset (PAC1934_MAX_NUM_CHANNELS). + */ + if (channel >= PAC1934_MAX_NUM_CHANNELS) + channel = channel - PAC1934_MAX_NUM_CHANNELS; + + ret = pac1934_retrieve_data(info, PAC1934_MIN_UPDATE_WAIT_TIME_US); + if (ret < 0) + return ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_VOLTAGE: + *val = info->chip_reg_data.vbus[channel]; + return IIO_VAL_INT; + case IIO_CURRENT: + *val = info->chip_reg_data.vsense[channel]; + return IIO_VAL_INT; + case IIO_POWER: + *val = info->chip_reg_data.vpower[channel]; + return IIO_VAL_INT; + case IIO_ENERGY: + curr_energy = info->chip_reg_data.energy_sec_acc[channel]; + *val = (u32)curr_energy; + *val2 = (u32)(curr_energy >> 32); + return IIO_VAL_INT_64; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_AVERAGE_RAW: + switch (chan->type) { + case IIO_VOLTAGE: + *val = info->chip_reg_data.vbus_avg[channel]; + return IIO_VAL_INT; + case IIO_CURRENT: + *val = info->chip_reg_data.vsense_avg[channel]; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->address) { + /* Voltages - scale for millivolts */ + case PAC1934_VBUS_1_ADDR: + case PAC1934_VBUS_2_ADDR: + case PAC1934_VBUS_3_ADDR: + case PAC1934_VBUS_4_ADDR: + case PAC1934_VBUS_AVG_1_ADDR: + case PAC1934_VBUS_AVG_2_ADDR: + case PAC1934_VBUS_AVG_3_ADDR: + case PAC1934_VBUS_AVG_4_ADDR: + *val = PAC1934_VOLTAGE_MILLIVOLTS_MAX; + if (chan->scan_type.sign == 'u') + *val2 = PAC1934_VOLTAGE_U_RES; + else + *val2 = PAC1934_VOLTAGE_S_RES; + return IIO_VAL_FRACTIONAL_LOG2; + /* + * Currents - scale for mA - depends on the + * channel's shunt value + * (100mV * 1000000) / (2^16 * shunt(uohm)) + */ + case PAC1934_VSENSE_1_ADDR: + case PAC1934_VSENSE_2_ADDR: + case PAC1934_VSENSE_3_ADDR: + case PAC1934_VSENSE_4_ADDR: + case PAC1934_VSENSE_AVG_1_ADDR: + case PAC1934_VSENSE_AVG_2_ADDR: + case PAC1934_VSENSE_AVG_3_ADDR: + case PAC1934_VSENSE_AVG_4_ADDR: + *val = PAC1934_MAX_VSENSE_RSHIFTED_BY_16B; + if (chan->scan_type.sign == 'u') + *val2 = info->shunts[channel]; + else + *val2 = info->shunts[channel] >> 1; + return IIO_VAL_FRACTIONAL; + /* + * Power - uW - it will use the combined scale + * for current and voltage + * current(mA) * voltage(mV) = power (uW) + */ + case PAC1934_VPOWER_1_ADDR: + case PAC1934_VPOWER_2_ADDR: + case PAC1934_VPOWER_3_ADDR: + case PAC1934_VPOWER_4_ADDR: + *val = PAC1934_MAX_VPOWER_RSHIFTED_BY_28B; + if (chan->scan_type.sign == 'u') + *val2 = info->shunts[channel]; + else + *val2 = info->shunts[channel] >> 1; + return IIO_VAL_FRACTIONAL; + case PAC1934_VPOWER_ACC_1_ADDR: + case PAC1934_VPOWER_ACC_2_ADDR: + case PAC1934_VPOWER_ACC_3_ADDR: + case PAC1934_VPOWER_ACC_4_ADDR: + /* + * expresses the 32 bit scale value here compute + * the scale for energy (miliWatt-second or miliJoule) + */ + *val = PAC1934_SCALE_CONSTANT; + + if (chan->scan_type.sign == 'u') + *val2 = info->shunts[channel]; + else + *val2 = info->shunts[channel] >> 1; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + *val = info->sample_rate_value; + return IIO_VAL_INT; + case IIO_CHAN_INFO_ENABLE: + *val = info->enable_energy[channel]; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int pac1934_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct pac1934_chip_info *info = iio_priv(indio_dev); + struct i2c_client *client = info->client; + int ret = -EINVAL; + s32 old_samp_rate; + u8 ctrl_reg; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = pac1934_get_samp_rate_idx(info, val); + if (ret < 0) + return ret; + + /* write the new sampling value and trigger a snapshot(incl refresh) */ + scoped_guard(mutex, &info->lock) { + ctrl_reg = FIELD_PREP(PAC1934_CRTL_SAMPLE_RATE_MASK, ret); + ret = i2c_smbus_write_byte_data(client, PAC1934_CTRL_REG_ADDR, ctrl_reg); + if (ret) { + dev_err(&client->dev, + "%s - can't update sample rate\n", + __func__); + return ret; + } + } + + old_samp_rate = info->sample_rate_value; + info->sample_rate_value = val; + + /* + * now, force a snapshot with refresh - call retrieve + * data in order to update the refresh timer + * alter the timestamp in order to force trigger a + * register snapshot and a timestamp update + */ + info->tstamp -= msecs_to_jiffies(PAC1934_MIN_POLLING_TIME_MS); + ret = pac1934_retrieve_data(info, (1024 / old_samp_rate) * 1000); + if (ret < 0) { + dev_err(&client->dev, + "%s - cannot snapshot ctrl and measurement regs\n", + __func__); + return ret; + } + + return 0; + case IIO_CHAN_INFO_ENABLE: + scoped_guard(mutex, &info->lock) { + info->enable_energy[chan->channel - 1] = val ? true : false; + if (!val) + info->chip_reg_data.energy_sec_acc[chan->channel - 1] = 0; + } + + return 0; + default: + return -EINVAL; + } +} + +static int pac1934_read_label(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, char *label) +{ + struct pac1934_chip_info *info = iio_priv(indio_dev); + + switch (chan->address) { + case PAC1934_VBUS_1_ADDR: + case PAC1934_VBUS_2_ADDR: + case PAC1934_VBUS_3_ADDR: + case PAC1934_VBUS_4_ADDR: + return sysfs_emit(label, "%s_VBUS_%d\n", + info->labels[chan->scan_index], + chan->scan_index + 1); + case PAC1934_VBUS_AVG_1_ADDR: + case PAC1934_VBUS_AVG_2_ADDR: + case PAC1934_VBUS_AVG_3_ADDR: + case PAC1934_VBUS_AVG_4_ADDR: + return sysfs_emit(label, "%s_VBUS_AVG_%d\n", + info->labels[chan->scan_index], + chan->scan_index + 1); + case PAC1934_VSENSE_1_ADDR: + case PAC1934_VSENSE_2_ADDR: + case PAC1934_VSENSE_3_ADDR: + case PAC1934_VSENSE_4_ADDR: + return sysfs_emit(label, "%s_IBUS_%d\n", + info->labels[chan->scan_index], + chan->scan_index + 1); + case PAC1934_VSENSE_AVG_1_ADDR: + case PAC1934_VSENSE_AVG_2_ADDR: + case PAC1934_VSENSE_AVG_3_ADDR: + case PAC1934_VSENSE_AVG_4_ADDR: + return sysfs_emit(label, "%s_IBUS_AVG_%d\n", + info->labels[chan->scan_index], + chan->scan_index + 1); + case PAC1934_VPOWER_1_ADDR: + case PAC1934_VPOWER_2_ADDR: + case PAC1934_VPOWER_3_ADDR: + case PAC1934_VPOWER_4_ADDR: + return sysfs_emit(label, "%s_POWER_%d\n", + info->labels[chan->scan_index], + chan->scan_index + 1); + case PAC1934_VPOWER_ACC_1_ADDR: + case PAC1934_VPOWER_ACC_2_ADDR: + case PAC1934_VPOWER_ACC_3_ADDR: + case PAC1934_VPOWER_ACC_4_ADDR: + return sysfs_emit(label, "%s_ENERGY_%d\n", + info->labels[chan->scan_index], + chan->scan_index + 1); + } + + return 0; +} + +static void pac1934_work_periodic_rfsh(struct work_struct *work) +{ + struct pac1934_chip_info *info = TO_PAC1934_CHIP_INFO((struct delayed_work *)work); + struct device *dev = &info->client->dev; + + dev_dbg(dev, "%s - Periodic refresh\n", __func__); + + /* do a REFRESH, then read */ + pac1934_reg_snapshot(info, true, PAC1934_REFRESH_REG_ADDR, + PAC1934_MIN_UPDATE_WAIT_TIME_US); + + schedule_delayed_work(&info->work_chip_rfsh, + msecs_to_jiffies(PAC1934_MAX_RFSH_LIMIT_MS)); +} + +static int pac1934_read_revision(struct pac1934_chip_info *info, u8 *buf) +{ + int ret; + struct i2c_client *client = info->client; + + ret = i2c_smbus_read_i2c_block_data(client, PAC1934_PID_REG_ADDR, + PAC1934_ID_REG_LEN, + buf); + if (ret < 0) { + dev_err(&client->dev, "cannot read revision\n"); + return ret; + } + + return 0; +} + +static int pac1934_chip_identify(struct pac1934_chip_info *info) +{ + u8 rev_info[PAC1934_ID_REG_LEN]; + struct device *dev = &info->client->dev; + int ret = 0; + + ret = pac1934_read_revision(info, (u8 *)rev_info); + if (ret) + return ret; + + info->chip_variant = rev_info[PAC1934_PID_IDX]; + info->chip_revision = rev_info[PAC1934_RID_IDX]; + + dev_dbg(dev, "Chip variant: 0x%02X\n", info->chip_variant); + dev_dbg(dev, "Chip revision: 0x%02X\n", info->chip_revision); + + switch (info->chip_variant) { + case PAC1934_PID: + return PAC1934; + case PAC1933_PID: + return PAC1933; + case PAC1932_PID: + return PAC1932; + case PAC1931_PID: + return PAC1931; + default: + return -EINVAL; + } +} + +/* + * documentation related to the ACPI device definition + * https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ApplicationNotes/ApplicationNotes/PAC193X-Integration-Notes-for-Microsoft-Windows-10-and-Windows-11-Driver-Support-DS00002534.pdf + */ +static int pac1934_acpi_parse_channel_config(struct i2c_client *client, + struct pac1934_chip_info *info) +{ + acpi_handle handle; + union acpi_object *rez; + struct device *dev = &client->dev; + unsigned short bi_dir_mask; + int idx, i; + guid_t guid; + + handle = ACPI_HANDLE(dev); + + guid_parse(PAC1934_DSM_UUID, &guid); + + rez = acpi_evaluate_dsm(handle, &guid, 0, PAC1934_ACPI_GET_NAMES_AND_MOHMS_VALS, NULL); + if (!rez) + return -EINVAL; + + for (i = 0; i < rez->package.count; i += 2) { + idx = i / 2; + info->labels[idx] = + devm_kmemdup(dev, rez->package.elements[i].string.pointer, + (size_t)rez->package.elements[i].string.length + 1, + GFP_KERNEL); + info->labels[idx][rez->package.elements[i].string.length] = '\0'; + info->shunts[idx] = rez->package.elements[i + 1].integer.value * 1000; + info->active_channels[idx] = (info->shunts[idx] != 0); + } + + ACPI_FREE(rez); + + rez = acpi_evaluate_dsm(handle, &guid, 1, PAC1934_ACPI_GET_UOHMS_VALS, NULL); + if (!rez) { + /* + * initializing with default values + * we assume all channels are unidirectional(the mask is zero) + * and assign the default sampling rate + */ + info->sample_rate_value = PAC1934_DEFAULT_CHIP_SAMP_SPEED_HZ; + return 0; + } + + for (i = 0; i < rez->package.count; i++) { + idx = i; + info->shunts[idx] = rez->package.elements[i].integer.value; + info->active_channels[idx] = (info->shunts[idx] != 0); + } + + ACPI_FREE(rez); + + rez = acpi_evaluate_dsm(handle, &guid, 1, PAC1934_ACPI_GET_BIPOLAR_SETTINGS, NULL); + if (!rez) + return -EINVAL; + + bi_dir_mask = rez->package.elements[0].integer.value; + info->bi_dir[0] = ((bi_dir_mask & (1 << 3)) | (bi_dir_mask & (1 << 7))) != 0; + info->bi_dir[1] = ((bi_dir_mask & (1 << 2)) | (bi_dir_mask & (1 << 6))) != 0; + info->bi_dir[2] = ((bi_dir_mask & (1 << 1)) | (bi_dir_mask & (1 << 5))) != 0; + info->bi_dir[3] = ((bi_dir_mask & (1 << 0)) | (bi_dir_mask & (1 << 4))) != 0; + + ACPI_FREE(rez); + + rez = acpi_evaluate_dsm(handle, &guid, 1, PAC1934_ACPI_GET_SAMP, NULL); + if (!rez) + return -EINVAL; + + info->sample_rate_value = rez->package.elements[0].integer.value; + + ACPI_FREE(rez); + + return 0; +} + +static int pac1934_fw_parse_channel_config(struct i2c_client *client, + struct pac1934_chip_info *info) +{ + struct device *dev = &client->dev; + unsigned int current_channel; + int idx, ret; + + info->sample_rate_value = 1024; + current_channel = 1; + + device_for_each_child_node_scoped(dev, node) { + ret = fwnode_property_read_u32(node, "reg", &idx); + if (ret) + return dev_err_probe(dev, ret, + "reading invalid channel index\n"); + + /* adjust idx to match channel index (1 to 4) from the datasheet */ + idx--; + + if (current_channel >= (info->phys_channels + 1) || + idx >= info->phys_channels || idx < 0) + return dev_err_probe(dev, -EINVAL, + "%s: invalid channel_index %d value\n", + fwnode_get_name(node), idx); + + /* enable channel */ + info->active_channels[idx] = true; + + ret = fwnode_property_read_u32(node, "shunt-resistor-micro-ohms", + &info->shunts[idx]); + if (ret) + return dev_err_probe(dev, ret, + "%s: invalid shunt-resistor value: %d\n", + fwnode_get_name(node), info->shunts[idx]); + + if (fwnode_property_present(node, "label")) { + ret = fwnode_property_read_string(node, "label", + (const char **)&info->labels[idx]); + if (ret) + return dev_err_probe(dev, ret, + "%s: invalid rail-name value\n", + fwnode_get_name(node)); + } + + info->bi_dir[idx] = fwnode_property_read_bool(node, "bipolar"); + + current_channel++; + } + + return 0; +} + +static void pac1934_cancel_delayed_work(void *dwork) +{ + cancel_delayed_work_sync(dwork); +} + +static int pac1934_chip_configure(struct pac1934_chip_info *info) +{ + int cnt, ret; + struct i2c_client *client = info->client; + u8 regs[PAC1934_CTRL_STATUS_INFO_LEN], idx, ctrl_reg; + u32 wait_time; + + info->chip_reg_data.num_enabled_channels = 0; + for (cnt = 0; cnt < info->phys_channels; cnt++) { + if (info->active_channels[cnt]) + info->chip_reg_data.num_enabled_channels++; + } + + /* + * read whatever information was gathered before the driver was loaded + * establish which channels are enabled/disabled and then establish the + * information retrieval mode (using SKIP or no). + * Read the chip ID values + */ + ret = i2c_smbus_read_i2c_block_data(client, PAC1934_CTRL_STAT_REGS_ADDR, + ARRAY_SIZE(regs), + (u8 *)regs); + if (ret < 0) { + dev_err_probe(&client->dev, ret, + "%s - cannot read regs from 0x%02X\n", + __func__, PAC1934_CTRL_STAT_REGS_ADDR); + return ret; + } + + /* write the CHANNEL_DIS and the NEG_PWR registers */ + regs[PAC1934_CHANNEL_DIS_REG_OFF] = + FIELD_PREP(PAC1934_CHAN_DIS_CH1_OFF_MASK, info->active_channels[0] ? 0 : 1) | + FIELD_PREP(PAC1934_CHAN_DIS_CH2_OFF_MASK, info->active_channels[1] ? 0 : 1) | + FIELD_PREP(PAC1934_CHAN_DIS_CH3_OFF_MASK, info->active_channels[2] ? 0 : 1) | + FIELD_PREP(PAC1934_CHAN_DIS_CH4_OFF_MASK, info->active_channels[3] ? 0 : 1) | + FIELD_PREP(PAC1934_SMBUS_TIMEOUT_MASK, 0) | + FIELD_PREP(PAC1934_SMBUS_BYTECOUNT_MASK, 0) | + FIELD_PREP(PAC1934_SMBUS_NO_SKIP_MASK, 0); + + regs[PAC1934_NEG_PWR_REG_OFF] = + FIELD_PREP(PAC1934_NEG_PWR_CH1_BIDI_MASK, info->bi_dir[0]) | + FIELD_PREP(PAC1934_NEG_PWR_CH2_BIDI_MASK, info->bi_dir[1]) | + FIELD_PREP(PAC1934_NEG_PWR_CH3_BIDI_MASK, info->bi_dir[2]) | + FIELD_PREP(PAC1934_NEG_PWR_CH4_BIDI_MASK, info->bi_dir[3]) | + FIELD_PREP(PAC1934_NEG_PWR_CH1_BIDV_MASK, info->bi_dir[0]) | + FIELD_PREP(PAC1934_NEG_PWR_CH2_BIDV_MASK, info->bi_dir[1]) | + FIELD_PREP(PAC1934_NEG_PWR_CH3_BIDV_MASK, info->bi_dir[2]) | + FIELD_PREP(PAC1934_NEG_PWR_CH4_BIDV_MASK, info->bi_dir[3]); + + /* no SLOW triggered REFRESH, clear POR */ + regs[PAC1934_SLOW_REG_OFF] = 0; + + /* + * Write the three bytes sequentially, as the device does not support + * block write. + */ + ret = i2c_smbus_write_byte_data(client, PAC1934_CTRL_STAT_REGS_ADDR, + regs[PAC1934_CHANNEL_DIS_REG_OFF]); + if (ret) + return ret; + + ret = i2c_smbus_write_byte_data(client, + PAC1934_CTRL_STAT_REGS_ADDR + PAC1934_NEG_PWR_REG_OFF, + regs[PAC1934_NEG_PWR_REG_OFF]); + if (ret) + return ret; + + ret = i2c_smbus_write_byte_data(client, PAC1934_SLOW_REG_ADDR, + regs[PAC1934_SLOW_REG_OFF]); + if (ret) + return ret; + + /* Default sampling rate */ + ctrl_reg = FIELD_PREP(PAC1934_CRTL_SAMPLE_RATE_MASK, PAC1934_SAMP_1024SPS); + + ret = i2c_smbus_write_byte_data(client, PAC1934_CTRL_REG_ADDR, ctrl_reg); + if (ret) + return ret; + + /* + * send a REFRESH to the chip, so the new settings take place + * as well as resetting the accumulators + */ + ret = i2c_smbus_write_byte(client, PAC1934_REFRESH_REG_ADDR); + if (ret) { + dev_err(&client->dev, + "%s - cannot send 0x%02X\n", + __func__, PAC1934_REFRESH_REG_ADDR); + return ret; + } + + /* + * get the current(in the chip) sampling speed and compute the + * required timeout based on its value + * the timeout is 1/sampling_speed + */ + idx = regs[PAC1934_CTRL_ACT_REG_OFF] >> PAC1934_SAMPLE_RATE_SHIFT; + wait_time = (1024 / samp_rate_map_tbl[idx]) * 1000; + + /* + * wait the maximum amount of time to be on the safe side + * the maximum wait time is for 8sps + */ + usleep_range(wait_time, wait_time + 100); + + INIT_DELAYED_WORK(&info->work_chip_rfsh, pac1934_work_periodic_rfsh); + /* Setup the latest moment for reading the regs before saturation */ + schedule_delayed_work(&info->work_chip_rfsh, + msecs_to_jiffies(PAC1934_MAX_RFSH_LIMIT_MS)); + + return devm_add_action_or_reset(&client->dev, pac1934_cancel_delayed_work, + &info->work_chip_rfsh); +} + +static int pac1934_prep_iio_channels(struct pac1934_chip_info *info, struct iio_dev *indio_dev) +{ + struct iio_chan_spec *ch_sp; + int channel_size, attribute_count, cnt; + void *dyn_ch_struct, *tmp_data; + struct device *dev = &info->client->dev; + + /* find out dynamically how many IIO channels we need */ + attribute_count = 0; + channel_size = 0; + for (cnt = 0; cnt < info->phys_channels; cnt++) { + if (!info->active_channels[cnt]) + continue; + + /* add the size of the properties of one chip physical channel */ + channel_size += sizeof(pac1934_single_channel); + /* count how many enabled channels we have */ + attribute_count += ARRAY_SIZE(pac1934_single_channel); + dev_dbg(dev, ":%s: Channel %d active\n", __func__, cnt + 1); + } + + dyn_ch_struct = devm_kzalloc(dev, channel_size, GFP_KERNEL); + if (!dyn_ch_struct) + return -ENOMEM; + + tmp_data = dyn_ch_struct; + + /* populate the dynamic channels and make all the adjustments */ + for (cnt = 0; cnt < info->phys_channels; cnt++) { + if (!info->active_channels[cnt]) + continue; + + memcpy(tmp_data, pac1934_single_channel, sizeof(pac1934_single_channel)); + ch_sp = (struct iio_chan_spec *)tmp_data; + ch_sp[PAC1934_CH_ENERGY].channel = cnt + 1; + ch_sp[PAC1934_CH_ENERGY].scan_index = cnt; + ch_sp[PAC1934_CH_ENERGY].address = cnt + PAC1934_VPOWER_ACC_1_ADDR; + ch_sp[PAC1934_CH_POWER].channel = cnt + 1; + ch_sp[PAC1934_CH_POWER].scan_index = cnt; + ch_sp[PAC1934_CH_POWER].address = cnt + PAC1934_VPOWER_1_ADDR; + ch_sp[PAC1934_CH_VOLTAGE].channel = cnt + 1; + ch_sp[PAC1934_CH_VOLTAGE].scan_index = cnt; + ch_sp[PAC1934_CH_VOLTAGE].address = cnt + PAC1934_VBUS_1_ADDR; + ch_sp[PAC1934_CH_CURRENT].channel = cnt + 1; + ch_sp[PAC1934_CH_CURRENT].scan_index = cnt; + ch_sp[PAC1934_CH_CURRENT].address = cnt + PAC1934_VSENSE_1_ADDR; + + /* + * In order to be able to use labels for PAC1934_CH_VOLTAGE, and + * PAC1934_CH_VOLTAGE_AVERAGE,respectively PAC1934_CH_CURRENT + * and PAC1934_CH_CURRENT_AVERAGE we need to use different + * channel numbers. We will add +5 (+1 to maximum PAC channels). + */ + ch_sp[PAC1934_CH_VOLTAGE_AVERAGE].channel = cnt + 5; + ch_sp[PAC1934_CH_VOLTAGE_AVERAGE].scan_index = cnt; + ch_sp[PAC1934_CH_VOLTAGE_AVERAGE].address = cnt + PAC1934_VBUS_AVG_1_ADDR; + ch_sp[PAC1934_CH_CURRENT_AVERAGE].channel = cnt + 5; + ch_sp[PAC1934_CH_CURRENT_AVERAGE].scan_index = cnt; + ch_sp[PAC1934_CH_CURRENT_AVERAGE].address = cnt + PAC1934_VSENSE_AVG_1_ADDR; + + /* + * now modify the parameters in all channels if the + * whole chip rail(channel) is bi-directional + */ + if (info->bi_dir[cnt]) { + ch_sp[PAC1934_CH_ENERGY].scan_type.sign = 's'; + ch_sp[PAC1934_CH_ENERGY].scan_type.realbits = 47; + ch_sp[PAC1934_CH_POWER].scan_type.sign = 's'; + ch_sp[PAC1934_CH_POWER].scan_type.realbits = 27; + ch_sp[PAC1934_CH_VOLTAGE].scan_type.sign = 's'; + ch_sp[PAC1934_CH_VOLTAGE].scan_type.realbits = 15; + ch_sp[PAC1934_CH_CURRENT].scan_type.sign = 's'; + ch_sp[PAC1934_CH_CURRENT].scan_type.realbits = 15; + ch_sp[PAC1934_CH_VOLTAGE_AVERAGE].scan_type.sign = 's'; + ch_sp[PAC1934_CH_VOLTAGE_AVERAGE].scan_type.realbits = 15; + ch_sp[PAC1934_CH_CURRENT_AVERAGE].scan_type.sign = 's'; + ch_sp[PAC1934_CH_CURRENT_AVERAGE].scan_type.realbits = 15; + } + tmp_data += sizeof(pac1934_single_channel); + } + + /* + * send the updated dynamic channel structure information towards IIO + * prepare the required field for IIO class registration + */ + indio_dev->num_channels = attribute_count; + indio_dev->channels = (const struct iio_chan_spec *)dyn_ch_struct; + + return 0; +} + +static IIO_DEVICE_ATTR(in_shunt_resistor1, 0644, + pac1934_shunt_value_show, pac1934_shunt_value_store, 0); +static IIO_DEVICE_ATTR(in_shunt_resistor2, 0644, + pac1934_shunt_value_show, pac1934_shunt_value_store, 1); +static IIO_DEVICE_ATTR(in_shunt_resistor3, 0644, + pac1934_shunt_value_show, pac1934_shunt_value_store, 2); +static IIO_DEVICE_ATTR(in_shunt_resistor4, 0644, + pac1934_shunt_value_show, pac1934_shunt_value_store, 3); + +static int pac1934_prep_custom_attributes(struct pac1934_chip_info *info, + struct iio_dev *indio_dev) +{ + int i, active_channels_count = 0; + struct attribute **pac1934_custom_attr; + struct attribute_group *pac1934_group; + struct device *dev = &info->client->dev; + + for (i = 0 ; i < info->phys_channels; i++) + if (info->active_channels[i]) + active_channels_count++; + + pac1934_group = devm_kzalloc(dev, sizeof(*pac1934_group), GFP_KERNEL); + if (!pac1934_group) + return -ENOMEM; + + pac1934_custom_attr = devm_kzalloc(dev, + (PAC1934_CUSTOM_ATTR_FOR_CHANNEL * + active_channels_count) + * sizeof(*pac1934_group) + 1, + GFP_KERNEL); + if (!pac1934_custom_attr) + return -ENOMEM; + + i = 0; + if (info->active_channels[0]) + pac1934_custom_attr[i++] = PAC1934_DEV_ATTR(in_shunt_resistor1); + + if (info->active_channels[1]) + pac1934_custom_attr[i++] = PAC1934_DEV_ATTR(in_shunt_resistor2); + + if (info->active_channels[2]) + pac1934_custom_attr[i++] = PAC1934_DEV_ATTR(in_shunt_resistor3); + + if (info->active_channels[3]) + pac1934_custom_attr[i] = PAC1934_DEV_ATTR(in_shunt_resistor4); + + pac1934_group->attrs = pac1934_custom_attr; + info->iio_info.attrs = pac1934_group; + + return 0; +} + +static const struct iio_info pac1934_info = { + .read_raw = pac1934_read_raw, + .write_raw = pac1934_write_raw, + .read_avail = pac1934_read_avail, + .read_label = pac1934_read_label, +}; + +static int pac1934_probe(struct i2c_client *client) +{ + struct pac1934_chip_info *info; + const struct pac1934_features *chip; + struct iio_dev *indio_dev; + int cnt, ret; + struct device *dev = &client->dev; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*info)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + + info->client = client; + + /* always start with energy accumulation enabled */ + for (cnt = 0; cnt < PAC1934_MAX_NUM_CHANNELS; cnt++) + info->enable_energy[cnt] = true; + + ret = pac1934_chip_identify(info); + if (ret < 0) { + /* + * If failed to identify the hardware based on internal + * registers, try using fallback compatible in device tree + * to deal with some newer part number. + */ + chip = i2c_get_match_data(client); + if (!chip) + return -EINVAL; + + info->phys_channels = chip->phys_channels; + indio_dev->name = chip->name; + } else { + info->phys_channels = pac1934_chip_config[ret].phys_channels; + indio_dev->name = pac1934_chip_config[ret].name; + } + + if (is_acpi_device_node(dev_fwnode(dev))) + ret = pac1934_acpi_parse_channel_config(client, info); + else + /* + * This makes it possible to use also ACPI PRP0001 for + * registering the device using device tree properties. + */ + ret = pac1934_fw_parse_channel_config(client, info); + + if (ret) + return dev_err_probe(dev, ret, + "parameter parsing returned an error\n"); + + ret = devm_mutex_init(dev, &info->lock); + if (ret < 0) + return ret; + + /* + * do now any chip specific initialization (e.g. read/write + * some registers), enable/disable certain channels, change the sampling + * rate to the requested value + */ + ret = pac1934_chip_configure(info); + if (ret < 0) + return ret; + + /* prepare the channel information */ + ret = pac1934_prep_iio_channels(info, indio_dev); + if (ret < 0) + return ret; + + info->iio_info = pac1934_info; + indio_dev->info = &info->iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = pac1934_prep_custom_attributes(info, indio_dev); + if (ret < 0) + return dev_err_probe(dev, ret, + "Can't configure custom attributes for PAC1934 device\n"); + + /* + * read whatever has been accumulated in the chip so far + * and reset the accumulators + */ + ret = pac1934_reg_snapshot(info, true, PAC1934_REFRESH_REG_ADDR, + PAC1934_MIN_UPDATE_WAIT_TIME_US); + if (ret < 0) + return ret; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret < 0) + return dev_err_probe(dev, ret, + "Can't register IIO device\n"); + + return 0; +} + +static const struct i2c_device_id pac1934_id[] = { + { .name = "pac1931", .driver_data = (kernel_ulong_t)&pac1934_chip_config[PAC1931] }, + { .name = "pac1932", .driver_data = (kernel_ulong_t)&pac1934_chip_config[PAC1932] }, + { .name = "pac1933", .driver_data = (kernel_ulong_t)&pac1934_chip_config[PAC1933] }, + { .name = "pac1934", .driver_data = (kernel_ulong_t)&pac1934_chip_config[PAC1934] }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pac1934_id); + +static const struct of_device_id pac1934_of_match[] = { + { + .compatible = "microchip,pac1931", + .data = &pac1934_chip_config[PAC1931] + }, + { + .compatible = "microchip,pac1932", + .data = &pac1934_chip_config[PAC1932] + }, + { + .compatible = "microchip,pac1933", + .data = &pac1934_chip_config[PAC1933] + }, + { + .compatible = "microchip,pac1934", + .data = &pac1934_chip_config[PAC1934] + }, + { } +}; +MODULE_DEVICE_TABLE(of, pac1934_of_match); + +/* + * using MCHP1930 to be compatible with BIOS ACPI. See example: + * https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ApplicationNotes/ApplicationNotes/PAC1934-Integration-Notes-for-Microsoft-Windows-10-and-Windows-11-Driver-Support-DS00002534.pdf + */ +static const struct acpi_device_id pac1934_acpi_match[] = { + { "MCHP1930", .driver_data = (kernel_ulong_t)&pac1934_chip_config[PAC1934] }, + { } +}; +MODULE_DEVICE_TABLE(acpi, pac1934_acpi_match); + +static struct i2c_driver pac1934_driver = { + .driver = { + .name = "pac1934", + .of_match_table = pac1934_of_match, + .acpi_match_table = pac1934_acpi_match + }, + .probe = pac1934_probe, + .id_table = pac1934_id, +}; + +module_i2c_driver(pac1934_driver); + +MODULE_AUTHOR("Bogdan Bolocan "); +MODULE_AUTHOR("Victor Tudose"); +MODULE_AUTHOR("Marius Cristea "); +MODULE_DESCRIPTION("IIO driver for PAC1934 Multi-Channel DC Power/Energy Monitor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/palmas_gpadc.c b/drivers/iio/adc/palmas_gpadc.c new file mode 100644 index 000000000..f777986a6 --- /dev/null +++ b/drivers/iio/adc/palmas_gpadc.c @@ -0,0 +1,1182 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * palmas-adc.c -- TI PALMAS GPADC. + * + * Copyright (c) 2013, NVIDIA Corporation. All rights reserved. + * + * Author: Pradeep Goudagunta + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MOD_NAME "palmas-gpadc" +#define PALMAS_ADC_CONVERSION_TIMEOUT (msecs_to_jiffies(5000)) +#define PALMAS_TO_BE_CALCULATED 0 +#define PALMAS_GPADC_TRIMINVALID -1 + +struct palmas_gpadc_info { +/* calibration codes and regs */ + int x1; /* lower ideal code */ + int x2; /* higher ideal code */ + int v1; /* expected lower volt reading */ + int v2; /* expected higher volt reading */ + u8 trim1_reg; /* register number for lower trim */ + u8 trim2_reg; /* register number for upper trim */ + int gain; /* calculated from above (after reading trim regs) */ + int offset; /* calculated from above (after reading trim regs) */ + int gain_error; /* calculated from above (after reading trim regs) */ + bool is_uncalibrated; /* if channel has calibration data */ +}; + +#define PALMAS_ADC_INFO(_chan, _x1, _x2, _v1, _v2, _t1, _t2, _is_uncalibrated) \ + [PALMAS_ADC_CH_##_chan] = { \ + .x1 = _x1, \ + .x2 = _x2, \ + .v1 = _v1, \ + .v2 = _v2, \ + .gain = PALMAS_TO_BE_CALCULATED, \ + .offset = PALMAS_TO_BE_CALCULATED, \ + .gain_error = PALMAS_TO_BE_CALCULATED, \ + .trim1_reg = PALMAS_GPADC_TRIM##_t1, \ + .trim2_reg = PALMAS_GPADC_TRIM##_t2, \ + .is_uncalibrated = _is_uncalibrated \ + } + +static struct palmas_gpadc_info palmas_gpadc_info[] = { + PALMAS_ADC_INFO(IN0, 2064, 3112, 630, 950, 1, 2, false), + PALMAS_ADC_INFO(IN1, 2064, 3112, 630, 950, 1, 2, false), + PALMAS_ADC_INFO(IN2, 2064, 3112, 1260, 1900, 3, 4, false), + PALMAS_ADC_INFO(IN3, 2064, 3112, 630, 950, 1, 2, false), + PALMAS_ADC_INFO(IN4, 2064, 3112, 630, 950, 1, 2, false), + PALMAS_ADC_INFO(IN5, 2064, 3112, 630, 950, 1, 2, false), + PALMAS_ADC_INFO(IN6, 2064, 3112, 2520, 3800, 5, 6, false), + PALMAS_ADC_INFO(IN7, 2064, 3112, 2520, 3800, 7, 8, false), + PALMAS_ADC_INFO(IN8, 2064, 3112, 3150, 4750, 9, 10, false), + PALMAS_ADC_INFO(IN9, 2064, 3112, 5670, 8550, 11, 12, false), + PALMAS_ADC_INFO(IN10, 2064, 3112, 3465, 5225, 13, 14, false), + PALMAS_ADC_INFO(IN11, 0, 0, 0, 0, INVALID, INVALID, true), + PALMAS_ADC_INFO(IN12, 0, 0, 0, 0, INVALID, INVALID, true), + PALMAS_ADC_INFO(IN13, 0, 0, 0, 0, INVALID, INVALID, true), + PALMAS_ADC_INFO(IN14, 2064, 3112, 3645, 5225, 15, 16, false), + PALMAS_ADC_INFO(IN15, 0, 0, 0, 0, INVALID, INVALID, true), +}; + +struct palmas_adc_event { + bool enabled; + int channel; + enum iio_event_direction direction; +}; + +struct palmas_gpadc_thresholds { + int high; + int low; +}; + +/* + * struct palmas_gpadc - the palmas_gpadc structure + * @ch0_current: channel 0 current source setting + * 0: 0 uA + * 1: 5 uA + * 2: 15 uA + * 3: 20 uA + * @ch3_current: channel 0 current source setting + * 0: 0 uA + * 1: 10 uA + * 2: 400 uA + * 3: 800 uA + * @extended_delay: enable the gpadc extended delay mode + * @auto_conversion_period: define the auto_conversion_period + * @lock: Lock to protect the device state during a potential concurrent + * read access from userspace. Reading a raw value requires a sequence + * of register writes, then a wait for a completion callback, + * and finally a register read, during which userspace could issue + * another read request. This lock protects a read access from + * occurring before another one has finished. + * + * This is the palmas_gpadc structure to store run-time information + * and pointers for this driver instance. + */ +struct palmas_gpadc { + struct device *dev; + struct palmas *palmas; + u8 ch0_current; + u8 ch3_current; + bool extended_delay; + int irq; + int irq_auto_0; + int irq_auto_1; + struct palmas_gpadc_info *adc_info; + struct completion conv_completion; + struct palmas_adc_event event0; + struct palmas_adc_event event1; + struct palmas_gpadc_thresholds thresholds[PALMAS_ADC_CH_MAX]; + int auto_conversion_period; + struct mutex lock; +}; + +static struct palmas_adc_event *palmas_gpadc_get_event(struct palmas_gpadc *adc, + int adc_chan, + enum iio_event_direction dir) +{ + if (adc_chan == adc->event0.channel && dir == adc->event0.direction) + return &adc->event0; + + if (adc_chan == adc->event1.channel && dir == adc->event1.direction) + return &adc->event1; + + return NULL; +} + +static bool palmas_gpadc_channel_is_freerunning(struct palmas_gpadc *adc, + int adc_chan) +{ + return palmas_gpadc_get_event(adc, adc_chan, IIO_EV_DIR_RISING) || + palmas_gpadc_get_event(adc, adc_chan, IIO_EV_DIR_FALLING); +} + +/* + * GPADC lock issue in AUTO mode. + * Impact: In AUTO mode, GPADC conversion can be locked after disabling AUTO + * mode feature. + * Details: + * When the AUTO mode is the only conversion mode enabled, if the AUTO + * mode feature is disabled with bit GPADC_AUTO_CTRL. AUTO_CONV1_EN = 0 + * or bit GPADC_AUTO_CTRL. AUTO_CONV0_EN = 0 during a conversion, the + * conversion mechanism can be seen as locked meaning that all following + * conversion will give 0 as a result. Bit GPADC_STATUS.GPADC_AVAILABLE + * will stay at 0 meaning that GPADC is busy. An RT conversion can unlock + * the GPADC. + * + * Workaround(s): + * To avoid the lock mechanism, the workaround to follow before any stop + * conversion request is: + * Force the GPADC state machine to be ON by using the GPADC_CTRL1. + * GPADC_FORCE bit = 1 + * Shutdown the GPADC AUTO conversion using + * GPADC_AUTO_CTRL.SHUTDOWN_CONV[01] = 0. + * After 100us, force the GPADC state machine to be OFF by using the + * GPADC_CTRL1. GPADC_FORCE bit = 0 + */ + +static int palmas_disable_auto_conversion(struct palmas_gpadc *adc) +{ + int ret; + + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_CTRL1, + PALMAS_GPADC_CTRL1_GPADC_FORCE, + PALMAS_GPADC_CTRL1_GPADC_FORCE); + if (ret < 0) { + dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret); + return ret; + } + + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_AUTO_CTRL, + PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV1 | + PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV0, + 0); + if (ret < 0) { + dev_err(adc->dev, "AUTO_CTRL update failed: %d\n", ret); + return ret; + } + + udelay(100); + + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_CTRL1, + PALMAS_GPADC_CTRL1_GPADC_FORCE, 0); + if (ret < 0) + dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret); + + return ret; +} + +static irqreturn_t palmas_gpadc_irq(int irq, void *data) +{ + struct palmas_gpadc *adc = data; + + complete(&adc->conv_completion); + + return IRQ_HANDLED; +} + +static irqreturn_t palmas_gpadc_irq_auto(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct palmas_gpadc *adc = iio_priv(indio_dev); + struct palmas_adc_event *ev; + + dev_dbg(adc->dev, "Threshold interrupt %d occurs\n", irq); + palmas_disable_auto_conversion(adc); + + ev = (irq == adc->irq_auto_0) ? &adc->event0 : &adc->event1; + if (ev->channel != -1) { + enum iio_event_direction dir; + u64 code; + + dir = ev->direction; + code = IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, ev->channel, + IIO_EV_TYPE_THRESH, dir); + iio_push_event(indio_dev, code, iio_get_time_ns(indio_dev)); + } + + return IRQ_HANDLED; +} + +static int palmas_gpadc_start_mask_interrupt(struct palmas_gpadc *adc, + bool mask) +{ + int ret; + + if (!mask) + ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE, + PALMAS_INT3_MASK, + PALMAS_INT3_MASK_GPADC_EOC_SW, 0); + else + ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE, + PALMAS_INT3_MASK, + PALMAS_INT3_MASK_GPADC_EOC_SW, + PALMAS_INT3_MASK_GPADC_EOC_SW); + if (ret < 0) + dev_err(adc->dev, "GPADC INT MASK update failed: %d\n", ret); + + return ret; +} + +static int palmas_gpadc_enable(struct palmas_gpadc *adc, int adc_chan, + int enable) +{ + unsigned int mask, val; + int ret; + + if (enable) { + val = (adc->extended_delay + << PALMAS_GPADC_RT_CTRL_EXTEND_DELAY_SHIFT); + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_RT_CTRL, + PALMAS_GPADC_RT_CTRL_EXTEND_DELAY, val); + if (ret < 0) { + dev_err(adc->dev, "RT_CTRL update failed: %d\n", ret); + return ret; + } + + mask = (PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_MASK | + PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_MASK | + PALMAS_GPADC_CTRL1_GPADC_FORCE); + val = (adc->ch0_current + << PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_SHIFT); + val |= (adc->ch3_current + << PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_SHIFT); + val |= PALMAS_GPADC_CTRL1_GPADC_FORCE; + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_CTRL1, mask, val); + if (ret < 0) { + dev_err(adc->dev, + "Failed to update current setting: %d\n", ret); + return ret; + } + + mask = (PALMAS_GPADC_SW_SELECT_SW_CONV0_SEL_MASK | + PALMAS_GPADC_SW_SELECT_SW_CONV_EN); + val = (adc_chan | PALMAS_GPADC_SW_SELECT_SW_CONV_EN); + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_SW_SELECT, mask, val); + if (ret < 0) { + dev_err(adc->dev, "SW_SELECT update failed: %d\n", ret); + return ret; + } + } else { + ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_SW_SELECT, 0); + if (ret < 0) + dev_err(adc->dev, "SW_SELECT write failed: %d\n", ret); + + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_CTRL1, + PALMAS_GPADC_CTRL1_GPADC_FORCE, 0); + if (ret < 0) { + dev_err(adc->dev, "CTRL1 update failed: %d\n", ret); + return ret; + } + } + + return ret; +} + +static int palmas_gpadc_read_prepare(struct palmas_gpadc *adc, int adc_chan) +{ + int ret; + + if (palmas_gpadc_channel_is_freerunning(adc, adc_chan)) + return 0; /* ADC already running */ + + ret = palmas_gpadc_enable(adc, adc_chan, true); + if (ret < 0) + return ret; + + return palmas_gpadc_start_mask_interrupt(adc, 0); +} + +static void palmas_gpadc_read_done(struct palmas_gpadc *adc, int adc_chan) +{ + palmas_gpadc_start_mask_interrupt(adc, 1); + palmas_gpadc_enable(adc, adc_chan, false); +} + +static int palmas_gpadc_calibrate(struct palmas_gpadc *adc, int adc_chan) +{ + int k; + int d1; + int d2; + int ret; + int gain; + int x1 = adc->adc_info[adc_chan].x1; + int x2 = adc->adc_info[adc_chan].x2; + int v1 = adc->adc_info[adc_chan].v1; + int v2 = adc->adc_info[adc_chan].v2; + + ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE, + adc->adc_info[adc_chan].trim1_reg, &d1); + if (ret < 0) { + dev_err(adc->dev, "TRIM read failed: %d\n", ret); + goto scrub; + } + + ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE, + adc->adc_info[adc_chan].trim2_reg, &d2); + if (ret < 0) { + dev_err(adc->dev, "TRIM read failed: %d\n", ret); + goto scrub; + } + + /* gain error calculation */ + k = (1000 + (1000 * (d2 - d1)) / (x2 - x1)); + + /* gain calculation */ + gain = ((v2 - v1) * 1000) / (x2 - x1); + + adc->adc_info[adc_chan].gain_error = k; + adc->adc_info[adc_chan].gain = gain; + /* offset Calculation */ + adc->adc_info[adc_chan].offset = (d1 * 1000) - ((k - 1000) * x1); + +scrub: + return ret; +} + +static int palmas_gpadc_start_conversion(struct palmas_gpadc *adc, int adc_chan) +{ + unsigned int val; + int ret; + + if (palmas_gpadc_channel_is_freerunning(adc, adc_chan)) { + int event = (adc_chan == adc->event0.channel) ? 0 : 1; + unsigned int reg = (event == 0) ? + PALMAS_GPADC_AUTO_CONV0_LSB : + PALMAS_GPADC_AUTO_CONV1_LSB; + + ret = palmas_bulk_read(adc->palmas, PALMAS_GPADC_BASE, + reg, &val, 2); + if (ret < 0) { + dev_err(adc->dev, "AUTO_CONV%x_LSB read failed: %d\n", + event, ret); + return ret; + } + } else { + init_completion(&adc->conv_completion); + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_SW_SELECT, + PALMAS_GPADC_SW_SELECT_SW_START_CONV0, + PALMAS_GPADC_SW_SELECT_SW_START_CONV0); + if (ret < 0) { + dev_err(adc->dev, "SELECT_SW_START write failed: %d\n", ret); + return ret; + } + + ret = wait_for_completion_timeout(&adc->conv_completion, + PALMAS_ADC_CONVERSION_TIMEOUT); + if (ret == 0) { + dev_err(adc->dev, "conversion not completed\n"); + return -ETIMEDOUT; + } + + ret = palmas_bulk_read(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_SW_CONV0_LSB, &val, 2); + if (ret < 0) { + dev_err(adc->dev, "SW_CONV0_LSB read failed: %d\n", ret); + return ret; + } + } + + ret = val & 0xFFF; + + return ret; +} + +static int palmas_gpadc_get_calibrated_code(struct palmas_gpadc *adc, + int adc_chan, int val) +{ + if (!adc->adc_info[adc_chan].is_uncalibrated) + val = (val*1000 - adc->adc_info[adc_chan].offset) / + adc->adc_info[adc_chan].gain_error; + + if (val < 0) { + if (val < -10) + dev_err(adc->dev, "Mismatch with calibration var = %d\n", val); + return 0; + } + + val = (val * adc->adc_info[adc_chan].gain) / 1000; + + return val; +} + +/* + * The high and low threshold values are calculated based on the advice given + * in TI Application Report SLIA087A, "Guide to Using the GPADC in PS65903x, + * TPS65917-Q1, TPS65919-Q1, and TPS65916 Devices". This document recommend + * taking ADC tolerances into account and is based on the device integral non- + * linearity (INL), offset error and gain error: + * + * raw high threshold = (ideal threshold + INL) * gain error + offset error + * + * The gain error include both gain error, as specified in the datasheet, and + * the gain error drift. These parameters vary depending on device and whether + * the channel is calibrated (trimmed) or not. + */ +static int palmas_gpadc_threshold_with_tolerance(int val, const int INL, + const int gain_error, + const int offset_error) +{ + val = ((val + INL) * (1000 + gain_error)) / 1000 + offset_error; + + return clamp(val, 0, 0xFFF); +} + +/* + * The values below are taken from the datasheet of TWL6035, TWL6037. + * todo: get max INL, gain error, and offset error from OF. + */ +static int palmas_gpadc_get_high_threshold_raw(struct palmas_gpadc *adc, + struct palmas_adc_event *ev) +{ + const int adc_chan = ev->channel; + int val = adc->thresholds[adc_chan].high; + /* integral nonlinearity, measured in LSB */ + const int max_INL = 2; + /* measured in LSB */ + int max_offset_error; + /* 0.2% when calibrated */ + int max_gain_error = 2; + + val = (val * 1000) / adc->adc_info[adc_chan].gain; + + if (adc->adc_info[adc_chan].is_uncalibrated) { + /* 2% worse */ + max_gain_error += 20; + max_offset_error = 36; + } else { + val = (val * adc->adc_info[adc_chan].gain_error + + adc->adc_info[adc_chan].offset) / + 1000; + max_offset_error = 2; + } + + return palmas_gpadc_threshold_with_tolerance(val, + max_INL, + max_gain_error, + max_offset_error); +} + +/* + * The values below are taken from the datasheet of TWL6035, TWL6037. + * todo: get min INL, gain error, and offset error from OF. + */ +static int palmas_gpadc_get_low_threshold_raw(struct palmas_gpadc *adc, + struct palmas_adc_event *ev) +{ + const int adc_chan = ev->channel; + int val = adc->thresholds[adc_chan].low; + /* integral nonlinearity, measured in LSB */ + const int min_INL = -2; + /* measured in LSB */ + int min_offset_error; + /* -0.6% when calibrated */ + int min_gain_error = -6; + + val = (val * 1000) / adc->adc_info[adc_chan].gain; + + if (adc->adc_info[adc_chan].is_uncalibrated) { + /* 2% worse */ + min_gain_error -= 20; + min_offset_error = -36; + } else { + val = (val * adc->adc_info[adc_chan].gain_error - + adc->adc_info[adc_chan].offset) / + 1000; + min_offset_error = -2; + } + + return palmas_gpadc_threshold_with_tolerance(val, + min_INL, + min_gain_error, + min_offset_error); +} + +static int palmas_gpadc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long mask) +{ + struct palmas_gpadc *adc = iio_priv(indio_dev); + int adc_chan = chan->channel; + int ret = 0; + + if (adc_chan >= PALMAS_ADC_CH_MAX) + return -EINVAL; + + mutex_lock(&adc->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + ret = palmas_gpadc_read_prepare(adc, adc_chan); + if (ret < 0) + goto out; + + ret = palmas_gpadc_start_conversion(adc, adc_chan); + if (ret < 0) { + dev_err(adc->dev, + "ADC start conversion failed\n"); + goto out; + } + + if (mask == IIO_CHAN_INFO_PROCESSED) + ret = palmas_gpadc_get_calibrated_code( + adc, adc_chan, ret); + + *val = ret; + + ret = IIO_VAL_INT; + goto out; + } + + mutex_unlock(&adc->lock); + return ret; + +out: + palmas_gpadc_read_done(adc, adc_chan); + mutex_unlock(&adc->lock); + + return ret; +} + +static int palmas_gpadc_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct palmas_gpadc *adc = iio_priv(indio_dev); + int adc_chan = chan->channel; + int ret = 0; + + if (adc_chan >= PALMAS_ADC_CH_MAX || type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + mutex_lock(&adc->lock); + + if (palmas_gpadc_get_event(adc, adc_chan, dir)) + ret = 1; + + mutex_unlock(&adc->lock); + + return ret; +} + +static int palmas_adc_configure_events(struct palmas_gpadc *adc); +static int palmas_adc_reset_events(struct palmas_gpadc *adc); + +static int palmas_gpadc_reconfigure_event_channels(struct palmas_gpadc *adc) +{ + return (adc->event0.enabled || adc->event1.enabled) ? + palmas_adc_configure_events(adc) : + palmas_adc_reset_events(adc); +} + +static int palmas_gpadc_enable_event_config(struct palmas_gpadc *adc, + const struct iio_chan_spec *chan, + enum iio_event_direction dir) +{ + struct palmas_adc_event *ev; + int adc_chan = chan->channel; + + if (palmas_gpadc_get_event(adc, adc_chan, dir)) + /* already enabled */ + return 0; + + if (adc->event0.channel == -1) { + ev = &adc->event0; + } else if (adc->event1.channel == -1) { + /* event0 has to be the lowest channel */ + if (adc_chan < adc->event0.channel) { + adc->event1 = adc->event0; + ev = &adc->event0; + } else { + ev = &adc->event1; + } + } else { /* both AUTO channels already in use */ + dev_warn(adc->dev, "event0 - %d, event1 - %d\n", + adc->event0.channel, adc->event1.channel); + return -EBUSY; + } + + ev->enabled = true; + ev->channel = adc_chan; + ev->direction = dir; + + return palmas_gpadc_reconfigure_event_channels(adc); +} + +static int palmas_gpadc_disable_event_config(struct palmas_gpadc *adc, + const struct iio_chan_spec *chan, + enum iio_event_direction dir) +{ + int adc_chan = chan->channel; + struct palmas_adc_event *ev = palmas_gpadc_get_event(adc, adc_chan, dir); + + if (!ev) + return 0; + + if (ev == &adc->event0) { + adc->event0 = adc->event1; + ev = &adc->event1; + } + + ev->enabled = false; + ev->channel = -1; + ev->direction = IIO_EV_DIR_NONE; + + return palmas_gpadc_reconfigure_event_channels(adc); +} + +static int palmas_gpadc_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct palmas_gpadc *adc = iio_priv(indio_dev); + int adc_chan = chan->channel; + int ret; + + if (adc_chan >= PALMAS_ADC_CH_MAX || type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + mutex_lock(&adc->lock); + + if (state) + ret = palmas_gpadc_enable_event_config(adc, chan, dir); + else + ret = palmas_gpadc_disable_event_config(adc, chan, dir); + + mutex_unlock(&adc->lock); + + return ret; +} + +static int palmas_gpadc_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct palmas_gpadc *adc = iio_priv(indio_dev); + int adc_chan = chan->channel; + int ret; + + if (adc_chan >= PALMAS_ADC_CH_MAX || type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + mutex_lock(&adc->lock); + + switch (info) { + case IIO_EV_INFO_VALUE: + *val = (dir == IIO_EV_DIR_RISING) ? + adc->thresholds[adc_chan].high : + adc->thresholds[adc_chan].low; + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + break; + } + + mutex_unlock(&adc->lock); + + return ret; +} + +static int palmas_gpadc_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct palmas_gpadc *adc = iio_priv(indio_dev); + int adc_chan = chan->channel; + int old; + int ret; + + if (adc_chan >= PALMAS_ADC_CH_MAX || type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + mutex_lock(&adc->lock); + switch (info) { + case IIO_EV_INFO_VALUE: + if (val < 0 || val > 0xFFF) { + ret = -EINVAL; + goto out_unlock; + } + if (dir == IIO_EV_DIR_RISING) { + old = adc->thresholds[adc_chan].high; + adc->thresholds[adc_chan].high = val; + } else { + old = adc->thresholds[adc_chan].low; + adc->thresholds[adc_chan].low = val; + } + ret = 0; + break; + default: + ret = -EINVAL; + goto out_unlock; + } + + if (val != old && palmas_gpadc_get_event(adc, adc_chan, dir)) + ret = palmas_gpadc_reconfigure_event_channels(adc); + +out_unlock: + mutex_unlock(&adc->lock); + + return ret; +} + +static const struct iio_info palmas_gpadc_iio_info = { + .read_raw = palmas_gpadc_read_raw, + .read_event_config = palmas_gpadc_read_event_config, + .write_event_config = palmas_gpadc_write_event_config, + .read_event_value = palmas_gpadc_read_event_value, + .write_event_value = palmas_gpadc_write_event_value, +}; + +static const struct iio_event_spec palmas_gpadc_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +#define PALMAS_ADC_CHAN_IIO(chan, _type, chan_info) \ +{ \ + .datasheet_name = PALMAS_DATASHEET_NAME(chan), \ + .type = _type, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(chan_info), \ + .indexed = 1, \ + .channel = PALMAS_ADC_CH_##chan, \ + .event_spec = palmas_gpadc_events, \ + .num_event_specs = ARRAY_SIZE(palmas_gpadc_events) \ +} + +static const struct iio_chan_spec palmas_gpadc_iio_channel[] = { + PALMAS_ADC_CHAN_IIO(IN0, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN1, IIO_TEMP, IIO_CHAN_INFO_RAW), + PALMAS_ADC_CHAN_IIO(IN2, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN3, IIO_TEMP, IIO_CHAN_INFO_RAW), + PALMAS_ADC_CHAN_IIO(IN4, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN5, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN6, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN7, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN8, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN9, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN10, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN11, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN12, IIO_TEMP, IIO_CHAN_INFO_RAW), + PALMAS_ADC_CHAN_IIO(IN13, IIO_TEMP, IIO_CHAN_INFO_RAW), + PALMAS_ADC_CHAN_IIO(IN14, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN15, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), +}; + +static int palmas_gpadc_get_adc_dt_data(struct platform_device *pdev, + struct palmas_gpadc_platform_data **gpadc_pdata) +{ + struct device_node *np = pdev->dev.of_node; + struct palmas_gpadc_platform_data *gp_data; + int ret; + u32 pval; + + gp_data = devm_kzalloc(&pdev->dev, sizeof(*gp_data), GFP_KERNEL); + if (!gp_data) + return -ENOMEM; + + ret = of_property_read_u32(np, "ti,channel0-current-microamp", &pval); + if (!ret) + gp_data->ch0_current = pval; + + ret = of_property_read_u32(np, "ti,channel3-current-microamp", &pval); + if (!ret) + gp_data->ch3_current = pval; + + gp_data->extended_delay = of_property_read_bool(np, + "ti,enable-extended-delay"); + + *gpadc_pdata = gp_data; + + return 0; +} + +static void palmas_gpadc_reset(void *data) +{ + struct palmas_gpadc *adc = data; + if (adc->event0.enabled || adc->event1.enabled) + palmas_adc_reset_events(adc); +} + +static int palmas_gpadc_probe(struct platform_device *pdev) +{ + struct palmas_gpadc *adc; + struct palmas_platform_data *pdata; + struct palmas_gpadc_platform_data *gpadc_pdata = NULL; + struct iio_dev *indio_dev; + int ret, i; + + pdata = dev_get_platdata(pdev->dev.parent); + + if (pdata && pdata->gpadc_pdata) + gpadc_pdata = pdata->gpadc_pdata; + + if (!gpadc_pdata && pdev->dev.of_node) { + ret = palmas_gpadc_get_adc_dt_data(pdev, &gpadc_pdata); + if (ret < 0) + return ret; + } + if (!gpadc_pdata) + return -EINVAL; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->dev = &pdev->dev; + adc->palmas = dev_get_drvdata(pdev->dev.parent); + adc->adc_info = palmas_gpadc_info; + + mutex_init(&adc->lock); + + init_completion(&adc->conv_completion); + platform_set_drvdata(pdev, indio_dev); + + adc->auto_conversion_period = gpadc_pdata->auto_conversion_period_ms; + adc->irq = palmas_irq_get_virq(adc->palmas, PALMAS_GPADC_EOC_SW_IRQ); + if (adc->irq < 0) + return dev_err_probe(adc->dev, adc->irq, "get virq failed\n"); + + ret = devm_request_threaded_irq(&pdev->dev, adc->irq, NULL, + palmas_gpadc_irq, + IRQF_ONESHOT, dev_name(adc->dev), + adc); + if (ret < 0) + return dev_err_probe(adc->dev, ret, + "request irq %d failed\n", adc->irq); + + adc->irq_auto_0 = platform_get_irq(pdev, 1); + if (adc->irq_auto_0 < 0) + return adc->irq_auto_0; + + ret = devm_request_threaded_irq(&pdev->dev, adc->irq_auto_0, NULL, + palmas_gpadc_irq_auto, IRQF_ONESHOT, + "palmas-adc-auto-0", indio_dev); + if (ret < 0) + return dev_err_probe(adc->dev, ret, + "request auto0 irq %d failed\n", + adc->irq_auto_0); + + adc->irq_auto_1 = platform_get_irq(pdev, 2); + if (adc->irq_auto_1 < 0) + return adc->irq_auto_1; + + ret = devm_request_threaded_irq(&pdev->dev, adc->irq_auto_1, NULL, + palmas_gpadc_irq_auto, IRQF_ONESHOT, + "palmas-adc-auto-1", indio_dev); + if (ret < 0) + return dev_err_probe(adc->dev, ret, + "request auto1 irq %d failed\n", + adc->irq_auto_1); + + adc->event0.enabled = false; + adc->event0.channel = -1; + adc->event0.direction = IIO_EV_DIR_NONE; + adc->event1.enabled = false; + adc->event1.channel = -1; + adc->event1.direction = IIO_EV_DIR_NONE; + + /* set the current source 0 (value 0/5/15/20 uA => 0..3) */ + if (gpadc_pdata->ch0_current <= 1) + adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_0; + else if (gpadc_pdata->ch0_current <= 5) + adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_5; + else if (gpadc_pdata->ch0_current <= 15) + adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_15; + else + adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_20; + + /* set the current source 3 (value 0/10/400/800 uA => 0..3) */ + if (gpadc_pdata->ch3_current <= 1) + adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_0; + else if (gpadc_pdata->ch3_current <= 10) + adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_10; + else if (gpadc_pdata->ch3_current <= 400) + adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_400; + else + adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_800; + + adc->extended_delay = gpadc_pdata->extended_delay; + + indio_dev->name = MOD_NAME; + indio_dev->info = &palmas_gpadc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = palmas_gpadc_iio_channel; + indio_dev->num_channels = ARRAY_SIZE(palmas_gpadc_iio_channel); + + ret = devm_iio_device_register(&pdev->dev, indio_dev); + if (ret < 0) + return dev_err_probe(adc->dev, ret, + "iio_device_register() failed\n"); + + device_set_wakeup_capable(&pdev->dev, 1); + for (i = 0; i < PALMAS_ADC_CH_MAX; i++) { + if (!(adc->adc_info[i].is_uncalibrated)) + palmas_gpadc_calibrate(adc, i); + } + + ret = devm_add_action(&pdev->dev, palmas_gpadc_reset, adc); + if (ret) + return ret; + + return 0; +} + +static int palmas_adc_configure_events(struct palmas_gpadc *adc) +{ + int adc_period, conv; + int i; + int ch0 = 0, ch1 = 0; + int thres; + int ret; + + adc_period = adc->auto_conversion_period; + for (i = 0; i < 16; ++i) { + if (((1000 * (1 << i)) / 32) >= adc_period) + break; + } + if (i > 0) + i--; + adc_period = i; + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_AUTO_CTRL, + PALMAS_GPADC_AUTO_CTRL_COUNTER_CONV_MASK, + adc_period); + if (ret < 0) { + dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret); + return ret; + } + + conv = 0; + if (adc->event0.enabled) { + struct palmas_adc_event *ev = &adc->event0; + int polarity; + + ch0 = ev->channel; + conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN; + switch (ev->direction) { + case IIO_EV_DIR_RISING: + thres = palmas_gpadc_get_high_threshold_raw(adc, ev); + polarity = 0; + break; + case IIO_EV_DIR_FALLING: + thres = palmas_gpadc_get_low_threshold_raw(adc, ev); + polarity = PALMAS_GPADC_THRES_CONV0_MSB_THRES_CONV0_POL; + break; + default: + return -EINVAL; + } + + ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_THRES_CONV0_LSB, thres & 0xFF); + if (ret < 0) { + dev_err(adc->dev, + "THRES_CONV0_LSB write failed: %d\n", ret); + return ret; + } + + ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_THRES_CONV0_MSB, + ((thres >> 8) & 0xF) | polarity); + if (ret < 0) { + dev_err(adc->dev, + "THRES_CONV0_MSB write failed: %d\n", ret); + return ret; + } + } + + if (adc->event1.enabled) { + struct palmas_adc_event *ev = &adc->event1; + int polarity; + + ch1 = ev->channel; + conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN; + switch (ev->direction) { + case IIO_EV_DIR_RISING: + thres = palmas_gpadc_get_high_threshold_raw(adc, ev); + polarity = 0; + break; + case IIO_EV_DIR_FALLING: + thres = palmas_gpadc_get_low_threshold_raw(adc, ev); + polarity = PALMAS_GPADC_THRES_CONV1_MSB_THRES_CONV1_POL; + break; + default: + return -EINVAL; + } + + ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_THRES_CONV1_LSB, thres & 0xFF); + if (ret < 0) { + dev_err(adc->dev, + "THRES_CONV1_LSB write failed: %d\n", ret); + return ret; + } + + ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_THRES_CONV1_MSB, + ((thres >> 8) & 0xF) | polarity); + if (ret < 0) { + dev_err(adc->dev, + "THRES_CONV1_MSB write failed: %d\n", ret); + return ret; + } + } + + ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_AUTO_SELECT, (ch1 << 4) | ch0); + if (ret < 0) { + dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret); + return ret; + } + + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_AUTO_CTRL, + PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN | + PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN, conv); + if (ret < 0) + dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret); + + return ret; +} + +static int palmas_adc_reset_events(struct palmas_gpadc *adc) +{ + int ret; + + ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_AUTO_SELECT, 0); + if (ret < 0) { + dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret); + return ret; + } + + ret = palmas_disable_auto_conversion(adc); + if (ret < 0) + dev_err(adc->dev, "Disable auto conversion failed: %d\n", ret); + + return ret; +} + +static int palmas_gpadc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct palmas_gpadc *adc = iio_priv(indio_dev); + + if (!device_may_wakeup(dev)) + return 0; + + if (adc->event0.enabled) + enable_irq_wake(adc->irq_auto_0); + + if (adc->event1.enabled) + enable_irq_wake(adc->irq_auto_1); + + return 0; +} + +static int palmas_gpadc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct palmas_gpadc *adc = iio_priv(indio_dev); + + if (!device_may_wakeup(dev)) + return 0; + + if (adc->event0.enabled) + disable_irq_wake(adc->irq_auto_0); + + if (adc->event1.enabled) + disable_irq_wake(adc->irq_auto_1); + + return 0; +}; + +static DEFINE_SIMPLE_DEV_PM_OPS(palmas_pm_ops, palmas_gpadc_suspend, + palmas_gpadc_resume); + +static const struct of_device_id of_palmas_gpadc_match_tbl[] = { + { .compatible = "ti,palmas-gpadc", }, + { } +}; +MODULE_DEVICE_TABLE(of, of_palmas_gpadc_match_tbl); + +static struct platform_driver palmas_gpadc_driver = { + .probe = palmas_gpadc_probe, + .driver = { + .name = MOD_NAME, + .pm = pm_sleep_ptr(&palmas_pm_ops), + .of_match_table = of_palmas_gpadc_match_tbl, + }, +}; +module_platform_driver(palmas_gpadc_driver); + +MODULE_DESCRIPTION("palmas GPADC driver"); +MODULE_AUTHOR("Pradeep Goudagunta"); +MODULE_ALIAS("platform:palmas-gpadc"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/qcom-pm8xxx-xoadc.c b/drivers/iio/adc/qcom-pm8xxx-xoadc.c new file mode 100644 index 000000000..3cdf90c44 --- /dev/null +++ b/drivers/iio/adc/qcom-pm8xxx-xoadc.c @@ -0,0 +1,1027 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Qualcomm PM8xxx PMIC XOADC driver + * + * These ADCs are known as HK/XO (house keeping / chrystal oscillator) + * "XO" in "XOADC" means Chrystal Oscillator. It's a bunch of + * specific-purpose and general purpose ADC converters and channels. + * + * Copyright (C) 2017 Linaro Ltd. + * Author: Linus Walleij + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Definitions for the "user processor" registers lifted from the v3.4 + * Qualcomm tree. Their kernel has two out-of-tree drivers for the ADC: + * drivers/misc/pmic8058-xoadc.c + * drivers/hwmon/pm8xxx-adc.c + * None of them contain any complete register specification, so this is + * a best effort of combining the information. + */ + +/* These appear to be "battery monitor" registers */ +#define ADC_ARB_BTM_CNTRL1 0x17e +#define ADC_ARB_BTM_CNTRL1_EN_BTM BIT(0) +#define ADC_ARB_BTM_CNTRL1_SEL_OP_MODE BIT(1) +#define ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL1 BIT(2) +#define ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL2 BIT(3) +#define ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL3 BIT(4) +#define ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL4 BIT(5) +#define ADC_ARB_BTM_CNTRL1_EOC BIT(6) +#define ADC_ARB_BTM_CNTRL1_REQ BIT(7) + +#define ADC_ARB_BTM_AMUX_CNTRL 0x17f +#define ADC_ARB_BTM_ANA_PARAM 0x180 +#define ADC_ARB_BTM_DIG_PARAM 0x181 +#define ADC_ARB_BTM_RSV 0x182 +#define ADC_ARB_BTM_DATA1 0x183 +#define ADC_ARB_BTM_DATA0 0x184 +#define ADC_ARB_BTM_BAT_COOL_THR1 0x185 +#define ADC_ARB_BTM_BAT_COOL_THR0 0x186 +#define ADC_ARB_BTM_BAT_WARM_THR1 0x187 +#define ADC_ARB_BTM_BAT_WARM_THR0 0x188 +#define ADC_ARB_BTM_CNTRL2 0x18c + +/* Proper ADC registers */ + +#define ADC_ARB_USRP_CNTRL 0x197 +#define ADC_ARB_USRP_CNTRL_EN_ARB BIT(0) +#define ADC_ARB_USRP_CNTRL_RSV1 BIT(1) +#define ADC_ARB_USRP_CNTRL_RSV2 BIT(2) +#define ADC_ARB_USRP_CNTRL_RSV3 BIT(3) +#define ADC_ARB_USRP_CNTRL_RSV4 BIT(4) +#define ADC_ARB_USRP_CNTRL_RSV5 BIT(5) +#define ADC_ARB_USRP_CNTRL_EOC BIT(6) +#define ADC_ARB_USRP_CNTRL_REQ BIT(7) + +#define ADC_ARB_USRP_AMUX_CNTRL 0x198 +/* + * The channel mask includes the bits selecting channel mux and prescaler + * on PM8058, or channel mux and premux on PM8921. + */ +#define ADC_ARB_USRP_AMUX_CNTRL_CHAN_MASK 0xfc +#define ADC_ARB_USRP_AMUX_CNTRL_RSV0 BIT(0) +#define ADC_ARB_USRP_AMUX_CNTRL_RSV1 BIT(1) +/* On PM8058 this is prescaling, on PM8921 this is premux */ +#define ADC_ARB_USRP_AMUX_CNTRL_PRESCALEMUX0 BIT(2) +#define ADC_ARB_USRP_AMUX_CNTRL_PRESCALEMUX1 BIT(3) +#define ADC_ARB_USRP_AMUX_CNTRL_SEL0 BIT(4) +#define ADC_ARB_USRP_AMUX_CNTRL_SEL1 BIT(5) +#define ADC_ARB_USRP_AMUX_CNTRL_SEL2 BIT(6) +#define ADC_ARB_USRP_AMUX_CNTRL_SEL3 BIT(7) +#define ADC_AMUX_PREMUX_SHIFT 2 +#define ADC_AMUX_SEL_SHIFT 4 + +/* We know very little about the bits in this register */ +#define ADC_ARB_USRP_ANA_PARAM 0x199 +#define ADC_ARB_USRP_ANA_PARAM_DIS 0xFE +#define ADC_ARB_USRP_ANA_PARAM_EN 0xFF + +#define ADC_ARB_USRP_DIG_PARAM 0x19A +#define ADC_ARB_USRP_DIG_PARAM_SEL_SHIFT0 BIT(0) +#define ADC_ARB_USRP_DIG_PARAM_SEL_SHIFT1 BIT(1) +#define ADC_ARB_USRP_DIG_PARAM_CLK_RATE0 BIT(2) +#define ADC_ARB_USRP_DIG_PARAM_CLK_RATE1 BIT(3) +#define ADC_ARB_USRP_DIG_PARAM_EOC BIT(4) +/* + * On a later ADC the decimation factors are defined as + * 00 = 512, 01 = 1024, 10 = 2048, 11 = 4096 so assume this + * holds also for this older XOADC. + */ +#define ADC_ARB_USRP_DIG_PARAM_DEC_RATE0 BIT(5) +#define ADC_ARB_USRP_DIG_PARAM_DEC_RATE1 BIT(6) +#define ADC_ARB_USRP_DIG_PARAM_EN BIT(7) +#define ADC_DIG_PARAM_DEC_SHIFT 5 + +#define ADC_ARB_USRP_RSV 0x19B +#define ADC_ARB_USRP_RSV_RST BIT(0) +#define ADC_ARB_USRP_RSV_DTEST0 BIT(1) +#define ADC_ARB_USRP_RSV_DTEST1 BIT(2) +#define ADC_ARB_USRP_RSV_OP BIT(3) +#define ADC_ARB_USRP_RSV_IP_SEL0 BIT(4) +#define ADC_ARB_USRP_RSV_IP_SEL1 BIT(5) +#define ADC_ARB_USRP_RSV_IP_SEL2 BIT(6) +#define ADC_ARB_USRP_RSV_TRM BIT(7) +#define ADC_RSV_IP_SEL_SHIFT 4 + +#define ADC_ARB_USRP_DATA0 0x19D +#define ADC_ARB_USRP_DATA1 0x19C + +/* + * Physical channels which MUST exist on all PM variants in order to provide + * proper reference points for calibration. + * + * @PM8XXX_CHANNEL_INTERNAL: 625mV reference channel + * @PM8XXX_CHANNEL_125V: 1250mV reference channel + * @PM8XXX_CHANNEL_INTERNAL_2: 325mV reference channel + * @PM8XXX_CHANNEL_MUXOFF: channel to reduce input load on mux, apparently also + * measures XO temperature + */ +#define PM8XXX_CHANNEL_INTERNAL 0x0c +#define PM8XXX_CHANNEL_125V 0x0d +#define PM8XXX_CHANNEL_INTERNAL_2 0x0e +#define PM8XXX_CHANNEL_MUXOFF 0x0f + +/* + * PM8058 AMUX premux scaling, two bits. This is done of the channel before + * reaching the AMUX. + */ +#define PM8058_AMUX_PRESCALE_0 0x0 /* No scaling on the signal */ +#define PM8058_AMUX_PRESCALE_1 0x1 /* Unity scaling selected by the user */ +#define PM8058_AMUX_PRESCALE_1_DIV3 0x2 /* 1/3 prescaler on the input */ + +/* Defines reference voltage for the XOADC */ +#define AMUX_RSV0 0x0 /* XO_IN/XOADC_GND, special selection to read XO temp */ +#define AMUX_RSV1 0x1 /* PMIC_IN/XOADC_GND */ +#define AMUX_RSV2 0x2 /* PMIC_IN/BMS_CSP */ +#define AMUX_RSV3 0x3 /* not used */ +#define AMUX_RSV4 0x4 /* XOADC_GND/XOADC_GND */ +#define AMUX_RSV5 0x5 /* XOADC_VREF/XOADC_GND */ +#define XOADC_RSV_MAX 5 /* 3 bits 0..7, 3 and 6,7 are invalid */ + +/** + * struct xoadc_channel - encodes channel properties and defaults + * @datasheet_name: the hardwarename of this channel + * @pre_scale_mux: prescale (PM8058) or premux (PM8921) for selecting + * this channel. Both this and the amux channel is needed to uniquely + * identify a channel. Values 0..3. + * @amux_channel: value of the ADC_ARB_USRP_AMUX_CNTRL register for this + * channel, bits 4..7, selects the amux, values 0..f + * @prescale: the channels have hard-coded prescale ratios defined + * by the hardware, this tells us what it is + * @type: corresponding IIO channel type, usually IIO_VOLTAGE or + * IIO_TEMP + * @scale_fn_type: the liner interpolation etc to convert the + * ADC code to the value that IIO expects, in uV or millicelsius + * etc. This scale function can be pretty elaborate if different + * thermistors are connected or other hardware characteristics are + * deployed. + * @amux_ip_rsv: ratiometric scale value used by the analog muxer: this + * selects the reference voltage for ratiometric scaling + */ +struct xoadc_channel { + const char *datasheet_name; + u8 pre_scale_mux:2; + u8 amux_channel:4; + const struct u32_fract prescale; + enum iio_chan_type type; + enum vadc_scale_fn_type scale_fn_type; + u8 amux_ip_rsv:3; +}; + +/** + * struct xoadc_variant - encodes the XOADC variant characteristics + * @name: name of this PMIC variant + * @channels: the hardware channels and respective settings and defaults + * @broken_ratiometric: if the PMIC has broken ratiometric scaling (this + * is a known problem on PM8058) + * @prescaling: this variant uses AMUX bits 2 & 3 for prescaling (PM8058) + * @second_level_mux: this variant uses AMUX bits 2 & 3 for a second level + * mux + */ +struct xoadc_variant { + const char name[16]; + const struct xoadc_channel *channels; + bool broken_ratiometric; + bool prescaling; + bool second_level_mux; +}; + +/* + * XOADC_CHAN macro parameters: + * _dname: the name of the channel + * _presmux: prescaler (PM8058) or premux (PM8921) setting for this channel + * _amux: the value in bits 2..7 of the ADC_ARB_USRP_AMUX_CNTRL register + * for this channel. On some PMICs some of the bits select a prescaler, and + * on some PMICs some of the bits select various complex multiplex settings. + * _type: IIO channel type + * _prenum: prescaler numerator (dividend) + * _preden: prescaler denominator (divisor) + * _scale: scaling function type, this selects how the raw valued is mangled + * to output the actual processed measurement + * _amip: analog mux input parent when using ratiometric measurements + */ +#define XOADC_CHAN(_dname, _presmux, _amux, _type, _prenum, _preden, _scale, _amip) \ + { \ + .datasheet_name = __stringify(_dname), \ + .pre_scale_mux = _presmux, \ + .amux_channel = _amux, \ + .prescale = { \ + .numerator = _prenum, .denominator = _preden, \ + }, \ + .type = _type, \ + .scale_fn_type = _scale, \ + .amux_ip_rsv = _amip, \ + } + +/* + * Taken from arch/arm/mach-msm/board-9615.c in the vendor tree: + * TODO: incomplete, needs testing. + */ +static const struct xoadc_channel pm8018_xoadc_channels[] = { + XOADC_CHAN(VCOIN, 0x00, 0x00, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(VBAT, 0x00, 0x01, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(VPH_PWR, 0x00, 0x02, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(DIE_TEMP, 0x00, 0x0b, IIO_TEMP, 1, 1, SCALE_PMIC_THERM, AMUX_RSV1), + /* Used for battery ID or battery temperature */ + XOADC_CHAN(AMUX8, 0x00, 0x08, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV2), + XOADC_CHAN(INTERNAL, 0x00, 0x0c, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(125V, 0x00, 0x0d, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(MUXOFF, 0x00, 0x0f, IIO_TEMP, 1, 1, SCALE_XOTHERM, AMUX_RSV0), + { }, /* Sentinel */ +}; + +/* + * Taken from arch/arm/mach-msm/board-8930-pmic.c in the vendor tree: + * TODO: needs testing. + */ +static const struct xoadc_channel pm8038_xoadc_channels[] = { + XOADC_CHAN(VCOIN, 0x00, 0x00, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(VBAT, 0x00, 0x01, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(DCIN, 0x00, 0x02, IIO_VOLTAGE, 1, 6, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ICHG, 0x00, 0x03, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(VPH_PWR, 0x00, 0x04, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX5, 0x00, 0x05, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX6, 0x00, 0x06, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX7, 0x00, 0x07, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + /* AMUX8 used for battery temperature in most cases */ + XOADC_CHAN(AMUX8, 0x00, 0x08, IIO_TEMP, 1, 1, SCALE_THERM_100K_PULLUP, AMUX_RSV2), + XOADC_CHAN(AMUX9, 0x00, 0x09, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(USB_VBUS, 0x00, 0x0a, IIO_VOLTAGE, 1, 4, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(DIE_TEMP, 0x00, 0x0b, IIO_TEMP, 1, 1, SCALE_PMIC_THERM, AMUX_RSV1), + XOADC_CHAN(INTERNAL, 0x00, 0x0c, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(125V, 0x00, 0x0d, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(INTERNAL_2, 0x00, 0x0e, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(MUXOFF, 0x00, 0x0f, IIO_TEMP, 1, 1, SCALE_XOTHERM, AMUX_RSV0), + { }, /* Sentinel */ +}; + +/* + * This was created by cross-referencing the vendor tree + * arch/arm/mach-msm/board-msm8x60.c msm_adc_channels_data[] + * with the "channel types" (first field) to find the right + * configuration for these channels on an MSM8x60 i.e. PM8058 + * setup. + */ +static const struct xoadc_channel pm8058_xoadc_channels[] = { + XOADC_CHAN(VCOIN, 0x00, 0x00, IIO_VOLTAGE, 1, 2, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(VBAT, 0x00, 0x01, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(DCIN, 0x00, 0x02, IIO_VOLTAGE, 1, 10, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ICHG, 0x00, 0x03, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(VPH_PWR, 0x00, 0x04, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + /* + * AMUX channels 5 thru 9 are referred to as MPP5 thru MPP9 in + * some code and documentation. But they are really just 5 + * channels just like any other. They are connected to a switching + * matrix where they can be routed to any of the MPPs, not just + * 1-to-1 onto MPP5 thru 9, so naming them MPP5 thru MPP9 is + * very confusing. + */ + XOADC_CHAN(AMUX5, 0x00, 0x05, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX6, 0x00, 0x06, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX7, 0x00, 0x07, IIO_VOLTAGE, 1, 2, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX8, 0x00, 0x08, IIO_VOLTAGE, 1, 2, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX9, 0x00, 0x09, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(USB_VBUS, 0x00, 0x0a, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(DIE_TEMP, 0x00, 0x0b, IIO_TEMP, 1, 1, SCALE_PMIC_THERM, AMUX_RSV1), + XOADC_CHAN(INTERNAL, 0x00, 0x0c, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(125V, 0x00, 0x0d, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(INTERNAL_2, 0x00, 0x0e, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(MUXOFF, 0x00, 0x0f, IIO_TEMP, 1, 1, SCALE_XOTHERM, AMUX_RSV0), + /* There are also "unity" and divided by 3 channels (prescaler) but noone is using them */ + { }, /* Sentinel */ +}; + +/* + * The PM8921 has some pre-muxing on its channels, this comes from the vendor tree + * include/linux/mfd/pm8xxx/pm8xxx-adc.h + * board-flo-pmic.c (Nexus 7) and board-8064-pmic.c + */ +static const struct xoadc_channel pm8921_xoadc_channels[] = { + XOADC_CHAN(VCOIN, 0x00, 0x00, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(VBAT, 0x00, 0x01, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(DCIN, 0x00, 0x02, IIO_VOLTAGE, 1, 6, SCALE_DEFAULT, AMUX_RSV1), + /* channel "ICHG" is reserved and not used on PM8921 */ + XOADC_CHAN(VPH_PWR, 0x00, 0x04, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(IBAT, 0x00, 0x05, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + /* CHAN 6 & 7 (MPP1 & MPP2) are reserved for MPP channels on PM8921 */ + XOADC_CHAN(BATT_THERM, 0x00, 0x08, IIO_TEMP, 1, 1, SCALE_THERM_100K_PULLUP, AMUX_RSV1), + XOADC_CHAN(BATT_ID, 0x00, 0x09, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(USB_VBUS, 0x00, 0x0a, IIO_VOLTAGE, 1, 4, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(DIE_TEMP, 0x00, 0x0b, IIO_TEMP, 1, 1, SCALE_PMIC_THERM, AMUX_RSV1), + XOADC_CHAN(INTERNAL, 0x00, 0x0c, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(125V, 0x00, 0x0d, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + /* FIXME: look into the scaling of this temperature */ + XOADC_CHAN(CHG_TEMP, 0x00, 0x0e, IIO_TEMP, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(MUXOFF, 0x00, 0x0f, IIO_TEMP, 1, 1, SCALE_XOTHERM, AMUX_RSV0), + /* The following channels have premux bit 0 set to 1 (all end in 4) */ + XOADC_CHAN(ATEST_8, 0x01, 0x00, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + /* Set scaling to 1/2 based on the name for these two */ + XOADC_CHAN(USB_SNS_DIV20, 0x01, 0x01, IIO_VOLTAGE, 1, 2, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(DCIN_SNS_DIV20, 0x01, 0x02, IIO_VOLTAGE, 1, 2, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX3, 0x01, 0x03, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX4, 0x01, 0x04, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX5, 0x01, 0x05, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX6, 0x01, 0x06, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX7, 0x01, 0x07, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX8, 0x01, 0x08, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + /* Internal test signals, I think */ + XOADC_CHAN(ATEST_1, 0x01, 0x09, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_2, 0x01, 0x0a, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_3, 0x01, 0x0b, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_4, 0x01, 0x0c, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_5, 0x01, 0x0d, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_6, 0x01, 0x0e, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_7, 0x01, 0x0f, IIO_VOLTAGE, 1, 1, SCALE_DEFAULT, AMUX_RSV1), + /* The following channels have premux bit 1 set to 1 (all end in 8) */ + /* I guess even ATEST8 will be divided by 3 here */ + XOADC_CHAN(ATEST_8, 0x02, 0x00, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + /* I guess div 2 div 3 becomes div 6 */ + XOADC_CHAN(USB_SNS_DIV20_DIV3, 0x02, 0x01, IIO_VOLTAGE, 1, 6, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(DCIN_SNS_DIV20_DIV3, 0x02, 0x02, IIO_VOLTAGE, 1, 6, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX3_DIV3, 0x02, 0x03, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX4_DIV3, 0x02, 0x04, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX5_DIV3, 0x02, 0x05, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX6_DIV3, 0x02, 0x06, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX7_DIV3, 0x02, 0x07, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(AMUX8_DIV3, 0x02, 0x08, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_1_DIV3, 0x02, 0x09, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_2_DIV3, 0x02, 0x0a, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_3_DIV3, 0x02, 0x0b, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_4_DIV3, 0x02, 0x0c, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_5_DIV3, 0x02, 0x0d, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_6_DIV3, 0x02, 0x0e, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + XOADC_CHAN(ATEST_7_DIV3, 0x02, 0x0f, IIO_VOLTAGE, 1, 3, SCALE_DEFAULT, AMUX_RSV1), + { }, /* Sentinel */ +}; + +/** + * struct pm8xxx_chan_info - ADC channel information + * @label: label of this channel from device tree (defaults to datasheet name if not specified) + * @hwchan: pointer to hardware channel information (muxing & scaling settings) + * @calibration: whether to use absolute or ratiometric calibration + * @decimation: 0,1,2,3 + * @amux_ip_rsv: ratiometric scale value if using ratiometric + * calibration: 0, 1, 2, 4, 5. + */ +struct pm8xxx_chan_info { + const char *label; + const struct xoadc_channel *hwchan; + enum vadc_calibration calibration; + u8 decimation:2; + u8 amux_ip_rsv:3; +}; + +/** + * struct pm8xxx_xoadc - state container for the XOADC + * @dev: pointer to device + * @map: regmap to access registers + * @variant: XOADC variant characteristics + * @vref: reference voltage regulator + * characteristics of the channels, and sensible default settings + * @nchans: number of channels, configured by the device tree + * @chans: the channel information per-channel, configured by the device tree + * @iio_chans: IIO channel specifiers + * @graph: linear calibration parameters for absolute and + * ratiometric measurements + * @complete: completion to indicate end of conversion + * @lock: lock to restrict access to the hardware to one client at the time + */ +struct pm8xxx_xoadc { + struct device *dev; + struct regmap *map; + const struct xoadc_variant *variant; + struct regulator *vref; + unsigned int nchans; + struct pm8xxx_chan_info *chans; + struct iio_chan_spec *iio_chans; + struct vadc_linear_graph graph[2]; + struct completion complete; + struct mutex lock; +}; + +static irqreturn_t pm8xxx_eoc_irq(int irq, void *d) +{ + struct iio_dev *indio_dev = d; + struct pm8xxx_xoadc *adc = iio_priv(indio_dev); + + complete(&adc->complete); + + return IRQ_HANDLED; +} + +static struct pm8xxx_chan_info * +pm8xxx_get_channel(struct pm8xxx_xoadc *adc, u8 chan) +{ + int i; + + for (i = 0; i < adc->nchans; i++) { + struct pm8xxx_chan_info *ch = &adc->chans[i]; + if (ch->hwchan->amux_channel == chan) + return ch; + } + return NULL; +} + +static int pm8xxx_read_channel_rsv(struct pm8xxx_xoadc *adc, + const struct pm8xxx_chan_info *ch, + u8 rsv, u16 *adc_code, + bool force_ratiometric) +{ + int ret; + unsigned int val; + u8 rsvmask, rsvval; + u8 lsb, msb; + + dev_dbg(adc->dev, "read channel \"%s\", amux %d, prescale/mux: %d, rsv %d\n", + ch->label, ch->hwchan->amux_channel, ch->hwchan->pre_scale_mux, rsv); + + mutex_lock(&adc->lock); + + /* Mux in this channel */ + val = ch->hwchan->amux_channel << ADC_AMUX_SEL_SHIFT; + val |= ch->hwchan->pre_scale_mux << ADC_AMUX_PREMUX_SHIFT; + ret = regmap_write(adc->map, ADC_ARB_USRP_AMUX_CNTRL, val); + if (ret) + goto unlock; + + /* Set up ratiometric scale value, mask off all bits except these */ + rsvmask = (ADC_ARB_USRP_RSV_RST | ADC_ARB_USRP_RSV_DTEST0 | + ADC_ARB_USRP_RSV_DTEST1 | ADC_ARB_USRP_RSV_OP); + if (adc->variant->broken_ratiometric && !force_ratiometric) { + /* + * Apparently the PM8058 has some kind of bug which is + * reflected in the vendor tree drivers/misc/pmix8058-xoadc.c + * which just hardcodes the RSV selector to SEL1 (0x20) for + * most cases and SEL0 (0x10) for the MUXOFF channel only. + * If we force ratiometric (currently only done when attempting + * to do ratiometric calibration) this doesn't seem to work + * very well and I suspect ratiometric conversion is simply + * broken or not supported on the PM8058. + * + * Maybe IO_SEL2 doesn't exist on PM8058 and bits 4 & 5 select + * the mode alone. + * + * Some PM8058 register documentation would be nice to get + * this right. + */ + if (ch->hwchan->amux_channel == PM8XXX_CHANNEL_MUXOFF) + rsvval = ADC_ARB_USRP_RSV_IP_SEL0; + else + rsvval = ADC_ARB_USRP_RSV_IP_SEL1; + } else { + if (rsv == 0xff) + rsvval = (ch->amux_ip_rsv << ADC_RSV_IP_SEL_SHIFT) | + ADC_ARB_USRP_RSV_TRM; + else + rsvval = (rsv << ADC_RSV_IP_SEL_SHIFT) | + ADC_ARB_USRP_RSV_TRM; + } + + ret = regmap_update_bits(adc->map, + ADC_ARB_USRP_RSV, + ~rsvmask, + rsvval); + if (ret) + goto unlock; + + ret = regmap_write(adc->map, ADC_ARB_USRP_ANA_PARAM, + ADC_ARB_USRP_ANA_PARAM_DIS); + if (ret) + goto unlock; + + /* Decimation factor */ + ret = regmap_write(adc->map, ADC_ARB_USRP_DIG_PARAM, + ADC_ARB_USRP_DIG_PARAM_SEL_SHIFT0 | + ADC_ARB_USRP_DIG_PARAM_SEL_SHIFT1 | + ch->decimation << ADC_DIG_PARAM_DEC_SHIFT); + if (ret) + goto unlock; + + ret = regmap_write(adc->map, ADC_ARB_USRP_ANA_PARAM, + ADC_ARB_USRP_ANA_PARAM_EN); + if (ret) + goto unlock; + + /* Enable the arbiter, the Qualcomm code does it twice like this */ + ret = regmap_write(adc->map, ADC_ARB_USRP_CNTRL, + ADC_ARB_USRP_CNTRL_EN_ARB); + if (ret) + goto unlock; + ret = regmap_write(adc->map, ADC_ARB_USRP_CNTRL, + ADC_ARB_USRP_CNTRL_EN_ARB); + if (ret) + goto unlock; + + + /* Fire a request! */ + reinit_completion(&adc->complete); + ret = regmap_write(adc->map, ADC_ARB_USRP_CNTRL, + ADC_ARB_USRP_CNTRL_EN_ARB | + ADC_ARB_USRP_CNTRL_REQ); + if (ret) + goto unlock; + + /* Next the interrupt occurs */ + if (!wait_for_completion_timeout(&adc->complete, VADC_CONV_TIME_MAX_US)) { + ret = -ETIMEDOUT; + goto unlock; + } + + ret = regmap_read(adc->map, ADC_ARB_USRP_DATA0, &val); + if (ret) + goto unlock; + lsb = val; + ret = regmap_read(adc->map, ADC_ARB_USRP_DATA1, &val); + if (ret) + goto unlock; + msb = val; + *adc_code = (msb << 8) | lsb; + + /* Turn off the ADC by setting the arbiter to 0 twice */ + ret = regmap_write(adc->map, ADC_ARB_USRP_CNTRL, 0); + if (ret) + goto unlock; + ret = regmap_write(adc->map, ADC_ARB_USRP_CNTRL, 0); + if (ret) + goto unlock; + +unlock: + mutex_unlock(&adc->lock); + return ret; +} + +static int pm8xxx_read_channel(struct pm8xxx_xoadc *adc, + const struct pm8xxx_chan_info *ch, + u16 *adc_code) +{ + /* + * Normally we just use the ratiometric scale value (RSV) predefined + * for the channel, but during calibration we need to modify this + * so this wrapper is a helper hiding the more complex version. + */ + return pm8xxx_read_channel_rsv(adc, ch, 0xff, adc_code, false); +} + +static int pm8xxx_calibrate_device(struct pm8xxx_xoadc *adc) +{ + const struct pm8xxx_chan_info *ch; + u16 read_1250v; + u16 read_0625v; + u16 read_nomux_rsv5; + u16 read_nomux_rsv4; + int ret; + + adc->graph[VADC_CALIB_ABSOLUTE].dx = VADC_ABSOLUTE_RANGE_UV; + adc->graph[VADC_CALIB_RATIOMETRIC].dx = VADC_RATIOMETRIC_RANGE; + + /* Common reference channel calibration */ + ch = pm8xxx_get_channel(adc, PM8XXX_CHANNEL_125V); + if (!ch) + return -ENODEV; + ret = pm8xxx_read_channel(adc, ch, &read_1250v); + if (ret) { + dev_err(adc->dev, "could not read 1.25V reference channel\n"); + return -ENODEV; + } + ch = pm8xxx_get_channel(adc, PM8XXX_CHANNEL_INTERNAL); + if (!ch) + return -ENODEV; + ret = pm8xxx_read_channel(adc, ch, &read_0625v); + if (ret) { + dev_err(adc->dev, "could not read 0.625V reference channel\n"); + return -ENODEV; + } + if (read_1250v == read_0625v) { + dev_err(adc->dev, "read same ADC code for 1.25V and 0.625V\n"); + return -ENODEV; + } + + adc->graph[VADC_CALIB_ABSOLUTE].dy = read_1250v - read_0625v; + adc->graph[VADC_CALIB_ABSOLUTE].gnd = read_0625v; + + dev_info(adc->dev, "absolute calibration dx = %d uV, dy = %d units\n", + VADC_ABSOLUTE_RANGE_UV, adc->graph[VADC_CALIB_ABSOLUTE].dy); + + /* Ratiometric calibration */ + ch = pm8xxx_get_channel(adc, PM8XXX_CHANNEL_MUXOFF); + if (!ch) + return -ENODEV; + ret = pm8xxx_read_channel_rsv(adc, ch, AMUX_RSV5, + &read_nomux_rsv5, true); + if (ret) { + dev_err(adc->dev, "could not read MUXOFF reference channel\n"); + return -ENODEV; + } + ret = pm8xxx_read_channel_rsv(adc, ch, AMUX_RSV4, + &read_nomux_rsv4, true); + if (ret) { + dev_err(adc->dev, "could not read MUXOFF reference channel\n"); + return -ENODEV; + } + adc->graph[VADC_CALIB_RATIOMETRIC].dy = + read_nomux_rsv5 - read_nomux_rsv4; + adc->graph[VADC_CALIB_RATIOMETRIC].gnd = read_nomux_rsv4; + + dev_info(adc->dev, "ratiometric calibration dx = %d, dy = %d units\n", + VADC_RATIOMETRIC_RANGE, + adc->graph[VADC_CALIB_RATIOMETRIC].dy); + + return 0; +} + +static int pm8xxx_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct pm8xxx_xoadc *adc = iio_priv(indio_dev); + const struct pm8xxx_chan_info *ch; + u16 adc_code; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ch = pm8xxx_get_channel(adc, chan->address); + if (!ch) + return -EINVAL; + ret = pm8xxx_read_channel(adc, ch, &adc_code); + if (ret) + return ret; + + ret = qcom_vadc_scale(ch->hwchan->scale_fn_type, + &adc->graph[ch->calibration], + &ch->hwchan->prescale, + (ch->calibration == VADC_CALIB_ABSOLUTE), + adc_code, val); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_RAW: + ch = pm8xxx_get_channel(adc, chan->address); + if (!ch) + return -EINVAL; + ret = pm8xxx_read_channel(adc, ch, &adc_code); + if (ret) + return ret; + + *val = (int)adc_code; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int pm8xxx_fwnode_xlate(struct iio_dev *indio_dev, + const struct fwnode_reference_args *iiospec) +{ + struct pm8xxx_xoadc *adc = iio_priv(indio_dev); + u8 pre_scale_mux; + u8 amux_channel; + unsigned int i; + + /* + * First cell is prescaler or premux, second cell is analog + * mux. + */ + if (iiospec->nargs != 2) { + dev_err(&indio_dev->dev, "wrong number of arguments for %pfwP need 2 got %d\n", + iiospec->fwnode, + iiospec->nargs); + return -EINVAL; + } + pre_scale_mux = (u8)iiospec->args[0]; + amux_channel = (u8)iiospec->args[1]; + dev_dbg(&indio_dev->dev, "pre scale/mux: %02x, amux: %02x\n", + pre_scale_mux, amux_channel); + + /* We need to match exactly on the prescale/premux and channel */ + for (i = 0; i < adc->nchans; i++) + if (adc->chans[i].hwchan->pre_scale_mux == pre_scale_mux && + adc->chans[i].hwchan->amux_channel == amux_channel) + return i; + + return -EINVAL; +} + +static int pm8xxx_read_label(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, char *label) +{ + struct pm8xxx_xoadc *adc = iio_priv(indio_dev); + const struct pm8xxx_chan_info *ch; + + ch = pm8xxx_get_channel(adc, chan->address); + if (!ch) + return -EINVAL; + return sysfs_emit(label, "%s\n", ch->label); +} + +static const struct iio_info pm8xxx_xoadc_info = { + .fwnode_xlate = pm8xxx_fwnode_xlate, + .read_label = pm8xxx_read_label, + .read_raw = pm8xxx_read_raw, +}; + +static int pm8xxx_xoadc_parse_channel(struct device *dev, + struct fwnode_handle *fwnode, + const struct xoadc_channel *hw_channels, + struct iio_chan_spec *iio_chan, + struct pm8xxx_chan_info *ch) +{ + const char *name = fwnode_get_name(fwnode); + const struct xoadc_channel *hwchan; + u32 pre_scale_mux, amux_channel, reg[2]; + u32 rsv, dec; + int ret; + int chid; + + ret = fwnode_property_read_u32_array(fwnode, "reg", reg, + ARRAY_SIZE(reg)); + if (ret) { + dev_err(dev, "invalid pre scale/mux or amux channel number %s\n", + name); + return ret; + } + + pre_scale_mux = reg[0]; + amux_channel = reg[1]; + + /* Find the right channel setting */ + chid = 0; + hwchan = &hw_channels[0]; + while (hwchan->datasheet_name) { + if (hwchan->pre_scale_mux == pre_scale_mux && + hwchan->amux_channel == amux_channel) + break; + hwchan++; + chid++; + } + /* The sentinel does not have a name assigned */ + if (!hwchan->datasheet_name) { + dev_err(dev, "could not locate channel %02x/%02x\n", + pre_scale_mux, amux_channel); + return -EINVAL; + } + ch->label = hwchan->datasheet_name; + fwnode_property_read_string(fwnode, "label", &ch->label); + ch->hwchan = hwchan; + /* Everyone seems to use absolute calibration except in special cases */ + ch->calibration = VADC_CALIB_ABSOLUTE; + /* Everyone seems to use default ("type 2") decimation */ + ch->decimation = VADC_DEF_DECIMATION; + + if (!fwnode_property_read_u32(fwnode, "qcom,ratiometric", &rsv)) { + ch->calibration = VADC_CALIB_RATIOMETRIC; + if (rsv > XOADC_RSV_MAX) { + dev_err(dev, "%s too large RSV value %d\n", name, rsv); + return -EINVAL; + } + if (rsv == AMUX_RSV3) { + dev_err(dev, "%s invalid RSV value %d\n", name, rsv); + return -EINVAL; + } + } + + /* Optional decimation, if omitted we use the default */ + ret = fwnode_property_read_u32(fwnode, "qcom,decimation", &dec); + if (!ret) { + ret = qcom_vadc_decimation_from_dt(dec); + if (ret < 0) { + dev_err(dev, "%s invalid decimation %d\n", + name, dec); + return ret; + } + ch->decimation = ret; + } + + iio_chan->channel = chid; + iio_chan->address = hwchan->amux_channel; + iio_chan->datasheet_name = hwchan->datasheet_name; + iio_chan->type = hwchan->type; + /* All channels are raw or processed */ + iio_chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_PROCESSED); + iio_chan->indexed = 1; + + dev_dbg(dev, + "channel [PRESCALE/MUX: %02x AMUX: %02x] \"%s\" ref voltage: %d, decimation %d prescale %d/%d, scale function %d\n", + hwchan->pre_scale_mux, hwchan->amux_channel, ch->label, + ch->amux_ip_rsv, ch->decimation, hwchan->prescale.numerator, + hwchan->prescale.denominator, hwchan->scale_fn_type); + + return 0; +} + +static int pm8xxx_xoadc_parse_channels(struct pm8xxx_xoadc *adc) +{ + struct pm8xxx_chan_info *ch; + int ret; + int i; + + adc->nchans = device_get_child_node_count(adc->dev); + if (!adc->nchans) { + dev_err(adc->dev, "no channel children\n"); + return -ENODEV; + } + dev_dbg(adc->dev, "found %d ADC channels\n", adc->nchans); + + adc->iio_chans = devm_kcalloc(adc->dev, adc->nchans, + sizeof(*adc->iio_chans), GFP_KERNEL); + if (!adc->iio_chans) + return -ENOMEM; + + adc->chans = devm_kcalloc(adc->dev, adc->nchans, + sizeof(*adc->chans), GFP_KERNEL); + if (!adc->chans) + return -ENOMEM; + + i = 0; + device_for_each_child_node_scoped(adc->dev, child) { + ch = &adc->chans[i]; + ret = pm8xxx_xoadc_parse_channel(adc->dev, child, + adc->variant->channels, + &adc->iio_chans[i], + ch); + if (ret) + return ret; + + i++; + } + + /* Check for required channels */ + ch = pm8xxx_get_channel(adc, PM8XXX_CHANNEL_125V); + if (!ch) { + dev_err(adc->dev, "missing 1.25V reference channel\n"); + return -ENODEV; + } + ch = pm8xxx_get_channel(adc, PM8XXX_CHANNEL_INTERNAL); + if (!ch) { + dev_err(adc->dev, "missing 0.625V reference channel\n"); + return -ENODEV; + } + ch = pm8xxx_get_channel(adc, PM8XXX_CHANNEL_MUXOFF); + if (!ch) { + dev_err(adc->dev, "missing MUXOFF reference channel\n"); + return -ENODEV; + } + + return 0; +} + +static int pm8xxx_xoadc_probe(struct platform_device *pdev) +{ + const struct xoadc_variant *variant; + struct pm8xxx_xoadc *adc; + struct iio_dev *indio_dev; + struct regmap *map; + struct device *dev = &pdev->dev; + int ret; + + variant = device_get_match_data(dev); + if (!variant) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + platform_set_drvdata(pdev, indio_dev); + + adc = iio_priv(indio_dev); + adc->dev = dev; + adc->variant = variant; + init_completion(&adc->complete); + mutex_init(&adc->lock); + + ret = pm8xxx_xoadc_parse_channels(adc); + if (ret) + return ret; + + map = dev_get_regmap(dev->parent, NULL); + if (!map) { + dev_err(dev, "parent regmap unavailable.\n"); + return -ENODEV; + } + adc->map = map; + + /* Bring up regulator */ + adc->vref = devm_regulator_get(dev, "xoadc-ref"); + if (IS_ERR(adc->vref)) + return dev_err_probe(dev, PTR_ERR(adc->vref), + "failed to get XOADC VREF regulator\n"); + ret = regulator_enable(adc->vref); + if (ret) { + dev_err(dev, "failed to enable XOADC VREF regulator\n"); + return ret; + } + + ret = devm_request_threaded_irq(dev, platform_get_irq(pdev, 0), + pm8xxx_eoc_irq, NULL, 0, variant->name, indio_dev); + if (ret) { + dev_err(dev, "unable to request IRQ\n"); + goto out_disable_vref; + } + + indio_dev->name = variant->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &pm8xxx_xoadc_info; + indio_dev->channels = adc->iio_chans; + indio_dev->num_channels = adc->nchans; + + ret = iio_device_register(indio_dev); + if (ret) + goto out_disable_vref; + + ret = pm8xxx_calibrate_device(adc); + if (ret) + goto out_unreg_device; + + dev_info(dev, "%s XOADC driver enabled\n", variant->name); + + return 0; + +out_unreg_device: + iio_device_unregister(indio_dev); +out_disable_vref: + regulator_disable(adc->vref); + + return ret; +} + +static void pm8xxx_xoadc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct pm8xxx_xoadc *adc = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + regulator_disable(adc->vref); +} + +static const struct xoadc_variant pm8018_variant = { + .name = "PM8018-XOADC", + .channels = pm8018_xoadc_channels, +}; + +static const struct xoadc_variant pm8038_variant = { + .name = "PM8038-XOADC", + .channels = pm8038_xoadc_channels, +}; + +static const struct xoadc_variant pm8058_variant = { + .name = "PM8058-XOADC", + .channels = pm8058_xoadc_channels, + .broken_ratiometric = true, + .prescaling = true, +}; + +static const struct xoadc_variant pm8921_variant = { + .name = "PM8921-XOADC", + .channels = pm8921_xoadc_channels, + .second_level_mux = true, +}; + +static const struct of_device_id pm8xxx_xoadc_id_table[] = { + { + .compatible = "qcom,pm8018-adc", + .data = &pm8018_variant, + }, + { + .compatible = "qcom,pm8038-adc", + .data = &pm8038_variant, + }, + { + .compatible = "qcom,pm8058-adc", + .data = &pm8058_variant, + }, + { + .compatible = "qcom,pm8921-adc", + .data = &pm8921_variant, + }, + { } +}; +MODULE_DEVICE_TABLE(of, pm8xxx_xoadc_id_table); + +static struct platform_driver pm8xxx_xoadc_driver = { + .driver = { + .name = "pm8xxx-adc", + .of_match_table = pm8xxx_xoadc_id_table, + }, + .probe = pm8xxx_xoadc_probe, + .remove = pm8xxx_xoadc_remove, +}; +module_platform_driver(pm8xxx_xoadc_driver); + +MODULE_DESCRIPTION("PM8xxx XOADC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:pm8xxx-xoadc"); diff --git a/drivers/iio/adc/qcom-spmi-adc5-gen3.c b/drivers/iio/adc/qcom-spmi-adc5-gen3.c new file mode 100644 index 000000000..c68c6c5f6 --- /dev/null +++ b/drivers/iio/adc/qcom-spmi-adc5-gen3.c @@ -0,0 +1,828 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ADC5_GEN3_VADC_SDAM 0x0 + +struct adc5_chip; + +/** + * struct adc5_channel_prop - ADC channel structure + * @common_props: structure with ADC channel properties (common to TM usage). + * @adc_tm: indicates TM type if the channel is used for TM measurements. + * @chip: pointer to top-level ADC device structure. + */ +struct adc5_channel_prop { + struct adc5_channel_common_prop common_props; + int adc_tm; + struct adc5_chip *chip; +}; + +/** + * struct adc5_chip - ADC private structure. + * @dev: SPMI ADC5 Gen3 device. + * @dev_data: Top-level ADC device data. + * @nchannels: number of ADC channels. + * @chan_props: array of ADC channel properties. + * @iio_chans: array of IIO channels specification. + * @complete: ADC result notification after interrupt is received. + * @lock: ADC lock for access to the peripheral, to prevent concurrent + * requests from multiple clients. + * @data: software configuration data. + * @n_tm_channels: number of ADC channels used for TM measurements. + */ +struct adc5_chip { + struct device *dev; + struct adc5_device_data dev_data; + unsigned int nchannels; + struct adc5_channel_prop *chan_props; + struct iio_chan_spec *iio_chans; + struct completion complete; + struct mutex lock; + const struct adc5_data *data; + unsigned int n_tm_channels; +}; + +int adc5_gen3_read(struct adc5_device_data *adc, unsigned int sdam_index, + u16 offset, u8 *data, int len) +{ + return regmap_bulk_read(adc->regmap, + adc->base[sdam_index].base_addr + offset, + data, len); +} +EXPORT_SYMBOL_NS_GPL(adc5_gen3_read, "QCOM_SPMI_ADC5_GEN3"); + +int adc5_gen3_write(struct adc5_device_data *adc, unsigned int sdam_index, + u16 offset, u8 *data, int len) +{ + return regmap_bulk_write(adc->regmap, + adc->base[sdam_index].base_addr + offset, + data, len); +} +EXPORT_SYMBOL_NS_GPL(adc5_gen3_write, "QCOM_SPMI_ADC5_GEN3"); + +static int adc5_gen3_read_voltage_data(struct adc5_chip *adc, u16 *data) +{ + u8 rslt[2]; + int ret; + + ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM, + ADC5_GEN3_CH_DATA0(0), rslt, sizeof(rslt)); + if (ret) + return ret; + + *data = get_unaligned_le16(rslt); + + if (*data == ADC5_USR_DATA_CHECK) { + dev_err(adc->dev, "Invalid data:%#x\n", *data); + return -EINVAL; + } + + dev_dbg(adc->dev, "voltage raw code:%#x\n", *data); + + return 0; +} + +void adc5_gen3_update_dig_param(struct adc5_channel_common_prop *prop, u8 *data) +{ + /* Update calibration select and decimation ratio select */ + *data &= ~(ADC5_GEN3_DIG_PARAM_CAL_SEL_MASK | ADC5_GEN3_DIG_PARAM_DEC_RATIO_SEL_MASK); + *data |= FIELD_PREP(ADC5_GEN3_DIG_PARAM_CAL_SEL_MASK, prop->cal_method); + *data |= FIELD_PREP(ADC5_GEN3_DIG_PARAM_DEC_RATIO_SEL_MASK, prop->decimation); +} +EXPORT_SYMBOL_NS_GPL(adc5_gen3_update_dig_param, "QCOM_SPMI_ADC5_GEN3"); + +#define ADC5_GEN3_READ_CONFIG_REGS 7 + +static int adc5_gen3_configure(struct adc5_chip *adc, + struct adc5_channel_common_prop *prop) +{ + u8 buf[ADC5_GEN3_READ_CONFIG_REGS]; + u8 conv_req = 0; + int ret; + + ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM, ADC5_GEN3_SID, + buf, sizeof(buf)); + if (ret) + return ret; + + /* Write SID */ + buf[0] = FIELD_PREP(ADC5_GEN3_SID_MASK, prop->sid); + + /* + * Use channel 0 by default for immediate conversion and to indicate + * there is an actual conversion request + */ + buf[1] = ADC5_GEN3_CHAN_CONV_REQ | 0; + + buf[2] = ADC5_GEN3_TIME_IMMEDIATE; + + /* Digital param selection */ + adc5_gen3_update_dig_param(prop, &buf[3]); + + /* Update fast average sample value */ + buf[4] = FIELD_PREP(ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK, + prop->avg_samples) | ADC5_GEN3_FAST_AVG_CTL_EN; + + /* Select ADC channel */ + buf[5] = prop->channel; + + /* Select HW settle delay for channel */ + buf[6] = FIELD_PREP(ADC5_GEN3_HW_SETTLE_DELAY_MASK, + prop->hw_settle_time_us); + + reinit_completion(&adc->complete); + + ret = adc5_gen3_write(&adc->dev_data, ADC5_GEN3_VADC_SDAM, ADC5_GEN3_SID, + buf, sizeof(buf)); + if (ret) + return ret; + + conv_req = ADC5_GEN3_CONV_REQ_REQ; + return adc5_gen3_write(&adc->dev_data, ADC5_GEN3_VADC_SDAM, + ADC5_GEN3_CONV_REQ, &conv_req, sizeof(conv_req)); +} + +/* + * Worst case delay from PBS in readying handshake bit can be up to 15ms, when + * PBS is busy running other simultaneous transactions, while in the best case, + * it is already ready at this point. Assigning polling delay and retry count + * accordingly. + */ + +#define ADC5_GEN3_HS_DELAY_US 100 +#define ADC5_GEN3_HS_RETRY_COUNT 150 + +int adc5_gen3_poll_wait_hs(struct adc5_device_data *adc, + unsigned int sdam_index) +{ + u8 conv_req = ADC5_GEN3_CONV_REQ_REQ; + int ret, count; + u8 status = 0; + + for (count = 0; count < ADC5_GEN3_HS_RETRY_COUNT; count++) { + ret = adc5_gen3_read(adc, sdam_index, ADC5_GEN3_HS, &status, sizeof(status)); + if (ret) + return ret; + + if (status == ADC5_GEN3_HS_READY) { + ret = adc5_gen3_read(adc, sdam_index, ADC5_GEN3_CONV_REQ, + &conv_req, sizeof(conv_req)); + if (ret) + return ret; + + if (!conv_req) + return 0; + } + + fsleep(ADC5_GEN3_HS_DELAY_US); + } + + pr_err("Setting HS ready bit timed out, sdam_index:%d, status:%#x\n", + sdam_index, status); + return -ETIMEDOUT; +} +EXPORT_SYMBOL_NS_GPL(adc5_gen3_poll_wait_hs, "QCOM_SPMI_ADC5_GEN3"); + +int adc5_gen3_status_clear(struct adc5_device_data *adc, + int sdam_index, u16 offset, u8 *val, int len) +{ + u8 value; + int ret; + + ret = adc5_gen3_write(adc, sdam_index, offset, val, len); + if (ret) + return ret; + + /* To indicate conversion request is only to clear a status */ + value = 0; + ret = adc5_gen3_write(adc, sdam_index, ADC5_GEN3_PERPH_CH, &value, + sizeof(value)); + if (ret) + return ret; + + value = ADC5_GEN3_CONV_REQ_REQ; + return adc5_gen3_write(adc, sdam_index, ADC5_GEN3_CONV_REQ, &value, + sizeof(value)); +} +EXPORT_SYMBOL_NS_GPL(adc5_gen3_status_clear, "QCOM_SPMI_ADC5_GEN3"); + +/* + * Worst case delay from PBS for conversion time can be up to 500ms, when PBS + * has timed out twice, once for the initial attempt and once for a retry of + * the same transaction. + */ + +#define ADC5_GEN3_CONV_TIMEOUT_MS 501 + +static int adc5_gen3_do_conversion(struct adc5_chip *adc, + struct adc5_channel_common_prop *prop, + u16 *data_volt) +{ + unsigned long rc; + int ret; + u8 val; + + guard(mutex)(&adc->lock); + ret = adc5_gen3_poll_wait_hs(&adc->dev_data, ADC5_GEN3_VADC_SDAM); + if (ret) + return ret; + + ret = adc5_gen3_configure(adc, prop); + if (ret) { + dev_err(adc->dev, "ADC configure failed with %d\n", ret); + return ret; + } + + /* No support for polling mode at present */ + rc = wait_for_completion_timeout(&adc->complete, + msecs_to_jiffies(ADC5_GEN3_CONV_TIMEOUT_MS)); + if (!rc) { + dev_err(adc->dev, "Reading ADC channel %s timed out\n", + prop->label); + return -ETIMEDOUT; + } + + ret = adc5_gen3_read_voltage_data(adc, data_volt); + if (ret) + return ret; + + val = BIT(0); + return adc5_gen3_status_clear(&adc->dev_data, ADC5_GEN3_VADC_SDAM, + ADC5_GEN3_EOC_CLR, &val, 1); +} + +static irqreturn_t adc5_gen3_isr(int irq, void *dev_id) +{ + struct adc5_chip *adc = dev_id; + struct device *dev = adc->dev; + u8 status, eoc_status, val; + int ret; + + ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM, + ADC5_GEN3_STATUS1, &status, sizeof(status)); + if (ret) { + dev_err(dev, "adc read status1 failed with %d\n", ret); + return IRQ_NONE; + } + + ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM, + ADC5_GEN3_EOC_STS, &eoc_status, sizeof(eoc_status)); + if (ret) { + dev_err(dev, "adc read eoc status failed with %d\n", ret); + return IRQ_NONE; + } + + if (status & ADC5_GEN3_STATUS1_CONV_FAULT) { + dev_err_ratelimited(dev, + "Unexpected conversion fault, status:%#x, eoc_status:%#x\n", + status, eoc_status); + val = ADC5_GEN3_CONV_ERR_CLR_REQ; + adc5_gen3_status_clear(&adc->dev_data, ADC5_GEN3_VADC_SDAM, + ADC5_GEN3_CONV_ERR_CLR, &val, 1); + return IRQ_HANDLED; + } + + dev_dbg(dev, "Interrupt status:%#x, EOC status:%#x\n", status, eoc_status); + + /* CHAN0 is the preconfigured channel for immediate conversion */ + if (!(eoc_status & ADC5_GEN3_EOC_CHAN_0)) + return IRQ_NONE; + + complete(&adc->complete); + return IRQ_HANDLED; +} + +static int adc5_gen3_fwnode_xlate(struct iio_dev *indio_dev, + const struct fwnode_reference_args *iiospec) +{ + struct adc5_chip *adc = iio_priv(indio_dev); + int i, v_channel; + + for (i = 0; i < adc->nchannels; i++) { + v_channel = ADC5_GEN3_V_CHAN(adc->chan_props[i].common_props); + if (v_channel == iiospec->args[0]) + return i; + } + + return -ENOENT; +} + +static int adc5_gen3_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct adc5_chip *adc = iio_priv(indio_dev); + struct adc5_channel_common_prop *prop; + u16 adc_code_volt; + int ret; + + prop = &adc->chan_props[chan->address].common_props; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ret = adc5_gen3_do_conversion(adc, prop, &adc_code_volt); + if (ret) + return ret; + + ret = qcom_adc5_hw_scale(prop->scale_fn_type, prop->prescale, + adc->data, adc_code_volt, val); + if (ret) + return ret; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int adc5_gen3_read_label(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, char *label) +{ + struct adc5_chip *adc = iio_priv(indio_dev); + struct adc5_channel_prop *prop; + + prop = &adc->chan_props[chan->address]; + return sprintf(label, "%s\n", prop->common_props.label); +} + +static const struct iio_info adc5_gen3_info = { + .read_raw = adc5_gen3_read_raw, + .read_label = adc5_gen3_read_label, + .fwnode_xlate = adc5_gen3_fwnode_xlate, +}; + +struct adc5_channels { + unsigned int prescale_index; + enum iio_chan_type type; + long info_mask; + enum vadc_scale_fn_type scale_fn_type; +}; + +/* In these definitions, _pre refers to an index into adc5_prescale_ratios. */ +#define ADC5_CHAN(_type, _mask, _pre, _scale) \ + { \ + .prescale_index = _pre, \ + .type = _type, \ + .info_mask = _mask, \ + .scale_fn_type = _scale, \ + }, \ + +#define ADC5_CHAN_TEMP(_pre, _scale) \ + ADC5_CHAN(IIO_TEMP, BIT(IIO_CHAN_INFO_PROCESSED), _pre, _scale) \ + +#define ADC5_CHAN_VOLT(_pre, _scale) \ + ADC5_CHAN(IIO_VOLTAGE, BIT(IIO_CHAN_INFO_PROCESSED), _pre, _scale) \ + +#define ADC5_CHAN_CUR(_pre, _scale) \ + ADC5_CHAN(IIO_CURRENT, BIT(IIO_CHAN_INFO_PROCESSED), _pre, _scale) \ + +static const struct adc5_channels adc5_gen3_chans_pmic[ADC5_MAX_CHANNEL] = { + [ADC5_GEN3_REF_GND] = ADC5_CHAN_VOLT(0, SCALE_HW_CALIB_DEFAULT) + [ADC5_GEN3_1P25VREF] = ADC5_CHAN_VOLT(0, SCALE_HW_CALIB_DEFAULT) + [ADC5_GEN3_VPH_PWR] = ADC5_CHAN_VOLT(1, SCALE_HW_CALIB_DEFAULT) + [ADC5_GEN3_VBAT_SNS_QBG] = ADC5_CHAN_VOLT(1, SCALE_HW_CALIB_DEFAULT) + [ADC5_GEN3_USB_SNS_V_16] = ADC5_CHAN_TEMP(8, SCALE_HW_CALIB_DEFAULT) + [ADC5_GEN3_VIN_DIV16_MUX] = ADC5_CHAN_TEMP(8, SCALE_HW_CALIB_DEFAULT) + [ADC5_GEN3_DIE_TEMP] = ADC5_CHAN_TEMP(0, + SCALE_HW_CALIB_PMIC_THERM_PM7) + [ADC5_GEN3_AMUX1_THM_100K_PU] = ADC5_CHAN_TEMP(0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX2_THM_100K_PU] = ADC5_CHAN_TEMP(0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX3_THM_100K_PU] = ADC5_CHAN_TEMP(0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX4_THM_100K_PU] = ADC5_CHAN_TEMP(0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX5_THM_100K_PU] = ADC5_CHAN_TEMP(0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX6_THM_100K_PU] = ADC5_CHAN_TEMP(0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX1_GPIO_100K_PU] = ADC5_CHAN_TEMP(0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX2_GPIO_100K_PU] = ADC5_CHAN_TEMP(0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX3_GPIO_100K_PU] = ADC5_CHAN_TEMP(0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC5_GEN3_AMUX4_GPIO_100K_PU] = ADC5_CHAN_TEMP(0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) +}; + +static int adc5_gen3_get_fw_channel_data(struct adc5_chip *adc, + struct adc5_channel_prop *prop, + struct fwnode_handle *fwnode) +{ + const char *name = fwnode_get_name(fwnode); + const struct adc5_data *data = adc->data; + struct device *dev = adc->dev; + const char *channel_name; + u32 chan, value, sid; + u32 varr[2]; + int ret; + + ret = fwnode_property_read_u32(fwnode, "reg", &chan); + if (ret < 0) + return dev_err_probe(dev, ret, "invalid channel number %s\n", + name); + + /* + * Value read from "reg" is virtual channel number + * virtual channel number = sid << 8 | channel number + */ + sid = FIELD_GET(ADC5_GEN3_VIRTUAL_SID_MASK, chan); + chan = FIELD_GET(ADC5_GEN3_CHANNEL_MASK, chan); + + if (chan >= ADC5_MAX_CHANNEL) + return dev_err_probe(dev, -EINVAL, + "%s invalid channel number %d\n", + name, chan); + + prop->common_props.channel = chan; + prop->common_props.sid = sid; + + if (!adc->data->adc_chans[chan].info_mask) + return dev_err_probe(dev, -EINVAL, "Channel %#x not supported\n", chan); + + channel_name = name; + fwnode_property_read_string(fwnode, "label", &channel_name); + prop->common_props.label = channel_name; + + value = data->decimation[ADC5_DECIMATION_DEFAULT]; + fwnode_property_read_u32(fwnode, "qcom,decimation", &value); + ret = qcom_adc5_decimation_from_dt(value, data->decimation); + if (ret < 0) + return dev_err_probe(dev, ret, "%#x invalid decimation %d\n", + chan, value); + prop->common_props.decimation = ret; + + prop->common_props.prescale = adc->data->adc_chans[chan].prescale_index; + ret = fwnode_property_read_u32_array(fwnode, "qcom,pre-scaling", varr, 2); + if (!ret) { + ret = qcom_adc5_prescaling_from_dt(varr[0], varr[1]); + if (ret < 0) + return dev_err_probe(dev, ret, + "%#x invalid pre-scaling <%d %d>\n", + chan, varr[0], varr[1]); + prop->common_props.prescale = ret; + } + + value = data->hw_settle_1[VADC_DEF_HW_SETTLE_TIME]; + fwnode_property_read_u32(fwnode, "qcom,hw-settle-time", &value); + ret = qcom_adc5_hw_settle_time_from_dt(value, data->hw_settle_1); + if (ret < 0) + return dev_err_probe(dev, ret, + "%#x invalid hw-settle-time %d us\n", + chan, value); + prop->common_props.hw_settle_time_us = ret; + + value = BIT(VADC_DEF_AVG_SAMPLES); + fwnode_property_read_u32(fwnode, "qcom,avg-samples", &value); + ret = qcom_adc5_avg_samples_from_dt(value); + if (ret < 0) + return dev_err_probe(dev, ret, "%#x invalid avg-samples %d\n", + chan, value); + prop->common_props.avg_samples = ret; + + if (fwnode_property_read_bool(fwnode, "qcom,ratiometric")) + prop->common_props.cal_method = ADC5_RATIOMETRIC_CAL; + else + prop->common_props.cal_method = ADC5_ABSOLUTE_CAL; + + prop->adc_tm = fwnode_property_read_bool(fwnode, "qcom,adc-tm"); + if (prop->adc_tm) { + adc->n_tm_channels++; + if (adc->n_tm_channels > (adc->dev_data.num_sdams * 8 - 1)) + return dev_err_probe(dev, -EINVAL, + "Number of TM nodes %u greater than channels supported:%u\n", + adc->n_tm_channels, + adc->dev_data.num_sdams * 8 - 1); + } + + return 0; +} + +static const struct adc5_data adc5_gen3_data_pmic = { + .full_scale_code_volt = 0x70e4, + .adc_chans = adc5_gen3_chans_pmic, + .info = &adc5_gen3_info, + .decimation = (unsigned int [ADC5_DECIMATION_SAMPLES_MAX]) + { 85, 340, 1360 }, + .hw_settle_1 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX]) + { 15, 100, 200, 300, + 400, 500, 600, 700, + 1000, 2000, 4000, 8000, + 16000, 32000, 64000, 128000 }, +}; + +static const struct of_device_id adc5_match_table[] = { + { + .compatible = "qcom,spmi-adc5-gen3", + .data = &adc5_gen3_data_pmic, + }, + { } +}; +MODULE_DEVICE_TABLE(of, adc5_match_table); + +static int adc5_get_fw_data(struct adc5_chip *adc) +{ + const struct adc5_channels *adc_chan; + struct adc5_channel_prop *chan_props; + struct iio_chan_spec *iio_chan; + struct device *dev = adc->dev; + unsigned int index = 0; + int ret; + + adc->nchannels = device_get_child_node_count(dev); + if (!adc->nchannels) + return dev_err_probe(dev, -EINVAL, "No ADC channels found\n"); + + adc->iio_chans = devm_kcalloc(dev, adc->nchannels, + sizeof(*adc->iio_chans), GFP_KERNEL); + if (!adc->iio_chans) + return -ENOMEM; + + adc->chan_props = devm_kcalloc(dev, adc->nchannels, + sizeof(*adc->chan_props), GFP_KERNEL); + if (!adc->chan_props) + return -ENOMEM; + + chan_props = adc->chan_props; + adc->n_tm_channels = 0; + iio_chan = adc->iio_chans; + adc->data = device_get_match_data(dev); + + device_for_each_child_node_scoped(dev, child) { + ret = adc5_gen3_get_fw_channel_data(adc, chan_props, child); + if (ret) + return ret; + + chan_props->chip = adc; + adc_chan = &adc->data->adc_chans[chan_props->common_props.channel]; + chan_props->common_props.scale_fn_type = adc_chan->scale_fn_type; + + iio_chan->channel = ADC5_GEN3_V_CHAN(chan_props->common_props); + iio_chan->info_mask_separate = adc_chan->info_mask; + iio_chan->type = adc_chan->type; + iio_chan->address = index; + iio_chan->indexed = 1; + iio_chan++; + chan_props++; + index++; + } + + return 0; +} + +static void adc5_gen3_uninit_aux(void *data) +{ + auxiliary_device_uninit(data); +} + +static void adc5_gen3_delete_aux(void *data) +{ + auxiliary_device_delete(data); +} + +static void adc5_gen3_aux_device_release(struct device *dev) {} + +static int adc5_gen3_add_aux_tm_device(struct adc5_chip *adc) +{ + struct tm5_aux_dev_wrapper *aux_device; + int i, ret, i_tm = 0; + + aux_device = devm_kzalloc(adc->dev, sizeof(*aux_device), GFP_KERNEL); + if (!aux_device) + return -ENOMEM; + + aux_device->aux_dev.name = "adc5_tm_gen3"; + aux_device->aux_dev.dev.parent = adc->dev; + aux_device->aux_dev.dev.release = adc5_gen3_aux_device_release; + + aux_device->tm_props = devm_kcalloc(adc->dev, adc->n_tm_channels, + sizeof(*aux_device->tm_props), + GFP_KERNEL); + if (!aux_device->tm_props) + return -ENOMEM; + + aux_device->dev_data = &adc->dev_data; + + for (i = 0; i < adc->nchannels; i++) { + if (!adc->chan_props[i].adc_tm) + continue; + aux_device->tm_props[i_tm] = adc->chan_props[i].common_props; + i_tm++; + } + + device_set_of_node_from_dev(&aux_device->aux_dev.dev, adc->dev); + + aux_device->n_tm_channels = adc->n_tm_channels; + + ret = auxiliary_device_init(&aux_device->aux_dev); + if (ret) + return ret; + + ret = devm_add_action_or_reset(adc->dev, adc5_gen3_uninit_aux, + &aux_device->aux_dev); + if (ret) + return ret; + + ret = auxiliary_device_add(&aux_device->aux_dev); + if (ret) + return ret; + ret = devm_add_action_or_reset(adc->dev, adc5_gen3_delete_aux, + &aux_device->aux_dev); + if (ret) + return ret; + + return 0; +} + +void adc5_gen3_mutex_lock(struct device *dev) + __acquires(&adc->lock) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev->parent); + struct adc5_chip *adc = iio_priv(indio_dev); + + mutex_lock(&adc->lock); +} +EXPORT_SYMBOL_NS_GPL(adc5_gen3_mutex_lock, "QCOM_SPMI_ADC5_GEN3"); + +void adc5_gen3_mutex_unlock(struct device *dev) + __releases(&adc->lock) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev->parent); + struct adc5_chip *adc = iio_priv(indio_dev); + + mutex_unlock(&adc->lock); +} +EXPORT_SYMBOL_NS_GPL(adc5_gen3_mutex_unlock, "QCOM_SPMI_ADC5_GEN3"); + +int adc5_gen3_get_scaled_reading(struct device *dev, + struct adc5_channel_common_prop *common_props, + int *val) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev->parent); + struct adc5_chip *adc = iio_priv(indio_dev); + u16 adc_code_volt; + int ret; + + ret = adc5_gen3_do_conversion(adc, common_props, &adc_code_volt); + if (ret) + return ret; + + return qcom_adc5_hw_scale(common_props->scale_fn_type, + common_props->prescale, + adc->data, adc_code_volt, val); +} +EXPORT_SYMBOL_NS_GPL(adc5_gen3_get_scaled_reading, "QCOM_SPMI_ADC5_GEN3"); + +int adc5_gen3_therm_code_to_temp(struct device *dev, + struct adc5_channel_common_prop *common_props, + u16 code, int *val) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev->parent); + struct adc5_chip *adc = iio_priv(indio_dev); + + return qcom_adc5_hw_scale(common_props->scale_fn_type, + common_props->prescale, + adc->data, code, val); +} +EXPORT_SYMBOL_NS_GPL(adc5_gen3_therm_code_to_temp, "QCOM_SPMI_ADC5_GEN3"); + +static int adc5_gen3_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct adc5_chip *adc; + struct regmap *regmap; + int ret, i; + u32 *reg; + + regmap = dev_get_regmap(dev->parent, NULL); + if (!regmap) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->dev_data.regmap = regmap; + adc->dev = dev; + + ret = device_property_count_u32(dev, "reg"); + if (ret < 0) + return ret; + + adc->dev_data.num_sdams = ret; + + reg = devm_kcalloc(dev, adc->dev_data.num_sdams, sizeof(u32), + GFP_KERNEL); + if (!reg) + return -ENOMEM; + + ret = device_property_read_u32_array(dev, "reg", reg, + adc->dev_data.num_sdams); + if (ret) + return dev_err_probe(dev, ret, + "Failed to read reg property\n"); + + adc->dev_data.base = devm_kcalloc(dev, adc->dev_data.num_sdams, + sizeof(*adc->dev_data.base), + GFP_KERNEL); + if (!adc->dev_data.base) + return -ENOMEM; + + platform_set_drvdata(pdev, indio_dev); + init_completion(&adc->complete); + ret = devm_mutex_init(dev, &adc->lock); + if (ret) + return ret; + + for (i = 0; i < adc->dev_data.num_sdams; i++) { + adc->dev_data.base[i].base_addr = reg[i]; + + ret = platform_get_irq(pdev, i); + if (ret < 0) + return dev_err_probe(dev, ret, + "Getting IRQ %d failed\n", i); + + adc->dev_data.base[i].irq = ret; + + adc->dev_data.base[i].irq_name = devm_kasprintf(dev, GFP_KERNEL, + "sdam%d", i); + if (!adc->dev_data.base[i].irq_name) + return -ENOMEM; + } + + /* + * This interrupt is shared with the ADC_TM auxiliary driver, which + * is threaded and uses IRQF_ONESHOT. Since shared interrupts need + * to agree on IRQF_ONESHOT configuration and there is a kernel + * warning for using IRQF_ONESHOT with non-threaded interrupts, + * make this also a threaded IRQ. + */ + ret = devm_request_threaded_irq(dev, adc->dev_data.base[ADC5_GEN3_VADC_SDAM].irq, + NULL, adc5_gen3_isr, IRQF_ONESHOT | IRQF_SHARED, + adc->dev_data.base[ADC5_GEN3_VADC_SDAM].irq_name, + adc); + if (ret) + return ret; + + ret = adc5_get_fw_data(adc); + if (ret) + return ret; + + if (adc->n_tm_channels > 0) { + ret = adc5_gen3_add_aux_tm_device(adc); + if (ret) + dev_err_probe(dev, ret, + "Failed to add auxiliary TM device\n"); + } + + indio_dev->name = "spmi-adc5-gen3"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &adc5_gen3_info; + indio_dev->channels = adc->iio_chans; + indio_dev->num_channels = adc->nchannels; + + return devm_iio_device_register(dev, indio_dev); +} + +static struct platform_driver adc5_gen3_driver = { + .driver = { + .name = "qcom-spmi-adc5-gen3", + .of_match_table = adc5_match_table, + }, + .probe = adc5_gen3_probe, +}; +module_platform_driver(adc5_gen3_driver); + +MODULE_DESCRIPTION("Qualcomm Technologies Inc. PMIC5 Gen3 ADC driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("QCOM_SPMI_ADC5_GEN3"); diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c new file mode 100644 index 000000000..83ecd3adf --- /dev/null +++ b/drivers/iio/adc/qcom-spmi-adc5.c @@ -0,0 +1,939 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018, 2020, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define ADC5_USR_REVISION1 0x0 +#define ADC5_USR_STATUS1 0x8 +#define ADC5_USR_STATUS1_CONV_FAULT BIT(7) +#define ADC5_USR_STATUS1_REQ_STS BIT(1) +#define ADC5_USR_STATUS1_EOC BIT(0) +#define ADC5_USR_STATUS1_REQ_STS_EOC_MASK 0x3 + +#define ADC5_USR_STATUS2 0x9 +#define ADC5_USR_STATUS2_CONV_SEQ_MASK 0x70 +#define ADC5_USR_STATUS2_CONV_SEQ_MASK_SHIFT 0x5 + +#define ADC5_USR_IBAT_MEAS 0xf +#define ADC5_USR_IBAT_MEAS_SUPPORTED BIT(0) + +#define ADC5_USR_DIG_PARAM 0x42 +#define ADC5_USR_DIG_PARAM_CAL_VAL BIT(6) +#define ADC5_USR_DIG_PARAM_CAL_VAL_SHIFT 6 +#define ADC5_USR_DIG_PARAM_CAL_SEL 0x30 +#define ADC5_USR_DIG_PARAM_CAL_SEL_SHIFT 4 +#define ADC5_USR_DIG_PARAM_DEC_RATIO_SEL 0xc +#define ADC5_USR_DIG_PARAM_DEC_RATIO_SEL_SHIFT 2 + +#define ADC5_USR_FAST_AVG_CTL 0x43 +#define ADC5_USR_FAST_AVG_CTL_EN BIT(7) +#define ADC5_USR_FAST_AVG_CTL_SAMPLES_MASK 0x7 + +#define ADC5_USR_CH_SEL_CTL 0x44 + +#define ADC5_USR_DELAY_CTL 0x45 +#define ADC5_USR_HW_SETTLE_DELAY_MASK 0xf + +#define ADC5_USR_EN_CTL1 0x46 +#define ADC5_USR_EN_CTL1_ADC_EN BIT(7) + +#define ADC5_USR_CONV_REQ 0x47 +#define ADC5_USR_CONV_REQ_REQ BIT(7) + +#define ADC5_USR_DATA0 0x50 + +#define ADC5_USR_DATA1 0x51 + +#define ADC5_USR_IBAT_DATA0 0x52 + +#define ADC5_USR_IBAT_DATA1 0x53 + +#define ADC_CHANNEL_OFFSET 0x8 +#define ADC_CHANNEL_MASK GENMASK(7, 0) + +/* + * Conversion time varies based on the decimation, clock rate, fast average + * samples and measurements queued across different VADC peripherals. + * Set the timeout to a max of 100ms. + */ +#define ADC5_CONV_TIME_MIN_US 263 +#define ADC5_CONV_TIME_MAX_US 264 +#define ADC5_CONV_TIME_RETRY 400 +#define ADC5_CONV_TIMEOUT msecs_to_jiffies(100) + +/* Digital version >= 5.3 supports hw_settle_2 */ +#define ADC5_HW_SETTLE_DIFF_MINOR 3 +#define ADC5_HW_SETTLE_DIFF_MAJOR 5 + +/* For PMIC7 */ +#define ADC_APP_SID 0x40 +#define ADC_APP_SID_MASK GENMASK(3, 0) +#define ADC7_CONV_TIMEOUT msecs_to_jiffies(10) + +enum adc5_cal_method { + ADC5_NO_CAL = 0, + ADC5_RATIOMETRIC_CAL, + ADC5_ABSOLUTE_CAL +}; + +enum adc5_cal_val { + ADC5_TIMER_CAL = 0, + ADC5_NEW_CAL +}; + +/** + * struct adc5_channel_prop - ADC channel property. + * @channel: channel number, refer to the channel list. + * @cal_method: calibration method. + * @cal_val: calibration value + * @decimation: sampling rate supported for the channel. + * @sid: slave id of PMIC owning the channel, for PMIC7. + * @prescale: channel scaling performed on the input signal. + * @hw_settle_time: the time between AMUX being configured and the + * start of conversion. + * @avg_samples: ability to provide single result from the ADC + * that is an average of multiple measurements. + * @scale_fn_type: Represents the scaling function to convert voltage + * physical units desired by the client for the channel. + * @channel_name: Channel name used in device tree. + */ +struct adc5_channel_prop { + unsigned int channel; + enum adc5_cal_method cal_method; + enum adc5_cal_val cal_val; + unsigned int decimation; + unsigned int sid; + unsigned int prescale; + unsigned int hw_settle_time; + unsigned int avg_samples; + enum vadc_scale_fn_type scale_fn_type; + const char *channel_name; +}; + +/** + * struct adc5_chip - ADC private structure. + * @regmap: SPMI ADC5 peripheral register map field. + * @dev: SPMI ADC5 device. + * @base: base address for the ADC peripheral. + * @nchannels: number of ADC channels. + * @chan_props: array of ADC channel properties. + * @iio_chans: array of IIO channels specification. + * @poll_eoc: use polling instead of interrupt. + * @complete: ADC result notification after interrupt is received. + * @lock: ADC lock for access to the peripheral. + * @data: software configuration data. + */ +struct adc5_chip { + struct regmap *regmap; + struct device *dev; + u16 base; + unsigned int nchannels; + struct adc5_channel_prop *chan_props; + struct iio_chan_spec *iio_chans; + bool poll_eoc; + struct completion complete; + struct mutex lock; + const struct adc5_data *data; +}; + +static int adc5_read(struct adc5_chip *adc, u16 offset, u8 *data, int len) +{ + return regmap_bulk_read(adc->regmap, adc->base + offset, data, len); +} + +static int adc5_write(struct adc5_chip *adc, u16 offset, u8 *data, int len) +{ + return regmap_bulk_write(adc->regmap, adc->base + offset, data, len); +} + +static int adc5_masked_write(struct adc5_chip *adc, u16 offset, u8 mask, u8 val) +{ + return regmap_update_bits(adc->regmap, adc->base + offset, mask, val); +} + +static int adc5_read_voltage_data(struct adc5_chip *adc, u16 *data) +{ + int ret; + u8 rslt_lsb, rslt_msb; + + ret = adc5_read(adc, ADC5_USR_DATA0, &rslt_lsb, sizeof(rslt_lsb)); + if (ret) + return ret; + + ret = adc5_read(adc, ADC5_USR_DATA1, &rslt_msb, sizeof(rslt_lsb)); + if (ret) + return ret; + + *data = (rslt_msb << 8) | rslt_lsb; + + if (*data == ADC5_USR_DATA_CHECK) { + dev_err(adc->dev, "Invalid data:0x%x\n", *data); + return -EINVAL; + } + + dev_dbg(adc->dev, "voltage raw code:0x%x\n", *data); + + return 0; +} + +static int adc5_poll_wait_eoc(struct adc5_chip *adc) +{ + unsigned int count, retry = ADC5_CONV_TIME_RETRY; + u8 status1; + int ret; + + for (count = 0; count < retry; count++) { + ret = adc5_read(adc, ADC5_USR_STATUS1, &status1, + sizeof(status1)); + if (ret) + return ret; + + status1 &= ADC5_USR_STATUS1_REQ_STS_EOC_MASK; + if (status1 == ADC5_USR_STATUS1_EOC) + return 0; + + usleep_range(ADC5_CONV_TIME_MIN_US, ADC5_CONV_TIME_MAX_US); + } + + return -ETIMEDOUT; +} + +static void adc5_update_dig_param(struct adc5_chip *adc, + struct adc5_channel_prop *prop, u8 *data) +{ + /* Update calibration value */ + *data &= ~ADC5_USR_DIG_PARAM_CAL_VAL; + *data |= (prop->cal_val << ADC5_USR_DIG_PARAM_CAL_VAL_SHIFT); + + /* Update calibration select */ + *data &= ~ADC5_USR_DIG_PARAM_CAL_SEL; + *data |= (prop->cal_method << ADC5_USR_DIG_PARAM_CAL_SEL_SHIFT); + + /* Update decimation ratio select */ + *data &= ~ADC5_USR_DIG_PARAM_DEC_RATIO_SEL; + *data |= (prop->decimation << ADC5_USR_DIG_PARAM_DEC_RATIO_SEL_SHIFT); +} + +static int adc5_configure(struct adc5_chip *adc, + struct adc5_channel_prop *prop) +{ + int ret; + u8 buf[6]; + + /* Read registers 0x42 through 0x46 */ + ret = adc5_read(adc, ADC5_USR_DIG_PARAM, buf, sizeof(buf)); + if (ret) + return ret; + + /* Digital param selection */ + adc5_update_dig_param(adc, prop, &buf[0]); + + /* Update fast average sample value */ + buf[1] &= (u8) ~ADC5_USR_FAST_AVG_CTL_SAMPLES_MASK; + buf[1] |= prop->avg_samples; + + /* Select ADC channel */ + buf[2] = prop->channel; + + /* Select HW settle delay for channel */ + buf[3] &= (u8) ~ADC5_USR_HW_SETTLE_DELAY_MASK; + buf[3] |= prop->hw_settle_time; + + /* Select ADC enable */ + buf[4] |= ADC5_USR_EN_CTL1_ADC_EN; + + /* Select CONV request */ + buf[5] |= ADC5_USR_CONV_REQ_REQ; + + if (!adc->poll_eoc) + reinit_completion(&adc->complete); + + return adc5_write(adc, ADC5_USR_DIG_PARAM, buf, sizeof(buf)); +} + +static int adc7_configure(struct adc5_chip *adc, + struct adc5_channel_prop *prop) +{ + int ret; + u8 conv_req = 0, buf[4]; + + ret = adc5_masked_write(adc, ADC_APP_SID, ADC_APP_SID_MASK, prop->sid); + if (ret) + return ret; + + ret = adc5_read(adc, ADC5_USR_DIG_PARAM, buf, sizeof(buf)); + if (ret) + return ret; + + /* Digital param selection */ + adc5_update_dig_param(adc, prop, &buf[0]); + + /* Update fast average sample value */ + buf[1] &= ~ADC5_USR_FAST_AVG_CTL_SAMPLES_MASK; + buf[1] |= prop->avg_samples; + + /* Select ADC channel */ + buf[2] = prop->channel; + + /* Select HW settle delay for channel */ + buf[3] &= ~ADC5_USR_HW_SETTLE_DELAY_MASK; + buf[3] |= prop->hw_settle_time; + + /* Select CONV request */ + conv_req = ADC5_USR_CONV_REQ_REQ; + + if (!adc->poll_eoc) + reinit_completion(&adc->complete); + + ret = adc5_write(adc, ADC5_USR_DIG_PARAM, buf, sizeof(buf)); + if (ret) + return ret; + + return adc5_write(adc, ADC5_USR_CONV_REQ, &conv_req, 1); +} + +static int adc5_do_conversion(struct adc5_chip *adc, + struct adc5_channel_prop *prop, + struct iio_chan_spec const *chan, + u16 *data_volt, u16 *data_cur) +{ + int ret; + + mutex_lock(&adc->lock); + + ret = adc5_configure(adc, prop); + if (ret) { + dev_err(adc->dev, "ADC configure failed with %d\n", ret); + goto unlock; + } + + if (adc->poll_eoc) { + ret = adc5_poll_wait_eoc(adc); + if (ret) { + dev_err(adc->dev, "EOC bit not set\n"); + goto unlock; + } + } else { + ret = wait_for_completion_timeout(&adc->complete, + ADC5_CONV_TIMEOUT); + if (!ret) { + dev_dbg(adc->dev, "Did not get completion timeout.\n"); + ret = adc5_poll_wait_eoc(adc); + if (ret) { + dev_err(adc->dev, "EOC bit not set\n"); + goto unlock; + } + } + } + + ret = adc5_read_voltage_data(adc, data_volt); +unlock: + mutex_unlock(&adc->lock); + + return ret; +} + +static int adc7_do_conversion(struct adc5_chip *adc, + struct adc5_channel_prop *prop, + struct iio_chan_spec const *chan, + u16 *data_volt, u16 *data_cur) +{ + int ret; + u8 status; + + mutex_lock(&adc->lock); + + ret = adc7_configure(adc, prop); + if (ret) { + dev_err(adc->dev, "ADC configure failed with %d\n", ret); + goto unlock; + } + + /* No support for polling mode at present */ + wait_for_completion_timeout(&adc->complete, ADC7_CONV_TIMEOUT); + + ret = adc5_read(adc, ADC5_USR_STATUS1, &status, 1); + if (ret) + goto unlock; + + if (status & ADC5_USR_STATUS1_CONV_FAULT) { + dev_err(adc->dev, "Unexpected conversion fault\n"); + ret = -EIO; + goto unlock; + } + + ret = adc5_read_voltage_data(adc, data_volt); + +unlock: + mutex_unlock(&adc->lock); + + return ret; +} + +typedef int (*adc_do_conversion)(struct adc5_chip *adc, + struct adc5_channel_prop *prop, + struct iio_chan_spec const *chan, + u16 *data_volt, u16 *data_cur); + +static irqreturn_t adc5_isr(int irq, void *dev_id) +{ + struct adc5_chip *adc = dev_id; + + complete(&adc->complete); + + return IRQ_HANDLED; +} + +static int adc5_fwnode_xlate(struct iio_dev *indio_dev, + const struct fwnode_reference_args *iiospec) +{ + struct adc5_chip *adc = iio_priv(indio_dev); + int i; + + for (i = 0; i < adc->nchannels; i++) + if (adc->chan_props[i].channel == iiospec->args[0]) + return i; + + return -EINVAL; +} + +static int adc7_fwnode_xlate(struct iio_dev *indio_dev, + const struct fwnode_reference_args *iiospec) +{ + struct adc5_chip *adc = iio_priv(indio_dev); + int i, v_channel; + + for (i = 0; i < adc->nchannels; i++) { + v_channel = (adc->chan_props[i].sid << ADC_CHANNEL_OFFSET) | + adc->chan_props[i].channel; + if (v_channel == iiospec->args[0]) + return i; + } + + return -EINVAL; +} + +static int adc_read_raw_common(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, + long mask, adc_do_conversion do_conv) +{ + struct adc5_chip *adc = iio_priv(indio_dev); + struct adc5_channel_prop *prop; + u16 adc_code_volt, adc_code_cur; + int ret; + + prop = &adc->chan_props[chan->address]; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ret = do_conv(adc, prop, chan, + &adc_code_volt, &adc_code_cur); + if (ret) + return ret; + + ret = qcom_adc5_hw_scale(prop->scale_fn_type, + prop->prescale, + adc->data, + adc_code_volt, val); + if (ret) + return ret; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int adc5_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, + long mask) +{ + return adc_read_raw_common(indio_dev, chan, val, val2, + mask, adc5_do_conversion); +} + +static int adc7_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, + long mask) +{ + return adc_read_raw_common(indio_dev, chan, val, val2, + mask, adc7_do_conversion); +} + +static const struct iio_info adc5_info = { + .read_raw = adc5_read_raw, + .fwnode_xlate = adc5_fwnode_xlate, +}; + +static const struct iio_info adc7_info = { + .read_raw = adc7_read_raw, + .fwnode_xlate = adc7_fwnode_xlate, +}; + +struct adc5_channels { + const char *datasheet_name; + unsigned int prescale_index; + enum iio_chan_type type; + long info_mask; + enum vadc_scale_fn_type scale_fn_type; +}; + +/* In these definitions, _pre refers to an index into adc5_prescale_ratios. */ +#define ADC5_CHAN(_dname, _type, _mask, _pre, _scale) \ + { \ + .datasheet_name = _dname, \ + .prescale_index = _pre, \ + .type = _type, \ + .info_mask = _mask, \ + .scale_fn_type = _scale, \ + }, \ + +#define ADC5_CHAN_TEMP(_dname, _pre, _scale) \ + ADC5_CHAN(_dname, IIO_TEMP, \ + BIT(IIO_CHAN_INFO_PROCESSED), \ + _pre, _scale) \ + +#define ADC5_CHAN_VOLT(_dname, _pre, _scale) \ + ADC5_CHAN(_dname, IIO_VOLTAGE, \ + BIT(IIO_CHAN_INFO_PROCESSED), \ + _pre, _scale) \ + +static const struct adc5_channels adc5_chans_pmic[ADC5_MAX_CHANNEL] = { + [ADC5_REF_GND] = ADC5_CHAN_VOLT("ref_gnd", 0, + SCALE_HW_CALIB_DEFAULT) + [ADC5_1P25VREF] = ADC5_CHAN_VOLT("vref_1p25", 0, + SCALE_HW_CALIB_DEFAULT) + [ADC5_VPH_PWR] = ADC5_CHAN_VOLT("vph_pwr", 1, + SCALE_HW_CALIB_DEFAULT) + [ADC5_VBAT_SNS] = ADC5_CHAN_VOLT("vbat_sns", 1, + SCALE_HW_CALIB_DEFAULT) + [ADC5_VCOIN] = ADC5_CHAN_VOLT("vcoin", 1, + SCALE_HW_CALIB_DEFAULT) + [ADC5_DIE_TEMP] = ADC5_CHAN_TEMP("die_temp", 0, + SCALE_HW_CALIB_PMIC_THERM) + [ADC5_USB_IN_I] = ADC5_CHAN_VOLT("usb_in_i_uv", 0, + SCALE_HW_CALIB_DEFAULT) + [ADC5_USB_IN_V_16] = ADC5_CHAN_VOLT("usb_in_v_div_16", 8, + SCALE_HW_CALIB_DEFAULT) + [ADC5_CHG_TEMP] = ADC5_CHAN_TEMP("chg_temp", 0, + SCALE_HW_CALIB_PM5_CHG_TEMP) + /* Charger prescales SBUx and MID_CHG to fit within 1.8V upper unit */ + [ADC5_SBUx] = ADC5_CHAN_VOLT("chg_sbux", 1, + SCALE_HW_CALIB_DEFAULT) + [ADC5_MID_CHG_DIV6] = ADC5_CHAN_VOLT("chg_mid_chg", 3, + SCALE_HW_CALIB_DEFAULT) + [ADC5_XO_THERM_100K_PU] = ADC5_CHAN_TEMP("xo_therm", 0, + SCALE_HW_CALIB_XOTHERM) + [ADC5_BAT_ID_100K_PU] = ADC5_CHAN_TEMP("bat_id", 0, + SCALE_HW_CALIB_DEFAULT) + [ADC5_AMUX_THM1_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_100k_pu", 0, + SCALE_HW_CALIB_THERM_100K_PULLUP) + [ADC5_AMUX_THM2_100K_PU] = ADC5_CHAN_TEMP("amux_thm2_100k_pu", 0, + SCALE_HW_CALIB_THERM_100K_PULLUP) + [ADC5_AMUX_THM3_100K_PU] = ADC5_CHAN_TEMP("amux_thm3_100k_pu", 0, + SCALE_HW_CALIB_THERM_100K_PULLUP) + [ADC5_AMUX_THM2] = ADC5_CHAN_TEMP("amux_thm2", 0, + SCALE_HW_CALIB_PM5_SMB_TEMP) + [ADC5_GPIO1_100K_PU] = ADC5_CHAN_TEMP("gpio1_100k_pu", 0, + SCALE_HW_CALIB_THERM_100K_PULLUP) + [ADC5_GPIO2_100K_PU] = ADC5_CHAN_TEMP("gpio2_100k_pu", 0, + SCALE_HW_CALIB_THERM_100K_PULLUP) + [ADC5_GPIO3_100K_PU] = ADC5_CHAN_TEMP("gpio3_100k_pu", 0, + SCALE_HW_CALIB_THERM_100K_PULLUP) + [ADC5_GPIO4_100K_PU] = ADC5_CHAN_TEMP("gpio4_100k_pu", 0, + SCALE_HW_CALIB_THERM_100K_PULLUP) +}; + +static const struct adc5_channels adc7_chans_pmic[ADC5_MAX_CHANNEL] = { + [ADC7_REF_GND] = ADC5_CHAN_VOLT("ref_gnd", 0, + SCALE_HW_CALIB_DEFAULT) + [ADC7_1P25VREF] = ADC5_CHAN_VOLT("vref_1p25", 0, + SCALE_HW_CALIB_DEFAULT) + [ADC7_VPH_PWR] = ADC5_CHAN_VOLT("vph_pwr", 1, + SCALE_HW_CALIB_DEFAULT) + [ADC7_VBAT_SNS] = ADC5_CHAN_VOLT("vbat_sns", 3, + SCALE_HW_CALIB_DEFAULT) + [ADC7_DIE_TEMP] = ADC5_CHAN_TEMP("die_temp", 0, + SCALE_HW_CALIB_PMIC_THERM_PM7) + [ADC7_AMUX_THM1_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC7_AMUX_THM2_100K_PU] = ADC5_CHAN_TEMP("amux_thm2_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC7_AMUX_THM3_100K_PU] = ADC5_CHAN_TEMP("amux_thm3_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC7_AMUX_THM4_100K_PU] = ADC5_CHAN_TEMP("amux_thm4_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC7_AMUX_THM5_100K_PU] = ADC5_CHAN_TEMP("amux_thm5_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC7_AMUX_THM6_100K_PU] = ADC5_CHAN_TEMP("amux_thm6_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC7_GPIO1_100K_PU] = ADC5_CHAN_TEMP("gpio1_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC7_GPIO2_100K_PU] = ADC5_CHAN_TEMP("gpio2_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC7_GPIO3_100K_PU] = ADC5_CHAN_TEMP("gpio3_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) + [ADC7_GPIO4_100K_PU] = ADC5_CHAN_TEMP("gpio4_pu2", 0, + SCALE_HW_CALIB_THERM_100K_PU_PM7) +}; + +static const struct adc5_channels adc5_chans_rev2[ADC5_MAX_CHANNEL] = { + [ADC5_REF_GND] = ADC5_CHAN_VOLT("ref_gnd", 0, + SCALE_HW_CALIB_DEFAULT) + [ADC5_1P25VREF] = ADC5_CHAN_VOLT("vref_1p25", 0, + SCALE_HW_CALIB_DEFAULT) + [ADC5_VREF_VADC] = ADC5_CHAN_VOLT("vref_vadc", 0, + SCALE_HW_CALIB_DEFAULT) + [ADC5_VPH_PWR] = ADC5_CHAN_VOLT("vph_pwr", 1, + SCALE_HW_CALIB_DEFAULT) + [ADC5_VBAT_SNS] = ADC5_CHAN_VOLT("vbat_sns", 1, + SCALE_HW_CALIB_DEFAULT) + [ADC5_VCOIN] = ADC5_CHAN_VOLT("vcoin", 1, + SCALE_HW_CALIB_DEFAULT) + [ADC5_DIE_TEMP] = ADC5_CHAN_TEMP("die_temp", 0, + SCALE_HW_CALIB_PMIC_THERM) + [ADC5_AMUX_THM1_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_100k_pu", 0, + SCALE_HW_CALIB_THERM_100K_PULLUP) + [ADC5_AMUX_THM2_100K_PU] = ADC5_CHAN_TEMP("amux_thm2_100k_pu", 0, + SCALE_HW_CALIB_THERM_100K_PULLUP) + [ADC5_AMUX_THM3_100K_PU] = ADC5_CHAN_TEMP("amux_thm3_100k_pu", 0, + SCALE_HW_CALIB_THERM_100K_PULLUP) + [ADC5_AMUX_THM4_100K_PU] = ADC5_CHAN_TEMP("amux_thm4_100k_pu", 0, + SCALE_HW_CALIB_THERM_100K_PULLUP) + [ADC5_AMUX_THM5_100K_PU] = ADC5_CHAN_TEMP("amux_thm5_100k_pu", 0, + SCALE_HW_CALIB_THERM_100K_PULLUP) + [ADC5_XO_THERM_100K_PU] = ADC5_CHAN_TEMP("xo_therm_100k_pu", 0, + SCALE_HW_CALIB_THERM_100K_PULLUP) +}; + +static int adc5_get_fw_channel_data(struct adc5_chip *adc, + struct adc5_channel_prop *prop, + struct fwnode_handle *fwnode, + const struct adc5_data *data) +{ + const char *channel_name; + char *name; + u32 chan, value, varr[2]; + u32 sid = 0; + int ret; + struct device *dev = adc->dev; + + name = devm_kasprintf(dev, GFP_KERNEL, "%pfwP", fwnode); + if (!name) + return -ENOMEM; + + /* Cut the address part */ + name[strchrnul(name, '@') - name] = '\0'; + + ret = fwnode_property_read_u32(fwnode, "reg", &chan); + if (ret) { + dev_err(dev, "invalid channel number %s\n", name); + return ret; + } + + /* Value read from "reg" is virtual channel number */ + + /* virtual channel number = sid << 8 | channel number */ + + if (adc->data->info == &adc7_info) { + sid = chan >> ADC_CHANNEL_OFFSET; + chan = chan & ADC_CHANNEL_MASK; + } + + if (chan > ADC5_PARALLEL_ISENSE_VBAT_IDATA) { + dev_err(dev, "%s invalid channel number %d\n", name, chan); + return -EINVAL; + } + + /* the channel has DT description */ + prop->channel = chan; + prop->sid = sid; + + ret = fwnode_property_read_string(fwnode, "label", &channel_name); + if (ret) + channel_name = data->adc_chans[chan].datasheet_name; + + prop->channel_name = channel_name; + + ret = fwnode_property_read_u32(fwnode, "qcom,decimation", &value); + if (!ret) { + ret = qcom_adc5_decimation_from_dt(value, data->decimation); + if (ret < 0) { + dev_err(dev, "%02x invalid decimation %d\n", + chan, value); + return ret; + } + prop->decimation = ret; + } else { + prop->decimation = ADC5_DECIMATION_DEFAULT; + } + + ret = fwnode_property_read_u32_array(fwnode, "qcom,pre-scaling", varr, 2); + if (!ret) { + ret = qcom_adc5_prescaling_from_dt(varr[0], varr[1]); + if (ret < 0) { + dev_err(dev, "%02x invalid pre-scaling <%d %d>\n", + chan, varr[0], varr[1]); + return ret; + } + prop->prescale = ret; + } else { + prop->prescale = + adc->data->adc_chans[prop->channel].prescale_index; + } + + ret = fwnode_property_read_u32(fwnode, "qcom,hw-settle-time", &value); + if (!ret) { + u8 dig_version[2]; + + ret = adc5_read(adc, ADC5_USR_REVISION1, dig_version, + sizeof(dig_version)); + if (ret) { + dev_err(dev, "Invalid dig version read %d\n", ret); + return ret; + } + + dev_dbg(dev, "dig_ver:minor:%d, major:%d\n", dig_version[0], + dig_version[1]); + /* Digital controller >= 5.3 have hw_settle_2 option */ + if ((dig_version[0] >= ADC5_HW_SETTLE_DIFF_MINOR && + dig_version[1] >= ADC5_HW_SETTLE_DIFF_MAJOR) || + adc->data->info == &adc7_info) + ret = qcom_adc5_hw_settle_time_from_dt(value, data->hw_settle_2); + else + ret = qcom_adc5_hw_settle_time_from_dt(value, data->hw_settle_1); + + if (ret < 0) { + dev_err(dev, "%02x invalid hw-settle-time %d us\n", + chan, value); + return ret; + } + prop->hw_settle_time = ret; + } else { + prop->hw_settle_time = VADC_DEF_HW_SETTLE_TIME; + } + + ret = fwnode_property_read_u32(fwnode, "qcom,avg-samples", &value); + if (!ret) { + ret = qcom_adc5_avg_samples_from_dt(value); + if (ret < 0) { + dev_err(dev, "%02x invalid avg-samples %d\n", + chan, value); + return ret; + } + prop->avg_samples = ret; + } else { + prop->avg_samples = VADC_DEF_AVG_SAMPLES; + } + + if (fwnode_property_read_bool(fwnode, "qcom,ratiometric")) + prop->cal_method = ADC5_RATIOMETRIC_CAL; + else + prop->cal_method = ADC5_ABSOLUTE_CAL; + + /* + * Default to using timer calibration. Using a fresh calibration value + * for every conversion will increase the overall time for a request. + */ + prop->cal_val = ADC5_TIMER_CAL; + + dev_dbg(dev, "%02x name %s\n", chan, name); + + return 0; +} + +static const struct adc5_data adc5_data_pmic = { + .full_scale_code_volt = 0x70e4, + .full_scale_code_cur = 0x2710, + .adc_chans = adc5_chans_pmic, + .info = &adc5_info, + .decimation = (unsigned int [ADC5_DECIMATION_SAMPLES_MAX]) + {250, 420, 840}, + .hw_settle_1 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX]) + {15, 100, 200, 300, 400, 500, 600, 700, + 800, 900, 1, 2, 4, 6, 8, 10}, + .hw_settle_2 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX]) + {15, 100, 200, 300, 400, 500, 600, 700, + 1, 2, 4, 8, 16, 32, 64, 128}, +}; + +static const struct adc5_data adc7_data_pmic = { + .full_scale_code_volt = 0x70e4, + .adc_chans = adc7_chans_pmic, + .info = &adc7_info, + .decimation = (unsigned int [ADC5_DECIMATION_SAMPLES_MAX]) + {85, 340, 1360}, + .hw_settle_2 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX]) + {15, 100, 200, 300, 400, 500, 600, 700, + 1000, 2000, 4000, 8000, 16000, 32000, + 64000, 128000}, +}; + +static const struct adc5_data adc5_data_pmic_rev2 = { + .full_scale_code_volt = 0x4000, + .full_scale_code_cur = 0x1800, + .adc_chans = adc5_chans_rev2, + .info = &adc5_info, + .decimation = (unsigned int [ADC5_DECIMATION_SAMPLES_MAX]) + {256, 512, 1024}, + .hw_settle_1 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX]) + {0, 100, 200, 300, 400, 500, 600, 700, + 800, 900, 1, 2, 4, 6, 8, 10}, + .hw_settle_2 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX]) + {15, 100, 200, 300, 400, 500, 600, 700, + 1, 2, 4, 8, 16, 32, 64, 128}, +}; + +static const struct of_device_id adc5_match_table[] = { + { + .compatible = "qcom,spmi-adc5", + .data = &adc5_data_pmic, + }, + { + .compatible = "qcom,spmi-adc7", + .data = &adc7_data_pmic, + }, + { + .compatible = "qcom,spmi-adc-rev2", + .data = &adc5_data_pmic_rev2, + }, + { } +}; +MODULE_DEVICE_TABLE(of, adc5_match_table); + +static int adc5_get_fw_data(struct adc5_chip *adc) +{ + const struct adc5_channels *adc_chan; + struct iio_chan_spec *iio_chan; + struct adc5_channel_prop prop, *chan_props; + unsigned int index = 0; + int ret; + + adc->nchannels = device_get_child_node_count(adc->dev); + if (!adc->nchannels) + return dev_err_probe(adc->dev, -EINVAL, "no channels defined\n"); + + adc->iio_chans = devm_kcalloc(adc->dev, adc->nchannels, + sizeof(*adc->iio_chans), GFP_KERNEL); + if (!adc->iio_chans) + return -ENOMEM; + + adc->chan_props = devm_kcalloc(adc->dev, adc->nchannels, + sizeof(*adc->chan_props), GFP_KERNEL); + if (!adc->chan_props) + return -ENOMEM; + + chan_props = adc->chan_props; + iio_chan = adc->iio_chans; + adc->data = device_get_match_data(adc->dev); + if (!adc->data) + adc->data = &adc5_data_pmic; + + device_for_each_child_node_scoped(adc->dev, child) { + ret = adc5_get_fw_channel_data(adc, &prop, child, adc->data); + if (ret) + return ret; + + prop.scale_fn_type = + adc->data->adc_chans[prop.channel].scale_fn_type; + *chan_props = prop; + adc_chan = &adc->data->adc_chans[prop.channel]; + + iio_chan->channel = prop.channel; + iio_chan->datasheet_name = adc_chan->datasheet_name; + iio_chan->extend_name = prop.channel_name; + iio_chan->info_mask_separate = adc_chan->info_mask; + iio_chan->type = adc_chan->type; + iio_chan->address = index; + iio_chan++; + chan_props++; + index++; + } + + return 0; +} + +static int adc5_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct adc5_chip *adc; + struct regmap *regmap; + int ret, irq_eoc; + u32 reg; + + regmap = dev_get_regmap(dev->parent, NULL); + if (!regmap) + return -ENODEV; + + ret = device_property_read_u32(dev, "reg", ®); + if (ret < 0) + return ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->regmap = regmap; + adc->dev = dev; + adc->base = reg; + + init_completion(&adc->complete); + mutex_init(&adc->lock); + + ret = adc5_get_fw_data(adc); + if (ret) + return ret; + + irq_eoc = platform_get_irq(pdev, 0); + if (irq_eoc < 0) { + if (irq_eoc == -EPROBE_DEFER || irq_eoc == -EINVAL) + return irq_eoc; + adc->poll_eoc = true; + } else { + ret = devm_request_irq(dev, irq_eoc, adc5_isr, 0, + "pm-adc5", adc); + if (ret) + return ret; + } + + indio_dev->name = pdev->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = adc->data->info; + indio_dev->channels = adc->iio_chans; + indio_dev->num_channels = adc->nchannels; + + return devm_iio_device_register(dev, indio_dev); +} + +static struct platform_driver adc5_driver = { + .driver = { + .name = "qcom-spmi-adc5", + .of_match_table = adc5_match_table, + }, + .probe = adc5_probe, +}; +module_platform_driver(adc5_driver); + +MODULE_ALIAS("platform:qcom-spmi-adc5"); +MODULE_DESCRIPTION("Qualcomm Technologies Inc. PMIC5 ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/qcom-spmi-iadc.c b/drivers/iio/adc/qcom-spmi-iadc.c new file mode 100644 index 000000000..0ec3a0c4b --- /dev/null +++ b/drivers/iio/adc/qcom-spmi-iadc.c @@ -0,0 +1,598 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* IADC register and bit definition */ +#define IADC_REVISION2 0x1 +#define IADC_REVISION2_SUPPORTED_IADC 1 + +#define IADC_PERPH_TYPE 0x4 +#define IADC_PERPH_TYPE_ADC 8 + +#define IADC_PERPH_SUBTYPE 0x5 +#define IADC_PERPH_SUBTYPE_IADC 3 + +#define IADC_STATUS1 0x8 +#define IADC_STATUS1_OP_MODE 4 +#define IADC_STATUS1_REQ_STS BIT(1) +#define IADC_STATUS1_EOC BIT(0) +#define IADC_STATUS1_REQ_STS_EOC_MASK 0x3 + +#define IADC_MODE_CTL 0x40 +#define IADC_OP_MODE_SHIFT 3 +#define IADC_OP_MODE_NORMAL 0 +#define IADC_TRIM_EN BIT(0) + +#define IADC_EN_CTL1 0x46 +#define IADC_EN_CTL1_SET BIT(7) + +#define IADC_CH_SEL_CTL 0x48 + +#define IADC_DIG_PARAM 0x50 +#define IADC_DIG_DEC_RATIO_SEL_SHIFT 2 + +#define IADC_HW_SETTLE_DELAY 0x51 + +#define IADC_CONV_REQ 0x52 +#define IADC_CONV_REQ_SET BIT(7) + +#define IADC_FAST_AVG_CTL 0x5a +#define IADC_FAST_AVG_EN 0x5b +#define IADC_FAST_AVG_EN_SET BIT(7) + +#define IADC_PERH_RESET_CTL3 0xda +#define IADC_FOLLOW_WARM_RB BIT(2) + +#define IADC_DATA 0x60 /* 16 bits */ + +#define IADC_SEC_ACCESS 0xd0 +#define IADC_SEC_ACCESS_DATA 0xa5 + +#define IADC_NOMINAL_RSENSE 0xf4 +#define IADC_NOMINAL_RSENSE_SIGN_MASK BIT(7) + +#define IADC_REF_GAIN_MICRO_VOLTS 17857 + +#define IADC_INT_RSENSE_DEVIATION 15625 /* nano Ohms per bit */ + +#define IADC_INT_RSENSE_IDEAL_VALUE 10000 /* micro Ohms */ +#define IADC_INT_RSENSE_DEFAULT_VALUE 7800 /* micro Ohms */ +#define IADC_INT_RSENSE_DEFAULT_GF 9000 /* micro Ohms */ +#define IADC_INT_RSENSE_DEFAULT_SMIC 9700 /* micro Ohms */ + +#define IADC_CONV_TIME_MIN_US 2000 +#define IADC_CONV_TIME_MAX_US 2100 + +#define IADC_DEF_PRESCALING 0 /* 1:1 */ +#define IADC_DEF_DECIMATION 0 /* 512 */ +#define IADC_DEF_HW_SETTLE_TIME 0 /* 0 us */ +#define IADC_DEF_AVG_SAMPLES 0 /* 1 sample */ + +/* IADC channel list */ +#define IADC_INT_RSENSE 0 +#define IADC_EXT_RSENSE 1 +#define IADC_GAIN_17P857MV 3 +#define IADC_EXT_OFFSET_CSP_CSN 5 +#define IADC_INT_OFFSET_CSP2_CSN2 6 + +/** + * struct iadc_chip - IADC Current ADC device structure. + * @regmap: regmap for register read/write. + * @dev: This device pointer. + * @base: base offset for the ADC peripheral. + * @rsense: Values of the internal and external sense resister in micro Ohms. + * @poll_eoc: Poll for end of conversion instead of waiting for IRQ. + * @offset: Raw offset values for the internal and external channels. + * @gain: Raw gain of the channels. + * @lock: ADC lock for access to the peripheral. + * @complete: ADC notification after end of conversion interrupt is received. + */ +struct iadc_chip { + struct regmap *regmap; + struct device *dev; + u16 base; + bool poll_eoc; + u32 rsense[2]; + u16 offset[2]; + u16 gain; + struct mutex lock; + struct completion complete; +}; + +static int iadc_read(struct iadc_chip *iadc, u16 offset, u8 *data) +{ + unsigned int val; + int ret; + + ret = regmap_read(iadc->regmap, iadc->base + offset, &val); + if (ret < 0) + return ret; + + *data = val; + return 0; +} + +static int iadc_write(struct iadc_chip *iadc, u16 offset, u8 data) +{ + return regmap_write(iadc->regmap, iadc->base + offset, data); +} + +static int iadc_reset(struct iadc_chip *iadc) +{ + u8 data; + int ret; + + ret = iadc_write(iadc, IADC_SEC_ACCESS, IADC_SEC_ACCESS_DATA); + if (ret < 0) + return ret; + + ret = iadc_read(iadc, IADC_PERH_RESET_CTL3, &data); + if (ret < 0) + return ret; + + ret = iadc_write(iadc, IADC_SEC_ACCESS, IADC_SEC_ACCESS_DATA); + if (ret < 0) + return ret; + + data |= IADC_FOLLOW_WARM_RB; + + return iadc_write(iadc, IADC_PERH_RESET_CTL3, data); +} + +static int iadc_set_state(struct iadc_chip *iadc, bool state) +{ + return iadc_write(iadc, IADC_EN_CTL1, state ? IADC_EN_CTL1_SET : 0); +} + +static void iadc_status_show(struct iadc_chip *iadc) +{ + u8 mode, sta1, chan, dig, en, req; + int ret; + + ret = iadc_read(iadc, IADC_MODE_CTL, &mode); + if (ret < 0) + return; + + ret = iadc_read(iadc, IADC_DIG_PARAM, &dig); + if (ret < 0) + return; + + ret = iadc_read(iadc, IADC_CH_SEL_CTL, &chan); + if (ret < 0) + return; + + ret = iadc_read(iadc, IADC_CONV_REQ, &req); + if (ret < 0) + return; + + ret = iadc_read(iadc, IADC_STATUS1, &sta1); + if (ret < 0) + return; + + ret = iadc_read(iadc, IADC_EN_CTL1, &en); + if (ret < 0) + return; + + dev_err(iadc->dev, + "mode:%02x en:%02x chan:%02x dig:%02x req:%02x sta1:%02x\n", + mode, en, chan, dig, req, sta1); +} + +static int iadc_configure(struct iadc_chip *iadc, int channel) +{ + u8 decim, mode; + int ret; + + /* Mode selection */ + mode = (IADC_OP_MODE_NORMAL << IADC_OP_MODE_SHIFT) | IADC_TRIM_EN; + ret = iadc_write(iadc, IADC_MODE_CTL, mode); + if (ret < 0) + return ret; + + /* Channel selection */ + ret = iadc_write(iadc, IADC_CH_SEL_CTL, channel); + if (ret < 0) + return ret; + + /* Digital parameter setup */ + decim = IADC_DEF_DECIMATION << IADC_DIG_DEC_RATIO_SEL_SHIFT; + ret = iadc_write(iadc, IADC_DIG_PARAM, decim); + if (ret < 0) + return ret; + + /* HW settle time delay */ + ret = iadc_write(iadc, IADC_HW_SETTLE_DELAY, IADC_DEF_HW_SETTLE_TIME); + if (ret < 0) + return ret; + + ret = iadc_write(iadc, IADC_FAST_AVG_CTL, IADC_DEF_AVG_SAMPLES); + if (ret < 0) + return ret; + + if (IADC_DEF_AVG_SAMPLES) + ret = iadc_write(iadc, IADC_FAST_AVG_EN, IADC_FAST_AVG_EN_SET); + else + ret = iadc_write(iadc, IADC_FAST_AVG_EN, 0); + + if (ret < 0) + return ret; + + if (!iadc->poll_eoc) + reinit_completion(&iadc->complete); + + ret = iadc_set_state(iadc, true); + if (ret < 0) + return ret; + + /* Request conversion */ + return iadc_write(iadc, IADC_CONV_REQ, IADC_CONV_REQ_SET); +} + +static int iadc_poll_wait_eoc(struct iadc_chip *iadc, unsigned int interval_us) +{ + unsigned int count, retry; + int ret; + u8 sta1; + + retry = interval_us / IADC_CONV_TIME_MIN_US; + + for (count = 0; count < retry; count++) { + ret = iadc_read(iadc, IADC_STATUS1, &sta1); + if (ret < 0) + return ret; + + sta1 &= IADC_STATUS1_REQ_STS_EOC_MASK; + if (sta1 == IADC_STATUS1_EOC) + return 0; + + usleep_range(IADC_CONV_TIME_MIN_US, IADC_CONV_TIME_MAX_US); + } + + iadc_status_show(iadc); + + return -ETIMEDOUT; +} + +static int iadc_read_result(struct iadc_chip *iadc, u16 *data) +{ + return regmap_bulk_read(iadc->regmap, iadc->base + IADC_DATA, data, 2); +} + +static int iadc_do_conversion(struct iadc_chip *iadc, int chan, u16 *data) +{ + unsigned int wait; + int ret; + + ret = iadc_configure(iadc, chan); + if (ret < 0) + goto exit; + + wait = BIT(IADC_DEF_AVG_SAMPLES) * IADC_CONV_TIME_MIN_US * 2; + + if (iadc->poll_eoc) { + ret = iadc_poll_wait_eoc(iadc, wait); + } else { + ret = wait_for_completion_timeout(&iadc->complete, + usecs_to_jiffies(wait)); + if (!ret) + ret = -ETIMEDOUT; + else + /* double check conversion status */ + ret = iadc_poll_wait_eoc(iadc, IADC_CONV_TIME_MIN_US); + } + + if (!ret) + ret = iadc_read_result(iadc, data); +exit: + iadc_set_state(iadc, false); + if (ret < 0) + dev_err(iadc->dev, "conversion failed\n"); + + return ret; +} + +static int iadc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct iadc_chip *iadc = iio_priv(indio_dev); + s32 isense_ua, vsense_uv; + u16 adc_raw, vsense_raw; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&iadc->lock); + ret = iadc_do_conversion(iadc, chan->channel, &adc_raw); + mutex_unlock(&iadc->lock); + if (ret < 0) + return ret; + + vsense_raw = adc_raw - iadc->offset[chan->channel]; + + vsense_uv = vsense_raw * IADC_REF_GAIN_MICRO_VOLTS; + vsense_uv /= (s32)iadc->gain - iadc->offset[chan->channel]; + + isense_ua = vsense_uv / iadc->rsense[chan->channel]; + + dev_dbg(iadc->dev, "off %d gain %d adc %d %duV I %duA\n", + iadc->offset[chan->channel], iadc->gain, + adc_raw, vsense_uv, isense_ua); + + *val = isense_ua; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = 1000; + return IIO_VAL_INT_PLUS_MICRO; + } + + return -EINVAL; +} + +static const struct iio_info iadc_info = { + .read_raw = iadc_read_raw, +}; + +static irqreturn_t iadc_isr(int irq, void *dev_id) +{ + struct iadc_chip *iadc = dev_id; + + complete(&iadc->complete); + + return IRQ_HANDLED; +} + +static int iadc_update_offset(struct iadc_chip *iadc) +{ + int ret; + + ret = iadc_do_conversion(iadc, IADC_GAIN_17P857MV, &iadc->gain); + if (ret < 0) + return ret; + + ret = iadc_do_conversion(iadc, IADC_INT_OFFSET_CSP2_CSN2, + &iadc->offset[IADC_INT_RSENSE]); + if (ret < 0) + return ret; + + if (iadc->gain == iadc->offset[IADC_INT_RSENSE]) { + dev_err(iadc->dev, "error: internal offset == gain %d\n", + iadc->gain); + return -EINVAL; + } + + ret = iadc_do_conversion(iadc, IADC_EXT_OFFSET_CSP_CSN, + &iadc->offset[IADC_EXT_RSENSE]); + if (ret < 0) + return ret; + + if (iadc->gain == iadc->offset[IADC_EXT_RSENSE]) { + dev_err(iadc->dev, "error: external offset == gain %d\n", + iadc->gain); + return -EINVAL; + } + + return 0; +} + +static int iadc_version_check(struct iadc_chip *iadc) +{ + u8 val; + int ret; + + ret = iadc_read(iadc, IADC_PERPH_TYPE, &val); + if (ret < 0) + return ret; + + if (val < IADC_PERPH_TYPE_ADC) { + dev_err(iadc->dev, "%d is not ADC\n", val); + return -EINVAL; + } + + ret = iadc_read(iadc, IADC_PERPH_SUBTYPE, &val); + if (ret < 0) + return ret; + + if (val < IADC_PERPH_SUBTYPE_IADC) { + dev_err(iadc->dev, "%d is not IADC\n", val); + return -EINVAL; + } + + ret = iadc_read(iadc, IADC_REVISION2, &val); + if (ret < 0) + return ret; + + if (val < IADC_REVISION2_SUPPORTED_IADC) { + dev_err(iadc->dev, "revision %d not supported\n", val); + return -EINVAL; + } + + return 0; +} + +static int iadc_rsense_read(struct iadc_chip *iadc, struct device_node *node) +{ + int ret, sign, int_sense; + u8 deviation; + + ret = of_property_read_u32(node, "qcom,external-resistor-micro-ohms", + &iadc->rsense[IADC_EXT_RSENSE]); + if (ret < 0) + iadc->rsense[IADC_EXT_RSENSE] = IADC_INT_RSENSE_IDEAL_VALUE; + + if (!iadc->rsense[IADC_EXT_RSENSE]) { + dev_err(iadc->dev, "external resistor can't be zero Ohms"); + return -EINVAL; + } + + ret = iadc_read(iadc, IADC_NOMINAL_RSENSE, &deviation); + if (ret < 0) + return ret; + + /* + * Deviation value stored is an offset from 10 mili Ohms, bit 7 is + * the sign, the remaining bits have an LSB of 15625 nano Ohms. + */ + sign = (deviation & IADC_NOMINAL_RSENSE_SIGN_MASK) ? -1 : 1; + + deviation &= ~IADC_NOMINAL_RSENSE_SIGN_MASK; + + /* Scale it to nono Ohms */ + int_sense = IADC_INT_RSENSE_IDEAL_VALUE * 1000; + int_sense += sign * deviation * IADC_INT_RSENSE_DEVIATION; + int_sense /= 1000; /* micro Ohms */ + + iadc->rsense[IADC_INT_RSENSE] = int_sense; + return 0; +} + +static const struct iio_chan_spec iadc_channels[] = { + { + .type = IIO_CURRENT, + .datasheet_name = "INTERNAL_RSENSE", + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .indexed = 1, + }, + { + .type = IIO_CURRENT, + .datasheet_name = "EXTERNAL_RSENSE", + .channel = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .indexed = 1, + }, +}; + +static void iadc_disable_irq_wake(void *data) +{ + disable_irq_wake((unsigned long)data); +} + +static int iadc_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct iadc_chip *iadc; + int ret, irq_eoc; + u32 res; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*iadc)); + if (!indio_dev) + return -ENOMEM; + + iadc = iio_priv(indio_dev); + iadc->dev = dev; + + iadc->regmap = dev_get_regmap(dev->parent, NULL); + if (!iadc->regmap) + return -ENODEV; + + init_completion(&iadc->complete); + mutex_init(&iadc->lock); + + ret = of_property_read_u32(node, "reg", &res); + if (ret < 0) + return -ENODEV; + + iadc->base = res; + + ret = iadc_version_check(iadc); + if (ret < 0) + return -ENODEV; + + ret = iadc_rsense_read(iadc, node); + if (ret < 0) + return -ENODEV; + + dev_dbg(iadc->dev, "sense resistors %d and %d micro Ohm\n", + iadc->rsense[IADC_INT_RSENSE], + iadc->rsense[IADC_EXT_RSENSE]); + + irq_eoc = platform_get_irq(pdev, 0); + if (irq_eoc == -EPROBE_DEFER) + return irq_eoc; + + if (irq_eoc < 0) + iadc->poll_eoc = true; + + ret = iadc_reset(iadc); + if (ret < 0) { + dev_err(dev, "reset failed\n"); + return ret; + } + + if (!iadc->poll_eoc) { + ret = devm_request_irq(dev, irq_eoc, iadc_isr, 0, + "spmi-iadc", iadc); + if (ret) + return ret; + + ret = enable_irq_wake(irq_eoc); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, iadc_disable_irq_wake, + (void *)(unsigned long)irq_eoc); + if (ret) + return ret; + } else { + ret = devm_device_init_wakeup(iadc->dev); + if (ret) + return dev_err_probe(iadc->dev, ret, "Failed to init wakeup\n"); + } + + ret = iadc_update_offset(iadc); + if (ret < 0) { + dev_err(dev, "failed offset calibration\n"); + return ret; + } + + indio_dev->name = pdev->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &iadc_info; + indio_dev->channels = iadc_channels; + indio_dev->num_channels = ARRAY_SIZE(iadc_channels); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id iadc_match_table[] = { + { .compatible = "qcom,spmi-iadc" }, + { } +}; + +MODULE_DEVICE_TABLE(of, iadc_match_table); + +static struct platform_driver iadc_driver = { + .driver = { + .name = "qcom-spmi-iadc", + .of_match_table = iadc_match_table, + }, + .probe = iadc_probe, +}; + +module_platform_driver(iadc_driver); + +MODULE_ALIAS("platform:qcom-spmi-iadc"); +MODULE_DESCRIPTION("Qualcomm SPMI PMIC current ADC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Ivan T. Ivanov "); diff --git a/drivers/iio/adc/qcom-spmi-rradc.c b/drivers/iio/adc/qcom-spmi-rradc.c new file mode 100644 index 000000000..1682c6faf --- /dev/null +++ b/drivers/iio/adc/qcom-spmi-rradc.c @@ -0,0 +1,1013 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016-2017, 2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Linaro Limited. + * Author: Casey Connolly + * + * This driver is for the Round Robin ADC found in the pmi8998 and pm660 PMICs. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#define DRIVER_NAME "qcom-spmi-rradc" + +#define RR_ADC_EN_CTL 0x46 +#define RR_ADC_SKIN_TEMP_LSB 0x50 +#define RR_ADC_SKIN_TEMP_MSB 0x51 +#define RR_ADC_CTL 0x52 +#define RR_ADC_CTL_CONTINUOUS_SEL BIT(3) +#define RR_ADC_LOG 0x53 +#define RR_ADC_LOG_CLR_CTRL BIT(0) + +#define RR_ADC_FAKE_BATT_LOW_LSB 0x58 +#define RR_ADC_FAKE_BATT_LOW_MSB 0x59 +#define RR_ADC_FAKE_BATT_HIGH_LSB 0x5A +#define RR_ADC_FAKE_BATT_HIGH_MSB 0x5B + +#define RR_ADC_BATT_ID_CTRL 0x60 +#define RR_ADC_BATT_ID_CTRL_CHANNEL_CONV BIT(0) +#define RR_ADC_BATT_ID_TRIGGER 0x61 +#define RR_ADC_BATT_ID_STS 0x62 +#define RR_ADC_BATT_ID_CFG 0x63 +#define BATT_ID_SETTLE_MASK GENMASK(7, 5) +#define RR_ADC_BATT_ID_5_LSB 0x66 +#define RR_ADC_BATT_ID_5_MSB 0x67 +#define RR_ADC_BATT_ID_15_LSB 0x68 +#define RR_ADC_BATT_ID_15_MSB 0x69 +#define RR_ADC_BATT_ID_150_LSB 0x6A +#define RR_ADC_BATT_ID_150_MSB 0x6B + +#define RR_ADC_BATT_THERM_CTRL 0x70 +#define RR_ADC_BATT_THERM_TRIGGER 0x71 +#define RR_ADC_BATT_THERM_STS 0x72 +#define RR_ADC_BATT_THERM_CFG 0x73 +#define RR_ADC_BATT_THERM_LSB 0x74 +#define RR_ADC_BATT_THERM_MSB 0x75 +#define RR_ADC_BATT_THERM_FREQ 0x76 + +#define RR_ADC_AUX_THERM_CTRL 0x80 +#define RR_ADC_AUX_THERM_TRIGGER 0x81 +#define RR_ADC_AUX_THERM_STS 0x82 +#define RR_ADC_AUX_THERM_CFG 0x83 +#define RR_ADC_AUX_THERM_LSB 0x84 +#define RR_ADC_AUX_THERM_MSB 0x85 + +#define RR_ADC_SKIN_HOT 0x86 +#define RR_ADC_SKIN_TOO_HOT 0x87 + +#define RR_ADC_AUX_THERM_C1 0x88 +#define RR_ADC_AUX_THERM_C2 0x89 +#define RR_ADC_AUX_THERM_C3 0x8A +#define RR_ADC_AUX_THERM_HALF_RANGE 0x8B + +#define RR_ADC_USB_IN_V_CTRL 0x90 +#define RR_ADC_USB_IN_V_TRIGGER 0x91 +#define RR_ADC_USB_IN_V_STS 0x92 +#define RR_ADC_USB_IN_V_LSB 0x94 +#define RR_ADC_USB_IN_V_MSB 0x95 +#define RR_ADC_USB_IN_I_CTRL 0x98 +#define RR_ADC_USB_IN_I_TRIGGER 0x99 +#define RR_ADC_USB_IN_I_STS 0x9A +#define RR_ADC_USB_IN_I_LSB 0x9C +#define RR_ADC_USB_IN_I_MSB 0x9D + +#define RR_ADC_DC_IN_V_CTRL 0xA0 +#define RR_ADC_DC_IN_V_TRIGGER 0xA1 +#define RR_ADC_DC_IN_V_STS 0xA2 +#define RR_ADC_DC_IN_V_LSB 0xA4 +#define RR_ADC_DC_IN_V_MSB 0xA5 +#define RR_ADC_DC_IN_I_CTRL 0xA8 +#define RR_ADC_DC_IN_I_TRIGGER 0xA9 +#define RR_ADC_DC_IN_I_STS 0xAA +#define RR_ADC_DC_IN_I_LSB 0xAC +#define RR_ADC_DC_IN_I_MSB 0xAD + +#define RR_ADC_PMI_DIE_TEMP_CTRL 0xB0 +#define RR_ADC_PMI_DIE_TEMP_TRIGGER 0xB1 +#define RR_ADC_PMI_DIE_TEMP_STS 0xB2 +#define RR_ADC_PMI_DIE_TEMP_CFG 0xB3 +#define RR_ADC_PMI_DIE_TEMP_LSB 0xB4 +#define RR_ADC_PMI_DIE_TEMP_MSB 0xB5 + +#define RR_ADC_CHARGER_TEMP_CTRL 0xB8 +#define RR_ADC_CHARGER_TEMP_TRIGGER 0xB9 +#define RR_ADC_CHARGER_TEMP_STS 0xBA +#define RR_ADC_CHARGER_TEMP_CFG 0xBB +#define RR_ADC_CHARGER_TEMP_LSB 0xBC +#define RR_ADC_CHARGER_TEMP_MSB 0xBD +#define RR_ADC_CHARGER_HOT 0xBE +#define RR_ADC_CHARGER_TOO_HOT 0xBF + +#define RR_ADC_GPIO_CTRL 0xC0 +#define RR_ADC_GPIO_TRIGGER 0xC1 +#define RR_ADC_GPIO_STS 0xC2 +#define RR_ADC_GPIO_LSB 0xC4 +#define RR_ADC_GPIO_MSB 0xC5 + +#define RR_ADC_ATEST_CTRL 0xC8 +#define RR_ADC_ATEST_TRIGGER 0xC9 +#define RR_ADC_ATEST_STS 0xCA +#define RR_ADC_ATEST_LSB 0xCC +#define RR_ADC_ATEST_MSB 0xCD +#define RR_ADC_SEC_ACCESS 0xD0 + +#define RR_ADC_PERPH_RESET_CTL2 0xD9 +#define RR_ADC_PERPH_RESET_CTL3 0xDA +#define RR_ADC_PERPH_RESET_CTL4 0xDB +#define RR_ADC_INT_TEST1 0xE0 +#define RR_ADC_INT_TEST_VAL 0xE1 + +#define RR_ADC_TM_TRIGGER_CTRLS 0xE2 +#define RR_ADC_TM_ADC_CTRLS 0xE3 +#define RR_ADC_TM_CNL_CTRL 0xE4 +#define RR_ADC_TM_BATT_ID_CTRL 0xE5 +#define RR_ADC_TM_THERM_CTRL 0xE6 +#define RR_ADC_TM_CONV_STS 0xE7 +#define RR_ADC_TM_ADC_READ_LSB 0xE8 +#define RR_ADC_TM_ADC_READ_MSB 0xE9 +#define RR_ADC_TM_ATEST_MUX_1 0xEA +#define RR_ADC_TM_ATEST_MUX_2 0xEB +#define RR_ADC_TM_REFERENCES 0xED +#define RR_ADC_TM_MISC_CTL 0xEE +#define RR_ADC_TM_RR_CTRL 0xEF + +#define RR_ADC_TRIGGER_EVERY_CYCLE BIT(7) +#define RR_ADC_TRIGGER_CTL BIT(0) + +#define RR_ADC_BATT_ID_RANGE 820 + +#define RR_ADC_BITS 10 +#define RR_ADC_CHAN_MSB (1 << RR_ADC_BITS) +#define RR_ADC_FS_VOLTAGE_MV 2500 + +/* BATT_THERM 0.25K/LSB */ +#define RR_ADC_BATT_THERM_LSB_K 4 + +#define RR_ADC_TEMP_FS_VOLTAGE_NUM 5000000 +#define RR_ADC_TEMP_FS_VOLTAGE_DEN 3 +#define RR_ADC_DIE_TEMP_OFFSET 601400 +#define RR_ADC_DIE_TEMP_SLOPE 2 +#define RR_ADC_DIE_TEMP_OFFSET_MILLI_DEGC 25000 + +#define RR_ADC_CHG_TEMP_GF_OFFSET_UV 1303168 +#define RR_ADC_CHG_TEMP_GF_SLOPE_UV_PER_C 3784 +#define RR_ADC_CHG_TEMP_SMIC_OFFSET_UV 1338433 +#define RR_ADC_CHG_TEMP_SMIC_SLOPE_UV_PER_C 3655 +#define RR_ADC_CHG_TEMP_660_GF_OFFSET_UV 1309001 +#define RR_ADC_CHG_TEMP_660_GF_SLOPE_UV_PER_C 3403 +#define RR_ADC_CHG_TEMP_660_SMIC_OFFSET_UV 1295898 +#define RR_ADC_CHG_TEMP_660_SMIC_SLOPE_UV_PER_C 3596 +#define RR_ADC_CHG_TEMP_660_MGNA_OFFSET_UV 1314779 +#define RR_ADC_CHG_TEMP_660_MGNA_SLOPE_UV_PER_C 3496 +#define RR_ADC_CHG_TEMP_OFFSET_MILLI_DEGC 25000 +#define RR_ADC_CHG_THRESHOLD_SCALE 4 + +#define RR_ADC_VOLT_INPUT_FACTOR 8 +#define RR_ADC_CURR_INPUT_FACTOR 2000 +#define RR_ADC_CURR_USBIN_INPUT_FACTOR_MIL 1886 +#define RR_ADC_CURR_USBIN_660_FACTOR_MIL 9 +#define RR_ADC_CURR_USBIN_660_UV_VAL 579500 + +#define RR_ADC_GPIO_FS_RANGE 5000 +#define RR_ADC_COHERENT_CHECK_RETRY 5 +#define RR_ADC_CHAN_MAX_CONTINUOUS_BUFFER_LEN 16 + +#define RR_ADC_STS_CHANNEL_READING_MASK GENMASK(1, 0) +#define RR_ADC_STS_CHANNEL_STS BIT(1) + +#define RR_ADC_TP_REV_VERSION1 21 +#define RR_ADC_TP_REV_VERSION2 29 +#define RR_ADC_TP_REV_VERSION3 32 + +#define RRADC_BATT_ID_DELAY_MAX 8 + +enum rradc_channel_id { + RR_ADC_BATT_ID = 0, + RR_ADC_BATT_THERM, + RR_ADC_SKIN_TEMP, + RR_ADC_USBIN_I, + RR_ADC_USBIN_V, + RR_ADC_DCIN_I, + RR_ADC_DCIN_V, + RR_ADC_DIE_TEMP, + RR_ADC_CHG_TEMP, + RR_ADC_GPIO, + RR_ADC_CHAN_MAX +}; + +struct rradc_chip; + +/** + * struct rradc_channel - rradc channel data + * @label: channel label + * @lsb: Channel least significant byte + * @status: Channel status address + * @size: number of bytes to read + * @trigger_addr: Trigger address, trigger is only used on some channels + * @trigger_mask: Trigger mask + * @scale_fn: Post process callback for channels which can't be exposed + * as offset + scale. + */ +struct rradc_channel { + const char *label; + u8 lsb; + u8 status; + int size; + int trigger_addr; + int trigger_mask; + int (*scale_fn)(struct rradc_chip *chip, u16 adc_code, int *result); +}; + +struct rradc_chip { + struct device *dev; + const struct qcom_spmi_pmic *pmic; + /* + * Lock held while doing channel conversion + * involving multiple register read/writes + */ + struct mutex conversion_lock; + struct regmap *regmap; + u32 base; + int batt_id_delay; + u16 batt_id_data; +}; + +static const int batt_id_delays[] = { 0, 1, 4, 12, 20, 40, 60, 80 }; +static const struct rradc_channel rradc_chans[RR_ADC_CHAN_MAX]; +static const struct iio_chan_spec rradc_iio_chans[RR_ADC_CHAN_MAX]; + +static int rradc_read(struct rradc_chip *chip, u16 addr, __le16 *buf, int len) +{ + int ret, retry_cnt = 0; + __le16 data_check[RR_ADC_CHAN_MAX_CONTINUOUS_BUFFER_LEN / 2]; + + if (len > RR_ADC_CHAN_MAX_CONTINUOUS_BUFFER_LEN) { + dev_err(chip->dev, + "Can't read more than %d bytes, but asked to read %d bytes.\n", + RR_ADC_CHAN_MAX_CONTINUOUS_BUFFER_LEN, len); + return -EINVAL; + } + + while (retry_cnt < RR_ADC_COHERENT_CHECK_RETRY) { + ret = regmap_bulk_read(chip->regmap, chip->base + addr, buf, + len); + if (ret < 0) { + dev_err(chip->dev, "rr_adc reg 0x%x failed :%d\n", addr, + ret); + return ret; + } + + ret = regmap_bulk_read(chip->regmap, chip->base + addr, + data_check, len); + if (ret < 0) { + dev_err(chip->dev, "rr_adc reg 0x%x failed :%d\n", addr, + ret); + return ret; + } + + if (memcmp(buf, data_check, len) != 0) { + retry_cnt++; + dev_dbg(chip->dev, + "coherent read error, retry_cnt:%d\n", + retry_cnt); + continue; + } + + break; + } + + if (retry_cnt == RR_ADC_COHERENT_CHECK_RETRY) + dev_err(chip->dev, "Retry exceeded for coherency check\n"); + + return ret; +} + +static int rradc_get_fab_coeff(struct rradc_chip *chip, int64_t *offset, + int64_t *slope) +{ + if (chip->pmic->subtype == PM660_SUBTYPE) { + switch (chip->pmic->fab_id) { + case PM660_FAB_ID_GF: + *offset = RR_ADC_CHG_TEMP_660_GF_OFFSET_UV; + *slope = RR_ADC_CHG_TEMP_660_GF_SLOPE_UV_PER_C; + return 0; + case PM660_FAB_ID_TSMC: + *offset = RR_ADC_CHG_TEMP_660_SMIC_OFFSET_UV; + *slope = RR_ADC_CHG_TEMP_660_SMIC_SLOPE_UV_PER_C; + return 0; + default: + *offset = RR_ADC_CHG_TEMP_660_MGNA_OFFSET_UV; + *slope = RR_ADC_CHG_TEMP_660_MGNA_SLOPE_UV_PER_C; + } + } else if (chip->pmic->subtype == PMI8998_SUBTYPE) { + switch (chip->pmic->fab_id) { + case PMI8998_FAB_ID_GF: + *offset = RR_ADC_CHG_TEMP_GF_OFFSET_UV; + *slope = RR_ADC_CHG_TEMP_GF_SLOPE_UV_PER_C; + return 0; + case PMI8998_FAB_ID_SMIC: + *offset = RR_ADC_CHG_TEMP_SMIC_OFFSET_UV; + *slope = RR_ADC_CHG_TEMP_SMIC_SLOPE_UV_PER_C; + return 0; + default: + return -EINVAL; + } + } + + return -EINVAL; +} + +/* + * These functions explicitly cast int64_t to int. + * They will never overflow, as the values are small enough. + */ +static int rradc_post_process_batt_id(struct rradc_chip *chip, u16 adc_code, + int *result_ohms) +{ + uint32_t current_value; + int64_t r_id; + + current_value = chip->batt_id_data; + r_id = ((int64_t)adc_code * RR_ADC_FS_VOLTAGE_MV); + r_id = div64_s64(r_id, (RR_ADC_CHAN_MSB * current_value)); + *result_ohms = (int)(r_id * MILLI); + + return 0; +} + +static int rradc_enable_continuous_mode(struct rradc_chip *chip) +{ + int ret; + + /* Clear channel log */ + ret = regmap_set_bits(chip->regmap, chip->base + RR_ADC_LOG, + RR_ADC_LOG_CLR_CTRL); + if (ret < 0) { + dev_err(chip->dev, "log ctrl update to clear failed:%d\n", ret); + return ret; + } + + ret = regmap_clear_bits(chip->regmap, chip->base + RR_ADC_LOG, + RR_ADC_LOG_CLR_CTRL); + if (ret < 0) { + dev_err(chip->dev, "log ctrl update to not clear failed:%d\n", + ret); + return ret; + } + + /* Switch to continuous mode */ + ret = regmap_set_bits(chip->regmap, chip->base + RR_ADC_CTL, + RR_ADC_CTL_CONTINUOUS_SEL); + if (ret < 0) + dev_err(chip->dev, "Update to continuous mode failed:%d\n", + ret); + + return ret; +} + +static int rradc_disable_continuous_mode(struct rradc_chip *chip) +{ + int ret; + + /* Switch to non continuous mode */ + ret = regmap_clear_bits(chip->regmap, chip->base + RR_ADC_CTL, + RR_ADC_CTL_CONTINUOUS_SEL); + if (ret < 0) + dev_err(chip->dev, "Update to non-continuous mode failed:%d\n", + ret); + + return ret; +} + +static bool rradc_is_ready(struct rradc_chip *chip, + enum rradc_channel_id chan_address) +{ + const struct rradc_channel *chan = &rradc_chans[chan_address]; + int ret; + unsigned int status, mask; + + /* BATT_ID STS bit does not get set initially */ + switch (chan_address) { + case RR_ADC_BATT_ID: + mask = RR_ADC_STS_CHANNEL_STS; + break; + default: + mask = RR_ADC_STS_CHANNEL_READING_MASK; + break; + } + + ret = regmap_read(chip->regmap, chip->base + chan->status, &status); + if (ret < 0 || !(status & mask)) + return false; + + return true; +} + +static int rradc_read_status_in_cont_mode(struct rradc_chip *chip, + enum rradc_channel_id chan_address) +{ + const struct rradc_channel *chan = &rradc_chans[chan_address]; + const struct iio_chan_spec *iio_chan = &rradc_iio_chans[chan_address]; + int ret, i; + + if (chan->trigger_mask == 0) { + dev_err(chip->dev, "Channel doesn't have a trigger mask\n"); + return -EINVAL; + } + + ret = regmap_set_bits(chip->regmap, chip->base + chan->trigger_addr, + chan->trigger_mask); + if (ret < 0) { + dev_err(chip->dev, + "Failed to apply trigger for channel '%s' ret=%d\n", + iio_chan->extend_name, ret); + return ret; + } + + ret = rradc_enable_continuous_mode(chip); + if (ret < 0) { + dev_err(chip->dev, "Failed to switch to continuous mode\n"); + goto disable_trigger; + } + + /* + * The wait/sleep values were found through trial and error, + * this is mostly for the battery ID channel which takes some + * time to settle. + */ + for (i = 0; i < 5; i++) { + if (rradc_is_ready(chip, chan_address)) + break; + usleep_range(50000, 50000 + 500); + } + + if (i == 5) { + dev_err(chip->dev, "Channel '%s' is not ready\n", + iio_chan->extend_name); + ret = -ETIMEDOUT; + } + + rradc_disable_continuous_mode(chip); + +disable_trigger: + regmap_clear_bits(chip->regmap, chip->base + chan->trigger_addr, + chan->trigger_mask); + + return ret; +} + +static int rradc_prepare_batt_id_conversion(struct rradc_chip *chip, + enum rradc_channel_id chan_address, + u16 *data) +{ + int ret; + + ret = regmap_set_bits(chip->regmap, chip->base + RR_ADC_BATT_ID_CTRL, + RR_ADC_BATT_ID_CTRL_CHANNEL_CONV); + if (ret < 0) { + dev_err(chip->dev, "Enabling BATT ID channel failed:%d\n", ret); + return ret; + } + + ret = regmap_set_bits(chip->regmap, + chip->base + RR_ADC_BATT_ID_TRIGGER, + RR_ADC_TRIGGER_CTL); + if (ret < 0) { + dev_err(chip->dev, "BATT_ID trigger set failed:%d\n", ret); + goto out_disable_batt_id; + } + + ret = rradc_read_status_in_cont_mode(chip, chan_address); + + /* Reset registers back to default values */ + regmap_clear_bits(chip->regmap, chip->base + RR_ADC_BATT_ID_TRIGGER, + RR_ADC_TRIGGER_CTL); + +out_disable_batt_id: + regmap_clear_bits(chip->regmap, chip->base + RR_ADC_BATT_ID_CTRL, + RR_ADC_BATT_ID_CTRL_CHANNEL_CONV); + + return ret; +} + +static int rradc_do_conversion(struct rradc_chip *chip, + enum rradc_channel_id chan_address, u16 *data) +{ + const struct rradc_channel *chan = &rradc_chans[chan_address]; + const struct iio_chan_spec *iio_chan = &rradc_iio_chans[chan_address]; + int ret; + __le16 buf[3]; + + mutex_lock(&chip->conversion_lock); + + switch (chan_address) { + case RR_ADC_BATT_ID: + ret = rradc_prepare_batt_id_conversion(chip, chan_address, data); + if (ret < 0) { + dev_err(chip->dev, "Battery ID conversion failed:%d\n", + ret); + goto unlock_out; + } + break; + + case RR_ADC_USBIN_V: + case RR_ADC_DIE_TEMP: + ret = rradc_read_status_in_cont_mode(chip, chan_address); + if (ret < 0) { + dev_err(chip->dev, + "Error reading in continuous mode:%d\n", ret); + goto unlock_out; + } + break; + default: + if (!rradc_is_ready(chip, chan_address)) { + /* + * Usually this means the channel isn't attached, for example + * the in_voltage_usbin_v_input channel will not be ready if + * no USB cable is attached + */ + dev_dbg(chip->dev, "channel '%s' is not ready\n", + iio_chan->extend_name); + ret = -ENODATA; + goto unlock_out; + } + break; + } + + ret = rradc_read(chip, chan->lsb, buf, chan->size); + if (ret) { + dev_err(chip->dev, "read data failed\n"); + goto unlock_out; + } + + /* + * For the battery ID we read the register for every ID ADC and then + * see which one is actually connected. + */ + if (chan_address == RR_ADC_BATT_ID) { + u16 batt_id_150 = le16_to_cpu(buf[2]); + u16 batt_id_15 = le16_to_cpu(buf[1]); + u16 batt_id_5 = le16_to_cpu(buf[0]); + + if (!batt_id_150 && !batt_id_15 && !batt_id_5) { + dev_err(chip->dev, + "Invalid batt_id values with all zeros\n"); + ret = -EINVAL; + goto unlock_out; + } + + if (batt_id_150 <= RR_ADC_BATT_ID_RANGE) { + *data = batt_id_150; + chip->batt_id_data = 150; + } else if (batt_id_15 <= RR_ADC_BATT_ID_RANGE) { + *data = batt_id_15; + chip->batt_id_data = 15; + } else { + *data = batt_id_5; + chip->batt_id_data = 5; + } + } else { + /* + * All of the other channels are either 1 or 2 bytes. + * We can rely on the second byte being 0 for 1-byte channels. + */ + *data = le16_to_cpu(buf[0]); + } + +unlock_out: + mutex_unlock(&chip->conversion_lock); + + return ret; +} + +static int rradc_read_scale(struct rradc_chip *chip, int chan_address, int *val, + int *val2) +{ + int64_t fab_offset, fab_slope; + int ret; + + ret = rradc_get_fab_coeff(chip, &fab_offset, &fab_slope); + if (ret < 0) { + dev_err(chip->dev, "Unable to get fab id coefficients\n"); + return -EINVAL; + } + + switch (chan_address) { + case RR_ADC_SKIN_TEMP: + *val = MILLI; + *val2 = RR_ADC_BATT_THERM_LSB_K; + return IIO_VAL_FRACTIONAL; + case RR_ADC_USBIN_I: + *val = RR_ADC_CURR_USBIN_INPUT_FACTOR_MIL * + RR_ADC_FS_VOLTAGE_MV; + *val2 = RR_ADC_CHAN_MSB; + return IIO_VAL_FRACTIONAL; + case RR_ADC_DCIN_I: + *val = RR_ADC_CURR_INPUT_FACTOR * RR_ADC_FS_VOLTAGE_MV; + *val2 = RR_ADC_CHAN_MSB; + return IIO_VAL_FRACTIONAL; + case RR_ADC_USBIN_V: + case RR_ADC_DCIN_V: + *val = RR_ADC_VOLT_INPUT_FACTOR * RR_ADC_FS_VOLTAGE_MV * MILLI; + *val2 = RR_ADC_CHAN_MSB; + return IIO_VAL_FRACTIONAL; + case RR_ADC_GPIO: + *val = RR_ADC_GPIO_FS_RANGE; + *val2 = RR_ADC_CHAN_MSB; + return IIO_VAL_FRACTIONAL; + case RR_ADC_CHG_TEMP: + /* + * We divide val2 by MILLI instead of multiplying val + * to avoid an integer overflow. + */ + *val = -RR_ADC_TEMP_FS_VOLTAGE_NUM; + *val2 = div64_s64(RR_ADC_TEMP_FS_VOLTAGE_DEN * RR_ADC_CHAN_MSB * + fab_slope, + MILLI); + + return IIO_VAL_FRACTIONAL; + case RR_ADC_DIE_TEMP: + *val = RR_ADC_TEMP_FS_VOLTAGE_NUM; + *val2 = RR_ADC_TEMP_FS_VOLTAGE_DEN * RR_ADC_CHAN_MSB * + RR_ADC_DIE_TEMP_SLOPE; + + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } +} + +static int rradc_read_offset(struct rradc_chip *chip, int chan_address, int *val) +{ + int64_t fab_offset, fab_slope; + int64_t offset1, offset2; + int ret; + + switch (chan_address) { + case RR_ADC_SKIN_TEMP: + /* + * Offset from kelvin to degC, divided by the + * scale factor (250). We lose some precision here. + * 273150 / 250 = 1092.6 + */ + *val = div64_s64(ABSOLUTE_ZERO_MILLICELSIUS, + (MILLI / RR_ADC_BATT_THERM_LSB_K)); + return IIO_VAL_INT; + case RR_ADC_CHG_TEMP: + ret = rradc_get_fab_coeff(chip, &fab_offset, &fab_slope); + if (ret < 0) { + dev_err(chip->dev, + "Unable to get fab id coefficients\n"); + return -EINVAL; + } + offset1 = -(fab_offset * RR_ADC_TEMP_FS_VOLTAGE_DEN * + RR_ADC_CHAN_MSB); + offset1 += (int64_t)RR_ADC_TEMP_FS_VOLTAGE_NUM / 2ULL; + offset1 = div64_s64(offset1, + (int64_t)(RR_ADC_TEMP_FS_VOLTAGE_NUM)); + + offset2 = (int64_t)RR_ADC_CHG_TEMP_OFFSET_MILLI_DEGC * + RR_ADC_TEMP_FS_VOLTAGE_DEN * RR_ADC_CHAN_MSB * + (int64_t)fab_slope; + offset2 += ((int64_t)MILLI * RR_ADC_TEMP_FS_VOLTAGE_NUM) / 2; + offset2 = div64_s64( + offset2, ((int64_t)MILLI * RR_ADC_TEMP_FS_VOLTAGE_NUM)); + + /* + * The -1 is to compensate for lost precision. + * It should actually be -0.7906976744186046. + * This works out to every value being off + * by about +0.091 degrees C after applying offset and scale. + */ + *val = (int)(offset1 - offset2 - 1); + return IIO_VAL_INT; + case RR_ADC_DIE_TEMP: + offset1 = -RR_ADC_DIE_TEMP_OFFSET * + (int64_t)RR_ADC_TEMP_FS_VOLTAGE_DEN * + (int64_t)RR_ADC_CHAN_MSB; + offset1 = div64_s64(offset1, RR_ADC_TEMP_FS_VOLTAGE_NUM); + + offset2 = -(int64_t)RR_ADC_CHG_TEMP_OFFSET_MILLI_DEGC * + RR_ADC_TEMP_FS_VOLTAGE_DEN * RR_ADC_CHAN_MSB * + RR_ADC_DIE_TEMP_SLOPE; + offset2 = div64_s64(offset2, + ((int64_t)RR_ADC_TEMP_FS_VOLTAGE_NUM)); + + /* + * The result is -339, it should be -338.69789, this results + * in the calculated die temp being off by + * -0.004 - -0.0175 degrees C + */ + *val = (int)(offset1 - offset2); + return IIO_VAL_INT; + default: + break; + } + return -EINVAL; +} + +static int rradc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan_spec, int *val, + int *val2, long mask) +{ + struct rradc_chip *chip = iio_priv(indio_dev); + const struct rradc_channel *chan; + int ret; + u16 adc_code; + + if (chan_spec->address >= RR_ADC_CHAN_MAX) { + dev_err(chip->dev, "Invalid channel index:%lu\n", + chan_spec->address); + return -EINVAL; + } + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return rradc_read_scale(chip, chan_spec->address, val, val2); + case IIO_CHAN_INFO_OFFSET: + return rradc_read_offset(chip, chan_spec->address, val); + case IIO_CHAN_INFO_RAW: + ret = rradc_do_conversion(chip, chan_spec->address, &adc_code); + if (ret < 0) + return ret; + + *val = adc_code; + return IIO_VAL_INT; + case IIO_CHAN_INFO_PROCESSED: + chan = &rradc_chans[chan_spec->address]; + if (!chan->scale_fn) + return -EINVAL; + ret = rradc_do_conversion(chip, chan_spec->address, &adc_code); + if (ret < 0) + return ret; + + *val = chan->scale_fn(chip, adc_code, val); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int rradc_read_label(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, char *label) +{ + return sysfs_emit(label, "%s\n", + rradc_chans[chan->address].label); +} + +static const struct iio_info rradc_info = { + .read_raw = rradc_read_raw, + .read_label = rradc_read_label, +}; + +static const struct rradc_channel rradc_chans[RR_ADC_CHAN_MAX] = { + { + .label = "batt_id", + .scale_fn = rradc_post_process_batt_id, + .lsb = RR_ADC_BATT_ID_5_LSB, + .status = RR_ADC_BATT_ID_STS, + .size = 6, + .trigger_addr = RR_ADC_BATT_ID_TRIGGER, + .trigger_mask = BIT(0), + }, { + .label = "batt", + .lsb = RR_ADC_BATT_THERM_LSB, + .status = RR_ADC_BATT_THERM_STS, + .size = 2, + .trigger_addr = RR_ADC_BATT_THERM_TRIGGER, + }, { + .label = "pmi8998_skin", + .lsb = RR_ADC_SKIN_TEMP_LSB, + .status = RR_ADC_AUX_THERM_STS, + .size = 2, + .trigger_addr = RR_ADC_AUX_THERM_TRIGGER, + }, { + .label = "usbin_i", + .lsb = RR_ADC_USB_IN_I_LSB, + .status = RR_ADC_USB_IN_I_STS, + .size = 2, + .trigger_addr = RR_ADC_USB_IN_I_TRIGGER, + }, { + .label = "usbin_v", + .lsb = RR_ADC_USB_IN_V_LSB, + .status = RR_ADC_USB_IN_V_STS, + .size = 2, + .trigger_addr = RR_ADC_USB_IN_V_TRIGGER, + .trigger_mask = BIT(7), + }, { + .label = "dcin_i", + .lsb = RR_ADC_DC_IN_I_LSB, + .status = RR_ADC_DC_IN_I_STS, + .size = 2, + .trigger_addr = RR_ADC_DC_IN_I_TRIGGER, + }, { + .label = "dcin_v", + .lsb = RR_ADC_DC_IN_V_LSB, + .status = RR_ADC_DC_IN_V_STS, + .size = 2, + .trigger_addr = RR_ADC_DC_IN_V_TRIGGER, + }, { + .label = "pmi8998_die", + .lsb = RR_ADC_PMI_DIE_TEMP_LSB, + .status = RR_ADC_PMI_DIE_TEMP_STS, + .size = 2, + .trigger_addr = RR_ADC_PMI_DIE_TEMP_TRIGGER, + .trigger_mask = RR_ADC_TRIGGER_EVERY_CYCLE, + }, { + .label = "chg", + .lsb = RR_ADC_CHARGER_TEMP_LSB, + .status = RR_ADC_CHARGER_TEMP_STS, + .size = 2, + .trigger_addr = RR_ADC_CHARGER_TEMP_TRIGGER, + }, { + .label = "gpio", + .lsb = RR_ADC_GPIO_LSB, + .status = RR_ADC_GPIO_STS, + .size = 2, + .trigger_addr = RR_ADC_GPIO_TRIGGER, + }, +}; + +static const struct iio_chan_spec rradc_iio_chans[RR_ADC_CHAN_MAX] = { + { + .type = IIO_RESISTANCE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .address = RR_ADC_BATT_ID, + .channel = 0, + .indexed = 1, + }, { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .address = RR_ADC_BATT_THERM, + .channel = 0, + .indexed = 1, + }, { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = RR_ADC_SKIN_TEMP, + .channel = 1, + .indexed = 1, + }, { + .type = IIO_CURRENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .address = RR_ADC_USBIN_I, + .channel = 0, + .indexed = 1, + }, { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .address = RR_ADC_USBIN_V, + .channel = 0, + .indexed = 1, + }, { + .type = IIO_CURRENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .address = RR_ADC_DCIN_I, + .channel = 1, + .indexed = 1, + }, { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .address = RR_ADC_DCIN_V, + .channel = 1, + .indexed = 1, + }, { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = RR_ADC_DIE_TEMP, + .channel = 2, + .indexed = 1, + }, { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .address = RR_ADC_CHG_TEMP, + .channel = 3, + .indexed = 1, + }, { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_SCALE), + .address = RR_ADC_GPIO, + .channel = 2, + .indexed = 1, + }, +}; + +static int rradc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct rradc_chip *chip; + int ret, i, batt_id_delay; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + chip->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!chip->regmap) + return dev_err_probe(dev, -EINVAL, "Couldn't get parent's regmap\n"); + + chip->dev = dev; + mutex_init(&chip->conversion_lock); + + ret = device_property_read_u32(dev, "reg", &chip->base); + if (ret < 0) + return dev_err_probe(dev, ret, "Couldn't find reg address\n"); + + batt_id_delay = -1; + ret = device_property_read_u32(dev, "qcom,batt-id-delay-ms", + &batt_id_delay); + if (!ret) { + for (i = 0; i < RRADC_BATT_ID_DELAY_MAX; i++) { + if (batt_id_delay == batt_id_delays[i]) + break; + } + if (i == RRADC_BATT_ID_DELAY_MAX) + batt_id_delay = -1; + } + + if (batt_id_delay >= 0) { + batt_id_delay = FIELD_PREP(BATT_ID_SETTLE_MASK, batt_id_delay); + ret = regmap_set_bits(chip->regmap, + chip->base + RR_ADC_BATT_ID_CFG, + batt_id_delay); + if (ret < 0) { + dev_err(chip->dev, + "BATT_ID settling time config failed:%d\n", + ret); + } + } + + /* Get the PMIC revision, we need it to handle some varying coefficients */ + chip->pmic = qcom_pmic_get(chip->dev); + if (IS_ERR(chip->pmic)) + return dev_err_probe(dev, PTR_ERR(chip->pmic), + "Unable to get reference to PMIC device\n"); + + switch (chip->pmic->subtype) { + case PMI8998_SUBTYPE: + indio_dev->name = "pmi8998-rradc"; + break; + case PM660_SUBTYPE: + indio_dev->name = "pm660-rradc"; + break; + default: + indio_dev->name = DRIVER_NAME; + break; + } + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &rradc_info; + indio_dev->channels = rradc_iio_chans; + indio_dev->num_channels = ARRAY_SIZE(rradc_iio_chans); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id rradc_match_table[] = { + { .compatible = "qcom,pm660-rradc" }, + { .compatible = "qcom,pmi8998-rradc" }, + { } +}; +MODULE_DEVICE_TABLE(of, rradc_match_table); + +static struct platform_driver rradc_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = rradc_match_table, + }, + .probe = rradc_probe, +}; +module_platform_driver(rradc_driver); + +MODULE_DESCRIPTION("QCOM SPMI PMIC RR ADC driver"); +MODULE_AUTHOR("Casey Connolly "); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/qcom-spmi-vadc.c b/drivers/iio/adc/qcom-spmi-vadc.c new file mode 100644 index 000000000..d7a2df3c8 --- /dev/null +++ b/drivers/iio/adc/qcom-spmi-vadc.c @@ -0,0 +1,950 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* VADC register and bit definitions */ +#define VADC_REVISION2 0x1 +#define VADC_REVISION2_SUPPORTED_VADC 1 + +#define VADC_PERPH_TYPE 0x4 +#define VADC_PERPH_TYPE_ADC 8 + +#define VADC_PERPH_SUBTYPE 0x5 +#define VADC_PERPH_SUBTYPE_VADC 1 + +#define VADC_STATUS1 0x8 +#define VADC_STATUS1_OP_MODE 4 +#define VADC_STATUS1_REQ_STS BIT(1) +#define VADC_STATUS1_EOC BIT(0) +#define VADC_STATUS1_REQ_STS_EOC_MASK 0x3 + +#define VADC_MODE_CTL 0x40 +#define VADC_OP_MODE_SHIFT 3 +#define VADC_OP_MODE_NORMAL 0 +#define VADC_AMUX_TRIM_EN BIT(1) +#define VADC_ADC_TRIM_EN BIT(0) + +#define VADC_EN_CTL1 0x46 +#define VADC_EN_CTL1_SET BIT(7) + +#define VADC_ADC_CH_SEL_CTL 0x48 + +#define VADC_ADC_DIG_PARAM 0x50 +#define VADC_ADC_DIG_DEC_RATIO_SEL_SHIFT 2 + +#define VADC_HW_SETTLE_DELAY 0x51 + +#define VADC_CONV_REQ 0x52 +#define VADC_CONV_REQ_SET BIT(7) + +#define VADC_FAST_AVG_CTL 0x5a +#define VADC_FAST_AVG_EN 0x5b +#define VADC_FAST_AVG_EN_SET BIT(7) + +#define VADC_ACCESS 0xd0 +#define VADC_ACCESS_DATA 0xa5 + +#define VADC_PERH_RESET_CTL3 0xda +#define VADC_FOLLOW_WARM_RB BIT(2) + +#define VADC_DATA 0x60 /* 16 bits */ + +#define VADC_CHAN_MIN VADC_USBIN +#define VADC_CHAN_MAX VADC_LR_MUX3_BUF_PU1_PU2_XO_THERM + +/** + * struct vadc_channel_prop - VADC channel property. + * @channel: channel number, refer to the channel list. + * @calibration: calibration type. + * @decimation: sampling rate supported for the channel. + * @prescale: channel scaling performed on the input signal. + * @hw_settle_time: the time between AMUX being configured and the + * start of conversion. + * @avg_samples: ability to provide single result from the ADC + * that is an average of multiple measurements. + * @scale_fn_type: Represents the scaling function to convert voltage + * physical units desired by the client for the channel. + * @channel_name: Channel name used in device tree. + */ +struct vadc_channel_prop { + unsigned int channel; + enum vadc_calibration calibration; + unsigned int decimation; + unsigned int prescale; + unsigned int hw_settle_time; + unsigned int avg_samples; + enum vadc_scale_fn_type scale_fn_type; + const char *channel_name; +}; + +/** + * struct vadc_priv - VADC private structure. + * @regmap: pointer to struct regmap. + * @dev: pointer to struct device. + * @base: base address for the ADC peripheral. + * @nchannels: number of VADC channels. + * @chan_props: array of VADC channel properties. + * @iio_chans: array of IIO channels specification. + * @are_ref_measured: are reference points measured. + * @poll_eoc: use polling instead of interrupt. + * @complete: VADC result notification after interrupt is received. + * @graph: store parameters for calibration. + * @lock: ADC lock for access to the peripheral. + */ +struct vadc_priv { + struct regmap *regmap; + struct device *dev; + u16 base; + unsigned int nchannels; + struct vadc_channel_prop *chan_props; + struct iio_chan_spec *iio_chans; + bool are_ref_measured; + bool poll_eoc; + struct completion complete; + struct vadc_linear_graph graph[2]; + struct mutex lock; +}; + +static const struct u32_fract vadc_prescale_ratios[] = { + { .numerator = 1, .denominator = 1 }, + { .numerator = 1, .denominator = 3 }, + { .numerator = 1, .denominator = 4 }, + { .numerator = 1, .denominator = 6 }, + { .numerator = 1, .denominator = 20 }, + { .numerator = 1, .denominator = 8 }, + { .numerator = 10, .denominator = 81 }, + { .numerator = 1, .denominator = 10 }, +}; + +static int vadc_read(struct vadc_priv *vadc, u16 offset, u8 *data) +{ + return regmap_bulk_read(vadc->regmap, vadc->base + offset, data, 1); +} + +static int vadc_write(struct vadc_priv *vadc, u16 offset, u8 data) +{ + return regmap_write(vadc->regmap, vadc->base + offset, data); +} + +static int vadc_reset(struct vadc_priv *vadc) +{ + u8 data; + int ret; + + ret = vadc_write(vadc, VADC_ACCESS, VADC_ACCESS_DATA); + if (ret) + return ret; + + ret = vadc_read(vadc, VADC_PERH_RESET_CTL3, &data); + if (ret) + return ret; + + ret = vadc_write(vadc, VADC_ACCESS, VADC_ACCESS_DATA); + if (ret) + return ret; + + data |= VADC_FOLLOW_WARM_RB; + + return vadc_write(vadc, VADC_PERH_RESET_CTL3, data); +} + +static int vadc_set_state(struct vadc_priv *vadc, bool state) +{ + return vadc_write(vadc, VADC_EN_CTL1, state ? VADC_EN_CTL1_SET : 0); +} + +static void vadc_show_status(struct vadc_priv *vadc) +{ + u8 mode, sta1, chan, dig, en, req; + int ret; + + ret = vadc_read(vadc, VADC_MODE_CTL, &mode); + if (ret) + return; + + ret = vadc_read(vadc, VADC_ADC_DIG_PARAM, &dig); + if (ret) + return; + + ret = vadc_read(vadc, VADC_ADC_CH_SEL_CTL, &chan); + if (ret) + return; + + ret = vadc_read(vadc, VADC_CONV_REQ, &req); + if (ret) + return; + + ret = vadc_read(vadc, VADC_STATUS1, &sta1); + if (ret) + return; + + ret = vadc_read(vadc, VADC_EN_CTL1, &en); + if (ret) + return; + + dev_err(vadc->dev, + "mode:%02x en:%02x chan:%02x dig:%02x req:%02x sta1:%02x\n", + mode, en, chan, dig, req, sta1); +} + +static int vadc_configure(struct vadc_priv *vadc, + struct vadc_channel_prop *prop) +{ + u8 decimation, mode_ctrl; + int ret; + + /* Mode selection */ + mode_ctrl = (VADC_OP_MODE_NORMAL << VADC_OP_MODE_SHIFT) | + VADC_ADC_TRIM_EN | VADC_AMUX_TRIM_EN; + ret = vadc_write(vadc, VADC_MODE_CTL, mode_ctrl); + if (ret) + return ret; + + /* Channel selection */ + ret = vadc_write(vadc, VADC_ADC_CH_SEL_CTL, prop->channel); + if (ret) + return ret; + + /* Digital parameter setup */ + decimation = prop->decimation << VADC_ADC_DIG_DEC_RATIO_SEL_SHIFT; + ret = vadc_write(vadc, VADC_ADC_DIG_PARAM, decimation); + if (ret) + return ret; + + /* HW settle time delay */ + ret = vadc_write(vadc, VADC_HW_SETTLE_DELAY, prop->hw_settle_time); + if (ret) + return ret; + + ret = vadc_write(vadc, VADC_FAST_AVG_CTL, prop->avg_samples); + if (ret) + return ret; + + if (prop->avg_samples) + ret = vadc_write(vadc, VADC_FAST_AVG_EN, VADC_FAST_AVG_EN_SET); + else + ret = vadc_write(vadc, VADC_FAST_AVG_EN, 0); + + return ret; +} + +static int vadc_poll_wait_eoc(struct vadc_priv *vadc, unsigned int interval_us) +{ + unsigned int count, retry; + u8 sta1; + int ret; + + retry = interval_us / VADC_CONV_TIME_MIN_US; + + for (count = 0; count < retry; count++) { + ret = vadc_read(vadc, VADC_STATUS1, &sta1); + if (ret) + return ret; + + sta1 &= VADC_STATUS1_REQ_STS_EOC_MASK; + if (sta1 == VADC_STATUS1_EOC) + return 0; + + usleep_range(VADC_CONV_TIME_MIN_US, VADC_CONV_TIME_MAX_US); + } + + vadc_show_status(vadc); + + return -ETIMEDOUT; +} + +static int vadc_read_result(struct vadc_priv *vadc, u16 *data) +{ + int ret; + + ret = regmap_bulk_read(vadc->regmap, vadc->base + VADC_DATA, data, 2); + if (ret) + return ret; + + *data = clamp_t(u16, *data, VADC_MIN_ADC_CODE, VADC_MAX_ADC_CODE); + + return 0; +} + +static struct vadc_channel_prop *vadc_get_channel(struct vadc_priv *vadc, + unsigned int num) +{ + unsigned int i; + + for (i = 0; i < vadc->nchannels; i++) + if (vadc->chan_props[i].channel == num) + return &vadc->chan_props[i]; + + dev_dbg(vadc->dev, "no such channel %02x\n", num); + + return NULL; +} + +static int vadc_do_conversion(struct vadc_priv *vadc, + struct vadc_channel_prop *prop, u16 *data) +{ + unsigned int timeout; + int ret; + + mutex_lock(&vadc->lock); + + ret = vadc_configure(vadc, prop); + if (ret) + goto unlock; + + if (!vadc->poll_eoc) + reinit_completion(&vadc->complete); + + ret = vadc_set_state(vadc, true); + if (ret) + goto unlock; + + ret = vadc_write(vadc, VADC_CONV_REQ, VADC_CONV_REQ_SET); + if (ret) + goto err_disable; + + timeout = BIT(prop->avg_samples) * VADC_CONV_TIME_MIN_US * 2; + + if (vadc->poll_eoc) { + ret = vadc_poll_wait_eoc(vadc, timeout); + } else { + ret = wait_for_completion_timeout(&vadc->complete, timeout); + if (!ret) { + ret = -ETIMEDOUT; + goto err_disable; + } + + /* Double check conversion status */ + ret = vadc_poll_wait_eoc(vadc, VADC_CONV_TIME_MIN_US); + if (ret) + goto err_disable; + } + + ret = vadc_read_result(vadc, data); + +err_disable: + vadc_set_state(vadc, false); + if (ret) + dev_err(vadc->dev, "conversion failed\n"); +unlock: + mutex_unlock(&vadc->lock); + return ret; +} + +static int vadc_measure_ref_points(struct vadc_priv *vadc) +{ + struct vadc_channel_prop *prop; + u16 read_1, read_2; + int ret; + + vadc->graph[VADC_CALIB_RATIOMETRIC].dx = VADC_RATIOMETRIC_RANGE; + vadc->graph[VADC_CALIB_ABSOLUTE].dx = VADC_ABSOLUTE_RANGE_UV; + + prop = vadc_get_channel(vadc, VADC_REF_1250MV); + ret = vadc_do_conversion(vadc, prop, &read_1); + if (ret) + goto err; + + /* Try with buffered 625mV channel first */ + prop = vadc_get_channel(vadc, VADC_SPARE1); + if (!prop) + prop = vadc_get_channel(vadc, VADC_REF_625MV); + + ret = vadc_do_conversion(vadc, prop, &read_2); + if (ret) + goto err; + + if (read_1 == read_2) { + ret = -EINVAL; + goto err; + } + + vadc->graph[VADC_CALIB_ABSOLUTE].dy = read_1 - read_2; + vadc->graph[VADC_CALIB_ABSOLUTE].gnd = read_2; + + /* Ratiometric calibration */ + prop = vadc_get_channel(vadc, VADC_VDD_VADC); + ret = vadc_do_conversion(vadc, prop, &read_1); + if (ret) + goto err; + + prop = vadc_get_channel(vadc, VADC_GND_REF); + ret = vadc_do_conversion(vadc, prop, &read_2); + if (ret) + goto err; + + if (read_1 == read_2) { + ret = -EINVAL; + goto err; + } + + vadc->graph[VADC_CALIB_RATIOMETRIC].dy = read_1 - read_2; + vadc->graph[VADC_CALIB_RATIOMETRIC].gnd = read_2; +err: + if (ret) + dev_err(vadc->dev, "measure reference points failed\n"); + + return ret; +} + +static int vadc_prescaling_from_dt(u32 numerator, u32 denominator) +{ + unsigned int pre; + + for (pre = 0; pre < ARRAY_SIZE(vadc_prescale_ratios); pre++) + if (vadc_prescale_ratios[pre].numerator == numerator && + vadc_prescale_ratios[pre].denominator == denominator) + break; + + if (pre == ARRAY_SIZE(vadc_prescale_ratios)) + return -EINVAL; + + return pre; +} + +static int vadc_hw_settle_time_from_dt(u32 value) +{ + if ((value <= 1000 && value % 100) || (value > 1000 && value % 2000)) + return -EINVAL; + + if (value <= 1000) + value /= 100; + else + value = value / 2000 + 10; + + return value; +} + +static int vadc_avg_samples_from_dt(u32 value) +{ + if (!is_power_of_2(value) || value > VADC_AVG_SAMPLES_MAX) + return -EINVAL; + + return __ffs64(value); +} + +static int vadc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, + long mask) +{ + struct vadc_priv *vadc = iio_priv(indio_dev); + struct vadc_channel_prop *prop; + u16 adc_code; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + prop = &vadc->chan_props[chan->address]; + ret = vadc_do_conversion(vadc, prop, &adc_code); + if (ret) + break; + + ret = qcom_vadc_scale(prop->scale_fn_type, + &vadc->graph[prop->calibration], + &vadc_prescale_ratios[prop->prescale], + (prop->calibration == VADC_CALIB_ABSOLUTE), + adc_code, val); + if (ret) + break; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_RAW: + prop = &vadc->chan_props[chan->address]; + ret = vadc_do_conversion(vadc, prop, &adc_code); + if (ret) + break; + + *val = (int)adc_code; + return IIO_VAL_INT; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int vadc_fwnode_xlate(struct iio_dev *indio_dev, + const struct fwnode_reference_args *iiospec) +{ + struct vadc_priv *vadc = iio_priv(indio_dev); + unsigned int i; + + for (i = 0; i < vadc->nchannels; i++) + if (vadc->iio_chans[i].channel == iiospec->args[0]) + return i; + + return -EINVAL; +} + +static int vadc_read_label(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, char *label) +{ + struct vadc_priv *vadc = iio_priv(indio_dev); + const char *name = vadc->chan_props[chan->address].channel_name; + + return sysfs_emit(label, "%s\n", name); +} + +static const struct iio_info vadc_info = { + .read_raw = vadc_read_raw, + .read_label = vadc_read_label, + .fwnode_xlate = vadc_fwnode_xlate, +}; + +struct vadc_channels { + const char *datasheet_name; + unsigned int prescale_index; + enum iio_chan_type type; + long info_mask; + enum vadc_scale_fn_type scale_fn_type; +}; + +#define VADC_CHAN(_dname, _type, _mask, _pre, _scale) \ + [VADC_##_dname] = { \ + .datasheet_name = __stringify(_dname), \ + .prescale_index = _pre, \ + .type = _type, \ + .info_mask = _mask, \ + .scale_fn_type = _scale \ + }, \ + +#define VADC_NO_CHAN(_dname, _type, _mask, _pre) \ + [VADC_##_dname] = { \ + .datasheet_name = __stringify(_dname), \ + .prescale_index = _pre, \ + .type = _type, \ + .info_mask = _mask \ + }, + +#define VADC_CHAN_TEMP(_dname, _pre, _scale) \ + VADC_CHAN(_dname, IIO_TEMP, \ + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED), \ + _pre, _scale) \ + +#define VADC_CHAN_VOLT(_dname, _pre, _scale) \ + VADC_CHAN(_dname, IIO_VOLTAGE, \ + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),\ + _pre, _scale) \ + +#define VADC_CHAN_NO_SCALE(_dname, _pre) \ + VADC_NO_CHAN(_dname, IIO_VOLTAGE, \ + BIT(IIO_CHAN_INFO_RAW), \ + _pre) \ + +/* + * The array represents all possible ADC channels found in the supported PMICs. + * Every index in the array is equal to the channel number per datasheet. The + * gaps in the array should be treated as reserved channels. + */ +static const struct vadc_channels vadc_chans[] = { + VADC_CHAN_VOLT(USBIN, 4, SCALE_DEFAULT) + VADC_CHAN_VOLT(DCIN, 4, SCALE_DEFAULT) + VADC_CHAN_NO_SCALE(VCHG_SNS, 3) + VADC_CHAN_NO_SCALE(SPARE1_03, 1) + VADC_CHAN_NO_SCALE(USB_ID_MV, 1) + VADC_CHAN_VOLT(VCOIN, 1, SCALE_DEFAULT) + VADC_CHAN_NO_SCALE(VBAT_SNS, 1) + VADC_CHAN_VOLT(VSYS, 1, SCALE_DEFAULT) + VADC_CHAN_TEMP(DIE_TEMP, 0, SCALE_PMIC_THERM) + VADC_CHAN_VOLT(REF_625MV, 0, SCALE_DEFAULT) + VADC_CHAN_VOLT(REF_1250MV, 0, SCALE_DEFAULT) + VADC_CHAN_NO_SCALE(CHG_TEMP, 0) + VADC_CHAN_NO_SCALE(SPARE1, 0) + VADC_CHAN_TEMP(SPARE2, 0, SCALE_PMI_CHG_TEMP) + VADC_CHAN_VOLT(GND_REF, 0, SCALE_DEFAULT) + VADC_CHAN_VOLT(VDD_VADC, 0, SCALE_DEFAULT) + + VADC_CHAN_NO_SCALE(P_MUX1_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX2_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX3_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX4_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX5_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX6_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX7_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX8_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX9_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX10_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX11_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX12_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX13_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX14_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX15_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX16_1_1, 0) + + VADC_CHAN_NO_SCALE(P_MUX1_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX2_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX3_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX4_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX5_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX6_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX7_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX8_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX9_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX10_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX11_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX12_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX13_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX14_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX15_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX16_1_3, 1) + + VADC_CHAN_NO_SCALE(LR_MUX1_BAT_THERM, 0) + VADC_CHAN_VOLT(LR_MUX2_BAT_ID, 0, SCALE_DEFAULT) + VADC_CHAN_NO_SCALE(LR_MUX3_XO_THERM, 0) + VADC_CHAN_NO_SCALE(LR_MUX4_AMUX_THM1, 0) + VADC_CHAN_NO_SCALE(LR_MUX5_AMUX_THM2, 0) + VADC_CHAN_NO_SCALE(LR_MUX6_AMUX_THM3, 0) + VADC_CHAN_NO_SCALE(LR_MUX7_HW_ID, 0) + VADC_CHAN_NO_SCALE(LR_MUX8_AMUX_THM4, 0) + VADC_CHAN_NO_SCALE(LR_MUX9_AMUX_THM5, 0) + VADC_CHAN_NO_SCALE(LR_MUX10_USB_ID, 0) + VADC_CHAN_NO_SCALE(AMUX_PU1, 0) + VADC_CHAN_NO_SCALE(AMUX_PU2, 0) + VADC_CHAN_NO_SCALE(LR_MUX3_BUF_XO_THERM, 0) + + VADC_CHAN_NO_SCALE(LR_MUX1_PU1_BAT_THERM, 0) + VADC_CHAN_NO_SCALE(LR_MUX2_PU1_BAT_ID, 0) + VADC_CHAN_NO_SCALE(LR_MUX3_PU1_XO_THERM, 0) + VADC_CHAN_TEMP(LR_MUX4_PU1_AMUX_THM1, 0, SCALE_THERM_100K_PULLUP) + VADC_CHAN_TEMP(LR_MUX5_PU1_AMUX_THM2, 0, SCALE_THERM_100K_PULLUP) + VADC_CHAN_TEMP(LR_MUX6_PU1_AMUX_THM3, 0, SCALE_THERM_100K_PULLUP) + VADC_CHAN_NO_SCALE(LR_MUX7_PU1_AMUX_HW_ID, 0) + VADC_CHAN_TEMP(LR_MUX8_PU1_AMUX_THM4, 0, SCALE_THERM_100K_PULLUP) + VADC_CHAN_TEMP(LR_MUX9_PU1_AMUX_THM5, 0, SCALE_THERM_100K_PULLUP) + VADC_CHAN_NO_SCALE(LR_MUX10_PU1_AMUX_USB_ID, 0) + VADC_CHAN_TEMP(LR_MUX3_BUF_PU1_XO_THERM, 0, SCALE_XOTHERM) + + VADC_CHAN_NO_SCALE(LR_MUX1_PU2_BAT_THERM, 0) + VADC_CHAN_NO_SCALE(LR_MUX2_PU2_BAT_ID, 0) + VADC_CHAN_NO_SCALE(LR_MUX3_PU2_XO_THERM, 0) + VADC_CHAN_NO_SCALE(LR_MUX4_PU2_AMUX_THM1, 0) + VADC_CHAN_NO_SCALE(LR_MUX5_PU2_AMUX_THM2, 0) + VADC_CHAN_NO_SCALE(LR_MUX6_PU2_AMUX_THM3, 0) + VADC_CHAN_NO_SCALE(LR_MUX7_PU2_AMUX_HW_ID, 0) + VADC_CHAN_NO_SCALE(LR_MUX8_PU2_AMUX_THM4, 0) + VADC_CHAN_NO_SCALE(LR_MUX9_PU2_AMUX_THM5, 0) + VADC_CHAN_NO_SCALE(LR_MUX10_PU2_AMUX_USB_ID, 0) + VADC_CHAN_NO_SCALE(LR_MUX3_BUF_PU2_XO_THERM, 0) + + VADC_CHAN_NO_SCALE(LR_MUX1_PU1_PU2_BAT_THERM, 0) + VADC_CHAN_NO_SCALE(LR_MUX2_PU1_PU2_BAT_ID, 0) + VADC_CHAN_NO_SCALE(LR_MUX3_PU1_PU2_XO_THERM, 0) + VADC_CHAN_NO_SCALE(LR_MUX4_PU1_PU2_AMUX_THM1, 0) + VADC_CHAN_NO_SCALE(LR_MUX5_PU1_PU2_AMUX_THM2, 0) + VADC_CHAN_NO_SCALE(LR_MUX6_PU1_PU2_AMUX_THM3, 0) + VADC_CHAN_NO_SCALE(LR_MUX7_PU1_PU2_AMUX_HW_ID, 0) + VADC_CHAN_NO_SCALE(LR_MUX8_PU1_PU2_AMUX_THM4, 0) + VADC_CHAN_NO_SCALE(LR_MUX9_PU1_PU2_AMUX_THM5, 0) + VADC_CHAN_NO_SCALE(LR_MUX10_PU1_PU2_AMUX_USB_ID, 0) + VADC_CHAN_NO_SCALE(LR_MUX3_BUF_PU1_PU2_XO_THERM, 0) +}; + +static int vadc_get_fw_channel_data(struct device *dev, + struct vadc_channel_prop *prop, + struct fwnode_handle *fwnode) +{ + const char *name = fwnode_get_name(fwnode), *label; + u32 chan, value, varr[2]; + int ret; + + ret = fwnode_property_read_u32(fwnode, "reg", &chan); + if (ret) { + dev_err(dev, "invalid channel number %s\n", name); + return ret; + } + + if (chan > VADC_CHAN_MAX || chan < VADC_CHAN_MIN) { + dev_err(dev, "%s invalid channel number %d\n", name, chan); + return -EINVAL; + } + + ret = fwnode_property_read_string(fwnode, "label", &label); + if (ret) + label = vadc_chans[chan].datasheet_name; + prop->channel_name = label; + + /* the channel has DT description */ + prop->channel = chan; + + ret = fwnode_property_read_u32(fwnode, "qcom,decimation", &value); + if (!ret) { + ret = qcom_vadc_decimation_from_dt(value); + if (ret < 0) { + dev_err(dev, "%02x invalid decimation %d\n", + chan, value); + return ret; + } + prop->decimation = ret; + } else { + prop->decimation = VADC_DEF_DECIMATION; + } + + ret = fwnode_property_read_u32_array(fwnode, "qcom,pre-scaling", varr, 2); + if (!ret) { + ret = vadc_prescaling_from_dt(varr[0], varr[1]); + if (ret < 0) { + dev_err(dev, "%02x invalid pre-scaling <%d %d>\n", + chan, varr[0], varr[1]); + return ret; + } + prop->prescale = ret; + } else { + prop->prescale = vadc_chans[prop->channel].prescale_index; + } + + ret = fwnode_property_read_u32(fwnode, "qcom,hw-settle-time", &value); + if (!ret) { + ret = vadc_hw_settle_time_from_dt(value); + if (ret < 0) { + dev_err(dev, "%02x invalid hw-settle-time %d us\n", + chan, value); + return ret; + } + prop->hw_settle_time = ret; + } else { + prop->hw_settle_time = VADC_DEF_HW_SETTLE_TIME; + } + + ret = fwnode_property_read_u32(fwnode, "qcom,avg-samples", &value); + if (!ret) { + ret = vadc_avg_samples_from_dt(value); + if (ret < 0) { + dev_err(dev, "%02x invalid avg-samples %d\n", + chan, value); + return ret; + } + prop->avg_samples = ret; + } else { + prop->avg_samples = VADC_DEF_AVG_SAMPLES; + } + + if (fwnode_property_read_bool(fwnode, "qcom,ratiometric")) + prop->calibration = VADC_CALIB_RATIOMETRIC; + else + prop->calibration = VADC_CALIB_ABSOLUTE; + + dev_dbg(dev, "%02x name %s\n", chan, name); + + return 0; +} + +static int vadc_get_fw_data(struct vadc_priv *vadc) +{ + const struct vadc_channels *vadc_chan; + struct iio_chan_spec *iio_chan; + struct vadc_channel_prop prop; + unsigned int index = 0; + int ret; + + vadc->nchannels = device_get_child_node_count(vadc->dev); + if (!vadc->nchannels) + return -EINVAL; + + vadc->iio_chans = devm_kcalloc(vadc->dev, vadc->nchannels, + sizeof(*vadc->iio_chans), GFP_KERNEL); + if (!vadc->iio_chans) + return -ENOMEM; + + vadc->chan_props = devm_kcalloc(vadc->dev, vadc->nchannels, + sizeof(*vadc->chan_props), GFP_KERNEL); + if (!vadc->chan_props) + return -ENOMEM; + + iio_chan = vadc->iio_chans; + + device_for_each_child_node_scoped(vadc->dev, child) { + ret = vadc_get_fw_channel_data(vadc->dev, &prop, child); + if (ret) + return ret; + + prop.scale_fn_type = vadc_chans[prop.channel].scale_fn_type; + vadc->chan_props[index] = prop; + + vadc_chan = &vadc_chans[prop.channel]; + + iio_chan->channel = prop.channel; + iio_chan->datasheet_name = vadc_chan->datasheet_name; + iio_chan->info_mask_separate = vadc_chan->info_mask; + iio_chan->type = vadc_chan->type; + iio_chan->indexed = 1; + iio_chan->address = index++; + + iio_chan++; + } + + /* These channels are mandatory, they are used as reference points */ + if (!vadc_get_channel(vadc, VADC_REF_1250MV)) { + dev_err(vadc->dev, "Please define 1.25V channel\n"); + return -ENODEV; + } + + if (!vadc_get_channel(vadc, VADC_REF_625MV)) { + dev_err(vadc->dev, "Please define 0.625V channel\n"); + return -ENODEV; + } + + if (!vadc_get_channel(vadc, VADC_VDD_VADC)) { + dev_err(vadc->dev, "Please define VDD channel\n"); + return -ENODEV; + } + + if (!vadc_get_channel(vadc, VADC_GND_REF)) { + dev_err(vadc->dev, "Please define GND channel\n"); + return -ENODEV; + } + + return 0; +} + +static irqreturn_t vadc_isr(int irq, void *dev_id) +{ + struct vadc_priv *vadc = dev_id; + + complete(&vadc->complete); + + return IRQ_HANDLED; +} + +static int vadc_check_revision(struct vadc_priv *vadc) +{ + u8 val; + int ret; + + ret = vadc_read(vadc, VADC_PERPH_TYPE, &val); + if (ret) + return ret; + + if (val < VADC_PERPH_TYPE_ADC) { + dev_err(vadc->dev, "%d is not ADC\n", val); + return -ENODEV; + } + + ret = vadc_read(vadc, VADC_PERPH_SUBTYPE, &val); + if (ret) + return ret; + + if (val < VADC_PERPH_SUBTYPE_VADC) { + dev_err(vadc->dev, "%d is not VADC\n", val); + return -ENODEV; + } + + ret = vadc_read(vadc, VADC_REVISION2, &val); + if (ret) + return ret; + + if (val < VADC_REVISION2_SUPPORTED_VADC) { + dev_err(vadc->dev, "revision %d not supported\n", val); + return -ENODEV; + } + + return 0; +} + +static int vadc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct vadc_priv *vadc; + struct regmap *regmap; + int ret, irq_eoc; + u32 reg; + + regmap = dev_get_regmap(dev->parent, NULL); + if (!regmap) + return -ENODEV; + + ret = device_property_read_u32(dev, "reg", ®); + if (ret < 0) + return ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*vadc)); + if (!indio_dev) + return -ENOMEM; + + vadc = iio_priv(indio_dev); + vadc->regmap = regmap; + vadc->dev = dev; + vadc->base = reg; + vadc->are_ref_measured = false; + init_completion(&vadc->complete); + mutex_init(&vadc->lock); + + ret = vadc_check_revision(vadc); + if (ret) + return ret; + + ret = vadc_get_fw_data(vadc); + if (ret) + return ret; + + irq_eoc = platform_get_irq(pdev, 0); + if (irq_eoc < 0) { + if (irq_eoc == -EPROBE_DEFER || irq_eoc == -EINVAL) + return irq_eoc; + vadc->poll_eoc = true; + } else { + ret = devm_request_irq(dev, irq_eoc, vadc_isr, 0, + "spmi-vadc", vadc); + if (ret) + return ret; + } + + ret = vadc_reset(vadc); + if (ret) { + dev_err(dev, "reset failed\n"); + return ret; + } + + ret = vadc_measure_ref_points(vadc); + if (ret) + return ret; + + indio_dev->name = pdev->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &vadc_info; + indio_dev->channels = vadc->iio_chans; + indio_dev->num_channels = vadc->nchannels; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id vadc_match_table[] = { + { .compatible = "qcom,spmi-vadc" }, + { } +}; +MODULE_DEVICE_TABLE(of, vadc_match_table); + +static struct platform_driver vadc_driver = { + .driver = { + .name = "qcom-spmi-vadc", + .of_match_table = vadc_match_table, + }, + .probe = vadc_probe, +}; +module_platform_driver(vadc_driver); + +MODULE_ALIAS("platform:qcom-spmi-vadc"); +MODULE_DESCRIPTION("Qualcomm SPMI PMIC voltage ADC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Stanimir Varbanov "); +MODULE_AUTHOR("Ivan T. Ivanov "); diff --git a/drivers/iio/adc/qcom-vadc-common.c b/drivers/iio/adc/qcom-vadc-common.c new file mode 100644 index 000000000..b03cf584b --- /dev/null +++ b/drivers/iio/adc/qcom-vadc-common.c @@ -0,0 +1,772 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * struct vadc_map_pt - Map the graph representation for ADC channel + * @x: Represent the ADC digitized code. + * @y: Represent the physical data which can be temperature, voltage, + * resistance. + */ +struct vadc_map_pt { + s32 x; + s32 y; +}; + +/* Voltage to temperature */ +static const struct vadc_map_pt adcmap_100k_104ef_104fb[] = { + {1758, -40000 }, + {1742, -35000 }, + {1719, -30000 }, + {1691, -25000 }, + {1654, -20000 }, + {1608, -15000 }, + {1551, -10000 }, + {1483, -5000 }, + {1404, 0 }, + {1315, 5000 }, + {1218, 10000 }, + {1114, 15000 }, + {1007, 20000 }, + {900, 25000 }, + {795, 30000 }, + {696, 35000 }, + {605, 40000 }, + {522, 45000 }, + {448, 50000 }, + {383, 55000 }, + {327, 60000 }, + {278, 65000 }, + {237, 70000 }, + {202, 75000 }, + {172, 80000 }, + {146, 85000 }, + {125, 90000 }, + {107, 95000 }, + {92, 100000 }, + {79, 105000 }, + {68, 110000 }, + {59, 115000 }, + {51, 120000 }, + {44, 125000 } +}; + +/* + * Voltage to temperature table for 100k pull up for NTCG104EF104 with + * 1.875V reference. + */ +static const struct vadc_map_pt adcmap_100k_104ef_104fb_1875_vref[] = { + { 1831, -40000 }, + { 1814, -35000 }, + { 1791, -30000 }, + { 1761, -25000 }, + { 1723, -20000 }, + { 1675, -15000 }, + { 1616, -10000 }, + { 1545, -5000 }, + { 1463, 0 }, + { 1370, 5000 }, + { 1268, 10000 }, + { 1160, 15000 }, + { 1049, 20000 }, + { 937, 25000 }, + { 828, 30000 }, + { 726, 35000 }, + { 630, 40000 }, + { 544, 45000 }, + { 467, 50000 }, + { 399, 55000 }, + { 340, 60000 }, + { 290, 65000 }, + { 247, 70000 }, + { 209, 75000 }, + { 179, 80000 }, + { 153, 85000 }, + { 130, 90000 }, + { 112, 95000 }, + { 96, 100000 }, + { 82, 105000 }, + { 71, 110000 }, + { 62, 115000 }, + { 53, 120000 }, + { 46, 125000 }, +}; + +static const struct vadc_map_pt adcmap7_die_temp[] = { + { 857300, 160000 }, + { 820100, 140000 }, + { 782500, 120000 }, + { 744600, 100000 }, + { 706400, 80000 }, + { 667900, 60000 }, + { 629300, 40000 }, + { 590500, 20000 }, + { 551500, 0 }, + { 512400, -20000 }, + { 473100, -40000 }, + { 433700, -60000 }, +}; + +/* + * Resistance to temperature table for 100k pull up for NTCG104EF104. + */ +static const struct vadc_map_pt adcmap7_100k[] = { + { 4250657, -40960 }, + { 3962085, -39936 }, + { 3694875, -38912 }, + { 3447322, -37888 }, + { 3217867, -36864 }, + { 3005082, -35840 }, + { 2807660, -34816 }, + { 2624405, -33792 }, + { 2454218, -32768 }, + { 2296094, -31744 }, + { 2149108, -30720 }, + { 2012414, -29696 }, + { 1885232, -28672 }, + { 1766846, -27648 }, + { 1656598, -26624 }, + { 1553884, -25600 }, + { 1458147, -24576 }, + { 1368873, -23552 }, + { 1285590, -22528 }, + { 1207863, -21504 }, + { 1135290, -20480 }, + { 1067501, -19456 }, + { 1004155, -18432 }, + { 944935, -17408 }, + { 889550, -16384 }, + { 837731, -15360 }, + { 789229, -14336 }, + { 743813, -13312 }, + { 701271, -12288 }, + { 661405, -11264 }, + { 624032, -10240 }, + { 588982, -9216 }, + { 556100, -8192 }, + { 525239, -7168 }, + { 496264, -6144 }, + { 469050, -5120 }, + { 443480, -4096 }, + { 419448, -3072 }, + { 396851, -2048 }, + { 375597, -1024 }, + { 355598, 0 }, + { 336775, 1024 }, + { 319052, 2048 }, + { 302359, 3072 }, + { 286630, 4096 }, + { 271806, 5120 }, + { 257829, 6144 }, + { 244646, 7168 }, + { 232209, 8192 }, + { 220471, 9216 }, + { 209390, 10240 }, + { 198926, 11264 }, + { 189040, 12288 }, + { 179698, 13312 }, + { 170868, 14336 }, + { 162519, 15360 }, + { 154622, 16384 }, + { 147150, 17408 }, + { 140079, 18432 }, + { 133385, 19456 }, + { 127046, 20480 }, + { 121042, 21504 }, + { 115352, 22528 }, + { 109960, 23552 }, + { 104848, 24576 }, + { 100000, 25600 }, + { 95402, 26624 }, + { 91038, 27648 }, + { 86897, 28672 }, + { 82965, 29696 }, + { 79232, 30720 }, + { 75686, 31744 }, + { 72316, 32768 }, + { 69114, 33792 }, + { 66070, 34816 }, + { 63176, 35840 }, + { 60423, 36864 }, + { 57804, 37888 }, + { 55312, 38912 }, + { 52940, 39936 }, + { 50681, 40960 }, + { 48531, 41984 }, + { 46482, 43008 }, + { 44530, 44032 }, + { 42670, 45056 }, + { 40897, 46080 }, + { 39207, 47104 }, + { 37595, 48128 }, + { 36057, 49152 }, + { 34590, 50176 }, + { 33190, 51200 }, + { 31853, 52224 }, + { 30577, 53248 }, + { 29358, 54272 }, + { 28194, 55296 }, + { 27082, 56320 }, + { 26020, 57344 }, + { 25004, 58368 }, + { 24033, 59392 }, + { 23104, 60416 }, + { 22216, 61440 }, + { 21367, 62464 }, + { 20554, 63488 }, + { 19776, 64512 }, + { 19031, 65536 }, + { 18318, 66560 }, + { 17636, 67584 }, + { 16982, 68608 }, + { 16355, 69632 }, + { 15755, 70656 }, + { 15180, 71680 }, + { 14628, 72704 }, + { 14099, 73728 }, + { 13592, 74752 }, + { 13106, 75776 }, + { 12640, 76800 }, + { 12192, 77824 }, + { 11762, 78848 }, + { 11350, 79872 }, + { 10954, 80896 }, + { 10574, 81920 }, + { 10209, 82944 }, + { 9858, 83968 }, + { 9521, 84992 }, + { 9197, 86016 }, + { 8886, 87040 }, + { 8587, 88064 }, + { 8299, 89088 }, + { 8023, 90112 }, + { 7757, 91136 }, + { 7501, 92160 }, + { 7254, 93184 }, + { 7017, 94208 }, + { 6789, 95232 }, + { 6570, 96256 }, + { 6358, 97280 }, + { 6155, 98304 }, + { 5959, 99328 }, + { 5770, 100352 }, + { 5588, 101376 }, + { 5412, 102400 }, + { 5243, 103424 }, + { 5080, 104448 }, + { 4923, 105472 }, + { 4771, 106496 }, + { 4625, 107520 }, + { 4484, 108544 }, + { 4348, 109568 }, + { 4217, 110592 }, + { 4090, 111616 }, + { 3968, 112640 }, + { 3850, 113664 }, + { 3736, 114688 }, + { 3626, 115712 }, + { 3519, 116736 }, + { 3417, 117760 }, + { 3317, 118784 }, + { 3221, 119808 }, + { 3129, 120832 }, + { 3039, 121856 }, + { 2952, 122880 }, + { 2868, 123904 }, + { 2787, 124928 }, + { 2709, 125952 }, + { 2633, 126976 }, + { 2560, 128000 }, + { 2489, 129024 }, + { 2420, 130048 } +}; + +static const struct u32_fract adc5_prescale_ratios[] = { + { .numerator = 1, .denominator = 1 }, + { .numerator = 1, .denominator = 3 }, + { .numerator = 1, .denominator = 4 }, + { .numerator = 1, .denominator = 6 }, + { .numerator = 1, .denominator = 20 }, + { .numerator = 1, .denominator = 8 }, + { .numerator = 10, .denominator = 81 }, + { .numerator = 1, .denominator = 10 }, + { .numerator = 1, .denominator = 16 }, +}; + +static int qcom_vadc_scale_hw_calib_volt( + const struct u32_fract *prescale, + const struct adc5_data *data, + u16 adc_code, int *result_uv); +static int qcom_vadc_scale_hw_calib_therm( + const struct u32_fract *prescale, + const struct adc5_data *data, + u16 adc_code, int *result_mdec); +static int qcom_vadc7_scale_hw_calib_therm( + const struct u32_fract *prescale, + const struct adc5_data *data, + u16 adc_code, int *result_mdec); +static int qcom_vadc_scale_hw_smb_temp( + const struct u32_fract *prescale, + const struct adc5_data *data, + u16 adc_code, int *result_mdec); +static int qcom_vadc_scale_hw_chg5_temp( + const struct u32_fract *prescale, + const struct adc5_data *data, + u16 adc_code, int *result_mdec); +static int qcom_vadc_scale_hw_calib_die_temp( + const struct u32_fract *prescale, + const struct adc5_data *data, + u16 adc_code, int *result_mdec); +static int qcom_vadc7_scale_hw_calib_die_temp( + const struct u32_fract *prescale, + const struct adc5_data *data, + u16 adc_code, int *result_mdec); + +static const struct qcom_adc5_scale_type scale_adc5_fn[] = { + [SCALE_HW_CALIB_DEFAULT] = {qcom_vadc_scale_hw_calib_volt}, + [SCALE_HW_CALIB_THERM_100K_PULLUP] = {qcom_vadc_scale_hw_calib_therm}, + [SCALE_HW_CALIB_XOTHERM] = {qcom_vadc_scale_hw_calib_therm}, + [SCALE_HW_CALIB_THERM_100K_PU_PM7] = { + qcom_vadc7_scale_hw_calib_therm}, + [SCALE_HW_CALIB_PMIC_THERM] = {qcom_vadc_scale_hw_calib_die_temp}, + [SCALE_HW_CALIB_PMIC_THERM_PM7] = { + qcom_vadc7_scale_hw_calib_die_temp}, + [SCALE_HW_CALIB_PM5_CHG_TEMP] = {qcom_vadc_scale_hw_chg5_temp}, + [SCALE_HW_CALIB_PM5_SMB_TEMP] = {qcom_vadc_scale_hw_smb_temp}, +}; + +static int qcom_vadc_map_voltage_temp(const struct vadc_map_pt *pts, + u32 tablesize, s32 input, int *output) +{ + u32 i = 0; + + if (!pts) + return -EINVAL; + + while (i < tablesize && pts[i].x > input) + i++; + + if (i == 0) { + *output = pts[0].y; + } else if (i == tablesize) { + *output = pts[tablesize - 1].y; + } else { + /* interpolate linearly */ + *output = fixp_linear_interpolate(pts[i - 1].x, pts[i - 1].y, + pts[i].x, pts[i].y, + input); + } + + return 0; +} + +static s32 qcom_vadc_map_temp_voltage(const struct vadc_map_pt *pts, + u32 tablesize, int input) +{ + u32 i = 0; + + /* + * Table must be sorted, find the interval of 'y' which contains value + * 'input' and map it to proper 'x' value + */ + while (i < tablesize && pts[i].y < input) + i++; + + if (i == 0) + return pts[0].x; + if (i == tablesize) + return pts[tablesize - 1].x; + + /* interpolate linearly */ + return fixp_linear_interpolate(pts[i - 1].y, pts[i - 1].x, + pts[i].y, pts[i].x, input); +} + +static void qcom_vadc_scale_calib(const struct vadc_linear_graph *calib_graph, + u16 adc_code, + bool absolute, + s64 *scale_voltage) +{ + *scale_voltage = (adc_code - calib_graph->gnd); + *scale_voltage *= calib_graph->dx; + *scale_voltage = div64_s64(*scale_voltage, calib_graph->dy); + if (absolute) + *scale_voltage += calib_graph->dx; + + if (*scale_voltage < 0) + *scale_voltage = 0; +} + +static int qcom_vadc_scale_volt(const struct vadc_linear_graph *calib_graph, + const struct u32_fract *prescale, + bool absolute, u16 adc_code, + int *result_uv) +{ + s64 voltage = 0, result = 0; + + qcom_vadc_scale_calib(calib_graph, adc_code, absolute, &voltage); + + voltage *= prescale->denominator; + result = div64_s64(voltage, prescale->numerator); + *result_uv = result; + + return 0; +} + +static int qcom_vadc_scale_therm(const struct vadc_linear_graph *calib_graph, + const struct u32_fract *prescale, + bool absolute, u16 adc_code, + int *result_mdec) +{ + s64 voltage = 0; + int ret; + + qcom_vadc_scale_calib(calib_graph, adc_code, absolute, &voltage); + + if (absolute) + voltage = div64_s64(voltage, 1000); + + ret = qcom_vadc_map_voltage_temp(adcmap_100k_104ef_104fb, + ARRAY_SIZE(adcmap_100k_104ef_104fb), + voltage, result_mdec); + if (ret) + return ret; + + return 0; +} + +static int qcom_vadc_scale_die_temp(const struct vadc_linear_graph *calib_graph, + const struct u32_fract *prescale, + bool absolute, + u16 adc_code, int *result_mdec) +{ + s64 voltage = 0; + u64 temp; /* Temporary variable for do_div */ + + qcom_vadc_scale_calib(calib_graph, adc_code, absolute, &voltage); + + if (voltage > 0) { + temp = voltage * prescale->denominator; + do_div(temp, prescale->numerator * 2); + voltage = temp; + } else { + voltage = 0; + } + + *result_mdec = milli_kelvin_to_millicelsius(voltage); + + return 0; +} + +static int qcom_vadc_scale_chg_temp(const struct vadc_linear_graph *calib_graph, + const struct u32_fract *prescale, + bool absolute, + u16 adc_code, int *result_mdec) +{ + s64 voltage = 0, result = 0; + + qcom_vadc_scale_calib(calib_graph, adc_code, absolute, &voltage); + + voltage *= prescale->denominator; + voltage = div64_s64(voltage, prescale->numerator); + voltage = ((PMI_CHG_SCALE_1) * (voltage * 2)); + voltage = (voltage + PMI_CHG_SCALE_2); + result = div64_s64(voltage, 1000000); + *result_mdec = result; + + return 0; +} + +/* convert voltage to ADC code, using 1.875V reference */ +static u16 qcom_vadc_scale_voltage_code(s32 voltage, + const struct u32_fract *prescale, + const u32 full_scale_code_volt, + unsigned int factor) +{ + s64 volt = voltage; + s64 adc_vdd_ref_mv = 1875; /* reference voltage */ + + volt *= prescale->numerator * factor * full_scale_code_volt; + volt = div64_s64(volt, (s64)prescale->denominator * adc_vdd_ref_mv * 1000); + + return volt; +} + +static int qcom_vadc_scale_code_voltage_factor(u16 adc_code, + const struct u32_fract *prescale, + const struct adc5_data *data, + unsigned int factor) +{ + s64 voltage, temp, adc_vdd_ref_mv = 1875; + + /* + * The normal data range is between 0V to 1.875V. On cases where + * we read low voltage values, the ADC code can go beyond the + * range and the scale result is incorrect so we clamp the values + * for the cases where the code represents a value below 0V + */ + if (adc_code > VADC5_MAX_CODE) + adc_code = 0; + + /* (ADC code * vref_vadc (1.875V)) / full_scale_code */ + voltage = (s64) adc_code * adc_vdd_ref_mv * 1000; + voltage = div64_s64(voltage, data->full_scale_code_volt); + if (voltage > 0) { + voltage *= prescale->denominator; + temp = prescale->numerator * factor; + voltage = div64_s64(voltage, temp); + } else { + voltage = 0; + } + + return (int) voltage; +} + +static int qcom_vadc7_scale_hw_calib_therm( + const struct u32_fract *prescale, + const struct adc5_data *data, + u16 adc_code, int *result_mdec) +{ + s64 resistance = adc_code; + int ret, result; + + if (adc_code >= RATIO_MAX_ADC7) + return -EINVAL; + + /* (ADC code * R_PULLUP (100Kohm)) / (full_scale_code - ADC code)*/ + resistance *= R_PU_100K; + resistance = div64_s64(resistance, RATIO_MAX_ADC7 - adc_code); + + ret = qcom_vadc_map_voltage_temp(adcmap7_100k, + ARRAY_SIZE(adcmap7_100k), + resistance, &result); + if (ret) + return ret; + + *result_mdec = result; + + return 0; +} + +static int qcom_vadc_scale_hw_calib_volt( + const struct u32_fract *prescale, + const struct adc5_data *data, + u16 adc_code, int *result_uv) +{ + *result_uv = qcom_vadc_scale_code_voltage_factor(adc_code, + prescale, data, 1); + + return 0; +} + +static int qcom_vadc_scale_hw_calib_therm( + const struct u32_fract *prescale, + const struct adc5_data *data, + u16 adc_code, int *result_mdec) +{ + int voltage; + + voltage = qcom_vadc_scale_code_voltage_factor(adc_code, + prescale, data, 1000); + + /* Map voltage to temperature from look-up table */ + return qcom_vadc_map_voltage_temp(adcmap_100k_104ef_104fb_1875_vref, + ARRAY_SIZE(adcmap_100k_104ef_104fb_1875_vref), + voltage, result_mdec); +} + +static int qcom_vadc_scale_hw_calib_die_temp( + const struct u32_fract *prescale, + const struct adc5_data *data, + u16 adc_code, int *result_mdec) +{ + *result_mdec = qcom_vadc_scale_code_voltage_factor(adc_code, + prescale, data, 2); + *result_mdec = milli_kelvin_to_millicelsius(*result_mdec); + + return 0; +} + +static int qcom_vadc7_scale_hw_calib_die_temp( + const struct u32_fract *prescale, + const struct adc5_data *data, + u16 adc_code, int *result_mdec) +{ + + int voltage; + + voltage = qcom_vadc_scale_code_voltage_factor(adc_code, + prescale, data, 1); + + return qcom_vadc_map_voltage_temp(adcmap7_die_temp, ARRAY_SIZE(adcmap7_die_temp), + voltage, result_mdec); +} + +static int qcom_vadc_scale_hw_smb_temp( + const struct u32_fract *prescale, + const struct adc5_data *data, + u16 adc_code, int *result_mdec) +{ + *result_mdec = qcom_vadc_scale_code_voltage_factor(adc_code * 100, + prescale, data, PMIC5_SMB_TEMP_SCALE_FACTOR); + *result_mdec = PMIC5_SMB_TEMP_CONSTANT - *result_mdec; + + return 0; +} + +static int qcom_vadc_scale_hw_chg5_temp( + const struct u32_fract *prescale, + const struct adc5_data *data, + u16 adc_code, int *result_mdec) +{ + *result_mdec = qcom_vadc_scale_code_voltage_factor(adc_code, + prescale, data, 4); + *result_mdec = PMIC5_CHG_TEMP_SCALE_FACTOR - *result_mdec; + + return 0; +} + +int qcom_vadc_scale(enum vadc_scale_fn_type scaletype, + const struct vadc_linear_graph *calib_graph, + const struct u32_fract *prescale, + bool absolute, + u16 adc_code, int *result) +{ + switch (scaletype) { + case SCALE_DEFAULT: + return qcom_vadc_scale_volt(calib_graph, prescale, + absolute, adc_code, + result); + case SCALE_THERM_100K_PULLUP: + case SCALE_XOTHERM: + return qcom_vadc_scale_therm(calib_graph, prescale, + absolute, adc_code, + result); + case SCALE_PMIC_THERM: + return qcom_vadc_scale_die_temp(calib_graph, prescale, + absolute, adc_code, + result); + case SCALE_PMI_CHG_TEMP: + return qcom_vadc_scale_chg_temp(calib_graph, prescale, + absolute, adc_code, + result); + default: + return -EINVAL; + } +} +EXPORT_SYMBOL(qcom_vadc_scale); + +u16 qcom_adc_tm5_temp_volt_scale(unsigned int prescale_ratio, + u32 full_scale_code_volt, int temp) +{ + const struct u32_fract *prescale = &adc5_prescale_ratios[prescale_ratio]; + s32 voltage; + + voltage = qcom_vadc_map_temp_voltage(adcmap_100k_104ef_104fb_1875_vref, + ARRAY_SIZE(adcmap_100k_104ef_104fb_1875_vref), + temp); + return qcom_vadc_scale_voltage_code(voltage, prescale, full_scale_code_volt, 1000); +} +EXPORT_SYMBOL(qcom_adc_tm5_temp_volt_scale); + +u16 qcom_adc_tm5_gen2_temp_res_scale(int temp) +{ + int64_t resistance; + + resistance = qcom_vadc_map_temp_voltage(adcmap7_100k, + ARRAY_SIZE(adcmap7_100k), temp); + + return div64_s64(resistance * RATIO_MAX_ADC7, resistance + R_PU_100K); +} +EXPORT_SYMBOL(qcom_adc_tm5_gen2_temp_res_scale); + +int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype, + unsigned int prescale_ratio, + const struct adc5_data *data, + u16 adc_code, int *result) +{ + const struct u32_fract *prescale = &adc5_prescale_ratios[prescale_ratio]; + + if (!(scaletype >= SCALE_HW_CALIB_DEFAULT && + scaletype < SCALE_HW_CALIB_INVALID)) { + pr_err("Invalid scale type %d\n", scaletype); + return -EINVAL; + } + + return scale_adc5_fn[scaletype].scale_fn(prescale, data, + adc_code, result); +} +EXPORT_SYMBOL(qcom_adc5_hw_scale); + +int qcom_adc5_prescaling_from_dt(u32 numerator, u32 denominator) +{ + unsigned int pre; + + for (pre = 0; pre < ARRAY_SIZE(adc5_prescale_ratios); pre++) + if (adc5_prescale_ratios[pre].numerator == numerator && + adc5_prescale_ratios[pre].denominator == denominator) + break; + + if (pre == ARRAY_SIZE(adc5_prescale_ratios)) + return -EINVAL; + + return pre; +} +EXPORT_SYMBOL(qcom_adc5_prescaling_from_dt); + +int qcom_adc5_hw_settle_time_from_dt(u32 value, + const unsigned int *hw_settle) +{ + unsigned int i; + + for (i = 0; i < VADC_HW_SETTLE_SAMPLES_MAX; i++) { + if (value == hw_settle[i]) + return i; + } + + return -EINVAL; +} +EXPORT_SYMBOL(qcom_adc5_hw_settle_time_from_dt); + +int qcom_adc5_avg_samples_from_dt(u32 value) +{ + if (!is_power_of_2(value) || value > ADC5_AVG_SAMPLES_MAX) + return -EINVAL; + + return __ffs(value); +} +EXPORT_SYMBOL(qcom_adc5_avg_samples_from_dt); + +int qcom_adc5_decimation_from_dt(u32 value, const unsigned int *decimation) +{ + unsigned int i; + + for (i = 0; i < ADC5_DECIMATION_SAMPLES_MAX; i++) { + if (value == decimation[i]) + return i; + } + + return -EINVAL; +} +EXPORT_SYMBOL(qcom_adc5_decimation_from_dt); + +int qcom_vadc_decimation_from_dt(u32 value) +{ + if (!is_power_of_2(value) || value < VADC_DECIMATION_MIN || + value > VADC_DECIMATION_MAX) + return -EINVAL; + + return __ffs64(value / VADC_DECIMATION_MIN); +} +EXPORT_SYMBOL(qcom_vadc_decimation_from_dt); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Qualcomm ADC common functionality"); diff --git a/drivers/iio/adc/rcar-gyroadc.c b/drivers/iio/adc/rcar-gyroadc.c new file mode 100644 index 000000000..d38cf3cae --- /dev/null +++ b/drivers/iio/adc/rcar-gyroadc.c @@ -0,0 +1,594 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Renesas R-Car GyroADC driver + * + * Copyright 2016 Marek Vasut + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define DRIVER_NAME "rcar-gyroadc" + +/* GyroADC registers. */ +#define RCAR_GYROADC_MODE_SELECT 0x00 +#define RCAR_GYROADC_MODE_SELECT_1_MB88101A 0x0 +#define RCAR_GYROADC_MODE_SELECT_2_ADCS7476 0x1 +#define RCAR_GYROADC_MODE_SELECT_3_MAX1162 0x3 + +#define RCAR_GYROADC_START_STOP 0x04 +#define RCAR_GYROADC_START_STOP_START BIT(0) + +#define RCAR_GYROADC_CLOCK_LENGTH 0x08 +#define RCAR_GYROADC_1_25MS_LENGTH 0x0c + +#define RCAR_GYROADC_REALTIME_DATA(ch) (0x10 + ((ch) * 4)) +#define RCAR_GYROADC_100MS_ADDED_DATA(ch) (0x30 + ((ch) * 4)) +#define RCAR_GYROADC_10MS_AVG_DATA(ch) (0x50 + ((ch) * 4)) + +#define RCAR_GYROADC_FIFO_STATUS 0x70 +#define RCAR_GYROADC_FIFO_STATUS_EMPTY(ch) BIT(0 + (4 * (ch))) +#define RCAR_GYROADC_FIFO_STATUS_FULL(ch) BIT(1 + (4 * (ch))) +#define RCAR_GYROADC_FIFO_STATUS_ERROR(ch) BIT(2 + (4 * (ch))) + +#define RCAR_GYROADC_INTR 0x74 +#define RCAR_GYROADC_INTR_INT BIT(0) + +#define RCAR_GYROADC_INTENR 0x78 +#define RCAR_GYROADC_INTENR_INTEN BIT(0) + +#define RCAR_GYROADC_SAMPLE_RATE 800 /* Hz */ + +#define RCAR_GYROADC_RUNTIME_PM_DELAY_MS 2000 + +enum rcar_gyroadc_model { + RCAR_GYROADC_MODEL_DEFAULT, + RCAR_GYROADC_MODEL_R8A7792, +}; + +struct rcar_gyroadc { + struct device *dev; + void __iomem *regs; + struct clk *clk; + struct regulator *vref[8]; + unsigned int num_channels; + enum rcar_gyroadc_model model; + unsigned int mode; + unsigned int sample_width; +}; + +static void rcar_gyroadc_hw_init(struct rcar_gyroadc *priv) +{ + const unsigned long clk_mhz = clk_get_rate(priv->clk) / 1000000; + const unsigned long clk_mul = + (priv->mode == RCAR_GYROADC_MODE_SELECT_1_MB88101A) ? 10 : 5; + unsigned long clk_len = clk_mhz * clk_mul; + + /* + * According to the R-Car Gen2 datasheet Rev. 1.01, Sept 08 2014, + * page 77-7, clock length must be even number. If it's odd number, + * add one. + */ + if (clk_len & 1) + clk_len++; + + /* Stop the GyroADC. */ + writel(0, priv->regs + RCAR_GYROADC_START_STOP); + + /* Disable IRQ on V2H. */ + if (priv->model == RCAR_GYROADC_MODEL_R8A7792) + writel(0, priv->regs + RCAR_GYROADC_INTENR); + + /* Set mode and timing. */ + writel(priv->mode, priv->regs + RCAR_GYROADC_MODE_SELECT); + writel(clk_len, priv->regs + RCAR_GYROADC_CLOCK_LENGTH); + writel(clk_mhz * 1250, priv->regs + RCAR_GYROADC_1_25MS_LENGTH); +} + +static void rcar_gyroadc_hw_start(struct rcar_gyroadc *priv) +{ + /* Start sampling. */ + writel(RCAR_GYROADC_START_STOP_START, + priv->regs + RCAR_GYROADC_START_STOP); + + /* + * Wait for the first conversion to complete. This is longer than + * the 1.25 mS in the datasheet because 1.25 mS is not enough for + * the hardware to deliver the first sample and the hardware does + * then return zeroes instead of valid data. + */ + mdelay(3); +} + +static void rcar_gyroadc_hw_stop(struct rcar_gyroadc *priv) +{ + /* Stop the GyroADC. */ + writel(0, priv->regs + RCAR_GYROADC_START_STOP); +} + +#define RCAR_GYROADC_CHAN(_idx) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ +} + +static const struct iio_chan_spec rcar_gyroadc_iio_channels_1[] = { + RCAR_GYROADC_CHAN(0), + RCAR_GYROADC_CHAN(1), + RCAR_GYROADC_CHAN(2), + RCAR_GYROADC_CHAN(3), +}; + +static const struct iio_chan_spec rcar_gyroadc_iio_channels_2[] = { + RCAR_GYROADC_CHAN(0), + RCAR_GYROADC_CHAN(1), + RCAR_GYROADC_CHAN(2), + RCAR_GYROADC_CHAN(3), + RCAR_GYROADC_CHAN(4), + RCAR_GYROADC_CHAN(5), + RCAR_GYROADC_CHAN(6), + RCAR_GYROADC_CHAN(7), +}; + +static const struct iio_chan_spec rcar_gyroadc_iio_channels_3[] = { + RCAR_GYROADC_CHAN(0), + RCAR_GYROADC_CHAN(1), + RCAR_GYROADC_CHAN(2), + RCAR_GYROADC_CHAN(3), + RCAR_GYROADC_CHAN(4), + RCAR_GYROADC_CHAN(5), + RCAR_GYROADC_CHAN(6), + RCAR_GYROADC_CHAN(7), +}; + +static int rcar_gyroadc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct rcar_gyroadc *priv = iio_priv(indio_dev); + struct regulator *consumer; + unsigned int datareg = RCAR_GYROADC_REALTIME_DATA(chan->channel); + unsigned int vref; + int ret; + + /* + * MB88101 is special in that it has only single regulator for + * all four channels. + */ + if (priv->mode == RCAR_GYROADC_MODE_SELECT_1_MB88101A) + consumer = priv->vref[0]; + else + consumer = priv->vref[chan->channel]; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type != IIO_VOLTAGE) + return -EINVAL; + + /* Channel not connected. */ + if (!consumer) + return -EINVAL; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = pm_runtime_resume_and_get(priv->dev); + if (ret < 0) { + iio_device_release_direct(indio_dev); + return ret; + } + + *val = readl(priv->regs + datareg); + *val &= BIT(priv->sample_width) - 1; + + ret = pm_runtime_put_autosuspend(priv->dev); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* Channel not connected. */ + if (!consumer) + return -EINVAL; + + vref = regulator_get_voltage(consumer); + *val = vref / 1000; + *val2 = 1 << priv->sample_width; + + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = RCAR_GYROADC_SAMPLE_RATE; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int rcar_gyroadc_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + struct rcar_gyroadc *priv = iio_priv(indio_dev); + unsigned int maxreg = RCAR_GYROADC_FIFO_STATUS; + + if (readval == NULL) + return -EINVAL; + + if (reg % 4) + return -EINVAL; + + /* Handle the V2H case with extra interrupt block. */ + if (priv->model == RCAR_GYROADC_MODEL_R8A7792) + maxreg = RCAR_GYROADC_INTENR; + + if (reg > maxreg) + return -EINVAL; + + *readval = readl(priv->regs + reg); + + return 0; +} + +static const struct iio_info rcar_gyroadc_iio_info = { + .read_raw = rcar_gyroadc_read_raw, + .debugfs_reg_access = rcar_gyroadc_reg_access, +}; + +static const struct of_device_id rcar_gyroadc_match[] = { + { + /* R-Car compatible GyroADC */ + .compatible = "renesas,rcar-gyroadc", + .data = (void *)RCAR_GYROADC_MODEL_DEFAULT, + }, { + /* R-Car V2H specialty with interrupt registers. */ + .compatible = "renesas,r8a7792-gyroadc", + .data = (void *)RCAR_GYROADC_MODEL_R8A7792, + }, { + /* sentinel */ + } +}; + +MODULE_DEVICE_TABLE(of, rcar_gyroadc_match); + +static const struct of_device_id rcar_gyroadc_child_match[] __maybe_unused = { + /* Mode 1 ADCs */ + { + .compatible = "fujitsu,mb88101a", + .data = (void *)RCAR_GYROADC_MODE_SELECT_1_MB88101A, + }, + /* Mode 2 ADCs */ + { + .compatible = "ti,adcs7476", + .data = (void *)RCAR_GYROADC_MODE_SELECT_2_ADCS7476, + }, { + .compatible = "ti,adc121", + .data = (void *)RCAR_GYROADC_MODE_SELECT_2_ADCS7476, + }, { + .compatible = "adi,ad7476", + .data = (void *)RCAR_GYROADC_MODE_SELECT_2_ADCS7476, + }, + /* Mode 3 ADCs */ + { + .compatible = "maxim,max1162", + .data = (void *)RCAR_GYROADC_MODE_SELECT_3_MAX1162, + }, { + .compatible = "maxim,max11100", + .data = (void *)RCAR_GYROADC_MODE_SELECT_3_MAX1162, + }, + { } +}; + +static int rcar_gyroadc_parse_subdevs(struct iio_dev *indio_dev) +{ + const struct of_device_id *of_id; + const struct iio_chan_spec *channels; + struct rcar_gyroadc *priv = iio_priv(indio_dev); + struct device *dev = priv->dev; + struct device_node *np = dev->of_node; + struct regulator *vref; + unsigned int reg; + unsigned int adcmode = -1, childmode; + unsigned int sample_width; + unsigned int num_channels; + int ret, first = 1; + + for_each_available_child_of_node_scoped(np, child) { + of_id = of_match_node(rcar_gyroadc_child_match, child); + if (!of_id) { + dev_err(dev, "Ignoring unsupported ADC \"%pOFn\".", + child); + continue; + } + + childmode = (uintptr_t)of_id->data; + switch (childmode) { + case RCAR_GYROADC_MODE_SELECT_1_MB88101A: + sample_width = 12; + channels = rcar_gyroadc_iio_channels_1; + num_channels = ARRAY_SIZE(rcar_gyroadc_iio_channels_1); + break; + case RCAR_GYROADC_MODE_SELECT_2_ADCS7476: + sample_width = 15; + channels = rcar_gyroadc_iio_channels_2; + num_channels = ARRAY_SIZE(rcar_gyroadc_iio_channels_2); + break; + case RCAR_GYROADC_MODE_SELECT_3_MAX1162: + sample_width = 16; + channels = rcar_gyroadc_iio_channels_3; + num_channels = ARRAY_SIZE(rcar_gyroadc_iio_channels_3); + break; + default: + return -EINVAL; + } + + /* + * MB88101 is special in that it's only a single chip taking + * up all the CHS lines. Thus, the DT binding is also special + * and has no reg property. If we run into such ADC, handle + * it here. + */ + if (childmode == RCAR_GYROADC_MODE_SELECT_1_MB88101A) { + reg = 0; + } else { + ret = of_property_read_u32(child, "reg", ®); + if (ret) { + dev_err(dev, + "Failed to get child reg property of ADC \"%pOFn\".\n", + child); + return ret; + } + + /* Channel number is too high. */ + if (reg >= num_channels) { + dev_err(dev, + "Only %i channels supported with %pOFn, but reg = <%i>.\n", + num_channels, child, reg); + return -EINVAL; + } + } + + /* Child node selected different mode than the rest. */ + if (!first && (adcmode != childmode)) { + dev_err(dev, + "Channel %i uses different ADC mode than the rest.\n", + reg); + return -EINVAL; + } + + /* Channel is valid, grab the regulator. */ + dev->of_node = child; + vref = devm_regulator_get(dev, "vref"); + dev->of_node = np; + if (IS_ERR(vref)) { + dev_dbg(dev, "Channel %i 'vref' supply not connected.\n", + reg); + return PTR_ERR(vref); + } + + priv->vref[reg] = vref; + + if (!first) + continue; + + /* First child node which passed sanity tests. */ + adcmode = childmode; + first = 0; + + priv->num_channels = num_channels; + priv->mode = childmode; + priv->sample_width = sample_width; + + indio_dev->channels = channels; + indio_dev->num_channels = num_channels; + + /* + * MB88101 is special and we only have one such device + * attached to the GyroADC at a time, so if we found it, + * we can stop parsing here. + */ + if (childmode == RCAR_GYROADC_MODE_SELECT_1_MB88101A) { + break; + } + } + + if (first) { + dev_err(dev, "No valid ADC channels found, aborting.\n"); + return -EINVAL; + } + + return 0; +} + +static void rcar_gyroadc_deinit_supplies(struct iio_dev *indio_dev) +{ + struct rcar_gyroadc *priv = iio_priv(indio_dev); + unsigned int i; + + for (i = 0; i < priv->num_channels; i++) { + if (!priv->vref[i]) + continue; + + regulator_disable(priv->vref[i]); + } +} + +static int rcar_gyroadc_init_supplies(struct iio_dev *indio_dev) +{ + struct rcar_gyroadc *priv = iio_priv(indio_dev); + struct device *dev = priv->dev; + unsigned int i; + int ret; + + for (i = 0; i < priv->num_channels; i++) { + if (!priv->vref[i]) + continue; + + ret = regulator_enable(priv->vref[i]); + if (ret) { + dev_err(dev, "Failed to enable regulator %i (ret=%i)\n", + i, ret); + goto err; + } + } + + return 0; + +err: + rcar_gyroadc_deinit_supplies(indio_dev); + return ret; +} + +static int rcar_gyroadc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rcar_gyroadc *priv; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + priv = iio_priv(indio_dev); + priv->dev = dev; + + priv->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->regs)) + return PTR_ERR(priv->regs); + + priv->clk = devm_clk_get(dev, "fck"); + if (IS_ERR(priv->clk)) + return dev_err_probe(dev, PTR_ERR(priv->clk), + "Failed to get IF clock\n"); + + ret = rcar_gyroadc_parse_subdevs(indio_dev); + if (ret) + return ret; + + ret = rcar_gyroadc_init_supplies(indio_dev); + if (ret) + return ret; + + priv->model = (uintptr_t)of_device_get_match_data(&pdev->dev); + + platform_set_drvdata(pdev, indio_dev); + + indio_dev->name = DRIVER_NAME; + indio_dev->info = &rcar_gyroadc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = clk_prepare_enable(priv->clk); + if (ret) { + dev_err(dev, "Could not prepare or enable the IF clock.\n"); + goto err_clk_if_enable; + } + + pm_runtime_set_autosuspend_delay(dev, RCAR_GYROADC_RUNTIME_PM_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + + ret = pm_runtime_resume_and_get(dev); + if (ret) + goto err_power_up; + + rcar_gyroadc_hw_init(priv); + rcar_gyroadc_hw_start(priv); + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(dev, "Couldn't register IIO device.\n"); + goto err_iio_device_register; + } + + pm_runtime_put_sync(dev); + + return 0; + +err_iio_device_register: + rcar_gyroadc_hw_stop(priv); + pm_runtime_put_sync(dev); +err_power_up: + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + clk_disable_unprepare(priv->clk); +err_clk_if_enable: + rcar_gyroadc_deinit_supplies(indio_dev); + + return ret; +} + +static void rcar_gyroadc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct rcar_gyroadc *priv = iio_priv(indio_dev); + struct device *dev = priv->dev; + + iio_device_unregister(indio_dev); + pm_runtime_get_sync(dev); + rcar_gyroadc_hw_stop(priv); + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + clk_disable_unprepare(priv->clk); + rcar_gyroadc_deinit_supplies(indio_dev); +} + +static int rcar_gyroadc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct rcar_gyroadc *priv = iio_priv(indio_dev); + + rcar_gyroadc_hw_stop(priv); + + return 0; +} + +static int rcar_gyroadc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct rcar_gyroadc *priv = iio_priv(indio_dev); + + rcar_gyroadc_hw_start(priv); + + return 0; +} + +static const struct dev_pm_ops rcar_gyroadc_pm_ops = { + RUNTIME_PM_OPS(rcar_gyroadc_suspend, rcar_gyroadc_resume, NULL) +}; + +static struct platform_driver rcar_gyroadc_driver = { + .probe = rcar_gyroadc_probe, + .remove = rcar_gyroadc_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = rcar_gyroadc_match, + .pm = pm_ptr(&rcar_gyroadc_pm_ops), + }, +}; + +module_platform_driver(rcar_gyroadc_driver); + +MODULE_AUTHOR("Marek Vasut "); +MODULE_DESCRIPTION("Renesas R-Car GyroADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/rn5t618-adc.c b/drivers/iio/adc/rn5t618-adc.c new file mode 100644 index 000000000..f78fc795b --- /dev/null +++ b/drivers/iio/adc/rn5t618-adc.c @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ADC driver for the RICOH RN5T618 power management chip family + * + * Copyright (C) 2019 Andreas Kemnade + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RN5T618_ADC_CONVERSION_TIMEOUT (msecs_to_jiffies(500)) +#define RN5T618_REFERENCE_VOLT 2500 + +/* mask for selecting channels for single conversion */ +#define RN5T618_ADCCNT3_CHANNEL_MASK 0x7 +/* average 4-time conversion mode */ +#define RN5T618_ADCCNT3_AVG BIT(3) +/* set for starting a single conversion, gets cleared by hw when done */ +#define RN5T618_ADCCNT3_GODONE BIT(4) +/* automatic conversion, period is in ADCCNT2, selected channels are + * in ADCCNT1 + */ +#define RN5T618_ADCCNT3_AUTO BIT(5) +#define RN5T618_ADCEND_IRQ BIT(0) + +struct rn5t618_adc_data { + struct device *dev; + struct rn5t618 *rn5t618; + struct completion conv_completion; + int irq; +}; + +enum rn5t618_channels { + LIMMON = 0, + VBAT, + VADP, + VUSB, + VSYS, + VTHM, + AIN1, + AIN0 +}; + +static const struct u16_fract rn5t618_ratios[8] = { + [LIMMON] = {50, 32}, /* measured across 20mOhm, amplified by 32 */ + [VBAT] = {2, 1}, + [VADP] = {3, 1}, + [VUSB] = {3, 1}, + [VSYS] = {3, 1}, + [VTHM] = {1, 1}, + [AIN1] = {1, 1}, + [AIN0] = {1, 1}, +}; + +static int rn5t618_read_adc_reg(struct rn5t618 *rn5t618, int reg, u16 *val) +{ + u8 data[2]; + int ret; + + ret = regmap_bulk_read(rn5t618->regmap, reg, data, sizeof(data)); + if (ret < 0) + return ret; + + *val = (data[0] << 4) | (data[1] & 0xF); + + return 0; +} + +static irqreturn_t rn5t618_adc_irq(int irq, void *data) +{ + struct rn5t618_adc_data *adc = data; + unsigned int r = 0; + int ret; + + /* clear low & high threshold irqs */ + regmap_write(adc->rn5t618->regmap, RN5T618_IR_ADC1, 0); + regmap_write(adc->rn5t618->regmap, RN5T618_IR_ADC2, 0); + + ret = regmap_read(adc->rn5t618->regmap, RN5T618_IR_ADC3, &r); + if (ret < 0) + dev_err(adc->dev, "failed to read IRQ status: %d\n", ret); + + regmap_write(adc->rn5t618->regmap, RN5T618_IR_ADC3, 0); + + if (r & RN5T618_ADCEND_IRQ) + complete(&adc->conv_completion); + + return IRQ_HANDLED; +} + +static int rn5t618_adc_read(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct rn5t618_adc_data *adc = iio_priv(iio_dev); + u16 raw; + int ret; + + if (mask == IIO_CHAN_INFO_SCALE) { + *val = RN5T618_REFERENCE_VOLT * + rn5t618_ratios[chan->channel].numerator; + *val2 = rn5t618_ratios[chan->channel].denominator * 4095; + + return IIO_VAL_FRACTIONAL; + } + + /* select channel */ + ret = regmap_update_bits(adc->rn5t618->regmap, RN5T618_ADCCNT3, + RN5T618_ADCCNT3_CHANNEL_MASK, + chan->channel); + if (ret < 0) + return ret; + + ret = regmap_write(adc->rn5t618->regmap, RN5T618_EN_ADCIR3, + RN5T618_ADCEND_IRQ); + if (ret < 0) + return ret; + + ret = regmap_update_bits(adc->rn5t618->regmap, RN5T618_ADCCNT3, + RN5T618_ADCCNT3_AVG, + mask == IIO_CHAN_INFO_AVERAGE_RAW ? + RN5T618_ADCCNT3_AVG : 0); + if (ret < 0) + return ret; + + init_completion(&adc->conv_completion); + /* single conversion */ + ret = regmap_set_bits(adc->rn5t618->regmap, RN5T618_ADCCNT3, + RN5T618_ADCCNT3_GODONE); + if (ret < 0) + return ret; + + ret = wait_for_completion_timeout(&adc->conv_completion, + RN5T618_ADC_CONVERSION_TIMEOUT); + if (ret == 0) { + dev_warn(adc->dev, "timeout waiting for adc result\n"); + return -ETIMEDOUT; + } + + ret = rn5t618_read_adc_reg(adc->rn5t618, + RN5T618_ILIMDATAH + 2 * chan->channel, + &raw); + if (ret < 0) + return ret; + + *val = raw; + + return IIO_VAL_INT; +} + +static const struct iio_info rn5t618_adc_iio_info = { + .read_raw = &rn5t618_adc_read, +}; + +#define RN5T618_ADC_CHANNEL(_channel, _type, _name) { \ + .type = _type, \ + .channel = _channel, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_AVERAGE_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = _name, \ + .indexed = 1. \ +} + +static const struct iio_chan_spec rn5t618_adc_iio_channels[] = { + RN5T618_ADC_CHANNEL(LIMMON, IIO_CURRENT, "LIMMON"), + RN5T618_ADC_CHANNEL(VBAT, IIO_VOLTAGE, "VBAT"), + RN5T618_ADC_CHANNEL(VADP, IIO_VOLTAGE, "VADP"), + RN5T618_ADC_CHANNEL(VUSB, IIO_VOLTAGE, "VUSB"), + RN5T618_ADC_CHANNEL(VSYS, IIO_VOLTAGE, "VSYS"), + RN5T618_ADC_CHANNEL(VTHM, IIO_VOLTAGE, "VTHM"), + RN5T618_ADC_CHANNEL(AIN1, IIO_VOLTAGE, "AIN1"), + RN5T618_ADC_CHANNEL(AIN0, IIO_VOLTAGE, "AIN0") +}; + +static const struct iio_map rn5t618_maps[] = { + IIO_MAP("VADP", "rn5t618-power", "vadp"), + IIO_MAP("VUSB", "rn5t618-power", "vusb"), + { } +}; + +static int rn5t618_adc_probe(struct platform_device *pdev) +{ + int ret; + struct iio_dev *iio_dev; + struct rn5t618_adc_data *adc; + struct rn5t618 *rn5t618 = dev_get_drvdata(pdev->dev.parent); + + iio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc)); + if (!iio_dev) + return -ENOMEM; + + adc = iio_priv(iio_dev); + adc->dev = &pdev->dev; + adc->rn5t618 = rn5t618; + + if (rn5t618->irq_data) + adc->irq = regmap_irq_get_virq(rn5t618->irq_data, + RN5T618_IRQ_ADC); + + if (adc->irq <= 0) { + dev_err(&pdev->dev, "get virq failed\n"); + return -EINVAL; + } + + init_completion(&adc->conv_completion); + + iio_dev->name = dev_name(&pdev->dev); + iio_dev->info = &rn5t618_adc_iio_info; + iio_dev->modes = INDIO_DIRECT_MODE; + iio_dev->channels = rn5t618_adc_iio_channels; + iio_dev->num_channels = ARRAY_SIZE(rn5t618_adc_iio_channels); + + /* stop any auto-conversion */ + ret = regmap_write(rn5t618->regmap, RN5T618_ADCCNT3, 0); + if (ret < 0) + return ret; + + platform_set_drvdata(pdev, iio_dev); + + ret = devm_request_threaded_irq(adc->dev, adc->irq, NULL, + rn5t618_adc_irq, + IRQF_ONESHOT, dev_name(adc->dev), + adc); + if (ret < 0) { + dev_err(adc->dev, "request irq %d failed: %d\n", adc->irq, ret); + return ret; + } + + ret = devm_iio_map_array_register(adc->dev, iio_dev, rn5t618_maps); + if (ret < 0) + return ret; + + return devm_iio_device_register(adc->dev, iio_dev); +} + +static struct platform_driver rn5t618_adc_driver = { + .driver = { + .name = "rn5t618-adc", + }, + .probe = rn5t618_adc_probe, +}; + +module_platform_driver(rn5t618_adc_driver); +MODULE_ALIAS("platform:rn5t618-adc"); +MODULE_DESCRIPTION("RICOH RN5T618 ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/rockchip_saradc.c b/drivers/iio/adc/rockchip_saradc.c new file mode 100644 index 000000000..0f0bf2906 --- /dev/null +++ b/drivers/iio/adc/rockchip_saradc.c @@ -0,0 +1,626 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Rockchip Successive Approximation Register (SAR) A/D Converter + * Copyright (C) 2014 Rockchip Electronics Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SARADC_DATA 0x00 + +#define SARADC_STAS 0x04 +#define SARADC_STAS_BUSY BIT(0) + +#define SARADC_CTRL 0x08 +#define SARADC_CTRL_IRQ_STATUS BIT(6) +#define SARADC_CTRL_IRQ_ENABLE BIT(5) +#define SARADC_CTRL_POWER_CTRL BIT(3) +#define SARADC_CTRL_CHN_MASK 0x7 + +#define SARADC_DLY_PU_SOC 0x0c +#define SARADC_DLY_PU_SOC_MASK 0x3f + +#define SARADC_TIMEOUT msecs_to_jiffies(100) +#define SARADC_MAX_CHANNELS 8 + +/* v2 registers */ +#define SARADC2_CONV_CON 0x000 +#define SARADC_T_PD_SOC 0x004 +#define SARADC_T_DAS_SOC 0x00c +#define SARADC2_END_INT_EN 0x104 +#define SARADC2_ST_CON 0x108 +#define SARADC2_STATUS 0x10c +#define SARADC2_END_INT_ST 0x110 +#define SARADC2_DATA_BASE 0x120 + +#define SARADC2_EN_END_INT BIT(0) +#define SARADC2_START BIT(4) +#define SARADC2_SINGLE_MODE BIT(5) + +#define SARADC2_CONV_CHANNELS GENMASK(3, 0) + +struct rockchip_saradc; + +struct rockchip_saradc_data { + const struct iio_chan_spec *channels; + int num_channels; + unsigned long clk_rate; + void (*start)(struct rockchip_saradc *info, int chn); + int (*read)(struct rockchip_saradc *info); + void (*power_down)(struct rockchip_saradc *info); +}; + +struct rockchip_saradc { + void __iomem *regs; + struct clk *pclk; + struct clk *clk; + struct completion completion; + struct regulator *vref; + /* lock to protect against multiple access to the device */ + struct mutex lock; + int uv_vref; + struct reset_control *reset; + const struct rockchip_saradc_data *data; + u16 last_val; + const struct iio_chan_spec *last_chan; + struct notifier_block nb; +}; + +static void rockchip_saradc_reset_controller(struct reset_control *reset); + +static void rockchip_saradc_start_v1(struct rockchip_saradc *info, int chn) +{ + /* 8 clock periods as delay between power up and start cmd */ + writel_relaxed(8, info->regs + SARADC_DLY_PU_SOC); + /* Select the channel to be used and trigger conversion */ + writel(SARADC_CTRL_POWER_CTRL | (chn & SARADC_CTRL_CHN_MASK) | + SARADC_CTRL_IRQ_ENABLE, info->regs + SARADC_CTRL); +} + +static void rockchip_saradc_start_v2(struct rockchip_saradc *info, int chn) +{ + int val; + + if (info->reset) + rockchip_saradc_reset_controller(info->reset); + + writel_relaxed(0xc, info->regs + SARADC_T_DAS_SOC); + writel_relaxed(0x20, info->regs + SARADC_T_PD_SOC); + val = FIELD_PREP(SARADC2_EN_END_INT, 1); + val |= SARADC2_EN_END_INT << 16; + writel_relaxed(val, info->regs + SARADC2_END_INT_EN); + val = FIELD_PREP(SARADC2_START, 1) | + FIELD_PREP(SARADC2_SINGLE_MODE, 1) | + FIELD_PREP(SARADC2_CONV_CHANNELS, chn); + val |= (SARADC2_START | SARADC2_SINGLE_MODE | SARADC2_CONV_CHANNELS) << 16; + writel(val, info->regs + SARADC2_CONV_CON); +} + +static void rockchip_saradc_start(struct rockchip_saradc *info, int chn) +{ + info->data->start(info, chn); +} + +static int rockchip_saradc_read_v1(struct rockchip_saradc *info) +{ + return readl_relaxed(info->regs + SARADC_DATA); +} + +static int rockchip_saradc_read_v2(struct rockchip_saradc *info) +{ + int offset; + + /* Clear irq */ + writel_relaxed(0x1, info->regs + SARADC2_END_INT_ST); + + offset = SARADC2_DATA_BASE + info->last_chan->channel * 0x4; + + return readl_relaxed(info->regs + offset); +} + +static int rockchip_saradc_read(struct rockchip_saradc *info) +{ + return info->data->read(info); +} + +static void rockchip_saradc_power_down_v1(struct rockchip_saradc *info) +{ + writel_relaxed(0, info->regs + SARADC_CTRL); +} + +static void rockchip_saradc_power_down(struct rockchip_saradc *info) +{ + if (info->data->power_down) + info->data->power_down(info); +} + +static int rockchip_saradc_conversion(struct rockchip_saradc *info, + struct iio_chan_spec const *chan) +{ + reinit_completion(&info->completion); + + info->last_chan = chan; + rockchip_saradc_start(info, chan->channel); + + if (!wait_for_completion_timeout(&info->completion, SARADC_TIMEOUT)) + return -ETIMEDOUT; + + return 0; +} + +static int rockchip_saradc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct rockchip_saradc *info = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&info->lock); + + ret = rockchip_saradc_conversion(info, chan); + if (ret) { + rockchip_saradc_power_down(info); + mutex_unlock(&info->lock); + return ret; + } + + *val = info->last_val; + mutex_unlock(&info->lock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = info->uv_vref / 1000; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static irqreturn_t rockchip_saradc_isr(int irq, void *dev_id) +{ + struct rockchip_saradc *info = dev_id; + + /* Read value */ + info->last_val = rockchip_saradc_read(info); + info->last_val &= GENMASK(info->last_chan->scan_type.realbits - 1, 0); + + rockchip_saradc_power_down(info); + + complete(&info->completion); + + return IRQ_HANDLED; +} + +static const struct iio_info rockchip_saradc_iio_info = { + .read_raw = rockchip_saradc_read_raw, +}; + +#define SARADC_CHANNEL(_index, _id, _res) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = _id, \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = _res, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ +} + +static const struct iio_chan_spec rockchip_saradc_iio_channels[] = { + SARADC_CHANNEL(0, "adc0", 10), + SARADC_CHANNEL(1, "adc1", 10), + SARADC_CHANNEL(2, "adc2", 10), +}; + +static const struct rockchip_saradc_data saradc_data = { + .channels = rockchip_saradc_iio_channels, + .num_channels = ARRAY_SIZE(rockchip_saradc_iio_channels), + .clk_rate = 1000000, + .start = rockchip_saradc_start_v1, + .read = rockchip_saradc_read_v1, + .power_down = rockchip_saradc_power_down_v1, +}; + +static const struct iio_chan_spec rockchip_rk3066_tsadc_iio_channels[] = { + SARADC_CHANNEL(0, "adc0", 12), + SARADC_CHANNEL(1, "adc1", 12), +}; + +static const struct rockchip_saradc_data rk3066_tsadc_data = { + .channels = rockchip_rk3066_tsadc_iio_channels, + .num_channels = ARRAY_SIZE(rockchip_rk3066_tsadc_iio_channels), + .clk_rate = 50000, + .start = rockchip_saradc_start_v1, + .read = rockchip_saradc_read_v1, + .power_down = rockchip_saradc_power_down_v1, +}; + +static const struct iio_chan_spec rockchip_rk3399_saradc_iio_channels[] = { + SARADC_CHANNEL(0, "adc0", 10), + SARADC_CHANNEL(1, "adc1", 10), + SARADC_CHANNEL(2, "adc2", 10), + SARADC_CHANNEL(3, "adc3", 10), + SARADC_CHANNEL(4, "adc4", 10), + SARADC_CHANNEL(5, "adc5", 10), +}; + +static const struct rockchip_saradc_data rk3399_saradc_data = { + .channels = rockchip_rk3399_saradc_iio_channels, + .num_channels = ARRAY_SIZE(rockchip_rk3399_saradc_iio_channels), + .clk_rate = 1000000, + .start = rockchip_saradc_start_v1, + .read = rockchip_saradc_read_v1, + .power_down = rockchip_saradc_power_down_v1, +}; + +static const struct iio_chan_spec rockchip_rk3528_saradc_iio_channels[] = { + SARADC_CHANNEL(0, "adc0", 10), + SARADC_CHANNEL(1, "adc1", 10), + SARADC_CHANNEL(2, "adc2", 10), + SARADC_CHANNEL(3, "adc3", 10), +}; + +static const struct rockchip_saradc_data rk3528_saradc_data = { + .channels = rockchip_rk3528_saradc_iio_channels, + .num_channels = ARRAY_SIZE(rockchip_rk3528_saradc_iio_channels), + .clk_rate = 1000000, + .start = rockchip_saradc_start_v2, + .read = rockchip_saradc_read_v2, +}; + +static const struct iio_chan_spec rockchip_rk3562_saradc_iio_channels[] = { + SARADC_CHANNEL(0, "adc0", 10), + SARADC_CHANNEL(1, "adc1", 10), + SARADC_CHANNEL(2, "adc2", 10), + SARADC_CHANNEL(3, "adc3", 10), + SARADC_CHANNEL(4, "adc4", 10), + SARADC_CHANNEL(5, "adc5", 10), + SARADC_CHANNEL(6, "adc6", 10), + SARADC_CHANNEL(7, "adc7", 10), +}; + +static const struct rockchip_saradc_data rk3562_saradc_data = { + .channels = rockchip_rk3562_saradc_iio_channels, + .num_channels = ARRAY_SIZE(rockchip_rk3562_saradc_iio_channels), + .clk_rate = 1000000, + .start = rockchip_saradc_start_v2, + .read = rockchip_saradc_read_v2, +}; + +static const struct iio_chan_spec rockchip_rk3568_saradc_iio_channels[] = { + SARADC_CHANNEL(0, "adc0", 10), + SARADC_CHANNEL(1, "adc1", 10), + SARADC_CHANNEL(2, "adc2", 10), + SARADC_CHANNEL(3, "adc3", 10), + SARADC_CHANNEL(4, "adc4", 10), + SARADC_CHANNEL(5, "adc5", 10), + SARADC_CHANNEL(6, "adc6", 10), + SARADC_CHANNEL(7, "adc7", 10), +}; + +static const struct rockchip_saradc_data rk3568_saradc_data = { + .channels = rockchip_rk3568_saradc_iio_channels, + .num_channels = ARRAY_SIZE(rockchip_rk3568_saradc_iio_channels), + .clk_rate = 1000000, + .start = rockchip_saradc_start_v1, + .read = rockchip_saradc_read_v1, + .power_down = rockchip_saradc_power_down_v1, +}; + +static const struct iio_chan_spec rockchip_rk3588_saradc_iio_channels[] = { + SARADC_CHANNEL(0, "adc0", 12), + SARADC_CHANNEL(1, "adc1", 12), + SARADC_CHANNEL(2, "adc2", 12), + SARADC_CHANNEL(3, "adc3", 12), + SARADC_CHANNEL(4, "adc4", 12), + SARADC_CHANNEL(5, "adc5", 12), + SARADC_CHANNEL(6, "adc6", 12), + SARADC_CHANNEL(7, "adc7", 12), +}; + +static const struct rockchip_saradc_data rk3588_saradc_data = { + .channels = rockchip_rk3588_saradc_iio_channels, + .num_channels = ARRAY_SIZE(rockchip_rk3588_saradc_iio_channels), + .clk_rate = 1000000, + .start = rockchip_saradc_start_v2, + .read = rockchip_saradc_read_v2, +}; + +static const struct of_device_id rockchip_saradc_match[] = { + { + .compatible = "rockchip,saradc", + .data = &saradc_data, + }, { + .compatible = "rockchip,rk3066-tsadc", + .data = &rk3066_tsadc_data, + }, { + .compatible = "rockchip,rk3399-saradc", + .data = &rk3399_saradc_data, + }, { + .compatible = "rockchip,rk3528-saradc", + .data = &rk3528_saradc_data, + }, { + .compatible = "rockchip,rk3562-saradc", + .data = &rk3562_saradc_data, + }, { + .compatible = "rockchip,rk3568-saradc", + .data = &rk3568_saradc_data, + }, { + .compatible = "rockchip,rk3588-saradc", + .data = &rk3588_saradc_data, + }, + { } +}; +MODULE_DEVICE_TABLE(of, rockchip_saradc_match); + +/* + * Reset SARADC Controller. + */ +static void rockchip_saradc_reset_controller(struct reset_control *reset) +{ + reset_control_assert(reset); + usleep_range(10, 20); + reset_control_deassert(reset); +} + +static void rockchip_saradc_regulator_disable(void *data) +{ + struct rockchip_saradc *info = data; + + regulator_disable(info->vref); +} + +static irqreturn_t rockchip_saradc_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *i_dev = pf->indio_dev; + struct rockchip_saradc *info = iio_priv(i_dev); + /* + * @values: each channel takes an u16 value + * @timestamp: will be 8-byte aligned automatically + */ + struct { + u16 values[SARADC_MAX_CHANNELS]; + aligned_s64 timestamp; + } data = { }; + int ret; + int i, j = 0; + + mutex_lock(&info->lock); + + iio_for_each_active_channel(i_dev, i) { + const struct iio_chan_spec *chan = &i_dev->channels[i]; + + ret = rockchip_saradc_conversion(info, chan); + if (ret) { + rockchip_saradc_power_down(info); + goto out; + } + + data.values[j] = info->last_val; + j++; + } + + iio_push_to_buffers_with_ts(i_dev, &data, sizeof(data), + iio_get_time_ns(i_dev)); +out: + mutex_unlock(&info->lock); + + iio_trigger_notify_done(i_dev->trig); + + return IRQ_HANDLED; +} + +static int rockchip_saradc_volt_notify(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct rockchip_saradc *info = + container_of(nb, struct rockchip_saradc, nb); + + if (event & REGULATOR_EVENT_VOLTAGE_CHANGE) + info->uv_vref = (unsigned long)data; + + return NOTIFY_OK; +} + +static void rockchip_saradc_regulator_unreg_notifier(void *data) +{ + struct rockchip_saradc *info = data; + + regulator_unregister_notifier(info->vref, &info->nb); +} + +static int rockchip_saradc_probe(struct platform_device *pdev) +{ + const struct rockchip_saradc_data *match_data; + struct rockchip_saradc *info = NULL; + struct device *dev = &pdev->dev; + struct device_node *np = pdev->dev.of_node; + struct iio_dev *indio_dev = NULL; + int ret; + int irq; + + if (!np) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*info)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + + match_data = of_device_get_match_data(dev); + if (!match_data) + return dev_err_probe(dev, -ENODEV, "failed to match device\n"); + + info->data = match_data; + + /* Sanity check for possible later IP variants with more channels */ + if (info->data->num_channels > SARADC_MAX_CHANNELS) + return dev_err_probe(dev, -EINVAL, "max channels exceeded"); + + info->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(info->regs)) + return PTR_ERR(info->regs); + + /* + * The reset should be an optional property, as it should work + * with old devicetrees as well + */ + info->reset = devm_reset_control_get_optional_exclusive(dev, "saradc-apb"); + if (IS_ERR(info->reset)) + return dev_err_probe(dev, PTR_ERR(info->reset), + "failed to get saradc-apb\n"); + + init_completion(&info->completion); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, rockchip_saradc_isr, + 0, dev_name(&pdev->dev), info); + if (ret < 0) + return dev_err_probe(dev, ret, "failed requesting irq %d\n", irq); + + info->vref = devm_regulator_get(dev, "vref"); + if (IS_ERR(info->vref)) + return dev_err_probe(dev, PTR_ERR(info->vref), + "failed to get regulator\n"); + + if (info->reset) + rockchip_saradc_reset_controller(info->reset); + + ret = regulator_enable(info->vref); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to enable vref regulator\n"); + + ret = devm_add_action_or_reset(dev, rockchip_saradc_regulator_disable, info); + if (ret) + return ret; + + ret = regulator_get_voltage(info->vref); + if (ret < 0) + return ret; + + info->uv_vref = ret; + + info->pclk = devm_clk_get_enabled(dev, "apb_pclk"); + if (IS_ERR(info->pclk)) + return dev_err_probe(dev, PTR_ERR(info->pclk), "failed to get pclk\n"); + + info->clk = devm_clk_get_enabled(dev, "saradc"); + if (IS_ERR(info->clk)) + return dev_err_probe(dev, PTR_ERR(info->clk), + "failed to get adc clock\n"); + /* + * Use a default value for the converter clock. + * This may become user-configurable in the future. + */ + ret = clk_set_rate(info->clk, info->data->clk_rate); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to set adc clk rate\n"); + + platform_set_drvdata(pdev, indio_dev); + + indio_dev->name = dev_name(dev); + indio_dev->info = &rockchip_saradc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + indio_dev->channels = info->data->channels; + indio_dev->num_channels = info->data->num_channels; + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + rockchip_saradc_trigger_handler, + NULL); + if (ret) + return ret; + + info->nb.notifier_call = rockchip_saradc_volt_notify; + ret = regulator_register_notifier(info->vref, &info->nb); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, + rockchip_saradc_regulator_unreg_notifier, + info); + if (ret) + return ret; + + mutex_init(&info->lock); + + return devm_iio_device_register(dev, indio_dev); +} + +static int rockchip_saradc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct rockchip_saradc *info = iio_priv(indio_dev); + + clk_disable_unprepare(info->clk); + clk_disable_unprepare(info->pclk); + regulator_disable(info->vref); + + return 0; +} + +static int rockchip_saradc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct rockchip_saradc *info = iio_priv(indio_dev); + int ret; + + ret = regulator_enable(info->vref); + if (ret) + return ret; + + ret = clk_prepare_enable(info->pclk); + if (ret) + return ret; + + ret = clk_prepare_enable(info->clk); + if (ret) + clk_disable_unprepare(info->pclk); + + return ret; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(rockchip_saradc_pm_ops, + rockchip_saradc_suspend, + rockchip_saradc_resume); + +static struct platform_driver rockchip_saradc_driver = { + .probe = rockchip_saradc_probe, + .driver = { + .name = "rockchip-saradc", + .of_match_table = rockchip_saradc_match, + .pm = pm_sleep_ptr(&rockchip_saradc_pm_ops), + }, +}; + +module_platform_driver(rockchip_saradc_driver); + +MODULE_AUTHOR("Heiko Stuebner "); +MODULE_DESCRIPTION("Rockchip SARADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/rohm-bd79112.c b/drivers/iio/adc/rohm-bd79112.c new file mode 100644 index 000000000..2506fec7b --- /dev/null +++ b/drivers/iio/adc/rohm-bd79112.c @@ -0,0 +1,550 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ROHM ADC driver for BD79112 signal monitoring hub. + * Copyright (C) 2025, ROHM Semiconductor. + * + * SPI communication derived from ad7923.c and ti-ads7950.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define BD79112_MAX_NUM_CHANNELS 32 + +struct bd79112_data { + struct spi_device *spi; + struct regmap *map; + struct device *dev; + struct gpio_chip gc; + unsigned long gpio_valid_mask; + unsigned int vref_mv; + struct spi_transfer read_xfer[2]; + struct spi_transfer write_xfer; + struct spi_message read_msg; + struct spi_message write_msg; + /* 16-bit TX, valid data in high byte */ + u8 read_tx[2] __aligned(IIO_DMA_MINALIGN); + /* 8-bit address followed by 8-bit data */ + u8 reg_write_tx[2]; + /* 12-bit of ADC data or 8 bit of reg data */ + __be16 read_rx; +}; + +/* + * The ADC data is read issuing SPI-command matching the channel number. + * We treat this as a register address. + */ +#define BD79112_REG_AGIO0A 0x00 +#define BD79112_REG_AGIO15B 0x1f + +/* + * ADC STATUS_FLAG appended to ADC data will be set, if the ADC result is being + * read for a channel, which input pin is muxed to be a GPIO. + */ +#define BD79112_ADC_STATUS_FLAG BIT(14) + +/* + * The BD79112 requires "R/W bit" to be set for SPI register (not ADC data) + * reads and an "IOSET bit" to be set for read/write operations (which aren't + * reading the ADC data). + */ +#define BD79112_BIT_RW BIT(4) +#define BD79112_BIT_IO BIT(5) + +#define BD79112_REG_GPI_VALUE_B8_15 (BD79112_BIT_IO | 0x0) +#define BD79112_REG_GPI_VALUE_B0_B7 (BD79112_BIT_IO | 0x1) +#define BD79112_REG_GPI_VALUE_A8_15 (BD79112_BIT_IO | 0x2) +#define BD79112_REG_GPI_VALUE_A0_A7 (BD79112_BIT_IO | 0x3) + +#define BD79112_REG_GPI_EN_B7_B15 (BD79112_BIT_IO | 0x4) +#define BD79112_REG_GPI_EN_B0_B7 (BD79112_BIT_IO | 0x5) +#define BD79112_REG_GPI_EN_A8_A15 (BD79112_BIT_IO | 0x6) +#define BD79112_REG_GPI_EN_A0_A7 (BD79112_BIT_IO | 0x7) + +#define BD79112_REG_GPO_EN_B7_B15 (BD79112_BIT_IO | 0x8) +#define BD79112_REG_GPO_EN_B0_B7 (BD79112_BIT_IO | 0x9) +#define BD79112_REG_GPO_EN_A8_A15 (BD79112_BIT_IO | 0xa) +#define BD79112_REG_GPO_EN_A0_A7 (BD79112_BIT_IO | 0xb) + +#define BD79112_NUM_GPIO_EN_REGS 8 +#define BD79112_FIRST_GPIO_EN_REG BD79112_REG_GPI_EN_B7_B15 + +#define BD79112_REG_GPO_VALUE_B8_15 (BD79112_BIT_IO | 0xc) +#define BD79112_REG_GPO_VALUE_B0_B7 (BD79112_BIT_IO | 0xd) +#define BD79112_REG_GPO_VALUE_A8_15 (BD79112_BIT_IO | 0xe) +#define BD79112_REG_GPO_VALUE_A0_A7 (BD79112_BIT_IO | 0xf) + +#define BD79112_REG_MAX BD79112_REG_GPO_VALUE_A0_A7 + +/* + * Read transaction consists of two 16-bit sequences separated by CSB. + * For register read, 'IOSET' bit must be set. For ADC read, IOSET is cleared + * and ADDR equals the channel number (0 ... 31). + * + * First 16-bit sequence, MOSI as below, MISO data ignored: + * - SCK: | 1 | 2 | 3 | 4 | 5 .. 8 | 9 .. 16 | + * - MOSI:| 0 | 0 | IOSET | RW (1) | ADDR | 8'b0 | + * + * CSB released and re-acquired between these sequences + * + * Second 16-bit sequence, MISO as below, MOSI data ignored: + * For Register read data is 8 bits: + * - SCK: | 1 .. 8 | 9 .. 16 | + * - MISO:| 8'b0 | 8-bit data | + * + * For ADC read data is 12 bits: + * - SCK: | 1 | 2 | 3 4 | 4 .. 16 | + * - MISO:| 0 | STATUS_FLAG | 2'b0 | 12-bit data | + * The 'STATUS_FLAG' is set if the read input pin was configured as a GPIO. + */ +static int bd79112_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + struct bd79112_data *data = context; + int ret; + + if (reg & BD79112_BIT_IO) + reg |= BD79112_BIT_RW; + + data->read_tx[0] = reg; + + ret = spi_sync(data->spi, &data->read_msg); + if (!ret) + *val = be16_to_cpu(data->read_rx); + + return ret; +} + +/* + * Write, single 16-bit sequence (broken down below): + * + * First 8-bit, MOSI as below, MISO data ignored: + * - SCK: | 1 | 2 | 3 | 4 | 5 .. 8 | + * - MOSI:| 0 | 0 |IOSET| RW(0) | ADDR | + * + * Last 8 SCK cycles (b8 ... b15), MISO contains register data, MOSI ignored. + * - SCK: | 9 .. 16 | + * - MISO:| data | + */ +static int bd79112_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct bd79112_data *data = context; + + data->reg_write_tx[0] = reg; + data->reg_write_tx[1] = val; + + return spi_sync(data->spi, &data->write_msg); +} + +static int _get_gpio_reg(unsigned int offset, unsigned int base) +{ + int regoffset = offset / 8; + + if (offset > 31) + return -EINVAL; + + return base - regoffset; +} + +#define GET_GPIO_BIT(offset) BIT((offset) % 8) +#define GET_GPO_EN_REG(offset) _get_gpio_reg((offset), BD79112_REG_GPO_EN_A0_A7) +#define GET_GPI_EN_REG(offset) _get_gpio_reg((offset), BD79112_REG_GPI_EN_A0_A7) +#define GET_GPO_VAL_REG(offset) _get_gpio_reg((offset), BD79112_REG_GPO_VALUE_A0_A7) +#define GET_GPI_VAL_REG(offset) _get_gpio_reg((offset), BD79112_REG_GPI_VALUE_A0_A7) + +static const struct regmap_range bd71815_volatile_ro_ranges[] = { + /* Read ADC data */ + regmap_reg_range(BD79112_REG_AGIO0A, BD79112_REG_AGIO15B), + /* GPI state */ + regmap_reg_range(BD79112_REG_GPI_VALUE_B8_15, BD79112_REG_GPI_VALUE_A0_A7), +}; + +static const struct regmap_access_table bd79112_volatile_regs = { + .yes_ranges = &bd71815_volatile_ro_ranges[0], + .n_yes_ranges = ARRAY_SIZE(bd71815_volatile_ro_ranges), +}; + +static const struct regmap_access_table bd79112_ro_regs = { + .no_ranges = &bd71815_volatile_ro_ranges[0], + .n_no_ranges = ARRAY_SIZE(bd71815_volatile_ro_ranges), +}; + +static const struct regmap_config bd79112_regmap = { + .reg_read = bd79112_reg_read, + .reg_write = bd79112_reg_write, + .volatile_table = &bd79112_volatile_regs, + .wr_table = &bd79112_ro_regs, + .cache_type = REGCACHE_MAPLE, + .max_register = BD79112_REG_MAX, +}; + +static int bd79112_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long m) +{ + struct bd79112_data *data = iio_priv(indio_dev); + int ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(data->map, chan->channel, val); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = data->vref_mv; + *val2 = 12; + + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static const struct iio_info bd79112_info = { + .read_raw = bd79112_read_raw, +}; + +static const struct iio_chan_spec bd79112_chan_template = { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .indexed = 1, +}; + +static int bd79112_gpio_init_valid_mask(struct gpio_chip *gc, + unsigned long *valid_mask, + unsigned int ngpios) +{ + struct bd79112_data *data = gpiochip_get_data(gc); + + *valid_mask = data->gpio_valid_mask; + + return 0; +} + +static int bd79112_gpio_dir_get(struct gpio_chip *gc, unsigned int offset) +{ + struct bd79112_data *data = gpiochip_get_data(gc); + unsigned int reg, bit, val; + int ret; + + bit = GET_GPIO_BIT(offset); + reg = GET_GPO_EN_REG(offset); + + ret = regmap_read(data->map, reg, &val); + if (ret) + return ret; + + if (bit & val) + return GPIO_LINE_DIRECTION_OUT; + + reg = GET_GPI_EN_REG(offset); + ret = regmap_read(data->map, reg, &val); + if (ret) + return ret; + + if (bit & val) + return GPIO_LINE_DIRECTION_IN; + + /* + * Ouch. Seems the pin is ADC input - shouldn't happen as changing mux + * at runtime is not supported and non GPIO pins should be invalidated + * by the valid_mask at probe. Maybe someone wrote a register bypassing + * the driver? + */ + dev_err(data->dev, "Pin not a GPIO\n"); + + return -EINVAL; +} + +static int bd79112_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct bd79112_data *data = gpiochip_get_data(gc); + unsigned int reg, bit, val; + int ret; + + bit = GET_GPIO_BIT(offset); + reg = GET_GPI_VAL_REG(offset); + + ret = regmap_read(data->map, reg, &val); + if (ret) + return ret; + + return !!(val & bit); +} + +static int bd79112_gpio_set(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct bd79112_data *data = gpiochip_get_data(gc); + unsigned int reg, bit; + + bit = GET_GPIO_BIT(offset); + reg = GET_GPO_VAL_REG(offset); + + return regmap_assign_bits(data->map, reg, bit, value); +} + +static int bd79112_gpio_set_multiple(struct gpio_chip *gc, unsigned long *mask, + unsigned long *bits) +{ + struct bd79112_data *data = gpiochip_get_data(gc); + unsigned long i, bank_mask; + + for_each_set_clump8(i, bank_mask, mask, gc->ngpio) { + unsigned long bank_bits; + unsigned int reg; + int ret; + + bank_bits = bitmap_get_value8(bits, i); + reg = BD79112_REG_GPO_VALUE_A0_A7 - i / 8; + ret = regmap_update_bits(data->map, reg, bank_mask, bank_bits); + if (ret) + return ret; + } + + return 0; +} + +static int bd79112_gpio_dir_set(struct bd79112_data *data, unsigned int offset, + int dir) +{ + unsigned int gpi_reg, gpo_reg, bit; + int ret; + + bit = GET_GPIO_BIT(offset); + gpi_reg = GET_GPI_EN_REG(offset); + gpo_reg = GET_GPO_EN_REG(offset); + + if (dir == GPIO_LINE_DIRECTION_OUT) { + ret = regmap_clear_bits(data->map, gpi_reg, bit); + if (ret) + return ret; + + return regmap_set_bits(data->map, gpo_reg, bit); + } + + ret = regmap_set_bits(data->map, gpi_reg, bit); + if (ret) + return ret; + + return regmap_clear_bits(data->map, gpo_reg, bit); +} + +static int bd79112_gpio_input(struct gpio_chip *gc, unsigned int offset) +{ + struct bd79112_data *data = gpiochip_get_data(gc); + + return bd79112_gpio_dir_set(data, offset, GPIO_LINE_DIRECTION_IN); +} + +static int bd79112_gpio_output(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct bd79112_data *data = gpiochip_get_data(gc); + int ret; + + ret = bd79112_gpio_set(gc, offset, value); + if (ret) + return ret; + + return bd79112_gpio_dir_set(data, offset, GPIO_LINE_DIRECTION_OUT); +} + +static const struct gpio_chip bd79112_gpio_chip = { + .label = "bd79112-gpio", + .get_direction = bd79112_gpio_dir_get, + .direction_input = bd79112_gpio_input, + .direction_output = bd79112_gpio_output, + .get = bd79112_gpio_get, + .set = bd79112_gpio_set, + .set_multiple = bd79112_gpio_set_multiple, + .init_valid_mask = bd79112_gpio_init_valid_mask, + .can_sleep = true, + .ngpio = 32, + .base = -1, +}; + +static unsigned int bd79112_get_gpio_pins(const struct iio_chan_spec *cs, int num_channels) +{ + unsigned int i, gpio_channels; + + /* + * Let's initialize the mux config to say that all 32 channels are + * GPIOs. Then we can just loop through the iio_chan_spec and clear the + * bits for found ADC channels. + */ + gpio_channels = GENMASK(31, 0); + for (i = 0; i < num_channels; i++) + gpio_channels &= ~BIT(cs[i].channel); + + return gpio_channels; +} + +/* ADC channels as named in the data-sheet */ +static const char * const bd79112_chan_names[] = { + "AGIO0A", "AGIO1A", "AGIO2A", "AGIO3A", /* 0 - 3 */ + "AGIO4A", "AGIO5A", "AGIO6A", "AGIO7A", /* 4 - 7 */ + "AGIO8A", "AGIO9A", "AGIO10A", "AGIO11A", /* 8 - 11 */ + "AGIO12A", "AGIO13A", "AGIO14A", "AGIO15A", /* 12 - 15 */ + "AGIO0B", "AGIO1B", "AGIO2B", "AGIO3B", /* 16 - 19 */ + "AGIO4B", "AGIO5B", "AGIO6B", "AGIO7B", /* 20 - 23 */ + "AGIO8B", "AGIO9B", "AGIO10B", "AGIO11B", /* 24 - 27 */ + "AGIO12B", "AGIO13B", "AGIO14B", "AGIO15B", /* 28 - 31 */ +}; + +static int bd79112_probe(struct spi_device *spi) +{ + struct bd79112_data *data; + struct iio_dev *iio_dev; + struct iio_chan_spec *cs; + struct device *dev = &spi->dev; + unsigned long gpio_pins, pin; + unsigned int i; + int ret; + + iio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!iio_dev) + return -ENOMEM; + + data = iio_priv(iio_dev); + data->spi = spi; + data->dev = dev; + data->map = devm_regmap_init(dev, NULL, data, &bd79112_regmap); + if (IS_ERR(data->map)) + return dev_err_probe(dev, PTR_ERR(data->map), + "Failed to initialize Regmap\n"); + + ret = devm_regulator_get_enable_read_voltage(dev, "vdd"); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get the Vdd\n"); + + data->vref_mv = ret / 1000; + + ret = devm_regulator_get_enable(dev, "iovdd"); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to enable I/O voltage\n"); + + data->read_xfer[0].tx_buf = &data->read_tx[0]; + data->read_xfer[0].len = sizeof(data->read_tx); + data->read_xfer[0].cs_change = 1; + data->read_xfer[1].rx_buf = &data->read_rx; + data->read_xfer[1].len = sizeof(data->read_rx); + spi_message_init_with_transfers(&data->read_msg, data->read_xfer, 2); + ret = devm_spi_optimize_message(dev, spi, &data->read_msg); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to optimize SPI read message\n"); + + data->write_xfer.tx_buf = &data->reg_write_tx[0]; + data->write_xfer.len = sizeof(data->reg_write_tx); + spi_message_init_with_transfers(&data->write_msg, &data->write_xfer, 1); + ret = devm_spi_optimize_message(dev, spi, &data->write_msg); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to optimize SPI write message\n"); + + ret = devm_iio_adc_device_alloc_chaninfo_se(dev, &bd79112_chan_template, + BD79112_MAX_NUM_CHANNELS - 1, + &cs); + + /* Register all pins as GPIOs if there are no ADC channels */ + if (ret == -ENOENT) + goto register_gpios; + + if (ret < 0) + return ret; + + iio_dev->num_channels = ret; + iio_dev->channels = cs; + + for (i = 0; i < iio_dev->num_channels; i++) + cs[i].datasheet_name = bd79112_chan_names[cs[i].channel]; + + iio_dev->info = &bd79112_info; + iio_dev->name = "bd79112"; + iio_dev->modes = INDIO_DIRECT_MODE; + + /* + * Ensure all channels are ADCs. This allows us to register the IIO + * device early (before checking which pins are to be used for GPIO) + * without having to worry about some pins being initially used for + * GPIO. + */ + for (i = 0; i < BD79112_NUM_GPIO_EN_REGS; i++) { + ret = regmap_write(data->map, BD79112_FIRST_GPIO_EN_REG + i, 0); + if (ret) + return dev_err_probe(dev, ret, + "Failed to initialize channels\n"); + } + + ret = devm_iio_device_register(data->dev, iio_dev); + if (ret) + return dev_err_probe(data->dev, ret, "Failed to register ADC\n"); + +register_gpios: + gpio_pins = bd79112_get_gpio_pins(iio_dev->channels, + iio_dev->num_channels); + + /* If all channels are reserved for ADC, then we're done. */ + if (!gpio_pins) + return 0; + + /* Default all the GPIO pins to GPI */ + for_each_set_bit(pin, &gpio_pins, BD79112_MAX_NUM_CHANNELS) { + ret = bd79112_gpio_dir_set(data, pin, GPIO_LINE_DIRECTION_IN); + if (ret) + return dev_err_probe(dev, ret, + "Failed to mark pin as GPI\n"); + } + + data->gpio_valid_mask = gpio_pins; + data->gc = bd79112_gpio_chip; + data->gc.parent = dev; + + return devm_gpiochip_add_data(dev, &data->gc, data); +} + +static const struct of_device_id bd79112_of_match[] = { + { .compatible = "rohm,bd79112" }, + { } +}; +MODULE_DEVICE_TABLE(of, bd79112_of_match); + +static const struct spi_device_id bd79112_id[] = { + { .name = "bd79112" }, + { } +}; +MODULE_DEVICE_TABLE(spi, bd79112_id); + +static struct spi_driver bd79112_driver = { + .driver = { + .name = "bd79112", + .of_match_table = bd79112_of_match, + }, + .probe = bd79112_probe, + .id_table = bd79112_id, +}; +module_spi_driver(bd79112_driver); + +MODULE_AUTHOR("Matti Vaittinen "); +MODULE_DESCRIPTION("Driver for ROHM BD79112 ADC/GPIO"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_DRIVER"); diff --git a/drivers/iio/adc/rohm-bd79124.c b/drivers/iio/adc/rohm-bd79124.c new file mode 100644 index 000000000..ed5427288 --- /dev/null +++ b/drivers/iio/adc/rohm-bd79124.c @@ -0,0 +1,1124 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ROHM ADC driver for BD79124 ADC/GPO device + * https://fscdn.rohm.com/en/products/databook/datasheet/ic/data_converter/dac/bd79124muf-c-e.pdf + * + * Copyright (c) 2025, ROHM Semiconductor. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#define BD79124_I2C_MULTI_READ 0x30 +#define BD79124_I2C_MULTI_WRITE 0x28 +#define BD79124_REG_MAX 0xaf + +#define BD79124_REG_SYSTEM_STATUS 0x00 +#define BD79124_REG_GEN_CFG 0x01 +#define BD79124_REG_OPMODE_CFG 0x04 +#define BD79124_REG_PINCFG 0x05 +#define BD79124_REG_GPO_VAL 0x0B +#define BD79124_REG_SEQ_CFG 0x10 +#define BD79124_REG_MANUAL_CHANNELS 0x11 +#define BD79124_REG_AUTO_CHANNELS 0x12 +#define BD79124_REG_ALERT_CH_SEL 0x14 +#define BD79124_REG_EVENT_FLAG 0x18 +#define BD79124_REG_EVENT_FLAG_HI 0x1a +#define BD79124_REG_EVENT_FLAG_LO 0x1c +#define BD79124_REG_HYSTERESIS_CH0 0x20 +#define BD79124_REG_EVENTCOUNT_CH0 0x22 +#define BD79124_REG_RECENT_CH0_LSB 0xa0 +#define BD79124_REG_RECENT_CH7_MSB 0xaf + +#define BD79124_ADC_BITS 12 + +/* Masks for the BD79124_REG_OPMODE_CFG */ +#define BD79124_MSK_CONV_MODE GENMASK(6, 5) +#define BD79124_CONV_MODE_MANSEQ 0 +#define BD79124_CONV_MODE_AUTO 1 +#define BD79124_MSK_AUTO_INTERVAL GENMASK(1, 0) +#define BD79124_INTERVAL_750_US 0 + +/* Masks for the BD79124_REG_GEN_CFG */ +#define BD79124_MSK_DWC_EN BIT(4) +#define BD79124_MSK_STATS_EN BIT(5) + +/* Masks for the BD79124_REG_SEQ_CFG */ +#define BD79124_MSK_SEQ_START BIT(4) +#define BD79124_MSK_SEQ_MODE GENMASK(1, 0) +#define BD79124_MSK_SEQ_MANUAL 0 +#define BD79124_MSK_SEQ_SEQ 1 + +#define BD79124_MSK_HYSTERESIS GENMASK(3, 0) +#define BD79124_LOW_LIMIT_MIN 0 +#define BD79124_HIGH_LIMIT_MAX GENMASK(11, 0) + +/* + * The high limit, low limit and last measurement result are each stored in + * 2 consecutive registers. 4 bits are in the high bits of the first register + * and 8 bits in the next register. + * + * These macros return the address of the first reg for the given channel. + */ +#define BD79124_GET_HIGH_LIMIT_REG(ch) (BD79124_REG_HYSTERESIS_CH0 + (ch) * 4) +#define BD79124_GET_LOW_LIMIT_REG(ch) (BD79124_REG_EVENTCOUNT_CH0 + (ch) * 4) +#define BD79124_GET_LIMIT_REG(ch, dir) ((dir) == IIO_EV_DIR_RISING ? \ + BD79124_GET_HIGH_LIMIT_REG(ch) : BD79124_GET_LOW_LIMIT_REG(ch)) +#define BD79124_GET_RECENT_RES_REG(ch) (BD79124_REG_RECENT_CH0_LSB + (ch) * 2) + +/* + * The hysteresis for a channel is stored in the same register where the + * 4 bits of high limit reside. + */ +#define BD79124_GET_HYSTERESIS_REG(ch) BD79124_GET_HIGH_LIMIT_REG(ch) + +#define BD79124_MAX_NUM_CHANNELS 8 + +struct bd79124_data { + s64 timestamp; + struct regmap *map; + struct device *dev; + int vmax; + /* + * Keep measurement status so read_raw() knows if the measurement needs + * to be started. + */ + int alarm_monitored[BD79124_MAX_NUM_CHANNELS]; + /* + * The BD79124 does not allow disabling/enabling limit separately for + * one direction only. Hence, we do the disabling by changing the limit + * to maximum/minimum measurable value. This means we need to cache + * the limit in order to maintain it over the time limit is disabled. + */ + u16 alarm_r_limit[BD79124_MAX_NUM_CHANNELS]; + u16 alarm_f_limit[BD79124_MAX_NUM_CHANNELS]; + /* Bitmask of disabled events (for rate limiting) for each channel. */ + int alarm_suppressed[BD79124_MAX_NUM_CHANNELS]; + /* + * The BD79124 is configured to run the measurements in the background. + * This is done for the event monitoring as well as for the read_raw(). + * Protect the measurement starting/stopping using a mutex. + */ + struct mutex mutex; + struct delayed_work alm_enable_work; + struct gpio_chip gc; + u8 gpio_valid_mask; +}; + +static const struct regmap_range bd79124_ro_ranges[] = { + regmap_reg_range(BD79124_REG_EVENT_FLAG, BD79124_REG_EVENT_FLAG), + regmap_reg_range(BD79124_REG_RECENT_CH0_LSB, BD79124_REG_RECENT_CH7_MSB), +}; + +static const struct regmap_access_table bd79124_ro_regs = { + .no_ranges = &bd79124_ro_ranges[0], + .n_no_ranges = ARRAY_SIZE(bd79124_ro_ranges), +}; + +static const struct regmap_range bd79124_volatile_ranges[] = { + regmap_reg_range(BD79124_REG_RECENT_CH0_LSB, BD79124_REG_RECENT_CH7_MSB), + regmap_reg_range(BD79124_REG_EVENT_FLAG, BD79124_REG_EVENT_FLAG), + regmap_reg_range(BD79124_REG_EVENT_FLAG_HI, BD79124_REG_EVENT_FLAG_HI), + regmap_reg_range(BD79124_REG_EVENT_FLAG_LO, BD79124_REG_EVENT_FLAG_LO), + regmap_reg_range(BD79124_REG_SYSTEM_STATUS, BD79124_REG_SYSTEM_STATUS), +}; + +static const struct regmap_access_table bd79124_volatile_regs = { + .yes_ranges = &bd79124_volatile_ranges[0], + .n_yes_ranges = ARRAY_SIZE(bd79124_volatile_ranges), +}; + +static const struct regmap_range bd79124_precious_ranges[] = { + regmap_reg_range(BD79124_REG_EVENT_FLAG_HI, BD79124_REG_EVENT_FLAG_HI), + regmap_reg_range(BD79124_REG_EVENT_FLAG_LO, BD79124_REG_EVENT_FLAG_LO), +}; + +static const struct regmap_access_table bd79124_precious_regs = { + .yes_ranges = &bd79124_precious_ranges[0], + .n_yes_ranges = ARRAY_SIZE(bd79124_precious_ranges), +}; + +static const struct regmap_config bd79124_regmap = { + .reg_bits = 16, + .val_bits = 8, + .read_flag_mask = BD79124_I2C_MULTI_READ, + .write_flag_mask = BD79124_I2C_MULTI_WRITE, + .max_register = BD79124_REG_MAX, + .cache_type = REGCACHE_MAPLE, + .volatile_table = &bd79124_volatile_regs, + .wr_table = &bd79124_ro_regs, + .precious_table = &bd79124_precious_regs, +}; + +static int bd79124gpo_direction_get(struct gpio_chip *gc, unsigned int offset) +{ + return GPIO_LINE_DIRECTION_OUT; +} + +static int bd79124gpo_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + struct bd79124_data *data = gpiochip_get_data(gc); + + return regmap_assign_bits(data->map, BD79124_REG_GPO_VAL, BIT(offset), + value); +} + +static int bd79124gpo_set_multiple(struct gpio_chip *gc, unsigned long *mask, + unsigned long *bits) +{ + unsigned int all_gpos; + int ret; + struct bd79124_data *data = gpiochip_get_data(gc); + + /* + * Ensure all GPIOs in 'mask' are set to be GPIOs + * The valid_mask was not obeyed by the gpiolib in all cases prior the + * https://lore.kernel.org/all/cd5e067b80e1bb590027bc3bfa817e7f794f21c3.1741180097.git.mazziesaccount@gmail.com/ + * + * Keep this check here for a couple of cycles. + */ + ret = regmap_read(data->map, BD79124_REG_PINCFG, &all_gpos); + if (ret) + return ret; + + if (all_gpos ^ *mask) { + dev_dbg(data->dev, "Invalid mux config. Can't set value.\n"); + + return -EINVAL; + } + + return regmap_update_bits(data->map, BD79124_REG_GPO_VAL, *mask, *bits); +} + +static int bd79124_init_valid_mask(struct gpio_chip *gc, + unsigned long *valid_mask, + unsigned int ngpios) +{ + struct bd79124_data *data = gpiochip_get_data(gc); + + *valid_mask = data->gpio_valid_mask; + + return 0; +} + +/* Template for GPIO chip */ +static const struct gpio_chip bd79124gpo_chip = { + .label = "bd79124-gpo", + .get_direction = bd79124gpo_direction_get, + .set = bd79124gpo_set, + .set_multiple = bd79124gpo_set_multiple, + .init_valid_mask = bd79124_init_valid_mask, + .can_sleep = true, + .ngpio = 8, + .base = -1, +}; + +struct bd79124_raw { + u8 val_bit3_0; /* Is set in high bits of the byte */ + u8 val_bit11_4; +}; +#define BD79124_RAW_TO_INT(r) ((r.val_bit11_4 << 4) | (r.val_bit3_0 >> 4)) +#define BD79124_INT_TO_RAW(val) { \ + .val_bit11_4 = (val) >> 4, \ + .val_bit3_0 = (val) << 4, \ +} + +/* + * The high and low limits as well as the recent result values are stored in + * the same way in 2 consequent registers. The first register contains 4 bits + * of the value. These bits are stored in the high bits [7:4] of register, but + * they represent the low bits [3:0] of the value. + * The value bits [11:4] are stored in the next register. + * + * Read data from register and convert to integer. + */ +static int bd79124_read_reg_to_int(struct bd79124_data *data, int reg, + unsigned int *val) +{ + int ret; + struct bd79124_raw raw; + + ret = regmap_bulk_read(data->map, reg, &raw, sizeof(raw)); + if (ret) { + dev_dbg(data->dev, "bulk_read failed %d\n", ret); + + return ret; + } + + *val = BD79124_RAW_TO_INT(raw); + + return 0; +} + +/* + * The high and low limits as well as the recent result values are stored in + * the same way in 2 consequent registers. The first register contains 4 bits + * of the value. These bits are stored in the high bits [7:4] of register, but + * they represent the low bits [3:0] of the value. + * The value bits [11:4] are stored in the next register. + * + * Convert the integer to register format and write it using rmw cycle. + */ +static int bd79124_write_int_to_reg(struct bd79124_data *data, int reg, + unsigned int val) +{ + struct bd79124_raw raw = BD79124_INT_TO_RAW(val); + unsigned int tmp; + int ret; + + ret = regmap_read(data->map, reg, &tmp); + if (ret) + return ret; + + raw.val_bit3_0 |= (tmp & 0xf); + + return regmap_bulk_write(data->map, reg, &raw, sizeof(raw)); +} + +static const struct iio_event_spec bd79124_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_HYSTERESIS), + }, +}; + +static const struct iio_chan_spec bd79124_chan_template_noirq = { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .indexed = 1, +}; + +static const struct iio_chan_spec bd79124_chan_template = { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .indexed = 1, + .event_spec = bd79124_events, + .num_event_specs = ARRAY_SIZE(bd79124_events), +}; + +static int bd79124_read_event_value(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, + int *val2) +{ + struct bd79124_data *data = iio_priv(iio_dev); + int ret, reg; + + if (chan->channel >= BD79124_MAX_NUM_CHANNELS) + return -EINVAL; + + switch (info) { + case IIO_EV_INFO_VALUE: + if (dir == IIO_EV_DIR_RISING) + *val = data->alarm_r_limit[chan->channel]; + else if (dir == IIO_EV_DIR_FALLING) + *val = data->alarm_f_limit[chan->channel]; + else + return -EINVAL; + + return IIO_VAL_INT; + + case IIO_EV_INFO_HYSTERESIS: + reg = BD79124_GET_HYSTERESIS_REG(chan->channel); + ret = regmap_read(data->map, reg, val); + if (ret) + return ret; + + *val &= BD79124_MSK_HYSTERESIS; + /* + * The data-sheet says the hysteresis register value needs to be + * shifted left by 3. + */ + *val <<= 3; + + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int bd79124_start_measurement(struct bd79124_data *data, int chan) +{ + unsigned int val, regval; + int ret; + + /* See if already started */ + ret = regmap_read(data->map, BD79124_REG_AUTO_CHANNELS, &val); + if (val & BIT(chan)) + return 0; + + /* + * The sequencer must be stopped when channels are added/removed from + * the list of the measured channels to ensure the new channel + * configuration is used. + */ + ret = regmap_clear_bits(data->map, BD79124_REG_SEQ_CFG, + BD79124_MSK_SEQ_START); + if (ret) + return ret; + + ret = regmap_write(data->map, BD79124_REG_AUTO_CHANNELS, val | BIT(chan)); + if (ret) + return ret; + + ret = regmap_set_bits(data->map, BD79124_REG_SEQ_CFG, + BD79124_MSK_SEQ_START); + if (ret) + return ret; + + /* + * Start the measurement at the background. Don't bother checking if + * it was started, regmap has cache. + */ + regval = FIELD_PREP(BD79124_MSK_CONV_MODE, BD79124_CONV_MODE_AUTO); + + return regmap_update_bits(data->map, BD79124_REG_OPMODE_CFG, + BD79124_MSK_CONV_MODE, regval); +} + +static int bd79124_stop_measurement(struct bd79124_data *data, int chan) +{ + unsigned int enabled_chans; + int ret; + + /* See if already stopped */ + ret = regmap_read(data->map, BD79124_REG_AUTO_CHANNELS, &enabled_chans); + if (!(enabled_chans & BIT(chan))) + return 0; + + ret = regmap_clear_bits(data->map, BD79124_REG_SEQ_CFG, + BD79124_MSK_SEQ_START); + + /* Clear the channel from the measured channels */ + enabled_chans &= ~BIT(chan); + ret = regmap_write(data->map, BD79124_REG_AUTO_CHANNELS, + enabled_chans); + if (ret) + return ret; + + /* + * Stop background conversion for power saving if it was the last + * channel. + */ + if (!enabled_chans) { + int regval = FIELD_PREP(BD79124_MSK_CONV_MODE, + BD79124_CONV_MODE_MANSEQ); + + ret = regmap_update_bits(data->map, BD79124_REG_OPMODE_CFG, + BD79124_MSK_CONV_MODE, regval); + if (ret) + return ret; + } + + return regmap_set_bits(data->map, BD79124_REG_SEQ_CFG, + BD79124_MSK_SEQ_START); +} + +static int bd79124_read_event_config(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct bd79124_data *data = iio_priv(iio_dev); + + if (chan->channel >= BD79124_MAX_NUM_CHANNELS) + return -EINVAL; + + return !!(data->alarm_monitored[chan->channel] & BIT(dir)); +} + +static int bd79124_disable_event(struct bd79124_data *data, + enum iio_event_direction dir, int channel) +{ + int dir_bit = BIT(dir); + int reg; + unsigned int limit; + + guard(mutex)(&data->mutex); + + /* + * Set thresholds either to 0 or to 2^12 - 1 as appropriate to prevent + * alerts and thus disable event generation. + */ + if (dir == IIO_EV_DIR_RISING) { + reg = BD79124_GET_HIGH_LIMIT_REG(channel); + limit = BD79124_HIGH_LIMIT_MAX; + } else if (dir == IIO_EV_DIR_FALLING) { + reg = BD79124_GET_LOW_LIMIT_REG(channel); + limit = BD79124_LOW_LIMIT_MIN; + } else { + return -EINVAL; + } + + data->alarm_monitored[channel] &= ~dir_bit; + + /* + * Stop measurement if there is no more events to monitor. + * We don't bother checking the retval because the limit + * setting should in any case effectively disable the alarm. + */ + if (!data->alarm_monitored[channel]) { + bd79124_stop_measurement(data, channel); + regmap_clear_bits(data->map, BD79124_REG_ALERT_CH_SEL, + BIT(channel)); + } + + return bd79124_write_int_to_reg(data, reg, limit); +} + +static int bd79124_enable_event(struct bd79124_data *data, + enum iio_event_direction dir, + unsigned int channel) +{ + int dir_bit = BIT(dir); + int reg, ret; + u16 *limit; + + guard(mutex)(&data->mutex); + ret = bd79124_start_measurement(data, channel); + if (ret) + return ret; + + data->alarm_monitored[channel] |= dir_bit; + + /* Add the channel to the list of monitored channels */ + ret = regmap_set_bits(data->map, BD79124_REG_ALERT_CH_SEL, BIT(channel)); + if (ret) + return ret; + + if (dir == IIO_EV_DIR_RISING) { + limit = &data->alarm_f_limit[channel]; + reg = BD79124_GET_HIGH_LIMIT_REG(channel); + } else { + limit = &data->alarm_f_limit[channel]; + reg = BD79124_GET_LOW_LIMIT_REG(channel); + } + /* + * Don't write the new limit to the hardware if we are in the + * rate-limit period. The timer which re-enables the event will set + * the limit. + */ + if (!(data->alarm_suppressed[channel] & dir_bit)) { + ret = bd79124_write_int_to_reg(data, reg, *limit); + if (ret) + return ret; + } + + /* + * Enable comparator. Trust the regmap cache, no need to check + * if it was already enabled. + * + * We could do this in the hw-init, but there may be users who + * never enable alarms and for them it makes sense to not + * enable the comparator at probe. + */ + return regmap_set_bits(data->map, BD79124_REG_GEN_CFG, + BD79124_MSK_DWC_EN); +} + +static int bd79124_write_event_config(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct bd79124_data *data = iio_priv(iio_dev); + + if (chan->channel >= BD79124_MAX_NUM_CHANNELS) + return -EINVAL; + + if (state) + return bd79124_enable_event(data, dir, chan->channel); + + return bd79124_disable_event(data, dir, chan->channel); +} + +static int bd79124_write_event_value(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, + int val2) +{ + struct bd79124_data *data = iio_priv(iio_dev); + int reg; + + if (chan->channel >= BD79124_MAX_NUM_CHANNELS) + return -EINVAL; + + switch (info) { + case IIO_EV_INFO_VALUE: + { + guard(mutex)(&data->mutex); + + if (dir == IIO_EV_DIR_RISING) { + data->alarm_r_limit[chan->channel] = val; + reg = BD79124_GET_HIGH_LIMIT_REG(chan->channel); + } else if (dir == IIO_EV_DIR_FALLING) { + data->alarm_f_limit[chan->channel] = val; + reg = BD79124_GET_LOW_LIMIT_REG(chan->channel); + } else { + return -EINVAL; + } + /* + * We don't want to enable the alarm if it is not enabled or + * if it is suppressed. In that case skip writing to the + * register. + */ + if (!(data->alarm_monitored[chan->channel] & BIT(dir)) || + data->alarm_suppressed[chan->channel] & BIT(dir)) + return 0; + + return bd79124_write_int_to_reg(data, reg, val); + } + case IIO_EV_INFO_HYSTERESIS: + reg = BD79124_GET_HYSTERESIS_REG(chan->channel); + val >>= 3; + + return regmap_update_bits(data->map, reg, BD79124_MSK_HYSTERESIS, + val); + default: + return -EINVAL; + } +} + +static int bd79124_single_chan_seq(struct bd79124_data *data, int chan, unsigned int *old) +{ + int ret; + + ret = regmap_clear_bits(data->map, BD79124_REG_SEQ_CFG, + BD79124_MSK_SEQ_START); + if (ret) + return ret; + + /* + * It may be we have some channels monitored for alarms so we want to + * cache the old config and return it when the single channel + * measurement has been completed. + */ + ret = regmap_read(data->map, BD79124_REG_AUTO_CHANNELS, old); + if (ret) + return ret; + + ret = regmap_write(data->map, BD79124_REG_AUTO_CHANNELS, BIT(chan)); + if (ret) + return ret; + + /* Restart the sequencer */ + return regmap_set_bits(data->map, BD79124_REG_SEQ_CFG, + BD79124_MSK_SEQ_START); +} + +static int bd79124_single_chan_seq_end(struct bd79124_data *data, unsigned int old) +{ + int ret; + + ret = regmap_clear_bits(data->map, BD79124_REG_SEQ_CFG, + BD79124_MSK_SEQ_START); + if (ret) + return ret; + + ret = regmap_write(data->map, BD79124_REG_AUTO_CHANNELS, old); + if (ret) + return ret; + + return regmap_set_bits(data->map, BD79124_REG_SEQ_CFG, + BD79124_MSK_SEQ_START); +} + +static int bd79124_read_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long m) +{ + struct bd79124_data *data = iio_priv(iio_dev); + int ret; + + if (chan->channel >= BD79124_MAX_NUM_CHANNELS) + return -EINVAL; + + switch (m) { + case IIO_CHAN_INFO_RAW: + { + unsigned int old_chan_cfg, regval; + int tmp; + + guard(mutex)(&data->mutex); + + /* + * Start the automatic conversion. This is needed here if no + * events have been enabled. + */ + regval = FIELD_PREP(BD79124_MSK_CONV_MODE, + BD79124_CONV_MODE_AUTO); + ret = regmap_update_bits(data->map, BD79124_REG_OPMODE_CFG, + BD79124_MSK_CONV_MODE, regval); + if (ret) + return ret; + + ret = bd79124_single_chan_seq(data, chan->channel, &old_chan_cfg); + if (ret) + return ret; + + /* The maximum conversion time is 6 uS. */ + udelay(6); + + ret = bd79124_read_reg_to_int(data, + BD79124_GET_RECENT_RES_REG(chan->channel), val); + /* + * Return the old chan config even if data reading failed in + * order to re-enable the event monitoring. + */ + tmp = bd79124_single_chan_seq_end(data, old_chan_cfg); + if (tmp) + dev_err(data->dev, + "Failed to return config. Alarms may be disabled\n"); + + if (ret) + return ret; + + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_SCALE: + *val = data->vmax / 1000; + *val2 = BD79124_ADC_BITS; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static const struct iio_info bd79124_info = { + .read_raw = bd79124_read_raw, + .read_event_config = &bd79124_read_event_config, + .write_event_config = &bd79124_write_event_config, + .read_event_value = &bd79124_read_event_value, + .write_event_value = &bd79124_write_event_value, +}; + +static void bd79124_re_enable_lo(struct bd79124_data *data, unsigned int channel) +{ + int ret, evbit = BIT(IIO_EV_DIR_FALLING); + + /* + * We should not re-enable the event if user has disabled it while + * rate-limiting was enabled. + */ + if (!(data->alarm_suppressed[channel] & evbit)) + return; + + data->alarm_suppressed[channel] &= ~evbit; + + if (!(data->alarm_monitored[channel] & evbit)) + return; + + ret = bd79124_write_int_to_reg(data, BD79124_GET_LOW_LIMIT_REG(channel), + data->alarm_f_limit[channel]); + if (ret) + dev_warn(data->dev, "Low limit enabling failed for channel%d\n", + channel); +} + +static void bd79124_re_enable_hi(struct bd79124_data *data, unsigned int channel) +{ + int ret, evbit = BIT(IIO_EV_DIR_RISING); + + /* + * We should not re-enable the event if user has disabled it while + * rate-limiting was enabled. + */ + if (!(data->alarm_suppressed[channel] & evbit)) + return; + + data->alarm_suppressed[channel] &= ~evbit; + + if (!(data->alarm_monitored[channel] & evbit)) + return; + + ret = bd79124_write_int_to_reg(data, BD79124_GET_HIGH_LIMIT_REG(channel), + data->alarm_r_limit[channel]); + if (ret) + dev_warn(data->dev, "High limit enabling failed for channel%d\n", + channel); +} + +static void bd79124_alm_enable_worker(struct work_struct *work) +{ + int i; + struct bd79124_data *data = container_of(work, struct bd79124_data, + alm_enable_work.work); + + /* Take the mutex so there is no race with user disabling the alarm */ + guard(mutex)(&data->mutex); + for (i = 0; i < BD79124_MAX_NUM_CHANNELS; i++) { + bd79124_re_enable_hi(data, i); + bd79124_re_enable_lo(data, i); + } +} + +static int __bd79124_event_ratelimit(struct bd79124_data *data, int reg, + unsigned int limit) +{ + int ret; + + if (limit > BD79124_HIGH_LIMIT_MAX) + return -EINVAL; + + ret = bd79124_write_int_to_reg(data, reg, limit); + if (ret) + return ret; + + /* + * We use 1 sec 'grace period'. At the moment I see no reason to make + * this user configurable. We need an ABI for this if configuration is + * needed. + */ + schedule_delayed_work(&data->alm_enable_work, msecs_to_jiffies(1000)); + + return 0; +} + +static int bd79124_event_ratelimit_hi(struct bd79124_data *data, + unsigned int channel) +{ + guard(mutex)(&data->mutex); + data->alarm_suppressed[channel] |= BIT(IIO_EV_DIR_RISING); + + return __bd79124_event_ratelimit(data, + BD79124_GET_HIGH_LIMIT_REG(channel), + BD79124_HIGH_LIMIT_MAX); +} + +static int bd79124_event_ratelimit_lo(struct bd79124_data *data, + unsigned int channel) +{ + guard(mutex)(&data->mutex); + data->alarm_suppressed[channel] |= BIT(IIO_EV_DIR_FALLING); + + return __bd79124_event_ratelimit(data, + BD79124_GET_LOW_LIMIT_REG(channel), + BD79124_LOW_LIMIT_MIN); +} + +static irqreturn_t bd79124_event_handler(int irq, void *priv) +{ + unsigned int i_hi, i_lo; + int i, ret; + struct iio_dev *iio_dev = priv; + struct bd79124_data *data = iio_priv(iio_dev); + + /* + * Return IRQ_NONE if bailing-out without acking. This allows the IRQ + * subsystem to disable the offending IRQ line if we get a hardware + * problem. This behaviour has saved my poor bottom a few times in the + * past as, instead of getting unusably unresponsive, the system has + * spilled out the magic words "...nobody cared". + */ + ret = regmap_read(data->map, BD79124_REG_EVENT_FLAG_HI, &i_hi); + if (ret) + return IRQ_NONE; + + ret = regmap_read(data->map, BD79124_REG_EVENT_FLAG_LO, &i_lo); + if (ret) + return IRQ_NONE; + + if (!i_lo && !i_hi) + return IRQ_NONE; + + for (i = 0; i < BD79124_MAX_NUM_CHANNELS; i++) { + u64 ecode; + + if (BIT(i) & i_hi) { + ecode = IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, i, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING); + + iio_push_event(iio_dev, ecode, data->timestamp); + /* + * The BD79124 keeps the IRQ asserted for as long as + * the voltage exceeds the threshold. It causes the IRQ + * to keep firing. + * + * Disable the event for the channel and schedule the + * re-enabling the event later to prevent storm of + * events. + */ + ret = bd79124_event_ratelimit_hi(data, i); + if (ret) + return IRQ_NONE; + } + if (BIT(i) & i_lo) { + ecode = IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, i, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING); + + iio_push_event(iio_dev, ecode, data->timestamp); + ret = bd79124_event_ratelimit_lo(data, i); + if (ret) + return IRQ_NONE; + } + } + + ret = regmap_write(data->map, BD79124_REG_EVENT_FLAG_HI, i_hi); + if (ret) + return IRQ_NONE; + + ret = regmap_write(data->map, BD79124_REG_EVENT_FLAG_LO, i_lo); + if (ret) + return IRQ_NONE; + + return IRQ_HANDLED; +} + +static irqreturn_t bd79124_irq_handler(int irq, void *priv) +{ + struct iio_dev *iio_dev = priv; + struct bd79124_data *data = iio_priv(iio_dev); + + data->timestamp = iio_get_time_ns(iio_dev); + + return IRQ_WAKE_THREAD; +} + +static int bd79124_chan_init(struct bd79124_data *data, int channel) +{ + int ret; + + ret = regmap_write(data->map, BD79124_GET_HIGH_LIMIT_REG(channel), + BD79124_HIGH_LIMIT_MAX); + if (ret) + return ret; + + return regmap_write(data->map, BD79124_GET_LOW_LIMIT_REG(channel), + BD79124_LOW_LIMIT_MIN); +} + +static int bd79124_get_gpio_pins(const struct iio_chan_spec *cs, int num_channels) +{ + int i, gpio_channels; + + /* + * Let's initialize the mux config to say that all 8 channels are + * GPIOs. Then we can just loop through the iio_chan_spec and clear the + * bits for found ADC channels. + */ + gpio_channels = GENMASK(7, 0); + for (i = 0; i < num_channels; i++) + gpio_channels &= ~BIT(cs[i].channel); + + return gpio_channels; +} + +static int bd79124_hw_init(struct bd79124_data *data) +{ + unsigned int regval; + int ret, i; + + for (i = 0; i < BD79124_MAX_NUM_CHANNELS; i++) { + ret = bd79124_chan_init(data, i); + if (ret) + return ret; + data->alarm_r_limit[i] = BD79124_HIGH_LIMIT_MAX; + } + /* Stop auto sequencer */ + ret = regmap_clear_bits(data->map, BD79124_REG_SEQ_CFG, + BD79124_MSK_SEQ_START); + if (ret) + return ret; + + /* Enable writing the measured values to the registers */ + ret = regmap_set_bits(data->map, BD79124_REG_GEN_CFG, + BD79124_MSK_STATS_EN); + if (ret) + return ret; + + /* Set no channels to be auto-measured */ + ret = regmap_write(data->map, BD79124_REG_AUTO_CHANNELS, 0x0); + if (ret) + return ret; + + /* Set no channels to be manually measured */ + ret = regmap_write(data->map, BD79124_REG_MANUAL_CHANNELS, 0x0); + if (ret) + return ret; + + regval = FIELD_PREP(BD79124_MSK_AUTO_INTERVAL, BD79124_INTERVAL_750_US); + ret = regmap_update_bits(data->map, BD79124_REG_OPMODE_CFG, + BD79124_MSK_AUTO_INTERVAL, regval); + if (ret) + return ret; + + /* Sequencer mode to auto */ + ret = regmap_set_bits(data->map, BD79124_REG_SEQ_CFG, + BD79124_MSK_SEQ_SEQ); + if (ret) + return ret; + + /* Don't start the measurement */ + regval = FIELD_PREP(BD79124_MSK_CONV_MODE, BD79124_CONV_MODE_MANSEQ); + return regmap_update_bits(data->map, BD79124_REG_OPMODE_CFG, + BD79124_MSK_CONV_MODE, regval); +} + +static int bd79124_probe(struct i2c_client *i2c) +{ + struct bd79124_data *data; + struct iio_dev *iio_dev; + const struct iio_chan_spec *template; + struct iio_chan_spec *cs; + struct device *dev = &i2c->dev; + unsigned int gpio_pins; + int ret; + + iio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!iio_dev) + return -ENOMEM; + + data = iio_priv(iio_dev); + data->dev = dev; + data->map = devm_regmap_init_i2c(i2c, &bd79124_regmap); + if (IS_ERR(data->map)) + return dev_err_probe(dev, PTR_ERR(data->map), + "Failed to initialize Regmap\n"); + + ret = devm_regulator_get_enable_read_voltage(dev, "vdd"); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get the Vdd\n"); + + data->vmax = ret; + + ret = devm_regulator_get_enable(dev, "iovdd"); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to enable I/O voltage\n"); + + ret = devm_delayed_work_autocancel(dev, &data->alm_enable_work, + bd79124_alm_enable_worker); + if (ret) + return ret; + + if (i2c->irq) { + template = &bd79124_chan_template; + } else { + template = &bd79124_chan_template_noirq; + dev_dbg(dev, "No IRQ found, events disabled\n"); + } + + ret = devm_mutex_init(dev, &data->mutex); + if (ret) + return ret; + + ret = devm_iio_adc_device_alloc_chaninfo_se(dev, template, + BD79124_MAX_NUM_CHANNELS - 1, &cs); + if (ret < 0) { + /* Register all pins as GPOs if there are no ADC channels */ + if (ret == -ENOENT) + goto register_gpios; + return ret; + } + iio_dev->channels = cs; + iio_dev->num_channels = ret; + iio_dev->info = &bd79124_info; + iio_dev->name = "bd79124"; + iio_dev->modes = INDIO_DIRECT_MODE; + + ret = bd79124_hw_init(data); + if (ret) + return ret; + + if (i2c->irq > 0) { + ret = devm_request_threaded_irq(dev, i2c->irq, + bd79124_irq_handler, &bd79124_event_handler, + IRQF_ONESHOT, "adc-thresh-alert", iio_dev); + if (ret) + return dev_err_probe(data->dev, ret, + "Failed to register IRQ\n"); + } + + ret = devm_iio_device_register(data->dev, iio_dev); + if (ret) + return dev_err_probe(data->dev, ret, "Failed to register ADC\n"); + +register_gpios: + gpio_pins = bd79124_get_gpio_pins(iio_dev->channels, + iio_dev->num_channels); + + /* + * The mux should default to "all ADCs", but better to not trust it. + * Thus we do set the mux even when we have only ADCs and no GPOs. + */ + ret = regmap_write(data->map, BD79124_REG_PINCFG, gpio_pins); + if (ret) + return ret; + + /* No GPOs if all channels are reserved for ADC, so we're done. */ + if (!gpio_pins) + return 0; + + data->gpio_valid_mask = gpio_pins; + data->gc = bd79124gpo_chip; + data->gc.parent = dev; + + return devm_gpiochip_add_data(dev, &data->gc, data); +} + +static const struct of_device_id bd79124_of_match[] = { + { .compatible = "rohm,bd79124" }, + { } +}; +MODULE_DEVICE_TABLE(of, bd79124_of_match); + +static const struct i2c_device_id bd79124_id[] = { + { .name = "bd79124" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bd79124_id); + +static struct i2c_driver bd79124_driver = { + .driver = { + .name = "bd79124", + .of_match_table = bd79124_of_match, + }, + .probe = bd79124_probe, + .id_table = bd79124_id, +}; +module_i2c_driver(bd79124_driver); + +MODULE_AUTHOR("Matti Vaittinen "); +MODULE_DESCRIPTION("Driver for ROHM BD79124 ADC"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_DRIVER"); diff --git a/drivers/iio/adc/rtq6056.c b/drivers/iio/adc/rtq6056.c new file mode 100644 index 000000000..2083c4351 --- /dev/null +++ b/drivers/iio/adc/rtq6056.c @@ -0,0 +1,900 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2022 Richtek Technology Corp. + * + * ChiYuan Huang + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define RTQ6056_REG_CONFIG 0x00 +#define RTQ6056_REG_SHUNTVOLT 0x01 +#define RTQ6056_REG_BUSVOLT 0x02 +#define RTQ6056_REG_POWER 0x03 +#define RTQ6056_REG_CURRENT 0x04 +#define RTQ6056_REG_CALIBRATION 0x05 +#define RTQ6056_REG_MASKENABLE 0x06 +#define RTQ6056_REG_ALERTLIMIT 0x07 +#define RTQ6056_REG_MANUFACTID 0xFE +#define RTQ6056_REG_DIEID 0xFF + +#define RTQ6056_VENDOR_ID 0x1214 +#define RTQ6056_DEFAULT_CONFIG 0x4127 +#define RTQ6056_CONT_ALLON 7 + +#define RTQ6059_DEFAULT_CONFIG 0x3C47 +#define RTQ6059_VBUS_LSB_OFFSET 3 +#define RTQ6059_AVG_BASE 8 + +enum { + RTQ6056_CH_VSHUNT = 0, + RTQ6056_CH_VBUS, + RTQ6056_CH_POWER, + RTQ6056_CH_CURRENT, + RTQ6056_MAX_CHANNEL +}; + +/* + * The enum is to present the 0x00 CONFIG RG bitfield for the 16bit RG value + * field value order from LSB to MSB + * RTQ6053/6 is OPMODE->VSHUNTCT->VBUSCT->AVG->RESET + * RTQ6059 is OPMODE->SADC->BADC->PGA->RESET + */ +enum { + F_OPMODE = 0, + F_VSHUNTCT, + F_RTQ6059_SADC = F_VSHUNTCT, + F_VBUSCT, + F_RTQ6059_BADC = F_VBUSCT, + F_AVG, + F_RTQ6059_PGA = F_AVG, + F_RESET, + F_MAX_FIELDS +}; + +struct rtq6056_priv; + +struct richtek_dev_data { + bool fixed_samp_freq; + u8 vbus_offset; + int default_conv_time_us; + unsigned int default_config; + unsigned int calib_coefficient; + const int *avg_sample_list; + int avg_sample_list_length; + const struct reg_field *reg_fields; + const struct iio_chan_spec *channels; + int num_channels; + int (*read_scale)(struct iio_chan_spec const *ch, int *val, int *val2); + int (*set_average)(struct rtq6056_priv *priv, int val); +}; + +struct rtq6056_priv { + struct device *dev; + struct regmap *regmap; + struct regmap_field *rm_fields[F_MAX_FIELDS]; + const struct richtek_dev_data *devdata; + u32 shunt_resistor_uohm; + int vshuntct_us; + int vbusct_us; + int avg_sample; +}; + +static const struct reg_field rtq6056_reg_fields[F_MAX_FIELDS] = { + [F_OPMODE] = REG_FIELD(RTQ6056_REG_CONFIG, 0, 2), + [F_VSHUNTCT] = REG_FIELD(RTQ6056_REG_CONFIG, 3, 5), + [F_VBUSCT] = REG_FIELD(RTQ6056_REG_CONFIG, 6, 8), + [F_AVG] = REG_FIELD(RTQ6056_REG_CONFIG, 9, 11), + [F_RESET] = REG_FIELD(RTQ6056_REG_CONFIG, 15, 15), +}; + +static const struct reg_field rtq6059_reg_fields[F_MAX_FIELDS] = { + [F_OPMODE] = REG_FIELD(RTQ6056_REG_CONFIG, 0, 2), + [F_RTQ6059_SADC] = REG_FIELD(RTQ6056_REG_CONFIG, 3, 6), + [F_RTQ6059_BADC] = REG_FIELD(RTQ6056_REG_CONFIG, 7, 10), + [F_RTQ6059_PGA] = REG_FIELD(RTQ6056_REG_CONFIG, 11, 12), + [F_RESET] = REG_FIELD(RTQ6056_REG_CONFIG, 15, 15), +}; + +static const struct iio_chan_spec rtq6056_channels[RTQ6056_MAX_CHANNEL + 1] = { + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .address = RTQ6056_REG_SHUNTVOLT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .address = RTQ6056_REG_BUSVOLT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 1, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_POWER, + .indexed = 1, + .channel = 2, + .address = RTQ6056_REG_POWER, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 2, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_CURRENT, + .indexed = 1, + .channel = 3, + .address = RTQ6056_REG_CURRENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 3, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(RTQ6056_MAX_CHANNEL), +}; + +/* + * Difference between RTQ6056 and RTQ6059 + * - Fixed sampling conversion time + * - Average sample numbers + * - Channel scale + * - calibration coefficient + */ +static const struct iio_chan_spec rtq6059_channels[RTQ6056_MAX_CHANNEL + 1] = { + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .address = RTQ6056_REG_SHUNTVOLT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .address = RTQ6056_REG_BUSVOLT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 1, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_POWER, + .indexed = 1, + .channel = 2, + .address = RTQ6056_REG_POWER, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 2, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_CURRENT, + .indexed = 1, + .channel = 3, + .address = RTQ6056_REG_CURRENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 3, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(RTQ6056_MAX_CHANNEL), +}; + +static int rtq6056_adc_read_channel(struct rtq6056_priv *priv, + struct iio_chan_spec const *ch, + int *val) +{ + const struct richtek_dev_data *devdata = priv->devdata; + struct device *dev = priv->dev; + unsigned int addr = ch->address; + unsigned int regval; + int ret; + + pm_runtime_get_sync(dev); + ret = regmap_read(priv->regmap, addr, ®val); + pm_runtime_mark_last_busy(dev); + pm_runtime_put(dev); + if (ret) + return ret; + + /* Power and VBUS is unsigned 16-bit, others are signed 16-bit */ + switch (addr) { + case RTQ6056_REG_BUSVOLT: + regval >>= devdata->vbus_offset; + *val = regval; + return IIO_VAL_INT; + case RTQ6056_REG_POWER: + *val = regval; + return IIO_VAL_INT; + case RTQ6056_REG_SHUNTVOLT: + case RTQ6056_REG_CURRENT: + *val = sign_extend32(regval, 15); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int rtq6056_adc_read_scale(struct iio_chan_spec const *ch, int *val, + int *val2) +{ + switch (ch->address) { + case RTQ6056_REG_SHUNTVOLT: + /* VSHUNT lsb 2.5uV */ + *val = 2500; + *val2 = 1000000; + return IIO_VAL_FRACTIONAL; + case RTQ6056_REG_BUSVOLT: + /* VBUS lsb 1.25mV */ + *val = 1250; + *val2 = 1000; + return IIO_VAL_FRACTIONAL; + case RTQ6056_REG_POWER: + /* Power lsb 25mW */ + *val = 25; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int rtq6059_adc_read_scale(struct iio_chan_spec const *ch, int *val, + int *val2) +{ + switch (ch->address) { + case RTQ6056_REG_SHUNTVOLT: + /* VSHUNT lsb 10uV */ + *val = 10000; + *val2 = 1000000; + return IIO_VAL_FRACTIONAL; + case RTQ6056_REG_BUSVOLT: + /* VBUS lsb 4mV */ + *val = 4; + return IIO_VAL_INT; + case RTQ6056_REG_POWER: + /* Power lsb 20mW */ + *val = 20; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +/* + * Sample frequency for channel VSHUNT and VBUS. The indices correspond + * with the bit value expected by the chip. And it can be found at + * https://www.richtek.com/assets/product_file/RTQ6056/DSQ6056-00.pdf + */ +static const int rtq6056_samp_freq_list[] = { + 7194, 4926, 3717, 1904, 964, 485, 243, 122, +}; + +static int rtq6056_adc_set_samp_freq(struct rtq6056_priv *priv, + struct iio_chan_spec const *ch, int val) +{ + struct regmap_field *rm_field; + unsigned int selector; + int *ct, ret; + + if (val > 7194 || val < 122) + return -EINVAL; + + if (ch->address == RTQ6056_REG_SHUNTVOLT) { + rm_field = priv->rm_fields[F_VSHUNTCT]; + ct = &priv->vshuntct_us; + } else if (ch->address == RTQ6056_REG_BUSVOLT) { + rm_field = priv->rm_fields[F_VBUSCT]; + ct = &priv->vbusct_us; + } else + return -EINVAL; + + selector = find_closest_descending(val, rtq6056_samp_freq_list, + ARRAY_SIZE(rtq6056_samp_freq_list)); + + ret = regmap_field_write(rm_field, selector); + if (ret) + return ret; + + *ct = 1000000 / rtq6056_samp_freq_list[selector]; + + return 0; +} + +/* + * Available averaging rate for rtq6056. The indices correspond with the bit + * value expected by the chip. And it can be found at + * https://www.richtek.com/assets/product_file/RTQ6056/DSQ6056-00.pdf + */ +static const int rtq6056_avg_sample_list[] = { + 1, 4, 16, 64, 128, 256, 512, 1024, +}; + +static const int rtq6059_avg_sample_list[] = { + 1, 2, 4, 8, 16, 32, 64, 128, +}; + +static int rtq6056_adc_set_average(struct rtq6056_priv *priv, int val) +{ + unsigned int selector; + int ret; + + if (val > 1024 || val < 1) + return -EINVAL; + + selector = find_closest(val, rtq6056_avg_sample_list, + ARRAY_SIZE(rtq6056_avg_sample_list)); + + ret = regmap_field_write(priv->rm_fields[F_AVG], selector); + if (ret) + return ret; + + priv->avg_sample = rtq6056_avg_sample_list[selector]; + + return 0; +} + +static int rtq6059_adc_set_average(struct rtq6056_priv *priv, int val) +{ + unsigned int selector; + int ret; + + if (val > 128 || val < 1) + return -EINVAL; + + /* The supported average sample is 2^x (x from 0 to 7) */ + selector = fls(val) - 1; + + ret = regmap_field_write(priv->rm_fields[F_RTQ6059_BADC], + RTQ6059_AVG_BASE + selector); + if (ret) + return ret; + + ret = regmap_field_write(priv->rm_fields[F_RTQ6059_SADC], + RTQ6059_AVG_BASE + selector); + + priv->avg_sample = BIT(selector); + + return 0; +} + +static int rtq6056_adc_get_sample_freq(struct rtq6056_priv *priv, + struct iio_chan_spec const *ch, int *val) +{ + int sample_time; + + if (ch->address == RTQ6056_REG_SHUNTVOLT) + sample_time = priv->vshuntct_us; + else if (ch->address == RTQ6056_REG_BUSVOLT) + sample_time = priv->vbusct_us; + else { + sample_time = priv->vshuntct_us + priv->vbusct_us; + sample_time *= priv->avg_sample; + } + + *val = 1000000 / sample_time; + + return IIO_VAL_INT; +} + +static int rtq6056_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct rtq6056_priv *priv = iio_priv(indio_dev); + const struct richtek_dev_data *devdata = priv->devdata; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return rtq6056_adc_read_channel(priv, chan, val); + case IIO_CHAN_INFO_SCALE: + return devdata->read_scale(chan, val, val2); + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = priv->avg_sample; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + return rtq6056_adc_get_sample_freq(priv, chan, val); + default: + return -EINVAL; + } +} + +static int rtq6056_adc_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct rtq6056_priv *priv = iio_priv(indio_dev); + const struct richtek_dev_data *devdata = priv->devdata; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = rtq6056_samp_freq_list; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(rtq6056_samp_freq_list); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = devdata->avg_sample_list; + *length = devdata->avg_sample_list_length; + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int __rtq6056_adc_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + long mask) +{ + struct rtq6056_priv *priv = iio_priv(indio_dev); + const struct richtek_dev_data *devdata = priv->devdata; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (devdata->fixed_samp_freq) + return -EINVAL; + return rtq6056_adc_set_samp_freq(priv, chan, val); + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return devdata->set_average(priv, val); + default: + return -EINVAL; + } +} + +static int rtq6056_adc_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = __rtq6056_adc_write_raw(indio_dev, chan, val, mask); + iio_device_release_direct(indio_dev); + return ret; +} + +static const char *rtq6056_channel_labels[RTQ6056_MAX_CHANNEL] = { + [RTQ6056_CH_VSHUNT] = "Vshunt", + [RTQ6056_CH_VBUS] = "Vbus", + [RTQ6056_CH_POWER] = "Power", + [RTQ6056_CH_CURRENT] = "Current", +}; + +static int rtq6056_adc_read_label(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + char *label) +{ + return sysfs_emit(label, "%s\n", rtq6056_channel_labels[chan->channel]); +} + +static int rtq6056_set_shunt_resistor(struct rtq6056_priv *priv, + int resistor_uohm) +{ + const struct richtek_dev_data *devdata = priv->devdata; + unsigned int calib_val; + int ret; + + if (resistor_uohm <= 0) { + dev_err(priv->dev, "Invalid resistor [%d]\n", resistor_uohm); + return -EINVAL; + } + + /* calibration = coefficient / (Rshunt (uOhm) * current lsb (1mA)) */ + calib_val = devdata->calib_coefficient / resistor_uohm; + ret = regmap_write(priv->regmap, RTQ6056_REG_CALIBRATION, calib_val); + if (ret) + return ret; + + priv->shunt_resistor_uohm = resistor_uohm; + + return 0; +} + +static ssize_t shunt_resistor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rtq6056_priv *priv = iio_priv(dev_to_iio_dev(dev)); + int vals[2] = { priv->shunt_resistor_uohm, 1000000 }; + + return iio_format_value(buf, IIO_VAL_FRACTIONAL, 1, vals); +} + +static ssize_t shunt_resistor_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct rtq6056_priv *priv = iio_priv(indio_dev); + int val, val_fract, ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = iio_str_to_fixpoint(buf, 100000, &val, &val_fract); + if (ret) + goto out_store; + + ret = rtq6056_set_shunt_resistor(priv, val * 1000000 + val_fract); + +out_store: + iio_device_release_direct(indio_dev); + + return ret ?: len; +} + +static IIO_DEVICE_ATTR_RW(shunt_resistor, 0); + +static struct attribute *rtq6056_attributes[] = { + &iio_dev_attr_shunt_resistor.dev_attr.attr, + NULL +}; + +static const struct attribute_group rtq6056_attribute_group = { + .attrs = rtq6056_attributes, +}; + +static const struct iio_info rtq6056_info = { + .attrs = &rtq6056_attribute_group, + .read_raw = rtq6056_adc_read_raw, + .read_avail = rtq6056_adc_read_avail, + .write_raw = rtq6056_adc_write_raw, + .read_label = rtq6056_adc_read_label, +}; + +static irqreturn_t rtq6056_buffer_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct rtq6056_priv *priv = iio_priv(indio_dev); + const struct richtek_dev_data *devdata = priv->devdata; + struct device *dev = priv->dev; + struct { + u16 vals[RTQ6056_MAX_CHANNEL]; + aligned_s64 timestamp; + } data = { }; + unsigned int raw; + int i = 0, bit, ret; + + pm_runtime_get_sync(dev); + + iio_for_each_active_channel(indio_dev, bit) { + unsigned int addr = rtq6056_channels[bit].address; + + ret = regmap_read(priv->regmap, addr, &raw); + if (ret) + goto out; + + if (addr == RTQ6056_REG_BUSVOLT) + raw >>= devdata->vbus_offset; + + data.vals[i++] = raw; + } + + iio_push_to_buffers_with_ts(indio_dev, &data, sizeof(data), + iio_get_time_ns(indio_dev)); + +out: + pm_runtime_mark_last_busy(dev); + pm_runtime_put(dev); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static void rtq6056_enter_shutdown_state(void *dev) +{ + struct rtq6056_priv *priv = dev_get_drvdata(dev); + + /* Enter shutdown state */ + regmap_field_write(priv->rm_fields[F_OPMODE], 0); +} + +static bool rtq6056_is_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RTQ6056_REG_CONFIG ... RTQ6056_REG_ALERTLIMIT: + case RTQ6056_REG_MANUFACTID ... RTQ6056_REG_DIEID: + return true; + default: + return false; + } +} + +static bool rtq6056_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RTQ6056_REG_CONFIG: + case RTQ6056_REG_CALIBRATION ... RTQ6056_REG_ALERTLIMIT: + return true; + default: + return false; + } +} + +static const struct regmap_config rtq6056_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .val_format_endian = REGMAP_ENDIAN_BIG, + .max_register = RTQ6056_REG_DIEID, + .readable_reg = rtq6056_is_readable_reg, + .writeable_reg = rtq6056_is_writeable_reg, +}; + +static int rtq6056_probe(struct i2c_client *i2c) +{ + struct iio_dev *indio_dev; + struct rtq6056_priv *priv; + struct device *dev = &i2c->dev; + struct regmap *regmap; + const struct richtek_dev_data *devdata; + unsigned int vendor_id, shunt_resistor_uohm; + int ret; + + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -EOPNOTSUPP; + + devdata = i2c_get_match_data(i2c); + if (!devdata) + return dev_err_probe(dev, -EINVAL, "Invalid dev data\n"); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + priv = iio_priv(indio_dev); + priv->dev = dev; + priv->vshuntct_us = priv->vbusct_us = devdata->default_conv_time_us; + priv->avg_sample = 1; + priv->devdata = devdata; + i2c_set_clientdata(i2c, priv); + + regmap = devm_regmap_init_i2c(i2c, &rtq6056_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Failed to init regmap\n"); + + priv->regmap = regmap; + + ret = regmap_read(regmap, RTQ6056_REG_MANUFACTID, &vendor_id); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get manufacturer info\n"); + + if (vendor_id != RTQ6056_VENDOR_ID) + return dev_err_probe(dev, -ENODEV, + "Invalid vendor id 0x%04x\n", vendor_id); + + ret = devm_regmap_field_bulk_alloc(dev, regmap, priv->rm_fields, + devdata->reg_fields, F_MAX_FIELDS); + if (ret) + return dev_err_probe(dev, ret, "Failed to init regmap field\n"); + + ret = regmap_write(regmap, RTQ6056_REG_CONFIG, devdata->default_config); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable continuous sensing\n"); + + ret = devm_add_action_or_reset(dev, rtq6056_enter_shutdown_state, dev); + if (ret) + return ret; + + pm_runtime_set_autosuspend_delay(dev, MSEC_PER_SEC); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_active(dev); + pm_runtime_mark_last_busy(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable pm_runtime\n"); + + /* By default, use 2000 micro-Ohm resistor */ + shunt_resistor_uohm = 2000; + device_property_read_u32(dev, "shunt-resistor-micro-ohms", + &shunt_resistor_uohm); + + ret = rtq6056_set_shunt_resistor(priv, shunt_resistor_uohm); + if (ret) + return dev_err_probe(dev, ret, + "Failed to init shunt resistor\n"); + + indio_dev->name = "rtq6056"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = devdata->channels; + indio_dev->num_channels = devdata->num_channels; + indio_dev->info = &rtq6056_info; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + rtq6056_buffer_trigger_handler, + NULL); + if (ret) + return dev_err_probe(dev, ret, + "Failed to allocate iio trigger buffer\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static int rtq6056_runtime_suspend(struct device *dev) +{ + struct rtq6056_priv *priv = dev_get_drvdata(dev); + + /* Configure to shutdown mode */ + return regmap_field_write(priv->rm_fields[F_OPMODE], 0); +} + +static int rtq6056_runtime_resume(struct device *dev) +{ + struct rtq6056_priv *priv = dev_get_drvdata(dev); + int sample_rdy_time_us, ret; + + ret = regmap_field_write(priv->rm_fields[F_OPMODE], RTQ6056_CONT_ALLON); + if (ret) + return ret; + + sample_rdy_time_us = priv->vbusct_us + priv->vshuntct_us; + sample_rdy_time_us *= priv->avg_sample; + + usleep_range(sample_rdy_time_us, sample_rdy_time_us + 100); + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(rtq6056_pm_ops, rtq6056_runtime_suspend, + rtq6056_runtime_resume, NULL); + +static const struct richtek_dev_data rtq6056_devdata = { + .default_conv_time_us = 1037, + .calib_coefficient = 5120000, + /* + * By default, configure average sample as 1, bus and shunt conversion + * time as 1037 microsecond, and operating mode to all on. + */ + .default_config = RTQ6056_DEFAULT_CONFIG, + .avg_sample_list = rtq6056_avg_sample_list, + .avg_sample_list_length = ARRAY_SIZE(rtq6056_avg_sample_list), + .reg_fields = rtq6056_reg_fields, + .channels = rtq6056_channels, + .num_channels = ARRAY_SIZE(rtq6056_channels), + .read_scale = rtq6056_adc_read_scale, + .set_average = rtq6056_adc_set_average, +}; + +static const struct richtek_dev_data rtq6059_devdata = { + .fixed_samp_freq = true, + .vbus_offset = RTQ6059_VBUS_LSB_OFFSET, + .default_conv_time_us = 532, + .calib_coefficient = 40960000, + /* + * By default, configure average sample as 1, bus and shunt conversion + * time as 532 microsecond, and operating mode to all on. + */ + .default_config = RTQ6059_DEFAULT_CONFIG, + .avg_sample_list = rtq6059_avg_sample_list, + .avg_sample_list_length = ARRAY_SIZE(rtq6059_avg_sample_list), + .reg_fields = rtq6059_reg_fields, + .channels = rtq6059_channels, + .num_channels = ARRAY_SIZE(rtq6059_channels), + .read_scale = rtq6059_adc_read_scale, + .set_average = rtq6059_adc_set_average, +}; + +static const struct i2c_device_id rtq6056_id[] = { + { .name = "rtq6056", .driver_data = (kernel_ulong_t)&rtq6056_devdata }, + { .name = "rtq6059", .driver_data = (kernel_ulong_t)&rtq6059_devdata }, + { } +}; +MODULE_DEVICE_TABLE(i2c, rtq6056_id); + +static const struct of_device_id rtq6056_device_match[] = { + { .compatible = "richtek,rtq6056", .data = &rtq6056_devdata }, + { .compatible = "richtek,rtq6059", .data = &rtq6059_devdata }, + { } +}; +MODULE_DEVICE_TABLE(of, rtq6056_device_match); + +static struct i2c_driver rtq6056_driver = { + .driver = { + .name = "rtq6056", + .of_match_table = rtq6056_device_match, + .pm = pm_ptr(&rtq6056_pm_ops), + }, + .probe = rtq6056_probe, + .id_table = rtq6056_id, +}; +module_i2c_driver(rtq6056_driver); + +MODULE_AUTHOR("ChiYuan Huang "); +MODULE_DESCRIPTION("Richtek RTQ6056 Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/rzg2l_adc.c b/drivers/iio/adc/rzg2l_adc.c new file mode 100644 index 000000000..408fbf8c2 --- /dev/null +++ b/drivers/iio/adc/rzg2l_adc.c @@ -0,0 +1,605 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * RZ/G2L A/D Converter driver + * + * Copyright (c) 2021 Renesas Electronics Europe GmbH + * + * Author: Lad Prabhakar + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "rzg2l-adc" + +#define RZG2L_ADM(n) ((n) * 0x4) +#define RZG2L_ADM0_ADCE BIT(0) +#define RZG2L_ADM0_ADBSY BIT(1) +#define RZG2L_ADM0_PWDWNB BIT(2) +#define RZG2L_ADM0_SRESB BIT(15) +#define RZG2L_ADM1_TRG BIT(0) +#define RZG2L_ADM1_MS BIT(2) +#define RZG2L_ADM1_BS BIT(4) +#define RZG2L_ADM1_EGA_MASK GENMASK(13, 12) +#define RZG2L_ADM3_ADIL_MASK GENMASK(31, 24) +#define RZG2L_ADM3_ADCMP_MASK GENMASK(23, 16) + +#define RZG2L_ADINT 0x20 +#define RZG2L_ADINT_CSEEN BIT(16) +#define RZG2L_ADINT_INTS BIT(31) + +#define RZG2L_ADSTS 0x24 +#define RZG2L_ADSTS_CSEST BIT(16) + +#define RZG2L_ADIVC 0x28 +#define RZG2L_ADIVC_DIVADC_MASK GENMASK(8, 0) +#define RZG2L_ADIVC_DIVADC_4 FIELD_PREP(RZG2L_ADIVC_DIVADC_MASK, 0x4) + +#define RZG2L_ADFIL 0x2c + +#define RZG2L_ADCR(n) (0x30 + ((n) * 0x4)) +#define RZG2L_ADCR_AD_MASK GENMASK(11, 0) + +#define RZG2L_ADC_MAX_CHANNELS 9 +#define RZG2L_ADC_TIMEOUT usecs_to_jiffies(1 * 4) + +/** + * struct rzg2l_adc_hw_params - ADC hardware specific parameters + * @default_adsmp: default ADC sampling period (see ADM3 register); index 0 is + * used for voltage channels, index 1 is used for temperature channel + * @adsmp_mask: ADC sampling period mask (see ADM3 register) + * @adint_inten_mask: conversion end interrupt mask (see ADINT register) + * @default_adcmp: default ADC cmp (see ADM3 register) + * @num_channels: number of supported channels + * @adivc: specifies if ADVIC register is available + */ +struct rzg2l_adc_hw_params { + u16 default_adsmp[2]; + u16 adsmp_mask; + u16 adint_inten_mask; + u8 default_adcmp; + u8 num_channels; + bool adivc; +}; + +struct rzg2l_adc_data { + const struct iio_chan_spec *channels; + u8 num_channels; +}; + +struct rzg2l_adc { + void __iomem *base; + struct reset_control *presetn; + struct reset_control *adrstn; + const struct rzg2l_adc_data *data; + const struct rzg2l_adc_hw_params *hw_params; + struct completion completion; + struct mutex lock; + u16 last_val[RZG2L_ADC_MAX_CHANNELS]; +}; + +/** + * struct rzg2l_adc_channel - ADC channel descriptor + * @name: ADC channel name + * @type: ADC channel type + */ +struct rzg2l_adc_channel { + const char * const name; + enum iio_chan_type type; +}; + +static const struct rzg2l_adc_channel rzg2l_adc_channels[] = { + { "adc0", IIO_VOLTAGE }, + { "adc1", IIO_VOLTAGE }, + { "adc2", IIO_VOLTAGE }, + { "adc3", IIO_VOLTAGE }, + { "adc4", IIO_VOLTAGE }, + { "adc5", IIO_VOLTAGE }, + { "adc6", IIO_VOLTAGE }, + { "adc7", IIO_VOLTAGE }, + { "adc8", IIO_TEMP }, +}; + +static unsigned int rzg2l_adc_readl(struct rzg2l_adc *adc, u32 reg) +{ + return readl(adc->base + reg); +} + +static void rzg2l_adc_writel(struct rzg2l_adc *adc, unsigned int reg, u32 val) +{ + writel(val, adc->base + reg); +} + +static void rzg2l_adc_pwr(struct rzg2l_adc *adc, bool on) +{ + u32 reg; + + reg = rzg2l_adc_readl(adc, RZG2L_ADM(0)); + if (on) + reg |= RZG2L_ADM0_PWDWNB; + else + reg &= ~RZG2L_ADM0_PWDWNB; + rzg2l_adc_writel(adc, RZG2L_ADM(0), reg); + udelay(2); +} + +static void rzg2l_adc_start_stop(struct rzg2l_adc *adc, bool start) +{ + int ret; + u32 reg; + + reg = rzg2l_adc_readl(adc, RZG2L_ADM(0)); + if (start) + reg |= RZG2L_ADM0_ADCE; + else + reg &= ~RZG2L_ADM0_ADCE; + rzg2l_adc_writel(adc, RZG2L_ADM(0), reg); + + if (start) + return; + + ret = read_poll_timeout(rzg2l_adc_readl, reg, !(reg & (RZG2L_ADM0_ADBSY | RZG2L_ADM0_ADCE)), + 200, 1000, true, adc, RZG2L_ADM(0)); + if (ret) + pr_err("%s stopping ADC timed out\n", __func__); +} + +static void rzg2l_set_trigger(struct rzg2l_adc *adc) +{ + u32 reg; + + /* + * Setup ADM1 for SW trigger + * EGA[13:12] - Set 00 to indicate hardware trigger is invalid + * BS[4] - Enable 1-buffer mode + * MS[1] - Enable Select mode + * TRG[0] - Enable software trigger mode + */ + reg = rzg2l_adc_readl(adc, RZG2L_ADM(1)); + reg &= ~RZG2L_ADM1_EGA_MASK; + reg &= ~RZG2L_ADM1_BS; + reg &= ~RZG2L_ADM1_TRG; + reg |= RZG2L_ADM1_MS; + rzg2l_adc_writel(adc, RZG2L_ADM(1), reg); +} + +static u8 rzg2l_adc_ch_to_adsmp_index(u8 ch) +{ + if (rzg2l_adc_channels[ch].type == IIO_VOLTAGE) + return 0; + + return 1; +} + +static int rzg2l_adc_conversion_setup(struct rzg2l_adc *adc, u8 ch) +{ + const struct rzg2l_adc_hw_params *hw_params = adc->hw_params; + u8 index = rzg2l_adc_ch_to_adsmp_index(ch); + u32 reg; + + if (rzg2l_adc_readl(adc, RZG2L_ADM(0)) & RZG2L_ADM0_ADBSY) + return -EBUSY; + + rzg2l_set_trigger(adc); + + /* Select analog input channel subjected to conversion. */ + reg = rzg2l_adc_readl(adc, RZG2L_ADM(2)); + reg &= ~GENMASK(hw_params->num_channels - 1, 0); + reg |= BIT(ch); + rzg2l_adc_writel(adc, RZG2L_ADM(2), reg); + + reg = rzg2l_adc_readl(adc, RZG2L_ADM(3)); + reg &= ~hw_params->adsmp_mask; + reg |= hw_params->default_adsmp[index]; + rzg2l_adc_writel(adc, RZG2L_ADM(3), reg); + + /* + * Setup ADINT + * INTS[31] - Select pulse signal + * CSEEN[16] - Enable channel select error interrupt + * INTEN[7:0] - Select channel interrupt + */ + reg = rzg2l_adc_readl(adc, RZG2L_ADINT); + reg &= ~RZG2L_ADINT_INTS; + reg &= ~hw_params->adint_inten_mask; + reg |= (RZG2L_ADINT_CSEEN | BIT(ch)); + rzg2l_adc_writel(adc, RZG2L_ADINT, reg); + + return 0; +} + +static int rzg2l_adc_conversion(struct iio_dev *indio_dev, struct rzg2l_adc *adc, u8 ch) +{ + const struct rzg2l_adc_hw_params *hw_params = adc->hw_params; + struct device *dev = indio_dev->dev.parent; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = rzg2l_adc_conversion_setup(adc, ch); + if (ret) + goto rpm_put; + + reinit_completion(&adc->completion); + + rzg2l_adc_start_stop(adc, true); + + if (!wait_for_completion_timeout(&adc->completion, RZG2L_ADC_TIMEOUT)) { + rzg2l_adc_writel(adc, RZG2L_ADINT, + rzg2l_adc_readl(adc, RZG2L_ADINT) & ~hw_params->adint_inten_mask); + ret = -ETIMEDOUT; + } + + rzg2l_adc_start_stop(adc, false); + +rpm_put: + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int rzg2l_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct rzg2l_adc *adc = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + if (chan->type != IIO_VOLTAGE && chan->type != IIO_TEMP) + return -EINVAL; + + guard(mutex)(&adc->lock); + + ret = rzg2l_adc_conversion(indio_dev, adc, chan->channel); + if (ret) + return ret; + + *val = adc->last_val[chan->channel]; + + return IIO_VAL_INT; + } + + default: + return -EINVAL; + } +} + +static int rzg2l_adc_read_label(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + char *label) +{ + return sysfs_emit(label, "%s\n", rzg2l_adc_channels[chan->channel].name); +} + +static const struct iio_info rzg2l_adc_iio_info = { + .read_raw = rzg2l_adc_read_raw, + .read_label = rzg2l_adc_read_label, +}; + +static irqreturn_t rzg2l_adc_isr(int irq, void *dev_id) +{ + struct rzg2l_adc *adc = dev_id; + const struct rzg2l_adc_hw_params *hw_params = adc->hw_params; + unsigned long intst; + u32 reg; + int ch; + + reg = rzg2l_adc_readl(adc, RZG2L_ADSTS); + + /* A/D conversion channel select error interrupt */ + if (reg & RZG2L_ADSTS_CSEST) { + rzg2l_adc_writel(adc, RZG2L_ADSTS, reg); + return IRQ_HANDLED; + } + + intst = reg & GENMASK(hw_params->num_channels - 1, 0); + if (!intst) + return IRQ_NONE; + + for_each_set_bit(ch, &intst, hw_params->num_channels) + adc->last_val[ch] = rzg2l_adc_readl(adc, RZG2L_ADCR(ch)) & RZG2L_ADCR_AD_MASK; + + /* clear the channel interrupt */ + rzg2l_adc_writel(adc, RZG2L_ADSTS, reg); + + complete(&adc->completion); + + return IRQ_HANDLED; +} + +static const struct iio_chan_spec rzg2l_adc_chan_template = { + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), +}; + +static int rzg2l_adc_parse_properties(struct platform_device *pdev, struct rzg2l_adc *adc) +{ + const struct rzg2l_adc_hw_params *hw_params = adc->hw_params; + struct iio_chan_spec *chan_array; + struct rzg2l_adc_data *data; + int num_channels; + u8 i; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + num_channels = devm_iio_adc_device_alloc_chaninfo_se(&pdev->dev, + &rzg2l_adc_chan_template, + hw_params->num_channels - 1, + &chan_array); + if (num_channels < 0) + return num_channels; + + if (num_channels > hw_params->num_channels) + return dev_err_probe(&pdev->dev, -EINVAL, + "num of channel children out of range\n"); + + for (i = 0; i < num_channels; i++) { + int channel = chan_array[i].channel; + + chan_array[i].datasheet_name = rzg2l_adc_channels[channel].name; + chan_array[i].type = rzg2l_adc_channels[channel].type; + } + + data->num_channels = num_channels; + data->channels = chan_array; + adc->data = data; + + return 0; +} + +static int rzg2l_adc_hw_init(struct device *dev, struct rzg2l_adc *adc) +{ + const struct rzg2l_adc_hw_params *hw_params = adc->hw_params; + u32 reg; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + /* SW reset */ + reg = rzg2l_adc_readl(adc, RZG2L_ADM(0)); + reg |= RZG2L_ADM0_SRESB; + rzg2l_adc_writel(adc, RZG2L_ADM(0), reg); + + ret = read_poll_timeout(rzg2l_adc_readl, reg, reg & RZG2L_ADM0_SRESB, + 200, 1000, false, adc, RZG2L_ADM(0)); + if (ret) + goto exit_hw_init; + + if (hw_params->adivc) { + /* Only division by 4 can be set */ + reg = rzg2l_adc_readl(adc, RZG2L_ADIVC); + reg &= ~RZG2L_ADIVC_DIVADC_MASK; + reg |= RZG2L_ADIVC_DIVADC_4; + rzg2l_adc_writel(adc, RZG2L_ADIVC, reg); + } + + /* + * Setup AMD3 + * ADIL[31:24] - Should be always set to 0 + * ADCMP[23:16] - Should be always set to 0xe + * ADSMP[15:0] - Set default (0x578) sampling period + */ + reg = rzg2l_adc_readl(adc, RZG2L_ADM(3)); + reg &= ~RZG2L_ADM3_ADIL_MASK; + reg &= ~RZG2L_ADM3_ADCMP_MASK; + reg &= ~hw_params->adsmp_mask; + reg |= FIELD_PREP(RZG2L_ADM3_ADCMP_MASK, hw_params->default_adcmp) | + hw_params->default_adsmp[0]; + + rzg2l_adc_writel(adc, RZG2L_ADM(3), reg); + +exit_hw_init: + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int rzg2l_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct rzg2l_adc *adc; + int ret; + int irq; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + platform_set_drvdata(pdev, indio_dev); + + adc = iio_priv(indio_dev); + + adc->hw_params = device_get_match_data(dev); + if (!adc->hw_params || adc->hw_params->num_channels > RZG2L_ADC_MAX_CHANNELS) + return -EINVAL; + + ret = rzg2l_adc_parse_properties(pdev, adc); + if (ret) + return ret; + + mutex_init(&adc->lock); + + adc->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(adc->base)) + return PTR_ERR(adc->base); + + adc->adrstn = devm_reset_control_get_exclusive_deasserted(dev, "adrst-n"); + if (IS_ERR(adc->adrstn)) + return dev_err_probe(dev, PTR_ERR(adc->adrstn), + "failed to get/deassert adrst-n\n"); + + adc->presetn = devm_reset_control_get_exclusive_deasserted(dev, "presetn"); + if (IS_ERR(adc->presetn)) + return dev_err_probe(dev, PTR_ERR(adc->presetn), + "failed to get/deassert presetn\n"); + + pm_runtime_set_autosuspend_delay(dev, 300); + pm_runtime_use_autosuspend(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + ret = rzg2l_adc_hw_init(dev, adc); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to initialize ADC HW\n"); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, rzg2l_adc_isr, + 0, dev_name(dev), adc); + if (ret < 0) + return ret; + + init_completion(&adc->completion); + + indio_dev->name = DRIVER_NAME; + indio_dev->info = &rzg2l_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = adc->data->channels; + indio_dev->num_channels = adc->data->num_channels; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct rzg2l_adc_hw_params rzg2l_hw_params = { + .num_channels = 8, + .default_adcmp = 0xe, + .default_adsmp = { 0x578 }, + .adsmp_mask = GENMASK(15, 0), + .adint_inten_mask = GENMASK(7, 0), + .adivc = true +}; + +static const struct rzg2l_adc_hw_params rzg3s_hw_params = { + .num_channels = 9, + .default_adcmp = 0x1d, + .default_adsmp = { 0x7f, 0xff }, + .adsmp_mask = GENMASK(7, 0), + .adint_inten_mask = GENMASK(11, 0), +}; + +static const struct of_device_id rzg2l_adc_match[] = { + { .compatible = "renesas,r9a08g045-adc", .data = &rzg3s_hw_params }, + { .compatible = "renesas,rzg2l-adc", .data = &rzg2l_hw_params }, + { } +}; +MODULE_DEVICE_TABLE(of, rzg2l_adc_match); + +static int rzg2l_adc_pm_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct rzg2l_adc *adc = iio_priv(indio_dev); + + rzg2l_adc_pwr(adc, false); + + return 0; +} + +static int rzg2l_adc_pm_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct rzg2l_adc *adc = iio_priv(indio_dev); + + rzg2l_adc_pwr(adc, true); + + return 0; +} + +static int rzg2l_adc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct rzg2l_adc *adc = iio_priv(indio_dev); + struct reset_control_bulk_data resets[] = { + { .rstc = adc->presetn }, + { .rstc = adc->adrstn }, + }; + int ret; + + ret = pm_runtime_force_suspend(dev); + if (ret) + return ret; + + ret = reset_control_bulk_assert(ARRAY_SIZE(resets), resets); + if (ret) + goto rpm_restore; + + return 0; + +rpm_restore: + pm_runtime_force_resume(dev); + return ret; +} + +static int rzg2l_adc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct rzg2l_adc *adc = iio_priv(indio_dev); + struct reset_control_bulk_data resets[] = { + { .rstc = adc->adrstn }, + { .rstc = adc->presetn }, + }; + int ret; + + ret = reset_control_bulk_deassert(ARRAY_SIZE(resets), resets); + if (ret) + return ret; + + ret = pm_runtime_force_resume(dev); + if (ret) + goto resets_restore; + + ret = rzg2l_adc_hw_init(dev, adc); + if (ret) + goto rpm_restore; + + return 0; + +rpm_restore: + pm_runtime_force_suspend(dev); +resets_restore: + reset_control_bulk_assert(ARRAY_SIZE(resets), resets); + return ret; +} + +static const struct dev_pm_ops rzg2l_adc_pm_ops = { + RUNTIME_PM_OPS(rzg2l_adc_pm_runtime_suspend, rzg2l_adc_pm_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(rzg2l_adc_suspend, rzg2l_adc_resume) +}; + +static struct platform_driver rzg2l_adc_driver = { + .probe = rzg2l_adc_probe, + .driver = { + .name = DRIVER_NAME, + .of_match_table = rzg2l_adc_match, + .pm = pm_ptr(&rzg2l_adc_pm_ops), + }, +}; + +module_platform_driver(rzg2l_adc_driver); + +MODULE_AUTHOR("Lad Prabhakar "); +MODULE_DESCRIPTION("Renesas RZ/G2L ADC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_DRIVER"); diff --git a/drivers/iio/adc/rzn1-adc.c b/drivers/iio/adc/rzn1-adc.c new file mode 100644 index 000000000..f921cd49b --- /dev/null +++ b/drivers/iio/adc/rzn1-adc.c @@ -0,0 +1,489 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas RZ/N1 ADC driver + * + * Copyright (C) 2025 Schneider-Electric + * + * Author: Herve Codina + * + * The RZ/N1 ADC controller can handle channels from its internal ADC1 and/or + * ADC2 cores. The driver use ADC1 and/or ADC2 cores depending on the presence + * of the related power supplies (AVDD and VREF) description in the device-tree. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RZN1_ADC_CONTROL_REG 0x02c +#define RZN1_ADC_CONTROL_ADC_BUSY BIT(6) + +#define RZN1_ADC_FORCE_REG 0x030 +#define RZN1_ADC_SET_FORCE_REG 0x034 +#define RZN1_ADC_CLEAR_FORCE_REG 0x038 +#define RZN1_ADC_FORCE_VC(_n) BIT(_n) + +#define RZN1_ADC_CONFIG_REG 0x040 +#define RZN1_ADC_CONFIG_ADC_POWER_DOWN BIT(3) + +#define RZN1_ADC_VC_REG(_n) (0x0c0 + 4 * (_n)) +#define RZN1_ADC_VC_ADC2_ENABLE BIT(16) +#define RZN1_ADC_VC_ADC1_ENABLE BIT(15) +#define RZN1_ADC_VC_ADC2_CHANNEL_SEL_MASK GENMASK(5, 3) +#define RZN1_ADC_VC_ADC1_CHANNEL_SEL_MASK GENMASK(2, 0) + +#define RZN1_ADC_ADC1_DATA_REG(_n) (0x100 + 4 * (_n)) +#define RZN1_ADC_ADC2_DATA_REG(_n) (0x140 + 4 * (_n)) +#define RZN1_ADC_ADCX_DATA_DATA_MASK GENMASK(11, 0) + +#define RZN1_ADC_NO_CHANNEL -1 + +#define RZN1_ADC_CHANNEL_SHARED_SCALE(_ch, _ds_name) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_ch), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = (_ds_name), \ +} + +#define RZN1_ADC_CHANNEL_SEPARATED_SCALE(_ch, _ds_name) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_ch), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = (_ds_name), \ +} + +/* + * 8 ADC1_IN signals existed numbered 0..4, 6..8 + * ADCx_IN5 doesn't exist in RZ/N1 datasheet + */ +static struct iio_chan_spec rzn1_adc1_channels[] = { + RZN1_ADC_CHANNEL_SHARED_SCALE(0, "ADC1_IN0"), + RZN1_ADC_CHANNEL_SHARED_SCALE(1, "ADC1_IN1"), + RZN1_ADC_CHANNEL_SHARED_SCALE(2, "ADC1_IN2"), + RZN1_ADC_CHANNEL_SHARED_SCALE(3, "ADC1_IN3"), + RZN1_ADC_CHANNEL_SHARED_SCALE(4, "ADC1_IN4"), + RZN1_ADC_CHANNEL_SHARED_SCALE(5, "ADC1_IN6"), + RZN1_ADC_CHANNEL_SHARED_SCALE(6, "ADC1_IN7"), + RZN1_ADC_CHANNEL_SHARED_SCALE(7, "ADC1_IN8"), +}; + +static struct iio_chan_spec rzn1_adc2_channels[] = { + RZN1_ADC_CHANNEL_SHARED_SCALE(8, "ADC2_IN0"), + RZN1_ADC_CHANNEL_SHARED_SCALE(9, "ADC2_IN1"), + RZN1_ADC_CHANNEL_SHARED_SCALE(10, "ADC2_IN2"), + RZN1_ADC_CHANNEL_SHARED_SCALE(11, "ADC2_IN3"), + RZN1_ADC_CHANNEL_SHARED_SCALE(12, "ADC2_IN4"), + RZN1_ADC_CHANNEL_SHARED_SCALE(13, "ADC2_IN6"), + RZN1_ADC_CHANNEL_SHARED_SCALE(14, "ADC2_IN7"), + RZN1_ADC_CHANNEL_SHARED_SCALE(15, "ADC2_IN8"), +}; + +/* + * If both ADCs core are used, scale cannot be common. Indeed, scale is + * based on Vref connected on each ADC core. + */ +static struct iio_chan_spec rzn1_adc1_adc2_channels[] = { + RZN1_ADC_CHANNEL_SEPARATED_SCALE(0, "ADC1_IN0"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(1, "ADC1_IN1"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(2, "ADC1_IN2"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(3, "ADC1_IN3"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(4, "ADC1_IN4"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(5, "ADC1_IN6"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(6, "ADC1_IN7"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(7, "ADC1_IN8"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(8, "ADC2_IN0"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(9, "ADC2_IN1"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(10, "ADC2_IN2"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(11, "ADC2_IN3"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(12, "ADC2_IN4"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(13, "ADC2_IN6"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(14, "ADC2_IN7"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(15, "ADC2_IN8"), +}; + +struct rzn1_adc { + struct device *dev; + void __iomem *regs; + struct mutex lock; /* ADC lock */ + int adc1_vref_mV; /* ADC1 Vref in mV. Negative if ADC1 is not used */ + int adc2_vref_mV; /* ADC2 Vref in mV. Negative if ADC2 is not used */ +}; + +static int rzn1_adc_power(struct rzn1_adc *rzn1_adc, bool power) +{ + u32 v; + + writel(power ? 0 : RZN1_ADC_CONFIG_ADC_POWER_DOWN, + rzn1_adc->regs + RZN1_ADC_CONFIG_REG); + + /* Wait for the ADC_BUSY to clear */ + return readl_poll_timeout_atomic(rzn1_adc->regs + RZN1_ADC_CONTROL_REG, + v, !(v & RZN1_ADC_CONTROL_ADC_BUSY), + 0, 500); +} + +static void rzn1_adc_vc_setup_conversion(struct rzn1_adc *rzn1_adc, u32 ch, + int adc1_ch, int adc2_ch) +{ + u32 vc = 0; + + if (adc1_ch != RZN1_ADC_NO_CHANNEL) + vc |= RZN1_ADC_VC_ADC1_ENABLE | + FIELD_PREP(RZN1_ADC_VC_ADC1_CHANNEL_SEL_MASK, adc1_ch); + + if (adc2_ch != RZN1_ADC_NO_CHANNEL) + vc |= RZN1_ADC_VC_ADC2_ENABLE | + FIELD_PREP(RZN1_ADC_VC_ADC2_CHANNEL_SEL_MASK, adc2_ch); + + writel(vc, rzn1_adc->regs + RZN1_ADC_VC_REG(ch)); +} + +static int rzn1_adc_vc_start_conversion(struct rzn1_adc *rzn1_adc, u32 ch) +{ + u32 val; + + val = readl(rzn1_adc->regs + RZN1_ADC_FORCE_REG); + if (val & RZN1_ADC_FORCE_VC(ch)) + return -EBUSY; + + writel(RZN1_ADC_FORCE_VC(ch), rzn1_adc->regs + RZN1_ADC_SET_FORCE_REG); + + return 0; +} + +static void rzn1_adc_vc_stop_conversion(struct rzn1_adc *rzn1_adc, u32 ch) +{ + writel(RZN1_ADC_FORCE_VC(ch), rzn1_adc->regs + RZN1_ADC_CLEAR_FORCE_REG); +} + +static int rzn1_adc_vc_wait_conversion(struct rzn1_adc *rzn1_adc, u32 ch, + u32 *adc1_data, u32 *adc2_data) +{ + u32 data_reg; + int ret; + u32 v; + + /* + * When a VC is selected, it needs 20 ADC clocks to perform the + * conversion. + * + * The worst case is when the 16 VCs need to perform a conversion and + * our VC is the lowest in term of priority. + * + * In that case, the conversion is performed in 16 * 20 ADC clocks. + * + * The ADC clock can be set from 4MHz to 20MHz. This leads to a worst + * case of 16 * 20 * 1/4Mhz = 80us. + * + * Round it up to 100us. + */ + + /* Wait for the ADC_FORCE_VC(n) to clear */ + ret = readl_poll_timeout_atomic(rzn1_adc->regs + RZN1_ADC_FORCE_REG, + v, !(v & RZN1_ADC_FORCE_VC(ch)), + 0, 100); + if (ret) + return ret; + + if (adc1_data) { + data_reg = readl(rzn1_adc->regs + RZN1_ADC_ADC1_DATA_REG(ch)); + *adc1_data = FIELD_GET(RZN1_ADC_ADCX_DATA_DATA_MASK, data_reg); + } + + if (adc2_data) { + data_reg = readl(rzn1_adc->regs + RZN1_ADC_ADC2_DATA_REG(ch)); + *adc2_data = FIELD_GET(RZN1_ADC_ADCX_DATA_DATA_MASK, data_reg); + } + + return 0; +} + +static int rzn1_adc_read_raw_ch(struct rzn1_adc *rzn1_adc, unsigned int chan, int *val) +{ + u32 *adc1_data, *adc2_data; + int adc1_ch, adc2_ch; + u32 adc_data; + int ret; + + /* + * IIO chan are decoupled from chans used in rzn1_adc_vc_*() functions. + * The RZ/N1 ADC VC controller can handle on a single VC chan one + * channel from the ADC1 core and one channel from the ADC2 core. + * + * Even if IIO chans are mapped 1:1 to ADC core chans and so uses only + * a chan from ADC1 or a chan from ADC2, future improvements can define + * an IIO chan that uses one chan from ADC1 and one chan from ADC2. + */ + + if (chan < 8) { + /* chan 0..7 used to get ADC1 ch 0..7 */ + adc1_ch = chan; + adc1_data = &adc_data; + adc2_ch = RZN1_ADC_NO_CHANNEL; + adc2_data = NULL; + } else if (chan < 16) { + /* chan 8..15 used to get ADC2 ch 0..7 */ + adc1_ch = RZN1_ADC_NO_CHANNEL; + adc1_data = NULL; + adc2_ch = chan - 8; + adc2_data = &adc_data; + } else { + return -EINVAL; + } + + ACQUIRE(pm_runtime_active_auto_try_enabled, pm)(rzn1_adc->dev); + ret = ACQUIRE_ERR(pm_runtime_active_auto_try_enabled, &pm); + if (ret < 0) + return ret; + + scoped_guard(mutex, &rzn1_adc->lock) { + rzn1_adc_vc_setup_conversion(rzn1_adc, chan, adc1_ch, adc2_ch); + + ret = rzn1_adc_vc_start_conversion(rzn1_adc, chan); + if (ret) + return ret; + + ret = rzn1_adc_vc_wait_conversion(rzn1_adc, chan, adc1_data, adc2_data); + if (ret) { + rzn1_adc_vc_stop_conversion(rzn1_adc, chan); + return ret; + } + } + + *val = adc_data; + ret = IIO_VAL_INT; + + return 0; +} + +static int rzn1_adc_get_vref_mV(struct rzn1_adc *rzn1_adc, unsigned int chan) +{ + /* chan 0..7 use ADC1 ch 0..7. Vref related to ADC1 core */ + if (chan < 8) + return rzn1_adc->adc1_vref_mV; + + /* chan 8..15 use ADC2 ch 0..7. Vref related to ADC2 core */ + if (chan < 16) + return rzn1_adc->adc2_vref_mV; + + return -EINVAL; +} + +static int rzn1_adc_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct rzn1_adc *rzn1_adc = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = rzn1_adc_read_raw_ch(rzn1_adc, chan->channel, val); + if (ret) + return ret; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + ret = rzn1_adc_get_vref_mV(rzn1_adc, chan->channel); + if (ret < 0) + return ret; + *val = ret; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } +} + +static const struct iio_info rzn1_adc_info = { + .read_raw = &rzn1_adc_read_raw, +}; + +static int rzn1_adc_set_iio_dev_channels(struct rzn1_adc *rzn1_adc, + struct iio_dev *indio_dev) +{ + /* + * When an ADC core is not used, its related vref_mV is set to a + * negative error code. Use the correct IIO channels table based on + * those vref_mV values. + */ + if (rzn1_adc->adc1_vref_mV >= 0) { + if (rzn1_adc->adc2_vref_mV >= 0) { + indio_dev->channels = rzn1_adc1_adc2_channels; + indio_dev->num_channels = ARRAY_SIZE(rzn1_adc1_adc2_channels); + } else { + indio_dev->channels = rzn1_adc1_channels; + indio_dev->num_channels = ARRAY_SIZE(rzn1_adc1_channels); + } + return 0; + } + + if (rzn1_adc->adc2_vref_mV >= 0) { + indio_dev->channels = rzn1_adc2_channels; + indio_dev->num_channels = ARRAY_SIZE(rzn1_adc2_channels); + return 0; + } + + return dev_err_probe(rzn1_adc->dev, -ENODEV, + "Failed to set IIO channels, no ADC core used\n"); +} + +static int rzn1_adc_core_get_regulators(struct rzn1_adc *rzn1_adc, + int *adc_vref_mV, + const char *avdd_name, const char *vref_name) +{ + struct device *dev = rzn1_adc->dev; + int ret; + + /* + * For a given ADC core (ADC1 or ADC2), both regulators (AVDD and VREF) + * must be available in order to have the ADC core used. + * + * We use the regulators presence to check the usage of the related + * ADC core. If both regulators are available, the ADC core is used. + * Otherwise, the ADC core is not used. + * + * The adc_vref_mV value is set to a negative error code (-ENODEV) when + * the ADC core is not used. Otherwise it is set to the VRef mV value. + */ + + *adc_vref_mV = -ENODEV; + + ret = devm_regulator_get_enable_optional(dev, avdd_name); + if (ret == -ENODEV) + return 0; + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get '%s' regulator\n", + avdd_name); + + ret = devm_regulator_get_enable_read_voltage(dev, vref_name); + if (ret == -ENODEV) + return 0; + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get '%s' regulator\n", + vref_name); + + /* + * Both regulators are available. + * Set adc_vref_mV to the Vref value in mV. This, as the value set is + * positive, also signals that the ADC is used. + */ + *adc_vref_mV = ret / 1000; + + return 0; +} + +static int rzn1_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct rzn1_adc *rzn1_adc; + struct clk *clk; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*rzn1_adc)); + if (!indio_dev) + return -ENOMEM; + + rzn1_adc = iio_priv(indio_dev); + rzn1_adc->dev = dev; + + ret = devm_mutex_init(dev, &rzn1_adc->lock); + if (ret) + return ret; + + rzn1_adc->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(rzn1_adc->regs)) + return PTR_ERR(rzn1_adc->regs); + + clk = devm_clk_get_enabled(dev, "pclk"); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "Failed to get pclk\n"); + + clk = devm_clk_get_enabled(dev, "adc"); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "Failed to get adc clk\n"); + + ret = rzn1_adc_core_get_regulators(rzn1_adc, &rzn1_adc->adc1_vref_mV, + "adc1-avdd", "adc1-vref"); + if (ret) + return ret; + + ret = rzn1_adc_core_get_regulators(rzn1_adc, &rzn1_adc->adc2_vref_mV, + "adc2-avdd", "adc2-vref"); + if (ret) + return ret; + + platform_set_drvdata(pdev, rzn1_adc); + + indio_dev->name = "rzn1-adc"; + indio_dev->info = &rzn1_adc_info; + indio_dev->modes = INDIO_DIRECT_MODE; + ret = rzn1_adc_set_iio_dev_channels(rzn1_adc, indio_dev); + if (ret) + return ret; + + pm_runtime_set_autosuspend_delay(dev, 500); + pm_runtime_use_autosuspend(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable runtime PM\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static int rzn1_adc_pm_runtime_suspend(struct device *dev) +{ + struct rzn1_adc *rzn1_adc = dev_get_drvdata(dev); + + return rzn1_adc_power(rzn1_adc, false); +} + +static int rzn1_adc_pm_runtime_resume(struct device *dev) +{ + struct rzn1_adc *rzn1_adc = dev_get_drvdata(dev); + + return rzn1_adc_power(rzn1_adc, true); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(rzn1_adc_pm_ops, + rzn1_adc_pm_runtime_suspend, + rzn1_adc_pm_runtime_resume, + NULL); + +static const struct of_device_id rzn1_adc_of_match[] = { + { .compatible = "renesas,rzn1-adc" }, + { } +}; +MODULE_DEVICE_TABLE(of, rzn1_adc_of_match); + +static struct platform_driver rzn1_adc_driver = { + .probe = rzn1_adc_probe, + .driver = { + .name = "rzn1-adc", + .of_match_table = rzn1_adc_of_match, + .pm = pm_ptr(&rzn1_adc_pm_ops), + }, +}; +module_platform_driver(rzn1_adc_driver); + +MODULE_AUTHOR("Herve Codina "); +MODULE_DESCRIPTION("Renesas RZ/N1 ADC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/rzt2h_adc.c b/drivers/iio/adc/rzt2h_adc.c new file mode 100644 index 000000000..4e0eb02d3 --- /dev/null +++ b/drivers/iio/adc/rzt2h_adc.c @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RZT2H_ADCSR_REG 0x00 +#define RZT2H_ADCSR_ADIE_MASK BIT(12) +#define RZT2H_ADCSR_ADCS_MASK GENMASK(14, 13) +#define RZT2H_ADCSR_ADCS_SINGLE 0b00 +#define RZT2H_ADCSR_ADST_MASK BIT(15) + +#define RZT2H_ADANSA0_REG 0x04 +#define RZT2H_ADANSA0_CH_MASK(x) BIT(x) + +#define RZT2H_ADDR_REG(x) (0x20 + 0x2 * (x)) + +#define RZT2H_ADCALCTL_REG 0x1f0 +#define RZT2H_ADCALCTL_CAL_MASK BIT(0) +#define RZT2H_ADCALCTL_CAL_RDY_MASK BIT(1) +#define RZT2H_ADCALCTL_CAL_ERR_MASK BIT(2) + +#define RZT2H_ADC_MAX_CHANNELS 16 + +struct rzt2h_adc { + void __iomem *base; + struct device *dev; + + struct completion completion; + /* lock to protect against multiple access to the device */ + struct mutex lock; + + const struct iio_chan_spec *channels; + unsigned int num_channels; + unsigned int max_channels; +}; + +static void rzt2h_adc_start(struct rzt2h_adc *adc, unsigned int conversion_type) +{ + u16 reg; + + reg = readw(adc->base + RZT2H_ADCSR_REG); + + /* Set conversion type */ + FIELD_MODIFY(RZT2H_ADCSR_ADCS_MASK, ®, conversion_type); + + /* Set end of conversion interrupt and start bit. */ + reg |= RZT2H_ADCSR_ADIE_MASK | RZT2H_ADCSR_ADST_MASK; + + writew(reg, adc->base + RZT2H_ADCSR_REG); +} + +static void rzt2h_adc_stop(struct rzt2h_adc *adc) +{ + u16 reg; + + reg = readw(adc->base + RZT2H_ADCSR_REG); + + /* Clear end of conversion interrupt and start bit. */ + reg &= ~(RZT2H_ADCSR_ADIE_MASK | RZT2H_ADCSR_ADST_MASK); + + writew(reg, adc->base + RZT2H_ADCSR_REG); +} + +static int rzt2h_adc_read_single(struct rzt2h_adc *adc, unsigned int ch, int *val) +{ + int ret; + + ret = pm_runtime_resume_and_get(adc->dev); + if (ret) + return ret; + + mutex_lock(&adc->lock); + + reinit_completion(&adc->completion); + + /* Enable a single channel */ + writew(RZT2H_ADANSA0_CH_MASK(ch), adc->base + RZT2H_ADANSA0_REG); + + rzt2h_adc_start(adc, RZT2H_ADCSR_ADCS_SINGLE); + + /* + * Datasheet Page 2770, Table 41.1: + * 0.32us per channel when sample-and-hold circuits are not in use. + */ + ret = wait_for_completion_timeout(&adc->completion, usecs_to_jiffies(1)); + if (!ret) { + ret = -ETIMEDOUT; + goto disable; + } + + *val = readw(adc->base + RZT2H_ADDR_REG(ch)); + ret = IIO_VAL_INT; + +disable: + rzt2h_adc_stop(adc); + + mutex_unlock(&adc->lock); + + pm_runtime_put_autosuspend(adc->dev); + + return ret; +} + +static void rzt2h_adc_set_cal(struct rzt2h_adc *adc, bool cal) +{ + u16 val; + + val = readw(adc->base + RZT2H_ADCALCTL_REG); + if (cal) + val |= RZT2H_ADCALCTL_CAL_MASK; + else + val &= ~RZT2H_ADCALCTL_CAL_MASK; + + writew(val, adc->base + RZT2H_ADCALCTL_REG); +} + +static int rzt2h_adc_calibrate(struct rzt2h_adc *adc) +{ + u16 val; + int ret; + + rzt2h_adc_set_cal(adc, true); + + ret = read_poll_timeout(readw, val, val & RZT2H_ADCALCTL_CAL_RDY_MASK, + 200, 1000, true, adc->base + RZT2H_ADCALCTL_REG); + if (ret) { + dev_err(adc->dev, "Calibration timed out: %d\n", ret); + return ret; + } + + rzt2h_adc_set_cal(adc, false); + + if (val & RZT2H_ADCALCTL_CAL_ERR_MASK) { + dev_err(adc->dev, "Calibration failed\n"); + return -EINVAL; + } + + return 0; +} + +static int rzt2h_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct rzt2h_adc *adc = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return rzt2h_adc_read_single(adc, chan->channel, val); + case IIO_CHAN_INFO_SCALE: + *val = 1800; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static const struct iio_info rzt2h_adc_iio_info = { + .read_raw = rzt2h_adc_read_raw, +}; + +static irqreturn_t rzt2h_adc_isr(int irq, void *private) +{ + struct rzt2h_adc *adc = private; + + complete(&adc->completion); + + return IRQ_HANDLED; +} + +static const struct iio_chan_spec rzt2h_adc_chan_template = { + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .type = IIO_VOLTAGE, +}; + +static int rzt2h_adc_parse_properties(struct rzt2h_adc *adc) +{ + struct iio_chan_spec *chan_array; + unsigned int i; + int ret; + + ret = devm_iio_adc_device_alloc_chaninfo_se(adc->dev, + &rzt2h_adc_chan_template, + RZT2H_ADC_MAX_CHANNELS - 1, + &chan_array); + if (ret < 0) + return dev_err_probe(adc->dev, ret, "Failed to read channel info"); + + adc->num_channels = ret; + adc->channels = chan_array; + + for (i = 0; i < adc->num_channels; i++) + if (chan_array[i].channel + 1 > adc->max_channels) + adc->max_channels = chan_array[i].channel + 1; + + return 0; +} + +static int rzt2h_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct rzt2h_adc *adc; + int ret, irq; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->dev = dev; + init_completion(&adc->completion); + + ret = devm_mutex_init(dev, &adc->lock); + if (ret) + return ret; + + platform_set_drvdata(pdev, adc); + + ret = rzt2h_adc_parse_properties(adc); + if (ret) + return ret; + + adc->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(adc->base)) + return PTR_ERR(adc->base); + + pm_runtime_set_autosuspend_delay(dev, 300); + pm_runtime_use_autosuspend(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + irq = platform_get_irq_byname(pdev, "adi"); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, rzt2h_adc_isr, 0, dev_name(dev), adc); + if (ret) + return ret; + + indio_dev->name = "rzt2h-adc"; + indio_dev->info = &rzt2h_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = adc->channels; + indio_dev->num_channels = adc->num_channels; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id rzt2h_adc_match[] = { + { .compatible = "renesas,r9a09g077-adc" }, + { } +}; +MODULE_DEVICE_TABLE(of, rzt2h_adc_match); + +static int rzt2h_adc_pm_runtime_resume(struct device *dev) +{ + struct rzt2h_adc *adc = dev_get_drvdata(dev); + + /* + * Datasheet Page 2810, Section 41.5.6: + * After release from the module-stop state, wait for at least + * 0.5 µs before starting A/D conversion. + */ + fsleep(1); + + return rzt2h_adc_calibrate(adc); +} + +static const struct dev_pm_ops rzt2h_adc_pm_ops = { + RUNTIME_PM_OPS(NULL, rzt2h_adc_pm_runtime_resume, NULL) +}; + +static struct platform_driver rzt2h_adc_driver = { + .probe = rzt2h_adc_probe, + .driver = { + .name = "rzt2h-adc", + .of_match_table = rzt2h_adc_match, + .pm = pm_ptr(&rzt2h_adc_pm_ops), + }, +}; + +module_platform_driver(rzt2h_adc_driver); + +MODULE_AUTHOR("Cosmin Tanislav "); +MODULE_DESCRIPTION("Renesas RZ/T2H / RZ/N2H ADC driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_DRIVER"); diff --git a/drivers/iio/adc/sc27xx_adc.c b/drivers/iio/adc/sc27xx_adc.c new file mode 100644 index 000000000..6209499c5 --- /dev/null +++ b/drivers/iio/adc/sc27xx_adc.c @@ -0,0 +1,954 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2018 Spreadtrum Communications Inc. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* PMIC global registers definition */ +#define SC2730_MODULE_EN 0x1808 +#define SC2731_MODULE_EN 0xc08 +#define SC27XX_MODULE_ADC_EN BIT(5) +#define SC2721_ARM_CLK_EN 0xc0c +#define SC2730_ARM_CLK_EN 0x180c +#define SC2731_ARM_CLK_EN 0xc10 +#define SC27XX_CLK_ADC_EN BIT(5) +#define SC27XX_CLK_ADC_CLK_EN BIT(6) + +/* ADC controller registers definition */ +#define SC27XX_ADC_CTL 0x0 +#define SC27XX_ADC_CH_CFG 0x4 +#define SC27XX_ADC_DATA 0x4c +#define SC27XX_ADC_INT_EN 0x50 +#define SC27XX_ADC_INT_CLR 0x54 +#define SC27XX_ADC_INT_STS 0x58 +#define SC27XX_ADC_INT_RAW 0x5c + +/* Bits and mask definition for SC27XX_ADC_CTL register */ +#define SC27XX_ADC_EN BIT(0) +#define SC27XX_ADC_CHN_RUN BIT(1) +#define SC27XX_ADC_12BIT_MODE BIT(2) +#define SC27XX_ADC_RUN_NUM_MASK GENMASK(7, 4) +#define SC27XX_ADC_RUN_NUM_SHIFT 4 + +/* Bits and mask definition for SC27XX_ADC_CH_CFG register */ +#define SC27XX_ADC_CHN_ID_MASK GENMASK(4, 0) +#define SC27XX_ADC_SCALE_MASK GENMASK(10, 9) +#define SC2721_ADC_SCALE_MASK BIT(5) +#define SC27XX_ADC_SCALE_SHIFT 9 +#define SC2721_ADC_SCALE_SHIFT 5 + +/* Bits definitions for SC27XX_ADC_INT_EN registers */ +#define SC27XX_ADC_IRQ_EN BIT(0) + +/* Bits definitions for SC27XX_ADC_INT_CLR registers */ +#define SC27XX_ADC_IRQ_CLR BIT(0) + +/* Bits definitions for SC27XX_ADC_INT_RAW registers */ +#define SC27XX_ADC_IRQ_RAW BIT(0) + +/* Mask definition for SC27XX_ADC_DATA register */ +#define SC27XX_ADC_DATA_MASK GENMASK(11, 0) + +/* Timeout (ms) for the trylock of hardware spinlocks */ +#define SC27XX_ADC_HWLOCK_TIMEOUT 5000 + +/* Timeout (us) for ADC data conversion according to ADC datasheet */ +#define SC27XX_ADC_RDY_TIMEOUT 1000000 +#define SC27XX_ADC_POLL_RAW_STATUS 500 + +/* Maximum ADC channel number */ +#define SC27XX_ADC_CHANNEL_MAX 32 + +/* ADC voltage ratio definition */ +#define SC27XX_VOLT_RATIO(n, d) \ + (((n) << SC27XX_RATIO_NUMERATOR_OFFSET) | (d)) +#define SC27XX_RATIO_NUMERATOR_OFFSET 16 +#define SC27XX_RATIO_DENOMINATOR_MASK GENMASK(15, 0) + +/* ADC specific channel reference voltage 3.5V */ +#define SC27XX_ADC_REFVOL_VDD35 3500000 + +/* ADC default channel reference voltage is 2.8V */ +#define SC27XX_ADC_REFVOL_VDD28 2800000 + +struct sc27xx_adc_data { + struct device *dev; + struct regulator *volref; + struct regmap *regmap; + /* lock to protect against multiple access to the device */ + struct mutex lock; + /* + * One hardware spinlock to synchronize between the multiple + * subsystems which will access the unique ADC controller. + */ + struct hwspinlock *hwlock; + int channel_scale[SC27XX_ADC_CHANNEL_MAX]; + u32 base; + int irq; + const struct sc27xx_adc_variant_data *var_data; +}; + +/* + * Since different PMICs of SC27xx series can have different + * address and ratio, we should save ratio config and base + * in the device data structure. + */ +struct sc27xx_adc_variant_data { + u32 module_en; + u32 clk_en; + u32 scale_shift; + u32 scale_mask; + const struct sc27xx_adc_linear_graph *bscale_cal; + const struct sc27xx_adc_linear_graph *sscale_cal; + void (*init_scale)(struct sc27xx_adc_data *data); + int (*get_ratio)(int channel, int scale); + bool set_volref; +}; + +struct sc27xx_adc_linear_graph { + int volt0; + int adc0; + int volt1; + int adc1; +}; + +/* + * According to the datasheet, we can convert one ADC value to one voltage value + * through 2 points in the linear graph. If the voltage is less than 1.2v, we + * should use the small-scale graph, and if more than 1.2v, we should use the + * big-scale graph. + */ +static struct sc27xx_adc_linear_graph big_scale_graph = { + 4200, 3310, + 3600, 2832, +}; + +static struct sc27xx_adc_linear_graph small_scale_graph = { + 1000, 3413, + 100, 341, +}; + +static const struct sc27xx_adc_linear_graph sc2731_big_scale_graph_calib = { + 4200, 850, + 3600, 728, +}; + +static const struct sc27xx_adc_linear_graph sc2731_small_scale_graph_calib = { + 1000, 838, + 100, 84, +}; + +static const struct sc27xx_adc_linear_graph big_scale_graph_calib = { + 4200, 856, + 3600, 733, +}; + +static const struct sc27xx_adc_linear_graph small_scale_graph_calib = { + 1000, 833, + 100, 80, +}; + +static int sc27xx_adc_get_calib_data(u32 calib_data, int calib_adc) +{ + return ((calib_data & 0xff) + calib_adc - 128) * 4; +} + +/* get the adc nvmem cell calibration data */ +static int adc_nvmem_cell_calib_data(struct sc27xx_adc_data *data, const char *cell_name) +{ + struct nvmem_cell *cell; + void *buf; + u32 origin_calib_data = 0; + size_t len; + + if (!data) + return -EINVAL; + + cell = nvmem_cell_get(data->dev, cell_name); + if (IS_ERR(cell)) + return PTR_ERR(cell); + + buf = nvmem_cell_read(cell, &len); + if (IS_ERR(buf)) { + nvmem_cell_put(cell); + return PTR_ERR(buf); + } + + memcpy(&origin_calib_data, buf, min(len, sizeof(u32))); + + kfree(buf); + nvmem_cell_put(cell); + return origin_calib_data; +} + +static int sc27xx_adc_scale_calibration(struct sc27xx_adc_data *data, + bool big_scale) +{ + const struct sc27xx_adc_linear_graph *calib_graph; + struct sc27xx_adc_linear_graph *graph; + const char *cell_name; + u32 calib_data = 0; + + if (big_scale) { + calib_graph = data->var_data->bscale_cal; + graph = &big_scale_graph; + cell_name = "big_scale_calib"; + } else { + calib_graph = data->var_data->sscale_cal; + graph = &small_scale_graph; + cell_name = "small_scale_calib"; + } + + calib_data = adc_nvmem_cell_calib_data(data, cell_name); + + /* Only need to calibrate the adc values in the linear graph. */ + graph->adc0 = sc27xx_adc_get_calib_data(calib_data, calib_graph->adc0); + graph->adc1 = sc27xx_adc_get_calib_data(calib_data >> 8, + calib_graph->adc1); + + return 0; +} + +static int sc2720_adc_get_ratio(int channel, int scale) +{ + switch (channel) { + case 14: + switch (scale) { + case 0: + return SC27XX_VOLT_RATIO(68, 900); + case 1: + return SC27XX_VOLT_RATIO(68, 1760); + case 2: + return SC27XX_VOLT_RATIO(68, 2327); + case 3: + return SC27XX_VOLT_RATIO(68, 3654); + default: + return SC27XX_VOLT_RATIO(1, 1); + } + case 16: + switch (scale) { + case 0: + return SC27XX_VOLT_RATIO(48, 100); + case 1: + return SC27XX_VOLT_RATIO(480, 1955); + case 2: + return SC27XX_VOLT_RATIO(480, 2586); + case 3: + return SC27XX_VOLT_RATIO(48, 406); + default: + return SC27XX_VOLT_RATIO(1, 1); + } + case 21: + case 22: + case 23: + switch (scale) { + case 0: + return SC27XX_VOLT_RATIO(3, 8); + case 1: + return SC27XX_VOLT_RATIO(375, 1955); + case 2: + return SC27XX_VOLT_RATIO(375, 2586); + case 3: + return SC27XX_VOLT_RATIO(300, 3248); + default: + return SC27XX_VOLT_RATIO(1, 1); + } + default: + switch (scale) { + case 0: + return SC27XX_VOLT_RATIO(1, 1); + case 1: + return SC27XX_VOLT_RATIO(1000, 1955); + case 2: + return SC27XX_VOLT_RATIO(1000, 2586); + case 3: + return SC27XX_VOLT_RATIO(100, 406); + default: + return SC27XX_VOLT_RATIO(1, 1); + } + } + return SC27XX_VOLT_RATIO(1, 1); +} + +static int sc2721_adc_get_ratio(int channel, int scale) +{ + switch (channel) { + case 1: + case 2: + case 3: + case 4: + return scale ? SC27XX_VOLT_RATIO(400, 1025) : + SC27XX_VOLT_RATIO(1, 1); + case 5: + return SC27XX_VOLT_RATIO(7, 29); + case 7: + case 9: + return scale ? SC27XX_VOLT_RATIO(100, 125) : + SC27XX_VOLT_RATIO(1, 1); + case 14: + return SC27XX_VOLT_RATIO(68, 900); + case 16: + return SC27XX_VOLT_RATIO(48, 100); + case 19: + return SC27XX_VOLT_RATIO(1, 3); + default: + return SC27XX_VOLT_RATIO(1, 1); + } + return SC27XX_VOLT_RATIO(1, 1); +} + +static int sc2730_adc_get_ratio(int channel, int scale) +{ + switch (channel) { + case 14: + switch (scale) { + case 0: + return SC27XX_VOLT_RATIO(68, 900); + case 1: + return SC27XX_VOLT_RATIO(68, 1760); + case 2: + return SC27XX_VOLT_RATIO(68, 2327); + case 3: + return SC27XX_VOLT_RATIO(68, 3654); + default: + return SC27XX_VOLT_RATIO(1, 1); + } + case 15: + switch (scale) { + case 0: + return SC27XX_VOLT_RATIO(1, 3); + case 1: + return SC27XX_VOLT_RATIO(1000, 5865); + case 2: + return SC27XX_VOLT_RATIO(500, 3879); + case 3: + return SC27XX_VOLT_RATIO(500, 6090); + default: + return SC27XX_VOLT_RATIO(1, 1); + } + case 16: + switch (scale) { + case 0: + return SC27XX_VOLT_RATIO(48, 100); + case 1: + return SC27XX_VOLT_RATIO(480, 1955); + case 2: + return SC27XX_VOLT_RATIO(480, 2586); + case 3: + return SC27XX_VOLT_RATIO(48, 406); + default: + return SC27XX_VOLT_RATIO(1, 1); + } + case 21: + case 22: + case 23: + switch (scale) { + case 0: + return SC27XX_VOLT_RATIO(3, 8); + case 1: + return SC27XX_VOLT_RATIO(375, 1955); + case 2: + return SC27XX_VOLT_RATIO(375, 2586); + case 3: + return SC27XX_VOLT_RATIO(300, 3248); + default: + return SC27XX_VOLT_RATIO(1, 1); + } + default: + switch (scale) { + case 0: + return SC27XX_VOLT_RATIO(1, 1); + case 1: + return SC27XX_VOLT_RATIO(1000, 1955); + case 2: + return SC27XX_VOLT_RATIO(1000, 2586); + case 3: + return SC27XX_VOLT_RATIO(1000, 4060); + default: + return SC27XX_VOLT_RATIO(1, 1); + } + } + return SC27XX_VOLT_RATIO(1, 1); +} + +static int sc2731_adc_get_ratio(int channel, int scale) +{ + switch (channel) { + case 1: + case 2: + case 3: + case 4: + return scale ? SC27XX_VOLT_RATIO(400, 1025) : + SC27XX_VOLT_RATIO(1, 1); + case 5: + return SC27XX_VOLT_RATIO(7, 29); + case 6: + return SC27XX_VOLT_RATIO(375, 9000); + case 7: + case 8: + return scale ? SC27XX_VOLT_RATIO(100, 125) : + SC27XX_VOLT_RATIO(1, 1); + case 19: + return SC27XX_VOLT_RATIO(1, 3); + default: + return SC27XX_VOLT_RATIO(1, 1); + } + return SC27XX_VOLT_RATIO(1, 1); +} + +/* + * According to the datasheet set specific value on some channel. + */ +static void sc2720_adc_scale_init(struct sc27xx_adc_data *data) +{ + int i; + + for (i = 0; i < SC27XX_ADC_CHANNEL_MAX; i++) { + switch (i) { + case 5: + data->channel_scale[i] = 3; + break; + case 7: + case 9: + data->channel_scale[i] = 2; + break; + case 13: + data->channel_scale[i] = 1; + break; + case 19: + case 30: + case 31: + data->channel_scale[i] = 3; + break; + default: + data->channel_scale[i] = 0; + break; + } + } +} + +static void sc2730_adc_scale_init(struct sc27xx_adc_data *data) +{ + int i; + + for (i = 0; i < SC27XX_ADC_CHANNEL_MAX; i++) { + switch (i) { + case 5: + case 10: + case 19: + case 30: + case 31: + data->channel_scale[i] = 3; + break; + case 7: + case 9: + data->channel_scale[i] = 2; + break; + case 13: + data->channel_scale[i] = 1; + break; + default: + data->channel_scale[i] = 0; + break; + } + } +} + +static void sc2731_adc_scale_init(struct sc27xx_adc_data *data) +{ + int i; + /* + * In the current software design, SC2731 support 2 scales, + * channels 5 uses big scale, others use smale. + */ + for (i = 0; i < SC27XX_ADC_CHANNEL_MAX; i++) { + switch (i) { + case 5: + data->channel_scale[i] = 1; + break; + default: + data->channel_scale[i] = 0; + break; + } + } +} + +static int sc27xx_adc_read(struct sc27xx_adc_data *data, int channel, + int scale, int *val) +{ + int ret, ret_volref; + u32 tmp, value, status; + + ret = hwspin_lock_timeout_raw(data->hwlock, SC27XX_ADC_HWLOCK_TIMEOUT); + if (ret) { + dev_err(data->dev, "timeout to get the hwspinlock\n"); + return ret; + } + + /* + * According to the sc2721 chip data sheet, the reference voltage of + * specific channel 30 and channel 31 in ADC module needs to be set from + * the default 2.8v to 3.5v. + */ + if ((data->var_data->set_volref) && (channel == 30 || channel == 31)) { + ret = regulator_set_voltage(data->volref, + SC27XX_ADC_REFVOL_VDD35, + SC27XX_ADC_REFVOL_VDD35); + if (ret) { + dev_err(data->dev, "failed to set the volref 3.5v\n"); + goto unlock_adc; + } + } + + ret = regmap_set_bits(data->regmap, data->base + SC27XX_ADC_CTL, + SC27XX_ADC_EN); + if (ret) + goto regulator_restore; + + ret = regmap_set_bits(data->regmap, data->base + SC27XX_ADC_INT_CLR, + SC27XX_ADC_IRQ_CLR); + if (ret) + goto disable_adc; + + /* Configure the channel id and scale */ + tmp = (scale << data->var_data->scale_shift) & data->var_data->scale_mask; + tmp |= channel & SC27XX_ADC_CHN_ID_MASK; + ret = regmap_update_bits(data->regmap, data->base + SC27XX_ADC_CH_CFG, + SC27XX_ADC_CHN_ID_MASK | + data->var_data->scale_mask, + tmp); + if (ret) + goto disable_adc; + + /* Select 12bit conversion mode, and only sample 1 time */ + tmp = SC27XX_ADC_12BIT_MODE; + tmp |= (0 << SC27XX_ADC_RUN_NUM_SHIFT) & SC27XX_ADC_RUN_NUM_MASK; + ret = regmap_update_bits(data->regmap, data->base + SC27XX_ADC_CTL, + SC27XX_ADC_RUN_NUM_MASK | SC27XX_ADC_12BIT_MODE, + tmp); + if (ret) + goto disable_adc; + + ret = regmap_set_bits(data->regmap, data->base + SC27XX_ADC_CTL, + SC27XX_ADC_CHN_RUN); + if (ret) + goto disable_adc; + + ret = regmap_read_poll_timeout(data->regmap, + data->base + SC27XX_ADC_INT_RAW, + status, (status & SC27XX_ADC_IRQ_RAW), + SC27XX_ADC_POLL_RAW_STATUS, + SC27XX_ADC_RDY_TIMEOUT); + if (ret) { + dev_err(data->dev, "read adc timeout, status = 0x%x\n", status); + goto disable_adc; + } + + ret = regmap_read(data->regmap, data->base + SC27XX_ADC_DATA, &value); + if (ret) + goto disable_adc; + + value &= SC27XX_ADC_DATA_MASK; + +disable_adc: + regmap_clear_bits(data->regmap, data->base + SC27XX_ADC_CTL, + SC27XX_ADC_EN); +regulator_restore: + if ((data->var_data->set_volref) && (channel == 30 || channel == 31)) { + ret_volref = regulator_set_voltage(data->volref, + SC27XX_ADC_REFVOL_VDD28, + SC27XX_ADC_REFVOL_VDD28); + if (ret_volref) { + dev_err(data->dev, "failed to set the volref 2.8v,ret_volref = 0x%x\n", + ret_volref); + ret = ret || ret_volref; + } + } +unlock_adc: + hwspin_unlock_raw(data->hwlock); + + if (!ret) + *val = value; + + return ret; +} + +static void sc27xx_adc_volt_ratio(struct sc27xx_adc_data *data, int channel, int scale, + struct u32_fract *fract) +{ + u32 ratio; + + ratio = data->var_data->get_ratio(channel, scale); + fract->numerator = ratio >> SC27XX_RATIO_NUMERATOR_OFFSET; + fract->denominator = ratio & SC27XX_RATIO_DENOMINATOR_MASK; +} + +static int adc_to_volt(struct sc27xx_adc_linear_graph *graph, + int raw_adc) +{ + int tmp; + + tmp = (graph->volt0 - graph->volt1) * (raw_adc - graph->adc1); + tmp /= (graph->adc0 - graph->adc1); + tmp += graph->volt1; + + return tmp; +} + +static int sc27xx_adc_to_volt(struct sc27xx_adc_linear_graph *graph, + int raw_adc) +{ + int tmp; + + tmp = adc_to_volt(graph, raw_adc); + + return tmp < 0 ? 0 : tmp; +} + +static int sc27xx_adc_convert_volt(struct sc27xx_adc_data *data, int channel, + int scale, int raw_adc) +{ + struct u32_fract fract; + u32 volt; + + /* + * Convert ADC values to voltage values according to the linear graph, + * and channel 5 and channel 1 has been calibrated, so we can just + * return the voltage values calculated by the linear graph. But other + * channels need be calculated to the real voltage values with the + * voltage ratio. + */ + switch (channel) { + case 5: + return sc27xx_adc_to_volt(&big_scale_graph, raw_adc); + + case 1: + return sc27xx_adc_to_volt(&small_scale_graph, raw_adc); + + default: + volt = sc27xx_adc_to_volt(&small_scale_graph, raw_adc); + break; + } + + sc27xx_adc_volt_ratio(data, channel, scale, &fract); + + return DIV_ROUND_CLOSEST(volt * fract.denominator, fract.numerator); +} + +static int sc27xx_adc_read_processed(struct sc27xx_adc_data *data, + int channel, int scale, int *val) +{ + int ret, raw_adc; + + ret = sc27xx_adc_read(data, channel, scale, &raw_adc); + if (ret) + return ret; + + *val = sc27xx_adc_convert_volt(data, channel, scale, raw_adc); + return 0; +} + +static int sc27xx_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct sc27xx_adc_data *data = iio_priv(indio_dev); + int scale = data->channel_scale[chan->channel]; + int ret, tmp; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&data->lock); + ret = sc27xx_adc_read(data, chan->channel, scale, &tmp); + mutex_unlock(&data->lock); + + if (ret) + return ret; + + *val = tmp; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_PROCESSED: + mutex_lock(&data->lock); + ret = sc27xx_adc_read_processed(data, chan->channel, scale, + &tmp); + mutex_unlock(&data->lock); + + if (ret) + return ret; + + *val = tmp; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = scale; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int sc27xx_adc_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct sc27xx_adc_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + data->channel_scale[chan->channel] = val; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static const struct iio_info sc27xx_info = { + .read_raw = &sc27xx_adc_read_raw, + .write_raw = &sc27xx_adc_write_raw, +}; + +#define SC27XX_ADC_CHANNEL(index, mask) { \ + .type = IIO_VOLTAGE, \ + .channel = index, \ + .info_mask_separate = mask | BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = "CH##index", \ + .indexed = 1, \ +} + +static const struct iio_chan_spec sc27xx_channels[] = { + SC27XX_ADC_CHANNEL(0, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(1, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(2, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(3, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(4, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(5, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(6, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(7, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(8, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(9, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(10, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(11, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(12, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(13, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(14, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(15, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(16, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(17, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(18, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(19, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(20, BIT(IIO_CHAN_INFO_RAW)), + SC27XX_ADC_CHANNEL(21, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(22, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(23, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(24, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(25, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(26, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(27, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(28, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(29, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(30, BIT(IIO_CHAN_INFO_PROCESSED)), + SC27XX_ADC_CHANNEL(31, BIT(IIO_CHAN_INFO_PROCESSED)), +}; + +static int sc27xx_adc_enable(struct sc27xx_adc_data *data) +{ + int ret; + + ret = regmap_set_bits(data->regmap, data->var_data->module_en, + SC27XX_MODULE_ADC_EN); + if (ret) + return ret; + + /* Enable ADC work clock and controller clock */ + ret = regmap_set_bits(data->regmap, data->var_data->clk_en, + SC27XX_CLK_ADC_EN | SC27XX_CLK_ADC_CLK_EN); + if (ret) + goto disable_adc; + + /* ADC channel scales' calibration from nvmem device */ + ret = sc27xx_adc_scale_calibration(data, true); + if (ret) + goto disable_clk; + + ret = sc27xx_adc_scale_calibration(data, false); + if (ret) + goto disable_clk; + + return 0; + +disable_clk: + regmap_clear_bits(data->regmap, data->var_data->clk_en, + SC27XX_CLK_ADC_EN | SC27XX_CLK_ADC_CLK_EN); +disable_adc: + regmap_clear_bits(data->regmap, data->var_data->module_en, + SC27XX_MODULE_ADC_EN); + + return ret; +} + +static void sc27xx_adc_disable(void *_data) +{ + struct sc27xx_adc_data *data = _data; + + /* Disable ADC work clock and controller clock */ + regmap_clear_bits(data->regmap, data->var_data->clk_en, + SC27XX_CLK_ADC_EN | SC27XX_CLK_ADC_CLK_EN); + + regmap_clear_bits(data->regmap, data->var_data->module_en, + SC27XX_MODULE_ADC_EN); +} + +static const struct sc27xx_adc_variant_data sc2731_data = { + .module_en = SC2731_MODULE_EN, + .clk_en = SC2731_ARM_CLK_EN, + .scale_shift = SC27XX_ADC_SCALE_SHIFT, + .scale_mask = SC27XX_ADC_SCALE_MASK, + .bscale_cal = &sc2731_big_scale_graph_calib, + .sscale_cal = &sc2731_small_scale_graph_calib, + .init_scale = sc2731_adc_scale_init, + .get_ratio = sc2731_adc_get_ratio, + .set_volref = false, +}; + +static const struct sc27xx_adc_variant_data sc2730_data = { + .module_en = SC2730_MODULE_EN, + .clk_en = SC2730_ARM_CLK_EN, + .scale_shift = SC27XX_ADC_SCALE_SHIFT, + .scale_mask = SC27XX_ADC_SCALE_MASK, + .bscale_cal = &big_scale_graph_calib, + .sscale_cal = &small_scale_graph_calib, + .init_scale = sc2730_adc_scale_init, + .get_ratio = sc2730_adc_get_ratio, + .set_volref = false, +}; + +static const struct sc27xx_adc_variant_data sc2721_data = { + .module_en = SC2731_MODULE_EN, + .clk_en = SC2721_ARM_CLK_EN, + .scale_shift = SC2721_ADC_SCALE_SHIFT, + .scale_mask = SC2721_ADC_SCALE_MASK, + .bscale_cal = &sc2731_big_scale_graph_calib, + .sscale_cal = &sc2731_small_scale_graph_calib, + .init_scale = sc2731_adc_scale_init, + .get_ratio = sc2721_adc_get_ratio, + .set_volref = true, +}; + +static const struct sc27xx_adc_variant_data sc2720_data = { + .module_en = SC2731_MODULE_EN, + .clk_en = SC2721_ARM_CLK_EN, + .scale_shift = SC27XX_ADC_SCALE_SHIFT, + .scale_mask = SC27XX_ADC_SCALE_MASK, + .bscale_cal = &big_scale_graph_calib, + .sscale_cal = &small_scale_graph_calib, + .init_scale = sc2720_adc_scale_init, + .get_ratio = sc2720_adc_get_ratio, + .set_volref = false, +}; + +static int sc27xx_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct sc27xx_adc_data *sc27xx_data; + const struct sc27xx_adc_variant_data *pdata; + struct iio_dev *indio_dev; + int ret; + + pdata = of_device_get_match_data(dev); + if (!pdata) + return dev_err_probe(dev, -EINVAL, "No matching driver data found\n"); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*sc27xx_data)); + if (!indio_dev) + return -ENOMEM; + + sc27xx_data = iio_priv(indio_dev); + + sc27xx_data->regmap = dev_get_regmap(dev->parent, NULL); + if (!sc27xx_data->regmap) + return dev_err_probe(dev, -ENODEV, "failed to get ADC regmap\n"); + + ret = of_property_read_u32(np, "reg", &sc27xx_data->base); + if (ret) + return dev_err_probe(dev, ret, "failed to get ADC base address\n"); + + sc27xx_data->irq = platform_get_irq(pdev, 0); + if (sc27xx_data->irq < 0) + return sc27xx_data->irq; + + ret = of_hwspin_lock_get_id(np, 0); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get hwspinlock id\n"); + + sc27xx_data->hwlock = devm_hwspin_lock_request_specific(dev, ret); + if (!sc27xx_data->hwlock) + return dev_err_probe(dev, -ENXIO, "failed to request hwspinlock\n"); + + sc27xx_data->dev = dev; + if (pdata->set_volref) { + sc27xx_data->volref = devm_regulator_get(dev, "vref"); + if (IS_ERR(sc27xx_data->volref)) + return dev_err_probe(dev, PTR_ERR(sc27xx_data->volref), + "failed to get ADC volref\n"); + } + + sc27xx_data->var_data = pdata; + sc27xx_data->var_data->init_scale(sc27xx_data); + + ret = sc27xx_adc_enable(sc27xx_data); + if (ret) + return dev_err_probe(dev, ret, "failed to enable ADC module\n"); + + ret = devm_add_action_or_reset(dev, sc27xx_adc_disable, sc27xx_data); + if (ret) + return dev_err_probe(dev, ret, "failed to add ADC disable action\n"); + + indio_dev->name = dev_name(dev); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &sc27xx_info; + indio_dev->channels = sc27xx_channels; + indio_dev->num_channels = ARRAY_SIZE(sc27xx_channels); + + mutex_init(&sc27xx_data->lock); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + dev_err(dev, "could not register iio (ADC)"); + + return ret; +} + +static const struct of_device_id sc27xx_adc_of_match[] = { + { .compatible = "sprd,sc2731-adc", .data = &sc2731_data}, + { .compatible = "sprd,sc2730-adc", .data = &sc2730_data}, + { .compatible = "sprd,sc2721-adc", .data = &sc2721_data}, + { .compatible = "sprd,sc2720-adc", .data = &sc2720_data}, + { } +}; +MODULE_DEVICE_TABLE(of, sc27xx_adc_of_match); + +static struct platform_driver sc27xx_adc_driver = { + .probe = sc27xx_adc_probe, + .driver = { + .name = "sc27xx-adc", + .of_match_table = sc27xx_adc_of_match, + }, +}; + +module_platform_driver(sc27xx_adc_driver); + +MODULE_AUTHOR("Freeman Liu "); +MODULE_DESCRIPTION("Spreadtrum SC27XX ADC Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/sd_adc_modulator.c b/drivers/iio/adc/sd_adc_modulator.c new file mode 100644 index 000000000..def44d883 --- /dev/null +++ b/drivers/iio/adc/sd_adc_modulator.c @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Generic sigma delta modulator driver + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Author: Arnaud Pouliquen . + */ + +#include +#include +#include +#include +#include +#include +#include + +static const struct iio_info iio_sd_mod_iio_info; + +static const struct iio_chan_spec iio_sd_mod_ch = { + .type = IIO_VOLTAGE, + .indexed = 1, + .scan_type = { + .sign = 'u', + .realbits = 1, + .shift = 0, + }, +}; + +struct iio_sd_backend_priv { + struct regulator *vref; + int vref_mv; +}; + +static int iio_sd_mod_enable(struct iio_backend *backend) +{ + struct iio_sd_backend_priv *priv = iio_backend_get_priv(backend); + + if (priv->vref) + return regulator_enable(priv->vref); + + return 0; +}; + +static void iio_sd_mod_disable(struct iio_backend *backend) +{ + struct iio_sd_backend_priv *priv = iio_backend_get_priv(backend); + + if (priv->vref) + regulator_disable(priv->vref); +}; + +static int iio_sd_mod_read(struct iio_backend *backend, struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct iio_sd_backend_priv *priv = iio_backend_get_priv(backend); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *val = priv->vref_mv; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_OFFSET: + *val = 0; + return IIO_VAL_INT; + } + + return -EOPNOTSUPP; +}; + +static const struct iio_backend_ops sd_backend_ops = { + .enable = iio_sd_mod_enable, + .disable = iio_sd_mod_disable, + .read_raw = iio_sd_mod_read, +}; + +static const struct iio_backend_info sd_backend_info = { + .name = "sd-modulator", + .ops = &sd_backend_ops, + .caps = IIO_BACKEND_CAP_ENABLE, +}; + +static int iio_sd_mod_register(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *iio; + + iio = devm_iio_device_alloc(dev, 0); + if (!iio) + return -ENOMEM; + + iio->name = dev_name(dev); + iio->info = &iio_sd_mod_iio_info; + iio->modes = INDIO_BUFFER_HARDWARE; + + iio->num_channels = 1; + iio->channels = &iio_sd_mod_ch; + + platform_set_drvdata(pdev, iio); + + return devm_iio_device_register(&pdev->dev, iio); +} + +static int iio_sd_mod_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct regulator *vref; + struct iio_sd_backend_priv *priv; + int ret; + + /* If sd modulator is not defined as an IIO backend device, fallback to legacy */ + if (!device_property_present(dev, "#io-backend-cells")) + return iio_sd_mod_register(pdev); + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* + * Get regulator reference if any, but don't enable regulator right now. + * Rely on enable and disable callbacks to manage regulator power. + */ + vref = devm_regulator_get_optional(dev, "vref"); + if (IS_ERR(vref)) { + if (PTR_ERR(vref) != -ENODEV) + return dev_err_probe(dev, PTR_ERR(vref), "Failed to get vref\n"); + } else { + /* + * Retrieve voltage right now, as regulator_get_voltage() provides it whatever + * the state of the regulator. + */ + ret = regulator_get_voltage(vref); + if (ret < 0) + return ret; + + priv->vref = vref; + priv->vref_mv = ret / 1000; + } + + return devm_iio_backend_register(&pdev->dev, &sd_backend_info, priv); +}; + +static const struct of_device_id sd_adc_of_match[] = { + { .compatible = "sd-modulator" }, + { .compatible = "ads1201" }, + { } +}; +MODULE_DEVICE_TABLE(of, sd_adc_of_match); + +static struct platform_driver iio_sd_mod_adc = { + .driver = { + .name = "iio_sd_adc_mod", + .of_match_table = sd_adc_of_match, + }, + .probe = iio_sd_mod_probe, +}; + +module_platform_driver(iio_sd_mod_adc); + +MODULE_DESCRIPTION("Basic sigma delta modulator"); +MODULE_AUTHOR("Arnaud Pouliquen "); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_BACKEND"); diff --git a/drivers/iio/adc/sophgo-cv1800b-adc.c b/drivers/iio/adc/sophgo-cv1800b-adc.c new file mode 100644 index 000000000..bdc3e1326 --- /dev/null +++ b/drivers/iio/adc/sophgo-cv1800b-adc.c @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Sophgo CV1800B SARADC Driver + * + * Copyright (C) Bootlin 2024 + * Author: Thomas Bonnefille + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define CV1800B_ADC_CTRL_REG 0x04 +#define CV1800B_ADC_EN BIT(0) +#define CV1800B_ADC_SEL(x) BIT((x) + 5) +#define CV1800B_ADC_STATUS_REG 0x08 +#define CV1800B_ADC_BUSY BIT(0) +#define CV1800B_ADC_CYC_SET_REG 0x0C +#define CV1800B_MASK_STARTUP_CYCLE GENMASK(4, 0) +#define CV1800B_MASK_SAMPLE_WINDOW GENMASK(11, 8) +#define CV1800B_MASK_CLKDIV GENMASK(15, 12) +#define CV1800B_MASK_COMPARE_CYCLE GENMASK(19, 16) +#define CV1800B_ADC_CH_RESULT_REG(x) (0x14 + 4 * (x)) +#define CV1800B_ADC_CH_RESULT GENMASK(11, 0) +#define CV1800B_ADC_CH_VALID BIT(15) +#define CV1800B_ADC_INTR_EN_REG 0x20 +#define CV1800B_ADC_INTR_CLR_REG 0x24 +#define CV1800B_ADC_INTR_CLR_BIT BIT(0) +#define CV1800B_ADC_INTR_STA_REG 0x28 +#define CV1800B_ADC_INTR_STA_BIT BIT(0) +#define CV1800B_READ_TIMEOUT_MS 1000 +#define CV1800B_READ_TIMEOUT_US (CV1800B_READ_TIMEOUT_MS * 1000) + +#define CV1800B_ADC_CHANNEL(index) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\ + .scan_index = index, \ + } + +struct cv1800b_adc { + struct completion completion; + void __iomem *regs; + struct mutex lock; /* ADC Control and Result register */ + struct clk *clk; + int irq; +}; + +static const struct iio_chan_spec sophgo_channels[] = { + CV1800B_ADC_CHANNEL(0), + CV1800B_ADC_CHANNEL(1), + CV1800B_ADC_CHANNEL(2), +}; + +static void cv1800b_adc_start_measurement(struct cv1800b_adc *saradc, + int channel) +{ + writel(0, saradc->regs + CV1800B_ADC_CTRL_REG); + writel(CV1800B_ADC_SEL(channel) | CV1800B_ADC_EN, + saradc->regs + CV1800B_ADC_CTRL_REG); +} + +static int cv1800b_adc_wait(struct cv1800b_adc *saradc) +{ + if (saradc->irq < 0) { + u32 reg; + + return readl_poll_timeout(saradc->regs + CV1800B_ADC_STATUS_REG, + reg, !(reg & CV1800B_ADC_BUSY), + 500, CV1800B_READ_TIMEOUT_US); + } + + return wait_for_completion_timeout(&saradc->completion, + msecs_to_jiffies(CV1800B_READ_TIMEOUT_MS)) > 0 ? + 0 : -ETIMEDOUT; +} + +static int cv1800b_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cv1800b_adc *saradc = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + u32 sample; + + scoped_guard(mutex, &saradc->lock) { + int ret; + + cv1800b_adc_start_measurement(saradc, chan->scan_index); + ret = cv1800b_adc_wait(saradc); + if (ret < 0) + return ret; + + sample = readl(saradc->regs + CV1800B_ADC_CH_RESULT_REG(chan->scan_index)); + } + if (!(sample & CV1800B_ADC_CH_VALID)) + return -ENODATA; + + *val = sample & CV1800B_ADC_CH_RESULT; + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_SCALE: + *val = 3300; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_SAMP_FREQ: { + u32 status_reg = readl(saradc->regs + CV1800B_ADC_CYC_SET_REG); + unsigned int clk_div = (1 + FIELD_GET(CV1800B_MASK_CLKDIV, status_reg)); + unsigned int freq = clk_get_rate(saradc->clk) / clk_div; + unsigned int nb_startup_cycle = 1 + FIELD_GET(CV1800B_MASK_STARTUP_CYCLE, status_reg); + unsigned int nb_sample_cycle = 1 + FIELD_GET(CV1800B_MASK_SAMPLE_WINDOW, status_reg); + unsigned int nb_compare_cycle = 1 + FIELD_GET(CV1800B_MASK_COMPARE_CYCLE, status_reg); + + *val = freq / (nb_startup_cycle + nb_sample_cycle + nb_compare_cycle); + return IIO_VAL_INT; + } + default: + return -EINVAL; + } +} + +static irqreturn_t cv1800b_adc_interrupt_handler(int irq, void *private) +{ + struct cv1800b_adc *saradc = private; + u32 reg = readl(saradc->regs + CV1800B_ADC_INTR_STA_REG); + + if (!(FIELD_GET(CV1800B_ADC_INTR_STA_BIT, reg))) + return IRQ_NONE; + + writel(CV1800B_ADC_INTR_CLR_BIT, saradc->regs + CV1800B_ADC_INTR_CLR_REG); + complete(&saradc->completion); + + return IRQ_HANDLED; +} + +static const struct iio_info cv1800b_adc_info = { + .read_raw = &cv1800b_adc_read_raw, +}; + +static int cv1800b_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cv1800b_adc *saradc; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*saradc)); + if (!indio_dev) + return -ENOMEM; + + saradc = iio_priv(indio_dev); + indio_dev->name = "sophgo-cv1800b-adc"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &cv1800b_adc_info; + indio_dev->num_channels = ARRAY_SIZE(sophgo_channels); + indio_dev->channels = sophgo_channels; + + saradc->clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(saradc->clk)) + return PTR_ERR(saradc->clk); + + saradc->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(saradc->regs)) + return PTR_ERR(saradc->regs); + + saradc->irq = platform_get_irq_optional(pdev, 0); + if (saradc->irq > 0) { + init_completion(&saradc->completion); + ret = devm_request_irq(dev, saradc->irq, + cv1800b_adc_interrupt_handler, 0, + dev_name(dev), saradc); + if (ret) + return ret; + + writel(1, saradc->regs + CV1800B_ADC_INTR_EN_REG); + } + + ret = devm_mutex_init(dev, &saradc->lock); + if (ret) + return ret; + + writel(FIELD_PREP(CV1800B_MASK_STARTUP_CYCLE, 15) | + FIELD_PREP(CV1800B_MASK_SAMPLE_WINDOW, 15) | + FIELD_PREP(CV1800B_MASK_CLKDIV, 1) | + FIELD_PREP(CV1800B_MASK_COMPARE_CYCLE, 15), + saradc->regs + CV1800B_ADC_CYC_SET_REG); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id cv1800b_adc_match[] = { + { .compatible = "sophgo,cv1800b-saradc", }, + { } +}; +MODULE_DEVICE_TABLE(of, cv1800b_adc_match); + +static struct platform_driver cv1800b_adc_driver = { + .driver = { + .name = "sophgo-cv1800b-saradc", + .of_match_table = cv1800b_adc_match, + }, + .probe = cv1800b_adc_probe, +}; +module_platform_driver(cv1800b_adc_driver); + +MODULE_AUTHOR("Thomas Bonnefille "); +MODULE_DESCRIPTION("Sophgo CV1800B SARADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/spear_adc.c b/drivers/iio/adc/spear_adc.c new file mode 100644 index 000000000..91c0fb1f4 --- /dev/null +++ b/drivers/iio/adc/spear_adc.c @@ -0,0 +1,365 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ST SPEAr ADC driver + * + * Copyright 2012 Stefan Roese + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* SPEAR registers definitions */ +#define SPEAR600_ADC_SCAN_RATE_LO(x) ((x) & 0xFFFF) +#define SPEAR600_ADC_SCAN_RATE_HI(x) (((x) >> 0x10) & 0xFFFF) +#define SPEAR_ADC_CLK_LOW(x) (((x) & 0xf) << 0) +#define SPEAR_ADC_CLK_HIGH(x) (((x) & 0xf) << 4) + +/* Bit definitions for SPEAR_ADC_STATUS */ +#define SPEAR_ADC_STATUS_START_CONVERSION BIT(0) +#define SPEAR_ADC_STATUS_CHANNEL_NUM_MASK GENMASK(3, 1) +#define SPEAR_ADC_STATUS_ADC_ENABLE BIT(4) +#define SPEAR_ADC_STATUS_AVG_SAMPLE_MASK GENMASK(8, 5) +#define SPEAR_ADC_STATUS_VREF_INTERNAL BIT(9) + +#define SPEAR_ADC_DATA_MASK 0x03ff +#define SPEAR_ADC_DATA_BITS 10 + +#define SPEAR_ADC_MOD_NAME "spear-adc" + +#define SPEAR_ADC_CHANNEL_NUM 8 + +#define SPEAR_ADC_CLK_MIN 2500000 +#define SPEAR_ADC_CLK_MAX 20000000 + +struct adc_regs_spear3xx { + u32 status; + u32 average; + u32 scan_rate; + u32 clk; /* Not avail for 1340 & 1310 */ + u32 ch_ctrl[SPEAR_ADC_CHANNEL_NUM]; + u32 ch_data[SPEAR_ADC_CHANNEL_NUM]; +}; + +struct chan_data { + u32 lsb; + u32 msb; +}; + +struct adc_regs_spear6xx { + u32 status; + u32 pad[2]; + u32 clk; + u32 ch_ctrl[SPEAR_ADC_CHANNEL_NUM]; + struct chan_data ch_data[SPEAR_ADC_CHANNEL_NUM]; + u32 scan_rate_lo; + u32 scan_rate_hi; + struct chan_data average; +}; + +struct spear_adc_state { + struct device *dev; + struct adc_regs_spear3xx __iomem *adc_base_spear3xx; + struct adc_regs_spear6xx __iomem *adc_base_spear6xx; + struct clk *clk; + struct completion completion; + /* + * Lock to protect the device state during a potential concurrent + * read access from userspace. Reading a raw value requires a sequence + * of register writes, then a wait for a completion callback, + * and finally a register read, during which userspace could issue + * another read request. This lock protects a read access from + * occurring before another one has finished. + */ + struct mutex lock; + u32 current_clk; + u32 sampling_freq; + u32 avg_samples; + u32 vref_external; + u32 value; +}; + +/* + * Functions to access some SPEAr ADC register. Abstracted into + * static inline functions, because of different register offsets + * on different SoC variants (SPEAr300 vs SPEAr600 etc). + */ +static void spear_adc_set_status(struct spear_adc_state *st, u32 val) +{ + __raw_writel(val, &st->adc_base_spear6xx->status); +} + +static void spear_adc_set_clk(struct spear_adc_state *st, u32 val) +{ + u32 clk_high, clk_low, count; + u32 apb_clk = clk_get_rate(st->clk); + + count = DIV_ROUND_UP(apb_clk, val); + clk_low = count / 2; + clk_high = count - clk_low; + st->current_clk = apb_clk / count; + + __raw_writel(SPEAR_ADC_CLK_LOW(clk_low) | SPEAR_ADC_CLK_HIGH(clk_high), + &st->adc_base_spear6xx->clk); +} + +static void spear_adc_set_ctrl(struct spear_adc_state *st, int n, + u32 val) +{ + __raw_writel(val, &st->adc_base_spear6xx->ch_ctrl[n]); +} + +static u32 spear_adc_get_average(struct spear_adc_state *st) +{ + if (device_is_compatible(st->dev, "st,spear600-adc")) { + return __raw_readl(&st->adc_base_spear6xx->average.msb) & + SPEAR_ADC_DATA_MASK; + } else { + return __raw_readl(&st->adc_base_spear3xx->average) & + SPEAR_ADC_DATA_MASK; + } +} + +static void spear_adc_set_scanrate(struct spear_adc_state *st, u32 rate) +{ + if (device_is_compatible(st->dev, "st,spear600-adc")) { + __raw_writel(SPEAR600_ADC_SCAN_RATE_LO(rate), + &st->adc_base_spear6xx->scan_rate_lo); + __raw_writel(SPEAR600_ADC_SCAN_RATE_HI(rate), + &st->adc_base_spear6xx->scan_rate_hi); + } else { + __raw_writel(rate, &st->adc_base_spear3xx->scan_rate); + } +} + +static int spear_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct spear_adc_state *st = iio_priv(indio_dev); + u32 status; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&st->lock); + + status = FIELD_PREP(SPEAR_ADC_STATUS_CHANNEL_NUM_MASK, chan->channel) | + FIELD_PREP(SPEAR_ADC_STATUS_AVG_SAMPLE_MASK, st->avg_samples) | + SPEAR_ADC_STATUS_START_CONVERSION | + SPEAR_ADC_STATUS_ADC_ENABLE; + if (st->vref_external == 0) + status |= SPEAR_ADC_STATUS_VREF_INTERNAL; + + spear_adc_set_status(st, status); + wait_for_completion(&st->completion); /* set by ISR */ + *val = st->value; + + mutex_unlock(&st->lock); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = st->vref_external; + *val2 = SPEAR_ADC_DATA_BITS; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = st->current_clk; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int spear_adc_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct spear_adc_state *st = iio_priv(indio_dev); + int ret = 0; + + if (mask != IIO_CHAN_INFO_SAMP_FREQ) + return -EINVAL; + + mutex_lock(&st->lock); + + if ((val < SPEAR_ADC_CLK_MIN) || + (val > SPEAR_ADC_CLK_MAX) || + (val2 != 0)) { + ret = -EINVAL; + goto out; + } + + spear_adc_set_clk(st, val); + +out: + mutex_unlock(&st->lock); + return ret; +} + +#define SPEAR_ADC_CHAN(idx) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\ + .channel = idx, \ +} + +static const struct iio_chan_spec spear_adc_iio_channels[] = { + SPEAR_ADC_CHAN(0), + SPEAR_ADC_CHAN(1), + SPEAR_ADC_CHAN(2), + SPEAR_ADC_CHAN(3), + SPEAR_ADC_CHAN(4), + SPEAR_ADC_CHAN(5), + SPEAR_ADC_CHAN(6), + SPEAR_ADC_CHAN(7), +}; + +static irqreturn_t spear_adc_isr(int irq, void *dev_id) +{ + struct spear_adc_state *st = dev_id; + + /* Read value to clear IRQ */ + st->value = spear_adc_get_average(st); + complete(&st->completion); + + return IRQ_HANDLED; +} + +static int spear_adc_configure(struct spear_adc_state *st) +{ + int i; + + /* Reset ADC core */ + spear_adc_set_status(st, 0); + __raw_writel(0, &st->adc_base_spear6xx->clk); + for (i = 0; i < 8; i++) + spear_adc_set_ctrl(st, i, 0); + spear_adc_set_scanrate(st, 0); + + spear_adc_set_clk(st, st->sampling_freq); + + return 0; +} + +static const struct iio_info spear_adc_info = { + .read_raw = &spear_adc_read_raw, + .write_raw = &spear_adc_write_raw, +}; + +static int spear_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct spear_adc_state *st; + struct iio_dev *indio_dev = NULL; + int ret = -ENODEV; + int irq; + + indio_dev = devm_iio_device_alloc(dev, sizeof(struct spear_adc_state)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->dev = dev; + + init_completion(&st->completion); + mutex_init(&st->lock); + + /* + * SPEAr600 has a different register layout than other SPEAr SoC's + * (e.g. SPEAr3xx). Let's provide two register base addresses + * to support multi-arch kernels. + */ + st->adc_base_spear6xx = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(st->adc_base_spear6xx)) + return PTR_ERR(st->adc_base_spear6xx); + + st->adc_base_spear3xx = + (struct adc_regs_spear3xx __iomem *)st->adc_base_spear6xx; + + st->clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(st->clk)) + return dev_err_probe(dev, PTR_ERR(st->clk), + "failed enabling clock\n"); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, spear_adc_isr, 0, SPEAR_ADC_MOD_NAME, + st); + if (ret < 0) + return dev_err_probe(dev, ret, "failed requesting interrupt\n"); + + if (device_property_read_u32(dev, "sampling-frequency", &st->sampling_freq)) + return dev_err_probe(dev, -EINVAL, + "sampling-frequency missing in DT\n"); + + /* + * Optional avg_samples defaults to 0, resulting in single data + * conversion + */ + device_property_read_u32(dev, "average-samples", &st->avg_samples); + + /* + * Optional vref_external defaults to 0, resulting in internal vref + * selection + */ + device_property_read_u32(dev, "vref-external", &st->vref_external); + + spear_adc_configure(st); + + indio_dev->name = SPEAR_ADC_MOD_NAME; + indio_dev->info = &spear_adc_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = spear_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(spear_adc_iio_channels); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ret; + + dev_info(dev, "SPEAR ADC driver loaded, IRQ %d\n", irq); + + return 0; +} + +static const struct of_device_id spear_adc_dt_ids[] = { + { .compatible = "st,spear600-adc", }, + { } +}; +MODULE_DEVICE_TABLE(of, spear_adc_dt_ids); + +static struct platform_driver spear_adc_driver = { + .probe = spear_adc_probe, + .driver = { + .name = SPEAR_ADC_MOD_NAME, + .of_match_table = spear_adc_dt_ids, + }, +}; + +module_platform_driver(spear_adc_driver); + +MODULE_AUTHOR("Stefan Roese "); +MODULE_DESCRIPTION("SPEAr ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/stm32-adc-core.c b/drivers/iio/adc/stm32-adc-core.c new file mode 100644 index 000000000..a42d82d61 --- /dev/null +++ b/drivers/iio/adc/stm32-adc-core.c @@ -0,0 +1,917 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This file is part of STM32 ADC driver + * + * Copyright (C) 2016, STMicroelectronics - All Rights Reserved + * Author: Fabrice Gasnier . + * + * Inspired from: fsl-imx25-tsadc + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "stm32-adc-core.h" + +#define STM32_ADC_CORE_SLEEP_DELAY_MS 2000 + +/* SYSCFG registers */ +#define STM32MP1_SYSCFG_PMCSETR 0x04 +#define STM32MP1_SYSCFG_PMCCLRR 0x44 + +/* SYSCFG bit fields */ +#define STM32MP1_SYSCFG_ANASWVDD_MASK BIT(9) + +/* SYSCFG capability flags */ +#define HAS_VBOOSTER BIT(0) +#define HAS_ANASWVDD BIT(1) + +/** + * struct stm32_adc_common_regs - stm32 common registers + * @csr: common status register offset + * @ccr: common control register offset + * @eoc_msk: array of eoc (end of conversion flag) masks in csr for adc1..n + * @ovr_msk: array of ovr (overrun flag) masks in csr for adc1..n + * @ier: interrupt enable register offset for each adc + * @eocie_msk: end of conversion interrupt enable mask in @ier + */ +struct stm32_adc_common_regs { + u32 csr; + u32 ccr; + u32 eoc_msk[STM32_ADC_MAX_ADCS]; + u32 ovr_msk[STM32_ADC_MAX_ADCS]; + u32 ier; + u32 eocie_msk; +}; + +struct stm32_adc_priv; + +/** + * struct stm32_adc_priv_cfg - stm32 core compatible configuration data + * @regs: common registers for all instances + * @clk_sel: clock selection routine + * @max_clk_rate_hz: maximum analog clock rate (Hz, from datasheet) + * @ipid: adc identification number + * @has_syscfg: SYSCFG capability flags + * @num_irqs: number of interrupt lines + * @num_adcs: maximum number of ADC instances in the common registers + */ +struct stm32_adc_priv_cfg { + const struct stm32_adc_common_regs *regs; + int (*clk_sel)(struct platform_device *, struct stm32_adc_priv *); + u32 max_clk_rate_hz; + u32 ipid; + unsigned int has_syscfg; + unsigned int num_irqs; + unsigned int num_adcs; +}; + +/** + * struct stm32_adc_priv - stm32 ADC core private data + * @irq: irq(s) for ADC block + * @nb_adc_max: actual maximum number of instance per ADC block + * @domain: irq domain reference + * @aclk: clock reference for the analog circuitry + * @bclk: bus clock common for all ADCs, depends on part used + * @max_clk_rate: desired maximum clock rate + * @booster: booster supply reference + * @vdd: vdd supply reference + * @vdda: vdda analog supply reference + * @vref: regulator reference + * @vdd_uv: vdd supply voltage (microvolts) + * @vdda_uv: vdda supply voltage (microvolts) + * @cfg: compatible configuration data + * @common: common data for all ADC instances + * @ccr_bak: backup CCR in low power mode + * @syscfg: reference to syscon, system control registers + */ +struct stm32_adc_priv { + int irq[STM32_ADC_MAX_ADCS]; + unsigned int nb_adc_max; + struct irq_domain *domain; + struct clk *aclk; + struct clk *bclk; + u32 max_clk_rate; + struct regulator *booster; + struct regulator *vdd; + struct regulator *vdda; + struct regulator *vref; + int vdd_uv; + int vdda_uv; + const struct stm32_adc_priv_cfg *cfg; + struct stm32_adc_common common; + u32 ccr_bak; + struct regmap *syscfg; +}; + +static struct stm32_adc_priv *to_stm32_adc_priv(struct stm32_adc_common *com) +{ + return container_of(com, struct stm32_adc_priv, common); +} + +/* STM32F4 ADC internal common clock prescaler division ratios */ +static int stm32f4_pclk_div[] = {2, 4, 6, 8}; + +/** + * stm32f4_adc_clk_sel() - Select stm32f4 ADC common clock prescaler + * @pdev: platform device + * @priv: stm32 ADC core private data + * Select clock prescaler used for analog conversions, before using ADC. + */ +static int stm32f4_adc_clk_sel(struct platform_device *pdev, + struct stm32_adc_priv *priv) +{ + unsigned long rate; + u32 val; + int i; + + /* stm32f4 has one clk input for analog (mandatory), enforce it here */ + if (!priv->aclk) { + dev_err(&pdev->dev, "No 'adc' clock found\n"); + return -ENOENT; + } + + rate = clk_get_rate(priv->aclk); + if (!rate) { + dev_err(&pdev->dev, "Invalid clock rate: 0\n"); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(stm32f4_pclk_div); i++) { + if ((rate / stm32f4_pclk_div[i]) <= priv->max_clk_rate) + break; + } + if (i >= ARRAY_SIZE(stm32f4_pclk_div)) { + dev_err(&pdev->dev, "adc clk selection failed\n"); + return -EINVAL; + } + + priv->common.rate = rate / stm32f4_pclk_div[i]; + val = readl_relaxed(priv->common.base + STM32F4_ADC_CCR); + val &= ~STM32F4_ADC_ADCPRE_MASK; + val |= i << STM32F4_ADC_ADCPRE_SHIFT; + writel_relaxed(val, priv->common.base + STM32F4_ADC_CCR); + + dev_dbg(&pdev->dev, "Using analog clock source at %ld kHz\n", + priv->common.rate / 1000); + + return 0; +} + +/** + * struct stm32h7_adc_ck_spec - specification for stm32h7 adc clock + * @ckmode: ADC clock mode, Async or sync with prescaler. + * @presc: prescaler bitfield for async clock mode + * @div: prescaler division ratio + */ +struct stm32h7_adc_ck_spec { + u32 ckmode; + u32 presc; + int div; +}; + +static const struct stm32h7_adc_ck_spec stm32h7_adc_ckmodes_spec[] = { + /* 00: CK_ADC[1..3]: Asynchronous clock modes */ + { 0, 0, 1 }, + { 0, 1, 2 }, + { 0, 2, 4 }, + { 0, 3, 6 }, + { 0, 4, 8 }, + { 0, 5, 10 }, + { 0, 6, 12 }, + { 0, 7, 16 }, + { 0, 8, 32 }, + { 0, 9, 64 }, + { 0, 10, 128 }, + { 0, 11, 256 }, + /* HCLK used: Synchronous clock modes (1, 2 or 4 prescaler) */ + { 1, 0, 1 }, + { 2, 0, 2 }, + { 3, 0, 4 }, +}; + +static int stm32h7_adc_clk_sel(struct platform_device *pdev, + struct stm32_adc_priv *priv) +{ + u32 ckmode, presc, val; + unsigned long rate; + int i, div, duty; + + /* stm32h7 bus clock is common for all ADC instances (mandatory) */ + if (!priv->bclk) { + dev_err(&pdev->dev, "No 'bus' clock found\n"); + return -ENOENT; + } + + /* + * stm32h7 can use either 'bus' or 'adc' clock for analog circuitry. + * So, choice is to have bus clock mandatory and adc clock optional. + * If optional 'adc' clock has been found, then try to use it first. + */ + if (priv->aclk) { + /* + * Asynchronous clock modes (e.g. ckmode == 0) + * From spec: PLL output mustn't exceed max rate + */ + rate = clk_get_rate(priv->aclk); + if (!rate) { + dev_err(&pdev->dev, "Invalid adc clock rate: 0\n"); + return -EINVAL; + } + + /* If duty is an error, kindly use at least /2 divider */ + duty = clk_get_scaled_duty_cycle(priv->aclk, 100); + if (duty < 0) + dev_warn(&pdev->dev, "adc clock duty: %d\n", duty); + + for (i = 0; i < ARRAY_SIZE(stm32h7_adc_ckmodes_spec); i++) { + ckmode = stm32h7_adc_ckmodes_spec[i].ckmode; + presc = stm32h7_adc_ckmodes_spec[i].presc; + div = stm32h7_adc_ckmodes_spec[i].div; + + if (ckmode) + continue; + + /* + * For proper operation, clock duty cycle range is 49% + * to 51%. Apply at least /2 prescaler otherwise. + */ + if (div == 1 && (duty < 49 || duty > 51)) + continue; + + if ((rate / div) <= priv->max_clk_rate) + goto out; + } + } + + /* Synchronous clock modes (e.g. ckmode is 1, 2 or 3) */ + rate = clk_get_rate(priv->bclk); + if (!rate) { + dev_err(&pdev->dev, "Invalid bus clock rate: 0\n"); + return -EINVAL; + } + + duty = clk_get_scaled_duty_cycle(priv->bclk, 100); + if (duty < 0) + dev_warn(&pdev->dev, "bus clock duty: %d\n", duty); + + for (i = 0; i < ARRAY_SIZE(stm32h7_adc_ckmodes_spec); i++) { + ckmode = stm32h7_adc_ckmodes_spec[i].ckmode; + presc = stm32h7_adc_ckmodes_spec[i].presc; + div = stm32h7_adc_ckmodes_spec[i].div; + + if (!ckmode) + continue; + + if (div == 1 && (duty < 49 || duty > 51)) + continue; + + if ((rate / div) <= priv->max_clk_rate) + goto out; + } + + dev_err(&pdev->dev, "adc clk selection failed\n"); + return -EINVAL; + +out: + /* rate used later by each ADC instance to control BOOST mode */ + priv->common.rate = rate / div; + + /* Set common clock mode and prescaler */ + val = readl_relaxed(priv->common.base + STM32H7_ADC_CCR); + val &= ~(STM32H7_CKMODE_MASK | STM32H7_PRESC_MASK); + val |= ckmode << STM32H7_CKMODE_SHIFT; + val |= presc << STM32H7_PRESC_SHIFT; + writel_relaxed(val, priv->common.base + STM32H7_ADC_CCR); + + dev_dbg(&pdev->dev, "Using %s clock/%d source at %ld kHz\n", + ckmode ? "bus" : "adc", div, priv->common.rate / 1000); + + return 0; +} + +/* STM32F4 common registers definitions */ +static const struct stm32_adc_common_regs stm32f4_adc_common_regs = { + .csr = STM32F4_ADC_CSR, + .ccr = STM32F4_ADC_CCR, + .eoc_msk = { STM32F4_EOC1, STM32F4_EOC2, STM32F4_EOC3 }, + .ovr_msk = { STM32F4_OVR1, STM32F4_OVR2, STM32F4_OVR3 }, + .ier = STM32F4_ADC_CR1, + .eocie_msk = STM32F4_EOCIE, +}; + +/* STM32H7 common registers definitions */ +static const struct stm32_adc_common_regs stm32h7_adc_common_regs = { + .csr = STM32H7_ADC_CSR, + .ccr = STM32H7_ADC_CCR, + .eoc_msk = { STM32H7_EOC_MST, STM32H7_EOC_SLV }, + .ovr_msk = { STM32H7_OVR_MST, STM32H7_OVR_SLV }, + .ier = STM32H7_ADC_IER, + .eocie_msk = STM32H7_EOCIE, +}; + +/* STM32MP13 common registers definitions */ +static const struct stm32_adc_common_regs stm32mp13_adc_common_regs = { + .csr = STM32H7_ADC_CSR, + .ccr = STM32H7_ADC_CCR, + .eoc_msk = { STM32H7_EOC_MST }, + .ovr_msk = { STM32H7_OVR_MST }, + .ier = STM32H7_ADC_IER, + .eocie_msk = STM32H7_EOCIE, +}; + +static const unsigned int stm32_adc_offset[STM32_ADC_MAX_ADCS] = { + 0, STM32_ADC_OFFSET, STM32_ADC_OFFSET * 2, +}; + +static unsigned int stm32_adc_eoc_enabled(struct stm32_adc_priv *priv, + unsigned int adc) +{ + u32 ier, offset = stm32_adc_offset[adc]; + + ier = readl_relaxed(priv->common.base + offset + priv->cfg->regs->ier); + + return ier & priv->cfg->regs->eocie_msk; +} + +/* ADC common interrupt for all instances */ +static void stm32_adc_irq_handler(struct irq_desc *desc) +{ + struct stm32_adc_priv *priv = irq_desc_get_handler_data(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); + int i; + u32 status; + + chained_irq_enter(chip, desc); + status = readl_relaxed(priv->common.base + priv->cfg->regs->csr); + + /* + * End of conversion may be handled by using IRQ or DMA. There may be a + * race here when two conversions complete at the same time on several + * ADCs. EOC may be read 'set' for several ADCs, with: + * - an ADC configured to use DMA (EOC triggers the DMA request, and + * is then automatically cleared by DR read in hardware) + * - an ADC configured to use IRQs (EOCIE bit is set. The handler must + * be called in this case) + * So both EOC status bit in CSR and EOCIE control bit must be checked + * before invoking the interrupt handler (e.g. call ISR only for + * IRQ-enabled ADCs). + */ + for (i = 0; i < priv->nb_adc_max; i++) { + if ((status & priv->cfg->regs->eoc_msk[i] && + stm32_adc_eoc_enabled(priv, i)) || + (status & priv->cfg->regs->ovr_msk[i])) + generic_handle_domain_irq(priv->domain, i); + } + + chained_irq_exit(chip, desc); +}; + +static int stm32_adc_domain_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hwirq) +{ + irq_set_chip_data(irq, d->host_data); + irq_set_chip_and_handler(irq, &dummy_irq_chip, handle_level_irq); + + return 0; +} + +static void stm32_adc_domain_unmap(struct irq_domain *d, unsigned int irq) +{ + irq_set_chip_and_handler(irq, NULL, NULL); + irq_set_chip_data(irq, NULL); +} + +static const struct irq_domain_ops stm32_adc_domain_ops = { + .map = stm32_adc_domain_map, + .unmap = stm32_adc_domain_unmap, + .xlate = irq_domain_xlate_onecell, +}; + +static int stm32_adc_irq_probe(struct platform_device *pdev, + struct stm32_adc_priv *priv) +{ + unsigned int i; + + /* + * Interrupt(s) must be provided, depending on the compatible: + * - stm32f4/h7 shares a common interrupt line. + * - stm32mp1, has one line per ADC + */ + for (i = 0; i < priv->cfg->num_irqs; i++) { + priv->irq[i] = platform_get_irq(pdev, i); + if (priv->irq[i] < 0) + return priv->irq[i]; + } + + priv->domain = irq_domain_create_simple(dev_fwnode(&pdev->dev), + STM32_ADC_MAX_ADCS, 0, + &stm32_adc_domain_ops, + priv); + if (!priv->domain) { + dev_err(&pdev->dev, "Failed to add irq domain\n"); + return -ENOMEM; + } + + for (i = 0; i < priv->cfg->num_irqs; i++) + irq_set_chained_handler_and_data(priv->irq[i], + stm32_adc_irq_handler, priv); + + return 0; +} + +static void stm32_adc_irq_remove(struct platform_device *pdev, + struct stm32_adc_priv *priv) +{ + int hwirq; + unsigned int i; + + for (hwirq = 0; hwirq < priv->nb_adc_max; hwirq++) + irq_dispose_mapping(irq_find_mapping(priv->domain, hwirq)); + irq_domain_remove(priv->domain); + + for (i = 0; i < priv->cfg->num_irqs; i++) + irq_set_chained_handler(priv->irq[i], NULL); +} + +static int stm32_adc_core_switches_supply_en(struct stm32_adc_priv *priv, + struct device *dev) +{ + int ret; + + /* + * On STM32H7 and STM32MP1, the ADC inputs are multiplexed with analog + * switches (via PCSEL) which have reduced performances when their + * supply is below 2.7V (vdda by default): + * - Voltage booster can be used, to get full ADC performances + * (increases power consumption). + * - Vdd can be used to supply them, if above 2.7V (STM32MP1 only). + * + * Recommended settings for ANASWVDD and EN_BOOSTER: + * - vdda < 2.7V but vdd > 2.7V: ANASWVDD = 1, EN_BOOSTER = 0 (stm32mp1) + * - vdda < 2.7V and vdd < 2.7V: ANASWVDD = 0, EN_BOOSTER = 1 + * - vdda >= 2.7V: ANASWVDD = 0, EN_BOOSTER = 0 (default) + */ + if (priv->vdda_uv < 2700000) { + if (priv->syscfg && priv->vdd_uv > 2700000) { + ret = regulator_enable(priv->vdd); + if (ret < 0) { + dev_err(dev, "vdd enable failed %d\n", ret); + return ret; + } + + ret = regmap_write(priv->syscfg, + STM32MP1_SYSCFG_PMCSETR, + STM32MP1_SYSCFG_ANASWVDD_MASK); + if (ret < 0) { + regulator_disable(priv->vdd); + dev_err(dev, "vdd select failed, %d\n", ret); + return ret; + } + dev_dbg(dev, "analog switches supplied by vdd\n"); + + return 0; + } + + if (priv->booster) { + /* + * This is optional, as this is a trade-off between + * analog performance and power consumption. + */ + ret = regulator_enable(priv->booster); + if (ret < 0) { + dev_err(dev, "booster enable failed %d\n", ret); + return ret; + } + dev_dbg(dev, "analog switches supplied by booster\n"); + + return 0; + } + } + + /* Fallback using vdda (default), nothing to do */ + dev_dbg(dev, "analog switches supplied by vdda (%d uV)\n", + priv->vdda_uv); + + return 0; +} + +static void stm32_adc_core_switches_supply_dis(struct stm32_adc_priv *priv) +{ + if (priv->vdda_uv < 2700000) { + if (priv->syscfg && priv->vdd_uv > 2700000) { + regmap_write(priv->syscfg, STM32MP1_SYSCFG_PMCCLRR, + STM32MP1_SYSCFG_ANASWVDD_MASK); + regulator_disable(priv->vdd); + return; + } + if (priv->booster) + regulator_disable(priv->booster); + } +} + +static int stm32_adc_core_hw_start(struct device *dev) +{ + struct stm32_adc_common *common = dev_get_drvdata(dev); + struct stm32_adc_priv *priv = to_stm32_adc_priv(common); + int ret; + + ret = regulator_enable(priv->vdda); + if (ret < 0) { + dev_err(dev, "vdda enable failed %d\n", ret); + return ret; + } + + ret = regulator_get_voltage(priv->vdda); + if (ret < 0) { + dev_err(dev, "vdda get voltage failed, %d\n", ret); + goto err_vdda_disable; + } + priv->vdda_uv = ret; + + ret = stm32_adc_core_switches_supply_en(priv, dev); + if (ret < 0) + goto err_vdda_disable; + + ret = regulator_enable(priv->vref); + if (ret < 0) { + dev_err(dev, "vref enable failed\n"); + goto err_switches_dis; + } + + ret = clk_prepare_enable(priv->bclk); + if (ret < 0) { + dev_err(dev, "bus clk enable failed\n"); + goto err_regulator_disable; + } + + ret = clk_prepare_enable(priv->aclk); + if (ret < 0) { + dev_err(dev, "adc clk enable failed\n"); + goto err_bclk_disable; + } + + writel_relaxed(priv->ccr_bak, priv->common.base + priv->cfg->regs->ccr); + + return 0; + +err_bclk_disable: + clk_disable_unprepare(priv->bclk); +err_regulator_disable: + regulator_disable(priv->vref); +err_switches_dis: + stm32_adc_core_switches_supply_dis(priv); +err_vdda_disable: + regulator_disable(priv->vdda); + + return ret; +} + +static void stm32_adc_core_hw_stop(struct device *dev) +{ + struct stm32_adc_common *common = dev_get_drvdata(dev); + struct stm32_adc_priv *priv = to_stm32_adc_priv(common); + + /* Backup CCR that may be lost (depends on power state to achieve) */ + priv->ccr_bak = readl_relaxed(priv->common.base + priv->cfg->regs->ccr); + clk_disable_unprepare(priv->aclk); + clk_disable_unprepare(priv->bclk); + regulator_disable(priv->vref); + stm32_adc_core_switches_supply_dis(priv); + regulator_disable(priv->vdda); +} + +static int stm32_adc_core_switches_probe(struct device *dev, + struct stm32_adc_priv *priv) +{ + struct device_node *np = dev->of_node; + int ret; + + /* Analog switches supply can be controlled by syscfg (optional) */ + priv->syscfg = syscon_regmap_lookup_by_phandle(np, "st,syscfg"); + if (IS_ERR(priv->syscfg)) { + ret = PTR_ERR(priv->syscfg); + if (ret != -ENODEV) + return dev_err_probe(dev, ret, "Can't probe syscfg\n"); + + priv->syscfg = NULL; + } + + /* Booster can be used to supply analog switches (optional) */ + if (priv->cfg->has_syscfg & HAS_VBOOSTER) { + priv->booster = devm_regulator_get_optional(dev, "booster"); + if (IS_ERR(priv->booster)) { + ret = PTR_ERR(priv->booster); + if (ret != -ENODEV) + return dev_err_probe(dev, ret, "can't get booster\n"); + + priv->booster = NULL; + } + } + + /* Vdd can be used to supply analog switches (optional) */ + if (priv->cfg->has_syscfg & HAS_ANASWVDD) { + priv->vdd = devm_regulator_get_optional(dev, "vdd"); + if (IS_ERR(priv->vdd)) { + ret = PTR_ERR(priv->vdd); + if (ret != -ENODEV) + return dev_err_probe(dev, ret, "can't get vdd\n"); + + priv->vdd = NULL; + } + } + + if (priv->vdd) { + ret = regulator_enable(priv->vdd); + if (ret < 0) { + dev_err(dev, "vdd enable failed %d\n", ret); + return ret; + } + + ret = regulator_get_voltage(priv->vdd); + if (ret < 0) { + dev_err(dev, "vdd get voltage failed %d\n", ret); + regulator_disable(priv->vdd); + return ret; + } + priv->vdd_uv = ret; + + regulator_disable(priv->vdd); + } + + return 0; +} + +static int stm32_adc_probe_identification(struct platform_device *pdev, + struct stm32_adc_priv *priv) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *child; + const char *compat; + int ret, count = 0; + u32 id, val; + + if (!priv->cfg->ipid) + return 0; + + id = FIELD_GET(STM32MP1_IPIDR_MASK, + readl_relaxed(priv->common.base + STM32MP1_ADC_IPDR)); + if (id != priv->cfg->ipid) { + dev_err(&pdev->dev, "Unexpected IP version: 0x%x", id); + return -EINVAL; + } + + for_each_child_of_node(np, child) { + ret = of_property_read_string(child, "compatible", &compat); + if (ret) + continue; + /* Count child nodes with stm32 adc compatible */ + if (strstr(compat, "st,stm32") && strstr(compat, "adc")) + count++; + } + + val = readl_relaxed(priv->common.base + STM32MP1_ADC_HWCFGR0); + priv->nb_adc_max = FIELD_GET(STM32MP1_ADCNUM_MASK, val); + if (count > priv->nb_adc_max) { + dev_err(&pdev->dev, "Unexpected child number: %d", count); + return -EINVAL; + } + + val = readl_relaxed(priv->common.base + STM32MP1_ADC_VERR); + dev_dbg(&pdev->dev, "ADC version: %lu.%lu\n", + FIELD_GET(STM32MP1_MAJREV_MASK, val), + FIELD_GET(STM32MP1_MINREV_MASK, val)); + + return 0; +} + +static int stm32_adc_probe(struct platform_device *pdev) +{ + struct stm32_adc_priv *priv; + struct device *dev = &pdev->dev; + struct device_node *np = pdev->dev.of_node; + struct resource *res; + u32 max_rate; + int ret; + + if (!pdev->dev.of_node) + return -ENODEV; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + platform_set_drvdata(pdev, &priv->common); + + priv->cfg = device_get_match_data(dev); + priv->nb_adc_max = priv->cfg->num_adcs; + spin_lock_init(&priv->common.lock); + + priv->common.base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(priv->common.base)) + return PTR_ERR(priv->common.base); + priv->common.phys_base = res->start; + + priv->vdda = devm_regulator_get(&pdev->dev, "vdda"); + if (IS_ERR(priv->vdda)) + return dev_err_probe(&pdev->dev, PTR_ERR(priv->vdda), + "vdda get failed\n"); + + priv->vref = devm_regulator_get(&pdev->dev, "vref"); + if (IS_ERR(priv->vref)) + return dev_err_probe(&pdev->dev, PTR_ERR(priv->vref), + "vref get failed\n"); + + priv->aclk = devm_clk_get_optional(&pdev->dev, "adc"); + if (IS_ERR(priv->aclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(priv->aclk), + "Can't get 'adc' clock\n"); + + priv->bclk = devm_clk_get_optional(&pdev->dev, "bus"); + if (IS_ERR(priv->bclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(priv->bclk), + "Can't get 'bus' clock\n"); + + ret = stm32_adc_core_switches_probe(dev, priv); + if (ret) + return ret; + + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); + pm_runtime_set_autosuspend_delay(dev, STM32_ADC_CORE_SLEEP_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + + ret = stm32_adc_core_hw_start(dev); + if (ret) + goto err_pm_stop; + + ret = stm32_adc_probe_identification(pdev, priv); + if (ret < 0) + goto err_hw_stop; + + ret = regulator_get_voltage(priv->vref); + if (ret < 0) { + dev_err(&pdev->dev, "vref get voltage failed, %d\n", ret); + goto err_hw_stop; + } + priv->common.vref_mv = ret / 1000; + dev_dbg(&pdev->dev, "vref+=%dmV\n", priv->common.vref_mv); + + ret = of_property_read_u32(pdev->dev.of_node, "st,max-clk-rate-hz", + &max_rate); + if (!ret) + priv->max_clk_rate = min(max_rate, priv->cfg->max_clk_rate_hz); + else + priv->max_clk_rate = priv->cfg->max_clk_rate_hz; + + ret = priv->cfg->clk_sel(pdev, priv); + if (ret < 0) + goto err_hw_stop; + + ret = stm32_adc_irq_probe(pdev, priv); + if (ret < 0) + goto err_hw_stop; + + ret = of_platform_populate(np, NULL, NULL, &pdev->dev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to populate DT children\n"); + goto err_irq_remove; + } + + pm_runtime_put_autosuspend(dev); + + return 0; + +err_irq_remove: + stm32_adc_irq_remove(pdev, priv); +err_hw_stop: + stm32_adc_core_hw_stop(dev); +err_pm_stop: + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_put_noidle(dev); + + return ret; +} + +static void stm32_adc_remove(struct platform_device *pdev) +{ + struct stm32_adc_common *common = platform_get_drvdata(pdev); + struct stm32_adc_priv *priv = to_stm32_adc_priv(common); + + pm_runtime_get_sync(&pdev->dev); + of_platform_depopulate(&pdev->dev); + stm32_adc_irq_remove(pdev, priv); + stm32_adc_core_hw_stop(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); +} + +static int stm32_adc_core_runtime_suspend(struct device *dev) +{ + stm32_adc_core_hw_stop(dev); + + return 0; +} + +static int stm32_adc_core_runtime_resume(struct device *dev) +{ + return stm32_adc_core_hw_start(dev); +} + +static int stm32_adc_core_runtime_idle(struct device *dev) +{ + pm_runtime_mark_last_busy(dev); + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(stm32_adc_core_pm_ops, + stm32_adc_core_runtime_suspend, + stm32_adc_core_runtime_resume, + stm32_adc_core_runtime_idle); + +static const struct stm32_adc_priv_cfg stm32f4_adc_priv_cfg = { + .regs = &stm32f4_adc_common_regs, + .clk_sel = stm32f4_adc_clk_sel, + .max_clk_rate_hz = 36000000, + .num_irqs = 1, + .num_adcs = 3, +}; + +static const struct stm32_adc_priv_cfg stm32h7_adc_priv_cfg = { + .regs = &stm32h7_adc_common_regs, + .clk_sel = stm32h7_adc_clk_sel, + .max_clk_rate_hz = 36000000, + .has_syscfg = HAS_VBOOSTER, + .num_irqs = 1, + .num_adcs = 2, +}; + +static const struct stm32_adc_priv_cfg stm32mp1_adc_priv_cfg = { + .regs = &stm32h7_adc_common_regs, + .clk_sel = stm32h7_adc_clk_sel, + .max_clk_rate_hz = 36000000, + .has_syscfg = HAS_VBOOSTER | HAS_ANASWVDD, + .ipid = STM32MP15_IPIDR_NUMBER, + .num_irqs = 2, +}; + +static const struct stm32_adc_priv_cfg stm32mp13_adc_priv_cfg = { + .regs = &stm32mp13_adc_common_regs, + .clk_sel = stm32h7_adc_clk_sel, + .max_clk_rate_hz = 75 * HZ_PER_MHZ, + .ipid = STM32MP13_IPIDR_NUMBER, + .num_irqs = 1, +}; + +static const struct of_device_id stm32_adc_of_match[] = { + { + .compatible = "st,stm32f4-adc-core", + .data = (void *)&stm32f4_adc_priv_cfg + }, { + .compatible = "st,stm32h7-adc-core", + .data = (void *)&stm32h7_adc_priv_cfg + }, { + .compatible = "st,stm32mp1-adc-core", + .data = (void *)&stm32mp1_adc_priv_cfg + }, { + .compatible = "st,stm32mp13-adc-core", + .data = (void *)&stm32mp13_adc_priv_cfg + }, { + }, +}; +MODULE_DEVICE_TABLE(of, stm32_adc_of_match); + +static struct platform_driver stm32_adc_driver = { + .probe = stm32_adc_probe, + .remove = stm32_adc_remove, + .driver = { + .name = "stm32-adc-core", + .of_match_table = stm32_adc_of_match, + .pm = pm_ptr(&stm32_adc_core_pm_ops), + }, +}; +module_platform_driver(stm32_adc_driver); + +MODULE_AUTHOR("Fabrice Gasnier "); +MODULE_DESCRIPTION("STMicroelectronics STM32 ADC core driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:stm32-adc-core"); diff --git a/drivers/iio/adc/stm32-adc-core.h b/drivers/iio/adc/stm32-adc-core.h new file mode 100644 index 000000000..db50a9f3b --- /dev/null +++ b/drivers/iio/adc/stm32-adc-core.h @@ -0,0 +1,279 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * This file is part of STM32 ADC driver + * + * Copyright (C) 2016, STMicroelectronics - All Rights Reserved + * Author: Fabrice Gasnier . + * + */ + +#ifndef __STM32_ADC_H +#define __STM32_ADC_H + +#include +#include + +/* + * STM32 - ADC global register map + * ________________________________________________________ + * | Offset | Register | + * -------------------------------------------------------- + * | 0x000 | Master ADC1 | + * -------------------------------------------------------- + * | 0x100 | Slave ADC2 | + * -------------------------------------------------------- + * | 0x200 | Slave ADC3 | + * -------------------------------------------------------- + * | 0x300 | Master & Slave common regs | + * -------------------------------------------------------- + */ +/* Maximum ADC instances number per ADC block for all supported SoCs */ +#define STM32_ADC_MAX_ADCS 3 +#define STM32_ADC_OFFSET 0x100 +#define STM32_ADCX_COMN_OFFSET 0x300 + +/* STM32F4 - Registers for each ADC instance */ +#define STM32F4_ADC_SR 0x00 +#define STM32F4_ADC_CR1 0x04 +#define STM32F4_ADC_CR2 0x08 +#define STM32F4_ADC_SMPR1 0x0C +#define STM32F4_ADC_SMPR2 0x10 +#define STM32F4_ADC_HTR 0x24 +#define STM32F4_ADC_LTR 0x28 +#define STM32F4_ADC_SQR1 0x2C +#define STM32F4_ADC_SQR2 0x30 +#define STM32F4_ADC_SQR3 0x34 +#define STM32F4_ADC_JSQR 0x38 +#define STM32F4_ADC_JDR1 0x3C +#define STM32F4_ADC_JDR2 0x40 +#define STM32F4_ADC_JDR3 0x44 +#define STM32F4_ADC_JDR4 0x48 +#define STM32F4_ADC_DR 0x4C + +/* STM32F4 - common registers for all ADC instances: 1, 2 & 3 */ +#define STM32F4_ADC_CSR (STM32_ADCX_COMN_OFFSET + 0x00) +#define STM32F4_ADC_CCR (STM32_ADCX_COMN_OFFSET + 0x04) + +/* STM32F4_ADC_SR - bit fields */ +#define STM32F4_OVR BIT(5) +#define STM32F4_STRT BIT(4) +#define STM32F4_EOC BIT(1) + +/* STM32F4_ADC_CR1 - bit fields */ +#define STM32F4_OVRIE BIT(26) +#define STM32F4_RES_SHIFT 24 +#define STM32F4_RES_MASK GENMASK(25, 24) +#define STM32F4_SCAN BIT(8) +#define STM32F4_EOCIE BIT(5) + +/* STM32F4_ADC_CR2 - bit fields */ +#define STM32F4_SWSTART BIT(30) +#define STM32F4_EXTEN_SHIFT 28 +#define STM32F4_EXTEN_MASK GENMASK(29, 28) +#define STM32F4_EXTSEL_SHIFT 24 +#define STM32F4_EXTSEL_MASK GENMASK(27, 24) +#define STM32F4_EOCS BIT(10) +#define STM32F4_DDS BIT(9) +#define STM32F4_DMA BIT(8) +#define STM32F4_ADON BIT(0) + +/* STM32F4_ADC_CSR - bit fields */ +#define STM32F4_OVR3 BIT(21) +#define STM32F4_EOC3 BIT(17) +#define STM32F4_OVR2 BIT(13) +#define STM32F4_EOC2 BIT(9) +#define STM32F4_OVR1 BIT(5) +#define STM32F4_EOC1 BIT(1) + +/* STM32F4_ADC_CCR - bit fields */ +#define STM32F4_ADC_ADCPRE_SHIFT 16 +#define STM32F4_ADC_ADCPRE_MASK GENMASK(17, 16) + +/* STM32H7 - Registers for each ADC instance */ +#define STM32H7_ADC_ISR 0x00 +#define STM32H7_ADC_IER 0x04 +#define STM32H7_ADC_CR 0x08 +#define STM32H7_ADC_CFGR 0x0C +#define STM32H7_ADC_CFGR2 0x10 +#define STM32H7_ADC_SMPR1 0x14 +#define STM32H7_ADC_SMPR2 0x18 +#define STM32H7_ADC_PCSEL 0x1C +#define STM32H7_ADC_SQR1 0x30 +#define STM32H7_ADC_SQR2 0x34 +#define STM32H7_ADC_SQR3 0x38 +#define STM32H7_ADC_SQR4 0x3C +#define STM32H7_ADC_DR 0x40 +#define STM32H7_ADC_DIFSEL 0xC0 +#define STM32H7_ADC_CALFACT 0xC4 +#define STM32H7_ADC_CALFACT2 0xC8 + +/* STM32MP1 - ADC2 instance option register */ +#define STM32MP1_ADC2_OR 0xD0 + +/* STM32MP1 - Identification registers */ +#define STM32MP1_ADC_HWCFGR0 0x3F0 +#define STM32MP1_ADC_VERR 0x3F4 +#define STM32MP1_ADC_IPDR 0x3F8 +#define STM32MP1_ADC_SIDR 0x3FC + +/* STM32MP13 - Registers for each ADC instance */ +#define STM32MP13_ADC_DIFSEL 0xB0 +#define STM32MP13_ADC_CALFACT 0xB4 +#define STM32MP13_ADC2_OR 0xC8 + +/* STM32H7 - common registers for all ADC instances */ +#define STM32H7_ADC_CSR (STM32_ADCX_COMN_OFFSET + 0x00) +#define STM32H7_ADC_CCR (STM32_ADCX_COMN_OFFSET + 0x08) + +/* STM32H7_ADC_ISR - bit fields */ +#define STM32MP1_VREGREADY BIT(12) +#define STM32H7_OVR BIT(4) +#define STM32H7_EOC BIT(2) +#define STM32H7_ADRDY BIT(0) + +/* STM32H7_ADC_IER - bit fields */ +#define STM32H7_OVRIE STM32H7_OVR +#define STM32H7_EOCIE STM32H7_EOC + +/* STM32H7_ADC_CR - bit fields */ +#define STM32H7_ADCAL BIT(31) +#define STM32H7_ADCALDIF BIT(30) +#define STM32H7_DEEPPWD BIT(29) +#define STM32H7_ADVREGEN BIT(28) +#define STM32H7_LINCALRDYW6 BIT(27) +#define STM32H7_LINCALRDYW5 BIT(26) +#define STM32H7_LINCALRDYW4 BIT(25) +#define STM32H7_LINCALRDYW3 BIT(24) +#define STM32H7_LINCALRDYW2 BIT(23) +#define STM32H7_LINCALRDYW1 BIT(22) +#define STM32H7_LINCALRDYW_MASK GENMASK(27, 22) +#define STM32H7_ADCALLIN BIT(16) +#define STM32H7_BOOST BIT(8) +#define STM32H7_ADSTP BIT(4) +#define STM32H7_ADSTART BIT(2) +#define STM32H7_ADDIS BIT(1) +#define STM32H7_ADEN BIT(0) + +/* STM32H7_ADC_CFGR bit fields */ +#define STM32H7_EXTEN_SHIFT 10 +#define STM32H7_EXTEN_MASK GENMASK(11, 10) +#define STM32H7_EXTSEL_SHIFT 5 +#define STM32H7_EXTSEL_MASK GENMASK(9, 5) +#define STM32H7_RES_SHIFT 2 +#define STM32H7_RES_MASK GENMASK(4, 2) +#define STM32H7_DMNGT_SHIFT 0 +#define STM32H7_DMNGT_MASK GENMASK(1, 0) + +/* STM32H7_ADC_CFGR2 bit fields */ +#define STM32H7_OVSR_MASK GENMASK(25, 16) /* Correspond to OSVR field in datasheet */ +#define STM32H7_OVSR(v) FIELD_PREP(STM32H7_OVSR_MASK, v) +#define STM32H7_OVSS_MASK GENMASK(8, 5) +#define STM32H7_OVSS(v) FIELD_PREP(STM32H7_OVSS_MASK, v) +#define STM32H7_ROVSE BIT(0) + +enum stm32h7_adc_dmngt { + STM32H7_DMNGT_DR_ONLY, /* Regular data in DR only */ + STM32H7_DMNGT_DMA_ONESHOT, /* DMA one shot mode */ + STM32H7_DMNGT_DFSDM, /* DFSDM mode */ + STM32H7_DMNGT_DMA_CIRC, /* DMA circular mode */ +}; + +/* STM32H7_ADC_DIFSEL - bit fields */ +#define STM32H7_DIFSEL_MASK GENMASK(19, 0) + +/* STM32H7_ADC_CALFACT - bit fields */ +#define STM32H7_CALFACT_D_SHIFT 16 +#define STM32H7_CALFACT_D_MASK GENMASK(26, 16) +#define STM32H7_CALFACT_S_SHIFT 0 +#define STM32H7_CALFACT_S_MASK GENMASK(10, 0) + +/* STM32H7_ADC_CALFACT2 - bit fields */ +#define STM32H7_LINCALFACT_SHIFT 0 +#define STM32H7_LINCALFACT_MASK GENMASK(29, 0) + +/* STM32H7_ADC_CSR - bit fields */ +#define STM32H7_OVR_SLV BIT(20) +#define STM32H7_EOC_SLV BIT(18) +#define STM32H7_OVR_MST BIT(4) +#define STM32H7_EOC_MST BIT(2) + +/* STM32H7_ADC_CCR - bit fields */ +#define STM32H7_VBATEN BIT(24) +#define STM32H7_VREFEN BIT(22) +#define STM32H7_PRESC_SHIFT 18 +#define STM32H7_PRESC_MASK GENMASK(21, 18) +#define STM32H7_CKMODE_SHIFT 16 +#define STM32H7_CKMODE_MASK GENMASK(17, 16) + +/* STM32MP1_ADC2_OR - bit fields */ +#define STM32MP1_VDDCOREEN BIT(0) + +/* STM32MP1_ADC_HWCFGR0 - bit fields */ +#define STM32MP1_ADCNUM_SHIFT 0 +#define STM32MP1_ADCNUM_MASK GENMASK(3, 0) +#define STM32MP1_MULPIPE_SHIFT 4 +#define STM32MP1_MULPIPE_MASK GENMASK(7, 4) +#define STM32MP1_OPBITS_SHIFT 8 +#define STM32MP1_OPBITS_MASK GENMASK(11, 8) +#define STM32MP1_IDLEVALUE_SHIFT 12 +#define STM32MP1_IDLEVALUE_MASK GENMASK(15, 12) + +/* STM32MP1_ADC_VERR - bit fields */ +#define STM32MP1_MINREV_SHIFT 0 +#define STM32MP1_MINREV_MASK GENMASK(3, 0) +#define STM32MP1_MAJREV_SHIFT 4 +#define STM32MP1_MAJREV_MASK GENMASK(7, 4) + +/* STM32MP1_ADC_IPDR - bit fields */ +#define STM32MP1_IPIDR_MASK GENMASK(31, 0) + +/* STM32MP1_ADC_SIDR - bit fields */ +#define STM32MP1_SIDR_MASK GENMASK(31, 0) + +/* STM32MP13_ADC_CFGR specific bit fields */ +#define STM32MP13_DMAEN BIT(0) +#define STM32MP13_DMACFG BIT(1) +#define STM32MP13_DFSDMCFG BIT(2) +#define STM32MP13_RES_SHIFT 3 +#define STM32MP13_RES_MASK GENMASK(4, 3) + +/* STM32MP13_ADC_CFGR2 bit fields */ +#define STM32MP13_OVSR_MASK GENMASK(4, 2) +#define STM32MP13_OVSR(v) FIELD_PREP(STM32MP13_OVSR_MASK, v) +#define STM32MP13_OVSS_MASK GENMASK(8, 5) +#define STM32MP13_OVSS(v) FIELD_PREP(STM32MP13_OVSS_MASK, v) + +/* STM32MP13_ADC_DIFSEL - bit fields */ +#define STM32MP13_DIFSEL_MASK GENMASK(18, 0) + +/* STM32MP13_ADC_CALFACT - bit fields */ +#define STM32MP13_CALFACT_D_SHIFT 16 +#define STM32MP13_CALFACT_D_MASK GENMASK(22, 16) +#define STM32MP13_CALFACT_S_SHIFT 0 +#define STM32MP13_CALFACT_S_MASK GENMASK(6, 0) + +/* STM32MP13_ADC2_OR - bit fields */ +#define STM32MP13_OP2 BIT(2) +#define STM32MP13_OP1 BIT(1) +#define STM32MP13_OP0 BIT(0) + +#define STM32MP15_IPIDR_NUMBER 0x00110005 +#define STM32MP13_IPIDR_NUMBER 0x00110006 + +/** + * struct stm32_adc_common - stm32 ADC driver common data (for all instances) + * @base: control registers base cpu addr + * @phys_base: control registers base physical addr + * @rate: clock rate used for analog circuitry + * @vref_mv: vref voltage (mv) + * @lock: spinlock + */ +struct stm32_adc_common { + void __iomem *base; + phys_addr_t phys_base; + unsigned long rate; + int vref_mv; + spinlock_t lock; /* lock for common register */ +}; + +#endif diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c new file mode 100644 index 000000000..5c6c06b26 --- /dev/null +++ b/drivers/iio/adc/stm32-adc.c @@ -0,0 +1,2791 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This file is part of STM32 ADC driver + * + * Copyright (C) 2016, STMicroelectronics - All Rights Reserved + * Author: Fabrice Gasnier . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "stm32-adc-core.h" + +/* Number of linear calibration shadow registers / LINCALRDYW control bits */ +#define STM32H7_LINCALFACT_NUM 6 + +/* BOOST bit must be set on STM32H7 when ADC clock is above 20MHz */ +#define STM32H7_BOOST_CLKRATE 20000000UL + +#define STM32_ADC_CH_MAX 20 /* max number of channels */ +#define STM32_ADC_CH_SZ 16 /* max channel name size */ +#define STM32_ADC_MAX_SQ 16 /* SQ1..SQ16 */ +#define STM32_ADC_MAX_SMP 7 /* SMPx range is [0..7] */ +#define STM32_ADC_TIMEOUT_US 100000 +#define STM32_ADC_TIMEOUT (msecs_to_jiffies(STM32_ADC_TIMEOUT_US / 1000)) +#define STM32_ADC_HW_STOP_DELAY_MS 100 +#define STM32_ADC_VREFINT_VOLTAGE 3300 + +#define STM32_DMA_BUFFER_SIZE PAGE_SIZE + +/* External trigger enable */ +enum stm32_adc_exten { + STM32_EXTEN_SWTRIG, + STM32_EXTEN_HWTRIG_RISING_EDGE, + STM32_EXTEN_HWTRIG_FALLING_EDGE, + STM32_EXTEN_HWTRIG_BOTH_EDGES, +}; + +/* extsel - trigger mux selection value */ +enum stm32_adc_extsel { + STM32_EXT0, + STM32_EXT1, + STM32_EXT2, + STM32_EXT3, + STM32_EXT4, + STM32_EXT5, + STM32_EXT6, + STM32_EXT7, + STM32_EXT8, + STM32_EXT9, + STM32_EXT10, + STM32_EXT11, + STM32_EXT12, + STM32_EXT13, + STM32_EXT14, + STM32_EXT15, + STM32_EXT16, + STM32_EXT17, + STM32_EXT18, + STM32_EXT19, + STM32_EXT20, +}; + +enum stm32_adc_int_ch { + STM32_ADC_INT_CH_NONE = -1, + STM32_ADC_INT_CH_VDDCORE, + STM32_ADC_INT_CH_VDDCPU, + STM32_ADC_INT_CH_VDDQ_DDR, + STM32_ADC_INT_CH_VREFINT, + STM32_ADC_INT_CH_VBAT, + STM32_ADC_INT_CH_NB, +}; + +/** + * struct stm32_adc_ic - ADC internal channels + * @name: name of the internal channel + * @idx: internal channel enum index + */ +struct stm32_adc_ic { + const char *name; + u32 idx; +}; + +static const struct stm32_adc_ic stm32_adc_ic[STM32_ADC_INT_CH_NB] = { + { "vddcore", STM32_ADC_INT_CH_VDDCORE }, + { "vddcpu", STM32_ADC_INT_CH_VDDCPU }, + { "vddq_ddr", STM32_ADC_INT_CH_VDDQ_DDR }, + { "vrefint", STM32_ADC_INT_CH_VREFINT }, + { "vbat", STM32_ADC_INT_CH_VBAT }, +}; + +/** + * struct stm32_adc_trig_info - ADC trigger info + * @name: name of the trigger, corresponding to its source + * @extsel: trigger selection + */ +struct stm32_adc_trig_info { + const char *name; + enum stm32_adc_extsel extsel; +}; + +/** + * struct stm32_adc_calib - optional adc calibration data + * @lincalfact: Linearity calibration factor + * @lincal_saved: Indicates that linear calibration factors are saved + */ +struct stm32_adc_calib { + u32 lincalfact[STM32H7_LINCALFACT_NUM]; + bool lincal_saved; +}; + +/** + * struct stm32_adc_regs - stm32 ADC misc registers & bitfield desc + * @reg: register offset + * @mask: bitfield mask + * @shift: left shift + */ +struct stm32_adc_regs { + int reg; + int mask; + int shift; +}; + +/** + * struct stm32_adc_vrefint - stm32 ADC internal reference voltage data + * @vrefint_cal: vrefint calibration value from nvmem + * @vrefint_data: vrefint actual value + */ +struct stm32_adc_vrefint { + u32 vrefint_cal; + u32 vrefint_data; +}; + +/** + * struct stm32_adc_regspec - stm32 registers definition + * @dr: data register offset + * @ier_eoc: interrupt enable register & eocie bitfield + * @ier_ovr: interrupt enable register & overrun bitfield + * @isr_eoc: interrupt status register & eoc bitfield + * @isr_ovr: interrupt status register & overrun bitfield + * @sqr: reference to sequence registers array + * @exten: trigger control register & bitfield + * @extsel: trigger selection register & bitfield + * @res: resolution selection register & bitfield + * @difsel: differential mode selection register & bitfield + * @smpr: smpr1 & smpr2 registers offset array + * @smp_bits: smpr1 & smpr2 index and bitfields + * @or_vddcore: option register & vddcore bitfield + * @or_vddcpu: option register & vddcpu bitfield + * @or_vddq_ddr: option register & vddq_ddr bitfield + * @ccr_vbat: common register & vbat bitfield + * @ccr_vref: common register & vrefint bitfield + */ +struct stm32_adc_regspec { + const u32 dr; + const struct stm32_adc_regs ier_eoc; + const struct stm32_adc_regs ier_ovr; + const struct stm32_adc_regs isr_eoc; + const struct stm32_adc_regs isr_ovr; + const struct stm32_adc_regs *sqr; + const struct stm32_adc_regs exten; + const struct stm32_adc_regs extsel; + const struct stm32_adc_regs res; + const struct stm32_adc_regs difsel; + const u32 smpr[2]; + const struct stm32_adc_regs *smp_bits; + const struct stm32_adc_regs or_vddcore; + const struct stm32_adc_regs or_vddcpu; + const struct stm32_adc_regs or_vddq_ddr; + const struct stm32_adc_regs ccr_vbat; + const struct stm32_adc_regs ccr_vref; +}; + +struct stm32_adc; + +/** + * struct stm32_adc_cfg - stm32 compatible configuration data + * @regs: registers descriptions + * @adc_info: per instance input channels definitions + * @trigs: external trigger sources + * @clk_required: clock is required + * @has_vregready: vregready status flag presence + * @has_boostmode: boost mode support flag + * @has_linearcal: linear calibration support flag + * @has_presel: channel preselection support flag + * @has_oversampling: oversampling support flag + * @prepare: optional prepare routine (power-up, enable) + * @start_conv: routine to start conversions + * @stop_conv: routine to stop conversions + * @unprepare: optional unprepare routine (disable, power-down) + * @irq_clear: routine to clear irqs + * @set_ovs: routine to set oversampling configuration + * @smp_cycles: programmable sampling time (ADC clock cycles) + * @ts_int_ch: pointer to array of internal channels minimum sampling time in ns + */ +struct stm32_adc_cfg { + const struct stm32_adc_regspec *regs; + const struct stm32_adc_info *adc_info; + const struct stm32_adc_trig_info *trigs; + bool clk_required; + bool has_vregready; + bool has_boostmode; + bool has_linearcal; + bool has_presel; + bool has_oversampling; + int (*prepare)(struct iio_dev *); + void (*start_conv)(struct iio_dev *, bool dma); + void (*stop_conv)(struct iio_dev *); + void (*unprepare)(struct iio_dev *); + void (*irq_clear)(struct iio_dev *indio_dev, u32 msk); + void (*set_ovs)(struct iio_dev *indio_dev, u32 ovs_idx); + const unsigned int *smp_cycles; + const unsigned int *ts_int_ch; +}; + +/** + * struct stm32_adc - private data of each ADC IIO instance + * @common: reference to ADC block common data + * @offset: ADC instance register offset in ADC block + * @cfg: compatible configuration data + * @completion: end of single conversion completion + * @buffer: data buffer + 8 bytes for timestamp if enabled + * @clk: clock for this adc instance + * @irq: interrupt for this adc instance + * @lock: spinlock + * @bufi: data buffer index + * @num_conv: expected number of scan conversions + * @res: data resolution (e.g. RES bitfield value) + * @trigger_polarity: external trigger polarity (e.g. exten) + * @dma_chan: dma channel + * @rx_buf: dma rx buffer cpu address + * @rx_dma_buf: dma rx buffer bus address + * @rx_buf_sz: dma rx buffer size + * @difsel: bitmask to set single-ended/differential channel + * @pcsel: bitmask to preselect channels on some devices + * @smpr_val: sampling time settings (e.g. smpr1 / smpr2) + * @cal: optional calibration data on some devices + * @vrefint: internal reference voltage data + * @chan_name: channel name array + * @num_diff: number of differential channels + * @int_ch: internal channel indexes array + * @nsmps: number of channels with optional sample time + * @ovs_idx: current oversampling ratio index (in oversampling array) + */ +struct stm32_adc { + struct stm32_adc_common *common; + u32 offset; + const struct stm32_adc_cfg *cfg; + struct completion completion; + u16 buffer[STM32_ADC_MAX_SQ + 4] __aligned(8); + struct clk *clk; + int irq; + spinlock_t lock; /* interrupt lock */ + unsigned int bufi; + unsigned int num_conv; + u32 res; + u32 trigger_polarity; + struct dma_chan *dma_chan; + u8 *rx_buf; + dma_addr_t rx_dma_buf; + unsigned int rx_buf_sz; + u32 difsel; + u32 pcsel; + u32 smpr_val[2]; + struct stm32_adc_calib cal; + struct stm32_adc_vrefint vrefint; + char chan_name[STM32_ADC_CH_MAX][STM32_ADC_CH_SZ]; + u32 num_diff; + int int_ch[STM32_ADC_INT_CH_NB]; + int nsmps; + int ovs_idx; +}; + +struct stm32_adc_diff_channel { + u32 vinp; + u32 vinn; +}; + +/** + * struct stm32_adc_info - stm32 ADC, per instance config data + * @max_channels: Number of channels + * @resolutions: available resolutions + * @oversampling: available oversampling ratios + * @num_res: number of available resolutions + * @num_ovs: number of available oversampling ratios + */ +struct stm32_adc_info { + int max_channels; + const unsigned int *resolutions; + const unsigned int *oversampling; + const unsigned int num_res; + const unsigned int num_ovs; +}; + +static const unsigned int stm32h7_adc_oversampling_avail[] = { + 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, +}; + +static const unsigned int stm32mp13_adc_oversampling_avail[] = { + 1, 2, 4, 8, 16, 32, 64, 128, 256, +}; + +static const unsigned int stm32f4_adc_resolutions[] = { + /* sorted values so the index matches RES[1:0] in STM32F4_ADC_CR1 */ + 12, 10, 8, 6, +}; + +/* stm32f4 can have up to 16 channels */ +static const struct stm32_adc_info stm32f4_adc_info = { + .max_channels = 16, + .resolutions = stm32f4_adc_resolutions, + .num_res = ARRAY_SIZE(stm32f4_adc_resolutions), +}; + +static const unsigned int stm32h7_adc_resolutions[] = { + /* sorted values so the index matches RES[2:0] in STM32H7_ADC_CFGR */ + 16, 14, 12, 10, 8, +}; + +/* stm32h7 can have up to 20 channels */ +static const struct stm32_adc_info stm32h7_adc_info = { + .max_channels = STM32_ADC_CH_MAX, + .resolutions = stm32h7_adc_resolutions, + .oversampling = stm32h7_adc_oversampling_avail, + .num_res = ARRAY_SIZE(stm32h7_adc_resolutions), + .num_ovs = ARRAY_SIZE(stm32h7_adc_oversampling_avail), +}; + +/* stm32mp13 can have up to 19 channels */ +static const struct stm32_adc_info stm32mp13_adc_info = { + .max_channels = 19, + .resolutions = stm32f4_adc_resolutions, + .oversampling = stm32mp13_adc_oversampling_avail, + .num_res = ARRAY_SIZE(stm32f4_adc_resolutions), + .num_ovs = ARRAY_SIZE(stm32mp13_adc_oversampling_avail), +}; + +/* + * stm32f4_sq - describe regular sequence registers + * - L: sequence len (register & bit field) + * - SQ1..SQ16: sequence entries (register & bit field) + */ +static const struct stm32_adc_regs stm32f4_sq[STM32_ADC_MAX_SQ + 1] = { + /* L: len bit field description to be kept as first element */ + { STM32F4_ADC_SQR1, GENMASK(23, 20), 20 }, + /* SQ1..SQ16 registers & bit fields (reg, mask, shift) */ + { STM32F4_ADC_SQR3, GENMASK(4, 0), 0 }, + { STM32F4_ADC_SQR3, GENMASK(9, 5), 5 }, + { STM32F4_ADC_SQR3, GENMASK(14, 10), 10 }, + { STM32F4_ADC_SQR3, GENMASK(19, 15), 15 }, + { STM32F4_ADC_SQR3, GENMASK(24, 20), 20 }, + { STM32F4_ADC_SQR3, GENMASK(29, 25), 25 }, + { STM32F4_ADC_SQR2, GENMASK(4, 0), 0 }, + { STM32F4_ADC_SQR2, GENMASK(9, 5), 5 }, + { STM32F4_ADC_SQR2, GENMASK(14, 10), 10 }, + { STM32F4_ADC_SQR2, GENMASK(19, 15), 15 }, + { STM32F4_ADC_SQR2, GENMASK(24, 20), 20 }, + { STM32F4_ADC_SQR2, GENMASK(29, 25), 25 }, + { STM32F4_ADC_SQR1, GENMASK(4, 0), 0 }, + { STM32F4_ADC_SQR1, GENMASK(9, 5), 5 }, + { STM32F4_ADC_SQR1, GENMASK(14, 10), 10 }, + { STM32F4_ADC_SQR1, GENMASK(19, 15), 15 }, +}; + +/* STM32F4 external trigger sources for all instances */ +static const struct stm32_adc_trig_info stm32f4_adc_trigs[] = { + { TIM1_CH1, STM32_EXT0 }, + { TIM1_CH2, STM32_EXT1 }, + { TIM1_CH3, STM32_EXT2 }, + { TIM2_CH2, STM32_EXT3 }, + { TIM2_CH3, STM32_EXT4 }, + { TIM2_CH4, STM32_EXT5 }, + { TIM2_TRGO, STM32_EXT6 }, + { TIM3_CH1, STM32_EXT7 }, + { TIM3_TRGO, STM32_EXT8 }, + { TIM4_CH4, STM32_EXT9 }, + { TIM5_CH1, STM32_EXT10 }, + { TIM5_CH2, STM32_EXT11 }, + { TIM5_CH3, STM32_EXT12 }, + { TIM8_CH1, STM32_EXT13 }, + { TIM8_TRGO, STM32_EXT14 }, + {}, /* sentinel */ +}; + +/* + * stm32f4_smp_bits[] - describe sampling time register index & bit fields + * Sorted so it can be indexed by channel number. + */ +static const struct stm32_adc_regs stm32f4_smp_bits[] = { + /* STM32F4_ADC_SMPR2: smpr[] index, mask, shift for SMP0 to SMP9 */ + { 1, GENMASK(2, 0), 0 }, + { 1, GENMASK(5, 3), 3 }, + { 1, GENMASK(8, 6), 6 }, + { 1, GENMASK(11, 9), 9 }, + { 1, GENMASK(14, 12), 12 }, + { 1, GENMASK(17, 15), 15 }, + { 1, GENMASK(20, 18), 18 }, + { 1, GENMASK(23, 21), 21 }, + { 1, GENMASK(26, 24), 24 }, + { 1, GENMASK(29, 27), 27 }, + /* STM32F4_ADC_SMPR1, smpr[] index, mask, shift for SMP10 to SMP18 */ + { 0, GENMASK(2, 0), 0 }, + { 0, GENMASK(5, 3), 3 }, + { 0, GENMASK(8, 6), 6 }, + { 0, GENMASK(11, 9), 9 }, + { 0, GENMASK(14, 12), 12 }, + { 0, GENMASK(17, 15), 15 }, + { 0, GENMASK(20, 18), 18 }, + { 0, GENMASK(23, 21), 21 }, + { 0, GENMASK(26, 24), 24 }, +}; + +/* STM32F4 programmable sampling time (ADC clock cycles) */ +static const unsigned int stm32f4_adc_smp_cycles[STM32_ADC_MAX_SMP + 1] = { + 3, 15, 28, 56, 84, 112, 144, 480, +}; + +static const struct stm32_adc_regspec stm32f4_adc_regspec = { + .dr = STM32F4_ADC_DR, + .ier_eoc = { STM32F4_ADC_CR1, STM32F4_EOCIE }, + .ier_ovr = { STM32F4_ADC_CR1, STM32F4_OVRIE }, + .isr_eoc = { STM32F4_ADC_SR, STM32F4_EOC }, + .isr_ovr = { STM32F4_ADC_SR, STM32F4_OVR }, + .sqr = stm32f4_sq, + .exten = { STM32F4_ADC_CR2, STM32F4_EXTEN_MASK, STM32F4_EXTEN_SHIFT }, + .extsel = { STM32F4_ADC_CR2, STM32F4_EXTSEL_MASK, + STM32F4_EXTSEL_SHIFT }, + .res = { STM32F4_ADC_CR1, STM32F4_RES_MASK, STM32F4_RES_SHIFT }, + .smpr = { STM32F4_ADC_SMPR1, STM32F4_ADC_SMPR2 }, + .smp_bits = stm32f4_smp_bits, +}; + +static const struct stm32_adc_regs stm32h7_sq[STM32_ADC_MAX_SQ + 1] = { + /* L: len bit field description to be kept as first element */ + { STM32H7_ADC_SQR1, GENMASK(3, 0), 0 }, + /* SQ1..SQ16 registers & bit fields (reg, mask, shift) */ + { STM32H7_ADC_SQR1, GENMASK(10, 6), 6 }, + { STM32H7_ADC_SQR1, GENMASK(16, 12), 12 }, + { STM32H7_ADC_SQR1, GENMASK(22, 18), 18 }, + { STM32H7_ADC_SQR1, GENMASK(28, 24), 24 }, + { STM32H7_ADC_SQR2, GENMASK(4, 0), 0 }, + { STM32H7_ADC_SQR2, GENMASK(10, 6), 6 }, + { STM32H7_ADC_SQR2, GENMASK(16, 12), 12 }, + { STM32H7_ADC_SQR2, GENMASK(22, 18), 18 }, + { STM32H7_ADC_SQR2, GENMASK(28, 24), 24 }, + { STM32H7_ADC_SQR3, GENMASK(4, 0), 0 }, + { STM32H7_ADC_SQR3, GENMASK(10, 6), 6 }, + { STM32H7_ADC_SQR3, GENMASK(16, 12), 12 }, + { STM32H7_ADC_SQR3, GENMASK(22, 18), 18 }, + { STM32H7_ADC_SQR3, GENMASK(28, 24), 24 }, + { STM32H7_ADC_SQR4, GENMASK(4, 0), 0 }, + { STM32H7_ADC_SQR4, GENMASK(10, 6), 6 }, +}; + +/* STM32H7 external trigger sources for all instances */ +static const struct stm32_adc_trig_info stm32h7_adc_trigs[] = { + { TIM1_CH1, STM32_EXT0 }, + { TIM1_CH2, STM32_EXT1 }, + { TIM1_CH3, STM32_EXT2 }, + { TIM2_CH2, STM32_EXT3 }, + { TIM3_TRGO, STM32_EXT4 }, + { TIM4_CH4, STM32_EXT5 }, + { TIM8_TRGO, STM32_EXT7 }, + { TIM8_TRGO2, STM32_EXT8 }, + { TIM1_TRGO, STM32_EXT9 }, + { TIM1_TRGO2, STM32_EXT10 }, + { TIM2_TRGO, STM32_EXT11 }, + { TIM4_TRGO, STM32_EXT12 }, + { TIM6_TRGO, STM32_EXT13 }, + { TIM15_TRGO, STM32_EXT14 }, + { TIM3_CH4, STM32_EXT15 }, + { LPTIM1_OUT, STM32_EXT18 }, + { LPTIM2_OUT, STM32_EXT19 }, + { LPTIM3_OUT, STM32_EXT20 }, + { } +}; + +/* + * stm32h7_smp_bits - describe sampling time register index & bit fields + * Sorted so it can be indexed by channel number. + */ +static const struct stm32_adc_regs stm32h7_smp_bits[] = { + /* STM32H7_ADC_SMPR1, smpr[] index, mask, shift for SMP0 to SMP9 */ + { 0, GENMASK(2, 0), 0 }, + { 0, GENMASK(5, 3), 3 }, + { 0, GENMASK(8, 6), 6 }, + { 0, GENMASK(11, 9), 9 }, + { 0, GENMASK(14, 12), 12 }, + { 0, GENMASK(17, 15), 15 }, + { 0, GENMASK(20, 18), 18 }, + { 0, GENMASK(23, 21), 21 }, + { 0, GENMASK(26, 24), 24 }, + { 0, GENMASK(29, 27), 27 }, + /* STM32H7_ADC_SMPR2, smpr[] index, mask, shift for SMP10 to SMP19 */ + { 1, GENMASK(2, 0), 0 }, + { 1, GENMASK(5, 3), 3 }, + { 1, GENMASK(8, 6), 6 }, + { 1, GENMASK(11, 9), 9 }, + { 1, GENMASK(14, 12), 12 }, + { 1, GENMASK(17, 15), 15 }, + { 1, GENMASK(20, 18), 18 }, + { 1, GENMASK(23, 21), 21 }, + { 1, GENMASK(26, 24), 24 }, + { 1, GENMASK(29, 27), 27 }, +}; + +/* STM32H7 programmable sampling time (ADC clock cycles, rounded down) */ +static const unsigned int stm32h7_adc_smp_cycles[STM32_ADC_MAX_SMP + 1] = { + 1, 2, 8, 16, 32, 64, 387, 810, +}; + +static const struct stm32_adc_regspec stm32h7_adc_regspec = { + .dr = STM32H7_ADC_DR, + .ier_eoc = { STM32H7_ADC_IER, STM32H7_EOCIE }, + .ier_ovr = { STM32H7_ADC_IER, STM32H7_OVRIE }, + .isr_eoc = { STM32H7_ADC_ISR, STM32H7_EOC }, + .isr_ovr = { STM32H7_ADC_ISR, STM32H7_OVR }, + .sqr = stm32h7_sq, + .exten = { STM32H7_ADC_CFGR, STM32H7_EXTEN_MASK, STM32H7_EXTEN_SHIFT }, + .extsel = { STM32H7_ADC_CFGR, STM32H7_EXTSEL_MASK, + STM32H7_EXTSEL_SHIFT }, + .res = { STM32H7_ADC_CFGR, STM32H7_RES_MASK, STM32H7_RES_SHIFT }, + .difsel = { STM32H7_ADC_DIFSEL, STM32H7_DIFSEL_MASK}, + .smpr = { STM32H7_ADC_SMPR1, STM32H7_ADC_SMPR2 }, + .smp_bits = stm32h7_smp_bits, +}; + +/* STM32MP13 programmable sampling time (ADC clock cycles, rounded down) */ +static const unsigned int stm32mp13_adc_smp_cycles[STM32_ADC_MAX_SMP + 1] = { + 2, 6, 12, 24, 47, 92, 247, 640, +}; + +static const struct stm32_adc_regspec stm32mp13_adc_regspec = { + .dr = STM32H7_ADC_DR, + .ier_eoc = { STM32H7_ADC_IER, STM32H7_EOCIE }, + .ier_ovr = { STM32H7_ADC_IER, STM32H7_OVRIE }, + .isr_eoc = { STM32H7_ADC_ISR, STM32H7_EOC }, + .isr_ovr = { STM32H7_ADC_ISR, STM32H7_OVR }, + .sqr = stm32h7_sq, + .exten = { STM32H7_ADC_CFGR, STM32H7_EXTEN_MASK, STM32H7_EXTEN_SHIFT }, + .extsel = { STM32H7_ADC_CFGR, STM32H7_EXTSEL_MASK, + STM32H7_EXTSEL_SHIFT }, + .res = { STM32H7_ADC_CFGR, STM32MP13_RES_MASK, STM32MP13_RES_SHIFT }, + .difsel = { STM32MP13_ADC_DIFSEL, STM32MP13_DIFSEL_MASK}, + .smpr = { STM32H7_ADC_SMPR1, STM32H7_ADC_SMPR2 }, + .smp_bits = stm32h7_smp_bits, + .or_vddcore = { STM32MP13_ADC2_OR, STM32MP13_OP0 }, + .or_vddcpu = { STM32MP13_ADC2_OR, STM32MP13_OP1 }, + .or_vddq_ddr = { STM32MP13_ADC2_OR, STM32MP13_OP2 }, + .ccr_vbat = { STM32H7_ADC_CCR, STM32H7_VBATEN }, + .ccr_vref = { STM32H7_ADC_CCR, STM32H7_VREFEN }, +}; + +static const struct stm32_adc_regspec stm32mp1_adc_regspec = { + .dr = STM32H7_ADC_DR, + .ier_eoc = { STM32H7_ADC_IER, STM32H7_EOCIE }, + .ier_ovr = { STM32H7_ADC_IER, STM32H7_OVRIE }, + .isr_eoc = { STM32H7_ADC_ISR, STM32H7_EOC }, + .isr_ovr = { STM32H7_ADC_ISR, STM32H7_OVR }, + .sqr = stm32h7_sq, + .exten = { STM32H7_ADC_CFGR, STM32H7_EXTEN_MASK, STM32H7_EXTEN_SHIFT }, + .extsel = { STM32H7_ADC_CFGR, STM32H7_EXTSEL_MASK, + STM32H7_EXTSEL_SHIFT }, + .res = { STM32H7_ADC_CFGR, STM32H7_RES_MASK, STM32H7_RES_SHIFT }, + .difsel = { STM32H7_ADC_DIFSEL, STM32H7_DIFSEL_MASK}, + .smpr = { STM32H7_ADC_SMPR1, STM32H7_ADC_SMPR2 }, + .smp_bits = stm32h7_smp_bits, + .or_vddcore = { STM32MP1_ADC2_OR, STM32MP1_VDDCOREEN }, + .ccr_vbat = { STM32H7_ADC_CCR, STM32H7_VBATEN }, + .ccr_vref = { STM32H7_ADC_CCR, STM32H7_VREFEN }, +}; + +/* + * STM32 ADC registers access routines + * @adc: stm32 adc instance + * @reg: reg offset in adc instance + * + * Note: All instances share same base, with 0x0, 0x100 or 0x200 offset resp. + * for adc1, adc2 and adc3. + */ +static u32 stm32_adc_readl(struct stm32_adc *adc, u32 reg) +{ + return readl_relaxed(adc->common->base + adc->offset + reg); +} + +#define stm32_adc_readl_addr(addr) stm32_adc_readl(adc, addr) + +#define stm32_adc_readl_poll_timeout(reg, val, cond, sleep_us, timeout_us) \ + readx_poll_timeout(stm32_adc_readl_addr, reg, val, \ + cond, sleep_us, timeout_us) + +static u16 stm32_adc_readw(struct stm32_adc *adc, u32 reg) +{ + return readw_relaxed(adc->common->base + adc->offset + reg); +} + +static void stm32_adc_writel(struct stm32_adc *adc, u32 reg, u32 val) +{ + writel_relaxed(val, adc->common->base + adc->offset + reg); +} + +static void stm32_adc_set_bits(struct stm32_adc *adc, u32 reg, u32 bits) +{ + unsigned long flags; + + spin_lock_irqsave(&adc->lock, flags); + stm32_adc_writel(adc, reg, stm32_adc_readl(adc, reg) | bits); + spin_unlock_irqrestore(&adc->lock, flags); +} + +static void stm32_adc_set_bits_common(struct stm32_adc *adc, u32 reg, u32 bits) +{ + spin_lock(&adc->common->lock); + writel_relaxed(readl_relaxed(adc->common->base + reg) | bits, + adc->common->base + reg); + spin_unlock(&adc->common->lock); +} + +static void stm32_adc_clr_bits(struct stm32_adc *adc, u32 reg, u32 bits) +{ + unsigned long flags; + + spin_lock_irqsave(&adc->lock, flags); + stm32_adc_writel(adc, reg, stm32_adc_readl(adc, reg) & ~bits); + spin_unlock_irqrestore(&adc->lock, flags); +} + +static void stm32_adc_clr_bits_common(struct stm32_adc *adc, u32 reg, u32 bits) +{ + spin_lock(&adc->common->lock); + writel_relaxed(readl_relaxed(adc->common->base + reg) & ~bits, + adc->common->base + reg); + spin_unlock(&adc->common->lock); +} + +/** + * stm32_adc_conv_irq_enable() - Enable end of conversion interrupt + * @adc: stm32 adc instance + */ +static void stm32_adc_conv_irq_enable(struct stm32_adc *adc) +{ + stm32_adc_set_bits(adc, adc->cfg->regs->ier_eoc.reg, + adc->cfg->regs->ier_eoc.mask); +}; + +/** + * stm32_adc_conv_irq_disable() - Disable end of conversion interrupt + * @adc: stm32 adc instance + */ +static void stm32_adc_conv_irq_disable(struct stm32_adc *adc) +{ + stm32_adc_clr_bits(adc, adc->cfg->regs->ier_eoc.reg, + adc->cfg->regs->ier_eoc.mask); +} + +static void stm32_adc_ovr_irq_enable(struct stm32_adc *adc) +{ + stm32_adc_set_bits(adc, adc->cfg->regs->ier_ovr.reg, + adc->cfg->regs->ier_ovr.mask); +} + +static void stm32_adc_ovr_irq_disable(struct stm32_adc *adc) +{ + stm32_adc_clr_bits(adc, adc->cfg->regs->ier_ovr.reg, + adc->cfg->regs->ier_ovr.mask); +} + +static void stm32_adc_set_res(struct stm32_adc *adc) +{ + const struct stm32_adc_regs *res = &adc->cfg->regs->res; + u32 val; + + val = stm32_adc_readl(adc, res->reg); + val = (val & ~res->mask) | (adc->res << res->shift); + stm32_adc_writel(adc, res->reg, val); +} + +static int stm32_adc_hw_stop(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct stm32_adc *adc = iio_priv(indio_dev); + + if (adc->cfg->unprepare) + adc->cfg->unprepare(indio_dev); + + clk_disable_unprepare(adc->clk); + + return 0; +} + +static int stm32_adc_hw_start(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct stm32_adc *adc = iio_priv(indio_dev); + int ret; + + ret = clk_prepare_enable(adc->clk); + if (ret) + return ret; + + stm32_adc_set_res(adc); + + if (adc->cfg->prepare) { + ret = adc->cfg->prepare(indio_dev); + if (ret) + goto err_clk_dis; + } + + return 0; + +err_clk_dis: + clk_disable_unprepare(adc->clk); + + return ret; +} + +static void stm32_adc_int_ch_enable(struct iio_dev *indio_dev) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + u32 i; + + for (i = 0; i < STM32_ADC_INT_CH_NB; i++) { + if (adc->int_ch[i] == STM32_ADC_INT_CH_NONE) + continue; + + switch (i) { + case STM32_ADC_INT_CH_VDDCORE: + dev_dbg(&indio_dev->dev, "Enable VDDCore\n"); + stm32_adc_set_bits(adc, adc->cfg->regs->or_vddcore.reg, + adc->cfg->regs->or_vddcore.mask); + break; + case STM32_ADC_INT_CH_VDDCPU: + dev_dbg(&indio_dev->dev, "Enable VDDCPU\n"); + stm32_adc_set_bits(adc, adc->cfg->regs->or_vddcpu.reg, + adc->cfg->regs->or_vddcpu.mask); + break; + case STM32_ADC_INT_CH_VDDQ_DDR: + dev_dbg(&indio_dev->dev, "Enable VDDQ_DDR\n"); + stm32_adc_set_bits(adc, adc->cfg->regs->or_vddq_ddr.reg, + adc->cfg->regs->or_vddq_ddr.mask); + break; + case STM32_ADC_INT_CH_VREFINT: + dev_dbg(&indio_dev->dev, "Enable VREFInt\n"); + stm32_adc_set_bits_common(adc, adc->cfg->regs->ccr_vref.reg, + adc->cfg->regs->ccr_vref.mask); + break; + case STM32_ADC_INT_CH_VBAT: + dev_dbg(&indio_dev->dev, "Enable VBAT\n"); + stm32_adc_set_bits_common(adc, adc->cfg->regs->ccr_vbat.reg, + adc->cfg->regs->ccr_vbat.mask); + break; + } + } +} + +static void stm32_adc_int_ch_disable(struct stm32_adc *adc) +{ + u32 i; + + for (i = 0; i < STM32_ADC_INT_CH_NB; i++) { + if (adc->int_ch[i] == STM32_ADC_INT_CH_NONE) + continue; + + switch (i) { + case STM32_ADC_INT_CH_VDDCORE: + stm32_adc_clr_bits(adc, adc->cfg->regs->or_vddcore.reg, + adc->cfg->regs->or_vddcore.mask); + break; + case STM32_ADC_INT_CH_VDDCPU: + stm32_adc_clr_bits(adc, adc->cfg->regs->or_vddcpu.reg, + adc->cfg->regs->or_vddcpu.mask); + break; + case STM32_ADC_INT_CH_VDDQ_DDR: + stm32_adc_clr_bits(adc, adc->cfg->regs->or_vddq_ddr.reg, + adc->cfg->regs->or_vddq_ddr.mask); + break; + case STM32_ADC_INT_CH_VREFINT: + stm32_adc_clr_bits_common(adc, adc->cfg->regs->ccr_vref.reg, + adc->cfg->regs->ccr_vref.mask); + break; + case STM32_ADC_INT_CH_VBAT: + stm32_adc_clr_bits_common(adc, adc->cfg->regs->ccr_vbat.reg, + adc->cfg->regs->ccr_vbat.mask); + break; + } + } +} + +/** + * stm32f4_adc_start_conv() - Start conversions for regular channels. + * @indio_dev: IIO device instance + * @dma: use dma to transfer conversion result + * + * Start conversions for regular channels. + * Also take care of normal or DMA mode. Circular DMA may be used for regular + * conversions, in IIO buffer modes. Otherwise, use ADC interrupt with direct + * DR read instead (e.g. read_raw, or triggered buffer mode without DMA). + */ +static void stm32f4_adc_start_conv(struct iio_dev *indio_dev, bool dma) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + + stm32_adc_set_bits(adc, STM32F4_ADC_CR1, STM32F4_SCAN); + + if (dma) + stm32_adc_set_bits(adc, STM32F4_ADC_CR2, + STM32F4_DMA | STM32F4_DDS); + + stm32_adc_set_bits(adc, STM32F4_ADC_CR2, STM32F4_EOCS | STM32F4_ADON); + + /* Wait for Power-up time (tSTAB from datasheet) */ + usleep_range(2, 3); + + /* Software start ? (e.g. trigger detection disabled ?) */ + if (!(stm32_adc_readl(adc, STM32F4_ADC_CR2) & STM32F4_EXTEN_MASK)) + stm32_adc_set_bits(adc, STM32F4_ADC_CR2, STM32F4_SWSTART); +} + +static void stm32f4_adc_stop_conv(struct iio_dev *indio_dev) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + + stm32_adc_clr_bits(adc, STM32F4_ADC_CR2, STM32F4_EXTEN_MASK); + stm32_adc_clr_bits(adc, STM32F4_ADC_SR, STM32F4_STRT); + + stm32_adc_clr_bits(adc, STM32F4_ADC_CR1, STM32F4_SCAN); + stm32_adc_clr_bits(adc, STM32F4_ADC_CR2, + STM32F4_ADON | STM32F4_DMA | STM32F4_DDS); +} + +static void stm32f4_adc_irq_clear(struct iio_dev *indio_dev, u32 msk) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + + stm32_adc_clr_bits(adc, adc->cfg->regs->isr_eoc.reg, msk); +} + +static void stm32h7_adc_start_conv(struct iio_dev *indio_dev, bool dma) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + enum stm32h7_adc_dmngt dmngt; + unsigned long flags; + u32 val; + + if (dma) + dmngt = STM32H7_DMNGT_DMA_CIRC; + else + dmngt = STM32H7_DMNGT_DR_ONLY; + + spin_lock_irqsave(&adc->lock, flags); + val = stm32_adc_readl(adc, STM32H7_ADC_CFGR); + val = (val & ~STM32H7_DMNGT_MASK) | (dmngt << STM32H7_DMNGT_SHIFT); + stm32_adc_writel(adc, STM32H7_ADC_CFGR, val); + spin_unlock_irqrestore(&adc->lock, flags); + + stm32_adc_set_bits(adc, STM32H7_ADC_CR, STM32H7_ADSTART); +} + +static void stm32h7_adc_stop_conv(struct iio_dev *indio_dev) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + int ret; + u32 val; + + stm32_adc_set_bits(adc, STM32H7_ADC_CR, STM32H7_ADSTP); + + ret = stm32_adc_readl_poll_timeout(STM32H7_ADC_CR, val, + !(val & (STM32H7_ADSTART)), + 100, STM32_ADC_TIMEOUT_US); + if (ret) + dev_warn(&indio_dev->dev, "stop failed\n"); + + /* STM32H7_DMNGT_MASK covers STM32MP13_DMAEN & STM32MP13_DMACFG */ + stm32_adc_clr_bits(adc, STM32H7_ADC_CFGR, STM32H7_DMNGT_MASK); +} + +static void stm32h7_adc_irq_clear(struct iio_dev *indio_dev, u32 msk) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + /* On STM32H7 IRQs are cleared by writing 1 into ISR register */ + stm32_adc_set_bits(adc, adc->cfg->regs->isr_eoc.reg, msk); +} + +static void stm32mp13_adc_start_conv(struct iio_dev *indio_dev, bool dma) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + + if (dma) + stm32_adc_set_bits(adc, STM32H7_ADC_CFGR, + STM32MP13_DMAEN | STM32MP13_DMACFG); + + stm32_adc_set_bits(adc, STM32H7_ADC_CR, STM32H7_ADSTART); +} + +static void stm32h7_adc_set_ovs(struct iio_dev *indio_dev, u32 ovs_idx) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + u32 ovsr_bits, bits, msk; + + msk = STM32H7_ROVSE | STM32H7_OVSR_MASK | STM32H7_OVSS_MASK; + stm32_adc_clr_bits(adc, STM32H7_ADC_CFGR2, msk); + + if (!ovs_idx) + return; + + /* + * Only the oversampling ratios corresponding to 2^ovs_idx are exposed in sysfs. + * Oversampling ratios [2,3,...,1024] are mapped on OVSR register values [1,2,...,1023]. + * OVSR = 2^ovs_idx - 1 + * These ratio increase the resolution by ovs_idx bits. Apply a right shift to keep initial + * resolution given by "assigned-resolution-bits" property. + * OVSS = ovs_idx + */ + ovsr_bits = GENMASK(ovs_idx - 1, 0); + bits = STM32H7_ROVSE | STM32H7_OVSS(ovs_idx) | STM32H7_OVSR(ovsr_bits); + + stm32_adc_set_bits(adc, STM32H7_ADC_CFGR2, bits & msk); +} + +static void stm32mp13_adc_set_ovs(struct iio_dev *indio_dev, u32 ovs_idx) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + u32 bits, msk; + + msk = STM32H7_ROVSE | STM32MP13_OVSR_MASK | STM32MP13_OVSS_MASK; + stm32_adc_clr_bits(adc, STM32H7_ADC_CFGR2, msk); + + if (!ovs_idx) + return; + + /* + * The oversampling ratios [2,4,8,..,256] are mapped on OVSR register values [0,1,...,7]. + * OVSR = ovs_idx - 1 + * These ratio increase the resolution by ovs_idx bits. Apply a right shift to keep initial + * resolution given by "assigned-resolution-bits" property. + * OVSS = ovs_idx + */ + bits = STM32H7_ROVSE | STM32MP13_OVSS(ovs_idx); + if (ovs_idx - 1) + bits |= STM32MP13_OVSR(ovs_idx - 1); + + stm32_adc_set_bits(adc, STM32H7_ADC_CFGR2, bits & msk); +} + +static int stm32h7_adc_exit_pwr_down(struct iio_dev *indio_dev) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + int ret; + u32 val; + + /* Exit deep power down, then enable ADC voltage regulator */ + stm32_adc_clr_bits(adc, STM32H7_ADC_CR, STM32H7_DEEPPWD); + stm32_adc_set_bits(adc, STM32H7_ADC_CR, STM32H7_ADVREGEN); + + if (adc->cfg->has_boostmode && + adc->common->rate > STM32H7_BOOST_CLKRATE) + stm32_adc_set_bits(adc, STM32H7_ADC_CR, STM32H7_BOOST); + + /* Wait for startup time */ + if (!adc->cfg->has_vregready) { + usleep_range(10, 20); + return 0; + } + + ret = stm32_adc_readl_poll_timeout(STM32H7_ADC_ISR, val, + val & STM32MP1_VREGREADY, 100, + STM32_ADC_TIMEOUT_US); + if (ret) { + stm32_adc_set_bits(adc, STM32H7_ADC_CR, STM32H7_DEEPPWD); + dev_err(&indio_dev->dev, "Failed to exit power down\n"); + } + + return ret; +} + +static void stm32h7_adc_enter_pwr_down(struct stm32_adc *adc) +{ + if (adc->cfg->has_boostmode) + stm32_adc_clr_bits(adc, STM32H7_ADC_CR, STM32H7_BOOST); + + /* Setting DEEPPWD disables ADC vreg and clears ADVREGEN */ + stm32_adc_set_bits(adc, STM32H7_ADC_CR, STM32H7_DEEPPWD); +} + +static int stm32h7_adc_enable(struct iio_dev *indio_dev) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + int ret; + u32 val; + + stm32_adc_set_bits(adc, STM32H7_ADC_CR, STM32H7_ADEN); + + /* Poll for ADRDY to be set (after adc startup time) */ + ret = stm32_adc_readl_poll_timeout(STM32H7_ADC_ISR, val, + val & STM32H7_ADRDY, + 100, STM32_ADC_TIMEOUT_US); + if (ret) { + stm32_adc_set_bits(adc, STM32H7_ADC_CR, STM32H7_ADDIS); + dev_err(&indio_dev->dev, "Failed to enable ADC\n"); + } else { + /* Clear ADRDY by writing one */ + stm32_adc_set_bits(adc, STM32H7_ADC_ISR, STM32H7_ADRDY); + } + + return ret; +} + +static void stm32h7_adc_disable(struct iio_dev *indio_dev) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + int ret; + u32 val; + + if (!(stm32_adc_readl(adc, STM32H7_ADC_CR) & STM32H7_ADEN)) + return; + + /* Disable ADC and wait until it's effectively disabled */ + stm32_adc_set_bits(adc, STM32H7_ADC_CR, STM32H7_ADDIS); + ret = stm32_adc_readl_poll_timeout(STM32H7_ADC_CR, val, + !(val & STM32H7_ADEN), 100, + STM32_ADC_TIMEOUT_US); + if (ret) + dev_warn(&indio_dev->dev, "Failed to disable\n"); +} + +/** + * stm32h7_adc_read_selfcalib() - read calibration shadow regs, save result + * @indio_dev: IIO device instance + * Note: Must be called once ADC is enabled, so LINCALRDYW[1..6] are writable + */ +static int stm32h7_adc_read_selfcalib(struct iio_dev *indio_dev) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + int i, ret; + u32 lincalrdyw_mask, val; + + /* Read linearity calibration */ + lincalrdyw_mask = STM32H7_LINCALRDYW6; + for (i = STM32H7_LINCALFACT_NUM - 1; i >= 0; i--) { + /* Clear STM32H7_LINCALRDYW[6..1]: transfer calib to CALFACT2 */ + stm32_adc_clr_bits(adc, STM32H7_ADC_CR, lincalrdyw_mask); + + /* Poll: wait calib data to be ready in CALFACT2 register */ + ret = stm32_adc_readl_poll_timeout(STM32H7_ADC_CR, val, + !(val & lincalrdyw_mask), + 100, STM32_ADC_TIMEOUT_US); + if (ret) { + dev_err(&indio_dev->dev, "Failed to read calfact\n"); + return ret; + } + + val = stm32_adc_readl(adc, STM32H7_ADC_CALFACT2); + adc->cal.lincalfact[i] = (val & STM32H7_LINCALFACT_MASK); + adc->cal.lincalfact[i] >>= STM32H7_LINCALFACT_SHIFT; + + lincalrdyw_mask >>= 1; + } + adc->cal.lincal_saved = true; + + return 0; +} + +/** + * stm32h7_adc_restore_selfcalib() - Restore saved self-calibration result + * @indio_dev: IIO device instance + * Note: ADC must be enabled, with no on-going conversions. + */ +static int stm32h7_adc_restore_selfcalib(struct iio_dev *indio_dev) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + int i, ret; + u32 lincalrdyw_mask, val; + + lincalrdyw_mask = STM32H7_LINCALRDYW6; + for (i = STM32H7_LINCALFACT_NUM - 1; i >= 0; i--) { + /* + * Write saved calibration data to shadow registers: + * Write CALFACT2, and set LINCALRDYW[6..1] bit to trigger + * data write. Then poll to wait for complete transfer. + */ + val = adc->cal.lincalfact[i] << STM32H7_LINCALFACT_SHIFT; + stm32_adc_writel(adc, STM32H7_ADC_CALFACT2, val); + stm32_adc_set_bits(adc, STM32H7_ADC_CR, lincalrdyw_mask); + ret = stm32_adc_readl_poll_timeout(STM32H7_ADC_CR, val, + val & lincalrdyw_mask, + 100, STM32_ADC_TIMEOUT_US); + if (ret) { + dev_err(&indio_dev->dev, "Failed to write calfact\n"); + return ret; + } + + /* + * Read back calibration data, has two effects: + * - It ensures bits LINCALRDYW[6..1] are kept cleared + * for next time calibration needs to be restored. + * - BTW, bit clear triggers a read, then check data has been + * correctly written. + */ + stm32_adc_clr_bits(adc, STM32H7_ADC_CR, lincalrdyw_mask); + ret = stm32_adc_readl_poll_timeout(STM32H7_ADC_CR, val, + !(val & lincalrdyw_mask), + 100, STM32_ADC_TIMEOUT_US); + if (ret) { + dev_err(&indio_dev->dev, "Failed to read calfact\n"); + return ret; + } + val = stm32_adc_readl(adc, STM32H7_ADC_CALFACT2); + if (val != adc->cal.lincalfact[i] << STM32H7_LINCALFACT_SHIFT) { + dev_err(&indio_dev->dev, "calfact not consistent\n"); + return -EIO; + } + + lincalrdyw_mask >>= 1; + } + + return 0; +} + +/* + * Fixed timeout value for ADC calibration. + * worst cases: + * - low clock frequency + * - maximum prescalers + * Calibration requires: + * - 131,072 ADC clock cycle for the linear calibration + * - 20 ADC clock cycle for the offset calibration + * + * Set to 100ms for now + */ +#define STM32H7_ADC_CALIB_TIMEOUT_US 100000 + +/** + * stm32h7_adc_selfcalib() - Procedure to calibrate ADC + * @indio_dev: IIO device instance + * @do_lincal: linear calibration request flag + * Note: Must be called once ADC is out of power down. + * + * Run offset calibration unconditionally. + * Run linear calibration if requested & supported. + */ +static int stm32h7_adc_selfcalib(struct iio_dev *indio_dev, int do_lincal) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + int ret; + u32 msk = STM32H7_ADCALDIF; + u32 val; + + if (adc->cfg->has_linearcal && do_lincal) + msk |= STM32H7_ADCALLIN; + /* ADC must be disabled for calibration */ + stm32h7_adc_disable(indio_dev); + + /* + * Select calibration mode: + * - Offset calibration for single ended inputs + * - No linearity calibration (do it later, before reading it) + */ + stm32_adc_clr_bits(adc, STM32H7_ADC_CR, msk); + + /* Start calibration, then wait for completion */ + stm32_adc_set_bits(adc, STM32H7_ADC_CR, STM32H7_ADCAL); + ret = stm32_adc_readl_poll_timeout(STM32H7_ADC_CR, val, + !(val & STM32H7_ADCAL), 100, + STM32H7_ADC_CALIB_TIMEOUT_US); + if (ret) { + dev_err(&indio_dev->dev, "calibration (single-ended) error %d\n", ret); + goto out; + } + + /* + * Select calibration mode, then start calibration: + * - Offset calibration for differential input + * - Linearity calibration (needs to be done only once for single/diff) + * will run simultaneously with offset calibration. + */ + stm32_adc_set_bits(adc, STM32H7_ADC_CR, msk); + stm32_adc_set_bits(adc, STM32H7_ADC_CR, STM32H7_ADCAL); + ret = stm32_adc_readl_poll_timeout(STM32H7_ADC_CR, val, + !(val & STM32H7_ADCAL), 100, + STM32H7_ADC_CALIB_TIMEOUT_US); + if (ret) { + dev_err(&indio_dev->dev, "calibration (diff%s) error %d\n", + (msk & STM32H7_ADCALLIN) ? "+linear" : "", ret); + goto out; + } + +out: + stm32_adc_clr_bits(adc, STM32H7_ADC_CR, msk); + + return ret; +} + +/** + * stm32h7_adc_check_selfcalib() - Check linear calibration status + * @indio_dev: IIO device instance + * + * Used to check if linear calibration has been done. + * Return true if linear calibration factors are already saved in private data + * or if a linear calibration has been done at boot stage. + */ +static int stm32h7_adc_check_selfcalib(struct iio_dev *indio_dev) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + u32 val; + + if (adc->cal.lincal_saved) + return true; + + /* + * Check if linear calibration factors are available in ADC registers, + * by checking that all LINCALRDYWx bits are set. + */ + val = stm32_adc_readl(adc, STM32H7_ADC_CR) & STM32H7_LINCALRDYW_MASK; + if (val == STM32H7_LINCALRDYW_MASK) + return true; + + return false; +} + +/** + * stm32h7_adc_prepare() - Leave power down mode to enable ADC. + * @indio_dev: IIO device instance + * Leave power down mode. + * Configure channels as single ended or differential before enabling ADC. + * Enable ADC. + * Restore calibration data. + * Pre-select channels that may be used in PCSEL (required by input MUX / IO): + * - Only one input is selected for single ended (e.g. 'vinp') + * - Two inputs are selected for differential channels (e.g. 'vinp' & 'vinn') + */ +static int stm32h7_adc_prepare(struct iio_dev *indio_dev) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + int lincal_done = false; + int ret; + + ret = stm32h7_adc_exit_pwr_down(indio_dev); + if (ret) + return ret; + + if (adc->cfg->has_linearcal) + lincal_done = stm32h7_adc_check_selfcalib(indio_dev); + + /* Always run offset calibration. Run linear calibration only once */ + ret = stm32h7_adc_selfcalib(indio_dev, !lincal_done); + if (ret < 0) + goto pwr_dwn; + + stm32_adc_int_ch_enable(indio_dev); + + stm32_adc_writel(adc, adc->cfg->regs->difsel.reg, adc->difsel); + + ret = stm32h7_adc_enable(indio_dev); + if (ret) + goto ch_disable; + + if (adc->cfg->has_linearcal) { + if (!adc->cal.lincal_saved) + ret = stm32h7_adc_read_selfcalib(indio_dev); + else + ret = stm32h7_adc_restore_selfcalib(indio_dev); + + if (ret) + goto disable; + } + + if (adc->cfg->has_presel) + stm32_adc_writel(adc, STM32H7_ADC_PCSEL, adc->pcsel); + + return 0; + +disable: + stm32h7_adc_disable(indio_dev); +ch_disable: + stm32_adc_int_ch_disable(adc); +pwr_dwn: + stm32h7_adc_enter_pwr_down(adc); + + return ret; +} + +static void stm32h7_adc_unprepare(struct iio_dev *indio_dev) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + + if (adc->cfg->has_presel) + stm32_adc_writel(adc, STM32H7_ADC_PCSEL, 0); + stm32h7_adc_disable(indio_dev); + stm32_adc_int_ch_disable(adc); + stm32h7_adc_enter_pwr_down(adc); +} + +/** + * stm32_adc_conf_scan_seq() - Build regular channels scan sequence + * @indio_dev: IIO device + * @scan_mask: channels to be converted + * + * Conversion sequence : + * Apply sampling time settings for all channels. + * Configure ADC scan sequence based on selected channels in scan_mask. + * Add channels to SQR registers, from scan_mask LSB to MSB, then + * program sequence len. + */ +static int stm32_adc_conf_scan_seq(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + const struct stm32_adc_regs *sqr = adc->cfg->regs->sqr; + const struct iio_chan_spec *chan; + u32 val, bit; + int i = 0; + + /* Apply sampling time settings */ + stm32_adc_writel(adc, adc->cfg->regs->smpr[0], adc->smpr_val[0]); + stm32_adc_writel(adc, adc->cfg->regs->smpr[1], adc->smpr_val[1]); + + for_each_set_bit(bit, scan_mask, iio_get_masklength(indio_dev)) { + chan = indio_dev->channels + bit; + /* + * Assign one channel per SQ entry in regular + * sequence, starting with SQ1. + */ + i++; + if (i > STM32_ADC_MAX_SQ) + return -EINVAL; + + dev_dbg(&indio_dev->dev, "%s chan %d to SQ%d\n", + __func__, chan->channel, i); + + val = stm32_adc_readl(adc, sqr[i].reg); + val &= ~sqr[i].mask; + val |= chan->channel << sqr[i].shift; + stm32_adc_writel(adc, sqr[i].reg, val); + } + + if (!i) + return -EINVAL; + + /* Sequence len */ + val = stm32_adc_readl(adc, sqr[0].reg); + val &= ~sqr[0].mask; + val |= ((i - 1) << sqr[0].shift); + stm32_adc_writel(adc, sqr[0].reg, val); + + return 0; +} + +/** + * stm32_adc_get_trig_extsel() - Get external trigger selection + * @indio_dev: IIO device structure + * @trig: trigger + * + * Returns trigger extsel value, if trig matches, -EINVAL otherwise. + */ +static int stm32_adc_get_trig_extsel(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + int i; + + /* lookup triggers registered by stm32 timer trigger driver */ + for (i = 0; adc->cfg->trigs[i].name; i++) { + /** + * Checking both stm32 timer trigger type and trig name + * should be safe against arbitrary trigger names. + */ + if ((is_stm32_timer_trigger(trig) || + is_stm32_lptim_trigger(trig)) && + !strcmp(adc->cfg->trigs[i].name, trig->name)) { + return adc->cfg->trigs[i].extsel; + } + } + + return -EINVAL; +} + +/** + * stm32_adc_set_trig() - Set a regular trigger + * @indio_dev: IIO device + * @trig: IIO trigger + * + * Set trigger source/polarity (e.g. SW, or HW with polarity) : + * - if HW trigger disabled (e.g. trig == NULL, conversion launched by sw) + * - if HW trigger enabled, set source & polarity + */ +static int stm32_adc_set_trig(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + u32 val, extsel = 0, exten = STM32_EXTEN_SWTRIG; + unsigned long flags; + int ret; + + if (trig) { + ret = stm32_adc_get_trig_extsel(indio_dev, trig); + if (ret < 0) + return ret; + + /* set trigger source and polarity (default to rising edge) */ + extsel = ret; + exten = adc->trigger_polarity + STM32_EXTEN_HWTRIG_RISING_EDGE; + } + + spin_lock_irqsave(&adc->lock, flags); + val = stm32_adc_readl(adc, adc->cfg->regs->exten.reg); + val &= ~(adc->cfg->regs->exten.mask | adc->cfg->regs->extsel.mask); + val |= exten << adc->cfg->regs->exten.shift; + val |= extsel << adc->cfg->regs->extsel.shift; + stm32_adc_writel(adc, adc->cfg->regs->exten.reg, val); + spin_unlock_irqrestore(&adc->lock, flags); + + return 0; +} + +static int stm32_adc_set_trig_pol(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int type) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + + adc->trigger_polarity = type; + + return 0; +} + +static int stm32_adc_get_trig_pol(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + + return adc->trigger_polarity; +} + +static const char * const stm32_trig_pol_items[] = { + "rising-edge", "falling-edge", "both-edges", +}; + +static const struct iio_enum stm32_adc_trig_pol = { + .items = stm32_trig_pol_items, + .num_items = ARRAY_SIZE(stm32_trig_pol_items), + .get = stm32_adc_get_trig_pol, + .set = stm32_adc_set_trig_pol, +}; + +/** + * stm32_adc_single_conv() - Performs a single conversion + * @indio_dev: IIO device + * @chan: IIO channel + * @res: conversion result + * + * The function performs a single conversion on a given channel: + * - Apply sampling time settings + * - Program sequencer with one channel (e.g. in SQ1 with len = 1) + * - Use SW trigger + * - Start conversion, then wait for interrupt completion. + */ +static int stm32_adc_single_conv(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *res) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + const struct stm32_adc_regspec *regs = adc->cfg->regs; + long time_left; + u32 val; + int ret; + + reinit_completion(&adc->completion); + + adc->bufi = 0; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + /* Apply sampling time settings */ + stm32_adc_writel(adc, regs->smpr[0], adc->smpr_val[0]); + stm32_adc_writel(adc, regs->smpr[1], adc->smpr_val[1]); + + /* Program chan number in regular sequence (SQ1) */ + val = stm32_adc_readl(adc, regs->sqr[1].reg); + val &= ~regs->sqr[1].mask; + val |= chan->channel << regs->sqr[1].shift; + stm32_adc_writel(adc, regs->sqr[1].reg, val); + + /* Set regular sequence len (0 for 1 conversion) */ + stm32_adc_clr_bits(adc, regs->sqr[0].reg, regs->sqr[0].mask); + + /* Trigger detection disabled (conversion can be launched in SW) */ + stm32_adc_clr_bits(adc, regs->exten.reg, regs->exten.mask); + + stm32_adc_conv_irq_enable(adc); + + adc->cfg->start_conv(indio_dev, false); + + time_left = wait_for_completion_interruptible_timeout( + &adc->completion, STM32_ADC_TIMEOUT); + if (time_left == 0) { + ret = -ETIMEDOUT; + } else if (time_left < 0) { + ret = time_left; + } else { + *res = adc->buffer[0]; + ret = IIO_VAL_INT; + } + + adc->cfg->stop_conv(indio_dev); + + stm32_adc_conv_irq_disable(adc); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static int stm32_adc_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + int nb = adc->cfg->adc_info->num_ovs; + unsigned int idx; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + if (val2) + return -EINVAL; + + for (idx = 0; idx < nb; idx++) + if (adc->cfg->adc_info->oversampling[idx] == val) + break; + if (idx >= nb) + return -EINVAL; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + goto err; + + adc->cfg->set_ovs(indio_dev, idx); + + pm_runtime_put_autosuspend(dev); + + adc->ovs_idx = idx; + +err: + iio_device_release_direct(indio_dev); + + return ret; + default: + return -EINVAL; + } +} + +static int stm32_adc_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, long m) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *type = IIO_VAL_INT; + *length = adc->cfg->adc_info->num_ovs; + *vals = adc->cfg->adc_info->oversampling; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int stm32_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + if (chan->type == IIO_VOLTAGE) + ret = stm32_adc_single_conv(indio_dev, chan, val); + else + ret = -EINVAL; + + if (mask == IIO_CHAN_INFO_PROCESSED) + *val = STM32_ADC_VREFINT_VOLTAGE * adc->vrefint.vrefint_cal / *val; + + iio_device_release_direct(indio_dev); + return ret; + + case IIO_CHAN_INFO_SCALE: + if (chan->differential) { + *val = adc->common->vref_mv * 2; + *val2 = chan->scan_type.realbits; + } else { + *val = adc->common->vref_mv; + *val2 = chan->scan_type.realbits; + } + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_OFFSET: + if (chan->differential) + /* ADC_full_scale / 2 */ + *val = -((1 << chan->scan_type.realbits) / 2); + else + *val = 0; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = adc->cfg->adc_info->oversampling[adc->ovs_idx]; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static void stm32_adc_irq_clear(struct iio_dev *indio_dev, u32 msk) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + + adc->cfg->irq_clear(indio_dev, msk); +} + +static irqreturn_t stm32_adc_threaded_isr(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct stm32_adc *adc = iio_priv(indio_dev); + const struct stm32_adc_regspec *regs = adc->cfg->regs; + u32 status = stm32_adc_readl(adc, regs->isr_eoc.reg); + + /* Check ovr status right now, as ovr mask should be already disabled */ + if (status & regs->isr_ovr.mask) { + /* + * Clear ovr bit to avoid subsequent calls to IRQ handler. + * This requires to stop ADC first. OVR bit state in ISR, + * is propagated to CSR register by hardware. + */ + adc->cfg->stop_conv(indio_dev); + stm32_adc_irq_clear(indio_dev, regs->isr_ovr.mask); + dev_err(&indio_dev->dev, "Overrun, stopping: restart needed\n"); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static irqreturn_t stm32_adc_isr(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct stm32_adc *adc = iio_priv(indio_dev); + const struct stm32_adc_regspec *regs = adc->cfg->regs; + u32 status = stm32_adc_readl(adc, regs->isr_eoc.reg); + + if (status & regs->isr_ovr.mask) { + /* + * Overrun occurred on regular conversions: data for wrong + * channel may be read. Unconditionally disable interrupts + * to stop processing data and print error message. + * Restarting the capture can be done by disabling, then + * re-enabling it (e.g. write 0, then 1 to buffer/enable). + */ + stm32_adc_ovr_irq_disable(adc); + stm32_adc_conv_irq_disable(adc); + return IRQ_WAKE_THREAD; + } + + if (status & regs->isr_eoc.mask) { + /* Reading DR also clears EOC status flag */ + adc->buffer[adc->bufi] = stm32_adc_readw(adc, regs->dr); + if (iio_buffer_enabled(indio_dev)) { + adc->bufi++; + if (adc->bufi >= adc->num_conv) { + stm32_adc_conv_irq_disable(adc); + iio_trigger_poll(indio_dev->trig); + } + } else { + complete(&adc->completion); + } + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +/** + * stm32_adc_validate_trigger() - validate trigger for stm32 adc + * @indio_dev: IIO device + * @trig: new trigger + * + * Returns: 0 if trig matches one of the triggers registered by stm32 adc + * driver, -EINVAL otherwise. + */ +static int stm32_adc_validate_trigger(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + return stm32_adc_get_trig_extsel(indio_dev, trig) < 0 ? -EINVAL : 0; +} + +static int stm32_adc_set_watermark(struct iio_dev *indio_dev, unsigned int val) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + unsigned int watermark = STM32_DMA_BUFFER_SIZE / 2; + unsigned int rx_buf_sz = STM32_DMA_BUFFER_SIZE; + + /* + * dma cyclic transfers are used, buffer is split into two periods. + * There should be : + * - always one buffer (period) dma is working on + * - one buffer (period) driver can push data. + */ + watermark = min(watermark, val * (unsigned)(sizeof(u16))); + adc->rx_buf_sz = min(rx_buf_sz, watermark * 2 * adc->num_conv); + + return 0; +} + +static int stm32_adc_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + adc->num_conv = bitmap_weight(scan_mask, iio_get_masklength(indio_dev)); + + ret = stm32_adc_conf_scan_seq(indio_dev, scan_mask); + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static int stm32_adc_fwnode_xlate(struct iio_dev *indio_dev, + const struct fwnode_reference_args *iiospec) +{ + int i; + + for (i = 0; i < indio_dev->num_channels; i++) + if (indio_dev->channels[i].channel == iiospec->args[0]) + return i; + + return -EINVAL; +} + +/** + * stm32_adc_debugfs_reg_access - read or write register value + * @indio_dev: IIO device structure + * @reg: register offset + * @writeval: value to write + * @readval: value to read + * + * To read a value from an ADC register: + * echo [ADC reg offset] > direct_reg_access + * cat direct_reg_access + * + * To write a value in a ADC register: + * echo [ADC_reg_offset] [value] > direct_reg_access + */ +static int stm32_adc_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned reg, unsigned writeval, + unsigned *readval) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + if (!readval) + stm32_adc_writel(adc, reg, writeval); + else + *readval = stm32_adc_readl(adc, reg); + + pm_runtime_put_autosuspend(dev); + + return 0; +} + +static const struct iio_info stm32_adc_iio_info = { + .read_raw = stm32_adc_read_raw, + .write_raw = stm32_adc_write_raw, + .read_avail = stm32_adc_read_avail, + .validate_trigger = stm32_adc_validate_trigger, + .hwfifo_set_watermark = stm32_adc_set_watermark, + .update_scan_mode = stm32_adc_update_scan_mode, + .debugfs_reg_access = stm32_adc_debugfs_reg_access, + .fwnode_xlate = stm32_adc_fwnode_xlate, +}; + +static unsigned int stm32_adc_dma_residue(struct stm32_adc *adc) +{ + struct dma_tx_state state; + enum dma_status status; + + status = dmaengine_tx_status(adc->dma_chan, + adc->dma_chan->cookie, + &state); + if (status == DMA_IN_PROGRESS) { + /* Residue is size in bytes from end of buffer */ + unsigned int i = adc->rx_buf_sz - state.residue; + unsigned int size; + + /* Return available bytes */ + if (i >= adc->bufi) + size = i - adc->bufi; + else + size = adc->rx_buf_sz + i - adc->bufi; + + return size; + } + + return 0; +} + +static void stm32_adc_dma_buffer_done(void *data) +{ + struct iio_dev *indio_dev = data; + struct stm32_adc *adc = iio_priv(indio_dev); + int residue = stm32_adc_dma_residue(adc); + + /* + * In DMA mode the trigger services of IIO are not used + * (e.g. no call to iio_trigger_poll). + * Calling irq handler associated to the hardware trigger is not + * relevant as the conversions have already been done. Data + * transfers are performed directly in DMA callback instead. + * This implementation avoids to call trigger irq handler that + * may sleep, in an atomic context (DMA irq handler context). + */ + dev_dbg(&indio_dev->dev, "%s bufi=%d\n", __func__, adc->bufi); + + while (residue >= indio_dev->scan_bytes) { + u16 *buffer = (u16 *)&adc->rx_buf[adc->bufi]; + + iio_push_to_buffers(indio_dev, buffer); + + residue -= indio_dev->scan_bytes; + adc->bufi += indio_dev->scan_bytes; + if (adc->bufi >= adc->rx_buf_sz) + adc->bufi = 0; + } +} + +static int stm32_adc_dma_start(struct iio_dev *indio_dev) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + struct dma_async_tx_descriptor *desc; + dma_cookie_t cookie; + int ret; + + if (!adc->dma_chan) + return 0; + + dev_dbg(&indio_dev->dev, "%s size=%d watermark=%d\n", __func__, + adc->rx_buf_sz, adc->rx_buf_sz / 2); + + /* Prepare a DMA cyclic transaction */ + desc = dmaengine_prep_dma_cyclic(adc->dma_chan, + adc->rx_dma_buf, + adc->rx_buf_sz, adc->rx_buf_sz / 2, + DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT); + if (!desc) + return -EBUSY; + + desc->callback = stm32_adc_dma_buffer_done; + desc->callback_param = indio_dev; + + cookie = dmaengine_submit(desc); + ret = dma_submit_error(cookie); + if (ret) { + dmaengine_terminate_sync(adc->dma_chan); + return ret; + } + + /* Issue pending DMA requests */ + dma_async_issue_pending(adc->dma_chan); + + return 0; +} + +static int stm32_adc_buffer_postenable(struct iio_dev *indio_dev) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + ret = stm32_adc_set_trig(indio_dev, indio_dev->trig); + if (ret) { + dev_err(&indio_dev->dev, "Can't set trigger\n"); + goto err_pm_put; + } + + ret = stm32_adc_dma_start(indio_dev); + if (ret) { + dev_err(&indio_dev->dev, "Can't start dma\n"); + goto err_clr_trig; + } + + /* Reset adc buffer index */ + adc->bufi = 0; + + stm32_adc_ovr_irq_enable(adc); + + if (!adc->dma_chan) + stm32_adc_conv_irq_enable(adc); + + adc->cfg->start_conv(indio_dev, !!adc->dma_chan); + + return 0; + +err_clr_trig: + stm32_adc_set_trig(indio_dev, NULL); +err_pm_put: + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static int stm32_adc_buffer_predisable(struct iio_dev *indio_dev) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + + adc->cfg->stop_conv(indio_dev); + if (!adc->dma_chan) + stm32_adc_conv_irq_disable(adc); + + stm32_adc_ovr_irq_disable(adc); + + if (adc->dma_chan) + dmaengine_terminate_sync(adc->dma_chan); + + if (stm32_adc_set_trig(indio_dev, NULL)) + dev_err(&indio_dev->dev, "Can't clear trigger\n"); + + pm_runtime_put_autosuspend(dev); + + return 0; +} + +static const struct iio_buffer_setup_ops stm32_adc_buffer_setup_ops = { + .postenable = &stm32_adc_buffer_postenable, + .predisable = &stm32_adc_buffer_predisable, +}; + +static irqreturn_t stm32_adc_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct stm32_adc *adc = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "%s bufi=%d\n", __func__, adc->bufi); + + /* reset buffer index */ + adc->bufi = 0; + iio_push_to_buffers_with_ts(indio_dev, adc->buffer, sizeof(adc->buffer), + pf->timestamp); + iio_trigger_notify_done(indio_dev->trig); + + /* re-enable eoc irq */ + stm32_adc_conv_irq_enable(adc); + + return IRQ_HANDLED; +} + +static const struct iio_chan_spec_ext_info stm32_adc_ext_info[] = { + IIO_ENUM("trigger_polarity", IIO_SHARED_BY_ALL, &stm32_adc_trig_pol), + { + .name = "trigger_polarity_available", + .shared = IIO_SHARED_BY_ALL, + .read = iio_enum_available_read, + .private = (uintptr_t)&stm32_adc_trig_pol, + }, + { } +}; + +static void stm32_adc_debugfs_init(struct iio_dev *indio_dev) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + struct dentry *d = iio_get_debugfs_dentry(indio_dev); + struct stm32_adc_calib *cal = &adc->cal; + char buf[16]; + unsigned int i; + + if (!adc->cfg->has_linearcal) + return; + + for (i = 0; i < STM32H7_LINCALFACT_NUM; i++) { + snprintf(buf, sizeof(buf), "lincalfact%d", i + 1); + debugfs_create_u32(buf, 0444, d, &cal->lincalfact[i]); + } +} + +static int stm32_adc_fw_get_resolution(struct iio_dev *indio_dev) +{ + struct device *dev = &indio_dev->dev; + struct stm32_adc *adc = iio_priv(indio_dev); + unsigned int i; + u32 res; + + if (device_property_read_u32(dev, "assigned-resolution-bits", &res)) + res = adc->cfg->adc_info->resolutions[0]; + + for (i = 0; i < adc->cfg->adc_info->num_res; i++) + if (res == adc->cfg->adc_info->resolutions[i]) + break; + if (i >= adc->cfg->adc_info->num_res) { + dev_err(&indio_dev->dev, "Bad resolution: %u bits\n", res); + return -EINVAL; + } + + dev_dbg(&indio_dev->dev, "Using %u bits resolution\n", res); + adc->res = i; + + return 0; +} + +static void stm32_adc_smpr_init(struct stm32_adc *adc, int channel, u32 smp_ns) +{ + const struct stm32_adc_regs *smpr = &adc->cfg->regs->smp_bits[channel]; + u32 period_ns, shift = smpr->shift, mask = smpr->mask; + unsigned int i, smp, r = smpr->reg; + + /* + * For internal channels, ensure that the sampling time cannot + * be lower than the one specified in the datasheet + */ + for (i = 0; i < STM32_ADC_INT_CH_NB; i++) + if (channel == adc->int_ch[i] && adc->int_ch[i] != STM32_ADC_INT_CH_NONE) + smp_ns = max(smp_ns, adc->cfg->ts_int_ch[i]); + + /* Determine sampling time (ADC clock cycles) */ + period_ns = NSEC_PER_SEC / adc->common->rate; + for (smp = 0; smp <= STM32_ADC_MAX_SMP; smp++) + if ((period_ns * adc->cfg->smp_cycles[smp]) >= smp_ns) + break; + if (smp > STM32_ADC_MAX_SMP) + smp = STM32_ADC_MAX_SMP; + + /* pre-build sampling time registers (e.g. smpr1, smpr2) */ + adc->smpr_val[r] = (adc->smpr_val[r] & ~mask) | (smp << shift); +} + +static void stm32_adc_chan_init_one(struct iio_dev *indio_dev, + struct iio_chan_spec *chan, u32 vinp, + u32 vinn, int scan_index, bool differential) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + char *name = adc->chan_name[vinp]; + + chan->type = IIO_VOLTAGE; + chan->channel = vinp; + if (differential) { + chan->differential = 1; + chan->channel2 = vinn; + snprintf(name, STM32_ADC_CH_SZ, "in%d-in%d", vinp, vinn); + } else { + snprintf(name, STM32_ADC_CH_SZ, "in%d", vinp); + } + chan->datasheet_name = name; + chan->scan_index = scan_index; + chan->indexed = 1; + if (chan->channel == adc->int_ch[STM32_ADC_INT_CH_VREFINT]) + chan->info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED); + else + chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); + chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET); + if (adc->cfg->has_oversampling) { + chan->info_mask_shared_by_all |= BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO); + chan->info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO); + } + chan->scan_type.sign = 'u'; + chan->scan_type.realbits = adc->cfg->adc_info->resolutions[adc->res]; + chan->scan_type.storagebits = 16; + chan->ext_info = stm32_adc_ext_info; + + /* pre-build selected channels mask */ + adc->pcsel |= BIT(chan->channel); + if (differential) { + /* pre-build diff channels mask */ + adc->difsel |= BIT(chan->channel) & adc->cfg->regs->difsel.mask; + /* Also add negative input to pre-selected channels */ + adc->pcsel |= BIT(chan->channel2); + } +} + +static int stm32_adc_get_legacy_chan_count(struct iio_dev *indio_dev, struct stm32_adc *adc) +{ + struct device *dev = &indio_dev->dev; + const struct stm32_adc_info *adc_info = adc->cfg->adc_info; + int num_channels = 0, ret; + + dev_dbg(&indio_dev->dev, "using legacy channel config\n"); + + ret = device_property_count_u32(dev, "st,adc-channels"); + if (ret > adc_info->max_channels) { + dev_err(&indio_dev->dev, "Bad st,adc-channels?\n"); + return -EINVAL; + } else if (ret > 0) { + num_channels += ret; + } + + /* + * each st,adc-diff-channels is a group of 2 u32 so we divide @ret + * to get the *real* number of channels. + */ + ret = device_property_count_u32(dev, "st,adc-diff-channels"); + if (ret > 0) { + ret /= (int)(sizeof(struct stm32_adc_diff_channel) / sizeof(u32)); + if (ret > adc_info->max_channels) { + dev_err(&indio_dev->dev, "Bad st,adc-diff-channels?\n"); + return -EINVAL; + } else if (ret > 0) { + adc->num_diff = ret; + num_channels += ret; + } + } + + /* Optional sample time is provided either for each, or all channels */ + adc->nsmps = device_property_count_u32(dev, "st,min-sample-time-nsecs"); + if (adc->nsmps > 1 && adc->nsmps != num_channels) { + dev_err(&indio_dev->dev, "Invalid st,min-sample-time-nsecs\n"); + return -EINVAL; + } + + return num_channels; +} + +static int stm32_adc_legacy_chan_init(struct iio_dev *indio_dev, + struct stm32_adc *adc, + struct iio_chan_spec *channels, + int nchans) +{ + const struct stm32_adc_info *adc_info = adc->cfg->adc_info; + struct stm32_adc_diff_channel diff[STM32_ADC_CH_MAX]; + struct device *dev = &indio_dev->dev; + u32 num_diff = adc->num_diff; + int num_se = nchans - num_diff; + int size = num_diff * sizeof(*diff) / sizeof(u32); + int scan_index = 0, ret, i, c; + u32 smp = 0, smps[STM32_ADC_CH_MAX], chans[STM32_ADC_CH_MAX]; + + if (num_diff) { + ret = device_property_read_u32_array(dev, "st,adc-diff-channels", + (u32 *)diff, size); + if (ret) { + dev_err(&indio_dev->dev, "Failed to get diff channels %d\n", ret); + return ret; + } + + for (i = 0; i < num_diff; i++) { + if (diff[i].vinp >= adc_info->max_channels || + diff[i].vinn >= adc_info->max_channels) { + dev_err(&indio_dev->dev, "Invalid channel in%d-in%d\n", + diff[i].vinp, diff[i].vinn); + return -EINVAL; + } + + stm32_adc_chan_init_one(indio_dev, &channels[scan_index], + diff[i].vinp, diff[i].vinn, + scan_index, true); + scan_index++; + } + } + if (num_se > 0) { + ret = device_property_read_u32_array(dev, "st,adc-channels", chans, num_se); + if (ret) { + dev_err(&indio_dev->dev, "Failed to get st,adc-channels %d\n", ret); + return ret; + } + + for (c = 0; c < num_se; c++) { + if (chans[c] >= adc_info->max_channels) { + dev_err(&indio_dev->dev, "Invalid channel %d\n", + chans[c]); + return -EINVAL; + } + + /* Channel can't be configured both as single-ended & diff */ + for (i = 0; i < num_diff; i++) { + if (chans[c] == diff[i].vinp) { + dev_err(&indio_dev->dev, "channel %d misconfigured\n", + chans[c]); + return -EINVAL; + } + } + stm32_adc_chan_init_one(indio_dev, &channels[scan_index], + chans[c], 0, scan_index, false); + scan_index++; + } + } + + if (adc->nsmps > 0) { + ret = device_property_read_u32_array(dev, "st,min-sample-time-nsecs", + smps, adc->nsmps); + if (ret) + return ret; + } + + for (i = 0; i < scan_index; i++) { + /* + * This check is used with the above logic so that smp value + * will only be modified if valid u32 value can be decoded. This + * allows to get either no value, 1 shared value for all indexes, + * or one value per channel. The point is to have the same + * behavior as 'of_property_read_u32_index()'. + */ + if (i < adc->nsmps) + smp = smps[i]; + + /* Prepare sampling time settings */ + stm32_adc_smpr_init(adc, channels[i].channel, smp); + } + + return scan_index; +} + +static int stm32_adc_populate_int_ch(struct iio_dev *indio_dev, const char *ch_name, + int chan) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + u16 vrefint; + int i, ret; + + for (i = 0; i < STM32_ADC_INT_CH_NB; i++) { + if (!strncmp(stm32_adc_ic[i].name, ch_name, STM32_ADC_CH_SZ)) { + /* Check internal channel availability */ + switch (i) { + case STM32_ADC_INT_CH_VDDCORE: + if (!adc->cfg->regs->or_vddcore.reg) + dev_warn(&indio_dev->dev, + "%s channel not available\n", ch_name); + break; + case STM32_ADC_INT_CH_VDDCPU: + if (!adc->cfg->regs->or_vddcpu.reg) + dev_warn(&indio_dev->dev, + "%s channel not available\n", ch_name); + break; + case STM32_ADC_INT_CH_VDDQ_DDR: + if (!adc->cfg->regs->or_vddq_ddr.reg) + dev_warn(&indio_dev->dev, + "%s channel not available\n", ch_name); + break; + case STM32_ADC_INT_CH_VREFINT: + if (!adc->cfg->regs->ccr_vref.reg) + dev_warn(&indio_dev->dev, + "%s channel not available\n", ch_name); + break; + case STM32_ADC_INT_CH_VBAT: + if (!adc->cfg->regs->ccr_vbat.reg) + dev_warn(&indio_dev->dev, + "%s channel not available\n", ch_name); + break; + } + + if (stm32_adc_ic[i].idx != STM32_ADC_INT_CH_VREFINT) { + adc->int_ch[i] = chan; + break; + } + + /* Get calibration data for vrefint channel */ + ret = nvmem_cell_read_u16(&indio_dev->dev, "vrefint", &vrefint); + if (ret && ret != -ENOENT) { + return dev_err_probe(indio_dev->dev.parent, ret, + "nvmem access error\n"); + } + if (ret == -ENOENT) { + dev_dbg(&indio_dev->dev, "vrefint calibration not found. Skip vrefint channel\n"); + return ret; + } else if (!vrefint) { + dev_dbg(&indio_dev->dev, "Null vrefint calibration value. Skip vrefint channel\n"); + return -ENOENT; + } + adc->int_ch[i] = chan; + adc->vrefint.vrefint_cal = vrefint; + } + } + + return 0; +} + +static int stm32_adc_generic_chan_init(struct iio_dev *indio_dev, + struct stm32_adc *adc, + struct iio_chan_spec *channels) +{ + const struct stm32_adc_info *adc_info = adc->cfg->adc_info; + struct device *dev = &indio_dev->dev; + const char *name; + int val, scan_index = 0, ret; + bool differential; + u32 vin[2]; + + device_for_each_child_node_scoped(dev, child) { + ret = fwnode_property_read_u32(child, "reg", &val); + if (ret) + return dev_err_probe(dev, ret, + "Missing channel index\n"); + + ret = fwnode_property_read_string(child, "label", &name); + /* label is optional */ + if (!ret) { + if (strlen(name) >= STM32_ADC_CH_SZ) + return dev_err_probe(dev, -EINVAL, + "Label %s exceeds %d characters\n", + name, STM32_ADC_CH_SZ); + + strscpy(adc->chan_name[val], name, STM32_ADC_CH_SZ); + ret = stm32_adc_populate_int_ch(indio_dev, name, val); + if (ret == -ENOENT) + continue; + else if (ret) + return ret; + } else if (ret != -EINVAL) { + return dev_err_probe(dev, ret, "Invalid label\n"); + } + + if (val >= adc_info->max_channels) + return dev_err_probe(dev, -EINVAL, + "Invalid channel %d\n", val); + + differential = false; + ret = fwnode_property_read_u32_array(child, "diff-channels", vin, 2); + /* diff-channels is optional */ + if (!ret) { + differential = true; + if (vin[0] != val || vin[1] >= adc_info->max_channels) + return dev_err_probe(dev, -EINVAL, + "Invalid channel in%d-in%d\n", + vin[0], vin[1]); + } else if (ret != -EINVAL) { + return dev_err_probe(dev, ret, + "Invalid diff-channels property\n"); + } + + stm32_adc_chan_init_one(indio_dev, &channels[scan_index], val, + vin[1], scan_index, differential); + + val = 0; + ret = fwnode_property_read_u32(child, "st,min-sample-time-ns", &val); + /* st,min-sample-time-ns is optional */ + if (ret && ret != -EINVAL) + return dev_err_probe(dev, ret, + "Invalid st,min-sample-time-ns property\n"); + + stm32_adc_smpr_init(adc, channels[scan_index].channel, val); + if (differential) + stm32_adc_smpr_init(adc, vin[1], val); + + scan_index++; + } + + return scan_index; +} + +static int stm32_adc_chan_fw_init(struct iio_dev *indio_dev, bool timestamping) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + const struct stm32_adc_info *adc_info = adc->cfg->adc_info; + struct iio_chan_spec *channels; + int scan_index = 0, num_channels = 0, ret, i; + bool legacy = false; + + for (i = 0; i < STM32_ADC_INT_CH_NB; i++) + adc->int_ch[i] = STM32_ADC_INT_CH_NONE; + + num_channels = device_get_child_node_count(&indio_dev->dev); + /* If no channels have been found, fallback to channels legacy properties. */ + if (!num_channels) { + legacy = true; + + ret = stm32_adc_get_legacy_chan_count(indio_dev, adc); + if (!ret) { + dev_err(indio_dev->dev.parent, "No channel found\n"); + return -ENODATA; + } else if (ret < 0) { + return ret; + } + + num_channels = ret; + } + + if (num_channels > adc_info->max_channels) { + dev_err(&indio_dev->dev, "Channel number [%d] exceeds %d\n", + num_channels, adc_info->max_channels); + return -EINVAL; + } + + if (timestamping) + num_channels++; + + channels = devm_kcalloc(&indio_dev->dev, num_channels, + sizeof(struct iio_chan_spec), GFP_KERNEL); + if (!channels) + return -ENOMEM; + + if (legacy) + ret = stm32_adc_legacy_chan_init(indio_dev, adc, channels, + timestamping ? num_channels - 1 : num_channels); + else + ret = stm32_adc_generic_chan_init(indio_dev, adc, channels); + if (ret < 0) + return ret; + scan_index = ret; + + if (timestamping) { + channels[scan_index] = IIO_CHAN_SOFT_TIMESTAMP(scan_index); + scan_index++; + } + + indio_dev->num_channels = scan_index; + indio_dev->channels = channels; + + return 0; +} + +static int stm32_adc_dma_request(struct device *dev, struct iio_dev *indio_dev) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + struct dma_slave_config config = { }; + int ret; + + adc->dma_chan = dma_request_chan(dev, "rx"); + if (IS_ERR(adc->dma_chan)) { + ret = PTR_ERR(adc->dma_chan); + if (ret != -ENODEV) + return dev_err_probe(dev, ret, + "DMA channel request failed with\n"); + + /* DMA is optional: fall back to IRQ mode */ + adc->dma_chan = NULL; + return 0; + } + + adc->rx_buf = dma_alloc_coherent(adc->dma_chan->device->dev, + STM32_DMA_BUFFER_SIZE, + &adc->rx_dma_buf, GFP_KERNEL); + if (!adc->rx_buf) { + ret = -ENOMEM; + goto err_release; + } + + /* Configure DMA channel to read data register */ + config.src_addr = (dma_addr_t)adc->common->phys_base; + config.src_addr += adc->offset + adc->cfg->regs->dr; + config.src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + + ret = dmaengine_slave_config(adc->dma_chan, &config); + if (ret) + goto err_free; + + return 0; + +err_free: + dma_free_coherent(adc->dma_chan->device->dev, STM32_DMA_BUFFER_SIZE, + adc->rx_buf, adc->rx_dma_buf); +err_release: + dma_release_channel(adc->dma_chan); + + return ret; +} + +static int stm32_adc_probe(struct platform_device *pdev) +{ + struct iio_dev *indio_dev; + struct device *dev = &pdev->dev; + irqreturn_t (*handler)(int irq, void *p) = NULL; + struct stm32_adc *adc; + bool timestamping = false; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->common = dev_get_drvdata(pdev->dev.parent); + spin_lock_init(&adc->lock); + init_completion(&adc->completion); + adc->cfg = device_get_match_data(dev); + + indio_dev->name = dev_name(&pdev->dev); + device_set_node(&indio_dev->dev, dev_fwnode(&pdev->dev)); + indio_dev->info = &stm32_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_HARDWARE_TRIGGERED; + + platform_set_drvdata(pdev, indio_dev); + + ret = device_property_read_u32(dev, "reg", &adc->offset); + if (ret != 0) { + dev_err(&pdev->dev, "missing reg property\n"); + return -EINVAL; + } + + adc->irq = platform_get_irq(pdev, 0); + if (adc->irq < 0) + return adc->irq; + + ret = devm_request_threaded_irq(&pdev->dev, adc->irq, stm32_adc_isr, + stm32_adc_threaded_isr, + 0, pdev->name, indio_dev); + if (ret) { + dev_err(&pdev->dev, "failed to request IRQ\n"); + return ret; + } + + adc->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(adc->clk)) { + ret = PTR_ERR(adc->clk); + if (ret == -ENOENT && !adc->cfg->clk_required) { + adc->clk = NULL; + } else { + dev_err(&pdev->dev, "Can't get clock\n"); + return ret; + } + } + + ret = stm32_adc_fw_get_resolution(indio_dev); + if (ret < 0) + return ret; + + ret = stm32_adc_dma_request(dev, indio_dev); + if (ret < 0) + return ret; + + if (!adc->dma_chan) { + /* For PIO mode only, iio_pollfunc_store_time stores a timestamp + * in the primary trigger IRQ handler and stm32_adc_trigger_handler + * runs in the IRQ thread to push out buffer along with timestamp. + */ + handler = &stm32_adc_trigger_handler; + timestamping = true; + } + + ret = stm32_adc_chan_fw_init(indio_dev, timestamping); + if (ret < 0) + goto err_dma_disable; + + ret = iio_triggered_buffer_setup(indio_dev, + &iio_pollfunc_store_time, handler, + &stm32_adc_buffer_setup_ops); + if (ret) { + dev_err(&pdev->dev, "buffer setup failed\n"); + goto err_dma_disable; + } + + /* Get stm32-adc-core PM online */ + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); + pm_runtime_set_autosuspend_delay(dev, STM32_ADC_HW_STOP_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + + ret = stm32_adc_hw_start(dev); + if (ret) + goto err_buffer_cleanup; + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "iio dev register failed\n"); + goto err_hw_stop; + } + + pm_runtime_put_autosuspend(dev); + + if (IS_ENABLED(CONFIG_DEBUG_FS)) + stm32_adc_debugfs_init(indio_dev); + + return 0; + +err_hw_stop: + stm32_adc_hw_stop(dev); + +err_buffer_cleanup: + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_put_noidle(dev); + iio_triggered_buffer_cleanup(indio_dev); + +err_dma_disable: + if (adc->dma_chan) { + dma_free_coherent(adc->dma_chan->device->dev, + STM32_DMA_BUFFER_SIZE, + adc->rx_buf, adc->rx_dma_buf); + dma_release_channel(adc->dma_chan); + } + + return ret; +} + +static void stm32_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct stm32_adc *adc = iio_priv(indio_dev); + + pm_runtime_get_sync(&pdev->dev); + /* iio_device_unregister() also removes debugfs entries */ + iio_device_unregister(indio_dev); + stm32_adc_hw_stop(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + iio_triggered_buffer_cleanup(indio_dev); + if (adc->dma_chan) { + dma_free_coherent(adc->dma_chan->device->dev, + STM32_DMA_BUFFER_SIZE, + adc->rx_buf, adc->rx_dma_buf); + dma_release_channel(adc->dma_chan); + } +} + +static int stm32_adc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + if (iio_buffer_enabled(indio_dev)) + stm32_adc_buffer_predisable(indio_dev); + + return pm_runtime_force_suspend(dev); +} + +static int stm32_adc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_force_resume(dev); + if (ret < 0) + return ret; + + if (!iio_buffer_enabled(indio_dev)) + return 0; + + ret = stm32_adc_update_scan_mode(indio_dev, + indio_dev->active_scan_mask); + if (ret < 0) + return ret; + + return stm32_adc_buffer_postenable(indio_dev); +} + +static int stm32_adc_runtime_suspend(struct device *dev) +{ + return stm32_adc_hw_stop(dev); +} + +static int stm32_adc_runtime_resume(struct device *dev) +{ + return stm32_adc_hw_start(dev); +} + +static const struct dev_pm_ops stm32_adc_pm_ops = { + SYSTEM_SLEEP_PM_OPS(stm32_adc_suspend, stm32_adc_resume) + RUNTIME_PM_OPS(stm32_adc_runtime_suspend, stm32_adc_runtime_resume, + NULL) +}; + +static const struct stm32_adc_cfg stm32f4_adc_cfg = { + .regs = &stm32f4_adc_regspec, + .adc_info = &stm32f4_adc_info, + .trigs = stm32f4_adc_trigs, + .clk_required = true, + .start_conv = stm32f4_adc_start_conv, + .stop_conv = stm32f4_adc_stop_conv, + .smp_cycles = stm32f4_adc_smp_cycles, + .irq_clear = stm32f4_adc_irq_clear, +}; + +static const unsigned int stm32_adc_min_ts_h7[] = { 0, 0, 0, 4300, 9000 }; +static_assert(ARRAY_SIZE(stm32_adc_min_ts_h7) == STM32_ADC_INT_CH_NB); + +static const struct stm32_adc_cfg stm32h7_adc_cfg = { + .regs = &stm32h7_adc_regspec, + .adc_info = &stm32h7_adc_info, + .trigs = stm32h7_adc_trigs, + .has_boostmode = true, + .has_linearcal = true, + .has_presel = true, + .has_oversampling = true, + .start_conv = stm32h7_adc_start_conv, + .stop_conv = stm32h7_adc_stop_conv, + .prepare = stm32h7_adc_prepare, + .unprepare = stm32h7_adc_unprepare, + .smp_cycles = stm32h7_adc_smp_cycles, + .irq_clear = stm32h7_adc_irq_clear, + .ts_int_ch = stm32_adc_min_ts_h7, + .set_ovs = stm32h7_adc_set_ovs, +}; + +static const unsigned int stm32_adc_min_ts_mp1[] = { 100, 100, 100, 4300, 9800 }; +static_assert(ARRAY_SIZE(stm32_adc_min_ts_mp1) == STM32_ADC_INT_CH_NB); + +static const struct stm32_adc_cfg stm32mp1_adc_cfg = { + .regs = &stm32mp1_adc_regspec, + .adc_info = &stm32h7_adc_info, + .trigs = stm32h7_adc_trigs, + .has_vregready = true, + .has_boostmode = true, + .has_linearcal = true, + .has_presel = true, + .has_oversampling = true, + .start_conv = stm32h7_adc_start_conv, + .stop_conv = stm32h7_adc_stop_conv, + .prepare = stm32h7_adc_prepare, + .unprepare = stm32h7_adc_unprepare, + .smp_cycles = stm32h7_adc_smp_cycles, + .irq_clear = stm32h7_adc_irq_clear, + .ts_int_ch = stm32_adc_min_ts_mp1, + .set_ovs = stm32h7_adc_set_ovs, +}; + +static const unsigned int stm32_adc_min_ts_mp13[] = { 100, 0, 0, 4300, 9800 }; +static_assert(ARRAY_SIZE(stm32_adc_min_ts_mp13) == STM32_ADC_INT_CH_NB); + +static const struct stm32_adc_cfg stm32mp13_adc_cfg = { + .regs = &stm32mp13_adc_regspec, + .adc_info = &stm32mp13_adc_info, + .trigs = stm32h7_adc_trigs, + .has_oversampling = true, + .start_conv = stm32mp13_adc_start_conv, + .stop_conv = stm32h7_adc_stop_conv, + .prepare = stm32h7_adc_prepare, + .unprepare = stm32h7_adc_unprepare, + .smp_cycles = stm32mp13_adc_smp_cycles, + .irq_clear = stm32h7_adc_irq_clear, + .ts_int_ch = stm32_adc_min_ts_mp13, + .set_ovs = stm32mp13_adc_set_ovs, +}; + +static const struct of_device_id stm32_adc_of_match[] = { + { .compatible = "st,stm32f4-adc", .data = (void *)&stm32f4_adc_cfg }, + { .compatible = "st,stm32h7-adc", .data = (void *)&stm32h7_adc_cfg }, + { .compatible = "st,stm32mp1-adc", .data = (void *)&stm32mp1_adc_cfg }, + { .compatible = "st,stm32mp13-adc", .data = (void *)&stm32mp13_adc_cfg }, + { } +}; +MODULE_DEVICE_TABLE(of, stm32_adc_of_match); + +static struct platform_driver stm32_adc_driver = { + .probe = stm32_adc_probe, + .remove = stm32_adc_remove, + .driver = { + .name = "stm32-adc", + .of_match_table = stm32_adc_of_match, + .pm = pm_ptr(&stm32_adc_pm_ops), + }, +}; +module_platform_driver(stm32_adc_driver); + +MODULE_AUTHOR("Fabrice Gasnier "); +MODULE_DESCRIPTION("STMicroelectronics STM32 ADC IIO driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:stm32-adc"); diff --git a/drivers/iio/adc/stm32-dfsdm-adc.c b/drivers/iio/adc/stm32-dfsdm-adc.c new file mode 100644 index 000000000..00f05e167 --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm-adc.c @@ -0,0 +1,1907 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This file is the ADC part of the STM32 DFSDM driver + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Author: Arnaud Pouliquen . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "stm32-dfsdm.h" + +#define DFSDM_DMA_BUFFER_SIZE (4 * PAGE_SIZE) + +/* Conversion timeout */ +#define DFSDM_TIMEOUT_US 100000 +#define DFSDM_TIMEOUT (msecs_to_jiffies(DFSDM_TIMEOUT_US / 1000)) + +/* Oversampling attribute default */ +#define DFSDM_DEFAULT_OVERSAMPLING 100 + +/* Oversampling max values */ +#define DFSDM_MAX_INT_OVERSAMPLING 256 +#define DFSDM_MAX_FL_OVERSAMPLING 1024 + +/* Limit filter output resolution to 31 bits. (i.e. sample range is +/-2^30) */ +#define DFSDM_DATA_MAX BIT(30) +/* + * Data are output as two's complement data in a 24 bit field. + * Data from filters are in the range +/-2^(n-1) + * 2^(n-1) maximum positive value cannot be coded in 2's complement n bits + * An extra bit is required to avoid wrap-around of the binary code for 2^(n-1) + * So, the resolution of samples from filter is actually limited to 23 bits + */ +#define DFSDM_DATA_RES 24 + +/* Filter configuration */ +#define DFSDM_CR1_CFG_MASK (DFSDM_CR1_RCH_MASK | DFSDM_CR1_RCONT_MASK | \ + DFSDM_CR1_RSYNC_MASK | DFSDM_CR1_JSYNC_MASK | \ + DFSDM_CR1_JSCAN_MASK) + +enum sd_converter_type { + DFSDM_AUDIO, + DFSDM_IIO, +}; + +struct stm32_dfsdm_dev_data { + int type; + int (*init)(struct device *dev, struct iio_dev *indio_dev); + unsigned int num_channels; + const struct regmap_config *regmap_cfg; +}; + +struct stm32_dfsdm_adc { + struct stm32_dfsdm *dfsdm; + const struct stm32_dfsdm_dev_data *dev_data; + unsigned int fl_id; + unsigned int nconv; + unsigned long smask; + + /* ADC specific */ + unsigned int oversamp; + struct iio_hw_consumer *hwc; + struct iio_backend **backend; + struct completion completion; + u32 *buffer; + + /* Audio specific */ + unsigned int spi_freq; /* SPI bus clock frequency */ + unsigned int sample_freq; /* Sample frequency after filter decimation */ + int (*cb)(const void *data, size_t size, void *cb_priv); + void *cb_priv; + + /* DMA */ + u8 *rx_buf; + unsigned int bufi; /* Buffer current position */ + unsigned int buf_sz; /* Buffer size */ + struct dma_chan *dma_chan; + dma_addr_t dma_buf; +}; + +struct stm32_dfsdm_str2field { + const char *name; + unsigned int val; +}; + +/* DFSDM channel serial interface type */ +static const struct stm32_dfsdm_str2field stm32_dfsdm_chan_type[] = { + { "SPI_R", 0 }, /* SPI with data on rising edge */ + { "SPI_F", 1 }, /* SPI with data on falling edge */ + { "MANCH_R", 2 }, /* Manchester codec, rising edge = logic 0 */ + { "MANCH_F", 3 }, /* Manchester codec, falling edge = logic 1 */ + { } +}; + +/* DFSDM channel clock source */ +static const struct stm32_dfsdm_str2field stm32_dfsdm_chan_src[] = { + /* External SPI clock (CLKIN x) */ + { "CLKIN", DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL }, + /* Internal SPI clock (CLKOUT) */ + { "CLKOUT", DFSDM_CHANNEL_SPI_CLOCK_INTERNAL }, + /* Internal SPI clock divided by 2 (falling edge) */ + { "CLKOUT_F", DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_FALLING }, + /* Internal SPI clock divided by 2 (falling edge) */ + { "CLKOUT_R", DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_RISING }, + { } +}; + +static int stm32_dfsdm_str2val(const char *str, + const struct stm32_dfsdm_str2field *list) +{ + const struct stm32_dfsdm_str2field *p = list; + + for (p = list; p && p->name; p++) + if (!strcmp(p->name, str)) + return p->val; + + return -EINVAL; +} + +/** + * struct stm32_dfsdm_trig_info - DFSDM trigger info + * @name: name of the trigger, corresponding to its source + * @jextsel: trigger signal selection + */ +struct stm32_dfsdm_trig_info { + const char *name; + unsigned int jextsel; +}; + +/* hardware injected trigger enable, edge selection */ +enum stm32_dfsdm_jexten { + STM32_DFSDM_JEXTEN_DISABLED, + STM32_DFSDM_JEXTEN_RISING_EDGE, + STM32_DFSDM_JEXTEN_FALLING_EDGE, + STM32_DFSDM_EXTEN_BOTH_EDGES, +}; + +static const struct stm32_dfsdm_trig_info stm32_dfsdm_trigs[] = { + { TIM1_TRGO, 0 }, + { TIM1_TRGO2, 1 }, + { TIM8_TRGO, 2 }, + { TIM8_TRGO2, 3 }, + { TIM3_TRGO, 4 }, + { TIM4_TRGO, 5 }, + { TIM16_OC1, 6 }, + { TIM6_TRGO, 7 }, + { TIM7_TRGO, 8 }, + { LPTIM1_OUT, 26 }, + { LPTIM2_OUT, 27 }, + { LPTIM3_OUT, 28 }, + { } +}; + +static int stm32_dfsdm_get_jextsel(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + int i; + + /* lookup triggers registered by stm32 timer trigger driver */ + for (i = 0; stm32_dfsdm_trigs[i].name; i++) { + /** + * Checking both stm32 timer trigger type and trig name + * should be safe against arbitrary trigger names. + */ + if ((is_stm32_timer_trigger(trig) || + is_stm32_lptim_trigger(trig)) && + !strcmp(stm32_dfsdm_trigs[i].name, trig->name)) { + return stm32_dfsdm_trigs[i].jextsel; + } + } + + return -EINVAL; +} + +static int stm32_dfsdm_compute_osrs(struct stm32_dfsdm_filter *fl, + unsigned int fast, unsigned int oversamp) +{ + unsigned int i, d, fosr, iosr; + u64 res, max; + int bits, shift; + unsigned int m = 1; /* multiplication factor */ + unsigned int p = fl->ford; /* filter order (ford) */ + struct stm32_dfsdm_filter_osr *flo = &fl->flo[fast]; + + pr_debug("Requested oversampling: %d\n", oversamp); + /* + * This function tries to compute filter oversampling and integrator + * oversampling, base on oversampling ratio requested by user. + * + * Decimation d depends on the filter order and the oversampling ratios. + * ford: filter order + * fosr: filter over sampling ratio + * iosr: integrator over sampling ratio + */ + if (fl->ford == DFSDM_FASTSINC_ORDER) { + m = 2; + p = 2; + } + + /* + * Look for filter and integrator oversampling ratios which allows + * to maximize data output resolution. + */ + for (fosr = 1; fosr <= DFSDM_MAX_FL_OVERSAMPLING; fosr++) { + for (iosr = 1; iosr <= DFSDM_MAX_INT_OVERSAMPLING; iosr++) { + if (fast) + d = fosr * iosr; + else if (fl->ford == DFSDM_FASTSINC_ORDER) + d = fosr * (iosr + 3) + 2; + else + d = fosr * (iosr - 1 + p) + p; + + if (d > oversamp) + break; + else if (d != oversamp) + continue; + /* + * Check resolution (limited to signed 32 bits) + * res <= 2^31 + * Sincx filters: + * res = m * fosr^p x iosr (with m=1, p=ford) + * FastSinc filter + * res = m * fosr^p x iosr (with m=2, p=2) + */ + res = fosr; + for (i = p - 1; i > 0; i--) { + res = res * (u64)fosr; + if (res > DFSDM_DATA_MAX) + break; + } + if (res > DFSDM_DATA_MAX) + continue; + + res = res * (u64)m * (u64)iosr; + if (res > DFSDM_DATA_MAX) + continue; + + if (res >= flo->res) { + flo->res = res; + flo->fosr = fosr; + flo->iosr = iosr; + + bits = fls(flo->res); + /* 8 LBSs in data register contain chan info */ + max = flo->res << 8; + + /* if resolution is not a power of two */ + if (flo->res > BIT(bits - 1)) + bits++; + else + max--; + + shift = DFSDM_DATA_RES - bits; + /* + * Compute right/left shift + * Right shift is performed by hardware + * when transferring samples to data register. + * Left shift is done by software on buffer + */ + if (shift > 0) { + /* Resolution is lower than 24 bits */ + flo->rshift = 0; + flo->lshift = shift; + } else { + /* + * If resolution is 24 bits or more, + * max positive value may be ambiguous + * (equal to max negative value as sign + * bit is dropped). + * Reduce resolution to 23 bits (rshift) + * to keep the sign on bit 23 and treat + * saturation before rescaling on 24 + * bits (lshift). + */ + flo->rshift = 1 - shift; + flo->lshift = 1; + max >>= flo->rshift; + } + flo->max = (s32)max; + flo->bits = bits; + + pr_debug("fast %d, fosr %d, iosr %d, res 0x%llx/%d bits, rshift %d, lshift %d\n", + fast, flo->fosr, flo->iosr, + flo->res, bits, flo->rshift, + flo->lshift); + } + } + } + + if (!flo->res) + return -EINVAL; + + return 0; +} + +static int stm32_dfsdm_compute_all_osrs(struct iio_dev *indio_dev, + unsigned int oversamp) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + struct stm32_dfsdm_filter *fl = &adc->dfsdm->fl_list[adc->fl_id]; + int ret0, ret1; + + memset(&fl->flo[0], 0, sizeof(fl->flo[0])); + memset(&fl->flo[1], 0, sizeof(fl->flo[1])); + + ret0 = stm32_dfsdm_compute_osrs(fl, 0, oversamp); + ret1 = stm32_dfsdm_compute_osrs(fl, 1, oversamp); + if (ret0 < 0 && ret1 < 0) { + dev_err(&indio_dev->dev, + "Filter parameters not found: errors %d/%d\n", + ret0, ret1); + return -EINVAL; + } + + return 0; +} + +static int stm32_dfsdm_start_channel(struct iio_dev *indio_dev) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + struct regmap *regmap = adc->dfsdm->regmap; + const struct iio_chan_spec *chan; + unsigned int bit; + int ret; + + for_each_set_bit(bit, &adc->smask, sizeof(adc->smask) * BITS_PER_BYTE) { + chan = indio_dev->channels + bit; + ret = regmap_update_bits(regmap, DFSDM_CHCFGR1(chan->channel), + DFSDM_CHCFGR1_CHEN_MASK, + DFSDM_CHCFGR1_CHEN(1)); + if (ret < 0) + return ret; + } + + return 0; +} + +static void stm32_dfsdm_stop_channel(struct iio_dev *indio_dev) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + struct regmap *regmap = adc->dfsdm->regmap; + const struct iio_chan_spec *chan; + unsigned int bit; + + for_each_set_bit(bit, &adc->smask, sizeof(adc->smask) * BITS_PER_BYTE) { + chan = indio_dev->channels + bit; + regmap_update_bits(regmap, DFSDM_CHCFGR1(chan->channel), + DFSDM_CHCFGR1_CHEN_MASK, + DFSDM_CHCFGR1_CHEN(0)); + } +} + +static int stm32_dfsdm_chan_configure(struct stm32_dfsdm *dfsdm, + struct stm32_dfsdm_channel *ch) +{ + unsigned int id = ch->id; + struct regmap *regmap = dfsdm->regmap; + int ret; + + ret = regmap_update_bits(regmap, DFSDM_CHCFGR1(id), + DFSDM_CHCFGR1_SITP_MASK, + DFSDM_CHCFGR1_SITP(ch->type)); + if (ret < 0) + return ret; + ret = regmap_update_bits(regmap, DFSDM_CHCFGR1(id), + DFSDM_CHCFGR1_SPICKSEL_MASK, + DFSDM_CHCFGR1_SPICKSEL(ch->src)); + if (ret < 0) + return ret; + return regmap_update_bits(regmap, DFSDM_CHCFGR1(id), + DFSDM_CHCFGR1_CHINSEL_MASK, + DFSDM_CHCFGR1_CHINSEL(ch->alt_si)); +} + +static int stm32_dfsdm_start_filter(struct stm32_dfsdm_adc *adc, + unsigned int fl_id, + struct iio_trigger *trig) +{ + struct stm32_dfsdm *dfsdm = adc->dfsdm; + int ret; + + /* Enable filter */ + ret = regmap_update_bits(dfsdm->regmap, DFSDM_CR1(fl_id), + DFSDM_CR1_DFEN_MASK, DFSDM_CR1_DFEN(1)); + if (ret < 0) + return ret; + + /* Nothing more to do for injected (scan mode/triggered) conversions */ + if (adc->nconv > 1 || trig) + return 0; + + /* Software start (single or continuous) regular conversion */ + return regmap_update_bits(dfsdm->regmap, DFSDM_CR1(fl_id), + DFSDM_CR1_RSWSTART_MASK, + DFSDM_CR1_RSWSTART(1)); +} + +static void stm32_dfsdm_stop_filter(struct stm32_dfsdm *dfsdm, + unsigned int fl_id) +{ + /* Disable conversion */ + regmap_update_bits(dfsdm->regmap, DFSDM_CR1(fl_id), + DFSDM_CR1_DFEN_MASK, DFSDM_CR1_DFEN(0)); +} + +static int stm32_dfsdm_filter_set_trig(struct iio_dev *indio_dev, + unsigned int fl_id, + struct iio_trigger *trig) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + struct regmap *regmap = adc->dfsdm->regmap; + u32 jextsel = 0, jexten = STM32_DFSDM_JEXTEN_DISABLED; + int ret; + + if (trig) { + ret = stm32_dfsdm_get_jextsel(indio_dev, trig); + if (ret < 0) + return ret; + + /* set trigger source and polarity (default to rising edge) */ + jextsel = ret; + jexten = STM32_DFSDM_JEXTEN_RISING_EDGE; + } + + ret = regmap_update_bits(regmap, DFSDM_CR1(fl_id), + DFSDM_CR1_JEXTSEL_MASK | DFSDM_CR1_JEXTEN_MASK, + DFSDM_CR1_JEXTSEL(jextsel) | + DFSDM_CR1_JEXTEN(jexten)); + if (ret < 0) + return ret; + + return 0; +} + +static int stm32_dfsdm_channels_configure(struct iio_dev *indio_dev, + unsigned int fl_id, + struct iio_trigger *trig) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + struct regmap *regmap = adc->dfsdm->regmap; + struct stm32_dfsdm_filter *fl = &adc->dfsdm->fl_list[fl_id]; + struct stm32_dfsdm_filter_osr *flo = &fl->flo[0]; + const struct iio_chan_spec *chan; + unsigned int bit; + int ret; + + fl->fast = 0; + + /* + * In continuous mode, use fast mode configuration, + * if it provides a better resolution. + */ + if (adc->nconv == 1 && !trig && iio_buffer_enabled(indio_dev)) { + if (fl->flo[1].res >= fl->flo[0].res) { + fl->fast = 1; + flo = &fl->flo[1]; + } + } + + if (!flo->res) + return -EINVAL; + + dev_dbg(&indio_dev->dev, "Samples actual resolution: %d bits", + min(flo->bits, (u32)DFSDM_DATA_RES - 1)); + + for_each_set_bit(bit, &adc->smask, + sizeof(adc->smask) * BITS_PER_BYTE) { + chan = indio_dev->channels + bit; + + ret = regmap_update_bits(regmap, + DFSDM_CHCFGR2(chan->channel), + DFSDM_CHCFGR2_DTRBS_MASK, + DFSDM_CHCFGR2_DTRBS(flo->rshift)); + if (ret) + return ret; + } + + return 0; +} + +static int stm32_dfsdm_filter_configure(struct iio_dev *indio_dev, + unsigned int fl_id, + struct iio_trigger *trig) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + struct regmap *regmap = adc->dfsdm->regmap; + struct stm32_dfsdm_filter *fl = &adc->dfsdm->fl_list[fl_id]; + struct stm32_dfsdm_filter_osr *flo = &fl->flo[fl->fast]; + u32 cr1; + const struct iio_chan_spec *chan; + unsigned int bit, jchg = 0; + int ret; + + /* Average integrator oversampling */ + ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id), DFSDM_FCR_IOSR_MASK, + DFSDM_FCR_IOSR(flo->iosr - 1)); + if (ret) + return ret; + + /* Filter order and Oversampling */ + ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id), DFSDM_FCR_FOSR_MASK, + DFSDM_FCR_FOSR(flo->fosr - 1)); + if (ret) + return ret; + + ret = regmap_update_bits(regmap, DFSDM_FCR(fl_id), DFSDM_FCR_FORD_MASK, + DFSDM_FCR_FORD(fl->ford)); + if (ret) + return ret; + + ret = stm32_dfsdm_filter_set_trig(indio_dev, fl_id, trig); + if (ret) + return ret; + + ret = regmap_update_bits(regmap, DFSDM_CR1(fl_id), + DFSDM_CR1_FAST_MASK, + DFSDM_CR1_FAST(fl->fast)); + if (ret) + return ret; + + /* + * DFSDM modes configuration W.R.T audio/iio type modes + * ---------------------------------------------------------------- + * Modes | regular | regular | injected | injected | + * | | continuous | | + scan | + * --------------|---------|--------------|----------|------------| + * single conv | x | | | | + * (1 chan) | | | | | + * --------------|---------|--------------|----------|------------| + * 1 Audio chan | | sample freq | | | + * | | or sync_mode | | | + * --------------|---------|--------------|----------|------------| + * 1 IIO chan | | sample freq | trigger | | + * | | or sync_mode | | | + * --------------|---------|--------------|----------|------------| + * 2+ IIO chans | | | | trigger or | + * | | | | sync_mode | + * ---------------------------------------------------------------- + */ + if (adc->nconv == 1 && !trig) { + bit = __ffs(adc->smask); + chan = indio_dev->channels + bit; + + /* Use regular conversion for single channel without trigger */ + cr1 = DFSDM_CR1_RCH(chan->channel); + + /* Continuous conversions triggered by SPI clk in buffer mode */ + if (iio_buffer_enabled(indio_dev)) + cr1 |= DFSDM_CR1_RCONT(1); + + cr1 |= DFSDM_CR1_RSYNC(fl->sync_mode); + } else { + /* Use injected conversion for multiple channels */ + for_each_set_bit(bit, &adc->smask, + sizeof(adc->smask) * BITS_PER_BYTE) { + chan = indio_dev->channels + bit; + jchg |= BIT(chan->channel); + } + ret = regmap_write(regmap, DFSDM_JCHGR(fl_id), jchg); + if (ret < 0) + return ret; + + /* Use scan mode for multiple channels */ + cr1 = DFSDM_CR1_JSCAN((adc->nconv > 1) ? 1 : 0); + + /* + * Continuous conversions not supported in injected mode, + * either use: + * - conversions in sync with filter 0 + * - triggered conversions + */ + if (!fl->sync_mode && !trig) + return -EINVAL; + cr1 |= DFSDM_CR1_JSYNC(fl->sync_mode); + } + + return regmap_update_bits(regmap, DFSDM_CR1(fl_id), DFSDM_CR1_CFG_MASK, + cr1); +} + +static int stm32_dfsdm_channel_parse_of(struct stm32_dfsdm *dfsdm, + struct iio_dev *indio_dev, + struct iio_chan_spec *ch) +{ + struct stm32_dfsdm_channel *df_ch; + const char *of_str; + int chan_idx = ch->scan_index; + int ret, val; + + ret = of_property_read_u32_index(indio_dev->dev.of_node, + "st,adc-channels", chan_idx, + &ch->channel); + if (ret < 0) { + dev_err(&indio_dev->dev, + " Error parsing 'st,adc-channels' for idx %d\n", + chan_idx); + return ret; + } + if (ch->channel >= dfsdm->num_chs) { + dev_err(&indio_dev->dev, + " Error bad channel number %d (max = %d)\n", + ch->channel, dfsdm->num_chs); + return -EINVAL; + } + + ret = of_property_read_string_index(indio_dev->dev.of_node, + "st,adc-channel-names", chan_idx, + &ch->datasheet_name); + if (ret < 0) { + dev_err(&indio_dev->dev, + " Error parsing 'st,adc-channel-names' for idx %d\n", + chan_idx); + return ret; + } + + df_ch = &dfsdm->ch_list[ch->channel]; + df_ch->id = ch->channel; + + ret = of_property_read_string_index(indio_dev->dev.of_node, + "st,adc-channel-types", chan_idx, + &of_str); + if (!ret) { + val = stm32_dfsdm_str2val(of_str, stm32_dfsdm_chan_type); + if (val < 0) + return val; + } else { + val = 0; + } + df_ch->type = val; + + ret = of_property_read_string_index(indio_dev->dev.of_node, + "st,adc-channel-clk-src", chan_idx, + &of_str); + if (!ret) { + val = stm32_dfsdm_str2val(of_str, stm32_dfsdm_chan_src); + if (val < 0) + return val; + } else { + val = 0; + } + df_ch->src = val; + + df_ch->alt_si = of_property_present(indio_dev->dev.of_node, + "st,adc-alt-channel"); + + return 0; +} + +static int stm32_dfsdm_generic_channel_parse_of(struct stm32_dfsdm *dfsdm, + struct iio_dev *indio_dev, + struct iio_chan_spec *ch, + struct fwnode_handle *node) +{ + struct stm32_dfsdm_channel *df_ch; + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + struct iio_backend *backend; + const char *of_str; + int ret, val; + + ret = fwnode_property_read_u32(node, "reg", &ch->channel); + if (ret < 0) { + dev_err(&indio_dev->dev, "Missing channel index %d\n", ret); + return ret; + } + + if (ch->channel >= dfsdm->num_chs) { + dev_err(&indio_dev->dev, " Error bad channel number %d (max = %d)\n", + ch->channel, dfsdm->num_chs); + return -EINVAL; + } + + if (fwnode_property_present(node, "label")) { + /* label is optional */ + ret = fwnode_property_read_string(node, "label", &ch->datasheet_name); + if (ret < 0) { + dev_err(&indio_dev->dev, + " Error parsing 'label' for idx %d\n", ch->channel); + return ret; + } + } + + df_ch = &dfsdm->ch_list[ch->channel]; + df_ch->id = ch->channel; + + ret = fwnode_property_read_string(node, "st,adc-channel-type", &of_str); + if (!ret) { + val = stm32_dfsdm_str2val(of_str, stm32_dfsdm_chan_type); + if (val < 0) + return val; + } else { + val = 0; + } + df_ch->type = val; + + ret = fwnode_property_read_string(node, "st,adc-channel-clk-src", &of_str); + if (!ret) { + val = stm32_dfsdm_str2val(of_str, stm32_dfsdm_chan_src); + if (val < 0) + return val; + } else { + val = 0; + } + df_ch->src = val; + + if (fwnode_property_present(node, "st,adc-alt-channel")) + df_ch->alt_si = 1; + + if (adc->dev_data->type == DFSDM_IIO) { + backend = devm_iio_backend_fwnode_get(&indio_dev->dev, NULL, node); + if (IS_ERR(backend)) + return dev_err_probe(&indio_dev->dev, PTR_ERR(backend), + "Failed to get backend\n"); + adc->backend[ch->scan_index] = backend; + } + + return 0; +} + +static ssize_t dfsdm_adc_audio_get_spiclk(struct iio_dev *indio_dev, + uintptr_t priv, + const struct iio_chan_spec *chan, + char *buf) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", adc->spi_freq); +} + +static int dfsdm_adc_set_samp_freq(struct iio_dev *indio_dev, + unsigned int sample_freq, + unsigned int spi_freq) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + unsigned int oversamp; + int ret; + + oversamp = DIV_ROUND_CLOSEST(spi_freq, sample_freq); + if (spi_freq % sample_freq) + dev_dbg(&indio_dev->dev, + "Rate not accurate. requested (%u), actual (%u)\n", + sample_freq, spi_freq / oversamp); + + ret = stm32_dfsdm_compute_all_osrs(indio_dev, oversamp); + if (ret < 0) + return ret; + + adc->sample_freq = spi_freq / oversamp; + adc->oversamp = oversamp; + + return 0; +} + +static ssize_t dfsdm_adc_audio_set_spiclk(struct iio_dev *indio_dev, + uintptr_t priv, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + struct stm32_dfsdm_channel *ch = &adc->dfsdm->ch_list[chan->channel]; + unsigned int sample_freq = adc->sample_freq; + unsigned int spi_freq; + int ret; + + dev_err(&indio_dev->dev, "enter %s\n", __func__); + /* If DFSDM is master on SPI, SPI freq can not be updated */ + if (ch->src != DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL) + return -EPERM; + + ret = kstrtoint(buf, 0, &spi_freq); + if (ret) + return ret; + + if (!spi_freq) + return -EINVAL; + + if (sample_freq) { + ret = dfsdm_adc_set_samp_freq(indio_dev, sample_freq, spi_freq); + if (ret < 0) + return ret; + } + adc->spi_freq = spi_freq; + + return len; +} + +static int stm32_dfsdm_start_conv(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + struct regmap *regmap = adc->dfsdm->regmap; + int ret; + + ret = stm32_dfsdm_channels_configure(indio_dev, adc->fl_id, trig); + if (ret < 0) + return ret; + + ret = stm32_dfsdm_start_channel(indio_dev); + if (ret < 0) + return ret; + + ret = stm32_dfsdm_filter_configure(indio_dev, adc->fl_id, trig); + if (ret < 0) + goto stop_channels; + + ret = stm32_dfsdm_start_filter(adc, adc->fl_id, trig); + if (ret < 0) + goto filter_unconfigure; + + return 0; + +filter_unconfigure: + regmap_clear_bits(regmap, DFSDM_CR1(adc->fl_id), DFSDM_CR1_CFG_MASK); +stop_channels: + stm32_dfsdm_stop_channel(indio_dev); + + return ret; +} + +static void stm32_dfsdm_stop_conv(struct iio_dev *indio_dev) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + struct regmap *regmap = adc->dfsdm->regmap; + + stm32_dfsdm_stop_filter(adc->dfsdm, adc->fl_id); + + regmap_clear_bits(regmap, DFSDM_CR1(adc->fl_id), DFSDM_CR1_CFG_MASK); + + stm32_dfsdm_stop_channel(indio_dev); +} + +static int stm32_dfsdm_set_watermark(struct iio_dev *indio_dev, + unsigned int val) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + unsigned int watermark = DFSDM_DMA_BUFFER_SIZE / 2; + unsigned int rx_buf_sz = DFSDM_DMA_BUFFER_SIZE; + + /* + * DMA cyclic transfers are used, buffer is split into two periods. + * There should be : + * - always one buffer (period) DMA is working on + * - one buffer (period) driver pushed to ASoC side. + */ + watermark = min(watermark, val * (unsigned int)(sizeof(u32))); + adc->buf_sz = min(rx_buf_sz, watermark * 2 * adc->nconv); + + return 0; +} + +static unsigned int stm32_dfsdm_adc_dma_residue(struct stm32_dfsdm_adc *adc) +{ + struct dma_tx_state state; + enum dma_status status; + + status = dmaengine_tx_status(adc->dma_chan, + adc->dma_chan->cookie, + &state); + if (status == DMA_IN_PROGRESS) { + /* Residue is size in bytes from end of buffer */ + unsigned int i = adc->buf_sz - state.residue; + unsigned int size; + + /* Return available bytes */ + if (i >= adc->bufi) + size = i - adc->bufi; + else + size = adc->buf_sz + i - adc->bufi; + + return size; + } + + return 0; +} + +static inline void stm32_dfsdm_process_data(struct stm32_dfsdm_adc *adc, + s32 *buffer) +{ + struct stm32_dfsdm_filter *fl = &adc->dfsdm->fl_list[adc->fl_id]; + struct stm32_dfsdm_filter_osr *flo = &fl->flo[fl->fast]; + unsigned int i = adc->nconv; + s32 *ptr = buffer; + + while (i--) { + /* Mask 8 LSB that contains the channel ID */ + *ptr &= 0xFFFFFF00; + /* Convert 2^(n-1) sample to 2^(n-1)-1 to avoid wrap-around */ + if (*ptr > flo->max) + *ptr -= 1; + /* + * Samples from filter are retrieved with 23 bits resolution + * or less. Shift left to align MSB on 24 bits. + */ + *ptr <<= flo->lshift; + + ptr++; + } +} + +static void stm32_dfsdm_dma_buffer_done(void *data) +{ + struct iio_dev *indio_dev = data; + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + int available = stm32_dfsdm_adc_dma_residue(adc); + size_t old_pos; + + /* + * FIXME: In Kernel interface does not support cyclic DMA buffer,and + * offers only an interface to push data samples per samples. + * For this reason IIO buffer interface is not used and interface is + * bypassed using a private callback registered by ASoC. + * This should be a temporary solution waiting a cyclic DMA engine + * support in IIO. + */ + + dev_dbg(&indio_dev->dev, "pos = %d, available = %d\n", + adc->bufi, available); + old_pos = adc->bufi; + + while (available >= indio_dev->scan_bytes) { + s32 *buffer = (s32 *)&adc->rx_buf[adc->bufi]; + + stm32_dfsdm_process_data(adc, buffer); + + available -= indio_dev->scan_bytes; + adc->bufi += indio_dev->scan_bytes; + if (adc->bufi >= adc->buf_sz) { + if (adc->cb) + adc->cb(&adc->rx_buf[old_pos], + adc->buf_sz - old_pos, adc->cb_priv); + adc->bufi = 0; + old_pos = 0; + } + /* + * In DMA mode the trigger services of IIO are not used + * (e.g. no call to iio_trigger_poll). + * Calling irq handler associated to the hardware trigger is not + * relevant as the conversions have already been done. Data + * transfers are performed directly in DMA callback instead. + * This implementation avoids to call trigger irq handler that + * may sleep, in an atomic context (DMA irq handler context). + */ + if (adc->dev_data->type == DFSDM_IIO) + iio_push_to_buffers(indio_dev, buffer); + } + if (adc->cb) + adc->cb(&adc->rx_buf[old_pos], adc->bufi - old_pos, + adc->cb_priv); +} + +static int stm32_dfsdm_adc_dma_start(struct iio_dev *indio_dev) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + /* + * The DFSDM supports half-word transfers. However, for 16 bits record, + * 4 bytes buswidth is kept, to avoid losing samples LSBs when left + * shift is required. + */ + struct dma_slave_config config = { + .src_addr = (dma_addr_t)adc->dfsdm->phys_base, + .src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES, + }; + struct dma_async_tx_descriptor *desc; + dma_cookie_t cookie; + int ret; + + if (!adc->dma_chan) + return -EINVAL; + + dev_dbg(&indio_dev->dev, "size=%d watermark=%d\n", + adc->buf_sz, adc->buf_sz / 2); + + if (adc->nconv == 1 && !indio_dev->trig) + config.src_addr += DFSDM_RDATAR(adc->fl_id); + else + config.src_addr += DFSDM_JDATAR(adc->fl_id); + ret = dmaengine_slave_config(adc->dma_chan, &config); + if (ret) + return ret; + + /* Prepare a DMA cyclic transaction */ + desc = dmaengine_prep_dma_cyclic(adc->dma_chan, + adc->dma_buf, + adc->buf_sz, adc->buf_sz / 2, + DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT); + if (!desc) + return -EBUSY; + + desc->callback = stm32_dfsdm_dma_buffer_done; + desc->callback_param = indio_dev; + + cookie = dmaengine_submit(desc); + ret = dma_submit_error(cookie); + if (ret) + goto err_stop_dma; + + /* Issue pending DMA requests */ + dma_async_issue_pending(adc->dma_chan); + + if (adc->nconv == 1 && !indio_dev->trig) { + /* Enable regular DMA transfer*/ + ret = regmap_set_bits(adc->dfsdm->regmap, + DFSDM_CR1(adc->fl_id), + DFSDM_CR1_RDMAEN_MASK); + } else { + /* Enable injected DMA transfer*/ + ret = regmap_set_bits(adc->dfsdm->regmap, + DFSDM_CR1(adc->fl_id), + DFSDM_CR1_JDMAEN_MASK); + } + + if (ret < 0) + goto err_stop_dma; + + return 0; + +err_stop_dma: + dmaengine_terminate_all(adc->dma_chan); + + return ret; +} + +static void stm32_dfsdm_adc_dma_stop(struct iio_dev *indio_dev) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + + if (!adc->dma_chan) + return; + + regmap_clear_bits(adc->dfsdm->regmap, DFSDM_CR1(adc->fl_id), + DFSDM_CR1_RDMAEN_MASK | DFSDM_CR1_JDMAEN_MASK); + dmaengine_terminate_all(adc->dma_chan); +} + +static int stm32_dfsdm_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + + adc->nconv = bitmap_weight(scan_mask, iio_get_masklength(indio_dev)); + adc->smask = *scan_mask; + + dev_dbg(&indio_dev->dev, "nconv=%d mask=%lx\n", adc->nconv, *scan_mask); + + return 0; +} + +static int stm32_dfsdm_postenable(struct iio_dev *indio_dev) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + int i = 0; + int ret; + + /* Reset adc buffer index */ + adc->bufi = 0; + + if (adc->hwc) { + ret = iio_hw_consumer_enable(adc->hwc); + if (ret < 0) + return ret; + } + + if (adc->backend) { + while (adc->backend[i]) { + ret = iio_backend_enable(adc->backend[i]); + if (ret < 0) + return ret; + i++; + } + } + + ret = stm32_dfsdm_start_dfsdm(adc->dfsdm); + if (ret < 0) + goto err_stop_hwc; + + ret = stm32_dfsdm_adc_dma_start(indio_dev); + if (ret) { + dev_err(&indio_dev->dev, "Can't start DMA\n"); + goto stop_dfsdm; + } + + ret = stm32_dfsdm_start_conv(indio_dev, indio_dev->trig); + if (ret) { + dev_err(&indio_dev->dev, "Can't start conversion\n"); + goto err_stop_dma; + } + + return 0; + +err_stop_dma: + stm32_dfsdm_adc_dma_stop(indio_dev); +stop_dfsdm: + stm32_dfsdm_stop_dfsdm(adc->dfsdm); +err_stop_hwc: + if (adc->hwc) + iio_hw_consumer_disable(adc->hwc); + + return ret; +} + +static int stm32_dfsdm_predisable(struct iio_dev *indio_dev) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + int i = 0; + + stm32_dfsdm_stop_conv(indio_dev); + + stm32_dfsdm_adc_dma_stop(indio_dev); + + stm32_dfsdm_stop_dfsdm(adc->dfsdm); + + if (adc->backend) { + while (adc->backend[i]) { + iio_backend_disable(adc->backend[i]); + i++; + } + } + + if (adc->hwc) + iio_hw_consumer_disable(adc->hwc); + + return 0; +} + +static const struct iio_buffer_setup_ops stm32_dfsdm_buffer_setup_ops = { + .postenable = &stm32_dfsdm_postenable, + .predisable = &stm32_dfsdm_predisable, +}; + +/** + * stm32_dfsdm_get_buff_cb() - register a callback that will be called when + * DMA transfer period is achieved. + * + * @iio_dev: Handle to IIO device. + * @cb: Pointer to callback function: + * - data: pointer to data buffer + * - size: size in byte of the data buffer + * - private: pointer to consumer private structure. + * @private: Pointer to consumer private structure. + */ +int stm32_dfsdm_get_buff_cb(struct iio_dev *iio_dev, + int (*cb)(const void *data, size_t size, + void *private), + void *private) +{ + struct stm32_dfsdm_adc *adc; + + if (!iio_dev) + return -EINVAL; + adc = iio_priv(iio_dev); + + adc->cb = cb; + adc->cb_priv = private; + + return 0; +} +EXPORT_SYMBOL_GPL(stm32_dfsdm_get_buff_cb); + +/** + * stm32_dfsdm_release_buff_cb - unregister buffer callback + * + * @iio_dev: Handle to IIO device. + */ +int stm32_dfsdm_release_buff_cb(struct iio_dev *iio_dev) +{ + struct stm32_dfsdm_adc *adc; + + if (!iio_dev) + return -EINVAL; + adc = iio_priv(iio_dev); + + adc->cb = NULL; + adc->cb_priv = NULL; + + return 0; +} +EXPORT_SYMBOL_GPL(stm32_dfsdm_release_buff_cb); + +static int stm32_dfsdm_single_conv(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *res) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + long time_left; + int ret; + + reinit_completion(&adc->completion); + + adc->buffer = res; + + ret = stm32_dfsdm_start_dfsdm(adc->dfsdm); + if (ret < 0) + return ret; + + ret = regmap_update_bits(adc->dfsdm->regmap, DFSDM_CR2(adc->fl_id), + DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(1)); + if (ret < 0) + goto stop_dfsdm; + + adc->nconv = 1; + adc->smask = BIT(chan->scan_index); + ret = stm32_dfsdm_start_conv(indio_dev, NULL); + if (ret < 0) { + regmap_update_bits(adc->dfsdm->regmap, DFSDM_CR2(adc->fl_id), + DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(0)); + goto stop_dfsdm; + } + + time_left = wait_for_completion_interruptible_timeout(&adc->completion, + DFSDM_TIMEOUT); + + /* Mask IRQ for regular conversion achievement*/ + regmap_update_bits(adc->dfsdm->regmap, DFSDM_CR2(adc->fl_id), + DFSDM_CR2_REOCIE_MASK, DFSDM_CR2_REOCIE(0)); + + if (time_left == 0) + ret = -ETIMEDOUT; + else if (time_left < 0) + ret = time_left; + else + ret = IIO_VAL_INT; + + stm32_dfsdm_stop_conv(indio_dev); + + stm32_dfsdm_process_data(adc, res); + +stop_dfsdm: + stm32_dfsdm_stop_dfsdm(adc->dfsdm); + + return ret; +} + +static int stm32_dfsdm_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + struct stm32_dfsdm_channel *ch = &adc->dfsdm->ch_list[chan->channel]; + unsigned int spi_freq; + int ret = -EINVAL; + + switch (ch->src) { + case DFSDM_CHANNEL_SPI_CLOCK_INTERNAL: + spi_freq = adc->dfsdm->spi_master_freq; + break; + case DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_FALLING: + case DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_RISING: + spi_freq = adc->dfsdm->spi_master_freq / 2; + break; + default: + spi_freq = adc->spi_freq; + } + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = stm32_dfsdm_compute_all_osrs(indio_dev, val); + if (!ret) { + dev_dbg(&indio_dev->dev, + "Sampling rate changed from (%u) to (%u)\n", + adc->sample_freq, spi_freq / val); + adc->oversamp = val; + adc->sample_freq = spi_freq / val; + } + iio_device_release_direct(indio_dev); + return ret; + + case IIO_CHAN_INFO_SAMP_FREQ: + if (!val) + return -EINVAL; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = dfsdm_adc_set_samp_freq(indio_dev, val, spi_freq); + iio_device_release_direct(indio_dev); + return ret; + } + + return -EINVAL; +} + +static int __stm32_dfsdm_read_info_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + int ret = 0; + + if (adc->hwc) + ret = iio_hw_consumer_enable(adc->hwc); + if (adc->backend) + ret = iio_backend_enable(adc->backend[chan->scan_index]); + if (ret < 0) { + dev_err(&indio_dev->dev, + "%s: IIO enable failed (channel %d)\n", + __func__, chan->channel); + return ret; + } + ret = stm32_dfsdm_single_conv(indio_dev, chan, val); + if (adc->hwc) + iio_hw_consumer_disable(adc->hwc); + if (adc->backend) + iio_backend_disable(adc->backend[chan->scan_index]); + if (ret < 0) { + dev_err(&indio_dev->dev, + "%s: Conversion failed (channel %d)\n", + __func__, chan->channel); + return ret; + } + + return 0; +} + +static int stm32_dfsdm_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + + struct stm32_dfsdm_filter *fl = &adc->dfsdm->fl_list[adc->fl_id]; + struct stm32_dfsdm_filter_osr *flo = &fl->flo[fl->fast]; + u32 max = flo->max << (flo->lshift - chan->scan_type.shift); + int idx = chan->scan_index; + int ret; + + if (flo->lshift < chan->scan_type.shift) + max = flo->max >> (chan->scan_type.shift - flo->lshift); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = __stm32_dfsdm_read_info_raw(indio_dev, chan, val); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = adc->oversamp; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = adc->sample_freq; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + /* + * Scale is expressed in mV. + * When fast mode is disabled, actual resolution may be lower + * than 2^n, where n = realbits - 1. + * This leads to underestimating the input voltage. + * To compensate this deviation, the voltage reference can be + * corrected with a factor = realbits resolution / actual max + */ + if (adc->backend) { + ret = iio_backend_read_scale(adc->backend[idx], chan, val, NULL); + if (ret < 0) + return ret; + + *val = div_u64((u64)*val * (u64)BIT(DFSDM_DATA_RES - 1), max); + *val2 = chan->scan_type.realbits; + if (chan->differential) + *val *= 2; + } + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_OFFSET: + /* + * DFSDM output data are in the range [-2^n, 2^n], + * with n = realbits - 1. + * - Differential modulator: + * Offset correspond to SD modulator offset. + * - Single ended modulator: + * Input is in [0V, Vref] range, + * where 0V corresponds to -2^n, and Vref to 2^n. + * Add 2^n to offset. (i.e. middle of input range) + * offset = offset(sd) * vref / res(sd) * max / vref. + */ + if (adc->backend) { + ret = iio_backend_read_offset(adc->backend[idx], chan, val, NULL); + if (ret < 0) + return ret; + + *val = div_u64((u64)max * *val, BIT(*val2 - 1)); + if (!chan->differential) + *val += max; + } + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int stm32_dfsdm_validate_trigger(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + return stm32_dfsdm_get_jextsel(indio_dev, trig) < 0 ? -EINVAL : 0; +} + +static const struct iio_info stm32_dfsdm_info_audio = { + .hwfifo_set_watermark = stm32_dfsdm_set_watermark, + .read_raw = stm32_dfsdm_read_raw, + .write_raw = stm32_dfsdm_write_raw, + .update_scan_mode = stm32_dfsdm_update_scan_mode, +}; + +static const struct iio_info stm32_dfsdm_info_adc = { + .hwfifo_set_watermark = stm32_dfsdm_set_watermark, + .read_raw = stm32_dfsdm_read_raw, + .write_raw = stm32_dfsdm_write_raw, + .update_scan_mode = stm32_dfsdm_update_scan_mode, + .validate_trigger = stm32_dfsdm_validate_trigger, +}; + +static irqreturn_t stm32_dfsdm_irq(int irq, void *arg) +{ + struct iio_dev *indio_dev = arg; + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + struct regmap *regmap = adc->dfsdm->regmap; + unsigned int status, int_en; + + regmap_read(regmap, DFSDM_ISR(adc->fl_id), &status); + regmap_read(regmap, DFSDM_CR2(adc->fl_id), &int_en); + + if (status & DFSDM_ISR_REOCF_MASK) { + /* Read the data register clean the IRQ status */ + regmap_read(regmap, DFSDM_RDATAR(adc->fl_id), adc->buffer); + complete(&adc->completion); + } + + if (status & DFSDM_ISR_ROVRF_MASK) { + if (int_en & DFSDM_CR2_ROVRIE_MASK) + dev_warn(&indio_dev->dev, "Overrun detected\n"); + regmap_set_bits(regmap, DFSDM_ICR(adc->fl_id), + DFSDM_ICR_CLRROVRF_MASK); + } + + return IRQ_HANDLED; +} + +/* + * Define external info for SPI Frequency and audio sampling rate that can be + * configured by ASoC driver through consumer.h API + */ +static const struct iio_chan_spec_ext_info dfsdm_adc_audio_ext_info[] = { + /* spi_clk_freq : clock freq on SPI/manchester bus used by channel */ + { + .name = "spi_clk_freq", + .shared = IIO_SHARED_BY_TYPE, + .read = dfsdm_adc_audio_get_spiclk, + .write = dfsdm_adc_audio_set_spiclk, + }, + { } +}; + +static void stm32_dfsdm_dma_release(struct iio_dev *indio_dev) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + + if (adc->dma_chan) { + dma_free_coherent(adc->dma_chan->device->dev, + DFSDM_DMA_BUFFER_SIZE, + adc->rx_buf, adc->dma_buf); + dma_release_channel(adc->dma_chan); + } +} + +static int stm32_dfsdm_dma_request(struct device *dev, + struct iio_dev *indio_dev) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + + adc->dma_chan = dma_request_chan(dev, "rx"); + if (IS_ERR(adc->dma_chan)) { + int ret = PTR_ERR(adc->dma_chan); + + adc->dma_chan = NULL; + return ret; + } + + adc->rx_buf = dma_alloc_coherent(adc->dma_chan->device->dev, + DFSDM_DMA_BUFFER_SIZE, + &adc->dma_buf, GFP_KERNEL); + if (!adc->rx_buf) { + dma_release_channel(adc->dma_chan); + return -ENOMEM; + } + + indio_dev->modes |= INDIO_BUFFER_SOFTWARE; + indio_dev->setup_ops = &stm32_dfsdm_buffer_setup_ops; + + return 0; +} + +static int stm32_dfsdm_adc_chan_init_one(struct iio_dev *indio_dev, struct iio_chan_spec *ch, + struct fwnode_handle *child) +{ + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + int ret; + + if (child) + ret = stm32_dfsdm_generic_channel_parse_of(adc->dfsdm, indio_dev, ch, child); + else /* Legacy binding */ + ret = stm32_dfsdm_channel_parse_of(adc->dfsdm, indio_dev, ch); + if (ret < 0) + return dev_err_probe(&indio_dev->dev, ret, "Failed to parse channel\n"); + + ch->type = IIO_VOLTAGE; + ch->indexed = 1; + + /* + * IIO_CHAN_INFO_RAW: used to compute regular conversion + * IIO_CHAN_INFO_OVERSAMPLING_RATIO: used to set oversampling + */ + if (child) { + ch->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET); + } else { + /* Legacy. Scaling not supported */ + ch->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); + } + + ch->info_mask_shared_by_all = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | + BIT(IIO_CHAN_INFO_SAMP_FREQ); + + if (adc->dev_data->type == DFSDM_AUDIO) { + ch->ext_info = dfsdm_adc_audio_ext_info; + ch->scan_index = 0; + } else { + ch->scan_type.shift = 8; + } + ch->scan_type.sign = 's'; + ch->scan_type.realbits = 24; + ch->scan_type.storagebits = 32; + + return stm32_dfsdm_chan_configure(adc->dfsdm, + &adc->dfsdm->ch_list[ch->channel]); +} + +static int stm32_dfsdm_chan_init(struct iio_dev *indio_dev, struct iio_chan_spec *channels) +{ + int num_ch = indio_dev->num_channels; + int chan_idx = 0; + int ret; + + for (chan_idx = 0; chan_idx < num_ch; chan_idx++) { + channels[chan_idx].scan_index = chan_idx; + ret = stm32_dfsdm_adc_chan_init_one(indio_dev, &channels[chan_idx], NULL); + if (ret < 0) + return dev_err_probe(&indio_dev->dev, ret, "Channels init failed\n"); + } + + return 0; +} + +static int stm32_dfsdm_generic_chan_init(struct iio_dev *indio_dev, struct iio_chan_spec *channels) +{ + int chan_idx = 0, ret; + + device_for_each_child_node_scoped(&indio_dev->dev, child) { + /* Skip DAI node in DFSDM audio nodes */ + if (fwnode_property_present(child, "compatible")) + continue; + + channels[chan_idx].scan_index = chan_idx; + ret = stm32_dfsdm_adc_chan_init_one(indio_dev, &channels[chan_idx], child); + if (ret < 0) + return dev_err_probe(&indio_dev->dev, ret, "Channels init failed\n"); + + chan_idx++; + } + + return chan_idx; +} + +static int stm32_dfsdm_audio_init(struct device *dev, struct iio_dev *indio_dev) +{ + struct iio_chan_spec *ch; + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + struct stm32_dfsdm_channel *d_ch; + bool legacy = false; + int num_ch, ret; + + /* If st,adc-channels is defined legacy binding is used. Else assume generic binding. */ + num_ch = of_property_count_u32_elems(indio_dev->dev.of_node, "st,adc-channels"); + if (num_ch == 1) + legacy = true; + + ch = devm_kzalloc(&indio_dev->dev, sizeof(*ch), GFP_KERNEL); + if (!ch) + return -ENOMEM; + + indio_dev->num_channels = 1; + indio_dev->channels = ch; + + if (legacy) + ret = stm32_dfsdm_chan_init(indio_dev, ch); + else + ret = stm32_dfsdm_generic_chan_init(indio_dev, ch); + + if (ret < 0) { + dev_err(&indio_dev->dev, "Channels init failed\n"); + return ret; + } + ch->info_mask_separate = BIT(IIO_CHAN_INFO_SAMP_FREQ); + + d_ch = &adc->dfsdm->ch_list[ch->channel]; + if (d_ch->src != DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL) + adc->spi_freq = adc->dfsdm->spi_master_freq; + + return stm32_dfsdm_dma_request(dev, indio_dev); +} + +static int stm32_dfsdm_adc_init(struct device *dev, struct iio_dev *indio_dev) +{ + struct iio_chan_spec *ch; + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + int num_ch, ret; + bool legacy = false; + + adc->oversamp = DFSDM_DEFAULT_OVERSAMPLING; + ret = stm32_dfsdm_compute_all_osrs(indio_dev, adc->oversamp); + if (ret < 0) + return ret; + + num_ch = device_get_child_node_count(&indio_dev->dev); + if (!num_ch) { + /* No channels nodes found. Assume legacy binding */ + num_ch = of_property_count_u32_elems(indio_dev->dev.of_node, "st,adc-channels"); + if (num_ch < 0) { + dev_err(&indio_dev->dev, "Bad st,adc-channels\n"); + return num_ch; + } + + legacy = true; + } + + if (num_ch > adc->dfsdm->num_chs) { + dev_err(&indio_dev->dev, "Number of channel [%d] exceeds [%d]\n", + num_ch, adc->dfsdm->num_chs); + return -EINVAL; + } + indio_dev->num_channels = num_ch; + + if (legacy) { + /* Bind to SD modulator IIO device. */ + adc->hwc = devm_iio_hw_consumer_alloc(&indio_dev->dev); + if (IS_ERR(adc->hwc)) + return dev_err_probe(&indio_dev->dev, -EPROBE_DEFER, + "waiting for SD modulator\n"); + } else { + /* Generic binding. SD modulator IIO device not used. Use SD modulator backend. */ + adc->hwc = NULL; + + adc->backend = devm_kcalloc(&indio_dev->dev, num_ch, sizeof(*adc->backend), + GFP_KERNEL); + if (!adc->backend) + return -ENOMEM; + } + + ch = devm_kcalloc(&indio_dev->dev, num_ch, sizeof(*ch), GFP_KERNEL); + if (!ch) + return -ENOMEM; + indio_dev->channels = ch; + + if (legacy) + ret = stm32_dfsdm_chan_init(indio_dev, ch); + else + ret = stm32_dfsdm_generic_chan_init(indio_dev, ch); + if (ret < 0) + return ret; + + init_completion(&adc->completion); + + /* Optionally request DMA */ + ret = stm32_dfsdm_dma_request(dev, indio_dev); + if (ret) { + if (ret != -ENODEV) + return dev_err_probe(dev, ret, + "DMA channel request failed with\n"); + + dev_dbg(dev, "No DMA support\n"); + return 0; + } + + ret = iio_triggered_buffer_setup(indio_dev, + &iio_pollfunc_store_time, NULL, + &stm32_dfsdm_buffer_setup_ops); + if (ret) { + stm32_dfsdm_dma_release(indio_dev); + dev_err(&indio_dev->dev, "buffer setup failed\n"); + return ret; + } + + /* lptimer/timer hardware triggers */ + indio_dev->modes |= INDIO_HARDWARE_TRIGGERED; + + return 0; +} + +static const struct stm32_dfsdm_dev_data stm32h7_dfsdm_adc_data = { + .type = DFSDM_IIO, + .init = stm32_dfsdm_adc_init, +}; + +static const struct stm32_dfsdm_dev_data stm32h7_dfsdm_audio_data = { + .type = DFSDM_AUDIO, + .init = stm32_dfsdm_audio_init, +}; + +static const struct of_device_id stm32_dfsdm_adc_match[] = { + { + .compatible = "st,stm32-dfsdm-adc", + .data = &stm32h7_dfsdm_adc_data, + }, + { + .compatible = "st,stm32-dfsdm-dmic", + .data = &stm32h7_dfsdm_audio_data, + }, + { } +}; +MODULE_DEVICE_TABLE(of, stm32_dfsdm_adc_match); + +static int stm32_dfsdm_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct stm32_dfsdm_adc *adc; + struct device_node *np = dev->of_node; + const struct stm32_dfsdm_dev_data *dev_data; + struct iio_dev *iio; + char *name; + int ret, irq, val; + + dev_data = of_device_get_match_data(dev); + iio = devm_iio_device_alloc(dev, sizeof(*adc)); + if (!iio) + return -ENOMEM; + + adc = iio_priv(iio); + adc->dfsdm = dev_get_drvdata(dev->parent); + + iio->dev.of_node = np; + iio->modes = INDIO_DIRECT_MODE; + + platform_set_drvdata(pdev, iio); + + ret = of_property_read_u32(dev->of_node, "reg", &adc->fl_id); + if (ret != 0 || adc->fl_id >= adc->dfsdm->num_fls) { + dev_err(dev, "Missing or bad reg property\n"); + return -EINVAL; + } + + name = devm_kzalloc(dev, sizeof("dfsdm-adc0"), GFP_KERNEL); + if (!name) + return -ENOMEM; + if (dev_data->type == DFSDM_AUDIO) { + iio->info = &stm32_dfsdm_info_audio; + snprintf(name, sizeof("dfsdm-pdm0"), "dfsdm-pdm%d", adc->fl_id); + } else { + iio->info = &stm32_dfsdm_info_adc; + snprintf(name, sizeof("dfsdm-adc0"), "dfsdm-adc%d", adc->fl_id); + } + iio->name = name; + + /* + * In a first step IRQs generated for channels are not treated. + * So IRQ associated to filter instance 0 is dedicated to the Filter 0. + */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, stm32_dfsdm_irq, + 0, pdev->name, iio); + if (ret < 0) { + dev_err(dev, "Failed to request IRQ\n"); + return ret; + } + + ret = of_property_read_u32(dev->of_node, "st,filter-order", &val); + if (ret < 0) { + dev_err(dev, "Failed to set filter order\n"); + return ret; + } + + adc->dfsdm->fl_list[adc->fl_id].ford = val; + + adc->dfsdm->fl_list[adc->fl_id].sync_mode = + of_property_read_bool(dev->of_node, "st,filter0-sync"); + + adc->dev_data = dev_data; + ret = dev_data->init(dev, iio); + if (ret < 0) + return ret; + + ret = iio_device_register(iio); + if (ret < 0) + goto err_cleanup; + + if (dev_data->type == DFSDM_AUDIO) { + ret = of_platform_populate(np, NULL, NULL, dev); + if (ret < 0) { + dev_err(dev, "Failed to find an audio DAI\n"); + goto err_unregister; + } + } + + return 0; + +err_unregister: + iio_device_unregister(iio); +err_cleanup: + stm32_dfsdm_dma_release(iio); + + return ret; +} + +static void stm32_dfsdm_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + + if (adc->dev_data->type == DFSDM_AUDIO) + of_platform_depopulate(&pdev->dev); + iio_device_unregister(indio_dev); + stm32_dfsdm_dma_release(indio_dev); +} + +static int stm32_dfsdm_adc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + if (iio_buffer_enabled(indio_dev)) + stm32_dfsdm_predisable(indio_dev); + + return 0; +} + +static int stm32_dfsdm_adc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct stm32_dfsdm_adc *adc = iio_priv(indio_dev); + const struct iio_chan_spec *chan; + struct stm32_dfsdm_channel *ch; + int i, ret; + + /* restore channels configuration */ + for (i = 0; i < indio_dev->num_channels; i++) { + chan = indio_dev->channels + i; + ch = &adc->dfsdm->ch_list[chan->channel]; + ret = stm32_dfsdm_chan_configure(adc->dfsdm, ch); + if (ret) + return ret; + } + + if (iio_buffer_enabled(indio_dev)) + stm32_dfsdm_postenable(indio_dev); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(stm32_dfsdm_adc_pm_ops, + stm32_dfsdm_adc_suspend, + stm32_dfsdm_adc_resume); + +static struct platform_driver stm32_dfsdm_adc_driver = { + .driver = { + .name = "stm32-dfsdm-adc", + .of_match_table = stm32_dfsdm_adc_match, + .pm = pm_sleep_ptr(&stm32_dfsdm_adc_pm_ops), + }, + .probe = stm32_dfsdm_adc_probe, + .remove = stm32_dfsdm_adc_remove, +}; +module_platform_driver(stm32_dfsdm_adc_driver); + +MODULE_DESCRIPTION("STM32 sigma delta ADC"); +MODULE_AUTHOR("Arnaud Pouliquen "); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_BACKEND"); diff --git a/drivers/iio/adc/stm32-dfsdm-core.c b/drivers/iio/adc/stm32-dfsdm-core.c new file mode 100644 index 000000000..47e2d1338 --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm-core.c @@ -0,0 +1,522 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This file is part the core part STM32 DFSDM driver + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Author(s): Arnaud Pouliquen for STMicroelectronics. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "stm32-dfsdm.h" + +/** + * struct stm32_dfsdm_dev_data - DFSDM compatible configuration data + * @ipid: DFSDM identification number. Used only if hardware provides identification registers + * @num_filters: DFSDM number of filters. Unused if identification registers are available + * @num_channels: DFSDM number of channels. Unused if identification registers are available + * @regmap_cfg: SAI register map configuration pointer + */ +struct stm32_dfsdm_dev_data { + u32 ipid; + unsigned int num_filters; + unsigned int num_channels; + const struct regmap_config *regmap_cfg; +}; + +#define STM32H7_DFSDM_NUM_FILTERS 4 +#define STM32H7_DFSDM_NUM_CHANNELS 8 + +static bool stm32_dfsdm_volatile_reg(struct device *dev, unsigned int reg) +{ + if (reg < DFSDM_FILTER_BASE_ADR) + return false; + + /* + * Mask is done on register to avoid to list registers of all + * filter instances. + */ + switch (reg & DFSDM_FILTER_REG_MASK) { + case DFSDM_CR1(0) & DFSDM_FILTER_REG_MASK: + case DFSDM_ISR(0) & DFSDM_FILTER_REG_MASK: + case DFSDM_JDATAR(0) & DFSDM_FILTER_REG_MASK: + case DFSDM_RDATAR(0) & DFSDM_FILTER_REG_MASK: + return true; + } + + return false; +} + +static const struct regmap_config stm32h7_dfsdm_regmap_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = sizeof(u32), + .max_register = 0x2B8, + .volatile_reg = stm32_dfsdm_volatile_reg, + .fast_io = true, +}; + +static const struct stm32_dfsdm_dev_data stm32h7_dfsdm_data = { + .num_filters = STM32H7_DFSDM_NUM_FILTERS, + .num_channels = STM32H7_DFSDM_NUM_CHANNELS, + .regmap_cfg = &stm32h7_dfsdm_regmap_cfg, +}; + +static const struct regmap_config stm32mp1_dfsdm_regmap_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = sizeof(u32), + .max_register = 0x7fc, + .volatile_reg = stm32_dfsdm_volatile_reg, + .fast_io = true, +}; + +static const struct stm32_dfsdm_dev_data stm32mp1_dfsdm_data = { + .ipid = STM32MP15_IPIDR_NUMBER, + .regmap_cfg = &stm32mp1_dfsdm_regmap_cfg, +}; + +struct dfsdm_priv { + struct platform_device *pdev; /* platform device */ + + struct stm32_dfsdm dfsdm; /* common data exported for all instances */ + + unsigned int spi_clk_out_div; /* SPI clkout divider value */ + atomic_t n_active_ch; /* number of current active channels */ + + struct clk *clk; /* DFSDM clock */ + struct clk *aclk; /* audio clock */ +}; + +static inline struct dfsdm_priv *to_stm32_dfsdm_priv(struct stm32_dfsdm *dfsdm) +{ + return container_of(dfsdm, struct dfsdm_priv, dfsdm); +} + +static int stm32_dfsdm_clk_prepare_enable(struct stm32_dfsdm *dfsdm) +{ + struct dfsdm_priv *priv = to_stm32_dfsdm_priv(dfsdm); + int ret; + + ret = clk_prepare_enable(priv->clk); + if (ret || !priv->aclk) + return ret; + + ret = clk_prepare_enable(priv->aclk); + if (ret) + clk_disable_unprepare(priv->clk); + + return ret; +} + +static void stm32_dfsdm_clk_disable_unprepare(struct stm32_dfsdm *dfsdm) +{ + struct dfsdm_priv *priv = to_stm32_dfsdm_priv(dfsdm); + + clk_disable_unprepare(priv->aclk); + clk_disable_unprepare(priv->clk); +} + +/** + * stm32_dfsdm_start_dfsdm - start global dfsdm interface. + * + * Enable interface if n_active_ch is not null. + * @dfsdm: Handle used to retrieve dfsdm context. + */ +int stm32_dfsdm_start_dfsdm(struct stm32_dfsdm *dfsdm) +{ + struct dfsdm_priv *priv = to_stm32_dfsdm_priv(dfsdm); + struct device *dev = &priv->pdev->dev; + unsigned int clk_div = priv->spi_clk_out_div, clk_src; + int ret; + + if (atomic_inc_return(&priv->n_active_ch) == 1) { + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + goto error_ret; + + /* select clock source, e.g. 0 for "dfsdm" or 1 for "audio" */ + clk_src = priv->aclk ? 1 : 0; + ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0), + DFSDM_CHCFGR1_CKOUTSRC_MASK, + DFSDM_CHCFGR1_CKOUTSRC(clk_src)); + if (ret < 0) + goto pm_put; + + /* Output the SPI CLKOUT (if clk_div == 0 clock if OFF) */ + ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0), + DFSDM_CHCFGR1_CKOUTDIV_MASK, + DFSDM_CHCFGR1_CKOUTDIV(clk_div)); + if (ret < 0) + goto pm_put; + + /* Global enable of DFSDM interface */ + ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0), + DFSDM_CHCFGR1_DFSDMEN_MASK, + DFSDM_CHCFGR1_DFSDMEN(1)); + if (ret < 0) + goto pm_put; + } + + dev_dbg(dev, "%s: n_active_ch %d\n", __func__, + atomic_read(&priv->n_active_ch)); + + return 0; + +pm_put: + pm_runtime_put_sync(dev); +error_ret: + atomic_dec(&priv->n_active_ch); + + return ret; +} +EXPORT_SYMBOL_GPL(stm32_dfsdm_start_dfsdm); + +/** + * stm32_dfsdm_stop_dfsdm - stop global DFSDM interface. + * + * Disable interface if n_active_ch is null + * @dfsdm: Handle used to retrieve dfsdm context. + */ +int stm32_dfsdm_stop_dfsdm(struct stm32_dfsdm *dfsdm) +{ + struct dfsdm_priv *priv = to_stm32_dfsdm_priv(dfsdm); + int ret; + + if (atomic_dec_and_test(&priv->n_active_ch)) { + /* Global disable of DFSDM interface */ + ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0), + DFSDM_CHCFGR1_DFSDMEN_MASK, + DFSDM_CHCFGR1_DFSDMEN(0)); + if (ret < 0) + return ret; + + /* Stop SPI CLKOUT */ + ret = regmap_update_bits(dfsdm->regmap, DFSDM_CHCFGR1(0), + DFSDM_CHCFGR1_CKOUTDIV_MASK, + DFSDM_CHCFGR1_CKOUTDIV(0)); + if (ret < 0) + return ret; + + pm_runtime_put_sync(&priv->pdev->dev); + } + dev_dbg(&priv->pdev->dev, "%s: n_active_ch %d\n", __func__, + atomic_read(&priv->n_active_ch)); + + return 0; +} +EXPORT_SYMBOL_GPL(stm32_dfsdm_stop_dfsdm); + +static int stm32_dfsdm_parse_of(struct platform_device *pdev, + struct dfsdm_priv *priv) +{ + struct device_node *node = pdev->dev.of_node; + struct resource *res; + unsigned long clk_freq, divider; + unsigned int spi_freq, rem; + int ret; + + if (!node) + return -EINVAL; + + priv->dfsdm.base = devm_platform_get_and_ioremap_resource(pdev, 0, + &res); + if (IS_ERR(priv->dfsdm.base)) + return PTR_ERR(priv->dfsdm.base); + + priv->dfsdm.phys_base = res->start; + + /* + * "dfsdm" clock is mandatory for DFSDM peripheral clocking. + * "dfsdm" or "audio" clocks can be used as source clock for + * the SPI clock out signal and internal processing, depending + * on use case. + */ + priv->clk = devm_clk_get(&pdev->dev, "dfsdm"); + if (IS_ERR(priv->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(priv->clk), + "Failed to get clock\n"); + + priv->aclk = devm_clk_get(&pdev->dev, "audio"); + if (IS_ERR(priv->aclk)) + priv->aclk = NULL; + + if (priv->aclk) + clk_freq = clk_get_rate(priv->aclk); + else + clk_freq = clk_get_rate(priv->clk); + + /* SPI clock out frequency */ + ret = of_property_read_u32(pdev->dev.of_node, "spi-max-frequency", + &spi_freq); + if (ret < 0) { + /* No SPI master mode */ + return 0; + } + + divider = div_u64_rem(clk_freq, spi_freq, &rem); + /* Round up divider when ckout isn't precise, not to exceed spi_freq */ + if (rem) + divider++; + + /* programmable divider is in range of [2:256] */ + if (divider < 2 || divider > 256) { + dev_err(&pdev->dev, "spi-max-frequency not achievable\n"); + return -EINVAL; + } + + /* SPI clock output divider is: divider = CKOUTDIV + 1 */ + priv->spi_clk_out_div = divider - 1; + priv->dfsdm.spi_master_freq = clk_freq / (priv->spi_clk_out_div + 1); + + if (rem) { + dev_warn(&pdev->dev, "SPI clock not accurate\n"); + dev_warn(&pdev->dev, "%ld = %d * %d + %d\n", + clk_freq, spi_freq, priv->spi_clk_out_div + 1, rem); + } + + return 0; +}; + +static const struct of_device_id stm32_dfsdm_of_match[] = { + { + .compatible = "st,stm32h7-dfsdm", + .data = &stm32h7_dfsdm_data, + }, + { + .compatible = "st,stm32mp1-dfsdm", + .data = &stm32mp1_dfsdm_data, + }, + { } +}; +MODULE_DEVICE_TABLE(of, stm32_dfsdm_of_match); + +static int stm32_dfsdm_probe_identification(struct platform_device *pdev, + struct dfsdm_priv *priv, + const struct stm32_dfsdm_dev_data *dev_data) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *child; + struct stm32_dfsdm *dfsdm = &priv->dfsdm; + const char *compat; + int ret, count = 0; + u32 id, val; + + if (!dev_data->ipid) { + dfsdm->num_fls = dev_data->num_filters; + dfsdm->num_chs = dev_data->num_channels; + return 0; + } + + ret = regmap_read(dfsdm->regmap, DFSDM_IPIDR, &id); + if (ret) + return ret; + + if (id != dev_data->ipid) { + dev_err(&pdev->dev, "Unexpected IP version: 0x%x", id); + return -EINVAL; + } + + for_each_child_of_node(np, child) { + ret = of_property_read_string(child, "compatible", &compat); + if (ret) + continue; + /* Count only child nodes with dfsdm compatible */ + if (strstr(compat, "dfsdm")) + count++; + } + + ret = regmap_read(dfsdm->regmap, DFSDM_HWCFGR, &val); + if (ret) + return ret; + + dfsdm->num_fls = FIELD_GET(DFSDM_HWCFGR_NBF_MASK, val); + dfsdm->num_chs = FIELD_GET(DFSDM_HWCFGR_NBT_MASK, val); + + if (count > dfsdm->num_fls) { + dev_err(&pdev->dev, "Unexpected child number: %d", count); + return -EINVAL; + } + + ret = regmap_read(dfsdm->regmap, DFSDM_VERR, &val); + if (ret) + return ret; + + dev_dbg(&pdev->dev, "DFSDM version: %lu.%lu. %d channels/%d filters\n", + FIELD_GET(DFSDM_VERR_MAJREV_MASK, val), + FIELD_GET(DFSDM_VERR_MINREV_MASK, val), + dfsdm->num_chs, dfsdm->num_fls); + + return 0; +} + +static int stm32_dfsdm_probe(struct platform_device *pdev) +{ + struct dfsdm_priv *priv; + const struct stm32_dfsdm_dev_data *dev_data; + struct stm32_dfsdm *dfsdm; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->pdev = pdev; + + dev_data = of_device_get_match_data(&pdev->dev); + + dfsdm = &priv->dfsdm; + + ret = stm32_dfsdm_parse_of(pdev, priv); + if (ret < 0) + return ret; + + dfsdm->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "dfsdm", + dfsdm->base, + dev_data->regmap_cfg); + if (IS_ERR(dfsdm->regmap)) { + ret = PTR_ERR(dfsdm->regmap); + dev_err(&pdev->dev, "%s: Failed to allocate regmap: %d\n", + __func__, ret); + return ret; + } + + ret = stm32_dfsdm_probe_identification(pdev, priv, dev_data); + if (ret < 0) + return ret; + + dfsdm->fl_list = devm_kcalloc(&pdev->dev, dfsdm->num_fls, + sizeof(*dfsdm->fl_list), GFP_KERNEL); + if (!dfsdm->fl_list) + return -ENOMEM; + + dfsdm->ch_list = devm_kcalloc(&pdev->dev, dfsdm->num_chs, + sizeof(*dfsdm->ch_list), GFP_KERNEL); + if (!dfsdm->ch_list) + return -ENOMEM; + + platform_set_drvdata(pdev, dfsdm); + + ret = stm32_dfsdm_clk_prepare_enable(dfsdm); + if (ret) { + dev_err(&pdev->dev, "Failed to start clock\n"); + return ret; + } + + pm_runtime_get_noresume(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + ret = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); + if (ret) + goto pm_put; + + pm_runtime_put(&pdev->dev); + + return 0; + +pm_put: + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + stm32_dfsdm_clk_disable_unprepare(dfsdm); + + return ret; +} + +static void stm32_dfsdm_core_remove(struct platform_device *pdev) +{ + struct stm32_dfsdm *dfsdm = platform_get_drvdata(pdev); + + pm_runtime_get_sync(&pdev->dev); + of_platform_depopulate(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + stm32_dfsdm_clk_disable_unprepare(dfsdm); +} + +static int stm32_dfsdm_core_suspend(struct device *dev) +{ + struct stm32_dfsdm *dfsdm = dev_get_drvdata(dev); + struct dfsdm_priv *priv = to_stm32_dfsdm_priv(dfsdm); + int ret; + + ret = pm_runtime_force_suspend(dev); + if (ret) + return ret; + + /* Balance devm_regmap_init_mmio_clk() clk_prepare() */ + clk_unprepare(priv->clk); + + return pinctrl_pm_select_sleep_state(dev); +} + +static int stm32_dfsdm_core_resume(struct device *dev) +{ + struct stm32_dfsdm *dfsdm = dev_get_drvdata(dev); + struct dfsdm_priv *priv = to_stm32_dfsdm_priv(dfsdm); + int ret; + + ret = pinctrl_pm_select_default_state(dev); + if (ret) + return ret; + + ret = clk_prepare(priv->clk); + if (ret) + return ret; + + return pm_runtime_force_resume(dev); +} + +static int stm32_dfsdm_core_runtime_suspend(struct device *dev) +{ + struct stm32_dfsdm *dfsdm = dev_get_drvdata(dev); + + stm32_dfsdm_clk_disable_unprepare(dfsdm); + + return 0; +} + +static int stm32_dfsdm_core_runtime_resume(struct device *dev) +{ + struct stm32_dfsdm *dfsdm = dev_get_drvdata(dev); + + return stm32_dfsdm_clk_prepare_enable(dfsdm); +} + +static const struct dev_pm_ops stm32_dfsdm_core_pm_ops = { + SYSTEM_SLEEP_PM_OPS(stm32_dfsdm_core_suspend, stm32_dfsdm_core_resume) + RUNTIME_PM_OPS(stm32_dfsdm_core_runtime_suspend, + stm32_dfsdm_core_runtime_resume, + NULL) +}; + +static struct platform_driver stm32_dfsdm_driver = { + .probe = stm32_dfsdm_probe, + .remove = stm32_dfsdm_core_remove, + .driver = { + .name = "stm32-dfsdm", + .of_match_table = stm32_dfsdm_of_match, + .pm = pm_ptr(&stm32_dfsdm_core_pm_ops), + }, +}; + +module_platform_driver(stm32_dfsdm_driver); + +MODULE_AUTHOR("Arnaud Pouliquen "); +MODULE_DESCRIPTION("STMicroelectronics STM32 dfsdm driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/stm32-dfsdm.h b/drivers/iio/adc/stm32-dfsdm.h new file mode 100644 index 000000000..570a1552a --- /dev/null +++ b/drivers/iio/adc/stm32-dfsdm.h @@ -0,0 +1,348 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * This file is part of STM32 DFSDM driver + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Author(s): Arnaud Pouliquen . + */ + +#ifndef MDF_STM32_DFSDM__H +#define MDF_STM32_DFSDM__H + +#include + +/* + * STM32 DFSDM - global register map + * __________________________________________________________ + * | Offset | Registers block | + * ---------------------------------------------------------- + * | 0x000 | CHANNEL 0 + COMMON CHANNEL FIELDS | + * ---------------------------------------------------------- + * | 0x020 | CHANNEL 1 | + * ---------------------------------------------------------- + * | ... | ..... | + * ---------------------------------------------------------- + * | 0x20 x n | CHANNEL n | + * ---------------------------------------------------------- + * | 0x100 | FILTER 0 + COMMON FILTER FIELDs | + * ---------------------------------------------------------- + * | 0x200 | FILTER 1 | + * ---------------------------------------------------------- + * | | ..... | + * ---------------------------------------------------------- + * | 0x100 x m | FILTER m | + * ---------------------------------------------------------- + * | | ..... | + * ---------------------------------------------------------- + * | 0x7F0-7FC | Identification registers | + * ---------------------------------------------------------- + */ + +/* + * Channels register definitions + */ +#define DFSDM_CHCFGR1(y) ((y) * 0x20 + 0x00) +#define DFSDM_CHCFGR2(y) ((y) * 0x20 + 0x04) +#define DFSDM_AWSCDR(y) ((y) * 0x20 + 0x08) +#define DFSDM_CHWDATR(y) ((y) * 0x20 + 0x0C) +#define DFSDM_CHDATINR(y) ((y) * 0x20 + 0x10) + +/* CHCFGR1: Channel configuration register 1 */ +#define DFSDM_CHCFGR1_SITP_MASK GENMASK(1, 0) +#define DFSDM_CHCFGR1_SITP(v) FIELD_PREP(DFSDM_CHCFGR1_SITP_MASK, v) +#define DFSDM_CHCFGR1_SPICKSEL_MASK GENMASK(3, 2) +#define DFSDM_CHCFGR1_SPICKSEL(v) FIELD_PREP(DFSDM_CHCFGR1_SPICKSEL_MASK, v) +#define DFSDM_CHCFGR1_SCDEN_MASK BIT(5) +#define DFSDM_CHCFGR1_SCDEN(v) FIELD_PREP(DFSDM_CHCFGR1_SCDEN_MASK, v) +#define DFSDM_CHCFGR1_CKABEN_MASK BIT(6) +#define DFSDM_CHCFGR1_CKABEN(v) FIELD_PREP(DFSDM_CHCFGR1_CKABEN_MASK, v) +#define DFSDM_CHCFGR1_CHEN_MASK BIT(7) +#define DFSDM_CHCFGR1_CHEN(v) FIELD_PREP(DFSDM_CHCFGR1_CHEN_MASK, v) +#define DFSDM_CHCFGR1_CHINSEL_MASK BIT(8) +#define DFSDM_CHCFGR1_CHINSEL(v) FIELD_PREP(DFSDM_CHCFGR1_CHINSEL_MASK, v) +#define DFSDM_CHCFGR1_DATMPX_MASK GENMASK(13, 12) +#define DFSDM_CHCFGR1_DATMPX(v) FIELD_PREP(DFSDM_CHCFGR1_DATMPX_MASK, v) +#define DFSDM_CHCFGR1_DATPACK_MASK GENMASK(15, 14) +#define DFSDM_CHCFGR1_DATPACK(v) FIELD_PREP(DFSDM_CHCFGR1_DATPACK_MASK, v) +#define DFSDM_CHCFGR1_CKOUTDIV_MASK GENMASK(23, 16) +#define DFSDM_CHCFGR1_CKOUTDIV(v) FIELD_PREP(DFSDM_CHCFGR1_CKOUTDIV_MASK, v) +#define DFSDM_CHCFGR1_CKOUTSRC_MASK BIT(30) +#define DFSDM_CHCFGR1_CKOUTSRC(v) FIELD_PREP(DFSDM_CHCFGR1_CKOUTSRC_MASK, v) +#define DFSDM_CHCFGR1_DFSDMEN_MASK BIT(31) +#define DFSDM_CHCFGR1_DFSDMEN(v) FIELD_PREP(DFSDM_CHCFGR1_DFSDMEN_MASK, v) + +/* CHCFGR2: Channel configuration register 2 */ +#define DFSDM_CHCFGR2_DTRBS_MASK GENMASK(7, 3) +#define DFSDM_CHCFGR2_DTRBS(v) FIELD_PREP(DFSDM_CHCFGR2_DTRBS_MASK, v) +#define DFSDM_CHCFGR2_OFFSET_MASK GENMASK(31, 8) +#define DFSDM_CHCFGR2_OFFSET(v) FIELD_PREP(DFSDM_CHCFGR2_OFFSET_MASK, v) + +/* AWSCDR: Channel analog watchdog and short circuit detector */ +#define DFSDM_AWSCDR_SCDT_MASK GENMASK(7, 0) +#define DFSDM_AWSCDR_SCDT(v) FIELD_PREP(DFSDM_AWSCDR_SCDT_MASK, v) +#define DFSDM_AWSCDR_BKSCD_MASK GENMASK(15, 12) +#define DFSDM_AWSCDR_BKSCD(v) FIELD_PREP(DFSDM_AWSCDR_BKSCD_MASK, v) +#define DFSDM_AWSCDR_AWFOSR_MASK GENMASK(20, 16) +#define DFSDM_AWSCDR_AWFOSR(v) FIELD_PREP(DFSDM_AWSCDR_AWFOSR_MASK, v) +#define DFSDM_AWSCDR_AWFORD_MASK GENMASK(23, 22) +#define DFSDM_AWSCDR_AWFORD(v) FIELD_PREP(DFSDM_AWSCDR_AWFORD_MASK, v) + +/* + * Filters register definitions + */ +#define DFSDM_FILTER_BASE_ADR 0x100 +#define DFSDM_FILTER_REG_MASK 0x7F +#define DFSDM_FILTER_X_BASE_ADR(x) ((x) * 0x80 + DFSDM_FILTER_BASE_ADR) + +#define DFSDM_CR1(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x00) +#define DFSDM_CR2(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x04) +#define DFSDM_ISR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x08) +#define DFSDM_ICR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x0C) +#define DFSDM_JCHGR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x10) +#define DFSDM_FCR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x14) +#define DFSDM_JDATAR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x18) +#define DFSDM_RDATAR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x1C) +#define DFSDM_AWHTR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x20) +#define DFSDM_AWLTR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x24) +#define DFSDM_AWSR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x28) +#define DFSDM_AWCFR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x2C) +#define DFSDM_EXMAX(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x30) +#define DFSDM_EXMIN(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x34) +#define DFSDM_CNVTIMR(x) (DFSDM_FILTER_X_BASE_ADR(x) + 0x38) + +/* CR1 Control register 1 */ +#define DFSDM_CR1_DFEN_MASK BIT(0) +#define DFSDM_CR1_DFEN(v) FIELD_PREP(DFSDM_CR1_DFEN_MASK, v) +#define DFSDM_CR1_JSWSTART_MASK BIT(1) +#define DFSDM_CR1_JSWSTART(v) FIELD_PREP(DFSDM_CR1_JSWSTART_MASK, v) +#define DFSDM_CR1_JSYNC_MASK BIT(3) +#define DFSDM_CR1_JSYNC(v) FIELD_PREP(DFSDM_CR1_JSYNC_MASK, v) +#define DFSDM_CR1_JSCAN_MASK BIT(4) +#define DFSDM_CR1_JSCAN(v) FIELD_PREP(DFSDM_CR1_JSCAN_MASK, v) +#define DFSDM_CR1_JDMAEN_MASK BIT(5) +#define DFSDM_CR1_JDMAEN(v) FIELD_PREP(DFSDM_CR1_JDMAEN_MASK, v) +#define DFSDM_CR1_JEXTSEL_MASK GENMASK(12, 8) +#define DFSDM_CR1_JEXTSEL(v) FIELD_PREP(DFSDM_CR1_JEXTSEL_MASK, v) +#define DFSDM_CR1_JEXTEN_MASK GENMASK(14, 13) +#define DFSDM_CR1_JEXTEN(v) FIELD_PREP(DFSDM_CR1_JEXTEN_MASK, v) +#define DFSDM_CR1_RSWSTART_MASK BIT(17) +#define DFSDM_CR1_RSWSTART(v) FIELD_PREP(DFSDM_CR1_RSWSTART_MASK, v) +#define DFSDM_CR1_RCONT_MASK BIT(18) +#define DFSDM_CR1_RCONT(v) FIELD_PREP(DFSDM_CR1_RCONT_MASK, v) +#define DFSDM_CR1_RSYNC_MASK BIT(19) +#define DFSDM_CR1_RSYNC(v) FIELD_PREP(DFSDM_CR1_RSYNC_MASK, v) +#define DFSDM_CR1_RDMAEN_MASK BIT(21) +#define DFSDM_CR1_RDMAEN(v) FIELD_PREP(DFSDM_CR1_RDMAEN_MASK, v) +#define DFSDM_CR1_RCH_MASK GENMASK(26, 24) +#define DFSDM_CR1_RCH(v) FIELD_PREP(DFSDM_CR1_RCH_MASK, v) +#define DFSDM_CR1_FAST_MASK BIT(29) +#define DFSDM_CR1_FAST(v) FIELD_PREP(DFSDM_CR1_FAST_MASK, v) +#define DFSDM_CR1_AWFSEL_MASK BIT(30) +#define DFSDM_CR1_AWFSEL(v) FIELD_PREP(DFSDM_CR1_AWFSEL_MASK, v) + +/* CR2: Control register 2 */ +#define DFSDM_CR2_IE_MASK GENMASK(6, 0) +#define DFSDM_CR2_IE(v) FIELD_PREP(DFSDM_CR2_IE_MASK, v) +#define DFSDM_CR2_JEOCIE_MASK BIT(0) +#define DFSDM_CR2_JEOCIE(v) FIELD_PREP(DFSDM_CR2_JEOCIE_MASK, v) +#define DFSDM_CR2_REOCIE_MASK BIT(1) +#define DFSDM_CR2_REOCIE(v) FIELD_PREP(DFSDM_CR2_REOCIE_MASK, v) +#define DFSDM_CR2_JOVRIE_MASK BIT(2) +#define DFSDM_CR2_JOVRIE(v) FIELD_PREP(DFSDM_CR2_JOVRIE_MASK, v) +#define DFSDM_CR2_ROVRIE_MASK BIT(3) +#define DFSDM_CR2_ROVRIE(v) FIELD_PREP(DFSDM_CR2_ROVRIE_MASK, v) +#define DFSDM_CR2_AWDIE_MASK BIT(4) +#define DFSDM_CR2_AWDIE(v) FIELD_PREP(DFSDM_CR2_AWDIE_MASK, v) +#define DFSDM_CR2_SCDIE_MASK BIT(5) +#define DFSDM_CR2_SCDIE(v) FIELD_PREP(DFSDM_CR2_SCDIE_MASK, v) +#define DFSDM_CR2_CKABIE_MASK BIT(6) +#define DFSDM_CR2_CKABIE(v) FIELD_PREP(DFSDM_CR2_CKABIE_MASK, v) +#define DFSDM_CR2_EXCH_MASK GENMASK(15, 8) +#define DFSDM_CR2_EXCH(v) FIELD_PREP(DFSDM_CR2_EXCH_MASK, v) +#define DFSDM_CR2_AWDCH_MASK GENMASK(23, 16) +#define DFSDM_CR2_AWDCH(v) FIELD_PREP(DFSDM_CR2_AWDCH_MASK, v) + +/* ISR: Interrupt status register */ +#define DFSDM_ISR_JEOCF_MASK BIT(0) +#define DFSDM_ISR_JEOCF(v) FIELD_PREP(DFSDM_ISR_JEOCF_MASK, v) +#define DFSDM_ISR_REOCF_MASK BIT(1) +#define DFSDM_ISR_REOCF(v) FIELD_PREP(DFSDM_ISR_REOCF_MASK, v) +#define DFSDM_ISR_JOVRF_MASK BIT(2) +#define DFSDM_ISR_JOVRF(v) FIELD_PREP(DFSDM_ISR_JOVRF_MASK, v) +#define DFSDM_ISR_ROVRF_MASK BIT(3) +#define DFSDM_ISR_ROVRF(v) FIELD_PREP(DFSDM_ISR_ROVRF_MASK, v) +#define DFSDM_ISR_AWDF_MASK BIT(4) +#define DFSDM_ISR_AWDF(v) FIELD_PREP(DFSDM_ISR_AWDF_MASK, v) +#define DFSDM_ISR_JCIP_MASK BIT(13) +#define DFSDM_ISR_JCIP(v) FIELD_PREP(DFSDM_ISR_JCIP_MASK, v) +#define DFSDM_ISR_RCIP_MASK BIT(14) +#define DFSDM_ISR_RCIP(v) FIELD_PREP(DFSDM_ISR_RCIP, v) +#define DFSDM_ISR_CKABF_MASK GENMASK(23, 16) +#define DFSDM_ISR_CKABF(v) FIELD_PREP(DFSDM_ISR_CKABF_MASK, v) +#define DFSDM_ISR_SCDF_MASK GENMASK(31, 24) +#define DFSDM_ISR_SCDF(v) FIELD_PREP(DFSDM_ISR_SCDF_MASK, v) + +/* ICR: Interrupt flag clear register */ +#define DFSDM_ICR_CLRJOVRF_MASK BIT(2) +#define DFSDM_ICR_CLRJOVRF(v) FIELD_PREP(DFSDM_ICR_CLRJOVRF_MASK, v) +#define DFSDM_ICR_CLRROVRF_MASK BIT(3) +#define DFSDM_ICR_CLRROVRF(v) FIELD_PREP(DFSDM_ICR_CLRROVRF_MASK, v) +#define DFSDM_ICR_CLRCKABF_MASK GENMASK(23, 16) +#define DFSDM_ICR_CLRCKABF(v) FIELD_PREP(DFSDM_ICR_CLRCKABF_MASK, v) +#define DFSDM_ICR_CLRCKABF_CH_MASK(y) BIT(16 + (y)) +#define DFSDM_ICR_CLRCKABF_CH(v, y) \ + (((v) << (16 + (y))) & DFSDM_ICR_CLRCKABF_CH_MASK(y)) +#define DFSDM_ICR_CLRSCDF_MASK GENMASK(31, 24) +#define DFSDM_ICR_CLRSCDF(v) FIELD_PREP(DFSDM_ICR_CLRSCDF_MASK, v) +#define DFSDM_ICR_CLRSCDF_CH_MASK(y) BIT(24 + (y)) +#define DFSDM_ICR_CLRSCDF_CH(v, y) \ + (((v) << (24 + (y))) & DFSDM_ICR_CLRSCDF_MASK(y)) + +/* FCR: Filter control register */ +#define DFSDM_FCR_IOSR_MASK GENMASK(7, 0) +#define DFSDM_FCR_IOSR(v) FIELD_PREP(DFSDM_FCR_IOSR_MASK, v) +#define DFSDM_FCR_FOSR_MASK GENMASK(25, 16) +#define DFSDM_FCR_FOSR(v) FIELD_PREP(DFSDM_FCR_FOSR_MASK, v) +#define DFSDM_FCR_FORD_MASK GENMASK(31, 29) +#define DFSDM_FCR_FORD(v) FIELD_PREP(DFSDM_FCR_FORD_MASK, v) + +/* RDATAR: Filter data register for regular channel */ +#define DFSDM_DATAR_CH_MASK GENMASK(2, 0) +#define DFSDM_DATAR_DATA_OFFSET 8 +#define DFSDM_DATAR_DATA_MASK GENMASK(31, DFSDM_DATAR_DATA_OFFSET) + +/* AWLTR: Filter analog watchdog low threshold register */ +#define DFSDM_AWLTR_BKAWL_MASK GENMASK(3, 0) +#define DFSDM_AWLTR_BKAWL(v) FIELD_PREP(DFSDM_AWLTR_BKAWL_MASK, v) +#define DFSDM_AWLTR_AWLT_MASK GENMASK(31, 8) +#define DFSDM_AWLTR_AWLT(v) FIELD_PREP(DFSDM_AWLTR_AWLT_MASK, v) + +/* AWHTR: Filter analog watchdog low threshold register */ +#define DFSDM_AWHTR_BKAWH_MASK GENMASK(3, 0) +#define DFSDM_AWHTR_BKAWH(v) FIELD_PREP(DFSDM_AWHTR_BKAWH_MASK, v) +#define DFSDM_AWHTR_AWHT_MASK GENMASK(31, 8) +#define DFSDM_AWHTR_AWHT(v) FIELD_PREP(DFSDM_AWHTR_AWHT_MASK, v) + +/* AWSR: Filter watchdog status register */ +#define DFSDM_AWSR_AWLTF_MASK GENMASK(7, 0) +#define DFSDM_AWSR_AWLTF(v) FIELD_PREP(DFSDM_AWSR_AWLTF_MASK, v) +#define DFSDM_AWSR_AWHTF_MASK GENMASK(15, 8) +#define DFSDM_AWSR_AWHTF(v) FIELD_PREP(DFSDM_AWSR_AWHTF_MASK, v) + +/* AWCFR: Filter watchdog status register */ +#define DFSDM_AWCFR_AWLTF_MASK GENMASK(7, 0) +#define DFSDM_AWCFR_AWLTF(v) FIELD_PREP(DFSDM_AWCFR_AWLTF_MASK, v) +#define DFSDM_AWCFR_AWHTF_MASK GENMASK(15, 8) +#define DFSDM_AWCFR_AWHTF(v) FIELD_PREP(DFSDM_AWCFR_AWHTF_MASK, v) + +/* + * Identification register definitions + */ +#define DFSDM_HWCFGR 0x7F0 +#define DFSDM_VERR 0x7F4 +#define DFSDM_IPIDR 0x7F8 +#define DFSDM_SIDR 0x7FC + +/* HWCFGR: Hardware configuration register */ +#define DFSDM_HWCFGR_NBT_MASK GENMASK(7, 0) +#define DFSDM_HWCFGR_NBF_MASK GENMASK(15, 8) + +/* VERR: Version register */ +#define DFSDM_VERR_MINREV_MASK GENMASK(3, 0) +#define DFSDM_VERR_MAJREV_MASK GENMASK(7, 4) + +#define STM32MP15_IPIDR_NUMBER 0x00110031 + +/* DFSDM filter order */ +enum stm32_dfsdm_sinc_order { + DFSDM_FASTSINC_ORDER, /* FastSinc filter type */ + DFSDM_SINC1_ORDER, /* Sinc 1 filter type */ + DFSDM_SINC2_ORDER, /* Sinc 2 filter type */ + DFSDM_SINC3_ORDER, /* Sinc 3 filter type */ + DFSDM_SINC4_ORDER, /* Sinc 4 filter type (N.A. for watchdog) */ + DFSDM_SINC5_ORDER, /* Sinc 5 filter type (N.A. for watchdog) */ + DFSDM_NB_SINC_ORDER, +}; + +/** + * struct stm32_dfsdm_filter_osr - DFSDM filter settings linked to oversampling + * @iosr: integrator oversampling + * @fosr: filter oversampling + * @rshift: output sample right shift (hardware shift) + * @lshift: output sample left shift (software shift) + * @res: output sample resolution + * @bits: output sample resolution in bits + * @max: output sample maximum positive value + */ +struct stm32_dfsdm_filter_osr { + unsigned int iosr; + unsigned int fosr; + unsigned int rshift; + unsigned int lshift; + u64 res; + u32 bits; + s32 max; +}; + +/** + * struct stm32_dfsdm_filter - structure relative to stm32 FDSDM filter + * @ford: filter order + * @flo: filter oversampling data table indexed by fast mode flag + * @sync_mode: filter synchronized with filter 0 + * @fast: filter fast mode + */ +struct stm32_dfsdm_filter { + enum stm32_dfsdm_sinc_order ford; + struct stm32_dfsdm_filter_osr flo[2]; + unsigned int sync_mode; + unsigned int fast; +}; + +/** + * struct stm32_dfsdm_channel - structure relative to stm32 FDSDM channel + * @id: id of the channel + * @type: interface type linked to stm32_dfsdm_chan_type + * @src: interface type linked to stm32_dfsdm_chan_src + * @alt_si: alternative serial input interface + */ +struct stm32_dfsdm_channel { + unsigned int id; + unsigned int type; + unsigned int src; + unsigned int alt_si; +}; + +/** + * struct stm32_dfsdm - stm32 FDSDM driver common data (for all instances) + * @base: control registers base cpu addr + * @phys_base: DFSDM IP register physical address + * @regmap: regmap for register read/write + * @fl_list: filter resources list + * @num_fls: number of filter resources available + * @ch_list: channel resources list + * @num_chs: number of channel resources available + * @spi_master_freq: SPI clock out frequency + */ +struct stm32_dfsdm { + void __iomem *base; + phys_addr_t phys_base; + struct regmap *regmap; + struct stm32_dfsdm_filter *fl_list; + unsigned int num_fls; + struct stm32_dfsdm_channel *ch_list; + unsigned int num_chs; + unsigned int spi_master_freq; +}; + +/* DFSDM channel serial spi clock source */ +enum stm32_dfsdm_spi_clk_src { + DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL, + DFSDM_CHANNEL_SPI_CLOCK_INTERNAL, + DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_FALLING, + DFSDM_CHANNEL_SPI_CLOCK_INTERNAL_DIV2_RISING +}; + +int stm32_dfsdm_start_dfsdm(struct stm32_dfsdm *dfsdm); +int stm32_dfsdm_stop_dfsdm(struct stm32_dfsdm *dfsdm); + +#endif diff --git a/drivers/iio/adc/stmpe-adc.c b/drivers/iio/adc/stmpe-adc.c new file mode 100644 index 000000000..8e26c47ed --- /dev/null +++ b/drivers/iio/adc/stmpe-adc.c @@ -0,0 +1,365 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * STMicroelectronics STMPE811 IIO ADC Driver + * + * 4 channel, 10/12-bit ADC + * + * Copyright (C) 2013-2018 Toradex AG + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STMPE_REG_INT_STA 0x0B +#define STMPE_REG_ADC_INT_EN 0x0E +#define STMPE_REG_ADC_INT_STA 0x0F + +#define STMPE_REG_ADC_CTRL1 0x20 +#define STMPE_REG_ADC_CTRL2 0x21 +#define STMPE_REG_ADC_CAPT 0x22 +#define STMPE_REG_ADC_DATA_CH(channel) (0x30 + 2 * (channel)) + +#define STMPE_REG_TEMP_CTRL 0x60 +#define STMPE_TEMP_CTRL_ENABLE BIT(0) +#define STMPE_TEMP_CTRL_ACQ BIT(1) +#define STMPE_TEMP_CTRL_THRES_EN BIT(3) +#define STMPE_START_ONE_TEMP_CONV (STMPE_TEMP_CTRL_ENABLE | \ + STMPE_TEMP_CTRL_ACQ | \ + STMPE_TEMP_CTRL_THRES_EN) +#define STMPE_REG_TEMP_DATA 0x61 +#define STMPE_REG_TEMP_TH 0x63 +#define STMPE_ADC_LAST_NR 7 +#define STMPE_TEMP_CHANNEL (STMPE_ADC_LAST_NR + 1) + +#define STMPE_ADC_CH(channel) ((1 << (channel)) & 0xff) + +#define STMPE_ADC_TIMEOUT msecs_to_jiffies(1000) + +struct stmpe_adc { + struct stmpe *stmpe; + struct clk *clk; + struct device *dev; + struct mutex lock; + + /* We are allocating plus one for the temperature channel */ + struct iio_chan_spec stmpe_adc_iio_channels[STMPE_ADC_LAST_NR + 2]; + + struct completion completion; + + u8 channel; + u32 value; +}; + +static int stmpe_read_voltage(struct stmpe_adc *info, + struct iio_chan_spec const *chan, int *val) +{ + unsigned long ret; + + mutex_lock(&info->lock); + + reinit_completion(&info->completion); + + info->channel = (u8)chan->channel; + + if (info->channel > STMPE_ADC_LAST_NR) { + mutex_unlock(&info->lock); + return -EINVAL; + } + + stmpe_reg_write(info->stmpe, STMPE_REG_ADC_CAPT, + STMPE_ADC_CH(info->channel)); + + ret = wait_for_completion_timeout(&info->completion, STMPE_ADC_TIMEOUT); + + if (ret == 0) { + stmpe_reg_write(info->stmpe, STMPE_REG_ADC_INT_STA, + STMPE_ADC_CH(info->channel)); + mutex_unlock(&info->lock); + return -ETIMEDOUT; + } + + *val = info->value; + + mutex_unlock(&info->lock); + + return 0; +} + +static int stmpe_read_temp(struct stmpe_adc *info, + struct iio_chan_spec const *chan, int *val) +{ + unsigned long ret; + + mutex_lock(&info->lock); + + reinit_completion(&info->completion); + + info->channel = (u8)chan->channel; + + if (info->channel != STMPE_TEMP_CHANNEL) { + mutex_unlock(&info->lock); + return -EINVAL; + } + + stmpe_reg_write(info->stmpe, STMPE_REG_TEMP_CTRL, + STMPE_START_ONE_TEMP_CONV); + + ret = wait_for_completion_timeout(&info->completion, STMPE_ADC_TIMEOUT); + + if (ret == 0) { + mutex_unlock(&info->lock); + return -ETIMEDOUT; + } + + /* + * absolute temp = +V3.3 * value /7.51 [K] + * scale to [milli °C] + */ + *val = ((449960l * info->value) / 1024l) - 273150; + + mutex_unlock(&info->lock); + + return 0; +} + +static int stmpe_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct stmpe_adc *info = iio_priv(indio_dev); + long ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + + switch (chan->type) { + case IIO_VOLTAGE: + ret = stmpe_read_voltage(info, chan, val); + break; + + case IIO_TEMP: + ret = stmpe_read_temp(info, chan, val); + break; + default: + return -EINVAL; + } + + if (ret < 0) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = 3300; + *val2 = info->stmpe->mod_12b ? 12 : 10; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + break; + } + + return -EINVAL; +} + +static irqreturn_t stmpe_adc_isr(int irq, void *dev_id) +{ + struct stmpe_adc *info = (struct stmpe_adc *)dev_id; + __be16 data; + + if (info->channel <= STMPE_ADC_LAST_NR) { + int int_sta; + + int_sta = stmpe_reg_read(info->stmpe, STMPE_REG_ADC_INT_STA); + + /* Is the interrupt relevant */ + if (!(int_sta & STMPE_ADC_CH(info->channel))) + return IRQ_NONE; + + /* Read value */ + stmpe_block_read(info->stmpe, + STMPE_REG_ADC_DATA_CH(info->channel), 2, (u8 *) &data); + + stmpe_reg_write(info->stmpe, STMPE_REG_ADC_INT_STA, int_sta); + } else if (info->channel == STMPE_TEMP_CHANNEL) { + /* Read value */ + stmpe_block_read(info->stmpe, STMPE_REG_TEMP_DATA, 2, + (u8 *) &data); + } else { + return IRQ_NONE; + } + + info->value = (u32) be16_to_cpu(data); + complete(&info->completion); + + return IRQ_HANDLED; +} + +static const struct iio_info stmpe_adc_iio_info = { + .read_raw = &stmpe_read_raw, +}; + +static void stmpe_adc_voltage_chan(struct iio_chan_spec *ics, int chan) +{ + ics->type = IIO_VOLTAGE; + ics->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); + ics->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE); + ics->indexed = 1; + ics->channel = chan; +} + +static void stmpe_adc_temp_chan(struct iio_chan_spec *ics, int chan) +{ + ics->type = IIO_TEMP; + ics->info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED); + ics->indexed = 1; + ics->channel = chan; +} + +static int stmpe_adc_init_hw(struct stmpe_adc *adc) +{ + int ret; + struct stmpe *stmpe = adc->stmpe; + + ret = stmpe_enable(stmpe, STMPE_BLOCK_ADC); + if (ret) { + dev_err(stmpe->dev, "Could not enable clock for ADC\n"); + return ret; + } + + ret = stmpe811_adc_common_init(stmpe); + if (ret) { + stmpe_disable(stmpe, STMPE_BLOCK_ADC); + return ret; + } + + /* use temp irq for each conversion completion */ + stmpe_reg_write(stmpe, STMPE_REG_TEMP_TH, 0); + stmpe_reg_write(stmpe, STMPE_REG_TEMP_TH + 1, 0); + + return 0; +} + +static int stmpe_adc_probe(struct platform_device *pdev) +{ + struct iio_dev *indio_dev; + struct stmpe_adc *info; + struct device_node *np; + u32 norequest_mask = 0; + unsigned long bits; + int irq_temp, irq_adc; + int num_chan = 0; + int i = 0; + int ret; + + irq_adc = platform_get_irq_byname(pdev, "STMPE_ADC"); + if (irq_adc < 0) + return irq_adc; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct stmpe_adc)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + mutex_init(&info->lock); + + init_completion(&info->completion); + ret = devm_request_threaded_irq(&pdev->dev, irq_adc, NULL, + stmpe_adc_isr, IRQF_ONESHOT, + "stmpe-adc", info); + if (ret < 0) { + dev_err(&pdev->dev, "failed requesting irq, irq = %d\n", + irq_adc); + return ret; + } + + irq_temp = platform_get_irq_byname(pdev, "STMPE_TEMP_SENS"); + if (irq_temp >= 0) { + ret = devm_request_threaded_irq(&pdev->dev, irq_temp, NULL, + stmpe_adc_isr, IRQF_ONESHOT, + "stmpe-adc", info); + if (ret < 0) + dev_warn(&pdev->dev, "failed requesting irq for" + " temp sensor, irq = %d\n", irq_temp); + } + + platform_set_drvdata(pdev, indio_dev); + + indio_dev->name = dev_name(&pdev->dev); + indio_dev->info = &stmpe_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + info->stmpe = dev_get_drvdata(pdev->dev.parent); + + np = pdev->dev.of_node; + + if (!np) + dev_err(&pdev->dev, "no device tree node found\n"); + + of_property_read_u32(np, "st,norequest-mask", &norequest_mask); + + bits = norequest_mask; + for_each_clear_bit(i, &bits, (STMPE_ADC_LAST_NR + 1)) { + stmpe_adc_voltage_chan(&info->stmpe_adc_iio_channels[num_chan], i); + num_chan++; + } + stmpe_adc_temp_chan(&info->stmpe_adc_iio_channels[num_chan], i); + num_chan++; + indio_dev->channels = info->stmpe_adc_iio_channels; + indio_dev->num_channels = num_chan; + + ret = stmpe_adc_init_hw(info); + if (ret) + return ret; + + stmpe_reg_write(info->stmpe, STMPE_REG_ADC_INT_EN, + ~(norequest_mask & 0xFF)); + + stmpe_reg_write(info->stmpe, STMPE_REG_ADC_INT_STA, + ~(norequest_mask & 0xFF)); + + return devm_iio_device_register(&pdev->dev, indio_dev); +} + +static int stmpe_adc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct stmpe_adc *info = iio_priv(indio_dev); + + stmpe_adc_init_hw(info); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(stmpe_adc_pm_ops, NULL, stmpe_adc_resume); + +static const struct of_device_id stmpe_adc_ids[] = { + { .compatible = "st,stmpe-adc", }, + { } +}; +MODULE_DEVICE_TABLE(of, stmpe_adc_ids); + +static struct platform_driver stmpe_adc_driver = { + .probe = stmpe_adc_probe, + .driver = { + .name = "stmpe-adc", + .pm = pm_sleep_ptr(&stmpe_adc_pm_ops), + .of_match_table = stmpe_adc_ids, + }, +}; +module_platform_driver(stmpe_adc_driver); + +MODULE_AUTHOR("Stefan Agner "); +MODULE_DESCRIPTION("STMPEXXX ADC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:stmpe-adc"); diff --git a/drivers/iio/adc/sun20i-gpadc-iio.c b/drivers/iio/adc/sun20i-gpadc-iio.c new file mode 100644 index 000000000..baa7661db --- /dev/null +++ b/drivers/iio/adc/sun20i-gpadc-iio.c @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * GPADC driver for sunxi platforms (D1, T113-S3 and R329) + * Copyright (c) 2023 Maksim Kiselev + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define SUN20I_GPADC_DRIVER_NAME "sun20i-gpadc" + +/* Register map definition */ +#define SUN20I_GPADC_SR 0x00 +#define SUN20I_GPADC_CTRL 0x04 +#define SUN20I_GPADC_CS_EN 0x08 +#define SUN20I_GPADC_FIFO_INTC 0x0c +#define SUN20I_GPADC_FIFO_INTS 0x10 +#define SUN20I_GPADC_FIFO_DATA 0X14 +#define SUN20I_GPADC_CB_DATA 0X18 +#define SUN20I_GPADC_DATAL_INTC 0x20 +#define SUN20I_GPADC_DATAH_INTC 0x24 +#define SUN20I_GPADC_DATA_INTC 0x28 +#define SUN20I_GPADC_DATAL_INTS 0x30 +#define SUN20I_GPADC_DATAH_INTS 0x34 +#define SUN20I_GPADC_DATA_INTS 0x38 +#define SUN20I_GPADC_CH_CMP_DATA(x) (0x40 + (x) * 4) +#define SUN20I_GPADC_CH_DATA(x) (0x80 + (x) * 4) + +#define SUN20I_GPADC_CTRL_ADC_AUTOCALI_EN_MASK BIT(23) +#define SUN20I_GPADC_CTRL_WORK_MODE_MASK GENMASK(19, 18) +#define SUN20I_GPADC_CTRL_ADC_EN_MASK BIT(16) +#define SUN20I_GPADC_CS_EN_ADC_CH(x) BIT(x) +#define SUN20I_GPADC_DATA_INTC_CH_DATA_IRQ_EN(x) BIT(x) + +#define SUN20I_GPADC_WORK_MODE_SINGLE 0 + +struct sun20i_gpadc_iio { + void __iomem *regs; + struct completion completion; + int last_channel; + /* + * Lock to protect the device state during a potential concurrent + * read access from userspace. Reading a raw value requires a sequence + * of register writes, then a wait for a completion callback, + * and finally a register read, during which userspace could issue + * another read request. This lock protects a read access from + * occurring before another one has finished. + */ + struct mutex lock; +}; + +static int sun20i_gpadc_adc_read(struct sun20i_gpadc_iio *info, + struct iio_chan_spec const *chan, int *val) +{ + u32 ctrl; + int ret = IIO_VAL_INT; + + mutex_lock(&info->lock); + + reinit_completion(&info->completion); + + if (info->last_channel != chan->channel) { + info->last_channel = chan->channel; + + /* enable the analog input channel */ + writel(SUN20I_GPADC_CS_EN_ADC_CH(chan->channel), + info->regs + SUN20I_GPADC_CS_EN); + + /* enable the data irq for input channel */ + writel(SUN20I_GPADC_DATA_INTC_CH_DATA_IRQ_EN(chan->channel), + info->regs + SUN20I_GPADC_DATA_INTC); + } + + /* enable the ADC function */ + ctrl = readl(info->regs + SUN20I_GPADC_CTRL); + ctrl |= FIELD_PREP(SUN20I_GPADC_CTRL_ADC_EN_MASK, 1); + writel(ctrl, info->regs + SUN20I_GPADC_CTRL); + + /* + * According to the datasheet maximum acquire time(TACQ) can be + * (65535+1)/24Mhz and conversion time(CONV_TIME) is always constant + * and equal to 14/24Mhz, so (TACQ+CONV_TIME) <= 2.73125ms. + * A 10ms delay should be enough to make sure an interrupt occurs in + * normal conditions. If it doesn't occur, then there is a timeout. + */ + if (!wait_for_completion_timeout(&info->completion, msecs_to_jiffies(10))) { + ret = -ETIMEDOUT; + goto err_unlock; + } + + /* read the ADC data */ + *val = readl(info->regs + SUN20I_GPADC_CH_DATA(chan->channel)); + +err_unlock: + mutex_unlock(&info->lock); + + return ret; +} + +static int sun20i_gpadc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct sun20i_gpadc_iio *info = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return sun20i_gpadc_adc_read(info, chan, val); + case IIO_CHAN_INFO_SCALE: + /* value in mv = 1800mV / 4096 raw */ + *val = 1800; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static irqreturn_t sun20i_gpadc_irq_handler(int irq, void *data) +{ + struct sun20i_gpadc_iio *info = data; + + /* clear data interrupt status register */ + writel(GENMASK(31, 0), info->regs + SUN20I_GPADC_DATA_INTS); + + complete(&info->completion); + + return IRQ_HANDLED; +} + +static int +sun20i_gpadc_fwnode_xlate(struct iio_dev *indio_dev, + const struct fwnode_reference_args *iiospec) +{ + if (iiospec->nargs != 1) + return -EINVAL; + + for (unsigned int i = 0; i < indio_dev->num_channels; i++) + if (indio_dev->channels[i].channel == iiospec->args[0]) + return i; + + return -EINVAL; +} + +static const struct iio_info sun20i_gpadc_iio_info = { + .read_raw = sun20i_gpadc_read_raw, + .fwnode_xlate = sun20i_gpadc_fwnode_xlate, +}; + +static void sun20i_gpadc_reset_assert(void *data) +{ + struct reset_control *rst = data; + + reset_control_assert(rst); +} + +static const struct iio_chan_spec sun20i_gpadc_chan_template = { + .type = IIO_VOLTAGE, + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), +}; + +static int sun20i_gpadc_alloc_channels(struct iio_dev *indio_dev, + struct device *dev) +{ + int num_channels; + struct iio_chan_spec *channels; + + num_channels = devm_iio_adc_device_alloc_chaninfo_se(dev, + &sun20i_gpadc_chan_template, -1, &channels); + if (num_channels < 0) + return num_channels; + + indio_dev->channels = channels; + indio_dev->num_channels = num_channels; + + return 0; +} + +static int sun20i_gpadc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sun20i_gpadc_iio *info; + struct clk_bulk_data *clks; + struct reset_control *rst; + struct iio_dev *indio_dev; + int irq; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*info)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + info->last_channel = -1; + + mutex_init(&info->lock); + init_completion(&info->completion); + + ret = sun20i_gpadc_alloc_channels(indio_dev, dev); + if (ret) + return ret; + + indio_dev->info = &sun20i_gpadc_iio_info; + indio_dev->name = SUN20I_GPADC_DRIVER_NAME; + + info->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(info->regs)) + return PTR_ERR(info->regs); + + ret = devm_clk_bulk_get_all_enabled(dev, &clks); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to enable clocks\n"); + if (ret == 0) + return dev_err_probe(dev, -ENODEV, "needs at least one clocks\n"); + + rst = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(rst)) + return dev_err_probe(dev, PTR_ERR(rst), "failed to get reset control\n"); + + ret = reset_control_deassert(rst); + if (ret) + return dev_err_probe(dev, ret, "failed to deassert reset\n"); + + ret = devm_add_action_or_reset(dev, sun20i_gpadc_reset_assert, rst); + if (ret) + return ret; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, sun20i_gpadc_irq_handler, 0, + dev_name(dev), info); + if (ret) + return dev_err_probe(dev, ret, "failed requesting irq %d\n", irq); + + writel(FIELD_PREP(SUN20I_GPADC_CTRL_ADC_AUTOCALI_EN_MASK, 1) | + FIELD_PREP(SUN20I_GPADC_CTRL_WORK_MODE_MASK, SUN20I_GPADC_WORK_MODE_SINGLE), + info->regs + SUN20I_GPADC_CTRL); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "could not register the device\n"); + + return 0; +} + +static const struct of_device_id sun20i_gpadc_of_id[] = { + { .compatible = "allwinner,sun20i-d1-gpadc" }, + { .compatible = "allwinner,sun55i-a523-gpadc" }, + { } +}; +MODULE_DEVICE_TABLE(of, sun20i_gpadc_of_id); + +static struct platform_driver sun20i_gpadc_driver = { + .driver = { + .name = SUN20I_GPADC_DRIVER_NAME, + .of_match_table = sun20i_gpadc_of_id, + }, + .probe = sun20i_gpadc_probe, +}; +module_platform_driver(sun20i_gpadc_driver); + +MODULE_DESCRIPTION("ADC driver for sunxi platforms"); +MODULE_AUTHOR("Maksim Kiselev "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_DRIVER"); diff --git a/drivers/iio/adc/sun4i-gpadc-iio.c b/drivers/iio/adc/sun4i-gpadc-iio.c new file mode 100644 index 000000000..203459ca9 --- /dev/null +++ b/drivers/iio/adc/sun4i-gpadc-iio.c @@ -0,0 +1,705 @@ +// SPDX-License-Identifier: GPL-2.0 +/* ADC driver for sunxi platforms' (A10, A13 and A31) GPADC + * + * Copyright (c) 2016 Quentin Schulz + * + * The Allwinner SoCs all have an ADC that can also act as a touchscreen + * controller and a thermal sensor. + * The thermal sensor works only when the ADC acts as a touchscreen controller + * and is configured to throw an interrupt every fixed periods of time (let say + * every X seconds). + * One would be tempted to disable the IP on the hardware side rather than + * disabling interrupts to save some power but that resets the internal clock of + * the IP, resulting in having to wait X seconds every time we want to read the + * value of the thermal sensor. + * This is also the reason of using autosuspend in pm_runtime. If there was no + * autosuspend, the thermal sensor would need X seconds after every + * pm_runtime_get_sync to get a value from the ADC. The autosuspend allows the + * thermal sensor to be requested again in a certain time span before it gets + * shutdown for not being used. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +static unsigned int sun4i_gpadc_chan_select(unsigned int chan) +{ + return SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(chan); +} + +static unsigned int sun6i_gpadc_chan_select(unsigned int chan) +{ + return SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(chan); +} + +struct gpadc_data { + int temp_offset; + int temp_scale; + unsigned int tp_mode_en; + unsigned int tp_adc_select; + unsigned int (*adc_chan_select)(unsigned int chan); + unsigned int adc_chan_mask; +}; + +static const struct gpadc_data sun4i_gpadc_data = { + .temp_offset = -1932, + .temp_scale = 133, + .tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN, + .tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT, + .adc_chan_select = &sun4i_gpadc_chan_select, + .adc_chan_mask = SUN4I_GPADC_CTRL1_ADC_CHAN_MASK, +}; + +static const struct gpadc_data sun5i_gpadc_data = { + .temp_offset = -1447, + .temp_scale = 100, + .tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN, + .tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT, + .adc_chan_select = &sun4i_gpadc_chan_select, + .adc_chan_mask = SUN4I_GPADC_CTRL1_ADC_CHAN_MASK, +}; + +static const struct gpadc_data sun6i_gpadc_data = { + .temp_offset = -1623, + .temp_scale = 167, + .tp_mode_en = SUN6I_GPADC_CTRL1_TP_MODE_EN, + .tp_adc_select = SUN6I_GPADC_CTRL1_TP_ADC_SELECT, + .adc_chan_select = &sun6i_gpadc_chan_select, + .adc_chan_mask = SUN6I_GPADC_CTRL1_ADC_CHAN_MASK, +}; + +static const struct gpadc_data sun8i_a33_gpadc_data = { + .temp_offset = -1662, + .temp_scale = 162, + .tp_mode_en = SUN8I_GPADC_CTRL1_CHOP_TEMP_EN, +}; + +struct sun4i_gpadc_iio { + struct iio_dev *indio_dev; + struct completion completion; + int temp_data; + u32 adc_data; + struct regmap *regmap; + unsigned int fifo_data_irq; + atomic_t ignore_fifo_data_irq; + unsigned int temp_data_irq; + atomic_t ignore_temp_data_irq; + const struct gpadc_data *data; + bool no_irq; + /* prevents concurrent reads of temperature and ADC */ + struct mutex mutex; + struct thermal_zone_device *tzd; + struct device *sensor_device; +}; + +#define SUN4I_GPADC_ADC_CHANNEL(_channel, _name) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _channel, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = _name, \ +} + +static const struct iio_map sun4i_gpadc_hwmon_maps[] = { + IIO_MAP("temp_adc", "iio_hwmon.0", NULL), + { } +}; + +static const struct iio_chan_spec sun4i_gpadc_channels[] = { + SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"), + SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"), + SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"), + SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"), + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .datasheet_name = "temp_adc", + }, +}; + +static const struct iio_chan_spec sun4i_gpadc_channels_no_temp[] = { + SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"), + SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"), + SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"), + SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"), +}; + +static const struct iio_chan_spec sun8i_a33_gpadc_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .datasheet_name = "temp_adc", + }, +}; + +static const struct regmap_config sun4i_gpadc_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +static int sun4i_prepare_for_irq(struct iio_dev *indio_dev, int channel, + unsigned int irq) +{ + struct sun4i_gpadc_iio *info = iio_priv(indio_dev); + int ret; + u32 reg; + + pm_runtime_get_sync(indio_dev->dev.parent); + + reinit_completion(&info->completion); + + ret = regmap_write(info->regmap, SUN4I_GPADC_INT_FIFOC, + SUN4I_GPADC_INT_FIFOC_TP_FIFO_TRIG_LEVEL(1) | + SUN4I_GPADC_INT_FIFOC_TP_FIFO_FLUSH); + if (ret) + return ret; + + ret = regmap_read(info->regmap, SUN4I_GPADC_CTRL1, ®); + if (ret) + return ret; + + if (irq == info->fifo_data_irq) { + ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1, + info->data->tp_mode_en | + info->data->tp_adc_select | + info->data->adc_chan_select(channel)); + /* + * When the IP changes channel, it needs a bit of time to get + * correct values. + */ + if ((reg & info->data->adc_chan_mask) != + info->data->adc_chan_select(channel)) + mdelay(10); + + } else { + /* + * The temperature sensor returns valid data only when the ADC + * operates in touchscreen mode. + */ + ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1, + info->data->tp_mode_en); + } + + if (ret) + return ret; + + /* + * When the IP changes mode between ADC or touchscreen, it + * needs a bit of time to get correct values. + */ + if ((reg & info->data->tp_adc_select) != info->data->tp_adc_select) + mdelay(100); + + return 0; +} + +static int sun4i_gpadc_read(struct iio_dev *indio_dev, int channel, int *val, + unsigned int irq) +{ + struct sun4i_gpadc_iio *info = iio_priv(indio_dev); + int ret; + + mutex_lock(&info->mutex); + + ret = sun4i_prepare_for_irq(indio_dev, channel, irq); + if (ret) + goto err; + + enable_irq(irq); + + /* + * The temperature sensor throws an interruption periodically (currently + * set at periods of ~0.6s in sun4i_gpadc_runtime_resume). A 1s delay + * makes sure an interruption occurs in normal conditions. If it doesn't + * occur, then there is a timeout. + */ + if (!wait_for_completion_timeout(&info->completion, + msecs_to_jiffies(1000))) { + ret = -ETIMEDOUT; + goto err; + } + + if (irq == info->fifo_data_irq) + *val = info->adc_data; + else + *val = info->temp_data; + + ret = 0; + +err: + pm_runtime_put_autosuspend(indio_dev->dev.parent); + disable_irq(irq); + mutex_unlock(&info->mutex); + + return ret; +} + +static int sun4i_gpadc_adc_read(struct iio_dev *indio_dev, int channel, + int *val) +{ + struct sun4i_gpadc_iio *info = iio_priv(indio_dev); + + return sun4i_gpadc_read(indio_dev, channel, val, info->fifo_data_irq); +} + +static int sun4i_gpadc_temp_read(struct iio_dev *indio_dev, int *val) +{ + struct sun4i_gpadc_iio *info = iio_priv(indio_dev); + + if (info->no_irq) { + pm_runtime_get_sync(indio_dev->dev.parent); + + regmap_read(info->regmap, SUN4I_GPADC_TEMP_DATA, val); + + pm_runtime_put_autosuspend(indio_dev->dev.parent); + + return 0; + } + + return sun4i_gpadc_read(indio_dev, 0, val, info->temp_data_irq); +} + +static int sun4i_gpadc_temp_offset(struct iio_dev *indio_dev, int *val) +{ + struct sun4i_gpadc_iio *info = iio_priv(indio_dev); + + *val = info->data->temp_offset; + + return 0; +} + +static int sun4i_gpadc_temp_scale(struct iio_dev *indio_dev, int *val) +{ + struct sun4i_gpadc_iio *info = iio_priv(indio_dev); + + *val = info->data->temp_scale; + + return 0; +} + +static int sun4i_gpadc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int ret; + + switch (mask) { + case IIO_CHAN_INFO_OFFSET: + ret = sun4i_gpadc_temp_offset(indio_dev, val); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_VOLTAGE) + ret = sun4i_gpadc_adc_read(indio_dev, chan->channel, + val); + else + ret = sun4i_gpadc_temp_read(indio_dev, val); + + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_VOLTAGE) { + /* 3000mV / 4096 * raw */ + *val = 0; + *val2 = 732421875; + return IIO_VAL_INT_PLUS_NANO; + } + + ret = sun4i_gpadc_temp_scale(indio_dev, val); + if (ret) + return ret; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static const struct iio_info sun4i_gpadc_iio_info = { + .read_raw = sun4i_gpadc_read_raw, +}; + +static irqreturn_t sun4i_gpadc_temp_data_irq_handler(int irq, void *dev_id) +{ + struct sun4i_gpadc_iio *info = dev_id; + + if (atomic_read(&info->ignore_temp_data_irq)) + goto out; + + if (!regmap_read(info->regmap, SUN4I_GPADC_TEMP_DATA, &info->temp_data)) + complete(&info->completion); + +out: + return IRQ_HANDLED; +} + +static irqreturn_t sun4i_gpadc_fifo_data_irq_handler(int irq, void *dev_id) +{ + struct sun4i_gpadc_iio *info = dev_id; + + if (atomic_read(&info->ignore_fifo_data_irq)) + goto out; + + if (!regmap_read(info->regmap, SUN4I_GPADC_DATA, &info->adc_data)) + complete(&info->completion); + +out: + return IRQ_HANDLED; +} + +static int sun4i_gpadc_runtime_suspend(struct device *dev) +{ + struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev)); + + /* Disable the ADC on IP */ + regmap_write(info->regmap, SUN4I_GPADC_CTRL1, 0); + /* Disable temperature sensor on IP */ + regmap_write(info->regmap, SUN4I_GPADC_TPR, 0); + + return 0; +} + +static int sun4i_gpadc_runtime_resume(struct device *dev) +{ + struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev)); + + /* clkin = 6MHz */ + regmap_write(info->regmap, SUN4I_GPADC_CTRL0, + SUN4I_GPADC_CTRL0_ADC_CLK_DIVIDER(2) | + SUN4I_GPADC_CTRL0_FS_DIV(7) | + SUN4I_GPADC_CTRL0_T_ACQ(63)); + regmap_write(info->regmap, SUN4I_GPADC_CTRL1, info->data->tp_mode_en); + regmap_write(info->regmap, SUN4I_GPADC_CTRL3, + SUN4I_GPADC_CTRL3_FILTER_EN | + SUN4I_GPADC_CTRL3_FILTER_TYPE(1)); + /* period = SUN4I_GPADC_TPR_TEMP_PERIOD * 256 * 16 / clkin; ~0.6s */ + regmap_write(info->regmap, SUN4I_GPADC_TPR, + SUN4I_GPADC_TPR_TEMP_ENABLE | + SUN4I_GPADC_TPR_TEMP_PERIOD(800)); + + return 0; +} + +static int sun4i_gpadc_get_temp(struct thermal_zone_device *tz, int *temp) +{ + struct sun4i_gpadc_iio *info = thermal_zone_device_priv(tz); + int val, scale, offset; + + if (sun4i_gpadc_temp_read(info->indio_dev, &val)) + return -ETIMEDOUT; + + sun4i_gpadc_temp_scale(info->indio_dev, &scale); + sun4i_gpadc_temp_offset(info->indio_dev, &offset); + + *temp = (val + offset) * scale; + + return 0; +} + +static const struct thermal_zone_device_ops sun4i_ts_tz_ops = { + .get_temp = &sun4i_gpadc_get_temp, +}; + +static const struct dev_pm_ops sun4i_gpadc_pm_ops = { + .runtime_suspend = &sun4i_gpadc_runtime_suspend, + .runtime_resume = &sun4i_gpadc_runtime_resume, +}; + +static int sun4i_irq_init(struct platform_device *pdev, const char *name, + irq_handler_t handler, const char *devname, + unsigned int *irq, atomic_t *atomic) +{ + int ret; + struct sun4i_gpadc_dev *mfd_dev = dev_get_drvdata(pdev->dev.parent); + struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(&pdev->dev)); + + /* + * Once the interrupt is activated, the IP continuously performs + * conversions thus throws interrupts. The interrupt is activated right + * after being requested but we want to control when these interrupts + * occur thus we disable it right after being requested. However, an + * interrupt might occur between these two instructions and we have to + * make sure that does not happen, by using atomic flags. We set the + * flag before requesting the interrupt and unset it right after + * disabling the interrupt. When an interrupt occurs between these two + * instructions, reading the atomic flag will tell us to ignore the + * interrupt. + */ + atomic_set(atomic, 1); + + ret = platform_get_irq_byname(pdev, name); + if (ret < 0) + return ret; + + ret = regmap_irq_get_virq(mfd_dev->regmap_irqc, ret); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get virq for irq %s\n", name); + return ret; + } + + *irq = ret; + ret = devm_request_any_context_irq(&pdev->dev, *irq, handler, + IRQF_NO_AUTOEN, + devname, info); + if (ret < 0) { + dev_err(&pdev->dev, "could not request %s interrupt: %d\n", + name, ret); + return ret; + } + + atomic_set(atomic, 0); + + return 0; +} + +static const struct of_device_id sun4i_gpadc_of_id[] = { + { + .compatible = "allwinner,sun8i-a33-ths", + .data = &sun8i_a33_gpadc_data, + }, + { } +}; + +static int sun4i_gpadc_probe_dt(struct platform_device *pdev, + struct iio_dev *indio_dev) +{ + struct sun4i_gpadc_iio *info = iio_priv(indio_dev); + void __iomem *base; + int ret; + + info->data = of_device_get_match_data(&pdev->dev); + if (!info->data) + return -ENODEV; + + info->no_irq = true; + indio_dev->num_channels = ARRAY_SIZE(sun8i_a33_gpadc_channels); + indio_dev->channels = sun8i_a33_gpadc_channels; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + info->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &sun4i_gpadc_regmap_config); + if (IS_ERR(info->regmap)) { + ret = PTR_ERR(info->regmap); + dev_err(&pdev->dev, "failed to init regmap: %d\n", ret); + return ret; + } + + if (IS_ENABLED(CONFIG_THERMAL_OF)) + info->sensor_device = &pdev->dev; + + return 0; +} + +static int sun4i_gpadc_probe_mfd(struct platform_device *pdev, + struct iio_dev *indio_dev) +{ + struct sun4i_gpadc_iio *info = iio_priv(indio_dev); + struct sun4i_gpadc_dev *sun4i_gpadc_dev = + dev_get_drvdata(pdev->dev.parent); + int ret; + + info->no_irq = false; + info->regmap = sun4i_gpadc_dev->regmap; + + indio_dev->num_channels = ARRAY_SIZE(sun4i_gpadc_channels); + indio_dev->channels = sun4i_gpadc_channels; + + info->data = (struct gpadc_data *)platform_get_device_id(pdev)->driver_data; + + /* + * Since the controller needs to be in touchscreen mode for its thermal + * sensor to operate properly, and that switching between the two modes + * needs a delay, always registering in the thermal framework will + * significantly slow down the conversion rate of the ADCs. + * + * Therefore, instead of depending on THERMAL_OF in Kconfig, we only + * register the sensor if that option is enabled, eventually leaving + * that choice to the user. + */ + + if (IS_ENABLED(CONFIG_THERMAL_OF)) { + /* + * This driver is a child of an MFD which has a node in the DT + * but not its children, because of DT backward compatibility + * for A10, A13 and A31 SoCs. Therefore, the resulting devices + * of this driver do not have an of_node variable. + * However, its parent (the MFD driver) has an of_node variable + * and since devm_thermal_zone_of_sensor_register uses its first + * argument to match the phandle defined in the node of the + * thermal driver with the of_node of the device passed as first + * argument and the third argument to call ops from + * thermal_zone_of_device_ops, the solution is to use the parent + * device as first argument to match the phandle with its + * of_node, and the device from this driver as third argument to + * return the temperature. + */ + info->sensor_device = pdev->dev.parent; + } else { + indio_dev->num_channels = + ARRAY_SIZE(sun4i_gpadc_channels_no_temp); + indio_dev->channels = sun4i_gpadc_channels_no_temp; + } + + if (IS_ENABLED(CONFIG_THERMAL_OF)) { + ret = sun4i_irq_init(pdev, "TEMP_DATA_PENDING", + sun4i_gpadc_temp_data_irq_handler, + "temp_data", &info->temp_data_irq, + &info->ignore_temp_data_irq); + if (ret < 0) + return ret; + } + + ret = sun4i_irq_init(pdev, "FIFO_DATA_PENDING", + sun4i_gpadc_fifo_data_irq_handler, "fifo_data", + &info->fifo_data_irq, &info->ignore_fifo_data_irq); + if (ret < 0) + return ret; + + if (IS_ENABLED(CONFIG_THERMAL_OF)) { + ret = iio_map_array_register(indio_dev, sun4i_gpadc_hwmon_maps); + if (ret < 0) { + dev_err(&pdev->dev, + "failed to register iio map array\n"); + return ret; + } + } + + return 0; +} + +static int sun4i_gpadc_probe(struct platform_device *pdev) +{ + struct sun4i_gpadc_iio *info; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + platform_set_drvdata(pdev, indio_dev); + + mutex_init(&info->mutex); + info->indio_dev = indio_dev; + init_completion(&info->completion); + indio_dev->name = dev_name(&pdev->dev); + indio_dev->info = &sun4i_gpadc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (pdev->dev.of_node) + ret = sun4i_gpadc_probe_dt(pdev, indio_dev); + else + ret = sun4i_gpadc_probe_mfd(pdev, indio_dev); + + if (ret) + return ret; + + pm_runtime_set_autosuspend_delay(&pdev->dev, + SUN4I_GPADC_AUTOSUSPEND_DELAY); + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + if (IS_ENABLED(CONFIG_THERMAL_OF)) { + info->tzd = devm_thermal_of_zone_register(info->sensor_device, + 0, info, + &sun4i_ts_tz_ops); + /* + * Do not fail driver probing when failing to register in + * thermal because no thermal DT node is found. + */ + if (IS_ERR(info->tzd) && PTR_ERR(info->tzd) != -ENODEV) { + dev_err(&pdev->dev, + "could not register thermal sensor: %ld\n", + PTR_ERR(info->tzd)); + return PTR_ERR(info->tzd); + } + } + + ret = devm_iio_device_register(&pdev->dev, indio_dev); + if (ret < 0) { + dev_err(&pdev->dev, "could not register the device\n"); + goto err_map; + } + + return 0; + +err_map: + if (!info->no_irq && IS_ENABLED(CONFIG_THERMAL_OF)) + iio_map_array_unregister(indio_dev); + + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static void sun4i_gpadc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct sun4i_gpadc_iio *info = iio_priv(indio_dev); + + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + if (!IS_ENABLED(CONFIG_THERMAL_OF)) + return; + + if (!info->no_irq) + iio_map_array_unregister(indio_dev); +} + +static const struct platform_device_id sun4i_gpadc_id[] = { + { .name = "sun4i-a10-gpadc-iio", .driver_data = (kernel_ulong_t)&sun4i_gpadc_data }, + { .name = "sun5i-a13-gpadc-iio", .driver_data = (kernel_ulong_t)&sun5i_gpadc_data }, + { .name = "sun6i-a31-gpadc-iio", .driver_data = (kernel_ulong_t)&sun6i_gpadc_data }, + { } +}; +MODULE_DEVICE_TABLE(platform, sun4i_gpadc_id); + +static struct platform_driver sun4i_gpadc_driver = { + .driver = { + .name = "sun4i-gpadc-iio", + .of_match_table = sun4i_gpadc_of_id, + .pm = &sun4i_gpadc_pm_ops, + }, + .id_table = sun4i_gpadc_id, + .probe = sun4i_gpadc_probe, + .remove = sun4i_gpadc_remove, +}; +MODULE_DEVICE_TABLE(of, sun4i_gpadc_of_id); + +module_platform_driver(sun4i_gpadc_driver); + +MODULE_DESCRIPTION("ADC driver for sunxi platforms"); +MODULE_AUTHOR("Quentin Schulz "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ti-adc081c.c b/drivers/iio/adc/ti-adc081c.c new file mode 100644 index 000000000..2df8f1c21 --- /dev/null +++ b/drivers/iio/adc/ti-adc081c.c @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * TI ADC081C/ADC101C/ADC121C 8/10/12-bit ADC driver + * + * Copyright (C) 2012 Avionic Design GmbH + * Copyright (C) 2016 Intel + * + * Datasheets: + * https://www.ti.com/lit/ds/symlink/adc081c021.pdf + * https://www.ti.com/lit/ds/symlink/adc101c021.pdf + * https://www.ti.com/lit/ds/symlink/adc121c021.pdf + * + * The devices have a very similar interface and differ mostly in the number of + * bits handled. For the 8-bit and 10-bit models the least-significant 4 or 2 + * bits of value registers are reserved. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +struct adc081c { + struct i2c_client *i2c; + struct regulator *ref; + + /* 8, 10 or 12 */ + int bits; + + /* Ensure natural alignment of buffer elements */ + struct { + u16 channel; + aligned_s64 ts; + } scan; +}; + +#define REG_CONV_RES 0x00 + +static int adc081c_read_raw(struct iio_dev *iio, + struct iio_chan_spec const *channel, int *value, + int *shift, long mask) +{ + struct adc081c *adc = iio_priv(iio); + int err; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + err = i2c_smbus_read_word_swapped(adc->i2c, REG_CONV_RES); + if (err < 0) + return err; + + *value = (err & 0xFFF) >> (12 - adc->bits); + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + err = regulator_get_voltage(adc->ref); + if (err < 0) + return err; + + *value = err / 1000; + *shift = adc->bits; + + return IIO_VAL_FRACTIONAL_LOG2; + + default: + break; + } + + return -EINVAL; +} + +#define ADCxx1C_CHAN(_bits) { \ + .type = IIO_VOLTAGE, \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (_bits), \ + .storagebits = 16, \ + .shift = 12 - (_bits), \ + .endianness = IIO_CPU, \ + }, \ +} + +#define DEFINE_ADCxx1C_CHANNELS(_name, _bits) \ + static const struct iio_chan_spec _name ## _channels[] = { \ + ADCxx1C_CHAN((_bits)), \ + IIO_CHAN_SOFT_TIMESTAMP(1), \ + }; \ + +#define ADC081C_NUM_CHANNELS 2 + +struct adcxx1c_model { + const struct iio_chan_spec* channels; + int bits; +}; + +DEFINE_ADCxx1C_CHANNELS(adc081c, 8); +DEFINE_ADCxx1C_CHANNELS(adc101c, 10); +DEFINE_ADCxx1C_CHANNELS(adc121c, 12); + +static const struct adcxx1c_model adc081c_model = { + .channels = adc081c_channels, + .bits = 8, +}; + +static const struct adcxx1c_model adc101c_model = { + .channels = adc101c_channels, + .bits = 10, +}; + +static const struct adcxx1c_model adc121c_model = { + .channels = adc121c_channels, + .bits = 12, +}; + +static const struct iio_info adc081c_info = { + .read_raw = adc081c_read_raw, +}; + +static irqreturn_t adc081c_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adc081c *data = iio_priv(indio_dev); + int ret; + + ret = i2c_smbus_read_word_swapped(data->i2c, REG_CONV_RES); + if (ret < 0) + goto out; + data->scan.channel = ret; + iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + iio_get_time_ns(indio_dev)); +out: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static void adc081c_reg_disable(void *reg) +{ + regulator_disable(reg); +} + +static int adc081c_probe(struct i2c_client *client) +{ + struct iio_dev *iio; + struct adc081c *adc; + const struct adcxx1c_model *model; + int err; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -EOPNOTSUPP; + + model = i2c_get_match_data(client); + + iio = devm_iio_device_alloc(&client->dev, sizeof(*adc)); + if (!iio) + return -ENOMEM; + + adc = iio_priv(iio); + adc->i2c = client; + adc->bits = model->bits; + + adc->ref = devm_regulator_get(&client->dev, "vref"); + if (IS_ERR(adc->ref)) + return PTR_ERR(adc->ref); + + err = regulator_enable(adc->ref); + if (err < 0) + return err; + + err = devm_add_action_or_reset(&client->dev, adc081c_reg_disable, + adc->ref); + if (err) + return err; + + iio->name = dev_name(&client->dev); + iio->modes = INDIO_DIRECT_MODE; + iio->info = &adc081c_info; + + iio->channels = model->channels; + iio->num_channels = ADC081C_NUM_CHANNELS; + + err = devm_iio_triggered_buffer_setup(&client->dev, iio, NULL, + adc081c_trigger_handler, NULL); + if (err < 0) + return dev_err_probe(&client->dev, err, "iio triggered buffer setup failed\n"); + + return devm_iio_device_register(&client->dev, iio); +} + +static const struct i2c_device_id adc081c_id[] = { + { .name = "adc081c", .driver_data = (kernel_ulong_t)&adc081c_model }, + { .name = "adc101c", .driver_data = (kernel_ulong_t)&adc101c_model }, + { .name = "adc121c", .driver_data = (kernel_ulong_t)&adc121c_model }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adc081c_id); + +static const struct acpi_device_id adc081c_acpi_match[] = { + /* Used on some AAEON boards */ + { "ADC081C", (kernel_ulong_t)&adc081c_model }, + { } +}; +MODULE_DEVICE_TABLE(acpi, adc081c_acpi_match); + +static const struct of_device_id adc081c_of_match[] = { + { .compatible = "ti,adc081c", .data = &adc081c_model }, + { .compatible = "ti,adc101c", .data = &adc101c_model }, + { .compatible = "ti,adc121c", .data = &adc121c_model }, + { } +}; +MODULE_DEVICE_TABLE(of, adc081c_of_match); + +static struct i2c_driver adc081c_driver = { + .driver = { + .name = "adc081c", + .of_match_table = adc081c_of_match, + .acpi_match_table = adc081c_acpi_match, + }, + .probe = adc081c_probe, + .id_table = adc081c_id, +}; +module_i2c_driver(adc081c_driver); + +MODULE_AUTHOR("Thierry Reding "); +MODULE_DESCRIPTION("Texas Instruments ADC081C/ADC101C/ADC121C driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ti-adc0832.c b/drivers/iio/adc/ti-adc0832.c new file mode 100644 index 000000000..a522d8676 --- /dev/null +++ b/drivers/iio/adc/ti-adc0832.c @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADC0831/ADC0832/ADC0834/ADC0838 8-bit ADC driver + * + * Copyright (c) 2016 Akinobu Mita + * + * Datasheet: https://www.ti.com/lit/ds/symlink/adc0832-n.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +enum { + adc0831, + adc0832, + adc0834, + adc0838, +}; + +struct adc0832 { + struct spi_device *spi; + struct regulator *reg; + struct mutex lock; + u8 mux_bits; + /* + * Max size needed: 16x 1 byte ADC data + 8 bytes timestamp + * May be shorter if not all channels are enabled subject + * to the timestamp remaining 8 byte aligned. + */ + u8 data[24] __aligned(8); + + u8 tx_buf[2] __aligned(IIO_DMA_MINALIGN); + u8 rx_buf[2]; +}; + +#define ADC0832_VOLTAGE_CHANNEL(chan) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = chan, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 8, \ + .storagebits = 8, \ + }, \ + } + +#define ADC0832_VOLTAGE_CHANNEL_DIFF(chan1, chan2, si) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (chan1), \ + .channel2 = (chan2), \ + .differential = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = si, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 8, \ + .storagebits = 8, \ + }, \ + } + +static const struct iio_chan_spec adc0831_channels[] = { + ADC0832_VOLTAGE_CHANNEL_DIFF(0, 1, 0), + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct iio_chan_spec adc0832_channels[] = { + ADC0832_VOLTAGE_CHANNEL(0), + ADC0832_VOLTAGE_CHANNEL(1), + ADC0832_VOLTAGE_CHANNEL_DIFF(0, 1, 2), + ADC0832_VOLTAGE_CHANNEL_DIFF(1, 0, 3), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const struct iio_chan_spec adc0834_channels[] = { + ADC0832_VOLTAGE_CHANNEL(0), + ADC0832_VOLTAGE_CHANNEL(1), + ADC0832_VOLTAGE_CHANNEL(2), + ADC0832_VOLTAGE_CHANNEL(3), + ADC0832_VOLTAGE_CHANNEL_DIFF(0, 1, 4), + ADC0832_VOLTAGE_CHANNEL_DIFF(1, 0, 5), + ADC0832_VOLTAGE_CHANNEL_DIFF(2, 3, 6), + ADC0832_VOLTAGE_CHANNEL_DIFF(3, 2, 7), + IIO_CHAN_SOFT_TIMESTAMP(8), +}; + +static const struct iio_chan_spec adc0838_channels[] = { + ADC0832_VOLTAGE_CHANNEL(0), + ADC0832_VOLTAGE_CHANNEL(1), + ADC0832_VOLTAGE_CHANNEL(2), + ADC0832_VOLTAGE_CHANNEL(3), + ADC0832_VOLTAGE_CHANNEL(4), + ADC0832_VOLTAGE_CHANNEL(5), + ADC0832_VOLTAGE_CHANNEL(6), + ADC0832_VOLTAGE_CHANNEL(7), + ADC0832_VOLTAGE_CHANNEL_DIFF(0, 1, 8), + ADC0832_VOLTAGE_CHANNEL_DIFF(1, 0, 9), + ADC0832_VOLTAGE_CHANNEL_DIFF(2, 3, 10), + ADC0832_VOLTAGE_CHANNEL_DIFF(3, 2, 11), + ADC0832_VOLTAGE_CHANNEL_DIFF(4, 5, 12), + ADC0832_VOLTAGE_CHANNEL_DIFF(5, 4, 13), + ADC0832_VOLTAGE_CHANNEL_DIFF(6, 7, 14), + ADC0832_VOLTAGE_CHANNEL_DIFF(7, 6, 15), + IIO_CHAN_SOFT_TIMESTAMP(16), +}; + +static int adc0831_adc_conversion(struct adc0832 *adc) +{ + struct spi_device *spi = adc->spi; + int ret; + + ret = spi_read(spi, &adc->rx_buf, 2); + if (ret) + return ret; + + /* + * Skip TRI-STATE and a leading zero + */ + return (adc->rx_buf[0] << 2 & 0xff) | (adc->rx_buf[1] >> 6); +} + +static int adc0832_adc_conversion(struct adc0832 *adc, int channel, + bool differential) +{ + struct spi_device *spi = adc->spi; + struct spi_transfer xfer = { + .tx_buf = adc->tx_buf, + .rx_buf = adc->rx_buf, + .len = 2, + }; + int ret; + + if (!adc->mux_bits) + return adc0831_adc_conversion(adc); + + /* start bit */ + adc->tx_buf[0] = 1 << (adc->mux_bits + 1); + /* single-ended or differential */ + adc->tx_buf[0] |= differential ? 0 : (1 << adc->mux_bits); + /* odd / sign */ + adc->tx_buf[0] |= (channel % 2) << (adc->mux_bits - 1); + /* select */ + if (adc->mux_bits > 1) + adc->tx_buf[0] |= channel / 2; + + /* align Data output BIT7 (MSB) to 8-bit boundary */ + adc->tx_buf[0] <<= 1; + + ret = spi_sync_transfer(spi, &xfer, 1); + if (ret) + return ret; + + return adc->rx_buf[1]; +} + +static int adc0832_read_raw(struct iio_dev *iio, + struct iio_chan_spec const *channel, int *value, + int *shift, long mask) +{ + struct adc0832 *adc = iio_priv(iio); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&adc->lock); + *value = adc0832_adc_conversion(adc, channel->channel, + channel->differential); + mutex_unlock(&adc->lock); + if (*value < 0) + return *value; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *value = regulator_get_voltage(adc->reg); + if (*value < 0) + return *value; + + /* convert regulator output voltage to mV */ + *value /= 1000; + *shift = 8; + + return IIO_VAL_FRACTIONAL_LOG2; + } + + return -EINVAL; +} + +static const struct iio_info adc0832_info = { + .read_raw = adc0832_read_raw, +}; + +static irqreturn_t adc0832_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adc0832 *adc = iio_priv(indio_dev); + int scan_index; + int i = 0; + + mutex_lock(&adc->lock); + + iio_for_each_active_channel(indio_dev, scan_index) { + const struct iio_chan_spec *scan_chan = + &indio_dev->channels[scan_index]; + int ret = adc0832_adc_conversion(adc, scan_chan->channel, + scan_chan->differential); + if (ret < 0) { + dev_warn(&adc->spi->dev, + "failed to get conversion data\n"); + goto out; + } + + adc->data[i] = ret; + i++; + } + iio_push_to_buffers_with_ts(indio_dev, adc->data, sizeof(adc->data), + iio_get_time_ns(indio_dev)); +out: + mutex_unlock(&adc->lock); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static void adc0832_reg_disable(void *reg) +{ + regulator_disable(reg); +} + +static int adc0832_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct adc0832 *adc; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->spi = spi; + mutex_init(&adc->lock); + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &adc0832_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + switch (spi_get_device_id(spi)->driver_data) { + case adc0831: + adc->mux_bits = 0; + indio_dev->channels = adc0831_channels; + indio_dev->num_channels = ARRAY_SIZE(adc0831_channels); + break; + case adc0832: + adc->mux_bits = 1; + indio_dev->channels = adc0832_channels; + indio_dev->num_channels = ARRAY_SIZE(adc0832_channels); + break; + case adc0834: + adc->mux_bits = 2; + indio_dev->channels = adc0834_channels; + indio_dev->num_channels = ARRAY_SIZE(adc0834_channels); + break; + case adc0838: + adc->mux_bits = 3; + indio_dev->channels = adc0838_channels; + indio_dev->num_channels = ARRAY_SIZE(adc0838_channels); + break; + default: + return -EINVAL; + } + + adc->reg = devm_regulator_get(&spi->dev, "vref"); + if (IS_ERR(adc->reg)) + return PTR_ERR(adc->reg); + + ret = regulator_enable(adc->reg); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&spi->dev, adc0832_reg_disable, + adc->reg); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, NULL, + adc0832_trigger_handler, NULL); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct of_device_id adc0832_dt_ids[] = { + { .compatible = "ti,adc0831", }, + { .compatible = "ti,adc0832", }, + { .compatible = "ti,adc0834", }, + { .compatible = "ti,adc0838", }, + { } +}; +MODULE_DEVICE_TABLE(of, adc0832_dt_ids); + +static const struct spi_device_id adc0832_id[] = { + { .name = "adc0831", .driver_data = adc0831 }, + { .name = "adc0832", .driver_data = adc0832 }, + { .name = "adc0834", .driver_data = adc0834 }, + { .name = "adc0838", .driver_data = adc0838 }, + { } +}; +MODULE_DEVICE_TABLE(spi, adc0832_id); + +static struct spi_driver adc0832_driver = { + .driver = { + .name = "adc0832", + .of_match_table = adc0832_dt_ids, + }, + .probe = adc0832_probe, + .id_table = adc0832_id, +}; +module_spi_driver(adc0832_driver); + +MODULE_AUTHOR("Akinobu Mita "); +MODULE_DESCRIPTION("ADC0831/ADC0832/ADC0834/ADC0838 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ti-adc084s021.c b/drivers/iio/adc/ti-adc084s021.c new file mode 100644 index 000000000..cf2f6ee59 --- /dev/null +++ b/drivers/iio/adc/ti-adc084s021.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2017 Axis Communications AB + * + * Driver for Texas Instruments' ADC084S021 ADC chip. + * Datasheets can be found here: + * https://www.ti.com/lit/ds/symlink/adc084s021.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ADC084S021_DRIVER_NAME "adc084s021" + +struct adc084s021 { + struct spi_device *spi; + struct spi_message message; + struct spi_transfer spi_trans; + struct regulator *reg; + struct mutex lock; + /* Buffer used to align data */ + struct { + __be16 channels[4]; + aligned_s64 ts; + } scan; + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache line. + */ + u16 tx_buf[4] __aligned(IIO_DMA_MINALIGN); + __be16 rx_buf[5]; /* First 16-bits are trash */ +}; + +#define ADC084S021_VOLTAGE_CHANNEL(num) \ + { \ + .type = IIO_VOLTAGE, \ + .channel = (num), \ + .indexed = 1, \ + .scan_index = (num), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 8, \ + .storagebits = 16, \ + .shift = 4, \ + .endianness = IIO_BE, \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),\ + } + +static const struct iio_chan_spec adc084s021_channels[] = { + ADC084S021_VOLTAGE_CHANNEL(0), + ADC084S021_VOLTAGE_CHANNEL(1), + ADC084S021_VOLTAGE_CHANNEL(2), + ADC084S021_VOLTAGE_CHANNEL(3), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +/** + * adc084s021_adc_conversion() - Read an ADC channel and return its value. + * + * @adc: The ADC SPI data. + * @data: Buffer for converted data. + */ +static int adc084s021_adc_conversion(struct adc084s021 *adc, __be16 *data) +{ + int n_words = (adc->spi_trans.len >> 1) - 1; /* Discard first word */ + int ret, i = 0; + + /* Do the transfer */ + ret = spi_sync(adc->spi, &adc->message); + if (ret < 0) + return ret; + + for (; i < n_words; i++) + *(data + i) = adc->rx_buf[i + 1]; + + return ret; +} + +static int adc084s021_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct adc084s021 *adc = iio_priv(indio_dev); + int ret; + __be16 be_val; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = regulator_enable(adc->reg); + if (ret) { + iio_device_release_direct(indio_dev); + return ret; + } + + adc->tx_buf[0] = channel->channel << 3; + ret = adc084s021_adc_conversion(adc, &be_val); + iio_device_release_direct(indio_dev); + regulator_disable(adc->reg); + if (ret < 0) + return ret; + + *val = be16_to_cpu(be_val); + *val = (*val >> channel->scan_type.shift) & 0xff; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = regulator_enable(adc->reg); + if (ret) + return ret; + + ret = regulator_get_voltage(adc->reg); + regulator_disable(adc->reg); + if (ret < 0) + return ret; + + *val = ret / 1000; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +/** + * adc084s021_buffer_trigger_handler() - Read ADC channels and push to buffer. + * + * @irq: The interrupt number (not used). + * @pollfunc: Pointer to the poll func. + */ +static irqreturn_t adc084s021_buffer_trigger_handler(int irq, void *pollfunc) +{ + struct iio_poll_func *pf = pollfunc; + struct iio_dev *indio_dev = pf->indio_dev; + struct adc084s021 *adc = iio_priv(indio_dev); + + mutex_lock(&adc->lock); + + if (adc084s021_adc_conversion(adc, adc->scan.channels) < 0) + dev_err(&adc->spi->dev, "Failed to read data\n"); + + iio_push_to_buffers_with_ts(indio_dev, &adc->scan, sizeof(adc->scan), + iio_get_time_ns(indio_dev)); + mutex_unlock(&adc->lock); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int adc084s021_buffer_preenable(struct iio_dev *indio_dev) +{ + struct adc084s021 *adc = iio_priv(indio_dev); + int scan_index; + int i = 0; + + iio_for_each_active_channel(indio_dev, scan_index) { + const struct iio_chan_spec *channel = + &indio_dev->channels[scan_index]; + adc->tx_buf[i++] = channel->channel << 3; + } + adc->spi_trans.len = 2 + (i * sizeof(__be16)); /* Trash + channels */ + + return regulator_enable(adc->reg); +} + +static int adc084s021_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct adc084s021 *adc = iio_priv(indio_dev); + + adc->spi_trans.len = 4; /* Trash + single channel */ + + return regulator_disable(adc->reg); +} + +static const struct iio_info adc084s021_info = { + .read_raw = adc084s021_read_raw, +}; + +static const struct iio_buffer_setup_ops adc084s021_buffer_setup_ops = { + .preenable = adc084s021_buffer_preenable, + .postdisable = adc084s021_buffer_postdisable, +}; + +static int adc084s021_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct adc084s021 *adc; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->spi = spi; + + /* Initiate the Industrial I/O device */ + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &adc084s021_info; + indio_dev->channels = adc084s021_channels; + indio_dev->num_channels = ARRAY_SIZE(adc084s021_channels); + + /* Create SPI transfer for channel reads */ + adc->spi_trans.tx_buf = adc->tx_buf; + adc->spi_trans.rx_buf = adc->rx_buf; + adc->spi_trans.len = 4; /* Trash + single channel */ + spi_message_init_with_transfers(&adc->message, &adc->spi_trans, 1); + + adc->reg = devm_regulator_get(&spi->dev, "vref"); + if (IS_ERR(adc->reg)) + return PTR_ERR(adc->reg); + + mutex_init(&adc->lock); + + /* Setup triggered buffer with pollfunction */ + ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, NULL, + adc084s021_buffer_trigger_handler, + &adc084s021_buffer_setup_ops); + if (ret) + return dev_err_probe(&spi->dev, ret, "Failed to setup triggered buffer\n"); + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct of_device_id adc084s021_of_match[] = { + { .compatible = "ti,adc084s021", }, + { } +}; +MODULE_DEVICE_TABLE(of, adc084s021_of_match); + +static const struct spi_device_id adc084s021_id[] = { + { .name = ADC084S021_DRIVER_NAME }, + { } +}; +MODULE_DEVICE_TABLE(spi, adc084s021_id); + +static struct spi_driver adc084s021_driver = { + .driver = { + .name = ADC084S021_DRIVER_NAME, + .of_match_table = adc084s021_of_match, + }, + .probe = adc084s021_probe, + .id_table = adc084s021_id, +}; +module_spi_driver(adc084s021_driver); + +MODULE_AUTHOR("Mårten Lindahl "); +MODULE_DESCRIPTION("Texas Instruments ADC084S021"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("1.0"); diff --git a/drivers/iio/adc/ti-adc108s102.c b/drivers/iio/adc/ti-adc108s102.c new file mode 100644 index 000000000..10959e835 --- /dev/null +++ b/drivers/iio/adc/ti-adc108s102.c @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * TI ADC108S102 SPI ADC driver + * + * Copyright (c) 2013-2015 Intel Corporation. + * Copyright (c) 2017 Siemens AG + * + * This IIO device driver is designed to work with the following + * analog to digital converters from Texas Instruments: + * ADC108S102 + * ADC128S102 + * The communication with ADC chip is via the SPI bus (mode 3). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * In case of ACPI, we use the hard-wired 5000 mV of the Galileo and IOT2000 + * boards as default for the reference pin VA. Device tree users encode that + * via the vref-supply regulator. + */ +#define ADC108S102_VA_MV_ACPI_DEFAULT 5000 + +/* + * Defining the ADC resolution being 12 bits, we can use the same driver for + * both ADC108S102 (10 bits resolution) and ADC128S102 (12 bits resolution) + * chips. The ADC108S102 effectively returns a 12-bit result with the 2 + * least-significant bits unset. + */ +#define ADC108S102_BITS 12 +#define ADC108S102_MAX_CHANNELS 8 + +/* + * 16-bit SPI command format: + * [15:14] Ignored + * [13:11] 3-bit channel address + * [10:0] Ignored + */ +#define ADC108S102_CMD(ch) ((u16)(ch) << 11) + +/* + * 16-bit SPI response format: + * [15:12] Zeros + * [11:0] 12-bit ADC sample (for ADC108S102, [1:0] will always be 0). + */ +#define ADC108S102_RES_DATA(res) ((u16)res & GENMASK(11, 0)) + +struct adc108s102_state { + struct spi_device *spi; + u32 va_millivolt; + /* SPI transfer used by triggered buffer handler*/ + struct spi_transfer ring_xfer; + /* SPI transfer used by direct scan */ + struct spi_transfer scan_single_xfer; + /* SPI message used by ring_xfer SPI transfer */ + struct spi_message ring_msg; + /* SPI message used by scan_single_xfer SPI transfer */ + struct spi_message scan_single_msg; + + /* + * SPI message buffers: + * tx_buf: |C0|C1|C2|C3|C4|C5|C6|C7|XX| + * rx_buf: |XX|R0|R1|R2|R3|R4|R5|R6|R7|tt|tt|tt|tt| + * + * tx_buf: 8 channel read commands, plus 1 dummy command + * rx_buf: 1 dummy response, 8 channel responses + */ + __be16 rx_buf[9] __aligned(IIO_DMA_MINALIGN); + __be16 tx_buf[9] __aligned(IIO_DMA_MINALIGN); +}; + +#define ADC108S102_V_CHAN(index) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .address = index, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = ADC108S102_BITS, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + } + +static const struct iio_chan_spec adc108s102_channels[] = { + ADC108S102_V_CHAN(0), + ADC108S102_V_CHAN(1), + ADC108S102_V_CHAN(2), + ADC108S102_V_CHAN(3), + ADC108S102_V_CHAN(4), + ADC108S102_V_CHAN(5), + ADC108S102_V_CHAN(6), + ADC108S102_V_CHAN(7), + IIO_CHAN_SOFT_TIMESTAMP(8), +}; + +static int adc108s102_update_scan_mode(struct iio_dev *indio_dev, + unsigned long const *active_scan_mask) +{ + struct adc108s102_state *st = iio_priv(indio_dev); + unsigned int bit, cmds; + + /* + * Fill in the first x shorts of tx_buf with the number of channels + * enabled for sampling by the triggered buffer. + */ + cmds = 0; + for_each_set_bit(bit, active_scan_mask, ADC108S102_MAX_CHANNELS) + st->tx_buf[cmds++] = cpu_to_be16(ADC108S102_CMD(bit)); + + /* One dummy command added, to clock in the last response */ + st->tx_buf[cmds++] = 0x00; + + /* build SPI ring message */ + st->ring_xfer.tx_buf = &st->tx_buf[0]; + st->ring_xfer.rx_buf = &st->rx_buf[0]; + st->ring_xfer.len = cmds * sizeof(st->tx_buf[0]); + + spi_message_init_with_transfers(&st->ring_msg, &st->ring_xfer, 1); + + return 0; +} + +static irqreturn_t adc108s102_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adc108s102_state *st = iio_priv(indio_dev); + int ret; + + ret = spi_sync(st->spi, &st->ring_msg); + if (ret < 0) + goto out_notify; + + /* Skip the dummy response in the first slot */ + iio_push_to_buffers_with_ts_unaligned(indio_dev, + &st->rx_buf[1], + st->ring_xfer.len - sizeof(st->rx_buf[1]), + iio_get_time_ns(indio_dev)); + +out_notify: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int adc108s102_scan_direct(struct adc108s102_state *st, unsigned int ch) +{ + int ret; + + st->tx_buf[0] = cpu_to_be16(ADC108S102_CMD(ch)); + ret = spi_sync(st->spi, &st->scan_single_msg); + if (ret) + return ret; + + /* Skip the dummy response in the first slot */ + return be16_to_cpu(st->rx_buf[1]); +} + +static int adc108s102_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long m) +{ + struct adc108s102_state *st = iio_priv(indio_dev); + int ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = adc108s102_scan_direct(st, chan->address); + + iio_device_release_direct(indio_dev); + + if (ret < 0) + return ret; + + *val = ADC108S102_RES_DATA(ret); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (chan->type != IIO_VOLTAGE) + break; + + *val = st->va_millivolt; + *val2 = chan->scan_type.realbits; + + return IIO_VAL_FRACTIONAL_LOG2; + default: + break; + } + + return -EINVAL; +} + +static const struct iio_info adc108s102_info = { + .read_raw = &adc108s102_read_raw, + .update_scan_mode = &adc108s102_update_scan_mode, +}; + +static int adc108s102_probe(struct spi_device *spi) +{ + struct adc108s102_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + if (ACPI_COMPANION(&spi->dev)) { + st->va_millivolt = ADC108S102_VA_MV_ACPI_DEFAULT; + } else { + ret = devm_regulator_get_enable_read_voltage(&spi->dev, "vref"); + if (ret < 0) + return dev_err_probe(&spi->dev, ret, "failed get vref voltage\n"); + + st->va_millivolt = ret / 1000; + } + + st->spi = spi; + + indio_dev->name = spi->modalias; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = adc108s102_channels; + indio_dev->num_channels = ARRAY_SIZE(adc108s102_channels); + indio_dev->info = &adc108s102_info; + + /* Setup default message */ + st->scan_single_xfer.tx_buf = st->tx_buf; + st->scan_single_xfer.rx_buf = st->rx_buf; + st->scan_single_xfer.len = 2 * sizeof(st->tx_buf[0]); + + spi_message_init_with_transfers(&st->scan_single_msg, + &st->scan_single_xfer, 1); + + ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, NULL, + &adc108s102_trigger_handler, + NULL); + if (ret) + return ret; + + ret = devm_iio_device_register(&spi->dev, indio_dev); + if (ret) + dev_err(&spi->dev, "Failed to register IIO device\n"); + return ret; +} + +static const struct of_device_id adc108s102_of_match[] = { + { .compatible = "ti,adc108s102" }, + { } +}; +MODULE_DEVICE_TABLE(of, adc108s102_of_match); + +static const struct acpi_device_id adc108s102_acpi_ids[] = { + { "INT3495", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, adc108s102_acpi_ids); + +static const struct spi_device_id adc108s102_id[] = { + { .name = "adc108s102" }, + { } +}; +MODULE_DEVICE_TABLE(spi, adc108s102_id); + +static struct spi_driver adc108s102_driver = { + .driver = { + .name = "adc108s102", + .of_match_table = adc108s102_of_match, + .acpi_match_table = adc108s102_acpi_ids, + }, + .probe = adc108s102_probe, + .id_table = adc108s102_id, +}; +module_spi_driver(adc108s102_driver); + +MODULE_AUTHOR("Bogdan Pricop "); +MODULE_DESCRIPTION("Texas Instruments ADC108S102 and ADC128S102 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ti-adc12138.c b/drivers/iio/adc/ti-adc12138.c new file mode 100644 index 000000000..279e4999e --- /dev/null +++ b/drivers/iio/adc/ti-adc12138.c @@ -0,0 +1,539 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADC12130/ADC12132/ADC12138 12-bit plus sign ADC driver + * + * Copyright (c) 2016 Akinobu Mita + * + * Datasheet: http://www.ti.com/lit/ds/symlink/adc12138.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ADC12138_MODE_AUTO_CAL 0x08 +#define ADC12138_MODE_READ_STATUS 0x0c +#define ADC12138_MODE_ACQUISITION_TIME_6 0x0e +#define ADC12138_MODE_ACQUISITION_TIME_10 0x4e +#define ADC12138_MODE_ACQUISITION_TIME_18 0x8e +#define ADC12138_MODE_ACQUISITION_TIME_34 0xce + +#define ADC12138_STATUS_CAL BIT(6) + +enum { + adc12130, + adc12132, + adc12138, +}; + +struct adc12138 { + struct spi_device *spi; + unsigned int id; + /* positive analog voltage reference */ + struct regulator *vref_p; + /* negative analog voltage reference */ + struct regulator *vref_n; + struct mutex lock; + struct completion complete; + /* The number of conversion clock periods for the S/H's acquisition time */ + unsigned int acquisition_time; + /* + * Maximum size needed: 16x 2 bytes ADC data + 8 bytes timestamp. + * Less may be need if not all channels are enabled, as long as + * the 8 byte alignment of the timestamp is maintained. + */ + __be16 data[20] __aligned(8); + + u8 tx_buf[2] __aligned(IIO_DMA_MINALIGN); + u8 rx_buf[2]; +}; + +#define ADC12138_VOLTAGE_CHANNEL(chan) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \ + | BIT(IIO_CHAN_INFO_OFFSET), \ + .scan_index = chan, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 13, \ + .storagebits = 16, \ + .shift = 3, \ + .endianness = IIO_BE, \ + }, \ + } + +#define ADC12138_VOLTAGE_CHANNEL_DIFF(chan1, chan2, si) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (chan1), \ + .channel2 = (chan2), \ + .differential = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \ + | BIT(IIO_CHAN_INFO_OFFSET), \ + .scan_index = si, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 13, \ + .storagebits = 16, \ + .shift = 3, \ + .endianness = IIO_BE, \ + }, \ + } + +static const struct iio_chan_spec adc12132_channels[] = { + ADC12138_VOLTAGE_CHANNEL(0), + ADC12138_VOLTAGE_CHANNEL(1), + ADC12138_VOLTAGE_CHANNEL_DIFF(0, 1, 2), + ADC12138_VOLTAGE_CHANNEL_DIFF(1, 0, 3), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const struct iio_chan_spec adc12138_channels[] = { + ADC12138_VOLTAGE_CHANNEL(0), + ADC12138_VOLTAGE_CHANNEL(1), + ADC12138_VOLTAGE_CHANNEL(2), + ADC12138_VOLTAGE_CHANNEL(3), + ADC12138_VOLTAGE_CHANNEL(4), + ADC12138_VOLTAGE_CHANNEL(5), + ADC12138_VOLTAGE_CHANNEL(6), + ADC12138_VOLTAGE_CHANNEL(7), + ADC12138_VOLTAGE_CHANNEL_DIFF(0, 1, 8), + ADC12138_VOLTAGE_CHANNEL_DIFF(1, 0, 9), + ADC12138_VOLTAGE_CHANNEL_DIFF(2, 3, 10), + ADC12138_VOLTAGE_CHANNEL_DIFF(3, 2, 11), + ADC12138_VOLTAGE_CHANNEL_DIFF(4, 5, 12), + ADC12138_VOLTAGE_CHANNEL_DIFF(5, 4, 13), + ADC12138_VOLTAGE_CHANNEL_DIFF(6, 7, 14), + ADC12138_VOLTAGE_CHANNEL_DIFF(7, 6, 15), + IIO_CHAN_SOFT_TIMESTAMP(16), +}; + +static int adc12138_mode_programming(struct adc12138 *adc, u8 mode, + void *rx_buf, int len) +{ + struct spi_transfer xfer = { + .tx_buf = adc->tx_buf, + .rx_buf = adc->rx_buf, + .len = len, + }; + int ret; + + /* Skip unused bits for ADC12130 and ADC12132 */ + if (adc->id != adc12138) + mode = (mode & 0xc0) | ((mode & 0x0f) << 2); + + adc->tx_buf[0] = mode; + + ret = spi_sync_transfer(adc->spi, &xfer, 1); + if (ret) + return ret; + + memcpy(rx_buf, adc->rx_buf, len); + + return 0; +} + +static int adc12138_read_status(struct adc12138 *adc) +{ + u8 rx_buf[2]; + int ret; + + ret = adc12138_mode_programming(adc, ADC12138_MODE_READ_STATUS, + rx_buf, 2); + if (ret) + return ret; + + return (rx_buf[0] << 1) | (rx_buf[1] >> 7); +} + +static int __adc12138_start_conv(struct adc12138 *adc, + struct iio_chan_spec const *channel, + void *data, int len) + +{ + static const u8 ch_to_mux[] = { 0, 4, 1, 5, 2, 6, 3, 7 }; + u8 mode = (ch_to_mux[channel->channel] << 4) | + (channel->differential ? 0 : 0x80); + + return adc12138_mode_programming(adc, mode, data, len); +} + +static int adc12138_start_conv(struct adc12138 *adc, + struct iio_chan_spec const *channel) +{ + u8 trash; + + return __adc12138_start_conv(adc, channel, &trash, 1); +} + +static int adc12138_start_and_read_conv(struct adc12138 *adc, + struct iio_chan_spec const *channel, + __be16 *data) +{ + return __adc12138_start_conv(adc, channel, data, 2); +} + +static int adc12138_read_conv_data(struct adc12138 *adc, __be16 *value) +{ + /* Issue a read status instruction and read previous conversion data */ + return adc12138_mode_programming(adc, ADC12138_MODE_READ_STATUS, + value, sizeof(*value)); +} + +static int adc12138_wait_eoc(struct adc12138 *adc, unsigned long timeout) +{ + if (!wait_for_completion_timeout(&adc->complete, timeout)) + return -ETIMEDOUT; + + return 0; +} + +static int adc12138_adc_conversion(struct adc12138 *adc, + struct iio_chan_spec const *channel, + __be16 *value) +{ + int ret; + + reinit_completion(&adc->complete); + + ret = adc12138_start_conv(adc, channel); + if (ret) + return ret; + + ret = adc12138_wait_eoc(adc, msecs_to_jiffies(100)); + if (ret) + return ret; + + return adc12138_read_conv_data(adc, value); +} + +static int adc12138_read_raw(struct iio_dev *iio, + struct iio_chan_spec const *channel, int *value, + int *shift, long mask) +{ + struct adc12138 *adc = iio_priv(iio); + int ret; + __be16 data; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&adc->lock); + ret = adc12138_adc_conversion(adc, channel, &data); + mutex_unlock(&adc->lock); + if (ret) + return ret; + + *value = sign_extend32(be16_to_cpu(data) >> channel->scan_type.shift, + channel->scan_type.realbits - 1); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = regulator_get_voltage(adc->vref_p); + if (ret < 0) + return ret; + *value = ret; + + if (!IS_ERR(adc->vref_n)) { + ret = regulator_get_voltage(adc->vref_n); + if (ret < 0) + return ret; + *value -= ret; + } + + /* convert regulator output voltage to mV */ + *value /= 1000; + *shift = channel->scan_type.realbits - 1; + + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: + if (!IS_ERR(adc->vref_n)) { + *value = regulator_get_voltage(adc->vref_n); + if (*value < 0) + return *value; + } else { + *value = 0; + } + + /* convert regulator output voltage to mV */ + *value /= 1000; + + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static const struct iio_info adc12138_info = { + .read_raw = adc12138_read_raw, +}; + +static int adc12138_init(struct adc12138 *adc) +{ + int ret; + int status; + u8 mode; + u8 trash; + + reinit_completion(&adc->complete); + + ret = adc12138_mode_programming(adc, ADC12138_MODE_AUTO_CAL, &trash, 1); + if (ret) + return ret; + + /* data output at this time has no significance */ + status = adc12138_read_status(adc); + if (status < 0) + return status; + + adc12138_wait_eoc(adc, msecs_to_jiffies(100)); + + status = adc12138_read_status(adc); + if (status & ADC12138_STATUS_CAL) { + dev_warn(&adc->spi->dev, + "Auto Cal sequence is still in progress: %#x\n", + status); + return -EIO; + } + + switch (adc->acquisition_time) { + case 6: + mode = ADC12138_MODE_ACQUISITION_TIME_6; + break; + case 10: + mode = ADC12138_MODE_ACQUISITION_TIME_10; + break; + case 18: + mode = ADC12138_MODE_ACQUISITION_TIME_18; + break; + case 34: + mode = ADC12138_MODE_ACQUISITION_TIME_34; + break; + default: + return -EINVAL; + } + + return adc12138_mode_programming(adc, mode, &trash, 1); +} + +static irqreturn_t adc12138_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adc12138 *adc = iio_priv(indio_dev); + __be16 trash; + int ret; + int scan_index; + int i = 0; + + mutex_lock(&adc->lock); + + iio_for_each_active_channel(indio_dev, scan_index) { + const struct iio_chan_spec *scan_chan = + &indio_dev->channels[scan_index]; + + reinit_completion(&adc->complete); + + ret = adc12138_start_and_read_conv(adc, scan_chan, + i ? &adc->data[i - 1] : &trash); + if (ret) { + dev_warn(&adc->spi->dev, + "failed to start conversion\n"); + goto out; + } + + ret = adc12138_wait_eoc(adc, msecs_to_jiffies(100)); + if (ret) { + dev_warn(&adc->spi->dev, "wait eoc timeout\n"); + goto out; + } + + i++; + } + + if (i) { + ret = adc12138_read_conv_data(adc, &adc->data[i - 1]); + if (ret) { + dev_warn(&adc->spi->dev, + "failed to get conversion data\n"); + goto out; + } + } + + iio_push_to_buffers_with_ts(indio_dev, adc->data, sizeof(adc->data), + iio_get_time_ns(indio_dev)); +out: + mutex_unlock(&adc->lock); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static irqreturn_t adc12138_eoc_handler(int irq, void *p) +{ + struct iio_dev *indio_dev = p; + struct adc12138 *adc = iio_priv(indio_dev); + + complete(&adc->complete); + + return IRQ_HANDLED; +} + +static int adc12138_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct adc12138 *adc; + struct clk *cclk; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->spi = spi; + adc->id = spi_get_device_id(spi)->driver_data; + mutex_init(&adc->lock); + init_completion(&adc->complete); + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &adc12138_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + switch (adc->id) { + case adc12130: + case adc12132: + indio_dev->channels = adc12132_channels; + indio_dev->num_channels = ARRAY_SIZE(adc12132_channels); + break; + case adc12138: + indio_dev->channels = adc12138_channels; + indio_dev->num_channels = ARRAY_SIZE(adc12138_channels); + break; + default: + return -EINVAL; + } + + ret = device_property_read_u32(&spi->dev, "ti,acquisition-time", + &adc->acquisition_time); + if (ret) + adc->acquisition_time = 10; + + ret = devm_request_irq(&spi->dev, spi->irq, adc12138_eoc_handler, + IRQF_TRIGGER_RISING, indio_dev->name, indio_dev); + if (ret) + return ret; + + cclk = devm_clk_get_enabled(&spi->dev, NULL); + if (IS_ERR(cclk)) + return PTR_ERR(cclk); + + adc->vref_p = devm_regulator_get(&spi->dev, "vref-p"); + if (IS_ERR(adc->vref_p)) + return PTR_ERR(adc->vref_p); + + adc->vref_n = devm_regulator_get_optional(&spi->dev, "vref-n"); + if (IS_ERR(adc->vref_n)) { + /* + * Assume vref_n is 0V if an optional regulator is not + * specified, otherwise return the error code. + */ + ret = PTR_ERR(adc->vref_n); + if (ret != -ENODEV) + return ret; + } + + ret = regulator_enable(adc->vref_p); + if (ret) + return ret; + + if (!IS_ERR(adc->vref_n)) { + ret = regulator_enable(adc->vref_n); + if (ret) + goto err_vref_p_disable; + } + + ret = adc12138_init(adc); + if (ret) + goto err_vref_n_disable; + + spi_set_drvdata(spi, indio_dev); + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + adc12138_trigger_handler, NULL); + if (ret) + goto err_vref_n_disable; + + ret = iio_device_register(indio_dev); + if (ret) + goto err_buffer_cleanup; + + return 0; +err_buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); +err_vref_n_disable: + if (!IS_ERR(adc->vref_n)) + regulator_disable(adc->vref_n); +err_vref_p_disable: + regulator_disable(adc->vref_p); + + return ret; +} + +static void adc12138_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct adc12138 *adc = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + if (!IS_ERR(adc->vref_n)) + regulator_disable(adc->vref_n); + regulator_disable(adc->vref_p); +} + +static const struct of_device_id adc12138_dt_ids[] = { + { .compatible = "ti,adc12130", }, + { .compatible = "ti,adc12132", }, + { .compatible = "ti,adc12138", }, + { } +}; +MODULE_DEVICE_TABLE(of, adc12138_dt_ids); + +static const struct spi_device_id adc12138_id[] = { + { .name = "adc12130", .driver_data = adc12130 }, + { .name = "adc12132", .driver_data = adc12132 }, + { .name = "adc12138", .driver_data = adc12138 }, + { } +}; +MODULE_DEVICE_TABLE(spi, adc12138_id); + +static struct spi_driver adc12138_driver = { + .driver = { + .name = "adc12138", + .of_match_table = adc12138_dt_ids, + }, + .probe = adc12138_probe, + .remove = adc12138_remove, + .id_table = adc12138_id, +}; +module_spi_driver(adc12138_driver); + +MODULE_AUTHOR("Akinobu Mita "); +MODULE_DESCRIPTION("ADC12130/ADC12132/ADC12138 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ti-adc128s052.c b/drivers/iio/adc/ti-adc128s052.c new file mode 100644 index 000000000..1899813c1 --- /dev/null +++ b/drivers/iio/adc/ti-adc128s052.c @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2014 Angelo Compagnucci + * + * Driver for Texas Instruments' ADC128S052, ADC122S021 and ADC124S021 ADC chip. + * Datasheets can be found here: + * https://www.ti.com/lit/ds/symlink/adc128s052.pdf + * https://www.ti.com/lit/ds/symlink/adc122s021.pdf + * https://www.ti.com/lit/ds/symlink/adc124s021.pdf + */ + +#include +#include +#include +#include +#include +#include +#include + +struct adc128_configuration { + const struct iio_chan_spec *channels; + u8 num_channels; + const char *refname; + int num_other_regulators; + const char * const (*other_regulators)[]; +}; + +struct adc128 { + struct spi_device *spi; + + /* + * Serialize the SPI 'write-channel + read data' accesses and protect + * the shared buffer. + */ + struct mutex lock; + int vref_mv; + union { + __be16 buffer16; + u8 buffer[2]; + } __aligned(IIO_DMA_MINALIGN); +}; + +static int adc128_adc_conversion(struct adc128 *adc, u8 channel) +{ + int ret; + + guard(mutex)(&adc->lock); + + adc->buffer[0] = channel << 3; + adc->buffer[1] = 0; + + ret = spi_write(adc->spi, &adc->buffer, sizeof(adc->buffer)); + if (ret < 0) + return ret; + + ret = spi_read(adc->spi, &adc->buffer16, sizeof(adc->buffer16)); + if (ret < 0) + return ret; + + return be16_to_cpu(adc->buffer16) & 0xFFF; +} + +static int adc128_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct adc128 *adc = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + + ret = adc128_adc_conversion(adc, channel->channel); + if (ret < 0) + return ret; + + *val = ret; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + + *val = adc->vref_mv; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } + +} + +#define ADC128_VOLTAGE_CHANNEL(num) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (num), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \ + } + +static const struct iio_chan_spec simple_1chan_adc_channels[] = { + ADC128_VOLTAGE_CHANNEL(0), +}; + +static const struct iio_chan_spec simple_2chan_adc_channels[] = { + ADC128_VOLTAGE_CHANNEL(0), + ADC128_VOLTAGE_CHANNEL(1), +}; + +static const struct iio_chan_spec simple_4chan_adc_channels[] = { + ADC128_VOLTAGE_CHANNEL(0), + ADC128_VOLTAGE_CHANNEL(1), + ADC128_VOLTAGE_CHANNEL(2), + ADC128_VOLTAGE_CHANNEL(3), +}; + +static const struct iio_chan_spec simple_8chan_adc_channels[] = { + ADC128_VOLTAGE_CHANNEL(0), + ADC128_VOLTAGE_CHANNEL(1), + ADC128_VOLTAGE_CHANNEL(2), + ADC128_VOLTAGE_CHANNEL(3), + ADC128_VOLTAGE_CHANNEL(4), + ADC128_VOLTAGE_CHANNEL(5), + ADC128_VOLTAGE_CHANNEL(6), + ADC128_VOLTAGE_CHANNEL(7), +}; + +static const char * const bd79104_regulators[] = { "iovdd" }; + +static const struct adc128_configuration adc122s_config = { + .channels = simple_2chan_adc_channels, + .num_channels = ARRAY_SIZE(simple_2chan_adc_channels), + .refname = "vref", +}; + +static const struct adc128_configuration adc124s_config = { + .channels = simple_4chan_adc_channels, + .num_channels = ARRAY_SIZE(simple_4chan_adc_channels), + .refname = "vref", +}; + +static const struct adc128_configuration adc128s_config = { + .channels = simple_8chan_adc_channels, + .num_channels = ARRAY_SIZE(simple_8chan_adc_channels), + .refname = "vref", +}; + +static const struct adc128_configuration bd79100_config = { + .channels = simple_1chan_adc_channels, + .num_channels = ARRAY_SIZE(simple_1chan_adc_channels), + .refname = "vdd", + .other_regulators = &bd79104_regulators, + .num_other_regulators = 1, +}; + +static const struct adc128_configuration bd79101_config = { + .channels = simple_2chan_adc_channels, + .num_channels = ARRAY_SIZE(simple_2chan_adc_channels), + .refname = "vdd", + .other_regulators = &bd79104_regulators, + .num_other_regulators = 1, +}; + +static const struct adc128_configuration bd79102_config = { + .channels = simple_4chan_adc_channels, + .num_channels = ARRAY_SIZE(simple_4chan_adc_channels), + .refname = "vdd", + .other_regulators = &bd79104_regulators, + .num_other_regulators = 1, +}; + +static const struct adc128_configuration bd79104_config = { + .channels = simple_8chan_adc_channels, + .num_channels = ARRAY_SIZE(simple_8chan_adc_channels), + .refname = "vdd", + .other_regulators = &bd79104_regulators, + .num_other_regulators = 1, +}; + +static const struct iio_info adc128_info = { + .read_raw = adc128_read_raw, +}; + +static int adc128_probe(struct spi_device *spi) +{ + const struct adc128_configuration *config; + struct iio_dev *indio_dev; + struct adc128 *adc; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->spi = spi; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &adc128_info; + + config = spi_get_device_match_data(spi); + + indio_dev->channels = config->channels; + indio_dev->num_channels = config->num_channels; + + ret = devm_regulator_get_enable_read_voltage(&spi->dev, + config->refname); + if (ret < 0) + return dev_err_probe(&spi->dev, ret, + "failed to read '%s' voltage", + config->refname); + + adc->vref_mv = ret / 1000; + + if (config->num_other_regulators) { + ret = devm_regulator_bulk_get_enable(&spi->dev, + config->num_other_regulators, + *config->other_regulators); + if (ret) + return dev_err_probe(&spi->dev, ret, + "Failed to enable regulators\n"); + } + + ret = devm_mutex_init(&spi->dev, &adc->lock); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct of_device_id adc128_of_match[] = { + { .compatible = "ti,adc128s052", .data = &adc128s_config }, + { .compatible = "ti,adc122s021", .data = &adc122s_config }, + { .compatible = "ti,adc122s051", .data = &adc122s_config }, + { .compatible = "ti,adc122s101", .data = &adc122s_config }, + { .compatible = "ti,adc124s021", .data = &adc124s_config }, + { .compatible = "ti,adc124s051", .data = &adc124s_config }, + { .compatible = "ti,adc124s101", .data = &adc124s_config }, + { .compatible = "rohm,bd79100", .data = &bd79100_config }, + { .compatible = "rohm,bd79101", .data = &bd79101_config }, + { .compatible = "rohm,bd79102", .data = &bd79102_config }, + { .compatible = "rohm,bd79103", .data = &bd79104_config }, + { .compatible = "rohm,bd79104", .data = &bd79104_config }, + { } +}; +MODULE_DEVICE_TABLE(of, adc128_of_match); + +static const struct spi_device_id adc128_id[] = { + { .name = "adc128s052", .driver_data = (kernel_ulong_t)&adc128s_config }, + { .name = "adc122s021", .driver_data = (kernel_ulong_t)&adc122s_config }, + { .name = "adc122s051", .driver_data = (kernel_ulong_t)&adc122s_config }, + { .name = "adc122s101", .driver_data = (kernel_ulong_t)&adc122s_config }, + { .name = "adc124s021", .driver_data = (kernel_ulong_t)&adc124s_config }, + { .name = "adc124s051", .driver_data = (kernel_ulong_t)&adc124s_config }, + { .name = "adc124s101", .driver_data = (kernel_ulong_t)&adc124s_config }, + { .name = "bd79100", .driver_data = (kernel_ulong_t)&bd79100_config }, + { .name = "bd79101", .driver_data = (kernel_ulong_t)&bd79101_config }, + { .name = "bd79102", .driver_data = (kernel_ulong_t)&bd79102_config }, + { .name = "bd79103", .driver_data = (kernel_ulong_t)&bd79104_config }, + { .name = "bd79104", .driver_data = (kernel_ulong_t)&bd79104_config }, + { } +}; +MODULE_DEVICE_TABLE(spi, adc128_id); + +static const struct acpi_device_id adc128_acpi_match[] = { + { "AANT1280", (kernel_ulong_t)&adc124s_config }, + { } +}; +MODULE_DEVICE_TABLE(acpi, adc128_acpi_match); + +static struct spi_driver adc128_driver = { + .driver = { + .name = "adc128s052", + .of_match_table = adc128_of_match, + .acpi_match_table = adc128_acpi_match, + }, + .probe = adc128_probe, + .id_table = adc128_id, +}; +module_spi_driver(adc128_driver); + +MODULE_AUTHOR("Angelo Compagnucci "); +MODULE_DESCRIPTION("Texas Instruments ADC128S052"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ti-adc161s626.c b/drivers/iio/adc/ti-adc161s626.c new file mode 100644 index 000000000..422df0685 --- /dev/null +++ b/drivers/iio/adc/ti-adc161s626.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ti-adc161s626.c - Texas Instruments ADC161S626 1-channel differential ADC + * + * ADC Devices Supported: + * adc141s626 - 14-bit ADC + * adc161s626 - 16-bit ADC + * + * Copyright (C) 2016-2018 + * Author: Matt Ranostay + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TI_ADC_DRV_NAME "ti-adc161s626" + +enum { + TI_ADC141S626, + TI_ADC161S626, +}; + +static const struct iio_chan_spec ti_adc141s626_channels[] = { + { + .type = IIO_VOLTAGE, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 14, + .storagebits = 16, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct iio_chan_spec ti_adc161s626_channels[] = { + { + .type = IIO_VOLTAGE, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +struct ti_adc_data { + struct iio_dev *indio_dev; + struct spi_device *spi; + struct regulator *ref; + + u8 read_size; + u8 shift; + u8 buf[3] __aligned(IIO_DMA_MINALIGN); +}; + +static int ti_adc_read_measurement(struct ti_adc_data *data, + struct iio_chan_spec const *chan, int *val) +{ + int ret; + + switch (data->read_size) { + case 2: + ret = spi_read(data->spi, data->buf, 2); + if (ret) + return ret; + + *val = get_unaligned_be16(data->buf); + break; + case 3: + ret = spi_read(data->spi, data->buf, 3); + if (ret) + return ret; + + *val = get_unaligned_be24(data->buf); + break; + default: + return -EINVAL; + } + + *val = sign_extend32(*val >> data->shift, chan->scan_type.realbits - 1); + + return 0; +} + +static irqreturn_t ti_adc_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct ti_adc_data *data = iio_priv(indio_dev); + struct { + s16 data; + aligned_s64 timestamp; + } scan = { }; + int ret, val; + + ret = ti_adc_read_measurement(data, &indio_dev->channels[0], &val); + if (ret) + goto exit_notify_done; + + scan.data = val; + iio_push_to_buffers_with_timestamp(indio_dev, &scan, iio_get_time_ns(indio_dev)); + + exit_notify_done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ti_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ti_adc_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = ti_adc_read_measurement(data, chan, val); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = regulator_get_voltage(data->ref); + if (ret < 0) + return ret; + + *val = ret / 1000; + *val2 = chan->scan_type.realbits; + + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: + *val = 1 << (chan->scan_type.realbits - 1); + return IIO_VAL_INT; + } + + return 0; +} + +static const struct iio_info ti_adc_info = { + .read_raw = ti_adc_read_raw, +}; + +static void ti_adc_reg_disable(void *reg) +{ + regulator_disable(reg); +} + +static int ti_adc_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct ti_adc_data *data; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + indio_dev->info = &ti_adc_info; + indio_dev->name = TI_ADC_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + data = iio_priv(indio_dev); + data->spi = spi; + + switch (spi_get_device_id(spi)->driver_data) { + case TI_ADC141S626: + indio_dev->channels = ti_adc141s626_channels; + indio_dev->num_channels = ARRAY_SIZE(ti_adc141s626_channels); + data->shift = 0; + data->read_size = 2; + break; + case TI_ADC161S626: + indio_dev->channels = ti_adc161s626_channels; + indio_dev->num_channels = ARRAY_SIZE(ti_adc161s626_channels); + data->shift = 6; + data->read_size = 3; + break; + } + + data->ref = devm_regulator_get(&spi->dev, "vdda"); + if (IS_ERR(data->ref)) + return PTR_ERR(data->ref); + + ret = regulator_enable(data->ref); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(&spi->dev, ti_adc_reg_disable, + data->ref); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, NULL, + ti_adc_trigger_handler, NULL); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct of_device_id ti_adc_dt_ids[] = { + { .compatible = "ti,adc141s626", }, + { .compatible = "ti,adc161s626", }, + { } +}; +MODULE_DEVICE_TABLE(of, ti_adc_dt_ids); + +static const struct spi_device_id ti_adc_id[] = { + { .name = "adc141s626", .driver_data = TI_ADC141S626 }, + { .name = "adc161s626", .driver_data = TI_ADC161S626 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ti_adc_id); + +static struct spi_driver ti_adc_driver = { + .driver = { + .name = TI_ADC_DRV_NAME, + .of_match_table = ti_adc_dt_ids, + }, + .probe = ti_adc_probe, + .id_table = ti_adc_id, +}; +module_spi_driver(ti_adc_driver); + +MODULE_AUTHOR("Matt Ranostay "); +MODULE_DESCRIPTION("Texas Instruments ADC1x1S 1-channel differential ADC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/ti-ads1015.c b/drivers/iio/adc/ti-ads1015.c new file mode 100644 index 000000000..0fbfa4e49 --- /dev/null +++ b/drivers/iio/adc/ti-ads1015.c @@ -0,0 +1,1153 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADS1015 - Texas Instruments Analog-to-Digital Converter + * + * Copyright (c) 2016, Intel Corporation. + * + * IIO driver for ADS1015 ADC 7-bit I2C slave address: + * * 0x48 - ADDR connected to Ground + * * 0x49 - ADDR connected to Vdd + * * 0x4A - ADDR connected to SDA + * * 0x4B - ADDR connected to SCL + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define ADS1015_DRV_NAME "ads1015" + +#define ADS1015_CHANNELS 8 + +#define ADS1015_CONV_REG 0x00 +#define ADS1015_CFG_REG 0x01 +#define ADS1015_LO_THRESH_REG 0x02 +#define ADS1015_HI_THRESH_REG 0x03 + +#define ADS1015_CFG_COMP_QUE_SHIFT 0 +#define ADS1015_CFG_COMP_LAT_SHIFT 2 +#define ADS1015_CFG_COMP_POL_SHIFT 3 +#define ADS1015_CFG_COMP_MODE_SHIFT 4 +#define ADS1015_CFG_DR_SHIFT 5 +#define ADS1015_CFG_MOD_SHIFT 8 +#define ADS1015_CFG_PGA_SHIFT 9 +#define ADS1015_CFG_MUX_SHIFT 12 + +#define ADS1015_CFG_COMP_QUE_MASK GENMASK(1, 0) +#define ADS1015_CFG_COMP_LAT_MASK BIT(2) +#define ADS1015_CFG_COMP_POL_MASK BIT(3) +#define ADS1015_CFG_COMP_MODE_MASK BIT(4) +#define ADS1015_CFG_DR_MASK GENMASK(7, 5) +#define ADS1015_CFG_MOD_MASK BIT(8) +#define ADS1015_CFG_PGA_MASK GENMASK(11, 9) +#define ADS1015_CFG_MUX_MASK GENMASK(14, 12) + +/* Comparator queue and disable field */ +#define ADS1015_CFG_COMP_DISABLE 3 + +/* Comparator polarity field */ +#define ADS1015_CFG_COMP_POL_LOW 0 +#define ADS1015_CFG_COMP_POL_HIGH 1 + +/* Comparator mode field */ +#define ADS1015_CFG_COMP_MODE_TRAD 0 +#define ADS1015_CFG_COMP_MODE_WINDOW 1 + +/* device operating modes */ +#define ADS1015_CONTINUOUS 0 +#define ADS1015_SINGLESHOT 1 + +#define ADS1015_SLEEP_DELAY_MS 2000 +#define ADS1015_DEFAULT_PGA 2 +#define ADS1015_DEFAULT_DATA_RATE 4 +#define ADS1015_DEFAULT_CHAN 0 + +struct ads1015_chip_data { + struct iio_chan_spec const *channels; + int num_channels; + const struct iio_info *info; + const int *data_rate; + const int data_rate_len; + const int *scale; + const int scale_len; + bool has_comparator; +}; + +enum ads1015_channels { + ADS1015_AIN0_AIN1 = 0, + ADS1015_AIN0_AIN3, + ADS1015_AIN1_AIN3, + ADS1015_AIN2_AIN3, + ADS1015_AIN0, + ADS1015_AIN1, + ADS1015_AIN2, + ADS1015_AIN3, + ADS1015_TIMESTAMP, +}; + +static const int ads1015_data_rate[] = { + 128, 250, 490, 920, 1600, 2400, 3300, 3300 +}; + +static const int ads1115_data_rate[] = { + 8, 16, 32, 64, 128, 250, 475, 860 +}; + +/* + * Translation from PGA bits to full-scale positive and negative input voltage + * range in mV + */ +static const int ads1015_fullscale_range[] = { + 6144, 4096, 2048, 1024, 512, 256, 256, 256 +}; + +static const int ads1015_scale[] = { /* 12bit ADC */ + 256, 11, + 512, 11, + 1024, 11, + 2048, 11, + 4096, 11, + 6144, 11 +}; + +static const int ads1115_scale[] = { /* 16bit ADC */ + 256, 15, + 512, 15, + 1024, 15, + 2048, 15, + 4096, 15, + 6144, 15 +}; + +/* + * Translation from COMP_QUE field value to the number of successive readings + * exceed the threshold values before an interrupt is generated + */ +static const int ads1015_comp_queue[] = { 1, 2, 4 }; + +static const struct iio_event_spec ads1015_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_PERIOD), + }, +}; + +/* + * Compile-time check whether _fitbits can accommodate up to _testbits + * bits. Returns _fitbits on success, fails to compile otherwise. + * + * The test works such that it multiplies constant _fitbits by constant + * double-negation of size of a non-empty structure, i.e. it multiplies + * constant _fitbits by constant 1 in each successful compilation case. + * The non-empty structure may contain C11 _Static_assert(), make use of + * this and place the kernel variant of static assert in there, so that + * it performs the compile-time check for _testbits <= _fitbits. Note + * that it is not possible to directly use static_assert in compound + * statements, hence this convoluted construct. + */ +#define FIT_CHECK(_testbits, _fitbits) \ + ( \ + (_fitbits) * \ + !!sizeof(struct { \ + static_assert((_testbits) <= (_fitbits)); \ + int pad; \ + }) \ + ) + +#define ADS1015_V_CHAN(_chan, _addr, _realbits, _shift, _event_spec, _num_event_specs) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .address = _addr, \ + .channel = _chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = _addr, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (_realbits), \ + .storagebits = FIT_CHECK((_realbits) + (_shift), 16), \ + .shift = (_shift), \ + .endianness = IIO_CPU, \ + }, \ + .event_spec = (_event_spec), \ + .num_event_specs = (_num_event_specs), \ + .datasheet_name = "AIN"#_chan, \ +} + +#define ADS1015_V_DIFF_CHAN(_chan, _chan2, _addr, _realbits, _shift, _event_spec, _num_event_specs) { \ + .type = IIO_VOLTAGE, \ + .differential = 1, \ + .indexed = 1, \ + .address = _addr, \ + .channel = _chan, \ + .channel2 = _chan2, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = _addr, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (_realbits), \ + .storagebits = FIT_CHECK((_realbits) + (_shift), 16), \ + .shift = (_shift), \ + .endianness = IIO_CPU, \ + }, \ + .event_spec = (_event_spec), \ + .num_event_specs = (_num_event_specs), \ + .datasheet_name = "AIN"#_chan"-AIN"#_chan2, \ +} + +struct ads1015_channel_data { + unsigned int pga; + unsigned int data_rate; +}; + +struct ads1015_thresh_data { + unsigned int comp_queue; + int high_thresh; + int low_thresh; +}; + +struct ads1015_data { + struct regmap *regmap; + /* + * Protects ADC ops, e.g: concurrent sysfs/buffered + * data reads, configuration updates + */ + struct mutex lock; + struct ads1015_channel_data channel_data[ADS1015_CHANNELS]; + + unsigned int event_channel; + unsigned int comp_mode; + struct ads1015_thresh_data thresh_data[ADS1015_CHANNELS]; + + const struct ads1015_chip_data *chip; + /* + * Set to true when the ADC is switched to the continuous-conversion + * mode and exits from a power-down state. This flag is used to avoid + * getting the stale result from the conversion register. + */ + bool conv_invalid; +}; + +static bool ads1015_event_channel_enabled(struct ads1015_data *data) +{ + return (data->event_channel != ADS1015_CHANNELS); +} + +static void ads1015_event_channel_enable(struct ads1015_data *data, int chan, + int comp_mode) +{ + WARN_ON(ads1015_event_channel_enabled(data)); + + data->event_channel = chan; + data->comp_mode = comp_mode; +} + +static void ads1015_event_channel_disable(struct ads1015_data *data, int chan) +{ + data->event_channel = ADS1015_CHANNELS; +} + +static const struct regmap_range ads1015_writeable_ranges[] = { + regmap_reg_range(ADS1015_CFG_REG, ADS1015_HI_THRESH_REG), +}; + +static const struct regmap_access_table ads1015_writeable_table = { + .yes_ranges = ads1015_writeable_ranges, + .n_yes_ranges = ARRAY_SIZE(ads1015_writeable_ranges), +}; + +static const struct regmap_config ads1015_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = ADS1015_HI_THRESH_REG, + .wr_table = &ads1015_writeable_table, +}; + +static const struct regmap_range tla2024_writeable_ranges[] = { + regmap_reg_range(ADS1015_CFG_REG, ADS1015_CFG_REG), +}; + +static const struct regmap_access_table tla2024_writeable_table = { + .yes_ranges = tla2024_writeable_ranges, + .n_yes_ranges = ARRAY_SIZE(tla2024_writeable_ranges), +}; + +static const struct regmap_config tla2024_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = ADS1015_CFG_REG, + .wr_table = &tla2024_writeable_table, +}; + +static const struct iio_chan_spec ads1015_channels[] = { + ADS1015_V_DIFF_CHAN(0, 1, ADS1015_AIN0_AIN1, 12, 4, + ads1015_events, ARRAY_SIZE(ads1015_events)), + ADS1015_V_DIFF_CHAN(0, 3, ADS1015_AIN0_AIN3, 12, 4, + ads1015_events, ARRAY_SIZE(ads1015_events)), + ADS1015_V_DIFF_CHAN(1, 3, ADS1015_AIN1_AIN3, 12, 4, + ads1015_events, ARRAY_SIZE(ads1015_events)), + ADS1015_V_DIFF_CHAN(2, 3, ADS1015_AIN2_AIN3, 12, 4, + ads1015_events, ARRAY_SIZE(ads1015_events)), + ADS1015_V_CHAN(0, ADS1015_AIN0, 12, 4, + ads1015_events, ARRAY_SIZE(ads1015_events)), + ADS1015_V_CHAN(1, ADS1015_AIN1, 12, 4, + ads1015_events, ARRAY_SIZE(ads1015_events)), + ADS1015_V_CHAN(2, ADS1015_AIN2, 12, 4, + ads1015_events, ARRAY_SIZE(ads1015_events)), + ADS1015_V_CHAN(3, ADS1015_AIN3, 12, 4, + ads1015_events, ARRAY_SIZE(ads1015_events)), + IIO_CHAN_SOFT_TIMESTAMP(ADS1015_TIMESTAMP), +}; + +static const struct iio_chan_spec ads1115_channels[] = { + ADS1015_V_DIFF_CHAN(0, 1, ADS1015_AIN0_AIN1, 16, 0, + ads1015_events, ARRAY_SIZE(ads1015_events)), + ADS1015_V_DIFF_CHAN(0, 3, ADS1015_AIN0_AIN3, 16, 0, + ads1015_events, ARRAY_SIZE(ads1015_events)), + ADS1015_V_DIFF_CHAN(1, 3, ADS1015_AIN1_AIN3, 16, 0, + ads1015_events, ARRAY_SIZE(ads1015_events)), + ADS1015_V_DIFF_CHAN(2, 3, ADS1015_AIN2_AIN3, 16, 0, + ads1015_events, ARRAY_SIZE(ads1015_events)), + ADS1015_V_CHAN(0, ADS1015_AIN0, 16, 0, + ads1015_events, ARRAY_SIZE(ads1015_events)), + ADS1015_V_CHAN(1, ADS1015_AIN1, 16, 0, + ads1015_events, ARRAY_SIZE(ads1015_events)), + ADS1015_V_CHAN(2, ADS1015_AIN2, 16, 0, + ads1015_events, ARRAY_SIZE(ads1015_events)), + ADS1015_V_CHAN(3, ADS1015_AIN3, 16, 0, + ads1015_events, ARRAY_SIZE(ads1015_events)), + IIO_CHAN_SOFT_TIMESTAMP(ADS1015_TIMESTAMP), +}; + +static const struct iio_chan_spec tla2024_channels[] = { + ADS1015_V_DIFF_CHAN(0, 1, ADS1015_AIN0_AIN1, 12, 4, NULL, 0), + ADS1015_V_DIFF_CHAN(0, 3, ADS1015_AIN0_AIN3, 12, 4, NULL, 0), + ADS1015_V_DIFF_CHAN(1, 3, ADS1015_AIN1_AIN3, 12, 4, NULL, 0), + ADS1015_V_DIFF_CHAN(2, 3, ADS1015_AIN2_AIN3, 12, 4, NULL, 0), + ADS1015_V_CHAN(0, ADS1015_AIN0, 12, 4, NULL, 0), + ADS1015_V_CHAN(1, ADS1015_AIN1, 12, 4, NULL, 0), + ADS1015_V_CHAN(2, ADS1015_AIN2, 12, 4, NULL, 0), + ADS1015_V_CHAN(3, ADS1015_AIN3, 12, 4, NULL, 0), + IIO_CHAN_SOFT_TIMESTAMP(ADS1015_TIMESTAMP), +}; + + +#ifdef CONFIG_PM +static int ads1015_set_power_state(struct ads1015_data *data, bool on) +{ + int ret; + struct device *dev = regmap_get_device(data->regmap); + + if (on) + ret = pm_runtime_resume_and_get(dev); + else + ret = pm_runtime_put_autosuspend(dev); + + return ret < 0 ? ret : 0; +} + +#else /* !CONFIG_PM */ + +static int ads1015_set_power_state(struct ads1015_data *data, bool on) +{ + return 0; +} + +#endif /* !CONFIG_PM */ + +static +int ads1015_get_adc_result(struct ads1015_data *data, int chan, int *val) +{ + const int *data_rate = data->chip->data_rate; + int ret, pga, dr, dr_old, conv_time; + unsigned int old, mask, cfg; + + if (chan < 0 || chan >= ADS1015_CHANNELS) + return -EINVAL; + + ret = regmap_read(data->regmap, ADS1015_CFG_REG, &old); + if (ret) + return ret; + + pga = data->channel_data[chan].pga; + dr = data->channel_data[chan].data_rate; + mask = ADS1015_CFG_MUX_MASK | ADS1015_CFG_PGA_MASK | + ADS1015_CFG_DR_MASK; + cfg = chan << ADS1015_CFG_MUX_SHIFT | pga << ADS1015_CFG_PGA_SHIFT | + dr << ADS1015_CFG_DR_SHIFT; + + if (ads1015_event_channel_enabled(data)) { + mask |= ADS1015_CFG_COMP_QUE_MASK | ADS1015_CFG_COMP_MODE_MASK; + cfg |= data->thresh_data[chan].comp_queue << + ADS1015_CFG_COMP_QUE_SHIFT | + data->comp_mode << + ADS1015_CFG_COMP_MODE_SHIFT; + } + + cfg = (old & ~mask) | (cfg & mask); + if (old != cfg) { + ret = regmap_write(data->regmap, ADS1015_CFG_REG, cfg); + if (ret) + return ret; + data->conv_invalid = true; + } + if (data->conv_invalid) { + dr_old = (old & ADS1015_CFG_DR_MASK) >> ADS1015_CFG_DR_SHIFT; + conv_time = DIV_ROUND_UP(USEC_PER_SEC, data_rate[dr_old]); + conv_time += DIV_ROUND_UP(USEC_PER_SEC, data_rate[dr]); + conv_time += conv_time / 10; /* 10% internal clock inaccuracy */ + usleep_range(conv_time, conv_time + 1); + data->conv_invalid = false; + } + + return regmap_read(data->regmap, ADS1015_CONV_REG, val); +} + +static irqreturn_t ads1015_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ads1015_data *data = iio_priv(indio_dev); + /* Ensure natural alignment of timestamp */ + struct { + s16 chan; + aligned_s64 timestamp; + } scan = { }; + int chan, ret, res; + + mutex_lock(&data->lock); + chan = find_first_bit(indio_dev->active_scan_mask, + iio_get_masklength(indio_dev)); + ret = ads1015_get_adc_result(data, chan, &res); + if (ret < 0) { + mutex_unlock(&data->lock); + goto err; + } + + scan.chan = res; + mutex_unlock(&data->lock); + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); + +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ads1015_set_scale(struct ads1015_data *data, + struct iio_chan_spec const *chan, + int scale, int uscale) +{ + int i; + int fullscale = div_s64((scale * 1000000LL + uscale) << + (chan->scan_type.realbits - 1), 1000000); + + for (i = 0; i < ARRAY_SIZE(ads1015_fullscale_range); i++) { + if (ads1015_fullscale_range[i] == fullscale) { + data->channel_data[chan->address].pga = i; + return 0; + } + } + + return -EINVAL; +} + +static int ads1015_set_data_rate(struct ads1015_data *data, int chan, int rate) +{ + int i; + + for (i = 0; i < data->chip->data_rate_len; i++) { + if (data->chip->data_rate[i] == rate) { + data->channel_data[chan].data_rate = i; + return 0; + } + } + + return -EINVAL; +} + +static int ads1015_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct ads1015_data *data = iio_priv(indio_dev); + + if (chan->type != IIO_VOLTAGE) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *type = IIO_VAL_FRACTIONAL_LOG2; + *vals = data->chip->scale; + *length = data->chip->scale_len; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *type = IIO_VAL_INT; + *vals = data->chip->data_rate; + *length = data->chip->data_rate_len; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int __ads1015_read_info_raw(struct ads1015_data *data, + struct iio_chan_spec const *chan, int *val) +{ + int ret; + + if (ads1015_event_channel_enabled(data) && + data->event_channel != chan->address) + return -EBUSY; + + ret = ads1015_set_power_state(data, true); + if (ret < 0) + return ret; + + ret = ads1015_get_adc_result(data, chan->address, val); + if (ret < 0) { + ads1015_set_power_state(data, false); + return ret; + } + + *val = sign_extend32(*val >> chan->scan_type.shift, + chan->scan_type.realbits - 1); + + return ads1015_set_power_state(data, false); +} + +static int ads1015_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int ret, idx; + struct ads1015_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = __ads1015_read_info_raw(data, chan, val); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + idx = data->channel_data[chan->address].pga; + *val = ads1015_fullscale_range[idx]; + *val2 = chan->scan_type.realbits - 1; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_SAMP_FREQ: + idx = data->channel_data[chan->address].data_rate; + *val = data->chip->data_rate[idx]; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ads1015_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct ads1015_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return ads1015_set_scale(data, chan, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return ads1015_set_data_rate(data, chan->address, val); + default: + return -EINVAL; + } +} + +static int ads1015_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, int *val, + int *val2) +{ + struct ads1015_data *data = iio_priv(indio_dev); + unsigned int comp_queue; + int period; + int dr; + + guard(mutex)(&data->lock); + + switch (info) { + case IIO_EV_INFO_VALUE: + *val = (dir == IIO_EV_DIR_RISING) ? + data->thresh_data[chan->address].high_thresh : + data->thresh_data[chan->address].low_thresh; + return IIO_VAL_INT; + case IIO_EV_INFO_PERIOD: + dr = data->channel_data[chan->address].data_rate; + comp_queue = data->thresh_data[chan->address].comp_queue; + period = ads1015_comp_queue[comp_queue] * + USEC_PER_SEC / data->chip->data_rate[dr]; + + *val = period / USEC_PER_SEC; + *val2 = period % USEC_PER_SEC; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int ads1015_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, int val, + int val2) +{ + struct ads1015_data *data = iio_priv(indio_dev); + const int *data_rate = data->chip->data_rate; + int realbits = chan->scan_type.realbits; + long long period; + int i; + int dr; + + guard(mutex)(&data->lock); + + switch (info) { + case IIO_EV_INFO_VALUE: + if (val >= 1 << (realbits - 1) || val < -1 << (realbits - 1)) + return -EINVAL; + + if (dir == IIO_EV_DIR_RISING) + data->thresh_data[chan->address].high_thresh = val; + else + data->thresh_data[chan->address].low_thresh = val; + return 0; + case IIO_EV_INFO_PERIOD: + dr = data->channel_data[chan->address].data_rate; + period = val * USEC_PER_SEC + val2; + + for (i = 0; i < ARRAY_SIZE(ads1015_comp_queue) - 1; i++) { + if (period <= ads1015_comp_queue[i] * + USEC_PER_SEC / data_rate[dr]) + break; + } + data->thresh_data[chan->address].comp_queue = i; + return 0; + default: + return -EINVAL; + } +} + +static int ads1015_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir) +{ + struct ads1015_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + if (data->event_channel != chan->address) + return 0; + + switch (dir) { + case IIO_EV_DIR_RISING: + return 1; + case IIO_EV_DIR_EITHER: + return (data->comp_mode == ADS1015_CFG_COMP_MODE_WINDOW); + default: + return -EINVAL; + } +} + +static int ads1015_enable_event_config(struct ads1015_data *data, + const struct iio_chan_spec *chan, int comp_mode) +{ + int low_thresh = data->thresh_data[chan->address].low_thresh; + int high_thresh = data->thresh_data[chan->address].high_thresh; + int ret; + unsigned int val; + + if (ads1015_event_channel_enabled(data)) { + if (data->event_channel != chan->address || + (data->comp_mode == ADS1015_CFG_COMP_MODE_TRAD && + comp_mode == ADS1015_CFG_COMP_MODE_WINDOW)) + return -EBUSY; + + return 0; + } + + if (comp_mode == ADS1015_CFG_COMP_MODE_TRAD) { + low_thresh = max(-1 << (chan->scan_type.realbits - 1), + high_thresh - 1); + } + ret = regmap_write(data->regmap, ADS1015_LO_THRESH_REG, + low_thresh << chan->scan_type.shift); + if (ret) + return ret; + + ret = regmap_write(data->regmap, ADS1015_HI_THRESH_REG, + high_thresh << chan->scan_type.shift); + if (ret) + return ret; + + ret = ads1015_set_power_state(data, true); + if (ret < 0) + return ret; + + ads1015_event_channel_enable(data, chan->address, comp_mode); + + ret = ads1015_get_adc_result(data, chan->address, &val); + if (ret) { + ads1015_event_channel_disable(data, chan->address); + ads1015_set_power_state(data, false); + } + + return ret; +} + +static int ads1015_disable_event_config(struct ads1015_data *data, + const struct iio_chan_spec *chan, int comp_mode) +{ + int ret; + + if (!ads1015_event_channel_enabled(data)) + return 0; + + if (data->event_channel != chan->address) + return 0; + + if (data->comp_mode == ADS1015_CFG_COMP_MODE_TRAD && + comp_mode == ADS1015_CFG_COMP_MODE_WINDOW) + return 0; + + ret = regmap_update_bits(data->regmap, ADS1015_CFG_REG, + ADS1015_CFG_COMP_QUE_MASK, + ADS1015_CFG_COMP_DISABLE << + ADS1015_CFG_COMP_QUE_SHIFT); + if (ret) + return ret; + + ads1015_event_channel_disable(data, chan->address); + + return ads1015_set_power_state(data, false); +} + +static int ads1015_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct ads1015_data *data = iio_priv(indio_dev); + int ret; + int comp_mode = (dir == IIO_EV_DIR_EITHER) ? + ADS1015_CFG_COMP_MODE_WINDOW : ADS1015_CFG_COMP_MODE_TRAD; + + guard(mutex)(&data->lock); + + /* Prevent from enabling both buffer and event at a time */ + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + if (state) + ret = ads1015_enable_event_config(data, chan, comp_mode); + else + ret = ads1015_disable_event_config(data, chan, comp_mode); + + iio_device_release_direct(indio_dev); + return ret; +} + +static irqreturn_t ads1015_event_handler(int irq, void *priv) +{ + struct iio_dev *indio_dev = priv; + struct ads1015_data *data = iio_priv(indio_dev); + int val; + int ret; + + /* Clear the latched ALERT/RDY pin */ + ret = regmap_read(data->regmap, ADS1015_CONV_REG, &val); + if (ret) + return IRQ_HANDLED; + + if (ads1015_event_channel_enabled(data)) { + enum iio_event_direction dir; + u64 code; + + dir = data->comp_mode == ADS1015_CFG_COMP_MODE_TRAD ? + IIO_EV_DIR_RISING : IIO_EV_DIR_EITHER; + code = IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, data->event_channel, + IIO_EV_TYPE_THRESH, dir); + iio_push_event(indio_dev, code, iio_get_time_ns(indio_dev)); + } + + return IRQ_HANDLED; +} + +static int ads1015_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ads1015_data *data = iio_priv(indio_dev); + + /* Prevent from enabling both buffer and event at a time */ + if (ads1015_event_channel_enabled(data)) + return -EBUSY; + + return ads1015_set_power_state(iio_priv(indio_dev), true); +} + +static int ads1015_buffer_postdisable(struct iio_dev *indio_dev) +{ + return ads1015_set_power_state(iio_priv(indio_dev), false); +} + +static const struct iio_buffer_setup_ops ads1015_buffer_setup_ops = { + .preenable = ads1015_buffer_preenable, + .postdisable = ads1015_buffer_postdisable, + .validate_scan_mask = &iio_validate_scan_mask_onehot, +}; + +static const struct iio_info ads1015_info = { + .read_avail = ads1015_read_avail, + .read_raw = ads1015_read_raw, + .write_raw = ads1015_write_raw, + .read_event_value = ads1015_read_event, + .write_event_value = ads1015_write_event, + .read_event_config = ads1015_read_event_config, + .write_event_config = ads1015_write_event_config, +}; + +static const struct iio_info tla2024_info = { + .read_avail = ads1015_read_avail, + .read_raw = ads1015_read_raw, + .write_raw = ads1015_write_raw, +}; + +static int ads1015_client_get_channels_config(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct ads1015_data *data = iio_priv(indio_dev); + struct device *dev = &client->dev; + int i = -1; + + device_for_each_child_node_scoped(dev, node) { + u32 pval; + unsigned int channel; + unsigned int pga = ADS1015_DEFAULT_PGA; + unsigned int data_rate = ADS1015_DEFAULT_DATA_RATE; + + if (fwnode_property_read_u32(node, "reg", &pval)) { + dev_err(dev, "invalid reg on %pfw\n", node); + continue; + } + + channel = pval; + if (channel >= ADS1015_CHANNELS) { + dev_err(dev, "invalid channel index %d on %pfw\n", + channel, node); + continue; + } + + if (!fwnode_property_read_u32(node, "ti,gain", &pval)) { + pga = pval; + if (pga > 5) { + dev_err(dev, "invalid gain on %pfw\n", node); + return -EINVAL; + } + } + + if (!fwnode_property_read_u32(node, "ti,datarate", &pval)) { + data_rate = pval; + if (data_rate > 7) { + dev_err(dev, "invalid data_rate on %pfw\n", node); + return -EINVAL; + } + } + + data->channel_data[channel].pga = pga; + data->channel_data[channel].data_rate = data_rate; + + i++; + } + + return i < 0 ? -EINVAL : 0; +} + +static void ads1015_get_channels_config(struct i2c_client *client) +{ + unsigned int k; + + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct ads1015_data *data = iio_priv(indio_dev); + + if (!ads1015_client_get_channels_config(client)) + return; + + /* fallback on default configuration */ + for (k = 0; k < ADS1015_CHANNELS; ++k) { + data->channel_data[k].pga = ADS1015_DEFAULT_PGA; + data->channel_data[k].data_rate = ADS1015_DEFAULT_DATA_RATE; + } +} + +static int ads1015_set_conv_mode(struct ads1015_data *data, int mode) +{ + return regmap_update_bits(data->regmap, ADS1015_CFG_REG, + ADS1015_CFG_MOD_MASK, + mode << ADS1015_CFG_MOD_SHIFT); +} + +static void ads1015_power_down(void *p) +{ + struct ads1015_data *data = p; + int ret; + + ret = ads1015_set_conv_mode(data, ADS1015_SINGLESHOT); + if (ret) + dev_warn(regmap_get_device(data->regmap), + "Failed to power down (%pe)\n", ERR_PTR(ret)); +} + +static int ads1015_probe(struct i2c_client *client) +{ + const struct ads1015_chip_data *chip; + struct iio_dev *indio_dev; + struct ads1015_data *data; + int ret; + int i; + + chip = i2c_get_match_data(client); + if (!chip) + return dev_err_probe(&client->dev, -EINVAL, "Unknown chip\n"); + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + + mutex_init(&data->lock); + + indio_dev->name = ADS1015_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + indio_dev->channels = chip->channels; + indio_dev->num_channels = chip->num_channels; + indio_dev->info = chip->info; + data->chip = chip; + data->event_channel = ADS1015_CHANNELS; + + /* + * Set default lower and upper threshold to min and max value + * respectively. + */ + for (i = 0; i < ADS1015_CHANNELS; i++) { + int realbits = indio_dev->channels[i].scan_type.realbits; + + data->thresh_data[i].low_thresh = -1 << (realbits - 1); + data->thresh_data[i].high_thresh = (1 << (realbits - 1)) - 1; + } + + /* we need to keep this ABI the same as used by hwmon ADS1015 driver */ + ads1015_get_channels_config(client); + + data->regmap = devm_regmap_init_i2c(client, chip->has_comparator ? + &ads1015_regmap_config : + &tla2024_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(&client->dev, "Failed to allocate register map\n"); + return PTR_ERR(data->regmap); + } + + ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, NULL, + ads1015_trigger_handler, + &ads1015_buffer_setup_ops); + if (ret < 0) { + dev_err(&client->dev, "iio triggered buffer setup failed\n"); + return ret; + } + + if (client->irq && chip->has_comparator) { + unsigned long irq_trig = irq_get_trigger_type(client->irq); + unsigned int cfg_comp_mask = ADS1015_CFG_COMP_QUE_MASK | + ADS1015_CFG_COMP_LAT_MASK | ADS1015_CFG_COMP_POL_MASK; + unsigned int cfg_comp = + ADS1015_CFG_COMP_DISABLE << ADS1015_CFG_COMP_QUE_SHIFT | + 1 << ADS1015_CFG_COMP_LAT_SHIFT; + + switch (irq_trig) { + case IRQF_TRIGGER_FALLING: + case IRQF_TRIGGER_LOW: + cfg_comp |= ADS1015_CFG_COMP_POL_LOW << + ADS1015_CFG_COMP_POL_SHIFT; + break; + case IRQF_TRIGGER_HIGH: + case IRQF_TRIGGER_RISING: + cfg_comp |= ADS1015_CFG_COMP_POL_HIGH << + ADS1015_CFG_COMP_POL_SHIFT; + break; + default: + return -EINVAL; + } + + ret = regmap_update_bits(data->regmap, ADS1015_CFG_REG, + cfg_comp_mask, cfg_comp); + if (ret) + return ret; + + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, ads1015_event_handler, + irq_trig | IRQF_ONESHOT, + client->name, indio_dev); + if (ret) + return ret; + } + + ret = ads1015_set_conv_mode(data, ADS1015_CONTINUOUS); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&client->dev, ads1015_power_down, data); + if (ret) + return ret; + + data->conv_invalid = true; + + ret = pm_runtime_set_active(&client->dev); + if (ret) + return ret; + pm_runtime_set_autosuspend_delay(&client->dev, ADS1015_SLEEP_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + ret = devm_pm_runtime_enable(&client->dev); + if (ret) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +#ifdef CONFIG_PM +static int ads1015_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct ads1015_data *data = iio_priv(indio_dev); + + return ads1015_set_conv_mode(data, ADS1015_SINGLESHOT); +} + +static int ads1015_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct ads1015_data *data = iio_priv(indio_dev); + int ret; + + ret = ads1015_set_conv_mode(data, ADS1015_CONTINUOUS); + if (!ret) + data->conv_invalid = true; + + return ret; +} +#endif + +static const struct dev_pm_ops ads1015_pm_ops = { + SET_RUNTIME_PM_OPS(ads1015_runtime_suspend, + ads1015_runtime_resume, NULL) +}; + +static const struct ads1015_chip_data ads1015_data = { + .channels = ads1015_channels, + .num_channels = ARRAY_SIZE(ads1015_channels), + .info = &ads1015_info, + .data_rate = ads1015_data_rate, + .data_rate_len = ARRAY_SIZE(ads1015_data_rate), + .scale = ads1015_scale, + .scale_len = ARRAY_SIZE(ads1015_scale), + .has_comparator = true, +}; + +static const struct ads1015_chip_data ads1115_data = { + .channels = ads1115_channels, + .num_channels = ARRAY_SIZE(ads1115_channels), + .info = &ads1015_info, + .data_rate = ads1115_data_rate, + .data_rate_len = ARRAY_SIZE(ads1115_data_rate), + .scale = ads1115_scale, + .scale_len = ARRAY_SIZE(ads1115_scale), + .has_comparator = true, +}; + +static const struct ads1015_chip_data tla2024_data = { + .channels = tla2024_channels, + .num_channels = ARRAY_SIZE(tla2024_channels), + .info = &tla2024_info, + .data_rate = ads1015_data_rate, + .data_rate_len = ARRAY_SIZE(ads1015_data_rate), + .scale = ads1015_scale, + .scale_len = ARRAY_SIZE(ads1015_scale), + .has_comparator = false, +}; + +static const struct i2c_device_id ads1015_id[] = { + { .name = "ads1015", .driver_data = (kernel_ulong_t)&ads1015_data }, + { .name = "ads1115", .driver_data = (kernel_ulong_t)&ads1115_data }, + { .name = "tla2024", .driver_data = (kernel_ulong_t)&tla2024_data }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ads1015_id); + +static const struct of_device_id ads1015_of_match[] = { + { .compatible = "ti,ads1015", .data = &ads1015_data }, + { .compatible = "ti,ads1115", .data = &ads1115_data }, + { .compatible = "ti,tla2024", .data = &tla2024_data }, + { } +}; +MODULE_DEVICE_TABLE(of, ads1015_of_match); + +static struct i2c_driver ads1015_driver = { + .driver = { + .name = ADS1015_DRV_NAME, + .of_match_table = ads1015_of_match, + .pm = &ads1015_pm_ops, + }, + .probe = ads1015_probe, + .id_table = ads1015_id, +}; + +module_i2c_driver(ads1015_driver); + +MODULE_AUTHOR("Daniel Baluta "); +MODULE_DESCRIPTION("Texas Instruments ADS1015 ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ti-ads1018.c b/drivers/iio/adc/ti-ads1018.c new file mode 100644 index 000000000..d9ec4a8d7 --- /dev/null +++ b/drivers/iio/adc/ti-ads1018.c @@ -0,0 +1,738 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Texas Instruments ADS1018 ADC driver + * + * Copyright (C) 2025 Kurt Borja + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#define ADS1018_CFG_OS_TRIG BIT(15) +#define ADS1018_CFG_TS_MODE_EN BIT(4) +#define ADS1018_CFG_PULL_UP BIT(3) +#define ADS1018_CFG_NOP BIT(1) +#define ADS1018_CFG_VALID (ADS1018_CFG_PULL_UP | ADS1018_CFG_NOP) + +#define ADS1018_CFG_MUX_MASK GENMASK(14, 12) + +#define ADS1018_CFG_PGA_MASK GENMASK(11, 9) +#define ADS1018_PGA_DEFAULT 2 + +#define ADS1018_CFG_MODE_MASK BIT(8) +#define ADS1018_MODE_CONTINUOUS 0 +#define ADS1018_MODE_ONESHOT 1 + +#define ADS1018_CFG_DRATE_MASK GENMASK(7, 5) +#define ADS1018_DRATE_DEFAULT 4 + +#define ADS1018_NUM_PGA_MODES 6 +#define ADS1018_CHANNELS_MAX 10 + +struct ads1018_chan_data { + u8 pga_mode; + u8 data_rate_mode; +}; + +struct ads1018_chip_info { + const char *name; + const struct iio_chan_spec *channels; + unsigned long num_channels; + + /* IIO_VAL_INT */ + const u32 *data_rate_mode_to_hz; + unsigned long num_data_rate_mode_to_hz; + + /* + * Let `res` be the chip's resolution and `fsr` (millivolts) be the + * full-scale range corresponding to the PGA mode given by the array + * index. Then, the gain is calculated using the following formula: + * + * gain = |fsr| / 2^(res - 1) + * + * This value then has to be represented in IIO_VAL_INT_PLUS_NANO + * format. For example if: + * + * gain = 6144 / 2^(16 - 1) = 0.1875 + * + * ...then the formatted value is: + * + * { 0, 187500000 } + */ + const u32 pga_mode_to_gain[ADS1018_NUM_PGA_MODES][2]; + + /* IIO_VAL_INT_PLUS_MICRO */ + const u32 temp_scale[2]; +}; + +struct ads1018 { + struct spi_device *spi; + struct iio_trigger *indio_trig; + + struct gpio_desc *drdy_gpiod; + int drdy_irq; + + struct ads1018_chan_data chan_data[ADS1018_CHANNELS_MAX]; + const struct ads1018_chip_info *chip_info; + + struct spi_message msg_read; + struct spi_transfer xfer; + __be16 tx_buf[2] __aligned(IIO_DMA_MINALIGN); + __be16 rx_buf[2]; +}; + +#define ADS1018_VOLT_DIFF_CHAN(_index, _chan, _chan2, _realbits) { \ + .type = IIO_VOLTAGE, \ + .channel = _chan, \ + .channel2 = _chan2, \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = _realbits, \ + .storagebits = 16, \ + .shift = 16 - _realbits, \ + .endianness = IIO_BE, \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .indexed = true, \ + .differential = true, \ +} + +#define ADS1018_VOLT_CHAN(_index, _chan, _realbits) { \ + .type = IIO_VOLTAGE, \ + .channel = _chan, \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = _realbits, \ + .storagebits = 16, \ + .shift = 16 - _realbits, \ + .endianness = IIO_BE, \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .indexed = true, \ +} + +#define ADS1018_TEMP_CHAN(_index, _realbits) { \ + .type = IIO_TEMP, \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = _realbits, \ + .storagebits = 16, \ + .shift = 16 - _realbits, \ + .endianness = IIO_BE, \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ +} + +static const struct iio_chan_spec ads1118_iio_channels[] = { + ADS1018_VOLT_DIFF_CHAN(0, 0, 1, 16), + ADS1018_VOLT_DIFF_CHAN(1, 0, 3, 16), + ADS1018_VOLT_DIFF_CHAN(2, 1, 3, 16), + ADS1018_VOLT_DIFF_CHAN(3, 2, 3, 16), + ADS1018_VOLT_CHAN(4, 0, 16), + ADS1018_VOLT_CHAN(5, 1, 16), + ADS1018_VOLT_CHAN(6, 2, 16), + ADS1018_VOLT_CHAN(7, 3, 16), + ADS1018_TEMP_CHAN(8, 14), + IIO_CHAN_SOFT_TIMESTAMP(9), +}; + +static const struct iio_chan_spec ads1018_iio_channels[] = { + ADS1018_VOLT_DIFF_CHAN(0, 0, 1, 12), + ADS1018_VOLT_DIFF_CHAN(1, 0, 3, 12), + ADS1018_VOLT_DIFF_CHAN(2, 1, 3, 12), + ADS1018_VOLT_DIFF_CHAN(3, 2, 3, 12), + ADS1018_VOLT_CHAN(4, 0, 12), + ADS1018_VOLT_CHAN(5, 1, 12), + ADS1018_VOLT_CHAN(6, 2, 12), + ADS1018_VOLT_CHAN(7, 3, 12), + ADS1018_TEMP_CHAN(8, 12), + IIO_CHAN_SOFT_TIMESTAMP(9), +}; + +/** + * ads1018_calc_delay - Calculates a suitable delay for a single-shot reading + * @hz: Sampling frequency + * + * Calculates an appropriate delay for a single shot reading given a sampling + * frequency. + * + * Return: Delay in microseconds (Always greater than 0). + */ +static u32 ads1018_calc_delay(unsigned int hz) +{ + /* + * Calculate the worst-case sampling rate by subtracting 10% error + * specified in the datasheet... + */ + hz -= DIV_ROUND_UP(hz, 10); + + /* ...Then calculate time per sample in microseconds. */ + return DIV_ROUND_UP(HZ_PER_MHZ, hz); +} + +/** + * ads1018_spi_read_exclusive - Reads a conversion value from the device + * @ads1018: Device data + * @cnv: ADC Conversion value (optional) + * @hold_cs: Keep CS line asserted after the SPI transfer + * + * Reads the most recent ADC conversion value, without updating the + * device's configuration. + * + * Context: Expects SPI bus *exclusive* use. + * + * Return: 0 on success, negative errno on error. + */ +static int ads1018_spi_read_exclusive(struct ads1018 *ads1018, __be16 *cnv, + bool hold_cs) +{ + int ret; + + ads1018->xfer.cs_change = hold_cs; + + ret = spi_sync_locked(ads1018->spi, &ads1018->msg_read); + if (ret) + return ret; + + if (cnv) + *cnv = ads1018->rx_buf[0]; + + return 0; +} + +/** + * ads1018_single_shot - Performs a one-shot reading sequence + * @ads1018: Device data + * @chan: Channel specification + * @cnv: Conversion value + * + * Writes a new configuration, waits an appropriate delay, then reads the most + * recent conversion. + * + * Context: Expects iio_device_claim_direct() is held. + * + * Return: 0 on success, negative errno on error. + */ +static int ads1018_single_shot(struct ads1018 *ads1018, + struct iio_chan_spec const *chan, u16 *cnv) +{ + u8 max_drate_mode = ads1018->chip_info->num_data_rate_mode_to_hz - 1; + u32 drate = ads1018->chip_info->data_rate_mode_to_hz[max_drate_mode]; + u8 pga_mode = ads1018->chan_data[chan->scan_index].pga_mode; + struct spi_transfer xfer[2] = { + { + .tx_buf = ads1018->tx_buf, + .len = sizeof(ads1018->tx_buf[0]), + .delay = { + .value = ads1018_calc_delay(drate), + .unit = SPI_DELAY_UNIT_USECS, + }, + .cs_change = 1, /* 16-bit mode requires CS de-assert */ + }, + { + .rx_buf = ads1018->rx_buf, + .len = sizeof(ads1018->rx_buf[0]), + }, + }; + u16 cfg; + int ret; + + cfg = ADS1018_CFG_VALID | ADS1018_CFG_OS_TRIG; + cfg |= FIELD_PREP(ADS1018_CFG_MUX_MASK, chan->scan_index); + cfg |= FIELD_PREP(ADS1018_CFG_PGA_MASK, pga_mode); + cfg |= FIELD_PREP(ADS1018_CFG_MODE_MASK, ADS1018_MODE_ONESHOT); + cfg |= FIELD_PREP(ADS1018_CFG_DRATE_MASK, max_drate_mode); + + if (chan->type == IIO_TEMP) + cfg |= ADS1018_CFG_TS_MODE_EN; + + ads1018->tx_buf[0] = cpu_to_be16(cfg); + ret = spi_sync_transfer(ads1018->spi, xfer, ARRAY_SIZE(xfer)); + if (ret) + return ret; + + *cnv = be16_to_cpu(ads1018->rx_buf[0]); + + return 0; +} + +static int +ads1018_read_raw_direct_mode(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, + long mask) +{ + struct ads1018 *ads1018 = iio_priv(indio_dev); + const struct ads1018_chip_info *chip_info = ads1018->chip_info; + u8 addr = chan->scan_index; + u8 pga_mode, drate_mode; + u16 cnv; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = ads1018_single_shot(ads1018, chan, &cnv); + if (ret) + return ret; + + cnv >>= chan->scan_type.shift; + *val = sign_extend32(cnv, chan->scan_type.realbits - 1); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + pga_mode = ads1018->chan_data[addr].pga_mode; + *val = chip_info->pga_mode_to_gain[pga_mode][0]; + *val2 = chip_info->pga_mode_to_gain[pga_mode][1]; + return IIO_VAL_INT_PLUS_NANO; + + case IIO_TEMP: + *val = chip_info->temp_scale[0]; + *val2 = chip_info->temp_scale[1]; + return IIO_VAL_INT_PLUS_MICRO; + + default: + return -EOPNOTSUPP; + } + + case IIO_CHAN_INFO_SAMP_FREQ: + drate_mode = ads1018->chan_data[addr].data_rate_mode; + *val = chip_info->data_rate_mode_to_hz[drate_mode]; + return IIO_VAL_INT; + + default: + return -EOPNOTSUPP; + } +} + +static int +ads1018_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = ads1018_read_raw_direct_mode(indio_dev, chan, val, val2, mask); + iio_device_release_direct(indio_dev); + + return ret; +} + +static int +ads1018_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, long mask) +{ + struct ads1018 *ads1018 = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *type = IIO_VAL_INT_PLUS_NANO; + *vals = (const int *)ads1018->chip_info->pga_mode_to_gain; + *length = ADS1018_NUM_PGA_MODES * 2; + return IIO_AVAIL_LIST; + + case IIO_CHAN_INFO_SAMP_FREQ: + *type = IIO_VAL_INT; + *vals = ads1018->chip_info->data_rate_mode_to_hz; + *length = ads1018->chip_info->num_data_rate_mode_to_hz; + return IIO_AVAIL_LIST; + + default: + return -EOPNOTSUPP; + } +} + +static int +ads1018_write_raw_direct_mode(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, + long mask) +{ + struct ads1018 *ads1018 = iio_priv(indio_dev); + const struct ads1018_chip_info *info = ads1018->chip_info; + unsigned int i; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + for (i = 0; i < ADS1018_NUM_PGA_MODES; i++) { + if (val == info->pga_mode_to_gain[i][0] && + val2 == info->pga_mode_to_gain[i][1]) + break; + } + if (i == ADS1018_NUM_PGA_MODES) + return -EINVAL; + + ads1018->chan_data[chan->scan_index].pga_mode = i; + return 0; + + case IIO_CHAN_INFO_SAMP_FREQ: + for (i = 0; i < info->num_data_rate_mode_to_hz; i++) { + if (val == info->data_rate_mode_to_hz[i]) + break; + } + if (i == info->num_data_rate_mode_to_hz) + return -EINVAL; + + ads1018->chan_data[chan->scan_index].data_rate_mode = i; + return 0; + + default: + return -EOPNOTSUPP; + } +} + +static int +ads1018_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = ads1018_write_raw_direct_mode(indio_dev, chan, val, val2, mask); + iio_device_release_direct(indio_dev); + + return ret; +} + +static int +ads1018_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT_PLUS_MICRO; + } +} + +static const struct iio_info ads1018_iio_info = { + .read_raw = ads1018_read_raw, + .read_avail = ads1018_read_avail, + .write_raw = ads1018_write_raw, + .write_raw_get_fmt = ads1018_write_raw_get_fmt, +}; + +static void ads1018_set_trigger_enable(struct ads1018 *ads1018) +{ + spi_bus_lock(ads1018->spi->controller); + ads1018_spi_read_exclusive(ads1018, NULL, true); + enable_irq(ads1018->drdy_irq); +} + +static void ads1018_set_trigger_disable(struct ads1018 *ads1018) +{ + disable_irq(ads1018->drdy_irq); + ads1018_spi_read_exclusive(ads1018, NULL, false); + spi_bus_unlock(ads1018->spi->controller); +} + +static int ads1018_set_trigger_state(struct iio_trigger *trig, bool state) +{ + struct ads1018 *ads1018 = iio_trigger_get_drvdata(trig); + + /* + * We need to lock the SPI bus and tie CS low (hold_cs) to catch + * data-ready interrupts, otherwise the MISO line enters a Hi-Z state. + */ + + if (state) + ads1018_set_trigger_enable(ads1018); + else + ads1018_set_trigger_disable(ads1018); + + return 0; +} + +static const struct iio_trigger_ops ads1018_trigger_ops = { + .set_trigger_state = ads1018_set_trigger_state, + .validate_device = iio_trigger_validate_own_device, +}; + +static int ads1018_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ads1018 *ads1018 = iio_priv(indio_dev); + const struct ads1018_chip_info *chip_info = ads1018->chip_info; + unsigned int pga, drate, addr; + u16 cfg; + + addr = find_first_bit(indio_dev->active_scan_mask, + iio_get_masklength(indio_dev)); + pga = ads1018->chan_data[addr].pga_mode; + drate = ads1018->chan_data[addr].data_rate_mode; + + cfg = ADS1018_CFG_VALID; + cfg |= FIELD_PREP(ADS1018_CFG_MUX_MASK, addr); + cfg |= FIELD_PREP(ADS1018_CFG_PGA_MASK, pga); + cfg |= FIELD_PREP(ADS1018_CFG_MODE_MASK, ADS1018_MODE_CONTINUOUS); + cfg |= FIELD_PREP(ADS1018_CFG_DRATE_MASK, drate); + + if (chip_info->channels[addr].type == IIO_TEMP) + cfg |= ADS1018_CFG_TS_MODE_EN; + + ads1018->tx_buf[0] = cpu_to_be16(cfg); + ads1018->tx_buf[1] = 0; + + return spi_write(ads1018->spi, ads1018->tx_buf, sizeof(ads1018->tx_buf)); +} + +static int ads1018_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ads1018 *ads1018 = iio_priv(indio_dev); + u16 cfg; + + cfg = ADS1018_CFG_VALID; + cfg |= FIELD_PREP(ADS1018_CFG_MODE_MASK, ADS1018_MODE_ONESHOT); + + ads1018->tx_buf[0] = cpu_to_be16(cfg); + ads1018->tx_buf[1] = 0; + + return spi_write(ads1018->spi, ads1018->tx_buf, sizeof(ads1018->tx_buf)); +} + +static const struct iio_buffer_setup_ops ads1018_buffer_ops = { + .preenable = ads1018_buffer_preenable, + .postdisable = ads1018_buffer_postdisable, + .validate_scan_mask = iio_validate_scan_mask_onehot, +}; + +static irqreturn_t ads1018_irq_handler(int irq, void *dev_id) +{ + struct ads1018 *ads1018 = dev_id; + + /* + * We need to check if the "drdy" pin is actually active or if it's a + * pending interrupt triggered by the SPI transfer. + */ + if (!gpiod_get_value(ads1018->drdy_gpiod)) + return IRQ_HANDLED; + + iio_trigger_poll(ads1018->indio_trig); + + return IRQ_HANDLED; +} + +static irqreturn_t ads1018_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ads1018 *ads1018 = iio_priv(indio_dev); + struct { + __be16 conv; + aligned_s64 ts; + } scan = {}; + int ret; + + if (iio_trigger_using_own(indio_dev)) { + disable_irq(ads1018->drdy_irq); + ret = ads1018_spi_read_exclusive(ads1018, &scan.conv, true); + enable_irq(ads1018->drdy_irq); + } else { + ret = spi_read(ads1018->spi, ads1018->rx_buf, sizeof(ads1018->rx_buf)); + scan.conv = ads1018->rx_buf[0]; + } + + if (!ret) + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + pf->timestamp); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ads1018_trigger_setup(struct iio_dev *indio_dev) +{ + struct ads1018 *ads1018 = iio_priv(indio_dev); + struct spi_device *spi = ads1018->spi; + struct device *dev = &spi->dev; + const char *con_id = "drdy"; + int ret; + + ads1018->drdy_gpiod = devm_gpiod_get_optional(dev, con_id, GPIOD_IN); + if (IS_ERR(ads1018->drdy_gpiod)) + return dev_err_probe(dev, PTR_ERR(ads1018->drdy_gpiod), + "Failed to get %s GPIO.\n", con_id); + + /* First try to get IRQ from SPI core, then from GPIO */ + if (spi->irq > 0) + ads1018->drdy_irq = spi->irq; + else if (ads1018->drdy_gpiod) + ads1018->drdy_irq = gpiod_to_irq(ads1018->drdy_gpiod); + if (ads1018->drdy_irq < 0) + return dev_err_probe(dev, ads1018->drdy_irq, + "Failed to get IRQ from %s GPIO.\n", con_id); + + /* An IRQ line is only an optional requirement for the IIO trigger */ + if (ads1018->drdy_irq == 0) + return 0; + + ads1018->indio_trig = devm_iio_trigger_alloc(dev, "%s-dev%d-%s", + indio_dev->name, + iio_device_id(indio_dev), + con_id); + if (!ads1018->indio_trig) + return -ENOMEM; + + iio_trigger_set_drvdata(ads1018->indio_trig, ads1018); + ads1018->indio_trig->ops = &ads1018_trigger_ops; + + ret = devm_iio_trigger_register(dev, ads1018->indio_trig); + if (ret) + return ret; + + /* + * The "data-ready" IRQ line is shared with the MOSI pin, thus we need + * to keep it disabled until we actually request data. + */ + return devm_request_irq(dev, ads1018->drdy_irq, ads1018_irq_handler, + IRQF_NO_AUTOEN, ads1018->chip_info->name, ads1018); +} + +static int ads1018_spi_probe(struct spi_device *spi) +{ + const struct ads1018_chip_info *info = spi_get_device_match_data(spi); + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ads1018 *ads1018; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*ads1018)); + if (!indio_dev) + return -ENOMEM; + + ads1018 = iio_priv(indio_dev); + ads1018->spi = spi; + ads1018->chip_info = info; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = info->name; + indio_dev->info = &ads1018_iio_info; + indio_dev->channels = info->channels; + indio_dev->num_channels = info->num_channels; + + for (unsigned int i = 0; i < ADS1018_CHANNELS_MAX; i++) { + ads1018->chan_data[i].data_rate_mode = ADS1018_DRATE_DEFAULT; + ads1018->chan_data[i].pga_mode = ADS1018_PGA_DEFAULT; + } + + ads1018->xfer.rx_buf = ads1018->rx_buf; + ads1018->xfer.len = sizeof(ads1018->rx_buf); + spi_message_init_with_transfers(&ads1018->msg_read, &ads1018->xfer, 1); + + ret = ads1018_trigger_setup(indio_dev); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + ads1018_trigger_handler, + &ads1018_buffer_ops); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const unsigned int ads1018_data_rate_table[] = { + 128, 250, 490, 920, 1600, 2400, 3300, +}; + +static const unsigned int ads1118_data_rate_table[] = { + 8, 16, 32, 64, 128, 250, 475, 860, +}; + +static const struct ads1018_chip_info ads1018_chip_info = { + .name = "ads1018", + .channels = ads1018_iio_channels, + .num_channels = ARRAY_SIZE(ads1018_iio_channels), + .data_rate_mode_to_hz = ads1018_data_rate_table, + .num_data_rate_mode_to_hz = ARRAY_SIZE(ads1018_data_rate_table), + .pga_mode_to_gain = { + { 3, 0 }, /* fsr = 6144 mV */ + { 2, 0 }, /* fsr = 4096 mV */ + { 1, 0 }, /* fsr = 2048 mV */ + { 0, 500000000 }, /* fsr = 1024 mV */ + { 0, 250000000 }, /* fsr = 512 mV */ + { 0, 125000000 }, /* fsr = 256 mV */ + }, + .temp_scale = { 125, 0 }, +}; + +static const struct ads1018_chip_info ads1118_chip_info = { + .name = "ads1118", + .channels = ads1118_iio_channels, + .num_channels = ARRAY_SIZE(ads1118_iio_channels), + .data_rate_mode_to_hz = ads1118_data_rate_table, + .num_data_rate_mode_to_hz = ARRAY_SIZE(ads1118_data_rate_table), + .pga_mode_to_gain = { + { 0, 187500000 }, /* fsr = 6144 mV */ + { 0, 125000000 }, /* fsr = 4096 mV */ + { 0, 62500000 }, /* fsr = 2048 mV */ + { 0, 31250000 }, /* fsr = 1024 mV */ + { 0, 15625000 }, /* fsr = 512 mV */ + { 0, 7812500 }, /* fsr = 256 mV */ + }, + .temp_scale = { 31, 250000 }, +}; + +static const struct of_device_id ads1018_of_match[] = { + { .compatible = "ti,ads1018", .data = &ads1018_chip_info }, + { .compatible = "ti,ads1118", .data = &ads1118_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ads1018_of_match); + +static const struct spi_device_id ads1018_spi_match[] = { + { .name = "ads1018", .driver_data = (kernel_ulong_t)&ads1018_chip_info }, + { .name = "ads1118", .driver_data = (kernel_ulong_t)&ads1118_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ads1018_spi_match); + +static struct spi_driver ads1018_spi_driver = { + .driver = { + .name = "ads1018", + .of_match_table = ads1018_of_match, + }, + .probe = ads1018_spi_probe, + .id_table = ads1018_spi_match, +}; +module_spi_driver(ads1018_spi_driver); + +MODULE_DESCRIPTION("Texas Instruments ADS1018 ADC Driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kurt Borja "); diff --git a/drivers/iio/adc/ti-ads1100.c b/drivers/iio/adc/ti-ads1100.c new file mode 100644 index 000000000..9fe8d54cc --- /dev/null +++ b/drivers/iio/adc/ti-ads1100.c @@ -0,0 +1,432 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADS1100 - Texas Instruments Analog-to-Digital Converter + * + * Copyright (c) 2023, Topic Embedded Products + * + * Datasheet: https://www.ti.com/lit/gpn/ads1100 + * IIO driver for ADS1100 and ADS1000 ADC 16-bit I2C + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* The ADS1100 has a single byte config register */ + +/* Conversion in progress bit */ +#define ADS1100_CFG_ST_BSY BIT(7) +/* Single conversion bit */ +#define ADS1100_CFG_SC BIT(4) +/* Data rate */ +#define ADS1100_DR_MASK GENMASK(3, 2) +/* Gain */ +#define ADS1100_PGA_MASK GENMASK(1, 0) + +#define ADS1100_CONTINUOUS 0 +#define ADS1100_SINGLESHOT ADS1100_CFG_SC + +#define ADS1100_SLEEP_DELAY_MS 2000 + +static const int ads1100_data_rate[] = { 128, 32, 16, 8 }; +static const int ads1100_data_rate_bits[] = { 12, 14, 15, 16 }; + +struct ads1100_data { + struct i2c_client *client; + struct regulator *reg_vdd; + struct mutex lock; + int scale_avail[2 * 4]; /* 4 gain settings */ + u8 config; + bool supports_data_rate; /* Only the ADS1100 can select the rate */ +}; + +static const struct iio_chan_spec ads1100_channel = { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + .datasheet_name = "AIN", +}; + +static int ads1100_set_config_bits(struct ads1100_data *data, u8 mask, u8 value) +{ + int ret; + u8 config = (data->config & ~mask) | (value & mask); + + if (data->config == config) + return 0; /* Already done */ + + ret = i2c_master_send(data->client, &config, 1); + if (ret < 0) + return ret; + + data->config = config; + + return 0; +}; + +static int ads1100_data_bits(struct ads1100_data *data) +{ + return ads1100_data_rate_bits[FIELD_GET(ADS1100_DR_MASK, data->config)]; +} + +static int ads1100_get_adc_result(struct ads1100_data *data, int chan, int *val) +{ + int ret; + __be16 buffer; + s16 value; + + if (chan != 0) + return -EINVAL; + + ret = pm_runtime_resume_and_get(&data->client->dev); + if (ret < 0) + return ret; + + ret = i2c_master_recv(data->client, (char *)&buffer, sizeof(buffer)); + + pm_runtime_put_autosuspend(&data->client->dev); + + if (ret < 0) { + dev_err(&data->client->dev, "I2C read fail: %d\n", ret); + return ret; + } + + /* Value is always 16-bit 2's complement */ + value = be16_to_cpu(buffer); + + /* Shift result to compensate for bit resolution vs. sample rate */ + value <<= 16 - ads1100_data_bits(data); + + *val = sign_extend32(value, 15); + + return 0; +} + +static int ads1100_set_scale(struct ads1100_data *data, int val, int val2) +{ + int microvolts; + int gain; + + /* With Vdd between 2.7 and 5V, the scale is always below 1 */ + if (val) + return -EINVAL; + + if (!val2) + return -EINVAL; + + microvolts = regulator_get_voltage(data->reg_vdd); + /* + * val2 is in 'micro' units, n = val2 / 1000000 + * result must be millivolts, d = microvolts / 1000 + * the full-scale value is d/n, corresponds to 2^15, + * hence the gain = (d / n) >> 15, factoring out the 1000 and moving the + * bitshift so everything fits in 32-bits yields this formula. + */ + gain = DIV_ROUND_CLOSEST(microvolts, BIT(15)) * MILLI / val2; + if (gain < BIT(0) || gain > BIT(3)) + return -EINVAL; + + ads1100_set_config_bits(data, ADS1100_PGA_MASK, ffs(gain) - 1); + + return 0; +} + +static int ads1100_set_data_rate(struct ads1100_data *data, int chan, int rate) +{ + unsigned int i; + unsigned int size; + + size = data->supports_data_rate ? ARRAY_SIZE(ads1100_data_rate) : 1; + for (i = 0; i < size; i++) { + if (ads1100_data_rate[i] == rate) + return ads1100_set_config_bits(data, ADS1100_DR_MASK, + FIELD_PREP(ADS1100_DR_MASK, i)); + } + + return -EINVAL; +} + +static int ads1100_get_vdd_millivolts(struct ads1100_data *data) +{ + return regulator_get_voltage(data->reg_vdd) / (MICRO / MILLI); +} + +static void ads1100_calc_scale_avail(struct ads1100_data *data) +{ + int millivolts = ads1100_get_vdd_millivolts(data); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(data->scale_avail) / 2; i++) { + data->scale_avail[i * 2 + 0] = millivolts; + data->scale_avail[i * 2 + 1] = 15 + i; + } +} + +static int ads1100_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct ads1100_data *data = iio_priv(indio_dev); + + if (chan->type != IIO_VOLTAGE) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *type = IIO_VAL_INT; + *vals = ads1100_data_rate; + if (data->supports_data_rate) + *length = ARRAY_SIZE(ads1100_data_rate); + else + *length = 1; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SCALE: + *type = IIO_VAL_FRACTIONAL_LOG2; + *vals = data->scale_avail; + *length = ARRAY_SIZE(data->scale_avail); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int ads1100_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int ret; + struct ads1100_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ads1100_get_adc_result(data, chan->address, val); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* full-scale is the supply voltage in millivolts */ + *val = ads1100_get_vdd_millivolts(data); + *val2 = 15 + FIELD_GET(ADS1100_PGA_MASK, data->config); + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = ads1100_data_rate[FIELD_GET(ADS1100_DR_MASK, + data->config)]; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ads1100_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct ads1100_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return ads1100_set_scale(data, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return ads1100_set_data_rate(data, chan->address, val); + default: + return -EINVAL; + } +} + +static const struct iio_info ads1100_info = { + .read_avail = ads1100_read_avail, + .read_raw = ads1100_read_raw, + .write_raw = ads1100_write_raw, +}; + +static int ads1100_setup(struct ads1100_data *data) +{ + int ret; + u8 buffer[3]; + + /* Setup continuous sampling mode at 8sps */ + buffer[0] = ADS1100_DR_MASK | ADS1100_CONTINUOUS; + ret = i2c_master_send(data->client, buffer, 1); + if (ret < 0) + return ret; + + ret = i2c_master_recv(data->client, buffer, sizeof(buffer)); + if (ret < 0) + return ret; + + /* Config register returned in third byte, strip away the busy status */ + data->config = buffer[2] & ~ADS1100_CFG_ST_BSY; + + /* Detect the sample rate capability by checking the DR bits */ + data->supports_data_rate = FIELD_GET(ADS1100_DR_MASK, buffer[2]) != 0; + + return 0; +} + +static void ads1100_reg_disable(void *reg) +{ + regulator_disable(reg); +} + +static void ads1100_disable_continuous(void *data) +{ + ads1100_set_config_bits(data, ADS1100_CFG_SC, ADS1100_SINGLESHOT); +} + +static int ads1100_probe(struct i2c_client *client) +{ + struct iio_dev *indio_dev; + struct ads1100_data *data; + struct device *dev = &client->dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + dev_set_drvdata(dev, data); + data->client = client; + mutex_init(&data->lock); + + indio_dev->name = "ads1100"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = &ads1100_channel; + indio_dev->num_channels = 1; + indio_dev->info = &ads1100_info; + + data->reg_vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(data->reg_vdd)) + return dev_err_probe(dev, PTR_ERR(data->reg_vdd), + "Failed to get vdd regulator\n"); + + ret = regulator_enable(data->reg_vdd); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to enable vdd regulator\n"); + + ret = devm_add_action_or_reset(dev, ads1100_reg_disable, data->reg_vdd); + if (ret) + return ret; + + ret = ads1100_setup(data); + if (ret) + return dev_err_probe(dev, ret, + "Failed to communicate with device\n"); + + ret = devm_add_action_or_reset(dev, ads1100_disable_continuous, data); + if (ret) + return ret; + + ads1100_calc_scale_avail(data); + + pm_runtime_set_autosuspend_delay(dev, ADS1100_SLEEP_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_active(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable pm_runtime\n"); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, + "Failed to register IIO device\n"); + + return 0; +} + +static int ads1100_runtime_suspend(struct device *dev) +{ + struct ads1100_data *data = dev_get_drvdata(dev); + + ads1100_set_config_bits(data, ADS1100_CFG_SC, ADS1100_SINGLESHOT); + regulator_disable(data->reg_vdd); + + return 0; +} + +static int ads1100_runtime_resume(struct device *dev) +{ + struct ads1100_data *data = dev_get_drvdata(dev); + int ret; + + ret = regulator_enable(data->reg_vdd); + if (ret) { + dev_err(&data->client->dev, "Failed to enable Vdd\n"); + return ret; + } + + /* + * We'll always change the mode bit in the config register, so there is + * no need here to "force" a write to the config register. If the device + * has been power-cycled, we'll re-write its config register now. + */ + return ads1100_set_config_bits(data, ADS1100_CFG_SC, + ADS1100_CONTINUOUS); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(ads1100_pm_ops, + ads1100_runtime_suspend, + ads1100_runtime_resume, + NULL); + +static const struct i2c_device_id ads1100_id[] = { + { .name = "ads1100" }, + { .name = "ads1000" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, ads1100_id); + +static const struct of_device_id ads1100_of_match[] = { + {.compatible = "ti,ads1100" }, + {.compatible = "ti,ads1000" }, + { } +}; + +MODULE_DEVICE_TABLE(of, ads1100_of_match); + +static struct i2c_driver ads1100_driver = { + .driver = { + .name = "ads1100", + .of_match_table = ads1100_of_match, + .pm = pm_ptr(&ads1100_pm_ops), + }, + .probe = ads1100_probe, + .id_table = ads1100_id, +}; + +module_i2c_driver(ads1100_driver); + +MODULE_AUTHOR("Mike Looijmans "); +MODULE_DESCRIPTION("Texas Instruments ADS1100 ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/ti-ads1119.c b/drivers/iio/adc/ti-ads1119.c new file mode 100644 index 000000000..b0f04741d --- /dev/null +++ b/drivers/iio/adc/ti-ads1119.c @@ -0,0 +1,829 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Texas Instruments ADS1119 ADC driver. + * + * Copyright 2024 Toradex + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define ADS1119_CMD_RESET 0x06 +#define ADS1119_CMD_POWERDOWN 0x02 +#define ADS1119_CMD_START_SYNC 0x08 +#define ADS1119_CMD_RDATA 0x10 +#define ADS1119_CMD_RREG_CONFIG 0x20 +#define ADS1119_CMD_RREG_STATUS 0x24 +#define ADS1119_CMD_WREG 0x40 + +#define ADS1119_CMD_RREG(reg) (0x20 | (reg) << 2) + +/* Config register */ +#define ADS1119_REG_CONFIG 0x00 +#define ADS1119_CONFIG_VREF_FIELD BIT(0) +#define ADS1119_CONFIG_CM_FIELD BIT(1) +#define ADS1119_CONFIG_DR_FIELD GENMASK(3, 2) +#define ADS1119_CONFIG_GAIN_FIELD BIT(4) +#define ADS1119_CONFIG_MUX_FIELD GENMASK(7, 5) + +#define ADS1119_VREF_INTERNAL 0 +#define ADS1119_VREF_EXTERNAL 1 +#define ADS1119_VREF_INTERNAL_VAL 2048000 + +#define ADS1119_CM_SINGLE 0 +#define ADS1119_CM_CONTINUOUS 1 + +#define ADS1119_DR_20_SPS 0 +#define ADS1119_DR_90_SPS 1 +#define ADS1119_DR_330_SPS 2 +#define ADS1119_DR_1000_SPS 3 + +#define ADS1119_GAIN_1 0 +#define ADS1119_GAIN_4 1 + +#define ADS1119_MUX_AIN0_AIN1 0 +#define ADS1119_MUX_AIN2_AIN3 1 +#define ADS1119_MUX_AIN1_AIN2 2 +#define ADS1119_MUX_AIN0 3 +#define ADS1119_MUX_AIN1 4 +#define ADS1119_MUX_AIN2 5 +#define ADS1119_MUX_AIN3 6 +#define ADS1119_MUX_SHORTED 7 + +/* Status register */ +#define ADS1119_REG_STATUS 0x01 +#define ADS1119_STATUS_DRDY_FIELD BIT(7) + +#define ADS1119_DEFAULT_GAIN 1 +#define ADS1119_DEFAULT_DATARATE 20 + +#define ADS1119_SUSPEND_DELAY 2000 + +/* Timeout based on the minimum sample rate of 20 SPS (50000us) */ +#define ADS1119_MAX_DRDY_TIMEOUT 85000 + +#define ADS1119_MAX_CHANNELS 7 +#define ADS1119_MAX_SINGLE_CHANNELS 4 + +struct ads1119_channel_config { + int gain; + int datarate; + int mux; +}; + +struct ads1119_state { + struct completion completion; + struct i2c_client *client; + struct gpio_desc *reset_gpio; + struct iio_trigger *trig; + struct ads1119_channel_config *channels_cfg; + unsigned int num_channels_cfg; + unsigned int cached_config; + int vref_uV; +}; + +static const char * const ads1119_power_supplies[] = { + "avdd", "dvdd" +}; + +static const int ads1119_available_datarates[] = { + 20, 90, 330, 1000, +}; + +static const int ads1119_available_gains[] = { + 1, 1, + 1, 4, +}; + +static int ads1119_upd_cfg_reg(struct ads1119_state *st, unsigned int fields, + unsigned int val) +{ + unsigned int config = st->cached_config; + int ret; + + config &= ~fields; + config |= val; + + ret = i2c_smbus_write_byte_data(st->client, ADS1119_CMD_WREG, config); + if (ret) + return ret; + + st->cached_config = config; + + return 0; +} + +static bool ads1119_data_ready(struct ads1119_state *st) +{ + int status; + + status = i2c_smbus_read_byte_data(st->client, ADS1119_CMD_RREG_STATUS); + if (status < 0) + return false; + + return FIELD_GET(ADS1119_STATUS_DRDY_FIELD, status); +} + +static int ads1119_reset(struct ads1119_state *st) +{ + st->cached_config = 0; + + if (!st->reset_gpio) + return i2c_smbus_write_byte(st->client, ADS1119_CMD_RESET); + + gpiod_set_value_cansleep(st->reset_gpio, 1); + udelay(1); + gpiod_set_value_cansleep(st->reset_gpio, 0); + udelay(1); + + return 0; +} + +static int ads1119_set_conv_mode(struct ads1119_state *st, bool continuous) +{ + unsigned int mode; + + if (continuous) + mode = ADS1119_CM_CONTINUOUS; + else + mode = ADS1119_CM_SINGLE; + + return ads1119_upd_cfg_reg(st, ADS1119_CONFIG_CM_FIELD, + FIELD_PREP(ADS1119_CONFIG_CM_FIELD, mode)); +} + +static int ads1119_get_hw_gain(int gain) +{ + if (gain == 4) + return ADS1119_GAIN_4; + else + return ADS1119_GAIN_1; +} + +static int ads1119_get_hw_datarate(int datarate) +{ + switch (datarate) { + case 90: + return ADS1119_DR_90_SPS; + case 330: + return ADS1119_DR_330_SPS; + case 1000: + return ADS1119_DR_1000_SPS; + case 20: + default: + return ADS1119_DR_20_SPS; + } +} + +static int ads1119_configure_channel(struct ads1119_state *st, int mux, + int gain, int datarate) +{ + int ret; + + ret = ads1119_upd_cfg_reg(st, ADS1119_CONFIG_MUX_FIELD, + FIELD_PREP(ADS1119_CONFIG_MUX_FIELD, mux)); + if (ret) + return ret; + + ret = ads1119_upd_cfg_reg(st, ADS1119_CONFIG_GAIN_FIELD, + FIELD_PREP(ADS1119_CONFIG_GAIN_FIELD, + ads1119_get_hw_gain(gain))); + if (ret) + return ret; + + return ads1119_upd_cfg_reg(st, ADS1119_CONFIG_DR_FIELD, + FIELD_PREP(ADS1119_CONFIG_DR_FIELD, + ads1119_get_hw_datarate(datarate))); +} + +static int ads1119_poll_data_ready(struct ads1119_state *st, + struct iio_chan_spec const *chan) +{ + unsigned int datarate = st->channels_cfg[chan->address].datarate; + unsigned long wait_time; + bool data_ready; + + /* Poll 5 times more than the data rate */ + wait_time = DIV_ROUND_CLOSEST(MICRO, 5 * datarate); + + return read_poll_timeout(ads1119_data_ready, data_ready, + data_ready, wait_time, + ADS1119_MAX_DRDY_TIMEOUT, false, st); +} + +static int ads1119_read_data(struct ads1119_state *st, + struct iio_chan_spec const *chan, + unsigned int *val) +{ + unsigned int timeout; + int ret = 0; + + timeout = msecs_to_jiffies(ADS1119_MAX_DRDY_TIMEOUT); + + if (!st->client->irq) { + ret = ads1119_poll_data_ready(st, chan); + if (ret) + return ret; + } else if (!wait_for_completion_timeout(&st->completion, timeout)) { + return -ETIMEDOUT; + } + + ret = i2c_smbus_read_word_swapped(st->client, ADS1119_CMD_RDATA); + if (ret < 0) + return ret; + + *val = ret; + + return 0; +} + +static int ads1119_single_conversion(struct ads1119_state *st, + struct iio_chan_spec const *chan, + int *val, + bool calib_offset) +{ + struct device *dev = &st->client->dev; + int mux = st->channels_cfg[chan->address].mux; + int gain = st->channels_cfg[chan->address].gain; + int datarate = st->channels_cfg[chan->address].datarate; + unsigned int sample; + int ret; + + if (calib_offset) + mux = ADS1119_MUX_SHORTED; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = ads1119_configure_channel(st, mux, gain, datarate); + if (ret) + goto pdown; + + if (st->client->irq) + reinit_completion(&st->completion); + + ret = i2c_smbus_write_byte(st->client, ADS1119_CMD_START_SYNC); + if (ret) + goto pdown; + + ret = ads1119_read_data(st, chan, &sample); + if (ret) + goto pdown; + + *val = sign_extend32(sample, chan->scan_type.realbits - 1); + ret = IIO_VAL_INT; +pdown: + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int ads1119_validate_datarate(struct ads1119_state *st, int datarate) +{ + switch (datarate) { + case 20: + case 90: + case 330: + case 1000: + return datarate; + default: + return -EINVAL; + } +} + +static int ads1119_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *type = IIO_VAL_FRACTIONAL; + *vals = ads1119_available_gains; + *length = ARRAY_SIZE(ads1119_available_gains); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *type = IIO_VAL_INT; + *vals = ads1119_available_datarates; + *length = ARRAY_SIZE(ads1119_available_datarates); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int ads1119_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct ads1119_state *st = iio_priv(indio_dev); + unsigned int index = chan->address; + int ret; + + if (index >= st->num_channels_cfg) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = ads1119_single_conversion(st, chan, val, false); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_OFFSET: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = ads1119_single_conversion(st, chan, val, true); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SCALE: + *val = st->vref_uV / 1000; + *val /= st->channels_cfg[index].gain; + *val2 = chan->scan_type.realbits - 1; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = st->channels_cfg[index].datarate; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ads1119_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct ads1119_state *st = iio_priv(indio_dev); + unsigned int index = chan->address; + int ret; + + if (index >= st->num_channels_cfg) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + ret = MICRO / ((val * MICRO) + val2); + if (ret != 1 && ret != 4) + return -EINVAL; + + st->channels_cfg[index].gain = ret; + return 0; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = ads1119_validate_datarate(st, val); + if (ret < 0) + return ret; + + st->channels_cfg[index].datarate = ret; + return 0; + default: + return -EINVAL; + } +} + +static int ads1119_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + struct ads1119_state *st = iio_priv(indio_dev); + int ret; + + if (reg > ADS1119_REG_STATUS) + return -EINVAL; + + if (readval) { + ret = i2c_smbus_read_byte_data(st->client, + ADS1119_CMD_RREG(reg)); + if (ret < 0) + return ret; + + *readval = ret; + return 0; + } + + if (reg > ADS1119_REG_CONFIG) + return -EINVAL; + + return i2c_smbus_write_byte_data(st->client, ADS1119_CMD_WREG, + writeval); +} + +static const struct iio_info ads1119_info = { + .read_avail = ads1119_read_avail, + .read_raw = ads1119_read_raw, + .write_raw = ads1119_write_raw, + .debugfs_reg_access = ads1119_debugfs_reg_access, +}; + +static int ads1119_triggered_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ads1119_state *st = iio_priv(indio_dev); + struct device *dev = &st->client->dev; + unsigned int index; + int ret; + + index = find_first_bit(indio_dev->active_scan_mask, + iio_get_masklength(indio_dev)); + + ret = ads1119_set_conv_mode(st, true); + if (ret) + return ret; + + ret = ads1119_configure_channel(st, + st->channels_cfg[index].mux, + st->channels_cfg[index].gain, + st->channels_cfg[index].datarate); + if (ret) + return ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = i2c_smbus_write_byte(st->client, ADS1119_CMD_START_SYNC); + if (ret) + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static int ads1119_triggered_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ads1119_state *st = iio_priv(indio_dev); + struct device *dev = &st->client->dev; + int ret; + + ret = ads1119_set_conv_mode(st, false); + if (ret) + return ret; + + pm_runtime_put_autosuspend(dev); + + return 0; +} + +static const struct iio_buffer_setup_ops ads1119_buffer_setup_ops = { + .preenable = ads1119_triggered_buffer_preenable, + .postdisable = ads1119_triggered_buffer_postdisable, + .validate_scan_mask = &iio_validate_scan_mask_onehot, +}; + +static const struct iio_trigger_ops ads1119_trigger_ops = { + .validate_device = &iio_trigger_validate_own_device, +}; + +static irqreturn_t ads1119_irq_handler(int irq, void *dev_id) +{ + struct iio_dev *indio_dev = dev_id; + struct ads1119_state *st = iio_priv(indio_dev); + + if (iio_buffer_enabled(indio_dev) && iio_trigger_using_own(indio_dev)) + iio_trigger_poll(indio_dev->trig); + else + complete(&st->completion); + + return IRQ_HANDLED; +} + +static irqreturn_t ads1119_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct ads1119_state *st = iio_priv(indio_dev); + struct { + s16 sample; + aligned_s64 timestamp; + } scan = { }; + unsigned int index; + int ret; + + if (!iio_trigger_using_own(indio_dev)) { + index = find_first_bit(indio_dev->active_scan_mask, + iio_get_masklength(indio_dev)); + + ret = ads1119_poll_data_ready(st, &indio_dev->channels[index]); + if (ret) { + dev_err(&st->client->dev, + "Failed to poll data on trigger (%d)\n", ret); + goto done; + } + } + + ret = i2c_smbus_read_word_swapped(st->client, ADS1119_CMD_RDATA); + if (ret < 0) { + dev_err(&st->client->dev, + "Failed to read data on trigger (%d)\n", ret); + goto done; + } + + scan.sample = ret; + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); +done: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int ads1119_init(struct ads1119_state *st, bool vref_external) +{ + int ret; + + ret = ads1119_reset(st); + if (ret) + return ret; + + if (vref_external) + return ads1119_upd_cfg_reg(st, + ADS1119_CONFIG_VREF_FIELD, + FIELD_PREP(ADS1119_CONFIG_VREF_FIELD, + ADS1119_VREF_EXTERNAL)); + return 0; +} + +static int ads1119_map_analog_inputs_mux(int ain_pos, int ain_neg, + bool differential) +{ + if (ain_pos >= ADS1119_MAX_SINGLE_CHANNELS) + return -EINVAL; + + if (!differential) + return ADS1119_MUX_AIN0 + ain_pos; + + if (ain_pos == 0 && ain_neg == 1) + return ADS1119_MUX_AIN0_AIN1; + else if (ain_pos == 1 && ain_neg == 2) + return ADS1119_MUX_AIN1_AIN2; + else if (ain_pos == 2 && ain_neg == 3) + return ADS1119_MUX_AIN2_AIN3; + + return -EINVAL; +} + +static int ads1119_alloc_and_config_channels(struct iio_dev *indio_dev) +{ + const struct iio_chan_spec ads1119_channel = + (const struct iio_chan_spec) { + .type = IIO_VOLTAGE, + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }; + const struct iio_chan_spec ads1119_ts = IIO_CHAN_SOFT_TIMESTAMP(0); + struct ads1119_state *st = iio_priv(indio_dev); + struct iio_chan_spec *iio_channels, *chan; + struct device *dev = &st->client->dev; + unsigned int num_channels, i; + bool differential; + u32 ain[2]; + int ret; + + st->num_channels_cfg = device_get_child_node_count(dev); + if (st->num_channels_cfg > ADS1119_MAX_CHANNELS) + return dev_err_probe(dev, -EINVAL, + "Too many channels %d, max is %d\n", + st->num_channels_cfg, + ADS1119_MAX_CHANNELS); + + st->channels_cfg = devm_kcalloc(dev, st->num_channels_cfg, + sizeof(*st->channels_cfg), GFP_KERNEL); + if (!st->channels_cfg) + return -ENOMEM; + + /* Allocate one more iio channel for the timestamp */ + num_channels = st->num_channels_cfg + 1; + iio_channels = devm_kcalloc(dev, num_channels, sizeof(*iio_channels), + GFP_KERNEL); + if (!iio_channels) + return -ENOMEM; + + i = 0; + + device_for_each_child_node_scoped(dev, child) { + chan = &iio_channels[i]; + + differential = fwnode_property_present(child, "diff-channels"); + if (differential) + ret = fwnode_property_read_u32_array(child, + "diff-channels", + ain, 2); + else + ret = fwnode_property_read_u32(child, "single-channel", + &ain[0]); + + if (ret) + return dev_err_probe(dev, ret, + "Failed to get channel property\n"); + + ret = ads1119_map_analog_inputs_mux(ain[0], ain[1], + differential); + if (ret < 0) + return dev_err_probe(dev, ret, + "Invalid channel value\n"); + + st->channels_cfg[i].mux = ret; + st->channels_cfg[i].gain = ADS1119_DEFAULT_GAIN; + st->channels_cfg[i].datarate = ADS1119_DEFAULT_DATARATE; + + *chan = ads1119_channel; + chan->channel = ain[0]; + chan->address = i; + chan->scan_index = i; + + if (differential) { + chan->channel2 = ain[1]; + chan->differential = 1; + } + + dev_dbg(dev, "channel: index %d, mux %d\n", i, + st->channels_cfg[i].mux); + + i++; + } + + iio_channels[i] = ads1119_ts; + iio_channels[i].address = i; + iio_channels[i].scan_index = i; + + indio_dev->channels = iio_channels; + indio_dev->num_channels = num_channels; + + return 0; +} + +static void ads1119_powerdown(void *data) +{ + struct ads1119_state *st = data; + + i2c_smbus_write_byte(st->client, ADS1119_CMD_POWERDOWN); +} + +static int ads1119_probe(struct i2c_client *client) +{ + struct iio_dev *indio_dev; + struct ads1119_state *st; + struct device *dev = &client->dev; + bool vref_external = true; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->client = client; + + indio_dev->name = "ads1119"; + indio_dev->info = &ads1119_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + i2c_set_clientdata(client, indio_dev); + + ret = devm_regulator_bulk_get_enable(dev, + ARRAY_SIZE(ads1119_power_supplies), + ads1119_power_supplies); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get and enable supplies\n"); + + st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "vref"); + if (st->vref_uV == -ENODEV) { + vref_external = false; + st->vref_uV = ADS1119_VREF_INTERNAL_VAL; + } else if (st->vref_uV < 0) { + return dev_err_probe(dev, st->vref_uV, "Failed to get vref\n"); + } + + st->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(st->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(st->reset_gpio), + "Failed to get reset gpio\n"); + + ret = ads1119_alloc_and_config_channels(indio_dev); + if (ret) + return ret; + + init_completion(&st->completion); + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + ads1119_trigger_handler, + &ads1119_buffer_setup_ops); + if (ret) + return dev_err_probe(dev, ret, "Failed to setup IIO buffer\n"); + + if (client->irq > 0) { + ret = devm_request_irq(dev, client->irq, ads1119_irq_handler, + IRQF_NO_THREAD, "ads1119", indio_dev); + if (ret) + return ret; + + st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!st->trig) + return -ENOMEM; + + st->trig->ops = &ads1119_trigger_ops; + iio_trigger_set_drvdata(st->trig, indio_dev); + + ret = devm_iio_trigger_register(dev, st->trig); + if (ret) + return dev_err_probe(dev, ret, + "Failed to register IIO trigger\n"); + } + + ret = ads1119_init(st, vref_external); + if (ret) + return dev_err_probe(dev, ret, + "Failed to initialize device\n"); + + pm_runtime_set_autosuspend_delay(dev, ADS1119_SUSPEND_DELAY); + pm_runtime_use_autosuspend(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_set_active(dev); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable pm runtime\n"); + + ret = devm_add_action_or_reset(dev, ads1119_powerdown, st); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static int ads1119_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct ads1119_state *st = iio_priv(indio_dev); + + return i2c_smbus_write_byte(st->client, ADS1119_CMD_POWERDOWN); +} + +/* + * The ADS1119 does not require a resume function because it automatically + * powers on after a reset. + * After a power down command, the ADS1119 can still communicate but turns off + * its analog parts. To resume from power down, the device will power up again + * upon receiving a start/sync command. + */ +static DEFINE_RUNTIME_DEV_PM_OPS(ads1119_pm_ops, ads1119_runtime_suspend, + NULL, NULL); + +static const struct of_device_id __maybe_unused ads1119_of_match[] = { + { .compatible = "ti,ads1119" }, + { } +}; +MODULE_DEVICE_TABLE(of, ads1119_of_match); + +static const struct i2c_device_id ads1119_id[] = { + { .name = "ads1119" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ads1119_id); + +static struct i2c_driver ads1119_driver = { + .driver = { + .name = "ads1119", + .of_match_table = ads1119_of_match, + .pm = pm_ptr(&ads1119_pm_ops), + }, + .probe = ads1119_probe, + .id_table = ads1119_id, +}; +module_i2c_driver(ads1119_driver); + +MODULE_AUTHOR("João Paulo Gonçalves "); +MODULE_DESCRIPTION("Texas Instruments ADS1119 ADC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c new file mode 100644 index 000000000..60eab4852 --- /dev/null +++ b/drivers/iio/adc/ti-ads112c14.c @@ -0,0 +1,1454 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * IIO driver for Texas Instruments ADS112C14 and similar ADCs. + * + * Copyright (C) 2026 Texas Instruments Incorporated - https://www.ti.com/ + * Copyright (C) 2026 Baylibre Inc. + * + * Datasheet: https://www.ti.com/lit/ds/symlink/ads122c14.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Arbitrary limit since channels are dynamic. */ +#define ADS112C14_MAX_MEASUREMENT_CHANNELS 16 + +/* Datasheet t_d(RST) - time to wait after reset before next I2C use. */ +#define ADS112C14_DELAY_RESET_US 500 + +#define ADS112C14_CMD_RDATA 0x00 +#define ADS112C14_CMD_RREG 0x40 +#define ADS112C14_CMD_WREG 0x80 + +#define ADS112C14_REG_DEVICE_ID 0x00 +#define ADS112C14_DEVICE_ID_BITS GENMASK(3, 0) + +#define ADS112C14_REG_REVISION_ID 0x01 + +#define ADS112C14_REG_STATUS_MSB 0x02 +#define ADS112C14_STATUS_MSB_RESETN BIT(7) +#define ADS112C14_STATUS_MSB_AVDD_UVN BIT(6) +#define ADS112C14_STATUS_MSB_REF_UVN BIT(5) +#define ADS112C14_STATUS_MSB_REG_MAP_CRC_FAULTN BIT(3) +#define ADS112C14_STATUS_MSB_MEM_FAULTN BIT(2) +#define ADS112C14_STATUS_MSB_REG_WRITE_FAULTN BIT(1) +#define ADS112C14_STATUS_MSB_DRDY BIT(0) + +#define ADS112C14_REG_STATUS_LSB 0x03 +#define ADS112C14_STATUS_LSB_CONV_COUNT GENMASK(7, 4) +#define ADS112C14_STATUS_LSB_GPIO3_DAT_IN BIT(3) +#define ADS112C14_STATUS_LSB_GPIO2_DAT_IN BIT(2) +#define ADS112C14_STATUS_LSB_GPIO1_DAT_IN BIT(1) +#define ADS112C14_STATUS_LSB_GPIO0_DAT_IN BIT(0) + +#define ADS112C14_REG_CONVERSION_CTRL 0x04 +#define ADS112C14_CONVERSION_CTRL_RESET GENMASK(7, 2) +#define ADS112C14_CONVERSION_CTRL_START BIT(1) +#define ADS112C14_CONVERSION_CTRL_STOP BIT(0) + +#define ADS112C14_REG_DEVICE_CFG 0x05 +#define ADS112C14_DEVICE_CFG_PWDN BIT(7) +#define ADS112C14_DEVICE_CFG_STBY_MODE BIT(6) +#define ADS112C14_DEVICE_CFG_BOCS GENMASK(5, 4) +#define ADS112C14_DEVICE_CFG_CLK_SEL BIT(3) +#define ADS112C14_DEVICE_CFG_CONV_MODE BIT(2) +#define ADS112C14_DEVICE_CFG_CONV_MODE_CONTINUOUS 0 +#define ADS112C14_DEVICE_CFG_CONV_MODE_SINGLE_SHOT 1 +#define ADS112C14_DEVICE_CFG_SPEED_MODE GENMASK(1, 0) + +#define ADS112C14_REG_DATA_RATE_CFG 0x06 +#define ADS112C14_DATA_RATE_CFG_DELAY GENMASK(7, 4) +#define ADS112C14_DATA_RATE_CFG_GC_EN BIT(3) +#define ADS112C14_DATA_RATE_CFG_FLTR_OSR GENMASK(2, 0) + +#define ADS112C14_REG_MUX_CFG 0x07 +#define ADS112C14_MUX_CFG_AINP GENMASK(7, 4) +#define ADS112C14_MUX_CFG_AINN GENMASK(3, 0) +#define ADS112C14_MUX_CFG_AIN_GND 8 + +#define ADS112C14_REG_GAIN_CFG 0x08 +#define ADS112C14_GAIN_CFG_SPARE BIT(7) +#define ADS112C14_GAIN_CFG_SYS_MON GENMASK(6, 4) +#define ADS112C14_GAIN_CFG_GAIN GENMASK(3, 0) + +#define ADS112C14_REG_REFERENCE_CFG 0x09 +#define ADS112C14_REFERENCE_CFG_REF_UV_EN BIT(7) +#define ADS112C14_REFERENCE_CFG_REFP_BUF_EN BIT(5) +#define ADS112C14_REFERENCE_CFG_REFN_BUF_EN BIT(4) +#define ADS112C14_REFERENCE_CFG_REF_VAL BIT(2) +#define ADS112C14_REFERENCE_CFG_REF_VAL_1_25V 0 +#define ADS112C14_REFERENCE_CFG_REF_VAL_2_5V 1 +#define ADS112C14_REFERENCE_CFG_REF_SEL GENMASK(1, 0) +#define ADS112C14_REFERENCE_CFG_REF_SEL_INTERNAL 0 +#define ADS112C14_REFERENCE_CFG_REF_SEL_EXTERNAL 1 +#define ADS112C14_REFERENCE_CFG_REF_SEL_AVDD 2 + +#define ADS112C14_REG_DIGITAL_CFG 0x0A +#define ADS112C14_DIGITAL_CFG_REG_MAP_CRC_EN BIT(6) +#define ADS112C14_DIGITAL_CFG_I2C_CRC_EN BIT(5) +#define ADS112C14_DIGITAL_CFG_STATUS_EN BIT(4) +#define ADS112C14_DIGITAL_CFG_FAULT_PIN_BEHAVIOR BIT(3) +#define ADS112C14_DIGITAL_CFG_CODING BIT(1) + +#define ADS112C14_REG_GPIO_CFG 0x0B +#define ADS112C14_GPIO_CFG_GPIO3_CFG GENMASK(7, 6) +#define ADS112C14_GPIO_CFG_GPIO2_CFG GENMASK(5, 4) +#define ADS112C14_GPIO_CFG_GPIO1_CFG GENMASK(3, 2) +#define ADS112C14_GPIO_CFG_GPIO0_CFG GENMASK(1, 0) + +#define ADS112C14_REG_GPIO_DATA_OUTPUT 0x0C +#define ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC BIT(7) +#define ADS112C14_GPIO_DATA_OUTPUT_GPIO2_SRC BIT(6) +#define ADS112C14_GPIO_DATA_OUTPUT_GPIO3_DAT_OUT BIT(3) +#define ADS112C14_GPIO_DATA_OUTPUT_GPIO2_DAT_OUT BIT(2) +#define ADS112C14_GPIO_DATA_OUTPUT_GPIO1_DAT_OUT BIT(1) +#define ADS112C14_GPIO_DATA_OUTPUT_GPIO0_DAT_OUT BIT(0) + +#define ADS112C14_REG_IDAC_MAG_CFG 0x0D +#define ADS112C14_IDAC_MAG_CFG_I2MAG GENMASK(7, 4) +#define ADS112C14_IDAC_MAG_CFG_I1MAG GENMASK(3, 0) + +#define ADS112C14_REG_IDAC_MUX_CFG 0x0E +#define ADS112C14_IDAC_MUX_CFG_IUNIT BIT(7) +#define ADS112C14_IDAC_MUX_CFG_I2MUX GENMASK(6, 4) +#define ADS112C14_IDAC_MUX_CFG_I1MUX GENMASK(2, 0) + +#define ADS112C14_REG_REG_MAP_CRC 0x0F + +#define ADS112C14_INT_REF0_mV 1250 +#define ADS112C14_INT_REF1_mV 2500 + +enum { + ADS112C14_VREF_SOURCE_INTERNAL_2_5V, + ADS112C14_VREF_SOURCE_INTERNAL_1_25V, + ADS112C14_VREF_SOURCE_EXTERNAL, + ADS112C14_VREF_SOURCE_AVDD, +}; + +static const char * const ads112c14_vref_source_names[] = { + [ADS112C14_VREF_SOURCE_INTERNAL_2_5V] = "internal-2.5v", + [ADS112C14_VREF_SOURCE_INTERNAL_1_25V] = "internal-1.25v", + [ADS112C14_VREF_SOURCE_EXTERNAL] = "external", + [ADS112C14_VREF_SOURCE_AVDD] = "avdd", +}; + +/* + * Available gains as tenths (e.g. value 5 == 0.5 gain). Indexes correspond to + * ADS112C14_GAIN_CFG_GAIN values. + */ +static const u32 ads112c14_pga_gains_x10[] = { + 5, 10, 20, 40, 50, 80, 100, 160, /* 0 - 7 */ + 200, 320, 500, 640, 1000, 1280, 2000, 2560, /* 8 - 15 */ +}; + +#define ADS112C14_I2C_CRC8_POLYNOMIAL 0x07 +DECLARE_CRC8_TABLE(ads112c14_crc8_table); + +struct ads112c14_chip_info { + const char *name; + u8 device_id; + u32 resolution_bits; +}; + +/* Fixed channels for system monitor measurements. */ + +#define ADS112C14_SYS_MON_CHANNEL_BASE 100 + +enum { + ADS112C14_SYS_MON_CHANNEL_TEMP = ADS112C14_SYS_MON_CHANNEL_BASE, + ADS112C14_SYS_MON_CHANNEL_EXT_REF, + ADS112C14_SYS_MON_CHANNEL_AVDD, + ADS112C14_SYS_MON_CHANNEL_DVDD, + ADS112C14_SYS_MON_CHANNEL_SHORT, +}; + +static const struct iio_chan_spec ads112c14_sys_mon_channels[] = { + { + .type = IIO_TEMP, + .indexed = 1, + .channel = ADS112C14_SYS_MON_CHANNEL_TEMP, + .address = 2, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) + | BIT(IIO_CHAN_INFO_SCALE) + | BIT(IIO_CHAN_INFO_OFFSET), + }, + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = ADS112C14_SYS_MON_CHANNEL_EXT_REF, + .address = 3, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) + | BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = ADS112C14_SYS_MON_CHANNEL_AVDD, + .address = 4, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) + | BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = ADS112C14_SYS_MON_CHANNEL_DVDD, + .address = 5, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) + | BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = ADS112C14_SYS_MON_CHANNEL_SHORT, + .channel2 = ADS112C14_SYS_MON_CHANNEL_SHORT, + .differential = 1, + .address = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) + | BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +struct ads112c14_measurement { + const char *label; + u32 vref_source; + u8 iunit; + u8 idac1_mag; + u8 idac2_mag; + u8 idac1_mux; + u8 idac2_mux; + u8 iadc_count; + u8 gain_val; + bool global_chop; + bool bipolar; + int scale_available[ARRAY_SIZE(ads112c14_pga_gains_x10)][2]; +}; + +struct ads112c14_data { + const struct ads112c14_chip_info *chip_info; + struct regmap *regmap; + /* Synchronizes access to register value fields. */ + struct mutex lock; + bool i2c_crc_enabled; + u32 avdd_uV; + u32 ext_ref_uV; + bool refp_is_avdd; + bool refn_is_gnd; + u32 ext_ref_ohms; + struct ads112c14_measurement *measurements; + u32 num_measurements; + u8 sys_mon_chan_short_gain_val; + int sys_mon_chan_short_scale_available[ARRAY_SIZE(ads112c14_pga_gains_x10)][2]; + IIO_DECLARE_BUFFER_WITH_TS(__be32, scan, ADS112C14_MAX_MEASUREMENT_CHANNELS + + ARRAY_SIZE(ads112c14_sys_mon_channels)); +}; + +static bool ads112c14_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ADS112C14_REG_DEVICE_ID: + case ADS112C14_REG_REVISION_ID: + case ADS112C14_REG_STATUS_LSB: + return false; + default: + return true; + } +} + +static bool ads112c14_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ADS112C14_REG_STATUS_MSB: + case ADS112C14_REG_STATUS_LSB: + case ADS112C14_REG_CONVERSION_CTRL: + return true; + default: + return false; + } +} + +static const struct reg_default ads112c14_reg_defaults[] = { + { ADS112C14_REG_DEVICE_CFG, 0 }, + { ADS112C14_REG_DATA_RATE_CFG, 0 }, + { ADS112C14_REG_MUX_CFG, 0 }, + { ADS112C14_REG_GAIN_CFG, FIELD_PREP_CONST(ADS112C14_GAIN_CFG_GAIN, 1) }, + { ADS112C14_REG_REFERENCE_CFG, 0 }, + { ADS112C14_REG_DIGITAL_CFG, 0 }, + { ADS112C14_REG_GPIO_CFG, 0 }, + { ADS112C14_REG_GPIO_DATA_OUTPUT, 0 }, + { ADS112C14_REG_IDAC_MAG_CFG, 0 }, + { ADS112C14_REG_IDAC_MUX_CFG, FIELD_PREP_CONST(ADS112C14_IDAC_MUX_CFG_I2MUX, 1) }, +}; + +/** + * ads112c14_i2c_read_bytes() - Read bytes from the device over I2C + * @client: I2C client for the device + * @cmd: Command to send to the device before reading + * @buf: Buffer to store the read bytes + * @len: Number of bytes to read + * @use_crc: Whether to use CRC8 for data integrity check + * + * If I2C_CRC is enabled, @use_crc may be set to true to perform a CRC8 check + * on the received data. + */ +static int ads112c14_i2c_read_bytes(struct i2c_client *client, u8 cmd, + u8 *buf, u8 len, bool use_crc) +{ + u8 rx_buf[4]; /* Up to 3 data bytes + 1 CRC byte. */ + u8 rx_len; + int ret; + + rx_len = len + (use_crc ? 1 : 0); + + if (rx_len > sizeof(rx_buf)) + return -EINVAL; + + ret = i2c_smbus_read_i2c_block_data(client, cmd, rx_len, rx_buf); + if (ret < 0) + return ret; + + if (use_crc) { + u8 crc = crc8(ads112c14_crc8_table, rx_buf, len, CRC8_INIT_VALUE); + + if (crc != rx_buf[len]) + return -EBADMSG; + } + + memcpy(buf, rx_buf, len); + + return 0; +} + +/** + * ads112c14_regmap_bus_read() - Read a register from the device + * @context: Pointer to the device context + * @reg_buf: Register address to read + * @reg_size: Size of the register address (should be 1) + * @val_buf: Buffer to store the read value + * @val_size: Size of the value to read + * + * Custom regmap read function that also does CRC check when enabled. + */ +static int ads112c14_regmap_bus_read(void *context, const void *reg_buf, + size_t reg_size, void *val_buf, + size_t val_size) +{ + struct ads112c14_data *data = context; + struct device *dev = regmap_get_device(data->regmap); + struct i2c_client *client = to_i2c_client(dev); + const u8 *cmd = reg_buf; + + if (reg_size != 1) + return -EINVAL; + + return ads112c14_i2c_read_bytes(client, cmd[0], val_buf, val_size, + data->i2c_crc_enabled); +} + +/** + * ads112c14_regmap_bus_write() - Write a register to the device + * @context: Pointer to the device context + * @data_buf: Buffer containing the register address and value to write + * @count: Number of bytes to write + * + * Custom regmap write function that also does readback with CRC check of + * nonvolatile registers when CRC is enabled. + */ +static int ads112c14_regmap_bus_write(void *context, const void *data_buf, + size_t count) +{ + struct ads112c14_data *data = context; + struct device *dev = regmap_get_device(data->regmap); + struct i2c_client *client = to_i2c_client(dev); + const u8 *tx = data_buf; + u8 reg, readback; + int ret; + + if (count != 2) + return -EINVAL; + + ret = i2c_smbus_write_byte_data(client, tx[0], tx[1]); + if (ret) + return ret; + + reg = tx[0] & ~ADS112C14_CMD_WREG; + + if (!data->i2c_crc_enabled || ads112c14_volatile_reg(dev, reg)) + return 0; + + ret = ads112c14_i2c_read_bytes(client, reg | ADS112C14_CMD_RREG, + &readback, sizeof(readback), true); + if (ret) + return ret; + + if (readback != tx[1]) + return -EIO; + + return 0; +} + +static const struct regmap_bus ads112c14_regmap_bus = { + .read = ads112c14_regmap_bus_read, + .write = ads112c14_regmap_bus_write, + .reg_format_endian_default = REGMAP_ENDIAN_BIG, + .val_format_endian_default = REGMAP_ENDIAN_BIG, +}; + +static const struct regmap_config ads112c14_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .read_flag_mask = ADS112C14_CMD_RREG, + .write_flag_mask = ADS112C14_CMD_WREG, + .max_register = ADS112C14_REG_REG_MAP_CRC, + .writeable_reg = ads112c14_writeable_reg, + .volatile_reg = ads112c14_volatile_reg, + .reg_defaults = ads112c14_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(ads112c14_reg_defaults), + .cache_type = REGCACHE_MAPLE, +}; + +static int ads112c14_prepare_measurement_channel(struct ads112c14_data *data, + const struct iio_chan_spec *chan) +{ + struct ads112c14_measurement *measurement = &data->measurements[chan->scan_index]; + u32 refp_buf_en, refn_buf_en, ref_val, ref_sel; + int ret; + + ret = regmap_update_bits(data->regmap, ADS112C14_REG_MUX_CFG, + ADS112C14_MUX_CFG_AINP | ADS112C14_MUX_CFG_AINN, + FIELD_PREP(ADS112C14_MUX_CFG_AINP, chan->channel) | + FIELD_PREP(ADS112C14_MUX_CFG_AINN, chan->channel2)); + if (ret) + return ret; + + ret = regmap_assign_bits(data->regmap, ADS112C14_REG_DIGITAL_CFG, + ADS112C14_DIGITAL_CFG_CODING, + !measurement->bipolar); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, ADS112C14_REG_GAIN_CFG, + ADS112C14_GAIN_CFG_SYS_MON | + ADS112C14_GAIN_CFG_GAIN, + FIELD_PREP(ADS112C14_GAIN_CFG_SYS_MON, 0) | + FIELD_PREP(ADS112C14_GAIN_CFG_GAIN, + measurement->gain_val)); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, ADS112C14_REG_IDAC_MAG_CFG, + ADS112C14_IDAC_MAG_CFG_I2MAG | + ADS112C14_IDAC_MAG_CFG_I1MAG, + FIELD_PREP(ADS112C14_IDAC_MAG_CFG_I2MAG, + measurement->idac2_mag) | + FIELD_PREP(ADS112C14_IDAC_MAG_CFG_I1MAG, + measurement->idac1_mag)); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, ADS112C14_REG_IDAC_MUX_CFG, + ADS112C14_IDAC_MUX_CFG_IUNIT | + ADS112C14_IDAC_MUX_CFG_I2MUX | + ADS112C14_IDAC_MUX_CFG_I1MUX, + FIELD_PREP(ADS112C14_IDAC_MUX_CFG_IUNIT, + measurement->iunit) | + FIELD_PREP(ADS112C14_IDAC_MUX_CFG_I2MUX, + measurement->idac2_mux) | + FIELD_PREP(ADS112C14_IDAC_MUX_CFG_I1MUX, + measurement->idac1_mux)); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, ADS112C14_REG_DATA_RATE_CFG, + ADS112C14_DATA_RATE_CFG_GC_EN, + FIELD_PREP(ADS112C14_DATA_RATE_CFG_GC_EN, + measurement->global_chop)); + if (ret) + return ret; + + refp_buf_en = !data->refp_is_avdd && + measurement->vref_source == ADS112C14_VREF_SOURCE_EXTERNAL; + refn_buf_en = !data->refn_is_gnd && + measurement->vref_source == ADS112C14_VREF_SOURCE_EXTERNAL; + + ref_val = measurement->vref_source == ADS112C14_VREF_SOURCE_INTERNAL_2_5V ? + ADS112C14_REFERENCE_CFG_REF_VAL_2_5V : + ADS112C14_REFERENCE_CFG_REF_VAL_1_25V; + + switch (measurement->vref_source) { + case ADS112C14_VREF_SOURCE_AVDD: + ref_sel = ADS112C14_REFERENCE_CFG_REF_SEL_AVDD; + break; + case ADS112C14_VREF_SOURCE_EXTERNAL: + ref_sel = ADS112C14_REFERENCE_CFG_REF_SEL_EXTERNAL; + break; + default: + ref_sel = ADS112C14_REFERENCE_CFG_REF_SEL_INTERNAL; + break; + } + + return regmap_update_bits(data->regmap, ADS112C14_REG_REFERENCE_CFG, + ADS112C14_REFERENCE_CFG_REFP_BUF_EN | + ADS112C14_REFERENCE_CFG_REFN_BUF_EN | + ADS112C14_REFERENCE_CFG_REF_VAL | + ADS112C14_REFERENCE_CFG_REF_SEL, + FIELD_PREP(ADS112C14_REFERENCE_CFG_REFP_BUF_EN, + refp_buf_en) | + FIELD_PREP(ADS112C14_REFERENCE_CFG_REFN_BUF_EN, + refn_buf_en) | + FIELD_PREP(ADS112C14_REFERENCE_CFG_REF_VAL, + ref_val) | + FIELD_PREP(ADS112C14_REFERENCE_CFG_REF_SEL, + ref_sel)); +} + +static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data, + const struct iio_chan_spec *chan) +{ + u32 gain_val; + int ret; + + /* + * NB: IDAC registers are left as-is in case they are generating current + * needed for the external reference measurement. + */ + + /* + * All SYS_MON channels use GAIN of 1 to keep it simple. Other than + * the internal short channel, where it is useful in practice. + */ + gain_val = chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT ? + data->sys_mon_chan_short_gain_val : 1; + + ret = regmap_update_bits(data->regmap, ADS112C14_REG_GAIN_CFG, + ADS112C14_GAIN_CFG_SYS_MON | + ADS112C14_GAIN_CFG_GAIN, + FIELD_PREP(ADS112C14_GAIN_CFG_SYS_MON, chan->address) | + FIELD_PREP(ADS112C14_GAIN_CFG_GAIN, gain_val)); + if (ret) + return ret; + + /* All SYS_MON channels use signed data to keep it simple. */ + ret = regmap_clear_bits(data->regmap, ADS112C14_REG_DIGITAL_CFG, + ADS112C14_DIGITAL_CFG_CODING); + if (ret) + return ret; + + /* + * REVISIT: if we implement regulator support for the REFOUT pin, we + * might need to make this voltage match what is required by that. In + * that case, we could also adjust GAIN so that we still get the same + * range. + */ + /* + * NB: SYS_MON channels ignore REF_SEL except for the shorted input + * channel, so we set it here to internal reference to be consistent. + * If we ever need to make a measurement of shorted input with other + * reference source, we could add additional channels for that. + */ + ret = regmap_update_bits(data->regmap, ADS112C14_REG_REFERENCE_CFG, + ADS112C14_REFERENCE_CFG_REF_VAL | + ADS112C14_REFERENCE_CFG_REF_SEL, + FIELD_PREP(ADS112C14_REFERENCE_CFG_REF_VAL, + ADS112C14_REFERENCE_CFG_REF_VAL_2_5V) | + FIELD_PREP(ADS112C14_REFERENCE_CFG_REF_SEL, + ADS112C14_REFERENCE_CFG_REF_SEL_INTERNAL)); + if (ret) + return ret; + + return 0; +} + +static int ads112c14_single_conversion(struct ads112c14_data *data, + const struct iio_chan_spec *chan, + u8 *buf, bool for_scan) +{ + struct i2c_client *client = to_i2c_client(regmap_get_device(data->regmap)); + u32 reg_val; + int ret; + + guard(mutex)(&data->lock); + + if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) { + ret = ads112c14_prepare_measurement_channel(data, chan); + if (ret) + return ret; + } else { + ret = ads112c14_prepare_sys_mon_channel(data, chan); + if (ret) + return ret; + } + + ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL, + ADS112C14_CONVERSION_CTRL_START); + if (ret) + return ret; + + ret = regmap_read_poll_timeout(data->regmap, + ADS112C14_REG_STATUS_MSB, reg_val, + FIELD_GET(ADS112C14_STATUS_MSB_DRDY, reg_val), + 1 * USEC_PER_MSEC, 100 * USEC_PER_MSEC); + if (ret) + return ret; + + /* + * When doing buffered read, we don't check the CRC, but rather pass it + * along with the raw data. This way, we don't silently drop samples + * with CRC errors, but rather leave it to userspace to decide what to + * do. + */ + if (for_scan) { + u8 len = BITS_TO_BYTES(data->chip_info->resolution_bits) + + (data->i2c_crc_enabled ? 1 : 0); + + ret = i2c_smbus_read_i2c_block_data(client, ADS112C14_CMD_RDATA, + len, buf); + if (ret < 0) + return ret; + + return 0; + } + + return ads112c14_i2c_read_bytes(client, ADS112C14_CMD_RDATA, buf, + BITS_TO_BYTES(data->chip_info->resolution_bits), + data->i2c_crc_enabled); +} + +static int ads112c14_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ads112c14_data *data = iio_priv(indio_dev); + struct ads112c14_measurement *measurement = NULL; + const int *scale_avail; + u32 vref_uV, fsr_bits; + + /* Selecting V_REF source is not implemented yet. */ + vref_uV = ADS112C14_INT_REF1_mV * (MICRO / MILLI); + + if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) { + measurement = &data->measurements[chan->scan_index]; + fsr_bits = data->chip_info->resolution_bits - measurement->bipolar; + } else { + /* All SYS_MON channels are using signed coding. */ + fsr_bits = data->chip_info->resolution_bits - 1; + } + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + u8 buf[3]; + int ret; + + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + ret = ads112c14_single_conversion(data, chan, buf, false); + if (ret) + return ret; + + switch (data->chip_info->resolution_bits) { + case 16: + *val = get_unaligned_be16(buf); + break; + case 24: + *val = get_unaligned_be24(buf); + break; + default: + return -EINVAL; + } + + if (!measurement || measurement->bipolar) + *val = sign_extend32(*val, fsr_bits); + + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_TEMP) { + /* TS_TC (typical) = 405 uV/°C */ + *val = MILLI * vref_uV / 405; + *val2 = fsr_bits; + return IIO_VAL_FRACTIONAL_LOG2; + } + + if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) { + guard(mutex)(&data->lock); + + scale_avail = measurement->scale_available[measurement->gain_val]; + *val = scale_avail[0]; + *val2 = scale_avail[1]; + + return IIO_VAL_DECIMAL64_PICO; + } + + if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT) { + u8 idx; + + guard(mutex)(&data->lock); + + idx = data->sys_mon_chan_short_gain_val; + scale_avail = data->sys_mon_chan_short_scale_available[idx]; + *val = scale_avail[0]; + *val2 = scale_avail[1]; + + return IIO_VAL_DECIMAL64_PICO; + } + + *val = vref_uV / (MICRO / MILLI); + + /* + * Some SYS_MON channels (ext ref, AVDD, DVDD) need to be + * multiplied by 8 to account for internal attenuation of / 8. + */ + switch (chan->address) { + case 3 ... 5: + *val2 = fsr_bits - 3; + break; + default: + *val2 = fsr_bits; + break; + } + + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: + /* Only the temperature channel has an offset. */ + if (chan->type != IIO_TEMP) + return -EINVAL; + /* + * Die temperature [°C] = 25°C + (Measured voltage – TS_Offset) / TS_TC + * TS_TC (typical) = 405 uV/°C + * TS_Offset (typical) = 119.5 mV + */ + *val = div_s64((s64)(25 * 405 - 119500) * BIT(fsr_bits), vref_uV); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ads112c14_read_avail(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, const int **vals, + int *type, int *length, long mask) +{ + struct ads112c14_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) { + struct ads112c14_measurement *measurement; + + guard(mutex)(&data->lock); + + measurement = &data->measurements[chan->scan_index]; + *vals = (const int *)measurement->scale_available; + *length = 2 * ARRAY_SIZE(measurement->scale_available); + *type = IIO_VAL_DECIMAL64_PICO; + return IIO_AVAIL_LIST; + } + + if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT) { + guard(mutex)(&data->lock); + + *vals = (const int *)data->sys_mon_chan_short_scale_available; + *length = 2 * ARRAY_SIZE(data->sys_mon_chan_short_scale_available); + *type = IIO_VAL_DECIMAL64_PICO; + return IIO_AVAIL_LIST; + } + + return -EINVAL; + default: + return -EINVAL; + } +} + +static int ads112c14_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct ads112c14_data *data = iio_priv(indio_dev); + const int (*scale_avail)[2]; + u8 *gain_val; + + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: { + guard(mutex)(&data->lock); + + if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) { + struct ads112c14_measurement *measurement; + + measurement = &data->measurements[chan->scan_index]; + scale_avail = measurement->scale_available; + gain_val = &measurement->gain_val; + } else if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT) { + scale_avail = data->sys_mon_chan_short_scale_available; + gain_val = &data->sys_mon_chan_short_gain_val; + } else { + return -EINVAL; + } + + for (u32 i = 0; i < ARRAY_SIZE(ads112c14_pga_gains_x10); i++) { + if (iio_val_s64_compose(val, val2) == + iio_val_s64_compose(scale_avail[i][0], scale_avail[i][1])) { + *gain_val = i; + return 0; + } + } + + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ads112c14_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_DECIMAL64_PICO; + default: + return IIO_VAL_INT_PLUS_MICRO; + } +} + +static int ads112c14_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct ads112c14_data *data = iio_priv(indio_dev); + + if (readval) + return regmap_read(data->regmap, reg, readval); + + return regmap_write(data->regmap, reg, writeval); +} + +static int ads112c14_read_label(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, char *label) +{ + struct ads112c14_data *data = iio_priv(indio_dev); + const char *label_source; + + /* measurement channels */ + if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) { + struct ads112c14_measurement *measurement; + + measurement = &data->measurements[chan->scan_index]; + if (!measurement->label) + return -EINVAL; + + return sysfs_emit(label, "%s\n", measurement->label); + } + + /* System monitor channels. */ + switch (chan->channel) { + case ADS112C14_SYS_MON_CHANNEL_TEMP: + label_source = "Internal temperature sensor"; + break; + case ADS112C14_SYS_MON_CHANNEL_EXT_REF: + label_source = "External reference"; + break; + case ADS112C14_SYS_MON_CHANNEL_AVDD: + label_source = "AVDD"; + break; + case ADS112C14_SYS_MON_CHANNEL_DVDD: + label_source = "DVDD"; + break; + case ADS112C14_SYS_MON_CHANNEL_SHORT: + label_source = "Internal short (internal reference source)"; + break; + default: + return -EINVAL; + } + + return sysfs_emit(label, "%s\n", label_source); +} + +static irqreturn_t ads112c14_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct ads112c14_data *data = iio_priv(indio_dev); + u32 offset = 0; + u32 i; + int ret; + + iio_for_each_active_channel(indio_dev, i) { + const struct iio_chan_spec *chan = &indio_dev->channels[i]; + + ret = ads112c14_single_conversion(data, chan, + (u8 *)&data->scan[offset++], + true); + if (ret) { + dev_err_once(indio_dev->dev.parent, + "failed to read channel %d: %pe; additional errors will be suppressed\n", + chan->channel, ERR_PTR(ret)); + goto out; + } + } + + iio_push_to_buffers_with_ts(indio_dev, data->scan, + sizeof(data->scan), pf->timestamp); +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_info ads112c14_info = { + .read_raw = ads112c14_read_raw, + .read_avail = ads112c14_read_avail, + .write_raw = ads112c14_write_raw, + .write_raw_get_fmt = ads112c14_write_raw_get_fmt, + .debugfs_reg_access = ads112c14_debugfs_reg_access, + .read_label = ads112c14_read_label, +}; + +static int ads112c14_populate_idac_mag(u32 current_nA, u8 *idac_mag) +{ + u32 current_uA = current_nA / (NANO / MICRO); + + /* Convert microamps to IMAG bits */ + if (current_uA == 1) + *idac_mag = 1; + else if (in_range(current_uA, 10, 100) && current_uA % 10 == 0) + *idac_mag = current_uA / 10 + 1; + else + return dev_err_probe(NULL, -EINVAL, + "invalid excitation-current-nanoamp value\n"); + + return 0; +} + +static int ads112c14_parse_channels(struct iio_dev *indio_dev, + bool *need_avdd_ref, bool *need_ext_ref) +{ + struct ads112c14_data *data = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + struct iio_chan_spec *channels; + u32 num_child_nodes, i, pair[2]; + int ret; + + *need_avdd_ref = false; + *need_ext_ref = false; + + num_child_nodes = device_get_named_child_node_count(dev, "channel"); + + data->measurements = devm_kcalloc(dev, num_child_nodes, + sizeof(*data->measurements), GFP_KERNEL); + if (!data->measurements) + return -ENOMEM; + + channels = devm_kcalloc(dev, num_child_nodes + + ARRAY_SIZE(ads112c14_sys_mon_channels) + 1, + sizeof(*channels), GFP_KERNEL); + if (!channels) + return -ENOMEM; + + i = 0; + device_for_each_named_child_node_scoped(dev, child, "channel") { + struct ads112c14_measurement *measurement = &data->measurements[i]; + struct iio_chan_spec *spec = &channels[i]; + + spec->indexed = 1; + spec->scan_index = i; + measurement->gain_val = 1; + + if (fwnode_property_present(child, "label")) { + ret = fwnode_property_read_string(child, "label", &measurement->label); + if (ret) + return dev_err_probe(dev, ret, + "failed to read label property\n"); + } + + if (fwnode_property_present(child, "single-channel")) { + ret = fwnode_property_read_u32(child, "single-channel", + &pair[0]); + if (ret) + return dev_err_probe(dev, ret, + "failed to read single-channel property\n"); + + if (pair[0] >= 8) + return dev_err_probe(dev, -EINVAL, + "single-channel value must be between 0 and 7\n"); + + spec->channel = pair[0]; + /* + * NB: channel2 is unused by iio core code in this case. + * Let's us avoid special case for negative input mux + * for single-ended channels when taking measurements. + */ + spec->channel2 = ADS112C14_MUX_CFG_AIN_GND; + } else if (fwnode_property_present(child, "diff-channels")) { + ret = fwnode_property_read_u32_array(child, "diff-channels", + pair, ARRAY_SIZE(pair)); + if (ret) + return dev_err_probe(dev, ret, + "failed to read diff-channels property\n"); + + if (pair[0] >= 8 || pair[1] >= 8) + return dev_err_probe(dev, -EINVAL, + "diff-channels values must be between 0 and 7\n"); + + spec->differential = 1; + spec->channel = pair[0]; + spec->channel2 = pair[1]; + } else { + return dev_err_probe(dev, -EINVAL, + "channel node missing channel type property\n"); + } + + if (fwnode_property_present(child, "excitation-channels")) { + ret = fwnode_property_count_u32(child, "excitation-channels"); + if (ret < 0) + return dev_err_probe(dev, ret, + "failed to read excitation-channels property\n"); + + if (ret < 1 || ret > 2) + return dev_err_probe(dev, -EINVAL, + "excitation-channels property must have 1 or 2 values\n"); + + measurement->iadc_count = ret; + pair[1] = 0; + + ret = fwnode_property_read_u32_array(child, "excitation-channels", + pair, measurement->iadc_count); + if (ret) + return dev_err_probe(dev, ret, + "failed to read excitation-channels property\n"); + + if (pair[0] >= 8 || pair[1] >= 8) + return dev_err_probe(dev, -EINVAL, + "excitation-channels values must be between 0 and 7\n"); + + measurement->idac1_mux = pair[0]; + measurement->idac2_mux = measurement->iadc_count > 1 ? pair[1] : 0; + + ret = fwnode_property_read_u32_array(child, "excitation-current-nanoamp", + pair, measurement->iadc_count); + if (ret) + return dev_err_probe(dev, ret, + "failed to read excitation-current-nanoamp property\n"); + + if (pair[0] <= 100 * (NANO / MICRO) && + (measurement->iadc_count == 1 || pair[1] <= 100 * (NANO / MICRO))) { + /* + * If both values are 100µA or less, then we can + * use IUNIT = 1µA for better precision. + */ + ret = ads112c14_populate_idac_mag(pair[0], + &measurement->idac1_mag); + if (ret) + return ret; + + if (measurement->iadc_count > 1) { + ret = ads112c14_populate_idac_mag(pair[1], + &measurement->idac2_mag); + if (ret) + return ret; + } + } else { + /* + * Otherwise, IUINT is 10µA (flag set) and so + * IxMAG is 1/10 of the actual current. + */ + measurement->iunit = 1; + + ret = ads112c14_populate_idac_mag(pair[0] / 10, + &measurement->idac1_mag); + if (ret) + return ret; + + if (measurement->iadc_count > 1) { + ret = ads112c14_populate_idac_mag(pair[1] / 10, + &measurement->idac2_mag); + if (ret) + return ret; + } + } + } + + measurement->bipolar = fwnode_property_read_bool(child, "bipolar"); + measurement->global_chop = fwnode_property_read_bool(child, + "input-chopping"); + + if (fwnode_property_present(child, "reference-sources")) { + ret = fwnode_property_match_property_string(child, + "reference-sources", ads112c14_vref_source_names, + ARRAY_SIZE(ads112c14_vref_source_names)); + if (ret < 0) + return dev_err_probe(dev, ret, + "invalid reference-sources value\n"); + + measurement->vref_source = ret; + } + + if (measurement->vref_source == ADS112C14_VREF_SOURCE_AVDD) + *need_avdd_ref = true; + if (measurement->vref_source == ADS112C14_VREF_SOURCE_EXTERNAL) + *need_ext_ref = true; + + spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE); + spec->info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE); + + /* + * If reference source is resistor rather than voltage supply, + * then the measurement is effectively a resistance measurement. + */ + spec->type = (measurement->vref_source == ADS112C14_VREF_SOURCE_EXTERNAL && + data->ext_ref_ohms) ? IIO_RESISTANCE : IIO_VOLTAGE; + + if (spec->type == IIO_RESISTANCE) + spec->differential = 0; + + spec->scan_type = (struct iio_scan_type){ + .format = measurement->bipolar ? + IIO_SCAN_FORMAT_SIGNED_INT : + IIO_SCAN_FORMAT_UNSIGNED_INT, + .realbits = data->chip_info->resolution_bits, + .storagebits = 32, + .shift = 32 - data->chip_info->resolution_bits, + .endianness = IIO_BE, + }; + + i++; + } + + data->num_measurements = i; + if (data->num_measurements > ADS112C14_MAX_MEASUREMENT_CHANNELS) + return dev_err_probe(dev, -EINVAL, + "too many measurement channels defined\n"); + + memcpy(channels + i, ads112c14_sys_mon_channels, sizeof(ads112c14_sys_mon_channels)); + + for (u32 j = 0; j < ARRAY_SIZE(ads112c14_sys_mon_channels); j++) { + struct iio_chan_spec *spec = &channels[i]; + + /* Update the template that was already copied with dynamic values. */ + spec->scan_index = i; + spec->scan_type = (struct iio_scan_type){ + .format = IIO_SCAN_FORMAT_SIGNED_INT, + .realbits = data->chip_info->resolution_bits, + .storagebits = 32, + .shift = 32 - data->chip_info->resolution_bits, + .endianness = IIO_BE, + }; + + i++; + } + + channels[i] = IIO_CHAN_SOFT_TIMESTAMP(i); + i++; + + indio_dev->channels = channels; + indio_dev->num_channels = i; + + return 0; +} + +static void ads112c14_populate_scale_available(int (*scale_avail)[2], + u32 full_scale, u32 fsr_bits) +{ + for (u32 i = 0; i < ARRAY_SIZE(ads112c14_pga_gains_x10); i++) { + u64 gain_x10 = ads112c14_pga_gains_x10[i]; + s64 scale; + + scale = div64_u64((u64)PICO * 10U * full_scale, + gain_x10 * BIT(fsr_bits)); + + iio_val_s64_decompose(scale, &scale_avail[i][0], + &scale_avail[i][1]); + } +} + +static void ads112c14_populate_tables(struct ads112c14_data *data) +{ + u32 full_scale, fsr_bits; + + for (u32 i = 0; i < data->num_measurements; i++) { + struct ads112c14_measurement *measurement = &data->measurements[i]; + + switch (measurement->vref_source) { + case ADS112C14_VREF_SOURCE_EXTERNAL: + if (data->ext_ref_ohms) + full_scale = data->ext_ref_ohms; + else + full_scale = data->ext_ref_uV / (MICRO / MILLI); + break; + case ADS112C14_VREF_SOURCE_AVDD: + full_scale = data->avdd_uV / (MICRO / MILLI); + break; + case ADS112C14_VREF_SOURCE_INTERNAL_1_25V: + full_scale = ADS112C14_INT_REF0_mV; + break; + default: + full_scale = ADS112C14_INT_REF1_mV; + break; + } + + fsr_bits = data->chip_info->resolution_bits - measurement->bipolar; + + ads112c14_populate_scale_available(measurement->scale_available, + full_scale, fsr_bits); + } + + /* For now, assuming all sys_mon channels are using 2.5V reference. */ + full_scale = ADS112C14_INT_REF1_mV; + fsr_bits = data->chip_info->resolution_bits - 1; + + ads112c14_populate_scale_available(data->sys_mon_chan_short_scale_available, + full_scale, fsr_bits); +} + +static int ads112c14_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + const struct ads112c14_chip_info *info; + struct iio_dev *indio_dev; + struct ads112c14_data *data; + bool need_avdd_ref, need_ext_ref; + u32 refp_uV = 0; + u32 refn_uV = 0; + u32 reg_val; + int ret; + + info = i2c_get_match_data(client); + if (!info) + return dev_err_probe(dev, -ENODEV, "missing match data\n"); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->chip_info = info; + + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; + + if (device_property_present(dev, "ti,refp-refn-resistor-ohms")) { + ret = device_property_read_u32(dev, "ti,refp-refn-resistor-ohms", + &data->ext_ref_ohms); + if (ret) + return dev_err_probe(dev, ret, + "failed to read ti,refp-refn-resistor-ohms property\n"); + } + + ret = ads112c14_parse_channels(indio_dev, &need_avdd_ref, &need_ext_ref); + if (ret) + return ret; + + ret = devm_regulator_get_enable(dev, "dvdd"); + if (ret) + return dev_err_probe(dev, ret, "failed to get dvdd regulator\n"); + + if (need_avdd_ref) { + ret = devm_regulator_get_enable_read_voltage(dev, "avdd"); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get avdd voltage\n"); + + data->avdd_uV = ret; + } else { + ret = devm_regulator_get_enable(dev, "avdd"); + if (ret) + return dev_err_probe(dev, ret, "failed to get avdd regulator\n"); + } + + if (device_property_present(dev, "refp-supply")) { + ret = devm_regulator_get_enable_read_voltage(dev, "refp"); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get refp voltage\n"); + + refp_uV = ret; + + struct fwnode_handle *refp_fwnode __free(fwnode_handle) = + fwnode_find_reference(dev->fwnode, "refp-supply", 0); + if (IS_ERR(refp_fwnode)) + return dev_err_probe(dev, PTR_ERR(refp_fwnode), + "failed to get refp fwnode\n"); + + struct fwnode_handle *avdd_fwnode __free(fwnode_handle) = + fwnode_find_reference(dev->fwnode, "avdd-supply", 0); + if (IS_ERR(avdd_fwnode)) + return dev_err_probe(dev, PTR_ERR(avdd_fwnode), + "failed to get avdd fwnode\n"); + + /* REFP buffer should not be enabled when connected to AVDD */ + data->refp_is_avdd = refp_fwnode == avdd_fwnode; + } + + if (device_property_present(dev, "refn-supply")) { + ret = devm_regulator_get_enable_read_voltage(dev, "refn"); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get refn voltage\n"); + + refn_uV = ret; + } else { + data->refn_is_gnd = true; + } + + data->ext_ref_uV = refp_uV - refn_uV; + + if (data->ext_ref_uV && data->ext_ref_ohms) + return dev_err_probe(dev, -EINVAL, + "ti,refp-refn-resistor-ohms property should not be present when refp-supply or refn-supply is present\n"); + + if (need_ext_ref && !data->ext_ref_uV && !data->ext_ref_ohms) + return dev_err_probe(dev, -EINVAL, + "external reference measurements require either refp-supply or ti,refp-refn-resistor-ohms property\n"); + + /* It takes some time for the internal reference to stabilize. */ + fsleep(10 * USEC_PER_MSEC); + + data->regmap = devm_regmap_init(dev, &ads112c14_regmap_bus, data, + &ads112c14_regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "failed to init regmap\n"); + + /* + * Write magic reset value (0x16) to ensure known state. The reset may + * cause an error because of failing to get the I2C ACK at the end of + * the message. The device still gets reset so it is safe to ignore the + * return value here. If something else is wrong, later read/write will + * likely have the same error. + */ + regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL, + FIELD_PREP(ADS112C14_CONVERSION_CTRL_RESET, 0x16)); + + fsleep(ADS112C14_DELAY_RESET_US); + + ret = regmap_read(data->regmap, ADS112C14_REG_STATUS_MSB, ®_val); + if (ret) + return ret; + + if (FIELD_GET(ADS112C14_STATUS_MSB_RESETN, reg_val)) + return dev_err_probe(dev, -EIO, "reset failed\n"); + + /* Default gain after reset is 1. */ + data->sys_mon_chan_short_gain_val = 1; + + /* + * Clear reset bit to prepare for next probe. And clear AVDD fault since + * that happens on every reset. + */ + ret = regmap_write(data->regmap, ADS112C14_REG_STATUS_MSB, + ADS112C14_STATUS_MSB_RESETN | + ADS112C14_STATUS_MSB_AVDD_UVN); + if (ret) + return ret; + + ret = regmap_set_bits(data->regmap, ADS112C14_REG_DIGITAL_CFG, + ADS112C14_DIGITAL_CFG_I2C_CRC_EN); + if (ret) + return ret; + + data->i2c_crc_enabled = true; + + ret = regmap_read(data->regmap, ADS112C14_REG_DEVICE_ID, ®_val); + if (ret) + return ret; + + if (FIELD_GET(ADS112C14_DEVICE_ID_BITS, reg_val) != info->device_id) + dev_info(dev, "device ID mismatch, expected 0x%X, got 0x%lX\n", + info->device_id, + FIELD_GET(ADS112C14_DEVICE_ID_BITS, reg_val)); + + ret = regmap_update_bits(data->regmap, ADS112C14_REG_DEVICE_CFG, + ADS112C14_DEVICE_CFG_CONV_MODE, + FIELD_PREP(ADS112C14_DEVICE_CFG_CONV_MODE, + ADS112C14_DEVICE_CFG_CONV_MODE_SINGLE_SHOT)); + if (ret) + return ret; + + ads112c14_populate_tables(data); + + indio_dev->name = info->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ads112c14_info; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + ads112c14_trigger_handler, NULL); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct ads112c14_chip_info ads112c14_chip_info = { + .name = "ads112c14", + .device_id = 0xE, + .resolution_bits = 16, +}; + +static const struct ads112c14_chip_info ads122c14_chip_info = { + .name = "ads122c14", + .device_id = 0xF, + .resolution_bits = 24, +}; + +static const struct of_device_id ads112c14_of_match[] = { + { .compatible = "ti,ads112c14", .data = &ads112c14_chip_info }, + { .compatible = "ti,ads122c14", .data = &ads122c14_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ads112c14_of_match); + +static const struct i2c_device_id ads112c14_id[] = { + { .name = "ads112c14", .driver_data = (kernel_ulong_t)&ads112c14_chip_info }, + { .name = "ads122c14", .driver_data = (kernel_ulong_t)&ads122c14_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ads112c14_id); + +static int ads112c14_i2c_add_driver(struct i2c_driver *driver) +{ + crc8_populate_msb(ads112c14_crc8_table, ADS112C14_I2C_CRC8_POLYNOMIAL); + + return i2c_add_driver(driver); +} + +static struct i2c_driver ads112c14_driver = { + .driver = { + .name = "ads112c14", + .of_match_table = ads112c14_of_match, + }, + .probe = ads112c14_probe, + .id_table = ads112c14_id, +}; +module_driver(ads112c14_driver, ads112c14_i2c_add_driver, i2c_del_driver); + +MODULE_AUTHOR("David Lechner (TI) "); +MODULE_DESCRIPTION("TI ADS112C14 I2C ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/ti-ads124s08.c b/drivers/iio/adc/ti-ads124s08.c new file mode 100644 index 000000000..49fbc6350 --- /dev/null +++ b/drivers/iio/adc/ti-ads124s08.c @@ -0,0 +1,376 @@ +// SPDX-License-Identifier: GPL-2.0 +/* TI ADS124S0X chip family driver + * Copyright (C) 2018 Texas Instruments Incorporated - https://www.ti.com/ + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +/* Commands */ +#define ADS124S08_CMD_NOP 0x00 +#define ADS124S08_CMD_WAKEUP 0x02 +#define ADS124S08_CMD_PWRDWN 0x04 +#define ADS124S08_CMD_RESET 0x06 +#define ADS124S08_CMD_START 0x08 +#define ADS124S08_CMD_STOP 0x0a +#define ADS124S08_CMD_SYOCAL 0x16 +#define ADS124S08_CMD_SYGCAL 0x17 +#define ADS124S08_CMD_SFOCAL 0x19 +#define ADS124S08_CMD_RDATA 0x12 +#define ADS124S08_CMD_RREG 0x20 +#define ADS124S08_CMD_WREG 0x40 + +/* Registers */ +#define ADS124S08_ID_REG 0x00 +#define ADS124S08_STATUS 0x01 +#define ADS124S08_INPUT_MUX 0x02 +#define ADS124S08_PGA 0x03 +#define ADS124S08_DATA_RATE 0x04 +#define ADS124S08_REF 0x05 +#define ADS124S08_IDACMAG 0x06 +#define ADS124S08_IDACMUX 0x07 +#define ADS124S08_VBIAS 0x08 +#define ADS124S08_SYS 0x09 +#define ADS124S08_OFCAL0 0x0a +#define ADS124S08_OFCAL1 0x0b +#define ADS124S08_OFCAL2 0x0c +#define ADS124S08_FSCAL0 0x0d +#define ADS124S08_FSCAL1 0x0e +#define ADS124S08_FSCAL2 0x0f +#define ADS124S08_GPIODAT 0x10 +#define ADS124S08_GPIOCON 0x11 + +/* ADS124S0x common channels */ +#define ADS124S08_AIN0 0x00 +#define ADS124S08_AIN1 0x01 +#define ADS124S08_AIN2 0x02 +#define ADS124S08_AIN3 0x03 +#define ADS124S08_AIN4 0x04 +#define ADS124S08_AIN5 0x05 +#define ADS124S08_AINCOM 0x0c +/* ADS124S08 only channels */ +#define ADS124S08_AIN6 0x06 +#define ADS124S08_AIN7 0x07 +#define ADS124S08_AIN8 0x08 +#define ADS124S08_AIN9 0x09 +#define ADS124S08_AIN10 0x0a +#define ADS124S08_AIN11 0x0b +#define ADS124S08_MAX_CHANNELS 12 + +#define ADS124S08_POS_MUX_SHIFT 0x04 +#define ADS124S08_INT_REF 0x09 + +#define ADS124S08_START_REG_MASK 0x1f +#define ADS124S08_NUM_BYTES_MASK 0x1f + +#define ADS124S08_START_CONV 0x01 +#define ADS124S08_STOP_CONV 0x00 + +enum ads124s_id { + ADS124S08_ID, + ADS124S06_ID, +}; + +struct ads124s_chip_info { + const struct iio_chan_spec *channels; + unsigned int num_channels; +}; + +struct ads124s_private { + const struct ads124s_chip_info *chip_info; + struct gpio_desc *reset_gpio; + struct spi_device *spi; + struct mutex lock; + /* + * Used to correctly align data. + * Ensure timestamp is naturally aligned. + * Note that the full buffer length may not be needed if not + * all channels are enabled, as long as the alignment of the + * timestamp is maintained. + */ + u32 buffer[ADS124S08_MAX_CHANNELS + sizeof(s64)/sizeof(u32)] __aligned(8); + u8 data[5] __aligned(IIO_DMA_MINALIGN); +}; + +#define ADS124S08_CHAN(index) \ +{ \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .scan_index = index, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 32, \ + .storagebits = 32, \ + }, \ +} + +static const struct iio_chan_spec ads124s06_channels[] = { + ADS124S08_CHAN(0), + ADS124S08_CHAN(1), + ADS124S08_CHAN(2), + ADS124S08_CHAN(3), + ADS124S08_CHAN(4), + ADS124S08_CHAN(5), +}; + +static const struct iio_chan_spec ads124s08_channels[] = { + ADS124S08_CHAN(0), + ADS124S08_CHAN(1), + ADS124S08_CHAN(2), + ADS124S08_CHAN(3), + ADS124S08_CHAN(4), + ADS124S08_CHAN(5), + ADS124S08_CHAN(6), + ADS124S08_CHAN(7), + ADS124S08_CHAN(8), + ADS124S08_CHAN(9), + ADS124S08_CHAN(10), + ADS124S08_CHAN(11), +}; + +static const struct ads124s_chip_info ads124s_chip_info_tbl[] = { + [ADS124S08_ID] = { + .channels = ads124s08_channels, + .num_channels = ARRAY_SIZE(ads124s08_channels), + }, + [ADS124S06_ID] = { + .channels = ads124s06_channels, + .num_channels = ARRAY_SIZE(ads124s06_channels), + }, +}; + +static int ads124s_write_cmd(struct iio_dev *indio_dev, u8 command) +{ + struct ads124s_private *priv = iio_priv(indio_dev); + + priv->data[0] = command; + + return spi_write(priv->spi, &priv->data[0], 1); +} + +static int ads124s_write_reg(struct iio_dev *indio_dev, u8 reg, u8 data) +{ + struct ads124s_private *priv = iio_priv(indio_dev); + + priv->data[0] = ADS124S08_CMD_WREG | reg; + priv->data[1] = 0x0; + priv->data[2] = data; + + return spi_write(priv->spi, &priv->data[0], 3); +} + +static int ads124s_reset(struct iio_dev *indio_dev) +{ + struct ads124s_private *priv = iio_priv(indio_dev); + + if (priv->reset_gpio) { + gpiod_set_value_cansleep(priv->reset_gpio, 0); + fsleep(200); + gpiod_set_value_cansleep(priv->reset_gpio, 1); + } else { + return ads124s_write_cmd(indio_dev, ADS124S08_CMD_RESET); + } + + return 0; +}; + +static int ads124s_read(struct iio_dev *indio_dev) +{ + struct ads124s_private *priv = iio_priv(indio_dev); + int ret; + struct spi_transfer t[] = { + { + .tx_buf = &priv->data[0], + .len = 4, + .cs_change = 1, + }, { + .tx_buf = &priv->data[1], + .rx_buf = &priv->data[1], + .len = 4, + }, + }; + + priv->data[0] = ADS124S08_CMD_RDATA; + memset(&priv->data[1], ADS124S08_CMD_NOP, sizeof(priv->data) - 1); + + ret = spi_sync_transfer(priv->spi, t, ARRAY_SIZE(t)); + if (ret < 0) + return ret; + + return get_unaligned_be24(&priv->data[2]); +} + +static int ads124s_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long m) +{ + struct ads124s_private *priv = iio_priv(indio_dev); + int ret; + + mutex_lock(&priv->lock); + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = ads124s_write_reg(indio_dev, ADS124S08_INPUT_MUX, + chan->channel); + if (ret) { + dev_err(&priv->spi->dev, "Set ADC CH failed\n"); + goto out; + } + + ret = ads124s_write_cmd(indio_dev, ADS124S08_START_CONV); + if (ret) { + dev_err(&priv->spi->dev, "Start conversions failed\n"); + goto out; + } + + ret = ads124s_read(indio_dev); + if (ret < 0) { + dev_err(&priv->spi->dev, "Read ADC failed\n"); + goto out; + } + + *val = ret; + + ret = ads124s_write_cmd(indio_dev, ADS124S08_STOP_CONV); + if (ret) { + dev_err(&priv->spi->dev, "Stop conversions failed\n"); + goto out; + } + + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + break; + } +out: + mutex_unlock(&priv->lock); + return ret; +} + +static const struct iio_info ads124s_info = { + .read_raw = &ads124s_read_raw, +}; + +static irqreturn_t ads124s_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ads124s_private *priv = iio_priv(indio_dev); + int scan_index, j = 0; + int ret; + + iio_for_each_active_channel(indio_dev, scan_index) { + ret = ads124s_write_reg(indio_dev, ADS124S08_INPUT_MUX, + scan_index); + if (ret) + dev_err(&priv->spi->dev, "Set ADC CH failed\n"); + + ret = ads124s_write_cmd(indio_dev, ADS124S08_START_CONV); + if (ret) + dev_err(&priv->spi->dev, "Start ADC conversions failed\n"); + + priv->buffer[j] = ads124s_read(indio_dev); + ret = ads124s_write_cmd(indio_dev, ADS124S08_STOP_CONV); + if (ret) + dev_err(&priv->spi->dev, "Stop ADC conversions failed\n"); + + j++; + } + + iio_push_to_buffers_with_ts(indio_dev, priv->buffer, sizeof(priv->buffer), + pf->timestamp); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ads124s_probe(struct spi_device *spi) +{ + struct ads124s_private *ads124s_priv; + struct iio_dev *indio_dev; + const struct spi_device_id *spi_id = spi_get_device_id(spi); + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*ads124s_priv)); + if (indio_dev == NULL) + return -ENOMEM; + + ads124s_priv = iio_priv(indio_dev); + + ads124s_priv->reset_gpio = devm_gpiod_get_optional(&spi->dev, + "reset", GPIOD_OUT_LOW); + if (IS_ERR(ads124s_priv->reset_gpio)) + return dev_err_probe(&spi->dev, PTR_ERR(ads124s_priv->reset_gpio), + "Failed to get reset GPIO\n"); + + ads124s_priv->chip_info = &ads124s_chip_info_tbl[spi_id->driver_data]; + + ads124s_priv->spi = spi; + + indio_dev->name = spi_id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ads124s_priv->chip_info->channels; + indio_dev->num_channels = ads124s_priv->chip_info->num_channels; + indio_dev->info = &ads124s_info; + + mutex_init(&ads124s_priv->lock); + + ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, NULL, + ads124s_trigger_handler, NULL); + if (ret) { + dev_err(&spi->dev, "iio triggered buffer setup failed\n"); + return ret; + } + + ads124s_reset(indio_dev); + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id ads124s_id[] = { + { .name = "ads124s06", .driver_data = ADS124S06_ID }, + { .name = "ads124s08", .driver_data = ADS124S08_ID }, + { } +}; +MODULE_DEVICE_TABLE(spi, ads124s_id); + +static const struct of_device_id ads124s_of_table[] = { + { .compatible = "ti,ads124s06" }, + { .compatible = "ti,ads124s08" }, + { } +}; +MODULE_DEVICE_TABLE(of, ads124s_of_table); + +static struct spi_driver ads124s_driver = { + .driver = { + .name = "ads124s08", + .of_match_table = ads124s_of_table, + }, + .probe = ads124s_probe, + .id_table = ads124s_id, +}; +module_spi_driver(ads124s_driver); + +MODULE_AUTHOR("Dan Murphy "); +MODULE_DESCRIPTION("TI TI_ADS12S0X ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ti-ads1298.c b/drivers/iio/adc/ti-ads1298.c new file mode 100644 index 000000000..06c6353aa --- /dev/null +++ b/drivers/iio/adc/ti-ads1298.c @@ -0,0 +1,769 @@ +// SPDX-License-Identifier: GPL-2.0 +/* TI ADS1298 chip family driver + * Copyright (C) 2023 - 2024 Topic Embedded Products + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +/* Commands */ +#define ADS1298_CMD_WAKEUP 0x02 +#define ADS1298_CMD_STANDBY 0x04 +#define ADS1298_CMD_RESET 0x06 +#define ADS1298_CMD_START 0x08 +#define ADS1298_CMD_STOP 0x0a +#define ADS1298_CMD_RDATAC 0x10 +#define ADS1298_CMD_SDATAC 0x11 +#define ADS1298_CMD_RDATA 0x12 +#define ADS1298_CMD_RREG 0x20 +#define ADS1298_CMD_WREG 0x40 + +/* Registers */ +#define ADS1298_REG_ID 0x00 +#define ADS1298_MASK_ID_FAMILY GENMASK(7, 3) +#define ADS1298_MASK_ID_CHANNELS GENMASK(2, 0) +#define ADS1298_ID_FAMILY_ADS129X 0x90 +#define ADS1298_ID_FAMILY_ADS129XR 0xd0 + +#define ADS1298_REG_CONFIG1 0x01 +#define ADS1298_MASK_CONFIG1_HR BIT(7) +#define ADS1298_MASK_CONFIG1_DR GENMASK(2, 0) +#define ADS1298_SHIFT_DR_HR 6 +#define ADS1298_SHIFT_DR_LP 7 +#define ADS1298_LOWEST_DR 0x06 + +#define ADS1298_REG_CONFIG2 0x02 +#define ADS1298_MASK_CONFIG2_RESERVED BIT(6) +#define ADS1298_MASK_CONFIG2_WCT_CHOP BIT(5) +#define ADS1298_MASK_CONFIG2_INT_TEST BIT(4) +#define ADS1298_MASK_CONFIG2_TEST_AMP BIT(2) +#define ADS1298_MASK_CONFIG2_TEST_FREQ_DC GENMASK(1, 0) +#define ADS1298_MASK_CONFIG2_TEST_FREQ_SLOW 0 +#define ADS1298_MASK_CONFIG2_TEST_FREQ_FAST BIT(0) + +#define ADS1298_REG_CONFIG3 0x03 +#define ADS1298_MASK_CONFIG3_PWR_REFBUF BIT(7) +#define ADS1298_MASK_CONFIG3_RESERVED BIT(6) +#define ADS1298_MASK_CONFIG3_VREF_4V BIT(5) + +#define ADS1298_REG_LOFF 0x04 +#define ADS1298_REG_CHnSET(n) (0x05 + (n)) +#define ADS1298_MASK_CH_PD BIT(7) +#define ADS1298_MASK_CH_PGA GENMASK(6, 4) +#define ADS1298_MASK_CH_MUX GENMASK(2, 0) + +#define ADS1298_REG_LOFF_STATP 0x12 +#define ADS1298_REG_LOFF_STATN 0x13 +#define ADS1298_REG_CONFIG4 0x17 +#define ADS1298_MASK_CONFIG4_SINGLE_SHOT BIT(3) + +#define ADS1298_REG_WCT1 0x18 +#define ADS1298_REG_WCT2 0x19 + +#define ADS1298_MAX_CHANNELS 8 +#define ADS1298_BITS_PER_SAMPLE 24 +#define ADS1298_CLK_RATE_HZ 2048000 +#define ADS1298_CLOCKS_TO_USECS(x) \ + (DIV_ROUND_UP((x) * MICROHZ_PER_HZ, ADS1298_CLK_RATE_HZ)) +/* + * Read/write register commands require 4 clocks to decode, for speeds above + * 2x the clock rate, this would require extra time between the command byte and + * the data. Much simpler is to just limit the SPI transfer speed while doing + * register access. + */ +#define ADS1298_SPI_BUS_SPEED_SLOW ADS1298_CLK_RATE_HZ +/* For reading and writing registers, we need a 3-byte buffer */ +#define ADS1298_SPI_CMD_BUFFER_SIZE 3 +/* Outputs status word and 'n' 24-bit samples, plus the command byte */ +#define ADS1298_SPI_RDATA_BUFFER_SIZE(n) (((n) + 1) * 3 + 1) +#define ADS1298_SPI_RDATA_BUFFER_SIZE_MAX \ + ADS1298_SPI_RDATA_BUFFER_SIZE(ADS1298_MAX_CHANNELS) + +struct ads1298_private { + const struct ads1298_chip_info *chip_info; + struct spi_device *spi; + struct regulator *reg_avdd; + struct regulator *reg_vref; + struct clk *clk; + struct regmap *regmap; + struct completion completion; + struct iio_trigger *trig; + struct spi_transfer rdata_xfer; + struct spi_message rdata_msg; + spinlock_t irq_busy_lock; /* Handshake between SPI and DRDY irqs */ + /* + * rdata_xfer_busy increments when a DRDY occurs and decrements when SPI + * completion is reported. Hence its meaning is: + * 0 = Waiting for DRDY interrupt + * 1 = SPI transfer in progress + * 2 = DRDY during SPI transfer, start another transfer on completion + * >2 = Multiple DRDY during transfer, lost rdata_xfer_busy - 2 samples + */ + unsigned int rdata_xfer_busy; + + /* Temporary storage for demuxing data after SPI transfer */ + u32 bounce_buffer[ADS1298_MAX_CHANNELS]; + + /* For synchronous SPI exchanges (read/write registers) */ + u8 cmd_buffer[ADS1298_SPI_CMD_BUFFER_SIZE] __aligned(IIO_DMA_MINALIGN); + + /* Buffer used for incoming SPI data */ + u8 rx_buffer[ADS1298_SPI_RDATA_BUFFER_SIZE_MAX]; + /* Contains the RDATA command and zeroes to clock out */ + u8 tx_buffer[ADS1298_SPI_RDATA_BUFFER_SIZE_MAX]; +}; + +/* Three bytes per sample in RX buffer, starting at offset 4 */ +#define ADS1298_OFFSET_IN_RX_BUFFER(index) (3 * (index) + 4) + +#define ADS1298_CHAN(index) \ +{ \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = index, \ + .address = ADS1298_OFFSET_IN_RX_BUFFER(index), \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = ADS1298_BITS_PER_SAMPLE, \ + .storagebits = 32, \ + .endianness = IIO_CPU, \ + }, \ +} + +static const struct iio_chan_spec ads1298_channels[] = { + ADS1298_CHAN(0), + ADS1298_CHAN(1), + ADS1298_CHAN(2), + ADS1298_CHAN(3), + ADS1298_CHAN(4), + ADS1298_CHAN(5), + ADS1298_CHAN(6), + ADS1298_CHAN(7), +}; + +static int ads1298_write_cmd(struct ads1298_private *priv, u8 command) +{ + struct spi_transfer xfer = { + .tx_buf = priv->cmd_buffer, + .rx_buf = priv->cmd_buffer, + .len = 1, + .speed_hz = ADS1298_SPI_BUS_SPEED_SLOW, + .delay = { + .value = 2, + .unit = SPI_DELAY_UNIT_USECS, + }, + }; + + priv->cmd_buffer[0] = command; + + return spi_sync_transfer(priv->spi, &xfer, 1); +} + +static int ads1298_read_one(struct ads1298_private *priv, int chan_index) +{ + int ret; + + /* Enable the channel */ + ret = regmap_update_bits(priv->regmap, ADS1298_REG_CHnSET(chan_index), + ADS1298_MASK_CH_PD, 0); + if (ret) + return ret; + + /* Enable single-shot mode, so we don't need to send a STOP */ + ret = regmap_update_bits(priv->regmap, ADS1298_REG_CONFIG4, + ADS1298_MASK_CONFIG4_SINGLE_SHOT, + ADS1298_MASK_CONFIG4_SINGLE_SHOT); + if (ret) + return ret; + + reinit_completion(&priv->completion); + + ret = ads1298_write_cmd(priv, ADS1298_CMD_START); + if (ret < 0) { + dev_err(&priv->spi->dev, "CMD_START error: %d\n", ret); + return ret; + } + + /* + * One conversion takes at most 4ms at the lowest sample rate (250Hz). + * Use 50ms to allow for kernel scheduling latency. + */ + if (!wait_for_completion_timeout(&priv->completion, msecs_to_jiffies(50))) + return -ETIMEDOUT; + + return 0; +} + +static int ads1298_get_samp_freq(struct ads1298_private *priv, int *val) +{ + unsigned long rate; + unsigned int cfg; + int ret; + + ret = regmap_read(priv->regmap, ADS1298_REG_CONFIG1, &cfg); + if (ret) + return ret; + + if (priv->clk) + rate = clk_get_rate(priv->clk); + else + rate = ADS1298_CLK_RATE_HZ; + if (!rate) + return -EINVAL; + + /* Data rate shift depends on HR/LP mode */ + if (cfg & ADS1298_MASK_CONFIG1_HR) + rate >>= ADS1298_SHIFT_DR_HR; + else + rate >>= ADS1298_SHIFT_DR_LP; + + *val = rate >> (cfg & ADS1298_MASK_CONFIG1_DR); + + return IIO_VAL_INT; +} + +static int ads1298_set_samp_freq(struct ads1298_private *priv, int val) +{ + unsigned long rate; + unsigned int factor; + unsigned int cfg; + + if (priv->clk) + rate = clk_get_rate(priv->clk); + else + rate = ADS1298_CLK_RATE_HZ; + if (!rate) + return -EINVAL; + if (val <= 0) + return -EINVAL; + + factor = (rate >> ADS1298_SHIFT_DR_HR) / val; + if (factor >= BIT(ADS1298_SHIFT_DR_LP)) + cfg = ADS1298_LOWEST_DR; + else if (factor) + cfg = ADS1298_MASK_CONFIG1_HR | ilog2(factor); /* Use HR mode */ + else + cfg = ADS1298_MASK_CONFIG1_HR; /* Fastest possible */ + + return regmap_update_bits(priv->regmap, ADS1298_REG_CONFIG1, + ADS1298_MASK_CONFIG1_HR | ADS1298_MASK_CONFIG1_DR, + cfg); +} + +static const u8 ads1298_pga_settings[] = { 6, 1, 2, 3, 4, 8, 12 }; + +static int ads1298_get_scale(struct ads1298_private *priv, + int channel, int *val, int *val2) +{ + unsigned int pga_idx; + int ret; + unsigned int regval; + u8 gain; + + if (priv->reg_vref) { + ret = regulator_get_voltage(priv->reg_vref); + if (ret < 0) + return ret; + + *val = ret / MILLI; /* Convert to millivolts */ + } else { + ret = regmap_read(priv->regmap, ADS1298_REG_CONFIG3, ®val); + if (ret) + return ret; + + /* Reference in millivolts */ + *val = regval & ADS1298_MASK_CONFIG3_VREF_4V ? 4000 : 2400; + } + + ret = regmap_read(priv->regmap, ADS1298_REG_CHnSET(channel), ®val); + if (ret) + return ret; + + pga_idx = FIELD_GET(ADS1298_MASK_CH_PGA, regval); + if (pga_idx >= ARRAY_SIZE(ads1298_pga_settings)) + return -EINVAL; + + gain = ads1298_pga_settings[pga_idx]; + *val /= gain; /* Full scale is VREF / gain */ + + *val2 = ADS1298_BITS_PER_SAMPLE - 1; /* Signed, hence the -1 */ + + return IIO_VAL_FRACTIONAL_LOG2; +} + +static int ads1298_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ads1298_private *priv = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ads1298_read_one(priv, chan->scan_index); + + iio_device_release_direct(indio_dev); + + if (ret) + return ret; + + *val = sign_extend32(get_unaligned_be24(priv->rx_buffer + chan->address), + ADS1298_BITS_PER_SAMPLE - 1); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + return ads1298_get_scale(priv, chan->channel, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return ads1298_get_samp_freq(priv, val); + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + ret = regmap_read(priv->regmap, ADS1298_REG_CONFIG1, val); + if (ret) + return ret; + + *val = 16 << (*val & ADS1298_MASK_CONFIG1_DR); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ads1298_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct ads1298_private *priv = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return ads1298_set_samp_freq(priv, val); + default: + return -EINVAL; + } +} + +static int ads1298_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct ads1298_private *priv = context; + struct spi_transfer reg_write_xfer = { + .tx_buf = priv->cmd_buffer, + .rx_buf = priv->cmd_buffer, + .len = 3, + .speed_hz = ADS1298_SPI_BUS_SPEED_SLOW, + .delay = { + .value = 2, + .unit = SPI_DELAY_UNIT_USECS, + }, + }; + + priv->cmd_buffer[0] = ADS1298_CMD_WREG | reg; + priv->cmd_buffer[1] = 0; /* Number of registers to be written - 1 */ + priv->cmd_buffer[2] = val; + + return spi_sync_transfer(priv->spi, ®_write_xfer, 1); +} + +static int ads1298_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + struct ads1298_private *priv = context; + struct spi_transfer reg_read_xfer = { + .tx_buf = priv->cmd_buffer, + .rx_buf = priv->cmd_buffer, + .len = 3, + .speed_hz = ADS1298_SPI_BUS_SPEED_SLOW, + .delay = { + .value = 2, + .unit = SPI_DELAY_UNIT_USECS, + }, + }; + int ret; + + priv->cmd_buffer[0] = ADS1298_CMD_RREG | reg; + priv->cmd_buffer[1] = 0; /* Number of registers to be read - 1 */ + priv->cmd_buffer[2] = 0; + + ret = spi_sync_transfer(priv->spi, ®_read_xfer, 1); + if (ret) + return ret; + + *val = priv->cmd_buffer[2]; + + return 0; +} + +static int ads1298_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct ads1298_private *priv = iio_priv(indio_dev); + + if (readval) + return regmap_read(priv->regmap, reg, readval); + + return regmap_write(priv->regmap, reg, writeval); +} + +static void ads1298_rdata_unmark_busy(struct ads1298_private *priv) +{ + /* Notify we're no longer waiting for the SPI transfer to complete */ + guard(spinlock_irqsave)(&priv->irq_busy_lock); + priv->rdata_xfer_busy = 0; +} + +static int ads1298_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ads1298_private *priv = iio_priv(indio_dev); + unsigned int val; + int ret; + int i; + + /* Make the interrupt routines start with a clean slate */ + ads1298_rdata_unmark_busy(priv); + + /* Configure power-down bits to match scan mask */ + for (i = 0; i < indio_dev->num_channels; i++) { + val = test_bit(i, scan_mask) ? 0 : ADS1298_MASK_CH_PD; + ret = regmap_update_bits(priv->regmap, ADS1298_REG_CHnSET(i), + ADS1298_MASK_CH_PD, val); + if (ret) + return ret; + } + + return 0; +} + +static const struct iio_info ads1298_info = { + .read_raw = &ads1298_read_raw, + .write_raw = &ads1298_write_raw, + .update_scan_mode = &ads1298_update_scan_mode, + .debugfs_reg_access = &ads1298_reg_access, +}; + +static void ads1298_rdata_release_busy_or_restart(struct ads1298_private *priv) +{ + guard(spinlock_irqsave)(&priv->irq_busy_lock); + + if (priv->rdata_xfer_busy > 1) { + /* + * DRDY interrupt occurred before SPI completion. Start a new + * SPI transaction now to retrieve the data that wasn't latched + * into the ADS1298 chip's transfer buffer yet. + */ + spi_async(priv->spi, &priv->rdata_msg); + /* + * If more than one DRDY took place, there was an overrun. Since + * the sample is already lost, reset the counter to 1 so that + * we will wait for a DRDY interrupt after this SPI transaction. + */ + priv->rdata_xfer_busy = 1; + } else { + /* No pending data, wait for DRDY */ + priv->rdata_xfer_busy = 0; + } +} + +/* Called from SPI completion interrupt handler */ +static void ads1298_rdata_complete(void *context) +{ + struct iio_dev *indio_dev = context; + struct ads1298_private *priv = iio_priv(indio_dev); + int scan_index; + u32 *bounce = priv->bounce_buffer; + + if (!iio_buffer_enabled(indio_dev)) { + /* + * for a single transfer mode we're kept in direct_mode until + * completion, avoiding a race with buffered IO. + */ + ads1298_rdata_unmark_busy(priv); + complete(&priv->completion); + return; + } + + /* Demux the channel data into our bounce buffer */ + iio_for_each_active_channel(indio_dev, scan_index) { + const struct iio_chan_spec *scan_chan = + &indio_dev->channels[scan_index]; + const u8 *data = priv->rx_buffer + scan_chan->address; + + *bounce++ = get_unaligned_be24(data); + } + + /* rx_buffer can be overwritten from this point on */ + ads1298_rdata_release_busy_or_restart(priv); + + iio_push_to_buffers(indio_dev, priv->bounce_buffer); +} + +static irqreturn_t ads1298_interrupt(int irq, void *dev_id) +{ + struct iio_dev *indio_dev = dev_id; + struct ads1298_private *priv = iio_priv(indio_dev); + unsigned int wasbusy; + + guard(spinlock_irqsave)(&priv->irq_busy_lock); + + wasbusy = priv->rdata_xfer_busy++; + /* When no SPI transfer in transit, start one now */ + if (!wasbusy) + spi_async(priv->spi, &priv->rdata_msg); + + return IRQ_HANDLED; +}; + +static int ads1298_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ads1298_private *priv = iio_priv(indio_dev); + int ret; + + /* Disable single-shot mode */ + ret = regmap_update_bits(priv->regmap, ADS1298_REG_CONFIG4, + ADS1298_MASK_CONFIG4_SINGLE_SHOT, 0); + if (ret) + return ret; + + return ads1298_write_cmd(priv, ADS1298_CMD_START); +} + +static int ads1298_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ads1298_private *priv = iio_priv(indio_dev); + + return ads1298_write_cmd(priv, ADS1298_CMD_STOP); +} + +static const struct iio_buffer_setup_ops ads1298_setup_ops = { + .postenable = &ads1298_buffer_postenable, + .predisable = &ads1298_buffer_predisable, +}; + +static void ads1298_reg_disable(void *reg) +{ + regulator_disable(reg); +} + +static const struct regmap_range ads1298_regmap_volatile_range[] = { + regmap_reg_range(ADS1298_REG_LOFF_STATP, ADS1298_REG_LOFF_STATN), +}; + +static const struct regmap_access_table ads1298_regmap_volatile = { + .yes_ranges = ads1298_regmap_volatile_range, + .n_yes_ranges = ARRAY_SIZE(ads1298_regmap_volatile_range), +}; + +static const struct regmap_config ads1298_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .reg_read = ads1298_reg_read, + .reg_write = ads1298_reg_write, + .max_register = ADS1298_REG_WCT2, + .volatile_table = &ads1298_regmap_volatile, + .cache_type = REGCACHE_MAPLE, +}; + +static int ads1298_init(struct iio_dev *indio_dev) +{ + struct ads1298_private *priv = iio_priv(indio_dev); + struct device *dev = &priv->spi->dev; + const char *suffix; + unsigned int val; + int ret; + + /* Device initializes into RDATAC mode, which we don't want */ + ret = ads1298_write_cmd(priv, ADS1298_CMD_SDATAC); + if (ret) + return ret; + + ret = regmap_read(priv->regmap, ADS1298_REG_ID, &val); + if (ret) + return ret; + + /* Fill in name and channel count based on what the chip told us */ + indio_dev->num_channels = 4 + 2 * (val & ADS1298_MASK_ID_CHANNELS); + switch (val & ADS1298_MASK_ID_FAMILY) { + case ADS1298_ID_FAMILY_ADS129X: + suffix = ""; + break; + case ADS1298_ID_FAMILY_ADS129XR: + suffix = "r"; + break; + default: + return dev_err_probe(dev, -ENODEV, "Unknown ID: 0x%x\n", val); + } + indio_dev->name = devm_kasprintf(dev, GFP_KERNEL, "ads129%u%s", + indio_dev->num_channels, suffix); + if (!indio_dev->name) + return -ENOMEM; + + val = ADS1298_MASK_CONFIG3_RESERVED; /* Must write 1 always */ + if (!priv->reg_vref) { + /* Enable internal reference */ + val |= ADS1298_MASK_CONFIG3_PWR_REFBUF; + /* Use 4V VREF when power supply is at least 4.4V */ + if (regulator_get_voltage(priv->reg_avdd) >= 4400000) + val |= ADS1298_MASK_CONFIG3_VREF_4V; + } + return regmap_write(priv->regmap, ADS1298_REG_CONFIG3, val); +} + +static int ads1298_probe(struct spi_device *spi) +{ + struct ads1298_private *priv; + struct iio_dev *indio_dev; + struct device *dev = &spi->dev; + struct gpio_desc *reset_gpio; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + priv = iio_priv(indio_dev); + + /* Reset to be asserted before enabling clock and power */ + reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(reset_gpio)) + return dev_err_probe(dev, PTR_ERR(reset_gpio), + "Cannot get reset GPIO\n"); + + /* VREF can be supplied externally, otherwise use internal reference */ + priv->reg_vref = devm_regulator_get_optional(dev, "vref"); + if (IS_ERR(priv->reg_vref)) { + if (PTR_ERR(priv->reg_vref) != -ENODEV) + return dev_err_probe(dev, PTR_ERR(priv->reg_vref), + "Failed to get vref regulator\n"); + + priv->reg_vref = NULL; + } else { + ret = regulator_enable(priv->reg_vref); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, ads1298_reg_disable, priv->reg_vref); + if (ret) + return ret; + } + + priv->clk = devm_clk_get_optional_enabled(dev, "clk"); + if (IS_ERR(priv->clk)) + return dev_err_probe(dev, PTR_ERR(priv->clk), "Failed to get clk\n"); + + priv->reg_avdd = devm_regulator_get(dev, "avdd"); + if (IS_ERR(priv->reg_avdd)) + return dev_err_probe(dev, PTR_ERR(priv->reg_avdd), + "Failed to get avdd regulator\n"); + + ret = regulator_enable(priv->reg_avdd); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable avdd regulator\n"); + + ret = devm_add_action_or_reset(dev, ads1298_reg_disable, priv->reg_avdd); + if (ret) + return ret; + + priv->spi = spi; + init_completion(&priv->completion); + spin_lock_init(&priv->irq_busy_lock); + priv->regmap = devm_regmap_init(dev, NULL, priv, &ads1298_regmap_config); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + indio_dev->channels = ads1298_channels; + indio_dev->info = &ads1298_info; + + if (reset_gpio) { + /* + * Deassert reset now that clock and power are active. + * Minimum reset pulsewidth is 2 clock cycles. + */ + fsleep(ADS1298_CLOCKS_TO_USECS(2)); + gpiod_set_value_cansleep(reset_gpio, 0); + } else { + ret = ads1298_write_cmd(priv, ADS1298_CMD_RESET); + if (ret) + return dev_err_probe(dev, ret, "RESET failed\n"); + } + /* Wait 18 clock cycles for reset command to complete */ + fsleep(ADS1298_CLOCKS_TO_USECS(18)); + + ret = ads1298_init(indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Init failed\n"); + + priv->tx_buffer[0] = ADS1298_CMD_RDATA; + priv->rdata_xfer.tx_buf = priv->tx_buffer; + priv->rdata_xfer.rx_buf = priv->rx_buffer; + priv->rdata_xfer.len = ADS1298_SPI_RDATA_BUFFER_SIZE(indio_dev->num_channels); + /* Must keep CS low for 4 clocks */ + priv->rdata_xfer.delay.value = 2; + priv->rdata_xfer.delay.unit = SPI_DELAY_UNIT_USECS; + spi_message_init_with_transfers(&priv->rdata_msg, &priv->rdata_xfer, 1); + priv->rdata_msg.complete = &ads1298_rdata_complete; + priv->rdata_msg.context = indio_dev; + + ret = devm_request_irq(dev, spi->irq, &ads1298_interrupt, + IRQF_TRIGGER_FALLING, indio_dev->name, + indio_dev); + if (ret) + return ret; + + ret = devm_iio_kfifo_buffer_setup(dev, indio_dev, &ads1298_setup_ops); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id ads1298_id[] = { + { .name = "ads1298" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ads1298_id); + +static const struct of_device_id ads1298_of_table[] = { + { .compatible = "ti,ads1298" }, + { } +}; +MODULE_DEVICE_TABLE(of, ads1298_of_table); + +static struct spi_driver ads1298_driver = { + .driver = { + .name = "ads1298", + .of_match_table = ads1298_of_table, + }, + .probe = ads1298_probe, + .id_table = ads1298_id, +}; +module_spi_driver(ads1298_driver); + +MODULE_AUTHOR("Mike Looijmans "); +MODULE_DESCRIPTION("TI ADS1298 ADC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/ti-ads131e08.c b/drivers/iio/adc/ti-ads131e08.c new file mode 100644 index 000000000..1d25f887c --- /dev/null +++ b/drivers/iio/adc/ti-ads131e08.c @@ -0,0 +1,939 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Texas Instruments ADS131E0x 4-, 6- and 8-Channel ADCs + * + * Copyright (c) 2020 AVL DiTEST GmbH + * Tomislav Denis + * + * Datasheet: https://www.ti.com/lit/ds/symlink/ads131e08.pdf + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +/* Commands */ +#define ADS131E08_CMD_RESET 0x06 +#define ADS131E08_CMD_START 0x08 +#define ADS131E08_CMD_STOP 0x0A +#define ADS131E08_CMD_OFFSETCAL 0x1A +#define ADS131E08_CMD_SDATAC 0x11 +#define ADS131E08_CMD_RDATA 0x12 +#define ADS131E08_CMD_RREG(r) (BIT(5) | (r & GENMASK(4, 0))) +#define ADS131E08_CMD_WREG(r) (BIT(6) | (r & GENMASK(4, 0))) + +/* Registers */ +#define ADS131E08_ADR_CFG1R 0x01 +#define ADS131E08_ADR_CFG3R 0x03 +#define ADS131E08_ADR_CH0R 0x05 + +/* Configuration register 1 */ +#define ADS131E08_CFG1R_DR_MASK GENMASK(2, 0) + +/* Configuration register 3 */ +#define ADS131E08_CFG3R_PDB_REFBUF_MASK BIT(7) +#define ADS131E08_CFG3R_VREF_4V_MASK BIT(5) + +/* Channel settings register */ +#define ADS131E08_CHR_GAIN_MASK GENMASK(6, 4) +#define ADS131E08_CHR_MUX_MASK GENMASK(2, 0) +#define ADS131E08_CHR_PWD_MASK BIT(7) + +/* ADC misc */ +#define ADS131E08_DEFAULT_DATA_RATE 1 +#define ADS131E08_DEFAULT_PGA_GAIN 1 +#define ADS131E08_DEFAULT_MUX 0 + +#define ADS131E08_VREF_2V4_mV 2400 +#define ADS131E08_VREF_4V_mV 4000 + +#define ADS131E08_WAIT_RESET_CYCLES 18 +#define ADS131E08_WAIT_SDECODE_CYCLES 4 +#define ADS131E08_WAIT_OFFSETCAL_MS 153 +#define ADS131E08_MAX_SETTLING_TIME_MS 6 + +#define ADS131E08_NUM_STATUS_BYTES 3 +#define ADS131E08_NUM_DATA_BYTES_MAX 24 +#define ADS131E08_NUM_DATA_BYTES(dr) (((dr) >= 32) ? 2 : 3) +#define ADS131E08_NUM_DATA_BITS(dr) (ADS131E08_NUM_DATA_BYTES(dr) * 8) +#define ADS131E08_NUM_STORAGE_BYTES 4 + +enum ads131e08_ids { + ads131e04, + ads131e06, + ads131e08, +}; + +struct ads131e08_info { + unsigned int max_channels; + const char *name; +}; + +struct ads131e08_channel_config { + unsigned int pga_gain; + unsigned int mux; +}; + +struct ads131e08_state { + const struct ads131e08_info *info; + struct spi_device *spi; + struct iio_trigger *trig; + struct clk *adc_clk; + struct regulator *vref_reg; + struct ads131e08_channel_config *channel_config; + unsigned int data_rate; + unsigned int vref_mv; + unsigned int sdecode_delay_us; + unsigned int reset_delay_us; + unsigned int readback_len; + struct completion completion; + struct { + u8 data[ADS131E08_NUM_DATA_BYTES_MAX]; + aligned_s64 ts; + } tmp_buf; + + u8 tx_buf[3] __aligned(IIO_DMA_MINALIGN); + /* + * Add extra one padding byte to be able to access the last channel + * value using u32 pointer + */ + u8 rx_buf[ADS131E08_NUM_STATUS_BYTES + + ADS131E08_NUM_DATA_BYTES_MAX + 1]; +}; + +static const struct ads131e08_info ads131e08_info_tbl[] = { + [ads131e04] = { + .max_channels = 4, + .name = "ads131e04", + }, + [ads131e06] = { + .max_channels = 6, + .name = "ads131e06", + }, + [ads131e08] = { + .max_channels = 8, + .name = "ads131e08", + }, +}; + +struct ads131e08_data_rate_desc { + unsigned int rate; /* data rate in kSPS */ + u8 reg; /* reg value */ +}; + +static const struct ads131e08_data_rate_desc ads131e08_data_rate_tbl[] = { + { .rate = 64, .reg = 0x00 }, + { .rate = 32, .reg = 0x01 }, + { .rate = 16, .reg = 0x02 }, + { .rate = 8, .reg = 0x03 }, + { .rate = 4, .reg = 0x04 }, + { .rate = 2, .reg = 0x05 }, + { .rate = 1, .reg = 0x06 }, +}; + +struct ads131e08_pga_gain_desc { + unsigned int gain; /* PGA gain value */ + u8 reg; /* field value */ +}; + +static const struct ads131e08_pga_gain_desc ads131e08_pga_gain_tbl[] = { + { .gain = 1, .reg = 0x01 }, + { .gain = 2, .reg = 0x02 }, + { .gain = 4, .reg = 0x04 }, + { .gain = 8, .reg = 0x05 }, + { .gain = 12, .reg = 0x06 }, +}; + +static const u8 ads131e08_valid_channel_mux_values[] = { 0, 1, 3, 4 }; + +static int ads131e08_exec_cmd(struct ads131e08_state *st, u8 cmd) +{ + int ret; + + ret = spi_write_then_read(st->spi, &cmd, 1, NULL, 0); + if (ret) + dev_err(&st->spi->dev, "Exec cmd(%02x) failed\n", cmd); + + return ret; +} + +static int ads131e08_read_reg(struct ads131e08_state *st, u8 reg) +{ + int ret; + struct spi_transfer transfer[] = { + { + .tx_buf = &st->tx_buf, + .len = 2, + .delay = { + .value = st->sdecode_delay_us, + .unit = SPI_DELAY_UNIT_USECS, + }, + }, { + .rx_buf = &st->rx_buf, + .len = 1, + }, + }; + + st->tx_buf[0] = ADS131E08_CMD_RREG(reg); + st->tx_buf[1] = 0; + + ret = spi_sync_transfer(st->spi, transfer, ARRAY_SIZE(transfer)); + if (ret) { + dev_err(&st->spi->dev, "Read register failed\n"); + return ret; + } + + return st->rx_buf[0]; +} + +static int ads131e08_write_reg(struct ads131e08_state *st, u8 reg, u8 value) +{ + int ret; + struct spi_transfer transfer[] = { + { + .tx_buf = &st->tx_buf, + .len = 3, + .delay = { + .value = st->sdecode_delay_us, + .unit = SPI_DELAY_UNIT_USECS, + }, + } + }; + + st->tx_buf[0] = ADS131E08_CMD_WREG(reg); + st->tx_buf[1] = 0; + st->tx_buf[2] = value; + + ret = spi_sync_transfer(st->spi, transfer, ARRAY_SIZE(transfer)); + if (ret) + dev_err(&st->spi->dev, "Write register failed\n"); + + return ret; +} + +static int ads131e08_read_data(struct ads131e08_state *st, int rx_len) +{ + int ret; + struct spi_transfer transfer[] = { + { + .tx_buf = &st->tx_buf, + .len = 1, + }, { + .rx_buf = &st->rx_buf, + .len = rx_len, + }, + }; + + st->tx_buf[0] = ADS131E08_CMD_RDATA; + + ret = spi_sync_transfer(st->spi, transfer, ARRAY_SIZE(transfer)); + if (ret) + dev_err(&st->spi->dev, "Read data failed\n"); + + return ret; +} + +static int ads131e08_set_data_rate(struct ads131e08_state *st, int data_rate) +{ + int i, reg, ret; + + for (i = 0; i < ARRAY_SIZE(ads131e08_data_rate_tbl); i++) { + if (ads131e08_data_rate_tbl[i].rate == data_rate) + break; + } + + if (i == ARRAY_SIZE(ads131e08_data_rate_tbl)) { + dev_err(&st->spi->dev, "invalid data rate value\n"); + return -EINVAL; + } + + reg = ads131e08_read_reg(st, ADS131E08_ADR_CFG1R); + if (reg < 0) + return reg; + + reg &= ~ADS131E08_CFG1R_DR_MASK; + reg |= FIELD_PREP(ADS131E08_CFG1R_DR_MASK, + ads131e08_data_rate_tbl[i].reg); + + ret = ads131e08_write_reg(st, ADS131E08_ADR_CFG1R, reg); + if (ret) + return ret; + + st->data_rate = data_rate; + st->readback_len = ADS131E08_NUM_STATUS_BYTES + + ADS131E08_NUM_DATA_BYTES(st->data_rate) * + st->info->max_channels; + + return 0; +} + +static int ads131e08_pga_gain_to_field_value(struct ads131e08_state *st, + unsigned int pga_gain) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ads131e08_pga_gain_tbl); i++) { + if (ads131e08_pga_gain_tbl[i].gain == pga_gain) + break; + } + + if (i == ARRAY_SIZE(ads131e08_pga_gain_tbl)) { + dev_err(&st->spi->dev, "invalid PGA gain value\n"); + return -EINVAL; + } + + return ads131e08_pga_gain_tbl[i].reg; +} + +static int ads131e08_set_pga_gain(struct ads131e08_state *st, + unsigned int channel, unsigned int pga_gain) +{ + int field_value, reg; + + field_value = ads131e08_pga_gain_to_field_value(st, pga_gain); + if (field_value < 0) + return field_value; + + reg = ads131e08_read_reg(st, ADS131E08_ADR_CH0R + channel); + if (reg < 0) + return reg; + + reg &= ~ADS131E08_CHR_GAIN_MASK; + reg |= FIELD_PREP(ADS131E08_CHR_GAIN_MASK, field_value); + + return ads131e08_write_reg(st, ADS131E08_ADR_CH0R + channel, reg); +} + +static int ads131e08_validate_channel_mux(struct ads131e08_state *st, + unsigned int mux) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ads131e08_valid_channel_mux_values); i++) { + if (ads131e08_valid_channel_mux_values[i] == mux) + break; + } + + if (i == ARRAY_SIZE(ads131e08_valid_channel_mux_values)) { + dev_err(&st->spi->dev, "invalid channel mux value\n"); + return -EINVAL; + } + + return 0; +} + +static int ads131e08_set_channel_mux(struct ads131e08_state *st, + unsigned int channel, unsigned int mux) +{ + int reg; + + reg = ads131e08_read_reg(st, ADS131E08_ADR_CH0R + channel); + if (reg < 0) + return reg; + + reg &= ~ADS131E08_CHR_MUX_MASK; + reg |= FIELD_PREP(ADS131E08_CHR_MUX_MASK, mux); + + return ads131e08_write_reg(st, ADS131E08_ADR_CH0R + channel, reg); +} + +static int ads131e08_power_down_channel(struct ads131e08_state *st, + unsigned int channel, bool value) +{ + int reg; + + reg = ads131e08_read_reg(st, ADS131E08_ADR_CH0R + channel); + if (reg < 0) + return reg; + + reg &= ~ADS131E08_CHR_PWD_MASK; + reg |= FIELD_PREP(ADS131E08_CHR_PWD_MASK, value); + + return ads131e08_write_reg(st, ADS131E08_ADR_CH0R + channel, reg); +} + +static int ads131e08_config_reference_voltage(struct ads131e08_state *st) +{ + int reg; + + reg = ads131e08_read_reg(st, ADS131E08_ADR_CFG3R); + if (reg < 0) + return reg; + + reg &= ~ADS131E08_CFG3R_PDB_REFBUF_MASK; + if (!st->vref_reg) { + reg |= FIELD_PREP(ADS131E08_CFG3R_PDB_REFBUF_MASK, 1); + reg &= ~ADS131E08_CFG3R_VREF_4V_MASK; + reg |= FIELD_PREP(ADS131E08_CFG3R_VREF_4V_MASK, + st->vref_mv == ADS131E08_VREF_4V_mV); + } + + return ads131e08_write_reg(st, ADS131E08_ADR_CFG3R, reg); +} + +static int ads131e08_initial_config(struct iio_dev *indio_dev) +{ + const struct iio_chan_spec *channel = indio_dev->channels; + struct ads131e08_state *st = iio_priv(indio_dev); + unsigned long active_channels = 0; + int ret, i; + + ret = ads131e08_exec_cmd(st, ADS131E08_CMD_RESET); + if (ret) + return ret; + + udelay(st->reset_delay_us); + + /* Disable read data in continuous mode (enabled by default) */ + ret = ads131e08_exec_cmd(st, ADS131E08_CMD_SDATAC); + if (ret) + return ret; + + ret = ads131e08_set_data_rate(st, ADS131E08_DEFAULT_DATA_RATE); + if (ret) + return ret; + + ret = ads131e08_config_reference_voltage(st); + if (ret) + return ret; + + for (i = 0; i < indio_dev->num_channels; i++) { + ret = ads131e08_set_pga_gain(st, channel->channel, + st->channel_config[i].pga_gain); + if (ret) + return ret; + + ret = ads131e08_set_channel_mux(st, channel->channel, + st->channel_config[i].mux); + if (ret) + return ret; + + active_channels |= BIT(channel->channel); + channel++; + } + + /* Power down unused channels */ + for_each_clear_bit(i, &active_channels, st->info->max_channels) { + ret = ads131e08_power_down_channel(st, i, true); + if (ret) + return ret; + } + + /* Request channel offset calibration */ + ret = ads131e08_exec_cmd(st, ADS131E08_CMD_OFFSETCAL); + if (ret) + return ret; + + /* + * Channel offset calibration is triggered with the first START + * command. Since calibration takes more time than settling operation, + * this causes timeout error when command START is sent first + * time (e.g. first call of the ads131e08_read_direct method). + * To avoid this problem offset calibration is triggered here. + */ + ret = ads131e08_exec_cmd(st, ADS131E08_CMD_START); + if (ret) + return ret; + + msleep(ADS131E08_WAIT_OFFSETCAL_MS); + + return ads131e08_exec_cmd(st, ADS131E08_CMD_STOP); +} + +static int ads131e08_pool_data(struct ads131e08_state *st) +{ + unsigned long timeout; + int ret; + + reinit_completion(&st->completion); + + ret = ads131e08_exec_cmd(st, ADS131E08_CMD_START); + if (ret) + return ret; + + timeout = msecs_to_jiffies(ADS131E08_MAX_SETTLING_TIME_MS); + ret = wait_for_completion_timeout(&st->completion, timeout); + if (!ret) + return -ETIMEDOUT; + + ret = ads131e08_read_data(st, st->readback_len); + if (ret) + return ret; + + return ads131e08_exec_cmd(st, ADS131E08_CMD_STOP); +} + +static int ads131e08_read_direct(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *value) +{ + struct ads131e08_state *st = iio_priv(indio_dev); + u8 num_bits, *src; + int ret; + + ret = ads131e08_pool_data(st); + if (ret) + return ret; + + src = st->rx_buf + ADS131E08_NUM_STATUS_BYTES + + channel->channel * ADS131E08_NUM_DATA_BYTES(st->data_rate); + + num_bits = ADS131E08_NUM_DATA_BITS(st->data_rate); + *value = sign_extend32(get_unaligned_be32(src) >> (32 - num_bits), num_bits - 1); + + return 0; +} + +static int ads131e08_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *value, + int *value2, long mask) +{ + struct ads131e08_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ads131e08_read_direct(indio_dev, channel, value); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + if (st->vref_reg) { + ret = regulator_get_voltage(st->vref_reg); + if (ret < 0) + return ret; + + *value = ret / 1000; + } else { + *value = st->vref_mv; + } + + *value /= st->channel_config[channel->address].pga_gain; + *value2 = ADS131E08_NUM_DATA_BITS(st->data_rate) - 1; + + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_SAMP_FREQ: + *value = st->data_rate; + + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int ads131e08_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int value, + int value2, long mask) +{ + struct ads131e08_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ads131e08_set_data_rate(st, value); + iio_device_release_direct(indio_dev); + return ret; + + default: + return -EINVAL; + } +} + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("1 2 4 8 16 32 64"); + +static struct attribute *ads131e08_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group ads131e08_attribute_group = { + .attrs = ads131e08_attributes, +}; + +static int ads131e08_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, unsigned int *readval) +{ + struct ads131e08_state *st = iio_priv(indio_dev); + + if (readval) { + int ret = ads131e08_read_reg(st, reg); + *readval = ret; + return ret; + } + + return ads131e08_write_reg(st, reg, writeval); +} + +static const struct iio_info ads131e08_iio_info = { + .read_raw = ads131e08_read_raw, + .write_raw = ads131e08_write_raw, + .attrs = &ads131e08_attribute_group, + .debugfs_reg_access = &ads131e08_debugfs_reg_access, +}; + +static int ads131e08_set_trigger_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct ads131e08_state *st = iio_priv(indio_dev); + u8 cmd = state ? ADS131E08_CMD_START : ADS131E08_CMD_STOP; + + return ads131e08_exec_cmd(st, cmd); +} + +static const struct iio_trigger_ops ads131e08_trigger_ops = { + .set_trigger_state = &ads131e08_set_trigger_state, + .validate_device = &iio_trigger_validate_own_device, +}; + +static irqreturn_t ads131e08_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct ads131e08_state *st = iio_priv(indio_dev); + unsigned int chn, i = 0; + u8 *src, *dest; + int ret; + + /* + * The number of data bits per channel depends on the data rate. + * For 32 and 64 ksps data rates, number of data bits per channel + * is 16. This case is not compliant with used (fixed) scan element + * type (be:s24/32>>8). So we use a little tweak to pack properly + * 16 bits of data into the buffer. + */ + unsigned int num_bytes = ADS131E08_NUM_DATA_BYTES(st->data_rate); + u8 tweak_offset = num_bytes == 2 ? 1 : 0; + + if (iio_trigger_using_own(indio_dev)) + ret = ads131e08_read_data(st, st->readback_len); + else + ret = ads131e08_pool_data(st); + + if (ret) + goto out; + + iio_for_each_active_channel(indio_dev, chn) { + src = st->rx_buf + ADS131E08_NUM_STATUS_BYTES + chn * num_bytes; + dest = st->tmp_buf.data + i * ADS131E08_NUM_STORAGE_BYTES; + + /* + * Tweak offset is 0: + * +---+---+---+---+ + * |D0 |D1 |D2 | X | (3 data bytes) + * +---+---+---+---+ + * a+0 a+1 a+2 a+3 + * + * Tweak offset is 1: + * +---+---+---+---+ + * |P0 |D0 |D1 | X | (one padding byte and 2 data bytes) + * +---+---+---+---+ + * a+0 a+1 a+2 a+3 + */ + memcpy(dest + tweak_offset, src, num_bytes); + + /* + * Data conversion from 16 bits of data to 24 bits of data + * is done by sign extension (properly filling padding byte). + */ + if (tweak_offset) + *dest = *src & BIT(7) ? 0xff : 0x00; + + i++; + } + + iio_push_to_buffers_with_ts(indio_dev, &st->tmp_buf, sizeof(st->tmp_buf), + iio_get_time_ns(indio_dev)); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static irqreturn_t ads131e08_interrupt(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ads131e08_state *st = iio_priv(indio_dev); + + if (iio_buffer_enabled(indio_dev) && iio_trigger_using_own(indio_dev)) + iio_trigger_poll(st->trig); + else + complete(&st->completion); + + return IRQ_HANDLED; +} + +static int ads131e08_alloc_channels(struct iio_dev *indio_dev) +{ + struct ads131e08_state *st = iio_priv(indio_dev); + struct ads131e08_channel_config *channel_config; + struct device *dev = &st->spi->dev; + struct iio_chan_spec *channels; + unsigned int channel, tmp; + int num_channels, i, ret; + + ret = device_property_read_u32(dev, "ti,vref-internal", &tmp); + if (ret) + tmp = 0; + + switch (tmp) { + case 0: + st->vref_mv = ADS131E08_VREF_2V4_mV; + break; + case 1: + st->vref_mv = ADS131E08_VREF_4V_mV; + break; + default: + dev_err(&st->spi->dev, "invalid internal voltage reference\n"); + return -EINVAL; + } + + num_channels = device_get_child_node_count(dev); + if (num_channels == 0) { + dev_err(&st->spi->dev, "no channel children\n"); + return -ENODEV; + } + + if (num_channels > st->info->max_channels) { + dev_err(&st->spi->dev, "num of channel children out of range\n"); + return -EINVAL; + } + + channels = devm_kcalloc(&st->spi->dev, num_channels, + sizeof(*channels), GFP_KERNEL); + if (!channels) + return -ENOMEM; + + channel_config = devm_kcalloc(&st->spi->dev, num_channels, + sizeof(*channel_config), GFP_KERNEL); + if (!channel_config) + return -ENOMEM; + + i = 0; + device_for_each_child_node_scoped(dev, node) { + ret = fwnode_property_read_u32(node, "reg", &channel); + if (ret) + return ret; + + ret = fwnode_property_read_u32(node, "ti,gain", &tmp); + if (ret) { + channel_config[i].pga_gain = ADS131E08_DEFAULT_PGA_GAIN; + } else { + ret = ads131e08_pga_gain_to_field_value(st, tmp); + if (ret < 0) + return ret; + + channel_config[i].pga_gain = tmp; + } + + ret = fwnode_property_read_u32(node, "ti,mux", &tmp); + if (ret) { + channel_config[i].mux = ADS131E08_DEFAULT_MUX; + } else { + ret = ads131e08_validate_channel_mux(st, tmp); + if (ret) + return ret; + + channel_config[i].mux = tmp; + } + + channels[i].type = IIO_VOLTAGE; + channels[i].indexed = 1; + channels[i].channel = channel; + channels[i].address = i; + channels[i].info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE); + channels[i].info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ); + channels[i].scan_index = channel; + channels[i].scan_type.sign = 's'; + channels[i].scan_type.realbits = 24; + channels[i].scan_type.storagebits = 32; + channels[i].scan_type.shift = 8; + channels[i].scan_type.endianness = IIO_BE; + i++; + } + + indio_dev->channels = channels; + indio_dev->num_channels = num_channels; + st->channel_config = channel_config; + + return 0; + +} + +static void ads131e08_regulator_disable(void *data) +{ + struct ads131e08_state *st = data; + + regulator_disable(st->vref_reg); +} + +static int ads131e08_probe(struct spi_device *spi) +{ + const struct ads131e08_info *info; + struct ads131e08_state *st; + struct iio_dev *indio_dev; + unsigned long adc_clk_hz; + unsigned long adc_clk_ns; + int ret; + + info = spi_get_device_match_data(spi); + if (!info) { + dev_err(&spi->dev, "failed to get match data\n"); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->info = info; + st->spi = spi; + + ret = ads131e08_alloc_channels(indio_dev); + if (ret) + return ret; + + indio_dev->name = st->info->name; + indio_dev->info = &ads131e08_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + init_completion(&st->completion); + + if (spi->irq) { + ret = devm_request_irq(&spi->dev, spi->irq, + ads131e08_interrupt, + IRQF_TRIGGER_FALLING | IRQF_NO_THREAD, + spi->dev.driver->name, indio_dev); + if (ret) + return dev_err_probe(&spi->dev, ret, + "request irq failed\n"); + } else { + dev_err(&spi->dev, "data ready IRQ missing\n"); + return -ENODEV; + } + + st->trig = devm_iio_trigger_alloc(&spi->dev, "%s-dev%d", + indio_dev->name, iio_device_id(indio_dev)); + if (!st->trig) + return -ENOMEM; + + st->trig->ops = &ads131e08_trigger_ops; + st->trig->dev.parent = &spi->dev; + iio_trigger_set_drvdata(st->trig, indio_dev); + ret = devm_iio_trigger_register(&spi->dev, st->trig); + if (ret) { + dev_err(&spi->dev, "failed to register IIO trigger\n"); + return ret; + } + + indio_dev->trig = iio_trigger_get(st->trig); + + ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, + NULL, &ads131e08_trigger_handler, NULL); + if (ret) { + dev_err(&spi->dev, "failed to setup IIO buffer\n"); + return ret; + } + + st->vref_reg = devm_regulator_get_optional(&spi->dev, "vref"); + if (!IS_ERR(st->vref_reg)) { + ret = regulator_enable(st->vref_reg); + if (ret) { + dev_err(&spi->dev, + "failed to enable external vref supply\n"); + return ret; + } + + ret = devm_add_action_or_reset(&spi->dev, ads131e08_regulator_disable, st); + if (ret) + return ret; + } else { + if (PTR_ERR(st->vref_reg) != -ENODEV) + return PTR_ERR(st->vref_reg); + + st->vref_reg = NULL; + } + + st->adc_clk = devm_clk_get_enabled(&spi->dev, "adc-clk"); + if (IS_ERR(st->adc_clk)) + return dev_err_probe(&spi->dev, PTR_ERR(st->adc_clk), + "failed to get the ADC clock\n"); + + adc_clk_hz = clk_get_rate(st->adc_clk); + if (!adc_clk_hz) { + dev_err(&spi->dev, "failed to get the ADC clock rate\n"); + return -EINVAL; + } + + adc_clk_ns = NSEC_PER_SEC / adc_clk_hz; + st->sdecode_delay_us = DIV_ROUND_UP( + ADS131E08_WAIT_SDECODE_CYCLES * adc_clk_ns, NSEC_PER_USEC); + st->reset_delay_us = DIV_ROUND_UP( + ADS131E08_WAIT_RESET_CYCLES * adc_clk_ns, NSEC_PER_USEC); + + ret = ads131e08_initial_config(indio_dev); + if (ret) { + dev_err(&spi->dev, "initial configuration failed\n"); + return ret; + } + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct of_device_id ads131e08_of_match[] = { + { .compatible = "ti,ads131e04", + .data = &ads131e08_info_tbl[ads131e04], }, + { .compatible = "ti,ads131e06", + .data = &ads131e08_info_tbl[ads131e06], }, + { .compatible = "ti,ads131e08", + .data = &ads131e08_info_tbl[ads131e08], }, + { } +}; +MODULE_DEVICE_TABLE(of, ads131e08_of_match); + +static const struct spi_device_id ads131e08_ids[] = { + { .name = "ads131e04", .driver_data = (kernel_ulong_t)&ads131e08_info_tbl[ads131e04] }, + { .name = "ads131e06", .driver_data = (kernel_ulong_t)&ads131e08_info_tbl[ads131e06] }, + { .name = "ads131e08", .driver_data = (kernel_ulong_t)&ads131e08_info_tbl[ads131e08] }, + { } +}; +MODULE_DEVICE_TABLE(spi, ads131e08_ids); + +static struct spi_driver ads131e08_driver = { + .driver = { + .name = "ads131e08", + .of_match_table = ads131e08_of_match, + }, + .probe = ads131e08_probe, + .id_table = ads131e08_ids, +}; +module_spi_driver(ads131e08_driver); + +MODULE_AUTHOR("Tomislav Denis "); +MODULE_DESCRIPTION("Driver for ADS131E0x ADC family"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ti-ads131m02.c b/drivers/iio/adc/ti-ads131m02.c new file mode 100644 index 000000000..2f8f75c82 --- /dev/null +++ b/drivers/iio/adc/ti-ads131m02.c @@ -0,0 +1,967 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Texas Instruments ADS131M02 family ADC chips. + * + * Copyright (C) 2024 Protonic Holland + * Copyright (C) 2025 Oleksij Rempel , Pengutronix + * + * Primary Datasheet Reference (used for citations): + * ADS131M08 8-Channel, Simultaneously-Sampling, 24-Bit, Delta-Sigma ADC + * Document SBAS950B, Revised February 2021 + * https://www.ti.com/lit/ds/symlink/ads131m08.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Max channels supported by the largest variant in the family (ADS131M08) */ +#define ADS131M_MAX_CHANNELS 8 + +/* Section 6.7, t_REGACQ (min time after reset) is 5us */ +#define ADS131M_RESET_DELAY_US 5 + +#define ADS131M_WORD_SIZE_BYTES 3 +#define ADS131M_RESPONSE_WORDS 1 +#define ADS131M_CRC_WORDS 1 + +/* + * SPI Frame word count calculation. + * Frame = N channel words + 1 response word + 1 CRC word. + * Word size depends on WLENGTH bits in MODE register (Default 24-bit). + */ +#define ADS131M_FRAME_WORDS(nch) \ + ((nch) + ADS131M_RESPONSE_WORDS + ADS131M_CRC_WORDS) + +/* + * SPI Frame byte size calculation. + * Assumes default word size of 24 bits (3 bytes). + */ +#define ADS131M_FRAME_BYTES(nch) \ + (ADS131M_FRAME_WORDS(nch) * ADS131M_WORD_SIZE_BYTES) + +/* + * Index calculation for the start byte of channel 'x' data within the RX buffer. + * Assumes 24-bit words (3 bytes per word). + * The received frame starts with the response word (e.g., STATUS register + * content when NULL command was sent), followed by data for channels 0 to N-1, + * and finally the output CRC word. + * Response = index 0..2, Chan0 = index 3..5, Chan1 = index 6..8, ... + * Index for ChanX = 3 (response) + x * 3 (channel data size). + */ +#define ADS131M_CHANNEL_INDEX(x) \ + ((x) * ADS131M_WORD_SIZE_BYTES + ADS131M_WORD_SIZE_BYTES) + +#define ADS131M_CMD_NULL 0x0000 +#define ADS131M_CMD_RESET 0x0011 + +#define ADS131M_CMD_ADDR_MASK GENMASK(11, 7) +#define ADS131M_CMD_NUM_MASK GENMASK(6, 0) + +#define ADS131M_CMD_RREG_OP 0xa000 +#define ADS131M_CMD_WREG_OP 0x6000 + +#define ADS131M_CMD_RREG(a, n) \ + (ADS131M_CMD_RREG_OP | \ + FIELD_PREP(ADS131M_CMD_ADDR_MASK, a) | \ + FIELD_PREP(ADS131M_CMD_NUM_MASK, n)) +#define ADS131M_CMD_WREG(a, n) \ + (ADS131M_CMD_WREG_OP | \ + FIELD_PREP(ADS131M_CMD_ADDR_MASK, a) | \ + FIELD_PREP(ADS131M_CMD_NUM_MASK, n)) + +/* STATUS Register (0x01h) bit definitions */ +#define ADS131M_STATUS_CRC_ERR BIT(12) /* Input CRC error */ + +#define ADS131M_REG_MODE 0x02 +#define ADS131M_MODE_RX_CRC_EN BIT(12) /* Enable Input CRC */ +#define ADS131M_MODE_CRC_TYPE_ANSI BIT(11) /* 0 = CCITT, 1 = ANSI */ +#define ADS131M_MODE_RESET_FLAG BIT(10) + +#define ADS131M_REG_CLOCK 0x03 +#define ADS131M_CLOCK_XTAL_DIS BIT(7) +#define ADS131M_CLOCK_EXTREF_EN BIT(6) + +/* 1.2V internal reference, in millivolts, for IIO_VAL_FRACTIONAL_LOG2 */ +#define ADS131M_VREF_INTERNAL_mV 1200 +/* 24-bit resolution */ +#define ADS131M_RESOLUTION_BITS 24 +/* Signed data uses (RESOLUTION_BITS - 1) magnitude bits */ +#define ADS131M_CODE_BITS (ADS131M_RESOLUTION_BITS - 1) + +/* External ref FSR = Vref * 0.96 */ +#define ADS131M_EXTREF_SCALE_NUM 96 +#define ADS131M_EXTREF_SCALE_DEN 100 + +struct ads131m_configuration { + const struct iio_chan_spec *channels; + const char *name; + u16 reset_ack; + u8 num_channels; + u8 supports_extref:1; + u8 supports_xtal:1; +}; + +struct ads131m_priv { + struct iio_dev *indio_dev; + struct spi_device *spi; + const struct ads131m_configuration *config; + + bool use_external_ref; + int scale_val; + int scale_val2; + + struct spi_transfer xfer; + struct spi_message msg; + + /* + * Protects the shared tx_buffer and rx_buffer. More importantly, + * this serializes all SPI communication to ensure the atomicity + * of multi-cycle command sequences (like WREG, RREG, or RESET). + */ + struct mutex lock; + + /* DMA-safe buffers should be placed at the end of the struct. */ + u8 tx_buffer[ADS131M_FRAME_BYTES(ADS131M_MAX_CHANNELS)] + __aligned(IIO_DMA_MINALIGN); + u8 rx_buffer[ADS131M_FRAME_BYTES(ADS131M_MAX_CHANNELS)]; +}; + +/** + * ads131m_tx_frame_unlocked - Sends a command frame with Input CRC + * @priv: Device private data structure. + * @command: The 16-bit command to send (e.g., NULL, RREG, RESET). + * + * This function sends a command in Word 0, and its calculated 16-bit + * CRC in Word 1, as required when Input CRC is enabled. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_tx_frame_unlocked(struct ads131m_priv *priv, u32 command) +{ + struct iio_dev *indio_dev = priv->indio_dev; + u16 crc; + + lockdep_assert_held(&priv->lock); + + memset(priv->tx_buffer, 0, ADS131M_FRAME_BYTES(indio_dev->num_channels)); + + /* Word 0: 16-bit command, MSB-aligned in 24-bit word */ + put_unaligned_be16(command, &priv->tx_buffer[0]); + + /* Word 1: Input CRC. Calculated over the 3 bytes of Word 0. */ + crc = crc_itu_t(0xffff, priv->tx_buffer, 3); + put_unaligned_be16(crc, &priv->tx_buffer[3]); + + return spi_sync(priv->spi, &priv->msg); +} + +/** + * ads131m_rx_frame_unlocked - Receives a full SPI data frame. + * @priv: Device private data structure. + * + * This function sends a NULL command (with its CRC) to clock out a + * full SPI frame from the device (e.g., response + channel data + CRC). + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_rx_frame_unlocked(struct ads131m_priv *priv) +{ + return ads131m_tx_frame_unlocked(priv, ADS131M_CMD_NULL); +} + +/** + * ads131m_check_status_crc_err - Checks for an Input CRC error. + * @priv: Device private data structure. + * + * Sends a NULL command to fetch the STATUS register and checks the + * CRC_ERR bit. This is used to verify the integrity of the previous + * command (like RREG or WREG). + * + * Return: 0 on success, -EIO if CRC_ERR bit is set. + */ +static int ads131m_check_status_crc_err(struct ads131m_priv *priv) +{ + struct device *dev = &priv->spi->dev; + u16 status; + int ret; + + lockdep_assert_held(&priv->lock); + + ret = ads131m_rx_frame_unlocked(priv); + if (ret < 0) { + dev_err_ratelimited(dev, + "SPI error on STATUS read for CRC check\n"); + return ret; + } + + status = get_unaligned_be16(&priv->rx_buffer[0]); + if (status & ADS131M_STATUS_CRC_ERR) { + dev_err_ratelimited(dev, + "Input CRC error reported in STATUS = 0x%04x\n", + status); + return -EIO; + } + + return 0; +} + +/** + * ads131m_write_reg_unlocked - Writes a single register and verifies the ACK. + * @priv: Device private data structure. + * @reg: The 8-bit register address. + * @val: The 16-bit value to write. + * + * This function performs the full 3-cycle WREG operation with Input CRC: + * 1. (Cycle 1) Sends WREG command, data, and its calculated CRC. + * 2. (Cycle 2) Sends NULL+CRC to retrieve the response from Cycle 1. + * 3. Verifies the response is the correct ACK for the WREG. + * 4. (Cycle 3) Sends NULL+CRC to retrieve STATUS and check for CRC_ERR. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_write_reg_unlocked(struct ads131m_priv *priv, u8 reg, u16 val) +{ + struct iio_dev *indio_dev = priv->indio_dev; + u16 command, expected_ack, response, crc; + struct device *dev = &priv->spi->dev; + int ret_crc_err = 0; + int ret; + + lockdep_assert_held(&priv->lock); + + command = ADS131M_CMD_WREG(reg, 0); /* n = 0 for 1 register */ + /* + * Per Table 8-11, WREG response is: 010a aaaa ammm mmmm + * For 1 reg (n = 0 -> m = 0): 010a aaaa a000 0000 = 0x4000 | (reg << 7) + */ + expected_ack = 0x4000 | (reg << 7); + + /* Cycle 1: Send WREG Command + Data + Input CRC */ + + memset(priv->tx_buffer, 0, ADS131M_FRAME_BYTES(indio_dev->num_channels)); + + /* Word 0: WREG command, 1 reg (n = 0), MSB-aligned */ + put_unaligned_be16(command, &priv->tx_buffer[0]); + + /* Word 1: Data, MSB-aligned */ + put_unaligned_be16(val, &priv->tx_buffer[3]); + + /* Word 2: Input CRC. Calculated over Word 0 (Cmd) and Word 1 (Data). */ + crc = crc_itu_t(0xffff, priv->tx_buffer, 6); + put_unaligned_be16(crc, &priv->tx_buffer[6]); + + /* Ignore the RX buffer (it's from the previous command) */ + ret = spi_sync(priv->spi, &priv->msg); + if (ret < 0) { + dev_err_ratelimited(dev, "SPI error on WREG (cycle 1)\n"); + return ret; + } + + /* Cycle 2: Send NULL Command to get the WREG response */ + ret = ads131m_rx_frame_unlocked(priv); + if (ret < 0) { + dev_err_ratelimited(dev, "SPI error on WREG ACK (cycle 2)\n"); + return ret; + } + + /* + * Response is in the first 2 bytes of the RX buffer + * (MSB-aligned 16-bit response) + */ + response = get_unaligned_be16(&priv->rx_buffer[0]); + if (response != expected_ack) { + dev_err_ratelimited(dev, "WREG(0x%02x) failed, expected ACK 0x%04x, got 0x%04x\n", + reg, expected_ack, response); + ret_crc_err = -EIO; + /* + * Don't return yet, still need to do Cycle 3 to clear + * any potential CRC_ERR flag from this failed command. + */ + } + + /* + * Cycle 3: Check STATUS for Input CRC error. + * This is necessary even if ACK was wrong, to clear the CRC_ERR flag. + */ + ret = ads131m_check_status_crc_err(priv); + if (ret < 0) + return ret; + + return ret_crc_err; +} + +/** + * ads131m_read_reg_unlocked - Reads a single register from the device. + * @priv: Device private data structure. + * @reg: The 8-bit register address. + * @val: Pointer to store the 16-bit register value. + * + * This function performs the full 3-cycle RREG operation with Input CRC: + * 1. (Cycle 1) Sends the RREG command + Input CRC. + * 2. (Cycle 2) Sends NULL+CRC to retrieve the register data. + * 3. (Cycle 3) Sends NULL+CRC to retrieve STATUS and check for CRC_ERR. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_read_reg_unlocked(struct ads131m_priv *priv, u8 reg, u16 *val) +{ + struct device *dev = &priv->spi->dev; + u16 command; + int ret; + + lockdep_assert_held(&priv->lock); + + command = ADS131M_CMD_RREG(reg, 0); /* n=0 for 1 register */ + + /* + * Cycle 1: Send RREG Command + Input CRC + * Ignore the RX buffer (it's from the previous command) + */ + ret = ads131m_tx_frame_unlocked(priv, command); + if (ret < 0) { + dev_err_ratelimited(dev, "SPI error on RREG (cycle 1)\n"); + return ret; + } + + /* Cycle 2: Send NULL Command to get the register data */ + ret = ads131m_rx_frame_unlocked(priv); + if (ret < 0) { + dev_err_ratelimited(dev, "SPI error on RREG data (cycle 2)\n"); + return ret; + } + + /* + * Per datasheet, for a single reg read, the response is the data. + * It's in the first 2 bytes of the RX buffer (MSB-aligned 16-bit). + */ + *val = get_unaligned_be16(&priv->rx_buffer[0]); + + /* + * Cycle 3: Check STATUS for Input CRC error. + * The RREG command does not execute if CRC is bad, but we read + * STATUS anyway to clear the flag in case it was set. + */ + return ads131m_check_status_crc_err(priv); +} + +/** + * ads131m_rmw_reg - Reads, modifies, and writes a single register. + * @priv: Device private data structure. + * @reg: The 8-bit register address. + * @clear: Bitmask of bits to clear. + * @set: Bitmask of bits to set. + * + * This function performs an atomic read-modify-write operation on a register. + * It reads the register, applies the clear and set masks, and writes + * the new value back if it has changed. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_rmw_reg(struct ads131m_priv *priv, u8 reg, u16 clear, u16 set) +{ + u16 old_val, new_val; + int ret; + + guard(mutex)(&priv->lock); + + ret = ads131m_read_reg_unlocked(priv, reg, &old_val); + if (ret < 0) + return ret; + + new_val = (old_val & ~clear) | set; + if (new_val == old_val) + return 0; + + return ads131m_write_reg_unlocked(priv, reg, new_val); +} + +/** + * ads131m_verify_output_crc - Verifies the CRC of the received SPI frame. + * @priv: Device private data structure. + * + * This function calculates the CRC-16-CCITT (Poly 0x1021, Seed 0xFFFF) over + * the received response and channel data, and compares it to the CRC word + * received at the end of the SPI frame. + * + * Return: 0 on success, -EIO on CRC mismatch. + */ +static int ads131m_verify_output_crc(struct ads131m_priv *priv) +{ + struct iio_dev *indio_dev = priv->indio_dev; + struct device *dev = &priv->spi->dev; + u16 calculated_crc, received_crc; + size_t data_len; + + lockdep_assert_held(&priv->lock); + + /* + * Frame: [Response][Chan 0]...[Chan N-1][CRC Word] + * Data for CRC: [Response][Chan 0]...[Chan N-1] + * Data length = (N_channels + 1) * 3 bytes (at 24-bit word size) + */ + data_len = ADS131M_FRAME_BYTES(indio_dev->num_channels) - 3; + calculated_crc = crc_itu_t(0xffff, priv->rx_buffer, data_len); + + /* + * The received 16-bit CRC is MSB-aligned in the last 24-bit word. + * We extract it from the first 2 bytes (BE) of that word. + */ + received_crc = get_unaligned_be16(&priv->rx_buffer[data_len]); + if (calculated_crc != received_crc) { + dev_err_ratelimited(dev, "Output CRC error. Got %04x, expected %04x\n", + received_crc, calculated_crc); + return -EIO; + } + + return 0; +} + +/** + * ads131m_adc_read - Reads channel data, checks input and output CRCs. + * @priv: Device private data structure. + * @channel: The channel number to read. + * @val: Pointer to store the raw 24-bit value. + * + * This function sends a NULL command (with Input CRC) to retrieve data. + * It checks the received STATUS word for any Input CRC errors from the + * previous command, and then verifies the Output CRC of the current + * data frame. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_adc_read(struct ads131m_priv *priv, u8 channel, s32 *val) +{ + struct device *dev = &priv->spi->dev; + u16 status; + int ret; + u8 *buf; + + guard(mutex)(&priv->lock); + + /* Send NULL command + Input CRC, and receive data frame */ + ret = ads131m_rx_frame_unlocked(priv); + if (ret < 0) + return ret; + + /* + * Check STATUS for Input CRC error from the previous command frame. + * Note: the STATUS word belongs to the frame before this NULL command. + */ + status = get_unaligned_be16(&priv->rx_buffer[0]); + if (status & ADS131M_STATUS_CRC_ERR) { + dev_err_ratelimited(dev, + "Previous input CRC error reported in STATUS (0x%04x)\n", + status); + } + + ret = ads131m_verify_output_crc(priv); + if (ret < 0) + return ret; + + buf = &priv->rx_buffer[ADS131M_CHANNEL_INDEX(channel)]; + *val = sign_extend32(get_unaligned_be24(buf), ADS131M_CODE_BITS); + + return 0; +} + +static int ads131m_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *channel, + int *val, int *val2, long mask) +{ + struct ads131m_priv *priv = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = ads131m_adc_read(priv, channel->channel, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = priv->scale_val; + *val2 = priv->scale_val2; + + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } +} + +#define ADS131M_VOLTAGE_CHANNEL(num) \ + { \ + .type = IIO_VOLTAGE, \ + .differential = 1, \ + .indexed = 1, \ + .channel = (num), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + } + +static const struct iio_chan_spec ads131m02_channels[] = { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), +}; + +static const struct iio_chan_spec ads131m03_channels[] = { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), + ADS131M_VOLTAGE_CHANNEL(2), +}; + +static const struct iio_chan_spec ads131m04_channels[] = { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), + ADS131M_VOLTAGE_CHANNEL(2), + ADS131M_VOLTAGE_CHANNEL(3), +}; + +static const struct iio_chan_spec ads131m06_channels[] = { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), + ADS131M_VOLTAGE_CHANNEL(2), + ADS131M_VOLTAGE_CHANNEL(3), + ADS131M_VOLTAGE_CHANNEL(4), + ADS131M_VOLTAGE_CHANNEL(5), +}; + +static const struct iio_chan_spec ads131m08_channels[] = { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), + ADS131M_VOLTAGE_CHANNEL(2), + ADS131M_VOLTAGE_CHANNEL(3), + ADS131M_VOLTAGE_CHANNEL(4), + ADS131M_VOLTAGE_CHANNEL(5), + ADS131M_VOLTAGE_CHANNEL(6), + ADS131M_VOLTAGE_CHANNEL(7), +}; + +static const struct ads131m_configuration ads131m02_config = { + .channels = ads131m02_channels, + .num_channels = ARRAY_SIZE(ads131m02_channels), + .reset_ack = 0xff22, + .name = "ads131m02", +}; + +static const struct ads131m_configuration ads131m03_config = { + .channels = ads131m03_channels, + .num_channels = ARRAY_SIZE(ads131m03_channels), + .reset_ack = 0xff23, + .name = "ads131m03", +}; + +static const struct ads131m_configuration ads131m04_config = { + .channels = ads131m04_channels, + .num_channels = ARRAY_SIZE(ads131m04_channels), + .reset_ack = 0xff24, + .name = "ads131m04", +}; + +static const struct ads131m_configuration ads131m06_config = { + .channels = ads131m06_channels, + .num_channels = ARRAY_SIZE(ads131m06_channels), + .reset_ack = 0xff26, + .supports_extref = true, + .supports_xtal = true, + .name = "ads131m06", +}; + +static const struct ads131m_configuration ads131m08_config = { + .channels = ads131m08_channels, + .num_channels = ARRAY_SIZE(ads131m08_channels), + .reset_ack = 0xff28, + .supports_extref = true, + .supports_xtal = true, + .name = "ads131m08", +}; + +static const struct iio_info ads131m_info = { + .read_raw = ads131m_read_raw, +}; + +/* + * Prepares the reusable SPI message structure for a full-duplex transfer. + * The ADS131M requires sending a command frame while simultaneously + * receiving the response/data frame from the previous command cycle. + * + * This message is optimized for the primary data acquisition workflow: + * sending a single-word command (like NULL) and receiving a full data + * frame (Response + N*Channels + CRC). + * + * This message is sized for a full data frame and is reused for all + * command/data cycles. The driver does not implement variable-length SPI + * messages. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_prepare_message(struct ads131m_priv *priv) +{ + struct iio_dev *indio_dev = priv->indio_dev; + struct device *dev = &priv->spi->dev; + int ret; + + priv->xfer.tx_buf = priv->tx_buffer; + priv->xfer.rx_buf = priv->rx_buffer; + priv->xfer.len = ADS131M_FRAME_BYTES(indio_dev->num_channels); + spi_message_init_with_transfers(&priv->msg, &priv->xfer, 1); + + ret = devm_spi_optimize_message(dev, priv->spi, &priv->msg); + if (ret) + return dev_err_probe(dev, ret, "failed to optimize SPI message\n"); + + return 0; +} + +/** + * ads131m_hw_reset - Pulses the optional hardware reset. + * @priv: Device private data structure. + * @rstc: Reset control for the /RESET line. + * + * Pulses the /RESET line to perform a hardware reset and waits the + * required t_REGACQ time for the device to be ready. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_hw_reset(struct ads131m_priv *priv, + struct reset_control *rstc) +{ + struct device *dev = &priv->spi->dev; + int ret; + + /* + * Manually pulse the reset line using the framework. + * The reset-gpio provider does not implement the .reset op, + * so we must use .assert and .deassert. + */ + ret = reset_control_assert(rstc); + if (ret) + return dev_err_probe(dev, ret, "Failed to assert reset\n"); + + /* Datasheet: Hold /RESET low for > 2 f_CLKIN cycles. 1us is ample. */ + fsleep(1); + + ret = reset_control_deassert(rstc); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to deassert reset\n"); + + /* Wait t_REGACQ (5us) for registers to be accessible */ + fsleep(ADS131M_RESET_DELAY_US); + + return 0; +} + +/** + * ads131m_sw_reset - Issues a software RESET and verifies ACK. + * @priv: Device private data structure. + * + * This function sends a RESET command (with Input CRC), waits t_REGACQ, + * reads back the RESET ACK, and then sends a final NULL to check for + * any input CRC errors. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_sw_reset(struct ads131m_priv *priv) +{ + u16 expected_ack = priv->config->reset_ack; + struct device *dev = &priv->spi->dev; + u16 response; + int ret; + + guard(mutex)(&priv->lock); + + ret = ads131m_tx_frame_unlocked(priv, ADS131M_CMD_RESET); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to send RESET command\n"); + + /* Wait t_REGACQ (5us) for device to be ready after reset */ + fsleep(ADS131M_RESET_DELAY_US); + + /* Cycle 2: Send NULL + CRC to retrieve the response to the RESET */ + ret = ads131m_rx_frame_unlocked(priv); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to read RESET ACK\n"); + + response = get_unaligned_be16(&priv->rx_buffer[0]); + + /* Check against the device-specific ACK value */ + if (response != expected_ack) + return dev_err_probe(dev, -EIO, + "RESET ACK mismatch, got 0x%04x, expected 0x%04x\n", + response, expected_ack); + + /* Cycle 3: Check STATUS for Input CRC error on the RESET command. */ + return ads131m_check_status_crc_err(priv); +} + +/** + * ads131m_reset - Resets the device using hardware or software. + * @priv: Device private data structure. + * @rstc: Optional reset control, or NULL for software reset. + * + * This function performs a hardware reset if supported (rstc provided), + * otherwise it issues a software RESET command via SPI. + * + * Note: The software reset path also validates the device's reset + * acknowledgment against the expected ID for the compatible string. + * The hardware reset path bypasses this ID check. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_reset(struct ads131m_priv *priv, struct reset_control *rstc) +{ + if (rstc) + return ads131m_hw_reset(priv, rstc); + + return ads131m_sw_reset(priv); +} + +static int ads131m_power_init(struct ads131m_priv *priv) +{ + static const char * const supply_ids[] = { "avdd", "dvdd" }; + struct device *dev = &priv->spi->dev; + int vref_uV; + int ret; + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(supply_ids), supply_ids); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to enable regulators\n"); + + /* Default to Internal 1.2V reference: 1200mV / 2^23 */ + priv->scale_val = ADS131M_VREF_INTERNAL_mV; + priv->scale_val2 = BIT(ADS131M_CODE_BITS); + + if (!priv->config->supports_extref) + return 0; + + ret = devm_regulator_get_enable_read_voltage(dev, "refin"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to get refin supply\n"); + + if (ret == 0) + return dev_err_probe(dev, -EINVAL, "refin supply reports 0V\n"); + + if (ret == -ENODEV) + return 0; + + vref_uV = ret; + + /* + * External reference found: Scale(mV) = (vref_uV * 0.96) / 1000 + * The denominator is 100 * 2^23 because of the 0.96 factor (96/100). + */ + priv->scale_val = div_s64((s64)vref_uV * ADS131M_EXTREF_SCALE_NUM, 1000); + priv->scale_val2 = ADS131M_EXTREF_SCALE_DEN * BIT(ADS131M_CODE_BITS); + priv->use_external_ref = true; + + return 0; +} + +/** + * ads131m_hw_init - Initialize the ADC hardware. + * @priv: Device private data structure. + * @rstc: Optional reset control, or NULL for software reset. + * @is_xtal: True if 'clock-names' is "xtal", false if "clkin". + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_hw_init(struct ads131m_priv *priv, + struct reset_control *rstc, bool is_xtal) +{ + struct device *dev = &priv->spi->dev; + u16 mode_clear, mode_set; + int ret; + + ret = ads131m_reset(priv, rstc); + if (ret < 0) + return ret; + + /* + * Configure CLOCK register (0x03) based on DT properties. + * This register only needs configuration for 32-pin (M06/M08) + * variants, as the configurable bits (XTAL_DIS, EXTREF_EN) + * are reserved on 20-pin (M02/M03/M04) variants. + */ + if (priv->config->supports_xtal || priv->config->supports_extref) { + u16 clk_set = 0; + + if (priv->config->supports_xtal && !is_xtal) + clk_set |= ADS131M_CLOCK_XTAL_DIS; + + if (priv->config->supports_extref && priv->use_external_ref) + clk_set |= ADS131M_CLOCK_EXTREF_EN; + + ret = ads131m_rmw_reg(priv, ADS131M_REG_CLOCK, + ADS131M_CLOCK_EXTREF_EN | ADS131M_CLOCK_XTAL_DIS, + clk_set); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to configure CLOCK register\n"); + } + + /* + * The RESET command sets all registers to default, which means: + * 1. The RESET bit (Bit 10) in MODE is set to '1'. + * 2. The CRC_TYPE bit (Bit 11) in MODE is '0' (CCITT). + * 3. The RX_CRC_EN bit (Bit 12) in MODE is '0' (Disabled). + * + * We must: + * 1. Clear the RESET bit. + * 2. Enable Input CRC (RX_CRC_EN). + * 3. Explicitly clear the ANSI CRC bit (for certainty). + */ + mode_clear = ADS131M_MODE_CRC_TYPE_ANSI | ADS131M_MODE_RESET_FLAG; + mode_set = ADS131M_MODE_RX_CRC_EN; + + ret = ads131m_rmw_reg(priv, ADS131M_REG_MODE, mode_clear, mode_set); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to configure MODE register\n"); + + return 0; +} + +/** + * ads131m_parse_clock - enable clock and detect "xtal" selection + * @priv: Device private data structure. + * @is_xtal: result flag (true if "xtal", false if default "clkin") + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_parse_clock(struct ads131m_priv *priv, bool *is_xtal) +{ + struct device *dev = &priv->spi->dev; + struct clk *clk; + int ret; + + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR_OR_NULL(clk)) { + if (IS_ERR(clk)) + ret = PTR_ERR(clk); + else + ret = -ENODEV; + + return dev_err_probe(dev, ret, "clk get enabled failed\n"); + } + + ret = device_property_match_string(dev, "clock-names", "xtal"); + if (ret > 0) + return dev_err_probe(dev, -EINVAL, + "'xtal' must be the only or first clock name"); + + if (ret < 0 && ret != -ENODATA) + return dev_err_probe(dev, ret, + "failed to read 'clock-names' property"); + + if (ret == 0 && !priv->config->supports_xtal) + return dev_err_probe(dev, -EINVAL, + "'xtal' clock not supported on this device"); + + *is_xtal = !ret; + + return 0; +} + +static int ads131m_probe(struct spi_device *spi) +{ + const struct ads131m_configuration *config; + struct device *dev = &spi->dev; + struct reset_control *rstc; + struct iio_dev *indio_dev; + struct ads131m_priv *priv; + bool is_xtal; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + priv = iio_priv(indio_dev); + priv->indio_dev = indio_dev; + priv->spi = spi; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ads131m_info; + + config = spi_get_device_match_data(spi); + + priv->config = config; + indio_dev->name = config->name; + indio_dev->channels = config->channels; + indio_dev->num_channels = config->num_channels; + + rstc = devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(rstc)) + return dev_err_probe(dev, PTR_ERR(rstc), + "Failed to get reset controller\n"); + + ret = devm_mutex_init(dev, &priv->lock); + if (ret < 0) + return ret; + + ret = ads131m_prepare_message(priv); + if (ret < 0) + return ret; + + ret = ads131m_power_init(priv); + if (ret < 0) + return ret; + + /* Power must be applied and stable before the clock is enabled. */ + ret = ads131m_parse_clock(priv, &is_xtal); + if (ret < 0) + return ret; + + ret = ads131m_hw_init(priv, rstc, is_xtal); + if (ret < 0) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ads131m_of_match[] = { + { .compatible = "ti,ads131m02", .data = &ads131m02_config }, + { .compatible = "ti,ads131m03", .data = &ads131m03_config }, + { .compatible = "ti,ads131m04", .data = &ads131m04_config }, + { .compatible = "ti,ads131m06", .data = &ads131m06_config }, + { .compatible = "ti,ads131m08", .data = &ads131m08_config }, + { } +}; +MODULE_DEVICE_TABLE(of, ads131m_of_match); + +static const struct spi_device_id ads131m_id[] = { + { .name = "ads131m02", .driver_data = (kernel_ulong_t)&ads131m02_config }, + { .name = "ads131m03", .driver_data = (kernel_ulong_t)&ads131m03_config }, + { .name = "ads131m04", .driver_data = (kernel_ulong_t)&ads131m04_config }, + { .name = "ads131m06", .driver_data = (kernel_ulong_t)&ads131m06_config }, + { .name = "ads131m08", .driver_data = (kernel_ulong_t)&ads131m08_config }, + { } +}; +MODULE_DEVICE_TABLE(spi, ads131m_id); + +static struct spi_driver ads131m_driver = { + .driver = { + .name = "ads131m02", + .of_match_table = ads131m_of_match, + }, + .probe = ads131m_probe, + .id_table = ads131m_id, +}; +module_spi_driver(ads131m_driver); + +MODULE_AUTHOR("David Jander "); +MODULE_DESCRIPTION("Texas Instruments ADS131M02 ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/ti-ads7138.c b/drivers/iio/adc/ti-ads7138.c new file mode 100644 index 000000000..da82a6947 --- /dev/null +++ b/drivers/iio/adc/ti-ads7138.c @@ -0,0 +1,773 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ADS7138 - Texas Instruments Analog-to-Digital Converter + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* + * Always assume 16 bits resolution as HW registers are aligned like that and + * with enabled oversampling/averaging it actually corresponds to 16 bits. + */ +#define ADS7138_RES_BITS 16 + +/* ADS7138 operation codes */ +#define ADS7138_OPCODE_SINGLE_WRITE 0x08 +#define ADS7138_OPCODE_SET_BIT 0x18 +#define ADS7138_OPCODE_CLEAR_BIT 0x20 +#define ADS7138_OPCODE_BLOCK_WRITE 0x28 +#define ADS7138_OPCODE_BLOCK_READ 0x30 + +/* ADS7138 registers */ +#define ADS7138_REG_GENERAL_CFG 0x01 +#define ADS7138_REG_OSR_CFG 0x03 +#define ADS7138_REG_OPMODE_CFG 0x04 +#define ADS7138_REG_SEQUENCE_CFG 0x10 +#define ADS7138_REG_AUTO_SEQ_CH_SEL 0x12 +#define ADS7138_REG_ALERT_CH_SEL 0x14 +#define ADS7138_REG_EVENT_FLAG 0x18 +#define ADS7138_REG_EVENT_HIGH_FLAG 0x1A +#define ADS7138_REG_EVENT_LOW_FLAG 0x1C +#define ADS7138_REG_HIGH_TH_HYS_CH(x) ((x) * 4 + 0x20) +#define ADS7138_REG_LOW_TH_CNT_CH(x) ((x) * 4 + 0x22) +#define ADS7138_REG_MAX_LSB_CH(x) ((x) * 2 + 0x60) +#define ADS7138_REG_MIN_LSB_CH(x) ((x) * 2 + 0x80) +#define ADS7138_REG_RECENT_LSB_CH(x) ((x) * 2 + 0xA0) + +#define ADS7138_GENERAL_CFG_RST BIT(0) +#define ADS7138_GENERAL_CFG_DWC_EN BIT(4) +#define ADS7138_GENERAL_CFG_STATS_EN BIT(5) +#define ADS7138_OSR_CFG_MASK GENMASK(2, 0) +#define ADS7138_OPMODE_CFG_CONV_MODE BIT(5) +#define ADS7138_OPMODE_CFG_FREQ_MASK GENMASK(4, 0) +#define ADS7138_SEQUENCE_CFG_SEQ_MODE BIT(0) +#define ADS7138_SEQUENCE_CFG_SEQ_START BIT(4) +#define ADS7138_THRESHOLD_LSB_MASK GENMASK(7, 4) + +enum ads7138_modes { + ADS7138_MODE_MANUAL, + ADS7138_MODE_AUTO, +}; + +struct ads7138_chip_data { + const char *name; + const int channel_num; +}; + +struct ads7138_data { + /* Protects RMW access to the I2C interface */ + struct mutex lock; + struct i2c_client *client; + struct regulator *vref_regu; + const struct ads7138_chip_data *chip_data; +}; + +/* + * 2D array of available sampling frequencies and the corresponding register + * values. Structured like this to be easily usable in read_avail function. + */ +static const int ads7138_samp_freqs_bits[2][26] = { + { + 163, 244, 326, 488, 651, 977, 1302, 1953, + 2604, 3906, 5208, 7813, 10417, 15625, 20833, 31250, + 41667, 62500, 83333, 125000, 166667, 250000, 333333, 500000, + 666667, 1000000 + }, { + 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, + 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, + /* Here is a hole, due to duplicate frequencies */ + 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, + 0x01, 0x00 + } +}; + +static const int ads7138_oversampling_ratios[] = { + 1, 2, 4, 8, 16, 32, 64, 128 +}; + +static int ads7138_i2c_write_block(const struct i2c_client *client, u8 reg, + u8 *values, u8 length) +{ + int ret; + int len = length + 2; /* "+ 2" for OPCODE and reg */ + + u8 *buf __free(kfree) = kmalloc(len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf[0] = ADS7138_OPCODE_BLOCK_WRITE; + buf[1] = reg; + memcpy(&buf[2], values, length); + + ret = i2c_master_send(client, buf, len); + if (ret < 0) + return ret; + if (ret != len) + return -EIO; + + return 0; +} + +static int ads7138_i2c_write_with_opcode(const struct i2c_client *client, + u8 reg, u8 regval, u8 opcode) +{ + u8 buf[3] = { opcode, reg, regval }; + int ret; + + ret = i2c_master_send(client, buf, ARRAY_SIZE(buf)); + if (ret < 0) + return ret; + if (ret != ARRAY_SIZE(buf)) + return -EIO; + + return 0; +} + +static int ads7138_i2c_write(const struct i2c_client *client, u8 reg, u8 value) +{ + return ads7138_i2c_write_with_opcode(client, reg, value, + ADS7138_OPCODE_SINGLE_WRITE); +} + +static int ads7138_i2c_set_bit(const struct i2c_client *client, u8 reg, u8 bits) +{ + return ads7138_i2c_write_with_opcode(client, reg, bits, + ADS7138_OPCODE_SET_BIT); +} + +static int ads7138_i2c_clear_bit(const struct i2c_client *client, u8 reg, u8 bits) +{ + return ads7138_i2c_write_with_opcode(client, reg, bits, + ADS7138_OPCODE_CLEAR_BIT); +} + +static int ads7138_i2c_read_block(const struct i2c_client *client, u8 reg, + u8 *out_values, u8 length) +{ + u8 buf[2] = { ADS7138_OPCODE_BLOCK_READ, reg }; + int ret; + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .len = ARRAY_SIZE(buf), + .buf = buf, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = length, + .buf = out_values, + }, + }; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + return ret; + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + return 0; +} + +static int ads7138_i2c_read(const struct i2c_client *client, u8 reg) +{ + u8 value; + int ret; + + ret = ads7138_i2c_read_block(client, reg, &value, sizeof(value)); + if (ret) + return ret; + return value; +} + +static int ads7138_freq_to_bits(int freq) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ads7138_samp_freqs_bits[0]); i++) + if (freq == ads7138_samp_freqs_bits[0][i]) + return ads7138_samp_freqs_bits[1][i]; + + return -EINVAL; +} + +static int ads7138_bits_to_freq(int bits) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ads7138_samp_freqs_bits[1]); i++) + if (bits == ads7138_samp_freqs_bits[1][i]) + return ads7138_samp_freqs_bits[0][i]; + + return -EINVAL; +} + +static int ads7138_osr_to_bits(int osr) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ads7138_oversampling_ratios); i++) + if (osr == ads7138_oversampling_ratios[i]) + return i; + + return -EINVAL; +} + +static int ads7138_read_statistics(const struct i2c_client *client, u8 reg, + u8 *out_values, u8 length) +{ + int ret; + + /* Disable statistics update so the value is not updated mid read */ + ret = ads7138_i2c_clear_bit(client, ADS7138_REG_GENERAL_CFG, + ADS7138_GENERAL_CFG_STATS_EN); + if (ret) + return ret; + + ret = ads7138_i2c_read_block(client, reg, out_values, length); + if (ret) + return ret; + + /* Enable statistics update after read */ + return ads7138_i2c_set_bit(client, ADS7138_REG_GENERAL_CFG, + ADS7138_GENERAL_CFG_STATS_EN); +} + +static int ads7138_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct ads7138_data *data = iio_priv(indio_dev); + int ret, vref, bits; + u8 values[2]; + + switch (mask) { + /* + * Reading the statistics registers reinitializes them. This is + * unfortunate but necessary to prevent data races. + */ + case IIO_CHAN_INFO_RAW: + ret = ads7138_read_statistics(data->client, + ADS7138_REG_RECENT_LSB_CH(chan->channel), + values, ARRAY_SIZE(values)); + if (ret) + return ret; + + *val = get_unaligned_le16(values); + return IIO_VAL_INT; + case IIO_CHAN_INFO_PEAK: + ret = ads7138_read_statistics(data->client, + ADS7138_REG_MAX_LSB_CH(chan->channel), + values, ARRAY_SIZE(values)); + if (ret) + return ret; + + *val = get_unaligned_le16(values); + return IIO_VAL_INT; + case IIO_CHAN_INFO_TROUGH: + ret = ads7138_read_statistics(data->client, + ADS7138_REG_MIN_LSB_CH(chan->channel), + values, ARRAY_SIZE(values)); + if (ret) + return ret; + + *val = get_unaligned_le16(values); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = ads7138_i2c_read(data->client, ADS7138_REG_OPMODE_CFG); + if (ret < 0) + return ret; + + bits = FIELD_GET(ADS7138_OPMODE_CFG_FREQ_MASK, ret); + *val = ads7138_bits_to_freq(bits); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + vref = regulator_get_voltage(data->vref_regu); + if (vref < 0) + return vref; + *val = vref / 1000; + *val2 = ADS7138_RES_BITS; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + ret = ads7138_i2c_read(data->client, ADS7138_REG_OSR_CFG); + if (ret < 0) + return ret; + + bits = FIELD_GET(ADS7138_OSR_CFG_MASK, ret); + *val = ads7138_oversampling_ratios[bits]; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ads7138_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct ads7138_data *data = iio_priv(indio_dev); + int bits, ret; + u8 value; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: { + bits = ads7138_freq_to_bits(val); + if (bits < 0) + return bits; + + guard(mutex)(&data->lock); + ret = ads7138_i2c_read(data->client, ADS7138_REG_OPMODE_CFG); + if (ret < 0) + return ret; + + value = ret & ~ADS7138_OPMODE_CFG_FREQ_MASK; + value |= FIELD_PREP(ADS7138_OPMODE_CFG_FREQ_MASK, bits); + return ads7138_i2c_write(data->client, ADS7138_REG_OPMODE_CFG, + value); + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + bits = ads7138_osr_to_bits(val); + if (bits < 0) + return bits; + + return ads7138_i2c_write(data->client, ADS7138_REG_OSR_CFG, + bits); + default: + return -EINVAL; + } +} + +static int ads7138_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, int *val2) +{ + struct ads7138_data *data = iio_priv(indio_dev); + u8 reg, values[2]; + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + reg = (dir == IIO_EV_DIR_RISING) ? + ADS7138_REG_HIGH_TH_HYS_CH(chan->channel) : + ADS7138_REG_LOW_TH_CNT_CH(chan->channel); + ret = ads7138_i2c_read_block(data->client, reg, values, + ARRAY_SIZE(values)); + if (ret) + return ret; + + *val = ((values[1] << 4) | (values[0] >> 4)); + return IIO_VAL_INT; + case IIO_EV_INFO_HYSTERESIS: + ret = ads7138_i2c_read(data->client, + ADS7138_REG_HIGH_TH_HYS_CH(chan->channel)); + if (ret < 0) + return ret; + + *val = ret & ~ADS7138_THRESHOLD_LSB_MASK; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ads7138_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, int val2) +{ + struct ads7138_data *data = iio_priv(indio_dev); + u8 reg, values[2]; + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: { + if (val >= BIT(12) || val < 0) + return -EINVAL; + + reg = (dir == IIO_EV_DIR_RISING) ? + ADS7138_REG_HIGH_TH_HYS_CH(chan->channel) : + ADS7138_REG_LOW_TH_CNT_CH(chan->channel); + + guard(mutex)(&data->lock); + ret = ads7138_i2c_read(data->client, reg); + if (ret < 0) + return ret; + + values[0] = ret & ~ADS7138_THRESHOLD_LSB_MASK; + values[0] |= FIELD_PREP(ADS7138_THRESHOLD_LSB_MASK, val); + values[1] = (val >> 4); + return ads7138_i2c_write_block(data->client, reg, values, + ARRAY_SIZE(values)); + } + case IIO_EV_INFO_HYSTERESIS: { + if (val >= BIT(4) || val < 0) + return -EINVAL; + + reg = ADS7138_REG_HIGH_TH_HYS_CH(chan->channel); + + guard(mutex)(&data->lock); + ret = ads7138_i2c_read(data->client, reg); + if (ret < 0) + return ret; + + values[0] = val & ~ADS7138_THRESHOLD_LSB_MASK; + values[0] |= FIELD_PREP(ADS7138_THRESHOLD_LSB_MASK, ret >> 4); + return ads7138_i2c_write(data->client, reg, values[0]); + } + default: + return -EINVAL; + } +} + +static int ads7138_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct ads7138_data *data = iio_priv(indio_dev); + int ret; + + if (dir != IIO_EV_DIR_EITHER) + return -EINVAL; + + ret = ads7138_i2c_read(data->client, ADS7138_REG_ALERT_CH_SEL); + if (ret < 0) + return ret; + + return (ret & BIT(chan->channel)) ? 1 : 0; +} + +static int ads7138_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct ads7138_data *data = iio_priv(indio_dev); + + if (dir != IIO_EV_DIR_EITHER) + return -EINVAL; + + if (state) + return ads7138_i2c_set_bit(data->client, + ADS7138_REG_ALERT_CH_SEL, + BIT(chan->channel)); + else + return ads7138_i2c_clear_bit(data->client, + ADS7138_REG_ALERT_CH_SEL, + BIT(chan->channel)); +} + +static int ads7138_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = ads7138_samp_freqs_bits[0]; + *length = ARRAY_SIZE(ads7138_samp_freqs_bits[0]); + *type = IIO_VAL_INT; + + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = ads7138_oversampling_ratios; + *length = ARRAY_SIZE(ads7138_oversampling_ratios); + *type = IIO_VAL_INT; + + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const struct iio_info ti_ads7138_info = { + .read_raw = &ads7138_read_raw, + .read_avail = &ads7138_read_avail, + .write_raw = &ads7138_write_raw, + .read_event_value = &ads7138_read_event, + .write_event_value = &ads7138_write_event, + .read_event_config = &ads7138_read_event_config, + .write_event_config = &ads7138_write_event_config, +}; + +static const struct iio_event_spec ads7138_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_HYSTERESIS) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +#define ADS7138_V_CHAN(_chan) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_PEAK) | \ + BIT(IIO_CHAN_INFO_TROUGH), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .datasheet_name = "AIN"#_chan, \ + .event_spec = ads7138_events, \ + .num_event_specs = ARRAY_SIZE(ads7138_events), \ +} + +static const struct iio_chan_spec ads7138_channels[] = { + ADS7138_V_CHAN(0), + ADS7138_V_CHAN(1), + ADS7138_V_CHAN(2), + ADS7138_V_CHAN(3), + ADS7138_V_CHAN(4), + ADS7138_V_CHAN(5), + ADS7138_V_CHAN(6), + ADS7138_V_CHAN(7), +}; + +static irqreturn_t ads7138_event_handler(int irq, void *priv) +{ + struct iio_dev *indio_dev = priv; + struct ads7138_data *data = iio_priv(indio_dev); + struct device *dev = &data->client->dev; + u8 i, events_high, events_low; + u64 code; + int ret; + + /* Check if interrupt was trigger by us */ + ret = ads7138_i2c_read(data->client, ADS7138_REG_EVENT_FLAG); + if (ret <= 0) + return IRQ_NONE; + + ret = ads7138_i2c_read(data->client, ADS7138_REG_EVENT_HIGH_FLAG); + if (ret < 0) { + dev_warn(dev, "Failed to read event high flags: %d\n", ret); + return IRQ_HANDLED; + } + events_high = ret; + + ret = ads7138_i2c_read(data->client, ADS7138_REG_EVENT_LOW_FLAG); + if (ret < 0) { + dev_warn(dev, "Failed to read event low flags: %d\n", ret); + return IRQ_HANDLED; + } + events_low = ret; + + for (i = 0; i < data->chip_data->channel_num; i++) { + if (events_high & BIT(i)) { + code = IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, i, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING); + iio_push_event(indio_dev, code, + iio_get_time_ns(indio_dev)); + } + if (events_low & BIT(i)) { + code = IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, i, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING); + iio_push_event(indio_dev, code, + iio_get_time_ns(indio_dev)); + } + } + + /* Try to clear all interrupt flags */ + ret = ads7138_i2c_write(data->client, ADS7138_REG_EVENT_HIGH_FLAG, 0xFF); + if (ret) + dev_warn(dev, "Failed to clear event high flags: %d\n", ret); + + ret = ads7138_i2c_write(data->client, ADS7138_REG_EVENT_LOW_FLAG, 0xFF); + if (ret) + dev_warn(dev, "Failed to clear event low flags: %d\n", ret); + + return IRQ_HANDLED; +} + +static int ads7138_set_conv_mode(struct ads7138_data *data, + enum ads7138_modes mode) +{ + if (mode == ADS7138_MODE_AUTO) + return ads7138_i2c_set_bit(data->client, ADS7138_REG_OPMODE_CFG, + ADS7138_OPMODE_CFG_CONV_MODE); + return ads7138_i2c_clear_bit(data->client, ADS7138_REG_OPMODE_CFG, + ADS7138_OPMODE_CFG_CONV_MODE); +} + +static int ads7138_init_hw(struct ads7138_data *data) +{ + struct device *dev = &data->client->dev; + int ret; + + data->vref_regu = devm_regulator_get(dev, "avdd"); + if (IS_ERR(data->vref_regu)) + return dev_err_probe(dev, PTR_ERR(data->vref_regu), + "Failed to get avdd regulator\n"); + + ret = regulator_get_voltage(data->vref_regu); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get avdd voltage\n"); + + /* Reset the chip to get a defined starting configuration */ + ret = ads7138_i2c_set_bit(data->client, ADS7138_REG_GENERAL_CFG, + ADS7138_GENERAL_CFG_RST); + if (ret) + return ret; + + ret = ads7138_set_conv_mode(data, ADS7138_MODE_AUTO); + if (ret) + return ret; + + /* Enable statistics and digital window comparator */ + ret = ads7138_i2c_set_bit(data->client, ADS7138_REG_GENERAL_CFG, + ADS7138_GENERAL_CFG_STATS_EN | + ADS7138_GENERAL_CFG_DWC_EN); + if (ret) + return ret; + + /* Enable all channels for auto sequencing */ + ret = ads7138_i2c_set_bit(data->client, ADS7138_REG_AUTO_SEQ_CH_SEL, 0xFF); + if (ret) + return ret; + + /* Set auto sequence mode and start sequencing */ + return ads7138_i2c_set_bit(data->client, ADS7138_REG_SEQUENCE_CFG, + ADS7138_SEQUENCE_CFG_SEQ_START | + ADS7138_SEQUENCE_CFG_SEQ_MODE); +} + +static int ads7138_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct ads7138_data *data; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + data->chip_data = i2c_get_match_data(client); + if (!data->chip_data) + return -ENODEV; + + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; + + indio_dev->name = data->chip_data->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ads7138_channels; + indio_dev->num_channels = ARRAY_SIZE(ads7138_channels); + indio_dev->info = &ti_ads7138_info; + + i2c_set_clientdata(client, indio_dev); + + if (client->irq > 0) { + ret = devm_request_threaded_irq(dev, client->irq, + NULL, ads7138_event_handler, + IRQF_TRIGGER_LOW | + IRQF_ONESHOT | IRQF_SHARED, + client->name, indio_dev); + if (ret) + return ret; + } + + ret = ads7138_init_hw(data); + if (ret) + return dev_err_probe(dev, ret, "Failed to initialize device\n"); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to register iio device\n"); + + return 0; +} + +static int ads7138_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ads7138_data *data = iio_priv(indio_dev); + + return ads7138_set_conv_mode(data, ADS7138_MODE_MANUAL); +} + +static int ads7138_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ads7138_data *data = iio_priv(indio_dev); + + return ads7138_set_conv_mode(data, ADS7138_MODE_AUTO); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(ads7138_pm_ops, + ads7138_runtime_suspend, + ads7138_runtime_resume, + NULL); + +static const struct ads7138_chip_data ads7128_data = { + .name = "ads7128", + .channel_num = 8, +}; + +static const struct ads7138_chip_data ads7138_data = { + .name = "ads7138", + .channel_num = 8, +}; + +static const struct of_device_id ads7138_of_match[] = { + { .compatible = "ti,ads7128", .data = &ads7128_data }, + { .compatible = "ti,ads7138", .data = &ads7138_data }, + { } +}; +MODULE_DEVICE_TABLE(of, ads7138_of_match); + +static const struct i2c_device_id ads7138_device_ids[] = { + { .name = "ads7128", .driver_data = (kernel_ulong_t)&ads7128_data }, + { .name = "ads7138", .driver_data = (kernel_ulong_t)&ads7138_data }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ads7138_device_ids); + +static struct i2c_driver ads7138_driver = { + .driver = { + .name = "ads7138", + .of_match_table = ads7138_of_match, + .pm = pm_ptr(&ads7138_pm_ops), + }, + .id_table = ads7138_device_ids, + .probe = ads7138_probe, +}; +module_i2c_driver(ads7138_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Tobias Sperling "); +MODULE_DESCRIPTION("Driver for TI ADS7138 ADCs"); diff --git a/drivers/iio/adc/ti-ads7924.c b/drivers/iio/adc/ti-ads7924.c new file mode 100644 index 000000000..ff30a52d2 --- /dev/null +++ b/drivers/iio/adc/ti-ads7924.c @@ -0,0 +1,469 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * IIO driver for Texas Instruments ADS7924 ADC, 12-bit, 4-Channels, I2C + * + * Author: Hugo Villeneuve + * Copyright 2022 DimOnOff + * + * based on iio/adc/ti-ads1015.c + * Copyright (c) 2016, Intel Corporation. + * + * Datasheet: https://www.ti.com/lit/gpn/ads7924 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define ADS7924_CHANNELS 4 +#define ADS7924_BITS 12 +#define ADS7924_DATA_SHIFT 4 + +/* Registers. */ +#define ADS7924_MODECNTRL_REG 0x00 +#define ADS7924_INTCNTRL_REG 0x01 +#define ADS7924_DATA0_U_REG 0x02 +#define ADS7924_DATA0_L_REG 0x03 +#define ADS7924_DATA1_U_REG 0x04 +#define ADS7924_DATA1_L_REG 0x05 +#define ADS7924_DATA2_U_REG 0x06 +#define ADS7924_DATA2_L_REG 0x07 +#define ADS7924_DATA3_U_REG 0x08 +#define ADS7924_DATA3_L_REG 0x09 +#define ADS7924_ULR0_REG 0x0A +#define ADS7924_LLR0_REG 0x0B +#define ADS7924_ULR1_REG 0x0C +#define ADS7924_LLR1_REG 0x0D +#define ADS7924_ULR2_REG 0x0E +#define ADS7924_LLR2_REG 0x0F +#define ADS7924_ULR3_REG 0x10 +#define ADS7924_LLR3_REG 0x11 +#define ADS7924_INTCONFIG_REG 0x12 +#define ADS7924_SLPCONFIG_REG 0x13 +#define ADS7924_ACQCONFIG_REG 0x14 +#define ADS7924_PWRCONFIG_REG 0x15 +#define ADS7924_RESET_REG 0x16 + +/* + * Register address INC bit: when set to '1', the register address is + * automatically incremented after every register read which allows convenient + * reading of multiple registers. Set INC to '0' when reading a single register. + */ +#define ADS7924_AUTO_INCREMENT_BIT BIT(7) + +#define ADS7924_MODECNTRL_MODE_MASK GENMASK(7, 2) + +#define ADS7924_MODECNTRL_SEL_MASK GENMASK(1, 0) + +#define ADS7924_CFG_INTPOL_BIT 1 +#define ADS7924_CFG_INTTRIG_BIT 0 + +#define ADS7924_CFG_INTPOL_MASK BIT(ADS7924_CFG_INTPOL_BIT) +#define ADS7924_CFG_INTTRIG_MASK BIT(ADS7924_CFG_INTTRIG_BIT) + +/* Interrupt pin polarity */ +#define ADS7924_CFG_INTPOL_LOW 0 +#define ADS7924_CFG_INTPOL_HIGH 1 + +/* Interrupt pin signaling */ +#define ADS7924_CFG_INTTRIG_LEVEL 0 +#define ADS7924_CFG_INTTRIG_EDGE 1 + +/* Mode control values */ +#define ADS7924_MODECNTRL_IDLE 0x00 +#define ADS7924_MODECNTRL_AWAKE 0x20 +#define ADS7924_MODECNTRL_MANUAL_SINGLE 0x30 +#define ADS7924_MODECNTRL_MANUAL_SCAN 0x32 +#define ADS7924_MODECNTRL_AUTO_SINGLE 0x31 +#define ADS7924_MODECNTRL_AUTO_SCAN 0x33 +#define ADS7924_MODECNTRL_AUTO_SINGLE_SLEEP 0x39 +#define ADS7924_MODECNTRL_AUTO_SCAN_SLEEP 0x3B +#define ADS7924_MODECNTRL_AUTO_BURST_SLEEP 0x3F + +#define ADS7924_ACQTIME_MASK GENMASK(4, 0) + +#define ADS7924_PWRUPTIME_MASK GENMASK(4, 0) + +/* + * The power-up time is allowed to elapse whenever the device has been shutdown + * in idle mode. Power-up time can allow external circuits, such as an + * operational amplifier, between the MUXOUT and ADCIN pins to turn on. + * The nominal time programmed by the PUTIME[4:0] register bits is given by: + * t PU = PWRUPTIME[4:0] × 2 μs + * If a power-up time is not required, set the bits to '0' to effectively bypass. + */ +#define ADS7924_PWRUPTIME_US 0 /* Bypass (0us). */ + +/* + * Acquisition Time according to ACQTIME[4:0] register bits. + * The Acquisition Time is given by: + * t ACQ = (ACQTIME[4:0] × 2 μs) + 6 μs + * Using default value of 0 for ACQTIME[4:0] results in a minimum acquisition + * time of 6us. + */ +#define ADS7924_ACQTIME_US 6 + +/* The conversion time is always 4μs and cannot be programmed by the user. */ +#define ADS7924_CONVTIME_US 4 + +#define ADS7924_TOTAL_CONVTIME_US (ADS7924_PWRUPTIME_US + ADS7924_ACQTIME_US + \ + ADS7924_CONVTIME_US) + +#define ADS7924_V_CHAN(_chan, _addr) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _chan, \ + .address = _addr, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = "AIN"#_chan, \ +} + +struct ads7924_data { + struct device *dev; + struct regmap *regmap; + struct regulator *vref_reg; + + /* GPIO descriptor for device hard-reset pin. */ + struct gpio_desc *reset_gpio; + + /* + * Protects ADC ops, e.g: concurrent sysfs/buffered + * data reads, configuration updates + */ + struct mutex lock; + + /* + * Set to true when the ADC is switched to the continuous-conversion + * mode and exits from a power-down state. This flag is used to avoid + * getting the stale result from the conversion register. + */ + bool conv_invalid; +}; + +static bool ads7924_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ADS7924_MODECNTRL_REG: + case ADS7924_INTCNTRL_REG: + case ADS7924_ULR0_REG: + case ADS7924_LLR0_REG: + case ADS7924_ULR1_REG: + case ADS7924_LLR1_REG: + case ADS7924_ULR2_REG: + case ADS7924_LLR2_REG: + case ADS7924_ULR3_REG: + case ADS7924_LLR3_REG: + case ADS7924_INTCONFIG_REG: + case ADS7924_SLPCONFIG_REG: + case ADS7924_ACQCONFIG_REG: + case ADS7924_PWRCONFIG_REG: + case ADS7924_RESET_REG: + return true; + default: + return false; + } +} + +static const struct regmap_config ads7924_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = ADS7924_RESET_REG, + .writeable_reg = ads7924_is_writeable_reg, +}; + +static const struct iio_chan_spec ads7924_channels[] = { + ADS7924_V_CHAN(0, ADS7924_DATA0_U_REG), + ADS7924_V_CHAN(1, ADS7924_DATA1_U_REG), + ADS7924_V_CHAN(2, ADS7924_DATA2_U_REG), + ADS7924_V_CHAN(3, ADS7924_DATA3_U_REG), +}; + +static int ads7924_get_adc_result(struct ads7924_data *data, + struct iio_chan_spec const *chan, int *val) +{ + int ret; + __be16 be_val; + + if (chan->channel < 0 || chan->channel >= ADS7924_CHANNELS) + return -EINVAL; + + guard(mutex)(&data->lock); + + if (data->conv_invalid) { + int conv_time; + + conv_time = ADS7924_TOTAL_CONVTIME_US; + /* Allow 10% for internal clock inaccuracy. */ + conv_time += conv_time / 10; + usleep_range(conv_time, conv_time + 1); + data->conv_invalid = false; + } + + ret = regmap_raw_read(data->regmap, ADS7924_AUTO_INCREMENT_BIT | + chan->address, &be_val, sizeof(be_val)); + if (ret) + return ret; + + *val = be16_to_cpu(be_val) >> ADS7924_DATA_SHIFT; + + return 0; +} + +static int ads7924_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int ret, vref_uv; + struct ads7924_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = ads7924_get_adc_result(data, chan, val); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + vref_uv = regulator_get_voltage(data->vref_reg); + if (vref_uv < 0) + return vref_uv; + + *val = vref_uv / 1000; /* Convert reg voltage to mV */ + *val2 = ADS7924_BITS; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static const struct iio_info ads7924_info = { + .read_raw = ads7924_read_raw, +}; + +static int ads7924_get_channels_config(struct device *dev) +{ + struct fwnode_handle *node; + int num_channels = 0; + + device_for_each_child_node(dev, node) { + u32 pval; + unsigned int channel; + + if (fwnode_property_read_u32(node, "reg", &pval)) { + dev_err(dev, "invalid reg on %pfw\n", node); + continue; + } + + channel = pval; + if (channel >= ADS7924_CHANNELS) { + dev_err(dev, "invalid channel index %d on %pfw\n", + channel, node); + continue; + } + + num_channels++; + } + + if (!num_channels) + return -EINVAL; + + return 0; +} + +static int ads7924_set_conv_mode(struct ads7924_data *data, int mode) +{ + int ret; + unsigned int mode_field; + struct device *dev = data->dev; + + /* + * When switching between modes, be sure to first select the Awake mode + * and then switch to the desired mode. This procedure ensures the + * internal control logic is properly synchronized. + */ + if (mode != ADS7924_MODECNTRL_IDLE) { + mode_field = FIELD_PREP(ADS7924_MODECNTRL_MODE_MASK, + ADS7924_MODECNTRL_AWAKE); + + ret = regmap_update_bits(data->regmap, ADS7924_MODECNTRL_REG, + ADS7924_MODECNTRL_MODE_MASK, + mode_field); + if (ret) { + dev_err(dev, "failed to set awake mode (%pe)\n", + ERR_PTR(ret)); + return ret; + } + } + + mode_field = FIELD_PREP(ADS7924_MODECNTRL_MODE_MASK, mode); + + ret = regmap_update_bits(data->regmap, ADS7924_MODECNTRL_REG, + ADS7924_MODECNTRL_MODE_MASK, mode_field); + if (ret) + dev_err(dev, "failed to set mode %d (%pe)\n", mode, + ERR_PTR(ret)); + + return ret; +} + +static int ads7924_reset(struct iio_dev *indio_dev) +{ + struct ads7924_data *data = iio_priv(indio_dev); + + if (data->reset_gpio) { + gpiod_set_value(data->reset_gpio, 1); /* Assert. */ + /* Educated guess: assert time not specified in datasheet... */ + mdelay(100); + gpiod_set_value(data->reset_gpio, 0); /* Deassert. */ + return 0; + } + + /* + * A write of 10101010 to this register will generate a + * software reset of the ADS7924. + */ + return regmap_write(data->regmap, ADS7924_RESET_REG, 0b10101010); +}; + +static void ads7924_reg_disable(void *data) +{ + regulator_disable(data); +} + +static void ads7924_set_idle_mode(void *data) +{ + ads7924_set_conv_mode(data, ADS7924_MODECNTRL_IDLE); +} + +static int ads7924_probe(struct i2c_client *client) +{ + struct iio_dev *indio_dev; + struct ads7924_data *data; + struct device *dev = &client->dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + + data->dev = dev; + + /* Initialize the reset GPIO as output with an initial value of 0. */ + data->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(data->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(data->reset_gpio), + "failed to get request reset GPIO\n"); + + mutex_init(&data->lock); + + indio_dev->name = "ads7924"; + indio_dev->modes = INDIO_DIRECT_MODE; + + indio_dev->channels = ads7924_channels; + indio_dev->num_channels = ARRAY_SIZE(ads7924_channels); + indio_dev->info = &ads7924_info; + + ret = ads7924_get_channels_config(dev); + if (ret < 0) + return dev_err_probe(dev, ret, + "failed to get channels configuration\n"); + + data->regmap = devm_regmap_init_i2c(client, &ads7924_regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "failed to init regmap\n"); + + data->vref_reg = devm_regulator_get(dev, "vref"); + if (IS_ERR(data->vref_reg)) + return dev_err_probe(dev, PTR_ERR(data->vref_reg), + "failed to get vref regulator\n"); + + ret = regulator_enable(data->vref_reg); + if (ret) + return dev_err_probe(dev, ret, + "failed to enable regulator\n"); + + ret = devm_add_action_or_reset(dev, ads7924_reg_disable, data->vref_reg); + if (ret) + return ret; + + ret = ads7924_reset(indio_dev); + if (ret < 0) + return dev_err_probe(dev, ret, + "failed to reset device\n"); + + ret = ads7924_set_conv_mode(data, ADS7924_MODECNTRL_AUTO_SCAN); + if (ret) + return dev_err_probe(dev, ret, + "failed to set conversion mode\n"); + + ret = devm_add_action_or_reset(dev, ads7924_set_idle_mode, data); + if (ret) + return ret; + + /* Use minimum signal acquire time. */ + ret = regmap_update_bits(data->regmap, ADS7924_ACQCONFIG_REG, + ADS7924_ACQTIME_MASK, + FIELD_PREP(ADS7924_ACQTIME_MASK, 0)); + if (ret < 0) + return dev_err_probe(dev, ret, + "failed to configure signal acquire time\n"); + + /* Disable power-up time. */ + ret = regmap_update_bits(data->regmap, ADS7924_PWRCONFIG_REG, + ADS7924_PWRUPTIME_MASK, + FIELD_PREP(ADS7924_PWRUPTIME_MASK, 0)); + if (ret < 0) + return dev_err_probe(dev, ret, + "failed to configure power-up time\n"); + + data->conv_invalid = true; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret < 0) + return dev_err_probe(dev, ret, + "failed to register IIO device\n"); + + return 0; +} + +static const struct i2c_device_id ads7924_id[] = { + { .name = "ads7924" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ads7924_id); + +static const struct of_device_id ads7924_of_match[] = { + { .compatible = "ti,ads7924", }, + { } +}; +MODULE_DEVICE_TABLE(of, ads7924_of_match); + +static struct i2c_driver ads7924_driver = { + .driver = { + .name = "ads7924", + .of_match_table = ads7924_of_match, + }, + .probe = ads7924_probe, + .id_table = ads7924_id, +}; + +module_i2c_driver(ads7924_driver); + +MODULE_AUTHOR("Hugo Villeneuve "); +MODULE_DESCRIPTION("Texas Instruments ADS7924 ADC I2C driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/ti-ads7950.c b/drivers/iio/adc/ti-ads7950.c new file mode 100644 index 000000000..d27c1f288 --- /dev/null +++ b/drivers/iio/adc/ti-ads7950.c @@ -0,0 +1,667 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Texas Instruments ADS7950 SPI ADC driver + * + * Copyright 2016 David Lechner + * + * Based on iio/ad7923.c: + * Copyright 2011 Analog Devices Inc + * Copyright 2012 CS Systemes d'Information + * + * And also on hwmon/ads79xx.c + * Copyright (C) 2013 Texas Instruments Incorporated - https://www.ti.com/ + * Nishanth Menon + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* + * In case of ACPI, we use the 5000 mV as default for the reference pin. + * Device tree users encode that via the vref-supply regulator. + */ +#define TI_ADS7950_VA_MV_ACPI_DEFAULT 5000 + +#define TI_ADS7950_CR_GPIO BIT(14) +#define TI_ADS7950_CR_MANUAL BIT(12) +#define TI_ADS7950_CR_WRITE BIT(11) +#define TI_ADS7950_CR_CHAN(ch) ((ch) << 7) +#define TI_ADS7950_CR_RANGE_5V BIT(6) +#define TI_ADS7950_CR_GPIO_DATA BIT(4) + +#define TI_ADS7950_MAX_CHAN 16 +#define TI_ADS7950_NUM_GPIOS 4 + +/* val = value, dec = left shift, bits = number of bits of the mask */ +#define TI_ADS7950_EXTRACT(val, dec, bits) \ + (((val) >> (dec)) & ((1 << (bits)) - 1)) + +#define TI_ADS7950_MAN_CMD(cmd) (TI_ADS7950_CR_MANUAL | (cmd)) +#define TI_ADS7950_GPIO_CMD(cmd) (TI_ADS7950_CR_GPIO | (cmd)) + +/* Manual mode configuration */ +#define TI_ADS7950_MAN_CMD_SETTINGS(st) \ + (TI_ADS7950_MAN_CMD(TI_ADS7950_CR_WRITE | st->cmd_settings_bitmask)) +/* GPIO mode configuration */ +#define TI_ADS7950_GPIO_CMD_SETTINGS(st) \ + (TI_ADS7950_GPIO_CMD(st->gpio_cmd_settings_bitmask)) + +struct ti_ads7950_state { + struct spi_device *spi; + struct spi_transfer ring_xfer; + struct spi_transfer scan_single_xfer[3]; + struct spi_message ring_msg; + struct spi_message scan_single_msg; + + /* Lock to protect the spi xfer buffers */ + struct mutex slock; + struct gpio_chip chip; + + struct regulator *reg; + unsigned int vref_mv; + + /* + * Bitmask of lower 7 bits used for configuration + * These bits only can be written when TI_ADS7950_CR_WRITE + * is set, otherwise it retains its original state. + * [0-3] GPIO signal + * [4] Set following frame to return GPIO signal values + * [5] Powers down device + * [6] Sets Vref range1(2.5v) or range2(5v) + * + * Bits present on Manual/Auto1/Auto2 commands + */ + unsigned int cmd_settings_bitmask; + + /* + * Bitmask of GPIO command + * [0-3] GPIO direction + * [4-6] Different GPIO alarm mode configurations + * [7] GPIO 2 as device range input + * [8] GPIO 3 as device power down input + * [9] Reset all registers + * [10-11] N/A + */ + unsigned int gpio_cmd_settings_bitmask; + + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + */ + u16 rx_buf[TI_ADS7950_MAX_CHAN + 2] __aligned(IIO_DMA_MINALIGN); + u16 tx_buf[TI_ADS7950_MAX_CHAN + 2]; + u16 single_tx; + u16 single_rx; + +}; + +struct ti_ads7950_chip_info { + const struct iio_chan_spec *channels; + unsigned int num_channels; +}; + +#define TI_ADS7950_V_CHAN(index, bits) \ +{ \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .address = index, \ + .datasheet_name = "CH##index", \ + .scan_index = index, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = bits, \ + .storagebits = 16, \ + .shift = 12 - (bits), \ + .endianness = IIO_CPU, \ + }, \ +} + +#define DECLARE_TI_ADS7950_4_CHANNELS(name, bits) \ +const struct iio_chan_spec name ## _channels[] = { \ + TI_ADS7950_V_CHAN(0, bits), \ + TI_ADS7950_V_CHAN(1, bits), \ + TI_ADS7950_V_CHAN(2, bits), \ + TI_ADS7950_V_CHAN(3, bits), \ + IIO_CHAN_SOFT_TIMESTAMP(4), \ +} + +#define DECLARE_TI_ADS7950_8_CHANNELS(name, bits) \ +const struct iio_chan_spec name ## _channels[] = { \ + TI_ADS7950_V_CHAN(0, bits), \ + TI_ADS7950_V_CHAN(1, bits), \ + TI_ADS7950_V_CHAN(2, bits), \ + TI_ADS7950_V_CHAN(3, bits), \ + TI_ADS7950_V_CHAN(4, bits), \ + TI_ADS7950_V_CHAN(5, bits), \ + TI_ADS7950_V_CHAN(6, bits), \ + TI_ADS7950_V_CHAN(7, bits), \ + IIO_CHAN_SOFT_TIMESTAMP(8), \ +} + +#define DECLARE_TI_ADS7950_12_CHANNELS(name, bits) \ +const struct iio_chan_spec name ## _channels[] = { \ + TI_ADS7950_V_CHAN(0, bits), \ + TI_ADS7950_V_CHAN(1, bits), \ + TI_ADS7950_V_CHAN(2, bits), \ + TI_ADS7950_V_CHAN(3, bits), \ + TI_ADS7950_V_CHAN(4, bits), \ + TI_ADS7950_V_CHAN(5, bits), \ + TI_ADS7950_V_CHAN(6, bits), \ + TI_ADS7950_V_CHAN(7, bits), \ + TI_ADS7950_V_CHAN(8, bits), \ + TI_ADS7950_V_CHAN(9, bits), \ + TI_ADS7950_V_CHAN(10, bits), \ + TI_ADS7950_V_CHAN(11, bits), \ + IIO_CHAN_SOFT_TIMESTAMP(12), \ +} + +#define DECLARE_TI_ADS7950_16_CHANNELS(name, bits) \ +const struct iio_chan_spec name ## _channels[] = { \ + TI_ADS7950_V_CHAN(0, bits), \ + TI_ADS7950_V_CHAN(1, bits), \ + TI_ADS7950_V_CHAN(2, bits), \ + TI_ADS7950_V_CHAN(3, bits), \ + TI_ADS7950_V_CHAN(4, bits), \ + TI_ADS7950_V_CHAN(5, bits), \ + TI_ADS7950_V_CHAN(6, bits), \ + TI_ADS7950_V_CHAN(7, bits), \ + TI_ADS7950_V_CHAN(8, bits), \ + TI_ADS7950_V_CHAN(9, bits), \ + TI_ADS7950_V_CHAN(10, bits), \ + TI_ADS7950_V_CHAN(11, bits), \ + TI_ADS7950_V_CHAN(12, bits), \ + TI_ADS7950_V_CHAN(13, bits), \ + TI_ADS7950_V_CHAN(14, bits), \ + TI_ADS7950_V_CHAN(15, bits), \ + IIO_CHAN_SOFT_TIMESTAMP(16), \ +} + +static DECLARE_TI_ADS7950_4_CHANNELS(ti_ads7950, 12); +static DECLARE_TI_ADS7950_8_CHANNELS(ti_ads7951, 12); +static DECLARE_TI_ADS7950_12_CHANNELS(ti_ads7952, 12); +static DECLARE_TI_ADS7950_16_CHANNELS(ti_ads7953, 12); +static DECLARE_TI_ADS7950_4_CHANNELS(ti_ads7954, 10); +static DECLARE_TI_ADS7950_8_CHANNELS(ti_ads7955, 10); +static DECLARE_TI_ADS7950_12_CHANNELS(ti_ads7956, 10); +static DECLARE_TI_ADS7950_16_CHANNELS(ti_ads7957, 10); +static DECLARE_TI_ADS7950_4_CHANNELS(ti_ads7958, 8); +static DECLARE_TI_ADS7950_8_CHANNELS(ti_ads7959, 8); +static DECLARE_TI_ADS7950_12_CHANNELS(ti_ads7960, 8); +static DECLARE_TI_ADS7950_16_CHANNELS(ti_ads7961, 8); + +static const struct ti_ads7950_chip_info ti_ads7950_chip_info = { + .channels = ti_ads7950_channels, + .num_channels = ARRAY_SIZE(ti_ads7950_channels), +}; + +static const struct ti_ads7950_chip_info ti_ads7951_chip_info = { + .channels = ti_ads7951_channels, + .num_channels = ARRAY_SIZE(ti_ads7951_channels), +}; + +static const struct ti_ads7950_chip_info ti_ads7952_chip_info = { + .channels = ti_ads7952_channels, + .num_channels = ARRAY_SIZE(ti_ads7952_channels), +}; + +static const struct ti_ads7950_chip_info ti_ads7953_chip_info = { + .channels = ti_ads7953_channels, + .num_channels = ARRAY_SIZE(ti_ads7953_channels), +}; + +static const struct ti_ads7950_chip_info ti_ads7954_chip_info = { + .channels = ti_ads7954_channels, + .num_channels = ARRAY_SIZE(ti_ads7954_channels), +}; + +static const struct ti_ads7950_chip_info ti_ads7955_chip_info = { + .channels = ti_ads7955_channels, + .num_channels = ARRAY_SIZE(ti_ads7955_channels), +}; + +static const struct ti_ads7950_chip_info ti_ads7956_chip_info = { + .channels = ti_ads7956_channels, + .num_channels = ARRAY_SIZE(ti_ads7956_channels), +}; + +static const struct ti_ads7950_chip_info ti_ads7957_chip_info = { + .channels = ti_ads7957_channels, + .num_channels = ARRAY_SIZE(ti_ads7957_channels), +}; + +static const struct ti_ads7950_chip_info ti_ads7958_chip_info = { + .channels = ti_ads7958_channels, + .num_channels = ARRAY_SIZE(ti_ads7958_channels), +}; + +static const struct ti_ads7950_chip_info ti_ads7959_chip_info = { + .channels = ti_ads7959_channels, + .num_channels = ARRAY_SIZE(ti_ads7959_channels), +}; + +static const struct ti_ads7950_chip_info ti_ads7960_chip_info = { + .channels = ti_ads7960_channels, + .num_channels = ARRAY_SIZE(ti_ads7960_channels), +}; + +static const struct ti_ads7950_chip_info ti_ads7961_chip_info = { + .channels = ti_ads7961_channels, + .num_channels = ARRAY_SIZE(ti_ads7961_channels), +}; + +static irqreturn_t ti_ads7950_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ti_ads7950_state *st = iio_priv(indio_dev); + int ret; + + do { + guard(mutex)(&st->slock); + + ret = spi_sync(st->spi, &st->ring_msg); + if (ret) + break; + + iio_push_to_buffers_with_ts_unaligned(indio_dev, &st->rx_buf[2], + sizeof(*st->rx_buf) * + TI_ADS7950_MAX_CHAN, + iio_get_time_ns(indio_dev)); + } while (0); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ti_ads7950_scan_direct(struct iio_dev *indio_dev, unsigned int ch) +{ + struct ti_ads7950_state *st = iio_priv(indio_dev); + int ret, cmd; + + guard(mutex)(&st->slock); + + cmd = TI_ADS7950_MAN_CMD(TI_ADS7950_CR_CHAN(ch)); + st->single_tx = cmd; + + ret = spi_sync(st->spi, &st->scan_single_msg); + if (ret) + return ret; + + return st->single_rx; +} + +static unsigned int ti_ads7950_get_range(struct ti_ads7950_state *st) +{ + unsigned int vref = st->vref_mv; + + if (st->cmd_settings_bitmask & TI_ADS7950_CR_RANGE_5V) + vref *= 2; + + return vref; +} + +static int ti_ads7950_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long m) +{ + struct ti_ads7950_state *st = iio_priv(indio_dev); + int ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = ti_ads7950_scan_direct(indio_dev, chan->address); + if (ret < 0) + return ret; + + if (chan->address != TI_ADS7950_EXTRACT(ret, 12, 4)) + return -EIO; + + *val = TI_ADS7950_EXTRACT(ret, chan->scan_type.shift, + chan->scan_type.realbits); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = ti_ads7950_get_range(st); + *val2 = (1 << chan->scan_type.realbits) - 1; + + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; +} + +static const struct iio_info ti_ads7950_info = { + .read_raw = &ti_ads7950_read_raw, +}; + +static int ti_ads7950_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ti_ads7950_state *st = iio_priv(indio_dev); + u32 len = 0; + u32 i; + u16 cmd; + + for_each_set_bit(i, indio_dev->active_scan_mask, indio_dev->num_channels) { + cmd = TI_ADS7950_MAN_CMD(TI_ADS7950_CR_CHAN(i)); + st->tx_buf[len++] = cmd; + } + + /* Data for the 1st channel is not returned until the 3rd transfer */ + st->tx_buf[len++] = 0; + st->tx_buf[len++] = 0; + + st->ring_xfer.len = len * 2; + + return spi_optimize_message(st->spi, &st->ring_msg); +} + +static int ti_ads7950_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ti_ads7950_state *st = iio_priv(indio_dev); + + spi_unoptimize_message(&st->ring_msg); + + return 0; +} + +static const struct iio_buffer_setup_ops ti_ads7950_buffer_setup_ops = { + .preenable = ti_ads7950_buffer_preenable, + .postdisable = ti_ads7950_buffer_postdisable, +}; + +static int ti_ads7950_set(struct gpio_chip *chip, unsigned int offset, + int value) +{ + struct ti_ads7950_state *st = gpiochip_get_data(chip); + + guard(mutex)(&st->slock); + + if (value) + st->cmd_settings_bitmask |= BIT(offset); + else + st->cmd_settings_bitmask &= ~BIT(offset); + + st->single_tx = TI_ADS7950_MAN_CMD_SETTINGS(st); + + return spi_sync(st->spi, &st->scan_single_msg); +} + +static int ti_ads7950_get(struct gpio_chip *chip, unsigned int offset) +{ + struct ti_ads7950_state *st = gpiochip_get_data(chip); + bool state; + int ret; + + guard(mutex)(&st->slock); + + /* If set as output, return the output */ + if (st->gpio_cmd_settings_bitmask & BIT(offset)) { + state = st->cmd_settings_bitmask & BIT(offset); + return state; + } + + /* GPIO data bit sets SDO bits 12-15 to GPIO input */ + st->cmd_settings_bitmask |= TI_ADS7950_CR_GPIO_DATA; + st->single_tx = TI_ADS7950_MAN_CMD_SETTINGS(st); + ret = spi_sync(st->spi, &st->scan_single_msg); + if (ret) + return ret; + + state = (st->single_rx >> 12) & BIT(offset); + + /* Revert back to original settings */ + st->cmd_settings_bitmask &= ~TI_ADS7950_CR_GPIO_DATA; + st->single_tx = TI_ADS7950_MAN_CMD_SETTINGS(st); + ret = spi_sync(st->spi, &st->scan_single_msg); + if (ret) + return ret; + + return state; +} + +static int ti_ads7950_get_direction(struct gpio_chip *chip, + unsigned int offset) +{ + struct ti_ads7950_state *st = gpiochip_get_data(chip); + + /* Bitmask is inverted from GPIO framework 0=input/1=output */ + return !(st->gpio_cmd_settings_bitmask & BIT(offset)); +} + +static int _ti_ads7950_set_direction(struct gpio_chip *chip, int offset, + int input) +{ + struct ti_ads7950_state *st = gpiochip_get_data(chip); + + guard(mutex)(&st->slock); + + /* Only change direction if needed */ + if (input && (st->gpio_cmd_settings_bitmask & BIT(offset))) + st->gpio_cmd_settings_bitmask &= ~BIT(offset); + else if (!input && !(st->gpio_cmd_settings_bitmask & BIT(offset))) + st->gpio_cmd_settings_bitmask |= BIT(offset); + else + return 0; + + st->single_tx = TI_ADS7950_GPIO_CMD_SETTINGS(st); + + return spi_sync(st->spi, &st->scan_single_msg); +} + +static int ti_ads7950_direction_input(struct gpio_chip *chip, + unsigned int offset) +{ + return _ti_ads7950_set_direction(chip, offset, 1); +} + +static int ti_ads7950_direction_output(struct gpio_chip *chip, + unsigned int offset, int value) +{ + int ret; + + ret = ti_ads7950_set(chip, offset, value); + if (ret) + return ret; + + return _ti_ads7950_set_direction(chip, offset, 0); +} + +static int ti_ads7950_init_hw(struct ti_ads7950_state *st) +{ + int ret; + + guard(mutex)(&st->slock); + + /* Settings for Manual/Auto1/Auto2 commands */ + /* Default to 5v ref */ + st->cmd_settings_bitmask = TI_ADS7950_CR_RANGE_5V; + st->single_tx = TI_ADS7950_MAN_CMD_SETTINGS(st); + ret = spi_sync(st->spi, &st->scan_single_msg); + if (ret) + return ret; + + /* Settings for GPIO command */ + st->gpio_cmd_settings_bitmask = 0x0; + st->single_tx = TI_ADS7950_GPIO_CMD_SETTINGS(st); + return spi_sync(st->spi, &st->scan_single_msg); +} + +static int ti_ads7950_probe(struct spi_device *spi) +{ + struct ti_ads7950_state *st; + struct iio_dev *indio_dev; + const struct ti_ads7950_chip_info *info; + int ret; + + spi->bits_per_word = 16; + spi->mode |= SPI_CS_WORD; + ret = spi_setup(spi); + if (ret) + return dev_err_probe(&spi->dev, ret, "Error in spi setup\n"); + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->spi = spi; + + info = spi_get_device_match_data(spi); + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = info->channels; + indio_dev->num_channels = info->num_channels; + indio_dev->info = &ti_ads7950_info; + + /* build spi ring message */ + spi_message_init(&st->ring_msg); + + st->ring_xfer.tx_buf = &st->tx_buf[0]; + st->ring_xfer.rx_buf = &st->rx_buf[0]; + /* len will be set later */ + + spi_message_add_tail(&st->ring_xfer, &st->ring_msg); + + /* + * Setup default message. The sample is read at the end of the first + * transfer, then it takes one full cycle to convert the sample and one + * more cycle to send the value. The conversion process is driven by + * the SPI clock, which is why we have 3 transfers. The middle one is + * just dummy data sent while the chip is converting the sample that + * was read at the end of the first transfer. + */ + + st->scan_single_xfer[0].tx_buf = &st->single_tx; + st->scan_single_xfer[0].len = 2; + st->scan_single_xfer[0].cs_change = 1; + st->scan_single_xfer[1].tx_buf = &st->single_tx; + st->scan_single_xfer[1].len = 2; + st->scan_single_xfer[1].cs_change = 1; + st->scan_single_xfer[2].rx_buf = &st->single_rx; + st->scan_single_xfer[2].len = 2; + + spi_message_init_with_transfers(&st->scan_single_msg, + st->scan_single_xfer, 3); + + ret = devm_mutex_init(&spi->dev, &st->slock); + if (ret) + return ret; + + /* Use hard coded value for reference voltage in ACPI case */ + if (ACPI_COMPANION(&spi->dev)) { + st->vref_mv = TI_ADS7950_VA_MV_ACPI_DEFAULT; + } else { + ret = devm_regulator_get_enable_read_voltage(&spi->dev, "vref"); + if (ret < 0) + return dev_err_probe(&spi->dev, ret, + "Failed to get regulator \"vref\"\n"); + + st->vref_mv = ret / 1000; + } + + ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, NULL, + &ti_ads7950_trigger_handler, + &ti_ads7950_buffer_setup_ops); + if (ret) + return dev_err_probe(&spi->dev, ret, + "Failed to setup triggered buffer\n"); + + ret = ti_ads7950_init_hw(st); + if (ret) + return dev_err_probe(&spi->dev, ret, + "Failed to init adc chip\n"); + + ret = devm_iio_device_register(&spi->dev, indio_dev); + if (ret) + return dev_err_probe(&spi->dev, ret, + "Failed to register iio device\n"); + + /* Add GPIO chip */ + st->chip.label = dev_name(&st->spi->dev); + st->chip.parent = &st->spi->dev; + st->chip.owner = THIS_MODULE; + st->chip.can_sleep = true; + st->chip.base = -1; + st->chip.ngpio = TI_ADS7950_NUM_GPIOS; + st->chip.get_direction = ti_ads7950_get_direction; + st->chip.direction_input = ti_ads7950_direction_input; + st->chip.direction_output = ti_ads7950_direction_output; + st->chip.get = ti_ads7950_get; + st->chip.set = ti_ads7950_set; + + ret = devm_gpiochip_add_data(&spi->dev, &st->chip, st); + if (ret) + return dev_err_probe(&spi->dev, ret, + "Failed to init GPIOs\n"); + + return 0; +} + +static const struct spi_device_id ti_ads7950_id[] = { + { .name = "ads7950", .driver_data = (kernel_ulong_t)&ti_ads7950_chip_info }, + { .name = "ads7951", .driver_data = (kernel_ulong_t)&ti_ads7951_chip_info }, + { .name = "ads7952", .driver_data = (kernel_ulong_t)&ti_ads7952_chip_info }, + { .name = "ads7953", .driver_data = (kernel_ulong_t)&ti_ads7953_chip_info }, + { .name = "ads7954", .driver_data = (kernel_ulong_t)&ti_ads7954_chip_info }, + { .name = "ads7955", .driver_data = (kernel_ulong_t)&ti_ads7955_chip_info }, + { .name = "ads7956", .driver_data = (kernel_ulong_t)&ti_ads7956_chip_info }, + { .name = "ads7957", .driver_data = (kernel_ulong_t)&ti_ads7957_chip_info }, + { .name = "ads7958", .driver_data = (kernel_ulong_t)&ti_ads7958_chip_info }, + { .name = "ads7959", .driver_data = (kernel_ulong_t)&ti_ads7959_chip_info }, + { .name = "ads7960", .driver_data = (kernel_ulong_t)&ti_ads7960_chip_info }, + { .name = "ads7961", .driver_data = (kernel_ulong_t)&ti_ads7961_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ti_ads7950_id); + +static const struct of_device_id ads7950_of_table[] = { + { .compatible = "ti,ads7950", .data = &ti_ads7950_chip_info }, + { .compatible = "ti,ads7951", .data = &ti_ads7951_chip_info }, + { .compatible = "ti,ads7952", .data = &ti_ads7952_chip_info }, + { .compatible = "ti,ads7953", .data = &ti_ads7953_chip_info }, + { .compatible = "ti,ads7954", .data = &ti_ads7954_chip_info }, + { .compatible = "ti,ads7955", .data = &ti_ads7955_chip_info }, + { .compatible = "ti,ads7956", .data = &ti_ads7956_chip_info }, + { .compatible = "ti,ads7957", .data = &ti_ads7957_chip_info }, + { .compatible = "ti,ads7958", .data = &ti_ads7958_chip_info }, + { .compatible = "ti,ads7959", .data = &ti_ads7959_chip_info }, + { .compatible = "ti,ads7960", .data = &ti_ads7960_chip_info }, + { .compatible = "ti,ads7961", .data = &ti_ads7961_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ads7950_of_table); + +static struct spi_driver ti_ads7950_driver = { + .driver = { + .name = "ads7950", + .of_match_table = ads7950_of_table, + }, + .probe = ti_ads7950_probe, + .id_table = ti_ads7950_id, +}; +module_spi_driver(ti_ads7950_driver); + +MODULE_AUTHOR("David Lechner "); +MODULE_DESCRIPTION("TI TI_ADS7950 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ti-ads8344.c b/drivers/iio/adc/ti-ads8344.c new file mode 100644 index 000000000..3bec8a2e6 --- /dev/null +++ b/drivers/iio/adc/ti-ads8344.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ADS8344 16-bit 8-Channel ADC driver + * + * Author: Gregory CLEMENT + * + * Datasheet: https://www.ti.com/lit/ds/symlink/ads8344.pdf + */ + +#include +#include +#include +#include +#include +#include + +#define ADS8344_START BIT(7) +#define ADS8344_SINGLE_END BIT(2) +#define ADS8344_CHANNEL(channel) ((channel) << 4) +#define ADS8344_CLOCK_INTERNAL 0x2 /* PD1 = 1 and PD0 = 0 */ + +struct ads8344 { + struct spi_device *spi; + struct regulator *reg; + /* + * Lock protecting access to adc->tx_buff and rx_buff, + * especially from concurrent read on sysfs file. + */ + struct mutex lock; + + u8 tx_buf __aligned(IIO_DMA_MINALIGN); + u8 rx_buf[3]; +}; + +#define ADS8344_VOLTAGE_CHANNEL(chan, addr) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .address = addr, \ + } + +#define ADS8344_VOLTAGE_CHANNEL_DIFF(chan1, chan2, addr) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (chan1), \ + .channel2 = (chan2), \ + .differential = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .address = addr, \ + } + +static const struct iio_chan_spec ads8344_channels[] = { + ADS8344_VOLTAGE_CHANNEL(0, 0), + ADS8344_VOLTAGE_CHANNEL(1, 4), + ADS8344_VOLTAGE_CHANNEL(2, 1), + ADS8344_VOLTAGE_CHANNEL(3, 5), + ADS8344_VOLTAGE_CHANNEL(4, 2), + ADS8344_VOLTAGE_CHANNEL(5, 6), + ADS8344_VOLTAGE_CHANNEL(6, 3), + ADS8344_VOLTAGE_CHANNEL(7, 7), + ADS8344_VOLTAGE_CHANNEL_DIFF(0, 1, 8), + ADS8344_VOLTAGE_CHANNEL_DIFF(2, 3, 9), + ADS8344_VOLTAGE_CHANNEL_DIFF(4, 5, 10), + ADS8344_VOLTAGE_CHANNEL_DIFF(6, 7, 11), + ADS8344_VOLTAGE_CHANNEL_DIFF(1, 0, 12), + ADS8344_VOLTAGE_CHANNEL_DIFF(3, 2, 13), + ADS8344_VOLTAGE_CHANNEL_DIFF(5, 4, 14), + ADS8344_VOLTAGE_CHANNEL_DIFF(7, 6, 15), +}; + +static int ads8344_adc_conversion(struct ads8344 *adc, int channel, + bool differential) +{ + struct spi_device *spi = adc->spi; + int ret; + + adc->tx_buf = ADS8344_START; + if (!differential) + adc->tx_buf |= ADS8344_SINGLE_END; + adc->tx_buf |= ADS8344_CHANNEL(channel); + adc->tx_buf |= ADS8344_CLOCK_INTERNAL; + + ret = spi_write(spi, &adc->tx_buf, 1); + if (ret) + return ret; + + udelay(9); + + ret = spi_read(spi, adc->rx_buf, sizeof(adc->rx_buf)); + if (ret) + return ret; + + return adc->rx_buf[0] << 9 | adc->rx_buf[1] << 1 | adc->rx_buf[2] >> 7; +} + +static int ads8344_read_raw(struct iio_dev *iio, + struct iio_chan_spec const *channel, int *value, + int *shift, long mask) +{ + struct ads8344 *adc = iio_priv(iio); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&adc->lock); + *value = ads8344_adc_conversion(adc, channel->address, + channel->differential); + mutex_unlock(&adc->lock); + if (*value < 0) + return *value; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *value = regulator_get_voltage(adc->reg); + if (*value < 0) + return *value; + + /* convert regulator output voltage to mV */ + *value /= 1000; + *shift = 16; + + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static const struct iio_info ads8344_info = { + .read_raw = ads8344_read_raw, +}; + +static void ads8344_reg_disable(void *data) +{ + regulator_disable(data); +} + +static int ads8344_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct ads8344 *adc; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->spi = spi; + mutex_init(&adc->lock); + + indio_dev->name = dev_name(&spi->dev); + indio_dev->info = &ads8344_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ads8344_channels; + indio_dev->num_channels = ARRAY_SIZE(ads8344_channels); + + adc->reg = devm_regulator_get(&spi->dev, "vref"); + if (IS_ERR(adc->reg)) + return PTR_ERR(adc->reg); + + ret = regulator_enable(adc->reg); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&spi->dev, ads8344_reg_disable, adc->reg); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct of_device_id ads8344_of_match[] = { + { .compatible = "ti,ads8344", }, + { } +}; +MODULE_DEVICE_TABLE(of, ads8344_of_match); + +static struct spi_driver ads8344_driver = { + .driver = { + .name = "ads8344", + .of_match_table = ads8344_of_match, + }, + .probe = ads8344_probe, +}; +module_spi_driver(ads8344_driver); + +MODULE_AUTHOR("Gregory CLEMENT "); +MODULE_DESCRIPTION("ADS8344 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/ti-ads8688.c b/drivers/iio/adc/ti-ads8688.c new file mode 100644 index 000000000..8ad070b5e --- /dev/null +++ b/drivers/iio/adc/ti-ads8688.c @@ -0,0 +1,481 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2015 Prevas A/S + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define ADS8688_CMD_REG(x) (x << 8) +#define ADS8688_CMD_REG_NOOP 0x00 +#define ADS8688_CMD_REG_RST 0x85 +#define ADS8688_CMD_REG_MAN_CH(chan) (0xC0 | (4 * chan)) +#define ADS8688_CMD_DONT_CARE_BITS 16 + +#define ADS8688_PROG_REG(x) (x << 9) +#define ADS8688_PROG_REG_RANGE_CH(chan) (0x05 + chan) +#define ADS8688_PROG_WR_BIT BIT(8) +#define ADS8688_PROG_DONT_CARE_BITS 8 + +#define ADS8688_REG_PLUSMINUS25VREF 0 +#define ADS8688_REG_PLUSMINUS125VREF 1 +#define ADS8688_REG_PLUSMINUS0625VREF 2 +#define ADS8688_REG_PLUS25VREF 5 +#define ADS8688_REG_PLUS125VREF 6 + +#define ADS8688_VREF_MV 4096 +#define ADS8688_REALBITS 16 +#define ADS8688_MAX_CHANNELS 8 + +/* + * enum ads8688_range - ADS8688 reference voltage range + * @ADS8688_PLUSMINUS25VREF: Device is configured for input range ±2.5 * VREF + * @ADS8688_PLUSMINUS125VREF: Device is configured for input range ±1.25 * VREF + * @ADS8688_PLUSMINUS0625VREF: Device is configured for input range ±0.625 * VREF + * @ADS8688_PLUS25VREF: Device is configured for input range 0 - 2.5 * VREF + * @ADS8688_PLUS125VREF: Device is configured for input range 0 - 1.25 * VREF + */ +enum ads8688_range { + ADS8688_PLUSMINUS25VREF, + ADS8688_PLUSMINUS125VREF, + ADS8688_PLUSMINUS0625VREF, + ADS8688_PLUS25VREF, + ADS8688_PLUS125VREF, +}; + +struct ads8688_chip_info { + const struct iio_chan_spec *channels; + unsigned int num_channels; +}; + +struct ads8688_state { + struct mutex lock; + const struct ads8688_chip_info *chip_info; + struct spi_device *spi; + unsigned int vref_mv; + int scale_avail[3][2]; + enum ads8688_range range[8]; + union { + __be32 d32; + u8 d8[4]; + } data[2] __aligned(IIO_DMA_MINALIGN); +}; + +enum ads8688_id { + ID_ADS8684, + ID_ADS8688, +}; + +struct ads8688_ranges { + enum ads8688_range range; + unsigned int scale; + int offset; + u8 reg; +}; + +static const struct ads8688_ranges ads8688_range_def[5] = { + { + .range = ADS8688_PLUSMINUS25VREF, + .scale = 76295, + .offset = -(1 << (ADS8688_REALBITS - 1)), + .reg = ADS8688_REG_PLUSMINUS25VREF, + }, { + .range = ADS8688_PLUSMINUS125VREF, + .scale = 38148, + .offset = -(1 << (ADS8688_REALBITS - 1)), + .reg = ADS8688_REG_PLUSMINUS125VREF, + }, { + .range = ADS8688_PLUSMINUS0625VREF, + .scale = 19074, + .offset = -(1 << (ADS8688_REALBITS - 1)), + .reg = ADS8688_REG_PLUSMINUS0625VREF, + }, { + .range = ADS8688_PLUS25VREF, + .scale = 38148, + .offset = 0, + .reg = ADS8688_REG_PLUS25VREF, + }, { + .range = ADS8688_PLUS125VREF, + .scale = 19074, + .offset = 0, + .reg = ADS8688_REG_PLUS125VREF, + } +}; + +static const int ads8688_offset_avail[] = { + -(1 << (ADS8688_REALBITS - 1)), + 0 +}; + +#define ADS8688_CHAN(index) \ +{ \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \ + | BIT(IIO_CHAN_INFO_SCALE) \ + | BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) \ + | BIT(IIO_CHAN_INFO_OFFSET), \ + .scan_index = index, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ +} + +static const struct iio_chan_spec ads8684_channels[] = { + ADS8688_CHAN(0), + ADS8688_CHAN(1), + ADS8688_CHAN(2), + ADS8688_CHAN(3), +}; + +static const struct iio_chan_spec ads8688_channels[] = { + ADS8688_CHAN(0), + ADS8688_CHAN(1), + ADS8688_CHAN(2), + ADS8688_CHAN(3), + ADS8688_CHAN(4), + ADS8688_CHAN(5), + ADS8688_CHAN(6), + ADS8688_CHAN(7), +}; + +static int ads8688_prog_write(struct iio_dev *indio_dev, unsigned int addr, + unsigned int val) +{ + struct ads8688_state *st = iio_priv(indio_dev); + u32 tmp; + + tmp = ADS8688_PROG_REG(addr) | ADS8688_PROG_WR_BIT | val; + tmp <<= ADS8688_PROG_DONT_CARE_BITS; + st->data[0].d32 = cpu_to_be32(tmp); + + return spi_write(st->spi, &st->data[0].d8[1], 3); +} + +static int ads8688_reset(struct iio_dev *indio_dev) +{ + struct ads8688_state *st = iio_priv(indio_dev); + u32 tmp; + + tmp = ADS8688_CMD_REG(ADS8688_CMD_REG_RST); + tmp <<= ADS8688_CMD_DONT_CARE_BITS; + st->data[0].d32 = cpu_to_be32(tmp); + + return spi_write(st->spi, &st->data[0].d8[0], 4); +} + +static int ads8688_read(struct iio_dev *indio_dev, unsigned int chan) +{ + struct ads8688_state *st = iio_priv(indio_dev); + int ret; + u32 tmp; + struct spi_transfer t[] = { + { + .tx_buf = &st->data[0].d8[0], + .len = 4, + .cs_change = 1, + }, { + .tx_buf = &st->data[1].d8[0], + .rx_buf = &st->data[1].d8[0], + .len = 4, + }, + }; + + tmp = ADS8688_CMD_REG(ADS8688_CMD_REG_MAN_CH(chan)); + tmp <<= ADS8688_CMD_DONT_CARE_BITS; + st->data[0].d32 = cpu_to_be32(tmp); + + tmp = ADS8688_CMD_REG(ADS8688_CMD_REG_NOOP); + tmp <<= ADS8688_CMD_DONT_CARE_BITS; + st->data[1].d32 = cpu_to_be32(tmp); + + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); + if (ret < 0) + return ret; + + return be32_to_cpu(st->data[1].d32) & 0xffff; +} + +static int ads8688_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long m) +{ + int ret, offset; + unsigned long scale_mv; + + struct ads8688_state *st = iio_priv(indio_dev); + + mutex_lock(&st->lock); + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = ads8688_read(indio_dev, chan->channel); + mutex_unlock(&st->lock); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + scale_mv = st->vref_mv; + scale_mv *= ads8688_range_def[st->range[chan->channel]].scale; + *val = 0; + *val2 = scale_mv; + mutex_unlock(&st->lock); + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + offset = ads8688_range_def[st->range[chan->channel]].offset; + *val = offset; + mutex_unlock(&st->lock); + return IIO_VAL_INT; + } + mutex_unlock(&st->lock); + + return -EINVAL; +} + +static int ads8688_write_reg_range(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + enum ads8688_range range) +{ + unsigned int tmp; + + tmp = ADS8688_PROG_REG_RANGE_CH(chan->channel); + + return ads8688_prog_write(indio_dev, tmp, range); +} + +static int ads8688_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ads8688_state *st = iio_priv(indio_dev); + unsigned int scale = 0; + int ret = -EINVAL, i, offset = 0; + + mutex_lock(&st->lock); + switch (mask) { + case IIO_CHAN_INFO_SCALE: + /* If the offset is 0 the ±2.5 * VREF mode is not available */ + offset = ads8688_range_def[st->range[chan->channel]].offset; + if (offset == 0 && val2 == ads8688_range_def[0].scale * st->vref_mv) { + mutex_unlock(&st->lock); + return -EINVAL; + } + + /* Lookup new mode */ + for (i = 0; i < ARRAY_SIZE(ads8688_range_def); i++) + if (val2 == ads8688_range_def[i].scale * st->vref_mv && + offset == ads8688_range_def[i].offset) { + ret = ads8688_write_reg_range(indio_dev, chan, + ads8688_range_def[i].reg); + break; + } + break; + case IIO_CHAN_INFO_OFFSET: + /* + * There are only two available offsets: + * 0 and -(1 << (ADS8688_REALBITS - 1)) + */ + if (!(ads8688_range_def[0].offset == val || + ads8688_range_def[3].offset == val)) { + mutex_unlock(&st->lock); + return -EINVAL; + } + + /* + * If the device are in ±2.5 * VREF mode, it's not allowed to + * switch to a mode where the offset is 0 + */ + if (val == 0 && + st->range[chan->channel] == ADS8688_PLUSMINUS25VREF) { + mutex_unlock(&st->lock); + return -EINVAL; + } + + scale = ads8688_range_def[st->range[chan->channel]].scale; + + /* Lookup new mode */ + for (i = 0; i < ARRAY_SIZE(ads8688_range_def); i++) + if (val == ads8688_range_def[i].offset && + scale == ads8688_range_def[i].scale) { + ret = ads8688_write_reg_range(indio_dev, chan, + ads8688_range_def[i].reg); + break; + } + break; + } + + if (!ret) + st->range[chan->channel] = ads8688_range_def[i].range; + + mutex_unlock(&st->lock); + + return ret; +} + +static int ads8688_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int ads8688_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct ads8688_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = (const int *)st->scale_avail; + *type = IIO_VAL_INT_PLUS_NANO; + *length = ARRAY_SIZE(st->scale_avail) * 2; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_OFFSET: + *vals = ads8688_offset_avail; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(ads8688_offset_avail); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const struct iio_info ads8688_info = { + .read_raw = &ads8688_read_raw, + .read_avail = &ads8688_read_avail, + .write_raw = &ads8688_write_raw, + .write_raw_get_fmt = &ads8688_write_raw_get_fmt, +}; + +static irqreturn_t ads8688_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + /* Ensure naturally aligned timestamp */ + u16 buffer[ADS8688_MAX_CHANNELS + sizeof(s64)/sizeof(u16)] __aligned(8) = { }; + int i, j = 0; + + iio_for_each_active_channel(indio_dev, i) { + buffer[j] = ads8688_read(indio_dev, i); + j++; + } + + iio_push_to_buffers_with_ts(indio_dev, buffer, sizeof(buffer), + iio_get_time_ns(indio_dev)); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct ads8688_chip_info ads8688_chip_info_tbl[] = { + [ID_ADS8684] = { + .channels = ads8684_channels, + .num_channels = ARRAY_SIZE(ads8684_channels), + }, + [ID_ADS8688] = { + .channels = ads8688_channels, + .num_channels = ARRAY_SIZE(ads8688_channels), + }, +}; + +static int ads8688_probe(struct spi_device *spi) +{ + struct ads8688_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + ret = devm_regulator_get_enable_read_voltage(&spi->dev, "vref"); + if (ret < 0 && ret != -ENODEV) + return ret; + + st->vref_mv = ret == -ENODEV ? ADS8688_VREF_MV : ret / 1000; + + for (unsigned int i = 0; i < ARRAY_SIZE(st->scale_avail); i++) { + st->scale_avail[i][0] = 0; + st->scale_avail[i][1] = ads8688_range_def[i].scale * st->vref_mv; + } + + st->chip_info = &ads8688_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + spi->mode = SPI_MODE_1; + + st->spi = spi; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + indio_dev->info = &ads8688_info; + + ads8688_reset(indio_dev); + + mutex_init(&st->lock); + + ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, NULL, + ads8688_trigger_handler, NULL); + if (ret < 0) + return dev_err_probe(&spi->dev, ret, + "iio triggered buffer setup failed\n"); + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id ads8688_id[] = { + { .name = "ads8684", .driver_data = ID_ADS8684 }, + { .name = "ads8688", .driver_data = ID_ADS8688 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ads8688_id); + +static const struct of_device_id ads8688_of_match[] = { + { .compatible = "ti,ads8684" }, + { .compatible = "ti,ads8688" }, + { } +}; +MODULE_DEVICE_TABLE(of, ads8688_of_match); + +static struct spi_driver ads8688_driver = { + .driver = { + .name = "ads8688", + .of_match_table = ads8688_of_match, + }, + .probe = ads8688_probe, + .id_table = ads8688_id, +}; +module_spi_driver(ads8688_driver); + +MODULE_AUTHOR("Sean Nyekjaer "); +MODULE_DESCRIPTION("Texas Instruments ADS8688 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ti-lmp92064.c b/drivers/iio/adc/ti-lmp92064.c new file mode 100644 index 000000000..d248bbea0 --- /dev/null +++ b/drivers/iio/adc/ti-lmp92064.c @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Texas Instruments LMP92064 SPI ADC driver + * + * Copyright (c) 2022 Leonard Göhrs , Pengutronix + * + * Based on linux/drivers/iio/adc/ti-tsc2046.c + * Copyright (c) 2021 Oleksij Rempel , Pengutronix + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define TI_LMP92064_REG_CONFIG_A 0x0000 +#define TI_LMP92064_REG_CONFIG_B 0x0001 +#define TI_LMP92064_REG_CHIP_REV 0x0006 + +#define TI_LMP92064_REG_MFR_ID1 0x000C +#define TI_LMP92064_REG_MFR_ID2 0x000D + +#define TI_LMP92064_REG_REG_UPDATE 0x000F +#define TI_LMP92064_REG_CONFIG_REG 0x0100 +#define TI_LMP92064_REG_STATUS 0x0103 + +#define TI_LMP92064_REG_DATA_VOUT_LSB 0x0200 +#define TI_LMP92064_REG_DATA_VOUT_MSB 0x0201 +#define TI_LMP92064_REG_DATA_COUT_LSB 0x0202 +#define TI_LMP92064_REG_DATA_COUT_MSB 0x0203 + +#define TI_LMP92064_VAL_CONFIG_A 0x99 +#define TI_LMP92064_VAL_CONFIG_B 0x00 +#define TI_LMP92064_VAL_STATUS_OK 0x01 + +/* + * Channel number definitions for the two channels of the device + * - IN Current (INC) + * - IN Voltage (INV) + */ +#define TI_LMP92064_CHAN_INC 0 +#define TI_LMP92064_CHAN_INV 1 + +static const struct regmap_range lmp92064_readable_reg_ranges[] = { + regmap_reg_range(TI_LMP92064_REG_CONFIG_A, TI_LMP92064_REG_CHIP_REV), + regmap_reg_range(TI_LMP92064_REG_MFR_ID1, TI_LMP92064_REG_MFR_ID2), + regmap_reg_range(TI_LMP92064_REG_REG_UPDATE, TI_LMP92064_REG_REG_UPDATE), + regmap_reg_range(TI_LMP92064_REG_CONFIG_REG, TI_LMP92064_REG_CONFIG_REG), + regmap_reg_range(TI_LMP92064_REG_STATUS, TI_LMP92064_REG_STATUS), + regmap_reg_range(TI_LMP92064_REG_DATA_VOUT_LSB, TI_LMP92064_REG_DATA_COUT_MSB), +}; + +static const struct regmap_access_table lmp92064_readable_regs = { + .yes_ranges = lmp92064_readable_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(lmp92064_readable_reg_ranges), +}; + +static const struct regmap_range lmp92064_writable_reg_ranges[] = { + regmap_reg_range(TI_LMP92064_REG_CONFIG_A, TI_LMP92064_REG_CONFIG_B), + regmap_reg_range(TI_LMP92064_REG_REG_UPDATE, TI_LMP92064_REG_REG_UPDATE), + regmap_reg_range(TI_LMP92064_REG_CONFIG_REG, TI_LMP92064_REG_CONFIG_REG), +}; + +static const struct regmap_access_table lmp92064_writable_regs = { + .yes_ranges = lmp92064_writable_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(lmp92064_writable_reg_ranges), +}; + +static const struct regmap_config lmp92064_spi_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = TI_LMP92064_REG_DATA_COUT_MSB, + .rd_table = &lmp92064_readable_regs, + .wr_table = &lmp92064_writable_regs, +}; + +struct lmp92064_adc_priv { + int shunt_resistor_uohm; + struct spi_device *spi; + struct regmap *regmap; +}; + +static const struct iio_chan_spec lmp92064_adc_channels[] = { + { + .type = IIO_CURRENT, + .address = TI_LMP92064_CHAN_INC, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .scan_index = TI_LMP92064_CHAN_INC, + .scan_type = { + .sign = 'u', + .realbits = 12, + .storagebits = 16, + }, + .datasheet_name = "INC", + }, + { + .type = IIO_VOLTAGE, + .address = TI_LMP92064_CHAN_INV, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .scan_index = TI_LMP92064_CHAN_INV, + .scan_type = { + .sign = 'u', + .realbits = 12, + .storagebits = 16, + }, + .datasheet_name = "INV", + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static const unsigned long lmp92064_scan_masks[] = { + BIT(TI_LMP92064_CHAN_INC) | BIT(TI_LMP92064_CHAN_INV), + 0 +}; + +static int lmp92064_read_meas(struct lmp92064_adc_priv *priv, u16 *res) +{ + __be16 raw[2]; + int ret; + + /* + * The ADC only latches in new samples if all DATA registers are read + * in descending sequential order. + * The ADC auto-decrements the register index with each clocked byte. + * Read both channels in single SPI transfer by selecting the highest + * register using the command below and clocking out all four data + * bytes. + */ + + ret = regmap_bulk_read(priv->regmap, TI_LMP92064_REG_DATA_COUT_MSB, + &raw, sizeof(raw)); + + if (ret) { + dev_err(&priv->spi->dev, "regmap_bulk_read failed: %pe\n", + ERR_PTR(ret)); + return ret; + } + + res[0] = be16_to_cpu(raw[0]); + res[1] = be16_to_cpu(raw[1]); + + return 0; +} + +static int lmp92064_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct lmp92064_adc_priv *priv = iio_priv(indio_dev); + u16 raw[2]; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = lmp92064_read_meas(priv, raw); + if (ret < 0) + return ret; + + *val = (chan->address == TI_LMP92064_CHAN_INC) ? raw[0] : raw[1]; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (chan->address == TI_LMP92064_CHAN_INC) { + /* + * processed (mA) = raw * current_lsb (mA) + * current_lsb (mA) = shunt_voltage_lsb (nV) / shunt_resistor (uOhm) + * shunt_voltage_lsb (nV) = 81920000 / 4096 = 20000 + */ + *val = 20000; + *val2 = priv->shunt_resistor_uohm; + } else { + /* + * processed (mV) = raw * voltage_lsb (mV) + * voltage_lsb (mV) = 2048 / 4096 + */ + *val = 2048; + *val2 = 4096; + } + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } +} + +static irqreturn_t lmp92064_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct lmp92064_adc_priv *priv = iio_priv(indio_dev); + struct { + u16 values[2]; + aligned_s64 timestamp; + } data = { }; + int ret; + + ret = lmp92064_read_meas(priv, data.values); + if (ret) + goto err; + + iio_push_to_buffers_with_ts(indio_dev, &data, sizeof(data), + iio_get_time_ns(indio_dev)); + +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int lmp92064_reset(struct lmp92064_adc_priv *priv, + struct gpio_desc *gpio_reset) +{ + unsigned int status; + int ret, i; + + if (gpio_reset) { + /* + * Perform a hard reset if gpio_reset is available. + * The datasheet specifies a very low 3.5ns reset pulse duration and does not + * specify how long to wait after a reset to access the device. + * Use more conservative pulse lengths to allow analog RC filtering of the + * reset line at the board level (as recommended in the datasheet). + */ + gpiod_set_value_cansleep(gpio_reset, 1); + usleep_range(1, 10); + gpiod_set_value_cansleep(gpio_reset, 0); + usleep_range(500, 750); + } else { + /* + * Perform a soft-reset if not. + * Also write default values to the config registers that are not + * affected by soft reset. + */ + ret = regmap_write(priv->regmap, TI_LMP92064_REG_CONFIG_A, + TI_LMP92064_VAL_CONFIG_A); + if (ret < 0) + return ret; + + ret = regmap_write(priv->regmap, TI_LMP92064_REG_CONFIG_B, + TI_LMP92064_VAL_CONFIG_B); + if (ret < 0) + return ret; + } + + /* + * Wait for the device to signal readiness to prevent reading bogus data + * and make sure device is actually connected. + * The datasheet does not specify how long this takes but usually it is + * not more than 3-4 iterations of this loop. + */ + for (i = 0; i < 10; i++) { + ret = regmap_read(priv->regmap, TI_LMP92064_REG_STATUS, &status); + if (ret < 0) + return ret; + + if (status == TI_LMP92064_VAL_STATUS_OK) + return 0; + + usleep_range(1000, 2000); + } + + /* + * No (correct) response received. + * Device is mostly likely not connected to the bus. + */ + return -ENXIO; +} + +static const struct iio_info lmp92064_adc_info = { + .read_raw = lmp92064_read_raw, +}; + +static int lmp92064_adc_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct lmp92064_adc_priv *priv; + struct gpio_desc *gpio_reset; + struct iio_dev *indio_dev; + u32 shunt_resistor_uohm; + struct regmap *regmap; + int ret; + + ret = spi_setup(spi); + if (ret < 0) + return dev_err_probe(dev, ret, "Error in SPI setup\n"); + + regmap = devm_regmap_init_spi(spi, &lmp92064_spi_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Failed to set up SPI regmap\n"); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + priv = iio_priv(indio_dev); + + priv->spi = spi; + priv->regmap = regmap; + + ret = device_property_read_u32(dev, "shunt-resistor-micro-ohms", + &shunt_resistor_uohm); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to get shunt-resistor value\n"); + + /* + * The shunt resistance is passed to userspace as the denominator of an iio + * fraction. Make sure it is in range for that. + */ + if (shunt_resistor_uohm == 0 || shunt_resistor_uohm > INT_MAX) { + dev_err(dev, "Shunt resistance is out of range\n"); + return -EINVAL; + } + + priv->shunt_resistor_uohm = shunt_resistor_uohm; + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return ret; + + ret = devm_regulator_get_enable(dev, "vdig"); + if (ret) + return ret; + + gpio_reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(gpio_reset)) + return dev_err_probe(dev, PTR_ERR(gpio_reset), + "Failed to get GPIO reset pin\n"); + + ret = lmp92064_reset(priv, gpio_reset); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to reset device\n"); + + indio_dev->name = "lmp92064"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = lmp92064_adc_channels; + indio_dev->num_channels = ARRAY_SIZE(lmp92064_adc_channels); + indio_dev->info = &lmp92064_adc_info; + indio_dev->available_scan_masks = lmp92064_scan_masks; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + lmp92064_trigger_handler, NULL); + if (ret) + return dev_err_probe(dev, ret, "Failed to setup buffered read\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id lmp92064_id_table[] = { + { .name = "lmp92064" }, + { } +}; +MODULE_DEVICE_TABLE(spi, lmp92064_id_table); + +static const struct of_device_id lmp92064_of_table[] = { + { .compatible = "ti,lmp92064" }, + { } +}; +MODULE_DEVICE_TABLE(of, lmp92064_of_table); + +static struct spi_driver lmp92064_adc_driver = { + .driver = { + .name = "lmp92064", + .of_match_table = lmp92064_of_table, + }, + .probe = lmp92064_adc_probe, + .id_table = lmp92064_id_table, +}; +module_spi_driver(lmp92064_adc_driver); + +MODULE_AUTHOR("Leonard Göhrs "); +MODULE_DESCRIPTION("TI LMP92064 ADC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/ti-tlc4541.c b/drivers/iio/adc/ti-tlc4541.c new file mode 100644 index 000000000..ea776f80d --- /dev/null +++ b/drivers/iio/adc/ti-tlc4541.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * TI tlc4541 ADC Driver + * + * Copyright (C) 2017 Phil Reid + * + * Datasheets can be found here: + * https://www.ti.com/lit/gpn/tlc3541 + * https://www.ti.com/lit/gpn/tlc4541 + * + * The tlc4541 requires 24 clock cycles to start a transfer. + * Conversion then takes 2.94us to complete before data is ready + * Data is returned MSB first. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct tlc4541_state { + struct spi_device *spi; + struct regulator *reg; + struct spi_transfer scan_single_xfer[3]; + struct spi_message scan_single_msg; + + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + * 2 bytes data + 6 bytes padding + 8 bytes timestamp when + * call iio_push_to_buffers_with_timestamp. + */ + __be16 rx_buf[8] __aligned(IIO_DMA_MINALIGN); +}; + +struct tlc4541_chip_info { + const struct iio_chan_spec *channels; + unsigned int num_channels; +}; + +enum tlc4541_id { + TLC3541, + TLC4541, +}; + +#define TLC4541_V_CHAN(bits, bitshift) { \ + .type = IIO_VOLTAGE, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = (bitshift), \ + .endianness = IIO_BE, \ + }, \ + } + +#define DECLARE_TLC4541_CHANNELS(name, bits, bitshift) \ +const struct iio_chan_spec name ## _channels[] = { \ + TLC4541_V_CHAN(bits, bitshift), \ + IIO_CHAN_SOFT_TIMESTAMP(1), \ +} + +static DECLARE_TLC4541_CHANNELS(tlc3541, 14, 2); +static DECLARE_TLC4541_CHANNELS(tlc4541, 16, 0); + +static const struct tlc4541_chip_info tlc4541_chip_info[] = { + [TLC3541] = { + .channels = tlc3541_channels, + .num_channels = ARRAY_SIZE(tlc3541_channels), + }, + [TLC4541] = { + .channels = tlc4541_channels, + .num_channels = ARRAY_SIZE(tlc4541_channels), + }, +}; + +static irqreturn_t tlc4541_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct tlc4541_state *st = iio_priv(indio_dev); + int ret; + + ret = spi_sync(st->spi, &st->scan_single_msg); + if (ret < 0) + goto done; + + iio_push_to_buffers_with_ts(indio_dev, st->rx_buf, sizeof(st->rx_buf), + iio_get_time_ns(indio_dev)); + +done: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int tlc4541_get_range(struct tlc4541_state *st) +{ + int vref; + + vref = regulator_get_voltage(st->reg); + if (vref < 0) + return vref; + + vref /= 1000; + + return vref; +} + +static int tlc4541_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret = 0; + struct tlc4541_state *st = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = spi_sync(st->spi, &st->scan_single_msg); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + *val = be16_to_cpu(st->rx_buf[0]); + *val = *val >> chan->scan_type.shift; + *val &= GENMASK(chan->scan_type.realbits - 1, 0); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = tlc4541_get_range(st); + if (ret < 0) + return ret; + *val = ret; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + +static const struct iio_info tlc4541_info = { + .read_raw = &tlc4541_read_raw, +}; + +static int tlc4541_probe(struct spi_device *spi) +{ + struct tlc4541_state *st; + struct iio_dev *indio_dev; + const struct tlc4541_chip_info *info; + int ret; + int8_t device_init = 0; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + spi_set_drvdata(spi, indio_dev); + + st->spi = spi; + + info = &tlc4541_chip_info[spi_get_device_id(spi)->driver_data]; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = info->channels; + indio_dev->num_channels = info->num_channels; + indio_dev->info = &tlc4541_info; + + /* perform reset */ + spi_write(spi, &device_init, 1); + + /* Setup default message */ + st->scan_single_xfer[0].rx_buf = &st->rx_buf[0]; + st->scan_single_xfer[0].len = 3; + st->scan_single_xfer[1].delay.value = 3; + st->scan_single_xfer[1].delay.unit = SPI_DELAY_UNIT_NSECS; + st->scan_single_xfer[2].rx_buf = &st->rx_buf[0]; + st->scan_single_xfer[2].len = 2; + + spi_message_init_with_transfers(&st->scan_single_msg, + st->scan_single_xfer, 3); + + st->reg = devm_regulator_get(&spi->dev, "vref"); + if (IS_ERR(st->reg)) + return PTR_ERR(st->reg); + + ret = regulator_enable(st->reg); + if (ret) + return ret; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + &tlc4541_trigger_handler, NULL); + if (ret) + goto error_disable_reg; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_cleanup_buffer; + + return 0; + +error_cleanup_buffer: + iio_triggered_buffer_cleanup(indio_dev); +error_disable_reg: + regulator_disable(st->reg); + + return ret; +} + +static void tlc4541_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct tlc4541_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + regulator_disable(st->reg); +} + +static const struct of_device_id tlc4541_dt_ids[] = { + { .compatible = "ti,tlc3541", }, + { .compatible = "ti,tlc4541", }, + { } +}; +MODULE_DEVICE_TABLE(of, tlc4541_dt_ids); + +static const struct spi_device_id tlc4541_id[] = { + { .name = "tlc3541", .driver_data = TLC3541 }, + { .name = "tlc4541", .driver_data = TLC4541 }, + { } +}; +MODULE_DEVICE_TABLE(spi, tlc4541_id); + +static struct spi_driver tlc4541_driver = { + .driver = { + .name = "tlc4541", + .of_match_table = tlc4541_dt_ids, + }, + .probe = tlc4541_probe, + .remove = tlc4541_remove, + .id_table = tlc4541_id, +}; +module_spi_driver(tlc4541_driver); + +MODULE_AUTHOR("Phil Reid "); +MODULE_DESCRIPTION("Texas Instruments TLC4541 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ti-tsc2046.c b/drivers/iio/adc/ti-tsc2046.c new file mode 100644 index 000000000..125f6d7e2 --- /dev/null +++ b/drivers/iio/adc/ti-tsc2046.c @@ -0,0 +1,838 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Texas Instruments TSC2046 SPI ADC driver + * + * Copyright (c) 2021 Oleksij Rempel , Pengutronix + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +/* + * The PENIRQ of TSC2046 controller is implemented as level shifter attached to + * the X+ line. If voltage of the X+ line reaches a specific level the IRQ will + * be activated or deactivated. + * To make this kind of IRQ reusable as trigger following additions were + * implemented: + * - rate limiting: + * For typical touchscreen use case, we need to trigger about each 10ms. + * - hrtimer: + * Continue triggering at least once after the IRQ was deactivated. Then + * deactivate this trigger to stop sampling in order to reduce power + * consumption. + */ + +#define TI_TSC2046_NAME "tsc2046" + +/* This driver doesn't aim at the peak continuous sample rate */ +#define TI_TSC2046_MAX_SAMPLE_RATE 125000 +#define TI_TSC2046_SAMPLE_BITS \ + BITS_PER_TYPE(struct tsc2046_adc_atom) +#define TI_TSC2046_MAX_CLK_FREQ \ + (TI_TSC2046_MAX_SAMPLE_RATE * TI_TSC2046_SAMPLE_BITS) + +#define TI_TSC2046_SAMPLE_INTERVAL_US 10000 + +#define TI_TSC2046_START BIT(7) +#define TI_TSC2046_ADDR GENMASK(6, 4) +#define TI_TSC2046_ADDR_TEMP1 7 +#define TI_TSC2046_ADDR_AUX 6 +#define TI_TSC2046_ADDR_X 5 +#define TI_TSC2046_ADDR_Z2 4 +#define TI_TSC2046_ADDR_Z1 3 +#define TI_TSC2046_ADDR_VBAT 2 +#define TI_TSC2046_ADDR_Y 1 +#define TI_TSC2046_ADDR_TEMP0 0 + +/* + * The mode bit sets the resolution of the ADC. With this bit low, the next + * conversion has 12-bit resolution, whereas with this bit high, the next + * conversion has 8-bit resolution. This driver is optimized for 12-bit mode. + * So, for this driver, this bit should stay zero. + */ +#define TI_TSC2046_8BIT_MODE BIT(3) + +/* + * SER/DFR - The SER/DFR bit controls the reference mode, either single-ended + * (high) or differential (low). + */ +#define TI_TSC2046_SER BIT(2) + +/* + * If VREF_ON and ADC_ON are both zero, then the chip operates in + * auto-wake/suspend mode. In most case this bits should stay zero. + */ +#define TI_TSC2046_PD1_VREF_ON BIT(1) +#define TI_TSC2046_PD0_ADC_ON BIT(0) + +/* + * All supported devices can do 8 or 12bit resolution. This driver + * supports only 12bit mode, here we have a 16bit data transfer, where + * the MSB and the 3 LSB are 0. + */ +#define TI_TSC2046_DATA_12BIT GENMASK(14, 3) + +#define TI_TSC2046_MAX_CHAN 8 +#define TI_TSC2046_MIN_POLL_CNT 3 +#define TI_TSC2046_EXT_POLL_CNT 3 +#define TI_TSC2046_POLL_CNT \ + (TI_TSC2046_MIN_POLL_CNT + TI_TSC2046_EXT_POLL_CNT) +#define TI_TSC2046_INT_VREF 2500 + +/* Represents a HW sample */ +struct tsc2046_adc_atom { + /* + * Command transmitted to the controller. This field is empty on the RX + * buffer. + */ + u8 cmd; + /* + * Data received from the controller. This field is empty for the TX + * buffer + */ + __be16 data; +} __packed; + +/* Layout of atomic buffers within big buffer */ +struct tsc2046_adc_group_layout { + /* Group offset within the SPI RX buffer */ + unsigned int offset; + /* + * Amount of tsc2046_adc_atom structs within the same command gathered + * within same group. + */ + unsigned int count; + /* + * Settling samples (tsc2046_adc_atom structs) which should be skipped + * before good samples will start. + */ + unsigned int skip; +}; + +struct tsc2046_adc_ch_cfg { + unsigned int settling_time_us; + unsigned int oversampling_ratio; +}; + +enum tsc2046_state { + TSC2046_STATE_SHUTDOWN, + TSC2046_STATE_STANDBY, + TSC2046_STATE_POLL, + TSC2046_STATE_POLL_IRQ_DISABLE, + TSC2046_STATE_ENABLE_IRQ, +}; + +struct tsc2046_adc_priv { + struct spi_device *spi; + bool internal_vref; + + struct iio_trigger *trig; + struct hrtimer trig_timer; + enum tsc2046_state state; + int poll_cnt; + spinlock_t state_lock; + + struct spi_transfer xfer; + struct spi_message msg; + + struct { + /* Scan data for each channel */ + u16 data[TI_TSC2046_MAX_CHAN]; + /* Timestamp */ + aligned_s64 ts; + } scan_buf; + + /* + * Lock to protect the layout and the SPI transfer buffer. + * tsc2046_adc_group_layout can be changed within update_scan_mode(), + * in this case the l[] and tx/rx buffer will be out of sync to each + * other. + */ + struct mutex slock; + struct tsc2046_adc_group_layout l[TI_TSC2046_MAX_CHAN]; + struct tsc2046_adc_atom *rx; + struct tsc2046_adc_atom *tx; + + unsigned int count; + unsigned int groups; + u32 effective_speed_hz; + u32 scan_interval_us; + u32 time_per_scan_us; + u32 time_per_bit_ns; + unsigned int vref_mv; + + struct tsc2046_adc_ch_cfg ch_cfg[TI_TSC2046_MAX_CHAN]; +}; + +#define TI_TSC2046_V_CHAN(index, bits, name) \ +{ \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = "#name", \ + .scan_index = index, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = bits, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ +} + +#define DECLARE_TI_TSC2046_8_CHANNELS(name, bits) \ +const struct iio_chan_spec name ## _channels[] = { \ + TI_TSC2046_V_CHAN(0, bits, TEMP0), \ + TI_TSC2046_V_CHAN(1, bits, Y), \ + TI_TSC2046_V_CHAN(2, bits, VBAT), \ + TI_TSC2046_V_CHAN(3, bits, Z1), \ + TI_TSC2046_V_CHAN(4, bits, Z2), \ + TI_TSC2046_V_CHAN(5, bits, X), \ + TI_TSC2046_V_CHAN(6, bits, AUX), \ + TI_TSC2046_V_CHAN(7, bits, TEMP1), \ + IIO_CHAN_SOFT_TIMESTAMP(8), \ +} + +static DECLARE_TI_TSC2046_8_CHANNELS(tsc2046_adc, 12); + +/* + * Convert time to a number of samples which can be transferred within this + * time. + */ +static unsigned int tsc2046_adc_time_to_count(struct tsc2046_adc_priv *priv, + unsigned long time) +{ + unsigned int bit_count, sample_count; + + bit_count = DIV_ROUND_UP(time * NSEC_PER_USEC, priv->time_per_bit_ns); + sample_count = DIV_ROUND_UP(bit_count, TI_TSC2046_SAMPLE_BITS); + + dev_dbg(&priv->spi->dev, "Effective speed %u, time per bit: %u, count bits: %u, count samples: %u\n", + priv->effective_speed_hz, priv->time_per_bit_ns, + bit_count, sample_count); + + return sample_count; +} + +static u8 tsc2046_adc_get_cmd(struct tsc2046_adc_priv *priv, int ch_idx, + bool keep_power) +{ + u32 pd; + + /* + * if PD bits are 0, controller will automatically disable ADC, VREF and + * enable IRQ. + */ + if (keep_power) + pd = TI_TSC2046_PD0_ADC_ON; + else + pd = 0; + + switch (ch_idx) { + case TI_TSC2046_ADDR_TEMP1: + case TI_TSC2046_ADDR_AUX: + case TI_TSC2046_ADDR_VBAT: + case TI_TSC2046_ADDR_TEMP0: + pd |= TI_TSC2046_SER; + if (priv->internal_vref) + pd |= TI_TSC2046_PD1_VREF_ON; + } + + return TI_TSC2046_START | FIELD_PREP(TI_TSC2046_ADDR, ch_idx) | pd; +} + +static u16 tsc2046_adc_get_value(struct tsc2046_adc_atom *buf) +{ + return FIELD_GET(TI_TSC2046_DATA_12BIT, get_unaligned_be16(&buf->data)); +} + +static int tsc2046_adc_read_one(struct tsc2046_adc_priv *priv, int ch_idx, + u32 *effective_speed_hz) +{ + struct tsc2046_adc_ch_cfg *ch = &priv->ch_cfg[ch_idx]; + unsigned int val, val_normalized = 0; + int ret, i, count_skip = 0, max_count; + struct spi_transfer xfer = { }; + struct spi_message msg; + u8 cmd; + + if (!effective_speed_hz) { + count_skip = tsc2046_adc_time_to_count(priv, ch->settling_time_us); + max_count = count_skip + ch->oversampling_ratio; + } else { + max_count = 1; + } + + if (sizeof(struct tsc2046_adc_atom) * max_count > PAGE_SIZE) + return -ENOSPC; + + struct tsc2046_adc_atom *tx_buf __free(kfree) = kzalloc_objs(*tx_buf, + max_count); + if (!tx_buf) + return -ENOMEM; + + struct tsc2046_adc_atom *rx_buf __free(kfree) = kzalloc_objs(*rx_buf, + max_count); + if (!rx_buf) + return -ENOMEM; + + /* + * Do not enable automatic power down on working samples. Otherwise the + * plates will never be completely charged. + */ + cmd = tsc2046_adc_get_cmd(priv, ch_idx, true); + + for (i = 0; i < max_count - 1; i++) + tx_buf[i].cmd = cmd; + + /* automatically power down on last sample */ + tx_buf[i].cmd = tsc2046_adc_get_cmd(priv, ch_idx, false); + + xfer.tx_buf = tx_buf; + xfer.rx_buf = rx_buf; + xfer.len = sizeof(*tx_buf) * max_count; + spi_message_init_with_transfers(&msg, &xfer, 1); + + /* + * We aren't using spi_write_then_read() because we need to be able + * to get hold of the effective_speed_hz from the xfer + */ + ret = spi_sync(priv->spi, &msg); + if (ret) { + dev_err_ratelimited(&priv->spi->dev, "SPI transfer failed %pe\n", + ERR_PTR(ret)); + return ret; + } + + if (effective_speed_hz) + *effective_speed_hz = xfer.effective_speed_hz; + + for (i = 0; i < max_count - count_skip; i++) { + val = tsc2046_adc_get_value(&rx_buf[count_skip + i]); + val_normalized += val; + } + + return DIV_ROUND_UP(val_normalized, max_count - count_skip); +} + +static size_t tsc2046_adc_group_set_layout(struct tsc2046_adc_priv *priv, + unsigned int group, + unsigned int ch_idx) +{ + struct tsc2046_adc_ch_cfg *ch = &priv->ch_cfg[ch_idx]; + struct tsc2046_adc_group_layout *cur; + unsigned int max_count, count_skip; + unsigned int offset = 0; + + if (group) + offset = priv->l[group - 1].offset + priv->l[group - 1].count; + + count_skip = tsc2046_adc_time_to_count(priv, ch->settling_time_us); + max_count = count_skip + ch->oversampling_ratio; + + cur = &priv->l[group]; + cur->offset = offset; + cur->count = max_count; + cur->skip = count_skip; + + return sizeof(*priv->tx) * max_count; +} + +static void tsc2046_adc_group_set_cmd(struct tsc2046_adc_priv *priv, + unsigned int group, int ch_idx) +{ + struct tsc2046_adc_group_layout *l = &priv->l[group]; + unsigned int i; + u8 cmd; + + /* + * Do not enable automatic power down on working samples. Otherwise the + * plates will never be completely charged. + */ + cmd = tsc2046_adc_get_cmd(priv, ch_idx, true); + + for (i = 0; i < l->count - 1; i++) + priv->tx[l->offset + i].cmd = cmd; + + /* automatically power down on last sample */ + priv->tx[l->offset + i].cmd = tsc2046_adc_get_cmd(priv, ch_idx, false); +} + +static u16 tsc2046_adc_get_val(struct tsc2046_adc_priv *priv, int group) +{ + struct tsc2046_adc_group_layout *l; + unsigned int val, val_normalized = 0; + int valid_count, i; + + l = &priv->l[group]; + valid_count = l->count - l->skip; + + for (i = 0; i < valid_count; i++) { + val = tsc2046_adc_get_value(&priv->rx[l->offset + l->skip + i]); + val_normalized += val; + } + + return DIV_ROUND_UP(val_normalized, valid_count); +} + +static int tsc2046_adc_scan(struct iio_dev *indio_dev) +{ + struct tsc2046_adc_priv *priv = iio_priv(indio_dev); + struct device *dev = &priv->spi->dev; + int group; + int ret; + + ret = spi_sync(priv->spi, &priv->msg); + if (ret < 0) { + dev_err_ratelimited(dev, "SPI transfer failed: %pe\n", ERR_PTR(ret)); + return ret; + } + + for (group = 0; group < priv->groups; group++) + priv->scan_buf.data[group] = tsc2046_adc_get_val(priv, group); + + ret = iio_push_to_buffers_with_ts(indio_dev, &priv->scan_buf, + sizeof(priv->scan_buf), + iio_get_time_ns(indio_dev)); + /* If the consumer is kfifo, we may get a EBUSY here - ignore it. */ + if (ret < 0 && ret != -EBUSY) { + dev_err_ratelimited(dev, "Failed to push scan buffer %pe\n", + ERR_PTR(ret)); + + return ret; + } + + return 0; +} + +static irqreturn_t tsc2046_adc_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct tsc2046_adc_priv *priv = iio_priv(indio_dev); + + mutex_lock(&priv->slock); + tsc2046_adc_scan(indio_dev); + mutex_unlock(&priv->slock); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int tsc2046_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long m) +{ + struct tsc2046_adc_priv *priv = iio_priv(indio_dev); + int ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = tsc2046_adc_read_one(priv, chan->channel, NULL); + if (ret < 0) + return ret; + + *val = ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* + * Note: the TSC2046 has internal voltage divider on the VBAT + * line. This divider can be influenced by external divider. + * So, it is better to use external voltage-divider driver + * instead, which is calculating complete chain. + */ + *val = priv->vref_mv; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + } + + return -EINVAL; +} + +static int tsc2046_adc_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *active_scan_mask) +{ + struct tsc2046_adc_priv *priv = iio_priv(indio_dev); + unsigned int ch_idx, group = 0; + size_t size; + + mutex_lock(&priv->slock); + + size = 0; + for_each_set_bit(ch_idx, active_scan_mask, ARRAY_SIZE(priv->l)) { + size += tsc2046_adc_group_set_layout(priv, group, ch_idx); + tsc2046_adc_group_set_cmd(priv, group, ch_idx); + group++; + } + + priv->groups = group; + priv->xfer.len = size; + priv->time_per_scan_us = size * 8 * priv->time_per_bit_ns / NSEC_PER_USEC; + + if (priv->scan_interval_us < priv->time_per_scan_us) + dev_warn(&priv->spi->dev, "The scan interval (%d) is less then calculated scan time (%d)\n", + priv->scan_interval_us, priv->time_per_scan_us); + + mutex_unlock(&priv->slock); + + return 0; +} + +static const struct iio_info tsc2046_adc_info = { + .read_raw = tsc2046_adc_read_raw, + .update_scan_mode = tsc2046_adc_update_scan_mode, +}; + +static enum hrtimer_restart tsc2046_adc_timer(struct hrtimer *hrtimer) +{ + struct tsc2046_adc_priv *priv = container_of(hrtimer, + struct tsc2046_adc_priv, + trig_timer); + unsigned long flags; + + /* + * This state machine should address following challenges : + * - the interrupt source is based on level shifter attached to the X + * channel of ADC. It will change the state every time we switch + * between channels. So, we need to disable IRQ if we do + * iio_trigger_poll(). + * - we should do iio_trigger_poll() at some reduced sample rate + * - we should still trigger for some amount of time after last + * interrupt with enabled IRQ was processed. + */ + + spin_lock_irqsave(&priv->state_lock, flags); + switch (priv->state) { + case TSC2046_STATE_ENABLE_IRQ: + if (priv->poll_cnt < TI_TSC2046_POLL_CNT) { + priv->poll_cnt++; + hrtimer_start(&priv->trig_timer, + us_to_ktime(priv->scan_interval_us), + HRTIMER_MODE_REL_SOFT); + + if (priv->poll_cnt >= TI_TSC2046_MIN_POLL_CNT) { + priv->state = TSC2046_STATE_POLL_IRQ_DISABLE; + enable_irq(priv->spi->irq); + } else { + priv->state = TSC2046_STATE_POLL; + } + } else { + priv->state = TSC2046_STATE_STANDBY; + enable_irq(priv->spi->irq); + } + break; + case TSC2046_STATE_POLL_IRQ_DISABLE: + disable_irq_nosync(priv->spi->irq); + fallthrough; + case TSC2046_STATE_POLL: + priv->state = TSC2046_STATE_ENABLE_IRQ; + /* iio_trigger_poll() starts hrtimer */ + iio_trigger_poll(priv->trig); + break; + case TSC2046_STATE_SHUTDOWN: + break; + case TSC2046_STATE_STANDBY: + fallthrough; + default: + dev_warn(&priv->spi->dev, "Got unexpected state: %i\n", + priv->state); + break; + } + spin_unlock_irqrestore(&priv->state_lock, flags); + + return HRTIMER_NORESTART; +} + +static irqreturn_t tsc2046_adc_irq(int irq, void *dev_id) +{ + struct iio_dev *indio_dev = dev_id; + struct tsc2046_adc_priv *priv = iio_priv(indio_dev); + unsigned long flags; + + hrtimer_try_to_cancel(&priv->trig_timer); + + spin_lock_irqsave(&priv->state_lock, flags); + if (priv->state != TSC2046_STATE_SHUTDOWN) { + priv->state = TSC2046_STATE_ENABLE_IRQ; + priv->poll_cnt = 0; + + /* iio_trigger_poll() starts hrtimer */ + disable_irq_nosync(priv->spi->irq); + iio_trigger_poll(priv->trig); + } + spin_unlock_irqrestore(&priv->state_lock, flags); + + return IRQ_HANDLED; +} + +static void tsc2046_adc_reenable_trigger(struct iio_trigger *trig) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct tsc2046_adc_priv *priv = iio_priv(indio_dev); + ktime_t tim; + + /* + * We can sample it as fast as we can, but usually we do not need so + * many samples. Reduce the sample rate for default (touchscreen) use + * case. + */ + tim = us_to_ktime(priv->scan_interval_us - priv->time_per_scan_us); + hrtimer_start(&priv->trig_timer, tim, HRTIMER_MODE_REL_SOFT); +} + +static int tsc2046_adc_set_trigger_state(struct iio_trigger *trig, bool enable) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct tsc2046_adc_priv *priv = iio_priv(indio_dev); + unsigned long flags; + + if (enable) { + spin_lock_irqsave(&priv->state_lock, flags); + if (priv->state == TSC2046_STATE_SHUTDOWN) { + priv->state = TSC2046_STATE_STANDBY; + enable_irq(priv->spi->irq); + } + spin_unlock_irqrestore(&priv->state_lock, flags); + } else { + spin_lock_irqsave(&priv->state_lock, flags); + + if (priv->state == TSC2046_STATE_STANDBY || + priv->state == TSC2046_STATE_POLL_IRQ_DISABLE) + disable_irq_nosync(priv->spi->irq); + + priv->state = TSC2046_STATE_SHUTDOWN; + spin_unlock_irqrestore(&priv->state_lock, flags); + + hrtimer_cancel(&priv->trig_timer); + } + + return 0; +} + +static const struct iio_trigger_ops tsc2046_adc_trigger_ops = { + .set_trigger_state = tsc2046_adc_set_trigger_state, + .reenable = tsc2046_adc_reenable_trigger, +}; + +static int tsc2046_adc_setup_spi_msg(struct tsc2046_adc_priv *priv) +{ + unsigned int ch_idx; + size_t size; + int ret; + + /* + * Make dummy read to set initial power state and get real SPI clock + * freq. It seems to be not important which channel is used for this + * case. + */ + ret = tsc2046_adc_read_one(priv, TI_TSC2046_ADDR_TEMP0, + &priv->effective_speed_hz); + if (ret < 0) + return ret; + + /* + * In case SPI controller do not report effective_speed_hz, use + * configure value and hope it will match. + */ + if (!priv->effective_speed_hz) + priv->effective_speed_hz = priv->spi->max_speed_hz; + + + priv->scan_interval_us = TI_TSC2046_SAMPLE_INTERVAL_US; + priv->time_per_bit_ns = DIV_ROUND_UP(NSEC_PER_SEC, + priv->effective_speed_hz); + + /* + * Calculate and allocate maximal size buffer if all channels are + * enabled. + */ + size = 0; + for (ch_idx = 0; ch_idx < ARRAY_SIZE(priv->l); ch_idx++) + size += tsc2046_adc_group_set_layout(priv, ch_idx, ch_idx); + + if (size > PAGE_SIZE) { + dev_err(&priv->spi->dev, + "Calculated scan buffer is too big. Try to reduce spi-max-frequency, settling-time-us or oversampling-ratio\n"); + return -ENOSPC; + } + + priv->tx = devm_kzalloc(&priv->spi->dev, size, GFP_KERNEL); + if (!priv->tx) + return -ENOMEM; + + priv->rx = devm_kzalloc(&priv->spi->dev, size, GFP_KERNEL); + if (!priv->rx) + return -ENOMEM; + + priv->xfer.tx_buf = priv->tx; + priv->xfer.rx_buf = priv->rx; + priv->xfer.len = size; + spi_message_init_with_transfers(&priv->msg, &priv->xfer, 1); + + return 0; +} + +static void tsc2046_adc_parse_fwnode(struct tsc2046_adc_priv *priv) +{ + struct fwnode_handle *child; + struct device *dev = &priv->spi->dev; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(priv->ch_cfg); i++) { + priv->ch_cfg[i].settling_time_us = 1; + priv->ch_cfg[i].oversampling_ratio = 1; + } + + device_for_each_child_node(dev, child) { + u32 stl, overs, reg; + int ret; + + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret) { + dev_err(dev, "invalid reg on %pfw, err: %pe\n", child, + ERR_PTR(ret)); + continue; + } + + if (reg >= ARRAY_SIZE(priv->ch_cfg)) { + dev_err(dev, "%pfw: Unsupported reg value: %i, max supported is: %zu.\n", + child, reg, ARRAY_SIZE(priv->ch_cfg)); + continue; + } + + ret = fwnode_property_read_u32(child, "settling-time-us", &stl); + if (!ret) + priv->ch_cfg[reg].settling_time_us = stl; + + ret = fwnode_property_read_u32(child, "oversampling-ratio", + &overs); + if (!ret) + priv->ch_cfg[reg].oversampling_ratio = overs; + } +} + +static int tsc2046_adc_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct tsc2046_adc_priv *priv; + struct iio_dev *indio_dev; + struct iio_trigger *trig; + int ret; + + if (spi->max_speed_hz > TI_TSC2046_MAX_CLK_FREQ) { + dev_err(dev, "SPI max_speed_hz is too high: %d Hz. Max supported freq is %zu Hz\n", + spi->max_speed_hz, TI_TSC2046_MAX_CLK_FREQ); + return -EINVAL; + } + + spi->mode &= ~SPI_MODE_X_MASK; + spi->mode |= SPI_MODE_0; + ret = spi_setup(spi); + if (ret < 0) + return dev_err_probe(dev, ret, "Error in SPI setup\n"); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + priv = iio_priv(indio_dev); + + priv->spi = spi; + + indio_dev->name = TI_TSC2046_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = tsc2046_adc_channels; + indio_dev->num_channels = ARRAY_SIZE(tsc2046_adc_channels); + indio_dev->info = &tsc2046_adc_info; + + ret = devm_regulator_get_enable_read_voltage(dev, "vref"); + if (ret < 0 && ret != -ENODEV) + return ret; + + priv->internal_vref = ret == -ENODEV; + priv->vref_mv = priv->internal_vref ? TI_TSC2046_INT_VREF : ret / MILLI; + + tsc2046_adc_parse_fwnode(priv); + + ret = tsc2046_adc_setup_spi_msg(priv); + if (ret) + return ret; + + mutex_init(&priv->slock); + + ret = devm_request_irq(dev, spi->irq, &tsc2046_adc_irq, + IRQF_NO_AUTOEN, indio_dev->name, indio_dev); + if (ret) + return ret; + + trig = devm_iio_trigger_alloc(dev, "touchscreen-%s", indio_dev->name); + if (!trig) + return -ENOMEM; + + priv->trig = trig; + iio_trigger_set_drvdata(trig, indio_dev); + trig->ops = &tsc2046_adc_trigger_ops; + + spin_lock_init(&priv->state_lock); + priv->state = TSC2046_STATE_SHUTDOWN; + hrtimer_setup(&priv->trig_timer, tsc2046_adc_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + + ret = devm_iio_trigger_register(dev, trig); + if (ret) { + dev_err(dev, "failed to register trigger\n"); + return ret; + } + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + &tsc2046_adc_trigger_handler, NULL); + if (ret) { + dev_err(dev, "Failed to setup triggered buffer\n"); + return ret; + } + + /* set default trigger */ + indio_dev->trig = iio_trigger_get(priv->trig); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ads7950_of_table[] = { + { .compatible = "ti,tsc2046e-adc" }, + { } +}; +MODULE_DEVICE_TABLE(of, ads7950_of_table); + +static const struct spi_device_id tsc2046_adc_spi_ids[] = { + { .name = "tsc2046e-adc" }, + { } +}; +MODULE_DEVICE_TABLE(spi, tsc2046_adc_spi_ids); + +static struct spi_driver tsc2046_adc_driver = { + .driver = { + .name = "tsc2046", + .of_match_table = ads7950_of_table, + }, + .id_table = tsc2046_adc_spi_ids, + .probe = tsc2046_adc_probe, +}; +module_spi_driver(tsc2046_adc_driver); + +MODULE_AUTHOR("Oleksij Rempel "); +MODULE_DESCRIPTION("TI TSC2046 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ti_am335x_adc.c b/drivers/iio/adc/ti_am335x_adc.c new file mode 100644 index 000000000..1516dd332 --- /dev/null +++ b/drivers/iio/adc/ti_am335x_adc.c @@ -0,0 +1,753 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * TI ADC MFD driver + * + * Copyright (C) 2012 Texas Instruments Incorporated - https://www.ti.com/ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#define DMA_BUFFER_SIZE SZ_2K + +struct tiadc_dma { + struct dma_slave_config conf; + struct dma_chan *chan; + dma_addr_t addr; + dma_cookie_t cookie; + u8 *buf; + int current_period; + int period_size; + u8 fifo_thresh; +}; + +struct tiadc_device { + struct ti_tscadc_dev *mfd_tscadc; + struct tiadc_dma dma; + struct mutex fifo1_lock; /* to protect fifo access */ + int channels; + int total_ch_enabled; + u8 channel_line[8]; + u8 channel_step[8]; + int buffer_en_ch_steps; + u16 data[8]; + u32 open_delay[8], sample_delay[8], step_avg[8]; +}; + +static unsigned int tiadc_readl(struct tiadc_device *adc, unsigned int reg) +{ + return readl(adc->mfd_tscadc->tscadc_base + reg); +} + +static void tiadc_writel(struct tiadc_device *adc, unsigned int reg, + unsigned int val) +{ + writel(val, adc->mfd_tscadc->tscadc_base + reg); +} + +static u32 get_adc_step_mask(struct tiadc_device *adc_dev) +{ + u32 step_en; + + step_en = ((1 << adc_dev->channels) - 1); + step_en <<= TOTAL_STEPS - adc_dev->channels + 1; + return step_en; +} + +static u32 get_adc_chan_step_mask(struct tiadc_device *adc_dev, + struct iio_chan_spec const *chan) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(adc_dev->channel_step); i++) { + if (chan->channel == adc_dev->channel_line[i]) { + u32 step; + + step = adc_dev->channel_step[i]; + /* +1 for the charger */ + return 1 << (step + 1); + } + } + WARN_ON(1); + return 0; +} + +static u32 get_adc_step_bit(struct tiadc_device *adc_dev, int chan) +{ + return 1 << adc_dev->channel_step[chan]; +} + +static int tiadc_wait_idle(struct tiadc_device *adc_dev) +{ + u32 val; + + return readl_poll_timeout(adc_dev->mfd_tscadc->tscadc_base + REG_ADCFSM, + val, !(val & SEQ_STATUS), 10, + IDLE_TIMEOUT_MS * 1000 * adc_dev->channels); +} + +static void tiadc_step_config(struct iio_dev *indio_dev) +{ + struct tiadc_device *adc_dev = iio_priv(indio_dev); + unsigned int stepconfig; + int i, steps = 0; + + /* + * There are 16 configurable steps and 8 analog input + * lines available which are shared between Touchscreen and ADC. + * + * Steps forward, i.e. from 0 towards 16, are used by ADC + * depending on the number of input lines needed. + * Channel would represent which analog input + * needs to be given to ADC to digitize data. + */ + for (i = 0; i < adc_dev->channels; i++) { + int chan; + + chan = adc_dev->channel_line[i]; + + if (adc_dev->step_avg[i] && adc_dev->step_avg[i] <= STEPCONFIG_AVG_16) + stepconfig = STEPCONFIG_AVG(ffs(adc_dev->step_avg[i]) - 1) | + STEPCONFIG_FIFO1; + else + stepconfig = STEPCONFIG_FIFO1; + + if (iio_buffer_enabled(indio_dev)) + stepconfig |= STEPCONFIG_MODE_SWCNT; + + tiadc_writel(adc_dev, REG_STEPCONFIG(steps), + stepconfig | STEPCONFIG_INP(chan) | + STEPCONFIG_INM_ADCREFM | STEPCONFIG_RFP_VREFP | + STEPCONFIG_RFM_VREFN); + + tiadc_writel(adc_dev, REG_STEPDELAY(steps), + STEPDELAY_OPEN(adc_dev->open_delay[i]) | + STEPDELAY_SAMPLE(adc_dev->sample_delay[i])); + + adc_dev->channel_step[i] = steps; + steps++; + } +} + +static irqreturn_t tiadc_irq_h(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct tiadc_device *adc_dev = iio_priv(indio_dev); + unsigned int status, config, adc_fsm; + unsigned short count = 0; + + status = tiadc_readl(adc_dev, REG_IRQSTATUS); + + /* + * ADC and touchscreen share the IRQ line. + * FIFO0 interrupts are used by TSC. Handle FIFO1 IRQs here only + */ + if (status & IRQENB_FIFO1OVRRUN) { + /* FIFO Overrun. Clear flag. Disable/Enable ADC to recover */ + config = tiadc_readl(adc_dev, REG_CTRL); + config &= ~(CNTRLREG_SSENB); + tiadc_writel(adc_dev, REG_CTRL, config); + tiadc_writel(adc_dev, REG_IRQSTATUS, + IRQENB_FIFO1OVRRUN | IRQENB_FIFO1UNDRFLW | + IRQENB_FIFO1THRES); + + /* + * Wait for the idle state. + * ADC needs to finish the current conversion + * before disabling the module + */ + do { + adc_fsm = tiadc_readl(adc_dev, REG_ADCFSM); + } while (adc_fsm != 0x10 && count++ < 100); + + tiadc_writel(adc_dev, REG_CTRL, (config | CNTRLREG_SSENB)); + return IRQ_HANDLED; + } else if (status & IRQENB_FIFO1THRES) { + /* Disable irq and wake worker thread */ + tiadc_writel(adc_dev, REG_IRQCLR, IRQENB_FIFO1THRES); + return IRQ_WAKE_THREAD; + } + + return IRQ_NONE; +} + +static irqreturn_t tiadc_worker_h(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct tiadc_device *adc_dev = iio_priv(indio_dev); + int i, k, fifo1count, read; + u16 *data = adc_dev->data; + + fifo1count = tiadc_readl(adc_dev, REG_FIFO1CNT); + for (k = 0; k < fifo1count; k = k + i) { + for (i = 0; i < indio_dev->scan_bytes / 2; i++) { + read = tiadc_readl(adc_dev, REG_FIFO1); + data[i] = read & FIFOREAD_DATA_MASK; + } + iio_push_to_buffers(indio_dev, (u8 *)data); + } + + tiadc_writel(adc_dev, REG_IRQSTATUS, IRQENB_FIFO1THRES); + tiadc_writel(adc_dev, REG_IRQENABLE, IRQENB_FIFO1THRES); + + return IRQ_HANDLED; +} + +static void tiadc_dma_rx_complete(void *param) +{ + struct iio_dev *indio_dev = param; + struct tiadc_device *adc_dev = iio_priv(indio_dev); + struct tiadc_dma *dma = &adc_dev->dma; + u8 *data; + int i; + + data = dma->buf + dma->current_period * dma->period_size; + dma->current_period = 1 - dma->current_period; /* swap the buffer ID */ + + for (i = 0; i < dma->period_size; i += indio_dev->scan_bytes) { + iio_push_to_buffers(indio_dev, data); + data += indio_dev->scan_bytes; + } +} + +static int tiadc_start_dma(struct iio_dev *indio_dev) +{ + struct tiadc_device *adc_dev = iio_priv(indio_dev); + struct tiadc_dma *dma = &adc_dev->dma; + struct dma_async_tx_descriptor *desc; + + dma->current_period = 0; /* We start to fill period 0 */ + + /* + * Make the fifo thresh as the multiple of total number of + * channels enabled, so make sure that cyclic DMA period + * length is also a multiple of total number of channels + * enabled. This ensures that no invalid data is reported + * to the stack via iio_push_to_buffers(). + */ + dma->fifo_thresh = rounddown(FIFO1_THRESHOLD + 1, + adc_dev->total_ch_enabled) - 1; + + /* Make sure that period length is multiple of fifo thresh level */ + dma->period_size = rounddown(DMA_BUFFER_SIZE / 2, + (dma->fifo_thresh + 1) * sizeof(u16)); + + dma->conf.src_maxburst = dma->fifo_thresh + 1; + dmaengine_slave_config(dma->chan, &dma->conf); + + desc = dmaengine_prep_dma_cyclic(dma->chan, dma->addr, + dma->period_size * 2, + dma->period_size, DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT); + if (!desc) + return -EBUSY; + + desc->callback = tiadc_dma_rx_complete; + desc->callback_param = indio_dev; + + dma->cookie = dmaengine_submit(desc); + + dma_async_issue_pending(dma->chan); + + tiadc_writel(adc_dev, REG_FIFO1THR, dma->fifo_thresh); + tiadc_writel(adc_dev, REG_DMA1REQ, dma->fifo_thresh); + tiadc_writel(adc_dev, REG_DMAENABLE_SET, DMA_FIFO1); + + return 0; +} + +static int tiadc_buffer_preenable(struct iio_dev *indio_dev) +{ + struct tiadc_device *adc_dev = iio_priv(indio_dev); + int i, fifo1count; + int ret; + + ret = tiadc_wait_idle(adc_dev); + if (ret) + return ret; + + tiadc_writel(adc_dev, REG_IRQCLR, + IRQENB_FIFO1THRES | IRQENB_FIFO1OVRRUN | + IRQENB_FIFO1UNDRFLW); + + /* Flush FIFO. Needed in corner cases in simultaneous tsc/adc use */ + fifo1count = tiadc_readl(adc_dev, REG_FIFO1CNT); + for (i = 0; i < fifo1count; i++) + tiadc_readl(adc_dev, REG_FIFO1); + + return 0; +} + +static int tiadc_buffer_postenable(struct iio_dev *indio_dev) +{ + struct tiadc_device *adc_dev = iio_priv(indio_dev); + struct tiadc_dma *dma = &adc_dev->dma; + unsigned int irq_enable; + unsigned int enb = 0; + u8 bit; + + tiadc_step_config(indio_dev); + for_each_set_bit(bit, indio_dev->active_scan_mask, adc_dev->channels) { + enb |= (get_adc_step_bit(adc_dev, bit) << 1); + adc_dev->total_ch_enabled++; + } + adc_dev->buffer_en_ch_steps = enb; + + if (dma->chan) + tiadc_start_dma(indio_dev); + + am335x_tsc_se_set_cache(adc_dev->mfd_tscadc, enb); + + tiadc_writel(adc_dev, REG_IRQSTATUS, + IRQENB_FIFO1THRES | IRQENB_FIFO1OVRRUN | + IRQENB_FIFO1UNDRFLW); + + irq_enable = IRQENB_FIFO1OVRRUN; + if (!dma->chan) + irq_enable |= IRQENB_FIFO1THRES; + tiadc_writel(adc_dev, REG_IRQENABLE, irq_enable); + + return 0; +} + +static int tiadc_buffer_predisable(struct iio_dev *indio_dev) +{ + struct tiadc_device *adc_dev = iio_priv(indio_dev); + struct tiadc_dma *dma = &adc_dev->dma; + int fifo1count, i; + + tiadc_writel(adc_dev, REG_IRQCLR, + IRQENB_FIFO1THRES | IRQENB_FIFO1OVRRUN | + IRQENB_FIFO1UNDRFLW); + am335x_tsc_se_clr(adc_dev->mfd_tscadc, adc_dev->buffer_en_ch_steps); + adc_dev->buffer_en_ch_steps = 0; + adc_dev->total_ch_enabled = 0; + if (dma->chan) { + tiadc_writel(adc_dev, REG_DMAENABLE_CLEAR, 0x2); + dmaengine_terminate_async(dma->chan); + } + + /* Flush FIFO of leftover data in the time it takes to disable adc */ + fifo1count = tiadc_readl(adc_dev, REG_FIFO1CNT); + for (i = 0; i < fifo1count; i++) + tiadc_readl(adc_dev, REG_FIFO1); + + return 0; +} + +static int tiadc_buffer_postdisable(struct iio_dev *indio_dev) +{ + tiadc_step_config(indio_dev); + + return 0; +} + +static const struct iio_buffer_setup_ops tiadc_buffer_setup_ops = { + .preenable = &tiadc_buffer_preenable, + .postenable = &tiadc_buffer_postenable, + .predisable = &tiadc_buffer_predisable, + .postdisable = &tiadc_buffer_postdisable, +}; + +static int tiadc_iio_buffered_hardware_setup(struct device *dev, + struct iio_dev *indio_dev, + irqreturn_t (*pollfunc_bh)(int irq, void *p), + irqreturn_t (*pollfunc_th)(int irq, void *p), + int irq, unsigned long flags, + const struct iio_buffer_setup_ops *setup_ops) +{ + int ret; + + ret = devm_iio_kfifo_buffer_setup(dev, indio_dev, setup_ops); + if (ret) + return ret; + + return devm_request_threaded_irq(dev, irq, pollfunc_th, pollfunc_bh, + flags, indio_dev->name, indio_dev); +} + +static const char * const chan_name_ain[] = { + "AIN0", + "AIN1", + "AIN2", + "AIN3", + "AIN4", + "AIN5", + "AIN6", + "AIN7", +}; + +static int tiadc_channel_init(struct device *dev, struct iio_dev *indio_dev, + int channels) +{ + struct tiadc_device *adc_dev = iio_priv(indio_dev); + struct iio_chan_spec *chan_array; + struct iio_chan_spec *chan; + int i; + + indio_dev->num_channels = channels; + chan_array = devm_kcalloc(dev, channels, sizeof(*chan_array), + GFP_KERNEL); + if (!chan_array) + return -ENOMEM; + + chan = chan_array; + for (i = 0; i < channels; i++, chan++) { + chan->type = IIO_VOLTAGE; + chan->indexed = 1; + chan->channel = adc_dev->channel_line[i]; + chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); + chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE); + chan->datasheet_name = chan_name_ain[chan->channel]; + chan->scan_index = i; + chan->scan_type.sign = 'u'; + chan->scan_type.realbits = 12; + chan->scan_type.storagebits = 16; + } + + indio_dev->channels = chan_array; + + return 0; +} + +static int tiadc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, + long mask) +{ + struct tiadc_device *adc_dev = iio_priv(indio_dev); + int i, map_val; + unsigned int fifo1count, read, stepid; + bool found = false; + u32 step_en; + unsigned long timeout; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + break; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + *val = 1800; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + + step_en = get_adc_chan_step_mask(adc_dev, chan); + if (!step_en) + return -EINVAL; + + mutex_lock(&adc_dev->fifo1_lock); + + ret = tiadc_wait_idle(adc_dev); + if (ret) + goto err_unlock; + + fifo1count = tiadc_readl(adc_dev, REG_FIFO1CNT); + while (fifo1count--) + tiadc_readl(adc_dev, REG_FIFO1); + + am335x_tsc_se_set_once(adc_dev->mfd_tscadc, step_en); + + /* Wait for Fifo threshold interrupt */ + timeout = jiffies + msecs_to_jiffies(IDLE_TIMEOUT_MS * adc_dev->channels); + while (1) { + fifo1count = tiadc_readl(adc_dev, REG_FIFO1CNT); + if (fifo1count) + break; + + if (time_after(jiffies, timeout)) { + am335x_tsc_se_adc_done(adc_dev->mfd_tscadc); + ret = -EAGAIN; + goto err_unlock; + } + } + + map_val = adc_dev->channel_step[chan->scan_index]; + + /* + * We check the complete FIFO. We programmed just one entry but in case + * something went wrong we left empty handed (-EAGAIN previously) and + * then the value appeared somehow in the FIFO we would have two entries. + * Therefore we read every item and keep only the latest version of the + * requested channel. + */ + for (i = 0; i < fifo1count; i++) { + read = tiadc_readl(adc_dev, REG_FIFO1); + stepid = read & FIFOREAD_CHNLID_MASK; + stepid = stepid >> 0x10; + + if (stepid == map_val) { + read = read & FIFOREAD_DATA_MASK; + found = true; + *val = (u16)read; + } + } + + am335x_tsc_se_adc_done(adc_dev->mfd_tscadc); + + if (!found) + ret = -EBUSY; + +err_unlock: + mutex_unlock(&adc_dev->fifo1_lock); + return ret ? ret : IIO_VAL_INT; +} + +static const struct iio_info tiadc_info = { + .read_raw = &tiadc_read_raw, +}; + +static int tiadc_request_dma(struct platform_device *pdev, + struct tiadc_device *adc_dev) +{ + struct tiadc_dma *dma = &adc_dev->dma; + dma_cap_mask_t mask; + + /* Default slave configuration parameters */ + dma->conf.direction = DMA_DEV_TO_MEM; + dma->conf.src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + dma->conf.src_addr = adc_dev->mfd_tscadc->tscadc_phys_base + REG_FIFO1; + + dma_cap_zero(mask); + dma_cap_set(DMA_CYCLIC, mask); + + /* Get a channel for RX */ + dma->chan = dma_request_chan(adc_dev->mfd_tscadc->dev, "fifo1"); + if (IS_ERR(dma->chan)) { + int ret = PTR_ERR(dma->chan); + + dma->chan = NULL; + return ret; + } + + /* RX buffer */ + dma->buf = dma_alloc_coherent(dma->chan->device->dev, DMA_BUFFER_SIZE, + &dma->addr, GFP_KERNEL); + if (!dma->buf) + goto err; + + return 0; + +err: + dma_release_channel(dma->chan); + return -ENOMEM; +} + +static int tiadc_parse_dt(struct platform_device *pdev, + struct tiadc_device *adc_dev) +{ + struct device_node *node = pdev->dev.of_node; + int channels = 0; + u32 val; + int i; + + of_property_for_each_u32(node, "ti,adc-channels", val) { + adc_dev->channel_line[channels] = val; + + /* Set Default values for optional DT parameters */ + adc_dev->open_delay[channels] = STEPCONFIG_OPENDLY; + adc_dev->sample_delay[channels] = STEPCONFIG_SAMPLEDLY; + adc_dev->step_avg[channels] = 16; + + channels++; + } + + adc_dev->channels = channels; + + of_property_read_u32_array(node, "ti,chan-step-avg", + adc_dev->step_avg, channels); + of_property_read_u32_array(node, "ti,chan-step-opendelay", + adc_dev->open_delay, channels); + of_property_read_u32_array(node, "ti,chan-step-sampledelay", + adc_dev->sample_delay, channels); + + for (i = 0; i < adc_dev->channels; i++) { + int chan; + + chan = adc_dev->channel_line[i]; + + if (adc_dev->step_avg[i] > STEPCONFIG_AVG_16) { + dev_warn(&pdev->dev, + "chan %d: wrong step avg, truncated to %ld\n", + chan, STEPCONFIG_AVG_16); + adc_dev->step_avg[i] = STEPCONFIG_AVG_16; + } + + if (adc_dev->open_delay[i] > STEPCONFIG_MAX_OPENDLY) { + dev_warn(&pdev->dev, + "chan %d: wrong open delay, truncated to 0x%lX\n", + chan, STEPCONFIG_MAX_OPENDLY); + adc_dev->open_delay[i] = STEPCONFIG_MAX_OPENDLY; + } + + if (adc_dev->sample_delay[i] > STEPCONFIG_MAX_SAMPLE) { + dev_warn(&pdev->dev, + "chan %d: wrong sample delay, truncated to 0x%lX\n", + chan, STEPCONFIG_MAX_SAMPLE); + adc_dev->sample_delay[i] = STEPCONFIG_MAX_SAMPLE; + } + } + + return 0; +} + +static int tiadc_probe(struct platform_device *pdev) +{ + struct iio_dev *indio_dev; + struct tiadc_device *adc_dev; + struct device_node *node = pdev->dev.of_node; + int err; + + if (!node) { + dev_err(&pdev->dev, "Could not find valid DT data.\n"); + return -EINVAL; + } + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc_dev)); + if (!indio_dev) + return -ENOMEM; + + adc_dev = iio_priv(indio_dev); + + adc_dev->mfd_tscadc = ti_tscadc_dev_get(pdev); + tiadc_parse_dt(pdev, adc_dev); + + indio_dev->name = dev_name(&pdev->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &tiadc_info; + + tiadc_step_config(indio_dev); + tiadc_writel(adc_dev, REG_FIFO1THR, FIFO1_THRESHOLD); + mutex_init(&adc_dev->fifo1_lock); + + err = tiadc_channel_init(&pdev->dev, indio_dev, adc_dev->channels); + if (err < 0) + return err; + + err = tiadc_iio_buffered_hardware_setup(&pdev->dev, indio_dev, + &tiadc_worker_h, + &tiadc_irq_h, + adc_dev->mfd_tscadc->irq, + IRQF_SHARED, + &tiadc_buffer_setup_ops); + if (err) + return err; + + err = iio_device_register(indio_dev); + if (err) + return err; + + platform_set_drvdata(pdev, indio_dev); + + err = tiadc_request_dma(pdev, adc_dev); + if (err && err != -ENODEV) { + dev_err_probe(&pdev->dev, err, "DMA request failed\n"); + goto err_dma; + } + + return 0; + +err_dma: + iio_device_unregister(indio_dev); + + return err; +} + +static void tiadc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct tiadc_device *adc_dev = iio_priv(indio_dev); + struct tiadc_dma *dma = &adc_dev->dma; + u32 step_en; + + if (dma->chan) { + dma_free_coherent(dma->chan->device->dev, DMA_BUFFER_SIZE, + dma->buf, dma->addr); + dma_release_channel(dma->chan); + } + iio_device_unregister(indio_dev); + + step_en = get_adc_step_mask(adc_dev); + am335x_tsc_se_clr(adc_dev->mfd_tscadc, step_en); +} + +static int tiadc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tiadc_device *adc_dev = iio_priv(indio_dev); + unsigned int idle; + + idle = tiadc_readl(adc_dev, REG_CTRL); + idle &= ~(CNTRLREG_SSENB); + tiadc_writel(adc_dev, REG_CTRL, idle | CNTRLREG_POWERDOWN); + + return 0; +} + +static int tiadc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tiadc_device *adc_dev = iio_priv(indio_dev); + unsigned int restore; + + /* Make sure ADC is powered up */ + restore = tiadc_readl(adc_dev, REG_CTRL); + restore &= ~CNTRLREG_POWERDOWN; + tiadc_writel(adc_dev, REG_CTRL, restore); + + tiadc_step_config(indio_dev); + am335x_tsc_se_set_cache(adc_dev->mfd_tscadc, + adc_dev->buffer_en_ch_steps); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(tiadc_pm_ops, tiadc_suspend, tiadc_resume); + +static const struct of_device_id ti_adc_dt_ids[] = { + { .compatible = "ti,am3359-adc", }, + { .compatible = "ti,am4372-adc", }, + { } +}; +MODULE_DEVICE_TABLE(of, ti_adc_dt_ids); + +static struct platform_driver tiadc_driver = { + .driver = { + .name = "TI-am335x-adc", + .pm = pm_sleep_ptr(&tiadc_pm_ops), + .of_match_table = ti_adc_dt_ids, + }, + .probe = tiadc_probe, + .remove = tiadc_remove, +}; +module_platform_driver(tiadc_driver); + +MODULE_DESCRIPTION("TI ADC controller driver"); +MODULE_AUTHOR("Rachna Patil "); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/twl4030-madc.c b/drivers/iio/adc/twl4030-madc.c new file mode 100644 index 000000000..0ee7e16b5 --- /dev/null +++ b/drivers/iio/adc/twl4030-madc.c @@ -0,0 +1,926 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * TWL4030 MADC module driver-This driver monitors the real time + * conversion of analog signals like battery temperature, + * battery type, battery level etc. + * + * Copyright (C) 2011 Texas Instruments Incorporated - https://www.ti.com/ + * J Keerthy + * + * Based on twl4030-madc.c + * Copyright (C) 2008 Nokia Corporation + * Mikko Ylinen + * + * Amit Kucheria + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define TWL4030_MADC_MAX_CHANNELS 16 + +#define TWL4030_MADC_CTRL1 0x00 +#define TWL4030_MADC_CTRL2 0x01 + +#define TWL4030_MADC_RTSELECT_LSB 0x02 +#define TWL4030_MADC_SW1SELECT_LSB 0x06 +#define TWL4030_MADC_SW2SELECT_LSB 0x0A + +#define TWL4030_MADC_RTAVERAGE_LSB 0x04 +#define TWL4030_MADC_SW1AVERAGE_LSB 0x08 +#define TWL4030_MADC_SW2AVERAGE_LSB 0x0C + +#define TWL4030_MADC_CTRL_SW1 0x12 +#define TWL4030_MADC_CTRL_SW2 0x13 + +#define TWL4030_MADC_RTCH0_LSB 0x17 +#define TWL4030_MADC_GPCH0_LSB 0x37 + +#define TWL4030_MADC_MADCON (1 << 0) /* MADC power on */ +#define TWL4030_MADC_BUSY (1 << 0) /* MADC busy */ +/* MADC conversion completion */ +#define TWL4030_MADC_EOC_SW (1 << 1) +/* MADC SWx start conversion */ +#define TWL4030_MADC_SW_START (1 << 5) +#define TWL4030_MADC_ADCIN0 (1 << 0) +#define TWL4030_MADC_ADCIN1 (1 << 1) +#define TWL4030_MADC_ADCIN2 (1 << 2) +#define TWL4030_MADC_ADCIN3 (1 << 3) +#define TWL4030_MADC_ADCIN4 (1 << 4) +#define TWL4030_MADC_ADCIN5 (1 << 5) +#define TWL4030_MADC_ADCIN6 (1 << 6) +#define TWL4030_MADC_ADCIN7 (1 << 7) +#define TWL4030_MADC_ADCIN8 (1 << 8) +#define TWL4030_MADC_ADCIN9 (1 << 9) +#define TWL4030_MADC_ADCIN10 (1 << 10) +#define TWL4030_MADC_ADCIN11 (1 << 11) +#define TWL4030_MADC_ADCIN12 (1 << 12) +#define TWL4030_MADC_ADCIN13 (1 << 13) +#define TWL4030_MADC_ADCIN14 (1 << 14) +#define TWL4030_MADC_ADCIN15 (1 << 15) + +/* Fixed channels */ +#define TWL4030_MADC_BTEMP TWL4030_MADC_ADCIN1 +#define TWL4030_MADC_VBUS TWL4030_MADC_ADCIN8 +#define TWL4030_MADC_VBKB TWL4030_MADC_ADCIN9 +#define TWL4030_MADC_ICHG TWL4030_MADC_ADCIN10 +#define TWL4030_MADC_VCHG TWL4030_MADC_ADCIN11 +#define TWL4030_MADC_VBAT TWL4030_MADC_ADCIN12 + +/* Step size and prescaler ratio */ +#define TEMP_STEP_SIZE 147 +#define TEMP_PSR_R 100 +#define CURR_STEP_SIZE 147 +#define CURR_PSR_R1 44 +#define CURR_PSR_R2 88 + +#define TWL4030_BCI_BCICTL1 0x23 +#define TWL4030_BCI_CGAIN 0x020 +#define TWL4030_BCI_MESBAT (1 << 1) +#define TWL4030_BCI_TYPEN (1 << 4) +#define TWL4030_BCI_ITHEN (1 << 3) + +#define REG_BCICTL2 0x024 +#define TWL4030_BCI_ITHSENS 0x007 + +/* Register and bits for GPBR1 register */ +#define TWL4030_REG_GPBR1 0x0c +#define TWL4030_GPBR1_MADC_HFCLK_EN (1 << 7) + +#define TWL4030_USB_SEL_MADC_MCPC (1<<3) +#define TWL4030_USB_CARKIT_ANA_CTRL 0xBB + +struct twl4030_madc_conversion_method { + u8 sel; + u8 avg; + u8 rbase; + u8 ctrl; +}; + +/** + * struct twl4030_madc_request - madc request packet for channel conversion + * @channels: 16 bit bitmap for individual channels + * @do_avg: sample the input channel for 4 consecutive cycles + * @method: RT, SW1, SW2 + * @type: Polling or interrupt based method + * @active: Flag if request is active + * @result_pending: Flag from irq handler, that result is ready + * @raw: Return raw value, do not convert it + * @rbuf: Result buffer + */ +struct twl4030_madc_request { + unsigned long channels; + bool do_avg; + u16 method; + u16 type; + bool active; + bool result_pending; + bool raw; + int rbuf[TWL4030_MADC_MAX_CHANNELS]; +}; + +enum conversion_methods { + TWL4030_MADC_RT, + TWL4030_MADC_SW1, + TWL4030_MADC_SW2, + TWL4030_MADC_NUM_METHODS +}; + +enum sample_type { + TWL4030_MADC_WAIT, + TWL4030_MADC_IRQ_ONESHOT, + TWL4030_MADC_IRQ_REARM +}; + +/** + * struct twl4030_madc_data - a container for madc info + * @dev: Pointer to device structure for madc + * @lock: Mutex protecting this data structure + * @usb3v1: Pointer to bias regulator for madc + * @requests: Array of request struct corresponding to SW1, SW2 and RT + * @use_second_irq: IRQ selection (main or co-processor) + * @imr: Interrupt mask register of MADC + * @isr: Interrupt status register of MADC + */ +struct twl4030_madc_data { + struct device *dev; + struct mutex lock; + struct regulator *usb3v1; + struct twl4030_madc_request requests[TWL4030_MADC_NUM_METHODS]; + bool use_second_irq; + u8 imr; + u8 isr; +}; + +static int twl4030_madc_conversion(struct twl4030_madc_request *req); + +static int twl4030_madc_read(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct twl4030_madc_data *madc = iio_priv(iio_dev); + struct twl4030_madc_request req; + int ret; + + req.method = madc->use_second_irq ? TWL4030_MADC_SW2 : TWL4030_MADC_SW1; + + req.channels = BIT(chan->channel); + req.active = false; + req.type = TWL4030_MADC_WAIT; + req.raw = !(mask == IIO_CHAN_INFO_PROCESSED); + req.do_avg = (mask == IIO_CHAN_INFO_AVERAGE_RAW); + + ret = twl4030_madc_conversion(&req); + if (ret < 0) + return ret; + + *val = req.rbuf[chan->channel]; + + return IIO_VAL_INT; +} + +static const struct iio_info twl4030_madc_iio_info = { + .read_raw = &twl4030_madc_read, +}; + +#define TWL4030_ADC_CHANNEL(_channel, _type, _name) { \ + .type = _type, \ + .channel = _channel, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_AVERAGE_RAW) | \ + BIT(IIO_CHAN_INFO_PROCESSED), \ + .datasheet_name = _name, \ + .indexed = 1, \ +} + +static const struct iio_chan_spec twl4030_madc_iio_channels[] = { + TWL4030_ADC_CHANNEL(0, IIO_VOLTAGE, "ADCIN0"), + TWL4030_ADC_CHANNEL(1, IIO_TEMP, "ADCIN1"), + TWL4030_ADC_CHANNEL(2, IIO_VOLTAGE, "ADCIN2"), + TWL4030_ADC_CHANNEL(3, IIO_VOLTAGE, "ADCIN3"), + TWL4030_ADC_CHANNEL(4, IIO_VOLTAGE, "ADCIN4"), + TWL4030_ADC_CHANNEL(5, IIO_VOLTAGE, "ADCIN5"), + TWL4030_ADC_CHANNEL(6, IIO_VOLTAGE, "ADCIN6"), + TWL4030_ADC_CHANNEL(7, IIO_VOLTAGE, "ADCIN7"), + TWL4030_ADC_CHANNEL(8, IIO_VOLTAGE, "ADCIN8"), + TWL4030_ADC_CHANNEL(9, IIO_VOLTAGE, "ADCIN9"), + TWL4030_ADC_CHANNEL(10, IIO_CURRENT, "ADCIN10"), + TWL4030_ADC_CHANNEL(11, IIO_VOLTAGE, "ADCIN11"), + TWL4030_ADC_CHANNEL(12, IIO_VOLTAGE, "ADCIN12"), + TWL4030_ADC_CHANNEL(13, IIO_VOLTAGE, "ADCIN13"), + TWL4030_ADC_CHANNEL(14, IIO_VOLTAGE, "ADCIN14"), + TWL4030_ADC_CHANNEL(15, IIO_VOLTAGE, "ADCIN15"), +}; + +static struct twl4030_madc_data *twl4030_madc; + +static const struct s16_fract twl4030_divider_ratios[16] = { + {1, 1}, /* CHANNEL 0 No Prescaler */ + {1, 1}, /* CHANNEL 1 No Prescaler */ + {6, 10}, /* CHANNEL 2 */ + {6, 10}, /* CHANNEL 3 */ + {6, 10}, /* CHANNEL 4 */ + {6, 10}, /* CHANNEL 5 */ + {6, 10}, /* CHANNEL 6 */ + {6, 10}, /* CHANNEL 7 */ + {3, 14}, /* CHANNEL 8 */ + {1, 3}, /* CHANNEL 9 */ + {1, 1}, /* CHANNEL 10 No Prescaler */ + {15, 100}, /* CHANNEL 11 */ + {1, 4}, /* CHANNEL 12 */ + {1, 1}, /* CHANNEL 13 Reserved channels */ + {1, 1}, /* CHANNEL 14 Reserved channels */ + {5, 11}, /* CHANNEL 15 */ +}; + +/* Conversion table from -3 to 55 degrees Celsius */ +static int twl4030_therm_tbl[] = { + 30800, 29500, 28300, 27100, + 26000, 24900, 23900, 22900, 22000, 21100, 20300, 19400, 18700, + 17900, 17200, 16500, 15900, 15300, 14700, 14100, 13600, 13100, + 12600, 12100, 11600, 11200, 10800, 10400, 10000, 9630, 9280, + 8950, 8620, 8310, 8020, 7730, 7460, 7200, 6950, 6710, + 6470, 6250, 6040, 5830, 5640, 5450, 5260, 5090, 4920, + 4760, 4600, 4450, 4310, 4170, 4040, 3910, 3790, 3670, + 3550 +}; + +/* + * Structure containing the registers + * of different conversion methods supported by MADC. + * Hardware or RT real time conversion request initiated by external host + * processor for RT Signal conversions. + * External host processors can also request for non RT conversions + * SW1 and SW2 software conversions also called asynchronous or GPC request. + */ +static +const struct twl4030_madc_conversion_method twl4030_conversion_methods[] = { + [TWL4030_MADC_RT] = { + .sel = TWL4030_MADC_RTSELECT_LSB, + .avg = TWL4030_MADC_RTAVERAGE_LSB, + .rbase = TWL4030_MADC_RTCH0_LSB, + }, + [TWL4030_MADC_SW1] = { + .sel = TWL4030_MADC_SW1SELECT_LSB, + .avg = TWL4030_MADC_SW1AVERAGE_LSB, + .rbase = TWL4030_MADC_GPCH0_LSB, + .ctrl = TWL4030_MADC_CTRL_SW1, + }, + [TWL4030_MADC_SW2] = { + .sel = TWL4030_MADC_SW2SELECT_LSB, + .avg = TWL4030_MADC_SW2AVERAGE_LSB, + .rbase = TWL4030_MADC_GPCH0_LSB, + .ctrl = TWL4030_MADC_CTRL_SW2, + }, +}; + +/** + * twl4030_madc_channel_raw_read() - Function to read a particular channel value + * @madc: pointer to struct twl4030_madc_data + * @reg: lsb of ADC Channel + * + * Return: 0 on success, an error code otherwise. + */ +static int twl4030_madc_channel_raw_read(struct twl4030_madc_data *madc, u8 reg) +{ + u16 val; + int ret; + /* + * For each ADC channel, we have MSB and LSB register pair. MSB address + * is always LSB address+1. reg parameter is the address of LSB register + */ + ret = twl_i2c_read_u16(TWL4030_MODULE_MADC, &val, reg); + if (ret) { + dev_err(madc->dev, "unable to read register 0x%X\n", reg); + return ret; + } + + return (int)(val >> 6); +} + +/* + * Return battery temperature in degrees Celsius + * Or < 0 on failure. + */ +static int twl4030battery_temperature(int raw_volt) +{ + u8 val; + int temp, curr, volt, res, ret; + + volt = (raw_volt * TEMP_STEP_SIZE) / TEMP_PSR_R; + /* Getting and calculating the supply current in micro amperes */ + ret = twl_i2c_read_u8(TWL_MODULE_MAIN_CHARGE, &val, + REG_BCICTL2); + if (ret < 0) + return ret; + + curr = ((val & TWL4030_BCI_ITHSENS) + 1) * 10; + /* Getting and calculating the thermistor resistance in ohms */ + res = volt * 1000 / curr; + /* calculating temperature */ + for (temp = 58; temp >= 0; temp--) { + int actual = twl4030_therm_tbl[temp]; + if ((actual - res) >= 0) + break; + } + + return temp + 1; +} + +static int twl4030battery_current(int raw_volt) +{ + int ret; + u8 val; + + ret = twl_i2c_read_u8(TWL_MODULE_MAIN_CHARGE, &val, + TWL4030_BCI_BCICTL1); + if (ret) + return ret; + if (val & TWL4030_BCI_CGAIN) /* slope of 0.44 mV/mA */ + return (raw_volt * CURR_STEP_SIZE) / CURR_PSR_R1; + else /* slope of 0.88 mV/mA */ + return (raw_volt * CURR_STEP_SIZE) / CURR_PSR_R2; +} + +/* + * Function to read channel values + * @madc - pointer to twl4030_madc_data struct + * @reg_base - Base address of the first channel + * @Channels - 16 bit bitmap. If the bit is set, channel's value is read + * @buf - The channel values are stored here. if read fails error + * @raw - Return raw values without conversion + * value is stored + * Returns the number of successfully read channels. + */ +static int twl4030_madc_read_channels(struct twl4030_madc_data *madc, + u8 reg_base, unsigned + long channels, int *buf, + bool raw) +{ + int count = 0; + int i; + u8 reg; + + for_each_set_bit(i, &channels, TWL4030_MADC_MAX_CHANNELS) { + reg = reg_base + (2 * i); + buf[i] = twl4030_madc_channel_raw_read(madc, reg); + if (buf[i] < 0) { + dev_err(madc->dev, "Unable to read register 0x%X\n", + reg); + return buf[i]; + } + if (raw) { + count++; + continue; + } + switch (i) { + case 10: + buf[i] = twl4030battery_current(buf[i]); + if (buf[i] < 0) { + dev_err(madc->dev, "err reading current\n"); + return buf[i]; + } else { + count++; + buf[i] = buf[i] - 750; + } + break; + case 1: + buf[i] = twl4030battery_temperature(buf[i]); + if (buf[i] < 0) { + dev_err(madc->dev, "err reading temperature\n"); + return buf[i]; + } else { + buf[i] -= 3; + count++; + } + break; + default: + count++; + /* Analog Input (V) = conv_result * step_size / R + * conv_result = decimal value of 10-bit conversion + * result + * step size = 1.5 / (2 ^ 10 -1) + * R = Prescaler ratio for input channels. + * Result given in mV hence multiplied by 1000. + */ + buf[i] = (buf[i] * 3 * 1000 * + twl4030_divider_ratios[i].denominator) + / (2 * 1023 * + twl4030_divider_ratios[i].numerator); + } + } + + return count; +} + +/* + * Disables irq. + * @madc - pointer to twl4030_madc_data struct + * @id - irq number to be disabled + * can take one of TWL4030_MADC_RT, TWL4030_MADC_SW1, TWL4030_MADC_SW2 + * corresponding to RT, SW1, SW2 conversion requests. + * Returns error if i2c read/write fails. + */ +static int twl4030_madc_disable_irq(struct twl4030_madc_data *madc, u8 id) +{ + u8 val; + int ret; + + ret = twl_i2c_read_u8(TWL4030_MODULE_MADC, &val, madc->imr); + if (ret) { + dev_err(madc->dev, "unable to read imr register 0x%X\n", + madc->imr); + return ret; + } + val |= (1 << id); + ret = twl_i2c_write_u8(TWL4030_MODULE_MADC, val, madc->imr); + if (ret) { + dev_err(madc->dev, + "unable to write imr register 0x%X\n", madc->imr); + return ret; + } + + return 0; +} + +static irqreturn_t twl4030_madc_threaded_irq_handler(int irq, void *_madc) +{ + struct twl4030_madc_data *madc = _madc; + const struct twl4030_madc_conversion_method *method; + u8 isr_val, imr_val; + int i, ret; + struct twl4030_madc_request *r; + + mutex_lock(&madc->lock); + ret = twl_i2c_read_u8(TWL4030_MODULE_MADC, &isr_val, madc->isr); + if (ret) { + dev_err(madc->dev, "unable to read isr register 0x%X\n", + madc->isr); + goto err_i2c; + } + ret = twl_i2c_read_u8(TWL4030_MODULE_MADC, &imr_val, madc->imr); + if (ret) { + dev_err(madc->dev, "unable to read imr register 0x%X\n", + madc->imr); + goto err_i2c; + } + isr_val &= ~imr_val; + for (i = 0; i < TWL4030_MADC_NUM_METHODS; i++) { + if (!(isr_val & (1 << i))) + continue; + ret = twl4030_madc_disable_irq(madc, i); + if (ret < 0) + dev_dbg(madc->dev, "Disable interrupt failed %d\n", i); + madc->requests[i].result_pending = true; + } + for (i = 0; i < TWL4030_MADC_NUM_METHODS; i++) { + r = &madc->requests[i]; + /* No pending results for this method, move to next one */ + if (!r->result_pending) + continue; + method = &twl4030_conversion_methods[r->method]; + /* Read results */ + twl4030_madc_read_channels(madc, method->rbase, + r->channels, r->rbuf, r->raw); + /* Free request */ + r->result_pending = false; + r->active = false; + } + mutex_unlock(&madc->lock); + + return IRQ_HANDLED; + +err_i2c: + /* + * In case of error check whichever request is active + * and service the same. + */ + for (i = 0; i < TWL4030_MADC_NUM_METHODS; i++) { + r = &madc->requests[i]; + if (!r->active) + continue; + method = &twl4030_conversion_methods[r->method]; + /* Read results */ + twl4030_madc_read_channels(madc, method->rbase, + r->channels, r->rbuf, r->raw); + /* Free request */ + r->result_pending = false; + r->active = false; + } + mutex_unlock(&madc->lock); + + return IRQ_HANDLED; +} + +/* + * Function which enables the madc conversion + * by writing to the control register. + * @madc - pointer to twl4030_madc_data struct + * @conv_method - can be TWL4030_MADC_RT, TWL4030_MADC_SW2, TWL4030_MADC_SW1 + * corresponding to RT SW1 or SW2 conversion methods. + * Returns 0 if succeeds else a negative error value + */ +static int twl4030_madc_start_conversion(struct twl4030_madc_data *madc, + int conv_method) +{ + const struct twl4030_madc_conversion_method *method; + int ret = 0; + + if (conv_method != TWL4030_MADC_SW1 && conv_method != TWL4030_MADC_SW2) + return -ENOTSUPP; + + method = &twl4030_conversion_methods[conv_method]; + ret = twl_i2c_write_u8(TWL4030_MODULE_MADC, TWL4030_MADC_SW_START, + method->ctrl); + if (ret) { + dev_err(madc->dev, "unable to write ctrl register 0x%X\n", + method->ctrl); + return ret; + } + + return 0; +} + +/* + * Function that waits for conversion to be ready + * @madc - pointer to twl4030_madc_data struct + * @timeout_ms - timeout value in milliseconds + * @status_reg - ctrl register + * returns 0 if succeeds else a negative error value + */ +static int twl4030_madc_wait_conversion_ready(struct twl4030_madc_data *madc, + unsigned int timeout_ms, + u8 status_reg) +{ + unsigned long timeout; + int ret; + + timeout = jiffies + msecs_to_jiffies(timeout_ms); + do { + u8 reg; + + ret = twl_i2c_read_u8(TWL4030_MODULE_MADC, ®, status_reg); + if (ret) { + dev_err(madc->dev, + "unable to read status register 0x%X\n", + status_reg); + return ret; + } + if (!(reg & TWL4030_MADC_BUSY) && (reg & TWL4030_MADC_EOC_SW)) + return 0; + usleep_range(500, 2000); + } while (!time_after(jiffies, timeout)); + dev_err(madc->dev, "conversion timeout!\n"); + + return -EAGAIN; +} + +/* + * An exported function which can be called from other kernel drivers. + * @req twl4030_madc_request structure + * req->rbuf will be filled with read values of channels based on the + * channel index. If a particular channel reading fails there will + * be a negative error value in the corresponding array element. + * returns 0 if succeeds else error value + */ +static int twl4030_madc_conversion(struct twl4030_madc_request *req) +{ + const struct twl4030_madc_conversion_method *method; + int ret; + + if (!req || !twl4030_madc) + return -EINVAL; + + mutex_lock(&twl4030_madc->lock); + if (req->method < TWL4030_MADC_RT || req->method > TWL4030_MADC_SW2) { + ret = -EINVAL; + goto out; + } + /* Do we have a conversion request ongoing */ + if (twl4030_madc->requests[req->method].active) { + ret = -EBUSY; + goto out; + } + method = &twl4030_conversion_methods[req->method]; + /* Select channels to be converted */ + ret = twl_i2c_write_u16(TWL4030_MODULE_MADC, req->channels, method->sel); + if (ret) { + dev_err(twl4030_madc->dev, + "unable to write sel register 0x%X\n", method->sel); + goto out; + } + /* Select averaging for all channels if do_avg is set */ + if (req->do_avg) { + ret = twl_i2c_write_u16(TWL4030_MODULE_MADC, req->channels, + method->avg); + if (ret) { + dev_err(twl4030_madc->dev, + "unable to write avg register 0x%X\n", + method->avg); + goto out; + } + } + /* With RT method we should not be here anymore */ + if (req->method == TWL4030_MADC_RT) { + ret = -EINVAL; + goto out; + } + ret = twl4030_madc_start_conversion(twl4030_madc, req->method); + if (ret < 0) + goto out; + twl4030_madc->requests[req->method].active = true; + /* Wait until conversion is ready (ctrl register returns EOC) */ + ret = twl4030_madc_wait_conversion_ready(twl4030_madc, 5, method->ctrl); + if (ret) { + twl4030_madc->requests[req->method].active = false; + goto out; + } + ret = twl4030_madc_read_channels(twl4030_madc, method->rbase, + req->channels, req->rbuf, req->raw); + twl4030_madc->requests[req->method].active = false; + +out: + mutex_unlock(&twl4030_madc->lock); + + return ret; +} + +/** + * twl4030_madc_set_current_generator() - setup bias current + * + * @madc: pointer to twl4030_madc_data struct + * @chan: can be one of the two values: + * 0 - Enables bias current for main battery type reading + * 1 - Enables bias current for main battery temperature sensing + * @on: enable or disable chan. + * + * Function to enable or disable bias current for + * main battery type reading or temperature sensing + */ +static int twl4030_madc_set_current_generator(struct twl4030_madc_data *madc, + int chan, int on) +{ + int ret; + int regmask; + u8 regval; + + ret = twl_i2c_read_u8(TWL_MODULE_MAIN_CHARGE, + ®val, TWL4030_BCI_BCICTL1); + if (ret) { + dev_err(madc->dev, "unable to read BCICTL1 reg 0x%X", + TWL4030_BCI_BCICTL1); + return ret; + } + + regmask = chan ? TWL4030_BCI_ITHEN : TWL4030_BCI_TYPEN; + if (on) + regval |= regmask; + else + regval &= ~regmask; + + ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, + regval, TWL4030_BCI_BCICTL1); + if (ret) { + dev_err(madc->dev, "unable to write BCICTL1 reg 0x%X\n", + TWL4030_BCI_BCICTL1); + return ret; + } + + return 0; +} + +/* + * Function that sets MADC software power on bit to enable MADC + * @madc - pointer to twl4030_madc_data struct + * @on - Enable or disable MADC software power on bit. + * returns error if i2c read/write fails else 0 + */ +static int twl4030_madc_set_power(struct twl4030_madc_data *madc, int on) +{ + u8 regval; + int ret; + + ret = twl_i2c_read_u8(TWL_MODULE_MAIN_CHARGE, + ®val, TWL4030_MADC_CTRL1); + if (ret) { + dev_err(madc->dev, "unable to read madc ctrl1 reg 0x%X\n", + TWL4030_MADC_CTRL1); + return ret; + } + if (on) + regval |= TWL4030_MADC_MADCON; + else + regval &= ~TWL4030_MADC_MADCON; + ret = twl_i2c_write_u8(TWL4030_MODULE_MADC, regval, TWL4030_MADC_CTRL1); + if (ret) { + dev_err(madc->dev, "unable to write madc ctrl1 reg 0x%X\n", + TWL4030_MADC_CTRL1); + return ret; + } + + return 0; +} + +/* + * Initialize MADC and request for threaded irq + */ +static int twl4030_madc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct twl4030_madc_platform_data *pdata = dev_get_platdata(dev); + struct twl4030_madc_data *madc; + int irq, ret; + u8 regval; + struct iio_dev *iio_dev = NULL; + + if (!pdata && !dev_fwnode(dev)) { + dev_err(&pdev->dev, "neither platform data nor Device Tree node available\n"); + return -EINVAL; + } + + iio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*madc)); + if (!iio_dev) + return -ENOMEM; + + madc = iio_priv(iio_dev); + madc->dev = &pdev->dev; + + iio_dev->name = dev_name(&pdev->dev); + iio_dev->info = &twl4030_madc_iio_info; + iio_dev->modes = INDIO_DIRECT_MODE; + iio_dev->channels = twl4030_madc_iio_channels; + iio_dev->num_channels = ARRAY_SIZE(twl4030_madc_iio_channels); + + /* + * Phoenix provides 2 interrupt lines. The first one is connected to + * the OMAP. The other one can be connected to the other processor such + * as modem. Hence two separate ISR and IMR registers. + */ + if (pdata) + madc->use_second_irq = (pdata->irq_line != 1); + else + madc->use_second_irq = device_property_read_bool(dev, + "ti,system-uses-second-madc-irq"); + + madc->imr = madc->use_second_irq ? TWL4030_MADC_IMR2 : + TWL4030_MADC_IMR1; + madc->isr = madc->use_second_irq ? TWL4030_MADC_ISR2 : + TWL4030_MADC_ISR1; + + ret = twl4030_madc_set_power(madc, 1); + if (ret < 0) + return ret; + ret = twl4030_madc_set_current_generator(madc, 0, 1); + if (ret < 0) + goto err_current_generator; + + ret = twl_i2c_read_u8(TWL_MODULE_MAIN_CHARGE, + ®val, TWL4030_BCI_BCICTL1); + if (ret) { + dev_err(&pdev->dev, "unable to read reg BCI CTL1 0x%X\n", + TWL4030_BCI_BCICTL1); + goto err_i2c; + } + regval |= TWL4030_BCI_MESBAT; + ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, + regval, TWL4030_BCI_BCICTL1); + if (ret) { + dev_err(&pdev->dev, "unable to write reg BCI Ctl1 0x%X\n", + TWL4030_BCI_BCICTL1); + goto err_i2c; + } + + /* Check that MADC clock is on */ + ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, ®val, TWL4030_REG_GPBR1); + if (ret) { + dev_err(&pdev->dev, "unable to read reg GPBR1 0x%X\n", + TWL4030_REG_GPBR1); + goto err_i2c; + } + + /* If MADC clk is not on, turn it on */ + if (!(regval & TWL4030_GPBR1_MADC_HFCLK_EN)) { + dev_info(&pdev->dev, "clk disabled, enabling\n"); + regval |= TWL4030_GPBR1_MADC_HFCLK_EN; + ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, regval, + TWL4030_REG_GPBR1); + if (ret) { + dev_err(&pdev->dev, "unable to write reg GPBR1 0x%X\n", + TWL4030_REG_GPBR1); + goto err_i2c; + } + } + + platform_set_drvdata(pdev, iio_dev); + mutex_init(&madc->lock); + + irq = platform_get_irq(pdev, 0); + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + twl4030_madc_threaded_irq_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "twl4030_madc", madc); + if (ret) { + dev_err(&pdev->dev, "could not request irq\n"); + goto err_i2c; + } + twl4030_madc = madc; + + /* Configure MADC[3:6] */ + ret = twl_i2c_read_u8(TWL_MODULE_USB, ®val, + TWL4030_USB_CARKIT_ANA_CTRL); + if (ret) { + dev_err(&pdev->dev, "unable to read reg CARKIT_ANA_CTRL 0x%X\n", + TWL4030_USB_CARKIT_ANA_CTRL); + goto err_i2c; + } + regval |= TWL4030_USB_SEL_MADC_MCPC; + ret = twl_i2c_write_u8(TWL_MODULE_USB, regval, + TWL4030_USB_CARKIT_ANA_CTRL); + if (ret) { + dev_err(&pdev->dev, "unable to write reg CARKIT_ANA_CTRL 0x%X\n", + TWL4030_USB_CARKIT_ANA_CTRL); + goto err_i2c; + } + + /* Enable 3v1 bias regulator for MADC[3:6] */ + madc->usb3v1 = devm_regulator_get(madc->dev, "vusb3v1"); + if (IS_ERR(madc->usb3v1)) { + ret = -ENODEV; + goto err_i2c; + } + + ret = regulator_enable(madc->usb3v1); + if (ret) { + dev_err(madc->dev, "could not enable 3v1 bias regulator\n"); + goto err_i2c; + } + + ret = iio_device_register(iio_dev); + if (ret) { + dev_err(&pdev->dev, "could not register iio device\n"); + goto err_usb3v1; + } + + return 0; + +err_usb3v1: + regulator_disable(madc->usb3v1); +err_i2c: + twl4030_madc_set_current_generator(madc, 0, 0); +err_current_generator: + twl4030_madc_set_power(madc, 0); + return ret; +} + +static void twl4030_madc_remove(struct platform_device *pdev) +{ + struct iio_dev *iio_dev = platform_get_drvdata(pdev); + struct twl4030_madc_data *madc = iio_priv(iio_dev); + + iio_device_unregister(iio_dev); + + twl4030_madc_set_current_generator(madc, 0, 0); + twl4030_madc_set_power(madc, 0); + + regulator_disable(madc->usb3v1); +} + +static const struct of_device_id twl_madc_of_match[] = { + { .compatible = "ti,twl4030-madc", }, + { } +}; +MODULE_DEVICE_TABLE(of, twl_madc_of_match); + +static struct platform_driver twl4030_madc_driver = { + .probe = twl4030_madc_probe, + .remove = twl4030_madc_remove, + .driver = { + .name = "twl4030_madc", + .of_match_table = twl_madc_of_match, + }, +}; + +module_platform_driver(twl4030_madc_driver); + +MODULE_DESCRIPTION("TWL4030 ADC driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("J Keerthy"); +MODULE_ALIAS("platform:twl4030_madc"); diff --git a/drivers/iio/adc/twl6030-gpadc.c b/drivers/iio/adc/twl6030-gpadc.c new file mode 100644 index 000000000..31b1c01ba --- /dev/null +++ b/drivers/iio/adc/twl6030-gpadc.c @@ -0,0 +1,1020 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * TWL6030 GPADC module driver + * + * Copyright (C) 2009-2013 Texas Instruments Inc. + * Nishant Kamat + * Balaji T K + * Graeme Gregory + * Girish S Ghongdemath + * Ambresh K + * Oleksandr Kozaruk + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "twl6030_gpadc" + +/* + * twl6030 per TRM has 17 channels, and twl6032 has 19 channels + * 2 test network channels are not used, + * 2 die temperature channels are not used either, as it is not + * defined how to convert ADC value to temperature + */ +#define TWL6030_GPADC_USED_CHANNELS 13 +#define TWL6030_GPADC_MAX_CHANNELS 15 +#define TWL6032_GPADC_USED_CHANNELS 15 +#define TWL6032_GPADC_MAX_CHANNELS 19 +#define TWL6030_GPADC_NUM_TRIM_REGS 16 + +#define TWL6030_GPADC_CTRL_P1 0x05 + +#define TWL6032_GPADC_GPSELECT_ISB 0x07 +#define TWL6032_GPADC_CTRL_P1 0x08 + +#define TWL6032_GPADC_GPCH0_LSB 0x0d +#define TWL6032_GPADC_GPCH0_MSB 0x0e + +#define TWL6030_GPADC_CTRL_P1_SP1 BIT(3) + +#define TWL6030_GPADC_GPCH0_LSB (0x29) + +#define TWL6030_GPADC_RT_SW1_EOC_MASK BIT(5) + +#define TWL6030_GPADC_TRIM1 0xCD + +#define TWL6030_REG_TOGGLE1 0x90 +#define TWL6030_GPADCS BIT(1) +#define TWL6030_GPADCR BIT(0) + +#define USB_VBUS_CTRL_SET 0x04 +#define USB_ID_CTRL_SET 0x06 + +#define TWL6030_MISC1 0xE4 +#define VBUS_MEAS 0x01 +#define ID_MEAS 0x01 + +#define VAC_MEAS 0x04 +#define VBAT_MEAS 0x02 +#define BB_MEAS 0x01 + + +/** + * struct twl6030_chnl_calib - channel calibration + * @gain: slope coefficient for ideal curve + * @gain_error: gain error + * @offset_error: offset of the real curve + */ +struct twl6030_chnl_calib { + s32 gain; + s32 gain_error; + s32 offset_error; +}; + +/** + * struct twl6030_ideal_code - GPADC calibration parameters + * GPADC is calibrated in two points: close to the beginning and + * to the and of the measurable input range + * + * @channel: channel number + * @code1: ideal code for the input at the beginning + * @code2: ideal code for at the end of the range + * @volt1: voltage input at the beginning(low voltage) + * @volt2: voltage input at the end(high voltage) + */ +struct twl6030_ideal_code { + int channel; + u16 code1; + u16 code2; + u16 volt1; + u16 volt2; +}; + +struct twl6030_gpadc_data; + +/** + * struct twl6030_gpadc_platform_data - platform specific data + * @nchannels: number of GPADC channels + * @iio_channels: iio channels + * @ideal: pointer to calibration parameters + * @start_conversion: pointer to ADC start conversion function + * @channel_to_reg: pointer to ADC function to convert channel to + * register address for reading conversion result + * @calibrate: pointer to calibration function + */ +struct twl6030_gpadc_platform_data { + const int nchannels; + const struct iio_chan_spec *iio_channels; + const struct twl6030_ideal_code *ideal; + int (*start_conversion)(int channel); + u8 (*channel_to_reg)(int channel); + int (*calibrate)(struct twl6030_gpadc_data *gpadc); +}; + +/** + * struct twl6030_gpadc_data - GPADC data + * @dev: device pointer + * @lock: mutual exclusion lock for the structure + * @irq_complete: completion to signal end of conversion + * @twl6030_cal_tbl: pointer to calibration data for each + * channel with gain error and offset + * @pdata: pointer to device specific data + */ +struct twl6030_gpadc_data { + struct device *dev; + struct mutex lock; + struct completion irq_complete; + struct twl6030_chnl_calib *twl6030_cal_tbl; + const struct twl6030_gpadc_platform_data *pdata; +}; + +/* + * channels 11, 12, 13, 15 and 16 have no calibration data + * calibration offset is same for channels 1, 3, 4, 5 + * + * The data is taken from GPADC_TRIM registers description. + * GPADC_TRIM registers keep difference between the code measured + * at volt1 and volt2 input voltages and corresponding code1 and code2 + */ +static const struct twl6030_ideal_code + twl6030_ideal[TWL6030_GPADC_USED_CHANNELS] = { + [0] = { /* ch 0, external, battery type, resistor value */ + .channel = 0, + .code1 = 116, + .code2 = 745, + .volt1 = 141, + .volt2 = 910, + }, + [1] = { /* ch 1, external, battery temperature, NTC resistor value */ + .channel = 1, + .code1 = 82, + .code2 = 900, + .volt1 = 100, + .volt2 = 1100, + }, + [2] = { /* ch 2, external, audio accessory/general purpose */ + .channel = 2, + .code1 = 55, + .code2 = 818, + .volt1 = 101, + .volt2 = 1499, + }, + [3] = { /* ch 3, external, general purpose */ + .channel = 3, + .code1 = 82, + .code2 = 900, + .volt1 = 100, + .volt2 = 1100, + }, + [4] = { /* ch 4, external, temperature measurement/general purpose */ + .channel = 4, + .code1 = 82, + .code2 = 900, + .volt1 = 100, + .volt2 = 1100, + }, + [5] = { /* ch 5, external, general purpose */ + .channel = 5, + .code1 = 82, + .code2 = 900, + .volt1 = 100, + .volt2 = 1100, + }, + [6] = { /* ch 6, external, general purpose */ + .channel = 6, + .code1 = 82, + .code2 = 900, + .volt1 = 100, + .volt2 = 1100, + }, + [7] = { /* ch 7, internal, main battery */ + .channel = 7, + .code1 = 614, + .code2 = 941, + .volt1 = 3001, + .volt2 = 4599, + }, + [8] = { /* ch 8, internal, backup battery */ + .channel = 8, + .code1 = 82, + .code2 = 688, + .volt1 = 501, + .volt2 = 4203, + }, + [9] = { /* ch 9, internal, external charger input */ + .channel = 9, + .code1 = 182, + .code2 = 818, + .volt1 = 2001, + .volt2 = 8996, + }, + [10] = { /* ch 10, internal, VBUS */ + .channel = 10, + .code1 = 149, + .code2 = 818, + .volt1 = 1001, + .volt2 = 5497, + }, + [11] = { /* ch 11, internal, VBUS charging current */ + .channel = 11, + }, + /* ch 12, internal, Die temperature */ + /* ch 13, internal, Die temperature */ + [12] = { /* ch 14, internal, USB ID line */ + .channel = 14, + .code1 = 48, + .code2 = 714, + .volt1 = 323, + .volt2 = 4800, + }, +}; + +static const struct twl6030_ideal_code + twl6032_ideal[TWL6032_GPADC_USED_CHANNELS] = { + [0] = { /* ch 0, external, battery type, resistor value */ + .channel = 0, + .code1 = 1441, + .code2 = 3276, + .volt1 = 440, + .volt2 = 1000, + }, + [1] = { /* ch 1, external, battery temperature, NTC resistor value */ + .channel = 1, + .code1 = 1441, + .code2 = 3276, + .volt1 = 440, + .volt2 = 1000, + }, + [2] = { /* ch 2, external, audio accessory/general purpose */ + .channel = 2, + .code1 = 1441, + .code2 = 3276, + .volt1 = 660, + .volt2 = 1500, + }, + [3] = { /* ch 3, external, temperature with external diode/general + purpose */ + .channel = 3, + .code1 = 1441, + .code2 = 3276, + .volt1 = 440, + .volt2 = 1000, + }, + [4] = { /* ch 4, external, temperature measurement/general purpose */ + .channel = 4, + .code1 = 1441, + .code2 = 3276, + .volt1 = 440, + .volt2 = 1000, + }, + [5] = { /* ch 5, external, general purpose */ + .channel = 5, + .code1 = 1441, + .code2 = 3276, + .volt1 = 440, + .volt2 = 1000, + }, + [6] = { /* ch 6, external, general purpose */ + .channel = 6, + .code1 = 1441, + .code2 = 3276, + .volt1 = 440, + .volt2 = 1000, + }, + [7] = { /* ch7, internal, system supply */ + .channel = 7, + .code1 = 1441, + .code2 = 3276, + .volt1 = 2200, + .volt2 = 5000, + }, + [8] = { /* ch8, internal, backup battery */ + .channel = 8, + .code1 = 1441, + .code2 = 3276, + .volt1 = 2200, + .volt2 = 5000, + }, + [9] = { /* ch 9, internal, external charger input */ + .channel = 9, + .code1 = 1441, + .code2 = 3276, + .volt1 = 3960, + .volt2 = 9000, + }, + [10] = { /* ch10, internal, VBUS */ + .channel = 10, + .code1 = 150, + .code2 = 751, + .volt1 = 1000, + .volt2 = 5000, + }, + [11] = { /* ch 11, internal, VBUS DC-DC output current */ + .channel = 11, + .code1 = 1441, + .code2 = 3276, + .volt1 = 660, + .volt2 = 1500, + }, + /* ch 12, internal, Die temperature */ + /* ch 13, internal, Die temperature */ + [12] = { /* ch 14, internal, USB ID line */ + .channel = 14, + .code1 = 1441, + .code2 = 3276, + .volt1 = 2420, + .volt2 = 5500, + }, + /* ch 15, internal, test network */ + /* ch 16, internal, test network */ + [13] = { /* ch 17, internal, battery charging current */ + .channel = 17, + }, + [14] = { /* ch 18, internal, battery voltage */ + .channel = 18, + .code1 = 1441, + .code2 = 3276, + .volt1 = 2200, + .volt2 = 5000, + }, +}; + +static inline int twl6030_gpadc_write(u8 reg, u8 val) +{ + return twl_i2c_write_u8(TWL6030_MODULE_GPADC, val, reg); +} + +static inline int twl6030_gpadc_read(u8 reg, u8 *val) +{ + + return twl_i2c_read(TWL6030_MODULE_GPADC, val, reg, 2); +} + +static int twl6030_gpadc_enable_irq(u8 mask) +{ + int ret; + + ret = twl6030_interrupt_unmask(mask, REG_INT_MSK_LINE_B); + if (ret < 0) + return ret; + + ret = twl6030_interrupt_unmask(mask, REG_INT_MSK_STS_B); + + return ret; +} + +static void twl6030_gpadc_disable_irq(u8 mask) +{ + twl6030_interrupt_mask(mask, REG_INT_MSK_LINE_B); + twl6030_interrupt_mask(mask, REG_INT_MSK_STS_B); +} + +static irqreturn_t twl6030_gpadc_irq_handler(int irq, void *indio_dev) +{ + struct twl6030_gpadc_data *gpadc = iio_priv(indio_dev); + + complete(&gpadc->irq_complete); + + return IRQ_HANDLED; +} + +static int twl6030_start_conversion(int channel) +{ + return twl6030_gpadc_write(TWL6030_GPADC_CTRL_P1, + TWL6030_GPADC_CTRL_P1_SP1); +} + +static int twl6032_start_conversion(int channel) +{ + int ret; + + ret = twl6030_gpadc_write(TWL6032_GPADC_GPSELECT_ISB, channel); + if (ret) + return ret; + + return twl6030_gpadc_write(TWL6032_GPADC_CTRL_P1, + TWL6030_GPADC_CTRL_P1_SP1); +} + +static u8 twl6030_channel_to_reg(int channel) +{ + return TWL6030_GPADC_GPCH0_LSB + 2 * channel; +} + +static u8 twl6032_channel_to_reg(int channel) +{ + /* + * for any prior chosen channel, when the conversion is ready + * the result is available in GPCH0_LSB, GPCH0_MSB. + */ + + return TWL6032_GPADC_GPCH0_LSB; +} + +static int twl6030_gpadc_lookup(const struct twl6030_ideal_code *ideal, + int channel, int size) +{ + int i; + + for (i = 0; i < size; i++) + if (ideal[i].channel == channel) + break; + + return i; +} + +static int twl6030_channel_calibrated(const struct twl6030_gpadc_platform_data + *pdata, int channel) +{ + const struct twl6030_ideal_code *ideal = pdata->ideal; + int i; + + i = twl6030_gpadc_lookup(ideal, channel, pdata->nchannels); + /* not calibrated channels have 0 in all structure members */ + return pdata->ideal[i].code2; +} + +static int twl6030_gpadc_make_correction(struct twl6030_gpadc_data *gpadc, + int channel, int raw_code) +{ + const struct twl6030_ideal_code *ideal = gpadc->pdata->ideal; + int corrected_code; + int i; + + i = twl6030_gpadc_lookup(ideal, channel, gpadc->pdata->nchannels); + corrected_code = ((raw_code * 1000) - + gpadc->twl6030_cal_tbl[i].offset_error) / + gpadc->twl6030_cal_tbl[i].gain_error; + + return corrected_code; +} + +static int twl6030_gpadc_get_raw(struct twl6030_gpadc_data *gpadc, + int channel, int *res) +{ + u8 reg = gpadc->pdata->channel_to_reg(channel); + __le16 val; + int raw_code; + int ret; + + ret = twl6030_gpadc_read(reg, (u8 *)&val); + if (ret) { + dev_dbg(gpadc->dev, "unable to read register 0x%X\n", reg); + return ret; + } + + raw_code = le16_to_cpu(val); + dev_dbg(gpadc->dev, "GPADC raw code: %d", raw_code); + + if (twl6030_channel_calibrated(gpadc->pdata, channel)) + *res = twl6030_gpadc_make_correction(gpadc, channel, raw_code); + else + *res = raw_code; + + return ret; +} + +static int twl6030_gpadc_get_processed(struct twl6030_gpadc_data *gpadc, + int channel, int *val) +{ + const struct twl6030_ideal_code *ideal = gpadc->pdata->ideal; + int corrected_code; + int channel_value; + int i; + int ret; + + ret = twl6030_gpadc_get_raw(gpadc, channel, &corrected_code); + if (ret) + return ret; + + i = twl6030_gpadc_lookup(ideal, channel, gpadc->pdata->nchannels); + channel_value = corrected_code * + gpadc->twl6030_cal_tbl[i].gain; + + /* Shift back into mV range */ + channel_value /= 1000; + + dev_dbg(gpadc->dev, "GPADC corrected code: %d", corrected_code); + dev_dbg(gpadc->dev, "GPADC value: %d", channel_value); + + *val = channel_value; + + return ret; +} + +static int twl6030_gpadc_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct twl6030_gpadc_data *gpadc = iio_priv(indio_dev); + int ret; + long time_left; + + mutex_lock(&gpadc->lock); + + ret = gpadc->pdata->start_conversion(chan->channel); + if (ret) { + dev_err(gpadc->dev, "failed to start conversion\n"); + goto err; + } + /* wait for conversion to complete */ + time_left = wait_for_completion_interruptible_timeout( + &gpadc->irq_complete, msecs_to_jiffies(5000)); + if (time_left == 0) { + ret = -ETIMEDOUT; + goto err; + } else if (time_left < 0) { + ret = -EINTR; + goto err; + } + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = twl6030_gpadc_get_raw(gpadc, chan->channel, val); + ret = ret ? -EIO : IIO_VAL_INT; + break; + + case IIO_CHAN_INFO_PROCESSED: + ret = twl6030_gpadc_get_processed(gpadc, chan->channel, val); + ret = ret ? -EIO : IIO_VAL_INT; + break; + + default: + break; + } +err: + mutex_unlock(&gpadc->lock); + + return ret; +} + +/* + * The GPADC channels are calibrated using a two point calibration method. + * The channels measured with two known values: volt1 and volt2, and + * ideal corresponding output codes are known: code1, code2. + * The difference(d1, d2) between ideal and measured codes stored in trim + * registers. + * The goal is to find offset and gain of the real curve for each calibrated + * channel. + * gain: k = 1 + ((d2 - d1) / (x2 - x1)) + * offset: b = d1 + (k - 1) * x1 + */ +static void twl6030_calibrate_channel(struct twl6030_gpadc_data *gpadc, + int channel, int d1, int d2) +{ + int b, k, gain, x1, x2, i; + const struct twl6030_ideal_code *ideal = gpadc->pdata->ideal; + + i = twl6030_gpadc_lookup(ideal, channel, gpadc->pdata->nchannels); + + /* Gain */ + gain = ((ideal[i].volt2 - ideal[i].volt1) * 1000) / + (ideal[i].code2 - ideal[i].code1); + + x1 = ideal[i].code1; + x2 = ideal[i].code2; + + /* k - real curve gain */ + k = 1000 + (((d2 - d1) * 1000) / (x2 - x1)); + + /* b - offset of the real curve gain */ + b = (d1 * 1000) - (k - 1000) * x1; + + gpadc->twl6030_cal_tbl[i].gain = gain; + gpadc->twl6030_cal_tbl[i].gain_error = k; + gpadc->twl6030_cal_tbl[i].offset_error = b; + + dev_dbg(gpadc->dev, "GPADC d1 for Chn: %d = %d\n", channel, d1); + dev_dbg(gpadc->dev, "GPADC d2 for Chn: %d = %d\n", channel, d2); + dev_dbg(gpadc->dev, "GPADC x1 for Chn: %d = %d\n", channel, x1); + dev_dbg(gpadc->dev, "GPADC x2 for Chn: %d = %d\n", channel, x2); + dev_dbg(gpadc->dev, "GPADC Gain for Chn: %d = %d\n", channel, gain); + dev_dbg(gpadc->dev, "GPADC k for Chn: %d = %d\n", channel, k); + dev_dbg(gpadc->dev, "GPADC b for Chn: %d = %d\n", channel, b); +} + +static inline int twl6030_gpadc_get_trim_offset(s8 d) +{ + /* + * XXX NOTE! + * bit 0 - sign, bit 7 - reserved, 6..1 - trim value + * though, the documentation states that trim value + * is absolute value, the correct conversion results are + * obtained if the value is interpreted as 2's complement. + */ + __u32 temp = ((d & 0x7f) >> 1) | ((d & 1) << 6); + + return sign_extend32(temp, 6); +} + +static int twl6030_calibration(struct twl6030_gpadc_data *gpadc) +{ + int ret; + int chn; + u8 trim_regs[TWL6030_GPADC_NUM_TRIM_REGS]; + s8 d1, d2; + + /* + * for calibration two measurements have been performed at + * factory, for some channels, during the production test and + * have been stored in registers. This two stored values are + * used to correct the measurements. The values represent + * offsets for the given input from the output on ideal curve. + */ + ret = twl_i2c_read(TWL6030_MODULE_ID2, trim_regs, + TWL6030_GPADC_TRIM1, TWL6030_GPADC_NUM_TRIM_REGS); + if (ret < 0) { + dev_err(gpadc->dev, "calibration failed\n"); + return ret; + } + + for (chn = 0; chn < TWL6030_GPADC_MAX_CHANNELS; chn++) { + + switch (chn) { + case 0: + d1 = trim_regs[0]; + d2 = trim_regs[1]; + break; + case 1: + case 3: + case 4: + case 5: + case 6: + d1 = trim_regs[4]; + d2 = trim_regs[5]; + break; + case 2: + d1 = trim_regs[12]; + d2 = trim_regs[13]; + break; + case 7: + d1 = trim_regs[6]; + d2 = trim_regs[7]; + break; + case 8: + d1 = trim_regs[2]; + d2 = trim_regs[3]; + break; + case 9: + d1 = trim_regs[8]; + d2 = trim_regs[9]; + break; + case 10: + d1 = trim_regs[10]; + d2 = trim_regs[11]; + break; + case 14: + d1 = trim_regs[14]; + d2 = trim_regs[15]; + break; + default: + continue; + } + + d1 = twl6030_gpadc_get_trim_offset(d1); + d2 = twl6030_gpadc_get_trim_offset(d2); + + twl6030_calibrate_channel(gpadc, chn, d1, d2); + } + + return 0; +} + +static int twl6032_get_trim_value(u8 *trim_regs, unsigned int reg0, + unsigned int reg1, unsigned int mask0, unsigned int mask1, + unsigned int shift0) +{ + int val; + + val = (trim_regs[reg0] & mask0) << shift0; + val |= (trim_regs[reg1] & mask1) >> 1; + if (trim_regs[reg1] & 0x01) + val = -val; + + return val; +} + +static int twl6032_calibration(struct twl6030_gpadc_data *gpadc) +{ + int chn, d1 = 0, d2 = 0, temp; + u8 trim_regs[TWL6030_GPADC_NUM_TRIM_REGS]; + int ret; + + ret = twl_i2c_read(TWL6030_MODULE_ID2, trim_regs, + TWL6030_GPADC_TRIM1, TWL6030_GPADC_NUM_TRIM_REGS); + if (ret < 0) { + dev_err(gpadc->dev, "calibration failed\n"); + return ret; + } + + /* + * Loop to calculate the value needed for returning voltages from + * GPADC not values. + * + * gain is calculated to 3 decimal places fixed point. + */ + for (chn = 0; chn < TWL6032_GPADC_MAX_CHANNELS; chn++) { + + switch (chn) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 11: + case 14: + d1 = twl6032_get_trim_value(trim_regs, 2, 0, 0x1f, + 0x06, 2); + d2 = twl6032_get_trim_value(trim_regs, 3, 1, 0x3f, + 0x06, 2); + break; + case 8: + temp = twl6032_get_trim_value(trim_regs, 2, 0, 0x1f, + 0x06, 2); + d1 = temp + twl6032_get_trim_value(trim_regs, 7, 6, + 0x18, 0x1E, 1); + + temp = twl6032_get_trim_value(trim_regs, 3, 1, 0x3F, + 0x06, 2); + d2 = temp + twl6032_get_trim_value(trim_regs, 9, 7, + 0x1F, 0x06, 2); + break; + case 9: + temp = twl6032_get_trim_value(trim_regs, 2, 0, 0x1f, + 0x06, 2); + d1 = temp + twl6032_get_trim_value(trim_regs, 13, 11, + 0x18, 0x1E, 1); + + temp = twl6032_get_trim_value(trim_regs, 3, 1, 0x3f, + 0x06, 2); + d2 = temp + twl6032_get_trim_value(trim_regs, 15, 13, + 0x1F, 0x06, 1); + break; + case 10: + d1 = twl6032_get_trim_value(trim_regs, 10, 8, 0x0f, + 0x0E, 3); + d2 = twl6032_get_trim_value(trim_regs, 14, 12, 0x0f, + 0x0E, 3); + break; + case 7: + case 18: + temp = twl6032_get_trim_value(trim_regs, 2, 0, 0x1f, + 0x06, 2); + + d1 = (trim_regs[4] & 0x7E) >> 1; + if (trim_regs[4] & 0x01) + d1 = -d1; + d1 += temp; + + temp = twl6032_get_trim_value(trim_regs, 3, 1, 0x3f, + 0x06, 2); + + d2 = (trim_regs[5] & 0xFE) >> 1; + if (trim_regs[5] & 0x01) + d2 = -d2; + + d2 += temp; + break; + default: + /* No data for other channels */ + continue; + } + + twl6030_calibrate_channel(gpadc, chn, d1, d2); + } + + return 0; +} + +#define TWL6030_GPADC_CHAN(chn, _type, chan_info) { \ + .type = _type, \ + .channel = chn, \ + .info_mask_separate = BIT(chan_info), \ + .indexed = 1, \ +} + +static const struct iio_chan_spec twl6030_gpadc_iio_channels[] = { + TWL6030_GPADC_CHAN(0, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(1, IIO_TEMP, IIO_CHAN_INFO_RAW), + TWL6030_GPADC_CHAN(2, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(3, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(4, IIO_TEMP, IIO_CHAN_INFO_RAW), + TWL6030_GPADC_CHAN(5, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(6, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(7, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(8, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(9, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(10, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(11, IIO_VOLTAGE, IIO_CHAN_INFO_RAW), + TWL6030_GPADC_CHAN(14, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), +}; + +static const struct iio_chan_spec twl6032_gpadc_iio_channels[] = { + TWL6030_GPADC_CHAN(0, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(1, IIO_TEMP, IIO_CHAN_INFO_RAW), + TWL6030_GPADC_CHAN(2, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(3, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(4, IIO_TEMP, IIO_CHAN_INFO_RAW), + TWL6030_GPADC_CHAN(5, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(6, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(7, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(8, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(9, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(10, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(11, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(14, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(17, IIO_VOLTAGE, IIO_CHAN_INFO_RAW), + TWL6030_GPADC_CHAN(18, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), +}; + +static const struct iio_info twl6030_gpadc_iio_info = { + .read_raw = &twl6030_gpadc_read_raw, +}; + +static const struct twl6030_gpadc_platform_data twl6030_pdata = { + .iio_channels = twl6030_gpadc_iio_channels, + .nchannels = TWL6030_GPADC_USED_CHANNELS, + .ideal = twl6030_ideal, + .start_conversion = twl6030_start_conversion, + .channel_to_reg = twl6030_channel_to_reg, + .calibrate = twl6030_calibration, +}; + +static const struct twl6030_gpadc_platform_data twl6032_pdata = { + .iio_channels = twl6032_gpadc_iio_channels, + .nchannels = TWL6032_GPADC_USED_CHANNELS, + .ideal = twl6032_ideal, + .start_conversion = twl6032_start_conversion, + .channel_to_reg = twl6032_channel_to_reg, + .calibrate = twl6032_calibration, +}; + +static const struct of_device_id of_twl6030_match_tbl[] = { + { + .compatible = "ti,twl6030-gpadc", + .data = &twl6030_pdata, + }, + { + .compatible = "ti,twl6032-gpadc", + .data = &twl6032_pdata, + }, + { } +}; +MODULE_DEVICE_TABLE(of, of_twl6030_match_tbl); + +static int twl6030_gpadc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct twl6030_gpadc_data *gpadc; + const struct twl6030_gpadc_platform_data *pdata; + struct iio_dev *indio_dev; + int irq; + int ret; + + pdata = device_get_match_data(&pdev->dev); + if (!pdata) + return -EINVAL; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*gpadc)); + if (!indio_dev) + return -ENOMEM; + + gpadc = iio_priv(indio_dev); + + gpadc->twl6030_cal_tbl = devm_kcalloc(dev, + pdata->nchannels, + sizeof(*gpadc->twl6030_cal_tbl), + GFP_KERNEL); + if (!gpadc->twl6030_cal_tbl) + return -ENOMEM; + + gpadc->dev = dev; + gpadc->pdata = pdata; + + platform_set_drvdata(pdev, indio_dev); + mutex_init(&gpadc->lock); + init_completion(&gpadc->irq_complete); + + ret = pdata->calibrate(gpadc); + if (ret < 0) { + dev_err(dev, "failed to read calibration registers\n"); + return ret; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(dev, irq, NULL, + twl6030_gpadc_irq_handler, + IRQF_ONESHOT, "twl6030_gpadc", indio_dev); + if (ret) + return ret; + + ret = twl6030_gpadc_enable_irq(TWL6030_GPADC_RT_SW1_EOC_MASK); + if (ret < 0) { + dev_err(dev, "failed to enable GPADC interrupt\n"); + return ret; + } + + ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, TWL6030_GPADCS, + TWL6030_REG_TOGGLE1); + if (ret < 0) { + dev_err(dev, "failed to enable GPADC module\n"); + return ret; + } + + ret = twl_i2c_write_u8(TWL_MODULE_USB, VBUS_MEAS, USB_VBUS_CTRL_SET); + if (ret < 0) { + dev_err(dev, "failed to wire up inputs\n"); + return ret; + } + + ret = twl_i2c_write_u8(TWL_MODULE_USB, ID_MEAS, USB_ID_CTRL_SET); + if (ret < 0) { + dev_err(dev, "failed to wire up inputs\n"); + return ret; + } + + ret = twl_i2c_write_u8(TWL6030_MODULE_ID0, + VBAT_MEAS | BB_MEAS | VAC_MEAS, + TWL6030_MISC1); + if (ret < 0) { + dev_err(dev, "failed to wire up inputs\n"); + return ret; + } + + indio_dev->name = DRIVER_NAME; + indio_dev->info = &twl6030_gpadc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = pdata->iio_channels; + indio_dev->num_channels = pdata->nchannels; + + return iio_device_register(indio_dev); +} + +static void twl6030_gpadc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + + twl6030_gpadc_disable_irq(TWL6030_GPADC_RT_SW1_EOC_MASK); + iio_device_unregister(indio_dev); +} + +static int twl6030_gpadc_suspend(struct device *pdev) +{ + int ret; + + ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, TWL6030_GPADCR, + TWL6030_REG_TOGGLE1); + if (ret) + dev_err(pdev, "error resetting GPADC (%d)!\n", ret); + + return 0; +}; + +static int twl6030_gpadc_resume(struct device *pdev) +{ + int ret; + + ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, TWL6030_GPADCS, + TWL6030_REG_TOGGLE1); + if (ret) + dev_err(pdev, "error setting GPADC (%d)!\n", ret); + + return 0; +}; + +static DEFINE_SIMPLE_DEV_PM_OPS(twl6030_gpadc_pm_ops, twl6030_gpadc_suspend, + twl6030_gpadc_resume); + +static struct platform_driver twl6030_gpadc_driver = { + .probe = twl6030_gpadc_probe, + .remove = twl6030_gpadc_remove, + .driver = { + .name = DRIVER_NAME, + .pm = pm_sleep_ptr(&twl6030_gpadc_pm_ops), + .of_match_table = of_twl6030_match_tbl, + }, +}; + +module_platform_driver(twl6030_gpadc_driver); + +MODULE_ALIAS("platform:" DRIVER_NAME); +MODULE_AUTHOR("Balaji T K "); +MODULE_AUTHOR("Graeme Gregory "); +MODULE_AUTHOR("Oleksandr Kozaruk +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "versal-sysmon.h" + +/* + * Oversampling ratio values exposed to userspace via IIO. + * Actual number of samples averaged: 1=none, 2=2x, 4=4x, 8=8x, 16=16x. + */ +static const int sysmon_oversampling_avail[] = { 1, 2, 4, 8, 16 }; + +/* TEMP hysteresis mode bit in SYSMON_TEMP_EV_CFG */ +#define SYSMON_TEMP_HYST_MASK BIT(1) + +/* Compute alarm register offset from a channel address */ +#define SYSMON_ALARM_OFFSET(addr) \ + (SYSMON_ALARM_REG + ((addr) / SYSMON_ALARM_BITS_PER_REG) * SYSMON_REG_STRIDE) + +#define SYSMON_CHAN_TEMP(_chan, _address, _name) \ +{ \ + .type = IIO_TEMP, \ + .indexed = 1, \ + .address = _address, \ + .channel = _chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .datasheet_name = _name, \ +} + +enum sysmon_alarm_bit { + SYSMON_BIT_ALARM0 = 0, + SYSMON_BIT_ALARM1 = 1, + SYSMON_BIT_ALARM2 = 2, + SYSMON_BIT_ALARM3 = 3, + SYSMON_BIT_ALARM4 = 4, + SYSMON_BIT_TEMP = 9, +}; + +/* Temperature event specification: rising threshold + hysteresis only */ +static const struct iio_event_spec sysmon_temp_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_HYSTERESIS), + }, +}; + +/* Supply event specifications */ +static const struct iio_event_spec sysmon_supply_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +/* + * Static temperature channels (always present). + * + * These are hardware-computed aggregate registers across all active + * temperature satellites: + * temp: current max temperature across all active satellites + * min: current min temperature across all active satellites + * max_max: highest peak recorded since last hardware reset + * min_min: lowest trough recorded since last hardware reset + */ +static const struct iio_chan_spec temp_channels[] = { + SYSMON_CHAN_TEMP(0, SYSMON_TEMP_MAX, "temp"), + SYSMON_CHAN_TEMP(1, SYSMON_TEMP_MIN, "min"), + SYSMON_CHAN_TEMP(2, SYSMON_TEMP_MAX_MAX, "max_max"), + SYSMON_CHAN_TEMP(3, SYSMON_TEMP_MIN_MIN, "min_min"), +}; + +static void sysmon_q8p7_to_millicelsius(s16 raw_data, int *val) +{ + *val = (raw_data * MILLIDEGREE_PER_DEGREE) >> SYSMON_FRACTIONAL_SHIFT; +} + +static void sysmon_millicelsius_to_q8p7(u32 *raw_data, int val) +{ + *raw_data = (val << SYSMON_FRACTIONAL_SHIFT) / MILLIDEGREE_PER_DEGREE; +} + +static void sysmon_supply_rawtoprocessed(int raw_data, int *val) +{ + int mantissa, format, exponent; + + mantissa = FIELD_GET(SYSMON_MANTISSA_MASK, raw_data); + exponent = SYSMON_SUPPLY_MANTISSA_BITS - FIELD_GET(SYSMON_MODE_MASK, raw_data); + format = FIELD_GET(SYSMON_FMT_MASK, raw_data); + /* + * When format bit is set the mantissa is two's complement + * (per hardware spec); sign-extend to int for correct arithmetic. + */ + if (format) + mantissa = sign_extend32(mantissa, 15); + + *val = (mantissa * (int)MILLI) >> exponent; +} + +static void sysmon_supply_processedtoraw(int val, u32 reg_val, u32 *raw_data) +{ + int exponent = FIELD_GET(SYSMON_MODE_MASK, reg_val); + int format = FIELD_GET(SYSMON_FMT_MASK, reg_val); + int scale, tmp; + + scale = BIT(SYSMON_SUPPLY_MANTISSA_BITS - exponent); + tmp = (val * scale) / (int)MILLI; + + if (format) + tmp = clamp(tmp, S16_MIN, S16_MAX); + else + tmp = clamp(tmp, 0, U16_MAX); + + *raw_data = (u16)tmp; +} + +static int sysmon_supply_thresh_offset(unsigned long address, enum iio_event_direction dir) +{ + if (dir == IIO_EV_DIR_RISING) + return (address * SYSMON_REG_STRIDE) + SYSMON_SUPPLY_TH_UP; + if (dir == IIO_EV_DIR_FALLING) + return (address * SYSMON_REG_STRIDE) + SYSMON_SUPPLY_TH_LOW; + + return -EINVAL; +} + +static int sysmon_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct sysmon *sysmon = iio_priv(indio_dev); + unsigned int regval; + int ret; + + guard(mutex)(&sysmon->lock); + + if (mask == IIO_CHAN_INFO_OVERSAMPLING_RATIO) { + *val = (chan->type == IIO_TEMP) ? sysmon->temp_oversampling : + sysmon->supply_oversampling; + return IIO_VAL_INT; + } + + switch (chan->type) { + case IIO_TEMP: + if (mask == IIO_CHAN_INFO_SCALE) { + /* Q8.7 to millicelsius: raw * 1000 / 128 */ + *val = MILLIDEGREE_PER_DEGREE; + *val2 = BIT(SYSMON_FRACTIONAL_SHIFT); + return IIO_VAL_FRACTIONAL; + } + if (mask != IIO_CHAN_INFO_RAW) + return -EINVAL; + + ret = regmap_read(sysmon->regmap, chan->address, ®val); + if (ret) + return ret; + + *val = sign_extend32(regval, 15); + return IIO_VAL_INT; + + case IIO_VOLTAGE: + if (mask != IIO_CHAN_INFO_PROCESSED) + return -EINVAL; + + ret = regmap_read(sysmon->regmap, + chan->address * SYSMON_REG_STRIDE + + SYSMON_SUPPLY_BASE, ®val); + if (ret) + return ret; + + sysmon_supply_rawtoprocessed(regval, val); + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static u32 sysmon_get_event_mask(const struct iio_chan_spec *chan) +{ + if (chan->type == IIO_TEMP) + return BIT(SYSMON_BIT_TEMP); + + return BIT(chan->address / SYSMON_ALARM_BITS_PER_REG); +} + +static int sysmon_read_alarm_config(struct sysmon *sysmon, + unsigned long address) +{ + u32 shift = address % SYSMON_ALARM_BITS_PER_REG; + u32 offset = SYSMON_ALARM_OFFSET(address); + + return regmap_test_bits(sysmon->regmap, offset, BIT(shift)); +} + +static int sysmon_write_alarm_config(struct sysmon *sysmon, + unsigned long address, bool enable) +{ + u32 shift = address % SYSMON_ALARM_BITS_PER_REG; + u32 offset = SYSMON_ALARM_OFFSET(address); + + return regmap_assign_bits(sysmon->regmap, offset, BIT(shift), enable); +} + +static int sysmon_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct sysmon *sysmon = iio_priv(indio_dev); + u32 mask = sysmon_get_event_mask(chan); + unsigned int imr; + int config_value; + int ret; + + ret = regmap_read(sysmon->regmap, SYSMON_IMR, &imr); + if (ret) + return ret; + + /* IMR bits are 1=masked, invert to get 1=enabled */ + imr = ~imr; + + switch (chan->type) { + case IIO_VOLTAGE: + config_value = sysmon_read_alarm_config(sysmon, chan->address); + if (config_value < 0) + return config_value; + return config_value && (imr & mask); + + case IIO_TEMP: + /* + * Return the administrative state, not the hardware IMR. + * The IRQ handler temporarily masks the interrupt during + * the polling window; reading IMR would show it as disabled. + * temp_mask bit is set when administratively disabled. + */ + return !(sysmon->temp_mask & mask); + + default: + return -EINVAL; + } +} + +static int sysmon_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + u32 offset = SYSMON_ALARM_OFFSET(chan->address); + struct sysmon *sysmon = iio_priv(indio_dev); + u32 mask = sysmon_get_event_mask(chan); + unsigned int alarm_config; + int ret; + + guard(mutex)(&sysmon->lock); + + switch (chan->type) { + case IIO_VOLTAGE: + ret = sysmon_write_alarm_config(sysmon, chan->address, state); + if (ret) + return ret; + + ret = regmap_read(sysmon->regmap, offset, &alarm_config); + if (ret) + return ret; + + if (alarm_config) + return regmap_write(sysmon->regmap, SYSMON_IER, mask); + + return regmap_write(sysmon->regmap, SYSMON_IDR, mask); + + case IIO_TEMP: + if (state) { + ret = regmap_write(sysmon->regmap, SYSMON_IER, mask); + if (ret) + return ret; + + scoped_guard(spinlock_irq, &sysmon->irq_lock) + sysmon->temp_mask &= ~mask; + } else { + ret = regmap_write(sysmon->regmap, SYSMON_IDR, mask); + if (ret) + return ret; + + scoped_guard(spinlock_irq, &sysmon->irq_lock) + sysmon->temp_mask |= mask; + } + return 0; + + default: + return -EINVAL; + } +} + +/* + * Recompute the lower threshold register from upper threshold and + * cached hysteresis. Called when either upper threshold or hysteresis + * is written. + */ +static int sysmon_update_temp_lower(struct sysmon *sysmon) +{ + unsigned int upper_reg; + int upper_mc, lower_mc; + u32 raw_val; + int ret; + + ret = regmap_read(sysmon->regmap, SYSMON_TEMP_TH_UP, &upper_reg); + if (ret) + return ret; + + sysmon_q8p7_to_millicelsius(upper_reg, &upper_mc); + lower_mc = clamp(upper_mc - sysmon->temp_hysteresis, -256000, 255992); + sysmon_millicelsius_to_q8p7(&raw_val, lower_mc); + + return regmap_write(sysmon->regmap, SYSMON_TEMP_TH_LOW, raw_val); +} + +static int sysmon_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct sysmon *sysmon = iio_priv(indio_dev); + unsigned int reg_val; + int offset; + int ret; + + guard(mutex)(&sysmon->lock); + + switch (chan->type) { + case IIO_TEMP: + switch (info) { + case IIO_EV_INFO_VALUE: + ret = regmap_read(sysmon->regmap, SYSMON_TEMP_TH_UP, ®_val); + if (ret) + return ret; + + sysmon_q8p7_to_millicelsius(reg_val, val); + + return IIO_VAL_INT; + + case IIO_EV_INFO_HYSTERESIS: + *val = sysmon->temp_hysteresis; + return IIO_VAL_INT; + + default: + return -EINVAL; + } + + case IIO_VOLTAGE: + offset = sysmon_supply_thresh_offset(chan->address, dir); + if (offset < 0) + return offset; + + ret = regmap_read(sysmon->regmap, offset, ®_val); + if (ret) + return ret; + + sysmon_supply_rawtoprocessed(reg_val, val); + + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int sysmon_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct sysmon *sysmon = iio_priv(indio_dev); + unsigned int reg_val; + u32 raw_val; + int offset; + int ret; + + guard(mutex)(&sysmon->lock); + + switch (chan->type) { + case IIO_TEMP: + switch (info) { + case IIO_EV_INFO_VALUE: + /* Q8.7 signed range: -256000 to +255992 mC */ + if (val < -256000 || val > 255992) + return -EINVAL; + + sysmon_millicelsius_to_q8p7(&raw_val, val); + + ret = regmap_write(sysmon->regmap, SYSMON_TEMP_TH_UP, raw_val); + if (ret) + return ret; + + /* Recompute lower = upper - hysteresis */ + return sysmon_update_temp_lower(sysmon); + + case IIO_EV_INFO_HYSTERESIS: + if (val < 0) + return -EINVAL; + + sysmon->temp_hysteresis = val; + + return sysmon_update_temp_lower(sysmon); + + default: + return -EINVAL; + } + + case IIO_VOLTAGE: + offset = sysmon_supply_thresh_offset(chan->address, dir); + if (offset < 0) + return offset; + + ret = regmap_read(sysmon->regmap, offset, ®_val); + if (ret) + return ret; + + /* Clamp to prevent overflow in processedtoraw conversion */ + if (val < -32768 || val > 32767) + return -EINVAL; + + sysmon_supply_processedtoraw(val, reg_val, &raw_val); + + /* + * The hardware threshold register returns FMT and MODE + * bits in the upper 16 bits on read, but only the lower + * 16-bit mantissa is used on write. + */ + return regmap_write(sysmon->regmap, offset, raw_val); + + default: + return -EINVAL; + } +} + +static int sysmon_set_avg_enable(struct sysmon *sysmon, + u32 base, u32 count, u32 val) +{ + struct regmap *map = sysmon->regmap; + int ret; + + for (unsigned int i = 0; i < count; i++) { + ret = regmap_write(map, base + i * SYSMON_REG_STRIDE, val); + if (ret) + return ret; + } + + return 0; +} + +static int sysmon_osr_write_temp(struct sysmon *sysmon, unsigned int val) +{ + /* + * HW register encoding is sample_count / 2: + * 0=none, 1=2x, 2=4x, 4=8x, 8=16x (not log2-based). + */ + unsigned int hw_val = val >> 1; + unsigned int readback; + int ret; + + ret = regmap_update_bits(sysmon->regmap, SYSMON_CONFIG, + SYSMON_CONFIG_TEMP_SAT_OSR, + FIELD_PREP(SYSMON_CONFIG_TEMP_SAT_OSR, hw_val)); + if (ret) + return ret; + + /* + * Readback fence: the SysMon CONFIG register resides in the + * PMC domain behind the NoC. A posted write may not reach the + * hardware before the next MMIO access. Reading the register + * back forces the interconnect to complete the write, preventing + * a bus hang on the subsequent access. + */ + regmap_read(sysmon->regmap, SYSMON_CONFIG, &readback); + + return sysmon_set_avg_enable(sysmon, SYSMON_TEMP_EN_AVG_BASE, + SYSMON_TEMP_EN_AVG_COUNT, + hw_val ? ~0 : 0); +} + +static int sysmon_osr_write_supply(struct sysmon *sysmon, unsigned int val) +{ + /* HW encoding: sample_count / 2 (see sysmon_osr_write_temp) */ + unsigned int hw_val = val >> 1; + unsigned int readback; + int ret; + + ret = regmap_update_bits(sysmon->regmap, SYSMON_CONFIG, + SYSMON_CONFIG_SUPPLY_OSR, + FIELD_PREP(SYSMON_CONFIG_SUPPLY_OSR, hw_val)); + if (ret) + return ret; + + /* Readback fence -- see sysmon_osr_write_temp for details */ + regmap_read(sysmon->regmap, SYSMON_CONFIG, &readback); + + return sysmon_set_avg_enable(sysmon, SYSMON_SUPPLY_EN_AVG_BASE, + SYSMON_SUPPLY_EN_AVG_COUNT, + hw_val ? ~0 : 0); +} + +static int sysmon_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct sysmon *sysmon = iio_priv(indio_dev); + unsigned int i; + int ret; + + if (mask != IIO_CHAN_INFO_OVERSAMPLING_RATIO) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(sysmon_oversampling_avail); i++) { + if (val == sysmon_oversampling_avail[i]) + break; + } + if (i == ARRAY_SIZE(sysmon_oversampling_avail)) + return -EINVAL; + + guard(mutex)(&sysmon->lock); + + if (chan->type == IIO_TEMP) { + ret = sysmon_osr_write_temp(sysmon, val); + if (ret) + return ret; + sysmon->temp_oversampling = val; + } else { + ret = sysmon_osr_write_supply(sysmon, val); + if (ret) + return ret; + sysmon->supply_oversampling = val; + } + + return 0; +} + +static int sysmon_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + if (mask == IIO_CHAN_INFO_OVERSAMPLING_RATIO) + return IIO_VAL_INT; + + return -EINVAL; +} + +static int sysmon_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, + int *length, long mask) +{ + if (mask != IIO_CHAN_INFO_OVERSAMPLING_RATIO) + return -EINVAL; + + *vals = sysmon_oversampling_avail; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(sysmon_oversampling_avail); + + return IIO_AVAIL_LIST; +} + +static int sysmon_read_label(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + char *label) +{ + if (chan->datasheet_name) + return sysfs_emit(label, "%s\n", chan->datasheet_name); + + return -EINVAL; +} + +static const struct iio_info sysmon_iio_info = { + .read_raw = sysmon_read_raw, + .write_raw = sysmon_write_raw, + .write_raw_get_fmt = sysmon_write_raw_get_fmt, + .read_avail = sysmon_read_avail, + .read_label = sysmon_read_label, + .read_event_config = sysmon_read_event_config, + .write_event_config = sysmon_write_event_config, + .read_event_value = sysmon_read_event_value, + .write_event_value = sysmon_write_event_value, +}; + +static void sysmon_push_event(struct iio_dev *indio_dev, u32 address) +{ + const struct iio_chan_spec *chan; + enum iio_event_direction dir; + + for (unsigned int i = 0; i < indio_dev->num_channels; i++) { + if (indio_dev->channels[i].address != address) + continue; + + chan = &indio_dev->channels[i]; + /* Temp uses hysteresis mode (rising only), voltage uses window */ + dir = (chan->type == IIO_TEMP) ? IIO_EV_DIR_RISING : + IIO_EV_DIR_EITHER; + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(chan->type, + chan->channel, + IIO_EV_TYPE_THRESH, + dir), + iio_get_time_ns(indio_dev)); + } +} + +static int sysmon_handle_event(struct iio_dev *indio_dev, u32 event) +{ + u32 alarm_flag_offset = SYSMON_ALARM_FLAG + event * SYSMON_REG_STRIDE; + u32 alarm_reg_offset = SYSMON_ALARM_REG + event * SYSMON_REG_STRIDE; + struct sysmon *sysmon = iio_priv(indio_dev); + unsigned long alarm_flag_reg; + unsigned int reg_val; + u32 address, bit; + int ret; + + switch (event) { + case SYSMON_BIT_TEMP: + sysmon_push_event(indio_dev, SYSMON_TEMP_MAX); + + ret = regmap_write(sysmon->regmap, SYSMON_IDR, BIT(SYSMON_BIT_TEMP)); + if (ret) + return ret; + + sysmon->masked_temp |= BIT(SYSMON_BIT_TEMP); + return 0; + + case SYSMON_BIT_ALARM0: + case SYSMON_BIT_ALARM1: + case SYSMON_BIT_ALARM2: + case SYSMON_BIT_ALARM3: + case SYSMON_BIT_ALARM4: + ret = regmap_read(sysmon->regmap, alarm_flag_offset, ®_val); + if (ret) + return ret; + + alarm_flag_reg = reg_val; + + for_each_set_bit(bit, &alarm_flag_reg, SYSMON_ALARM_BITS_PER_REG) { + address = bit + SYSMON_ALARM_BITS_PER_REG * event; + sysmon_push_event(indio_dev, address); + ret = regmap_clear_bits(sysmon->regmap, alarm_reg_offset, BIT(bit)); + if (ret) + return ret; + } + + return regmap_write(sysmon->regmap, alarm_flag_offset, alarm_flag_reg); + + default: + return -EINVAL; + } +} + +static void sysmon_handle_events(struct iio_dev *indio_dev, + unsigned long events) +{ + unsigned int bit; + + for_each_set_bit(bit, &events, SYSMON_NO_OF_EVENTS) + sysmon_handle_event(indio_dev, bit); +} + +static void sysmon_unmask_temp(struct sysmon *sysmon, unsigned int isr) +{ + unsigned int status; + u32 ier; + + status = isr & SYSMON_TEMP_INTR_MASK; + + ier = ~status & sysmon->masked_temp; + sysmon->masked_temp &= status; + + /* Only unmask if not administratively disabled by userspace */ + ier &= ~sysmon->temp_mask; + + regmap_write(sysmon->regmap, SYSMON_IER, ier); +} + +/* + * Versal threshold interrupts are level-sensitive. Active threshold + * interrupts are masked in the handler and polled via delayed work + * until the condition clears, then unmasked. + */ +static void sysmon_unmask_worker(struct work_struct *work) +{ + struct sysmon *sysmon = + container_of(work, struct sysmon, sysmon_unmask_work.work); + unsigned int isr; + + /* + * If the ISR read fails, skip processing to avoid acting + * on undefined data. + */ + scoped_guard(spinlock_irq, &sysmon->irq_lock) { + if (regmap_read(sysmon->regmap, SYSMON_ISR, &isr)) + break; + regmap_write(sysmon->regmap, SYSMON_ISR, isr); + sysmon_unmask_temp(sysmon, isr); + } + + if (sysmon->masked_temp) + schedule_delayed_work(&sysmon->sysmon_unmask_work, + msecs_to_jiffies(SYSMON_UNMASK_WORK_DELAY_MS)); + else + regmap_write(sysmon->regmap, SYSMON_STATUS_RESET, 1); +} + +static irqreturn_t sysmon_iio_irq(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct sysmon *sysmon = iio_priv(indio_dev); + unsigned int isr, imr; + + guard(spinlock)(&sysmon->irq_lock); + + if (regmap_read(sysmon->regmap, SYSMON_ISR, &isr) || + regmap_read(sysmon->regmap, SYSMON_IMR, &imr)) + return IRQ_NONE; + + isr &= ~imr; + if (!isr) + return IRQ_NONE; + + regmap_write(sysmon->regmap, SYSMON_ISR, isr); + + sysmon_handle_events(indio_dev, isr); + schedule_delayed_work(&sysmon->sysmon_unmask_work, + msecs_to_jiffies(SYSMON_UNMASK_WORK_DELAY_MS)); + + return IRQ_HANDLED; +} + +static void sysmon_disable_interrupts(void *data) +{ + struct sysmon *sysmon = data; + + regmap_write(sysmon->regmap, SYSMON_IDR, SYSMON_INTR_ALL_MASK); + + scoped_guard(spinlock_irq, &sysmon->irq_lock) + sysmon->masked_temp = 0; + + cancel_delayed_work_sync(&sysmon->sysmon_unmask_work); +} + +static int sysmon_init_interrupt(struct sysmon *sysmon, + struct device *dev, + struct iio_dev *indio_dev, + int irq) +{ + unsigned int imr; + int ret; + + /* Events not supported without IRQ (e.g. I2C path) */ + if (!irq) + return 0; + + INIT_DELAYED_WORK(&sysmon->sysmon_unmask_work, sysmon_unmask_worker); + + ret = regmap_read(sysmon->regmap, SYSMON_IMR, &imr); + if (ret) + return ret; + sysmon->temp_mask = imr & SYSMON_TEMP_INTR_MASK; + + ret = devm_request_irq(dev, irq, sysmon_iio_irq, 0, "sysmon-irq", indio_dev); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, sysmon_disable_interrupts, sysmon); +} + +/* + * Initialize the cached hysteresis for a temperature channel from the + * current hardware threshold registers: hysteresis = upper - lower. + */ +static int sysmon_init_hysteresis(struct sysmon *sysmon, int *hysteresis) +{ + unsigned int upper_reg, lower_reg; + int upper_mc, lower_mc; + int ret; + + ret = regmap_read(sysmon->regmap, SYSMON_TEMP_TH_UP, &upper_reg); + if (ret) + return ret; + + ret = regmap_read(sysmon->regmap, SYSMON_TEMP_TH_LOW, &lower_reg); + if (ret) + return ret; + + sysmon_q8p7_to_millicelsius(upper_reg, &upper_mc); + sysmon_q8p7_to_millicelsius(lower_reg, &lower_mc); + *hysteresis = upper_mc - lower_mc; + + return 0; +} + +/** + * sysmon_parse_fw() - Parse firmware nodes and configure IIO channels. + * @indio_dev: IIO device instance + * @dev: Parent device + * @irq: IRQ number (positive enables event channels, 0 disables) + * + * Reads voltage-channels and temperature-channels container nodes from + * firmware and builds the IIO channel array. Static temperature channels + * and event channels are prepended, followed by supply and satellite + * channels from DT. + * + * Event channels and per-channel event specs are only added when the + * device has an IRQ. I2C devices have no interrupt line, and the I2C + * regmap cannot be called from atomic context, so events are not + * supported on that path. + * + * Return: 0 on success, negative errno on failure. + */ +static int sysmon_parse_fw(struct iio_dev *indio_dev, struct device *dev, int irq) +{ + unsigned int num_chan, num_static, num_supply, num_temp; + unsigned int idx, temp_chan_idx, volt_chan_idx; + struct iio_chan_spec *sysmon_channels; + const char *label; + u32 reg; + int ret; + + struct fwnode_handle *supply_node __free(fwnode_handle) = + device_get_named_child_node(dev, "voltage-channels"); + num_supply = fwnode_get_child_node_count(supply_node); + + struct fwnode_handle *temp_node __free(fwnode_handle) = + device_get_named_child_node(dev, "temperature-channels"); + num_temp = fwnode_get_child_node_count(temp_node); + + num_static = ARRAY_SIZE(temp_channels); + num_chan = size_add(num_temp, size_add(num_static, num_supply)); + sysmon_channels = devm_kcalloc(dev, num_chan, sizeof(*sysmon_channels), GFP_KERNEL); + if (!sysmon_channels) + return -ENOMEM; + + memcpy(sysmon_channels, temp_channels, sizeof(temp_channels)); + + /* Attach event spec to channel 0 when IRQ is available */ + if (irq > 0) { + sysmon_channels[0].event_spec = sysmon_temp_events; + sysmon_channels[0].num_event_specs = ARRAY_SIZE(sysmon_temp_events); + } + + idx = num_static; + + /* Supply channels from DT */ + fwnode_for_each_child_node_scoped(supply_node, child) { + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret) + return dev_err_probe(dev, ret, + "missing reg for supply channel\n"); + + if (reg > SYSMON_SUPPLY_IDX_MAX) + return dev_err_probe(dev, -EINVAL, + "supply reg %u exceeds max %u\n", + reg, SYSMON_SUPPLY_IDX_MAX); + + ret = fwnode_property_read_string(child, "label", &label); + if (ret) + return dev_err_probe(dev, ret, + "missing label for supply channel\n"); + + sysmon_channels[idx++] = (struct iio_chan_spec) { + .type = IIO_VOLTAGE, + .indexed = 1, + .address = reg, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_shared_by_type = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_type_available = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .event_spec = irq > 0 ? + sysmon_supply_events : NULL, + .num_event_specs = irq > 0 ? + ARRAY_SIZE(sysmon_supply_events) : 0, + .datasheet_name = label, + }; + } + + /* Temperature satellite channels from DT */ + fwnode_for_each_child_node_scoped(temp_node, child) { + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret) + return dev_err_probe(dev, ret, + "missing reg for temp channel\n"); + + if (reg < 1 || reg > SYSMON_TEMP_SAT_MAX) + return dev_err_probe(dev, -EINVAL, + "temp reg %u out of range [1..%u]\n", + reg, SYSMON_TEMP_SAT_MAX); + + ret = fwnode_property_read_string(child, "label", &label); + if (ret) + return dev_err_probe(dev, ret, + "missing label for temp channel\n"); + + sysmon_channels[idx++] = (struct iio_chan_spec) { + .type = IIO_TEMP, + .indexed = 1, + .address = SYSMON_TEMP_SAT_BASE + + (reg - 1) * SYSMON_REG_STRIDE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_type_available = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .datasheet_name = label, + }; + } + + indio_dev->num_channels = idx; + indio_dev->info = &sysmon_iio_info; + + /* + * Assign per-type sequential channel numbers. + * IIO sysfs uses type prefix (in_tempN, in_voltageN) + * so numbers only need to be unique within each type. + */ + temp_chan_idx = 0; + volt_chan_idx = 0; + for (unsigned int idx = 0; idx < indio_dev->num_channels; idx++) { + if (sysmon_channels[idx].type == IIO_TEMP) + sysmon_channels[idx].channel = temp_chan_idx++; + else + sysmon_channels[idx].channel = volt_chan_idx++; + } + + indio_dev->channels = sysmon_channels; + + return 0; +} + +/** + * devm_versal_sysmon_core_probe() - Initialize Versal SysMon core + * @dev: Parent device + * @regmap: Register map for hardware access + * + * Return: 0 on success, negative errno on failure. + */ +int devm_versal_sysmon_core_probe(struct device *dev, struct regmap *regmap) +{ + struct iio_dev *indio_dev; + struct sysmon *sysmon; + int irq; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*sysmon)); + if (!indio_dev) + return -ENOMEM; + + sysmon = iio_priv(indio_dev); + sysmon->regmap = regmap; + sysmon->temp_oversampling = 1; + sysmon->supply_oversampling = 1; + + ret = devm_mutex_init(dev, &sysmon->lock); + if (ret) + return ret; + spin_lock_init(&sysmon->irq_lock); + + /* Disable all interrupts and clear pending status */ + ret = regmap_write(sysmon->regmap, SYSMON_IDR, SYSMON_INTR_ALL_MASK); + if (ret) + return ret; + ret = regmap_write(sysmon->regmap, SYSMON_ISR, SYSMON_INTR_ALL_MASK); + if (ret) + return ret; + + irq = fwnode_irq_get(dev_fwnode(dev), 0); + if (irq == -EPROBE_DEFER) + return dev_err_probe(dev, irq, "failed to get IRQ\n"); + + indio_dev->name = "versal-sysmon"; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = sysmon_parse_fw(indio_dev, dev, irq); + if (ret) + return ret; + + if (irq > 0) { + /* Set hysteresis mode for temperature threshold */ + ret = regmap_set_bits(sysmon->regmap, SYSMON_TEMP_EV_CFG, + SYSMON_TEMP_HYST_MASK); + if (ret) + return ret; + + /* Initialize cached hysteresis from hardware registers */ + ret = sysmon_init_hysteresis(sysmon, &sysmon->temp_hysteresis); + if (ret) + return ret; + + ret = sysmon_init_interrupt(sysmon, dev, indio_dev, irq); + if (ret) + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS_GPL(devm_versal_sysmon_core_probe, "VERSAL_SYSMON"); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("AMD Versal SysMon Core Driver"); +MODULE_AUTHOR("Salih Erim "); diff --git a/drivers/iio/adc/versal-sysmon-i2c.c b/drivers/iio/adc/versal-sysmon-i2c.c new file mode 100644 index 000000000..e9a762915 --- /dev/null +++ b/drivers/iio/adc/versal-sysmon-i2c.c @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AMD Versal SysMon I2C driver + * + * Copyright (C) 2023 - 2026, Advanced Micro Devices, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "versal-sysmon.h" + +#define SYSMON_I2C_INSTR_READ BIT(2) +#define SYSMON_I2C_INSTR_WRITE BIT(3) + +/* + * I2C command frame layout (8 bytes): + * [0..3] data payload (little-endian u32) + * [4..5] register offset >> 2 (little-endian u16) + * [6] instruction (read/write) + * [7] reserved + */ +#define SYSMON_I2C_DATA_OFS 0 +#define SYSMON_I2C_REG_OFS 4 +#define SYSMON_I2C_INSTR_OFS 6 + +static int sysmon_i2c_reg_read(void *context, unsigned int reg, + unsigned int *val) +{ + struct i2c_client *client = context; + u8 write_buf[8] = { }; + u8 read_buf[4]; + int ret; + + put_unaligned_le16(reg >> 2, &write_buf[SYSMON_I2C_REG_OFS]); + write_buf[SYSMON_I2C_INSTR_OFS] = SYSMON_I2C_INSTR_READ; + + ret = i2c_master_send(client, write_buf, sizeof(write_buf)); + if (ret < 0) + return ret; + if (ret != sizeof(write_buf)) + return -EIO; + + ret = i2c_master_recv(client, read_buf, sizeof(read_buf)); + if (ret < 0) + return ret; + if (ret != sizeof(read_buf)) + return -EIO; + + *val = get_unaligned_le32(read_buf); + + return 0; +} + +static int sysmon_i2c_reg_write(void *context, unsigned int reg, + unsigned int val) +{ + struct i2c_client *client = context; + u8 write_buf[8] = { }; + int ret; + + put_unaligned_le32(val, &write_buf[SYSMON_I2C_DATA_OFS]); + put_unaligned_le16(reg >> 2, &write_buf[SYSMON_I2C_REG_OFS]); + write_buf[SYSMON_I2C_INSTR_OFS] = SYSMON_I2C_INSTR_WRITE; + + ret = i2c_master_send(client, write_buf, sizeof(write_buf)); + if (ret < 0) + return ret; + if (ret != sizeof(write_buf)) + return -EIO; + + return 0; +} + +/* + * Almost all registers are volatile (live ADC readings, interrupt + * status). The rest are not accessed often enough to benefit from + * caching. + */ +static const struct regmap_config sysmon_i2c_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = SYSMON_REG_STRIDE, + .max_register = SYSMON_MAX_REG, + .reg_read = sysmon_i2c_reg_read, + .reg_write = sysmon_i2c_reg_write, +}; + +static int sysmon_i2c_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct regmap *regmap; + + regmap = devm_regmap_init(dev, NULL, client, &sysmon_i2c_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + /* I2C has no IRQ connection; events are not supported */ + return devm_versal_sysmon_core_probe(dev, regmap); +} + +static const struct of_device_id sysmon_i2c_of_match_table[] = { + { .compatible = "xlnx,versal-sysmon" }, + { } +}; +MODULE_DEVICE_TABLE(of, sysmon_i2c_of_match_table); + +static const struct i2c_device_id sysmon_i2c_id_table[] = { + { .name = "versal-sysmon" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sysmon_i2c_id_table); + +static struct i2c_driver sysmon_i2c_driver = { + .probe = sysmon_i2c_probe, + .driver = { + .name = "versal-sysmon-i2c", + .of_match_table = sysmon_i2c_of_match_table, + }, + .id_table = sysmon_i2c_id_table, +}; +module_i2c_driver(sysmon_i2c_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("AMD Versal SysMon I2C Driver"); +MODULE_IMPORT_NS("VERSAL_SYSMON"); +MODULE_AUTHOR("Conall O'Griofa "); +MODULE_AUTHOR("Salih Erim "); diff --git a/drivers/iio/adc/versal-sysmon.c b/drivers/iio/adc/versal-sysmon.c new file mode 100644 index 000000000..529d0486c --- /dev/null +++ b/drivers/iio/adc/versal-sysmon.c @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AMD Versal SysMon MMIO platform driver + * + * Copyright (C) 2019 - 2022, Xilinx, Inc. + * Copyright (C) 2022 - 2026, Advanced Micro Devices, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "versal-sysmon.h" + +struct sysmon_mmio { + void __iomem *base; +}; + +static int sysmon_mmio_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + struct sysmon_mmio *mmio = context; + + *val = readl(mmio->base + reg); + return 0; +} + +static int sysmon_mmio_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct sysmon_mmio *mmio = context; + + /* NPI must be unlocked before any register write except to NPI_LOCK */ + if (reg != SYSMON_NPI_LOCK) + writel(SYSMON_NPI_UNLOCK_CODE, mmio->base + SYSMON_NPI_LOCK); + writel(val, mmio->base + reg); + + return 0; +} + +static const struct regmap_config sysmon_mmio_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = SYSMON_REG_STRIDE, + .max_register = SYSMON_MAX_REG, + .reg_read = sysmon_mmio_reg_read, + .reg_write = sysmon_mmio_reg_write, + .fast_io = true, +}; + +static int sysmon_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sysmon_mmio *mmio; + struct regmap *regmap; + + mmio = devm_kzalloc(dev, sizeof(*mmio), GFP_KERNEL); + if (!mmio) + return -ENOMEM; + + mmio->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mmio->base)) + return PTR_ERR(mmio->base); + + regmap = devm_regmap_init(dev, NULL, mmio, &sysmon_mmio_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return devm_versal_sysmon_core_probe(dev, regmap); +} + +static const struct of_device_id sysmon_of_match_table[] = { + { .compatible = "xlnx,versal-sysmon" }, + { } +}; +MODULE_DEVICE_TABLE(of, sysmon_of_match_table); + +static struct platform_driver sysmon_platform_driver = { + .probe = sysmon_platform_probe, + .driver = { + .name = "versal-sysmon", + .of_match_table = sysmon_of_match_table, + }, +}; +module_platform_driver(sysmon_platform_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("AMD Versal SysMon Platform Driver"); +MODULE_IMPORT_NS("VERSAL_SYSMON"); +MODULE_AUTHOR("Salih Erim "); diff --git a/drivers/iio/adc/versal-sysmon.h b/drivers/iio/adc/versal-sysmon.h new file mode 100644 index 000000000..bb9a75bf7 --- /dev/null +++ b/drivers/iio/adc/versal-sysmon.h @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * AMD Versal SysMon driver + * + * Copyright (C) 2019 - 2022, Xilinx, Inc. + * Copyright (C) 2022 - 2026, Advanced Micro Devices, Inc. + */ + +#ifndef _VERSAL_SYSMON_H_ +#define _VERSAL_SYSMON_H_ + +#include +#include +#include +#include + +struct device; +struct regmap; + +/* Register offsets (sorted by address) */ +#define SYSMON_NPI_LOCK 0x000C +#define SYSMON_ISR 0x0044 +#define SYSMON_IMR 0x0048 +#define SYSMON_IER 0x004C +#define SYSMON_IDR 0x0050 +#define SYSMON_CONFIG 0x0100 +#define SYSMON_TEMP_MAX 0x1030 +#define SYSMON_TEMP_MIN 0x1034 +#define SYSMON_SUPPLY_BASE 0x1040 +#define SYSMON_ALARM_FLAG 0x1018 +#define SYSMON_ALARM_REG 0x1940 +#define SYSMON_SUPPLY_EN_AVG_BASE 0x1958 +#define SYSMON_TEMP_TH_LOW 0x1970 +#define SYSMON_TEMP_TH_UP 0x1974 +#define SYSMON_SUPPLY_TH_LOW 0x1980 +#define SYSMON_SUPPLY_TH_UP 0x1C80 +#define SYSMON_TEMP_EV_CFG 0x1F84 +#define SYSMON_TEMP_MIN_MIN 0x1F8C +#define SYSMON_TEMP_MAX_MAX 0x1F90 +#define SYSMON_STATUS_RESET 0x1F94 +#define SYSMON_TEMP_SAT_BASE 0x1FAC +#define SYSMON_TEMP_EN_AVG_BASE 0x24B4 +#define SYSMON_MAX_REG 0x24C0 + +/* NPI unlock value written to SYSMON_NPI_LOCK */ +#define SYSMON_NPI_UNLOCK_CODE 0xF9E8D7C6 + +/* Register stride: 4 bytes per 32-bit register */ +#define SYSMON_REG_STRIDE 4 + +#define SYSMON_SUPPLY_IDX_MAX 159 +#define SYSMON_TEMP_SAT_MAX 64 +#define SYSMON_NO_OF_EVENTS 32 +#define SYSMON_INTR_ALL_MASK GENMASK(31, 0) + +/* ISR/IMR temperature alarm mask (bit 9) */ +#define SYSMON_TEMP_INTR_MASK BIT(9) + +/* SYSMON_CONFIG: supply oversampling ratio */ +#define SYSMON_CONFIG_SUPPLY_OSR GENMASK(17, 14) + +/* SYSMON_CONFIG: temperature satellite oversampling ratio */ +#define SYSMON_CONFIG_TEMP_SAT_OSR GENMASK(27, 24) + +/* Per-channel averaging enable register counts */ +#define SYSMON_SUPPLY_EN_AVG_COUNT 5 +#define SYSMON_TEMP_EN_AVG_COUNT 2 + +/* Supply voltage conversion register fields */ +#define SYSMON_MANTISSA_MASK GENMASK(15, 0) +#define SYSMON_FMT_MASK BIT(16) +#define SYSMON_MODE_MASK GENMASK(18, 17) + +/* Q8.7 fractional shift */ +#define SYSMON_FRACTIONAL_SHIFT 7U +#define SYSMON_SUPPLY_MANTISSA_BITS 16 + +/* Bits per alarm register */ +#define SYSMON_ALARM_BITS_PER_REG 32 + +#define SYSMON_UNMASK_WORK_DELAY_MS 500 + +/** + * struct sysmon - Driver data for Versal SysMon + * @regmap: register map for hardware access + * @lock: protects read-modify-write sequences on threshold registers + * and cached state that spans multiple regmap calls + * @irq_lock: protects interrupt mask register updates (MMIO path only) + * @masked_temp: currently masked temperature alarm bits + * @temp_mask: temperature interrupt configuration mask + * @temp_hysteresis: cached DEVICE_TEMP hysteresis in millicelsius + * @sysmon_unmask_work: re-enables events after alarm condition clears + * @temp_oversampling: current temp oversampling ratio + * @supply_oversampling: current supply oversampling ratio + */ +struct sysmon { + struct regmap *regmap; + /* + * Protects read-modify-write sequences on threshold registers + * and cached state (oversampling ratios, hysteresis values) + * that spans multiple regmap calls. + */ + struct mutex lock; + /* + * Protects interrupt mask register updates. Only used on the + * MMIO path (fast_io regmap); I2C has no IRQ and never reaches + * the event code that takes this lock. + */ + spinlock_t irq_lock; + unsigned int masked_temp; + unsigned int temp_mask; + int temp_hysteresis; + struct delayed_work sysmon_unmask_work; + unsigned int temp_oversampling; + unsigned int supply_oversampling; +}; + +int devm_versal_sysmon_core_probe(struct device *dev, struct regmap *regmap); + +#endif /* _VERSAL_SYSMON_H_ */ diff --git a/drivers/iio/adc/vf610_adc.c b/drivers/iio/adc/vf610_adc.c new file mode 100644 index 000000000..bcbeb482e --- /dev/null +++ b/drivers/iio/adc/vf610_adc.c @@ -0,0 +1,961 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Freescale Vybrid vf610 ADC driver + * + * Copyright 2013 Freescale Semiconductor, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* Vybrid/IMX ADC registers */ +#define VF610_REG_ADC_HC0 0x00 +#define VF610_REG_ADC_HC1 0x04 +#define VF610_REG_ADC_HS 0x08 +#define VF610_REG_ADC_R0 0x0c +#define VF610_REG_ADC_R1 0x10 +#define VF610_REG_ADC_CFG 0x14 +#define VF610_REG_ADC_GC 0x18 +#define VF610_REG_ADC_GS 0x1c +#define VF610_REG_ADC_CV 0x20 +#define VF610_REG_ADC_OFS 0x24 +#define VF610_REG_ADC_CAL 0x28 +#define VF610_REG_ADC_PCTL 0x30 + +/* Configuration register field define */ +#define VF610_ADC_MODE_BIT8 0x00 +#define VF610_ADC_MODE_BIT10 0x04 +#define VF610_ADC_MODE_BIT12 0x08 +#define VF610_ADC_MODE_MASK 0x0c +#define VF610_ADC_BUSCLK2_SEL 0x01 +#define VF610_ADC_ALTCLK_SEL 0x02 +#define VF610_ADC_ADACK_SEL 0x03 +#define VF610_ADC_ADCCLK_MASK 0x03 +#define VF610_ADC_CLK_DIV2 0x20 +#define VF610_ADC_CLK_DIV4 0x40 +#define VF610_ADC_CLK_DIV8 0x60 +#define VF610_ADC_CLK_MASK 0x60 +#define VF610_ADC_ADLSMP_LONG 0x10 +#define VF610_ADC_ADSTS_SHORT 0x100 +#define VF610_ADC_ADSTS_NORMAL 0x200 +#define VF610_ADC_ADSTS_LONG 0x300 +#define VF610_ADC_ADSTS_MASK 0x300 +#define VF610_ADC_ADLPC_EN 0x80 +#define VF610_ADC_ADHSC_EN 0x400 +#define VF610_ADC_REFSEL_VALT 0x800 +#define VF610_ADC_REFSEL_VBG 0x1000 +#define VF610_ADC_ADTRG_HARD 0x2000 +#define VF610_ADC_AVGS_8 0x4000 +#define VF610_ADC_AVGS_16 0x8000 +#define VF610_ADC_AVGS_32 0xC000 +#define VF610_ADC_AVGS_MASK 0xC000 +#define VF610_ADC_OVWREN 0x10000 + +/* General control register field define */ +#define VF610_ADC_ADACKEN 0x1 +#define VF610_ADC_DMAEN 0x2 +#define VF610_ADC_ACREN 0x4 +#define VF610_ADC_ACFGT 0x8 +#define VF610_ADC_ACFE 0x10 +#define VF610_ADC_AVGEN 0x20 +#define VF610_ADC_ADCON 0x40 +#define VF610_ADC_CAL 0x80 + +/* Other field define */ +#define VF610_ADC_ADCHC(x) ((x) & 0x1F) +#define VF610_ADC_AIEN (0x1 << 7) +#define VF610_ADC_CONV_DISABLE 0x1F +#define VF610_ADC_HS_COCO0 0x1 +#define VF610_ADC_CALF 0x2 +#define VF610_ADC_TIMEOUT msecs_to_jiffies(100) + +#define DEFAULT_SAMPLE_TIME 1000 + +/* V at 25°C of 696 mV */ +#define VF610_VTEMP25_3V0 950 +/* V at 25°C of 699 mV */ +#define VF610_VTEMP25_3V3 867 +/* Typical sensor slope coefficient at all temperatures */ +#define VF610_TEMP_SLOPE_COEFF 1840 + +enum clk_sel { + VF610_ADCIOC_BUSCLK_SET, + VF610_ADCIOC_ALTCLK_SET, + VF610_ADCIOC_ADACK_SET, +}; + +enum vol_ref { + VF610_ADCIOC_VR_VREF_SET, + VF610_ADCIOC_VR_VALT_SET, + VF610_ADCIOC_VR_VBG_SET, +}; + +enum average_sel { + VF610_ADC_SAMPLE_1, + VF610_ADC_SAMPLE_4, + VF610_ADC_SAMPLE_8, + VF610_ADC_SAMPLE_16, + VF610_ADC_SAMPLE_32, +}; + +enum conversion_mode_sel { + VF610_ADC_CONV_NORMAL, + VF610_ADC_CONV_HIGH_SPEED, + VF610_ADC_CONV_LOW_POWER, +}; + +enum lst_adder_sel { + VF610_ADCK_CYCLES_3, + VF610_ADCK_CYCLES_5, + VF610_ADCK_CYCLES_7, + VF610_ADCK_CYCLES_9, + VF610_ADCK_CYCLES_13, + VF610_ADCK_CYCLES_17, + VF610_ADCK_CYCLES_21, + VF610_ADCK_CYCLES_25, +}; + +struct vf610_adc_feature { + enum clk_sel clk_sel; + enum vol_ref vol_ref; + enum conversion_mode_sel conv_mode; + + int clk_div; + int sample_rate; + int res_mode; + u32 lst_adder_index; + u32 default_sample_time; + + bool calibration; + bool ovwren; +}; + +struct vf610_adc { + struct device *dev; + void __iomem *regs; + struct clk *clk; + + /* lock to protect against multiple access to the device */ + struct mutex lock; + + u32 vref_uv; + u32 value; + struct regulator *vref; + + u32 max_adck_rate[3]; + struct vf610_adc_feature adc_feature; + + u32 sample_freq_avail[5]; + + struct completion completion; + /* Ensure the timestamp is naturally aligned */ + struct { + u16 chan; + aligned_s64 timestamp; + } scan; +}; + +struct vf610_chip_info { + u8 num_channels; +}; + +static const u32 vf610_hw_avgs[] = { 1, 4, 8, 16, 32 }; +static const u32 vf610_lst_adder[] = { 3, 5, 7, 9, 13, 17, 21, 25 }; + +static inline void vf610_adc_calculate_rates(struct vf610_adc *info) +{ + struct vf610_adc_feature *adc_feature = &info->adc_feature; + unsigned long adck_rate, ipg_rate = clk_get_rate(info->clk); + u32 adck_period, lst_addr_min; + int divisor, i; + + adck_rate = info->max_adck_rate[adc_feature->conv_mode]; + + if (adck_rate) { + /* calculate clk divider which is within specification */ + divisor = ipg_rate / adck_rate; + adc_feature->clk_div = 1 << fls(divisor + 1); + } else { + /* fall-back value using a safe divisor */ + adc_feature->clk_div = 8; + } + + adck_rate = ipg_rate / adc_feature->clk_div; + + /* + * Determine the long sample time adder value to be used based + * on the default minimum sample time provided. + */ + adck_period = NSEC_PER_SEC / adck_rate; + lst_addr_min = adc_feature->default_sample_time / adck_period; + for (i = 0; i < ARRAY_SIZE(vf610_lst_adder); i++) { + if (vf610_lst_adder[i] > lst_addr_min) { + adc_feature->lst_adder_index = i; + break; + } + } + + /* + * Calculate ADC sample frequencies + * Sample time unit is ADCK cycles. ADCK clk source is ipg clock, + * which is the same as bus clock. + * + * ADC conversion time = SFCAdder + AverageNum x (BCT + LSTAdder) + * SFCAdder: fixed to 6 ADCK cycles + * AverageNum: 1, 4, 8, 16, 32 samples for hardware average. + * BCT (Base Conversion Time): fixed to 25 ADCK cycles for 12 bit mode + * LSTAdder(Long Sample Time): 3, 5, 7, 9, 13, 17, 21, 25 ADCK cycles + */ + for (i = 0; i < ARRAY_SIZE(vf610_hw_avgs); i++) + info->sample_freq_avail[i] = + adck_rate / (6 + vf610_hw_avgs[i] * + (25 + vf610_lst_adder[adc_feature->lst_adder_index])); +} + +static inline void vf610_adc_cfg_init(struct vf610_adc *info) +{ + struct vf610_adc_feature *adc_feature = &info->adc_feature; + + /* set default Configuration for ADC controller */ + adc_feature->clk_sel = VF610_ADCIOC_BUSCLK_SET; + adc_feature->vol_ref = VF610_ADCIOC_VR_VREF_SET; + + adc_feature->calibration = true; + adc_feature->ovwren = true; + + adc_feature->res_mode = 12; + adc_feature->sample_rate = 1; + + adc_feature->conv_mode = VF610_ADC_CONV_LOW_POWER; + + vf610_adc_calculate_rates(info); +} + +static void vf610_adc_cfg_post_set(struct vf610_adc *info) +{ + struct vf610_adc_feature *adc_feature = &info->adc_feature; + int cfg_data = 0; + int gc_data = 0; + + switch (adc_feature->clk_sel) { + case VF610_ADCIOC_ALTCLK_SET: + cfg_data |= VF610_ADC_ALTCLK_SEL; + break; + case VF610_ADCIOC_ADACK_SET: + cfg_data |= VF610_ADC_ADACK_SEL; + break; + default: + break; + } + + /* low power set for calibration */ + cfg_data |= VF610_ADC_ADLPC_EN; + + /* enable high speed for calibration */ + cfg_data |= VF610_ADC_ADHSC_EN; + + /* voltage reference */ + switch (adc_feature->vol_ref) { + case VF610_ADCIOC_VR_VREF_SET: + break; + case VF610_ADCIOC_VR_VALT_SET: + cfg_data |= VF610_ADC_REFSEL_VALT; + break; + case VF610_ADCIOC_VR_VBG_SET: + cfg_data |= VF610_ADC_REFSEL_VBG; + break; + default: + dev_err(info->dev, "error voltage reference\n"); + } + + /* data overwrite enable */ + if (adc_feature->ovwren) + cfg_data |= VF610_ADC_OVWREN; + + writel(cfg_data, info->regs + VF610_REG_ADC_CFG); + writel(gc_data, info->regs + VF610_REG_ADC_GC); +} + +static void vf610_adc_calibration(struct vf610_adc *info) +{ + int adc_gc, hc_cfg; + + if (!info->adc_feature.calibration) + return; + + /* enable calibration interrupt */ + hc_cfg = VF610_ADC_AIEN | VF610_ADC_CONV_DISABLE; + writel(hc_cfg, info->regs + VF610_REG_ADC_HC0); + + adc_gc = readl(info->regs + VF610_REG_ADC_GC); + writel(adc_gc | VF610_ADC_CAL, info->regs + VF610_REG_ADC_GC); + + if (!wait_for_completion_timeout(&info->completion, VF610_ADC_TIMEOUT)) + dev_err(info->dev, "Timeout for adc calibration\n"); + + adc_gc = readl(info->regs + VF610_REG_ADC_GS); + if (adc_gc & VF610_ADC_CALF) + dev_err(info->dev, "ADC calibration failed\n"); + + info->adc_feature.calibration = false; +} + +static void vf610_adc_cfg_set(struct vf610_adc *info) +{ + struct vf610_adc_feature *adc_feature = &(info->adc_feature); + int cfg_data; + + cfg_data = readl(info->regs + VF610_REG_ADC_CFG); + + cfg_data &= ~VF610_ADC_ADLPC_EN; + if (adc_feature->conv_mode == VF610_ADC_CONV_LOW_POWER) + cfg_data |= VF610_ADC_ADLPC_EN; + + cfg_data &= ~VF610_ADC_ADHSC_EN; + if (adc_feature->conv_mode == VF610_ADC_CONV_HIGH_SPEED) + cfg_data |= VF610_ADC_ADHSC_EN; + + writel(cfg_data, info->regs + VF610_REG_ADC_CFG); +} + +static void vf610_adc_sample_set(struct vf610_adc *info) +{ + struct vf610_adc_feature *adc_feature = &(info->adc_feature); + int cfg_data, gc_data; + + cfg_data = readl(info->regs + VF610_REG_ADC_CFG); + gc_data = readl(info->regs + VF610_REG_ADC_GC); + + /* resolution mode */ + cfg_data &= ~VF610_ADC_MODE_MASK; + switch (adc_feature->res_mode) { + case 8: + cfg_data |= VF610_ADC_MODE_BIT8; + break; + case 10: + cfg_data |= VF610_ADC_MODE_BIT10; + break; + case 12: + cfg_data |= VF610_ADC_MODE_BIT12; + break; + default: + dev_err(info->dev, "error resolution mode\n"); + break; + } + + /* clock select and clock divider */ + cfg_data &= ~(VF610_ADC_CLK_MASK | VF610_ADC_ADCCLK_MASK); + switch (adc_feature->clk_div) { + case 1: + break; + case 2: + cfg_data |= VF610_ADC_CLK_DIV2; + break; + case 4: + cfg_data |= VF610_ADC_CLK_DIV4; + break; + case 8: + cfg_data |= VF610_ADC_CLK_DIV8; + break; + case 16: + switch (adc_feature->clk_sel) { + case VF610_ADCIOC_BUSCLK_SET: + cfg_data |= VF610_ADC_BUSCLK2_SEL | VF610_ADC_CLK_DIV8; + break; + default: + dev_err(info->dev, "error clk divider\n"); + break; + } + break; + } + + /* + * Set ADLSMP and ADSTS based on the Long Sample Time Adder value + * determined. + */ + switch (adc_feature->lst_adder_index) { + case VF610_ADCK_CYCLES_3: + break; + case VF610_ADCK_CYCLES_5: + cfg_data |= VF610_ADC_ADSTS_SHORT; + break; + case VF610_ADCK_CYCLES_7: + cfg_data |= VF610_ADC_ADSTS_NORMAL; + break; + case VF610_ADCK_CYCLES_9: + cfg_data |= VF610_ADC_ADSTS_LONG; + break; + case VF610_ADCK_CYCLES_13: + cfg_data |= VF610_ADC_ADLSMP_LONG; + break; + case VF610_ADCK_CYCLES_17: + cfg_data |= VF610_ADC_ADLSMP_LONG; + cfg_data |= VF610_ADC_ADSTS_SHORT; + break; + case VF610_ADCK_CYCLES_21: + cfg_data |= VF610_ADC_ADLSMP_LONG; + cfg_data |= VF610_ADC_ADSTS_NORMAL; + break; + case VF610_ADCK_CYCLES_25: + cfg_data |= VF610_ADC_ADLSMP_LONG; + cfg_data |= VF610_ADC_ADSTS_NORMAL; + break; + default: + dev_err(info->dev, "error in sample time select\n"); + } + + /* update hardware average selection */ + cfg_data &= ~VF610_ADC_AVGS_MASK; + gc_data &= ~VF610_ADC_AVGEN; + switch (adc_feature->sample_rate) { + case VF610_ADC_SAMPLE_1: + break; + case VF610_ADC_SAMPLE_4: + gc_data |= VF610_ADC_AVGEN; + break; + case VF610_ADC_SAMPLE_8: + gc_data |= VF610_ADC_AVGEN; + cfg_data |= VF610_ADC_AVGS_8; + break; + case VF610_ADC_SAMPLE_16: + gc_data |= VF610_ADC_AVGEN; + cfg_data |= VF610_ADC_AVGS_16; + break; + case VF610_ADC_SAMPLE_32: + gc_data |= VF610_ADC_AVGEN; + cfg_data |= VF610_ADC_AVGS_32; + break; + default: + dev_err(info->dev, + "error hardware sample average select\n"); + } + + writel(cfg_data, info->regs + VF610_REG_ADC_CFG); + writel(gc_data, info->regs + VF610_REG_ADC_GC); +} + +static void vf610_adc_hw_init(struct vf610_adc *info) +{ + /* CFG: Feature set */ + vf610_adc_cfg_post_set(info); + vf610_adc_sample_set(info); + + /* adc calibration */ + vf610_adc_calibration(info); + + /* CFG: power and speed set */ + vf610_adc_cfg_set(info); +} + +static int vf610_set_conversion_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct vf610_adc *info = iio_priv(indio_dev); + + mutex_lock(&info->lock); + info->adc_feature.conv_mode = mode; + vf610_adc_calculate_rates(info); + vf610_adc_hw_init(info); + mutex_unlock(&info->lock); + + return 0; +} + +static int vf610_get_conversion_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct vf610_adc *info = iio_priv(indio_dev); + + return info->adc_feature.conv_mode; +} + +static const char * const vf610_conv_modes[] = { "normal", "high-speed", + "low-power" }; + +static const struct iio_enum vf610_conversion_mode = { + .items = vf610_conv_modes, + .num_items = ARRAY_SIZE(vf610_conv_modes), + .get = vf610_get_conversion_mode, + .set = vf610_set_conversion_mode, +}; + +static const struct iio_chan_spec_ext_info vf610_ext_info[] = { + IIO_ENUM("conversion_mode", IIO_SHARED_BY_DIR, &vf610_conversion_mode), + { } +}; + +#define VF610_ADC_CHAN(_idx, _chan_type) { \ + .type = (_chan_type), \ + .indexed = 1, \ + .channel = (_idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .ext_info = vf610_ext_info, \ + .scan_index = (_idx), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 12, \ + .storagebits = 16, \ + }, \ +} + +#define VF610_ADC_TEMPERATURE_CHAN(_idx, _chan_type) { \ + .type = (_chan_type), \ + .channel = (_idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ + .scan_index = (_idx), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 12, \ + .storagebits = 16, \ + }, \ +} + +static const struct iio_chan_spec vf610_adc_iio_channels[] = { + VF610_ADC_CHAN(0, IIO_VOLTAGE), + VF610_ADC_CHAN(1, IIO_VOLTAGE), + VF610_ADC_CHAN(2, IIO_VOLTAGE), + VF610_ADC_CHAN(3, IIO_VOLTAGE), + VF610_ADC_CHAN(4, IIO_VOLTAGE), + VF610_ADC_CHAN(5, IIO_VOLTAGE), + VF610_ADC_CHAN(6, IIO_VOLTAGE), + VF610_ADC_CHAN(7, IIO_VOLTAGE), + VF610_ADC_CHAN(8, IIO_VOLTAGE), + VF610_ADC_CHAN(9, IIO_VOLTAGE), + VF610_ADC_CHAN(10, IIO_VOLTAGE), + VF610_ADC_CHAN(11, IIO_VOLTAGE), + VF610_ADC_CHAN(12, IIO_VOLTAGE), + VF610_ADC_CHAN(13, IIO_VOLTAGE), + VF610_ADC_CHAN(14, IIO_VOLTAGE), + VF610_ADC_CHAN(15, IIO_VOLTAGE), + VF610_ADC_TEMPERATURE_CHAN(26, IIO_TEMP), + IIO_CHAN_SOFT_TIMESTAMP(32), + /* sentinel */ +}; + +static int vf610_adc_read_data(struct vf610_adc *info) +{ + int result; + + result = readl(info->regs + VF610_REG_ADC_R0); + + switch (info->adc_feature.res_mode) { + case 8: + result &= 0xFF; + break; + case 10: + result &= 0x3FF; + break; + case 12: + result &= 0xFFF; + break; + default: + break; + } + + return result; +} + +static irqreturn_t vf610_adc_isr(int irq, void *dev_id) +{ + struct iio_dev *indio_dev = dev_id; + struct vf610_adc *info = iio_priv(indio_dev); + int coco; + + coco = readl(info->regs + VF610_REG_ADC_HS); + if (coco & VF610_ADC_HS_COCO0) { + info->value = vf610_adc_read_data(info); + if (iio_buffer_enabled(indio_dev)) { + info->scan.chan = info->value; + iio_push_to_buffers_with_ts(indio_dev, &info->scan, + sizeof(info->scan), + iio_get_time_ns(indio_dev)); + iio_trigger_notify_done(indio_dev->trig); + } else + complete(&info->completion); + } + + return IRQ_HANDLED; +} + +static ssize_t vf610_show_samp_freq_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vf610_adc *info = iio_priv(dev_to_iio_dev(dev)); + size_t len = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(info->sample_freq_avail); i++) + len += scnprintf(buf + len, PAGE_SIZE - len, + "%u ", info->sample_freq_avail[i]); + + /* replace trailing space by newline */ + buf[len - 1] = '\n'; + + return len; +} + +static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(vf610_show_samp_freq_avail); + +static struct attribute *vf610_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group vf610_attribute_group = { + .attrs = vf610_attributes, +}; + +static int vf610_read_sample(struct vf610_adc *info, + struct iio_chan_spec const *chan, int *val) +{ + unsigned int hc_cfg; + int ret; + + guard(mutex)(&info->lock); + reinit_completion(&info->completion); + hc_cfg = VF610_ADC_ADCHC(chan->channel); + hc_cfg |= VF610_ADC_AIEN; + writel(hc_cfg, info->regs + VF610_REG_ADC_HC0); + ret = wait_for_completion_interruptible_timeout(&info->completion, + VF610_ADC_TIMEOUT); + if (ret == 0) + return -ETIMEDOUT; + + if (ret < 0) + return ret; + + switch (chan->type) { + case IIO_VOLTAGE: + *val = info->value; + return 0; + case IIO_TEMP: + /* + * Calculate in degree Celsius times 1000 + * Using the typical sensor slope of 1.84 mV/°C + * and VREFH_ADC at 3.3V, V at 25°C of 699 mV + */ + *val = 25000 - ((int)info->value - VF610_VTEMP25_3V3) * + 1000000 / VF610_TEMP_SLOPE_COEFF; + + return 0; + default: + return -EINVAL; + } +} + +static int vf610_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct vf610_adc *info = iio_priv(indio_dev); + long ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = vf610_read_sample(info, chan, val); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = info->vref_uv / 1000; + *val2 = info->adc_feature.res_mode; + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = info->sample_freq_avail[info->adc_feature.sample_rate]; + *val2 = 0; + return IIO_VAL_INT; + + default: + break; + } + + return -EINVAL; +} + +static int vf610_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct vf610_adc *info = iio_priv(indio_dev); + int i; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + for (i = 0; + i < ARRAY_SIZE(info->sample_freq_avail); + i++) + if (val == info->sample_freq_avail[i]) { + info->adc_feature.sample_rate = i; + vf610_adc_sample_set(info); + return 0; + } + break; + + default: + break; + } + + return -EINVAL; +} + +static int vf610_adc_buffer_postenable(struct iio_dev *indio_dev) +{ + struct vf610_adc *info = iio_priv(indio_dev); + unsigned int channel; + int val; + + val = readl(info->regs + VF610_REG_ADC_GC); + val |= VF610_ADC_ADCON; + writel(val, info->regs + VF610_REG_ADC_GC); + + channel = find_first_bit(indio_dev->active_scan_mask, + iio_get_masklength(indio_dev)); + + val = VF610_ADC_ADCHC(channel); + val |= VF610_ADC_AIEN; + + writel(val, info->regs + VF610_REG_ADC_HC0); + + return 0; +} + +static int vf610_adc_buffer_predisable(struct iio_dev *indio_dev) +{ + struct vf610_adc *info = iio_priv(indio_dev); + unsigned int hc_cfg = 0; + int val; + + val = readl(info->regs + VF610_REG_ADC_GC); + val &= ~VF610_ADC_ADCON; + writel(val, info->regs + VF610_REG_ADC_GC); + + hc_cfg |= VF610_ADC_CONV_DISABLE; + hc_cfg &= ~VF610_ADC_AIEN; + + writel(hc_cfg, info->regs + VF610_REG_ADC_HC0); + + return 0; +} + +static const struct iio_buffer_setup_ops iio_triggered_buffer_setup_ops = { + .postenable = &vf610_adc_buffer_postenable, + .predisable = &vf610_adc_buffer_predisable, + .validate_scan_mask = &iio_validate_scan_mask_onehot, +}; + +static int vf610_adc_reg_access(struct iio_dev *indio_dev, + unsigned reg, unsigned writeval, + unsigned *readval) +{ + struct vf610_adc *info = iio_priv(indio_dev); + + if ((readval == NULL) || + ((reg % 4) || (reg > VF610_REG_ADC_PCTL))) + return -EINVAL; + + *readval = readl(info->regs + reg); + + return 0; +} + +static const struct iio_info vf610_adc_iio_info = { + .read_raw = &vf610_read_raw, + .write_raw = &vf610_write_raw, + .debugfs_reg_access = &vf610_adc_reg_access, + .attrs = &vf610_attribute_group, +}; + +static const struct vf610_chip_info vf610_chip_info = { + .num_channels = ARRAY_SIZE(vf610_adc_iio_channels), +}; + +static const struct vf610_chip_info imx6sx_chip_info = { + .num_channels = 4, +}; + +static const struct of_device_id vf610_adc_match[] = { + { .compatible = "fsl,imx6sx-adc", .data = &imx6sx_chip_info}, + { .compatible = "fsl,vf610-adc", .data = &vf610_chip_info}, + { } +}; +MODULE_DEVICE_TABLE(of, vf610_adc_match); + +static void vf610_adc_action_remove(void *d) +{ + struct vf610_adc *info = d; + + regulator_disable(info->vref); +} + +static int vf610_adc_probe(struct platform_device *pdev) +{ + const struct vf610_chip_info *chip_info; + struct device *dev = &pdev->dev; + struct vf610_adc *info; + struct iio_dev *indio_dev; + int irq; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct vf610_adc)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + info->dev = &pdev->dev; + + info->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(info->regs)) + return PTR_ERR(info->regs); + + chip_info = device_get_match_data(dev); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(info->dev, irq, + vf610_adc_isr, 0, + dev_name(&pdev->dev), indio_dev); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "failed requesting irq, irq = %d\n", irq); + + info->clk = devm_clk_get_enabled(&pdev->dev, "adc"); + if (IS_ERR(info->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(info->clk), "failed getting clock\n"); + + info->vref = devm_regulator_get(&pdev->dev, "vref"); + if (IS_ERR(info->vref)) + return PTR_ERR(info->vref); + + ret = regulator_enable(info->vref); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&pdev->dev, vf610_adc_action_remove, info); + if (ret) + return ret; + + info->vref_uv = regulator_get_voltage(info->vref); + + device_property_read_u32_array(dev, "fsl,adck-max-frequency", info->max_adck_rate, 3); + + info->adc_feature.default_sample_time = DEFAULT_SAMPLE_TIME; + device_property_read_u32(dev, "min-sample-time", &info->adc_feature.default_sample_time); + + platform_set_drvdata(pdev, indio_dev); + + init_completion(&info->completion); + + indio_dev->name = dev_name(&pdev->dev); + indio_dev->info = &vf610_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = vf610_adc_iio_channels; + indio_dev->num_channels = chip_info->num_channels; + + vf610_adc_cfg_init(info); + vf610_adc_hw_init(info); + + ret = devm_iio_triggered_buffer_setup(&pdev->dev, indio_dev, &iio_pollfunc_store_time, + NULL, &iio_triggered_buffer_setup_ops); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "Couldn't initialise the buffer\n"); + + mutex_init(&info->lock); + + ret = devm_iio_device_register(&pdev->dev, indio_dev); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Couldn't register the device.\n"); + + return 0; +} + +static int vf610_adc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct vf610_adc *info = iio_priv(indio_dev); + int hc_cfg; + + /* ADC controller enters to stop mode */ + hc_cfg = readl(info->regs + VF610_REG_ADC_HC0); + hc_cfg |= VF610_ADC_CONV_DISABLE; + writel(hc_cfg, info->regs + VF610_REG_ADC_HC0); + + clk_disable_unprepare(info->clk); + regulator_disable(info->vref); + + return 0; +} + +static int vf610_adc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct vf610_adc *info = iio_priv(indio_dev); + int ret; + + ret = regulator_enable(info->vref); + if (ret) + return ret; + + ret = clk_prepare_enable(info->clk); + if (ret) + goto disable_reg; + + vf610_adc_hw_init(info); + + return 0; + +disable_reg: + regulator_disable(info->vref); + return ret; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(vf610_adc_pm_ops, vf610_adc_suspend, + vf610_adc_resume); + +static struct platform_driver vf610_adc_driver = { + .probe = vf610_adc_probe, + .driver = { + .name = "vf610-adc", + .of_match_table = vf610_adc_match, + .pm = pm_sleep_ptr(&vf610_adc_pm_ops), + }, +}; + +module_platform_driver(vf610_adc_driver); + +MODULE_AUTHOR("Fugang Duan "); +MODULE_DESCRIPTION("Freescale VF610 ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/viperboard_adc.c b/drivers/iio/adc/viperboard_adc.c new file mode 100644 index 000000000..6efe1c618 --- /dev/null +++ b/drivers/iio/adc/viperboard_adc.c @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Nano River Technologies viperboard IIO ADC driver + * + * (C) 2012 by Lemonage GmbH + * Author: Lars Poeschel + * All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#define VPRBRD_ADC_CMD_GET 0x00 + +struct vprbrd_adc_msg { + u8 cmd; + u8 chan; + u8 val; +} __packed; + +struct vprbrd_adc { + struct vprbrd *vb; +}; + +#define VPRBRD_ADC_CHANNEL(_index) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ +} + +static struct iio_chan_spec const vprbrd_adc_iio_channels[] = { + VPRBRD_ADC_CHANNEL(0), + VPRBRD_ADC_CHANNEL(1), + VPRBRD_ADC_CHANNEL(2), + VPRBRD_ADC_CHANNEL(3), +}; + +static int vprbrd_iio_read_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long info) +{ + int ret, error = 0; + struct vprbrd_adc *adc = iio_priv(iio_dev); + struct vprbrd *vb = adc->vb; + struct vprbrd_adc_msg *admsg = (struct vprbrd_adc_msg *)vb->buf; + + switch (info) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&vb->lock); + + admsg->cmd = VPRBRD_ADC_CMD_GET; + admsg->chan = chan->channel; + admsg->val = 0x00; + + ret = usb_control_msg(vb->usb_dev, + usb_sndctrlpipe(vb->usb_dev, 0), VPRBRD_USB_REQUEST_ADC, + VPRBRD_USB_TYPE_OUT, 0x0000, 0x0000, admsg, + sizeof(struct vprbrd_adc_msg), VPRBRD_USB_TIMEOUT_MS); + if (ret != sizeof(struct vprbrd_adc_msg)) { + mutex_unlock(&vb->lock); + error = -EREMOTEIO; + dev_err(&iio_dev->dev, "usb send error on adc read\n"); + goto error; + } + + ret = usb_control_msg(vb->usb_dev, + usb_rcvctrlpipe(vb->usb_dev, 0), VPRBRD_USB_REQUEST_ADC, + VPRBRD_USB_TYPE_IN, 0x0000, 0x0000, admsg, + sizeof(struct vprbrd_adc_msg), VPRBRD_USB_TIMEOUT_MS); + + *val = admsg->val; + + mutex_unlock(&vb->lock); + + if (ret != sizeof(struct vprbrd_adc_msg)) { + dev_err(&iio_dev->dev, "usb recv error on adc read\n"); + error = -EREMOTEIO; + } + + if (error) + goto error; + + return IIO_VAL_INT; + default: + error = -EINVAL; + break; + } +error: + return error; +} + +static const struct iio_info vprbrd_adc_iio_info = { + .read_raw = &vprbrd_iio_read_raw, +}; + +static int vprbrd_adc_probe(struct platform_device *pdev) +{ + struct vprbrd *vb = dev_get_drvdata(pdev->dev.parent); + struct vprbrd_adc *adc; + struct iio_dev *indio_dev; + int ret; + + /* registering iio */ + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->vb = vb; + indio_dev->name = "viperboard adc"; + indio_dev->info = &vprbrd_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = vprbrd_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(vprbrd_adc_iio_channels); + + ret = devm_iio_device_register(&pdev->dev, indio_dev); + if (ret) { + dev_err(&pdev->dev, "could not register iio (adc)"); + return ret; + } + + return 0; +} + +static struct platform_driver vprbrd_adc_driver = { + .driver = { + .name = "viperboard-adc", + }, + .probe = vprbrd_adc_probe, +}; + +module_platform_driver(vprbrd_adc_driver); + +MODULE_AUTHOR("Lars Poeschel "); +MODULE_DESCRIPTION("IIO ADC driver for Nano River Techs Viperboard"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:viperboard-adc"); diff --git a/drivers/iio/adc/xilinx-ams.c b/drivers/iio/adc/xilinx-ams.c new file mode 100644 index 000000000..158e6133a --- /dev/null +++ b/drivers/iio/adc/xilinx-ams.c @@ -0,0 +1,1401 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Xilinx AMS driver + * + * Copyright (C) 2021 Xilinx, Inc. + * + * Manish Narani + * Rajnikant Bhojani + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* AMS registers definitions */ +#define AMS_ISR_0 0x010 +#define AMS_ISR_1 0x014 +#define AMS_IER_0 0x020 +#define AMS_IER_1 0x024 +#define AMS_IDR_0 0x028 +#define AMS_IDR_1 0x02C +#define AMS_PS_CSTS 0x040 +#define AMS_PL_CSTS 0x044 + +#define AMS_VCC_PSPLL0 0x060 +#define AMS_VCC_PSPLL3 0x06C +#define AMS_VCCINT 0x078 +#define AMS_VCCBRAM 0x07C +#define AMS_VCCAUX 0x080 +#define AMS_PSDDRPLL 0x084 +#define AMS_PSINTFPDDR 0x09C + +#define AMS_VCC_PSPLL0_CH 48 +#define AMS_VCC_PSPLL3_CH 51 +#define AMS_VCCINT_CH 54 +#define AMS_VCCBRAM_CH 55 +#define AMS_VCCAUX_CH 56 +#define AMS_PSDDRPLL_CH 57 +#define AMS_PSINTFPDDR_CH 63 + +#define AMS_REG_CONFIG0 0x100 +#define AMS_REG_CONFIG1 0x104 +#define AMS_REG_CONFIG3 0x10C +#define AMS_REG_CONFIG4 0x110 +#define AMS_REG_SEQ_CH0 0x120 +#define AMS_REG_SEQ_CH1 0x124 +#define AMS_REG_SEQ_CH2 0x118 + +#define AMS_VUSER0_MASK BIT(0) +#define AMS_VUSER1_MASK BIT(1) +#define AMS_VUSER2_MASK BIT(2) +#define AMS_VUSER3_MASK BIT(3) + +#define AMS_TEMP 0x000 +#define AMS_SUPPLY1 0x004 +#define AMS_SUPPLY2 0x008 +#define AMS_VP_VN 0x00C +#define AMS_VREFP 0x010 +#define AMS_VREFN 0x014 +#define AMS_SUPPLY3 0x018 +#define AMS_SUPPLY4 0x034 +#define AMS_SUPPLY5 0x038 +#define AMS_SUPPLY6 0x03C +#define AMS_SUPPLY7 0x200 +#define AMS_SUPPLY8 0x204 +#define AMS_SUPPLY9 0x208 +#define AMS_SUPPLY10 0x20C +#define AMS_VCCAMS 0x210 +#define AMS_TEMP_REMOTE 0x214 + +#define AMS_REG_VAUX(x) (0x40 + 4 * (x)) + +#define AMS_PS_RESET_VALUE 0xFFFF +#define AMS_PL_RESET_VALUE 0xFFFF + +#define AMS_CONF0_CHANNEL_NUM_MASK GENMASK(6, 0) + +#define AMS_CONF1_SEQ_MASK GENMASK(15, 12) +#define AMS_CONF1_SEQ_DEFAULT FIELD_PREP(AMS_CONF1_SEQ_MASK, 0) +#define AMS_CONF1_SEQ_CONTINUOUS FIELD_PREP(AMS_CONF1_SEQ_MASK, 2) +#define AMS_CONF1_SEQ_SINGLE_CHANNEL FIELD_PREP(AMS_CONF1_SEQ_MASK, 3) + +#define AMS_REG_SEQ0_MASK GENMASK(15, 0) +#define AMS_REG_SEQ2_MASK GENMASK(21, 16) +#define AMS_REG_SEQ1_MASK GENMASK_ULL(37, 22) + +#define AMS_PS_SEQ_MASK GENMASK(21, 0) +#define AMS_PL_SEQ_MASK GENMASK_ULL(59, 22) + +#define AMS_ALARM_NONE 0x000 /* not a real offset */ +#define AMS_ALARM_TEMP 0x140 +#define AMS_ALARM_SUPPLY1 0x144 +#define AMS_ALARM_SUPPLY2 0x148 +#define AMS_ALARM_SUPPLY3 0x160 +#define AMS_ALARM_SUPPLY4 0x164 +#define AMS_ALARM_SUPPLY5 0x168 +#define AMS_ALARM_SUPPLY6 0x16C +#define AMS_ALARM_SUPPLY7 0x180 +#define AMS_ALARM_SUPPLY8 0x184 +#define AMS_ALARM_SUPPLY9 0x188 +#define AMS_ALARM_SUPPLY10 0x18C +#define AMS_ALARM_VCCAMS 0x190 +#define AMS_ALARM_TEMP_REMOTE 0x194 +#define AMS_ALARM_THRESHOLD_OFF_10 0x10 +#define AMS_ALARM_THRESHOLD_OFF_20 0x20 + +#define AMS_ALARM_THR_DIRECT_MASK BIT(0) +#define AMS_ALARM_THR_MIN 0x0000 +#define AMS_ALARM_THR_MAX (BIT(16) - 1) + +#define AMS_ALARM_MASK GENMASK_ULL(63, 0) +#define AMS_NO_OF_ALARMS 32 +#define AMS_PL_ALARM_START 16 +#define AMS_PL_ALARM_MASK GENMASK(31, 16) +#define AMS_ISR0_ALARM_MASK GENMASK(31, 0) +#define AMS_ISR1_ALARM_MASK (GENMASK(31, 29) | GENMASK(4, 0)) +#define AMS_ISR1_EOC_MASK BIT(3) +#define AMS_ISR1_INTR_MASK GENMASK_ULL(63, 32) +#define AMS_ISR0_ALARM_2_TO_0_MASK GENMASK(2, 0) +#define AMS_ISR0_ALARM_6_TO_3_MASK GENMASK(6, 3) +#define AMS_ISR0_ALARM_12_TO_7_MASK GENMASK(13, 8) +#define AMS_CONF1_ALARM_2_TO_0_MASK GENMASK(3, 1) +#define AMS_CONF1_ALARM_6_TO_3_MASK GENMASK(11, 8) +#define AMS_CONF1_ALARM_12_TO_7_MASK GENMASK(5, 0) +#define AMS_REGCFG1_ALARM_MASK \ + (AMS_CONF1_ALARM_2_TO_0_MASK | AMS_CONF1_ALARM_6_TO_3_MASK | BIT(0)) +#define AMS_REGCFG3_ALARM_MASK AMS_CONF1_ALARM_12_TO_7_MASK + +#define AMS_PS_CSTS_PS_READY (BIT(27) | BIT(16)) +#define AMS_PL_CSTS_ACCESS_MASK BIT(1) + +#define AMS_PL_MAX_FIXED_CHANNEL 10 +#define AMS_PL_MAX_EXT_CHANNEL 20 + +#define AMS_INIT_POLL_TIME_US 200 +#define AMS_INIT_TIMEOUT_US 10000 +#define AMS_UNMASK_TIMEOUT_MS 500 + +/* + * Following scale and offset value is derived from + * UG580 (v1.7) December 20, 2016 + */ +#define AMS_SUPPLY_SCALE_1VOLT_mV 1000 +#define AMS_SUPPLY_SCALE_3VOLT_mV 3000 +#define AMS_SUPPLY_SCALE_6VOLT_mV 6000 +#define AMS_SUPPLY_SCALE_DIV_BIT 16 + +#define AMS_TEMP_SCALE 509314 +#define AMS_TEMP_SCALE_DIV_BIT 16 +#define AMS_TEMP_OFFSET -((280230LL << 16) / 509314) + +enum ams_alarm_bit { + AMS_ALARM_BIT_TEMP = 0, + AMS_ALARM_BIT_SUPPLY1 = 1, + AMS_ALARM_BIT_SUPPLY2 = 2, + AMS_ALARM_BIT_SUPPLY3 = 3, + AMS_ALARM_BIT_SUPPLY4 = 4, + AMS_ALARM_BIT_SUPPLY5 = 5, + AMS_ALARM_BIT_SUPPLY6 = 6, + AMS_ALARM_BIT_RESERVED = 7, + AMS_ALARM_BIT_SUPPLY7 = 8, + AMS_ALARM_BIT_SUPPLY8 = 9, + AMS_ALARM_BIT_SUPPLY9 = 10, + AMS_ALARM_BIT_SUPPLY10 = 11, + AMS_ALARM_BIT_VCCAMS = 12, + AMS_ALARM_BIT_TEMP_REMOTE = 13, +}; + +enum ams_seq { + AMS_SEQ_VCC_PSPLL = 0, + AMS_SEQ_VCC_PSBATT = 1, + AMS_SEQ_VCCINT = 2, + AMS_SEQ_VCCBRAM = 3, + AMS_SEQ_VCCAUX = 4, + AMS_SEQ_PSDDRPLL = 5, + AMS_SEQ_INTDDR = 6, +}; + +enum ams_ps_pl_seq { + AMS_SEQ_CALIB = 0, + AMS_SEQ_RSVD_1 = 1, + AMS_SEQ_RSVD_2 = 2, + AMS_SEQ_TEST = 3, + AMS_SEQ_RSVD_4 = 4, + AMS_SEQ_SUPPLY4 = 5, + AMS_SEQ_SUPPLY5 = 6, + AMS_SEQ_SUPPLY6 = 7, + AMS_SEQ_TEMP = 8, + AMS_SEQ_SUPPLY2 = 9, + AMS_SEQ_SUPPLY1 = 10, + AMS_SEQ_VP_VN = 11, + AMS_SEQ_VREFP = 12, + AMS_SEQ_VREFN = 13, + AMS_SEQ_SUPPLY3 = 14, + AMS_SEQ_CURRENT_MON = 15, + AMS_SEQ_SUPPLY7 = 16, + AMS_SEQ_SUPPLY8 = 17, + AMS_SEQ_SUPPLY9 = 18, + AMS_SEQ_SUPPLY10 = 19, + AMS_SEQ_VCCAMS = 20, + AMS_SEQ_TEMP_REMOTE = 21, + AMS_SEQ_MAX = 22 +}; + +#define AMS_PS_SEQ_MAX AMS_SEQ_MAX +#define AMS_SEQ(x) (AMS_SEQ_MAX + (x)) +#define PS_SEQ(x) (x) +#define PL_SEQ(x) (AMS_PS_SEQ_MAX + (x)) +#define AMS_CTRL_SEQ_BASE (AMS_PS_SEQ_MAX * 3) + +#define AMS_CHAN_TEMP(_scan_index, _addr, _name) { \ + .type = IIO_TEMP, \ + .indexed = 1, \ + .address = (_addr), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .event_spec = ams_temp_events, \ + .scan_index = _scan_index, \ + .num_event_specs = ARRAY_SIZE(ams_temp_events), \ + .datasheet_name = _name, \ +} + +#define AMS_CHAN_VOLTAGE(_scan_index, _addr, _alarm, _name) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .address = (_addr), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .event_spec = (_alarm) ? ams_voltage_events : NULL, \ + .scan_index = _scan_index, \ + .num_event_specs = (_alarm) ? ARRAY_SIZE(ams_voltage_events) : 0, \ + .datasheet_name = _name, \ +} + +#define AMS_PS_CHAN_TEMP(_scan_index, _addr, _name) \ + AMS_CHAN_TEMP(PS_SEQ(_scan_index), _addr, _name) +#define AMS_PS_CHAN_VOLTAGE(_scan_index, _addr, _name) \ + AMS_CHAN_VOLTAGE(PS_SEQ(_scan_index), _addr, true, _name) + +#define AMS_PL_CHAN_TEMP(_scan_index, _addr, _name) \ + AMS_CHAN_TEMP(PL_SEQ(_scan_index), _addr, _name) +#define AMS_PL_CHAN_VOLTAGE(_scan_index, _addr, _alarm, _name) \ + AMS_CHAN_VOLTAGE(PL_SEQ(_scan_index), _addr, _alarm, _name) +#define AMS_PL_AUX_CHAN_VOLTAGE(_auxno) \ + AMS_CHAN_VOLTAGE(PL_SEQ(AMS_SEQ(_auxno)), AMS_REG_VAUX(_auxno), false, \ + "VAUX" #_auxno) +#define AMS_CTRL_CHAN_VOLTAGE(_scan_index, _addr, _name) \ + AMS_CHAN_VOLTAGE(PL_SEQ(AMS_SEQ(AMS_SEQ(_scan_index))), _addr, false, \ + _name) + +/** + * struct ams - This structure contains necessary state for xilinx-ams to operate + * @base: physical base address of device + * @ps_base: physical base address of PS device + * @pl_base: physical base address of PL device + * @clk: clocks associated with the device + * @dev: pointer to device struct + * @lock: to handle multiple user interaction + * @intr_lock: to protect interrupt mask values + * @alarm_mask: alarm configuration + * @current_masked_alarm: currently masked due to alarm + * @intr_mask: interrupt configuration + * @ams_unmask_work: re-enables event once the event condition disappears + * + */ +struct ams { + void __iomem *base; + void __iomem *ps_base; + void __iomem *pl_base; + struct clk *clk; + struct device *dev; + struct mutex lock; + spinlock_t intr_lock; + unsigned int alarm_mask; + unsigned int current_masked_alarm; + u64 intr_mask; + struct delayed_work ams_unmask_work; +}; + +static inline void ams_ps_update_reg(struct ams *ams, unsigned int offset, + u32 mask, u32 data) +{ + u32 val, regval; + + val = readl(ams->ps_base + offset); + regval = (val & ~mask) | (data & mask); + writel(regval, ams->ps_base + offset); +} + +static inline void ams_pl_update_reg(struct ams *ams, unsigned int offset, + u32 mask, u32 data) +{ + u32 val, regval; + + val = readl(ams->pl_base + offset); + regval = (val & ~mask) | (data & mask); + writel(regval, ams->pl_base + offset); +} + +static void ams_update_intrmask(struct ams *ams, u64 mask, u64 val) +{ + u32 regval; + + ams->intr_mask = (ams->intr_mask & ~mask) | (val & mask); + + regval = ~(ams->intr_mask | ams->current_masked_alarm); + writel(regval, ams->base + AMS_IER_0); + + regval = ~(FIELD_GET(AMS_ISR1_INTR_MASK, ams->intr_mask)); + writel(regval, ams->base + AMS_IER_1); + + regval = ams->intr_mask | ams->current_masked_alarm; + writel(regval, ams->base + AMS_IDR_0); + + regval = FIELD_GET(AMS_ISR1_INTR_MASK, ams->intr_mask); + writel(regval, ams->base + AMS_IDR_1); +} + +static void ams_disable_all_alarms(struct ams *ams) +{ + /* disable PS module alarm */ + if (ams->ps_base) { + ams_ps_update_reg(ams, AMS_REG_CONFIG1, AMS_REGCFG1_ALARM_MASK, + AMS_REGCFG1_ALARM_MASK); + ams_ps_update_reg(ams, AMS_REG_CONFIG3, AMS_REGCFG3_ALARM_MASK, + AMS_REGCFG3_ALARM_MASK); + } + + /* disable PL module alarm */ + if (ams->pl_base) { + ams_pl_update_reg(ams, AMS_REG_CONFIG1, AMS_REGCFG1_ALARM_MASK, + AMS_REGCFG1_ALARM_MASK); + ams_pl_update_reg(ams, AMS_REG_CONFIG3, AMS_REGCFG3_ALARM_MASK, + AMS_REGCFG3_ALARM_MASK); + } +} + +static void ams_update_ps_alarm(struct ams *ams, unsigned long alarm_mask) +{ + u32 cfg; + u32 val; + + val = FIELD_GET(AMS_ISR0_ALARM_2_TO_0_MASK, alarm_mask); + cfg = ~(FIELD_PREP(AMS_CONF1_ALARM_2_TO_0_MASK, val)); + + val = FIELD_GET(AMS_ISR0_ALARM_6_TO_3_MASK, alarm_mask); + cfg &= ~(FIELD_PREP(AMS_CONF1_ALARM_6_TO_3_MASK, val)); + + ams_ps_update_reg(ams, AMS_REG_CONFIG1, AMS_REGCFG1_ALARM_MASK, cfg); + + val = FIELD_GET(AMS_ISR0_ALARM_12_TO_7_MASK, alarm_mask); + cfg = ~(FIELD_PREP(AMS_CONF1_ALARM_12_TO_7_MASK, val)); + ams_ps_update_reg(ams, AMS_REG_CONFIG3, AMS_REGCFG3_ALARM_MASK, cfg); +} + +static void ams_update_pl_alarm(struct ams *ams, unsigned long alarm_mask) +{ + unsigned long pl_alarm_mask; + u32 cfg; + u32 val; + + pl_alarm_mask = FIELD_GET(AMS_PL_ALARM_MASK, alarm_mask); + + val = FIELD_GET(AMS_ISR0_ALARM_2_TO_0_MASK, pl_alarm_mask); + cfg = ~(FIELD_PREP(AMS_CONF1_ALARM_2_TO_0_MASK, val)); + + val = FIELD_GET(AMS_ISR0_ALARM_6_TO_3_MASK, pl_alarm_mask); + cfg &= ~(FIELD_PREP(AMS_CONF1_ALARM_6_TO_3_MASK, val)); + + ams_pl_update_reg(ams, AMS_REG_CONFIG1, AMS_REGCFG1_ALARM_MASK, cfg); + + val = FIELD_GET(AMS_ISR0_ALARM_12_TO_7_MASK, pl_alarm_mask); + cfg = ~(FIELD_PREP(AMS_CONF1_ALARM_12_TO_7_MASK, val)); + ams_pl_update_reg(ams, AMS_REG_CONFIG3, AMS_REGCFG3_ALARM_MASK, cfg); +} + +static void ams_unmask(struct ams *ams) +{ + unsigned int status, unmask; + + status = readl(ams->base + AMS_ISR_0); + + /* Clear those bits which are not active anymore */ + unmask = (ams->current_masked_alarm ^ status) & ams->current_masked_alarm; + + /* Clear status of disabled alarm */ + unmask |= ams->intr_mask; + + ams->current_masked_alarm &= status; + + /* Also clear those which are masked out anyway */ + ams->current_masked_alarm &= ~ams->intr_mask; + + /* Clear the interrupts before we unmask them */ + writel(unmask, ams->base + AMS_ISR_0); + + ams_update_intrmask(ams, ~AMS_ALARM_MASK, ~AMS_ALARM_MASK); +} + +static void ams_update_alarm(struct ams *ams, unsigned long alarm_mask) +{ + unsigned long flags; + + if (ams->ps_base) + ams_update_ps_alarm(ams, alarm_mask); + + if (ams->pl_base) + ams_update_pl_alarm(ams, alarm_mask); + + spin_lock_irqsave(&ams->intr_lock, flags); + ams_update_intrmask(ams, AMS_ISR0_ALARM_MASK, ~alarm_mask); + ams_unmask(ams); + spin_unlock_irqrestore(&ams->intr_lock, flags); +} + +static void ams_enable_channel_sequence(struct iio_dev *indio_dev) +{ + struct ams *ams = iio_priv(indio_dev); + unsigned long long scan_mask; + int i; + u32 regval; + + /* + * Enable channel sequence. First 22 bits of scan_mask represent + * PS channels, and next remaining bits represent PL channels. + */ + + /* Run calibration of PS & PL as part of the sequence */ + scan_mask = BIT(0) | BIT(AMS_PS_SEQ_MAX); + for (i = 0; i < indio_dev->num_channels; i++) { + const struct iio_chan_spec *chan = &indio_dev->channels[i]; + + if (chan->scan_index < AMS_CTRL_SEQ_BASE) + scan_mask |= BIT_ULL(chan->scan_index); + } + + if (ams->ps_base) { + /* put sysmon in a soft reset to change the sequence */ + ams_ps_update_reg(ams, AMS_REG_CONFIG1, AMS_CONF1_SEQ_MASK, + AMS_CONF1_SEQ_DEFAULT); + + /* configure basic channels */ + regval = FIELD_GET(AMS_REG_SEQ0_MASK, scan_mask); + writel(regval, ams->ps_base + AMS_REG_SEQ_CH0); + + regval = FIELD_GET(AMS_REG_SEQ2_MASK, scan_mask); + writel(regval, ams->ps_base + AMS_REG_SEQ_CH2); + + /* set continuous sequence mode */ + ams_ps_update_reg(ams, AMS_REG_CONFIG1, AMS_CONF1_SEQ_MASK, + AMS_CONF1_SEQ_CONTINUOUS); + } + + if (ams->pl_base) { + /* put sysmon in a soft reset to change the sequence */ + ams_pl_update_reg(ams, AMS_REG_CONFIG1, AMS_CONF1_SEQ_MASK, + AMS_CONF1_SEQ_DEFAULT); + + /* configure basic channels */ + scan_mask = FIELD_GET(AMS_PL_SEQ_MASK, scan_mask); + + regval = FIELD_GET(AMS_REG_SEQ0_MASK, scan_mask); + writel(regval, ams->pl_base + AMS_REG_SEQ_CH0); + + regval = FIELD_GET(AMS_REG_SEQ1_MASK, scan_mask); + writel(regval, ams->pl_base + AMS_REG_SEQ_CH1); + + regval = FIELD_GET(AMS_REG_SEQ2_MASK, scan_mask); + writel(regval, ams->pl_base + AMS_REG_SEQ_CH2); + + /* set continuous sequence mode */ + ams_pl_update_reg(ams, AMS_REG_CONFIG1, AMS_CONF1_SEQ_MASK, + AMS_CONF1_SEQ_CONTINUOUS); + } +} + +static int ams_init_device(struct ams *ams) +{ + u32 expect = AMS_PS_CSTS_PS_READY; + u32 reg, value; + int ret; + + /* reset AMS */ + if (ams->ps_base) { + writel(AMS_PS_RESET_VALUE, ams->ps_base + AMS_VP_VN); + + ret = readl_poll_timeout(ams->base + AMS_PS_CSTS, reg, (reg & expect), + AMS_INIT_POLL_TIME_US, AMS_INIT_TIMEOUT_US); + if (ret) + return ret; + + /* put sysmon in a default state */ + ams_ps_update_reg(ams, AMS_REG_CONFIG1, AMS_CONF1_SEQ_MASK, + AMS_CONF1_SEQ_DEFAULT); + } + + if (ams->pl_base) { + value = readl(ams->base + AMS_PL_CSTS); + if (value == 0) + return 0; + + writel(AMS_PL_RESET_VALUE, ams->pl_base + AMS_VP_VN); + + /* put sysmon in a default state */ + ams_pl_update_reg(ams, AMS_REG_CONFIG1, AMS_CONF1_SEQ_MASK, + AMS_CONF1_SEQ_DEFAULT); + } + + ams_disable_all_alarms(ams); + + /* Disable interrupt */ + ams_update_intrmask(ams, AMS_ALARM_MASK, AMS_ALARM_MASK); + + /* Clear any pending interrupt */ + writel(AMS_ISR0_ALARM_MASK, ams->base + AMS_ISR_0); + writel(AMS_ISR1_ALARM_MASK, ams->base + AMS_ISR_1); + + return 0; +} + +static int ams_read_label(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, char *label) +{ + return sysfs_emit(label, "%s\n", chan->datasheet_name); +} + +static int ams_enable_single_channel(struct ams *ams, unsigned int offset) +{ + u8 channel_num; + + switch (offset) { + case AMS_VCC_PSPLL0: + channel_num = AMS_VCC_PSPLL0_CH; + break; + case AMS_VCC_PSPLL3: + channel_num = AMS_VCC_PSPLL3_CH; + break; + case AMS_VCCINT: + channel_num = AMS_VCCINT_CH; + break; + case AMS_VCCBRAM: + channel_num = AMS_VCCBRAM_CH; + break; + case AMS_VCCAUX: + channel_num = AMS_VCCAUX_CH; + break; + case AMS_PSDDRPLL: + channel_num = AMS_PSDDRPLL_CH; + break; + case AMS_PSINTFPDDR: + channel_num = AMS_PSINTFPDDR_CH; + break; + default: + return -EINVAL; + } + + /* put sysmon in a soft reset to change the sequence */ + ams_ps_update_reg(ams, AMS_REG_CONFIG1, AMS_CONF1_SEQ_MASK, + AMS_CONF1_SEQ_DEFAULT); + + /* write the channel number */ + ams_ps_update_reg(ams, AMS_REG_CONFIG0, AMS_CONF0_CHANNEL_NUM_MASK, + channel_num); + + /* set single channel, sequencer off mode */ + ams_ps_update_reg(ams, AMS_REG_CONFIG1, AMS_CONF1_SEQ_MASK, + AMS_CONF1_SEQ_SINGLE_CHANNEL); + + return 0; +} + +static int ams_read_vcc_reg(struct ams *ams, unsigned int offset, u32 *data) +{ + u32 expect = AMS_ISR1_EOC_MASK; + u32 reg; + int ret; + + ret = ams_enable_single_channel(ams, offset); + if (ret) + return ret; + + /* clear end-of-conversion flag, wait for next conversion to complete */ + writel(expect, ams->base + AMS_ISR_1); + ret = readl_poll_timeout(ams->base + AMS_ISR_1, reg, (reg & expect), + AMS_INIT_POLL_TIME_US, AMS_INIT_TIMEOUT_US); + if (ret) + return ret; + + *data = readl(ams->base + offset); + + return 0; +} + +static int ams_get_ps_scale(int address) +{ + int val; + + switch (address) { + case AMS_SUPPLY1: + case AMS_SUPPLY2: + case AMS_SUPPLY3: + case AMS_SUPPLY4: + case AMS_SUPPLY9: + case AMS_SUPPLY10: + case AMS_VCCAMS: + val = AMS_SUPPLY_SCALE_3VOLT_mV; + break; + case AMS_SUPPLY5: + case AMS_SUPPLY6: + case AMS_SUPPLY7: + case AMS_SUPPLY8: + val = AMS_SUPPLY_SCALE_6VOLT_mV; + break; + default: + val = AMS_SUPPLY_SCALE_1VOLT_mV; + break; + } + + return val; +} + +static int ams_get_pl_scale(struct ams *ams, int address) +{ + int val, regval; + + switch (address) { + case AMS_SUPPLY1: + case AMS_SUPPLY2: + case AMS_SUPPLY3: + case AMS_SUPPLY4: + case AMS_SUPPLY5: + case AMS_SUPPLY6: + case AMS_VCCAMS: + case AMS_VREFP: + case AMS_VREFN: + val = AMS_SUPPLY_SCALE_3VOLT_mV; + break; + case AMS_SUPPLY7: + regval = readl(ams->pl_base + AMS_REG_CONFIG4); + if (FIELD_GET(AMS_VUSER0_MASK, regval)) + val = AMS_SUPPLY_SCALE_6VOLT_mV; + else + val = AMS_SUPPLY_SCALE_3VOLT_mV; + break; + case AMS_SUPPLY8: + regval = readl(ams->pl_base + AMS_REG_CONFIG4); + if (FIELD_GET(AMS_VUSER1_MASK, regval)) + val = AMS_SUPPLY_SCALE_6VOLT_mV; + else + val = AMS_SUPPLY_SCALE_3VOLT_mV; + break; + case AMS_SUPPLY9: + regval = readl(ams->pl_base + AMS_REG_CONFIG4); + if (FIELD_GET(AMS_VUSER2_MASK, regval)) + val = AMS_SUPPLY_SCALE_6VOLT_mV; + else + val = AMS_SUPPLY_SCALE_3VOLT_mV; + break; + case AMS_SUPPLY10: + regval = readl(ams->pl_base + AMS_REG_CONFIG4); + if (FIELD_GET(AMS_VUSER3_MASK, regval)) + val = AMS_SUPPLY_SCALE_6VOLT_mV; + else + val = AMS_SUPPLY_SCALE_3VOLT_mV; + break; + case AMS_VP_VN: + case AMS_REG_VAUX(0) ... AMS_REG_VAUX(15): + val = AMS_SUPPLY_SCALE_1VOLT_mV; + break; + default: + val = AMS_SUPPLY_SCALE_1VOLT_mV; + break; + } + + return val; +} + +static int ams_get_ctrl_scale(int address) +{ + int val; + + switch (address) { + case AMS_VCC_PSPLL0: + case AMS_VCC_PSPLL3: + case AMS_VCCINT: + case AMS_VCCBRAM: + case AMS_VCCAUX: + case AMS_PSDDRPLL: + case AMS_PSINTFPDDR: + val = AMS_SUPPLY_SCALE_3VOLT_mV; + break; + default: + val = AMS_SUPPLY_SCALE_1VOLT_mV; + break; + } + + return val; +} + +static int ams_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ams *ams = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + guard(mutex)(&ams->lock); + if (chan->scan_index >= AMS_CTRL_SEQ_BASE) { + ret = ams_read_vcc_reg(ams, chan->address, val); + if (ret) + return ret; + ams_enable_channel_sequence(indio_dev); + } else if (chan->scan_index >= AMS_PS_SEQ_MAX) + *val = readl(ams->pl_base + chan->address); + else + *val = readl(ams->ps_base + chan->address); + + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->scan_index < AMS_PS_SEQ_MAX) + *val = ams_get_ps_scale(chan->address); + else if (chan->scan_index >= AMS_PS_SEQ_MAX && + chan->scan_index < AMS_CTRL_SEQ_BASE) + *val = ams_get_pl_scale(ams, chan->address); + else + *val = ams_get_ctrl_scale(chan->address); + + *val2 = AMS_SUPPLY_SCALE_DIV_BIT; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_TEMP: + *val = AMS_TEMP_SCALE; + *val2 = AMS_TEMP_SCALE_DIV_BIT; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + /* Only the temperature channel has an offset */ + *val = AMS_TEMP_OFFSET; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +struct ams_alarm_map { + enum ams_ps_pl_seq scan_index; + unsigned int base_offset; +}; + +/* + * Array index matches enum ams_alarm_bit. + * Entries with base_offset == AMS_ALARM_NONE are unused/invalid + * (e.g. RESERVED) and must be skipped. + */ +static const struct ams_alarm_map alarm_map[] = { + [AMS_ALARM_BIT_TEMP] = { AMS_SEQ_TEMP, AMS_ALARM_TEMP }, + [AMS_ALARM_BIT_SUPPLY1] = { AMS_SEQ_SUPPLY1, AMS_ALARM_SUPPLY1 }, + [AMS_ALARM_BIT_SUPPLY2] = { AMS_SEQ_SUPPLY2, AMS_ALARM_SUPPLY2 }, + [AMS_ALARM_BIT_SUPPLY3] = { AMS_SEQ_SUPPLY3, AMS_ALARM_SUPPLY3 }, + [AMS_ALARM_BIT_SUPPLY4] = { AMS_SEQ_SUPPLY4, AMS_ALARM_SUPPLY4 }, + [AMS_ALARM_BIT_SUPPLY5] = { AMS_SEQ_SUPPLY5, AMS_ALARM_SUPPLY5 }, + [AMS_ALARM_BIT_SUPPLY6] = { AMS_SEQ_SUPPLY6, AMS_ALARM_SUPPLY6 }, + [AMS_ALARM_BIT_RESERVED] = { 0, AMS_ALARM_NONE }, + [AMS_ALARM_BIT_SUPPLY7] = { AMS_SEQ_SUPPLY7, AMS_ALARM_SUPPLY7 }, + [AMS_ALARM_BIT_SUPPLY8] = { AMS_SEQ_SUPPLY8, AMS_ALARM_SUPPLY8 }, + [AMS_ALARM_BIT_SUPPLY9] = { AMS_SEQ_SUPPLY9, AMS_ALARM_SUPPLY9 }, + [AMS_ALARM_BIT_SUPPLY10] = { AMS_SEQ_SUPPLY10, AMS_ALARM_SUPPLY10 }, + [AMS_ALARM_BIT_VCCAMS] = { AMS_SEQ_VCCAMS, AMS_ALARM_VCCAMS }, + [AMS_ALARM_BIT_TEMP_REMOTE] = { AMS_SEQ_TEMP_REMOTE, AMS_ALARM_TEMP_REMOTE }, +}; + +static int ams_scan_index_to_event(int scan_index) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(alarm_map); i++) { + if (alarm_map[i].base_offset == AMS_ALARM_NONE) + continue; + + if (alarm_map[i].scan_index == scan_index) + return i; + } + + return -EINVAL; +} + +static int ams_get_alarm_offset(int scan_index, enum iio_event_direction dir) +{ + int offset, event; + + if (scan_index >= AMS_PS_SEQ_MAX) + scan_index -= AMS_PS_SEQ_MAX; + + if (dir == IIO_EV_DIR_FALLING) { + if (scan_index < AMS_SEQ_SUPPLY7) + offset = AMS_ALARM_THRESHOLD_OFF_10; + else + offset = AMS_ALARM_THRESHOLD_OFF_20; + } else { + offset = 0; + } + + event = ams_scan_index_to_event(scan_index); + if (event < 0 || alarm_map[event].base_offset == AMS_ALARM_NONE) + return 0; + + return alarm_map[event].base_offset + offset; +} + +static const struct iio_chan_spec *ams_event_to_channel(struct iio_dev *dev, + u32 event) +{ + int scan_index = 0, i; + + if (event >= AMS_PL_ALARM_START) { + event -= AMS_PL_ALARM_START; + scan_index = AMS_PS_SEQ_MAX; + } + + if (event >= ARRAY_SIZE(alarm_map)) + return NULL; + + if (alarm_map[event].base_offset == AMS_ALARM_NONE) + return NULL; + + scan_index += alarm_map[event].scan_index; + + for (i = 0; i < dev->num_channels; i++) + if (dev->channels[i].scan_index == scan_index) + break; + + if (i == dev->num_channels) + return NULL; + + return &dev->channels[i]; +} + +static int ams_get_alarm_mask(int scan_index) +{ + int bit = 0, event; + + if (scan_index >= AMS_PS_SEQ_MAX) { + bit = AMS_PL_ALARM_START; + scan_index -= AMS_PS_SEQ_MAX; + } + + event = ams_scan_index_to_event(scan_index); + if (event < 0) + return 0; + + return BIT(event + bit); +} + +static int ams_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct ams *ams = iio_priv(indio_dev); + + return !!(ams->alarm_mask & ams_get_alarm_mask(chan->scan_index)); +} + +static int ams_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct ams *ams = iio_priv(indio_dev); + unsigned int alarm; + + alarm = ams_get_alarm_mask(chan->scan_index); + + guard(mutex)(&ams->lock); + + if (state) + ams->alarm_mask |= alarm; + else + ams->alarm_mask &= ~alarm; + + ams_update_alarm(ams, ams->alarm_mask); + + return 0; +} + +static int ams_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, int *val2) +{ + struct ams *ams = iio_priv(indio_dev); + unsigned int offset = ams_get_alarm_offset(chan->scan_index, dir); + + guard(mutex)(&ams->lock); + + if (chan->scan_index >= AMS_PS_SEQ_MAX) + *val = readl(ams->pl_base + offset); + else + *val = readl(ams->ps_base + offset); + + return IIO_VAL_INT; +} + +static int ams_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, int val2) +{ + struct ams *ams = iio_priv(indio_dev); + unsigned int offset; + + guard(mutex)(&ams->lock); + + /* Set temperature channel threshold to direct threshold */ + if (chan->type == IIO_TEMP) { + offset = ams_get_alarm_offset(chan->scan_index, IIO_EV_DIR_FALLING); + + if (chan->scan_index >= AMS_PS_SEQ_MAX) + ams_pl_update_reg(ams, offset, + AMS_ALARM_THR_DIRECT_MASK, + AMS_ALARM_THR_DIRECT_MASK); + else + ams_ps_update_reg(ams, offset, + AMS_ALARM_THR_DIRECT_MASK, + AMS_ALARM_THR_DIRECT_MASK); + } + + offset = ams_get_alarm_offset(chan->scan_index, dir); + if (chan->scan_index >= AMS_PS_SEQ_MAX) + writel(val, ams->pl_base + offset); + else + writel(val, ams->ps_base + offset); + + return 0; +} + +static void ams_handle_event(struct iio_dev *indio_dev, u32 event) +{ + const struct iio_chan_spec *chan; + + chan = ams_event_to_channel(indio_dev, event); + if (!chan) + return; + + if (chan->type == IIO_TEMP) { + /* + * The temperature channel only supports over-temperature + * events. + */ + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(chan->type, chan->channel, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + } else { + /* + * For other channels we don't know whether it is a upper or + * lower threshold event. Userspace will have to check the + * channel value if it wants to know. + */ + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(chan->type, chan->channel, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + } +} + +static void ams_handle_events(struct iio_dev *indio_dev, unsigned long events) +{ + unsigned int bit; + + for_each_set_bit(bit, &events, AMS_NO_OF_ALARMS) + ams_handle_event(indio_dev, bit); +} + +/** + * ams_unmask_worker - ams alarm interrupt unmask worker + * @work: work to be done + * + * The ZynqMP threshold interrupts are level sensitive. Since we can't make the + * threshold condition go way from within the interrupt handler, this means as + * soon as a threshold condition is present we would enter the interrupt handler + * again and again. To work around this we mask all active threshold interrupts + * in the interrupt handler and start a timer. In this timer we poll the + * interrupt status and only if the interrupt is inactive we unmask it again. + */ +static void ams_unmask_worker(struct work_struct *work) +{ + struct ams *ams = container_of(work, struct ams, ams_unmask_work.work); + + spin_lock_irq(&ams->intr_lock); + ams_unmask(ams); + spin_unlock_irq(&ams->intr_lock); + + /* If still pending some alarm re-trigger the timer */ + if (ams->current_masked_alarm) + schedule_delayed_work(&ams->ams_unmask_work, + msecs_to_jiffies(AMS_UNMASK_TIMEOUT_MS)); +} + +static irqreturn_t ams_irq(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct ams *ams = iio_priv(indio_dev); + u32 isr0; + + spin_lock(&ams->intr_lock); + + isr0 = readl(ams->base + AMS_ISR_0); + + /* Only process alarms that are not masked */ + isr0 &= ~((ams->intr_mask & AMS_ISR0_ALARM_MASK) | ams->current_masked_alarm); + if (!isr0) { + spin_unlock(&ams->intr_lock); + return IRQ_NONE; + } + + /* Clear interrupt */ + writel(isr0, ams->base + AMS_ISR_0); + + /* Mask the alarm interrupts until cleared */ + ams->current_masked_alarm |= isr0; + ams_update_intrmask(ams, ~AMS_ALARM_MASK, ~AMS_ALARM_MASK); + + ams_handle_events(indio_dev, isr0); + + schedule_delayed_work(&ams->ams_unmask_work, + msecs_to_jiffies(AMS_UNMASK_TIMEOUT_MS)); + + spin_unlock(&ams->intr_lock); + + return IRQ_HANDLED; +} + +static const struct iio_event_spec ams_temp_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | BIT(IIO_EV_INFO_VALUE), + }, +}; + +static const struct iio_event_spec ams_voltage_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec ams_ps_channels[] = { + AMS_PS_CHAN_TEMP(AMS_SEQ_TEMP, AMS_TEMP, "Temp_LPD"), + AMS_PS_CHAN_TEMP(AMS_SEQ_TEMP_REMOTE, AMS_TEMP_REMOTE, "Temp_FPD"), + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY1, AMS_SUPPLY1, "VCC_PSINTLP"), + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY2, AMS_SUPPLY2, "VCC_PSINTFP"), + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY3, AMS_SUPPLY3, "VCC_PSAUX"), + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY4, AMS_SUPPLY4, "VCC_PSDDR"), + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY5, AMS_SUPPLY5, "VCC_PSIO3"), + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY6, AMS_SUPPLY6, "VCC_PSIO0"), + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY7, AMS_SUPPLY7, "VCC_PSIO1"), + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY8, AMS_SUPPLY8, "VCC_PSIO2"), + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY9, AMS_SUPPLY9, "PS_MGTRAVCC"), + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY10, AMS_SUPPLY10, "PS_MGTRAVTT"), + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_VCCAMS, AMS_VCCAMS, "VCC_PSADC"), +}; + +static const struct iio_chan_spec ams_pl_channels[] = { + AMS_PL_CHAN_TEMP(AMS_SEQ_TEMP, AMS_TEMP, "Temp_PL"), + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY1, AMS_SUPPLY1, true, "VCCINT"), + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY2, AMS_SUPPLY2, true, "VCCAUX"), + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_VREFP, AMS_VREFP, false, "VREFP"), + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_VREFN, AMS_VREFN, false, "VREFN"), + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY3, AMS_SUPPLY3, true, "VCCBRAM"), + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY4, AMS_SUPPLY4, true, "VCC_PSINTLP"), + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY5, AMS_SUPPLY5, true, "VCC_PSINTFP"), + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY6, AMS_SUPPLY6, true, "VCC_PSAUX"), + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_VCCAMS, AMS_VCCAMS, true, "VCCAMS"), + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_VP_VN, AMS_VP_VN, false, "VP_VN"), + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY7, AMS_SUPPLY7, true, "VUser0"), + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY8, AMS_SUPPLY8, true, "VUser1"), + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY9, AMS_SUPPLY9, true, "VUser2"), + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY10, AMS_SUPPLY10, true, "VUser3"), + AMS_PL_AUX_CHAN_VOLTAGE(0), + AMS_PL_AUX_CHAN_VOLTAGE(1), + AMS_PL_AUX_CHAN_VOLTAGE(2), + AMS_PL_AUX_CHAN_VOLTAGE(3), + AMS_PL_AUX_CHAN_VOLTAGE(4), + AMS_PL_AUX_CHAN_VOLTAGE(5), + AMS_PL_AUX_CHAN_VOLTAGE(6), + AMS_PL_AUX_CHAN_VOLTAGE(7), + AMS_PL_AUX_CHAN_VOLTAGE(8), + AMS_PL_AUX_CHAN_VOLTAGE(9), + AMS_PL_AUX_CHAN_VOLTAGE(10), + AMS_PL_AUX_CHAN_VOLTAGE(11), + AMS_PL_AUX_CHAN_VOLTAGE(12), + AMS_PL_AUX_CHAN_VOLTAGE(13), + AMS_PL_AUX_CHAN_VOLTAGE(14), + AMS_PL_AUX_CHAN_VOLTAGE(15), +}; + +static const struct iio_chan_spec ams_ctrl_channels[] = { + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_VCC_PSPLL, AMS_VCC_PSPLL0, "VCC_PSPLL"), + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_VCC_PSBATT, AMS_VCC_PSPLL3, "VCC_PSBATT"), + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_VCCINT, AMS_VCCINT, "VCCINT"), + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_VCCBRAM, AMS_VCCBRAM, "VCCBRAM"), + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_VCCAUX, AMS_VCCAUX, "VCCAUX"), + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_PSDDRPLL, AMS_PSDDRPLL, "VCC_PSDDR_PLL"), + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_INTDDR, AMS_PSINTFPDDR, "VCC_PSINTFP_DDR"), +}; + +static int ams_get_ext_chan(struct fwnode_handle *chan_node, + struct iio_chan_spec *channels, int num_channels) +{ + struct iio_chan_spec *chan; + struct fwnode_handle *child; + unsigned int reg, ext_chan; + int ret; + + fwnode_for_each_child_node(chan_node, child) { + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret || reg > AMS_PL_MAX_EXT_CHANNEL + 30) + continue; + + chan = &channels[num_channels]; + ext_chan = reg + AMS_PL_MAX_FIXED_CHANNEL - 30; + memcpy(chan, &ams_pl_channels[ext_chan], sizeof(*channels)); + + if (fwnode_property_read_bool(child, "xlnx,bipolar")) + chan->scan_type.sign = 's'; + + num_channels++; + } + + return num_channels; +} + +static void ams_iounmap_ps(void *data) +{ + struct ams *ams = data; + + iounmap(ams->ps_base); +} + +static void ams_iounmap_pl(void *data) +{ + struct ams *ams = data; + + iounmap(ams->pl_base); +} + +static int ams_init_module(struct iio_dev *indio_dev, + struct fwnode_handle *fwnode, + struct iio_chan_spec *channels) +{ + struct device *dev = indio_dev->dev.parent; + struct ams *ams = iio_priv(indio_dev); + int num_channels = 0; + int ret; + + if (fwnode_device_is_compatible(fwnode, "xlnx,zynqmp-ams-ps")) { + ams->ps_base = fwnode_iomap(fwnode, 0); + if (!ams->ps_base) + return -ENXIO; + ret = devm_add_action_or_reset(dev, ams_iounmap_ps, ams); + if (ret < 0) + return ret; + + /* add PS channels to iio device channels */ + memcpy(channels, ams_ps_channels, sizeof(ams_ps_channels)); + num_channels = ARRAY_SIZE(ams_ps_channels); + } else if (fwnode_device_is_compatible(fwnode, "xlnx,zynqmp-ams-pl")) { + ams->pl_base = fwnode_iomap(fwnode, 0); + if (!ams->pl_base) + return -ENXIO; + + ret = devm_add_action_or_reset(dev, ams_iounmap_pl, ams); + if (ret < 0) + return ret; + + /* Copy only first 10 fix channels */ + memcpy(channels, ams_pl_channels, AMS_PL_MAX_FIXED_CHANNEL * sizeof(*channels)); + num_channels += AMS_PL_MAX_FIXED_CHANNEL; + num_channels = ams_get_ext_chan(fwnode, channels, + num_channels); + } else if (fwnode_device_is_compatible(fwnode, "xlnx,zynqmp-ams")) { + /* add AMS channels to iio device channels */ + memcpy(channels, ams_ctrl_channels, sizeof(ams_ctrl_channels)); + num_channels += ARRAY_SIZE(ams_ctrl_channels); + } else { + return -EINVAL; + } + + return num_channels; +} + +static int ams_parse_firmware(struct iio_dev *indio_dev) +{ + struct ams *ams = iio_priv(indio_dev); + struct iio_chan_spec *ams_channels, *dev_channels; + struct device *dev = indio_dev->dev.parent; + struct fwnode_handle *fwnode = dev_fwnode(dev); + size_t ams_size; + int ret, ch_cnt = 0, i, rising_off, falling_off; + unsigned int num_channels = 0; + + ams_size = ARRAY_SIZE(ams_ps_channels) + ARRAY_SIZE(ams_pl_channels) + + ARRAY_SIZE(ams_ctrl_channels); + + /* Initialize buffer for channel specification */ + ams_channels = devm_kcalloc(dev, ams_size, sizeof(*ams_channels), GFP_KERNEL); + if (!ams_channels) + return -ENOMEM; + + if (fwnode_device_is_available(fwnode)) { + ret = ams_init_module(indio_dev, fwnode, ams_channels); + if (ret < 0) + return ret; + + num_channels += ret; + } + + device_for_each_child_node_scoped(dev, child) { + ret = ams_init_module(indio_dev, child, ams_channels + num_channels); + if (ret < 0) + return ret; + + num_channels += ret; + } + + for (i = 0; i < num_channels; i++) { + ams_channels[i].channel = ch_cnt++; + + if (ams_channels[i].scan_index < AMS_CTRL_SEQ_BASE) { + /* set threshold to max and min for each channel */ + falling_off = + ams_get_alarm_offset(ams_channels[i].scan_index, + IIO_EV_DIR_FALLING); + rising_off = + ams_get_alarm_offset(ams_channels[i].scan_index, + IIO_EV_DIR_RISING); + if (ams_channels[i].scan_index >= AMS_PS_SEQ_MAX) { + writel(AMS_ALARM_THR_MIN, + ams->pl_base + falling_off); + writel(AMS_ALARM_THR_MAX, + ams->pl_base + rising_off); + } else { + writel(AMS_ALARM_THR_MIN, + ams->ps_base + falling_off); + writel(AMS_ALARM_THR_MAX, + ams->ps_base + rising_off); + } + } + } + + dev_channels = devm_krealloc_array(dev, ams_channels, num_channels, + sizeof(*dev_channels), GFP_KERNEL); + if (!dev_channels) + return -ENOMEM; + + indio_dev->channels = dev_channels; + indio_dev->num_channels = num_channels; + + return 0; +} + +static const struct iio_info iio_ams_info = { + .read_label = ams_read_label, + .read_raw = &ams_read_raw, + .read_event_config = &ams_read_event_config, + .write_event_config = &ams_write_event_config, + .read_event_value = &ams_read_event_value, + .write_event_value = &ams_write_event_value, +}; + +static const struct of_device_id ams_of_match_table[] = { + { .compatible = "xlnx,zynqmp-ams" }, + { } +}; +MODULE_DEVICE_TABLE(of, ams_of_match_table); + +static int ams_probe(struct platform_device *pdev) +{ + struct iio_dev *indio_dev; + struct ams *ams; + int ret; + int irq; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*ams)); + if (!indio_dev) + return -ENOMEM; + + ams = iio_priv(indio_dev); + mutex_init(&ams->lock); + spin_lock_init(&ams->intr_lock); + + indio_dev->name = "xilinx-ams"; + + indio_dev->info = &iio_ams_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ams->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ams->base)) + return PTR_ERR(ams->base); + + ams->clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(ams->clk)) + return PTR_ERR(ams->clk); + + ret = devm_delayed_work_autocancel(&pdev->dev, &ams->ams_unmask_work, + ams_unmask_worker); + if (ret < 0) + return ret; + + ret = ams_parse_firmware(indio_dev); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failure in parsing DT\n"); + + ret = ams_init_device(ams); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to initialize AMS\n"); + + ams_enable_channel_sequence(indio_dev); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(&pdev->dev, irq, &ams_irq, 0, "ams-irq", + indio_dev); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "failed to register interrupt\n"); + + platform_set_drvdata(pdev, indio_dev); + + return devm_iio_device_register(&pdev->dev, indio_dev); +} + +static int ams_suspend(struct device *dev) +{ + struct ams *ams = iio_priv(dev_get_drvdata(dev)); + + clk_disable_unprepare(ams->clk); + + return 0; +} + +static int ams_resume(struct device *dev) +{ + struct ams *ams = iio_priv(dev_get_drvdata(dev)); + + return clk_prepare_enable(ams->clk); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ams_pm_ops, ams_suspend, ams_resume); + +static struct platform_driver ams_driver = { + .probe = ams_probe, + .driver = { + .name = "xilinx-ams", + .pm = pm_sleep_ptr(&ams_pm_ops), + .of_match_table = ams_of_match_table, + }, +}; +module_platform_driver(ams_driver); + +MODULE_DESCRIPTION("Xilinx AMS driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Xilinx, Inc."); diff --git a/drivers/iio/adc/xilinx-xadc-core.c b/drivers/iio/adc/xilinx-xadc-core.c new file mode 100644 index 000000000..cab66bb8c --- /dev/null +++ b/drivers/iio/adc/xilinx-xadc-core.c @@ -0,0 +1,1459 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Xilinx XADC driver + * + * Copyright 2013-2014 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Documentation for the parts can be found at: + * - XADC hardmacro: Xilinx UG480 + * - ZYNQ XADC interface: Xilinx UG585 + * - AXI XADC interface: Xilinx PG019 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "xilinx-xadc.h" + +static const unsigned int XADC_ZYNQ_UNMASK_TIMEOUT = 500; + +/* ZYNQ register definitions */ +#define XADC_ZYNQ_REG_CFG 0x00 +#define XADC_ZYNQ_REG_INTSTS 0x04 +#define XADC_ZYNQ_REG_INTMSK 0x08 +#define XADC_ZYNQ_REG_STATUS 0x0c +#define XADC_ZYNQ_REG_CFIFO 0x10 +#define XADC_ZYNQ_REG_DFIFO 0x14 +#define XADC_ZYNQ_REG_CTL 0x18 + +#define XADC_ZYNQ_CFG_ENABLE BIT(31) +#define XADC_ZYNQ_CFG_CFIFOTH_MASK (0xf << 20) +#define XADC_ZYNQ_CFG_CFIFOTH_OFFSET 20 +#define XADC_ZYNQ_CFG_DFIFOTH_MASK (0xf << 16) +#define XADC_ZYNQ_CFG_DFIFOTH_OFFSET 16 +#define XADC_ZYNQ_CFG_WEDGE BIT(13) +#define XADC_ZYNQ_CFG_REDGE BIT(12) +#define XADC_ZYNQ_CFG_TCKRATE_MASK (0x3 << 8) +#define XADC_ZYNQ_CFG_TCKRATE_DIV2 (0x0 << 8) +#define XADC_ZYNQ_CFG_TCKRATE_DIV4 (0x1 << 8) +#define XADC_ZYNQ_CFG_TCKRATE_DIV8 (0x2 << 8) +#define XADC_ZYNQ_CFG_TCKRATE_DIV16 (0x3 << 8) +#define XADC_ZYNQ_CFG_IGAP_MASK 0x1f +#define XADC_ZYNQ_CFG_IGAP(x) (x) + +#define XADC_ZYNQ_INT_CFIFO_LTH BIT(9) +#define XADC_ZYNQ_INT_DFIFO_GTH BIT(8) +#define XADC_ZYNQ_INT_ALARM_MASK 0xff +#define XADC_ZYNQ_INT_ALARM_OFFSET 0 + +#define XADC_ZYNQ_STATUS_CFIFO_LVL_MASK (0xf << 16) +#define XADC_ZYNQ_STATUS_CFIFO_LVL_OFFSET 16 +#define XADC_ZYNQ_STATUS_DFIFO_LVL_MASK (0xf << 12) +#define XADC_ZYNQ_STATUS_DFIFO_LVL_OFFSET 12 +#define XADC_ZYNQ_STATUS_CFIFOF BIT(11) +#define XADC_ZYNQ_STATUS_CFIFOE BIT(10) +#define XADC_ZYNQ_STATUS_DFIFOF BIT(9) +#define XADC_ZYNQ_STATUS_DFIFOE BIT(8) +#define XADC_ZYNQ_STATUS_OT BIT(7) +#define XADC_ZYNQ_STATUS_ALM(x) BIT(x) + +#define XADC_ZYNQ_CTL_RESET BIT(4) + +#define XADC_ZYNQ_CMD_NOP 0x00 +#define XADC_ZYNQ_CMD_READ 0x01 +#define XADC_ZYNQ_CMD_WRITE 0x02 + +#define XADC_ZYNQ_CMD(cmd, addr, data) (((cmd) << 26) | ((addr) << 16) | (data)) + +/* AXI register definitions */ +#define XADC_AXI_REG_RESET 0x00 +#define XADC_AXI_REG_STATUS 0x04 +#define XADC_AXI_REG_ALARM_STATUS 0x08 +#define XADC_AXI_REG_CONVST 0x0c +#define XADC_AXI_REG_XADC_RESET 0x10 +#define XADC_AXI_REG_GIER 0x5c +#define XADC_AXI_REG_IPISR 0x60 +#define XADC_AXI_REG_IPIER 0x68 + +/* 7 Series */ +#define XADC_7S_AXI_ADC_REG_OFFSET 0x200 + +/* UltraScale */ +#define XADC_US_AXI_ADC_REG_OFFSET 0x400 + +#define XADC_AXI_RESET_MAGIC 0xa +#define XADC_AXI_GIER_ENABLE BIT(31) + +#define XADC_AXI_INT_EOS BIT(4) +#define XADC_AXI_INT_ALARM_MASK 0x3c0f + +#define XADC_FLAGS_BUFFERED BIT(0) +#define XADC_FLAGS_IRQ_OPTIONAL BIT(1) + +/* + * The XADC hardware supports a samplerate of up to 1MSPS. Unfortunately it does + * not have a hardware FIFO. Which means an interrupt is generated for each + * conversion sequence. At 1MSPS sample rate the CPU in ZYNQ7000 is completely + * overloaded by the interrupts that it soft-lockups. For this reason the driver + * limits the maximum samplerate 150kSPS. At this rate the CPU is fairly busy, + * but still responsive. + */ +#define XADC_MAX_SAMPLERATE 150000 + +static void xadc_write_reg(struct xadc *xadc, unsigned int reg, + uint32_t val) +{ + writel(val, xadc->base + reg); +} + +static void xadc_read_reg(struct xadc *xadc, unsigned int reg, + uint32_t *val) +{ + *val = readl(xadc->base + reg); +} + +/* + * The ZYNQ interface uses two asynchronous FIFOs for communication with the + * XADC. Reads and writes to the XADC register are performed by submitting a + * request to the command FIFO (CFIFO), once the request has been completed the + * result can be read from the data FIFO (DFIFO). The method currently used in + * this driver is to submit the request for a read/write operation, then go to + * sleep and wait for an interrupt that signals that a response is available in + * the data FIFO. + */ + +static void xadc_zynq_write_fifo(struct xadc *xadc, uint32_t *cmd, + unsigned int n) +{ + unsigned int i; + + for (i = 0; i < n; i++) + xadc_write_reg(xadc, XADC_ZYNQ_REG_CFIFO, cmd[i]); +} + +static void xadc_zynq_drain_fifo(struct xadc *xadc) +{ + uint32_t status, tmp; + + xadc_read_reg(xadc, XADC_ZYNQ_REG_STATUS, &status); + + while (!(status & XADC_ZYNQ_STATUS_DFIFOE)) { + xadc_read_reg(xadc, XADC_ZYNQ_REG_DFIFO, &tmp); + xadc_read_reg(xadc, XADC_ZYNQ_REG_STATUS, &status); + } +} + +static void xadc_zynq_update_intmsk(struct xadc *xadc, unsigned int mask, + unsigned int val) +{ + xadc->zynq_intmask &= ~mask; + xadc->zynq_intmask |= val; + + xadc_write_reg(xadc, XADC_ZYNQ_REG_INTMSK, + xadc->zynq_intmask | xadc->zynq_masked_alarm); +} + +static int xadc_zynq_write_adc_reg(struct xadc *xadc, unsigned int reg, + uint16_t val) +{ + uint32_t cmd[1]; + uint32_t tmp; + int ret; + + spin_lock_irq(&xadc->lock); + xadc_zynq_update_intmsk(xadc, XADC_ZYNQ_INT_DFIFO_GTH, + XADC_ZYNQ_INT_DFIFO_GTH); + + reinit_completion(&xadc->completion); + + cmd[0] = XADC_ZYNQ_CMD(XADC_ZYNQ_CMD_WRITE, reg, val); + xadc_zynq_write_fifo(xadc, cmd, ARRAY_SIZE(cmd)); + xadc_read_reg(xadc, XADC_ZYNQ_REG_CFG, &tmp); + tmp &= ~XADC_ZYNQ_CFG_DFIFOTH_MASK; + tmp |= 0 << XADC_ZYNQ_CFG_DFIFOTH_OFFSET; + xadc_write_reg(xadc, XADC_ZYNQ_REG_CFG, tmp); + + xadc_zynq_update_intmsk(xadc, XADC_ZYNQ_INT_DFIFO_GTH, 0); + spin_unlock_irq(&xadc->lock); + + ret = wait_for_completion_interruptible_timeout(&xadc->completion, HZ); + if (ret == 0) + ret = -EIO; + else + ret = 0; + + xadc_read_reg(xadc, XADC_ZYNQ_REG_DFIFO, &tmp); + + return ret; +} + +static int xadc_zynq_read_adc_reg(struct xadc *xadc, unsigned int reg, + uint16_t *val) +{ + uint32_t cmd[2]; + uint32_t resp, tmp; + int ret; + + cmd[0] = XADC_ZYNQ_CMD(XADC_ZYNQ_CMD_READ, reg, 0); + cmd[1] = XADC_ZYNQ_CMD(XADC_ZYNQ_CMD_NOP, 0, 0); + + spin_lock_irq(&xadc->lock); + xadc_zynq_update_intmsk(xadc, XADC_ZYNQ_INT_DFIFO_GTH, + XADC_ZYNQ_INT_DFIFO_GTH); + xadc_zynq_drain_fifo(xadc); + reinit_completion(&xadc->completion); + + xadc_zynq_write_fifo(xadc, cmd, ARRAY_SIZE(cmd)); + xadc_read_reg(xadc, XADC_ZYNQ_REG_CFG, &tmp); + tmp &= ~XADC_ZYNQ_CFG_DFIFOTH_MASK; + tmp |= 1 << XADC_ZYNQ_CFG_DFIFOTH_OFFSET; + xadc_write_reg(xadc, XADC_ZYNQ_REG_CFG, tmp); + + xadc_zynq_update_intmsk(xadc, XADC_ZYNQ_INT_DFIFO_GTH, 0); + spin_unlock_irq(&xadc->lock); + ret = wait_for_completion_interruptible_timeout(&xadc->completion, HZ); + if (ret == 0) + ret = -EIO; + if (ret < 0) + return ret; + + xadc_read_reg(xadc, XADC_ZYNQ_REG_DFIFO, &resp); + xadc_read_reg(xadc, XADC_ZYNQ_REG_DFIFO, &resp); + + *val = resp & 0xffff; + + return 0; +} + +static unsigned int xadc_zynq_transform_alarm(unsigned int alarm) +{ + return ((alarm & 0x80) >> 4) | + ((alarm & 0x78) << 1) | + (alarm & 0x07); +} + +/* + * The ZYNQ threshold interrupts are level sensitive. Since we can't make the + * threshold condition go way from within the interrupt handler, this means as + * soon as a threshold condition is present we would enter the interrupt handler + * again and again. To work around this we mask all active thresholds interrupts + * in the interrupt handler and start a timer. In this timer we poll the + * interrupt status and only if the interrupt is inactive we unmask it again. + */ +static void xadc_zynq_unmask_worker(struct work_struct *work) +{ + struct xadc *xadc = container_of(work, struct xadc, zynq_unmask_work.work); + unsigned int misc_sts, unmask; + + xadc_read_reg(xadc, XADC_ZYNQ_REG_STATUS, &misc_sts); + + misc_sts &= XADC_ZYNQ_INT_ALARM_MASK; + + spin_lock_irq(&xadc->lock); + + /* Clear those bits which are not active anymore */ + unmask = (xadc->zynq_masked_alarm ^ misc_sts) & xadc->zynq_masked_alarm; + xadc->zynq_masked_alarm &= misc_sts; + + /* Also clear those which are masked out anyway */ + xadc->zynq_masked_alarm &= ~xadc->zynq_intmask; + + /* Clear the interrupts before we unmask them */ + xadc_write_reg(xadc, XADC_ZYNQ_REG_INTSTS, unmask); + + xadc_zynq_update_intmsk(xadc, 0, 0); + + spin_unlock_irq(&xadc->lock); + + /* if still pending some alarm re-trigger the timer */ + if (xadc->zynq_masked_alarm) { + schedule_delayed_work(&xadc->zynq_unmask_work, + msecs_to_jiffies(XADC_ZYNQ_UNMASK_TIMEOUT)); + } + +} + +static irqreturn_t xadc_zynq_interrupt_handler(int irq, void *devid) +{ + struct iio_dev *indio_dev = devid; + struct xadc *xadc = iio_priv(indio_dev); + uint32_t status; + + xadc_read_reg(xadc, XADC_ZYNQ_REG_INTSTS, &status); + + status &= ~(xadc->zynq_intmask | xadc->zynq_masked_alarm); + + if (!status) + return IRQ_NONE; + + spin_lock(&xadc->lock); + + xadc_write_reg(xadc, XADC_ZYNQ_REG_INTSTS, status); + + if (status & XADC_ZYNQ_INT_DFIFO_GTH) { + xadc_zynq_update_intmsk(xadc, XADC_ZYNQ_INT_DFIFO_GTH, + XADC_ZYNQ_INT_DFIFO_GTH); + complete(&xadc->completion); + } + + status &= XADC_ZYNQ_INT_ALARM_MASK; + if (status) { + xadc->zynq_masked_alarm |= status; + /* + * mask the current event interrupt, + * unmask it when the interrupt is no more active. + */ + xadc_zynq_update_intmsk(xadc, 0, 0); + + xadc_handle_events(indio_dev, + xadc_zynq_transform_alarm(status)); + + /* unmask the required interrupts in timer. */ + schedule_delayed_work(&xadc->zynq_unmask_work, + msecs_to_jiffies(XADC_ZYNQ_UNMASK_TIMEOUT)); + } + spin_unlock(&xadc->lock); + + return IRQ_HANDLED; +} + +#define XADC_ZYNQ_TCK_RATE_MAX 50000000 +#define XADC_ZYNQ_IGAP_DEFAULT 20 +#define XADC_ZYNQ_PCAP_RATE_MAX 200000000 + +static int xadc_zynq_setup(struct platform_device *pdev, + struct iio_dev *indio_dev, int irq) +{ + struct xadc *xadc = iio_priv(indio_dev); + unsigned long pcap_rate; + unsigned int tck_div; + unsigned int div; + unsigned int igap; + unsigned int tck_rate; + int ret; + + /* TODO: Figure out how to make igap and tck_rate configurable */ + igap = XADC_ZYNQ_IGAP_DEFAULT; + tck_rate = XADC_ZYNQ_TCK_RATE_MAX; + + xadc->zynq_intmask = ~0; + + pcap_rate = clk_get_rate(xadc->clk); + if (!pcap_rate) + return -EINVAL; + + if (pcap_rate > XADC_ZYNQ_PCAP_RATE_MAX) { + ret = clk_set_rate(xadc->clk, + (unsigned long)XADC_ZYNQ_PCAP_RATE_MAX); + if (ret) + return ret; + } + + if (tck_rate > pcap_rate / 2) { + div = 2; + } else { + div = pcap_rate / tck_rate; + if (pcap_rate / div > XADC_ZYNQ_TCK_RATE_MAX) + div++; + } + + if (div <= 3) + tck_div = XADC_ZYNQ_CFG_TCKRATE_DIV2; + else if (div <= 7) + tck_div = XADC_ZYNQ_CFG_TCKRATE_DIV4; + else if (div <= 15) + tck_div = XADC_ZYNQ_CFG_TCKRATE_DIV8; + else + tck_div = XADC_ZYNQ_CFG_TCKRATE_DIV16; + + xadc_write_reg(xadc, XADC_ZYNQ_REG_CTL, XADC_ZYNQ_CTL_RESET); + xadc_write_reg(xadc, XADC_ZYNQ_REG_CTL, 0); + xadc_write_reg(xadc, XADC_ZYNQ_REG_INTSTS, ~0); + xadc_write_reg(xadc, XADC_ZYNQ_REG_INTMSK, xadc->zynq_intmask); + xadc_write_reg(xadc, XADC_ZYNQ_REG_CFG, XADC_ZYNQ_CFG_ENABLE | + XADC_ZYNQ_CFG_REDGE | XADC_ZYNQ_CFG_WEDGE | + tck_div | XADC_ZYNQ_CFG_IGAP(igap)); + + if (pcap_rate > XADC_ZYNQ_PCAP_RATE_MAX) { + ret = clk_set_rate(xadc->clk, pcap_rate); + if (ret) + return ret; + } + + return 0; +} + +static unsigned long xadc_zynq_get_dclk_rate(struct xadc *xadc) +{ + unsigned int div; + uint32_t val; + + xadc_read_reg(xadc, XADC_ZYNQ_REG_CFG, &val); + + switch (val & XADC_ZYNQ_CFG_TCKRATE_MASK) { + case XADC_ZYNQ_CFG_TCKRATE_DIV4: + div = 4; + break; + case XADC_ZYNQ_CFG_TCKRATE_DIV8: + div = 8; + break; + case XADC_ZYNQ_CFG_TCKRATE_DIV16: + div = 16; + break; + default: + div = 2; + break; + } + + return clk_get_rate(xadc->clk) / div; +} + +static void xadc_zynq_update_alarm(struct xadc *xadc, unsigned int alarm) +{ + unsigned long flags; + uint32_t status; + + /* Move OT to bit 7 */ + alarm = ((alarm & 0x08) << 4) | ((alarm & 0xf0) >> 1) | (alarm & 0x07); + + spin_lock_irqsave(&xadc->lock, flags); + + /* Clear previous interrupts if any. */ + xadc_read_reg(xadc, XADC_ZYNQ_REG_INTSTS, &status); + xadc_write_reg(xadc, XADC_ZYNQ_REG_INTSTS, status & alarm); + + xadc_zynq_update_intmsk(xadc, XADC_ZYNQ_INT_ALARM_MASK, + ~alarm & XADC_ZYNQ_INT_ALARM_MASK); + + spin_unlock_irqrestore(&xadc->lock, flags); +} + +static const struct xadc_ops xadc_zynq_ops = { + .read = xadc_zynq_read_adc_reg, + .write = xadc_zynq_write_adc_reg, + .setup = xadc_zynq_setup, + .get_dclk_rate = xadc_zynq_get_dclk_rate, + .interrupt_handler = xadc_zynq_interrupt_handler, + .update_alarm = xadc_zynq_update_alarm, + .type = XADC_TYPE_S7, + /* Temp in C = (val * 503.975) / 2**bits - 273.15 */ + .temp_scale = 503975, + .temp_offset = 273150, +}; + +static const unsigned int xadc_axi_reg_offsets[] = { + [XADC_TYPE_S7] = XADC_7S_AXI_ADC_REG_OFFSET, + [XADC_TYPE_US] = XADC_US_AXI_ADC_REG_OFFSET, +}; + +static int xadc_axi_read_adc_reg(struct xadc *xadc, unsigned int reg, + uint16_t *val) +{ + uint32_t val32; + + xadc_read_reg(xadc, xadc_axi_reg_offsets[xadc->ops->type] + reg * 4, + &val32); + *val = val32 & 0xffff; + + return 0; +} + +static int xadc_axi_write_adc_reg(struct xadc *xadc, unsigned int reg, + uint16_t val) +{ + xadc_write_reg(xadc, xadc_axi_reg_offsets[xadc->ops->type] + reg * 4, + val); + + return 0; +} + +static int xadc_axi_setup(struct platform_device *pdev, + struct iio_dev *indio_dev, int irq) +{ + struct xadc *xadc = iio_priv(indio_dev); + + xadc_write_reg(xadc, XADC_AXI_REG_RESET, XADC_AXI_RESET_MAGIC); + xadc_write_reg(xadc, XADC_AXI_REG_GIER, XADC_AXI_GIER_ENABLE); + + return 0; +} + +static irqreturn_t xadc_axi_interrupt_handler(int irq, void *devid) +{ + struct iio_dev *indio_dev = devid; + struct xadc *xadc = iio_priv(indio_dev); + uint32_t status, mask; + unsigned int events; + + xadc_read_reg(xadc, XADC_AXI_REG_IPISR, &status); + xadc_read_reg(xadc, XADC_AXI_REG_IPIER, &mask); + status &= mask; + + if (!status) + return IRQ_NONE; + + if ((status & XADC_AXI_INT_EOS) && xadc->trigger) + iio_trigger_poll(xadc->trigger); + + if (status & XADC_AXI_INT_ALARM_MASK) { + /* + * The order of the bits in the AXI-XADC status register does + * not match the order of the bits in the XADC alarm enable + * register. xadc_handle_events() expects the events to be in + * the same order as the XADC alarm enable register. + */ + events = (status & 0x000e) >> 1; + events |= (status & 0x0001) << 3; + events |= (status & 0x3c00) >> 6; + xadc_handle_events(indio_dev, events); + } + + xadc_write_reg(xadc, XADC_AXI_REG_IPISR, status); + + return IRQ_HANDLED; +} + +static void xadc_axi_update_alarm(struct xadc *xadc, unsigned int alarm) +{ + uint32_t val; + unsigned long flags; + + /* + * The order of the bits in the AXI-XADC status register does not match + * the order of the bits in the XADC alarm enable register. We get + * passed the alarm mask in the same order as in the XADC alarm enable + * register. + */ + alarm = ((alarm & 0x07) << 1) | ((alarm & 0x08) >> 3) | + ((alarm & 0xf0) << 6); + + spin_lock_irqsave(&xadc->lock, flags); + xadc_read_reg(xadc, XADC_AXI_REG_IPIER, &val); + val &= ~XADC_AXI_INT_ALARM_MASK; + val |= alarm; + xadc_write_reg(xadc, XADC_AXI_REG_IPIER, val); + spin_unlock_irqrestore(&xadc->lock, flags); +} + +static unsigned long xadc_axi_get_dclk(struct xadc *xadc) +{ + return clk_get_rate(xadc->clk); +} + +static const struct xadc_ops xadc_7s_axi_ops = { + .read = xadc_axi_read_adc_reg, + .write = xadc_axi_write_adc_reg, + .setup = xadc_axi_setup, + .get_dclk_rate = xadc_axi_get_dclk, + .update_alarm = xadc_axi_update_alarm, + .interrupt_handler = xadc_axi_interrupt_handler, + .flags = XADC_FLAGS_BUFFERED | XADC_FLAGS_IRQ_OPTIONAL, + .type = XADC_TYPE_S7, + /* Temp in C = (val * 503.975) / 2**bits - 273.15 */ + .temp_scale = 503975, + .temp_offset = 273150, +}; + +static const struct xadc_ops xadc_us_axi_ops = { + .read = xadc_axi_read_adc_reg, + .write = xadc_axi_write_adc_reg, + .setup = xadc_axi_setup, + .get_dclk_rate = xadc_axi_get_dclk, + .update_alarm = xadc_axi_update_alarm, + .interrupt_handler = xadc_axi_interrupt_handler, + .flags = XADC_FLAGS_BUFFERED | XADC_FLAGS_IRQ_OPTIONAL, + .type = XADC_TYPE_US, + /** + * Values below are for UltraScale+ (SYSMONE4) using internal reference. + * See https://docs.xilinx.com/v/u/en-US/ug580-ultrascale-sysmon + */ + .temp_scale = 509314, + .temp_offset = 280231, +}; + +static int _xadc_update_adc_reg(struct xadc *xadc, unsigned int reg, + uint16_t mask, uint16_t val) +{ + uint16_t tmp; + int ret; + + ret = _xadc_read_adc_reg(xadc, reg, &tmp); + if (ret) + return ret; + + return _xadc_write_adc_reg(xadc, reg, (tmp & ~mask) | val); +} + +static int xadc_update_adc_reg(struct xadc *xadc, unsigned int reg, + uint16_t mask, uint16_t val) +{ + int ret; + + mutex_lock(&xadc->mutex); + ret = _xadc_update_adc_reg(xadc, reg, mask, val); + mutex_unlock(&xadc->mutex); + + return ret; +} + +static unsigned long xadc_get_dclk_rate(struct xadc *xadc) +{ + return xadc->ops->get_dclk_rate(xadc); +} + +static int xadc_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *mask) +{ + struct xadc *xadc = iio_priv(indio_dev); + size_t n; + void *data; + + n = bitmap_weight(mask, iio_get_masklength(indio_dev)); + + data = devm_krealloc_array(indio_dev->dev.parent, xadc->data, + n, sizeof(*xadc->data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + memset(data, 0, n * sizeof(*xadc->data)); + xadc->data = data; + + return 0; +} + +static unsigned int xadc_scan_index_to_channel(unsigned int scan_index) +{ + switch (scan_index) { + case 5: + return XADC_REG_VCCPINT; + case 6: + return XADC_REG_VCCPAUX; + case 7: + return XADC_REG_VCCO_DDR; + case 8: + return XADC_REG_TEMP; + case 9: + return XADC_REG_VCCINT; + case 10: + return XADC_REG_VCCAUX; + case 11: + return XADC_REG_VPVN; + case 12: + return XADC_REG_VREFP; + case 13: + return XADC_REG_VREFN; + case 14: + return XADC_REG_VCCBRAM; + default: + return XADC_REG_VAUX(scan_index - 16); + } +} + +static irqreturn_t xadc_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct xadc *xadc = iio_priv(indio_dev); + unsigned int chan; + int i, j; + + if (!xadc->data) + goto out; + + j = 0; + iio_for_each_active_channel(indio_dev, i) { + chan = xadc_scan_index_to_channel(i); + xadc_read_adc_reg(xadc, chan, &xadc->data[j]); + j++; + } + + iio_push_to_buffers(indio_dev, xadc->data); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int xadc_trigger_set_state(struct iio_trigger *trigger, bool state) +{ + struct xadc *xadc = iio_trigger_get_drvdata(trigger); + unsigned long flags; + unsigned int convst; + unsigned int val; + int ret = 0; + + mutex_lock(&xadc->mutex); + + if (state) { + /* Only one of the two triggers can be active at a time. */ + if (xadc->trigger != NULL) { + ret = -EBUSY; + goto err_out; + } else { + xadc->trigger = trigger; + if (trigger == xadc->convst_trigger) + convst = XADC_CONF0_EC; + else + convst = 0; + } + ret = _xadc_update_adc_reg(xadc, XADC_REG_CONF1, XADC_CONF0_EC, + convst); + if (ret) + goto err_out; + } else { + xadc->trigger = NULL; + } + + spin_lock_irqsave(&xadc->lock, flags); + xadc_read_reg(xadc, XADC_AXI_REG_IPIER, &val); + xadc_write_reg(xadc, XADC_AXI_REG_IPISR, XADC_AXI_INT_EOS); + if (state) + val |= XADC_AXI_INT_EOS; + else + val &= ~XADC_AXI_INT_EOS; + xadc_write_reg(xadc, XADC_AXI_REG_IPIER, val); + spin_unlock_irqrestore(&xadc->lock, flags); + +err_out: + mutex_unlock(&xadc->mutex); + + return ret; +} + +static const struct iio_trigger_ops xadc_trigger_ops = { + .set_trigger_state = &xadc_trigger_set_state, +}; + +static struct iio_trigger *xadc_alloc_trigger(struct iio_dev *indio_dev, + const char *name) +{ + struct device *dev = indio_dev->dev.parent; + struct iio_trigger *trig; + int ret; + + trig = devm_iio_trigger_alloc(dev, "%s%d-%s", indio_dev->name, + iio_device_id(indio_dev), name); + if (trig == NULL) + return ERR_PTR(-ENOMEM); + + trig->ops = &xadc_trigger_ops; + iio_trigger_set_drvdata(trig, iio_priv(indio_dev)); + + ret = devm_iio_trigger_register(dev, trig); + if (ret) + return ERR_PTR(ret); + + return trig; +} + +static int xadc_power_adc_b(struct xadc *xadc, unsigned int seq_mode) +{ + uint16_t val; + + /* + * As per datasheet the power-down bits are don't care in the + * UltraScale, but as per reality setting the power-down bit for the + * non-existing ADC-B powers down the main ADC, so just return and don't + * do anything. + */ + if (xadc->ops->type == XADC_TYPE_US) + return 0; + + /* Powerdown the ADC-B when it is not needed. */ + switch (seq_mode) { + case XADC_CONF1_SEQ_SIMULTANEOUS: + case XADC_CONF1_SEQ_INDEPENDENT: + val = 0; + break; + default: + val = XADC_CONF2_PD_ADC_B; + break; + } + + return xadc_update_adc_reg(xadc, XADC_REG_CONF2, XADC_CONF2_PD_MASK, + val); +} + +static int xadc_get_seq_mode(struct xadc *xadc, unsigned long scan_mode) +{ + unsigned int aux_scan_mode = scan_mode >> 16; + + /* UltraScale has only one ADC and supports only continuous mode */ + if (xadc->ops->type == XADC_TYPE_US) + return XADC_CONF1_SEQ_CONTINUOUS; + + if (xadc->external_mux_mode == XADC_EXTERNAL_MUX_DUAL) + return XADC_CONF1_SEQ_SIMULTANEOUS; + + if ((aux_scan_mode & 0xff00) == 0 || + (aux_scan_mode & 0x00ff) == 0) + return XADC_CONF1_SEQ_CONTINUOUS; + + return XADC_CONF1_SEQ_SIMULTANEOUS; +} + +static int xadc_postdisable(struct iio_dev *indio_dev) +{ + struct xadc *xadc = iio_priv(indio_dev); + unsigned long scan_mask; + int seq_mode; + int ret; + int i; + + scan_mask = 1; /* Run calibration as part of the sequence */ + for (i = 0; i < indio_dev->num_channels; i++) + scan_mask |= BIT(indio_dev->channels[i].scan_index); + + /* + * Use the correct sequencer mode for the idle state: simultaneous + * mode for dual external mux configurations, continuous otherwise. + */ + seq_mode = xadc_get_seq_mode(xadc, scan_mask); + + /* Enable all channels and calibration */ + ret = xadc_write_adc_reg(xadc, XADC_REG_SEQ(0), scan_mask & 0xffff); + if (ret) + return ret; + + ret = xadc_write_adc_reg(xadc, XADC_REG_SEQ(1), scan_mask >> 16); + if (ret) + return ret; + + ret = xadc_update_adc_reg(xadc, XADC_REG_CONF1, XADC_CONF1_SEQ_MASK, + seq_mode); + if (ret) + return ret; + + return xadc_power_adc_b(xadc, seq_mode); +} + +static int xadc_preenable(struct iio_dev *indio_dev) +{ + struct xadc *xadc = iio_priv(indio_dev); + unsigned long scan_mask; + int seq_mode; + int ret; + + ret = xadc_update_adc_reg(xadc, XADC_REG_CONF1, XADC_CONF1_SEQ_MASK, + XADC_CONF1_SEQ_DEFAULT); + if (ret) + goto err; + + scan_mask = *indio_dev->active_scan_mask; + seq_mode = xadc_get_seq_mode(xadc, scan_mask); + + ret = xadc_write_adc_reg(xadc, XADC_REG_SEQ(0), scan_mask & 0xffff); + if (ret) + goto err; + + /* + * In simultaneous mode the upper and lower aux channels are samples at + * the same time. In this mode the upper 8 bits in the sequencer + * register are don't care and the lower 8 bits control two channels + * each. As such we must set the bit if either the channel in the lower + * group or the upper group is enabled. + */ + if (seq_mode == XADC_CONF1_SEQ_SIMULTANEOUS) + scan_mask = ((scan_mask >> 8) | scan_mask) & 0xff0000; + + ret = xadc_write_adc_reg(xadc, XADC_REG_SEQ(1), scan_mask >> 16); + if (ret) + goto err; + + ret = xadc_power_adc_b(xadc, seq_mode); + if (ret) + goto err; + + ret = xadc_update_adc_reg(xadc, XADC_REG_CONF1, XADC_CONF1_SEQ_MASK, + seq_mode); + if (ret) + goto err; + + return 0; +err: + xadc_postdisable(indio_dev); + return ret; +} + +static const struct iio_buffer_setup_ops xadc_buffer_ops = { + .preenable = &xadc_preenable, + .postdisable = &xadc_postdisable, +}; + +static int xadc_read_samplerate(struct xadc *xadc) +{ + unsigned int div; + uint16_t val16; + int ret; + + ret = xadc_read_adc_reg(xadc, XADC_REG_CONF2, &val16); + if (ret) + return ret; + + div = (val16 & XADC_CONF2_DIV_MASK) >> XADC_CONF2_DIV_OFFSET; + if (div < 2) + div = 2; + + return xadc_get_dclk_rate(xadc) / div / 26; +} + +static int xadc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long info) +{ + struct xadc *xadc = iio_priv(indio_dev); + unsigned int bits = chan->scan_type.realbits; + uint16_t val16; + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + ret = xadc_read_adc_reg(xadc, chan->address, &val16); + if (ret < 0) + return ret; + + val16 >>= chan->scan_type.shift; + if (chan->scan_type.sign == 'u') + *val = val16; + else + *val = sign_extend32(val16, bits - 1); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + /* V = (val * 3.0) / 2**bits */ + switch (chan->address) { + case XADC_REG_VCCINT: + case XADC_REG_VCCAUX: + case XADC_REG_VREFP: + case XADC_REG_VREFN: + case XADC_REG_VCCBRAM: + case XADC_REG_VCCPINT: + case XADC_REG_VCCPAUX: + case XADC_REG_VCCO_DDR: + *val = 3000; + break; + default: + *val = 1000; + break; + } + *val2 = bits; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_TEMP: + *val = xadc->ops->temp_scale; + *val2 = bits; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + /* Only the temperature channel has an offset */ + *val = -((xadc->ops->temp_offset << bits) / xadc->ops->temp_scale); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = xadc_read_samplerate(xadc); + if (ret < 0) + return ret; + + *val = ret; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int xadc_write_samplerate(struct xadc *xadc, int val) +{ + unsigned long clk_rate = xadc_get_dclk_rate(xadc); + unsigned int div; + + if (!clk_rate) + return -EINVAL; + + if (val <= 0) + return -EINVAL; + + /* Max. 150 kSPS */ + if (val > XADC_MAX_SAMPLERATE) + val = XADC_MAX_SAMPLERATE; + + val *= 26; + + /* Min 1MHz */ + if (val < 1000000) + val = 1000000; + + /* + * We want to round down, but only if we do not exceed the 150 kSPS + * limit. + */ + div = clk_rate / val; + if (clk_rate / div / 26 > XADC_MAX_SAMPLERATE) + div++; + if (div < 2) + div = 2; + else if (div > 0xff) + div = 0xff; + + return xadc_update_adc_reg(xadc, XADC_REG_CONF2, XADC_CONF2_DIV_MASK, + div << XADC_CONF2_DIV_OFFSET); +} + +static int xadc_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long info) +{ + struct xadc *xadc = iio_priv(indio_dev); + + if (info != IIO_CHAN_INFO_SAMP_FREQ) + return -EINVAL; + + return xadc_write_samplerate(xadc, val); +} + +static const struct iio_event_spec xadc_temp_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_HYSTERESIS), + }, +}; + +/* Separate values for upper and lower thresholds, but only a shared enabled */ +static const struct iio_event_spec xadc_voltage_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +#define XADC_CHAN_TEMP(_chan, _scan_index, _addr, _bits) { \ + .type = IIO_TEMP, \ + .indexed = 1, \ + .channel = (_chan), \ + .address = (_addr), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .event_spec = xadc_temp_events, \ + .num_event_specs = ARRAY_SIZE(xadc_temp_events), \ + .scan_index = (_scan_index), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (_bits), \ + .storagebits = 16, \ + .shift = 16 - (_bits), \ + .endianness = IIO_CPU, \ + }, \ +} + +#define XADC_CHAN_VOLTAGE(_chan, _scan_index, _addr, _bits, _ext, _alarm) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_chan), \ + .address = (_addr), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .event_spec = (_alarm) ? xadc_voltage_events : NULL, \ + .num_event_specs = (_alarm) ? ARRAY_SIZE(xadc_voltage_events) : 0, \ + .scan_index = (_scan_index), \ + .scan_type = { \ + .sign = ((_addr) == XADC_REG_VREFN) ? 's' : 'u', \ + .realbits = (_bits), \ + .storagebits = 16, \ + .shift = 16 - (_bits), \ + .endianness = IIO_CPU, \ + }, \ + .extend_name = _ext, \ +} + +/* 7 Series */ +#define XADC_7S_CHAN_TEMP(_chan, _scan_index, _addr) \ + XADC_CHAN_TEMP(_chan, _scan_index, _addr, 12) +#define XADC_7S_CHAN_VOLTAGE(_chan, _scan_index, _addr, _ext, _alarm) \ + XADC_CHAN_VOLTAGE(_chan, _scan_index, _addr, 12, _ext, _alarm) + +static const struct iio_chan_spec xadc_7s_channels[] = { + XADC_7S_CHAN_TEMP(0, 8, XADC_REG_TEMP), + XADC_7S_CHAN_VOLTAGE(0, 9, XADC_REG_VCCINT, "vccint", true), + XADC_7S_CHAN_VOLTAGE(1, 10, XADC_REG_VCCAUX, "vccaux", true), + XADC_7S_CHAN_VOLTAGE(2, 14, XADC_REG_VCCBRAM, "vccbram", true), + XADC_7S_CHAN_VOLTAGE(3, 5, XADC_REG_VCCPINT, "vccpint", true), + XADC_7S_CHAN_VOLTAGE(4, 6, XADC_REG_VCCPAUX, "vccpaux", true), + XADC_7S_CHAN_VOLTAGE(5, 7, XADC_REG_VCCO_DDR, "vccoddr", true), + XADC_7S_CHAN_VOLTAGE(6, 12, XADC_REG_VREFP, "vrefp", false), + XADC_7S_CHAN_VOLTAGE(7, 13, XADC_REG_VREFN, "vrefn", false), + XADC_7S_CHAN_VOLTAGE(8, 11, XADC_REG_VPVN, NULL, false), + XADC_7S_CHAN_VOLTAGE(9, 16, XADC_REG_VAUX(0), NULL, false), + XADC_7S_CHAN_VOLTAGE(10, 17, XADC_REG_VAUX(1), NULL, false), + XADC_7S_CHAN_VOLTAGE(11, 18, XADC_REG_VAUX(2), NULL, false), + XADC_7S_CHAN_VOLTAGE(12, 19, XADC_REG_VAUX(3), NULL, false), + XADC_7S_CHAN_VOLTAGE(13, 20, XADC_REG_VAUX(4), NULL, false), + XADC_7S_CHAN_VOLTAGE(14, 21, XADC_REG_VAUX(5), NULL, false), + XADC_7S_CHAN_VOLTAGE(15, 22, XADC_REG_VAUX(6), NULL, false), + XADC_7S_CHAN_VOLTAGE(16, 23, XADC_REG_VAUX(7), NULL, false), + XADC_7S_CHAN_VOLTAGE(17, 24, XADC_REG_VAUX(8), NULL, false), + XADC_7S_CHAN_VOLTAGE(18, 25, XADC_REG_VAUX(9), NULL, false), + XADC_7S_CHAN_VOLTAGE(19, 26, XADC_REG_VAUX(10), NULL, false), + XADC_7S_CHAN_VOLTAGE(20, 27, XADC_REG_VAUX(11), NULL, false), + XADC_7S_CHAN_VOLTAGE(21, 28, XADC_REG_VAUX(12), NULL, false), + XADC_7S_CHAN_VOLTAGE(22, 29, XADC_REG_VAUX(13), NULL, false), + XADC_7S_CHAN_VOLTAGE(23, 30, XADC_REG_VAUX(14), NULL, false), + XADC_7S_CHAN_VOLTAGE(24, 31, XADC_REG_VAUX(15), NULL, false), +}; + +/* UltraScale */ +#define XADC_US_CHAN_TEMP(_chan, _scan_index, _addr) \ + XADC_CHAN_TEMP(_chan, _scan_index, _addr, 10) +#define XADC_US_CHAN_VOLTAGE(_chan, _scan_index, _addr, _ext, _alarm) \ + XADC_CHAN_VOLTAGE(_chan, _scan_index, _addr, 10, _ext, _alarm) + +static const struct iio_chan_spec xadc_us_channels[] = { + XADC_US_CHAN_TEMP(0, 8, XADC_REG_TEMP), + XADC_US_CHAN_VOLTAGE(0, 9, XADC_REG_VCCINT, "vccint", true), + XADC_US_CHAN_VOLTAGE(1, 10, XADC_REG_VCCAUX, "vccaux", true), + XADC_US_CHAN_VOLTAGE(2, 14, XADC_REG_VCCBRAM, "vccbram", true), + XADC_US_CHAN_VOLTAGE(3, 5, XADC_REG_VCCPINT, "vccpsintlp", true), + XADC_US_CHAN_VOLTAGE(4, 6, XADC_REG_VCCPAUX, "vccpsintfp", true), + XADC_US_CHAN_VOLTAGE(5, 7, XADC_REG_VCCO_DDR, "vccpsaux", true), + XADC_US_CHAN_VOLTAGE(6, 12, XADC_REG_VREFP, "vrefp", false), + XADC_US_CHAN_VOLTAGE(7, 13, XADC_REG_VREFN, "vrefn", false), + XADC_US_CHAN_VOLTAGE(8, 11, XADC_REG_VPVN, NULL, false), + XADC_US_CHAN_VOLTAGE(9, 16, XADC_REG_VAUX(0), NULL, false), + XADC_US_CHAN_VOLTAGE(10, 17, XADC_REG_VAUX(1), NULL, false), + XADC_US_CHAN_VOLTAGE(11, 18, XADC_REG_VAUX(2), NULL, false), + XADC_US_CHAN_VOLTAGE(12, 19, XADC_REG_VAUX(3), NULL, false), + XADC_US_CHAN_VOLTAGE(13, 20, XADC_REG_VAUX(4), NULL, false), + XADC_US_CHAN_VOLTAGE(14, 21, XADC_REG_VAUX(5), NULL, false), + XADC_US_CHAN_VOLTAGE(15, 22, XADC_REG_VAUX(6), NULL, false), + XADC_US_CHAN_VOLTAGE(16, 23, XADC_REG_VAUX(7), NULL, false), + XADC_US_CHAN_VOLTAGE(17, 24, XADC_REG_VAUX(8), NULL, false), + XADC_US_CHAN_VOLTAGE(18, 25, XADC_REG_VAUX(9), NULL, false), + XADC_US_CHAN_VOLTAGE(19, 26, XADC_REG_VAUX(10), NULL, false), + XADC_US_CHAN_VOLTAGE(20, 27, XADC_REG_VAUX(11), NULL, false), + XADC_US_CHAN_VOLTAGE(21, 28, XADC_REG_VAUX(12), NULL, false), + XADC_US_CHAN_VOLTAGE(22, 29, XADC_REG_VAUX(13), NULL, false), + XADC_US_CHAN_VOLTAGE(23, 30, XADC_REG_VAUX(14), NULL, false), + XADC_US_CHAN_VOLTAGE(24, 31, XADC_REG_VAUX(15), NULL, false), +}; + +static const struct iio_info xadc_info = { + .read_raw = &xadc_read_raw, + .write_raw = &xadc_write_raw, + .read_event_config = &xadc_read_event_config, + .write_event_config = &xadc_write_event_config, + .read_event_value = &xadc_read_event_value, + .write_event_value = &xadc_write_event_value, + .update_scan_mode = &xadc_update_scan_mode, +}; + +static const struct of_device_id xadc_of_match_table[] = { + { + .compatible = "xlnx,zynq-xadc-1.00.a", + .data = &xadc_zynq_ops + }, { + .compatible = "xlnx,axi-xadc-1.00.a", + .data = &xadc_7s_axi_ops + }, { + .compatible = "xlnx,system-management-wiz-1.3", + .data = &xadc_us_axi_ops + }, + { } +}; +MODULE_DEVICE_TABLE(of, xadc_of_match_table); + +static int xadc_parse_dt(struct iio_dev *indio_dev, unsigned int *conf, int irq) +{ + struct device *dev = indio_dev->dev.parent; + struct xadc *xadc = iio_priv(indio_dev); + const struct iio_chan_spec *channel_templates; + struct iio_chan_spec *channels, *chan; + struct fwnode_handle *chan_node, *child; + unsigned int max_channels; + unsigned int num_channels; + const char *external_mux; + u32 ext_mux_chan; + u32 reg; + int ret; + int i; + + *conf = 0; + + ret = device_property_read_string(dev, "xlnx,external-mux", &external_mux); + if (ret < 0 || strcasecmp(external_mux, "none") == 0) + xadc->external_mux_mode = XADC_EXTERNAL_MUX_NONE; + else if (strcasecmp(external_mux, "single") == 0) + xadc->external_mux_mode = XADC_EXTERNAL_MUX_SINGLE; + else if (strcasecmp(external_mux, "dual") == 0) + xadc->external_mux_mode = XADC_EXTERNAL_MUX_DUAL; + else + return -EINVAL; + + if (xadc->external_mux_mode != XADC_EXTERNAL_MUX_NONE) { + ret = device_property_read_u32(dev, "xlnx,external-mux-channel", &ext_mux_chan); + if (ret < 0) + return ret; + + if (xadc->external_mux_mode == XADC_EXTERNAL_MUX_SINGLE) { + if (ext_mux_chan == 0) + ext_mux_chan = XADC_REG_VPVN; + else if (ext_mux_chan <= 16) + ext_mux_chan = XADC_REG_VAUX(ext_mux_chan - 1); + else + return -EINVAL; + } else { + if (ext_mux_chan > 0 && ext_mux_chan <= 8) + ext_mux_chan = XADC_REG_VAUX(ext_mux_chan - 1); + else + return -EINVAL; + } + + *conf |= XADC_CONF0_MUX | XADC_CONF0_CHAN(ext_mux_chan); + } + if (xadc->ops->type == XADC_TYPE_S7) { + channel_templates = xadc_7s_channels; + max_channels = ARRAY_SIZE(xadc_7s_channels); + } else { + channel_templates = xadc_us_channels; + max_channels = ARRAY_SIZE(xadc_us_channels); + } + channels = devm_kmemdup_array(dev, channel_templates, max_channels, + sizeof(*channel_templates), GFP_KERNEL); + if (!channels) + return -ENOMEM; + + num_channels = 9; + chan = &channels[9]; + + chan_node = device_get_named_child_node(dev, "xlnx,channels"); + fwnode_for_each_child_node(chan_node, child) { + if (num_channels >= max_channels) { + fwnode_handle_put(child); + break; + } + + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret || reg > 16) + continue; + + if (fwnode_property_read_bool(child, "xlnx,bipolar")) + chan->scan_type.sign = 's'; + + if (reg == 0) { + chan->scan_index = 11; + chan->address = XADC_REG_VPVN; + } else { + chan->scan_index = 15 + reg; + chan->address = XADC_REG_VAUX(reg - 1); + } + num_channels++; + chan++; + } + fwnode_handle_put(chan_node); + + /* No IRQ => no events */ + if (irq <= 0) { + for (i = 0; i < num_channels; i++) { + channels[i].event_spec = NULL; + channels[i].num_event_specs = 0; + } + } + + indio_dev->num_channels = num_channels; + indio_dev->channels = devm_krealloc_array(dev, channels, + num_channels, sizeof(*channels), + GFP_KERNEL); + /* If we can't resize the channels array, just use the original */ + if (!indio_dev->channels) + indio_dev->channels = channels; + + return 0; +} + +static const char * const xadc_type_names[] = { + [XADC_TYPE_S7] = "xadc", + [XADC_TYPE_US] = "xilinx-system-monitor", +}; + +static void xadc_cancel_delayed_work(void *data) +{ + struct delayed_work *work = data; + + cancel_delayed_work_sync(work); +} + +static int xadc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct xadc_ops *ops; + struct iio_dev *indio_dev; + unsigned int bipolar_mask; + unsigned int conf0; + struct xadc *xadc; + int ret; + int irq; + int i; + + ops = device_get_match_data(dev); + if (!ops) + return -EINVAL; + + irq = platform_get_irq_optional(pdev, 0); + if (irq < 0 && + (irq != -ENXIO || !(ops->flags & XADC_FLAGS_IRQ_OPTIONAL))) + return irq; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*xadc)); + if (!indio_dev) + return -ENOMEM; + + xadc = iio_priv(indio_dev); + xadc->ops = ops; + init_completion(&xadc->completion); + mutex_init(&xadc->mutex); + spin_lock_init(&xadc->lock); + INIT_DELAYED_WORK(&xadc->zynq_unmask_work, xadc_zynq_unmask_worker); + + xadc->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(xadc->base)) + return PTR_ERR(xadc->base); + + indio_dev->name = xadc_type_names[xadc->ops->type]; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &xadc_info; + + ret = xadc_parse_dt(indio_dev, &conf0, irq); + if (ret) + return ret; + + if (xadc->ops->flags & XADC_FLAGS_BUFFERED) { + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + &iio_pollfunc_store_time, + &xadc_trigger_handler, + &xadc_buffer_ops); + if (ret) + return ret; + + if (irq > 0) { + xadc->convst_trigger = xadc_alloc_trigger(indio_dev, "convst"); + if (IS_ERR(xadc->convst_trigger)) + return PTR_ERR(xadc->convst_trigger); + + xadc->samplerate_trigger = xadc_alloc_trigger(indio_dev, + "samplerate"); + if (IS_ERR(xadc->samplerate_trigger)) + return PTR_ERR(xadc->samplerate_trigger); + } + } + + xadc->clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(xadc->clk)) + return PTR_ERR(xadc->clk); + + /* + * Make sure not to exceed the maximum samplerate since otherwise the + * resulting interrupt storm will soft-lock the system. + */ + if (xadc->ops->flags & XADC_FLAGS_BUFFERED) { + ret = xadc_read_samplerate(xadc); + if (ret < 0) + return ret; + + if (ret > XADC_MAX_SAMPLERATE) { + ret = xadc_write_samplerate(xadc, XADC_MAX_SAMPLERATE); + if (ret < 0) + return ret; + } + } + + if (irq > 0) { + ret = devm_request_irq(dev, irq, xadc->ops->interrupt_handler, + 0, dev_name(dev), indio_dev); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, xadc_cancel_delayed_work, + &xadc->zynq_unmask_work); + if (ret) + return ret; + } + + ret = xadc->ops->setup(pdev, indio_dev, irq); + if (ret) + return ret; + + for (i = 0; i < 16; i++) + xadc_read_adc_reg(xadc, XADC_REG_THRESHOLD(i), + &xadc->threshold[i]); + + ret = xadc_write_adc_reg(xadc, XADC_REG_CONF0, conf0); + if (ret) + return ret; + + bipolar_mask = 0; + for (i = 0; i < indio_dev->num_channels; i++) { + if (indio_dev->channels[i].scan_type.sign == 's') + bipolar_mask |= BIT(indio_dev->channels[i].scan_index); + } + + ret = xadc_write_adc_reg(xadc, XADC_REG_INPUT_MODE(0), bipolar_mask); + if (ret) + return ret; + + ret = xadc_write_adc_reg(xadc, XADC_REG_INPUT_MODE(1), + bipolar_mask >> 16); + if (ret) + return ret; + + /* Go to non-buffered mode */ + xadc_postdisable(indio_dev); + + return devm_iio_device_register(dev, indio_dev); +} + +static struct platform_driver xadc_driver = { + .probe = xadc_probe, + .driver = { + .name = "xadc", + .of_match_table = xadc_of_match_table, + }, +}; +module_platform_driver(xadc_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Xilinx XADC IIO driver"); diff --git a/drivers/iio/adc/xilinx-xadc-events.c b/drivers/iio/adc/xilinx-xadc-events.c new file mode 100644 index 000000000..c188d3dca --- /dev/null +++ b/drivers/iio/adc/xilinx-xadc-events.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Xilinx XADC driver + * + * Copyright 2013 Analog Devices Inc. + * Author: Lars-Peter Clausen + */ + +#include +#include +#include + +#include "xilinx-xadc.h" + +static const struct iio_chan_spec *xadc_event_to_channel( + struct iio_dev *indio_dev, unsigned int event) +{ + switch (event) { + case XADC_THRESHOLD_OT_MAX: + case XADC_THRESHOLD_TEMP_MAX: + return &indio_dev->channels[0]; + case XADC_THRESHOLD_VCCINT_MAX: + case XADC_THRESHOLD_VCCAUX_MAX: + return &indio_dev->channels[event]; + default: + return &indio_dev->channels[event-1]; + } +} + +static void xadc_handle_event(struct iio_dev *indio_dev, unsigned int event) +{ + const struct iio_chan_spec *chan; + + /* Temperature threshold error, we don't handle this yet */ + if (event == 0) + return; + + chan = xadc_event_to_channel(indio_dev, event); + + if (chan->type == IIO_TEMP) { + /* + * The temperature channel only supports over-temperature + * events. + */ + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(chan->type, chan->channel, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + } else { + /* + * For other channels we don't know whether it is a upper or + * lower threshold event. Userspace will have to check the + * channel value if it wants to know. + */ + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(chan->type, chan->channel, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + } +} + +void xadc_handle_events(struct iio_dev *indio_dev, unsigned long events) +{ + unsigned int i; + + for_each_set_bit(i, &events, 8) + xadc_handle_event(indio_dev, i); +} + +static unsigned int xadc_get_threshold_offset(const struct iio_chan_spec *chan, + enum iio_event_direction dir) +{ + unsigned int offset; + + if (chan->type == IIO_TEMP) { + offset = XADC_THRESHOLD_OT_MAX; + } else { + if (chan->channel < 2) + offset = chan->channel + 1; + else + offset = chan->channel + 6; + } + + if (dir == IIO_EV_DIR_FALLING) + offset += 4; + + return offset; +} + +static unsigned int xadc_get_alarm_mask(const struct iio_chan_spec *chan) +{ + if (chan->type == IIO_TEMP) + return XADC_ALARM_OT_MASK; + switch (chan->channel) { + case 0: + return XADC_ALARM_VCCINT_MASK; + case 1: + return XADC_ALARM_VCCAUX_MASK; + case 2: + return XADC_ALARM_VCCBRAM_MASK; + case 3: + return XADC_ALARM_VCCPINT_MASK; + case 4: + return XADC_ALARM_VCCPAUX_MASK; + case 5: + return XADC_ALARM_VCCODDR_MASK; + default: + /* We will never get here */ + return 0; + } +} + +int xadc_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir) +{ + struct xadc *xadc = iio_priv(indio_dev); + + return (bool)(xadc->alarm_mask & xadc_get_alarm_mask(chan)); +} + +int xadc_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + unsigned int alarm = xadc_get_alarm_mask(chan); + struct xadc *xadc = iio_priv(indio_dev); + uint16_t cfg, old_cfg; + int ret; + + mutex_lock(&xadc->mutex); + + if (state) + xadc->alarm_mask |= alarm; + else + xadc->alarm_mask &= ~alarm; + + xadc->ops->update_alarm(xadc, xadc->alarm_mask); + + ret = _xadc_read_adc_reg(xadc, XADC_REG_CONF1, &cfg); + if (ret) + goto err_out; + + old_cfg = cfg; + cfg |= XADC_CONF1_ALARM_MASK; + cfg &= ~((xadc->alarm_mask & 0xf0) << 4); /* bram, pint, paux, ddr */ + cfg &= ~((xadc->alarm_mask & 0x08) >> 3); /* ot */ + cfg &= ~((xadc->alarm_mask & 0x07) << 1); /* temp, vccint, vccaux */ + if (old_cfg != cfg) + ret = _xadc_write_adc_reg(xadc, XADC_REG_CONF1, cfg); + +err_out: + mutex_unlock(&xadc->mutex); + + return ret; +} + +int xadc_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, + int *val, int *val2) +{ + unsigned int offset = xadc_get_threshold_offset(chan, dir); + struct xadc *xadc = iio_priv(indio_dev); + + switch (info) { + case IIO_EV_INFO_VALUE: + *val = xadc->threshold[offset]; + break; + case IIO_EV_INFO_HYSTERESIS: + *val = xadc->temp_hysteresis; + break; + default: + return -EINVAL; + } + + /* MSB aligned */ + *val >>= 16 - chan->scan_type.realbits; + + return IIO_VAL_INT; +} + +int xadc_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, + int val, int val2) +{ + unsigned int offset = xadc_get_threshold_offset(chan, dir); + struct xadc *xadc = iio_priv(indio_dev); + int ret = 0; + + /* MSB aligned */ + val <<= 16 - chan->scan_type.realbits; + + if (val < 0 || val > 0xffff) + return -EINVAL; + + mutex_lock(&xadc->mutex); + + switch (info) { + case IIO_EV_INFO_VALUE: + xadc->threshold[offset] = val; + break; + case IIO_EV_INFO_HYSTERESIS: + xadc->temp_hysteresis = val; + break; + default: + mutex_unlock(&xadc->mutex); + return -EINVAL; + } + + if (chan->type == IIO_TEMP) { + /* + * According to the datasheet we need to set the lower 4 bits to + * 0x3, otherwise 125 degree celsius will be used as the + * threshold. + */ + val |= 0x3; + + /* + * Since we store the hysteresis as relative (to the threshold) + * value, but the hardware expects an absolute value we need to + * recalculate this value whenever the hysteresis or the + * threshold changes. + */ + if (xadc->threshold[offset] < xadc->temp_hysteresis) + xadc->threshold[offset + 4] = 0; + else + xadc->threshold[offset + 4] = xadc->threshold[offset] - + xadc->temp_hysteresis; + ret = _xadc_write_adc_reg(xadc, XADC_REG_THRESHOLD(offset + 4), + xadc->threshold[offset + 4]); + if (ret) + goto out_unlock; + } + + if (info == IIO_EV_INFO_VALUE) + ret = _xadc_write_adc_reg(xadc, XADC_REG_THRESHOLD(offset), val); + +out_unlock: + mutex_unlock(&xadc->mutex); + + return ret; +} diff --git a/drivers/iio/adc/xilinx-xadc.h b/drivers/iio/adc/xilinx-xadc.h new file mode 100644 index 000000000..b4d9d4683 --- /dev/null +++ b/drivers/iio/adc/xilinx-xadc.h @@ -0,0 +1,214 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Xilinx XADC driver + * + * Copyright 2013 Analog Devices Inc. + * Author: Lars-Peter Clausen + */ + +#ifndef __IIO_XILINX_XADC__ +#define __IIO_XILINX_XADC__ + +#include +#include +#include + +struct iio_dev; +struct clk; +struct xadc_ops; +struct platform_device; + +void xadc_handle_events(struct iio_dev *indio_dev, unsigned long events); + +int xadc_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir); +int xadc_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, bool state); +int xadc_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, + int *val, int *val2); +int xadc_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, + int val, int val2); + +enum xadc_external_mux_mode { + XADC_EXTERNAL_MUX_NONE, + XADC_EXTERNAL_MUX_SINGLE, + XADC_EXTERNAL_MUX_DUAL, +}; + +struct xadc { + void __iomem *base; + struct clk *clk; + + const struct xadc_ops *ops; + + uint16_t threshold[16]; + uint16_t temp_hysteresis; + unsigned int alarm_mask; + + uint16_t *data; + + struct iio_trigger *trigger; + struct iio_trigger *convst_trigger; + struct iio_trigger *samplerate_trigger; + + enum xadc_external_mux_mode external_mux_mode; + + unsigned int zynq_masked_alarm; + unsigned int zynq_intmask; + struct delayed_work zynq_unmask_work; + + struct mutex mutex; + spinlock_t lock; + + struct completion completion; +}; + +enum xadc_type { + XADC_TYPE_S7, /* Series 7 */ + XADC_TYPE_US, /* UltraScale and UltraScale+ */ +}; + +struct xadc_ops { + int (*read)(struct xadc *xadc, unsigned int reg, uint16_t *val); + int (*write)(struct xadc *xadc, unsigned int reg, uint16_t val); + int (*setup)(struct platform_device *pdev, struct iio_dev *indio_dev, + int irq); + void (*update_alarm)(struct xadc *xadc, unsigned int alarm); + unsigned long (*get_dclk_rate)(struct xadc *xadc); + irqreturn_t (*interrupt_handler)(int irq, void *devid); + + unsigned int flags; + enum xadc_type type; + int temp_scale; + int temp_offset; +}; + +static inline int _xadc_read_adc_reg(struct xadc *xadc, unsigned int reg, + uint16_t *val) +{ + lockdep_assert_held(&xadc->mutex); + return xadc->ops->read(xadc, reg, val); +} + +static inline int _xadc_write_adc_reg(struct xadc *xadc, unsigned int reg, + uint16_t val) +{ + lockdep_assert_held(&xadc->mutex); + return xadc->ops->write(xadc, reg, val); +} + +static inline int xadc_read_adc_reg(struct xadc *xadc, unsigned int reg, + uint16_t *val) +{ + int ret; + + mutex_lock(&xadc->mutex); + ret = _xadc_read_adc_reg(xadc, reg, val); + mutex_unlock(&xadc->mutex); + return ret; +} + +static inline int xadc_write_adc_reg(struct xadc *xadc, unsigned int reg, + uint16_t val) +{ + int ret; + + mutex_lock(&xadc->mutex); + ret = _xadc_write_adc_reg(xadc, reg, val); + mutex_unlock(&xadc->mutex); + return ret; +} + +/* XADC hardmacro register definitions */ +#define XADC_REG_TEMP 0x00 +#define XADC_REG_VCCINT 0x01 +#define XADC_REG_VCCAUX 0x02 +#define XADC_REG_VPVN 0x03 +#define XADC_REG_VREFP 0x04 +#define XADC_REG_VREFN 0x05 +#define XADC_REG_VCCBRAM 0x06 + +#define XADC_REG_VCCPINT 0x0d +#define XADC_REG_VCCPAUX 0x0e +#define XADC_REG_VCCO_DDR 0x0f +#define XADC_REG_VAUX(x) (0x10 + (x)) + +#define XADC_REG_MAX_TEMP 0x20 +#define XADC_REG_MAX_VCCINT 0x21 +#define XADC_REG_MAX_VCCAUX 0x22 +#define XADC_REG_MAX_VCCBRAM 0x23 +#define XADC_REG_MIN_TEMP 0x24 +#define XADC_REG_MIN_VCCINT 0x25 +#define XADC_REG_MIN_VCCAUX 0x26 +#define XADC_REG_MIN_VCCBRAM 0x27 +#define XADC_REG_MAX_VCCPINT 0x28 +#define XADC_REG_MAX_VCCPAUX 0x29 +#define XADC_REG_MAX_VCCO_DDR 0x2a +#define XADC_REG_MIN_VCCPINT 0x2c +#define XADC_REG_MIN_VCCPAUX 0x2d +#define XADC_REG_MIN_VCCO_DDR 0x2e + +#define XADC_REG_CONF0 0x40 +#define XADC_REG_CONF1 0x41 +#define XADC_REG_CONF2 0x42 +#define XADC_REG_SEQ(x) (0x48 + (x)) +#define XADC_REG_INPUT_MODE(x) (0x4c + (x)) +#define XADC_REG_THRESHOLD(x) (0x50 + (x)) + +#define XADC_REG_FLAG 0x3f + +#define XADC_CONF0_EC BIT(9) +#define XADC_CONF0_ACQ BIT(8) +#define XADC_CONF0_MUX BIT(11) +#define XADC_CONF0_CHAN(x) (x) + +#define XADC_CONF1_SEQ_MASK (0xf << 12) +#define XADC_CONF1_SEQ_DEFAULT (0 << 12) +#define XADC_CONF1_SEQ_SINGLE_PASS (1 << 12) +#define XADC_CONF1_SEQ_CONTINUOUS (2 << 12) +#define XADC_CONF1_SEQ_SINGLE_CHANNEL (3 << 12) +#define XADC_CONF1_SEQ_SIMULTANEOUS (4 << 12) +#define XADC_CONF1_SEQ_INDEPENDENT (8 << 12) +#define XADC_CONF1_ALARM_MASK 0x0f0f + +#define XADC_CONF2_DIV_MASK 0xff00 +#define XADC_CONF2_DIV_OFFSET 8 + +#define XADC_CONF2_PD_MASK (0x3 << 4) +#define XADC_CONF2_PD_NONE (0x0 << 4) +#define XADC_CONF2_PD_ADC_B (0x2 << 4) +#define XADC_CONF2_PD_BOTH (0x3 << 4) + +#define XADC_ALARM_TEMP_MASK BIT(0) +#define XADC_ALARM_VCCINT_MASK BIT(1) +#define XADC_ALARM_VCCAUX_MASK BIT(2) +#define XADC_ALARM_OT_MASK BIT(3) +#define XADC_ALARM_VCCBRAM_MASK BIT(4) +#define XADC_ALARM_VCCPINT_MASK BIT(5) +#define XADC_ALARM_VCCPAUX_MASK BIT(6) +#define XADC_ALARM_VCCODDR_MASK BIT(7) + +#define XADC_THRESHOLD_TEMP_MAX 0x0 +#define XADC_THRESHOLD_VCCINT_MAX 0x1 +#define XADC_THRESHOLD_VCCAUX_MAX 0x2 +#define XADC_THRESHOLD_OT_MAX 0x3 +#define XADC_THRESHOLD_TEMP_MIN 0x4 +#define XADC_THRESHOLD_VCCINT_MIN 0x5 +#define XADC_THRESHOLD_VCCAUX_MIN 0x6 +#define XADC_THRESHOLD_OT_MIN 0x7 +#define XADC_THRESHOLD_VCCBRAM_MAX 0x8 +#define XADC_THRESHOLD_VCCPINT_MAX 0x9 +#define XADC_THRESHOLD_VCCPAUX_MAX 0xa +#define XADC_THRESHOLD_VCCODDR_MAX 0xb +#define XADC_THRESHOLD_VCCBRAM_MIN 0xc +#define XADC_THRESHOLD_VCCPINT_MIN 0xd +#define XADC_THRESHOLD_VCCPAUX_MIN 0xe +#define XADC_THRESHOLD_VCCODDR_MIN 0xf + +#endif diff --git a/drivers/iio/addac/Kconfig b/drivers/iio/addac/Kconfig new file mode 100644 index 000000000..16876b8b5 --- /dev/null +++ b/drivers/iio/addac/Kconfig @@ -0,0 +1,55 @@ +# +# ADC DAC drivers +# +# When adding new entries keep the list in alphabetical order + +menu "Analog to digital and digital to analog converters" + +config AD74115 + tristate "Analog Devices AD74115H driver" + depends on GPIOLIB && SPI + select CRC8 + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select REGMAP_SPI + help + Say yes here to build support for Analog Devices AD74115H + single-channel software configurable input/output solution. + + To compile this driver as a module, choose M here: the + module will be called ad74115. + +config AD74413R + tristate "Analog Devices AD74412R/AD74413R driver" + depends on GPIOLIB && SPI + select REGMAP_SPI + select CRC8 + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Analog Devices AD74412R/AD74413R + quad-channel software configurable input/output solution. + + To compile this driver as a module, choose M here: the + module will be called ad74413r. + +config STX104 + tristate "Apex Embedded Systems STX104 driver" + depends on PC104 && X86 + select ISA_BUS_API + select REGMAP_MMIO + select GPIOLIB + select GPIO_REGMAP + select I8254 + help + Say yes here to build support for the Apex Embedded Systems STX104 + integrated analog PC/104 card. + + This driver supports the 16 channels of single-ended (8 channels of + differential) analog inputs, 2 channels of analog output, 4 digital + inputs, and 4 digital outputs provided by the STX104. + + The base port addresses for the devices may be configured via the base + array module parameter. + +endmenu diff --git a/drivers/iio/addac/Makefile b/drivers/iio/addac/Makefile new file mode 100644 index 000000000..577777276 --- /dev/null +++ b/drivers/iio/addac/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for industrial I/O ADDAC drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_AD74115) += ad74115.o +obj-$(CONFIG_AD74413R) += ad74413r.o +obj-$(CONFIG_STX104) += stx104.o diff --git a/drivers/iio/addac/ad74115.c b/drivers/iio/addac/ad74115.c new file mode 100644 index 000000000..9b9d7a12c --- /dev/null +++ b/drivers/iio/addac/ad74115.c @@ -0,0 +1,1933 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Analog Devices, Inc. + * Author: Cosmin Tanislav + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#define AD74115_NAME "ad74115" + +#define AD74115_CH_FUNC_SETUP_REG 0x01 + +#define AD74115_ADC_CONFIG_REG 0x02 +#define AD74115_ADC_CONFIG_CONV2_RATE_MASK GENMASK(15, 13) +#define AD74115_ADC_CONFIG_CONV1_RATE_MASK GENMASK(12, 10) +#define AD74115_ADC_CONFIG_CONV2_RANGE_MASK GENMASK(9, 7) +#define AD74115_ADC_CONFIG_CONV1_RANGE_MASK GENMASK(6, 4) + +#define AD74115_PWR_OPTIM_CONFIG_REG 0x03 + +#define AD74115_DIN_CONFIG1_REG 0x04 +#define AD74115_DIN_COMPARATOR_EN_MASK BIT(13) +#define AD74115_DIN_SINK_MASK GENMASK(11, 7) +#define AD74115_DIN_DEBOUNCE_MASK GENMASK(4, 0) + +#define AD74115_DIN_CONFIG2_REG 0x05 +#define AD74115_COMP_THRESH_MASK GENMASK(6, 0) + +#define AD74115_OUTPUT_CONFIG_REG 0x06 +#define AD74115_OUTPUT_SLEW_EN_MASK GENMASK(6, 5) +#define AD74115_OUTPUT_SLEW_LIN_STEP_MASK GENMASK(4, 3) +#define AD74115_OUTPUT_SLEW_LIN_RATE_MASK GENMASK(2, 1) + +#define AD74115_RTD3W4W_CONFIG_REG 0x07 + +#define AD74115_BURNOUT_CONFIG_REG 0x0a +#define AD74115_BURNOUT_EXT2_EN_MASK BIT(10) +#define AD74115_BURNOUT_EXT1_EN_MASK BIT(5) +#define AD74115_BURNOUT_VIOUT_EN_MASK BIT(0) + +#define AD74115_DAC_CODE_REG 0x0b + +#define AD74115_DAC_ACTIVE_REG 0x0d + +#define AD74115_GPIO_CONFIG_X_REG(x) (0x35 + (x)) +#define AD74115_GPIO_CONFIG_GPI_DATA BIT(5) +#define AD74115_GPIO_CONFIG_GPO_DATA BIT(4) +#define AD74115_GPIO_CONFIG_SELECT_MASK GENMASK(2, 0) + +#define AD74115_CHARGE_PUMP_REG 0x3a + +#define AD74115_ADC_CONV_CTRL_REG 0x3b +#define AD74115_ADC_CONV_SEQ_MASK GENMASK(13, 12) + +#define AD74115_DIN_COMP_OUT_REG 0x40 + +#define AD74115_LIVE_STATUS_REG 0x42 +#define AD74115_ADC_DATA_RDY_MASK BIT(3) + +#define AD74115_READ_SELECT_REG 0x64 + +#define AD74115_CMD_KEY_REG 0x78 +#define AD74115_CMD_KEY_RESET1 0x15fa +#define AD74115_CMD_KEY_RESET2 0xaf51 + +#define AD74115_CRC_POLYNOMIAL 0x7 +DECLARE_CRC8_TABLE(ad74115_crc8_table); + +#define AD74115_ADC_CODE_MAX ((int)GENMASK(15, 0)) +#define AD74115_ADC_CODE_HALF (AD74115_ADC_CODE_MAX / 2) + +#define AD74115_DAC_VOLTAGE_MAX 12000 +#define AD74115_DAC_CURRENT_MAX 25 +#define AD74115_DAC_CODE_MAX ((int)GENMASK(13, 0)) +#define AD74115_DAC_CODE_HALF (AD74115_DAC_CODE_MAX / 2) + +#define AD74115_COMP_THRESH_MAX 98 + +#define AD74115_SENSE_RESISTOR_OHMS 100 +#define AD74115_REF_RESISTOR_OHMS 2100 + +#define AD74115_DIN_SINK_LOW_STEP 120 +#define AD74115_DIN_SINK_HIGH_STEP 240 +#define AD74115_DIN_SINK_MAX 31 + +#define AD74115_FRAME_SIZE 4 +#define AD74115_GPIO_NUM 4 + +#define AD74115_CONV_TIME_US 1000000 + +enum ad74115_dac_ch { + AD74115_DAC_CH_MAIN, + AD74115_DAC_CH_COMPARATOR, +}; + +enum ad74115_adc_ch { + AD74115_ADC_CH_CONV1, + AD74115_ADC_CH_CONV2, + AD74115_ADC_CH_NUM +}; + +enum ad74115_ch_func { + AD74115_CH_FUNC_HIGH_IMPEDANCE, + AD74115_CH_FUNC_VOLTAGE_OUTPUT, + AD74115_CH_FUNC_CURRENT_OUTPUT, + AD74115_CH_FUNC_VOLTAGE_INPUT, + AD74115_CH_FUNC_CURRENT_INPUT_EXT_POWER, + AD74115_CH_FUNC_CURRENT_INPUT_LOOP_POWER, + AD74115_CH_FUNC_2_WIRE_RESISTANCE_INPUT, + AD74115_CH_FUNC_3_4_WIRE_RESISTANCE_INPUT, + AD74115_CH_FUNC_DIGITAL_INPUT_LOGIC, + AD74115_CH_FUNC_DIGITAL_INPUT_LOOP_POWER, + AD74115_CH_FUNC_CURRENT_OUTPUT_HART, + AD74115_CH_FUNC_CURRENT_INPUT_EXT_POWER_HART, + AD74115_CH_FUNC_CURRENT_INPUT_LOOP_POWER_HART, + AD74115_CH_FUNC_MAX = AD74115_CH_FUNC_CURRENT_INPUT_LOOP_POWER_HART, + AD74115_CH_FUNC_NUM +}; + +enum ad74115_adc_range { + AD74115_ADC_RANGE_12V, + AD74115_ADC_RANGE_12V_BIPOLAR, + AD74115_ADC_RANGE_2_5V_BIPOLAR, + AD74115_ADC_RANGE_2_5V_NEG, + AD74115_ADC_RANGE_2_5V, + AD74115_ADC_RANGE_0_625V, + AD74115_ADC_RANGE_104MV_BIPOLAR, + AD74115_ADC_RANGE_12V_OTHER, + AD74115_ADC_RANGE_MAX = AD74115_ADC_RANGE_12V_OTHER, + AD74115_ADC_RANGE_NUM +}; + +enum ad74115_adc_conv_seq { + AD74115_ADC_CONV_SEQ_STANDBY = 0b00, + AD74115_ADC_CONV_SEQ_SINGLE = 0b01, + AD74115_ADC_CONV_SEQ_CONTINUOUS = 0b10, +}; + +enum ad74115_din_threshold_mode { + AD74115_DIN_THRESHOLD_MODE_AVDD, + AD74115_DIN_THRESHOLD_MODE_FIXED, + AD74115_DIN_THRESHOLD_MODE_MAX = AD74115_DIN_THRESHOLD_MODE_FIXED, +}; + +enum ad74115_slew_mode { + AD74115_SLEW_MODE_DISABLED, + AD74115_SLEW_MODE_LINEAR, + AD74115_SLEW_MODE_HART, +}; + +enum ad74115_slew_step { + AD74115_SLEW_STEP_0_8_PERCENT, + AD74115_SLEW_STEP_1_5_PERCENT, + AD74115_SLEW_STEP_6_1_PERCENT, + AD74115_SLEW_STEP_22_2_PERCENT, +}; + +enum ad74115_slew_rate { + AD74115_SLEW_RATE_4KHZ, + AD74115_SLEW_RATE_64KHZ, + AD74115_SLEW_RATE_150KHZ, + AD74115_SLEW_RATE_240KHZ, +}; + +enum ad74115_gpio_config { + AD74115_GPIO_CONFIG_OUTPUT_BUFFERED = 0b010, + AD74115_GPIO_CONFIG_INPUT = 0b011, +}; + +enum ad74115_gpio_mode { + AD74115_GPIO_MODE_LOGIC = 1, + AD74115_GPIO_MODE_SPECIAL = 2, +}; + +struct ad74115_channels { + const struct iio_chan_spec *channels; + unsigned int num_channels; +}; + +struct ad74115_state { + struct spi_device *spi; + struct regmap *regmap; + struct iio_trigger *trig; + + /* + * Synchronize consecutive operations when doing a one-shot + * conversion and when updating the ADC samples SPI message. + */ + struct mutex lock; + struct gpio_chip gc; + struct gpio_chip comp_gc; + int irq; + + unsigned int avdd_mv; + unsigned long gpio_valid_mask; + bool dac_bipolar; + bool dac_hart_slew; + bool rtd_mode_4_wire; + enum ad74115_ch_func ch_func; + enum ad74115_din_threshold_mode din_threshold_mode; + + struct completion adc_data_completion; + struct spi_message adc_samples_msg; + struct spi_transfer adc_samples_xfer[AD74115_ADC_CH_NUM + 1]; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + u8 reg_tx_buf[AD74115_FRAME_SIZE] __aligned(IIO_DMA_MINALIGN); + u8 reg_rx_buf[AD74115_FRAME_SIZE]; + u8 adc_samples_tx_buf[AD74115_FRAME_SIZE * AD74115_ADC_CH_NUM]; + u8 adc_samples_rx_buf[AD74115_FRAME_SIZE * AD74115_ADC_CH_NUM]; +}; + +struct ad74115_fw_prop { + const char *name; + bool is_boolean; + bool negate; + unsigned int max; + unsigned int reg; + unsigned int mask; + const unsigned int *lookup_tbl; + unsigned int lookup_tbl_len; +}; + +#define AD74115_FW_PROP(_name, _max, _reg, _mask) \ +{ \ + .name = (_name), \ + .max = (_max), \ + .reg = (_reg), \ + .mask = (_mask), \ +} + +#define AD74115_FW_PROP_TBL(_name, _tbl, _reg, _mask) \ +{ \ + .name = (_name), \ + .reg = (_reg), \ + .mask = (_mask), \ + .lookup_tbl = (_tbl), \ + .lookup_tbl_len = ARRAY_SIZE(_tbl), \ +} + +#define AD74115_FW_PROP_BOOL(_name, _reg, _mask) \ +{ \ + .name = (_name), \ + .is_boolean = true, \ + .reg = (_reg), \ + .mask = (_mask), \ +} + +#define AD74115_FW_PROP_BOOL_NEG(_name, _reg, _mask) \ +{ \ + .name = (_name), \ + .is_boolean = true, \ + .negate = true, \ + .reg = (_reg), \ + .mask = (_mask), \ +} + +static const int ad74115_dac_rate_tbl[] = { + 0, + 4 * 8, + 4 * 15, + 4 * 61, + 4 * 222, + 64 * 8, + 64 * 15, + 64 * 61, + 64 * 222, + 150 * 8, + 150 * 15, + 150 * 61, + 150 * 222, + 240 * 8, + 240 * 15, + 240 * 61, + 240 * 222, +}; + +static const unsigned int ad74115_dac_rate_step_tbl[][3] = { + { AD74115_SLEW_MODE_DISABLED }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_0_8_PERCENT, AD74115_SLEW_RATE_4KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_1_5_PERCENT, AD74115_SLEW_RATE_4KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_6_1_PERCENT, AD74115_SLEW_RATE_4KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_22_2_PERCENT, AD74115_SLEW_RATE_4KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_0_8_PERCENT, AD74115_SLEW_RATE_64KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_1_5_PERCENT, AD74115_SLEW_RATE_64KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_6_1_PERCENT, AD74115_SLEW_RATE_64KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_22_2_PERCENT, AD74115_SLEW_RATE_64KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_0_8_PERCENT, AD74115_SLEW_RATE_150KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_1_5_PERCENT, AD74115_SLEW_RATE_150KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_6_1_PERCENT, AD74115_SLEW_RATE_150KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_22_2_PERCENT, AD74115_SLEW_RATE_150KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_0_8_PERCENT, AD74115_SLEW_RATE_240KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_1_5_PERCENT, AD74115_SLEW_RATE_240KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_6_1_PERCENT, AD74115_SLEW_RATE_240KHZ }, + { AD74115_SLEW_MODE_LINEAR, AD74115_SLEW_STEP_22_2_PERCENT, AD74115_SLEW_RATE_240KHZ }, +}; + +static const unsigned int ad74115_rtd_excitation_current_ua_tbl[] = { + 250, 500, 750, 1000 +}; + +static const unsigned int ad74115_burnout_current_na_tbl[] = { + 0, 50, 0, 500, 1000, 0, 10000, 0 +}; + +static const unsigned int ad74115_viout_burnout_current_na_tbl[] = { + 0, 0, 0, 0, 1000, 0, 10000, 0 +}; + +static const unsigned int ad74115_gpio_mode_tbl[] = { + 0, 0, 0, 1, 2, 3, 4, 5 +}; + +static const unsigned int ad74115_adc_conv_rate_tbl[] = { + 10, 20, 1200, 4800, 9600 +}; + +static const unsigned int ad74115_debounce_tbl[] = { + 0, 13, 18, 24, 32, 42, 56, 75, + 100, 130, 180, 240, 320, 420, 560, 750, + 1000, 1300, 1800, 2400, 3200, 4200, 5600, 7500, + 10000, 13000, 18000, 24000, 32000, 42000, 56000, 75000, +}; + +static const unsigned int ad74115_adc_ch_data_regs_tbl[] = { + [AD74115_ADC_CH_CONV1] = 0x44, + [AD74115_ADC_CH_CONV2] = 0x46, +}; + +static const unsigned int ad74115_adc_ch_en_bit_tbl[] = { + [AD74115_ADC_CH_CONV1] = BIT(0), + [AD74115_ADC_CH_CONV2] = BIT(1), +}; + +static const bool ad74115_adc_bipolar_tbl[AD74115_ADC_RANGE_NUM] = { + [AD74115_ADC_RANGE_12V_BIPOLAR] = true, + [AD74115_ADC_RANGE_2_5V_BIPOLAR] = true, + [AD74115_ADC_RANGE_104MV_BIPOLAR] = true, +}; + +static const unsigned int ad74115_adc_conv_mul_tbl[AD74115_ADC_RANGE_NUM] = { + [AD74115_ADC_RANGE_12V] = 12000, + [AD74115_ADC_RANGE_12V_BIPOLAR] = 24000, + [AD74115_ADC_RANGE_2_5V_BIPOLAR] = 5000, + [AD74115_ADC_RANGE_2_5V_NEG] = 2500, + [AD74115_ADC_RANGE_2_5V] = 2500, + [AD74115_ADC_RANGE_0_625V] = 625, + [AD74115_ADC_RANGE_104MV_BIPOLAR] = 208, + [AD74115_ADC_RANGE_12V_OTHER] = 12000, +}; + +static const unsigned int ad74115_adc_gain_tbl[AD74115_ADC_RANGE_NUM][2] = { + [AD74115_ADC_RANGE_12V] = { 5, 24 }, + [AD74115_ADC_RANGE_12V_BIPOLAR] = { 5, 24 }, + [AD74115_ADC_RANGE_2_5V_BIPOLAR] = { 1, 1 }, + [AD74115_ADC_RANGE_2_5V_NEG] = { 1, 1 }, + [AD74115_ADC_RANGE_2_5V] = { 1, 1 }, + [AD74115_ADC_RANGE_0_625V] = { 4, 1 }, + [AD74115_ADC_RANGE_104MV_BIPOLAR] = { 24, 1 }, + [AD74115_ADC_RANGE_12V_OTHER] = { 5, 24 }, +}; + +static const int ad74115_adc_range_tbl[AD74115_ADC_RANGE_NUM][2] = { + [AD74115_ADC_RANGE_12V] = { 0, 12000000 }, + [AD74115_ADC_RANGE_12V_BIPOLAR] = { -12000000, 12000000 }, + [AD74115_ADC_RANGE_2_5V_BIPOLAR] = { -2500000, 2500000 }, + [AD74115_ADC_RANGE_2_5V_NEG] = { -2500000, 0 }, + [AD74115_ADC_RANGE_2_5V] = { 0, 2500000 }, + [AD74115_ADC_RANGE_0_625V] = { 0, 625000 }, + [AD74115_ADC_RANGE_104MV_BIPOLAR] = { -104000, 104000 }, + [AD74115_ADC_RANGE_12V_OTHER] = { 0, 12000000 }, +}; + +static int _ad74115_find_tbl_index(const unsigned int *tbl, unsigned int tbl_len, + unsigned int val, unsigned int *index) +{ + unsigned int i; + + for (i = 0; i < tbl_len; i++) + if (val == tbl[i]) { + *index = i; + return 0; + } + + return -EINVAL; +} + +#define ad74115_find_tbl_index(tbl, val, index) \ + _ad74115_find_tbl_index(tbl, ARRAY_SIZE(tbl), val, index) + +static int ad74115_crc(u8 *buf) +{ + return crc8(ad74115_crc8_table, buf, 3, 0); +} + +static void ad74115_format_reg_write(u8 reg, u16 val, u8 *buf) +{ + buf[0] = reg; + put_unaligned_be16(val, &buf[1]); + buf[3] = ad74115_crc(buf); +} + +static int ad74115_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct ad74115_state *st = context; + + ad74115_format_reg_write(reg, val, st->reg_tx_buf); + + return spi_write(st->spi, st->reg_tx_buf, AD74115_FRAME_SIZE); +} + +static int ad74115_crc_check(struct ad74115_state *st, u8 *buf) +{ + struct device *dev = &st->spi->dev; + u8 expected_crc = ad74115_crc(buf); + + if (buf[3] != expected_crc) { + dev_err(dev, "Bad CRC %02x for %02x%02x%02x, expected %02x\n", + buf[3], buf[0], buf[1], buf[2], expected_crc); + return -EINVAL; + } + + return 0; +} + +static int ad74115_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + struct ad74115_state *st = context; + struct spi_transfer reg_read_xfer[] = { + { + .tx_buf = st->reg_tx_buf, + .len = sizeof(st->reg_tx_buf), + .cs_change = 1, + }, + { + .rx_buf = st->reg_rx_buf, + .len = sizeof(st->reg_rx_buf), + }, + }; + int ret; + + ad74115_format_reg_write(AD74115_READ_SELECT_REG, reg, st->reg_tx_buf); + + ret = spi_sync_transfer(st->spi, reg_read_xfer, ARRAY_SIZE(reg_read_xfer)); + if (ret) + return ret; + + ret = ad74115_crc_check(st, st->reg_rx_buf); + if (ret) + return ret; + + *val = get_unaligned_be16(&st->reg_rx_buf[1]); + + return 0; +} + +static const struct regmap_config ad74115_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .reg_read = ad74115_reg_read, + .reg_write = ad74115_reg_write, +}; + +static int ad74115_gpio_config_set(struct ad74115_state *st, unsigned int offset, + enum ad74115_gpio_config cfg) +{ + return regmap_update_bits(st->regmap, AD74115_GPIO_CONFIG_X_REG(offset), + AD74115_GPIO_CONFIG_SELECT_MASK, + FIELD_PREP(AD74115_GPIO_CONFIG_SELECT_MASK, cfg)); +} + +static int ad74115_gpio_init_valid_mask(struct gpio_chip *gc, + unsigned long *valid_mask, + unsigned int ngpios) +{ + struct ad74115_state *st = gpiochip_get_data(gc); + + *valid_mask = st->gpio_valid_mask; + + return 0; +} + +static int ad74115_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + struct ad74115_state *st = gpiochip_get_data(gc); + unsigned int val; + int ret; + + ret = regmap_read(st->regmap, AD74115_GPIO_CONFIG_X_REG(offset), &val); + if (ret) + return ret; + + return FIELD_GET(AD74115_GPIO_CONFIG_SELECT_MASK, val) == AD74115_GPIO_CONFIG_INPUT; +} + +static int ad74115_gpio_direction_input(struct gpio_chip *gc, unsigned int offset) +{ + struct ad74115_state *st = gpiochip_get_data(gc); + + return ad74115_gpio_config_set(st, offset, AD74115_GPIO_CONFIG_INPUT); +} + +static int ad74115_gpio_direction_output(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct ad74115_state *st = gpiochip_get_data(gc); + + return ad74115_gpio_config_set(st, offset, AD74115_GPIO_CONFIG_OUTPUT_BUFFERED); +} + +static int ad74115_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct ad74115_state *st = gpiochip_get_data(gc); + unsigned int val; + int ret; + + ret = regmap_read(st->regmap, AD74115_GPIO_CONFIG_X_REG(offset), &val); + if (ret) + return ret; + + return FIELD_GET(AD74115_GPIO_CONFIG_GPI_DATA, val); +} + +static int ad74115_gpio_set(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct ad74115_state *st = gpiochip_get_data(gc); + + return regmap_update_bits(st->regmap, + AD74115_GPIO_CONFIG_X_REG(offset), + AD74115_GPIO_CONFIG_GPO_DATA, + FIELD_PREP(AD74115_GPIO_CONFIG_GPO_DATA, + value)); +} + +static int ad74115_set_comp_debounce(struct ad74115_state *st, unsigned int val) +{ + unsigned int len = ARRAY_SIZE(ad74115_debounce_tbl); + unsigned int i; + + for (i = 0; i < len; i++) + if (val <= ad74115_debounce_tbl[i]) + break; + + if (i == len) + i = len - 1; + + return regmap_update_bits(st->regmap, AD74115_DIN_CONFIG1_REG, + AD74115_DIN_DEBOUNCE_MASK, + FIELD_PREP(AD74115_DIN_DEBOUNCE_MASK, val)); +} + +static int ad74115_comp_gpio_get_direction(struct gpio_chip *chip, + unsigned int offset) +{ + return GPIO_LINE_DIRECTION_IN; +} + +static int ad74115_comp_gpio_set_config(struct gpio_chip *chip, + unsigned int offset, + unsigned long config) +{ + struct ad74115_state *st = gpiochip_get_data(chip); + u32 param = pinconf_to_config_param(config); + u32 arg = pinconf_to_config_argument(config); + + switch (param) { + case PIN_CONFIG_INPUT_DEBOUNCE: + return ad74115_set_comp_debounce(st, arg); + default: + return -ENOTSUPP; + } +} + +static int ad74115_comp_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct ad74115_state *st = gpiochip_get_data(chip); + unsigned int val; + int ret; + + ret = regmap_read(st->regmap, AD74115_DIN_COMP_OUT_REG, &val); + if (ret) + return ret; + + return !!val; +} + +static irqreturn_t ad74115_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad74115_state *st = iio_priv(indio_dev); + int ret; + + ret = spi_sync(st->spi, &st->adc_samples_msg); + if (ret) + goto out; + + iio_push_to_buffers(indio_dev, st->adc_samples_rx_buf); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static irqreturn_t ad74115_adc_data_interrupt(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct ad74115_state *st = iio_priv(indio_dev); + + if (iio_buffer_enabled(indio_dev)) + iio_trigger_poll(st->trig); + else + complete(&st->adc_data_completion); + + return IRQ_HANDLED; +} + +static int ad74115_set_adc_ch_en(struct ad74115_state *st, + enum ad74115_adc_ch channel, bool status) +{ + unsigned int mask = ad74115_adc_ch_en_bit_tbl[channel]; + + return regmap_update_bits(st->regmap, AD74115_ADC_CONV_CTRL_REG, mask, + status ? mask : 0); +} + +static int ad74115_set_adc_conv_seq(struct ad74115_state *st, + enum ad74115_adc_conv_seq conv_seq) +{ + return regmap_update_bits(st->regmap, AD74115_ADC_CONV_CTRL_REG, + AD74115_ADC_CONV_SEQ_MASK, + FIELD_PREP(AD74115_ADC_CONV_SEQ_MASK, conv_seq)); +} + +static int ad74115_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *active_scan_mask) +{ + struct ad74115_state *st = iio_priv(indio_dev); + struct spi_transfer *xfer = st->adc_samples_xfer; + u8 *rx_buf = st->adc_samples_rx_buf; + u8 *tx_buf = st->adc_samples_tx_buf; + unsigned int i; + int ret = 0; + + mutex_lock(&st->lock); + + spi_message_init(&st->adc_samples_msg); + + for_each_clear_bit(i, active_scan_mask, AD74115_ADC_CH_NUM) { + ret = ad74115_set_adc_ch_en(st, i, false); + if (ret) + goto out; + } + + /* + * The read select register is used to select which register's value + * will be sent by the slave on the next SPI frame. + * + * Create an SPI message that, on each step, writes to the read select + * register to select the ADC result of the next enabled channel, and + * reads the ADC result of the previous enabled channel. + * + * Example: + * W: [WCH1] [WCH2] [WCH2] [WCH3] [ ] + * R: [ ] [RCH1] [RCH2] [RCH3] [RCH4] + */ + for_each_set_bit(i, active_scan_mask, AD74115_ADC_CH_NUM) { + ret = ad74115_set_adc_ch_en(st, i, true); + if (ret) + goto out; + + if (xfer == st->adc_samples_xfer) + xfer->rx_buf = NULL; + else + xfer->rx_buf = rx_buf; + + xfer->tx_buf = tx_buf; + xfer->len = AD74115_FRAME_SIZE; + xfer->cs_change = 1; + + ad74115_format_reg_write(AD74115_READ_SELECT_REG, + ad74115_adc_ch_data_regs_tbl[i], tx_buf); + + spi_message_add_tail(xfer, &st->adc_samples_msg); + + tx_buf += AD74115_FRAME_SIZE; + if (xfer != st->adc_samples_xfer) + rx_buf += AD74115_FRAME_SIZE; + xfer++; + } + + xfer->rx_buf = rx_buf; + xfer->tx_buf = NULL; + xfer->len = AD74115_FRAME_SIZE; + xfer->cs_change = 0; + + spi_message_add_tail(xfer, &st->adc_samples_msg); + +out: + mutex_unlock(&st->lock); + + return ret; +} + +static int ad74115_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad74115_state *st = iio_priv(indio_dev); + + return ad74115_set_adc_conv_seq(st, AD74115_ADC_CONV_SEQ_CONTINUOUS); +} + +static int ad74115_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad74115_state *st = iio_priv(indio_dev); + unsigned int i; + int ret; + + mutex_lock(&st->lock); + + ret = ad74115_set_adc_conv_seq(st, AD74115_ADC_CONV_SEQ_STANDBY); + if (ret) + goto out; + + /* + * update_scan_mode() is not called in the disable path, disable all + * channels here. + */ + for (i = 0; i < AD74115_ADC_CH_NUM; i++) { + ret = ad74115_set_adc_ch_en(st, i, false); + if (ret) + goto out; + } + +out: + mutex_unlock(&st->lock); + + return ret; +} + +static const struct iio_buffer_setup_ops ad74115_buffer_ops = { + .postenable = &ad74115_buffer_postenable, + .predisable = &ad74115_buffer_predisable, +}; + +static const struct iio_trigger_ops ad74115_trigger_ops = { + .validate_device = iio_trigger_validate_own_device, +}; + +static int ad74115_get_adc_rate(struct ad74115_state *st, + enum ad74115_adc_ch channel, int *val) +{ + unsigned int i; + int ret; + + ret = regmap_read(st->regmap, AD74115_ADC_CONFIG_REG, &i); + if (ret) + return ret; + + if (channel == AD74115_ADC_CH_CONV1) + i = FIELD_GET(AD74115_ADC_CONFIG_CONV1_RATE_MASK, i); + else + i = FIELD_GET(AD74115_ADC_CONFIG_CONV2_RATE_MASK, i); + + *val = ad74115_adc_conv_rate_tbl[i]; + + return IIO_VAL_INT; +} + +static int _ad74115_get_adc_code(struct ad74115_state *st, + enum ad74115_adc_ch channel, int *val) +{ + unsigned int uval; + int ret; + + reinit_completion(&st->adc_data_completion); + + ret = ad74115_set_adc_ch_en(st, channel, true); + if (ret) + return ret; + + ret = ad74115_set_adc_conv_seq(st, AD74115_ADC_CONV_SEQ_SINGLE); + if (ret) + return ret; + + if (st->irq) { + ret = wait_for_completion_timeout(&st->adc_data_completion, + msecs_to_jiffies(1000)); + if (!ret) + return -ETIMEDOUT; + } else { + unsigned int regval, wait_time; + int rate; + + ret = ad74115_get_adc_rate(st, channel, &rate); + if (ret < 0) + return ret; + + wait_time = DIV_ROUND_CLOSEST(AD74115_CONV_TIME_US, rate); + + ret = regmap_read_poll_timeout(st->regmap, AD74115_LIVE_STATUS_REG, + regval, regval & AD74115_ADC_DATA_RDY_MASK, + wait_time, 5 * wait_time); + if (ret) + return ret; + + /* + * The ADC_DATA_RDY bit is W1C. + * See datasheet page 98, Table 62. Bit Descriptions for + * LIVE_STATUS. + * Although the datasheet mentions that the bit will auto-clear + * when writing to the ADC_CONV_CTRL register, this does not + * seem to happen. + */ + ret = regmap_write_bits(st->regmap, AD74115_LIVE_STATUS_REG, + AD74115_ADC_DATA_RDY_MASK, + FIELD_PREP(AD74115_ADC_DATA_RDY_MASK, 1)); + if (ret) + return ret; + } + + ret = regmap_read(st->regmap, ad74115_adc_ch_data_regs_tbl[channel], &uval); + if (ret) + return ret; + + ret = ad74115_set_adc_conv_seq(st, AD74115_ADC_CONV_SEQ_STANDBY); + if (ret) + return ret; + + ret = ad74115_set_adc_ch_en(st, channel, false); + if (ret) + return ret; + + *val = uval; + + return IIO_VAL_INT; +} + +static int ad74115_get_adc_code(struct iio_dev *indio_dev, + enum ad74115_adc_ch channel, int *val) +{ + struct ad74115_state *st = iio_priv(indio_dev); + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + mutex_lock(&st->lock); + ret = _ad74115_get_adc_code(st, channel, val); + mutex_unlock(&st->lock); + + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ad74115_adc_code_to_resistance(int code, int *val, int *val2) +{ + if (code == AD74115_ADC_CODE_MAX) + code--; + + *val = code * AD74115_REF_RESISTOR_OHMS; + *val2 = AD74115_ADC_CODE_MAX - code; + + return IIO_VAL_FRACTIONAL; +} + +static int ad74115_set_dac_code(struct ad74115_state *st, + enum ad74115_dac_ch channel, int val) +{ + if (val < 0) + return -EINVAL; + + if (channel == AD74115_DAC_CH_COMPARATOR) { + if (val > AD74115_COMP_THRESH_MAX) + return -EINVAL; + + return regmap_update_bits(st->regmap, AD74115_DIN_CONFIG2_REG, + AD74115_COMP_THRESH_MASK, + FIELD_PREP(AD74115_COMP_THRESH_MASK, val)); + } + + if (val > AD74115_DAC_CODE_MAX) + return -EINVAL; + + return regmap_write(st->regmap, AD74115_DAC_CODE_REG, val); +} + +static int ad74115_get_dac_code(struct ad74115_state *st, + enum ad74115_dac_ch channel, int *val) +{ + unsigned int uval; + int ret; + + if (channel == AD74115_DAC_CH_COMPARATOR) + return -EINVAL; + + ret = regmap_read(st->regmap, AD74115_DAC_ACTIVE_REG, &uval); + if (ret) + return ret; + + *val = uval; + + return IIO_VAL_INT; +} + +static int ad74115_set_adc_rate(struct ad74115_state *st, + enum ad74115_adc_ch channel, int val) +{ + unsigned int i; + int ret; + + ret = ad74115_find_tbl_index(ad74115_adc_conv_rate_tbl, val, &i); + if (ret) + return ret; + + if (channel == AD74115_ADC_CH_CONV1) + return regmap_update_bits(st->regmap, AD74115_ADC_CONFIG_REG, + AD74115_ADC_CONFIG_CONV1_RATE_MASK, + FIELD_PREP(AD74115_ADC_CONFIG_CONV1_RATE_MASK, i)); + + return regmap_update_bits(st->regmap, AD74115_ADC_CONFIG_REG, + AD74115_ADC_CONFIG_CONV2_RATE_MASK, + FIELD_PREP(AD74115_ADC_CONFIG_CONV2_RATE_MASK, i)); +} + +static int ad74115_get_dac_rate(struct ad74115_state *st, int *val) +{ + unsigned int i, en_val, step_val, rate_val, tmp; + int ret; + + ret = regmap_read(st->regmap, AD74115_OUTPUT_CONFIG_REG, &tmp); + if (ret) + return ret; + + en_val = FIELD_GET(AD74115_OUTPUT_SLEW_EN_MASK, tmp); + step_val = FIELD_GET(AD74115_OUTPUT_SLEW_LIN_STEP_MASK, tmp); + rate_val = FIELD_GET(AD74115_OUTPUT_SLEW_LIN_RATE_MASK, tmp); + + for (i = 0; i < ARRAY_SIZE(ad74115_dac_rate_step_tbl); i++) + if (en_val == ad74115_dac_rate_step_tbl[i][0] && + step_val == ad74115_dac_rate_step_tbl[i][1] && + rate_val == ad74115_dac_rate_step_tbl[i][2]) + break; + + if (i == ARRAY_SIZE(ad74115_dac_rate_step_tbl)) + return -EINVAL; + + *val = ad74115_dac_rate_tbl[i]; + + return IIO_VAL_INT; +} + +static int ad74115_set_dac_rate(struct ad74115_state *st, int val) +{ + unsigned int i, en_val, step_val, rate_val, mask, tmp; + int ret; + + ret = ad74115_find_tbl_index(ad74115_dac_rate_tbl, val, &i); + if (ret) + return ret; + + en_val = ad74115_dac_rate_step_tbl[i][0]; + step_val = ad74115_dac_rate_step_tbl[i][1]; + rate_val = ad74115_dac_rate_step_tbl[i][2]; + + mask = AD74115_OUTPUT_SLEW_EN_MASK; + mask |= AD74115_OUTPUT_SLEW_LIN_STEP_MASK; + mask |= AD74115_OUTPUT_SLEW_LIN_RATE_MASK; + + tmp = FIELD_PREP(AD74115_OUTPUT_SLEW_EN_MASK, en_val); + tmp |= FIELD_PREP(AD74115_OUTPUT_SLEW_LIN_STEP_MASK, step_val); + tmp |= FIELD_PREP(AD74115_OUTPUT_SLEW_LIN_RATE_MASK, rate_val); + + return regmap_update_bits(st->regmap, AD74115_OUTPUT_CONFIG_REG, mask, tmp); +} + +static int ad74115_get_dac_scale(struct ad74115_state *st, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + if (chan->channel == AD74115_DAC_CH_MAIN) { + if (chan->type == IIO_VOLTAGE) { + *val = AD74115_DAC_VOLTAGE_MAX; + + if (st->dac_bipolar) + *val *= 2; + + } else { + *val = AD74115_DAC_CURRENT_MAX; + } + + *val2 = AD74115_DAC_CODE_MAX; + } else { + if (st->din_threshold_mode == AD74115_DIN_THRESHOLD_MODE_AVDD) { + *val = 196 * st->avdd_mv; + *val2 = 10 * AD74115_COMP_THRESH_MAX; + } else { + *val = 49000; + *val2 = AD74115_COMP_THRESH_MAX; + } + } + + return IIO_VAL_FRACTIONAL; +} + +static int ad74115_get_dac_offset(struct ad74115_state *st, + struct iio_chan_spec const *chan, int *val) +{ + if (chan->channel == AD74115_DAC_CH_MAIN) { + if (chan->type == IIO_VOLTAGE && st->dac_bipolar) + *val = -AD74115_DAC_CODE_HALF; + else + *val = 0; + } else { + if (st->din_threshold_mode == AD74115_DIN_THRESHOLD_MODE_AVDD) + *val = -48; + else + *val = -38; + } + + return IIO_VAL_INT; +} + +static int ad74115_get_adc_range(struct ad74115_state *st, + enum ad74115_adc_ch channel, unsigned int *val) +{ + int ret; + + ret = regmap_read(st->regmap, AD74115_ADC_CONFIG_REG, val); + if (ret) + return ret; + + if (channel == AD74115_ADC_CH_CONV1) + *val = FIELD_GET(AD74115_ADC_CONFIG_CONV1_RANGE_MASK, *val); + else + *val = FIELD_GET(AD74115_ADC_CONFIG_CONV2_RANGE_MASK, *val); + + return 0; +} + +static int ad74115_get_adc_resistance_scale(struct ad74115_state *st, + unsigned int range, + int *val, int *val2) +{ + *val = ad74115_adc_gain_tbl[range][1] * AD74115_REF_RESISTOR_OHMS; + *val2 = ad74115_adc_gain_tbl[range][0]; + + if (ad74115_adc_bipolar_tbl[range]) + *val2 *= AD74115_ADC_CODE_HALF; + else + *val2 *= AD74115_ADC_CODE_MAX; + + return IIO_VAL_FRACTIONAL; +} + +static int ad74115_get_adc_scale(struct ad74115_state *st, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + unsigned int range; + int ret; + + ret = ad74115_get_adc_range(st, chan->channel, &range); + if (ret) + return ret; + + if (chan->type == IIO_RESISTANCE) + return ad74115_get_adc_resistance_scale(st, range, val, val2); + + *val = ad74115_adc_conv_mul_tbl[range]; + *val2 = AD74115_ADC_CODE_MAX; + + if (chan->type == IIO_CURRENT) + *val2 *= AD74115_SENSE_RESISTOR_OHMS; + + return IIO_VAL_FRACTIONAL; +} + +static int ad74115_get_adc_resistance_offset(struct ad74115_state *st, + unsigned int range, + int *val, int *val2) +{ + unsigned int d = 10 * AD74115_REF_RESISTOR_OHMS + * ad74115_adc_gain_tbl[range][1]; + + *val = 5; + + if (ad74115_adc_bipolar_tbl[range]) + *val -= AD74115_ADC_CODE_HALF; + + *val *= d; + + if (!st->rtd_mode_4_wire) { + /* Add 0.2 Ohm to the final result for 3-wire RTD. */ + unsigned int v = 2 * ad74115_adc_gain_tbl[range][0]; + + if (ad74115_adc_bipolar_tbl[range]) + v *= AD74115_ADC_CODE_HALF; + else + v *= AD74115_ADC_CODE_MAX; + + *val += v; + } + + *val2 = d; + + return IIO_VAL_FRACTIONAL; +} + +static int ad74115_get_adc_offset(struct ad74115_state *st, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + unsigned int range; + int ret; + + ret = ad74115_get_adc_range(st, chan->channel, &range); + if (ret) + return ret; + + if (chan->type == IIO_RESISTANCE) + return ad74115_get_adc_resistance_offset(st, range, val, val2); + + if (ad74115_adc_bipolar_tbl[range]) + *val = -AD74115_ADC_CODE_HALF; + else if (range == AD74115_ADC_RANGE_2_5V_NEG) + *val = -AD74115_ADC_CODE_MAX; + else + *val = 0; + + return IIO_VAL_INT; +} + +static int ad74115_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad74115_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (chan->output) + return ad74115_get_dac_code(st, chan->channel, val); + + return ad74115_get_adc_code(indio_dev, chan->channel, val); + case IIO_CHAN_INFO_PROCESSED: + ret = ad74115_get_adc_code(indio_dev, chan->channel, val); + if (ret) + return ret; + + return ad74115_adc_code_to_resistance(*val, val, val2); + case IIO_CHAN_INFO_SCALE: + if (chan->output) + return ad74115_get_dac_scale(st, chan, val, val2); + + return ad74115_get_adc_scale(st, chan, val, val2); + case IIO_CHAN_INFO_OFFSET: + if (chan->output) + return ad74115_get_dac_offset(st, chan, val); + + return ad74115_get_adc_offset(st, chan, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + if (chan->output) + return ad74115_get_dac_rate(st, val); + + return ad74115_get_adc_rate(st, chan->channel, val); + default: + return -EINVAL; + } +} + +static int ad74115_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, + long info) +{ + struct ad74115_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (!chan->output) + return -EINVAL; + + return ad74115_set_dac_code(st, chan->channel, val); + case IIO_CHAN_INFO_SAMP_FREQ: + if (chan->output) + return ad74115_set_dac_rate(st, val); + + return ad74115_set_adc_rate(st, chan->channel, val); + default: + return -EINVAL; + } +} + +static int ad74115_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, long info) +{ + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (chan->output) { + *vals = ad74115_dac_rate_tbl; + *length = ARRAY_SIZE(ad74115_dac_rate_tbl); + } else { + *vals = ad74115_adc_conv_rate_tbl; + *length = ARRAY_SIZE(ad74115_adc_conv_rate_tbl); + } + + *type = IIO_VAL_INT; + + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int ad74115_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct ad74115_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); +} + +static const struct iio_info ad74115_info = { + .read_raw = ad74115_read_raw, + .write_raw = ad74115_write_raw, + .read_avail = ad74115_read_avail, + .update_scan_mode = ad74115_update_scan_mode, + .debugfs_reg_access = ad74115_reg_access, +}; + +#define AD74115_DAC_CHANNEL(_type, index) \ + { \ + .type = (_type), \ + .channel = (index), \ + .indexed = 1, \ + .output = 1, \ + .scan_index = -1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \ + | BIT(IIO_CHAN_INFO_SCALE) \ + | BIT(IIO_CHAN_INFO_OFFSET), \ + } + +#define _AD74115_ADC_CHANNEL(_type, index, extra_mask_separate) \ + { \ + .type = (_type), \ + .channel = (index), \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \ + | BIT(IIO_CHAN_INFO_SAMP_FREQ) \ + | (extra_mask_separate), \ + .info_mask_separate_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = index, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 32, \ + .shift = 8, \ + .endianness = IIO_BE, \ + }, \ + } + +#define AD74115_ADC_CHANNEL(_type, index) \ + _AD74115_ADC_CHANNEL(_type, index, BIT(IIO_CHAN_INFO_SCALE) \ + | BIT(IIO_CHAN_INFO_OFFSET)) + +static const struct iio_chan_spec ad74115_voltage_input_channels[] = { + AD74115_ADC_CHANNEL(IIO_VOLTAGE, AD74115_ADC_CH_CONV1), + AD74115_ADC_CHANNEL(IIO_VOLTAGE, AD74115_ADC_CH_CONV2), +}; + +static const struct iio_chan_spec ad74115_voltage_output_channels[] = { + AD74115_DAC_CHANNEL(IIO_VOLTAGE, AD74115_DAC_CH_MAIN), + AD74115_ADC_CHANNEL(IIO_CURRENT, AD74115_ADC_CH_CONV1), + AD74115_ADC_CHANNEL(IIO_VOLTAGE, AD74115_ADC_CH_CONV2), +}; + +static const struct iio_chan_spec ad74115_current_input_channels[] = { + AD74115_ADC_CHANNEL(IIO_CURRENT, AD74115_ADC_CH_CONV1), + AD74115_ADC_CHANNEL(IIO_VOLTAGE, AD74115_ADC_CH_CONV2), +}; + +static const struct iio_chan_spec ad74115_current_output_channels[] = { + AD74115_DAC_CHANNEL(IIO_CURRENT, AD74115_DAC_CH_MAIN), + AD74115_ADC_CHANNEL(IIO_VOLTAGE, AD74115_ADC_CH_CONV1), + AD74115_ADC_CHANNEL(IIO_VOLTAGE, AD74115_ADC_CH_CONV2), +}; + +static const struct iio_chan_spec ad74115_2_wire_resistance_input_channels[] = { + _AD74115_ADC_CHANNEL(IIO_RESISTANCE, AD74115_ADC_CH_CONV1, + BIT(IIO_CHAN_INFO_PROCESSED)), + AD74115_ADC_CHANNEL(IIO_VOLTAGE, AD74115_ADC_CH_CONV2), +}; + +static const struct iio_chan_spec ad74115_3_4_wire_resistance_input_channels[] = { + AD74115_ADC_CHANNEL(IIO_RESISTANCE, AD74115_ADC_CH_CONV1), + AD74115_ADC_CHANNEL(IIO_VOLTAGE, AD74115_ADC_CH_CONV2), +}; + +static const struct iio_chan_spec ad74115_digital_input_logic_channels[] = { + AD74115_DAC_CHANNEL(IIO_VOLTAGE, AD74115_DAC_CH_COMPARATOR), + AD74115_ADC_CHANNEL(IIO_VOLTAGE, AD74115_ADC_CH_CONV1), + AD74115_ADC_CHANNEL(IIO_VOLTAGE, AD74115_ADC_CH_CONV2), +}; + +static const struct iio_chan_spec ad74115_digital_input_loop_channels[] = { + AD74115_DAC_CHANNEL(IIO_CURRENT, AD74115_DAC_CH_MAIN), + AD74115_DAC_CHANNEL(IIO_VOLTAGE, AD74115_DAC_CH_COMPARATOR), + AD74115_ADC_CHANNEL(IIO_VOLTAGE, AD74115_ADC_CH_CONV1), + AD74115_ADC_CHANNEL(IIO_VOLTAGE, AD74115_ADC_CH_CONV2), +}; + +#define _AD74115_CHANNELS(_channels) \ + { \ + .channels = _channels, \ + .num_channels = ARRAY_SIZE(_channels), \ + } + +#define AD74115_CHANNELS(name) \ + _AD74115_CHANNELS(ad74115_ ## name ## _channels) + +static const struct ad74115_channels ad74115_channels_map[AD74115_CH_FUNC_NUM] = { + [AD74115_CH_FUNC_HIGH_IMPEDANCE] = AD74115_CHANNELS(voltage_input), + [AD74115_CH_FUNC_VOLTAGE_INPUT] = AD74115_CHANNELS(voltage_input), + + [AD74115_CH_FUNC_VOLTAGE_OUTPUT] = AD74115_CHANNELS(voltage_output), + + [AD74115_CH_FUNC_CURRENT_INPUT_EXT_POWER] = AD74115_CHANNELS(current_input), + [AD74115_CH_FUNC_CURRENT_INPUT_LOOP_POWER] = AD74115_CHANNELS(current_input), + [AD74115_CH_FUNC_CURRENT_INPUT_EXT_POWER_HART] = AD74115_CHANNELS(current_input), + [AD74115_CH_FUNC_CURRENT_INPUT_LOOP_POWER_HART] = AD74115_CHANNELS(current_input), + + [AD74115_CH_FUNC_CURRENT_OUTPUT] = AD74115_CHANNELS(current_output), + [AD74115_CH_FUNC_CURRENT_OUTPUT_HART] = AD74115_CHANNELS(current_output), + + [AD74115_CH_FUNC_2_WIRE_RESISTANCE_INPUT] = AD74115_CHANNELS(2_wire_resistance_input), + [AD74115_CH_FUNC_3_4_WIRE_RESISTANCE_INPUT] = AD74115_CHANNELS(3_4_wire_resistance_input), + + [AD74115_CH_FUNC_DIGITAL_INPUT_LOGIC] = AD74115_CHANNELS(digital_input_logic), + + [AD74115_CH_FUNC_DIGITAL_INPUT_LOOP_POWER] = AD74115_CHANNELS(digital_input_loop), +}; + +#define AD74115_GPIO_MODE_FW_PROP(i) \ +{ \ + .name = "adi,gpio" __stringify(i) "-mode", \ + .reg = AD74115_GPIO_CONFIG_X_REG(i), \ + .mask = AD74115_GPIO_CONFIG_SELECT_MASK, \ + .lookup_tbl = ad74115_gpio_mode_tbl, \ + .lookup_tbl_len = ARRAY_SIZE(ad74115_gpio_mode_tbl), \ +} + +static const struct ad74115_fw_prop ad74115_gpio_mode_fw_props[] = { + AD74115_GPIO_MODE_FW_PROP(0), + AD74115_GPIO_MODE_FW_PROP(1), + AD74115_GPIO_MODE_FW_PROP(2), + AD74115_GPIO_MODE_FW_PROP(3), +}; + +static const struct ad74115_fw_prop ad74115_din_threshold_mode_fw_prop = + AD74115_FW_PROP_BOOL("adi,digital-input-threshold-mode-fixed", + AD74115_DIN_CONFIG2_REG, BIT(7)); + +static const struct ad74115_fw_prop ad74115_dac_bipolar_fw_prop = + AD74115_FW_PROP_BOOL("adi,dac-bipolar", AD74115_OUTPUT_CONFIG_REG, BIT(7)); + +static const struct ad74115_fw_prop ad74115_ch_func_fw_prop = + AD74115_FW_PROP("adi,ch-func", AD74115_CH_FUNC_MAX, + AD74115_CH_FUNC_SETUP_REG, GENMASK(3, 0)); + +static const struct ad74115_fw_prop ad74115_rtd_mode_fw_prop = + AD74115_FW_PROP_BOOL("adi,4-wire-rtd", AD74115_RTD3W4W_CONFIG_REG, BIT(3)); + +static const struct ad74115_fw_prop ad74115_din_range_fw_prop = + AD74115_FW_PROP_BOOL("adi,digital-input-sink-range-high", + AD74115_DIN_CONFIG1_REG, BIT(12)); + +static const struct ad74115_fw_prop ad74115_ext2_burnout_current_fw_prop = + AD74115_FW_PROP_TBL("adi,ext2-burnout-current-nanoamp", + ad74115_burnout_current_na_tbl, + AD74115_BURNOUT_CONFIG_REG, GENMASK(14, 12)); + +static const struct ad74115_fw_prop ad74115_ext1_burnout_current_fw_prop = + AD74115_FW_PROP_TBL("adi,ext1-burnout-current-nanoamp", + ad74115_burnout_current_na_tbl, + AD74115_BURNOUT_CONFIG_REG, GENMASK(9, 7)); + +static const struct ad74115_fw_prop ad74115_viout_burnout_current_fw_prop = + AD74115_FW_PROP_TBL("adi,viout-burnout-current-nanoamp", + ad74115_viout_burnout_current_na_tbl, + AD74115_BURNOUT_CONFIG_REG, GENMASK(4, 2)); + +static const struct ad74115_fw_prop ad74115_fw_props[] = { + AD74115_FW_PROP("adi,conv2-mux", 3, + AD74115_ADC_CONFIG_REG, GENMASK(3, 2)), + + AD74115_FW_PROP_BOOL_NEG("adi,sense-agnd-buffer-low-power", + AD74115_PWR_OPTIM_CONFIG_REG, BIT(4)), + AD74115_FW_PROP_BOOL_NEG("adi,lf-buffer-low-power", + AD74115_PWR_OPTIM_CONFIG_REG, BIT(3)), + AD74115_FW_PROP_BOOL_NEG("adi,hf-buffer-low-power", + AD74115_PWR_OPTIM_CONFIG_REG, BIT(2)), + AD74115_FW_PROP_BOOL_NEG("adi,ext2-buffer-low-power", + AD74115_PWR_OPTIM_CONFIG_REG, BIT(1)), + AD74115_FW_PROP_BOOL_NEG("adi,ext1-buffer-low-power", + AD74115_PWR_OPTIM_CONFIG_REG, BIT(0)), + + AD74115_FW_PROP_BOOL("adi,comparator-invert", + AD74115_DIN_CONFIG1_REG, BIT(14)), + AD74115_FW_PROP_BOOL("adi,digital-input-debounce-mode-counter-reset", + AD74115_DIN_CONFIG1_REG, BIT(6)), + + AD74115_FW_PROP_BOOL("adi,digital-input-unbuffered", + AD74115_DIN_CONFIG2_REG, BIT(10)), + AD74115_FW_PROP_BOOL("adi,digital-input-short-circuit-detection", + AD74115_DIN_CONFIG2_REG, BIT(9)), + AD74115_FW_PROP_BOOL("adi,digital-input-open-circuit-detection", + AD74115_DIN_CONFIG2_REG, BIT(8)), + + AD74115_FW_PROP_BOOL("adi,dac-current-limit-low", + AD74115_OUTPUT_CONFIG_REG, BIT(0)), + + AD74115_FW_PROP_BOOL("adi,3-wire-rtd-excitation-swap", + AD74115_RTD3W4W_CONFIG_REG, BIT(2)), + AD74115_FW_PROP_TBL("adi,rtd-excitation-current-microamp", + ad74115_rtd_excitation_current_ua_tbl, + AD74115_RTD3W4W_CONFIG_REG, GENMASK(1, 0)), + + AD74115_FW_PROP_BOOL("adi,ext2-burnout-current-polarity-sourcing", + AD74115_BURNOUT_CONFIG_REG, BIT(11)), + AD74115_FW_PROP_BOOL("adi,ext1-burnout-current-polarity-sourcing", + AD74115_BURNOUT_CONFIG_REG, BIT(6)), + AD74115_FW_PROP_BOOL("adi,viout-burnout-current-polarity-sourcing", + AD74115_BURNOUT_CONFIG_REG, BIT(1)), + + AD74115_FW_PROP_BOOL("adi,charge-pump", + AD74115_CHARGE_PUMP_REG, BIT(0)), +}; + +static int ad74115_apply_fw_prop(struct ad74115_state *st, + const struct ad74115_fw_prop *prop, u32 *retval) +{ + struct device *dev = &st->spi->dev; + u32 val = 0; + int ret; + + if (prop->is_boolean) { + val = device_property_read_bool(dev, prop->name); + } else { + ret = device_property_read_u32(dev, prop->name, &val); + if (ret && prop->lookup_tbl) + val = prop->lookup_tbl[0]; + } + + *retval = val; + + if (prop->negate) + val = !val; + + if (prop->lookup_tbl) + ret = _ad74115_find_tbl_index(prop->lookup_tbl, + prop->lookup_tbl_len, val, &val); + else if (prop->max && val > prop->max) + ret = -EINVAL; + else + ret = 0; + + if (ret) + return dev_err_probe(dev, -EINVAL, + "Invalid value %u for prop %s\n", + val, prop->name); + + WARN(!prop->mask, "Prop %s mask is empty\n", prop->name); + + val = (val << __ffs(prop->mask)) & prop->mask; + + return regmap_update_bits(st->regmap, prop->reg, prop->mask, val); +} + +static int ad74115_setup_adc_conv2_range(struct ad74115_state *st) +{ + unsigned int tbl_len = ARRAY_SIZE(ad74115_adc_range_tbl); + const char *prop_name = "adi,conv2-range-microvolt"; + s32 vals[2] = { + ad74115_adc_range_tbl[0][0], + ad74115_adc_range_tbl[0][1], + }; + struct device *dev = &st->spi->dev; + unsigned int i; + + device_property_read_u32_array(dev, prop_name, vals, 2); + + for (i = 0; i < tbl_len; i++) + if (vals[0] == ad74115_adc_range_tbl[i][0] && + vals[1] == ad74115_adc_range_tbl[i][1]) + break; + + if (i == tbl_len) + return dev_err_probe(dev, -EINVAL, + "Invalid value %d, %d for prop %s\n", + vals[0], vals[1], prop_name); + + return regmap_update_bits(st->regmap, AD74115_ADC_CONFIG_REG, + AD74115_ADC_CONFIG_CONV2_RANGE_MASK, + FIELD_PREP(AD74115_ADC_CONFIG_CONV2_RANGE_MASK, i)); +} + +static int ad74115_setup_iio_channels(struct iio_dev *indio_dev) +{ + struct ad74115_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + struct iio_chan_spec *channels; + + channels = devm_kcalloc(dev, sizeof(*channels), + indio_dev->num_channels, GFP_KERNEL); + if (!channels) + return -ENOMEM; + + indio_dev->channels = channels; + + memcpy(channels, ad74115_channels_map[st->ch_func].channels, + sizeof(*channels) * ad74115_channels_map[st->ch_func].num_channels); + + if (channels[0].output && channels[0].channel == AD74115_DAC_CH_MAIN && + channels[0].type == IIO_VOLTAGE && !st->dac_hart_slew) { + channels[0].info_mask_separate |= BIT(IIO_CHAN_INFO_SAMP_FREQ); + channels[0].info_mask_separate_available |= BIT(IIO_CHAN_INFO_SAMP_FREQ); + } + + return 0; +} + +static int ad74115_setup_gpio_chip(struct ad74115_state *st) +{ + struct device *dev = &st->spi->dev; + + if (!st->gpio_valid_mask) + return 0; + + st->gc = (struct gpio_chip) { + .owner = THIS_MODULE, + .label = AD74115_NAME, + .base = -1, + .ngpio = AD74115_GPIO_NUM, + .parent = dev, + .can_sleep = true, + .init_valid_mask = ad74115_gpio_init_valid_mask, + .get_direction = ad74115_gpio_get_direction, + .direction_input = ad74115_gpio_direction_input, + .direction_output = ad74115_gpio_direction_output, + .get = ad74115_gpio_get, + .set = ad74115_gpio_set, + }; + + return devm_gpiochip_add_data(dev, &st->gc, st); +} + +static int ad74115_setup_comp_gpio_chip(struct ad74115_state *st) +{ + struct device *dev = &st->spi->dev; + u32 val; + int ret; + + ret = regmap_read(st->regmap, AD74115_DIN_CONFIG1_REG, &val); + if (ret) + return ret; + + if (!(val & AD74115_DIN_COMPARATOR_EN_MASK)) + return 0; + + st->comp_gc = (struct gpio_chip) { + .owner = THIS_MODULE, + .label = AD74115_NAME, + .base = -1, + .ngpio = 1, + .parent = dev, + .can_sleep = true, + .get_direction = ad74115_comp_gpio_get_direction, + .get = ad74115_comp_gpio_get, + .set_config = ad74115_comp_gpio_set_config, + }; + + return devm_gpiochip_add_data(dev, &st->comp_gc, st); +} + +static int ad74115_setup(struct iio_dev *indio_dev) +{ + struct ad74115_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + u32 val, din_range_high; + unsigned int i; + int ret; + + ret = ad74115_apply_fw_prop(st, &ad74115_ch_func_fw_prop, &val); + if (ret) + return ret; + + indio_dev->num_channels += ad74115_channels_map[val].num_channels; + st->ch_func = val; + + ret = ad74115_setup_adc_conv2_range(st); + if (ret) + return ret; + + val = device_property_read_bool(dev, "adi,dac-hart-slew"); + if (val) { + st->dac_hart_slew = val; + + ret = regmap_update_bits(st->regmap, AD74115_OUTPUT_CONFIG_REG, + AD74115_OUTPUT_SLEW_EN_MASK, + FIELD_PREP(AD74115_OUTPUT_SLEW_EN_MASK, + AD74115_SLEW_MODE_HART)); + if (ret) + return ret; + } + + ret = ad74115_apply_fw_prop(st, &ad74115_din_range_fw_prop, + &din_range_high); + if (ret) + return ret; + + ret = device_property_read_u32(dev, "adi,digital-input-sink-microamp", &val); + if (!ret) { + if (din_range_high) + val = DIV_ROUND_CLOSEST(val, AD74115_DIN_SINK_LOW_STEP); + else + val = DIV_ROUND_CLOSEST(val, AD74115_DIN_SINK_HIGH_STEP); + + if (val > AD74115_DIN_SINK_MAX) + val = AD74115_DIN_SINK_MAX; + + ret = regmap_update_bits(st->regmap, AD74115_DIN_CONFIG1_REG, + AD74115_DIN_SINK_MASK, + FIELD_PREP(AD74115_DIN_SINK_MASK, val)); + if (ret) + return ret; + } + + ret = ad74115_apply_fw_prop(st, &ad74115_din_threshold_mode_fw_prop, &val); + if (ret) + return ret; + + if (val == AD74115_DIN_THRESHOLD_MODE_AVDD && !st->avdd_mv) + return dev_err_probe(dev, -EINVAL, + "AVDD voltage is required for digital input threshold mode AVDD\n"); + + st->din_threshold_mode = val; + + ret = ad74115_apply_fw_prop(st, &ad74115_dac_bipolar_fw_prop, &val); + if (ret) + return ret; + + st->dac_bipolar = val; + + ret = ad74115_apply_fw_prop(st, &ad74115_rtd_mode_fw_prop, &val); + if (ret) + return ret; + + st->rtd_mode_4_wire = val; + + ret = ad74115_apply_fw_prop(st, &ad74115_ext2_burnout_current_fw_prop, &val); + if (ret) + return ret; + + if (val) { + ret = regmap_update_bits(st->regmap, AD74115_BURNOUT_CONFIG_REG, + AD74115_BURNOUT_EXT2_EN_MASK, + FIELD_PREP(AD74115_BURNOUT_EXT2_EN_MASK, 1)); + if (ret) + return ret; + } + + ret = ad74115_apply_fw_prop(st, &ad74115_ext1_burnout_current_fw_prop, &val); + if (ret) + return ret; + + if (val) { + ret = regmap_update_bits(st->regmap, AD74115_BURNOUT_CONFIG_REG, + AD74115_BURNOUT_EXT1_EN_MASK, + FIELD_PREP(AD74115_BURNOUT_EXT1_EN_MASK, 1)); + if (ret) + return ret; + } + + ret = ad74115_apply_fw_prop(st, &ad74115_viout_burnout_current_fw_prop, &val); + if (ret) + return ret; + + if (val) { + ret = regmap_update_bits(st->regmap, AD74115_BURNOUT_CONFIG_REG, + AD74115_BURNOUT_VIOUT_EN_MASK, + FIELD_PREP(AD74115_BURNOUT_VIOUT_EN_MASK, 1)); + if (ret) + return ret; + } + + for (i = 0; i < AD74115_GPIO_NUM; i++) { + ret = ad74115_apply_fw_prop(st, &ad74115_gpio_mode_fw_props[i], &val); + if (ret) + return ret; + + if (val == AD74115_GPIO_MODE_LOGIC) + st->gpio_valid_mask |= BIT(i); + } + + for (i = 0; i < ARRAY_SIZE(ad74115_fw_props); i++) { + ret = ad74115_apply_fw_prop(st, &ad74115_fw_props[i], &val); + if (ret) + return ret; + } + + ret = ad74115_setup_gpio_chip(st); + if (ret) + return ret; + + ret = ad74115_setup_comp_gpio_chip(st); + if (ret) + return ret; + + return ad74115_setup_iio_channels(indio_dev); +} + +static int ad74115_reset(struct ad74115_state *st) +{ + struct device *dev = &st->spi->dev; + struct gpio_desc *reset_gpio; + int ret; + + reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(reset_gpio)) + return dev_err_probe(dev, PTR_ERR(reset_gpio), + "Failed to find reset GPIO\n"); + + if (reset_gpio) { + fsleep(100); + + gpiod_set_value_cansleep(reset_gpio, 0); + } else { + ret = regmap_write(st->regmap, AD74115_CMD_KEY_REG, + AD74115_CMD_KEY_RESET1); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD74115_CMD_KEY_REG, + AD74115_CMD_KEY_RESET2); + if (ret) + return ret; + } + + fsleep(1000); + + return 0; +} + +static int ad74115_setup_trigger(struct iio_dev *indio_dev) +{ + struct ad74115_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + int ret; + + st->irq = fwnode_irq_get_byname(dev_fwnode(dev), "adc_rdy"); + + if (st->irq == -EPROBE_DEFER) + return -EPROBE_DEFER; + + if (st->irq < 0) { + st->irq = 0; + return 0; + } + + ret = devm_request_irq(dev, st->irq, ad74115_adc_data_interrupt, + 0, AD74115_NAME, indio_dev); + if (ret) + return ret; + + st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", AD74115_NAME, + iio_device_id(indio_dev)); + if (!st->trig) + return -ENOMEM; + + st->trig->ops = &ad74115_trigger_ops; + iio_trigger_set_drvdata(st->trig, st); + + ret = devm_iio_trigger_register(dev, st->trig); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(st->trig); + + return 0; +} + +static int ad74115_probe(struct spi_device *spi) +{ + static const char * const regulator_names[] = { + "avcc", "dvcc", "dovdd", "refin", + }; + struct device *dev = &spi->dev; + struct ad74115_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->spi = spi; + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + init_completion(&st->adc_data_completion); + + indio_dev->name = AD74115_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ad74115_info; + + ret = devm_regulator_get_enable_read_voltage(dev, "avdd"); + if (ret < 0) { + /* + * Since this is both a power supply and only optionally a + * reference voltage, make sure to enable it even when the + * voltage is not available. + */ + ret = devm_regulator_get_enable(dev, "avdd"); + if (ret) + return dev_err_probe(dev, ret, "failed to enable avdd\n"); + } else { + st->avdd_mv = ret / 1000; + } + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulator_names), + regulator_names); + if (ret) + return ret; + + st->regmap = devm_regmap_init(dev, NULL, st, &ad74115_regmap_config); + if (IS_ERR(st->regmap)) + return PTR_ERR(st->regmap); + + ret = ad74115_reset(st); + if (ret) + return ret; + + ret = ad74115_setup(indio_dev); + if (ret) + return ret; + + ret = ad74115_setup_trigger(indio_dev); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + ad74115_trigger_handler, + &ad74115_buffer_ops); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static int ad74115_unregister_driver(struct spi_driver *spi) +{ + spi_unregister_driver(spi); + + return 0; +} + +static int __init ad74115_register_driver(struct spi_driver *spi) +{ + crc8_populate_msb(ad74115_crc8_table, AD74115_CRC_POLYNOMIAL); + + return spi_register_driver(spi); +} + +static const struct spi_device_id ad74115_spi_id[] = { + { .name = "ad74115h" }, + { } +}; + +MODULE_DEVICE_TABLE(spi, ad74115_spi_id); + +static const struct of_device_id ad74115_dt_id[] = { + { .compatible = "adi,ad74115h" }, + { } +}; +MODULE_DEVICE_TABLE(of, ad74115_dt_id); + +static struct spi_driver ad74115_driver = { + .driver = { + .name = "ad74115", + .of_match_table = ad74115_dt_id, + }, + .probe = ad74115_probe, + .id_table = ad74115_spi_id, +}; + +module_driver(ad74115_driver, + ad74115_register_driver, ad74115_unregister_driver); + +MODULE_AUTHOR("Cosmin Tanislav "); +MODULE_DESCRIPTION("Analog Devices AD74115 ADDAC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/addac/ad74413r.c b/drivers/iio/addac/ad74413r.c new file mode 100644 index 000000000..2e5a58e48 --- /dev/null +++ b/drivers/iio/addac/ad74413r.c @@ -0,0 +1,1531 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 Analog Devices, Inc. + * Author: Cosmin Tanislav + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define AD74413R_CRC_POLYNOMIAL 0x7 +DECLARE_CRC8_TABLE(ad74413r_crc8_table); + +#define AD74413R_CHANNEL_MAX 4 + +#define AD74413R_FRAME_SIZE 4 + +struct ad74413r_chip_info { + const char *name; + bool hart_support; +}; + +struct ad74413r_channel_config { + u32 func; + u32 drive_strength; + bool gpo_comparator; + bool initialized; +}; + +struct ad74413r_channels { + const struct iio_chan_spec *channels; + unsigned int num_channels; +}; + +struct ad74413r_state { + struct ad74413r_channel_config channel_configs[AD74413R_CHANNEL_MAX]; + unsigned int gpo_gpio_offsets[AD74413R_CHANNEL_MAX]; + unsigned int comp_gpio_offsets[AD74413R_CHANNEL_MAX]; + struct gpio_chip gpo_gpiochip; + struct gpio_chip comp_gpiochip; + struct completion adc_data_completion; + unsigned int num_gpo_gpios; + unsigned int num_comparator_gpios; + u32 sense_resistor_ohms; + int refin_reg_uv; + /* + * Synchronize consecutive operations when doing a one-shot + * conversion and when updating the ADC samples SPI message. + */ + struct mutex lock; + + const struct ad74413r_chip_info *chip_info; + struct spi_device *spi; + struct regmap *regmap; + struct device *dev; + struct iio_trigger *trig; + + size_t adc_active_channels; + struct spi_message adc_samples_msg; + struct spi_transfer adc_samples_xfer[AD74413R_CHANNEL_MAX + 1]; + + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + */ + struct { + u8 rx_buf[AD74413R_FRAME_SIZE * AD74413R_CHANNEL_MAX]; + aligned_s64 timestamp; + } adc_samples_buf __aligned(IIO_DMA_MINALIGN); + + u8 adc_samples_tx_buf[AD74413R_FRAME_SIZE * AD74413R_CHANNEL_MAX]; + u8 reg_tx_buf[AD74413R_FRAME_SIZE]; + u8 reg_rx_buf[AD74413R_FRAME_SIZE]; +}; + +#define AD74413R_REG_NOP 0x00 + +#define AD74413R_REG_CH_FUNC_SETUP_X(x) (0x01 + (x)) +#define AD74413R_CH_FUNC_SETUP_MASK GENMASK(3, 0) + +#define AD74413R_REG_ADC_CONFIG_X(x) (0x05 + (x)) +#define AD74413R_ADC_CONFIG_RANGE_MASK GENMASK(7, 5) +#define AD74413R_ADC_CONFIG_REJECTION_MASK GENMASK(4, 3) +#define AD74413R_ADC_CONFIG_CH_200K_TO_GND BIT(2) +#define AD74413R_ADC_RANGE_10V 0b000 +#define AD74413R_ADC_RANGE_2P5V_EXT_POW 0b001 +#define AD74413R_ADC_RANGE_2P5V_INT_POW 0b010 +#define AD74413R_ADC_RANGE_5V_BI_DIR 0b011 +#define AD74413R_ADC_REJECTION_50_60 0b00 +#define AD74413R_ADC_REJECTION_NONE 0b01 +#define AD74413R_ADC_REJECTION_50_60_HART 0b10 +#define AD74413R_ADC_REJECTION_HART 0b11 + +#define AD74413R_REG_DIN_CONFIG_X(x) (0x09 + (x)) +#define AD74413R_DIN_DEBOUNCE_MASK GENMASK(4, 0) +#define AD74413R_DIN_DEBOUNCE_LEN BIT(5) +#define AD74413R_DIN_SINK_MASK GENMASK(9, 6) + +#define AD74413R_REG_DAC_CODE_X(x) (0x16 + (x)) +#define AD74413R_DAC_CODE_MAX GENMASK(12, 0) +#define AD74413R_DAC_VOLTAGE_MAX 11000 + +#define AD74413R_REG_GPO_PAR_DATA 0x0d +#define AD74413R_REG_GPO_CONFIG_X(x) (0x0e + (x)) +#define AD74413R_GPO_CONFIG_DATA_MASK BIT(3) +#define AD74413R_GPO_CONFIG_SELECT_MASK GENMASK(2, 0) +#define AD74413R_GPO_CONFIG_100K_PULL_DOWN 0b000 +#define AD74413R_GPO_CONFIG_LOGIC 0b001 +#define AD74413R_GPO_CONFIG_LOGIC_PARALLEL 0b010 +#define AD74413R_GPO_CONFIG_COMPARATOR 0b011 +#define AD74413R_GPO_CONFIG_HIGH_IMPEDANCE 0b100 + +#define AD74413R_REG_ADC_CONV_CTRL 0x23 +#define AD74413R_CONV_SEQ_MASK GENMASK(9, 8) +#define AD74413R_CONV_SEQ_ON 0b00 +#define AD74413R_CONV_SEQ_SINGLE 0b01 +#define AD74413R_CONV_SEQ_CONTINUOUS 0b10 +#define AD74413R_CONV_SEQ_OFF 0b11 +#define AD74413R_CH_EN_MASK(x) BIT(x) + +#define AD74413R_REG_DIN_COMP_OUT 0x25 + +#define AD74413R_REG_ADC_RESULT_X(x) (0x26 + (x)) +#define AD74413R_ADC_RESULT_MAX GENMASK(15, 0) + +#define AD74413R_REG_READ_SELECT 0x41 + +#define AD74413R_REG_CMD_KEY 0x44 +#define AD74413R_CMD_KEY_LDAC 0x953a +#define AD74413R_CMD_KEY_RESET1 0x15fa +#define AD74413R_CMD_KEY_RESET2 0xaf51 + +static const int ad74413r_adc_sampling_rates[] = { + 20, 4800, +}; + +static const int ad74413r_adc_sampling_rates_hart[] = { + 10, 20, 1200, 4800, +}; + +static int ad74413r_crc(u8 *buf) +{ + return crc8(ad74413r_crc8_table, buf, 3, 0); +} + +static void ad74413r_format_reg_write(u8 reg, u16 val, u8 *buf) +{ + buf[0] = reg; + put_unaligned_be16(val, &buf[1]); + buf[3] = ad74413r_crc(buf); +} + +static int ad74413r_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct ad74413r_state *st = context; + + ad74413r_format_reg_write(reg, val, st->reg_tx_buf); + + return spi_write(st->spi, st->reg_tx_buf, AD74413R_FRAME_SIZE); +} + +static int ad74413r_crc_check(struct ad74413r_state *st, u8 *buf) +{ + u8 expected_crc = ad74413r_crc(buf); + + if (buf[3] != expected_crc) { + dev_err(st->dev, "Bad CRC %02x for %02x%02x%02x\n", + buf[3], buf[0], buf[1], buf[2]); + return -EINVAL; + } + + return 0; +} + +static int ad74413r_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + struct ad74413r_state *st = context; + struct spi_transfer reg_read_xfer[] = { + { + .tx_buf = st->reg_tx_buf, + .len = AD74413R_FRAME_SIZE, + .cs_change = 1, + }, + { + .rx_buf = st->reg_rx_buf, + .len = AD74413R_FRAME_SIZE, + }, + }; + int ret; + + ad74413r_format_reg_write(AD74413R_REG_READ_SELECT, reg, + st->reg_tx_buf); + + ret = spi_sync_transfer(st->spi, reg_read_xfer, + ARRAY_SIZE(reg_read_xfer)); + if (ret) + return ret; + + ret = ad74413r_crc_check(st, st->reg_rx_buf); + if (ret) + return ret; + + *val = get_unaligned_be16(&st->reg_rx_buf[1]); + + return 0; +} + +static const struct regmap_config ad74413r_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .reg_read = ad74413r_reg_read, + .reg_write = ad74413r_reg_write, +}; + +static int ad74413r_set_gpo_config(struct ad74413r_state *st, + unsigned int offset, u8 mode) +{ + return regmap_update_bits(st->regmap, AD74413R_REG_GPO_CONFIG_X(offset), + AD74413R_GPO_CONFIG_SELECT_MASK, mode); +} + +static const unsigned int ad74413r_debounce_map[AD74413R_DIN_DEBOUNCE_LEN] = { + 0, 13, 18, 24, 32, 42, 56, 75, + 100, 130, 180, 240, 320, 420, 560, 750, + 1000, 1300, 1800, 2400, 3200, 4200, 5600, 7500, + 10000, 13000, 18000, 24000, 32000, 42000, 56000, 75000, +}; + +static int ad74413r_set_comp_debounce(struct ad74413r_state *st, + unsigned int offset, + unsigned int debounce) +{ + unsigned int val = AD74413R_DIN_DEBOUNCE_LEN - 1; + unsigned int i; + + for (i = 0; i < AD74413R_DIN_DEBOUNCE_LEN; i++) + if (debounce <= ad74413r_debounce_map[i]) { + val = i; + break; + } + + return regmap_update_bits(st->regmap, + AD74413R_REG_DIN_CONFIG_X(offset), + AD74413R_DIN_DEBOUNCE_MASK, + val); +} + +static int ad74413r_set_comp_drive_strength(struct ad74413r_state *st, + unsigned int offset, + unsigned int strength) +{ + strength = min(strength, 1800U); + + return regmap_update_bits(st->regmap, AD74413R_REG_DIN_CONFIG_X(offset), + AD74413R_DIN_SINK_MASK, + FIELD_PREP(AD74413R_DIN_SINK_MASK, strength / 120)); +} + + +static int ad74413r_gpio_set(struct gpio_chip *chip, unsigned int offset, + int val) +{ + struct ad74413r_state *st = gpiochip_get_data(chip); + unsigned int real_offset = st->gpo_gpio_offsets[offset]; + int ret; + + ret = ad74413r_set_gpo_config(st, real_offset, + AD74413R_GPO_CONFIG_LOGIC); + if (ret) + return ret; + + return regmap_update_bits(st->regmap, + AD74413R_REG_GPO_CONFIG_X(real_offset), + AD74413R_GPO_CONFIG_DATA_MASK, + val ? AD74413R_GPO_CONFIG_DATA_MASK : 0); +} + +static int ad74413r_gpio_set_multiple(struct gpio_chip *chip, + unsigned long *mask, unsigned long *bits) +{ + struct ad74413r_state *st = gpiochip_get_data(chip); + unsigned long real_mask = 0; + unsigned long real_bits = 0; + unsigned int offset; + int ret; + + for_each_set_bit(offset, mask, chip->ngpio) { + unsigned int real_offset = st->gpo_gpio_offsets[offset]; + + ret = ad74413r_set_gpo_config(st, real_offset, + AD74413R_GPO_CONFIG_LOGIC_PARALLEL); + if (ret) + return ret; + + real_mask |= BIT(real_offset); + if (*bits & offset) + real_bits |= BIT(real_offset); + } + + return regmap_update_bits(st->regmap, AD74413R_REG_GPO_PAR_DATA, + real_mask, real_bits); +} + +static int ad74413r_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct ad74413r_state *st = gpiochip_get_data(chip); + unsigned int real_offset = st->comp_gpio_offsets[offset]; + unsigned int status; + int ret; + + ret = regmap_read(st->regmap, AD74413R_REG_DIN_COMP_OUT, &status); + if (ret) + return ret; + + status &= BIT(real_offset); + + return status ? 1 : 0; +} + +static int ad74413r_gpio_get_multiple(struct gpio_chip *chip, + unsigned long *mask, + unsigned long *bits) +{ + struct ad74413r_state *st = gpiochip_get_data(chip); + unsigned int offset; + unsigned int val; + int ret; + + ret = regmap_read(st->regmap, AD74413R_REG_DIN_COMP_OUT, &val); + if (ret) + return ret; + + for_each_set_bit(offset, mask, chip->ngpio) { + unsigned int real_offset = st->comp_gpio_offsets[offset]; + + __assign_bit(offset, bits, val & BIT(real_offset)); + } + + return ret; +} + +static int ad74413r_gpio_get_gpo_direction(struct gpio_chip *chip, + unsigned int offset) +{ + return GPIO_LINE_DIRECTION_OUT; +} + +static int ad74413r_gpio_get_comp_direction(struct gpio_chip *chip, + unsigned int offset) +{ + return GPIO_LINE_DIRECTION_IN; +} + +static int ad74413r_gpio_set_gpo_config(struct gpio_chip *chip, + unsigned int offset, + unsigned long config) +{ + struct ad74413r_state *st = gpiochip_get_data(chip); + unsigned int real_offset = st->gpo_gpio_offsets[offset]; + + switch (pinconf_to_config_param(config)) { + case PIN_CONFIG_BIAS_PULL_DOWN: + return ad74413r_set_gpo_config(st, real_offset, + AD74413R_GPO_CONFIG_100K_PULL_DOWN); + case PIN_CONFIG_BIAS_HIGH_IMPEDANCE: + return ad74413r_set_gpo_config(st, real_offset, + AD74413R_GPO_CONFIG_HIGH_IMPEDANCE); + default: + return -ENOTSUPP; + } +} + +static int ad74413r_gpio_set_comp_config(struct gpio_chip *chip, + unsigned int offset, + unsigned long config) +{ + struct ad74413r_state *st = gpiochip_get_data(chip); + unsigned int real_offset = st->comp_gpio_offsets[offset]; + + switch (pinconf_to_config_param(config)) { + case PIN_CONFIG_INPUT_DEBOUNCE: + return ad74413r_set_comp_debounce(st, real_offset, + pinconf_to_config_argument(config)); + default: + return -ENOTSUPP; + } +} + +static int ad74413r_reset(struct ad74413r_state *st) +{ + struct gpio_desc *reset_gpio; + int ret; + + reset_gpio = devm_gpiod_get_optional(st->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(reset_gpio)) + return PTR_ERR(reset_gpio); + + if (reset_gpio) { + fsleep(50); + gpiod_set_value_cansleep(reset_gpio, 0); + return 0; + } + + ret = regmap_write(st->regmap, AD74413R_REG_CMD_KEY, + AD74413R_CMD_KEY_RESET1); + if (ret) + return ret; + + return regmap_write(st->regmap, AD74413R_REG_CMD_KEY, + AD74413R_CMD_KEY_RESET2); +} + +static int ad74413r_set_channel_dac_code(struct ad74413r_state *st, + unsigned int channel, int dac_code) +{ + struct reg_sequence reg_seq[2] = { + { AD74413R_REG_DAC_CODE_X(channel), dac_code }, + { AD74413R_REG_CMD_KEY, AD74413R_CMD_KEY_LDAC }, + }; + + return regmap_multi_reg_write(st->regmap, reg_seq, 2); +} + +static int ad74413r_set_channel_function(struct ad74413r_state *st, + unsigned int channel, u8 func) +{ + int ret; + + ret = regmap_update_bits(st->regmap, + AD74413R_REG_CH_FUNC_SETUP_X(channel), + AD74413R_CH_FUNC_SETUP_MASK, + CH_FUNC_HIGH_IMPEDANCE); + if (ret) + return ret; + + /* Set DAC code to 0 prior to changing channel function */ + ret = ad74413r_set_channel_dac_code(st, channel, 0); + if (ret) + return ret; + + /* Delay required before transition to new desired mode */ + usleep_range(130, 150); + + ret = regmap_update_bits(st->regmap, + AD74413R_REG_CH_FUNC_SETUP_X(channel), + AD74413R_CH_FUNC_SETUP_MASK, func); + if (ret) + return ret; + + /* Delay required before updating the new DAC code */ + usleep_range(150, 170); + + if (func == CH_FUNC_CURRENT_INPUT_LOOP_POWER) + ret = regmap_set_bits(st->regmap, + AD74413R_REG_ADC_CONFIG_X(channel), + AD74413R_ADC_CONFIG_CH_200K_TO_GND); + + return ret; +} + +static int ad74413r_set_adc_conv_seq(struct ad74413r_state *st, + unsigned int status) +{ + int ret; + + /* + * These bits do not clear when a conversion completes. + * To enable a subsequent conversion, repeat the write. + */ + ret = regmap_write_bits(st->regmap, AD74413R_REG_ADC_CONV_CTRL, + AD74413R_CONV_SEQ_MASK, + FIELD_PREP(AD74413R_CONV_SEQ_MASK, status)); + if (ret) + return ret; + + /* + * Wait 100us before starting conversions. + */ + usleep_range(100, 120); + + return 0; +} + +static int ad74413r_set_adc_channel_enable(struct ad74413r_state *st, + unsigned int channel, + bool status) +{ + return regmap_update_bits(st->regmap, AD74413R_REG_ADC_CONV_CTRL, + AD74413R_CH_EN_MASK(channel), + status ? AD74413R_CH_EN_MASK(channel) : 0); +} + +static int ad74413r_get_adc_range(struct ad74413r_state *st, + unsigned int channel, + unsigned int *val) +{ + int ret; + + ret = regmap_read(st->regmap, AD74413R_REG_ADC_CONFIG_X(channel), val); + if (ret) + return ret; + + *val = FIELD_GET(AD74413R_ADC_CONFIG_RANGE_MASK, *val); + + return 0; +} + +static int ad74413r_get_adc_rejection(struct ad74413r_state *st, + unsigned int channel, + unsigned int *val) +{ + int ret; + + ret = regmap_read(st->regmap, AD74413R_REG_ADC_CONFIG_X(channel), val); + if (ret) + return ret; + + *val = FIELD_GET(AD74413R_ADC_CONFIG_REJECTION_MASK, *val); + + return 0; +} + +static int ad74413r_set_adc_rejection(struct ad74413r_state *st, + unsigned int channel, + unsigned int val) +{ + return regmap_update_bits(st->regmap, + AD74413R_REG_ADC_CONFIG_X(channel), + AD74413R_ADC_CONFIG_REJECTION_MASK, + FIELD_PREP(AD74413R_ADC_CONFIG_REJECTION_MASK, + val)); +} + +static int ad74413r_rejection_to_rate(struct ad74413r_state *st, + unsigned int rej, int *val) +{ + switch (rej) { + case AD74413R_ADC_REJECTION_50_60: + *val = 20; + return 0; + case AD74413R_ADC_REJECTION_NONE: + *val = 4800; + return 0; + case AD74413R_ADC_REJECTION_50_60_HART: + *val = 10; + return 0; + case AD74413R_ADC_REJECTION_HART: + *val = 1200; + return 0; + default: + dev_err(st->dev, "ADC rejection invalid\n"); + return -EINVAL; + } +} + +static int ad74413r_rate_to_rejection(struct ad74413r_state *st, + int rate, unsigned int *val) +{ + switch (rate) { + case 20: + *val = AD74413R_ADC_REJECTION_50_60; + return 0; + case 4800: + *val = AD74413R_ADC_REJECTION_NONE; + return 0; + case 10: + *val = AD74413R_ADC_REJECTION_50_60_HART; + return 0; + case 1200: + *val = AD74413R_ADC_REJECTION_HART; + return 0; + default: + dev_err(st->dev, "ADC rate invalid\n"); + return -EINVAL; + } +} + +static int ad74413r_range_to_voltage_range(struct ad74413r_state *st, + unsigned int range, int *val) +{ + switch (range) { + case AD74413R_ADC_RANGE_10V: + *val = 10000; + return 0; + case AD74413R_ADC_RANGE_2P5V_EXT_POW: + case AD74413R_ADC_RANGE_2P5V_INT_POW: + *val = 2500; + return 0; + case AD74413R_ADC_RANGE_5V_BI_DIR: + *val = 5000; + return 0; + default: + dev_err(st->dev, "ADC range invalid\n"); + return -EINVAL; + } +} + +static int ad74413r_range_to_voltage_offset(struct ad74413r_state *st, + unsigned int range, int *val) +{ + switch (range) { + case AD74413R_ADC_RANGE_10V: + case AD74413R_ADC_RANGE_2P5V_EXT_POW: + *val = 0; + return 0; + case AD74413R_ADC_RANGE_2P5V_INT_POW: + case AD74413R_ADC_RANGE_5V_BI_DIR: + *val = -2500; + return 0; + default: + dev_err(st->dev, "ADC range invalid\n"); + return -EINVAL; + } +} + +static int ad74413r_range_to_voltage_offset_raw(struct ad74413r_state *st, + unsigned int range, int *val) +{ + switch (range) { + case AD74413R_ADC_RANGE_10V: + case AD74413R_ADC_RANGE_2P5V_EXT_POW: + *val = 0; + return 0; + case AD74413R_ADC_RANGE_2P5V_INT_POW: + *val = -((int)AD74413R_ADC_RESULT_MAX); + return 0; + case AD74413R_ADC_RANGE_5V_BI_DIR: + *val = -((int)AD74413R_ADC_RESULT_MAX / 2); + return 0; + default: + dev_err(st->dev, "ADC range invalid\n"); + return -EINVAL; + } +} + +static int ad74413r_get_output_voltage_scale(struct ad74413r_state *st, + int *val, int *val2) +{ + *val = AD74413R_DAC_VOLTAGE_MAX; + *val2 = AD74413R_DAC_CODE_MAX; + + return IIO_VAL_FRACTIONAL; +} + +static int ad74413r_get_output_current_scale(struct ad74413r_state *st, + int *val, int *val2) +{ + *val = st->refin_reg_uv; + *val2 = st->sense_resistor_ohms * AD74413R_DAC_CODE_MAX * 1000; + + return IIO_VAL_FRACTIONAL; +} + +static int ad74413r_get_input_voltage_scale(struct ad74413r_state *st, + unsigned int channel, + int *val, int *val2) +{ + unsigned int range; + int ret; + + ret = ad74413r_get_adc_range(st, channel, &range); + if (ret) + return ret; + + ret = ad74413r_range_to_voltage_range(st, range, val); + if (ret) + return ret; + + *val2 = AD74413R_ADC_RESULT_MAX; + + return IIO_VAL_FRACTIONAL; +} + +static int ad74413r_get_input_voltage_offset(struct ad74413r_state *st, + unsigned int channel, int *val) +{ + unsigned int range; + int ret; + + ret = ad74413r_get_adc_range(st, channel, &range); + if (ret) + return ret; + + ret = ad74413r_range_to_voltage_offset_raw(st, range, val); + if (ret) + return ret; + + return IIO_VAL_INT; +} + +static int ad74413r_get_input_current_scale(struct ad74413r_state *st, + unsigned int channel, int *val, + int *val2) +{ + unsigned int range; + int ret; + + ret = ad74413r_get_adc_range(st, channel, &range); + if (ret) + return ret; + + ret = ad74413r_range_to_voltage_range(st, range, val); + if (ret) + return ret; + + *val2 = AD74413R_ADC_RESULT_MAX * st->sense_resistor_ohms; + + return IIO_VAL_FRACTIONAL; +} + +static int ad74413r_get_input_current_offset(struct ad74413r_state *st, + unsigned int channel, int *val) +{ + unsigned int range; + int voltage_range; + int voltage_offset; + int ret; + + ret = ad74413r_get_adc_range(st, channel, &range); + if (ret) + return ret; + + ret = ad74413r_range_to_voltage_range(st, range, &voltage_range); + if (ret) + return ret; + + ret = ad74413r_range_to_voltage_offset(st, range, &voltage_offset); + if (ret) + return ret; + + *val = voltage_offset * (int)AD74413R_ADC_RESULT_MAX / voltage_range; + + return IIO_VAL_INT; +} + +static int ad74413r_get_adc_rate(struct ad74413r_state *st, + unsigned int channel, int *val) +{ + unsigned int rejection; + int ret; + + ret = ad74413r_get_adc_rejection(st, channel, &rejection); + if (ret) + return ret; + + ret = ad74413r_rejection_to_rate(st, rejection, val); + if (ret) + return ret; + + return IIO_VAL_INT; +} + +static int ad74413r_set_adc_rate(struct ad74413r_state *st, + unsigned int channel, int val) +{ + unsigned int rejection; + int ret; + + ret = ad74413r_rate_to_rejection(st, val, &rejection); + if (ret) + return ret; + + return ad74413r_set_adc_rejection(st, channel, rejection); +} + +static irqreturn_t ad74413r_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad74413r_state *st = iio_priv(indio_dev); + u8 *rx_buf = st->adc_samples_buf.rx_buf; + unsigned int i; + int ret; + + ret = spi_sync(st->spi, &st->adc_samples_msg); + if (ret) + goto out; + + for (i = 0; i < st->adc_active_channels; i++) + ad74413r_crc_check(st, &rx_buf[i * AD74413R_FRAME_SIZE]); + + iio_push_to_buffers_with_timestamp(indio_dev, &st->adc_samples_buf, + iio_get_time_ns(indio_dev)); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static irqreturn_t ad74413r_adc_data_interrupt(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct ad74413r_state *st = iio_priv(indio_dev); + + if (iio_buffer_enabled(indio_dev)) + iio_trigger_poll(st->trig); + else + complete(&st->adc_data_completion); + + return IRQ_HANDLED; +} + +static int _ad74413r_get_single_adc_result(struct ad74413r_state *st, + unsigned int channel, int *val) +{ + unsigned int uval; + int ret; + + guard(mutex)(&st->lock); + + reinit_completion(&st->adc_data_completion); + + ret = ad74413r_set_adc_channel_enable(st, channel, true); + if (ret) + return ret; + + ret = ad74413r_set_adc_conv_seq(st, AD74413R_CONV_SEQ_SINGLE); + if (ret) + return ret; + + if (!wait_for_completion_timeout(&st->adc_data_completion, + msecs_to_jiffies(1000))) + return -ETIMEDOUT; + + ret = regmap_read(st->regmap, AD74413R_REG_ADC_RESULT_X(channel), + &uval); + if (ret) + return ret; + + ret = ad74413r_set_adc_conv_seq(st, AD74413R_CONV_SEQ_OFF); + if (ret) + return ret; + + ret = ad74413r_set_adc_channel_enable(st, channel, false); + if (ret) + return ret; + + *val = uval; + + return IIO_VAL_INT; +} + +static int ad74413r_get_single_adc_result(struct iio_dev *indio_dev, + unsigned int channel, int *val) +{ + struct ad74413r_state *st = iio_priv(indio_dev); + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = _ad74413r_get_single_adc_result(st, channel, val); + iio_device_release_direct(indio_dev); + return ret; +} + +static void ad74413r_adc_to_resistance_result(int adc_result, int *val) +{ + if (adc_result == AD74413R_ADC_RESULT_MAX) + adc_result = AD74413R_ADC_RESULT_MAX - 1; + + *val = DIV_ROUND_CLOSEST(adc_result * 2100, + AD74413R_ADC_RESULT_MAX - adc_result); +} + +static int ad74413r_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *active_scan_mask) +{ + struct ad74413r_state *st = iio_priv(indio_dev); + struct spi_transfer *xfer = st->adc_samples_xfer; + u8 *rx_buf = st->adc_samples_buf.rx_buf; + u8 *tx_buf = st->adc_samples_tx_buf; + unsigned int channel; + int ret = -EINVAL; + + guard(mutex)(&st->lock); + + spi_message_init(&st->adc_samples_msg); + st->adc_active_channels = 0; + + for_each_clear_bit(channel, active_scan_mask, AD74413R_CHANNEL_MAX) { + ret = ad74413r_set_adc_channel_enable(st, channel, false); + if (ret) + return ret; + } + + if (*active_scan_mask == 0) + return ret; + + /* + * The read select register is used to select which register's value + * will be sent by the slave on the next SPI frame. + * + * Create an SPI message that, on each step, writes to the read select + * register to select the ADC result of the next enabled channel, and + * reads the ADC result of the previous enabled channel. + * + * Example: + * W: [WCH1] [WCH2] [WCH2] [WCH3] [ ] + * R: [ ] [RCH1] [RCH2] [RCH3] [RCH4] + */ + + for_each_set_bit(channel, active_scan_mask, AD74413R_CHANNEL_MAX) { + ret = ad74413r_set_adc_channel_enable(st, channel, true); + if (ret) + return ret; + + st->adc_active_channels++; + + if (xfer == st->adc_samples_xfer) + xfer->rx_buf = NULL; + else + xfer->rx_buf = rx_buf; + + xfer->tx_buf = tx_buf; + xfer->len = AD74413R_FRAME_SIZE; + xfer->cs_change = 1; + + ad74413r_format_reg_write(AD74413R_REG_READ_SELECT, + AD74413R_REG_ADC_RESULT_X(channel), + tx_buf); + + spi_message_add_tail(xfer, &st->adc_samples_msg); + + tx_buf += AD74413R_FRAME_SIZE; + if (xfer != st->adc_samples_xfer) + rx_buf += AD74413R_FRAME_SIZE; + xfer++; + } + + xfer->rx_buf = rx_buf; + xfer->tx_buf = NULL; + xfer->len = AD74413R_FRAME_SIZE; + xfer->cs_change = 0; + + spi_message_add_tail(xfer, &st->adc_samples_msg); + return 0; +} + +static int ad74413r_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad74413r_state *st = iio_priv(indio_dev); + + return ad74413r_set_adc_conv_seq(st, AD74413R_CONV_SEQ_CONTINUOUS); +} + +static int ad74413r_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad74413r_state *st = iio_priv(indio_dev); + + return ad74413r_set_adc_conv_seq(st, AD74413R_CONV_SEQ_OFF); +} + +static int ad74413r_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad74413r_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->output) + return ad74413r_get_output_voltage_scale(st, + val, val2); + else + return ad74413r_get_input_voltage_scale(st, + chan->channel, val, val2); + case IIO_CURRENT: + if (chan->output) + return ad74413r_get_output_current_scale(st, + val, val2); + else + return ad74413r_get_input_current_scale(st, + chan->channel, val, val2); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_VOLTAGE: + return ad74413r_get_input_voltage_offset(st, + chan->channel, val); + case IIO_CURRENT: + return ad74413r_get_input_current_offset(st, + chan->channel, val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_RAW: + if (chan->output) + return -EINVAL; + + return ad74413r_get_single_adc_result(indio_dev, chan->channel, + val); + case IIO_CHAN_INFO_PROCESSED: { + int ret; + + ret = ad74413r_get_single_adc_result(indio_dev, chan->channel, + val); + if (ret < 0) + return ret; + + ad74413r_adc_to_resistance_result(*val, val); + + return ret; + } + case IIO_CHAN_INFO_SAMP_FREQ: + return ad74413r_get_adc_rate(st, chan->channel, val); + default: + return -EINVAL; + } +} + +static int ad74413r_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct ad74413r_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (!chan->output) + return -EINVAL; + + if (val < 0 || val > AD74413R_DAC_CODE_MAX) { + dev_err(st->dev, "Invalid DAC code\n"); + return -EINVAL; + } + + return ad74413r_set_channel_dac_code(st, chan->channel, val); + case IIO_CHAN_INFO_SAMP_FREQ: + return ad74413r_set_adc_rate(st, chan->channel, val); + default: + return -EINVAL; + } +} + +static int ad74413r_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long info) +{ + struct ad74413r_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (st->chip_info->hart_support) { + *vals = ad74413r_adc_sampling_rates_hart; + *length = ARRAY_SIZE(ad74413r_adc_sampling_rates_hart); + } else { + *vals = ad74413r_adc_sampling_rates; + *length = ARRAY_SIZE(ad74413r_adc_sampling_rates); + } + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const struct iio_buffer_setup_ops ad74413r_buffer_ops = { + .postenable = &ad74413r_buffer_postenable, + .predisable = &ad74413r_buffer_predisable, +}; + +static const struct iio_trigger_ops ad74413r_trigger_ops = { + .validate_device = iio_trigger_validate_own_device, +}; + +static const struct iio_info ad74413r_info = { + .read_raw = &ad74413r_read_raw, + .write_raw = &ad74413r_write_raw, + .read_avail = &ad74413r_read_avail, + .update_scan_mode = &ad74413r_update_scan_mode, +}; + +#define AD74413R_DAC_CHANNEL(_type, extra_mask_separate) \ + { \ + .type = (_type), \ + .indexed = 1, \ + .output = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \ + | (extra_mask_separate), \ + } + +#define AD74413R_ADC_CHANNEL(_type, extra_mask_separate) \ + { \ + .type = (_type), \ + .indexed = 1, \ + .output = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \ + | BIT(IIO_CHAN_INFO_SAMP_FREQ) \ + | (extra_mask_separate), \ + .info_mask_separate_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 32, \ + .shift = 8, \ + .endianness = IIO_BE, \ + }, \ + } + +#define AD74413R_ADC_VOLTAGE_CHANNEL \ + AD74413R_ADC_CHANNEL(IIO_VOLTAGE, BIT(IIO_CHAN_INFO_SCALE) \ + | BIT(IIO_CHAN_INFO_OFFSET)) + +#define AD74413R_ADC_CURRENT_CHANNEL \ + AD74413R_ADC_CHANNEL(IIO_CURRENT, BIT(IIO_CHAN_INFO_SCALE) \ + | BIT(IIO_CHAN_INFO_OFFSET)) + +static const struct iio_chan_spec ad74413r_voltage_output_channels[] = { + AD74413R_DAC_CHANNEL(IIO_VOLTAGE, BIT(IIO_CHAN_INFO_SCALE)), + AD74413R_ADC_CURRENT_CHANNEL, +}; + +static const struct iio_chan_spec ad74413r_current_output_channels[] = { + AD74413R_DAC_CHANNEL(IIO_CURRENT, BIT(IIO_CHAN_INFO_SCALE)), + AD74413R_ADC_VOLTAGE_CHANNEL, +}; + +static const struct iio_chan_spec ad74413r_voltage_input_channels[] = { + AD74413R_ADC_VOLTAGE_CHANNEL, +}; + +static const struct iio_chan_spec ad74413r_current_input_channels[] = { + AD74413R_ADC_CURRENT_CHANNEL, +}; + +static const struct iio_chan_spec ad74413r_current_input_loop_channels[] = { + AD74413R_DAC_CHANNEL(IIO_CURRENT, BIT(IIO_CHAN_INFO_SCALE)), + AD74413R_ADC_CURRENT_CHANNEL, +}; + +static const struct iio_chan_spec ad74413r_resistance_input_channels[] = { + AD74413R_ADC_CHANNEL(IIO_RESISTANCE, BIT(IIO_CHAN_INFO_PROCESSED)), +}; + +static const struct iio_chan_spec ad74413r_digital_input_channels[] = { + AD74413R_ADC_VOLTAGE_CHANNEL, +}; + +#define _AD74413R_CHANNELS(_channels) \ + { \ + .channels = _channels, \ + .num_channels = ARRAY_SIZE(_channels), \ + } + +#define AD74413R_CHANNELS(name) \ + _AD74413R_CHANNELS(ad74413r_ ## name ## _channels) + +static const struct ad74413r_channels ad74413r_channels_map[] = { + [CH_FUNC_HIGH_IMPEDANCE] = AD74413R_CHANNELS(voltage_input), + [CH_FUNC_VOLTAGE_OUTPUT] = AD74413R_CHANNELS(voltage_output), + [CH_FUNC_CURRENT_OUTPUT] = AD74413R_CHANNELS(current_output), + [CH_FUNC_VOLTAGE_INPUT] = AD74413R_CHANNELS(voltage_input), + [CH_FUNC_CURRENT_INPUT_EXT_POWER] = AD74413R_CHANNELS(current_input), + [CH_FUNC_CURRENT_INPUT_LOOP_POWER] = AD74413R_CHANNELS(current_input_loop), + [CH_FUNC_RESISTANCE_INPUT] = AD74413R_CHANNELS(resistance_input), + [CH_FUNC_DIGITAL_INPUT_LOGIC] = AD74413R_CHANNELS(digital_input), + [CH_FUNC_DIGITAL_INPUT_LOOP_POWER] = AD74413R_CHANNELS(digital_input), + [CH_FUNC_CURRENT_INPUT_EXT_POWER_HART] = AD74413R_CHANNELS(current_input), + [CH_FUNC_CURRENT_INPUT_LOOP_POWER_HART] = AD74413R_CHANNELS(current_input), +}; + +static int ad74413r_parse_channel_config(struct iio_dev *indio_dev, + struct fwnode_handle *channel_node) +{ + struct ad74413r_state *st = iio_priv(indio_dev); + struct ad74413r_channel_config *config; + u32 index; + int ret; + + ret = fwnode_property_read_u32(channel_node, "reg", &index); + if (ret) { + dev_err(st->dev, "Failed to read channel reg: %d\n", ret); + return ret; + } + + if (index >= AD74413R_CHANNEL_MAX) { + dev_err(st->dev, "Channel index %u is too large\n", index); + return -EINVAL; + } + + config = &st->channel_configs[index]; + if (config->initialized) { + dev_err(st->dev, "Channel %u already initialized\n", index); + return -EINVAL; + } + + config->func = CH_FUNC_HIGH_IMPEDANCE; + fwnode_property_read_u32(channel_node, "adi,ch-func", &config->func); + + if (config->func < CH_FUNC_MIN || config->func > CH_FUNC_MAX) { + dev_err(st->dev, "Invalid channel function %u\n", config->func); + return -EINVAL; + } + + if (!st->chip_info->hart_support && + (config->func == CH_FUNC_CURRENT_INPUT_EXT_POWER_HART || + config->func == CH_FUNC_CURRENT_INPUT_LOOP_POWER_HART)) { + dev_err(st->dev, "Unsupported HART function %u\n", config->func); + return -EINVAL; + } + + if (config->func == CH_FUNC_DIGITAL_INPUT_LOGIC || + config->func == CH_FUNC_DIGITAL_INPUT_LOOP_POWER) + st->num_comparator_gpios++; + + config->gpo_comparator = fwnode_property_read_bool(channel_node, + "adi,gpo-comparator"); + + fwnode_property_read_u32(channel_node, "drive-strength-microamp", + &config->drive_strength); + + if (!config->gpo_comparator) + st->num_gpo_gpios++; + + indio_dev->num_channels += ad74413r_channels_map[config->func].num_channels; + + config->initialized = true; + + return 0; +} + +static int ad74413r_parse_channel_configs(struct iio_dev *indio_dev) +{ + struct ad74413r_state *st = iio_priv(indio_dev); + int ret; + + device_for_each_child_node_scoped(st->dev, channel_node) { + ret = ad74413r_parse_channel_config(indio_dev, channel_node); + if (ret) + return ret; + } + + return 0; +} + +static int ad74413r_setup_channels(struct iio_dev *indio_dev) +{ + struct ad74413r_state *st = iio_priv(indio_dev); + struct ad74413r_channel_config *config; + const struct iio_chan_spec *chans; + struct iio_chan_spec *channels; + unsigned int i, num_chans, chan_i; + int ret; + + channels = devm_kcalloc(st->dev, sizeof(*channels), + indio_dev->num_channels, GFP_KERNEL); + if (!channels) + return -ENOMEM; + + indio_dev->channels = channels; + + for (i = 0; i < AD74413R_CHANNEL_MAX; i++) { + config = &st->channel_configs[i]; + chans = ad74413r_channels_map[config->func].channels; + num_chans = ad74413r_channels_map[config->func].num_channels; + + memcpy(channels, chans, num_chans * sizeof(*chans)); + + for (chan_i = 0; chan_i < num_chans; chan_i++) { + struct iio_chan_spec *chan = &channels[chan_i]; + + chan->channel = i; + if (chan->output) + chan->scan_index = -1; + else + chan->scan_index = i; + } + + ret = ad74413r_set_channel_function(st, i, config->func); + if (ret) + return ret; + + channels += num_chans; + } + + return 0; +} + +static int ad74413r_setup_gpios(struct ad74413r_state *st) +{ + struct ad74413r_channel_config *config; + unsigned int comp_gpio_i = 0; + unsigned int gpo_gpio_i = 0; + unsigned int i; + u8 gpo_config; + u32 strength; + int ret; + + for (i = 0; i < AD74413R_CHANNEL_MAX; i++) { + config = &st->channel_configs[i]; + + if (config->gpo_comparator) { + gpo_config = AD74413R_GPO_CONFIG_COMPARATOR; + } else { + gpo_config = AD74413R_GPO_CONFIG_LOGIC; + st->gpo_gpio_offsets[gpo_gpio_i++] = i; + } + + if (config->func == CH_FUNC_DIGITAL_INPUT_LOGIC || + config->func == CH_FUNC_DIGITAL_INPUT_LOOP_POWER) { + st->comp_gpio_offsets[comp_gpio_i++] = i; + + strength = config->drive_strength; + ret = ad74413r_set_comp_drive_strength(st, i, strength); + if (ret) + return ret; + } + + ret = ad74413r_set_gpo_config(st, i, gpo_config); + if (ret) + return ret; + } + + return 0; +} + +static int ad74413r_probe(struct spi_device *spi) +{ + struct ad74413r_state *st; + struct iio_dev *indio_dev; + 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; + st->dev = &spi->dev; + st->chip_info = spi_get_device_match_data(spi); + if (!st->chip_info) + return -EINVAL; + + ret = devm_mutex_init(st->dev, &st->lock); + if (ret) + return ret; + + init_completion(&st->adc_data_completion); + + st->regmap = devm_regmap_init(st->dev, NULL, st, + &ad74413r_regmap_config); + if (IS_ERR(st->regmap)) + return PTR_ERR(st->regmap); + + ret = devm_regulator_get_enable_read_voltage(st->dev, "refin"); + if (ret < 0) + return dev_err_probe(st->dev, ret, + "Failed to get refin regulator voltage\n"); + st->refin_reg_uv = ret; + + st->sense_resistor_ohms = 100000000; + device_property_read_u32(st->dev, "shunt-resistor-micro-ohms", + &st->sense_resistor_ohms); + st->sense_resistor_ohms /= 1000000; + + st->trig = devm_iio_trigger_alloc(st->dev, "%s-dev%d", + st->chip_info->name, iio_device_id(indio_dev)); + if (!st->trig) + return -ENOMEM; + + st->trig->ops = &ad74413r_trigger_ops; + iio_trigger_set_drvdata(st->trig, st); + + ret = devm_iio_trigger_register(st->dev, st->trig); + if (ret) + return ret; + + indio_dev->name = st->chip_info->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ad74413r_info; + indio_dev->trig = iio_trigger_get(st->trig); + + ret = ad74413r_reset(st); + if (ret) + return ret; + + ret = ad74413r_parse_channel_configs(indio_dev); + if (ret) + return ret; + + ret = ad74413r_setup_channels(indio_dev); + if (ret) + return ret; + + ret = ad74413r_setup_gpios(st); + if (ret) + return ret; + + if (st->num_gpo_gpios) { + st->gpo_gpiochip.owner = THIS_MODULE; + st->gpo_gpiochip.label = st->chip_info->name; + st->gpo_gpiochip.base = -1; + st->gpo_gpiochip.ngpio = st->num_gpo_gpios; + st->gpo_gpiochip.parent = st->dev; + st->gpo_gpiochip.can_sleep = true; + st->gpo_gpiochip.set = ad74413r_gpio_set; + st->gpo_gpiochip.set_multiple = ad74413r_gpio_set_multiple; + st->gpo_gpiochip.set_config = ad74413r_gpio_set_gpo_config; + st->gpo_gpiochip.get_direction = + ad74413r_gpio_get_gpo_direction; + + ret = devm_gpiochip_add_data(st->dev, &st->gpo_gpiochip, st); + if (ret) + return ret; + } + + if (st->num_comparator_gpios) { + st->comp_gpiochip.owner = THIS_MODULE; + st->comp_gpiochip.label = st->chip_info->name; + st->comp_gpiochip.base = -1; + st->comp_gpiochip.ngpio = st->num_comparator_gpios; + st->comp_gpiochip.parent = st->dev; + st->comp_gpiochip.can_sleep = true; + st->comp_gpiochip.get = ad74413r_gpio_get; + st->comp_gpiochip.get_multiple = ad74413r_gpio_get_multiple; + st->comp_gpiochip.set_config = ad74413r_gpio_set_comp_config; + st->comp_gpiochip.get_direction = + ad74413r_gpio_get_comp_direction; + + ret = devm_gpiochip_add_data(st->dev, &st->comp_gpiochip, st); + if (ret) + return ret; + } + + ret = ad74413r_set_adc_conv_seq(st, AD74413R_CONV_SEQ_OFF); + if (ret) + return ret; + + ret = devm_request_irq(st->dev, spi->irq, ad74413r_adc_data_interrupt, + 0, st->chip_info->name, indio_dev); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(st->dev, indio_dev, + &iio_pollfunc_store_time, + &ad74413r_trigger_handler, + &ad74413r_buffer_ops); + if (ret) + return ret; + + return devm_iio_device_register(st->dev, indio_dev); +} + +static int ad74413r_unregister_driver(struct spi_driver *spi) +{ + spi_unregister_driver(spi); + + return 0; +} + +static int __init ad74413r_register_driver(struct spi_driver *spi) +{ + crc8_populate_msb(ad74413r_crc8_table, AD74413R_CRC_POLYNOMIAL); + + return spi_register_driver(spi); +} + +static const struct ad74413r_chip_info ad74412r_chip_info_data = { + .hart_support = false, + .name = "ad74412r", +}; + +static const struct ad74413r_chip_info ad74413r_chip_info_data = { + .hart_support = true, + .name = "ad74413r", +}; + +static const struct of_device_id ad74413r_dt_id[] = { + { + .compatible = "adi,ad74412r", + .data = &ad74412r_chip_info_data, + }, + { + .compatible = "adi,ad74413r", + .data = &ad74413r_chip_info_data, + }, + { } +}; +MODULE_DEVICE_TABLE(of, ad74413r_dt_id); + +static const struct spi_device_id ad74413r_spi_id[] = { + { .name = "ad74412r", .driver_data = (kernel_ulong_t)&ad74412r_chip_info_data }, + { .name = "ad74413r", .driver_data = (kernel_ulong_t)&ad74413r_chip_info_data }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad74413r_spi_id); + +static struct spi_driver ad74413r_driver = { + .driver = { + .name = "ad74413r", + .of_match_table = ad74413r_dt_id, + }, + .probe = ad74413r_probe, + .id_table = ad74413r_spi_id, +}; + +module_driver(ad74413r_driver, + ad74413r_register_driver, + ad74413r_unregister_driver); + +MODULE_AUTHOR("Cosmin Tanislav "); +MODULE_DESCRIPTION("Analog Devices AD74413R ADDAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/addac/stx104.c b/drivers/iio/addac/stx104.c new file mode 100644 index 000000000..6207eb2fa --- /dev/null +++ b/drivers/iio/addac/stx104.c @@ -0,0 +1,523 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * IIO driver for the Apex Embedded Systems STX104 + * Copyright (C) 2016 William Breathitt Gray + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STX104_OUT_CHAN(chan) { \ + .type = IIO_VOLTAGE, \ + .channel = chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .indexed = 1, \ + .output = 1 \ +} +#define STX104_IN_CHAN(chan, diff) { \ + .type = IIO_VOLTAGE, \ + .channel = chan, \ + .channel2 = chan, \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_HARDWAREGAIN) | \ + BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .indexed = 1, \ + .differential = diff \ +} + +#define STX104_NUM_OUT_CHAN 2 + +#define STX104_EXTENT 16 + +static unsigned int base[max_num_isa_dev(STX104_EXTENT)]; +static unsigned int num_stx104; +module_param_hw_array(base, uint, ioport, &num_stx104, 0); +MODULE_PARM_DESC(base, "Apex Embedded Systems STX104 base addresses"); + +#define STX104_AIO_BASE 0x0 +#define STX104_SOFTWARE_STROBE STX104_AIO_BASE +#define STX104_ADC_DATA STX104_AIO_BASE +#define STX104_ADC_CHANNEL (STX104_AIO_BASE + 0x2) +#define STX104_DIO_REG (STX104_AIO_BASE + 0x3) +#define STX104_DAC_BASE (STX104_AIO_BASE + 0x4) +#define STX104_ADC_STATUS (STX104_AIO_BASE + 0x8) +#define STX104_ADC_CONTROL (STX104_AIO_BASE + 0x9) +#define STX104_ADC_CONFIGURATION (STX104_AIO_BASE + 0x11) +#define STX104_I8254_BASE (STX104_AIO_BASE + 0x12) + +#define STX104_AIO_DATA_STRIDE 2 +#define STX104_DAC_OFFSET(_channel) (STX104_DAC_BASE + STX104_AIO_DATA_STRIDE * (_channel)) + +/* ADC Channel */ +#define STX104_FC GENMASK(3, 0) +#define STX104_LC GENMASK(7, 4) +#define STX104_SINGLE_CHANNEL(_channel) \ + (u8_encode_bits(_channel, STX104_FC) | u8_encode_bits(_channel, STX104_LC)) + +/* ADC Status */ +#define STX104_SD BIT(5) +#define STX104_CNV BIT(7) +#define STX104_DIFFERENTIAL 1 + +/* ADC Control */ +#define STX104_ALSS GENMASK(1, 0) +#define STX104_SOFTWARE_TRIGGER u8_encode_bits(0x0, STX104_ALSS) + +/* ADC Configuration */ +#define STX104_GAIN GENMASK(1, 0) +#define STX104_ADBU BIT(2) +#define STX104_RBK GENMASK(7, 4) +#define STX104_BIPOLAR 0 +#define STX104_GAIN_X1 0 +#define STX104_GAIN_X2 1 +#define STX104_GAIN_X4 2 +#define STX104_GAIN_X8 3 + +/** + * struct stx104_iio - IIO device private data structure + * @lock: synchronization lock to prevent I/O race conditions + * @aio_data_map: Regmap for analog I/O data + * @aio_ctl_map: Regmap for analog I/O control + */ +struct stx104_iio { + struct mutex lock; + struct regmap *aio_data_map; + struct regmap *aio_ctl_map; +}; + +static const struct regmap_range aio_ctl_wr_ranges[] = { + regmap_reg_range(0x0, 0x0), regmap_reg_range(0x2, 0x2), regmap_reg_range(0x9, 0x9), + regmap_reg_range(0x11, 0x11), +}; +static const struct regmap_range aio_ctl_rd_ranges[] = { + regmap_reg_range(0x2, 0x2), regmap_reg_range(0x8, 0x9), regmap_reg_range(0x11, 0x11), +}; +static const struct regmap_range aio_ctl_volatile_ranges[] = { + regmap_reg_range(0x8, 0x8), +}; +static const struct regmap_access_table aio_ctl_wr_table = { + .yes_ranges = aio_ctl_wr_ranges, + .n_yes_ranges = ARRAY_SIZE(aio_ctl_wr_ranges), +}; +static const struct regmap_access_table aio_ctl_rd_table = { + .yes_ranges = aio_ctl_rd_ranges, + .n_yes_ranges = ARRAY_SIZE(aio_ctl_rd_ranges), +}; +static const struct regmap_access_table aio_ctl_volatile_table = { + .yes_ranges = aio_ctl_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(aio_ctl_volatile_ranges), +}; + +static const struct regmap_config aio_ctl_regmap_config = { + .name = "aio_ctl", + .reg_bits = 8, + .reg_stride = 1, + .reg_base = STX104_AIO_BASE, + .val_bits = 8, + .io_port = true, + .wr_table = &aio_ctl_wr_table, + .rd_table = &aio_ctl_rd_table, + .volatile_table = &aio_ctl_volatile_table, + .cache_type = REGCACHE_FLAT, +}; + +static const struct regmap_range aio_data_wr_ranges[] = { + regmap_reg_range(0x4, 0x6), +}; +static const struct regmap_range aio_data_rd_ranges[] = { + regmap_reg_range(0x0, 0x0), +}; +static const struct regmap_access_table aio_data_wr_table = { + .yes_ranges = aio_data_wr_ranges, + .n_yes_ranges = ARRAY_SIZE(aio_data_wr_ranges), +}; +static const struct regmap_access_table aio_data_rd_table = { + .yes_ranges = aio_data_rd_ranges, + .n_yes_ranges = ARRAY_SIZE(aio_data_rd_ranges), +}; + +static const struct regmap_config aio_data_regmap_config = { + .name = "aio_data", + .reg_bits = 16, + .reg_stride = STX104_AIO_DATA_STRIDE, + .reg_base = STX104_AIO_BASE, + .val_bits = 16, + .io_port = true, + .wr_table = &aio_data_wr_table, + .rd_table = &aio_data_rd_table, + .volatile_table = &aio_data_rd_table, + .cache_type = REGCACHE_FLAT, +}; + +static const struct regmap_config dio_regmap_config = { + .name = "dio", + .reg_bits = 8, + .reg_stride = 1, + .reg_base = STX104_DIO_REG, + .val_bits = 8, + .io_port = true, +}; + +static const struct regmap_range pit_wr_ranges[] = { + regmap_reg_range(0x0, 0x3), +}; +static const struct regmap_range pit_rd_ranges[] = { + regmap_reg_range(0x0, 0x2), +}; +static const struct regmap_access_table pit_wr_table = { + .yes_ranges = pit_wr_ranges, + .n_yes_ranges = ARRAY_SIZE(pit_wr_ranges), +}; +static const struct regmap_access_table pit_rd_table = { + .yes_ranges = pit_rd_ranges, + .n_yes_ranges = ARRAY_SIZE(pit_rd_ranges), +}; + +static const struct regmap_config pit_regmap_config = { + .name = "i8254", + .reg_bits = 8, + .reg_stride = 1, + .reg_base = STX104_I8254_BASE, + .val_bits = 8, + .io_port = true, + .wr_table = &pit_wr_table, + .rd_table = &pit_rd_table, +}; + +static int stx104_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long mask) +{ + struct stx104_iio *const priv = iio_priv(indio_dev); + int err; + unsigned int adc_config; + unsigned int value; + unsigned int adc_status; + + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + err = regmap_read(priv->aio_ctl_map, STX104_ADC_CONFIGURATION, &adc_config); + if (err) + return err; + + *val = BIT(u8_get_bits(adc_config, STX104_GAIN)); + return IIO_VAL_INT; + case IIO_CHAN_INFO_RAW: + if (chan->output) { + err = regmap_read(priv->aio_data_map, STX104_DAC_OFFSET(chan->channel), + &value); + if (err) + return err; + *val = value; + return IIO_VAL_INT; + } + + mutex_lock(&priv->lock); + + /* select ADC channel */ + err = regmap_write(priv->aio_ctl_map, STX104_ADC_CHANNEL, + STX104_SINGLE_CHANNEL(chan->channel)); + if (err) { + mutex_unlock(&priv->lock); + return err; + } + + /* + * Trigger ADC sample capture by writing to the 8-bit Software Strobe Register and + * wait for completion; the conversion time range is 5 microseconds to 53.68 seconds + * in steps of 25 nanoseconds. The actual Analog Input Frame Timer time interval is + * calculated as: + * ai_time_frame_ns = ( AIFT + 1 ) * ( 25 nanoseconds ). + * Where 0 <= AIFT <= 2147483648. + */ + err = regmap_write(priv->aio_ctl_map, STX104_SOFTWARE_STROBE, 0); + if (err) { + mutex_unlock(&priv->lock); + return err; + } + err = regmap_read_poll_timeout(priv->aio_ctl_map, STX104_ADC_STATUS, adc_status, + !u8_get_bits(adc_status, STX104_CNV), 0, 53687092); + if (err) { + mutex_unlock(&priv->lock); + return err; + } + + err = regmap_read(priv->aio_data_map, STX104_ADC_DATA, &value); + if (err) { + mutex_unlock(&priv->lock); + return err; + } + *val = value; + + mutex_unlock(&priv->lock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + /* get ADC bipolar/unipolar configuration */ + err = regmap_read(priv->aio_ctl_map, STX104_ADC_CONFIGURATION, &adc_config); + if (err) + return err; + + *val = (u8_get_bits(adc_config, STX104_ADBU) == STX104_BIPOLAR) ? -32768 : 0; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* get ADC bipolar/unipolar and gain configuration */ + err = regmap_read(priv->aio_ctl_map, STX104_ADC_CONFIGURATION, &adc_config); + if (err) + return err; + + *val = 5; + *val2 = (u8_get_bits(adc_config, STX104_ADBU) == STX104_BIPOLAR) ? 14 : 15; + *val2 += u8_get_bits(adc_config, STX104_GAIN); + return IIO_VAL_FRACTIONAL_LOG2; + } + + return -EINVAL; +} + +static int stx104_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + struct stx104_iio *const priv = iio_priv(indio_dev); + u8 gain; + + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + /* Only four gain states (x1, x2, x4, x8) */ + switch (val) { + case 1: + gain = STX104_GAIN_X1; + break; + case 2: + gain = STX104_GAIN_X2; + break; + case 4: + gain = STX104_GAIN_X4; + break; + case 8: + gain = STX104_GAIN_X8; + break; + default: + return -EINVAL; + } + + return regmap_write(priv->aio_ctl_map, STX104_ADC_CONFIGURATION, gain); + case IIO_CHAN_INFO_RAW: + if (!chan->output) + return -EINVAL; + + if (val < 0 || val > U16_MAX) + return -EINVAL; + + return regmap_write(priv->aio_data_map, STX104_DAC_OFFSET(chan->channel), val); + } + + return -EINVAL; +} + +static const struct iio_info stx104_info = { + .read_raw = stx104_read_raw, + .write_raw = stx104_write_raw +}; + +/* single-ended input channels configuration */ +static const struct iio_chan_spec stx104_channels_sing[] = { + STX104_OUT_CHAN(0), STX104_OUT_CHAN(1), + STX104_IN_CHAN(0, 0), STX104_IN_CHAN(1, 0), STX104_IN_CHAN(2, 0), + STX104_IN_CHAN(3, 0), STX104_IN_CHAN(4, 0), STX104_IN_CHAN(5, 0), + STX104_IN_CHAN(6, 0), STX104_IN_CHAN(7, 0), STX104_IN_CHAN(8, 0), + STX104_IN_CHAN(9, 0), STX104_IN_CHAN(10, 0), STX104_IN_CHAN(11, 0), + STX104_IN_CHAN(12, 0), STX104_IN_CHAN(13, 0), STX104_IN_CHAN(14, 0), + STX104_IN_CHAN(15, 0) +}; +/* differential input channels configuration */ +static const struct iio_chan_spec stx104_channels_diff[] = { + STX104_OUT_CHAN(0), STX104_OUT_CHAN(1), + STX104_IN_CHAN(0, 1), STX104_IN_CHAN(1, 1), STX104_IN_CHAN(2, 1), + STX104_IN_CHAN(3, 1), STX104_IN_CHAN(4, 1), STX104_IN_CHAN(5, 1), + STX104_IN_CHAN(6, 1), STX104_IN_CHAN(7, 1) +}; + +static int stx104_reg_mask_xlate(struct gpio_regmap *const gpio, enum gpio_regmap_operation op, + const unsigned int base, unsigned int offset, + unsigned int *const reg, unsigned int *const mask) +{ + /* Output lines are located at same register bit offsets as input lines */ + if (offset >= 4) + offset -= 4; + + *reg = base; + *mask = BIT(offset); + + return 0; +} + +#define STX104_NGPIO 8 +static const char *stx104_names[STX104_NGPIO] = { + "DIN0", "DIN1", "DIN2", "DIN3", "DOUT0", "DOUT1", "DOUT2", "DOUT3" +}; + +static int bank_select_i8254(struct regmap *map) +{ + const u8 select_i8254[] = { 0x3, 0xB, 0xA }; + size_t i; + int err; + + for (i = 0; i < ARRAY_SIZE(select_i8254); i++) { + err = regmap_write_bits(map, STX104_ADC_CONFIGURATION, STX104_RBK, select_i8254[i]); + if (err) + return err; + } + + return 0; +} + +static int stx104_init_hw(struct stx104_iio *const priv) +{ + int err; + + /* configure device for software trigger operation */ + err = regmap_write(priv->aio_ctl_map, STX104_ADC_CONTROL, STX104_SOFTWARE_TRIGGER); + if (err) + return err; + + /* initialize gain setting to x1 */ + err = regmap_write(priv->aio_ctl_map, STX104_ADC_CONFIGURATION, STX104_GAIN_X1); + if (err) + return err; + + /* initialize DAC outputs to 0V */ + err = regmap_write(priv->aio_data_map, STX104_DAC_BASE, 0); + if (err) + return err; + err = regmap_write(priv->aio_data_map, STX104_DAC_BASE + STX104_AIO_DATA_STRIDE, 0); + if (err) + return err; + + return bank_select_i8254(priv->aio_ctl_map); +} + +static int stx104_probe(struct device *dev, unsigned int id) +{ + struct iio_dev *indio_dev; + struct stx104_iio *priv; + struct gpio_regmap_config gpio_config; + struct i8254_regmap_config pit_config; + void __iomem *stx104_base; + struct regmap *aio_ctl_map; + struct regmap *aio_data_map; + struct regmap *dio_map; + int err; + unsigned int adc_status; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + if (!devm_request_region(dev, base[id], STX104_EXTENT, + dev_name(dev))) { + dev_err(dev, "Unable to lock port addresses (0x%X-0x%X)\n", + base[id], base[id] + STX104_EXTENT); + return -EBUSY; + } + + stx104_base = devm_ioport_map(dev, base[id], STX104_EXTENT); + if (!stx104_base) + return -ENOMEM; + + aio_ctl_map = devm_regmap_init_mmio(dev, stx104_base, &aio_ctl_regmap_config); + if (IS_ERR(aio_ctl_map)) + return dev_err_probe(dev, PTR_ERR(aio_ctl_map), + "Unable to initialize aio_ctl register map\n"); + + aio_data_map = devm_regmap_init_mmio(dev, stx104_base, &aio_data_regmap_config); + if (IS_ERR(aio_data_map)) + return dev_err_probe(dev, PTR_ERR(aio_data_map), + "Unable to initialize aio_data register map\n"); + + dio_map = devm_regmap_init_mmio(dev, stx104_base, &dio_regmap_config); + if (IS_ERR(dio_map)) + return dev_err_probe(dev, PTR_ERR(dio_map), + "Unable to initialize dio register map\n"); + + pit_config.map = devm_regmap_init_mmio(dev, stx104_base, &pit_regmap_config); + if (IS_ERR(pit_config.map)) + return dev_err_probe(dev, PTR_ERR(pit_config.map), + "Unable to initialize i8254 register map\n"); + + priv = iio_priv(indio_dev); + priv->aio_ctl_map = aio_ctl_map; + priv->aio_data_map = aio_data_map; + + indio_dev->info = &stx104_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + err = regmap_read(aio_ctl_map, STX104_ADC_STATUS, &adc_status); + if (err) + return err; + + if (u8_get_bits(adc_status, STX104_SD) == STX104_DIFFERENTIAL) { + indio_dev->num_channels = ARRAY_SIZE(stx104_channels_diff); + indio_dev->channels = stx104_channels_diff; + } else { + indio_dev->num_channels = ARRAY_SIZE(stx104_channels_sing); + indio_dev->channels = stx104_channels_sing; + } + + indio_dev->name = dev_name(dev); + + mutex_init(&priv->lock); + + err = stx104_init_hw(priv); + if (err) + return err; + + err = devm_iio_device_register(dev, indio_dev); + if (err) + return err; + + gpio_config = (struct gpio_regmap_config) { + .parent = dev, + .regmap = dio_map, + .ngpio = STX104_NGPIO, + .names = stx104_names, + .reg_dat_base = GPIO_REGMAP_ADDR(STX104_DIO_REG), + .reg_set_base = GPIO_REGMAP_ADDR(STX104_DIO_REG), + .ngpio_per_reg = STX104_NGPIO, + .reg_mask_xlate = stx104_reg_mask_xlate, + .drvdata = dio_map, + }; + + err = PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_config)); + if (err) + return err; + + pit_config.parent = dev; + + return devm_i8254_regmap_register(dev, &pit_config); +} + +static struct isa_driver stx104_driver = { + .probe = stx104_probe, + .driver = { + .name = "stx104" + }, +}; + +module_isa_driver(stx104_driver, num_stx104); + +MODULE_AUTHOR("William Breathitt Gray "); +MODULE_DESCRIPTION("Apex Embedded Systems STX104 IIO driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("I8254"); diff --git a/drivers/iio/afe/Kconfig b/drivers/iio/afe/Kconfig new file mode 100644 index 000000000..9a1d95c1c --- /dev/null +++ b/drivers/iio/afe/Kconfig @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Analog Front End drivers +# +# When adding new entries keep the list in alphabetical order + +menu "Analog Front Ends" + +config IIO_RESCALE + tristate "IIO rescale" + help + Say yes here to build support for the IIO rescaling + that handles voltage dividers, current sense shunts and + current sense amplifiers. + + To compile this driver as a module, choose M here: the + module will be called iio-rescale. + +endmenu diff --git a/drivers/iio/afe/Makefile b/drivers/iio/afe/Makefile new file mode 100644 index 000000000..4c56c8edb --- /dev/null +++ b/drivers/iio/afe/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for industrial I/O Analog Front Ends (AFE) +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_IIO_RESCALE) += iio-rescale.o diff --git a/drivers/iio/afe/iio-rescale.c b/drivers/iio/afe/iio-rescale.c new file mode 100644 index 000000000..3a6a09ffa --- /dev/null +++ b/drivers/iio/afe/iio-rescale.c @@ -0,0 +1,611 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * IIO rescale driver + * + * Copyright (C) 2018 Axentia Technologies AB + * Copyright (C) 2022 Liam Beguin + * + * Author: Peter Rosin + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +int rescale_process_scale(struct rescale *rescale, int scale_type, + int *val, int *val2) +{ + s64 tmp; + int _val, _val2; + s32 rem, rem2; + u32 mult; + u32 neg; + + switch (scale_type) { + case IIO_VAL_INT: + *val *= rescale->numerator; + if (rescale->denominator == 1) + return scale_type; + *val2 = rescale->denominator; + return IIO_VAL_FRACTIONAL; + case IIO_VAL_FRACTIONAL: + /* + * When the product of both scales doesn't overflow, avoid + * potential accuracy loss (for in kernel consumers) by + * keeping a fractional representation. + */ + if (!check_mul_overflow(*val, rescale->numerator, &_val) && + !check_mul_overflow(*val2, rescale->denominator, &_val2)) { + *val = _val; + *val2 = _val2; + return IIO_VAL_FRACTIONAL; + } + fallthrough; + case IIO_VAL_FRACTIONAL_LOG2: + tmp = (s64)*val * 1000000000LL; + tmp = div_s64(tmp, rescale->denominator); + tmp *= rescale->numerator; + + tmp = div_s64_rem(tmp, 1000000000LL, &rem); + *val = tmp; + + if (!rem) + return scale_type; + + if (scale_type == IIO_VAL_FRACTIONAL) + tmp = *val2; + else + tmp = ULL(1) << *val2; + + rem2 = *val % (int)tmp; + *val = *val / (int)tmp; + + *val2 = rem / (int)tmp; + if (rem2) + *val2 += div_s64((s64)rem2 * 1000000000LL, tmp); + + return IIO_VAL_INT_PLUS_NANO; + case IIO_VAL_INT_PLUS_NANO: + case IIO_VAL_INT_PLUS_MICRO: + mult = scale_type == IIO_VAL_INT_PLUS_NANO ? 1000000000L : 1000000L; + + /* + * For IIO_VAL_INT_PLUS_{MICRO,NANO} scale types if either *val + * OR *val2 is negative the schan scale is negative, i.e. + * *val = 1 and *val2 = -0.5 yields -1.5 not -0.5. + */ + neg = *val < 0 || *val2 < 0; + + tmp = (s64)abs(*val) * abs(rescale->numerator); + *val = div_s64_rem(tmp, abs(rescale->denominator), &rem); + + tmp = (s64)rem * mult + (s64)abs(*val2) * abs(rescale->numerator); + tmp = div_s64(tmp, abs(rescale->denominator)); + + *val += div_s64_rem(tmp, mult, val2); + + /* + * If only one of the rescaler elements or the schan scale is + * negative, the combined scale is negative. + */ + if (neg ^ ((rescale->numerator < 0) ^ (rescale->denominator < 0))) { + if (*val) + *val = -*val; + else + *val2 = -*val2; + } + + return scale_type; + default: + return -EOPNOTSUPP; + } +} +EXPORT_SYMBOL_NS_GPL(rescale_process_scale, "IIO_RESCALE"); + +int rescale_process_offset(struct rescale *rescale, int scale_type, + int scale, int scale2, int schan_off, + int *val, int *val2) +{ + s64 tmp, tmp2; + + switch (scale_type) { + case IIO_VAL_FRACTIONAL: + tmp = (s64)rescale->offset * scale2; + *val = div_s64(tmp, scale) + schan_off; + return IIO_VAL_INT; + case IIO_VAL_INT: + *val = div_s64(rescale->offset, scale) + schan_off; + return IIO_VAL_INT; + case IIO_VAL_FRACTIONAL_LOG2: + tmp = (s64)rescale->offset * (1 << scale2); + *val = div_s64(tmp, scale) + schan_off; + return IIO_VAL_INT; + case IIO_VAL_INT_PLUS_NANO: + tmp = (s64)rescale->offset * 1000000000LL; + tmp2 = ((s64)scale * 1000000000LL) + scale2; + *val = div64_s64(tmp, tmp2) + schan_off; + return IIO_VAL_INT; + case IIO_VAL_INT_PLUS_MICRO: + tmp = (s64)rescale->offset * 1000000LL; + tmp2 = ((s64)scale * 1000000LL) + scale2; + *val = div64_s64(tmp, tmp2) + schan_off; + return IIO_VAL_INT; + default: + return -EOPNOTSUPP; + } +} +EXPORT_SYMBOL_NS_GPL(rescale_process_offset, "IIO_RESCALE"); + +static int rescale_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct rescale *rescale = iio_priv(indio_dev); + int scale, scale2; + int schan_off = 0; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (rescale->chan_processed) + /* + * When only processed channels are supported, we + * read the processed data and scale it by 1/1 + * augmented with whatever the rescaler has calculated. + */ + return iio_read_channel_processed(rescale->source, val); + else + return iio_read_channel_raw(rescale->source, val); + + case IIO_CHAN_INFO_SCALE: + if (rescale->chan_processed) { + /* + * Processed channels are scaled 1-to-1 + */ + *val = 1; + *val2 = 1; + ret = IIO_VAL_FRACTIONAL; + } else { + ret = iio_read_channel_scale(rescale->source, val, val2); + } + return rescale_process_scale(rescale, ret, val, val2); + case IIO_CHAN_INFO_OFFSET: + /* + * Processed channels are scaled 1-to-1 and source offset is + * already taken into account. + * + * In other cases, real world measurement are expressed as: + * + * schan_scale * (raw + schan_offset) + * + * Given that the rescaler parameters are applied recursively: + * + * rescaler_scale * (schan_scale * (raw + schan_offset) + + * rescaler_offset) + * + * Or, + * + * (rescaler_scale * schan_scale) * (raw + + * (schan_offset + rescaler_offset / schan_scale) + * + * Thus, reusing the original expression the parameters exposed + * to userspace are: + * + * scale = schan_scale * rescaler_scale + * offset = schan_offset + rescaler_offset / schan_scale + */ + if (rescale->chan_processed) { + *val = rescale->offset; + return IIO_VAL_INT; + } + + if (iio_channel_has_info(rescale->source->channel, + IIO_CHAN_INFO_OFFSET)) { + ret = iio_read_channel_offset(rescale->source, + &schan_off, NULL); + if (ret != IIO_VAL_INT) + return ret < 0 ? ret : -EOPNOTSUPP; + } + + if (iio_channel_has_info(rescale->source->channel, + IIO_CHAN_INFO_SCALE)) { + ret = iio_read_channel_scale(rescale->source, &scale, &scale2); + return rescale_process_offset(rescale, ret, scale, scale2, + schan_off, val, val2); + } + + /* + * If we get here we have no scale so scale 1:1 but apply + * rescaler and offset, if any. + */ + return rescale_process_offset(rescale, IIO_VAL_FRACTIONAL, 1, 1, + schan_off, val, val2); + default: + return -EINVAL; + } +} + +static int rescale_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct rescale *rescale = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *type = IIO_VAL_INT; + return iio_read_avail_channel_raw(rescale->source, + vals, length); + default: + return -EINVAL; + } +} + +static const struct iio_info rescale_info = { + .read_raw = rescale_read_raw, + .read_avail = rescale_read_avail, +}; + +static ssize_t rescale_read_ext_info(struct iio_dev *indio_dev, + uintptr_t private, + struct iio_chan_spec const *chan, + char *buf) +{ + struct rescale *rescale = iio_priv(indio_dev); + + return iio_read_channel_ext_info(rescale->source, + rescale->ext_info[private].name, + buf); +} + +static ssize_t rescale_write_ext_info(struct iio_dev *indio_dev, + uintptr_t private, + struct iio_chan_spec const *chan, + const char *buf, size_t len) +{ + struct rescale *rescale = iio_priv(indio_dev); + + return iio_write_channel_ext_info(rescale->source, + rescale->ext_info[private].name, + buf, len); +} + +static int rescale_configure_channel(struct device *dev, + struct rescale *rescale) +{ + struct iio_chan_spec *chan = &rescale->chan; + struct iio_chan_spec const *schan = rescale->source->channel; + + chan->indexed = 1; + chan->output = schan->output; + chan->ext_info = rescale->ext_info; + chan->type = rescale->cfg->type; + + if (iio_channel_has_info(schan, IIO_CHAN_INFO_RAW) && + (iio_channel_has_info(schan, IIO_CHAN_INFO_SCALE) || + iio_channel_has_info(schan, IIO_CHAN_INFO_OFFSET))) { + dev_info(dev, "using raw+scale/offset source channel\n"); + } else if (iio_channel_has_info(schan, IIO_CHAN_INFO_PROCESSED)) { + dev_info(dev, "using processed channel\n"); + rescale->chan_processed = true; + } else { + dev_err(dev, "source channel is not supported\n"); + return -EINVAL; + } + + chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE); + + if (rescale->offset) + chan->info_mask_separate |= BIT(IIO_CHAN_INFO_OFFSET); + + /* + * Using .read_avail() is fringe to begin with and makes no sense + * whatsoever for processed channels, so we make sure that this cannot + * be called on a processed channel. + */ + if (iio_channel_has_available(schan, IIO_CHAN_INFO_RAW) && + !rescale->chan_processed) + chan->info_mask_separate_available |= BIT(IIO_CHAN_INFO_RAW); + + return 0; +} + +static int rescale_current_sense_amplifier_props(struct device *dev, + struct rescale *rescale) +{ + u32 sense; + u32 gain_mult = 1; + u32 gain_div = 1; + u32 factor; + int ret; + + ret = device_property_read_u32(dev, "sense-resistor-micro-ohms", + &sense); + if (ret) { + dev_err(dev, "failed to read the sense resistance: %d\n", ret); + return ret; + } + + device_property_read_u32(dev, "sense-gain-mult", &gain_mult); + device_property_read_u32(dev, "sense-gain-div", &gain_div); + + /* + * Calculate the scaling factor, 1 / (gain * sense), or + * gain_div / (gain_mult * sense), while trying to keep the + * numerator/denominator from overflowing. + */ + factor = gcd(sense, 1000000); + rescale->numerator = 1000000 / factor; + rescale->denominator = sense / factor; + + factor = gcd(rescale->numerator, gain_mult); + rescale->numerator /= factor; + rescale->denominator *= gain_mult / factor; + + factor = gcd(rescale->denominator, gain_div); + rescale->numerator *= gain_div / factor; + rescale->denominator /= factor; + + return 0; +} + +static int rescale_current_sense_shunt_props(struct device *dev, + struct rescale *rescale) +{ + u32 shunt; + u32 factor; + int ret; + + ret = device_property_read_u32(dev, "shunt-resistor-micro-ohms", + &shunt); + if (ret) { + dev_err(dev, "failed to read the shunt resistance: %d\n", ret); + return ret; + } + + factor = gcd(shunt, 1000000); + rescale->numerator = 1000000 / factor; + rescale->denominator = shunt / factor; + + return 0; +} + +static int rescale_voltage_divider_props(struct device *dev, + struct rescale *rescale) +{ + int ret; + u32 factor; + + ret = device_property_read_u32(dev, "output-ohms", + &rescale->denominator); + if (ret) { + dev_err(dev, "failed to read output-ohms: %d\n", ret); + return ret; + } + + ret = device_property_read_u32(dev, "full-ohms", + &rescale->numerator); + if (ret) { + dev_err(dev, "failed to read full-ohms: %d\n", ret); + return ret; + } + + factor = gcd(rescale->numerator, rescale->denominator); + rescale->numerator /= factor; + rescale->denominator /= factor; + + return 0; +} + +static int rescale_temp_sense_rtd_props(struct device *dev, + struct rescale *rescale) +{ + u32 factor; + u32 alpha; + u32 iexc; + u32 tmp; + int ret; + u32 r0; + + ret = device_property_read_u32(dev, "excitation-current-microamp", + &iexc); + if (ret) { + dev_err(dev, "failed to read excitation-current-microamp: %d\n", + ret); + return ret; + } + + ret = device_property_read_u32(dev, "alpha-ppm-per-celsius", &alpha); + if (ret) { + dev_err(dev, "failed to read alpha-ppm-per-celsius: %d\n", + ret); + return ret; + } + + ret = device_property_read_u32(dev, "r-naught-ohms", &r0); + if (ret) { + dev_err(dev, "failed to read r-naught-ohms: %d\n", ret); + return ret; + } + + tmp = r0 * iexc * alpha / 1000000; + factor = gcd(tmp, 1000000); + rescale->numerator = 1000000 / factor; + rescale->denominator = tmp / factor; + + rescale->offset = -1 * ((r0 * iexc) / 1000); + + return 0; +} + +static int rescale_temp_transducer_props(struct device *dev, + struct rescale *rescale) +{ + s32 offset = 0; + s32 sense = 1; + s32 alpha; + int ret; + + device_property_read_u32(dev, "sense-offset-millicelsius", &offset); + device_property_read_u32(dev, "sense-resistor-ohms", &sense); + ret = device_property_read_u32(dev, "alpha-ppm-per-celsius", &alpha); + if (ret) { + dev_err(dev, "failed to read alpha-ppm-per-celsius: %d\n", ret); + return ret; + } + + rescale->numerator = 1000000; + rescale->denominator = alpha * sense; + + rescale->offset = div_s64((s64)offset * rescale->denominator, + rescale->numerator); + + return 0; +} + +enum rescale_variant { + CURRENT_SENSE_AMPLIFIER, + CURRENT_SENSE_SHUNT, + VOLTAGE_DIVIDER, + TEMP_SENSE_RTD, + TEMP_TRANSDUCER, +}; + +static const struct rescale_cfg rescale_cfg[] = { + [CURRENT_SENSE_AMPLIFIER] = { + .type = IIO_CURRENT, + .props = rescale_current_sense_amplifier_props, + }, + [CURRENT_SENSE_SHUNT] = { + .type = IIO_CURRENT, + .props = rescale_current_sense_shunt_props, + }, + [VOLTAGE_DIVIDER] = { + .type = IIO_VOLTAGE, + .props = rescale_voltage_divider_props, + }, + [TEMP_SENSE_RTD] = { + .type = IIO_TEMP, + .props = rescale_temp_sense_rtd_props, + }, + [TEMP_TRANSDUCER] = { + .type = IIO_TEMP, + .props = rescale_temp_transducer_props, + }, +}; + +static const struct of_device_id rescale_match[] = { + { .compatible = "current-sense-amplifier", + .data = &rescale_cfg[CURRENT_SENSE_AMPLIFIER], }, + { .compatible = "current-sense-shunt", + .data = &rescale_cfg[CURRENT_SENSE_SHUNT], }, + { .compatible = "voltage-divider", + .data = &rescale_cfg[VOLTAGE_DIVIDER], }, + { .compatible = "temperature-sense-rtd", + .data = &rescale_cfg[TEMP_SENSE_RTD], }, + { .compatible = "temperature-transducer", + .data = &rescale_cfg[TEMP_TRANSDUCER], }, + { } +}; +MODULE_DEVICE_TABLE(of, rescale_match); + +static int rescale_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct iio_channel *source; + struct rescale *rescale; + int sizeof_ext_info; + int sizeof_priv; + int i; + int ret; + + source = devm_iio_channel_get(dev, NULL); + if (IS_ERR(source)) + return dev_err_probe(dev, PTR_ERR(source), + "failed to get source channel\n"); + + sizeof_ext_info = iio_get_channel_ext_info_count(source); + if (sizeof_ext_info) { + sizeof_ext_info += 1; /* one extra entry for the sentinel */ + sizeof_ext_info *= sizeof(*rescale->ext_info); + } + + sizeof_priv = sizeof(*rescale) + sizeof_ext_info; + + indio_dev = devm_iio_device_alloc(dev, sizeof_priv); + if (!indio_dev) + return -ENOMEM; + + rescale = iio_priv(indio_dev); + + rescale->cfg = device_get_match_data(dev); + rescale->numerator = 1; + rescale->denominator = 1; + rescale->offset = 0; + + ret = rescale->cfg->props(dev, rescale); + if (ret) + return ret; + + if (!rescale->numerator || !rescale->denominator) { + dev_err(dev, "invalid scaling factor.\n"); + return -EINVAL; + } + + platform_set_drvdata(pdev, indio_dev); + + rescale->source = source; + + indio_dev->name = dev_name(dev); + indio_dev->info = &rescale_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = &rescale->chan; + indio_dev->num_channels = 1; + if (sizeof_ext_info) { + rescale->ext_info = devm_kmemdup(dev, + source->channel->ext_info, + sizeof_ext_info, GFP_KERNEL); + if (!rescale->ext_info) + return -ENOMEM; + + for (i = 0; rescale->ext_info[i].name; ++i) { + struct iio_chan_spec_ext_info *ext_info = + &rescale->ext_info[i]; + + if (source->channel->ext_info[i].read) + ext_info->read = rescale_read_ext_info; + if (source->channel->ext_info[i].write) + ext_info->write = rescale_write_ext_info; + ext_info->private = i; + } + } + + ret = rescale_configure_channel(dev, rescale); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static struct platform_driver rescale_driver = { + .probe = rescale_probe, + .driver = { + .name = "iio-rescale", + .of_match_table = rescale_match, + }, +}; +module_platform_driver(rescale_driver); + +MODULE_DESCRIPTION("IIO rescale driver"); +MODULE_AUTHOR("Peter Rosin "); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_CONSUMER"); diff --git a/drivers/iio/amplifiers/Kconfig b/drivers/iio/amplifiers/Kconfig new file mode 100644 index 000000000..9e24421b5 --- /dev/null +++ b/drivers/iio/amplifiers/Kconfig @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Gain Amplifiers, etc. +# +# When adding new entries keep the list in alphabetical order + +menu "Amplifiers" + +config AD8366 + tristate "Analog Devices AD8366 and similar Gain Amplifiers" + depends on SPI + depends on GPIOLIB + select BITREVERSE + help + Say yes here to build support for Analog Devices AD8366 and similar + gain amplifiers. This driver supports the following gain amplifiers + from Analog Devices: + AD8366 Dual-Digital Variable Gain Amplifier (VGA) + ADA4961 BiCMOS RF Digital Gain Amplifier (DGA) + ADL5240 Digitally controlled variable gain amplifier (VGA) + ADRF5702: 0.125 dB LSB, 8-Bit, Silicon Digital Attenuator + ADRF5703: 0.25 dB LSB, 7-Bit, Silicon Digital Attenuator + ADRF5720: 0.5 dB LSB, 6-Bit, Silicon Digital Attenuator + ADRF5730: 0.5 dB LSB, 6-Bit, Silicon Digital Attenuator + ADRF5731: 2 dB LSB, 4-Bit, Silicon Digital Attenuator + HMC271A: 1dB LSB 5-Bit Digital Attenuator SMT + HMC792A 0.25 dB LSB GaAs MMIC 6-Bit Digital Attenuator + HMC1018A: 1.0 dB LSB GaAs MMIC 5-BIT Digital Attenuator + HMC1019A: 0.5 dB LSB GaAs MMIC 5-BIT Digital Attenuator + HMC1119 0.25 dB LSB, 7-Bit, Silicon Digital Attenuator + + To compile this driver as a module, choose M here: the + module will be called ad8366. + +config ADA4250 + tristate "Analog Devices ADA4250 Instrumentation Amplifier" + depends on SPI + select REGMAP_SPI + help + Say yes here to build support for Analog Devices ADA4250 + SPI Amplifier's support. The driver provides direct access via + sysfs. + + To compile this driver as a module, choose M here: the + module will be called ada4250. + +config ADL8113 + tristate "Analog Devices ADL8113 Low Noise Amplifier" + depends on GPIOLIB + help + Say yes here to build support for Analog Devices ADL8113 Low Noise + Amplifier with integrated bypass switches. The device supports four + operation modes controlled by GPIO pins: internal amplifier, + internal bypass, and two external bypass modes. + + To compile this driver as a module, choose M here: the + module will be called adl8113. + +config HMC425 + tristate "Analog Devices HMC425A and similar GPIO Gain Amplifiers" + depends on GPIOLIB + help + Say yes here to build support for Analog Devices HMC425A and similar + gain amplifiers or step attenuators. + + To compile this driver as a module, choose M here: the + module will be called hmc425a. + +endmenu diff --git a/drivers/iio/amplifiers/Makefile b/drivers/iio/amplifiers/Makefile new file mode 100644 index 000000000..0a76443be --- /dev/null +++ b/drivers/iio/amplifiers/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile iio/amplifiers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_AD8366) += ad8366.o +obj-$(CONFIG_ADA4250) += ada4250.o +obj-$(CONFIG_ADL8113) += adl8113.o +obj-$(CONFIG_HMC425) += hmc425a.o diff --git a/drivers/iio/amplifiers/ad8366.c b/drivers/iio/amplifiers/ad8366.c new file mode 100644 index 000000000..e12f8604f --- /dev/null +++ b/drivers/iio/amplifiers/ad8366.c @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AD8366 and similar Gain Amplifiers + * This driver supports the following gain amplifiers: + * AD8366 Dual-Digital Variable Gain Amplifier (VGA) + * ADA4961 BiCMOS RF Digital Gain Amplifier (DGA) + * ADL5240 Digitally controlled variable gain amplifier (VGA) + * ADRF5702: 0.125 dB LSB, 8-Bit, Silicon Digital Attenuator, 50 MHz to 20 GHz + * ADRF5703: 0.25 dB LSB, 7-Bit, Silicon Digital Attenuator, 9 kHz to 20 GHz + * ADRF5720: 0.5 dB LSB, 6-Bit, Silicon Digital Attenuator, 9 kHz to 40 GHz + * ADRF5730: 0.5 dB LSB, 6-Bit, Silicon Digital Attenuator, 100 MHz to 40 GHz + * ADRF5731: 2 dB LSB, 4-Bit, Silicon Digital Attenuator, 100 MHz to 40 GHz + * HMC271A: 1dB LSB 5-Bit Digital Attenuator SMT, 0.7 - 3.7 GHz + * HMC792A 0.25 dB LSB GaAs MMIC 6-Bit Digital Attenuator + * HMC1018A: 1.0 dB LSB GaAs MMIC 5-BIT DIGITAL ATTENUATOR, 0.1 - 30 GHz + * HMC1019A: 0.5 dB LSB GaAs MMIC 5-BIT DIGITAL ATTENUATOR, 0.1 - 30 GHz + * HMC1119 0.25 dB LSB, 7-Bit, Silicon Digital Attenuator + * + * Copyright 2012-2026 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct ad8366_info { + const char *name; + int gain_min; + int gain_max; + int gain_step; + size_t num_channels; + size_t (*pack_code)(const unsigned char *code, size_t num_channels, + unsigned char *data); +}; + +struct ad8366_state { + struct spi_device *spi; + struct mutex lock; /* protect sensor state */ + unsigned char ch[2]; + const struct ad8366_info *info; + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + */ + unsigned char data[2] __aligned(IIO_DMA_MINALIGN); +}; + +static size_t ad8366_pack_code(const unsigned char *code, size_t num_channels, + unsigned char *data) +{ + u8 ch_a = bitrev8(code[0]) >> 2; + u8 ch_b = bitrev8(code[1]) >> 2; + + put_unaligned_be16((ch_b << 6) | ch_a, &data[0]); + return sizeof(__be16); +} + +static size_t adrf5731_pack_code(const unsigned char *code, size_t num_channels, + unsigned char *data) +{ + data[0] = code[0] << 2; + return 1; +} + +static size_t hmc271_pack_code(const unsigned char *code, size_t num_channels, + unsigned char *data) +{ + data[0] = bitrev8(code[0]) >> 3; + return 1; +} + +static const struct ad8366_info ad8366_chip_info = { + .name = "ad8366", + .gain_min = 4500, + .gain_max = 20500, + .gain_step = 253, + .num_channels = 2, + .pack_code = ad8366_pack_code, +}; + +static const struct ad8366_info ada4961_chip_info = { + .name = "ada4961", + .gain_min = -6000, + .gain_max = 15000, + .gain_step = -1000, + .num_channels = 1, +}; + +static const struct ad8366_info adl5240_chip_info = { + .name = "adl5240", + .gain_min = -11500, + .gain_max = 20000, + .gain_step = 500, + .num_channels = 1, +}; + +static const struct ad8366_info adrf5702_chip_info = { + .name = "adrf5702", + .gain_min = -31875, + .gain_max = 0, + .gain_step = -125, + .num_channels = 1, +}; + +static const struct ad8366_info adrf5703_chip_info = { + .name = "adrf5703", + .gain_min = -31750, + .gain_max = 0, + .gain_step = -250, + .num_channels = 1, +}; + +static const struct ad8366_info adrf5720_chip_info = { + .name = "adrf5720", + .gain_min = -31500, + .gain_max = 0, + .gain_step = -500, + .num_channels = 1, +}; + +static const struct ad8366_info adrf5730_chip_info = { + .name = "adrf5730", + .gain_min = -31500, + .gain_max = 0, + .gain_step = -500, + .num_channels = 1, +}; + +static const struct ad8366_info adrf5731_chip_info = { + .name = "adrf5731", + .gain_min = -30000, + .gain_max = 0, + .gain_step = -2000, + .num_channels = 1, + .pack_code = adrf5731_pack_code, +}; + +static const struct ad8366_info hmc271_chip_info = { + .name = "hmc271a", + .gain_min = -31000, + .gain_max = 0, + .gain_step = 1000, + .num_channels = 1, + .pack_code = hmc271_pack_code, +}; + +static const struct ad8366_info hmc792_chip_info = { + .name = "hmc792a", + .gain_min = -15750, + .gain_max = 0, + .gain_step = 250, + .num_channels = 1, +}; + +static const struct ad8366_info hmc1018_chip_info = { + .name = "hmc1018a", + .gain_min = -31000, + .gain_max = 0, + .gain_step = 1000, + .num_channels = 1, +}; + +static const struct ad8366_info hmc1019_chip_info = { + .name = "hmc1019a", + .gain_min = -15500, + .gain_max = 0, + .gain_step = 500, + .num_channels = 1, +}; + +static const struct ad8366_info hmc1119_chip_info = { + .name = "hmc1119", + .gain_min = -31750, + .gain_max = 0, + .gain_step = -250, + .num_channels = 1, +}; + +static int ad8366_write_code(struct ad8366_state *st) +{ + const struct ad8366_info *inf = st->info; + size_t len = 1; + + if (inf->pack_code) + len = inf->pack_code(st->ch, inf->num_channels, st->data); + else + st->data[0] = st->ch[0]; + + return spi_write(st->spi, st->data, len); +} + +static int ad8366_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad8366_state *st = iio_priv(indio_dev); + const struct ad8366_info *inf = st->info; + int ret; + int code, gain = 0; + + mutex_lock(&st->lock); + switch (m) { + case IIO_CHAN_INFO_HARDWAREGAIN: + code = st->ch[chan->channel]; + gain = inf->gain_step > 0 ? inf->gain_min : inf->gain_max; + gain += inf->gain_step * code; + /* Values in dB */ + *val = gain / 1000; + *val2 = (gain % 1000) * 1000; + + ret = IIO_VAL_INT_PLUS_MICRO_DB; + break; + default: + ret = -EINVAL; + } + mutex_unlock(&st->lock); + + return ret; +}; + +static int ad8366_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad8366_state *st = iio_priv(indio_dev); + const struct ad8366_info *inf = st->info; + int code = 0, gain; + int ret; + + /* Values in dB */ + if (val < 0) + gain = (val * 1000) - (val2 / 1000); + else + gain = (val * 1000) + (val2 / 1000); + + if (gain > inf->gain_max || gain < inf->gain_min) + return -EINVAL; + + gain -= inf->gain_step > 0 ? inf->gain_min : inf->gain_max; + code = DIV_ROUND_CLOSEST(gain, inf->gain_step); + + mutex_lock(&st->lock); + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + st->ch[chan->channel] = code; + ret = ad8366_write_code(st); + break; + default: + ret = -EINVAL; + } + mutex_unlock(&st->lock); + + return ret; +} + +static int ad8366_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + return IIO_VAL_INT_PLUS_MICRO_DB; + default: + return -EINVAL; + } +} + +static const struct iio_info ad8366_info = { + .read_raw = &ad8366_read_raw, + .write_raw = &ad8366_write_raw, + .write_raw_get_fmt = &ad8366_write_raw_get_fmt, +}; + +#define AD8366_CHAN(_channel) { \ + .type = IIO_VOLTAGE, \ + .output = 1, \ + .indexed = 1, \ + .channel = _channel, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_HARDWAREGAIN),\ +} + +static const struct iio_chan_spec ad8366_channels[] = { + AD8366_CHAN(0), + AD8366_CHAN(1), +}; + +static int ad8366_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct gpio_desc *enable_gpio; + struct reset_control *rstc; + struct iio_dev *indio_dev; + struct ad8366_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + ret = devm_regulator_get_enable(dev, "vcc"); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulator\n"); + + st->spi = spi; + st->info = spi_get_device_match_data(spi); + + enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_HIGH); + if (IS_ERR(enable_gpio)) + return dev_err_probe(dev, PTR_ERR(enable_gpio), + "Failed to get enable GPIO\n"); + + rstc = devm_reset_control_get_optional_exclusive_deasserted(dev, NULL); + if (IS_ERR(rstc)) + return dev_err_probe(dev, PTR_ERR(rstc), + "Failed to get reset controller\n"); + + indio_dev->name = st->info->name; + indio_dev->info = &ad8366_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad8366_channels; + indio_dev->num_channels = st->info->num_channels; + + ret = ad8366_write_code(st); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to write initial gain\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id ad8366_id[] = { + { .name = "ad8366", .driver_data = (kernel_ulong_t)&ad8366_chip_info }, + { .name = "ada4961", .driver_data = (kernel_ulong_t)&ada4961_chip_info }, + { .name = "adl5240", .driver_data = (kernel_ulong_t)&adl5240_chip_info }, + { .name = "adrf5702", .driver_data = (kernel_ulong_t)&adrf5702_chip_info }, + { .name = "adrf5703", .driver_data = (kernel_ulong_t)&adrf5703_chip_info }, + { .name = "adrf5720", .driver_data = (kernel_ulong_t)&adrf5720_chip_info }, + { .name = "adrf5730", .driver_data = (kernel_ulong_t)&adrf5730_chip_info }, + { .name = "adrf5731", .driver_data = (kernel_ulong_t)&adrf5731_chip_info }, + { .name = "hmc271a", .driver_data = (kernel_ulong_t)&hmc271_chip_info }, + { .name = "hmc792a", .driver_data = (kernel_ulong_t)&hmc792_chip_info }, + { .name = "hmc1018a", .driver_data = (kernel_ulong_t)&hmc1018_chip_info }, + { .name = "hmc1019a", .driver_data = (kernel_ulong_t)&hmc1019_chip_info }, + { .name = "hmc1119", .driver_data = (kernel_ulong_t)&hmc1119_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad8366_id); + +static const struct of_device_id ad8366_of_match[] = { + { .compatible = "adi,ad8366", .data = &ad8366_chip_info }, + { .compatible = "adi,ada4961", .data = &ada4961_chip_info }, + { .compatible = "adi,adl5240", .data = &adl5240_chip_info }, + { .compatible = "adi,adrf5702", .data = &adrf5702_chip_info }, + { .compatible = "adi,adrf5703", .data = &adrf5703_chip_info }, + { .compatible = "adi,adrf5720", .data = &adrf5720_chip_info }, + { .compatible = "adi,adrf5730", .data = &adrf5730_chip_info }, + { .compatible = "adi,adrf5731", .data = &adrf5731_chip_info }, + { .compatible = "adi,hmc271a", .data = &hmc271_chip_info }, + { .compatible = "adi,hmc792a", .data = &hmc792_chip_info }, + { .compatible = "adi,hmc1018a", .data = &hmc1018_chip_info }, + { .compatible = "adi,hmc1019a", .data = &hmc1019_chip_info }, + { .compatible = "adi,hmc1119", .data = &hmc1119_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad8366_of_match); + +static struct spi_driver ad8366_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = ad8366_of_match, + }, + .probe = ad8366_probe, + .id_table = ad8366_id, +}; + +module_spi_driver(ad8366_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD8366 and similar Gain Amplifiers"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/amplifiers/ada4250.c b/drivers/iio/amplifiers/ada4250.c new file mode 100644 index 000000000..9702de3a5 --- /dev/null +++ b/drivers/iio/amplifiers/ada4250.c @@ -0,0 +1,384 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADA4250 driver + * + * Copyright 2022 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ADA4250 Register Map */ +#define ADA4250_REG_GAIN_MUX 0x00 +#define ADA4250_REG_REFBUF_EN 0x01 +#define ADA4250_REG_RESET 0x02 +#define ADA4250_REG_SNSR_CAL_VAL 0x04 +#define ADA4250_REG_SNSR_CAL_CNFG 0x05 +#define ADA4250_REG_DIE_REV 0x18 +#define ADA4250_REG_CHIP_ID 0x19 + +/* ADA4250_REG_GAIN_MUX Map */ +#define ADA4250_GAIN_MUX_MSK GENMASK(2, 0) + +/* ADA4250_REG_REFBUF Map */ +#define ADA4250_REFBUF_MSK BIT(0) + +/* ADA4250_REG_RESET Map */ +#define ADA4250_RESET_MSK BIT(0) + +/* ADA4250_REG_SNSR_CAL_VAL Map */ +#define ADA4250_CAL_CFG_BIAS_MSK GENMASK(7, 0) + +/* ADA4250_REG_SNSR_CAL_CNFG Bit Definition */ +#define ADA4250_BIAS_SET_MSK GENMASK(3, 2) +#define ADA4250_RANGE_SET_MSK GENMASK(1, 0) + +/* Miscellaneous definitions */ +#define ADA4250_CHIP_ID 0x4250 +#define ADA4250_RANGE1 0 +#define ADA4250_RANGE4 3 + +/* ADA4250 current bias set */ +enum ada4250_current_bias { + ADA4250_BIAS_DISABLED, + ADA4250_BIAS_BANDGAP, + ADA4250_BIAS_AVDD, +}; + +struct ada4250_state { + struct spi_device *spi; + struct regmap *regmap; + /* Protect against concurrent accesses to the device and data content */ + struct mutex lock; + int avdd_uv; + int offset_uv; + u8 bias; + u8 gain; + bool refbuf_en; + __le16 reg_val_16 __aligned(IIO_DMA_MINALIGN); +}; + +/* ADA4250 Current Bias Source Settings: Disabled, Bandgap Reference, AVDD */ +static const int calibbias_table[] = {0, 1, 2}; + +/* ADA4250 Gain (V/V) values: 1, 2, 4, 8, 16, 32, 64, 128 */ +static const int hwgain_table[] = {1, 2, 4, 8, 16, 32, 64, 128}; + +static const struct regmap_config ada4250_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .read_flag_mask = BIT(7), + .max_register = 0x1A, +}; + +static int ada4250_set_offset_uv(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int offset_uv) +{ + struct ada4250_state *st = iio_priv(indio_dev); + + int i, ret, x[8], max_vos, min_vos, voltage_v, vlsb = 0; + u8 offset_raw, range = ADA4250_RANGE1; + u32 lsb_coeff[6] = {1333, 2301, 4283, 8289, 16311, 31599}; + + if (st->bias == 0 || st->bias == 3) + return -EINVAL; + + voltage_v = DIV_ROUND_CLOSEST(st->avdd_uv, MICRO); + + if (st->bias == ADA4250_BIAS_AVDD) + x[0] = voltage_v; + else + x[0] = 5; + + x[1] = 126 * (x[0] - 1); + + for (i = 0; i < 6; i++) + x[i + 2] = DIV_ROUND_CLOSEST(x[1] * 1000, lsb_coeff[i]); + + if (st->gain == 0) + return -EINVAL; + + /* + * Compute Range and Voltage per LSB for the Sensor Offset Calibration + * Example of computation for Range 1 and Range 2 (Current Bias Set = AVDD): + * Range 1 Range 2 + * Gain | Max Vos(mV) | LSB(mV) | Max Vos(mV) | LSB(mV) | + * 2 | X1*127 | X1=0.126(AVDD-1) | X1*3*127 | X1*3 | + * 4 | X2*127 | X2=X1/1.3333 | X2*3*127 | X2*3 | + * 8 | X3*127 | X3=X1/2.301 | X3*3*127 | X3*3 | + * 16 | X4*127 | X4=X1/4.283 | X4*3*127 | X4*3 | + * 32 | X5*127 | X5=X1/8.289 | X5*3*127 | X5*3 | + * 64 | X6*127 | X6=X1/16.311 | X6*3*127 | X6*3 | + * 128 | X7*127 | X7=X1/31.599 | X7*3*127 | X7*3 | + */ + for (i = ADA4250_RANGE1; i <= ADA4250_RANGE4; i++) { + max_vos = x[st->gain] * 127 * ((1 << (i + 1)) - 1); + min_vos = -1 * max_vos; + if (offset_uv > min_vos && offset_uv < max_vos) { + range = i; + vlsb = x[st->gain] * ((1 << (i + 1)) - 1); + break; + } + } + + if (vlsb <= 0) + return -EINVAL; + + offset_raw = DIV_ROUND_CLOSEST(abs(offset_uv), vlsb); + + mutex_lock(&st->lock); + ret = regmap_update_bits(st->regmap, ADA4250_REG_SNSR_CAL_CNFG, + ADA4250_RANGE_SET_MSK, + FIELD_PREP(ADA4250_RANGE_SET_MSK, range)); + if (ret) + goto exit; + + st->offset_uv = offset_raw * vlsb; + + /* + * To set the offset calibration value, use bits [6:0] and bit 7 as the + * polarity bit (set to "0" for a negative offset and "1" for a positive + * offset). + */ + if (offset_uv < 0) { + offset_raw |= BIT(7); + st->offset_uv *= (-1); + } + + ret = regmap_write(st->regmap, ADA4250_REG_SNSR_CAL_VAL, offset_raw); + +exit: + mutex_unlock(&st->lock); + + return ret; +} + +static int ada4250_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ada4250_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_HARDWAREGAIN: + ret = regmap_read(st->regmap, ADA4250_REG_GAIN_MUX, val); + if (ret) + return ret; + + *val = BIT(*val); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + *val = st->offset_uv; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + ret = regmap_read(st->regmap, ADA4250_REG_SNSR_CAL_CNFG, val); + if (ret) + return ret; + + *val = FIELD_GET(ADA4250_BIAS_SET_MSK, *val); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 1; + *val2 = 1000000; + + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } +} + +static int ada4250_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct ada4250_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_HARDWAREGAIN: + ret = regmap_write(st->regmap, ADA4250_REG_GAIN_MUX, + FIELD_PREP(ADA4250_GAIN_MUX_MSK, ilog2(val))); + if (ret) + return ret; + + st->gain = ilog2(val); + + return ret; + case IIO_CHAN_INFO_OFFSET: + return ada4250_set_offset_uv(indio_dev, chan, val); + case IIO_CHAN_INFO_CALIBBIAS: + ret = regmap_update_bits(st->regmap, ADA4250_REG_SNSR_CAL_CNFG, + ADA4250_BIAS_SET_MSK, + FIELD_PREP(ADA4250_BIAS_SET_MSK, val)); + if (ret) + return ret; + + st->bias = val; + + return ret; + default: + return -EINVAL; + } +} + +static int ada4250_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + *vals = calibbias_table; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(calibbias_table); + + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_HARDWAREGAIN: + *vals = hwgain_table; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(hwgain_table); + + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int ada4250_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int write_val, + unsigned int *read_val) +{ + struct ada4250_state *st = iio_priv(indio_dev); + + if (read_val) + return regmap_read(st->regmap, reg, read_val); + else + return regmap_write(st->regmap, reg, write_val); +} + +static const struct iio_info ada4250_info = { + .read_raw = ada4250_read_raw, + .write_raw = ada4250_write_raw, + .read_avail = &ada4250_read_avail, + .debugfs_reg_access = &ada4250_reg_access, +}; + +static const struct iio_chan_spec ada4250_channels[] = { + { + .type = IIO_VOLTAGE, + .output = 1, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_HARDWAREGAIN) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_HARDWAREGAIN), + } +}; + +static int ada4250_init(struct ada4250_state *st) +{ + struct device *dev = &st->spi->dev; + int ret; + u16 chip_id; + + st->refbuf_en = device_property_read_bool(dev, "adi,refbuf-enable"); + + st->avdd_uv = devm_regulator_get_enable_read_voltage(dev, "avdd"); + if (st->avdd_uv < 0) + return dev_err_probe(dev, st->avdd_uv, + "failed to get the AVDD voltage\n"); + + ret = regmap_write(st->regmap, ADA4250_REG_RESET, + FIELD_PREP(ADA4250_RESET_MSK, 1)); + if (ret) + return ret; + + ret = regmap_bulk_read(st->regmap, ADA4250_REG_CHIP_ID, &st->reg_val_16, + sizeof(st->reg_val_16)); + if (ret) + return ret; + + chip_id = le16_to_cpu(st->reg_val_16); + + if (chip_id != ADA4250_CHIP_ID) + dev_info(dev, "Invalid chip ID: 0x%02X.\n", chip_id); + + return regmap_write(st->regmap, ADA4250_REG_REFBUF_EN, + FIELD_PREP(ADA4250_REFBUF_MSK, st->refbuf_en)); +} + +static int ada4250_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct regmap *regmap; + struct ada4250_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_spi(spi, &ada4250_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + st = iio_priv(indio_dev); + st->regmap = regmap; + st->spi = spi; + + indio_dev->info = &ada4250_info; + indio_dev->name = "ada4250"; + indio_dev->channels = ada4250_channels; + indio_dev->num_channels = ARRAY_SIZE(ada4250_channels); + + mutex_init(&st->lock); + + ret = ada4250_init(st); + if (ret) + return dev_err_probe(&spi->dev, ret, "ADA4250 init failed\n"); + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id ada4250_id[] = { + { .name = "ada4250" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ada4250_id); + +static const struct of_device_id ada4250_of_match[] = { + { .compatible = "adi,ada4250" }, + { } +}; +MODULE_DEVICE_TABLE(of, ada4250_of_match); + +static struct spi_driver ada4250_driver = { + .driver = { + .name = "ada4250", + .of_match_table = ada4250_of_match, + }, + .probe = ada4250_probe, + .id_table = ada4250_id, +}; +module_spi_driver(ada4250_driver); + +MODULE_AUTHOR("Antoniu Miclaus +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum adl8113_signal_path { + ADL8113_INTERNAL_AMP, + ADL8113_INTERNAL_BYPASS, + ADL8113_EXTERNAL_A, + ADL8113_EXTERNAL_B, +}; + +struct adl8113_gain_config { + enum adl8113_signal_path path; + int gain_db; +}; + +struct adl8113_state { + struct gpio_descs *gpios; + struct adl8113_gain_config *gain_configs; + unsigned int num_gain_configs; + enum adl8113_signal_path current_path; +}; + +static const char * const adl8113_supply_names[] = { + "vdd1", + "vss2", + "vdd2", +}; + +static int adl8113_set_path(struct adl8113_state *st, + enum adl8113_signal_path path) +{ + DECLARE_BITMAP(values, 2); + int ret; + + /* + * Determine GPIO values based on signal path. + * Va: bit 0, Vb: bit 1. + */ + switch (path) { + case ADL8113_INTERNAL_AMP: + bitmap_write(values, 0x00, 0, 2); + break; + case ADL8113_INTERNAL_BYPASS: + bitmap_write(values, 0x03, 0, 2); + break; + case ADL8113_EXTERNAL_A: + bitmap_write(values, 0x02, 0, 2); + break; + case ADL8113_EXTERNAL_B: + bitmap_write(values, 0x01, 0, 2); + break; + default: + return -EINVAL; + } + + ret = gpiod_set_array_value_cansleep(st->gpios->ndescs, st->gpios->desc, + st->gpios->info, values); + if (ret) + return ret; + + st->current_path = path; + return 0; +} + +static int adl8113_find_gain_config(struct adl8113_state *st, int gain_db) +{ + unsigned int i; + + for (i = 0; i < st->num_gain_configs; i++) { + if (st->gain_configs[i].gain_db == gain_db) + return i; + } + return -EINVAL; +} + +static const struct iio_chan_spec adl8113_channels[] = { + { + .type = IIO_VOLTAGE, + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_HARDWAREGAIN), + }, +}; + +static int adl8113_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct adl8113_state *st = iio_priv(indio_dev); + unsigned int i; + + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + /* Find current gain configuration */ + for (i = 0; i < st->num_gain_configs; i++) { + if (st->gain_configs[i].path == st->current_path) { + *val = st->gain_configs[i].gain_db; + *val2 = 0; + return IIO_VAL_INT_PLUS_MICRO_DB; + } + } + return -EINVAL; + default: + return -EINVAL; + } +} + +static int adl8113_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct adl8113_state *st = iio_priv(indio_dev); + int config_idx; + + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + if (val2 != 0) + return -EINVAL; + + config_idx = adl8113_find_gain_config(st, val); + if (config_idx < 0) + return config_idx; + + return adl8113_set_path(st, st->gain_configs[config_idx].path); + default: + return -EINVAL; + } +} + +static const struct iio_info adl8113_info = { + .read_raw = adl8113_read_raw, + .write_raw = adl8113_write_raw, +}; + +static int adl8113_init_gain_configs(struct device *dev, struct adl8113_state *st) +{ + int external_a_gain, external_b_gain; + unsigned int i; + + /* + * Allocate for all 4 possible paths: + * - Internal amp and bypass (always present) + * - External bypass A and B (optional if configured) + */ + st->gain_configs = devm_kcalloc(dev, 4, sizeof(*st->gain_configs), + GFP_KERNEL); + if (!st->gain_configs) + return -ENOMEM; + + /* Start filling the gain configurations with data */ + i = 0; + + /* Always include internal amplifier (14dB) */ + st->gain_configs[i++] = (struct adl8113_gain_config) { + .path = ADL8113_INTERNAL_AMP, + .gain_db = 14, + }; + + /* Always include internal bypass (-2dB insertion loss) */ + st->gain_configs[i++] = (struct adl8113_gain_config) { + .path = ADL8113_INTERNAL_BYPASS, + .gain_db = -2, + }; + + /* Add external bypass A if configured */ + if (!device_property_read_u32(dev, "adi,external-bypass-a-gain-db", + &external_a_gain)) { + st->gain_configs[i++] = (struct adl8113_gain_config) { + .path = ADL8113_EXTERNAL_A, + .gain_db = external_a_gain, + }; + } + + /* Add external bypass B if configured */ + if (!device_property_read_u32(dev, "adi,external-bypass-b-gain-db", + &external_b_gain)) { + st->gain_configs[i++] = (struct adl8113_gain_config) { + .path = ADL8113_EXTERNAL_B, + .gain_db = external_b_gain, + }; + } + + st->num_gain_configs = i; + + return 0; +} + +static int adl8113_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct adl8113_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->gpios = devm_gpiod_get_array(dev, "ctrl", GPIOD_OUT_LOW); + if (IS_ERR(st->gpios)) + return dev_err_probe(dev, PTR_ERR(st->gpios), + "failed to get control GPIOs\n"); + + if (st->gpios->ndescs != 2) + return dev_err_probe(dev, -EINVAL, + "expected 2 control GPIOs, got %u\n", + st->gpios->ndescs); + + ret = devm_regulator_bulk_get_enable(dev, + ARRAY_SIZE(adl8113_supply_names), + adl8113_supply_names); + if (ret) + return dev_err_probe(dev, ret, + "failed to get and enable supplies\n"); + + /* Initialize gain configurations from devicetree */ + ret = adl8113_init_gain_configs(dev, st); + if (ret) + return ret; + + /* Initialize to internal amplifier path (14dB) */ + ret = adl8113_set_path(st, ADL8113_INTERNAL_AMP); + if (ret) + return ret; + + indio_dev->info = &adl8113_info; + indio_dev->name = "adl8113"; + indio_dev->channels = adl8113_channels; + indio_dev->num_channels = ARRAY_SIZE(adl8113_channels); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id adl8113_of_match[] = { + { .compatible = "adi,adl8113" }, + { } +}; +MODULE_DEVICE_TABLE(of, adl8113_of_match); + +static struct platform_driver adl8113_driver = { + .driver = { + .name = "adl8113", + .of_match_table = adl8113_of_match, + }, + .probe = adl8113_probe, +}; +module_platform_driver(adl8113_driver); + +MODULE_AUTHOR("Antoniu Miclaus "); +MODULE_DESCRIPTION("Analog Devices ADL8113 Low Noise Amplifier"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/amplifiers/hmc425a.c b/drivers/iio/amplifiers/hmc425a.c new file mode 100644 index 000000000..85bfc8dcc --- /dev/null +++ b/drivers/iio/amplifiers/hmc425a.c @@ -0,0 +1,424 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * HMC425A and similar Gain Amplifiers + * + * Copyright 2020, 2024 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * The LTC6373 amplifier supports configuring gain using GPIO's with the following + * values (OUTPUT_V / INPUT_V): 0(shutdown), 0.25, 0.5, 1, 2, 4, 8, 16 + * + * Except for the shutdown value, all can be converted to dB using 20 * log10(x) + * From here, it is observed that all values are multiples of the '2' gain setting, + * with the correspondent of 6.020dB. + */ +#define LTC6373_CONVERSION_CONSTANT 6020 +#define LTC6373_MIN_GAIN_CODE 0x6 +#define LTC6373_CONVERSION_MASK GENMASK(2, 0) +#define LTC6373_SHUTDOWN GENMASK(2, 0) + +enum hmc425a_type { + ID_HMC425A, + ID_HMC540S, + ID_ADRF5740, + ID_LTC6373, +}; + +struct hmc425a_chip_info { + const char *name; + const struct iio_chan_spec *channels; + unsigned int num_channels; + unsigned int num_gpios; + int gain_min; + int gain_max; + int default_gain; + int powerdown_val; + bool has_powerdown; + + int (*gain_dB_to_code)(int gain, int *code); + int (*code_to_gain_dB)(int code, int *val, int *val2); +}; + +struct hmc425a_state { + struct mutex lock; /* protect sensor state */ + const struct hmc425a_chip_info *chip_info; + struct gpio_descs *gpios; + u32 gain; + bool powerdown; +}; + +static int gain_dB_to_code(struct hmc425a_state *st, int val, int val2, int *code) +{ + const struct hmc425a_chip_info *inf = st->chip_info; + int gain; + + if (val < 0) + gain = (val * 1000) - (val2 / 1000); + else + gain = (val * 1000) + (val2 / 1000); + + if (gain > inf->gain_max || gain < inf->gain_min) + return -EINVAL; + if (st->powerdown) + return -EPERM; + + return st->chip_info->gain_dB_to_code(gain, code); +} + +static int hmc425a_gain_dB_to_code(int gain, int *code) +{ + *code = ~((abs(gain) / 500) & 0x3F); + return 0; +} + +static int hmc540s_gain_dB_to_code(int gain, int *code) +{ + *code = ~((abs(gain) / 1000) & 0xF); + return 0; +} + +static int adrf5740_gain_dB_to_code(int gain, int *code) +{ + int temp = (abs(gain) / 2000) & 0xF; + + /* Bit [0-3]: 2dB 4dB 8dB 8dB */ + *code = temp & BIT(3) ? temp | BIT(2) : temp; + return 0; +} + +static int ltc6373_gain_dB_to_code(int gain, int *code) +{ + *code = ~(DIV_ROUND_CLOSEST(gain, LTC6373_CONVERSION_CONSTANT) + 3) + & LTC6373_CONVERSION_MASK; + return 0; +} + +static int code_to_gain_dB(struct hmc425a_state *st, int *val, int *val2) +{ + if (st->powerdown) + return -EPERM; + return st->chip_info->code_to_gain_dB(st->gain, val, val2); +} + +static int hmc425a_code_to_gain_dB(int code, int *val, int *val2) +{ + *val = (~code * -500) / 1000; + *val2 = ((~code * -500) % 1000) * 1000; + return 0; +} + +static int hmc540s_code_to_gain_dB(int code, int *val, int *val2) +{ + *val = (~code * -1000) / 1000; + *val2 = ((~code * -1000) % 1000) * 1000; + return 0; +} + +static int adrf5740_code_to_gain_dB(int code, int *val, int *val2) +{ + /* + * Bit [0-3]: 2dB 4dB 8dB 8dB + * When BIT(3) is set, unset BIT(2) and use 3 as double the place value + */ + code = code & BIT(3) ? code & ~BIT(2) : code; + *val = (code * -2000) / 1000; + *val2 = ((code * -2000) % 1000) * 1000; + return 0; +} + +static int ltc6373_code_to_gain_dB(int code, int *val, int *val2) +{ + int gain = ((~code & LTC6373_CONVERSION_MASK) - 3) * + LTC6373_CONVERSION_CONSTANT; + + *val = gain / 1000; + *val2 = (gain % 1000) * 1000; + return 0; +} + +static int hmc425a_write(struct iio_dev *indio_dev, u32 value) +{ + struct hmc425a_state *st = iio_priv(indio_dev); + DECLARE_BITMAP(values, BITS_PER_TYPE(value)); + + values[0] = value; + + gpiod_multi_set_value_cansleep(st->gpios, values); + return 0; +} + +static int hmc425a_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long m) +{ + struct hmc425a_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->lock); + switch (m) { + case IIO_CHAN_INFO_HARDWAREGAIN: + ret = code_to_gain_dB(st, val, val2); + if (ret) + break; + ret = IIO_VAL_INT_PLUS_MICRO_DB; + break; + default: + ret = -EINVAL; + } + mutex_unlock(&st->lock); + + return ret; +}; + +static int hmc425a_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct hmc425a_state *st = iio_priv(indio_dev); + int code = 0, ret; + + mutex_lock(&st->lock); + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + ret = gain_dB_to_code(st, val, val2, &code); + if (ret) + break; + st->gain = code; + + ret = hmc425a_write(indio_dev, st->gain); + break; + default: + ret = -EINVAL; + } + mutex_unlock(&st->lock); + + return ret; +} + +static int hmc425a_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + return IIO_VAL_INT_PLUS_MICRO_DB; + default: + return -EINVAL; + } +} + +static const struct iio_info hmc425a_info = { + .read_raw = &hmc425a_read_raw, + .write_raw = &hmc425a_write_raw, + .write_raw_get_fmt = &hmc425a_write_raw_get_fmt, +}; + +static ssize_t ltc6373_read_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct hmc425a_state *st = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", st->powerdown); +} + +static ssize_t ltc6373_write_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, + size_t len) +{ + struct hmc425a_state *st = iio_priv(indio_dev); + bool powerdown; + int code, ret; + + ret = kstrtobool(buf, &powerdown); + if (ret) + return ret; + + mutex_lock(&st->lock); + st->powerdown = powerdown; + code = (powerdown) ? LTC6373_SHUTDOWN : st->gain; + hmc425a_write(indio_dev, code); + mutex_unlock(&st->lock); + return len; +} + +static const struct iio_chan_spec_ext_info ltc6373_ext_info[] = { + { + .name = "powerdown", + .read = ltc6373_read_powerdown, + .write = ltc6373_write_powerdown, + .shared = IIO_SEPARATE, + }, + { } +}; + +#define HMC425A_CHAN(_channel) \ +{ \ + .type = IIO_VOLTAGE, \ + .output = 1, \ + .indexed = 1, \ + .channel = _channel, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_HARDWAREGAIN), \ +} + +#define LTC6373_CHAN(_channel) \ +{ \ + .type = IIO_VOLTAGE, \ + .output = 1, \ + .indexed = 1, \ + .channel = _channel, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_HARDWAREGAIN), \ + .ext_info = ltc6373_ext_info, \ +} + +static const struct iio_chan_spec hmc425a_channels[] = { + HMC425A_CHAN(0), +}; + +static const struct iio_chan_spec ltc6373_channels[] = { + LTC6373_CHAN(0), +}; + +static const struct hmc425a_chip_info hmc425a_chip_info_tbl[] = { + [ID_HMC425A] = { + .name = "hmc425a", + .channels = hmc425a_channels, + .num_channels = ARRAY_SIZE(hmc425a_channels), + .num_gpios = 6, + .gain_min = -31500, + .gain_max = 0, + .default_gain = -0x40, /* set default gain -31.5db*/ + .gain_dB_to_code = hmc425a_gain_dB_to_code, + .code_to_gain_dB = hmc425a_code_to_gain_dB, + }, + [ID_HMC540S] = { + .name = "hmc540s", + .channels = hmc425a_channels, + .num_channels = ARRAY_SIZE(hmc425a_channels), + .num_gpios = 4, + .gain_min = -15000, + .gain_max = 0, + .default_gain = -0x10, /* set default gain -15.0db*/ + .gain_dB_to_code = hmc540s_gain_dB_to_code, + .code_to_gain_dB = hmc540s_code_to_gain_dB, + }, + [ID_ADRF5740] = { + .name = "adrf5740", + .channels = hmc425a_channels, + .num_channels = ARRAY_SIZE(hmc425a_channels), + .num_gpios = 4, + .gain_min = -22000, + .gain_max = 0, + .default_gain = 0xF, /* set default gain -22.0db*/ + .gain_dB_to_code = adrf5740_gain_dB_to_code, + .code_to_gain_dB = adrf5740_code_to_gain_dB, + }, + [ID_LTC6373] = { + .name = "ltc6373", + .channels = ltc6373_channels, + .num_channels = ARRAY_SIZE(ltc6373_channels), + .num_gpios = 3, + .gain_min = -12041, /* gain setting x0.25*/ + .gain_max = 24082, /* gain setting x16 */ + .default_gain = LTC6373_MIN_GAIN_CODE, + .powerdown_val = LTC6373_SHUTDOWN, + .has_powerdown = true, + .gain_dB_to_code = ltc6373_gain_dB_to_code, + .code_to_gain_dB = ltc6373_code_to_gain_dB, + }, +}; + +static int hmc425a_probe(struct platform_device *pdev) +{ + struct iio_dev *indio_dev; + struct hmc425a_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->chip_info = device_get_match_data(&pdev->dev); + indio_dev->num_channels = st->chip_info->num_channels; + indio_dev->channels = st->chip_info->channels; + indio_dev->name = st->chip_info->name; + st->gain = st->chip_info->default_gain; + + st->gpios = devm_gpiod_get_array(&pdev->dev, "ctrl", GPIOD_OUT_LOW); + if (IS_ERR(st->gpios)) + return dev_err_probe(&pdev->dev, PTR_ERR(st->gpios), + "failed to get gpios\n"); + + if (st->gpios->ndescs != st->chip_info->num_gpios) { + dev_err(&pdev->dev, "%d GPIOs needed to operate\n", + st->chip_info->num_gpios); + return -ENODEV; + } + + ret = devm_regulator_get_enable(&pdev->dev, "vcc-supply"); + if (ret) + return ret; + + mutex_init(&st->lock); + + indio_dev->info = &hmc425a_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (st->chip_info->has_powerdown) { + st->powerdown = true; + hmc425a_write(indio_dev, st->chip_info->powerdown_val); + } else { + /* Set default gain */ + hmc425a_write(indio_dev, st->gain); + } + + return devm_iio_device_register(&pdev->dev, indio_dev); +} + +static const struct of_device_id hmc425a_of_match[] = { + { .compatible = "adi,hmc425a", + .data = &hmc425a_chip_info_tbl[ID_HMC425A]}, + { .compatible = "adi,hmc540s", + .data = &hmc425a_chip_info_tbl[ID_HMC540S]}, + { .compatible = "adi,adrf5740", + .data = &hmc425a_chip_info_tbl[ID_ADRF5740]}, + { .compatible = "adi,ltc6373", + .data = &hmc425a_chip_info_tbl[ID_LTC6373]}, + { } +}; +MODULE_DEVICE_TABLE(of, hmc425a_of_match); + +static struct platform_driver hmc425a_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = hmc425a_of_match, + }, + .probe = hmc425a_probe, +}; +module_platform_driver(hmc425a_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices HMC425A and similar GPIO control Gain Amplifiers"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/buffer/Kconfig b/drivers/iio/buffer/Kconfig new file mode 100644 index 000000000..047b93159 --- /dev/null +++ b/drivers/iio/buffer/Kconfig @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Industrial I/O generic buffer implementations +# +# When adding new entries keep the list in alphabetical order + +config IIO_BUFFER_CB + tristate "IIO callback buffer used for push in-kernel interfaces" + help + Should be selected by any drivers that do in-kernel push + usage. That is, those where the data is pushed to the consumer. + +config IIO_BUFFER_DMA + tristate "Industrial I/O DMA buffer infrastructure" + help + Provides the generic IIO DMA buffer infrastructure that can be used by + drivers for devices with DMA support to implement the IIO buffer. + + Should be selected by drivers that want to use the generic DMA buffer + infrastructure. + +config IIO_BUFFER_DMAENGINE + tristate "Industrial I/O DMA buffer integration with DMAEngine" + select IIO_BUFFER_DMA + help + Provides a bonding of the generic IIO DMA buffer infrastructure with the + DMAEngine framework. This can be used by converter drivers with a DMA port + connected to an external DMA controller which is supported by the + DMAEngine framework. + + Should be selected by drivers that want to use this functionality. + +config IIO_BUFFER_HW_CONSUMER + tristate "Industrial I/O HW buffering" + help + Provides a way to bonding when an IIO device has a direct connection + to another device in hardware. In this case buffers for data transfers + are handled by hardware. + + Should be selected by drivers that want to use the generic Hw consumer + interface. + +config IIO_KFIFO_BUF + tristate "Industrial I/O buffering based on kfifo" + help + A simple fifo based on kfifo. Note that this currently provides + no buffer events so it is up to userspace to work out how + often to read from the buffer. + +config IIO_TRIGGERED_BUFFER + tristate "Industrial I/O triggered buffer support" + select IIO_TRIGGER + select IIO_KFIFO_BUF + help + Provides helper functions for setting up triggered buffers. diff --git a/drivers/iio/buffer/Makefile b/drivers/iio/buffer/Makefile new file mode 100644 index 000000000..1403eb2f9 --- /dev/null +++ b/drivers/iio/buffer/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the industrial I/O buffer implementations +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_IIO_BUFFER_CB) += industrialio-buffer-cb.o +obj-$(CONFIG_IIO_BUFFER_DMA) += industrialio-buffer-dma.o +obj-$(CONFIG_IIO_BUFFER_DMAENGINE) += industrialio-buffer-dmaengine.o +obj-$(CONFIG_IIO_BUFFER_HW_CONSUMER) += industrialio-hw-consumer.o +obj-$(CONFIG_IIO_TRIGGERED_BUFFER) += industrialio-triggered-buffer.o +obj-$(CONFIG_IIO_KFIFO_BUF) += kfifo_buf.o diff --git a/drivers/iio/buffer/industrialio-buffer-cb.c b/drivers/iio/buffer/industrialio-buffer-cb.c new file mode 100644 index 000000000..b58263bcf --- /dev/null +++ b/drivers/iio/buffer/industrialio-buffer-cb.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* The industrial I/O callback buffer + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct iio_cb_buffer { + struct iio_buffer buffer; + /* Must be safe to call from any context (e.g. must not sleep). */ + int (*cb)(const void *data, void *private); + void *private; + struct iio_channel *channels; + struct iio_dev *indio_dev; +}; + +static struct iio_cb_buffer *buffer_to_cb_buffer(struct iio_buffer *buffer) +{ + return container_of(buffer, struct iio_cb_buffer, buffer); +} + +static int iio_buffer_cb_store_to(struct iio_buffer *buffer, const void *data) +{ + struct iio_cb_buffer *cb_buff = buffer_to_cb_buffer(buffer); + return cb_buff->cb(data, cb_buff->private); +} + +static void iio_buffer_cb_release(struct iio_buffer *buffer) +{ + struct iio_cb_buffer *cb_buff = buffer_to_cb_buffer(buffer); + + bitmap_free(cb_buff->buffer.scan_mask); + kfree(cb_buff); +} + +static const struct iio_buffer_access_funcs iio_cb_access = { + .store_to = &iio_buffer_cb_store_to, + .release = &iio_buffer_cb_release, + + .modes = INDIO_BUFFER_SOFTWARE | INDIO_BUFFER_TRIGGERED, +}; + +struct iio_cb_buffer *iio_channel_get_all_cb(struct device *dev, + int (*cb)(const void *data, + void *private), + void *private) +{ + int ret; + struct iio_cb_buffer *cb_buff; + struct iio_channel *chan; + + if (!cb) { + dev_err(dev, "Invalid arguments: A callback must be provided!\n"); + return ERR_PTR(-EINVAL); + } + + cb_buff = kzalloc_obj(*cb_buff); + if (cb_buff == NULL) + return ERR_PTR(-ENOMEM); + + iio_buffer_init(&cb_buff->buffer); + + cb_buff->private = private; + cb_buff->cb = cb; + cb_buff->buffer.access = &iio_cb_access; + + cb_buff->channels = iio_channel_get_all(dev); + if (IS_ERR(cb_buff->channels)) { + ret = PTR_ERR(cb_buff->channels); + goto error_free_cb_buff; + } + + cb_buff->indio_dev = cb_buff->channels[0].indio_dev; + cb_buff->buffer.scan_mask = bitmap_zalloc(iio_get_masklength(cb_buff->indio_dev), + GFP_KERNEL); + if (cb_buff->buffer.scan_mask == NULL) { + ret = -ENOMEM; + goto error_release_channels; + } + chan = &cb_buff->channels[0]; + while (chan->indio_dev) { + if (chan->indio_dev != cb_buff->indio_dev) { + ret = -EINVAL; + goto error_free_scan_mask; + } + set_bit(chan->channel->scan_index, + cb_buff->buffer.scan_mask); + chan++; + } + + return cb_buff; + +error_free_scan_mask: + bitmap_free(cb_buff->buffer.scan_mask); +error_release_channels: + iio_channel_release_all(cb_buff->channels); +error_free_cb_buff: + kfree(cb_buff); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(iio_channel_get_all_cb); + +int iio_channel_cb_set_buffer_watermark(struct iio_cb_buffer *cb_buff, + size_t watermark) +{ + if (!watermark) + return -EINVAL; + cb_buff->buffer.watermark = watermark; + + return 0; +} +EXPORT_SYMBOL_GPL(iio_channel_cb_set_buffer_watermark); + +int iio_channel_start_all_cb(struct iio_cb_buffer *cb_buff) +{ + return iio_update_buffers(cb_buff->indio_dev, &cb_buff->buffer, + NULL); +} +EXPORT_SYMBOL_GPL(iio_channel_start_all_cb); + +void iio_channel_stop_all_cb(struct iio_cb_buffer *cb_buff) +{ + iio_update_buffers(cb_buff->indio_dev, NULL, &cb_buff->buffer); +} +EXPORT_SYMBOL_GPL(iio_channel_stop_all_cb); + +void iio_channel_release_all_cb(struct iio_cb_buffer *cb_buff) +{ + iio_channel_release_all(cb_buff->channels); + iio_buffer_put(&cb_buff->buffer); +} +EXPORT_SYMBOL_GPL(iio_channel_release_all_cb); + +struct iio_channel +*iio_channel_cb_get_channels(const struct iio_cb_buffer *cb_buffer) +{ + return cb_buffer->channels; +} +EXPORT_SYMBOL_GPL(iio_channel_cb_get_channels); + +struct iio_dev +*iio_channel_cb_get_iio_dev(const struct iio_cb_buffer *cb_buffer) +{ + return cb_buffer->indio_dev; +} +EXPORT_SYMBOL_GPL(iio_channel_cb_get_iio_dev); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("Industrial I/O callback buffer"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_CONSUMER"); diff --git a/drivers/iio/buffer/industrialio-buffer-dma.c b/drivers/iio/buffer/industrialio-buffer-dma.c new file mode 100644 index 000000000..1daac2308 --- /dev/null +++ b/drivers/iio/buffer/industrialio-buffer-dma.c @@ -0,0 +1,875 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2013-2015 Analog Devices Inc. + * Author: Lars-Peter Clausen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * For DMA buffers the storage is sub-divided into so called blocks. Each block + * has its own memory buffer. The size of the block is the granularity at which + * memory is exchanged between the hardware and the application. Increasing the + * basic unit of data exchange from one sample to one block decreases the + * management overhead that is associated with each sample. E.g. if we say the + * management overhead for one exchange is x and the unit of exchange is one + * sample the overhead will be x for each sample. Whereas when using a block + * which contains n samples the overhead per sample is reduced to x/n. This + * allows to achieve much higher samplerates than what can be sustained with + * the one sample approach. + * + * Blocks are exchanged between the DMA controller and the application via the + * means of two queues. The incoming queue and the outgoing queue. Blocks on the + * incoming queue are waiting for the DMA controller to pick them up and fill + * them with data. Block on the outgoing queue have been filled with data and + * are waiting for the application to dequeue them and read the data. + * + * A block can be in one of the following states: + * * Owned by the application. In this state the application can read data from + * the block. + * * On the incoming list: Blocks on the incoming list are queued up to be + * processed by the DMA controller. + * * Owned by the DMA controller: The DMA controller is processing the block + * and filling it with data. + * * On the outgoing list: Blocks on the outgoing list have been successfully + * processed by the DMA controller and contain data. They can be dequeued by + * the application. + * * Dead: A block that is dead has been marked as to be freed. It might still + * be owned by either the application or the DMA controller at the moment. + * But once they are done processing it instead of going to either the + * incoming or outgoing queue the block will be freed. + * + * In addition to this blocks are reference counted and the memory associated + * with both the block structure as well as the storage memory for the block + * will be freed when the last reference to the block is dropped. This means a + * block must not be accessed without holding a reference. + * + * The iio_dma_buffer implementation provides a generic infrastructure for + * managing the blocks. + * + * A driver for a specific piece of hardware that has DMA capabilities need to + * implement the submit() callback from the iio_dma_buffer_ops structure. This + * callback is supposed to initiate the DMA transfer copying data from the + * converter to the memory region of the block. Once the DMA transfer has been + * completed the driver must call iio_dma_buffer_block_done() for the completed + * block. + * + * Prior to this it must set the bytes_used field of the block contains + * the actual number of bytes in the buffer. Typically this will be equal to the + * size of the block, but if the DMA hardware has certain alignment requirements + * for the transfer length it might choose to use less than the full size. In + * either case it is expected that bytes_used is a multiple of the bytes per + * datum, i.e. the block must not contain partial samples. + * + * The driver must call iio_dma_buffer_block_done() for each block it has + * received through its submit_block() callback, even if it does not actually + * perform a DMA transfer for the block, e.g. because the buffer was disabled + * before the block transfer was started. In this case it should set bytes_used + * to 0. + * + * In addition it is recommended that a driver implements the abort() callback. + * It will be called when the buffer is disabled and can be used to cancel + * pending and stop active transfers. + * + * The specific driver implementation should use the default callback + * implementations provided by this module for the iio_buffer_access_funcs + * struct. It may overload some callbacks with custom variants if the hardware + * has special requirements that are not handled by the generic functions. If a + * driver chooses to overload a callback it has to ensure that the generic + * callback is called from within the custom callback. + */ + +static void iio_buffer_block_release(struct kref *kref) +{ + struct iio_dma_buffer_block *block = container_of(kref, + struct iio_dma_buffer_block, kref); + struct iio_dma_buffer_queue *queue = block->queue; + + WARN_ON(block->fileio && block->state != IIO_BLOCK_STATE_DEAD); + + if (block->fileio) { + dma_free_coherent(queue->dev, PAGE_ALIGN(block->size), + block->vaddr, block->phys_addr); + } else { + atomic_dec(&queue->num_dmabufs); + } + + iio_buffer_put(&queue->buffer); + kfree(block); +} + +static void iio_buffer_block_get(struct iio_dma_buffer_block *block) +{ + kref_get(&block->kref); +} + +static void iio_buffer_block_put(struct iio_dma_buffer_block *block) +{ + kref_put(&block->kref, iio_buffer_block_release); +} + +/* + * dma_free_coherent can sleep, hence we need to take some special care to be + * able to drop a reference from an atomic context. + */ +static LIST_HEAD(iio_dma_buffer_dead_blocks); +static DEFINE_SPINLOCK(iio_dma_buffer_dead_blocks_lock); + +static void iio_dma_buffer_cleanup_worker(struct work_struct *work) +{ + struct iio_dma_buffer_block *block, *_block; + LIST_HEAD(block_list); + + scoped_guard(spinlock_irq, &iio_dma_buffer_dead_blocks_lock) + list_splice_tail_init(&iio_dma_buffer_dead_blocks, &block_list); + + list_for_each_entry_safe(block, _block, &block_list, head) + iio_buffer_block_release(&block->kref); +} +static DECLARE_WORK(iio_dma_buffer_cleanup_work, iio_dma_buffer_cleanup_worker); + +static void iio_buffer_block_release_atomic(struct kref *kref) +{ + struct iio_dma_buffer_block *block; + + block = container_of(kref, struct iio_dma_buffer_block, kref); + + scoped_guard(spinlock_irqsave, &iio_dma_buffer_dead_blocks_lock) + list_add_tail(&block->head, &iio_dma_buffer_dead_blocks); + + schedule_work(&iio_dma_buffer_cleanup_work); +} + +/* + * Version of iio_buffer_block_put() that can be called from atomic context + */ +static void iio_buffer_block_put_atomic(struct iio_dma_buffer_block *block) +{ + kref_put(&block->kref, iio_buffer_block_release_atomic); +} + +static struct iio_dma_buffer_queue *iio_buffer_to_queue(struct iio_buffer *buf) +{ + return container_of(buf, struct iio_dma_buffer_queue, buffer); +} + +static struct iio_dma_buffer_block * +iio_dma_buffer_alloc_block(struct iio_dma_buffer_queue *queue, size_t size, + bool fileio) +{ + struct iio_dma_buffer_block *block __free(kfree) = + kzalloc_obj(*block); + if (!block) + return NULL; + + if (fileio) { + block->vaddr = dma_alloc_coherent(queue->dev, PAGE_ALIGN(size), + &block->phys_addr, GFP_KERNEL); + if (!block->vaddr) + return NULL; + } + + block->fileio = fileio; + block->size = size; + block->state = IIO_BLOCK_STATE_DONE; + block->queue = queue; + INIT_LIST_HEAD(&block->head); + kref_init(&block->kref); + + iio_buffer_get(&queue->buffer); + + if (!fileio) + atomic_inc(&queue->num_dmabufs); + + return_ptr(block); +} + +static void _iio_dma_buffer_block_done(struct iio_dma_buffer_block *block) +{ + if (block->state != IIO_BLOCK_STATE_DEAD) + block->state = IIO_BLOCK_STATE_DONE; +} + +static void iio_dma_buffer_queue_wake(struct iio_dma_buffer_queue *queue) +{ + __poll_t flags; + + if (queue->buffer.direction == IIO_BUFFER_DIRECTION_IN) + flags = EPOLLIN | EPOLLRDNORM; + else + flags = EPOLLOUT | EPOLLWRNORM; + + wake_up_interruptible_poll(&queue->buffer.pollq, flags); +} + +/** + * iio_dma_buffer_block_done() - Indicate that a block has been completed + * @block: The completed block + * + * Should be called when the DMA controller has finished handling the block to + * pass back ownership of the block to the queue. + */ +void iio_dma_buffer_block_done(struct iio_dma_buffer_block *block) +{ + struct iio_dma_buffer_queue *queue = block->queue; + bool cookie; + + cookie = dma_fence_begin_signalling(); + + scoped_guard(spinlock_irqsave, &queue->list_lock) + _iio_dma_buffer_block_done(block); + + if (!block->fileio) + iio_buffer_signal_dmabuf_done(block->fence, 0); + + iio_buffer_block_put_atomic(block); + iio_dma_buffer_queue_wake(queue); + dma_fence_end_signalling(cookie); +} +EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_block_done, "IIO_DMA_BUFFER"); + +/** + * iio_dma_buffer_block_list_abort() - Indicate that a list block has been + * aborted + * @queue: Queue for which to complete blocks. + * @list: List of aborted blocks. All blocks in this list must be from @queue. + * + * Typically called from the abort() callback after the DMA controller has been + * stopped. This will set bytes_used to 0 for each block in the list and then + * hand the blocks back to the queue. + */ +void iio_dma_buffer_block_list_abort(struct iio_dma_buffer_queue *queue, + struct list_head *list) +{ + struct iio_dma_buffer_block *block, *_block; + bool cookie; + + cookie = dma_fence_begin_signalling(); + + scoped_guard(spinlock_irqsave, &queue->list_lock) { + list_for_each_entry_safe(block, _block, list, head) { + list_del(&block->head); + block->bytes_used = 0; + _iio_dma_buffer_block_done(block); + + if (!block->fileio) + iio_buffer_signal_dmabuf_done(block->fence, + -EINTR); + iio_buffer_block_put_atomic(block); + } + } + + if (queue->fileio.enabled) + queue->fileio.enabled = false; + + iio_dma_buffer_queue_wake(queue); + dma_fence_end_signalling(cookie); +} +EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_block_list_abort, "IIO_DMA_BUFFER"); + +static bool iio_dma_block_reusable(struct iio_dma_buffer_block *block) +{ + /* + * If the core owns the block it can be re-used. This should be the + * default case when enabling the buffer, unless the DMA controller does + * not support abort and has not given back the block yet. + */ + switch (block->state) { + case IIO_BLOCK_STATE_QUEUED: + case IIO_BLOCK_STATE_DONE: + return true; + default: + return false; + } +} + +static bool iio_dma_buffer_can_use_fileio(struct iio_dma_buffer_queue *queue) +{ + /* + * Note that queue->num_dmabufs cannot increase while the queue is + * locked, it can only decrease, so it does not race against + * iio_dma_buffer_alloc_block(). + */ + return queue->fileio.enabled || !atomic_read(&queue->num_dmabufs); +} + +/** + * iio_dma_buffer_request_update() - DMA buffer request_update callback + * @buffer: The buffer which to request an update + * + * Should be used as the iio_dma_buffer_request_update() callback for + * iio_buffer_access_ops struct for DMA buffers. + */ +int iio_dma_buffer_request_update(struct iio_buffer *buffer) +{ + struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer); + struct iio_dma_buffer_block *block; + bool try_reuse = false; + size_t size; + int i; + + /* + * Split the buffer into two even parts. This is used as a double + * buffering scheme with usually one block at a time being used by the + * DMA and the other one by the application. + */ + size = DIV_ROUND_UP(queue->buffer.bytes_per_datum * + queue->buffer.length, 2); + + guard(mutex)(&queue->lock); + + queue->fileio.enabled = iio_dma_buffer_can_use_fileio(queue); + + /* If DMABUFs were created, disable fileio interface */ + if (!queue->fileio.enabled) + return 0; + + /* Allocations are page aligned */ + if (PAGE_ALIGN(queue->fileio.block_size) == PAGE_ALIGN(size)) + try_reuse = true; + + queue->fileio.block_size = size; + queue->fileio.active_block = NULL; + + scoped_guard(spinlock_irq, &queue->list_lock) { + for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) { + block = queue->fileio.blocks[i]; + + /* If we can't re-use it free it */ + if (block && (!iio_dma_block_reusable(block) || !try_reuse)) + block->state = IIO_BLOCK_STATE_DEAD; + } + + /* + * At this point all blocks are either owned by the core or + * marked as dead. This means we can reset the lists without + * having to fear corruption. + */ + } + + INIT_LIST_HEAD(&queue->incoming); + + for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) { + if (queue->fileio.blocks[i]) { + block = queue->fileio.blocks[i]; + if (block->state == IIO_BLOCK_STATE_DEAD) { + /* Could not reuse it */ + iio_buffer_block_put(block); + block = NULL; + } else { + block->size = size; + } + } else { + block = NULL; + } + + if (!block) { + block = iio_dma_buffer_alloc_block(queue, size, true); + if (!block) + return -ENOMEM; + + queue->fileio.blocks[i] = block; + } + + /* + * block->bytes_used may have been modified previously, e.g. by + * iio_dma_buffer_block_list_abort(). Reset it here to the + * block's so that iio_dma_buffer_io() will work. + */ + block->bytes_used = block->size; + + /* + * If it's an input buffer, mark the block as queued, and + * iio_dma_buffer_enable() will submit it. Otherwise mark it as + * done, which means it's ready to be dequeued. + */ + if (queue->buffer.direction == IIO_BUFFER_DIRECTION_IN) { + block->state = IIO_BLOCK_STATE_QUEUED; + list_add_tail(&block->head, &queue->incoming); + } else { + block->state = IIO_BLOCK_STATE_DONE; + } + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_request_update, "IIO_DMA_BUFFER"); + +static void iio_dma_buffer_fileio_free(struct iio_dma_buffer_queue *queue) +{ + unsigned int i; + + scoped_guard(spinlock_irq, &queue->list_lock) { + for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) { + if (!queue->fileio.blocks[i]) + continue; + queue->fileio.blocks[i]->state = IIO_BLOCK_STATE_DEAD; + } + } + + INIT_LIST_HEAD(&queue->incoming); + + for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) { + if (!queue->fileio.blocks[i]) + continue; + iio_buffer_block_put(queue->fileio.blocks[i]); + queue->fileio.blocks[i] = NULL; + } + queue->fileio.active_block = NULL; +} + +static void iio_dma_buffer_submit_block(struct iio_dma_buffer_queue *queue, + struct iio_dma_buffer_block *block) +{ + int ret; + + /* + * If the hardware has already been removed we put the block into + * limbo. It will neither be on the incoming nor outgoing list, nor will + * it ever complete. It will just wait to be freed eventually. + */ + if (!queue->ops) + return; + + block->state = IIO_BLOCK_STATE_ACTIVE; + iio_buffer_block_get(block); + + ret = queue->ops->submit(queue, block); + if (ret) { + if (!block->fileio) + iio_buffer_signal_dmabuf_done(block->fence, ret); + + /* + * This is a bit of a problem and there is not much we can do + * other then wait for the buffer to be disabled and re-enabled + * and try again. But it should not really happen unless we run + * out of memory or something similar. + * + * TODO: Implement support in the IIO core to allow buffers to + * notify consumers that something went wrong and the buffer + * should be disabled. + */ + iio_buffer_block_put(block); + } +} + +/** + * iio_dma_buffer_enable() - Enable DMA buffer + * @buffer: IIO buffer to enable + * @indio_dev: IIO device the buffer is attached to + * + * Needs to be called when the device that the buffer is attached to starts + * sampling. Typically should be the iio_buffer_access_ops enable callback. + * + * This will allocate the DMA buffers and start the DMA transfers. + */ +int iio_dma_buffer_enable(struct iio_buffer *buffer, struct iio_dev *indio_dev) +{ + struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer); + struct iio_dma_buffer_block *block, *_block; + + guard(mutex)(&queue->lock); + queue->active = true; + list_for_each_entry_safe(block, _block, &queue->incoming, head) { + list_del(&block->head); + iio_dma_buffer_submit_block(queue, block); + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_enable, "IIO_DMA_BUFFER"); + +/** + * iio_dma_buffer_disable() - Disable DMA buffer + * @buffer: IIO DMA buffer to disable + * @indio_dev: IIO device the buffer is attached to + * + * Needs to be called when the device that the buffer is attached to stops + * sampling. Typically should be the iio_buffer_access_ops disable callback. + */ +int iio_dma_buffer_disable(struct iio_buffer *buffer, struct iio_dev *indio_dev) +{ + struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer); + + guard(mutex)(&queue->lock); + queue->active = false; + + if (queue->ops && queue->ops->abort) + queue->ops->abort(queue); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_disable, "IIO_DMA_BUFFER"); + +static void iio_dma_buffer_enqueue(struct iio_dma_buffer_queue *queue, + struct iio_dma_buffer_block *block) +{ + if (block->state == IIO_BLOCK_STATE_DEAD) { + iio_buffer_block_put(block); + } else if (queue->active) { + iio_dma_buffer_submit_block(queue, block); + } else { + block->state = IIO_BLOCK_STATE_QUEUED; + list_add_tail(&block->head, &queue->incoming); + } +} + +static struct iio_dma_buffer_block * +iio_dma_buffer_dequeue(struct iio_dma_buffer_queue *queue) +{ + struct iio_dma_buffer_block *block; + unsigned int idx; + + guard(spinlock_irq)(&queue->list_lock); + + idx = queue->fileio.next_dequeue; + block = queue->fileio.blocks[idx]; + + if (block->state != IIO_BLOCK_STATE_DONE) + return NULL; + + idx = (idx + 1) % ARRAY_SIZE(queue->fileio.blocks); + queue->fileio.next_dequeue = idx; + + return block; +} + +static int iio_dma_buffer_io(struct iio_buffer *buffer, size_t n, + char __user *user_buffer, bool is_from_user) +{ + struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer); + struct iio_dma_buffer_block *block; + void *addr; + int ret; + + if (n < buffer->bytes_per_datum) + return -EINVAL; + + guard(mutex)(&queue->lock); + + if (!queue->fileio.active_block) { + block = iio_dma_buffer_dequeue(queue); + if (!block) + return 0; + + queue->fileio.pos = 0; + queue->fileio.active_block = block; + } else { + block = queue->fileio.active_block; + } + + n = rounddown(n, buffer->bytes_per_datum); + if (n > block->bytes_used - queue->fileio.pos) + n = block->bytes_used - queue->fileio.pos; + addr = block->vaddr + queue->fileio.pos; + + if (is_from_user) + ret = copy_from_user(addr, user_buffer, n); + else + ret = copy_to_user(user_buffer, addr, n); + if (ret) + return -EFAULT; + + queue->fileio.pos += n; + + if (queue->fileio.pos == block->bytes_used) { + queue->fileio.active_block = NULL; + iio_dma_buffer_enqueue(queue, block); + } + + return n; +} + +/** + * iio_dma_buffer_read() - DMA buffer read callback + * @buffer: Buffer to read from + * @n: Number of bytes to read + * @user_buffer: Userspace buffer to copy the data to + * + * Should be used as the read callback for iio_buffer_access_ops + * struct for DMA buffers. + */ +int iio_dma_buffer_read(struct iio_buffer *buffer, size_t n, + char __user *user_buffer) +{ + return iio_dma_buffer_io(buffer, n, user_buffer, false); +} +EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_read, "IIO_DMA_BUFFER"); + +/** + * iio_dma_buffer_write() - DMA buffer write callback + * @buffer: Buffer to write to + * @n: Number of bytes to read + * @user_buffer: Userspace buffer to copy the data from + * + * Should be used as the write callback for iio_buffer_access_ops + * struct for DMA buffers. + */ +int iio_dma_buffer_write(struct iio_buffer *buffer, size_t n, + const char __user *user_buffer) +{ + return iio_dma_buffer_io(buffer, n, + (__force __user char *)user_buffer, true); +} +EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_write, "IIO_DMA_BUFFER"); + +/** + * iio_dma_buffer_usage() - DMA buffer data_available and + * space_available callback + * @buf: Buffer to check for data availability + * + * Should be used as the data_available and space_available callbacks for + * iio_buffer_access_ops struct for DMA buffers. + */ +size_t iio_dma_buffer_usage(struct iio_buffer *buf) +{ + struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buf); + struct iio_dma_buffer_block *block; + size_t data_available = 0; + unsigned int i; + + /* + * For counting the available bytes we'll use the size of the block not + * the number of actual bytes available in the block. Otherwise it is + * possible that we end up with a value that is lower than the watermark + * but won't increase since all blocks are in use. + */ + + guard(mutex)(&queue->lock); + if (queue->fileio.active_block) + data_available += queue->fileio.active_block->size; + + guard(spinlock_irq)(&queue->list_lock); + + for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) { + block = queue->fileio.blocks[i]; + + if (block != queue->fileio.active_block && block->state == IIO_BLOCK_STATE_DONE) + data_available += block->size; + } + + return data_available; +} +EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_usage, "IIO_DMA_BUFFER"); + +struct iio_dma_buffer_block * +iio_dma_buffer_attach_dmabuf(struct iio_buffer *buffer, + struct dma_buf_attachment *attach) +{ + struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer); + struct iio_dma_buffer_block *block; + + guard(mutex)(&queue->lock); + + /* + * If the buffer is enabled and in fileio mode new blocks can't be + * allocated. + */ + if (queue->fileio.enabled) + return ERR_PTR(-EBUSY); + + block = iio_dma_buffer_alloc_block(queue, attach->dmabuf->size, false); + if (!block) + return ERR_PTR(-ENOMEM); + + /* Free memory that might be in use for fileio mode */ + iio_dma_buffer_fileio_free(queue); + + return block; +} +EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_attach_dmabuf, "IIO_DMA_BUFFER"); + +void iio_dma_buffer_detach_dmabuf(struct iio_buffer *buffer, + struct iio_dma_buffer_block *block) +{ + block->state = IIO_BLOCK_STATE_DEAD; + iio_buffer_block_put_atomic(block); +} +EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_detach_dmabuf, "IIO_DMA_BUFFER"); + +static int iio_dma_can_enqueue_block(struct iio_dma_buffer_block *block) +{ + struct iio_dma_buffer_queue *queue = block->queue; + + /* If in fileio mode buffers can't be enqueued. */ + if (queue->fileio.enabled) + return -EBUSY; + + switch (block->state) { + case IIO_BLOCK_STATE_QUEUED: + return -EPERM; + case IIO_BLOCK_STATE_ACTIVE: + case IIO_BLOCK_STATE_DEAD: + return -EBUSY; + case IIO_BLOCK_STATE_DONE: + break; + } + + return 0; +} + +int iio_dma_buffer_enqueue_dmabuf(struct iio_buffer *buffer, + struct iio_dma_buffer_block *block, + struct dma_fence *fence, + struct sg_table *sgt, + size_t size, bool cyclic) +{ + struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer); + bool cookie; + int ret; + + lockdep_assert_held(&queue->lock); + + cookie = dma_fence_begin_signalling(); + + ret = iio_dma_can_enqueue_block(block); + if (ret < 0) + goto out_end_signalling; + + block->bytes_used = size; + block->cyclic = cyclic; + block->sg_table = sgt; + block->fence = fence; + + iio_dma_buffer_enqueue(queue, block); + +out_end_signalling: + dma_fence_end_signalling(cookie); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_enqueue_dmabuf, "IIO_DMA_BUFFER"); + +struct device *iio_dma_buffer_get_dma_dev(struct iio_buffer *buffer) +{ + return iio_buffer_to_queue(buffer)->dev; +} +EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_get_dma_dev, "IIO_DMA_BUFFER"); + +void iio_dma_buffer_lock_queue(struct iio_buffer *buffer) +{ + struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer); + + mutex_lock(&queue->lock); +} +EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_lock_queue, "IIO_DMA_BUFFER"); + +void iio_dma_buffer_unlock_queue(struct iio_buffer *buffer) +{ + struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer); + + mutex_unlock(&queue->lock); +} +EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_unlock_queue, "IIO_DMA_BUFFER"); + +/** + * iio_dma_buffer_set_bytes_per_datum() - DMA buffer set_bytes_per_datum callback + * @buffer: Buffer to set the bytes-per-datum for + * @bpd: The new bytes-per-datum value + * + * Should be used as the set_bytes_per_datum callback for iio_buffer_access_ops + * struct for DMA buffers. + */ +int iio_dma_buffer_set_bytes_per_datum(struct iio_buffer *buffer, size_t bpd) +{ + buffer->bytes_per_datum = bpd; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_set_bytes_per_datum, "IIO_DMA_BUFFER"); + +/** + * iio_dma_buffer_set_length - DMA buffer set_length callback + * @buffer: Buffer to set the length for + * @length: The new buffer length + * + * Should be used as the set_length callback for iio_buffer_access_ops + * struct for DMA buffers. + */ +int iio_dma_buffer_set_length(struct iio_buffer *buffer, unsigned int length) +{ + /* Avoid an invalid state */ + if (length < 2) + length = 2; + buffer->length = length; + buffer->watermark = length / 2; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_set_length, "IIO_DMA_BUFFER"); + +/** + * iio_dma_buffer_init() - Initialize DMA buffer queue + * @queue: Buffer to initialize + * @dev: DMA device + * @ops: DMA buffer queue callback operations + * + * The DMA device will be used by the queue to do DMA memory allocations. So it + * should refer to the device that will perform the DMA to ensure that + * allocations are done from a memory region that can be accessed by the device. + */ +void iio_dma_buffer_init(struct iio_dma_buffer_queue *queue, struct device *dev, + const struct iio_dma_buffer_ops *ops) +{ + iio_buffer_init(&queue->buffer); + queue->buffer.length = PAGE_SIZE; + queue->buffer.watermark = queue->buffer.length / 2; + queue->dev = dev; + queue->ops = ops; + + INIT_LIST_HEAD(&queue->incoming); + + mutex_init(&queue->lock); + spin_lock_init(&queue->list_lock); +} +EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_init, "IIO_DMA_BUFFER"); + +/** + * iio_dma_buffer_exit() - Cleanup DMA buffer queue + * @queue: Buffer to cleanup + * + * After this function has completed it is safe to free any resources that are + * associated with the buffer and are accessed inside the callback operations. + */ +void iio_dma_buffer_exit(struct iio_dma_buffer_queue *queue) +{ + guard(mutex)(&queue->lock); + + iio_dma_buffer_fileio_free(queue); + queue->ops = NULL; +} +EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_exit, "IIO_DMA_BUFFER"); + +/** + * iio_dma_buffer_release() - Release final buffer resources + * @queue: Buffer to release + * + * Frees resources that can't yet be freed in iio_dma_buffer_exit(). Should be + * called in the buffers release callback implementation right before freeing + * the memory associated with the buffer. + */ +void iio_dma_buffer_release(struct iio_dma_buffer_queue *queue) +{ + mutex_destroy(&queue->lock); +} +EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_release, "IIO_DMA_BUFFER"); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("DMA buffer for the IIO framework"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c new file mode 100644 index 000000000..1bd82d3db --- /dev/null +++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c @@ -0,0 +1,459 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2014-2015 Analog Devices Inc. + * Author: Lars-Peter Clausen + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* + * The IIO DMAengine buffer combines the generic IIO DMA buffer infrastructure + * with the DMAengine framework. The generic IIO DMA buffer infrastructure is + * used to manage the buffer memory and implement the IIO buffer operations + * while the DMAengine framework is used to perform the DMA transfers. Combined + * this results in a device independent fully functional DMA buffer + * implementation that can be used by device drivers for peripherals which are + * connected to a DMA controller which has a DMAengine driver implementation. + */ + +struct dmaengine_buffer { + struct iio_dma_buffer_queue queue; + + struct dma_chan *chan; + struct list_head active; + + size_t align; + size_t max_size; +}; + +static struct dmaengine_buffer *iio_buffer_to_dmaengine_buffer(struct iio_buffer *buffer) +{ + return container_of(buffer, struct dmaengine_buffer, queue.buffer); +} + +static void iio_dmaengine_buffer_block_done(void *data, + const struct dmaengine_result *result) +{ + struct iio_dma_buffer_block *block = data; + + scoped_guard(spinlock_irqsave, &block->queue->list_lock) + list_del(&block->head); + block->bytes_used -= result->residue; + iio_dma_buffer_block_done(block); +} + +/* + * Cyclic transfers run until abort. Cap active cyclic blocks to one until there + * is a use case for more. + */ +static bool iio_dmaengine_buffer_has_active_cyclic(struct dmaengine_buffer *dmaengine_buffer) +{ + struct iio_dma_buffer_block *block; + + guard(spinlock_irqsave)(&dmaengine_buffer->queue.list_lock); + list_for_each_entry(block, &dmaengine_buffer->active, head) { + if (block->cyclic) + return true; + } + + return false; +} + +static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue, + struct iio_dma_buffer_block *block) +{ + struct dmaengine_buffer *dmaengine_buffer = + iio_buffer_to_dmaengine_buffer(&queue->buffer); + struct dma_async_tx_descriptor *desc; + enum dma_transfer_direction dma_dir; + struct scatterlist *sgl; + struct dma_vec *vecs; + size_t max_size; + dma_cookie_t cookie; + size_t len_total; + unsigned int i; + int nents; + + max_size = min(block->size, dmaengine_buffer->max_size); + max_size = round_down(max_size, dmaengine_buffer->align); + + if (queue->buffer.direction == IIO_BUFFER_DIRECTION_IN) + dma_dir = DMA_DEV_TO_MEM; + else + dma_dir = DMA_MEM_TO_DEV; + + if (block->cyclic && iio_dmaengine_buffer_has_active_cyclic(dmaengine_buffer)) { + dev_err(queue->dev, "cyclic DMA transfer already active\n"); + return -EBUSY; + } + + if (block->sg_table) { + unsigned long flags; + + sgl = block->sg_table->sgl; + nents = sg_nents_for_len(sgl, block->bytes_used); + if (nents < 0) + return nents; + + vecs = kmalloc_objs(*vecs, nents, GFP_ATOMIC); + if (!vecs) + return -ENOMEM; + + len_total = block->bytes_used; + + for (i = 0; i < nents; i++) { + vecs[i].addr = sg_dma_address(sgl); + vecs[i].len = min(sg_dma_len(sgl), len_total); + len_total -= vecs[i].len; + + sgl = sg_next(sgl); + } + + if (block->cyclic) + flags = DMA_PREP_REPEAT; + else + flags = DMA_PREP_INTERRUPT; + + /* + * A new transfer may need to end an already active cyclic transfer + * before it can run, so always set the EOT flag. + */ + flags |= DMA_PREP_LOAD_EOT; + desc = dmaengine_prep_peripheral_dma_vec(dmaengine_buffer->chan, + vecs, nents, dma_dir, + flags); + kfree(vecs); + } else { + max_size = min(block->size, dmaengine_buffer->max_size); + max_size = round_down(max_size, dmaengine_buffer->align); + + if (queue->buffer.direction == IIO_BUFFER_DIRECTION_IN) + block->bytes_used = max_size; + + if (!block->bytes_used || block->bytes_used > max_size) + return -EINVAL; + + desc = dmaengine_prep_slave_single(dmaengine_buffer->chan, + block->phys_addr, + block->bytes_used, + dma_dir, + DMA_PREP_INTERRUPT); + } + if (!desc) + return -ENOMEM; + + if (!block->cyclic) { + desc->callback_result = iio_dmaengine_buffer_block_done; + desc->callback_param = block; + } + + cookie = dmaengine_submit(desc); + if (dma_submit_error(cookie)) + return dma_submit_error(cookie); + + scoped_guard(spinlock_irq, &dmaengine_buffer->queue.list_lock) + list_add_tail(&block->head, &dmaengine_buffer->active); + + dma_async_issue_pending(dmaengine_buffer->chan); + + return 0; +} + +static void iio_dmaengine_buffer_abort(struct iio_dma_buffer_queue *queue) +{ + struct dmaengine_buffer *dmaengine_buffer = + iio_buffer_to_dmaengine_buffer(&queue->buffer); + + dmaengine_terminate_sync(dmaengine_buffer->chan); + iio_dma_buffer_block_list_abort(queue, &dmaengine_buffer->active); +} + +static void iio_dmaengine_buffer_release(struct iio_buffer *buf) +{ + struct dmaengine_buffer *dmaengine_buffer = + iio_buffer_to_dmaengine_buffer(buf); + + iio_dma_buffer_release(&dmaengine_buffer->queue); + kfree(dmaengine_buffer); +} + +static const struct iio_buffer_access_funcs iio_dmaengine_buffer_ops = { + .read = iio_dma_buffer_read, + .write = iio_dma_buffer_write, + .set_bytes_per_datum = iio_dma_buffer_set_bytes_per_datum, + .set_length = iio_dma_buffer_set_length, + .request_update = iio_dma_buffer_request_update, + .enable = iio_dma_buffer_enable, + .disable = iio_dma_buffer_disable, + .data_available = iio_dma_buffer_usage, + .space_available = iio_dma_buffer_usage, + .release = iio_dmaengine_buffer_release, + + .enqueue_dmabuf = iio_dma_buffer_enqueue_dmabuf, + .attach_dmabuf = iio_dma_buffer_attach_dmabuf, + .detach_dmabuf = iio_dma_buffer_detach_dmabuf, + + .lock_queue = iio_dma_buffer_lock_queue, + .unlock_queue = iio_dma_buffer_unlock_queue, + + .get_dma_dev = iio_dma_buffer_get_dma_dev, + + .modes = INDIO_BUFFER_HARDWARE, + .flags = INDIO_BUFFER_FLAG_FIXED_WATERMARK, +}; + +static const struct iio_dma_buffer_ops iio_dmaengine_default_ops = { + .submit = iio_dmaengine_buffer_submit_block, + .abort = iio_dmaengine_buffer_abort, +}; + +static ssize_t iio_dmaengine_buffer_get_length_align(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_buffer *buffer = to_iio_dev_attr(attr)->buffer; + struct dmaengine_buffer *dmaengine_buffer = + iio_buffer_to_dmaengine_buffer(buffer); + + return sysfs_emit(buf, "%zu\n", dmaengine_buffer->align); +} + +static IIO_DEVICE_ATTR(length_align_bytes, 0444, + iio_dmaengine_buffer_get_length_align, NULL, 0); + +static const struct iio_dev_attr *iio_dmaengine_buffer_attrs[] = { + &iio_dev_attr_length_align_bytes, + NULL, +}; + +/** + * iio_dmaengine_buffer_alloc() - Allocate new buffer which uses DMAengine + * @chan: DMA channel. + * + * This allocates a new IIO buffer which internally uses the DMAengine framework + * to perform its transfers. + * + * Once done using the buffer iio_dmaengine_buffer_free() should be used to + * release it. + */ +static struct iio_buffer *iio_dmaengine_buffer_alloc(struct dma_chan *chan) +{ + struct dmaengine_buffer *dmaengine_buffer; + unsigned int width, src_width, dest_width; + struct dma_slave_caps caps; + int ret; + + ret = dma_get_slave_caps(chan, &caps); + if (ret < 0) + return ERR_PTR(ret); + + dmaengine_buffer = kzalloc_obj(*dmaengine_buffer); + if (!dmaengine_buffer) + return ERR_PTR(-ENOMEM); + + /* Needs to be aligned to the maximum of the minimums */ + if (caps.src_addr_widths) + src_width = __ffs(caps.src_addr_widths); + else + src_width = 1; + if (caps.dst_addr_widths) + dest_width = __ffs(caps.dst_addr_widths); + else + dest_width = 1; + width = max(src_width, dest_width); + + INIT_LIST_HEAD(&dmaengine_buffer->active); + dmaengine_buffer->chan = chan; + dmaengine_buffer->align = width; + dmaengine_buffer->max_size = dma_get_max_seg_size(chan->device->dev); + + iio_dma_buffer_init(&dmaengine_buffer->queue, chan->device->dev, + &iio_dmaengine_default_ops); + + dmaengine_buffer->queue.buffer.attrs = iio_dmaengine_buffer_attrs; + dmaengine_buffer->queue.buffer.access = &iio_dmaengine_buffer_ops; + + return &dmaengine_buffer->queue.buffer; +} + +/** + * iio_dmaengine_buffer_free() - Free dmaengine buffer + * @buffer: Buffer to free + * + * Frees a buffer previously allocated with iio_dmaengine_buffer_alloc(). + */ +static void iio_dmaengine_buffer_free(struct iio_buffer *buffer) +{ + struct dmaengine_buffer *dmaengine_buffer = + iio_buffer_to_dmaengine_buffer(buffer); + + iio_dma_buffer_exit(&dmaengine_buffer->queue); + iio_buffer_put(buffer); +} + +/** + * iio_dmaengine_buffer_teardown() - Releases DMA channel and frees buffer + * @buffer: Buffer to free + * + * Releases the DMA channel and frees the buffer previously setup with + * iio_dmaengine_buffer_setup_ext(). + */ +void iio_dmaengine_buffer_teardown(struct iio_buffer *buffer) +{ + struct dmaengine_buffer *dmaengine_buffer = + iio_buffer_to_dmaengine_buffer(buffer); + struct dma_chan *chan = dmaengine_buffer->chan; + + iio_dmaengine_buffer_free(buffer); + dma_release_channel(chan); +} +EXPORT_SYMBOL_NS_GPL(iio_dmaengine_buffer_teardown, "IIO_DMAENGINE_BUFFER"); + +static struct iio_buffer +*__iio_dmaengine_buffer_setup_ext(struct iio_dev *indio_dev, + struct dma_chan *chan, + enum iio_buffer_direction dir) +{ + struct iio_buffer *buffer; + int ret; + + buffer = iio_dmaengine_buffer_alloc(chan); + if (IS_ERR(buffer)) + return ERR_CAST(buffer); + + indio_dev->modes |= INDIO_BUFFER_HARDWARE; + + buffer->direction = dir; + + ret = iio_device_attach_buffer(indio_dev, buffer); + if (ret) { + iio_dmaengine_buffer_free(buffer); + return ERR_PTR(ret); + } + + return buffer; +} + +/** + * iio_dmaengine_buffer_setup_ext() - Setup a DMA buffer for an IIO device + * @dev: DMA channel consumer device + * @indio_dev: IIO device to which to attach this buffer. + * @channel: DMA channel name, typically "rx". + * @dir: Direction of buffer (in or out) + * + * This allocates a new IIO buffer with devm_iio_dmaengine_buffer_alloc() + * and attaches it to an IIO device with iio_device_attach_buffer(). + * It also appends the INDIO_BUFFER_HARDWARE mode to the supported modes of the + * IIO device. + * + * Once done using the buffer iio_dmaengine_buffer_teardown() should be used to + * release it. + */ +struct iio_buffer *iio_dmaengine_buffer_setup_ext(struct device *dev, + struct iio_dev *indio_dev, + const char *channel, + enum iio_buffer_direction dir) +{ + struct dma_chan *chan; + struct iio_buffer *buffer; + + chan = dma_request_chan(dev, channel); + if (IS_ERR(chan)) + return ERR_CAST(chan); + + buffer = __iio_dmaengine_buffer_setup_ext(indio_dev, chan, dir); + if (IS_ERR(buffer)) + dma_release_channel(chan); + + return buffer; +} +EXPORT_SYMBOL_NS_GPL(iio_dmaengine_buffer_setup_ext, "IIO_DMAENGINE_BUFFER"); + +static void devm_iio_dmaengine_buffer_teardown(void *buffer) +{ + iio_dmaengine_buffer_teardown(buffer); +} + +/** + * devm_iio_dmaengine_buffer_setup_ext() - Setup a DMA buffer for an IIO device + * @dev: Device for devm ownership and DMA channel consumer device + * @indio_dev: IIO device to which to attach this buffer. + * @channel: DMA channel name, typically "rx". + * @dir: Direction of buffer (in or out) + * + * This allocates a new IIO buffer with devm_iio_dmaengine_buffer_alloc() + * and attaches it to an IIO device with iio_device_attach_buffer(). + * It also appends the INDIO_BUFFER_HARDWARE mode to the supported modes of the + * IIO device. + */ +int devm_iio_dmaengine_buffer_setup_ext(struct device *dev, + struct iio_dev *indio_dev, + const char *channel, + enum iio_buffer_direction dir) +{ + struct iio_buffer *buffer; + + buffer = iio_dmaengine_buffer_setup_ext(dev, indio_dev, channel, dir); + if (IS_ERR(buffer)) + return PTR_ERR(buffer); + + return devm_add_action_or_reset(dev, devm_iio_dmaengine_buffer_teardown, + buffer); +} +EXPORT_SYMBOL_NS_GPL(devm_iio_dmaengine_buffer_setup_ext, "IIO_DMAENGINE_BUFFER"); + +static void devm_iio_dmaengine_buffer_free(void *buffer) +{ + iio_dmaengine_buffer_free(buffer); +} + +/** + * devm_iio_dmaengine_buffer_setup_with_handle() - Setup a DMA buffer for an + * IIO device + * @dev: Device for devm ownership + * @indio_dev: IIO device to which to attach this buffer. + * @chan: DMA channel + * @dir: Direction of buffer (in or out) + * + * This allocates a new IIO buffer with devm_iio_dmaengine_buffer_alloc() + * and attaches it to an IIO device with iio_device_attach_buffer(). + * It also appends the INDIO_BUFFER_HARDWARE mode to the supported modes of the + * IIO device. + * + * This is the same as devm_iio_dmaengine_buffer_setup_ext() except that the + * caller manages requesting and releasing the DMA channel handle. + */ +int devm_iio_dmaengine_buffer_setup_with_handle(struct device *dev, + struct iio_dev *indio_dev, + struct dma_chan *chan, + enum iio_buffer_direction dir) +{ + struct iio_buffer *buffer; + + buffer = __iio_dmaengine_buffer_setup_ext(indio_dev, chan, dir); + if (IS_ERR(buffer)) + return PTR_ERR(buffer); + + return devm_add_action_or_reset(dev, devm_iio_dmaengine_buffer_free, + buffer); +} +EXPORT_SYMBOL_NS_GPL(devm_iio_dmaengine_buffer_setup_with_handle, + "IIO_DMAENGINE_BUFFER"); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("DMA buffer for the IIO framework"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_DMA_BUFFER"); diff --git a/drivers/iio/buffer/industrialio-hw-consumer.c b/drivers/iio/buffer/industrialio-hw-consumer.c new file mode 100644 index 000000000..d035aa33b --- /dev/null +++ b/drivers/iio/buffer/industrialio-hw-consumer.c @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2017 Analog Devices Inc. + * Author: Lars-Peter Clausen + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +/** + * struct iio_hw_consumer - IIO hw consumer block + * @buffers: hardware buffers list head. + * @channels: IIO provider channels. + */ +struct iio_hw_consumer { + struct list_head buffers; + struct iio_channel *channels; +}; + +struct hw_consumer_buffer { + struct list_head head; + struct iio_dev *indio_dev; + struct iio_buffer buffer; +}; + +static struct hw_consumer_buffer *iio_buffer_to_hw_consumer_buffer( + struct iio_buffer *buffer) +{ + return container_of(buffer, struct hw_consumer_buffer, buffer); +} + +static void iio_hw_buf_release(struct iio_buffer *buffer) +{ + struct hw_consumer_buffer *hw_buf = + iio_buffer_to_hw_consumer_buffer(buffer); + + bitmap_free(buffer->scan_mask); + kfree(hw_buf); +} + +static const struct iio_buffer_access_funcs iio_hw_buf_access = { + .release = &iio_hw_buf_release, + .modes = INDIO_BUFFER_HARDWARE, +}; + +static struct hw_consumer_buffer *iio_hw_consumer_get_buffer( + struct iio_hw_consumer *hwc, struct iio_dev *indio_dev) +{ + struct hw_consumer_buffer *buf; + + list_for_each_entry(buf, &hwc->buffers, head) { + if (buf->indio_dev == indio_dev) + return buf; + } + + buf = kzalloc_obj(*buf); + if (!buf) + return NULL; + + buf->buffer.access = &iio_hw_buf_access; + buf->indio_dev = indio_dev; + buf->buffer.scan_mask = bitmap_zalloc(iio_get_masklength(indio_dev), + GFP_KERNEL); + if (!buf->buffer.scan_mask) { + kfree(buf); + return NULL; + } + + iio_buffer_init(&buf->buffer); + list_add_tail(&buf->head, &hwc->buffers); + + return buf; +} + +/** + * iio_hw_consumer_alloc() - Allocate IIO hardware consumer + * @dev: Pointer to consumer device. + * + * Returns a valid iio_hw_consumer on success or a ERR_PTR() on failure. + */ +struct iio_hw_consumer *iio_hw_consumer_alloc(struct device *dev) +{ + struct hw_consumer_buffer *buf, *tmp; + struct iio_hw_consumer *hwc; + struct iio_channel *chan; + int ret; + + hwc = kzalloc_obj(*hwc); + if (!hwc) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&hwc->buffers); + + hwc->channels = iio_channel_get_all(dev); + if (IS_ERR(hwc->channels)) { + ret = PTR_ERR(hwc->channels); + goto err_free_hwc; + } + + chan = &hwc->channels[0]; + while (chan->indio_dev) { + buf = iio_hw_consumer_get_buffer(hwc, chan->indio_dev); + if (!buf) { + ret = -ENOMEM; + goto err_put_buffers; + } + set_bit(chan->channel->scan_index, buf->buffer.scan_mask); + chan++; + } + + return hwc; + +err_put_buffers: + list_for_each_entry_safe(buf, tmp, &hwc->buffers, head) + iio_buffer_put(&buf->buffer); + iio_channel_release_all(hwc->channels); +err_free_hwc: + kfree(hwc); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(iio_hw_consumer_alloc); + +/** + * iio_hw_consumer_free() - Free IIO hardware consumer + * @hwc: hw consumer to free. + */ +void iio_hw_consumer_free(struct iio_hw_consumer *hwc) +{ + struct hw_consumer_buffer *buf, *n; + + iio_channel_release_all(hwc->channels); + list_for_each_entry_safe(buf, n, &hwc->buffers, head) + iio_buffer_put(&buf->buffer); + kfree(hwc); +} +EXPORT_SYMBOL_GPL(iio_hw_consumer_free); + +static void devm_iio_hw_consumer_release(void *iio_hwc) +{ + iio_hw_consumer_free(iio_hwc); +} + +/** + * devm_iio_hw_consumer_alloc - Resource-managed iio_hw_consumer_alloc() + * @dev: Pointer to consumer device. + * + * Managed iio_hw_consumer_alloc. iio_hw_consumer allocated with this function + * is automatically freed on driver detach. + * + * returns pointer to allocated iio_hw_consumer on success, NULL on failure. + */ +struct iio_hw_consumer *devm_iio_hw_consumer_alloc(struct device *dev) +{ + struct iio_hw_consumer *iio_hwc; + int ret; + + iio_hwc = iio_hw_consumer_alloc(dev); + if (IS_ERR(iio_hwc)) + return iio_hwc; + + ret = devm_add_action_or_reset(dev, devm_iio_hw_consumer_release, + iio_hwc); + if (ret) + return ERR_PTR(ret); + + return iio_hwc; +} +EXPORT_SYMBOL_GPL(devm_iio_hw_consumer_alloc); + +/** + * iio_hw_consumer_enable() - Enable IIO hardware consumer + * @hwc: iio_hw_consumer to enable. + * + * Returns 0 on success. + */ +int iio_hw_consumer_enable(struct iio_hw_consumer *hwc) +{ + struct hw_consumer_buffer *buf; + int ret; + + list_for_each_entry(buf, &hwc->buffers, head) { + ret = iio_update_buffers(buf->indio_dev, &buf->buffer, NULL); + if (ret) + goto err_disable_buffers; + } + + return 0; + +err_disable_buffers: + list_for_each_entry_continue_reverse(buf, &hwc->buffers, head) + iio_update_buffers(buf->indio_dev, NULL, &buf->buffer); + return ret; +} +EXPORT_SYMBOL_GPL(iio_hw_consumer_enable); + +/** + * iio_hw_consumer_disable() - Disable IIO hardware consumer + * @hwc: iio_hw_consumer to disable. + */ +void iio_hw_consumer_disable(struct iio_hw_consumer *hwc) +{ + struct hw_consumer_buffer *buf; + + list_for_each_entry(buf, &hwc->buffers, head) + iio_update_buffers(buf->indio_dev, NULL, &buf->buffer); +} +EXPORT_SYMBOL_GPL(iio_hw_consumer_disable); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Hardware consumer buffer the IIO framework"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_CONSUMER"); diff --git a/drivers/iio/buffer/industrialio-triggered-buffer.c b/drivers/iio/buffer/industrialio-triggered-buffer.c new file mode 100644 index 000000000..9bf75dee7 --- /dev/null +++ b/drivers/iio/buffer/industrialio-triggered-buffer.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2012 Analog Devices, Inc. + * Author: Lars-Peter Clausen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * iio_triggered_buffer_setup_ext() - Setup triggered buffer and pollfunc + * @indio_dev: IIO device structure + * @h: Function which will be used as pollfunc top half + * @thread: Function which will be used as pollfunc bottom half + * @direction: Direction of the data stream (in/out). + * @setup_ops: Buffer setup functions to use for this device. + * If NULL the default setup functions for triggered + * buffers will be used. + * @buffer_attrs: Extra sysfs buffer attributes for this IIO buffer + * + * This function combines some common tasks which will normally be performed + * when setting up a triggered buffer. It will allocate the buffer and the + * pollfunc. + * + * Before calling this function the indio_dev structure should already be + * completely initialized, but not yet registered. In practice this means that + * this function should be called right before iio_device_register(). + * + * To free the resources allocated by this function call + * iio_triggered_buffer_cleanup(). + */ +int iio_triggered_buffer_setup_ext(struct iio_dev *indio_dev, + irqreturn_t (*h)(int irq, void *p), + irqreturn_t (*thread)(int irq, void *p), + enum iio_buffer_direction direction, + const struct iio_buffer_setup_ops *setup_ops, + const struct iio_dev_attr **buffer_attrs) +{ + struct iio_buffer *buffer; + int ret; + + /* + * iio_triggered_buffer_cleanup() assumes that the buffer allocated here + * is assigned to indio_dev->buffer but this is only the case if this + * function is the first caller to iio_device_attach_buffer(). If + * indio_dev->buffer is already set then we can't proceed otherwise the + * cleanup function will try to free a buffer that was not allocated here. + */ + if (indio_dev->buffer) + return -EADDRINUSE; + + buffer = iio_kfifo_allocate(); + if (!buffer) { + ret = -ENOMEM; + goto error_ret; + } + + indio_dev->pollfunc = iio_alloc_pollfunc(h, + thread, + IRQF_ONESHOT, + indio_dev, + "%s_consumer%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (indio_dev->pollfunc == NULL) { + ret = -ENOMEM; + goto error_kfifo_free; + } + + /* Ring buffer functions - here trigger setup related */ + indio_dev->setup_ops = setup_ops; + + /* Flag that polled ring buffering is possible */ + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + + buffer->direction = direction; + buffer->attrs = buffer_attrs; + + ret = iio_device_attach_buffer(indio_dev, buffer); + if (ret < 0) + goto error_dealloc_pollfunc; + + return 0; + +error_dealloc_pollfunc: + iio_dealloc_pollfunc(indio_dev->pollfunc); +error_kfifo_free: + iio_kfifo_free(buffer); +error_ret: + return ret; +} +EXPORT_SYMBOL(iio_triggered_buffer_setup_ext); + +/** + * iio_triggered_buffer_cleanup() - Free resources allocated by iio_triggered_buffer_setup_ext() + * @indio_dev: IIO device structure + */ +void iio_triggered_buffer_cleanup(struct iio_dev *indio_dev) +{ + iio_dealloc_pollfunc(indio_dev->pollfunc); + iio_kfifo_free(indio_dev->buffer); +} +EXPORT_SYMBOL(iio_triggered_buffer_cleanup); + +static void devm_iio_triggered_buffer_clean(void *indio_dev) +{ + iio_triggered_buffer_cleanup(indio_dev); +} + +int devm_iio_triggered_buffer_setup_ext(struct device *dev, + struct iio_dev *indio_dev, + irqreturn_t (*h)(int irq, void *p), + irqreturn_t (*thread)(int irq, void *p), + enum iio_buffer_direction direction, + const struct iio_buffer_setup_ops *ops, + const struct iio_dev_attr **buffer_attrs) +{ + int ret; + + ret = iio_triggered_buffer_setup_ext(indio_dev, h, thread, direction, + ops, buffer_attrs); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, devm_iio_triggered_buffer_clean, + indio_dev); +} +EXPORT_SYMBOL_GPL(devm_iio_triggered_buffer_setup_ext); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("IIO helper functions for setting up triggered buffers"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/buffer/kfifo_buf.c b/drivers/iio/buffer/kfifo_buf.c new file mode 100644 index 000000000..b67ea6468 --- /dev/null +++ b/drivers/iio/buffer/kfifo_buf.c @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct iio_kfifo { + struct iio_buffer buffer; + struct kfifo kf; + struct mutex user_lock; + int update_needed; +}; + +#define iio_to_kfifo(r) container_of(r, struct iio_kfifo, buffer) + +static inline int __iio_allocate_kfifo(struct iio_kfifo *buf, + size_t bytes_per_datum, unsigned int length) +{ + if ((length == 0) || (bytes_per_datum == 0)) + return -EINVAL; + + /* + * Make sure we don't overflow an unsigned int after kfifo rounds up to + * the next power of 2. + */ + if (roundup_pow_of_two(length) > UINT_MAX / bytes_per_datum) + return -EINVAL; + + return __kfifo_alloc((struct __kfifo *)&buf->kf, length, + bytes_per_datum, GFP_KERNEL); +} + +static int iio_request_update_kfifo(struct iio_buffer *r) +{ + int ret = 0; + struct iio_kfifo *buf = iio_to_kfifo(r); + + mutex_lock(&buf->user_lock); + if (buf->update_needed) { + kfifo_free(&buf->kf); + ret = __iio_allocate_kfifo(buf, buf->buffer.bytes_per_datum, + buf->buffer.length); + if (ret >= 0) + buf->update_needed = false; + } else { + kfifo_reset_out(&buf->kf); + } + mutex_unlock(&buf->user_lock); + + return ret; +} + +static int iio_mark_update_needed_kfifo(struct iio_buffer *r) +{ + struct iio_kfifo *kf = iio_to_kfifo(r); + kf->update_needed = true; + return 0; +} + +static int iio_set_bytes_per_datum_kfifo(struct iio_buffer *r, size_t bpd) +{ + if (r->bytes_per_datum != bpd) { + r->bytes_per_datum = bpd; + iio_mark_update_needed_kfifo(r); + } + return 0; +} + +static int iio_set_length_kfifo(struct iio_buffer *r, unsigned int length) +{ + /* Avoid an invalid state */ + if (length < 2) + length = 2; + if (r->length != length) { + r->length = length; + iio_mark_update_needed_kfifo(r); + } + return 0; +} + +static int iio_store_to_kfifo(struct iio_buffer *r, + const void *data) +{ + int ret; + struct iio_kfifo *kf = iio_to_kfifo(r); + ret = kfifo_in(&kf->kf, data, 1); + if (ret != 1) + return -EBUSY; + return 0; +} + +static int iio_read_kfifo(struct iio_buffer *r, size_t n, char __user *buf) +{ + int ret, copied; + struct iio_kfifo *kf = iio_to_kfifo(r); + + if (mutex_lock_interruptible(&kf->user_lock)) + return -ERESTARTSYS; + + if (!kfifo_initialized(&kf->kf) || n < kfifo_esize(&kf->kf)) + ret = -EINVAL; + else + ret = kfifo_to_user(&kf->kf, buf, n, &copied); + mutex_unlock(&kf->user_lock); + if (ret < 0) + return ret; + + return copied; +} + +static size_t iio_kfifo_buf_data_available(struct iio_buffer *r) +{ + struct iio_kfifo *kf = iio_to_kfifo(r); + size_t samples; + + mutex_lock(&kf->user_lock); + samples = kfifo_len(&kf->kf); + mutex_unlock(&kf->user_lock); + + return samples; +} + +static void iio_kfifo_buffer_release(struct iio_buffer *buffer) +{ + struct iio_kfifo *kf = iio_to_kfifo(buffer); + + mutex_destroy(&kf->user_lock); + kfifo_free(&kf->kf); + kfree(kf); +} + +static size_t iio_kfifo_buf_space_available(struct iio_buffer *r) +{ + struct iio_kfifo *kf = iio_to_kfifo(r); + size_t avail; + + mutex_lock(&kf->user_lock); + avail = kfifo_avail(&kf->kf); + mutex_unlock(&kf->user_lock); + + return avail; +} + +static int iio_kfifo_remove_from(struct iio_buffer *r, void *data) +{ + int ret; + struct iio_kfifo *kf = iio_to_kfifo(r); + + if (kfifo_size(&kf->kf) < 1) + return -EBUSY; + + ret = kfifo_out(&kf->kf, data, 1); + if (ret != 1) + return -EBUSY; + + wake_up_interruptible_poll(&r->pollq, EPOLLOUT | EPOLLWRNORM); + + return 0; +} + +static int iio_kfifo_write(struct iio_buffer *r, size_t n, + const char __user *buf) +{ + struct iio_kfifo *kf = iio_to_kfifo(r); + int ret, copied; + + mutex_lock(&kf->user_lock); + if (!kfifo_initialized(&kf->kf) || n < kfifo_esize(&kf->kf)) + ret = -EINVAL; + else + ret = kfifo_from_user(&kf->kf, buf, n, &copied); + mutex_unlock(&kf->user_lock); + if (ret) + return ret; + + return copied; +} + +static const struct iio_buffer_access_funcs kfifo_access_funcs = { + .store_to = &iio_store_to_kfifo, + .read = &iio_read_kfifo, + .data_available = iio_kfifo_buf_data_available, + .remove_from = &iio_kfifo_remove_from, + .write = &iio_kfifo_write, + .space_available = &iio_kfifo_buf_space_available, + .request_update = &iio_request_update_kfifo, + .set_bytes_per_datum = &iio_set_bytes_per_datum_kfifo, + .set_length = &iio_set_length_kfifo, + .release = &iio_kfifo_buffer_release, + + .modes = INDIO_BUFFER_SOFTWARE | INDIO_BUFFER_TRIGGERED, +}; + +struct iio_buffer *iio_kfifo_allocate(void) +{ + struct iio_kfifo *kf; + + kf = kzalloc_obj(*kf); + if (!kf) + return NULL; + + kf->update_needed = true; + iio_buffer_init(&kf->buffer); + kf->buffer.access = &kfifo_access_funcs; + kf->buffer.length = 2; + mutex_init(&kf->user_lock); + + return &kf->buffer; +} +EXPORT_SYMBOL(iio_kfifo_allocate); + +void iio_kfifo_free(struct iio_buffer *r) +{ + iio_buffer_put(r); +} +EXPORT_SYMBOL(iio_kfifo_free); + +static void devm_iio_kfifo_release(void *buffer) +{ + iio_kfifo_free(buffer); +} + +/** + * devm_iio_kfifo_buffer_setup_ext - Allocate a kfifo buffer & attach it to an IIO device + * @dev: Device object to which to attach the life-time of this kfifo buffer + * @indio_dev: The device the buffer should be attached to + * @setup_ops: The setup_ops required to configure the HW part of the buffer (optional) + * @buffer_attrs: Extra sysfs buffer attributes for this IIO buffer + * + * This function allocates a kfifo buffer via iio_kfifo_allocate() and + * attaches it to the IIO device via iio_device_attach_buffer(). + * This is meant to be a bit of a short-hand/helper function as there are a few + * drivers that seem to do this. + * + * Return: 0 on success, negative error code on failure. + */ +int devm_iio_kfifo_buffer_setup_ext(struct device *dev, + struct iio_dev *indio_dev, + const struct iio_buffer_setup_ops *setup_ops, + const struct iio_dev_attr **buffer_attrs) +{ + struct iio_buffer *buffer; + int ret; + + buffer = iio_kfifo_allocate(); + if (!buffer) + return -ENOMEM; + + ret = devm_add_action_or_reset(dev, devm_iio_kfifo_release, buffer); + if (ret) + return ret; + + indio_dev->modes |= INDIO_BUFFER_SOFTWARE; + indio_dev->setup_ops = setup_ops; + + buffer->attrs = buffer_attrs; + + return iio_device_attach_buffer(indio_dev, buffer); +} +EXPORT_SYMBOL_GPL(devm_iio_kfifo_buffer_setup_ext); + +MODULE_DESCRIPTION("Industrial I/O buffering based on kfifo"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/cdc/Kconfig b/drivers/iio/cdc/Kconfig new file mode 100644 index 000000000..e0a5ce66a --- /dev/null +++ b/drivers/iio/cdc/Kconfig @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# CDC drivers +# +menu "Capacitance to digital converters" + +config AD7150 + tristate "Analog Devices ad7150/1/6 capacitive sensor driver" + depends on I2C + help + Say yes here to build support for Analog Devices capacitive sensors. + (ad7150, ad7151, ad7156) Provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called ad7150. + +config AD7746 + tristate "Analog Devices AD7745, AD7746 AD7747 capacitive sensor driver" + depends on I2C + help + Say yes here to build support for Analog Devices capacitive sensors. + (AD7745, AD7746, AD7747) Provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called ad7746. + +endmenu diff --git a/drivers/iio/cdc/Makefile b/drivers/iio/cdc/Makefile new file mode 100644 index 000000000..41db756d8 --- /dev/null +++ b/drivers/iio/cdc/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for industrial I/O capacitance to digital converter (CDC) drivers +# + +obj-$(CONFIG_AD7150) += ad7150.o +obj-$(CONFIG_AD7746) += ad7746.o diff --git a/drivers/iio/cdc/ad7150.c b/drivers/iio/cdc/ad7150.c new file mode 100644 index 000000000..2f35c6d2f --- /dev/null +++ b/drivers/iio/cdc/ad7150.c @@ -0,0 +1,656 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * AD7150 capacitive sensor driver supporting AD7150/1/6 + * + * Copyright 2010-2011 Analog Devices Inc. + * Copyright 2021 Jonathan Cameron + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define AD7150_STATUS_REG 0 +#define AD7150_STATUS_OUT1 BIT(3) +#define AD7150_STATUS_OUT2 BIT(5) +#define AD7150_CH1_DATA_HIGH_REG 1 +#define AD7150_CH2_DATA_HIGH_REG 3 +#define AD7150_CH1_AVG_HIGH_REG 5 +#define AD7150_CH2_AVG_HIGH_REG 7 +#define AD7150_CH1_SENSITIVITY_REG 9 +#define AD7150_CH1_THR_HOLD_H_REG 9 +#define AD7150_CH1_TIMEOUT_REG 10 +#define AD7150_CH_TIMEOUT_RECEDING GENMASK(3, 0) +#define AD7150_CH_TIMEOUT_APPROACHING GENMASK(7, 4) +#define AD7150_CH1_SETUP_REG 11 +#define AD7150_CH2_SENSITIVITY_REG 12 +#define AD7150_CH2_THR_HOLD_H_REG 12 +#define AD7150_CH2_TIMEOUT_REG 13 +#define AD7150_CH2_SETUP_REG 14 +#define AD7150_CFG_REG 15 +#define AD7150_CFG_FIX BIT(7) +#define AD7150_CFG_THRESHTYPE_MSK GENMASK(6, 5) +#define AD7150_CFG_TT_NEG 0x0 +#define AD7150_CFG_TT_POS 0x1 +#define AD7150_CFG_TT_IN_WINDOW 0x2 +#define AD7150_CFG_TT_OUT_WINDOW 0x3 +#define AD7150_PD_TIMER_REG 16 +#define AD7150_CH1_CAPDAC_REG 17 +#define AD7150_CH2_CAPDAC_REG 18 +#define AD7150_SN3_REG 19 +#define AD7150_SN2_REG 20 +#define AD7150_SN1_REG 21 +#define AD7150_SN0_REG 22 +#define AD7150_ID_REG 23 + +enum { + AD7150, + AD7151, +}; + +/** + * struct ad7150_chip_info - instance specific chip data + * @client: i2c client for this device + * @threshold: thresholds for simple capacitance value events + * @thresh_sensitivity: threshold for simple capacitance offset + * from 'average' value. + * @thresh_timeout: a timeout, in samples from the moment an + * adaptive threshold event occurs to when the average + * value jumps to current value. Note made up of two fields, + * 3:0 are for timeout receding - applies if below lower threshold + * 7:4 are for timeout approaching - applies if above upper threshold + * @state_lock: ensure consistent state of this structure wrt the + * hardware. + * @interrupts: one or two interrupt numbers depending on device type. + * @int_enabled: is a given interrupt currently enabled. + * @type: threshold type + * @dir: threshold direction + */ +struct ad7150_chip_info { + struct i2c_client *client; + u16 threshold[2][2]; + u8 thresh_sensitivity[2][2]; + u8 thresh_timeout[2][2]; + struct mutex state_lock; + int interrupts[2]; + bool int_enabled[2]; + enum iio_event_type type; + enum iio_event_direction dir; +}; + +static const u8 ad7150_addresses[][6] = { + { AD7150_CH1_DATA_HIGH_REG, AD7150_CH1_AVG_HIGH_REG, + AD7150_CH1_SETUP_REG, AD7150_CH1_THR_HOLD_H_REG, + AD7150_CH1_SENSITIVITY_REG, AD7150_CH1_TIMEOUT_REG }, + { AD7150_CH2_DATA_HIGH_REG, AD7150_CH2_AVG_HIGH_REG, + AD7150_CH2_SETUP_REG, AD7150_CH2_THR_HOLD_H_REG, + AD7150_CH2_SENSITIVITY_REG, AD7150_CH2_TIMEOUT_REG }, +}; + +static int ad7150_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct ad7150_chip_info *chip = iio_priv(indio_dev); + int channel = chan->channel; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = i2c_smbus_read_word_swapped(chip->client, + ad7150_addresses[channel][0]); + if (ret < 0) + return ret; + *val = ret >> 4; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_AVERAGE_RAW: + ret = i2c_smbus_read_word_swapped(chip->client, + ad7150_addresses[channel][1]); + if (ret < 0) + return ret; + *val = ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* + * Base units for capacitance are nano farads and the value + * calculated from the datasheet formula is in picofarad + * so multiply by 1000 + */ + *val = 1000; + *val2 = 40944 >> 4; /* To match shift in _RAW */ + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_OFFSET: + *val = -(12288 >> 4); /* To match shift in _RAW */ + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + /* Strangely same for both 1 and 2 chan parts */ + *val = 100; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad7150_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct ad7150_chip_info *chip = iio_priv(indio_dev); + u8 threshtype; + bool thrfixed; + int ret; + + ret = i2c_smbus_read_byte_data(chip->client, AD7150_CFG_REG); + if (ret < 0) + return ret; + + threshtype = FIELD_GET(AD7150_CFG_THRESHTYPE_MSK, ret); + + /*check if threshold mode is fixed or adaptive*/ + thrfixed = FIELD_GET(AD7150_CFG_FIX, ret); + + switch (type) { + case IIO_EV_TYPE_THRESH_ADAPTIVE: + if (dir == IIO_EV_DIR_RISING) + return !thrfixed && (threshtype == AD7150_CFG_TT_POS); + return !thrfixed && (threshtype == AD7150_CFG_TT_NEG); + case IIO_EV_TYPE_THRESH: + if (dir == IIO_EV_DIR_RISING) + return thrfixed && (threshtype == AD7150_CFG_TT_POS); + return thrfixed && (threshtype == AD7150_CFG_TT_NEG); + default: + break; + } + return -EINVAL; +} + +/* state_lock should be held to ensure consistent state */ +static int ad7150_write_event_params(struct iio_dev *indio_dev, + unsigned int chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct ad7150_chip_info *chip = iio_priv(indio_dev); + int rising = (dir == IIO_EV_DIR_RISING); + + /* Only update value live, if parameter is in use */ + if ((type != chip->type) || (dir != chip->dir)) + return 0; + + switch (type) { + /* Note completely different from the adaptive versions */ + case IIO_EV_TYPE_THRESH: { + u16 value = chip->threshold[rising][chan]; + return i2c_smbus_write_word_swapped(chip->client, + ad7150_addresses[chan][3], + value); + } + case IIO_EV_TYPE_THRESH_ADAPTIVE: { + int ret; + u8 sens, timeout; + + sens = chip->thresh_sensitivity[rising][chan]; + ret = i2c_smbus_write_byte_data(chip->client, + ad7150_addresses[chan][4], + sens); + if (ret) + return ret; + + /* + * Single timeout register contains timeouts for both + * directions. + */ + timeout = FIELD_PREP(AD7150_CH_TIMEOUT_APPROACHING, + chip->thresh_timeout[1][chan]); + timeout |= FIELD_PREP(AD7150_CH_TIMEOUT_RECEDING, + chip->thresh_timeout[0][chan]); + return i2c_smbus_write_byte_data(chip->client, + ad7150_addresses[chan][5], + timeout); + } + default: + return -EINVAL; + } +} + +static int ad7150_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct ad7150_chip_info *chip = iio_priv(indio_dev); + int ret = 0; + + /* + * There is only a single shared control and no on chip + * interrupt disables for the two interrupt lines. + * So, enabling will switch the events configured to enable + * whatever was most recently requested and if necessary enable_irq() + * the interrupt and any disable will disable_irq() for that + * channels interrupt. + */ + if (!state) { + if ((chip->int_enabled[chan->channel]) && + (type == chip->type) && (dir == chip->dir)) { + disable_irq(chip->interrupts[chan->channel]); + chip->int_enabled[chan->channel] = false; + } + return 0; + } + + mutex_lock(&chip->state_lock); + if ((type != chip->type) || (dir != chip->dir)) { + int rising = (dir == IIO_EV_DIR_RISING); + u8 thresh_type, cfg, fixed; + + /* + * Need to temporarily disable both interrupts if + * enabled - this is to avoid races around changing + * config and thresholds. + * Note enable/disable_irq() are reference counted so + * no need to check if already enabled. + */ + disable_irq(chip->interrupts[0]); + disable_irq(chip->interrupts[1]); + + ret = i2c_smbus_read_byte_data(chip->client, AD7150_CFG_REG); + if (ret < 0) + goto error_ret; + + cfg = ret & ~(AD7150_CFG_THRESHTYPE_MSK | AD7150_CFG_FIX); + + if (type == IIO_EV_TYPE_THRESH_ADAPTIVE) + fixed = 0; + else + fixed = 1; + + if (rising) + thresh_type = AD7150_CFG_TT_POS; + else + thresh_type = AD7150_CFG_TT_NEG; + + cfg |= FIELD_PREP(AD7150_CFG_FIX, fixed) | + FIELD_PREP(AD7150_CFG_THRESHTYPE_MSK, thresh_type); + + ret = i2c_smbus_write_byte_data(chip->client, AD7150_CFG_REG, + cfg); + if (ret < 0) + goto error_ret; + + /* + * There is a potential race condition here, but not easy + * to close given we can't disable the interrupt at the + * chip side of things. Rely on the status bit. + */ + chip->type = type; + chip->dir = dir; + + /* update control attributes */ + ret = ad7150_write_event_params(indio_dev, chan->channel, type, + dir); + if (ret) + goto error_ret; + /* re-enable any IRQs we disabled whilst changing mode */ + enable_irq(chip->interrupts[0]); + enable_irq(chip->interrupts[1]); + } + if (!chip->int_enabled[chan->channel]) { + enable_irq(chip->interrupts[chan->channel]); + chip->int_enabled[chan->channel] = true; + } + +error_ret: + mutex_unlock(&chip->state_lock); + + return ret; +} + +static int ad7150_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct ad7150_chip_info *chip = iio_priv(indio_dev); + int rising = (dir == IIO_EV_DIR_RISING); + + /* Complex register sharing going on here */ + switch (info) { + case IIO_EV_INFO_VALUE: + switch (type) { + case IIO_EV_TYPE_THRESH_ADAPTIVE: + *val = chip->thresh_sensitivity[rising][chan->channel]; + return IIO_VAL_INT; + case IIO_EV_TYPE_THRESH: + *val = chip->threshold[rising][chan->channel]; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_EV_INFO_TIMEOUT: + *val = 0; + *val2 = chip->thresh_timeout[rising][chan->channel] * 10000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int ad7150_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + int ret; + struct ad7150_chip_info *chip = iio_priv(indio_dev); + int rising = (dir == IIO_EV_DIR_RISING); + + mutex_lock(&chip->state_lock); + switch (info) { + case IIO_EV_INFO_VALUE: + switch (type) { + case IIO_EV_TYPE_THRESH_ADAPTIVE: + chip->thresh_sensitivity[rising][chan->channel] = val; + break; + case IIO_EV_TYPE_THRESH: + chip->threshold[rising][chan->channel] = val; + break; + default: + ret = -EINVAL; + goto error_ret; + } + break; + case IIO_EV_INFO_TIMEOUT: { + /* + * Raw timeout is in cycles of 10 msecs as long as both + * channels are enabled. + * In terms of INT_PLUS_MICRO, that is in units of 10,000 + */ + int timeout = val2 / 10000; + + if (val != 0 || timeout < 0 || timeout > 15 || val2 % 10000) { + ret = -EINVAL; + goto error_ret; + } + + chip->thresh_timeout[rising][chan->channel] = timeout; + break; + } + default: + ret = -EINVAL; + goto error_ret; + } + + /* write back if active */ + ret = ad7150_write_event_params(indio_dev, chan->channel, type, dir); + +error_ret: + mutex_unlock(&chip->state_lock); + return ret; +} + +static const struct iio_event_spec ad7150_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH_ADAPTIVE, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_TIMEOUT), + }, { + .type = IIO_EV_TYPE_THRESH_ADAPTIVE, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_TIMEOUT), + }, +}; + +#define AD7150_CAPACITANCE_CHAN(_chan) { \ + .type = IIO_CAPACITANCE, \ + .indexed = 1, \ + .channel = _chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_AVERAGE_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\ + .event_spec = ad7150_events, \ + .num_event_specs = ARRAY_SIZE(ad7150_events), \ + } + +#define AD7150_CAPACITANCE_CHAN_NO_IRQ(_chan) { \ + .type = IIO_CAPACITANCE, \ + .indexed = 1, \ + .channel = _chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_AVERAGE_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\ + } + +static const struct iio_chan_spec ad7150_channels[] = { + AD7150_CAPACITANCE_CHAN(0), + AD7150_CAPACITANCE_CHAN(1), +}; + +static const struct iio_chan_spec ad7150_channels_no_irq[] = { + AD7150_CAPACITANCE_CHAN_NO_IRQ(0), + AD7150_CAPACITANCE_CHAN_NO_IRQ(1), +}; + +static const struct iio_chan_spec ad7151_channels[] = { + AD7150_CAPACITANCE_CHAN(0), +}; + +static const struct iio_chan_spec ad7151_channels_no_irq[] = { + AD7150_CAPACITANCE_CHAN_NO_IRQ(0), +}; + +static irqreturn_t __ad7150_event_handler(void *private, u8 status_mask, + int channel) +{ + struct iio_dev *indio_dev = private; + struct ad7150_chip_info *chip = iio_priv(indio_dev); + s64 timestamp = iio_get_time_ns(indio_dev); + int int_status; + + int_status = i2c_smbus_read_byte_data(chip->client, AD7150_STATUS_REG); + if (int_status < 0) + return IRQ_HANDLED; + + if (!(int_status & status_mask)) + return IRQ_HANDLED; + + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_CAPACITANCE, channel, + chip->type, chip->dir), + timestamp); + + return IRQ_HANDLED; +} + +static irqreturn_t ad7150_event_handler_ch1(int irq, void *private) +{ + return __ad7150_event_handler(private, AD7150_STATUS_OUT1, 0); +} + +static irqreturn_t ad7150_event_handler_ch2(int irq, void *private) +{ + return __ad7150_event_handler(private, AD7150_STATUS_OUT2, 1); +} + +static IIO_CONST_ATTR(in_capacitance_thresh_adaptive_timeout_available, + "[0 0.01 0.15]"); + +static struct attribute *ad7150_event_attributes[] = { + &iio_const_attr_in_capacitance_thresh_adaptive_timeout_available + .dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad7150_event_attribute_group = { + .attrs = ad7150_event_attributes, + .name = "events", +}; + +static const struct iio_info ad7150_info = { + .event_attrs = &ad7150_event_attribute_group, + .read_raw = &ad7150_read_raw, + .read_event_config = &ad7150_read_event_config, + .write_event_config = &ad7150_write_event_config, + .read_event_value = &ad7150_read_event_value, + .write_event_value = &ad7150_write_event_value, +}; + +static const struct iio_info ad7150_info_no_irq = { + .read_raw = &ad7150_read_raw, +}; + +static int ad7150_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct ad7150_chip_info *chip; + struct iio_dev *indio_dev; + bool use_irq = true; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + mutex_init(&chip->state_lock); + chip->client = client; + + indio_dev->name = id->name; + + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_regulator_get_enable(&client->dev, "vdd"); + if (ret) + return ret; + + chip->interrupts[0] = fwnode_irq_get(dev_fwnode(&client->dev), 0); + if (chip->interrupts[0] < 0) + use_irq = false; + else if (id->driver_data == AD7150) { + chip->interrupts[1] = fwnode_irq_get(dev_fwnode(&client->dev), 1); + if (chip->interrupts[1] < 0) + use_irq = false; + } + if (use_irq) { + irq_set_status_flags(chip->interrupts[0], IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(&client->dev, + chip->interrupts[0], + NULL, + &ad7150_event_handler_ch1, + IRQF_TRIGGER_RISING | + IRQF_ONESHOT, + "ad7150_irq1", + indio_dev); + if (ret) + return ret; + + indio_dev->info = &ad7150_info; + switch (id->driver_data) { + case AD7150: + indio_dev->channels = ad7150_channels; + indio_dev->num_channels = ARRAY_SIZE(ad7150_channels); + irq_set_status_flags(chip->interrupts[1], IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(&client->dev, + chip->interrupts[1], + NULL, + &ad7150_event_handler_ch2, + IRQF_TRIGGER_RISING | + IRQF_ONESHOT, + "ad7150_irq2", + indio_dev); + if (ret) + return ret; + break; + case AD7151: + indio_dev->channels = ad7151_channels; + indio_dev->num_channels = ARRAY_SIZE(ad7151_channels); + break; + default: + return -EINVAL; + } + + } else { + indio_dev->info = &ad7150_info_no_irq; + switch (id->driver_data) { + case AD7150: + indio_dev->channels = ad7150_channels_no_irq; + indio_dev->num_channels = + ARRAY_SIZE(ad7150_channels_no_irq); + break; + case AD7151: + indio_dev->channels = ad7151_channels_no_irq; + indio_dev->num_channels = + ARRAY_SIZE(ad7151_channels_no_irq); + break; + default: + return -EINVAL; + } + } + + return devm_iio_device_register(indio_dev->dev.parent, indio_dev); +} + +static const struct i2c_device_id ad7150_id[] = { + { .name = "ad7150", .driver_data = AD7150 }, + { .name = "ad7151", .driver_data = AD7151 }, + { .name = "ad7156", .driver_data = AD7150 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, ad7150_id); + +static const struct of_device_id ad7150_of_match[] = { + { "adi,ad7150" }, + { "adi,ad7151" }, + { "adi,ad7156" }, + { } +}; +static struct i2c_driver ad7150_driver = { + .driver = { + .name = "ad7150", + .of_match_table = ad7150_of_match, + }, + .probe = ad7150_probe, + .id_table = ad7150_id, +}; +module_i2c_driver(ad7150_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices AD7150/1/6 capacitive sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/cdc/ad7746.c b/drivers/iio/cdc/ad7746.c new file mode 100644 index 000000000..cf68b882b --- /dev/null +++ b/drivers/iio/cdc/ad7746.c @@ -0,0 +1,819 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AD7746 capacitive sensor driver supporting AD7745, AD7746 and AD7747 + * + * Copyright 2011 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +/* AD7746 Register Definition */ + +#define AD7746_REG_STATUS 0 +#define AD7746_REG_CAP_DATA_HIGH 1 +#define AD7746_REG_VT_DATA_HIGH 4 +#define AD7746_REG_CAP_SETUP 7 +#define AD7746_REG_VT_SETUP 8 +#define AD7746_REG_EXC_SETUP 9 +#define AD7746_REG_CFG 10 +#define AD7746_REG_CAPDACA 11 +#define AD7746_REG_CAPDACB 12 +#define AD7746_REG_CAP_OFFH 13 +#define AD7746_REG_CAP_GAINH 15 +#define AD7746_REG_VOLT_GAINH 17 + +/* Status Register Bit Designations (AD7746_REG_STATUS) */ +#define AD7746_STATUS_EXCERR BIT(3) +#define AD7746_STATUS_RDY BIT(2) +#define AD7746_STATUS_RDYVT BIT(1) +#define AD7746_STATUS_RDYCAP BIT(0) + +/* Capacitive Channel Setup Register Bit Designations (AD7746_REG_CAP_SETUP) */ +#define AD7746_CAPSETUP_CAPEN BIT(7) +#define AD7746_CAPSETUP_CIN2 BIT(6) /* AD7746 only */ +#define AD7746_CAPSETUP_CAPDIFF BIT(5) +#define AD7746_CAPSETUP_CACHOP BIT(0) + +/* Voltage/Temperature Setup Register Bit Designations (AD7746_REG_VT_SETUP) */ +#define AD7746_VTSETUP_VTEN BIT(7) +#define AD7746_VTSETUP_VTMD_MASK GENMASK(6, 5) +#define AD7746_VTSETUP_VTMD_INT_TEMP 0 +#define AD7746_VTSETUP_VTMD_EXT_TEMP 1 +#define AD7746_VTSETUP_VTMD_VDD_MON 2 +#define AD7746_VTSETUP_VTMD_EXT_VIN 3 +#define AD7746_VTSETUP_EXTREF BIT(4) +#define AD7746_VTSETUP_VTSHORT BIT(1) +#define AD7746_VTSETUP_VTCHOP BIT(0) + +/* Excitation Setup Register Bit Designations (AD7746_REG_EXC_SETUP) */ +#define AD7746_EXCSETUP_CLKCTRL BIT(7) +#define AD7746_EXCSETUP_EXCON BIT(6) +#define AD7746_EXCSETUP_EXCB BIT(5) +#define AD7746_EXCSETUP_NEXCB BIT(4) +#define AD7746_EXCSETUP_EXCA BIT(3) +#define AD7746_EXCSETUP_NEXCA BIT(2) +#define AD7746_EXCSETUP_EXCLVL_MASK GENMASK(1, 0) + +/* Config Register Bit Designations (AD7746_REG_CFG) */ +#define AD7746_CONF_VTFS_MASK GENMASK(7, 6) +#define AD7746_CONF_CAPFS_MASK GENMASK(5, 3) +#define AD7746_CONF_MODE_MASK GENMASK(2, 0) +#define AD7746_CONF_MODE_IDLE 0 +#define AD7746_CONF_MODE_CONT_CONV 1 +#define AD7746_CONF_MODE_SINGLE_CONV 2 +#define AD7746_CONF_MODE_PWRDN 3 +#define AD7746_CONF_MODE_OFFS_CAL 5 +#define AD7746_CONF_MODE_GAIN_CAL 6 + +/* CAPDAC Register Bit Designations (AD7746_REG_CAPDACx) */ +#define AD7746_CAPDAC_DACEN BIT(7) +#define AD7746_CAPDAC_DACP_MASK GENMASK(6, 0) + +struct ad7746_chip_info { + struct i2c_client *client; + struct mutex lock; /* protect sensor state */ + /* + * Capacitive channel digital filter setup; + * conversion time/update rate setup per channel + */ + u8 config; + u8 cap_setup; + u8 vt_setup; + u8 capdac[2][2]; + s8 capdac_set; +}; + +enum ad7746_chan { + VIN, + VIN_VDD, + TEMP_INT, + TEMP_EXT, + CIN1, + CIN1_DIFF, + CIN2, + CIN2_DIFF, +}; + +struct ad7746_chan_info { + u8 addr; + union { + u8 vtmd; + struct { /* CAP SETUP fields */ + unsigned int cin2 : 1; + unsigned int capdiff : 1; + }; + }; +}; + +static const struct ad7746_chan_info ad7746_chan_info[] = { + [VIN] = { + .addr = AD7746_REG_VT_DATA_HIGH, + .vtmd = AD7746_VTSETUP_VTMD_EXT_VIN, + }, + [VIN_VDD] = { + .addr = AD7746_REG_VT_DATA_HIGH, + .vtmd = AD7746_VTSETUP_VTMD_VDD_MON, + }, + [TEMP_INT] = { + .addr = AD7746_REG_VT_DATA_HIGH, + .vtmd = AD7746_VTSETUP_VTMD_INT_TEMP, + }, + [TEMP_EXT] = { + .addr = AD7746_REG_VT_DATA_HIGH, + .vtmd = AD7746_VTSETUP_VTMD_EXT_TEMP, + }, + [CIN1] = { + .addr = AD7746_REG_CAP_DATA_HIGH, + }, + [CIN1_DIFF] = { + .addr = AD7746_REG_CAP_DATA_HIGH, + .capdiff = 1, + }, + [CIN2] = { + .addr = AD7746_REG_CAP_DATA_HIGH, + .cin2 = 1, + }, + [CIN2_DIFF] = { + .addr = AD7746_REG_CAP_DATA_HIGH, + .cin2 = 1, + .capdiff = 1, + }, +}; + +static const struct iio_chan_spec ad7746_channels[] = { + [VIN] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .address = VIN, + }, + [VIN_VDD] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .extend_name = "supply", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .address = VIN_VDD, + }, + [TEMP_INT] = { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .address = TEMP_INT, + }, + [TEMP_EXT] = { + .type = IIO_TEMP, + .indexed = 1, + .channel = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .address = TEMP_EXT, + }, + [CIN1] = { + .type = IIO_CAPACITANCE, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | BIT(IIO_CHAN_INFO_OFFSET), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .address = CIN1, + }, + [CIN1_DIFF] = { + .type = IIO_CAPACITANCE, + .differential = 1, + .indexed = 1, + .channel = 0, + .channel2 = 2, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | BIT(IIO_CHAN_INFO_ZEROPOINT), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .address = CIN1_DIFF, + }, + [CIN2] = { + .type = IIO_CAPACITANCE, + .indexed = 1, + .channel = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | BIT(IIO_CHAN_INFO_OFFSET), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .address = CIN2, + }, + [CIN2_DIFF] = { + .type = IIO_CAPACITANCE, + .differential = 1, + .indexed = 1, + .channel = 1, + .channel2 = 3, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | BIT(IIO_CHAN_INFO_ZEROPOINT), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .address = CIN2_DIFF, + } +}; + +/* Values are Update Rate (Hz), Conversion Time (ms) + 1*/ +static const unsigned char ad7746_vt_filter_rate_table[][2] = { + { 50, 20 + 1 }, { 31, 32 + 1 }, { 16, 62 + 1 }, { 8, 122 + 1 }, +}; + +static const unsigned char ad7746_cap_filter_rate_table[][2] = { + { 91, 11 + 1 }, { 84, 12 + 1 }, { 50, 20 + 1 }, { 26, 38 + 1 }, + { 16, 62 + 1 }, { 13, 77 + 1 }, { 11, 92 + 1 }, { 9, 110 + 1 }, +}; + +static int ad7746_set_capdac(struct ad7746_chip_info *chip, int channel) +{ + int ret = i2c_smbus_write_byte_data(chip->client, + AD7746_REG_CAPDACA, + chip->capdac[channel][0]); + if (ret < 0) + return ret; + + return i2c_smbus_write_byte_data(chip->client, + AD7746_REG_CAPDACB, + chip->capdac[channel][1]); +} + +static int ad7746_select_channel(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + struct ad7746_chip_info *chip = iio_priv(indio_dev); + u8 vt_setup, cap_setup; + int ret, delay, idx; + + switch (chan->type) { + case IIO_CAPACITANCE: + cap_setup = FIELD_PREP(AD7746_CAPSETUP_CIN2, + ad7746_chan_info[chan->address].cin2) | + FIELD_PREP(AD7746_CAPSETUP_CAPDIFF, + ad7746_chan_info[chan->address].capdiff) | + FIELD_PREP(AD7746_CAPSETUP_CAPEN, 1); + vt_setup = chip->vt_setup & ~AD7746_VTSETUP_VTEN; + idx = FIELD_GET(AD7746_CONF_CAPFS_MASK, chip->config); + delay = ad7746_cap_filter_rate_table[idx][1]; + + ret = ad7746_set_capdac(chip, chan->channel); + if (ret < 0) + return ret; + + chip->capdac_set = chan->channel; + break; + case IIO_VOLTAGE: + case IIO_TEMP: + vt_setup = FIELD_PREP(AD7746_VTSETUP_VTMD_MASK, + ad7746_chan_info[chan->address].vtmd) | + FIELD_PREP(AD7746_VTSETUP_VTEN, 1); + cap_setup = chip->cap_setup & ~AD7746_CAPSETUP_CAPEN; + idx = FIELD_GET(AD7746_CONF_VTFS_MASK, chip->config); + delay = ad7746_cap_filter_rate_table[idx][1]; + break; + default: + return -EINVAL; + } + + if (chip->cap_setup != cap_setup) { + ret = i2c_smbus_write_byte_data(chip->client, + AD7746_REG_CAP_SETUP, + cap_setup); + if (ret < 0) + return ret; + + chip->cap_setup = cap_setup; + } + + if (chip->vt_setup != vt_setup) { + ret = i2c_smbus_write_byte_data(chip->client, + AD7746_REG_VT_SETUP, + vt_setup); + if (ret < 0) + return ret; + + chip->vt_setup = vt_setup; + } + + return delay; +} + +static inline ssize_t ad7746_start_calib(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len, + u8 regval) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7746_chip_info *chip = iio_priv(indio_dev); + int ret, timeout = 10; + bool doit; + + ret = kstrtobool(buf, &doit); + if (ret < 0) + return ret; + + if (!doit) + return 0; + + mutex_lock(&chip->lock); + regval |= chip->config; + ret = i2c_smbus_write_byte_data(chip->client, AD7746_REG_CFG, regval); + if (ret < 0) + goto unlock; + + do { + msleep(20); + ret = i2c_smbus_read_byte_data(chip->client, AD7746_REG_CFG); + if (ret < 0) + goto unlock; + + } while ((ret == regval) && timeout--); + + mutex_unlock(&chip->lock); + + return len; + +unlock: + mutex_unlock(&chip->lock); + return ret; +} + +static ssize_t ad7746_start_offset_calib(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + int ret = ad7746_select_channel(indio_dev, + &ad7746_channels[to_iio_dev_attr(attr)->address]); + if (ret < 0) + return ret; + + return ad7746_start_calib(dev, attr, buf, len, + FIELD_PREP(AD7746_CONF_MODE_MASK, + AD7746_CONF_MODE_OFFS_CAL)); +} + +static ssize_t ad7746_start_gain_calib(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + int ret = ad7746_select_channel(indio_dev, + &ad7746_channels[to_iio_dev_attr(attr)->address]); + if (ret < 0) + return ret; + + return ad7746_start_calib(dev, attr, buf, len, + FIELD_PREP(AD7746_CONF_MODE_MASK, + AD7746_CONF_MODE_GAIN_CAL)); +} + +static IIO_DEVICE_ATTR(in_capacitance0_calibbias_calibration, + 0200, NULL, ad7746_start_offset_calib, CIN1); +static IIO_DEVICE_ATTR(in_capacitance1_calibbias_calibration, + 0200, NULL, ad7746_start_offset_calib, CIN2); +static IIO_DEVICE_ATTR(in_capacitance0_calibscale_calibration, + 0200, NULL, ad7746_start_gain_calib, CIN1); +static IIO_DEVICE_ATTR(in_capacitance1_calibscale_calibration, + 0200, NULL, ad7746_start_gain_calib, CIN2); +static IIO_DEVICE_ATTR(in_voltage0_calibscale_calibration, + 0200, NULL, ad7746_start_gain_calib, VIN); + +static int ad7746_store_cap_filter_rate_setup(struct ad7746_chip_info *chip, + int val) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ad7746_cap_filter_rate_table); i++) + if (val >= ad7746_cap_filter_rate_table[i][0]) + break; + + if (i >= ARRAY_SIZE(ad7746_cap_filter_rate_table)) + i = ARRAY_SIZE(ad7746_cap_filter_rate_table) - 1; + + chip->config &= ~AD7746_CONF_CAPFS_MASK; + chip->config |= FIELD_PREP(AD7746_CONF_CAPFS_MASK, i); + + return 0; +} + +static int ad7746_store_vt_filter_rate_setup(struct ad7746_chip_info *chip, + int val) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ad7746_vt_filter_rate_table); i++) + if (val >= ad7746_vt_filter_rate_table[i][0]) + break; + + if (i >= ARRAY_SIZE(ad7746_vt_filter_rate_table)) + i = ARRAY_SIZE(ad7746_vt_filter_rate_table) - 1; + + chip->config &= ~AD7746_CONF_VTFS_MASK; + chip->config |= FIELD_PREP(AD7746_CONF_VTFS_MASK, i); + + return 0; +} + +static struct attribute *ad7746_attributes[] = { + &iio_dev_attr_in_capacitance0_calibbias_calibration.dev_attr.attr, + &iio_dev_attr_in_capacitance0_calibscale_calibration.dev_attr.attr, + &iio_dev_attr_in_capacitance1_calibscale_calibration.dev_attr.attr, + &iio_dev_attr_in_capacitance1_calibbias_calibration.dev_attr.attr, + &iio_dev_attr_in_voltage0_calibscale_calibration.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad7746_attribute_group = { + .attrs = ad7746_attributes, +}; + +static int ad7746_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad7746_chip_info *chip = iio_priv(indio_dev); + int ret, reg; + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + if (val != 1) + return -EINVAL; + + val = (val2 * 1024) / 15625; + + switch (chan->type) { + case IIO_CAPACITANCE: + reg = AD7746_REG_CAP_GAINH; + break; + case IIO_VOLTAGE: + reg = AD7746_REG_VOLT_GAINH; + break; + default: + return -EINVAL; + } + + mutex_lock(&chip->lock); + ret = i2c_smbus_write_word_swapped(chip->client, reg, val); + mutex_unlock(&chip->lock); + if (ret < 0) + return ret; + + return 0; + case IIO_CHAN_INFO_CALIBBIAS: + if (val < 0 || val > 0xFFFF) + return -EINVAL; + + mutex_lock(&chip->lock); + ret = i2c_smbus_write_word_swapped(chip->client, + AD7746_REG_CAP_OFFH, val); + mutex_unlock(&chip->lock); + if (ret < 0) + return ret; + + return 0; + case IIO_CHAN_INFO_OFFSET: + case IIO_CHAN_INFO_ZEROPOINT: + if (val < 0 || val > 43008000) /* 21pF */ + return -EINVAL; + + /* + * CAPDAC Scale = 21pF_typ / 127 + * CIN Scale = 8.192pF / 2^24 + * Offset Scale = CAPDAC Scale / CIN Scale = 338646 + */ + + val /= 338646; + mutex_lock(&chip->lock); + chip->capdac[chan->channel][chan->differential] = val > 0 ? + FIELD_PREP(AD7746_CAPDAC_DACP_MASK, val) | AD7746_CAPDAC_DACEN : 0; + + ret = ad7746_set_capdac(chip, chan->channel); + if (ret < 0) { + mutex_unlock(&chip->lock); + return ret; + } + + chip->capdac_set = chan->channel; + mutex_unlock(&chip->lock); + + return 0; + case IIO_CHAN_INFO_SAMP_FREQ: + if (val2) + return -EINVAL; + + switch (chan->type) { + case IIO_CAPACITANCE: + mutex_lock(&chip->lock); + ret = ad7746_store_cap_filter_rate_setup(chip, val); + mutex_unlock(&chip->lock); + return ret; + case IIO_VOLTAGE: + mutex_lock(&chip->lock); + ret = ad7746_store_vt_filter_rate_setup(chip, val); + mutex_unlock(&chip->lock); + return ret; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static const int ad7746_v_samp_freq[] = { 50, 31, 16, 8, }; +static const int ad7746_cap_samp_freq[] = { 91, 84, 50, 26, 16, 13, 11, 9, }; + +static int ad7746_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, const int **vals, + int *type, int *length, long mask) +{ + if (mask != IIO_CHAN_INFO_SAMP_FREQ) + return -EINVAL; + + switch (chan->type) { + case IIO_VOLTAGE: + *vals = ad7746_v_samp_freq; + *length = ARRAY_SIZE(ad7746_v_samp_freq); + break; + case IIO_CAPACITANCE: + *vals = ad7746_cap_samp_freq; + *length = ARRAY_SIZE(ad7746_cap_samp_freq); + break; + default: + return -EINVAL; + } + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; +} + +static int ad7746_read_channel(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct ad7746_chip_info *chip = iio_priv(indio_dev); + int ret, delay; + u8 data[3]; + u8 regval; + + ret = ad7746_select_channel(indio_dev, chan); + if (ret < 0) + return ret; + delay = ret; + + regval = chip->config | FIELD_PREP(AD7746_CONF_MODE_MASK, + AD7746_CONF_MODE_SINGLE_CONV); + ret = i2c_smbus_write_byte_data(chip->client, AD7746_REG_CFG, regval); + if (ret < 0) + return ret; + + msleep(delay); + /* Now read the actual register */ + ret = i2c_smbus_read_i2c_block_data(chip->client, + ad7746_chan_info[chan->address].addr, + sizeof(data), data); + if (ret < 0) + return ret; + + /* + * Offset applied internally because the _offset userspace interface is + * needed for the CAP DACs which apply a controllable offset. + */ + *val = get_unaligned_be24(data) - 0x800000; + + return 0; +} + +static int ad7746_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct ad7746_chip_info *chip = iio_priv(indio_dev); + int ret, idx; + u8 reg; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&chip->lock); + ret = ad7746_read_channel(indio_dev, chan, val); + mutex_unlock(&chip->lock); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + switch (chan->type) { + case IIO_CAPACITANCE: + reg = AD7746_REG_CAP_GAINH; + break; + case IIO_VOLTAGE: + reg = AD7746_REG_VOLT_GAINH; + break; + default: + return -EINVAL; + } + + mutex_lock(&chip->lock); + ret = i2c_smbus_read_word_swapped(chip->client, reg); + mutex_unlock(&chip->lock); + if (ret < 0) + return ret; + /* 1 + gain_val / 2^16 */ + *val = 1; + *val2 = (15625 * ret) / 1024; + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBBIAS: + mutex_lock(&chip->lock); + ret = i2c_smbus_read_word_swapped(chip->client, + AD7746_REG_CAP_OFFH); + mutex_unlock(&chip->lock); + if (ret < 0) + return ret; + *val = ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + case IIO_CHAN_INFO_ZEROPOINT: + *val = FIELD_GET(AD7746_CAPDAC_DACP_MASK, + chip->capdac[chan->channel][chan->differential]) * 338646; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_CAPACITANCE: + /* 8.192pf / 2^24 */ + *val = 0; + *val2 = 488; + return IIO_VAL_INT_PLUS_NANO; + case IIO_VOLTAGE: + /* 1170mV / 2^23 */ + *val = 1170; + if (chan->channel == 1) + *val *= 6; + *val2 = 23; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_TEMP: + *val = 125; + *val2 = 8; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + switch (chan->type) { + case IIO_CAPACITANCE: + idx = FIELD_GET(AD7746_CONF_CAPFS_MASK, chip->config); + *val = ad7746_cap_filter_rate_table[idx][0]; + return IIO_VAL_INT; + case IIO_VOLTAGE: + idx = FIELD_GET(AD7746_CONF_VTFS_MASK, chip->config); + *val = ad7746_vt_filter_rate_table[idx][0]; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static const struct iio_info ad7746_info = { + .attrs = &ad7746_attribute_group, + .read_raw = ad7746_read_raw, + .read_avail = ad7746_read_avail, + .write_raw = ad7746_write_raw, +}; + +static int ad7746_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct device *dev = &client->dev; + struct ad7746_chip_info *chip; + struct iio_dev *indio_dev; + unsigned char regval = 0; + unsigned int vdd_permille; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + mutex_init(&chip->lock); + + chip->client = client; + chip->capdac_set = -1; + + indio_dev->name = id->name; + indio_dev->info = &ad7746_info; + indio_dev->channels = ad7746_channels; + if (id->driver_data == 7746) + indio_dev->num_channels = ARRAY_SIZE(ad7746_channels); + else + indio_dev->num_channels = ARRAY_SIZE(ad7746_channels) - 2; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (device_property_read_bool(dev, "adi,exca-output-en")) { + if (device_property_read_bool(dev, "adi,exca-output-invert")) + regval |= AD7746_EXCSETUP_NEXCA; + else + regval |= AD7746_EXCSETUP_EXCA; + } + + if (device_property_read_bool(dev, "adi,excb-output-en")) { + if (device_property_read_bool(dev, "adi,excb-output-invert")) + regval |= AD7746_EXCSETUP_NEXCB; + else + regval |= AD7746_EXCSETUP_EXCB; + } + + ret = device_property_read_u32(dev, "adi,excitation-vdd-permille", + &vdd_permille); + if (!ret) { + switch (vdd_permille) { + case 125: + regval |= FIELD_PREP(AD7746_EXCSETUP_EXCLVL_MASK, 0); + break; + case 250: + regval |= FIELD_PREP(AD7746_EXCSETUP_EXCLVL_MASK, 1); + break; + case 375: + regval |= FIELD_PREP(AD7746_EXCSETUP_EXCLVL_MASK, 2); + break; + case 500: + regval |= FIELD_PREP(AD7746_EXCSETUP_EXCLVL_MASK, 3); + break; + default: + break; + } + } + + ret = i2c_smbus_write_byte_data(chip->client, AD7746_REG_EXC_SETUP, + regval); + if (ret < 0) + return ret; + + return devm_iio_device_register(indio_dev->dev.parent, indio_dev); +} + +static const struct i2c_device_id ad7746_id[] = { + { .name = "ad7745", .driver_data = 7745 }, + { .name = "ad7746", .driver_data = 7746 }, + { .name = "ad7747", .driver_data = 7747 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ad7746_id); + +static const struct of_device_id ad7746_of_match[] = { + { .compatible = "adi,ad7745" }, + { .compatible = "adi,ad7746" }, + { .compatible = "adi,ad7747" }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7746_of_match); + +static struct i2c_driver ad7746_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = ad7746_of_match, + }, + .probe = ad7746_probe, + .id_table = ad7746_id, +}; +module_i2c_driver(ad7746_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7746/5/7 capacitive sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/chemical/Kconfig b/drivers/iio/chemical/Kconfig new file mode 100644 index 000000000..b22afa1f6 --- /dev/null +++ b/drivers/iio/chemical/Kconfig @@ -0,0 +1,260 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Chemical sensors +# + +menu "Chemical Sensors" + +config AOSONG_AGS02MA + tristate "Aosong AGS02MA TVOC sensor driver" + depends on I2C + select CRC8 + help + Say Y here to build support for Aosong AGS02MA TVOC (Total Volatile + Organic Compounds) sensor. + + To compile this driver as module, choose M here: the module will be + called ags02ma. + +config ATLAS_PH_SENSOR + tristate "Atlas Scientific OEM SM sensors" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select IRQ_WORK + help + Say Y here to build I2C interface support for the following + Atlas Scientific OEM SM sensors: + * pH SM sensor + * EC SM sensor + * ORP SM sensor + + To compile this driver as module, choose M here: the + module will be called atlas-ph-sensor. + +config ATLAS_EZO_SENSOR + tristate "Atlas Scientific EZO sensors" + depends on I2C + help + Say Y here to build I2C interface support for the following + Atlas Scientific EZO sensors + * CO2 EZO Sensor + + To compile this driver as module, choose M here: the + module will be called atlas-ezo-sensor. + +config BME680 + tristate "Bosch Sensortec BME680 sensor driver" + depends on (I2C || SPI) + select REGMAP + select BME680_I2C if I2C + select BME680_SPI if SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Bosch Sensortec BME680 sensor with + temperature, pressure, humidity and gas sensing capability. + + This driver can also be built as a module. If so, the module for I2C + would be called bme680_i2c and bme680_spi for SPI support. + +config BME680_I2C + tristate + depends on I2C && BME680 + select REGMAP_I2C + +config BME680_SPI + tristate + depends on SPI && BME680 + select REGMAP_SPI + +config CCS811 + tristate "AMS CCS811 VOC sensor" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here to build I2C interface support for the AMS + CCS811 VOC (Volatile Organic Compounds) sensor + +config ENS160 + tristate "ScioSense ENS160 sensor driver" + depends on (I2C || SPI) + select REGMAP + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select ENS160_I2C if I2C + select ENS160_SPI if SPI + help + Say yes here to build support for ScioSense ENS160 multi-gas sensor. + + This driver can also be built as a module. If so, the module for I2C + would be called ens160_i2c and ens160_spi for SPI support. + +config ENS160_I2C + tristate + select REGMAP_I2C + +config ENS160_SPI + tristate + select REGMAP_SPI + +config IAQCORE + tristate "AMS iAQ-Core VOC sensors" + depends on I2C + help + Say Y here to build I2C interface support for the AMS + iAQ-Core Continuous/Pulsed VOC (Volatile Organic Compounds) + sensors + +config MHZ19B + tristate "Winsen MHZ19B CO2 sensor" + depends on SERIAL_DEV_BUS + help + Say Y here to build Serdev interface support for the Winsen + MHZ19B CO2 sensor. + + To compile this driver as a module, choose M here: the module will + be called mhz19b. + +config PMS7003 + tristate "Plantower PMS7003 particulate matter sensor" + depends on SERIAL_DEV_BUS + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here to build support for the Plantower PMS7003 particulate + matter sensor. + + To compile this driver as a module, choose M here: the module will + be called pms7003. + +config SCD30_CORE + tristate "SCD30 carbon dioxide sensor driver" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here to build support for the Sensirion SCD30 sensor with carbon + dioxide, relative humidity and temperature sensing capabilities. + + To compile this driver as a module, choose M here: the module will + be called scd30_core. + +config SCD30_I2C + tristate "SCD30 carbon dioxide sensor I2C driver" + depends on SCD30_CORE && I2C + select CRC8 + help + Say Y here to build support for the Sensirion SCD30 I2C interface + driver. + + To compile this driver as a module, choose M here: the module will + be called scd30_i2c. + +config SCD30_SERIAL + tristate "SCD30 carbon dioxide sensor serial driver" + depends on SCD30_CORE && SERIAL_DEV_BUS + select CRC16 + help + Say Y here to build support for the Sensirion SCD30 serial interface + driver. + + To compile this driver as a module, choose M here: the module will + be called scd30_serial. + +config SCD4X + tristate "SCD4X carbon dioxide sensor driver" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + depends on I2C + select CRC8 + help + Say Y here to build support for the Sensirion SCD4X sensor with carbon + dioxide, relative humidity and temperature sensing capabilities. + + To compile this driver as a module, choose M here: the module will + be called scd4x. + +config SEN0322 + tristate "SEN0322 oxygen sensor" + depends on I2C + select REGMAP_I2C + help + Say Y here to build support for the DFRobot SEN0322 oxygen sensor. + + To compile this driver as a module, choose M here: the module will + be called sen0322. + +config SENSIRION_SGP30 + tristate "Sensirion SGPxx gas sensors" + depends on I2C + select CRC8 + help + Say Y here to build I2C interface support for the following + Sensirion SGP gas sensors: + * SGP30 gas sensor + * SGPC3 low power gas sensor + + To compile this driver as module, choose M here: the + module will be called sgp30. + +config SENSIRION_SGP40 + tristate "Sensirion SGP40 gas sensor" + depends on I2C + select CRC8 + help + Say Y here to build I2C interface to support Sensirion SGP40 gas + sensor + + To compile this driver as module, choose M here: the + module will be called sgp40. + +config SPS30 + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config SPS30_I2C + tristate "SPS30 particulate matter sensor I2C driver" + depends on I2C + select SPS30 + select CRC8 + help + Say Y here to build support for the Sensirion SPS30 I2C interface + driver. + + To compile this driver as a module, choose M here: the module will + be called sps30_i2c. + +config SPS30_SERIAL + tristate "SPS30 particulate matter sensor serial driver" + depends on SERIAL_DEV_BUS + select SPS30 + help + Say Y here to build support for the Sensirion SPS30 serial interface + driver. + + To compile this driver as a module, choose M here: the module will + be called sps30_serial. + +config SENSEAIR_SUNRISE_CO2 + tristate "Senseair Sunrise 006-0-0007 CO2 sensor" + depends on I2C + select REGMAP_I2C + help + Say yes here to build support for Senseair Sunrise 006-0-0007 CO2 + sensor. + + To compile this driver as a module, choose M here: the + module will be called sunrise_co2. + +config VZ89X + tristate "SGX Sensortech MiCS VZ89X VOC sensor" + depends on I2C + help + Say Y here to build I2C interface support for the SGX + Sensortech MiCS VZ89X VOC (Volatile Organic Compounds) + sensors + +endmenu diff --git a/drivers/iio/chemical/Makefile b/drivers/iio/chemical/Makefile new file mode 100644 index 000000000..2287a00a6 --- /dev/null +++ b/drivers/iio/chemical/Makefile @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for IIO chemical sensors +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_AOSONG_AGS02MA) += ags02ma.o +obj-$(CONFIG_ATLAS_PH_SENSOR) += atlas-sensor.o +obj-$(CONFIG_ATLAS_EZO_SENSOR) += atlas-ezo-sensor.o +obj-$(CONFIG_BME680) += bme680_core.o +obj-$(CONFIG_BME680_I2C) += bme680_i2c.o +obj-$(CONFIG_BME680_SPI) += bme680_spi.o +obj-$(CONFIG_CCS811) += ccs811.o +obj-$(CONFIG_ENS160) += ens160_core.o +obj-$(CONFIG_ENS160_I2C) += ens160_i2c.o +obj-$(CONFIG_ENS160_SPI) += ens160_spi.o +obj-$(CONFIG_IAQCORE) += ams-iaq-core.o +obj-$(CONFIG_MHZ19B) += mhz19b.o +obj-$(CONFIG_PMS7003) += pms7003.o +obj-$(CONFIG_SCD30_CORE) += scd30_core.o +obj-$(CONFIG_SCD30_I2C) += scd30_i2c.o +obj-$(CONFIG_SCD30_SERIAL) += scd30_serial.o +obj-$(CONFIG_SCD4X) += scd4x.o +obj-$(CONFIG_SEN0322) += sen0322.o +obj-$(CONFIG_SENSEAIR_SUNRISE_CO2) += sunrise_co2.o +obj-$(CONFIG_SENSIRION_SGP30) += sgp30.o +obj-$(CONFIG_SENSIRION_SGP40) += sgp40.o +obj-$(CONFIG_SPS30) += sps30.o +obj-$(CONFIG_SPS30_I2C) += sps30_i2c.o +obj-$(CONFIG_SPS30_SERIAL) += sps30_serial.o +obj-$(CONFIG_VZ89X) += vz89x.o diff --git a/drivers/iio/chemical/ags02ma.c b/drivers/iio/chemical/ags02ma.c new file mode 100644 index 000000000..8b97ea4a9 --- /dev/null +++ b/drivers/iio/chemical/ags02ma.c @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023 Anshul Dalal + * + * Driver for Aosong AGS02MA + * + * Datasheet: + * https://asairsensors.com/wp-content/uploads/2021/09/AGS02MA.pdf + * Product Page: + * http://www.aosong.com/m/en/products-33.html + */ + +#include +#include +#include +#include + +#include + +#define AGS02MA_TVOC_READ_REG 0x00 +#define AGS02MA_VERSION_REG 0x11 + +#define AGS02MA_VERSION_PROCESSING_DELAY 30 +#define AGS02MA_TVOC_READ_PROCESSING_DELAY 1500 + +#define AGS02MA_CRC8_INIT 0xff +#define AGS02MA_CRC8_POLYNOMIAL 0x31 + +DECLARE_CRC8_TABLE(ags02ma_crc8_table); + +struct ags02ma_data { + struct i2c_client *client; +}; + +struct ags02ma_reading { + __be32 data; + u8 crc; +} __packed; + +static int ags02ma_register_read(struct i2c_client *client, u8 reg, u16 delay, + u32 *val) +{ + int ret; + u8 crc; + struct ags02ma_reading read_buffer; + + ret = i2c_master_send(client, ®, sizeof(reg)); + if (ret < 0) { + dev_err(&client->dev, + "Failed to send data to register 0x%x: %d", reg, ret); + return ret; + } + + /* Processing Delay, Check Table 7.7 in the datasheet */ + msleep_interruptible(delay); + + ret = i2c_master_recv(client, (u8 *)&read_buffer, sizeof(read_buffer)); + if (ret < 0) { + dev_err(&client->dev, + "Failed to receive from register 0x%x: %d", reg, ret); + return ret; + } + + crc = crc8(ags02ma_crc8_table, (u8 *)&read_buffer.data, + sizeof(read_buffer.data), AGS02MA_CRC8_INIT); + if (crc != read_buffer.crc) { + dev_err(&client->dev, "CRC error\n"); + return -EIO; + } + + *val = be32_to_cpu(read_buffer.data); + return 0; +} + +static int ags02ma_read_raw(struct iio_dev *iio_device, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int ret; + struct ags02ma_data *data = iio_priv(iio_device); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = ags02ma_register_read(data->client, AGS02MA_TVOC_READ_REG, + AGS02MA_TVOC_READ_PROCESSING_DELAY, + val); + if (ret < 0) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* The sensor reads data as ppb */ + *val = 0; + *val2 = 100; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static const struct iio_info ags02ma_info = { + .read_raw = ags02ma_read_raw, +}; + +static const struct iio_chan_spec ags02ma_channel = { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_VOC, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), +}; + +static int ags02ma_probe(struct i2c_client *client) +{ + int ret; + struct ags02ma_data *data; + struct iio_dev *indio_dev; + u32 version; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + crc8_populate_msb(ags02ma_crc8_table, AGS02MA_CRC8_POLYNOMIAL); + + ret = ags02ma_register_read(client, AGS02MA_VERSION_REG, + AGS02MA_VERSION_PROCESSING_DELAY, &version); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to read device version\n"); + dev_dbg(&client->dev, "Aosong AGS02MA, Version: 0x%x", version); + + data = iio_priv(indio_dev); + data->client = client; + indio_dev->info = &ags02ma_info; + indio_dev->channels = &ags02ma_channel; + indio_dev->num_channels = 1; + indio_dev->name = "ags02ma"; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id ags02ma_id_table[] = { + { .name = "ags02ma" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ags02ma_id_table); + +static const struct of_device_id ags02ma_of_table[] = { + { .compatible = "aosong,ags02ma" }, + { } +}; +MODULE_DEVICE_TABLE(of, ags02ma_of_table); + +static struct i2c_driver ags02ma_driver = { + .driver = { + .name = "ags02ma", + .of_match_table = ags02ma_of_table, + }, + .id_table = ags02ma_id_table, + .probe = ags02ma_probe, +}; +module_i2c_driver(ags02ma_driver); + +MODULE_AUTHOR("Anshul Dalal "); +MODULE_DESCRIPTION("Aosong AGS02MA TVOC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/chemical/ams-iaq-core.c b/drivers/iio/chemical/ams-iaq-core.c new file mode 100644 index 000000000..7af515110 --- /dev/null +++ b/drivers/iio/chemical/ams-iaq-core.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ams-iaq-core.c - Support for AMS iAQ-Core VOC sensors + * + * Copyright (C) 2015, 2018 + * Author: Matt Ranostay + */ + +#include +#include +#include +#include +#include + +#define AMS_IAQCORE_DATA_SIZE 9 + +#define AMS_IAQCORE_VOC_CO2_IDX 0 +#define AMS_IAQCORE_VOC_RESISTANCE_IDX 1 +#define AMS_IAQCORE_VOC_TVOC_IDX 2 + +struct ams_iaqcore_reading { + __be16 co2_ppm; + u8 status; + __be32 resistance; + __be16 voc_ppb; +} __packed; + +struct ams_iaqcore_data { + struct i2c_client *client; + struct mutex lock; + unsigned long last_update; + + struct ams_iaqcore_reading buffer; +}; + +static const struct iio_chan_spec ams_iaqcore_channels[] = { + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_CO2, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = AMS_IAQCORE_VOC_CO2_IDX, + }, + { + .type = IIO_RESISTANCE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = AMS_IAQCORE_VOC_RESISTANCE_IDX, + }, + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_VOC, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = AMS_IAQCORE_VOC_TVOC_IDX, + }, +}; + +static int ams_iaqcore_read_measurement(struct ams_iaqcore_data *data) +{ + struct i2c_client *client = data->client; + int ret; + + struct i2c_msg msg = { + .addr = client->addr, + .flags = client->flags | I2C_M_RD, + .len = AMS_IAQCORE_DATA_SIZE, + .buf = (char *) &data->buffer, + }; + + ret = i2c_transfer(client->adapter, &msg, 1); + + return (ret == AMS_IAQCORE_DATA_SIZE) ? 0 : ret; +} + +static int ams_iaqcore_get_measurement(struct ams_iaqcore_data *data) +{ + int ret; + + /* sensor can only be polled once a second max per datasheet */ + if (!time_after(jiffies, data->last_update + HZ)) + return 0; + + ret = ams_iaqcore_read_measurement(data); + if (ret < 0) + return ret; + + data->last_update = jiffies; + + return 0; +} + +static int ams_iaqcore_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct ams_iaqcore_data *data = iio_priv(indio_dev); + int ret; + + if (mask != IIO_CHAN_INFO_PROCESSED) + return -EINVAL; + + mutex_lock(&data->lock); + ret = ams_iaqcore_get_measurement(data); + + if (ret) + goto err_out; + + switch (chan->address) { + case AMS_IAQCORE_VOC_CO2_IDX: + *val = 0; + *val2 = be16_to_cpu(data->buffer.co2_ppm); + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case AMS_IAQCORE_VOC_RESISTANCE_IDX: + *val = be32_to_cpu(data->buffer.resistance); + ret = IIO_VAL_INT; + break; + case AMS_IAQCORE_VOC_TVOC_IDX: + *val = 0; + *val2 = be16_to_cpu(data->buffer.voc_ppb); + ret = IIO_VAL_INT_PLUS_NANO; + break; + default: + ret = -EINVAL; + } + +err_out: + mutex_unlock(&data->lock); + + return ret; +} + +static const struct iio_info ams_iaqcore_info = { + .read_raw = ams_iaqcore_read_raw, +}; + +static int ams_iaqcore_probe(struct i2c_client *client) +{ + struct iio_dev *indio_dev; + struct ams_iaqcore_data *data; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + /* so initial reading will complete */ + data->last_update = jiffies - HZ; + mutex_init(&data->lock); + + indio_dev->info = &ams_iaqcore_info; + indio_dev->name = dev_name(&client->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + + indio_dev->channels = ams_iaqcore_channels; + indio_dev->num_channels = ARRAY_SIZE(ams_iaqcore_channels); + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id ams_iaqcore_id[] = { + { .name = "ams-iaq-core" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ams_iaqcore_id); + +static const struct of_device_id ams_iaqcore_dt_ids[] = { + { .compatible = "ams,iaq-core" }, + { } +}; +MODULE_DEVICE_TABLE(of, ams_iaqcore_dt_ids); + +static struct i2c_driver ams_iaqcore_driver = { + .driver = { + .name = "ams-iaq-core", + .of_match_table = ams_iaqcore_dt_ids, + }, + .probe = ams_iaqcore_probe, + .id_table = ams_iaqcore_id, +}; +module_i2c_driver(ams_iaqcore_driver); + +MODULE_AUTHOR("Matt Ranostay "); +MODULE_DESCRIPTION("AMS iAQ-Core VOC sensors"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/chemical/atlas-ezo-sensor.c b/drivers/iio/chemical/atlas-ezo-sensor.c new file mode 100644 index 000000000..298b2fe48 --- /dev/null +++ b/drivers/iio/chemical/atlas-ezo-sensor.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * atlas-ezo-sensor.c - Support for Atlas Scientific EZO sensors + * + * Copyright (C) 2020 Konsulko Group + * Author: Matt Ranostay + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define ATLAS_EZO_DRV_NAME "atlas-ezo-sensor" +#define ATLAS_INT_TIME_IN_MS 950 +#define ATLAS_INT_HUM_TIME_IN_MS 350 + +enum { + ATLAS_CO2_EZO, + ATLAS_O2_EZO, + ATLAS_HUM_EZO, +}; + +struct atlas_ezo_device { + const struct iio_chan_spec *channels; + int num_channels; + int delay; +}; + +struct atlas_ezo_data { + struct i2c_client *client; + const struct atlas_ezo_device *chip; + + /* lock to avoid multiple concurrent read calls */ + struct mutex lock; + + u8 buffer[8]; +}; + +#define ATLAS_CONCENTRATION_CHANNEL(_modifier) \ + { \ + .type = IIO_CONCENTRATION, \ + .modified = 1,\ + .channel2 = _modifier, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = 0, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 32, \ + .storagebits = 32, \ + .endianness = IIO_CPU, \ + }, \ + } + +static const struct iio_chan_spec atlas_co2_ezo_channels[] = { + ATLAS_CONCENTRATION_CHANNEL(IIO_MOD_CO2), +}; + +static const struct iio_chan_spec atlas_o2_ezo_channels[] = { + ATLAS_CONCENTRATION_CHANNEL(IIO_MOD_O2), +}; + +static const struct iio_chan_spec atlas_hum_ezo_channels[] = { + { + .type = IIO_HUMIDITYRELATIVE, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, +}; + +static const struct atlas_ezo_device atlas_ezo_devices[] = { + [ATLAS_CO2_EZO] = { + .channels = atlas_co2_ezo_channels, + .num_channels = 1, + .delay = ATLAS_INT_TIME_IN_MS, + }, + [ATLAS_O2_EZO] = { + .channels = atlas_o2_ezo_channels, + .num_channels = 1, + .delay = ATLAS_INT_TIME_IN_MS, + }, + [ATLAS_HUM_EZO] = { + .channels = atlas_hum_ezo_channels, + .num_channels = 1, + .delay = ATLAS_INT_HUM_TIME_IN_MS, + }, +}; + +static void atlas_ezo_sanitize(char *buf) +{ + char *ptr = strchr(buf, '.'); + + if (!ptr) + return; + + memmove(ptr, ptr + 1, strlen(ptr)); +} + +static int atlas_ezo_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct atlas_ezo_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + + if (chan->type != IIO_CONCENTRATION) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + int ret; + long tmp; + + mutex_lock(&data->lock); + + tmp = i2c_smbus_write_byte(client, 'R'); + + if (tmp < 0) { + mutex_unlock(&data->lock); + return tmp; + } + + msleep(data->chip->delay); + + tmp = i2c_master_recv(client, data->buffer, sizeof(data->buffer)); + + if (tmp < 0 || data->buffer[0] != 1) { + mutex_unlock(&data->lock); + return -EBUSY; + } + + /* removing floating point for fixed number representation */ + atlas_ezo_sanitize(data->buffer + 2); + + ret = kstrtol(data->buffer + 1, 10, &tmp); + + *val = tmp; + + mutex_unlock(&data->lock); + + return ret ? ret : IIO_VAL_INT; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_HUMIDITYRELATIVE: + *val = 10; + return IIO_VAL_INT; + case IIO_CONCENTRATION: + break; + default: + return -EINVAL; + } + + /* IIO_CONCENTRATION modifiers */ + switch (chan->channel2) { + case IIO_MOD_CO2: + *val = 0; + *val2 = 100; /* 0.0001 */ + return IIO_VAL_INT_PLUS_MICRO; + case IIO_MOD_O2: + *val = 100; + return IIO_VAL_INT; + } + return -EINVAL; + } + + return 0; +} + +static const struct iio_info atlas_info = { + .read_raw = atlas_ezo_read_raw, +}; + +static const struct i2c_device_id atlas_ezo_id[] = { + { .name = "atlas-co2-ezo", .driver_data = (kernel_ulong_t)&atlas_ezo_devices[ATLAS_CO2_EZO] }, + { .name = "atlas-o2-ezo", .driver_data = (kernel_ulong_t)&atlas_ezo_devices[ATLAS_O2_EZO] }, + { .name = "atlas-hum-ezo", .driver_data = (kernel_ulong_t)&atlas_ezo_devices[ATLAS_HUM_EZO] }, + { } +}; +MODULE_DEVICE_TABLE(i2c, atlas_ezo_id); + +static const struct of_device_id atlas_ezo_dt_ids[] = { + { .compatible = "atlas,co2-ezo", .data = &atlas_ezo_devices[ATLAS_CO2_EZO], }, + { .compatible = "atlas,o2-ezo", .data = &atlas_ezo_devices[ATLAS_O2_EZO], }, + { .compatible = "atlas,hum-ezo", .data = &atlas_ezo_devices[ATLAS_HUM_EZO], }, + { } +}; +MODULE_DEVICE_TABLE(of, atlas_ezo_dt_ids); + +static int atlas_ezo_probe(struct i2c_client *client) +{ + const struct atlas_ezo_device *chip; + struct atlas_ezo_data *data; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + chip = i2c_get_match_data(client); + if (!chip) + return -EINVAL; + + indio_dev->info = &atlas_info; + indio_dev->name = ATLAS_EZO_DRV_NAME; + indio_dev->channels = chip->channels; + indio_dev->num_channels = chip->num_channels; + indio_dev->modes = INDIO_DIRECT_MODE; + + data = iio_priv(indio_dev); + data->client = client; + data->chip = chip; + mutex_init(&data->lock); + + return devm_iio_device_register(&client->dev, indio_dev); +}; + +static struct i2c_driver atlas_ezo_driver = { + .driver = { + .name = ATLAS_EZO_DRV_NAME, + .of_match_table = atlas_ezo_dt_ids, + }, + .probe = atlas_ezo_probe, + .id_table = atlas_ezo_id, +}; +module_i2c_driver(atlas_ezo_driver); + +MODULE_AUTHOR("Matt Ranostay "); +MODULE_DESCRIPTION("Atlas Scientific EZO sensors"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/chemical/atlas-sensor.c b/drivers/iio/chemical/atlas-sensor.c new file mode 100644 index 000000000..1d4a9919d --- /dev/null +++ b/drivers/iio/chemical/atlas-sensor.c @@ -0,0 +1,763 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * atlas-sensor.c - Support for Atlas Scientific OEM SM sensors + * + * Copyright (C) 2015-2019 Konsulko Group + * Author: Matt Ranostay + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ATLAS_DRV_NAME "atlas" + +#define ATLAS_REG_DEV_TYPE 0x00 +#define ATLAS_REG_DEV_VERSION 0x01 + +#define ATLAS_REG_INT_CONTROL 0x04 +#define ATLAS_REG_INT_CONTROL_EN BIT(3) + +#define ATLAS_REG_PWR_CONTROL 0x06 + +#define ATLAS_REG_PH_CALIB_STATUS 0x0d +#define ATLAS_REG_PH_CALIB_STATUS_MASK 0x07 +#define ATLAS_REG_PH_CALIB_STATUS_LOW BIT(0) +#define ATLAS_REG_PH_CALIB_STATUS_MID BIT(1) +#define ATLAS_REG_PH_CALIB_STATUS_HIGH BIT(2) + +#define ATLAS_REG_EC_CALIB_STATUS 0x0f +#define ATLAS_REG_EC_CALIB_STATUS_MASK 0x0f +#define ATLAS_REG_EC_CALIB_STATUS_DRY BIT(0) +#define ATLAS_REG_EC_CALIB_STATUS_SINGLE BIT(1) +#define ATLAS_REG_EC_CALIB_STATUS_LOW BIT(2) +#define ATLAS_REG_EC_CALIB_STATUS_HIGH BIT(3) + +#define ATLAS_REG_DO_CALIB_STATUS 0x09 +#define ATLAS_REG_DO_CALIB_STATUS_MASK 0x03 +#define ATLAS_REG_DO_CALIB_STATUS_PRESSURE BIT(0) +#define ATLAS_REG_DO_CALIB_STATUS_DO BIT(1) + +#define ATLAS_REG_RTD_DATA 0x0e + +#define ATLAS_REG_PH_TEMP_DATA 0x0e +#define ATLAS_REG_PH_DATA 0x16 + +#define ATLAS_REG_EC_PROBE 0x08 +#define ATLAS_REG_EC_TEMP_DATA 0x10 +#define ATLAS_REG_EC_DATA 0x18 +#define ATLAS_REG_TDS_DATA 0x1c +#define ATLAS_REG_PSS_DATA 0x20 + +#define ATLAS_REG_ORP_CALIB_STATUS 0x0d +#define ATLAS_REG_ORP_DATA 0x0e + +#define ATLAS_REG_DO_TEMP_DATA 0x12 +#define ATLAS_REG_DO_DATA 0x22 + +#define ATLAS_PH_INT_TIME_IN_MS 450 +#define ATLAS_EC_INT_TIME_IN_MS 650 +#define ATLAS_ORP_INT_TIME_IN_MS 450 +#define ATLAS_DO_INT_TIME_IN_MS 450 +#define ATLAS_RTD_INT_TIME_IN_MS 450 + +enum { + ATLAS_PH_SM, + ATLAS_EC_SM, + ATLAS_ORP_SM, + ATLAS_DO_SM, + ATLAS_RTD_SM, +}; + +struct atlas_data { + struct i2c_client *client; + struct iio_trigger *trig; + const struct atlas_device *chip; + struct regmap *regmap; + unsigned int interrupt_enabled; + /* 96-bit data + 32-bit pad + 64-bit timestamp */ + __be32 buffer[6] __aligned(8); +}; + +static const struct regmap_config atlas_regmap_config = { + .name = "atlas_regmap", + .reg_bits = 8, + .val_bits = 8, +}; + +static int atlas_buffer_num_channels(const struct iio_chan_spec *spec) +{ + int idx = 0; + + for (; spec->type != IIO_TIMESTAMP; spec++) + idx++; + + return idx; +}; + +static const struct iio_chan_spec atlas_ph_channels[] = { + { + .type = IIO_PH, + .address = ATLAS_REG_PH_DATA, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_BE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), + { + .type = IIO_TEMP, + .address = ATLAS_REG_PH_TEMP_DATA, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .output = 1, + .scan_index = -1 + }, +}; + +#define ATLAS_CONCENTRATION_CHANNEL(_idx, _addr) \ + {\ + .type = IIO_CONCENTRATION, \ + .indexed = 1, \ + .channel = _idx, \ + .address = _addr, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = _idx + 1, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 32, \ + .storagebits = 32, \ + .endianness = IIO_BE, \ + }, \ + } + +static const struct iio_chan_spec atlas_ec_channels[] = { + { + .type = IIO_ELECTRICALCONDUCTIVITY, + .address = ATLAS_REG_EC_DATA, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_BE, + }, + }, + ATLAS_CONCENTRATION_CHANNEL(0, ATLAS_REG_TDS_DATA), + ATLAS_CONCENTRATION_CHANNEL(1, ATLAS_REG_PSS_DATA), + IIO_CHAN_SOFT_TIMESTAMP(3), + { + .type = IIO_TEMP, + .address = ATLAS_REG_EC_TEMP_DATA, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .output = 1, + .scan_index = -1 + }, +}; + +static const struct iio_chan_spec atlas_orp_channels[] = { + { + .type = IIO_VOLTAGE, + .address = ATLAS_REG_ORP_DATA, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_BE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct iio_chan_spec atlas_do_channels[] = { + { + .type = IIO_CONCENTRATION, + .address = ATLAS_REG_DO_DATA, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_BE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), + { + .type = IIO_TEMP, + .address = ATLAS_REG_DO_TEMP_DATA, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .output = 1, + .scan_index = -1 + }, +}; + +static const struct iio_chan_spec atlas_rtd_channels[] = { + { + .type = IIO_TEMP, + .address = ATLAS_REG_RTD_DATA, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_BE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static int atlas_check_ph_calibration(struct atlas_data *data) +{ + struct device *dev = &data->client->dev; + int ret; + unsigned int val; + + ret = regmap_read(data->regmap, ATLAS_REG_PH_CALIB_STATUS, &val); + if (ret) + return ret; + + if (!(val & ATLAS_REG_PH_CALIB_STATUS_MASK)) { + dev_warn(dev, "device has not been calibrated\n"); + return 0; + } + + if (!(val & ATLAS_REG_PH_CALIB_STATUS_LOW)) + dev_warn(dev, "device missing low point calibration\n"); + + if (!(val & ATLAS_REG_PH_CALIB_STATUS_MID)) + dev_warn(dev, "device missing mid point calibration\n"); + + if (!(val & ATLAS_REG_PH_CALIB_STATUS_HIGH)) + dev_warn(dev, "device missing high point calibration\n"); + + return 0; +} + +static int atlas_check_ec_calibration(struct atlas_data *data) +{ + struct device *dev = &data->client->dev; + int ret; + unsigned int val; + __be16 rval; + + ret = regmap_bulk_read(data->regmap, ATLAS_REG_EC_PROBE, &rval, 2); + if (ret) + return ret; + + val = be16_to_cpu(rval); + dev_info(dev, "probe set to K = %d.%.2d", val / 100, val % 100); + + ret = regmap_read(data->regmap, ATLAS_REG_EC_CALIB_STATUS, &val); + if (ret) + return ret; + + if (!(val & ATLAS_REG_EC_CALIB_STATUS_MASK)) { + dev_warn(dev, "device has not been calibrated\n"); + return 0; + } + + if (!(val & ATLAS_REG_EC_CALIB_STATUS_DRY)) + dev_warn(dev, "device missing dry point calibration\n"); + + if (val & ATLAS_REG_EC_CALIB_STATUS_SINGLE) { + dev_warn(dev, "device using single point calibration\n"); + } else { + if (!(val & ATLAS_REG_EC_CALIB_STATUS_LOW)) + dev_warn(dev, "device missing low point calibration\n"); + + if (!(val & ATLAS_REG_EC_CALIB_STATUS_HIGH)) + dev_warn(dev, "device missing high point calibration\n"); + } + + return 0; +} + +static int atlas_check_orp_calibration(struct atlas_data *data) +{ + struct device *dev = &data->client->dev; + int ret; + unsigned int val; + + ret = regmap_read(data->regmap, ATLAS_REG_ORP_CALIB_STATUS, &val); + if (ret) + return ret; + + if (!val) + dev_warn(dev, "device has not been calibrated\n"); + + return 0; +} + +static int atlas_check_do_calibration(struct atlas_data *data) +{ + struct device *dev = &data->client->dev; + int ret; + unsigned int val; + + ret = regmap_read(data->regmap, ATLAS_REG_DO_CALIB_STATUS, &val); + if (ret) + return ret; + + if (!(val & ATLAS_REG_DO_CALIB_STATUS_MASK)) { + dev_warn(dev, "device has not been calibrated\n"); + return 0; + } + + if (!(val & ATLAS_REG_DO_CALIB_STATUS_PRESSURE)) + dev_warn(dev, "device missing atmospheric pressure calibration\n"); + + if (!(val & ATLAS_REG_DO_CALIB_STATUS_DO)) + dev_warn(dev, "device missing dissolved oxygen calibration\n"); + + return 0; +} + +struct atlas_device { + const struct iio_chan_spec *channels; + int num_channels; + int data_reg; + + int (*calibration)(struct atlas_data *data); + int delay; +}; + +static const struct atlas_device atlas_devices[] = { + [ATLAS_PH_SM] = { + .channels = atlas_ph_channels, + .num_channels = 3, + .data_reg = ATLAS_REG_PH_DATA, + .calibration = &atlas_check_ph_calibration, + .delay = ATLAS_PH_INT_TIME_IN_MS, + }, + [ATLAS_EC_SM] = { + .channels = atlas_ec_channels, + .num_channels = 5, + .data_reg = ATLAS_REG_EC_DATA, + .calibration = &atlas_check_ec_calibration, + .delay = ATLAS_EC_INT_TIME_IN_MS, + }, + [ATLAS_ORP_SM] = { + .channels = atlas_orp_channels, + .num_channels = 2, + .data_reg = ATLAS_REG_ORP_DATA, + .calibration = &atlas_check_orp_calibration, + .delay = ATLAS_ORP_INT_TIME_IN_MS, + }, + [ATLAS_DO_SM] = { + .channels = atlas_do_channels, + .num_channels = 3, + .data_reg = ATLAS_REG_DO_DATA, + .calibration = &atlas_check_do_calibration, + .delay = ATLAS_DO_INT_TIME_IN_MS, + }, + [ATLAS_RTD_SM] = { + .channels = atlas_rtd_channels, + .num_channels = 2, + .data_reg = ATLAS_REG_RTD_DATA, + .delay = ATLAS_RTD_INT_TIME_IN_MS, + }, +}; + +static int atlas_set_powermode(struct atlas_data *data, int on) +{ + return regmap_write(data->regmap, ATLAS_REG_PWR_CONTROL, on); +} + +static int atlas_set_interrupt(struct atlas_data *data, bool state) +{ + if (!data->interrupt_enabled) + return 0; + + return regmap_update_bits(data->regmap, ATLAS_REG_INT_CONTROL, + ATLAS_REG_INT_CONTROL_EN, + state ? ATLAS_REG_INT_CONTROL_EN : 0); +} + +static int atlas_buffer_postenable(struct iio_dev *indio_dev) +{ + struct atlas_data *data = iio_priv(indio_dev); + int ret; + + ret = pm_runtime_resume_and_get(&data->client->dev); + if (ret) + return ret; + + ret = atlas_set_interrupt(data, true); + if (ret) + pm_runtime_put_autosuspend(&data->client->dev); + + return ret; +} + +static int atlas_buffer_predisable(struct iio_dev *indio_dev) +{ + struct atlas_data *data = iio_priv(indio_dev); + int ret; + + ret = atlas_set_interrupt(data, false); + if (ret) + return ret; + + ret = pm_runtime_put_autosuspend(&data->client->dev); + if (ret) + return ret; + + return 0; +} + +static const struct iio_buffer_setup_ops atlas_buffer_setup_ops = { + .postenable = atlas_buffer_postenable, + .predisable = atlas_buffer_predisable, +}; + +static irqreturn_t atlas_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct atlas_data *data = iio_priv(indio_dev); + int channels = atlas_buffer_num_channels(data->chip->channels); + int ret; + + ret = regmap_bulk_read(data->regmap, data->chip->data_reg, + &data->buffer, sizeof(__be32) * channels); + + if (!ret) + iio_push_to_buffers_with_ts(indio_dev, data->buffer, + sizeof(data->buffer), + iio_get_time_ns(indio_dev)); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static irqreturn_t atlas_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct atlas_data *data = iio_priv(indio_dev); + + iio_trigger_poll_nested(data->trig); + + return IRQ_HANDLED; +} + +static int atlas_read_measurement(struct atlas_data *data, int reg, __be32 *val) +{ + struct device *dev = &data->client->dev; + int suspended = pm_runtime_suspended(dev); + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + if (suspended) + msleep(data->chip->delay); + + ret = regmap_bulk_read(data->regmap, reg, val, sizeof(*val)); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static int atlas_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct atlas_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + case IIO_CHAN_INFO_RAW: { + int ret; + __be32 reg; + + switch (chan->type) { + case IIO_TEMP: + ret = regmap_bulk_read(data->regmap, chan->address, + ®, sizeof(reg)); + break; + case IIO_PH: + case IIO_CONCENTRATION: + case IIO_ELECTRICALCONDUCTIVITY: + case IIO_VOLTAGE: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = atlas_read_measurement(data, chan->address, ®); + + iio_device_release_direct(indio_dev); + break; + default: + ret = -EINVAL; + } + + if (!ret) { + *val = be32_to_cpu(reg); + ret = IIO_VAL_INT; + } + return ret; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + *val = 10; + return IIO_VAL_INT; + case IIO_PH: + *val = 1; /* 0.001 */ + *val2 = 1000; + break; + case IIO_ELECTRICALCONDUCTIVITY: + *val = 1; /* 0.00001 */ + *val2 = 100000; + break; + case IIO_CONCENTRATION: + *val = 0; /* 0.000000001 */ + *val2 = 1000; + return IIO_VAL_INT_PLUS_NANO; + case IIO_VOLTAGE: + *val = 1; /* 0.1 */ + *val2 = 10; + break; + default: + return -EINVAL; + } + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; +} + +static int atlas_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct atlas_data *data = iio_priv(indio_dev); + __be32 reg = cpu_to_be32(val / 10); + + if (val2 != 0 || val < 0 || val > 20000) + return -EINVAL; + + if (mask != IIO_CHAN_INFO_RAW || chan->type != IIO_TEMP) + return -EINVAL; + + return regmap_bulk_write(data->regmap, chan->address, + ®, sizeof(reg)); +} + +static const struct iio_info atlas_info = { + .read_raw = atlas_read_raw, + .write_raw = atlas_write_raw, +}; + +static const struct i2c_device_id atlas_id[] = { + { .name = "atlas-ph-sm", .driver_data = (kernel_ulong_t)&atlas_devices[ATLAS_PH_SM] }, + { .name = "atlas-ec-sm", .driver_data = (kernel_ulong_t)&atlas_devices[ATLAS_EC_SM] }, + { .name = "atlas-orp-sm", .driver_data = (kernel_ulong_t)&atlas_devices[ATLAS_ORP_SM] }, + { .name = "atlas-do-sm", .driver_data = (kernel_ulong_t)&atlas_devices[ATLAS_DO_SM] }, + { .name = "atlas-rtd-sm", .driver_data = (kernel_ulong_t)&atlas_devices[ATLAS_RTD_SM] }, + { } +}; +MODULE_DEVICE_TABLE(i2c, atlas_id); + +static const struct of_device_id atlas_dt_ids[] = { + { .compatible = "atlas,ph-sm", .data = &atlas_devices[ATLAS_PH_SM] }, + { .compatible = "atlas,ec-sm", .data = &atlas_devices[ATLAS_EC_SM] }, + { .compatible = "atlas,orp-sm", .data = &atlas_devices[ATLAS_ORP_SM] }, + { .compatible = "atlas,do-sm", .data = &atlas_devices[ATLAS_DO_SM] }, + { .compatible = "atlas,rtd-sm", .data = &atlas_devices[ATLAS_RTD_SM] }, + { } +}; +MODULE_DEVICE_TABLE(of, atlas_dt_ids); + +static int atlas_probe(struct i2c_client *client) +{ + struct atlas_data *data; + const struct atlas_device *chip; + struct iio_trigger *trig; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + chip = i2c_get_match_data(client); + + indio_dev->info = &atlas_info; + indio_dev->name = ATLAS_DRV_NAME; + indio_dev->channels = chip->channels; + indio_dev->num_channels = chip->num_channels; + indio_dev->modes = INDIO_BUFFER_SOFTWARE | INDIO_DIRECT_MODE; + + trig = devm_iio_trigger_alloc(&client->dev, "%s-dev%d", + indio_dev->name, iio_device_id(indio_dev)); + + if (!trig) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + data->trig = trig; + data->chip = chip; + iio_trigger_set_drvdata(trig, indio_dev); + + i2c_set_clientdata(client, indio_dev); + + data->regmap = devm_regmap_init_i2c(client, &atlas_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(&client->dev, "regmap initialization failed\n"); + return PTR_ERR(data->regmap); + } + + ret = pm_runtime_set_active(&client->dev); + if (ret) + return ret; + + ret = chip->calibration(data); + if (ret) + return ret; + + ret = iio_trigger_register(trig); + if (ret) { + dev_err(&client->dev, "failed to register trigger\n"); + return ret; + } + + ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + &atlas_trigger_handler, &atlas_buffer_setup_ops); + if (ret) { + dev_err(&client->dev, "cannot setup iio trigger\n"); + goto unregister_trigger; + } + + if (client->irq > 0) { + /* interrupt pin toggles on new conversion */ + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, atlas_interrupt_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "atlas_irq", + indio_dev); + + if (ret) + dev_warn(&client->dev, + "request irq (%d) failed\n", client->irq); + else + data->interrupt_enabled = 1; + } + + ret = atlas_set_powermode(data, 1); + if (ret) { + dev_err(&client->dev, "cannot power device on"); + goto unregister_buffer; + } + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, 2500); + pm_runtime_use_autosuspend(&client->dev); + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&client->dev, "unable to register device\n"); + goto unregister_pm; + } + + return 0; + +unregister_pm: + pm_runtime_disable(&client->dev); + atlas_set_powermode(data, 0); + +unregister_buffer: + iio_triggered_buffer_cleanup(indio_dev); + +unregister_trigger: + iio_trigger_unregister(data->trig); + + return ret; +} + +static void atlas_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct atlas_data *data = iio_priv(indio_dev); + int ret; + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + iio_trigger_unregister(data->trig); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + ret = atlas_set_powermode(data, 0); + if (ret) + dev_err(&client->dev, "Failed to power down device (%pe)\n", + ERR_PTR(ret)); +} + +static int atlas_runtime_suspend(struct device *dev) +{ + struct atlas_data *data = + iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + return atlas_set_powermode(data, 0); +} + +static int atlas_runtime_resume(struct device *dev) +{ + struct atlas_data *data = + iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + return atlas_set_powermode(data, 1); +} + +static const struct dev_pm_ops atlas_pm_ops = { + RUNTIME_PM_OPS(atlas_runtime_suspend, atlas_runtime_resume, NULL) +}; + +static struct i2c_driver atlas_driver = { + .driver = { + .name = ATLAS_DRV_NAME, + .of_match_table = atlas_dt_ids, + .pm = pm_ptr(&atlas_pm_ops), + }, + .probe = atlas_probe, + .remove = atlas_remove, + .id_table = atlas_id, +}; +module_i2c_driver(atlas_driver); + +MODULE_AUTHOR("Matt Ranostay "); +MODULE_DESCRIPTION("Atlas Scientific SM sensors"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/chemical/bme680.h b/drivers/iio/chemical/bme680.h new file mode 100644 index 000000000..7d86ed8b0 --- /dev/null +++ b/drivers/iio/chemical/bme680.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef BME680_H_ +#define BME680_H_ + +#include +#include + +#define BME680_REG_CHIP_ID 0xD0 +#define BME680_CHIP_ID_VAL 0x61 +#define BME680_REG_SOFT_RESET 0xE0 +#define BME680_CMD_SOFTRESET 0xB6 +#define BME680_REG_STATUS 0x73 +#define BME680_SPI_MEM_PAGE_BIT BIT(4) +#define BME680_SPI_MEM_PAGE_1_VAL 1 + +#define BME680_REG_TEMP_MSB 0x22 +#define BME680_REG_PRESS_MSB 0x1F +#define BME680_REG_HUMIDITY_MSB 0x25 +#define BME680_REG_GAS_MSB 0x2A +#define BME680_REG_GAS_R_LSB 0x2B +#define BME680_GAS_STAB_BIT BIT(4) +#define BME680_GAS_RANGE_MASK GENMASK(3, 0) + +#define BME680_REG_CTRL_HUMIDITY 0x72 +#define BME680_OSRS_HUMIDITY_MASK GENMASK(2, 0) + +#define BME680_REG_CTRL_MEAS 0x74 +#define BME680_OSRS_TEMP_MASK GENMASK(7, 5) +#define BME680_OSRS_PRESS_MASK GENMASK(4, 2) +#define BME680_MODE_MASK GENMASK(1, 0) + +#define BME680_REG_CONFIG 0x75 +#define BME680_FILTER_MASK GENMASK(4, 2) +#define BME680_FILTER_COEFF_VAL BIT(1) + +/* TEMP/PRESS/HUMID reading skipped */ +#define BME680_MEAS_SKIPPED 0x8000 + +#define BME680_MAX_OVERFLOW_VAL 0x40000000 +#define BME680_HUM_REG_SHIFT_VAL 4 +#define BME680_BIT_H1_DATA_MASK GENMASK(3, 0) + +#define BME680_RHRANGE_MASK GENMASK(5, 4) +#define BME680_REG_RES_HEAT_VAL 0x00 +#define BME680_RSERROR_MASK GENMASK(7, 4) +#define BME680_REG_IDAC_HEAT_0 0x50 +#define BME680_REG_RES_HEAT_0 0x5A +#define BME680_REG_GAS_WAIT_0 0x64 +#define BME680_ADC_GAS_RES GENMASK(15, 6) +#define BME680_AMB_TEMP 25 + +#define BME680_REG_CTRL_GAS_1 0x71 +#define BME680_RUN_GAS_MASK BIT(4) +#define BME680_NB_CONV_MASK GENMASK(3, 0) + +#define BME680_REG_MEAS_STAT_0 0x1D +#define BME680_NEW_DATA_BIT BIT(7) +#define BME680_GAS_MEAS_BIT BIT(6) +#define BME680_MEAS_BIT BIT(5) + +#define BME680_TEMP_NUM_BYTES 3 +#define BME680_PRESS_NUM_BYTES 3 +#define BME680_HUMID_NUM_BYTES 2 +#define BME680_GAS_NUM_BYTES 2 + +#define BME680_MEAS_TRIM_MASK GENMASK(24, 4) + +/* Datasheet Section 1.1, Table 1 */ +#define BME680_STARTUP_TIME_US 2000 + +#define BME680_NUM_CHANNELS 4 +#define BME680_NUM_BULK_READ_REGS 15 + +/* Calibration Parameters */ +#define BME680_T2_LSB_REG 0x8A +#define BME680_H2_MSB_REG 0xE1 +#define BME680_GH3_REG 0xEE + +#define BME680_CALIB_RANGE_1_LEN 23 +#define BME680_CALIB_RANGE_2_LEN 14 +#define BME680_CALIB_RANGE_3_LEN 5 + +extern const struct regmap_config bme680_regmap_config; +extern const struct dev_pm_ops bme680_dev_pm_ops; + +int bme680_core_probe(struct device *dev, struct regmap *regmap, + const char *name); + +#endif /* BME680_H_ */ diff --git a/drivers/iio/chemical/bme680_core.c b/drivers/iio/chemical/bme680_core.c new file mode 100644 index 000000000..ac9a73748 --- /dev/null +++ b/drivers/iio/chemical/bme680_core.c @@ -0,0 +1,1266 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Bosch BME680 - Temperature, Pressure, Humidity & Gas Sensor + * + * Copyright (C) 2017 - 2018 Bosch Sensortec GmbH + * Copyright (C) 2018 Himanshu Jha + * + * Datasheet: + * https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BME680-DS001-00.pdf + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "bme680.h" + +/* 1st set of calibration data */ +enum { + /* Temperature calib indexes */ + T2_LSB = 0, + T3 = 2, + /* Pressure calib indexes */ + P1_LSB = 4, + P2_LSB = 6, + P3 = 8, + P4_LSB = 10, + P5_LSB = 12, + P7 = 14, + P6 = 15, + P8_LSB = 18, + P9_LSB = 20, + P10 = 22, +}; + +/* 2nd set of calibration data */ +enum { + /* Humidity calib indexes */ + H2_MSB = 0, + H1_LSB = 1, + H3 = 3, + H4 = 4, + H5 = 5, + H6 = 6, + H7 = 7, + /* Stray T1 calib index */ + T1_LSB = 8, + /* Gas heater calib indexes */ + GH2_LSB = 10, + GH1 = 12, + GH3 = 13, +}; + +/* 3rd set of calibration data */ +enum { + RES_HEAT_VAL = 0, + RES_HEAT_RANGE = 2, + RANGE_SW_ERR = 4, +}; + +struct bme680_calib { + u16 par_t1; + s16 par_t2; + s8 par_t3; + u16 par_p1; + s16 par_p2; + s8 par_p3; + s16 par_p4; + s16 par_p5; + s8 par_p6; + s8 par_p7; + s16 par_p8; + s16 par_p9; + u8 par_p10; + u16 par_h1; + u16 par_h2; + s8 par_h3; + s8 par_h4; + s8 par_h5; + u8 par_h6; + s8 par_h7; + s8 par_gh1; + s16 par_gh2; + s8 par_gh3; + u8 res_heat_range; + s8 res_heat_val; + s8 range_sw_err; +}; + +/* values of CTRL_MEAS register */ +enum bme680_op_mode { + BME680_MODE_SLEEP = 0, + BME680_MODE_FORCED = 1, +}; + +enum bme680_scan { + BME680_TEMP, + BME680_PRESS, + BME680_HUMID, + BME680_GAS, +}; + +static const char *const bme680_supply_names[] = { "vdd", "vddio" }; + +struct bme680_data { + struct regmap *regmap; + struct bme680_calib bme680; + struct mutex lock; /* Protect multiple serial R/W ops to device. */ + u8 oversampling_temp; + u8 oversampling_press; + u8 oversampling_humid; + u8 preheat_curr_mA; + u16 heater_dur; + u16 heater_temp; + + struct { + s32 chan[BME680_NUM_CHANNELS]; + aligned_s64 ts; + } scan; + + union { + u8 buf[BME680_NUM_BULK_READ_REGS]; + unsigned int check; + __be16 be16; + u8 bme680_cal_buf_1[BME680_CALIB_RANGE_1_LEN]; + u8 bme680_cal_buf_2[BME680_CALIB_RANGE_2_LEN]; + u8 bme680_cal_buf_3[BME680_CALIB_RANGE_3_LEN]; + }; +}; + +static const struct regmap_range bme680_volatile_ranges[] = { + regmap_reg_range(BME680_REG_MEAS_STAT_0, BME680_REG_GAS_R_LSB), + regmap_reg_range(BME680_REG_STATUS, BME680_REG_STATUS), + regmap_reg_range(BME680_T2_LSB_REG, BME680_GH3_REG), +}; + +static const struct regmap_access_table bme680_volatile_table = { + .yes_ranges = bme680_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(bme680_volatile_ranges), +}; + +const struct regmap_config bme680_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xef, + .volatile_table = &bme680_volatile_table, + .cache_type = REGCACHE_MAPLE, +}; +EXPORT_SYMBOL_NS(bme680_regmap_config, "IIO_BME680"); + +static const struct iio_chan_spec bme680_channels[] = { + { + .type = IIO_TEMP, + /* PROCESSED maintained for ABI backwards compatibility */ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_PRESSURE, + /* PROCESSED maintained for ABI backwards compatibility */ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 1, + .scan_type = { + .sign = 'u', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_HUMIDITYRELATIVE, + /* PROCESSED maintained for ABI backwards compatibility */ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 2, + .scan_type = { + .sign = 'u', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_RESISTANCE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = 3, + .scan_type = { + .sign = 'u', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(4), + { + .type = IIO_CURRENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .output = 1, + .scan_index = -1, + }, +}; + +static int bme680_read_calib(struct bme680_data *data, + struct bme680_calib *calib) +{ + struct device *dev = regmap_get_device(data->regmap); + unsigned int tmp_msb, tmp_lsb; + int ret; + + ret = regmap_bulk_read(data->regmap, BME680_T2_LSB_REG, + data->bme680_cal_buf_1, + sizeof(data->bme680_cal_buf_1)); + if (ret < 0) { + dev_err(dev, "failed to read 1st set of calib data;\n"); + return ret; + } + + calib->par_t2 = get_unaligned_le16(&data->bme680_cal_buf_1[T2_LSB]); + calib->par_t3 = data->bme680_cal_buf_1[T3]; + calib->par_p1 = get_unaligned_le16(&data->bme680_cal_buf_1[P1_LSB]); + calib->par_p2 = get_unaligned_le16(&data->bme680_cal_buf_1[P2_LSB]); + calib->par_p3 = data->bme680_cal_buf_1[P3]; + calib->par_p4 = get_unaligned_le16(&data->bme680_cal_buf_1[P4_LSB]); + calib->par_p5 = get_unaligned_le16(&data->bme680_cal_buf_1[P5_LSB]); + calib->par_p7 = data->bme680_cal_buf_1[P7]; + calib->par_p6 = data->bme680_cal_buf_1[P6]; + calib->par_p8 = get_unaligned_le16(&data->bme680_cal_buf_1[P8_LSB]); + calib->par_p9 = get_unaligned_le16(&data->bme680_cal_buf_1[P9_LSB]); + calib->par_p10 = data->bme680_cal_buf_1[P10]; + + ret = regmap_bulk_read(data->regmap, BME680_H2_MSB_REG, + data->bme680_cal_buf_2, + sizeof(data->bme680_cal_buf_2)); + if (ret < 0) { + dev_err(dev, "failed to read 2nd set of calib data;\n"); + return ret; + } + + tmp_lsb = data->bme680_cal_buf_2[H1_LSB]; + tmp_msb = data->bme680_cal_buf_2[H1_LSB + 1]; + calib->par_h1 = (tmp_msb << BME680_HUM_REG_SHIFT_VAL) | + (tmp_lsb & BME680_BIT_H1_DATA_MASK); + + tmp_msb = data->bme680_cal_buf_2[H2_MSB]; + tmp_lsb = data->bme680_cal_buf_2[H2_MSB + 1]; + calib->par_h2 = (tmp_msb << BME680_HUM_REG_SHIFT_VAL) | + (tmp_lsb >> BME680_HUM_REG_SHIFT_VAL); + + calib->par_h3 = data->bme680_cal_buf_2[H3]; + calib->par_h4 = data->bme680_cal_buf_2[H4]; + calib->par_h5 = data->bme680_cal_buf_2[H5]; + calib->par_h6 = data->bme680_cal_buf_2[H6]; + calib->par_h7 = data->bme680_cal_buf_2[H7]; + calib->par_t1 = get_unaligned_le16(&data->bme680_cal_buf_2[T1_LSB]); + calib->par_gh2 = get_unaligned_le16(&data->bme680_cal_buf_2[GH2_LSB]); + calib->par_gh1 = data->bme680_cal_buf_2[GH1]; + calib->par_gh3 = data->bme680_cal_buf_2[GH3]; + + ret = regmap_bulk_read(data->regmap, BME680_REG_RES_HEAT_VAL, + data->bme680_cal_buf_3, + sizeof(data->bme680_cal_buf_3)); + if (ret < 0) { + dev_err(dev, "failed to read 3rd set of calib data;\n"); + return ret; + } + + calib->res_heat_val = data->bme680_cal_buf_3[RES_HEAT_VAL]; + + calib->res_heat_range = FIELD_GET(BME680_RHRANGE_MASK, + data->bme680_cal_buf_3[RES_HEAT_RANGE]); + + calib->range_sw_err = FIELD_GET(BME680_RSERROR_MASK, + data->bme680_cal_buf_3[RANGE_SW_ERR]); + + return 0; +} + +static int bme680_read_temp_adc(struct bme680_data *data, u32 *adc_temp) +{ + struct device *dev = regmap_get_device(data->regmap); + u32 value_temp; + int ret; + + ret = regmap_bulk_read(data->regmap, BME680_REG_TEMP_MSB, + data->buf, BME680_TEMP_NUM_BYTES); + if (ret < 0) { + dev_err(dev, "failed to read temperature\n"); + return ret; + } + + value_temp = FIELD_GET(BME680_MEAS_TRIM_MASK, + get_unaligned_be24(data->buf)); + if (value_temp == BME680_MEAS_SKIPPED) { + /* reading was skipped */ + dev_err(dev, "reading temperature skipped\n"); + return -EINVAL; + } + *adc_temp = value_temp; + + return 0; +} + +/* + * Taken from Bosch BME680 API: + * https://github.com/BoschSensortec/BME680_driver/blob/63bb5336/bme680.c#L876 + * + * Returns temperature measurement in DegC, resolutions is 0.01 DegC. Therefore, + * output value of "3233" represents 32.33 DegC. + */ +static s32 bme680_calc_t_fine(struct bme680_data *data, u32 adc_temp) +{ + struct bme680_calib *calib = &data->bme680; + s64 var1, var2, var3; + + /* If the calibration is invalid, attempt to reload it */ + if (!calib->par_t2) + bme680_read_calib(data, calib); + + var1 = ((s32)adc_temp >> 3) - ((s32)calib->par_t1 << 1); + var2 = (var1 * calib->par_t2) >> 11; + var3 = ((var1 >> 1) * (var1 >> 1)) >> 12; + var3 = (var3 * ((s32)calib->par_t3 << 4)) >> 14; + return var2 + var3; /* t_fine = var2 + var3 */ +} + +static int bme680_get_t_fine(struct bme680_data *data, s32 *t_fine) +{ + u32 adc_temp; + int ret; + + ret = bme680_read_temp_adc(data, &adc_temp); + if (ret) + return ret; + + *t_fine = bme680_calc_t_fine(data, adc_temp); + + return 0; +} + +static s16 bme680_compensate_temp(struct bme680_data *data, + u32 adc_temp) +{ + return (bme680_calc_t_fine(data, adc_temp) * 5 + 128) / 256; +} + +static int bme680_read_press_adc(struct bme680_data *data, u32 *adc_press) +{ + struct device *dev = regmap_get_device(data->regmap); + u32 value_press; + int ret; + + ret = regmap_bulk_read(data->regmap, BME680_REG_PRESS_MSB, + data->buf, BME680_PRESS_NUM_BYTES); + if (ret < 0) { + dev_err(dev, "failed to read pressure\n"); + return ret; + } + + value_press = FIELD_GET(BME680_MEAS_TRIM_MASK, + get_unaligned_be24(data->buf)); + if (value_press == BME680_MEAS_SKIPPED) { + /* reading was skipped */ + dev_err(dev, "reading pressure skipped\n"); + return -EINVAL; + } + *adc_press = value_press; + + return 0; +} + +/* + * Taken from Bosch BME680 API: + * https://github.com/BoschSensortec/BME680_driver/blob/63bb5336/bme680.c#L896 + * + * Returns pressure measurement in Pa. Output value of "97356" represents + * 97356 Pa = 973.56 hPa. + */ +static u32 bme680_compensate_press(struct bme680_data *data, + u32 adc_press, s32 t_fine) +{ + struct bme680_calib *calib = &data->bme680; + s32 var1, var2, var3, press_comp; + + var1 = (t_fine >> 1) - 64000; + var2 = ((((var1 >> 2) * (var1 >> 2)) >> 11) * calib->par_p6) >> 2; + var2 = var2 + (var1 * calib->par_p5 << 1); + var2 = (var2 >> 2) + ((s32)calib->par_p4 << 16); + var1 = (((((var1 >> 2) * (var1 >> 2)) >> 13) * + ((s32)calib->par_p3 << 5)) >> 3) + + ((calib->par_p2 * var1) >> 1); + var1 = var1 >> 18; + var1 = ((32768 + var1) * calib->par_p1) >> 15; + press_comp = 1048576 - adc_press; + press_comp = ((press_comp - (var2 >> 12)) * 3125); + + if (press_comp >= BME680_MAX_OVERFLOW_VAL) + press_comp = ((press_comp / (u32)var1) << 1); + else + press_comp = ((press_comp << 1) / (u32)var1); + + var1 = (calib->par_p9 * (((press_comp >> 3) * + (press_comp >> 3)) >> 13)) >> 12; + var2 = ((press_comp >> 2) * calib->par_p8) >> 13; + var3 = ((press_comp >> 8) * (press_comp >> 8) * + (press_comp >> 8) * calib->par_p10) >> 17; + + press_comp += (var1 + var2 + var3 + ((s32)calib->par_p7 << 7)) >> 4; + + return press_comp; +} + +static int bme680_read_humid_adc(struct bme680_data *data, u32 *adc_humidity) +{ + struct device *dev = regmap_get_device(data->regmap); + u32 value_humidity; + int ret; + + ret = regmap_bulk_read(data->regmap, BME680_REG_HUMIDITY_MSB, + &data->be16, BME680_HUMID_NUM_BYTES); + if (ret < 0) { + dev_err(dev, "failed to read humidity\n"); + return ret; + } + + value_humidity = be16_to_cpu(data->be16); + if (value_humidity == BME680_MEAS_SKIPPED) { + /* reading was skipped */ + dev_err(dev, "reading humidity skipped\n"); + return -EINVAL; + } + *adc_humidity = value_humidity; + + return 0; +} + +/* + * Taken from Bosch BME680 API: + * https://github.com/BoschSensortec/BME680_driver/blob/63bb5336/bme680.c#L937 + * + * Returns humidity measurement in percent, resolution is 0.001 percent. Output + * value of "43215" represents 43.215 %rH. + */ +static u32 bme680_compensate_humid(struct bme680_data *data, + u16 adc_humid, s32 t_fine) +{ + struct bme680_calib *calib = &data->bme680; + s32 var1, var2, var3, var4, var5, var6, temp_scaled, calc_hum; + + temp_scaled = (t_fine * 5 + 128) >> 8; + var1 = (adc_humid - (((s32)calib->par_h1 * 16))) - + (((temp_scaled * calib->par_h3) / 100) >> 1); + var2 = (calib->par_h2 * + (((temp_scaled * calib->par_h4) / 100) + + (((temp_scaled * ((temp_scaled * calib->par_h5) / 100)) + >> 6) / 100) + (1 << 14))) >> 10; + var3 = var1 * var2; + var4 = (s32)calib->par_h6 << 7; + var4 = (var4 + ((temp_scaled * calib->par_h7) / 100)) >> 4; + var5 = ((var3 >> 14) * (var3 >> 14)) >> 10; + var6 = (var4 * var5) >> 1; + calc_hum = (((var3 + var6) >> 10) * 1000) >> 12; + + calc_hum = clamp(calc_hum, 0, 100000); /* clamp between 0-100 %rH */ + + return calc_hum; +} + +/* + * Taken from Bosch BME680 API: + * https://github.com/BoschSensortec/BME680_driver/blob/63bb5336/bme680.c#L973 + * + * Returns gas measurement in Ohm. Output value of "82986" represent 82986 ohms. + */ +static u32 bme680_compensate_gas(struct bme680_data *data, u16 gas_res_adc, + u8 gas_range) +{ + struct bme680_calib *calib = &data->bme680; + s64 var1; + u64 var2; + s64 var3; + u32 calc_gas_res; + + /* Look up table for the possible gas range values */ + static const u32 lookup_table[16] = { + 2147483647u, 2147483647u, 2147483647u, 2147483647u, + 2147483647u, 2126008810u, 2147483647u, 2130303777u, + 2147483647u, 2147483647u, 2143188679u, 2136746228u, + 2147483647u, 2126008810u, 2147483647u, 2147483647u + }; + + var1 = ((1340LL + (5 * calib->range_sw_err)) * + (lookup_table[gas_range])) >> 16; + var2 = ((gas_res_adc << 15) - 16777216) + var1; + var3 = ((125000 << (15 - gas_range)) * var1) >> 9; + var3 += (var2 >> 1); + calc_gas_res = div64_s64(var3, (s64)var2); + + return calc_gas_res; +} + +/* + * Taken from Bosch BME680 API: + * https://github.com/BoschSensortec/BME680_driver/blob/63bb5336/bme680.c#L1002 + */ +static u8 bme680_calc_heater_res(struct bme680_data *data, u16 temp) +{ + struct bme680_calib *calib = &data->bme680; + s32 var1, var2, var3, var4, var5, heatr_res_x100; + u8 heatr_res; + + if (temp > 400) /* Cap temperature */ + temp = 400; + + var1 = (((s32)BME680_AMB_TEMP * calib->par_gh3) / 1000) * 256; + var2 = (calib->par_gh1 + 784) * (((((calib->par_gh2 + 154009) * + temp * 5) / 100) + + 3276800) / 10); + var3 = var1 + (var2 / 2); + var4 = (var3 / (calib->res_heat_range + 4)); + var5 = 131 * calib->res_heat_val + 65536; + heatr_res_x100 = ((var4 / var5) - 250) * 34; + heatr_res = DIV_ROUND_CLOSEST(heatr_res_x100, 100); + + return heatr_res; +} + +/* + * Taken from Bosch BME680 API: + * https://github.com/BoschSensortec/BME680_driver/blob/63bb5336/bme680.c#L1188 + */ +static u8 bme680_calc_heater_dur(u16 dur) +{ + u8 durval, factor = 0; + + if (dur >= 0xfc0) { + durval = 0xff; /* Max duration */ + } else { + while (dur > 0x3F) { + dur = dur / 4; + factor += 1; + } + durval = dur + (factor * 64); + } + + return durval; +} + +/* Taken from datasheet, section 5.3.3 */ +static u8 bme680_calc_heater_preheat_current(u8 curr) +{ + return 8 * curr - 1; +} + +static int bme680_set_mode(struct bme680_data *data, enum bme680_op_mode mode) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = regmap_write_bits(data->regmap, BME680_REG_CTRL_MEAS, + BME680_MODE_MASK, mode); + if (ret < 0) { + dev_err(dev, "failed to set ctrl_meas register\n"); + return ret; + } + + return ret; +} + +static u8 bme680_oversampling_to_reg(u8 val) +{ + return ilog2(val) + 1; +} + +/* + * Taken from Bosch BME680 API: + * https://github.com/boschsensortec/BME68x_SensorAPI/blob/v4.4.8/bme68x.c#L490 + */ +static int bme680_wait_for_eoc(struct bme680_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + /* + * (Sum of oversampling ratios * time per oversampling) + + * TPH measurement + gas measurement + wait transition from forced mode + * + heater duration + */ + int wait_eoc_us = ((data->oversampling_temp + data->oversampling_press + + data->oversampling_humid) * 1963) + (477 * 4) + + (477 * 5) + 1000 + (data->heater_dur * 1000); + + fsleep(wait_eoc_us); + + ret = regmap_read(data->regmap, BME680_REG_MEAS_STAT_0, &data->check); + if (ret) { + dev_err(dev, "failed to read measurement status register.\n"); + return ret; + } + if (data->check & BME680_MEAS_BIT) { + dev_err(dev, "Device measurement cycle incomplete.\n"); + return -EBUSY; + } + if (!(data->check & BME680_NEW_DATA_BIT)) { + dev_err(dev, "No new data available from the device.\n"); + return -ENODATA; + } + + return 0; +} + +static int bme680_chip_config(struct bme680_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + u8 osrs; + + osrs = FIELD_PREP(BME680_OSRS_HUMIDITY_MASK, + bme680_oversampling_to_reg(data->oversampling_humid)); + /* + * Highly recommended to set oversampling of humidity before + * temperature/pressure oversampling. + */ + ret = regmap_update_bits(data->regmap, BME680_REG_CTRL_HUMIDITY, + BME680_OSRS_HUMIDITY_MASK, osrs); + if (ret < 0) { + dev_err(dev, "failed to write ctrl_hum register\n"); + return ret; + } + + /* IIR filter settings */ + ret = regmap_update_bits(data->regmap, BME680_REG_CONFIG, + BME680_FILTER_MASK, BME680_FILTER_COEFF_VAL); + if (ret < 0) { + dev_err(dev, "failed to write config register\n"); + return ret; + } + + osrs = FIELD_PREP(BME680_OSRS_TEMP_MASK, + bme680_oversampling_to_reg(data->oversampling_temp)) | + FIELD_PREP(BME680_OSRS_PRESS_MASK, + bme680_oversampling_to_reg(data->oversampling_press)); + ret = regmap_write_bits(data->regmap, BME680_REG_CTRL_MEAS, + BME680_OSRS_TEMP_MASK | BME680_OSRS_PRESS_MASK, + osrs); + if (ret < 0) { + dev_err(dev, "failed to write ctrl_meas register\n"); + return ret; + } + + return 0; +} + +static int bme680_preheat_curr_config(struct bme680_data *data, u8 val) +{ + struct device *dev = regmap_get_device(data->regmap); + u8 heatr_curr; + int ret; + + heatr_curr = bme680_calc_heater_preheat_current(val); + ret = regmap_write(data->regmap, BME680_REG_IDAC_HEAT_0, heatr_curr); + if (ret < 0) + dev_err(dev, "failed to write idac_heat_0 register\n"); + + return ret; +} + +static int bme680_gas_config(struct bme680_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + u8 heatr_res, heatr_dur; + + ret = bme680_set_mode(data, BME680_MODE_SLEEP); + if (ret < 0) + return ret; + + heatr_res = bme680_calc_heater_res(data, data->heater_temp); + + /* set target heater temperature */ + ret = regmap_write(data->regmap, BME680_REG_RES_HEAT_0, heatr_res); + if (ret < 0) { + dev_err(dev, "failed to write res_heat_0 register\n"); + return ret; + } + + heatr_dur = bme680_calc_heater_dur(data->heater_dur); + + /* set target heating duration */ + ret = regmap_write(data->regmap, BME680_REG_GAS_WAIT_0, heatr_dur); + if (ret < 0) { + dev_err(dev, "failed to write gas_wait_0 register\n"); + return ret; + } + + ret = bme680_preheat_curr_config(data, data->preheat_curr_mA); + if (ret) + return ret; + + /* Enable the gas sensor and select heater profile set-point 0 */ + ret = regmap_update_bits(data->regmap, BME680_REG_CTRL_GAS_1, + BME680_RUN_GAS_MASK | BME680_NB_CONV_MASK, + FIELD_PREP(BME680_RUN_GAS_MASK, 1) | + FIELD_PREP(BME680_NB_CONV_MASK, 0)); + if (ret < 0) + dev_err(dev, "failed to write ctrl_gas_1 register\n"); + + return ret; +} + +static int bme680_read_temp(struct bme680_data *data, s16 *comp_temp) +{ + int ret; + u32 adc_temp; + + ret = bme680_read_temp_adc(data, &adc_temp); + if (ret) + return ret; + + *comp_temp = bme680_compensate_temp(data, adc_temp); + return 0; +} + +static int bme680_read_press(struct bme680_data *data, u32 *comp_press) +{ + int ret; + u32 adc_press; + s32 t_fine; + + ret = bme680_get_t_fine(data, &t_fine); + if (ret) + return ret; + + ret = bme680_read_press_adc(data, &adc_press); + if (ret) + return ret; + + *comp_press = bme680_compensate_press(data, adc_press, t_fine); + return 0; +} + +static int bme680_read_humid(struct bme680_data *data, u32 *comp_humidity) +{ + int ret; + u32 adc_humidity; + s32 t_fine; + + ret = bme680_get_t_fine(data, &t_fine); + if (ret) + return ret; + + ret = bme680_read_humid_adc(data, &adc_humidity); + if (ret) + return ret; + + *comp_humidity = bme680_compensate_humid(data, adc_humidity, t_fine); + return 0; +} + +static int bme680_read_gas(struct bme680_data *data, int *comp_gas_res) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + u16 adc_gas_res, gas_regs_val; + u8 gas_range; + + ret = regmap_read(data->regmap, BME680_REG_MEAS_STAT_0, &data->check); + if (data->check & BME680_GAS_MEAS_BIT) { + dev_err(dev, "gas measurement incomplete\n"); + return -EBUSY; + } + + ret = regmap_bulk_read(data->regmap, BME680_REG_GAS_MSB, + &data->be16, BME680_GAS_NUM_BYTES); + if (ret < 0) { + dev_err(dev, "failed to read gas resistance\n"); + return ret; + } + + gas_regs_val = be16_to_cpu(data->be16); + adc_gas_res = FIELD_GET(BME680_ADC_GAS_RES, gas_regs_val); + + /* + * This may occur if either the gas heating duration was insufficient + * to reach the target heater temperature or the target + * heater temperature was too high for the heater sink to + * reach. + */ + if ((gas_regs_val & BME680_GAS_STAB_BIT) == 0) { + dev_err(dev, "heater failed to reach the target temperature\n"); + return -EINVAL; + } + + gas_range = FIELD_GET(BME680_GAS_RANGE_MASK, gas_regs_val); + *comp_gas_res = bme680_compensate_gas(data, adc_gas_res, gas_range); + return 0; +} + +static int __bme680_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bme680_data *data = iio_priv(indio_dev); + int chan_val, ret; + s16 temp_chan_val; + + guard(mutex)(&data->lock); + + ret = bme680_set_mode(data, BME680_MODE_FORCED); + if (ret < 0) + return ret; + + ret = bme680_wait_for_eoc(data); + if (ret) + return ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_TEMP: + ret = bme680_read_temp(data, &temp_chan_val); + if (ret) + return ret; + + *val = temp_chan_val * 10; + return IIO_VAL_INT; + case IIO_PRESSURE: + ret = bme680_read_press(data, &chan_val); + if (ret) + return ret; + + *val = chan_val; + *val2 = 1000; + return IIO_VAL_FRACTIONAL; + case IIO_HUMIDITYRELATIVE: + ret = bme680_read_humid(data, &chan_val); + if (ret) + return ret; + + *val = chan_val; + *val2 = 1000; + return IIO_VAL_FRACTIONAL; + case IIO_RESISTANCE: + ret = bme680_read_gas(data, &chan_val); + if (ret) + return ret; + + *val = chan_val; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_TEMP: + ret = bme680_read_temp(data, &temp_chan_val); + if (ret) + return ret; + + *val = temp_chan_val; + return IIO_VAL_INT; + case IIO_PRESSURE: + ret = bme680_read_press(data, &chan_val); + if (ret) + return ret; + + *val = chan_val; + return IIO_VAL_INT; + case IIO_HUMIDITYRELATIVE: + ret = bme680_read_humid(data, &chan_val); + if (ret) + return ret; + + *val = chan_val; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + *val = 10; + return IIO_VAL_INT; + case IIO_PRESSURE: + *val = 1; + *val2 = 1000; + return IIO_VAL_FRACTIONAL; + case IIO_HUMIDITYRELATIVE: + *val = 1; + *val2 = 1000; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_TEMP: + *val = data->oversampling_temp; + return IIO_VAL_INT; + case IIO_PRESSURE: + *val = data->oversampling_press; + return IIO_VAL_INT; + case IIO_HUMIDITYRELATIVE: + *val = data->oversampling_humid; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int bme680_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bme680_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = __bme680_read_raw(indio_dev, chan, val, val2, mask); + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static bool bme680_is_valid_oversampling(int rate) +{ + return (rate > 0 && rate <= 16 && is_power_of_2(rate)); +} + +static int __bme680_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bme680_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + + if (val2 != 0) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + { + if (!bme680_is_valid_oversampling(val)) + return -EINVAL; + + switch (chan->type) { + case IIO_TEMP: + data->oversampling_temp = val; + break; + case IIO_PRESSURE: + data->oversampling_press = val; + break; + case IIO_HUMIDITYRELATIVE: + data->oversampling_humid = val; + break; + default: + return -EINVAL; + } + + return bme680_chip_config(data); + } + case IIO_CHAN_INFO_PROCESSED: + { + switch (chan->type) { + case IIO_CURRENT: + return bme680_preheat_curr_config(data, (u8)val); + default: + return -EINVAL; + } + } + default: + return -EINVAL; + } +} + +static int bme680_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bme680_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = __bme680_write_raw(indio_dev, chan, val, val2, mask); + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static const char bme680_oversampling_ratio_show[] = "1 2 4 8 16"; + +static IIO_CONST_ATTR(oversampling_ratio_available, + bme680_oversampling_ratio_show); + +static struct attribute *bme680_attributes[] = { + &iio_const_attr_oversampling_ratio_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group bme680_attribute_group = { + .attrs = bme680_attributes, +}; + +static const struct iio_info bme680_info = { + .read_raw = &bme680_read_raw, + .write_raw = &bme680_write_raw, + .attrs = &bme680_attribute_group, +}; + +static const unsigned long bme680_avail_scan_masks[] = { + BIT(BME680_GAS) | BIT(BME680_HUMID) | BIT(BME680_PRESS) | BIT(BME680_TEMP), + 0 +}; + +static irqreturn_t bme680_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bme680_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); + u32 adc_temp, adc_press, adc_humid; + u16 adc_gas_res, gas_regs_val; + u8 gas_range; + s32 t_fine; + int ret; + + guard(mutex)(&data->lock); + + ret = bme680_set_mode(data, BME680_MODE_FORCED); + if (ret < 0) + goto out; + + ret = bme680_wait_for_eoc(data); + if (ret) + goto out; + + ret = regmap_bulk_read(data->regmap, BME680_REG_MEAS_STAT_0, + data->buf, sizeof(data->buf)); + if (ret) { + dev_err(dev, "failed to burst read sensor data\n"); + goto out; + } + if (data->buf[0] & BME680_GAS_MEAS_BIT) { + dev_err(dev, "gas measurement incomplete\n"); + goto out; + } + + /* Temperature calculations */ + adc_temp = FIELD_GET(BME680_MEAS_TRIM_MASK, get_unaligned_be24(&data->buf[5])); + if (adc_temp == BME680_MEAS_SKIPPED) { + dev_err(dev, "reading temperature skipped\n"); + goto out; + } + data->scan.chan[0] = bme680_compensate_temp(data, adc_temp); + t_fine = bme680_calc_t_fine(data, adc_temp); + + /* Pressure calculations */ + adc_press = FIELD_GET(BME680_MEAS_TRIM_MASK, get_unaligned_be24(&data->buf[2])); + if (adc_press == BME680_MEAS_SKIPPED) { + dev_err(dev, "reading pressure skipped\n"); + goto out; + } + data->scan.chan[1] = bme680_compensate_press(data, adc_press, t_fine); + + /* Humidity calculations */ + adc_humid = get_unaligned_be16(&data->buf[8]); + if (adc_humid == BME680_MEAS_SKIPPED) { + dev_err(dev, "reading humidity skipped\n"); + goto out; + } + data->scan.chan[2] = bme680_compensate_humid(data, adc_humid, t_fine); + + /* Gas calculations */ + gas_regs_val = get_unaligned_be16(&data->buf[13]); + adc_gas_res = FIELD_GET(BME680_ADC_GAS_RES, gas_regs_val); + if ((gas_regs_val & BME680_GAS_STAB_BIT) == 0) { + dev_err(dev, "heater failed to reach the target temperature\n"); + goto out; + } + gas_range = FIELD_GET(BME680_GAS_RANGE_MASK, gas_regs_val); + data->scan.chan[3] = bme680_compensate_gas(data, adc_gas_res, gas_range); + + iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + iio_get_time_ns(indio_dev)); +out: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int bme680_buffer_preenable(struct iio_dev *indio_dev) +{ + struct bme680_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); + + return pm_runtime_resume_and_get(dev); +} + +static int bme680_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct bme680_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); + + pm_runtime_put_autosuspend(dev); + return 0; +} + +static const struct iio_buffer_setup_ops bme680_buffer_setup_ops = { + .preenable = bme680_buffer_preenable, + .postdisable = bme680_buffer_postdisable, +}; + +int bme680_core_probe(struct device *dev, struct regmap *regmap, + const char *name) +{ + struct iio_dev *indio_dev; + struct bme680_data *data; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + mutex_init(&data->lock); + dev_set_drvdata(dev, indio_dev); + data->regmap = regmap; + indio_dev->name = name; + indio_dev->channels = bme680_channels; + indio_dev->num_channels = ARRAY_SIZE(bme680_channels); + indio_dev->available_scan_masks = bme680_avail_scan_masks; + indio_dev->info = &bme680_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + /* default values for the sensor */ + data->oversampling_humid = 2; /* 2X oversampling rate */ + data->oversampling_press = 4; /* 4X oversampling rate */ + data->oversampling_temp = 8; /* 8X oversampling rate */ + data->heater_temp = 320; /* degree Celsius */ + data->heater_dur = 150; /* milliseconds */ + data->preheat_curr_mA = 0; + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(bme680_supply_names), + bme680_supply_names); + if (ret) + return dev_err_probe(dev, ret, + "failed to get and enable supplies.\n"); + + fsleep(BME680_STARTUP_TIME_US); + + ret = regmap_write(regmap, BME680_REG_SOFT_RESET, BME680_CMD_SOFTRESET); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to reset chip\n"); + + fsleep(BME680_STARTUP_TIME_US); + + ret = regmap_read(regmap, BME680_REG_CHIP_ID, &data->check); + if (ret < 0) + return dev_err_probe(dev, ret, "Error reading chip ID\n"); + + if (data->check != BME680_CHIP_ID_VAL) { + dev_err(dev, "Wrong chip ID, got %x expected %x\n", + data->check, BME680_CHIP_ID_VAL); + return -ENODEV; + } + + ret = bme680_read_calib(data, &data->bme680); + if (ret < 0) { + return dev_err_probe(dev, ret, + "failed to read calibration coefficients at probe\n"); + } + + ret = bme680_chip_config(data); + if (ret < 0) + return dev_err_probe(dev, ret, + "failed to set chip_config data\n"); + + ret = bme680_gas_config(data); + if (ret < 0) + return dev_err_probe(dev, ret, + "failed to set gas config data\n"); + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + bme680_trigger_handler, + &bme680_buffer_setup_ops); + if (ret) + return dev_err_probe(dev, ret, + "iio triggered buffer setup failed\n"); + + /* Enable runtime PM */ + pm_runtime_set_autosuspend_delay(dev, BME680_STARTUP_TIME_US); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_active(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS_GPL(bme680_core_probe, "IIO_BME680"); + +static int bme680_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bme680_data *data = iio_priv(indio_dev); + + return bme680_set_mode(data, BME680_MODE_SLEEP); +} + +static int bme680_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bme680_data *data = iio_priv(indio_dev); + int ret; + + ret = bme680_chip_config(data); + if (ret) + return ret; + + return bme680_gas_config(data); +} + +EXPORT_RUNTIME_DEV_PM_OPS(bme680_dev_pm_ops, bme680_runtime_suspend, + bme680_runtime_resume, NULL); + +MODULE_AUTHOR("Himanshu Jha "); +MODULE_DESCRIPTION("Bosch BME680 Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/chemical/bme680_i2c.c b/drivers/iio/chemical/bme680_i2c.c new file mode 100644 index 000000000..c34d67bcb --- /dev/null +++ b/drivers/iio/chemical/bme680_i2c.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * BME680 - I2C Driver + * + * Copyright (C) 2018 Himanshu Jha + * + * 7-Bit I2C slave address is: + * - 0x76 if SDO is pulled to GND + * - 0x77 if SDO is pulled to VDDIO + * + * Note: SDO pin cannot be left floating otherwise I2C address + * will be undefined. + */ +#include +#include +#include + +#include "bme680.h" + +static int bme680_i2c_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct regmap *regmap; + const char *name = NULL; + + regmap = devm_regmap_init_i2c(client, &bme680_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(&client->dev, PTR_ERR(regmap), + "Failed to register i2c regmap\n"); + + if (id) + name = id->name; + + return bme680_core_probe(&client->dev, regmap, name); +} + +static const struct i2c_device_id bme680_i2c_id[] = { + { .name = "bme680" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bme680_i2c_id); + +static const struct of_device_id bme680_of_i2c_match[] = { + { .compatible = "bosch,bme680", }, + { } +}; +MODULE_DEVICE_TABLE(of, bme680_of_i2c_match); + +static struct i2c_driver bme680_i2c_driver = { + .driver = { + .name = "bme680_i2c", + .of_match_table = bme680_of_i2c_match, + .pm = pm_ptr(&bme680_dev_pm_ops), + }, + .probe = bme680_i2c_probe, + .id_table = bme680_i2c_id, +}; +module_i2c_driver(bme680_i2c_driver); + +MODULE_AUTHOR("Himanshu Jha "); +MODULE_DESCRIPTION("BME680 I2C driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_BME680"); diff --git a/drivers/iio/chemical/bme680_spi.c b/drivers/iio/chemical/bme680_spi.c new file mode 100644 index 000000000..2175b9564 --- /dev/null +++ b/drivers/iio/chemical/bme680_spi.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * BME680 - SPI Driver + * + * Copyright (C) 2018 Himanshu Jha + */ +#include +#include +#include + +#include "bme680.h" + +struct bme680_spi_bus_context { + struct spi_device *spi; + u8 current_page; +}; + +/* + * In SPI mode there are only 7 address bits, a "page" register determines + * which part of the 8-bit range is active. This function looks at the address + * and writes the page selection bit if needed + */ +static int bme680_regmap_spi_select_page( + struct bme680_spi_bus_context *ctx, u8 reg) +{ + struct spi_device *spi = ctx->spi; + int ret; + u8 buf[2]; + u8 page = (reg & 0x80) ? 0 : 1; /* Page "1" is low range */ + + if (page == ctx->current_page) + return 0; + + /* + * Data sheet claims we're only allowed to change bit 4, so we must do + * a read-modify-write on each and every page select + */ + buf[0] = BME680_REG_STATUS; + ret = spi_write_then_read(spi, buf, 1, buf + 1, 1); + if (ret < 0) { + dev_err(&spi->dev, "failed to set page %u\n", page); + return ret; + } + + buf[0] = BME680_REG_STATUS; + if (page) + buf[1] |= BME680_SPI_MEM_PAGE_BIT; + else + buf[1] &= ~BME680_SPI_MEM_PAGE_BIT; + + ret = spi_write(spi, buf, 2); + if (ret < 0) { + dev_err(&spi->dev, "failed to set page %u\n", page); + return ret; + } + + ctx->current_page = page; + + return 0; +} + +static int bme680_regmap_spi_write(void *context, const void *data, + size_t count) +{ + struct bme680_spi_bus_context *ctx = context; + struct spi_device *spi = ctx->spi; + int ret; + u8 buf[2]; + + memcpy(buf, data, 2); + + ret = bme680_regmap_spi_select_page(ctx, buf[0]); + if (ret) + return ret; + + /* + * The SPI register address (= full register address without bit 7) + * and the write command (bit7 = RW = '0') + */ + buf[0] &= ~0x80; + + return spi_write(spi, buf, 2); +} + +static int bme680_regmap_spi_read(void *context, const void *reg, + size_t reg_size, void *val, size_t val_size) +{ + struct bme680_spi_bus_context *ctx = context; + struct spi_device *spi = ctx->spi; + int ret; + u8 addr = *(const u8 *)reg; + + ret = bme680_regmap_spi_select_page(ctx, addr); + if (ret) + return ret; + + addr |= 0x80; /* bit7 = RW = '1' */ + + return spi_write_then_read(spi, &addr, 1, val, val_size); +} + +static const struct regmap_bus bme680_regmap_bus = { + .write = bme680_regmap_spi_write, + .read = bme680_regmap_spi_read, + .reg_format_endian_default = REGMAP_ENDIAN_BIG, + .val_format_endian_default = REGMAP_ENDIAN_BIG, +}; + +static int bme680_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct bme680_spi_bus_context *bus_context; + struct regmap *regmap; + + bus_context = devm_kzalloc(&spi->dev, sizeof(*bus_context), GFP_KERNEL); + if (!bus_context) + return -ENOMEM; + + bus_context->spi = spi; + bus_context->current_page = 0xff; /* Undefined on warm boot */ + + regmap = devm_regmap_init(&spi->dev, &bme680_regmap_bus, + bus_context, &bme680_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(&spi->dev, PTR_ERR(regmap), + "Failed to register spi regmap\n"); + + return bme680_core_probe(&spi->dev, regmap, id->name); +} + +static const struct spi_device_id bme680_spi_id[] = { + { .name = "bme680" }, + { } +}; +MODULE_DEVICE_TABLE(spi, bme680_spi_id); + +static const struct of_device_id bme680_of_spi_match[] = { + { .compatible = "bosch,bme680", }, + { } +}; +MODULE_DEVICE_TABLE(of, bme680_of_spi_match); + +static struct spi_driver bme680_spi_driver = { + .driver = { + .name = "bme680_spi", + .of_match_table = bme680_of_spi_match, + .pm = pm_ptr(&bme680_dev_pm_ops), + }, + .probe = bme680_spi_probe, + .id_table = bme680_spi_id, +}; +module_spi_driver(bme680_spi_driver); + +MODULE_AUTHOR("Himanshu Jha "); +MODULE_DESCRIPTION("Bosch BME680 SPI driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_BME680"); diff --git a/drivers/iio/chemical/ccs811.c b/drivers/iio/chemical/ccs811.c new file mode 100644 index 000000000..d126ee12f --- /dev/null +++ b/drivers/iio/chemical/ccs811.c @@ -0,0 +1,577 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ccs811.c - Support for AMS CCS811 VOC Sensor + * + * Copyright (C) 2017 Narcisa Vasile + * + * Datasheet: ams.com/content/download/951091/2269479/CCS811_DS000459_3-00.pdf + * + * IIO driver for AMS CCS811 (I2C address 0x5A/0x5B set by ADDR Low/High) + * + * TODO: + * 1. Make the drive mode selectable form userspace + * 2. Add support for interrupts + * 3. Adjust time to wait for data to be ready based on selected operation mode + * 4. Read error register and put the information in logs + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CCS811_STATUS 0x00 +#define CCS811_MEAS_MODE 0x01 +#define CCS811_ALG_RESULT_DATA 0x02 +#define CCS811_RAW_DATA 0x03 +#define CCS811_HW_ID 0x20 +#define CCS811_HW_ID_VALUE 0x81 +#define CCS811_HW_VERSION 0x21 +#define CCS811_HW_VERSION_VALUE 0x10 +#define CCS811_HW_VERSION_MASK 0xF0 +#define CCS811_ERR 0xE0 +/* Used to transition from boot to application mode */ +#define CCS811_APP_START 0xF4 +#define CCS811_SW_RESET 0xFF + +/* Status register flags */ +#define CCS811_STATUS_ERROR BIT(0) +#define CCS811_STATUS_DATA_READY BIT(3) +#define CCS811_STATUS_APP_VALID_MASK BIT(4) +#define CCS811_STATUS_APP_VALID_LOADED BIT(4) +/* + * Value of FW_MODE bit of STATUS register describes the sensor's state: + * 0: Firmware is in boot mode, this allows new firmware to be loaded + * 1: Firmware is in application mode. CCS811 is ready to take ADC measurements + */ +#define CCS811_STATUS_FW_MODE_MASK BIT(7) +#define CCS811_STATUS_FW_MODE_APPLICATION BIT(7) + +/* Measurement modes */ +#define CCS811_MODE_IDLE 0x00 +#define CCS811_MODE_IAQ_1SEC 0x10 +#define CCS811_MODE_IAQ_10SEC 0x20 +#define CCS811_MODE_IAQ_60SEC 0x30 +#define CCS811_MODE_RAW_DATA 0x40 + +#define CCS811_MEAS_MODE_INTERRUPT BIT(3) + +#define CCS811_VOLTAGE_MASK 0x3FF + +struct ccs811_reading { + __be16 co2; + __be16 voc; + u8 status; + u8 error; + __be16 raw_data; +} __attribute__((__packed__)); + +struct ccs811_data { + struct i2c_client *client; + struct mutex lock; /* Protect readings */ + struct ccs811_reading buffer; + struct iio_trigger *drdy_trig; + struct gpio_desc *wakeup_gpio; + bool drdy_trig_on; + /* Ensures correct alignment of timestamp if present */ + struct { + s16 channels[2]; + aligned_s64 ts; + } scan; +}; + +static const struct iio_chan_spec ccs811_channels[] = { + { + .type = IIO_CURRENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = -1, + }, { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = -1, + }, { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_CO2, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, + }, + }, { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_VOC, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 1, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +/* + * The CCS811 powers-up in boot mode. A setup write to CCS811_APP_START will + * transition the sensor to application mode. + */ +static int ccs811_start_sensor_application(struct i2c_client *client) +{ + int ret; + + ret = i2c_smbus_read_byte_data(client, CCS811_STATUS); + if (ret < 0) + return ret; + + if ((ret & CCS811_STATUS_FW_MODE_APPLICATION)) + return 0; + + if ((ret & CCS811_STATUS_APP_VALID_MASK) != + CCS811_STATUS_APP_VALID_LOADED) + return -EIO; + + ret = i2c_smbus_write_byte(client, CCS811_APP_START); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(client, CCS811_STATUS); + if (ret < 0) + return ret; + + if ((ret & CCS811_STATUS_FW_MODE_MASK) != + CCS811_STATUS_FW_MODE_APPLICATION) { + dev_err(&client->dev, "Application failed to start. Sensor is still in boot mode.\n"); + return -EIO; + } + + return 0; +} + +static int ccs811_setup(struct i2c_client *client) +{ + int ret; + + ret = ccs811_start_sensor_application(client); + if (ret < 0) + return ret; + + return i2c_smbus_write_byte_data(client, CCS811_MEAS_MODE, + CCS811_MODE_IAQ_1SEC); +} + +static void ccs811_set_wakeup(struct ccs811_data *data, bool enable) +{ + if (!data->wakeup_gpio) + return; + + gpiod_set_value(data->wakeup_gpio, enable); + + if (enable) + usleep_range(50, 60); + else + usleep_range(20, 30); +} + +static int ccs811_get_measurement(struct ccs811_data *data) +{ + int ret, tries = 11; + + ccs811_set_wakeup(data, true); + + /* Maximum waiting time: 1s, as measurements are made every second */ + while (tries-- > 0) { + ret = i2c_smbus_read_byte_data(data->client, CCS811_STATUS); + if (ret < 0) + return ret; + + if ((ret & CCS811_STATUS_DATA_READY) || tries == 0) + break; + msleep(100); + } + if (!(ret & CCS811_STATUS_DATA_READY)) + return -EIO; + + ret = i2c_smbus_read_i2c_block_data(data->client, + CCS811_ALG_RESULT_DATA, 8, + (char *)&data->buffer); + ccs811_set_wakeup(data, false); + + return ret; +} + +static int ccs811_read_info_raw(struct ccs811_data *data, + struct iio_chan_spec const *chan, + int *val, int mask) +{ + int ret; + + guard(mutex)(&data->lock); + ret = ccs811_get_measurement(data); + if (ret < 0) + return ret; + + switch (chan->type) { + case IIO_VOLTAGE: + *val = be16_to_cpu(data->buffer.raw_data) & CCS811_VOLTAGE_MASK; + return IIO_VAL_INT; + case IIO_CURRENT: + *val = be16_to_cpu(data->buffer.raw_data) >> 10; + return IIO_VAL_INT; + case IIO_CONCENTRATION: + switch (chan->channel2) { + case IIO_MOD_CO2: + *val = be16_to_cpu(data->buffer.co2); + return IIO_VAL_INT; + case IIO_MOD_VOC: + *val = be16_to_cpu(data->buffer.voc); + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ccs811_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ccs811_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ccs811_read_info_raw(data, chan, val, mask); + + iio_device_release_direct(indio_dev); + + return ret; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + *val = 1; + *val2 = 612903; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CURRENT: + *val = 0; + *val2 = 1000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CONCENTRATION: + switch (chan->channel2) { + case IIO_MOD_CO2: + *val = 0; + *val2 = 100; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_MOD_VOC: + *val = 0; + *val2 = 100; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static const struct iio_info ccs811_info = { + .read_raw = ccs811_read_raw, +}; + +static int ccs811_set_trigger_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct ccs811_data *data = iio_priv(indio_dev); + int ret; + + ret = i2c_smbus_read_byte_data(data->client, CCS811_MEAS_MODE); + if (ret < 0) + return ret; + + if (state) + ret |= CCS811_MEAS_MODE_INTERRUPT; + else + ret &= ~CCS811_MEAS_MODE_INTERRUPT; + + data->drdy_trig_on = state; + + return i2c_smbus_write_byte_data(data->client, CCS811_MEAS_MODE, ret); +} + +static const struct iio_trigger_ops ccs811_trigger_ops = { + .set_trigger_state = ccs811_set_trigger_state, +}; + +static irqreturn_t ccs811_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ccs811_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + int ret; + + ret = i2c_smbus_read_i2c_block_data(client, CCS811_ALG_RESULT_DATA, + sizeof(data->scan.channels), + (u8 *)data->scan.channels); + if (ret != 4) { + dev_err(&client->dev, "cannot read sensor data\n"); + goto err; + } + + iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + iio_get_time_ns(indio_dev)); + +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static irqreturn_t ccs811_data_rdy_trigger_poll(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ccs811_data *data = iio_priv(indio_dev); + + if (data->drdy_trig_on) + iio_trigger_poll(data->drdy_trig); + + return IRQ_HANDLED; +} + +static int ccs811_reset(struct i2c_client *client) +{ + struct gpio_desc *reset_gpio; + int ret; + + reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(reset_gpio)) + return PTR_ERR(reset_gpio); + + /* Try to reset using nRESET pin if available else do SW reset */ + if (reset_gpio) { + gpiod_set_value(reset_gpio, 1); + usleep_range(20, 30); + gpiod_set_value(reset_gpio, 0); + } else { + /* + * As per the datasheet, this sequence of values needs to be + * written to the SW_RESET register for triggering the soft + * reset in the device and placing it in boot mode. + */ + static const u8 reset_seq[] = { + 0x11, 0xE5, 0x72, 0x8A, + }; + + ret = i2c_smbus_write_i2c_block_data(client, CCS811_SW_RESET, + sizeof(reset_seq), reset_seq); + if (ret < 0) { + dev_err(&client->dev, "Failed to reset sensor\n"); + return ret; + } + } + + /* tSTART delay required after reset */ + usleep_range(1000, 2000); + + return 0; +} + +static int ccs811_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct iio_dev *indio_dev; + struct ccs811_data *data; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WRITE_BYTE + | I2C_FUNC_SMBUS_BYTE_DATA + | I2C_FUNC_SMBUS_READ_I2C_BLOCK)) + return -EOPNOTSUPP; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + data->wakeup_gpio = devm_gpiod_get_optional(&client->dev, "wakeup", + GPIOD_OUT_HIGH); + if (IS_ERR(data->wakeup_gpio)) + return PTR_ERR(data->wakeup_gpio); + + ccs811_set_wakeup(data, true); + + ret = ccs811_reset(client); + if (ret) { + ccs811_set_wakeup(data, false); + return ret; + } + + /* Check hardware id (should be 0x81 for this family of devices) */ + ret = i2c_smbus_read_byte_data(client, CCS811_HW_ID); + if (ret < 0) { + ccs811_set_wakeup(data, false); + return ret; + } + + if (ret != CCS811_HW_ID_VALUE) { + dev_err(&client->dev, "hardware id doesn't match CCS81x\n"); + ccs811_set_wakeup(data, false); + return -ENODEV; + } + + ret = i2c_smbus_read_byte_data(client, CCS811_HW_VERSION); + if (ret < 0) { + ccs811_set_wakeup(data, false); + return ret; + } + + if ((ret & CCS811_HW_VERSION_MASK) != CCS811_HW_VERSION_VALUE) { + dev_err(&client->dev, "no CCS811 sensor\n"); + ccs811_set_wakeup(data, false); + return -ENODEV; + } + + ret = ccs811_setup(client); + if (ret < 0) { + ccs811_set_wakeup(data, false); + return ret; + } + + ccs811_set_wakeup(data, false); + + mutex_init(&data->lock); + + indio_dev->name = id->name; + indio_dev->info = &ccs811_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + indio_dev->channels = ccs811_channels; + indio_dev->num_channels = ARRAY_SIZE(ccs811_channels); + + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + ccs811_data_rdy_trigger_poll, + NULL, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "ccs811_irq", indio_dev); + if (ret) + goto err_poweroff; + + data->drdy_trig = devm_iio_trigger_alloc(&client->dev, + "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->drdy_trig) { + ret = -ENOMEM; + goto err_poweroff; + } + + data->drdy_trig->ops = &ccs811_trigger_ops; + iio_trigger_set_drvdata(data->drdy_trig, indio_dev); + ret = iio_trigger_register(data->drdy_trig); + if (ret) + goto err_poweroff; + + indio_dev->trig = iio_trigger_get(data->drdy_trig); + } + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + ccs811_trigger_handler, NULL); + + if (ret < 0) { + dev_err(&client->dev, "triggered buffer setup failed\n"); + goto err_trigger_unregister; + } + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "unable to register iio device\n"); + goto err_buffer_cleanup; + } + return 0; + +err_buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); +err_trigger_unregister: + if (data->drdy_trig) + iio_trigger_unregister(data->drdy_trig); +err_poweroff: + i2c_smbus_write_byte_data(client, CCS811_MEAS_MODE, CCS811_MODE_IDLE); + + return ret; +} + +static void ccs811_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct ccs811_data *data = iio_priv(indio_dev); + int ret; + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + if (data->drdy_trig) + iio_trigger_unregister(data->drdy_trig); + + ret = i2c_smbus_write_byte_data(client, CCS811_MEAS_MODE, + CCS811_MODE_IDLE); + if (ret) + dev_warn(&client->dev, "Failed to power down device (%pe)\n", + ERR_PTR(ret)); +} + +static const struct i2c_device_id ccs811_id[] = { + { .name = "ccs811" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ccs811_id); + +static const struct of_device_id ccs811_dt_ids[] = { + { .compatible = "ams,ccs811" }, + { } +}; +MODULE_DEVICE_TABLE(of, ccs811_dt_ids); + +static struct i2c_driver ccs811_driver = { + .driver = { + .name = "ccs811", + .of_match_table = ccs811_dt_ids, + }, + .probe = ccs811_probe, + .remove = ccs811_remove, + .id_table = ccs811_id, +}; +module_i2c_driver(ccs811_driver); + +MODULE_AUTHOR("Narcisa Vasile "); +MODULE_DESCRIPTION("CCS811 volatile organic compounds sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/chemical/ens160.h b/drivers/iio/chemical/ens160.h new file mode 100644 index 000000000..f9f0575ce --- /dev/null +++ b/drivers/iio/chemical/ens160.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef ENS160_H_ +#define ENS160_H_ + +int devm_ens160_core_probe(struct device *dev, struct regmap *regmap, int irq, + const char *name); + +extern const struct dev_pm_ops ens160_pm_ops; + +#endif diff --git a/drivers/iio/chemical/ens160_core.c b/drivers/iio/chemical/ens160_core.c new file mode 100644 index 000000000..7c1ffda38 --- /dev/null +++ b/drivers/iio/chemical/ens160_core.c @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ScioSense ENS160 multi-gas sensor driver + * + * Copyright (c) 2024 Gustavo Silva + * + * Datasheet: + * https://www.sciosense.com/wp-content/uploads/2023/12/ENS160-Datasheet.pdf + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "ens160.h" + +#define ENS160_PART_ID 0x160 + +#define ENS160_BOOTING_TIME_MS 10U + +#define ENS160_REG_PART_ID 0x00 + +#define ENS160_REG_OPMODE 0x10 + +#define ENS160_REG_CONFIG 0x11 +#define ENS160_REG_CONFIG_INTEN BIT(0) +#define ENS160_REG_CONFIG_INTDAT BIT(1) +#define ENS160_REG_CONFIG_INT_CFG BIT(5) + +#define ENS160_REG_MODE_DEEP_SLEEP 0x00 +#define ENS160_REG_MODE_IDLE 0x01 +#define ENS160_REG_MODE_STANDARD 0x02 +#define ENS160_REG_MODE_RESET 0xF0 + +#define ENS160_REG_COMMAND 0x12 +#define ENS160_REG_COMMAND_GET_APPVER 0x0E +#define ENS160_REG_COMMAND_CLRGPR 0xCC + +#define ENS160_REG_TEMP_IN 0x13 +#define ENS160_REG_RH_IN 0x15 +#define ENS160_REG_DEVICE_STATUS 0x20 +#define ENS160_REG_DATA_AQI 0x21 +#define ENS160_REG_DATA_TVOC 0x22 +#define ENS160_REG_DATA_ECO2 0x24 +#define ENS160_REG_DATA_T 0x30 +#define ENS160_REG_DATA_RH 0x32 +#define ENS160_REG_GPR_READ4 0x4C + +#define ENS160_STATUS_VALIDITY_FLAG GENMASK(3, 2) + +#define ENS160_STATUS_NORMAL 0x00 + +struct ens160_data { + struct regmap *regmap; + /* Protect reads from the sensor */ + struct mutex mutex; + struct { + __le16 chans[2]; + aligned_s64 timestamp; + } scan __aligned(IIO_DMA_MINALIGN); + u8 fw_version[3]; + __le16 buf; +}; + +static const struct iio_chan_spec ens160_channels[] = { + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_VOC, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .address = ENS160_REG_DATA_TVOC, + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_CO2, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .address = ENS160_REG_DATA_ECO2, + .scan_index = 1, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static int __ens160_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct ens160_data *data = iio_priv(indio_dev); + int ret; + + guard(mutex)(&data->mutex); + ret = regmap_bulk_read(data->regmap, chan->address, + &data->buf, sizeof(data->buf)); + if (ret) + return ret; + *val = le16_to_cpu(data->buf); + return IIO_VAL_INT; +} + +static int ens160_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = __ens160_read_raw(indio_dev, chan, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SCALE: + switch (chan->channel2) { + case IIO_MOD_CO2: + /* The sensor reads CO2 data as ppm */ + *val = 0; + *val2 = 100; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_MOD_VOC: + /* The sensor reads VOC data as ppb */ + *val = 0; + *val2 = 100; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ens160_set_mode(struct ens160_data *data, u8 mode) +{ + int ret; + + ret = regmap_write(data->regmap, ENS160_REG_OPMODE, mode); + if (ret) + return ret; + + msleep(ENS160_BOOTING_TIME_MS); + + return 0; +} + +static void ens160_set_idle(void *data) +{ + ens160_set_mode(data, ENS160_REG_MODE_IDLE); +} + +static int ens160_chip_init(struct ens160_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + unsigned int status; + int ret; + + ret = ens160_set_mode(data, ENS160_REG_MODE_RESET); + if (ret) + return ret; + + ret = regmap_bulk_read(data->regmap, ENS160_REG_PART_ID, &data->buf, + sizeof(data->buf)); + if (ret) + return ret; + + if (le16_to_cpu(data->buf) != ENS160_PART_ID) + return -ENODEV; + + ret = ens160_set_mode(data, ENS160_REG_MODE_IDLE); + if (ret) + return ret; + + ret = regmap_write(data->regmap, ENS160_REG_COMMAND, + ENS160_REG_COMMAND_CLRGPR); + if (ret) + return ret; + + ret = regmap_write(data->regmap, ENS160_REG_COMMAND, + ENS160_REG_COMMAND_GET_APPVER); + if (ret) + return ret; + + ret = regmap_bulk_read(data->regmap, ENS160_REG_GPR_READ4, + data->fw_version, sizeof(data->fw_version)); + if (ret) + return ret; + + dev_info(dev, "firmware version: %u.%u.%u\n", data->fw_version[2], + data->fw_version[1], data->fw_version[0]); + + ret = ens160_set_mode(data, ENS160_REG_MODE_STANDARD); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, ens160_set_idle, data); + if (ret) + return ret; + + ret = regmap_read(data->regmap, ENS160_REG_DEVICE_STATUS, &status); + if (ret) + return ret; + + if (FIELD_GET(ENS160_STATUS_VALIDITY_FLAG, status) + != ENS160_STATUS_NORMAL) + return -EINVAL; + + return 0; +} + +static const struct iio_info ens160_info = { + .read_raw = ens160_read_raw, +}; + +static int ens160_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ens160_data *data = iio_priv(indio_dev); + + return ens160_set_mode(data, ENS160_REG_MODE_DEEP_SLEEP); +} + +static int ens160_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ens160_data *data = iio_priv(indio_dev); + int ret; + + ret = ens160_set_mode(data, ENS160_REG_MODE_IDLE); + if (ret) + return ret; + + return ens160_set_mode(data, ENS160_REG_MODE_STANDARD); +} +EXPORT_NS_SIMPLE_DEV_PM_OPS(ens160_pm_ops, ens160_suspend, ens160_resume, + IIO_ENS160); + +static irqreturn_t ens160_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ens160_data *data = iio_priv(indio_dev); + int ret; + + guard(mutex)(&data->mutex); + + ret = regmap_bulk_read(data->regmap, ENS160_REG_DATA_TVOC, + data->scan.chans, sizeof(data->scan.chans)); + if (ret) + goto err; + + iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + pf->timestamp); +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ens160_set_trigger_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct ens160_data *data = iio_priv(indio_dev); + unsigned int int_bits = ENS160_REG_CONFIG_INTEN | + ENS160_REG_CONFIG_INTDAT | + ENS160_REG_CONFIG_INT_CFG; + + if (state) + return regmap_set_bits(data->regmap, ENS160_REG_CONFIG, + int_bits); + else + return regmap_clear_bits(data->regmap, ENS160_REG_CONFIG, + int_bits); +} + +static const struct iio_trigger_ops ens160_trigger_ops = { + .set_trigger_state = ens160_set_trigger_state, + .validate_device = iio_trigger_validate_own_device, +}; + +static int ens160_setup_trigger(struct iio_dev *indio_dev, int irq) +{ + struct device *dev = indio_dev->dev.parent; + struct iio_trigger *trig; + int ret; + + trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, + iio_device_id(indio_dev)); + if (!trig) + return -ENOMEM; + + trig->ops = &ens160_trigger_ops; + iio_trigger_set_drvdata(trig, indio_dev); + + ret = devm_iio_trigger_register(dev, trig); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(trig); + + ret = devm_request_irq(dev, irq, iio_trigger_generic_data_rdy_poll, + IRQF_NO_THREAD, indio_dev->name, + indio_dev->trig); + if (ret) + return ret; + + return 0; +} + +int devm_ens160_core_probe(struct device *dev, struct regmap *regmap, int irq, + const char *name) +{ + struct ens160_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->regmap = regmap; + + indio_dev->name = name; + indio_dev->info = &ens160_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ens160_channels; + indio_dev->num_channels = ARRAY_SIZE(ens160_channels); + + if (irq > 0) { + ret = ens160_setup_trigger(indio_dev, irq); + if (ret) + return dev_err_probe(dev, ret, + "failed to setup trigger\n"); + } + + ret = ens160_chip_init(data); + if (ret) + return dev_err_probe(dev, ret, "chip initialization failed\n"); + + mutex_init(&data->mutex); + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + ens160_trigger_handler, NULL); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS(devm_ens160_core_probe, "IIO_ENS160"); + +MODULE_AUTHOR("Gustavo Silva "); +MODULE_DESCRIPTION("ScioSense ENS160 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/chemical/ens160_i2c.c b/drivers/iio/chemical/ens160_i2c.c new file mode 100644 index 000000000..ab0e8dcce --- /dev/null +++ b/drivers/iio/chemical/ens160_i2c.c @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ScioSense ENS160 multi-gas sensor I2C driver + * + * Copyright (c) 2024 Gustavo Silva + * + * 7-Bit I2C slave address is: + * - 0x52 if ADDR pin LOW + * - 0x53 if ADDR pin HIGH + */ + +#include +#include +#include + +#include "ens160.h" + +static const struct regmap_config ens160_regmap_i2c_conf = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int ens160_i2c_probe(struct i2c_client *client) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, &ens160_regmap_i2c_conf); + if (IS_ERR(regmap)) + return dev_err_probe(&client->dev, PTR_ERR(regmap), + "Failed to register i2c regmap\n"); + + return devm_ens160_core_probe(&client->dev, regmap, client->irq, + "ens160"); +} + +static const struct i2c_device_id ens160_i2c_id[] = { + { .name = "ens160" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ens160_i2c_id); + +static const struct of_device_id ens160_of_i2c_match[] = { + { .compatible = "sciosense,ens160" }, + { } +}; +MODULE_DEVICE_TABLE(of, ens160_of_i2c_match); + +static struct i2c_driver ens160_i2c_driver = { + .driver = { + .name = "ens160", + .of_match_table = ens160_of_i2c_match, + .pm = pm_sleep_ptr(&ens160_pm_ops), + }, + .probe = ens160_i2c_probe, + .id_table = ens160_i2c_id, +}; +module_i2c_driver(ens160_i2c_driver); + +MODULE_AUTHOR("Gustavo Silva "); +MODULE_DESCRIPTION("ScioSense ENS160 I2C driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ENS160"); diff --git a/drivers/iio/chemical/ens160_spi.c b/drivers/iio/chemical/ens160_spi.c new file mode 100644 index 000000000..7c11d1929 --- /dev/null +++ b/drivers/iio/chemical/ens160_spi.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ScioSense ENS160 multi-gas sensor SPI driver + * + * Copyright (c) 2024 Gustavo Silva + */ + +#include +#include +#include + +#include "ens160.h" + +#define ENS160_SPI_READ BIT(0) + +static const struct regmap_config ens160_regmap_spi_conf = { + .reg_bits = 8, + .val_bits = 8, + .reg_shift = -1, + .read_flag_mask = ENS160_SPI_READ, +}; + +static int ens160_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_spi(spi, &ens160_regmap_spi_conf); + if (IS_ERR(regmap)) + return dev_err_probe(&spi->dev, PTR_ERR(regmap), + "Failed to register spi regmap\n"); + + return devm_ens160_core_probe(&spi->dev, regmap, spi->irq, "ens160"); +} + +static const struct of_device_id ens160_spi_of_match[] = { + { .compatible = "sciosense,ens160" }, + { } +}; +MODULE_DEVICE_TABLE(of, ens160_spi_of_match); + +static const struct spi_device_id ens160_spi_id[] = { + { .name = "ens160" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ens160_spi_id); + +static struct spi_driver ens160_spi_driver = { + .driver = { + .name = "ens160", + .of_match_table = ens160_spi_of_match, + .pm = pm_sleep_ptr(&ens160_pm_ops), + }, + .probe = ens160_spi_probe, + .id_table = ens160_spi_id, +}; +module_spi_driver(ens160_spi_driver); + +MODULE_AUTHOR("Gustavo Silva "); +MODULE_DESCRIPTION("ScioSense ENS160 SPI driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ENS160"); diff --git a/drivers/iio/chemical/mhz19b.c b/drivers/iio/chemical/mhz19b.c new file mode 100644 index 000000000..a793620e9 --- /dev/null +++ b/drivers/iio/chemical/mhz19b.c @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mh-z19b CO₂ sensor driver + * + * Copyright (c) 2025 Gyeyoung Baek + * + * Datasheet: + * https://www.winsen-sensor.com/d/files/infrared-gas-sensor/mh-z19b-co2-ver1_0.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Commands have following format: + * + * +------+------+-----+------+------+------+------+------+-------+ + * | 0xFF | 0x01 | cmd | arg0 | arg1 | 0x00 | 0x00 | 0x00 | cksum | + * +------+------+-----+------+------+------+------+------+-------+ + */ +#define MHZ19B_CMD_SIZE 9 + +/* ABC logic in MHZ19B means auto calibration. */ +#define MHZ19B_ABC_LOGIC_CMD 0x79 +#define MHZ19B_READ_CO2_CMD 0x86 +#define MHZ19B_SPAN_POINT_CMD 0x88 +#define MHZ19B_ZERO_POINT_CMD 0x87 + +#define MHZ19B_SPAN_POINT_PPM_MIN 1000 +#define MHZ19B_SPAN_POINT_PPM_MAX 5000 + +#define MHZ19B_SERDEV_TIMEOUT msecs_to_jiffies(100) + +struct mhz19b_state { + struct serdev_device *serdev; + + /* Must wait until the 'buf' is filled with 9 bytes.*/ + struct completion buf_ready; + + u8 buf_idx; + bool buf_overflow; + + /* + * Serdev receive buffer. + * When data is received from the MH-Z19B, + * the 'mhz19b_receive_buf' callback function is called and fills this buffer. + */ + u8 buf[MHZ19B_CMD_SIZE] __aligned(IIO_DMA_MINALIGN); +}; + +static u8 mhz19b_get_checksum(u8 *cmd_buf) +{ + u8 i, checksum = 0; + +/* + * +------+------+-----+------+------+------+------+------+-------+ + * | 0xFF | 0x01 | cmd | arg0 | arg1 | 0x00 | 0x00 | 0x00 | cksum | + * +------+------+-----+------+------+------+------+------+-------+ + * i:1 2 3 4 5 6 7 + * + * Sum all cmd_buf elements from index 1 to 7. + */ + for (i = 1; i < 8; i++) + checksum += cmd_buf[i]; + + return -checksum; +} + +static int mhz19b_serdev_cmd(struct iio_dev *indio_dev, int cmd, u16 arg) +{ + struct mhz19b_state *st = iio_priv(indio_dev); + struct serdev_device *serdev = st->serdev; + struct device *dev = &indio_dev->dev; + int ret; + + /* + * cmd_buf[3,4] : arg0,1 + * cmd_buf[8] : checksum + */ + u8 cmd_buf[MHZ19B_CMD_SIZE] = { + 0xFF, 0x01, cmd, + }; + + switch (cmd) { + case MHZ19B_ABC_LOGIC_CMD: + cmd_buf[3] = arg ? 0xA0 : 0; + break; + case MHZ19B_SPAN_POINT_CMD: + put_unaligned_be16(arg, &cmd_buf[3]); + break; + default: + break; + } + cmd_buf[8] = mhz19b_get_checksum(cmd_buf); + + /* Write buf to uart ctrl synchronously */ + st->buf_idx = 0; + st->buf_overflow = false; + reinit_completion(&st->buf_ready); + + ret = serdev_device_write(serdev, cmd_buf, MHZ19B_CMD_SIZE, 0); + if (ret < 0) + return ret; + if (ret != MHZ19B_CMD_SIZE) + return -EIO; + + switch (cmd) { + case MHZ19B_READ_CO2_CMD: + ret = wait_for_completion_interruptible_timeout(&st->buf_ready, + MHZ19B_SERDEV_TIMEOUT); + if (ret < 0) + return ret; + if (!ret) + return -ETIMEDOUT; + + if (st->buf_overflow) + return -EMSGSIZE; + + if (st->buf[8] != mhz19b_get_checksum(st->buf)) { + dev_err(dev, "checksum err"); + return -EINVAL; + } + + return get_unaligned_be16(&st->buf[2]); + default: + /* No response commands. */ + return 0; + } +} + +static int mhz19b_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + + ret = mhz19b_serdev_cmd(indio_dev, MHZ19B_READ_CO2_CMD, 0); + if (ret < 0) + return ret; + + *val = ret; + return IIO_VAL_INT; +} + +/* + * echo 0 > calibration_auto_enable : ABC logic off + * echo 1 > calibration_auto_enable : ABC logic on + */ +static ssize_t calibration_auto_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + bool enable; + int ret; + + ret = kstrtobool(buf, &enable); + if (ret) + return ret; + + ret = mhz19b_serdev_cmd(indio_dev, MHZ19B_ABC_LOGIC_CMD, enable); + if (ret < 0) + return ret; + + return len; +} +static IIO_DEVICE_ATTR_WO(calibration_auto_enable, 0); + +/* + * echo 0 > calibration_forced_value : zero point calibration + * (make sure the sensor has been working under 400ppm for over 20 minutes.) + * echo [1000 1 5000] > calibration_forced_value : span point calibration + * (make sure the sensor has been working under a certain level CO₂ for over 20 minutes.) + */ +static ssize_t calibration_forced_value_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + u16 ppm; + int cmd, ret; + + ret = kstrtou16(buf, 0, &ppm); + if (ret) + return ret; + + if (ppm) { + if (!in_range(ppm, MHZ19B_SPAN_POINT_PPM_MIN, + MHZ19B_SPAN_POINT_PPM_MAX - MHZ19B_SPAN_POINT_PPM_MIN + 1)) { + dev_dbg(&indio_dev->dev, + "span point ppm should be in a range [%d-%d]\n", + MHZ19B_SPAN_POINT_PPM_MIN, MHZ19B_SPAN_POINT_PPM_MAX); + return -EINVAL; + } + + cmd = MHZ19B_SPAN_POINT_CMD; + } else { + cmd = MHZ19B_ZERO_POINT_CMD; + } + + ret = mhz19b_serdev_cmd(indio_dev, cmd, ppm); + if (ret < 0) + return ret; + + return len; +} +static IIO_DEVICE_ATTR_WO(calibration_forced_value, 0); + +static struct attribute *mhz19b_attrs[] = { + &iio_dev_attr_calibration_auto_enable.dev_attr.attr, + &iio_dev_attr_calibration_forced_value.dev_attr.attr, + NULL +}; + +static const struct attribute_group mhz19b_attr_group = { + .attrs = mhz19b_attrs, +}; + +static const struct iio_info mhz19b_info = { + .attrs = &mhz19b_attr_group, + .read_raw = mhz19b_read_raw, +}; + +static const struct iio_chan_spec mhz19b_channels[] = { + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_CO2, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, +}; + +static size_t mhz19b_receive_buf(struct serdev_device *serdev, + const u8 *data, size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(&serdev->dev); + struct mhz19b_state *st = iio_priv(indio_dev); + size_t remaining = MHZ19B_CMD_SIZE - st->buf_idx; + + if (len > remaining) { + st->buf_idx = 0; + st->buf_overflow = true; + complete(&st->buf_ready); + return len; + } + + memcpy(st->buf + st->buf_idx, data, len); + st->buf_idx += len; + + if (st->buf_idx == MHZ19B_CMD_SIZE) { + st->buf_idx = 0; + complete(&st->buf_ready); + } + + return len; +} + +static const struct serdev_device_ops mhz19b_ops = { + .receive_buf = mhz19b_receive_buf, + .write_wakeup = serdev_device_write_wakeup, +}; + +static int mhz19b_probe(struct serdev_device *serdev) +{ + int ret; + struct device *dev = &serdev->dev; + struct iio_dev *indio_dev; + struct mhz19b_state *st; + + serdev_device_set_client_ops(serdev, &mhz19b_ops); + ret = devm_serdev_device_open(dev, serdev); + if (ret) + return ret; + serdev_device_set_baudrate(serdev, 9600); + serdev_device_set_flow_control(serdev, false); + ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); + if (ret) + return ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + serdev_device_set_drvdata(serdev, indio_dev); + + st = iio_priv(indio_dev); + st->serdev = serdev; + + init_completion(&st->buf_ready); + + ret = devm_regulator_get_enable(dev, "vin"); + if (ret) + return ret; + + indio_dev->name = "mh-z19b"; + indio_dev->channels = mhz19b_channels; + indio_dev->num_channels = ARRAY_SIZE(mhz19b_channels); + indio_dev->info = &mhz19b_info; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id mhz19b_of_match[] = { + { .compatible = "winsen,mhz19b", }, + { } +}; +MODULE_DEVICE_TABLE(of, mhz19b_of_match); + +static struct serdev_device_driver mhz19b_driver = { + .driver = { + .name = "mhz19b", + .of_match_table = mhz19b_of_match, + }, + .probe = mhz19b_probe, +}; +module_serdev_device_driver(mhz19b_driver); + +MODULE_AUTHOR("Gyeyoung Baek"); +MODULE_DESCRIPTION("MH-Z19B CO2 sensor driver using serdev interface"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/chemical/pms7003.c b/drivers/iio/chemical/pms7003.c new file mode 100644 index 000000000..c50edb24a --- /dev/null +++ b/drivers/iio/chemical/pms7003.c @@ -0,0 +1,351 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Plantower PMS7003 particulate matter sensor driver + * + * Copyright (c) Tomasz Duszynski + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PMS7003_DRIVER_NAME "pms7003" + +#define PMS7003_MAGIC 0x424d +/* last 2 data bytes hold frame checksum */ +#define PMS7003_MAX_DATA_LENGTH 28 +#define PMS7003_CHECKSUM_LENGTH 2 +#define PMS7003_PM10_OFFSET 10 +#define PMS7003_PM2P5_OFFSET 8 +#define PMS7003_PM1_OFFSET 6 + +#define PMS7003_TIMEOUT msecs_to_jiffies(6000) +#define PMS7003_CMD_LENGTH 7 +#define PMS7003_PM_MAX 1000 +#define PMS7003_PM_MIN 0 + +enum { + PM1, + PM2P5, + PM10, +}; + +enum pms7003_cmd { + CMD_WAKEUP, + CMD_ENTER_PASSIVE_MODE, + CMD_READ_PASSIVE, + CMD_SLEEP, +}; + +/* + * commands have following format: + * + * +------+------+-----+------+-----+-----------+-----------+ + * | 0x42 | 0x4d | cmd | 0x00 | arg | cksum msb | cksum lsb | + * +------+------+-----+------+-----+-----------+-----------+ + */ +static const u8 pms7003_cmd_tbl[][PMS7003_CMD_LENGTH] = { + [CMD_WAKEUP] = { 0x42, 0x4d, 0xe4, 0x00, 0x01, 0x01, 0x74 }, + [CMD_ENTER_PASSIVE_MODE] = { 0x42, 0x4d, 0xe1, 0x00, 0x00, 0x01, 0x70 }, + [CMD_READ_PASSIVE] = { 0x42, 0x4d, 0xe2, 0x00, 0x00, 0x01, 0x71 }, + [CMD_SLEEP] = { 0x42, 0x4d, 0xe4, 0x00, 0x00, 0x01, 0x73 }, +}; + +struct pms7003_frame { + u8 data[PMS7003_MAX_DATA_LENGTH]; + u16 expected_length; + u16 length; +}; + +struct pms7003_state { + struct serdev_device *serdev; + struct pms7003_frame frame; + struct completion frame_ready; + struct mutex lock; /* must be held whenever state gets touched */ + /* Used to construct scan to push to the IIO buffer */ + struct { + u16 data[3]; /* PM1, PM2P5, PM10 */ + aligned_s64 ts; + } scan; +}; + +static int pms7003_do_cmd(struct pms7003_state *state, enum pms7003_cmd cmd) +{ + int ret; + + ret = serdev_device_write(state->serdev, pms7003_cmd_tbl[cmd], + PMS7003_CMD_LENGTH, PMS7003_TIMEOUT); + if (ret < PMS7003_CMD_LENGTH) + return ret < 0 ? ret : -EIO; + + ret = wait_for_completion_interruptible_timeout(&state->frame_ready, + PMS7003_TIMEOUT); + if (!ret) + ret = -ETIMEDOUT; + + return ret < 0 ? ret : 0; +} + +static u16 pms7003_get_pm(const u8 *data) +{ + return clamp_val(get_unaligned_be16(data), + PMS7003_PM_MIN, PMS7003_PM_MAX); +} + +static irqreturn_t pms7003_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct pms7003_state *state = iio_priv(indio_dev); + struct pms7003_frame *frame = &state->frame; + int ret; + + mutex_lock(&state->lock); + ret = pms7003_do_cmd(state, CMD_READ_PASSIVE); + if (ret) { + mutex_unlock(&state->lock); + goto err; + } + + state->scan.data[PM1] = + pms7003_get_pm(frame->data + PMS7003_PM1_OFFSET); + state->scan.data[PM2P5] = + pms7003_get_pm(frame->data + PMS7003_PM2P5_OFFSET); + state->scan.data[PM10] = + pms7003_get_pm(frame->data + PMS7003_PM10_OFFSET); + mutex_unlock(&state->lock); + + iio_push_to_buffers_with_ts(indio_dev, &state->scan, sizeof(state->scan), + iio_get_time_ns(indio_dev)); +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int pms7003_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct pms7003_state *state = iio_priv(indio_dev); + struct pms7003_frame *frame = &state->frame; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_MASSCONCENTRATION: + mutex_lock(&state->lock); + ret = pms7003_do_cmd(state, CMD_READ_PASSIVE); + if (ret) { + mutex_unlock(&state->lock); + return ret; + } + + *val = pms7003_get_pm(frame->data + chan->address); + mutex_unlock(&state->lock); + + return IIO_VAL_INT; + default: + return -EINVAL; + } + } + + return -EINVAL; +} + +static const struct iio_info pms7003_info = { + .read_raw = pms7003_read_raw, +}; + +#define PMS7003_CHAN(_index, _mod, _addr) { \ + .type = IIO_MASSCONCENTRATION, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## _mod, \ + .address = _addr, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 10, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ +} + +static const struct iio_chan_spec pms7003_channels[] = { + PMS7003_CHAN(0, PM1, PMS7003_PM1_OFFSET), + PMS7003_CHAN(1, PM2P5, PMS7003_PM2P5_OFFSET), + PMS7003_CHAN(2, PM10, PMS7003_PM10_OFFSET), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static u16 pms7003_calc_checksum(struct pms7003_frame *frame) +{ + u16 checksum = (PMS7003_MAGIC >> 8) + (u8)(PMS7003_MAGIC & 0xff) + + (frame->length >> 8) + (u8)frame->length; + int i; + + for (i = 0; i < frame->length - PMS7003_CHECKSUM_LENGTH; i++) + checksum += frame->data[i]; + + return checksum; +} + +static bool pms7003_frame_is_okay(struct pms7003_frame *frame) +{ + int offset = frame->length - PMS7003_CHECKSUM_LENGTH; + u16 checksum = get_unaligned_be16(frame->data + offset); + + return checksum == pms7003_calc_checksum(frame); +} + +static size_t pms7003_receive_buf(struct serdev_device *serdev, const u8 *buf, + size_t size) +{ + struct iio_dev *indio_dev = serdev_device_get_drvdata(serdev); + struct pms7003_state *state = iio_priv(indio_dev); + struct pms7003_frame *frame = &state->frame; + size_t num; + + if (!frame->expected_length) { + u16 magic; + + /* wait for SOF and data length */ + if (size < 4) + return 0; + + magic = get_unaligned_be16(buf); + if (magic != PMS7003_MAGIC) + return 2; + + num = get_unaligned_be16(buf + 2); + if (num <= PMS7003_MAX_DATA_LENGTH) { + frame->expected_length = num; + frame->length = 0; + } + + return 4; + } + + num = min(size, (size_t)(frame->expected_length - frame->length)); + memcpy(frame->data + frame->length, buf, num); + frame->length += num; + + if (frame->length == frame->expected_length) { + if (pms7003_frame_is_okay(frame)) + complete(&state->frame_ready); + + frame->expected_length = 0; + } + + return num; +} + +static const struct serdev_device_ops pms7003_serdev_ops = { + .receive_buf = pms7003_receive_buf, + .write_wakeup = serdev_device_write_wakeup, +}; + +static void pms7003_stop(void *data) +{ + struct pms7003_state *state = data; + + pms7003_do_cmd(state, CMD_SLEEP); +} + +static const unsigned long pms7003_scan_masks[] = { 0x07, 0x00 }; + +static int pms7003_probe(struct serdev_device *serdev) +{ + struct pms7003_state *state; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&serdev->dev, sizeof(*state)); + if (!indio_dev) + return -ENOMEM; + + state = iio_priv(indio_dev); + serdev_device_set_drvdata(serdev, indio_dev); + state->serdev = serdev; + indio_dev->info = &pms7003_info; + indio_dev->name = PMS7003_DRIVER_NAME; + indio_dev->channels = pms7003_channels; + indio_dev->num_channels = ARRAY_SIZE(pms7003_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->available_scan_masks = pms7003_scan_masks; + + mutex_init(&state->lock); + init_completion(&state->frame_ready); + + serdev_device_set_client_ops(serdev, &pms7003_serdev_ops); + ret = devm_serdev_device_open(&serdev->dev, serdev); + if (ret) + return ret; + + serdev_device_set_baudrate(serdev, 9600); + serdev_device_set_flow_control(serdev, false); + + ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); + if (ret) + return ret; + + ret = pms7003_do_cmd(state, CMD_WAKEUP); + if (ret) { + dev_err(&serdev->dev, "failed to wakeup sensor\n"); + return ret; + } + + ret = pms7003_do_cmd(state, CMD_ENTER_PASSIVE_MODE); + if (ret) { + dev_err(&serdev->dev, "failed to enter passive mode\n"); + return ret; + } + + ret = devm_add_action_or_reset(&serdev->dev, pms7003_stop, state); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(&serdev->dev, indio_dev, NULL, + pms7003_trigger_handler, NULL); + if (ret) + return ret; + + return devm_iio_device_register(&serdev->dev, indio_dev); +} + +static const struct of_device_id pms7003_of_match[] = { + { .compatible = "plantower,pms1003" }, + { .compatible = "plantower,pms3003" }, + { .compatible = "plantower,pms5003" }, + { .compatible = "plantower,pms6003" }, + { .compatible = "plantower,pms7003" }, + { .compatible = "plantower,pmsa003" }, + { } +}; +MODULE_DEVICE_TABLE(of, pms7003_of_match); + +static struct serdev_device_driver pms7003_driver = { + .driver = { + .name = PMS7003_DRIVER_NAME, + .of_match_table = pms7003_of_match, + }, + .probe = pms7003_probe, +}; +module_serdev_device_driver(pms7003_driver); + +MODULE_AUTHOR("Tomasz Duszynski "); +MODULE_DESCRIPTION("Plantower PMS7003 particulate matter sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/chemical/scd30.h b/drivers/iio/chemical/scd30.h new file mode 100644 index 000000000..1ac9f3f79 --- /dev/null +++ b/drivers/iio/chemical/scd30.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _SCD30_H +#define _SCD30_H + +#include +#include +#include +#include +#include +#include + +struct scd30_state; + +enum scd30_cmd { + /* start continuous measurement with pressure compensation */ + CMD_START_MEAS, + /* stop continuous measurement */ + CMD_STOP_MEAS, + /* set/get measurement interval */ + CMD_MEAS_INTERVAL, + /* check whether new measurement is ready */ + CMD_MEAS_READY, + /* get measurement */ + CMD_READ_MEAS, + /* turn on/off automatic self calibration */ + CMD_ASC, + /* set/get forced recalibration value */ + CMD_FRC, + /* set/get temperature offset */ + CMD_TEMP_OFFSET, + /* get firmware version */ + CMD_FW_VERSION, + /* reset sensor */ + CMD_RESET, + /* + * Command for altitude compensation was omitted intentionally because + * the same can be achieved by means of CMD_START_MEAS which takes + * pressure above the sea level as an argument. + */ +}; + +#define SCD30_MEAS_COUNT 3 + +typedef int (*scd30_command_t)(struct scd30_state *state, enum scd30_cmd cmd, u16 arg, + void *response, int size); + +struct scd30_state { + /* serialize access to the device */ + struct mutex lock; + struct device *dev; + struct regulator *vdd; + struct completion meas_ready; + /* + * priv pointer is solely for serdev driver private data. We keep it + * here because driver_data inside dev has been already used for iio and + * struct serdev_device doesn't have one. + */ + void *priv; + int irq; + /* + * no way to retrieve current ambient pressure compensation value from + * the sensor so keep one around + */ + u16 pressure_comp; + u16 meas_interval; + int meas[SCD30_MEAS_COUNT]; + + scd30_command_t command; +}; + +extern const struct dev_pm_ops scd30_pm_ops; + +int scd30_probe(struct device *dev, int irq, const char *name, void *priv, scd30_command_t command); + +#endif diff --git a/drivers/iio/chemical/scd30_core.c b/drivers/iio/chemical/scd30_core.c new file mode 100644 index 000000000..770571c21 --- /dev/null +++ b/drivers/iio/chemical/scd30_core.c @@ -0,0 +1,779 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Sensirion SCD30 carbon dioxide sensor core driver + * + * Copyright (c) 2020 Tomasz Duszynski + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "scd30.h" + +#define SCD30_PRESSURE_COMP_MIN_MBAR 700 +#define SCD30_PRESSURE_COMP_MAX_MBAR 1400 +#define SCD30_PRESSURE_COMP_DEFAULT 1013 +#define SCD30_MEAS_INTERVAL_MIN_S 2 +#define SCD30_MEAS_INTERVAL_MAX_S 1800 +#define SCD30_MEAS_INTERVAL_DEFAULT SCD30_MEAS_INTERVAL_MIN_S +#define SCD30_FRC_MIN_PPM 400 +#define SCD30_FRC_MAX_PPM 2000 +#define SCD30_TEMP_OFFSET_MAX 655360 +#define SCD30_EXTRA_TIMEOUT_PER_S 250 + +/* Floating point arithmetic macros */ +#define SCD30_FLOAT_MANTISSA_MSK GENMASK(22, 0) +#define SCD30_FLOAT_EXP_MSK GENMASK(30, 23) +#define SCD30_FLOAT_SIGN_MSK BIT(31) + +enum { + SCD30_CONC, + SCD30_TEMP, + SCD30_HR, +}; + +static int scd30_command_write(struct scd30_state *state, enum scd30_cmd cmd, u16 arg) +{ + return state->command(state, cmd, arg, NULL, 0); +} + +static int scd30_command_read(struct scd30_state *state, enum scd30_cmd cmd, u16 *val) +{ + __be16 tmp; + int ret; + + ret = state->command(state, cmd, 0, &tmp, sizeof(tmp)); + *val = be16_to_cpup(&tmp); + + return ret; +} + +static int scd30_reset(struct scd30_state *state) +{ + int ret; + u16 val; + + ret = scd30_command_write(state, CMD_RESET, 0); + if (ret) + return ret; + + /* sensor boots up within 2 secs */ + msleep(2000); + /* + * Power-on-reset causes sensor to produce some glitch on i2c bus and + * some controllers end up in error state. Try to recover by placing + * any data on the bus. + */ + scd30_command_read(state, CMD_MEAS_READY, &val); + + return 0; +} + +/* simplified float to fixed point conversion with a scaling factor of 0.01 */ +static int scd30_float_to_fp(int float32) +{ + int fraction, shift, sign; + int mantissa = FIELD_GET(SCD30_FLOAT_MANTISSA_MSK, float32); + int exp = FIELD_GET(SCD30_FLOAT_EXP_MSK, float32); + + if (float32 & SCD30_FLOAT_SIGN_MSK) + sign = -1; + else + sign = 1; + + /* special case 0 */ + if (!exp && !mantissa) + return 0; + + exp -= 127; + if (exp < 0) { + exp = -exp; + /* return values ranging from 1 to 99 */ + return sign * ((((BIT(23) + mantissa) * 100) >> 23) >> exp); + } + + /* return values starting at 100 */ + shift = 23 - exp; + float32 = BIT(exp) + (mantissa >> shift); + fraction = mantissa & GENMASK(shift - 1, 0); + + return sign * (float32 * 100 + ((fraction * 100) >> shift)); +} + +static int scd30_read_meas(struct scd30_state *state) +{ + int i, ret; + + ret = state->command(state, CMD_READ_MEAS, 0, state->meas, sizeof(state->meas)); + if (ret) + return ret; + + be32_to_cpu_array(state->meas, (__be32 *)state->meas, ARRAY_SIZE(state->meas)); + + for (i = 0; i < ARRAY_SIZE(state->meas); i++) + state->meas[i] = scd30_float_to_fp(state->meas[i]); + + /* + * co2 is left unprocessed while temperature and humidity are scaled + * to milli deg C and milli percent respectively. + */ + state->meas[SCD30_TEMP] *= 10; + state->meas[SCD30_HR] *= 10; + + return 0; +} + +static int scd30_wait_meas_irq(struct scd30_state *state) +{ + int ret, timeout; + + reinit_completion(&state->meas_ready); + enable_irq(state->irq); + timeout = msecs_to_jiffies(state->meas_interval * (1000 + SCD30_EXTRA_TIMEOUT_PER_S)); + ret = wait_for_completion_interruptible_timeout(&state->meas_ready, timeout); + if (ret > 0) + ret = 0; + else if (!ret) + ret = -ETIMEDOUT; + + disable_irq(state->irq); + + return ret; +} + +static int scd30_wait_meas_poll(struct scd30_state *state) +{ + int timeout = state->meas_interval * SCD30_EXTRA_TIMEOUT_PER_S, tries = 5; + + do { + int ret; + u16 val; + + ret = scd30_command_read(state, CMD_MEAS_READY, &val); + if (ret) + return -EIO; + + /* new measurement available */ + if (val) + break; + + msleep_interruptible(timeout); + } while (--tries); + + return tries ? 0 : -ETIMEDOUT; +} + +static int scd30_read_poll(struct scd30_state *state) +{ + int ret; + + ret = scd30_wait_meas_poll(state); + if (ret) + return ret; + + return scd30_read_meas(state); +} + +static int scd30_read(struct scd30_state *state) +{ + if (state->irq > 0) + return scd30_wait_meas_irq(state); + + return scd30_read_poll(state); +} + +static int scd30_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct scd30_state *state = iio_priv(indio_dev); + int ret; + u16 tmp; + + guard(mutex)(&state->lock); + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + if (chan->output) { + *val = state->pressure_comp; + return IIO_VAL_INT; + } + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = scd30_read(state); + if (ret) { + iio_device_release_direct(indio_dev); + return ret; + } + + *val = state->meas[chan->address]; + iio_device_release_direct(indio_dev); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = 1; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = scd30_command_read(state, CMD_MEAS_INTERVAL, &tmp); + if (ret) + return ret; + + *val = 0; + *val2 = 1000000000 / tmp; + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_CALIBBIAS: + ret = scd30_command_read(state, CMD_TEMP_OFFSET, &tmp); + if (ret) + return ret; + + *val = tmp; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int scd30_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct scd30_state *state = iio_priv(indio_dev); + int ret; + + guard(mutex)(&state->lock); + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (val || !val2) + return -EINVAL; + + val = 1000000000 / val2; + if (val < SCD30_MEAS_INTERVAL_MIN_S || val > SCD30_MEAS_INTERVAL_MAX_S) + return -EINVAL; + + ret = scd30_command_write(state, CMD_MEAS_INTERVAL, val); + if (ret) + return ret; + + state->meas_interval = val; + return 0; + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_PRESSURE: + if (val < SCD30_PRESSURE_COMP_MIN_MBAR || + val > SCD30_PRESSURE_COMP_MAX_MBAR) + return -EINVAL; + + ret = scd30_command_write(state, CMD_START_MEAS, val); + if (ret) + return ret; + + state->pressure_comp = val; + return 0; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + if (val < 0 || val > SCD30_TEMP_OFFSET_MAX) + return -EINVAL; + /* + * Manufacturer does not explicitly specify min/max sensible + * values hence check is omitted for simplicity. + */ + return scd30_command_write(state, CMD_TEMP_OFFSET / 10, val); + default: + return -EINVAL; + } +} + +static int scd30_write_raw_get_fmt(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_CALIBBIAS: + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static const int scd30_pressure_raw_available[] = { + SCD30_PRESSURE_COMP_MIN_MBAR, 1, SCD30_PRESSURE_COMP_MAX_MBAR, +}; + +static const int scd30_temp_calibbias_available[] = { + 0, 10, SCD30_TEMP_OFFSET_MAX, +}; + +static int scd30_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_RAW: + *vals = scd30_pressure_raw_available; + *type = IIO_VAL_INT; + + return IIO_AVAIL_RANGE; + case IIO_CHAN_INFO_CALIBBIAS: + *vals = scd30_temp_calibbias_available; + *type = IIO_VAL_INT; + + return IIO_AVAIL_RANGE; + } + + return -EINVAL; +} + +static ssize_t sampling_frequency_available_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int i = SCD30_MEAS_INTERVAL_MIN_S; + ssize_t len = 0; + + do { + len += sysfs_emit_at(buf, len, "0.%09u ", 1000000000 / i); + /* + * Not all values fit PAGE_SIZE buffer hence print every 6th + * (each frequency differs by 6s in time domain from the + * adjacent). Unlisted but valid ones are still accepted. + */ + i += 6; + } while (i <= SCD30_MEAS_INTERVAL_MAX_S); + + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t calibration_auto_enable_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct scd30_state *state = iio_priv(indio_dev); + int ret; + u16 val; + + guard(mutex)(&state->lock); + + ret = scd30_command_read(state, CMD_ASC, &val); + if (ret) + return ret; + + return sysfs_emit(buf, "%d\n", val); +} + +static ssize_t calibration_auto_enable_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct scd30_state *state = iio_priv(indio_dev); + bool val; + int ret; + + ret = kstrtobool(buf, &val); + if (ret) + return ret; + + guard(mutex)(&state->lock); + + ret = scd30_command_write(state, CMD_ASC, val); + if (ret) + return ret; + + return len; +} + +static ssize_t calibration_forced_value_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct scd30_state *state = iio_priv(indio_dev); + int ret; + u16 val; + + guard(mutex)(&state->lock); + + ret = scd30_command_read(state, CMD_FRC, &val); + if (ret) + return ret; + + return sysfs_emit(buf, "%d\n", val); +} + +static ssize_t calibration_forced_value_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct scd30_state *state = iio_priv(indio_dev); + int ret; + u16 val; + + ret = kstrtou16(buf, 0, &val); + if (ret) + return ret; + + if (val < SCD30_FRC_MIN_PPM || val > SCD30_FRC_MAX_PPM) + return -EINVAL; + + guard(mutex)(&state->lock); + + ret = scd30_command_write(state, CMD_FRC, val); + if (ret) + return ret; + + return len; +} + +static IIO_DEVICE_ATTR_RO(sampling_frequency_available, 0); +static IIO_DEVICE_ATTR_RW(calibration_auto_enable, 0); +static IIO_DEVICE_ATTR_RW(calibration_forced_value, 0); + +static struct attribute *scd30_attrs[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_calibration_auto_enable.dev_attr.attr, + &iio_dev_attr_calibration_forced_value.dev_attr.attr, + NULL +}; + +static const struct attribute_group scd30_attr_group = { + .attrs = scd30_attrs, +}; + +static const struct iio_info scd30_info = { + .attrs = &scd30_attr_group, + .read_raw = scd30_read_raw, + .write_raw = scd30_write_raw, + .write_raw_get_fmt = scd30_write_raw_get_fmt, + .read_avail = scd30_read_avail, +}; + +#define SCD30_CHAN_SCAN_TYPE(_sign, _realbits) .scan_type = { \ + .sign = _sign, \ + .realbits = _realbits, \ + .storagebits = 32, \ + .endianness = IIO_CPU, \ +} + +static const struct iio_chan_spec scd30_channels[] = { + { + /* + * this channel is special in a sense we are pretending that + * sensor is able to change measurement chamber pressure but in + * fact we're just setting pressure compensation value + */ + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW), + .output = 1, + .scan_index = -1, + }, + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_CO2, + .address = SCD30_CONC, + .scan_index = SCD30_CONC, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .modified = 1, + + SCD30_CHAN_SCAN_TYPE('u', 20), + }, + { + .type = IIO_TEMP, + .address = SCD30_TEMP, + .scan_index = SCD30_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + + SCD30_CHAN_SCAN_TYPE('s', 18), + }, + { + .type = IIO_HUMIDITYRELATIVE, + .address = SCD30_HR, + .scan_index = SCD30_HR, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + + SCD30_CHAN_SCAN_TYPE('u', 17), + }, + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static int scd30_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct scd30_state *state = iio_priv(indio_dev); + int ret; + + ret = scd30_command_write(state, CMD_STOP_MEAS, 0); + if (ret) + return ret; + + return regulator_disable(state->vdd); +} + +static int scd30_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct scd30_state *state = iio_priv(indio_dev); + int ret; + + ret = regulator_enable(state->vdd); + if (ret) + return ret; + + return scd30_command_write(state, CMD_START_MEAS, state->pressure_comp); +} + +EXPORT_NS_SIMPLE_DEV_PM_OPS(scd30_pm_ops, scd30_suspend, scd30_resume, IIO_SCD30); + +static void scd30_stop_meas(void *data) +{ + struct scd30_state *state = data; + + scd30_command_write(state, CMD_STOP_MEAS, 0); +} + +static void scd30_disable_regulator(void *data) +{ + struct scd30_state *state = data; + + regulator_disable(state->vdd); +} + +static irqreturn_t scd30_irq_handler(int irq, void *priv) +{ + struct iio_dev *indio_dev = priv; + + if (iio_buffer_enabled(indio_dev)) { + iio_trigger_poll(indio_dev->trig); + + return IRQ_HANDLED; + } + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t scd30_irq_thread_handler(int irq, void *priv) +{ + struct iio_dev *indio_dev = priv; + struct scd30_state *state = iio_priv(indio_dev); + int ret; + + ret = scd30_read_meas(state); + if (ret) + goto out; + + complete_all(&state->meas_ready); +out: + return IRQ_HANDLED; +} + +static int scd30_trigger_handler_helper(struct iio_dev *indio_dev, int *scan_data, + size_t scan_data_size) +{ + struct scd30_state *state = iio_priv(indio_dev); + int ret; + + guard(mutex)(&state->lock); + + if (!iio_trigger_using_own(indio_dev)) + ret = scd30_read_poll(state); + else + ret = scd30_read_meas(state); + memcpy(scan_data, state->meas, scan_data_size); + + return ret; +} + +static irqreturn_t scd30_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct { + int data[SCD30_MEAS_COUNT]; + aligned_s64 ts; + } scan = { }; + int ret; + + ret = scd30_trigger_handler_helper(indio_dev, scan.data, sizeof(scan.data)); + if (ret) + goto out; + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); +out: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int scd30_set_trigger_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct scd30_state *st = iio_priv(indio_dev); + + if (state) + enable_irq(st->irq); + else + disable_irq(st->irq); + + return 0; +} + +static const struct iio_trigger_ops scd30_trigger_ops = { + .set_trigger_state = scd30_set_trigger_state, + .validate_device = iio_trigger_validate_own_device, +}; + +static int scd30_setup_trigger(struct iio_dev *indio_dev) +{ + struct scd30_state *state = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + struct iio_trigger *trig; + int ret; + + trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, + iio_device_id(indio_dev)); + if (!trig) + return -ENOMEM; + + trig->ops = &scd30_trigger_ops; + iio_trigger_set_drvdata(trig, indio_dev); + + ret = devm_iio_trigger_register(dev, trig); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(trig); + + /* + * Interrupt is enabled just before taking a fresh measurement + * and disabled afterwards. This means we need to ensure it is not + * enabled here to keep calls to enable/disable balanced. + */ + ret = devm_request_threaded_irq(dev, state->irq, scd30_irq_handler, + scd30_irq_thread_handler, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT | + IRQF_NO_AUTOEN, + indio_dev->name, indio_dev); + if (ret) + return ret; + + return 0; +} + +int scd30_probe(struct device *dev, int irq, const char *name, void *priv, + scd30_command_t command) +{ + static const unsigned long scd30_scan_masks[] = { 0x07, 0x00 }; + struct scd30_state *state; + struct iio_dev *indio_dev; + int ret; + u16 val; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*state)); + if (!indio_dev) + return -ENOMEM; + + state = iio_priv(indio_dev); + state->dev = dev; + state->priv = priv; + state->irq = irq; + state->pressure_comp = SCD30_PRESSURE_COMP_DEFAULT; + state->meas_interval = SCD30_MEAS_INTERVAL_DEFAULT; + state->command = command; + + ret = devm_mutex_init(dev, &state->lock); + if (ret) + return ret; + + init_completion(&state->meas_ready); + + dev_set_drvdata(dev, indio_dev); + + indio_dev->info = &scd30_info; + indio_dev->name = name; + indio_dev->channels = scd30_channels; + indio_dev->num_channels = ARRAY_SIZE(scd30_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->available_scan_masks = scd30_scan_masks; + + state->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(state->vdd)) + return dev_err_probe(dev, PTR_ERR(state->vdd), "failed to get regulator\n"); + + ret = regulator_enable(state->vdd); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, scd30_disable_regulator, state); + if (ret) + return ret; + + ret = scd30_reset(state); + if (ret) + return dev_err_probe(dev, ret, "failed to reset device\n"); + + if (state->irq > 0) { + ret = scd30_setup_trigger(indio_dev); + if (ret) + return dev_err_probe(dev, ret, "failed to setup trigger\n"); + } + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, scd30_trigger_handler, NULL); + if (ret) + return ret; + + ret = scd30_command_read(state, CMD_FW_VERSION, &val); + if (ret) + return dev_err_probe(dev, ret, "failed to read firmware version\n"); + dev_info(dev, "firmware version: %d.%d\n", val >> 8, (char)val); + + ret = scd30_command_write(state, CMD_MEAS_INTERVAL, state->meas_interval); + if (ret) + return dev_err_probe(dev, ret, "failed to set measurement interval\n"); + + ret = scd30_command_write(state, CMD_START_MEAS, state->pressure_comp); + if (ret) + return dev_err_probe(dev, ret, "failed to start measurement\n"); + + ret = devm_add_action_or_reset(dev, scd30_stop_meas, state); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS(scd30_probe, "IIO_SCD30"); + +MODULE_AUTHOR("Tomasz Duszynski "); +MODULE_DESCRIPTION("Sensirion SCD30 carbon dioxide sensor core driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/chemical/scd30_i2c.c b/drivers/iio/chemical/scd30_i2c.c new file mode 100644 index 000000000..bcef1e473 --- /dev/null +++ b/drivers/iio/chemical/scd30_i2c.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Sensirion SCD30 carbon dioxide sensor i2c driver + * + * Copyright (c) 2020 Tomasz Duszynski + * + * I2C slave address: 0x61 + */ +#include +#include +#include +#include +#include +#include +#include + +#include "scd30.h" + +#define SCD30_I2C_MAX_BUF_SIZE 18 +#define SCD30_I2C_CRC8_POLYNOMIAL 0x31 + +static const u16 scd30_i2c_cmd_lookup_tbl[] = { + [CMD_START_MEAS] = 0x0010, + [CMD_STOP_MEAS] = 0x0104, + [CMD_MEAS_INTERVAL] = 0x4600, + [CMD_MEAS_READY] = 0x0202, + [CMD_READ_MEAS] = 0x0300, + [CMD_ASC] = 0x5306, + [CMD_FRC] = 0x5204, + [CMD_TEMP_OFFSET] = 0x5403, + [CMD_FW_VERSION] = 0xd100, + [CMD_RESET] = 0xd304, +}; + +DECLARE_CRC8_TABLE(scd30_i2c_crc8_tbl); + +static int scd30_i2c_xfer(struct scd30_state *state, char *txbuf, int txsize, + char *rxbuf, int rxsize) +{ + struct i2c_client *client = to_i2c_client(state->dev); + int ret; + + /* + * repeated start is not supported hence instead of sending two i2c + * messages in a row we send one by one + */ + ret = i2c_master_send(client, txbuf, txsize); + if (ret < 0) + return ret; + if (ret != txsize) + return -EIO; + + if (!rxbuf) + return 0; + + ret = i2c_master_recv(client, rxbuf, rxsize); + if (ret < 0) + return ret; + if (ret != rxsize) + return -EIO; + + return 0; +} + +static int scd30_i2c_command(struct scd30_state *state, enum scd30_cmd cmd, u16 arg, + void *response, int size) +{ + char buf[SCD30_I2C_MAX_BUF_SIZE]; + char *rsp = response; + int i, ret; + char crc; + + if (!response && size != 0) + return -EINVAL; + + put_unaligned_be16(scd30_i2c_cmd_lookup_tbl[cmd], buf); + i = 2; + + if (rsp) { + /* each two bytes are followed by a crc8 */ + size += size / 2; + } else { + put_unaligned_be16(arg, buf + i); + crc = crc8(scd30_i2c_crc8_tbl, buf + i, 2, CRC8_INIT_VALUE); + i += 2; + buf[i] = crc; + i += 1; + + /* commands below don't take an argument */ + if ((cmd == CMD_STOP_MEAS) || (cmd == CMD_RESET)) + i -= 3; + } + + ret = scd30_i2c_xfer(state, buf, i, buf, size); + if (ret) + return ret; + + /* validate received data and strip off crc bytes */ + for (i = 0; i < size; i += 3) { + crc = crc8(scd30_i2c_crc8_tbl, buf + i, 2, CRC8_INIT_VALUE); + if (crc != buf[i + 2]) { + dev_err(state->dev, "data integrity check failed\n"); + return -EIO; + } + + *rsp++ = buf[i]; + *rsp++ = buf[i + 1]; + } + + return 0; +} + +static int scd30_i2c_probe(struct i2c_client *client) +{ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -EOPNOTSUPP; + + crc8_populate_msb(scd30_i2c_crc8_tbl, SCD30_I2C_CRC8_POLYNOMIAL); + + return scd30_probe(&client->dev, client->irq, client->name, NULL, scd30_i2c_command); +} + +static const struct of_device_id scd30_i2c_of_match[] = { + { .compatible = "sensirion,scd30" }, + { } +}; +MODULE_DEVICE_TABLE(of, scd30_i2c_of_match); + +static struct i2c_driver scd30_i2c_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = scd30_i2c_of_match, + .pm = pm_sleep_ptr(&scd30_pm_ops), + }, + .probe = scd30_i2c_probe, +}; +module_i2c_driver(scd30_i2c_driver); + +MODULE_AUTHOR("Tomasz Duszynski "); +MODULE_DESCRIPTION("Sensirion SCD30 carbon dioxide sensor i2c driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_SCD30"); diff --git a/drivers/iio/chemical/scd30_serial.c b/drivers/iio/chemical/scd30_serial.c new file mode 100644 index 000000000..31b0fc957 --- /dev/null +++ b/drivers/iio/chemical/scd30_serial.c @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Sensirion SCD30 carbon dioxide sensor serial driver + * + * Copyright (c) 2020 Tomasz Duszynski + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "scd30.h" + +#define SCD30_SERDEV_ADDR 0x61 +#define SCD30_SERDEV_WRITE 0x06 +#define SCD30_SERDEV_READ 0x03 +#define SCD30_SERDEV_MAX_BUF_SIZE 17 +#define SCD30_SERDEV_RX_HEADER_SIZE 3 +#define SCD30_SERDEV_CRC_SIZE 2 +#define SCD30_SERDEV_TIMEOUT msecs_to_jiffies(200) + +struct scd30_serdev_priv { + struct completion meas_ready; + char *buf; + int num_expected; + int num; +}; + +static u16 scd30_serdev_cmd_lookup_tbl[] = { + [CMD_START_MEAS] = 0x0036, + [CMD_STOP_MEAS] = 0x0037, + [CMD_MEAS_INTERVAL] = 0x0025, + [CMD_MEAS_READY] = 0x0027, + [CMD_READ_MEAS] = 0x0028, + [CMD_ASC] = 0x003a, + [CMD_FRC] = 0x0039, + [CMD_TEMP_OFFSET] = 0x003b, + [CMD_FW_VERSION] = 0x0020, + [CMD_RESET] = 0x0034, +}; + +static u16 scd30_serdev_calc_crc(const char *buf, int size) +{ + return crc16(0xffff, buf, size); +} + +static int scd30_serdev_xfer(struct scd30_state *state, char *txbuf, int txsize, + char *rxbuf, int rxsize) +{ + struct serdev_device *serdev = to_serdev_device(state->dev); + struct scd30_serdev_priv *priv = state->priv; + int ret; + + priv->buf = rxbuf; + priv->num_expected = rxsize; + priv->num = 0; + + ret = serdev_device_write(serdev, txbuf, txsize, SCD30_SERDEV_TIMEOUT); + if (ret < 0) + return ret; + if (ret != txsize) + return -EIO; + + ret = wait_for_completion_interruptible_timeout(&priv->meas_ready, SCD30_SERDEV_TIMEOUT); + if (ret < 0) + return ret; + if (!ret) + return -ETIMEDOUT; + + return 0; +} + +static int scd30_serdev_command(struct scd30_state *state, enum scd30_cmd cmd, u16 arg, + void *response, int size) +{ + /* + * Communication over serial line is based on modbus protocol (or rather + * its variation called modbus over serial to be precise). Upon + * receiving a request device should reply with response. + * + * Frame below represents a request message. Each field takes + * exactly one byte. + * + * +------+------+-----+-----+-------+-------+-----+-----+ + * | dev | op | reg | reg | byte1 | byte0 | crc | crc | + * | addr | code | msb | lsb | | | lsb | msb | + * +------+------+-----+-----+-------+-------+-----+-----+ + * + * The message device replies with depends on the 'op code' field from + * the request. In case it was set to SCD30_SERDEV_WRITE sensor should + * reply with unchanged request. Otherwise 'op code' was set to + * SCD30_SERDEV_READ and response looks like the one below. As with + * request, each field takes one byte. + * + * +------+------+--------+-------+-----+-------+-----+-----+ + * | dev | op | num of | byte0 | ... | byteN | crc | crc | + * | addr | code | bytes | | | | lsb | msb | + * +------+------+--------+-------+-----+-------+-----+-----+ + */ + char txbuf[SCD30_SERDEV_MAX_BUF_SIZE] = { SCD30_SERDEV_ADDR }, + rxbuf[SCD30_SERDEV_MAX_BUF_SIZE]; + int ret, rxsize, txsize = 2; + char *rsp = response; + u16 crc; + + put_unaligned_be16(scd30_serdev_cmd_lookup_tbl[cmd], txbuf + txsize); + txsize += 2; + + if (rsp) { + txbuf[1] = SCD30_SERDEV_READ; + if (cmd == CMD_READ_MEAS) + /* number of u16 words to read */ + put_unaligned_be16(size / 2, txbuf + txsize); + else + put_unaligned_be16(0x0001, txbuf + txsize); + txsize += 2; + crc = scd30_serdev_calc_crc(txbuf, txsize); + put_unaligned_le16(crc, txbuf + txsize); + txsize += 2; + rxsize = SCD30_SERDEV_RX_HEADER_SIZE + size + SCD30_SERDEV_CRC_SIZE; + } else { + if ((cmd == CMD_STOP_MEAS) || (cmd == CMD_RESET)) + arg = 0x0001; + + txbuf[1] = SCD30_SERDEV_WRITE; + put_unaligned_be16(arg, txbuf + txsize); + txsize += 2; + crc = scd30_serdev_calc_crc(txbuf, txsize); + put_unaligned_le16(crc, txbuf + txsize); + txsize += 2; + rxsize = txsize; + } + + ret = scd30_serdev_xfer(state, txbuf, txsize, rxbuf, rxsize); + if (ret) + return ret; + + switch (txbuf[1]) { + case SCD30_SERDEV_WRITE: + if (memcmp(txbuf, rxbuf, txsize)) { + dev_err(state->dev, "wrong message received\n"); + return -EIO; + } + break; + case SCD30_SERDEV_READ: + if (rxbuf[2] != (rxsize - SCD30_SERDEV_RX_HEADER_SIZE - SCD30_SERDEV_CRC_SIZE)) { + dev_err(state->dev, "received data size does not match header\n"); + return -EIO; + } + + rxsize -= SCD30_SERDEV_CRC_SIZE; + crc = get_unaligned_le16(rxbuf + rxsize); + if (crc != scd30_serdev_calc_crc(rxbuf, rxsize)) { + dev_err(state->dev, "data integrity check failed\n"); + return -EIO; + } + + rxsize -= SCD30_SERDEV_RX_HEADER_SIZE; + memcpy(rsp, rxbuf + SCD30_SERDEV_RX_HEADER_SIZE, rxsize); + break; + default: + dev_err(state->dev, "received unknown op code\n"); + return -EIO; + } + + return 0; +} + +static size_t scd30_serdev_receive_buf(struct serdev_device *serdev, + const u8 *buf, size_t size) +{ + struct iio_dev *indio_dev = serdev_device_get_drvdata(serdev); + struct scd30_serdev_priv *priv; + struct scd30_state *state; + size_t num; + + if (!indio_dev) + return 0; + + state = iio_priv(indio_dev); + priv = state->priv; + + /* just in case sensor puts some unexpected bytes on the bus */ + if (!priv->buf) + return 0; + + if (priv->num + size >= priv->num_expected) + num = priv->num_expected - priv->num; + else + num = size; + + memcpy(priv->buf + priv->num, buf, num); + priv->num += num; + + if (priv->num == priv->num_expected) { + priv->buf = NULL; + complete(&priv->meas_ready); + } + + return num; +} + +static const struct serdev_device_ops scd30_serdev_ops = { + .receive_buf = scd30_serdev_receive_buf, + .write_wakeup = serdev_device_write_wakeup, +}; + +static int scd30_serdev_probe(struct serdev_device *serdev) +{ + struct device *dev = &serdev->dev; + struct scd30_serdev_priv *priv; + int irq, ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + init_completion(&priv->meas_ready); + serdev_device_set_client_ops(serdev, &scd30_serdev_ops); + + ret = devm_serdev_device_open(dev, serdev); + if (ret) + return ret; + + serdev_device_set_baudrate(serdev, 19200); + serdev_device_set_flow_control(serdev, false); + + ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); + if (ret) + return ret; + + irq = fwnode_irq_get(dev_fwnode(dev), 0); + + return scd30_probe(dev, irq, KBUILD_MODNAME, priv, scd30_serdev_command); +} + +static const struct of_device_id scd30_serdev_of_match[] = { + { .compatible = "sensirion,scd30" }, + { } +}; +MODULE_DEVICE_TABLE(of, scd30_serdev_of_match); + +static struct serdev_device_driver scd30_serdev_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = scd30_serdev_of_match, + .pm = pm_sleep_ptr(&scd30_pm_ops), + }, + .probe = scd30_serdev_probe, +}; +module_serdev_device_driver(scd30_serdev_driver); + +MODULE_AUTHOR("Tomasz Duszynski "); +MODULE_DESCRIPTION("Sensirion SCD30 carbon dioxide sensor serial driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_SCD30"); diff --git a/drivers/iio/chemical/scd4x.c b/drivers/iio/chemical/scd4x.c new file mode 100644 index 000000000..23a326fb6 --- /dev/null +++ b/drivers/iio/chemical/scd4x.c @@ -0,0 +1,768 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Sensirion SCD4X carbon dioxide sensor i2c driver + * + * Copyright (C) 2021 Protonic Holland + * Author: Roan van Dijk + * + * I2C slave address: 0x62 + * + * Datasheets: + * https://www.sensirion.com/file/datasheet_scd4x + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SCD4X_CRC8_POLYNOMIAL 0x31 +#define SCD4X_TIMEOUT_ERR 1000 +#define SCD4X_READ_BUF_SIZE 9 +#define SCD4X_COMMAND_BUF_SIZE 2 +#define SCD4X_WRITE_BUF_SIZE 5 +#define SCD4X_FRC_MIN_PPM 0 +#define SCD4X_FRC_MAX_PPM 2000 +#define SCD4X_PRESSURE_COMP_MIN_MBAR 700 +#define SCD4X_PRESSURE_COMP_MAX_MBAR 1200 +#define SCD4X_READY_MASK 0x01 + +/*Commands SCD4X*/ +enum scd4x_cmd { + CMD_START_MEAS = 0x21b1, + CMD_READ_MEAS = 0xec05, + CMD_STOP_MEAS = 0x3f86, + CMD_SET_TEMP_OFFSET = 0x241d, + CMD_GET_TEMP_OFFSET = 0x2318, + CMD_SET_AMB_PRESSURE = 0xe000, + CMD_GET_AMB_PRESSURE = 0xe000, + CMD_FRC = 0x362f, + CMD_SET_ASC = 0x2416, + CMD_GET_ASC = 0x2313, + CMD_GET_DATA_READY = 0xe4b8, +}; + +enum scd4x_channel_idx { + SCD4X_CO2, + SCD4X_TEMP, + SCD4X_HR, + /* kernel timestamp, at the end of buffer */ + SCD4X_TS, +}; + +struct scd4x_state { + struct i2c_client *client; + /* maintain access to device, to prevent concurrent reads/writes */ + struct mutex lock; + struct regulator *vdd; +}; + +DECLARE_CRC8_TABLE(scd4x_crc8_table); + +static int scd4x_i2c_xfer(struct scd4x_state *state, char *txbuf, int txsize, + char *rxbuf, int rxsize) +{ + struct i2c_client *client = state->client; + int ret; + + ret = i2c_master_send(client, txbuf, txsize); + + if (ret < 0) + return ret; + if (ret != txsize) + return -EIO; + + if (rxsize == 0) + return 0; + + ret = i2c_master_recv(client, rxbuf, rxsize); + if (ret < 0) + return ret; + if (ret != rxsize) + return -EIO; + + return 0; +} + +static int scd4x_send_command(struct scd4x_state *state, enum scd4x_cmd cmd) +{ + char buf[SCD4X_COMMAND_BUF_SIZE]; + int ret; + + /* + * Measurement needs to be stopped before sending commands. + * Except stop and start command. + */ + if ((cmd != CMD_STOP_MEAS) && (cmd != CMD_START_MEAS)) { + + ret = scd4x_send_command(state, CMD_STOP_MEAS); + if (ret) + return ret; + + /* execution time for stopping measurement */ + msleep_interruptible(500); + } + + put_unaligned_be16(cmd, buf); + ret = scd4x_i2c_xfer(state, buf, 2, buf, 0); + if (ret) + return ret; + + if ((cmd != CMD_STOP_MEAS) && (cmd != CMD_START_MEAS)) { + ret = scd4x_send_command(state, CMD_START_MEAS); + if (ret) + return ret; + } + + return 0; +} + +static int scd4x_read(struct scd4x_state *state, enum scd4x_cmd cmd, + void *response, int response_sz) +{ + struct i2c_client *client = state->client; + char buf[SCD4X_READ_BUF_SIZE]; + char *rsp = response; + int i, ret; + char crc; + + /* + * Measurement needs to be stopped before sending commands. + * Except for reading measurement and data ready command. + */ + if ((cmd != CMD_GET_DATA_READY) && (cmd != CMD_READ_MEAS) && + (cmd != CMD_GET_AMB_PRESSURE)) { + ret = scd4x_send_command(state, CMD_STOP_MEAS); + if (ret) + return ret; + + /* execution time for stopping measurement */ + msleep_interruptible(500); + } + + /* CRC byte for every 2 bytes of data */ + response_sz += response_sz / 2; + + put_unaligned_be16(cmd, buf); + ret = scd4x_i2c_xfer(state, buf, 2, buf, response_sz); + if (ret) + return ret; + + for (i = 0; i < response_sz; i += 3) { + crc = crc8(scd4x_crc8_table, buf + i, 2, CRC8_INIT_VALUE); + if (crc != buf[i + 2]) { + dev_err(&client->dev, "CRC error\n"); + return -EIO; + } + + *rsp++ = buf[i]; + *rsp++ = buf[i + 1]; + } + + /* start measurement */ + if ((cmd != CMD_GET_DATA_READY) && (cmd != CMD_READ_MEAS) && + (cmd != CMD_GET_AMB_PRESSURE)) { + ret = scd4x_send_command(state, CMD_START_MEAS); + if (ret) + return ret; + } + + return 0; +} + +static int scd4x_write(struct scd4x_state *state, enum scd4x_cmd cmd, uint16_t arg) +{ + char buf[SCD4X_WRITE_BUF_SIZE]; + int ret; + char crc; + + put_unaligned_be16(cmd, buf); + put_unaligned_be16(arg, buf + 2); + + crc = crc8(scd4x_crc8_table, buf + 2, 2, CRC8_INIT_VALUE); + buf[4] = crc; + + /* measurement needs to be stopped before sending commands */ + if (cmd != CMD_SET_AMB_PRESSURE) { + ret = scd4x_send_command(state, CMD_STOP_MEAS); + if (ret) + return ret; + } + + /* execution time */ + msleep_interruptible(500); + + ret = scd4x_i2c_xfer(state, buf, SCD4X_WRITE_BUF_SIZE, buf, 0); + if (ret) + return ret; + + /* start measurement, except for forced calibration command */ + if ((cmd != CMD_FRC) && (cmd != CMD_SET_AMB_PRESSURE)) { + ret = scd4x_send_command(state, CMD_START_MEAS); + if (ret) + return ret; + } + + return 0; +} + +static int scd4x_write_and_fetch(struct scd4x_state *state, enum scd4x_cmd cmd, + uint16_t arg, void *response, int response_sz) +{ + struct i2c_client *client = state->client; + char buf[SCD4X_READ_BUF_SIZE]; + char *rsp = response; + int i, ret; + char crc; + + ret = scd4x_write(state, CMD_FRC, arg); + if (ret) + goto err; + + /* execution time */ + msleep_interruptible(400); + + /* CRC byte for every 2 bytes of data */ + response_sz += response_sz / 2; + + ret = i2c_master_recv(client, buf, response_sz); + if (ret < 0) + goto err; + if (ret != response_sz) { + ret = -EIO; + goto err; + } + + for (i = 0; i < response_sz; i += 3) { + crc = crc8(scd4x_crc8_table, buf + i, 2, CRC8_INIT_VALUE); + if (crc != buf[i + 2]) { + dev_err(&client->dev, "CRC error\n"); + ret = -EIO; + goto err; + } + + *rsp++ = buf[i]; + *rsp++ = buf[i + 1]; + } + + return scd4x_send_command(state, CMD_START_MEAS); + +err: + /* + * on error try to start the measurement, + * puts sensor back into continuous measurement + */ + scd4x_send_command(state, CMD_START_MEAS); + + return ret; +} + +static int scd4x_read_meas(struct scd4x_state *state, uint16_t *meas) +{ + int i, ret; + __be16 buf[3]; + + ret = scd4x_read(state, CMD_READ_MEAS, buf, sizeof(buf)); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(buf); i++) + meas[i] = be16_to_cpu(buf[i]); + + return 0; +} + +static int scd4x_wait_meas_poll(struct scd4x_state *state) +{ + struct i2c_client *client = state->client; + int tries = 6; + int ret; + + do { + __be16 bval; + uint16_t val; + + ret = scd4x_read(state, CMD_GET_DATA_READY, &bval, sizeof(bval)); + if (ret) + return -EIO; + val = be16_to_cpu(bval); + + /* new measurement available */ + if (val & 0x7FF) + return 0; + + msleep_interruptible(1000); + } while (--tries); + + /* try to start sensor on timeout */ + ret = scd4x_send_command(state, CMD_START_MEAS); + if (ret) + dev_err(&client->dev, "failed to start measurement: %d\n", ret); + + return -ETIMEDOUT; +} + +static int scd4x_read_poll(struct scd4x_state *state, uint16_t *buf) +{ + int ret; + + ret = scd4x_wait_meas_poll(state); + if (ret) + return ret; + + return scd4x_read_meas(state, buf); +} + +static int scd4x_read_channel(struct scd4x_state *state, int chan) +{ + int ret; + uint16_t buf[3]; + + ret = scd4x_read_poll(state, buf); + if (ret) + return ret; + + return buf[chan]; +} + +static int scd4x_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct scd4x_state *state = iio_priv(indio_dev); + int ret; + __be16 tmp; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->output) { + mutex_lock(&state->lock); + ret = scd4x_read(state, CMD_GET_AMB_PRESSURE, &tmp, sizeof(tmp)); + mutex_unlock(&state->lock); + + if (ret) + return ret; + + *val = be16_to_cpu(tmp); + return IIO_VAL_INT; + } + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + mutex_lock(&state->lock); + ret = scd4x_read_channel(state, chan->address); + mutex_unlock(&state->lock); + + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_CONCENTRATION) { + *val = 0; + *val2 = 100; + return IIO_VAL_INT_PLUS_MICRO; + } else if (chan->type == IIO_TEMP) { + *val = 175000; + *val2 = 65536; + return IIO_VAL_FRACTIONAL; + } else if (chan->type == IIO_HUMIDITYRELATIVE) { + *val = 100000; + *val2 = 65536; + return IIO_VAL_FRACTIONAL; + } + return -EINVAL; + case IIO_CHAN_INFO_OFFSET: + *val = -16852; + *val2 = 114286; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBBIAS: + mutex_lock(&state->lock); + ret = scd4x_read(state, CMD_GET_TEMP_OFFSET, &tmp, sizeof(tmp)); + mutex_unlock(&state->lock); + if (ret) + return ret; + + *val = be16_to_cpu(tmp); + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static const int scd4x_pressure_calibbias_available[] = { + SCD4X_PRESSURE_COMP_MIN_MBAR, 1, SCD4X_PRESSURE_COMP_MAX_MBAR, +}; + +static int scd4x_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_RAW: + *vals = scd4x_pressure_calibbias_available; + *type = IIO_VAL_INT; + + return IIO_AVAIL_RANGE; + } + + return -EINVAL; +} + + +static int scd4x_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct scd4x_state *state = iio_priv(indio_dev); + int ret = 0; + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + mutex_lock(&state->lock); + ret = scd4x_write(state, CMD_SET_TEMP_OFFSET, val); + mutex_unlock(&state->lock); + + return ret; + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_PRESSURE: + if (val < SCD4X_PRESSURE_COMP_MIN_MBAR || + val > SCD4X_PRESSURE_COMP_MAX_MBAR) + return -EINVAL; + + mutex_lock(&state->lock); + ret = scd4x_write(state, CMD_SET_AMB_PRESSURE, val); + mutex_unlock(&state->lock); + + return ret; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static ssize_t calibration_auto_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct scd4x_state *state = iio_priv(indio_dev); + int ret; + __be16 bval; + u16 val; + + mutex_lock(&state->lock); + ret = scd4x_read(state, CMD_GET_ASC, &bval, sizeof(bval)); + mutex_unlock(&state->lock); + if (ret) { + dev_err(dev, "failed to read automatic calibration"); + return ret; + } + + val = (be16_to_cpu(bval) & SCD4X_READY_MASK) ? 1 : 0; + + return sysfs_emit(buf, "%d\n", val); +} + +static ssize_t calibration_auto_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct scd4x_state *state = iio_priv(indio_dev); + bool val; + int ret; + uint16_t value; + + ret = kstrtobool(buf, &val); + if (ret) + return ret; + + value = val; + + mutex_lock(&state->lock); + ret = scd4x_write(state, CMD_SET_ASC, value); + mutex_unlock(&state->lock); + if (ret) + dev_err(dev, "failed to set automatic calibration"); + + return ret ?: len; +} + +static ssize_t calibration_forced_value_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct scd4x_state *state = iio_priv(indio_dev); + uint16_t val, arg; + int ret; + + ret = kstrtou16(buf, 0, &arg); + if (ret) + return ret; + + if (arg < SCD4X_FRC_MIN_PPM || arg > SCD4X_FRC_MAX_PPM) + return -EINVAL; + + mutex_lock(&state->lock); + ret = scd4x_write_and_fetch(state, CMD_FRC, arg, &val, sizeof(val)); + mutex_unlock(&state->lock); + + if (ret) + return ret; + + if (val == 0xff) { + dev_err(dev, "forced calibration has failed"); + return -EINVAL; + } + + return len; +} + +static IIO_DEVICE_ATTR_RW(calibration_auto_enable, 0); +static IIO_DEVICE_ATTR_WO(calibration_forced_value, 0); + +static IIO_CONST_ATTR(calibration_forced_value_available, + __stringify([SCD4X_FRC_MIN_PPM 1 SCD4X_FRC_MAX_PPM])); + +static struct attribute *scd4x_attrs[] = { + &iio_dev_attr_calibration_auto_enable.dev_attr.attr, + &iio_dev_attr_calibration_forced_value.dev_attr.attr, + &iio_const_attr_calibration_forced_value_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group scd4x_attr_group = { + .attrs = scd4x_attrs, +}; + +static const struct iio_info scd4x_info = { + .attrs = &scd4x_attr_group, + .read_raw = scd4x_read_raw, + .write_raw = scd4x_write_raw, + .read_avail = scd4x_read_avail, +}; + +static const struct iio_chan_spec scd4x_channels[] = { + { + /* + * this channel is special in a sense we are pretending that + * sensor is able to change measurement chamber pressure but in + * fact we're just setting pressure compensation value + */ + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW), + .output = 1, + .scan_index = -1, + }, + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_CO2, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .address = SCD4X_CO2, + .scan_index = SCD4X_CO2, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .address = SCD4X_TEMP, + .scan_index = SCD4X_TEMP, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_HUMIDITYRELATIVE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .address = SCD4X_HR, + .scan_index = SCD4X_HR, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(SCD4X_TS), +}; + +static int scd4x_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct scd4x_state *state = iio_priv(indio_dev); + int ret; + + ret = scd4x_send_command(state, CMD_STOP_MEAS); + if (ret) + return ret; + + return regulator_disable(state->vdd); +} + +static int scd4x_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct scd4x_state *state = iio_priv(indio_dev); + int ret; + + ret = regulator_enable(state->vdd); + if (ret) + return ret; + + return scd4x_send_command(state, CMD_START_MEAS); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(scd4x_pm_ops, scd4x_suspend, scd4x_resume); + +static void scd4x_stop_meas(void *state) +{ + scd4x_send_command(state, CMD_STOP_MEAS); +} + +static void scd4x_disable_regulator(void *data) +{ + struct scd4x_state *state = data; + + regulator_disable(state->vdd); +} + +static irqreturn_t scd4x_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct scd4x_state *state = iio_priv(indio_dev); + struct { + uint16_t data[3]; + aligned_s64 ts; + } scan = { }; + int ret; + + mutex_lock(&state->lock); + ret = scd4x_read_poll(state, scan.data); + mutex_unlock(&state->lock); + if (ret) + goto out; + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); +out: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int scd4x_probe(struct i2c_client *client) +{ + static const unsigned long scd4x_scan_masks[] = { 0x07, 0x00 }; + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct scd4x_state *state; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*state)); + if (!indio_dev) + return -ENOMEM; + + state = iio_priv(indio_dev); + mutex_init(&state->lock); + state->client = client; + crc8_populate_msb(scd4x_crc8_table, SCD4X_CRC8_POLYNOMIAL); + + indio_dev->info = &scd4x_info; + indio_dev->name = client->name; + indio_dev->channels = scd4x_channels; + indio_dev->num_channels = ARRAY_SIZE(scd4x_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->available_scan_masks = scd4x_scan_masks; + + state->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(state->vdd)) + return dev_err_probe(dev, PTR_ERR(state->vdd), "failed to get regulator\n"); + + ret = regulator_enable(state->vdd); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, scd4x_disable_regulator, state); + if (ret) + return ret; + + ret = scd4x_send_command(state, CMD_STOP_MEAS); + if (ret) { + dev_err(dev, "failed to stop measurement: %d\n", ret); + return ret; + } + + /* execution time */ + msleep_interruptible(500); + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, scd4x_trigger_handler, NULL); + if (ret) + return ret; + + ret = scd4x_send_command(state, CMD_START_MEAS); + if (ret) { + dev_err(dev, "failed to start measurement: %d\n", ret); + return ret; + } + + ret = devm_add_action_or_reset(dev, scd4x_stop_meas, state); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id scd4x_dt_ids[] = { + { .compatible = "sensirion,scd40" }, + { .compatible = "sensirion,scd41" }, + { } +}; +MODULE_DEVICE_TABLE(of, scd4x_dt_ids); + +static struct i2c_driver scd4x_i2c_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = scd4x_dt_ids, + .pm = pm_sleep_ptr(&scd4x_pm_ops), + }, + .probe = scd4x_probe, +}; +module_i2c_driver(scd4x_i2c_driver); + +MODULE_AUTHOR("Roan van Dijk "); +MODULE_DESCRIPTION("Sensirion SCD4X carbon dioxide sensor core driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/chemical/sen0322.c b/drivers/iio/chemical/sen0322.c new file mode 100644 index 000000000..96c6fc120 --- /dev/null +++ b/drivers/iio/chemical/sen0322.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the DFRobot SEN0322 oxygen sensor. + * + * Datasheet: + * https://wiki.dfrobot.com/Gravity_I2C_Oxygen_Sensor_SKU_SEN0322 + * + * Possible I2C slave addresses: + * 0x70 + * 0x71 + * 0x72 + * 0x73 + * + * Copyright (C) 2025 Tóth János + */ + +#include +#include + +#include + +#define SEN0322_REG_DATA 0x03 +#define SEN0322_REG_COEFF 0x0A + +struct sen0322 { + struct regmap *regmap; +}; + +static int sen0322_read_data(struct sen0322 *sen0322) +{ + u8 data[3] = { }; + int ret; + + ret = regmap_bulk_read(sen0322->regmap, SEN0322_REG_DATA, data, + sizeof(data)); + if (ret < 0) + return ret; + + /* + * The actual value in the registers is: + * val = data[0] + data[1] / 10 + data[2] / 100 + * but it is multiplied by 100 here to avoid floating-point math + * and the scale is divided by 100 to compensate this. + */ + return data[0] * 100 + data[1] * 10 + data[2]; +} + +static int sen0322_read_scale(struct sen0322 *sen0322, int *num, int *den) +{ + u32 val; + int ret; + + ret = regmap_read(sen0322->regmap, SEN0322_REG_COEFF, &val); + if (ret < 0) + return ret; + + if (val) { + *num = val; + *den = 100000; /* Coeff is scaled by 1000 at calibration. */ + } else { /* The device is not calibrated, using the factory-defaults. */ + *num = 209; /* Oxygen content in the atmosphere is 20.9%. */ + *den = 120000; /* Output of the sensor at 20.9% is 120 uA. */ + } + + dev_dbg(regmap_get_device(sen0322->regmap), "scale: %d/%d\n", + *num, *den); + + return 0; +} + +static int sen0322_read_raw(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct sen0322 *sen0322 = iio_priv(iio_dev); + int ret; + + if (chan->type != IIO_CONCENTRATION) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = sen0322_read_data(sen0322); + if (ret < 0) + return ret; + + *val = ret; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + ret = sen0322_read_scale(sen0322, val, val2); + if (ret < 0) + return ret; + + return IIO_VAL_FRACTIONAL; + + default: + return -EINVAL; + } +} + +static const struct iio_info sen0322_info = { + .read_raw = sen0322_read_raw, +}; + +static const struct regmap_config sen0322_regmap_conf = { + .reg_bits = 8, + .val_bits = 8, +}; + +static const struct iio_chan_spec sen0322_channel = { + .type = IIO_CONCENTRATION, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), +}; + +static int sen0322_probe(struct i2c_client *client) +{ + struct sen0322 *sen0322; + struct iio_dev *iio_dev; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -ENODEV; + + iio_dev = devm_iio_device_alloc(&client->dev, sizeof(*sen0322)); + if (!iio_dev) + return -ENOMEM; + + sen0322 = iio_priv(iio_dev); + + sen0322->regmap = devm_regmap_init_i2c(client, &sen0322_regmap_conf); + if (IS_ERR(sen0322->regmap)) + return PTR_ERR(sen0322->regmap); + + iio_dev->info = &sen0322_info; + iio_dev->name = "sen0322"; + iio_dev->channels = &sen0322_channel; + iio_dev->num_channels = 1; + iio_dev->modes = INDIO_DIRECT_MODE; + + return devm_iio_device_register(&client->dev, iio_dev); +} + +static const struct of_device_id sen0322_of_match[] = { + { .compatible = "dfrobot,sen0322" }, + { } +}; +MODULE_DEVICE_TABLE(of, sen0322_of_match); + +static struct i2c_driver sen0322_driver = { + .driver = { + .name = "sen0322", + .of_match_table = sen0322_of_match, + }, + .probe = sen0322_probe, +}; +module_i2c_driver(sen0322_driver); + +MODULE_AUTHOR("Tóth János "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("SEN0322 oxygen sensor driver"); diff --git a/drivers/iio/chemical/sgp30.c b/drivers/iio/chemical/sgp30.c new file mode 100644 index 000000000..379c1c4af --- /dev/null +++ b/drivers/iio/chemical/sgp30.c @@ -0,0 +1,588 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * sgp30.c - Support for Sensirion SGP Gas Sensors + * + * Copyright (C) 2018 Andreas Brauchli + * + * I2C slave address: 0x58 + * + * Datasheets: + * https://www.sensirion.com/file/datasheet_sgp30 + * https://www.sensirion.com/file/datasheet_sgpc3 + * + * TODO: + * - baseline support + * - humidity compensation + * - power mode switching (SGPC3) + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define SGP_WORD_LEN 2 +#define SGP_CRC8_POLYNOMIAL 0x31 +#define SGP_CRC8_INIT 0xff +#define SGP_CRC8_LEN 1 +#define SGP_CMD(cmd_word) cpu_to_be16(cmd_word) +#define SGP_CMD_DURATION_US 12000 +#define SGP_MEASUREMENT_DURATION_US 50000 +#define SGP_CMD_LEN SGP_WORD_LEN +#define SGP_CMD_MAX_BUF_SIZE (SGP_CMD_LEN + 2 * SGP_WORD_LEN) +#define SGP_MEASUREMENT_LEN 2 +#define SGP30_MEASURE_INTERVAL_HZ 1 +#define SGPC3_MEASURE_INTERVAL_HZ 2 +#define SGP_VERS_PRODUCT(data) ((((data)->feature_set) & 0xf000) >> 12) +#define SGP_VERS_RESERVED(data) ((((data)->feature_set) & 0x0800) >> 11) +#define SGP_VERS_GEN(data) ((((data)->feature_set) & 0x0600) >> 9) +#define SGP_VERS_ENG_BIT(data) ((((data)->feature_set) & 0x0100) >> 8) +#define SGP_VERS_MAJOR(data) ((((data)->feature_set) & 0x00e0) >> 5) +#define SGP_VERS_MINOR(data) (((data)->feature_set) & 0x001f) + +DECLARE_CRC8_TABLE(sgp_crc8_table); + +enum sgp_product_id { + SGP30 = 0, + SGPC3, +}; + +enum sgp30_channel_idx { + SGP30_IAQ_TVOC_IDX = 0, + SGP30_IAQ_CO2EQ_IDX, + SGP30_SIG_ETOH_IDX, + SGP30_SIG_H2_IDX, +}; + +enum sgpc3_channel_idx { + SGPC3_IAQ_TVOC_IDX = 10, + SGPC3_SIG_ETOH_IDX, +}; + +enum sgp_cmd { + SGP_CMD_IAQ_INIT = SGP_CMD(0x2003), + SGP_CMD_IAQ_MEASURE = SGP_CMD(0x2008), + SGP_CMD_GET_FEATURE_SET = SGP_CMD(0x202f), + SGP_CMD_GET_SERIAL_ID = SGP_CMD(0x3682), + + SGP30_CMD_MEASURE_SIGNAL = SGP_CMD(0x2050), + + SGPC3_CMD_MEASURE_RAW = SGP_CMD(0x2046), +}; + +struct sgp_version { + u8 major; + u8 minor; +}; + +struct sgp_crc_word { + __be16 value; + u8 crc8; +} __attribute__((__packed__)); + +union sgp_reading { + u8 start; + struct sgp_crc_word raw_words[4]; +}; + +enum _iaq_buffer_state { + IAQ_BUFFER_EMPTY = 0, + IAQ_BUFFER_DEFAULT_VALS, + IAQ_BUFFER_VALID, +}; + +struct sgp_data { + struct i2c_client *client; + struct task_struct *iaq_thread; + struct mutex data_lock; + unsigned long iaq_init_start_jiffies; + unsigned long iaq_defval_skip_jiffies; + u16 product_id; + u16 feature_set; + unsigned long measure_interval_jiffies; + enum sgp_cmd iaq_init_cmd; + enum sgp_cmd measure_iaq_cmd; + enum sgp_cmd measure_gas_signals_cmd; + union sgp_reading buffer; + union sgp_reading iaq_buffer; + enum _iaq_buffer_state iaq_buffer_state; +}; + +struct sgp_device { + unsigned long product_id; + const struct iio_chan_spec *channels; + int num_channels; +}; + +static const struct sgp_version supported_versions_sgp30[] = { + { + .major = 1, + .minor = 0, + }, +}; + +static const struct sgp_version supported_versions_sgpc3[] = { + { + .major = 0, + .minor = 4, + }, +}; + +static const struct iio_chan_spec sgp30_channels[] = { + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_VOC, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = SGP30_IAQ_TVOC_IDX, + }, + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_CO2, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = SGP30_IAQ_CO2EQ_IDX, + }, + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_ETHANOL, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .address = SGP30_SIG_ETOH_IDX, + }, + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_H2, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .address = SGP30_SIG_H2_IDX, + }, +}; + +static const struct iio_chan_spec sgpc3_channels[] = { + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_VOC, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = SGPC3_IAQ_TVOC_IDX, + }, + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_ETHANOL, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .address = SGPC3_SIG_ETOH_IDX, + }, +}; + +static const struct sgp_device sgp_devices[] = { + [SGP30] = { + .product_id = SGP30, + .channels = sgp30_channels, + .num_channels = ARRAY_SIZE(sgp30_channels), + }, + [SGPC3] = { + .product_id = SGPC3, + .channels = sgpc3_channels, + .num_channels = ARRAY_SIZE(sgpc3_channels), + }, +}; + +/** + * sgp_verify_buffer() - verify the checksums of the data buffer words + * + * @data: SGP data + * @buf: Raw data buffer + * @word_count: Num data words stored in the buffer, excluding CRC bytes + * + * Return: 0 on success, negative error otherwise. + */ +static int sgp_verify_buffer(const struct sgp_data *data, + union sgp_reading *buf, size_t word_count) +{ + size_t size = word_count * (SGP_WORD_LEN + SGP_CRC8_LEN); + int i; + u8 crc; + u8 *data_buf = &buf->start; + + for (i = 0; i < size; i += SGP_WORD_LEN + SGP_CRC8_LEN) { + crc = crc8(sgp_crc8_table, &data_buf[i], SGP_WORD_LEN, + SGP_CRC8_INIT); + if (crc != data_buf[i + SGP_WORD_LEN]) { + dev_err(&data->client->dev, "CRC error\n"); + return -EIO; + } + } + + return 0; +} + +/** + * sgp_read_cmd() - reads data from sensor after issuing a command + * The caller must hold data->data_lock for the duration of the call. + * @data: SGP data + * @cmd: SGP Command to issue + * @buf: Raw data buffer to use + * @word_count: Num words to read, excluding CRC bytes + * @duration_us: Time taken to sensor to take a reading and data to be ready. + * + * Return: 0 on success, negative error otherwise. + */ +static int sgp_read_cmd(struct sgp_data *data, enum sgp_cmd cmd, + union sgp_reading *buf, size_t word_count, + unsigned long duration_us) +{ + int ret; + struct i2c_client *client = data->client; + size_t size = word_count * (SGP_WORD_LEN + SGP_CRC8_LEN); + u8 *data_buf; + + ret = i2c_master_send(client, (const char *)&cmd, SGP_CMD_LEN); + if (ret != SGP_CMD_LEN) + return -EIO; + usleep_range(duration_us, duration_us + 1000); + + if (word_count == 0) + return 0; + + data_buf = &buf->start; + ret = i2c_master_recv(client, data_buf, size); + if (ret < 0) + return ret; + if (ret != size) + return -EIO; + + return sgp_verify_buffer(data, buf, word_count); +} + +/** + * sgp_measure_iaq() - measure and retrieve IAQ values from sensor + * The caller must hold data->data_lock for the duration of the call. + * @data: SGP data + * + * Return: 0 on success, -EBUSY on default values, negative error + * otherwise. + */ + +static int sgp_measure_iaq(struct sgp_data *data) +{ + int ret; + /* data contains default values */ + bool default_vals = !time_after(jiffies, data->iaq_init_start_jiffies + + data->iaq_defval_skip_jiffies); + + ret = sgp_read_cmd(data, data->measure_iaq_cmd, &data->iaq_buffer, + SGP_MEASUREMENT_LEN, SGP_MEASUREMENT_DURATION_US); + if (ret < 0) + return ret; + + data->iaq_buffer_state = IAQ_BUFFER_DEFAULT_VALS; + + if (default_vals) + return -EBUSY; + + data->iaq_buffer_state = IAQ_BUFFER_VALID; + + return 0; +} + +static void sgp_iaq_thread_sleep_until(const struct sgp_data *data, + unsigned long sleep_jiffies) +{ + const long IAQ_POLL = 50000; + + while (!time_after(jiffies, sleep_jiffies)) { + usleep_range(IAQ_POLL, IAQ_POLL + 10000); + if (kthread_should_stop() || data->iaq_init_start_jiffies == 0) + return; + } +} + +static int sgp_iaq_threadfn(void *p) +{ + struct sgp_data *data = (struct sgp_data *)p; + unsigned long next_update_jiffies; + int ret; + + while (!kthread_should_stop()) { + mutex_lock(&data->data_lock); + if (data->iaq_init_start_jiffies == 0) { + ret = sgp_read_cmd(data, data->iaq_init_cmd, NULL, 0, + SGP_CMD_DURATION_US); + if (ret < 0) + goto unlock_sleep_continue; + data->iaq_init_start_jiffies = jiffies; + } + + ret = sgp_measure_iaq(data); + if (ret && ret != -EBUSY) { + dev_warn(&data->client->dev, + "IAQ measurement error [%d]\n", ret); + } +unlock_sleep_continue: + next_update_jiffies = jiffies + data->measure_interval_jiffies; + mutex_unlock(&data->data_lock); + sgp_iaq_thread_sleep_until(data, next_update_jiffies); + } + + return 0; +} + +static int sgp_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct sgp_data *data = iio_priv(indio_dev); + struct sgp_crc_word *words; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + mutex_lock(&data->data_lock); + if (data->iaq_buffer_state != IAQ_BUFFER_VALID) { + mutex_unlock(&data->data_lock); + return -EBUSY; + } + words = data->iaq_buffer.raw_words; + switch (chan->address) { + case SGP30_IAQ_TVOC_IDX: + case SGPC3_IAQ_TVOC_IDX: + *val = 0; + *val2 = be16_to_cpu(words[1].value); + ret = IIO_VAL_INT_PLUS_NANO; + break; + case SGP30_IAQ_CO2EQ_IDX: + *val = 0; + *val2 = be16_to_cpu(words[0].value); + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + ret = -EINVAL; + break; + } + mutex_unlock(&data->data_lock); + break; + case IIO_CHAN_INFO_RAW: + mutex_lock(&data->data_lock); + if (chan->address == SGPC3_SIG_ETOH_IDX) { + if (data->iaq_buffer_state == IAQ_BUFFER_EMPTY) + ret = -EBUSY; + else + ret = 0; + words = data->iaq_buffer.raw_words; + } else { + ret = sgp_read_cmd(data, data->measure_gas_signals_cmd, + &data->buffer, SGP_MEASUREMENT_LEN, + SGP_MEASUREMENT_DURATION_US); + words = data->buffer.raw_words; + } + if (ret) { + mutex_unlock(&data->data_lock); + return ret; + } + + switch (chan->address) { + case SGP30_SIG_ETOH_IDX: + *val = be16_to_cpu(words[1].value); + ret = IIO_VAL_INT; + break; + case SGPC3_SIG_ETOH_IDX: + case SGP30_SIG_H2_IDX: + *val = be16_to_cpu(words[0].value); + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + break; + } + mutex_unlock(&data->data_lock); + break; + default: + return -EINVAL; + } + + return ret; +} + +static int sgp_check_compat(struct sgp_data *data, + unsigned int product_id) +{ + struct device *dev = &data->client->dev; + const struct sgp_version *supported_versions; + u16 ix, num_fs; + u16 product, generation, major, minor; + + /* driver does not match product */ + generation = SGP_VERS_GEN(data); + if (generation != 0) { + dev_err(dev, + "incompatible product generation %d != 0", generation); + return -ENODEV; + } + + product = SGP_VERS_PRODUCT(data); + if (product != product_id) { + dev_err(dev, "sensor reports a different product: 0x%04x\n", + product); + return -ENODEV; + } + + if (SGP_VERS_RESERVED(data)) + dev_warn(dev, "reserved bit is set\n"); + + /* engineering samples are not supported: no interface guarantees */ + if (SGP_VERS_ENG_BIT(data)) + return -ENODEV; + + switch (product) { + case SGP30: + supported_versions = supported_versions_sgp30; + num_fs = ARRAY_SIZE(supported_versions_sgp30); + break; + case SGPC3: + supported_versions = supported_versions_sgpc3; + num_fs = ARRAY_SIZE(supported_versions_sgpc3); + break; + default: + return -ENODEV; + } + + major = SGP_VERS_MAJOR(data); + minor = SGP_VERS_MINOR(data); + for (ix = 0; ix < num_fs; ix++) { + if (major == supported_versions[ix].major && + minor >= supported_versions[ix].minor) + return 0; + } + dev_err(dev, "unsupported sgp version: %d.%d\n", major, minor); + + return -ENODEV; +} + +static void sgp_init(struct sgp_data *data) +{ + data->iaq_init_cmd = SGP_CMD_IAQ_INIT; + data->iaq_init_start_jiffies = 0; + data->iaq_buffer_state = IAQ_BUFFER_EMPTY; + switch (SGP_VERS_PRODUCT(data)) { + case SGP30: + data->measure_interval_jiffies = SGP30_MEASURE_INTERVAL_HZ * HZ; + data->measure_iaq_cmd = SGP_CMD_IAQ_MEASURE; + data->measure_gas_signals_cmd = SGP30_CMD_MEASURE_SIGNAL; + data->product_id = SGP30; + data->iaq_defval_skip_jiffies = 15 * HZ; + break; + case SGPC3: + data->measure_interval_jiffies = SGPC3_MEASURE_INTERVAL_HZ * HZ; + data->measure_iaq_cmd = SGPC3_CMD_MEASURE_RAW; + data->measure_gas_signals_cmd = SGPC3_CMD_MEASURE_RAW; + data->product_id = SGPC3; + data->iaq_defval_skip_jiffies = + 43 * data->measure_interval_jiffies; + break; + } +} + +static const struct iio_info sgp_info = { + .read_raw = sgp_read_raw, +}; + +static const struct of_device_id sgp_dt_ids[] = { + { .compatible = "sensirion,sgp30", .data = &sgp_devices[SGP30] }, + { .compatible = "sensirion,sgpc3", .data = &sgp_devices[SGPC3] }, + { } +}; +MODULE_DEVICE_TABLE(of, sgp_dt_ids); + +static int sgp_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + const struct sgp_device *match_data; + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct sgp_data *data; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + match_data = i2c_get_match_data(client); + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + crc8_populate_msb(sgp_crc8_table, SGP_CRC8_POLYNOMIAL); + mutex_init(&data->data_lock); + + /* get feature set version and write it to client data */ + ret = sgp_read_cmd(data, SGP_CMD_GET_FEATURE_SET, &data->buffer, 1, + SGP_CMD_DURATION_US); + if (ret < 0) + return ret; + + data->feature_set = be16_to_cpu(data->buffer.raw_words[0].value); + + ret = sgp_check_compat(data, match_data->product_id); + if (ret) + return ret; + + indio_dev->info = &sgp_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = match_data->channels; + indio_dev->num_channels = match_data->num_channels; + + sgp_init(data); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) { + dev_err(dev, "failed to register iio device\n"); + return ret; + } + + data->iaq_thread = kthread_run(sgp_iaq_threadfn, data, + "%s-iaq", data->client->name); + if (IS_ERR(data->iaq_thread)) + return dev_err_probe(dev, PTR_ERR(data->iaq_thread), + "failed to start IAQ thread\n"); + + return 0; +} + +static void sgp_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct sgp_data *data = iio_priv(indio_dev); + + if (data->iaq_thread) + kthread_stop(data->iaq_thread); +} + +static const struct i2c_device_id sgp_id[] = { + { .name = "sgp30", .driver_data = (kernel_ulong_t)&sgp_devices[SGP30] }, + { .name = "sgpc3", .driver_data = (kernel_ulong_t)&sgp_devices[SGPC3] }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sgp_id); + +static struct i2c_driver sgp_driver = { + .driver = { + .name = "sgp30", + .of_match_table = sgp_dt_ids, + }, + .probe = sgp_probe, + .remove = sgp_remove, + .id_table = sgp_id, +}; +module_i2c_driver(sgp_driver); + +MODULE_AUTHOR("Andreas Brauchli "); +MODULE_AUTHOR("Pascal Sachs "); +MODULE_DESCRIPTION("Sensirion SGP gas sensors"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/chemical/sgp40.c b/drivers/iio/chemical/sgp40.c new file mode 100644 index 000000000..b2b5e32a9 --- /dev/null +++ b/drivers/iio/chemical/sgp40.c @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * sgp40.c - Support for Sensirion SGP40 Gas Sensor + * + * Copyright (C) 2021 Andreas Klinger + * + * I2C slave address: 0x59 + * + * Datasheet can be found here: + * https://www.sensirion.com/file/datasheet_sgp40 + * + * There are two functionalities supported: + * + * 1) read raw logarithmic resistance value from sensor + * --> useful to pass it to the algorithm of the sensor vendor for + * measuring deteriorations and improvements of air quality. + * It can be read from the attribute in_resistance_raw. + * + * 2) calculate an estimated absolute voc index (in_concentration_input) + * with 0 - 500 index points) for measuring the air quality. + * For this purpose the value of the resistance for which the voc index + * will be 250 can be set up using in_resistance_calibbias (default 30000). + * + * The voc index is calculated as: + * x = (in_resistance_raw - in_resistance_calibbias) * 0.65 + * in_concentration_input = 500 / (1 + e^x) + * + * Compensation values of relative humidity and temperature can be set up + * by writing to the out values of temp and humidityrelative. + */ + +#include +#include +#include +#include +#include +#include + +/* + * floating point calculation of voc is done as integer + * where numbers are multiplied by 1 << SGP40_CALC_POWER + */ +#define SGP40_CALC_POWER 14 + +#define SGP40_CRC8_POLYNOMIAL 0x31 +#define SGP40_CRC8_INIT 0xff + +DECLARE_CRC8_TABLE(sgp40_crc8_table); + +struct sgp40_data { + struct device *dev; + struct i2c_client *client; + int rht; + int temp; + int res_calibbias; + /* Prevent concurrent access to rht, tmp, calibbias */ + struct mutex lock; +}; + +struct sgp40_tg_measure { + u8 command[2]; + __be16 rht_ticks; + u8 rht_crc; + __be16 temp_ticks; + u8 temp_crc; +} __packed; + +struct sgp40_tg_result { + __be16 res_ticks; + u8 res_crc; +} __packed; + +static const struct iio_chan_spec sgp40_channels[] = { + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_VOC, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, + { + .type = IIO_RESISTANCE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .output = 1, + }, + { + .type = IIO_HUMIDITYRELATIVE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .output = 1, + }, +}; + +/* + * taylor approximation of e^x: + * y = 1 + x + x^2 / 2 + x^3 / 6 + x^4 / 24 + ... + x^n / n! + * + * Because we are calculating x real value multiplied by 2^power we get + * an additional 2^power^n to divide for every element. For a reasonable + * precision this would overflow after a few iterations. Therefore we + * divide the x^n part whenever its about to overflow (xmax). + */ + +static u32 sgp40_exp(int exp, u32 power, u32 rounds) +{ + u32 x, y, xp; + u32 factorial, divider, xmax; + int sign = 1; + int i; + + if (exp == 0) + return 1 << power; + else if (exp < 0) { + sign = -1; + exp *= -1; + } + + xmax = 0x7FFFFFFF / exp; + x = exp; + xp = 1; + factorial = 1; + y = 1 << power; + divider = 0; + + for (i = 1; i <= rounds; i++) { + xp *= x; + factorial *= i; + y += (xp >> divider) / factorial; + divider += power; + /* divide when next multiplication would overflow */ + if (xp >= xmax) { + xp >>= power; + divider -= power; + } + } + + if (sign == -1) + return (1 << (power * 2)) / y; + else + return y; +} + +static int sgp40_calc_voc(struct sgp40_data *data, u16 resistance_raw, int *voc) +{ + int x; + u32 exp = 0; + + /* we calculate as a multiple of 16384 (2^14) */ + mutex_lock(&data->lock); + x = ((int)resistance_raw - data->res_calibbias) * 106; + mutex_unlock(&data->lock); + + /* voc = 500 / (1 + e^x) */ + exp = sgp40_exp(x, SGP40_CALC_POWER, 18); + *voc = 500 * ((1 << (SGP40_CALC_POWER * 2)) / ((1<dev, "raw: %d res_calibbias: %d x: %d exp: %d voc: %d\n", + resistance_raw, data->res_calibbias, x, exp, *voc); + + return 0; +} + +static int sgp40_measure_resistance_raw(struct sgp40_data *data, u16 *resistance_raw) +{ + int ret; + struct i2c_client *client = data->client; + u32 ticks; + u16 ticks16; + u8 crc; + struct sgp40_tg_measure tg = {.command = {0x26, 0x0F}}; + struct sgp40_tg_result tgres; + + mutex_lock(&data->lock); + + ticks = (data->rht / 10) * 65535 / 10000; + ticks16 = (u16)clamp(ticks, 0u, 65535u); /* clamp between 0 .. 100 %rH */ + tg.rht_ticks = cpu_to_be16(ticks16); + tg.rht_crc = crc8(sgp40_crc8_table, (u8 *)&tg.rht_ticks, 2, SGP40_CRC8_INIT); + + ticks = ((data->temp + 45000) / 10 ) * 65535 / 17500; + ticks16 = (u16)clamp(ticks, 0u, 65535u); /* clamp between -45 .. +130 °C */ + tg.temp_ticks = cpu_to_be16(ticks16); + tg.temp_crc = crc8(sgp40_crc8_table, (u8 *)&tg.temp_ticks, 2, SGP40_CRC8_INIT); + + mutex_unlock(&data->lock); + + ret = i2c_master_send(client, (const char *)&tg, sizeof(tg)); + if (ret != sizeof(tg)) { + dev_warn(data->dev, "i2c_master_send ret: %d sizeof: %zu\n", ret, sizeof(tg)); + return -EIO; + } + msleep(30); + + ret = i2c_master_recv(client, (u8 *)&tgres, sizeof(tgres)); + if (ret < 0) + return ret; + if (ret != sizeof(tgres)) { + dev_warn(data->dev, "i2c_master_recv ret: %d sizeof: %zu\n", ret, sizeof(tgres)); + return -EIO; + } + + crc = crc8(sgp40_crc8_table, (u8 *)&tgres.res_ticks, 2, SGP40_CRC8_INIT); + if (crc != tgres.res_crc) { + dev_err(data->dev, "CRC error while measure-raw\n"); + return -EIO; + } + + *resistance_raw = be16_to_cpu(tgres.res_ticks); + + return 0; +} + +static int sgp40_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct sgp40_data *data = iio_priv(indio_dev); + int ret, voc; + u16 resistance_raw; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_RESISTANCE: + ret = sgp40_measure_resistance_raw(data, &resistance_raw); + if (ret) + return ret; + + *val = resistance_raw; + return IIO_VAL_INT; + case IIO_TEMP: + mutex_lock(&data->lock); + *val = data->temp; + mutex_unlock(&data->lock); + return IIO_VAL_INT; + case IIO_HUMIDITYRELATIVE: + mutex_lock(&data->lock); + *val = data->rht; + mutex_unlock(&data->lock); + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_PROCESSED: + ret = sgp40_measure_resistance_raw(data, &resistance_raw); + if (ret) + return ret; + + ret = sgp40_calc_voc(data, resistance_raw, &voc); + if (ret) + return ret; + + *val = voc / (1 << SGP40_CALC_POWER); + /* + * calculation should fit into integer, where: + * voc <= (500 * 2^SGP40_CALC_POWER) = 8192000 + * (with SGP40_CALC_POWER = 14) + */ + *val2 = ((voc % (1 << SGP40_CALC_POWER)) * 244) / (1 << (SGP40_CALC_POWER - 12)); + dev_dbg(data->dev, "voc: %d val: %d.%06d\n", voc, *val, *val2); + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBBIAS: + mutex_lock(&data->lock); + *val = data->res_calibbias; + mutex_unlock(&data->lock); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int sgp40_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct sgp40_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_TEMP: + if ((val < -45000) || (val > 130000)) + return -EINVAL; + + mutex_lock(&data->lock); + data->temp = val; + mutex_unlock(&data->lock); + return 0; + case IIO_HUMIDITYRELATIVE: + if ((val < 0) || (val > 100000)) + return -EINVAL; + + mutex_lock(&data->lock); + data->rht = val; + mutex_unlock(&data->lock); + return 0; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + if ((val < 20000) || (val > 52768)) + return -EINVAL; + + mutex_lock(&data->lock); + data->res_calibbias = val; + mutex_unlock(&data->lock); + return 0; + } + return -EINVAL; +} + +static const struct iio_info sgp40_info = { + .read_raw = sgp40_read_raw, + .write_raw = sgp40_write_raw, +}; + +static int sgp40_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct sgp40_data *data; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + data->dev = dev; + + crc8_populate_msb(sgp40_crc8_table, SGP40_CRC8_POLYNOMIAL); + + mutex_init(&data->lock); + + /* set default values */ + data->rht = 50000; /* 50 % */ + data->temp = 25000; /* 25 °C */ + data->res_calibbias = 30000; /* resistance raw value for voc index of 250 */ + + indio_dev->info = &sgp40_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = sgp40_channels; + indio_dev->num_channels = ARRAY_SIZE(sgp40_channels); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + dev_err(dev, "failed to register iio device\n"); + + return ret; +} + +static const struct i2c_device_id sgp40_id[] = { + { .name = "sgp40" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, sgp40_id); + +static const struct of_device_id sgp40_dt_ids[] = { + { .compatible = "sensirion,sgp40" }, + { } +}; + +MODULE_DEVICE_TABLE(of, sgp40_dt_ids); + +static struct i2c_driver sgp40_driver = { + .driver = { + .name = "sgp40", + .of_match_table = sgp40_dt_ids, + }, + .probe = sgp40_probe, + .id_table = sgp40_id, +}; +module_i2c_driver(sgp40_driver); + +MODULE_AUTHOR("Andreas Klinger "); +MODULE_DESCRIPTION("Sensirion SGP40 gas sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/chemical/sps30.c b/drivers/iio/chemical/sps30.c new file mode 100644 index 000000000..8e15baa31 --- /dev/null +++ b/drivers/iio/chemical/sps30.c @@ -0,0 +1,375 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Sensirion SPS30 particulate matter sensor driver + * + * Copyright (c) Tomasz Duszynski + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sps30.h" + +/* sensor measures reliably up to 3000 ug / m3 */ +#define SPS30_MAX_PM 3000 +/* minimum and maximum self cleaning periods in seconds */ +#define SPS30_AUTO_CLEANING_PERIOD_MIN 0 +#define SPS30_AUTO_CLEANING_PERIOD_MAX 604800 + +enum { + PM1, + PM2P5, + PM4, + PM10, +}; + +enum { + RESET, + MEASURING, +}; + +static s32 sps30_float_to_int_clamped(__be32 *fp) +{ + int val = be32_to_cpup(fp); + int mantissa = val & GENMASK(22, 0); + /* this is fine since passed float is always non-negative */ + int exp = val >> 23; + int fraction, shift; + + /* special case 0 */ + if (!exp && !mantissa) + return 0; + + exp -= 127; + if (exp < 0) { + /* return values ranging from 1 to 99 */ + return ((((1 << 23) + mantissa) * 100) >> 23) >> (-exp); + } + + /* return values ranging from 100 to 300000 */ + shift = 23 - exp; + val = (1 << exp) + (mantissa >> shift); + if (val >= SPS30_MAX_PM) + return SPS30_MAX_PM * 100; + + fraction = mantissa & GENMASK(shift - 1, 0); + + return val * 100 + ((fraction * 100) >> shift); +} + +static int sps30_do_meas(struct sps30_state *state, s32 *data, int size) +{ + int i, ret; + + guard(mutex)(&state->lock); + + if (state->state == RESET) { + ret = state->ops->start_meas(state); + if (ret) + return ret; + + state->state = MEASURING; + } + + ret = state->ops->read_meas(state, (__be32 *)data, size); + if (ret) + return ret; + + for (i = 0; i < size; i++) + data[i] = sps30_float_to_int_clamped((__be32 *)&data[i]); + + return 0; +} + +static int sps30_do_reset(struct sps30_state *state) +{ + int ret; + + ret = state->ops->reset(state); + if (ret) + return ret; + + state->state = RESET; + + return 0; +} + +static irqreturn_t sps30_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct sps30_state *state = iio_priv(indio_dev); + int ret; + struct { + s32 data[4]; /* PM1, PM2P5, PM4, PM10 */ + aligned_s64 ts; + } scan; + + ret = sps30_do_meas(state, scan.data, ARRAY_SIZE(scan.data)); + if (ret) + goto err; + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int sps30_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct sps30_state *state = iio_priv(indio_dev); + int data[4], ret = -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_MASSCONCENTRATION: + /* read up to the number of bytes actually needed */ + switch (chan->channel2) { + case IIO_MOD_PM1: + ret = sps30_do_meas(state, data, 1); + break; + case IIO_MOD_PM2P5: + ret = sps30_do_meas(state, data, 2); + break; + case IIO_MOD_PM4: + ret = sps30_do_meas(state, data, 3); + break; + case IIO_MOD_PM10: + ret = sps30_do_meas(state, data, 4); + break; + } + if (ret) + return ret; + + *val = data[chan->address] / 100; + *val2 = (data[chan->address] % 100) * 10000; + + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_MASSCONCENTRATION: + switch (chan->channel2) { + case IIO_MOD_PM1: + case IIO_MOD_PM2P5: + case IIO_MOD_PM4: + case IIO_MOD_PM10: + *val = 0; + *val2 = 10000; + + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + default: + return -EINVAL; + } + } + + return -EINVAL; +} + +static ssize_t start_cleaning_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct sps30_state *state = iio_priv(indio_dev); + int val, ret; + + if (kstrtoint(buf, 0, &val) || val != 1) + return -EINVAL; + + guard(mutex)(&state->lock); + + ret = state->ops->clean_fan(state); + if (ret) + return ret; + + return len; +} + +static ssize_t cleaning_period_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct sps30_state *state = iio_priv(indio_dev); + __be32 val; + int ret; + + guard(mutex)(&state->lock); + + ret = state->ops->read_cleaning_period(state, &val); + if (ret) + return ret; + + return sysfs_emit(buf, "%d\n", be32_to_cpu(val)); +} + +static ssize_t cleaning_period_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct sps30_state *state = iio_priv(indio_dev); + int val, ret; + + if (kstrtoint(buf, 0, &val)) + return -EINVAL; + + if ((val < SPS30_AUTO_CLEANING_PERIOD_MIN) || + (val > SPS30_AUTO_CLEANING_PERIOD_MAX)) + return -EINVAL; + + guard(mutex)(&state->lock); + + ret = state->ops->write_cleaning_period(state, cpu_to_be32(val)); + if (ret) + return ret; + + msleep(20); + + /* + * sensor requires reset in order to return up to date self cleaning + * period + */ + ret = sps30_do_reset(state); + if (ret) + dev_warn(dev, + "period changed but reads will return the old value\n"); + + return len; +} + +static ssize_t cleaning_period_available_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "[%d %d %d]\n", + SPS30_AUTO_CLEANING_PERIOD_MIN, 1, + SPS30_AUTO_CLEANING_PERIOD_MAX); +} + +static IIO_DEVICE_ATTR_WO(start_cleaning, 0); +static IIO_DEVICE_ATTR_RW(cleaning_period, 0); +static IIO_DEVICE_ATTR_RO(cleaning_period_available, 0); + +static struct attribute *sps30_attrs[] = { + &iio_dev_attr_start_cleaning.dev_attr.attr, + &iio_dev_attr_cleaning_period.dev_attr.attr, + &iio_dev_attr_cleaning_period_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group sps30_attr_group = { + .attrs = sps30_attrs, +}; + +static const struct iio_info sps30_info = { + .attrs = &sps30_attr_group, + .read_raw = sps30_read_raw, +}; + +#define SPS30_CHAN(_index, _mod) { \ + .type = IIO_MASSCONCENTRATION, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## _mod, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .address = _mod, \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 19, \ + .storagebits = 32, \ + .endianness = IIO_CPU, \ + }, \ +} + +static const struct iio_chan_spec sps30_channels[] = { + SPS30_CHAN(0, PM1), + SPS30_CHAN(1, PM2P5), + SPS30_CHAN(2, PM4), + SPS30_CHAN(3, PM10), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static void sps30_devm_stop_meas(void *data) +{ + struct sps30_state *state = data; + + if (state->state == MEASURING) + state->ops->stop_meas(state); +} + +static const unsigned long sps30_scan_masks[] = { 0x0f, 0x00 }; + +int sps30_probe(struct device *dev, const char *name, void *priv, const struct sps30_ops *ops) +{ + struct iio_dev *indio_dev; + struct sps30_state *state; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*state)); + if (!indio_dev) + return -ENOMEM; + + dev_set_drvdata(dev, indio_dev); + + state = iio_priv(indio_dev); + state->dev = dev; + state->priv = priv; + state->ops = ops; + mutex_init(&state->lock); + + indio_dev->info = &sps30_info; + indio_dev->name = name; + indio_dev->channels = sps30_channels; + indio_dev->num_channels = ARRAY_SIZE(sps30_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->available_scan_masks = sps30_scan_masks; + + ret = sps30_do_reset(state); + if (ret) { + dev_err(dev, "failed to reset device\n"); + return ret; + } + + ret = state->ops->show_info(state); + if (ret) { + dev_err(dev, "failed to read device info\n"); + return ret; + } + + ret = devm_add_action_or_reset(dev, sps30_devm_stop_meas, state); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + sps30_trigger_handler, NULL); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS_GPL(sps30_probe, "IIO_SPS30"); + +MODULE_AUTHOR("Tomasz Duszynski "); +MODULE_DESCRIPTION("Sensirion SPS30 particulate matter sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/chemical/sps30.h b/drivers/iio/chemical/sps30.h new file mode 100644 index 000000000..a58ee43cf --- /dev/null +++ b/drivers/iio/chemical/sps30.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _SPS30_H +#define _SPS30_H + +#include + +struct sps30_state; +struct sps30_ops { + int (*start_meas)(struct sps30_state *state); + int (*stop_meas)(struct sps30_state *state); + int (*read_meas)(struct sps30_state *state, __be32 *meas, size_t num); + int (*reset)(struct sps30_state *state); + int (*clean_fan)(struct sps30_state *state); + int (*read_cleaning_period)(struct sps30_state *state, __be32 *period); + int (*write_cleaning_period)(struct sps30_state *state, __be32 period); + int (*show_info)(struct sps30_state *state); +}; + +struct sps30_state { + /* serialize access to the device */ + struct mutex lock; + struct device *dev; + int state; + /* + * priv pointer is solely for serdev driver private data. We keep it + * here because driver_data inside dev has been already used for iio and + * struct serdev_device doesn't have one. + */ + void *priv; + const struct sps30_ops *ops; +}; + +int sps30_probe(struct device *dev, const char *name, void *priv, const struct sps30_ops *ops); + +#endif diff --git a/drivers/iio/chemical/sps30_i2c.c b/drivers/iio/chemical/sps30_i2c.c new file mode 100644 index 000000000..2d3a876cc --- /dev/null +++ b/drivers/iio/chemical/sps30_i2c.c @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Sensirion SPS30 particulate matter sensor i2c driver + * + * Copyright (c) 2020 Tomasz Duszynski + * + * I2C slave address: 0x69 + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sps30.h" + +#define SPS30_I2C_CRC8_POLYNOMIAL 0x31 +/* max number of bytes needed to store PM measurements or serial string */ +#define SPS30_I2C_MAX_BUF_SIZE 48 + +DECLARE_CRC8_TABLE(sps30_i2c_crc8_table); + +#define SPS30_I2C_START_MEAS 0x0010 +#define SPS30_I2C_STOP_MEAS 0x0104 +#define SPS30_I2C_READ_MEAS 0x0300 +#define SPS30_I2C_MEAS_READY 0x0202 +#define SPS30_I2C_RESET 0xd304 +#define SPS30_I2C_CLEAN_FAN 0x5607 +#define SPS30_I2C_PERIOD 0x8004 +#define SPS30_I2C_READ_SERIAL 0xd033 +#define SPS30_I2C_READ_VERSION 0xd100 + +static int sps30_i2c_xfer(struct sps30_state *state, unsigned char *txbuf, size_t txsize, + unsigned char *rxbuf, size_t rxsize) +{ + struct i2c_client *client = to_i2c_client(state->dev); + int ret; + + /* + * Sensor does not support repeated start so instead of + * sending two i2c messages in a row we just send one by one. + */ + ret = i2c_master_send(client, txbuf, txsize); + if (ret < 0) + return ret; + if (ret != txsize) + return -EIO; + + if (!rxsize) + return 0; + + ret = i2c_master_recv(client, rxbuf, rxsize); + if (ret < 0) + return ret; + if (ret != rxsize) + return -EIO; + + return 0; +} + +static int sps30_i2c_command(struct sps30_state *state, u16 cmd, void *arg, size_t arg_size, + void *rsp, size_t rsp_size) +{ + /* + * Internally sensor stores measurements in a following manner: + * + * PM1: upper two bytes, crc8, lower two bytes, crc8 + * PM2P5: upper two bytes, crc8, lower two bytes, crc8 + * PM4: upper two bytes, crc8, lower two bytes, crc8 + * PM10: upper two bytes, crc8, lower two bytes, crc8 + * + * What follows next are number concentration measurements and + * typical particle size measurement which we omit. + */ + unsigned char buf[SPS30_I2C_MAX_BUF_SIZE]; + unsigned char *tmp; + unsigned char crc; + size_t i; + int ret; + + put_unaligned_be16(cmd, buf); + i = 2; + + if (rsp) { + /* each two bytes are followed by a crc8 */ + rsp_size += rsp_size / 2; + } else { + tmp = arg; + + while (arg_size) { + buf[i] = *tmp++; + buf[i + 1] = *tmp++; + buf[i + 2] = crc8(sps30_i2c_crc8_table, buf + i, 2, CRC8_INIT_VALUE); + arg_size -= 2; + i += 3; + } + } + + ret = sps30_i2c_xfer(state, buf, i, buf, rsp_size); + if (ret) + return ret; + + /* validate received data and strip off crc bytes */ + tmp = rsp; + for (i = 0; i < rsp_size; i += 3) { + crc = crc8(sps30_i2c_crc8_table, buf + i, 2, CRC8_INIT_VALUE); + if (crc != buf[i + 2]) { + dev_err(state->dev, "data integrity check failed\n"); + return -EIO; + } + + *tmp++ = buf[i]; + *tmp++ = buf[i + 1]; + } + + return 0; +} + +static int sps30_i2c_start_meas(struct sps30_state *state) +{ + /* request BE IEEE754 formatted data */ + unsigned char buf[] = { 0x03, 0x00 }; + + return sps30_i2c_command(state, SPS30_I2C_START_MEAS, buf, sizeof(buf), NULL, 0); +} + +static int sps30_i2c_stop_meas(struct sps30_state *state) +{ + return sps30_i2c_command(state, SPS30_I2C_STOP_MEAS, NULL, 0, NULL, 0); +} + +static int sps30_i2c_reset(struct sps30_state *state) +{ + int ret; + + ret = sps30_i2c_command(state, SPS30_I2C_RESET, NULL, 0, NULL, 0); + msleep(500); + /* + * Power-on-reset causes sensor to produce some glitch on i2c bus and + * some controllers end up in error state. Recover simply by placing + * some data on the bus, for example STOP_MEAS command, which + * is NOP in this case. + */ + sps30_i2c_stop_meas(state); + + return ret; +} + +static bool sps30_i2c_meas_ready(struct sps30_state *state) +{ + unsigned char buf[2]; + int ret; + + ret = sps30_i2c_command(state, SPS30_I2C_MEAS_READY, NULL, 0, buf, sizeof(buf)); + if (ret) + return false; + + return buf[1]; +} + +static int sps30_i2c_read_meas(struct sps30_state *state, __be32 *meas, size_t num) +{ + /* measurements are ready within a second */ + if (msleep_interruptible(1000)) + return -EINTR; + + if (!sps30_i2c_meas_ready(state)) + return -ETIMEDOUT; + + return sps30_i2c_command(state, SPS30_I2C_READ_MEAS, NULL, 0, meas, sizeof(*meas) * num); +} + +static int sps30_i2c_clean_fan(struct sps30_state *state) +{ + return sps30_i2c_command(state, SPS30_I2C_CLEAN_FAN, NULL, 0, NULL, 0); +} + +static int sps30_i2c_read_cleaning_period(struct sps30_state *state, __be32 *period) +{ + return sps30_i2c_command(state, SPS30_I2C_PERIOD, NULL, 0, period, sizeof(*period)); +} + +static int sps30_i2c_write_cleaning_period(struct sps30_state *state, __be32 period) +{ + return sps30_i2c_command(state, SPS30_I2C_PERIOD, &period, sizeof(period), NULL, 0); +} + +static int sps30_i2c_show_info(struct sps30_state *state) +{ + /* extra nul just in case */ + unsigned char buf[32 + 1] = { 0x00 }; + int ret; + + ret = sps30_i2c_command(state, SPS30_I2C_READ_SERIAL, NULL, 0, buf, sizeof(buf) - 1); + if (ret) + return ret; + + dev_info(state->dev, "serial number: %s\n", buf); + + ret = sps30_i2c_command(state, SPS30_I2C_READ_VERSION, NULL, 0, buf, 2); + if (ret) + return ret; + + dev_info(state->dev, "fw version: %u.%u\n", buf[0], buf[1]); + + return 0; +} + +static const struct sps30_ops sps30_i2c_ops = { + .start_meas = sps30_i2c_start_meas, + .stop_meas = sps30_i2c_stop_meas, + .read_meas = sps30_i2c_read_meas, + .reset = sps30_i2c_reset, + .clean_fan = sps30_i2c_clean_fan, + .read_cleaning_period = sps30_i2c_read_cleaning_period, + .write_cleaning_period = sps30_i2c_write_cleaning_period, + .show_info = sps30_i2c_show_info, +}; + +static int sps30_i2c_probe(struct i2c_client *client) +{ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -EOPNOTSUPP; + + crc8_populate_msb(sps30_i2c_crc8_table, SPS30_I2C_CRC8_POLYNOMIAL); + + return sps30_probe(&client->dev, client->name, NULL, &sps30_i2c_ops); +} + +static const struct i2c_device_id sps30_i2c_id[] = { + { .name = "sps30" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sps30_i2c_id); + +static const struct of_device_id sps30_i2c_of_match[] = { + { .compatible = "sensirion,sps30" }, + { } +}; +MODULE_DEVICE_TABLE(of, sps30_i2c_of_match); + +static struct i2c_driver sps30_i2c_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = sps30_i2c_of_match, + }, + .id_table = sps30_i2c_id, + .probe = sps30_i2c_probe, +}; +module_i2c_driver(sps30_i2c_driver); + +MODULE_AUTHOR("Tomasz Duszynski "); +MODULE_DESCRIPTION("Sensirion SPS30 particulate matter sensor i2c driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_SPS30"); diff --git a/drivers/iio/chemical/sps30_serial.c b/drivers/iio/chemical/sps30_serial.c new file mode 100644 index 000000000..0cdba8703 --- /dev/null +++ b/drivers/iio/chemical/sps30_serial.c @@ -0,0 +1,431 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Sensirion SPS30 particulate matter sensor serial driver + * + * Copyright (c) 2021 Tomasz Duszynski + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sps30.h" + +#define SPS30_SERIAL_DEV_NAME "sps30" + +#define SPS30_SERIAL_SOF_EOF 0x7e +#define SPS30_SERIAL_TIMEOUT msecs_to_jiffies(20) +#define SPS30_SERIAL_MAX_BUF_SIZE 263 +#define SPS30_SERIAL_ESCAPE_CHAR 0x7d + +#define SPS30_SERIAL_FRAME_MIN_SIZE 7 +#define SPS30_SERIAL_FRAME_ADR_OFFSET 1 +#define SPS30_SERIAL_FRAME_CMD_OFFSET 2 +#define SPS30_SERIAL_FRAME_MOSI_LEN_OFFSET 3 +#define SPS30_SERIAL_FRAME_MISO_STATE_OFFSET 3 +#define SPS30_SERIAL_FRAME_MISO_LEN_OFFSET 4 +#define SPS30_SERIAL_FRAME_MISO_DATA_OFFSET 5 + +#define SPS30_SERIAL_START_MEAS 0x00 +#define SPS30_SERIAL_STOP_MEAS 0x01 +#define SPS30_SERIAL_READ_MEAS 0x03 +#define SPS30_SERIAL_RESET 0xd3 +#define SPS30_SERIAL_CLEAN_FAN 0x56 +#define SPS30_SERIAL_PERIOD 0x80 +#define SPS30_SERIAL_DEV_INFO 0xd0 +#define SPS30_SERIAL_READ_VERSION 0xd1 + +struct sps30_serial_priv { + struct completion new_frame; + unsigned char buf[SPS30_SERIAL_MAX_BUF_SIZE]; + size_t num; + bool escaped; + bool done; +}; + +static int sps30_serial_xfer(struct sps30_state *state, const unsigned char *buf, size_t size) +{ + struct serdev_device *serdev = to_serdev_device(state->dev); + struct sps30_serial_priv *priv = state->priv; + int ret; + + priv->num = 0; + priv->escaped = false; + priv->done = false; + + ret = serdev_device_write(serdev, buf, size, SPS30_SERIAL_TIMEOUT); + if (ret < 0) + return ret; + if (ret != size) + return -EIO; + + ret = wait_for_completion_interruptible_timeout(&priv->new_frame, SPS30_SERIAL_TIMEOUT); + if (ret < 0) + return ret; + if (!ret) + return -ETIMEDOUT; + + return 0; +} + +static const struct { + u8 byte; + u8 byte2; +} sps30_serial_bytes[] = { + { 0x11, 0x31 }, + { 0x13, 0x33 }, + { 0x7e, 0x5e }, + { 0x7d, 0x5d }, +}; + +static int sps30_serial_put_byte(u8 *buf, u8 byte) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sps30_serial_bytes); i++) { + if (sps30_serial_bytes[i].byte != byte) + continue; + + buf[0] = SPS30_SERIAL_ESCAPE_CHAR; + buf[1] = sps30_serial_bytes[i].byte2; + + return 2; + } + + buf[0] = byte; + + return 1; +} + +static u8 sps30_serial_get_byte(bool escaped, u8 byte2) +{ + int i; + + if (!escaped) + return byte2; + + for (i = 0; i < ARRAY_SIZE(sps30_serial_bytes); i++) { + if (sps30_serial_bytes[i].byte2 != byte2) + continue; + + return sps30_serial_bytes[i].byte; + } + + return 0; +} + +static unsigned char sps30_serial_calc_chksum(const unsigned char *buf, size_t num) +{ + unsigned int chksum = 0; + size_t i; + + for (i = 0; i < num; i++) + chksum += buf[i]; + + return ~chksum; +} + +static int sps30_serial_prep_frame(u8 *buf, u8 cmd, const u8 *arg, + size_t arg_size) +{ + unsigned char chksum; + int num = 0; + size_t i; + + buf[num++] = SPS30_SERIAL_SOF_EOF; + buf[num++] = 0; + num += sps30_serial_put_byte(buf + num, cmd); + num += sps30_serial_put_byte(buf + num, arg_size); + + for (i = 0; i < arg_size; i++) + num += sps30_serial_put_byte(buf + num, arg[i]); + + /* SOF isn't checksummed */ + chksum = sps30_serial_calc_chksum(buf + 1, num - 1); + num += sps30_serial_put_byte(buf + num, chksum); + buf[num++] = SPS30_SERIAL_SOF_EOF; + + return num; +} + +static bool sps30_serial_frame_valid(struct sps30_state *state, const unsigned char *buf) +{ + struct sps30_serial_priv *priv = state->priv; + unsigned char chksum; + + if ((priv->num < SPS30_SERIAL_FRAME_MIN_SIZE) || + (priv->num != SPS30_SERIAL_FRAME_MIN_SIZE + + priv->buf[SPS30_SERIAL_FRAME_MISO_LEN_OFFSET])) { + dev_err(state->dev, "frame has invalid number of bytes\n"); + return false; + } + + if ((priv->buf[SPS30_SERIAL_FRAME_ADR_OFFSET] != buf[SPS30_SERIAL_FRAME_ADR_OFFSET]) || + (priv->buf[SPS30_SERIAL_FRAME_CMD_OFFSET] != buf[SPS30_SERIAL_FRAME_CMD_OFFSET])) { + dev_err(state->dev, "frame has wrong ADR and CMD bytes\n"); + return false; + } + + if (priv->buf[SPS30_SERIAL_FRAME_MISO_STATE_OFFSET]) { + dev_err(state->dev, "frame with non-zero state received (0x%02x)\n", + priv->buf[SPS30_SERIAL_FRAME_MISO_STATE_OFFSET]); + return false; + } + + /* SOF, checksum and EOF are not checksummed */ + chksum = sps30_serial_calc_chksum(priv->buf + 1, priv->num - 3); + if (priv->buf[priv->num - 2] != chksum) { + dev_err(state->dev, "frame integrity check failed\n"); + return false; + } + + return true; +} + +static int sps30_serial_command(struct sps30_state *state, unsigned char cmd, + const void *arg, size_t arg_size, void *rsp, size_t rsp_size) +{ + struct sps30_serial_priv *priv = state->priv; + unsigned char buf[SPS30_SERIAL_MAX_BUF_SIZE]; + int ret, size; + + size = sps30_serial_prep_frame(buf, cmd, arg, arg_size); + ret = sps30_serial_xfer(state, buf, size); + if (ret) + return ret; + + if (!sps30_serial_frame_valid(state, buf)) + return -EIO; + + if (rsp) { + rsp_size = min_t(size_t, priv->buf[SPS30_SERIAL_FRAME_MISO_LEN_OFFSET], rsp_size); + memcpy(rsp, &priv->buf[SPS30_SERIAL_FRAME_MISO_DATA_OFFSET], rsp_size); + } + + return rsp_size; +} + +static size_t sps30_serial_receive_buf(struct serdev_device *serdev, + const u8 *buf, size_t size) +{ + struct iio_dev *indio_dev = dev_get_drvdata(&serdev->dev); + struct sps30_serial_priv *priv; + struct sps30_state *state; + size_t i; + u8 byte; + + if (!indio_dev) + return 0; + + state = iio_priv(indio_dev); + priv = state->priv; + + /* just in case device put some unexpected data on the bus */ + if (priv->done) + return size; + + /* wait for the start of frame */ + if (!priv->num && size && buf[0] != SPS30_SERIAL_SOF_EOF) + return 1; + + if (priv->num + size >= ARRAY_SIZE(priv->buf)) + size = ARRAY_SIZE(priv->buf) - priv->num; + + for (i = 0; i < size; i++) { + byte = buf[i]; + /* remove stuffed bytes on-the-fly */ + if (byte == SPS30_SERIAL_ESCAPE_CHAR) { + priv->escaped = true; + continue; + } + + byte = sps30_serial_get_byte(priv->escaped, byte); + if (priv->escaped && !byte) + dev_warn(state->dev, "unrecognized escaped char (0x%02x)\n", byte); + + priv->buf[priv->num++] = byte; + + /* EOF received */ + if (!priv->escaped && byte == SPS30_SERIAL_SOF_EOF) { + if (priv->num < SPS30_SERIAL_FRAME_MIN_SIZE) + continue; + + priv->done = true; + complete(&priv->new_frame); + i++; + break; + } + + priv->escaped = false; + } + + return i; +} + +static const struct serdev_device_ops sps30_serial_device_ops = { + .receive_buf = sps30_serial_receive_buf, + .write_wakeup = serdev_device_write_wakeup, +}; + +static int sps30_serial_start_meas(struct sps30_state *state) +{ + /* request BE IEEE754 formatted data */ + unsigned char buf[] = { 0x01, 0x03 }; + + return sps30_serial_command(state, SPS30_SERIAL_START_MEAS, buf, sizeof(buf), NULL, 0); +} + +static int sps30_serial_stop_meas(struct sps30_state *state) +{ + return sps30_serial_command(state, SPS30_SERIAL_STOP_MEAS, NULL, 0, NULL, 0); +} + +static int sps30_serial_reset(struct sps30_state *state) +{ + int ret; + + ret = sps30_serial_command(state, SPS30_SERIAL_RESET, NULL, 0, NULL, 0); + msleep(500); + + return ret; +} + +static int sps30_serial_read_meas(struct sps30_state *state, __be32 *meas, size_t num) +{ + int ret; + + /* measurements are ready within a second */ + if (msleep_interruptible(1000)) + return -EINTR; + + ret = sps30_serial_command(state, SPS30_SERIAL_READ_MEAS, NULL, 0, meas, num * sizeof(*meas)); + if (ret < 0) + return ret; + /* if measurements aren't ready sensor returns empty frame */ + if (ret == SPS30_SERIAL_FRAME_MIN_SIZE) + return -ETIMEDOUT; + if (ret != num * sizeof(*meas)) + return -EIO; + + return 0; +} + +static int sps30_serial_clean_fan(struct sps30_state *state) +{ + return sps30_serial_command(state, SPS30_SERIAL_CLEAN_FAN, NULL, 0, NULL, 0); +} + +static int sps30_serial_read_cleaning_period(struct sps30_state *state, __be32 *period) +{ + unsigned char buf[] = { 0x00 }; + int ret; + + ret = sps30_serial_command(state, SPS30_SERIAL_PERIOD, buf, sizeof(buf), + period, sizeof(*period)); + if (ret < 0) + return ret; + if (ret != sizeof(*period)) + return -EIO; + + return 0; +} + +static int sps30_serial_write_cleaning_period(struct sps30_state *state, __be32 period) +{ + unsigned char buf[5] = { 0x00 }; + + memcpy(buf + 1, &period, sizeof(period)); + + return sps30_serial_command(state, SPS30_SERIAL_PERIOD, buf, sizeof(buf), NULL, 0); +} + +static int sps30_serial_show_info(struct sps30_state *state) +{ + /* + * tell device do return serial number and add extra nul byte just in case + * serial number isn't a valid string + */ + unsigned char buf[32 + 1] = { 0x03 }; + struct device *dev = state->dev; + int ret; + + ret = sps30_serial_command(state, SPS30_SERIAL_DEV_INFO, buf, 1, buf, sizeof(buf) - 1); + if (ret < 0) + return ret; + if (ret != sizeof(buf) - 1) + return -EIO; + + dev_info(dev, "serial number: %s\n", buf); + + ret = sps30_serial_command(state, SPS30_SERIAL_READ_VERSION, NULL, 0, buf, sizeof(buf) - 1); + if (ret < 0) + return ret; + if (ret < 2) + return -EIO; + + dev_info(dev, "fw version: %u.%u\n", buf[0], buf[1]); + + return 0; +} + +static const struct sps30_ops sps30_serial_ops = { + .start_meas = sps30_serial_start_meas, + .stop_meas = sps30_serial_stop_meas, + .read_meas = sps30_serial_read_meas, + .reset = sps30_serial_reset, + .clean_fan = sps30_serial_clean_fan, + .read_cleaning_period = sps30_serial_read_cleaning_period, + .write_cleaning_period = sps30_serial_write_cleaning_period, + .show_info = sps30_serial_show_info, +}; + +static int sps30_serial_probe(struct serdev_device *serdev) +{ + struct device *dev = &serdev->dev; + struct sps30_serial_priv *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + init_completion(&priv->new_frame); + serdev_device_set_client_ops(serdev, &sps30_serial_device_ops); + + ret = devm_serdev_device_open(dev, serdev); + if (ret) + return ret; + + serdev_device_set_baudrate(serdev, 115200); + serdev_device_set_flow_control(serdev, false); + + ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); + if (ret) + return ret; + + return sps30_probe(dev, SPS30_SERIAL_DEV_NAME, priv, &sps30_serial_ops); +} + +static const struct of_device_id sps30_serial_of_match[] = { + { .compatible = "sensirion,sps30" }, + { } +}; +MODULE_DEVICE_TABLE(of, sps30_serial_of_match); + +static struct serdev_device_driver sps30_serial_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = sps30_serial_of_match, + }, + .probe = sps30_serial_probe, +}; +module_serdev_device_driver(sps30_serial_driver); + +MODULE_AUTHOR("Tomasz Duszynski "); +MODULE_DESCRIPTION("Sensirion SPS30 particulate matter sensor serial driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_SPS30"); diff --git a/drivers/iio/chemical/sunrise_co2.c b/drivers/iio/chemical/sunrise_co2.c new file mode 100644 index 000000000..dae8a7025 --- /dev/null +++ b/drivers/iio/chemical/sunrise_co2.c @@ -0,0 +1,534 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Senseair Sunrise 006-0-0007 CO2 sensor driver. + * + * Copyright (C) 2021 Jacopo Mondi + * + * List of features not yet supported by the driver: + * - controllable EN pin + * - single-shot operations using the nDRY pin. + * - ABC/target calibration + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DRIVER_NAME "sunrise_co2" + +#define SUNRISE_ERROR_STATUS_REG 0x00 +#define SUNRISE_CO2_FILTERED_COMP_REG 0x06 +#define SUNRISE_CHIP_TEMPERATURE_REG 0x08 +#define SUNRISE_CALIBRATION_STATUS_REG 0x81 +#define SUNRISE_CALIBRATION_COMMAND_REG 0x82 +#define SUNRISE_CALIBRATION_FACTORY_CMD 0x7c02 +#define SUNRISE_CALIBRATION_BACKGROUND_CMD 0x7c06 +/* + * The calibration timeout is not characterized in the datasheet. + * Use 30 seconds as a reasonable upper limit. + */ +#define SUNRISE_CALIBRATION_TIMEOUT_US (30 * USEC_PER_SEC) + +struct sunrise_dev { + struct i2c_client *client; + struct regmap *regmap; + /* Protects access to IIO attributes. */ + struct mutex lock; + bool ignore_nak; +}; + +/* Custom regmap read/write operations: perform unlocked access to the i2c bus. */ + +static int sunrise_regmap_read(void *context, const void *reg_buf, + size_t reg_size, void *val_buf, size_t val_size) +{ + struct i2c_client *client = context; + struct sunrise_dev *sunrise = i2c_get_clientdata(client); + union i2c_smbus_data data = { }; + int ret; + + if (reg_size != 1 || !val_size) + return -EINVAL; + + data.block[0] = val_size; + + /* + * Wake up sensor by sending sensor address: START, sensor address, + * STOP. Sensor will not ACK this byte. + * + * The chip enters a low power state after 15ms without + * communications or after a complete read/write sequence. + */ + __i2c_smbus_xfer(client->adapter, client->addr, + sunrise->ignore_nak ? I2C_M_IGNORE_NAK : 0, + I2C_SMBUS_WRITE, 0, I2C_SMBUS_BYTE_DATA, &data); + + usleep_range(500, 1500); + + ret = __i2c_smbus_xfer(client->adapter, client->addr, client->flags, + I2C_SMBUS_READ, ((u8 *)reg_buf)[0], + I2C_SMBUS_I2C_BLOCK_DATA, &data); + if (ret < 0) + return ret; + + memcpy(val_buf, &data.block[1], data.block[0]); + + return 0; +} + +static int sunrise_regmap_write(void *context, const void *val_buf, size_t count) +{ + struct i2c_client *client = context; + struct sunrise_dev *sunrise = i2c_get_clientdata(client); + union i2c_smbus_data data = { }; + + /* Discard reg address from values count. */ + if (!count) + return -EINVAL; + count--; + + data.block[0] = count; + memcpy(&data.block[1], (u8 *)val_buf + 1, count); + + __i2c_smbus_xfer(client->adapter, client->addr, + sunrise->ignore_nak ? I2C_M_IGNORE_NAK : 0, + I2C_SMBUS_WRITE, 0, I2C_SMBUS_BYTE_DATA, &data); + + usleep_range(500, 1500); + + return __i2c_smbus_xfer(client->adapter, client->addr, client->flags, + I2C_SMBUS_WRITE, ((u8 *)val_buf)[0], + I2C_SMBUS_I2C_BLOCK_DATA, &data); +} + +/* + * Sunrise i2c read/write operations: lock the i2c segment to avoid losing the + * wake up session. Use custom regmap operations that perform unlocked access to + * the i2c bus. + */ +static int sunrise_read_byte(struct sunrise_dev *sunrise, u8 reg) +{ + const struct i2c_client *client = sunrise->client; + const struct device *dev = &client->dev; + unsigned int val; + int ret; + + i2c_lock_bus(client->adapter, I2C_LOCK_SEGMENT); + ret = regmap_read(sunrise->regmap, reg, &val); + i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT); + if (ret) { + dev_err(dev, "Read byte failed: reg 0x%02x (%d)\n", reg, ret); + return ret; + } + + return val; +} + +static int sunrise_read_word(struct sunrise_dev *sunrise, u8 reg, u16 *val) +{ + const struct i2c_client *client = sunrise->client; + const struct device *dev = &client->dev; + __be16 be_val; + int ret; + + i2c_lock_bus(client->adapter, I2C_LOCK_SEGMENT); + ret = regmap_bulk_read(sunrise->regmap, reg, &be_val, sizeof(be_val)); + i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT); + if (ret) { + dev_err(dev, "Read word failed: reg 0x%02x (%d)\n", reg, ret); + return ret; + } + + *val = be16_to_cpu(be_val); + + return 0; +} + +static int sunrise_write_byte(struct sunrise_dev *sunrise, u8 reg, u8 val) +{ + const struct i2c_client *client = sunrise->client; + const struct device *dev = &client->dev; + int ret; + + i2c_lock_bus(client->adapter, I2C_LOCK_SEGMENT); + ret = regmap_write(sunrise->regmap, reg, val); + i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT); + if (ret) + dev_err(dev, "Write byte failed: reg 0x%02x (%d)\n", reg, ret); + + return ret; +} + +static int sunrise_write_word(struct sunrise_dev *sunrise, u8 reg, u16 data) +{ + const struct i2c_client *client = sunrise->client; + const struct device *dev = &client->dev; + __be16 be_data = cpu_to_be16(data); + int ret; + + i2c_lock_bus(client->adapter, I2C_LOCK_SEGMENT); + ret = regmap_bulk_write(sunrise->regmap, reg, &be_data, sizeof(be_data)); + i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT); + if (ret) + dev_err(dev, "Write word failed: reg 0x%02x (%d)\n", reg, ret); + + return ret; +} + +/* Trigger a calibration cycle. */ + +enum { + SUNRISE_CALIBRATION_FACTORY, + SUNRISE_CALIBRATION_BACKGROUND, +}; + +static const struct sunrise_calib_data { + u16 cmd; + u8 bit; + const char * const name; +} calib_data[] = { + [SUNRISE_CALIBRATION_FACTORY] = { + SUNRISE_CALIBRATION_FACTORY_CMD, + BIT(2), + "factory_calibration", + }, + [SUNRISE_CALIBRATION_BACKGROUND] = { + SUNRISE_CALIBRATION_BACKGROUND_CMD, + BIT(5), + "background_calibration", + }, +}; + +static int sunrise_calibrate(struct sunrise_dev *sunrise, + const struct sunrise_calib_data *data) +{ + unsigned int status; + int ret; + + /* Reset the calibration status reg. */ + ret = sunrise_write_byte(sunrise, SUNRISE_CALIBRATION_STATUS_REG, 0x00); + if (ret) + return ret; + + /* Write a calibration command and poll the calibration status bit. */ + ret = sunrise_write_word(sunrise, SUNRISE_CALIBRATION_COMMAND_REG, data->cmd); + if (ret) + return ret; + + dev_dbg(&sunrise->client->dev, "%s in progress\n", data->name); + + /* + * Calibration takes several seconds, so the sleep time between reads + * can be pretty relaxed. + */ + return read_poll_timeout(sunrise_read_byte, status, status & data->bit, + 200000, SUNRISE_CALIBRATION_TIMEOUT_US, false, + sunrise, SUNRISE_CALIBRATION_STATUS_REG); +} + +static ssize_t sunrise_cal_factory_write(struct iio_dev *iiodev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct sunrise_dev *sunrise = iio_priv(iiodev); + bool enable; + int ret; + + ret = kstrtobool(buf, &enable); + if (ret) + return ret; + + if (!enable) + return len; + + mutex_lock(&sunrise->lock); + ret = sunrise_calibrate(sunrise, &calib_data[SUNRISE_CALIBRATION_FACTORY]); + mutex_unlock(&sunrise->lock); + if (ret) + return ret; + + return len; +} + +static ssize_t sunrise_cal_background_write(struct iio_dev *iiodev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct sunrise_dev *sunrise = iio_priv(iiodev); + bool enable; + int ret; + + ret = kstrtobool(buf, &enable); + if (ret) + return ret; + + if (!enable) + return len; + + mutex_lock(&sunrise->lock); + ret = sunrise_calibrate(sunrise, &calib_data[SUNRISE_CALIBRATION_BACKGROUND]); + mutex_unlock(&sunrise->lock); + if (ret) + return ret; + + return len; +} + + /* Enumerate and retrieve the chip error status. */ +enum { + SUNRISE_ERROR_FATAL, + SUNRISE_ERROR_I2C, + SUNRISE_ERROR_ALGORITHM, + SUNRISE_ERROR_CALIBRATION, + SUNRISE_ERROR_SELF_DIAGNOSTIC, + SUNRISE_ERROR_OUT_OF_RANGE, + SUNRISE_ERROR_MEMORY, + SUNRISE_ERROR_NO_MEASUREMENT, + SUNRISE_ERROR_LOW_VOLTAGE, + SUNRISE_ERROR_MEASUREMENT_TIMEOUT, +}; + +static const char * const sunrise_error_statuses[] = { + [SUNRISE_ERROR_FATAL] = "error_fatal", + [SUNRISE_ERROR_I2C] = "error_i2c", + [SUNRISE_ERROR_ALGORITHM] = "error_algorithm", + [SUNRISE_ERROR_CALIBRATION] = "error_calibration", + [SUNRISE_ERROR_SELF_DIAGNOSTIC] = "error_self_diagnostic", + [SUNRISE_ERROR_OUT_OF_RANGE] = "error_out_of_range", + [SUNRISE_ERROR_MEMORY] = "error_memory", + [SUNRISE_ERROR_NO_MEASUREMENT] = "error_no_measurement", + [SUNRISE_ERROR_LOW_VOLTAGE] = "error_low_voltage", + [SUNRISE_ERROR_MEASUREMENT_TIMEOUT] = "error_measurement_timeout", +}; + +static const struct iio_enum sunrise_error_statuses_enum = { + .items = sunrise_error_statuses, + .num_items = ARRAY_SIZE(sunrise_error_statuses), +}; + +static ssize_t sunrise_error_status_read(struct iio_dev *iiodev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct sunrise_dev *sunrise = iio_priv(iiodev); + unsigned long errors; + ssize_t len = 0; + u16 value; + int ret; + u8 i; + + mutex_lock(&sunrise->lock); + ret = sunrise_read_word(sunrise, SUNRISE_ERROR_STATUS_REG, &value); + if (ret) { + mutex_unlock(&sunrise->lock); + return ret; + } + + errors = value; + for_each_set_bit(i, &errors, ARRAY_SIZE(sunrise_error_statuses)) + len += sysfs_emit_at(buf, len, "%s ", sunrise_error_statuses[i]); + + if (len) + buf[len - 1] = '\n'; + + mutex_unlock(&sunrise->lock); + + return len; +} + +static const struct iio_chan_spec_ext_info sunrise_concentration_ext_info[] = { + /* Calibration triggers. */ + { + .name = "calibration_factory", + .write = sunrise_cal_factory_write, + .shared = IIO_SEPARATE, + }, + { + .name = "calibration_background", + .write = sunrise_cal_background_write, + .shared = IIO_SEPARATE, + }, + + /* Error statuses. */ + { + .name = "error_status", + .read = sunrise_error_status_read, + .shared = IIO_SHARED_BY_ALL, + }, + { + .name = "error_status_available", + .shared = IIO_SHARED_BY_ALL, + .read = iio_enum_available_read, + .private = (uintptr_t)&sunrise_error_statuses_enum, + }, + { } +}; + +static const struct iio_chan_spec sunrise_channels[] = { + { + .type = IIO_CONCENTRATION, + .modified = 1, + .channel2 = IIO_MOD_CO2, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .ext_info = sunrise_concentration_ext_info, + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static int sunrise_read_raw(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct sunrise_dev *sunrise = iio_priv(iio_dev); + u16 value; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_CONCENTRATION: + mutex_lock(&sunrise->lock); + ret = sunrise_read_word(sunrise, SUNRISE_CO2_FILTERED_COMP_REG, + &value); + mutex_unlock(&sunrise->lock); + + if (ret) + return ret; + + *val = value; + return IIO_VAL_INT; + + case IIO_TEMP: + mutex_lock(&sunrise->lock); + ret = sunrise_read_word(sunrise, SUNRISE_CHIP_TEMPERATURE_REG, + &value); + mutex_unlock(&sunrise->lock); + + if (ret) + return ret; + + *val = value; + return IIO_VAL_INT; + + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_CONCENTRATION: + /* + * 1 / 10^4 to comply with IIO scale for CO2 + * (percentage). The chip CO2 reading range is [400 - + * 5000] ppm which corresponds to [0,004 - 0,5] %. + */ + *val = 1; + *val2 = 10000; + return IIO_VAL_FRACTIONAL; + + case IIO_TEMP: + /* x10 to comply with IIO scale (millidegrees celsius). */ + *val = 10; + return IIO_VAL_INT; + + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +static const struct iio_info sunrise_info = { + .read_raw = sunrise_read_raw, +}; + +static const struct regmap_bus sunrise_regmap_bus = { + .read = sunrise_regmap_read, + .write = sunrise_regmap_write, +}; + +static const struct regmap_config sunrise_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int sunrise_probe(struct i2c_client *client) +{ + struct sunrise_dev *sunrise; + struct iio_dev *iio_dev; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA)) { + dev_err(&client->dev, + "Adapter does not support required functionalities\n"); + return -EOPNOTSUPP; + } + + iio_dev = devm_iio_device_alloc(&client->dev, sizeof(*sunrise)); + if (!iio_dev) + return -ENOMEM; + + sunrise = iio_priv(iio_dev); + sunrise->client = client; + mutex_init(&sunrise->lock); + + i2c_set_clientdata(client, sunrise); + + sunrise->regmap = devm_regmap_init(&client->dev, &sunrise_regmap_bus, + client, &sunrise_regmap_config); + if (IS_ERR(sunrise->regmap)) { + dev_err(&client->dev, "Failed to initialize regmap\n"); + return PTR_ERR(sunrise->regmap); + } + + /* + * The chip nacks the wake up message. If the adapter does not support + * protocol mangling do not set the I2C_M_IGNORE_NAK flag at the expense + * of possible cruft in the logs. + */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_PROTOCOL_MANGLING)) + sunrise->ignore_nak = true; + + iio_dev->info = &sunrise_info; + iio_dev->name = DRIVER_NAME; + iio_dev->channels = sunrise_channels; + iio_dev->num_channels = ARRAY_SIZE(sunrise_channels); + iio_dev->modes = INDIO_DIRECT_MODE; + + return devm_iio_device_register(&client->dev, iio_dev); +} + +static const struct of_device_id sunrise_of_match[] = { + { .compatible = "senseair,sunrise-006-0-0007" }, + { } +}; +MODULE_DEVICE_TABLE(of, sunrise_of_match); + +static struct i2c_driver sunrise_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = sunrise_of_match, + }, + .probe = sunrise_probe, +}; +module_i2c_driver(sunrise_driver); + +MODULE_AUTHOR("Jacopo Mondi "); +MODULE_DESCRIPTION("Senseair Sunrise 006-0-0007 CO2 sensor IIO driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/chemical/vz89x.c b/drivers/iio/chemical/vz89x.c new file mode 100644 index 000000000..2e10a6b17 --- /dev/null +++ b/drivers/iio/chemical/vz89x.c @@ -0,0 +1,405 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * vz89x.c - Support for SGX Sensortech MiCS VZ89X VOC sensors + * + * Copyright (C) 2015-2018 + * Author: Matt Ranostay + */ + +#include +#include +#include +#include + +#include +#include + +#define VZ89X_REG_MEASUREMENT 0x09 +#define VZ89X_REG_MEASUREMENT_RD_SIZE 6 +#define VZ89X_REG_MEASUREMENT_WR_SIZE 3 + +#define VZ89X_VOC_CO2_IDX 0 +#define VZ89X_VOC_SHORT_IDX 1 +#define VZ89X_VOC_TVOC_IDX 2 +#define VZ89X_VOC_RESISTANCE_IDX 3 + +#define VZ89TE_REG_MEASUREMENT 0x0c +#define VZ89TE_REG_MEASUREMENT_RD_SIZE 7 +#define VZ89TE_REG_MEASUREMENT_WR_SIZE 6 + +#define VZ89TE_VOC_TVOC_IDX 0 +#define VZ89TE_VOC_CO2_IDX 1 +#define VZ89TE_VOC_RESISTANCE_IDX 2 + +enum { + VZ89X, + VZ89TE, +}; + +struct vz89x_chip_data; + +struct vz89x_data { + struct i2c_client *client; + const struct vz89x_chip_data *chip; + struct mutex lock; + int (*xfer)(struct vz89x_data *data, u8 cmd); + + bool is_valid; + unsigned long last_update; + u8 buffer[VZ89TE_REG_MEASUREMENT_RD_SIZE]; +}; + +struct vz89x_chip_data { + bool (*valid)(struct vz89x_data *data); + const struct iio_chan_spec *channels; + u8 num_channels; + + u8 cmd; + u8 read_size; + u8 write_size; +}; + +static const struct iio_chan_spec vz89x_channels[] = { + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_CO2, + .modified = 1, + .info_mask_separate = + BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW), + .address = VZ89X_VOC_CO2_IDX, + }, + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_VOC, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .address = VZ89X_VOC_SHORT_IDX, + .extend_name = "short", + }, + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_VOC, + .modified = 1, + .info_mask_separate = + BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW), + .address = VZ89X_VOC_TVOC_IDX, + }, + { + .type = IIO_RESISTANCE, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .address = VZ89X_VOC_RESISTANCE_IDX, + .scan_index = -1, + .scan_type = { + .endianness = IIO_LE, + }, + }, +}; + +static const struct iio_chan_spec vz89te_channels[] = { + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_VOC, + .modified = 1, + .info_mask_separate = + BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW), + .address = VZ89TE_VOC_TVOC_IDX, + }, + + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_CO2, + .modified = 1, + .info_mask_separate = + BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_RAW), + .address = VZ89TE_VOC_CO2_IDX, + }, + { + .type = IIO_RESISTANCE, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .address = VZ89TE_VOC_RESISTANCE_IDX, + .scan_index = -1, + .scan_type = { + .endianness = IIO_BE, + }, + }, +}; + +static IIO_CONST_ATTR(in_concentration_co2_scale, "0.00000698689"); +static IIO_CONST_ATTR(in_concentration_voc_scale, "0.00000000436681223"); + +static struct attribute *vz89x_attributes[] = { + &iio_const_attr_in_concentration_co2_scale.dev_attr.attr, + &iio_const_attr_in_concentration_voc_scale.dev_attr.attr, + NULL, +}; + +static const struct attribute_group vz89x_attrs_group = { + .attrs = vz89x_attributes, +}; + +/* + * Chipset sometime updates in the middle of a reading causing it to reset the + * data pointer, and causing invalid reading of previous data. + * We can check for this by reading MSB of the resistance reading that is + * always zero, and by also confirming the VOC_short isn't zero. + */ + +static bool vz89x_measurement_is_valid(struct vz89x_data *data) +{ + if (data->buffer[VZ89X_VOC_SHORT_IDX] == 0) + return true; + + return !!(data->buffer[data->chip->read_size - 1] > 0); +} + +/* VZ89TE device has a modified CRC-8 two complement check */ +static bool vz89te_measurement_is_valid(struct vz89x_data *data) +{ + u8 crc = 0; + int i, sum = 0; + + for (i = 0; i < (data->chip->read_size - 1); i++) { + sum = crc + data->buffer[i]; + crc = sum; + crc += sum / 256; + } + + return !((0xff - crc) == data->buffer[data->chip->read_size - 1]); +} + +static int vz89x_i2c_xfer(struct vz89x_data *data, u8 cmd) +{ + const struct vz89x_chip_data *chip = data->chip; + struct i2c_client *client = data->client; + struct i2c_msg msg[2]; + int ret; + u8 buf[6] = { cmd, 0, 0, 0, 0, 0xf3 }; + + msg[0].addr = client->addr; + msg[0].flags = client->flags; + msg[0].len = chip->write_size; + msg[0].buf = (char *) &buf; + + msg[1].addr = client->addr; + msg[1].flags = client->flags | I2C_M_RD; + msg[1].len = chip->read_size; + msg[1].buf = (char *) &data->buffer; + + ret = i2c_transfer(client->adapter, msg, 2); + + return (ret == 2) ? 0 : ret; +} + +static int vz89x_smbus_xfer(struct vz89x_data *data, u8 cmd) +{ + struct i2c_client *client = data->client; + int ret; + int i; + + ret = i2c_smbus_write_word_data(client, cmd, 0); + if (ret < 0) + return ret; + + for (i = 0; i < data->chip->read_size; i++) { + ret = i2c_smbus_read_byte(client); + if (ret < 0) + return ret; + data->buffer[i] = ret; + } + + return 0; +} + +static int vz89x_get_measurement(struct vz89x_data *data) +{ + const struct vz89x_chip_data *chip = data->chip; + int ret; + + /* sensor can only be polled once a second max per datasheet */ + if (!time_after(jiffies, data->last_update + HZ)) + return data->is_valid ? 0 : -EAGAIN; + + data->is_valid = false; + data->last_update = jiffies; + + ret = data->xfer(data, chip->cmd); + if (ret < 0) + return ret; + + ret = chip->valid(data); + if (ret) + return -EAGAIN; + + data->is_valid = true; + + return 0; +} + +static int vz89x_get_resistance_reading(struct vz89x_data *data, + struct iio_chan_spec const *chan, + int *val) +{ + u8 *tmp = &data->buffer[chan->address]; + + switch (chan->scan_type.endianness) { + case IIO_LE: + *val = le32_to_cpup((__le32 *) tmp) & GENMASK(23, 0); + break; + case IIO_BE: + *val = be32_to_cpup((__be32 *) tmp) >> 8; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int vz89x_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct vz89x_data *data = iio_priv(indio_dev); + int ret = -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&data->lock); + ret = vz89x_get_measurement(data); + mutex_unlock(&data->lock); + + if (ret) + return ret; + + switch (chan->type) { + case IIO_CONCENTRATION: + *val = data->buffer[chan->address]; + return IIO_VAL_INT; + case IIO_RESISTANCE: + ret = vz89x_get_resistance_reading(data, chan, val); + if (!ret) + return IIO_VAL_INT; + break; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_RESISTANCE: + *val = 10; + return IIO_VAL_INT; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_OFFSET: + switch (chan->channel2) { + case IIO_MOD_CO2: + *val = 44; + *val2 = 250000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_MOD_VOC: + *val = -13; + return IIO_VAL_INT; + default: + return -EINVAL; + } + } + + return ret; +} + +static const struct iio_info vz89x_info = { + .attrs = &vz89x_attrs_group, + .read_raw = vz89x_read_raw, +}; + +static const struct vz89x_chip_data vz89x_chips[] = { + { + .valid = vz89x_measurement_is_valid, + + .cmd = VZ89X_REG_MEASUREMENT, + .read_size = VZ89X_REG_MEASUREMENT_RD_SIZE, + .write_size = VZ89X_REG_MEASUREMENT_WR_SIZE, + + .channels = vz89x_channels, + .num_channels = ARRAY_SIZE(vz89x_channels), + }, + { + .valid = vz89te_measurement_is_valid, + + .cmd = VZ89TE_REG_MEASUREMENT, + .read_size = VZ89TE_REG_MEASUREMENT_RD_SIZE, + .write_size = VZ89TE_REG_MEASUREMENT_WR_SIZE, + + .channels = vz89te_channels, + .num_channels = ARRAY_SIZE(vz89te_channels), + }, +}; + +static const struct of_device_id vz89x_dt_ids[] = { + { .compatible = "sgx,vz89x", .data = &vz89x_chips[VZ89X] }, + { .compatible = "sgx,vz89te", .data = &vz89x_chips[VZ89TE] }, + { } +}; +MODULE_DEVICE_TABLE(of, vz89x_dt_ids); + +static int vz89x_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct vz89x_data *data; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + data = iio_priv(indio_dev); + + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + data->xfer = vz89x_i2c_xfer; + else if (i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE)) + data->xfer = vz89x_smbus_xfer; + else + return -EOPNOTSUPP; + + data->chip = i2c_get_match_data(client); + + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->last_update = jiffies - HZ; + mutex_init(&data->lock); + + indio_dev->info = &vz89x_info; + indio_dev->name = dev_name(dev); + indio_dev->modes = INDIO_DIRECT_MODE; + + indio_dev->channels = data->chip->channels; + indio_dev->num_channels = data->chip->num_channels; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct i2c_device_id vz89x_id[] = { + { .name = "vz89x", .driver_data = (kernel_ulong_t)&vz89x_chips[VZ89X] }, + { .name = "vz89te", .driver_data = (kernel_ulong_t)&vz89x_chips[VZ89TE] }, + { } +}; +MODULE_DEVICE_TABLE(i2c, vz89x_id); + +static struct i2c_driver vz89x_driver = { + .driver = { + .name = "vz89x", + .of_match_table = vz89x_dt_ids, + }, + .probe = vz89x_probe, + .id_table = vz89x_id, +}; +module_i2c_driver(vz89x_driver); + +MODULE_AUTHOR("Matt Ranostay "); +MODULE_DESCRIPTION("SGX Sensortech MiCS VZ89X VOC sensors"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/Kconfig b/drivers/iio/common/Kconfig new file mode 100644 index 000000000..1ccb5ccf3 --- /dev/null +++ b/drivers/iio/common/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# IIO common modules +# + +source "drivers/iio/common/cros_ec_sensors/Kconfig" +source "drivers/iio/common/hid-sensors/Kconfig" +source "drivers/iio/common/inv_sensors/Kconfig" +source "drivers/iio/common/ms_sensors/Kconfig" +source "drivers/iio/common/scmi_sensors/Kconfig" +source "drivers/iio/common/ssp_sensors/Kconfig" +source "drivers/iio/common/st_sensors/Kconfig" diff --git a/drivers/iio/common/Makefile b/drivers/iio/common/Makefile new file mode 100644 index 000000000..d3e952239 --- /dev/null +++ b/drivers/iio/common/Makefile @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the IIO common modules. +# Common modules contains modules, which can be shared among multiple +# IIO modules. For example if the trigger processing is common for +# multiple IIO modules then this can be moved to a common module +# instead of duplicating in each module. +# + +# When adding new entries keep the list in alphabetical order +obj-y += cros_ec_sensors/ +obj-y += hid-sensors/ +obj-y += inv_sensors/ +obj-y += ms_sensors/ +obj-y += scmi_sensors/ +obj-y += ssp_sensors/ +obj-y += st_sensors/ diff --git a/drivers/iio/common/cros_ec_sensors/Kconfig b/drivers/iio/common/cros_ec_sensors/Kconfig new file mode 100644 index 000000000..394e319c9 --- /dev/null +++ b/drivers/iio/common/cros_ec_sensors/Kconfig @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Chrome OS Embedded Controller managed sensors library +# +config IIO_CROS_EC_SENSORS_CORE + tristate "ChromeOS EC Sensors Core" + depends on SYSFS && CROS_EC_SENSORHUB + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Base module for the ChromeOS EC Sensors module. + Contains core functions used by other IIO CrosEC sensor + drivers. + Define common attributes and sysfs interrupt handler. + +config IIO_CROS_EC_SENSORS + tristate "ChromeOS EC Contiguous Sensors" + depends on IIO_CROS_EC_SENSORS_CORE + help + Module to handle 3d contiguous sensors like + Accelerometers, Gyroscope and Magnetometer that are + presented by the ChromeOS EC Sensor hub. + Creates an IIO device for each functions. + +config IIO_CROS_EC_SENSORS_LID_ANGLE + tristate "ChromeOS EC Sensor for lid angle" + depends on IIO_CROS_EC_SENSORS_CORE + help + Module to report the angle between lid and base for some + convertible devices. + This module is loaded when the EC can calculate the angle between the base + and the lid. + +config IIO_CROS_EC_ACTIVITY + tristate "ChromeOS EC Activity Sensors" + depends on IIO_CROS_EC_SENSORS_CORE + help + Module to handle activity events presented by the ChromeOS EC sensor hub. + Activities can be a proximity detector (on body/off body detection) + or a significant motion detector. + Creates an IIO device to manage all activities. diff --git a/drivers/iio/common/cros_ec_sensors/Makefile b/drivers/iio/common/cros_ec_sensors/Makefile new file mode 100644 index 000000000..a7dfb5794 --- /dev/null +++ b/drivers/iio/common/cros_ec_sensors/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for sensors seen through the ChromeOS EC sensor hub. +# + +cros-ec-sensors-core-objs += cros_ec_sensors_core.o cros_ec_sensors_trace.o +obj-$(CONFIG_IIO_CROS_EC_SENSORS_CORE) += cros-ec-sensors-core.o +obj-$(CONFIG_IIO_CROS_EC_SENSORS) += cros_ec_sensors.o +obj-$(CONFIG_IIO_CROS_EC_SENSORS_LID_ANGLE) += cros_ec_lid_angle.o +obj-$(CONFIG_IIO_CROS_EC_ACTIVITY) += cros_ec_activity.o diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_activity.c b/drivers/iio/common/cros_ec_sensors/cros_ec_activity.c new file mode 100644 index 000000000..6762685e6 --- /dev/null +++ b/drivers/iio/common/cros_ec_sensors/cros_ec_activity.c @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * cros_ec_activity - Driver for activities/gesture recognition. + * + * Copyright 2025 Google, Inc + * + * This driver uses the cros-ec interface to communicate with the ChromeOS + * EC about activity data. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#define DRV_NAME "cros-ec-activity" + +/* state data for ec_sensors iio driver. */ +struct cros_ec_sensors_state { + /* Shared by all sensors */ + struct cros_ec_sensors_core_state core; + + struct iio_chan_spec *channels; + + int body_detection_channel_index; + int sig_motion_channel_index; +}; + +static const struct iio_event_spec cros_ec_activity_single_shot[] = { + { + .type = IIO_EV_TYPE_CHANGE, + /* significant motion trigger when we get out of still. */ + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_event_spec cros_ec_body_detect_events[] = { + { + .type = IIO_EV_TYPE_CHANGE, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static int cros_ec_activity_sensors_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cros_ec_sensors_state *st = iio_priv(indio_dev); + int ret; + + if (chan->type != IIO_PROXIMITY || mask != IIO_CHAN_INFO_RAW) + return -EINVAL; + + guard(mutex)(&st->core.cmd_lock); + st->core.param.cmd = MOTIONSENSE_CMD_GET_ACTIVITY; + st->core.param.get_activity.activity = + MOTIONSENSE_ACTIVITY_BODY_DETECTION; + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + if (ret) + return ret; + + /* + * EC actually report if a body is near (1) or far (0). + * Units for proximity sensor after scale is in meter, + * so invert the result to return 0m when near and 1m when far. + */ + *val = !st->core.resp->get_activity.state; + return IIO_VAL_INT; +} + +static int cros_ec_activity_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct cros_ec_sensors_state *st = iio_priv(indio_dev); + int ret; + + if (chan->type != IIO_ACTIVITY && chan->type != IIO_PROXIMITY) + return -EINVAL; + + guard(mutex)(&st->core.cmd_lock); + st->core.param.cmd = MOTIONSENSE_CMD_LIST_ACTIVITIES; + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + if (ret) + return ret; + + switch (chan->type) { + case IIO_PROXIMITY: + return !!(st->core.resp->list_activities.enabled & + (1 << MOTIONSENSE_ACTIVITY_BODY_DETECTION)); + case IIO_ACTIVITY: + if (chan->channel2 == IIO_MOD_STILL) { + return !!(st->core.resp->list_activities.enabled & + (1 << MOTIONSENSE_ACTIVITY_SIG_MOTION)); + } + + dev_warn(&indio_dev->dev, "Unknown activity: %d\n", + chan->channel2); + return -EINVAL; + default: + dev_warn(&indio_dev->dev, "Unknown channel type: %d\n", + chan->type); + return -EINVAL; + } +} + +static int cros_ec_activity_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct cros_ec_sensors_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->core.cmd_lock); + st->core.param.cmd = MOTIONSENSE_CMD_SET_ACTIVITY; + switch (chan->type) { + case IIO_PROXIMITY: + st->core.param.set_activity.activity = + MOTIONSENSE_ACTIVITY_BODY_DETECTION; + break; + case IIO_ACTIVITY: + if (chan->channel2 == IIO_MOD_STILL) { + st->core.param.set_activity.activity = + MOTIONSENSE_ACTIVITY_SIG_MOTION; + break; + } + dev_warn(&indio_dev->dev, "Unknown activity: %d\n", + chan->channel2); + return -EINVAL; + default: + dev_warn(&indio_dev->dev, "Unknown channel type: %d\n", + chan->type); + return -EINVAL; + } + st->core.param.set_activity.enable = state; + return cros_ec_motion_send_host_cmd(&st->core, 0); +} + +static int cros_ec_activity_push_data(struct iio_dev *indio_dev, + s16 *data, s64 timestamp) +{ + struct ec_response_activity_data *activity_data = + (struct ec_response_activity_data *)data; + enum motionsensor_activity activity = activity_data->activity; + u8 state = activity_data->state; + const struct cros_ec_sensors_state *st = iio_priv(indio_dev); + const struct iio_chan_spec *chan; + enum iio_event_direction dir; + int index; + + switch (activity) { + case MOTIONSENSE_ACTIVITY_BODY_DETECTION: + index = st->body_detection_channel_index; + dir = state ? IIO_EV_DIR_FALLING : IIO_EV_DIR_RISING; + break; + case MOTIONSENSE_ACTIVITY_SIG_MOTION: + index = st->sig_motion_channel_index; + dir = IIO_EV_DIR_FALLING; + break; + default: + dev_warn(&indio_dev->dev, "Unknown activity: %d\n", activity); + return 0; + } + chan = &st->channels[index]; + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(chan->type, index, chan->event_spec[0].type, dir), + timestamp); + return 0; +} + +static irqreturn_t cros_ec_activity_capture(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + + /* + * This callback would be called when a software trigger is + * used. But when this virtual sensor is present, it is guaranteed + * the sensor hub is advanced enough to not need a software trigger. + */ + dev_warn(&indio_dev->dev, "%s: Not Expected\n", __func__); + return IRQ_NONE; +} + +static const struct iio_info ec_sensors_info = { + .read_raw = &cros_ec_activity_sensors_read_raw, + .read_event_config = cros_ec_activity_read_event_config, + .write_event_config = cros_ec_activity_write_event_config, +}; + +static int cros_ec_sensors_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cros_ec_device *ec_device = dev_get_drvdata(dev->parent); + struct iio_dev *indio_dev; + struct cros_ec_sensors_state *st; + struct iio_chan_spec *channel; + unsigned long activities; + int i, index, ret, nb_activities; + + if (!ec_device) { + dev_warn(dev, "No CROS EC device found.\n"); + return -EINVAL; + } + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + ret = cros_ec_sensors_core_init(pdev, indio_dev, true, + cros_ec_activity_capture); + if (ret) + return ret; + + indio_dev->info = &ec_sensors_info; + st = iio_priv(indio_dev); + st->core.type = st->core.resp->info.type; + st->core.read_ec_sensors_data = cros_ec_sensors_read_cmd; + + st->core.param.cmd = MOTIONSENSE_CMD_LIST_ACTIVITIES; + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + if (ret) + return ret; + + activities = st->core.resp->list_activities.enabled | + st->core.resp->list_activities.disabled; + if (!activities) + return -ENODEV; + + /* Allocate a channel per activity and one for timestamp */ + nb_activities = hweight_long(activities) + 1; + st->channels = devm_kcalloc(dev, nb_activities, + sizeof(*st->channels), GFP_KERNEL); + if (!st->channels) + return -ENOMEM; + + channel = &st->channels[0]; + index = 0; + for_each_set_bit(i, &activities, BITS_PER_LONG) { + /* List all available triggers */ + if (i == MOTIONSENSE_ACTIVITY_BODY_DETECTION) { + channel->type = IIO_PROXIMITY; + channel->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); + channel->event_spec = cros_ec_body_detect_events; + channel->num_event_specs = + ARRAY_SIZE(cros_ec_body_detect_events); + st->body_detection_channel_index = index; + } else { + channel->type = IIO_ACTIVITY; + channel->modified = 1; + channel->event_spec = cros_ec_activity_single_shot; + channel->num_event_specs = + ARRAY_SIZE(cros_ec_activity_single_shot); + if (i == MOTIONSENSE_ACTIVITY_SIG_MOTION) { + channel->channel2 = IIO_MOD_STILL; + st->sig_motion_channel_index = index; + } else { + dev_warn(dev, "Unknown activity: %d\n", i); + continue; + } + } + channel->ext_info = cros_ec_sensors_limited_info; + channel->scan_index = index++; + channel++; + } + + *channel = IIO_CHAN_SOFT_TIMESTAMP(index); + + indio_dev->channels = st->channels; + indio_dev->num_channels = index + 1; + + return cros_ec_sensors_core_register(dev, indio_dev, + cros_ec_activity_push_data); +} + +static struct platform_driver cros_ec_sensors_platform_driver = { + .driver = { + .name = DRV_NAME, + }, + .probe = cros_ec_sensors_probe, +}; +module_platform_driver(cros_ec_sensors_platform_driver); + +MODULE_DESCRIPTION("ChromeOS EC activity sensors driver"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_lid_angle.c b/drivers/iio/common/cros_ec_sensors/cros_ec_lid_angle.c new file mode 100644 index 000000000..8f5bf40a0 --- /dev/null +++ b/drivers/iio/common/cros_ec_sensors/cros_ec_lid_angle.c @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * cros_ec_lid_angle - Driver for CrOS EC lid angle sensor. + * + * Copyright 2018 Google, Inc + * + * This driver uses the cros-ec interface to communicate with the Chrome OS + * EC about counter sensors. Counters are presented through + * iio sysfs. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "cros-ec-lid-angle" + +/* + * One channel for the lid angle, the other for timestamp. + */ +static const struct iio_chan_spec cros_ec_lid_angle_channels[] = { + { + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_type.realbits = CROS_EC_SENSOR_BITS, + .scan_type.storagebits = CROS_EC_SENSOR_BITS, + .scan_type.sign = 'u', + .type = IIO_ANGL + }, + IIO_CHAN_SOFT_TIMESTAMP(1) +}; + +/* State data for ec_sensors iio driver. */ +struct cros_ec_lid_angle_state { + /* Shared by all sensors */ + struct cros_ec_sensors_core_state core; +}; + +static int cros_ec_sensors_read_lid_angle(struct iio_dev *indio_dev, + unsigned long scan_mask, s16 *data) +{ + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + int ret; + + st->param.cmd = MOTIONSENSE_CMD_LID_ANGLE; + ret = cros_ec_motion_send_host_cmd(st, sizeof(st->resp->lid_angle)); + if (ret) { + dev_warn(&indio_dev->dev, "Unable to read lid angle\n"); + return ret; + } + + *data = st->resp->lid_angle.value; + return 0; +} + +static int cros_ec_lid_angle_read(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cros_ec_lid_angle_state *st = iio_priv(indio_dev); + s16 data; + int ret; + + mutex_lock(&st->core.cmd_lock); + ret = cros_ec_sensors_read_lid_angle(indio_dev, 1, &data); + if (ret == 0) { + *val = data; + ret = IIO_VAL_INT; + } + mutex_unlock(&st->core.cmd_lock); + return ret; +} + +static const struct iio_info cros_ec_lid_angle_info = { + .read_raw = &cros_ec_lid_angle_read, +}; + +static int cros_ec_lid_angle_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct cros_ec_lid_angle_state *state; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*state)); + if (!indio_dev) + return -ENOMEM; + + ret = cros_ec_sensors_core_init(pdev, indio_dev, false, NULL); + if (ret) + return ret; + + indio_dev->info = &cros_ec_lid_angle_info; + state = iio_priv(indio_dev); + indio_dev->channels = cros_ec_lid_angle_channels; + indio_dev->num_channels = ARRAY_SIZE(cros_ec_lid_angle_channels); + + state->core.read_ec_sensors_data = cros_ec_sensors_read_lid_angle; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + cros_ec_sensors_capture, NULL); + if (ret) + return ret; + + return cros_ec_sensors_core_register(dev, indio_dev, NULL); +} + +static const struct platform_device_id cros_ec_lid_angle_ids[] = { + { + .name = DRV_NAME, + }, + { } +}; +MODULE_DEVICE_TABLE(platform, cros_ec_lid_angle_ids); + +static struct platform_driver cros_ec_lid_angle_platform_driver = { + .driver = { + .name = DRV_NAME, + }, + .probe = cros_ec_lid_angle_probe, + .id_table = cros_ec_lid_angle_ids, +}; +module_platform_driver(cros_ec_lid_angle_platform_driver); + +MODULE_DESCRIPTION("ChromeOS EC driver for reporting convertible lid angle."); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c new file mode 100644 index 000000000..b971f8b64 --- /dev/null +++ b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * cros_ec_sensors - Driver for Chrome OS Embedded Controller sensors. + * + * Copyright (C) 2016 Google, Inc + * + * This driver uses the cros-ec interface to communicate with the Chrome OS + * EC about sensors data. Data access is presented through iio sysfs. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CROS_EC_SENSORS_MAX_CHANNELS 4 + +/* State data for ec_sensors iio driver. */ +struct cros_ec_sensors_state { + /* Shared by all sensors */ + struct cros_ec_sensors_core_state core; + + struct iio_chan_spec channels[CROS_EC_SENSORS_MAX_CHANNELS]; +}; + +static int cros_ec_sensors_read(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cros_ec_sensors_state *st = iio_priv(indio_dev); + s16 data = 0; + s64 val64; + int i; + int ret; + int idx = chan->scan_index; + + mutex_lock(&st->core.cmd_lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = st->core.read_ec_sensors_data(indio_dev, 1 << idx, &data); + if (ret < 0) + break; + ret = IIO_VAL_INT; + *val = data; + break; + case IIO_CHAN_INFO_CALIBBIAS: + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_OFFSET; + st->core.param.sensor_offset.flags = 0; + + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + if (ret < 0) + break; + + /* Save values */ + for (i = CROS_EC_SENSOR_X; i < CROS_EC_SENSOR_MAX_AXIS; i++) + st->core.calib[i].offset = + st->core.resp->sensor_offset.offset[i]; + ret = IIO_VAL_INT; + *val = st->core.calib[idx].offset; + break; + case IIO_CHAN_INFO_CALIBSCALE: + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_SCALE; + st->core.param.sensor_offset.flags = 0; + + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + if (ret == -EPROTO || ret == -EOPNOTSUPP) { + /* Reading calibscale is not supported on older EC. */ + *val = 1; + *val2 = 0; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + } else if (ret) { + break; + } + + /* Save values */ + for (i = CROS_EC_SENSOR_X; i < CROS_EC_SENSOR_MAX_AXIS; i++) + st->core.calib[i].scale = + st->core.resp->sensor_scale.scale[i]; + + *val = st->core.calib[idx].scale >> 15; + *val2 = ((st->core.calib[idx].scale & 0x7FFF) * 1000000LL) / + MOTION_SENSE_DEFAULT_SCALE; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case IIO_CHAN_INFO_SCALE: + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE; + st->core.param.sensor_range.data = EC_MOTION_SENSE_NO_VALUE; + + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + if (ret < 0) + break; + + val64 = st->core.resp->sensor_range.ret; + switch (st->core.type) { + case MOTIONSENSE_TYPE_ACCEL: + /* + * EC returns data in g, IIO expects m/s^2. + * Do not use IIO_G_TO_M_S_2 to avoid precision loss. + */ + *val = div_s64(val64 * 980665, 10); + *val2 = 10000 << (CROS_EC_SENSOR_BITS - 1); + ret = IIO_VAL_FRACTIONAL; + break; + case MOTIONSENSE_TYPE_GYRO: + /* + * EC returns data in dps, iio expects rad/s. + * Do not use IIO_DEGREE_TO_RAD to avoid precision + * loss. Round to the nearest integer. + */ + *val = 0; + *val2 = div_s64(val64 * 3141592653ULL, + 180 << (CROS_EC_SENSOR_BITS - 1)); + ret = IIO_VAL_INT_PLUS_NANO; + break; + case MOTIONSENSE_TYPE_MAG: + /* + * EC returns data in 16LSB / uT, + * iio expects Gauss + */ + *val = val64; + *val2 = 100 << (CROS_EC_SENSOR_BITS - 1); + ret = IIO_VAL_FRACTIONAL; + break; + default: + ret = -EINVAL; + } + break; + default: + ret = cros_ec_sensors_core_read(&st->core, chan, val, val2, + mask); + break; + } + mutex_unlock(&st->core.cmd_lock); + + return ret; +} + +static int cros_ec_sensors_write(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct cros_ec_sensors_state *st = iio_priv(indio_dev); + int i; + int ret; + int idx = chan->scan_index; + + mutex_lock(&st->core.cmd_lock); + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + st->core.calib[idx].offset = val; + + /* Send to EC for each axis, even if not complete */ + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_OFFSET; + st->core.param.sensor_offset.flags = + MOTION_SENSE_SET_OFFSET; + for (i = CROS_EC_SENSOR_X; i < CROS_EC_SENSOR_MAX_AXIS; i++) + st->core.param.sensor_offset.offset[i] = + st->core.calib[i].offset; + st->core.param.sensor_offset.temp = + EC_MOTION_SENSE_INVALID_CALIB_TEMP; + + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + break; + case IIO_CHAN_INFO_CALIBSCALE: + st->core.calib[idx].scale = val; + /* Send to EC for each axis, even if not complete */ + + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_SCALE; + st->core.param.sensor_offset.flags = + MOTION_SENSE_SET_OFFSET; + for (i = CROS_EC_SENSOR_X; i < CROS_EC_SENSOR_MAX_AXIS; i++) + st->core.param.sensor_scale.scale[i] = + st->core.calib[i].scale; + st->core.param.sensor_scale.temp = + EC_MOTION_SENSE_INVALID_CALIB_TEMP; + + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + break; + case IIO_CHAN_INFO_SCALE: + if (st->core.type == MOTIONSENSE_TYPE_MAG) { + ret = -EINVAL; + break; + } + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE; + st->core.param.sensor_range.data = val; + + /* Always roundup, so caller gets at least what it asks for. */ + st->core.param.sensor_range.roundup = 1; + + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + if (ret == 0) { + st->core.range_updated = true; + st->core.curr_range = val; + } + break; + default: + ret = cros_ec_sensors_core_write( + &st->core, chan, val, val2, mask); + break; + } + + mutex_unlock(&st->core.cmd_lock); + + return ret; +} + +static const struct iio_info ec_sensors_info = { + .read_raw = &cros_ec_sensors_read, + .write_raw = &cros_ec_sensors_write, + .read_avail = &cros_ec_sensors_core_read_avail, +}; + +static int cros_ec_sensors_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct cros_ec_sensors_state *state; + struct iio_chan_spec *channel; + int ret, i; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*state)); + if (!indio_dev) + return -ENOMEM; + + ret = cros_ec_sensors_core_init(pdev, indio_dev, true, + cros_ec_sensors_capture); + if (ret) + return ret; + + indio_dev->info = &ec_sensors_info; + state = iio_priv(indio_dev); + for (channel = state->channels, i = CROS_EC_SENSOR_X; + i < CROS_EC_SENSOR_MAX_AXIS; i++, channel++) { + /* Common part */ + channel->info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_CALIBSCALE); + channel->info_mask_shared_by_all = + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ); + channel->info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_SAMP_FREQ); + channel->scan_type.realbits = CROS_EC_SENSOR_BITS; + channel->scan_type.storagebits = CROS_EC_SENSOR_BITS; + channel->scan_index = i; + channel->ext_info = cros_ec_sensors_ext_info; + channel->modified = 1; + channel->channel2 = IIO_MOD_X + i; + channel->scan_type.sign = 's'; + + /* Sensor specific */ + switch (state->core.type) { + case MOTIONSENSE_TYPE_ACCEL: + channel->type = IIO_ACCEL; + break; + case MOTIONSENSE_TYPE_GYRO: + channel->type = IIO_ANGL_VEL; + break; + case MOTIONSENSE_TYPE_MAG: + channel->type = IIO_MAGN; + break; + default: + dev_err(&pdev->dev, "Unknown motion sensor\n"); + return -EINVAL; + } + } + + *channel = IIO_CHAN_SOFT_TIMESTAMP(CROS_EC_SENSOR_MAX_AXIS); + + indio_dev->channels = state->channels; + indio_dev->num_channels = CROS_EC_SENSORS_MAX_CHANNELS; + + /* There is only enough room for accel and gyro in the io space */ + if ((state->core.ec->cmd_readmem != NULL) && + (state->core.type != MOTIONSENSE_TYPE_MAG)) + state->core.read_ec_sensors_data = cros_ec_sensors_read_lpc; + else + state->core.read_ec_sensors_data = cros_ec_sensors_read_cmd; + + return cros_ec_sensors_core_register(dev, indio_dev, + cros_ec_sensors_push_data); +} + +static const struct platform_device_id cros_ec_sensors_ids[] = { + { + .name = "cros-ec-accel", + }, + { + .name = "cros-ec-gyro", + }, + { + .name = "cros-ec-mag", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, cros_ec_sensors_ids); + +static struct platform_driver cros_ec_sensors_platform_driver = { + .driver = { + .name = "cros-ec-sensors", + .pm = &cros_ec_sensors_pm_ops, + }, + .probe = cros_ec_sensors_probe, + .id_table = cros_ec_sensors_ids, +}; +module_platform_driver(cros_ec_sensors_platform_driver); + +MODULE_DESCRIPTION("ChromeOS EC 3-axis sensors driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c new file mode 100644 index 000000000..5133755c2 --- /dev/null +++ b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c @@ -0,0 +1,886 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * cros_ec_sensors_core - Common function for Chrome OS EC sensor driver. + * + * Copyright (C) 2016 Google, Inc + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cros_ec_sensors_trace.h" + +/* + * Hard coded to the first device to support sensor fifo. The EC has a 2048 + * byte fifo and will trigger an interrupt when fifo is 2/3 full. + */ +#define CROS_EC_FIFO_SIZE (2048 * 2 / 3) + +static int cros_ec_get_host_cmd_version_mask(struct cros_ec_device *ec_dev, + u16 cmd_offset, u16 cmd, u32 *mask) +{ + DEFINE_RAW_FLEX(struct cros_ec_command, buf, data, + MAX(sizeof(struct ec_response_get_cmd_versions), + sizeof(struct ec_params_get_cmd_versions))); + int ret; + + buf->command = EC_CMD_GET_CMD_VERSIONS + cmd_offset; + buf->insize = sizeof(struct ec_response_get_cmd_versions); + buf->outsize = sizeof(struct ec_params_get_cmd_versions); + ((struct ec_params_get_cmd_versions *)buf->data)->cmd = cmd; + + ret = cros_ec_cmd_xfer_status(ec_dev, buf); + if (ret >= 0) + *mask = ((struct ec_response_get_cmd_versions *)buf->data)->version_mask; + return ret; +} + +static void get_default_min_max_freq(enum motionsensor_type type, + u32 *min_freq, + u32 *max_freq, + u32 *max_fifo_events) +{ + /* + * We don't know fifo size, set to size previously used by older + * hardware. + */ + *max_fifo_events = CROS_EC_FIFO_SIZE; + + switch (type) { + case MOTIONSENSE_TYPE_ACCEL: + *min_freq = 12500; + *max_freq = 100000; + break; + case MOTIONSENSE_TYPE_GYRO: + *min_freq = 25000; + *max_freq = 100000; + break; + case MOTIONSENSE_TYPE_MAG: + *min_freq = 5000; + *max_freq = 25000; + break; + case MOTIONSENSE_TYPE_PROX: + case MOTIONSENSE_TYPE_LIGHT: + *min_freq = 100; + *max_freq = 50000; + break; + case MOTIONSENSE_TYPE_BARO: + *min_freq = 250; + *max_freq = 20000; + break; + case MOTIONSENSE_TYPE_ACTIVITY: + default: + *min_freq = 0; + *max_freq = 0; + break; + } +} + +static ssize_t cros_ec_sensor_set_report_latency(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + int integer, fract, ret; + int latency; + + ret = iio_str_to_fixpoint(buf, 100000, &integer, &fract); + if (ret) + return ret; + + /* EC rate is in ms. */ + latency = integer * 1000 + fract / 1000; + + mutex_lock(&st->cmd_lock); + st->param.cmd = MOTIONSENSE_CMD_EC_RATE; + st->param.ec_rate.data = min(U16_MAX, latency); + ret = cros_ec_motion_send_host_cmd(st, 0); + if (ret < 0) { + mutex_unlock(&st->cmd_lock); + return ret; + } + + /* + * Flush samples currently in the FIFO, especially when the new latency + * is shorter than the old one: new timeout value is only considered when + * there is a new sample available. It can take a while for a slow + * sensor. + */ + st->param.cmd = MOTIONSENSE_CMD_FIFO_FLUSH; + ret = cros_ec_motion_send_host_cmd(st, 0); + mutex_unlock(&st->cmd_lock); + if (ret < 0) + return ret; + + return len; +} + +static ssize_t cros_ec_sensor_get_report_latency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + int latency, ret; + + mutex_lock(&st->cmd_lock); + st->param.cmd = MOTIONSENSE_CMD_EC_RATE; + st->param.ec_rate.data = EC_MOTION_SENSE_NO_VALUE; + + ret = cros_ec_motion_send_host_cmd(st, 0); + latency = st->resp->ec_rate.ret; + mutex_unlock(&st->cmd_lock); + if (ret < 0) + return ret; + + return sprintf(buf, "%d.%06u\n", + latency / 1000, + (latency % 1000) * 1000); +} + +static IIO_DEVICE_ATTR(hwfifo_timeout, 0644, + cros_ec_sensor_get_report_latency, + cros_ec_sensor_set_report_latency, 0); + +static ssize_t hwfifo_watermark_max_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", st->fifo_max_event_count); +} + +static IIO_DEVICE_ATTR_RO(hwfifo_watermark_max, 0); + +static const struct iio_dev_attr *cros_ec_sensor_fifo_attributes[] = { + &iio_dev_attr_hwfifo_timeout, + &iio_dev_attr_hwfifo_watermark_max, + NULL, +}; + +int cros_ec_sensors_push_data(struct iio_dev *indio_dev, + s16 *data, + s64 timestamp) +{ + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + s16 *out; + s64 delta; + unsigned int i; + + /* + * Ignore samples if the buffer is not set: it is needed if the ODR is + * set but the buffer is not enabled yet. + */ + if (!iio_device_try_claim_buffer_mode(indio_dev)) + return 0; + + out = (s16 *)st->samples; + iio_for_each_active_channel(indio_dev, i) { + *out = data[i]; + out++; + } + + if (iio_device_get_clock(indio_dev) != CLOCK_BOOTTIME) + delta = iio_get_time_ns(indio_dev) - cros_ec_get_time_ns(); + else + delta = 0; + + iio_push_to_buffers_with_timestamp(indio_dev, st->samples, + timestamp + delta); + + iio_device_release_buffer_mode(indio_dev); + return 0; +} +EXPORT_SYMBOL_GPL(cros_ec_sensors_push_data); + +static void cros_ec_sensors_core_clean(void *arg) +{ + struct platform_device *pdev = (struct platform_device *)arg; + struct cros_ec_sensorhub *sensor_hub = + dev_get_drvdata(pdev->dev.parent); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + u8 sensor_num = st->param.info.sensor_num; + + cros_ec_sensorhub_unregister_push_data(sensor_hub, sensor_num); +} + +/** + * cros_ec_sensors_core_init() - basic initialization of the core structure + * @pdev: platform device created for the sensor + * @indio_dev: iio device structure of the device + * @physical_device: true if the device refers to a physical device + * @trigger_capture: function pointer to call buffer is triggered, + * for backward compatibility. + * + * Return: 0 on success, -errno on failure. + */ +int cros_ec_sensors_core_init(struct platform_device *pdev, + struct iio_dev *indio_dev, + bool physical_device, + cros_ec_sensors_capture_t trigger_capture) +{ + struct device *dev = &pdev->dev; + struct cros_ec_sensors_core_state *state = iio_priv(indio_dev); + struct cros_ec_sensorhub *sensor_hub = dev_get_drvdata(dev->parent); + struct cros_ec_dev *ec = sensor_hub->ec; + struct cros_ec_sensor_platform *sensor_platform = dev_get_platdata(dev); + u32 ver_mask, temp; + int frequencies[ARRAY_SIZE(state->frequencies) / 2] = { 0 }; + int ret, i; + + platform_set_drvdata(pdev, indio_dev); + + state->ec = ec->ec_dev; + state->msg = devm_kzalloc(&pdev->dev, sizeof(*state->msg) + + max((u16)sizeof(struct ec_params_motion_sense), + state->ec->max_response), GFP_KERNEL); + if (!state->msg) + return -ENOMEM; + + state->resp = (struct ec_response_motion_sense *)state->msg->data; + + mutex_init(&state->cmd_lock); + + ret = cros_ec_get_host_cmd_version_mask(state->ec, + ec->cmd_offset, + EC_CMD_MOTION_SENSE_CMD, + &ver_mask); + if (ret < 0) + return ret; + + /* Set up the host command structure. */ + state->msg->version = fls(ver_mask) - 1; + state->msg->command = EC_CMD_MOTION_SENSE_CMD + ec->cmd_offset; + state->msg->outsize = sizeof(struct ec_params_motion_sense); + + indio_dev->name = pdev->name; + + if (physical_device) { + enum motionsensor_location loc; + + state->param.cmd = MOTIONSENSE_CMD_INFO; + state->param.info.sensor_num = sensor_platform->sensor_num; + ret = cros_ec_motion_send_host_cmd(state, 0); + if (ret) { + dev_warn(dev, "Can not access sensor info\n"); + return ret; + } + state->type = state->resp->info.type; + loc = state->resp->info.location; + if (loc == MOTIONSENSE_LOC_BASE) + indio_dev->label = "accel-base"; + else if (loc == MOTIONSENSE_LOC_LID) + indio_dev->label = "accel-display"; + else if (loc == MOTIONSENSE_LOC_CAMERA) + indio_dev->label = "accel-camera"; + + /* Set sign vector, only used for backward compatibility. */ + memset(state->sign, 1, CROS_EC_SENSOR_MAX_AXIS); + + for (i = CROS_EC_SENSOR_X; i < CROS_EC_SENSOR_MAX_AXIS; i++) + state->calib[i].scale = MOTION_SENSE_DEFAULT_SCALE; + + /* 0 is a correct value used to stop the device */ + if (state->msg->version < 3) { + get_default_min_max_freq(state->resp->info.type, + &frequencies[1], + &frequencies[2], + &state->fifo_max_event_count); + } else { + if (state->resp->info_3.max_frequency == 0) { + get_default_min_max_freq(state->resp->info.type, + &frequencies[1], + &frequencies[2], + &temp); + } else { + frequencies[1] = state->resp->info_3.min_frequency; + frequencies[2] = state->resp->info_3.max_frequency; + } + state->fifo_max_event_count = state->resp->info_3.fifo_max_event_count; + } + for (i = 0; i < ARRAY_SIZE(frequencies); i++) { + state->frequencies[2 * i] = frequencies[i] / 1000; + state->frequencies[2 * i + 1] = + (frequencies[i] % 1000) * 1000; + } + + if (cros_ec_check_features(ec, EC_FEATURE_MOTION_SENSE_FIFO)) { + /* + * Create a software buffer, feed by the EC FIFO. + * We can not use trigger here, as events are generated + * as soon as sample_frequency is set. + */ + ret = devm_iio_kfifo_buffer_setup_ext(dev, indio_dev, NULL, + cros_ec_sensor_fifo_attributes); + if (ret) + return ret; + + /* Timestamp coming from FIFO are in ns since boot. */ + ret = iio_device_set_clock(indio_dev, CLOCK_BOOTTIME); + if (ret) + return ret; + + } else { + /* + * The only way to get samples in buffer is to set a + * software trigger (systrig, hrtimer). + */ + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + NULL, trigger_capture, NULL); + if (ret) + return ret; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(cros_ec_sensors_core_init); + +/** + * cros_ec_sensors_core_register() - Register callback to FIFO and IIO when + * sensor is ready. + * It must be called at the end of the sensor probe routine. + * @dev: device created for the sensor + * @indio_dev: iio device structure of the device + * @push_data: function to call when cros_ec_sensorhub receives + * a sample for that sensor. + * + * Return: 0 on success, -errno on failure. + */ +int cros_ec_sensors_core_register(struct device *dev, + struct iio_dev *indio_dev, + cros_ec_sensorhub_push_data_cb_t push_data) +{ + struct cros_ec_sensor_platform *sensor_platform = dev_get_platdata(dev); + struct cros_ec_sensorhub *sensor_hub = dev_get_drvdata(dev->parent); + struct platform_device *pdev = to_platform_device(dev); + struct cros_ec_dev *ec = sensor_hub->ec; + int ret; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ret; + + if (!push_data || + !cros_ec_check_features(ec, EC_FEATURE_MOTION_SENSE_FIFO)) + return 0; + + ret = cros_ec_sensorhub_register_push_data( + sensor_hub, sensor_platform->sensor_num, + indio_dev, push_data); + if (ret) + return ret; + + return devm_add_action_or_reset( + dev, cros_ec_sensors_core_clean, pdev); +} +EXPORT_SYMBOL_GPL(cros_ec_sensors_core_register); + +/** + * cros_ec_motion_send_host_cmd() - send motion sense host command + * @state: pointer to state information for device + * @opt_length: optional length to reduce the response size, useful on the data + * path. Otherwise, the maximal allowed response size is used + * + * When called, the sub-command is assumed to be set in param->cmd. + * + * Return: 0 on success, -errno on failure. + */ +int cros_ec_motion_send_host_cmd(struct cros_ec_sensors_core_state *state, + u16 opt_length) +{ + struct ec_response_motion_sense *resp = (struct ec_response_motion_sense *)state->msg->data; + int ret; + + if (opt_length) + state->msg->insize = min(opt_length, state->ec->max_response); + else + state->msg->insize = state->ec->max_response; + + memcpy(state->msg->data, &state->param, sizeof(state->param)); + + ret = cros_ec_cmd_xfer_status(state->ec, state->msg); + trace_cros_ec_motion_host_cmd(&state->param, resp, ret); + if (ret < 0) + return ret; + + if (ret && state->resp != resp) + memcpy(state->resp, resp, ret); + + return 0; +} +EXPORT_SYMBOL_GPL(cros_ec_motion_send_host_cmd); + +static ssize_t cros_ec_sensors_calibrate(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + int ret, i; + bool calibrate; + + ret = kstrtobool(buf, &calibrate); + if (ret < 0) + return ret; + + mutex_lock(&st->cmd_lock); + st->param.cmd = MOTIONSENSE_CMD_PERFORM_CALIB; + st->param.perform_calib.enable = calibrate; + ret = cros_ec_motion_send_host_cmd(st, 0); + if (ret != 0) { + dev_warn(&indio_dev->dev, "Unable to calibrate sensor: %d\n", + ret); + } else { + /* Save values */ + for (i = CROS_EC_SENSOR_X; i < CROS_EC_SENSOR_MAX_AXIS; i++) + st->calib[i].offset = st->resp->perform_calib.offset[i]; + } + mutex_unlock(&st->cmd_lock); + + return ret ? ret : len; +} + +static ssize_t cros_ec_sensors_id(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, char *buf) +{ + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", st->param.info.sensor_num); +} + +const struct iio_chan_spec_ext_info cros_ec_sensors_ext_info[] = { + { + .name = "calibrate", + .shared = IIO_SHARED_BY_ALL, + .write = cros_ec_sensors_calibrate + }, + { + .name = "id", + .shared = IIO_SHARED_BY_ALL, + .read = cros_ec_sensors_id + }, + { } +}; +EXPORT_SYMBOL_GPL(cros_ec_sensors_ext_info); + +const struct iio_chan_spec_ext_info cros_ec_sensors_limited_info[] = { + { + .name = "id", + .shared = IIO_SHARED_BY_ALL, + .read = cros_ec_sensors_id + }, + { } +}; +EXPORT_SYMBOL_GPL(cros_ec_sensors_limited_info); + +/** + * cros_ec_sensors_idx_to_reg - convert index into offset in shared memory + * @st: pointer to state information for device + * @idx: sensor index (should be element of enum sensor_index) + * + * Return: address to read at + */ +static unsigned int cros_ec_sensors_idx_to_reg( + struct cros_ec_sensors_core_state *st, + unsigned int idx) +{ + /* + * When using LPC interface, only space for 2 Accel and one Gyro. + * First halfword of MOTIONSENSE_TYPE_ACCEL is used by angle. + */ + if (st->type == MOTIONSENSE_TYPE_ACCEL) + return EC_MEMMAP_ACC_DATA + sizeof(u16) * + (1 + idx + st->param.info.sensor_num * + CROS_EC_SENSOR_MAX_AXIS); + + return EC_MEMMAP_GYRO_DATA + sizeof(u16) * idx; +} + +static int cros_ec_sensors_cmd_read_u8(struct cros_ec_device *ec, + unsigned int offset, u8 *dest) +{ + return ec->cmd_readmem(ec, offset, 1, dest); +} + +static int cros_ec_sensors_cmd_read_u16(struct cros_ec_device *ec, + unsigned int offset, u16 *dest) +{ + __le16 tmp; + int ret = ec->cmd_readmem(ec, offset, 2, &tmp); + + if (ret >= 0) + *dest = le16_to_cpu(tmp); + + return ret; +} + +/** + * cros_ec_sensors_read_until_not_busy() - read until is not busy + * + * @st: pointer to state information for device + * + * Read from EC status byte until it reads not busy. + * Return: 8-bit status if ok, -errno on failure. + */ +static int cros_ec_sensors_read_until_not_busy( + struct cros_ec_sensors_core_state *st) +{ + struct cros_ec_device *ec = st->ec; + u8 status; + int ret, attempts = 0; + + ret = cros_ec_sensors_cmd_read_u8(ec, EC_MEMMAP_ACC_STATUS, &status); + if (ret < 0) + return ret; + + while (status & EC_MEMMAP_ACC_STATUS_BUSY_BIT) { + /* Give up after enough attempts, return error. */ + if (attempts++ >= 50) + return -EIO; + + /* Small delay every so often. */ + if (attempts % 5 == 0) + msleep(25); + + ret = cros_ec_sensors_cmd_read_u8(ec, EC_MEMMAP_ACC_STATUS, + &status); + if (ret < 0) + return ret; + } + + return status; +} + +/** + * cros_ec_sensors_read_data_unsafe() - read acceleration data from EC shared memory + * @indio_dev: pointer to IIO device + * @scan_mask: bitmap of the sensor indices to scan + * @data: location to store data + * + * This is the unsafe function for reading the EC data. It does not guarantee + * that the EC will not modify the data as it is being read in. + * + * Return: 0 on success, -errno on failure. + */ +static int cros_ec_sensors_read_data_unsafe(struct iio_dev *indio_dev, + unsigned long scan_mask, s16 *data) +{ + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + struct cros_ec_device *ec = st->ec; + unsigned int i; + int ret; + + /* Read all sensors enabled in scan_mask. Each value is 2 bytes. */ + for_each_set_bit(i, &scan_mask, iio_get_masklength(indio_dev)) { + ret = cros_ec_sensors_cmd_read_u16(ec, + cros_ec_sensors_idx_to_reg(st, i), + data); + if (ret < 0) + return ret; + + *data *= st->sign[i]; + data++; + } + + return 0; +} + +/** + * cros_ec_sensors_read_lpc() - read acceleration data from EC shared memory. + * @indio_dev: pointer to IIO device. + * @scan_mask: bitmap of the sensor indices to scan. + * @data: location to store data. + * + * Note: this is the safe function for reading the EC data. It guarantees + * that the data sampled was not modified by the EC while being read. + * + * Return: 0 on success, -errno on failure. + */ +int cros_ec_sensors_read_lpc(struct iio_dev *indio_dev, + unsigned long scan_mask, s16 *data) +{ + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + struct cros_ec_device *ec = st->ec; + u8 samp_id = 0xff, status = 0; + int ret, attempts = 0; + + /* + * Continually read all data from EC until the status byte after + * all reads reflects that the EC is not busy and the sample id + * matches the sample id from before all reads. This guarantees + * that data read in was not modified by the EC while reading. + */ + while ((status & (EC_MEMMAP_ACC_STATUS_BUSY_BIT | + EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK)) != samp_id) { + /* If we have tried to read too many times, return error. */ + if (attempts++ >= 5) + return -EIO; + + /* Read status byte until EC is not busy. */ + ret = cros_ec_sensors_read_until_not_busy(st); + if (ret < 0) + return ret; + + /* + * Store the current sample id so that we can compare to the + * sample id after reading the data. + */ + samp_id = ret & EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK; + + /* Read all EC data, format it, and store it into data. */ + ret = cros_ec_sensors_read_data_unsafe(indio_dev, scan_mask, + data); + if (ret < 0) + return ret; + + /* Read status byte. */ + ret = cros_ec_sensors_cmd_read_u8(ec, EC_MEMMAP_ACC_STATUS, + &status); + if (ret < 0) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(cros_ec_sensors_read_lpc); + +/** + * cros_ec_sensors_read_cmd() - retrieve data using the EC command protocol + * @indio_dev: pointer to IIO device + * @scan_mask: bitmap of the sensor indices to scan + * @data: location to store data + * + * Return: 0 on success, -errno on failure. + */ +int cros_ec_sensors_read_cmd(struct iio_dev *indio_dev, + unsigned long scan_mask, s16 *data) +{ + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + int ret; + unsigned int i; + + /* Read all sensor data through a command. */ + st->param.cmd = MOTIONSENSE_CMD_DATA; + ret = cros_ec_motion_send_host_cmd(st, sizeof(st->resp->data)); + if (ret != 0) { + dev_warn(&indio_dev->dev, "Unable to read sensor data\n"); + return ret; + } + + for_each_set_bit(i, &scan_mask, iio_get_masklength(indio_dev)) { + *data = st->resp->data.data[i]; + data++; + } + + return 0; +} +EXPORT_SYMBOL_GPL(cros_ec_sensors_read_cmd); + +/** + * cros_ec_sensors_capture() - the trigger handler function + * @irq: the interrupt number. + * @p: a pointer to the poll function. + * + * On a trigger event occurring, if the pollfunc is attached then this + * handler is called as a threaded interrupt (and hence may sleep). It + * is responsible for grabbing data from the device and pushing it into + * the associated buffer. + * + * Return: IRQ_HANDLED + */ +irqreturn_t cros_ec_sensors_capture(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->cmd_lock); + + /* Clear capture data. */ + memset(st->samples, 0, indio_dev->scan_bytes); + + /* Read data based on which channels are enabled in scan mask. */ + ret = st->read_ec_sensors_data(indio_dev, + *(indio_dev->active_scan_mask), + (s16 *)st->samples); + if (ret < 0) + goto done; + + iio_push_to_buffers_with_timestamp(indio_dev, st->samples, + iio_get_time_ns(indio_dev)); + +done: + /* + * Tell the core we are done with this trigger and ready for the + * next one. + */ + iio_trigger_notify_done(indio_dev->trig); + + mutex_unlock(&st->cmd_lock); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL_GPL(cros_ec_sensors_capture); + +/** + * cros_ec_sensors_core_read() - function to request a value from the sensor + * @st: pointer to state information for device + * @chan: channel specification structure table + * @val: will contain one element making up the returned value + * @val2: will contain another element making up the returned value + * @mask: specifies which values to be requested + * + * Return: the type of value returned by the device + */ +int cros_ec_sensors_core_read(struct cros_ec_sensors_core_state *st, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret, frequency; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + st->param.cmd = MOTIONSENSE_CMD_SENSOR_ODR; + st->param.sensor_odr.data = + EC_MOTION_SENSE_NO_VALUE; + + ret = cros_ec_motion_send_host_cmd(st, 0); + if (ret) + break; + + frequency = st->resp->sensor_odr.ret; + *val = frequency / 1000; + *val2 = (frequency % 1000) * 1000; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} +EXPORT_SYMBOL_GPL(cros_ec_sensors_core_read); + +/** + * cros_ec_sensors_core_read_avail() - get available values + * @indio_dev: pointer to state information for device + * @chan: channel specification structure table + * @vals: list of available values + * @type: type of data returned + * @length: number of data returned in the array + * @mask: specifies which values to be requested + * + * Return: an error code, IIO_AVAIL_RANGE or IIO_AVAIL_LIST + */ +int cros_ec_sensors_core_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, + int *type, + int *length, + long mask) +{ + struct cros_ec_sensors_core_state *state = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *length = ARRAY_SIZE(state->frequencies); + *vals = (const int *)&state->frequencies; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(cros_ec_sensors_core_read_avail); + +/** + * cros_ec_sensors_core_write() - function to write a value to the sensor + * @st: pointer to state information for device + * @chan: channel specification structure table + * @val: first part of value to write + * @val2: second part of value to write + * @mask: specifies which values to write + * + * Return: the type of value returned by the device + */ +int cros_ec_sensors_core_write(struct cros_ec_sensors_core_state *st, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret, frequency; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + frequency = val * 1000 + val2 / 1000; + st->param.cmd = MOTIONSENSE_CMD_SENSOR_ODR; + st->param.sensor_odr.data = frequency; + + /* Always roundup, so caller gets at least what it asks for. */ + st->param.sensor_odr.roundup = 1; + + ret = cros_ec_motion_send_host_cmd(st, 0); + if (ret) + break; + + /* Flush the FIFO when a sensor is stopped. + * If the FIFO has just been emptied, pending samples will be + * stuck until new samples are available. It will not happen + * when all the sensors are stopped. + */ + if (frequency == 0) { + st->param.cmd = MOTIONSENSE_CMD_FIFO_FLUSH; + ret = cros_ec_motion_send_host_cmd(st, 0); + } + break; + default: + ret = -EINVAL; + break; + } + return ret; +} +EXPORT_SYMBOL_GPL(cros_ec_sensors_core_write); + +static int __maybe_unused cros_ec_sensors_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + int ret = 0; + + if (st->range_updated) { + mutex_lock(&st->cmd_lock); + st->param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE; + st->param.sensor_range.data = st->curr_range; + st->param.sensor_range.roundup = 1; + ret = cros_ec_motion_send_host_cmd(st, 0); + mutex_unlock(&st->cmd_lock); + } + return ret; +} + +SIMPLE_DEV_PM_OPS(cros_ec_sensors_pm_ops, NULL, cros_ec_sensors_resume); +EXPORT_SYMBOL_GPL(cros_ec_sensors_pm_ops); + +MODULE_DESCRIPTION("ChromeOS EC sensor hub core functions"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_trace.c b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_trace.c new file mode 100644 index 000000000..c4db949fa --- /dev/null +++ b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_trace.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0 +// Trace events for the ChromeOS Embedded Controller +// +// Copyright 2025 Google LLC. + +#define TRACE_SYMBOL(a) {a, #a} + +// Generate the list using the following script: +// sed -n 's/^.*\(MOTIONSENSE_CMD.*\) = .*,$/\tTRACE_SYMBOL(\1), \\/p' include/linux/platform_data/cros_ec_commands.h +#define MOTIONSENSE_CMDS \ + TRACE_SYMBOL(MOTIONSENSE_CMD_DUMP), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_INFO), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_EC_RATE), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_SENSOR_ODR), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_SENSOR_RANGE), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_KB_WAKE_ANGLE), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_DATA), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_FIFO_INFO), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_FIFO_FLUSH), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_FIFO_READ), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_PERFORM_CALIB), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_SENSOR_OFFSET), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_LIST_ACTIVITIES), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_SET_ACTIVITY), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_LID_ANGLE), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_FIFO_INT_ENABLE), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_SPOOF), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_TABLET_MODE_LID_ANGLE), \ + TRACE_SYMBOL(MOTIONSENSE_CMD_SENSOR_SCALE) + +#define CREATE_TRACE_POINTS +#include "cros_ec_sensors_trace.h" diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_trace.h b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_trace.h new file mode 100644 index 000000000..8956f2e8a --- /dev/null +++ b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_trace.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Trace events for the ChromeOS Embedded Controller + * + * Copyright 2025 Google LLC. + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM cros_ec + +#if !defined(_CROS_EC_SENSORS_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ) +#define _CROS_EC_SENSORS_TRACE_H_ + +#include +#include +#include +#include + +#include + +TRACE_EVENT(cros_ec_motion_host_cmd, + TP_PROTO(struct ec_params_motion_sense *param, + struct ec_response_motion_sense *resp, + int retval), + TP_ARGS(param, resp, retval), + TP_STRUCT__entry(__field(uint8_t, cmd) + __field(uint8_t, sensor_id) + __field(uint32_t, data) + __field(int, retval) + __field(int32_t, ret) + ), + TP_fast_assign(__entry->cmd = param->cmd; + __entry->sensor_id = param->sensor_odr.sensor_num; + __entry->data = param->sensor_odr.data; + __entry->retval = retval; + __entry->ret = retval > 0 ? resp->sensor_odr.ret : -1; + ), + TP_printk("%s, id: %d, data: %u, result: %u, return: %d", + __print_symbolic(__entry->cmd, MOTIONSENSE_CMDS), + __entry->sensor_id, + __entry->data, + __entry->retval, + __entry->ret) +); + +#endif /* _CROS_EC_SENSORS_TRACE_H_ */ + +/* this part must be outside header guard */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH ../../drivers/iio/common/cros_ec_sensors + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE cros_ec_sensors_trace + +#include diff --git a/drivers/iio/common/hid-sensors/Kconfig b/drivers/iio/common/hid-sensors/Kconfig new file mode 100644 index 000000000..2a3dd3b90 --- /dev/null +++ b/drivers/iio/common/hid-sensors/Kconfig @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Hid Sensor common modules +# +menu "Hid Sensor IIO Common" + +config HID_SENSOR_IIO_COMMON + tristate "Common modules for all HID Sensor IIO drivers" + depends on HID_SENSOR_HUB + select HID_SENSOR_IIO_TRIGGER if IIO_BUFFER + help + Say yes here to build support for HID sensor to use + HID sensor common processing for attributes and IIO triggers. + There are many attributes which can be shared among multiple + HID sensor drivers, this module contains processing for those + attributes. + +config HID_SENSOR_IIO_TRIGGER + tristate "Common module (trigger) for all HID Sensor IIO drivers" + depends on HID_SENSOR_HUB && HID_SENSOR_IIO_COMMON && IIO_BUFFER + select IIO_TRIGGER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build trigger support for HID sensors. + Triggers will be send if all requested attributes were read. + + If this driver is compiled as a module, it will be named + hid-sensor-trigger. + +endmenu diff --git a/drivers/iio/common/hid-sensors/Makefile b/drivers/iio/common/hid-sensors/Makefile new file mode 100644 index 000000000..64b01a81f --- /dev/null +++ b/drivers/iio/common/hid-sensors/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the Hid sensor common modules. +# + +obj-$(CONFIG_HID_SENSOR_IIO_COMMON) += hid-sensor-iio-common.o +obj-$(CONFIG_HID_SENSOR_IIO_TRIGGER) += hid-sensor-trigger.o +hid-sensor-iio-common-y := hid-sensor-attributes.o diff --git a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c new file mode 100644 index 000000000..9e7c26e37 --- /dev/null +++ b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c @@ -0,0 +1,593 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HID Sensors Driver + * Copyright (c) 2012, Intel Corporation. + */ +#include +#include +#include +#include + +#include +#include + +static const struct { + u32 usage_id; + int unit; /* 0 for default others from HID sensor spec */ + int scale_val0; /* scale, whole number */ + int scale_val1; /* scale, fraction in nanos */ +} unit_conversion[] = { + {HID_USAGE_SENSOR_ACCEL_3D, 0, 9, 806650000}, + {HID_USAGE_SENSOR_ACCEL_3D, + HID_USAGE_SENSOR_UNITS_METERS_PER_SEC_SQRD, 1, 0}, + {HID_USAGE_SENSOR_ACCEL_3D, + HID_USAGE_SENSOR_UNITS_G, 9, 806650000}, + + {HID_USAGE_SENSOR_GRAVITY_VECTOR, 0, 9, 806650000}, + {HID_USAGE_SENSOR_GRAVITY_VECTOR, + HID_USAGE_SENSOR_UNITS_METERS_PER_SEC_SQRD, 1, 0}, + {HID_USAGE_SENSOR_GRAVITY_VECTOR, + HID_USAGE_SENSOR_UNITS_G, 9, 806650000}, + + {HID_USAGE_SENSOR_GYRO_3D, 0, 0, 17453293}, + {HID_USAGE_SENSOR_GYRO_3D, + HID_USAGE_SENSOR_UNITS_RADIANS_PER_SECOND, 1, 0}, + {HID_USAGE_SENSOR_GYRO_3D, + HID_USAGE_SENSOR_UNITS_DEGREES_PER_SECOND, 0, 17453293}, + + {HID_USAGE_SENSOR_COMPASS_3D, 0, 0, 1000000}, + {HID_USAGE_SENSOR_COMPASS_3D, HID_USAGE_SENSOR_UNITS_GAUSS, 1, 0}, + + {HID_USAGE_SENSOR_INCLINOMETER_3D, 0, 0, 17453293}, + {HID_USAGE_SENSOR_INCLINOMETER_3D, + HID_USAGE_SENSOR_UNITS_DEGREES, 0, 17453293}, + {HID_USAGE_SENSOR_INCLINOMETER_3D, + HID_USAGE_SENSOR_UNITS_RADIANS, 1, 0}, + + {HID_USAGE_SENSOR_ALS, 0, 1, 0}, + {HID_USAGE_SENSOR_ALS, HID_USAGE_SENSOR_UNITS_LUX, 1, 0}, + + {HID_USAGE_SENSOR_PRESSURE, 0, 100, 0}, + {HID_USAGE_SENSOR_PRESSURE, HID_USAGE_SENSOR_UNITS_PASCAL, 0, 1000000}, + + {HID_USAGE_SENSOR_TIME_TIMESTAMP, 0, 1000000000, 0}, + {HID_USAGE_SENSOR_TIME_TIMESTAMP, HID_USAGE_SENSOR_UNITS_MILLISECOND, + 1000000, 0}, + + {HID_USAGE_SENSOR_DEVICE_ORIENTATION, 0, 1, 0}, + + {HID_USAGE_SENSOR_RELATIVE_ORIENTATION, 0, 1, 0}, + + {HID_USAGE_SENSOR_GEOMAGNETIC_ORIENTATION, 0, 1, 0}, + + {HID_USAGE_SENSOR_TEMPERATURE, 0, 1000, 0}, + {HID_USAGE_SENSOR_TEMPERATURE, HID_USAGE_SENSOR_UNITS_DEGREES, 1000, 0}, + + {HID_USAGE_SENSOR_HUMIDITY, 0, 1000, 0}, + {HID_USAGE_SENSOR_HINGE, 0, 0, 17453293}, + {HID_USAGE_SENSOR_HINGE, HID_USAGE_SENSOR_UNITS_DEGREES, 0, 17453293}, + + {HID_USAGE_SENSOR_HUMAN_PRESENCE, 0, 1, 0}, + {HID_USAGE_SENSOR_HUMAN_PROXIMITY, 0, 1, 0}, + {HID_USAGE_SENSOR_HUMAN_ATTENTION, 0, 1, 0}, +}; + +static void simple_div(int dividend, int divisor, int *whole, int *micro_frac) +{ + int rem; + int exp = 0; + + *micro_frac = 0; + if (divisor == 0) { + *whole = 0; + return; + } + *whole = dividend/divisor; + rem = dividend % divisor; + if (rem) { + while (rem <= divisor) { + rem *= 10; + exp++; + } + *micro_frac = (rem / divisor) * int_pow(10, 6 - exp); + } +} + +static void split_micro_fraction(unsigned int no, int exp, int *val1, int *val2) +{ + int divisor = int_pow(10, exp); + + *val1 = no / divisor; + *val2 = no % divisor * int_pow(10, 6 - exp); +} + +/* +VTF format uses exponent and variable size format. +For example if the size is 2 bytes +0x0067 with VTF16E14 format -> +1.03 +To convert just change to 0x67 to decimal and use two decimal as E14 stands +for 10^-2. +Negative numbers are 2's complement +*/ +static void convert_from_vtf_format(u32 value, int size, int exp, + int *val1, int *val2) +{ + int sign = 1; + + if (value & BIT(size*8 - 1)) { + value = ((1LL << (size * 8)) - value); + sign = -1; + } + exp = hid_sensor_convert_exponent(exp); + if (exp >= 0) { + *val1 = sign * value * int_pow(10, exp); + *val2 = 0; + } else { + split_micro_fraction(value, -exp, val1, val2); + if (*val1) + *val1 = sign * (*val1); + else + *val2 = sign * (*val2); + } +} + +static u32 convert_to_vtf_format(int size, int exp, int val1, int val2) +{ + int divisor; + u32 value; + int sign = 1; + + if (val1 < 0 || val2 < 0) + sign = -1; + exp = hid_sensor_convert_exponent(exp); + if (exp < 0) { + divisor = int_pow(10, 6 + exp); + value = abs(val1) * int_pow(10, -exp); + value += abs(val2) / divisor; + } else { + divisor = int_pow(10, exp); + value = abs(val1) / divisor; + } + if (sign < 0) + value = ((1LL << (size * 8)) - value); + + return value; +} + +s32 hid_sensor_read_poll_value(struct hid_sensor_common *st) +{ + s32 value = 0; + int ret; + + ret = sensor_hub_get_feature(st->hsdev, + st->poll.report_id, + st->poll.index, sizeof(value), &value); + + if (ret < 0 || value < 0) { + return -EINVAL; + } else { + if (st->poll.units == HID_USAGE_SENSOR_UNITS_SECOND) + value = value * 1000; + } + + return value; +} +EXPORT_SYMBOL_NS(hid_sensor_read_poll_value, "IIO_HID_ATTRIBUTES"); + +int hid_sensor_read_samp_freq_value(struct hid_sensor_common *st, + int *val1, int *val2) +{ + s32 value; + int ret; + + ret = sensor_hub_get_feature(st->hsdev, + st->poll.report_id, + st->poll.index, sizeof(value), &value); + if (ret < 0 || value < 0) { + *val1 = *val2 = 0; + return -EINVAL; + } else { + if (st->poll.units == HID_USAGE_SENSOR_UNITS_MILLISECOND) + simple_div(1000, value, val1, val2); + else if (st->poll.units == HID_USAGE_SENSOR_UNITS_SECOND) + simple_div(1, value, val1, val2); + else { + *val1 = *val2 = 0; + return -EINVAL; + } + } + + return IIO_VAL_INT_PLUS_MICRO; +} +EXPORT_SYMBOL_NS(hid_sensor_read_samp_freq_value, "IIO_HID"); + +int hid_sensor_write_samp_freq_value(struct hid_sensor_common *st, + int val1, int val2) +{ + s32 value; + int ret; + + if (val1 < 0 || val2 < 0) + return -EINVAL; + + value = val1 * HZ_PER_MHZ + val2; + if (value) { + if (st->poll.units == HID_USAGE_SENSOR_UNITS_MILLISECOND) + value = NSEC_PER_SEC / value; + else if (st->poll.units == HID_USAGE_SENSOR_UNITS_SECOND) + value = USEC_PER_SEC / value; + else + value = 0; + } + ret = sensor_hub_set_feature(st->hsdev, st->poll.report_id, + st->poll.index, sizeof(value), &value); + if (ret < 0 || value < 0) + return -EINVAL; + + ret = sensor_hub_get_feature(st->hsdev, + st->poll.report_id, + st->poll.index, sizeof(value), &value); + if (ret < 0 || value < 0) + return -EINVAL; + + st->poll_interval = value; + + return 0; +} +EXPORT_SYMBOL_NS(hid_sensor_write_samp_freq_value, "IIO_HID"); + +int hid_sensor_read_raw_hyst_value(struct hid_sensor_common *st, + int *val1, int *val2) +{ + s32 value; + int ret; + + ret = sensor_hub_get_feature(st->hsdev, + st->sensitivity.report_id, + st->sensitivity.index, + sizeof(value), &value); + if (ret < 0 || value < 0) { + *val1 = *val2 = 0; + return -EINVAL; + } else { + convert_from_vtf_format(value, st->sensitivity.size, + st->sensitivity.unit_expo, + val1, val2); + } + + return IIO_VAL_INT_PLUS_MICRO; +} +EXPORT_SYMBOL_NS(hid_sensor_read_raw_hyst_value, "IIO_HID"); + +int hid_sensor_read_raw_hyst_rel_value(struct hid_sensor_common *st, int *val1, + int *val2) +{ + s32 value; + int ret; + + ret = sensor_hub_get_feature(st->hsdev, + st->sensitivity_rel.report_id, + st->sensitivity_rel.index, + sizeof(value), &value); + if (ret < 0 || value < 0) { + *val1 = *val2 = 0; + return -EINVAL; + } + + convert_from_vtf_format(value, st->sensitivity_rel.size, + st->sensitivity_rel.unit_expo, val1, val2); + + return IIO_VAL_INT_PLUS_MICRO; +} +EXPORT_SYMBOL_NS(hid_sensor_read_raw_hyst_rel_value, "IIO_HID"); + +int hid_sensor_write_raw_hyst_value(struct hid_sensor_common *st, + int val1, int val2) +{ + s32 value; + int ret; + + if (val1 < 0 || val2 < 0) + return -EINVAL; + + value = convert_to_vtf_format(st->sensitivity.size, + st->sensitivity.unit_expo, + val1, val2); + ret = sensor_hub_set_feature(st->hsdev, st->sensitivity.report_id, + st->sensitivity.index, sizeof(value), + &value); + if (ret < 0 || value < 0) + return -EINVAL; + + ret = sensor_hub_get_feature(st->hsdev, + st->sensitivity.report_id, + st->sensitivity.index, + sizeof(value), &value); + if (ret < 0 || value < 0) + return -EINVAL; + + st->raw_hystersis = value; + + return 0; +} +EXPORT_SYMBOL_NS(hid_sensor_write_raw_hyst_value, "IIO_HID"); + +int hid_sensor_write_raw_hyst_rel_value(struct hid_sensor_common *st, + int val1, int val2) +{ + s32 value; + int ret; + + if (val1 < 0 || val2 < 0) + return -EINVAL; + + value = convert_to_vtf_format(st->sensitivity_rel.size, + st->sensitivity_rel.unit_expo, + val1, val2); + ret = sensor_hub_set_feature(st->hsdev, st->sensitivity_rel.report_id, + st->sensitivity_rel.index, sizeof(value), + &value); + if (ret < 0 || value < 0) + return -EINVAL; + + ret = sensor_hub_get_feature(st->hsdev, + st->sensitivity_rel.report_id, + st->sensitivity_rel.index, + sizeof(value), &value); + if (ret < 0 || value < 0) + return -EINVAL; + + st->raw_hystersis = value; + + return 0; +} +EXPORT_SYMBOL_NS(hid_sensor_write_raw_hyst_rel_value, "IIO_HID"); + +/* + * This function applies the unit exponent to the scale. + * For example: + * 9.806650000 ->exp:2-> val0[980]val1[665000000] + * 9.000806000 ->exp:2-> val0[900]val1[80600000] + * 0.174535293 ->exp:2-> val0[17]val1[453529300] + * 1.001745329 ->exp:0-> val0[1]val1[1745329] + * 1.001745329 ->exp:2-> val0[100]val1[174532900] + * 1.001745329 ->exp:4-> val0[10017]val1[453290000] + * 9.806650000 ->exp:-2-> val0[0]val1[98066500] + */ +static void adjust_exponent_nano(int *val0, int *val1, + int scale0, int scale1, int exp) +{ + int divisor; + int i; + int x; + int res; + int rem; + + if (exp > 0) { + *val0 = scale0 * int_pow(10, exp); + res = 0; + if (exp > 9) { + *val1 = 0; + return; + } + for (i = 0; i < exp; ++i) { + divisor = int_pow(10, 8 - i); + x = scale1 / divisor; + res += int_pow(10, exp - 1 - i) * x; + scale1 = scale1 % divisor; + } + *val0 += res; + *val1 = scale1 * int_pow(10, exp); + } else if (exp < 0) { + exp = abs(exp); + if (exp > 9) { + *val0 = *val1 = 0; + return; + } + divisor = int_pow(10, exp); + *val0 = scale0 / divisor; + rem = scale0 % divisor; + res = 0; + for (i = 0; i < (9 - exp); ++i) { + divisor = int_pow(10, 8 - i); + x = scale1 / divisor; + res += int_pow(10, 8 - exp - i) * x; + scale1 = scale1 % divisor; + } + *val1 = rem * int_pow(10, 9 - exp) + res; + } else { + *val0 = scale0; + *val1 = scale1; + } +} + +int hid_sensor_format_scale(u32 usage_id, + struct hid_sensor_hub_attribute_info *attr_info, + int *val0, int *val1) +{ + int i; + int exp; + + *val0 = 1; + *val1 = 0; + + for (i = 0; i < ARRAY_SIZE(unit_conversion); ++i) { + if (unit_conversion[i].usage_id == usage_id && + unit_conversion[i].unit == attr_info->units) { + exp = hid_sensor_convert_exponent(attr_info->unit_expo); + adjust_exponent_nano(val0, val1, + unit_conversion[i].scale_val0, + unit_conversion[i].scale_val1, exp); + break; + } + } + + return IIO_VAL_INT_PLUS_NANO; +} +EXPORT_SYMBOL_NS(hid_sensor_format_scale, "IIO_HID"); + +int64_t hid_sensor_convert_timestamp(struct hid_sensor_common *st, + int64_t raw_value) +{ + return st->timestamp_ns_scale * raw_value; +} +EXPORT_SYMBOL_NS(hid_sensor_convert_timestamp, "IIO_HID"); + +static +int hid_sensor_get_reporting_interval(struct hid_sensor_hub_device *hsdev, + u32 usage_id, + struct hid_sensor_common *st) +{ + sensor_hub_input_get_attribute_info(hsdev, + HID_FEATURE_REPORT, usage_id, + HID_USAGE_SENSOR_PROP_REPORT_INTERVAL, + &st->poll); + /* Default unit of measure is milliseconds */ + if (st->poll.units == 0) + st->poll.units = HID_USAGE_SENSOR_UNITS_MILLISECOND; + + st->poll_interval = -1; + + return 0; + +} + +static void hid_sensor_get_report_latency_info(struct hid_sensor_hub_device *hsdev, + u32 usage_id, + struct hid_sensor_common *st) +{ + sensor_hub_input_get_attribute_info(hsdev, HID_FEATURE_REPORT, + usage_id, + HID_USAGE_SENSOR_PROP_REPORT_LATENCY, + &st->report_latency); + + hid_dbg(hsdev->hdev, "Report latency attributes: %x:%x\n", + st->report_latency.index, st->report_latency.report_id); +} + +int hid_sensor_get_report_latency(struct hid_sensor_common *st) +{ + int ret; + int value; + + ret = sensor_hub_get_feature(st->hsdev, + st->report_latency.report_id, + st->report_latency.index, + sizeof(value), &value); + if (ret < 0) + return ret; + + return value; +} +EXPORT_SYMBOL_NS(hid_sensor_get_report_latency, "IIO_HID_ATTRIBUTES"); + +int hid_sensor_set_report_latency(struct hid_sensor_common *st, int latency_ms) +{ + return sensor_hub_set_feature(st->hsdev, st->report_latency.report_id, + st->report_latency.index, + sizeof(latency_ms), &latency_ms); +} +EXPORT_SYMBOL_NS(hid_sensor_set_report_latency, "IIO_HID_ATTRIBUTES"); + +bool hid_sensor_batch_mode_supported(struct hid_sensor_common *st) +{ + return st->report_latency.index > 0 && st->report_latency.report_id > 0; +} +EXPORT_SYMBOL_NS(hid_sensor_batch_mode_supported, "IIO_HID_ATTRIBUTES"); + +int hid_sensor_parse_common_attributes(struct hid_sensor_hub_device *hsdev, + u32 usage_id, + struct hid_sensor_common *st, + const u32 *sensitivity_addresses, + u32 sensitivity_addresses_len) +{ + + struct hid_sensor_hub_attribute_info timestamp; + s32 value; + int ret; + int i; + + hid_sensor_get_reporting_interval(hsdev, usage_id, st); + + sensor_hub_input_get_attribute_info(hsdev, + HID_FEATURE_REPORT, usage_id, + HID_USAGE_SENSOR_PROP_REPORT_STATE, + &st->report_state); + + sensor_hub_input_get_attribute_info(hsdev, + HID_FEATURE_REPORT, usage_id, + HID_USAGE_SENSOR_PROY_POWER_STATE, + &st->power_state); + + st->power_state.logical_minimum = 1; + st->report_state.logical_minimum = 1; + + sensor_hub_input_get_attribute_info(hsdev, + HID_FEATURE_REPORT, usage_id, + HID_USAGE_SENSOR_PROP_SENSITIVITY_ABS, + &st->sensitivity); + + sensor_hub_input_get_attribute_info(hsdev, + HID_FEATURE_REPORT, usage_id, + HID_USAGE_SENSOR_PROP_SENSITIVITY_REL_PCT, + &st->sensitivity_rel); + /* + * Set Sensitivity field ids, when there is no individual modifier, will + * check absolute sensitivity and relative sensitivity of data field + */ + for (i = 0; i < sensitivity_addresses_len; i++) { + if (st->sensitivity.index < 0) + sensor_hub_input_get_attribute_info( + hsdev, HID_FEATURE_REPORT, usage_id, + HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS | + sensitivity_addresses[i], + &st->sensitivity); + + if (st->sensitivity_rel.index < 0) + sensor_hub_input_get_attribute_info( + hsdev, HID_FEATURE_REPORT, usage_id, + HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_REL_PCT | + sensitivity_addresses[i], + &st->sensitivity_rel); + } + + st->raw_hystersis = -1; + + sensor_hub_input_get_attribute_info(hsdev, + HID_INPUT_REPORT, usage_id, + HID_USAGE_SENSOR_TIME_TIMESTAMP, + ×tamp); + if (timestamp.index >= 0 && timestamp.report_id) { + int val0, val1; + + hid_sensor_format_scale(HID_USAGE_SENSOR_TIME_TIMESTAMP, + ×tamp, &val0, &val1); + st->timestamp_ns_scale = val0; + } else + st->timestamp_ns_scale = 1000000000; + + hid_sensor_get_report_latency_info(hsdev, usage_id, st); + + hid_dbg(hsdev->hdev, "common attributes: %x:%x, %x:%x, %x:%x %x:%x %x:%x\n", + st->poll.index, st->poll.report_id, + st->report_state.index, st->report_state.report_id, + st->power_state.index, st->power_state.report_id, + st->sensitivity.index, st->sensitivity.report_id, + timestamp.index, timestamp.report_id); + + ret = sensor_hub_get_feature(hsdev, + st->power_state.report_id, + st->power_state.index, + sizeof(value), &value); + if (ret < 0) + return ret; + if (value < 0) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL_NS(hid_sensor_parse_common_attributes, "IIO_HID"); + +MODULE_AUTHOR("Srinivas Pandruvada "); +MODULE_DESCRIPTION("HID Sensor common attribute processing"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c new file mode 100644 index 000000000..60808e243 --- /dev/null +++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HID Sensors Driver + * Copyright (c) 2012, Intel Corporation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "hid-sensor-trigger.h" + +static ssize_t _hid_sensor_set_report_latency(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct hid_sensor_common *attrb = iio_device_get_drvdata(indio_dev); + int integer, fract, ret; + int latency; + + ret = iio_str_to_fixpoint(buf, 100000, &integer, &fract); + if (ret) + return ret; + + latency = integer * 1000 + fract / 1000; + ret = hid_sensor_set_report_latency(attrb, latency); + if (ret < 0) + return ret; + + attrb->latency_ms = hid_sensor_get_report_latency(attrb); + + return len; +} + +static ssize_t _hid_sensor_get_report_latency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct hid_sensor_common *attrb = iio_device_get_drvdata(indio_dev); + int latency; + + latency = hid_sensor_get_report_latency(attrb); + if (latency < 0) + return latency; + + return sprintf(buf, "%d.%06u\n", latency / 1000, (latency % 1000) * 1000); +} + +static ssize_t _hid_sensor_get_fifo_state(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct hid_sensor_common *attrb = iio_device_get_drvdata(indio_dev); + int latency; + + latency = hid_sensor_get_report_latency(attrb); + if (latency < 0) + return latency; + + return sprintf(buf, "%d\n", !!latency); +} + +static IIO_DEVICE_ATTR(hwfifo_timeout, 0644, + _hid_sensor_get_report_latency, + _hid_sensor_set_report_latency, 0); +static IIO_DEVICE_ATTR(hwfifo_enabled, 0444, + _hid_sensor_get_fifo_state, NULL, 0); + +static const struct iio_dev_attr *hid_sensor_fifo_attributes[] = { + &iio_dev_attr_hwfifo_timeout, + &iio_dev_attr_hwfifo_enabled, + NULL, +}; + +static int _hid_sensor_power_state(struct hid_sensor_common *st, bool state) +{ + int state_val; + int report_val; + s32 poll_value = 0; + + if (state) { + if (sensor_hub_device_open(st->hsdev)) + return -EIO; + + atomic_inc(&st->data_ready); + + state_val = hid_sensor_get_usage_index(st->hsdev, + st->power_state.report_id, + st->power_state.index, + HID_USAGE_SENSOR_PROP_POWER_STATE_D0_FULL_POWER_ENUM); + report_val = hid_sensor_get_usage_index(st->hsdev, + st->report_state.report_id, + st->report_state.index, + HID_USAGE_SENSOR_PROP_REPORTING_STATE_ALL_EVENTS_ENUM); + + poll_value = hid_sensor_read_poll_value(st); + } else { + int val; + + val = atomic_dec_if_positive(&st->data_ready); + if (val < 0) + return 0; + + sensor_hub_device_close(st->hsdev); + state_val = hid_sensor_get_usage_index(st->hsdev, + st->power_state.report_id, + st->power_state.index, + HID_USAGE_SENSOR_PROP_POWER_STATE_D4_POWER_OFF_ENUM); + report_val = hid_sensor_get_usage_index(st->hsdev, + st->report_state.report_id, + st->report_state.index, + HID_USAGE_SENSOR_PROP_REPORTING_STATE_NO_EVENTS_ENUM); + } + + if (state_val >= 0) { + state_val += st->power_state.logical_minimum; + sensor_hub_set_feature(st->hsdev, st->power_state.report_id, + st->power_state.index, sizeof(state_val), + &state_val); + } + + if (report_val >= 0) { + report_val += st->report_state.logical_minimum; + sensor_hub_set_feature(st->hsdev, st->report_state.report_id, + st->report_state.index, + sizeof(report_val), + &report_val); + } + + pr_debug("HID_SENSOR %s set power_state %d report_state %d\n", + st->pdev->name, state_val, report_val); + + sensor_hub_get_feature(st->hsdev, st->power_state.report_id, + st->power_state.index, + sizeof(state_val), &state_val); + if (state && poll_value) + msleep_interruptible(poll_value * 2); + + return 0; +} +EXPORT_SYMBOL_NS(hid_sensor_power_state, "IIO_HID"); + +int hid_sensor_power_state(struct hid_sensor_common *st, bool state) +{ + +#ifdef CONFIG_PM + int ret; + + if (atomic_add_unless(&st->runtime_pm_enable, 1, 1)) + pm_runtime_enable(&st->pdev->dev); + + if (state) { + atomic_inc(&st->user_requested_state); + ret = pm_runtime_resume_and_get(&st->pdev->dev); + } else { + atomic_dec(&st->user_requested_state); + pm_runtime_use_autosuspend(&st->pdev->dev); + ret = pm_runtime_put_autosuspend(&st->pdev->dev); + } + if (ret < 0) + return ret; + + return 0; +#else + atomic_set(&st->user_requested_state, state); + return _hid_sensor_power_state(st, state); +#endif +} + +static void hid_sensor_set_power_work(struct work_struct *work) +{ + struct hid_sensor_common *attrb = container_of(work, + struct hid_sensor_common, + work); + + if (attrb->poll_interval >= 0) + sensor_hub_set_feature(attrb->hsdev, attrb->poll.report_id, + attrb->poll.index, + sizeof(attrb->poll_interval), + &attrb->poll_interval); + + if (attrb->raw_hystersis >= 0) + sensor_hub_set_feature(attrb->hsdev, + attrb->sensitivity.report_id, + attrb->sensitivity.index, + sizeof(attrb->raw_hystersis), + &attrb->raw_hystersis); + + if (attrb->latency_ms > 0) + hid_sensor_set_report_latency(attrb, attrb->latency_ms); + + if (atomic_read(&attrb->user_requested_state)) + _hid_sensor_power_state(attrb, true); +} + +static int buffer_postenable(struct iio_dev *indio_dev) +{ + return hid_sensor_power_state(iio_device_get_drvdata(indio_dev), 1); +} + +static int buffer_predisable(struct iio_dev *indio_dev) +{ + return hid_sensor_power_state(iio_device_get_drvdata(indio_dev), 0); +} + +static const struct iio_buffer_setup_ops hid_sensor_buffer_ops = { + .postenable = buffer_postenable, + .predisable = buffer_predisable, +}; + +void hid_sensor_remove_trigger(struct iio_dev *indio_dev, + struct hid_sensor_common *attrb) +{ + if (atomic_read(&attrb->runtime_pm_enable)) + pm_runtime_disable(&attrb->pdev->dev); + + pm_runtime_set_suspended(&attrb->pdev->dev); + + cancel_work_sync(&attrb->work); + iio_trigger_unregister(attrb->trigger); + iio_trigger_free(attrb->trigger); +} +EXPORT_SYMBOL_NS(hid_sensor_remove_trigger, "IIO_HID"); + +int hid_sensor_setup_trigger(struct iio_dev *indio_dev, const char *name, + struct hid_sensor_common *attrb) +{ + const struct iio_dev_attr **fifo_attrs; + int ret; + struct iio_trigger *trig; + + if (hid_sensor_batch_mode_supported(attrb)) + fifo_attrs = hid_sensor_fifo_attributes; + else + fifo_attrs = NULL; + + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_HARDWARE_TRIGGERED; + + ret = devm_iio_kfifo_buffer_setup_ext(&indio_dev->dev, indio_dev, + &hid_sensor_buffer_ops, + fifo_attrs); + if (ret) { + dev_err(&indio_dev->dev, "Kfifo Buffer Setup Failed\n"); + return ret; + } + + /* + * The current user space in distro "iio-sensor-proxy" is not working in + * trigerless mode and it expects + * /sys/bus/iio/devices/iio:device0/trigger/current_trigger. + * The change replacing iio_triggered_buffer_setup_ext() with + * devm_iio_kfifo_buffer_setup_ext() will not create attribute without + * registering a trigger with INDIO_HARDWARE_TRIGGERED. + * So the below code fragment is still required. + */ + + trig = iio_trigger_alloc(indio_dev->dev.parent, + "%s-dev%d", name, iio_device_id(indio_dev)); + if (!trig) { + dev_err(&indio_dev->dev, "Trigger Allocate Failed\n"); + return -ENOMEM; + } + + iio_trigger_set_drvdata(trig, attrb); + ret = iio_trigger_register(trig); + + if (ret) { + dev_err(&indio_dev->dev, "Trigger Register Failed\n"); + goto error_free_trig; + } + attrb->trigger = trig; + indio_dev->trig = iio_trigger_get(trig); + + ret = pm_runtime_set_active(&indio_dev->dev); + if (ret) + goto error_unreg_trigger; + + iio_device_set_drvdata(indio_dev, attrb); + + INIT_WORK(&attrb->work, hid_sensor_set_power_work); + + pm_suspend_ignore_children(&attrb->pdev->dev, true); + /* Default to 3 seconds, but can be changed from sysfs */ + pm_runtime_set_autosuspend_delay(&attrb->pdev->dev, + 3000); + return ret; +error_unreg_trigger: + iio_trigger_unregister(trig); +error_free_trig: + iio_trigger_free(trig); + return ret; +} +EXPORT_SYMBOL_NS(hid_sensor_setup_trigger, "IIO_HID"); + +static int __maybe_unused hid_sensor_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct hid_sensor_common *attrb = iio_device_get_drvdata(indio_dev); + + return _hid_sensor_power_state(attrb, false); +} + +static int __maybe_unused hid_sensor_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct hid_sensor_common *attrb = iio_device_get_drvdata(indio_dev); + + schedule_work(&attrb->work); + + return 0; +} + +static int __maybe_unused hid_sensor_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct hid_sensor_common *attrb = iio_device_get_drvdata(indio_dev); + + return _hid_sensor_power_state(attrb, true); +} + +const struct dev_pm_ops hid_sensor_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(hid_sensor_suspend, hid_sensor_resume) + SET_RUNTIME_PM_OPS(hid_sensor_suspend, + hid_sensor_runtime_resume, NULL) +}; +EXPORT_SYMBOL_NS(hid_sensor_pm_ops, "IIO_HID"); + +MODULE_AUTHOR("Srinivas Pandruvada "); +MODULE_DESCRIPTION("HID Sensor trigger processing"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HID_ATTRIBUTES"); diff --git a/drivers/iio/common/hid-sensors/hid-sensor-trigger.h b/drivers/iio/common/hid-sensors/hid-sensor-trigger.h new file mode 100644 index 000000000..589de858e --- /dev/null +++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * HID Sensors Driver + * Copyright (c) 2012, Intel Corporation. + */ +#ifndef _HID_SENSOR_TRIGGER_H +#define _HID_SENSOR_TRIGGER_H + +#include +#include + +struct hid_sensor_common; +struct iio_dev; + +extern const struct dev_pm_ops hid_sensor_pm_ops; + +int hid_sensor_setup_trigger(struct iio_dev *indio_dev, const char *name, + struct hid_sensor_common *attrb); +void hid_sensor_remove_trigger(struct iio_dev *indio_dev, + struct hid_sensor_common *attrb); +int hid_sensor_power_state(struct hid_sensor_common *st, bool state); + +#endif diff --git a/drivers/iio/common/inv_sensors/Kconfig b/drivers/iio/common/inv_sensors/Kconfig new file mode 100644 index 000000000..28815fb43 --- /dev/null +++ b/drivers/iio/common/inv_sensors/Kconfig @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# TDK-InvenSense sensors common library +# + +config IIO_INV_SENSORS_TIMESTAMP + tristate diff --git a/drivers/iio/common/inv_sensors/Makefile b/drivers/iio/common/inv_sensors/Makefile new file mode 100644 index 000000000..dcf39f249 --- /dev/null +++ b/drivers/iio/common/inv_sensors/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for TDK-InvenSense sensors module. +# + +obj-$(CONFIG_IIO_INV_SENSORS_TIMESTAMP) += inv_sensors_timestamp.o diff --git a/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c b/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c new file mode 100644 index 000000000..88a82d137 --- /dev/null +++ b/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Invensense, Inc. + */ + +#include +#include +#include +#include + +#include + +/* compute min and max following jitter in per mille */ +#define INV_SENSORS_TIMESTAMP_MIN(_val, _jitter) \ + (((_val) * (1000 - (_jitter))) / 1000) +#define INV_SENSORS_TIMESTAMP_MAX(_val, _jitter) \ + (((_val) * (1000 + (_jitter))) / 1000) + +/* minimum timestamp delta between 2 interrupts for measuring period (20ms) */ +#define INV_SENSORS_MIN_IT_DELTA (20 * NSEC_PER_MSEC) + +/* Add a new value inside an accumulator and update the estimate value */ +static void inv_update_acc(struct inv_sensors_timestamp_acc *acc, u32 val) +{ + u64 sum = 0; + size_t i; + + acc->values[acc->idx++] = val; + if (acc->idx >= ARRAY_SIZE(acc->values)) + acc->idx = 0; + + /* compute the mean of all stored values, use 0 as empty slot */ + for (i = 0; i < ARRAY_SIZE(acc->values); ++i) { + if (acc->values[i] == 0) + break; + sum += acc->values[i]; + } + + acc->val = div_u64(sum, i); +} + +void inv_sensors_timestamp_init(struct inv_sensors_timestamp *ts, + const struct inv_sensors_timestamp_chip *chip) +{ + memset(ts, 0, sizeof(*ts)); + + /* save chip parameters and compute min and max clock period */ + ts->chip = *chip; + ts->min_period = INV_SENSORS_TIMESTAMP_MIN(chip->clock_period, chip->jitter); + ts->max_period = INV_SENSORS_TIMESTAMP_MAX(chip->clock_period, chip->jitter); + + /* current multiplier and period values after reset */ + ts->mult = chip->init_period / chip->clock_period; + ts->period = chip->init_period; + + /* use theoretical value for chip period */ + inv_update_acc(&ts->chip_period, chip->clock_period); +} +EXPORT_SYMBOL_NS_GPL(inv_sensors_timestamp_init, "IIO_INV_SENSORS_TIMESTAMP"); + +int inv_sensors_timestamp_update_odr(struct inv_sensors_timestamp *ts, + u32 period, bool fifo) +{ + u32 mult; + + /* when FIFO is on, prevent odr change if one is already pending */ + if (fifo && ts->new_mult != 0) + return -EAGAIN; + + mult = period / ts->chip.clock_period; + if (mult != ts->mult) + ts->new_mult = mult; + + /* When FIFO is off, directly apply the new ODR */ + if (!fifo) + inv_sensors_timestamp_apply_odr(ts, 0, 0, 0); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(inv_sensors_timestamp_update_odr, "IIO_INV_SENSORS_TIMESTAMP"); + +static bool inv_validate_period(struct inv_sensors_timestamp *ts, u32 period) +{ + u32 period_min, period_max; + + /* check that period is acceptable */ + period_min = ts->min_period * ts->mult; + period_max = ts->max_period * ts->mult; + if (period > period_min && period < period_max) + return true; + else + return false; +} + +static bool inv_update_chip_period(struct inv_sensors_timestamp *ts, + u32 period) +{ + u32 new_chip_period; + + if (!inv_validate_period(ts, period)) + return false; + + /* update chip internal period estimation */ + new_chip_period = period / ts->mult; + inv_update_acc(&ts->chip_period, new_chip_period); + + return true; +} + +static u32 inv_align_timestamp_it(struct inv_sensors_timestamp *ts, + unsigned int sample_nb) +{ + const s64 period_min = (s64)ts->min_period * ts->mult; + const s64 period_max = (s64)ts->max_period * ts->mult; + s64 new_period; + + /* compute new period aligning last timestamp with interrupt timestamp */ + new_period = div_s64(ts->it.up - ts->timestamp, sample_nb); + + /* ensure that period never overflows the jitter */ + return clamp(new_period, period_min, period_max); +} + +void inv_sensors_timestamp_interrupt(struct inv_sensors_timestamp *ts, + size_t sample_nb, s64 timestamp) +{ + struct inv_sensors_timestamp_interval *it; + s64 delta, delta_threshold, interval; + u32 period; + bool valid = false; + + if (sample_nb == 0) + return; + + /* no previous data, compute theoretical value from interrupt */ + if (ts->timestamp == 0) { + /* elapsed time: sensor period * sensor samples number */ + interval = (s64)ts->period * (s64)sample_nb; + ts->timestamp = timestamp - interval; + } + + /* update delta timestamps and estimated period */ + it = &ts->delta; + ts->delta_counter += sample_nb; + delta = timestamp - it->up; + delta_threshold = INV_SENSORS_TIMESTAMP_MIN(INV_SENSORS_MIN_IT_DELTA, ts->chip.jitter); + if (delta >= delta_threshold) { + it->lo = it->up; + it->up = timestamp; + if (it->lo != 0) { + /* compute period: delta time divided by number of samples */ + delta = it->up - it->lo; + period = div_s64(delta, ts->delta_counter); + inv_update_chip_period(ts, period); + } + ts->delta_counter = 0; + } + + /* update interrupt timestamp and compute chip and sensor periods */ + it = &ts->it; + it->lo = it->up; + it->up = timestamp; + delta = it->up - it->lo; + if (it->lo != 0) { + /* compute period and check validity */ + period = div_s64(delta, sample_nb); + valid = inv_validate_period(ts, period); + } + + /* if interrupt interval is valid, sync with interrupt timestamp */ + if (valid) + ts->period = inv_align_timestamp_it(ts, sample_nb); + else + ts->period = ts->mult * ts->chip_period.val; +} +EXPORT_SYMBOL_NS_GPL(inv_sensors_timestamp_interrupt, "IIO_INV_SENSORS_TIMESTAMP"); + +void inv_sensors_timestamp_apply_odr(struct inv_sensors_timestamp *ts, + u32 fifo_period, size_t fifo_nb, + unsigned int fifo_no) +{ + s64 interval; + u32 fifo_mult; + + if (ts->new_mult == 0) + return; + + /* update to new multiplier and update period */ + ts->mult = ts->new_mult; + ts->new_mult = 0; + ts->period = ts->mult * ts->chip_period.val; + + /* + * After ODR change the time interval with the previous sample is + * undertermined (depends when the change occurs). So we compute the + * timestamp from the current interrupt using the new FIFO period, the + * total number of samples and the current sample numero. + */ + if (ts->timestamp != 0) { + /* compute measured fifo period */ + fifo_mult = fifo_period / ts->chip.clock_period; + fifo_period = fifo_mult * ts->chip_period.val; + /* computes time interval between interrupt and this sample */ + interval = (s64)(fifo_nb - fifo_no) * (s64)fifo_period; + ts->timestamp = ts->it.up - interval; + } +} +EXPORT_SYMBOL_NS_GPL(inv_sensors_timestamp_apply_odr, "IIO_INV_SENSORS_TIMESTAMP"); + +MODULE_AUTHOR("InvenSense, Inc."); +MODULE_DESCRIPTION("InvenSense sensors timestamp module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/common/ms_sensors/Kconfig b/drivers/iio/common/ms_sensors/Kconfig new file mode 100644 index 000000000..45012b7ad --- /dev/null +++ b/drivers/iio/common/ms_sensors/Kconfig @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Measurements Specialties sensors common library +# + +config IIO_MS_SENSORS_I2C + tristate diff --git a/drivers/iio/common/ms_sensors/Makefile b/drivers/iio/common/ms_sensors/Makefile new file mode 100644 index 000000000..028573b9b --- /dev/null +++ b/drivers/iio/common/ms_sensors/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the Measurement Specialties sensor common modules. +# + +obj-$(CONFIG_IIO_MS_SENSORS_I2C) += ms_sensors_i2c.o diff --git a/drivers/iio/common/ms_sensors/ms_sensors_i2c.c b/drivers/iio/common/ms_sensors/ms_sensors_i2c.c new file mode 100644 index 000000000..1960a2ce8 --- /dev/null +++ b/drivers/iio/common/ms_sensors/ms_sensors_i2c.c @@ -0,0 +1,697 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Measurements Specialties driver common i2c functions + * + * Copyright (c) 2015 Measurement-Specialties + */ + +#include +#include +#include +#include + +#include "ms_sensors_i2c.h" + +/* Conversion times in us */ +static const u16 ms_sensors_ht_t_conversion_time[] = { 50000, 25000, + 13000, 7000 }; +static const u16 ms_sensors_ht_h_conversion_time[] = { 16000, 5000, + 3000, 8000 }; +static const u16 ms_sensors_tp_conversion_time[] = { 500, 1100, 2100, + 4100, 8220, 16440 }; + +#define MS_SENSORS_SERIAL_READ_MSB 0xFA0F +#define MS_SENSORS_SERIAL_READ_LSB 0xFCC9 +#define MS_SENSORS_CONFIG_REG_WRITE 0xE6 +#define MS_SENSORS_CONFIG_REG_READ 0xE7 +#define MS_SENSORS_HT_T_CONVERSION_START 0xF3 +#define MS_SENSORS_HT_H_CONVERSION_START 0xF5 + +#define MS_SENSORS_TP_PROM_READ 0xA0 +#define MS_SENSORS_TP_T_CONVERSION_START 0x50 +#define MS_SENSORS_TP_P_CONVERSION_START 0x40 +#define MS_SENSORS_TP_ADC_READ 0x00 + +#define MS_SENSORS_NO_READ_CMD 0xFF + +/** + * ms_sensors_reset() - Reset function + * @cli: pointer to device client + * @cmd: reset cmd. Depends on device in use + * @delay: usleep minimal delay after reset command is issued + * + * Generic I2C reset function for Measurement Specialties devices. + * + * Return: 0 on success, negative errno otherwise. + */ +int ms_sensors_reset(void *cli, u8 cmd, unsigned int delay) +{ + int ret; + struct i2c_client *client = cli; + + ret = i2c_smbus_write_byte(client, cmd); + if (ret) { + dev_err(&client->dev, "Failed to reset device\n"); + return ret; + } + usleep_range(delay, delay + 1000); + + return 0; +} +EXPORT_SYMBOL_NS(ms_sensors_reset, "IIO_MEAS_SPEC_SENSORS"); + +/** + * ms_sensors_read_prom_word() - PROM word read function + * @cli: pointer to device client + * @cmd: PROM read cmd. Depends on device and prom id + * @word: pointer to word destination value + * + * Generic i2c prom word read function for Measurement Specialties devices. + * + * Return: 0 on success, negative errno otherwise. + */ +int ms_sensors_read_prom_word(void *cli, int cmd, u16 *word) +{ + int ret; + struct i2c_client *client = cli; + + ret = i2c_smbus_read_word_swapped(client, cmd); + if (ret < 0) { + dev_err(&client->dev, "Failed to read prom word\n"); + return ret; + } + *word = ret; + + return 0; +} +EXPORT_SYMBOL_NS(ms_sensors_read_prom_word, "IIO_MEAS_SPEC_SENSORS"); + +/** + * ms_sensors_convert_and_read() - ADC conversion & read function + * @cli: pointer to device client + * @conv: ADC conversion command. Depends on device in use + * @rd: ADC read command. Depends on device in use + * @delay: usleep minimal delay after conversion command is issued + * @adc: pointer to ADC destination value + * + * Generic ADC conversion & read function for Measurement Specialties + * devices. + * The function will issue conversion command, sleep appropriate delay, and + * issue command to read ADC. + * + * Return: 0 on success, negative errno otherwise. + */ +int ms_sensors_convert_and_read(void *cli, u8 conv, u8 rd, + unsigned int delay, u32 *adc) +{ + int ret; + __be32 buf = 0; + struct i2c_client *client = cli; + + /* Trigger conversion */ + ret = i2c_smbus_write_byte(client, conv); + if (ret) + goto err; + usleep_range(delay, delay + 1000); + + /* Retrieve ADC value */ + if (rd != MS_SENSORS_NO_READ_CMD) + ret = i2c_smbus_read_i2c_block_data(client, rd, 3, (u8 *)&buf); + else + ret = i2c_master_recv(client, (u8 *)&buf, 3); + if (ret < 0) + goto err; + + dev_dbg(&client->dev, "ADC raw value : %x\n", be32_to_cpu(buf) >> 8); + *adc = be32_to_cpu(buf) >> 8; + + return 0; +err: + dev_err(&client->dev, "Unable to make sensor adc conversion\n"); + return ret; +} +EXPORT_SYMBOL_NS(ms_sensors_convert_and_read, "IIO_MEAS_SPEC_SENSORS"); + +/** + * ms_sensors_crc_valid() - CRC check function + * @value: input and CRC compare value + * + * Cyclic Redundancy Check function used in TSYS02D, HTU21, MS8607. + * This function performs a x^8 + x^5 + x^4 + 1 polynomial CRC. + * The argument contains CRC value in LSB byte while the bytes 1 and 2 + * are used for CRC computation. + * + * Return: 1 if CRC is valid, 0 otherwise. + */ +static bool ms_sensors_crc_valid(u32 value) +{ + u32 polynom = 0x988000; /* x^8 + x^5 + x^4 + 1 */ + u32 msb = 0x800000; + u32 mask = 0xFF8000; + u32 result = value & 0xFFFF00; + u8 crc = value & 0xFF; + + while (msb != 0x80) { + if (result & msb) + result = ((result ^ polynom) & mask) + | (result & ~mask); + msb >>= 1; + mask >>= 1; + polynom >>= 1; + } + + return result == crc; +} + +/** + * ms_sensors_read_serial() - Serial number read function + * @client: pointer to i2c client + * @sn: pointer to 64-bits destination value + * + * Generic i2c serial number read function for Measurement Specialties devices. + * This function is used for TSYS02d, HTU21, MS8607 chipset. + * Refer to datasheet: + * http://www.meas-spec.com/downloads/HTU2X_Serial_Number_Reading.pdf + * + * Sensor raw MSB serial number format is the following : + * [ SNB3, CRC, SNB2, CRC, SNB1, CRC, SNB0, CRC] + * Sensor raw LSB serial number format is the following : + * [ X, X, SNC1, SNC0, CRC, SNA1, SNA0, CRC] + * The resulting serial number is following : + * [ SNA1, SNA0, SNB3, SNB2, SNB1, SNB0, SNC1, SNC0] + * + * Return: 0 on success, negative errno otherwise. + */ +int ms_sensors_read_serial(struct i2c_client *client, u64 *sn) +{ + u8 i; + __be64 rcv_buf = 0; + u64 rcv_val; + __be16 send_buf; + int ret; + + struct i2c_msg msg[2] = { + { + .addr = client->addr, + .flags = client->flags, + .len = 2, + .buf = (__u8 *)&send_buf, + }, + { + .addr = client->addr, + .flags = client->flags | I2C_M_RD, + .buf = (__u8 *)&rcv_buf, + }, + }; + + /* Read MSB part of serial number */ + send_buf = cpu_to_be16(MS_SENSORS_SERIAL_READ_MSB); + msg[1].len = 8; + ret = i2c_transfer(client->adapter, msg, 2); + if (ret < 0) { + dev_err(&client->dev, "Unable to read device serial number"); + return ret; + } + + rcv_val = be64_to_cpu(rcv_buf); + dev_dbg(&client->dev, "Serial MSB raw : %llx\n", rcv_val); + + for (i = 0; i < 64; i += 16) { + if (!ms_sensors_crc_valid((rcv_val >> i) & 0xFFFF)) + return -ENODEV; + } + + *sn = (((rcv_val >> 32) & 0xFF000000) | + ((rcv_val >> 24) & 0x00FF0000) | + ((rcv_val >> 16) & 0x0000FF00) | + ((rcv_val >> 8) & 0x000000FF)) << 16; + + /* Read LSB part of serial number */ + send_buf = cpu_to_be16(MS_SENSORS_SERIAL_READ_LSB); + msg[1].len = 6; + rcv_buf = 0; + ret = i2c_transfer(client->adapter, msg, 2); + if (ret < 0) { + dev_err(&client->dev, "Unable to read device serial number"); + return ret; + } + + rcv_val = be64_to_cpu(rcv_buf) >> 16; + dev_dbg(&client->dev, "Serial MSB raw : %llx\n", rcv_val); + + for (i = 0; i < 48; i += 24) { + if (!ms_sensors_crc_valid((rcv_val >> i) & 0xFFFFFF)) + return -ENODEV; + } + + *sn |= (rcv_val & 0xFFFF00) << 40 | (rcv_val >> 32); + + return 0; +} +EXPORT_SYMBOL_NS(ms_sensors_read_serial, "IIO_MEAS_SPEC_SENSORS"); + +static int ms_sensors_read_config_reg(struct i2c_client *client, + u8 *config_reg) +{ + int ret; + + ret = i2c_smbus_write_byte(client, MS_SENSORS_CONFIG_REG_READ); + if (ret) { + dev_err(&client->dev, "Unable to read config register"); + return ret; + } + + ret = i2c_master_recv(client, config_reg, 1); + if (ret < 0) { + dev_err(&client->dev, "Unable to read config register"); + return ret; + } + dev_dbg(&client->dev, "Config register :%x\n", *config_reg); + + return 0; +} + +/** + * ms_sensors_write_resolution() - Set resolution function + * @dev_data: pointer to temperature/humidity device data + * @i: resolution index to set + * + * This function will program the appropriate resolution based on the index + * provided when user space will set samp_freq channel. + * This function is used for TSYS02D, HTU21 and MS8607 chipsets. + * + * Return: 0 on success, negative errno otherwise. + */ +ssize_t ms_sensors_write_resolution(struct ms_ht_dev *dev_data, + u8 i) +{ + u8 config_reg; + int ret; + + ret = ms_sensors_read_config_reg(dev_data->client, &config_reg); + if (ret) + return ret; + + config_reg &= 0x7E; + config_reg |= ((i & 1) << 7) + ((i & 2) >> 1); + + return i2c_smbus_write_byte_data(dev_data->client, + MS_SENSORS_CONFIG_REG_WRITE, + config_reg); +} +EXPORT_SYMBOL_NS(ms_sensors_write_resolution, "IIO_MEAS_SPEC_SENSORS"); + +/** + * ms_sensors_show_battery_low() - Show device battery low indicator + * @dev_data: pointer to temperature/humidity device data + * @buf: pointer to char buffer to write result + * + * This function will read battery indicator value in the device and + * return 1 if the device voltage is below 2.25V. + * This function is used for TSYS02D, HTU21 and MS8607 chipsets. + * + * Return: length of sprintf on success, negative errno otherwise. + */ +ssize_t ms_sensors_show_battery_low(struct ms_ht_dev *dev_data, + char *buf) +{ + int ret; + u8 config_reg; + + mutex_lock(&dev_data->lock); + ret = ms_sensors_read_config_reg(dev_data->client, &config_reg); + mutex_unlock(&dev_data->lock); + if (ret) + return ret; + + return sysfs_emit(buf, "%d\n", (config_reg & 0x40) >> 6); +} +EXPORT_SYMBOL_NS(ms_sensors_show_battery_low, "IIO_MEAS_SPEC_SENSORS"); + +/** + * ms_sensors_show_heater() - Show device heater + * @dev_data: pointer to temperature/humidity device data + * @buf: pointer to char buffer to write result + * + * This function will read heater enable value in the device and + * return 1 if the heater is enabled. + * This function is used for HTU21 and MS8607 chipsets. + * + * Return: length of sprintf on success, negative errno otherwise. + */ +ssize_t ms_sensors_show_heater(struct ms_ht_dev *dev_data, + char *buf) +{ + u8 config_reg; + int ret; + + mutex_lock(&dev_data->lock); + ret = ms_sensors_read_config_reg(dev_data->client, &config_reg); + mutex_unlock(&dev_data->lock); + if (ret) + return ret; + + return sysfs_emit(buf, "%d\n", (config_reg & 0x4) >> 2); +} +EXPORT_SYMBOL_NS(ms_sensors_show_heater, "IIO_MEAS_SPEC_SENSORS"); + +/** + * ms_sensors_write_heater() - Write device heater + * @dev_data: pointer to temperature/humidity device data + * @buf: pointer to char buffer from user space + * @len: length of buf + * + * This function will write 1 or 0 value in the device + * to enable or disable heater. + * This function is used for HTU21 and MS8607 chipsets. + * + * Return: length of buffer, negative errno otherwise. + */ +ssize_t ms_sensors_write_heater(struct ms_ht_dev *dev_data, + const char *buf, size_t len) +{ + u8 val, config_reg; + int ret; + + ret = kstrtou8(buf, 10, &val); + if (ret) + return ret; + + if (val > 1) + return -EINVAL; + + mutex_lock(&dev_data->lock); + ret = ms_sensors_read_config_reg(dev_data->client, &config_reg); + if (ret) { + mutex_unlock(&dev_data->lock); + return ret; + } + + config_reg &= 0xFB; + config_reg |= val << 2; + + ret = i2c_smbus_write_byte_data(dev_data->client, + MS_SENSORS_CONFIG_REG_WRITE, + config_reg); + mutex_unlock(&dev_data->lock); + if (ret) { + dev_err(&dev_data->client->dev, "Unable to write config register\n"); + return ret; + } + + return len; +} +EXPORT_SYMBOL_NS(ms_sensors_write_heater, "IIO_MEAS_SPEC_SENSORS"); + +/** + * ms_sensors_ht_read_temperature() - Read temperature + * @dev_data: pointer to temperature/humidity device data + * @temperature:pointer to temperature destination value + * + * This function will get temperature ADC value from the device, + * check the CRC and compute the temperature value. + * This function is used for TSYS02D, HTU21 and MS8607 chipsets. + * + * Return: 0 on success, negative errno otherwise. + */ +int ms_sensors_ht_read_temperature(struct ms_ht_dev *dev_data, + s32 *temperature) +{ + int ret; + u32 adc; + u16 delay; + + mutex_lock(&dev_data->lock); + delay = ms_sensors_ht_t_conversion_time[dev_data->res_index]; + ret = ms_sensors_convert_and_read(dev_data->client, + MS_SENSORS_HT_T_CONVERSION_START, + MS_SENSORS_NO_READ_CMD, + delay, &adc); + mutex_unlock(&dev_data->lock); + if (ret) + return ret; + + if (!ms_sensors_crc_valid(adc)) { + dev_err(&dev_data->client->dev, + "Temperature read crc check error\n"); + return -ENODEV; + } + + /* Temperature algorithm */ + *temperature = (((s64)(adc >> 8) * 175720) >> 16) - 46850; + + return 0; +} +EXPORT_SYMBOL_NS(ms_sensors_ht_read_temperature, "IIO_MEAS_SPEC_SENSORS"); + +/** + * ms_sensors_ht_read_humidity() - Read humidity + * @dev_data: pointer to temperature/humidity device data + * @humidity: pointer to humidity destination value + * + * This function will get humidity ADC value from the device, + * check the CRC and compute the temperature value. + * This function is used for HTU21 and MS8607 chipsets. + * + * Return: 0 on success, negative errno otherwise. + */ +int ms_sensors_ht_read_humidity(struct ms_ht_dev *dev_data, + u32 *humidity) +{ + int ret; + u32 adc; + u16 delay; + + mutex_lock(&dev_data->lock); + delay = ms_sensors_ht_h_conversion_time[dev_data->res_index]; + ret = ms_sensors_convert_and_read(dev_data->client, + MS_SENSORS_HT_H_CONVERSION_START, + MS_SENSORS_NO_READ_CMD, + delay, &adc); + mutex_unlock(&dev_data->lock); + if (ret) + return ret; + + if (!ms_sensors_crc_valid(adc)) { + dev_err(&dev_data->client->dev, + "Humidity read crc check error\n"); + return -ENODEV; + } + + /* Humidity algorithm */ + *humidity = (((s32)(adc >> 8) * 12500) >> 16) * 10 - 6000; + if (*humidity >= 100000) + *humidity = 100000; + + return 0; +} +EXPORT_SYMBOL_NS(ms_sensors_ht_read_humidity, "IIO_MEAS_SPEC_SENSORS"); + +/** + * ms_sensors_tp_crc4() - Calculate PROM CRC for + * Temperature and pressure devices. + * This function is only used when reading PROM coefficients + * + * @prom: pointer to PROM coefficients array + * + * Return: CRC. + */ +static u8 ms_sensors_tp_crc4(u16 *prom) +{ + unsigned int cnt, n_bit; + u16 n_rem = 0x0000; + + for (cnt = 0; cnt < MS_SENSORS_TP_PROM_WORDS_NB * 2; cnt++) { + if (cnt % 2 == 1) + n_rem ^= prom[cnt >> 1] & 0x00FF; + else + n_rem ^= prom[cnt >> 1] >> 8; + + for (n_bit = 8; n_bit > 0; n_bit--) { + if (n_rem & 0x8000) + n_rem = (n_rem << 1) ^ 0x3000; + else + n_rem <<= 1; + } + } + + return n_rem >> 12; +} + +/** + * ms_sensors_tp_crc_valid_112() - CRC check function for + * Temperature and pressure devices for 112bit PROM. + * This function is only used when reading PROM coefficients + * + * @prom: pointer to PROM coefficients array + * + * Return: True if CRC is ok. + */ +static bool ms_sensors_tp_crc_valid_112(u16 *prom) +{ + u16 w0 = prom[0], crc_read = (w0 & 0xF000) >> 12; + u8 crc; + + prom[0] &= 0x0FFF; /* Clear the CRC computation part */ + prom[MS_SENSORS_TP_PROM_WORDS_NB - 1] = 0; + + crc = ms_sensors_tp_crc4(prom); + + prom[0] = w0; + + return crc == crc_read; +} + +/** + * ms_sensors_tp_crc_valid_128() - CRC check function for + * Temperature and pressure devices for 128bit PROM. + * This function is only used when reading PROM coefficients + * + * @prom: pointer to PROM coefficients array + * + * Return: True if CRC is ok. + */ +static bool ms_sensors_tp_crc_valid_128(u16 *prom) +{ + u16 w7 = prom[7], crc_read = w7 & 0x000F; + u8 crc; + + prom[7] &= 0xFF00; /* Clear the CRC and LSB part */ + + crc = ms_sensors_tp_crc4(prom); + + prom[7] = w7; + + return crc == crc_read; +} + +/** + * ms_sensors_tp_read_prom() - prom coeff read function + * @dev_data: pointer to temperature/pressure device data + * + * This function will read prom coefficients and check CRC. + * This function is used for MS5637 and MS8607 chipsets. + * + * Return: 0 on success, negative errno otherwise. + */ +int ms_sensors_tp_read_prom(struct ms_tp_dev *dev_data) +{ + int i, ret; + bool valid; + + for (i = 0; i < dev_data->hw->prom_len; i++) { + ret = ms_sensors_read_prom_word( + dev_data->client, + MS_SENSORS_TP_PROM_READ + (i << 1), + &dev_data->prom[i]); + + if (ret) + return ret; + } + + if (dev_data->hw->prom_len == 8) + valid = ms_sensors_tp_crc_valid_128(dev_data->prom); + else + valid = ms_sensors_tp_crc_valid_112(dev_data->prom); + + if (!valid) { + dev_err(&dev_data->client->dev, + "Calibration coefficients crc check error\n"); + return -ENODEV; + } + + return 0; +} +EXPORT_SYMBOL_NS(ms_sensors_tp_read_prom, "IIO_MEAS_SPEC_SENSORS"); + +/** + * ms_sensors_read_temp_and_pressure() - read temp and pressure + * @dev_data: pointer to temperature/pressure device data + * @temperature:pointer to temperature destination value + * @pressure: pointer to pressure destination value + * + * This function will read ADC and compute pressure and temperature value. + * This function is used for MS5637 and MS8607 chipsets. + * + * Return: 0 on success, negative errno otherwise. + */ +int ms_sensors_read_temp_and_pressure(struct ms_tp_dev *dev_data, + int *temperature, + unsigned int *pressure) +{ + int ret; + u32 t_adc, p_adc; + s32 dt, temp; + s64 off, sens, t2, off2, sens2; + u16 *prom = dev_data->prom, delay; + + mutex_lock(&dev_data->lock); + delay = ms_sensors_tp_conversion_time[dev_data->res_index]; + + ret = ms_sensors_convert_and_read( + dev_data->client, + MS_SENSORS_TP_T_CONVERSION_START + + dev_data->res_index * 2, + MS_SENSORS_TP_ADC_READ, + delay, &t_adc); + if (ret) { + mutex_unlock(&dev_data->lock); + return ret; + } + + ret = ms_sensors_convert_and_read( + dev_data->client, + MS_SENSORS_TP_P_CONVERSION_START + + dev_data->res_index * 2, + MS_SENSORS_TP_ADC_READ, + delay, &p_adc); + mutex_unlock(&dev_data->lock); + if (ret) + return ret; + + dt = (s32)t_adc - (prom[5] << 8); + + /* Actual temperature = 2000 + dT * TEMPSENS */ + temp = 2000 + (((s64)dt * prom[6]) >> 23); + + /* Second order temperature compensation */ + if (temp < 2000) { + s64 tmp = (s64)temp - 2000; + + t2 = (3 * ((s64)dt * (s64)dt)) >> 33; + off2 = (61 * tmp * tmp) >> 4; + sens2 = (29 * tmp * tmp) >> 4; + + if (temp < -1500) { + s64 tmp = (s64)temp + 1500; + + off2 += 17 * tmp * tmp; + sens2 += 9 * tmp * tmp; + } + } else { + t2 = (5 * ((s64)dt * (s64)dt)) >> 38; + off2 = 0; + sens2 = 0; + } + + /* OFF = OFF_T1 + TCO * dT */ + off = (((s64)prom[2]) << 17) + ((((s64)prom[4]) * (s64)dt) >> 6); + off -= off2; + + /* Sensitivity at actual temperature = SENS_T1 + TCS * dT */ + sens = (((s64)prom[1]) << 16) + (((s64)prom[3] * dt) >> 7); + sens -= sens2; + + /* Temperature compensated pressure = D1 * SENS - OFF */ + *temperature = (temp - t2) * 10; + *pressure = (u32)(((((s64)p_adc * sens) >> 21) - off) >> 15); + + return 0; +} +EXPORT_SYMBOL_NS(ms_sensors_read_temp_and_pressure, "IIO_MEAS_SPEC_SENSORS"); + +MODULE_DESCRIPTION("Measurement-Specialties common i2c driver"); +MODULE_AUTHOR("William Markezana "); +MODULE_AUTHOR("Ludovic Tancerel "); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/iio/common/ms_sensors/ms_sensors_i2c.h b/drivers/iio/common/ms_sensors/ms_sensors_i2c.h new file mode 100644 index 000000000..f15b973f2 --- /dev/null +++ b/drivers/iio/common/ms_sensors/ms_sensors_i2c.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Measurements Specialties common sensor driver + * + * Copyright (c) 2015 Measurement-Specialties + */ + +#ifndef _MS_SENSORS_I2C_H +#define _MS_SENSORS_I2C_H + +#include +#include + +#define MS_SENSORS_TP_PROM_WORDS_NB 8 + +/** + * struct ms_ht_dev - Humidity/Temperature sensor device structure + * @client: i2c client + * @lock: lock protecting the i2c conversion + * @res_index: index to selected sensor resolution + */ +struct ms_ht_dev { + struct i2c_client *client; + struct mutex lock; + u8 res_index; +}; + +/** + * struct ms_hw_data - Temperature/Pressure sensor hardware data + * @prom_len: number of words in the PROM + * @max_res_index: maximum sensor resolution index + */ +struct ms_tp_hw_data { + u8 prom_len; + u8 max_res_index; +}; + +/** + * struct ms_tp_dev - Temperature/Pressure sensor device structure + * @client: i2c client + * @lock: lock protecting the i2c conversion + * @prom: array of PROM coefficients used for conversion. Added element + * for CRC computation + * @res_index: index to selected sensor resolution + */ +struct ms_tp_dev { + struct i2c_client *client; + struct mutex lock; + const struct ms_tp_hw_data *hw; + u16 prom[MS_SENSORS_TP_PROM_WORDS_NB]; + u8 res_index; +}; + +int ms_sensors_reset(void *cli, u8 cmd, unsigned int delay); +int ms_sensors_read_prom_word(void *cli, int cmd, u16 *word); +int ms_sensors_convert_and_read(void *cli, u8 conv, u8 rd, + unsigned int delay, u32 *adc); +int ms_sensors_read_serial(struct i2c_client *client, u64 *sn); +ssize_t ms_sensors_show_serial(struct ms_ht_dev *dev_data, char *buf); +ssize_t ms_sensors_write_resolution(struct ms_ht_dev *dev_data, u8 i); +ssize_t ms_sensors_show_battery_low(struct ms_ht_dev *dev_data, char *buf); +ssize_t ms_sensors_show_heater(struct ms_ht_dev *dev_data, char *buf); +ssize_t ms_sensors_write_heater(struct ms_ht_dev *dev_data, + const char *buf, size_t len); +int ms_sensors_ht_read_temperature(struct ms_ht_dev *dev_data, + s32 *temperature); +int ms_sensors_ht_read_humidity(struct ms_ht_dev *dev_data, + u32 *humidity); +int ms_sensors_tp_read_prom(struct ms_tp_dev *dev_data); +int ms_sensors_read_temp_and_pressure(struct ms_tp_dev *dev_data, + int *temperature, + unsigned int *pressure); + +#endif /* _MS_SENSORS_I2C_H */ diff --git a/drivers/iio/common/scmi_sensors/Kconfig b/drivers/iio/common/scmi_sensors/Kconfig new file mode 100644 index 000000000..67e084cbb --- /dev/null +++ b/drivers/iio/common/scmi_sensors/Kconfig @@ -0,0 +1,18 @@ +# +# IIO over SCMI +# +# When adding new entries keep the list in alphabetical order + +menu "IIO SCMI Sensors" + +config IIO_SCMI + tristate "IIO SCMI" + depends on ARM_SCMI_PROTOCOL + select IIO_BUFFER + select IIO_KFIFO_BUF + help + Say yes here to build support for IIO SCMI Driver. + This provides ARM SCMI Protocol based IIO device. + This driver provides support for accelerometer and gyroscope + sensors available on SCMI based platforms. +endmenu diff --git a/drivers/iio/common/scmi_sensors/Makefile b/drivers/iio/common/scmi_sensors/Makefile new file mode 100644 index 000000000..645e0fce1 --- /dev/null +++ b/drivers/iio/common/scmi_sensors/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the IIO over SCMI +# +obj-$(CONFIG_IIO_SCMI) += scmi_iio.o diff --git a/drivers/iio/common/scmi_sensors/scmi_iio.c b/drivers/iio/common/scmi_sensors/scmi_iio.c new file mode 100644 index 000000000..442b40ef2 --- /dev/null +++ b/drivers/iio/common/scmi_sensors/scmi_iio.c @@ -0,0 +1,710 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * System Control and Management Interface(SCMI) based IIO sensor driver + * + * Copyright (C) 2021 Google LLC + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SCMI_IIO_NUM_OF_AXIS 3 + +struct scmi_iio_priv { + const struct scmi_sensor_proto_ops *sensor_ops; + struct scmi_protocol_handle *ph; + const struct scmi_sensor_info *sensor_info; + struct iio_dev *indio_dev; + /* lock to protect against multiple access to the device */ + struct mutex lock; + /* adding one additional channel for timestamp */ + s64 iio_buf[SCMI_IIO_NUM_OF_AXIS + 1]; + struct notifier_block sensor_update_nb; + u32 *freq_avail; +}; + +static int scmi_iio_sensor_update_cb(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct scmi_sensor_update_report *sensor_update = data; + struct iio_dev *scmi_iio_dev; + struct scmi_iio_priv *sensor; + s8 tstamp_scale; + u64 time, time_ns; + int i; + + if (sensor_update->readings_count == 0) + return NOTIFY_DONE; + + sensor = container_of(nb, struct scmi_iio_priv, sensor_update_nb); + + for (i = 0; i < sensor_update->readings_count; i++) + sensor->iio_buf[i] = sensor_update->readings[i].value; + + if (!sensor->sensor_info->timestamped) { + time_ns = ktime_to_ns(sensor_update->timestamp); + } else { + /* + * All the axes are supposed to have the same value for timestamp. + * We are just using the values from the Axis 0 here. + */ + time = sensor_update->readings[0].timestamp; + + /* + * Timestamp returned by SCMI is in seconds and is equal to + * time * power-of-10 multiplier(tstamp_scale) seconds. + * Converting the timestamp to nanoseconds (10⁹) below. + */ + tstamp_scale = sensor->sensor_info->tstamp_scale + 9; + if (tstamp_scale < 0) { + do_div(time, int_pow(10, abs(tstamp_scale))); + time_ns = time; + } else { + time_ns = time * int_pow(10, tstamp_scale); + } + } + + scmi_iio_dev = sensor->indio_dev; + iio_push_to_buffers_with_timestamp(scmi_iio_dev, sensor->iio_buf, + time_ns); + return NOTIFY_OK; +} + +static int scmi_iio_buffer_preenable(struct iio_dev *iio_dev) +{ + struct scmi_iio_priv *sensor = iio_priv(iio_dev); + u32 sensor_config = 0; + int err; + + if (sensor->sensor_info->timestamped) + sensor_config |= FIELD_PREP(SCMI_SENS_CFG_TSTAMP_ENABLED_MASK, + SCMI_SENS_CFG_TSTAMP_ENABLE); + + sensor_config |= FIELD_PREP(SCMI_SENS_CFG_SENSOR_ENABLED_MASK, + SCMI_SENS_CFG_SENSOR_ENABLE); + err = sensor->sensor_ops->config_set(sensor->ph, + sensor->sensor_info->id, + sensor_config); + if (err) + dev_err(&iio_dev->dev, "Error in enabling sensor %s err %d", + sensor->sensor_info->name, err); + + return err; +} + +static int scmi_iio_buffer_postdisable(struct iio_dev *iio_dev) +{ + struct scmi_iio_priv *sensor = iio_priv(iio_dev); + u32 sensor_config = 0; + int err; + + sensor_config |= FIELD_PREP(SCMI_SENS_CFG_SENSOR_ENABLED_MASK, + SCMI_SENS_CFG_SENSOR_DISABLE); + err = sensor->sensor_ops->config_set(sensor->ph, + sensor->sensor_info->id, + sensor_config); + if (err) { + dev_err(&iio_dev->dev, + "Error in disabling sensor %s with err %d", + sensor->sensor_info->name, err); + } + + return err; +} + +static const struct iio_buffer_setup_ops scmi_iio_buffer_ops = { + .preenable = scmi_iio_buffer_preenable, + .postdisable = scmi_iio_buffer_postdisable, +}; + +static int scmi_iio_set_odr_val(struct iio_dev *iio_dev, int val, int val2) +{ + struct scmi_iio_priv *sensor = iio_priv(iio_dev); + u64 sec, mult, uHz, sf; + u32 sensor_config; + char buf[32]; + + int err = sensor->sensor_ops->config_get(sensor->ph, + sensor->sensor_info->id, + &sensor_config); + if (err) { + dev_err(&iio_dev->dev, + "Error in getting sensor config for sensor %s err %d", + sensor->sensor_info->name, err); + return err; + } + + uHz = val * MICROHZ_PER_HZ + val2; + + /* + * The seconds field in the sensor interval in SCMI is 16 bits long + * Therefore seconds = 1/Hz <= 0xFFFF. As floating point calculations are + * discouraged in the kernel driver code, to calculate the scale factor (sf) + * (1* 1000000 * sf)/uHz <= 0xFFFF. Therefore, sf <= (uHz * 0xFFFF)/1000000 + * To calculate the multiplier,we convert the sf into char string and + * count the number of characters + */ + sf = uHz * 0xFFFF; + do_div(sf, MICROHZ_PER_HZ); + mult = scnprintf(buf, sizeof(buf), "%llu", sf) - 1; + + sec = int_pow(10, mult) * MICROHZ_PER_HZ; + do_div(sec, uHz); + if (sec == 0) { + dev_err(&iio_dev->dev, + "Trying to set invalid sensor update value for sensor %s", + sensor->sensor_info->name); + return -EINVAL; + } + + sensor_config &= ~SCMI_SENS_CFG_UPDATE_SECS_MASK; + sensor_config |= FIELD_PREP(SCMI_SENS_CFG_UPDATE_SECS_MASK, sec); + sensor_config &= ~SCMI_SENS_CFG_UPDATE_EXP_MASK; + sensor_config |= FIELD_PREP(SCMI_SENS_CFG_UPDATE_EXP_MASK, -mult); + + if (sensor->sensor_info->timestamped) { + sensor_config &= ~SCMI_SENS_CFG_TSTAMP_ENABLED_MASK; + sensor_config |= FIELD_PREP(SCMI_SENS_CFG_TSTAMP_ENABLED_MASK, + SCMI_SENS_CFG_TSTAMP_ENABLE); + } + + sensor_config &= ~SCMI_SENS_CFG_ROUND_MASK; + sensor_config |= + FIELD_PREP(SCMI_SENS_CFG_ROUND_MASK, SCMI_SENS_CFG_ROUND_AUTO); + + err = sensor->sensor_ops->config_set(sensor->ph, + sensor->sensor_info->id, + sensor_config); + if (err) + dev_err(&iio_dev->dev, + "Error in setting sensor update interval for sensor %s value %u err %d", + sensor->sensor_info->name, sensor_config, err); + + return err; +} + +static int scmi_iio_write_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct scmi_iio_priv *sensor = iio_priv(iio_dev); + int err; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + mutex_lock(&sensor->lock); + err = scmi_iio_set_odr_val(iio_dev, val, val2); + mutex_unlock(&sensor->lock); + return err; + default: + return -EINVAL; + } +} + +static int scmi_iio_read_avail(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct scmi_iio_priv *sensor = iio_priv(iio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = sensor->freq_avail; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = sensor->sensor_info->intervals.count * 2; + if (sensor->sensor_info->intervals.segmented) + return IIO_AVAIL_RANGE; + else + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static void convert_ns_to_freq(u64 interval_ns, u64 *hz, u64 *uhz) +{ + u64 rem, freq; + + freq = NSEC_PER_SEC; + rem = do_div(freq, interval_ns); + *hz = freq; + *uhz = rem * 1000000UL; + do_div(*uhz, interval_ns); +} + +static int scmi_iio_get_odr_val(struct iio_dev *iio_dev, int *val, int *val2) +{ + u64 sensor_update_interval, sensor_interval_mult, hz, uhz; + struct scmi_iio_priv *sensor = iio_priv(iio_dev); + u32 sensor_config; + int mult; + + int err = sensor->sensor_ops->config_get(sensor->ph, + sensor->sensor_info->id, + &sensor_config); + if (err) { + dev_err(&iio_dev->dev, + "Error in getting sensor config for sensor %s err %d", + sensor->sensor_info->name, err); + return err; + } + + sensor_update_interval = + SCMI_SENS_CFG_GET_UPDATE_SECS(sensor_config) * NSEC_PER_SEC; + + mult = SCMI_SENS_CFG_GET_UPDATE_EXP(sensor_config); + if (mult < 0) { + sensor_interval_mult = int_pow(10, abs(mult)); + do_div(sensor_update_interval, sensor_interval_mult); + } else { + sensor_interval_mult = int_pow(10, mult); + sensor_update_interval = + sensor_update_interval * sensor_interval_mult; + } + + convert_ns_to_freq(sensor_update_interval, &hz, &uhz); + *val = hz; + *val2 = uhz; + return 0; +} + +static int scmi_iio_read_channel_data(struct iio_dev *iio_dev, + struct iio_chan_spec const *ch, int *val, int *val2) +{ + struct scmi_iio_priv *sensor = iio_priv(iio_dev); + u32 sensor_config; + struct scmi_sensor_reading readings[SCMI_IIO_NUM_OF_AXIS]; + int err; + + sensor_config = FIELD_PREP(SCMI_SENS_CFG_SENSOR_ENABLED_MASK, + SCMI_SENS_CFG_SENSOR_ENABLE); + err = sensor->sensor_ops->config_set( + sensor->ph, sensor->sensor_info->id, sensor_config); + if (err) { + dev_err(&iio_dev->dev, + "Error in enabling sensor %s err %d", + sensor->sensor_info->name, err); + return err; + } + + err = sensor->sensor_ops->reading_get_timestamped( + sensor->ph, sensor->sensor_info->id, + sensor->sensor_info->num_axis, readings); + if (err) { + dev_err(&iio_dev->dev, + "Error in reading raw attribute for sensor %s err %d", + sensor->sensor_info->name, err); + return err; + } + + sensor_config = FIELD_PREP(SCMI_SENS_CFG_SENSOR_ENABLED_MASK, + SCMI_SENS_CFG_SENSOR_DISABLE); + err = sensor->sensor_ops->config_set( + sensor->ph, sensor->sensor_info->id, sensor_config); + if (err) { + dev_err(&iio_dev->dev, + "Error in disabling sensor %s err %d", + sensor->sensor_info->name, err); + return err; + } + + *val = lower_32_bits(readings[ch->scan_index].value); + *val2 = upper_32_bits(readings[ch->scan_index].value); + + return IIO_VAL_INT_64; +} + +static int scmi_iio_read_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *ch, int *val, + int *val2, long mask) +{ + struct scmi_iio_priv *sensor = iio_priv(iio_dev); + s8 scale; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + scale = sensor->sensor_info->axis[ch->scan_index].scale; + if (scale < 0) { + *val = 1; + *val2 = int_pow(10, abs(scale)); + return IIO_VAL_FRACTIONAL; + } + *val = int_pow(10, scale); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = scmi_iio_get_odr_val(iio_dev, val, val2); + return ret ? ret : IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(iio_dev)) + return -EBUSY; + + ret = scmi_iio_read_channel_data(iio_dev, ch, val, val2); + iio_device_release_direct(iio_dev); + return ret; + default: + return -EINVAL; + } +} + +static const struct iio_info scmi_iio_info = { + .read_raw = scmi_iio_read_raw, + .read_avail = scmi_iio_read_avail, + .write_raw = scmi_iio_write_raw, +}; + +static ssize_t scmi_iio_get_raw_available(struct iio_dev *iio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct scmi_iio_priv *sensor = iio_priv(iio_dev); + u64 resolution, rem; + s64 min_range, max_range; + s8 exponent, scale; + int len = 0; + + /* + * All the axes are supposed to have the same value for range and resolution. + * We are just using the values from the Axis 0 here. + */ + if (sensor->sensor_info->axis[0].extended_attrs) { + min_range = sensor->sensor_info->axis[0].attrs.min_range; + max_range = sensor->sensor_info->axis[0].attrs.max_range; + resolution = sensor->sensor_info->axis[0].resolution; + exponent = sensor->sensor_info->axis[0].exponent; + scale = sensor->sensor_info->axis[0].scale; + + /* + * To provide the raw value for the resolution to the userspace, + * need to divide the resolution exponent by the sensor scale + */ + exponent = exponent - scale; + if (exponent < 0) { + rem = do_div(resolution, + int_pow(10, abs(exponent)) + ); + len = sysfs_emit(buf, + "[%lld %llu.%llu %lld]\n", min_range, + resolution, rem, max_range); + } else { + resolution = resolution * int_pow(10, exponent); + len = sysfs_emit(buf, "[%lld %llu %lld]\n", + min_range, resolution, max_range); + } + } + return len; +} + +static const struct iio_chan_spec_ext_info scmi_iio_ext_info[] = { + { + .name = "raw_available", + .read = scmi_iio_get_raw_available, + .shared = IIO_SHARED_BY_TYPE, + }, + { } +}; + +static void scmi_iio_set_data_channel(struct iio_chan_spec *iio_chan, + enum iio_chan_type type, + enum iio_modifier mod, int scan_index) +{ + iio_chan->type = type; + iio_chan->modified = 1; + iio_chan->channel2 = mod; + iio_chan->info_mask_separate = + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_RAW); + iio_chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ); + iio_chan->info_mask_shared_by_type_available = + BIT(IIO_CHAN_INFO_SAMP_FREQ); + iio_chan->scan_index = scan_index; + iio_chan->scan_type.sign = 's'; + iio_chan->scan_type.realbits = 64; + iio_chan->scan_type.storagebits = 64; + iio_chan->scan_type.endianness = IIO_LE; + iio_chan->ext_info = scmi_iio_ext_info; +} + +static int scmi_iio_get_chan_modifier(const char *name, + enum iio_modifier *modifier) +{ + char *pch, mod; + + if (!name) + return -EINVAL; + + pch = strrchr(name, '_'); + if (!pch) + return -EINVAL; + + mod = *(pch + 1); + switch (mod) { + case 'X': + *modifier = IIO_MOD_X; + return 0; + case 'Y': + *modifier = IIO_MOD_Y; + return 0; + case 'Z': + *modifier = IIO_MOD_Z; + return 0; + default: + return -EINVAL; + } +} + +static int scmi_iio_get_chan_type(u8 scmi_type, enum iio_chan_type *iio_type) +{ + switch (scmi_type) { + case METERS_SEC_SQUARED: + *iio_type = IIO_ACCEL; + return 0; + case RADIANS_SEC: + *iio_type = IIO_ANGL_VEL; + return 0; + default: + return -EINVAL; + } +} + +static u64 scmi_iio_convert_interval_to_ns(u32 val) +{ + u64 sensor_update_interval = + SCMI_SENS_INTVL_GET_SECS(val) * NSEC_PER_SEC; + u64 sensor_interval_mult; + int mult; + + mult = SCMI_SENS_INTVL_GET_EXP(val); + if (mult < 0) { + sensor_interval_mult = int_pow(10, abs(mult)); + do_div(sensor_update_interval, sensor_interval_mult); + } else { + sensor_interval_mult = int_pow(10, mult); + sensor_update_interval = + sensor_update_interval * sensor_interval_mult; + } + return sensor_update_interval; +} + +static int scmi_iio_set_sampling_freq_avail(struct iio_dev *iio_dev) +{ + u64 cur_interval_ns, low_interval_ns, high_interval_ns, step_size_ns, + hz, uhz; + unsigned int cur_interval, low_interval, high_interval, step_size; + struct scmi_iio_priv *sensor = iio_priv(iio_dev); + int i; + + sensor->freq_avail = + devm_kcalloc(&iio_dev->dev, + array_size(sensor->sensor_info->intervals.count, 2), + sizeof(*sensor->freq_avail), + GFP_KERNEL); + if (!sensor->freq_avail) + return -ENOMEM; + + if (sensor->sensor_info->intervals.segmented) { + low_interval = sensor->sensor_info->intervals + .desc[SCMI_SENS_INTVL_SEGMENT_LOW]; + low_interval_ns = scmi_iio_convert_interval_to_ns(low_interval); + convert_ns_to_freq(low_interval_ns, &hz, &uhz); + sensor->freq_avail[0] = hz; + sensor->freq_avail[1] = uhz; + + step_size = sensor->sensor_info->intervals + .desc[SCMI_SENS_INTVL_SEGMENT_STEP]; + step_size_ns = scmi_iio_convert_interval_to_ns(step_size); + convert_ns_to_freq(step_size_ns, &hz, &uhz); + sensor->freq_avail[2] = hz; + sensor->freq_avail[3] = uhz; + + high_interval = sensor->sensor_info->intervals + .desc[SCMI_SENS_INTVL_SEGMENT_HIGH]; + high_interval_ns = + scmi_iio_convert_interval_to_ns(high_interval); + convert_ns_to_freq(high_interval_ns, &hz, &uhz); + sensor->freq_avail[4] = hz; + sensor->freq_avail[5] = uhz; + } else { + for (i = 0; i < sensor->sensor_info->intervals.count; i++) { + cur_interval = sensor->sensor_info->intervals.desc[i]; + cur_interval_ns = + scmi_iio_convert_interval_to_ns(cur_interval); + convert_ns_to_freq(cur_interval_ns, &hz, &uhz); + sensor->freq_avail[i * 2] = hz; + sensor->freq_avail[i * 2 + 1] = uhz; + } + } + return 0; +} + +static struct iio_dev * +scmi_alloc_iiodev(struct scmi_device *sdev, + const struct scmi_sensor_proto_ops *ops, + struct scmi_protocol_handle *ph, + const struct scmi_sensor_info *sensor_info) +{ + struct iio_chan_spec *iio_channels; + struct scmi_iio_priv *sensor; + enum iio_modifier modifier; + enum iio_chan_type type; + struct iio_dev *iiodev; + struct device *dev = &sdev->dev; + const struct scmi_handle *handle = sdev->handle; + int i, ret; + + iiodev = devm_iio_device_alloc(dev, sizeof(*sensor)); + if (!iiodev) + return ERR_PTR(-ENOMEM); + + iiodev->modes = INDIO_DIRECT_MODE; + sensor = iio_priv(iiodev); + sensor->sensor_ops = ops; + sensor->ph = ph; + sensor->sensor_info = sensor_info; + sensor->sensor_update_nb.notifier_call = scmi_iio_sensor_update_cb; + sensor->indio_dev = iiodev; + mutex_init(&sensor->lock); + + /* adding one additional channel for timestamp */ + iiodev->num_channels = sensor_info->num_axis + 1; + iiodev->name = sensor_info->name; + iiodev->info = &scmi_iio_info; + + iio_channels = + devm_kcalloc(dev, iiodev->num_channels, + sizeof(*iio_channels), + GFP_KERNEL); + if (!iio_channels) + return ERR_PTR(-ENOMEM); + + ret = scmi_iio_set_sampling_freq_avail(iiodev); + if (ret < 0) + return ERR_PTR(ret); + + for (i = 0; i < sensor_info->num_axis; i++) { + ret = scmi_iio_get_chan_type(sensor_info->axis[i].type, &type); + if (ret < 0) + return ERR_PTR(ret); + + ret = scmi_iio_get_chan_modifier(sensor_info->axis[i].name, + &modifier); + if (ret < 0) + return ERR_PTR(ret); + + scmi_iio_set_data_channel(&iio_channels[i], type, modifier, + sensor_info->axis[i].id); + } + + ret = handle->notify_ops->devm_event_notifier_register(sdev, + SCMI_PROTOCOL_SENSOR, SCMI_EVENT_SENSOR_UPDATE, + &sensor->sensor_info->id, + &sensor->sensor_update_nb); + if (ret) + return dev_err_ptr_probe(&iiodev->dev, ret, + "Error in registering sensor update notifier for sensor %s\n", + sensor->sensor_info->name); + + iio_channels[i] = IIO_CHAN_SOFT_TIMESTAMP(i); + iiodev->channels = iio_channels; + return iiodev; +} + +static int scmi_iio_dev_probe(struct scmi_device *sdev) +{ + const struct scmi_sensor_info *sensor_info; + struct scmi_handle *handle = sdev->handle; + const struct scmi_sensor_proto_ops *sensor_ops; + struct scmi_protocol_handle *ph; + struct device *dev = &sdev->dev; + struct iio_dev *scmi_iio_dev; + u16 nr_sensors; + int err = -ENODEV, i; + + if (!handle) + return -ENODEV; + + sensor_ops = handle->devm_protocol_get(sdev, SCMI_PROTOCOL_SENSOR, &ph); + if (IS_ERR(sensor_ops)) + return dev_err_probe(dev, PTR_ERR(sensor_ops), + "SCMI device has no sensor interface\n"); + + nr_sensors = sensor_ops->count_get(ph); + if (!nr_sensors) { + dev_dbg(dev, "0 sensors found via SCMI bus\n"); + return -ENODEV; + } + + for (i = 0; i < nr_sensors; i++) { + sensor_info = sensor_ops->info_get(ph, i); + if (!sensor_info) { + return dev_err_probe(dev, -EINVAL, + "SCMI sensor %d has missing info\n", i); + } + + /* This driver only supports 3-axis accel and gyro, skipping other sensors */ + if (sensor_info->num_axis != SCMI_IIO_NUM_OF_AXIS) + continue; + + /* This driver only supports 3-axis accel and gyro, skipping other sensors */ + if (sensor_info->axis[0].type != METERS_SEC_SQUARED && + sensor_info->axis[0].type != RADIANS_SEC) + continue; + + scmi_iio_dev = scmi_alloc_iiodev(sdev, sensor_ops, ph, + sensor_info); + if (IS_ERR(scmi_iio_dev)) { + return dev_err_probe(dev, PTR_ERR(scmi_iio_dev), + "failed to allocate IIO device for sensor %s\n", + sensor_info->name); + } + + err = devm_iio_kfifo_buffer_setup(&scmi_iio_dev->dev, + scmi_iio_dev, + &scmi_iio_buffer_ops); + if (err < 0) { + return dev_err_probe(dev, err, + "IIO buffer setup error at sensor %s\n", + sensor_info->name); + } + + err = devm_iio_device_register(dev, scmi_iio_dev); + if (err) + return dev_err_probe(dev, err, + "IIO device registration failed at sensor %s\n", + sensor_info->name); + } + return err; +} + +static const struct scmi_device_id scmi_id_table[] = { + { SCMI_PROTOCOL_SENSOR, "iiodev" }, + { } +}; + +MODULE_DEVICE_TABLE(scmi, scmi_id_table); + +static struct scmi_driver scmi_iiodev_driver = { + .name = "scmi-sensor-iiodev", + .probe = scmi_iio_dev_probe, + .id_table = scmi_id_table, +}; + +module_scmi_driver(scmi_iiodev_driver); + +MODULE_AUTHOR("Jyoti Bhayana "); +MODULE_DESCRIPTION("SCMI IIO Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/ssp_sensors/Kconfig b/drivers/iio/common/ssp_sensors/Kconfig new file mode 100644 index 000000000..5262409e4 --- /dev/null +++ b/drivers/iio/common/ssp_sensors/Kconfig @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# SSP sensor drivers and commons configuration +# +menu "SSP Sensor Common" + +config IIO_SSP_SENSORS_COMMONS + tristate "Commons for all SSP Sensor IIO drivers" + depends on IIO_SSP_SENSORHUB + select IIO_BUFFER + select IIO_KFIFO_BUF + help + Say yes here to build commons for SSP sensors. + To compile this as a module, choose M here: the module + will be called ssp_iio. + +config IIO_SSP_SENSORHUB + tristate "Samsung Sensorhub driver" + depends on SPI + select MFD_CORE + help + SSP driver for sensorhub. + If you say yes here you get ssp support for sensorhub. + To compile this driver as a module, choose M here: the + module will be called sensorhub. + +endmenu diff --git a/drivers/iio/common/ssp_sensors/Makefile b/drivers/iio/common/ssp_sensors/Makefile new file mode 100644 index 000000000..ba831429b --- /dev/null +++ b/drivers/iio/common/ssp_sensors/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for SSP sensor drivers and commons. +# + +sensorhub-objs := ssp_dev.o ssp_spi.o +obj-$(CONFIG_IIO_SSP_SENSORHUB) += sensorhub.o + +obj-$(CONFIG_IIO_SSP_SENSORS_COMMONS) += ssp_iio.o diff --git a/drivers/iio/common/ssp_sensors/ssp.h b/drivers/iio/common/ssp_sensors/ssp.h new file mode 100644 index 000000000..f649cdecc --- /dev/null +++ b/drivers/iio/common/ssp_sensors/ssp.h @@ -0,0 +1,246 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014, Samsung Electronics Co. Ltd. All Rights Reserved. + */ + +#ifndef __SSP_SENSORHUB_H__ +#define __SSP_SENSORHUB_H__ + +#include +#include +#include +#include +#include + +#define SSP_DEVICE_ID 0x55 + +#ifdef SSP_DBG +#define ssp_dbg(format, ...) pr_info("[SSP] "format, ##__VA_ARGS__) +#else +#define ssp_dbg(format, ...) +#endif + +#define SSP_SW_RESET_TIME 3000 +/* Sensor polling in ms */ +#define SSP_DEFAULT_POLLING_DELAY 200 +#define SSP_DEFAULT_RETRIES 3 +#define SSP_DATA_PACKET_SIZE 960 +#define SSP_HEADER_BUFFER_SIZE 4 + +enum { + SSP_KERNEL_BINARY = 0, + SSP_KERNEL_CRASHED_BINARY, +}; + +enum { + SSP_INITIALIZATION_STATE = 0, + SSP_NO_SENSOR_STATE, + SSP_ADD_SENSOR_STATE, + SSP_RUNNING_SENSOR_STATE, +}; + +/* Firmware download STATE */ +enum { + SSP_FW_DL_STATE_FAIL = -1, + SSP_FW_DL_STATE_NONE = 0, + SSP_FW_DL_STATE_NEED_TO_SCHEDULE, + SSP_FW_DL_STATE_SCHEDULED, + SSP_FW_DL_STATE_DOWNLOADING, + SSP_FW_DL_STATE_SYNC, + SSP_FW_DL_STATE_DONE, +}; + +#define SSP_INVALID_REVISION 99999 +#define SSP_INVALID_REVISION2 0xffffff + +/* AP -> SSP Instruction */ +#define SSP_MSG2SSP_INST_BYPASS_SENSOR_ADD 0xa1 +#define SSP_MSG2SSP_INST_BYPASS_SENSOR_RM 0xa2 +#define SSP_MSG2SSP_INST_REMOVE_ALL 0xa3 +#define SSP_MSG2SSP_INST_CHANGE_DELAY 0xa4 +#define SSP_MSG2SSP_INST_LIBRARY_ADD 0xb1 +#define SSP_MSG2SSP_INST_LIBRARY_REMOVE 0xb2 +#define SSP_MSG2SSP_INST_LIB_NOTI 0xb4 +#define SSP_MSG2SSP_INST_LIB_DATA 0xc1 + +#define SSP_MSG2SSP_AP_MCU_SET_GYRO_CAL 0xcd +#define SSP_MSG2SSP_AP_MCU_SET_ACCEL_CAL 0xce +#define SSP_MSG2SSP_AP_STATUS_SHUTDOWN 0xd0 +#define SSP_MSG2SSP_AP_STATUS_WAKEUP 0xd1 +#define SSP_MSG2SSP_AP_STATUS_SLEEP 0xd2 +#define SSP_MSG2SSP_AP_STATUS_RESUME 0xd3 +#define SSP_MSG2SSP_AP_STATUS_SUSPEND 0xd4 +#define SSP_MSG2SSP_AP_STATUS_RESET 0xd5 +#define SSP_MSG2SSP_AP_STATUS_POW_CONNECTED 0xd6 +#define SSP_MSG2SSP_AP_STATUS_POW_DISCONNECTED 0xd7 +#define SSP_MSG2SSP_AP_TEMPHUMIDITY_CAL_DONE 0xda +#define SSP_MSG2SSP_AP_MCU_SET_DUMPMODE 0xdb +#define SSP_MSG2SSP_AP_MCU_DUMP_CHECK 0xdc +#define SSP_MSG2SSP_AP_MCU_BATCH_FLUSH 0xdd +#define SSP_MSG2SSP_AP_MCU_BATCH_COUNT 0xdf + +#define SSP_MSG2SSP_AP_WHOAMI 0x0f +#define SSP_MSG2SSP_AP_FIRMWARE_REV 0xf0 +#define SSP_MSG2SSP_AP_SENSOR_FORMATION 0xf1 +#define SSP_MSG2SSP_AP_SENSOR_PROXTHRESHOLD 0xf2 +#define SSP_MSG2SSP_AP_SENSOR_BARCODE_EMUL 0xf3 +#define SSP_MSG2SSP_AP_SENSOR_SCANNING 0xf4 +#define SSP_MSG2SSP_AP_SET_MAGNETIC_HWOFFSET 0xf5 +#define SSP_MSG2SSP_AP_GET_MAGNETIC_HWOFFSET 0xf6 +#define SSP_MSG2SSP_AP_SENSOR_GESTURE_CURRENT 0xf7 +#define SSP_MSG2SSP_AP_GET_THERM 0xf8 +#define SSP_MSG2SSP_AP_GET_BIG_DATA 0xf9 +#define SSP_MSG2SSP_AP_SET_BIG_DATA 0xfa +#define SSP_MSG2SSP_AP_START_BIG_DATA 0xfb +#define SSP_MSG2SSP_AP_SET_MAGNETIC_STATIC_MATRIX 0xfd +#define SSP_MSG2SSP_AP_SENSOR_TILT 0xea +#define SSP_MSG2SSP_AP_MCU_SET_TIME 0xfe +#define SSP_MSG2SSP_AP_MCU_GET_TIME 0xff + +#define SSP_MSG2SSP_AP_FUSEROM 0x01 + +/* voice data */ +#define SSP_TYPE_WAKE_UP_VOICE_SERVICE 0x01 +#define SSP_TYPE_WAKE_UP_VOICE_SOUND_SOURCE_AM 0x01 +#define SSP_TYPE_WAKE_UP_VOICE_SOUND_SOURCE_GRAMMER 0x02 + +/* Factory Test */ +#define SSP_ACCELEROMETER_FACTORY 0x80 +#define SSP_GYROSCOPE_FACTORY 0x81 +#define SSP_GEOMAGNETIC_FACTORY 0x82 +#define SSP_PRESSURE_FACTORY 0x85 +#define SSP_GESTURE_FACTORY 0x86 +#define SSP_TEMPHUMIDITY_CRC_FACTORY 0x88 +#define SSP_GYROSCOPE_TEMP_FACTORY 0x8a +#define SSP_GYROSCOPE_DPS_FACTORY 0x8b +#define SSP_MCU_FACTORY 0x8c +#define SSP_MCU_SLEEP_FACTORY 0x8d + +/* SSP -> AP ACK about write CMD */ +#define SSP_MSG_ACK 0x80 /* ACK from SSP to AP */ +#define SSP_MSG_NAK 0x70 /* NAK from SSP to AP */ + +struct ssp_sensorhub_info { + char *fw_name; + char *fw_crashed_name; + unsigned int fw_rev; + const u8 * const mag_table; + const unsigned int mag_length; +}; + +/* ssp_msg options bit */ +#define SSP_RW 0 +#define SSP_INDEX 3 + +#define SSP_AP2HUB_READ 0 +#define SSP_AP2HUB_WRITE 1 +#define SSP_HUB2AP_WRITE 2 +#define SSP_AP2HUB_READY 3 +#define SSP_AP2HUB_RETURN 4 + +/** + * struct ssp_data - ssp platformdata structure + * @spi: spi device + * @sensorhub_info: info about sensorhub board specific features + * @wdt_timer: watchdog timer + * @work_wdt: watchdog work + * @work_firmware: firmware upgrade work queue + * @work_refresh: refresh work queue for reset request from MCU + * @shut_down: shut down flag + * @mcu_dump_mode: mcu dump mode for debug + * @time_syncing: time syncing indication flag + * @timestamp: previous time in ns calculated for time syncing + * @check_status: status table for each sensor + * @com_fail_cnt: communication fail count + * @reset_cnt: reset count + * @timeout_cnt: timeout count + * @available_sensors: available sensors seen by sensorhub (bit array) + * @cur_firm_rev: cached current firmware revision + * @last_resume_state: last AP resume/suspend state used to handle the PM + * state of ssp + * @last_ap_state: (obsolete) sleep notification for MCU + * @sensor_enable: sensor enable mask + * @delay_buf: data acquisition intervals table + * @batch_latency_buf: yet unknown but existing in communication protocol + * @batch_opt_buf: yet unknown but existing in communication protocol + * @accel_position: yet unknown but existing in communication protocol + * @mag_position: yet unknown but existing in communication protocol + * @fw_dl_state: firmware download state + * @comm_lock: lock protecting the handshake + * @pending_lock: lock protecting pending list and completion + * @mcu_reset_gpiod: mcu reset line + * @ap_mcu_gpiod: ap to mcu gpio line + * @mcu_ap_gpiod: mcu to ap gpio line + * @pending_list: pending list for messages queued to be sent/read + * @sensor_devs: registered IIO devices table + * @enable_refcount: enable reference count for wdt (watchdog timer) + * @header_buffer: cache aligned buffer for packet header + */ +struct ssp_data { + struct spi_device *spi; + const struct ssp_sensorhub_info *sensorhub_info; + struct timer_list wdt_timer; + struct work_struct work_wdt; + struct delayed_work work_refresh; + + bool shut_down; + bool mcu_dump_mode; + bool time_syncing; + int64_t timestamp; + + int check_status[SSP_SENSOR_MAX]; + + unsigned int com_fail_cnt; + unsigned int reset_cnt; + unsigned int timeout_cnt; + + unsigned int available_sensors; + unsigned int cur_firm_rev; + + char last_resume_state; + char last_ap_state; + + unsigned int sensor_enable; + u32 delay_buf[SSP_SENSOR_MAX]; + s32 batch_latency_buf[SSP_SENSOR_MAX]; + s8 batch_opt_buf[SSP_SENSOR_MAX]; + + int accel_position; + int mag_position; + int fw_dl_state; + + struct mutex comm_lock; + struct mutex pending_lock; + + struct gpio_desc *mcu_reset_gpiod; + struct gpio_desc *ap_mcu_gpiod; + struct gpio_desc *mcu_ap_gpiod; + + struct list_head pending_list; + + struct iio_dev *sensor_devs[SSP_SENSOR_MAX]; + atomic_t enable_refcount; + + __le16 header_buffer[SSP_HEADER_BUFFER_SIZE / sizeof(__le16)] __aligned(IIO_DMA_MINALIGN); +}; + +void ssp_clean_pending_list(struct ssp_data *data); + +int ssp_command(struct ssp_data *data, char command, int arg); + +int ssp_send_instruction(struct ssp_data *data, u8 inst, u8 sensor_type, + u8 *send_buf, u8 length); + +int ssp_irq_msg(struct ssp_data *data); + +int ssp_get_chipid(struct ssp_data *data); + +int ssp_set_magnetic_matrix(struct ssp_data *data); + +unsigned int ssp_get_sensor_scanning_info(struct ssp_data *data); + +unsigned int ssp_get_firmware_rev(struct ssp_data *data); + +int ssp_queue_ssp_refresh_task(struct ssp_data *data, unsigned int delay); + +#endif /* __SSP_SENSORHUB_H__ */ diff --git a/drivers/iio/common/ssp_sensors/ssp_dev.c b/drivers/iio/common/ssp_sensors/ssp_dev.c new file mode 100644 index 000000000..828fcfe1d --- /dev/null +++ b/drivers/iio/common/ssp_sensors/ssp_dev.c @@ -0,0 +1,668 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2014, Samsung Electronics Co. Ltd. All Rights Reserved. + */ + +#include +#include +#include +#include +#include +#include + +#include "ssp.h" + +#define SSP_WDT_TIME 10000 +#define SSP_LIMIT_RESET_CNT 20 +#define SSP_LIMIT_TIMEOUT_CNT 3 + +/* It is possible that it is max clk rate for version 1.0 of bootcode */ +#define SSP_BOOT_SPI_HZ 400000 + +/* + * These fields can look enigmatic but this structure is used mainly to flat + * some values and depends on command type. + */ +struct ssp_instruction { + __le32 a; + __le32 b; + u8 c; +} __packed__; + +static const u8 ssp_magnitude_table[] = {110, 85, 171, 71, 203, 195, 0, 67, + 208, 56, 175, 244, 206, 213, 0, 92, 250, 0, 55, 48, 189, 252, 171, + 243, 13, 45, 250}; + +static const struct ssp_sensorhub_info ssp_rinato_info = { + .fw_name = "ssp_B2.fw", + .fw_crashed_name = "ssp_crashed.fw", + .fw_rev = 14052300, + .mag_table = ssp_magnitude_table, + .mag_length = ARRAY_SIZE(ssp_magnitude_table), +}; + +static const struct ssp_sensorhub_info ssp_thermostat_info = { + .fw_name = "thermostat_B2.fw", + .fw_crashed_name = "ssp_crashed.fw", + .fw_rev = 14080600, + .mag_table = ssp_magnitude_table, + .mag_length = ARRAY_SIZE(ssp_magnitude_table), +}; + +static const struct mfd_cell sensorhub_sensor_devs[] = { + { + .name = "ssp-accelerometer", + }, + { + .name = "ssp-gyroscope", + }, +}; + +static void ssp_toggle_mcu_reset_gpio(struct ssp_data *data) +{ + gpiod_set_value(data->mcu_reset_gpiod, 0); + usleep_range(1000, 1200); + gpiod_set_value(data->mcu_reset_gpiod, 1); + msleep(50); +} + +static void ssp_sync_available_sensors(struct ssp_data *data) +{ + int i, ret; + + for (i = 0; i < SSP_SENSOR_MAX; ++i) { + if (data->available_sensors & BIT(i)) { + ret = ssp_enable_sensor(data, i, data->delay_buf[i]); + if (ret < 0) { + dev_err(&data->spi->dev, + "Sync sensor nr: %d fail\n", i); + continue; + } + } + } + + ret = ssp_command(data, SSP_MSG2SSP_AP_MCU_SET_DUMPMODE, + data->mcu_dump_mode); + if (ret < 0) + dev_err(&data->spi->dev, + "SSP_MSG2SSP_AP_MCU_SET_DUMPMODE failed\n"); +} + +static void ssp_enable_mcu(struct ssp_data *data, bool enable) +{ + dev_info(&data->spi->dev, "current shutdown = %d, old = %d\n", enable, + data->shut_down); + + if (enable && data->shut_down) { + data->shut_down = false; + enable_irq(data->spi->irq); + enable_irq_wake(data->spi->irq); + } else if (!enable && !data->shut_down) { + data->shut_down = true; + disable_irq(data->spi->irq); + disable_irq_wake(data->spi->irq); + } else { + dev_warn(&data->spi->dev, "current shutdown = %d, old = %d\n", + enable, data->shut_down); + } +} + +/* + * This function is the first one which communicates with the mcu so it is + * possible that the first attempt will fail + */ +static int ssp_check_fwbl(struct ssp_data *data) +{ + int retries = 0; + + while (retries++ < 5) { + data->cur_firm_rev = ssp_get_firmware_rev(data); + if (data->cur_firm_rev == SSP_INVALID_REVISION || + data->cur_firm_rev == SSP_INVALID_REVISION2) { + dev_warn(&data->spi->dev, + "Invalid revision, trying %d time\n", retries); + } else { + break; + } + } + + if (data->cur_firm_rev == SSP_INVALID_REVISION || + data->cur_firm_rev == SSP_INVALID_REVISION2) { + dev_err(&data->spi->dev, "SSP_INVALID_REVISION\n"); + return SSP_FW_DL_STATE_NEED_TO_SCHEDULE; + } + + dev_info(&data->spi->dev, + "MCU Firm Rev : Old = %8u, New = %8u\n", + data->cur_firm_rev, + data->sensorhub_info->fw_rev); + + if (data->cur_firm_rev != data->sensorhub_info->fw_rev) + return SSP_FW_DL_STATE_NEED_TO_SCHEDULE; + + return SSP_FW_DL_STATE_NONE; +} + +static void ssp_reset_mcu(struct ssp_data *data) +{ + ssp_enable_mcu(data, false); + ssp_clean_pending_list(data); + ssp_toggle_mcu_reset_gpio(data); + ssp_enable_mcu(data, true); +} + +static void ssp_wdt_work_func(struct work_struct *work) +{ + struct ssp_data *data = container_of(work, struct ssp_data, work_wdt); + + dev_err(&data->spi->dev, "%s - Sensor state: 0x%x, RC: %u, CC: %u\n", + __func__, data->available_sensors, data->reset_cnt, + data->com_fail_cnt); + + ssp_reset_mcu(data); + data->com_fail_cnt = 0; + data->timeout_cnt = 0; +} + +static void ssp_wdt_timer_func(struct timer_list *t) +{ + struct ssp_data *data = timer_container_of(data, t, wdt_timer); + + switch (data->fw_dl_state) { + case SSP_FW_DL_STATE_FAIL: + case SSP_FW_DL_STATE_DOWNLOADING: + case SSP_FW_DL_STATE_SYNC: + goto _mod; + } + + if (data->timeout_cnt > SSP_LIMIT_TIMEOUT_CNT || + data->com_fail_cnt > SSP_LIMIT_RESET_CNT) + queue_work(system_power_efficient_wq, &data->work_wdt); +_mod: + mod_timer(&data->wdt_timer, jiffies + msecs_to_jiffies(SSP_WDT_TIME)); +} + +static void ssp_enable_wdt_timer(struct ssp_data *data) +{ + mod_timer(&data->wdt_timer, jiffies + msecs_to_jiffies(SSP_WDT_TIME)); +} + +static void ssp_disable_wdt_timer(struct ssp_data *data) +{ + timer_delete_sync(&data->wdt_timer); + cancel_work_sync(&data->work_wdt); +} + +/** + * ssp_get_sensor_delay() - gets sensor data acquisition period + * @data: sensorhub structure + * @type: SSP sensor type + * + * Returns acquisition period in ms + */ +u32 ssp_get_sensor_delay(struct ssp_data *data, enum ssp_sensor_type type) +{ + return data->delay_buf[type]; +} +EXPORT_SYMBOL_NS(ssp_get_sensor_delay, "IIO_SSP_SENSORS"); + +/** + * ssp_enable_sensor() - enables data acquisition for sensor + * @data: sensorhub structure + * @type: SSP sensor type + * @delay: delay in ms + * + * Returns 0 or negative value in case of error + */ +int ssp_enable_sensor(struct ssp_data *data, enum ssp_sensor_type type, + u32 delay) +{ + int ret; + struct ssp_instruction to_send; + + to_send.a = cpu_to_le32(delay); + to_send.b = cpu_to_le32(data->batch_latency_buf[type]); + to_send.c = data->batch_opt_buf[type]; + + switch (data->check_status[type]) { + case SSP_INITIALIZATION_STATE: + /* do calibration step, now just enable */ + case SSP_ADD_SENSOR_STATE: + ret = ssp_send_instruction(data, + SSP_MSG2SSP_INST_BYPASS_SENSOR_ADD, + type, + (u8 *)&to_send, sizeof(to_send)); + if (ret < 0) { + dev_err(&data->spi->dev, "Enabling sensor failed\n"); + data->check_status[type] = SSP_NO_SENSOR_STATE; + goto derror; + } + + data->sensor_enable |= BIT(type); + data->check_status[type] = SSP_RUNNING_SENSOR_STATE; + break; + case SSP_RUNNING_SENSOR_STATE: + ret = ssp_send_instruction(data, + SSP_MSG2SSP_INST_CHANGE_DELAY, type, + (u8 *)&to_send, sizeof(to_send)); + if (ret < 0) { + dev_err(&data->spi->dev, + "Changing sensor delay failed\n"); + goto derror; + } + break; + default: + data->check_status[type] = SSP_ADD_SENSOR_STATE; + break; + } + + data->delay_buf[type] = delay; + + if (atomic_inc_return(&data->enable_refcount) == 1) + ssp_enable_wdt_timer(data); + + return 0; + +derror: + return ret; +} +EXPORT_SYMBOL_NS(ssp_enable_sensor, "IIO_SSP_SENSORS"); + +/** + * ssp_change_delay() - changes data acquisition for sensor + * @data: sensorhub structure + * @type: SSP sensor type + * @delay: delay in ms + * + * Returns 0 or negative value in case of error + */ +int ssp_change_delay(struct ssp_data *data, enum ssp_sensor_type type, + u32 delay) +{ + int ret; + struct ssp_instruction to_send; + + to_send.a = cpu_to_le32(delay); + to_send.b = cpu_to_le32(data->batch_latency_buf[type]); + to_send.c = data->batch_opt_buf[type]; + + ret = ssp_send_instruction(data, SSP_MSG2SSP_INST_CHANGE_DELAY, type, + (u8 *)&to_send, sizeof(to_send)); + if (ret < 0) { + dev_err(&data->spi->dev, "Changing sensor delay failed\n"); + return ret; + } + + data->delay_buf[type] = delay; + + return 0; +} +EXPORT_SYMBOL_NS(ssp_change_delay, "IIO_SSP_SENSORS"); + +/** + * ssp_disable_sensor() - disables sensor + * + * @data: sensorhub structure + * @type: SSP sensor type + * + * Returns 0 or negative value in case of error + */ +int ssp_disable_sensor(struct ssp_data *data, enum ssp_sensor_type type) +{ + int ret; + __le32 command; + + if (data->sensor_enable & BIT(type)) { + command = cpu_to_le32(data->delay_buf[type]); + + ret = ssp_send_instruction(data, + SSP_MSG2SSP_INST_BYPASS_SENSOR_RM, + type, (u8 *)&command, + sizeof(command)); + if (ret < 0) { + dev_err(&data->spi->dev, "Remove sensor fail\n"); + return ret; + } + + data->sensor_enable &= ~BIT(type); + } + + data->check_status[type] = SSP_ADD_SENSOR_STATE; + + if (atomic_dec_and_test(&data->enable_refcount)) + ssp_disable_wdt_timer(data); + + return 0; +} +EXPORT_SYMBOL_NS(ssp_disable_sensor, "IIO_SSP_SENSORS"); + +static irqreturn_t ssp_irq_thread_fn(int irq, void *dev_id) +{ + struct ssp_data *data = dev_id; + + /* + * This wrapper is done to preserve error path for ssp_irq_msg, also + * it is defined in different file. + */ + ssp_irq_msg(data); + + return IRQ_HANDLED; +} + +static int ssp_initialize_mcu(struct ssp_data *data) +{ + int ret; + + ssp_clean_pending_list(data); + + ret = ssp_get_chipid(data); + if (ret != SSP_DEVICE_ID) { + dev_err(&data->spi->dev, "%s - MCU %s ret = %d\n", __func__, + ret < 0 ? "is not working" : "identification failed", + ret); + return ret < 0 ? ret : -ENODEV; + } + + dev_info(&data->spi->dev, "MCU device ID = %d\n", ret); + + /* + * needs clarification, for now do not want to export all transfer + * methods to sensors' drivers + */ + ret = ssp_set_magnetic_matrix(data); + if (ret < 0) { + dev_err(&data->spi->dev, + "%s - ssp_set_magnetic_matrix failed\n", __func__); + return ret; + } + + data->available_sensors = ssp_get_sensor_scanning_info(data); + if (data->available_sensors == 0) { + dev_err(&data->spi->dev, + "%s - ssp_get_sensor_scanning_info failed\n", __func__); + return -EIO; + } + + data->cur_firm_rev = ssp_get_firmware_rev(data); + dev_info(&data->spi->dev, "MCU Firm Rev : New = %8u\n", + data->cur_firm_rev); + + return ssp_command(data, SSP_MSG2SSP_AP_MCU_DUMP_CHECK, 0); +} + +/* + * sensorhub can request its reinitialization as some brutal and rare error + * handling. It can be requested from the MCU. + */ +static void ssp_refresh_task(struct work_struct *work) +{ + struct ssp_data *data = container_of((struct delayed_work *)work, + struct ssp_data, work_refresh); + + dev_info(&data->spi->dev, "refreshing\n"); + + data->reset_cnt++; + + if (ssp_initialize_mcu(data) >= 0) { + ssp_sync_available_sensors(data); + if (data->last_ap_state != 0) + ssp_command(data, data->last_ap_state, 0); + + if (data->last_resume_state != 0) + ssp_command(data, data->last_resume_state, 0); + + data->timeout_cnt = 0; + data->com_fail_cnt = 0; + } +} + +int ssp_queue_ssp_refresh_task(struct ssp_data *data, unsigned int delay) +{ + cancel_delayed_work_sync(&data->work_refresh); + + return queue_delayed_work(system_power_efficient_wq, + &data->work_refresh, + msecs_to_jiffies(delay)); +} + +static const struct of_device_id ssp_of_match[] = { + { + .compatible = "samsung,sensorhub-rinato", + .data = &ssp_rinato_info, + }, { + .compatible = "samsung,sensorhub-thermostat", + .data = &ssp_thermostat_info, + }, + { } +}; +MODULE_DEVICE_TABLE(of, ssp_of_match); + +static struct ssp_data *ssp_parse_dt(struct device *dev) +{ + struct ssp_data *data; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return NULL; + + data->mcu_ap_gpiod = devm_gpiod_get(dev, "mcu-ap", GPIOD_IN); + if (IS_ERR(data->mcu_ap_gpiod)) + return NULL; + + data->ap_mcu_gpiod = devm_gpiod_get(dev, "ap-mcu", GPIOD_OUT_HIGH); + if (IS_ERR(data->ap_mcu_gpiod)) + return NULL; + + data->mcu_reset_gpiod = devm_gpiod_get(dev, "mcu-reset", + GPIOD_OUT_HIGH); + if (IS_ERR(data->mcu_reset_gpiod)) + return NULL; + + data->sensorhub_info = device_get_match_data(dev); + + dev_set_drvdata(dev, data); + + return data; +} + +/** + * ssp_register_consumer() - registers iio consumer in ssp framework + * + * @indio_dev: consumer iio device + * @type: ssp sensor type + */ +void ssp_register_consumer(struct iio_dev *indio_dev, enum ssp_sensor_type type) +{ + struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent); + + data->sensor_devs[type] = indio_dev; +} +EXPORT_SYMBOL_NS(ssp_register_consumer, "IIO_SSP_SENSORS"); + +static int ssp_probe(struct spi_device *spi) +{ + int ret, i; + struct ssp_data *data; + + data = ssp_parse_dt(&spi->dev); + if (!data) { + dev_err(&spi->dev, "Failed to find platform data\n"); + return -ENODEV; + } + + ret = mfd_add_devices(&spi->dev, PLATFORM_DEVID_NONE, + sensorhub_sensor_devs, + ARRAY_SIZE(sensorhub_sensor_devs), NULL, 0, NULL); + if (ret < 0) { + dev_err(&spi->dev, "mfd add devices fail\n"); + return ret; + } + + spi->mode = SPI_MODE_1; + ret = spi_setup(spi); + if (ret < 0) { + dev_err(&spi->dev, "Failed to setup spi\n"); + goto err_setup_spi; + } + + data->fw_dl_state = SSP_FW_DL_STATE_NONE; + data->spi = spi; + spi_set_drvdata(spi, data); + + mutex_init(&data->comm_lock); + + for (i = 0; i < SSP_SENSOR_MAX; ++i) { + data->delay_buf[i] = SSP_DEFAULT_POLLING_DELAY; + data->batch_latency_buf[i] = 0; + data->batch_opt_buf[i] = 0; + data->check_status[i] = SSP_INITIALIZATION_STATE; + } + + data->delay_buf[SSP_BIO_HRM_LIB] = 100; + + data->time_syncing = true; + + mutex_init(&data->pending_lock); + INIT_LIST_HEAD(&data->pending_list); + + atomic_set(&data->enable_refcount, 0); + + INIT_WORK(&data->work_wdt, ssp_wdt_work_func); + INIT_DELAYED_WORK(&data->work_refresh, ssp_refresh_task); + + timer_setup(&data->wdt_timer, ssp_wdt_timer_func, 0); + + ret = request_threaded_irq(data->spi->irq, NULL, + ssp_irq_thread_fn, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "SSP_Int", data); + if (ret < 0) { + dev_err(&spi->dev, "Irq request fail\n"); + goto err_setup_irq; + } + + /* Let's start with enabled one so irq balance could be ok */ + data->shut_down = false; + + /* just to avoid unbalanced irq set wake up */ + enable_irq_wake(data->spi->irq); + + data->fw_dl_state = ssp_check_fwbl(data); + if (data->fw_dl_state == SSP_FW_DL_STATE_NONE) { + ret = ssp_initialize_mcu(data); + if (ret < 0) { + dev_err(&spi->dev, "Initialize_mcu failed\n"); + goto err_read_reg; + } + } else { + dev_err(&spi->dev, "Firmware version not supported\n"); + ret = -EPERM; + goto err_read_reg; + } + + return 0; + +err_read_reg: + free_irq(data->spi->irq, data); +err_setup_irq: + mutex_destroy(&data->pending_lock); + mutex_destroy(&data->comm_lock); +err_setup_spi: + mfd_remove_devices(&spi->dev); + + dev_err(&spi->dev, "Probe failed!\n"); + + return ret; +} + +static void ssp_remove(struct spi_device *spi) +{ + struct ssp_data *data = spi_get_drvdata(spi); + + if (ssp_command(data, SSP_MSG2SSP_AP_STATUS_SHUTDOWN, 0) < 0) + dev_err(&data->spi->dev, + "SSP_MSG2SSP_AP_STATUS_SHUTDOWN failed\n"); + + ssp_enable_mcu(data, false); + ssp_disable_wdt_timer(data); + + ssp_clean_pending_list(data); + + free_irq(data->spi->irq, data); + cancel_delayed_work_sync(&data->work_refresh); + + timer_delete_sync(&data->wdt_timer); + cancel_work_sync(&data->work_wdt); + + mutex_destroy(&data->comm_lock); + mutex_destroy(&data->pending_lock); + + mfd_remove_devices(&spi->dev); +} + +static int ssp_suspend(struct device *dev) +{ + int ret; + struct ssp_data *data = spi_get_drvdata(to_spi_device(dev)); + + data->last_resume_state = SSP_MSG2SSP_AP_STATUS_SUSPEND; + + if (atomic_read(&data->enable_refcount) > 0) + ssp_disable_wdt_timer(data); + + ret = ssp_command(data, SSP_MSG2SSP_AP_STATUS_SUSPEND, 0); + if (ret < 0) { + dev_err(&data->spi->dev, + "%s SSP_MSG2SSP_AP_STATUS_SUSPEND failed\n", __func__); + + ssp_enable_wdt_timer(data); + return ret; + } + + data->time_syncing = false; + disable_irq(data->spi->irq); + + return 0; +} + +static int ssp_resume(struct device *dev) +{ + int ret; + struct ssp_data *data = spi_get_drvdata(to_spi_device(dev)); + + enable_irq(data->spi->irq); + + if (atomic_read(&data->enable_refcount) > 0) + ssp_enable_wdt_timer(data); + + ret = ssp_command(data, SSP_MSG2SSP_AP_STATUS_RESUME, 0); + if (ret < 0) { + dev_err(&data->spi->dev, + "%s SSP_MSG2SSP_AP_STATUS_RESUME failed\n", __func__); + ssp_disable_wdt_timer(data); + return ret; + } + + /* timesyncing is set by MCU */ + data->last_resume_state = SSP_MSG2SSP_AP_STATUS_RESUME; + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ssp_pm_ops, ssp_suspend, ssp_resume); + +static struct spi_driver ssp_driver = { + .probe = ssp_probe, + .remove = ssp_remove, + .driver = { + .pm = pm_sleep_ptr(&ssp_pm_ops), + .of_match_table = ssp_of_match, + .name = "sensorhub" + }, +}; + +module_spi_driver(ssp_driver); + +MODULE_DESCRIPTION("ssp sensorhub driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/common/ssp_sensors/ssp_iio.c b/drivers/iio/common/ssp_sensors/ssp_iio.c new file mode 100644 index 000000000..78ac689de --- /dev/null +++ b/drivers/iio/common/ssp_sensors/ssp_iio.c @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2014, Samsung Electronics Co. Ltd. All Rights Reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "ssp_iio_sensor.h" + +/** + * ssp_common_buffer_postenable() - generic postenable callback for ssp buffer + * + * @indio_dev: iio device + * + * Returns 0 or negative value in case of error + */ +int ssp_common_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ssp_sensor_data *spd = iio_priv(indio_dev); + struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent); + + /* the allocation is made in post because scan size is known in this + * moment + * */ + spd->buffer = kmalloc(indio_dev->scan_bytes, GFP_KERNEL | GFP_DMA); + if (!spd->buffer) + return -ENOMEM; + + return ssp_enable_sensor(data, spd->type, + ssp_get_sensor_delay(data, spd->type)); +} +EXPORT_SYMBOL_NS(ssp_common_buffer_postenable, "IIO_SSP_SENSORS"); + +/** + * ssp_common_buffer_postdisable() - generic postdisable callback for ssp buffer + * + * @indio_dev: iio device + * + * Returns 0 or negative value in case of error + */ +int ssp_common_buffer_postdisable(struct iio_dev *indio_dev) +{ + int ret; + struct ssp_sensor_data *spd = iio_priv(indio_dev); + struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent); + + ret = ssp_disable_sensor(data, spd->type); + if (ret < 0) + return ret; + + kfree(spd->buffer); + + return ret; +} +EXPORT_SYMBOL_NS(ssp_common_buffer_postdisable, "IIO_SSP_SENSORS"); + +/** + * ssp_common_process_data() - Common process data callback for ssp sensors + * + * @indio_dev: iio device + * @buf: source buffer + * @len: sensor data length + * @timestamp: system timestamp + * + * Returns 0 or negative value in case of error + */ +int ssp_common_process_data(struct iio_dev *indio_dev, void *buf, + unsigned int len, int64_t timestamp) +{ + int64_t calculated_time; + struct ssp_sensor_data *spd = iio_priv(indio_dev); + + if (indio_dev->scan_bytes == 0) + return 0; + + /* + * it always sends full set of samples, remember about available masks + */ + memcpy(spd->buffer, buf, len); + + calculated_time = timestamp + + (int64_t)get_unaligned_le32(buf + len) * MEGA; + + return iio_push_to_buffers_with_timestamp(indio_dev, spd->buffer, + calculated_time); +} +EXPORT_SYMBOL_NS(ssp_common_process_data, "IIO_SSP_SENSORS"); + +MODULE_AUTHOR("Karol Wrona "); +MODULE_DESCRIPTION("Samsung sensorhub commons"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_SSP_SENSORS"); diff --git a/drivers/iio/common/ssp_sensors/ssp_iio_sensor.h b/drivers/iio/common/ssp_sensors/ssp_iio_sensor.h new file mode 100644 index 000000000..05fcad61c --- /dev/null +++ b/drivers/iio/common/ssp_sensors/ssp_iio_sensor.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __SSP_IIO_SENSOR_H__ +#define __SSP_IIO_SENSOR_H__ + +#define SSP_CHANNEL_AG(_type, _mod, _index) \ +{ \ + .type = _type,\ + .modified = 1,\ + .channel2 = _mod,\ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ),\ + .scan_index = _index,\ + .scan_type = {\ + .sign = 's',\ + .realbits = 16,\ + .storagebits = 16,\ + .shift = 0,\ + .endianness = IIO_LE,\ + },\ +} + +#define SSP_MS_PER_S 1000 +#define SSP_INVERTED_SCALING_FACTOR 1000000U + +#define SSP_FACTOR_WITH_MS \ + (SSP_INVERTED_SCALING_FACTOR * SSP_MS_PER_S) + +int ssp_common_buffer_postenable(struct iio_dev *indio_dev); + +int ssp_common_buffer_postdisable(struct iio_dev *indio_dev); + +int ssp_common_process_data(struct iio_dev *indio_dev, void *buf, + unsigned int len, int64_t timestamp); + +/* Converts time in ms to frequency */ +static inline void ssp_convert_to_freq(u32 time, int *integer_part, + int *fractional) +{ + if (time == 0) { + *fractional = 0; + *integer_part = 0; + return; + } + + *integer_part = SSP_FACTOR_WITH_MS / time; + *fractional = *integer_part % SSP_INVERTED_SCALING_FACTOR; + *integer_part = *integer_part / SSP_INVERTED_SCALING_FACTOR; +} + +/* Converts frequency to time in ms */ +static inline int ssp_convert_to_time(int integer_part, int fractional) +{ + u64 value; + + value = (u64)integer_part * SSP_INVERTED_SCALING_FACTOR + fractional; + if (value == 0) + return 0; + + return div64_u64((u64)SSP_FACTOR_WITH_MS, value); +} +#endif /* __SSP_IIO_SENSOR_H__ */ diff --git a/drivers/iio/common/ssp_sensors/ssp_spi.c b/drivers/iio/common/ssp_sensors/ssp_spi.c new file mode 100644 index 000000000..08ed92859 --- /dev/null +++ b/drivers/iio/common/ssp_sensors/ssp_spi.c @@ -0,0 +1,601 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2014, Samsung Electronics Co. Ltd. All Rights Reserved. + */ + +#include "ssp.h" + +#define SSP_DEV (&data->spi->dev) +#define SSP_GET_MESSAGE_TYPE(data) ((data) & (3 << SSP_RW)) + +/* + * SSP -> AP Instruction + * They tell what packet type can be expected. In the future there will + * be less of them. BYPASS means common sensor packets with accel, gyro, + * hrm etc. data. LIBRARY and META are mock-up's for now. + */ +#define SSP_MSG2AP_INST_BYPASS_DATA 0x37 +#define SSP_MSG2AP_INST_LIBRARY_DATA 0x01 +#define SSP_MSG2AP_INST_DEBUG_DATA 0x03 +#define SSP_MSG2AP_INST_BIG_DATA 0x04 +#define SSP_MSG2AP_INST_META_DATA 0x05 +#define SSP_MSG2AP_INST_TIME_SYNC 0x06 +#define SSP_MSG2AP_INST_RESET 0x07 + +#define SSP_UNIMPLEMENTED -1 + +struct ssp_msg_header { + u8 cmd; + __le16 length; + __le16 options; + __le32 data; +} __packed; + +struct ssp_msg { + u16 length; + u16 options; + struct list_head list; + struct completion *done; + struct ssp_msg_header *h; + char *buffer; +}; + +static const int ssp_offset_map[SSP_SENSOR_MAX] = { + [SSP_ACCELEROMETER_SENSOR] = SSP_ACCELEROMETER_SIZE + + SSP_TIME_SIZE, + [SSP_GYROSCOPE_SENSOR] = SSP_GYROSCOPE_SIZE + + SSP_TIME_SIZE, + [SSP_GEOMAGNETIC_UNCALIB_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_GEOMAGNETIC_RAW] = SSP_UNIMPLEMENTED, + [SSP_GEOMAGNETIC_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_PRESSURE_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_GESTURE_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_PROXIMITY_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_TEMPERATURE_HUMIDITY_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_LIGHT_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_PROXIMITY_RAW] = SSP_UNIMPLEMENTED, + [SSP_ORIENTATION_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_STEP_DETECTOR] = SSP_UNIMPLEMENTED, + [SSP_SIG_MOTION_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_GYRO_UNCALIB_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_GAME_ROTATION_VECTOR] = SSP_UNIMPLEMENTED, + [SSP_ROTATION_VECTOR] = SSP_UNIMPLEMENTED, + [SSP_STEP_COUNTER] = SSP_UNIMPLEMENTED, + [SSP_BIO_HRM_RAW] = SSP_BIO_HRM_RAW_SIZE + + SSP_TIME_SIZE, + [SSP_BIO_HRM_RAW_FAC] = SSP_BIO_HRM_RAW_FAC_SIZE + + SSP_TIME_SIZE, + [SSP_BIO_HRM_LIB] = SSP_BIO_HRM_LIB_SIZE + + SSP_TIME_SIZE, +}; + +#define SSP_HEADER_SIZE (sizeof(struct ssp_msg_header)) +#define SSP_HEADER_SIZE_ALIGNED (ALIGN(SSP_HEADER_SIZE, 4)) + +static struct ssp_msg *ssp_create_msg(u8 cmd, u16 len, u16 opt, u32 data) +{ + struct ssp_msg_header h; + struct ssp_msg *msg; + + msg = kzalloc_obj(*msg); + if (!msg) + return NULL; + + h.cmd = cmd; + h.length = cpu_to_le16(len); + h.options = cpu_to_le16(opt); + h.data = cpu_to_le32(data); + + msg->buffer = kzalloc(SSP_HEADER_SIZE_ALIGNED + len, + GFP_KERNEL | GFP_DMA); + if (!msg->buffer) { + kfree(msg); + return NULL; + } + + msg->length = len; + msg->options = opt; + + memcpy(msg->buffer, &h, SSP_HEADER_SIZE); + + return msg; +} + +/* + * It is a bit heavy to do it this way but often the function is used to compose + * the message from smaller chunks which are placed on the stack. Often the + * chunks are small so memcpy should be optimized. + */ +static inline void ssp_fill_buffer(struct ssp_msg *m, unsigned int offset, + const void *src, unsigned int len) +{ + memcpy(&m->buffer[SSP_HEADER_SIZE_ALIGNED + offset], src, len); +} + +static inline void ssp_get_buffer(struct ssp_msg *m, unsigned int offset, + void *dest, unsigned int len) +{ + memcpy(dest, &m->buffer[SSP_HEADER_SIZE_ALIGNED + offset], len); +} + +#define SSP_GET_BUFFER_AT_INDEX(m, index) \ + ((m)->buffer[SSP_HEADER_SIZE_ALIGNED + (index)]) +#define SSP_SET_BUFFER_AT_INDEX(m, index, val) \ + ((m)->buffer[SSP_HEADER_SIZE_ALIGNED + (index)] = val) + +static void ssp_clean_msg(struct ssp_msg *m) +{ + kfree(m->buffer); + kfree(m); +} + +static int ssp_print_mcu_debug(char *data_frame, int *data_index, + int received_len) +{ + int length = data_frame[(*data_index)++]; + + if (length > received_len - *data_index || length <= 0) { + ssp_dbg("[SSP]: MSG From MCU-invalid debug length(%d/%d)\n", + length, received_len); + return -EPROTO; + } + + ssp_dbg("[SSP]: MSG From MCU - %s\n", &data_frame[*data_index]); + + *data_index += length; + + return 0; +} + +/* + * It was designed that way - additional lines to some kind of handshake, + * please do not ask why - only the firmware guy can know it. + */ +static int ssp_check_lines(struct ssp_data *data, bool state) +{ + int delay_cnt = 0; + + gpiod_set_value_cansleep(data->ap_mcu_gpiod, state); + + while (gpiod_get_value_cansleep(data->mcu_ap_gpiod) != state) { + usleep_range(3000, 3500); + + if (data->shut_down || delay_cnt++ > 500) { + dev_err(SSP_DEV, "%s:timeout, hw ack wait fail %d\n", + __func__, state); + + if (!state) + gpiod_set_value_cansleep(data->ap_mcu_gpiod, 1); + + return -ETIMEDOUT; + } + } + + return 0; +} + +static int ssp_do_transfer(struct ssp_data *data, struct ssp_msg *msg, + struct completion *done, int timeout) +{ + int status; + /* + * check if this is a short one way message or the whole transfer has + * second part after an interrupt + */ + const bool use_no_irq = msg->length == 0; + + if (data->shut_down) + return -EPERM; + + msg->done = done; + + mutex_lock(&data->comm_lock); + + status = ssp_check_lines(data, false); + if (status < 0) + goto _error_locked; + + status = spi_write(data->spi, msg->buffer, SSP_HEADER_SIZE); + if (status < 0) { + gpiod_set_value_cansleep(data->ap_mcu_gpiod, 1); + dev_err(SSP_DEV, "%s spi_write fail\n", __func__); + goto _error_locked; + } + + if (!use_no_irq) { + mutex_lock(&data->pending_lock); + list_add_tail(&msg->list, &data->pending_list); + mutex_unlock(&data->pending_lock); + } + + status = ssp_check_lines(data, true); + if (status < 0) { + if (!use_no_irq) { + mutex_lock(&data->pending_lock); + list_del(&msg->list); + mutex_unlock(&data->pending_lock); + } + goto _error_locked; + } + + mutex_unlock(&data->comm_lock); + + if (!use_no_irq && done) + if (wait_for_completion_timeout(done, + msecs_to_jiffies(timeout)) == + 0) { + mutex_lock(&data->pending_lock); + list_del(&msg->list); + mutex_unlock(&data->pending_lock); + + data->timeout_cnt++; + return -ETIMEDOUT; + } + + return 0; + +_error_locked: + mutex_unlock(&data->comm_lock); + data->timeout_cnt++; + return status; +} + +static inline int ssp_spi_sync_command(struct ssp_data *data, + struct ssp_msg *msg) +{ + return ssp_do_transfer(data, msg, NULL, 0); +} + +static int ssp_spi_sync(struct ssp_data *data, struct ssp_msg *msg, + int timeout) +{ + DECLARE_COMPLETION_ONSTACK(done); + + if (WARN_ON(!msg->length)) + return -EPERM; + + return ssp_do_transfer(data, msg, &done, timeout); +} + +static int ssp_handle_big_data(struct ssp_data *data, char *dataframe, int *idx) +{ + /* mock-up, it will be changed with adding another sensor types */ + *idx += 8; + return 0; +} + +static int ssp_parse_dataframe(struct ssp_data *data, char *dataframe, int len) +{ + int idx, sd; + struct ssp_sensor_data *spd; + struct iio_dev **indio_devs = data->sensor_devs; + + for (idx = 0; idx < len;) { + switch (dataframe[idx++]) { + case SSP_MSG2AP_INST_BYPASS_DATA: + if (idx >= len) + return -EPROTO; + sd = dataframe[idx++]; + if (sd < 0 || sd >= SSP_SENSOR_MAX) { + dev_err(SSP_DEV, + "Mcu data frame1 error %d\n", sd); + return -EPROTO; + } + + if (indio_devs[sd]) { + spd = iio_priv(indio_devs[sd]); + if (spd->process_data) { + if (idx >= len) + return -EPROTO; + spd->process_data(indio_devs[sd], + &dataframe[idx], + data->timestamp); + } + } else { + dev_err(SSP_DEV, "no client for frame\n"); + } + + idx += ssp_offset_map[sd]; + break; + case SSP_MSG2AP_INST_DEBUG_DATA: + if (idx >= len) + return -EPROTO; + sd = ssp_print_mcu_debug(dataframe, &idx, len); + if (sd) { + dev_err(SSP_DEV, + "Mcu data frame3 error %d\n", sd); + return sd; + } + break; + case SSP_MSG2AP_INST_LIBRARY_DATA: + idx += len; + break; + case SSP_MSG2AP_INST_BIG_DATA: + ssp_handle_big_data(data, dataframe, &idx); + break; + case SSP_MSG2AP_INST_TIME_SYNC: + data->time_syncing = true; + break; + case SSP_MSG2AP_INST_RESET: + ssp_queue_ssp_refresh_task(data, 0); + break; + } + } + + if (data->time_syncing) + data->timestamp = ktime_get_real_ns(); + + return 0; +} + +/* threaded irq */ +int ssp_irq_msg(struct ssp_data *data) +{ + char *buffer; + u8 msg_type; + int ret; + u16 length, msg_options; + struct ssp_msg *msg = NULL, *iter, *n; + + ret = spi_read(data->spi, data->header_buffer, SSP_HEADER_BUFFER_SIZE); + if (ret < 0) { + dev_err(SSP_DEV, "header read fail\n"); + return ret; + } + + length = le16_to_cpu(data->header_buffer[1]); + msg_options = le16_to_cpu(data->header_buffer[0]); + + if (length == 0) { + dev_err(SSP_DEV, "length received from mcu is 0\n"); + return -EINVAL; + } + + msg_type = SSP_GET_MESSAGE_TYPE(msg_options); + + switch (msg_type) { + case SSP_AP2HUB_READ: + case SSP_AP2HUB_WRITE: + /* + * this is a small list, a few elements - the packets can be + * received with no order + */ + mutex_lock(&data->pending_lock); + list_for_each_entry_safe(iter, n, &data->pending_list, list) { + if (iter->options == msg_options) { + list_del(&iter->list); + msg = iter; + break; + } + } + + if (!msg) { + /* + * here can be implemented dead messages handling + * but the slave should not send such ones - it is to + * check but let's handle this + */ + buffer = kmalloc(length, GFP_KERNEL | GFP_DMA); + if (!buffer) { + ret = -ENOMEM; + goto _unlock; + } + + /* got dead packet so it is always an error */ + ret = spi_read(data->spi, buffer, length); + if (ret >= 0) + ret = -EPROTO; + + kfree(buffer); + + dev_err(SSP_DEV, "No match error %x\n", + msg_options); + + goto _unlock; + } + + if (msg_type == SSP_AP2HUB_READ) + ret = spi_read(data->spi, + &msg->buffer[SSP_HEADER_SIZE_ALIGNED], + msg->length); + + if (msg_type == SSP_AP2HUB_WRITE) { + ret = spi_write(data->spi, + &msg->buffer[SSP_HEADER_SIZE_ALIGNED], + msg->length); + if (msg_options & SSP_AP2HUB_RETURN) { + msg->options = + SSP_AP2HUB_READ | SSP_AP2HUB_RETURN; + msg->length = 1; + + list_add_tail(&msg->list, &data->pending_list); + goto _unlock; + } + } + + if (msg->done) + if (!completion_done(msg->done)) + complete(msg->done); +_unlock: + mutex_unlock(&data->pending_lock); + break; + case SSP_HUB2AP_WRITE: + buffer = kzalloc(length, GFP_KERNEL | GFP_DMA); + if (!buffer) + return -ENOMEM; + + ret = spi_read(data->spi, buffer, length); + if (ret < 0) { + dev_err(SSP_DEV, "spi read fail\n"); + kfree(buffer); + break; + } + + ret = ssp_parse_dataframe(data, buffer, length); + + kfree(buffer); + break; + + default: + dev_err(SSP_DEV, "unknown msg type\n"); + return -EPROTO; + } + + return ret; +} + +void ssp_clean_pending_list(struct ssp_data *data) +{ + struct ssp_msg *msg, *n; + + mutex_lock(&data->pending_lock); + list_for_each_entry_safe(msg, n, &data->pending_list, list) { + list_del(&msg->list); + + if (msg->done) + if (!completion_done(msg->done)) + complete(msg->done); + } + mutex_unlock(&data->pending_lock); +} + +int ssp_command(struct ssp_data *data, char command, int arg) +{ + int ret; + struct ssp_msg *msg; + + msg = ssp_create_msg(command, 0, SSP_AP2HUB_WRITE, arg); + if (!msg) + return -ENOMEM; + + ssp_dbg("%s - command 0x%x %d\n", __func__, command, arg); + + ret = ssp_spi_sync_command(data, msg); + ssp_clean_msg(msg); + + return ret; +} + +int ssp_send_instruction(struct ssp_data *data, u8 inst, u8 sensor_type, + u8 *send_buf, u8 length) +{ + int ret; + struct ssp_msg *msg; + + if (data->fw_dl_state == SSP_FW_DL_STATE_DOWNLOADING) { + dev_err(SSP_DEV, "%s - Skip Inst! DL state = %d\n", + __func__, data->fw_dl_state); + return -EBUSY; + } else if (!(data->available_sensors & BIT(sensor_type)) && + (inst <= SSP_MSG2SSP_INST_CHANGE_DELAY)) { + dev_err(SSP_DEV, "%s - Bypass Inst Skip! - %u\n", + __func__, sensor_type); + return -EIO; /* just fail */ + } + + msg = ssp_create_msg(inst, length + 2, SSP_AP2HUB_WRITE, 0); + if (!msg) + return -ENOMEM; + + ssp_fill_buffer(msg, 0, &sensor_type, 1); + ssp_fill_buffer(msg, 1, send_buf, length); + + ssp_dbg("%s - Inst = 0x%x, Sensor Type = 0x%x, data = %u\n", + __func__, inst, sensor_type, send_buf[1]); + + ret = ssp_spi_sync(data, msg, 1000); + ssp_clean_msg(msg); + + return ret; +} + +int ssp_get_chipid(struct ssp_data *data) +{ + int ret; + char buffer; + struct ssp_msg *msg; + + msg = ssp_create_msg(SSP_MSG2SSP_AP_WHOAMI, 1, SSP_AP2HUB_READ, 0); + if (!msg) + return -ENOMEM; + + ret = ssp_spi_sync(data, msg, 1000); + + buffer = SSP_GET_BUFFER_AT_INDEX(msg, 0); + + ssp_clean_msg(msg); + + return ret < 0 ? ret : buffer; +} + +int ssp_set_magnetic_matrix(struct ssp_data *data) +{ + int ret; + struct ssp_msg *msg; + + msg = ssp_create_msg(SSP_MSG2SSP_AP_SET_MAGNETIC_STATIC_MATRIX, + data->sensorhub_info->mag_length, SSP_AP2HUB_WRITE, + 0); + if (!msg) + return -ENOMEM; + + ssp_fill_buffer(msg, 0, data->sensorhub_info->mag_table, + data->sensorhub_info->mag_length); + + ret = ssp_spi_sync(data, msg, 1000); + ssp_clean_msg(msg); + + return ret; +} + +unsigned int ssp_get_sensor_scanning_info(struct ssp_data *data) +{ + int ret; + __le32 result; + u32 cpu_result = 0; + + struct ssp_msg *msg = ssp_create_msg(SSP_MSG2SSP_AP_SENSOR_SCANNING, 4, + SSP_AP2HUB_READ, 0); + if (!msg) + return 0; + + ret = ssp_spi_sync(data, msg, 1000); + if (ret < 0) { + dev_err(SSP_DEV, "%s - spi read fail %d\n", __func__, ret); + goto _exit; + } + + ssp_get_buffer(msg, 0, &result, 4); + cpu_result = le32_to_cpu(result); + + dev_info(SSP_DEV, "%s state: 0x%08x\n", __func__, cpu_result); + +_exit: + ssp_clean_msg(msg); + return cpu_result; +} + +unsigned int ssp_get_firmware_rev(struct ssp_data *data) +{ + int ret; + __le32 result; + + struct ssp_msg *msg = ssp_create_msg(SSP_MSG2SSP_AP_FIRMWARE_REV, 4, + SSP_AP2HUB_READ, 0); + if (!msg) + return SSP_INVALID_REVISION; + + ret = ssp_spi_sync(data, msg, 1000); + if (ret < 0) { + dev_err(SSP_DEV, "%s - transfer fail %d\n", __func__, ret); + ret = SSP_INVALID_REVISION; + goto _exit; + } + + ssp_get_buffer(msg, 0, &result, 4); + ret = le32_to_cpu(result); + +_exit: + ssp_clean_msg(msg); + return ret; +} diff --git a/drivers/iio/common/st_sensors/Kconfig b/drivers/iio/common/st_sensors/Kconfig new file mode 100644 index 000000000..eda8f347f --- /dev/null +++ b/drivers/iio/common/st_sensors/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# STMicroelectronics sensors common library +# + +config IIO_ST_SENSORS_I2C + tristate + select REGMAP_I2C + +config IIO_ST_SENSORS_SPI + tristate + select REGMAP_SPI + +config IIO_ST_SENSORS_CORE + tristate diff --git a/drivers/iio/common/st_sensors/Makefile b/drivers/iio/common/st_sensors/Makefile new file mode 100644 index 000000000..f7fb3b79b --- /dev/null +++ b/drivers/iio/common/st_sensors/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the STMicroelectronics sensor common modules. +# + +obj-$(CONFIG_IIO_ST_SENSORS_I2C) += st_sensors_i2c.o +obj-$(CONFIG_IIO_ST_SENSORS_SPI) += st_sensors_spi.o +obj-$(CONFIG_IIO_ST_SENSORS_CORE) += st_sensors.o +st_sensors-y := st_sensors_core.o +st_sensors-$(CONFIG_IIO_BUFFER) += st_sensors_buffer.o +st_sensors-$(CONFIG_IIO_TRIGGER) += st_sensors_trigger.o diff --git a/drivers/iio/common/st_sensors/st_sensors_buffer.c b/drivers/iio/common/st_sensors/st_sensors_buffer.c new file mode 100644 index 000000000..57f087c22 --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_buffer.c @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics sensors buffer library driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +static int st_sensors_get_buffer_element(struct iio_dev *indio_dev, u8 *buf) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + unsigned int num_data_channels = sdata->num_data_channels; + int i; + + for_each_set_bit(i, indio_dev->active_scan_mask, num_data_channels) { + const struct iio_chan_spec *channel = &indio_dev->channels[i]; + unsigned int bytes_to_read = + DIV_ROUND_UP(channel->scan_type.realbits + + channel->scan_type.shift, 8); + unsigned int storage_bytes = + channel->scan_type.storagebits >> 3; + + buf = PTR_ALIGN(buf, storage_bytes); + if (regmap_bulk_read(sdata->regmap, channel->address, + buf, bytes_to_read) < 0) + return -EIO; + + /* Advance the buffer pointer */ + buf += storage_bytes; + } + + return 0; +} + +irqreturn_t st_sensors_trigger_handler(int irq, void *p) +{ + int len; + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct st_sensor_data *sdata = iio_priv(indio_dev); + s64 timestamp; + + /* + * If we do timestamping here, do it before reading the values, because + * once we've read the values, new interrupts can occur (when using + * the hardware trigger) and the hw_timestamp may get updated. + * By storing it in a local variable first, we are safe. + */ + if (iio_trigger_using_own(indio_dev)) + timestamp = sdata->hw_timestamp; + else + timestamp = iio_get_time_ns(indio_dev); + + len = st_sensors_get_buffer_element(indio_dev, sdata->buffer_data); + if (len < 0) + goto st_sensors_get_buffer_element_error; + + iio_push_to_buffers_with_timestamp(indio_dev, sdata->buffer_data, + timestamp); + +st_sensors_get_buffer_element_error: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL_NS(st_sensors_trigger_handler, "IIO_ST_SENSORS"); diff --git a/drivers/iio/common/st_sensors/st_sensors_core.c b/drivers/iio/common/st_sensors/st_sensors_core.c new file mode 100644 index 000000000..76f91696f --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_core.c @@ -0,0 +1,666 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics sensors core library driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "st_sensors_core.h" + +int st_sensors_write_data_with_mask(struct iio_dev *indio_dev, + u8 reg_addr, u8 mask, u8 data) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + + return regmap_update_bits(sdata->regmap, + reg_addr, mask, data << __ffs(mask)); +} + +int st_sensors_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned reg, unsigned writeval, + unsigned *readval) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + int err; + + if (!readval) + return regmap_write(sdata->regmap, reg, writeval); + + err = regmap_read(sdata->regmap, reg, readval); + if (err < 0) + return err; + + return 0; +} +EXPORT_SYMBOL_NS(st_sensors_debugfs_reg_access, "IIO_ST_SENSORS"); + +static int st_sensors_match_odr(struct st_sensor_settings *sensor_settings, + unsigned int odr, struct st_sensor_odr_avl *odr_out) +{ + int i, ret = -EINVAL; + + for (i = 0; i < ST_SENSORS_ODR_LIST_MAX; i++) { + if (sensor_settings->odr.odr_avl[i].hz == 0) + goto st_sensors_match_odr_error; + + if (sensor_settings->odr.odr_avl[i].hz == odr) { + odr_out->hz = sensor_settings->odr.odr_avl[i].hz; + odr_out->value = sensor_settings->odr.odr_avl[i].value; + ret = 0; + break; + } + } + +st_sensors_match_odr_error: + return ret; +} + +int st_sensors_set_odr(struct iio_dev *indio_dev, unsigned int odr) +{ + int err = 0; + struct st_sensor_odr_avl odr_out = {0, 0}; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + mutex_lock(&sdata->odr_lock); + + if (!sdata->sensor_settings->odr.mask) + goto unlock_mutex; + + err = st_sensors_match_odr(sdata->sensor_settings, odr, &odr_out); + if (err < 0) + goto unlock_mutex; + + if ((sdata->sensor_settings->odr.addr == + sdata->sensor_settings->pw.addr) && + (sdata->sensor_settings->odr.mask == + sdata->sensor_settings->pw.mask)) { + if (sdata->enabled == true) { + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->odr.addr, + sdata->sensor_settings->odr.mask, + odr_out.value); + } else { + err = 0; + } + } else { + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->odr.addr, + sdata->sensor_settings->odr.mask, + odr_out.value); + } + if (err >= 0) + sdata->odr = odr_out.hz; + +unlock_mutex: + mutex_unlock(&sdata->odr_lock); + + return err; +} +EXPORT_SYMBOL_NS(st_sensors_set_odr, "IIO_ST_SENSORS"); + +static int st_sensors_match_fs(struct st_sensor_settings *sensor_settings, + unsigned int fs, int *index_fs_avl) +{ + int i, ret = -EINVAL; + + for (i = 0; i < ST_SENSORS_FULLSCALE_AVL_MAX; i++) { + if (sensor_settings->fs.fs_avl[i].num == 0) + return ret; + + if (sensor_settings->fs.fs_avl[i].num == fs) { + *index_fs_avl = i; + ret = 0; + break; + } + } + + return ret; +} + +static int st_sensors_set_fullscale(struct iio_dev *indio_dev, unsigned int fs) +{ + int err, i = 0; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + if (sdata->sensor_settings->fs.addr == 0) + return 0; + + err = st_sensors_match_fs(sdata->sensor_settings, fs, &i); + if (err < 0) + goto st_accel_set_fullscale_error; + + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->fs.addr, + sdata->sensor_settings->fs.mask, + sdata->sensor_settings->fs.fs_avl[i].value); + if (err < 0) + goto st_accel_set_fullscale_error; + + sdata->current_fullscale = &sdata->sensor_settings->fs.fs_avl[i]; + return err; + +st_accel_set_fullscale_error: + dev_err(indio_dev->dev.parent, "failed to set new fullscale.\n"); + return err; +} + +int st_sensors_set_enable(struct iio_dev *indio_dev, bool enable) +{ + u8 tmp_value; + int err = -EINVAL; + bool found = false; + struct st_sensor_odr_avl odr_out = {0, 0}; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + if (enable) { + tmp_value = sdata->sensor_settings->pw.value_on; + if ((sdata->sensor_settings->odr.addr == + sdata->sensor_settings->pw.addr) && + (sdata->sensor_settings->odr.mask == + sdata->sensor_settings->pw.mask)) { + err = st_sensors_match_odr(sdata->sensor_settings, + sdata->odr, &odr_out); + if (err < 0) + goto set_enable_error; + tmp_value = odr_out.value; + found = true; + } + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->pw.addr, + sdata->sensor_settings->pw.mask, tmp_value); + if (err < 0) + goto set_enable_error; + + sdata->enabled = true; + + if (found) + sdata->odr = odr_out.hz; + } else { + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->pw.addr, + sdata->sensor_settings->pw.mask, + sdata->sensor_settings->pw.value_off); + if (err < 0) + goto set_enable_error; + + sdata->enabled = false; + } + +set_enable_error: + return err; +} +EXPORT_SYMBOL_NS(st_sensors_set_enable, "IIO_ST_SENSORS"); + +int st_sensors_set_axis_enable(struct iio_dev *indio_dev, u8 axis_enable) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + int err = 0; + + if (sdata->sensor_settings->enable_axis.addr) + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->enable_axis.addr, + sdata->sensor_settings->enable_axis.mask, + axis_enable); + return err; +} +EXPORT_SYMBOL_NS(st_sensors_set_axis_enable, "IIO_ST_SENSORS"); + + +int st_sensors_power_enable(struct iio_dev *indio_dev) +{ + static const char * const regulator_names[] = { "vdd", "vddio" }; + struct device *parent = indio_dev->dev.parent; + int err; + + /* Regulators not mandatory, but if requested we should enable them. */ + err = devm_regulator_bulk_get_enable(parent, + ARRAY_SIZE(regulator_names), + regulator_names); + if (err) + return dev_err_probe(parent, err, "unable to enable supplies\n"); + + return 0; +} +EXPORT_SYMBOL_NS(st_sensors_power_enable, "IIO_ST_SENSORS"); + +static int st_sensors_set_drdy_int_pin(struct iio_dev *indio_dev, + struct st_sensors_platform_data *pdata) +{ + struct device *parent = indio_dev->dev.parent; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + /* Sensor does not support interrupts */ + if (!sdata->sensor_settings->drdy_irq.int1.addr && + !sdata->sensor_settings->drdy_irq.int2.addr) { + if (pdata->drdy_int_pin) + dev_info(parent, + "DRDY on pin INT%d specified, but sensor does not support interrupts\n", + pdata->drdy_int_pin); + return 0; + } + + switch (pdata->drdy_int_pin) { + case 1: + if (!sdata->sensor_settings->drdy_irq.int1.mask) { + dev_err(parent, "DRDY on INT1 not available.\n"); + return -EINVAL; + } + sdata->drdy_int_pin = 1; + break; + case 2: + if (!sdata->sensor_settings->drdy_irq.int2.mask) { + dev_err(parent, "DRDY on INT2 not available.\n"); + return -EINVAL; + } + sdata->drdy_int_pin = 2; + break; + default: + dev_err(parent, "DRDY on pdata not valid.\n"); + return -EINVAL; + } + + if (pdata->open_drain) { + if (!sdata->sensor_settings->drdy_irq.int1.addr_od && + !sdata->sensor_settings->drdy_irq.int2.addr_od) + dev_err(parent, + "open drain requested but unsupported.\n"); + else + sdata->int_pin_open_drain = true; + } + + return 0; +} + +static struct st_sensors_platform_data *st_sensors_dev_probe(struct device *dev, + struct st_sensors_platform_data *defdata) +{ + struct st_sensors_platform_data *pdata; + u32 val; + + if (!dev_fwnode(dev)) + return NULL; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + if (!device_property_read_u32(dev, "st,drdy-int-pin", &val) && (val <= 2)) + pdata->drdy_int_pin = (u8) val; + else + pdata->drdy_int_pin = defdata ? defdata->drdy_int_pin : 0; + + pdata->open_drain = device_property_read_bool(dev, "drive-open-drain"); + + return pdata; +} + +/** + * st_sensors_dev_name_probe() - device probe for ST sensor name + * @dev: driver model representation of the device. + * @name: device name buffer reference. + * @len: device name buffer length. + * + * In effect this function matches an ID to an internal kernel + * name for a certain sensor device, so that the rest of the autodetection can + * rely on that name from this point on. I2C/SPI devices will be renamed + * to match the internal kernel convention. + */ +void st_sensors_dev_name_probe(struct device *dev, char *name, int len) +{ + const void *match; + + match = device_get_match_data(dev); + if (!match) + return; + + /* The name from the match takes precedence if present */ + strscpy(name, match, len); +} +EXPORT_SYMBOL_NS(st_sensors_dev_name_probe, "IIO_ST_SENSORS"); + +int st_sensors_init_sensor(struct iio_dev *indio_dev, + struct st_sensors_platform_data *pdata) +{ + struct device *parent = indio_dev->dev.parent; + struct st_sensor_data *sdata = iio_priv(indio_dev); + struct st_sensors_platform_data *of_pdata; + int err = 0; + + mutex_init(&sdata->odr_lock); + + /* If OF/DT pdata exists, it will take precedence of anything else */ + of_pdata = st_sensors_dev_probe(parent, pdata); + if (IS_ERR(of_pdata)) + return PTR_ERR(of_pdata); + if (of_pdata) + pdata = of_pdata; + + if (pdata) { + err = st_sensors_set_drdy_int_pin(indio_dev, pdata); + if (err < 0) + return err; + } + + err = st_sensors_set_enable(indio_dev, false); + if (err < 0) + return err; + + /* Disable DRDY, this might be still be enabled after reboot. */ + err = st_sensors_set_dataready_irq(indio_dev, false); + if (err < 0) + return err; + + if (sdata->current_fullscale) { + err = st_sensors_set_fullscale(indio_dev, + sdata->current_fullscale->num); + if (err < 0) + return err; + } else + dev_info(parent, "Full-scale not possible\n"); + + err = st_sensors_set_odr(indio_dev, sdata->odr); + if (err < 0) + return err; + + /* set BDU */ + if (sdata->sensor_settings->bdu.addr) { + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->bdu.addr, + sdata->sensor_settings->bdu.mask, true); + if (err < 0) + return err; + } + + /* set DAS */ + if (sdata->sensor_settings->das.addr) { + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->das.addr, + sdata->sensor_settings->das.mask, 1); + if (err < 0) + return err; + } + + if (sdata->int_pin_open_drain) { + u8 addr, mask; + + if (sdata->drdy_int_pin == 1) { + addr = sdata->sensor_settings->drdy_irq.int1.addr_od; + mask = sdata->sensor_settings->drdy_irq.int1.mask_od; + } else { + addr = sdata->sensor_settings->drdy_irq.int2.addr_od; + mask = sdata->sensor_settings->drdy_irq.int2.mask_od; + } + + dev_info(parent, + "set interrupt line to open drain mode on pin %d\n", + sdata->drdy_int_pin); + err = st_sensors_write_data_with_mask(indio_dev, addr, + mask, 1); + if (err < 0) + return err; + } + + err = st_sensors_set_axis_enable(indio_dev, ST_SENSORS_ENABLE_ALL_AXIS); + + return err; +} +EXPORT_SYMBOL_NS(st_sensors_init_sensor, "IIO_ST_SENSORS"); + +int st_sensors_set_dataready_irq(struct iio_dev *indio_dev, bool enable) +{ + int err; + u8 drdy_addr, drdy_mask; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + if (!sdata->sensor_settings->drdy_irq.int1.addr && + !sdata->sensor_settings->drdy_irq.int2.addr) { + /* + * there are some devices (e.g. LIS3MDL) where drdy line is + * routed to a given pin and it is not possible to select a + * different one. Take into account irq status register + * to understand if irq trigger can be properly supported + */ + if (sdata->sensor_settings->drdy_irq.stat_drdy.addr) + sdata->hw_irq_trigger = enable; + return 0; + } + + /* Enable/Disable the interrupt generator 1. */ + if (sdata->sensor_settings->drdy_irq.ig1.en_addr > 0) { + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->drdy_irq.ig1.en_addr, + sdata->sensor_settings->drdy_irq.ig1.en_mask, + (int)enable); + if (err < 0) + goto st_accel_set_dataready_irq_error; + } + + if (sdata->drdy_int_pin == 1) { + drdy_addr = sdata->sensor_settings->drdy_irq.int1.addr; + drdy_mask = sdata->sensor_settings->drdy_irq.int1.mask; + } else { + drdy_addr = sdata->sensor_settings->drdy_irq.int2.addr; + drdy_mask = sdata->sensor_settings->drdy_irq.int2.mask; + } + + /* Flag to the poll function that the hardware trigger is in use */ + sdata->hw_irq_trigger = enable; + + /* Enable/Disable the interrupt generator for data ready. */ + err = st_sensors_write_data_with_mask(indio_dev, drdy_addr, + drdy_mask, (int)enable); + +st_accel_set_dataready_irq_error: + return err; +} +EXPORT_SYMBOL_NS(st_sensors_set_dataready_irq, "IIO_ST_SENSORS"); + +int st_sensors_set_fullscale_by_gain(struct iio_dev *indio_dev, int scale) +{ + int err = -EINVAL, i; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + for (i = 0; i < ST_SENSORS_FULLSCALE_AVL_MAX; i++) { + if ((sdata->sensor_settings->fs.fs_avl[i].gain == scale) && + (sdata->sensor_settings->fs.fs_avl[i].gain != 0)) { + err = 0; + break; + } + } + if (err < 0) + goto st_sensors_match_scale_error; + + err = st_sensors_set_fullscale(indio_dev, + sdata->sensor_settings->fs.fs_avl[i].num); + +st_sensors_match_scale_error: + return err; +} +EXPORT_SYMBOL_NS(st_sensors_set_fullscale_by_gain, "IIO_ST_SENSORS"); + +static int st_sensors_read_axis_data(struct iio_dev *indio_dev, + struct iio_chan_spec const *ch, int *data) +{ + int err; + u8 *outdata; + struct st_sensor_data *sdata = iio_priv(indio_dev); + unsigned int byte_for_channel; + u32 tmp; + + byte_for_channel = DIV_ROUND_UP(ch->scan_type.realbits + + ch->scan_type.shift, 8); + outdata = sdata->buffer_data; + + err = regmap_bulk_read(sdata->regmap, ch->address, + outdata, byte_for_channel); + if (err < 0) + return err; + + if (byte_for_channel == 1) { + tmp = *outdata; + } else if (byte_for_channel == 2) { + if (ch->scan_type.endianness == IIO_BE) + tmp = get_unaligned_be16(outdata); + else + tmp = get_unaligned_le16(outdata); + } else if (byte_for_channel == 3) { + if (ch->scan_type.endianness == IIO_BE) + tmp = get_unaligned_be24(outdata); + else + tmp = get_unaligned_le24(outdata); + } else { + return -EINVAL; + } + *data = sign_extend32(tmp, BYTES_TO_BITS(byte_for_channel) - 1); + + return 0; +} + +int st_sensors_read_info_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *ch, int *val) +{ + int err; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + mutex_lock(&sdata->odr_lock); + + err = st_sensors_set_enable(indio_dev, true); + if (err < 0) + goto out; + + msleep((sdata->sensor_settings->bootime * 1000) / sdata->odr); + err = st_sensors_read_axis_data(indio_dev, ch, val); + if (err < 0) + goto out; + + *val = *val >> ch->scan_type.shift; + + err = st_sensors_set_enable(indio_dev, false); + +out: + mutex_unlock(&sdata->odr_lock); + iio_device_release_direct(indio_dev); + + return err; +} +EXPORT_SYMBOL_NS(st_sensors_read_info_raw, "IIO_ST_SENSORS"); + +/* + * st_sensors_get_settings_index() - get index of the sensor settings for a + * specific device from list of settings + * @name: device name buffer reference. + * @list: sensor settings list. + * @list_length: length of sensor settings list. + * + * Return: non negative number on success (valid index), + * negative error code otherwise. + */ +int st_sensors_get_settings_index(const char *name, + const struct st_sensor_settings *list, + const int list_length) +{ + int i, n; + + for (i = 0; i < list_length; i++) { + for (n = 0; n < ST_SENSORS_MAX_4WAI; n++) { + if (strcmp(name, list[i].sensors_supported[n]) == 0) + return i; + } + } + + return -ENODEV; +} +EXPORT_SYMBOL_NS(st_sensors_get_settings_index, "IIO_ST_SENSORS"); + +/* + * st_sensors_verify_id() - verify sensor ID (WhoAmI) is matching with the + * expected value + * @indio_dev: IIO device reference. + * + * Return: 0 on success (valid sensor ID), else a negative error code. + */ +int st_sensors_verify_id(struct iio_dev *indio_dev) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + struct device *parent = indio_dev->dev.parent; + int wai, err; + + if (sdata->sensor_settings->wai_addr) { + err = regmap_read(sdata->regmap, + sdata->sensor_settings->wai_addr, &wai); + if (err < 0) { + return dev_err_probe(parent, err, + "failed to read Who-Am-I register.\n"); + } + + if (sdata->sensor_settings->wai != wai) { + dev_warn(parent, "%s: WhoAmI mismatch (0x%x).\n", + indio_dev->name, wai); + } + } + + return 0; +} +EXPORT_SYMBOL_NS(st_sensors_verify_id, "IIO_ST_SENSORS"); + +ssize_t st_sensors_sysfs_sampling_frequency_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, len = 0; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct st_sensor_data *sdata = iio_priv(indio_dev); + + for (i = 0; i < ST_SENSORS_ODR_LIST_MAX; i++) { + if (sdata->sensor_settings->odr.odr_avl[i].hz == 0) + break; + + len += scnprintf(buf + len, PAGE_SIZE - len, "%d ", + sdata->sensor_settings->odr.odr_avl[i].hz); + } + buf[len - 1] = '\n'; + + return len; +} +EXPORT_SYMBOL_NS(st_sensors_sysfs_sampling_frequency_avail, "IIO_ST_SENSORS"); + +ssize_t st_sensors_sysfs_scale_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, len = 0, q, r; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct st_sensor_data *sdata = iio_priv(indio_dev); + + for (i = 0; i < ST_SENSORS_FULLSCALE_AVL_MAX; i++) { + if (sdata->sensor_settings->fs.fs_avl[i].num == 0) + break; + + q = sdata->sensor_settings->fs.fs_avl[i].gain / 1000000; + r = sdata->sensor_settings->fs.fs_avl[i].gain % 1000000; + + len += scnprintf(buf + len, PAGE_SIZE - len, "%u.%06u ", q, r); + } + buf[len - 1] = '\n'; + + return len; +} +EXPORT_SYMBOL_NS(st_sensors_sysfs_scale_avail, "IIO_ST_SENSORS"); + +MODULE_AUTHOR("Denis Ciocca "); +MODULE_DESCRIPTION("STMicroelectronics ST-sensors core"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/st_sensors/st_sensors_core.h b/drivers/iio/common/st_sensors/st_sensors_core.h new file mode 100644 index 000000000..09f3e602a --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_core.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Local functions in the ST Sensors core + */ +#ifndef __ST_SENSORS_CORE_H +#define __ST_SENSORS_CORE_H +struct iio_dev; +int st_sensors_write_data_with_mask(struct iio_dev *indio_dev, + u8 reg_addr, u8 mask, u8 data); +#endif diff --git a/drivers/iio/common/st_sensors/st_sensors_i2c.c b/drivers/iio/common/st_sensors/st_sensors_i2c.c new file mode 100644 index 000000000..7156302fe --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_i2c.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics sensors i2c library driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include +#include + +#include + +#define ST_SENSORS_I2C_MULTIREAD 0x80 + +static const struct regmap_config st_sensors_i2c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static const struct regmap_config st_sensors_i2c_regmap_multiread_bit_config = { + .reg_bits = 8, + .val_bits = 8, + .read_flag_mask = ST_SENSORS_I2C_MULTIREAD, +}; + +/* + * st_sensors_i2c_configure() - configure I2C interface + * @indio_dev: IIO device reference. + * @client: i2c client reference. + * + * Return: 0 on success, else a negative error code. + */ +int st_sensors_i2c_configure(struct iio_dev *indio_dev, + struct i2c_client *client) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + const struct regmap_config *config; + + if (sdata->sensor_settings->multi_read_bit) + config = &st_sensors_i2c_regmap_multiread_bit_config; + else + config = &st_sensors_i2c_regmap_config; + + sdata->regmap = devm_regmap_init_i2c(client, config); + if (IS_ERR(sdata->regmap)) { + dev_err(&client->dev, "Failed to register i2c regmap (%ld)\n", + PTR_ERR(sdata->regmap)); + return PTR_ERR(sdata->regmap); + } + + i2c_set_clientdata(client, indio_dev); + + indio_dev->name = client->name; + + sdata->irq = client->irq; + + return 0; +} +EXPORT_SYMBOL_NS(st_sensors_i2c_configure, "IIO_ST_SENSORS"); + +MODULE_AUTHOR("Denis Ciocca "); +MODULE_DESCRIPTION("STMicroelectronics ST-sensors i2c driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/st_sensors/st_sensors_spi.c b/drivers/iio/common/st_sensors/st_sensors_spi.c new file mode 100644 index 000000000..0da270139 --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_spi.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics sensors spi library driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include +#include +#include + +#include + +#define ST_SENSORS_SPI_MULTIREAD 0xc0 + +static const struct regmap_config st_sensors_spi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static const struct regmap_config st_sensors_spi_regmap_multiread_bit_config = { + .reg_bits = 8, + .val_bits = 8, + .read_flag_mask = ST_SENSORS_SPI_MULTIREAD, +}; + +/* + * st_sensors_is_spi_3_wire() - check if SPI 3-wire mode has been selected + * @spi: spi device reference. + * + * Return: true if SPI 3-wire mode is selected, false otherwise. + */ +static bool st_sensors_is_spi_3_wire(struct spi_device *spi) +{ + struct st_sensors_platform_data *pdata; + struct device *dev = &spi->dev; + + if (device_property_read_bool(dev, "spi-3wire")) + return true; + + pdata = dev_get_platdata(dev); + if (pdata && pdata->spi_3wire) + return true; + + return false; +} + +/* + * st_sensors_configure_spi_3_wire() - configure SPI 3-wire if needed + * @spi: spi device reference. + * @settings: sensor specific settings reference. + * + * Return: 0 on success, else a negative error code. + */ +static int st_sensors_configure_spi_3_wire(struct spi_device *spi, + struct st_sensor_settings *settings) +{ + if (settings->sim.addr) { + u8 buffer[] = { + settings->sim.addr, + settings->sim.value + }; + + return spi_write(spi, buffer, 2); + } + + return 0; +} + +/* + * st_sensors_spi_configure() - configure SPI interface + * @indio_dev: IIO device reference. + * @spi: spi device reference. + * + * Return: 0 on success, else a negative error code. + */ +int st_sensors_spi_configure(struct iio_dev *indio_dev, + struct spi_device *spi) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + const struct regmap_config *config; + int err; + + if (st_sensors_is_spi_3_wire(spi)) { + err = st_sensors_configure_spi_3_wire(spi, + sdata->sensor_settings); + if (err < 0) + return err; + } + + if (sdata->sensor_settings->multi_read_bit) + config = &st_sensors_spi_regmap_multiread_bit_config; + else + config = &st_sensors_spi_regmap_config; + + sdata->regmap = devm_regmap_init_spi(spi, config); + if (IS_ERR(sdata->regmap)) { + dev_err(&spi->dev, "Failed to register spi regmap (%ld)\n", + PTR_ERR(sdata->regmap)); + return PTR_ERR(sdata->regmap); + } + + spi_set_drvdata(spi, indio_dev); + + indio_dev->name = spi->modalias; + + sdata->irq = spi->irq; + + return 0; +} +EXPORT_SYMBOL_NS(st_sensors_spi_configure, "IIO_ST_SENSORS"); + +MODULE_AUTHOR("Denis Ciocca "); +MODULE_DESCRIPTION("STMicroelectronics ST-sensors spi driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/st_sensors/st_sensors_trigger.c b/drivers/iio/common/st_sensors/st_sensors_trigger.c new file mode 100644 index 000000000..7b3e9732c --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_trigger.c @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics sensors trigger library driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include +#include +#include +#include "st_sensors_core.h" + +/** + * st_sensors_new_samples_available() - check if more samples came in + * @indio_dev: IIO device reference. + * @sdata: Sensor data. + * + * returns: + * false - no new samples available or read error + * true - new samples available + */ +static bool st_sensors_new_samples_available(struct iio_dev *indio_dev, + struct st_sensor_data *sdata) +{ + int ret, status; + + /* How would I know if I can't check it? */ + if (!sdata->sensor_settings->drdy_irq.stat_drdy.addr) + return true; + + /* No scan mask, no interrupt */ + if (!indio_dev->active_scan_mask) + return false; + + ret = regmap_read(sdata->regmap, + sdata->sensor_settings->drdy_irq.stat_drdy.addr, + &status); + if (ret < 0) { + dev_err(indio_dev->dev.parent, + "error checking samples available\n"); + return false; + } + + return !!(status & sdata->sensor_settings->drdy_irq.stat_drdy.mask); +} + +/** + * st_sensors_irq_handler() - top half of the IRQ-based triggers + * @irq: irq number + * @p: private handler data + */ +static irqreturn_t st_sensors_irq_handler(int irq, void *p) +{ + struct iio_trigger *trig = p; + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct st_sensor_data *sdata = iio_priv(indio_dev); + + /* Get the time stamp as close in time as possible */ + sdata->hw_timestamp = iio_get_time_ns(indio_dev); + return IRQ_WAKE_THREAD; +} + +/** + * st_sensors_irq_thread() - bottom half of the IRQ-based triggers + * @irq: irq number + * @p: private handler data + */ +static irqreturn_t st_sensors_irq_thread(int irq, void *p) +{ + struct iio_trigger *trig = p; + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct st_sensor_data *sdata = iio_priv(indio_dev); + + /* + * If this trigger is backed by a hardware interrupt and we have a + * status register, check if this IRQ came from us. Notice that + * we will process also if st_sensors_new_samples_available() + * returns negative: if we can't check status, then poll + * unconditionally. + */ + if (sdata->hw_irq_trigger && + st_sensors_new_samples_available(indio_dev, sdata)) { + iio_trigger_poll_nested(p); + } else { + dev_dbg(indio_dev->dev.parent, "spurious IRQ\n"); + return IRQ_NONE; + } + + /* + * If we have proper level IRQs the handler will be re-entered if + * the line is still active, so return here and come back in through + * the top half if need be. + */ + if (!sdata->edge_irq) + return IRQ_HANDLED; + + /* + * If we are using edge IRQs, new samples arrived while processing + * the IRQ and those may be missed unless we pick them here, so poll + * again. If the sensor delivery frequency is very high, this thread + * turns into a polled loop handler. + */ + while (sdata->hw_irq_trigger && + st_sensors_new_samples_available(indio_dev, sdata)) { + dev_dbg(indio_dev->dev.parent, + "more samples came in during polling\n"); + sdata->hw_timestamp = iio_get_time_ns(indio_dev); + iio_trigger_poll_nested(p); + } + + return IRQ_HANDLED; +} + +int st_sensors_allocate_trigger(struct iio_dev *indio_dev, + const struct iio_trigger_ops *trigger_ops) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + struct device *parent = indio_dev->dev.parent; + unsigned long irq_trig; + int err; + + sdata->trig = devm_iio_trigger_alloc(parent, "%s-trigger", + indio_dev->name); + if (sdata->trig == NULL) { + dev_err(parent, "failed to allocate iio trigger.\n"); + return -ENOMEM; + } + + iio_trigger_set_drvdata(sdata->trig, indio_dev); + sdata->trig->ops = trigger_ops; + + /* + * If the IRQ is triggered on falling edge, we need to mark the + * interrupt as active low, if the hardware supports this. + */ + irq_trig = irq_get_trigger_type(sdata->irq); + switch(irq_trig) { + case IRQF_TRIGGER_FALLING: + case IRQF_TRIGGER_LOW: + if (!sdata->sensor_settings->drdy_irq.addr_ihl) { + dev_err(parent, + "falling/low specified for IRQ but hardware supports only rising/high: will request rising/high\n"); + if (irq_trig == IRQF_TRIGGER_FALLING) + irq_trig = IRQF_TRIGGER_RISING; + if (irq_trig == IRQF_TRIGGER_LOW) + irq_trig = IRQF_TRIGGER_HIGH; + } else { + /* Set up INT active low i.e. falling edge */ + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->drdy_irq.addr_ihl, + sdata->sensor_settings->drdy_irq.mask_ihl, 1); + if (err < 0) + return err; + dev_info(parent, + "interrupts on the falling edge or active low level\n"); + } + break; + case IRQF_TRIGGER_RISING: + dev_info(parent, "interrupts on the rising edge\n"); + break; + case IRQF_TRIGGER_HIGH: + dev_info(parent, "interrupts active high level\n"); + break; + default: + /* This is the most preferred mode, if possible */ + dev_err(parent, + "unsupported IRQ trigger specified (%lx), enforce rising edge\n", irq_trig); + irq_trig = IRQF_TRIGGER_RISING; + } + + /* Tell the interrupt handler that we're dealing with edges */ + if (irq_trig == IRQF_TRIGGER_FALLING || + irq_trig == IRQF_TRIGGER_RISING) { + if (!sdata->sensor_settings->drdy_irq.stat_drdy.addr) { + dev_err(parent, + "edge IRQ not supported w/o stat register.\n"); + return -EOPNOTSUPP; + } + sdata->edge_irq = true; + } else { + /* + * If we're not using edges (i.e. level interrupts) we + * just mask off the IRQ, handle one interrupt, then + * if the line is still low, we return to the + * interrupt handler top half again and start over. + */ + irq_trig |= IRQF_ONESHOT; + } + + /* + * If the interrupt pin is Open Drain, by definition this + * means that the interrupt line may be shared with other + * peripherals. But to do this we also need to have a status + * register and mask to figure out if this sensor was firing + * the IRQ or not, so we can tell the interrupt handle that + * it was "our" interrupt. + */ + if (sdata->int_pin_open_drain && + sdata->sensor_settings->drdy_irq.stat_drdy.addr) + irq_trig |= IRQF_SHARED; + + err = devm_request_threaded_irq(parent, + sdata->irq, + st_sensors_irq_handler, + st_sensors_irq_thread, + irq_trig, + sdata->trig->name, + sdata->trig); + if (err) + return err; + + err = devm_iio_trigger_register(parent, sdata->trig); + if (err < 0) { + dev_err(parent, "failed to register iio trigger.\n"); + return err; + } + indio_dev->trig = iio_trigger_get(sdata->trig); + + return 0; +} +EXPORT_SYMBOL_NS(st_sensors_allocate_trigger, "IIO_ST_SENSORS"); + +int st_sensors_validate_device(struct iio_trigger *trig, + struct iio_dev *indio_dev) +{ + struct iio_dev *indio = iio_trigger_get_drvdata(trig); + + if (indio != indio_dev) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL_NS(st_sensors_validate_device, "IIO_ST_SENSORS"); diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig new file mode 100644 index 000000000..3b3c77d8b --- /dev/null +++ b/drivers/iio/dac/Kconfig @@ -0,0 +1,700 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# DAC drivers +# +# When adding new entries keep the list in alphabetical order + +menu "Digital to analog converters" + +config AD3530R + tristate "Analog Devices AD3530R and Similar DACs driver" + depends on SPI + select REGMAP_SPI + help + Say yes here to build support for the following Analog Devices + Digital to Analog Converters: + - AD3530/AD3530R (8-channel) + - AD3531/AD3531R (4-channel) + - AD3532/AD3532R (16-channel) + + To compile this driver as a module, choose M here: the + module will be called ad3530r. + +config AD3552R_HS + tristate "Analog Devices AD3552R DAC High Speed driver" + select AD3552R_LIB + select IIO_BACKEND + help + Say yes here to build support for Analog Devices AD3552R + Digital to Analog Converter High Speed driver. + + The driver requires the assistance of an IP core to operate, + since data is streamed into target device via DMA, sent over a + QSPI + DDR (Double Data Rate) bus. + + To compile this driver as a module, choose M here: the + module will be called ad3552r-hs. + +config AD3552R_LIB + tristate + +config AD3552R + tristate "Analog Devices AD3552R DAC driver" + depends on SPI_MASTER + select AD3552R_LIB + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Analog Devices AD3552R + Digital to Analog Converter. + + To compile this driver as a module, choose M here: the + module will be called ad3552r. + +config AD5064 + tristate "Analog Devices AD5064 and similar multi-channel DAC driver" + depends on (SPI_MASTER && I2C!=m) || I2C + help + Say yes here to build support for Analog Devices AD5024, AD5025, AD5044, + AD5045, AD5064, AD5064-1, AD5065, AD5625, AD5625R, AD5627, AD5627R, + AD5628, AD5629R, AD5645R, AD5647R, AD5648, AD5665, AD5665R, AD5666, + AD5667, AD5667R, AD5668, AD5669R, LTC2606, LTC2607, LTC2609, LTC2616, + LTC2617, LTC2619, LTC2626, LTC2627, LTC2629, LTC2631, LTC2633, LTC2635 + Digital to Analog Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5064. + +config AD5360 + tristate "Analog Devices AD5360/61/62/63/70/71/73 DAC driver" + depends on SPI + help + Say yes here to build support for Analog Devices AD5360, AD5361, + AD5362, AD5363, AD5370, AD5371, AD5373 multi-channel + Digital to Analog Converters (DAC). + + To compile this driver as module choose M here: the module will be called + ad5360. + +config AD5380 + tristate "Analog Devices AD5380/81/82/83/84/90/91/92 DAC driver" + depends on (SPI_MASTER && I2C!=m) || I2C + select REGMAP_I2C if I2C + select REGMAP_SPI if SPI_MASTER + help + Say yes here to build support for Analog Devices AD5380, AD5381, + AD5382, AD5383, AD5384, AD5390, AD5391, AD5392 multi-channel + Digital to Analog Converters (DAC). + + To compile this driver as module choose M here: the module will be called + ad5380. + +config AD5421 + tristate "Analog Devices AD5421 DAC driver" + depends on SPI + help + Say yes here to build support for Analog Devices AD5421 loop-powered + digital-to-analog converters (DAC). + + To compile this driver as module choose M here: the module will be called + ad5421. + +config AD5446 + tristate + +config AD5446_SPI + tristate "Analog Devices AD5446 and similar single channel DACs driver (SPI)" + depends on SPI + select AD5446 + help + Say yes here to build support for Analog Devices AD5300, AD5310, + AD5320, AD5444, AD5446, AD5450, AD5451, AD5452, AD5453, AD5512A, + AD5541A, AD5542A, AD5543, AD5553, AD5600, AD5601, AD5611, AD5620, + AD5621, AD5640, AD5641, AD5660, AD5662 DACs as well as + Texas Instruments DAC081S101, DAC101S101, DAC121S101. + + To compile this driver as a module, choose M here: the + module will be called ad5446-spi. + +config AD5446_I2C + tristate "Analog Devices AD5446 and similar single channel DACs driver (I2C)" + depends on I2C + select AD5446 + help + Say yes here to build support for Analog Devices AD5301, AD5311, AD5321, + AD5602, AD5612, AD5622 DACs. + + To compile this driver as a module, choose M here: the + module will be called ad5446-i2c. + +config AD5449 + tristate "Analog Devices AD5449 and similar DACs driver" + depends on SPI_MASTER + help + Say yes here to build support for Analog Devices AD5415, AD5426, AD5429, + AD5432, AD5439, AD5443, AD5449 Digital to Analog Converters. + + To compile this driver as a module, choose M here: the + module will be called ad5449. + +config AD5592R_BASE + tristate + +config AD5592R + tristate "Analog Devices AD5592R ADC/DAC driver" + depends on SPI_MASTER + select GPIOLIB + select AD5592R_BASE + help + Say yes here to build support for Analog Devices AD5592R + Digital to Analog / Analog to Digital Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5592r. + +config AD5593R + tristate "Analog Devices AD5593R ADC/DAC driver" + depends on I2C + select GPIOLIB + select AD5592R_BASE + help + Say yes here to build support for Analog Devices AD5593R + Digital to Analog / Analog to Digital Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5593r. + +config AD5504 + tristate "Analog Devices AD5504/AD5501 DAC SPI driver" + depends on SPI + help + Say yes here to build support for Analog Devices AD5504, AD5501, + High Voltage Digital to Analog Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5504. + +config AD5624R_SPI + tristate "Analog Devices AD5624/44/64R DAC spi driver" + depends on SPI + help + Say yes here to build support for Analog Devices AD5624R, AD5644R and + AD5664R converters (DAC). This driver uses the common SPI interface. + +config AD5706R + tristate "Analog Devices AD5706R DAC driver" + depends on SPI + select REGMAP + help + Say yes here to build support for Analog Devices AD5706R 4-channel, + 16-bit current output DAC. + + To compile this driver as a module, choose M here: the + module will be called ad5706r. + +config AD9739A + tristate "Analog Devices AD9739A RF DAC spi driver" + depends on SPI + select REGMAP_SPI + select IIO_BACKEND + help + Say yes here to build support for Analog Devices AD9739A Digital-to + Analog Converter. + + The driver requires the assistance of the AXI DAC IP core to operate, + since SPI is used for configuration only, while data has to be + streamed into memory via DMA. + + To compile this driver as a module, choose M here: the module will be + called ad9739a. + +config ADI_AXI_DAC + tristate "Analog Devices Generic AXI DAC IP core driver" + depends on MICROBLAZE || NIOS2 || ARCH_ZYNQ || ARCH_ZYNQMP || ARCH_INTEL_SOCFPGA || COMPILE_TEST + select IIO_BUFFER + select IIO_BUFFER_DMAENGINE + select REGMAP_MMIO + select IIO_BACKEND + help + Say yes here to build support for Analog Devices Generic + AXI DAC IP core. The IP core is used for interfacing with + digital-to-analog (DAC) converters that require either a high-speed + serial interface (JESD204B/C) or a source synchronous parallel + interface (LVDS/CMOS). + Typically (for such devices) SPI will be used for configuration only, + while this IP core handles the streaming of data into memory via DMA. + + Link: https://wiki.analog.com/resources/fpga/docs/axi_dac_ip + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called adi-axi-dac. + +config LTC2688 + tristate "Analog Devices LTC2688 DAC spi driver" + depends on SPI + select REGMAP + help + Say yes here to build support for Analog Devices + LTC2688 converters (DAC). + + To compile this driver as a module, choose M here: the + module will be called ltc2688. + +config AD5686 + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config AD5686_SPI + tristate "Analog Devices AD5686 and similar multi-channel DACs (SPI)" + depends on SPI + select AD5686 + help + Say yes here to build support for Analog Devices Voltage Output + Digital to Analog Converters: + - Single-channel: + AD5310R, AD5681R, AD5682R, AD5683, AD5683R + - Dual-channel: + AD5313R, AD5687, AD5687R, AD5689, AD5689R + - Quad-channel: + AD5317R, AD5684, AD5684R, AD5685R, AD5686, AD5686R + - 8-channel: + AD5672R, AD5676, AD5676R + - 16-channel: + AD5674, AD5674R, AD5679, AD5679R + + To compile this driver as a module, choose M here: the + module will be called ad5686. + +config AD5696_I2C + tristate "Analog Devices AD5696 and similar multi-channel DACs (I2C)" + depends on I2C + select AD5686 + help + Say yes here to build support for Analog Devices Voltage Output + Digital to Analog Converters: + - Single-channel: + AD5311R, AD5691R, AD5692R, AD5693, AD5693R + - Dual-channel: + AD5338R, AD5697R + - Quad-channel: + AD5316R, AD5694, AD5694R, AD5695R, AD5696, AD5696R + - 8-channel: + AD5671R, AD5675, AD5675R + - 16-channel: + AD5673R, AD5677R + + To compile this driver as a module, choose M here: the module will be + called ad5696. + +config AD5755 + tristate "Analog Devices AD5755/AD5755-1/AD5757/AD5735/AD5737 DAC driver" + depends on SPI_MASTER + help + Say yes here to build support for Analog Devices AD5755, AD5755-1, + AD5757, AD5735, AD5737 quad channel Digital to + Analog Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5755. + +config AD5758 + tristate "Analog Devices AD5758 DAC driver" + depends on SPI_MASTER + help + Say yes here to build support for Analog Devices AD5758 single channel + Digital to Analog Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5758. + +config AD5761 + tristate "Analog Devices AD5761/61R/21/21R DAC driver" + depends on SPI_MASTER + help + Say yes here to build support for Analog Devices AD5761, AD5761R, AD5721, + AD5721R Digital to Analog Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5761. + +config AD5764 + tristate "Analog Devices AD5764/64R/44/44R DAC driver" + depends on SPI_MASTER + help + Say yes here to build support for Analog Devices AD5764, AD5764R, AD5744, + AD5744R Digital to Analog Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5764. + +config AD5766 + tristate "Analog Devices AD5766/AD5767 DAC driver" + depends on SPI_MASTER + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Analog Devices AD5766, AD5767 + Digital to Analog Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5766. + +config AD5770R + tristate "Analog Devices AD5770R IDAC driver" + depends on SPI_MASTER + select REGMAP_SPI + help + Say yes here to build support for Analog Devices AD5770R Digital to + Analog Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5770r. + +config AD5791 + tristate "Analog Devices AD5760/AD5780/AD5781/AD5790/AD5791 DAC SPI driver" + depends on SPI + select SPI_OFFLOAD + select IIO_BUFFER + select IIO_BUFFER_DMAENGINE + help + Say yes here to build support for Analog Devices AD5760, AD5780, + AD5781, AD5790, AD5791 High Resolution Voltage Output Digital to + Analog Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5791. + +config AD7293 + tristate "Analog Devices AD7293 Power Amplifier Current Controller" + depends on SPI + help + Say yes here to build support for Analog Devices AD7293 + Power Amplifier Current Controller with + ADC, DACs, and Temperature and Current Sensors + + To compile this driver as a module, choose M here: the + module will be called ad7293. + +config AD7303 + tristate "Analog Devices AD7303 DAC driver" + depends on SPI + help + Say yes here to build support for Analog Devices AD7303 Digital to Analog + Converters (DAC). + + To compile this driver as module choose M here: the module will be called + ad7303. + +config AD8460 + tristate "Analog Devices AD8460 DAC driver" + depends on SPI + select REGMAP_SPI + select IIO_BUFFER + select IIO_BUFFER_DMAENGINE + help + Say yes here to build support for Analog Devices AD8460 Digital to + Analog Converters (DAC). + + To compile this driver as a module choose M here: the module will be called + ad8460. + +config AD8801 + tristate "Analog Devices AD8801/AD8803 DAC driver" + depends on SPI_MASTER + help + Say yes here to build support for Analog Devices AD8801, AD8803 Digital to + Analog Converters (DAC). + + To compile this driver as a module choose M here: the module will be called + ad8801. + +config BD79703 + tristate "ROHM Semiconductor BD79703 DAC driver" + depends on SPI + select REGMAP_SPI + help + Say yes here to build support for ROHM Semiconductor BD79703 Digital + to Analog Converter (DAC). + +config CIO_DAC + tristate "Measurement Computing CIO-DAC IIO driver" + depends on X86 && (ISA_BUS || PC104) + select ISA_BUS_API + select REGMAP_MMIO + help + Say yes here to build support for the Measurement Computing CIO-DAC + analog output device family (CIO-DAC16, CIO-DAC08, PC104-DAC06). The + base port addresses for the devices may be configured via the base + array module parameter. + +config DPOT_DAC + tristate "DAC emulation using a DPOT" + help + Say yes here to build support for DAC emulation using a digital + potentiometer. + + To compile this driver as a module, choose M here: the module will be + called dpot-dac. + +config DS4424 + tristate "Maxim Integrated DS4422/DS4424 DAC driver" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for Maxim chips DS4422, DS4424. + + This driver can also be built as a module. If so, the module + will be called ds4424. + +config LPC18XX_DAC + tristate "NXP LPC18xx DAC driver" + depends on ARCH_LPC18XX || COMPILE_TEST + depends on HAS_IOMEM + help + Say yes here to build support for NXP LPC18XX DAC. + + To compile this driver as a module, choose M here: the module will be + called lpc18xx_dac. + +config LTC1660 + tristate "Linear Technology LTC1660/LTC1665 DAC SPI driver" + depends on SPI + select REGMAP_SPI + help + Say yes here to build support for Linear Technology + LTC1660 and LTC1665 Digital to Analog Converters. + + To compile this driver as a module, choose M here: the + module will be called ltc1660. + +config LTC2632 + tristate "Linear Technology LTC2632-12/10/8 and similar DAC spi driver" + depends on SPI + help + Say yes here to build support for Linear Technology + LTC2632, LTC2634 and LTC2636 DAC resolution 12/10/8 bit + low 0-2.5V and high 0-4.096V range converters. + + To compile this driver as a module, choose M here: the + module will be called ltc2632. + +config LTC2664 + tristate "Analog Devices LTC2664 and LTC2672 DAC SPI driver" + depends on SPI + select REGMAP_SPI + help + Say yes here to build support for Analog Devices + LTC2664 and LTC2672 converters (DAC). + + To compile this driver as a module, choose M here: the + module will be called ltc2664. + +config M62332 + tristate "Mitsubishi M62332 DAC driver" + depends on I2C + help + If you say yes here you get support for the Mitsubishi M62332 + (I2C 8-Bit DACs with rail-to-rail outputs). + + This driver can also be built as a module. If so, the module + will be called m62332. + +config MAX517 + tristate "Maxim MAX517/518/519/520/521 DAC driver" + depends on I2C + help + If you say yes here you get support for the following Maxim chips + (I2C 8-Bit DACs with rail-to-rail outputs): + MAX517 - Single channel, single reference + MAX518 - Dual channel, ref=Vdd + MAX519 - Dual channel, dual reference + MAX520 - Quad channel, quad reference + MAX521 - Octal channel, independent ref for ch0-3, shared ref for ch4-7 + + This driver can also be built as a module. If so, the module + will be called max517. + +config MAX22007 + tristate "Analog Devices MAX22007 DAC Driver" + depends on SPI + select REGMAP_SPI + select CRC8 + help + Say Y here if you want to build a driver for Analog Devices MAX22007. + + MAX22007 is a quad-channel, 12-bit, voltage-output digital to + analog converter (DAC) with SPI interface. + + If compiled as a module, it will be called max22007. + +config MAX5522 + tristate "Maxim MAX5522 DAC driver" + depends on SPI_MASTER + select REGMAP_SPI + help + Say Y here if you want to build a driver for the Maxim MAX5522. + + MAX5522 is a dual, ultra-low-power, 10-Bit, voltage-output + digital to analog converter (DAC) offering rail-to-rail buffered + voltage outputs. + + If compiled as a module, it will be called max5522. + +config MAX5821 + tristate "Maxim MAX5821 DAC driver" + depends on I2C + help + Say yes here to build support for Maxim MAX5821 + 10 bits DAC. + +config MCF54415_DAC + tristate "NXP MCF54415 DAC driver" + depends on M5441x || COMPILE_TEST + select REGMAP_MMIO + help + Say yes here if you want to build support for NXP ColdFire + MCF54415/6/7/8 12-bit DAC module. + + To compile this driver as a module, choose M here: the module + will be called mcf54415_dac. + +config MCP4725 + tristate "MCP4725/6 DAC driver" + depends on I2C + help + Say Y here if you want to build a driver for the Microchip + MCP 4725/6 12-bit digital-to-analog converter (DAC) with I2C + interface. + + To compile this driver as a module, choose M here: the module + will be called mcp4725. + +config MCP4728 + tristate "MCP4728 DAC driver" + depends on I2C + help + Say Y here if you want to build a driver for the Microchip + MCP4728 quad channel, 12-bit digital-to-analog converter (DAC) + with I2C interface. + + To compile this driver as a module, choose M here: the module + will be called mcp4728. + +config MCP47A1 + tristate "MCP47A1 DAC driver" + depends on I2C + help + Say Y here if you want to build a driver for the Microchip + MCP47A1 digital-to-analog converter with an I2C interface. + + To compile this driver as a module, choose M here: the module + will be called mcp47a1. + +config MCP47FEB02 + tristate "MCP47F(E/V)B01/02/04/08/11/12/14/18/21/22/24/28 DAC driver" + depends on I2C + select REGMAP_I2C + help + Say yes here if you want to build the driver for the Microchip: + - 8-bit DAC: + MCP47FEB01, MCP47FEB02, MCP47FEB04, MCP47FEB08, + MCP47FVB01, MCP47FVB02, MCP47FVB04, MCP47FVB08 + - 10-bit DAC: + MCP47FEB11, MCP47FEB12, MCP47FEB14, MCP47FEB18, + MCP47FVB11, MCP47FVB12, MCP47FVB14, MCP47FVB18 + - 12-bit DAC: + MCP47FEB21, MCP47FEB22, MCP47FEB24, MCP47FEB28, + MCP47FVB21, MCP47FVB22, MCP47FVB24, MCP47FVB28 + having 1 to 8 channels, 8/10/12-bit digital-to-analog converter + (DAC) with I2C interface. + + To compile this driver as a module, choose M here: the module + will be called mcp47feb02. + +config MCP4821 + tristate "MCP4801/02/11/12/21/22 DAC driver" + depends on SPI + help + Say yes here to build the driver for the Microchip MCP4801 + MCP4802, MCP4811, MCP4812, MCP4821 and MCP4822 DAC devices. + + To compile this driver as a module, choose M here: the module + will be called mcp4821. + +config MCP4922 + tristate "MCP4902, MCP4912, MCP4922 DAC driver" + depends on SPI + help + Say yes here to build the driver for the Microchip MCP4902 + MCP4912, and MCP4922 DAC devices. + + To compile this driver as a module, choose M here: the module + will be called mcp4922. + +config STM32_DAC + tristate "STMicroelectronics STM32 DAC" + depends on (ARCH_STM32 && OF) || COMPILE_TEST + depends on REGULATOR + select STM32_DAC_CORE + help + Say yes here to build support for STMicroelectronics STM32 Digital + to Analog Converter (DAC). + + This driver can also be built as a module. If so, the module + will be called stm32-dac. + +config STM32_DAC_CORE + tristate + select REGMAP_MMIO + +config TI_DAC082S085 + tristate "Texas Instruments 8/10/12-bit 2/4-channel DAC driver" + depends on SPI_MASTER + help + Driver for the Texas Instruments (formerly National Semiconductor) + DAC082S085, DAC102S085, DAC122S085, DAC084S085, DAC104S085 and + DAC124S085. + + If compiled as a module, it will be called ti-dac082s085. + +config TI_DAC5571 + tristate "Texas Instruments 8/10/12/16-bit 1/2/4-channel DAC driver" + depends on I2C + help + Driver for the Texas Instruments + DAC5571, DAC6571, DAC7571, DAC5574, DAC6574, DAC7574, DAC5573, + DAC6573, DAC7573, DAC8571, DAC8574, DAC121C081. + + If compiled as a module, it will be called ti-dac5571. + +config TI_DAC7311 + tristate "Texas Instruments 8/10/12-bit 1-channel DAC driver" + depends on SPI + help + Driver for the Texas Instruments + DAC7311, DAC6311, DAC5311. + + If compiled as a module, it will be called ti-dac7311. + +config TI_DAC7612 + tristate "Texas Instruments 12-bit 2-channel DAC driver" + depends on SPI_MASTER && GPIOLIB + help + Driver for the Texas Instruments DAC7612, DAC7612U, DAC7612UB + The driver hand drive the load pin automatically, otherwise + it needs to be toggled manually. + + If compiled as a module, it will be called ti-dac7612. + +config VF610_DAC + tristate "Vybrid vf610 DAC driver" + depends on HAS_IOMEM + help + Say yes here to support Vybrid board digital-to-analog converter. + + This driver can also be built as a module. If so, the module will + be called vf610_dac. + +endmenu diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile new file mode 100644 index 000000000..992f8930f --- /dev/null +++ b/drivers/iio/dac/Makefile @@ -0,0 +1,68 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for industrial I/O DAC drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_AD3530R) += ad3530r.o +obj-$(CONFIG_AD3552R_HS) += ad3552r-hs.o +obj-$(CONFIG_AD3552R_LIB) += ad3552r-common.o +obj-$(CONFIG_AD3552R) += ad3552r.o +obj-$(CONFIG_AD5360) += ad5360.o +obj-$(CONFIG_AD5380) += ad5380.o +obj-$(CONFIG_AD5421) += ad5421.o +obj-$(CONFIG_AD5624R_SPI) += ad5624r_spi.o +obj-$(CONFIG_AD5064) += ad5064.o +obj-$(CONFIG_AD5504) += ad5504.o +obj-$(CONFIG_AD5446) += ad5446.o +obj-$(CONFIG_AD5446_SPI) += ad5446-spi.o +obj-$(CONFIG_AD5446_I2C) += ad5446-i2c.o +obj-$(CONFIG_AD5449) += ad5449.o +obj-$(CONFIG_AD5592R_BASE) += ad5592r-base.o +obj-$(CONFIG_AD5592R) += ad5592r.o +obj-$(CONFIG_AD5593R) += ad5593r.o +obj-$(CONFIG_AD5706R) += ad5706r.o +obj-$(CONFIG_AD5755) += ad5755.o +obj-$(CONFIG_AD5758) += ad5758.o +obj-$(CONFIG_AD5761) += ad5761.o +obj-$(CONFIG_AD5764) += ad5764.o +obj-$(CONFIG_AD5766) += ad5766.o +obj-$(CONFIG_AD5770R) += ad5770r.o +obj-$(CONFIG_AD5791) += ad5791.o +obj-$(CONFIG_AD5686) += ad5686.o +obj-$(CONFIG_AD5686_SPI) += ad5686-spi.o +obj-$(CONFIG_AD5696_I2C) += ad5696-i2c.o +obj-$(CONFIG_AD7293) += ad7293.o +obj-$(CONFIG_AD7303) += ad7303.o +obj-$(CONFIG_AD8460) += ad8460.o +obj-$(CONFIG_AD8801) += ad8801.o +obj-$(CONFIG_AD9739A) += ad9739a.o +obj-$(CONFIG_ADI_AXI_DAC) += adi-axi-dac.o +obj-$(CONFIG_BD79703) += rohm-bd79703.o +obj-$(CONFIG_CIO_DAC) += cio-dac.o +obj-$(CONFIG_DPOT_DAC) += dpot-dac.o +obj-$(CONFIG_DS4424) += ds4424.o +obj-$(CONFIG_LPC18XX_DAC) += lpc18xx_dac.o +obj-$(CONFIG_LTC1660) += ltc1660.o +obj-$(CONFIG_LTC2632) += ltc2632.o +obj-$(CONFIG_LTC2664) += ltc2664.o +obj-$(CONFIG_LTC2688) += ltc2688.o +obj-$(CONFIG_M62332) += m62332.o +obj-$(CONFIG_MAX517) += max517.o +obj-$(CONFIG_MAX22007) += max22007.o +obj-$(CONFIG_MAX5522) += max5522.o +obj-$(CONFIG_MAX5821) += max5821.o +obj-$(CONFIG_MCF54415_DAC) += mcf54415_dac.o +obj-$(CONFIG_MCP4725) += mcp4725.o +obj-$(CONFIG_MCP4728) += mcp4728.o +obj-$(CONFIG_MCP47A1) += mcp47a1.o +obj-$(CONFIG_MCP47FEB02) += mcp47feb02.o +obj-$(CONFIG_MCP4821) += mcp4821.o +obj-$(CONFIG_MCP4922) += mcp4922.o +obj-$(CONFIG_STM32_DAC_CORE) += stm32-dac-core.o +obj-$(CONFIG_STM32_DAC) += stm32-dac.o +obj-$(CONFIG_TI_DAC082S085) += ti-dac082s085.o +obj-$(CONFIG_TI_DAC5571) += ti-dac5571.o +obj-$(CONFIG_TI_DAC7311) += ti-dac7311.o +obj-$(CONFIG_TI_DAC7612) += ti-dac7612.o +obj-$(CONFIG_VF610_DAC) += vf610_dac.o diff --git a/drivers/iio/dac/ad3530r.c b/drivers/iio/dac/ad3530r.c new file mode 100644 index 000000000..06b7f280f --- /dev/null +++ b/drivers/iio/dac/ad3530r.c @@ -0,0 +1,822 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AD3530R/AD3530 8-channel, 16-bit Voltage Output DAC Driver + * AD3531R/AD3531 4-channel, 16-bit Voltage Output DAC Driver + * AD3532R/AD3532 16-channel, 16-bit Voltage Output DAC Driver + * + * Copyright 2025 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AD3530R_INTERFACE_CONFIG_A 0x00 +#define AD3530R_OUTPUT_OPERATING_MODE_0 0x20 +#define AD3530R_OUTPUT_OPERATING_MODE_1 0x21 +#define AD3530R_OUTPUT_CONTROL_0 0x2A +#define AD3530R_REFERENCE_CONTROL_0 0x3C +#define AD3530R_SW_LDAC_TRIG_A 0xE5 +#define AD3530R_INPUT_CH 0xEB +#define AD3530R_MAX_REG_ADDR 0xF9 + +#define AD3531R_SW_LDAC_TRIG_A 0xDD +#define AD3531R_INPUT_CH 0xE3 + +/* AD3532R/AD3532 bank 0 registers (channels 0-7) */ +#define AD3532R_INTERFACE_CONFIG_A_0 0x1000 +#define AD3532R_OUTPUT_OPERATING_MODE_0 0x1020 +#define AD3532R_OUTPUT_OPERATING_MODE_1 0x1021 +#define AD3532R_OUTPUT_CONTROL_0 0x102A +#define AD3532R_REFERENCE_CONTROL_0 0x103C +#define AD3532R_SW_LDAC_TRIG_0 0x10E5 +#define AD3532R_INPUT_CH_0 0x10EB + +/* AD3532R/AD3532 bank 1 registers (channels 8-15) */ +#define AD3532R_INTERFACE_CONFIG_A_1 0x3000 +#define AD3532R_OUTPUT_OPERATING_MODE_2 0x3020 +#define AD3532R_OUTPUT_OPERATING_MODE_3 0x3021 +#define AD3532R_OUTPUT_CONTROL_1 0x302A +#define AD3532R_REFERENCE_CONTROL_1 0x303C +#define AD3532R_SW_LDAC_TRIG_1 0x30E5 +#define AD3532R_INPUT_CH_1 0x30EB +#define AD3532R_MAX_REG_ADDR 0x30F9 + +#define AD3530R_SLD_TRIG_A BIT(7) +#define AD3530R_OUTPUT_CONTROL_RANGE BIT(2) +#define AD3530R_REFERENCE_CONTROL_SEL BIT(0) +#define AD3530R_REG_VAL_MASK GENMASK(15, 0) +#define AD3530R_OP_MODE_CHAN_MSK(chan) (GENMASK(1, 0) << 2 * (chan)) + +#define AD3530R_SW_RESET (BIT(7) | BIT(0)) +#define AD3530R_INTERNAL_VREF_mV 2500 +#define AD3530R_LDAC_PULSE_US 100 + +#define AD3530R_DAC_MAX_VAL GENMASK(15, 0) +#define AD3530R_CH_PER_REG 4 +#define AD3530R_CH_PER_BANK 8 +#define AD3531R_MAX_CHANNELS 4 +#define AD3532R_MAX_CHANNELS 16 + +enum ad3530r_mode { + AD3530R_NORMAL_OP, + AD3530R_POWERDOWN_1K, + AD3530R_POWERDOWN_7K7, + AD3530R_POWERDOWN_32K, +}; + +struct ad3530r_chan { + enum ad3530r_mode powerdown_mode; + bool powerdown; +}; + +struct ad3530r_chip_info { + const char *name; + const struct iio_chan_spec *channels; + const struct regmap_config *regmap_config; + int (*input_ch_reg)(unsigned int channel); + int (*sw_ldac_trig_reg)(unsigned int channel); + const unsigned int *interface_config_a; + const unsigned int *output_control; + const unsigned int *reference_control; + const unsigned int *op_mode; + unsigned int num_channels; + unsigned int num_banks; + unsigned int num_op_mode_regs; + bool internal_ref_support; +}; + +struct ad3530r_state { + struct regmap *regmap; + /* lock to protect against multiple access to the device and shared data */ + struct mutex lock; + struct ad3530r_chan chan[AD3532R_MAX_CHANNELS]; + const struct ad3530r_chip_info *chip_info; + struct gpio_desc *ldac_gpio; + int vref_mV; + /* + * DMA (thus cache coherency maintenance) may require the transfer + * buffers to live in their own cache lines. + */ + __be16 buf __aligned(IIO_DMA_MINALIGN); +}; + +static int ad3530r_input_ch_reg(unsigned int channel) +{ + return 2 * channel + AD3530R_INPUT_CH; +} + +static int ad3531r_input_ch_reg(unsigned int channel) +{ + return 2 * channel + AD3531R_INPUT_CH; +} + +static int ad3532r_input_ch_reg(unsigned int channel) +{ + unsigned int bank = channel / AD3530R_CH_PER_BANK; + unsigned int local_ch = channel % AD3530R_CH_PER_BANK; + + return 2 * local_ch + (bank ? AD3532R_INPUT_CH_1 : AD3532R_INPUT_CH_0); +} + +static const char * const ad3530r_powerdown_modes[] = { + "1kohm_to_gnd", + "7.7kohm_to_gnd", + "32kohm_to_gnd", +}; + +static const char * const ad3531r_powerdown_modes[] = { + "500ohm_to_gnd", + "3.85kohm_to_gnd", + "16kohm_to_gnd", +}; + +static const char * const ad3532r_powerdown_modes[] = { + "1kohm_to_gnd", + "10kohm_to_gnd", + "three_state", +}; + +static int ad3530r_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad3530r_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->lock); + return st->chan[chan->channel].powerdown_mode - 1; +} + +static int ad3530r_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct ad3530r_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->lock); + st->chan[chan->channel].powerdown_mode = mode + 1; + + return 0; +} + +static const struct iio_enum ad3530r_powerdown_mode_enum = { + .items = ad3530r_powerdown_modes, + .num_items = ARRAY_SIZE(ad3530r_powerdown_modes), + .get = ad3530r_get_powerdown_mode, + .set = ad3530r_set_powerdown_mode, +}; + +static const struct iio_enum ad3531r_powerdown_mode_enum = { + .items = ad3531r_powerdown_modes, + .num_items = ARRAY_SIZE(ad3531r_powerdown_modes), + .get = ad3530r_get_powerdown_mode, + .set = ad3530r_set_powerdown_mode, +}; + +static const struct iio_enum ad3532r_powerdown_mode_enum = { + .items = ad3532r_powerdown_modes, + .num_items = ARRAY_SIZE(ad3532r_powerdown_modes), + .get = ad3530r_get_powerdown_mode, + .set = ad3530r_set_powerdown_mode, +}; + +static ssize_t ad3530r_get_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ad3530r_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->lock); + return sysfs_emit(buf, "%d\n", st->chan[chan->channel].powerdown); +} + +static ssize_t ad3530r_set_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ad3530r_state *st = iio_priv(indio_dev); + int ret; + unsigned int reg, pdmode, mask, val; + bool powerdown; + + ret = kstrtobool(buf, &powerdown); + if (ret) + return ret; + + guard(mutex)(&st->lock); + reg = chan->channel < AD3531R_MAX_CHANNELS ? + AD3530R_OUTPUT_OPERATING_MODE_0 : + AD3530R_OUTPUT_OPERATING_MODE_1; + pdmode = powerdown ? st->chan[chan->channel].powerdown_mode : 0; + mask = chan->channel < AD3531R_MAX_CHANNELS ? + AD3530R_OP_MODE_CHAN_MSK(chan->channel) : + AD3530R_OP_MODE_CHAN_MSK(chan->channel - 4); + val = field_prep(mask, pdmode); + + ret = regmap_update_bits(st->regmap, reg, mask, val); + if (ret) + return ret; + + st->chan[chan->channel].powerdown = powerdown; + + return len; +} + +static ssize_t ad3532r_set_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ad3530r_state *st = iio_priv(indio_dev); + unsigned int bank, local_ch, reg_in_bank, ch_in_reg; + unsigned int reg, mask, val; + bool powerdown; + int ret; + + ret = kstrtobool(buf, &powerdown); + if (ret) + return ret; + + bank = chan->channel / AD3530R_CH_PER_BANK; + local_ch = chan->channel % AD3530R_CH_PER_BANK; + reg_in_bank = local_ch / AD3530R_CH_PER_REG; + ch_in_reg = local_ch % AD3530R_CH_PER_REG; + + reg = reg_in_bank + (bank ? AD3532R_OUTPUT_OPERATING_MODE_2 : + AD3532R_OUTPUT_OPERATING_MODE_0); + mask = AD3530R_OP_MODE_CHAN_MSK(ch_in_reg); + + guard(mutex)(&st->lock); + if (powerdown) { + val = field_prep(mask, st->chan[chan->channel].powerdown_mode); + ret = regmap_update_bits(st->regmap, reg, mask, val); + } else { + ret = regmap_clear_bits(st->regmap, reg, mask); + } + if (ret) + return ret; + + st->chan[chan->channel].powerdown = powerdown; + + return len; +} + +static int ad3530r_trigger_sw_ldac_reg(unsigned int channel) +{ + return AD3530R_SW_LDAC_TRIG_A; +} + +static int ad3531r_trigger_sw_ldac_reg(unsigned int channel) +{ + return AD3531R_SW_LDAC_TRIG_A; +} + +static int ad3532r_trigger_sw_ldac_reg(unsigned int channel) +{ + unsigned int bank = channel / AD3530R_CH_PER_BANK; + + return bank ? AD3532R_SW_LDAC_TRIG_1 : AD3532R_SW_LDAC_TRIG_0; +} + +static int ad3530r_trigger_hw_ldac(struct gpio_desc *ldac_gpio) +{ + gpiod_set_value_cansleep(ldac_gpio, 1); + fsleep(AD3530R_LDAC_PULSE_US); + gpiod_set_value_cansleep(ldac_gpio, 0); + + return 0; +} + +static int ad3530r_dac_write(struct ad3530r_state *st, unsigned int chan, + unsigned int val) +{ + int ret; + + guard(mutex)(&st->lock); + st->buf = cpu_to_be16(val); + + ret = regmap_bulk_write(st->regmap, st->chip_info->input_ch_reg(chan), + &st->buf, sizeof(st->buf)); + if (ret) + return ret; + + if (st->ldac_gpio) + return ad3530r_trigger_hw_ldac(st->ldac_gpio); + + return regmap_set_bits(st->regmap, st->chip_info->sw_ldac_trig_reg(chan), + AD3530R_SLD_TRIG_A); +} + +static int ad3530r_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad3530r_state *st = iio_priv(indio_dev); + int ret; + + guard(mutex)(&st->lock); + switch (info) { + case IIO_CHAN_INFO_RAW: + ret = regmap_bulk_read(st->regmap, + st->chip_info->input_ch_reg(chan->channel), + &st->buf, sizeof(st->buf)); + if (ret) + return ret; + + *val = FIELD_GET(AD3530R_REG_VAL_MASK, be16_to_cpu(st->buf)); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = st->vref_mV; + *val2 = 16; + + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static int ad3530r_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct ad3530r_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (val < 0 || val > AD3530R_DAC_MAX_VAL) + return -EINVAL; + + return ad3530r_dac_write(st, chan->channel, val); + default: + return -EINVAL; + } +} + +static int ad3530r_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct ad3530r_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); +} + +static const struct iio_chan_spec_ext_info ad3530r_ext_info[] = { + { + .name = "powerdown", + .shared = IIO_SEPARATE, + .read = ad3530r_get_dac_powerdown, + .write = ad3530r_set_dac_powerdown, + }, + IIO_ENUM("powerdown_mode", IIO_SEPARATE, &ad3530r_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, + &ad3530r_powerdown_mode_enum), + { } +}; + +static const struct iio_chan_spec_ext_info ad3531r_ext_info[] = { + { + .name = "powerdown", + .shared = IIO_SEPARATE, + .read = ad3530r_get_dac_powerdown, + .write = ad3530r_set_dac_powerdown, + }, + IIO_ENUM("powerdown_mode", IIO_SEPARATE, &ad3531r_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, + &ad3531r_powerdown_mode_enum), + { } +}; + +static const struct iio_chan_spec_ext_info ad3532r_ext_info[] = { + { + .name = "powerdown", + .shared = IIO_SEPARATE, + .read = ad3530r_get_dac_powerdown, + .write = ad3532r_set_dac_powerdown, + }, + IIO_ENUM("powerdown_mode", IIO_SEPARATE, &ad3532r_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, + &ad3532r_powerdown_mode_enum), + { } +}; + +#define AD3530R_CHAN(_chan, _ext_info) \ +{ \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _chan, \ + .output = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info = _ext_info, \ +} + +static const struct iio_chan_spec ad3530r_channels[] = { + AD3530R_CHAN(0, ad3530r_ext_info), + AD3530R_CHAN(1, ad3530r_ext_info), + AD3530R_CHAN(2, ad3530r_ext_info), + AD3530R_CHAN(3, ad3530r_ext_info), + AD3530R_CHAN(4, ad3530r_ext_info), + AD3530R_CHAN(5, ad3530r_ext_info), + AD3530R_CHAN(6, ad3530r_ext_info), + AD3530R_CHAN(7, ad3530r_ext_info), +}; + +static const struct iio_chan_spec ad3531r_channels[] = { + AD3530R_CHAN(0, ad3531r_ext_info), + AD3530R_CHAN(1, ad3531r_ext_info), + AD3530R_CHAN(2, ad3531r_ext_info), + AD3530R_CHAN(3, ad3531r_ext_info), +}; + +static const struct iio_chan_spec ad3532r_channels[] = { + AD3530R_CHAN(0, ad3532r_ext_info), + AD3530R_CHAN(1, ad3532r_ext_info), + AD3530R_CHAN(2, ad3532r_ext_info), + AD3530R_CHAN(3, ad3532r_ext_info), + AD3530R_CHAN(4, ad3532r_ext_info), + AD3530R_CHAN(5, ad3532r_ext_info), + AD3530R_CHAN(6, ad3532r_ext_info), + AD3530R_CHAN(7, ad3532r_ext_info), + AD3530R_CHAN(8, ad3532r_ext_info), + AD3530R_CHAN(9, ad3532r_ext_info), + AD3530R_CHAN(10, ad3532r_ext_info), + AD3530R_CHAN(11, ad3532r_ext_info), + AD3530R_CHAN(12, ad3532r_ext_info), + AD3530R_CHAN(13, ad3532r_ext_info), + AD3530R_CHAN(14, ad3532r_ext_info), + AD3530R_CHAN(15, ad3532r_ext_info), +}; + +static const unsigned int ad3530r_if_config[] = { + AD3530R_INTERFACE_CONFIG_A, +}; + +static const unsigned int ad3530r_out_ctrl[] = { + AD3530R_OUTPUT_CONTROL_0, +}; + +static const unsigned int ad3530r_ref_ctrl[] = { + AD3530R_REFERENCE_CONTROL_0, +}; + +static const unsigned int ad3530r_op_mode[] = { + AD3530R_OUTPUT_OPERATING_MODE_0, + AD3530R_OUTPUT_OPERATING_MODE_1, +}; + +static const unsigned int ad3531r_op_mode[] = { + AD3530R_OUTPUT_OPERATING_MODE_0, +}; + +static const unsigned int ad3532r_if_config[] = { + AD3532R_INTERFACE_CONFIG_A_0, + AD3532R_INTERFACE_CONFIG_A_1, +}; + +static const unsigned int ad3532r_out_ctrl[] = { + AD3532R_OUTPUT_CONTROL_0, + AD3532R_OUTPUT_CONTROL_1, +}; + +static const unsigned int ad3532r_ref_ctrl[] = { + AD3532R_REFERENCE_CONTROL_0, + AD3532R_REFERENCE_CONTROL_1, +}; + +static const unsigned int ad3532r_op_mode[] = { + AD3532R_OUTPUT_OPERATING_MODE_0, + AD3532R_OUTPUT_OPERATING_MODE_1, + AD3532R_OUTPUT_OPERATING_MODE_2, + AD3532R_OUTPUT_OPERATING_MODE_3, +}; + +static const struct regmap_config ad3530r_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = AD3530R_MAX_REG_ADDR, +}; + +static const struct regmap_config ad3532r_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = AD3532R_MAX_REG_ADDR, +}; + +static const struct ad3530r_chip_info ad3530_chip = { + .name = "ad3530", + .channels = ad3530r_channels, + .regmap_config = &ad3530r_regmap_config, + .num_channels = ARRAY_SIZE(ad3530r_channels), + .sw_ldac_trig_reg = ad3530r_trigger_sw_ldac_reg, + .input_ch_reg = ad3530r_input_ch_reg, + .interface_config_a = ad3530r_if_config, + .output_control = ad3530r_out_ctrl, + .reference_control = ad3530r_ref_ctrl, + .op_mode = ad3530r_op_mode, + .num_banks = ARRAY_SIZE(ad3530r_if_config), + .num_op_mode_regs = ARRAY_SIZE(ad3530r_op_mode), + .internal_ref_support = false, +}; + +static const struct ad3530r_chip_info ad3530r_chip = { + .name = "ad3530r", + .channels = ad3530r_channels, + .regmap_config = &ad3530r_regmap_config, + .num_channels = ARRAY_SIZE(ad3530r_channels), + .sw_ldac_trig_reg = ad3530r_trigger_sw_ldac_reg, + .input_ch_reg = ad3530r_input_ch_reg, + .interface_config_a = ad3530r_if_config, + .output_control = ad3530r_out_ctrl, + .reference_control = ad3530r_ref_ctrl, + .op_mode = ad3530r_op_mode, + .num_banks = ARRAY_SIZE(ad3530r_if_config), + .num_op_mode_regs = ARRAY_SIZE(ad3530r_op_mode), + .internal_ref_support = true, +}; + +static const struct ad3530r_chip_info ad3531_chip = { + .name = "ad3531", + .channels = ad3531r_channels, + .regmap_config = &ad3530r_regmap_config, + .num_channels = ARRAY_SIZE(ad3531r_channels), + .sw_ldac_trig_reg = ad3531r_trigger_sw_ldac_reg, + .input_ch_reg = ad3531r_input_ch_reg, + .interface_config_a = ad3530r_if_config, + .output_control = ad3530r_out_ctrl, + .reference_control = ad3530r_ref_ctrl, + .op_mode = ad3531r_op_mode, + .num_banks = ARRAY_SIZE(ad3530r_if_config), + .num_op_mode_regs = ARRAY_SIZE(ad3531r_op_mode), + .internal_ref_support = false, +}; + +static const struct ad3530r_chip_info ad3531r_chip = { + .name = "ad3531r", + .channels = ad3531r_channels, + .regmap_config = &ad3530r_regmap_config, + .num_channels = ARRAY_SIZE(ad3531r_channels), + .sw_ldac_trig_reg = ad3531r_trigger_sw_ldac_reg, + .input_ch_reg = ad3531r_input_ch_reg, + .interface_config_a = ad3530r_if_config, + .output_control = ad3530r_out_ctrl, + .reference_control = ad3530r_ref_ctrl, + .op_mode = ad3531r_op_mode, + .num_banks = ARRAY_SIZE(ad3530r_if_config), + .num_op_mode_regs = ARRAY_SIZE(ad3531r_op_mode), + .internal_ref_support = true, +}; + +static const struct ad3530r_chip_info ad3532_chip = { + .name = "ad3532", + .channels = ad3532r_channels, + .regmap_config = &ad3532r_regmap_config, + .num_channels = ARRAY_SIZE(ad3532r_channels), + .sw_ldac_trig_reg = ad3532r_trigger_sw_ldac_reg, + .input_ch_reg = ad3532r_input_ch_reg, + .interface_config_a = ad3532r_if_config, + .output_control = ad3532r_out_ctrl, + .reference_control = ad3532r_ref_ctrl, + .op_mode = ad3532r_op_mode, + .num_banks = ARRAY_SIZE(ad3532r_if_config), + .num_op_mode_regs = ARRAY_SIZE(ad3532r_op_mode), + .internal_ref_support = false, +}; + +static const struct ad3530r_chip_info ad3532r_chip = { + .name = "ad3532r", + .channels = ad3532r_channels, + .regmap_config = &ad3532r_regmap_config, + .num_channels = ARRAY_SIZE(ad3532r_channels), + .sw_ldac_trig_reg = ad3532r_trigger_sw_ldac_reg, + .input_ch_reg = ad3532r_input_ch_reg, + .interface_config_a = ad3532r_if_config, + .output_control = ad3532r_out_ctrl, + .reference_control = ad3532r_ref_ctrl, + .op_mode = ad3532r_op_mode, + .num_banks = ARRAY_SIZE(ad3532r_if_config), + .num_op_mode_regs = ARRAY_SIZE(ad3532r_op_mode), + .internal_ref_support = true, +}; + +static int ad3530r_set_reg_bank_bits(const struct ad3530r_state *st, + const unsigned int *regs, + unsigned int num_regs, + unsigned int mask) +{ + int ret; + + for (unsigned int i = 0; i < num_regs; i++) { + ret = regmap_set_bits(st->regmap, regs[i], mask); + if (ret) + return ret; + } + + return 0; +} + +static int ad3530r_write_reg_banks(const struct ad3530r_state *st, + const unsigned int *regs, + unsigned int num_regs, + unsigned int val) +{ + int ret; + + for (unsigned int i = 0; i < num_regs; i++) { + ret = regmap_write(st->regmap, regs[i], val); + if (ret) + return ret; + } + + return 0; +} + +static int ad3530r_setup(struct ad3530r_state *st, int external_vref_uV) +{ + const struct ad3530r_chip_info *chip_info = st->chip_info; + struct device *dev = regmap_get_device(st->regmap); + struct gpio_desc *reset_gpio; + u8 range_multiplier, val; + int ret; + + reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(reset_gpio)) + return dev_err_probe(dev, PTR_ERR(reset_gpio), + "Failed to get reset GPIO\n"); + + if (reset_gpio) { + /* Perform hardware reset */ + fsleep(1 * USEC_PER_MSEC); + gpiod_set_value_cansleep(reset_gpio, 0); + } else { + /* Perform software reset */ + ret = ad3530r_set_reg_bank_bits(st, chip_info->interface_config_a, + chip_info->num_banks, + AD3530R_SW_RESET); + if (ret) + return ret; + } + + fsleep(10 * USEC_PER_MSEC); + + range_multiplier = 1; + if (device_property_read_bool(dev, "adi,range-double")) { + ret = ad3530r_set_reg_bank_bits(st, chip_info->output_control, + chip_info->num_banks, + AD3530R_OUTPUT_CONTROL_RANGE); + if (ret) + return ret; + + range_multiplier = 2; + } + + if (external_vref_uV) { + st->vref_mV = range_multiplier * external_vref_uV / MILLI; + } else { + ret = ad3530r_set_reg_bank_bits(st, chip_info->reference_control, + chip_info->num_banks, + AD3530R_REFERENCE_CONTROL_SEL); + if (ret) + return ret; + + st->vref_mV = range_multiplier * AD3530R_INTERNAL_VREF_mV; + } + + /* Set normal operating mode for all channels */ + val = FIELD_PREP(AD3530R_OP_MODE_CHAN_MSK(0), AD3530R_NORMAL_OP) | + FIELD_PREP(AD3530R_OP_MODE_CHAN_MSK(1), AD3530R_NORMAL_OP) | + FIELD_PREP(AD3530R_OP_MODE_CHAN_MSK(2), AD3530R_NORMAL_OP) | + FIELD_PREP(AD3530R_OP_MODE_CHAN_MSK(3), AD3530R_NORMAL_OP); + + ret = ad3530r_write_reg_banks(st, st->chip_info->op_mode, + st->chip_info->num_op_mode_regs, val); + if (ret) + return ret; + + for (unsigned int i = 0; i < st->chip_info->num_channels; i++) + st->chan[i].powerdown_mode = AD3530R_POWERDOWN_32K; + + st->ldac_gpio = devm_gpiod_get_optional(dev, "ldac", GPIOD_OUT_LOW); + if (IS_ERR(st->ldac_gpio)) + return dev_err_probe(dev, PTR_ERR(st->ldac_gpio), + "Failed to get ldac GPIO\n"); + + return 0; +} + +static const struct iio_info ad3530r_info = { + .read_raw = ad3530r_read_raw, + .write_raw = ad3530r_write_raw, + .debugfs_reg_access = ad3530r_reg_access, +}; + +static int ad3530r_probe(struct spi_device *spi) +{ + static const char * const regulators[] = { "vdd", "iovdd" }; + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ad3530r_state *st; + int ret, external_vref_uV; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->chip_info = spi_get_device_match_data(spi); + if (!st->chip_info) + return -ENODEV; + + st->regmap = devm_regmap_init_spi(spi, st->chip_info->regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "Failed to init regmap"); + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulators), + regulators); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulators\n"); + + external_vref_uV = devm_regulator_get_enable_read_voltage(dev, "ref"); + if (external_vref_uV < 0 && external_vref_uV != -ENODEV) + return external_vref_uV; + + if (external_vref_uV == -ENODEV) + external_vref_uV = 0; + + if (!st->chip_info->internal_ref_support && external_vref_uV == 0) + return -ENODEV; + + ret = ad3530r_setup(st, external_vref_uV); + if (ret) + return ret; + + indio_dev->name = st->chip_info->name; + indio_dev->info = &ad3530r_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id ad3530r_id[] = { + { .name = "ad3530", .driver_data = (kernel_ulong_t)&ad3530_chip }, + { .name = "ad3530r", .driver_data = (kernel_ulong_t)&ad3530r_chip }, + { .name = "ad3531", .driver_data = (kernel_ulong_t)&ad3531_chip }, + { .name = "ad3531r", .driver_data = (kernel_ulong_t)&ad3531r_chip }, + { .name = "ad3532", .driver_data = (kernel_ulong_t)&ad3532_chip }, + { .name = "ad3532r", .driver_data = (kernel_ulong_t)&ad3532r_chip }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad3530r_id); + +static const struct of_device_id ad3530r_of_match[] = { + { .compatible = "adi,ad3530", .data = &ad3530_chip }, + { .compatible = "adi,ad3530r", .data = &ad3530r_chip }, + { .compatible = "adi,ad3531", .data = &ad3531_chip }, + { .compatible = "adi,ad3531r", .data = &ad3531r_chip }, + { .compatible = "adi,ad3532", .data = &ad3532_chip }, + { .compatible = "adi,ad3532r", .data = &ad3532r_chip }, + { } +}; +MODULE_DEVICE_TABLE(of, ad3530r_of_match); + +static struct spi_driver ad3530r_driver = { + .driver = { + .name = "ad3530r", + .of_match_table = ad3530r_of_match, + }, + .probe = ad3530r_probe, + .id_table = ad3530r_id, +}; +module_spi_driver(ad3530r_driver); + +MODULE_AUTHOR("Kim Seer Paller "); +MODULE_DESCRIPTION("Analog Devices AD3530R and Similar DACs Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/dac/ad3552r-common.c b/drivers/iio/dac/ad3552r-common.c new file mode 100644 index 000000000..38baaea0e --- /dev/null +++ b/drivers/iio/dac/ad3552r-common.c @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// Copyright (c) 2010-2024 Analog Devices Inc. +// Copyright (c) 2024 Baylibre, SAS + +#include +#include +#include +#include +#include + +#include "ad3552r.h" + +static const s32 ad3552r_ch_ranges[AD3552R_MAX_RANGES][2] = { + [AD3552R_CH_OUTPUT_RANGE_0__2P5V] = { 0, 2500 }, + [AD3552R_CH_OUTPUT_RANGE_0__5V] = { 0, 5000 }, + [AD3552R_CH_OUTPUT_RANGE_0__10V] = { 0, 10000 }, + [AD3552R_CH_OUTPUT_RANGE_NEG_5__5V] = { -5000, 5000 }, + [AD3552R_CH_OUTPUT_RANGE_NEG_10__10V] = { -10000, 10000 } +}; + +static const s32 ad3542r_ch_ranges[AD3542R_MAX_RANGES][2] = { + [AD3542R_CH_OUTPUT_RANGE_0__2P5V] = { 0, 2500 }, + [AD3542R_CH_OUTPUT_RANGE_0__5V] = { 0, 5000 }, + [AD3542R_CH_OUTPUT_RANGE_0__10V] = { 0, 10000 }, + [AD3542R_CH_OUTPUT_RANGE_NEG_5__5V] = { -5000, 5000 }, + [AD3542R_CH_OUTPUT_RANGE_NEG_2P5__7P5V] = { -2500, 7500 } +}; + +/* Gain * AD3552R_GAIN_SCALE */ +static const s32 gains_scaling_table[] = { + [AD3552R_CH_GAIN_SCALING_1] = 1000, + [AD3552R_CH_GAIN_SCALING_0_5] = 500, + [AD3552R_CH_GAIN_SCALING_0_25] = 250, + [AD3552R_CH_GAIN_SCALING_0_125] = 125 +}; + +const struct ad3552r_model_data ad3541r_model_data = { + .model_name = "ad3541r", + .chip_id = AD3541R_ID, + .num_hw_channels = 1, + .ranges_table = ad3542r_ch_ranges, + .num_ranges = ARRAY_SIZE(ad3542r_ch_ranges), + .requires_output_range = true, + .num_spi_data_lanes = 2, + .max_reg_addr = 0x46, +}; +EXPORT_SYMBOL_NS_GPL(ad3541r_model_data, "IIO_AD3552R"); + +const struct ad3552r_model_data ad3542r_model_data = { + .model_name = "ad3542r", + .chip_id = AD3542R_ID, + .num_hw_channels = 2, + .ranges_table = ad3542r_ch_ranges, + .num_ranges = ARRAY_SIZE(ad3542r_ch_ranges), + .requires_output_range = true, + .num_spi_data_lanes = 2, + .max_reg_addr = 0x49, +}; +EXPORT_SYMBOL_NS_GPL(ad3542r_model_data, "IIO_AD3552R"); + +const struct ad3552r_model_data ad3551r_model_data = { + .model_name = "ad3551r", + .chip_id = AD3551R_ID, + .num_hw_channels = 1, + .ranges_table = ad3552r_ch_ranges, + .num_ranges = ARRAY_SIZE(ad3552r_ch_ranges), + .requires_output_range = false, + .num_spi_data_lanes = 4, + .max_reg_addr = 0x46, +}; +EXPORT_SYMBOL_NS_GPL(ad3551r_model_data, "IIO_AD3552R"); + +const struct ad3552r_model_data ad3552r_model_data = { + .model_name = "ad3552r", + .chip_id = AD3552R_ID, + .num_hw_channels = 2, + .ranges_table = ad3552r_ch_ranges, + .num_ranges = ARRAY_SIZE(ad3552r_ch_ranges), + .requires_output_range = false, + .num_spi_data_lanes = 4, + .max_reg_addr = 0x49, +}; +EXPORT_SYMBOL_NS_GPL(ad3552r_model_data, "IIO_AD3552R"); + +u16 ad3552r_calc_custom_gain(u8 p, u8 n, s16 goffs) +{ + return FIELD_PREP(AD3552R_MASK_CH_RANGE_OVERRIDE, 1) | + FIELD_PREP(AD3552R_MASK_CH_GAIN_SCALING_P, p) | + FIELD_PREP(AD3552R_MASK_CH_GAIN_SCALING_N, n) | + FIELD_PREP(AD3552R_MASK_CH_OFFSET_BIT_8, abs(goffs)) | + FIELD_PREP(AD3552R_MASK_CH_OFFSET_POLARITY, goffs < 0); +} +EXPORT_SYMBOL_NS_GPL(ad3552r_calc_custom_gain, "IIO_AD3552R"); + +static void ad3552r_get_custom_range(struct ad3552r_ch_data *ch_data, + s32 *v_min, s32 *v_max) +{ + s64 vref, tmp, common, offset, gn, gp; + /* + * From datasheet formula (In Volts): + * Vmin = 2.5 + [(GainN + Offset / 1024) * 2.5 * Rfb * 1.03] + * Vmax = 2.5 - [(GainP + Offset / 1024) * 2.5 * Rfb * 1.03] + * Calculus are converted to milivolts + */ + vref = 2500; + /* 2.5 * 1.03 * 1000 (To mV) */ + common = 2575 * ch_data->rfb; + offset = ch_data->gain_offset; + + gn = gains_scaling_table[ch_data->n]; + tmp = (1024 * gn + AD3552R_GAIN_SCALE * offset) * common; + tmp = div_s64(tmp, 1024 * AD3552R_GAIN_SCALE); + *v_max = vref + tmp; + + gp = gains_scaling_table[ch_data->p]; + tmp = (1024 * gp - AD3552R_GAIN_SCALE * offset) * common; + tmp = div_s64(tmp, 1024 * AD3552R_GAIN_SCALE); + *v_min = vref - tmp; +} + +void ad3552r_calc_gain_and_offset(struct ad3552r_ch_data *ch_data, + const struct ad3552r_model_data *model_data) +{ + s32 idx, v_max, v_min, span, rem; + s64 tmp; + + if (ch_data->range_override) { + ad3552r_get_custom_range(ch_data, &v_min, &v_max); + } else { + /* Normal range */ + idx = ch_data->range; + v_min = model_data->ranges_table[idx][0]; + v_max = model_data->ranges_table[idx][1]; + } + + /* + * From datasheet formula: + * Vout = Span * (D / 65536) + Vmin + * Converted to scale and offset: + * Scale = Span / 65536 + * Offset = 65536 * Vmin / Span + * + * Reminders are in micros in order to be printed as + * IIO_VAL_INT_PLUS_MICRO + */ + span = v_max - v_min; + ch_data->scale_int = div_s64_rem(span, 65536, &rem); + /* Do operations in microvolts */ + ch_data->scale_dec = DIV_ROUND_CLOSEST((s64)rem * 1000000, 65536); + + ch_data->offset_int = div_s64_rem(v_min * 65536, span, &rem); + tmp = (s64)rem * 1000000; + ch_data->offset_dec = div_s64(tmp, span); +} +EXPORT_SYMBOL_NS_GPL(ad3552r_calc_gain_and_offset, "IIO_AD3552R"); + +int ad3552r_get_ref_voltage(struct device *dev, u32 *val) +{ + int voltage; + int delta = 100000; + + voltage = devm_regulator_get_enable_read_voltage(dev, "vref"); + if (voltage < 0 && voltage != -ENODEV) + return dev_err_probe(dev, voltage, + "Error getting vref voltage\n"); + + if (voltage == -ENODEV) { + if (device_property_read_bool(dev, "adi,vref-out-en")) + *val = AD3552R_INTERNAL_VREF_PIN_2P5V; + else + *val = AD3552R_INTERNAL_VREF_PIN_FLOATING; + + return 0; + } + + if (voltage > 2500000 + delta || voltage < 2500000 - delta) { + dev_warn(dev, "vref-supply must be 2.5V"); + return -EINVAL; + } + + *val = AD3552R_EXTERNAL_VREF_PIN_INPUT; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ad3552r_get_ref_voltage, "IIO_AD3552R"); + +int ad3552r_get_drive_strength(struct device *dev, u32 *val) +{ + int err; + u32 drive_strength; + + err = device_property_read_u32(dev, "adi,sdo-drive-strength", + &drive_strength); + if (err) + return err; + + if (drive_strength > 3) { + dev_err_probe(dev, -EINVAL, + "adi,sdo-drive-strength must be less than 4\n"); + return -EINVAL; + } + + *val = drive_strength; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ad3552r_get_drive_strength, "IIO_AD3552R"); + +int ad3552r_get_custom_gain(struct device *dev, struct fwnode_handle *child, + u8 *gs_p, u8 *gs_n, u16 *rfb, s16 *goffs) +{ + int err; + u32 val; + struct fwnode_handle *gain_child __free(fwnode_handle) = + fwnode_get_named_child_node(child, + "custom-output-range-config"); + + if (!gain_child) + return dev_err_probe(dev, -EINVAL, + "custom-output-range-config mandatory\n"); + + err = fwnode_property_read_u32(gain_child, "adi,gain-scaling-p", &val); + if (err) + return dev_err_probe(dev, err, + "adi,gain-scaling-p mandatory\n"); + *gs_p = val; + + err = fwnode_property_read_u32(gain_child, "adi,gain-scaling-n", &val); + if (err) + return dev_err_probe(dev, err, + "adi,gain-scaling-n property mandatory\n"); + *gs_n = val; + + err = fwnode_property_read_u32(gain_child, "adi,rfb-ohms", &val); + if (err) + return dev_err_probe(dev, err, + "adi,rfb-ohms mandatory\n"); + *rfb = val; + + err = fwnode_property_read_u32(gain_child, "adi,gain-offset", &val); + if (err) + return dev_err_probe(dev, err, + "adi,gain-offset mandatory\n"); + *goffs = val; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ad3552r_get_custom_gain, "IIO_AD3552R"); + +static int ad3552r_find_range(const struct ad3552r_model_data *model_info, + s32 *vals) +{ + int i; + + for (i = 0; i < model_info->num_ranges; i++) + if (vals[0] == model_info->ranges_table[i][0] * 1000 && + vals[1] == model_info->ranges_table[i][1] * 1000) + return i; + + return -EINVAL; +} + +int ad3552r_get_output_range(struct device *dev, + const struct ad3552r_model_data *model_info, + struct fwnode_handle *child, u32 *val) +{ + int ret; + s32 vals[2]; + + /* This property is optional, so returning -ENOENT if missing */ + if (!fwnode_property_present(child, "adi,output-range-microvolt")) + return -ENOENT; + + ret = fwnode_property_read_u32_array(child, + "adi,output-range-microvolt", + vals, 2); + if (ret) + return dev_err_probe(dev, ret, + "invalid adi,output-range-microvolt\n"); + + ret = ad3552r_find_range(model_info, vals); + if (ret < 0) + return dev_err_probe(dev, ret, + "invalid adi,output-range-microvolt value\n"); + + *val = ret; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ad3552r_get_output_range, "IIO_AD3552R"); + +MODULE_DESCRIPTION("ad3552r common functions"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/dac/ad3552r-hs.c b/drivers/iio/dac/ad3552r-hs.c new file mode 100644 index 000000000..f865843aa --- /dev/null +++ b/drivers/iio/dac/ad3552r-hs.c @@ -0,0 +1,886 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices AD3552R + * Digital to Analog converter driver, High Speed version + * + * Copyright 2024 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ad3552r.h" +#include "ad3552r-hs.h" + +/* + * Important notes for register map access: + * ======================================== + * + * Register address space is divided in 2 regions, primary (config) and + * secondary (DAC). Primary region can only be accessed in simple SPI mode, + * with exception for ad355x models where setting QSPI pin high allows QSPI + * access to both the regions. + * + * Due to the fact that ad3541/2r do not implement QSPI, for proper device + * detection, HDL keeps "QSPI" pin level low at boot (see ad3552r manual, rev B + * table 7, pin 31, digital input). For this reason, actually the working mode + * between SPI, DSPI and QSPI must be set via software, configuring the target + * DAC appropriately, together with the backend API to configure the bus mode + * accordingly. + * + * Also, important to note that none of the three modes allow to read in DDR. + * + * In non-buffering operations, mode is set to simple SPI SDR for all primary + * and secondary region r/w accesses, to avoid to switch the mode each time DAC + * register is accessed (raw accesses, r/w), and to be able to dump registers + * content (possible as non DDR only). + * In buffering mode, driver sets best possible mode, D/QSPI and DDR. + */ + +struct ad3552r_hs_state { + const struct ad3552r_model_data *model_data; + struct gpio_desc *reset_gpio; + struct device *dev; + struct iio_backend *back; + bool single_channel; + struct ad3552r_ch_data ch_data[AD3552R_MAX_CH]; + struct ad3552r_hs_platform_data *data; + /* INTERFACE_CONFIG_D register cache, in DDR we cannot read values. */ + u32 config_d; + /* Protects backend I/O operations from concurrent accesses. */ + struct mutex lock; +}; + +enum ad3552r_sources { + AD3552R_SRC_NORMAL, + AD3552R_SRC_RAMP_16BIT, +}; + +static const char * const dbgfs_attr_source[] = { + [AD3552R_SRC_NORMAL] = "normal", + [AD3552R_SRC_RAMP_16BIT] = "ramp-16bit", +}; + +static int ad3552r_hs_reg_read(struct ad3552r_hs_state *st, u32 reg, u32 *val, + size_t xfer_size) +{ + /* No chip in the family supports DDR read. Informing of this. */ + WARN_ON_ONCE(st->config_d & AD3552R_MASK_SPI_CONFIG_DDR); + + return st->data->bus_reg_read(st->back, reg, val, xfer_size); +} + +static int ad3552r_hs_set_data_source(struct ad3552r_hs_state *st, + enum iio_backend_data_source type) +{ + int i, ret; + + for (i = 0; i < st->model_data->num_hw_channels; ++i) { + ret = iio_backend_data_source_set(st->back, i, type); + if (ret) + return ret; + } + + return 0; +} + +static int ad3552r_hs_update_reg_bits(struct ad3552r_hs_state *st, u32 reg, + u32 mask, u32 val, size_t xfer_size) +{ + u32 rval; + int ret; + + ret = ad3552r_hs_reg_read(st, reg, &rval, xfer_size); + if (ret) + return ret; + + rval = (rval & ~mask) | val; + + return st->data->bus_reg_write(st->back, reg, rval, xfer_size); +} + +static int ad3552r_hs_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ad3552r_hs_state *st = iio_priv(indio_dev); + int ret; + int ch = chan->channel; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + /* + * Using a "num_spi_data_lanes" variable since ad3541/2 have + * only DSPI interface, while ad355x is QSPI. Then using 2 as + * DDR mode is considered always on (considering buffering + * mode always). + */ + *val = DIV_ROUND_CLOSEST(st->data->bus_sample_data_clock_hz * + st->model_data->num_spi_data_lanes * 2, + chan->scan_type.realbits); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_RAW: + /* For RAW accesses, stay always in simple-spi. */ + ret = ad3552r_hs_reg_read(st, + AD3552R_REG_ADDR_CH_DAC_16B(chan->channel), + val, 2); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = st->ch_data[ch].scale_int; + *val2 = st->ch_data[ch].scale_dec; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OFFSET: + *val = st->ch_data[ch].offset_int; + *val2 = st->ch_data[ch].offset_dec; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int ad3552r_hs_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ad3552r_hs_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + /* For RAW accesses, stay always in simple-spi. */ + ret = st->data->bus_reg_write(st->back, + AD3552R_REG_ADDR_CH_DAC_16B(chan->channel), + val, 2); + + iio_device_release_direct(indio_dev); + return ret; + default: + return -EINVAL; + } +} + +static int ad3552r_hs_set_bus_io_mode_hs(struct ad3552r_hs_state *st) +{ + int bus_mode; + + if (st->model_data->num_spi_data_lanes == 4) + bus_mode = AD3552R_IO_MODE_QSPI; + else + bus_mode = AD3552R_IO_MODE_DSPI; + + return st->data->bus_set_io_mode(st->back, bus_mode); +} + +static int ad3552r_hs_set_target_io_mode_hs(struct ad3552r_hs_state *st) +{ + u32 mode_target; + + /* + * Best access for secondary reg area, QSPI where possible, + * else as DSPI. + */ + if (st->model_data->num_spi_data_lanes == 4) + mode_target = AD3552R_QUAD_SPI; + else + mode_target = AD3552R_DUAL_SPI; + + /* + * Better to not use update here, since generally it is already + * set as DDR mode, and it's not possible to read in DDR mode. + */ + return st->data->bus_reg_write(st->back, + AD3552R_REG_ADDR_TRANSFER_REGISTER, + FIELD_PREP(AD3552R_MASK_MULTI_IO_MODE, + mode_target) | + AD3552R_MASK_STREAM_LENGTH_KEEP_VALUE, 1); +} + +static int ad3552r_hs_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad3552r_hs_state *st = iio_priv(indio_dev); + struct iio_backend_data_fmt fmt = { + .type = IIO_BACKEND_DATA_UNSIGNED + }; + int loop_len, val, ret; + + switch (*indio_dev->active_scan_mask) { + case AD3552R_CH0_ACTIVE: + st->single_channel = true; + loop_len = 2; + val = AD3552R_REG_ADDR_CH_DAC_16B(0); + break; + case AD3552R_CH1_ACTIVE: + st->single_channel = true; + loop_len = 2; + val = AD3552R_REG_ADDR_CH_DAC_16B(1); + break; + case AD3552R_CH0_ACTIVE | AD3552R_CH1_ACTIVE: + st->single_channel = false; + loop_len = 4; + val = AD3552R_REG_ADDR_CH_DAC_16B(1); + break; + default: + return -EINVAL; + } + + /* + * With ad3541/2r support, QSPI pin is held low at reset from HDL, + * streaming start sequence must respect strictly the order below. + */ + + /* Primary region access, set streaming mode (now in SPI + SDR). */ + ret = ad3552r_hs_update_reg_bits(st, + AD3552R_REG_ADDR_INTERFACE_CONFIG_B, + AD3552R_MASK_SINGLE_INST, 0, 1); + if (ret) + return ret; + + /* + * Set target loop len, keeping the value: streaming writes at address + * 0x2c or 0x2a, in descending loop (2 or 4 bytes), keeping loop len + * value so that it's not cleared hereafter when _CS is deasserted. + */ + ret = ad3552r_hs_update_reg_bits(st, AD3552R_REG_ADDR_TRANSFER_REGISTER, + AD3552R_MASK_STREAM_LENGTH_KEEP_VALUE, + AD3552R_MASK_STREAM_LENGTH_KEEP_VALUE, + 1); + if (ret) + goto exit_err_streaming; + + ret = st->data->bus_reg_write(st->back, + AD3552R_REG_ADDR_STREAM_MODE, + loop_len, 1); + if (ret) + goto exit_err_streaming; + + st->config_d |= AD3552R_MASK_SPI_CONFIG_DDR; + ret = st->data->bus_reg_write(st->back, + AD3552R_REG_ADDR_INTERFACE_CONFIG_D, + st->config_d, 1); + if (ret) + goto exit_err_streaming; + + ret = iio_backend_ddr_enable(st->back); + if (ret) + goto exit_err_ddr_mode_target; + + /* + * From here onward mode is DDR, so reading any register is not possible + * anymore, including calling "ad3552r_hs_update_reg_bits" function. + */ + + /* Set target to best high speed mode (D or QSPI). */ + ret = ad3552r_hs_set_target_io_mode_hs(st); + if (ret) + goto exit_err_ddr_mode; + + /* Set bus to best high speed mode (D or QSPI). */ + ret = ad3552r_hs_set_bus_io_mode_hs(st); + if (ret) + goto exit_err_bus_mode_target; + + /* + * Backend setup must be done now only, or related register values will + * be disrupted by previous bus accesses. + */ + ret = iio_backend_data_transfer_addr(st->back, val); + if (ret) + goto exit_err_bus_mode_target; + + ret = iio_backend_data_format_set(st->back, 0, &fmt); + if (ret) + goto exit_err_bus_mode_target; + + ret = iio_backend_data_stream_enable(st->back); + if (ret) + goto exit_err_bus_mode_target; + + return 0; + +exit_err_bus_mode_target: + /* Back to simple SPI, not using update to avoid read. */ + st->data->bus_reg_write(st->back, AD3552R_REG_ADDR_TRANSFER_REGISTER, + FIELD_PREP(AD3552R_MASK_MULTI_IO_MODE, + AD3552R_SPI) | + AD3552R_MASK_STREAM_LENGTH_KEEP_VALUE, 1); + + /* + * Back bus to simple SPI, this must be executed together with above + * target mode unwind, and can be done only after it. + */ + st->data->bus_set_io_mode(st->back, AD3552R_IO_MODE_SPI); + +exit_err_ddr_mode: + iio_backend_ddr_disable(st->back); + +exit_err_ddr_mode_target: + /* + * Back to SDR. In DDR we cannot read, whatever the mode is, so not + * using update. + */ + st->config_d &= ~AD3552R_MASK_SPI_CONFIG_DDR; + st->data->bus_reg_write(st->back, AD3552R_REG_ADDR_INTERFACE_CONFIG_D, + st->config_d, 1); + +exit_err_streaming: + /* Back to single instruction mode, disabling loop. */ + st->data->bus_reg_write(st->back, AD3552R_REG_ADDR_INTERFACE_CONFIG_B, + AD3552R_MASK_SINGLE_INST | + AD3552R_MASK_SHORT_INSTRUCTION, 1); + + return ret; +} + +static int ad3552r_hs_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad3552r_hs_state *st = iio_priv(indio_dev); + int ret; + + ret = iio_backend_data_stream_disable(st->back); + if (ret) + return ret; + + /* + * Set us to simple SPI, even if still in ddr, so to be able to write + * in primary region. + */ + ret = st->data->bus_set_io_mode(st->back, AD3552R_IO_MODE_SPI); + if (ret) + return ret; + + /* + * Back to SDR (in DDR we cannot read, whatever the mode is, so not + * using update). + */ + st->config_d &= ~AD3552R_MASK_SPI_CONFIG_DDR; + ret = st->data->bus_reg_write(st->back, + AD3552R_REG_ADDR_INTERFACE_CONFIG_D, + st->config_d, 1); + if (ret) + return ret; + + ret = iio_backend_ddr_disable(st->back); + if (ret) + return ret; + + /* + * Back to simple SPI for secondary region too now, so to be able to + * dump/read registers there too if needed. + */ + ret = ad3552r_hs_update_reg_bits(st, AD3552R_REG_ADDR_TRANSFER_REGISTER, + AD3552R_MASK_MULTI_IO_MODE, + AD3552R_SPI, 1); + if (ret) + return ret; + + /* Back to single instruction mode, disabling loop. */ + ret = ad3552r_hs_update_reg_bits(st, + AD3552R_REG_ADDR_INTERFACE_CONFIG_B, + AD3552R_MASK_SINGLE_INST, + AD3552R_MASK_SINGLE_INST, 1); + if (ret) + return ret; + + return 0; +} + +static inline int ad3552r_hs_set_output_range(struct ad3552r_hs_state *st, + int ch, unsigned int mode) +{ + int val; + + if (ch == 0) + val = FIELD_PREP(AD3552R_MASK_CH0_RANGE, mode); + else + val = FIELD_PREP(AD3552R_MASK_CH1_RANGE, mode); + + return ad3552r_hs_update_reg_bits(st, + AD3552R_REG_ADDR_CH0_CH1_OUTPUT_RANGE, + AD3552R_MASK_CH_OUTPUT_RANGE_SEL(ch), + val, 1); +} + +static int ad3552r_hs_reset(struct ad3552r_hs_state *st) +{ + int ret; + + st->reset_gpio = devm_gpiod_get_optional(st->dev, + "reset", GPIOD_OUT_HIGH); + if (IS_ERR(st->reset_gpio)) + return PTR_ERR(st->reset_gpio); + + if (st->reset_gpio) { + fsleep(10); + gpiod_set_value_cansleep(st->reset_gpio, 0); + } else { + ret = ad3552r_hs_update_reg_bits(st, + AD3552R_REG_ADDR_INTERFACE_CONFIG_A, + AD3552R_MASK_SOFTWARE_RESET, + AD3552R_MASK_SOFTWARE_RESET, 1); + if (ret) + return ret; + } + msleep(100); + + return 0; +} + +static int ad3552r_hs_scratch_pad_test(struct ad3552r_hs_state *st) +{ + int ret, val; + + ret = st->data->bus_reg_write(st->back, AD3552R_REG_ADDR_SCRATCH_PAD, + AD3552R_SCRATCH_PAD_TEST_VAL1, 1); + if (ret) + return ret; + + ret = st->data->bus_reg_read(st->back, AD3552R_REG_ADDR_SCRATCH_PAD, + &val, 1); + if (ret) + return ret; + + if (val != AD3552R_SCRATCH_PAD_TEST_VAL1) + return dev_err_probe(st->dev, -EIO, + "SCRATCH_PAD_TEST mismatch. Expected 0x%x, Read 0x%x\n", + AD3552R_SCRATCH_PAD_TEST_VAL1, val); + + ret = st->data->bus_reg_write(st->back, AD3552R_REG_ADDR_SCRATCH_PAD, + AD3552R_SCRATCH_PAD_TEST_VAL2, 1); + if (ret) + return ret; + + ret = st->data->bus_reg_read(st->back, AD3552R_REG_ADDR_SCRATCH_PAD, + &val, 1); + if (ret) + return ret; + + if (val != AD3552R_SCRATCH_PAD_TEST_VAL2) + return dev_err_probe(st->dev, -EIO, + "SCRATCH_PAD_TEST mismatch. Expected 0x%x, Read 0x%x\n", + AD3552R_SCRATCH_PAD_TEST_VAL2, val); + + return 0; +} + +static int ad3552r_hs_setup_custom_gain(struct ad3552r_hs_state *st, + int ch, u16 gain, u16 offset) +{ + int ret; + + ret = st->data->bus_reg_write(st->back, AD3552R_REG_ADDR_CH_OFFSET(ch), + offset, 1); + if (ret) + return ret; + + return st->data->bus_reg_write(st->back, AD3552R_REG_ADDR_CH_GAIN(ch), + gain, 1); +} + +static int ad3552r_hs_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct ad3552r_hs_state *st = iio_priv(indio_dev); + + if (reg > st->model_data->max_reg_addr) + return -EINVAL; + + /* + * There are 8, 16 or 24 bit registers, but HDL supports only reading 8 + * or 16 bit data, not 24. So, also to avoid to check any proper read + * alignment, supporting only 8-bit readings here. + */ + if (readval) + return ad3552r_hs_reg_read(st, reg, readval, 1); + + return st->data->bus_reg_write(st->back, reg, writeval, 1); +} + +static ssize_t ad3552r_hs_show_data_source(struct file *f, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct ad3552r_hs_state *st = file_inode(f)->i_private; + enum iio_backend_data_source type; + int idx, ret; + + guard(mutex)(&st->lock); + + ret = iio_backend_data_source_get(st->back, 0, &type); + if (ret) + return ret; + + switch (type) { + case IIO_BACKEND_INTERNAL_RAMP_16BIT: + idx = AD3552R_SRC_RAMP_16BIT; + break; + case IIO_BACKEND_EXTERNAL: + idx = AD3552R_SRC_NORMAL; + break; + default: + return -EINVAL; + } + + return simple_read_from_buffer(userbuf, count, ppos, + dbgfs_attr_source[idx], + strlen(dbgfs_attr_source[idx])); +} + +static ssize_t ad3552r_hs_write_data_source(struct file *f, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct ad3552r_hs_state *st = file_inode(f)->i_private; + char buf[64]; + int ret, source; + + guard(mutex)(&st->lock); + + if (*ppos != 0 || count >= sizeof(buf)) + return -ENOSPC; + + ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, userbuf, + count); + if (ret < 0) + return ret; + + buf[ret] = '\0'; + + ret = match_string(dbgfs_attr_source, ARRAY_SIZE(dbgfs_attr_source), + buf); + if (ret < 0) + return ret; + + switch (ret) { + case AD3552R_SRC_RAMP_16BIT: + source = IIO_BACKEND_INTERNAL_RAMP_16BIT; + break; + case AD3552R_SRC_NORMAL: + source = IIO_BACKEND_EXTERNAL; + break; + default: + return -EINVAL; + } + + ret = ad3552r_hs_set_data_source(st, source); + if (ret) + return ret; + + return count; +} + +static ssize_t ad3552r_hs_show_data_source_avail(struct file *f, + char __user *userbuf, + size_t count, loff_t *ppos) +{ + ssize_t len = 0; + char buf[128]; + int i; + + for (i = 0; i < ARRAY_SIZE(dbgfs_attr_source); i++) { + len += scnprintf(buf + len, sizeof(buf) - len, "%s ", + dbgfs_attr_source[i]); + } + buf[len - 1] = '\n'; + + return simple_read_from_buffer(userbuf, count, ppos, buf, len); +} + +static const struct file_operations ad3552r_hs_data_source_fops = { + .owner = THIS_MODULE, + .write = ad3552r_hs_write_data_source, + .read = ad3552r_hs_show_data_source, +}; + +static const struct file_operations ad3552r_hs_data_source_avail_fops = { + .owner = THIS_MODULE, + .read = ad3552r_hs_show_data_source_avail, +}; + +static int ad3552r_hs_setup(struct ad3552r_hs_state *st) +{ + u16 id; + u16 gain = 0, offset = 0; + u32 ch, val, range; + int ret; + + ret = ad3552r_hs_reset(st); + if (ret) + return ret; + + /* HDL starts with DDR enabled, disabling it. */ + ret = iio_backend_ddr_disable(st->back); + if (ret) + return ret; + + ret = st->data->bus_reg_write(st->back, + AD3552R_REG_ADDR_INTERFACE_CONFIG_B, + AD3552R_MASK_SINGLE_INST | + AD3552R_MASK_SHORT_INSTRUCTION, 1); + if (ret) + return ret; + + ret = ad3552r_hs_scratch_pad_test(st); + if (ret) + return ret; + + /* + * Caching config_d, needed to restore it after streaming, + * and also, to detect possible DDR read, that's not allowed. + */ + ret = st->data->bus_reg_read(st->back, + AD3552R_REG_ADDR_INTERFACE_CONFIG_D, + &st->config_d, 1); + if (ret) + return ret; + + ret = ad3552r_hs_reg_read(st, AD3552R_REG_ADDR_PRODUCT_ID_L, &val, 1); + if (ret) + return ret; + + id = val; + + ret = ad3552r_hs_reg_read(st, AD3552R_REG_ADDR_PRODUCT_ID_H, &val, 1); + if (ret) + return ret; + + id |= val << 8; + if (id != st->model_data->chip_id) + dev_warn(st->dev, + "chip ID mismatch, detected 0x%x but expected 0x%x\n", + id, st->model_data->chip_id); + + dev_dbg(st->dev, "chip id %s detected", st->model_data->model_name); + + /* Clear reset error flag, see ad3552r manual, rev B table 38. */ + ret = st->data->bus_reg_write(st->back, AD3552R_REG_ADDR_ERR_STATUS, + AD3552R_MASK_RESET_STATUS, 1); + if (ret) + return ret; + + ret = st->data->bus_reg_write(st->back, + AD3552R_REG_ADDR_SH_REFERENCE_CONFIG, + 0, 1); + if (ret) + return ret; + + ret = ad3552r_hs_set_data_source(st, IIO_BACKEND_EXTERNAL); + if (ret) + return ret; + + ret = ad3552r_get_ref_voltage(st->dev, &val); + if (ret < 0) + return ret; + + val = ret; + + ret = ad3552r_hs_update_reg_bits(st, + AD3552R_REG_ADDR_SH_REFERENCE_CONFIG, + AD3552R_MASK_REFERENCE_VOLTAGE_SEL, + val, 1); + if (ret) + return ret; + + ret = ad3552r_get_drive_strength(st->dev, &val); + if (!ret) { + st->config_d |= + FIELD_PREP(AD3552R_MASK_SDO_DRIVE_STRENGTH, val); + + ret = st->data->bus_reg_write(st->back, + AD3552R_REG_ADDR_INTERFACE_CONFIG_D, + st->config_d, 1); + if (ret) + return ret; + } + + device_for_each_child_node_scoped(st->dev, child) { + ret = fwnode_property_read_u32(child, "reg", &ch); + if (ret) + return dev_err_probe(st->dev, ret, + "reg property missing\n"); + + ret = ad3552r_get_output_range(st->dev, st->model_data, child, + &range); + if (ret && ret != -ENOENT) + return ret; + if (ret == -ENOENT) { + ret = ad3552r_get_custom_gain(st->dev, child, + &st->ch_data[ch].p, + &st->ch_data[ch].n, + &st->ch_data[ch].rfb, + &st->ch_data[ch].gain_offset); + if (ret) + return ret; + + gain = ad3552r_calc_custom_gain(st->ch_data[ch].p, + st->ch_data[ch].n, + st->ch_data[ch].gain_offset); + offset = abs(st->ch_data[ch].gain_offset); + + st->ch_data[ch].range_override = 1; + + ret = ad3552r_hs_setup_custom_gain(st, ch, gain, + offset); + if (ret) + return ret; + } else { + st->ch_data[ch].range = range; + + ret = ad3552r_hs_set_output_range(st, ch, range); + if (ret) + return ret; + } + + ad3552r_calc_gain_and_offset(&st->ch_data[ch], st->model_data); + } + + return 0; +} + +static const struct iio_buffer_setup_ops ad3552r_hs_buffer_setup_ops = { + .postenable = ad3552r_hs_buffer_postenable, + .predisable = ad3552r_hs_buffer_predisable, +}; + +#define AD3552R_CHANNEL(ch) { \ + .type = IIO_VOLTAGE, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .output = 1, \ + .indexed = 1, \ + .channel = (ch), \ + .scan_index = (ch), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + } \ +} + +static const struct iio_chan_spec ad3552r_hs_channels[] = { + AD3552R_CHANNEL(0), + AD3552R_CHANNEL(1), +}; + +static const struct iio_info ad3552r_hs_info = { + .read_raw = &ad3552r_hs_read_raw, + .write_raw = &ad3552r_hs_write_raw, + .debugfs_reg_access = &ad3552r_hs_reg_access, +}; + +static void ad3552r_hs_debugfs_init(struct iio_dev *indio_dev) +{ + struct ad3552r_hs_state *st = iio_priv(indio_dev); + struct dentry *d = iio_get_debugfs_dentry(indio_dev); + + if (!IS_ENABLED(CONFIG_DEBUG_FS)) + return; + + d = iio_get_debugfs_dentry(indio_dev); + if (!d) { + dev_warn(st->dev, "can't set debugfs in driver dir\n"); + return; + } + + debugfs_create_file("data_source", 0600, d, st, + &ad3552r_hs_data_source_fops); + debugfs_create_file("data_source_available", 0600, d, st, + &ad3552r_hs_data_source_avail_fops); +} + +static int ad3552r_hs_probe(struct platform_device *pdev) +{ + struct ad3552r_hs_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->dev = &pdev->dev; + + st->data = dev_get_platdata(st->dev); + if (!st->data) + return dev_err_probe(st->dev, -ENODEV, "No platform data !"); + + st->back = devm_iio_backend_get(&pdev->dev, NULL); + if (IS_ERR(st->back)) + return PTR_ERR(st->back); + + ret = devm_iio_backend_enable(&pdev->dev, st->back); + if (ret) + return ret; + + st->model_data = device_get_match_data(&pdev->dev); + if (!st->model_data) + return -ENODEV; + + indio_dev->name = "ad3552r"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->setup_ops = &ad3552r_hs_buffer_setup_ops; + indio_dev->channels = ad3552r_hs_channels; + indio_dev->num_channels = ARRAY_SIZE(ad3552r_hs_channels); + indio_dev->info = &ad3552r_hs_info; + + ret = devm_iio_backend_request_buffer(&pdev->dev, st->back, indio_dev); + if (ret) + return ret; + + ret = ad3552r_hs_setup(st); + if (ret) + return ret; + + ret = devm_iio_device_register(&pdev->dev, indio_dev); + if (ret) + return ret; + + ret = devm_mutex_init(&pdev->dev, &st->lock); + if (ret) + return ret; + + ad3552r_hs_debugfs_init(indio_dev); + + return ret; +} + +static const struct of_device_id ad3552r_hs_of_id[] = { + { .compatible = "adi,ad3541r", .data = &ad3541r_model_data }, + { .compatible = "adi,ad3542r", .data = &ad3542r_model_data }, + { .compatible = "adi,ad3551r", .data = &ad3551r_model_data }, + { .compatible = "adi,ad3552r", .data = &ad3552r_model_data }, + { } +}; +MODULE_DEVICE_TABLE(of, ad3552r_hs_of_id); + +static struct platform_driver ad3552r_hs_driver = { + .driver = { + .name = "ad3552r-hs", + .of_match_table = ad3552r_hs_of_id, + }, + .probe = ad3552r_hs_probe, +}; +module_platform_driver(ad3552r_hs_driver); + +MODULE_AUTHOR("Dragos Bogdan "); +MODULE_AUTHOR("Angelo Dureghello "); +MODULE_DESCRIPTION("AD3552R Driver - High Speed version"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_BACKEND"); +MODULE_IMPORT_NS("IIO_AD3552R"); diff --git a/drivers/iio/dac/ad3552r-hs.h b/drivers/iio/dac/ad3552r-hs.h new file mode 100644 index 000000000..4a9e35234 --- /dev/null +++ b/drivers/iio/dac/ad3552r-hs.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2024 Analog Devices Inc. + * Copyright (c) 2024 Baylibre, SAS + */ +#ifndef __LINUX_PLATFORM_DATA_AD3552R_HS_H__ +#define __LINUX_PLATFORM_DATA_AD3552R_HS_H__ + +struct iio_backend; + +enum ad3552r_io_mode { + AD3552R_IO_MODE_SPI, + AD3552R_IO_MODE_DSPI, + AD3552R_IO_MODE_QSPI, +}; + +struct ad3552r_hs_platform_data { + int (*bus_reg_read)(struct iio_backend *back, u32 reg, u32 *val, + size_t data_size); + int (*bus_reg_write)(struct iio_backend *back, u32 reg, u32 val, + size_t data_size); + int (*bus_set_io_mode)(struct iio_backend *back, + enum ad3552r_io_mode mode); + u32 bus_sample_data_clock_hz; +}; + +#endif /* __LINUX_PLATFORM_DATA_AD3552R_HS_H__ */ diff --git a/drivers/iio/dac/ad3552r.c b/drivers/iio/dac/ad3552r.c new file mode 100644 index 000000000..16db94fef --- /dev/null +++ b/drivers/iio/dac/ad3552r.c @@ -0,0 +1,701 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices AD3552R + * Digital to Analog converter driver + * + * Copyright 2021 Analog Devices Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ad3552r.h" + +struct ad3552r_desc { + const struct ad3552r_model_data *model_data; + /* Used to look the spi bus for atomic operations where needed */ + struct mutex lock; + struct gpio_desc *gpio_reset; + struct gpio_desc *gpio_ldac; + struct spi_device *spi; + struct ad3552r_ch_data ch_data[AD3552R_MAX_CH]; + struct iio_chan_spec channels[AD3552R_MAX_CH + 1]; + unsigned long enabled_ch; + unsigned int num_ch; +}; + +static u8 _ad3552r_reg_len(u8 addr) +{ + switch (addr) { + case AD3552R_REG_ADDR_HW_LDAC_16B: + case AD3552R_REG_ADDR_CH_SELECT_16B: + case AD3552R_REG_ADDR_SW_LDAC_16B: + case AD3552R_REG_ADDR_HW_LDAC_24B: + case AD3552R_REG_ADDR_CH_SELECT_24B: + case AD3552R_REG_ADDR_SW_LDAC_24B: + return 1; + default: + break; + } + + if (addr > AD3552R_REG_ADDR_HW_LDAC_24B) + return 3; + if (addr > AD3552R_REG_ADDR_HW_LDAC_16B) + return 2; + + return 1; +} + +/* SPI transfer to device */ +static int ad3552r_transfer(struct ad3552r_desc *dac, u8 addr, u32 len, + u8 *data, bool is_read) +{ + /* Maximum transfer: Addr (1B) + 2 * (Data Reg (3B)) + SW LDAC(1B) */ + u8 buf[8]; + + buf[0] = addr & AD3552R_ADDR_MASK; + buf[0] |= is_read ? AD3552R_READ_BIT : 0; + if (is_read) + return spi_write_then_read(dac->spi, buf, 1, data, len); + + memcpy(buf + 1, data, len); + return spi_write_then_read(dac->spi, buf, len + 1, NULL, 0); +} + +static int ad3552r_write_reg(struct ad3552r_desc *dac, u8 addr, u16 val) +{ + u8 reg_len; + u8 buf[AD3552R_MAX_REG_SIZE] = { 0 }; + + reg_len = _ad3552r_reg_len(addr); + if (reg_len == 2) + /* Only DAC register are 2 bytes wide */ + val &= AD3552R_MASK_DAC_12B; + if (reg_len == 1) + buf[0] = val & 0xFF; + else + /* reg_len can be 2 or 3, but 3rd bytes needs to be set to 0 */ + put_unaligned_be16(val, buf); + + return ad3552r_transfer(dac, addr, reg_len, buf, false); +} + +static int ad3552r_read_reg(struct ad3552r_desc *dac, u8 addr, u16 *val) +{ + int err; + u8 reg_len, buf[AD3552R_MAX_REG_SIZE] = { 0 }; + + reg_len = _ad3552r_reg_len(addr); + err = ad3552r_transfer(dac, addr, reg_len, buf, true); + if (err) + return err; + + if (reg_len == 1) + *val = buf[0]; + else + /* reg_len can be 2 or 3, but only first 2 bytes are relevant */ + *val = get_unaligned_be16(buf); + + return 0; +} + +/* Update field of a register, shift val if needed */ +static int ad3552r_update_reg_field(struct ad3552r_desc *dac, u8 addr, u16 mask, + u16 val) +{ + int ret; + u16 reg; + + ret = ad3552r_read_reg(dac, addr, ®); + if (ret < 0) + return ret; + + reg &= ~mask; + reg |= val; + + return ad3552r_write_reg(dac, addr, reg); +} + +#define AD3552R_CH_DAC(_idx) ((struct iio_chan_spec) { \ + .type = IIO_VOLTAGE, \ + .output = true, \ + .indexed = true, \ + .channel = _idx, \ + .scan_index = _idx, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_ENABLE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ +}) + +static int ad3552r_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct ad3552r_desc *dac = iio_priv(indio_dev); + u16 tmp_val; + int err; + u8 ch = chan->channel; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&dac->lock); + err = ad3552r_read_reg(dac, AD3552R_REG_ADDR_CH_DAC_24B(ch), + &tmp_val); + mutex_unlock(&dac->lock); + if (err < 0) + return err; + *val = tmp_val; + return IIO_VAL_INT; + case IIO_CHAN_INFO_ENABLE: + mutex_lock(&dac->lock); + err = ad3552r_read_reg(dac, AD3552R_REG_ADDR_POWERDOWN_CONFIG, + &tmp_val); + mutex_unlock(&dac->lock); + if (err < 0) + return err; + *val = !field_get(AD3552R_MASK_CH_DAC_POWERDOWN(ch), tmp_val); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = dac->ch_data[ch].scale_int; + *val2 = dac->ch_data[ch].scale_dec; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OFFSET: + *val = dac->ch_data[ch].offset_int; + *val2 = dac->ch_data[ch].offset_dec; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int ad3552r_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad3552r_desc *dac = iio_priv(indio_dev); + int err; + + mutex_lock(&dac->lock); + switch (mask) { + case IIO_CHAN_INFO_RAW: + err = ad3552r_write_reg(dac, + AD3552R_REG_ADDR_CH_DAC_24B(chan->channel), + val); + break; + case IIO_CHAN_INFO_ENABLE: + if (chan->channel == 0) + val = FIELD_PREP(AD3552R_MASK_CH_DAC_POWERDOWN(0), !val); + else + val = FIELD_PREP(AD3552R_MASK_CH_DAC_POWERDOWN(1), !val); + + err = ad3552r_update_reg_field(dac, AD3552R_REG_ADDR_POWERDOWN_CONFIG, + AD3552R_MASK_CH_DAC_POWERDOWN(chan->channel), + val); + break; + default: + err = -EINVAL; + break; + } + mutex_unlock(&dac->lock); + + return err; +} + +static const struct iio_info ad3552r_iio_info = { + .read_raw = ad3552r_read_raw, + .write_raw = ad3552r_write_raw +}; + +static int32_t ad3552r_trigger_hw_ldac(struct gpio_desc *ldac) +{ + gpiod_set_value_cansleep(ldac, 0); + usleep_range(AD3552R_LDAC_PULSE_US, AD3552R_LDAC_PULSE_US + 10); + gpiod_set_value_cansleep(ldac, 1); + + return 0; +} + +static int ad3552r_write_all_channels(struct ad3552r_desc *dac, u8 *data) +{ + int err, len; + u8 addr, buff[AD3552R_MAX_CH * AD3552R_MAX_REG_SIZE + 1]; + + addr = AD3552R_REG_ADDR_CH_INPUT_24B(1); + /* CH1 */ + memcpy(buff, data + 2, 2); + buff[2] = 0; + /* CH0 */ + memcpy(buff + 3, data, 2); + buff[5] = 0; + len = 6; + if (!dac->gpio_ldac) { + /* Software LDAC */ + buff[6] = AD3552R_MASK_ALL_CH; + ++len; + } + err = ad3552r_transfer(dac, addr, len, buff, false); + if (err) + return err; + + if (dac->gpio_ldac) + return ad3552r_trigger_hw_ldac(dac->gpio_ldac); + + return 0; +} + +static int ad3552r_write_codes(struct ad3552r_desc *dac, u32 mask, u8 *data) +{ + int err; + u8 addr, buff[AD3552R_MAX_REG_SIZE]; + + if (mask == AD3552R_MASK_ALL_CH) { + if (memcmp(data, data + 2, 2) != 0) + return ad3552r_write_all_channels(dac, data); + + addr = AD3552R_REG_ADDR_INPUT_PAGE_MASK_24B; + } else { + addr = AD3552R_REG_ADDR_CH_INPUT_24B(__ffs(mask)); + } + + memcpy(buff, data, 2); + buff[2] = 0; + err = ad3552r_transfer(dac, addr, 3, data, false); + if (err) + return err; + + if (dac->gpio_ldac) + return ad3552r_trigger_hw_ldac(dac->gpio_ldac); + + return ad3552r_write_reg(dac, AD3552R_REG_ADDR_SW_LDAC_24B, mask); +} + +static irqreturn_t ad3552r_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct iio_buffer *buf = indio_dev->buffer; + struct ad3552r_desc *dac = iio_priv(indio_dev); + /* Maximum size of a scan */ + u8 buff[AD3552R_MAX_CH * AD3552R_MAX_REG_SIZE] = { }; + int err; + + err = iio_pop_from_buffer(buf, buff); + if (err) + goto end; + + mutex_lock(&dac->lock); + ad3552r_write_codes(dac, *indio_dev->active_scan_mask, buff); + mutex_unlock(&dac->lock); +end: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ad3552r_check_scratch_pad(struct ad3552r_desc *dac) +{ + const u16 val1 = AD3552R_SCRATCH_PAD_TEST_VAL1; + const u16 val2 = AD3552R_SCRATCH_PAD_TEST_VAL2; + u16 val; + int err; + + err = ad3552r_write_reg(dac, AD3552R_REG_ADDR_SCRATCH_PAD, val1); + if (err < 0) + return err; + + err = ad3552r_read_reg(dac, AD3552R_REG_ADDR_SCRATCH_PAD, &val); + if (err < 0) + return err; + + if (val1 != val) + return -ENODEV; + + err = ad3552r_write_reg(dac, AD3552R_REG_ADDR_SCRATCH_PAD, val2); + if (err < 0) + return err; + + err = ad3552r_read_reg(dac, AD3552R_REG_ADDR_SCRATCH_PAD, &val); + if (err < 0) + return err; + + if (val2 != val) + return -ENODEV; + + return 0; +} + +struct reg_addr_pool { + struct ad3552r_desc *dac; + u8 addr; +}; + +static int ad3552r_read_reg_wrapper(struct reg_addr_pool *addr) +{ + int err; + u16 val; + + err = ad3552r_read_reg(addr->dac, addr->addr, &val); + if (err) + return err; + + return val; +} + +static int ad3552r_reset(struct ad3552r_desc *dac) +{ + struct reg_addr_pool addr; + int ret; + int val; + + dac->gpio_reset = devm_gpiod_get_optional(&dac->spi->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(dac->gpio_reset)) + return dev_err_probe(&dac->spi->dev, PTR_ERR(dac->gpio_reset), + "Error while getting gpio reset"); + + if (dac->gpio_reset) { + /* Perform hardware reset */ + usleep_range(10, 20); + gpiod_set_value_cansleep(dac->gpio_reset, 1); + } else { + /* Perform software reset if no GPIO provided */ + ret = ad3552r_update_reg_field(dac, + AD3552R_REG_ADDR_INTERFACE_CONFIG_A, + AD3552R_MASK_SOFTWARE_RESET, + AD3552R_MASK_SOFTWARE_RESET); + if (ret < 0) + return ret; + + } + + addr.dac = dac; + addr.addr = AD3552R_REG_ADDR_INTERFACE_CONFIG_B; + ret = readx_poll_timeout(ad3552r_read_reg_wrapper, &addr, val, + val == AD3552R_DEFAULT_CONFIG_B_VALUE || + val < 0, + 5000, 50000); + if (val < 0) + ret = val; + if (ret) { + dev_err(&dac->spi->dev, "Error while resetting"); + return ret; + } + + ret = readx_poll_timeout(ad3552r_read_reg_wrapper, &addr, val, + !(val & AD3552R_MASK_INTERFACE_NOT_READY) || + val < 0, + 5000, 50000); + if (val < 0) + ret = val; + if (ret) { + dev_err(&dac->spi->dev, "Error while resetting"); + return ret; + } + + /* Clear reset error flag, see ad3552r manual, rev B table 38. */ + ret = ad3552r_write_reg(dac, AD3552R_REG_ADDR_ERR_STATUS, + AD3552R_MASK_RESET_STATUS); + if (ret) + return ret; + + return ad3552r_update_reg_field(dac, + AD3552R_REG_ADDR_INTERFACE_CONFIG_A, + AD3552R_MASK_ADDR_ASCENSION, + FIELD_PREP(AD3552R_MASK_ADDR_ASCENSION, val)); +} + +static int ad3552r_configure_custom_gain(struct ad3552r_desc *dac, + struct fwnode_handle *child, + u32 ch) +{ + struct device *dev = &dac->spi->dev; + int err; + u8 addr; + u16 reg; + + err = ad3552r_get_custom_gain(dev, child, + &dac->ch_data[ch].p, + &dac->ch_data[ch].n, + &dac->ch_data[ch].rfb, + &dac->ch_data[ch].gain_offset); + if (err) + return err; + + dac->ch_data[ch].range_override = 1; + + addr = AD3552R_REG_ADDR_CH_GAIN(ch); + err = ad3552r_write_reg(dac, addr, + abs((s32)dac->ch_data[ch].gain_offset) & + AD3552R_MASK_CH_OFFSET_BITS_0_7); + if (err) + return dev_err_probe(dev, err, "Error writing register\n"); + + reg = ad3552r_calc_custom_gain(dac->ch_data[ch].p, dac->ch_data[ch].n, + dac->ch_data[ch].gain_offset); + + err = ad3552r_write_reg(dac, addr, reg); + if (err) + return dev_err_probe(dev, err, "Error writing register\n"); + + return 0; +} + +static int ad3552r_configure_device(struct ad3552r_desc *dac) +{ + struct device *dev = &dac->spi->dev; + int err, cnt = 0; + u32 val, ch; + + dac->gpio_ldac = devm_gpiod_get_optional(dev, "ldac", GPIOD_OUT_HIGH); + if (IS_ERR(dac->gpio_ldac)) + return dev_err_probe(dev, PTR_ERR(dac->gpio_ldac), + "Error getting gpio ldac"); + + err = ad3552r_get_ref_voltage(dev, &val); + if (err < 0) + return err; + + err = ad3552r_update_reg_field(dac, + AD3552R_REG_ADDR_SH_REFERENCE_CONFIG, + AD3552R_MASK_REFERENCE_VOLTAGE_SEL, + FIELD_PREP(AD3552R_MASK_REFERENCE_VOLTAGE_SEL, val)); + if (err) + return err; + + err = ad3552r_get_drive_strength(dev, &val); + if (!err) { + err = ad3552r_update_reg_field(dac, + AD3552R_REG_ADDR_INTERFACE_CONFIG_D, + AD3552R_MASK_SDO_DRIVE_STRENGTH, + FIELD_PREP(AD3552R_MASK_SDO_DRIVE_STRENGTH, val)); + if (err) + return err; + } + + dac->num_ch = device_get_child_node_count(dev); + if (!dac->num_ch) { + dev_err(dev, "No channels defined\n"); + return -ENODEV; + } + + device_for_each_child_node_scoped(dev, child) { + err = fwnode_property_read_u32(child, "reg", &ch); + if (err) + return dev_err_probe(dev, err, + "mandatory reg property missing\n"); + if (ch >= dac->model_data->num_hw_channels) + return dev_err_probe(dev, -EINVAL, + "reg must be less than %d\n", + dac->model_data->num_hw_channels); + + err = ad3552r_get_output_range(dev, dac->model_data, + child, &val); + if (err && err != -ENOENT) + return err; + + if (!err) { + if (ch == 0) + val = FIELD_PREP(AD3552R_MASK_CH_OUTPUT_RANGE_SEL(0), val); + else + val = FIELD_PREP(AD3552R_MASK_CH_OUTPUT_RANGE_SEL(1), val); + + err = ad3552r_update_reg_field(dac, + AD3552R_REG_ADDR_CH0_CH1_OUTPUT_RANGE, + AD3552R_MASK_CH_OUTPUT_RANGE_SEL(ch), + val); + if (err) + return err; + + dac->ch_data[ch].range = val; + } else if (dac->model_data->requires_output_range) { + return dev_err_probe(dev, -EINVAL, + "adi,output-range-microvolt is required for %s\n", + dac->model_data->model_name); + } else { + err = ad3552r_configure_custom_gain(dac, child, ch); + if (err) + return err; + } + + ad3552r_calc_gain_and_offset(&dac->ch_data[ch], dac->model_data); + dac->enabled_ch |= BIT(ch); + + if (ch == 0) + val = FIELD_PREP(AD3552R_MASK_CH(0), 1); + else + val = FIELD_PREP(AD3552R_MASK_CH(1), 1); + + err = ad3552r_update_reg_field(dac, + AD3552R_REG_ADDR_CH_SELECT_16B, + AD3552R_MASK_CH(ch), val); + if (err < 0) + return err; + + dac->channels[cnt] = AD3552R_CH_DAC(ch); + ++cnt; + + } + + /* Disable unused channels */ + for_each_clear_bit(ch, &dac->enabled_ch, + dac->model_data->num_hw_channels) { + if (ch == 0) + val = FIELD_PREP(AD3552R_MASK_CH_AMPLIFIER_POWERDOWN(0), 1); + else + val = FIELD_PREP(AD3552R_MASK_CH_AMPLIFIER_POWERDOWN(1), 1); + + err = ad3552r_update_reg_field(dac, + AD3552R_REG_ADDR_POWERDOWN_CONFIG, + AD3552R_MASK_CH_AMPLIFIER_POWERDOWN(ch), + val); + if (err) + return err; + } + + dac->num_ch = cnt; + + return 0; +} + +static int ad3552r_init(struct ad3552r_desc *dac) +{ + int err; + u16 val, id; + + err = ad3552r_reset(dac); + if (err) { + dev_err(&dac->spi->dev, "Reset failed\n"); + return err; + } + + err = ad3552r_check_scratch_pad(dac); + if (err) { + dev_err(&dac->spi->dev, "Scratch pad test failed\n"); + return err; + } + + err = ad3552r_read_reg(dac, AD3552R_REG_ADDR_PRODUCT_ID_L, &val); + if (err) { + dev_err(&dac->spi->dev, "Fail read PRODUCT_ID_L\n"); + return err; + } + + id = val; + err = ad3552r_read_reg(dac, AD3552R_REG_ADDR_PRODUCT_ID_H, &val); + if (err) { + dev_err(&dac->spi->dev, "Fail read PRODUCT_ID_H\n"); + return err; + } + + id |= val << 8; + if (id != dac->model_data->chip_id) { + dev_err(&dac->spi->dev, "Product id not matching\n"); + return -ENODEV; + } + + return ad3552r_configure_device(dac); +} + +static int ad3552r_probe(struct spi_device *spi) +{ + struct ad3552r_desc *dac; + struct iio_dev *indio_dev; + int err; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*dac)); + if (!indio_dev) + return -ENOMEM; + + dac = iio_priv(indio_dev); + dac->spi = spi; + dac->model_data = spi_get_device_match_data(spi); + if (!dac->model_data) + return -EINVAL; + + err = devm_mutex_init(&spi->dev, &dac->lock); + if (err) + return err; + + err = ad3552r_init(dac); + if (err) + return err; + + /* Config triggered buffer device */ + indio_dev->name = dac->model_data->model_name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &ad3552r_iio_info; + indio_dev->num_channels = dac->num_ch; + indio_dev->channels = dac->channels; + indio_dev->modes = INDIO_DIRECT_MODE; + + err = devm_iio_triggered_buffer_setup_ext(&indio_dev->dev, indio_dev, NULL, + &ad3552r_trigger_handler, + IIO_BUFFER_DIRECTION_OUT, + NULL, + NULL); + if (err) + return err; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id ad3552r_id[] = { + { + .name = "ad3541r", + .driver_data = (kernel_ulong_t)&ad3541r_model_data + }, + { + .name = "ad3542r", + .driver_data = (kernel_ulong_t)&ad3542r_model_data + }, + { + .name = "ad3551r", + .driver_data = (kernel_ulong_t)&ad3551r_model_data + }, + { + .name = "ad3552r", + .driver_data = (kernel_ulong_t)&ad3552r_model_data + }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad3552r_id); + +static const struct of_device_id ad3552r_of_match[] = { + { .compatible = "adi,ad3541r", .data = &ad3541r_model_data }, + { .compatible = "adi,ad3542r", .data = &ad3542r_model_data }, + { .compatible = "adi,ad3551r", .data = &ad3551r_model_data }, + { .compatible = "adi,ad3552r", .data = &ad3552r_model_data }, + { } +}; +MODULE_DEVICE_TABLE(of, ad3552r_of_match); + +static struct spi_driver ad3552r_driver = { + .driver = { + .name = "ad3552r", + .of_match_table = ad3552r_of_match, + }, + .probe = ad3552r_probe, + .id_table = ad3552r_id +}; +module_spi_driver(ad3552r_driver); + +MODULE_AUTHOR("Mihail Chindris "); +MODULE_DESCRIPTION("Analog Device AD3552R DAC"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_AD3552R"); diff --git a/drivers/iio/dac/ad3552r.h b/drivers/iio/dac/ad3552r.h new file mode 100644 index 000000000..9bb46a9e0 --- /dev/null +++ b/drivers/iio/dac/ad3552r.h @@ -0,0 +1,232 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AD3552R Digital <-> Analog converters common header + * + * Copyright 2021-2024 Analog Devices Inc. + * Author: Angelo Dureghello + */ + +#ifndef __DRIVERS_IIO_DAC_AD3552R_H__ +#define __DRIVERS_IIO_DAC_AD3552R_H__ + +/* Register addresses */ +/* Primary address space */ +#define AD3552R_REG_ADDR_INTERFACE_CONFIG_A 0x00 +#define AD3552R_MASK_SOFTWARE_RESET (BIT(7) | BIT(0)) +#define AD3552R_MASK_ADDR_ASCENSION BIT(5) +#define AD3552R_MASK_SDO_ACTIVE BIT(4) +#define AD3552R_REG_ADDR_INTERFACE_CONFIG_B 0x01 +#define AD3552R_MASK_SINGLE_INST BIT(7) +#define AD3552R_MASK_SHORT_INSTRUCTION BIT(3) +#define AD3552R_REG_ADDR_DEVICE_CONFIG 0x02 +#define AD3552R_MASK_DEVICE_STATUS(n) BIT(4 + (n)) +#define AD3552R_MASK_CUSTOM_MODES GENMASK(3, 2) +#define AD3552R_MASK_OPERATING_MODES GENMASK(1, 0) +#define AD3552R_REG_ADDR_CHIP_TYPE 0x03 +#define AD3552R_MASK_CLASS GENMASK(7, 0) +#define AD3552R_REG_ADDR_PRODUCT_ID_L 0x04 +#define AD3552R_REG_ADDR_PRODUCT_ID_H 0x05 +#define AD3552R_REG_ADDR_CHIP_GRADE 0x06 +#define AD3552R_MASK_GRADE GENMASK(7, 4) +#define AD3552R_MASK_DEVICE_REVISION GENMASK(3, 0) +#define AD3552R_REG_ADDR_SCRATCH_PAD 0x0A +#define AD3552R_REG_ADDR_SPI_REVISION 0x0B +#define AD3552R_REG_ADDR_VENDOR_L 0x0C +#define AD3552R_REG_ADDR_VENDOR_H 0x0D +#define AD3552R_REG_ADDR_STREAM_MODE 0x0E +#define AD3552R_MASK_LENGTH GENMASK(7, 0) +#define AD3552R_REG_ADDR_TRANSFER_REGISTER 0x0F +#define AD3552R_MASK_MULTI_IO_MODE GENMASK(7, 6) +#define AD3552R_MASK_STREAM_LENGTH_KEEP_VALUE BIT(2) +#define AD3552R_REG_ADDR_INTERFACE_CONFIG_C 0x10 +#define AD3552R_MASK_CRC_ENABLE \ + (GENMASK(7, 6) | GENMASK(1, 0)) +#define AD3552R_MASK_STRICT_REGISTER_ACCESS BIT(5) +#define AD3552R_REG_ADDR_INTERFACE_STATUS_A 0x11 +#define AD3552R_MASK_INTERFACE_NOT_READY BIT(7) +#define AD3552R_MASK_CLOCK_COUNTING_ERROR BIT(5) +#define AD3552R_MASK_INVALID_OR_NO_CRC BIT(3) +#define AD3552R_MASK_WRITE_TO_READ_ONLY_REGISTER BIT(2) +#define AD3552R_MASK_PARTIAL_REGISTER_ACCESS BIT(1) +#define AD3552R_MASK_REGISTER_ADDRESS_INVALID BIT(0) +#define AD3552R_REG_ADDR_INTERFACE_CONFIG_D 0x14 +#define AD3552R_MASK_ALERT_ENABLE_PULLUP BIT(6) +#define AD3552R_MASK_MEM_CRC_EN BIT(4) +#define AD3552R_MASK_SDO_DRIVE_STRENGTH GENMASK(3, 2) +#define AD3552R_MASK_DUAL_SPI_SYNCHROUNOUS_EN BIT(1) +#define AD3552R_MASK_SPI_CONFIG_DDR BIT(0) +#define AD3552R_REG_ADDR_SH_REFERENCE_CONFIG 0x15 +#define AD3552R_MASK_IDUMP_FAST_MODE BIT(6) +#define AD3552R_MASK_SAMPLE_HOLD_DIFF_USER_EN BIT(5) +#define AD3552R_MASK_SAMPLE_HOLD_USER_TRIM GENMASK(4, 3) +#define AD3552R_MASK_SAMPLE_HOLD_USER_ENABLE BIT(2) +#define AD3552R_MASK_REFERENCE_VOLTAGE_SEL GENMASK(1, 0) +#define AD3552R_REG_ADDR_ERR_ALARM_MASK 0x16 +#define AD3552R_MASK_REF_RANGE_ALARM BIT(6) +#define AD3552R_MASK_CLOCK_COUNT_ERR_ALARM BIT(5) +#define AD3552R_MASK_MEM_CRC_ERR_ALARM BIT(4) +#define AD3552R_MASK_SPI_CRC_ERR_ALARM BIT(3) +#define AD3552R_MASK_WRITE_TO_READ_ONLY_ALARM BIT(2) +#define AD3552R_MASK_PARTIAL_REGISTER_ACCESS_ALARM BIT(1) +#define AD3552R_MASK_REGISTER_ADDRESS_INVALID_ALARM BIT(0) +#define AD3552R_REG_ADDR_ERR_STATUS 0x17 +#define AD3552R_MASK_REF_RANGE_ERR_STATUS BIT(6) +#define AD3552R_MASK_STREAM_EXCEEDS_DAC_ERR_STATUS BIT(5) +#define AD3552R_MASK_MEM_CRC_ERR_STATUS BIT(4) +#define AD3552R_MASK_RESET_STATUS BIT(0) +#define AD3552R_REG_ADDR_POWERDOWN_CONFIG 0x18 +#define AD3552R_MASK_CH_DAC_POWERDOWN(ch) BIT(4 + (ch)) +#define AD3552R_MASK_CH_AMPLIFIER_POWERDOWN(ch) BIT(ch) +#define AD3552R_REG_ADDR_CH0_CH1_OUTPUT_RANGE 0x19 +#define AD3552R_MASK_CH0_RANGE GENMASK(2, 0) +#define AD3552R_MASK_CH1_RANGE GENMASK(6, 4) +#define AD3552R_MASK_CH_OUTPUT_RANGE GENMASK(7, 0) +#define AD3552R_MASK_CH_OUTPUT_RANGE_SEL(ch) \ + ((ch) ? GENMASK(7, 4) : GENMASK(3, 0)) +#define AD3552R_REG_ADDR_CH_OFFSET(ch) (0x1B + (ch) * 2) +#define AD3552R_MASK_CH_OFFSET_BITS_0_7 GENMASK(7, 0) +#define AD3552R_REG_ADDR_CH_GAIN(ch) (0x1C + (ch) * 2) +#define AD3552R_MASK_CH_RANGE_OVERRIDE BIT(7) +#define AD3552R_MASK_CH_GAIN_SCALING_N GENMASK(6, 5) +#define AD3552R_MASK_CH_GAIN_SCALING_P GENMASK(4, 3) +#define AD3552R_MASK_CH_OFFSET_POLARITY BIT(2) +#define AD3552R_MASK_CH_OFFSET_BIT_8 BIT(8) +/* + * Secondary region + * For multibyte registers specify the highest address because the access is + * done in descending order + */ +#define AD3552R_SECONDARY_REGION_START 0x28 +#define AD3552R_REG_ADDR_HW_LDAC_16B 0x28 +#define AD3552R_REG_ADDR_CH_DAC_16B(ch) (0x2C - (1 - (ch)) * 2) +#define AD3552R_REG_ADDR_DAC_PAGE_MASK_16B 0x2E +#define AD3552R_REG_ADDR_CH_SELECT_16B 0x2F +#define AD3552R_REG_ADDR_INPUT_PAGE_MASK_16B 0x31 +#define AD3552R_REG_ADDR_SW_LDAC_16B 0x32 +#define AD3552R_REG_ADDR_CH_INPUT_16B(ch) (0x36 - (1 - (ch)) * 2) +/* 3 bytes registers */ +#define AD3552R_REG_START_24B 0x37 +#define AD3552R_REG_ADDR_HW_LDAC_24B 0x37 +#define AD3552R_REG_ADDR_CH_DAC_24B(ch) (0x3D - (1 - (ch)) * 3) +#define AD3552R_REG_ADDR_DAC_PAGE_MASK_24B 0x40 +#define AD3552R_REG_ADDR_CH_SELECT_24B 0x41 +#define AD3552R_REG_ADDR_INPUT_PAGE_MASK_24B 0x44 +#define AD3552R_REG_ADDR_SW_LDAC_24B 0x45 +#define AD3552R_REG_ADDR_CH_INPUT_24B(ch) (0x4B - (1 - (ch)) * 3) + +#define AD3552R_MAX_CH 2 +#define AD3552R_MASK_CH(ch) BIT(ch) +#define AD3552R_MASK_ALL_CH GENMASK(1, 0) +#define AD3552R_MAX_REG_SIZE 3 +#define AD3552R_READ_BIT BIT(7) +#define AD3552R_ADDR_MASK GENMASK(6, 0) +#define AD3552R_MASK_DAC_12B GENMASK(15, 4) +#define AD3552R_DEFAULT_CONFIG_B_VALUE 0x8 +#define AD3552R_SCRATCH_PAD_TEST_VAL1 0x34 +#define AD3552R_SCRATCH_PAD_TEST_VAL2 0xB2 +#define AD3552R_GAIN_SCALE 1000 +#define AD3552R_LDAC_PULSE_US 100 + +#define AD3552R_CH0_ACTIVE BIT(0) +#define AD3552R_CH1_ACTIVE BIT(1) + +#define AD3552R_MAX_RANGES 5 +#define AD3542R_MAX_RANGES 5 +#define AD3552R_SPI 0 +#define AD3552R_DUAL_SPI 1 +#define AD3552R_QUAD_SPI 2 + +extern const struct ad3552r_model_data ad3541r_model_data; +extern const struct ad3552r_model_data ad3542r_model_data; +extern const struct ad3552r_model_data ad3551r_model_data; +extern const struct ad3552r_model_data ad3552r_model_data; + +enum ad3552r_id { + AD3541R_ID = 0x400b, + AD3542R_ID = 0x4009, + AD3551R_ID = 0x400a, + AD3552R_ID = 0x4008, +}; + +struct ad3552r_model_data { + const char *model_name; + enum ad3552r_id chip_id; + unsigned int num_hw_channels; + const s32 (*ranges_table)[2]; + int num_ranges; + bool requires_output_range; + int num_spi_data_lanes; + int max_reg_addr; +}; + +struct ad3552r_ch_data { + s32 scale_int; + s32 scale_dec; + s32 offset_int; + s32 offset_dec; + s16 gain_offset; + u16 rfb; + u8 n; + u8 p; + u8 range; + bool range_override; +}; + +enum ad3552r_ch_gain_scaling { + /* Gain scaling of 1 */ + AD3552R_CH_GAIN_SCALING_1, + /* Gain scaling of 0.5 */ + AD3552R_CH_GAIN_SCALING_0_5, + /* Gain scaling of 0.25 */ + AD3552R_CH_GAIN_SCALING_0_25, + /* Gain scaling of 0.125 */ + AD3552R_CH_GAIN_SCALING_0_125, +}; + +enum ad3552r_ch_vref_select { + /* Internal source with Vref I/O floating */ + AD3552R_INTERNAL_VREF_PIN_FLOATING, + /* Internal source with Vref I/O at 2.5V */ + AD3552R_INTERNAL_VREF_PIN_2P5V, + /* External source with Vref I/O as input */ + AD3552R_EXTERNAL_VREF_PIN_INPUT +}; + +enum ad3542r_ch_output_range { + /* Range from 0 V to 2.5 V. Requires Rfb1x connection */ + AD3542R_CH_OUTPUT_RANGE_0__2P5V, + /* Range from 0 V to 5 V. Requires Rfb1x connection */ + AD3542R_CH_OUTPUT_RANGE_0__5V, + /* Range from 0 V to 10 V. Requires Rfb2x connection */ + AD3542R_CH_OUTPUT_RANGE_0__10V, + /* Range from -5 V to 5 V. Requires Rfb2x connection */ + AD3542R_CH_OUTPUT_RANGE_NEG_5__5V, + /* Range from -2.5 V to 7.5 V. Requires Rfb2x connection */ + AD3542R_CH_OUTPUT_RANGE_NEG_2P5__7P5V, +}; + +enum ad3552r_ch_output_range { + /* Range from 0 V to 2.5 V. Requires Rfb1x connection */ + AD3552R_CH_OUTPUT_RANGE_0__2P5V, + /* Range from 0 V to 5 V. Requires Rfb1x connection */ + AD3552R_CH_OUTPUT_RANGE_0__5V, + /* Range from 0 V to 10 V. Requires Rfb2x connection */ + AD3552R_CH_OUTPUT_RANGE_0__10V, + /* Range from -5 V to 5 V. Requires Rfb2x connection */ + AD3552R_CH_OUTPUT_RANGE_NEG_5__5V, + /* Range from -10 V to 10 V. Requires Rfb4x connection */ + AD3552R_CH_OUTPUT_RANGE_NEG_10__10V, +}; + +int ad3552r_get_output_range(struct device *dev, + const struct ad3552r_model_data *model_info, + struct fwnode_handle *child, u32 *val); +int ad3552r_get_custom_gain(struct device *dev, struct fwnode_handle *child, + u8 *gs_p, u8 *gs_n, u16 *rfb, s16 *goffs); +u16 ad3552r_calc_custom_gain(u8 p, u8 n, s16 goffs); +int ad3552r_get_ref_voltage(struct device *dev, u32 *val); +int ad3552r_get_drive_strength(struct device *dev, u32 *val); +void ad3552r_calc_gain_and_offset(struct ad3552r_ch_data *ch_data, + const struct ad3552r_model_data *model_data); + +#endif /* __DRIVERS_IIO_DAC_AD3552R_H__ */ diff --git a/drivers/iio/dac/ad5064.c b/drivers/iio/dac/ad5064.c new file mode 100644 index 000000000..298f3a703 --- /dev/null +++ b/drivers/iio/dac/ad5064.c @@ -0,0 +1,1107 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD5024, AD5025, AD5044, AD5045, AD5064, AD5064-1, AD5065, AD5625, AD5625R, + * AD5627, AD5627R, AD5628, AD5629R, AD5645R, AD5647R, AD5648, AD5665, AD5665R, + * AD5666, AD5667, AD5667R, AD5668, AD5669R, LTC2606, LTC2607, LTC2609, LTC2616, + * LTC2617, LTC2619, LTC2626, LTC2627, LTC2629, LTC2631, LTC2633, LTC2635 + * Digital to analog converters driver + * + * Copyright 2011 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define AD5064_MAX_DAC_CHANNELS 8 +#define AD5064_MAX_VREFS 4 + +#define AD5064_ADDR(x) ((x) << 20) +#define AD5064_CMD(x) ((x) << 24) + +#define AD5064_ADDR_ALL_DAC 0xF + +#define AD5064_CMD_WRITE_INPUT_N 0x0 +#define AD5064_CMD_UPDATE_DAC_N 0x1 +#define AD5064_CMD_WRITE_INPUT_N_UPDATE_ALL 0x2 +#define AD5064_CMD_WRITE_INPUT_N_UPDATE_N 0x3 +#define AD5064_CMD_POWERDOWN_DAC 0x4 +#define AD5064_CMD_CLEAR 0x5 +#define AD5064_CMD_LDAC_MASK 0x6 +#define AD5064_CMD_RESET 0x7 +#define AD5064_CMD_CONFIG 0x8 + +#define AD5064_CMD_RESET_V2 0x5 +#define AD5064_CMD_CONFIG_V2 0x7 + +#define AD5064_CONFIG_DAISY_CHAIN_ENABLE BIT(1) +#define AD5064_CONFIG_INT_VREF_ENABLE BIT(0) + +#define AD5064_LDAC_PWRDN_NONE 0x0 +#define AD5064_LDAC_PWRDN_1K 0x1 +#define AD5064_LDAC_PWRDN_100K 0x2 +#define AD5064_LDAC_PWRDN_3STATE 0x3 + +/** + * enum ad5064_regmap_type - Register layout variant + * @AD5064_REGMAP_ADI: Old Analog Devices register map layout + * @AD5064_REGMAP_ADI2: New Analog Devices register map layout + * @AD5064_REGMAP_LTC: LTC register map layout + */ +enum ad5064_regmap_type { + AD5064_REGMAP_ADI, + AD5064_REGMAP_ADI2, + AD5064_REGMAP_LTC, +}; + +/** + * struct ad5064_chip_info - chip specific information + * @shared_vref: whether the vref supply is shared between channels + * @internal_vref: internal reference voltage. 0 if the chip has no + * internal vref. + * @channels: channel specification + * @num_channels: number of channels + * @regmap_type: register map layout variant + */ + +struct ad5064_chip_info { + bool shared_vref; + unsigned long internal_vref; + const struct iio_chan_spec *channels; + unsigned int num_channels; + enum ad5064_regmap_type regmap_type; +}; + +struct ad5064_state; + +typedef int (*ad5064_write_func)(struct ad5064_state *st, unsigned int cmd, + unsigned int addr, unsigned int val); + +/** + * struct ad5064_state - driver instance specific data + * @dev: the device for this driver instance + * @chip_info: chip model specific constants, available modes etc + * @vref_reg: vref supply regulators + * @pwr_down: whether channel is powered down + * @pwr_down_mode: channel's current power down mode + * @dac_cache: current DAC raw value (chip does not support readback) + * @use_internal_vref: set to true if the internal reference voltage should be + * used. + * @write: register write callback + * @lock: maintain consistency between cached and dev state + * @data: i2c/spi transfer buffers + */ + +struct ad5064_state { + struct device *dev; + const struct ad5064_chip_info *chip_info; + struct regulator_bulk_data vref_reg[AD5064_MAX_VREFS]; + bool pwr_down[AD5064_MAX_DAC_CHANNELS]; + u8 pwr_down_mode[AD5064_MAX_DAC_CHANNELS]; + unsigned int dac_cache[AD5064_MAX_DAC_CHANNELS]; + bool use_internal_vref; + + ad5064_write_func write; + struct mutex lock; + + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + */ + union { + u8 i2c[3]; + __be32 spi; + } data __aligned(IIO_DMA_MINALIGN); +}; + +enum ad5064_type { + ID_AD5024, + ID_AD5025, + ID_AD5044, + ID_AD5045, + ID_AD5064, + ID_AD5064_1, + ID_AD5065, + ID_AD5625, + ID_AD5625R_1V25, + ID_AD5625R_2V5, + ID_AD5627, + ID_AD5627R_1V25, + ID_AD5627R_2V5, + ID_AD5628_1, + ID_AD5628_2, + ID_AD5629_1, + ID_AD5629_2, + ID_AD5645R_1V25, + ID_AD5645R_2V5, + ID_AD5647R_1V25, + ID_AD5647R_2V5, + ID_AD5648_1, + ID_AD5648_2, + ID_AD5665, + ID_AD5665R_1V25, + ID_AD5665R_2V5, + ID_AD5666_1, + ID_AD5666_2, + ID_AD5667, + ID_AD5667R_1V25, + ID_AD5667R_2V5, + ID_AD5668_1, + ID_AD5668_2, + ID_AD5669_1, + ID_AD5669_2, + ID_LTC2606, + ID_LTC2607, + ID_LTC2609, + ID_LTC2616, + ID_LTC2617, + ID_LTC2619, + ID_LTC2626, + ID_LTC2627, + ID_LTC2629, + ID_LTC2631_L12, + ID_LTC2631_H12, + ID_LTC2631_L10, + ID_LTC2631_H10, + ID_LTC2631_L8, + ID_LTC2631_H8, + ID_LTC2633_L12, + ID_LTC2633_H12, + ID_LTC2633_L10, + ID_LTC2633_H10, + ID_LTC2633_L8, + ID_LTC2633_H8, + ID_LTC2635_L12, + ID_LTC2635_H12, + ID_LTC2635_L10, + ID_LTC2635_H10, + ID_LTC2635_L8, + ID_LTC2635_H8, +}; + +static int ad5064_write(struct ad5064_state *st, unsigned int cmd, + unsigned int addr, unsigned int val, unsigned int shift) +{ + val <<= shift; + + return st->write(st, cmd, addr, val); +} + +static int ad5064_sync_powerdown_mode(struct ad5064_state *st, + const struct iio_chan_spec *chan) +{ + unsigned int val, address; + unsigned int shift; + int ret; + + if (st->chip_info->regmap_type == AD5064_REGMAP_LTC) { + val = 0; + address = chan->address; + } else { + if (st->chip_info->regmap_type == AD5064_REGMAP_ADI2) + shift = 4; + else + shift = 8; + + val = (0x1 << chan->address); + address = 0; + + if (st->pwr_down[chan->channel]) + val |= st->pwr_down_mode[chan->channel] << shift; + } + + ret = ad5064_write(st, AD5064_CMD_POWERDOWN_DAC, address, val, 0); + + return ret; +} + +static const char * const ad5064_powerdown_modes[] = { + "1kohm_to_gnd", + "100kohm_to_gnd", + "three_state", +}; + +static const char * const ltc2617_powerdown_modes[] = { + "90kohm_to_gnd", +}; + +static int ad5064_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad5064_state *st = iio_priv(indio_dev); + + return st->pwr_down_mode[chan->channel] - 1; +} + +static int ad5064_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int mode) +{ + struct ad5064_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->lock); + st->pwr_down_mode[chan->channel] = mode + 1; + + ret = ad5064_sync_powerdown_mode(st, chan); + mutex_unlock(&st->lock); + + return ret; +} + +static const struct iio_enum ad5064_powerdown_mode_enum = { + .items = ad5064_powerdown_modes, + .num_items = ARRAY_SIZE(ad5064_powerdown_modes), + .get = ad5064_get_powerdown_mode, + .set = ad5064_set_powerdown_mode, +}; + +static const struct iio_enum ltc2617_powerdown_mode_enum = { + .items = ltc2617_powerdown_modes, + .num_items = ARRAY_SIZE(ltc2617_powerdown_modes), + .get = ad5064_get_powerdown_mode, + .set = ad5064_set_powerdown_mode, +}; + +static ssize_t ad5064_read_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, char *buf) +{ + struct ad5064_state *st = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", st->pwr_down[chan->channel]); +} + +static ssize_t ad5064_write_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, const char *buf, + size_t len) +{ + struct ad5064_state *st = iio_priv(indio_dev); + bool pwr_down; + int ret; + + ret = kstrtobool(buf, &pwr_down); + if (ret) + return ret; + + mutex_lock(&st->lock); + st->pwr_down[chan->channel] = pwr_down; + + ret = ad5064_sync_powerdown_mode(st, chan); + mutex_unlock(&st->lock); + return ret ? ret : len; +} + +static int ad5064_get_vref(struct ad5064_state *st, + struct iio_chan_spec const *chan) +{ + unsigned int i; + + if (st->use_internal_vref) + return st->chip_info->internal_vref; + + i = st->chip_info->shared_vref ? 0 : chan->channel; + return regulator_get_voltage(st->vref_reg[i].consumer); +} + +static int ad5064_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad5064_state *st = iio_priv(indio_dev); + int scale_uv; + + switch (m) { + case IIO_CHAN_INFO_RAW: + *val = st->dac_cache[chan->channel]; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + scale_uv = ad5064_get_vref(st, chan); + if (scale_uv < 0) + return scale_uv; + + *val = scale_uv / 1000; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + default: + break; + } + return -EINVAL; +} + +static int ad5064_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + struct ad5064_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val >= (1 << chan->scan_type.realbits) || val < 0) + return -EINVAL; + + mutex_lock(&st->lock); + ret = ad5064_write(st, AD5064_CMD_WRITE_INPUT_N_UPDATE_N, + chan->address, val, chan->scan_type.shift); + if (ret == 0) + st->dac_cache[chan->channel] = val; + mutex_unlock(&st->lock); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info ad5064_info = { + .read_raw = ad5064_read_raw, + .write_raw = ad5064_write_raw, +}; + +static const struct iio_chan_spec_ext_info ad5064_ext_info[] = { + { + .name = "powerdown", + .read = ad5064_read_dac_powerdown, + .write = ad5064_write_dac_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SEPARATE, &ad5064_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, &ad5064_powerdown_mode_enum), + { } +}; + +static const struct iio_chan_spec_ext_info ltc2617_ext_info[] = { + { + .name = "powerdown", + .read = ad5064_read_dac_powerdown, + .write = ad5064_write_dac_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SEPARATE, <c2617_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, <c2617_powerdown_mode_enum), + { } +}; + +#define AD5064_CHANNEL(chan, addr, bits, _shift, _ext_info) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .address = addr, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = (_shift), \ + }, \ + .ext_info = (_ext_info), \ +} + +#define DECLARE_AD5064_CHANNELS(name, bits, shift, ext_info) \ +const struct iio_chan_spec name[] = { \ + AD5064_CHANNEL(0, 0, bits, shift, ext_info), \ + AD5064_CHANNEL(1, 1, bits, shift, ext_info), \ + AD5064_CHANNEL(2, 2, bits, shift, ext_info), \ + AD5064_CHANNEL(3, 3, bits, shift, ext_info), \ + AD5064_CHANNEL(4, 4, bits, shift, ext_info), \ + AD5064_CHANNEL(5, 5, bits, shift, ext_info), \ + AD5064_CHANNEL(6, 6, bits, shift, ext_info), \ + AD5064_CHANNEL(7, 7, bits, shift, ext_info), \ +} + +#define DECLARE_AD5065_CHANNELS(name, bits, shift, ext_info) \ +const struct iio_chan_spec name[] = { \ + AD5064_CHANNEL(0, 0, bits, shift, ext_info), \ + AD5064_CHANNEL(1, 3, bits, shift, ext_info), \ +} + +static DECLARE_AD5064_CHANNELS(ad5024_channels, 12, 8, ad5064_ext_info); +static DECLARE_AD5064_CHANNELS(ad5044_channels, 14, 6, ad5064_ext_info); +static DECLARE_AD5064_CHANNELS(ad5064_channels, 16, 4, ad5064_ext_info); + +static DECLARE_AD5065_CHANNELS(ad5025_channels, 12, 8, ad5064_ext_info); +static DECLARE_AD5065_CHANNELS(ad5045_channels, 14, 6, ad5064_ext_info); +static DECLARE_AD5065_CHANNELS(ad5065_channels, 16, 4, ad5064_ext_info); + +static DECLARE_AD5064_CHANNELS(ad5629_channels, 12, 4, ad5064_ext_info); +static DECLARE_AD5064_CHANNELS(ad5645_channels, 14, 2, ad5064_ext_info); +static DECLARE_AD5064_CHANNELS(ad5669_channels, 16, 0, ad5064_ext_info); + +static DECLARE_AD5064_CHANNELS(ltc2607_channels, 16, 0, ltc2617_ext_info); +static DECLARE_AD5064_CHANNELS(ltc2617_channels, 14, 2, ltc2617_ext_info); +static DECLARE_AD5064_CHANNELS(ltc2627_channels, 12, 4, ltc2617_ext_info); +#define ltc2631_12_channels ltc2627_channels +static DECLARE_AD5064_CHANNELS(ltc2631_10_channels, 10, 6, ltc2617_ext_info); +static DECLARE_AD5064_CHANNELS(ltc2631_8_channels, 8, 8, ltc2617_ext_info); + +#define LTC2631_INFO(vref, pchannels, nchannels) \ + { \ + .shared_vref = true, \ + .internal_vref = vref, \ + .channels = pchannels, \ + .num_channels = nchannels, \ + .regmap_type = AD5064_REGMAP_LTC, \ + } + + +static const struct ad5064_chip_info ad5064_chip_info_tbl[] = { + [ID_AD5024] = { + .shared_vref = false, + .channels = ad5024_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI, + }, + [ID_AD5025] = { + .shared_vref = false, + .channels = ad5025_channels, + .num_channels = 2, + .regmap_type = AD5064_REGMAP_ADI, + }, + [ID_AD5044] = { + .shared_vref = false, + .channels = ad5044_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI, + }, + [ID_AD5045] = { + .shared_vref = false, + .channels = ad5045_channels, + .num_channels = 2, + .regmap_type = AD5064_REGMAP_ADI, + }, + [ID_AD5064] = { + .shared_vref = false, + .channels = ad5064_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI, + }, + [ID_AD5064_1] = { + .shared_vref = true, + .channels = ad5064_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI, + }, + [ID_AD5065] = { + .shared_vref = false, + .channels = ad5065_channels, + .num_channels = 2, + .regmap_type = AD5064_REGMAP_ADI, + }, + [ID_AD5625] = { + .shared_vref = true, + .channels = ad5629_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5625R_1V25] = { + .shared_vref = true, + .internal_vref = 1250000, + .channels = ad5629_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5625R_2V5] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5629_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5627] = { + .shared_vref = true, + .channels = ad5629_channels, + .num_channels = 2, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5627R_1V25] = { + .shared_vref = true, + .internal_vref = 1250000, + .channels = ad5629_channels, + .num_channels = 2, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5627R_2V5] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5629_channels, + .num_channels = 2, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5628_1] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5024_channels, + .num_channels = 8, + .regmap_type = AD5064_REGMAP_ADI, + }, + [ID_AD5628_2] = { + .shared_vref = true, + .internal_vref = 5000000, + .channels = ad5024_channels, + .num_channels = 8, + .regmap_type = AD5064_REGMAP_ADI, + }, + [ID_AD5629_1] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5629_channels, + .num_channels = 8, + .regmap_type = AD5064_REGMAP_ADI, + }, + [ID_AD5629_2] = { + .shared_vref = true, + .internal_vref = 5000000, + .channels = ad5629_channels, + .num_channels = 8, + .regmap_type = AD5064_REGMAP_ADI, + }, + [ID_AD5645R_1V25] = { + .shared_vref = true, + .internal_vref = 1250000, + .channels = ad5645_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5645R_2V5] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5645_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5647R_1V25] = { + .shared_vref = true, + .internal_vref = 1250000, + .channels = ad5645_channels, + .num_channels = 2, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5647R_2V5] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5645_channels, + .num_channels = 2, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5648_1] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5044_channels, + .num_channels = 8, + .regmap_type = AD5064_REGMAP_ADI, + }, + [ID_AD5648_2] = { + .shared_vref = true, + .internal_vref = 5000000, + .channels = ad5044_channels, + .num_channels = 8, + .regmap_type = AD5064_REGMAP_ADI, + }, + [ID_AD5665] = { + .shared_vref = true, + .channels = ad5669_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5665R_1V25] = { + .shared_vref = true, + .internal_vref = 1250000, + .channels = ad5669_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5665R_2V5] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5669_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5666_1] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5064_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI, + }, + [ID_AD5666_2] = { + .shared_vref = true, + .internal_vref = 5000000, + .channels = ad5064_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI, + }, + [ID_AD5667] = { + .shared_vref = true, + .channels = ad5669_channels, + .num_channels = 2, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5667R_1V25] = { + .shared_vref = true, + .internal_vref = 1250000, + .channels = ad5669_channels, + .num_channels = 2, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5667R_2V5] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5669_channels, + .num_channels = 2, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5668_1] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5064_channels, + .num_channels = 8, + .regmap_type = AD5064_REGMAP_ADI, + }, + [ID_AD5668_2] = { + .shared_vref = true, + .internal_vref = 5000000, + .channels = ad5064_channels, + .num_channels = 8, + .regmap_type = AD5064_REGMAP_ADI, + }, + [ID_AD5669_1] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5669_channels, + .num_channels = 8, + .regmap_type = AD5064_REGMAP_ADI, + }, + [ID_AD5669_2] = { + .shared_vref = true, + .internal_vref = 5000000, + .channels = ad5669_channels, + .num_channels = 8, + .regmap_type = AD5064_REGMAP_ADI, + }, + [ID_LTC2606] = { + .shared_vref = true, + .internal_vref = 0, + .channels = ltc2607_channels, + .num_channels = 1, + .regmap_type = AD5064_REGMAP_LTC, + }, + [ID_LTC2607] = { + .shared_vref = true, + .internal_vref = 0, + .channels = ltc2607_channels, + .num_channels = 2, + .regmap_type = AD5064_REGMAP_LTC, + }, + [ID_LTC2609] = { + .shared_vref = false, + .internal_vref = 0, + .channels = ltc2607_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_LTC, + }, + [ID_LTC2616] = { + .shared_vref = true, + .internal_vref = 0, + .channels = ltc2617_channels, + .num_channels = 1, + .regmap_type = AD5064_REGMAP_LTC, + }, + [ID_LTC2617] = { + .shared_vref = true, + .internal_vref = 0, + .channels = ltc2617_channels, + .num_channels = 2, + .regmap_type = AD5064_REGMAP_LTC, + }, + [ID_LTC2619] = { + .shared_vref = false, + .internal_vref = 0, + .channels = ltc2617_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_LTC, + }, + [ID_LTC2626] = { + .shared_vref = true, + .internal_vref = 0, + .channels = ltc2627_channels, + .num_channels = 1, + .regmap_type = AD5064_REGMAP_LTC, + }, + [ID_LTC2627] = { + .shared_vref = true, + .internal_vref = 0, + .channels = ltc2627_channels, + .num_channels = 2, + .regmap_type = AD5064_REGMAP_LTC, + }, + [ID_LTC2629] = { + .shared_vref = false, + .internal_vref = 0, + .channels = ltc2627_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_LTC, + }, + [ID_LTC2631_L12] = LTC2631_INFO(2500000, ltc2631_12_channels, 1), + [ID_LTC2631_H12] = LTC2631_INFO(4096000, ltc2631_12_channels, 1), + [ID_LTC2631_L10] = LTC2631_INFO(2500000, ltc2631_10_channels, 1), + [ID_LTC2631_H10] = LTC2631_INFO(4096000, ltc2631_10_channels, 1), + [ID_LTC2631_L8] = LTC2631_INFO(2500000, ltc2631_8_channels, 1), + [ID_LTC2631_H8] = LTC2631_INFO(4096000, ltc2631_8_channels, 1), + [ID_LTC2633_L12] = LTC2631_INFO(2500000, ltc2631_12_channels, 2), + [ID_LTC2633_H12] = LTC2631_INFO(4096000, ltc2631_12_channels, 2), + [ID_LTC2633_L10] = LTC2631_INFO(2500000, ltc2631_10_channels, 2), + [ID_LTC2633_H10] = LTC2631_INFO(4096000, ltc2631_10_channels, 2), + [ID_LTC2633_L8] = LTC2631_INFO(2500000, ltc2631_8_channels, 2), + [ID_LTC2633_H8] = LTC2631_INFO(4096000, ltc2631_8_channels, 2), + [ID_LTC2635_L12] = LTC2631_INFO(2500000, ltc2631_12_channels, 4), + [ID_LTC2635_H12] = LTC2631_INFO(4096000, ltc2631_12_channels, 4), + [ID_LTC2635_L10] = LTC2631_INFO(2500000, ltc2631_10_channels, 4), + [ID_LTC2635_H10] = LTC2631_INFO(4096000, ltc2631_10_channels, 4), + [ID_LTC2635_L8] = LTC2631_INFO(2500000, ltc2631_8_channels, 4), + [ID_LTC2635_H8] = LTC2631_INFO(4096000, ltc2631_8_channels, 4), +}; + +static inline unsigned int ad5064_num_vref(struct ad5064_state *st) +{ + return st->chip_info->shared_vref ? 1 : st->chip_info->num_channels; +} + +static const char * const ad5064_vref_names[] = { + "vrefA", + "vrefB", + "vrefC", + "vrefD", +}; + +static const char *ad5064_vref_name(struct ad5064_state *st, + unsigned int vref) +{ + return st->chip_info->shared_vref ? "vref" : ad5064_vref_names[vref]; +} + +static int ad5064_set_config(struct ad5064_state *st, unsigned int val) +{ + unsigned int cmd; + + switch (st->chip_info->regmap_type) { + case AD5064_REGMAP_ADI2: + cmd = AD5064_CMD_CONFIG_V2; + break; + default: + cmd = AD5064_CMD_CONFIG; + break; + } + + return ad5064_write(st, cmd, 0, val, 0); +} + +static int ad5064_request_vref(struct ad5064_state *st, struct device *dev) +{ + unsigned int i; + int ret; + + for (i = 0; i < ad5064_num_vref(st); ++i) + st->vref_reg[i].supply = ad5064_vref_name(st, i); + + if (!st->chip_info->internal_vref) + return devm_regulator_bulk_get(dev, ad5064_num_vref(st), + st->vref_reg); + + /* + * This assumes that when the regulator has an internal VREF + * there is only one external VREF connection, which is + * currently the case for all supported devices. + */ + st->vref_reg[0].consumer = devm_regulator_get_optional(dev, "vref"); + if (!IS_ERR(st->vref_reg[0].consumer)) + return 0; + + ret = PTR_ERR(st->vref_reg[0].consumer); + if (ret != -ENODEV) + return ret; + + /* If no external regulator was supplied use the internal VREF */ + st->use_internal_vref = true; + ret = ad5064_set_config(st, AD5064_CONFIG_INT_VREF_ENABLE); + if (ret) + dev_err(dev, "Failed to enable internal vref: %d\n", ret); + + return ret; +} + +static void ad5064_bulk_reg_disable(void *data) +{ + struct ad5064_state *st = data; + + regulator_bulk_disable(ad5064_num_vref(st), st->vref_reg); +} + +static int ad5064_probe(struct device *dev, enum ad5064_type type, + const char *name, ad5064_write_func write) +{ + struct iio_dev *indio_dev; + struct ad5064_state *st; + unsigned int midscale; + unsigned int i; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + mutex_init(&st->lock); + + st->chip_info = &ad5064_chip_info_tbl[type]; + st->dev = dev; + st->write = write; + + ret = ad5064_request_vref(st, dev); + if (ret) + return ret; + + if (!st->use_internal_vref) { + ret = regulator_bulk_enable(ad5064_num_vref(st), st->vref_reg); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, ad5064_bulk_reg_disable, st); + if (ret) + return ret; + } + + indio_dev->name = name; + indio_dev->info = &ad5064_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + + midscale = (1 << indio_dev->channels[0].scan_type.realbits) / 2; + + for (i = 0; i < st->chip_info->num_channels; ++i) { + st->pwr_down_mode[i] = AD5064_LDAC_PWRDN_1K; + st->dac_cache[i] = midscale; + } + + return devm_iio_device_register(dev, indio_dev); +} + +#if IS_ENABLED(CONFIG_SPI_MASTER) + +static int ad5064_spi_write(struct ad5064_state *st, unsigned int cmd, + unsigned int addr, unsigned int val) +{ + struct spi_device *spi = to_spi_device(st->dev); + + st->data.spi = cpu_to_be32(AD5064_CMD(cmd) | AD5064_ADDR(addr) | val); + return spi_write(spi, &st->data.spi, sizeof(st->data.spi)); +} + +static int ad5064_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + + return ad5064_probe(&spi->dev, id->driver_data, id->name, + ad5064_spi_write); +} + +static const struct spi_device_id ad5064_spi_ids[] = { + { .name = "ad5024", .driver_data = ID_AD5024 }, + { .name = "ad5025", .driver_data = ID_AD5025 }, + { .name = "ad5044", .driver_data = ID_AD5044 }, + { .name = "ad5045", .driver_data = ID_AD5045 }, + { .name = "ad5064", .driver_data = ID_AD5064 }, + { .name = "ad5064-1", .driver_data = ID_AD5064_1 }, + { .name = "ad5065", .driver_data = ID_AD5065 }, + { .name = "ad5628-1", .driver_data = ID_AD5628_1 }, + { .name = "ad5628-2", .driver_data = ID_AD5628_2 }, + { .name = "ad5648-1", .driver_data = ID_AD5648_1 }, + { .name = "ad5648-2", .driver_data = ID_AD5648_2 }, + { .name = "ad5666-1", .driver_data = ID_AD5666_1 }, + { .name = "ad5666-2", .driver_data = ID_AD5666_2 }, + { .name = "ad5668-1", .driver_data = ID_AD5668_1 }, + { .name = "ad5668-2", .driver_data = ID_AD5668_2 }, + { .name = "ad5668-3", .driver_data = ID_AD5668_2 }, /* similar enough to ad5668-2 */ + { } +}; +MODULE_DEVICE_TABLE(spi, ad5064_spi_ids); + +static struct spi_driver ad5064_spi_driver = { + .driver = { + .name = "ad5064", + }, + .probe = ad5064_spi_probe, + .id_table = ad5064_spi_ids, +}; + +static int __init ad5064_spi_register_driver(void) +{ + return spi_register_driver(&ad5064_spi_driver); +} + +static void ad5064_spi_unregister_driver(void) +{ + spi_unregister_driver(&ad5064_spi_driver); +} + +#else + +static inline int ad5064_spi_register_driver(void) { return 0; } +static inline void ad5064_spi_unregister_driver(void) { } + +#endif + +#if IS_ENABLED(CONFIG_I2C) + +static int ad5064_i2c_write(struct ad5064_state *st, unsigned int cmd, + unsigned int addr, unsigned int val) +{ + struct i2c_client *i2c = to_i2c_client(st->dev); + unsigned int cmd_shift; + int ret; + + switch (st->chip_info->regmap_type) { + case AD5064_REGMAP_ADI2: + cmd_shift = 3; + break; + default: + cmd_shift = 4; + break; + } + + st->data.i2c[0] = (cmd << cmd_shift) | addr; + put_unaligned_be16(val, &st->data.i2c[1]); + + ret = i2c_master_send(i2c, st->data.i2c, 3); + if (ret < 0) + return ret; + + return 0; +} + +static int ad5064_i2c_probe(struct i2c_client *i2c) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(i2c); + return ad5064_probe(&i2c->dev, id->driver_data, id->name, + ad5064_i2c_write); +} + +static const struct i2c_device_id ad5064_i2c_ids[] = { + { .name = "ad5625", .driver_data = ID_AD5625 }, + { .name = "ad5625r-1v25", .driver_data = ID_AD5625R_1V25 }, + { .name = "ad5625r-2v5", .driver_data = ID_AD5625R_2V5 }, + { .name = "ad5627", .driver_data = ID_AD5627 }, + { .name = "ad5627r-1v25", .driver_data = ID_AD5627R_1V25 }, + { .name = "ad5627r-2v5", .driver_data = ID_AD5627R_2V5 }, + { .name = "ad5629-1", .driver_data = ID_AD5629_1 }, + { .name = "ad5629-2", .driver_data = ID_AD5629_2 }, + { .name = "ad5629-3", .driver_data = ID_AD5629_2 }, /* similar enough to ad5629-2 */ + { .name = "ad5645r-1v25", .driver_data = ID_AD5645R_1V25 }, + { .name = "ad5645r-2v5", .driver_data = ID_AD5645R_2V5 }, + { .name = "ad5665", .driver_data = ID_AD5665 }, + { .name = "ad5665r-1v25", .driver_data = ID_AD5665R_1V25 }, + { .name = "ad5665r-2v5", .driver_data = ID_AD5665R_2V5 }, + { .name = "ad5667", .driver_data = ID_AD5667 }, + { .name = "ad5667r-1v25", .driver_data = ID_AD5667R_1V25 }, + { .name = "ad5667r-2v5", .driver_data = ID_AD5667R_2V5 }, + { .name = "ad5669-1", .driver_data = ID_AD5669_1 }, + { .name = "ad5669-2", .driver_data = ID_AD5669_2 }, + { .name = "ad5669-3", .driver_data = ID_AD5669_2 }, /* similar enough to ad5669-2 */ + { .name = "ltc2606", .driver_data = ID_LTC2606 }, + { .name = "ltc2607", .driver_data = ID_LTC2607 }, + { .name = "ltc2609", .driver_data = ID_LTC2609 }, + { .name = "ltc2616", .driver_data = ID_LTC2616 }, + { .name = "ltc2617", .driver_data = ID_LTC2617 }, + { .name = "ltc2619", .driver_data = ID_LTC2619 }, + { .name = "ltc2626", .driver_data = ID_LTC2626 }, + { .name = "ltc2627", .driver_data = ID_LTC2627 }, + { .name = "ltc2629", .driver_data = ID_LTC2629 }, + { .name = "ltc2631-l12", .driver_data = ID_LTC2631_L12 }, + { .name = "ltc2631-h12", .driver_data = ID_LTC2631_H12 }, + { .name = "ltc2631-l10", .driver_data = ID_LTC2631_L10 }, + { .name = "ltc2631-h10", .driver_data = ID_LTC2631_H10 }, + { .name = "ltc2631-l8", .driver_data = ID_LTC2631_L8 }, + { .name = "ltc2631-h8", .driver_data = ID_LTC2631_H8 }, + { .name = "ltc2633-l12", .driver_data = ID_LTC2633_L12 }, + { .name = "ltc2633-h12", .driver_data = ID_LTC2633_H12 }, + { .name = "ltc2633-l10", .driver_data = ID_LTC2633_L10 }, + { .name = "ltc2633-h10", .driver_data = ID_LTC2633_H10 }, + { .name = "ltc2633-l8", .driver_data = ID_LTC2633_L8 }, + { .name = "ltc2633-h8", .driver_data = ID_LTC2633_H8 }, + { .name = "ltc2635-l12", .driver_data = ID_LTC2635_L12 }, + { .name = "ltc2635-h12", .driver_data = ID_LTC2635_H12 }, + { .name = "ltc2635-l10", .driver_data = ID_LTC2635_L10 }, + { .name = "ltc2635-h10", .driver_data = ID_LTC2635_H10 }, + { .name = "ltc2635-l8", .driver_data = ID_LTC2635_L8 }, + { .name = "ltc2635-h8", .driver_data = ID_LTC2635_H8 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ad5064_i2c_ids); + +static struct i2c_driver ad5064_i2c_driver = { + .driver = { + .name = "ad5064", + }, + .probe = ad5064_i2c_probe, + .id_table = ad5064_i2c_ids, +}; + +static int __init ad5064_i2c_register_driver(void) +{ + return i2c_add_driver(&ad5064_i2c_driver); +} + +static void __exit ad5064_i2c_unregister_driver(void) +{ + i2c_del_driver(&ad5064_i2c_driver); +} + +#else + +static inline int ad5064_i2c_register_driver(void) { return 0; } +static inline void ad5064_i2c_unregister_driver(void) { } + +#endif + +static int __init ad5064_init(void) +{ + int ret; + + ret = ad5064_spi_register_driver(); + if (ret) + return ret; + + ret = ad5064_i2c_register_driver(); + if (ret) { + ad5064_spi_unregister_driver(); + return ret; + } + + return 0; +} +module_init(ad5064_init); + +static void __exit ad5064_exit(void) +{ + ad5064_i2c_unregister_driver(); + ad5064_spi_unregister_driver(); +} +module_exit(ad5064_exit); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Analog Devices AD5024 and similar multi-channel DACs"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5360.c b/drivers/iio/dac/ad5360.c new file mode 100644 index 000000000..c1553f18a --- /dev/null +++ b/drivers/iio/dac/ad5360.c @@ -0,0 +1,550 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog devices AD5360, AD5361, AD5362, AD5363, AD5370, AD5371, AD5373 + * multi-channel Digital to Analog Converters driver + * + * Copyright 2011 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define AD5360_CMD(x) ((x) << 22) +#define AD5360_ADDR(x) ((x) << 16) + +#define AD5360_READBACK_TYPE(x) ((x) << 13) +#define AD5360_READBACK_ADDR(x) ((x) << 7) + +#define AD5360_CHAN_ADDR(chan) ((chan) + 0x8) + +#define AD5360_CMD_WRITE_DATA 0x3 +#define AD5360_CMD_WRITE_OFFSET 0x2 +#define AD5360_CMD_WRITE_GAIN 0x1 +#define AD5360_CMD_SPECIAL_FUNCTION 0x0 + +/* Special function register addresses */ +#define AD5360_REG_SF_NOP 0x0 +#define AD5360_REG_SF_CTRL 0x1 +#define AD5360_REG_SF_OFS(x) (0x2 + (x)) +#define AD5360_REG_SF_READBACK 0x5 + +#define AD5360_SF_CTRL_PWR_DOWN BIT(0) + +#define AD5360_READBACK_X1A 0x0 +#define AD5360_READBACK_X1B 0x1 +#define AD5360_READBACK_OFFSET 0x2 +#define AD5360_READBACK_GAIN 0x3 +#define AD5360_READBACK_SF 0x4 + + +/** + * struct ad5360_chip_info - chip specific information + * @channel_template: channel specification template + * @num_channels: number of channels + * @channels_per_group: number of channels per group + * @num_vrefs: number of vref supplies for the chip +*/ + +struct ad5360_chip_info { + struct iio_chan_spec channel_template; + unsigned int num_channels; + unsigned int channels_per_group; + unsigned int num_vrefs; +}; + +/** + * struct ad5360_state - driver instance specific data + * @spi: spi_device + * @chip_info: chip model specific constants, available modes etc + * @vref_reg: vref supply regulators + * @ctrl: control register cache + * @lock: lock to protect the data buffer during SPI ops + * @data: spi transfer buffers + */ + +struct ad5360_state { + struct spi_device *spi; + const struct ad5360_chip_info *chip_info; + struct regulator_bulk_data vref_reg[3]; + unsigned int ctrl; + struct mutex lock; + + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + */ + union { + __be32 d32; + u8 d8[4]; + } data[2] __aligned(IIO_DMA_MINALIGN); +}; + +enum ad5360_type { + ID_AD5360, + ID_AD5361, + ID_AD5362, + ID_AD5363, + ID_AD5370, + ID_AD5371, + ID_AD5372, + ID_AD5373, +}; + +#define AD5360_CHANNEL(bits) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 16 - (bits), \ + }, \ +} + +static const struct ad5360_chip_info ad5360_chip_info_tbl[] = { + [ID_AD5360] = { + .channel_template = AD5360_CHANNEL(16), + .num_channels = 16, + .channels_per_group = 8, + .num_vrefs = 2, + }, + [ID_AD5361] = { + .channel_template = AD5360_CHANNEL(14), + .num_channels = 16, + .channels_per_group = 8, + .num_vrefs = 2, + }, + [ID_AD5362] = { + .channel_template = AD5360_CHANNEL(16), + .num_channels = 8, + .channels_per_group = 4, + .num_vrefs = 2, + }, + [ID_AD5363] = { + .channel_template = AD5360_CHANNEL(14), + .num_channels = 8, + .channels_per_group = 4, + .num_vrefs = 2, + }, + [ID_AD5370] = { + .channel_template = AD5360_CHANNEL(16), + .num_channels = 40, + .channels_per_group = 8, + .num_vrefs = 2, + }, + [ID_AD5371] = { + .channel_template = AD5360_CHANNEL(14), + .num_channels = 40, + .channels_per_group = 8, + .num_vrefs = 3, + }, + [ID_AD5372] = { + .channel_template = AD5360_CHANNEL(16), + .num_channels = 32, + .channels_per_group = 8, + .num_vrefs = 2, + }, + [ID_AD5373] = { + .channel_template = AD5360_CHANNEL(14), + .num_channels = 32, + .channels_per_group = 8, + .num_vrefs = 2, + }, +}; + +static unsigned int ad5360_get_channel_vref_index(struct ad5360_state *st, + unsigned int channel) +{ + unsigned int i; + + /* The first groups have their own vref, while the remaining groups + * share the last vref */ + i = channel / st->chip_info->channels_per_group; + if (i >= st->chip_info->num_vrefs) + i = st->chip_info->num_vrefs - 1; + + return i; +} + +static int ad5360_get_channel_vref(struct ad5360_state *st, + unsigned int channel) +{ + unsigned int i = ad5360_get_channel_vref_index(st, channel); + + return regulator_get_voltage(st->vref_reg[i].consumer); +} + + +static int ad5360_write_unlocked(struct iio_dev *indio_dev, + unsigned int cmd, unsigned int addr, unsigned int val, + unsigned int shift) +{ + struct ad5360_state *st = iio_priv(indio_dev); + + val <<= shift; + val |= AD5360_CMD(cmd) | AD5360_ADDR(addr); + st->data[0].d32 = cpu_to_be32(val); + + return spi_write(st->spi, &st->data[0].d8[1], 3); +} + +static int ad5360_write(struct iio_dev *indio_dev, unsigned int cmd, + unsigned int addr, unsigned int val, unsigned int shift) +{ + struct ad5360_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->lock); + return ad5360_write_unlocked(indio_dev, cmd, addr, val, shift); +} + +static int ad5360_read(struct iio_dev *indio_dev, unsigned int type, + unsigned int addr) +{ + struct ad5360_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer t[] = { + { + .tx_buf = &st->data[0].d8[1], + .len = 3, + .cs_change = 1, + }, { + .rx_buf = &st->data[1].d8[1], + .len = 3, + }, + }; + + guard(mutex)(&st->lock); + + st->data[0].d32 = cpu_to_be32(AD5360_CMD(AD5360_CMD_SPECIAL_FUNCTION) | + AD5360_ADDR(AD5360_REG_SF_READBACK) | + AD5360_READBACK_TYPE(type) | + AD5360_READBACK_ADDR(addr)); + + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); + if (ret < 0) + return ret; + + return be32_to_cpu(st->data[1].d32) & 0xffff; +} + +static ssize_t ad5360_read_dac_powerdown(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad5360_state *st = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", (bool)(st->ctrl & AD5360_SF_CTRL_PWR_DOWN)); +} + +static int ad5360_update_ctrl(struct iio_dev *indio_dev, unsigned int set, + unsigned int clr) +{ + struct ad5360_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->lock); + + st->ctrl |= set; + st->ctrl &= ~clr; + + return ad5360_write_unlocked(indio_dev, AD5360_CMD_SPECIAL_FUNCTION, + AD5360_REG_SF_CTRL, st->ctrl, 0); +} + +static ssize_t ad5360_write_dac_powerdown(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + bool pwr_down; + int ret; + + ret = kstrtobool(buf, &pwr_down); + if (ret) + return ret; + + if (pwr_down) + ret = ad5360_update_ctrl(indio_dev, AD5360_SF_CTRL_PWR_DOWN, 0); + else + ret = ad5360_update_ctrl(indio_dev, 0, AD5360_SF_CTRL_PWR_DOWN); + + return ret ? ret : len; +} + +static IIO_DEVICE_ATTR(out_voltage_powerdown, + S_IRUGO | S_IWUSR, + ad5360_read_dac_powerdown, + ad5360_write_dac_powerdown, 0); + +static struct attribute *ad5360_attributes[] = { + &iio_dev_attr_out_voltage_powerdown.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad5360_attribute_group = { + .attrs = ad5360_attributes, +}; + +static int ad5360_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad5360_state *st = iio_priv(indio_dev); + int max_val = (1 << chan->scan_type.realbits); + unsigned int ofs_index; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val >= max_val || val < 0) + return -EINVAL; + + return ad5360_write(indio_dev, AD5360_CMD_WRITE_DATA, + chan->address, val, chan->scan_type.shift); + + case IIO_CHAN_INFO_CALIBBIAS: + if (val >= max_val || val < 0) + return -EINVAL; + + return ad5360_write(indio_dev, AD5360_CMD_WRITE_OFFSET, + chan->address, val, chan->scan_type.shift); + + case IIO_CHAN_INFO_CALIBSCALE: + if (val >= max_val || val < 0) + return -EINVAL; + + return ad5360_write(indio_dev, AD5360_CMD_WRITE_GAIN, + chan->address, val, chan->scan_type.shift); + + case IIO_CHAN_INFO_OFFSET: + if (val <= -max_val || val > 0) + return -EINVAL; + + val = -val; + + /* offset is supposed to have the same scale as raw, but it + * is always 14bits wide, so on a chip where the raw value has + * more bits, we need to shift offset. */ + val >>= (chan->scan_type.realbits - 14); + + /* There is one DAC offset register per vref. Changing one + * channels offset will also change the offset for all other + * channels which share the same vref supply. */ + ofs_index = ad5360_get_channel_vref_index(st, chan->channel); + return ad5360_write(indio_dev, AD5360_CMD_SPECIAL_FUNCTION, + AD5360_REG_SF_OFS(ofs_index), val, 0); + default: + break; + } + + return -EINVAL; +} + +static int ad5360_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad5360_state *st = iio_priv(indio_dev); + unsigned int ofs_index; + int scale_uv; + int ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = ad5360_read(indio_dev, AD5360_READBACK_X1A, + chan->address); + if (ret < 0) + return ret; + *val = ret >> chan->scan_type.shift; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + scale_uv = ad5360_get_channel_vref(st, chan->channel); + if (scale_uv < 0) + return scale_uv; + + /* vout = 4 * vref * dac_code */ + *val = scale_uv * 4 / 1000; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_CALIBBIAS: + ret = ad5360_read(indio_dev, AD5360_READBACK_OFFSET, + chan->address); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + ret = ad5360_read(indio_dev, AD5360_READBACK_GAIN, + chan->address); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + ofs_index = ad5360_get_channel_vref_index(st, chan->channel); + ret = ad5360_read(indio_dev, AD5360_READBACK_SF, + AD5360_REG_SF_OFS(ofs_index)); + if (ret < 0) + return ret; + + ret <<= (chan->scan_type.realbits - 14); + *val = -ret; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static const struct iio_info ad5360_info = { + .read_raw = ad5360_read_raw, + .write_raw = ad5360_write_raw, + .attrs = &ad5360_attribute_group, +}; + +static const char * const ad5360_vref_name[] = { + "vref0", "vref1", "vref2" +}; + +static int ad5360_alloc_channels(struct iio_dev *indio_dev) +{ + struct ad5360_state *st = iio_priv(indio_dev); + struct iio_chan_spec *channels; + unsigned int i; + + channels = kzalloc_objs(struct iio_chan_spec, + st->chip_info->num_channels); + + if (!channels) + return -ENOMEM; + + for (i = 0; i < st->chip_info->num_channels; ++i) { + channels[i] = st->chip_info->channel_template; + channels[i].channel = i; + channels[i].address = AD5360_CHAN_ADDR(i); + } + + indio_dev->channels = channels; + + return 0; +} + +static int ad5360_probe(struct spi_device *spi) +{ + enum ad5360_type type = spi_get_device_id(spi)->driver_data; + struct iio_dev *indio_dev; + struct ad5360_state *st; + unsigned int i; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) { + dev_err(&spi->dev, "Failed to allocate iio device\n"); + return -ENOMEM; + } + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + st->chip_info = &ad5360_chip_info_tbl[type]; + st->spi = spi; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &ad5360_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = st->chip_info->num_channels; + + mutex_init(&st->lock); + + ret = ad5360_alloc_channels(indio_dev); + if (ret) { + dev_err(&spi->dev, "Failed to allocate channel spec: %d\n", ret); + return ret; + } + + for (i = 0; i < st->chip_info->num_vrefs; ++i) + st->vref_reg[i].supply = ad5360_vref_name[i]; + + ret = devm_regulator_bulk_get(&st->spi->dev, st->chip_info->num_vrefs, + st->vref_reg); + if (ret) { + dev_err(&spi->dev, "Failed to request vref regulators: %d\n", ret); + goto error_free_channels; + } + + ret = regulator_bulk_enable(st->chip_info->num_vrefs, st->vref_reg); + if (ret) { + dev_err(&spi->dev, "Failed to enable vref regulators: %d\n", ret); + goto error_free_channels; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&spi->dev, "Failed to register iio device: %d\n", ret); + goto error_disable_reg; + } + + return 0; + +error_disable_reg: + regulator_bulk_disable(st->chip_info->num_vrefs, st->vref_reg); +error_free_channels: + kfree(indio_dev->channels); + + return ret; +} + +static void ad5360_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad5360_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + kfree(indio_dev->channels); + + regulator_bulk_disable(st->chip_info->num_vrefs, st->vref_reg); +} + +static const struct spi_device_id ad5360_ids[] = { + { .name = "ad5360", .driver_data = ID_AD5360 }, + { .name = "ad5361", .driver_data = ID_AD5361 }, + { .name = "ad5362", .driver_data = ID_AD5362 }, + { .name = "ad5363", .driver_data = ID_AD5363 }, + { .name = "ad5370", .driver_data = ID_AD5370 }, + { .name = "ad5371", .driver_data = ID_AD5371 }, + { .name = "ad5372", .driver_data = ID_AD5372 }, + { .name = "ad5373", .driver_data = ID_AD5373 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad5360_ids); + +static struct spi_driver ad5360_driver = { + .driver = { + .name = "ad5360", + }, + .probe = ad5360_probe, + .remove = ad5360_remove, + .id_table = ad5360_ids, +}; +module_spi_driver(ad5360_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Analog Devices AD5360/61/62/63/70/71/72/73 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5380.c b/drivers/iio/dac/ad5380.c new file mode 100644 index 000000000..4a5ad419f --- /dev/null +++ b/drivers/iio/dac/ad5380.c @@ -0,0 +1,595 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog devices AD5380, AD5381, AD5382, AD5383, AD5390, AD5391, AD5392 + * multi-channel Digital to Analog Converters driver + * + * Copyright 2011 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define AD5380_REG_DATA(x) (((x) << 2) | 3) +#define AD5380_REG_OFFSET(x) (((x) << 2) | 2) +#define AD5380_REG_GAIN(x) (((x) << 2) | 1) +#define AD5380_REG_SF_PWR_DOWN (8 << 2) +#define AD5380_REG_SF_PWR_UP (9 << 2) +#define AD5380_REG_SF_CTRL (12 << 2) + +#define AD5380_CTRL_PWR_DOWN_MODE_OFFSET 13 +#define AD5380_CTRL_INT_VREF_2V5 BIT(12) +#define AD5380_CTRL_INT_VREF_EN BIT(10) + +/** + * struct ad5380_chip_info - chip specific information + * @channel_template: channel specification template + * @num_channels: number of channels + * @int_vref: internal vref in uV + */ +struct ad5380_chip_info { + struct iio_chan_spec channel_template; + unsigned int num_channels; + unsigned int int_vref; +}; + +/** + * struct ad5380_state - driver instance specific data + * @regmap: regmap instance used by the device + * @chip_info: chip model specific constants, available modes etc + * @vref: actual reference voltage used in uA + * @pwr_down: whether the chip is currently in power down mode + * @lock: lock to protect the data buffer during regmap ops + */ +struct ad5380_state { + struct regmap *regmap; + const struct ad5380_chip_info *chip_info; + int vref; + bool pwr_down; + struct mutex lock; +}; + +enum ad5380_type { + ID_AD5380_3, + ID_AD5380_5, + ID_AD5381_3, + ID_AD5381_5, + ID_AD5382_3, + ID_AD5382_5, + ID_AD5383_3, + ID_AD5383_5, + ID_AD5390_3, + ID_AD5390_5, + ID_AD5391_3, + ID_AD5391_5, + ID_AD5392_3, + ID_AD5392_5, +}; + +static ssize_t ad5380_read_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, char *buf) +{ + struct ad5380_state *st = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", st->pwr_down); +} + +static ssize_t ad5380_write_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, const char *buf, + size_t len) +{ + struct ad5380_state *st = iio_priv(indio_dev); + bool pwr_down; + int ret; + + ret = kstrtobool(buf, &pwr_down); + if (ret) + return ret; + + mutex_lock(&st->lock); + + if (pwr_down) + ret = regmap_write(st->regmap, AD5380_REG_SF_PWR_DOWN, 0); + else + ret = regmap_write(st->regmap, AD5380_REG_SF_PWR_UP, 0); + + st->pwr_down = pwr_down; + + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +static const char * const ad5380_powerdown_modes[] = { + "100kohm_to_gnd", + "three_state", +}; + +static int ad5380_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad5380_state *st = iio_priv(indio_dev); + unsigned int mode; + int ret; + + ret = regmap_read(st->regmap, AD5380_REG_SF_CTRL, &mode); + if (ret) + return ret; + + mode = (mode >> AD5380_CTRL_PWR_DOWN_MODE_OFFSET) & 1; + + return mode; +} + +static int ad5380_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int mode) +{ + struct ad5380_state *st = iio_priv(indio_dev); + int ret; + + ret = regmap_update_bits(st->regmap, AD5380_REG_SF_CTRL, + 1 << AD5380_CTRL_PWR_DOWN_MODE_OFFSET, + mode << AD5380_CTRL_PWR_DOWN_MODE_OFFSET); + + return ret; +} + +static const struct iio_enum ad5380_powerdown_mode_enum = { + .items = ad5380_powerdown_modes, + .num_items = ARRAY_SIZE(ad5380_powerdown_modes), + .get = ad5380_get_powerdown_mode, + .set = ad5380_set_powerdown_mode, +}; + +static unsigned int ad5380_info_to_reg(struct iio_chan_spec const *chan, + long info) +{ + switch (info) { + case IIO_CHAN_INFO_RAW: + return AD5380_REG_DATA(chan->address); + case IIO_CHAN_INFO_CALIBBIAS: + return AD5380_REG_OFFSET(chan->address); + case IIO_CHAN_INFO_CALIBSCALE: + return AD5380_REG_GAIN(chan->address); + default: + break; + } + + return 0; +} + +static int ad5380_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long info) +{ + const unsigned int max_val = (1 << chan->scan_type.realbits); + struct ad5380_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_CALIBSCALE: + if (val >= max_val || val < 0) + return -EINVAL; + + return regmap_write(st->regmap, + ad5380_info_to_reg(chan, info), + val << chan->scan_type.shift); + case IIO_CHAN_INFO_CALIBBIAS: + val += (1 << chan->scan_type.realbits) / 2; + if (val >= max_val || val < 0) + return -EINVAL; + + return regmap_write(st->regmap, + AD5380_REG_OFFSET(chan->address), + val << chan->scan_type.shift); + default: + break; + } + return -EINVAL; +} + +static int ad5380_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long info) +{ + struct ad5380_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_CALIBSCALE: + ret = regmap_read(st->regmap, ad5380_info_to_reg(chan, info), + val); + if (ret) + return ret; + *val >>= chan->scan_type.shift; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + ret = regmap_read(st->regmap, AD5380_REG_OFFSET(chan->address), + val); + if (ret) + return ret; + *val >>= chan->scan_type.shift; + *val -= (1 << chan->scan_type.realbits) / 2; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 2 * st->vref; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + default: + break; + } + + return -EINVAL; +} + +static const struct iio_info ad5380_info = { + .read_raw = ad5380_read_raw, + .write_raw = ad5380_write_raw, +}; + +static const struct iio_chan_spec_ext_info ad5380_ext_info[] = { + { + .name = "powerdown", + .read = ad5380_read_dac_powerdown, + .write = ad5380_write_dac_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SHARED_BY_TYPE, + &ad5380_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, &ad5380_powerdown_mode_enum), + { } +}; + +#define AD5380_CHANNEL(_bits) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (_bits), \ + .storagebits = 16, \ + .shift = 14 - (_bits), \ + }, \ + .ext_info = ad5380_ext_info, \ +} + +static const struct ad5380_chip_info ad5380_chip_info_tbl[] = { + [ID_AD5380_3] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 40, + .int_vref = 1250, + }, + [ID_AD5380_5] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 40, + .int_vref = 2500, + }, + [ID_AD5381_3] = { + .channel_template = AD5380_CHANNEL(12), + .num_channels = 16, + .int_vref = 1250, + }, + [ID_AD5381_5] = { + .channel_template = AD5380_CHANNEL(12), + .num_channels = 16, + .int_vref = 2500, + }, + [ID_AD5382_3] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 32, + .int_vref = 1250, + }, + [ID_AD5382_5] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 32, + .int_vref = 2500, + }, + [ID_AD5383_3] = { + .channel_template = AD5380_CHANNEL(12), + .num_channels = 32, + .int_vref = 1250, + }, + [ID_AD5383_5] = { + .channel_template = AD5380_CHANNEL(12), + .num_channels = 32, + .int_vref = 2500, + }, + [ID_AD5390_3] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 16, + .int_vref = 1250, + }, + [ID_AD5390_5] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 16, + .int_vref = 2500, + }, + [ID_AD5391_3] = { + .channel_template = AD5380_CHANNEL(12), + .num_channels = 16, + .int_vref = 1250, + }, + [ID_AD5391_5] = { + .channel_template = AD5380_CHANNEL(12), + .num_channels = 16, + .int_vref = 2500, + }, + [ID_AD5392_3] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 8, + .int_vref = 1250, + }, + [ID_AD5392_5] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 8, + .int_vref = 2500, + }, +}; + +static int ad5380_alloc_channels(struct device *dev, struct iio_dev *indio_dev) +{ + struct ad5380_state *st = iio_priv(indio_dev); + struct iio_chan_spec *channels; + unsigned int i; + + channels = devm_kcalloc(dev, st->chip_info->num_channels, + sizeof(struct iio_chan_spec), GFP_KERNEL); + + if (!channels) + return -ENOMEM; + + for (i = 0; i < st->chip_info->num_channels; ++i) { + channels[i] = st->chip_info->channel_template; + channels[i].channel = i; + channels[i].address = i; + } + + indio_dev->channels = channels; + + return 0; +} + +static int ad5380_probe(struct device *dev, struct regmap *regmap, + enum ad5380_type type, const char *name) +{ + struct iio_dev *indio_dev; + struct ad5380_state *st; + unsigned int ctrl = 0; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->chip_info = &ad5380_chip_info_tbl[type]; + st->regmap = regmap; + + indio_dev->name = name; + indio_dev->info = &ad5380_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = st->chip_info->num_channels; + + mutex_init(&st->lock); + + ret = ad5380_alloc_channels(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to allocate channel spec\n"); + + if (st->chip_info->int_vref == 2500) + ctrl |= AD5380_CTRL_INT_VREF_2V5; + + ret = devm_regulator_get_enable_read_voltage(dev, "vref"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "Failed to get vref voltage\n"); + if (ret == -ENODEV) { + st->vref = st->chip_info->int_vref; + ctrl |= AD5380_CTRL_INT_VREF_EN; + } else { + st->vref = ret / 1000; + } + + ret = regmap_write(st->regmap, AD5380_REG_SF_CTRL, ctrl); + if (ret) + return dev_err_probe(dev, ret, "Failed to write to device\n"); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to register iio device\n"); + + return 0; +} + +static bool ad5380_reg_false(struct device *dev, unsigned int reg) +{ + return false; +} + +static const struct regmap_config ad5380_regmap_config = { + .reg_bits = 10, + .val_bits = 14, + + .max_register = AD5380_REG_DATA(40), + .cache_type = REGCACHE_MAPLE, + + .volatile_reg = ad5380_reg_false, + .readable_reg = ad5380_reg_false, +}; + +#if IS_ENABLED(CONFIG_SPI_MASTER) + +static int ad5380_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct regmap *regmap; + + regmap = devm_regmap_init_spi(spi, &ad5380_regmap_config); + + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return ad5380_probe(&spi->dev, regmap, id->driver_data, id->name); +} + +static const struct spi_device_id ad5380_spi_ids[] = { + { .name = "ad5380-3", .driver_data = ID_AD5380_3 }, + { .name = "ad5380-5", .driver_data = ID_AD5380_5 }, + { .name = "ad5381-3", .driver_data = ID_AD5381_3 }, + { .name = "ad5381-5", .driver_data = ID_AD5381_5 }, + { .name = "ad5382-3", .driver_data = ID_AD5382_3 }, + { .name = "ad5382-5", .driver_data = ID_AD5382_5 }, + { .name = "ad5383-3", .driver_data = ID_AD5383_3 }, + { .name = "ad5383-5", .driver_data = ID_AD5383_5 }, + { .name = "ad5384-3", .driver_data = ID_AD5380_3 }, + { .name = "ad5384-5", .driver_data = ID_AD5380_5 }, + { .name = "ad5390-3", .driver_data = ID_AD5390_3 }, + { .name = "ad5390-5", .driver_data = ID_AD5390_5 }, + { .name = "ad5391-3", .driver_data = ID_AD5391_3 }, + { .name = "ad5391-5", .driver_data = ID_AD5391_5 }, + { .name = "ad5392-3", .driver_data = ID_AD5392_3 }, + { .name = "ad5392-5", .driver_data = ID_AD5392_5 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad5380_spi_ids); + +static struct spi_driver ad5380_spi_driver = { + .driver = { + .name = "ad5380", + }, + .probe = ad5380_spi_probe, + .id_table = ad5380_spi_ids, +}; + +static inline int ad5380_spi_register_driver(void) +{ + return spi_register_driver(&ad5380_spi_driver); +} + +static inline void ad5380_spi_unregister_driver(void) +{ + spi_unregister_driver(&ad5380_spi_driver); +} + +#else + +static inline int ad5380_spi_register_driver(void) +{ + return 0; +} + +static inline void ad5380_spi_unregister_driver(void) +{ +} + +#endif + +#if IS_ENABLED(CONFIG_I2C) + +static int ad5380_i2c_probe(struct i2c_client *i2c) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(i2c); + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(i2c, &ad5380_regmap_config); + + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return ad5380_probe(&i2c->dev, regmap, id->driver_data, id->name); +} + +static const struct i2c_device_id ad5380_i2c_ids[] = { + { .name = "ad5380-3", .driver_data = ID_AD5380_3 }, + { .name = "ad5380-5", .driver_data = ID_AD5380_5 }, + { .name = "ad5381-3", .driver_data = ID_AD5381_3 }, + { .name = "ad5381-5", .driver_data = ID_AD5381_5 }, + { .name = "ad5382-3", .driver_data = ID_AD5382_3 }, + { .name = "ad5382-5", .driver_data = ID_AD5382_5 }, + { .name = "ad5383-3", .driver_data = ID_AD5383_3 }, + { .name = "ad5383-5", .driver_data = ID_AD5383_5 }, + { .name = "ad5384-3", .driver_data = ID_AD5380_3 }, + { .name = "ad5384-5", .driver_data = ID_AD5380_5 }, + { .name = "ad5390-3", .driver_data = ID_AD5390_3 }, + { .name = "ad5390-5", .driver_data = ID_AD5390_5 }, + { .name = "ad5391-3", .driver_data = ID_AD5391_3 }, + { .name = "ad5391-5", .driver_data = ID_AD5391_5 }, + { .name = "ad5392-3", .driver_data = ID_AD5392_3 }, + { .name = "ad5392-5", .driver_data = ID_AD5392_5 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ad5380_i2c_ids); + +static struct i2c_driver ad5380_i2c_driver = { + .driver = { + .name = "ad5380", + }, + .probe = ad5380_i2c_probe, + .id_table = ad5380_i2c_ids, +}; + +static inline int ad5380_i2c_register_driver(void) +{ + return i2c_add_driver(&ad5380_i2c_driver); +} + +static inline void ad5380_i2c_unregister_driver(void) +{ + i2c_del_driver(&ad5380_i2c_driver); +} + +#else + +static inline int ad5380_i2c_register_driver(void) +{ + return 0; +} + +static inline void ad5380_i2c_unregister_driver(void) +{ +} + +#endif + +static int __init ad5380_spi_init(void) +{ + int ret; + + ret = ad5380_spi_register_driver(); + if (ret) + return ret; + + ret = ad5380_i2c_register_driver(); + if (ret) { + ad5380_spi_unregister_driver(); + return ret; + } + + return 0; +} +module_init(ad5380_spi_init); + +static void __exit ad5380_spi_exit(void) +{ + ad5380_i2c_unregister_driver(); + ad5380_spi_unregister_driver(); + +} +module_exit(ad5380_spi_exit); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Analog Devices AD5380/81/82/83/84/90/91/92 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5421.c b/drivers/iio/dac/ad5421.c new file mode 100644 index 000000000..d9d7031c4 --- /dev/null +++ b/drivers/iio/dac/ad5421.c @@ -0,0 +1,537 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD5421 Digital to analog converters driver + * + * Copyright 2011 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +#define AD5421_REG_DAC_DATA 0x1 +#define AD5421_REG_CTRL 0x2 +#define AD5421_REG_OFFSET 0x3 +#define AD5421_REG_GAIN 0x4 +/* load dac and fault shared the same register number. Writing to it will cause + * a dac load command, reading from it will return the fault status register */ +#define AD5421_REG_LOAD_DAC 0x5 +#define AD5421_REG_FAULT 0x5 +#define AD5421_REG_FORCE_ALARM_CURRENT 0x6 +#define AD5421_REG_RESET 0x7 +#define AD5421_REG_START_CONVERSION 0x8 +#define AD5421_REG_NOOP 0x9 + +#define AD5421_CTRL_WATCHDOG_DISABLE BIT(12) +#define AD5421_CTRL_AUTO_FAULT_READBACK BIT(11) +#define AD5421_CTRL_MIN_CURRENT BIT(9) +#define AD5421_CTRL_ADC_SOURCE_TEMP BIT(8) +#define AD5421_CTRL_ADC_ENABLE BIT(7) +#define AD5421_CTRL_PWR_DOWN_INT_VREF BIT(6) + +#define AD5421_FAULT_SPI BIT(15) +#define AD5421_FAULT_PEC BIT(14) +#define AD5421_FAULT_OVER_CURRENT BIT(13) +#define AD5421_FAULT_UNDER_CURRENT BIT(12) +#define AD5421_FAULT_TEMP_OVER_140 BIT(11) +#define AD5421_FAULT_TEMP_OVER_100 BIT(10) +#define AD5421_FAULT_UNDER_VOLTAGE_6V BIT(9) +#define AD5421_FAULT_UNDER_VOLTAGE_12V BIT(8) + +/* These bits will cause the fault pin to go high */ +#define AD5421_FAULT_TRIGGER_IRQ \ + (AD5421_FAULT_SPI | AD5421_FAULT_PEC | AD5421_FAULT_OVER_CURRENT | \ + AD5421_FAULT_UNDER_CURRENT | AD5421_FAULT_TEMP_OVER_140) + +/** + * struct ad5421_state - driver instance specific data + * @spi: spi_device + * @ctrl: control register cache + * @current_range: current range which the device is configured for + * @data: spi transfer buffers + * @fault_mask: software masking of events + * @lock: lock to protect the data buffer during SPI ops + */ +struct ad5421_state { + struct spi_device *spi; + unsigned int ctrl; + enum ad5421_current_range current_range; + unsigned int fault_mask; + struct mutex lock; + + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + */ + union { + __be32 d32; + u8 d8[4]; + } data[2] __aligned(IIO_DMA_MINALIGN); +}; + +static const struct iio_event_spec ad5421_current_event[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_event_spec ad5421_temp_event[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec ad5421_channels[] = { + { + .type = IIO_CURRENT, + .indexed = 1, + .output = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + }, + .event_spec = ad5421_current_event, + .num_event_specs = ARRAY_SIZE(ad5421_current_event), + }, + { + .type = IIO_TEMP, + .channel = -1, + .event_spec = ad5421_temp_event, + .num_event_specs = ARRAY_SIZE(ad5421_temp_event), + }, +}; + +static int ad5421_write_unlocked(struct iio_dev *indio_dev, + unsigned int reg, unsigned int val) +{ + struct ad5421_state *st = iio_priv(indio_dev); + + st->data[0].d32 = cpu_to_be32((reg << 16) | val); + + return spi_write(st->spi, &st->data[0].d8[1], 3); +} + +static int ad5421_write(struct iio_dev *indio_dev, unsigned int reg, + unsigned int val) +{ + struct ad5421_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->lock); + ret = ad5421_write_unlocked(indio_dev, reg, val); + mutex_unlock(&st->lock); + + return ret; +} + +static int ad5421_read(struct iio_dev *indio_dev, unsigned int reg) +{ + struct ad5421_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer t[] = { + { + .tx_buf = &st->data[0].d8[1], + .len = 3, + .cs_change = 1, + }, { + .rx_buf = &st->data[1].d8[1], + .len = 3, + }, + }; + + mutex_lock(&st->lock); + + st->data[0].d32 = cpu_to_be32((1 << 23) | (reg << 16)); + + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); + if (ret >= 0) + ret = be32_to_cpu(st->data[1].d32) & 0xffff; + + mutex_unlock(&st->lock); + + return ret; +} + +static int ad5421_update_ctrl(struct iio_dev *indio_dev, unsigned int set, + unsigned int clr) +{ + struct ad5421_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->lock); + + st->ctrl &= ~clr; + st->ctrl |= set; + + ret = ad5421_write_unlocked(indio_dev, AD5421_REG_CTRL, st->ctrl); + + mutex_unlock(&st->lock); + + return ret; +} + +static irqreturn_t ad5421_fault_handler(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct ad5421_state *st = iio_priv(indio_dev); + unsigned int fault; + unsigned int old_fault = 0; + unsigned int events; + + fault = ad5421_read(indio_dev, AD5421_REG_FAULT); + if (!fault) + return IRQ_NONE; + + /* If we had a fault, this might mean that the DAC has lost its state + * and has been reset. Make sure that the control register actually + * contains what we expect it to contain. Otherwise the watchdog might + * be enabled and we get watchdog timeout faults, which will render the + * DAC unusable. */ + ad5421_update_ctrl(indio_dev, 0, 0); + + + /* The fault pin stays high as long as a fault condition is present and + * it is not possible to mask fault conditions. For certain fault + * conditions for example like over-temperature it takes some time + * until the fault condition disappears. If we would exit the interrupt + * handler immediately after handling the event it would be entered + * again instantly. Thus we fall back to polling in case we detect that + * a interrupt condition is still present. + */ + do { + /* 0xffff is a invalid value for the register and will only be + * read if there has been a communication error */ + if (fault == 0xffff) + fault = 0; + + /* we are only interested in new events */ + events = (old_fault ^ fault) & fault; + events &= st->fault_mask; + + if (events & AD5421_FAULT_OVER_CURRENT) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_CURRENT, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + } + + if (events & AD5421_FAULT_UNDER_CURRENT) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_CURRENT, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns(indio_dev)); + } + + if (events & AD5421_FAULT_TEMP_OVER_140) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, + 0, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + } + + old_fault = fault; + fault = ad5421_read(indio_dev, AD5421_REG_FAULT); + + /* still active? go to sleep for some time */ + if (fault & AD5421_FAULT_TRIGGER_IRQ) + msleep(1000); + + } while (fault & AD5421_FAULT_TRIGGER_IRQ); + + + return IRQ_HANDLED; +} + +static void ad5421_get_current_min_max(struct ad5421_state *st, + unsigned int *min, unsigned int *max) +{ + /* The current range is configured using external pins, which are + * usually hard-wired and not run-time switchable. */ + switch (st->current_range) { + case AD5421_CURRENT_RANGE_4mA_20mA: + *min = 4000; + *max = 20000; + break; + case AD5421_CURRENT_RANGE_3mA8_21mA: + *min = 3800; + *max = 21000; + break; + case AD5421_CURRENT_RANGE_3mA2_24mA: + *min = 3200; + *max = 24000; + break; + default: + *min = 0; + *max = 1; + break; + } +} + +static inline unsigned int ad5421_get_offset(struct ad5421_state *st) +{ + unsigned int min, max; + + ad5421_get_current_min_max(st, &min, &max); + return (min * (1 << 16)) / (max - min); +} + +static int ad5421_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long m) +{ + struct ad5421_state *st = iio_priv(indio_dev); + unsigned int min, max; + int ret; + + if (chan->type != IIO_CURRENT) + return -EINVAL; + + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = ad5421_read(indio_dev, AD5421_REG_DAC_DATA); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ad5421_get_current_min_max(st, &min, &max); + *val = max - min; + *val2 = (1 << 16) * 1000; + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_OFFSET: + *val = ad5421_get_offset(st); + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + ret = ad5421_read(indio_dev, AD5421_REG_OFFSET); + if (ret < 0) + return ret; + *val = ret - 32768; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + ret = ad5421_read(indio_dev, AD5421_REG_GAIN); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int ad5421_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + const unsigned int max_val = 1 << 16; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val >= max_val || val < 0) + return -EINVAL; + + return ad5421_write(indio_dev, AD5421_REG_DAC_DATA, val); + case IIO_CHAN_INFO_CALIBBIAS: + val += 32768; + if (val >= max_val || val < 0) + return -EINVAL; + + return ad5421_write(indio_dev, AD5421_REG_OFFSET, val); + case IIO_CHAN_INFO_CALIBSCALE: + if (val >= max_val || val < 0) + return -EINVAL; + + return ad5421_write(indio_dev, AD5421_REG_GAIN, val); + default: + break; + } + + return -EINVAL; +} + +static int ad5421_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct ad5421_state *st = iio_priv(indio_dev); + unsigned int mask; + + switch (chan->type) { + case IIO_CURRENT: + if (dir == IIO_EV_DIR_RISING) + mask = AD5421_FAULT_OVER_CURRENT; + else + mask = AD5421_FAULT_UNDER_CURRENT; + break; + case IIO_TEMP: + mask = AD5421_FAULT_TEMP_OVER_140; + break; + default: + return -EINVAL; + } + + mutex_lock(&st->lock); + if (state) + st->fault_mask |= mask; + else + st->fault_mask &= ~mask; + mutex_unlock(&st->lock); + + return 0; +} + +static int ad5421_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir) +{ + struct ad5421_state *st = iio_priv(indio_dev); + unsigned int mask; + + switch (chan->type) { + case IIO_CURRENT: + if (dir == IIO_EV_DIR_RISING) + mask = AD5421_FAULT_OVER_CURRENT; + else + mask = AD5421_FAULT_UNDER_CURRENT; + break; + case IIO_TEMP: + mask = AD5421_FAULT_TEMP_OVER_140; + break; + default: + return -EINVAL; + } + + return (bool)(st->fault_mask & mask); +} + +static int ad5421_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, int *val, + int *val2) +{ + int ret; + + switch (chan->type) { + case IIO_CURRENT: + ret = ad5421_read(indio_dev, AD5421_REG_DAC_DATA); + if (ret < 0) + return ret; + *val = ret; + break; + case IIO_TEMP: + *val = 140000; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; +} + +static const struct iio_info ad5421_info = { + .read_raw = ad5421_read_raw, + .write_raw = ad5421_write_raw, + .read_event_config = ad5421_read_event_config, + .write_event_config = ad5421_write_event_config, + .read_event_value = ad5421_read_event_value, +}; + +static int ad5421_probe(struct spi_device *spi) +{ + struct ad5421_platform_data *pdata = dev_get_platdata(&spi->dev); + struct iio_dev *indio_dev; + struct ad5421_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) { + dev_err(&spi->dev, "Failed to allocate iio device\n"); + return -ENOMEM; + } + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + st->spi = spi; + + indio_dev->name = "ad5421"; + indio_dev->info = &ad5421_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad5421_channels; + indio_dev->num_channels = ARRAY_SIZE(ad5421_channels); + + mutex_init(&st->lock); + + st->ctrl = AD5421_CTRL_WATCHDOG_DISABLE | + AD5421_CTRL_AUTO_FAULT_READBACK; + + if (pdata) { + st->current_range = pdata->current_range; + if (pdata->external_vref) + st->ctrl |= AD5421_CTRL_PWR_DOWN_INT_VREF; + } else { + st->current_range = AD5421_CURRENT_RANGE_4mA_20mA; + } + + /* write initial ctrl register value */ + ad5421_update_ctrl(indio_dev, 0, 0); + + if (spi->irq) { + ret = devm_request_threaded_irq(&spi->dev, spi->irq, + NULL, + ad5421_fault_handler, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "ad5421 fault", + indio_dev); + if (ret) + return ret; + } + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static struct spi_driver ad5421_driver = { + .driver = { + .name = "ad5421", + }, + .probe = ad5421_probe, +}; +module_spi_driver(ad5421_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Analog Devices AD5421 DAC"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:ad5421"); diff --git a/drivers/iio/dac/ad5446-i2c.c b/drivers/iio/dac/ad5446-i2c.c new file mode 100644 index 000000000..286695e24 --- /dev/null +++ b/drivers/iio/dac/ad5446-i2c.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AD5446 SPI I2C driver + * + * Copyright 2025 Analog Devices Inc. + */ +#include +#include +#include + +#include + +#include "ad5446.h" + +static int ad5622_write(struct ad5446_state *st, unsigned int val) +{ + struct i2c_client *client = to_i2c_client(st->dev); + int ret; + + st->d16 = cpu_to_be16(val); + + ret = i2c_master_send_dmasafe(client, (char *)&st->d16, sizeof(st->d16)); + if (ret < 0) + return ret; + if (ret != sizeof(st->d16)) + return -EIO; + + return 0; +} + +static int ad5446_i2c_probe(struct i2c_client *i2c) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(i2c); + const struct ad5446_chip_info *chip_info; + + chip_info = i2c_get_match_data(i2c); + if (!chip_info) + return -ENODEV; + + return ad5446_probe(&i2c->dev, id->name, chip_info); +} + +/* + * ad5446_supported_i2c_device_ids: + * The AD5620/40/60 parts are available in different fixed internal reference + * voltage options. The actual part numbers may look differently + * (and a bit cryptic), however this style is used to make clear which + * parts are supported here. + */ + +static const struct ad5446_chip_info ad5602_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(8, 16, 4), + .write = ad5622_write, +}; + +static const struct ad5446_chip_info ad5612_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(10, 16, 2), + .write = ad5622_write, +}; + +static const struct ad5446_chip_info ad5622_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 0), + .write = ad5622_write, +}; + +static const struct i2c_device_id ad5446_i2c_ids[] = { + { .name = "ad5301", .driver_data = (kernel_ulong_t)&ad5602_chip_info }, + { .name = "ad5311", .driver_data = (kernel_ulong_t)&ad5612_chip_info }, + { .name = "ad5321", .driver_data = (kernel_ulong_t)&ad5622_chip_info }, + { .name = "ad5602", .driver_data = (kernel_ulong_t)&ad5602_chip_info }, + { .name = "ad5612", .driver_data = (kernel_ulong_t)&ad5612_chip_info }, + { .name = "ad5622", .driver_data = (kernel_ulong_t)&ad5622_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ad5446_i2c_ids); + +static const struct of_device_id ad5446_i2c_of_ids[] = { + { .compatible = "adi,ad5301", .data = &ad5602_chip_info }, + { .compatible = "adi,ad5311", .data = &ad5612_chip_info }, + { .compatible = "adi,ad5321", .data = &ad5622_chip_info }, + { .compatible = "adi,ad5602", .data = &ad5602_chip_info }, + { .compatible = "adi,ad5612", .data = &ad5612_chip_info }, + { .compatible = "adi,ad5622", .data = &ad5622_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad5446_i2c_of_ids); + +static struct i2c_driver ad5446_i2c_driver = { + .driver = { + .name = "ad5446", + .of_match_table = ad5446_i2c_of_ids, + }, + .probe = ad5446_i2c_probe, + .id_table = ad5446_i2c_ids, +}; +module_i2c_driver(ad5446_i2c_driver); + +MODULE_AUTHOR("Nuno Sá "); +MODULE_DESCRIPTION("Analog Devices AD5622 and similar I2C DACs"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_AD5446"); diff --git a/drivers/iio/dac/ad5446-spi.c b/drivers/iio/dac/ad5446-spi.c new file mode 100644 index 000000000..85bb88290 --- /dev/null +++ b/drivers/iio/dac/ad5446-spi.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AD5446 SPI DAC driver + * + * Copyright 2025 Analog Devices Inc. + */ +#include +#include +#include +#include + +#include + +#include "ad5446.h" + +static int ad5446_write(struct ad5446_state *st, unsigned int val) +{ + struct spi_device *spi = to_spi_device(st->dev); + + st->d16 = cpu_to_be16(val); + + return spi_write(spi, &st->d16, sizeof(st->d16)); +} + +static int ad5660_write(struct ad5446_state *st, unsigned int val) +{ + struct spi_device *spi = to_spi_device(st->dev); + + put_unaligned_be24(val, st->d24); + + return spi_write(spi, st->d24, sizeof(st->d24)); +} + +static int ad5446_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + const struct ad5446_chip_info *chip_info; + + chip_info = spi_get_device_match_data(spi); + if (!chip_info) + return -ENODEV; + + return ad5446_probe(&spi->dev, id->name, chip_info); +} + +/* + * ad5446_supported_spi_device_ids: + * The AD5620/40/60 parts are available in different fixed internal reference + * voltage options. The actual part numbers may look differently + * (and a bit cryptic), however this style is used to make clear which + * parts are supported here. + */ + +static const struct ad5446_chip_info ad5300_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(8, 16, 4), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5310_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(10, 16, 2), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5320_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 0), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5444_chip_info = { + .channel = AD5446_CHANNEL(12, 16, 2), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5446_chip_info = { + .channel = AD5446_CHANNEL(14, 16, 0), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5450_chip_info = { + .channel = AD5446_CHANNEL(8, 16, 6), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5451_chip_info = { + .channel = AD5446_CHANNEL(10, 16, 4), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5541a_chip_info = { + .channel = AD5446_CHANNEL(16, 16, 0), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5512a_chip_info = { + .channel = AD5446_CHANNEL(12, 16, 4), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5553_chip_info = { + .channel = AD5446_CHANNEL(14, 16, 0), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5601_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(8, 16, 6), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5611_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(10, 16, 4), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5621_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 2), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5641_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(14, 16, 0), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5620_2500_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 2), + .int_vref_mv = 2500, + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5620_1250_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 2), + .int_vref_mv = 1250, + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5640_2500_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(14, 16, 0), + .int_vref_mv = 2500, + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5640_1250_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(14, 16, 0), + .int_vref_mv = 1250, + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5660_2500_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(16, 16, 0), + .int_vref_mv = 2500, + .write = ad5660_write, +}; + +static const struct ad5446_chip_info ad5660_1250_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(16, 16, 0), + .int_vref_mv = 1250, + .write = ad5660_write, +}; + +static const struct ad5446_chip_info ad5662_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(16, 16, 0), + .write = ad5660_write, +}; + +static const struct spi_device_id ad5446_spi_ids[] = { + { .name = "ad5300", .driver_data = (kernel_ulong_t)&ad5300_chip_info }, + { .name = "ad5310", .driver_data = (kernel_ulong_t)&ad5310_chip_info }, + { .name = "ad5320", .driver_data = (kernel_ulong_t)&ad5320_chip_info }, + { .name = "ad5444", .driver_data = (kernel_ulong_t)&ad5444_chip_info }, + { .name = "ad5446", .driver_data = (kernel_ulong_t)&ad5446_chip_info }, + { .name = "ad5450", .driver_data = (kernel_ulong_t)&ad5450_chip_info }, + { .name = "ad5451", .driver_data = (kernel_ulong_t)&ad5451_chip_info }, + { .name = "ad5452", .driver_data = (kernel_ulong_t)&ad5444_chip_info }, /* ad5452 is compatible to the ad5444 */ + { .name = "ad5453", .driver_data = (kernel_ulong_t)&ad5446_chip_info }, /* ad5453 is compatible to the ad5446 */ + { .name = "ad5512a", .driver_data = (kernel_ulong_t)&ad5512a_chip_info }, + { .name = "ad5541a", .driver_data = (kernel_ulong_t)&ad5541a_chip_info }, + { .name = "ad5542", .driver_data = (kernel_ulong_t)&ad5541a_chip_info }, /* ad5541a and ad5542 are compatible */ + { .name = "ad5542a", .driver_data = (kernel_ulong_t)&ad5541a_chip_info }, /* ad5541a and ad5542a are compatible */ + { .name = "ad5543", .driver_data = (kernel_ulong_t)&ad5541a_chip_info }, /* ad5541a and ad5543 are compatible */ + { .name = "ad5553", .driver_data = (kernel_ulong_t)&ad5553_chip_info }, + { .name = "ad5600", .driver_data = (kernel_ulong_t)&ad5541a_chip_info }, /* ad5541a and ad5600 are compatible */ + { .name = "ad5601", .driver_data = (kernel_ulong_t)&ad5601_chip_info }, + { .name = "ad5611", .driver_data = (kernel_ulong_t)&ad5611_chip_info }, + { .name = "ad5621", .driver_data = (kernel_ulong_t)&ad5621_chip_info }, + { .name = "ad5641", .driver_data = (kernel_ulong_t)&ad5641_chip_info }, + { .name = "ad5620-2500", .driver_data = (kernel_ulong_t)&ad5620_2500_chip_info }, /* AD5620/40/60: */ + /* part numbers may look differently */ + { .name = "ad5620-1250", .driver_data = (kernel_ulong_t)&ad5620_1250_chip_info }, + { .name = "ad5640-2500", .driver_data = (kernel_ulong_t)&ad5640_2500_chip_info }, + { .name = "ad5640-1250", .driver_data = (kernel_ulong_t)&ad5640_1250_chip_info }, + { .name = "ad5660-2500", .driver_data = (kernel_ulong_t)&ad5660_2500_chip_info }, + { .name = "ad5660-1250", .driver_data = (kernel_ulong_t)&ad5660_1250_chip_info }, + { .name = "ad5662", .driver_data = (kernel_ulong_t)&ad5662_chip_info }, + { .name = "dac081s101", .driver_data = (kernel_ulong_t)&ad5300_chip_info }, /* compatible Texas Instruments chips */ + { .name = "dac101s101", .driver_data = (kernel_ulong_t)&ad5310_chip_info }, + { .name = "dac121s101", .driver_data = (kernel_ulong_t)&ad5320_chip_info }, + { .name = "dac7512", .driver_data = (kernel_ulong_t)&ad5320_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad5446_spi_ids); + +static const struct of_device_id ad5446_of_ids[] = { + { .compatible = "adi,ad5300", .data = &ad5300_chip_info }, + { .compatible = "adi,ad5310", .data = &ad5310_chip_info }, + { .compatible = "adi,ad5320", .data = &ad5320_chip_info }, + { .compatible = "adi,ad5444", .data = &ad5444_chip_info }, + { .compatible = "adi,ad5446", .data = &ad5446_chip_info }, + { .compatible = "adi,ad5450", .data = &ad5450_chip_info }, + { .compatible = "adi,ad5451", .data = &ad5451_chip_info }, + { .compatible = "adi,ad5452", .data = &ad5444_chip_info }, + { .compatible = "adi,ad5453", .data = &ad5446_chip_info }, + { .compatible = "adi,ad5512a", .data = &ad5512a_chip_info }, + { .compatible = "adi,ad5541a", .data = &ad5541a_chip_info }, + { .compatible = "adi,ad5542", .data = &ad5541a_chip_info }, + { .compatible = "adi,ad5542a", .data = &ad5541a_chip_info }, + { .compatible = "adi,ad5543", .data = &ad5541a_chip_info }, + { .compatible = "adi,ad5553", .data = &ad5553_chip_info }, + { .compatible = "adi,ad5600", .data = &ad5541a_chip_info }, + { .compatible = "adi,ad5601", .data = &ad5601_chip_info }, + { .compatible = "adi,ad5611", .data = &ad5611_chip_info }, + { .compatible = "adi,ad5621", .data = &ad5621_chip_info }, + { .compatible = "adi,ad5641", .data = &ad5641_chip_info }, + { .compatible = "adi,ad5620-2500", .data = &ad5620_2500_chip_info }, + { .compatible = "adi,ad5620-1250", .data = &ad5620_1250_chip_info }, + { .compatible = "adi,ad5640-2500", .data = &ad5640_2500_chip_info }, + { .compatible = "adi,ad5640-1250", .data = &ad5640_1250_chip_info }, + { .compatible = "adi,ad5660-2500", .data = &ad5660_2500_chip_info }, + { .compatible = "adi,ad5660-1250", .data = &ad5660_1250_chip_info }, + { .compatible = "adi,ad5662", .data = &ad5662_chip_info }, + { .compatible = "ti,dac081s101", .data = &ad5300_chip_info }, + { .compatible = "ti,dac101s101", .data = &ad5310_chip_info }, + { .compatible = "ti,dac121s101", .data = &ad5320_chip_info }, + { .compatible = "ti,dac7512", .data = &ad5320_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad5446_of_ids); + +static struct spi_driver ad5446_spi_driver = { + .driver = { + .name = "ad5446", + .of_match_table = ad5446_of_ids, + }, + .probe = ad5446_spi_probe, + .id_table = ad5446_spi_ids, +}; +module_spi_driver(ad5446_spi_driver); + +MODULE_AUTHOR("Nuno Sá "); +MODULE_DESCRIPTION("Analog Devices AD5446 and similar SPI DACs"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_AD5446"); diff --git a/drivers/iio/dac/ad5446.c b/drivers/iio/dac/ad5446.c new file mode 100644 index 000000000..46a2eadb1 --- /dev/null +++ b/drivers/iio/dac/ad5446.c @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * AD5446 CORE DAC driver + * + * Copyright 2010 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ad5446.h" + +#define MODE_PWRDWN_1k 0x1 +#define MODE_PWRDWN_100k 0x2 +#define MODE_PWRDWN_TRISTATE 0x3 + +static const char * const ad5446_powerdown_modes[] = { + "1kohm_to_gnd", "100kohm_to_gnd", "three_state" +}; + +static int ad5446_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct ad5446_state *st = iio_priv(indio_dev); + + st->pwr_down_mode = mode + 1; + + return 0; +} + +static int ad5446_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad5446_state *st = iio_priv(indio_dev); + + return st->pwr_down_mode - 1; +} + +static const struct iio_enum ad5446_powerdown_mode_enum = { + .items = ad5446_powerdown_modes, + .num_items = ARRAY_SIZE(ad5446_powerdown_modes), + .get = ad5446_get_powerdown_mode, + .set = ad5446_set_powerdown_mode, +}; + +static ssize_t ad5446_read_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ad5446_state *st = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", st->pwr_down); +} + +static ssize_t ad5446_write_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ad5446_state *st = iio_priv(indio_dev); + unsigned int shift; + unsigned int val; + bool powerdown; + int ret; + + ret = kstrtobool(buf, &powerdown); + if (ret) + return ret; + + guard(mutex)(&st->lock); + st->pwr_down = powerdown; + + if (st->pwr_down) { + shift = chan->scan_type.realbits + chan->scan_type.shift; + val = st->pwr_down_mode << shift; + } else { + val = st->cached_val; + } + + ret = st->chip_info->write(st, val); + if (ret) + return ret; + + return len; +} + +const struct iio_chan_spec_ext_info ad5446_ext_info_powerdown[] = { + { + .name = "powerdown", + .read = ad5446_read_dac_powerdown, + .write = ad5446_write_dac_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SEPARATE, &ad5446_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, &ad5446_powerdown_mode_enum), + { } +}; +EXPORT_SYMBOL_NS_GPL(ad5446_ext_info_powerdown, "IIO_AD5446"); + +static int ad5446_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad5446_state *st = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + *val = st->cached_val >> chan->scan_type.shift; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = st->vref_mv; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + +static int ad5446_write_dac_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int val) +{ + struct ad5446_state *st = iio_priv(indio_dev); + + if (val >= (1 << chan->scan_type.realbits) || val < 0) + return -EINVAL; + + val <<= chan->scan_type.shift; + guard(mutex)(&st->lock); + + st->cached_val = val; + if (st->pwr_down) + return 0; + + return st->chip_info->write(st, val); +} + +static int ad5446_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_RAW: + return ad5446_write_dac_raw(indio_dev, chan, val); + default: + return -EINVAL; + } +} + +static const struct iio_info ad5446_info = { + .read_raw = ad5446_read_raw, + .write_raw = ad5446_write_raw, +}; + +int ad5446_probe(struct device *dev, const char *name, + const struct ad5446_chip_info *chip_info) +{ + struct ad5446_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->chip_info = chip_info; + + st->dev = dev; + + indio_dev->name = name; + indio_dev->info = &ad5446_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = &st->chip_info->channel; + indio_dev->num_channels = 1; + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + st->pwr_down_mode = MODE_PWRDWN_1k; + + ret = devm_regulator_get_enable_read_voltage(dev, "vcc"); + if (ret < 0 && ret != -ENODEV) + return ret; + if (ret == -ENODEV) { + if (!chip_info->int_vref_mv) + return dev_err_probe(dev, ret, + "reference voltage unspecified\n"); + + st->vref_mv = chip_info->int_vref_mv; + } else { + st->vref_mv = ret / 1000; + } + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS_GPL(ad5446_probe, "IIO_AD5446"); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices CORE AD5446 DAC and similar devices"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5446.h b/drivers/iio/dac/ad5446.h new file mode 100644 index 000000000..6ba31d98f --- /dev/null +++ b/drivers/iio/dac/ad5446.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_AD5446_H +#define _LINUX_AD5446_H + +#include +#include +#include +#include +#include + +struct device; + +extern const struct iio_chan_spec_ext_info ad5446_ext_info_powerdown[]; + +#define _AD5446_CHANNEL(bits, storage, _shift, ext) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = (storage), \ + .shift = (_shift), \ + }, \ + .ext_info = (ext), \ +} + +#define AD5446_CHANNEL(bits, storage, shift) \ + _AD5446_CHANNEL(bits, storage, shift, NULL) + +#define AD5446_CHANNEL_POWERDOWN(bits, storage, shift) \ + _AD5446_CHANNEL(bits, storage, shift, ad5446_ext_info_powerdown) + +/** + * struct ad5446_state - driver instance specific data + * @dev: this device + * @chip_info: chip model specific constants, available modes etc + * @vref_mv: actual reference voltage used + * @cached_val: store/retrieve values during power down + * @pwr_down_mode: power down mode (1k, 100k or tristate) + * @pwr_down: true if the device is in power down + * @lock: lock to protect the data buffer during write ops + */ +struct ad5446_state { + struct device *dev; + const struct ad5446_chip_info *chip_info; + unsigned short vref_mv; + unsigned int cached_val; + unsigned int pwr_down_mode; + unsigned int pwr_down; + /* mutex to protect device shared data */ + struct mutex lock; + union { + __be16 d16; + u8 d24[3]; + } __aligned(IIO_DMA_MINALIGN); +}; + +/** + * struct ad5446_chip_info - chip specific information + * @channel: channel spec for the DAC + * @int_vref_mv: AD5620/40/60: the internal reference voltage + * @write: chip specific helper function to write to the register + */ +struct ad5446_chip_info { + struct iio_chan_spec channel; + u16 int_vref_mv; + int (*write)(struct ad5446_state *st, unsigned int val); +}; + +int ad5446_probe(struct device *dev, const char *name, + const struct ad5446_chip_info *chip_info); + +#endif diff --git a/drivers/iio/dac/ad5449.c b/drivers/iio/dac/ad5449.c new file mode 100644 index 000000000..19e66bc69 --- /dev/null +++ b/drivers/iio/dac/ad5449.c @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD5415, AD5426, AD5429, AD5432, AD5439, AD5443, AD5449 Digital to Analog + * Converter driver. + * + * Copyright 2012 Analog Devices Inc. + * Author: Lars-Peter Clausen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define AD5449_MAX_CHANNELS 2 +#define AD5449_MAX_VREFS 2 + +#define AD5449_CMD_NOOP 0x0 +#define AD5449_CMD_LOAD_AND_UPDATE(x) (0x1 + (x) * 3) +#define AD5449_CMD_READ(x) (0x2 + (x) * 3) +#define AD5449_CMD_LOAD(x) (0x3 + (x) * 3) +#define AD5449_CMD_CTRL 13 + +#define AD5449_CTRL_SDO_OFFSET 10 +#define AD5449_CTRL_DAISY_CHAIN BIT(9) +#define AD5449_CTRL_HCLR_TO_MIDSCALE BIT(8) +#define AD5449_CTRL_SAMPLE_RISING BIT(7) + +/** + * struct ad5449_chip_info - chip specific information + * @channels: Channel specification + * @num_channels: Number of channels + * @has_ctrl: Chip has a control register + */ +struct ad5449_chip_info { + const struct iio_chan_spec *channels; + unsigned int num_channels; + bool has_ctrl; +}; + +/** + * struct ad5449 - driver instance specific data + * @spi: the SPI device for this driver instance + * @chip_info: chip model specific constants, available modes etc + * @vref_reg: vref supply regulators + * @has_sdo: whether the SDO line is connected + * @dac_cache: Cache for the DAC values + * @data: spi transfer buffers + * @lock: lock to protect the data buffer during SPI ops + */ +struct ad5449 { + struct spi_device *spi; + const struct ad5449_chip_info *chip_info; + struct regulator_bulk_data vref_reg[AD5449_MAX_VREFS]; + struct mutex lock; + + bool has_sdo; + uint16_t dac_cache[AD5449_MAX_CHANNELS]; + + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + */ + __be16 data[2] __aligned(IIO_DMA_MINALIGN); +}; + +enum ad5449_type { + ID_AD5426, + ID_AD5429, + ID_AD5432, + ID_AD5439, + ID_AD5443, + ID_AD5449, +}; + +static int ad5449_write(struct iio_dev *indio_dev, unsigned int addr, + unsigned int val) +{ + struct ad5449 *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->lock); + st->data[0] = cpu_to_be16((addr << 12) | val); + ret = spi_write(st->spi, st->data, 2); + mutex_unlock(&st->lock); + + return ret; +} + +static int ad5449_read(struct iio_dev *indio_dev, unsigned int addr, + unsigned int *val) +{ + struct ad5449 *st = iio_priv(indio_dev); + int ret; + struct spi_transfer t[] = { + { + .tx_buf = &st->data[0], + .len = 2, + .cs_change = 1, + }, { + .tx_buf = &st->data[1], + .rx_buf = &st->data[1], + .len = 2, + }, + }; + + mutex_lock(&st->lock); + st->data[0] = cpu_to_be16(addr << 12); + st->data[1] = cpu_to_be16(AD5449_CMD_NOOP); + + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); + if (ret < 0) + goto out_unlock; + + *val = be16_to_cpu(st->data[1]); + +out_unlock: + mutex_unlock(&st->lock); + return ret; +} + +static int ad5449_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long info) +{ + struct ad5449 *st = iio_priv(indio_dev); + struct regulator_bulk_data *reg; + int scale_uv; + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (st->has_sdo) { + ret = ad5449_read(indio_dev, + AD5449_CMD_READ(chan->address), val); + if (ret) + return ret; + *val &= 0xfff; + } else { + *val = st->dac_cache[chan->address]; + } + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + reg = &st->vref_reg[chan->channel]; + scale_uv = regulator_get_voltage(reg->consumer); + if (scale_uv < 0) + return scale_uv; + + *val = scale_uv / 1000; + *val2 = chan->scan_type.realbits; + + return IIO_VAL_FRACTIONAL_LOG2; + default: + break; + } + + return -EINVAL; +} + +static int ad5449_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long info) +{ + struct ad5449 *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (val < 0 || val >= (1 << chan->scan_type.realbits)) + return -EINVAL; + + ret = ad5449_write(indio_dev, + AD5449_CMD_LOAD_AND_UPDATE(chan->address), + val << chan->scan_type.shift); + if (ret == 0) + st->dac_cache[chan->address] = val; + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info ad5449_info = { + .read_raw = ad5449_read_raw, + .write_raw = ad5449_write_raw, +}; + +#define AD5449_CHANNEL(chan, bits) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .address = (chan), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 12 - (bits), \ + }, \ +} + +#define DECLARE_AD5449_CHANNELS(name, bits) \ +const struct iio_chan_spec name[] = { \ + AD5449_CHANNEL(0, bits), \ + AD5449_CHANNEL(1, bits), \ +} + +static DECLARE_AD5449_CHANNELS(ad5429_channels, 8); +static DECLARE_AD5449_CHANNELS(ad5439_channels, 10); +static DECLARE_AD5449_CHANNELS(ad5449_channels, 12); + +static const struct ad5449_chip_info ad5449_chip_info[] = { + [ID_AD5426] = { + .channels = ad5429_channels, + .num_channels = 1, + .has_ctrl = false, + }, + [ID_AD5429] = { + .channels = ad5429_channels, + .num_channels = 2, + .has_ctrl = true, + }, + [ID_AD5432] = { + .channels = ad5439_channels, + .num_channels = 1, + .has_ctrl = false, + }, + [ID_AD5439] = { + .channels = ad5439_channels, + .num_channels = 2, + .has_ctrl = true, + }, + [ID_AD5443] = { + .channels = ad5449_channels, + .num_channels = 1, + .has_ctrl = false, + }, + [ID_AD5449] = { + .channels = ad5449_channels, + .num_channels = 2, + .has_ctrl = true, + }, +}; + +static const char *ad5449_vref_name(struct ad5449 *st, int n) +{ + if (st->chip_info->num_channels == 1) + return "VREF"; + + if (n == 0) + return "VREFA"; + else + return "VREFB"; +} + +static int ad5449_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct iio_dev *indio_dev; + struct ad5449 *st; + unsigned int i; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + st->chip_info = &ad5449_chip_info[id->driver_data]; + st->spi = spi; + + for (i = 0; i < st->chip_info->num_channels; ++i) + st->vref_reg[i].supply = ad5449_vref_name(st, i); + + ret = devm_regulator_bulk_get(&spi->dev, st->chip_info->num_channels, + st->vref_reg); + if (ret) + return ret; + + ret = regulator_bulk_enable(st->chip_info->num_channels, st->vref_reg); + if (ret) + return ret; + + indio_dev->name = id->name; + indio_dev->info = &ad5449_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + + mutex_init(&st->lock); + + if (st->chip_info->has_ctrl) { + st->has_sdo = true; + ad5449_write(indio_dev, AD5449_CMD_CTRL, 0x0); + } + + ret = iio_device_register(indio_dev); + if (ret) + goto error_disable_reg; + + return 0; + +error_disable_reg: + regulator_bulk_disable(st->chip_info->num_channels, st->vref_reg); + + return ret; +} + +static void ad5449_spi_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad5449 *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + regulator_bulk_disable(st->chip_info->num_channels, st->vref_reg); +} + +static const struct spi_device_id ad5449_spi_ids[] = { + { .name = "ad5415", .driver_data = ID_AD5449 }, + { .name = "ad5426", .driver_data = ID_AD5426 }, + { .name = "ad5429", .driver_data = ID_AD5429 }, + { .name = "ad5432", .driver_data = ID_AD5432 }, + { .name = "ad5439", .driver_data = ID_AD5439 }, + { .name = "ad5443", .driver_data = ID_AD5443 }, + { .name = "ad5449", .driver_data = ID_AD5449 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad5449_spi_ids); + +static struct spi_driver ad5449_spi_driver = { + .driver = { + .name = "ad5449", + }, + .probe = ad5449_spi_probe, + .remove = ad5449_spi_remove, + .id_table = ad5449_spi_ids, +}; +module_spi_driver(ad5449_spi_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Analog Devices AD5449 and similar DACs"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5504.c b/drivers/iio/dac/ad5504.c new file mode 100644 index 000000000..ad1a74678 --- /dev/null +++ b/drivers/iio/dac/ad5504.c @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD5504, AD5501 High Voltage Digital to Analog Converter + * + * Copyright 2011 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define AD5504_RES_MASK GENMASK(11, 0) +#define AD5504_CMD_READ BIT(15) +#define AD5504_CMD_WRITE 0 +#define AD5504_ADDR(addr) ((addr) << 12) + +/* Registers */ +#define AD5504_ADDR_NOOP 0 +#define AD5504_ADDR_DAC(x) ((x) + 1) +#define AD5504_ADDR_ALL_DAC 5 +#define AD5504_ADDR_CTRL 7 + +/* Control Register */ +#define AD5504_DAC_PWR(ch) ((ch) << 2) +#define AD5504_DAC_PWRDWN_MODE(mode) ((mode) << 6) +#define AD5504_DAC_PWRDN_20K 0 +#define AD5504_DAC_PWRDN_3STATE 1 + +/** + * struct ad5504_state - driver instance specific data + * @spi: spi_device + * @reg: supply regulator + * @vref_mv: actual reference voltage used + * @pwr_down_mask: power down mask + * @pwr_down_mode: current power down mode + * @data: transfer buffer + */ +struct ad5504_state { + struct spi_device *spi; + struct regulator *reg; + unsigned short vref_mv; + unsigned pwr_down_mask; + unsigned pwr_down_mode; + + __be16 data[2] __aligned(IIO_DMA_MINALIGN); +}; + +/* + * ad5504_supported_device_ids: + */ +enum ad5504_supported_device_ids { + ID_AD5504, + ID_AD5501, +}; + +static int ad5504_spi_write(struct ad5504_state *st, u8 addr, u16 val) +{ + st->data[0] = cpu_to_be16(AD5504_CMD_WRITE | AD5504_ADDR(addr) | + (val & AD5504_RES_MASK)); + + return spi_write(st->spi, &st->data[0], 2); +} + +static int ad5504_spi_read(struct ad5504_state *st, u8 addr) +{ + int ret; + struct spi_transfer t = { + .tx_buf = &st->data[0], + .rx_buf = &st->data[1], + .len = 2, + }; + + st->data[0] = cpu_to_be16(AD5504_CMD_READ | AD5504_ADDR(addr)); + ret = spi_sync_transfer(st->spi, &t, 1); + if (ret < 0) + return ret; + + return be16_to_cpu(st->data[1]) & AD5504_RES_MASK; +} + +static int ad5504_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad5504_state *st = iio_priv(indio_dev); + int ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = ad5504_spi_read(st, chan->address); + if (ret < 0) + return ret; + + *val = ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = st->vref_mv; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + +static int ad5504_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad5504_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val >= (1 << chan->scan_type.realbits) || val < 0) + return -EINVAL; + + return ad5504_spi_write(st, chan->address, val); + default: + return -EINVAL; + } +} + +static const char * const ad5504_powerdown_modes[] = { + "20kohm_to_gnd", + "three_state", +}; + +static int ad5504_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad5504_state *st = iio_priv(indio_dev); + + return st->pwr_down_mode; +} + +static int ad5504_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int mode) +{ + struct ad5504_state *st = iio_priv(indio_dev); + + st->pwr_down_mode = mode; + + return 0; +} + +static const struct iio_enum ad5504_powerdown_mode_enum = { + .items = ad5504_powerdown_modes, + .num_items = ARRAY_SIZE(ad5504_powerdown_modes), + .get = ad5504_get_powerdown_mode, + .set = ad5504_set_powerdown_mode, +}; + +static ssize_t ad5504_read_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, char *buf) +{ + struct ad5504_state *st = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", + !(st->pwr_down_mask & (1 << chan->channel))); +} + +static ssize_t ad5504_write_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, const char *buf, + size_t len) +{ + bool pwr_down; + int ret; + struct ad5504_state *st = iio_priv(indio_dev); + + ret = kstrtobool(buf, &pwr_down); + if (ret) + return ret; + + if (pwr_down) + st->pwr_down_mask &= ~(1 << chan->channel); + else + st->pwr_down_mask |= (1 << chan->channel); + + ret = ad5504_spi_write(st, AD5504_ADDR_CTRL, + AD5504_DAC_PWRDWN_MODE(st->pwr_down_mode) | + AD5504_DAC_PWR(st->pwr_down_mask)); + + /* writes to the CTRL register must be followed by a NOOP */ + ad5504_spi_write(st, AD5504_ADDR_NOOP, 0); + + return ret ? ret : len; +} + +static IIO_CONST_ATTR(temp0_thresh_rising_value, "110000"); +static IIO_CONST_ATTR(temp0_thresh_rising_en, "1"); + +static struct attribute *ad5504_ev_attributes[] = { + &iio_const_attr_temp0_thresh_rising_value.dev_attr.attr, + &iio_const_attr_temp0_thresh_rising_en.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad5504_ev_attribute_group = { + .attrs = ad5504_ev_attributes, +}; + +static irqreturn_t ad5504_event_handler(int irq, void *private) +{ + iio_push_event(private, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(private)); + + return IRQ_HANDLED; +} + +static const struct iio_info ad5504_info = { + .write_raw = ad5504_write_raw, + .read_raw = ad5504_read_raw, + .event_attrs = &ad5504_ev_attribute_group, +}; + +static const struct iio_chan_spec_ext_info ad5504_ext_info[] = { + { + .name = "powerdown", + .read = ad5504_read_dac_powerdown, + .write = ad5504_write_dac_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SHARED_BY_TYPE, + &ad5504_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, &ad5504_powerdown_mode_enum), + { } +}; + +#define AD5504_CHANNEL(_chan) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (_chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .address = AD5504_ADDR_DAC(_chan), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 12, \ + .storagebits = 16, \ + }, \ + .ext_info = ad5504_ext_info, \ +} + +static const struct iio_chan_spec ad5504_channels[] = { + AD5504_CHANNEL(0), + AD5504_CHANNEL(1), + AD5504_CHANNEL(2), + AD5504_CHANNEL(3), +}; + +static int ad5504_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + const struct ad5504_platform_data *pdata = dev_get_platdata(dev); + struct iio_dev *indio_dev; + struct ad5504_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + ret = devm_regulator_get_enable_read_voltage(dev, "vcc"); + if (ret < 0 && ret != -ENODEV) + return ret; + if (ret == -ENODEV) { + if (pdata->vref_mv) + st->vref_mv = pdata->vref_mv; + else + dev_warn(dev, "reference voltage unspecified\n"); + } else { + st->vref_mv = ret / 1000; + } + + st->spi = spi; + indio_dev->name = spi_get_device_id(st->spi)->name; + indio_dev->info = &ad5504_info; + if (spi_get_device_id(st->spi)->driver_data == ID_AD5501) + indio_dev->num_channels = 1; + else + indio_dev->num_channels = 4; + indio_dev->channels = ad5504_channels; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (spi->irq) { + ret = devm_request_threaded_irq(dev, spi->irq, + NULL, + &ad5504_event_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + spi_get_device_id(st->spi)->name, + indio_dev); + if (ret) + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id ad5504_id[] = { + { .name = "ad5504", .driver_data = ID_AD5504 }, + { .name = "ad5501", .driver_data = ID_AD5501 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad5504_id); + +static struct spi_driver ad5504_driver = { + .driver = { + .name = "ad5504", + }, + .probe = ad5504_probe, + .id_table = ad5504_id, +}; +module_spi_driver(ad5504_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD5501/AD5501 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5592r-base.c b/drivers/iio/dac/ad5592r-base.c new file mode 100644 index 000000000..4720733d6 --- /dev/null +++ b/drivers/iio/dac/ad5592r-base.c @@ -0,0 +1,666 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD5592R Digital <-> Analog converters driver + * + * Copyright 2014-2016 Analog Devices Inc. + * Author: Paul Cercueil + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ad5592r-base.h" + +static int ad5592r_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct ad5592r_state *st = gpiochip_get_data(chip); + int ret = 0; + u8 val = 0; + + scoped_guard(mutex, &st->gpio_lock) { + if (st->gpio_out & BIT(offset)) + val = st->gpio_val; + else + ret = st->ops->gpio_read(st, &val); + } + + if (ret < 0) + return ret; + + return !!(val & BIT(offset)); +} + +static int ad5592r_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) +{ + struct ad5592r_state *st = gpiochip_get_data(chip); + + guard(mutex)(&st->gpio_lock); + + if (value) + st->gpio_val |= BIT(offset); + else + st->gpio_val &= ~BIT(offset); + + return st->ops->reg_write(st, AD5592R_REG_GPIO_SET, st->gpio_val); +} + +static int ad5592r_gpio_direction_input(struct gpio_chip *chip, unsigned offset) +{ + struct ad5592r_state *st = gpiochip_get_data(chip); + int ret; + + guard(mutex)(&st->gpio_lock); + + st->gpio_out &= ~BIT(offset); + st->gpio_in |= BIT(offset); + + ret = st->ops->reg_write(st, AD5592R_REG_GPIO_OUT_EN, st->gpio_out); + if (ret < 0) + return ret; + + return st->ops->reg_write(st, AD5592R_REG_GPIO_IN_EN, st->gpio_in); +} + +static int ad5592r_gpio_direction_output(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct ad5592r_state *st = gpiochip_get_data(chip); + int ret; + + guard(mutex)(&st->gpio_lock); + + if (value) + st->gpio_val |= BIT(offset); + else + st->gpio_val &= ~BIT(offset); + + st->gpio_in &= ~BIT(offset); + st->gpio_out |= BIT(offset); + + ret = st->ops->reg_write(st, AD5592R_REG_GPIO_SET, st->gpio_val); + if (ret < 0) + return ret; + + ret = st->ops->reg_write(st, AD5592R_REG_GPIO_OUT_EN, st->gpio_out); + if (ret < 0) + return ret; + + return st->ops->reg_write(st, AD5592R_REG_GPIO_IN_EN, st->gpio_in); +} + +static int ad5592r_gpio_request(struct gpio_chip *chip, unsigned offset) +{ + struct ad5592r_state *st = gpiochip_get_data(chip); + + if (!(st->gpio_map & BIT(offset))) { + dev_err(st->dev, "GPIO %d is reserved by alternate function\n", + offset); + return -ENODEV; + } + + return 0; +} + +static const char * const ad5592r_gpio_names[] = { + "GPIO0", "GPIO1", "GPIO2", "GPIO3", "GPIO4", "GPIO5", "GPIO6", "GPIO7", +}; + +static int ad5592r_gpio_init(struct ad5592r_state *st) +{ + if (!st->gpio_map) + return 0; + + st->gpiochip.label = dev_name(st->dev); + st->gpiochip.base = -1; + st->gpiochip.ngpio = 8; + st->gpiochip.parent = st->dev; + st->gpiochip.can_sleep = true; + st->gpiochip.direction_input = ad5592r_gpio_direction_input; + st->gpiochip.direction_output = ad5592r_gpio_direction_output; + st->gpiochip.get = ad5592r_gpio_get; + st->gpiochip.set = ad5592r_gpio_set; + st->gpiochip.request = ad5592r_gpio_request; + st->gpiochip.owner = THIS_MODULE; + st->gpiochip.names = ad5592r_gpio_names; + + mutex_init(&st->gpio_lock); + + return gpiochip_add_data(&st->gpiochip, st); +} + +static void ad5592r_gpio_cleanup(struct ad5592r_state *st) +{ + if (st->gpio_map) + gpiochip_remove(&st->gpiochip); + + mutex_destroy(&st->gpio_lock); +} + +static int ad5592r_reset(struct ad5592r_state *st) +{ + struct gpio_desc *gpio; + + gpio = devm_gpiod_get_optional(st->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(gpio)) + return PTR_ERR(gpio); + + if (gpio) { + udelay(1); + gpiod_set_value(gpio, 1); + } else { + scoped_guard(mutex, &st->lock) + /* Writing this magic value resets the device */ + st->ops->reg_write(st, AD5592R_REG_RESET, 0xdac); + } + + udelay(250); + + return 0; +} + +static int ad5592r_get_vref(struct ad5592r_state *st) +{ + int ret; + + if (st->reg) { + ret = regulator_get_voltage(st->reg); + if (ret < 0) + return ret; + + return ret / 1000; + } else { + return 2500; + } +} + +static int ad5592r_set_channel_modes(struct ad5592r_state *st) +{ + const struct ad5592r_rw_ops *ops = st->ops; + int ret; + unsigned i; + u8 pulldown = 0, tristate = 0, dac = 0, adc = 0; + u16 read_back; + + for (i = 0; i < st->num_channels; i++) { + switch (st->channel_modes[i]) { + case CH_MODE_DAC: + dac |= BIT(i); + break; + + case CH_MODE_ADC: + adc |= BIT(i); + break; + + case CH_MODE_DAC_AND_ADC: + dac |= BIT(i); + adc |= BIT(i); + break; + + case CH_MODE_GPIO: + st->gpio_map |= BIT(i); + st->gpio_in |= BIT(i); /* Default to input */ + break; + + case CH_MODE_UNUSED: + default: + switch (st->channel_offstate[i]) { + case CH_OFFSTATE_OUT_TRISTATE: + tristate |= BIT(i); + break; + + case CH_OFFSTATE_OUT_LOW: + st->gpio_out |= BIT(i); + break; + + case CH_OFFSTATE_OUT_HIGH: + st->gpio_out |= BIT(i); + st->gpio_val |= BIT(i); + break; + + case CH_OFFSTATE_PULLDOWN: + default: + pulldown |= BIT(i); + break; + } + } + } + + guard(mutex)(&st->lock); + + /* Pull down unused pins to GND */ + ret = ops->reg_write(st, AD5592R_REG_PULLDOWN, pulldown); + if (ret) + return ret; + + ret = ops->reg_write(st, AD5592R_REG_TRISTATE, tristate); + if (ret) + return ret; + + /* Configure pins that we use */ + ret = ops->reg_write(st, AD5592R_REG_DAC_EN, dac); + if (ret) + return ret; + + ret = ops->reg_write(st, AD5592R_REG_ADC_EN, adc); + if (ret) + return ret; + + ret = ops->reg_write(st, AD5592R_REG_GPIO_SET, st->gpio_val); + if (ret) + return ret; + + ret = ops->reg_write(st, AD5592R_REG_GPIO_OUT_EN, st->gpio_out); + if (ret) + return ret; + + ret = ops->reg_write(st, AD5592R_REG_GPIO_IN_EN, st->gpio_in); + if (ret) + return ret; + + /* Verify that we can read back at least one register */ + ret = ops->reg_read(st, AD5592R_REG_ADC_EN, &read_back); + if (!ret && (read_back & 0xff) != adc) + return -EIO; + + return 0; +} + +static int ad5592r_reset_channel_modes(struct ad5592r_state *st) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(st->channel_modes); i++) + st->channel_modes[i] = CH_MODE_UNUSED; + + return ad5592r_set_channel_modes(st); +} + +static int ad5592r_write_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + struct ad5592r_state *st = iio_priv(iio_dev); + int ret = 0; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + + if (val >= (1 << chan->scan_type.realbits) || val < 0) + return -EINVAL; + + if (!chan->output) + return -EINVAL; + + scoped_guard(mutex, &st->lock) { + ret = st->ops->write_dac(st, chan->channel, val); + if (!ret) + st->cached_dac[chan->channel] = val; + } + return ret; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_VOLTAGE) { + bool gain; + + if (val == st->scale_avail[0][0] && + val2 == st->scale_avail[0][1]) + gain = false; + else if (val == st->scale_avail[1][0] && + val2 == st->scale_avail[1][1]) + gain = true; + else + return -EINVAL; + + guard(mutex)(&st->lock); + + ret = st->ops->reg_read(st, AD5592R_REG_CTRL, + &st->cached_gp_ctrl); + if (ret < 0) + return ret; + + if (chan->output) { + if (gain) + st->cached_gp_ctrl |= + AD5592R_REG_CTRL_DAC_RANGE; + else + st->cached_gp_ctrl &= + ~AD5592R_REG_CTRL_DAC_RANGE; + } else { + if (gain) + st->cached_gp_ctrl |= + AD5592R_REG_CTRL_ADC_RANGE; + else + st->cached_gp_ctrl &= + ~AD5592R_REG_CTRL_ADC_RANGE; + } + + return st->ops->reg_write(st, AD5592R_REG_CTRL, + st->cached_gp_ctrl); + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ad5592r_read_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long m) +{ + struct ad5592r_state *st = iio_priv(iio_dev); + u16 read_val = 0; + int ret = 0, mult = 0; + + switch (m) { + case IIO_CHAN_INFO_RAW: + if (!chan->output) { + scoped_guard(mutex, &st->lock) + ret = st->ops->read_adc(st, chan->channel, + &read_val); + if (ret) + return ret; + + if ((read_val >> 12 & 0x7) != (chan->channel & 0x7)) { + dev_err(st->dev, "Error while reading channel %u\n", + chan->channel); + return -EIO; + } + + read_val &= GENMASK(11, 0); + + } else { + scoped_guard(mutex, &st->lock) + read_val = st->cached_dac[chan->channel]; + } + + dev_dbg(st->dev, "Channel %u read: 0x%04hX\n", + chan->channel, read_val); + + *val = (int) read_val; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = ad5592r_get_vref(st); + + if (chan->type == IIO_TEMP) { + s64 tmp = *val * (3767897513LL / 25LL); + *val = div_s64_rem(tmp, 1000000000LL, val2); + + return IIO_VAL_INT_PLUS_NANO; + } + + scoped_guard(mutex, &st->lock) { + if (chan->output) + mult = !!(st->cached_gp_ctrl & + AD5592R_REG_CTRL_DAC_RANGE); + else + mult = !!(st->cached_gp_ctrl & + AD5592R_REG_CTRL_ADC_RANGE); + } + + *val *= ++mult; + + *val2 = chan->scan_type.realbits; + + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: { + ret = ad5592r_get_vref(st); + + guard(mutex)(&st->lock); + + if (st->cached_gp_ctrl & AD5592R_REG_CTRL_ADC_RANGE) + *val = (-34365 * 25) / ret; + else + *val = (-75365 * 25) / ret; + + return IIO_VAL_INT; + } + default: + return -EINVAL; + } +} + +static int ad5592r_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + + default: + return IIO_VAL_INT_PLUS_MICRO; + } + + return -EINVAL; +} + +static const struct iio_info ad5592r_info = { + .read_raw = ad5592r_read_raw, + .write_raw = ad5592r_write_raw, + .write_raw_get_fmt = ad5592r_write_raw_get_fmt, +}; + +static ssize_t ad5592r_show_scale_available(struct iio_dev *iio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ad5592r_state *st = iio_priv(iio_dev); + + return sprintf(buf, "%d.%09u %d.%09u\n", + st->scale_avail[0][0], st->scale_avail[0][1], + st->scale_avail[1][0], st->scale_avail[1][1]); +} + +static const struct iio_chan_spec_ext_info ad5592r_ext_info[] = { + { + .name = "scale_available", + .read = ad5592r_show_scale_available, + .shared = IIO_SHARED_BY_TYPE, + }, + { } +}; + +static void ad5592r_setup_channel(struct iio_dev *iio_dev, + struct iio_chan_spec *chan, bool output, unsigned id) +{ + chan->type = IIO_VOLTAGE; + chan->indexed = 1; + chan->output = output; + chan->channel = id; + chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); + chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE); + chan->scan_type.sign = 'u'; + chan->scan_type.realbits = 12; + chan->scan_type.storagebits = 16; + chan->ext_info = ad5592r_ext_info; +} + +static int ad5592r_alloc_channels(struct iio_dev *iio_dev) +{ + struct ad5592r_state *st = iio_priv(iio_dev); + unsigned i, curr_channel = 0, + num_channels = st->num_channels; + struct iio_chan_spec *channels; + struct fwnode_handle *child; + u32 reg, tmp; + int ret; + + device_for_each_child_node(st->dev, child) { + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret || reg >= ARRAY_SIZE(st->channel_modes)) + continue; + + ret = fwnode_property_read_u32(child, "adi,mode", &tmp); + if (!ret) + st->channel_modes[reg] = tmp; + + ret = fwnode_property_read_u32(child, "adi,off-state", &tmp); + if (!ret) + st->channel_offstate[reg] = tmp; + } + + channels = devm_kcalloc(st->dev, + 1 + 2 * num_channels, sizeof(*channels), + GFP_KERNEL); + if (!channels) + return -ENOMEM; + + for (i = 0; i < num_channels; i++) { + switch (st->channel_modes[i]) { + case CH_MODE_DAC: + ad5592r_setup_channel(iio_dev, &channels[curr_channel], + true, i); + curr_channel++; + break; + + case CH_MODE_ADC: + ad5592r_setup_channel(iio_dev, &channels[curr_channel], + false, i); + curr_channel++; + break; + + case CH_MODE_DAC_AND_ADC: + ad5592r_setup_channel(iio_dev, &channels[curr_channel], + true, i); + curr_channel++; + ad5592r_setup_channel(iio_dev, &channels[curr_channel], + false, i); + curr_channel++; + break; + + default: + continue; + } + } + + channels[curr_channel].type = IIO_TEMP; + channels[curr_channel].channel = 8; + channels[curr_channel].info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET); + curr_channel++; + + iio_dev->num_channels = curr_channel; + iio_dev->channels = channels; + + return 0; +} + +static void ad5592r_init_scales(struct ad5592r_state *st, int vref_mV) +{ + s64 tmp = (s64)vref_mV * 1000000000LL >> 12; + + st->scale_avail[0][0] = + div_s64_rem(tmp, 1000000000LL, &st->scale_avail[0][1]); + st->scale_avail[1][0] = + div_s64_rem(tmp * 2, 1000000000LL, &st->scale_avail[1][1]); +} + +int ad5592r_probe(struct device *dev, const char *name, + const struct ad5592r_rw_ops *ops) +{ + struct iio_dev *iio_dev; + struct ad5592r_state *st; + int ret; + + iio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!iio_dev) + return -ENOMEM; + + st = iio_priv(iio_dev); + st->dev = dev; + st->ops = ops; + st->num_channels = 8; + dev_set_drvdata(dev, iio_dev); + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + st->reg = devm_regulator_get_optional(dev, "vref"); + if (IS_ERR(st->reg)) { + if ((PTR_ERR(st->reg) != -ENODEV) && dev_fwnode(dev)) + return PTR_ERR(st->reg); + + st->reg = NULL; + } else { + ret = regulator_enable(st->reg); + if (ret) + return ret; + } + + iio_dev->name = name; + iio_dev->info = &ad5592r_info; + iio_dev->modes = INDIO_DIRECT_MODE; + + ad5592r_init_scales(st, ad5592r_get_vref(st)); + + ret = ad5592r_reset(st); + if (ret) + goto error_disable_reg; + + ret = ops->reg_write(st, AD5592R_REG_PD, + (st->reg == NULL) ? AD5592R_REG_PD_EN_REF : 0); + if (ret) + goto error_disable_reg; + + ret = ad5592r_alloc_channels(iio_dev); + if (ret) + goto error_disable_reg; + + ret = ad5592r_set_channel_modes(st); + if (ret) + goto error_reset_ch_modes; + + ret = iio_device_register(iio_dev); + if (ret) + goto error_reset_ch_modes; + + ret = ad5592r_gpio_init(st); + if (ret) + goto error_dev_unregister; + + return 0; + +error_dev_unregister: + iio_device_unregister(iio_dev); + +error_reset_ch_modes: + ad5592r_reset_channel_modes(st); + +error_disable_reg: + if (st->reg) + regulator_disable(st->reg); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(ad5592r_probe, "IIO_AD5592R"); + +void ad5592r_remove(struct device *dev) +{ + struct iio_dev *iio_dev = dev_get_drvdata(dev); + struct ad5592r_state *st = iio_priv(iio_dev); + + iio_device_unregister(iio_dev); + ad5592r_reset_channel_modes(st); + ad5592r_gpio_cleanup(st); + + if (st->reg) + regulator_disable(st->reg); +} +EXPORT_SYMBOL_NS_GPL(ad5592r_remove, "IIO_AD5592R"); + +MODULE_AUTHOR("Paul Cercueil "); +MODULE_DESCRIPTION("Analog Devices AD5592R multi-channel converters"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5592r-base.h b/drivers/iio/dac/ad5592r-base.h new file mode 100644 index 000000000..cc7be426c --- /dev/null +++ b/drivers/iio/dac/ad5592r-base.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AD5592R / AD5593R Digital <-> Analog converters driver + * + * Copyright 2015-2016 Analog Devices Inc. + * Author: Paul Cercueil + */ + +#ifndef __DRIVERS_IIO_DAC_AD5592R_BASE_H__ +#define __DRIVERS_IIO_DAC_AD5592R_BASE_H__ + +#include +#include +#include +#include + +#include + +struct device; +struct ad5592r_state; + +enum ad5592r_registers { + AD5592R_REG_NOOP = 0x0, + AD5592R_REG_DAC_READBACK = 0x1, + AD5592R_REG_ADC_SEQ = 0x2, + AD5592R_REG_CTRL = 0x3, + AD5592R_REG_ADC_EN = 0x4, + AD5592R_REG_DAC_EN = 0x5, + AD5592R_REG_PULLDOWN = 0x6, + AD5592R_REG_LDAC = 0x7, + AD5592R_REG_GPIO_OUT_EN = 0x8, + AD5592R_REG_GPIO_SET = 0x9, + AD5592R_REG_GPIO_IN_EN = 0xA, + AD5592R_REG_PD = 0xB, + AD5592R_REG_OPEN_DRAIN = 0xC, + AD5592R_REG_TRISTATE = 0xD, + AD5592R_REG_RESET = 0xF, +}; + +#define AD5592R_REG_PD_EN_REF BIT(9) +#define AD5592R_REG_CTRL_ADC_RANGE BIT(5) +#define AD5592R_REG_CTRL_DAC_RANGE BIT(4) + +struct ad5592r_rw_ops { + int (*write_dac)(struct ad5592r_state *st, unsigned chan, u16 value); + int (*read_adc)(struct ad5592r_state *st, unsigned chan, u16 *value); + int (*reg_write)(struct ad5592r_state *st, u8 reg, u16 value); + int (*reg_read)(struct ad5592r_state *st, u8 reg, u16 *value); + int (*gpio_read)(struct ad5592r_state *st, u8 *value); +}; + +struct ad5592r_state { + struct device *dev; + struct regulator *reg; + struct gpio_chip gpiochip; + struct mutex gpio_lock; /* Protect cached gpio_out, gpio_val, etc. */ + struct mutex lock; + unsigned int num_channels; + const struct ad5592r_rw_ops *ops; + int scale_avail[2][2]; + u16 cached_dac[8]; + u16 cached_gp_ctrl; + u8 channel_modes[8]; + u8 channel_offstate[8]; + u8 gpio_map; + u8 gpio_out; + u8 gpio_in; + u8 gpio_val; + + __be16 spi_msg __aligned(IIO_DMA_MINALIGN); + __be16 spi_msg_nop; +}; + +int ad5592r_probe(struct device *dev, const char *name, + const struct ad5592r_rw_ops *ops); +void ad5592r_remove(struct device *dev); + +#endif /* __DRIVERS_IIO_DAC_AD5592R_BASE_H__ */ diff --git a/drivers/iio/dac/ad5592r.c b/drivers/iio/dac/ad5592r.c new file mode 100644 index 000000000..88197bc6a --- /dev/null +++ b/drivers/iio/dac/ad5592r.c @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD5592R Digital <-> Analog converters driver + * + * Copyright 2015-2016 Analog Devices Inc. + * Author: Paul Cercueil + */ + +#include "ad5592r-base.h" + +#include +#include +#include + +#define AD5592R_GPIO_READBACK_EN BIT(10) +#define AD5592R_LDAC_READBACK_EN BIT(6) + +static int ad5592r_spi_wnop_r16(struct ad5592r_state *st, __be16 *buf) +{ + struct spi_device *spi = container_of(st->dev, struct spi_device, dev); + struct spi_transfer t = { + .tx_buf = &st->spi_msg_nop, + .rx_buf = buf, + .len = 2 + }; + + st->spi_msg_nop = 0; /* NOP */ + + return spi_sync_transfer(spi, &t, 1); +} + +static int ad5592r_write_dac(struct ad5592r_state *st, unsigned chan, u16 value) +{ + struct spi_device *spi = container_of(st->dev, struct spi_device, dev); + + st->spi_msg = cpu_to_be16(BIT(15) | (chan << 12) | value); + + return spi_write(spi, &st->spi_msg, sizeof(st->spi_msg)); +} + +static int ad5592r_read_adc(struct ad5592r_state *st, unsigned chan, u16 *value) +{ + struct spi_device *spi = container_of(st->dev, struct spi_device, dev); + int ret; + + st->spi_msg = cpu_to_be16((AD5592R_REG_ADC_SEQ << 11) | BIT(chan)); + + ret = spi_write(spi, &st->spi_msg, sizeof(st->spi_msg)); + if (ret) + return ret; + + /* + * Invalid data: + * See Figure 40. Single-Channel ADC Conversion Sequence + */ + ret = ad5592r_spi_wnop_r16(st, &st->spi_msg); + if (ret) + return ret; + + ret = ad5592r_spi_wnop_r16(st, &st->spi_msg); + if (ret) + return ret; + + *value = be16_to_cpu(st->spi_msg); + + return 0; +} + +static int ad5592r_reg_write(struct ad5592r_state *st, u8 reg, u16 value) +{ + struct spi_device *spi = container_of(st->dev, struct spi_device, dev); + + st->spi_msg = cpu_to_be16((reg << 11) | value); + + return spi_write(spi, &st->spi_msg, sizeof(st->spi_msg)); +} + +static int ad5592r_reg_read(struct ad5592r_state *st, u8 reg, u16 *value) +{ + struct spi_device *spi = container_of(st->dev, struct spi_device, dev); + int ret; + + st->spi_msg = cpu_to_be16((AD5592R_REG_LDAC << 11) | + AD5592R_LDAC_READBACK_EN | (reg << 2)); + + ret = spi_write(spi, &st->spi_msg, sizeof(st->spi_msg)); + if (ret) + return ret; + + ret = ad5592r_spi_wnop_r16(st, &st->spi_msg); + if (ret) + return ret; + + *value = be16_to_cpu(st->spi_msg); + + return 0; +} + +static int ad5592r_gpio_read(struct ad5592r_state *st, u8 *value) +{ + int ret; + + ret = ad5592r_reg_write(st, AD5592R_REG_GPIO_IN_EN, + AD5592R_GPIO_READBACK_EN | st->gpio_in); + if (ret) + return ret; + + ret = ad5592r_spi_wnop_r16(st, &st->spi_msg); + if (ret) + return ret; + + *value = (u8) be16_to_cpu(st->spi_msg); + + return 0; +} + +static const struct ad5592r_rw_ops ad5592r_rw_ops = { + .write_dac = ad5592r_write_dac, + .read_adc = ad5592r_read_adc, + .reg_write = ad5592r_reg_write, + .reg_read = ad5592r_reg_read, + .gpio_read = ad5592r_gpio_read, +}; + +static int ad5592r_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + + return ad5592r_probe(&spi->dev, id->name, &ad5592r_rw_ops); +} + +static void ad5592r_spi_remove(struct spi_device *spi) +{ + ad5592r_remove(&spi->dev); +} + +static const struct spi_device_id ad5592r_spi_ids[] = { + { .name = "ad5592r", }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad5592r_spi_ids); + +static const struct of_device_id ad5592r_of_match[] = { + { .compatible = "adi,ad5592r", }, + { } +}; +MODULE_DEVICE_TABLE(of, ad5592r_of_match); + +static const struct acpi_device_id ad5592r_acpi_match[] = { + {"ADS5592", }, + { } +}; +MODULE_DEVICE_TABLE(acpi, ad5592r_acpi_match); + +static struct spi_driver ad5592r_spi_driver = { + .driver = { + .name = "ad5592r", + .of_match_table = ad5592r_of_match, + .acpi_match_table = ad5592r_acpi_match, + }, + .probe = ad5592r_spi_probe, + .remove = ad5592r_spi_remove, + .id_table = ad5592r_spi_ids, +}; +module_spi_driver(ad5592r_spi_driver); + +MODULE_AUTHOR("Paul Cercueil "); +MODULE_DESCRIPTION("Analog Devices AD5592R multi-channel converters"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_AD5592R"); diff --git a/drivers/iio/dac/ad5593r.c b/drivers/iio/dac/ad5593r.c new file mode 100644 index 000000000..3e4215f0c --- /dev/null +++ b/drivers/iio/dac/ad5593r.c @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD5593R Digital <-> Analog converters driver + * + * Copyright 2015-2016 Analog Devices Inc. + * Author: Paul Cercueil + */ + +#include "ad5592r-base.h" + +#include +#include +#include + +#include + +#define AD5593R_MODE_CONF (0 << 4) +#define AD5593R_MODE_DAC_WRITE (1 << 4) +#define AD5593R_MODE_ADC_READBACK (4 << 4) +#define AD5593R_MODE_DAC_READBACK (5 << 4) +#define AD5593R_MODE_GPIO_READBACK (6 << 4) +#define AD5593R_MODE_REG_READBACK (7 << 4) + +static int ad5593r_read_word(struct i2c_client *i2c, u8 reg, u16 *value) +{ + int ret; + u8 buf[2]; + + ret = i2c_smbus_write_byte(i2c, reg); + if (ret < 0) + return ret; + + ret = i2c_master_recv(i2c, buf, sizeof(buf)); + if (ret < 0) + return ret; + + *value = get_unaligned_be16(buf); + + return 0; +} + +static int ad5593r_write_dac(struct ad5592r_state *st, unsigned chan, u16 value) +{ + struct i2c_client *i2c = to_i2c_client(st->dev); + + return i2c_smbus_write_word_swapped(i2c, + AD5593R_MODE_DAC_WRITE | chan, value); +} + +static int ad5593r_read_adc(struct ad5592r_state *st, unsigned chan, u16 *value) +{ + struct i2c_client *i2c = to_i2c_client(st->dev); + s32 val; + + val = i2c_smbus_write_word_swapped(i2c, + AD5593R_MODE_CONF | AD5592R_REG_ADC_SEQ, BIT(chan)); + if (val < 0) + return (int) val; + + return ad5593r_read_word(i2c, AD5593R_MODE_ADC_READBACK, value); +} + +static int ad5593r_reg_write(struct ad5592r_state *st, u8 reg, u16 value) +{ + struct i2c_client *i2c = to_i2c_client(st->dev); + + return i2c_smbus_write_word_swapped(i2c, + AD5593R_MODE_CONF | reg, value); +} + +static int ad5593r_reg_read(struct ad5592r_state *st, u8 reg, u16 *value) +{ + struct i2c_client *i2c = to_i2c_client(st->dev); + + return ad5593r_read_word(i2c, AD5593R_MODE_REG_READBACK | reg, value); +} + +static int ad5593r_gpio_read(struct ad5592r_state *st, u8 *value) +{ + struct i2c_client *i2c = to_i2c_client(st->dev); + u16 val; + int ret; + + ret = ad5593r_read_word(i2c, AD5593R_MODE_GPIO_READBACK, &val); + if (ret) + return ret; + + *value = (u8) val; + + return 0; +} + +static const struct ad5592r_rw_ops ad5593r_rw_ops = { + .write_dac = ad5593r_write_dac, + .read_adc = ad5593r_read_adc, + .reg_write = ad5593r_reg_write, + .reg_read = ad5593r_reg_read, + .gpio_read = ad5593r_gpio_read, +}; + +static int ad5593r_i2c_probe(struct i2c_client *i2c) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(i2c); + if (!i2c_check_functionality(i2c->adapter, + I2C_FUNC_SMBUS_BYTE | I2C_FUNC_I2C)) + return -EOPNOTSUPP; + + return ad5592r_probe(&i2c->dev, id->name, &ad5593r_rw_ops); +} + +static void ad5593r_i2c_remove(struct i2c_client *i2c) +{ + ad5592r_remove(&i2c->dev); +} + +static const struct i2c_device_id ad5593r_i2c_ids[] = { + { .name = "ad5593r", }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ad5593r_i2c_ids); + +static const struct of_device_id ad5593r_of_match[] = { + { .compatible = "adi,ad5593r", }, + { } +}; +MODULE_DEVICE_TABLE(of, ad5593r_of_match); + +static const struct acpi_device_id ad5593r_acpi_match[] = { + {"ADS5593", }, + { } +}; +MODULE_DEVICE_TABLE(acpi, ad5593r_acpi_match); + +static struct i2c_driver ad5593r_driver = { + .driver = { + .name = "ad5593r", + .of_match_table = ad5593r_of_match, + .acpi_match_table = ad5593r_acpi_match, + }, + .probe = ad5593r_i2c_probe, + .remove = ad5593r_i2c_remove, + .id_table = ad5593r_i2c_ids, +}; +module_i2c_driver(ad5593r_driver); + +MODULE_AUTHOR("Paul Cercueil "); +MODULE_DESCRIPTION("Analog Devices AD5593R multi-channel converters"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_AD5592R"); diff --git a/drivers/iio/dac/ad5624r.h b/drivers/iio/dac/ad5624r.h new file mode 100644 index 000000000..098fb5a76 --- /dev/null +++ b/drivers/iio/dac/ad5624r.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AD5624R SPI DAC driver + * + * Copyright 2010-2011 Analog Devices Inc. + */ +#ifndef SPI_AD5624R_H_ +#define SPI_AD5624R_H_ + +#define AD5624R_DAC_CHANNELS 4 + +#define AD5624R_ADDR_DAC0 0x0 +#define AD5624R_ADDR_DAC1 0x1 +#define AD5624R_ADDR_DAC2 0x2 +#define AD5624R_ADDR_DAC3 0x3 +#define AD5624R_ADDR_ALL_DAC 0x7 + +#define AD5624R_CMD_WRITE_INPUT_N 0x0 +#define AD5624R_CMD_UPDATE_DAC_N 0x1 +#define AD5624R_CMD_WRITE_INPUT_N_UPDATE_ALL 0x2 +#define AD5624R_CMD_WRITE_INPUT_N_UPDATE_N 0x3 +#define AD5624R_CMD_POWERDOWN_DAC 0x4 +#define AD5624R_CMD_RESET 0x5 +#define AD5624R_CMD_LDAC_SETUP 0x6 +#define AD5624R_CMD_INTERNAL_REFER_SETUP 0x7 + +#define AD5624R_LDAC_PWRDN_NONE 0x0 +#define AD5624R_LDAC_PWRDN_1K 0x1 +#define AD5624R_LDAC_PWRDN_100K 0x2 +#define AD5624R_LDAC_PWRDN_3STATE 0x3 + +/** + * struct ad5624r_chip_info - chip specific information + * @channels: channel spec for the DAC + * @int_vref_mv: AD5620/40/60: the internal reference voltage + */ + +struct ad5624r_chip_info { + const struct iio_chan_spec *channels; + u16 int_vref_mv; +}; + +/** + * struct ad5624r_state - driver instance specific data + * @us: spi_device + * @chip_info: chip model specific constants, available modes etc + * @vref_mv: actual reference voltage used + * @pwr_down_mask power down mask + * @pwr_down_mode current power down mode + */ + +struct ad5624r_state { + struct spi_device *us; + const struct ad5624r_chip_info *chip_info; + unsigned short vref_mv; + unsigned pwr_down_mask; + unsigned pwr_down_mode; +}; + +/** + * ad5624r_supported_device_ids: + * The AD5624/44/64 parts are available in different + * fixed internal reference voltage options. + */ + +enum ad5624r_supported_device_ids { + ID_AD5624R3, + ID_AD5644R3, + ID_AD5664R3, + ID_AD5624R5, + ID_AD5644R5, + ID_AD5664R5, +}; + +#endif /* SPI_AD5624R_H_ */ diff --git a/drivers/iio/dac/ad5624r_spi.c b/drivers/iio/dac/ad5624r_spi.c new file mode 100644 index 000000000..56aea4b1c --- /dev/null +++ b/drivers/iio/dac/ad5624r_spi.c @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD5624R, AD5644R, AD5664R Digital to analog convertors spi driver + * + * Copyright 2010-2011 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "ad5624r.h" + +static int ad5624r_spi_write(struct spi_device *spi, + u8 cmd, u8 addr, u16 val, u8 shift) +{ + u32 data; + u8 msg[3]; + + /* + * The input shift register is 24 bits wide. The first two bits are + * don't care bits. The next three are the command bits, C2 to C0, + * followed by the 3-bit DAC address, A2 to A0, and then the + * 16-, 14-, 12-bit data-word. The data-word comprises the 16-, + * 14-, 12-bit input code followed by 0, 2, or 4 don't care bits, + * for the AD5664R, AD5644R, and AD5624R, respectively. + */ + data = (0 << 22) | (cmd << 19) | (addr << 16) | (val << shift); + put_unaligned_be24(data, &msg[0]); + + return spi_write(spi, msg, sizeof(msg)); +} + +static int ad5624r_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad5624r_state *st = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_SCALE: + *val = st->vref_mv; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + +static int ad5624r_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad5624r_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val >= (1 << chan->scan_type.realbits) || val < 0) + return -EINVAL; + + return ad5624r_spi_write(st->us, + AD5624R_CMD_WRITE_INPUT_N_UPDATE_N, + chan->address, val, + chan->scan_type.shift); + default: + return -EINVAL; + } +} + +static const char * const ad5624r_powerdown_modes[] = { + "1kohm_to_gnd", + "100kohm_to_gnd", + "three_state" +}; + +static int ad5624r_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad5624r_state *st = iio_priv(indio_dev); + + return st->pwr_down_mode; +} + +static int ad5624r_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int mode) +{ + struct ad5624r_state *st = iio_priv(indio_dev); + + st->pwr_down_mode = mode; + + return 0; +} + +static const struct iio_enum ad5624r_powerdown_mode_enum = { + .items = ad5624r_powerdown_modes, + .num_items = ARRAY_SIZE(ad5624r_powerdown_modes), + .get = ad5624r_get_powerdown_mode, + .set = ad5624r_set_powerdown_mode, +}; + +static ssize_t ad5624r_read_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, char *buf) +{ + struct ad5624r_state *st = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", + !!(st->pwr_down_mask & (1 << chan->channel))); +} + +static ssize_t ad5624r_write_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, const char *buf, + size_t len) +{ + bool pwr_down; + int ret; + struct ad5624r_state *st = iio_priv(indio_dev); + + ret = kstrtobool(buf, &pwr_down); + if (ret) + return ret; + + if (pwr_down) + st->pwr_down_mask |= (1 << chan->channel); + else + st->pwr_down_mask &= ~(1 << chan->channel); + + ret = ad5624r_spi_write(st->us, AD5624R_CMD_POWERDOWN_DAC, 0, + (st->pwr_down_mode << 4) | + st->pwr_down_mask, 16); + + return ret ? ret : len; +} + +static const struct iio_info ad5624r_info = { + .write_raw = ad5624r_write_raw, + .read_raw = ad5624r_read_raw, +}; + +static const struct iio_chan_spec_ext_info ad5624r_ext_info[] = { + { + .name = "powerdown", + .read = ad5624r_read_dac_powerdown, + .write = ad5624r_write_dac_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SHARED_BY_TYPE, + &ad5624r_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, &ad5624r_powerdown_mode_enum), + { } +}; + +#define AD5624R_CHANNEL(_chan, _bits) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (_chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .address = (_chan), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (_bits), \ + .storagebits = 16, \ + .shift = 16 - (_bits), \ + }, \ + .ext_info = ad5624r_ext_info, \ +} + +#define DECLARE_AD5624R_CHANNELS(_name, _bits) \ + const struct iio_chan_spec _name##_channels[] = { \ + AD5624R_CHANNEL(0, _bits), \ + AD5624R_CHANNEL(1, _bits), \ + AD5624R_CHANNEL(2, _bits), \ + AD5624R_CHANNEL(3, _bits), \ +} + +static DECLARE_AD5624R_CHANNELS(ad5624r, 12); +static DECLARE_AD5624R_CHANNELS(ad5644r, 14); +static DECLARE_AD5624R_CHANNELS(ad5664r, 16); + +static const struct ad5624r_chip_info ad5624r_chip_info_tbl[] = { + [ID_AD5624R3] = { + .channels = ad5624r_channels, + .int_vref_mv = 1250, + }, + [ID_AD5624R5] = { + .channels = ad5624r_channels, + .int_vref_mv = 2500, + }, + [ID_AD5644R3] = { + .channels = ad5644r_channels, + .int_vref_mv = 1250, + }, + [ID_AD5644R5] = { + .channels = ad5644r_channels, + .int_vref_mv = 2500, + }, + [ID_AD5664R3] = { + .channels = ad5664r_channels, + .int_vref_mv = 1250, + }, + [ID_AD5664R5] = { + .channels = ad5664r_channels, + .int_vref_mv = 2500, + }, +}; + +static int ad5624r_probe(struct spi_device *spi) +{ + struct ad5624r_state *st; + struct iio_dev *indio_dev; + bool external_vref; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + st = iio_priv(indio_dev); + ret = devm_regulator_get_enable_read_voltage(&spi->dev, "vref"); + if (ret == -ENODEV) + /* Backwards compatibility. This naming is not correct */ + ret = devm_regulator_get_enable_read_voltage(&spi->dev, "vcc"); + if (ret < 0 && ret != -ENODEV) + return ret; + + external_vref = ret != -ENODEV; + st->vref_mv = external_vref ? ret / 1000 : st->chip_info->int_vref_mv; + + st->chip_info = + &ad5624r_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + st->us = spi; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &ad5624r_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = AD5624R_DAC_CHANNELS; + + ret = ad5624r_spi_write(spi, AD5624R_CMD_INTERNAL_REFER_SETUP, 0, + external_vref, 16); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id ad5624r_id[] = { + { .name = "ad5624r3", .driver_data = ID_AD5624R3 }, + { .name = "ad5644r3", .driver_data = ID_AD5644R3 }, + { .name = "ad5664r3", .driver_data = ID_AD5664R3 }, + { .name = "ad5624r5", .driver_data = ID_AD5624R5 }, + { .name = "ad5644r5", .driver_data = ID_AD5644R5 }, + { .name = "ad5664r5", .driver_data = ID_AD5664R5 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad5624r_id); + +static struct spi_driver ad5624r_driver = { + .driver = { + .name = "ad5624r", + }, + .probe = ad5624r_probe, + .id_table = ad5624r_id, +}; +module_spi_driver(ad5624r_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices AD5624/44/64R DAC spi driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5686-spi.c b/drivers/iio/dac/ad5686-spi.c new file mode 100644 index 000000000..69cd9753f --- /dev/null +++ b/drivers/iio/dac/ad5686-spi.c @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SPI driver for AD5686 and similar Digital to Analog Converters + * + * Copyright 2018 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "ad5686.h" + +/** + * struct ad5686_spi_data - SPI bus specific data + * @msg: SPI message used for transfers + * @size: number of transfers currently in the message + * @capacity: maximum number of transfers that can be added to the message + * @xfers: array of SPI transfers, allocated with the provided capacity + */ +struct ad5686_spi_data { + struct spi_message msg; + unsigned int size; + unsigned int capacity; + struct spi_transfer xfers[] __counted_by(capacity); +}; + +static int ad5686_spi_write(struct ad5686_state *st, + u8 cmd, u8 addr, u16 val) +{ + struct ad5686_spi_data *bus_data = st->bus_data; + struct spi_transfer *xfer; + + if (bus_data->size >= bus_data->capacity) + return -E2BIG; + + /* + * This function stores spi transfers to a spi message to be sent over + * the bus when sync() op is called. If there are already transfers in + * the spi message, set the cs_change flag on the last transfer to + * ensure that the chip select is deasserted between transfers. If this + * is the first transfer, initialize the spi message. Later on, the + * current transfer is added to the message with spi_message_add_tail(). + */ + if (bus_data->size) + bus_data->xfers[bus_data->size - 1].cs_change = 1; + else + spi_message_init(&bus_data->msg); + + xfer = &bus_data->xfers[bus_data->size]; + auto buf = &st->data[bus_data->size]; + switch (st->chip_info->regmap_type) { + case AD5310_REGMAP: + buf->d16 = cpu_to_be16(FIELD_PREP(AD5310_CMD_MSK, cmd) | + FIELD_PREP(AD5310_DATA_MSK, val)); + *xfer = (struct spi_transfer) { + .tx_buf = &buf->d16, + .len = sizeof(buf->d16), + }; + break; + case AD5683_REGMAP: + buf->d32 = cpu_to_be32(FIELD_PREP(AD5686_CMD_MSK, cmd) | + FIELD_PREP(AD5683_DATA_MSK, val)); + *xfer = (struct spi_transfer) { + .tx_buf = &buf->d8[1], + .len = sizeof(buf->d8) - 1, + }; + break; + case AD5686_REGMAP: + buf->d32 = cpu_to_be32(FIELD_PREP(AD5686_CMD_MSK, cmd) | + FIELD_PREP(AD5686_ADDR_MSK, addr) | + FIELD_PREP(AD5686_DATA_MSK, val)); + *xfer = (struct spi_transfer) { + .tx_buf = &buf->d8[1], + .len = sizeof(buf->d8) - 1, + }; + break; + default: + return -EINVAL; + } + + spi_message_add_tail(xfer, &bus_data->msg); + bus_data->size++; + + return 0; +} + +static int ad5686_spi_sync(struct ad5686_state *st) +{ + struct spi_device *spi = to_spi_device(st->dev); + struct ad5686_spi_data *bus_data = st->bus_data; + + bus_data->size = 0; /* always reset, even on sync failure */ + return spi_sync(spi, &bus_data->msg); +} + +static int ad5686_spi_read(struct ad5686_state *st, u8 addr) +{ + struct spi_device *spi = to_spi_device(st->dev); + struct ad5686_spi_data *bus_data = st->bus_data; + struct spi_transfer *xfer = &bus_data->xfers[0]; + u8 cmd = 0; + int ret; + + switch (st->chip_info->regmap_type) { + case AD5310_REGMAP: + return -ENOTSUPP; + case AD5683_REGMAP: + cmd = AD5686_CMD_READBACK_ENABLE_V2; + break; + case AD5686_REGMAP: + cmd = AD5686_CMD_READBACK_ENABLE; + break; + default: + return -EINVAL; + } + + st->data[0].d32 = cpu_to_be32(FIELD_PREP(AD5686_CMD_MSK, cmd) | + FIELD_PREP(AD5686_ADDR_MSK, addr)); + st->data[1].d32 = cpu_to_be32(FIELD_PREP(AD5686_CMD_MSK, AD5686_CMD_NOOP)); + + xfer[0] = (struct spi_transfer) { + .tx_buf = &st->data[0].d8[1], + .len = sizeof(st->data[0].d8) - 1, + .cs_change = 1, + }; + xfer[1] = (struct spi_transfer) { + .tx_buf = &st->data[1].d8[1], + .rx_buf = &st->data[2].d8[1], + .len = sizeof(st->data[1].d8) - 1, + }; + + spi_message_init_with_transfers(&bus_data->msg, xfer, 2); + + ret = spi_sync(spi, &bus_data->msg); + if (ret) + return ret; + + return be32_to_cpu(st->data[2].d32); +} + +static const struct ad5686_bus_ops ad5686_spi_ops = { + .write = ad5686_spi_write, + .read = ad5686_spi_read, + .sync = ad5686_spi_sync, +}; + +static int ad5686_spi_probe(struct spi_device *spi) +{ + const struct ad5686_chip_info *info; + struct ad5686_spi_data *bus_data; + struct device *dev = &spi->dev; + unsigned int capacity; + + info = spi_get_device_match_data(spi); + if (!info) + return -ENODATA; + + /* read operation requires at least 2 transfers */ + capacity = max(info->num_channels, 2); + bus_data = devm_kzalloc(dev, struct_size(bus_data, xfers, capacity), + GFP_KERNEL); + if (!bus_data) + return -ENOMEM; + + bus_data->capacity = capacity; + + return ad5686_probe(dev, info, spi->modalias, &ad5686_spi_ops, bus_data); +} + +static const struct spi_device_id ad5686_spi_id[] = { + { .name = "ad5310r", .driver_data = (kernel_ulong_t)&ad5310r_chip_info }, + { .name = "ad5313r", .driver_data = (kernel_ulong_t)&ad5338r_chip_info }, + { .name = "ad5317r", .driver_data = (kernel_ulong_t)&ad5317r_chip_info }, + { .name = "ad5672r", .driver_data = (kernel_ulong_t)&ad5672r_chip_info }, + { .name = "ad5674", .driver_data = (kernel_ulong_t)&ad5674_chip_info }, + { .name = "ad5674r", .driver_data = (kernel_ulong_t)&ad5674r_chip_info }, + { .name = "ad5676", .driver_data = (kernel_ulong_t)&ad5676_chip_info }, + { .name = "ad5676r", .driver_data = (kernel_ulong_t)&ad5676r_chip_info }, + { .name = "ad5679", .driver_data = (kernel_ulong_t)&ad5679_chip_info }, + { .name = "ad5679r", .driver_data = (kernel_ulong_t)&ad5679r_chip_info }, + { .name = "ad5681r", .driver_data = (kernel_ulong_t)&ad5681r_chip_info }, + { .name = "ad5682r", .driver_data = (kernel_ulong_t)&ad5682r_chip_info }, + { .name = "ad5683", .driver_data = (kernel_ulong_t)&ad5683_chip_info }, + { .name = "ad5683r", .driver_data = (kernel_ulong_t)&ad5683r_chip_info }, + { .name = "ad5684", .driver_data = (kernel_ulong_t)&ad5684_chip_info }, + { .name = "ad5684r", .driver_data = (kernel_ulong_t)&ad5684r_chip_info }, + { .name = "ad5685", .driver_data = (kernel_ulong_t)&ad5685r_chip_info }, /* nonexistent */ + { .name = "ad5685r", .driver_data = (kernel_ulong_t)&ad5685r_chip_info }, + { .name = "ad5686", .driver_data = (kernel_ulong_t)&ad5686_chip_info }, + { .name = "ad5686r", .driver_data = (kernel_ulong_t)&ad5686r_chip_info }, + { .name = "ad5687", .driver_data = (kernel_ulong_t)&ad5687_chip_info }, + { .name = "ad5687r", .driver_data = (kernel_ulong_t)&ad5687r_chip_info }, + { .name = "ad5689", .driver_data = (kernel_ulong_t)&ad5689_chip_info }, + { .name = "ad5689r", .driver_data = (kernel_ulong_t)&ad5689r_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad5686_spi_id); + +static const struct of_device_id ad5686_of_match[] = { + { .compatible = "adi,ad5310r", .data = &ad5310r_chip_info }, + { .compatible = "adi,ad5313r", .data = &ad5338r_chip_info }, + { .compatible = "adi,ad5317r", .data = &ad5317r_chip_info }, + { .compatible = "adi,ad5672r", .data = &ad5672r_chip_info }, + { .compatible = "adi,ad5674", .data = &ad5674_chip_info }, + { .compatible = "adi,ad5674r", .data = &ad5674r_chip_info }, + { .compatible = "adi,ad5676", .data = &ad5676_chip_info }, + { .compatible = "adi,ad5676r", .data = &ad5676r_chip_info }, + { .compatible = "adi,ad5679", .data = &ad5679_chip_info }, + { .compatible = "adi,ad5679r", .data = &ad5679r_chip_info }, + { .compatible = "adi,ad5681r", .data = &ad5681r_chip_info }, + { .compatible = "adi,ad5682r", .data = &ad5682r_chip_info }, + { .compatible = "adi,ad5683", .data = &ad5683_chip_info }, + { .compatible = "adi,ad5683r", .data = &ad5683r_chip_info }, + { .compatible = "adi,ad5684", .data = &ad5684_chip_info }, + { .compatible = "adi,ad5684r", .data = &ad5684r_chip_info }, + { .compatible = "adi,ad5685r", .data = &ad5685r_chip_info }, + { .compatible = "adi,ad5686", .data = &ad5686_chip_info }, + { .compatible = "adi,ad5686r", .data = &ad5686r_chip_info }, + { .compatible = "adi,ad5687", .data = &ad5687_chip_info }, + { .compatible = "adi,ad5687r", .data = &ad5687r_chip_info }, + { .compatible = "adi,ad5689", .data = &ad5689_chip_info }, + { .compatible = "adi,ad5689r", .data = &ad5689r_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad5686_of_match); + +static struct spi_driver ad5686_spi_driver = { + .driver = { + .name = "ad5686", + .of_match_table = ad5686_of_match, + }, + .probe = ad5686_spi_probe, + .id_table = ad5686_spi_id, +}; + +module_spi_driver(ad5686_spi_driver); + +MODULE_AUTHOR("Stefan Popa "); +MODULE_DESCRIPTION("Analog Devices AD5686 and similar multi-channel DACs"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_AD5686"); diff --git a/drivers/iio/dac/ad5686.c b/drivers/iio/dac/ad5686.c new file mode 100644 index 000000000..0cc1c9c22 --- /dev/null +++ b/drivers/iio/dac/ad5686.c @@ -0,0 +1,808 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Core driver for AD5686 and similar Digital to Analog Converters + * + * Copyright 2011 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "ad5686.h" + +static const char * const ad5686_powerdown_modes[] = { + "1kohm_to_gnd", + "100kohm_to_gnd", + "three_state" +}; + +static int ad5310_control_sync(struct ad5686_state *st) +{ + unsigned int pd_val = st->pwr_down_mask & st->pwr_down_mode; + + return ad5686_write(st, AD5686_CMD_CONTROL_REG, 0, + FIELD_PREP(AD5310_DATA_PD_MSK, pd_val & AD5686_PD_MSK) | + FIELD_PREP(AD5310_DATA_REF_MSK, st->use_internal_vref ? 0 : 1) | + FIELD_PREP(AD5310_DATA_GAIN_MSK, st->double_scale ? 1 : 0)); +} + +static int ad5683_control_sync(struct ad5686_state *st) +{ + unsigned int pd_val = st->pwr_down_mask & st->pwr_down_mode; + + return ad5686_write(st, AD5686_CMD_CONTROL_REG, 0, + FIELD_PREP(AD5683_DATA_PD_MSK, pd_val & AD5686_PD_MSK) | + FIELD_PREP(AD5683_DATA_REF_MSK, st->use_internal_vref ? 0 : 1) | + FIELD_PREP(AD5683_DATA_GAIN_MSK, st->double_scale ? 1 : 0)); +} + +static inline unsigned int ad5686_pd_mask_shift(const struct iio_chan_spec *chan) +{ + if (chan->channel == chan->address) + return chan->channel * 2; + + /* one-hot encoding is used in dual/quad channel devices */ + return __ffs(chan->address) * 2; +} + +static inline void ad5686_pd_field_set(const struct iio_chan_spec *chan, + unsigned int *pd, unsigned int val) +{ + unsigned int shift = ad5686_pd_mask_shift(chan); + + *pd = (*pd & ~(AD5686_PD_MSK << shift)) | ((val & AD5686_PD_MSK) << shift); +} + +static inline unsigned int ad5686_pd_field_get(const struct iio_chan_spec *chan, + unsigned int pd) +{ + unsigned int shift = ad5686_pd_mask_shift(chan); + + return (pd >> shift) & AD5686_PD_MSK; +} + +static int ad5686_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad5686_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->lock); + + return ad5686_pd_field_get(chan, st->pwr_down_mode) - 1; +} + +static int ad5686_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct ad5686_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->lock); + ad5686_pd_field_set(chan, &st->pwr_down_mode, mode + 1); + + return 0; +} + +static const struct iio_enum ad5686_powerdown_mode_enum = { + .items = ad5686_powerdown_modes, + .num_items = ARRAY_SIZE(ad5686_powerdown_modes), + .get = ad5686_get_powerdown_mode, + .set = ad5686_set_powerdown_mode, +}; + +static ssize_t ad5686_read_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, char *buf) +{ + struct ad5686_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->lock); + + return sysfs_emit(buf, "%d\n", + !!ad5686_pd_field_get(chan, st->pwr_down_mask)); +} + +static ssize_t ad5686_write_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, + size_t len) +{ + bool readin; + int ret; + struct ad5686_state *st = iio_priv(indio_dev); + unsigned int val; + u8 address; + + ret = kstrtobool(buf, &readin); + if (ret) + return ret; + + guard(mutex)(&st->lock); + + if (readin) + ad5686_pd_field_set(chan, &st->pwr_down_mask, AD5686_PD_MSK_PWR_DOWN); + else + ad5686_pd_field_set(chan, &st->pwr_down_mask, AD5686_PD_MSK_PWR_UP); + + switch (st->chip_info->regmap_type) { + case AD5310_REGMAP: + ret = ad5310_control_sync(st); + if (ret) + return ret; + break; + case AD5683_REGMAP: + ret = ad5683_control_sync(st); + if (ret) + return ret; + break; + case AD5686_REGMAP: + /* AD5674R/AD5679R have 16 channels and 2 powerdown registers */ + val = st->pwr_down_mask & st->pwr_down_mode; + if (chan->channel > 0x7) { + address = 0x8; + val = upper_16_bits(val); + } else { + address = 0x0; + val = lower_16_bits(val); + } + ret = ad5686_write(st, AD5686_CMD_POWERDOWN_DAC, address, val); + if (ret) + return ret; + break; + default: + return -EINVAL; + } + + return len; +} + +static int ad5686_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad5686_state *st = iio_priv(indio_dev); + int ret; + + guard(mutex)(&st->lock); + + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = ad5686_read(st, chan->address); + if (ret < 0) + return ret; + *val = (ret >> chan->scan_type.shift) & + GENMASK(chan->scan_type.realbits - 1, 0); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (st->double_scale) { + *val = st->scale_avail[2]; + *val2 = st->scale_avail[3]; + } else { + *val = st->scale_avail[0]; + *val2 = st->scale_avail[1]; + } + return IIO_VAL_INT_PLUS_NANO; + } + return -EINVAL; +} + +static int ad5686_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad5686_state *st = iio_priv(indio_dev); + bool double_scale; + int ret; + + guard(mutex)(&st->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val >= (1 << chan->scan_type.realbits) || val < 0) + return -EINVAL; + + return ad5686_write(st, AD5686_CMD_WRITE_INPUT_N_UPDATE_N, + chan->address, val << chan->scan_type.shift); + case IIO_CHAN_INFO_SCALE: + if (val == st->scale_avail[0] && val2 == st->scale_avail[1]) + double_scale = false; + else if (val == st->scale_avail[2] && val2 == st->scale_avail[3]) + double_scale = true; + else + return -EINVAL; + + if (st->double_scale == double_scale) + return 0; /* no change */ + + if (st->chip_info->regmap_type == AD5686_REGMAP && !st->gain_gpio) + return -EINVAL; /* GAIN pin is board-strapped */ + + st->double_scale = double_scale; + switch (st->chip_info->regmap_type) { + case AD5310_REGMAP: + ret = ad5310_control_sync(st); + break; + case AD5683_REGMAP: + ret = ad5683_control_sync(st); + break; + case AD5686_REGMAP: + ret = gpiod_set_value_cansleep(st->gain_gpio, + st->double_scale ? 1 : 0); + break; + default: + ret = -EINVAL; + } + if (ret) + st->double_scale = !double_scale; /* revert on failure */ + return ret; + default: + return -EINVAL; + } +} + +static int ad5686_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_RAW: + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static int ad5686_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct ad5686_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *type = IIO_VAL_INT_PLUS_NANO; + + if (st->chip_info->regmap_type == AD5686_REGMAP && !st->gain_gpio) { + /* + * GAIN pin is board-strapped, so only the current + * scale is available. + */ + *vals = st->double_scale ? &st->scale_avail[2] : + &st->scale_avail[0]; + *length = 2; + return IIO_AVAIL_LIST; + } + + *vals = st->scale_avail; + *length = ARRAY_SIZE(st->scale_avail); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const struct iio_info ad5686_info = { + .read_raw = ad5686_read_raw, + .write_raw = ad5686_write_raw, + .write_raw_get_fmt = ad5686_write_raw_get_fmt, + .read_avail = ad5686_read_avail, +}; + +static const struct iio_chan_spec_ext_info ad5686_ext_info[] = { + { + .name = "powerdown", + .read = ad5686_read_dac_powerdown, + .write = ad5686_write_dac_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SEPARATE, &ad5686_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, &ad5686_powerdown_mode_enum), + { } +}; + +#define AD5868_CHANNEL(chan, addr, bits, _shift) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),\ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE),\ + .address = addr, \ + .scan_index = chan, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = (_shift), \ + }, \ + .ext_info = ad5686_ext_info, \ +} + +#define DECLARE_AD5683_CHANNELS(name, bits, _shift) \ +static const struct iio_chan_spec name[] = { \ + AD5868_CHANNEL(0, 0, bits, _shift), \ +} + +#define DECLARE_AD5338_CHANNELS(name, bits, _shift) \ +static const struct iio_chan_spec name[] = { \ + AD5868_CHANNEL(0, 1, bits, _shift), \ + AD5868_CHANNEL(1, 8, bits, _shift), \ +} + +#define DECLARE_AD5686_CHANNELS(name, bits, _shift) \ +static const struct iio_chan_spec name[] = { \ + AD5868_CHANNEL(0, 1, bits, _shift), \ + AD5868_CHANNEL(1, 2, bits, _shift), \ + AD5868_CHANNEL(2, 4, bits, _shift), \ + AD5868_CHANNEL(3, 8, bits, _shift), \ +} + +#define DECLARE_AD5676_CHANNELS(name, bits, _shift) \ +static const struct iio_chan_spec name[] = { \ + AD5868_CHANNEL(0, 0, bits, _shift), \ + AD5868_CHANNEL(1, 1, bits, _shift), \ + AD5868_CHANNEL(2, 2, bits, _shift), \ + AD5868_CHANNEL(3, 3, bits, _shift), \ + AD5868_CHANNEL(4, 4, bits, _shift), \ + AD5868_CHANNEL(5, 5, bits, _shift), \ + AD5868_CHANNEL(6, 6, bits, _shift), \ + AD5868_CHANNEL(7, 7, bits, _shift), \ +} + +#define DECLARE_AD5679_CHANNELS(name, bits, _shift) \ +static const struct iio_chan_spec name[] = { \ + AD5868_CHANNEL(0, 0, bits, _shift), \ + AD5868_CHANNEL(1, 1, bits, _shift), \ + AD5868_CHANNEL(2, 2, bits, _shift), \ + AD5868_CHANNEL(3, 3, bits, _shift), \ + AD5868_CHANNEL(4, 4, bits, _shift), \ + AD5868_CHANNEL(5, 5, bits, _shift), \ + AD5868_CHANNEL(6, 6, bits, _shift), \ + AD5868_CHANNEL(7, 7, bits, _shift), \ + AD5868_CHANNEL(8, 8, bits, _shift), \ + AD5868_CHANNEL(9, 9, bits, _shift), \ + AD5868_CHANNEL(10, 10, bits, _shift), \ + AD5868_CHANNEL(11, 11, bits, _shift), \ + AD5868_CHANNEL(12, 12, bits, _shift), \ + AD5868_CHANNEL(13, 13, bits, _shift), \ + AD5868_CHANNEL(14, 14, bits, _shift), \ + AD5868_CHANNEL(15, 15, bits, _shift), \ +} + +/* single-channel */ +DECLARE_AD5683_CHANNELS(ad5310r_channels, 10, 2); +DECLARE_AD5683_CHANNELS(ad5311r_channels, 10, 6); +DECLARE_AD5683_CHANNELS(ad5681r_channels, 12, 4); +DECLARE_AD5683_CHANNELS(ad5682r_channels, 14, 2); +DECLARE_AD5683_CHANNELS(ad5683r_channels, 16, 0); + +/* dual-channel */ +DECLARE_AD5338_CHANNELS(ad5337r_channels, 8, 8); +DECLARE_AD5338_CHANNELS(ad5338r_channels, 10, 6); +DECLARE_AD5338_CHANNELS(ad5687r_channels, 12, 4); +DECLARE_AD5338_CHANNELS(ad5689r_channels, 16, 0); + +/* quad-channel */ +DECLARE_AD5686_CHANNELS(ad5317r_channels, 10, 6); +DECLARE_AD5686_CHANNELS(ad5684r_channels, 12, 4); +DECLARE_AD5686_CHANNELS(ad5685r_channels, 14, 2); +DECLARE_AD5686_CHANNELS(ad5686r_channels, 16, 0); + +/* 8-channel */ +DECLARE_AD5676_CHANNELS(ad5672r_channels, 12, 4); +DECLARE_AD5676_CHANNELS(ad5676r_channels, 16, 0); + +/* 16-channel */ +DECLARE_AD5679_CHANNELS(ad5674r_channels, 12, 4); +DECLARE_AD5679_CHANNELS(ad5679r_channels, 16, 0); + +const struct ad5686_chip_info ad5310r_chip_info = { + .channels = ad5310r_channels, + .int_vref_mv = 2500, + .num_channels = 1, + .regmap_type = AD5310_REGMAP, +}; +EXPORT_SYMBOL_NS_GPL(ad5310r_chip_info, "IIO_AD5686"); + +const struct ad5686_chip_info ad5311r_chip_info = { + .channels = ad5311r_channels, + .int_vref_mv = 2500, + .num_channels = 1, + .regmap_type = AD5683_REGMAP, +}; +EXPORT_SYMBOL_NS_GPL(ad5311r_chip_info, "IIO_AD5686"); + +const struct ad5686_chip_info ad5681r_chip_info = { + .channels = ad5681r_channels, + .int_vref_mv = 2500, + .num_channels = 1, + .regmap_type = AD5683_REGMAP, +}; +EXPORT_SYMBOL_NS_GPL(ad5681r_chip_info, "IIO_AD5686"); + +const struct ad5686_chip_info ad5682r_chip_info = { + .channels = ad5682r_channels, + .int_vref_mv = 2500, + .num_channels = 1, + .regmap_type = AD5683_REGMAP, +}; +EXPORT_SYMBOL_NS_GPL(ad5682r_chip_info, "IIO_AD5686"); + +const struct ad5686_chip_info ad5683_chip_info = { + .channels = ad5683r_channels, + .num_channels = 1, + .regmap_type = AD5683_REGMAP, +}; +EXPORT_SYMBOL_NS_GPL(ad5683_chip_info, "IIO_AD5686"); + +const struct ad5686_chip_info ad5683r_chip_info = { + .channels = ad5683r_channels, + .int_vref_mv = 2500, + .num_channels = 1, + .regmap_type = AD5683_REGMAP, +}; +EXPORT_SYMBOL_NS_GPL(ad5683r_chip_info, "IIO_AD5686"); + +const struct ad5686_chip_info ad5337r_chip_info = { + .channels = ad5337r_channels, + .int_vref_mv = 2500, + .num_channels = 2, + .regmap_type = AD5686_REGMAP, +}; +EXPORT_SYMBOL_NS_GPL(ad5337r_chip_info, "IIO_AD5686"); + +const struct ad5686_chip_info ad5338r_chip_info = { + .channels = ad5338r_channels, + .int_vref_mv = 2500, + .num_channels = 2, + .regmap_type = AD5686_REGMAP, +}; +EXPORT_SYMBOL_NS_GPL(ad5338r_chip_info, "IIO_AD5686"); + +const struct ad5686_chip_info ad5687_chip_info = { + .channels = ad5687r_channels, + .num_channels = 2, + .regmap_type = AD5686_REGMAP, +}; +EXPORT_SYMBOL_NS_GPL(ad5687_chip_info, "IIO_AD5686"); + +const struct ad5686_chip_info ad5687r_chip_info = { + .channels = ad5687r_channels, + .int_vref_mv = 2500, + .num_channels = 2, + .regmap_type = AD5686_REGMAP, +}; +EXPORT_SYMBOL_NS_GPL(ad5687r_chip_info, "IIO_AD5686"); + +const struct ad5686_chip_info ad5689_chip_info = { + .channels = ad5689r_channels, + .num_channels = 2, + .regmap_type = AD5686_REGMAP, +}; +EXPORT_SYMBOL_NS_GPL(ad5689_chip_info, "IIO_AD5686"); + +const struct ad5686_chip_info ad5689r_chip_info = { + .channels = ad5689r_channels, + .int_vref_mv = 2500, + .num_channels = 2, + .regmap_type = AD5686_REGMAP, +}; +EXPORT_SYMBOL_NS_GPL(ad5689r_chip_info, "IIO_AD5686"); + +const struct ad5686_chip_info ad5317r_chip_info = { + .channels = ad5317r_channels, + .int_vref_mv = 2500, + .num_channels = 4, + .regmap_type = AD5686_REGMAP, +}; +EXPORT_SYMBOL_NS_GPL(ad5317r_chip_info, "IIO_AD5686"); + +const struct ad5686_chip_info ad5684_chip_info = { + .channels = ad5684r_channels, + .num_channels = 4, + .regmap_type = AD5686_REGMAP, +}; +EXPORT_SYMBOL_NS_GPL(ad5684_chip_info, "IIO_AD5686"); + +const struct ad5686_chip_info ad5684r_chip_info = { + .channels = ad5684r_channels, + .int_vref_mv = 2500, + .num_channels = 4, + .regmap_type = AD5686_REGMAP, +}; +EXPORT_SYMBOL_NS_GPL(ad5684r_chip_info, "IIO_AD5686"); + +const struct ad5686_chip_info ad5685r_chip_info = { + .channels = ad5685r_channels, + .int_vref_mv = 2500, + .num_channels = 4, + .regmap_type = AD5686_REGMAP, +}; +EXPORT_SYMBOL_NS_GPL(ad5685r_chip_info, "IIO_AD5686"); + +const struct ad5686_chip_info ad5686_chip_info = { + .channels = ad5686r_channels, + .num_channels = 4, + .regmap_type = AD5686_REGMAP, +}; +EXPORT_SYMBOL_NS_GPL(ad5686_chip_info, "IIO_AD5686"); + +const struct ad5686_chip_info ad5686r_chip_info = { + .channels = ad5686r_channels, + .int_vref_mv = 2500, + .num_channels = 4, + .regmap_type = AD5686_REGMAP, +}; +EXPORT_SYMBOL_NS_GPL(ad5686r_chip_info, "IIO_AD5686"); + +const struct ad5686_chip_info ad5672r_chip_info = { + .channels = ad5672r_channels, + .int_vref_mv = 2500, + .num_channels = 8, + .regmap_type = AD5686_REGMAP, +}; +EXPORT_SYMBOL_NS_GPL(ad5672r_chip_info, "IIO_AD5686"); + +const struct ad5686_chip_info ad5676_chip_info = { + .channels = ad5676r_channels, + .num_channels = 8, + .regmap_type = AD5686_REGMAP, +}; +EXPORT_SYMBOL_NS_GPL(ad5676_chip_info, "IIO_AD5686"); + +const struct ad5686_chip_info ad5676r_chip_info = { + .channels = ad5676r_channels, + .int_vref_mv = 2500, + .num_channels = 8, + .regmap_type = AD5686_REGMAP, +}; +EXPORT_SYMBOL_NS_GPL(ad5676r_chip_info, "IIO_AD5686"); + +const struct ad5686_chip_info ad5674_chip_info = { + .channels = ad5674r_channels, + .num_channels = 16, + .regmap_type = AD5686_REGMAP, +}; +EXPORT_SYMBOL_NS_GPL(ad5674_chip_info, "IIO_AD5686"); + +const struct ad5686_chip_info ad5674r_chip_info = { + .channels = ad5674r_channels, + .int_vref_mv = 2500, + .num_channels = 16, + .regmap_type = AD5686_REGMAP, +}; +EXPORT_SYMBOL_NS_GPL(ad5674r_chip_info, "IIO_AD5686"); + +const struct ad5686_chip_info ad5679_chip_info = { + .channels = ad5679r_channels, + .num_channels = 16, + .regmap_type = AD5686_REGMAP, +}; +EXPORT_SYMBOL_NS_GPL(ad5679_chip_info, "IIO_AD5686"); + +const struct ad5686_chip_info ad5679r_chip_info = { + .channels = ad5679r_channels, + .int_vref_mv = 2500, + .num_channels = 16, + .regmap_type = AD5686_REGMAP, +}; +EXPORT_SYMBOL_NS_GPL(ad5679r_chip_info, "IIO_AD5686"); + +static void ad5686_init_scale_avail(struct ad5686_state *st) +{ + int realbits = st->chip_info->channels[0].scan_type.realbits; + s64 tmp = 2ULL * st->vref_mv * NANO >> realbits; + + st->scale_avail[2] = div_s64_rem(tmp, NANO, &st->scale_avail[3]); + st->scale_avail[0] = div_s64_rem(tmp >> 1, NANO, &st->scale_avail[1]); +} + +static void do_ad5686_trigger_handler(struct iio_dev *indio_dev) +{ + struct ad5686_state *st = iio_priv(indio_dev); + u16 val[AD5686_MAX_CHANNELS] = { }; + unsigned int scan_count, ch, i; + bool async_update; + u8 cmd; + + if (iio_pop_from_buffer(indio_dev->buffer, val)) + return; + + guard(mutex)(&st->lock); + + scan_count = bitmap_weight(indio_dev->active_scan_mask, + iio_get_masklength(indio_dev)); + async_update = st->ldac_gpio && scan_count > 1; + if (async_update) { + /* use LDAC to update all channels simultaneously */ + cmd = AD5686_CMD_WRITE_INPUT_N; + gpiod_set_value_cansleep(st->ldac_gpio, 0); + } else { + cmd = AD5686_CMD_WRITE_INPUT_N_UPDATE_N; + } + + i = 0; + iio_for_each_active_channel(indio_dev, ch) { + if (st->ops->write(st, cmd, indio_dev->channels[ch].address, val[i++])) + break; + } + + /* + * If sync() is available, it is called here regardless of write + * failure to allow bus implementation to reset. In that case, partial + * writes are unlikely as the write operations would just queue up + * the transfers. + */ + if (st->ops->sync) + st->ops->sync(st); + + if (async_update) + gpiod_set_value_cansleep(st->ldac_gpio, 1); +} + +static irqreturn_t ad5686_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + + do_ad5686_trigger_handler(indio_dev); + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +int ad5686_probe(struct device *dev, + const struct ad5686_chip_info *chip_info, + const char *name, const struct ad5686_bus_ops *ops, + void *bus_data) +{ + struct reset_control *rstc; + struct ad5686_state *st; + struct iio_dev *indio_dev; + int ret, i; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->dev = dev; + st->ops = ops; + st->bus_data = bus_data; + st->chip_info = chip_info; + + rstc = devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(rstc)) + return dev_err_probe(dev, PTR_ERR(rstc), + "Failed to get reset control\n"); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "failed to enable vdd supply\n"); + + ret = devm_regulator_get_enable(dev, "vlogic"); + if (ret) + return dev_err_probe(dev, ret, "failed to enable vlogic supply\n"); + + ret = devm_regulator_get_enable_read_voltage(dev, "vref"); + if (ret == -ENODEV) /* vcc-supply is deprecated, but supported still */ + ret = devm_regulator_get_enable_read_voltage(dev, "vcc"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to read vref voltage\n"); + + st->use_internal_vref = ret == -ENODEV; + st->vref_mv = st->use_internal_vref ? st->chip_info->int_vref_mv : ret / 1000; + if (!st->vref_mv) + return dev_err_probe(dev, -EINVAL, + "invalid or not provided vref voltage\n"); + + /* 4.5us power-up time: Datasheet Table 4: Timing Characteristics */ + fsleep(5); + + /* 1us >> 30ns reset pulse activation time: Datasheet Table 4 */ + reset_control_assert(rstc); + fsleep(1); + reset_control_deassert(rstc); + + st->ldac_gpio = devm_gpiod_get_optional(dev, "ldac", GPIOD_OUT_HIGH); + if (IS_ERR(st->ldac_gpio)) + return dev_err_probe(dev, PTR_ERR(st->ldac_gpio), + "Failed to get LDAC GPIO\n"); + + st->double_scale = device_property_read_bool(dev, "adi,range-double"); + st->gain_gpio = devm_gpiod_get_optional(dev, "gain", + st->double_scale ? GPIOD_OUT_HIGH : + GPIOD_OUT_LOW); + if (IS_ERR(st->gain_gpio)) + return dev_err_probe(dev, PTR_ERR(st->gain_gpio), + "Failed to get GAIN GPIO\n"); + + ad5686_init_scale_avail(st); + + /* Initialize masks to all ones */ + st->pwr_down_mask = ~0; + st->pwr_down_mode = ~0; + + /* Set all the power down mode for all channels to 1K pulldown */ + for (i = 0; i < st->chip_info->num_channels; i++) { + ad5686_pd_field_set(&st->chip_info->channels[i], + &st->pwr_down_mask, AD5686_PD_MSK_PWR_UP); + ad5686_pd_field_set(&st->chip_info->channels[i], + &st->pwr_down_mode, AD5686_PD_MODE_1K_TO_GND); + } + + indio_dev->name = name; + indio_dev->info = &ad5686_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + switch (st->chip_info->regmap_type) { + case AD5310_REGMAP: + ret = ad5310_control_sync(st); + if (ret) + return ret; + break; + case AD5683_REGMAP: + ret = ad5683_control_sync(st); + if (ret) + return ret; + break; + case AD5686_REGMAP: + ret = ad5686_write(st, AD5686_CMD_INTERNAL_REFER_SETUP, 0, + st->use_internal_vref ? 0 : AD5686_DATA_REF_MSK); + if (ret) + return ret; + break; + default: + return -EINVAL; + } + + ret = devm_iio_triggered_buffer_setup_ext(dev, indio_dev, NULL, + &ad5686_trigger_handler, + IIO_BUFFER_DIRECTION_OUT, + NULL, NULL); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS_GPL(ad5686_probe, "IIO_AD5686"); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD5686/85/84 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5686.h b/drivers/iio/dac/ad5686.h new file mode 100644 index 000000000..482bc7051 --- /dev/null +++ b/drivers/iio/dac/ad5686.h @@ -0,0 +1,200 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * This file is part of AD5686 DAC driver + * + * Copyright 2018 Analog Devices Inc. + */ + +#ifndef __DRIVERS_IIO_DAC_AD5686_H__ +#define __DRIVERS_IIO_DAC_AD5686_H__ + +#include +#include +#include + +#include + +#define AD5310_CMD_MSK GENMASK(15, 12) +#define AD5310_DATA_MSK GENMASK(11, 0) + +#define AD5683_DATA_MSK GENMASK(19, 4) + +#define AD5686_CMD_MSK GENMASK(23, 20) +#define AD5686_ADDR_MSK GENMASK(19, 16) +#define AD5686_DATA_MSK GENMASK(15, 0) + +#define AD5686_ADDR_DAC(chan) (0x1 << (chan)) +#define AD5686_ADDR_ALL_DAC 0xF +#define AD5686_MAX_CHANNELS 16 + +#define AD5686_CMD_NOOP 0x0 +#define AD5686_CMD_WRITE_INPUT_N 0x1 +#define AD5686_CMD_UPDATE_DAC_N 0x2 +#define AD5686_CMD_WRITE_INPUT_N_UPDATE_N 0x3 +#define AD5686_CMD_POWERDOWN_DAC 0x4 +#define AD5686_CMD_LDAC_MASK 0x5 +#define AD5686_CMD_RESET 0x6 +#define AD5686_CMD_INTERNAL_REFER_SETUP 0x7 +#define AD5686_CMD_DAISY_CHAIN_ENABLE 0x8 +#define AD5686_CMD_READBACK_ENABLE 0x9 + +#define AD5686_CMD_CONTROL_REG 0x4 +#define AD5686_CMD_READBACK_ENABLE_V2 0x5 + +#define AD5310_DATA_GAIN_MSK BIT(7) +#define AD5310_DATA_REF_MSK BIT(8) +#define AD5310_DATA_PD_MSK GENMASK(10, 9) + +#define AD5683_DATA_GAIN_MSK BIT(11) /* DB15 */ +#define AD5683_DATA_REF_MSK BIT(12) /* DB16 */ +#define AD5683_DATA_PD_MSK GENMASK(14, 13) /* DB18:DB17 */ + +#define AD5686_DATA_REF_MSK BIT(0) + +#define AD5686_PD_MSK GENMASK(1, 0) +#define AD5686_PD_MODE_1K_TO_GND 0x1 +#define AD5686_PD_MODE_100K_TO_GND 0x2 +#define AD5686_PD_MODE_THREE_STATE 0x3 + +#define AD5686_PD_MSK_PWR_UP 0x0 +#define AD5686_PD_MSK_PWR_DOWN 0x3 + +enum ad5686_regmap_type { + AD5310_REGMAP, + AD5683_REGMAP, + AD5686_REGMAP, +}; + +struct gpio_desc; + +struct ad5686_state; + +/** + * struct ad5686_bus_ops - bus specific read/write operations + * @read: read a register value at the given address + * @write: write a command, address and value to the device + * @sync: ensure the completion of the write operation (optional) + */ +struct ad5686_bus_ops { + int (*read)(struct ad5686_state *st, u8 addr); + int (*write)(struct ad5686_state *st, u8 cmd, u8 addr, u16 val); + int (*sync)(struct ad5686_state *st); +}; + +/** + * struct ad5686_chip_info - chip specific information + * @int_vref_mv: the internal reference voltage + * @num_channels: number of channels + * @channel: channel specification + * @regmap_type: register map layout variant + */ +struct ad5686_chip_info { + u16 int_vref_mv; + unsigned int num_channels; + const struct iio_chan_spec *channels; + enum ad5686_regmap_type regmap_type; +}; + +/* single-channel instances */ +extern const struct ad5686_chip_info ad5310r_chip_info; +extern const struct ad5686_chip_info ad5311r_chip_info; +extern const struct ad5686_chip_info ad5681r_chip_info; +extern const struct ad5686_chip_info ad5682r_chip_info; +extern const struct ad5686_chip_info ad5683_chip_info; +extern const struct ad5686_chip_info ad5683r_chip_info; + +/* dual-channel instances */ +extern const struct ad5686_chip_info ad5337r_chip_info; +extern const struct ad5686_chip_info ad5338r_chip_info; +extern const struct ad5686_chip_info ad5687_chip_info; +extern const struct ad5686_chip_info ad5687r_chip_info; +extern const struct ad5686_chip_info ad5689_chip_info; +extern const struct ad5686_chip_info ad5689r_chip_info; + +/* quad-channel instances */ +extern const struct ad5686_chip_info ad5317r_chip_info; +extern const struct ad5686_chip_info ad5684_chip_info; +extern const struct ad5686_chip_info ad5684r_chip_info; +extern const struct ad5686_chip_info ad5685r_chip_info; +extern const struct ad5686_chip_info ad5686_chip_info; +extern const struct ad5686_chip_info ad5686r_chip_info; + +/* 8-channel instances */ +extern const struct ad5686_chip_info ad5672r_chip_info; +extern const struct ad5686_chip_info ad5676_chip_info; +extern const struct ad5686_chip_info ad5676r_chip_info; + +/* 16-channel instances */ +extern const struct ad5686_chip_info ad5674_chip_info; +extern const struct ad5686_chip_info ad5674r_chip_info; +extern const struct ad5686_chip_info ad5679_chip_info; +extern const struct ad5686_chip_info ad5679r_chip_info; + +/** + * struct ad5686_state - driver instance specific data + * @dev: device instance + * @chip_info: chip model specific constants, available modes etc + * @ops: bus specific operations + * @ldac_gpio: LDAC pin GPIO descriptor + * @gain_gpio: GAIN pin GPIO descriptor + * @pwr_down_mask: power down mask + * @pwr_down_mode: current power down mode + * @scale_avail: pre-calculated available scale values + * @vref_mv: actual reference voltage used + * @double_scale: flag to indicate the gain multiplier is applied + * @use_internal_vref: set to true if the internal reference voltage is used + * @lock: lock to protect access to state fields, which includes + * the data buffer during regmap ops + * @bus_data: bus specific data + * @data: transfer buffers + */ +struct ad5686_state { + struct device *dev; + const struct ad5686_chip_info *chip_info; + const struct ad5686_bus_ops *ops; + struct gpio_desc *ldac_gpio; + struct gpio_desc *gain_gpio; + unsigned int pwr_down_mask; + unsigned int pwr_down_mode; + int scale_avail[4]; + unsigned short vref_mv; + bool double_scale; + bool use_internal_vref; + struct mutex lock; + void *bus_data; + + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + */ + + union { + __be32 d32; + __be16 d16; + u8 d8[4]; + } data[AD5686_MAX_CHANNELS] __aligned(IIO_DMA_MINALIGN); +}; + + +int ad5686_probe(struct device *dev, + const struct ad5686_chip_info *chip_info, + const char *name, const struct ad5686_bus_ops *ops, + void *bus_data); + +static inline int ad5686_write(struct ad5686_state *st, u8 cmd, u8 addr, u16 val) +{ + int ret; + + ret = st->ops->write(st, cmd, addr, val); + if (ret) + return ret; + + return st->ops->sync ? st->ops->sync(st) : 0; +} + +static inline int ad5686_read(struct ad5686_state *st, u8 addr) +{ + return st->ops->read(st, addr); +} + +#endif /* __DRIVERS_IIO_DAC_AD5686_H__ */ diff --git a/drivers/iio/dac/ad5696-i2c.c b/drivers/iio/dac/ad5696-i2c.c new file mode 100644 index 000000000..f91abfcdb --- /dev/null +++ b/drivers/iio/dac/ad5696-i2c.c @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * I2C driver for AD5696 and similar Digital to Analog Converters + * + * Copyright 2018 Analog Devices Inc. + */ + +#include +#include +#include +#include + +#include + +#include "ad5686.h" + +static int ad5686_i2c_read(struct ad5686_state *st, u8 addr) +{ + struct i2c_client *i2c = to_i2c_client(st->dev); + struct i2c_msg msg[2] = { + { + .addr = i2c->addr, + .flags = i2c->flags, + .len = 3, + .buf = &st->data[0].d8[1], + }, + { + .addr = i2c->addr, + .flags = i2c->flags | I2C_M_RD, + .len = 2, + .buf = (char *)&st->data[0].d16, + }, + }; + int ret; + + st->data[0].d32 = cpu_to_be32(FIELD_PREP(AD5686_CMD_MSK, AD5686_CMD_NOOP) | + FIELD_PREP(AD5686_ADDR_MSK, addr)); + + ret = i2c_transfer(i2c->adapter, msg, 2); + if (ret < 0) + return ret; + + return be16_to_cpu(st->data[0].d16); +} + +static int ad5686_i2c_write(struct ad5686_state *st, + u8 cmd, u8 addr, u16 val) +{ + struct i2c_client *i2c = to_i2c_client(st->dev); + int ret; + + st->data[0].d32 = cpu_to_be32(FIELD_PREP(AD5686_CMD_MSK, cmd) | + FIELD_PREP(AD5686_ADDR_MSK, addr) | + FIELD_PREP(AD5686_DATA_MSK, val)); + + ret = i2c_master_send(i2c, &st->data[0].d8[1], 3); + if (ret < 0) + return ret; + + return (ret != 3) ? -EIO : 0; +} + +static const struct ad5686_bus_ops ad5686_i2c_ops = { + .write = ad5686_i2c_write, + .read = ad5686_i2c_read, +}; + +static int ad5686_i2c_probe(struct i2c_client *i2c) +{ + const struct ad5686_chip_info *info; + + info = i2c_get_match_data(i2c); + if (!info) + return -ENODATA; + + return ad5686_probe(&i2c->dev, info, i2c->name, &ad5686_i2c_ops, NULL); +} + +static const struct i2c_device_id ad5686_i2c_id[] = { + { .name = "ad5311r", .driver_data = (kernel_ulong_t)&ad5311r_chip_info }, + { .name = "ad5316r", .driver_data = (kernel_ulong_t)&ad5317r_chip_info }, + { .name = "ad5337r", .driver_data = (kernel_ulong_t)&ad5337r_chip_info }, + { .name = "ad5338r", .driver_data = (kernel_ulong_t)&ad5338r_chip_info }, + { .name = "ad5671r", .driver_data = (kernel_ulong_t)&ad5672r_chip_info }, + { .name = "ad5673r", .driver_data = (kernel_ulong_t)&ad5674r_chip_info }, + { .name = "ad5675", .driver_data = (kernel_ulong_t)&ad5676_chip_info }, + { .name = "ad5675r", .driver_data = (kernel_ulong_t)&ad5676r_chip_info }, + { .name = "ad5677r", .driver_data = (kernel_ulong_t)&ad5679r_chip_info }, + { .name = "ad5691r", .driver_data = (kernel_ulong_t)&ad5681r_chip_info }, + { .name = "ad5692r", .driver_data = (kernel_ulong_t)&ad5682r_chip_info }, + { .name = "ad5693", .driver_data = (kernel_ulong_t)&ad5683_chip_info }, + { .name = "ad5693r", .driver_data = (kernel_ulong_t)&ad5683r_chip_info }, + { .name = "ad5694", .driver_data = (kernel_ulong_t)&ad5684_chip_info }, + { .name = "ad5694r", .driver_data = (kernel_ulong_t)&ad5684r_chip_info }, + { .name = "ad5695r", .driver_data = (kernel_ulong_t)&ad5685r_chip_info }, + { .name = "ad5696", .driver_data = (kernel_ulong_t)&ad5686_chip_info }, + { .name = "ad5696r", .driver_data = (kernel_ulong_t)&ad5686r_chip_info }, + { .name = "ad5697r", .driver_data = (kernel_ulong_t)&ad5687r_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ad5686_i2c_id); + +static const struct of_device_id ad5686_of_match[] = { + { .compatible = "adi,ad5311r", .data = &ad5311r_chip_info }, + { .compatible = "adi,ad5316r", .data = &ad5317r_chip_info }, + { .compatible = "adi,ad5337r", .data = &ad5337r_chip_info }, + { .compatible = "adi,ad5338r", .data = &ad5338r_chip_info }, + { .compatible = "adi,ad5671r", .data = &ad5672r_chip_info }, + { .compatible = "adi,ad5673r", .data = &ad5674r_chip_info }, + { .compatible = "adi,ad5675", .data = &ad5676_chip_info }, + { .compatible = "adi,ad5675r", .data = &ad5676r_chip_info }, + { .compatible = "adi,ad5677r", .data = &ad5679r_chip_info }, + { .compatible = "adi,ad5691r", .data = &ad5681r_chip_info }, + { .compatible = "adi,ad5692r", .data = &ad5682r_chip_info }, + { .compatible = "adi,ad5693", .data = &ad5683_chip_info }, + { .compatible = "adi,ad5693r", .data = &ad5683r_chip_info }, + { .compatible = "adi,ad5694", .data = &ad5684_chip_info }, + { .compatible = "adi,ad5694r", .data = &ad5684r_chip_info }, + { .compatible = "adi,ad5695r", .data = &ad5685r_chip_info }, + { .compatible = "adi,ad5696", .data = &ad5686_chip_info }, + { .compatible = "adi,ad5696r", .data = &ad5686r_chip_info }, + { .compatible = "adi,ad5697r", .data = &ad5687r_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad5686_of_match); + +static struct i2c_driver ad5686_i2c_driver = { + .driver = { + .name = "ad5696", + .of_match_table = ad5686_of_match, + }, + .probe = ad5686_i2c_probe, + .id_table = ad5686_i2c_id, +}; + +module_i2c_driver(ad5686_i2c_driver); + +MODULE_AUTHOR("Stefan Popa "); +MODULE_DESCRIPTION("Analog Devices AD5686 and similar multi-channel DACs"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_AD5686"); diff --git a/drivers/iio/dac/ad5706r.c b/drivers/iio/dac/ad5706r.c new file mode 100644 index 000000000..06c4860a7 --- /dev/null +++ b/drivers/iio/dac/ad5706r.c @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD5706R 16-bit Current Output Digital to Analog Converter + * + * Copyright 2026 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* SPI frame layout */ +#define AD5706R_RD_MASK BIT(15) +#define AD5706R_ADDR_MASK GENMASK(11, 0) + +/* Registers */ +#define AD5706R_REG_DAC_INPUT_A_CH(x) (0x60 + ((x) * 2)) +#define AD5706R_REG_DAC_DATA_READBACK_CH(x) (0x68 + ((x) * 2)) + +#define AD5706R_DAC_RESOLUTION 16 +#define AD5706R_DAC_MAX_CODE GENMASK(15, 0) +#define AD5706R_MULTIBYTE_REG_START 0x14 +#define AD5706R_MULTIBYTE_REG_END 0x71 +#define AD5706R_MAX_REG 0x77 + +struct ad5706r_state { + struct spi_device *spi; + struct regmap *regmap; + + u8 tx_buf[4] __aligned(IIO_DMA_MINALIGN); + u8 rx_buf[4]; +}; + +static int ad5706r_reg_len(unsigned int reg) +{ + if (reg >= AD5706R_MULTIBYTE_REG_START && reg <= AD5706R_MULTIBYTE_REG_END) + return 2; + + return 1; +} + +static int ad5706r_regmap_write(void *context, const void *data, size_t count) +{ + struct ad5706r_state *st = context; + unsigned int num_bytes; + u16 reg, val; + + if (count != 4) + return -EINVAL; + + reg = get_unaligned_be16(data); + val = get_unaligned_be16(data + 2); + num_bytes = ad5706r_reg_len(reg); + + struct spi_transfer xfer = { + .tx_buf = st->tx_buf, + .len = num_bytes + 2, + }; + + put_unaligned_be16(reg, &st->tx_buf[0]); + + if (num_bytes == 1) + st->tx_buf[2] = (u8)val; + else if (num_bytes == 2) + put_unaligned_be16(val, &st->tx_buf[2]); + else + return -EINVAL; + + return spi_sync_transfer(st->spi, &xfer, 1); +} + +static int ad5706r_regmap_read(void *context, const void *reg_buf, + size_t reg_size, void *val_buf, size_t val_size) +{ + struct ad5706r_state *st = context; + unsigned int num_bytes; + u16 reg, cmd, val; + int ret; + + if (reg_size != 2 || val_size != 2) + return -EINVAL; + + reg = get_unaligned_be16(reg_buf); + num_bytes = ad5706r_reg_len(reg); + + /* Full duplex, device responds immediately after command */ + struct spi_transfer xfer = { + .tx_buf = st->tx_buf, + .rx_buf = st->rx_buf, + .len = 2 + num_bytes, + }; + + cmd = AD5706R_RD_MASK | (reg & AD5706R_ADDR_MASK); + put_unaligned_be16(cmd, &st->tx_buf[0]); + put_unaligned_be16(0, &st->tx_buf[2]); + + ret = spi_sync_transfer(st->spi, &xfer, 1); + if (ret) + return ret; + + /* Extract value from response (skip 2-byte command echo) */ + if (num_bytes == 1) + val = st->rx_buf[2]; + else if (num_bytes == 2) + val = get_unaligned_be16(&st->rx_buf[2]); + else + return -EINVAL; + + put_unaligned_be16(val, val_buf); + + return 0; +} + +static int ad5706r_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ad5706r_state *st = iio_priv(indio_dev); + unsigned int reg, reg_val; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + reg = AD5706R_REG_DAC_DATA_READBACK_CH(chan->channel); + ret = regmap_read(st->regmap, reg, ®_val); + if (ret) + return ret; + + *val = reg_val; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 50; + *val2 = AD5706R_DAC_RESOLUTION; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static int ad5706r_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ad5706r_state *st = iio_priv(indio_dev); + unsigned int reg; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val < 0 || val > AD5706R_DAC_MAX_CODE) + return -EINVAL; + + reg = AD5706R_REG_DAC_INPUT_A_CH(chan->channel); + return regmap_write(st->regmap, reg, val); + default: + return -EINVAL; + } +} + +static const struct regmap_bus ad5706r_regmap_bus = { + .write = ad5706r_regmap_write, + .read = ad5706r_regmap_read, + .reg_format_endian_default = REGMAP_ENDIAN_BIG, + .val_format_endian_default = REGMAP_ENDIAN_BIG, +}; + +static const struct regmap_config ad5706r_regmap_config = { + .reg_bits = 16, + .val_bits = 16, + .max_register = AD5706R_MAX_REG, +}; + +static const struct iio_info ad5706r_info = { + .read_raw = ad5706r_read_raw, + .write_raw = ad5706r_write_raw, +}; + +#define AD5706R_CHAN(_channel) { \ + .type = IIO_CURRENT, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .output = 1, \ + .indexed = 1, \ + .channel = _channel, \ +} + +static const struct iio_chan_spec ad5706r_channels[] = { + AD5706R_CHAN(0), + AD5706R_CHAN(1), + AD5706R_CHAN(2), + AD5706R_CHAN(3), +}; + +static int ad5706r_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ad5706r_state *st; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + + st->regmap = devm_regmap_init(dev, &ad5706r_regmap_bus, + st, &ad5706r_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "Failed to init regmap\n"); + + indio_dev->name = "ad5706r"; + indio_dev->info = &ad5706r_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad5706r_channels; + indio_dev->num_channels = ARRAY_SIZE(ad5706r_channels); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ad5706r_of_match[] = { + { .compatible = "adi,ad5706r" }, + { } +}; +MODULE_DEVICE_TABLE(of, ad5706r_of_match); + +static const struct spi_device_id ad5706r_id[] = { + { .name = "ad5706r" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad5706r_id); + +static struct spi_driver ad5706r_driver = { + .driver = { + .name = "ad5706r", + .of_match_table = ad5706r_of_match, + }, + .probe = ad5706r_probe, + .id_table = ad5706r_id, +}; +module_spi_driver(ad5706r_driver); + +MODULE_AUTHOR("Alexis Czezar Torreno "); +MODULE_DESCRIPTION("AD5706R 16-bit Current Output DAC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/dac/ad5755.c b/drivers/iio/dac/ad5755.c new file mode 100644 index 000000000..0828b9909 --- /dev/null +++ b/drivers/iio/dac/ad5755.c @@ -0,0 +1,883 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD5755, AD5755-1, AD5757, AD5735, AD5737 Digital to analog converters driver + * + * Copyright 2012 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define AD5755_NUM_CHANNELS 4 + +#define AD5755_ADDR(x) ((x) << 16) + +#define AD5755_WRITE_REG_DATA(chan) (chan) +#define AD5755_WRITE_REG_GAIN(chan) (0x08 | (chan)) +#define AD5755_WRITE_REG_OFFSET(chan) (0x10 | (chan)) +#define AD5755_WRITE_REG_CTRL(chan) (0x1c | (chan)) + +#define AD5755_READ_REG_DATA(chan) (chan) +#define AD5755_READ_REG_CTRL(chan) (0x4 | (chan)) +#define AD5755_READ_REG_GAIN(chan) (0x8 | (chan)) +#define AD5755_READ_REG_OFFSET(chan) (0xc | (chan)) +#define AD5755_READ_REG_CLEAR(chan) (0x10 | (chan)) +#define AD5755_READ_REG_SLEW(chan) (0x14 | (chan)) +#define AD5755_READ_REG_STATUS 0x18 +#define AD5755_READ_REG_MAIN 0x19 +#define AD5755_READ_REG_DC_DC 0x1a + +#define AD5755_CTRL_REG_SLEW 0x0 +#define AD5755_CTRL_REG_MAIN 0x1 +#define AD5755_CTRL_REG_DAC 0x2 +#define AD5755_CTRL_REG_DC_DC 0x3 +#define AD5755_CTRL_REG_SW 0x4 + +#define AD5755_READ_FLAG 0x800000 + +#define AD5755_NOOP 0x1CE000 + +#define AD5755_DAC_INT_EN BIT(8) +#define AD5755_DAC_CLR_EN BIT(7) +#define AD5755_DAC_OUT_EN BIT(6) +#define AD5755_DAC_INT_CURRENT_SENSE_RESISTOR BIT(5) +#define AD5755_DAC_DC_DC_EN BIT(4) +#define AD5755_DAC_VOLTAGE_OVERRANGE_EN BIT(3) + +#define AD5755_DC_DC_MAXV 0 +#define AD5755_DC_DC_FREQ_SHIFT 2 +#define AD5755_DC_DC_PHASE_SHIFT 4 +#define AD5755_EXT_DC_DC_COMP_RES BIT(6) + +#define AD5755_SLEW_STEP_SIZE_SHIFT 0 +#define AD5755_SLEW_RATE_SHIFT 3 +#define AD5755_SLEW_ENABLE BIT(12) + +enum ad5755_mode { + AD5755_MODE_VOLTAGE_0V_5V = 0, + AD5755_MODE_VOLTAGE_0V_10V = 1, + AD5755_MODE_VOLTAGE_PLUSMINUS_5V = 2, + AD5755_MODE_VOLTAGE_PLUSMINUS_10V = 3, + AD5755_MODE_CURRENT_4mA_20mA = 4, + AD5755_MODE_CURRENT_0mA_20mA = 5, + AD5755_MODE_CURRENT_0mA_24mA = 6, +}; + +enum ad5755_dc_dc_phase { + AD5755_DC_DC_PHASE_ALL_SAME_EDGE = 0, + AD5755_DC_DC_PHASE_A_B_SAME_EDGE_C_D_OPP_EDGE = 1, + AD5755_DC_DC_PHASE_A_C_SAME_EDGE_B_D_OPP_EDGE = 2, + AD5755_DC_DC_PHASE_90_DEGREE = 3, +}; + +enum ad5755_dc_dc_freq { + AD5755_DC_DC_FREQ_250kHZ = 0, + AD5755_DC_DC_FREQ_410kHZ = 1, + AD5755_DC_DC_FREQ_650kHZ = 2, +}; + +enum ad5755_dc_dc_maxv { + AD5755_DC_DC_MAXV_23V = 0, + AD5755_DC_DC_MAXV_24V5 = 1, + AD5755_DC_DC_MAXV_27V = 2, + AD5755_DC_DC_MAXV_29V5 = 3, +}; + +enum ad5755_slew_rate { + AD5755_SLEW_RATE_64k = 0, + AD5755_SLEW_RATE_32k = 1, + AD5755_SLEW_RATE_16k = 2, + AD5755_SLEW_RATE_8k = 3, + AD5755_SLEW_RATE_4k = 4, + AD5755_SLEW_RATE_2k = 5, + AD5755_SLEW_RATE_1k = 6, + AD5755_SLEW_RATE_500 = 7, + AD5755_SLEW_RATE_250 = 8, + AD5755_SLEW_RATE_125 = 9, + AD5755_SLEW_RATE_64 = 10, + AD5755_SLEW_RATE_32 = 11, + AD5755_SLEW_RATE_16 = 12, + AD5755_SLEW_RATE_8 = 13, + AD5755_SLEW_RATE_4 = 14, + AD5755_SLEW_RATE_0_5 = 15, +}; + +enum ad5755_slew_step_size { + AD5755_SLEW_STEP_SIZE_1 = 0, + AD5755_SLEW_STEP_SIZE_2 = 1, + AD5755_SLEW_STEP_SIZE_4 = 2, + AD5755_SLEW_STEP_SIZE_8 = 3, + AD5755_SLEW_STEP_SIZE_16 = 4, + AD5755_SLEW_STEP_SIZE_32 = 5, + AD5755_SLEW_STEP_SIZE_64 = 6, + AD5755_SLEW_STEP_SIZE_128 = 7, + AD5755_SLEW_STEP_SIZE_256 = 8, +}; + +/** + * struct ad5755_platform_data - AD5755 DAC driver platform data + * @ext_dc_dc_compenstation_resistor: Whether an external DC-DC converter + * compensation register is used. + * @dc_dc_phase: DC-DC converter phase. + * @dc_dc_freq: DC-DC converter frequency. + * @dc_dc_maxv: DC-DC maximum allowed boost voltage. + * @dac: Per DAC instance parameters. + * @dac.mode: The mode to be used for the DAC output. + * @dac.ext_current_sense_resistor: Whether an external current sense resistor + * is used. + * @dac.enable_voltage_overrange: Whether to enable 20% voltage output overrange. + * @dac.slew.enable: Whether to enable digital slew. + * @dac.slew.rate: Slew rate of the digital slew. + * @dac.slew.step_size: Slew step size of the digital slew. + **/ +struct ad5755_platform_data { + bool ext_dc_dc_compenstation_resistor; + enum ad5755_dc_dc_phase dc_dc_phase; + enum ad5755_dc_dc_freq dc_dc_freq; + enum ad5755_dc_dc_maxv dc_dc_maxv; + + struct { + enum ad5755_mode mode; + bool ext_current_sense_resistor; + bool enable_voltage_overrange; + struct { + bool enable; + enum ad5755_slew_rate rate; + enum ad5755_slew_step_size step_size; + } slew; + } dac[4]; +}; + +/** + * struct ad5755_chip_info - chip specific information + * @channel_template: channel specification + * @calib_shift: shift for the calibration data registers + * @has_voltage_out: whether the chip has voltage outputs + */ +struct ad5755_chip_info { + const struct iio_chan_spec channel_template; + unsigned int calib_shift; + bool has_voltage_out; +}; + +/** + * struct ad5755_state - driver instance specific data + * @spi: spi device the driver is attached to + * @chip_info: chip model specific constants, available modes etc + * @pwr_down: bitmask which contains hether a channel is powered down or not + * @ctrl: software shadow of the channel ctrl registers + * @channels: iio channel spec for the device + * @lock: lock to protect the data buffer during SPI ops + * @data: spi transfer buffers + */ +struct ad5755_state { + struct spi_device *spi; + const struct ad5755_chip_info *chip_info; + unsigned int pwr_down; + unsigned int ctrl[AD5755_NUM_CHANNELS]; + struct iio_chan_spec channels[AD5755_NUM_CHANNELS]; + struct mutex lock; + + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + */ + + union { + __be32 d32; + u8 d8[4]; + } data[2] __aligned(IIO_DMA_MINALIGN); +}; + +enum ad5755_type { + ID_AD5755, + ID_AD5757, + ID_AD5735, + ID_AD5737, +}; + +static const int ad5755_dcdc_freq_table[][2] = { + { 250000, AD5755_DC_DC_FREQ_250kHZ }, + { 410000, AD5755_DC_DC_FREQ_410kHZ }, + { 650000, AD5755_DC_DC_FREQ_650kHZ } +}; + +static const int ad5755_dcdc_maxv_table[][2] = { + { 23000000, AD5755_DC_DC_MAXV_23V }, + { 24500000, AD5755_DC_DC_MAXV_24V5 }, + { 27000000, AD5755_DC_DC_MAXV_27V }, + { 29500000, AD5755_DC_DC_MAXV_29V5 }, +}; + +static const int ad5755_slew_rate_table[][2] = { + { 64000, AD5755_SLEW_RATE_64k }, + { 32000, AD5755_SLEW_RATE_32k }, + { 16000, AD5755_SLEW_RATE_16k }, + { 8000, AD5755_SLEW_RATE_8k }, + { 4000, AD5755_SLEW_RATE_4k }, + { 2000, AD5755_SLEW_RATE_2k }, + { 1000, AD5755_SLEW_RATE_1k }, + { 500, AD5755_SLEW_RATE_500 }, + { 250, AD5755_SLEW_RATE_250 }, + { 125, AD5755_SLEW_RATE_125 }, + { 64, AD5755_SLEW_RATE_64 }, + { 32, AD5755_SLEW_RATE_32 }, + { 16, AD5755_SLEW_RATE_16 }, + { 8, AD5755_SLEW_RATE_8 }, + { 4, AD5755_SLEW_RATE_4 }, + { 0, AD5755_SLEW_RATE_0_5 }, +}; + +static const int ad5755_slew_step_table[][2] = { + { 256, AD5755_SLEW_STEP_SIZE_256 }, + { 128, AD5755_SLEW_STEP_SIZE_128 }, + { 64, AD5755_SLEW_STEP_SIZE_64 }, + { 32, AD5755_SLEW_STEP_SIZE_32 }, + { 16, AD5755_SLEW_STEP_SIZE_16 }, + { 4, AD5755_SLEW_STEP_SIZE_4 }, + { 2, AD5755_SLEW_STEP_SIZE_2 }, + { 1, AD5755_SLEW_STEP_SIZE_1 }, +}; + +static int ad5755_write_unlocked(struct iio_dev *indio_dev, + unsigned int reg, unsigned int val) +{ + struct ad5755_state *st = iio_priv(indio_dev); + + st->data[0].d32 = cpu_to_be32((reg << 16) | val); + + return spi_write(st->spi, &st->data[0].d8[1], 3); +} + +static int ad5755_write_ctrl_unlocked(struct iio_dev *indio_dev, + unsigned int channel, unsigned int reg, unsigned int val) +{ + return ad5755_write_unlocked(indio_dev, + AD5755_WRITE_REG_CTRL(channel), (reg << 13) | val); +} + +static int ad5755_write(struct iio_dev *indio_dev, unsigned int reg, + unsigned int val) +{ + struct ad5755_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->lock); + ret = ad5755_write_unlocked(indio_dev, reg, val); + mutex_unlock(&st->lock); + + return ret; +} + +static int ad5755_write_ctrl(struct iio_dev *indio_dev, unsigned int channel, + unsigned int reg, unsigned int val) +{ + struct ad5755_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->lock); + ret = ad5755_write_ctrl_unlocked(indio_dev, channel, reg, val); + mutex_unlock(&st->lock); + + return ret; +} + +static int ad5755_read(struct iio_dev *indio_dev, unsigned int addr) +{ + struct ad5755_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer t[] = { + { + .tx_buf = &st->data[0].d8[1], + .len = 3, + .cs_change = 1, + }, { + .tx_buf = &st->data[1].d8[1], + .rx_buf = &st->data[1].d8[1], + .len = 3, + }, + }; + + mutex_lock(&st->lock); + + st->data[0].d32 = cpu_to_be32(AD5755_READ_FLAG | (addr << 16)); + st->data[1].d32 = cpu_to_be32(AD5755_NOOP); + + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); + if (ret >= 0) + ret = be32_to_cpu(st->data[1].d32) & 0xffff; + + mutex_unlock(&st->lock); + + return ret; +} + +static int ad5755_update_dac_ctrl(struct iio_dev *indio_dev, + unsigned int channel, unsigned int set, unsigned int clr) +{ + struct ad5755_state *st = iio_priv(indio_dev); + int ret; + + st->ctrl[channel] |= set; + st->ctrl[channel] &= ~clr; + + ret = ad5755_write_ctrl_unlocked(indio_dev, channel, + AD5755_CTRL_REG_DAC, st->ctrl[channel]); + + return ret; +} + +static int ad5755_set_channel_pwr_down(struct iio_dev *indio_dev, + unsigned int channel, bool pwr_down) +{ + struct ad5755_state *st = iio_priv(indio_dev); + unsigned int mask = BIT(channel); + + mutex_lock(&st->lock); + + if ((bool)(st->pwr_down & mask) == pwr_down) + goto out_unlock; + + if (!pwr_down) { + st->pwr_down &= ~mask; + ad5755_update_dac_ctrl(indio_dev, channel, + AD5755_DAC_INT_EN | AD5755_DAC_DC_DC_EN, 0); + udelay(200); + ad5755_update_dac_ctrl(indio_dev, channel, + AD5755_DAC_OUT_EN, 0); + } else { + st->pwr_down |= mask; + ad5755_update_dac_ctrl(indio_dev, channel, + 0, AD5755_DAC_INT_EN | AD5755_DAC_OUT_EN | + AD5755_DAC_DC_DC_EN); + } + +out_unlock: + mutex_unlock(&st->lock); + + return 0; +} + +static const int ad5755_min_max_table[][2] = { + [AD5755_MODE_VOLTAGE_0V_5V] = { 0, 5000 }, + [AD5755_MODE_VOLTAGE_0V_10V] = { 0, 10000 }, + [AD5755_MODE_VOLTAGE_PLUSMINUS_5V] = { -5000, 5000 }, + [AD5755_MODE_VOLTAGE_PLUSMINUS_10V] = { -10000, 10000 }, + [AD5755_MODE_CURRENT_4mA_20mA] = { 4, 20 }, + [AD5755_MODE_CURRENT_0mA_20mA] = { 0, 20 }, + [AD5755_MODE_CURRENT_0mA_24mA] = { 0, 24 }, +}; + +static void ad5755_get_min_max(struct ad5755_state *st, + struct iio_chan_spec const *chan, int *min, int *max) +{ + enum ad5755_mode mode = st->ctrl[chan->channel] & 7; + *min = ad5755_min_max_table[mode][0]; + *max = ad5755_min_max_table[mode][1]; +} + +static inline int ad5755_get_offset(struct ad5755_state *st, + struct iio_chan_spec const *chan) +{ + int min, max; + + ad5755_get_min_max(st, chan, &min, &max); + return (min * (1 << chan->scan_type.realbits)) / (max - min); +} + +static int ad5755_chan_reg_info(struct ad5755_state *st, + struct iio_chan_spec const *chan, long info, bool write, + unsigned int *reg, unsigned int *shift, unsigned int *offset) +{ + switch (info) { + case IIO_CHAN_INFO_RAW: + if (write) + *reg = AD5755_WRITE_REG_DATA(chan->address); + else + *reg = AD5755_READ_REG_DATA(chan->address); + *shift = chan->scan_type.shift; + *offset = 0; + break; + case IIO_CHAN_INFO_CALIBBIAS: + if (write) + *reg = AD5755_WRITE_REG_OFFSET(chan->address); + else + *reg = AD5755_READ_REG_OFFSET(chan->address); + *shift = st->chip_info->calib_shift; + *offset = 32768; + break; + case IIO_CHAN_INFO_CALIBSCALE: + if (write) + *reg = AD5755_WRITE_REG_GAIN(chan->address); + else + *reg = AD5755_READ_REG_GAIN(chan->address); + *shift = st->chip_info->calib_shift; + *offset = 0; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ad5755_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, int *val2, long info) +{ + struct ad5755_state *st = iio_priv(indio_dev); + unsigned int reg, shift, offset; + int min, max; + int ret; + + switch (info) { + case IIO_CHAN_INFO_SCALE: + ad5755_get_min_max(st, chan, &min, &max); + *val = max - min; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: + *val = ad5755_get_offset(st, chan); + return IIO_VAL_INT; + default: + ret = ad5755_chan_reg_info(st, chan, info, false, + ®, &shift, &offset); + if (ret) + return ret; + + ret = ad5755_read(indio_dev, reg); + if (ret < 0) + return ret; + + *val = (ret - offset) >> shift; + + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int ad5755_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int val, int val2, long info) +{ + struct ad5755_state *st = iio_priv(indio_dev); + unsigned int shift, reg, offset; + int ret; + + ret = ad5755_chan_reg_info(st, chan, info, true, + ®, &shift, &offset); + if (ret) + return ret; + + val <<= shift; + val += offset; + + if (val < 0 || val > 0xffff) + return -EINVAL; + + return ad5755_write(indio_dev, reg, val); +} + +static ssize_t ad5755_read_powerdown(struct iio_dev *indio_dev, uintptr_t priv, + const struct iio_chan_spec *chan, char *buf) +{ + struct ad5755_state *st = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", + (bool)(st->pwr_down & (1 << chan->channel))); +} + +static ssize_t ad5755_write_powerdown(struct iio_dev *indio_dev, uintptr_t priv, + struct iio_chan_spec const *chan, const char *buf, size_t len) +{ + bool pwr_down; + int ret; + + ret = kstrtobool(buf, &pwr_down); + if (ret) + return ret; + + ret = ad5755_set_channel_pwr_down(indio_dev, chan->channel, pwr_down); + return ret ? ret : len; +} + +static const struct iio_info ad5755_info = { + .read_raw = ad5755_read_raw, + .write_raw = ad5755_write_raw, +}; + +static const struct iio_chan_spec_ext_info ad5755_ext_info[] = { + { + .name = "powerdown", + .read = ad5755_read_powerdown, + .write = ad5755_write_powerdown, + .shared = IIO_SEPARATE, + }, + { } +}; + +#define AD5755_CHANNEL(_bits) { \ + .indexed = 1, \ + .output = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (_bits), \ + .storagebits = 16, \ + .shift = 16 - (_bits), \ + }, \ + .ext_info = ad5755_ext_info, \ +} + +static const struct ad5755_chip_info ad5755_chip_info_tbl[] = { + [ID_AD5735] = { + .channel_template = AD5755_CHANNEL(14), + .has_voltage_out = true, + .calib_shift = 4, + }, + [ID_AD5737] = { + .channel_template = AD5755_CHANNEL(14), + .has_voltage_out = false, + .calib_shift = 4, + }, + [ID_AD5755] = { + .channel_template = AD5755_CHANNEL(16), + .has_voltage_out = true, + .calib_shift = 0, + }, + [ID_AD5757] = { + .channel_template = AD5755_CHANNEL(16), + .has_voltage_out = false, + .calib_shift = 0, + }, +}; + +static bool ad5755_is_valid_mode(struct ad5755_state *st, enum ad5755_mode mode) +{ + switch (mode) { + case AD5755_MODE_VOLTAGE_0V_5V: + case AD5755_MODE_VOLTAGE_0V_10V: + case AD5755_MODE_VOLTAGE_PLUSMINUS_5V: + case AD5755_MODE_VOLTAGE_PLUSMINUS_10V: + return st->chip_info->has_voltage_out; + case AD5755_MODE_CURRENT_4mA_20mA: + case AD5755_MODE_CURRENT_0mA_20mA: + case AD5755_MODE_CURRENT_0mA_24mA: + return true; + default: + return false; + } +} + +static int ad5755_setup_pdata(struct iio_dev *indio_dev, + const struct ad5755_platform_data *pdata) +{ + struct ad5755_state *st = iio_priv(indio_dev); + unsigned int val; + unsigned int i; + int ret; + + if (pdata->dc_dc_phase > AD5755_DC_DC_PHASE_90_DEGREE || + pdata->dc_dc_freq > AD5755_DC_DC_FREQ_650kHZ || + pdata->dc_dc_maxv > AD5755_DC_DC_MAXV_29V5) + return -EINVAL; + + val = pdata->dc_dc_maxv << AD5755_DC_DC_MAXV; + val |= pdata->dc_dc_freq << AD5755_DC_DC_FREQ_SHIFT; + val |= pdata->dc_dc_phase << AD5755_DC_DC_PHASE_SHIFT; + if (pdata->ext_dc_dc_compenstation_resistor) + val |= AD5755_EXT_DC_DC_COMP_RES; + + ret = ad5755_write_ctrl(indio_dev, 0, AD5755_CTRL_REG_DC_DC, val); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(pdata->dac); ++i) { + val = pdata->dac[i].slew.step_size << + AD5755_SLEW_STEP_SIZE_SHIFT; + val |= pdata->dac[i].slew.rate << + AD5755_SLEW_RATE_SHIFT; + if (pdata->dac[i].slew.enable) + val |= AD5755_SLEW_ENABLE; + + ret = ad5755_write_ctrl(indio_dev, i, + AD5755_CTRL_REG_SLEW, val); + if (ret < 0) + return ret; + } + + for (i = 0; i < ARRAY_SIZE(pdata->dac); ++i) { + if (!ad5755_is_valid_mode(st, pdata->dac[i].mode)) + return -EINVAL; + + val = 0; + if (!pdata->dac[i].ext_current_sense_resistor) + val |= AD5755_DAC_INT_CURRENT_SENSE_RESISTOR; + if (pdata->dac[i].enable_voltage_overrange) + val |= AD5755_DAC_VOLTAGE_OVERRANGE_EN; + val |= pdata->dac[i].mode; + + ret = ad5755_update_dac_ctrl(indio_dev, i, val, 0); + if (ret < 0) + return ret; + } + + return 0; +} + +static bool ad5755_is_voltage_mode(enum ad5755_mode mode) +{ + switch (mode) { + case AD5755_MODE_VOLTAGE_0V_5V: + case AD5755_MODE_VOLTAGE_0V_10V: + case AD5755_MODE_VOLTAGE_PLUSMINUS_5V: + case AD5755_MODE_VOLTAGE_PLUSMINUS_10V: + return true; + default: + return false; + } +} + +static int ad5755_init_channels(struct iio_dev *indio_dev, + const struct ad5755_platform_data *pdata) +{ + struct ad5755_state *st = iio_priv(indio_dev); + struct iio_chan_spec *channels = st->channels; + unsigned int i; + + for (i = 0; i < AD5755_NUM_CHANNELS; ++i) { + channels[i] = st->chip_info->channel_template; + channels[i].channel = i; + channels[i].address = i; + if (pdata && ad5755_is_voltage_mode(pdata->dac[i].mode)) + channels[i].type = IIO_VOLTAGE; + else + channels[i].type = IIO_CURRENT; + } + + indio_dev->channels = channels; + + return 0; +} + +#define AD5755_DEFAULT_DAC_PDATA { \ + .mode = AD5755_MODE_CURRENT_4mA_20mA, \ + .ext_current_sense_resistor = true, \ + .enable_voltage_overrange = false, \ + .slew = { \ + .enable = false, \ + .rate = AD5755_SLEW_RATE_64k, \ + .step_size = AD5755_SLEW_STEP_SIZE_1, \ + }, \ + } + +static const struct ad5755_platform_data ad5755_default_pdata = { + .ext_dc_dc_compenstation_resistor = false, + .dc_dc_phase = AD5755_DC_DC_PHASE_ALL_SAME_EDGE, + .dc_dc_freq = AD5755_DC_DC_FREQ_410kHZ, + .dc_dc_maxv = AD5755_DC_DC_MAXV_23V, + .dac = { + [0] = AD5755_DEFAULT_DAC_PDATA, + [1] = AD5755_DEFAULT_DAC_PDATA, + [2] = AD5755_DEFAULT_DAC_PDATA, + [3] = AD5755_DEFAULT_DAC_PDATA, + }, +}; + +static struct ad5755_platform_data *ad5755_parse_fw(struct device *dev) +{ + struct ad5755_platform_data *pdata; + unsigned int tmp; + unsigned int tmparray[3]; + int devnr, i; + + if (!dev_fwnode(dev)) + return NULL; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + pdata->ext_dc_dc_compenstation_resistor = + device_property_read_bool(dev, "adi,ext-dc-dc-compenstation-resistor"); + + pdata->dc_dc_phase = AD5755_DC_DC_PHASE_ALL_SAME_EDGE; + device_property_read_u32(dev, "adi,dc-dc-phase", &pdata->dc_dc_phase); + + pdata->dc_dc_freq = AD5755_DC_DC_FREQ_410kHZ; + if (!device_property_read_u32(dev, "adi,dc-dc-freq-hz", &tmp)) { + for (i = 0; i < ARRAY_SIZE(ad5755_dcdc_freq_table); i++) { + if (tmp == ad5755_dcdc_freq_table[i][0]) { + pdata->dc_dc_freq = ad5755_dcdc_freq_table[i][1]; + break; + } + } + + if (i == ARRAY_SIZE(ad5755_dcdc_freq_table)) + dev_err(dev, + "adi,dc-dc-freq out of range selecting 410kHz\n"); + } + + pdata->dc_dc_maxv = AD5755_DC_DC_MAXV_23V; + if (!device_property_read_u32(dev, "adi,dc-dc-max-microvolt", &tmp)) { + for (i = 0; i < ARRAY_SIZE(ad5755_dcdc_maxv_table); i++) { + if (tmp == ad5755_dcdc_maxv_table[i][0]) { + pdata->dc_dc_maxv = ad5755_dcdc_maxv_table[i][1]; + break; + } + } + if (i == ARRAY_SIZE(ad5755_dcdc_maxv_table)) + dev_err(dev, + "adi,dc-dc-maxv out of range selecting 23V\n"); + } + + devnr = 0; + device_for_each_child_node_scoped(dev, pp) { + if (devnr >= AD5755_NUM_CHANNELS) { + dev_err(dev, + "There are too many channels defined in DT\n"); + devm_kfree(dev, pdata); + return NULL; + } + + pdata->dac[devnr].mode = AD5755_MODE_CURRENT_4mA_20mA; + fwnode_property_read_u32(pp, "adi,mode", &pdata->dac[devnr].mode); + + pdata->dac[devnr].ext_current_sense_resistor = + fwnode_property_read_bool(pp, "adi,ext-current-sense-resistor"); + + pdata->dac[devnr].enable_voltage_overrange = + fwnode_property_read_bool(pp, "adi,enable-voltage-overrange"); + + if (!fwnode_property_read_u32_array(pp, "adi,slew", tmparray, 3)) { + pdata->dac[devnr].slew.enable = tmparray[0]; + + pdata->dac[devnr].slew.rate = AD5755_SLEW_RATE_64k; + for (i = 0; i < ARRAY_SIZE(ad5755_slew_rate_table); i++) { + if (tmparray[1] == ad5755_slew_rate_table[i][0]) { + pdata->dac[devnr].slew.rate = + ad5755_slew_rate_table[i][1]; + break; + } + } + if (i == ARRAY_SIZE(ad5755_slew_rate_table)) + dev_err(dev, + "channel %d slew rate out of range selecting 64kHz\n", + devnr); + + pdata->dac[devnr].slew.step_size = AD5755_SLEW_STEP_SIZE_1; + for (i = 0; i < ARRAY_SIZE(ad5755_slew_step_table); i++) { + if (tmparray[2] == ad5755_slew_step_table[i][0]) { + pdata->dac[devnr].slew.step_size = + ad5755_slew_step_table[i][1]; + break; + } + } + if (i == ARRAY_SIZE(ad5755_slew_step_table)) + dev_err(dev, + "channel %d slew step size out of range selecting 1 LSB\n", + devnr); + } else { + pdata->dac[devnr].slew.enable = false; + pdata->dac[devnr].slew.rate = AD5755_SLEW_RATE_64k; + pdata->dac[devnr].slew.step_size = + AD5755_SLEW_STEP_SIZE_1; + } + devnr++; + } + + return pdata; +} + +static int ad5755_probe(struct spi_device *spi) +{ + const struct ad5755_platform_data *pdata; + struct iio_dev *indio_dev; + struct ad5755_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) { + dev_err(&spi->dev, "Failed to allocate iio device\n"); + return -ENOMEM; + } + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + st->chip_info = spi_get_device_match_data(spi); + st->spi = spi; + st->pwr_down = 0xf; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &ad5755_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = AD5755_NUM_CHANNELS; + + ret = devm_mutex_init(&spi->dev, &st->lock); + if (ret) + return ret; + + pdata = ad5755_parse_fw(&spi->dev); + if (!pdata) { + dev_warn(&spi->dev, "no firmware provided parameters? using default\n"); + pdata = &ad5755_default_pdata; + } + + ret = ad5755_init_channels(indio_dev, pdata); + if (ret) + return ret; + + ret = ad5755_setup_pdata(indio_dev, pdata); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id ad5755_id[] = { + { .name = "ad5755", .driver_data = (kernel_ulong_t)&ad5755_chip_info_tbl[ID_AD5755] }, + { .name = "ad5755-1", .driver_data = (kernel_ulong_t)&ad5755_chip_info_tbl[ID_AD5755] }, + { .name = "ad5757", .driver_data = (kernel_ulong_t)&ad5755_chip_info_tbl[ID_AD5757] }, + { .name = "ad5735", .driver_data = (kernel_ulong_t)&ad5755_chip_info_tbl[ID_AD5735] }, + { .name = "ad5737", .driver_data = (kernel_ulong_t)&ad5755_chip_info_tbl[ID_AD5737] }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad5755_id); + +static const struct of_device_id ad5755_of_match[] = { + { .compatible = "adi,ad5755", &ad5755_chip_info_tbl[ID_AD5755] }, + { .compatible = "adi,ad5755-1", &ad5755_chip_info_tbl[ID_AD5755] }, + { .compatible = "adi,ad5757", &ad5755_chip_info_tbl[ID_AD5757] }, + { .compatible = "adi,ad5735", &ad5755_chip_info_tbl[ID_AD5735] }, + { .compatible = "adi,ad5737", &ad5755_chip_info_tbl[ID_AD5737] }, + { } +}; +MODULE_DEVICE_TABLE(of, ad5755_of_match); + +static struct spi_driver ad5755_driver = { + .driver = { + .name = "ad5755", + .of_match_table = ad5755_of_match, + }, + .probe = ad5755_probe, + .id_table = ad5755_id, +}; +module_spi_driver(ad5755_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Analog Devices AD5755/55-1/57/35/37 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5758.c b/drivers/iio/dac/ad5758.c new file mode 100644 index 000000000..44efa373c --- /dev/null +++ b/drivers/iio/dac/ad5758.c @@ -0,0 +1,905 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AD5758 Digital to analog converters driver + * + * Copyright 2018 Analog Devices Inc. + * + * TODO: Currently CRC is not supported in this driver + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* AD5758 registers definition */ +#define AD5758_NOP 0x00 +#define AD5758_DAC_INPUT 0x01 +#define AD5758_DAC_OUTPUT 0x02 +#define AD5758_CLEAR_CODE 0x03 +#define AD5758_USER_GAIN 0x04 +#define AD5758_USER_OFFSET 0x05 +#define AD5758_DAC_CONFIG 0x06 +#define AD5758_SW_LDAC 0x07 +#define AD5758_KEY 0x08 +#define AD5758_GP_CONFIG1 0x09 +#define AD5758_GP_CONFIG2 0x0A +#define AD5758_DCDC_CONFIG1 0x0B +#define AD5758_DCDC_CONFIG2 0x0C +#define AD5758_WDT_CONFIG 0x0F +#define AD5758_DIGITAL_DIAG_CONFIG 0x10 +#define AD5758_ADC_CONFIG 0x11 +#define AD5758_FAULT_PIN_CONFIG 0x12 +#define AD5758_TWO_STAGE_READBACK_SELECT 0x13 +#define AD5758_DIGITAL_DIAG_RESULTS 0x14 +#define AD5758_ANALOG_DIAG_RESULTS 0x15 +#define AD5758_STATUS 0x16 +#define AD5758_CHIP_ID 0x17 +#define AD5758_FREQ_MONITOR 0x18 +#define AD5758_DEVICE_ID_0 0x19 +#define AD5758_DEVICE_ID_1 0x1A +#define AD5758_DEVICE_ID_2 0x1B +#define AD5758_DEVICE_ID_3 0x1C + +/* AD5758_DAC_CONFIG */ +#define AD5758_DAC_CONFIG_RANGE_MSK GENMASK(3, 0) +#define AD5758_DAC_CONFIG_RANGE_MODE(x) (((x) & 0xF) << 0) +#define AD5758_DAC_CONFIG_INT_EN_MSK BIT(5) +#define AD5758_DAC_CONFIG_INT_EN_MODE(x) (((x) & 0x1) << 5) +#define AD5758_DAC_CONFIG_OUT_EN_MSK BIT(6) +#define AD5758_DAC_CONFIG_OUT_EN_MODE(x) (((x) & 0x1) << 6) +#define AD5758_DAC_CONFIG_SR_EN_MSK BIT(8) +#define AD5758_DAC_CONFIG_SR_EN_MODE(x) (((x) & 0x1) << 8) +#define AD5758_DAC_CONFIG_SR_CLOCK_MSK GENMASK(12, 9) +#define AD5758_DAC_CONFIG_SR_CLOCK_MODE(x) (((x) & 0xF) << 9) +#define AD5758_DAC_CONFIG_SR_STEP_MSK GENMASK(15, 13) +#define AD5758_DAC_CONFIG_SR_STEP_MODE(x) (((x) & 0x7) << 13) + +/* AD5758_KEY */ +#define AD5758_KEY_CODE_RESET_1 0x15FA +#define AD5758_KEY_CODE_RESET_2 0xAF51 +#define AD5758_KEY_CODE_SINGLE_ADC_CONV 0x1ADC +#define AD5758_KEY_CODE_RESET_WDT 0x0D06 +#define AD5758_KEY_CODE_CALIB_MEM_REFRESH 0xFCBA + +/* AD5758_DCDC_CONFIG1 */ +#define AD5758_DCDC_CONFIG1_DCDC_VPROG_MSK GENMASK(4, 0) +#define AD5758_DCDC_CONFIG1_DCDC_VPROG_MODE(x) (((x) & 0x1F) << 0) +#define AD5758_DCDC_CONFIG1_DCDC_MODE_MSK GENMASK(6, 5) +#define AD5758_DCDC_CONFIG1_DCDC_MODE_MODE(x) (((x) & 0x3) << 5) + +/* AD5758_DCDC_CONFIG2 */ +#define AD5758_DCDC_CONFIG2_ILIMIT_MSK GENMASK(3, 1) +#define AD5758_DCDC_CONFIG2_ILIMIT_MODE(x) (((x) & 0x7) << 1) +#define AD5758_DCDC_CONFIG2_INTR_SAT_3WI_MSK BIT(11) +#define AD5758_DCDC_CONFIG2_BUSY_3WI_MSK BIT(12) + +/* AD5758_DIGITAL_DIAG_RESULTS */ +#define AD5758_CAL_MEM_UNREFRESHED_MSK BIT(15) + +/* AD5758_ADC_CONFIG */ +#define AD5758_ADC_CONFIG_PPC_BUF_EN(x) (((x) & 0x1) << 11) +#define AD5758_ADC_CONFIG_PPC_BUF_MSK BIT(11) + +#define AD5758_WR_FLAG_MSK(x) (0x80 | ((x) & 0x1F)) + +#define AD5758_FULL_SCALE_MICRO 65535000000ULL + +struct ad5758_range { + int reg; + int min; + int max; +}; + +/** + * struct ad5758_state - driver instance specific data + * @spi: spi_device + * @lock: mutex lock + * @gpio_reset: gpio descriptor for the reset line + * @out_range: struct which stores the output range + * @dc_dc_mode: variable which stores the mode of operation + * @dc_dc_ilim: variable which stores the dc-to-dc converter current limit + * @slew_time: variable which stores the target slew time + * @pwr_down: variable which contains whether a channel is powered down or not + * @d32: spi transfer buffers + */ +struct ad5758_state { + struct spi_device *spi; + struct mutex lock; + struct gpio_desc *gpio_reset; + struct ad5758_range out_range; + unsigned int dc_dc_mode; + unsigned int dc_dc_ilim; + unsigned int slew_time; + bool pwr_down; + __be32 d32[3]; +}; + +/* + * Output ranges corresponding to bits [3:0] from DAC_CONFIG register + * 0000: 0 V to 5 V voltage range + * 0001: 0 V to 10 V voltage range + * 0010: ±5 V voltage range + * 0011: ±10 V voltage range + * 1000: 0 mA to 20 mA current range + * 1001: 0 mA to 24 mA current range + * 1010: 4 mA to 20 mA current range + * 1011: ±20 mA current range + * 1100: ±24 mA current range + * 1101: -1 mA to +22 mA current range + */ +enum ad5758_output_range { + AD5758_RANGE_0V_5V, + AD5758_RANGE_0V_10V, + AD5758_RANGE_PLUSMINUS_5V, + AD5758_RANGE_PLUSMINUS_10V, + AD5758_RANGE_0mA_20mA = 8, + AD5758_RANGE_0mA_24mA, + AD5758_RANGE_4mA_24mA, + AD5758_RANGE_PLUSMINUS_20mA, + AD5758_RANGE_PLUSMINUS_24mA, + AD5758_RANGE_MINUS_1mA_PLUS_22mA, +}; + +enum ad5758_dc_dc_mode { + AD5758_DCDC_MODE_POWER_OFF, + AD5758_DCDC_MODE_DPC_CURRENT, + AD5758_DCDC_MODE_DPC_VOLTAGE, + AD5758_DCDC_MODE_PPC_CURRENT, +}; + +static const struct ad5758_range ad5758_voltage_range[] = { + { AD5758_RANGE_0V_5V, 0, 5000000 }, + { AD5758_RANGE_0V_10V, 0, 10000000 }, + { AD5758_RANGE_PLUSMINUS_5V, -5000000, 5000000 }, + { AD5758_RANGE_PLUSMINUS_10V, -10000000, 10000000 } +}; + +static const struct ad5758_range ad5758_current_range[] = { + { AD5758_RANGE_0mA_20mA, 0, 20000}, + { AD5758_RANGE_0mA_24mA, 0, 24000 }, + { AD5758_RANGE_4mA_24mA, 4, 24000 }, + { AD5758_RANGE_PLUSMINUS_20mA, -20000, 20000 }, + { AD5758_RANGE_PLUSMINUS_24mA, -24000, 24000 }, + { AD5758_RANGE_MINUS_1mA_PLUS_22mA, -1000, 22000 }, +}; + +static const int ad5758_sr_clk[16] = { + 240000, 200000, 150000, 128000, 64000, 32000, 16000, 8000, 4000, 2000, + 1000, 512, 256, 128, 64, 16 +}; + +static const int ad5758_sr_step[8] = { + 4, 12, 64, 120, 256, 500, 1820, 2048 +}; + +static const int ad5758_dc_dc_ilim[6] = { + 150000, 200000, 250000, 300000, 350000, 400000 +}; + +static int ad5758_spi_reg_read(struct ad5758_state *st, unsigned int addr) +{ + struct spi_transfer t[] = { + { + .tx_buf = &st->d32[0], + .len = 4, + .cs_change = 1, + }, { + .tx_buf = &st->d32[1], + .rx_buf = &st->d32[2], + .len = 4, + }, + }; + int ret; + + st->d32[0] = cpu_to_be32( + (AD5758_WR_FLAG_MSK(AD5758_TWO_STAGE_READBACK_SELECT) << 24) | + (addr << 8)); + st->d32[1] = cpu_to_be32(AD5758_WR_FLAG_MSK(AD5758_NOP) << 24); + + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); + if (ret < 0) + return ret; + + return (be32_to_cpu(st->d32[2]) >> 8) & 0xFFFF; +} + +static int ad5758_spi_reg_write(struct ad5758_state *st, + unsigned int addr, + unsigned int val) +{ + st->d32[0] = cpu_to_be32((AD5758_WR_FLAG_MSK(addr) << 24) | + ((val & 0xFFFF) << 8)); + + return spi_write(st->spi, &st->d32[0], sizeof(st->d32[0])); +} + +static int ad5758_spi_write_mask(struct ad5758_state *st, + unsigned int addr, + unsigned long int mask, + unsigned int val) +{ + int regval; + + regval = ad5758_spi_reg_read(st, addr); + if (regval < 0) + return regval; + + regval &= ~mask; + regval |= val; + + return ad5758_spi_reg_write(st, addr, regval); +} + +static int cmpfunc(const void *a, const void *b) +{ + return *(int *)a - *(int *)b; +} + +static int ad5758_find_closest_match(const int *array, + unsigned int size, int val) +{ + int i; + + for (i = 0; i < size; i++) { + if (val <= array[i]) + return i; + } + + return size - 1; +} + +static int ad5758_wait_for_task_complete(struct ad5758_state *st, + unsigned int reg, + unsigned int mask) +{ + unsigned int timeout; + int ret; + + timeout = 10; + do { + ret = ad5758_spi_reg_read(st, reg); + if (ret < 0) + return ret; + + if (!(ret & mask)) + return 0; + + usleep_range(100, 1000); + } while (--timeout); + + dev_err(&st->spi->dev, + "Error reading bit 0x%x in 0x%x register\n", mask, reg); + + return -EIO; +} + +static int ad5758_calib_mem_refresh(struct ad5758_state *st) +{ + int ret; + + ret = ad5758_spi_reg_write(st, AD5758_KEY, + AD5758_KEY_CODE_CALIB_MEM_REFRESH); + if (ret < 0) { + dev_err(&st->spi->dev, + "Failed to initiate a calibration memory refresh\n"); + return ret; + } + + /* Wait to allow time for the internal calibrations to complete */ + return ad5758_wait_for_task_complete(st, AD5758_DIGITAL_DIAG_RESULTS, + AD5758_CAL_MEM_UNREFRESHED_MSK); +} + +static int ad5758_soft_reset(struct ad5758_state *st) +{ + int ret; + + ret = ad5758_spi_reg_write(st, AD5758_KEY, AD5758_KEY_CODE_RESET_1); + if (ret < 0) + return ret; + + ret = ad5758_spi_reg_write(st, AD5758_KEY, AD5758_KEY_CODE_RESET_2); + + /* Perform a software reset and wait at least 100us */ + usleep_range(100, 1000); + + return ret; +} + +static int ad5758_set_dc_dc_conv_mode(struct ad5758_state *st, + enum ad5758_dc_dc_mode mode) +{ + int ret; + + /* + * The ENABLE_PPC_BUFFERS bit must be set prior to enabling PPC current + * mode. + */ + if (mode == AD5758_DCDC_MODE_PPC_CURRENT) { + ret = ad5758_spi_write_mask(st, AD5758_ADC_CONFIG, + AD5758_ADC_CONFIG_PPC_BUF_MSK, + AD5758_ADC_CONFIG_PPC_BUF_EN(1)); + if (ret < 0) + return ret; + } + + ret = ad5758_spi_write_mask(st, AD5758_DCDC_CONFIG1, + AD5758_DCDC_CONFIG1_DCDC_MODE_MSK, + AD5758_DCDC_CONFIG1_DCDC_MODE_MODE(mode)); + if (ret < 0) + return ret; + + /* + * Poll the BUSY_3WI bit in the DCDC_CONFIG2 register until it is 0. + * This allows the 3-wire interface communication to complete. + */ + ret = ad5758_wait_for_task_complete(st, AD5758_DCDC_CONFIG2, + AD5758_DCDC_CONFIG2_BUSY_3WI_MSK); + if (ret < 0) + return ret; + + st->dc_dc_mode = mode; + + return ret; +} + +static int ad5758_set_dc_dc_ilim(struct ad5758_state *st, unsigned int ilim) +{ + int ret; + + ret = ad5758_spi_write_mask(st, AD5758_DCDC_CONFIG2, + AD5758_DCDC_CONFIG2_ILIMIT_MSK, + AD5758_DCDC_CONFIG2_ILIMIT_MODE(ilim)); + if (ret < 0) + return ret; + /* + * Poll the BUSY_3WI bit in the DCDC_CONFIG2 register until it is 0. + * This allows the 3-wire interface communication to complete. + */ + return ad5758_wait_for_task_complete(st, AD5758_DCDC_CONFIG2, + AD5758_DCDC_CONFIG2_BUSY_3WI_MSK); +} + +static int ad5758_slew_rate_set(struct ad5758_state *st, + unsigned int sr_clk_idx, + unsigned int sr_step_idx) +{ + unsigned int mode; + unsigned long int mask; + int ret; + + mask = AD5758_DAC_CONFIG_SR_EN_MSK | + AD5758_DAC_CONFIG_SR_CLOCK_MSK | + AD5758_DAC_CONFIG_SR_STEP_MSK; + mode = AD5758_DAC_CONFIG_SR_EN_MODE(1) | + AD5758_DAC_CONFIG_SR_STEP_MODE(sr_step_idx) | + AD5758_DAC_CONFIG_SR_CLOCK_MODE(sr_clk_idx); + + ret = ad5758_spi_write_mask(st, AD5758_DAC_CONFIG, mask, mode); + if (ret < 0) + return ret; + + /* Wait to allow time for the internal calibrations to complete */ + return ad5758_wait_for_task_complete(st, AD5758_DIGITAL_DIAG_RESULTS, + AD5758_CAL_MEM_UNREFRESHED_MSK); +} + +static int ad5758_slew_rate_config(struct ad5758_state *st) +{ + unsigned int sr_clk_idx, sr_step_idx; + int i, res; + s64 diff_new, diff_old; + u64 sr_step, calc_slew_time; + + sr_clk_idx = 0; + sr_step_idx = 0; + diff_old = S64_MAX; + /* + * The slew time can be determined by using the formula: + * Slew Time = (Full Scale Out / (Step Size x Update Clk Freq)) + * where Slew time is expressed in microseconds + * Given the desired slew time, the following algorithm determines the + * best match for the step size and the update clock frequency. + */ + for (i = 0; i < ARRAY_SIZE(ad5758_sr_clk); i++) { + /* + * Go through each valid update clock freq and determine a raw + * value for the step size by using the formula: + * Step Size = Full Scale Out / (Update Clk Freq * Slew Time) + */ + sr_step = AD5758_FULL_SCALE_MICRO; + do_div(sr_step, ad5758_sr_clk[i]); + do_div(sr_step, st->slew_time); + /* + * After a raw value for step size was determined, find the + * closest valid match + */ + res = ad5758_find_closest_match(ad5758_sr_step, + ARRAY_SIZE(ad5758_sr_step), + sr_step); + /* Calculate the slew time */ + calc_slew_time = AD5758_FULL_SCALE_MICRO; + do_div(calc_slew_time, ad5758_sr_step[res]); + do_div(calc_slew_time, ad5758_sr_clk[i]); + /* + * Determine with how many microseconds the calculated slew time + * is different from the desired slew time and store the diff + * for the next iteration + */ + diff_new = abs(st->slew_time - calc_slew_time); + if (diff_new < diff_old) { + diff_old = diff_new; + sr_clk_idx = i; + sr_step_idx = res; + } + } + + return ad5758_slew_rate_set(st, sr_clk_idx, sr_step_idx); +} + +static int ad5758_set_out_range(struct ad5758_state *st, int range) +{ + int ret; + + ret = ad5758_spi_write_mask(st, AD5758_DAC_CONFIG, + AD5758_DAC_CONFIG_RANGE_MSK, + AD5758_DAC_CONFIG_RANGE_MODE(range)); + if (ret < 0) + return ret; + + /* Wait to allow time for the internal calibrations to complete */ + return ad5758_wait_for_task_complete(st, AD5758_DIGITAL_DIAG_RESULTS, + AD5758_CAL_MEM_UNREFRESHED_MSK); +} + +static int ad5758_internal_buffers_en(struct ad5758_state *st, bool enable) +{ + int ret; + + ret = ad5758_spi_write_mask(st, AD5758_DAC_CONFIG, + AD5758_DAC_CONFIG_INT_EN_MSK, + AD5758_DAC_CONFIG_INT_EN_MODE(enable)); + if (ret < 0) + return ret; + + /* Wait to allow time for the internal calibrations to complete */ + return ad5758_wait_for_task_complete(st, AD5758_DIGITAL_DIAG_RESULTS, + AD5758_CAL_MEM_UNREFRESHED_MSK); +} + +static int ad5758_reset(struct ad5758_state *st) +{ + if (st->gpio_reset) { + gpiod_set_value(st->gpio_reset, 0); + usleep_range(100, 1000); + gpiod_set_value(st->gpio_reset, 1); + usleep_range(100, 1000); + + return 0; + } else { + /* Perform a software reset */ + return ad5758_soft_reset(st); + } +} + +static int ad5758_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct ad5758_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->lock); + if (readval) { + ret = ad5758_spi_reg_read(st, reg); + if (ret < 0) { + mutex_unlock(&st->lock); + return ret; + } + + *readval = ret; + ret = 0; + } else { + ret = ad5758_spi_reg_write(st, reg, writeval); + } + mutex_unlock(&st->lock); + + return ret; +} + +static int ad5758_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad5758_state *st = iio_priv(indio_dev); + int max, min, ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&st->lock); + ret = ad5758_spi_reg_read(st, AD5758_DAC_INPUT); + mutex_unlock(&st->lock); + if (ret < 0) + return ret; + + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + min = st->out_range.min; + max = st->out_range.max; + *val = (max - min) / 1000; + *val2 = 16; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: + min = st->out_range.min; + max = st->out_range.max; + *val = ((min * (1 << 16)) / (max - min)) / 1000; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad5758_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct ad5758_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&st->lock); + ret = ad5758_spi_reg_write(st, AD5758_DAC_INPUT, val); + mutex_unlock(&st->lock); + return ret; + default: + return -EINVAL; + } +} + +static ssize_t ad5758_read_powerdown(struct iio_dev *indio_dev, + uintptr_t priv, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ad5758_state *st = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", st->pwr_down); +} + +static ssize_t ad5758_write_powerdown(struct iio_dev *indio_dev, + uintptr_t priv, + struct iio_chan_spec const *chan, + const char *buf, size_t len) +{ + struct ad5758_state *st = iio_priv(indio_dev); + bool pwr_down; + unsigned int dac_config_mode, val; + unsigned long int dac_config_msk; + int ret; + + ret = kstrtobool(buf, &pwr_down); + if (ret) + return ret; + + mutex_lock(&st->lock); + if (pwr_down) + val = 0; + else + val = 1; + + dac_config_mode = AD5758_DAC_CONFIG_OUT_EN_MODE(val) | + AD5758_DAC_CONFIG_INT_EN_MODE(val); + dac_config_msk = AD5758_DAC_CONFIG_OUT_EN_MSK | + AD5758_DAC_CONFIG_INT_EN_MSK; + + ret = ad5758_spi_write_mask(st, AD5758_DAC_CONFIG, + dac_config_msk, + dac_config_mode); + if (ret < 0) + goto err_unlock; + + st->pwr_down = pwr_down; + +err_unlock: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +static const struct iio_info ad5758_info = { + .read_raw = ad5758_read_raw, + .write_raw = ad5758_write_raw, + .debugfs_reg_access = &ad5758_reg_access, +}; + +static const struct iio_chan_spec_ext_info ad5758_ext_info[] = { + { + .name = "powerdown", + .read = ad5758_read_powerdown, + .write = ad5758_write_powerdown, + .shared = IIO_SHARED_BY_TYPE, + }, + { } +}; + +#define AD5758_DAC_CHAN(_chan_type) { \ + .type = (_chan_type), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .indexed = 1, \ + .output = 1, \ + .ext_info = ad5758_ext_info, \ +} + +static const struct iio_chan_spec ad5758_voltage_ch[] = { + AD5758_DAC_CHAN(IIO_VOLTAGE) +}; + +static const struct iio_chan_spec ad5758_current_ch[] = { + AD5758_DAC_CHAN(IIO_CURRENT) +}; + +static bool ad5758_is_valid_mode(enum ad5758_dc_dc_mode mode) +{ + switch (mode) { + case AD5758_DCDC_MODE_DPC_CURRENT: + case AD5758_DCDC_MODE_DPC_VOLTAGE: + case AD5758_DCDC_MODE_PPC_CURRENT: + return true; + default: + return false; + } +} + +static int ad5758_crc_disable(struct ad5758_state *st) +{ + unsigned int mask; + + mask = (AD5758_WR_FLAG_MSK(AD5758_DIGITAL_DIAG_CONFIG) << 24) | 0x5C3A; + st->d32[0] = cpu_to_be32(mask); + + return spi_write(st->spi, &st->d32[0], 4); +} + +static int ad5758_find_out_range(struct ad5758_state *st, + const struct ad5758_range *range, + unsigned int size, + int min, int max) +{ + int i; + + for (i = 0; i < size; i++) { + if ((min == range[i].min) && (max == range[i].max)) { + st->out_range.reg = range[i].reg; + st->out_range.min = range[i].min; + st->out_range.max = range[i].max; + + return 0; + } + } + + return -EINVAL; +} + +static int ad5758_parse_dt(struct ad5758_state *st) +{ + unsigned int tmp, tmparray[2], size; + const struct ad5758_range *range; + int *index, ret; + + st->dc_dc_ilim = 0; + ret = device_property_read_u32(&st->spi->dev, + "adi,dc-dc-ilim-microamp", &tmp); + if (ret) { + dev_dbg(&st->spi->dev, + "Missing \"dc-dc-ilim-microamp\" property\n"); + } else { + index = bsearch(&tmp, ad5758_dc_dc_ilim, + ARRAY_SIZE(ad5758_dc_dc_ilim), + sizeof(int), cmpfunc); + if (!index) + dev_dbg(&st->spi->dev, "dc-dc-ilim out of range\n"); + else + st->dc_dc_ilim = index - ad5758_dc_dc_ilim; + } + + ret = device_property_read_u32(&st->spi->dev, "adi,dc-dc-mode", + &st->dc_dc_mode); + if (ret) { + dev_err(&st->spi->dev, "Missing \"dc-dc-mode\" property\n"); + return ret; + } + + if (!ad5758_is_valid_mode(st->dc_dc_mode)) + return -EINVAL; + + if (st->dc_dc_mode == AD5758_DCDC_MODE_DPC_VOLTAGE) { + ret = device_property_read_u32_array(&st->spi->dev, + "adi,range-microvolt", + tmparray, 2); + if (ret) { + dev_err(&st->spi->dev, + "Missing \"range-microvolt\" property\n"); + return ret; + } + range = ad5758_voltage_range; + size = ARRAY_SIZE(ad5758_voltage_range); + } else { + ret = device_property_read_u32_array(&st->spi->dev, + "adi,range-microamp", + tmparray, 2); + if (ret) { + dev_err(&st->spi->dev, + "Missing \"range-microamp\" property\n"); + return ret; + } + range = ad5758_current_range; + size = ARRAY_SIZE(ad5758_current_range); + } + + ret = ad5758_find_out_range(st, range, size, tmparray[0], tmparray[1]); + if (ret) { + dev_err(&st->spi->dev, "range invalid\n"); + return ret; + } + + ret = device_property_read_u32(&st->spi->dev, "adi,slew-time-us", &tmp); + if (ret) { + dev_dbg(&st->spi->dev, "Missing \"slew-time-us\" property\n"); + st->slew_time = 0; + } else { + st->slew_time = tmp; + } + + return 0; +} + +static int ad5758_init(struct ad5758_state *st) +{ + int regval, ret; + + st->gpio_reset = devm_gpiod_get_optional(&st->spi->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(st->gpio_reset)) + return PTR_ERR(st->gpio_reset); + + /* Disable CRC checks */ + ret = ad5758_crc_disable(st); + if (ret < 0) + return ret; + + /* Perform a reset */ + ret = ad5758_reset(st); + if (ret < 0) + return ret; + + /* Disable CRC checks */ + ret = ad5758_crc_disable(st); + if (ret < 0) + return ret; + + /* Perform a calibration memory refresh */ + ret = ad5758_calib_mem_refresh(st); + if (ret < 0) + return ret; + + regval = ad5758_spi_reg_read(st, AD5758_DIGITAL_DIAG_RESULTS); + if (regval < 0) + return regval; + + /* Clear all the error flags */ + ret = ad5758_spi_reg_write(st, AD5758_DIGITAL_DIAG_RESULTS, regval); + if (ret < 0) + return ret; + + /* Set the dc-to-dc current limit */ + ret = ad5758_set_dc_dc_ilim(st, st->dc_dc_ilim); + if (ret < 0) + return ret; + + /* Configure the dc-to-dc controller mode */ + ret = ad5758_set_dc_dc_conv_mode(st, st->dc_dc_mode); + if (ret < 0) + return ret; + + /* Configure the output range */ + ret = ad5758_set_out_range(st, st->out_range.reg); + if (ret < 0) + return ret; + + /* Enable Slew Rate Control, set the slew rate clock and step */ + if (st->slew_time) { + ret = ad5758_slew_rate_config(st); + if (ret < 0) + return ret; + } + + /* Power up the DAC and internal (INT) amplifiers */ + ret = ad5758_internal_buffers_en(st, 1); + if (ret < 0) + return ret; + + /* Enable VIOUT */ + return ad5758_spi_write_mask(st, AD5758_DAC_CONFIG, + AD5758_DAC_CONFIG_OUT_EN_MSK, + AD5758_DAC_CONFIG_OUT_EN_MODE(1)); +} + +static int ad5758_probe(struct spi_device *spi) +{ + struct ad5758_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + st->spi = spi; + + ret = devm_mutex_init(&spi->dev, &st->lock); + if (ret) + return ret; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &ad5758_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = 1; + + ret = ad5758_parse_dt(st); + if (ret < 0) + return ret; + + if (st->dc_dc_mode == AD5758_DCDC_MODE_DPC_VOLTAGE) + indio_dev->channels = ad5758_voltage_ch; + else + indio_dev->channels = ad5758_current_ch; + + ret = ad5758_init(st); + if (ret < 0) { + dev_err(&spi->dev, "AD5758 init failed\n"); + return ret; + } + + return devm_iio_device_register(&st->spi->dev, indio_dev); +} + +static const struct spi_device_id ad5758_id[] = { + { .name = "ad5758" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad5758_id); + +static const struct of_device_id ad5758_of_match[] = { + { .compatible = "adi,ad5758" }, + { }, +}; +MODULE_DEVICE_TABLE(of, ad5758_of_match); + +static struct spi_driver ad5758_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = ad5758_of_match, + }, + .probe = ad5758_probe, + .id_table = ad5758_id, +}; + +module_spi_driver(ad5758_driver); + +MODULE_AUTHOR("Stefan Popa "); +MODULE_DESCRIPTION("Analog Devices AD5758 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5761.c b/drivers/iio/dac/ad5761.c new file mode 100644 index 000000000..c49568b94 --- /dev/null +++ b/drivers/iio/dac/ad5761.c @@ -0,0 +1,364 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD5721, AD5721R, AD5761, AD5761R, Voltage Output Digital to Analog Converter + * + * Copyright 2016 Qtechnology A/S + * 2016 Ricardo Ribalda + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define AD5761_ADDR(addr) ((addr & 0xf) << 16) +#define AD5761_ADDR_NOOP 0x0 +#define AD5761_ADDR_DAC_WRITE 0x3 +#define AD5761_ADDR_CTRL_WRITE_REG 0x4 +#define AD5761_ADDR_SW_DATA_RESET 0x7 +#define AD5761_ADDR_DAC_READ 0xb +#define AD5761_ADDR_CTRL_READ_REG 0xc +#define AD5761_ADDR_SW_FULL_RESET 0xf + +#define AD5761_CTRL_USE_INTVREF BIT(5) +#define AD5761_CTRL_ETS BIT(6) + +/** + * struct ad5761_chip_info - chip specific information + * @int_vref: Value of the internal reference voltage in mV - 0 if external + * reference voltage is used + * @channel: channel specification +*/ + +struct ad5761_chip_info { + unsigned long int_vref; + const struct iio_chan_spec channel; +}; + +struct ad5761_range_params { + int m; + int c; +}; + +enum ad5761_supported_device_ids { + ID_AD5721, + ID_AD5721R, + ID_AD5761, + ID_AD5761R, +}; + +/** + * struct ad5761_state - driver instance specific data + * @spi: spi_device + * @use_intref: true when the internal voltage reference is used + * @vref: actual voltage reference in mVolts + * @range: output range mode used + * @lock: lock to protect the data buffer during SPI ops + * @data: cache aligned spi buffer + */ +struct ad5761_state { + struct spi_device *spi; + struct mutex lock; + + bool use_intref; + int vref; + enum ad5761_voltage_range range; + + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + */ + union { + __be32 d32; + u8 d8[4]; + } data[3] __aligned(IIO_DMA_MINALIGN); +}; + +static const struct ad5761_range_params ad5761_range_params[] = { + [AD5761_VOLTAGE_RANGE_M10V_10V] = { + .m = 80, + .c = 40, + }, + [AD5761_VOLTAGE_RANGE_0V_10V] = { + .m = 40, + .c = 0, + }, + [AD5761_VOLTAGE_RANGE_M5V_5V] = { + .m = 40, + .c = 20, + }, + [AD5761_VOLTAGE_RANGE_0V_5V] = { + .m = 20, + .c = 0, + }, + [AD5761_VOLTAGE_RANGE_M2V5_7V5] = { + .m = 40, + .c = 10, + }, + [AD5761_VOLTAGE_RANGE_M3V_3V] = { + .m = 24, + .c = 12, + }, + [AD5761_VOLTAGE_RANGE_0V_16V] = { + .m = 64, + .c = 0, + }, + [AD5761_VOLTAGE_RANGE_0V_20V] = { + .m = 80, + .c = 0, + }, +}; + +static int _ad5761_spi_write(struct ad5761_state *st, u8 addr, u16 val) +{ + st->data[0].d32 = cpu_to_be32(AD5761_ADDR(addr) | val); + + return spi_write(st->spi, &st->data[0].d8[1], 3); +} + +static int ad5761_spi_write(struct iio_dev *indio_dev, u8 addr, u16 val) +{ + struct ad5761_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->lock); + ret = _ad5761_spi_write(st, addr, val); + mutex_unlock(&st->lock); + + return ret; +} + +static int _ad5761_spi_read(struct ad5761_state *st, u8 addr, u16 *val) +{ + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = &st->data[0].d8[1], + .len = 3, + .cs_change = true, + }, { + .tx_buf = &st->data[1].d8[1], + .rx_buf = &st->data[2].d8[1], + .len = 3, + }, + }; + + st->data[0].d32 = cpu_to_be32(AD5761_ADDR(addr)); + st->data[1].d32 = cpu_to_be32(AD5761_ADDR(AD5761_ADDR_NOOP)); + + ret = spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers)); + + *val = be32_to_cpu(st->data[2].d32); + + return ret; +} + +static int ad5761_spi_read(struct iio_dev *indio_dev, u8 addr, u16 *val) +{ + struct ad5761_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->lock); + ret = _ad5761_spi_read(st, addr, val); + mutex_unlock(&st->lock); + + return ret; +} + +static int ad5761_spi_set_range(struct ad5761_state *st, + enum ad5761_voltage_range range) +{ + u16 aux; + int ret; + + aux = (range & 0x7) | AD5761_CTRL_ETS; + + if (st->use_intref) + aux |= AD5761_CTRL_USE_INTVREF; + + ret = _ad5761_spi_write(st, AD5761_ADDR_SW_FULL_RESET, 0); + if (ret) + return ret; + + ret = _ad5761_spi_write(st, AD5761_ADDR_CTRL_WRITE_REG, aux); + if (ret) + return ret; + + st->range = range; + + return 0; +} + +static int ad5761_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct ad5761_state *st; + int ret; + u16 aux; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = ad5761_spi_read(indio_dev, AD5761_ADDR_DAC_READ, &aux); + if (ret) + return ret; + *val = aux >> chan->scan_type.shift; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + st = iio_priv(indio_dev); + *val = st->vref * ad5761_range_params[st->range].m; + *val /= 10; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: + st = iio_priv(indio_dev); + *val = -(1 << chan->scan_type.realbits); + *val *= ad5761_range_params[st->range].c; + *val /= ad5761_range_params[st->range].m; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad5761_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + u16 aux; + + if (mask != IIO_CHAN_INFO_RAW) + return -EINVAL; + + if (val2 || (val << chan->scan_type.shift) > 0xffff || val < 0) + return -EINVAL; + + aux = val << chan->scan_type.shift; + + return ad5761_spi_write(indio_dev, AD5761_ADDR_DAC_WRITE, aux); +} + +static const struct iio_info ad5761_info = { + .read_raw = &ad5761_read_raw, + .write_raw = &ad5761_write_raw, +}; + +#define AD5761_CHAN(_bits) { \ + .type = IIO_VOLTAGE, \ + .output = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (_bits), \ + .storagebits = 16, \ + .shift = 16 - (_bits), \ + }, \ +} + +static const struct ad5761_chip_info ad5761_chip_infos[] = { + [ID_AD5721] = { + .int_vref = 0, + .channel = AD5761_CHAN(12), + }, + [ID_AD5721R] = { + .int_vref = 2500, + .channel = AD5761_CHAN(12), + }, + [ID_AD5761] = { + .int_vref = 0, + .channel = AD5761_CHAN(16), + }, + [ID_AD5761R] = { + .int_vref = 2500, + .channel = AD5761_CHAN(16), + }, +}; + +static int ad5761_probe(struct spi_device *spi) +{ + struct iio_dev *iio_dev; + struct ad5761_state *st; + int ret; + const struct ad5761_chip_info *chip_info = + &ad5761_chip_infos[spi_get_device_id(spi)->driver_data]; + enum ad5761_voltage_range voltage_range = AD5761_VOLTAGE_RANGE_0V_5V; + struct ad5761_platform_data *pdata = dev_get_platdata(&spi->dev); + + iio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!iio_dev) + return -ENOMEM; + + st = iio_priv(iio_dev); + + st->spi = spi; + + ret = devm_regulator_get_enable_read_voltage(&spi->dev, "vref"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(&spi->dev, ret, + "Failed to get voltage reference value\n"); + if (ret == -ENODEV) { + /* Use Internal regulator */ + if (!chip_info->int_vref) + return dev_err_probe(&spi->dev, -EIO, + "Voltage reference not found\n"); + + st->use_intref = true; + st->vref = chip_info->int_vref; + } else { + if (ret < 2000000 || ret > 3000000) + return dev_err_probe(&spi->dev, -EIO, + "Invalid external voltage ref. value %d uV\n", + ret); + + st->use_intref = false; + st->vref = ret / 1000; + } + + if (pdata) + voltage_range = pdata->voltage_range; + + mutex_init(&st->lock); + + ret = ad5761_spi_set_range(st, voltage_range); + if (ret) + return ret; + + iio_dev->info = &ad5761_info; + iio_dev->modes = INDIO_DIRECT_MODE; + iio_dev->channels = &chip_info->channel; + iio_dev->num_channels = 1; + iio_dev->name = spi_get_device_id(st->spi)->name; + + return devm_iio_device_register(&spi->dev, iio_dev); +} + +static const struct spi_device_id ad5761_id[] = { + { .name = "ad5721", .driver_data = ID_AD5721 }, + { .name = "ad5721r", .driver_data = ID_AD5721R }, + { .name = "ad5761", .driver_data = ID_AD5761 }, + { .name = "ad5761r", .driver_data = ID_AD5761R }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad5761_id); + +static struct spi_driver ad5761_driver = { + .driver = { + .name = "ad5761", + }, + .probe = ad5761_probe, + .id_table = ad5761_id, +}; +module_spi_driver(ad5761_driver); + +MODULE_AUTHOR("Ricardo Ribalda "); +MODULE_DESCRIPTION("Analog Devices AD5721, AD5721R, AD5761, AD5761R driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5764.c b/drivers/iio/dac/ad5764.c new file mode 100644 index 000000000..2be4bee7e --- /dev/null +++ b/drivers/iio/dac/ad5764.c @@ -0,0 +1,365 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog devices AD5764, AD5764R, AD5744, AD5744R quad-channel + * Digital to Analog Converters driver + * + * Copyright 2011 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define AD5764_REG_SF_NOP 0x0 +#define AD5764_REG_SF_CONFIG 0x1 +#define AD5764_REG_SF_CLEAR 0x4 +#define AD5764_REG_SF_LOAD 0x5 +#define AD5764_REG_DATA(x) ((2 << 3) | (x)) +#define AD5764_REG_COARSE_GAIN(x) ((3 << 3) | (x)) +#define AD5764_REG_FINE_GAIN(x) ((4 << 3) | (x)) +#define AD5764_REG_OFFSET(x) ((5 << 3) | (x)) + +#define AD5764_NUM_CHANNELS 4 + +/** + * struct ad5764_chip_info - chip specific information + * @int_vref: Value of the internal reference voltage in uV - 0 if external + * reference voltage is used + * @channels: channel specification +*/ +struct ad5764_chip_info { + unsigned long int_vref; + const struct iio_chan_spec *channels; +}; + +/** + * struct ad5764_state - driver instance specific data + * @spi: spi_device + * @chip_info: chip info + * @vref_reg: vref supply regulators + * @lock: lock to protect the data buffer during SPI ops + * @data: spi transfer buffers + */ + +struct ad5764_state { + struct spi_device *spi; + const struct ad5764_chip_info *chip_info; + struct regulator_bulk_data vref_reg[2]; + struct mutex lock; + + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + */ + union { + __be32 d32; + u8 d8[4]; + } data[2] __aligned(IIO_DMA_MINALIGN); +}; + +enum ad5764_type { + ID_AD5744, + ID_AD5744R, + ID_AD5764, + ID_AD5764R, +}; + +#define AD5764_CHANNEL(_chan, _bits) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (_chan), \ + .address = (_chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (_bits), \ + .storagebits = 16, \ + .shift = 16 - (_bits), \ + }, \ +} + +#define DECLARE_AD5764_CHANNELS(_name, _bits) \ +const struct iio_chan_spec _name##_channels[] = { \ + AD5764_CHANNEL(0, (_bits)), \ + AD5764_CHANNEL(1, (_bits)), \ + AD5764_CHANNEL(2, (_bits)), \ + AD5764_CHANNEL(3, (_bits)), \ +}; + +static DECLARE_AD5764_CHANNELS(ad5764, 16); +static DECLARE_AD5764_CHANNELS(ad5744, 14); + +static const struct ad5764_chip_info ad5764_chip_infos[] = { + [ID_AD5744] = { + .int_vref = 0, + .channels = ad5744_channels, + }, + [ID_AD5744R] = { + .int_vref = 5000000, + .channels = ad5744_channels, + }, + [ID_AD5764] = { + .int_vref = 0, + .channels = ad5764_channels, + }, + [ID_AD5764R] = { + .int_vref = 5000000, + .channels = ad5764_channels, + }, +}; + +static int ad5764_write(struct iio_dev *indio_dev, unsigned int reg, + unsigned int val) +{ + struct ad5764_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->lock); + st->data[0].d32 = cpu_to_be32((reg << 16) | val); + + ret = spi_write(st->spi, &st->data[0].d8[1], 3); + mutex_unlock(&st->lock); + + return ret; +} + +static int ad5764_read(struct iio_dev *indio_dev, unsigned int reg, + unsigned int *val) +{ + struct ad5764_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer t[] = { + { + .tx_buf = &st->data[0].d8[1], + .len = 3, + .cs_change = 1, + }, { + .rx_buf = &st->data[1].d8[1], + .len = 3, + }, + }; + + mutex_lock(&st->lock); + + st->data[0].d32 = cpu_to_be32((1 << 23) | (reg << 16)); + + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); + if (ret >= 0) + *val = be32_to_cpu(st->data[1].d32) & 0xffff; + + mutex_unlock(&st->lock); + + return ret; +} + +static int ad5764_chan_info_to_reg(struct iio_chan_spec const *chan, long info) +{ + switch (info) { + case IIO_CHAN_INFO_RAW: + return AD5764_REG_DATA(chan->address); + case IIO_CHAN_INFO_CALIBBIAS: + return AD5764_REG_OFFSET(chan->address); + case IIO_CHAN_INFO_CALIBSCALE: + return AD5764_REG_FINE_GAIN(chan->address); + default: + break; + } + + return 0; +} + +static int ad5764_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long info) +{ + const int max_val = (1 << chan->scan_type.realbits); + unsigned int reg; + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (val >= max_val || val < 0) + return -EINVAL; + val <<= chan->scan_type.shift; + break; + case IIO_CHAN_INFO_CALIBBIAS: + if (val >= 128 || val < -128) + return -EINVAL; + break; + case IIO_CHAN_INFO_CALIBSCALE: + if (val >= 32 || val < -32) + return -EINVAL; + break; + default: + return -EINVAL; + } + + reg = ad5764_chan_info_to_reg(chan, info); + return ad5764_write(indio_dev, reg, (u16)val); +} + +static int ad5764_get_channel_vref(struct ad5764_state *st, + unsigned int channel) +{ + if (st->chip_info->int_vref) + return st->chip_info->int_vref; + else + return regulator_get_voltage(st->vref_reg[channel / 2].consumer); +} + +static int ad5764_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long info) +{ + struct ad5764_state *st = iio_priv(indio_dev); + unsigned int reg; + int vref; + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + reg = AD5764_REG_DATA(chan->address); + ret = ad5764_read(indio_dev, reg, val); + if (ret < 0) + return ret; + *val >>= chan->scan_type.shift; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + reg = AD5764_REG_OFFSET(chan->address); + ret = ad5764_read(indio_dev, reg, val); + if (ret < 0) + return ret; + *val = sign_extend32(*val, 7); + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + reg = AD5764_REG_FINE_GAIN(chan->address); + ret = ad5764_read(indio_dev, reg, val); + if (ret < 0) + return ret; + *val = sign_extend32(*val, 5); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* vout = 4 * vref + ((dac_code / 65536) - 0.5) */ + vref = ad5764_get_channel_vref(st, chan->channel); + if (vref < 0) + return vref; + + *val = vref * 4 / 1000; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: + *val = -(1 << chan->scan_type.realbits) / 2; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static const struct iio_info ad5764_info = { + .read_raw = ad5764_read_raw, + .write_raw = ad5764_write_raw, +}; + +static int ad5764_probe(struct spi_device *spi) +{ + enum ad5764_type type = spi_get_device_id(spi)->driver_data; + struct iio_dev *indio_dev; + struct ad5764_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + st->spi = spi; + st->chip_info = &ad5764_chip_infos[type]; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &ad5764_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = AD5764_NUM_CHANNELS; + indio_dev->channels = st->chip_info->channels; + + mutex_init(&st->lock); + + if (st->chip_info->int_vref == 0) { + st->vref_reg[0].supply = "vrefAB"; + st->vref_reg[1].supply = "vrefCD"; + + ret = devm_regulator_bulk_get(&st->spi->dev, + ARRAY_SIZE(st->vref_reg), st->vref_reg); + if (ret) { + dev_err(&spi->dev, "Failed to request vref regulators: %d\n", + ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(st->vref_reg), + st->vref_reg); + if (ret) { + dev_err(&spi->dev, "Failed to enable vref regulators: %d\n", + ret); + return ret; + } + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&spi->dev, "Failed to register iio device: %d\n", ret); + goto error_disable_reg; + } + + return 0; + +error_disable_reg: + if (st->chip_info->int_vref == 0) + regulator_bulk_disable(ARRAY_SIZE(st->vref_reg), st->vref_reg); + return ret; +} + +static void ad5764_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad5764_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + if (st->chip_info->int_vref == 0) + regulator_bulk_disable(ARRAY_SIZE(st->vref_reg), st->vref_reg); +} + +static const struct spi_device_id ad5764_ids[] = { + { .name = "ad5744", .driver_data = ID_AD5744 }, + { .name = "ad5744r", .driver_data = ID_AD5744R }, + { .name = "ad5764", .driver_data = ID_AD5764 }, + { .name = "ad5764r", .driver_data = ID_AD5764R }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad5764_ids); + +static struct spi_driver ad5764_driver = { + .driver = { + .name = "ad5764", + }, + .probe = ad5764_probe, + .remove = ad5764_remove, + .id_table = ad5764_ids, +}; +module_spi_driver(ad5764_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Analog Devices AD5744/AD5744R/AD5764/AD5764R DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5766.c b/drivers/iio/dac/ad5766.c new file mode 100644 index 000000000..061b43fc0 --- /dev/null +++ b/drivers/iio/dac/ad5766.c @@ -0,0 +1,672 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices AD5766, AD5767 + * Digital to Analog Converters driver + * Copyright 2019-2020 Analog Devices Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AD5766_UPPER_WORD_SPI_MASK GENMASK(31, 16) +#define AD5766_LOWER_WORD_SPI_MASK GENMASK(15, 0) +#define AD5766_DITHER_SOURCE_MASK(ch) GENMASK(((2 * ch) + 1), (2 * ch)) +#define AD5766_DITHER_SOURCE(ch, source) BIT((ch * 2) + source) +#define AD5766_DITHER_SCALE_MASK(x) AD5766_DITHER_SOURCE_MASK(x) +#define AD5766_DITHER_SCALE(ch, scale) (scale << (ch * 2)) +#define AD5766_DITHER_ENABLE_MASK(ch) BIT(ch) +#define AD5766_DITHER_ENABLE(ch, state) ((!state) << ch) +#define AD5766_DITHER_INVERT_MASK(ch) BIT(ch) +#define AD5766_DITHER_INVERT(ch, state) (state << ch) + +#define AD5766_CMD_NOP_MUX_OUT 0x00 +#define AD5766_CMD_SDO_CNTRL 0x01 +#define AD5766_CMD_WR_IN_REG(x) (0x10 | ((x) & GENMASK(3, 0))) +#define AD5766_CMD_WR_DAC_REG(x) (0x20 | ((x) & GENMASK(3, 0))) +#define AD5766_CMD_SW_LDAC 0x30 +#define AD5766_CMD_SPAN_REG 0x40 +#define AD5766_CMD_WR_PWR_DITHER 0x51 +#define AD5766_CMD_WR_DAC_REG_ALL 0x60 +#define AD5766_CMD_SW_FULL_RESET 0x70 +#define AD5766_CMD_READBACK_REG(x) (0x80 | ((x) & GENMASK(3, 0))) +#define AD5766_CMD_DITHER_SIG_1 0x90 +#define AD5766_CMD_DITHER_SIG_2 0xA0 +#define AD5766_CMD_INV_DITHER 0xB0 +#define AD5766_CMD_DITHER_SCALE_1 0xC0 +#define AD5766_CMD_DITHER_SCALE_2 0xD0 + +#define AD5766_FULL_RESET_CODE 0x1234 + +enum ad5766_type { + ID_AD5766, + ID_AD5767, +}; + +enum ad5766_voltage_range { + AD5766_VOLTAGE_RANGE_M20V_0V, + AD5766_VOLTAGE_RANGE_M16V_to_0V, + AD5766_VOLTAGE_RANGE_M10V_to_0V, + AD5766_VOLTAGE_RANGE_M12V_to_14V, + AD5766_VOLTAGE_RANGE_M16V_to_10V, + AD5766_VOLTAGE_RANGE_M10V_to_6V, + AD5766_VOLTAGE_RANGE_M5V_to_5V, + AD5766_VOLTAGE_RANGE_M10V_to_10V, +}; + +/** + * struct ad5766_chip_info - chip specific information + * @num_channels: number of channels + * @channels: channel specification + */ +struct ad5766_chip_info { + unsigned int num_channels; + const struct iio_chan_spec *channels; +}; + +enum { + AD5766_DITHER_ENABLE, + AD5766_DITHER_INVERT, + AD5766_DITHER_SOURCE, +}; + +/* + * Dither signal can also be scaled. + * Available dither scale strings corresponding to "dither_scale" field in + * "struct ad5766_state". + */ +static const char * const ad5766_dither_scales[] = { + "1", + "0.75", + "0.5", + "0.25", +}; + +/** + * struct ad5766_state - driver instance specific data + * @spi: SPI device + * @lock: Lock used to restrict concurrent access to SPI device + * @chip_info: Chip model specific constants + * @gpio_reset: Reset GPIO, used to reset the device + * @crt_range: Current selected output range + * @dither_enable: Power enable bit for each channel dither block (for + * example, D15 = DAC 15,D8 = DAC 8, and D0 = DAC 0) + * 0 - Normal operation, 1 - Power down + * @dither_invert: Inverts the dither signal applied to the selected DAC + * outputs + * @dither_source: Selects between 2 possible sources: + * 1: N0, 2: N1 + * Two bits are used for each channel + * @dither_scale: Two bits are used for each of the 16 channels: + * 0: 1 SCALING, 1: 0.75 SCALING, 2: 0.5 SCALING, + * 3: 0.25 SCALING. + * @data: SPI transfer buffers + */ +struct ad5766_state { + struct spi_device *spi; + struct mutex lock; + const struct ad5766_chip_info *chip_info; + struct gpio_desc *gpio_reset; + enum ad5766_voltage_range crt_range; + u16 dither_enable; + u16 dither_invert; + u32 dither_source; + u32 dither_scale; + union { + u32 d32; + u16 w16[2]; + u8 b8[4]; + } data[3] __aligned(IIO_DMA_MINALIGN); +}; + +struct ad5766_span_tbl { + int min; + int max; +}; + +static const struct ad5766_span_tbl ad5766_span_tbl[] = { + [AD5766_VOLTAGE_RANGE_M20V_0V] = {-20, 0}, + [AD5766_VOLTAGE_RANGE_M16V_to_0V] = {-16, 0}, + [AD5766_VOLTAGE_RANGE_M10V_to_0V] = {-10, 0}, + [AD5766_VOLTAGE_RANGE_M12V_to_14V] = {-12, 14}, + [AD5766_VOLTAGE_RANGE_M16V_to_10V] = {-16, 10}, + [AD5766_VOLTAGE_RANGE_M10V_to_6V] = {-10, 6}, + [AD5766_VOLTAGE_RANGE_M5V_to_5V] = {-5, 5}, + [AD5766_VOLTAGE_RANGE_M10V_to_10V] = {-10, 10}, +}; + +static int __ad5766_spi_read(struct ad5766_state *st, u8 dac, int *val) +{ + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = &st->data[0].d32, + .len = 3, + .cs_change = 1, + }, { + .tx_buf = &st->data[1].d32, + .rx_buf = &st->data[2].d32, + .len = 3, + }, + }; + + st->data[0].d32 = AD5766_CMD_READBACK_REG(dac); + st->data[1].d32 = AD5766_CMD_NOP_MUX_OUT; + + ret = spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers)); + if (ret) + return ret; + + *val = st->data[2].w16[1]; + + return ret; +} + +static int __ad5766_spi_write(struct ad5766_state *st, u8 command, u16 data) +{ + st->data[0].b8[0] = command; + put_unaligned_be16(data, &st->data[0].b8[1]); + + return spi_write(st->spi, &st->data[0].b8[0], 3); +} + +static int ad5766_read(struct iio_dev *indio_dev, u8 dac, int *val) +{ + struct ad5766_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->lock); + ret = __ad5766_spi_read(st, dac, val); + mutex_unlock(&st->lock); + + return ret; +} + +static int ad5766_write(struct iio_dev *indio_dev, u8 dac, u16 data) +{ + struct ad5766_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->lock); + ret = __ad5766_spi_write(st, AD5766_CMD_WR_DAC_REG(dac), data); + mutex_unlock(&st->lock); + + return ret; +} + +static int ad5766_reset(struct ad5766_state *st) +{ + int ret; + + if (st->gpio_reset) { + gpiod_set_value_cansleep(st->gpio_reset, 1); + ndelay(100); /* t_reset >= 100ns */ + gpiod_set_value_cansleep(st->gpio_reset, 0); + } else { + ret = __ad5766_spi_write(st, AD5766_CMD_SW_FULL_RESET, + AD5766_FULL_RESET_CODE); + if (ret < 0) + return ret; + } + + /* + * Minimum time between a reset and the subsequent successful write is + * typically 25 ns + */ + ndelay(25); + + return 0; +} + +static int ad5766_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad5766_state *st = iio_priv(indio_dev); + int ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = ad5766_read(indio_dev, chan->address, val); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + *val = ad5766_span_tbl[st->crt_range].min; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = ad5766_span_tbl[st->crt_range].max - + ad5766_span_tbl[st->crt_range].min; + *val2 = st->chip_info->channels[0].scan_type.realbits; + + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static int ad5766_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long info) +{ + switch (info) { + case IIO_CHAN_INFO_RAW: + { + const int max_val = GENMASK(chan->scan_type.realbits - 1, 0); + + if (val > max_val || val < 0) + return -EINVAL; + val <<= chan->scan_type.shift; + return ad5766_write(indio_dev, chan->address, val); + } + default: + return -EINVAL; + } +} + +static const struct iio_info ad5766_info = { + .read_raw = ad5766_read_raw, + .write_raw = ad5766_write_raw, +}; + +static int ad5766_get_dither_source(struct iio_dev *dev, + const struct iio_chan_spec *chan) +{ + struct ad5766_state *st = iio_priv(dev); + u32 source; + + source = st->dither_source & AD5766_DITHER_SOURCE_MASK(chan->channel); + source = source >> (chan->channel * 2); + source -= 1; + + return source; +} + +static int ad5766_set_dither_source(struct iio_dev *dev, + const struct iio_chan_spec *chan, + unsigned int source) +{ + struct ad5766_state *st = iio_priv(dev); + uint16_t val; + int ret; + + st->dither_source &= ~AD5766_DITHER_SOURCE_MASK(chan->channel); + st->dither_source |= AD5766_DITHER_SOURCE(chan->channel, source); + + val = FIELD_GET(AD5766_LOWER_WORD_SPI_MASK, st->dither_source); + ret = ad5766_write(dev, AD5766_CMD_DITHER_SIG_1, val); + if (ret) + return ret; + + val = FIELD_GET(AD5766_UPPER_WORD_SPI_MASK, st->dither_source); + + return ad5766_write(dev, AD5766_CMD_DITHER_SIG_2, val); +} + +static int ad5766_get_dither_scale(struct iio_dev *dev, + const struct iio_chan_spec *chan) +{ + struct ad5766_state *st = iio_priv(dev); + u32 scale; + + scale = st->dither_scale & AD5766_DITHER_SCALE_MASK(chan->channel); + + return (scale >> (chan->channel * 2)); +} + +static int ad5766_set_dither_scale(struct iio_dev *dev, + const struct iio_chan_spec *chan, + unsigned int scale) +{ + int ret; + struct ad5766_state *st = iio_priv(dev); + uint16_t val; + + st->dither_scale &= ~AD5766_DITHER_SCALE_MASK(chan->channel); + st->dither_scale |= AD5766_DITHER_SCALE(chan->channel, scale); + + val = FIELD_GET(AD5766_LOWER_WORD_SPI_MASK, st->dither_scale); + ret = ad5766_write(dev, AD5766_CMD_DITHER_SCALE_1, val); + if (ret) + return ret; + val = FIELD_GET(AD5766_UPPER_WORD_SPI_MASK, st->dither_scale); + + return ad5766_write(dev, AD5766_CMD_DITHER_SCALE_2, val); +} + +static const struct iio_enum ad5766_dither_scale_enum = { + .items = ad5766_dither_scales, + .num_items = ARRAY_SIZE(ad5766_dither_scales), + .set = ad5766_set_dither_scale, + .get = ad5766_get_dither_scale, +}; + +static ssize_t ad5766_read_ext(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ad5766_state *st = iio_priv(indio_dev); + + switch (private) { + case AD5766_DITHER_ENABLE: + return sprintf(buf, "%u\n", + !(st->dither_enable & BIT(chan->channel))); + break; + case AD5766_DITHER_INVERT: + return sprintf(buf, "%u\n", + !!(st->dither_invert & BIT(chan->channel))); + break; + case AD5766_DITHER_SOURCE: + return sprintf(buf, "%d\n", + ad5766_get_dither_source(indio_dev, chan)); + default: + return -EINVAL; + } +} + +static ssize_t ad5766_write_ext(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ad5766_state *st = iio_priv(indio_dev); + bool readin; + int ret; + + ret = kstrtobool(buf, &readin); + if (ret) + return ret; + + switch (private) { + case AD5766_DITHER_ENABLE: + st->dither_enable &= ~AD5766_DITHER_ENABLE_MASK(chan->channel); + st->dither_enable |= AD5766_DITHER_ENABLE(chan->channel, + readin); + ret = ad5766_write(indio_dev, AD5766_CMD_WR_PWR_DITHER, + st->dither_enable); + break; + case AD5766_DITHER_INVERT: + st->dither_invert &= ~AD5766_DITHER_INVERT_MASK(chan->channel); + st->dither_invert |= AD5766_DITHER_INVERT(chan->channel, + readin); + ret = ad5766_write(indio_dev, AD5766_CMD_INV_DITHER, + st->dither_invert); + break; + case AD5766_DITHER_SOURCE: + ret = ad5766_set_dither_source(indio_dev, chan, readin); + break; + default: + return -EINVAL; + } + + return ret ? ret : len; +} + +#define _AD5766_CHAN_EXT_INFO(_name, _what, _shared) { \ + .name = _name, \ + .read = ad5766_read_ext, \ + .write = ad5766_write_ext, \ + .private = _what, \ + .shared = _shared, \ +} + +static const struct iio_chan_spec_ext_info ad5766_ext_info[] = { + + _AD5766_CHAN_EXT_INFO("dither_enable", AD5766_DITHER_ENABLE, + IIO_SEPARATE), + _AD5766_CHAN_EXT_INFO("dither_invert", AD5766_DITHER_INVERT, + IIO_SEPARATE), + _AD5766_CHAN_EXT_INFO("dither_source", AD5766_DITHER_SOURCE, + IIO_SEPARATE), + IIO_ENUM("dither_scale", IIO_SEPARATE, &ad5766_dither_scale_enum), + IIO_ENUM_AVAILABLE("dither_scale", IIO_SEPARATE, + &ad5766_dither_scale_enum), + { } +}; + +#define AD576x_CHANNEL(_chan, _bits) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (_chan), \ + .address = (_chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = (_chan), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (_bits), \ + .storagebits = 16, \ + .shift = 16 - (_bits), \ + }, \ + .ext_info = ad5766_ext_info, \ +} + +#define DECLARE_AD576x_CHANNELS(_name, _bits) \ +const struct iio_chan_spec _name[] = { \ + AD576x_CHANNEL(0, (_bits)), \ + AD576x_CHANNEL(1, (_bits)), \ + AD576x_CHANNEL(2, (_bits)), \ + AD576x_CHANNEL(3, (_bits)), \ + AD576x_CHANNEL(4, (_bits)), \ + AD576x_CHANNEL(5, (_bits)), \ + AD576x_CHANNEL(6, (_bits)), \ + AD576x_CHANNEL(7, (_bits)), \ + AD576x_CHANNEL(8, (_bits)), \ + AD576x_CHANNEL(9, (_bits)), \ + AD576x_CHANNEL(10, (_bits)), \ + AD576x_CHANNEL(11, (_bits)), \ + AD576x_CHANNEL(12, (_bits)), \ + AD576x_CHANNEL(13, (_bits)), \ + AD576x_CHANNEL(14, (_bits)), \ + AD576x_CHANNEL(15, (_bits)), \ +} + +static DECLARE_AD576x_CHANNELS(ad5766_channels, 16); +static DECLARE_AD576x_CHANNELS(ad5767_channels, 12); + +static const struct ad5766_chip_info ad5766_chip_infos[] = { + [ID_AD5766] = { + .num_channels = ARRAY_SIZE(ad5766_channels), + .channels = ad5766_channels, + }, + [ID_AD5767] = { + .num_channels = ARRAY_SIZE(ad5767_channels), + .channels = ad5767_channels, + }, +}; + +static int ad5766_get_output_range(struct ad5766_state *st) +{ + int i, ret, min, max, tmp[2]; + + ret = device_property_read_u32_array(&st->spi->dev, + "output-range-microvolts", + tmp, 2); + if (ret) + return ret; + + min = tmp[0] / 1000000; + max = tmp[1] / 1000000; + for (i = 0; i < ARRAY_SIZE(ad5766_span_tbl); i++) { + if (ad5766_span_tbl[i].min != min || + ad5766_span_tbl[i].max != max) + continue; + + st->crt_range = i; + + return 0; + } + + return -EINVAL; +} + +static int ad5766_default_setup(struct ad5766_state *st) +{ + uint16_t val; + int ret, i; + + /* Always issue a reset before writing to the span register. */ + ret = ad5766_reset(st); + if (ret) + return ret; + + ret = ad5766_get_output_range(st); + if (ret) + return ret; + + /* Dither power down */ + st->dither_enable = GENMASK(15, 0); + ret = __ad5766_spi_write(st, AD5766_CMD_WR_PWR_DITHER, + st->dither_enable); + if (ret) + return ret; + + st->dither_source = 0; + for (i = 0; i < ARRAY_SIZE(ad5766_channels); i++) + st->dither_source |= AD5766_DITHER_SOURCE(i, 0); + val = FIELD_GET(AD5766_LOWER_WORD_SPI_MASK, st->dither_source); + ret = __ad5766_spi_write(st, AD5766_CMD_DITHER_SIG_1, val); + if (ret) + return ret; + + val = FIELD_GET(AD5766_UPPER_WORD_SPI_MASK, st->dither_source); + ret = __ad5766_spi_write(st, AD5766_CMD_DITHER_SIG_2, val); + if (ret) + return ret; + + st->dither_scale = 0; + val = FIELD_GET(AD5766_LOWER_WORD_SPI_MASK, st->dither_scale); + ret = __ad5766_spi_write(st, AD5766_CMD_DITHER_SCALE_1, val); + if (ret) + return ret; + + val = FIELD_GET(AD5766_UPPER_WORD_SPI_MASK, st->dither_scale); + ret = __ad5766_spi_write(st, AD5766_CMD_DITHER_SCALE_2, val); + if (ret) + return ret; + + st->dither_invert = 0; + ret = __ad5766_spi_write(st, AD5766_CMD_INV_DITHER, st->dither_invert); + if (ret) + return ret; + + return __ad5766_spi_write(st, AD5766_CMD_SPAN_REG, st->crt_range); +} + +static irqreturn_t ad5766_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct iio_buffer *buffer = indio_dev->buffer; + struct ad5766_state *st = iio_priv(indio_dev); + int ret, ch, i; + u16 data[ARRAY_SIZE(ad5766_channels)]; + + ret = iio_pop_from_buffer(buffer, data); + if (ret) + goto done; + + i = 0; + mutex_lock(&st->lock); + for_each_set_bit(ch, indio_dev->active_scan_mask, + st->chip_info->num_channels - 1) + __ad5766_spi_write(st, AD5766_CMD_WR_IN_REG(ch), data[i++]); + + __ad5766_spi_write(st, AD5766_CMD_SW_LDAC, + *indio_dev->active_scan_mask); + mutex_unlock(&st->lock); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ad5766_probe(struct spi_device *spi) +{ + enum ad5766_type type; + struct iio_dev *indio_dev; + struct ad5766_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + mutex_init(&st->lock); + + st->spi = spi; + type = spi_get_device_id(spi)->driver_data; + st->chip_info = &ad5766_chip_infos[type]; + + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + indio_dev->info = &ad5766_info; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + + st->gpio_reset = devm_gpiod_get_optional(&st->spi->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(st->gpio_reset)) + return PTR_ERR(st->gpio_reset); + + ret = ad5766_default_setup(st); + if (ret) + return ret; + + /* Configure trigger buffer */ + ret = devm_iio_triggered_buffer_setup_ext(&spi->dev, indio_dev, NULL, + ad5766_trigger_handler, + IIO_BUFFER_DIRECTION_OUT, + NULL, + NULL); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct of_device_id ad5766_dt_match[] = { + { .compatible = "adi,ad5766" }, + { .compatible = "adi,ad5767" }, + { } +}; +MODULE_DEVICE_TABLE(of, ad5766_dt_match); + +static const struct spi_device_id ad5766_spi_ids[] = { + { .name = "ad5766", .driver_data = ID_AD5766 }, + { .name = "ad5767", .driver_data = ID_AD5767 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad5766_spi_ids); + +static struct spi_driver ad5766_driver = { + .driver = { + .name = "ad5766", + .of_match_table = ad5766_dt_match, + }, + .probe = ad5766_probe, + .id_table = ad5766_spi_ids, +}; +module_spi_driver(ad5766_driver); + +MODULE_AUTHOR("Denis-Gabriel Gheorghescu "); +MODULE_DESCRIPTION("Analog Devices AD5766/AD5767 DACs"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5770r.c b/drivers/iio/dac/ad5770r.c new file mode 100644 index 000000000..bdc48e570 --- /dev/null +++ b/drivers/iio/dac/ad5770r.c @@ -0,0 +1,663 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * AD5770R Digital to analog converters driver + * + * Copyright 2018 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ADI_SPI_IF_CONFIG_A 0x00 +#define ADI_SPI_IF_CONFIG_B 0x01 +#define ADI_SPI_IF_DEVICE_CONFIG 0x02 +#define ADI_SPI_IF_CHIP_TYPE 0x03 +#define ADI_SPI_IF_PRODUCT_ID_L 0x04 +#define ADI_SPI_IF_PRODUCT_ID_H 0x05 +#define ADI_SPI_IF_CHIP_GRADE 0x06 +#define ADI_SPI_IF_SCRACTH_PAD 0x0A +#define ADI_SPI_IF_SPI_REVISION 0x0B +#define ADI_SPI_IF_SPI_VENDOR_L 0x0C +#define ADI_SPI_IF_SPI_VENDOR_H 0x0D +#define ADI_SPI_IF_SPI_STREAM_MODE 0x0E +#define ADI_SPI_IF_CONFIG_C 0x10 +#define ADI_SPI_IF_STATUS_A 0x11 + +/* ADI_SPI_IF_CONFIG_A */ +#define ADI_SPI_IF_SW_RESET_MSK (BIT(0) | BIT(7)) +#define ADI_SPI_IF_SW_RESET_SEL(x) ((x) & ADI_SPI_IF_SW_RESET_MSK) +#define ADI_SPI_IF_ADDR_ASC_MSK (BIT(2) | BIT(5)) +#define ADI_SPI_IF_ADDR_ASC_SEL(x) (((x) << 2) & ADI_SPI_IF_ADDR_ASC_MSK) + +/* ADI_SPI_IF_CONFIG_B */ +#define ADI_SPI_IF_SINGLE_INS_MSK BIT(7) +#define ADI_SPI_IF_SINGLE_INS_SEL(x) FIELD_PREP(ADI_SPI_IF_SINGLE_INS_MSK, x) +#define ADI_SPI_IF_SHORT_INS_MSK BIT(7) +#define ADI_SPI_IF_SHORT_INS_SEL(x) FIELD_PREP(ADI_SPI_IF_SINGLE_INS_MSK, x) + +/* ADI_SPI_IF_CONFIG_C */ +#define ADI_SPI_IF_STRICT_REG_MSK BIT(5) +#define ADI_SPI_IF_STRICT_REG_GET(x) FIELD_GET(ADI_SPI_IF_STRICT_REG_MSK, x) + +/* AD5770R configuration registers */ +#define AD5770R_CHANNEL_CONFIG 0x14 +#define AD5770R_OUTPUT_RANGE(ch) (0x15 + (ch)) +#define AD5770R_FILTER_RESISTOR(ch) (0x1D + (ch)) +#define AD5770R_REFERENCE 0x1B +#define AD5770R_DAC_LSB(ch) (0x26 + 2 * (ch)) +#define AD5770R_DAC_MSB(ch) (0x27 + 2 * (ch)) +#define AD5770R_CH_SELECT 0x34 +#define AD5770R_CH_ENABLE 0x44 + +/* AD5770R_CHANNEL_CONFIG */ +#define AD5770R_CFG_CH0_SINK_EN(x) (((x) & 0x1) << 7) +#define AD5770R_CFG_SHUTDOWN_B(x, ch) (((x) & 0x1) << (ch)) + +/* AD5770R_OUTPUT_RANGE */ +#define AD5770R_RANGE_OUTPUT_SCALING(x) (((x) & GENMASK(5, 0)) << 2) +#define AD5770R_RANGE_MODE(x) ((x) & GENMASK(1, 0)) + +/* AD5770R_REFERENCE */ +#define AD5770R_REF_RESISTOR_SEL(x) (((x) & 0x1) << 2) +#define AD5770R_REF_SEL(x) ((x) & GENMASK(1, 0)) + +/* AD5770R_CH_ENABLE */ +#define AD5770R_CH_SET(x, ch) (((x) & 0x1) << (ch)) + +#define AD5770R_MAX_CHANNELS 6 +#define AD5770R_MAX_CH_MODES 14 +#define AD5770R_LOW_VREF_mV 1250 +#define AD5770R_HIGH_VREF_mV 2500 + +enum ad5770r_ch0_modes { + AD5770R_CH0_0_300 = 0, + AD5770R_CH0_NEG_60_0, + AD5770R_CH0_NEG_60_300 +}; + +enum ad5770r_ch1_modes { + AD5770R_CH1_0_140_LOW_HEAD = 1, + AD5770R_CH1_0_140_LOW_NOISE, + AD5770R_CH1_0_250 +}; + +enum ad5770r_ch2_5_modes { + AD5770R_CH_LOW_RANGE = 0, + AD5770R_CH_HIGH_RANGE +}; + +enum ad5770r_ref_v { + AD5770R_EXT_2_5_V = 0, + AD5770R_INT_1_25_V_OUT_ON, + AD5770R_EXT_1_25_V, + AD5770R_INT_1_25_V_OUT_OFF +}; + +enum ad5770r_output_filter_resistor { + AD5770R_FILTER_60_OHM = 0x0, + AD5770R_FILTER_5_6_KOHM = 0x5, + AD5770R_FILTER_11_2_KOHM, + AD5770R_FILTER_22_2_KOHM, + AD5770R_FILTER_44_4_KOHM, + AD5770R_FILTER_104_KOHM, +}; + +struct ad5770r_out_range { + u8 out_scale; + u8 out_range_mode; +}; + +/** + * struct ad5770r_state - driver instance specific data + * @spi: spi_device + * @regmap: regmap + * @gpio_reset: gpio descriptor + * @output_mode: array contains channels output ranges + * @vref: reference value + * @ch_pwr_down: powerdown flags + * @internal_ref: internal reference flag + * @external_res: external 2.5k resistor flag + * @transf_buf: cache aligned buffer for spi read/write + */ +struct ad5770r_state { + struct spi_device *spi; + struct regmap *regmap; + struct gpio_desc *gpio_reset; + struct ad5770r_out_range output_mode[AD5770R_MAX_CHANNELS]; + int vref; + bool ch_pwr_down[AD5770R_MAX_CHANNELS]; + bool internal_ref; + bool external_res; + u8 transf_buf[2] __aligned(IIO_DMA_MINALIGN); +}; + +static const struct regmap_config ad5770r_spi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .read_flag_mask = BIT(7), +}; + +struct ad5770r_output_modes { + unsigned int ch; + u8 mode; + int min; + int max; +}; + +static const struct ad5770r_output_modes ad5770r_rng_tbl[] = { + { 0, AD5770R_CH0_0_300, 0, 300 }, + { 0, AD5770R_CH0_NEG_60_0, -60, 0 }, + { 0, AD5770R_CH0_NEG_60_300, -60, 300 }, + { 1, AD5770R_CH1_0_140_LOW_HEAD, 0, 140 }, + { 1, AD5770R_CH1_0_140_LOW_NOISE, 0, 140 }, + { 1, AD5770R_CH1_0_250, 0, 250 }, + { 2, AD5770R_CH_LOW_RANGE, 0, 55 }, + { 2, AD5770R_CH_HIGH_RANGE, 0, 150 }, + { 3, AD5770R_CH_LOW_RANGE, 0, 45 }, + { 3, AD5770R_CH_HIGH_RANGE, 0, 100 }, + { 4, AD5770R_CH_LOW_RANGE, 0, 45 }, + { 4, AD5770R_CH_HIGH_RANGE, 0, 100 }, + { 5, AD5770R_CH_LOW_RANGE, 0, 45 }, + { 5, AD5770R_CH_HIGH_RANGE, 0, 100 }, +}; + +static const unsigned int ad5770r_filter_freqs[] = { + 153, 357, 715, 1400, 2800, 262000, +}; + +static const unsigned int ad5770r_filter_reg_vals[] = { + AD5770R_FILTER_104_KOHM, + AD5770R_FILTER_44_4_KOHM, + AD5770R_FILTER_22_2_KOHM, + AD5770R_FILTER_11_2_KOHM, + AD5770R_FILTER_5_6_KOHM, + AD5770R_FILTER_60_OHM +}; + +static int ad5770r_set_output_mode(struct ad5770r_state *st, + const struct ad5770r_out_range *out_mode, + int channel) +{ + unsigned int regval; + + regval = AD5770R_RANGE_OUTPUT_SCALING(out_mode->out_scale) | + AD5770R_RANGE_MODE(out_mode->out_range_mode); + + return regmap_write(st->regmap, + AD5770R_OUTPUT_RANGE(channel), regval); +} + +static int ad5770r_set_reference(struct ad5770r_state *st) +{ + unsigned int regval; + + regval = AD5770R_REF_RESISTOR_SEL(st->external_res); + + if (st->internal_ref) { + regval |= AD5770R_REF_SEL(AD5770R_INT_1_25_V_OUT_OFF); + } else { + switch (st->vref) { + case AD5770R_LOW_VREF_mV: + regval |= AD5770R_REF_SEL(AD5770R_EXT_1_25_V); + break; + case AD5770R_HIGH_VREF_mV: + regval |= AD5770R_REF_SEL(AD5770R_EXT_2_5_V); + break; + default: + regval = AD5770R_REF_SEL(AD5770R_INT_1_25_V_OUT_OFF); + break; + } + } + + return regmap_write(st->regmap, AD5770R_REFERENCE, regval); +} + +static int ad5770r_soft_reset(struct ad5770r_state *st) +{ + return regmap_write(st->regmap, ADI_SPI_IF_CONFIG_A, + ADI_SPI_IF_SW_RESET_SEL(1)); +} + +static int ad5770r_reset(struct ad5770r_state *st) +{ + /* Perform software reset if no GPIO provided */ + if (!st->gpio_reset) + return ad5770r_soft_reset(st); + + gpiod_set_value_cansleep(st->gpio_reset, 0); + usleep_range(10, 20); + gpiod_set_value_cansleep(st->gpio_reset, 1); + + /* data must not be written during reset timeframe */ + usleep_range(100, 200); + + return 0; +} + +static int ad5770r_get_range(struct ad5770r_state *st, + int ch, int *min, int *max) +{ + int i; + u8 tbl_ch, tbl_mode, out_range; + + out_range = st->output_mode[ch].out_range_mode; + + for (i = 0; i < AD5770R_MAX_CH_MODES; i++) { + tbl_ch = ad5770r_rng_tbl[i].ch; + tbl_mode = ad5770r_rng_tbl[i].mode; + if (tbl_ch == ch && tbl_mode == out_range) { + *min = ad5770r_rng_tbl[i].min; + *max = ad5770r_rng_tbl[i].max; + return 0; + } + } + + return -EINVAL; +} + +static int ad5770r_get_filter_freq(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *freq) +{ + struct ad5770r_state *st = iio_priv(indio_dev); + int ret; + unsigned int regval, i; + + ret = regmap_read(st->regmap, + AD5770R_FILTER_RESISTOR(chan->channel), ®val); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(ad5770r_filter_reg_vals); i++) + if (regval == ad5770r_filter_reg_vals[i]) + break; + if (i == ARRAY_SIZE(ad5770r_filter_reg_vals)) + return -EINVAL; + + *freq = ad5770r_filter_freqs[i]; + + return IIO_VAL_INT; +} + +static int ad5770r_set_filter_freq(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int freq) +{ + struct ad5770r_state *st = iio_priv(indio_dev); + unsigned int regval, i; + + for (i = 0; i < ARRAY_SIZE(ad5770r_filter_freqs); i++) + if (ad5770r_filter_freqs[i] >= freq) + break; + if (i == ARRAY_SIZE(ad5770r_filter_freqs)) + return -EINVAL; + + regval = ad5770r_filter_reg_vals[i]; + + return regmap_write(st->regmap, AD5770R_FILTER_RESISTOR(chan->channel), + regval); +} + +static int ad5770r_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad5770r_state *st = iio_priv(indio_dev); + int max, min, ret; + u16 buf16; + + switch (info) { + case IIO_CHAN_INFO_RAW: + ret = regmap_bulk_read(st->regmap, + chan->address, + st->transf_buf, 2); + if (ret) + return ret; + + buf16 = get_unaligned_le16(st->transf_buf); + *val = buf16 >> 2; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = ad5770r_get_range(st, chan->channel, &min, &max); + if (ret < 0) + return ret; + *val = max - min; + /* There is no sign bit. (negative current is mapped from 0) + * (sourced/sinked) current = raw * scale + offset + * where offset in case of CH0 can be negative. + */ + *val2 = 14; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return ad5770r_get_filter_freq(indio_dev, chan, val); + case IIO_CHAN_INFO_OFFSET: + ret = ad5770r_get_range(st, chan->channel, &min, &max); + if (ret < 0) + return ret; + *val = min; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad5770r_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct ad5770r_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_RAW: + st->transf_buf[0] = ((u16)val >> 6); + st->transf_buf[1] = (val & GENMASK(5, 0)) << 2; + return regmap_bulk_write(st->regmap, chan->address, + st->transf_buf, 2); + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return ad5770r_set_filter_freq(indio_dev, chan, val); + default: + return -EINVAL; + } +} + +static int ad5770r_read_freq_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + *type = IIO_VAL_INT; + *vals = ad5770r_filter_freqs; + *length = ARRAY_SIZE(ad5770r_filter_freqs); + return IIO_AVAIL_LIST; + } + + return -EINVAL; +} + +static int ad5770r_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct ad5770r_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + else + return regmap_write(st->regmap, reg, writeval); +} + +static const struct iio_info ad5770r_info = { + .read_raw = ad5770r_read_raw, + .write_raw = ad5770r_write_raw, + .read_avail = ad5770r_read_freq_avail, + .debugfs_reg_access = &ad5770r_reg_access, +}; + +static int ad5770r_store_output_range(struct ad5770r_state *st, + int min, int max, int index) +{ + int i; + + for (i = 0; i < AD5770R_MAX_CH_MODES; i++) { + if (ad5770r_rng_tbl[i].ch != index) + continue; + if (ad5770r_rng_tbl[i].min != min || + ad5770r_rng_tbl[i].max != max) + continue; + st->output_mode[index].out_range_mode = ad5770r_rng_tbl[i].mode; + + return 0; + } + + return -EINVAL; +} + +static ssize_t ad5770r_read_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ad5770r_state *st = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", st->ch_pwr_down[chan->channel]); +} + +static ssize_t ad5770r_write_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ad5770r_state *st = iio_priv(indio_dev); + unsigned int regval; + unsigned int mask; + bool readin; + int ret; + + ret = kstrtobool(buf, &readin); + if (ret) + return ret; + + readin = !readin; + + regval = AD5770R_CFG_SHUTDOWN_B(readin, chan->channel); + if (chan->channel == 0 && + st->output_mode[0].out_range_mode > AD5770R_CH0_0_300) { + regval |= AD5770R_CFG_CH0_SINK_EN(readin); + mask = BIT(chan->channel) + BIT(7); + } else { + mask = BIT(chan->channel); + } + ret = regmap_update_bits(st->regmap, AD5770R_CHANNEL_CONFIG, mask, + regval); + if (ret) + return ret; + + regval = AD5770R_CH_SET(readin, chan->channel); + ret = regmap_update_bits(st->regmap, AD5770R_CH_ENABLE, + BIT(chan->channel), regval); + if (ret) + return ret; + + st->ch_pwr_down[chan->channel] = !readin; + + return len; +} + +static const struct iio_chan_spec_ext_info ad5770r_ext_info[] = { + { + .name = "powerdown", + .read = ad5770r_read_dac_powerdown, + .write = ad5770r_write_dac_powerdown, + .shared = IIO_SEPARATE, + }, + { } +}; + +#define AD5770R_IDAC_CHANNEL(index, reg) { \ + .type = IIO_CURRENT, \ + .address = reg, \ + .indexed = 1, \ + .channel = index, \ + .output = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .ext_info = ad5770r_ext_info, \ +} + +static const struct iio_chan_spec ad5770r_channels[] = { + AD5770R_IDAC_CHANNEL(0, AD5770R_DAC_MSB(0)), + AD5770R_IDAC_CHANNEL(1, AD5770R_DAC_MSB(1)), + AD5770R_IDAC_CHANNEL(2, AD5770R_DAC_MSB(2)), + AD5770R_IDAC_CHANNEL(3, AD5770R_DAC_MSB(3)), + AD5770R_IDAC_CHANNEL(4, AD5770R_DAC_MSB(4)), + AD5770R_IDAC_CHANNEL(5, AD5770R_DAC_MSB(5)), +}; + +static int ad5770r_channel_config(struct ad5770r_state *st) +{ + int ret, tmp[2], min, max; + unsigned int num; + + num = device_get_child_node_count(&st->spi->dev); + if (num != AD5770R_MAX_CHANNELS) + return -EINVAL; + + device_for_each_child_node_scoped(&st->spi->dev, child) { + ret = fwnode_property_read_u32(child, "reg", &num); + if (ret) + return ret; + if (num >= AD5770R_MAX_CHANNELS) + return -EINVAL; + + ret = fwnode_property_read_u32_array(child, + "adi,range-microamp", + tmp, 2); + if (ret) + return ret; + + min = tmp[0] / 1000; + max = tmp[1] / 1000; + ret = ad5770r_store_output_range(st, min, max, num); + if (ret) + return ret; + } + + return 0; +} + +static int ad5770r_init(struct ad5770r_state *st) +{ + int ret, i; + + st->gpio_reset = devm_gpiod_get_optional(&st->spi->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(st->gpio_reset)) + return PTR_ERR(st->gpio_reset); + + /* Perform a reset */ + ret = ad5770r_reset(st); + if (ret) + return ret; + + /* Set output range */ + ret = ad5770r_channel_config(st); + if (ret) + return ret; + + for (i = 0; i < AD5770R_MAX_CHANNELS; i++) { + ret = ad5770r_set_output_mode(st, &st->output_mode[i], i); + if (ret) + return ret; + } + + st->external_res = fwnode_property_read_bool(st->spi->dev.fwnode, + "adi,external-resistor"); + + ret = ad5770r_set_reference(st); + if (ret) + return ret; + + /* Set outputs off */ + ret = regmap_write(st->regmap, AD5770R_CHANNEL_CONFIG, 0x00); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD5770R_CH_ENABLE, 0x00); + if (ret) + return ret; + + for (i = 0; i < AD5770R_MAX_CHANNELS; i++) + st->ch_pwr_down[i] = true; + + return ret; +} + +static int ad5770r_probe(struct spi_device *spi) +{ + struct ad5770r_state *st; + struct iio_dev *indio_dev; + struct regmap *regmap; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + st->spi = spi; + + regmap = devm_regmap_init_spi(spi, &ad5770r_spi_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "Error initializing spi regmap: %ld\n", + PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + st->regmap = regmap; + + ret = devm_regulator_get_enable_read_voltage(&spi->dev, "vref"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(&spi->dev, ret, "Failed to get vref voltage\n"); + + st->internal_ref = ret == -ENODEV; + st->vref = st->internal_ref ? AD5770R_LOW_VREF_mV : ret / 1000; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &ad5770r_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad5770r_channels; + indio_dev->num_channels = ARRAY_SIZE(ad5770r_channels); + + ret = ad5770r_init(st); + if (ret < 0) { + dev_err(&spi->dev, "AD5770R init failed\n"); + return ret; + } + + return devm_iio_device_register(&st->spi->dev, indio_dev); +} + +static const struct of_device_id ad5770r_of_id[] = { + { .compatible = "adi,ad5770r", }, + { } +}; +MODULE_DEVICE_TABLE(of, ad5770r_of_id); + +static const struct spi_device_id ad5770r_id[] = { + { .name = "ad5770r" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad5770r_id); + +static struct spi_driver ad5770r_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = ad5770r_of_id, + }, + .probe = ad5770r_probe, + .id_table = ad5770r_id, +}; + +module_spi_driver(ad5770r_driver); + +MODULE_AUTHOR("Mircea Caprioru "); +MODULE_DESCRIPTION("Analog Devices AD5770R IDAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5791.c b/drivers/iio/dac/ad5791.c new file mode 100644 index 000000000..a5eda135c --- /dev/null +++ b/drivers/iio/dac/ad5791.c @@ -0,0 +1,611 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD5760, AD5780, AD5781, AD5790, AD5791 Voltage Output Digital to Analog + * Converter + * + * Copyright 2011 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define AD5791_DAC_MASK GENMASK(19, 0) + +#define AD5791_CMD_READ BIT(23) +#define AD5791_CMD_WRITE 0 +#define AD5791_ADDR(addr) ((addr) << 20) + +/* Registers */ +#define AD5791_ADDR_NOOP 0 +#define AD5791_ADDR_DAC0 1 +#define AD5791_ADDR_CTRL 2 +#define AD5791_ADDR_CLRCODE 3 +#define AD5791_ADDR_SW_CTRL 4 + +/* Control Register */ +#define AD5791_CTRL_RBUF BIT(1) +#define AD5791_CTRL_OPGND BIT(2) +#define AD5791_CTRL_DACTRI BIT(3) +#define AD5791_CTRL_BIN2SC BIT(4) +#define AD5791_CTRL_SDODIS BIT(5) +#define AD5761_CTRL_LINCOMP(x) ((x) << 6) + +#define AD5791_LINCOMP_0_10 0 +#define AD5791_LINCOMP_10_12 1 +#define AD5791_LINCOMP_12_16 2 +#define AD5791_LINCOMP_16_19 3 +#define AD5791_LINCOMP_19_20 12 + +#define AD5780_LINCOMP_0_10 0 +#define AD5780_LINCOMP_10_20 12 + +/* Software Control Register */ +#define AD5791_SWCTRL_LDAC BIT(0) +#define AD5791_SWCTRL_CLR BIT(1) +#define AD5791_SWCTRL_RESET BIT(2) + +#define AD5791_DAC_PWRDN_6K 0 +#define AD5791_DAC_PWRDN_3STATE 1 + +/** + * struct ad5791_chip_info - chip specific information + * @name: name of the dac chip + * @channel: channel specification + * @channel_offload: channel specification for offload + * @get_lin_comp: function pointer to the device specific function + */ +struct ad5791_chip_info { + const char *name; + const struct iio_chan_spec channel; + const struct iio_chan_spec channel_offload; + int (*get_lin_comp)(unsigned int span); +}; + +/** + * struct ad5791_state - driver instance specific data + * @spi: spi_device + * @gpio_reset: reset gpio + * @gpio_clear: clear gpio + * @gpio_ldac: load dac gpio + * @chip_info: chip model specific constants + * @offload_msg: spi message used for offload + * @offload_xfer: spi transfer used for offload + * @offload: offload device + * @offload_trigger: offload trigger + * @offload_trigger_hz: offload sample rate + * @vref_mv: actual reference voltage used + * @vref_neg_mv: voltage of the negative supply + * @ctrl: control register cache + * @pwr_down_mode: current power down mode + * @pwr_down: true if device is powered down + * @data: spi transfer buffers + */ +struct ad5791_state { + struct spi_device *spi; + struct gpio_desc *gpio_reset; + struct gpio_desc *gpio_clear; + struct gpio_desc *gpio_ldac; + const struct ad5791_chip_info *chip_info; + struct spi_message offload_msg; + struct spi_transfer offload_xfer; + struct spi_offload *offload; + struct spi_offload_trigger *offload_trigger; + unsigned int offload_trigger_hz; + unsigned short vref_mv; + unsigned int vref_neg_mv; + unsigned ctrl; + unsigned pwr_down_mode; + bool pwr_down; + + union { + __be32 d32; + u8 d8[4]; + } data[3] __aligned(IIO_DMA_MINALIGN); +}; + +static int ad5791_spi_write(struct ad5791_state *st, u8 addr, u32 val) +{ + st->data[0].d32 = cpu_to_be32(AD5791_CMD_WRITE | + AD5791_ADDR(addr) | + (val & AD5791_DAC_MASK)); + + return spi_write(st->spi, &st->data[0].d8[1], 3); +} + +static int ad5791_spi_read(struct ad5791_state *st, u8 addr, u32 *val) +{ + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = &st->data[0].d8[1], + .len = 3, + .cs_change = 1, + }, { + .tx_buf = &st->data[1].d8[1], + .rx_buf = &st->data[2].d8[1], + .len = 3, + }, + }; + + st->data[0].d32 = cpu_to_be32(AD5791_CMD_READ | + AD5791_ADDR(addr)); + st->data[1].d32 = cpu_to_be32(AD5791_ADDR(AD5791_ADDR_NOOP)); + + ret = spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers)); + + *val = be32_to_cpu(st->data[2].d32); + + return ret; +} + +static const char * const ad5791_powerdown_modes[] = { + "6kohm_to_gnd", + "three_state", +}; + +static int ad5791_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad5791_state *st = iio_priv(indio_dev); + + return st->pwr_down_mode; +} + +static int ad5791_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int mode) +{ + struct ad5791_state *st = iio_priv(indio_dev); + + st->pwr_down_mode = mode; + + return 0; +} + +static const struct iio_enum ad5791_powerdown_mode_enum = { + .items = ad5791_powerdown_modes, + .num_items = ARRAY_SIZE(ad5791_powerdown_modes), + .get = ad5791_get_powerdown_mode, + .set = ad5791_set_powerdown_mode, +}; + +static ssize_t ad5791_read_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, char *buf) +{ + struct ad5791_state *st = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", st->pwr_down); +} + +static ssize_t ad5791_write_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, const char *buf, + size_t len) +{ + bool pwr_down; + int ret; + struct ad5791_state *st = iio_priv(indio_dev); + + ret = kstrtobool(buf, &pwr_down); + if (ret) + return ret; + + if (!pwr_down) { + st->ctrl &= ~(AD5791_CTRL_OPGND | AD5791_CTRL_DACTRI); + } else { + if (st->pwr_down_mode == AD5791_DAC_PWRDN_6K) + st->ctrl |= AD5791_CTRL_OPGND; + else if (st->pwr_down_mode == AD5791_DAC_PWRDN_3STATE) + st->ctrl |= AD5791_CTRL_DACTRI; + } + st->pwr_down = pwr_down; + + ret = ad5791_spi_write(st, AD5791_ADDR_CTRL, st->ctrl); + + return ret ? ret : len; +} + +static int ad5791_get_lin_comp(unsigned int span) +{ + if (span <= 10000) + return AD5791_LINCOMP_0_10; + else if (span <= 12000) + return AD5791_LINCOMP_10_12; + else if (span <= 16000) + return AD5791_LINCOMP_12_16; + else if (span <= 19000) + return AD5791_LINCOMP_16_19; + else + return AD5791_LINCOMP_19_20; +} + +static int ad5780_get_lin_comp(unsigned int span) +{ + if (span <= 10000) + return AD5780_LINCOMP_0_10; + else + return AD5780_LINCOMP_10_20; +} + +static int ad5791_set_sample_freq(struct ad5791_state *st, int val) +{ + struct spi_offload_trigger_config config = { + .type = SPI_OFFLOAD_TRIGGER_PERIODIC, + .periodic = { + .frequency_hz = val, + }, + }; + int ret; + + ret = spi_offload_trigger_validate(st->offload_trigger, &config); + if (ret) + return ret; + + st->offload_trigger_hz = config.periodic.frequency_hz; + + return 0; +} + +static int ad5791_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad5791_state *st = iio_priv(indio_dev); + u64 val64; + int ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = ad5791_spi_read(st, chan->address, val); + if (ret) + return ret; + *val &= AD5791_DAC_MASK; + *val >>= chan->scan_type.shift; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = st->vref_mv; + *val2 = (1 << chan->scan_type.realbits) - 1; + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_OFFSET: + val64 = (((u64)st->vref_neg_mv) << chan->scan_type.realbits); + do_div(val64, st->vref_mv); + *val = -val64; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = st->offload_trigger_hz; + return IIO_VAL_INT; + default: + return -EINVAL; + } + +}; + +static const struct iio_chan_spec_ext_info ad5791_ext_info[] = { + { + .name = "powerdown", + .shared = IIO_SHARED_BY_TYPE, + .read = ad5791_read_dac_powerdown, + .write = ad5791_write_dac_powerdown, + }, + IIO_ENUM("powerdown_mode", IIO_SHARED_BY_TYPE, + &ad5791_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, &ad5791_powerdown_mode_enum), + { } +}; + +#define AD5791_DEFINE_CHIP_INFO(_name, bits, _shift, _lin_comp) \ +static const struct ad5791_chip_info _name##_chip_info = { \ + .name = #_name, \ + .get_lin_comp = &(_lin_comp), \ + .channel = { \ + .type = IIO_VOLTAGE, \ + .output = 1, \ + .indexed = 1, \ + .address = AD5791_ADDR_DAC0, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 32, \ + .shift = (_shift), \ + }, \ + .ext_info = ad5791_ext_info, \ + }, \ + .channel_offload = { \ + .type = IIO_VOLTAGE, \ + .output = 1, \ + .indexed = 1, \ + .address = AD5791_ADDR_DAC0, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 32, \ + .shift = (_shift), \ + }, \ + .ext_info = ad5791_ext_info, \ + }, \ +} + +AD5791_DEFINE_CHIP_INFO(ad5760, 16, 4, ad5780_get_lin_comp); +AD5791_DEFINE_CHIP_INFO(ad5780, 18, 2, ad5780_get_lin_comp); +AD5791_DEFINE_CHIP_INFO(ad5781, 18, 2, ad5791_get_lin_comp); +AD5791_DEFINE_CHIP_INFO(ad5790, 20, 0, ad5791_get_lin_comp); +AD5791_DEFINE_CHIP_INFO(ad5791, 20, 0, ad5791_get_lin_comp); + +static int ad5791_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad5791_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + val &= GENMASK(chan->scan_type.realbits - 1, 0); + val <<= chan->scan_type.shift; + + return ad5791_spi_write(st, chan->address, val); + + case IIO_CHAN_INFO_SAMP_FREQ: + if (val < 1) + return -EINVAL; + return ad5791_set_sample_freq(st, val); + default: + return -EINVAL; + } +} + +static int ad5791_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT; + default: + return IIO_VAL_INT_PLUS_MICRO; + } +} + +static int ad5791_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ad5791_state *st = iio_priv(indio_dev); + struct spi_offload_trigger_config config = { + .type = SPI_OFFLOAD_TRIGGER_PERIODIC, + .periodic = { + .frequency_hz = st->offload_trigger_hz, + }, + }; + + if (st->pwr_down) + return -EINVAL; + + return spi_offload_trigger_enable(st->offload, st->offload_trigger, + &config); +} + +static int ad5791_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ad5791_state *st = iio_priv(indio_dev); + + spi_offload_trigger_disable(st->offload, st->offload_trigger); + + return 0; +} + +static const struct iio_buffer_setup_ops ad5791_buffer_setup_ops = { + .preenable = &ad5791_buffer_preenable, + .postdisable = &ad5791_buffer_postdisable, +}; + +static int ad5791_offload_setup(struct iio_dev *indio_dev) +{ + struct ad5791_state *st = iio_priv(indio_dev); + struct spi_device *spi = st->spi; + struct dma_chan *tx_dma; + int ret; + + st->offload_trigger = devm_spi_offload_trigger_get(&spi->dev, + st->offload, SPI_OFFLOAD_TRIGGER_PERIODIC); + if (IS_ERR(st->offload_trigger)) + return dev_err_probe(&spi->dev, PTR_ERR(st->offload_trigger), + "failed to get offload trigger\n"); + + ret = ad5791_set_sample_freq(st, 1 * MEGA); + if (ret) + return dev_err_probe(&spi->dev, ret, + "failed to init sample rate\n"); + + tx_dma = devm_spi_offload_tx_stream_request_dma_chan(&spi->dev, + st->offload); + if (IS_ERR(tx_dma)) + return dev_err_probe(&spi->dev, PTR_ERR(tx_dma), + "failed to get offload TX DMA\n"); + + ret = devm_iio_dmaengine_buffer_setup_with_handle(&spi->dev, + indio_dev, tx_dma, IIO_BUFFER_DIRECTION_OUT); + if (ret) + return ret; + + st->offload_xfer.len = 4; + st->offload_xfer.bits_per_word = 24; + st->offload_xfer.offload_flags = SPI_OFFLOAD_XFER_TX_STREAM; + + spi_message_init_with_transfers(&st->offload_msg, &st->offload_xfer, 1); + st->offload_msg.offload = st->offload; + + return devm_spi_optimize_message(&spi->dev, st->spi, &st->offload_msg); +} + +static const struct iio_info ad5791_info = { + .read_raw = &ad5791_read_raw, + .write_raw = &ad5791_write_raw, + .write_raw_get_fmt = &ad5791_write_raw_get_fmt, +}; + +static const struct spi_offload_config ad5791_offload_config = { + .capability_flags = SPI_OFFLOAD_CAP_TRIGGER | + SPI_OFFLOAD_CAP_TX_STREAM_DMA, +}; + +static int ad5791_probe(struct spi_device *spi) +{ + const struct ad5791_platform_data *pdata = dev_get_platdata(&spi->dev); + struct iio_dev *indio_dev; + struct ad5791_state *st; + int ret, pos_voltage_uv = 0, neg_voltage_uv = 0; + bool use_rbuf_gain2; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + st = iio_priv(indio_dev); + + st->gpio_reset = devm_gpiod_get_optional(&spi->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(st->gpio_reset)) + return PTR_ERR(st->gpio_reset); + + st->gpio_clear = devm_gpiod_get_optional(&spi->dev, "clear", + GPIOD_OUT_LOW); + if (IS_ERR(st->gpio_clear)) + return PTR_ERR(st->gpio_clear); + + st->gpio_ldac = devm_gpiod_get_optional(&spi->dev, "ldac", + GPIOD_OUT_HIGH); + if (IS_ERR(st->gpio_ldac)) + return PTR_ERR(st->gpio_ldac); + + st->pwr_down = true; + st->spi = spi; + + if (pdata) + use_rbuf_gain2 = pdata->use_rbuf_gain2; + else + use_rbuf_gain2 = device_property_read_bool(&spi->dev, + "adi,rbuf-gain2-en"); + + pos_voltage_uv = devm_regulator_get_enable_read_voltage(&spi->dev, "vdd"); + if (pos_voltage_uv < 0 && pos_voltage_uv != -ENODEV) + return dev_err_probe(&spi->dev, pos_voltage_uv, + "failed to get vdd voltage\n"); + + neg_voltage_uv = devm_regulator_get_enable_read_voltage(&spi->dev, "vss"); + if (neg_voltage_uv < 0 && neg_voltage_uv != -ENODEV) + return dev_err_probe(&spi->dev, neg_voltage_uv, + "failed to get vss voltage\n"); + + if (neg_voltage_uv >= 0 && pos_voltage_uv >= 0) { + st->vref_mv = (pos_voltage_uv + neg_voltage_uv) / 1000; + st->vref_neg_mv = neg_voltage_uv / 1000; + } else if (pdata) { + st->vref_mv = pdata->vref_pos_mv + pdata->vref_neg_mv; + st->vref_neg_mv = pdata->vref_neg_mv; + } else { + dev_warn(&spi->dev, "reference voltage unspecified\n"); + } + + if (st->gpio_reset) { + fsleep(20); + gpiod_set_value_cansleep(st->gpio_reset, 0); + } else { + ret = ad5791_spi_write(st, AD5791_ADDR_SW_CTRL, AD5791_SWCTRL_RESET); + if (ret) + return dev_err_probe(&spi->dev, ret, "fail to reset\n"); + } + + st->chip_info = spi_get_device_match_data(spi); + if (!st->chip_info) + return dev_err_probe(&spi->dev, -EINVAL, "no chip info\n"); + + st->ctrl = AD5761_CTRL_LINCOMP(st->chip_info->get_lin_comp(st->vref_mv)) + | (use_rbuf_gain2 ? 0 : AD5791_CTRL_RBUF) | + AD5791_CTRL_BIN2SC; + + ret = ad5791_spi_write(st, AD5791_ADDR_CTRL, st->ctrl | + AD5791_CTRL_OPGND | AD5791_CTRL_DACTRI); + if (ret) + return dev_err_probe(&spi->dev, ret, "fail to write ctrl register\n"); + + indio_dev->info = &ad5791_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = &st->chip_info->channel; + indio_dev->num_channels = 1; + indio_dev->name = st->chip_info->name; + + st->offload = devm_spi_offload_get(&spi->dev, spi, &ad5791_offload_config); + ret = PTR_ERR_OR_ZERO(st->offload); + if (ret && ret != -ENODEV) + return dev_err_probe(&spi->dev, ret, "failed to get offload\n"); + + if (ret != -ENODEV) { + indio_dev->channels = &st->chip_info->channel_offload; + indio_dev->setup_ops = &ad5791_buffer_setup_ops; + ret = ad5791_offload_setup(indio_dev); + if (ret) + return dev_err_probe(&spi->dev, ret, + "fail to setup offload\n"); + } + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct of_device_id ad5791_of_match[] = { + { .compatible = "adi,ad5760", .data = &ad5760_chip_info }, + { .compatible = "adi,ad5780", .data = &ad5780_chip_info }, + { .compatible = "adi,ad5781", .data = &ad5781_chip_info }, + { .compatible = "adi,ad5790", .data = &ad5790_chip_info }, + { .compatible = "adi,ad5791", .data = &ad5791_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad5791_of_match); + +static const struct spi_device_id ad5791_id[] = { + { .name = "ad5760", .driver_data = (kernel_ulong_t)&ad5760_chip_info }, + { .name = "ad5780", .driver_data = (kernel_ulong_t)&ad5780_chip_info }, + { .name = "ad5781", .driver_data = (kernel_ulong_t)&ad5781_chip_info }, + { .name = "ad5790", .driver_data = (kernel_ulong_t)&ad5790_chip_info }, + { .name = "ad5791", .driver_data = (kernel_ulong_t)&ad5791_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad5791_id); + +static struct spi_driver ad5791_driver = { + .driver = { + .name = "ad5791", + .of_match_table = ad5791_of_match, + }, + .probe = ad5791_probe, + .id_table = ad5791_id, +}; +module_spi_driver(ad5791_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD5760/AD5780/AD5781/AD5790/AD5791 DAC"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER"); diff --git a/drivers/iio/dac/ad7293.c b/drivers/iio/dac/ad7293.c new file mode 100644 index 000000000..33a49813e --- /dev/null +++ b/drivers/iio/dac/ad7293.c @@ -0,0 +1,895 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD7293 driver + * + * Copyright 2021 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define AD7293_R1B BIT(16) +#define AD7293_R2B BIT(17) +#define AD7293_PAGE_ADDR_MSK GENMASK(15, 8) +#define AD7293_PAGE(x) FIELD_PREP(AD7293_PAGE_ADDR_MSK, x) + +/* AD7293 Register Map Common */ +#define AD7293_REG_NO_OP (AD7293_R1B | AD7293_PAGE(0x0) | 0x0) +#define AD7293_REG_PAGE_SELECT (AD7293_R1B | AD7293_PAGE(0x0) | 0x1) +#define AD7293_REG_CONV_CMD (AD7293_R2B | AD7293_PAGE(0x0) | 0x2) +#define AD7293_REG_RESULT (AD7293_R1B | AD7293_PAGE(0x0) | 0x3) +#define AD7293_REG_DAC_EN (AD7293_R1B | AD7293_PAGE(0x0) | 0x4) +#define AD7293_REG_DEVICE_ID (AD7293_R2B | AD7293_PAGE(0x0) | 0xC) +#define AD7293_REG_SOFT_RESET (AD7293_R2B | AD7293_PAGE(0x0) | 0xF) + +/* AD7293 Register Map Page 0x0 */ +#define AD7293_REG_VIN0 (AD7293_R2B | AD7293_PAGE(0x0) | 0x10) +#define AD7293_REG_VIN1 (AD7293_R2B | AD7293_PAGE(0x0) | 0x11) +#define AD7293_REG_VIN2 (AD7293_R2B | AD7293_PAGE(0x0) | 0x12) +#define AD7293_REG_VIN3 (AD7293_R2B | AD7293_PAGE(0x0) | 0x13) +#define AD7293_REG_TSENSE_INT (AD7293_R2B | AD7293_PAGE(0x0) | 0x20) +#define AD7293_REG_TSENSE_D0 (AD7293_R2B | AD7293_PAGE(0x0) | 0x21) +#define AD7293_REG_TSENSE_D1 (AD7293_R2B | AD7293_PAGE(0x0) | 0x22) +#define AD7293_REG_ISENSE_0 (AD7293_R2B | AD7293_PAGE(0x0) | 0x28) +#define AD7293_REG_ISENSE_1 (AD7293_R2B | AD7293_PAGE(0x0) | 0x29) +#define AD7293_REG_ISENSE_2 (AD7293_R2B | AD7293_PAGE(0x0) | 0x2A) +#define AD7293_REG_ISENSE_3 (AD7293_R2B | AD7293_PAGE(0x0) | 0x2B) +#define AD7293_REG_UNI_VOUT0 (AD7293_R2B | AD7293_PAGE(0x0) | 0x30) +#define AD7293_REG_UNI_VOUT1 (AD7293_R2B | AD7293_PAGE(0x0) | 0x31) +#define AD7293_REG_UNI_VOUT2 (AD7293_R2B | AD7293_PAGE(0x0) | 0x32) +#define AD7293_REG_UNI_VOUT3 (AD7293_R2B | AD7293_PAGE(0x0) | 0x33) +#define AD7293_REG_BI_VOUT0 (AD7293_R2B | AD7293_PAGE(0x0) | 0x34) +#define AD7293_REG_BI_VOUT1 (AD7293_R2B | AD7293_PAGE(0x0) | 0x35) +#define AD7293_REG_BI_VOUT2 (AD7293_R2B | AD7293_PAGE(0x0) | 0x36) +#define AD7293_REG_BI_VOUT3 (AD7293_R2B | AD7293_PAGE(0x0) | 0x37) + +/* AD7293 Register Map Page 0x2 */ +#define AD7293_REG_DIGITAL_OUT_EN (AD7293_R2B | AD7293_PAGE(0x2) | 0x11) +#define AD7293_REG_DIGITAL_INOUT_FUNC (AD7293_R2B | AD7293_PAGE(0x2) | 0x12) +#define AD7293_REG_DIGITAL_FUNC_POL (AD7293_R2B | AD7293_PAGE(0x2) | 0x13) +#define AD7293_REG_GENERAL (AD7293_R2B | AD7293_PAGE(0x2) | 0x14) +#define AD7293_REG_VINX_RANGE0 (AD7293_R2B | AD7293_PAGE(0x2) | 0x15) +#define AD7293_REG_VINX_RANGE1 (AD7293_R2B | AD7293_PAGE(0x2) | 0x16) +#define AD7293_REG_VINX_DIFF_SE (AD7293_R2B | AD7293_PAGE(0x2) | 0x17) +#define AD7293_REG_VINX_FILTER (AD7293_R2B | AD7293_PAGE(0x2) | 0x18) +#define AD7293_REG_BG_EN (AD7293_R2B | AD7293_PAGE(0x2) | 0x19) +#define AD7293_REG_CONV_DELAY (AD7293_R2B | AD7293_PAGE(0x2) | 0x1A) +#define AD7293_REG_TSENSE_BG_EN (AD7293_R2B | AD7293_PAGE(0x2) | 0x1B) +#define AD7293_REG_ISENSE_BG_EN (AD7293_R2B | AD7293_PAGE(0x2) | 0x1C) +#define AD7293_REG_ISENSE_GAIN (AD7293_R2B | AD7293_PAGE(0x2) | 0x1D) +#define AD7293_REG_DAC_SNOOZE_O (AD7293_R2B | AD7293_PAGE(0x2) | 0x1F) +#define AD7293_REG_DAC_SNOOZE_1 (AD7293_R2B | AD7293_PAGE(0x2) | 0x20) +#define AD7293_REG_RSX_MON_BG_EN (AD7293_R2B | AD7293_PAGE(0x2) | 0x23) +#define AD7293_REG_INTEGR_CL (AD7293_R2B | AD7293_PAGE(0x2) | 0x28) +#define AD7293_REG_PA_ON_CTRL (AD7293_R2B | AD7293_PAGE(0x2) | 0x29) +#define AD7293_REG_RAMP_TIME_0 (AD7293_R2B | AD7293_PAGE(0x2) | 0x2A) +#define AD7293_REG_RAMP_TIME_1 (AD7293_R2B | AD7293_PAGE(0x2) | 0x2B) +#define AD7293_REG_RAMP_TIME_2 (AD7293_R2B | AD7293_PAGE(0x2) | 0x2C) +#define AD7293_REG_RAMP_TIME_3 (AD7293_R2B | AD7293_PAGE(0x2) | 0x2D) +#define AD7293_REG_CL_FR_IT (AD7293_R2B | AD7293_PAGE(0x2) | 0x2E) +#define AD7293_REG_INTX_AVSS_AVDD (AD7293_R2B | AD7293_PAGE(0x2) | 0x2F) + +/* AD7293 Register Map Page 0x3 */ +#define AD7293_REG_VINX_SEQ (AD7293_R2B | AD7293_PAGE(0x3) | 0x10) +#define AD7293_REG_ISENSEX_TSENSEX_SEQ (AD7293_R2B | AD7293_PAGE(0x3) | 0x11) +#define AD7293_REG_RSX_MON_BI_VOUTX_SEQ (AD7293_R2B | AD7293_PAGE(0x3) | 0x12) + +/* AD7293 Register Map Page 0xE */ +#define AD7293_REG_VIN0_OFFSET (AD7293_R1B | AD7293_PAGE(0xE) | 0x10) +#define AD7293_REG_VIN1_OFFSET (AD7293_R1B | AD7293_PAGE(0xE) | 0x11) +#define AD7293_REG_VIN2_OFFSET (AD7293_R1B | AD7293_PAGE(0xE) | 0x12) +#define AD7293_REG_VIN3_OFFSET (AD7293_R1B | AD7293_PAGE(0xE) | 0x13) +#define AD7293_REG_TSENSE_INT_OFFSET (AD7293_R1B | AD7293_PAGE(0xE) | 0x20) +#define AD7293_REG_TSENSE_D0_OFFSET (AD7293_R1B | AD7293_PAGE(0xE) | 0x21) +#define AD7293_REG_TSENSE_D1_OFFSET (AD7293_R1B | AD7293_PAGE(0xE) | 0x22) +#define AD7293_REG_ISENSE0_OFFSET (AD7293_R1B | AD7293_PAGE(0xE) | 0x28) +#define AD7293_REG_ISENSE1_OFFSET (AD7293_R1B | AD7293_PAGE(0xE) | 0x29) +#define AD7293_REG_ISENSE2_OFFSET (AD7293_R1B | AD7293_PAGE(0xE) | 0x2A) +#define AD7293_REG_ISENSE3_OFFSET (AD7293_R1B | AD7293_PAGE(0xE) | 0x2B) +#define AD7293_REG_UNI_VOUT0_OFFSET (AD7293_R1B | AD7293_PAGE(0xE) | 0x30) +#define AD7293_REG_UNI_VOUT1_OFFSET (AD7293_R1B | AD7293_PAGE(0xE) | 0x31) +#define AD7293_REG_UNI_VOUT2_OFFSET (AD7293_R1B | AD7293_PAGE(0xE) | 0x32) +#define AD7293_REG_UNI_VOUT3_OFFSET (AD7293_R1B | AD7293_PAGE(0xE) | 0x33) +#define AD7293_REG_BI_VOUT0_OFFSET (AD7293_R1B | AD7293_PAGE(0xE) | 0x34) +#define AD7293_REG_BI_VOUT1_OFFSET (AD7293_R1B | AD7293_PAGE(0xE) | 0x35) +#define AD7293_REG_BI_VOUT2_OFFSET (AD7293_R1B | AD7293_PAGE(0xE) | 0x36) +#define AD7293_REG_BI_VOUT3_OFFSET (AD7293_R1B | AD7293_PAGE(0xE) | 0x37) + +/* AD7293 Miscellaneous Definitions */ +#define AD7293_READ BIT(7) +#define AD7293_TRANSF_LEN_MSK GENMASK(17, 16) + +#define AD7293_REG_ADDR_MSK GENMASK(7, 0) +#define AD7293_REG_VOUT_OFFSET_MSK GENMASK(5, 4) +#define AD7293_REG_DATA_RAW_MSK GENMASK(15, 4) +#define AD7293_REG_VINX_RANGE_GET_CH_MSK(x, ch) (((x) >> (ch)) & 0x1) +#define AD7293_REG_VINX_RANGE_SET_CH_MSK(x, ch) (((x) & 0x1) << (ch)) +#define AD7293_GENERAL_ADC_REF_MSK BIT(7) +#define AD7293_CHIP_ID 0x18 + +enum ad7293_ch_type { + AD7293_ADC_VINX, + AD7293_ADC_TSENSE, + AD7293_ADC_ISENSE, + AD7293_DAC, +}; + +enum ad7293_max_offset { + AD7293_TSENSE_MIN_OFFSET_CH = 4, + AD7293_ISENSE_MIN_OFFSET_CH = 7, + AD7293_VOUT_MIN_OFFSET_CH = 11, + AD7293_VOUT_MAX_OFFSET_CH = 18, +}; + +static const int dac_offset_table[] = {0, 1, 2}; + +static const int isense_gain_table[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + +static const int adc_range_table[] = {0, 1, 2, 3}; + +struct ad7293_state { + struct spi_device *spi; + /* Protect against concurrent accesses to the device, page selection and data content */ + struct mutex lock; + struct gpio_desc *gpio_reset; + bool vrefin_en; + u8 page_select; + u8 data[3] __aligned(IIO_DMA_MINALIGN); +}; + +static int ad7293_page_select(struct ad7293_state *st, unsigned int reg) +{ + int ret; + + if (st->page_select != FIELD_GET(AD7293_PAGE_ADDR_MSK, reg)) { + st->data[0] = FIELD_GET(AD7293_REG_ADDR_MSK, AD7293_REG_PAGE_SELECT); + st->data[1] = FIELD_GET(AD7293_PAGE_ADDR_MSK, reg); + + ret = spi_write(st->spi, &st->data[0], 2); + if (ret) + return ret; + + st->page_select = FIELD_GET(AD7293_PAGE_ADDR_MSK, reg); + } + + return 0; +} + +static int __ad7293_spi_read(struct ad7293_state *st, unsigned int reg, + u16 *val) +{ + int ret; + unsigned int length; + struct spi_transfer t = {0}; + + length = FIELD_GET(AD7293_TRANSF_LEN_MSK, reg); + + ret = ad7293_page_select(st, reg); + if (ret) + return ret; + + st->data[0] = AD7293_READ | FIELD_GET(AD7293_REG_ADDR_MSK, reg); + st->data[1] = 0x0; + st->data[2] = 0x0; + + t.tx_buf = &st->data[0]; + t.rx_buf = &st->data[0]; + t.len = length + 1; + + ret = spi_sync_transfer(st->spi, &t, 1); + if (ret) + return ret; + + if (length == 1) + *val = st->data[1]; + else + *val = get_unaligned_be16(&st->data[1]); + + return 0; +} + +static int ad7293_spi_read(struct ad7293_state *st, unsigned int reg, + u16 *val) +{ + int ret; + + mutex_lock(&st->lock); + ret = __ad7293_spi_read(st, reg, val); + mutex_unlock(&st->lock); + + return ret; +} + +static int __ad7293_spi_write(struct ad7293_state *st, unsigned int reg, + u16 val) +{ + int ret; + unsigned int length; + + length = FIELD_GET(AD7293_TRANSF_LEN_MSK, reg); + + ret = ad7293_page_select(st, reg); + if (ret) + return ret; + + st->data[0] = FIELD_GET(AD7293_REG_ADDR_MSK, reg); + + if (length == 1) + st->data[1] = val; + else + put_unaligned_be16(val, &st->data[1]); + + return spi_write(st->spi, &st->data[0], length + 1); +} + +static int ad7293_spi_write(struct ad7293_state *st, unsigned int reg, + u16 val) +{ + int ret; + + mutex_lock(&st->lock); + ret = __ad7293_spi_write(st, reg, val); + mutex_unlock(&st->lock); + + return ret; +} + +static int __ad7293_spi_update_bits(struct ad7293_state *st, unsigned int reg, + u16 mask, u16 val) +{ + int ret; + u16 data, temp; + + ret = __ad7293_spi_read(st, reg, &data); + if (ret) + return ret; + + temp = (data & ~mask) | (val & mask); + + return __ad7293_spi_write(st, reg, temp); +} + +static int ad7293_spi_update_bits(struct ad7293_state *st, unsigned int reg, + u16 mask, u16 val) +{ + int ret; + + mutex_lock(&st->lock); + ret = __ad7293_spi_update_bits(st, reg, mask, val); + mutex_unlock(&st->lock); + + return ret; +} + +static int ad7293_adc_get_scale(struct ad7293_state *st, unsigned int ch, + u16 *range) +{ + int ret; + u16 data; + + mutex_lock(&st->lock); + + ret = __ad7293_spi_read(st, AD7293_REG_VINX_RANGE1, &data); + if (ret) + goto exit; + + *range = AD7293_REG_VINX_RANGE_GET_CH_MSK(data, ch); + + ret = __ad7293_spi_read(st, AD7293_REG_VINX_RANGE0, &data); + if (ret) + goto exit; + + *range |= AD7293_REG_VINX_RANGE_GET_CH_MSK(data, ch) << 1; + +exit: + mutex_unlock(&st->lock); + + return ret; +} + +static int ad7293_adc_set_scale(struct ad7293_state *st, unsigned int ch, + u16 range) +{ + int ret; + unsigned int ch_msk = BIT(ch); + + mutex_lock(&st->lock); + ret = __ad7293_spi_update_bits(st, AD7293_REG_VINX_RANGE1, ch_msk, + AD7293_REG_VINX_RANGE_SET_CH_MSK(range, ch)); + if (ret) + goto exit; + + ret = __ad7293_spi_update_bits(st, AD7293_REG_VINX_RANGE0, ch_msk, + AD7293_REG_VINX_RANGE_SET_CH_MSK((range >> 1), ch)); + +exit: + mutex_unlock(&st->lock); + + return ret; +} + +static int ad7293_get_offset(struct ad7293_state *st, unsigned int ch, + u16 *offset) +{ + if (ch < AD7293_TSENSE_MIN_OFFSET_CH) + return ad7293_spi_read(st, AD7293_REG_VIN0_OFFSET + ch, offset); + else if (ch < AD7293_ISENSE_MIN_OFFSET_CH) + return ad7293_spi_read(st, AD7293_REG_TSENSE_INT_OFFSET + (ch - 4), offset); + else if (ch < AD7293_VOUT_MIN_OFFSET_CH) + return ad7293_spi_read(st, AD7293_REG_ISENSE0_OFFSET + (ch - 7), offset); + else if (ch <= AD7293_VOUT_MAX_OFFSET_CH) + return ad7293_spi_read(st, AD7293_REG_UNI_VOUT0_OFFSET + (ch - 11), offset); + + return -EINVAL; +} + +static int ad7293_set_offset(struct ad7293_state *st, unsigned int ch, + u16 offset) +{ + if (ch < AD7293_TSENSE_MIN_OFFSET_CH) + return ad7293_spi_write(st, AD7293_REG_VIN0_OFFSET + ch, + offset); + else if (ch < AD7293_ISENSE_MIN_OFFSET_CH) + return ad7293_spi_write(st, + AD7293_REG_TSENSE_INT_OFFSET + + (ch - AD7293_TSENSE_MIN_OFFSET_CH), + offset); + else if (ch < AD7293_VOUT_MIN_OFFSET_CH) + return ad7293_spi_write(st, + AD7293_REG_ISENSE0_OFFSET + + (ch - AD7293_ISENSE_MIN_OFFSET_CH), + offset); + else if (ch <= AD7293_VOUT_MAX_OFFSET_CH) + return ad7293_spi_update_bits(st, + AD7293_REG_UNI_VOUT0_OFFSET + + (ch - AD7293_VOUT_MIN_OFFSET_CH), + AD7293_REG_VOUT_OFFSET_MSK, + FIELD_PREP(AD7293_REG_VOUT_OFFSET_MSK, offset)); + + return -EINVAL; +} + +static int ad7293_isense_set_scale(struct ad7293_state *st, unsigned int ch, + u16 gain) +{ + unsigned int ch_msk = (0xf << (4 * ch)); + + return ad7293_spi_update_bits(st, AD7293_REG_ISENSE_GAIN, ch_msk, + gain << (4 * ch)); +} + +static int ad7293_isense_get_scale(struct ad7293_state *st, unsigned int ch, + u16 *gain) +{ + int ret; + + ret = ad7293_spi_read(st, AD7293_REG_ISENSE_GAIN, gain); + if (ret) + return ret; + + *gain = (*gain >> (4 * ch)) & 0xf; + + return ret; +} + +static int ad7293_dac_write_raw(struct ad7293_state *st, unsigned int ch, + u16 raw) +{ + int ret; + + mutex_lock(&st->lock); + + ret = __ad7293_spi_update_bits(st, AD7293_REG_DAC_EN, BIT(ch), BIT(ch)); + if (ret) + goto exit; + + ret = __ad7293_spi_write(st, AD7293_REG_UNI_VOUT0 + ch, + FIELD_PREP(AD7293_REG_DATA_RAW_MSK, raw)); + +exit: + mutex_unlock(&st->lock); + + return ret; +} + +static int ad7293_ch_read_raw(struct ad7293_state *st, enum ad7293_ch_type type, + unsigned int ch, u16 *raw) +{ + int ret; + unsigned int reg_wr, reg_rd, data_wr; + + switch (type) { + case AD7293_ADC_VINX: + reg_wr = AD7293_REG_VINX_SEQ; + reg_rd = AD7293_REG_VIN0 + ch; + data_wr = BIT(ch); + + break; + case AD7293_ADC_TSENSE: + reg_wr = AD7293_REG_ISENSEX_TSENSEX_SEQ; + reg_rd = AD7293_REG_TSENSE_INT + ch; + data_wr = BIT(ch); + + break; + case AD7293_ADC_ISENSE: + reg_wr = AD7293_REG_ISENSEX_TSENSEX_SEQ; + reg_rd = AD7293_REG_ISENSE_0 + ch; + data_wr = BIT(ch) << 8; + + break; + case AD7293_DAC: + reg_rd = AD7293_REG_UNI_VOUT0 + ch; + + break; + default: + return -EINVAL; + } + + mutex_lock(&st->lock); + + if (type != AD7293_DAC) { + if (type == AD7293_ADC_TSENSE) { + ret = __ad7293_spi_write(st, AD7293_REG_TSENSE_BG_EN, + BIT(ch)); + if (ret) + goto exit; + + usleep_range(9000, 9900); + } else if (type == AD7293_ADC_ISENSE) { + ret = __ad7293_spi_write(st, AD7293_REG_ISENSE_BG_EN, + BIT(ch)); + if (ret) + goto exit; + + usleep_range(2000, 7000); + } + + ret = __ad7293_spi_write(st, reg_wr, data_wr); + if (ret) + goto exit; + + ret = __ad7293_spi_write(st, AD7293_REG_CONV_CMD, 0x82); + if (ret) + goto exit; + } + + ret = __ad7293_spi_read(st, reg_rd, raw); + + *raw = FIELD_GET(AD7293_REG_DATA_RAW_MSK, *raw); + +exit: + mutex_unlock(&st->lock); + + return ret; +} + +static int ad7293_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad7293_state *st = iio_priv(indio_dev); + int ret; + u16 data; + + switch (info) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->output) + ret = ad7293_ch_read_raw(st, AD7293_DAC, + chan->channel, &data); + else + ret = ad7293_ch_read_raw(st, AD7293_ADC_VINX, + chan->channel, &data); + + break; + case IIO_CURRENT: + ret = ad7293_ch_read_raw(st, AD7293_ADC_ISENSE, + chan->channel, &data); + + break; + case IIO_TEMP: + ret = ad7293_ch_read_raw(st, AD7293_ADC_TSENSE, + chan->channel, &data); + + break; + default: + return -EINVAL; + } + + if (ret) + return ret; + + *val = data; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->output) { + ret = ad7293_get_offset(st, + chan->channel + AD7293_VOUT_MIN_OFFSET_CH, + &data); + + data = FIELD_GET(AD7293_REG_VOUT_OFFSET_MSK, data); + } else { + ret = ad7293_get_offset(st, chan->channel, &data); + } + + break; + case IIO_CURRENT: + ret = ad7293_get_offset(st, + chan->channel + AD7293_ISENSE_MIN_OFFSET_CH, + &data); + + break; + case IIO_TEMP: + ret = ad7293_get_offset(st, + chan->channel + AD7293_TSENSE_MIN_OFFSET_CH, + &data); + + break; + default: + return -EINVAL; + } + if (ret) + return ret; + + *val = data; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + ret = ad7293_adc_get_scale(st, chan->channel, &data); + if (ret) + return ret; + + *val = data; + + return IIO_VAL_INT; + case IIO_CURRENT: + ret = ad7293_isense_get_scale(st, chan->channel, &data); + if (ret) + return ret; + + *val = data; + + return IIO_VAL_INT; + case IIO_TEMP: + *val = 1; + *val2 = 8; + + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ad7293_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct ad7293_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_VOLTAGE: + if (!chan->output) + return -EINVAL; + + return ad7293_dac_write_raw(st, chan->channel, val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->output) + return ad7293_set_offset(st, + chan->channel + + AD7293_VOUT_MIN_OFFSET_CH, + val); + else + return ad7293_set_offset(st, chan->channel, val); + case IIO_CURRENT: + return ad7293_set_offset(st, + chan->channel + + AD7293_ISENSE_MIN_OFFSET_CH, + val); + case IIO_TEMP: + return ad7293_set_offset(st, + chan->channel + + AD7293_TSENSE_MIN_OFFSET_CH, + val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + return ad7293_adc_set_scale(st, chan->channel, val); + case IIO_CURRENT: + return ad7293_isense_set_scale(st, chan->channel, val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ad7293_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int write_val, + unsigned int *read_val) +{ + struct ad7293_state *st = iio_priv(indio_dev); + int ret; + + if (read_val) { + u16 temp; + ret = ad7293_spi_read(st, reg, &temp); + *read_val = temp; + } else { + ret = ad7293_spi_write(st, reg, (u16)write_val); + } + + return ret; +} + +static int ad7293_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long info) +{ + switch (info) { + case IIO_CHAN_INFO_OFFSET: + *vals = dac_offset_table; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(dac_offset_table); + + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SCALE: + *type = IIO_VAL_INT; + + switch (chan->type) { + case IIO_VOLTAGE: + *vals = adc_range_table; + *length = ARRAY_SIZE(adc_range_table); + return IIO_AVAIL_LIST; + case IIO_CURRENT: + *vals = isense_gain_table; + *length = ARRAY_SIZE(isense_gain_table); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +#define AD7293_CHAN_ADC(_channel) { \ + .type = IIO_VOLTAGE, \ + .output = 0, \ + .indexed = 1, \ + .channel = _channel, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE) \ +} + +#define AD7293_CHAN_DAC(_channel) { \ + .type = IIO_VOLTAGE, \ + .output = 1, \ + .indexed = 1, \ + .channel = _channel, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_OFFSET) \ +} + +#define AD7293_CHAN_ISENSE(_channel) { \ + .type = IIO_CURRENT, \ + .output = 0, \ + .indexed = 1, \ + .channel = _channel, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE) \ +} + +#define AD7293_CHAN_TEMP(_channel) { \ + .type = IIO_TEMP, \ + .output = 0, \ + .indexed = 1, \ + .channel = _channel, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \ +} + +static const struct iio_chan_spec ad7293_channels[] = { + AD7293_CHAN_ADC(0), + AD7293_CHAN_ADC(1), + AD7293_CHAN_ADC(2), + AD7293_CHAN_ADC(3), + AD7293_CHAN_ISENSE(0), + AD7293_CHAN_ISENSE(1), + AD7293_CHAN_ISENSE(2), + AD7293_CHAN_ISENSE(3), + AD7293_CHAN_TEMP(0), + AD7293_CHAN_TEMP(1), + AD7293_CHAN_TEMP(2), + AD7293_CHAN_DAC(0), + AD7293_CHAN_DAC(1), + AD7293_CHAN_DAC(2), + AD7293_CHAN_DAC(3), + AD7293_CHAN_DAC(4), + AD7293_CHAN_DAC(5), + AD7293_CHAN_DAC(6), + AD7293_CHAN_DAC(7) +}; + +static int ad7293_soft_reset(struct ad7293_state *st) +{ + int ret; + + ret = __ad7293_spi_write(st, AD7293_REG_SOFT_RESET, 0x7293); + if (ret) + return ret; + + return __ad7293_spi_write(st, AD7293_REG_SOFT_RESET, 0x0000); +} + +static int ad7293_reset(struct ad7293_state *st) +{ + if (st->gpio_reset) { + gpiod_set_value(st->gpio_reset, 0); + usleep_range(100, 1000); + gpiod_set_value(st->gpio_reset, 1); + usleep_range(100, 1000); + + return 0; + } + + /* Perform a software reset */ + return ad7293_soft_reset(st); +} + +static int ad7293_properties_parse(struct ad7293_state *st) +{ + struct device *dev = &st->spi->dev; + int ret; + + ret = devm_regulator_get_enable(dev, "avdd"); + if (ret) + return dev_err_probe(dev, ret, "failed to enable AVDD\n"); + + ret = devm_regulator_get_enable(dev, "vdrive"); + if (ret) + return dev_err_probe(dev, ret, "failed to enable VDRIVE\n"); + + ret = devm_regulator_get_enable_optional(dev, "vrefin"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to enable VREFIN\n"); + + st->vrefin_en = ret != -ENODEV; + + st->gpio_reset = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(st->gpio_reset)) + return dev_err_probe(dev, PTR_ERR(st->gpio_reset), + "failed to get the reset GPIO\n"); + + return 0; +} + +static int ad7293_init(struct ad7293_state *st) +{ + int ret; + u16 chip_id; + struct device *dev = &st->spi->dev; + + ret = ad7293_properties_parse(st); + if (ret) + return ret; + + ret = ad7293_reset(st); + if (ret) + return ret; + + /* Check Chip ID */ + ret = __ad7293_spi_read(st, AD7293_REG_DEVICE_ID, &chip_id); + if (ret) + return ret; + + if (chip_id != AD7293_CHIP_ID) + return dev_err_probe(dev, -EINVAL, "Invalid Chip ID.\n"); + + if (!st->vrefin_en) + return __ad7293_spi_update_bits(st, AD7293_REG_GENERAL, + AD7293_GENERAL_ADC_REF_MSK, + AD7293_GENERAL_ADC_REF_MSK); + + return 0; +} + +static const struct iio_info ad7293_info = { + .read_raw = ad7293_read_raw, + .write_raw = ad7293_write_raw, + .read_avail = &ad7293_read_avail, + .debugfs_reg_access = &ad7293_reg_access, +}; + +static int ad7293_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct ad7293_state *st; + struct device *dev = &spi->dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + indio_dev->info = &ad7293_info; + indio_dev->name = "ad7293"; + indio_dev->channels = ad7293_channels; + indio_dev->num_channels = ARRAY_SIZE(ad7293_channels); + + st->spi = spi; + st->page_select = 0; + + mutex_init(&st->lock); + + ret = ad7293_init(st); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id ad7293_id[] = { + { .name = "ad7293" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7293_id); + +static const struct of_device_id ad7293_of_match[] = { + { .compatible = "adi,ad7293" }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7293_of_match); + +static struct spi_driver ad7293_driver = { + .driver = { + .name = "ad7293", + .of_match_table = ad7293_of_match, + }, + .probe = ad7293_probe, + .id_table = ad7293_id, +}; +module_spi_driver(ad7293_driver); + +MODULE_AUTHOR("Antoniu Miclaus +#include +#include +#include +#include +#include +#include + +#include +#include + +#define AD7303_CFG_EXTERNAL_VREF BIT(15) +#define AD7303_CFG_POWER_DOWN(ch) BIT(11 + (ch)) +#define AD7303_CFG_ADDR_OFFSET 10 + +#define AD7303_CMD_UPDATE_DAC (0x3 << 8) + +/** + * struct ad7303_state - driver instance specific data + * @spi: the device for this driver instance + * @config: cached config register value + * @dac_cache: current DAC raw value (chip does not support readback) + * @vdd_reg: reference to VDD regulator + * @vref_reg: reference to VREF regulator + * @lock: protect writes and cache updates + * @data: spi transfer buffer + */ + +struct ad7303_state { + struct spi_device *spi; + uint16_t config; + uint8_t dac_cache[2]; + + struct regulator *vdd_reg; + struct regulator *vref_reg; + + struct mutex lock; + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + */ + __be16 data __aligned(IIO_DMA_MINALIGN); +}; + +static int ad7303_write(struct ad7303_state *st, unsigned int chan, + uint8_t val) +{ + st->data = cpu_to_be16(AD7303_CMD_UPDATE_DAC | + (chan << AD7303_CFG_ADDR_OFFSET) | + st->config | val); + + return spi_write(st->spi, &st->data, sizeof(st->data)); +} + +static ssize_t ad7303_read_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, char *buf) +{ + struct ad7303_state *st = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", (bool)(st->config & + AD7303_CFG_POWER_DOWN(chan->channel))); +} + +static ssize_t ad7303_write_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, const char *buf, + size_t len) +{ + struct ad7303_state *st = iio_priv(indio_dev); + bool pwr_down; + int ret; + + ret = kstrtobool(buf, &pwr_down); + if (ret) + return ret; + + mutex_lock(&st->lock); + + if (pwr_down) + st->config |= AD7303_CFG_POWER_DOWN(chan->channel); + else + st->config &= ~AD7303_CFG_POWER_DOWN(chan->channel); + + /* There is no noop cmd which allows us to only update the powerdown + * mode, so just write one of the DAC channels again */ + ad7303_write(st, chan->channel, st->dac_cache[chan->channel]); + + mutex_unlock(&st->lock); + return len; +} + +static int ad7303_get_vref(struct ad7303_state *st, + struct iio_chan_spec const *chan) +{ + int ret; + + if (st->config & AD7303_CFG_EXTERNAL_VREF) + return regulator_get_voltage(st->vref_reg); + + ret = regulator_get_voltage(st->vdd_reg); + if (ret < 0) + return ret; + return ret / 2; +} + +static int ad7303_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long info) +{ + struct ad7303_state *st = iio_priv(indio_dev); + int vref_uv; + + switch (info) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&st->lock); + *val = st->dac_cache[chan->channel]; + mutex_unlock(&st->lock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + vref_uv = ad7303_get_vref(st, chan); + if (vref_uv < 0) + return vref_uv; + + *val = 2 * vref_uv / 1000; + *val2 = chan->scan_type.realbits; + + return IIO_VAL_FRACTIONAL_LOG2; + default: + break; + } + return -EINVAL; +} + +static int ad7303_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + struct ad7303_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val >= (1 << chan->scan_type.realbits) || val < 0) + return -EINVAL; + + mutex_lock(&st->lock); + ret = ad7303_write(st, chan->address, val); + if (ret == 0) + st->dac_cache[chan->channel] = val; + mutex_unlock(&st->lock); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info ad7303_info = { + .read_raw = ad7303_read_raw, + .write_raw = ad7303_write_raw, +}; + +static const struct iio_chan_spec_ext_info ad7303_ext_info[] = { + { + .name = "powerdown", + .read = ad7303_read_dac_powerdown, + .write = ad7303_write_dac_powerdown, + .shared = IIO_SEPARATE, + }, + { } +}; + +#define AD7303_CHANNEL(chan) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .address = (chan), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 8, \ + .storagebits = 8, \ + .shift = 0, \ + }, \ + .ext_info = ad7303_ext_info, \ +} + +static const struct iio_chan_spec ad7303_channels[] = { + AD7303_CHANNEL(0), + AD7303_CHANNEL(1), +}; + +static void ad7303_reg_disable(void *reg) +{ + regulator_disable(reg); +} + +static int ad7303_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct iio_dev *indio_dev; + struct ad7303_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->spi = spi; + + ret = devm_mutex_init(&spi->dev, &st->lock); + if (ret) + return ret; + + st->vdd_reg = devm_regulator_get(&spi->dev, "Vdd"); + if (IS_ERR(st->vdd_reg)) + return PTR_ERR(st->vdd_reg); + + ret = regulator_enable(st->vdd_reg); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&spi->dev, ad7303_reg_disable, st->vdd_reg); + if (ret) + return ret; + + st->vref_reg = devm_regulator_get_optional(&spi->dev, "REF"); + if (IS_ERR(st->vref_reg)) { + ret = PTR_ERR(st->vref_reg); + if (ret != -ENODEV) + return ret; + st->vref_reg = NULL; + } + + if (st->vref_reg) { + ret = regulator_enable(st->vref_reg); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&spi->dev, ad7303_reg_disable, + st->vref_reg); + if (ret) + return ret; + + st->config |= AD7303_CFG_EXTERNAL_VREF; + } + + indio_dev->name = id->name; + indio_dev->info = &ad7303_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad7303_channels; + indio_dev->num_channels = ARRAY_SIZE(ad7303_channels); + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct of_device_id ad7303_spi_of_match[] = { + { .compatible = "adi,ad7303", }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7303_spi_of_match); + +static const struct spi_device_id ad7303_spi_ids[] = { + { .name = "ad7303" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7303_spi_ids); + +static struct spi_driver ad7303_driver = { + .driver = { + .name = "ad7303", + .of_match_table = ad7303_spi_of_match, + }, + .probe = ad7303_probe, + .id_table = ad7303_spi_ids, +}; +module_spi_driver(ad7303_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Analog Devices AD7303 DAC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad8460.c b/drivers/iio/dac/ad8460.c new file mode 100644 index 000000000..a2292810e --- /dev/null +++ b/drivers/iio/dac/ad8460.c @@ -0,0 +1,957 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AD8460 Waveform generator DAC Driver + * + * Copyright (C) 2024 Analog Devices, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define AD8460_CTRL_REG(x) (x) +#define AD8460_HVDAC_DATA_WORD(x) (0x60 + (2 * (x))) + +#define AD8460_HV_RESET_MSK BIT(7) +#define AD8460_HV_SLEEP_MSK BIT(4) +#define AD8460_WAVE_GEN_MODE_MSK BIT(0) + +#define AD8460_HVDAC_SLEEP_MSK BIT(3) + +#define AD8460_FAULT_ARM_MSK BIT(7) +#define AD8460_FAULT_LIMIT_MSK GENMASK(6, 0) + +#define AD8460_APG_MODE_ENABLE_MSK BIT(5) +#define AD8460_PATTERN_DEPTH_MSK GENMASK(3, 0) + +#define AD8460_QUIESCENT_CURRENT_MSK GENMASK(7, 0) + +#define AD8460_SHUTDOWN_FLAG_MSK BIT(7) + +#define AD8460_DATA_BYTE_LOW_MSK GENMASK(7, 0) +#define AD8460_DATA_BYTE_HIGH_MSK GENMASK(5, 0) +#define AD8460_DATA_BYTE_FULL_MSK GENMASK(13, 0) + +#define AD8460_DEFAULT_FAULT_PROTECT 0x00 +#define AD8460_DATA_BYTE_WORD_LENGTH 2 +#define AD8460_NUM_DATA_WORDS 16 +#define AD8460_NOMINAL_VOLTAGE_SPAN 80 +#define AD8460_MIN_EXT_RESISTOR_OHMS 2000 +#define AD8460_MAX_EXT_RESISTOR_OHMS 20000 +#define AD8460_MIN_VREFIO_UV 120000 +#define AD8460_MAX_VREFIO_UV 1200000 +#define AD8460_ABS_MAX_OVERVOLTAGE_UV 55000000 +#define AD8460_ABS_MAX_OVERCURRENT_UA 1000000 +#define AD8460_MAX_OVERTEMPERATURE_MC 150000 +#define AD8460_MIN_OVERTEMPERATURE_MC 20000 +#define AD8460_CURRENT_LIMIT_CONV(x) ((x) / 15625) +#define AD8460_VOLTAGE_LIMIT_CONV(x) ((x) / 1953000) +#define AD8460_TEMP_LIMIT_CONV(x) (((x) + 266640) / 6510) + +enum ad8460_fault_type { + AD8460_OVERCURRENT_SRC, + AD8460_OVERCURRENT_SNK, + AD8460_OVERVOLTAGE_POS, + AD8460_OVERVOLTAGE_NEG, + AD8460_OVERTEMPERATURE, +}; + +struct ad8460_state { + struct spi_device *spi; + struct regmap *regmap; + struct iio_channel *tmp_adc_channel; + struct clk *sync_clk; + /* lock to protect against multiple access to the device and shared data */ + struct mutex lock; + int refio_1p2v_mv; + u32 ext_resistor_ohms; + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + __le16 spi_tx_buf __aligned(IIO_DMA_MINALIGN); +}; + +static int ad8460_hv_reset(struct ad8460_state *state) +{ + int ret; + + ret = regmap_set_bits(state->regmap, AD8460_CTRL_REG(0x00), + AD8460_HV_RESET_MSK); + if (ret) + return ret; + + fsleep(20); + + return regmap_clear_bits(state->regmap, AD8460_CTRL_REG(0x00), + AD8460_HV_RESET_MSK); +} + +static int ad8460_reset(const struct ad8460_state *state) +{ + struct device *dev = &state->spi->dev; + struct gpio_desc *reset; + + reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(reset)) + return dev_err_probe(dev, PTR_ERR(reset), + "Failed to get reset gpio"); + if (reset) { + /* minimum duration of 10ns */ + ndelay(10); + gpiod_set_value_cansleep(reset, 1); + return 0; + } + + /* bring all registers to their default state */ + return regmap_write(state->regmap, AD8460_CTRL_REG(0x03), 1); +} + +static int ad8460_enable_apg_mode(struct ad8460_state *state, int val) +{ + int ret; + + ret = regmap_update_bits(state->regmap, AD8460_CTRL_REG(0x02), + AD8460_APG_MODE_ENABLE_MSK, + FIELD_PREP(AD8460_APG_MODE_ENABLE_MSK, val)); + if (ret) + return ret; + + return regmap_update_bits(state->regmap, AD8460_CTRL_REG(0x00), + AD8460_WAVE_GEN_MODE_MSK, + FIELD_PREP(AD8460_WAVE_GEN_MODE_MSK, val)); +} + +static int ad8460_read_shutdown_flag(struct ad8460_state *state, u64 *flag) +{ + int ret, val; + + ret = regmap_read(state->regmap, AD8460_CTRL_REG(0x0E), &val); + if (ret) + return ret; + + *flag = FIELD_GET(AD8460_SHUTDOWN_FLAG_MSK, val); + return 0; +} + +static int ad8460_get_hvdac_word(struct ad8460_state *state, int index, int *val) +{ + int ret; + + ret = regmap_bulk_read(state->regmap, AD8460_HVDAC_DATA_WORD(index), + &state->spi_tx_buf, AD8460_DATA_BYTE_WORD_LENGTH); + if (ret) + return ret; + + *val = le16_to_cpu(state->spi_tx_buf); + + return ret; +} + +static int ad8460_set_hvdac_word(struct ad8460_state *state, int index, int val) +{ + state->spi_tx_buf = cpu_to_le16(FIELD_PREP(AD8460_DATA_BYTE_FULL_MSK, val)); + + return regmap_bulk_write(state->regmap, AD8460_HVDAC_DATA_WORD(index), + &state->spi_tx_buf, AD8460_DATA_BYTE_WORD_LENGTH); +} + +static ssize_t ad8460_dac_input_read(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *chan, char *buf) +{ + struct ad8460_state *state = iio_priv(indio_dev); + unsigned int reg; + int ret; + + ret = ad8460_get_hvdac_word(state, private, ®); + if (ret) + return ret; + + return sysfs_emit(buf, "%u\n", reg); +} + +static ssize_t ad8460_dac_input_write(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ad8460_state *state = iio_priv(indio_dev); + unsigned int reg; + int ret; + + ret = kstrtou32(buf, 10, ®); + if (ret) + return ret; + + guard(mutex)(&state->lock); + + return ad8460_set_hvdac_word(state, private, reg); +} + +static ssize_t ad8460_read_symbol(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *chan, char *buf) +{ + struct ad8460_state *state = iio_priv(indio_dev); + unsigned int reg; + int ret; + + ret = regmap_read(state->regmap, AD8460_CTRL_REG(0x02), ®); + if (ret) + return ret; + + return sysfs_emit(buf, "%lu\n", FIELD_GET(AD8460_PATTERN_DEPTH_MSK, reg)); +} + +static ssize_t ad8460_write_symbol(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ad8460_state *state = iio_priv(indio_dev); + uint16_t sym; + int ret; + + ret = kstrtou16(buf, 10, &sym); + if (ret) + return ret; + + guard(mutex)(&state->lock); + + return regmap_update_bits(state->regmap, + AD8460_CTRL_REG(0x02), + AD8460_PATTERN_DEPTH_MSK, + FIELD_PREP(AD8460_PATTERN_DEPTH_MSK, sym)); +} + +static ssize_t ad8460_read_toggle_en(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *chan, char *buf) +{ + struct ad8460_state *state = iio_priv(indio_dev); + unsigned int reg; + int ret; + + ret = regmap_read(state->regmap, AD8460_CTRL_REG(0x02), ®); + if (ret) + return ret; + + return sysfs_emit(buf, "%ld\n", FIELD_GET(AD8460_APG_MODE_ENABLE_MSK, reg)); +} + +static ssize_t ad8460_write_toggle_en(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ad8460_state *state = iio_priv(indio_dev); + bool toggle_en; + int ret; + + ret = kstrtobool(buf, &toggle_en); + if (ret) + return ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad8460_enable_apg_mode(state, toggle_en); + iio_device_release_direct(indio_dev); + return ret; +} + +static ssize_t ad8460_read_powerdown(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *chan, char *buf) +{ + struct ad8460_state *state = iio_priv(indio_dev); + unsigned int reg; + int ret; + + ret = regmap_read(state->regmap, AD8460_CTRL_REG(0x01), ®); + if (ret) + return ret; + + return sysfs_emit(buf, "%ld\n", FIELD_GET(AD8460_HVDAC_SLEEP_MSK, reg)); +} + +static ssize_t ad8460_write_powerdown(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ad8460_state *state = iio_priv(indio_dev); + bool pwr_down; + u64 sdn_flag; + int ret; + + ret = kstrtobool(buf, &pwr_down); + if (ret) + return ret; + + guard(mutex)(&state->lock); + + /* + * If powerdown is set, HVDAC is enabled and the HV driver is + * enabled via HV_RESET in case it is in shutdown mode, + * If powerdown is cleared, HVDAC is set to shutdown state + * as well as the HV driver. Quiescent current decreases and ouput is + * floating (high impedance). + */ + + ret = regmap_update_bits(state->regmap, AD8460_CTRL_REG(0x01), + AD8460_HVDAC_SLEEP_MSK, + FIELD_PREP(AD8460_HVDAC_SLEEP_MSK, pwr_down)); + if (ret) + return ret; + + if (!pwr_down) { + ret = ad8460_read_shutdown_flag(state, &sdn_flag); + if (ret) + return ret; + + if (sdn_flag) { + ret = ad8460_hv_reset(state); + if (ret) + return ret; + } + } + + ret = regmap_update_bits(state->regmap, AD8460_CTRL_REG(0x00), + AD8460_HV_SLEEP_MSK, + FIELD_PREP(AD8460_HV_SLEEP_MSK, !pwr_down)); + if (ret) + return ret; + + return len; +} + +static const char * const ad8460_powerdown_modes[] = { + "three_state", +}; + +static int ad8460_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + return 0; +} + +static int ad8460_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int type) +{ + return 0; +} + +static int ad8460_set_sample(struct ad8460_state *state, int val) +{ + int ret; + + ret = ad8460_enable_apg_mode(state, 1); + if (ret) + return ret; + + guard(mutex)(&state->lock); + ret = ad8460_set_hvdac_word(state, 0, val); + if (ret) + return ret; + + return regmap_update_bits(state->regmap, AD8460_CTRL_REG(0x02), + AD8460_PATTERN_DEPTH_MSK, + FIELD_PREP(AD8460_PATTERN_DEPTH_MSK, 0)); +} + +static int ad8460_set_fault_threshold(struct ad8460_state *state, + enum ad8460_fault_type fault, + unsigned int threshold) +{ + return regmap_update_bits(state->regmap, AD8460_CTRL_REG(0x08 + fault), + AD8460_FAULT_LIMIT_MSK, + FIELD_PREP(AD8460_FAULT_LIMIT_MSK, threshold)); +} + +static int ad8460_get_fault_threshold(struct ad8460_state *state, + enum ad8460_fault_type fault, + unsigned int *threshold) +{ + unsigned int val; + int ret; + + ret = regmap_read(state->regmap, AD8460_CTRL_REG(0x08 + fault), &val); + if (ret) + return ret; + + *threshold = FIELD_GET(AD8460_FAULT_LIMIT_MSK, val); + + return ret; +} + +static int ad8460_set_fault_threshold_en(struct ad8460_state *state, + enum ad8460_fault_type fault, bool en) +{ + return regmap_update_bits(state->regmap, AD8460_CTRL_REG(0x08 + fault), + AD8460_FAULT_ARM_MSK, + FIELD_PREP(AD8460_FAULT_ARM_MSK, en)); +} + +static int ad8460_get_fault_threshold_en(struct ad8460_state *state, + enum ad8460_fault_type fault, bool *en) +{ + unsigned int val; + int ret; + + ret = regmap_read(state->regmap, AD8460_CTRL_REG(0x08 + fault), &val); + if (ret) + return ret; + + *en = FIELD_GET(AD8460_FAULT_ARM_MSK, val); + + return 0; +} + +static int ad8460_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, + long mask) +{ + struct ad8460_state *state = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_VOLTAGE: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = ad8460_set_sample(state, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CURRENT: + return regmap_write(state->regmap, AD8460_CTRL_REG(0x04), + FIELD_PREP(AD8460_QUIESCENT_CURRENT_MSK, val)); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ad8460_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ad8460_state *state = iio_priv(indio_dev); + int data, ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_VOLTAGE: + scoped_guard(mutex, &state->lock) { + ret = ad8460_get_hvdac_word(state, 0, &data); + if (ret) + return ret; + } + *val = data; + return IIO_VAL_INT; + case IIO_CURRENT: + ret = regmap_read(state->regmap, AD8460_CTRL_REG(0x04), + &data); + if (ret) + return ret; + *val = data; + return IIO_VAL_INT; + case IIO_TEMP: + ret = iio_read_channel_raw(state->tmp_adc_channel, &data); + if (ret) + return ret; + *val = data; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + *val = clk_get_rate(state->sync_clk); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* + * vCONV = vNOMINAL_SPAN * (DAC_CODE / 2**14) - 40V + * vMAX = vNOMINAL_SPAN * (2**14 / 2**14) - 40V + * vMIN = vNOMINAL_SPAN * (0 / 2**14) - 40V + * vADJ = vCONV * (2000 / rSET) * (vREF / 1.2) + * vSPAN = vADJ_MAX - vADJ_MIN + * See datasheet page 49, section FULL-SCALE REDUCTION + */ + *val = AD8460_NOMINAL_VOLTAGE_SPAN * 2000 * state->refio_1p2v_mv; + *val2 = state->ext_resistor_ohms * 1200; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } +} + +static int ad8460_select_fault_type(int chan_type, enum iio_event_direction dir) +{ + switch (chan_type) { + case IIO_VOLTAGE: + switch (dir) { + case IIO_EV_DIR_RISING: + return AD8460_OVERVOLTAGE_POS; + case IIO_EV_DIR_FALLING: + return AD8460_OVERVOLTAGE_NEG; + default: + return -EINVAL; + } + case IIO_CURRENT: + switch (dir) { + case IIO_EV_DIR_RISING: + return AD8460_OVERCURRENT_SRC; + case IIO_EV_DIR_FALLING: + return AD8460_OVERCURRENT_SNK; + default: + return -EINVAL; + } + case IIO_TEMP: + switch (dir) { + case IIO_EV_DIR_RISING: + return AD8460_OVERTEMPERATURE; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ad8460_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, int val2) +{ + struct ad8460_state *state = iio_priv(indio_dev); + int fault; + + if (type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + if (info != IIO_EV_INFO_VALUE) + return -EINVAL; + + fault = ad8460_select_fault_type(chan->type, dir); + if (fault < 0) + return fault; + + return ad8460_set_fault_threshold(state, fault, val); +} + +static int ad8460_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, int *val2) +{ + struct ad8460_state *state = iio_priv(indio_dev); + int fault; + + if (type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + if (info != IIO_EV_INFO_VALUE) + return -EINVAL; + + fault = ad8460_select_fault_type(chan->type, dir); + if (fault < 0) + return fault; + + return ad8460_get_fault_threshold(state, fault, val); +} + +static int ad8460_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, bool val) +{ + struct ad8460_state *state = iio_priv(indio_dev); + int fault; + + if (type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + fault = ad8460_select_fault_type(chan->type, dir); + if (fault < 0) + return fault; + + return ad8460_set_fault_threshold_en(state, fault, val); +} + +static int ad8460_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct ad8460_state *state = iio_priv(indio_dev); + int fault, ret; + bool en; + + if (type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + fault = ad8460_select_fault_type(chan->type, dir); + if (fault < 0) + return fault; + + ret = ad8460_get_fault_threshold_en(state, fault, &en); + if (ret) + return ret; + + return en; +} + +static int ad8460_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct ad8460_state *state = iio_priv(indio_dev); + + if (readval) + return regmap_read(state->regmap, reg, readval); + + return regmap_write(state->regmap, reg, writeval); +} + +static int ad8460_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ad8460_state *state = iio_priv(indio_dev); + + return ad8460_enable_apg_mode(state, 0); +} + +static int ad8460_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ad8460_state *state = iio_priv(indio_dev); + + return ad8460_enable_apg_mode(state, 1); +} + +static const struct iio_buffer_setup_ops ad8460_buffer_setup_ops = { + .preenable = &ad8460_buffer_preenable, + .postdisable = &ad8460_buffer_postdisable, +}; + +static const struct iio_info ad8460_info = { + .read_raw = &ad8460_read_raw, + .write_raw = &ad8460_write_raw, + .write_event_value = &ad8460_write_event_value, + .read_event_value = &ad8460_read_event_value, + .write_event_config = &ad8460_write_event_config, + .read_event_config = &ad8460_read_event_config, + .debugfs_reg_access = &ad8460_reg_access, +}; + +static const struct iio_enum ad8460_powerdown_mode_enum = { + .items = ad8460_powerdown_modes, + .num_items = ARRAY_SIZE(ad8460_powerdown_modes), + .get = ad8460_get_powerdown_mode, + .set = ad8460_set_powerdown_mode, +}; + +#define AD8460_CHAN_EXT_INFO(_name, _what, _read, _write) { \ + .name = (_name), \ + .read = (_read), \ + .write = (_write), \ + .private = (_what), \ + .shared = IIO_SEPARATE, \ +} + +static const struct iio_chan_spec_ext_info ad8460_ext_info[] = { + AD8460_CHAN_EXT_INFO("raw0", 0, ad8460_dac_input_read, + ad8460_dac_input_write), + AD8460_CHAN_EXT_INFO("raw1", 1, ad8460_dac_input_read, + ad8460_dac_input_write), + AD8460_CHAN_EXT_INFO("raw2", 2, ad8460_dac_input_read, + ad8460_dac_input_write), + AD8460_CHAN_EXT_INFO("raw3", 3, ad8460_dac_input_read, + ad8460_dac_input_write), + AD8460_CHAN_EXT_INFO("raw4", 4, ad8460_dac_input_read, + ad8460_dac_input_write), + AD8460_CHAN_EXT_INFO("raw5", 5, ad8460_dac_input_read, + ad8460_dac_input_write), + AD8460_CHAN_EXT_INFO("raw6", 6, ad8460_dac_input_read, + ad8460_dac_input_write), + AD8460_CHAN_EXT_INFO("raw7", 7, ad8460_dac_input_read, + ad8460_dac_input_write), + AD8460_CHAN_EXT_INFO("raw8", 8, ad8460_dac_input_read, + ad8460_dac_input_write), + AD8460_CHAN_EXT_INFO("raw9", 9, ad8460_dac_input_read, + ad8460_dac_input_write), + AD8460_CHAN_EXT_INFO("raw10", 10, ad8460_dac_input_read, + ad8460_dac_input_write), + AD8460_CHAN_EXT_INFO("raw11", 11, ad8460_dac_input_read, + ad8460_dac_input_write), + AD8460_CHAN_EXT_INFO("raw12", 12, ad8460_dac_input_read, + ad8460_dac_input_write), + AD8460_CHAN_EXT_INFO("raw13", 13, ad8460_dac_input_read, + ad8460_dac_input_write), + AD8460_CHAN_EXT_INFO("raw14", 14, ad8460_dac_input_read, + ad8460_dac_input_write), + AD8460_CHAN_EXT_INFO("raw15", 15, ad8460_dac_input_read, + ad8460_dac_input_write), + AD8460_CHAN_EXT_INFO("toggle_en", 0, ad8460_read_toggle_en, + ad8460_write_toggle_en), + AD8460_CHAN_EXT_INFO("symbol", 0, ad8460_read_symbol, + ad8460_write_symbol), + AD8460_CHAN_EXT_INFO("powerdown", 0, ad8460_read_powerdown, + ad8460_write_powerdown), + IIO_ENUM("powerdown_mode", IIO_SEPARATE, &ad8460_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, + &ad8460_powerdown_mode_enum), + { } +}; + +static const struct iio_event_spec ad8460_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +#define AD8460_VOLTAGE_CHAN { \ + .type = IIO_VOLTAGE, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .output = 1, \ + .indexed = 1, \ + .channel = 0, \ + .scan_index = 0, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 14, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ + .ext_info = ad8460_ext_info, \ + .event_spec = ad8460_events, \ + .num_event_specs = ARRAY_SIZE(ad8460_events), \ +} + +#define AD8460_CURRENT_CHAN { \ + .type = IIO_CURRENT, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .output = 1, \ + .indexed = 1, \ + .channel = 0, \ + .scan_index = -1, \ + .event_spec = ad8460_events, \ + .num_event_specs = ARRAY_SIZE(ad8460_events), \ +} + +#define AD8460_TEMP_CHAN { \ + .type = IIO_TEMP, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .indexed = 1, \ + .channel = 0, \ + .scan_index = -1, \ + .event_spec = ad8460_events, \ + .num_event_specs = 1, \ +} + +static const struct iio_chan_spec ad8460_channels[] = { + AD8460_VOLTAGE_CHAN, + AD8460_CURRENT_CHAN, +}; + +static const struct iio_chan_spec ad8460_channels_with_tmp_adc[] = { + AD8460_VOLTAGE_CHAN, + AD8460_CURRENT_CHAN, + AD8460_TEMP_CHAN, +}; + +static const struct regmap_config ad8460_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x7F, +}; + +static const char * const ad8460_supplies[] = { + "avdd_3p3v", "dvdd_3p3v", "vcc_5v", "hvcc", "hvee", "vref_5v" +}; + +static int ad8460_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct ad8460_state *state; + struct iio_dev *indio_dev; + u32 tmp[2], temp; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*state)); + if (!indio_dev) + return -ENOMEM; + + state = iio_priv(indio_dev); + + indio_dev->name = "ad8460"; + indio_dev->info = &ad8460_info; + + state->spi = spi; + + state->regmap = devm_regmap_init_spi(spi, &ad8460_regmap_config); + if (IS_ERR(state->regmap)) + return dev_err_probe(dev, PTR_ERR(state->regmap), + "Failed to initialize regmap"); + + ret = devm_mutex_init(dev, &state->lock); + if (ret) + return ret; + + state->sync_clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(state->sync_clk)) + return dev_err_probe(dev, PTR_ERR(state->sync_clk), + "Failed to get sync clk\n"); + + state->tmp_adc_channel = devm_iio_channel_get(dev, "ad8460-tmp"); + if (IS_ERR(state->tmp_adc_channel)) { + if (PTR_ERR(state->tmp_adc_channel) == -EPROBE_DEFER) + return -EPROBE_DEFER; + indio_dev->channels = ad8460_channels; + indio_dev->num_channels = ARRAY_SIZE(ad8460_channels); + } else { + indio_dev->channels = ad8460_channels_with_tmp_adc; + indio_dev->num_channels = ARRAY_SIZE(ad8460_channels_with_tmp_adc); + } + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(ad8460_supplies), + ad8460_supplies); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable power supplies\n"); + + ret = devm_regulator_get_enable_read_voltage(dev, "refio_1p2v"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "Failed to get reference voltage\n"); + + state->refio_1p2v_mv = ret == -ENODEV ? 1200 : ret / 1000; + + if (!in_range(state->refio_1p2v_mv, AD8460_MIN_VREFIO_UV / 1000, + AD8460_MAX_VREFIO_UV / 1000)) + return dev_err_probe(dev, -EINVAL, + "Invalid ref voltage range(%u mV) [%u mV, %u mV]\n", + state->refio_1p2v_mv, + AD8460_MIN_VREFIO_UV / 1000, + AD8460_MAX_VREFIO_UV / 1000); + + ret = device_property_read_u32(dev, "adi,external-resistor-ohms", + &state->ext_resistor_ohms); + if (ret) + state->ext_resistor_ohms = 2000; + else if (!in_range(state->ext_resistor_ohms, AD8460_MIN_EXT_RESISTOR_OHMS, + AD8460_MAX_EXT_RESISTOR_OHMS)) + return dev_err_probe(dev, -EINVAL, + "Invalid resistor set range(%u) [%u, %u]\n", + state->ext_resistor_ohms, + AD8460_MIN_EXT_RESISTOR_OHMS, + AD8460_MAX_EXT_RESISTOR_OHMS); + + ret = device_property_read_u32_array(dev, "adi,range-microamp", + tmp, ARRAY_SIZE(tmp)); + if (!ret) { + if (in_range(tmp[1], 0, AD8460_ABS_MAX_OVERCURRENT_UA)) + regmap_write(state->regmap, AD8460_CTRL_REG(0x08), + FIELD_PREP(AD8460_FAULT_ARM_MSK, 1) | + AD8460_CURRENT_LIMIT_CONV(tmp[1])); + + if (in_range(tmp[0], -AD8460_ABS_MAX_OVERCURRENT_UA, 0)) + regmap_write(state->regmap, AD8460_CTRL_REG(0x09), + FIELD_PREP(AD8460_FAULT_ARM_MSK, 1) | + AD8460_CURRENT_LIMIT_CONV(abs(tmp[0]))); + } + + ret = device_property_read_u32_array(dev, "adi,range-microvolt", + tmp, ARRAY_SIZE(tmp)); + if (!ret) { + if (in_range(tmp[1], 0, AD8460_ABS_MAX_OVERVOLTAGE_UV)) + regmap_write(state->regmap, AD8460_CTRL_REG(0x0A), + FIELD_PREP(AD8460_FAULT_ARM_MSK, 1) | + AD8460_VOLTAGE_LIMIT_CONV(tmp[1])); + + if (in_range(tmp[0], -AD8460_ABS_MAX_OVERVOLTAGE_UV, 0)) + regmap_write(state->regmap, AD8460_CTRL_REG(0x0B), + FIELD_PREP(AD8460_FAULT_ARM_MSK, 1) | + AD8460_VOLTAGE_LIMIT_CONV(abs(tmp[0]))); + } + + ret = device_property_read_u32(dev, "adi,max-millicelsius", &temp); + if (!ret) { + if (in_range(temp, AD8460_MIN_OVERTEMPERATURE_MC, + AD8460_MAX_OVERTEMPERATURE_MC)) + regmap_write(state->regmap, AD8460_CTRL_REG(0x0C), + FIELD_PREP(AD8460_FAULT_ARM_MSK, 1) | + AD8460_TEMP_LIMIT_CONV(abs(temp))); + } + + ret = ad8460_reset(state); + if (ret) + return ret; + + /* Enables DAC by default */ + ret = regmap_clear_bits(state->regmap, AD8460_CTRL_REG(0x01), + AD8460_HVDAC_SLEEP_MSK); + if (ret) + return ret; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->setup_ops = &ad8460_buffer_setup_ops; + + ret = devm_iio_dmaengine_buffer_setup_ext(dev, indio_dev, "tx", + IIO_BUFFER_DIRECTION_OUT); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get DMA buffer\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ad8460_of_match[] = { + { .compatible = "adi,ad8460" }, + { } +}; +MODULE_DEVICE_TABLE(of, ad8460_of_match); + +static const struct spi_device_id ad8460_spi_match[] = { + { .name = "ad8460" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad8460_spi_match); + +static struct spi_driver ad8460_driver = { + .driver = { + .name = "ad8460", + .of_match_table = ad8460_of_match, + }, + .probe = ad8460_probe, + .id_table = ad8460_spi_match, +}; +module_spi_driver(ad8460_driver); + +MODULE_AUTHOR("Mariel Tinaco +#include +#include +#include +#include + +#define AD8801_CFG_ADDR_OFFSET 8 + +enum ad8801_device_ids { + ID_AD8801, + ID_AD8803, +}; + +struct ad8801_state { + struct spi_device *spi; + unsigned char dac_cache[8]; /* Value write on each channel */ + unsigned int vrefh_mv; + unsigned int vrefl_mv; + + __be16 data __aligned(IIO_DMA_MINALIGN); +}; + +static int ad8801_spi_write(struct ad8801_state *state, + u8 channel, unsigned char value) +{ + state->data = cpu_to_be16((channel << AD8801_CFG_ADDR_OFFSET) | value); + return spi_write(state->spi, &state->data, sizeof(state->data)); +} + +static int ad8801_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + struct ad8801_state *state = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val >= 256 || val < 0) + return -EINVAL; + + ret = ad8801_spi_write(state, chan->channel, val); + if (ret == 0) + state->dac_cache[chan->channel] = val; + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int ad8801_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long info) +{ + struct ad8801_state *state = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_RAW: + *val = state->dac_cache[chan->channel]; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = state->vrefh_mv - state->vrefl_mv; + *val2 = 8; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: + *val = state->vrefl_mv; + return IIO_VAL_INT; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static const struct iio_info ad8801_info = { + .read_raw = ad8801_read_raw, + .write_raw = ad8801_write_raw, +}; + +#define AD8801_CHANNEL(chan) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ +} + +static const struct iio_chan_spec ad8801_channels[] = { + AD8801_CHANNEL(0), + AD8801_CHANNEL(1), + AD8801_CHANNEL(2), + AD8801_CHANNEL(3), + AD8801_CHANNEL(4), + AD8801_CHANNEL(5), + AD8801_CHANNEL(6), + AD8801_CHANNEL(7), +}; + +static int ad8801_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct ad8801_state *state; + const struct spi_device_id *id; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*state)); + if (indio_dev == NULL) + return -ENOMEM; + + state = iio_priv(indio_dev); + state->spi = spi; + id = spi_get_device_id(spi); + + ret = devm_regulator_get_enable_read_voltage(&spi->dev, "vrefh"); + if (ret < 0) + return dev_err_probe(&spi->dev, ret, + "failed to get Vrefh voltage\n"); + + state->vrefh_mv = ret / 1000; + + if (id->driver_data == ID_AD8803) { + ret = devm_regulator_get_enable_read_voltage(&spi->dev, "vrefl"); + if (ret < 0) + return dev_err_probe(&spi->dev, ret, + "failed to get Vrefl voltage\n"); + + state->vrefl_mv = ret / 1000; + } + + indio_dev->info = &ad8801_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad8801_channels; + indio_dev->num_channels = ARRAY_SIZE(ad8801_channels); + indio_dev->name = id->name; + + ret = devm_iio_device_register(&spi->dev, indio_dev); + if (ret) + return dev_err_probe(&spi->dev, ret, + "Failed to register iio device\n"); + + return 0; +} + +static const struct spi_device_id ad8801_ids[] = { + { .name = "ad8801", .driver_data = ID_AD8801 }, + { .name = "ad8803", .driver_data = ID_AD8803 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad8801_ids); + +static struct spi_driver ad8801_driver = { + .driver = { + .name = "ad8801", + }, + .probe = ad8801_probe, + .id_table = ad8801_ids, +}; +module_spi_driver(ad8801_driver); + +MODULE_AUTHOR("Gwenhael Goavec-Merou "); +MODULE_DESCRIPTION("Analog Devices AD8801/AD8803 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad9739a.c b/drivers/iio/dac/ad9739a.c new file mode 100644 index 000000000..5263a0c96 --- /dev/null +++ b/drivers/iio/dac/ad9739a.c @@ -0,0 +1,468 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices AD9739a SPI DAC driver + * + * Copyright 2015-2024 Analog Devices Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define AD9739A_REG_MODE 0 +#define AD9739A_RESET_MASK BIT(5) +#define AD9739A_REG_FSC_1 0x06 +#define AD9739A_REG_FSC_2 0x07 +#define AD9739A_FSC_MSB GENMASK(1, 0) +#define AD9739A_REG_DEC_CNT 0x8 +#define AD9739A_NORMAL_MODE 0 +#define AD9739A_MIXED_MODE 2 +#define AD9739A_DAC_DEC GENMASK(1, 0) +#define AD9739A_REG_LVDS_REC_CNT1 0x10 +#define AD9739A_RCVR_LOOP_EN_MASK GENMASK(1, 0) +#define AD9739A_REG_LVDS_REC_CNT4 0x13 +#define AD9739A_FINE_DEL_SKW_MASK GENMASK(3, 0) +#define AD9739A_REG_LVDS_REC_STAT9 0x21 +#define AD9739A_RCVR_TRACK_AND_LOCK (BIT(3) | BIT(0)) +#define AD9739A_REG_CROSS_CNT1 0x22 +#define AD9739A_REG_CROSS_CNT2 0x23 +#define AD9739A_REG_PHS_DET 0x24 +#define AD9739A_REG_MU_DUTY 0x25 +#define AD9739A_REG_MU_CNT1 0x26 +#define AD9739A_MU_EN_MASK BIT(0) +#define AD9739A_MU_GAIN_MASK BIT(1) +#define AD9739A_REG_MU_CNT2 0x27 +#define AD9739A_REG_MU_CNT3 0x28 +#define AD9739A_REG_MU_CNT4 0x29 +#define AD9739A_MU_CNT4_DEFAULT 0xcb +#define AD9739A_REG_MU_STAT1 0x2A +#define AD9739A_MU_LOCK_MASK BIT(0) +#define AD9739A_REG_ANA_CNT_1 0x32 +#define AD9739A_REG_ID 0x35 + +#define AD9739A_ID 0x24 +#define AD9739A_REG_IS_RESERVED(reg) \ + ((reg) == 0x5 || (reg) == 0x9 || (reg) == 0x0E || (reg) == 0x0D || \ + (reg) == 0x2B || (reg) == 0x2C || (reg) == 0x34) + +#define AD9739A_FSC_MIN 8580 +#define AD9739A_FSC_MAX 31700 +#define AD9739A_FSC_RANGE (AD9739A_FSC_MAX - AD9739A_FSC_MIN + 1) + +#define AD9739A_MIN_DAC_CLK (1600 * MEGA) +#define AD9739A_MAX_DAC_CLK (2500 * MEGA) +#define AD9739A_DAC_CLK_RANGE (AD9739A_MAX_DAC_CLK - AD9739A_MIN_DAC_CLK + 1) +/* as recommended by the datasheet */ +#define AD9739A_LOCK_N_TRIES 3 + +struct ad9739a_state { + struct iio_backend *back; + struct regmap *regmap; + unsigned long sample_rate; +}; + +static int ad9739a_oper_mode_get(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad9739a_state *st = iio_priv(indio_dev); + u32 mode; + int ret; + + ret = regmap_read(st->regmap, AD9739A_REG_DEC_CNT, &mode); + if (ret) + return ret; + + mode = FIELD_GET(AD9739A_DAC_DEC, mode); + /* sanity check we get valid values from the HW */ + if (mode != AD9739A_NORMAL_MODE && mode != AD9739A_MIXED_MODE) + return -EIO; + if (!mode) + return AD9739A_NORMAL_MODE; + + /* + * We get 2 from the device but for IIO modes, that means 1. Hence the + * minus 1. + */ + return AD9739A_MIXED_MODE - 1; +} + +static int ad9739a_oper_mode_set(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, u32 mode) +{ + struct ad9739a_state *st = iio_priv(indio_dev); + + /* + * On the IIO interface we have 0 and 1 for mode. But for mixed_mode, we + * need to write 2 in the device. That's what the below check is about. + */ + if (mode == AD9739A_MIXED_MODE - 1) + mode = AD9739A_MIXED_MODE; + + return regmap_update_bits(st->regmap, AD9739A_REG_DEC_CNT, + AD9739A_DAC_DEC, mode); +} + +static int ad9739a_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ad9739a_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *val = st->sample_rate; + *val2 = 0; + return IIO_VAL_INT_64; + default: + return -EINVAL; + } +} + +static int ad9739a_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ad9739a_state *st = iio_priv(indio_dev); + + return iio_backend_data_source_set(st->back, 0, IIO_BACKEND_EXTERNAL); +} + +static int ad9739a_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ad9739a_state *st = iio_priv(indio_dev); + + return iio_backend_data_source_set(st->back, 0, + IIO_BACKEND_INTERNAL_CONTINUOUS_WAVE); +} + +static bool ad9739a_reg_accessible(struct device *dev, unsigned int reg) +{ + if (AD9739A_REG_IS_RESERVED(reg)) + return false; + if (reg > AD9739A_REG_MU_STAT1 && reg < AD9739A_REG_ANA_CNT_1) + return false; + + return true; +} + +static int ad9739a_reset(struct device *dev, const struct ad9739a_state *st) +{ + struct gpio_desc *gpio; + int ret; + + gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(gpio)) + return PTR_ERR(gpio); + if (gpio) { + /* minimum pulse width of 40ns */ + ndelay(40); + gpiod_set_value_cansleep(gpio, 0); + return 0; + } + + /* bring all registers to their default state */ + ret = regmap_set_bits(st->regmap, AD9739A_REG_MODE, AD9739A_RESET_MASK); + if (ret) + return ret; + + ndelay(40); + + return regmap_clear_bits(st->regmap, AD9739A_REG_MODE, + AD9739A_RESET_MASK); +} + +/* + * Recommended values (as per datasheet) for the dac clk common mode voltage + * and Mu controller. Look at table 29. + */ +static const struct reg_sequence ad9739a_clk_mu_ctrl[] = { + /* DAC clk common mode voltage */ + { AD9739A_REG_CROSS_CNT1, 0x0f }, + { AD9739A_REG_CROSS_CNT2, 0x0f }, + /* Mu controller configuration */ + { AD9739A_REG_PHS_DET, 0x30 }, + { AD9739A_REG_MU_DUTY, 0x80 }, + { AD9739A_REG_MU_CNT2, 0x44 }, + { AD9739A_REG_MU_CNT3, 0x6c }, +}; + +static int ad9739a_init(struct device *dev, const struct ad9739a_state *st) +{ + unsigned int i = 0, lock, fsc; + u32 fsc_raw; + int ret; + + ret = regmap_multi_reg_write(st->regmap, ad9739a_clk_mu_ctrl, + ARRAY_SIZE(ad9739a_clk_mu_ctrl)); + if (ret) + return ret; + + /* + * Try to get the Mu lock. Repeat the below steps AD9739A_LOCK_N_TRIES + * (as specified by the datasheet) until we get the lock. + */ + do { + ret = regmap_write(st->regmap, AD9739A_REG_MU_CNT4, + AD9739A_MU_CNT4_DEFAULT); + if (ret) + return ret; + + /* Enable the Mu controller search and track mode. */ + ret = regmap_write(st->regmap, AD9739A_REG_MU_CNT1, + AD9739A_MU_EN_MASK | AD9739A_MU_GAIN_MASK); + if (ret) + return ret; + + /* Ensure the DLL loop is locked */ + ret = regmap_read_poll_timeout(st->regmap, AD9739A_REG_MU_STAT1, + lock, lock & AD9739A_MU_LOCK_MASK, + 0, 1000); + if (ret && ret != -ETIMEDOUT) + return ret; + } while (ret && ++i < AD9739A_LOCK_N_TRIES); + + if (i == AD9739A_LOCK_N_TRIES) + return dev_err_probe(dev, ret, "Mu lock timeout\n"); + + /* Receiver tracking and lock. Same deal as the Mu controller */ + i = 0; + do { + ret = regmap_update_bits(st->regmap, AD9739A_REG_LVDS_REC_CNT4, + AD9739A_FINE_DEL_SKW_MASK, + FIELD_PREP(AD9739A_FINE_DEL_SKW_MASK, 2)); + if (ret) + return ret; + + /* Disable the receiver and the loop. */ + ret = regmap_write(st->regmap, AD9739A_REG_LVDS_REC_CNT1, 0); + if (ret) + return ret; + + /* + * Re-enable the loop so it falls out of lock and begins the + * search/track routine again. + */ + ret = regmap_set_bits(st->regmap, AD9739A_REG_LVDS_REC_CNT1, + AD9739A_RCVR_LOOP_EN_MASK); + if (ret) + return ret; + + /* Ensure the DLL loop is locked */ + ret = regmap_read_poll_timeout(st->regmap, + AD9739A_REG_LVDS_REC_STAT9, lock, + lock == AD9739A_RCVR_TRACK_AND_LOCK, + 0, 1000); + if (ret && ret != -ETIMEDOUT) + return ret; + } while (ret && ++i < AD9739A_LOCK_N_TRIES); + + if (i == AD9739A_LOCK_N_TRIES) + return dev_err_probe(dev, ret, "Receiver lock timeout\n"); + + ret = device_property_read_u32(dev, "adi,full-scale-microamp", &fsc); + if (ret && ret == -EINVAL) + return 0; + if (ret) + return ret; + if (!in_range(fsc, AD9739A_FSC_MIN, AD9739A_FSC_RANGE)) + return dev_err_probe(dev, -EINVAL, + "Invalid full scale current(%u) [%u %u]\n", + fsc, AD9739A_FSC_MIN, AD9739A_FSC_MAX); + /* + * IOUTFS is given by + * Ioutfs = 0.0226 * FSC + 8.58 + * and is given in mA. Hence we'll have to multiply by 10 * MILLI in + * order to get rid of the fractional. + */ + fsc_raw = DIV_ROUND_CLOSEST(fsc * 10 - 85800, 226); + + ret = regmap_write(st->regmap, AD9739A_REG_FSC_1, fsc_raw & 0xff); + if (ret) + return ret; + + return regmap_update_bits(st->regmap, AD9739A_REG_FSC_2, + AD9739A_FSC_MSB, fsc_raw >> 8); +} + +static const char * const ad9739a_modes_avail[] = { "normal", "mixed-mode" }; + +static const struct iio_enum ad9739a_modes = { + .items = ad9739a_modes_avail, + .num_items = ARRAY_SIZE(ad9739a_modes_avail), + .get = ad9739a_oper_mode_get, + .set = ad9739a_oper_mode_set, +}; + +static const struct iio_chan_spec_ext_info ad9739a_ext_info[] = { + IIO_ENUM_AVAILABLE("operating_mode", IIO_SEPARATE, &ad9739a_modes), + IIO_ENUM("operating_mode", IIO_SEPARATE, &ad9739a_modes), + { } +}; + +/* + * The reason for having two different channels is because we have, in reality, + * two sources of data: + * ALTVOLTAGE: It's a Continuous Wave that's internally generated by the + * backend device. + * VOLTAGE: It's the typical data we can have in a DAC device and the source + * of it has nothing to do with the backend. The backend will only + * forward it into our data interface to be sent out. + */ +static struct iio_chan_spec ad9739a_channels[] = { + { + .type = IIO_ALTVOLTAGE, + .indexed = 1, + .output = 1, + .scan_index = -1, + }, + { + .type = IIO_VOLTAGE, + .indexed = 1, + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .output = 1, + .ext_info = ad9739a_ext_info, + .scan_type = { + .sign = 's', + .storagebits = 16, + .realbits = 16, + }, + } +}; + +static const struct iio_info ad9739a_info = { + .read_raw = ad9739a_read_raw, +}; + +static const struct iio_buffer_setup_ops ad9739a_buffer_setup_ops = { + .preenable = &ad9739a_buffer_preenable, + .postdisable = &ad9739a_buffer_postdisable, +}; + +static const struct regmap_config ad9739a_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .readable_reg = ad9739a_reg_accessible, + .writeable_reg = ad9739a_reg_accessible, + .max_register = AD9739A_REG_ID, +}; + +static int ad9739a_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ad9739a_state *st; + unsigned int id; + struct clk *clk; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "Could not get clkin\n"); + + st->sample_rate = clk_get_rate(clk); + if (!in_range(st->sample_rate, AD9739A_MIN_DAC_CLK, + AD9739A_DAC_CLK_RANGE)) + return dev_err_probe(dev, -EINVAL, + "Invalid dac clk range(%lu) [%lu %lu]\n", + st->sample_rate, AD9739A_MIN_DAC_CLK, + AD9739A_MAX_DAC_CLK); + + st->regmap = devm_regmap_init_spi(spi, &ad9739a_regmap_config); + if (IS_ERR(st->regmap)) + return PTR_ERR(st->regmap); + + ret = regmap_read(st->regmap, AD9739A_REG_ID, &id); + if (ret) + return ret; + + if (id != AD9739A_ID) + dev_warn(dev, "Unrecognized CHIP_ID 0x%X", id); + + ret = ad9739a_reset(dev, st); + if (ret) + return ret; + + ret = ad9739a_init(dev, st); + if (ret) + return ret; + + st->back = devm_iio_backend_get(dev, NULL); + if (IS_ERR(st->back)) + return PTR_ERR(st->back); + + ret = devm_iio_backend_request_buffer(dev, st->back, indio_dev); + if (ret) + return ret; + + ret = iio_backend_extend_chan_spec(st->back, &ad9739a_channels[0]); + if (ret) + return ret; + + ret = iio_backend_set_sampling_freq(st->back, 0, st->sample_rate); + if (ret) + return ret; + + ret = devm_iio_backend_enable(dev, st->back); + if (ret) + return ret; + + indio_dev->name = "ad9739a"; + indio_dev->info = &ad9739a_info; + indio_dev->channels = ad9739a_channels; + indio_dev->num_channels = ARRAY_SIZE(ad9739a_channels); + indio_dev->setup_ops = &ad9739a_buffer_setup_ops; + + ret = devm_iio_device_register(&spi->dev, indio_dev); + if (ret) + return ret; + + iio_backend_debugfs_add(st->back, indio_dev); + + return 0; +} + +static const struct of_device_id ad9739a_of_match[] = { + { .compatible = "adi,ad9739a" }, + { } +}; +MODULE_DEVICE_TABLE(of, ad9739a_of_match); + +static const struct spi_device_id ad9739a_id[] = { + { .name = "ad9739a" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad9739a_id); + +static struct spi_driver ad9739a_driver = { + .driver = { + .name = "ad9739a", + .of_match_table = ad9739a_of_match, + }, + .probe = ad9739a_probe, + .id_table = ad9739a_id, +}; +module_spi_driver(ad9739a_driver); + +MODULE_AUTHOR("Dragos Bogdan "); +MODULE_AUTHOR("Nuno Sa "); +MODULE_DESCRIPTION("Analog Devices AD9739 DAC"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_BACKEND"); diff --git a/drivers/iio/dac/adi-axi-dac.c b/drivers/iio/dac/adi-axi-dac.c new file mode 100644 index 000000000..f4f3bc67c --- /dev/null +++ b/drivers/iio/dac/adi-axi-dac.c @@ -0,0 +1,1041 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices Generic AXI DAC IP core + * Link: https://wiki.analog.com/resources/fpga/docs/axi_dac_ip + * + * Copyright 2016-2024 Analog Devices Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ad3552r-hs.h" + +/* + * Register definitions: + * https://wiki.analog.com/resources/fpga/docs/axi_dac_ip#register_map + */ + +/* Base controls */ +#define AXI_DAC_CONFIG_REG 0x0c +#define AXI_DAC_CONFIG_DDS_DISABLE BIT(6) + + /* DAC controls */ +#define AXI_DAC_RSTN_REG 0x0040 +#define AXI_DAC_RSTN_CE_N BIT(2) +#define AXI_DAC_RSTN_MMCM_RSTN BIT(1) +#define AXI_DAC_RSTN_RSTN BIT(0) +#define AXI_DAC_CNTRL_1_REG 0x0044 +#define AXI_DAC_CNTRL_1_SYNC BIT(0) +#define AXI_DAC_CNTRL_2_REG 0x0048 +#define AXI_DAC_CNTRL_2_SDR_DDR_N BIT(16) +#define AXI_DAC_CNTRL_2_SYMB_8B BIT(14) +#define ADI_DAC_CNTRL_2_R1_MODE BIT(5) +#define AXI_DAC_CNTRL_2_UNSIGNED_DATA BIT(4) +#define AXI_DAC_STATUS_1_REG 0x0054 +#define AXI_DAC_STATUS_2_REG 0x0058 +#define AXI_DAC_DRP_STATUS_REG 0x0074 +#define AXI_DAC_DRP_STATUS_DRP_LOCKED BIT(17) +#define AXI_DAC_CUSTOM_RD_REG 0x0080 +#define AXI_DAC_CUSTOM_WR_REG 0x0084 +#define AXI_DAC_CUSTOM_WR_DATA_8 GENMASK(23, 16) +#define AXI_DAC_CUSTOM_WR_DATA_16 GENMASK(23, 8) +#define AXI_DAC_UI_STATUS_REG 0x0088 +#define AXI_DAC_UI_STATUS_IF_BUSY BIT(4) +#define AXI_DAC_CUSTOM_CTRL_REG 0x008C +#define AXI_DAC_CUSTOM_CTRL_ADDRESS GENMASK(31, 24) +#define AXI_DAC_CUSTOM_CTRL_MULTI_IO_MODE GENMASK(3, 2) +#define AXI_DAC_CUSTOM_CTRL_STREAM BIT(1) +#define AXI_DAC_CUSTOM_CTRL_TRANSFER_DATA BIT(0) + +#define AXI_DAC_CUSTOM_CTRL_STREAM_ENABLE (AXI_DAC_CUSTOM_CTRL_TRANSFER_DATA | \ + AXI_DAC_CUSTOM_CTRL_STREAM) + +/* DAC Channel controls */ +#define AXI_DAC_CHAN_CNTRL_1_REG(c) (0x0400 + (c) * 0x40) +#define AXI_DAC_CHAN_CNTRL_3_REG(c) (0x0408 + (c) * 0x40) +#define AXI_DAC_CHAN_CNTRL_3_SCALE_SIGN BIT(15) +#define AXI_DAC_CHAN_CNTRL_3_SCALE_INT BIT(14) +#define AXI_DAC_CHAN_CNTRL_3_SCALE GENMASK(14, 0) +#define AXI_DAC_CHAN_CNTRL_2_REG(c) (0x0404 + (c) * 0x40) +#define AXI_DAC_CHAN_CNTRL_2_PHASE GENMASK(31, 16) +#define AXI_DAC_CHAN_CNTRL_2_FREQUENCY GENMASK(15, 0) +#define AXI_DAC_CHAN_CNTRL_4_REG(c) (0x040c + (c) * 0x40) +#define AXI_DAC_CHAN_CNTRL_7_REG(c) (0x0418 + (c) * 0x40) +#define AXI_DAC_CHAN_CNTRL_7_DATA_SEL GENMASK(3, 0) + +#define AXI_DAC_CHAN_CNTRL_MAX 15 +#define AXI_DAC_RD_ADDR(x) (BIT(7) | (x)) + +/* 360 degrees in rad */ +#define AXI_DAC_2_PI_MEGA 6283190 + +enum { + AXI_DAC_DATA_INTERNAL_TONE, + AXI_DAC_DATA_DMA = 2, + AXI_DAC_DATA_INTERNAL_RAMP_16BIT = 11, +}; + +struct axi_dac_info { + unsigned int version; + const struct iio_backend_info *backend_info; + bool has_dac_clk; + bool has_child_nodes; +}; + +struct axi_dac_state { + struct regmap *regmap; + struct device *dev; + /* + * lock to protect multiple accesses to the device registers and global + * data/variables. + */ + struct mutex lock; + const struct axi_dac_info *info; + u64 dac_clk; + u32 reg_config; + int dac_clk_rate; +}; + +static int axi_dac_enable(struct iio_backend *back) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + unsigned int __val; + int ret; + + guard(mutex)(&st->lock); + ret = regmap_set_bits(st->regmap, AXI_DAC_RSTN_REG, + AXI_DAC_RSTN_MMCM_RSTN); + if (ret) + return ret; + /* + * Make sure the DRP (Dynamic Reconfiguration Port) is locked. Not all + * designs really use it but if they don't we still get the lock bit + * set. So let's do it all the time so the code is generic. + */ + ret = regmap_read_poll_timeout(st->regmap, AXI_DAC_DRP_STATUS_REG, + __val, + __val & AXI_DAC_DRP_STATUS_DRP_LOCKED, + 100, 1000); + if (ret) + return ret; + + return regmap_set_bits(st->regmap, AXI_DAC_RSTN_REG, + AXI_DAC_RSTN_RSTN | AXI_DAC_RSTN_MMCM_RSTN); +} + +static void axi_dac_disable(struct iio_backend *back) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + + guard(mutex)(&st->lock); + regmap_write(st->regmap, AXI_DAC_RSTN_REG, 0); +} + +static struct iio_buffer *axi_dac_request_buffer(struct iio_backend *back, + struct iio_dev *indio_dev) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + const char *dma_name; + + if (device_property_read_string(st->dev, "dma-names", &dma_name)) + dma_name = "tx"; + + return iio_dmaengine_buffer_setup_ext(st->dev, indio_dev, dma_name, + IIO_BUFFER_DIRECTION_OUT); +} + +static void axi_dac_free_buffer(struct iio_backend *back, + struct iio_buffer *buffer) +{ + iio_dmaengine_buffer_teardown(buffer); +} + +enum { + AXI_DAC_FREQ_TONE_1, + AXI_DAC_FREQ_TONE_2, + AXI_DAC_SCALE_TONE_1, + AXI_DAC_SCALE_TONE_2, + AXI_DAC_PHASE_TONE_1, + AXI_DAC_PHASE_TONE_2, +}; + +static int __axi_dac_frequency_get(struct axi_dac_state *st, unsigned int chan, + unsigned int tone_2, unsigned int *freq) +{ + u32 reg, raw; + int ret; + + if (chan > AXI_DAC_CHAN_CNTRL_MAX) + return -EINVAL; + + if (!st->dac_clk) { + dev_err(st->dev, "Sampling rate is 0...\n"); + return -EINVAL; + } + + if (tone_2) + reg = AXI_DAC_CHAN_CNTRL_4_REG(chan); + else + reg = AXI_DAC_CHAN_CNTRL_2_REG(chan); + + ret = regmap_read(st->regmap, reg, &raw); + if (ret) + return ret; + + raw = FIELD_GET(AXI_DAC_CHAN_CNTRL_2_FREQUENCY, raw); + *freq = DIV_ROUND_CLOSEST_ULL(raw * st->dac_clk, BIT(16)); + + return 0; +} + +static int axi_dac_frequency_get(struct axi_dac_state *st, + const struct iio_chan_spec *chan, char *buf, + unsigned int tone_2) +{ + unsigned int freq; + int ret; + + scoped_guard(mutex, &st->lock) { + ret = __axi_dac_frequency_get(st, chan->channel, tone_2, &freq); + if (ret) + return ret; + } + + return sysfs_emit(buf, "%u\n", freq); +} + +static int axi_dac_scale_get(struct axi_dac_state *st, + const struct iio_chan_spec *chan, char *buf, + unsigned int tone_2) +{ + unsigned int scale, sign; + int ret, vals[2]; + u32 reg, raw; + + if (chan->channel > AXI_DAC_CHAN_CNTRL_MAX) + return -EINVAL; + + if (tone_2) + reg = AXI_DAC_CHAN_CNTRL_3_REG(chan->channel); + else + reg = AXI_DAC_CHAN_CNTRL_1_REG(chan->channel); + + ret = regmap_read(st->regmap, reg, &raw); + if (ret) + return ret; + + sign = FIELD_GET(AXI_DAC_CHAN_CNTRL_3_SCALE_SIGN, raw); + raw = FIELD_GET(AXI_DAC_CHAN_CNTRL_3_SCALE, raw); + scale = DIV_ROUND_CLOSEST_ULL((u64)raw * MEGA, + AXI_DAC_CHAN_CNTRL_3_SCALE_INT); + + vals[0] = scale / MEGA; + vals[1] = scale % MEGA; + + if (sign) { + vals[0] *= -1; + if (!vals[0]) + vals[1] *= -1; + } + + return iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, ARRAY_SIZE(vals), + vals); +} + +static int axi_dac_phase_get(struct axi_dac_state *st, + const struct iio_chan_spec *chan, char *buf, + unsigned int tone_2) +{ + u32 reg, raw, phase; + int ret, vals[2]; + + if (chan->channel > AXI_DAC_CHAN_CNTRL_MAX) + return -EINVAL; + + if (tone_2) + reg = AXI_DAC_CHAN_CNTRL_4_REG(chan->channel); + else + reg = AXI_DAC_CHAN_CNTRL_2_REG(chan->channel); + + ret = regmap_read(st->regmap, reg, &raw); + if (ret) + return ret; + + raw = FIELD_GET(AXI_DAC_CHAN_CNTRL_2_PHASE, raw); + phase = DIV_ROUND_CLOSEST_ULL((u64)raw * AXI_DAC_2_PI_MEGA, U16_MAX); + + vals[0] = phase / MEGA; + vals[1] = phase % MEGA; + + return iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, ARRAY_SIZE(vals), + vals); +} + +static int __axi_dac_frequency_set(struct axi_dac_state *st, unsigned int chan, + u64 sample_rate, unsigned int freq, + unsigned int tone_2) +{ + u32 reg; + u16 raw; + int ret; + + if (chan > AXI_DAC_CHAN_CNTRL_MAX) + return -EINVAL; + + if (!sample_rate || freq > sample_rate / 2) { + dev_err(st->dev, "Invalid frequency(%u) dac_clk(%llu)\n", + freq, sample_rate); + return -EINVAL; + } + + if (tone_2) + reg = AXI_DAC_CHAN_CNTRL_4_REG(chan); + else + reg = AXI_DAC_CHAN_CNTRL_2_REG(chan); + + raw = DIV64_U64_ROUND_CLOSEST((u64)freq * BIT(16), sample_rate); + + ret = regmap_update_bits(st->regmap, reg, + AXI_DAC_CHAN_CNTRL_2_FREQUENCY, raw); + if (ret) + return ret; + + /* synchronize channels */ + return regmap_set_bits(st->regmap, AXI_DAC_CNTRL_1_REG, + AXI_DAC_CNTRL_1_SYNC); +} + +static int axi_dac_frequency_set(struct axi_dac_state *st, + const struct iio_chan_spec *chan, + const char *buf, size_t len, unsigned int tone_2) +{ + unsigned int freq; + int ret; + + ret = kstrtou32(buf, 10, &freq); + if (ret) + return ret; + + guard(mutex)(&st->lock); + ret = __axi_dac_frequency_set(st, chan->channel, st->dac_clk, freq, + tone_2); + if (ret) + return ret; + + return len; +} + +static int axi_dac_scale_set(struct axi_dac_state *st, + const struct iio_chan_spec *chan, + const char *buf, size_t len, unsigned int tone_2) +{ + int integer, frac, scale; + u32 raw = 0, reg; + int ret; + + if (chan->channel > AXI_DAC_CHAN_CNTRL_MAX) + return -EINVAL; + + ret = iio_str_to_fixpoint(buf, 100000, &integer, &frac); + if (ret) + return ret; + + scale = integer * MEGA + frac; + if (scale <= -2 * (int)MEGA || scale >= 2 * (int)MEGA) + return -EINVAL; + + /* format is 1.1.14 (sign, integer and fractional bits) */ + if (scale < 0) { + raw = FIELD_PREP(AXI_DAC_CHAN_CNTRL_3_SCALE_SIGN, 1); + scale *= -1; + } + + raw |= div_u64((u64)scale * AXI_DAC_CHAN_CNTRL_3_SCALE_INT, MEGA); + + if (tone_2) + reg = AXI_DAC_CHAN_CNTRL_3_REG(chan->channel); + else + reg = AXI_DAC_CHAN_CNTRL_1_REG(chan->channel); + + guard(mutex)(&st->lock); + ret = regmap_write(st->regmap, reg, raw); + if (ret) + return ret; + + /* synchronize channels */ + ret = regmap_set_bits(st->regmap, AXI_DAC_CNTRL_1_REG, + AXI_DAC_CNTRL_1_SYNC); + if (ret) + return ret; + + return len; +} + +static int axi_dac_phase_set(struct axi_dac_state *st, + const struct iio_chan_spec *chan, + const char *buf, size_t len, unsigned int tone_2) +{ + int integer, frac, phase; + u32 raw, reg; + int ret; + + if (chan->channel > AXI_DAC_CHAN_CNTRL_MAX) + return -EINVAL; + + ret = iio_str_to_fixpoint(buf, 100000, &integer, &frac); + if (ret) + return ret; + + phase = integer * MEGA + frac; + if (phase < 0 || phase > AXI_DAC_2_PI_MEGA) + return -EINVAL; + + raw = DIV_ROUND_CLOSEST_ULL((u64)phase * U16_MAX, AXI_DAC_2_PI_MEGA); + + if (tone_2) + reg = AXI_DAC_CHAN_CNTRL_4_REG(chan->channel); + else + reg = AXI_DAC_CHAN_CNTRL_2_REG(chan->channel); + + guard(mutex)(&st->lock); + ret = regmap_update_bits(st->regmap, reg, AXI_DAC_CHAN_CNTRL_2_PHASE, + FIELD_PREP(AXI_DAC_CHAN_CNTRL_2_PHASE, raw)); + if (ret) + return ret; + + /* synchronize channels */ + ret = regmap_set_bits(st->regmap, AXI_DAC_CNTRL_1_REG, + AXI_DAC_CNTRL_1_SYNC); + if (ret) + return ret; + + return len; +} + +static int axi_dac_ext_info_set(struct iio_backend *back, uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + + switch (private) { + case AXI_DAC_FREQ_TONE_1: + case AXI_DAC_FREQ_TONE_2: + return axi_dac_frequency_set(st, chan, buf, len, + private == AXI_DAC_FREQ_TONE_2); + case AXI_DAC_SCALE_TONE_1: + case AXI_DAC_SCALE_TONE_2: + return axi_dac_scale_set(st, chan, buf, len, + private == AXI_DAC_SCALE_TONE_2); + case AXI_DAC_PHASE_TONE_1: + case AXI_DAC_PHASE_TONE_2: + return axi_dac_phase_set(st, chan, buf, len, + private == AXI_DAC_PHASE_TONE_2); + default: + return -EOPNOTSUPP; + } +} + +static int axi_dac_ext_info_get(struct iio_backend *back, uintptr_t private, + const struct iio_chan_spec *chan, char *buf) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + + switch (private) { + case AXI_DAC_FREQ_TONE_1: + case AXI_DAC_FREQ_TONE_2: + return axi_dac_frequency_get(st, chan, buf, + private - AXI_DAC_FREQ_TONE_1); + case AXI_DAC_SCALE_TONE_1: + case AXI_DAC_SCALE_TONE_2: + return axi_dac_scale_get(st, chan, buf, + private - AXI_DAC_SCALE_TONE_1); + case AXI_DAC_PHASE_TONE_1: + case AXI_DAC_PHASE_TONE_2: + return axi_dac_phase_get(st, chan, buf, + private - AXI_DAC_PHASE_TONE_1); + default: + return -EOPNOTSUPP; + } +} + +static const struct iio_chan_spec_ext_info axi_dac_ext_info[] = { + IIO_BACKEND_EX_INFO("frequency0", IIO_SEPARATE, AXI_DAC_FREQ_TONE_1), + IIO_BACKEND_EX_INFO("frequency1", IIO_SEPARATE, AXI_DAC_FREQ_TONE_2), + IIO_BACKEND_EX_INFO("scale0", IIO_SEPARATE, AXI_DAC_SCALE_TONE_1), + IIO_BACKEND_EX_INFO("scale1", IIO_SEPARATE, AXI_DAC_SCALE_TONE_2), + IIO_BACKEND_EX_INFO("phase0", IIO_SEPARATE, AXI_DAC_PHASE_TONE_1), + IIO_BACKEND_EX_INFO("phase1", IIO_SEPARATE, AXI_DAC_PHASE_TONE_2), + { } +}; + +static int axi_dac_extend_chan(struct iio_backend *back, + struct iio_chan_spec *chan) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + + if (chan->type != IIO_ALTVOLTAGE) + return -EINVAL; + if (st->reg_config & AXI_DAC_CONFIG_DDS_DISABLE) + /* nothing to extend */ + return 0; + + chan->ext_info = axi_dac_ext_info; + + return 0; +} + +static int axi_dac_data_source_set(struct iio_backend *back, unsigned int chan, + enum iio_backend_data_source data) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + + if (chan > AXI_DAC_CHAN_CNTRL_MAX) + return -EINVAL; + + switch (data) { + case IIO_BACKEND_INTERNAL_CONTINUOUS_WAVE: + return regmap_update_bits(st->regmap, + AXI_DAC_CHAN_CNTRL_7_REG(chan), + AXI_DAC_CHAN_CNTRL_7_DATA_SEL, + AXI_DAC_DATA_INTERNAL_TONE); + case IIO_BACKEND_EXTERNAL: + return regmap_update_bits(st->regmap, + AXI_DAC_CHAN_CNTRL_7_REG(chan), + AXI_DAC_CHAN_CNTRL_7_DATA_SEL, + AXI_DAC_DATA_DMA); + case IIO_BACKEND_INTERNAL_RAMP_16BIT: + return regmap_update_bits(st->regmap, + AXI_DAC_CHAN_CNTRL_7_REG(chan), + AXI_DAC_CHAN_CNTRL_7_DATA_SEL, + AXI_DAC_DATA_INTERNAL_RAMP_16BIT); + default: + return -EINVAL; + } +} + +static int axi_dac_data_source_get(struct iio_backend *back, unsigned int chan, + enum iio_backend_data_source *data) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + int ret; + u32 val; + + if (chan > AXI_DAC_CHAN_CNTRL_MAX) + return -EINVAL; + + ret = regmap_read(st->regmap, AXI_DAC_CHAN_CNTRL_7_REG(chan), &val); + if (ret) + return ret; + + switch (val) { + case AXI_DAC_DATA_INTERNAL_TONE: + *data = IIO_BACKEND_INTERNAL_CONTINUOUS_WAVE; + return 0; + case AXI_DAC_DATA_DMA: + *data = IIO_BACKEND_EXTERNAL; + return 0; + case AXI_DAC_DATA_INTERNAL_RAMP_16BIT: + *data = IIO_BACKEND_INTERNAL_RAMP_16BIT; + return 0; + default: + return -EIO; + } +} + +static int axi_dac_set_sample_rate(struct iio_backend *back, unsigned int chan, + u64 sample_rate) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + unsigned int freq; + int ret, tone; + + if (chan > AXI_DAC_CHAN_CNTRL_MAX) + return -EINVAL; + if (!sample_rate) + return -EINVAL; + if (st->reg_config & AXI_DAC_CONFIG_DDS_DISABLE) + /* sample_rate has no meaning if DDS is disabled */ + return 0; + + guard(mutex)(&st->lock); + /* + * If dac_clk is 0 then this must be the first time we're being notified + * about the interface sample rate. Hence, just update our internal + * variable and bail... If it's not 0, then we get the current DDS + * frequency (for the old rate) and update the registers for the new + * sample rate. + */ + if (!st->dac_clk) { + st->dac_clk = sample_rate; + return 0; + } + + for (tone = 0; tone <= AXI_DAC_FREQ_TONE_2; tone++) { + ret = __axi_dac_frequency_get(st, chan, tone, &freq); + if (ret) + return ret; + + ret = __axi_dac_frequency_set(st, chan, sample_rate, tone, freq); + if (ret) + return ret; + } + + st->dac_clk = sample_rate; + + return 0; +} + +static int axi_dac_reg_access(struct iio_backend *back, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + + if (readval) + return regmap_read(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); +} + +static int axi_dac_ddr_enable(struct iio_backend *back) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + + return regmap_clear_bits(st->regmap, AXI_DAC_CNTRL_2_REG, + AXI_DAC_CNTRL_2_SDR_DDR_N); +} + +static int axi_dac_ddr_disable(struct iio_backend *back) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + + return regmap_set_bits(st->regmap, AXI_DAC_CNTRL_2_REG, + AXI_DAC_CNTRL_2_SDR_DDR_N); +} + +static int axi_dac_wait_bus_free(struct axi_dac_state *st) +{ + u32 val; + int ret; + + ret = regmap_read_poll_timeout(st->regmap, AXI_DAC_UI_STATUS_REG, val, + FIELD_GET(AXI_DAC_UI_STATUS_IF_BUSY, val) == 0, 10, + 100 * KILO); + if (ret == -ETIMEDOUT) + dev_err(st->dev, "AXI bus timeout\n"); + + return ret; +} + +static int axi_dac_data_stream_enable(struct iio_backend *back) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + int ret; + + ret = axi_dac_wait_bus_free(st); + if (ret) + return ret; + + return regmap_set_bits(st->regmap, AXI_DAC_CUSTOM_CTRL_REG, + AXI_DAC_CUSTOM_CTRL_STREAM_ENABLE); +} + +static int axi_dac_data_stream_disable(struct iio_backend *back) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + + return regmap_clear_bits(st->regmap, AXI_DAC_CUSTOM_CTRL_REG, + AXI_DAC_CUSTOM_CTRL_STREAM_ENABLE); +} + +static int axi_dac_data_transfer_addr(struct iio_backend *back, u32 address) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + + if (address > FIELD_MAX(AXI_DAC_CUSTOM_CTRL_ADDRESS)) + return -EINVAL; + + /* + * Sample register address, when the DAC is configured, or stream + * start address when the FSM is in stream state. + */ + return regmap_update_bits(st->regmap, AXI_DAC_CUSTOM_CTRL_REG, + AXI_DAC_CUSTOM_CTRL_ADDRESS, + FIELD_PREP(AXI_DAC_CUSTOM_CTRL_ADDRESS, + address)); +} + +static int axi_dac_data_format_set(struct iio_backend *back, unsigned int ch, + const struct iio_backend_data_fmt *data) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + + switch (data->type) { + case IIO_BACKEND_DATA_UNSIGNED: + return regmap_clear_bits(st->regmap, AXI_DAC_CNTRL_2_REG, + AXI_DAC_CNTRL_2_UNSIGNED_DATA); + default: + return -EINVAL; + } +} + +static int __axi_dac_bus_reg_write(struct iio_backend *back, u32 reg, + u32 val, size_t data_size) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + int ret; + u32 ival; + + /* + * Both AXI_DAC_CNTRL_2_REG and AXI_DAC_CUSTOM_WR_REG need to know + * the data size. So keeping data size control here only, + * since data size is mandatory for the current transfer. + * DDR state handled separately by specific backend calls, + * generally all raw register writes are SDR. + */ + if (data_size == sizeof(u16)) + ival = FIELD_PREP(AXI_DAC_CUSTOM_WR_DATA_16, val); + else + ival = FIELD_PREP(AXI_DAC_CUSTOM_WR_DATA_8, val); + + ret = regmap_write(st->regmap, AXI_DAC_CUSTOM_WR_REG, ival); + if (ret) + return ret; + + if (data_size == sizeof(u8)) + ret = regmap_set_bits(st->regmap, AXI_DAC_CNTRL_2_REG, + AXI_DAC_CNTRL_2_SYMB_8B); + else + ret = regmap_clear_bits(st->regmap, AXI_DAC_CNTRL_2_REG, + AXI_DAC_CNTRL_2_SYMB_8B); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, AXI_DAC_CUSTOM_CTRL_REG, + AXI_DAC_CUSTOM_CTRL_ADDRESS, + FIELD_PREP(AXI_DAC_CUSTOM_CTRL_ADDRESS, reg)); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, AXI_DAC_CUSTOM_CTRL_REG, + AXI_DAC_CUSTOM_CTRL_TRANSFER_DATA, + AXI_DAC_CUSTOM_CTRL_TRANSFER_DATA); + if (ret) + return ret; + + ret = axi_dac_wait_bus_free(st); + if (ret) + return ret; + + /* Cleaning always AXI_DAC_CUSTOM_CTRL_TRANSFER_DATA */ + return regmap_clear_bits(st->regmap, AXI_DAC_CUSTOM_CTRL_REG, + AXI_DAC_CUSTOM_CTRL_TRANSFER_DATA); +} + +static int axi_dac_bus_reg_write(struct iio_backend *back, u32 reg, + u32 val, size_t data_size) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + + guard(mutex)(&st->lock); + return __axi_dac_bus_reg_write(back, reg, val, data_size); +} + +static int axi_dac_bus_reg_read(struct iio_backend *back, u32 reg, u32 *val, + size_t data_size) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + int ret; + + guard(mutex)(&st->lock); + + /* + * SPI, we write with read flag, then we read just at the AXI + * io address space to get data read. + */ + ret = __axi_dac_bus_reg_write(back, AXI_DAC_RD_ADDR(reg), 0, + data_size); + if (ret) + return ret; + + ret = axi_dac_wait_bus_free(st); + if (ret) + return ret; + + return regmap_read(st->regmap, AXI_DAC_CUSTOM_RD_REG, val); +} + +static int axi_dac_bus_set_io_mode(struct iio_backend *back, + enum ad3552r_io_mode mode) +{ + struct axi_dac_state *st = iio_backend_get_priv(back); + int ret; + + if (mode > AD3552R_IO_MODE_QSPI) + return -EINVAL; + + guard(mutex)(&st->lock); + + ret = regmap_update_bits(st->regmap, AXI_DAC_CUSTOM_CTRL_REG, + AXI_DAC_CUSTOM_CTRL_MULTI_IO_MODE, + FIELD_PREP(AXI_DAC_CUSTOM_CTRL_MULTI_IO_MODE, mode)); + if (ret) + return ret; + + return axi_dac_wait_bus_free(st); +} + +static void axi_dac_child_remove(void *data) +{ + platform_device_unregister(data); +} + +static int axi_dac_create_platform_device(struct axi_dac_state *st, + struct fwnode_handle *child) +{ + struct ad3552r_hs_platform_data pdata = { + .bus_reg_read = axi_dac_bus_reg_read, + .bus_reg_write = axi_dac_bus_reg_write, + .bus_set_io_mode = axi_dac_bus_set_io_mode, + .bus_sample_data_clock_hz = st->dac_clk_rate, + }; + struct platform_device_info pi = { + .parent = st->dev, + .name = fwnode_get_name(child), + .id = PLATFORM_DEVID_AUTO, + .fwnode = child, + .data = &pdata, + .size_data = sizeof(pdata), + }; + struct platform_device *pdev; + + pdev = platform_device_register_full(&pi); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + return devm_add_action_or_reset(st->dev, axi_dac_child_remove, pdev); +} + +static const struct iio_backend_ops axi_dac_generic_ops = { + .enable = axi_dac_enable, + .disable = axi_dac_disable, + .request_buffer = axi_dac_request_buffer, + .free_buffer = axi_dac_free_buffer, + .extend_chan_spec = axi_dac_extend_chan, + .ext_info_set = axi_dac_ext_info_set, + .ext_info_get = axi_dac_ext_info_get, + .data_source_set = axi_dac_data_source_set, + .set_sample_rate = axi_dac_set_sample_rate, + .debugfs_reg_access = iio_backend_debugfs_ptr(axi_dac_reg_access), +}; + +static const struct iio_backend_ops axi_ad3552r_ops = { + .enable = axi_dac_enable, + .disable = axi_dac_disable, + .request_buffer = axi_dac_request_buffer, + .free_buffer = axi_dac_free_buffer, + .data_source_set = axi_dac_data_source_set, + .data_source_get = axi_dac_data_source_get, + .ddr_enable = axi_dac_ddr_enable, + .ddr_disable = axi_dac_ddr_disable, + .data_stream_enable = axi_dac_data_stream_enable, + .data_stream_disable = axi_dac_data_stream_disable, + .data_format_set = axi_dac_data_format_set, + .data_transfer_addr = axi_dac_data_transfer_addr, +}; + +static const struct iio_backend_info axi_dac_generic = { + .name = "axi-dac", + .ops = &axi_dac_generic_ops, + .caps = IIO_BACKEND_CAP_BUFFER | IIO_BACKEND_CAP_ENABLE, +}; + +static const struct iio_backend_info axi_ad3552r = { + .name = "axi-ad3552r", + .ops = &axi_ad3552r_ops, + .caps = IIO_BACKEND_CAP_BUFFER | IIO_BACKEND_CAP_ENABLE, +}; + +static const struct regmap_config axi_dac_regmap_config = { + .val_bits = 32, + .reg_bits = 32, + .reg_stride = 4, + .max_register = 0x0800, +}; + +static int axi_dac_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct axi_dac_state *st; + void __iomem *base; + unsigned int ver; + struct clk *clk; + int ret; + + st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL); + if (!st) + return -ENOMEM; + + st->info = device_get_match_data(dev); + if (!st->info) + return -ENODEV; + clk = devm_clk_get_enabled(dev, "s_axi_aclk"); + if (IS_ERR(clk)) { + /* Backward compat., old fdt versions without clock-names. */ + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), + "failed to get clock\n"); + } + + if (st->info->has_dac_clk) { + struct clk *dac_clk; + + dac_clk = devm_clk_get_enabled(dev, "dac_clk"); + if (IS_ERR(dac_clk)) + return dev_err_probe(dev, PTR_ERR(dac_clk), + "failed to get dac_clk clock\n"); + + /* We only care about the streaming mode rate */ + st->dac_clk_rate = clk_get_rate(dac_clk) / 2; + } + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + st->dev = dev; + st->regmap = devm_regmap_init_mmio(dev, base, &axi_dac_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "failed to init register map\n"); + + /* + * Force disable the core. Up to the frontend to enable us. And we can + * still read/write registers... + */ + ret = regmap_write(st->regmap, AXI_DAC_RSTN_REG, 0); + if (ret) + return ret; + + ret = regmap_read(st->regmap, ADI_AXI_REG_VERSION, &ver); + if (ret) + return ret; + + if (ADI_AXI_PCORE_VER_MAJOR(ver) != ADI_AXI_PCORE_VER_MAJOR(st->info->version)) + return dev_err_probe(dev, -ENODEV, + "Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n", + ADI_AXI_PCORE_VER_MAJOR(st->info->version), + ADI_AXI_PCORE_VER_MINOR(st->info->version), + ADI_AXI_PCORE_VER_PATCH(st->info->version), + ADI_AXI_PCORE_VER_MAJOR(ver), + ADI_AXI_PCORE_VER_MINOR(ver), + ADI_AXI_PCORE_VER_PATCH(ver)); + + /* Let's get the core read only configuration */ + ret = regmap_read(st->regmap, AXI_DAC_CONFIG_REG, &st->reg_config); + if (ret) + return ret; + + /* + * In some designs, setting the R1_MODE bit to 0 (which is the default + * value) causes all channels of the frontend to be routed to the same + * DMA (so they are sampled together). This is for things like + * Multiple-Input and Multiple-Output (MIMO). As most of the times we + * want independent channels let's override the core's default value and + * set the R1_MODE bit. + */ + ret = regmap_set_bits(st->regmap, AXI_DAC_CNTRL_2_REG, + ADI_DAC_CNTRL_2_R1_MODE); + if (ret) + return ret; + + mutex_init(&st->lock); + + ret = devm_iio_backend_register(dev, st->info->backend_info, st); + if (ret) + return dev_err_probe(dev, ret, + "failed to register iio backend\n"); + + device_for_each_child_node_scoped(dev, child) { + int val; + + if (!st->info->has_child_nodes) + return dev_err_probe(dev, -EINVAL, + "invalid fdt axi-dac compatible."); + + /* Processing only reg 0 node */ + ret = fwnode_property_read_u32(child, "reg", &val); + if (ret) + return dev_err_probe(dev, ret, "invalid reg property."); + if (val != 0) + return dev_err_probe(dev, -EINVAL, + "invalid node address."); + + ret = axi_dac_create_platform_device(st, child); + if (ret) + return dev_err_probe(dev, -EINVAL, + "cannot create device."); + } + + dev_info(dev, "AXI DAC IP core (%d.%.2d.%c) probed\n", + ADI_AXI_PCORE_VER_MAJOR(ver), + ADI_AXI_PCORE_VER_MINOR(ver), + ADI_AXI_PCORE_VER_PATCH(ver)); + + return 0; +} + +static const struct axi_dac_info dac_generic = { + .version = ADI_AXI_PCORE_VER(9, 1, 'b'), + .backend_info = &axi_dac_generic, +}; + +static const struct axi_dac_info dac_ad3552r = { + .version = ADI_AXI_PCORE_VER(9, 1, 'b'), + .backend_info = &axi_ad3552r, + .has_dac_clk = true, + .has_child_nodes = true, +}; + +static const struct of_device_id axi_dac_of_match[] = { + { .compatible = "adi,axi-dac-9.1.b", .data = &dac_generic }, + { .compatible = "adi,axi-ad3552r", .data = &dac_ad3552r }, + { } +}; +MODULE_DEVICE_TABLE(of, axi_dac_of_match); + +static struct platform_driver axi_dac_driver = { + .driver = { + .name = "adi-axi-dac", + .of_match_table = axi_dac_of_match, + }, + .probe = axi_dac_probe, +}; +module_platform_driver(axi_dac_driver); + +MODULE_AUTHOR("Nuno Sa "); +MODULE_DESCRIPTION("Analog Devices Generic AXI DAC IP core driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER"); +MODULE_IMPORT_NS("IIO_BACKEND"); diff --git a/drivers/iio/dac/cio-dac.c b/drivers/iio/dac/cio-dac.c new file mode 100644 index 000000000..069904d00 --- /dev/null +++ b/drivers/iio/dac/cio-dac.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * IIO driver for the Measurement Computing CIO-DAC + * Copyright (C) 2016 William Breathitt Gray + * + * This driver supports the following Measurement Computing devices: CIO-DAC16, + * CIO-DAC08, and PC104-DAC06. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CIO_DAC_NUM_CHAN 16 + +#define CIO_DAC_CHAN(chan) { \ + .type = IIO_VOLTAGE, \ + .channel = chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .indexed = 1, \ + .output = 1 \ +} + +#define CIO_DAC_EXTENT 32 + +static unsigned int base[max_num_isa_dev(CIO_DAC_EXTENT)]; +static unsigned int num_cio_dac; +module_param_hw_array(base, uint, ioport, &num_cio_dac, 0); +MODULE_PARM_DESC(base, "Measurement Computing CIO-DAC base addresses"); + +#define CIO_DAC_BASE 0x00 +#define CIO_DAC_CHANNEL_STRIDE 2 + +static bool cio_dac_precious_reg(struct device *dev, unsigned int reg) +{ + /* + * All registers are considered precious; if the XFER jumper is set on + * the device, then no update occurs until a DAC register is read. + */ + return true; +} + +static const struct regmap_config cio_dac_regmap_config = { + .reg_bits = 16, + .reg_stride = 2, + .val_bits = 16, + .io_port = true, + .max_register = 0x1F, + .precious_reg = cio_dac_precious_reg, +}; + +/** + * struct cio_dac_iio - IIO device private data structure + * @map: Regmap for the device + */ +struct cio_dac_iio { + struct regmap *map; +}; + +static int cio_dac_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long mask) +{ + struct cio_dac_iio *const priv = iio_priv(indio_dev); + const unsigned int offset = chan->channel * CIO_DAC_CHANNEL_STRIDE; + int err; + unsigned int dac_val; + + if (mask != IIO_CHAN_INFO_RAW) + return -EINVAL; + + err = regmap_read(priv->map, CIO_DAC_BASE + offset, &dac_val); + if (err) + return err; + + *val = dac_val; + + return IIO_VAL_INT; +} + +static int cio_dac_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + struct cio_dac_iio *const priv = iio_priv(indio_dev); + const unsigned int offset = chan->channel * CIO_DAC_CHANNEL_STRIDE; + + if (mask != IIO_CHAN_INFO_RAW) + return -EINVAL; + + /* DAC can only accept up to a 12-bit value */ + if ((unsigned int)val > 4095) + return -EINVAL; + + return regmap_write(priv->map, CIO_DAC_BASE + offset, val); +} + +static const struct iio_info cio_dac_info = { + .read_raw = cio_dac_read_raw, + .write_raw = cio_dac_write_raw +}; + +static const struct iio_chan_spec cio_dac_channels[CIO_DAC_NUM_CHAN] = { + CIO_DAC_CHAN(0), CIO_DAC_CHAN(1), CIO_DAC_CHAN(2), CIO_DAC_CHAN(3), + CIO_DAC_CHAN(4), CIO_DAC_CHAN(5), CIO_DAC_CHAN(6), CIO_DAC_CHAN(7), + CIO_DAC_CHAN(8), CIO_DAC_CHAN(9), CIO_DAC_CHAN(10), CIO_DAC_CHAN(11), + CIO_DAC_CHAN(12), CIO_DAC_CHAN(13), CIO_DAC_CHAN(14), CIO_DAC_CHAN(15) +}; + +static int cio_dac_probe(struct device *dev, unsigned int id) +{ + struct iio_dev *indio_dev; + struct cio_dac_iio *priv; + void __iomem *regs; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + if (!devm_request_region(dev, base[id], CIO_DAC_EXTENT, + dev_name(dev))) { + dev_err(dev, "Unable to request port addresses (0x%X-0x%X)\n", + base[id], base[id] + CIO_DAC_EXTENT); + return -EBUSY; + } + + regs = devm_ioport_map(dev, base[id], CIO_DAC_EXTENT); + if (!regs) + return -ENOMEM; + + priv = iio_priv(indio_dev); + priv->map = devm_regmap_init_mmio(dev, regs, &cio_dac_regmap_config); + if (IS_ERR(priv->map)) + return dev_err_probe(dev, PTR_ERR(priv->map), + "Unable to initialize register map\n"); + + indio_dev->info = &cio_dac_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = cio_dac_channels; + indio_dev->num_channels = CIO_DAC_NUM_CHAN; + indio_dev->name = dev_name(dev); + + return devm_iio_device_register(dev, indio_dev); +} + +static struct isa_driver cio_dac_driver = { + .probe = cio_dac_probe, + .driver = { + .name = "cio-dac" + } +}; + +module_isa_driver(cio_dac_driver, num_cio_dac); + +MODULE_AUTHOR("William Breathitt Gray "); +MODULE_DESCRIPTION("Measurement Computing CIO-DAC IIO driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/dpot-dac.c b/drivers/iio/dac/dpot-dac.c new file mode 100644 index 000000000..de36e2e27 --- /dev/null +++ b/drivers/iio/dac/dpot-dac.c @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * IIO DAC emulation driver using a digital potentiometer + * + * Copyright (C) 2016 Axentia Technologies AB + * + * Author: Peter Rosin + */ + +/* + * It is assumed that the dpot is used as a voltage divider between the + * current dpot wiper setting and the maximum resistance of the dpot. The + * divided voltage is provided by a vref regulator. + * + * .------. + * .-----------. | | + * | vref |--' .---. + * | regulator |--. | | + * '-----------' | | d | + * | | p | + * | | o | wiper + * | | t |<---------+ + * | | | + * | '---' dac output voltage + * | | + * '------+------------+ + */ + +#include +#include +#include +#include +#include +#include + +struct dpot_dac { + struct regulator *vref; + struct iio_channel *dpot; + u32 max_ohms; +}; + +static const struct iio_chan_spec dpot_dac_iio_channel = { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) + | BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW), + .output = 1, + .indexed = 1, +}; + +static int dpot_dac_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct dpot_dac *dac = iio_priv(indio_dev); + int ret; + unsigned long long tmp; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return iio_read_channel_raw(dac->dpot, val); + + case IIO_CHAN_INFO_SCALE: + ret = iio_read_channel_scale(dac->dpot, val, val2); + switch (ret) { + case IIO_VAL_FRACTIONAL_LOG2: + tmp = *val * 1000000000LL; + do_div(tmp, dac->max_ohms); + tmp *= regulator_get_voltage(dac->vref) / 1000; + do_div(tmp, 1000000000LL); + *val = tmp; + return ret; + case IIO_VAL_INT: + /* + * Convert integer scale to fractional scale by + * setting the denominator (val2) to one... + */ + *val2 = 1; + ret = IIO_VAL_FRACTIONAL; + /* ...and fall through. Say it again for GCC. */ + fallthrough; + case IIO_VAL_FRACTIONAL: + *val *= regulator_get_voltage(dac->vref) / 1000; + *val2 *= dac->max_ohms; + break; + } + + return ret; + } + + return -EINVAL; +} + +static int dpot_dac_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct dpot_dac *dac = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *type = IIO_VAL_INT; + return iio_read_avail_channel_raw(dac->dpot, vals, length); + } + + return -EINVAL; +} + +static int dpot_dac_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct dpot_dac *dac = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return iio_write_channel_raw(dac->dpot, val); + } + + return -EINVAL; +} + +static const struct iio_info dpot_dac_info = { + .read_raw = dpot_dac_read_raw, + .read_avail = dpot_dac_read_avail, + .write_raw = dpot_dac_write_raw, +}; + +static int dpot_dac_channel_max_ohms(struct iio_dev *indio_dev) +{ + struct device *dev = &indio_dev->dev; + struct dpot_dac *dac = iio_priv(indio_dev); + unsigned long long tmp; + int ret; + int val; + int val2; + int max; + + ret = iio_read_max_channel_raw(dac->dpot, &max); + if (ret < 0) { + dev_err(dev, "dpot does not indicate its raw maximum value\n"); + return ret; + } + + switch (iio_read_channel_scale(dac->dpot, &val, &val2)) { + case IIO_VAL_INT: + return max * val; + case IIO_VAL_FRACTIONAL: + tmp = (unsigned long long)max * val; + do_div(tmp, val2); + return tmp; + case IIO_VAL_FRACTIONAL_LOG2: + tmp = val * 1000000000LL * max >> val2; + do_div(tmp, 1000000000LL); + return tmp; + default: + dev_err(dev, "dpot has a scale that is too weird\n"); + } + + return -EINVAL; +} + +static int dpot_dac_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct dpot_dac *dac; + enum iio_chan_type type; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*dac)); + if (!indio_dev) + return -ENOMEM; + + platform_set_drvdata(pdev, indio_dev); + dac = iio_priv(indio_dev); + + indio_dev->name = dev_name(dev); + indio_dev->info = &dpot_dac_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = &dpot_dac_iio_channel; + indio_dev->num_channels = 1; + + dac->vref = devm_regulator_get(dev, "vref"); + if (IS_ERR(dac->vref)) + return dev_err_probe(&pdev->dev, PTR_ERR(dac->vref), + "failed to get vref regulator\n"); + + dac->dpot = devm_iio_channel_get(dev, "dpot"); + if (IS_ERR(dac->dpot)) + return dev_err_probe(&pdev->dev, PTR_ERR(dac->dpot), + "failed to get dpot input channel\n"); + + ret = iio_get_channel_type(dac->dpot, &type); + if (ret < 0) + return ret; + + if (type != IIO_RESISTANCE) { + dev_err(dev, "dpot is of the wrong type\n"); + return -EINVAL; + } + + ret = dpot_dac_channel_max_ohms(indio_dev); + if (ret < 0) + return ret; + dac->max_ohms = ret; + + ret = regulator_enable(dac->vref); + if (ret) { + dev_err(dev, "failed to enable the vref regulator\n"); + return ret; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(dev, "failed to register iio device\n"); + goto disable_reg; + } + + return 0; + +disable_reg: + regulator_disable(dac->vref); + return ret; +} + +static void dpot_dac_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct dpot_dac *dac = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + regulator_disable(dac->vref); +} + +static const struct of_device_id dpot_dac_match[] = { + { .compatible = "dpot-dac" }, + { } +}; +MODULE_DEVICE_TABLE(of, dpot_dac_match); + +static struct platform_driver dpot_dac_driver = { + .probe = dpot_dac_probe, + .remove = dpot_dac_remove, + .driver = { + .name = "iio-dpot-dac", + .of_match_table = dpot_dac_match, + }, +}; +module_platform_driver(dpot_dac_driver); + +MODULE_DESCRIPTION("DAC emulation driver using a digital potentiometer"); +MODULE_AUTHOR("Peter Rosin "); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_CONSUMER"); diff --git a/drivers/iio/dac/ds4424.c b/drivers/iio/dac/ds4424.c new file mode 100644 index 000000000..b6430b832 --- /dev/null +++ b/drivers/iio/dac/ds4424.c @@ -0,0 +1,439 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Maxim Integrated + * 7-bit, Multi-Channel Sink/Source Current DAC Driver + * Copyright (C) 2017 Maxim Integrated + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define DS4422_MAX_DAC_CHANNELS 2 +#define DS4424_MAX_DAC_CHANNELS 4 + +#define DS4424_DAC_MASK GENMASK(6, 0) +#define DS4404_DAC_MASK GENMASK(4, 0) +#define DS4424_DAC_SOURCE BIT(7) + +#define DS4424_DAC_ADDR(chan) ((chan) + 0xf8) + +#define DS4424_CHANNEL(chan) { \ + .type = IIO_CURRENT, \ + .indexed = 1, \ + .output = 1, \ + .channel = chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ +} + +#define DS4424_CHANNEL_WITH_SCALE(chan) { \ + .type = IIO_CURRENT, \ + .indexed = 1, \ + .output = 1, \ + .channel = chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ +} + +struct ds4424_chip_info { + const char *name; + int vref_mV; + int scale_denom; + u8 result_mask; + u8 num_channels; +}; + +static const struct ds4424_chip_info ds4402_info = { + .name = "ds4402", + .vref_mV = 1230, + .scale_denom = 4, + .result_mask = DS4404_DAC_MASK, + .num_channels = DS4422_MAX_DAC_CHANNELS, +}; + +static const struct ds4424_chip_info ds4404_info = { + .name = "ds4404", + .vref_mV = 1230, + .scale_denom = 4, + .result_mask = DS4404_DAC_MASK, + .num_channels = DS4424_MAX_DAC_CHANNELS, +}; + +static const struct ds4424_chip_info ds4422_info = { + .name = "ds4422", + .vref_mV = 976, + .scale_denom = 16, + .result_mask = DS4424_DAC_MASK, + .num_channels = DS4422_MAX_DAC_CHANNELS, +}; + +static const struct ds4424_chip_info ds4424_info = { + .name = "ds4424", + .vref_mV = 976, + .scale_denom = 16, + .result_mask = DS4424_DAC_MASK, + .num_channels = DS4424_MAX_DAC_CHANNELS, +}; + +struct ds4424_data { + struct regmap *regmap; + struct regulator *vcc_reg; + const struct ds4424_chip_info *chip_info; + u32 rfs_ohms[DS4424_MAX_DAC_CHANNELS]; + bool has_rfs; +}; + +static const struct iio_chan_spec ds4424_channels[] = { + DS4424_CHANNEL(0), + DS4424_CHANNEL(1), + DS4424_CHANNEL(2), + DS4424_CHANNEL(3), +}; + +static const struct iio_chan_spec ds4424_channels_with_scale[] = { + DS4424_CHANNEL_WITH_SCALE(0), + DS4424_CHANNEL_WITH_SCALE(1), + DS4424_CHANNEL_WITH_SCALE(2), + DS4424_CHANNEL_WITH_SCALE(3), +}; + +static const struct regmap_range ds44x2_ranges[] = { + regmap_reg_range(DS4424_DAC_ADDR(0), DS4424_DAC_ADDR(1)), +}; + +static const struct regmap_range ds44x4_ranges[] = { + regmap_reg_range(DS4424_DAC_ADDR(0), DS4424_DAC_ADDR(3)), +}; + +static const struct regmap_access_table ds44x2_table = { + .yes_ranges = ds44x2_ranges, + .n_yes_ranges = ARRAY_SIZE(ds44x2_ranges), +}; + +static const struct regmap_access_table ds44x4_table = { + .yes_ranges = ds44x4_ranges, + .n_yes_ranges = ARRAY_SIZE(ds44x4_ranges), +}; + +static const struct regmap_config ds44x2_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_MAPLE, + .max_register = DS4424_DAC_ADDR(1), + .rd_table = &ds44x2_table, + .wr_table = &ds44x2_table, +}; + +static const struct regmap_config ds44x4_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_MAPLE, + .max_register = DS4424_DAC_ADDR(3), + .rd_table = &ds44x4_table, + .wr_table = &ds44x4_table, +}; + +static int ds4424_init_regmap(struct i2c_client *client, + struct iio_dev *indio_dev) +{ + struct ds4424_data *data = iio_priv(indio_dev); + const struct regmap_config *regmap_config; + u8 vals[DS4424_MAX_DAC_CHANNELS]; + int ret; + + if (indio_dev->num_channels == DS4424_MAX_DAC_CHANNELS) + regmap_config = &ds44x4_regmap_config; + else + regmap_config = &ds44x2_regmap_config; + + data->regmap = devm_regmap_init_i2c(client, regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(&client->dev, PTR_ERR(data->regmap), + "Failed to init regmap.\n"); + + /* + * Prime the cache with the bootloader's configuration. + * regmap_bulk_read() will automatically populate the cache with + * the values read from the hardware. + */ + ret = regmap_bulk_read(data->regmap, DS4424_DAC_ADDR(0), vals, + indio_dev->num_channels); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to read hardware values\n"); + + return 0; +} + +static int ds4424_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ds4424_data *data = iio_priv(indio_dev); + unsigned int regval; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(data->regmap, DS4424_DAC_ADDR(chan->channel), + ®val); + if (ret < 0) { + dev_err_ratelimited(indio_dev->dev.parent, + "Failed to read channel %d: %pe\n", + chan->channel, ERR_PTR(ret)); + return ret; + } + + *val = regval & data->chip_info->result_mask; + if (!(regval & DS4424_DAC_SOURCE)) + *val = -*val; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (!data->has_rfs) + return -EINVAL; + + /* SCALE is mA/step: mV / Ohm = mA. */ + *val = data->chip_info->vref_mV; + *val2 = data->rfs_ohms[chan->channel] * + data->chip_info->scale_denom; + return IIO_VAL_FRACTIONAL; + + default: + return -EINVAL; + } +} + +static int ds4424_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ds4424_data *data = iio_priv(indio_dev); + unsigned int abs_val; + + if (val2 != 0) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + abs_val = abs(val); + if (abs_val > data->chip_info->result_mask) + return -EINVAL; + + /* + * Currents exiting the IC (Source) are positive. 0 is a valid + * value for no current flow; the direction bit (Source vs Sink) + * is treated as don't-care by the hardware at 0. + */ + if (val > 0) + abs_val |= DS4424_DAC_SOURCE; + + return regmap_write(data->regmap, DS4424_DAC_ADDR(chan->channel), + abs_val); + + default: + return -EINVAL; + } +} + +static int ds4424_parse_rfs(struct i2c_client *client, + struct ds4424_data *data, + struct iio_dev *indio_dev) +{ + struct device *dev = &client->dev; + int count, ret; + + if (!device_property_present(dev, "maxim,rfs-ohms")) + return 0; + + count = device_property_count_u32(dev, "maxim,rfs-ohms"); + if (count < 0) + return dev_err_probe(dev, count, "Failed to count maxim,rfs-ohms entries\n"); + if (count != indio_dev->num_channels) + return dev_err_probe(dev, -EINVAL, "maxim,rfs-ohms must have %u entries\n", + indio_dev->num_channels); + + ret = device_property_read_u32_array(dev, "maxim,rfs-ohms", + data->rfs_ohms, + indio_dev->num_channels); + if (ret) + return dev_err_probe(dev, ret, "Failed to read maxim,rfs-ohms property\n"); + + for (unsigned int i = 0; i < indio_dev->num_channels; i++) { + if (!data->rfs_ohms[i]) + return dev_err_probe(dev, -EINVAL, "maxim,rfs-ohms entry %u is zero\n", i); + } + + data->has_rfs = true; + + return 0; +} + +static int ds4424_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ds4424_data *data = iio_priv(indio_dev); + u8 zero_buf[DS4424_MAX_DAC_CHANNELS] = { }; + int ret; + + /* Disable all outputs, bypass cache so the '0' isn't saved */ + regcache_cache_bypass(data->regmap, true); + ret = regmap_bulk_write(data->regmap, DS4424_DAC_ADDR(0), + zero_buf, indio_dev->num_channels); + regcache_cache_bypass(data->regmap, false); + if (ret) { + dev_err(dev, "Failed to zero outputs: %pe\n", ERR_PTR(ret)); + return ret; + } + + regcache_cache_only(data->regmap, true); + regcache_mark_dirty(data->regmap); + + return 0; +} + +static int ds4424_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ds4424_data *data = iio_priv(indio_dev); + + regcache_cache_only(data->regmap, false); + return regcache_sync(data->regmap); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ds4424_pm_ops, ds4424_suspend, ds4424_resume); + +static const struct iio_info ds4424_iio_info = { + .read_raw = ds4424_read_raw, + .write_raw = ds4424_write_raw, +}; + +static int ds4424_probe(struct i2c_client *client) +{ + const struct ds4424_chip_info *chip_info; + struct ds4424_data *data; + struct iio_dev *indio_dev; + int ret; + + chip_info = i2c_get_match_data(client); + if (!chip_info) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + indio_dev->name = chip_info->name; + data->chip_info = chip_info; + + data->vcc_reg = devm_regulator_get(&client->dev, "vcc"); + if (IS_ERR(data->vcc_reg)) + return dev_err_probe(&client->dev, PTR_ERR(data->vcc_reg), + "Failed to get vcc-supply regulator.\n"); + + ret = regulator_enable(data->vcc_reg); + if (ret < 0) { + dev_err(&client->dev, + "Unable to enable the regulator.\n"); + return ret; + } + + /* + * The datasheet does not specify a power-up to I2C ready time. + * Maintain the existing conservative 1ms delay to ensure the + * device is ready for communication. + */ + fsleep(1 * USEC_PER_MSEC); + + indio_dev->num_channels = chip_info->num_channels; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ds4424_iio_info; + + ret = ds4424_init_regmap(client, indio_dev); + if (ret) + goto fail; + + ret = ds4424_parse_rfs(client, data, indio_dev); + if (ret) + goto fail; + + if (data->has_rfs) + indio_dev->channels = ds4424_channels_with_scale; + else + indio_dev->channels = ds4424_channels; + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, + "iio_device_register failed. ret: %d\n", ret); + goto fail; + } + + return ret; + +fail: + regulator_disable(data->vcc_reg); + return ret; +} + +static void ds4424_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct ds4424_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + regulator_disable(data->vcc_reg); +} + +static const struct i2c_device_id ds4424_id[] = { + { .name = "ds4402", .driver_data = (kernel_ulong_t)&ds4402_info }, + { .name = "ds4404", .driver_data = (kernel_ulong_t)&ds4404_info }, + { .name = "ds4422", .driver_data = (kernel_ulong_t)&ds4422_info }, + { .name = "ds4424", .driver_data = (kernel_ulong_t)&ds4424_info }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, ds4424_id); + +static const struct of_device_id ds4424_of_match[] = { + { .compatible = "maxim,ds4402", .data = &ds4402_info }, + { .compatible = "maxim,ds4404", .data = &ds4404_info }, + { .compatible = "maxim,ds4422", .data = &ds4422_info }, + { .compatible = "maxim,ds4424", .data = &ds4424_info }, + { } +}; + +MODULE_DEVICE_TABLE(of, ds4424_of_match); + +static struct i2c_driver ds4424_driver = { + .driver = { + .name = "ds4424", + .of_match_table = ds4424_of_match, + .pm = pm_sleep_ptr(&ds4424_pm_ops), + }, + .probe = ds4424_probe, + .remove = ds4424_remove, + .id_table = ds4424_id, +}; +module_i2c_driver(ds4424_driver); + +MODULE_DESCRIPTION("Maxim DS4424 DAC Driver"); +MODULE_AUTHOR("Ismail H. Kose "); +MODULE_AUTHOR("Vishal Sood "); +MODULE_AUTHOR("David Jung "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/lpc18xx_dac.c b/drivers/iio/dac/lpc18xx_dac.c new file mode 100644 index 000000000..43fb9e5a2 --- /dev/null +++ b/drivers/iio/dac/lpc18xx_dac.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * IIO DAC driver for NXP LPC18xx DAC + * + * Copyright (C) 2016 Joachim Eastwood + * + * UNSUPPORTED hardware features: + * - Interrupts + * - DMA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* LPC18XX DAC registers and bits */ +#define LPC18XX_DAC_CR 0x000 +#define LPC18XX_DAC_CR_VALUE_SHIFT 6 +#define LPC18XX_DAC_CR_VALUE_MASK 0x3ff +#define LPC18XX_DAC_CR_BIAS BIT(16) +#define LPC18XX_DAC_CTRL 0x004 +#define LPC18XX_DAC_CTRL_DMA_ENA BIT(3) + +struct lpc18xx_dac { + struct regulator *vref; + void __iomem *base; + struct mutex lock; + struct clk *clk; +}; + +static const struct iio_chan_spec lpc18xx_dac_iio_channels[] = { + { + .type = IIO_VOLTAGE, + .output = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static int lpc18xx_dac_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct lpc18xx_dac *dac = iio_priv(indio_dev); + u32 reg; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + reg = readl(dac->base + LPC18XX_DAC_CR); + *val = reg >> LPC18XX_DAC_CR_VALUE_SHIFT; + *val &= LPC18XX_DAC_CR_VALUE_MASK; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = regulator_get_voltage(dac->vref) / 1000; + *val2 = 10; + + return IIO_VAL_FRACTIONAL_LOG2; + } + + return -EINVAL; +} + +static int lpc18xx_dac_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct lpc18xx_dac *dac = iio_priv(indio_dev); + u32 reg; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val < 0 || val > LPC18XX_DAC_CR_VALUE_MASK) + return -EINVAL; + + reg = LPC18XX_DAC_CR_BIAS; + reg |= val << LPC18XX_DAC_CR_VALUE_SHIFT; + + mutex_lock(&dac->lock); + writel(reg, dac->base + LPC18XX_DAC_CR); + writel(LPC18XX_DAC_CTRL_DMA_ENA, dac->base + LPC18XX_DAC_CTRL); + mutex_unlock(&dac->lock); + + return 0; + } + + return -EINVAL; +} + +static const struct iio_info lpc18xx_dac_info = { + .read_raw = lpc18xx_dac_read_raw, + .write_raw = lpc18xx_dac_write_raw, +}; + +static int lpc18xx_dac_probe(struct platform_device *pdev) +{ + struct iio_dev *indio_dev; + struct lpc18xx_dac *dac; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*dac)); + if (!indio_dev) + return -ENOMEM; + + platform_set_drvdata(pdev, indio_dev); + dac = iio_priv(indio_dev); + mutex_init(&dac->lock); + + dac->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dac->base)) + return PTR_ERR(dac->base); + + dac->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(dac->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(dac->clk), + "error getting clock\n"); + + dac->vref = devm_regulator_get(&pdev->dev, "vref"); + if (IS_ERR(dac->vref)) + return dev_err_probe(&pdev->dev, PTR_ERR(dac->vref), + "error getting regulator\n"); + + indio_dev->name = dev_name(&pdev->dev); + indio_dev->info = &lpc18xx_dac_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = lpc18xx_dac_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(lpc18xx_dac_iio_channels); + + ret = regulator_enable(dac->vref); + if (ret) { + dev_err(&pdev->dev, "unable to enable regulator\n"); + return ret; + } + + ret = clk_prepare_enable(dac->clk); + if (ret) { + dev_err(&pdev->dev, "unable to enable clock\n"); + goto dis_reg; + } + + writel(0, dac->base + LPC18XX_DAC_CTRL); + writel(0, dac->base + LPC18XX_DAC_CR); + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "unable to register device\n"); + goto dis_clk; + } + + return 0; + +dis_clk: + clk_disable_unprepare(dac->clk); +dis_reg: + regulator_disable(dac->vref); + return ret; +} + +static void lpc18xx_dac_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct lpc18xx_dac *dac = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + writel(0, dac->base + LPC18XX_DAC_CTRL); + clk_disable_unprepare(dac->clk); + regulator_disable(dac->vref); +} + +static const struct of_device_id lpc18xx_dac_match[] = { + { .compatible = "nxp,lpc1850-dac" }, + { } +}; +MODULE_DEVICE_TABLE(of, lpc18xx_dac_match); + +static struct platform_driver lpc18xx_dac_driver = { + .probe = lpc18xx_dac_probe, + .remove = lpc18xx_dac_remove, + .driver = { + .name = "lpc18xx-dac", + .of_match_table = lpc18xx_dac_match, + }, +}; +module_platform_driver(lpc18xx_dac_driver); + +MODULE_DESCRIPTION("LPC18xx DAC driver"); +MODULE_AUTHOR("Joachim Eastwood "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ltc1660.c b/drivers/iio/dac/ltc1660.c new file mode 100644 index 000000000..90ffb58e7 --- /dev/null +++ b/drivers/iio/dac/ltc1660.c @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Linear Technology LTC1665/LTC1660, 8 channels DAC + * + * Copyright (C) 2018 Marcus Folkesson + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define LTC1660_REG_WAKE 0x0 +#define LTC1660_REG_DAC_A 0x1 +#define LTC1660_REG_DAC_B 0x2 +#define LTC1660_REG_DAC_C 0x3 +#define LTC1660_REG_DAC_D 0x4 +#define LTC1660_REG_DAC_E 0x5 +#define LTC1660_REG_DAC_F 0x6 +#define LTC1660_REG_DAC_G 0x7 +#define LTC1660_REG_DAC_H 0x8 +#define LTC1660_REG_SLEEP 0xe + +#define LTC1660_NUM_CHANNELS 8 + +static const struct regmap_config ltc1660_regmap_config = { + .reg_bits = 4, + .val_bits = 12, +}; + +enum ltc1660_supported_device_ids { + ID_LTC1660, + ID_LTC1665, +}; + +struct ltc1660_priv { + struct spi_device *spi; + struct regmap *regmap; + struct regulator *vref_reg; + unsigned int value[LTC1660_NUM_CHANNELS]; + unsigned int vref_mv; +}; + +static int ltc1660_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct ltc1660_priv *priv = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val = priv->value[chan->channel]; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = regulator_get_voltage(priv->vref_reg); + if (*val < 0) { + dev_err(&priv->spi->dev, "failed to read vref regulator: %d\n", + *val); + return *val; + } + + /* Convert to mV */ + *val /= 1000; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static int ltc1660_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ltc1660_priv *priv = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val2 != 0) + return -EINVAL; + + if (val < 0 || val > GENMASK(chan->scan_type.realbits - 1, 0)) + return -EINVAL; + + ret = regmap_write(priv->regmap, chan->channel, + (val << chan->scan_type.shift)); + if (!ret) + priv->value[chan->channel] = val; + + return ret; + default: + return -EINVAL; + } +} + +#define LTC1660_CHAN(chan, bits) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 12 - (bits), \ + }, \ +} + +#define LTC1660_OCTAL_CHANNELS(bits) { \ + LTC1660_CHAN(LTC1660_REG_DAC_A, bits), \ + LTC1660_CHAN(LTC1660_REG_DAC_B, bits), \ + LTC1660_CHAN(LTC1660_REG_DAC_C, bits), \ + LTC1660_CHAN(LTC1660_REG_DAC_D, bits), \ + LTC1660_CHAN(LTC1660_REG_DAC_E, bits), \ + LTC1660_CHAN(LTC1660_REG_DAC_F, bits), \ + LTC1660_CHAN(LTC1660_REG_DAC_G, bits), \ + LTC1660_CHAN(LTC1660_REG_DAC_H, bits), \ +} + +static const struct iio_chan_spec ltc1660_channels[][LTC1660_NUM_CHANNELS] = { + [ID_LTC1660] = LTC1660_OCTAL_CHANNELS(10), + [ID_LTC1665] = LTC1660_OCTAL_CHANNELS(8), +}; + +static const struct iio_info ltc1660_info = { + .read_raw = <c1660_read_raw, + .write_raw = <c1660_write_raw, +}; + +static int ltc1660_suspend(struct device *dev) +{ + struct ltc1660_priv *priv = iio_priv(spi_get_drvdata( + to_spi_device(dev))); + return regmap_write(priv->regmap, LTC1660_REG_SLEEP, 0x00); +} + +static int ltc1660_resume(struct device *dev) +{ + struct ltc1660_priv *priv = iio_priv(spi_get_drvdata( + to_spi_device(dev))); + return regmap_write(priv->regmap, LTC1660_REG_WAKE, 0x00); +} +static DEFINE_SIMPLE_DEV_PM_OPS(ltc1660_pm_ops, ltc1660_suspend, + ltc1660_resume); + +static int ltc1660_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct ltc1660_priv *priv; + const struct spi_device_id *id = spi_get_device_id(spi); + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*priv)); + if (indio_dev == NULL) + return -ENOMEM; + + priv = iio_priv(indio_dev); + priv->regmap = devm_regmap_init_spi(spi, <c1660_regmap_config); + if (IS_ERR(priv->regmap)) { + dev_err(&spi->dev, "failed to register spi regmap %ld\n", + PTR_ERR(priv->regmap)); + return PTR_ERR(priv->regmap); + } + + priv->vref_reg = devm_regulator_get(&spi->dev, "vref"); + if (IS_ERR(priv->vref_reg)) + return dev_err_probe(&spi->dev, PTR_ERR(priv->vref_reg), + "vref regulator not specified\n"); + + ret = regulator_enable(priv->vref_reg); + if (ret) { + dev_err(&spi->dev, "failed to enable vref regulator: %d\n", + ret); + return ret; + } + + priv->spi = spi; + spi_set_drvdata(spi, indio_dev); + indio_dev->info = <c1660_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ltc1660_channels[id->driver_data]; + indio_dev->num_channels = LTC1660_NUM_CHANNELS; + indio_dev->name = id->name; + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&spi->dev, "failed to register iio device: %d\n", + ret); + goto error_disable_reg; + } + + return 0; + +error_disable_reg: + regulator_disable(priv->vref_reg); + + return ret; +} + +static void ltc1660_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ltc1660_priv *priv = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + regulator_disable(priv->vref_reg); +} + +static const struct of_device_id ltc1660_dt_ids[] = { + { .compatible = "lltc,ltc1660", .data = (void *)ID_LTC1660 }, + { .compatible = "lltc,ltc1665", .data = (void *)ID_LTC1665 }, + { } +}; +MODULE_DEVICE_TABLE(of, ltc1660_dt_ids); + +static const struct spi_device_id ltc1660_id[] = { + { .name = "ltc1660", .driver_data = ID_LTC1660 }, + { .name = "ltc1665", .driver_data = ID_LTC1665 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ltc1660_id); + +static struct spi_driver ltc1660_driver = { + .driver = { + .name = "ltc1660", + .of_match_table = ltc1660_dt_ids, + .pm = pm_sleep_ptr(<c1660_pm_ops), + }, + .probe = ltc1660_probe, + .remove = ltc1660_remove, + .id_table = ltc1660_id, +}; +module_spi_driver(ltc1660_driver); + +MODULE_AUTHOR("Marcus Folkesson "); +MODULE_DESCRIPTION("Linear Technology LTC1660/LTC1665 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ltc2632.c b/drivers/iio/dac/ltc2632.c new file mode 100644 index 000000000..3800aee8d --- /dev/null +++ b/drivers/iio/dac/ltc2632.c @@ -0,0 +1,428 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * LTC2632 Digital to analog convertors spi driver + * + * Copyright 2017 Maxime Roussin-Bélanger + * expanded by Silvan Murer + */ + +#include +#include +#include +#include +#include +#include + +#include + +#define LTC2632_CMD_WRITE_INPUT_N 0x0 +#define LTC2632_CMD_UPDATE_DAC_N 0x1 +#define LTC2632_CMD_WRITE_INPUT_N_UPDATE_ALL 0x2 +#define LTC2632_CMD_WRITE_INPUT_N_UPDATE_N 0x3 +#define LTC2632_CMD_POWERDOWN_DAC_N 0x4 +#define LTC2632_CMD_POWERDOWN_CHIP 0x5 +#define LTC2632_CMD_INTERNAL_REFER 0x6 +#define LTC2632_CMD_EXTERNAL_REFER 0x7 + +/** + * struct ltc2632_chip_info - chip specific information + * @channels: channel spec for the DAC + * @num_channels: DAC channel count of the chip + * @vref_mv: internal reference voltage + */ +struct ltc2632_chip_info { + const struct iio_chan_spec *channels; + const size_t num_channels; + const int vref_mv; +}; + +/** + * struct ltc2632_state - driver instance specific data + * @spi_dev: pointer to the spi_device struct + * @powerdown_cache_mask: used to show current channel powerdown state + * @vref_mv: used reference voltage (internal or external) + */ +struct ltc2632_state { + struct spi_device *spi_dev; + unsigned int powerdown_cache_mask; + int vref_mv; +}; + +static int ltc2632_spi_write(struct spi_device *spi, + u8 cmd, u8 addr, u16 val, u8 shift) +{ + u32 data; + u8 msg[3]; + + /* + * The input shift register is 24 bits wide. + * The next four are the command bits, C3 to C0, + * followed by the 4-bit DAC address, A3 to A0, and then the + * 16-, 12-, 10-, 8-bit data-word. The data-word comprises the + * 16-, 12-, 10-, 8-bit input code followed by 0, 4, 6, or 8 + * don't care bits. + */ + data = (cmd << 20) | (addr << 16) | (val << shift); + put_unaligned_be24(data, &msg[0]); + + return spi_write(spi, msg, sizeof(msg)); +} + +static int ltc2632_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + const struct ltc2632_state *st = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_SCALE: + *val = st->vref_mv; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + +static int ltc2632_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ltc2632_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val >= (1 << chan->scan_type.realbits) || val < 0) + return -EINVAL; + + return ltc2632_spi_write(st->spi_dev, + LTC2632_CMD_WRITE_INPUT_N_UPDATE_N, + chan->address, val, + chan->scan_type.shift); + default: + return -EINVAL; + } +} + +static ssize_t ltc2632_read_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ltc2632_state *st = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", + !!(st->powerdown_cache_mask & (1 << chan->channel))); +} + +static ssize_t ltc2632_write_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, + size_t len) +{ + bool pwr_down; + int ret; + struct ltc2632_state *st = iio_priv(indio_dev); + + ret = kstrtobool(buf, &pwr_down); + if (ret) + return ret; + + if (pwr_down) + st->powerdown_cache_mask |= (1 << chan->channel); + else + st->powerdown_cache_mask &= ~(1 << chan->channel); + + ret = ltc2632_spi_write(st->spi_dev, + LTC2632_CMD_POWERDOWN_DAC_N, + chan->channel, 0, 0); + + return ret ? ret : len; +} + +static const struct iio_info ltc2632_info = { + .write_raw = ltc2632_write_raw, + .read_raw = ltc2632_read_raw, +}; + +static const struct iio_chan_spec_ext_info ltc2632_ext_info[] = { + { + .name = "powerdown", + .read = ltc2632_read_dac_powerdown, + .write = ltc2632_write_dac_powerdown, + .shared = IIO_SEPARATE, + }, + { } +}; + +#define LTC2632_CHANNEL(_chan, _bits) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (_chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .address = (_chan), \ + .scan_type = { \ + .realbits = (_bits), \ + .shift = 16 - (_bits), \ + }, \ + .ext_info = ltc2632_ext_info, \ +} + +#define DECLARE_LTC2632_CHANNELS(_name, _bits) \ + const struct iio_chan_spec _name ## _channels[] = { \ + LTC2632_CHANNEL(0, _bits), \ + LTC2632_CHANNEL(1, _bits), \ + LTC2632_CHANNEL(2, _bits), \ + LTC2632_CHANNEL(3, _bits), \ + LTC2632_CHANNEL(4, _bits), \ + LTC2632_CHANNEL(5, _bits), \ + LTC2632_CHANNEL(6, _bits), \ + LTC2632_CHANNEL(7, _bits), \ + } + +static DECLARE_LTC2632_CHANNELS(ltc2632x16, 16); +static DECLARE_LTC2632_CHANNELS(ltc2632x12, 12); +static DECLARE_LTC2632_CHANNELS(ltc2632x10, 10); +static DECLARE_LTC2632_CHANNELS(ltc2632x8, 8); + +static const struct ltc2632_chip_info ltc2632l12_chip_info = { + .channels = ltc2632x12_channels, + .num_channels = 2, + .vref_mv = 2500, +}; + +static const struct ltc2632_chip_info ltc2632l10_chip_info = { + .channels = ltc2632x10_channels, + .num_channels = 2, + .vref_mv = 2500, +}; + +static const struct ltc2632_chip_info ltc2632l8_chip_info = { + .channels = ltc2632x8_channels, + .num_channels = 2, + .vref_mv = 2500, +}; + +static const struct ltc2632_chip_info ltc2632h12_chip_info = { + .channels = ltc2632x12_channels, + .num_channels = 2, + .vref_mv = 4096, +}; + +static const struct ltc2632_chip_info ltc2632h10_chip_info = { + .channels = ltc2632x10_channels, + .num_channels = 2, + .vref_mv = 4096, +}; + +static const struct ltc2632_chip_info ltc2632h8_chip_info = { + .channels = ltc2632x8_channels, + .num_channels = 2, + .vref_mv = 4096, +}; + +static const struct ltc2632_chip_info ltc2634l12_chip_info = { + .channels = ltc2632x12_channels, + .num_channels = 4, + .vref_mv = 2500, +}; + +static const struct ltc2632_chip_info ltc2634l10_chip_info = { + .channels = ltc2632x10_channels, + .num_channels = 4, + .vref_mv = 2500, +}; + +static const struct ltc2632_chip_info ltc2634l8_chip_info = { + .channels = ltc2632x8_channels, + .num_channels = 4, + .vref_mv = 2500, +}; + +static const struct ltc2632_chip_info ltc2634h12_chip_info = { + .channels = ltc2632x12_channels, + .num_channels = 4, + .vref_mv = 4096, +}; + +static const struct ltc2632_chip_info ltc2634h10_chip_info = { + .channels = ltc2632x10_channels, + .num_channels = 4, + .vref_mv = 4096, +}; + +static const struct ltc2632_chip_info ltc2634h8_chip_info = { + .channels = ltc2632x8_channels, + .num_channels = 4, + .vref_mv = 4096, +}; + +static const struct ltc2632_chip_info ltc2636l12_chip_info = { + .channels = ltc2632x12_channels, + .num_channels = 8, + .vref_mv = 2500, +}; + +static const struct ltc2632_chip_info ltc2636l10_chip_info = { + .channels = ltc2632x10_channels, + .num_channels = 8, + .vref_mv = 2500, +}; + +static const struct ltc2632_chip_info ltc2636l8_chip_info = { + .channels = ltc2632x8_channels, + .num_channels = 8, + .vref_mv = 2500, +}; + +static const struct ltc2632_chip_info ltc2636h12_chip_info = { + .channels = ltc2632x12_channels, + .num_channels = 8, + .vref_mv = 4096, +}; + +static const struct ltc2632_chip_info ltc2636h10_chip_info = { + .channels = ltc2632x10_channels, + .num_channels = 8, + .vref_mv = 4096, +}; + +static const struct ltc2632_chip_info ltc2636h8_chip_info = { + .channels = ltc2632x8_channels, + .num_channels = 8, + .vref_mv = 4096, +}; + +static const struct ltc2632_chip_info ltc2654l16_chip_info = { + .channels = ltc2632x16_channels, + .num_channels = 4, + .vref_mv = 2500, +}; + +static const struct ltc2632_chip_info ltc2654h16_chip_info = { + .channels = ltc2632x16_channels, + .num_channels = 4, + .vref_mv = 4096, +}; + +static int ltc2632_probe(struct spi_device *spi) +{ + struct ltc2632_state *st; + struct iio_dev *indio_dev; + struct ltc2632_chip_info *chip_info; + bool has_external_vref; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->spi_dev = spi; + + chip_info = (struct ltc2632_chip_info *) + spi_get_device_id(spi)->driver_data; + + ret = devm_regulator_get_enable_read_voltage(&spi->dev, "vref"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(&spi->dev, ret, + "Failed to get vref regulator voltage\n"); + + has_external_vref = ret != -ENODEV; + st->vref_mv = has_external_vref ? ret / 1000 : chip_info->vref_mv; + + if (has_external_vref) { + ret = ltc2632_spi_write(spi, LTC2632_CMD_EXTERNAL_REFER, + 0, 0, 0); + if (ret) + return dev_err_probe(&spi->dev, ret, + "Set external reference command failed\n"); + } else { + ret = ltc2632_spi_write(spi, LTC2632_CMD_INTERNAL_REFER, + 0, 0, 0); + if (ret) + return dev_err_probe(&spi->dev, ret, + "Set internal reference command failed\n"); + } + + indio_dev->name = fwnode_get_name(dev_fwnode(&spi->dev)) ?: spi_get_device_id(spi)->name; + indio_dev->info = <c2632_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = chip_info->channels; + indio_dev->num_channels = chip_info->num_channels; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id ltc2632_id[] = { + { .name = "ltc2632-l12", .driver_data = (kernel_ulong_t)<c2632l12_chip_info }, + { .name = "ltc2632-l10", .driver_data = (kernel_ulong_t)<c2632l10_chip_info }, + { .name = "ltc2632-l8", .driver_data = (kernel_ulong_t)<c2632l8_chip_info }, + { .name = "ltc2632-h12", .driver_data = (kernel_ulong_t)<c2632h12_chip_info }, + { .name = "ltc2632-h10", .driver_data = (kernel_ulong_t)<c2632h10_chip_info }, + { .name = "ltc2632-h8", .driver_data = (kernel_ulong_t)<c2632h8_chip_info }, + { .name = "ltc2634-l12", .driver_data = (kernel_ulong_t)<c2634l12_chip_info }, + { .name = "ltc2634-l10", .driver_data = (kernel_ulong_t)<c2634l10_chip_info }, + { .name = "ltc2634-l8", .driver_data = (kernel_ulong_t)<c2634l8_chip_info }, + { .name = "ltc2634-h12", .driver_data = (kernel_ulong_t)<c2634h12_chip_info }, + { .name = "ltc2634-h10", .driver_data = (kernel_ulong_t)<c2634h10_chip_info }, + { .name = "ltc2634-h8", .driver_data = (kernel_ulong_t)<c2634h8_chip_info }, + { .name = "ltc2636-l12", .driver_data = (kernel_ulong_t)<c2636l12_chip_info }, + { .name = "ltc2636-l10", .driver_data = (kernel_ulong_t)<c2636l10_chip_info }, + { .name = "ltc2636-l8", .driver_data = (kernel_ulong_t)<c2636l8_chip_info }, + { .name = "ltc2636-h12", .driver_data = (kernel_ulong_t)<c2636h12_chip_info }, + { .name = "ltc2636-h10", .driver_data = (kernel_ulong_t)<c2636h10_chip_info }, + { .name = "ltc2636-h8", .driver_data = (kernel_ulong_t)<c2636h8_chip_info }, + { .name = "ltc2654-l16", .driver_data = (kernel_ulong_t)<c2654l16_chip_info }, + { .name = "ltc2654-l12", .driver_data = (kernel_ulong_t)<c2634l12_chip_info }, + { .name = "ltc2654-h16", .driver_data = (kernel_ulong_t)<c2654h16_chip_info }, + { .name = "ltc2654-h12", .driver_data = (kernel_ulong_t)<c2634h12_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ltc2632_id); + +static const struct of_device_id ltc2632_of_match[] = { + { .compatible = "lltc,ltc2632-l12", .data = <c2632l12_chip_info }, + { .compatible = "lltc,ltc2632-l10", .data = <c2632l10_chip_info }, + { .compatible = "lltc,ltc2632-l8", .data = <c2632l8_chip_info }, + { .compatible = "lltc,ltc2632-h12", .data = <c2632h12_chip_info }, + { .compatible = "lltc,ltc2632-h10", .data = <c2632h10_chip_info }, + { .compatible = "lltc,ltc2632-h8", .data = <c2632h8_chip_info }, + { .compatible = "lltc,ltc2634-l12", .data = <c2634l12_chip_info }, + { .compatible = "lltc,ltc2634-l10", .data = <c2634l10_chip_info }, + { .compatible = "lltc,ltc2634-l8", .data = <c2634l8_chip_info }, + { .compatible = "lltc,ltc2634-h12", .data = <c2634h12_chip_info }, + { .compatible = "lltc,ltc2634-h10", .data = <c2634h10_chip_info }, + { .compatible = "lltc,ltc2634-h8", .data = <c2634h8_chip_info }, + { .compatible = "lltc,ltc2636-l12", .data = <c2636l12_chip_info }, + { .compatible = "lltc,ltc2636-l10", .data = <c2636l10_chip_info }, + { .compatible = "lltc,ltc2636-l8", .data = <c2636l8_chip_info }, + { .compatible = "lltc,ltc2636-h12", .data = <c2636h12_chip_info }, + { .compatible = "lltc,ltc2636-h10", .data = <c2636h10_chip_info }, + { .compatible = "lltc,ltc2636-h8", .data = <c2636h8_chip_info }, + { .compatible = "lltc,ltc2654-l16", .data = <c2654l16_chip_info }, + { .compatible = "lltc,ltc2654-h16", .data = <c2654h16_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ltc2632_of_match); + +static struct spi_driver ltc2632_driver = { + .driver = { + .name = "ltc2632", + .of_match_table = ltc2632_of_match, + }, + .probe = ltc2632_probe, + .id_table = ltc2632_id, +}; +module_spi_driver(ltc2632_driver); + +MODULE_AUTHOR("Maxime Roussin-Belanger "); +MODULE_DESCRIPTION("LTC2632 and similar DAC SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ltc2664.c b/drivers/iio/dac/ltc2664.c new file mode 100644 index 000000000..35bec55eb --- /dev/null +++ b/drivers/iio/dac/ltc2664.c @@ -0,0 +1,737 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * LTC2664 4 channel, 12-/16-Bit Voltage Output SoftSpan DAC driver + * LTC2672 5 channel, 12-/16-Bit Current Output Softspan DAC driver + * + * Copyright 2024 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LTC2664_CMD_WRITE_N(n) (0x00 + (n)) +#define LTC2664_CMD_UPDATE_N(n) (0x10 + (n)) +#define LTC2664_CMD_WRITE_N_UPDATE_ALL 0x20 +#define LTC2664_CMD_WRITE_N_UPDATE_N(n) (0x30 + (n)) +#define LTC2664_CMD_POWER_DOWN_N(n) (0x40 + (n)) +#define LTC2664_CMD_POWER_DOWN_ALL 0x50 +#define LTC2664_CMD_SPAN_N(n) (0x60 + (n)) +#define LTC2664_CMD_CONFIG 0x70 +#define LTC2664_CMD_MUX 0xB0 +#define LTC2664_CMD_TOGGLE_SEL 0xC0 +#define LTC2664_CMD_GLOBAL_TOGGLE 0xD0 +#define LTC2664_CMD_NO_OPERATION 0xF0 +#define LTC2664_REF_DISABLE 0x0001 +#define LTC2664_MSPAN_SOFTSPAN 7 + +#define LTC2672_MAX_CHANNEL 5 +#define LTC2672_MAX_SPAN 7 +#define LTC2672_SCALE_MULTIPLIER(n) (50 * BIT(n)) + +enum { + LTC2664_SPAN_RANGE_0V_5V, + LTC2664_SPAN_RANGE_0V_10V, + LTC2664_SPAN_RANGE_M5V_5V, + LTC2664_SPAN_RANGE_M10V_10V, + LTC2664_SPAN_RANGE_M2V5_2V5, +}; + +enum { + LTC2664_INPUT_A, + LTC2664_INPUT_B, + LTC2664_INPUT_B_AVAIL, + LTC2664_POWERDOWN, + LTC2664_POWERDOWN_MODE, + LTC2664_TOGGLE_EN, + LTC2664_GLOBAL_TOGGLE, +}; + +static const u16 ltc2664_mspan_lut[8][2] = { + { LTC2664_SPAN_RANGE_M10V_10V, 32768 }, /* MPS2=0, MPS1=0, MSP0=0 (0)*/ + { LTC2664_SPAN_RANGE_M5V_5V, 32768 }, /* MPS2=0, MPS1=0, MSP0=1 (1)*/ + { LTC2664_SPAN_RANGE_M2V5_2V5, 32768 }, /* MPS2=0, MPS1=1, MSP0=0 (2)*/ + { LTC2664_SPAN_RANGE_0V_10V, 0 }, /* MPS2=0, MPS1=1, MSP0=1 (3)*/ + { LTC2664_SPAN_RANGE_0V_10V, 32768 }, /* MPS2=1, MPS1=0, MSP0=0 (4)*/ + { LTC2664_SPAN_RANGE_0V_5V, 0 }, /* MPS2=1, MPS1=0, MSP0=1 (5)*/ + { LTC2664_SPAN_RANGE_0V_5V, 32768 }, /* MPS2=1, MPS1=1, MSP0=0 (6)*/ + { LTC2664_SPAN_RANGE_0V_5V, 0 } /* MPS2=1, MPS1=1, MSP0=1 (7)*/ +}; + +struct ltc2664_state; + +struct ltc2664_chip_info { + const char *name; + int (*scale_get)(const struct ltc2664_state *st, int c); + int (*offset_get)(const struct ltc2664_state *st, int c); + int measurement_type; + unsigned int num_channels; + const int (*span_helper)[2]; + unsigned int num_span; + unsigned int internal_vref_mv; + bool manual_span_support; + bool rfsadj_support; +}; + +struct ltc2664_chan { + /* indicates if the channel should be toggled */ + bool toggle_chan; + /* indicates if the channel is in powered down state */ + bool powerdown; + /* span code of the channel */ + u8 span; + /* raw data of the current state of the chip registers (A/B) */ + u16 raw[2]; +}; + +struct ltc2664_state { + struct spi_device *spi; + struct regmap *regmap; + struct ltc2664_chan channels[LTC2672_MAX_CHANNEL]; + /* lock to protect against multiple access to the device and shared data */ + struct mutex lock; + const struct ltc2664_chip_info *chip_info; + struct iio_chan_spec *iio_channels; + int vref_mv; + u32 rfsadj_ohms; + u32 toggle_sel; + bool global_toggle; +}; + +static const int ltc2664_span_helper[][2] = { + { 0, 5000 }, + { 0, 10000 }, + { -5000, 5000 }, + { -10000, 10000 }, + { -2500, 2500 }, +}; + +static const int ltc2672_span_helper[][2] = { + { 0, 0 }, + { 0, 3125 }, + { 0, 6250 }, + { 0, 12500 }, + { 0, 25000 }, + { 0, 50000 }, + { 0, 100000 }, + { 0, 200000 }, + { 0, 300000 }, +}; + +static int ltc2664_scale_get(const struct ltc2664_state *st, int c) +{ + const struct ltc2664_chan *chan = &st->channels[c]; + const int (*span_helper)[2] = st->chip_info->span_helper; + int span, fs; + + span = chan->span; + if (span < 0) + return span; + + fs = span_helper[span][1] - span_helper[span][0]; + + return fs * st->vref_mv / 2500; +} + +static int ltc2672_scale_get(const struct ltc2664_state *st, int c) +{ + const struct ltc2664_chan *chan = &st->channels[c]; + int span, fs; + + span = chan->span - 1; + if (span < 0) + return span; + + fs = 1000 * st->vref_mv; + + if (span == LTC2672_MAX_SPAN) + return mul_u64_u32_div(4800, fs, st->rfsadj_ohms); + + return mul_u64_u32_div(LTC2672_SCALE_MULTIPLIER(span), fs, st->rfsadj_ohms); +} + +static int ltc2664_offset_get(const struct ltc2664_state *st, int c) +{ + const struct ltc2664_chan *chan = &st->channels[c]; + int span; + + span = chan->span; + if (span < 0) + return span; + + if (st->chip_info->span_helper[span][0] < 0) + return -32768; + + return 0; +} + +static int ltc2664_dac_code_write(struct ltc2664_state *st, u32 chan, u32 input, + u16 code) +{ + struct ltc2664_chan *c = &st->channels[chan]; + int ret, reg; + + guard(mutex)(&st->lock); + /* select the correct input register to write to */ + if (c->toggle_chan) { + ret = regmap_write(st->regmap, LTC2664_CMD_TOGGLE_SEL, + input << chan); + if (ret) + return ret; + } + /* + * If in toggle mode the dac should be updated by an + * external signal (or sw toggle) and not here. + */ + if (st->toggle_sel & BIT(chan)) + reg = LTC2664_CMD_WRITE_N(chan); + else + reg = LTC2664_CMD_WRITE_N_UPDATE_N(chan); + + ret = regmap_write(st->regmap, reg, code); + if (ret) + return ret; + + c->raw[input] = code; + + if (c->toggle_chan) { + ret = regmap_write(st->regmap, LTC2664_CMD_TOGGLE_SEL, + st->toggle_sel); + if (ret) + return ret; + } + + return 0; +} + +static void ltc2664_dac_code_read(struct ltc2664_state *st, u32 chan, u32 input, + u32 *code) +{ + guard(mutex)(&st->lock); + *code = st->channels[chan].raw[input]; +} + +static const int ltc2664_raw_range[] = { 0, 1, U16_MAX }; + +static int ltc2664_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long info) +{ + switch (info) { + case IIO_CHAN_INFO_RAW: + *vals = ltc2664_raw_range; + *type = IIO_VAL_INT; + + return IIO_AVAIL_RANGE; + default: + return -EINVAL; + } +} + +static int ltc2664_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long info) +{ + struct ltc2664_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_RAW: + ltc2664_dac_code_read(st, chan->channel, LTC2664_INPUT_A, val); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + *val = st->chip_info->offset_get(st, chan->channel); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = st->chip_info->scale_get(st, chan->channel); + + *val2 = 16; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static int ltc2664_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long info) +{ + struct ltc2664_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (val > U16_MAX || val < 0) + return -EINVAL; + + return ltc2664_dac_code_write(st, chan->channel, + LTC2664_INPUT_A, val); + default: + return -EINVAL; + } +} + +static ssize_t ltc2664_reg_bool_get(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ltc2664_state *st = iio_priv(indio_dev); + u32 val; + + guard(mutex)(&st->lock); + switch (private) { + case LTC2664_POWERDOWN: + val = st->channels[chan->channel].powerdown; + + return sysfs_emit(buf, "%u\n", val); + case LTC2664_POWERDOWN_MODE: + return sysfs_emit(buf, "42kohm_to_gnd\n"); + case LTC2664_TOGGLE_EN: + val = !!(st->toggle_sel & BIT(chan->channel)); + + return sysfs_emit(buf, "%u\n", val); + case LTC2664_GLOBAL_TOGGLE: + val = st->global_toggle; + + return sysfs_emit(buf, "%u\n", val); + default: + return -EINVAL; + } +} + +static ssize_t ltc2664_reg_bool_set(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ltc2664_state *st = iio_priv(indio_dev); + int ret; + bool en; + + ret = kstrtobool(buf, &en); + if (ret) + return ret; + + guard(mutex)(&st->lock); + switch (private) { + case LTC2664_POWERDOWN: + ret = regmap_write(st->regmap, + en ? LTC2664_CMD_POWER_DOWN_N(chan->channel) : + LTC2664_CMD_UPDATE_N(chan->channel), en); + if (ret) + return ret; + + st->channels[chan->channel].powerdown = en; + + return len; + case LTC2664_TOGGLE_EN: + if (en) + st->toggle_sel |= BIT(chan->channel); + else + st->toggle_sel &= ~BIT(chan->channel); + + ret = regmap_write(st->regmap, LTC2664_CMD_TOGGLE_SEL, + st->toggle_sel); + if (ret) + return ret; + + return len; + case LTC2664_GLOBAL_TOGGLE: + ret = regmap_write(st->regmap, LTC2664_CMD_GLOBAL_TOGGLE, en); + if (ret) + return ret; + + st->global_toggle = en; + + return len; + default: + return -EINVAL; + } +} + +static ssize_t ltc2664_dac_input_read(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ltc2664_state *st = iio_priv(indio_dev); + u32 val; + + if (private == LTC2664_INPUT_B_AVAIL) + return sysfs_emit(buf, "[%u %u %u]\n", ltc2664_raw_range[0], + ltc2664_raw_range[1], + ltc2664_raw_range[2] / 4); + + ltc2664_dac_code_read(st, chan->channel, private, &val); + + return sysfs_emit(buf, "%u\n", val); +} + +static ssize_t ltc2664_dac_input_write(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ltc2664_state *st = iio_priv(indio_dev); + int ret; + u16 val; + + if (private == LTC2664_INPUT_B_AVAIL) + return -EINVAL; + + ret = kstrtou16(buf, 10, &val); + if (ret) + return ret; + + ret = ltc2664_dac_code_write(st, chan->channel, private, val); + if (ret) + return ret; + + return len; +} + +static int ltc2664_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct ltc2664_state *st = iio_priv(indio_dev); + + if (readval) + return -EOPNOTSUPP; + + return regmap_write(st->regmap, reg, writeval); +} + +#define LTC2664_CHAN_EXT_INFO(_name, _what, _shared, _read, _write) { \ + .name = _name, \ + .read = (_read), \ + .write = (_write), \ + .private = (_what), \ + .shared = (_shared), \ +} + +/* + * For toggle mode we only expose the symbol attr (sw_toggle) in case a TGPx is + * not provided in dts. + */ +static const struct iio_chan_spec_ext_info ltc2664_toggle_sym_ext_info[] = { + LTC2664_CHAN_EXT_INFO("raw0", LTC2664_INPUT_A, IIO_SEPARATE, + ltc2664_dac_input_read, ltc2664_dac_input_write), + LTC2664_CHAN_EXT_INFO("raw1", LTC2664_INPUT_B, IIO_SEPARATE, + ltc2664_dac_input_read, ltc2664_dac_input_write), + LTC2664_CHAN_EXT_INFO("powerdown", LTC2664_POWERDOWN, IIO_SEPARATE, + ltc2664_reg_bool_get, ltc2664_reg_bool_set), + LTC2664_CHAN_EXT_INFO("powerdown_mode", LTC2664_POWERDOWN_MODE, + IIO_SEPARATE, ltc2664_reg_bool_get, NULL), + LTC2664_CHAN_EXT_INFO("symbol", LTC2664_GLOBAL_TOGGLE, IIO_SEPARATE, + ltc2664_reg_bool_get, ltc2664_reg_bool_set), + LTC2664_CHAN_EXT_INFO("toggle_en", LTC2664_TOGGLE_EN, + IIO_SEPARATE, ltc2664_reg_bool_get, + ltc2664_reg_bool_set), + { } +}; + +static const struct iio_chan_spec_ext_info ltc2664_ext_info[] = { + LTC2664_CHAN_EXT_INFO("powerdown", LTC2664_POWERDOWN, IIO_SEPARATE, + ltc2664_reg_bool_get, ltc2664_reg_bool_set), + LTC2664_CHAN_EXT_INFO("powerdown_mode", LTC2664_POWERDOWN_MODE, + IIO_SEPARATE, ltc2664_reg_bool_get, NULL), + { } +}; + +static const struct iio_chan_spec ltc2664_channel_template = { + .indexed = 1, + .output = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_RAW), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW), + .ext_info = ltc2664_ext_info, +}; + +static const struct ltc2664_chip_info ltc2664_chip = { + .name = "ltc2664", + .scale_get = ltc2664_scale_get, + .offset_get = ltc2664_offset_get, + .measurement_type = IIO_VOLTAGE, + .num_channels = 4, + .span_helper = ltc2664_span_helper, + .num_span = ARRAY_SIZE(ltc2664_span_helper), + .internal_vref_mv = 2500, + .manual_span_support = true, + .rfsadj_support = false, +}; + +static const struct ltc2664_chip_info ltc2672_chip = { + .name = "ltc2672", + .scale_get = ltc2672_scale_get, + .offset_get = ltc2664_offset_get, + .measurement_type = IIO_CURRENT, + .num_channels = 5, + .span_helper = ltc2672_span_helper, + .num_span = ARRAY_SIZE(ltc2672_span_helper), + .internal_vref_mv = 1250, + .manual_span_support = false, + .rfsadj_support = true, +}; + +static int ltc2664_set_span(const struct ltc2664_state *st, int min, int max, + int chan) +{ + const struct ltc2664_chip_info *chip_info = st->chip_info; + const int (*span_helper)[2] = chip_info->span_helper; + int span, ret; + + for (span = 0; span < chip_info->num_span; span++) { + if (min == span_helper[span][0] && max == span_helper[span][1]) + break; + } + + if (span == chip_info->num_span) + return -EINVAL; + + ret = regmap_write(st->regmap, LTC2664_CMD_SPAN_N(chan), span); + if (ret) + return ret; + + return span; +} + +static int ltc2664_channel_config(struct ltc2664_state *st) +{ + const struct ltc2664_chip_info *chip_info = st->chip_info; + struct device *dev = &st->spi->dev; + u32 reg, tmp[2], mspan; + int ret; + + mspan = LTC2664_MSPAN_SOFTSPAN; + ret = device_property_read_u32(dev, "adi,manual-span-operation-config", + &mspan); + if (!ret) { + if (!chip_info->manual_span_support) + return dev_err_probe(dev, -EINVAL, + "adi,manual-span-operation-config not supported\n"); + + if (mspan >= ARRAY_SIZE(ltc2664_mspan_lut)) + return dev_err_probe(dev, -EINVAL, + "adi,manual-span-operation-config not in range\n"); + } + + st->rfsadj_ohms = 20000; + ret = device_property_read_u32(dev, "adi,rfsadj-ohms", &st->rfsadj_ohms); + if (!ret) { + if (!chip_info->rfsadj_support) + return dev_err_probe(dev, -EINVAL, + "adi,rfsadj-ohms not supported\n"); + + if (st->rfsadj_ohms < 19000 || st->rfsadj_ohms > 41000) + return dev_err_probe(dev, -EINVAL, + "adi,rfsadj-ohms not in range\n"); + } + + device_for_each_child_node_scoped(dev, child) { + struct ltc2664_chan *chan; + + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get reg property\n"); + + if (reg >= chip_info->num_channels) + return dev_err_probe(dev, -EINVAL, + "reg bigger than: %d\n", + chip_info->num_channels); + + chan = &st->channels[reg]; + + if (fwnode_property_read_bool(child, "adi,toggle-mode")) { + chan->toggle_chan = true; + /* assume sw toggle ABI */ + st->iio_channels[reg].ext_info = ltc2664_toggle_sym_ext_info; + + /* + * Clear IIO_CHAN_INFO_RAW bit as toggle channels expose + * out_voltage/current_raw{0|1} files. + */ + __clear_bit(IIO_CHAN_INFO_RAW, + &st->iio_channels[reg].info_mask_separate); + } + + chan->raw[0] = ltc2664_mspan_lut[mspan][1]; + chan->raw[1] = ltc2664_mspan_lut[mspan][1]; + + chan->span = ltc2664_mspan_lut[mspan][0]; + + ret = fwnode_property_read_u32_array(child, "output-range-microvolt", + tmp, ARRAY_SIZE(tmp)); + if (!ret && mspan == LTC2664_MSPAN_SOFTSPAN) { + ret = ltc2664_set_span(st, tmp[0] / 1000, tmp[1] / 1000, reg); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to set span\n"); + chan->span = ret; + } + + ret = fwnode_property_read_u32_array(child, "output-range-microamp", + tmp, ARRAY_SIZE(tmp)); + if (!ret) { + ret = ltc2664_set_span(st, 0, tmp[1] / 1000, reg); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to set span\n"); + chan->span = ret; + } + } + + return 0; +} + +static int ltc2664_setup(struct ltc2664_state *st) +{ + const struct ltc2664_chip_info *chip_info = st->chip_info; + struct gpio_desc *gpio; + int ret, i; + + /* If we have a clr/reset pin, use that to reset the chip. */ + gpio = devm_gpiod_get_optional(&st->spi->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(gpio)) + return dev_err_probe(&st->spi->dev, PTR_ERR(gpio), + "Failed to get reset gpio"); + if (gpio) { + fsleep(1000); + gpiod_set_value_cansleep(gpio, 0); + } + + /* + * Duplicate the default channel configuration as it can change during + * @ltc2664_channel_config() + */ + st->iio_channels = devm_kcalloc(&st->spi->dev, + chip_info->num_channels, + sizeof(struct iio_chan_spec), + GFP_KERNEL); + if (!st->iio_channels) + return -ENOMEM; + + for (i = 0; i < chip_info->num_channels; i++) { + st->iio_channels[i] = ltc2664_channel_template; + st->iio_channels[i].type = chip_info->measurement_type; + st->iio_channels[i].channel = i; + } + + ret = ltc2664_channel_config(st); + if (ret) + return ret; + + return regmap_set_bits(st->regmap, LTC2664_CMD_CONFIG, LTC2664_REF_DISABLE); +} + +static const struct regmap_config ltc2664_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = LTC2664_CMD_NO_OPERATION, +}; + +static const struct iio_info ltc2664_info = { + .write_raw = ltc2664_write_raw, + .read_raw = ltc2664_read_raw, + .read_avail = ltc2664_read_avail, + .debugfs_reg_access = ltc2664_reg_access, +}; + +static int ltc2664_probe(struct spi_device *spi) +{ + static const char * const regulators[] = { "vcc", "iovcc", "v-neg" }; + const struct ltc2664_chip_info *chip_info; + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ltc2664_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + + chip_info = spi_get_device_match_data(spi); + if (!chip_info) + return -ENODEV; + + st->chip_info = chip_info; + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + st->regmap = devm_regmap_init_spi(spi, <c2664_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "Failed to init regmap"); + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulators), + regulators); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulators\n"); + + ret = devm_regulator_get_enable_read_voltage(dev, "ref"); + if (ret < 0 && ret != -ENODEV) + return ret; + + st->vref_mv = ret > 0 ? ret / 1000 : chip_info->internal_vref_mv; + + ret = ltc2664_setup(st); + if (ret) + return ret; + + indio_dev->name = chip_info->name; + indio_dev->info = <c2664_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->iio_channels; + indio_dev->num_channels = chip_info->num_channels; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id ltc2664_id[] = { + { .name = "ltc2664", .driver_data = (kernel_ulong_t)<c2664_chip }, + { .name = "ltc2672", .driver_data = (kernel_ulong_t)<c2672_chip }, + { } +}; +MODULE_DEVICE_TABLE(spi, ltc2664_id); + +static const struct of_device_id ltc2664_of_id[] = { + { .compatible = "adi,ltc2664", .data = <c2664_chip }, + { .compatible = "adi,ltc2672", .data = <c2672_chip }, + { } +}; +MODULE_DEVICE_TABLE(of, ltc2664_of_id); + +static struct spi_driver ltc2664_driver = { + .driver = { + .name = "ltc2664", + .of_match_table = ltc2664_of_id, + }, + .probe = ltc2664_probe, + .id_table = ltc2664_id, +}; +module_spi_driver(ltc2664_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_AUTHOR("Kim Seer Paller "); +MODULE_DESCRIPTION("Analog Devices LTC2664 and LTC2672 DAC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/dac/ltc2688.c b/drivers/iio/dac/ltc2688.c new file mode 100644 index 000000000..0301bb7dc --- /dev/null +++ b/drivers/iio/dac/ltc2688.c @@ -0,0 +1,1011 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * LTC2688 16 channel, 16 bit Voltage Output SoftSpan DAC driver + * + * Copyright 2022 Analog Devices Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LTC2688_DAC_CHANNELS 16 + +#define LTC2688_CMD_CH_CODE(x) (0x00 + (x)) +#define LTC2688_CMD_CH_SETTING(x) (0x10 + (x)) +#define LTC2688_CMD_CH_OFFSET(x) (0X20 + (x)) +#define LTC2688_CMD_CH_GAIN(x) (0x30 + (x)) +#define LTC2688_CMD_CH_CODE_UPDATE(x) (0x40 + (x)) + +#define LTC2688_CMD_CONFIG 0x70 +#define LTC2688_CMD_POWERDOWN 0x71 +#define LTC2688_CMD_A_B_SELECT 0x72 +#define LTC2688_CMD_SW_TOGGLE 0x73 +#define LTC2688_CMD_TOGGLE_DITHER_EN 0x74 +#define LTC2688_CMD_THERMAL_STAT 0x77 +#define LTC2688_CMD_UPDATE_ALL 0x7C +#define LTC2688_CMD_NOOP 0xFF + +#define LTC2688_READ_OPERATION 0x80 + +/* Channel Settings */ +#define LTC2688_CH_SPAN_MSK GENMASK(2, 0) +#define LTC2688_CH_OVERRANGE_MSK BIT(3) +#define LTC2688_CH_TD_SEL_MSK GENMASK(5, 4) +#define LTC2688_CH_TGP_MAX 3 +#define LTC2688_CH_DIT_PER_MSK GENMASK(8, 6) +#define LTC2688_CH_DIT_PH_MSK GENMASK(10, 9) +#define LTC2688_CH_MODE_MSK BIT(11) + +#define LTC2688_DITHER_RAW_MASK GENMASK(15, 2) +#define LTC2688_CH_CALIBBIAS_MASK GENMASK(15, 2) +#define LTC2688_DITHER_RAW_MAX_VAL (BIT(14) - 1) +#define LTC2688_CH_CALIBBIAS_MAX_VAL (BIT(14) - 1) + +/* Configuration register */ +#define LTC2688_CONFIG_RST BIT(15) +#define LTC2688_CONFIG_EXT_REF BIT(1) + +#define LTC2688_DITHER_FREQ_AVAIL_N 5 + +enum { + LTC2688_SPAN_RANGE_0V_5V, + LTC2688_SPAN_RANGE_0V_10V, + LTC2688_SPAN_RANGE_M5V_5V, + LTC2688_SPAN_RANGE_M10V_10V, + LTC2688_SPAN_RANGE_M15V_15V, + LTC2688_SPAN_RANGE_MAX +}; + +enum { + LTC2688_MODE_DEFAULT, + LTC2688_MODE_DITHER_TOGGLE, +}; + +struct ltc2688_chan { + long dither_frequency[LTC2688_DITHER_FREQ_AVAIL_N]; + bool overrange; + bool toggle_chan; + u8 mode; +}; + +struct ltc2688_state { + struct spi_device *spi; + struct regmap *regmap; + struct ltc2688_chan channels[LTC2688_DAC_CHANNELS]; + struct iio_chan_spec *iio_chan; + /* lock to protect against multiple access to the device and shared data */ + struct mutex lock; + int vref; + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + */ + u8 tx_data[6] __aligned(IIO_DMA_MINALIGN); + u8 rx_data[3]; +}; + +static int ltc2688_spi_read(void *context, const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + struct ltc2688_state *st = context; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx_data, + .len = reg_size + val_size, + .cs_change = 1, + }, { + .tx_buf = st->tx_data + 3, + .rx_buf = st->rx_data, + .len = reg_size + val_size, + }, + }; + int ret; + + memcpy(st->tx_data, reg, reg_size); + + ret = spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers)); + if (ret) + return ret; + + memcpy(val, &st->rx_data[1], val_size); + + return 0; +} + +static int ltc2688_spi_write(void *context, const void *data, size_t count) +{ + struct ltc2688_state *st = context; + + return spi_write(st->spi, data, count); +} + +static int ltc2688_span_get(const struct ltc2688_state *st, int c) +{ + int ret, reg, span; + + ret = regmap_read(st->regmap, LTC2688_CMD_CH_SETTING(c), ®); + if (ret) + return ret; + + span = FIELD_GET(LTC2688_CH_SPAN_MSK, reg); + /* sanity check to make sure we don't get any weird value from the HW */ + if (span >= LTC2688_SPAN_RANGE_MAX) + return -EIO; + + return span; +} + +static const int ltc2688_span_helper[LTC2688_SPAN_RANGE_MAX][2] = { + {0, 5000}, {0, 10000}, {-5000, 5000}, {-10000, 10000}, {-15000, 15000}, +}; + +static int ltc2688_scale_get(const struct ltc2688_state *st, int c, int *val) +{ + const struct ltc2688_chan *chan = &st->channels[c]; + int span, fs; + + span = ltc2688_span_get(st, c); + if (span < 0) + return span; + + fs = ltc2688_span_helper[span][1] - ltc2688_span_helper[span][0]; + if (chan->overrange) + fs = mult_frac(fs, 105, 100); + + *val = DIV_ROUND_CLOSEST(fs * st->vref, 4096); + + return 0; +} + +static int ltc2688_offset_get(const struct ltc2688_state *st, int c, int *val) +{ + int span; + + span = ltc2688_span_get(st, c); + if (span < 0) + return span; + + if (ltc2688_span_helper[span][0] < 0) + *val = -32768; + else + *val = 0; + + return 0; +} + +enum { + LTC2688_INPUT_A, + LTC2688_INPUT_B, + LTC2688_INPUT_B_AVAIL, + LTC2688_DITHER_OFF, + LTC2688_DITHER_FREQ_AVAIL, +}; + +static int ltc2688_dac_code_write(struct ltc2688_state *st, u32 chan, u32 input, + u16 code) +{ + struct ltc2688_chan *c = &st->channels[chan]; + int ret, reg; + + /* 2 LSBs set to 0 if writing dither amplitude */ + if (!c->toggle_chan && input == LTC2688_INPUT_B) { + if (code > LTC2688_DITHER_RAW_MAX_VAL) + return -EINVAL; + + code = FIELD_PREP(LTC2688_DITHER_RAW_MASK, code); + } + + guard(mutex)(&st->lock); + /* select the correct input register to read from */ + ret = regmap_update_bits(st->regmap, LTC2688_CMD_A_B_SELECT, BIT(chan), + input << chan); + if (ret) + return ret; + + /* + * If in dither/toggle mode the dac should be updated by an + * external signal (or sw toggle) and not here. + */ + if (c->mode == LTC2688_MODE_DEFAULT) + reg = LTC2688_CMD_CH_CODE_UPDATE(chan); + else + reg = LTC2688_CMD_CH_CODE(chan); + + return regmap_write(st->regmap, reg, code); +} + +static int ltc2688_dac_code_read(struct ltc2688_state *st, u32 chan, u32 input, + u32 *code) +{ + struct ltc2688_chan *c = &st->channels[chan]; + int ret; + + guard(mutex)(&st->lock); + ret = regmap_update_bits(st->regmap, LTC2688_CMD_A_B_SELECT, BIT(chan), + input << chan); + if (ret) + return ret; + + ret = regmap_read(st->regmap, LTC2688_CMD_CH_CODE(chan), code); + if (ret) + return ret; + + if (!c->toggle_chan && input == LTC2688_INPUT_B) + *code = FIELD_GET(LTC2688_DITHER_RAW_MASK, *code); + + return 0; +} + +static const int ltc2688_raw_range[] = {0, 1, U16_MAX}; + +static int ltc2688_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long info) +{ + switch (info) { + case IIO_CHAN_INFO_RAW: + *vals = ltc2688_raw_range; + *type = IIO_VAL_INT; + return IIO_AVAIL_RANGE; + default: + return -EINVAL; + } +} + +static int ltc2688_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long info) +{ + struct ltc2688_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + ret = ltc2688_dac_code_read(st, chan->channel, LTC2688_INPUT_A, + val); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + ret = ltc2688_offset_get(st, chan->channel, val); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = ltc2688_scale_get(st, chan->channel, val); + if (ret) + return ret; + + *val2 = 16; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_CALIBBIAS: + ret = regmap_read(st->regmap, + LTC2688_CMD_CH_OFFSET(chan->channel), val); + if (ret) + return ret; + + *val = FIELD_GET(LTC2688_CH_CALIBBIAS_MASK, *val); + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + ret = regmap_read(st->regmap, + LTC2688_CMD_CH_GAIN(chan->channel), val); + if (ret) + return ret; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ltc2688_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long info) +{ + struct ltc2688_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (val > U16_MAX || val < 0) + return -EINVAL; + + return ltc2688_dac_code_write(st, chan->channel, + LTC2688_INPUT_A, val); + case IIO_CHAN_INFO_CALIBBIAS: + if (val > LTC2688_CH_CALIBBIAS_MAX_VAL) + return -EINVAL; + + return regmap_write(st->regmap, + LTC2688_CMD_CH_OFFSET(chan->channel), + FIELD_PREP(LTC2688_CH_CALIBBIAS_MASK, val)); + case IIO_CHAN_INFO_CALIBSCALE: + return regmap_write(st->regmap, + LTC2688_CMD_CH_GAIN(chan->channel), val); + default: + return -EINVAL; + } +} + +static ssize_t ltc2688_dither_toggle_set(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ltc2688_state *st = iio_priv(indio_dev); + struct ltc2688_chan *c = &st->channels[chan->channel]; + int ret; + bool en; + + ret = kstrtobool(buf, &en); + if (ret) + return ret; + + guard(mutex)(&st->lock); + ret = regmap_update_bits(st->regmap, LTC2688_CMD_TOGGLE_DITHER_EN, + BIT(chan->channel), en << chan->channel); + if (ret) + return ret; + + c->mode = en ? LTC2688_MODE_DITHER_TOGGLE : LTC2688_MODE_DEFAULT; + + return len; +} + +static ssize_t ltc2688_reg_bool_get(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + const struct ltc2688_state *st = iio_priv(indio_dev); + int ret; + u32 val; + + ret = regmap_read(st->regmap, private, &val); + if (ret) + return ret; + + return sysfs_emit(buf, "%u\n", !!(val & BIT(chan->channel))); +} + +static ssize_t ltc2688_reg_bool_set(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + const struct ltc2688_state *st = iio_priv(indio_dev); + int ret; + bool en; + + ret = kstrtobool(buf, &en); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, private, BIT(chan->channel), + en << chan->channel); + if (ret) + return ret; + + return len; +} + +static ssize_t ltc2688_dither_freq_avail(const struct ltc2688_state *st, + const struct ltc2688_chan *chan, + char *buf) +{ + int sz = 0; + u32 f; + + for (f = 0; f < ARRAY_SIZE(chan->dither_frequency); f++) + sz += sysfs_emit_at(buf, sz, "%ld ", chan->dither_frequency[f]); + + buf[sz - 1] = '\n'; + + return sz; +} + +static ssize_t ltc2688_dither_freq_get(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + const struct ltc2688_state *st = iio_priv(indio_dev); + const struct ltc2688_chan *c = &st->channels[chan->channel]; + u32 reg, freq; + int ret; + + if (private == LTC2688_DITHER_FREQ_AVAIL) + return ltc2688_dither_freq_avail(st, c, buf); + + ret = regmap_read(st->regmap, LTC2688_CMD_CH_SETTING(chan->channel), + ®); + if (ret) + return ret; + + freq = FIELD_GET(LTC2688_CH_DIT_PER_MSK, reg); + if (freq >= ARRAY_SIZE(c->dither_frequency)) + return -EIO; + + return sysfs_emit(buf, "%ld\n", c->dither_frequency[freq]); +} + +static ssize_t ltc2688_dither_freq_set(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + const struct ltc2688_state *st = iio_priv(indio_dev); + const struct ltc2688_chan *c = &st->channels[chan->channel]; + long val; + u32 freq; + int ret; + + if (private == LTC2688_DITHER_FREQ_AVAIL) + return -EINVAL; + + ret = kstrtol(buf, 10, &val); + if (ret) + return ret; + + for (freq = 0; freq < ARRAY_SIZE(c->dither_frequency); freq++) { + if (val == c->dither_frequency[freq]) + break; + } + + if (freq == ARRAY_SIZE(c->dither_frequency)) + return -EINVAL; + + ret = regmap_update_bits(st->regmap, + LTC2688_CMD_CH_SETTING(chan->channel), + LTC2688_CH_DIT_PER_MSK, + FIELD_PREP(LTC2688_CH_DIT_PER_MSK, freq)); + if (ret) + return ret; + + return len; +} + +static ssize_t ltc2688_dac_input_read(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ltc2688_state *st = iio_priv(indio_dev); + int ret; + u32 val; + + if (private == LTC2688_INPUT_B_AVAIL) + return sysfs_emit(buf, "[%u %u %u]\n", ltc2688_raw_range[0], + ltc2688_raw_range[1], + ltc2688_raw_range[2] / 4); + + if (private == LTC2688_DITHER_OFF) + return sysfs_emit(buf, "0\n"); + + ret = ltc2688_dac_code_read(st, chan->channel, private, &val); + if (ret) + return ret; + + return sysfs_emit(buf, "%u\n", val); +} + +static ssize_t ltc2688_dac_input_write(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ltc2688_state *st = iio_priv(indio_dev); + int ret; + u16 val; + + if (private == LTC2688_INPUT_B_AVAIL || private == LTC2688_DITHER_OFF) + return -EINVAL; + + ret = kstrtou16(buf, 10, &val); + if (ret) + return ret; + + ret = ltc2688_dac_code_write(st, chan->channel, private, val); + if (ret) + return ret; + + return len; +} + +static int ltc2688_get_dither_phase(struct iio_dev *dev, + const struct iio_chan_spec *chan) +{ + struct ltc2688_state *st = iio_priv(dev); + int ret, regval; + + ret = regmap_read(st->regmap, LTC2688_CMD_CH_SETTING(chan->channel), + ®val); + if (ret) + return ret; + + return FIELD_GET(LTC2688_CH_DIT_PH_MSK, regval); +} + +static int ltc2688_set_dither_phase(struct iio_dev *dev, + const struct iio_chan_spec *chan, + unsigned int phase) +{ + struct ltc2688_state *st = iio_priv(dev); + + return regmap_update_bits(st->regmap, + LTC2688_CMD_CH_SETTING(chan->channel), + LTC2688_CH_DIT_PH_MSK, + FIELD_PREP(LTC2688_CH_DIT_PH_MSK, phase)); +} + +static int ltc2688_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct ltc2688_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); +} + +static const char * const ltc2688_dither_phase[] = { + "0", "1.5708", "3.14159", "4.71239", +}; + +static const struct iio_enum ltc2688_dither_phase_enum = { + .items = ltc2688_dither_phase, + .num_items = ARRAY_SIZE(ltc2688_dither_phase), + .set = ltc2688_set_dither_phase, + .get = ltc2688_get_dither_phase, +}; + +#define LTC2688_CHAN_EXT_INFO(_name, _what, _shared, _read, _write) { \ + .name = _name, \ + .read = (_read), \ + .write = (_write), \ + .private = (_what), \ + .shared = (_shared), \ +} + +/* + * For toggle mode we only expose the symbol attr (sw_toggle) in case a TGPx is + * not provided in dts. + */ +static const struct iio_chan_spec_ext_info ltc2688_toggle_sym_ext_info[] = { + LTC2688_CHAN_EXT_INFO("raw0", LTC2688_INPUT_A, IIO_SEPARATE, + ltc2688_dac_input_read, ltc2688_dac_input_write), + LTC2688_CHAN_EXT_INFO("raw1", LTC2688_INPUT_B, IIO_SEPARATE, + ltc2688_dac_input_read, ltc2688_dac_input_write), + LTC2688_CHAN_EXT_INFO("toggle_en", LTC2688_CMD_TOGGLE_DITHER_EN, + IIO_SEPARATE, ltc2688_reg_bool_get, + ltc2688_dither_toggle_set), + LTC2688_CHAN_EXT_INFO("powerdown", LTC2688_CMD_POWERDOWN, IIO_SEPARATE, + ltc2688_reg_bool_get, ltc2688_reg_bool_set), + LTC2688_CHAN_EXT_INFO("symbol", LTC2688_CMD_SW_TOGGLE, IIO_SEPARATE, + ltc2688_reg_bool_get, ltc2688_reg_bool_set), + { } +}; + +static const struct iio_chan_spec_ext_info ltc2688_toggle_ext_info[] = { + LTC2688_CHAN_EXT_INFO("raw0", LTC2688_INPUT_A, IIO_SEPARATE, + ltc2688_dac_input_read, ltc2688_dac_input_write), + LTC2688_CHAN_EXT_INFO("raw1", LTC2688_INPUT_B, IIO_SEPARATE, + ltc2688_dac_input_read, ltc2688_dac_input_write), + LTC2688_CHAN_EXT_INFO("toggle_en", LTC2688_CMD_TOGGLE_DITHER_EN, + IIO_SEPARATE, ltc2688_reg_bool_get, + ltc2688_dither_toggle_set), + LTC2688_CHAN_EXT_INFO("powerdown", LTC2688_CMD_POWERDOWN, IIO_SEPARATE, + ltc2688_reg_bool_get, ltc2688_reg_bool_set), + { } +}; + +static const struct iio_chan_spec_ext_info ltc2688_dither_ext_info[] = { + LTC2688_CHAN_EXT_INFO("dither_raw", LTC2688_INPUT_B, IIO_SEPARATE, + ltc2688_dac_input_read, ltc2688_dac_input_write), + LTC2688_CHAN_EXT_INFO("dither_raw_available", LTC2688_INPUT_B_AVAIL, + IIO_SEPARATE, ltc2688_dac_input_read, + ltc2688_dac_input_write), + LTC2688_CHAN_EXT_INFO("dither_offset", LTC2688_DITHER_OFF, IIO_SEPARATE, + ltc2688_dac_input_read, ltc2688_dac_input_write), + /* + * Not IIO_ENUM because the available freq needs to be computed at + * probe. We could still use it, but it didn't felt much right. + */ + LTC2688_CHAN_EXT_INFO("dither_frequency", 0, IIO_SEPARATE, + ltc2688_dither_freq_get, ltc2688_dither_freq_set), + LTC2688_CHAN_EXT_INFO("dither_frequency_available", + LTC2688_DITHER_FREQ_AVAIL, IIO_SEPARATE, + ltc2688_dither_freq_get, ltc2688_dither_freq_set), + IIO_ENUM("dither_phase", IIO_SEPARATE, <c2688_dither_phase_enum), + IIO_ENUM_AVAILABLE("dither_phase", IIO_SEPARATE, + <c2688_dither_phase_enum), + LTC2688_CHAN_EXT_INFO("dither_en", LTC2688_CMD_TOGGLE_DITHER_EN, + IIO_SEPARATE, ltc2688_reg_bool_get, + ltc2688_dither_toggle_set), + LTC2688_CHAN_EXT_INFO("powerdown", LTC2688_CMD_POWERDOWN, IIO_SEPARATE, + ltc2688_reg_bool_get, ltc2688_reg_bool_set), + { } +}; + +static const struct iio_chan_spec_ext_info ltc2688_ext_info[] = { + LTC2688_CHAN_EXT_INFO("powerdown", LTC2688_CMD_POWERDOWN, IIO_SEPARATE, + ltc2688_reg_bool_get, ltc2688_reg_bool_set), + { } +}; + +#define LTC2688_CHANNEL(_chan) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (_chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS) | BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW), \ + .ext_info = ltc2688_ext_info, \ +} + +static const struct iio_chan_spec ltc2688_channels[] = { + LTC2688_CHANNEL(0), + LTC2688_CHANNEL(1), + LTC2688_CHANNEL(2), + LTC2688_CHANNEL(3), + LTC2688_CHANNEL(4), + LTC2688_CHANNEL(5), + LTC2688_CHANNEL(6), + LTC2688_CHANNEL(7), + LTC2688_CHANNEL(8), + LTC2688_CHANNEL(9), + LTC2688_CHANNEL(10), + LTC2688_CHANNEL(11), + LTC2688_CHANNEL(12), + LTC2688_CHANNEL(13), + LTC2688_CHANNEL(14), + LTC2688_CHANNEL(15), +}; + +static void ltc2688_clk_disable(void *clk) +{ + clk_disable_unprepare(clk); +} + +static const int ltc2688_period[LTC2688_DITHER_FREQ_AVAIL_N] = { + 4, 8, 16, 32, 64, +}; + +static int ltc2688_tgp_clk_setup(struct ltc2688_state *st, + struct ltc2688_chan *chan, + struct fwnode_handle *node, int tgp) +{ + struct device *dev = &st->spi->dev; + unsigned long rate; + struct clk *clk; + int ret, f; + + clk = devm_get_clk_from_child(dev, to_of_node(node), NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "failed to get tgp clk.\n"); + + ret = clk_prepare_enable(clk); + if (ret) + return dev_err_probe(dev, ret, "failed to enable tgp clk.\n"); + + ret = devm_add_action_or_reset(dev, ltc2688_clk_disable, clk); + if (ret) + return ret; + + if (chan->toggle_chan) + return 0; + + /* calculate available dither frequencies */ + rate = clk_get_rate(clk); + for (f = 0; f < ARRAY_SIZE(chan->dither_frequency); f++) + chan->dither_frequency[f] = DIV_ROUND_CLOSEST(rate, ltc2688_period[f]); + + return 0; +} + +static int ltc2688_span_lookup(const struct ltc2688_state *st, int min, int max) +{ + u32 span; + + for (span = 0; span < ARRAY_SIZE(ltc2688_span_helper); span++) { + if (min == ltc2688_span_helper[span][0] && + max == ltc2688_span_helper[span][1]) + return span; + } + + return -EINVAL; +} + +static int ltc2688_channel_config(struct ltc2688_state *st) +{ + struct device *dev = &st->spi->dev; + u32 reg, clk_input, val, tmp[2]; + int ret, span; + + device_for_each_child_node_scoped(dev, child) { + struct ltc2688_chan *chan; + + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get reg property\n"); + + if (reg >= LTC2688_DAC_CHANNELS) + return dev_err_probe(dev, -EINVAL, + "reg bigger than: %d\n", + LTC2688_DAC_CHANNELS); + + val = 0; + chan = &st->channels[reg]; + if (fwnode_property_read_bool(child, "adi,toggle-mode")) { + chan->toggle_chan = true; + /* assume sw toggle ABI */ + st->iio_chan[reg].ext_info = ltc2688_toggle_sym_ext_info; + /* + * Clear IIO_CHAN_INFO_RAW bit as toggle channels expose + * out_voltage_raw{0|1} files. + */ + __clear_bit(IIO_CHAN_INFO_RAW, + &st->iio_chan[reg].info_mask_separate); + } + + ret = fwnode_property_read_u32_array(child, "adi,output-range-microvolt", + tmp, ARRAY_SIZE(tmp)); + if (!ret) { + span = ltc2688_span_lookup(st, (int)tmp[0] / 1000, + tmp[1] / 1000); + if (span < 0) + return dev_err_probe(dev, span, + "output range not valid:[%d %d]\n", + tmp[0], tmp[1]); + + val |= FIELD_PREP(LTC2688_CH_SPAN_MSK, span); + } + + ret = fwnode_property_read_u32(child, "adi,toggle-dither-input", + &clk_input); + if (!ret) { + if (clk_input >= LTC2688_CH_TGP_MAX) { + return dev_err_probe(dev, -EINVAL, + "toggle-dither-input inv value(%d)\n", + clk_input); + } + + ret = ltc2688_tgp_clk_setup(st, chan, child, clk_input); + if (ret) + return ret; + + /* + * 0 means software toggle which is the default mode. + * Hence the +1. + */ + val |= FIELD_PREP(LTC2688_CH_TD_SEL_MSK, clk_input + 1); + + /* + * If a TGPx is given, we automatically assume a dither + * capable channel (unless toggle is already enabled). + * On top of this we just set here the dither bit in the + * channel settings. It won't have any effect until the + * global toggle/dither bit is enabled. + */ + if (!chan->toggle_chan) { + val |= FIELD_PREP(LTC2688_CH_MODE_MSK, 1); + st->iio_chan[reg].ext_info = ltc2688_dither_ext_info; + } else { + /* wait, no sw toggle after all */ + st->iio_chan[reg].ext_info = ltc2688_toggle_ext_info; + } + } + + if (fwnode_property_read_bool(child, "adi,overrange")) { + chan->overrange = true; + val |= LTC2688_CH_OVERRANGE_MSK; + } + + if (!val) + continue; + + ret = regmap_write(st->regmap, LTC2688_CMD_CH_SETTING(reg), + val); + if (ret) + return dev_err_probe(dev, ret, + "failed to set chan settings\n"); + } + + return 0; +} + +static int ltc2688_setup(struct ltc2688_state *st, bool has_external_vref) +{ + struct device *dev = &st->spi->dev; + struct gpio_desc *gpio; + int ret; + + /* + * If we have a reset pin, use that to reset the board, If not, use + * the reset bit. + */ + gpio = devm_gpiod_get_optional(dev, "clr", GPIOD_OUT_HIGH); + if (IS_ERR(gpio)) + return dev_err_probe(dev, PTR_ERR(gpio), "Failed to get reset gpio"); + if (gpio) { + usleep_range(1000, 1200); + /* bring device out of reset */ + gpiod_set_value_cansleep(gpio, 0); + } else { + ret = regmap_set_bits(st->regmap, LTC2688_CMD_CONFIG, + LTC2688_CONFIG_RST); + if (ret) + return ret; + } + + usleep_range(10000, 12000); + + /* + * Duplicate the default channel configuration as it can change during + * @ltc2688_channel_config() + */ + st->iio_chan = devm_kmemdup(dev, ltc2688_channels, + sizeof(ltc2688_channels), GFP_KERNEL); + if (!st->iio_chan) + return -ENOMEM; + + ret = ltc2688_channel_config(st); + if (ret) + return ret; + + if (!has_external_vref) + return 0; + + return regmap_set_bits(st->regmap, LTC2688_CMD_CONFIG, + LTC2688_CONFIG_EXT_REF); +} + +static bool ltc2688_reg_readable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LTC2688_CMD_CH_CODE(0) ... LTC2688_CMD_CH_GAIN(15): + return true; + case LTC2688_CMD_CONFIG ... LTC2688_CMD_THERMAL_STAT: + return true; + default: + return false; + } +} + +static bool ltc2688_reg_writable(struct device *dev, unsigned int reg) +{ + /* + * There's a jump from 0x76 to 0x78 in the write codes and the thermal + * status code is 0x77 (which is read only) so that we need to check + * that special condition. + */ + if (reg <= LTC2688_CMD_UPDATE_ALL && reg != LTC2688_CMD_THERMAL_STAT) + return true; + + return false; +} + +static const struct regmap_bus ltc2688_regmap_bus = { + .read = ltc2688_spi_read, + .write = ltc2688_spi_write, + .read_flag_mask = LTC2688_READ_OPERATION, + .reg_format_endian_default = REGMAP_ENDIAN_BIG, + .val_format_endian_default = REGMAP_ENDIAN_BIG, +}; + +static const struct regmap_config ltc2688_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .readable_reg = ltc2688_reg_readable, + .writeable_reg = ltc2688_reg_writable, + /* ignoring the no op command */ + .max_register = LTC2688_CMD_UPDATE_ALL, +}; + +static const struct iio_info ltc2688_info = { + .write_raw = ltc2688_write_raw, + .read_raw = ltc2688_read_raw, + .read_avail = ltc2688_read_avail, + .debugfs_reg_access = ltc2688_reg_access, +}; + +static int ltc2688_probe(struct spi_device *spi) +{ + static const char * const regulators[] = { "vcc", "iovcc" }; + struct ltc2688_state *st; + struct iio_dev *indio_dev; + struct device *dev = &spi->dev; + bool has_external_vref; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + + /* Just write this once. No need to do it in every regmap read. */ + st->tx_data[3] = LTC2688_CMD_NOOP; + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + st->regmap = devm_regmap_init(dev, <c2688_regmap_bus, st, + <c2688_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "Failed to init regmap"); + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulators), + regulators); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulators\n"); + + ret = devm_regulator_get_enable_read_voltage(dev, "vref"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, + "Failed to get vref regulator voltage\n"); + + has_external_vref = ret != -ENODEV; + st->vref = has_external_vref ? ret / 1000 : 0; + + ret = ltc2688_setup(st, has_external_vref); + if (ret) + return ret; + + indio_dev->name = "ltc2688"; + indio_dev->info = <c2688_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->iio_chan; + indio_dev->num_channels = ARRAY_SIZE(ltc2688_channels); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ltc2688_of_id[] = { + { .compatible = "adi,ltc2688" }, + { } +}; +MODULE_DEVICE_TABLE(of, ltc2688_of_id); + +static const struct spi_device_id ltc2688_id[] = { + { .name = "ltc2688" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ltc2688_id); + +static struct spi_driver ltc2688_driver = { + .driver = { + .name = "ltc2688", + .of_match_table = ltc2688_of_id, + }, + .probe = ltc2688_probe, + .id_table = ltc2688_id, +}; +module_spi_driver(ltc2688_driver); + +MODULE_AUTHOR("Nuno Sá "); +MODULE_DESCRIPTION("Analog Devices LTC2688 DAC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/dac/m62332.c b/drivers/iio/dac/m62332.c new file mode 100644 index 000000000..60bb672b7 --- /dev/null +++ b/drivers/iio/dac/m62332.c @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * m62332.c - Support for Mitsubishi m62332 DAC + * + * Copyright (c) 2014 Dmitry Eremin-Solenikov + * + * Based on max517 driver: + * Copyright (C) 2010, 2011 Roland Stigge + */ + +#include +#include +#include +#include + +#include +#include + +#include + +#define M62332_CHANNELS 2 + +struct m62332_data { + struct i2c_client *client; + struct regulator *vcc; + struct mutex mutex; + u8 raw[M62332_CHANNELS]; + u8 save[M62332_CHANNELS]; +}; + +static int m62332_set_value(struct iio_dev *indio_dev, u8 val, int channel) +{ + struct m62332_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + bool enabling, disabling; + u8 outbuf[2]; + int res; + + if (val == data->raw[channel]) + return 0; + + outbuf[0] = channel; + outbuf[1] = val; + + mutex_lock(&data->mutex); + + enabling = val && !data->raw[channel]; + disabling = !val && data->raw[channel]; + + if (enabling) { + res = regulator_enable(data->vcc); + if (res) + goto out; + } + + res = i2c_master_send(client, outbuf, ARRAY_SIZE(outbuf)); + if (res >= 0 && res != ARRAY_SIZE(outbuf)) + res = -EIO; + if (res < 0) { + if (enabling) + regulator_disable(data->vcc); + goto out; + } + + if (disabling) + regulator_disable(data->vcc); + + data->raw[channel] = val; + + mutex_unlock(&data->mutex); + + return 0; + +out: + mutex_unlock(&data->mutex); + + return res; +} + +static int m62332_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct m62332_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + /* Corresponds to Vref / 2^(bits) */ + ret = regulator_get_voltage(data->vcc); + if (ret < 0) + return ret; + + *val = ret / 1000; /* mV */ + *val2 = 8; + + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_RAW: + *val = data->raw[chan->channel]; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + *val = 1; + + return IIO_VAL_INT; + default: + break; + } + + return -EINVAL; +} + +static int m62332_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val < 0 || val > 255) + return -EINVAL; + + return m62332_set_value(indio_dev, val, chan->channel); + default: + break; + } + + return -EINVAL; +} + +static int m62332_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct m62332_data *data = iio_priv(indio_dev); + int ret; + + data->save[0] = data->raw[0]; + data->save[1] = data->raw[1]; + + ret = m62332_set_value(indio_dev, 0, 0); + if (ret < 0) + return ret; + + return m62332_set_value(indio_dev, 0, 1); +} + +static int m62332_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct m62332_data *data = iio_priv(indio_dev); + int ret; + + ret = m62332_set_value(indio_dev, data->save[0], 0); + if (ret < 0) + return ret; + + return m62332_set_value(indio_dev, data->save[1], 1); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(m62332_pm_ops, m62332_suspend, m62332_resume); + +static const struct iio_info m62332_info = { + .read_raw = m62332_read_raw, + .write_raw = m62332_write_raw, +}; + +#define M62332_CHANNEL(chan) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (chan), \ + .datasheet_name = "CH" #chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ +} + +static const struct iio_chan_spec m62332_channels[M62332_CHANNELS] = { + M62332_CHANNEL(0), + M62332_CHANNEL(1) +}; + +static int m62332_probe(struct i2c_client *client) +{ + struct m62332_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + mutex_init(&data->mutex); + + data->vcc = devm_regulator_get(&client->dev, "VCC"); + if (IS_ERR(data->vcc)) + return PTR_ERR(data->vcc); + + indio_dev->num_channels = ARRAY_SIZE(m62332_channels); + indio_dev->channels = m62332_channels; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &m62332_info; + + ret = iio_map_array_register(indio_dev, dev_get_platdata(&client->dev)); + if (ret < 0) + return ret; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto err; + + return 0; + +err: + iio_map_array_unregister(indio_dev); + + return ret; +} + +static void m62332_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + iio_map_array_unregister(indio_dev); + m62332_set_value(indio_dev, 0, 0); + m62332_set_value(indio_dev, 0, 1); +} + +static const struct i2c_device_id m62332_id[] = { + { .name = "m62332" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, m62332_id); + +static struct i2c_driver m62332_driver = { + .driver = { + .name = "m62332", + .pm = pm_sleep_ptr(&m62332_pm_ops), + }, + .probe = m62332_probe, + .remove = m62332_remove, + .id_table = m62332_id, +}; +module_i2c_driver(m62332_driver); + +MODULE_AUTHOR("Dmitry Eremin-Solenikov"); +MODULE_DESCRIPTION("M62332 8-bit DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/max22007.c b/drivers/iio/dac/max22007.c new file mode 100644 index 000000000..e2c7e2ad3 --- /dev/null +++ b/drivers/iio/dac/max22007.c @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * max22007.c - MAX22007 DAC driver + * + * Driver for Analog Devices MAX22007 Digital to Analog Converter. + * + * Copyright (c) 2026 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +struct device; + +#define MAX22007_NUM_CHANNELS 4 +#define MAX22007_REV_ID_REG 0x00 +#define MAX22007_STAT_INTR_REG 0x01 +#define MAX22007_INTERRUPT_EN_REG 0x02 +#define MAX22007_CONFIG_REG 0x03 +#define MAX22007_CONTROL_REG 0x04 +#define MAX22007_CHANNEL_MODE_REG 0x05 +#define MAX22007_SOFT_RESET_REG 0x06 +#define MAX22007_DAC_CHANNEL_REG(ch) (0x07 + (ch)) +#define MAX22007_GPIO_CTRL_REG 0x0B +#define MAX22007_GPIO_DATA_REG 0x0C +#define MAX22007_GPI_EDGE_INT_CTRL_REG 0x0D +#define MAX22007_GPI_INT_STATUS_REG 0x0E + +/* Channel mask definitions */ +#define MAX22007_CH_MODE_CH_MASK(ch) BIT(12 + (ch)) +#define MAX22007_CH_PWRON_CH_MASK(ch) BIT(8 + (ch)) +#define MAX22007_DAC_LATCH_MODE_MASK(ch) BIT(12 + (ch)) +#define MAX22007_LDAC_UPDATE_MASK(ch) BIT(12 + (ch)) +#define MAX22007_SW_RST_MASK BIT(8) +#define MAX22007_SW_CLR_MASK BIT(12) +#define MAX22007_SOFT_RESET_BITS_MASK (MAX22007_SW_RST_MASK | \ + MAX22007_SW_CLR_MASK) +#define MAX22007_DAC_DATA_MASK GENMASK(15, 4) +#define MAX22007_DAC_MAX_RAW GENMASK(11, 0) +#define MAX22007_CRC8_POLYNOMIAL 0x8C +#define MAX22007_CRC_EN_MASK BIT(0) +#define MAX22007_RW_MASK BIT(0) +#define MAX22007_CRC_OVERHEAD 1 +#define MAX22007_NUM_SUPPLIES 3 +#define MAX22007_REF_MV 2500 + +/* Field value preparation macros with masking */ +#define MAX22007_CH_PWR_VAL(ch, val) (((val) & 0x1) << (8 + (ch))) +#define MAX22007_CH_MODE_VAL(ch, val) (((val) & 0x1) << (12 + (ch))) +#define MAX22007_DAC_LATCH_MODE_VAL(ch, val) (((val) & 0x1) << (12 + (ch))) + +static u8 max22007_crc8_table[CRC8_TABLE_SIZE]; + +static const char * const max22007_supply_names[MAX22007_NUM_SUPPLIES] = { + "vdd", + "hvdd", + "hvss", +}; + +struct max22007_state { + struct spi_device *spi; + struct regmap *regmap; + struct iio_chan_spec *iio_chans; + u8 tx_buf[4] __aligned(IIO_DMA_MINALIGN); + u8 rx_buf[4]; +}; + +static int max22007_spi_read(void *context, const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + struct max22007_state *st = context; + u8 calculated_crc, received_crc; + u8 rx_buf[4]; + u8 reg_byte; + int ret; + + if (reg_size != 1) + return -EINVAL; + + if (val_size == 0 || val_size > 3) + return -EINVAL; + + memcpy(®_byte, reg, 1); + + ret = spi_write_then_read(st->spi, ®_byte, 1, rx_buf, + val_size + MAX22007_CRC_OVERHEAD); + if (ret) { + dev_err(&st->spi->dev, "SPI transfer failed: %d\n", ret); + return ret; + } + + calculated_crc = crc8(max22007_crc8_table, ®_byte, 1, 0x00); + calculated_crc = crc8(max22007_crc8_table, rx_buf, 2, calculated_crc); + received_crc = rx_buf[val_size]; + + if (calculated_crc != received_crc) { + dev_err(&st->spi->dev, "CRC mismatch on read register %02x\n", reg_byte); + return -EIO; + } + + memcpy(val, rx_buf, val_size); + + return 0; +} + +static int max22007_spi_write(void *context, const void *data, size_t count) +{ + struct max22007_state *st = context; + struct spi_transfer xfer = { + .tx_buf = st->tx_buf, + .rx_buf = st->rx_buf, + }; + + if (count + MAX22007_CRC_OVERHEAD > sizeof(st->tx_buf)) + return -EINVAL; + + memset(st->tx_buf, 0, sizeof(st->tx_buf)); + + xfer.len = count + MAX22007_CRC_OVERHEAD; + + memcpy(st->tx_buf, data, count); + st->tx_buf[count] = crc8(max22007_crc8_table, st->tx_buf, + sizeof(st->tx_buf) - 1, 0x00); + + return spi_sync_transfer(st->spi, &xfer, 1); +} + +static bool max22007_reg_readable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX22007_REV_ID_REG: + case MAX22007_STAT_INTR_REG: + case MAX22007_CONFIG_REG: + case MAX22007_CONTROL_REG: + case MAX22007_CHANNEL_MODE_REG: + case MAX22007_SOFT_RESET_REG: + case MAX22007_GPIO_CTRL_REG: + case MAX22007_GPIO_DATA_REG: + case MAX22007_GPI_EDGE_INT_CTRL_REG: + case MAX22007_GPI_INT_STATUS_REG: + return true; + case MAX22007_DAC_CHANNEL_REG(0) ... MAX22007_DAC_CHANNEL_REG(MAX22007_NUM_CHANNELS - 1): + return true; + default: + return false; + } +} + +static bool max22007_reg_writable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX22007_CONFIG_REG: + case MAX22007_CONTROL_REG: + case MAX22007_CHANNEL_MODE_REG: + case MAX22007_SOFT_RESET_REG: + case MAX22007_GPIO_CTRL_REG: + case MAX22007_GPIO_DATA_REG: + case MAX22007_GPI_EDGE_INT_CTRL_REG: + return true; + case MAX22007_DAC_CHANNEL_REG(0) ... MAX22007_DAC_CHANNEL_REG(MAX22007_NUM_CHANNELS - 1): + return true; + default: + return false; + } +} + +static const struct regmap_bus max22007_regmap_bus = { + .read = max22007_spi_read, + .write = max22007_spi_write, + .read_flag_mask = MAX22007_RW_MASK, + .reg_format_endian_default = REGMAP_ENDIAN_BIG, + .val_format_endian_default = REGMAP_ENDIAN_BIG, +}; + +static const struct regmap_config max22007_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .reg_shift = -1, + .readable_reg = max22007_reg_readable, + .writeable_reg = max22007_reg_writable, + .max_register = 0x0E, +}; + +static int max22007_write_channel_data(struct max22007_state *st, + unsigned int channel, int data) +{ + unsigned int reg_val; + + if (data < 0 || data > MAX22007_DAC_MAX_RAW) + return -EINVAL; + + reg_val = FIELD_PREP(MAX22007_DAC_DATA_MASK, data); + + return regmap_write(st->regmap, MAX22007_DAC_CHANNEL_REG(channel), reg_val); +} + +static int max22007_read_channel_data(struct max22007_state *st, + unsigned int channel, int *data) +{ + unsigned int reg_val; + int ret; + + ret = regmap_read(st->regmap, MAX22007_DAC_CHANNEL_REG(channel), ®_val); + if (ret) + return ret; + + *data = FIELD_GET(MAX22007_DAC_DATA_MASK, reg_val); + + return 0; +} + +static int max22007_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max22007_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = max22007_read_channel_data(st, chan->channel, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_VOLTAGE) + *val = 5 * MAX22007_REF_MV; /* 5 * Vref in mV */ + else + *val = 25; /* Vref / (2 * Rsense) = MAX22007_REF_MV / 100 */ + *val2 = 12; /* 12-bit DAC resolution */ + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static int max22007_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct max22007_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return max22007_write_channel_data(st, chan->channel, val); + default: + return -EINVAL; + } +} + +static const struct iio_info max22007_info = { + .read_raw = max22007_read_raw, + .write_raw = max22007_write_raw, +}; + +static ssize_t max22007_read_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct max22007_state *st = iio_priv(indio_dev); + unsigned int reg_val; + bool powerdown; + int ret; + + ret = regmap_read(st->regmap, MAX22007_CHANNEL_MODE_REG, ®_val); + if (ret) + return ret; + + powerdown = !(reg_val & MAX22007_CH_PWRON_CH_MASK(chan->channel)); + + return sysfs_emit(buf, "%d\n", powerdown); +} + +static ssize_t max22007_write_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct max22007_state *st = iio_priv(indio_dev); + bool powerdown; + int ret; + + ret = kstrtobool(buf, &powerdown); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, MAX22007_CHANNEL_MODE_REG, + MAX22007_CH_PWRON_CH_MASK(chan->channel), + MAX22007_CH_PWR_VAL(chan->channel, powerdown ? 0 : 1)); + if (ret) + return ret; + + return len; +} + +static const struct iio_chan_spec_ext_info max22007_ext_info[] = { + { + .name = "powerdown", + .read = max22007_read_dac_powerdown, + .write = max22007_write_dac_powerdown, + .shared = IIO_SEPARATE, + }, + { } +}; + +static int max22007_parse_channel_cfg(struct max22007_state *st, u8 *num_channels) +{ + struct device *dev = &st->spi->dev; + int ret, num_chan; + int i = 0; + u32 reg; + + num_chan = device_get_child_node_count(dev); + if (!num_chan) + return dev_err_probe(dev, -ENODEV, "no channels configured\n"); + + st->iio_chans = devm_kcalloc(dev, num_chan, sizeof(*st->iio_chans), GFP_KERNEL); + if (!st->iio_chans) + return -ENOMEM; + + device_for_each_child_node_scoped(dev, child) { + u32 ch_func; + enum iio_chan_type chan_type; + + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret) + return dev_err_probe(dev, ret, + "failed to read reg property of %pfwP\n", child); + + if (reg >= MAX22007_NUM_CHANNELS) + return dev_err_probe(dev, -EINVAL, + "reg out of range in %pfwP\n", child); + + ret = fwnode_property_read_u32(child, "adi,ch-func", &ch_func); + if (ret) + return dev_err_probe(dev, ret, + "missing adi,ch-func property for %pfwP\n", child); + + switch (ch_func) { + case CH_FUNC_VOLTAGE_OUTPUT: + chan_type = IIO_VOLTAGE; + break; + case CH_FUNC_CURRENT_OUTPUT: + chan_type = IIO_CURRENT; + break; + default: + return dev_err_probe(dev, -EINVAL, + "invalid adi,ch-func %u for %pfwP\n", + ch_func, child); + } + + st->iio_chans[i++] = (struct iio_chan_spec) { + .output = 1, + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .ext_info = max22007_ext_info, + .channel = reg, + .type = chan_type, + }; + + ret = regmap_update_bits(st->regmap, MAX22007_CHANNEL_MODE_REG, + MAX22007_CH_MODE_CH_MASK(reg), + MAX22007_CH_MODE_VAL(reg, ch_func - 1)); + if (ret) + return ret; + + /* Set DAC to transparent mode (immediate update) */ + ret = regmap_update_bits(st->regmap, MAX22007_CONFIG_REG, + MAX22007_DAC_LATCH_MODE_MASK(reg), + MAX22007_DAC_LATCH_MODE_VAL(reg, 1)); + if (ret) + return ret; + } + + *num_channels = num_chan; + + return 0; +} + +static int max22007_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct gpio_desc *reset_gpio; + struct max22007_state *st; + struct iio_dev *indio_dev; + u8 num_channels; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + + crc8_populate_lsb(max22007_crc8_table, MAX22007_CRC8_POLYNOMIAL); + + st->regmap = devm_regmap_init(dev, &max22007_regmap_bus, st, + &max22007_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "Failed to initialize regmap\n"); + + ret = devm_regulator_bulk_get_enable(dev, MAX22007_NUM_SUPPLIES, + max22007_supply_names); + if (ret) + return dev_err_probe(dev, ret, "Failed to get and enable regulators\n"); + + reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(reset_gpio)) + return dev_err_probe(dev, PTR_ERR(reset_gpio), + "Failed to get reset GPIO\n"); + + if (reset_gpio) { + gpiod_set_value_cansleep(reset_gpio, 1); + usleep_range(1000, 5000); + gpiod_set_value_cansleep(reset_gpio, 0); + usleep_range(1000, 5000); + } else { + ret = regmap_write(st->regmap, MAX22007_SOFT_RESET_REG, + MAX22007_SOFT_RESET_BITS_MASK); + if (ret) + return ret; + } + + ret = regmap_set_bits(st->regmap, MAX22007_CONFIG_REG, + MAX22007_CRC_EN_MASK); + if (ret) + return ret; + + ret = max22007_parse_channel_cfg(st, &num_channels); + if (ret) + return ret; + + indio_dev->info = &max22007_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->iio_chans; + indio_dev->num_channels = num_channels; + indio_dev->name = "max22007"; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id max22007_id[] = { + { .name = "max22007" }, + { } +}; +MODULE_DEVICE_TABLE(spi, max22007_id); + +static const struct of_device_id max22007_of_match[] = { + { .compatible = "adi,max22007" }, + { } +}; +MODULE_DEVICE_TABLE(of, max22007_of_match); + +static struct spi_driver max22007_driver = { + .driver = { + .name = "max22007", + .of_match_table = max22007_of_match, + }, + .probe = max22007_probe, + .id_table = max22007_id, +}; +module_spi_driver(max22007_driver); + +MODULE_AUTHOR("Janani Sunil "); +MODULE_DESCRIPTION("Analog Devices MAX22007 DAC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/dac/max517.c b/drivers/iio/dac/max517.c new file mode 100644 index 000000000..d2ddc72e0 --- /dev/null +++ b/drivers/iio/dac/max517.c @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * max517.c - Support for Maxim MAX517, MAX518 and MAX519 + * + * Copyright (C) 2010, 2011 Roland Stigge + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +/* Commands */ +#define COMMAND_CHANNEL0 0x00 +#define COMMAND_CHANNEL1 0x01 /* for MAX518 and MAX519 */ +#define COMMAND_PD 0x08 /* Power Down */ + +enum max517_device_ids { + ID_MAX517, + ID_MAX518, + ID_MAX519, + ID_MAX520, + ID_MAX521, +}; + +struct max517_data { + struct i2c_client *client; + unsigned short vref_mv[8]; +}; + +/* + * channel: bit 0: channel 1 + * bit 1: channel 2 + * (this way, it's possible to set both channels at once) + */ +static int max517_set_value(struct iio_dev *indio_dev, + long val, int channel) +{ + struct max517_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + u8 outbuf[2]; + int res; + + if (val < 0 || val > 255) + return -EINVAL; + + outbuf[0] = channel; + outbuf[1] = val; + + res = i2c_master_send(client, outbuf, 2); + if (res < 0) + return res; + else if (res != 2) + return -EIO; + else + return 0; +} + +static int max517_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct max517_data *data = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_SCALE: + /* Corresponds to Vref / 2^(bits) */ + *val = data->vref_mv[chan->channel]; + *val2 = 8; + return IIO_VAL_FRACTIONAL_LOG2; + default: + break; + } + return -EINVAL; +} + +static int max517_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = max517_set_value(indio_dev, val, chan->channel); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int max517_suspend(struct device *dev) +{ + u8 outbuf = COMMAND_PD; + + return i2c_master_send(to_i2c_client(dev), &outbuf, 1); +} + +static int max517_resume(struct device *dev) +{ + u8 outbuf = 0; + + return i2c_master_send(to_i2c_client(dev), &outbuf, 1); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(max517_pm_ops, max517_suspend, max517_resume); + +static const struct iio_info max517_info = { + .read_raw = max517_read_raw, + .write_raw = max517_write_raw, +}; + +#define MAX517_CHANNEL(chan) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec max517_channels[] = { + MAX517_CHANNEL(0), + MAX517_CHANNEL(1), + MAX517_CHANNEL(2), + MAX517_CHANNEL(3), + MAX517_CHANNEL(4), + MAX517_CHANNEL(5), + MAX517_CHANNEL(6), + MAX517_CHANNEL(7), +}; + +static int max517_probe(struct i2c_client *client) +{ + const struct max517_platform_data *platform_data = dev_get_platdata(&client->dev); + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct max517_data *data; + struct iio_dev *indio_dev; + int chan; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + data = iio_priv(indio_dev); + data->client = client; + + switch (id->driver_data) { + case ID_MAX521: + indio_dev->num_channels = 8; + break; + case ID_MAX520: + indio_dev->num_channels = 4; + break; + case ID_MAX519: + case ID_MAX518: + indio_dev->num_channels = 2; + break; + default: /* single channel for MAX517 */ + indio_dev->num_channels = 1; + break; + } + indio_dev->channels = max517_channels; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &max517_info; + + /* + * Reference voltage on MAX518 and default is 5V, else take vref_mv + * from platform_data + */ + for (chan = 0; chan < indio_dev->num_channels; chan++) { + if (id->driver_data == ID_MAX518 || !platform_data) + data->vref_mv[chan] = 5000; /* mV */ + else + data->vref_mv[chan] = platform_data->vref_mv[chan]; + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id max517_id[] = { + { .name = "max517", .driver_data = ID_MAX517 }, + { .name = "max518", .driver_data = ID_MAX518 }, + { .name = "max519", .driver_data = ID_MAX519 }, + { .name = "max520", .driver_data = ID_MAX520 }, + { .name = "max521", .driver_data = ID_MAX521 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max517_id); + +static struct i2c_driver max517_driver = { + .driver = { + .name = "max517", + .pm = pm_sleep_ptr(&max517_pm_ops), + }, + .probe = max517_probe, + .id_table = max517_id, +}; +module_i2c_driver(max517_driver); + +MODULE_AUTHOR("Roland Stigge "); +MODULE_DESCRIPTION("MAX517/518/519/520/521 8-bit DAC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/dac/max5522.c b/drivers/iio/dac/max5522.c new file mode 100644 index 000000000..f3415748c --- /dev/null +++ b/drivers/iio/dac/max5522.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Maxim MAX5522 + * Dual, Ultra-Low-Power 10-Bit, Voltage-Output DACs + * + * Copyright 2022 Timesys Corp. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define MAX5522_MAX_ADDR 15 +#define MAX5522_CTRL_NONE 0 +#define MAX5522_CTRL_LOAD_IN_A 9 +#define MAX5522_CTRL_LOAD_IN_B 10 + +#define MAX5522_REG_DATA(x) ((x) + MAX5522_CTRL_LOAD_IN_A) + +struct max5522_state { + struct regmap *regmap; + unsigned short dac_cache[2]; + int vref_mV; +}; + +#define MAX5522_CHANNEL(chan) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 10, \ + .storagebits = 16, \ + .shift = 2, \ + } \ +} + +static const struct iio_chan_spec max5522_channels[] = { + MAX5522_CHANNEL(0), + MAX5522_CHANNEL(1), +}; + +static inline int max5522_info_to_reg(struct iio_chan_spec const *chan) +{ + return MAX5522_REG_DATA(chan->channel); +} + +static int max5522_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct max5522_state *state = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_RAW: + *val = state->dac_cache[chan->channel]; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = state->vref_mV; + *val2 = 10; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static int max5522_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct max5522_state *state = iio_priv(indio_dev); + int rval; + + if (val > 1023 || val < 0) + return -EINVAL; + + rval = regmap_write(state->regmap, max5522_info_to_reg(chan), + val << chan->scan_type.shift); + if (rval < 0) + return rval; + + state->dac_cache[chan->channel] = val; + + return 0; +} + +static const struct iio_info max5522_info = { + .read_raw = max5522_read_raw, + .write_raw = max5522_write_raw, +}; + +static const struct regmap_config max5522_regmap_config = { + .reg_bits = 4, + .val_bits = 12, + .max_register = MAX5522_MAX_ADDR, +}; + +static int max5522_spi_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct max5522_state *state; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*state)); + if (indio_dev == NULL) { + dev_err(&spi->dev, "failed to allocate iio device\n"); + return -ENOMEM; + } + + state = iio_priv(indio_dev); + + ret = devm_regulator_get_enable_read_voltage(&spi->dev, "vrefin"); + if (ret < 0) + return dev_err_probe(&spi->dev, ret, + "Failed to get vrefin regulator\n"); + state->vref_mV = ret / (MICRO / MILLI); + + state->regmap = devm_regmap_init_spi(spi, &max5522_regmap_config); + + if (IS_ERR(state->regmap)) + return PTR_ERR(state->regmap); + + indio_dev->info = &max5522_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = max5522_channels; + indio_dev->num_channels = ARRAY_SIZE(max5522_channels); + indio_dev->name = "max5522"; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id max5522_ids[] = { + { .name = "max5522" }, + { } +}; +MODULE_DEVICE_TABLE(spi, max5522_ids); + +static const struct of_device_id max5522_of_match[] = { + { .compatible = "maxim,max5522" }, + { } +}; +MODULE_DEVICE_TABLE(of, max5522_of_match); + +static struct spi_driver max5522_spi_driver = { + .driver = { + .name = "max5522", + .of_match_table = max5522_of_match, + }, + .probe = max5522_spi_probe, + .id_table = max5522_ids, +}; +module_spi_driver(max5522_spi_driver); + +MODULE_AUTHOR("Angelo Dureghello +#include +#include +#include +#include + +#define MAX5821_MAX_DAC_CHANNELS 2 + +/* command bytes */ +#define MAX5821_LOAD_DAC_A_IN_REG_B 0x00 +#define MAX5821_LOAD_DAC_B_IN_REG_A 0x10 +#define MAX5821_EXTENDED_COMMAND_MODE 0xf0 +#define MAX5821_READ_DAC_A_COMMAND 0xf1 +#define MAX5821_READ_DAC_B_COMMAND 0xf2 + +#define MAX5821_EXTENDED_POWER_UP 0x00 +#define MAX5821_EXTENDED_POWER_DOWN_MODE0 0x01 +#define MAX5821_EXTENDED_POWER_DOWN_MODE1 0x02 +#define MAX5821_EXTENDED_POWER_DOWN_MODE2 0x03 +#define MAX5821_EXTENDED_DAC_A 0x04 +#define MAX5821_EXTENDED_DAC_B 0x08 + +struct max5821_data { + struct i2c_client *client; + unsigned short vref_mv; + bool powerdown[MAX5821_MAX_DAC_CHANNELS]; + u8 powerdown_mode[MAX5821_MAX_DAC_CHANNELS]; + struct mutex lock; +}; + +static const char * const max5821_powerdown_modes[] = { + "three_state", + "1kohm_to_gnd", + "100kohm_to_gnd", +}; + +enum { + MAX5821_THREE_STATE, + MAX5821_1KOHM_TO_GND, + MAX5821_100KOHM_TO_GND +}; + +static int max5821_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct max5821_data *st = iio_priv(indio_dev); + + return st->powerdown_mode[chan->channel]; +} + +static int max5821_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct max5821_data *st = iio_priv(indio_dev); + + st->powerdown_mode[chan->channel] = mode; + + return 0; +} + +static const struct iio_enum max5821_powerdown_mode_enum = { + .items = max5821_powerdown_modes, + .num_items = ARRAY_SIZE(max5821_powerdown_modes), + .get = max5821_get_powerdown_mode, + .set = max5821_set_powerdown_mode, +}; + +static ssize_t max5821_read_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct max5821_data *st = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", st->powerdown[chan->channel]); +} + +static int max5821_sync_powerdown_mode(struct max5821_data *data, + const struct iio_chan_spec *chan) +{ + u8 outbuf[2]; + int ret; + + outbuf[0] = MAX5821_EXTENDED_COMMAND_MODE; + + if (chan->channel == 0) + outbuf[1] = MAX5821_EXTENDED_DAC_A; + else + outbuf[1] = MAX5821_EXTENDED_DAC_B; + + if (data->powerdown[chan->channel]) + outbuf[1] |= data->powerdown_mode[chan->channel] + 1; + else + outbuf[1] |= MAX5821_EXTENDED_POWER_UP; + + ret = i2c_master_send(data->client, outbuf, sizeof(outbuf)); + if (ret < 0) + return ret; + if (ret != sizeof(outbuf)) + return -EIO; + + return 0; +} + +static ssize_t max5821_write_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct max5821_data *data = iio_priv(indio_dev); + bool powerdown; + int ret; + + ret = kstrtobool(buf, &powerdown); + if (ret) + return ret; + + data->powerdown[chan->channel] = powerdown; + + ret = max5821_sync_powerdown_mode(data, chan); + if (ret < 0) + return ret; + + return len; +} + +static const struct iio_chan_spec_ext_info max5821_ext_info[] = { + { + .name = "powerdown", + .read = max5821_read_dac_powerdown, + .write = max5821_write_dac_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SEPARATE, &max5821_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, &max5821_powerdown_mode_enum), + { } +}; + +#define MAX5821_CHANNEL(chan) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info = max5821_ext_info, \ +} + +static const struct iio_chan_spec max5821_channels[] = { + MAX5821_CHANNEL(0), + MAX5821_CHANNEL(1) +}; + +static const u8 max5821_read_dac_command[] = { + MAX5821_READ_DAC_A_COMMAND, + MAX5821_READ_DAC_B_COMMAND +}; + +static const u8 max5821_load_dac_command[] = { + MAX5821_LOAD_DAC_A_IN_REG_B, + MAX5821_LOAD_DAC_B_IN_REG_A +}; + +static int max5821_get_value(struct iio_dev *indio_dev, + int *val, int channel) +{ + struct max5821_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + u8 outbuf[1]; + u8 inbuf[2]; + int ret; + + if ((channel != 0) && (channel != 1)) + return -EINVAL; + + outbuf[0] = max5821_read_dac_command[channel]; + + mutex_lock(&data->lock); + + ret = i2c_master_send(client, outbuf, 1); + if (ret < 0) { + mutex_unlock(&data->lock); + return ret; + } else if (ret != 1) { + mutex_unlock(&data->lock); + return -EIO; + } + + ret = i2c_master_recv(client, inbuf, 2); + if (ret < 0) { + mutex_unlock(&data->lock); + return ret; + } else if (ret != 2) { + mutex_unlock(&data->lock); + return -EIO; + } + + mutex_unlock(&data->lock); + + *val = ((inbuf[0] & 0x0f) << 6) | (inbuf[1] >> 2); + + return IIO_VAL_INT; +} + +static int max5821_set_value(struct iio_dev *indio_dev, + int val, int channel) +{ + struct max5821_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + u8 outbuf[2]; + int ret; + + if ((val < 0) || (val > 1023)) + return -EINVAL; + + if ((channel != 0) && (channel != 1)) + return -EINVAL; + + outbuf[0] = max5821_load_dac_command[channel]; + outbuf[0] |= val >> 6; + outbuf[1] = (val & 0x3f) << 2; + + ret = i2c_master_send(client, outbuf, 2); + if (ret < 0) + return ret; + else if (ret != 2) + return -EIO; + else + return 0; +} + +static int max5821_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max5821_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return max5821_get_value(indio_dev, val, chan->channel); + case IIO_CHAN_INFO_SCALE: + *val = data->vref_mv; + *val2 = 10; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static int max5821_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + if (val2 != 0) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return max5821_set_value(indio_dev, val, chan->channel); + default: + return -EINVAL; + } +} + +static int max5821_suspend(struct device *dev) +{ + u8 outbuf[2] = { MAX5821_EXTENDED_COMMAND_MODE, + MAX5821_EXTENDED_DAC_A | + MAX5821_EXTENDED_DAC_B | + MAX5821_EXTENDED_POWER_DOWN_MODE2 }; + + return i2c_master_send(to_i2c_client(dev), outbuf, 2); +} + +static int max5821_resume(struct device *dev) +{ + u8 outbuf[2] = { MAX5821_EXTENDED_COMMAND_MODE, + MAX5821_EXTENDED_DAC_A | + MAX5821_EXTENDED_DAC_B | + MAX5821_EXTENDED_POWER_UP }; + + return i2c_master_send(to_i2c_client(dev), outbuf, 2); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(max5821_pm_ops, max5821_suspend, + max5821_resume); + +static const struct iio_info max5821_info = { + .read_raw = max5821_read_raw, + .write_raw = max5821_write_raw, +}; + +static int max5821_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct max5821_data *data; + struct iio_dev *indio_dev; + u32 tmp; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + data = iio_priv(indio_dev); + data->client = client; + mutex_init(&data->lock); + + /* max5821 start in powerdown mode 100Kohm to ground */ + for (tmp = 0; tmp < MAX5821_MAX_DAC_CHANNELS; tmp++) { + data->powerdown[tmp] = true; + data->powerdown_mode[tmp] = MAX5821_100KOHM_TO_GND; + } + + ret = devm_regulator_get_enable_read_voltage(&client->dev, "vref"); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to get vref regulator voltage\n"); + + data->vref_mv = ret / 1000; + + indio_dev->name = id->name; + indio_dev->num_channels = ARRAY_SIZE(max5821_channels); + indio_dev->channels = max5821_channels; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &max5821_info; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id max5821_id[] = { + { .name = "max5821" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max5821_id); + +static const struct of_device_id max5821_of_match[] = { + { .compatible = "maxim,max5821" }, + { } +}; +MODULE_DEVICE_TABLE(of, max5821_of_match); + +static struct i2c_driver max5821_driver = { + .driver = { + .name = "max5821", + .of_match_table = max5821_of_match, + .pm = pm_sleep_ptr(&max5821_pm_ops), + }, + .probe = max5821_probe, + .id_table = max5821_id, +}; +module_i2c_driver(max5821_driver); + +MODULE_AUTHOR("Philippe Reynes "); +MODULE_DESCRIPTION("MAX5821 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/mcf54415_dac.c b/drivers/iio/dac/mcf54415_dac.c new file mode 100644 index 000000000..e2c12241a --- /dev/null +++ b/drivers/iio/dac/mcf54415_dac.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NXP mcf54415 DAC driver + * + * Copyright 2026 BayLibre - adureghello@baylibre.com + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define MCF54415_DAC_CR 0x00 +#define MCF54415_DAC_CR_PDN BIT(0) +#define MCF54415_DAC_CR_HSLS BIT(6) +#define MCF54415_DAC_CR_WMLVL GENMASK(9, 8) +#define MCF54415_DAC_CR_FILT BIT(12) + +#define MCF54415_DAC_DATA 0x02 + +struct mcf54415_dac { + struct regmap *map; + struct clk *clk; +}; + +static const struct regmap_config mcf54415_dac_regmap_config = { + .reg_bits = 16, + .reg_stride = 2, + .val_bits = 16, + .max_register = 0x0c, /* DACX_FILTCNT, R.M. Table 30-2 */ + .val_format_endian = REGMAP_ENDIAN_BIG, + .reg_format_endian = REGMAP_ENDIAN_BIG, +}; + +static int mcf54415_dac_init(struct mcf54415_dac *info) +{ + u16 val = MCF54415_DAC_CR_FILT | FIELD_PREP(MCF54415_DAC_CR_WMLVL, 1); + int ret; + + /* Fixed defaults and enable DAC (bit 0 set to 0) */ + ret = regmap_write(info->map, MCF54415_DAC_CR, val); + if (ret) + return ret; + + /* DAC is ready after 12us, from RM table 40-3 */ + fsleep(12); + + return 0; +} + +static void mcf54415_dac_exit(void *data) +{ + struct mcf54415_dac *info = data; + + regmap_set_bits(info->map, MCF54415_DAC_CR, MCF54415_DAC_CR_PDN); +} + +static const struct iio_chan_spec mcf54415_dac_iio_channel = { + .type = IIO_VOLTAGE, + .output = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), +}; + +static int mcf54415_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mcf54415_dac *info = iio_priv(indio_dev); + unsigned int reg; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(info->map, MCF54415_DAC_DATA, ®); + if (ret) + return ret; + *val = reg & GENMASK(11, 0); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* Reference voltage as per ColdFire datasheet is 3.3V */ + *val = 3300 /* mV */; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static int mcf54415_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct mcf54415_dac *info = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* Check based on RM 30.3.2 (DACn_DATA) reg. resolution */ + if (val < 0 || val > 4095) + return -EINVAL; + return regmap_write(info->map, MCF54415_DAC_DATA, val); + default: + return -EINVAL; + } +} + +static const struct iio_info mcf54415_dac_iio_info = { + .read_raw = &mcf54415_read_raw, + .write_raw = &mcf54415_write_raw, +}; + +static int mcf54415_dac_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct mcf54415_dac *info; + void __iomem *regs; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*info)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return dev_err_probe(dev, PTR_ERR(regs), "failed to get io regs\n"); + + info->map = devm_regmap_init_mmio(dev, regs, &mcf54415_dac_regmap_config); + if (IS_ERR(info->map)) + return PTR_ERR(info->map); + + info->clk = devm_clk_get_enabled(dev, "dac"); + if (IS_ERR(info->clk)) + return dev_err_probe(dev, PTR_ERR(info->clk), "failed getting clock\n"); + + indio_dev->name = "mcf54415"; + indio_dev->info = &mcf54415_dac_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = &mcf54415_dac_iio_channel; + indio_dev->num_channels = 1; + + ret = mcf54415_dac_init(info); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, mcf54415_dac_exit, info); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct platform_device_id mcf54415_dac_ids[] = { + { .name = "mcfdac" }, + { } +}; +MODULE_DEVICE_TABLE(platform, mcf54415_dac_ids); + +static struct platform_driver mcf54415_dac_driver = { + .driver = { + .name = "mcf54415_dac", + }, + .probe = mcf54415_dac_probe, + .id_table = mcf54415_dac_ids, +}; +module_platform_driver(mcf54415_dac_driver); + +MODULE_AUTHOR("Angelo Dureghello "); +MODULE_DESCRIPTION("NXP MCF54415 DAC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/dac/mcp4725.c b/drivers/iio/dac/mcp4725.c new file mode 100644 index 000000000..3fa7acc69 --- /dev/null +++ b/drivers/iio/dac/mcp4725.c @@ -0,0 +1,558 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * mcp4725.c - Support for Microchip MCP4725/6 + * + * Copyright (C) 2012 Peter Meerwald + * + * Based on max517 by Roland Stigge + * + * driver for the Microchip I2C 12-bit digital-to-analog converter (DAC) + * (7-bit I2C slave address 0x60, the three LSBs can be configured in + * hardware) + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#define MCP472X_REF_VDD 0x00 +#define MCP472X_REF_VREF_UNBUFFERED 0x02 +#define MCP472X_REF_VREF_BUFFERED 0x03 + +struct mcp4725_chip_info { + const struct iio_chan_spec *chan_spec; + u8 dac_reg_offset; + bool use_ext_ref_voltage; +}; + +struct mcp4725_data { + struct i2c_client *client; + unsigned ref_mode; + bool vref_buffered; + u16 dac_value; + bool powerdown; + unsigned powerdown_mode; + struct regulator *vdd_reg; + struct regulator *vref_reg; +}; + +static int mcp4725_suspend(struct device *dev) +{ + struct mcp4725_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + u8 outbuf[2]; + int ret; + + outbuf[0] = (data->powerdown_mode + 1) << 4; + outbuf[1] = 0; + data->powerdown = true; + + ret = i2c_master_send(data->client, outbuf, 2); + if (ret < 0) + return ret; + else if (ret != 2) + return -EIO; + return 0; +} + +static int mcp4725_resume(struct device *dev) +{ + struct mcp4725_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + u8 outbuf[2]; + int ret; + + /* restore previous DAC value */ + outbuf[0] = (data->dac_value >> 8) & 0xf; + outbuf[1] = data->dac_value & 0xff; + data->powerdown = false; + + ret = i2c_master_send(data->client, outbuf, 2); + if (ret < 0) + return ret; + else if (ret != 2) + return -EIO; + return 0; +} +static DEFINE_SIMPLE_DEV_PM_OPS(mcp4725_pm_ops, mcp4725_suspend, + mcp4725_resume); + +static ssize_t mcp4725_store_eeprom(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct mcp4725_data *data = iio_priv(indio_dev); + int tries = 20; + u8 inoutbuf[3]; + bool state; + int ret; + + ret = kstrtobool(buf, &state); + if (ret < 0) + return ret; + + if (!state) + return 0; + + inoutbuf[0] = 0x60; /* write EEPROM */ + inoutbuf[0] |= data->ref_mode << 3; + inoutbuf[0] |= data->powerdown ? ((data->powerdown_mode + 1) << 1) : 0; + inoutbuf[1] = data->dac_value >> 4; + inoutbuf[2] = (data->dac_value & 0xf) << 4; + + ret = i2c_master_send(data->client, inoutbuf, 3); + if (ret < 0) + return ret; + else if (ret != 3) + return -EIO; + + /* wait for write complete, takes up to 50ms */ + while (tries--) { + msleep(20); + ret = i2c_master_recv(data->client, inoutbuf, 3); + if (ret < 0) + return ret; + else if (ret != 3) + return -EIO; + + if (inoutbuf[0] & 0x80) + break; + } + + if (tries < 0) { + dev_err(&data->client->dev, + "mcp4725_store_eeprom() failed, incomplete\n"); + return -EIO; + } + + return len; +} + +static IIO_DEVICE_ATTR(store_eeprom, S_IWUSR, NULL, mcp4725_store_eeprom, 0); + +static struct attribute *mcp4725_attributes[] = { + &iio_dev_attr_store_eeprom.dev_attr.attr, + NULL, +}; + +static const struct attribute_group mcp4725_attribute_group = { + .attrs = mcp4725_attributes, +}; + +static const char * const mcp4725_powerdown_modes[] = { + "1kohm_to_gnd", + "100kohm_to_gnd", + "500kohm_to_gnd" +}; + +static const char * const mcp4726_powerdown_modes[] = { + "1kohm_to_gnd", + "125kohm_to_gnd", + "640kohm_to_gnd" +}; + +static int mcp4725_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct mcp4725_data *data = iio_priv(indio_dev); + + return data->powerdown_mode; +} + +static int mcp4725_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned mode) +{ + struct mcp4725_data *data = iio_priv(indio_dev); + + data->powerdown_mode = mode; + + return 0; +} + +static ssize_t mcp4725_read_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, char *buf) +{ + struct mcp4725_data *data = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", data->powerdown); +} + +static ssize_t mcp4725_write_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct mcp4725_data *data = iio_priv(indio_dev); + bool state; + int ret; + + ret = kstrtobool(buf, &state); + if (ret) + return ret; + + if (state) + ret = mcp4725_suspend(&data->client->dev); + else + ret = mcp4725_resume(&data->client->dev); + if (ret < 0) + return ret; + + return len; +} + +enum chip_id { + MCP4725, + MCP4726, +}; + +static const struct iio_enum mcp472x_powerdown_mode_enum[] = { + [MCP4725] = { + .items = mcp4725_powerdown_modes, + .num_items = ARRAY_SIZE(mcp4725_powerdown_modes), + .get = mcp4725_get_powerdown_mode, + .set = mcp4725_set_powerdown_mode, + }, + [MCP4726] = { + .items = mcp4726_powerdown_modes, + .num_items = ARRAY_SIZE(mcp4726_powerdown_modes), + .get = mcp4725_get_powerdown_mode, + .set = mcp4725_set_powerdown_mode, + }, +}; + +static const struct iio_chan_spec_ext_info mcp4725_ext_info[] = { + { + .name = "powerdown", + .read = mcp4725_read_powerdown, + .write = mcp4725_write_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SEPARATE, + &mcp472x_powerdown_mode_enum[MCP4725]), + IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, + &mcp472x_powerdown_mode_enum[MCP4725]), + { } +}; + +static const struct iio_chan_spec_ext_info mcp4726_ext_info[] = { + { + .name = "powerdown", + .read = mcp4725_read_powerdown, + .write = mcp4725_write_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SEPARATE, + &mcp472x_powerdown_mode_enum[MCP4726]), + IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, + &mcp472x_powerdown_mode_enum[MCP4726]), + { } +}; + +static const struct iio_chan_spec mcp472x_channel[] = { + [MCP4725] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .output = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .ext_info = mcp4725_ext_info, + }, + [MCP4726] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .output = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .ext_info = mcp4726_ext_info, + }, +}; + +static int mcp4725_set_value(struct iio_dev *indio_dev, int val) +{ + struct mcp4725_data *data = iio_priv(indio_dev); + u8 outbuf[2]; + int ret; + + if (val >= (1 << 12) || val < 0) + return -EINVAL; + + outbuf[0] = (val >> 8) & 0xf; + outbuf[1] = val & 0xff; + + ret = i2c_master_send(data->client, outbuf, 2); + if (ret < 0) + return ret; + else if (ret != 2) + return -EIO; + else + return 0; +} + +static int mcp4726_set_cfg(struct iio_dev *indio_dev) +{ + struct mcp4725_data *data = iio_priv(indio_dev); + u8 outbuf[3]; + int ret; + + outbuf[0] = 0x40; + outbuf[0] |= data->ref_mode << 3; + if (data->powerdown) + outbuf[0] |= data->powerdown << 1; + outbuf[1] = data->dac_value >> 4; + outbuf[2] = (data->dac_value & 0xf) << 4; + + ret = i2c_master_send(data->client, outbuf, 3); + if (ret < 0) + return ret; + else if (ret != 3) + return -EIO; + else + return 0; +} + +static int mcp4725_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mcp4725_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val = data->dac_value; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (data->ref_mode == MCP472X_REF_VDD) + ret = regulator_get_voltage(data->vdd_reg); + else + ret = regulator_get_voltage(data->vref_reg); + + if (ret < 0) + return ret; + + *val = ret / 1000; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + +static int mcp4725_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct mcp4725_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = mcp4725_set_value(indio_dev, val); + data->dac_value = val; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct iio_info mcp4725_info = { + .read_raw = mcp4725_read_raw, + .write_raw = mcp4725_write_raw, + .attrs = &mcp4725_attribute_group, +}; + +static int mcp4725_probe_dt(struct device *dev, + struct mcp4725_platform_data *pdata) +{ + /* check if is the vref-supply defined */ + pdata->use_vref = device_property_present(dev, "vref-supply"); + pdata->vref_buffered = + device_property_read_bool(dev, "microchip,vref-buffered"); + + return 0; +} + +static int mcp4725_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + const struct mcp4725_chip_info *info; + struct mcp4725_data *data; + struct iio_dev *indio_dev; + struct mcp4725_platform_data *pdata, pdata_dt; + u8 inbuf[4]; + u8 pd; + u8 ref; + int err; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (indio_dev == NULL) + return -ENOMEM; + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + info = i2c_get_match_data(client); + pdata = dev_get_platdata(&client->dev); + + if (!pdata) { + err = mcp4725_probe_dt(&client->dev, &pdata_dt); + if (err) { + dev_err(&client->dev, + "invalid platform or devicetree data"); + return err; + } + pdata = &pdata_dt; + } + + if (info->use_ext_ref_voltage && pdata->use_vref) { + dev_err(&client->dev, + "external reference is unavailable on MCP4725"); + return -EINVAL; + } + + if (!pdata->use_vref && pdata->vref_buffered) { + dev_err(&client->dev, + "buffering is unavailable on the internal reference"); + return -EINVAL; + } + + if (!pdata->use_vref) + data->ref_mode = MCP472X_REF_VDD; + else + data->ref_mode = pdata->vref_buffered ? + MCP472X_REF_VREF_BUFFERED : + MCP472X_REF_VREF_UNBUFFERED; + + data->vdd_reg = devm_regulator_get(&client->dev, "vdd"); + if (IS_ERR(data->vdd_reg)) + return PTR_ERR(data->vdd_reg); + + err = regulator_enable(data->vdd_reg); + if (err) + return err; + + if (pdata->use_vref) { + data->vref_reg = devm_regulator_get(&client->dev, "vref"); + if (IS_ERR(data->vref_reg)) { + err = PTR_ERR(data->vref_reg); + goto err_disable_vdd_reg; + } + + err = regulator_enable(data->vref_reg); + if (err) + goto err_disable_vdd_reg; + } + + indio_dev->name = id->name; + indio_dev->info = &mcp4725_info; + indio_dev->channels = info->chan_spec; + indio_dev->num_channels = 1; + indio_dev->modes = INDIO_DIRECT_MODE; + + /* read current DAC value and settings */ + err = i2c_master_recv(client, inbuf, info->dac_reg_offset); + + if (err < 0) { + dev_err(&client->dev, "failed to read DAC value"); + goto err_disable_vref_reg; + } + pd = (inbuf[0] >> 1) & 0x3; + data->powerdown = pd > 0; + data->powerdown_mode = pd ? pd - 1 : 2; /* largest resistor to gnd */ + data->dac_value = (inbuf[1] << 4) | (inbuf[2] >> 4); + if (!info->use_ext_ref_voltage) + ref = (inbuf[3] >> 3) & 0x3; + + if (!info->use_ext_ref_voltage && ref != data->ref_mode) { + dev_info(&client->dev, + "voltage reference mode differs (conf: %u, eeprom: %u), setting %u", + data->ref_mode, ref, data->ref_mode); + err = mcp4726_set_cfg(indio_dev); + if (err < 0) + goto err_disable_vref_reg; + } + + err = iio_device_register(indio_dev); + if (err) + goto err_disable_vref_reg; + + return 0; + +err_disable_vref_reg: + if (data->vref_reg) + regulator_disable(data->vref_reg); + +err_disable_vdd_reg: + regulator_disable(data->vdd_reg); + + return err; +} + +static void mcp4725_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct mcp4725_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + if (data->vref_reg) + regulator_disable(data->vref_reg); + regulator_disable(data->vdd_reg); +} + +static const struct mcp4725_chip_info mcp4725 = { + .chan_spec = &mcp472x_channel[MCP4725], + .dac_reg_offset = 3, + .use_ext_ref_voltage = true, +}; + +static const struct mcp4725_chip_info mcp4726 = { + .chan_spec = &mcp472x_channel[MCP4726], + .dac_reg_offset = 4, +}; + +static const struct i2c_device_id mcp4725_id[] = { + { .name = "mcp4725", .driver_data = (kernel_ulong_t)&mcp4725 }, + { .name = "mcp4726", .driver_data = (kernel_ulong_t)&mcp4726 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mcp4725_id); + +static const struct of_device_id mcp4725_of_match[] = { + { + .compatible = "microchip,mcp4725", + .data = &mcp4725 + }, + { + .compatible = "microchip,mcp4726", + .data = &mcp4726 + }, + { } +}; +MODULE_DEVICE_TABLE(of, mcp4725_of_match); + +static struct i2c_driver mcp4725_driver = { + .driver = { + .name = "mcp4725", + .of_match_table = mcp4725_of_match, + .pm = pm_sleep_ptr(&mcp4725_pm_ops), + }, + .probe = mcp4725_probe, + .remove = mcp4725_remove, + .id_table = mcp4725_id, +}; +module_i2c_driver(mcp4725_driver); + +MODULE_AUTHOR("Peter Meerwald "); +MODULE_DESCRIPTION("MCP4725/6 12-bit DAC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/dac/mcp4728.c b/drivers/iio/dac/mcp4728.c new file mode 100644 index 000000000..1fe184d49 --- /dev/null +++ b/drivers/iio/dac/mcp4728.c @@ -0,0 +1,598 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Support for Microchip MCP4728 + * + * Copyright (C) 2023 Andrea Collamati + * + * Based on mcp4725 by Peter Meerwald + * + * Driver for the Microchip I2C 12-bit digital-to-analog quad channels + * converter (DAC). + * + * (7-bit I2C slave address 0x60, the three LSBs can be configured in + * hardware) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MCP4728_RESOLUTION 12 +#define MCP4728_N_CHANNELS 4 + +#define MCP4728_CMD_MASK GENMASK(7, 3) +#define MCP4728_CHSEL_MASK GENMASK(2, 1) +#define MCP4728_UDAC_MASK BIT(0) + +#define MCP4728_VREF_MASK BIT(7) +#define MCP4728_PDMODE_MASK GENMASK(6, 5) +#define MCP4728_GAIN_MASK BIT(4) + +#define MCP4728_DAC_H_MASK GENMASK(3, 0) +#define MCP4728_DAC_L_MASK GENMASK(7, 0) + +#define MCP4728_RDY_MASK BIT(7) + +#define MCP4728_MW_CMD 0x08 /* Multiwrite Command */ +#define MCP4728_SW_CMD 0x0A /* Sequential Write Command with EEPROM */ + +#define MCP4728_READ_RESPONSE_LEN (MCP4728_N_CHANNELS * 3 * 2) +#define MCP4728_WRITE_EEPROM_LEN (1 + MCP4728_N_CHANNELS * 2) + +enum vref_mode { + MCP4728_VREF_EXTERNAL_VDD = 0, + MCP4728_VREF_INTERNAL_2048mV = 1, +}; + +enum gain_mode { + MCP4728_GAIN_X1 = 0, + MCP4728_GAIN_X2 = 1, +}; + +enum iio_powerdown_mode { + MCP4728_IIO_1K, + MCP4728_IIO_100K, + MCP4728_IIO_500K, +}; + +struct mcp4728_channel_data { + enum vref_mode ref_mode; + enum iio_powerdown_mode pd_mode; + enum gain_mode g_mode; + u16 dac_value; +}; + +/* MCP4728 Full Scale Ranges + * the device available ranges are + * - VREF = VDD FSR = from 0.0V to VDD + * - VREF = Internal Gain = 1 FSR = from 0.0V to VREF + * - VREF = Internal Gain = 2 FSR = from 0.0V to 2*VREF + */ +enum mcp4728_scale { + MCP4728_SCALE_VDD, + MCP4728_SCALE_VINT_NO_GAIN, + MCP4728_SCALE_VINT_GAIN_X2, + MCP4728_N_SCALES +}; + +struct mcp4728_data { + struct i2c_client *client; + bool powerdown; + int scales_avail[MCP4728_N_SCALES * 2]; + struct mcp4728_channel_data chdata[MCP4728_N_CHANNELS]; +}; + +#define MCP4728_CHAN(chan) { \ + .type = IIO_VOLTAGE, \ + .output = 1, \ + .indexed = 1, \ + .channel = chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info = mcp4728_ext_info, \ +} + +static int mcp4728_suspend(struct device *dev); +static int mcp4728_resume(struct device *dev); + +static ssize_t mcp4728_store_eeprom(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct mcp4728_data *data = iio_priv(indio_dev); + u8 outbuf[MCP4728_WRITE_EEPROM_LEN]; + int tries = 20; + u8 inbuf[3]; + bool state; + int ret; + unsigned int i; + + ret = kstrtobool(buf, &state); + if (ret < 0) + return ret; + + if (!state) + return 0; + + outbuf[0] = FIELD_PREP(MCP4728_CMD_MASK, MCP4728_SW_CMD); + + for (i = 0; i < MCP4728_N_CHANNELS; i++) { + struct mcp4728_channel_data *ch = &data->chdata[i]; + int offset = 1 + i * 2; + + outbuf[offset] = FIELD_PREP(MCP4728_VREF_MASK, ch->ref_mode); + + if (data->powerdown) { + u8 mcp4728_pd_mode = ch->pd_mode + 1; + + outbuf[offset] |= FIELD_PREP(MCP4728_PDMODE_MASK, + mcp4728_pd_mode); + } + + outbuf[offset] |= FIELD_PREP(MCP4728_GAIN_MASK, ch->g_mode); + outbuf[offset] |= + FIELD_PREP(MCP4728_DAC_H_MASK, ch->dac_value >> 8); + outbuf[offset + 1] = + FIELD_PREP(MCP4728_DAC_L_MASK, ch->dac_value); + } + + ret = i2c_master_send(data->client, outbuf, MCP4728_WRITE_EEPROM_LEN); + if (ret < 0) + return ret; + else if (ret != MCP4728_WRITE_EEPROM_LEN) + return -EIO; + + /* wait RDY signal for write complete, takes up to 50ms */ + while (tries--) { + msleep(20); + ret = i2c_master_recv(data->client, inbuf, 3); + if (ret < 0) + return ret; + else if (ret != 3) + return -EIO; + + if (FIELD_GET(MCP4728_RDY_MASK, inbuf[0])) + break; + } + + if (tries < 0) { + dev_err(&data->client->dev, "%s failed, incomplete\n", + __func__); + return -EIO; + } + return len; +} + +static IIO_DEVICE_ATTR(store_eeprom, 0200, NULL, mcp4728_store_eeprom, 0); + +static struct attribute *mcp4728_attributes[] = { + &iio_dev_attr_store_eeprom.dev_attr.attr, + NULL, +}; + +static const struct attribute_group mcp4728_attribute_group = { + .attrs = mcp4728_attributes, +}; + +static int mcp4728_program_channel_cfg(int channel, struct iio_dev *indio_dev) +{ + struct mcp4728_data *data = iio_priv(indio_dev); + struct mcp4728_channel_data *ch = &data->chdata[channel]; + u8 outbuf[3]; + int ret; + + outbuf[0] = FIELD_PREP(MCP4728_CMD_MASK, MCP4728_MW_CMD); + outbuf[0] |= FIELD_PREP(MCP4728_CHSEL_MASK, channel); + outbuf[0] |= FIELD_PREP(MCP4728_UDAC_MASK, 0); + + outbuf[1] = FIELD_PREP(MCP4728_VREF_MASK, ch->ref_mode); + + if (data->powerdown) + outbuf[1] |= FIELD_PREP(MCP4728_PDMODE_MASK, ch->pd_mode + 1); + + outbuf[1] |= FIELD_PREP(MCP4728_GAIN_MASK, ch->g_mode); + outbuf[1] |= FIELD_PREP(MCP4728_DAC_H_MASK, ch->dac_value >> 8); + outbuf[2] = FIELD_PREP(MCP4728_DAC_L_MASK, ch->dac_value); + + ret = i2c_master_send(data->client, outbuf, 3); + if (ret < 0) + return ret; + else if (ret != 3) + return -EIO; + + return 0; +} + +static const char *const mcp4728_powerdown_modes[] = { "1kohm_to_gnd", + "100kohm_to_gnd", + "500kohm_to_gnd" }; + +static int mcp4728_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct mcp4728_data *data = iio_priv(indio_dev); + + return data->chdata[chan->channel].pd_mode; +} + +static int mcp4728_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct mcp4728_data *data = iio_priv(indio_dev); + + data->chdata[chan->channel].pd_mode = mode; + + return 0; +} + +static ssize_t mcp4728_read_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct mcp4728_data *data = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", data->powerdown); +} + +static ssize_t mcp4728_write_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct mcp4728_data *data = iio_priv(indio_dev); + bool state; + int ret; + + ret = kstrtobool(buf, &state); + if (ret) + return ret; + + if (state) + ret = mcp4728_suspend(&data->client->dev); + else + ret = mcp4728_resume(&data->client->dev); + + if (ret < 0) + return ret; + + return len; +} + +static const struct iio_enum mcp4728_powerdown_mode_enum = { + .items = mcp4728_powerdown_modes, + .num_items = ARRAY_SIZE(mcp4728_powerdown_modes), + .get = mcp4728_get_powerdown_mode, + .set = mcp4728_set_powerdown_mode, +}; + +static const struct iio_chan_spec_ext_info mcp4728_ext_info[] = { + { + .name = "powerdown", + .read = mcp4728_read_powerdown, + .write = mcp4728_write_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SEPARATE, &mcp4728_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, + &mcp4728_powerdown_mode_enum), + { } +}; + +static const struct iio_chan_spec mcp4728_channels[MCP4728_N_CHANNELS] = { + MCP4728_CHAN(0), + MCP4728_CHAN(1), + MCP4728_CHAN(2), + MCP4728_CHAN(3), +}; + +static void mcp4728_get_scale_avail(enum mcp4728_scale scale, + struct mcp4728_data *data, int *val, + int *val2) +{ + *val = data->scales_avail[scale * 2]; + *val2 = data->scales_avail[scale * 2 + 1]; +} + +static void mcp4728_get_scale(int channel, struct mcp4728_data *data, int *val, + int *val2) +{ + int ref_mode = data->chdata[channel].ref_mode; + int g_mode = data->chdata[channel].g_mode; + + if (ref_mode == MCP4728_VREF_EXTERNAL_VDD) { + mcp4728_get_scale_avail(MCP4728_SCALE_VDD, data, val, val2); + } else { + if (g_mode == MCP4728_GAIN_X1) { + mcp4728_get_scale_avail(MCP4728_SCALE_VINT_NO_GAIN, + data, val, val2); + } else { + mcp4728_get_scale_avail(MCP4728_SCALE_VINT_GAIN_X2, + data, val, val2); + } + } +} + +static int mcp4728_find_matching_scale(struct mcp4728_data *data, int val, + int val2) +{ + for (int i = 0; i < MCP4728_N_SCALES; i++) { + if (data->scales_avail[i * 2] == val && + data->scales_avail[i * 2 + 1] == val2) + return i; + } + return -EINVAL; +} + +static int mcp4728_set_scale(int channel, struct mcp4728_data *data, int val, + int val2) +{ + int scale = mcp4728_find_matching_scale(data, val, val2); + + if (scale < 0) + return scale; + + switch (scale) { + case MCP4728_SCALE_VDD: + data->chdata[channel].ref_mode = MCP4728_VREF_EXTERNAL_VDD; + return 0; + case MCP4728_SCALE_VINT_NO_GAIN: + data->chdata[channel].ref_mode = MCP4728_VREF_INTERNAL_2048mV; + data->chdata[channel].g_mode = MCP4728_GAIN_X1; + return 0; + case MCP4728_SCALE_VINT_GAIN_X2: + data->chdata[channel].ref_mode = MCP4728_VREF_INTERNAL_2048mV; + data->chdata[channel].g_mode = MCP4728_GAIN_X2; + return 0; + default: + return -EINVAL; + } +} + +static int mcp4728_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct mcp4728_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val = data->chdata[chan->channel].dac_value; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + mcp4728_get_scale(chan->channel, data, val, val2); + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static int mcp4728_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct mcp4728_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val < 0 || val > GENMASK(MCP4728_RESOLUTION - 1, 0)) + return -EINVAL; + data->chdata[chan->channel].dac_value = val; + return mcp4728_program_channel_cfg(chan->channel, indio_dev); + case IIO_CHAN_INFO_SCALE: + ret = mcp4728_set_scale(chan->channel, data, val, val2); + if (ret) + return ret; + + return mcp4728_program_channel_cfg(chan->channel, indio_dev); + default: + return -EINVAL; + } +} + +static void mcp4728_init_scale_avail(enum mcp4728_scale scale, int vref_mv, + struct mcp4728_data *data) +{ + s64 tmp; + int value_micro; + int value_int; + + tmp = (s64)vref_mv * 1000000LL >> MCP4728_RESOLUTION; + value_int = div_s64_rem(tmp, 1000000LL, &value_micro); + + data->scales_avail[scale * 2] = value_int; + data->scales_avail[scale * 2 + 1] = value_micro; +} + +static int mcp4728_init_scales_avail(struct mcp4728_data *data, int vdd_mv) +{ + mcp4728_init_scale_avail(MCP4728_SCALE_VDD, vdd_mv, data); + mcp4728_init_scale_avail(MCP4728_SCALE_VINT_NO_GAIN, 2048, data); + mcp4728_init_scale_avail(MCP4728_SCALE_VINT_GAIN_X2, 4096, data); + + return 0; +} + +static int mcp4728_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long info) +{ + struct mcp4728_data *data = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SCALE: + *type = IIO_VAL_INT_PLUS_MICRO; + + switch (chan->type) { + case IIO_VOLTAGE: + *vals = data->scales_avail; + *length = MCP4728_N_SCALES * 2; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static const struct iio_info mcp4728_info = { + .read_raw = mcp4728_read_raw, + .write_raw = mcp4728_write_raw, + .read_avail = &mcp4728_read_avail, + .attrs = &mcp4728_attribute_group, +}; + +static int mcp4728_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct mcp4728_data *data = iio_priv(indio_dev); + unsigned int i; + + data->powerdown = true; + + for (i = 0; i < MCP4728_N_CHANNELS; i++) { + int err = mcp4728_program_channel_cfg(i, indio_dev); + + if (err) + return err; + } + return 0; +} + +static int mcp4728_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct mcp4728_data *data = iio_priv(indio_dev); + int err = 0; + unsigned int i; + + data->powerdown = false; + + for (i = 0; i < MCP4728_N_CHANNELS; i++) { + int ret = mcp4728_program_channel_cfg(i, indio_dev); + + if (ret) + err = ret; + } + return err; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(mcp4728_pm_ops, mcp4728_suspend, + mcp4728_resume); + +static int mcp4728_init_channels_data(struct mcp4728_data *data) +{ + u8 inbuf[MCP4728_READ_RESPONSE_LEN]; + int ret; + unsigned int i; + + ret = i2c_master_recv(data->client, inbuf, MCP4728_READ_RESPONSE_LEN); + if (ret < 0) { + return dev_err_probe(&data->client->dev, ret, + "failed to read mcp4728 conf.\n"); + } else if (ret != MCP4728_READ_RESPONSE_LEN) { + return dev_err_probe(&data->client->dev, -EIO, + "failed to read mcp4728 conf. Wrong Response Len ret=%d\n", + ret); + } + + for (i = 0; i < MCP4728_N_CHANNELS; i++) { + struct mcp4728_channel_data *ch = &data->chdata[i]; + u8 r2 = inbuf[i * 6 + 1]; + u8 r3 = inbuf[i * 6 + 2]; + + ch->dac_value = FIELD_GET(MCP4728_DAC_H_MASK, r2) << 8 | + FIELD_GET(MCP4728_DAC_L_MASK, r3); + ch->ref_mode = FIELD_GET(MCP4728_VREF_MASK, r2); + ch->pd_mode = FIELD_GET(MCP4728_PDMODE_MASK, r2); + ch->g_mode = FIELD_GET(MCP4728_GAIN_MASK, r2); + } + + return 0; +} + +static int mcp4728_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct mcp4728_data *data; + struct iio_dev *indio_dev; + int ret, vdd_mv; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + ret = devm_regulator_get_enable_read_voltage(&client->dev, "vdd"); + if (ret < 0) + return ret; + + vdd_mv = ret / 1000; + + /* + * MCP4728 has internal EEPROM that save each channel boot + * configuration. It means that device configuration is unknown to the + * driver at kernel boot. mcp4728_init_channels_data() reads back DAC + * settings and stores them in data structure. + */ + ret = mcp4728_init_channels_data(data); + if (ret) { + return dev_err_probe(&client->dev, ret, + "failed to read mcp4728 current configuration\n"); + } + + ret = mcp4728_init_scales_avail(data, vdd_mv); + if (ret) { + return dev_err_probe(&client->dev, ret, + "failed to init scales\n"); + } + + indio_dev->name = id->name; + indio_dev->info = &mcp4728_info; + indio_dev->channels = mcp4728_channels; + indio_dev->num_channels = MCP4728_N_CHANNELS; + indio_dev->modes = INDIO_DIRECT_MODE; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id mcp4728_id[] = { + { .name = "mcp4728" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mcp4728_id); + +static const struct of_device_id mcp4728_of_match[] = { + { .compatible = "microchip,mcp4728" }, + { } +}; +MODULE_DEVICE_TABLE(of, mcp4728_of_match); + +static struct i2c_driver mcp4728_driver = { + .driver = { + .name = "mcp4728", + .of_match_table = mcp4728_of_match, + .pm = pm_sleep_ptr(&mcp4728_pm_ops), + }, + .probe = mcp4728_probe, + .id_table = mcp4728_id, +}; +module_i2c_driver(mcp4728_driver); + +MODULE_AUTHOR("Andrea Collamati "); +MODULE_DESCRIPTION("MCP4728 12-bit DAC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/dac/mcp47a1.c b/drivers/iio/dac/mcp47a1.c new file mode 100644 index 000000000..0bf994aa0 --- /dev/null +++ b/drivers/iio/dac/mcp47a1.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Microchip MCP47A1 DAC driver + * + * Copyright (c) 2026 Joshua Crofts + * + * Datasheet: https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/25154A.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define MCP47A1_CMD_CODE 0x00 +#define MCP47A1_MAX_STEPS 64 + +struct mcp47a1_data { + struct i2c_client *client; + int vref_mV; +}; + +static const int mcp47a1_raw_avail[] = { 0, 1, MCP47A1_MAX_STEPS - 1 }; + +static const struct iio_chan_spec mcp47a1_channel = { + .type = IIO_VOLTAGE, + .indexed = 1, + .output = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), +}; + +static int mcp47a1_write(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct mcp47a1_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!in_range(val, 0, MCP47A1_MAX_STEPS)) + return -EINVAL; + + return i2c_smbus_write_byte_data(data->client, MCP47A1_CMD_CODE, + val); + default: + return -EINVAL; + } +} + +static int mcp47a1_read(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mcp47a1_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = i2c_smbus_read_byte_data(data->client, MCP47A1_CMD_CODE); + if (ret < 0) + return ret; + + *val = ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = data->vref_mV; + *val2 = MCP47A1_MAX_STEPS; + + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } +} + +static int mcp47a1_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_RAW: + *vals = mcp47a1_raw_avail; + *type = IIO_VAL_INT; + return IIO_AVAIL_RANGE; + default: + return -EINVAL; + } +} + +static const struct iio_info mcp47a1_info = { + .write_raw = mcp47a1_write, + .read_raw = mcp47a1_read, + .read_avail = mcp47a1_read_avail, +}; + +static int mcp47a1_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct mcp47a1_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulator\n"); + + /* Delay after device exits reset state (see AC/DC characteristics) */ + fsleep(20); + + ret = devm_regulator_get_enable_read_voltage(dev, "vref"); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to read vref\n"); + + data->vref_mV = ret / (MICRO / MILLI); + + indio_dev->name = "mcp47a1"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &mcp47a1_info; + indio_dev->channels = &mcp47a1_channel; + indio_dev->num_channels = 1; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id mcp47a1_of_match[] = { + { .compatible = "microchip,mcp47a1" }, + { } +}; +MODULE_DEVICE_TABLE(of, mcp47a1_of_match); + +static const struct i2c_device_id mcp47a1_id[] = { + { .name = "mcp47a1" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mcp47a1_id); + +static struct i2c_driver mcp47a1_driver = { + .driver = { + .name = "mcp47a1", + .of_match_table = mcp47a1_of_match, + }, + .probe = mcp47a1_probe, + .id_table = mcp47a1_id, +}; +module_i2c_driver(mcp47a1_driver); + +MODULE_AUTHOR("Joshua Crofts "); +MODULE_DESCRIPTION("Microchip MCP47A1 DAC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/dac/mcp47feb02.c b/drivers/iio/dac/mcp47feb02.c new file mode 100644 index 000000000..8640b0ef4 --- /dev/null +++ b/drivers/iio/dac/mcp47feb02.c @@ -0,0 +1,1249 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * IIO driver for MCP47FEB02 Multi-Channel DAC with I2C interface + * + * Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries + * + * Author: Ariana Lazar + * + * Datasheet links: + * [MCP47FEBxx] https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005375A.pdf + * [MCP47FVBxx] https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005405A.pdf + * [MCP47FxBx4/8] https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP47FXBX48-Data-Sheet-DS200006368A.pdf + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register addresses must be left shifted with 3 positions in order to append command mask */ +#define MCP47FEB02_DAC0_REG_ADDR 0x00 +#define MCP47FEB02_VREF_REG_ADDR 0x40 +#define MCP47FEB02_POWER_DOWN_REG_ADDR 0x48 +#define MCP47FEB02_DAC_CTRL_MASK GENMASK(1, 0) + +#define MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR 0x50 +#define MCP47FEB02_GAIN_BIT_MASK BIT(0) +#define MCP47FEB02_GAIN_BIT_STATUS_EEWA_MASK BIT(6) +#define MCP47FEB02_GAIN_BITS_MASK GENMASK(15, 8) + +#define MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR 0x58 + +#define MCP47FEB02_NV_DAC0_REG_ADDR 0x80 +#define MCP47FEB02_NV_VREF_REG_ADDR 0xC0 +#define MCP47FEB02_NV_POWER_DOWN_REG_ADDR 0xC8 +#define MCP47FEB02_NV_GAIN_CTRL_I2C_SLAVE_REG_ADDR 0xD0 +#define MCP47FEB02_NV_I2C_SLAVE_ADDR_MASK GENMASK(7, 0) + +/* Voltage reference, Power-Down control register and DAC Wiperlock status register fields */ +#define DAC_CTRL_MASK(ch) (GENMASK(1, 0) << (2 * (ch))) +#define DAC_CTRL_VAL(ch, val) ((val) << (2 * (ch))) + +/* Gain Control and I2C Slave Address Reguster fields */ +#define DAC_GAIN_MASK(ch) (BIT(0) << (8 + (ch))) +#define DAC_GAIN_VAL(ch, val) ((val) << (8 + (ch))) + +#define REG_ADDR(reg) ((reg) << 3) +#define NV_REG_ADDR(reg) ((NV_DAC_ADDR_OFFSET + (reg)) << 3) +#define READFLAG_MASK GENMASK(2, 1) + +#define MCP47FEB02_MAX_CH 8 +#define MCP47FEB02_MAX_SCALES_CH 3 +#define MCP47FEB02_DAC_WIPER_UNLOCKED 0 +#define MCP47FEB02_NORMAL_OPERATION 0 +#define MCP47FEB02_INTERNAL_BAND_GAP_uV 2440000 +#define NV_DAC_ADDR_OFFSET 0x10 + +enum mcp47feb02_vref_mode { + MCP47FEB02_VREF_VDD = 0, + MCP47FEB02_INTERNAL_BAND_GAP = 1, + MCP47FEB02_EXTERNAL_VREF_UNBUFFERED = 2, + MCP47FEB02_EXTERNAL_VREF_BUFFERED = 3, +}; + +enum mcp47feb02_scale { + MCP47FEB02_SCALE_VDD = 0, + MCP47FEB02_SCALE_GAIN_X1 = 1, + MCP47FEB02_SCALE_GAIN_X2 = 2, +}; + +enum mcp47feb02_gain_bit_mode { + MCP47FEB02_GAIN_BIT_X1 = 0, + MCP47FEB02_GAIN_BIT_X2 = 1, +}; + +static const char * const mcp47feb02_powerdown_modes[] = { + "1kohm_to_gnd", + "100kohm_to_gnd", + "open_circuit", +}; + +/** + * struct mcp47feb02_features - chip specific data + * @name: device name + * @phys_channels: number of hardware channels + * @resolution: DAC resolution + * @have_ext_vref1: does the hardware have an the second external voltage reference? + * @have_eeprom: does the hardware have an internal eeprom? + */ +struct mcp47feb02_features { + const char *name; + unsigned int phys_channels; + unsigned int resolution; + bool have_ext_vref1; + bool have_eeprom; +}; + +static const struct mcp47feb02_features mcp47feb01_chip_features = { + .name = "mcp47feb01", + .phys_channels = 1, + .resolution = 8, + .have_ext_vref1 = false, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb02_chip_features = { + .name = "mcp47feb02", + .phys_channels = 2, + .resolution = 8, + .have_ext_vref1 = false, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb04_chip_features = { + .name = "mcp47feb04", + .phys_channels = 4, + .resolution = 8, + .have_ext_vref1 = true, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb08_chip_features = { + .name = "mcp47feb08", + .phys_channels = 8, + .resolution = 8, + .have_ext_vref1 = true, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb11_chip_features = { + .name = "mcp47feb11", + .phys_channels = 1, + .resolution = 10, + .have_ext_vref1 = false, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb12_chip_features = { + .name = "mcp47feb12", + .phys_channels = 2, + .resolution = 10, + .have_ext_vref1 = false, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb14_chip_features = { + .name = "mcp47feb14", + .phys_channels = 4, + .resolution = 10, + .have_ext_vref1 = true, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb18_chip_features = { + .name = "mcp47feb18", + .phys_channels = 8, + .resolution = 10, + .have_ext_vref1 = true, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb21_chip_features = { + .name = "mcp47feb21", + .phys_channels = 1, + .resolution = 12, + .have_ext_vref1 = false, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb22_chip_features = { + .name = "mcp47feb22", + .phys_channels = 2, + .resolution = 12, + .have_ext_vref1 = false, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb24_chip_features = { + .name = "mcp47feb24", + .phys_channels = 4, + .resolution = 12, + .have_ext_vref1 = true, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb28_chip_features = { + .name = "mcp47feb28", + .phys_channels = 8, + .resolution = 12, + .have_ext_vref1 = true, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47fvb01_chip_features = { + .name = "mcp47fvb01", + .phys_channels = 1, + .resolution = 8, + .have_ext_vref1 = false, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb02_chip_features = { + .name = "mcp47fvb02", + .phys_channels = 2, + .resolution = 8, + .have_ext_vref1 = false, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb04_chip_features = { + .name = "mcp47fvb04", + .phys_channels = 4, + .resolution = 8, + .have_ext_vref1 = true, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb08_chip_features = { + .name = "mcp47fvb08", + .phys_channels = 8, + .resolution = 8, + .have_ext_vref1 = true, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb11_chip_features = { + .name = "mcp47fvb11", + .phys_channels = 1, + .resolution = 10, + .have_ext_vref1 = false, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb12_chip_features = { + .name = "mcp47fvb12", + .phys_channels = 2, + .resolution = 10, + .have_ext_vref1 = false, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb14_chip_features = { + .name = "mcp47fvb14", + .phys_channels = 4, + .resolution = 10, + .have_ext_vref1 = true, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb18_chip_features = { + .name = "mcp47fvb18", + .phys_channels = 8, + .resolution = 10, + .have_ext_vref1 = true, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb21_chip_features = { + .name = "mcp47fvb21", + .phys_channels = 1, + .resolution = 12, + .have_ext_vref1 = false, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb22_chip_features = { + .name = "mcp47fvb22", + .phys_channels = 2, + .resolution = 12, + .have_ext_vref1 = false, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb24_chip_features = { + .name = "mcp47fvb24", + .phys_channels = 4, + .resolution = 12, + .have_ext_vref1 = true, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb28_chip_features = { + .name = "mcp47fvb28", + .phys_channels = 8, + .resolution = 12, + .have_ext_vref1 = true, + .have_eeprom = false, +}; + +/** + * struct mcp47feb02_channel_data - channel configuration + * @ref_mode: chosen voltage for reference + * @use_2x_gain: output driver gain control + * @powerdown: is false if the channel is in normal operation mode + * @powerdown_mode: selected power-down mode + * @dac_data: dac value + */ +struct mcp47feb02_channel_data { + u8 ref_mode; + bool use_2x_gain; + bool powerdown; + u8 powerdown_mode; + u16 dac_data; +}; + +/** + * struct mcp47feb02_data - chip configuration + * @chdata: options configured for each channel on the device + * @lock: prevents concurrent reads/writes to driver's state members + * @chip_features: pointer to features struct + * @scale_1: scales set on channels that are based on Vref1 + * @scale: scales set on channels that are based on Vref/Vref0 + * @active_channels_mask: enabled channels + * @regmap: regmap for directly accessing device register + * @labels: table with channels labels + * @phys_channels: physical channels on the device + * @vref1_buffered: Vref1 buffer is enabled + * @vref_buffered: Vref/Vref0 buffer is enabled + * @use_vref1: vref1-supply is defined + * @use_vref: vref-supply is defined + */ +struct mcp47feb02_data { + struct mcp47feb02_channel_data chdata[MCP47FEB02_MAX_CH]; + struct mutex lock; /* prevents concurrent reads/writes to driver's state members */ + const struct mcp47feb02_features *chip_features; + int scale_1[2 * MCP47FEB02_MAX_SCALES_CH]; + int scale[2 * MCP47FEB02_MAX_SCALES_CH]; + unsigned long active_channels_mask; + struct regmap *regmap; + const char *labels[MCP47FEB02_MAX_CH]; + u16 phys_channels; + bool vref1_buffered; + bool vref_buffered; + bool use_vref1; + bool use_vref; +}; + +static const struct regmap_range mcp47feb02_readable_ranges[] = { + regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR), + regmap_reg_range(MCP47FEB02_NV_DAC0_REG_ADDR, MCP47FEB02_NV_GAIN_CTRL_I2C_SLAVE_REG_ADDR), +}; + +static const struct regmap_range mcp47feb02_writable_ranges[] = { + regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR), + regmap_reg_range(MCP47FEB02_NV_DAC0_REG_ADDR, MCP47FEB02_NV_GAIN_CTRL_I2C_SLAVE_REG_ADDR), +}; + +static const struct regmap_range mcp47feb02_volatile_ranges[] = { + regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR), + regmap_reg_range(MCP47FEB02_NV_DAC0_REG_ADDR, MCP47FEB02_NV_GAIN_CTRL_I2C_SLAVE_REG_ADDR), + regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR), + regmap_reg_range(MCP47FEB02_NV_DAC0_REG_ADDR, MCP47FEB02_NV_GAIN_CTRL_I2C_SLAVE_REG_ADDR), +}; + +static const struct regmap_access_table mcp47feb02_readable_table = { + .yes_ranges = mcp47feb02_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(mcp47feb02_readable_ranges), +}; + +static const struct regmap_access_table mcp47feb02_writable_table = { + .yes_ranges = mcp47feb02_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(mcp47feb02_writable_ranges), +}; + +static const struct regmap_access_table mcp47feb02_volatile_table = { + .yes_ranges = mcp47feb02_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(mcp47feb02_volatile_ranges), +}; + +static const struct regmap_config mcp47feb02_regmap_config = { + .name = "mcp47feb02_regmap", + .reg_bits = 8, + .val_bits = 16, + .rd_table = &mcp47feb02_readable_table, + .wr_table = &mcp47feb02_writable_table, + .volatile_table = &mcp47feb02_volatile_table, + .max_register = MCP47FEB02_NV_GAIN_CTRL_I2C_SLAVE_REG_ADDR, + .read_flag_mask = READFLAG_MASK, + .cache_type = REGCACHE_MAPLE, + .val_format_endian = REGMAP_ENDIAN_BIG, +}; + +/* For devices that doesn't have nonvolatile memory */ +static const struct regmap_range mcp47fvb02_readable_ranges[] = { + regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR), +}; + +static const struct regmap_range mcp47fvb02_writable_ranges[] = { + regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR), +}; + +static const struct regmap_range mcp47fvb02_volatile_ranges[] = { + regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR), + regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR), +}; + +static const struct regmap_access_table mcp47fvb02_readable_table = { + .yes_ranges = mcp47fvb02_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(mcp47fvb02_readable_ranges), +}; + +static const struct regmap_access_table mcp47fvb02_writable_table = { + .yes_ranges = mcp47fvb02_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(mcp47fvb02_writable_ranges), +}; + +static const struct regmap_access_table mcp47fvb02_volatile_table = { + .yes_ranges = mcp47fvb02_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(mcp47fvb02_volatile_ranges), +}; + +static const struct regmap_config mcp47fvb02_regmap_config = { + .name = "mcp47fvb02_regmap", + .reg_bits = 8, + .val_bits = 16, + .rd_table = &mcp47fvb02_readable_table, + .wr_table = &mcp47fvb02_writable_table, + .volatile_table = &mcp47fvb02_volatile_table, + .max_register = MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR, + .read_flag_mask = READFLAG_MASK, + .cache_type = REGCACHE_MAPLE, + .val_format_endian = REGMAP_ENDIAN_BIG, +}; + +static int mcp47feb02_write_to_eeprom(struct mcp47feb02_data *data, unsigned int reg, + unsigned int val) +{ + int eewa_val, ret; + + /* + * Wait until the currently occurring EEPROM Write Cycle is completed. + * Only serial commands to the volatile memory are allowed. + */ + guard(mutex)(&data->lock); + + ret = regmap_read_poll_timeout(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, + eewa_val, + !(eewa_val & MCP47FEB02_GAIN_BIT_STATUS_EEWA_MASK), + USEC_PER_MSEC, USEC_PER_MSEC * 5); + if (ret) + return ret; + + return regmap_write(data->regmap, reg, val); +} + +static ssize_t store_eeprom_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + struct mcp47feb02_data *data = iio_priv(dev_to_iio_dev(dev)); + unsigned int i, val, val1, eewa_val; + bool state; + int ret; + + ret = kstrtobool(buf, &state); + if (ret) + return ret; + + if (!state) + return 0; + + /* + * Verify DAC Wiper and DAC Configuration are unlocked. If both are disabled, + * writing to EEPROM is available. + */ + ret = regmap_read(data->regmap, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR, &val); + if (ret) + return ret; + + if (val) { + dev_err(dev, "DAC Wiper and DAC Configuration not are unlocked.\n"); + return -EINVAL; + } + + for_each_set_bit(i, &data->active_channels_mask, data->phys_channels) { + ret = mcp47feb02_write_to_eeprom(data, NV_REG_ADDR(i), + data->chdata[i].dac_data); + if (ret) + return ret; + } + + ret = regmap_read(data->regmap, MCP47FEB02_VREF_REG_ADDR, &val); + if (ret) + return ret; + + ret = mcp47feb02_write_to_eeprom(data, MCP47FEB02_NV_VREF_REG_ADDR, val); + if (ret) + return ret; + + ret = regmap_read(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR, &val); + if (ret) + return ret; + + ret = mcp47feb02_write_to_eeprom(data, MCP47FEB02_NV_POWER_DOWN_REG_ADDR, val); + if (ret) + return ret; + + ret = regmap_read_poll_timeout(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, eewa_val, + !(eewa_val & MCP47FEB02_GAIN_BIT_STATUS_EEWA_MASK), + USEC_PER_MSEC, USEC_PER_MSEC * 5); + if (ret) + return ret; + + ret = regmap_read(data->regmap, MCP47FEB02_NV_GAIN_CTRL_I2C_SLAVE_REG_ADDR, &val); + if (ret) + return ret; + + ret = regmap_read(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, &val1); + if (ret) + return ret; + + ret = mcp47feb02_write_to_eeprom(data, MCP47FEB02_NV_GAIN_CTRL_I2C_SLAVE_REG_ADDR, + (val1 & MCP47FEB02_GAIN_BITS_MASK) | + (val & MCP47FEB02_NV_I2C_SLAVE_ADDR_MASK)); + if (ret) + return ret; + + return len; +} + +static IIO_DEVICE_ATTR_WO(store_eeprom, 0); + +static struct attribute *mcp47feb02_attributes[] = { + &iio_dev_attr_store_eeprom.dev_attr.attr, + NULL +}; + +static const struct attribute_group mcp47feb02_attribute_group = { + .attrs = mcp47feb02_attributes, +}; + +static int mcp47feb02_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct mcp47feb02_data *data = iio_priv(indio_dev); + int ret; + u8 ch; + + guard(mutex)(&data->lock); + + for_each_set_bit(ch, &data->active_channels_mask, data->phys_channels) { + u8 pd_mode; + + data->chdata[ch].powerdown = true; + pd_mode = data->chdata[ch].powerdown_mode + 1; + ret = regmap_update_bits(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR, + DAC_CTRL_MASK(ch), DAC_CTRL_VAL(ch, pd_mode)); + if (ret) + return ret; + + ret = regmap_write(data->regmap, REG_ADDR(ch), data->chdata[ch].dac_data); + if (ret) + return ret; + } + + return 0; +} + +static int mcp47feb02_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct mcp47feb02_data *data = iio_priv(indio_dev); + u8 ch; + + guard(mutex)(&data->lock); + + for_each_set_bit(ch, &data->active_channels_mask, data->phys_channels) { + u8 pd_mode; + int ret; + + data->chdata[ch].powerdown = false; + pd_mode = data->chdata[ch].powerdown_mode + 1; + + ret = regmap_write(data->regmap, REG_ADDR(ch), data->chdata[ch].dac_data); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, MCP47FEB02_VREF_REG_ADDR, + DAC_CTRL_MASK(ch), DAC_CTRL_VAL(ch, pd_mode)); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, + DAC_GAIN_MASK(ch), + DAC_GAIN_VAL(ch, data->chdata[ch].use_2x_gain)); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR, + DAC_CTRL_MASK(ch), + DAC_CTRL_VAL(ch, MCP47FEB02_NORMAL_OPERATION)); + if (ret) + return ret; + } + + return 0; +} + +static int mcp47feb02_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct mcp47feb02_data *data = iio_priv(indio_dev); + + return data->chdata[chan->address].powerdown_mode; +} + +static int mcp47feb02_set_powerdown_mode(struct iio_dev *indio_dev, const struct iio_chan_spec *ch, + unsigned int mode) +{ + struct mcp47feb02_data *data = iio_priv(indio_dev); + + data->chdata[ch->address].powerdown_mode = mode; + + return 0; +} + +static ssize_t mcp47feb02_read_powerdown(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *ch, char *buf) +{ + struct mcp47feb02_data *data = iio_priv(indio_dev); + + /* Print if channel is in a power-down mode or not */ + return sysfs_emit(buf, "%d\n", data->chdata[ch->address].powerdown); +} + +static ssize_t mcp47feb02_write_powerdown(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *ch, const char *buf, + size_t len) +{ + struct mcp47feb02_data *data = iio_priv(indio_dev); + u32 reg = ch->address; + u8 tmp_pd_mode; + bool state; + int ret; + + guard(mutex)(&data->lock); + + ret = kstrtobool(buf, &state); + if (ret) + return ret; + + /* + * Set the channel to the specified power-down mode. Exiting power-down mode + * requires writing normal operation mode (0) to the channel-specific register bits. + */ + tmp_pd_mode = state ? (data->chdata[reg].powerdown_mode + 1) : MCP47FEB02_NORMAL_OPERATION; + ret = regmap_update_bits(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR, + DAC_CTRL_MASK(reg), DAC_CTRL_VAL(reg, tmp_pd_mode)); + if (ret) + return ret; + + data->chdata[reg].powerdown = state; + + return len; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(mcp47feb02_pm_ops, mcp47feb02_suspend, mcp47feb02_resume); + +static const struct iio_enum mcp47febxx_powerdown_mode_enum = { + .items = mcp47feb02_powerdown_modes, + .num_items = ARRAY_SIZE(mcp47feb02_powerdown_modes), + .get = mcp47feb02_get_powerdown_mode, + .set = mcp47feb02_set_powerdown_mode, +}; + +static const struct iio_chan_spec_ext_info mcp47feb02_ext_info[] = { + { + .name = "powerdown", + .read = mcp47feb02_read_powerdown, + .write = mcp47feb02_write_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SEPARATE, &mcp47febxx_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, &mcp47febxx_powerdown_mode_enum), + { } +}; + +static const struct iio_chan_spec mcp47febxx_ch_template = { + .type = IIO_VOLTAGE, + .output = 1, + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE), + .ext_info = mcp47feb02_ext_info, +}; + +static void mcp47feb02_init_scale(struct mcp47feb02_data *data, enum mcp47feb02_scale scale, + int vref_uV, int scale_avail[]) +{ + u32 value_micro, value_int; + u64 tmp; + + /* vref_uV should not be negative */ + tmp = (u64)vref_uV * MILLI >> data->chip_features->resolution; + value_int = div_u64_rem(tmp, MICRO, &value_micro); + scale_avail[scale * 2] = value_int; + scale_avail[scale * 2 + 1] = value_micro; +} + +static int mcp47feb02_init_scales_avail(struct mcp47feb02_data *data, int vdd_uV, + int vref_uV, int vref1_uV) +{ + int tmp_vref; + + mcp47feb02_init_scale(data, MCP47FEB02_SCALE_VDD, vdd_uV, data->scale); + + if (data->use_vref) + tmp_vref = vref_uV; + else + tmp_vref = MCP47FEB02_INTERNAL_BAND_GAP_uV; + + mcp47feb02_init_scale(data, MCP47FEB02_SCALE_GAIN_X1, tmp_vref, data->scale); + mcp47feb02_init_scale(data, MCP47FEB02_SCALE_GAIN_X2, tmp_vref * 2, data->scale); + + if (data->phys_channels >= 4) { + mcp47feb02_init_scale(data, MCP47FEB02_SCALE_VDD, vdd_uV, data->scale_1); + + if (data->use_vref1) + tmp_vref = vref1_uV; + else + tmp_vref = MCP47FEB02_INTERNAL_BAND_GAP_uV; + + mcp47feb02_init_scale(data, MCP47FEB02_SCALE_GAIN_X1, + tmp_vref, data->scale_1); + mcp47feb02_init_scale(data, MCP47FEB02_SCALE_GAIN_X2, + tmp_vref * 2, data->scale_1); + } + + return 0; +} + +static int mcp47feb02_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *ch, + const int **vals, int *type, int *length, long info) +{ + struct mcp47feb02_data *data = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SCALE: + switch (ch->type) { + case IIO_VOLTAGE: + if (data->phys_channels >= 4 && (ch->address % 2)) + *vals = data->scale_1; + else + *vals = data->scale; + + *length = 2 * MCP47FEB02_MAX_SCALES_CH; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static void mcp47feb02_get_scale(int ch, struct mcp47feb02_data *data, int *val, int *val2) +{ + enum mcp47feb02_scale current_scale; + + if (data->chdata[ch].ref_mode == MCP47FEB02_VREF_VDD) + current_scale = MCP47FEB02_SCALE_VDD; + else if (data->chdata[ch].use_2x_gain) + current_scale = MCP47FEB02_SCALE_GAIN_X2; + else + current_scale = MCP47FEB02_SCALE_GAIN_X1; + + if (data->phys_channels >= 4 && (ch % 2)) { + *val = data->scale_1[current_scale * 2]; + *val2 = data->scale_1[current_scale * 2 + 1]; + } else { + *val = data->scale[current_scale * 2]; + *val2 = data->scale[current_scale * 2 + 1]; + } +} + +static int mcp47feb02_check_scale(struct mcp47feb02_data *data, int val, int val2, int scale[]) +{ + unsigned int i; + + for (i = 0; i < MCP47FEB02_MAX_SCALES_CH; i++) { + if (scale[i * 2] == val && scale[i * 2 + 1] == val2) + return i; + } + + return -EINVAL; +} + +static int mcp47feb02_ch_scale(struct mcp47feb02_data *data, int ch, int scale) +{ + int tmp_val, ret; + + if (scale == MCP47FEB02_SCALE_VDD) { + tmp_val = MCP47FEB02_VREF_VDD; + } else if (data->phys_channels >= 4 && (ch % 2)) { + if (data->use_vref1) { + if (data->vref1_buffered) + tmp_val = MCP47FEB02_EXTERNAL_VREF_BUFFERED; + else + tmp_val = MCP47FEB02_EXTERNAL_VREF_UNBUFFERED; + } else { + tmp_val = MCP47FEB02_INTERNAL_BAND_GAP; + } + } else if (data->use_vref) { + if (data->vref_buffered) + tmp_val = MCP47FEB02_EXTERNAL_VREF_BUFFERED; + else + tmp_val = MCP47FEB02_EXTERNAL_VREF_UNBUFFERED; + } else { + tmp_val = MCP47FEB02_INTERNAL_BAND_GAP; + } + + ret = regmap_update_bits(data->regmap, MCP47FEB02_VREF_REG_ADDR, + DAC_CTRL_MASK(ch), DAC_CTRL_VAL(ch, tmp_val)); + if (ret) + return ret; + + data->chdata[ch].ref_mode = tmp_val; + + return 0; +} + +/* + * Setting the scale in order to choose between VDD and (Vref or Band Gap) from the user + * space. The VREF pin is either an input or an output, therefore the user cannot + * simultaneously connect an external voltage reference to the pin and select the + * internal Band Gap. + * When the DAC’s voltage reference is configured as the VREF pin, the pin is an input. + * When the DAC’s voltage reference is configured as the internal Band Gap, + * the VREF pin is an output. + * If Vref/Vref1 voltage is not available, then the internal Band Gap will be used + * to calculate the values for the scale. + */ +static int mcp47feb02_set_scale(struct mcp47feb02_data *data, int ch, int scale) +{ + int tmp_val, ret; + + ret = mcp47feb02_ch_scale(data, ch, scale); + if (ret) + return ret; + + if (scale == MCP47FEB02_SCALE_GAIN_X2) + tmp_val = MCP47FEB02_GAIN_BIT_X2; + else + tmp_val = MCP47FEB02_GAIN_BIT_X1; + + ret = regmap_update_bits(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, + DAC_GAIN_MASK(ch), DAC_GAIN_VAL(ch, tmp_val)); + if (ret) + return ret; + + data->chdata[ch].use_2x_gain = tmp_val; + + return 0; +} + +static int mcp47feb02_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *ch, + int *val, int *val2, long mask) +{ + struct mcp47feb02_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(data->regmap, REG_ADDR(ch->address), val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + mcp47feb02_get_scale(ch->address, data, val, val2); + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int mcp47feb02_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *ch, + int val, int val2, long mask) +{ + struct mcp47feb02_data *data = iio_priv(indio_dev); + int *tmp_scale, ret; + + guard(mutex)(&data->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_write(data->regmap, REG_ADDR(ch->address), val); + if (ret) + return ret; + + data->chdata[ch->address].dac_data = val; + return 0; + case IIO_CHAN_INFO_SCALE: + if (data->phys_channels >= 4 && (ch->address % 2)) + tmp_scale = data->scale_1; + else + tmp_scale = data->scale; + + ret = mcp47feb02_check_scale(data, val, val2, tmp_scale); + if (ret < 0) + return ret; + + return mcp47feb02_set_scale(data, ch->address, ret); + default: + return -EINVAL; + } +} + +static int mcp47feb02_read_label(struct iio_dev *indio_dev, struct iio_chan_spec const *ch, + char *label) +{ + struct mcp47feb02_data *data = iio_priv(indio_dev); + + return sysfs_emit(label, "%s\n", data->labels[ch->address]); +} + +static const struct iio_info mcp47feb02_info = { + .read_raw = mcp47feb02_read_raw, + .write_raw = mcp47feb02_write_raw, + .read_label = mcp47feb02_read_label, + .read_avail = &mcp47feb02_read_avail, + .attrs = &mcp47feb02_attribute_group, +}; + +static const struct iio_info mcp47fvb02_info = { + .read_raw = mcp47feb02_read_raw, + .write_raw = mcp47feb02_write_raw, + .read_label = mcp47feb02_read_label, + .read_avail = &mcp47feb02_read_avail, +}; + +static int mcp47feb02_parse_fw(struct iio_dev *indio_dev, + const struct mcp47feb02_features *chip_features) +{ + struct iio_chan_spec chanspec = mcp47febxx_ch_template; + struct mcp47feb02_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); + struct iio_chan_spec *channels; + u32 num_channels; + u8 chan_idx = 0; + + num_channels = device_get_child_node_count(dev); + if (num_channels > chip_features->phys_channels) + return dev_err_probe(dev, -EINVAL, "More channels than the chip supports\n"); + + if (!num_channels) + return dev_err_probe(dev, -EINVAL, "No channel specified in the devicetree.\n"); + + channels = devm_kcalloc(dev, num_channels, sizeof(*channels), GFP_KERNEL); + if (!channels) + return -ENOMEM; + + device_for_each_child_node_scoped(dev, child) { + u32 reg = 0; + int ret; + + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret) + return dev_err_probe(dev, ret, "Invalid channel number\n"); + + if (reg >= chip_features->phys_channels) + return dev_err_probe(dev, -EINVAL, + "The index of the channels does not match the chip\n"); + + set_bit(reg, &data->active_channels_mask); + + ret = fwnode_property_read_string(child, "label", &data->labels[reg]); + if (ret) + return dev_err_probe(dev, ret, "%pfw: invalid label\n", + fwnode_get_name(child)); + + chanspec.address = reg; + chanspec.channel = reg; + channels[chan_idx] = chanspec; + chan_idx++; + } + + indio_dev->num_channels = num_channels; + indio_dev->channels = channels; + indio_dev->modes = INDIO_DIRECT_MODE; + data->phys_channels = chip_features->phys_channels; + + data->vref_buffered = device_property_read_bool(dev, "microchip,vref-buffered"); + + if (chip_features->have_ext_vref1) + data->vref1_buffered = device_property_read_bool(dev, "microchip,vref1-buffered"); + + return 0; +} + +static int mcp47feb02_init_ctrl_regs(struct mcp47feb02_data *data) +{ + unsigned int i, vref_ch, gain_ch, pd_ch; + int ret; + + ret = regmap_read(data->regmap, MCP47FEB02_VREF_REG_ADDR, &vref_ch); + if (ret) + return ret; + + ret = regmap_read(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, &gain_ch); + if (ret) + return ret; + + ret = regmap_read(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR, &pd_ch); + if (ret) + return ret; + + gain_ch = gain_ch & MCP47FEB02_GAIN_BITS_MASK; + for_each_set_bit(i, &data->active_channels_mask, data->phys_channels) { + struct device *dev = regmap_get_device(data->regmap); + unsigned int pd_tmp; + + data->chdata[i].ref_mode = (vref_ch >> (2 * i)) & MCP47FEB02_DAC_CTRL_MASK; + data->chdata[i].use_2x_gain = (gain_ch >> i) & MCP47FEB02_GAIN_BIT_MASK; + + /* + * Inform the user that the current voltage reference read from the volatile + * register of the chip is different from the one specified in the device tree. + * Considering that the user cannot have an external voltage reference connected + * to the pin and select the internal Band Gap at the same time, in order to avoid + * miscofiguring the reference voltage, the volatile register will not be written. + * In order to overwrite the setting from volatile register with the one from the + * device tree, the user needs to write the chosen scale. + */ + switch (data->chdata[i].ref_mode) { + case MCP47FEB02_INTERNAL_BAND_GAP: + if (data->phys_channels >= 4 && (i % 2) && data->use_vref1) { + dev_dbg(dev, "ch[%u]: was configured to use internal band gap", i); + dev_dbg(dev, "ch[%u]: reference voltage set to VREF1", i); + break; + } + if ((data->phys_channels < 4 || (data->phys_channels >= 4 && !(i % 2))) && + data->use_vref) { + dev_dbg(dev, "ch[%u]: was configured to use internal band gap", i); + dev_dbg(dev, "ch[%u]: reference voltage set to VREF", i); + break; + } + break; + case MCP47FEB02_EXTERNAL_VREF_UNBUFFERED: + case MCP47FEB02_EXTERNAL_VREF_BUFFERED: + if (data->phys_channels >= 4 && (i % 2) && !data->use_vref1) { + dev_dbg(dev, "ch[%u]: was configured to use VREF1", i); + dev_dbg(dev, + "ch[%u]: reference voltage set to internal band gap", i); + break; + } + if ((data->phys_channels < 4 || (data->phys_channels >= 4 && !(i % 2))) && + !data->use_vref) { + dev_dbg(dev, "ch[%u]: was configured to use VREF", i); + dev_dbg(dev, + "ch[%u]: reference voltage set to internal band gap", i); + break; + } + break; + } + + pd_tmp = (pd_ch >> (2 * i)) & MCP47FEB02_DAC_CTRL_MASK; + data->chdata[i].powerdown_mode = pd_tmp ? (pd_tmp - 1) : pd_tmp; + data->chdata[i].powerdown = !!(data->chdata[i].powerdown_mode); + } + + return 0; +} + +static int mcp47feb02_init_ch_scales(struct mcp47feb02_data *data, int vdd_uV, + int vref_uV, int vref1_uV) +{ + unsigned int i; + + for_each_set_bit(i, &data->active_channels_mask, data->phys_channels) { + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = mcp47feb02_init_scales_avail(data, vdd_uV, vref_uV, vref1_uV); + if (ret) + return dev_err_probe(dev, ret, "failed to init scales for ch %u\n", i); + } + + return 0; +} + +static int mcp47feb02_probe(struct i2c_client *client) +{ + const struct mcp47feb02_features *chip_features; + struct device *dev = &client->dev; + struct mcp47feb02_data *data; + struct iio_dev *indio_dev; + int vref1_uV, vref_uV, vdd_uV, ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + chip_features = i2c_get_match_data(client); + if (!chip_features) + return -EINVAL; + + data->chip_features = chip_features; + + if (chip_features->have_eeprom) { + data->regmap = devm_regmap_init_i2c(client, &mcp47feb02_regmap_config); + indio_dev->info = &mcp47feb02_info; + } else { + data->regmap = devm_regmap_init_i2c(client, &mcp47fvb02_regmap_config); + indio_dev->info = &mcp47fvb02_info; + } + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), "Error initializing i2c regmap\n"); + + indio_dev->name = chip_features->name; + + ret = mcp47feb02_parse_fw(indio_dev, chip_features); + if (ret) + return dev_err_probe(dev, ret, "Error parsing firmware data\n"); + + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; + + ret = devm_regulator_get_enable_read_voltage(dev, "vdd"); + if (ret < 0) + return ret; + + vdd_uV = ret; + + if (device_property_present(dev, "vref-supply")) { + vref_uV = devm_regulator_get_enable_read_voltage(dev, "vref"); + if (vref_uV < 0) + return vref_uV; + + if (vref_uV == 0) + return dev_err_probe(dev, -EINVAL, "Vref is 0 uV.\n"); + + data->use_vref = true; + } else { + vref_uV = 0; + dev_dbg(dev, "Using internal band gap as voltage reference.\n"); + } + + if (chip_features->have_ext_vref1 && + device_property_present(dev, "vref1-supply")) { + vref1_uV = devm_regulator_get_enable_read_voltage(dev, "vref1"); + if (vref1_uV < 0) + return vref1_uV; + + if (vref1_uV == 0) + return dev_err_probe(dev, -EINVAL, "Vref1 is 0 uV.\n"); + + data->use_vref1 = true; + } else { + vref1_uV = 0; + dev_dbg(dev, "Using internal band gap as voltage reference 1.\n"); + } + + ret = mcp47feb02_init_ctrl_regs(data); + if (ret) + return dev_err_probe(dev, ret, "Error initialising vref register\n"); + + ret = mcp47feb02_init_ch_scales(data, vdd_uV, vref_uV, vref1_uV); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct i2c_device_id mcp47feb02_id[] = { + { .name = "mcp47feb01", .driver_data = (kernel_ulong_t)&mcp47feb01_chip_features }, + { .name = "mcp47feb02", .driver_data = (kernel_ulong_t)&mcp47feb02_chip_features }, + { .name = "mcp47feb04", .driver_data = (kernel_ulong_t)&mcp47feb04_chip_features }, + { .name = "mcp47feb08", .driver_data = (kernel_ulong_t)&mcp47feb08_chip_features }, + { .name = "mcp47feb11", .driver_data = (kernel_ulong_t)&mcp47feb11_chip_features }, + { .name = "mcp47feb12", .driver_data = (kernel_ulong_t)&mcp47feb12_chip_features }, + { .name = "mcp47feb14", .driver_data = (kernel_ulong_t)&mcp47feb14_chip_features }, + { .name = "mcp47feb18", .driver_data = (kernel_ulong_t)&mcp47feb18_chip_features }, + { .name = "mcp47feb21", .driver_data = (kernel_ulong_t)&mcp47feb21_chip_features }, + { .name = "mcp47feb22", .driver_data = (kernel_ulong_t)&mcp47feb22_chip_features }, + { .name = "mcp47feb24", .driver_data = (kernel_ulong_t)&mcp47feb24_chip_features }, + { .name = "mcp47feb28", .driver_data = (kernel_ulong_t)&mcp47feb28_chip_features }, + { .name = "mcp47fvb01", .driver_data = (kernel_ulong_t)&mcp47fvb01_chip_features }, + { .name = "mcp47fvb02", .driver_data = (kernel_ulong_t)&mcp47fvb02_chip_features }, + { .name = "mcp47fvb04", .driver_data = (kernel_ulong_t)&mcp47fvb04_chip_features }, + { .name = "mcp47fvb08", .driver_data = (kernel_ulong_t)&mcp47fvb08_chip_features }, + { .name = "mcp47fvb11", .driver_data = (kernel_ulong_t)&mcp47fvb11_chip_features }, + { .name = "mcp47fvb12", .driver_data = (kernel_ulong_t)&mcp47fvb12_chip_features }, + { .name = "mcp47fvb14", .driver_data = (kernel_ulong_t)&mcp47fvb14_chip_features }, + { .name = "mcp47fvb18", .driver_data = (kernel_ulong_t)&mcp47fvb18_chip_features }, + { .name = "mcp47fvb21", .driver_data = (kernel_ulong_t)&mcp47fvb21_chip_features }, + { .name = "mcp47fvb22", .driver_data = (kernel_ulong_t)&mcp47fvb22_chip_features }, + { .name = "mcp47fvb24", .driver_data = (kernel_ulong_t)&mcp47fvb24_chip_features }, + { .name = "mcp47fvb28", .driver_data = (kernel_ulong_t)&mcp47fvb28_chip_features }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mcp47feb02_id); + +static const struct of_device_id mcp47feb02_of_match[] = { + { .compatible = "microchip,mcp47feb01", .data = &mcp47feb01_chip_features }, + { .compatible = "microchip,mcp47feb02", .data = &mcp47feb02_chip_features }, + { .compatible = "microchip,mcp47feb04", .data = &mcp47feb04_chip_features }, + { .compatible = "microchip,mcp47feb08", .data = &mcp47feb08_chip_features }, + { .compatible = "microchip,mcp47feb11", .data = &mcp47feb11_chip_features }, + { .compatible = "microchip,mcp47feb12", .data = &mcp47feb12_chip_features }, + { .compatible = "microchip,mcp47feb14", .data = &mcp47feb14_chip_features }, + { .compatible = "microchip,mcp47feb18", .data = &mcp47feb18_chip_features }, + { .compatible = "microchip,mcp47feb21", .data = &mcp47feb21_chip_features }, + { .compatible = "microchip,mcp47feb22", .data = &mcp47feb22_chip_features }, + { .compatible = "microchip,mcp47feb24", .data = &mcp47feb24_chip_features }, + { .compatible = "microchip,mcp47feb28", .data = &mcp47feb28_chip_features }, + { .compatible = "microchip,mcp47fvb01", .data = &mcp47fvb01_chip_features }, + { .compatible = "microchip,mcp47fvb02", .data = &mcp47fvb02_chip_features }, + { .compatible = "microchip,mcp47fvb04", .data = &mcp47fvb04_chip_features }, + { .compatible = "microchip,mcp47fvb08", .data = &mcp47fvb08_chip_features }, + { .compatible = "microchip,mcp47fvb11", .data = &mcp47fvb11_chip_features }, + { .compatible = "microchip,mcp47fvb12", .data = &mcp47fvb12_chip_features }, + { .compatible = "microchip,mcp47fvb14", .data = &mcp47fvb14_chip_features }, + { .compatible = "microchip,mcp47fvb18", .data = &mcp47fvb18_chip_features }, + { .compatible = "microchip,mcp47fvb21", .data = &mcp47fvb21_chip_features }, + { .compatible = "microchip,mcp47fvb22", .data = &mcp47fvb22_chip_features }, + { .compatible = "microchip,mcp47fvb24", .data = &mcp47fvb24_chip_features }, + { .compatible = "microchip,mcp47fvb28", .data = &mcp47fvb28_chip_features }, + { } +}; +MODULE_DEVICE_TABLE(of, mcp47feb02_of_match); + +static struct i2c_driver mcp47feb02_driver = { + .driver = { + .name = "mcp47feb02", + .of_match_table = mcp47feb02_of_match, + .pm = pm_sleep_ptr(&mcp47feb02_pm_ops), + }, + .probe = mcp47feb02_probe, + .id_table = mcp47feb02_id, +}; +module_i2c_driver(mcp47feb02_driver); + +MODULE_AUTHOR("Ariana Lazar "); +MODULE_DESCRIPTION("IIO driver for MCP47FEB02 Multi-Channel DAC with I2C interface"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/dac/mcp4821.c b/drivers/iio/dac/mcp4821.c new file mode 100644 index 000000000..8d8c00cd6 --- /dev/null +++ b/drivers/iio/dac/mcp4821.c @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023 Anshul Dalal + * + * Driver for Microchip MCP4801, MCP4802, MCP4811, MCP4812, MCP4821 and MCP4822 + * + * Based on the work of: + * Michael Welling (MCP4922 Driver) + * + * Datasheet: + * MCP48x1: https://ww1.microchip.com/downloads/en/DeviceDoc/22244B.pdf + * MCP48x2: https://ww1.microchip.com/downloads/en/DeviceDoc/20002249B.pdf + * + * TODO: + * - Regulator control + */ + +#include +#include +#include + +#include +#include + +#include + +#define MCP4821_ACTIVE_MODE BIT(12) +#define MCP4821_GAIN_ENABLE BIT(13) +#define MCP4802_SECOND_CHAN BIT(15) + +/* DAC uses an internal Voltage reference of 2.048V */ +#define MCP4821_VREF_MV 2048 + +/* + * MCP48xx DAC output: + * + * Vout = (Vref * D / 2^N) * G + * + * where: + * - Vref = 2.048V (internal reference) + * - N = DAC resolution (12 bits for MCP4821) + * - G = gain selection: + * 1x when GA bit = 1 + * 2x when GA bit = 0 (default) + * + * Therefore full-scale voltage is: + * - 1x gain: 2.048V + * - 2x gain: 4.096V + * + * Scale = Vfull-scale / 2^N + */ + +enum mcp4821_supported_device_ids { + ID_MCP4801, + ID_MCP4802, + ID_MCP4811, + ID_MCP4812, + ID_MCP4821, + ID_MCP4822, +}; + +struct mcp4821_state { + struct spi_device *spi; + u16 dac_value[2]; + int gain; + int scale_avail[4]; +}; + +struct mcp4821_chip_info { + const char *name; + int num_channels; + const struct iio_chan_spec channels[2]; +}; + +#define MCP4821_CHAN(channel_id, resolution) \ + { \ + .type = IIO_VOLTAGE, .output = 1, .indexed = 1, \ + .channel = (channel_id), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_type = { \ + .realbits = (resolution), \ + .shift = 12 - (resolution), \ + }, \ + } + +static const struct mcp4821_chip_info mcp4821_chip_info_table[6] = { + [ID_MCP4801] = { + .name = "mcp4801", + .num_channels = 1, + .channels = { + MCP4821_CHAN(0, 8), + }, + }, + [ID_MCP4802] = { + .name = "mcp4802", + .num_channels = 2, + .channels = { + MCP4821_CHAN(0, 8), + MCP4821_CHAN(1, 8), + }, + }, + [ID_MCP4811] = { + .name = "mcp4811", + .num_channels = 1, + .channels = { + MCP4821_CHAN(0, 10), + }, + }, + [ID_MCP4812] = { + .name = "mcp4812", + .num_channels = 2, + .channels = { + MCP4821_CHAN(0, 10), + MCP4821_CHAN(1, 10), + }, + }, + [ID_MCP4821] = { + .name = "mcp4821", + .num_channels = 1, + .channels = { + MCP4821_CHAN(0, 12), + }, + }, + [ID_MCP4822] = { + .name = "mcp4822", + .num_channels = 2, + .channels = { + MCP4821_CHAN(0, 12), + MCP4821_CHAN(1, 12), + }, + }, +}; + +static int mcp4821_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct mcp4821_state *state = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val = state->dac_value[chan->channel]; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = MCP4821_VREF_MV * state->gain; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static void mcp4821_calc_scale(int vref_mv, int resolution, + int *val, int *val2) +{ + s64 tmp; + int micro; + + tmp = (s64)vref_mv * MICRO >> resolution; + *val = div_s64_rem(tmp, MICRO, µ); + *val2 = micro; +} + +static int mcp4821_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct mcp4821_state *state = iio_priv(indio_dev); + u16 write_val; + __be16 write_buffer; + int ret; + int v, v2; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + + if (val2 != 0) + return -EINVAL; + + if (val < 0 || val >= BIT(chan->scan_type.realbits)) + return -EINVAL; + + write_val = MCP4821_ACTIVE_MODE | val << chan->scan_type.shift; + if (chan->channel) + write_val |= MCP4802_SECOND_CHAN; + + /* GA bit = 1 -> 1x gain */ + if (state->gain == 1) + write_val |= MCP4821_GAIN_ENABLE; + + write_buffer = cpu_to_be16(write_val); + ret = spi_write(state->spi, &write_buffer, sizeof(write_buffer)); + if (ret) { + dev_err(&state->spi->dev, "Failed to write to device: %d", ret); + return ret; + } + + state->dac_value[chan->channel] = val; + return 0; + + case IIO_CHAN_INFO_SCALE: + mcp4821_calc_scale(MCP4821_VREF_MV, chan->scan_type.realbits, &v, &v2); + if (val == v && val2 == v2) { + state->gain = 1; + return 0; + } + + mcp4821_calc_scale(MCP4821_VREF_MV * 2, + chan->scan_type.realbits, &v, &v2); + if (val == v && val2 == v2) { + state->gain = 2; + return 0; + } + return -EINVAL; + default: + return -EINVAL; + } +} + +static inline void mcp4821_init_avail_gain(struct mcp4821_state *state, + int resolution) +{ + state->scale_avail[0] = MCP4821_VREF_MV; + state->scale_avail[1] = resolution; + state->scale_avail[2] = MCP4821_VREF_MV * 2; + state->scale_avail[3] = resolution; +} + +static int mcp4821_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long info) +{ + struct mcp4821_state *state = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SCALE: + *vals = state->scale_avail; + *type = IIO_VAL_FRACTIONAL_LOG2; + *length = ARRAY_SIZE(state->scale_avail); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const struct iio_info mcp4821_info = { + .read_raw = &mcp4821_read_raw, + .write_raw = &mcp4821_write_raw, + .read_avail = &mcp4821_read_avail, +}; + +static int mcp4821_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct mcp4821_state *state; + const struct mcp4821_chip_info *info; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*state)); + if (indio_dev == NULL) + return -ENOMEM; + + state = iio_priv(indio_dev); + state->spi = spi; + + /* default gain is 2x */ + state->gain = 2; + info = spi_get_device_match_data(spi); + indio_dev->name = info->name; + indio_dev->info = &mcp4821_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = info->channels; + indio_dev->num_channels = info->num_channels; + mcp4821_init_avail_gain(state, info->channels[0].scan_type.realbits); + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +#define MCP4821_COMPATIBLE(of_compatible, id) \ + { \ + .compatible = of_compatible, \ + .data = &mcp4821_chip_info_table[id] \ + } + +static const struct of_device_id mcp4821_of_table[] = { + MCP4821_COMPATIBLE("microchip,mcp4801", ID_MCP4801), + MCP4821_COMPATIBLE("microchip,mcp4802", ID_MCP4802), + MCP4821_COMPATIBLE("microchip,mcp4811", ID_MCP4811), + MCP4821_COMPATIBLE("microchip,mcp4812", ID_MCP4812), + MCP4821_COMPATIBLE("microchip,mcp4821", ID_MCP4821), + MCP4821_COMPATIBLE("microchip,mcp4822", ID_MCP4822), + { } +}; +MODULE_DEVICE_TABLE(of, mcp4821_of_table); + +static const struct spi_device_id mcp4821_id_table[] = { + { .name = "mcp4801", .driver_data = (kernel_ulong_t)&mcp4821_chip_info_table[ID_MCP4801] }, + { .name = "mcp4802", .driver_data = (kernel_ulong_t)&mcp4821_chip_info_table[ID_MCP4802] }, + { .name = "mcp4811", .driver_data = (kernel_ulong_t)&mcp4821_chip_info_table[ID_MCP4811] }, + { .name = "mcp4812", .driver_data = (kernel_ulong_t)&mcp4821_chip_info_table[ID_MCP4812] }, + { .name = "mcp4821", .driver_data = (kernel_ulong_t)&mcp4821_chip_info_table[ID_MCP4821] }, + { .name = "mcp4822", .driver_data = (kernel_ulong_t)&mcp4821_chip_info_table[ID_MCP4822] }, + { } +}; +MODULE_DEVICE_TABLE(spi, mcp4821_id_table); + +static struct spi_driver mcp4821_driver = { + .driver = { + .name = "mcp4821", + .of_match_table = mcp4821_of_table, + }, + .probe = mcp4821_probe, + .id_table = mcp4821_id_table, +}; +module_spi_driver(mcp4821_driver); + +MODULE_AUTHOR("Anshul Dalal "); +MODULE_DESCRIPTION("Microchip MCP4821 DAC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/dac/mcp4922.c b/drivers/iio/dac/mcp4922.c new file mode 100644 index 000000000..15072a908 --- /dev/null +++ b/drivers/iio/dac/mcp4922.c @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * mcp4922.c + * + * Driver for Microchip Digital to Analog Converters. + * Supports MCP4902, MCP4912, and MCP4922. + * + * Copyright (c) 2014 EMAC Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define MCP4922_NUM_CHANNELS 2 +#define MCP4921_NUM_CHANNELS 1 + +enum mcp4922_supported_device_ids { + ID_MCP4902, + ID_MCP4912, + ID_MCP4921, + ID_MCP4922, +}; + +struct mcp4922_state { + struct spi_device *spi; + unsigned int value[MCP4922_NUM_CHANNELS]; + unsigned int vref_mv; + u8 mosi[2] __aligned(IIO_DMA_MINALIGN); +}; + +#define MCP4922_CHAN(chan, bits) { \ + .type = IIO_VOLTAGE, \ + .output = 1, \ + .indexed = 1, \ + .channel = chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 12 - (bits), \ + }, \ +} + +static int mcp4922_spi_write(struct mcp4922_state *state, u8 addr, u32 val) +{ + state->mosi[1] = val & 0xff; + state->mosi[0] = (addr == 0) ? 0x00 : 0x80; + state->mosi[0] |= 0x30 | ((val >> 8) & 0x0f); + + return spi_write(state->spi, state->mosi, 2); +} + +static int mcp4922_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct mcp4922_state *state = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val = state->value[chan->channel]; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = state->vref_mv; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static int mcp4922_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct mcp4922_state *state = iio_priv(indio_dev); + int ret; + + if (val2 != 0) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val < 0 || val > GENMASK(chan->scan_type.realbits - 1, 0)) + return -EINVAL; + val <<= chan->scan_type.shift; + + ret = mcp4922_spi_write(state, chan->channel, val); + if (!ret) + state->value[chan->channel] = val; + return ret; + + default: + return -EINVAL; + } +} + +static const struct iio_chan_spec mcp4922_channels[4][MCP4922_NUM_CHANNELS] = { + [ID_MCP4902] = { MCP4922_CHAN(0, 8), MCP4922_CHAN(1, 8) }, + [ID_MCP4912] = { MCP4922_CHAN(0, 10), MCP4922_CHAN(1, 10) }, + [ID_MCP4921] = { MCP4922_CHAN(0, 12), {} }, + [ID_MCP4922] = { MCP4922_CHAN(0, 12), MCP4922_CHAN(1, 12) }, +}; + +static const struct iio_info mcp4922_info = { + .read_raw = &mcp4922_read_raw, + .write_raw = &mcp4922_write_raw, +}; + +static int mcp4922_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct mcp4922_state *state; + const struct spi_device_id *id; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*state)); + if (indio_dev == NULL) + return -ENOMEM; + + state = iio_priv(indio_dev); + state->spi = spi; + + ret = devm_regulator_get_enable_read_voltage(&spi->dev, "vref"); + if (ret < 0) + return dev_err_probe(&spi->dev, ret, "Failed to get vref voltage\n"); + + state->vref_mv = ret / 1000; + + id = spi_get_device_id(spi); + indio_dev->info = &mcp4922_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = mcp4922_channels[id->driver_data]; + if (id->driver_data == ID_MCP4921) + indio_dev->num_channels = MCP4921_NUM_CHANNELS; + else + indio_dev->num_channels = MCP4922_NUM_CHANNELS; + indio_dev->name = id->name; + + ret = devm_iio_device_register(&spi->dev, indio_dev); + if (ret) + return dev_err_probe(&spi->dev, ret, "Failed to register iio device\n"); + + return 0; +} + + +static const struct spi_device_id mcp4922_id[] = { + { .name = "mcp4902", .driver_data = ID_MCP4902 }, + { .name = "mcp4912", .driver_data = ID_MCP4912 }, + { .name = "mcp4921", .driver_data = ID_MCP4921 }, + { .name = "mcp4922", .driver_data = ID_MCP4922 }, + { } +}; +MODULE_DEVICE_TABLE(spi, mcp4922_id); + +static struct spi_driver mcp4922_driver = { + .driver = { + .name = "mcp4922", + }, + .probe = mcp4922_probe, + .id_table = mcp4922_id, +}; +module_spi_driver(mcp4922_driver); + +MODULE_AUTHOR("Michael Welling "); +MODULE_DESCRIPTION("Microchip MCP4902, MCP4912, MCP4922 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/rohm-bd79703.c b/drivers/iio/dac/rohm-bd79703.c new file mode 100644 index 000000000..6e2e7787f --- /dev/null +++ b/drivers/iio/dac/rohm-bd79703.c @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * BD79703 ROHM Digital to Analog converter + * + * Copyright (c) 2024, ROHM Semiconductor. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define BD79703_MAX_REGISTER 0xf +#define BD79703_DAC_BITS 8 +#define BD79703_REG_OUT_ALL GENMASK(2, 0) + +/* + * The BD79703 uses 12-bit SPI commands. First four bits (high bits) define + * channel(s) which are operated on, and also the mode. The mode can be to set + * a DAC word only, or set DAC word and output. The data-sheet is not very + * specific on how a previously set DAC word can be 'taken in to use'. Thus + * this driver only uses the 'set DAC and output it' -mode. + * + * The BD79703 latches last 12-bits when the chip-select is toggled. Thus we + * can use 16-bit transfers which should be widely supported. To simplify this + * further, we treat the last 8 bits as a value, and first 8 bits as an + * address. This allows us to separate channels/mode by address and treat the + * 8-bit register value as DAC word. The highest 4 bits of address will be + * discarded when the transfer is latched. + */ +static const struct regmap_config bd79703_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = BD79703_MAX_REGISTER, + .cache_type = REGCACHE_MAPLE, +}; + +/* Dynamic driver private data */ +struct bd79703_data { + struct regmap *regmap; + int vfs; +}; + +/* Static, IC type specific data for different variants */ +struct bd7970x_chip_data { + const char *name; + const struct iio_chan_spec *channels; + int num_channels; + bool has_vfs; +}; + +static int bd79703_read_raw(struct iio_dev *idev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct bd79703_data *data = iio_priv(idev); + + if (mask != IIO_CHAN_INFO_SCALE) + return -EINVAL; + + *val = data->vfs / 1000; + *val2 = BD79703_DAC_BITS; + + return IIO_VAL_FRACTIONAL_LOG2; +} + +static int bd79703_write_raw(struct iio_dev *idev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct bd79703_data *data = iio_priv(idev); + + if (val < 0 || val >= 1 << BD79703_DAC_BITS) + return -EINVAL; + + return regmap_write(data->regmap, chan->address, val); +}; + +static const struct iio_info bd79703_info = { + .read_raw = bd79703_read_raw, + .write_raw = bd79703_write_raw, +}; + +#define BD79703_CHAN_ADDR(_chan, _addr) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (_chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .address = (_addr), \ +} + +#define BD79703_CHAN(_chan) BD79703_CHAN_ADDR((_chan), (_chan) + 1) + +static const struct iio_chan_spec bd79700_channels[] = { + BD79703_CHAN(0), + BD79703_CHAN(1), +}; + +static const struct iio_chan_spec bd79701_channels[] = { + BD79703_CHAN(0), + BD79703_CHAN(1), + BD79703_CHAN(2), +}; + +/* + * The BD79702 has 4 channels. They aren't mapped to BD79703 channels 0, 1, 2 + * and 3, but to the channels 0, 1, 4, 5. So the addressing used with SPI + * accesses is 1, 2, 5 and 6 for them. Thus, they're not constant offset to + * the channel number as with other IC variants. + */ +static const struct iio_chan_spec bd79702_channels[] = { + BD79703_CHAN_ADDR(0, 1), + BD79703_CHAN_ADDR(1, 2), + BD79703_CHAN_ADDR(2, 5), + BD79703_CHAN_ADDR(3, 6), +}; + +static const struct iio_chan_spec bd79703_channels[] = { + BD79703_CHAN(0), + BD79703_CHAN(1), + BD79703_CHAN(2), + BD79703_CHAN(3), + BD79703_CHAN(4), + BD79703_CHAN(5), +}; + +static const struct bd7970x_chip_data bd79700_chip_data = { + .name = "bd79700", + .channels = bd79700_channels, + .num_channels = ARRAY_SIZE(bd79700_channels), + .has_vfs = false, +}; + +static const struct bd7970x_chip_data bd79701_chip_data = { + .name = "bd79701", + .channels = bd79701_channels, + .num_channels = ARRAY_SIZE(bd79701_channels), + .has_vfs = false, +}; + +static const struct bd7970x_chip_data bd79702_chip_data = { + .name = "bd79702", + .channels = bd79702_channels, + .num_channels = ARRAY_SIZE(bd79702_channels), + .has_vfs = true, +}; + +static const struct bd7970x_chip_data bd79703_chip_data = { + .name = "bd79703", + .channels = bd79703_channels, + .num_channels = ARRAY_SIZE(bd79703_channels), + .has_vfs = true, +}; + +static int bd79703_probe(struct spi_device *spi) +{ + const struct bd7970x_chip_data *cd; + struct device *dev = &spi->dev; + struct bd79703_data *data; + struct iio_dev *idev; + int ret; + + cd = spi_get_device_match_data(spi); + if (!cd) + return -ENODEV; + + idev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!idev) + return -ENOMEM; + + data = iio_priv(idev); + + data->regmap = devm_regmap_init_spi(spi, &bd79703_regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "Failed to initialize Regmap\n"); + + /* + * BD79703 has a separate VFS pin, whereas the BD79700 and BD79701 use + * VCC for their full-scale output voltage. + */ + if (cd->has_vfs) { + ret = devm_regulator_get_enable(dev, "vcc"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable VCC\n"); + + ret = devm_regulator_get_enable_read_voltage(dev, "vfs"); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get Vfs\n"); + } else { + ret = devm_regulator_get_enable_read_voltage(dev, "vcc"); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get VCC\n"); + } + data->vfs = ret; + + idev->channels = cd->channels; + idev->num_channels = cd->num_channels; + idev->modes = INDIO_DIRECT_MODE; + idev->info = &bd79703_info; + idev->name = cd->name; + + /* Initialize all to output zero */ + ret = regmap_write(data->regmap, BD79703_REG_OUT_ALL, 0); + if (ret) + return ret; + + return devm_iio_device_register(dev, idev); +} + +static const struct spi_device_id bd79703_id[] = { + { .name = "bd79700", .driver_data = (kernel_ulong_t)&bd79700_chip_data }, + { .name = "bd79701", .driver_data = (kernel_ulong_t)&bd79701_chip_data }, + { .name = "bd79702", .driver_data = (kernel_ulong_t)&bd79702_chip_data }, + { .name = "bd79703", .driver_data = (kernel_ulong_t)&bd79703_chip_data }, + { } +}; +MODULE_DEVICE_TABLE(spi, bd79703_id); + +static const struct of_device_id bd79703_of_match[] = { + { .compatible = "rohm,bd79700", .data = &bd79700_chip_data }, + { .compatible = "rohm,bd79701", .data = &bd79701_chip_data }, + { .compatible = "rohm,bd79702", .data = &bd79702_chip_data }, + { .compatible = "rohm,bd79703", .data = &bd79703_chip_data }, + { } +}; +MODULE_DEVICE_TABLE(of, bd79703_of_match); + +static struct spi_driver bd79703_driver = { + .driver = { + .name = "bd79703", + .of_match_table = bd79703_of_match, + }, + .probe = bd79703_probe, + .id_table = bd79703_id, +}; +module_spi_driver(bd79703_driver); + +MODULE_AUTHOR("Matti Vaittinen "); +MODULE_DESCRIPTION("ROHM BD79703 DAC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/dac/stm32-dac-core.c b/drivers/iio/dac/stm32-dac-core.c new file mode 100644 index 000000000..b5795c14f --- /dev/null +++ b/drivers/iio/dac/stm32-dac-core.c @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This file is part of STM32 DAC driver + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Author: Fabrice Gasnier . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "stm32-dac-core.h" + +/** + * struct stm32_dac_priv - stm32 DAC core private data + * @pclk: peripheral clock common for all DACs + * @vref: regulator reference + * @common: Common data for all DAC instances + */ +struct stm32_dac_priv { + struct clk *pclk; + struct regulator *vref; + struct stm32_dac_common common; +}; + +/** + * struct stm32_dac_cfg - DAC configuration + * @has_hfsel: DAC has high frequency control + */ +struct stm32_dac_cfg { + bool has_hfsel; +}; + +static struct stm32_dac_priv *to_stm32_dac_priv(struct stm32_dac_common *com) +{ + return container_of(com, struct stm32_dac_priv, common); +} + +static const struct regmap_config stm32_dac_regmap_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = sizeof(u32), + .max_register = 0x3fc, +}; + +static int stm32_dac_core_hw_start(struct device *dev) +{ + struct stm32_dac_common *common = dev_get_drvdata(dev); + struct stm32_dac_priv *priv = to_stm32_dac_priv(common); + int ret; + + ret = regulator_enable(priv->vref); + if (ret < 0) { + dev_err(dev, "vref enable failed: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(priv->pclk); + if (ret < 0) { + dev_err(dev, "pclk enable failed: %d\n", ret); + goto err_regulator_disable; + } + + return 0; + +err_regulator_disable: + regulator_disable(priv->vref); + + return ret; +} + +static void stm32_dac_core_hw_stop(struct device *dev) +{ + struct stm32_dac_common *common = dev_get_drvdata(dev); + struct stm32_dac_priv *priv = to_stm32_dac_priv(common); + + clk_disable_unprepare(priv->pclk); + regulator_disable(priv->vref); +} + +static int stm32_dac_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct stm32_dac_cfg *cfg; + struct stm32_dac_priv *priv; + struct regmap *regmap; + void __iomem *mmio; + struct reset_control *rst; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + platform_set_drvdata(pdev, &priv->common); + + cfg = device_get_match_data(dev); + + mmio = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mmio)) + return PTR_ERR(mmio); + + regmap = devm_regmap_init_mmio_clk(dev, "pclk", mmio, + &stm32_dac_regmap_cfg); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + priv->common.regmap = regmap; + + priv->pclk = devm_clk_get(dev, "pclk"); + if (IS_ERR(priv->pclk)) + return dev_err_probe(dev, PTR_ERR(priv->pclk), "pclk get failed\n"); + + priv->vref = devm_regulator_get(dev, "vref"); + if (IS_ERR(priv->vref)) + return dev_err_probe(dev, PTR_ERR(priv->vref), "vref get failed\n"); + + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + ret = stm32_dac_core_hw_start(dev); + if (ret) + goto err_pm_stop; + + ret = regulator_get_voltage(priv->vref); + if (ret < 0) { + dev_err(dev, "vref get voltage failed, %d\n", ret); + goto err_hw_stop; + } + priv->common.vref_mv = ret / 1000; + dev_dbg(dev, "vref+=%dmV\n", priv->common.vref_mv); + + rst = devm_reset_control_get_optional_exclusive(dev, NULL); + if (rst) { + if (IS_ERR(rst)) { + ret = dev_err_probe(dev, PTR_ERR(rst), "reset get failed\n"); + goto err_hw_stop; + } + + reset_control_assert(rst); + udelay(2); + reset_control_deassert(rst); + } + + if (cfg && cfg->has_hfsel) { + /* When clock speed is higher than 80MHz, set HFSEL */ + priv->common.hfsel = (clk_get_rate(priv->pclk) > 80000000UL); + ret = regmap_update_bits(regmap, STM32_DAC_CR, + STM32H7_DAC_CR_HFSEL, + priv->common.hfsel ? + STM32H7_DAC_CR_HFSEL : 0); + if (ret) + goto err_hw_stop; + } + + + ret = of_platform_populate(pdev->dev.of_node, NULL, NULL, dev); + if (ret < 0) { + dev_err(dev, "failed to populate DT children\n"); + goto err_hw_stop; + } + + pm_runtime_put(dev); + + return 0; + +err_hw_stop: + stm32_dac_core_hw_stop(dev); +err_pm_stop: + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_put_noidle(dev); + + return ret; +} + +static void stm32_dac_remove(struct platform_device *pdev) +{ + pm_runtime_get_sync(&pdev->dev); + of_platform_depopulate(&pdev->dev); + stm32_dac_core_hw_stop(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); +} + +static int stm32_dac_core_resume(struct device *dev) +{ + struct stm32_dac_common *common = dev_get_drvdata(dev); + struct stm32_dac_priv *priv = to_stm32_dac_priv(common); + int ret; + + if (priv->common.hfsel) { + /* restore hfsel (maybe lost under low power state) */ + ret = regmap_set_bits(priv->common.regmap, STM32_DAC_CR, + STM32H7_DAC_CR_HFSEL); + if (ret) + return ret; + } + + return pm_runtime_force_resume(dev); +} + +static int stm32_dac_core_runtime_suspend(struct device *dev) +{ + stm32_dac_core_hw_stop(dev); + + return 0; +} + +static int stm32_dac_core_runtime_resume(struct device *dev) +{ + return stm32_dac_core_hw_start(dev); +} + +static const struct dev_pm_ops stm32_dac_core_pm_ops = { + SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, stm32_dac_core_resume) + RUNTIME_PM_OPS(stm32_dac_core_runtime_suspend, + stm32_dac_core_runtime_resume, + NULL) +}; + +static const struct stm32_dac_cfg stm32h7_dac_cfg = { + .has_hfsel = true, +}; + +static const struct of_device_id stm32_dac_of_match[] = { + { + .compatible = "st,stm32f4-dac-core", + }, { + .compatible = "st,stm32h7-dac-core", + .data = (void *)&stm32h7_dac_cfg, + }, + { } +}; +MODULE_DEVICE_TABLE(of, stm32_dac_of_match); + +static struct platform_driver stm32_dac_driver = { + .probe = stm32_dac_probe, + .remove = stm32_dac_remove, + .driver = { + .name = "stm32-dac-core", + .of_match_table = stm32_dac_of_match, + .pm = pm_ptr(&stm32_dac_core_pm_ops), + }, +}; +module_platform_driver(stm32_dac_driver); + +MODULE_AUTHOR("Fabrice Gasnier "); +MODULE_DESCRIPTION("STMicroelectronics STM32 DAC core driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:stm32-dac-core"); diff --git a/drivers/iio/dac/stm32-dac-core.h b/drivers/iio/dac/stm32-dac-core.h new file mode 100644 index 000000000..d3b415fb9 --- /dev/null +++ b/drivers/iio/dac/stm32-dac-core.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * This file is part of STM32 DAC driver + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Author: Fabrice Gasnier . + */ + +#ifndef __STM32_DAC_CORE_H +#define __STM32_DAC_CORE_H + +#include + +/* STM32 DAC registers */ +#define STM32_DAC_CR 0x00 +#define STM32_DAC_DHR12R1 0x08 +#define STM32_DAC_DHR12R2 0x14 +#define STM32_DAC_DOR1 0x2C +#define STM32_DAC_DOR2 0x30 + +/* STM32_DAC_CR bit fields */ +#define STM32_DAC_CR_EN1 BIT(0) +#define STM32H7_DAC_CR_HFSEL BIT(15) +#define STM32_DAC_CR_EN2 BIT(16) + +/** + * struct stm32_dac_common - stm32 DAC driver common data (for all instances) + * @regmap: DAC registers shared via regmap + * @vref_mv: reference voltage (mv) + * @hfsel: high speed bus clock selected + */ +struct stm32_dac_common { + struct regmap *regmap; + int vref_mv; + bool hfsel; +}; + +#endif diff --git a/drivers/iio/dac/stm32-dac.c b/drivers/iio/dac/stm32-dac.c new file mode 100644 index 000000000..99438f6d4 --- /dev/null +++ b/drivers/iio/dac/stm32-dac.c @@ -0,0 +1,403 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This file is part of STM32 DAC driver + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Authors: Amelie Delaunay + * Fabrice Gasnier + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "stm32-dac-core.h" + +#define STM32_DAC_CHANNEL_1 1 +#define STM32_DAC_CHANNEL_2 2 +#define STM32_DAC_IS_CHAN_1(ch) ((ch) & STM32_DAC_CHANNEL_1) + +#define STM32_DAC_AUTO_SUSPEND_DELAY_MS 2000 + +/** + * struct stm32_dac - private data of DAC driver + * @common: reference to DAC common data + * @lock: lock to protect against potential races when reading + * and update CR, to keep it in sync with pm_runtime + */ +struct stm32_dac { + struct stm32_dac_common *common; + struct mutex lock; +}; + +static int stm32_dac_is_enabled(struct iio_dev *indio_dev, int channel) +{ + struct stm32_dac *dac = iio_priv(indio_dev); + u32 en, val; + int ret; + + ret = regmap_read(dac->common->regmap, STM32_DAC_CR, &val); + if (ret < 0) + return ret; + if (STM32_DAC_IS_CHAN_1(channel)) + en = FIELD_GET(STM32_DAC_CR_EN1, val); + else + en = FIELD_GET(STM32_DAC_CR_EN2, val); + + return !!en; +} + +static int stm32_dac_set_enable_state(struct iio_dev *indio_dev, int ch, + bool enable) +{ + struct stm32_dac *dac = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + u32 msk = STM32_DAC_IS_CHAN_1(ch) ? STM32_DAC_CR_EN1 : STM32_DAC_CR_EN2; + u32 en = enable ? msk : 0; + int ret; + + /* already enabled / disabled ? */ + mutex_lock(&dac->lock); + ret = stm32_dac_is_enabled(indio_dev, ch); + if (ret < 0 || enable == !!ret) { + mutex_unlock(&dac->lock); + return ret < 0 ? ret : 0; + } + + if (enable) { + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + mutex_unlock(&dac->lock); + return ret; + } + } + + ret = regmap_update_bits(dac->common->regmap, STM32_DAC_CR, msk, en); + mutex_unlock(&dac->lock); + if (ret) { + dev_err(&indio_dev->dev, "%s failed\n", str_enable_disable(en)); + if (enable) + pm_runtime_put_autosuspend(dev); + return ret; + } + + /* + * When HFSEL is set, it is not allowed to write the DHRx register + * during 8 clock cycles after the ENx bit is set. It is not allowed + * to make software/hardware trigger during this period either. + */ + if (en && dac->common->hfsel) + udelay(1); + + if (!enable) + pm_runtime_put_autosuspend(dev); + + return 0; +} + +static int stm32_dac_get_value(struct stm32_dac *dac, int channel, int *val) +{ + int ret; + + if (STM32_DAC_IS_CHAN_1(channel)) + ret = regmap_read(dac->common->regmap, STM32_DAC_DOR1, val); + else + ret = regmap_read(dac->common->regmap, STM32_DAC_DOR2, val); + + return ret ? ret : IIO_VAL_INT; +} + +static int stm32_dac_set_value(struct stm32_dac *dac, int channel, int val) +{ + int ret; + + if (STM32_DAC_IS_CHAN_1(channel)) + ret = regmap_write(dac->common->regmap, STM32_DAC_DHR12R1, val); + else + ret = regmap_write(dac->common->regmap, STM32_DAC_DHR12R2, val); + + return ret; +} + +static int stm32_dac_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct stm32_dac *dac = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return stm32_dac_get_value(dac, chan->channel, val); + case IIO_CHAN_INFO_SCALE: + *val = dac->common->vref_mv; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static int stm32_dac_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct stm32_dac *dac = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return stm32_dac_set_value(dac, chan->channel, val); + default: + return -EINVAL; + } +} + +static int stm32_dac_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned reg, unsigned writeval, + unsigned *readval) +{ + struct stm32_dac *dac = iio_priv(indio_dev); + + if (!readval) + return regmap_write(dac->common->regmap, reg, writeval); + else + return regmap_read(dac->common->regmap, reg, readval); +} + +static const struct iio_info stm32_dac_iio_info = { + .read_raw = stm32_dac_read_raw, + .write_raw = stm32_dac_write_raw, + .debugfs_reg_access = stm32_dac_debugfs_reg_access, +}; + +static const char * const stm32_dac_powerdown_modes[] = { + "three_state", +}; + +static int stm32_dac_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + return 0; +} + +static int stm32_dac_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int type) +{ + return 0; +} + +static ssize_t stm32_dac_read_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + int ret = stm32_dac_is_enabled(indio_dev, chan->channel); + + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%d\n", ret ? 0 : 1); +} + +static ssize_t stm32_dac_write_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + bool powerdown; + int ret; + + ret = kstrtobool(buf, &powerdown); + if (ret) + return ret; + + ret = stm32_dac_set_enable_state(indio_dev, chan->channel, !powerdown); + if (ret) + return ret; + + return len; +} + +static const struct iio_enum stm32_dac_powerdown_mode_en = { + .items = stm32_dac_powerdown_modes, + .num_items = ARRAY_SIZE(stm32_dac_powerdown_modes), + .get = stm32_dac_get_powerdown_mode, + .set = stm32_dac_set_powerdown_mode, +}; + +static const struct iio_chan_spec_ext_info stm32_dac_ext_info[] = { + { + .name = "powerdown", + .read = stm32_dac_read_powerdown, + .write = stm32_dac_write_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SEPARATE, &stm32_dac_powerdown_mode_en), + IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, &stm32_dac_powerdown_mode_en), + { } +}; + +#define STM32_DAC_CHANNEL(chan, name) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = chan, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + /* scan_index is always 0 as num_channels is 1 */ \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 12, \ + .storagebits = 16, \ + }, \ + .datasheet_name = name, \ + .ext_info = stm32_dac_ext_info \ +} + +static const struct iio_chan_spec stm32_dac_channels[] = { + STM32_DAC_CHANNEL(STM32_DAC_CHANNEL_1, "out1"), + STM32_DAC_CHANNEL(STM32_DAC_CHANNEL_2, "out2"), +}; + +static int stm32_dac_chan_of_init(struct iio_dev *indio_dev) +{ + struct device_node *np = indio_dev->dev.of_node; + unsigned int i; + u32 channel; + int ret; + + ret = of_property_read_u32(np, "reg", &channel); + if (ret) { + dev_err(&indio_dev->dev, "Failed to read reg property\n"); + return ret; + } + + for (i = 0; i < ARRAY_SIZE(stm32_dac_channels); i++) { + if (stm32_dac_channels[i].channel == channel) + break; + } + if (i >= ARRAY_SIZE(stm32_dac_channels)) { + dev_err(&indio_dev->dev, "Invalid reg property\n"); + return -EINVAL; + } + + indio_dev->channels = &stm32_dac_channels[i]; + /* + * Expose only one channel here, as they can be used independently, + * with separate trigger. Then separate IIO devices are instantiated + * to manage this. + */ + indio_dev->num_channels = 1; + + return 0; +}; + +static int stm32_dac_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct stm32_dac *dac; + int ret; + + if (!np) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*dac)); + if (!indio_dev) + return -ENOMEM; + platform_set_drvdata(pdev, indio_dev); + + dac = iio_priv(indio_dev); + dac->common = dev_get_drvdata(pdev->dev.parent); + indio_dev->name = dev_name(&pdev->dev); + indio_dev->dev.of_node = pdev->dev.of_node; + indio_dev->info = &stm32_dac_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + mutex_init(&dac->lock); + + ret = stm32_dac_chan_of_init(indio_dev); + if (ret < 0) + return ret; + + /* Get stm32-dac-core PM online */ + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); + pm_runtime_set_autosuspend_delay(dev, STM32_DAC_AUTO_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + + ret = iio_device_register(indio_dev); + if (ret) + goto err_pm_put; + + pm_runtime_put_autosuspend(dev); + + return 0; + +err_pm_put: + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_put_noidle(dev); + + return ret; +} + +static void stm32_dac_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + + pm_runtime_get_sync(&pdev->dev); + iio_device_unregister(indio_dev); + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); +} + +static int stm32_dac_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + int channel = indio_dev->channels[0].channel; + int ret; + + /* Ensure DAC is disabled before suspend */ + ret = stm32_dac_is_enabled(indio_dev, channel); + if (ret) + return ret < 0 ? ret : -EBUSY; + + return pm_runtime_force_suspend(dev); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(stm32_dac_pm_ops, stm32_dac_suspend, + pm_runtime_force_resume); + +static const struct of_device_id stm32_dac_of_match[] = { + { .compatible = "st,stm32-dac", }, + { } +}; +MODULE_DEVICE_TABLE(of, stm32_dac_of_match); + +static struct platform_driver stm32_dac_driver = { + .probe = stm32_dac_probe, + .remove = stm32_dac_remove, + .driver = { + .name = "stm32-dac", + .of_match_table = stm32_dac_of_match, + .pm = pm_sleep_ptr(&stm32_dac_pm_ops), + }, +}; +module_platform_driver(stm32_dac_driver); + +MODULE_ALIAS("platform:stm32-dac"); +MODULE_AUTHOR("Amelie Delaunay "); +MODULE_DESCRIPTION("STMicroelectronics STM32 DAC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ti-dac082s085.c b/drivers/iio/dac/ti-dac082s085.c new file mode 100644 index 000000000..afa7d9bad --- /dev/null +++ b/drivers/iio/dac/ti-dac082s085.c @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ti-dac082s085.c - Texas Instruments 8/10/12-bit 2/4-channel DAC driver + * + * Copyright (C) 2017 KUNBUS GmbH + * + * https://www.ti.com/lit/ds/symlink/dac082s085.pdf + * https://www.ti.com/lit/ds/symlink/dac102s085.pdf + * https://www.ti.com/lit/ds/symlink/dac122s085.pdf + * https://www.ti.com/lit/ds/symlink/dac084s085.pdf + * https://www.ti.com/lit/ds/symlink/dac104s085.pdf + * https://www.ti.com/lit/ds/symlink/dac124s085.pdf + */ + +#include +#include +#include +#include + +enum { dual_8bit, dual_10bit, dual_12bit, quad_8bit, quad_10bit, quad_12bit }; + +struct ti_dac_spec { + u8 num_channels; + u8 resolution; +}; + +static const struct ti_dac_spec ti_dac_spec[] = { + [dual_8bit] = { .num_channels = 2, .resolution = 8 }, + [dual_10bit] = { .num_channels = 2, .resolution = 10 }, + [dual_12bit] = { .num_channels = 2, .resolution = 12 }, + [quad_8bit] = { .num_channels = 4, .resolution = 8 }, + [quad_10bit] = { .num_channels = 4, .resolution = 10 }, + [quad_12bit] = { .num_channels = 4, .resolution = 12 }, +}; + +/** + * struct ti_dac_chip - TI DAC chip + * @lock: protects write sequences + * @vref: regulator generating Vref + * @mesg: SPI message to perform a write + * @xfer: SPI transfer used by @mesg + * @val: cached value of each output + * @powerdown: whether the chip is powered down + * @powerdown_mode: selected by the user + * @resolution: resolution of the chip + * @buf: buffer for @xfer + */ +struct ti_dac_chip { + struct mutex lock; + struct regulator *vref; + struct spi_message mesg; + struct spi_transfer xfer; + u16 val[4]; + bool powerdown; + u8 powerdown_mode; + u8 resolution; + u8 buf[2] __aligned(IIO_DMA_MINALIGN); +}; + +#define WRITE_NOT_UPDATE(chan) (0x00 | (chan) << 6) +#define WRITE_AND_UPDATE(chan) (0x10 | (chan) << 6) +#define WRITE_ALL_UPDATE 0x20 +#define POWERDOWN(mode) (0x30 | ((mode) + 1) << 6) + +static int ti_dac_cmd(struct ti_dac_chip *ti_dac, u8 cmd, u16 val) +{ + u8 shift = 12 - ti_dac->resolution; + + ti_dac->buf[0] = cmd | (val >> (8 - shift)); + ti_dac->buf[1] = (val << shift) & 0xff; + return spi_sync(ti_dac->mesg.spi, &ti_dac->mesg); +} + +static const char * const ti_dac_powerdown_modes[] = { + "2.5kohm_to_gnd", "100kohm_to_gnd", "three_state", +}; + +static int ti_dac_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ti_dac_chip *ti_dac = iio_priv(indio_dev); + + return ti_dac->powerdown_mode; +} + +static int ti_dac_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct ti_dac_chip *ti_dac = iio_priv(indio_dev); + int ret = 0; + + if (ti_dac->powerdown_mode == mode) + return 0; + + mutex_lock(&ti_dac->lock); + if (ti_dac->powerdown) { + ret = ti_dac_cmd(ti_dac, POWERDOWN(mode), 0); + if (ret) + goto out; + } + ti_dac->powerdown_mode = mode; + +out: + mutex_unlock(&ti_dac->lock); + return ret; +} + +static const struct iio_enum ti_dac_powerdown_mode = { + .items = ti_dac_powerdown_modes, + .num_items = ARRAY_SIZE(ti_dac_powerdown_modes), + .get = ti_dac_get_powerdown_mode, + .set = ti_dac_set_powerdown_mode, +}; + +static ssize_t ti_dac_read_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ti_dac_chip *ti_dac = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", ti_dac->powerdown); +} + +static ssize_t ti_dac_write_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ti_dac_chip *ti_dac = iio_priv(indio_dev); + bool powerdown; + int ret; + + ret = kstrtobool(buf, &powerdown); + if (ret) + return ret; + + if (ti_dac->powerdown == powerdown) + return len; + + mutex_lock(&ti_dac->lock); + if (powerdown) + ret = ti_dac_cmd(ti_dac, POWERDOWN(ti_dac->powerdown_mode), 0); + else + ret = ti_dac_cmd(ti_dac, WRITE_AND_UPDATE(0), ti_dac->val[0]); + if (!ret) + ti_dac->powerdown = powerdown; + mutex_unlock(&ti_dac->lock); + + return ret ? ret : len; +} + +static const struct iio_chan_spec_ext_info ti_dac_ext_info[] = { + { + .name = "powerdown", + .read = ti_dac_read_powerdown, + .write = ti_dac_write_powerdown, + .shared = IIO_SHARED_BY_TYPE, + }, + IIO_ENUM("powerdown_mode", IIO_SHARED_BY_TYPE, &ti_dac_powerdown_mode), + IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, &ti_dac_powerdown_mode), + { } +}; + +#define TI_DAC_CHANNEL(chan) { \ + .type = IIO_VOLTAGE, \ + .channel = (chan), \ + .address = (chan), \ + .indexed = true, \ + .output = true, \ + .datasheet_name = (const char[]){ 'A' + (chan), 0 }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info = ti_dac_ext_info, \ +} + +static const struct iio_chan_spec ti_dac_channels[] = { + TI_DAC_CHANNEL(0), + TI_DAC_CHANNEL(1), + TI_DAC_CHANNEL(2), + TI_DAC_CHANNEL(3), +}; + +static int ti_dac_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ti_dac_chip *ti_dac = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val = ti_dac->val[chan->channel]; + ret = IIO_VAL_INT; + break; + + case IIO_CHAN_INFO_SCALE: + ret = regulator_get_voltage(ti_dac->vref); + if (ret < 0) + return ret; + + *val = ret / 1000; + *val2 = ti_dac->resolution; + ret = IIO_VAL_FRACTIONAL_LOG2; + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static int ti_dac_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ti_dac_chip *ti_dac = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (ti_dac->val[chan->channel] == val) + return 0; + + if (val >= (1 << ti_dac->resolution) || val < 0) + return -EINVAL; + + if (ti_dac->powerdown) + return -EBUSY; + + mutex_lock(&ti_dac->lock); + ret = ti_dac_cmd(ti_dac, WRITE_AND_UPDATE(chan->channel), val); + if (!ret) + ti_dac->val[chan->channel] = val; + mutex_unlock(&ti_dac->lock); + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static int ti_dac_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long mask) +{ + return IIO_VAL_INT; +} + +static const struct iio_info ti_dac_info = { + .read_raw = ti_dac_read_raw, + .write_raw = ti_dac_write_raw, + .write_raw_get_fmt = ti_dac_write_raw_get_fmt, +}; + +static int ti_dac_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + const struct ti_dac_spec *spec; + struct ti_dac_chip *ti_dac; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*ti_dac)); + if (!indio_dev) + return -ENOMEM; + + indio_dev->info = &ti_dac_info; + indio_dev->name = spi->modalias; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ti_dac_channels; + spi_set_drvdata(spi, indio_dev); + + ti_dac = iio_priv(indio_dev); + ti_dac->xfer.tx_buf = &ti_dac->buf; + ti_dac->xfer.len = sizeof(ti_dac->buf); + spi_message_init_with_transfers(&ti_dac->mesg, &ti_dac->xfer, 1); + ti_dac->mesg.spi = spi; + + spec = &ti_dac_spec[spi_get_device_id(spi)->driver_data]; + indio_dev->num_channels = spec->num_channels; + ti_dac->resolution = spec->resolution; + + ti_dac->vref = devm_regulator_get(dev, "vref"); + if (IS_ERR(ti_dac->vref)) + return PTR_ERR(ti_dac->vref); + + ret = regulator_enable(ti_dac->vref); + if (ret < 0) + return ret; + + mutex_init(&ti_dac->lock); + + ret = ti_dac_cmd(ti_dac, WRITE_ALL_UPDATE, 0); + if (ret) { + dev_err(dev, "failed to initialize outputs to 0\n"); + goto err; + } + + ret = iio_device_register(indio_dev); + if (ret) + goto err; + + return 0; + +err: + mutex_destroy(&ti_dac->lock); + regulator_disable(ti_dac->vref); + return ret; +} + +static void ti_dac_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ti_dac_chip *ti_dac = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + mutex_destroy(&ti_dac->lock); + regulator_disable(ti_dac->vref); +} + +static const struct of_device_id ti_dac_of_id[] = { + { .compatible = "ti,dac082s085" }, + { .compatible = "ti,dac102s085" }, + { .compatible = "ti,dac122s085" }, + { .compatible = "ti,dac084s085" }, + { .compatible = "ti,dac104s085" }, + { .compatible = "ti,dac124s085" }, + { } +}; +MODULE_DEVICE_TABLE(of, ti_dac_of_id); + +static const struct spi_device_id ti_dac_spi_id[] = { + { .name = "dac082s085", .driver_data = dual_8bit }, + { .name = "dac102s085", .driver_data = dual_10bit }, + { .name = "dac122s085", .driver_data = dual_12bit }, + { .name = "dac084s085", .driver_data = quad_8bit }, + { .name = "dac104s085", .driver_data = quad_10bit }, + { .name = "dac124s085", .driver_data = quad_12bit }, + { } +}; +MODULE_DEVICE_TABLE(spi, ti_dac_spi_id); + +static struct spi_driver ti_dac_driver = { + .driver = { + .name = "ti-dac082s085", + .of_match_table = ti_dac_of_id, + }, + .probe = ti_dac_probe, + .remove = ti_dac_remove, + .id_table = ti_dac_spi_id, +}; +module_spi_driver(ti_dac_driver); + +MODULE_AUTHOR("Lukas Wunner "); +MODULE_DESCRIPTION("Texas Instruments 8/10/12-bit 2/4-channel DAC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ti-dac5571.c b/drivers/iio/dac/ti-dac5571.c new file mode 100644 index 000000000..78fd5fa42 --- /dev/null +++ b/drivers/iio/dac/ti-dac5571.c @@ -0,0 +1,432 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ti-dac5571.c - Texas Instruments 8/10/12-bit 1/4-channel DAC driver + * + * Copyright (C) 2018 Prevas A/S + * + * https://www.ti.com/lit/ds/symlink/dac5571.pdf + * https://www.ti.com/lit/ds/symlink/dac6571.pdf + * https://www.ti.com/lit/ds/symlink/dac7571.pdf + * https://www.ti.com/lit/ds/symlink/dac5574.pdf + * https://www.ti.com/lit/ds/symlink/dac6574.pdf + * https://www.ti.com/lit/ds/symlink/dac7574.pdf + * https://www.ti.com/lit/ds/symlink/dac5573.pdf + * https://www.ti.com/lit/ds/symlink/dac6573.pdf + * https://www.ti.com/lit/ds/symlink/dac7573.pdf + * https://www.ti.com/lit/ds/symlink/dac081c081.pdf + * https://www.ti.com/lit/ds/symlink/dac121c081.pdf + */ + +#include +#include +#include +#include +#include + +enum chip_id { + single_8bit, single_10bit, single_12bit, + quad_8bit, quad_10bit, quad_12bit +}; + +struct dac5571_spec { + u8 num_channels; + u8 resolution; +}; + +static const struct dac5571_spec dac5571_spec[] = { + [single_8bit] = {.num_channels = 1, .resolution = 8}, + [single_10bit] = {.num_channels = 1, .resolution = 10}, + [single_12bit] = {.num_channels = 1, .resolution = 12}, + [quad_8bit] = {.num_channels = 4, .resolution = 8}, + [quad_10bit] = {.num_channels = 4, .resolution = 10}, + [quad_12bit] = {.num_channels = 4, .resolution = 12}, +}; + +struct dac5571_data { + struct i2c_client *client; + struct mutex lock; + struct regulator *vref; + u16 val[4]; + bool powerdown[4]; + u8 powerdown_mode[4]; + struct dac5571_spec const *spec; + int (*dac5571_cmd)(struct dac5571_data *data, int channel, u16 val); + int (*dac5571_pwrdwn)(struct dac5571_data *data, int channel, u8 pwrdwn); + u8 buf[3] __aligned(IIO_DMA_MINALIGN); +}; + +#define DAC5571_POWERDOWN(mode) ((mode) + 1) +#define DAC5571_POWERDOWN_FLAG BIT(0) +#define DAC5571_CHANNEL_SELECT 1 +#define DAC5571_LOADMODE_DIRECT BIT(4) +#define DAC5571_SINGLE_PWRDWN_BITS 4 +#define DAC5571_QUAD_PWRDWN_BITS 6 + +static int dac5571_cmd_single(struct dac5571_data *data, int channel, u16 val) +{ + unsigned int shift; + + shift = 12 - data->spec->resolution; + data->buf[1] = val << shift; + data->buf[0] = val >> (8 - shift); + + if (i2c_master_send(data->client, data->buf, 2) != 2) + return -EIO; + + return 0; +} + +static int dac5571_cmd_quad(struct dac5571_data *data, int channel, u16 val) +{ + unsigned int shift; + + shift = 16 - data->spec->resolution; + data->buf[2] = val << shift; + data->buf[1] = (val >> (8 - shift)); + data->buf[0] = (channel << DAC5571_CHANNEL_SELECT) | + DAC5571_LOADMODE_DIRECT; + + if (i2c_master_send(data->client, data->buf, 3) != 3) + return -EIO; + + return 0; +} + +static int dac5571_pwrdwn_single(struct dac5571_data *data, int channel, u8 pwrdwn) +{ + data->buf[1] = 0; + data->buf[0] = pwrdwn << DAC5571_SINGLE_PWRDWN_BITS; + + if (i2c_master_send(data->client, data->buf, 2) != 2) + return -EIO; + + return 0; +} + +static int dac5571_pwrdwn_quad(struct dac5571_data *data, int channel, u8 pwrdwn) +{ + data->buf[2] = 0; + data->buf[1] = pwrdwn << DAC5571_QUAD_PWRDWN_BITS; + data->buf[0] = (channel << DAC5571_CHANNEL_SELECT) | + DAC5571_LOADMODE_DIRECT | DAC5571_POWERDOWN_FLAG; + + if (i2c_master_send(data->client, data->buf, 3) != 3) + return -EIO; + + return 0; +} + +static const char *const dac5571_powerdown_modes[] = { + "1kohm_to_gnd", "100kohm_to_gnd", "three_state", +}; + +static int dac5571_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct dac5571_data *data = iio_priv(indio_dev); + + return data->powerdown_mode[chan->channel]; +} + +static int dac5571_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct dac5571_data *data = iio_priv(indio_dev); + int ret = 0; + + if (data->powerdown_mode[chan->channel] == mode) + return 0; + + mutex_lock(&data->lock); + if (data->powerdown[chan->channel]) { + ret = data->dac5571_pwrdwn(data, chan->channel, + DAC5571_POWERDOWN(mode)); + if (ret) + goto out; + } + data->powerdown_mode[chan->channel] = mode; + + out: + mutex_unlock(&data->lock); + + return ret; +} + +static const struct iio_enum dac5571_powerdown_mode = { + .items = dac5571_powerdown_modes, + .num_items = ARRAY_SIZE(dac5571_powerdown_modes), + .get = dac5571_get_powerdown_mode, + .set = dac5571_set_powerdown_mode, +}; + +static ssize_t dac5571_read_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct dac5571_data *data = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", data->powerdown[chan->channel]); +} + +static ssize_t dac5571_write_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct dac5571_data *data = iio_priv(indio_dev); + bool powerdown; + int ret; + + ret = kstrtobool(buf, &powerdown); + if (ret) + return ret; + + if (data->powerdown[chan->channel] == powerdown) + return len; + + mutex_lock(&data->lock); + if (powerdown) + ret = data->dac5571_pwrdwn(data, chan->channel, + DAC5571_POWERDOWN(data->powerdown_mode[chan->channel])); + else + ret = data->dac5571_cmd(data, chan->channel, + data->val[chan->channel]); + if (ret) + goto out; + + data->powerdown[chan->channel] = powerdown; + + out: + mutex_unlock(&data->lock); + + return ret ? ret : len; +} + + +static const struct iio_chan_spec_ext_info dac5571_ext_info[] = { + { + .name = "powerdown", + .read = dac5571_read_powerdown, + .write = dac5571_write_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SEPARATE, &dac5571_powerdown_mode), + IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, &dac5571_powerdown_mode), + { } +}; + +#define dac5571_CHANNEL(chan, name) { \ + .type = IIO_VOLTAGE, \ + .channel = (chan), \ + .address = (chan), \ + .indexed = true, \ + .output = true, \ + .datasheet_name = name, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info = dac5571_ext_info, \ +} + +static const struct iio_chan_spec dac5571_channels[] = { + dac5571_CHANNEL(0, "A"), + dac5571_CHANNEL(1, "B"), + dac5571_CHANNEL(2, "C"), + dac5571_CHANNEL(3, "D"), +}; + +static int dac5571_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct dac5571_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val = data->val[chan->channel]; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + ret = regulator_get_voltage(data->vref); + if (ret < 0) + return ret; + + *val = ret / 1000; + *val2 = data->spec->resolution; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } +} + +static int dac5571_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct dac5571_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (data->val[chan->channel] == val) + return 0; + + if (val >= (1 << data->spec->resolution) || val < 0) + return -EINVAL; + + if (data->powerdown[chan->channel]) + return -EBUSY; + + mutex_lock(&data->lock); + ret = data->dac5571_cmd(data, chan->channel, val); + if (ret == 0) + data->val[chan->channel] = val; + mutex_unlock(&data->lock); + return ret; + + default: + return -EINVAL; + } +} + +static int dac5571_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + return IIO_VAL_INT; +} + +static const struct iio_info dac5571_info = { + .read_raw = dac5571_read_raw, + .write_raw = dac5571_write_raw, + .write_raw_get_fmt = dac5571_write_raw_get_fmt, +}; + +static int dac5571_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct device *dev = &client->dev; + const struct dac5571_spec *spec; + struct dac5571_data *data; + struct iio_dev *indio_dev; + int ret, i; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + indio_dev->info = &dac5571_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = dac5571_channels; + + spec = i2c_get_match_data(client); + + indio_dev->num_channels = spec->num_channels; + data->spec = spec; + + data->vref = devm_regulator_get(dev, "vref"); + if (IS_ERR(data->vref)) + return PTR_ERR(data->vref); + + ret = regulator_enable(data->vref); + if (ret < 0) + return ret; + + mutex_init(&data->lock); + + switch (spec->num_channels) { + case 1: + data->dac5571_cmd = dac5571_cmd_single; + data->dac5571_pwrdwn = dac5571_pwrdwn_single; + break; + case 4: + data->dac5571_cmd = dac5571_cmd_quad; + data->dac5571_pwrdwn = dac5571_pwrdwn_quad; + break; + default: + ret = -EINVAL; + goto err; + } + + for (i = 0; i < spec->num_channels; i++) { + ret = data->dac5571_cmd(data, i, 0); + if (ret) { + dev_err(dev, "failed to initialize channel %d to 0\n", i); + goto err; + } + } + + ret = iio_device_register(indio_dev); + if (ret) + goto err; + + return 0; + + err: + regulator_disable(data->vref); + return ret; +} + +static void dac5571_remove(struct i2c_client *i2c) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(i2c); + struct dac5571_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + regulator_disable(data->vref); +} + +static const struct of_device_id dac5571_of_id[] = { + {.compatible = "ti,dac081c081", .data = &dac5571_spec[single_8bit] }, + {.compatible = "ti,dac121c081", .data = &dac5571_spec[single_12bit] }, + {.compatible = "ti,dac5571", .data = &dac5571_spec[single_8bit] }, + {.compatible = "ti,dac6571", .data = &dac5571_spec[single_10bit] }, + {.compatible = "ti,dac7571", .data = &dac5571_spec[single_12bit] }, + {.compatible = "ti,dac5574", .data = &dac5571_spec[quad_8bit] }, + {.compatible = "ti,dac6574", .data = &dac5571_spec[quad_10bit] }, + {.compatible = "ti,dac7574", .data = &dac5571_spec[quad_12bit] }, + {.compatible = "ti,dac5573", .data = &dac5571_spec[quad_8bit] }, + {.compatible = "ti,dac6573", .data = &dac5571_spec[quad_10bit] }, + {.compatible = "ti,dac7573", .data = &dac5571_spec[quad_12bit] }, + { } +}; +MODULE_DEVICE_TABLE(of, dac5571_of_id); + +static const struct i2c_device_id dac5571_id[] = { + { .name = "dac081c081", .driver_data = (kernel_ulong_t)&dac5571_spec[single_8bit] }, + { .name = "dac121c081", .driver_data = (kernel_ulong_t)&dac5571_spec[single_12bit] }, + { .name = "dac5571", .driver_data = (kernel_ulong_t)&dac5571_spec[single_8bit] }, + { .name = "dac6571", .driver_data = (kernel_ulong_t)&dac5571_spec[single_10bit] }, + { .name = "dac7571", .driver_data = (kernel_ulong_t)&dac5571_spec[single_12bit] }, + { .name = "dac5574", .driver_data = (kernel_ulong_t)&dac5571_spec[quad_8bit] }, + { .name = "dac6574", .driver_data = (kernel_ulong_t)&dac5571_spec[quad_10bit] }, + { .name = "dac7574", .driver_data = (kernel_ulong_t)&dac5571_spec[quad_12bit] }, + { .name = "dac5573", .driver_data = (kernel_ulong_t)&dac5571_spec[quad_8bit] }, + { .name = "dac6573", .driver_data = (kernel_ulong_t)&dac5571_spec[quad_10bit] }, + { .name = "dac7573", .driver_data = (kernel_ulong_t)&dac5571_spec[quad_12bit] }, + { } +}; +MODULE_DEVICE_TABLE(i2c, dac5571_id); + +static struct i2c_driver dac5571_driver = { + .driver = { + .name = "ti-dac5571", + .of_match_table = dac5571_of_id, + }, + .probe = dac5571_probe, + .remove = dac5571_remove, + .id_table = dac5571_id, +}; +module_i2c_driver(dac5571_driver); + +MODULE_AUTHOR("Sean Nyekjaer "); +MODULE_DESCRIPTION("Texas Instruments 8/10/12-bit 1/4-channel DAC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ti-dac7311.c b/drivers/iio/dac/ti-dac7311.c new file mode 100644 index 000000000..e5843bd35 --- /dev/null +++ b/drivers/iio/dac/ti-dac7311.c @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: GPL-2.0 +/* ti-dac7311.c - Texas Instruments 8/10/12-bit 1-channel DAC driver + * + * Copyright (C) 2018 CMC NV + * + * https://www.ti.com/lit/ds/symlink/dac7311.pdf + */ + +#include +#include +#include +#include + +enum { + ID_DAC5311 = 0, + ID_DAC6311, + ID_DAC7311, +}; + +enum { + POWER_1KOHM_TO_GND = 0, + POWER_100KOHM_TO_GND, + POWER_TRI_STATE, +}; + +struct ti_dac_spec { + u8 resolution; +}; + +static const struct ti_dac_spec ti_dac_spec[] = { + [ID_DAC5311] = { .resolution = 8 }, + [ID_DAC6311] = { .resolution = 10 }, + [ID_DAC7311] = { .resolution = 12 }, +}; + +/** + * struct ti_dac_chip - TI DAC chip + * @lock: protects write sequences + * @vref: regulator generating Vref + * @spi: SPI device to send data to the device + * @val: cached value + * @powerdown: whether the chip is powered down + * @powerdown_mode: selected by the user + * @resolution: resolution of the chip + * @buf: buffer for transfer data + */ +struct ti_dac_chip { + struct mutex lock; + struct regulator *vref; + struct spi_device *spi; + u16 val; + bool powerdown; + u8 powerdown_mode; + u8 resolution; + u8 buf[2] __aligned(IIO_DMA_MINALIGN); +}; + +static u8 ti_dac_get_power(struct ti_dac_chip *ti_dac, bool powerdown) +{ + if (powerdown) + return ti_dac->powerdown_mode + 1; + + return 0; +} + +static int ti_dac_cmd(struct ti_dac_chip *ti_dac, u8 power, u16 val) +{ + u8 shift = 14 - ti_dac->resolution; + + ti_dac->buf[0] = (val << shift) & 0xFF; + ti_dac->buf[1] = (power << 6) | (val >> (8 - shift)); + return spi_write(ti_dac->spi, ti_dac->buf, 2); +} + +static const char * const ti_dac_powerdown_modes[] = { + "1kohm_to_gnd", + "100kohm_to_gnd", + "three_state", +}; + +static int ti_dac_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ti_dac_chip *ti_dac = iio_priv(indio_dev); + + return ti_dac->powerdown_mode; +} + +static int ti_dac_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct ti_dac_chip *ti_dac = iio_priv(indio_dev); + + ti_dac->powerdown_mode = mode; + return 0; +} + +static const struct iio_enum ti_dac_powerdown_mode = { + .items = ti_dac_powerdown_modes, + .num_items = ARRAY_SIZE(ti_dac_powerdown_modes), + .get = ti_dac_get_powerdown_mode, + .set = ti_dac_set_powerdown_mode, +}; + +static ssize_t ti_dac_read_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ti_dac_chip *ti_dac = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", ti_dac->powerdown); +} + +static ssize_t ti_dac_write_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ti_dac_chip *ti_dac = iio_priv(indio_dev); + bool powerdown; + u8 power; + int ret; + + ret = kstrtobool(buf, &powerdown); + if (ret) + return ret; + + power = ti_dac_get_power(ti_dac, powerdown); + + mutex_lock(&ti_dac->lock); + ret = ti_dac_cmd(ti_dac, power, 0); + if (!ret) + ti_dac->powerdown = powerdown; + mutex_unlock(&ti_dac->lock); + + return ret ? ret : len; +} + +static const struct iio_chan_spec_ext_info ti_dac_ext_info[] = { + { + .name = "powerdown", + .read = ti_dac_read_powerdown, + .write = ti_dac_write_powerdown, + .shared = IIO_SHARED_BY_TYPE, + }, + IIO_ENUM("powerdown_mode", IIO_SHARED_BY_TYPE, &ti_dac_powerdown_mode), + IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, &ti_dac_powerdown_mode), + { } +}; + +#define TI_DAC_CHANNEL(chan) { \ + .type = IIO_VOLTAGE, \ + .channel = (chan), \ + .output = true, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info = ti_dac_ext_info, \ +} + +static const struct iio_chan_spec ti_dac_channels[] = { + TI_DAC_CHANNEL(0), +}; + +static int ti_dac_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ti_dac_chip *ti_dac = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val = ti_dac->val; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + ret = regulator_get_voltage(ti_dac->vref); + if (ret < 0) + return ret; + + *val = ret / 1000; + *val2 = ti_dac->resolution; + return IIO_VAL_FRACTIONAL_LOG2; + } + + return -EINVAL; +} + +static int ti_dac_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ti_dac_chip *ti_dac = iio_priv(indio_dev); + u8 power = ti_dac_get_power(ti_dac, ti_dac->powerdown); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (ti_dac->val == val) + return 0; + + if (val >= (1 << ti_dac->resolution) || val < 0) + return -EINVAL; + + if (ti_dac->powerdown) + return -EBUSY; + + mutex_lock(&ti_dac->lock); + ret = ti_dac_cmd(ti_dac, power, val); + if (!ret) + ti_dac->val = val; + mutex_unlock(&ti_dac->lock); + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static int ti_dac_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long mask) +{ + return IIO_VAL_INT; +} + +static const struct iio_info ti_dac_info = { + .read_raw = ti_dac_read_raw, + .write_raw = ti_dac_write_raw, + .write_raw_get_fmt = ti_dac_write_raw_get_fmt, +}; + +static int ti_dac_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + const struct ti_dac_spec *spec; + struct ti_dac_chip *ti_dac; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*ti_dac)); + if (!indio_dev) + return -ENOMEM; + + spi->mode = SPI_MODE_1; + spi->bits_per_word = 16; + ret = spi_setup(spi); + if (ret < 0) + return dev_err_probe(dev, ret, "spi_setup failed\n"); + + indio_dev->info = &ti_dac_info; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ti_dac_channels; + spi_set_drvdata(spi, indio_dev); + + ti_dac = iio_priv(indio_dev); + ti_dac->powerdown = false; + ti_dac->spi = spi; + + spec = &ti_dac_spec[spi_get_device_id(spi)->driver_data]; + indio_dev->num_channels = 1; + ti_dac->resolution = spec->resolution; + + ti_dac->vref = devm_regulator_get(dev, "vref"); + if (IS_ERR(ti_dac->vref)) + return dev_err_probe(dev, PTR_ERR(ti_dac->vref), + "error to get regulator\n"); + + ret = regulator_enable(ti_dac->vref); + if (ret < 0) { + dev_err(dev, "can not enable regulator\n"); + return ret; + } + + mutex_init(&ti_dac->lock); + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(dev, "fail to register iio device: %d\n", ret); + goto err; + } + + return 0; + +err: + mutex_destroy(&ti_dac->lock); + regulator_disable(ti_dac->vref); + return ret; +} + +static void ti_dac_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ti_dac_chip *ti_dac = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + mutex_destroy(&ti_dac->lock); + regulator_disable(ti_dac->vref); +} + +static const struct of_device_id ti_dac_of_id[] = { + { .compatible = "ti,dac5311" }, + { .compatible = "ti,dac6311" }, + { .compatible = "ti,dac7311" }, + { } +}; +MODULE_DEVICE_TABLE(of, ti_dac_of_id); + +static const struct spi_device_id ti_dac_spi_id[] = { + { .name = "dac5311", .driver_data = ID_DAC5311 }, + { .name = "dac6311", .driver_data = ID_DAC6311 }, + { .name = "dac7311", .driver_data = ID_DAC7311 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ti_dac_spi_id); + +static struct spi_driver ti_dac_driver = { + .driver = { + .name = "ti-dac7311", + .of_match_table = ti_dac_of_id, + }, + .probe = ti_dac_probe, + .remove = ti_dac_remove, + .id_table = ti_dac_spi_id, +}; +module_spi_driver(ti_dac_driver); + +MODULE_AUTHOR("Charles-Antoine Couret "); +MODULE_DESCRIPTION("Texas Instruments 8/10/12-bit 1-channel DAC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ti-dac7612.c b/drivers/iio/dac/ti-dac7612.c new file mode 100644 index 000000000..9065663cd --- /dev/null +++ b/drivers/iio/dac/ti-dac7612.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * DAC7612 Dual, 12-Bit Serial input Digital-to-Analog Converter + * + * Copyright 2019 Qtechnology A/S + * 2019 Ricardo Ribalda + * + * Licensed under the GPL-2. + */ +#include +#include +#include +#include +#include + +#define DAC7612_RESOLUTION 12 +#define DAC7612_ADDRESS 4 +#define DAC7612_START 5 + +struct dac7612 { + struct spi_device *spi; + struct gpio_desc *loaddacs; + uint16_t cache[2]; + + /* + * Lock to protect the state of the device from potential concurrent + * write accesses from userspace. The write operation requires an + * SPI write, then toggling of a GPIO, so the lock aims to protect + * the sanity of the entire sequence of operation. + */ + struct mutex lock; + + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + */ + uint8_t data[2] __aligned(IIO_DMA_MINALIGN); +}; + +static int dac7612_cmd_single(struct dac7612 *priv, int channel, u16 val) +{ + int ret; + + priv->data[0] = BIT(DAC7612_START) | (channel << DAC7612_ADDRESS); + priv->data[0] |= val >> 8; + priv->data[1] = val & 0xff; + + priv->cache[channel] = val; + + ret = spi_write(priv->spi, priv->data, sizeof(priv->data)); + if (ret) + return ret; + + gpiod_set_value(priv->loaddacs, 1); + gpiod_set_value(priv->loaddacs, 0); + + return 0; +} + +#define dac7612_CHANNEL(chan, name) { \ + .type = IIO_VOLTAGE, \ + .channel = (chan), \ + .indexed = 1, \ + .output = 1, \ + .datasheet_name = name, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec dac7612_channels[] = { + dac7612_CHANNEL(0, "OUTA"), + dac7612_CHANNEL(1, "OUTB"), +}; + +static int dac7612_read_raw(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct dac7612 *priv; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + priv = iio_priv(iio_dev); + *val = priv->cache[chan->channel]; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = 1; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int dac7612_write_raw(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + int val, int val2, long mask) +{ + struct dac7612 *priv = iio_priv(iio_dev); + int ret; + + if (mask != IIO_CHAN_INFO_RAW) + return -EINVAL; + + if ((val >= BIT(DAC7612_RESOLUTION)) || val < 0 || val2) + return -EINVAL; + + if (val == priv->cache[chan->channel]) + return 0; + + mutex_lock(&priv->lock); + ret = dac7612_cmd_single(priv, chan->channel, val); + mutex_unlock(&priv->lock); + + return ret; +} + +static const struct iio_info dac7612_info = { + .read_raw = dac7612_read_raw, + .write_raw = dac7612_write_raw, +}; + +static int dac7612_probe(struct spi_device *spi) +{ + struct iio_dev *iio_dev; + struct dac7612 *priv; + int i; + int ret; + + iio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*priv)); + if (!iio_dev) + return -ENOMEM; + + priv = iio_priv(iio_dev); + /* + * LOADDACS pin can be controlled by the driver or externally. + * When controlled by the driver, the DAC value is updated after + * every write. + * When the driver does not control the PIN, the user or an external + * event can change the value of all DACs by pulsing down the LOADDACs + * pin. + */ + priv->loaddacs = devm_gpiod_get_optional(&spi->dev, "ti,loaddacs", + GPIOD_OUT_LOW); + if (IS_ERR(priv->loaddacs)) + return PTR_ERR(priv->loaddacs); + priv->spi = spi; + spi_set_drvdata(spi, iio_dev); + iio_dev->info = &dac7612_info; + iio_dev->modes = INDIO_DIRECT_MODE; + iio_dev->channels = dac7612_channels; + iio_dev->num_channels = ARRAY_SIZE(priv->cache); + iio_dev->name = spi_get_device_id(spi)->name; + + mutex_init(&priv->lock); + + for (i = 0; i < ARRAY_SIZE(priv->cache); i++) { + ret = dac7612_cmd_single(priv, i, 0); + if (ret) + return ret; + } + + return devm_iio_device_register(&spi->dev, iio_dev); +} + +static const struct spi_device_id dac7612_id[] = { + { .name = "ti-dac7612" }, + { } +}; +MODULE_DEVICE_TABLE(spi, dac7612_id); + +static const struct of_device_id dac7612_of_match[] = { + { .compatible = "ti,dac7612" }, + { .compatible = "ti,dac7612u" }, + { .compatible = "ti,dac7612ub" }, + { } +}; +MODULE_DEVICE_TABLE(of, dac7612_of_match); + +static struct spi_driver dac7612_driver = { + .driver = { + .name = "ti-dac7612", + .of_match_table = dac7612_of_match, + }, + .probe = dac7612_probe, + .id_table = dac7612_id, +}; +module_spi_driver(dac7612_driver); + +MODULE_AUTHOR("Ricardo Ribalda "); +MODULE_DESCRIPTION("Texas Instruments DAC7612 DAC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/vf610_dac.c b/drivers/iio/dac/vf610_dac.c new file mode 100644 index 000000000..3aa22fecd --- /dev/null +++ b/drivers/iio/dac/vf610_dac.c @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Freescale Vybrid vf610 DAC driver + * + * Copyright 2016 Toradex AG + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define VF610_DACx_STATCTRL 0x20 + +#define VF610_DAC_DACEN BIT(15) +#define VF610_DAC_DACRFS BIT(14) +#define VF610_DAC_LPEN BIT(11) + +#define VF610_DAC_DAT0(x) ((x) & 0xFFF) + +enum vf610_conversion_mode_sel { + VF610_DAC_CONV_HIGH_POWER, + VF610_DAC_CONV_LOW_POWER, +}; + +struct vf610_dac { + struct clk *clk; + struct device *dev; + enum vf610_conversion_mode_sel conv_mode; + void __iomem *regs; + struct mutex lock; +}; + +static void vf610_dac_init(struct vf610_dac *info) +{ + int val; + + info->conv_mode = VF610_DAC_CONV_LOW_POWER; + val = VF610_DAC_DACEN | VF610_DAC_DACRFS | + VF610_DAC_LPEN; + writel(val, info->regs + VF610_DACx_STATCTRL); +} + +static void vf610_dac_exit(struct vf610_dac *info) +{ + int val; + + val = readl(info->regs + VF610_DACx_STATCTRL); + val &= ~VF610_DAC_DACEN; + writel(val, info->regs + VF610_DACx_STATCTRL); +} + +static int vf610_set_conversion_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct vf610_dac *info = iio_priv(indio_dev); + int val; + + mutex_lock(&info->lock); + info->conv_mode = mode; + val = readl(info->regs + VF610_DACx_STATCTRL); + if (mode) + val |= VF610_DAC_LPEN; + else + val &= ~VF610_DAC_LPEN; + writel(val, info->regs + VF610_DACx_STATCTRL); + mutex_unlock(&info->lock); + + return 0; +} + +static int vf610_get_conversion_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct vf610_dac *info = iio_priv(indio_dev); + + return info->conv_mode; +} + +static const char * const vf610_conv_modes[] = { "high-power", "low-power" }; + +static const struct iio_enum vf610_conversion_mode = { + .items = vf610_conv_modes, + .num_items = ARRAY_SIZE(vf610_conv_modes), + .get = vf610_get_conversion_mode, + .set = vf610_set_conversion_mode, +}; + +static const struct iio_chan_spec_ext_info vf610_ext_info[] = { + IIO_ENUM("conversion_mode", IIO_SHARED_BY_DIR, + &vf610_conversion_mode), + { } +}; + +#define VF610_DAC_CHAN(_chan_type) { \ + .type = (_chan_type), \ + .output = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info = vf610_ext_info, \ +} + +static const struct iio_chan_spec vf610_dac_iio_channels[] = { + VF610_DAC_CHAN(IIO_VOLTAGE), +}; + +static int vf610_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct vf610_dac *info = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val = VF610_DAC_DAT0(readl(info->regs)); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* + * DACRFS is always 1 for valid reference and typical + * reference voltage as per Vybrid datasheet is 3.3V + * from section 9.1.2.1 of Vybrid datasheet + */ + *val = 3300 /* mV */; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } +} + +static int vf610_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, + long mask) +{ + struct vf610_dac *info = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&info->lock); + writel(VF610_DAC_DAT0(val), info->regs); + mutex_unlock(&info->lock); + return 0; + + default: + return -EINVAL; + } +} + +static const struct iio_info vf610_dac_iio_info = { + .read_raw = &vf610_read_raw, + .write_raw = &vf610_write_raw, +}; + +static const struct of_device_id vf610_dac_match[] = { + { .compatible = "fsl,vf610-dac", }, + { } +}; +MODULE_DEVICE_TABLE(of, vf610_dac_match); + +static int vf610_dac_probe(struct platform_device *pdev) +{ + struct iio_dev *indio_dev; + struct vf610_dac *info; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, + sizeof(struct vf610_dac)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + info->dev = &pdev->dev; + + info->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(info->regs)) + return PTR_ERR(info->regs); + + info->clk = devm_clk_get_enabled(&pdev->dev, "dac"); + if (IS_ERR(info->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(info->clk), + "Failed getting clock\n"); + + platform_set_drvdata(pdev, indio_dev); + + indio_dev->name = dev_name(&pdev->dev); + indio_dev->info = &vf610_dac_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = vf610_dac_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(vf610_dac_iio_channels); + + mutex_init(&info->lock); + + vf610_dac_init(info); + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "Couldn't register the device\n"); + goto error_iio_device_register; + } + + return 0; + +error_iio_device_register: + vf610_dac_exit(info); + + return ret; +} + +static void vf610_dac_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct vf610_dac *info = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + vf610_dac_exit(info); +} + +static int vf610_dac_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct vf610_dac *info = iio_priv(indio_dev); + + vf610_dac_exit(info); + clk_disable_unprepare(info->clk); + + return 0; +} + +static int vf610_dac_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct vf610_dac *info = iio_priv(indio_dev); + int ret; + + ret = clk_prepare_enable(info->clk); + if (ret) + return ret; + + vf610_dac_init(info); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(vf610_dac_pm_ops, vf610_dac_suspend, + vf610_dac_resume); + +static struct platform_driver vf610_dac_driver = { + .probe = vf610_dac_probe, + .remove = vf610_dac_remove, + .driver = { + .name = "vf610-dac", + .of_match_table = vf610_dac_match, + .pm = pm_sleep_ptr(&vf610_dac_pm_ops), + }, +}; +module_platform_driver(vf610_dac_driver); + +MODULE_AUTHOR("Sanchayan Maity "); +MODULE_DESCRIPTION("Freescale VF610 DAC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dummy/Kconfig b/drivers/iio/dummy/Kconfig new file mode 100644 index 000000000..1f46cb9e5 --- /dev/null +++ b/drivers/iio/dummy/Kconfig @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Industrial I/O subsystem Dummy Driver configuration +# +menu "IIO dummy driver" + depends on IIO + +config IIO_DUMMY_EVGEN + select IRQ_SIM + tristate + +config IIO_SIMPLE_DUMMY + tristate "An example driver with no hardware requirements" + depends on IIO_SW_DEVICE + help + Driver intended mainly as documentation for how to write + a driver. May also be useful for testing userspace code + without hardware. + +if IIO_SIMPLE_DUMMY + +config IIO_SIMPLE_DUMMY_EVENTS + bool "Event generation support" + select IIO_DUMMY_EVGEN + help + Add some dummy events to the simple dummy driver. + + The purpose of this is to generate 'fake' event interrupts thus + allowing that driver's code to be as close as possible to that + a normal driver talking to hardware. + +config IIO_SIMPLE_DUMMY_BUFFER + bool "Buffered capture support" + select IIO_BUFFER + select IIO_TRIGGER + select IIO_KFIFO_BUF + select IIO_TRIGGERED_BUFFER + help + Add buffered data capture to the simple dummy driver. + + Buffer handling elements of industrial I/O reference driver. + Uses the kfifo buffer. + +endif # IIO_SIMPLE_DUMMY + +endmenu diff --git a/drivers/iio/dummy/Makefile b/drivers/iio/dummy/Makefile new file mode 100644 index 000000000..f14fe20f3 --- /dev/null +++ b/drivers/iio/dummy/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the IIO Dummy Driver +# + +obj-$(CONFIG_IIO_SIMPLE_DUMMY) += iio_dummy.o +iio_dummy-y := iio_simple_dummy.o +iio_dummy-$(CONFIG_IIO_SIMPLE_DUMMY_EVENTS) += iio_simple_dummy_events.o +iio_dummy-$(CONFIG_IIO_SIMPLE_DUMMY_BUFFER) += iio_simple_dummy_buffer.o + +obj-$(CONFIG_IIO_DUMMY_EVGEN) += iio_dummy_evgen.o diff --git a/drivers/iio/dummy/iio_dummy_evgen.c b/drivers/iio/dummy/iio_dummy_evgen.c new file mode 100644 index 000000000..30f0ae7c5 --- /dev/null +++ b/drivers/iio/dummy/iio_dummy_evgen.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2011 Jonathan Cameron + * + * Companion module to the iio simple dummy example driver. + * The purpose of this is to generate 'fake' event interrupts thus + * allowing that driver's code to be as close as possible to that of + * a normal driver talking to hardware. The approach used here + * is not intended to be general and just happens to work for this + * particular use case. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "iio_dummy_evgen.h" +#include +#include +#include + +/* Fiddly bit of faking and irq without hardware */ +#define IIO_EVENTGEN_NO 10 + +/** + * struct iio_dummy_eventgen - event generator specific state + * @regs: irq regs we are faking + * @lock: protect the evgen state + * @inuse: mask of which irqs are connected + * @irq_sim_domain: irq simulator domain + */ +struct iio_dummy_eventgen { + struct iio_dummy_regs regs[IIO_EVENTGEN_NO]; + struct mutex lock; + bool inuse[IIO_EVENTGEN_NO]; + struct irq_domain *irq_sim_domain; +}; + +/* We can only ever have one instance of this 'device' */ +static struct iio_dummy_eventgen *iio_evgen; + +static int iio_dummy_evgen_create(void) +{ + int ret; + + iio_evgen = kzalloc_obj(*iio_evgen); + if (!iio_evgen) + return -ENOMEM; + + iio_evgen->irq_sim_domain = irq_domain_create_sim(NULL, + IIO_EVENTGEN_NO); + if (IS_ERR(iio_evgen->irq_sim_domain)) { + ret = PTR_ERR(iio_evgen->irq_sim_domain); + kfree(iio_evgen); + return ret; + } + + mutex_init(&iio_evgen->lock); + + return 0; +} + +/** + * iio_dummy_evgen_get_irq() - get an evgen provided irq for a device + * + * This function will give a free allocated irq to a client device. + * That irq can then be caused to 'fire' by using the associated sysfs file. + */ +int iio_dummy_evgen_get_irq(void) +{ + int i, ret = 0; + + if (!iio_evgen) + return -ENODEV; + + mutex_lock(&iio_evgen->lock); + for (i = 0; i < IIO_EVENTGEN_NO; i++) { + if (!iio_evgen->inuse[i]) { + ret = irq_create_mapping(iio_evgen->irq_sim_domain, i); + iio_evgen->inuse[i] = true; + break; + } + } + mutex_unlock(&iio_evgen->lock); + if (i == IIO_EVENTGEN_NO) + return -ENOMEM; + + return ret; +} +EXPORT_SYMBOL_GPL(iio_dummy_evgen_get_irq); + +/** + * iio_dummy_evgen_release_irq() - give the irq back. + * @irq: irq being returned to the pool + * + * Used by client driver instances to give the irqs back when they disconnect + */ +void iio_dummy_evgen_release_irq(int irq) +{ + struct irq_data *irqd = irq_get_irq_data(irq); + + mutex_lock(&iio_evgen->lock); + iio_evgen->inuse[irqd_to_hwirq(irqd)] = false; + irq_dispose_mapping(irq); + mutex_unlock(&iio_evgen->lock); +} +EXPORT_SYMBOL_GPL(iio_dummy_evgen_release_irq); + +struct iio_dummy_regs *iio_dummy_evgen_get_regs(int irq) +{ + struct irq_data *irqd = irq_get_irq_data(irq); + + return &iio_evgen->regs[irqd_to_hwirq(irqd)]; + +} +EXPORT_SYMBOL_GPL(iio_dummy_evgen_get_regs); + +static void iio_dummy_evgen_free(void) +{ + irq_domain_remove_sim(iio_evgen->irq_sim_domain); + kfree(iio_evgen); +} + +static void iio_evgen_release(struct device *dev) +{ + iio_dummy_evgen_free(); +} + +static ssize_t iio_evgen_poke(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + unsigned long event; + int ret, irq; + + ret = kstrtoul(buf, 10, &event); + if (ret) + return ret; + + iio_evgen->regs[this_attr->address].reg_id = this_attr->address; + iio_evgen->regs[this_attr->address].reg_data = event; + + irq = irq_find_mapping(iio_evgen->irq_sim_domain, this_attr->address); + ret = irq_set_irqchip_state(irq, IRQCHIP_STATE_PENDING, true); + if (ret) + return ret; + + return len; +} + +static IIO_DEVICE_ATTR(poke_ev0, S_IWUSR, NULL, &iio_evgen_poke, 0); +static IIO_DEVICE_ATTR(poke_ev1, S_IWUSR, NULL, &iio_evgen_poke, 1); +static IIO_DEVICE_ATTR(poke_ev2, S_IWUSR, NULL, &iio_evgen_poke, 2); +static IIO_DEVICE_ATTR(poke_ev3, S_IWUSR, NULL, &iio_evgen_poke, 3); +static IIO_DEVICE_ATTR(poke_ev4, S_IWUSR, NULL, &iio_evgen_poke, 4); +static IIO_DEVICE_ATTR(poke_ev5, S_IWUSR, NULL, &iio_evgen_poke, 5); +static IIO_DEVICE_ATTR(poke_ev6, S_IWUSR, NULL, &iio_evgen_poke, 6); +static IIO_DEVICE_ATTR(poke_ev7, S_IWUSR, NULL, &iio_evgen_poke, 7); +static IIO_DEVICE_ATTR(poke_ev8, S_IWUSR, NULL, &iio_evgen_poke, 8); +static IIO_DEVICE_ATTR(poke_ev9, S_IWUSR, NULL, &iio_evgen_poke, 9); + +static struct attribute *iio_evgen_attrs[] = { + &iio_dev_attr_poke_ev0.dev_attr.attr, + &iio_dev_attr_poke_ev1.dev_attr.attr, + &iio_dev_attr_poke_ev2.dev_attr.attr, + &iio_dev_attr_poke_ev3.dev_attr.attr, + &iio_dev_attr_poke_ev4.dev_attr.attr, + &iio_dev_attr_poke_ev5.dev_attr.attr, + &iio_dev_attr_poke_ev6.dev_attr.attr, + &iio_dev_attr_poke_ev7.dev_attr.attr, + &iio_dev_attr_poke_ev8.dev_attr.attr, + &iio_dev_attr_poke_ev9.dev_attr.attr, + NULL, +}; + +static const struct attribute_group iio_evgen_group = { + .attrs = iio_evgen_attrs, +}; + +static const struct attribute_group *iio_evgen_groups[] = { + &iio_evgen_group, + NULL +}; + +static struct device iio_evgen_dev = { + .bus = &iio_bus_type, + .groups = iio_evgen_groups, + .release = &iio_evgen_release, +}; + +static __init int iio_dummy_evgen_init(void) +{ + int ret = iio_dummy_evgen_create(); + + if (ret < 0) + return ret; + device_initialize(&iio_evgen_dev); + dev_set_name(&iio_evgen_dev, "iio_evgen"); + ret = device_add(&iio_evgen_dev); + if (ret) + put_device(&iio_evgen_dev); + return ret; +} +module_init(iio_dummy_evgen_init); + +static __exit void iio_dummy_evgen_exit(void) +{ + device_unregister(&iio_evgen_dev); +} +module_exit(iio_dummy_evgen_exit); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("IIO dummy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dummy/iio_dummy_evgen.h b/drivers/iio/dummy/iio_dummy_evgen.h new file mode 100644 index 000000000..e0bf64fe9 --- /dev/null +++ b/drivers/iio/dummy/iio_dummy_evgen.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _IIO_DUMMY_EVGEN_H_ +#define _IIO_DUMMY_EVGEN_H_ + +struct iio_dummy_regs { + u32 reg_id; + u32 reg_data; +}; + +struct iio_dummy_regs *iio_dummy_evgen_get_regs(int irq); +int iio_dummy_evgen_get_irq(void); +void iio_dummy_evgen_release_irq(int irq); + +#endif /* _IIO_DUMMY_EVGEN_H_ */ diff --git a/drivers/iio/dummy/iio_simple_dummy.c b/drivers/iio/dummy/iio_simple_dummy.c new file mode 100644 index 000000000..19fcdbbc1 --- /dev/null +++ b/drivers/iio/dummy/iio_simple_dummy.c @@ -0,0 +1,736 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2011 Jonathan Cameron + * + * A reference industrial I/O driver to illustrate the functionality available. + * + * There are numerous real drivers to illustrate the finer points. + * The purpose of this driver is to provide a driver with far more comments + * and explanatory notes than any 'real' driver would have. + * Anyone starting out writing an IIO driver should first make sure they + * understand all of this driver except those bits specifically marked + * as being present to allow us to 'fake' the presence of hardware. + */ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "iio_simple_dummy.h" + +static const struct config_item_type iio_dummy_type = { + .ct_owner = THIS_MODULE, +}; + +/** + * struct iio_dummy_accel_calibscale - realworld to register mapping + * @val: first value in read_raw - here integer part. + * @val2: second value in read_raw etc - here micro part. + * @regval: register value - magic device specific numbers. + */ +struct iio_dummy_accel_calibscale { + int val; + int val2; + int regval; /* what would be written to hardware */ +}; + +static const struct iio_dummy_accel_calibscale dummy_scales[] = { + { 0, 100, 0x8 }, /* 0.000100 */ + { 0, 133, 0x7 }, /* 0.000133 */ + { 733, 13, 0x9 }, /* 733.000013 */ +}; + +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + +/* + * simple event - triggered when value rises above + * a threshold + */ +static const struct iio_event_spec iio_dummy_event = { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE), +}; + +/* + * simple step detect event - triggered when a step is detected + */ +static const struct iio_event_spec step_detect_event = { + .type = IIO_EV_TYPE_CHANGE, + .dir = IIO_EV_DIR_NONE, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), +}; + +/* + * simple transition event - triggered when the reported running confidence + * value rises above a threshold value + */ +static const struct iio_event_spec iio_running_event = { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE), +}; + +/* + * simple transition event - triggered when the reported walking confidence + * value falls under a threshold value + */ +static const struct iio_event_spec iio_walking_event = { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE), +}; +#endif + +/* + * iio_dummy_channels - Description of available channels + * + * This array of structures tells the IIO core about what the device + * actually provides for a given channel. + */ +static const struct iio_chan_spec iio_dummy_channels[] = { + /* indexed ADC channel in_voltage0_raw etc */ + { + .type = IIO_VOLTAGE, + /* Channel has a numeric index of 0 */ + .indexed = 1, + .channel = 0, + /* What other information is available? */ + .info_mask_separate = + /* + * in_voltage0_raw + * Raw (unscaled no bias removal etc) measurement + * from the device. + */ + BIT(IIO_CHAN_INFO_RAW) | + /* + * in_voltage0_offset + * Offset for userspace to apply prior to scale + * when converting to standard units (microvolts) + */ + BIT(IIO_CHAN_INFO_OFFSET) | + /* + * in_voltage0_scale + * Multipler for userspace to apply post offset + * when converting to standard units (microvolts) + */ + BIT(IIO_CHAN_INFO_SCALE), + /* + * sampling_frequency + * The frequency in Hz at which the channels are sampled + */ + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ), + /* The ordering of elements in the buffer via an enum */ + .scan_index = DUMMY_INDEX_VOLTAGE_0, + .scan_type = { /* Description of storage in buffer */ + .sign = 'u', /* unsigned */ + .realbits = 13, /* 13 bits */ + .storagebits = 16, /* 16 bits used for storage */ + .shift = 0, /* zero shift */ + }, +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + .event_spec = &iio_dummy_event, + .num_event_specs = 1, +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ + }, + /* Differential ADC channel in_voltage1-voltage2_raw etc*/ + { + .type = IIO_VOLTAGE, + .differential = 1, + /* + * Indexing for differential channels uses channel + * for the positive part, channel2 for the negative. + */ + .indexed = 1, + .channel = 1, + .channel2 = 2, + /* + * in_voltage1-voltage2_raw + * Raw (unscaled no bias removal etc) measurement + * from the device. + */ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + /* + * in_voltage-voltage_scale + * Shared version of scale - shared by differential + * input channels of type IIO_VOLTAGE. + */ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + /* + * sampling_frequency + * The frequency in Hz at which the channels are sampled + */ + .scan_index = DUMMY_INDEX_DIFFVOLTAGE_1M2, + .scan_type = { /* Description of storage in buffer */ + .sign = 's', /* signed */ + .realbits = 12, /* 12 bits */ + .storagebits = 16, /* 16 bits used for storage */ + .shift = 0, /* zero shift */ + }, + }, + /* Differential ADC channel in_voltage3-voltage4_raw etc*/ + { + .type = IIO_VOLTAGE, + .differential = 1, + .indexed = 1, + .channel = 3, + .channel2 = 4, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = DUMMY_INDEX_DIFFVOLTAGE_3M4, + .scan_type = { + .sign = 's', + .realbits = 11, + .storagebits = 16, + .shift = 0, + }, + }, + /* + * 'modified' (i.e. axis specified) acceleration channel + * in_accel_z_raw + */ + { + .type = IIO_ACCEL, + .modified = 1, + /* Channel 2 is use for modifiers */ + .channel2 = IIO_MOD_X, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + /* + * Internal bias and gain correction values. Applied + * by the hardware or driver prior to userspace + * seeing the readings. Typically part of hardware + * calibration. + */ + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = DUMMY_INDEX_ACCELX, + .scan_type = { /* Description of storage in buffer */ + .sign = 's', /* signed */ + .realbits = 16, /* 16 bits */ + .storagebits = 16, /* 16 bits used for storage */ + .shift = 0, /* zero shift */ + }, + }, + /* + * Convenience macro for timestamps. 4 is the index in + * the buffer. + */ + IIO_CHAN_SOFT_TIMESTAMP(4), + /* DAC channel out_voltage0_raw */ + { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_index = -1, /* No buffer support */ + .output = 1, + .indexed = 1, + .channel = 0, + }, + { + .type = IIO_STEPS, + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_CALIBHEIGHT), + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = -1, /* No buffer support */ +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + .event_spec = &step_detect_event, + .num_event_specs = 1, +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ + }, + { + .type = IIO_ACTIVITY, + .modified = 1, + .channel2 = IIO_MOD_RUNNING, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = -1, /* No buffer support */ +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + .event_spec = &iio_running_event, + .num_event_specs = 1, +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ + }, + { + .type = IIO_ACTIVITY, + .modified = 1, + .channel2 = IIO_MOD_WALKING, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = -1, /* No buffer support */ +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + .event_spec = &iio_walking_event, + .num_event_specs = 1, +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ + }, +}; + +static int __iio_dummy_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->lock); + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->output) { + /* Set integer part to cached value */ + *val = st->dac_val; + return IIO_VAL_INT; + } else if (chan->differential) { + if (chan->channel == 1) + *val = st->differential_adc_val[0]; + else + *val = st->differential_adc_val[1]; + return IIO_VAL_INT; + } else { + *val = st->single_ended_adc_val; + return IIO_VAL_INT; + } + + case IIO_ACCEL: + *val = st->accel_val; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int __iio_dummy_read_processed(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->lock); + switch (chan->type) { + case IIO_STEPS: + *val = st->steps; + return IIO_VAL_INT; + case IIO_ACTIVITY: + switch (chan->channel2) { + case IIO_MOD_RUNNING: + *val = st->activity_running; + return IIO_VAL_INT; + case IIO_MOD_WALKING: + *val = st->activity_walking; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +/** + * iio_dummy_read_raw() - data read function. + * @indio_dev: the struct iio_dev associated with this device instance + * @chan: the channel whose data is to be read + * @val: first element of returned value (typically INT) + * @val2: second element of returned value (typically MICRO) + * @mask: what we actually want to read as per the info_mask_* + * in iio_chan_spec. + */ +static int iio_dummy_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: /* magic value - channel value read */ + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = __iio_dummy_read_raw(indio_dev, chan, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_PROCESSED: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = __iio_dummy_read_processed(indio_dev, chan, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_OFFSET: + /* only single ended adc -> 7 */ + *val = 7; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + switch (chan->differential) { + case 0: + /* only single ended adc -> 0.001333 */ + *val = 0; + *val2 = 1333; + return IIO_VAL_INT_PLUS_MICRO; + case 1: + /* all differential adc -> 0.000001344 */ + *val = 0; + *val2 = 1344; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: { + guard(mutex)(&st->lock); + /* only the acceleration axis - read from cache */ + *val = st->accel_calibbias; + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_CALIBSCALE: { + guard(mutex)(&st->lock); + *val = st->accel_calibscale->val; + *val2 = st->accel_calibscale->val2; + return IIO_VAL_INT_PLUS_MICRO; + } + case IIO_CHAN_INFO_SAMP_FREQ: + *val = 3; + *val2 = 33; + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_ENABLE: { + guard(mutex)(&st->lock); + switch (chan->type) { + case IIO_STEPS: + *val = st->steps_enabled; + return IIO_VAL_INT; + default: + return -EINVAL; + } + } + case IIO_CHAN_INFO_CALIBHEIGHT: { + guard(mutex)(&st->lock); + switch (chan->type) { + case IIO_STEPS: + *val = st->height; + return IIO_VAL_INT; + default: + return -EINVAL; + } + } + default: + return -EINVAL; + } +} + +/** + * iio_dummy_write_raw() - data write function. + * @indio_dev: the struct iio_dev associated with this device instance + * @chan: the channel whose data is to be written + * @val: first element of value to set (typically INT) + * @val2: second element of value to set (typically MICRO) + * @mask: what we actually want to write as per the info_mask_* + * in iio_chan_spec. + * + * Note that all raw writes are assumed IIO_VAL_INT and info mask elements + * are assumed to be IIO_INT_PLUS_MICRO unless the callback write_raw_get_fmt + * in struct iio_info is provided by the driver. + */ +static int iio_dummy_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + int i; + struct iio_dummy_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->output == 0) + return -EINVAL; + + scoped_guard(mutex, &st->lock) { + /* Locking not required as writing single value */ + st->dac_val = val; + } + return 0; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_STEPS: + scoped_guard(mutex, &st->lock) { + st->steps = val; + } + return 0; + case IIO_ACTIVITY: + if (val < 0) + val = 0; + if (val > 100) + val = 100; + switch (chan->channel2) { + case IIO_MOD_RUNNING: + st->activity_running = val; + return 0; + case IIO_MOD_WALKING: + st->activity_walking = val; + return 0; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBSCALE: { + guard(mutex)(&st->lock); + /* Compare against table - hard matching here */ + for (i = 0; i < ARRAY_SIZE(dummy_scales); i++) + if (val == dummy_scales[i].val && + val2 == dummy_scales[i].val2) + break; + if (i == ARRAY_SIZE(dummy_scales)) + return -EINVAL; + st->accel_calibscale = &dummy_scales[i]; + return 0; + } + case IIO_CHAN_INFO_CALIBBIAS: + scoped_guard(mutex, &st->lock) { + st->accel_calibbias = val; + } + return 0; + case IIO_CHAN_INFO_ENABLE: + switch (chan->type) { + case IIO_STEPS: + scoped_guard(mutex, &st->lock) { + st->steps_enabled = val; + } + return 0; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBHEIGHT: + switch (chan->type) { + case IIO_STEPS: + st->height = val; + return 0; + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +/* + * Device type specific information. + */ +static const struct iio_info iio_dummy_info = { + .read_raw = &iio_dummy_read_raw, + .write_raw = &iio_dummy_write_raw, +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + .read_event_config = &iio_simple_dummy_read_event_config, + .write_event_config = &iio_simple_dummy_write_event_config, + .read_event_value = &iio_simple_dummy_read_event_value, + .write_event_value = &iio_simple_dummy_write_event_value, +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ +}; + +/** + * iio_dummy_init_device() - device instance specific init + * @indio_dev: the iio device structure + * + * Most drivers have one of these to set up default values, + * reset the device to known state etc. + */ +static int iio_dummy_init_device(struct iio_dev *indio_dev) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + st->dac_val = 0; + st->single_ended_adc_val = 73; + st->differential_adc_val[0] = 33; + st->differential_adc_val[1] = -34; + st->accel_val = 34; + st->accel_calibbias = -7; + st->accel_calibscale = &dummy_scales[0]; + st->steps = 47; + st->activity_running = 98; + st->activity_walking = 4; + + return 0; +} + +/** + * iio_dummy_probe() - device instance probe + * @name: name of this instance. + * + * Arguments are bus type specific. + * I2C: iio_dummy_probe(struct i2c_client *client, + * const struct i2c_device_id *id) + * SPI: iio_dummy_probe(struct spi_device *spi) + */ +static struct iio_sw_device *iio_dummy_probe(const char *name) +{ + int ret; + struct iio_dev *indio_dev; + struct iio_dummy_state *st; + struct iio_sw_device *swd; + struct device *parent = NULL; + + /* + * With hardware: Set the parent device. + * parent = &spi->dev; + * parent = &client->dev; + */ + + swd = kzalloc_obj(*swd); + if (!swd) + return ERR_PTR(-ENOMEM); + + /* + * Allocate an IIO device. + * + * This structure contains all generic state + * information about the device instance. + * It also has a region (accessed by iio_priv() + * for chip specific state information. + */ + indio_dev = iio_device_alloc(parent, sizeof(*st)); + if (!indio_dev) { + ret = -ENOMEM; + goto error_free_swd; + } + + st = iio_priv(indio_dev); + mutex_init(&st->lock); + + iio_dummy_init_device(indio_dev); + + /* + * Make the iio_dev struct available to remove function. + * Bus equivalents + * i2c_set_clientdata(client, indio_dev); + * spi_set_drvdata(spi, indio_dev); + */ + swd->device = indio_dev; + + /* + * Set the device name. + * + * This is typically a part number and obtained from the module + * id table. + * e.g. for i2c and spi: + * indio_dev->name = id->name; + * indio_dev->name = spi_get_device_id(spi)->name; + */ + indio_dev->name = kstrdup(name, GFP_KERNEL); + if (!indio_dev->name) { + ret = -ENOMEM; + goto error_free_device; + } + + /* Provide description of available channels */ + indio_dev->channels = iio_dummy_channels; + indio_dev->num_channels = ARRAY_SIZE(iio_dummy_channels); + + /* + * Provide device type specific interface functions and + * constant data. + */ + indio_dev->info = &iio_dummy_info; + + /* Specify that device provides sysfs type interfaces */ + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_simple_dummy_events_register(indio_dev); + if (ret < 0) + goto error_free_name; + + ret = iio_simple_dummy_configure_buffer(indio_dev); + if (ret < 0) + goto error_unregister_events; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto error_unconfigure_buffer; + + iio_swd_group_init_type_name(swd, name, &iio_dummy_type); + + return swd; +error_unconfigure_buffer: + iio_simple_dummy_unconfigure_buffer(indio_dev); +error_unregister_events: + iio_simple_dummy_events_unregister(indio_dev); +error_free_name: + kfree(indio_dev->name); +error_free_device: + iio_device_free(indio_dev); +error_free_swd: + kfree(swd); + return ERR_PTR(ret); +} + +/** + * iio_dummy_remove() - device instance removal function + * @swd: pointer to software IIO device abstraction + * + * Parameters follow those of iio_dummy_probe for buses. + */ +static int iio_dummy_remove(struct iio_sw_device *swd) +{ + /* + * Get a pointer to the device instance iio_dev structure + * from the bus subsystem. E.g. + * struct iio_dev *indio_dev = i2c_get_clientdata(client); + * struct iio_dev *indio_dev = spi_get_drvdata(spi); + */ + struct iio_dev *indio_dev = swd->device; + + /* Unregister the device */ + iio_device_unregister(indio_dev); + + /* Device specific code to power down etc */ + + /* Buffered capture related cleanup */ + iio_simple_dummy_unconfigure_buffer(indio_dev); + + iio_simple_dummy_events_unregister(indio_dev); + + /* Free all structures */ + kfree(indio_dev->name); + iio_device_free(indio_dev); + + return 0; +} + +/* + * module_iio_sw_device_driver() - device driver registration + * + * Varies depending on bus type of the device. As there is no device + * here, call probe directly. For information on device registration + * i2c: + * Documentation/i2c/writing-clients.rst + * spi: + * Documentation/spi/spi-summary.rst + */ +static const struct iio_sw_device_ops iio_dummy_device_ops = { + .probe = iio_dummy_probe, + .remove = iio_dummy_remove, +}; + +static struct iio_sw_device_type iio_dummy_device = { + .name = "dummy", + .owner = THIS_MODULE, + .ops = &iio_dummy_device_ops, +}; + +module_iio_sw_device_driver(iio_dummy_device); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("IIO dummy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dummy/iio_simple_dummy.h b/drivers/iio/dummy/iio_simple_dummy.h new file mode 100644 index 000000000..8246f25db --- /dev/null +++ b/drivers/iio/dummy/iio_simple_dummy.h @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/** + * Copyright (c) 2011 Jonathan Cameron + * + * Join together the various functionality of iio_simple_dummy driver + */ + +#ifndef _IIO_SIMPLE_DUMMY_H_ +#define _IIO_SIMPLE_DUMMY_H_ +#include + +struct iio_dummy_accel_calibscale; +struct iio_dummy_regs; + +/** + * struct iio_dummy_state - device instance specific state. + * @dac_val: cache for dac value + * @single_ended_adc_val: cache for single ended adc value + * @differential_adc_val: cache for differential adc value + * @accel_val: cache for acceleration value + * @accel_calibbias: cache for acceleration calibbias + * @accel_calibscale: cache for acceleration calibscale + * @lock: lock to ensure state is consistent + * @event_irq: irq number for event line (faked) + * @event_val: cache for event threshold value + * @event_en: cache of whether event is enabled + */ +struct iio_dummy_state { + int dac_val; + int single_ended_adc_val; + int differential_adc_val[2]; + int accel_val; + int accel_calibbias; + int activity_running; + int activity_walking; + const struct iio_dummy_accel_calibscale *accel_calibscale; + struct mutex lock; + struct iio_dummy_regs *regs; + int steps_enabled; + int steps; + int height; +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + int event_irq; + int event_val; + bool event_en; + s64 event_timestamp; +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ +}; + +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + +struct iio_dev; + +int iio_simple_dummy_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir); + +int iio_simple_dummy_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state); + +int iio_simple_dummy_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, + int *val2); + +int iio_simple_dummy_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, + int val2); + +int iio_simple_dummy_events_register(struct iio_dev *indio_dev); +void iio_simple_dummy_events_unregister(struct iio_dev *indio_dev); + +#else /* Stubs for when events are disabled at compile time */ + +static inline int +iio_simple_dummy_events_register(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline void +iio_simple_dummy_events_unregister(struct iio_dev *indio_dev) +{} + +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS*/ + +/** + * enum iio_simple_dummy_scan_elements - scan index enum + * @DUMMY_INDEX_VOLTAGE_0: the single ended voltage channel + * @DUMMY_INDEX_DIFFVOLTAGE_1M2: first differential channel + * @DUMMY_INDEX_DIFFVOLTAGE_3M4: second differential channel + * @DUMMY_INDEX_ACCELX: acceleration channel + * + * Enum provides convenient numbering for the scan index. + */ +enum iio_simple_dummy_scan_elements { + DUMMY_INDEX_VOLTAGE_0, + DUMMY_INDEX_DIFFVOLTAGE_1M2, + DUMMY_INDEX_DIFFVOLTAGE_3M4, + DUMMY_INDEX_ACCELX, +}; + +#ifdef CONFIG_IIO_SIMPLE_DUMMY_BUFFER +int iio_simple_dummy_configure_buffer(struct iio_dev *indio_dev); +void iio_simple_dummy_unconfigure_buffer(struct iio_dev *indio_dev); +#else +static inline int iio_simple_dummy_configure_buffer(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline +void iio_simple_dummy_unconfigure_buffer(struct iio_dev *indio_dev) +{} + +#endif /* CONFIG_IIO_SIMPLE_DUMMY_BUFFER */ +#endif /* _IIO_SIMPLE_DUMMY_H_ */ diff --git a/drivers/iio/dummy/iio_simple_dummy_buffer.c b/drivers/iio/dummy/iio_simple_dummy_buffer.c new file mode 100644 index 000000000..57f3611a8 --- /dev/null +++ b/drivers/iio/dummy/iio_simple_dummy_buffer.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2011 Jonathan Cameron + * + * Buffer handling elements of industrial I/O reference driver. + * Uses the kfifo buffer. + * + * To test without hardware use the sysfs trigger. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "iio_simple_dummy.h" + +/* Some fake data */ + +static const s16 fakedata[] = { + [DUMMY_INDEX_VOLTAGE_0] = 7, + [DUMMY_INDEX_DIFFVOLTAGE_1M2] = -33, + [DUMMY_INDEX_DIFFVOLTAGE_3M4] = -2, + [DUMMY_INDEX_ACCELX] = 344, +}; + +struct dummy_scan { + s16 data[ARRAY_SIZE(fakedata)]; + aligned_s64 timestamp; +}; + +/** + * iio_simple_dummy_trigger_h() - the trigger handler function + * @irq: the interrupt number + * @p: private data - always a pointer to the poll func. + * + * This is the guts of buffered capture. On a trigger event occurring, + * if the pollfunc is attached then this handler is called as a threaded + * interrupt (and hence may sleep). It is responsible for grabbing data + * from the device and pushing it into the associated buffer. + */ +static irqreturn_t iio_simple_dummy_trigger_h(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct dummy_scan *scan; + int i = 0, j; + + /* + * Note that some buses such as SPI require DMA safe buffers which + * cannot be on the stack. Two easy ways to do this: + * - Local kzalloc (as done here) + * - A buffer at the end of the structure accessed via iio_priv() + * that is marked __aligned(IIO_DMA_MINALIGN). + */ + scan = kzalloc_obj(*scan); + if (!scan) + goto done; + + /* + * Three common options here: + * hardware scans: + * certain combinations of channels make up a fast read. The capture + * will consist of all of them. Hence we just call the grab data + * function and fill the buffer without processing. + * software scans: + * can be considered to be random access so efficient reading is just + * a case of minimal bus transactions. + * software culled hardware scans: + * occasionally a driver may process the nearest hardware scan to avoid + * storing elements that are not desired. This is the fiddliest option + * by far. + * Here let's pretend we have random access. And the values are in the + * constant table fakedata. + */ + iio_for_each_active_channel(indio_dev, j) + scan->data[i++] = fakedata[j]; + + iio_push_to_buffers_with_ts(indio_dev, scan, sizeof(*scan), + iio_get_time_ns(indio_dev)); + + kfree(scan); +done: + /* + * Tell the core we are done with this trigger and ready for the + * next one. + */ + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_buffer_setup_ops iio_simple_dummy_buffer_setup_ops = { +}; + +int iio_simple_dummy_configure_buffer(struct iio_dev *indio_dev) +{ + return iio_triggered_buffer_setup(indio_dev, NULL, + iio_simple_dummy_trigger_h, + &iio_simple_dummy_buffer_setup_ops); +} + +/** + * iio_simple_dummy_unconfigure_buffer() - release buffer resources + * @indio_dev: device instance state + */ +void iio_simple_dummy_unconfigure_buffer(struct iio_dev *indio_dev) +{ + iio_triggered_buffer_cleanup(indio_dev); +} diff --git a/drivers/iio/dummy/iio_simple_dummy_events.c b/drivers/iio/dummy/iio_simple_dummy_events.c new file mode 100644 index 000000000..b51ec21b6 --- /dev/null +++ b/drivers/iio/dummy/iio_simple_dummy_events.c @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2011 Jonathan Cameron + * + * Event handling elements of industrial I/O reference driver. + */ +#include +#include +#include +#include + +#include +#include +#include +#include "iio_simple_dummy.h" + +/* Evgen 'fakes' interrupt events for this example */ +#include "iio_dummy_evgen.h" + +/** + * iio_simple_dummy_read_event_config() - is event enabled? + * @indio_dev: the device instance data + * @chan: channel for the event whose state is being queried + * @type: type of the event whose state is being queried + * @dir: direction of the vent whose state is being queried + * + * This function would normally query the relevant registers or a cache to + * discover if the event generation is enabled on the device. + */ +int iio_simple_dummy_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + return st->event_en; +} + +/** + * iio_simple_dummy_write_event_config() - set whether event is enabled + * @indio_dev: the device instance data + * @chan: channel for the event whose state is being set + * @type: type of the event whose state is being set + * @dir: direction of the vent whose state is being set + * @state: whether to enable or disable the device. + * + * This function would normally set the relevant registers on the devices + * so that it generates the specified event. Here it just sets up a cached + * value. + */ +int iio_simple_dummy_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + /* + * Deliberately over the top code splitting to illustrate + * how this is done when multiple events exist. + */ + switch (chan->type) { + case IIO_VOLTAGE: + switch (type) { + case IIO_EV_TYPE_THRESH: + if (dir == IIO_EV_DIR_RISING) + st->event_en = state; + else + return -EINVAL; + break; + default: + return -EINVAL; + } + break; + case IIO_ACTIVITY: + switch (type) { + case IIO_EV_TYPE_THRESH: + st->event_en = state; + break; + default: + return -EINVAL; + } + break; + case IIO_STEPS: + switch (type) { + case IIO_EV_TYPE_CHANGE: + st->event_en = state; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +/** + * iio_simple_dummy_read_event_value() - get value associated with event + * @indio_dev: device instance specific data + * @chan: channel for the event whose value is being read + * @type: type of the event whose value is being read + * @dir: direction of the vent whose value is being read + * @info: info type of the event whose value is being read + * @val: value for the event code. + * @val2: unused + * + * Many devices provide a large set of events of which only a subset may + * be enabled at a time, with value registers whose meaning changes depending + * on the event enabled. This often means that the driver must cache the values + * associated with each possible events so that the right value is in place when + * the enabled event is changed. + */ +int iio_simple_dummy_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + *val = st->event_val; + + return IIO_VAL_INT; +} + +/** + * iio_simple_dummy_write_event_value() - set value associate with event + * @indio_dev: device instance specific data + * @chan: channel for the event whose value is being set + * @type: type of the event whose value is being set + * @dir: direction of the vent whose value is being set + * @info: info type of the event whose value is being set + * @val: the value to be set. + * @val2: unused + */ +int iio_simple_dummy_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + st->event_val = val; + + return 0; +} + +static irqreturn_t iio_simple_dummy_get_timestamp(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct iio_dummy_state *st = iio_priv(indio_dev); + + st->event_timestamp = iio_get_time_ns(indio_dev); + return IRQ_WAKE_THREAD; +} + +/** + * iio_simple_dummy_event_handler() - identify and pass on event + * @irq: irq of event line + * @private: pointer to device instance state. + * + * This handler is responsible for querying the device to find out what + * event occurred and for then pushing that event towards userspace. + * Here only one event occurs so we push that directly on with locally + * grabbed timestamp. + */ +static irqreturn_t iio_simple_dummy_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct iio_dummy_state *st = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "id %x event %x\n", + st->regs->reg_id, st->regs->reg_data); + + switch (st->regs->reg_data) { + case 0: + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + st->event_timestamp); + break; + case 1: + if (st->activity_running > st->event_val) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACTIVITY, 0, + IIO_MOD_RUNNING, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + st->event_timestamp); + break; + case 2: + if (st->activity_walking < st->event_val) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACTIVITY, 0, + IIO_MOD_WALKING, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + st->event_timestamp); + break; + case 3: + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_STEPS, 0, + IIO_EV_TYPE_CHANGE, + IIO_EV_DIR_NONE), + st->event_timestamp); + break; + default: + break; + } + + return IRQ_HANDLED; +} + +/** + * iio_simple_dummy_events_register() - setup interrupt handling for events + * @indio_dev: device instance data + * + * This function requests the threaded interrupt to handle the events. + * Normally the irq is a hardware interrupt and the number comes + * from board configuration files. Here we get it from a companion + * module that fakes the interrupt for us. Note that module in + * no way forms part of this example. Just assume that events magically + * appear via the provided interrupt. + */ +int iio_simple_dummy_events_register(struct iio_dev *indio_dev) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + int ret; + + /* Fire up event source - normally not present */ + st->event_irq = iio_dummy_evgen_get_irq(); + if (st->event_irq < 0) { + ret = st->event_irq; + goto error_ret; + } + st->regs = iio_dummy_evgen_get_regs(st->event_irq); + + ret = request_threaded_irq(st->event_irq, + &iio_simple_dummy_get_timestamp, + &iio_simple_dummy_event_handler, + IRQF_ONESHOT, + "iio_simple_event", + indio_dev); + if (ret < 0) + goto error_free_evgen; + return 0; + +error_free_evgen: + iio_dummy_evgen_release_irq(st->event_irq); +error_ret: + return ret; +} + +/** + * iio_simple_dummy_events_unregister() - tidy up interrupt handling on remove + * @indio_dev: device instance data + */ +void iio_simple_dummy_events_unregister(struct iio_dev *indio_dev) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + free_irq(st->event_irq, indio_dev); + /* Not part of normal driver */ + iio_dummy_evgen_release_irq(st->event_irq); +} diff --git a/drivers/iio/filter/Kconfig b/drivers/iio/filter/Kconfig new file mode 100644 index 000000000..a85b345ea --- /dev/null +++ b/drivers/iio/filter/Kconfig @@ -0,0 +1,19 @@ +# +# Filter drivers +# +# When adding new entries keep the list in alphabetical order + +menu "Filters" + +config ADMV8818 + tristate "Analog Devices ADMV8818 High-Pass and Low-Pass Filter" + depends on SPI && COMMON_CLK && 64BIT + select REGMAP_SPI + help + Say yes here to build support for Analog Devices ADMV8818 + 2 GHz to 18 GHz, Digitally Tunable, High-Pass and Low-Pass Filter. + + To compile this driver as a module, choose M here: the + modiule will be called admv8818. + +endmenu diff --git a/drivers/iio/filter/Makefile b/drivers/iio/filter/Makefile new file mode 100644 index 000000000..55e228c0d --- /dev/null +++ b/drivers/iio/filter/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for industrial I/O Filter drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_ADMV8818) += admv8818.o diff --git a/drivers/iio/filter/admv8818.c b/drivers/iio/filter/admv8818.c new file mode 100644 index 000000000..b88689e60 --- /dev/null +++ b/drivers/iio/filter/admv8818.c @@ -0,0 +1,815 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADMV8818 driver + * + * Copyright 2021 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ADMV8818 Register Map */ +#define ADMV8818_REG_SPI_CONFIG_A 0x0 +#define ADMV8818_REG_SPI_CONFIG_B 0x1 +#define ADMV8818_REG_CHIPTYPE 0x3 +#define ADMV8818_REG_PRODUCT_ID_L 0x4 +#define ADMV8818_REG_PRODUCT_ID_H 0x5 +#define ADMV8818_REG_FAST_LATCH_POINTER 0x10 +#define ADMV8818_REG_FAST_LATCH_STOP 0x11 +#define ADMV8818_REG_FAST_LATCH_START 0x12 +#define ADMV8818_REG_FAST_LATCH_DIRECTION 0x13 +#define ADMV8818_REG_FAST_LATCH_STATE 0x14 +#define ADMV8818_REG_WR0_SW 0x20 +#define ADMV8818_REG_WR0_FILTER 0x21 +#define ADMV8818_REG_WR1_SW 0x22 +#define ADMV8818_REG_WR1_FILTER 0x23 +#define ADMV8818_REG_WR2_SW 0x24 +#define ADMV8818_REG_WR2_FILTER 0x25 +#define ADMV8818_REG_WR3_SW 0x26 +#define ADMV8818_REG_WR3_FILTER 0x27 +#define ADMV8818_REG_WR4_SW 0x28 +#define ADMV8818_REG_WR4_FILTER 0x29 +#define ADMV8818_REG_LUT0_SW 0x100 +#define ADMV8818_REG_LUT0_FILTER 0x101 +#define ADMV8818_REG_LUT127_SW 0x1FE +#define ADMV8818_REG_LUT127_FILTER 0x1FF + +/* ADMV8818_REG_SPI_CONFIG_A Map */ +#define ADMV8818_SOFTRESET_N_MSK BIT(7) +#define ADMV8818_LSB_FIRST_N_MSK BIT(6) +#define ADMV8818_ENDIAN_N_MSK BIT(5) +#define ADMV8818_SDOACTIVE_N_MSK BIT(4) +#define ADMV8818_SDOACTIVE_MSK BIT(3) +#define ADMV8818_ENDIAN_MSK BIT(2) +#define ADMV8818_LSBFIRST_MSK BIT(1) +#define ADMV8818_SOFTRESET_MSK BIT(0) + +/* ADMV8818_REG_SPI_CONFIG_B Map */ +#define ADMV8818_SINGLE_INSTRUCTION_MSK BIT(7) +#define ADMV8818_CSB_STALL_MSK BIT(6) +#define ADMV8818_MASTER_SLAVE_RB_MSK BIT(5) +#define ADMV8818_MASTER_SLAVE_TRANSFER_MSK BIT(0) + +/* ADMV8818_REG_WR0_SW Map */ +#define ADMV8818_SW_IN_SET_WR0_MSK BIT(7) +#define ADMV8818_SW_OUT_SET_WR0_MSK BIT(6) +#define ADMV8818_SW_IN_WR0_MSK GENMASK(5, 3) +#define ADMV8818_SW_OUT_WR0_MSK GENMASK(2, 0) + +/* ADMV8818_REG_WR0_FILTER Map */ +#define ADMV8818_HPF_WR0_MSK GENMASK(7, 4) +#define ADMV8818_LPF_WR0_MSK GENMASK(3, 0) + +#define ADMV8818_BAND_BYPASS 0 +#define ADMV8818_BAND_MIN 1 +#define ADMV8818_BAND_MAX 4 +#define ADMV8818_BAND_CORNER_LOW 0 +#define ADMV8818_BAND_CORNER_HIGH 1 + +#define ADMV8818_STATE_MIN 0 +#define ADMV8818_STATE_MAX 15 +#define ADMV8818_NUM_STATES 16 + +enum { + ADMV8818_BW_FREQ, + ADMV8818_CENTER_FREQ +}; + +enum { + ADMV8818_AUTO_MODE, + ADMV8818_MANUAL_MODE, + ADMV8818_BYPASS_MODE, +}; + +struct admv8818_state { + struct spi_device *spi; + struct regmap *regmap; + struct clk *clkin; + struct notifier_block nb; + /* Protect against concurrent accesses to the device and data content*/ + struct mutex lock; + unsigned int filter_mode; + u64 cf_hz; + u64 lpf_margin_hz; + u64 hpf_margin_hz; +}; + +static const unsigned long long freq_range_hpf[5][2] = { + {0ULL, 0ULL}, /* bypass */ + {1750000000ULL, 3550000000ULL}, + {3400000000ULL, 7250000000ULL}, + {6600000000, 12000000000}, + {12500000000, 19900000000} +}; + +static const unsigned long long freq_range_lpf[5][2] = { + {U64_MAX, U64_MAX}, /* bypass */ + {2050000000ULL, 3850000000ULL}, + {3350000000ULL, 7250000000ULL}, + {7000000000, 13000000000}, + {12550000000, 18850000000} +}; + +static const struct regmap_config admv8818_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .read_flag_mask = 0x80, + .max_register = 0x1FF, +}; + +static const char * const admv8818_modes[] = { + [0] = "auto", + [1] = "manual", + [2] = "bypass" +}; + +static int __admv8818_hpf_select(struct admv8818_state *st, u64 freq) +{ + int band, state, ret; + unsigned int hpf_state = ADMV8818_STATE_MIN, hpf_band = ADMV8818_BAND_BYPASS; + u64 freq_error, min_freq_error, freq_corner, freq_step; + + if (freq < freq_range_hpf[ADMV8818_BAND_MIN][ADMV8818_BAND_CORNER_LOW]) + goto hpf_write; + + if (freq >= freq_range_hpf[ADMV8818_BAND_MAX][ADMV8818_BAND_CORNER_HIGH]) { + hpf_state = ADMV8818_STATE_MAX; + hpf_band = ADMV8818_BAND_MAX; + goto hpf_write; + } + + /* Close HPF frequency gap between 12 and 12.5 GHz */ + if (freq >= 12000ULL * HZ_PER_MHZ && freq < 12500ULL * HZ_PER_MHZ) { + hpf_state = ADMV8818_STATE_MAX; + hpf_band = 3; + goto hpf_write; + } + + min_freq_error = U64_MAX; + for (band = ADMV8818_BAND_MIN; band <= ADMV8818_BAND_MAX; band++) { + /* + * This (and therefore all other ranges) have a corner + * frequency higher than the target frequency. + */ + if (freq_range_hpf[band][ADMV8818_BAND_CORNER_LOW] > freq) + break; + + freq_step = freq_range_hpf[band][ADMV8818_BAND_CORNER_HIGH] - + freq_range_hpf[band][ADMV8818_BAND_CORNER_LOW]; + freq_step = div_u64(freq_step, ADMV8818_NUM_STATES - 1); + + for (state = ADMV8818_STATE_MIN; state <= ADMV8818_STATE_MAX; state++) { + freq_corner = freq_range_hpf[band][ADMV8818_BAND_CORNER_LOW] + + freq_step * state; + + /* + * This (and therefore all other states) have a corner + * frequency higher than the target frequency. + */ + if (freq_corner > freq) + break; + + freq_error = freq - freq_corner; + if (freq_error < min_freq_error) { + min_freq_error = freq_error; + hpf_state = state; + hpf_band = band; + } + } + } + +hpf_write: + ret = regmap_update_bits(st->regmap, ADMV8818_REG_WR0_SW, + ADMV8818_SW_IN_SET_WR0_MSK | + ADMV8818_SW_IN_WR0_MSK, + FIELD_PREP(ADMV8818_SW_IN_SET_WR0_MSK, 1) | + FIELD_PREP(ADMV8818_SW_IN_WR0_MSK, hpf_band)); + if (ret) + return ret; + + return regmap_update_bits(st->regmap, ADMV8818_REG_WR0_FILTER, + ADMV8818_HPF_WR0_MSK, + FIELD_PREP(ADMV8818_HPF_WR0_MSK, hpf_state)); +} + +static int admv8818_hpf_select(struct admv8818_state *st, u64 freq) +{ + int ret; + + mutex_lock(&st->lock); + ret = __admv8818_hpf_select(st, freq); + mutex_unlock(&st->lock); + + return ret; +} + +static int __admv8818_lpf_select(struct admv8818_state *st, u64 freq) +{ + int band, state, ret; + unsigned int lpf_state = ADMV8818_STATE_MIN, lpf_band = ADMV8818_BAND_BYPASS; + u64 freq_error, min_freq_error, freq_corner, freq_step; + + if (freq > freq_range_lpf[ADMV8818_BAND_MAX][ADMV8818_BAND_CORNER_HIGH]) + goto lpf_write; + + if (freq < freq_range_lpf[ADMV8818_BAND_MIN][ADMV8818_BAND_CORNER_LOW]) { + lpf_state = ADMV8818_STATE_MIN; + lpf_band = ADMV8818_BAND_MIN; + goto lpf_write; + } + + min_freq_error = U64_MAX; + for (band = ADMV8818_BAND_MAX; band >= ADMV8818_BAND_MIN; --band) { + /* + * At this point the highest corner frequency of + * all remaining ranges is below the target. + * LPF corner should be >= the target. + */ + if (freq > freq_range_lpf[band][ADMV8818_BAND_CORNER_HIGH]) + break; + + freq_step = freq_range_lpf[band][ADMV8818_BAND_CORNER_HIGH] - + freq_range_lpf[band][ADMV8818_BAND_CORNER_LOW]; + freq_step = div_u64(freq_step, ADMV8818_NUM_STATES - 1); + + for (state = ADMV8818_STATE_MAX; state >= ADMV8818_STATE_MIN; --state) { + + freq_corner = freq_range_lpf[band][ADMV8818_BAND_CORNER_LOW] + + state * freq_step; + + /* + * At this point all other states in range will + * place the corner frequency below the target + * LPF corner should >= the target. + */ + if (freq > freq_corner) + break; + + freq_error = freq_corner - freq; + if (freq_error < min_freq_error) { + min_freq_error = freq_error; + lpf_state = state; + lpf_band = band; + } + } + } + +lpf_write: + ret = regmap_update_bits(st->regmap, ADMV8818_REG_WR0_SW, + ADMV8818_SW_OUT_SET_WR0_MSK | + ADMV8818_SW_OUT_WR0_MSK, + FIELD_PREP(ADMV8818_SW_OUT_SET_WR0_MSK, 1) | + FIELD_PREP(ADMV8818_SW_OUT_WR0_MSK, lpf_band)); + if (ret) + return ret; + + return regmap_update_bits(st->regmap, ADMV8818_REG_WR0_FILTER, + ADMV8818_LPF_WR0_MSK, + FIELD_PREP(ADMV8818_LPF_WR0_MSK, lpf_state)); +} + +static int admv8818_lpf_select(struct admv8818_state *st, u64 freq) +{ + int ret; + + mutex_lock(&st->lock); + ret = __admv8818_lpf_select(st, freq); + mutex_unlock(&st->lock); + + return ret; +} + +static int admv8818_rfin_band_select(struct admv8818_state *st) +{ + int ret; + u64 hpf_corner_target, lpf_corner_target; + + st->cf_hz = clk_get_rate(st->clkin); + + /* Check for underflow */ + if (st->cf_hz > st->hpf_margin_hz) + hpf_corner_target = st->cf_hz - st->hpf_margin_hz; + else + hpf_corner_target = 0; + + /* Check for overflow */ + lpf_corner_target = st->cf_hz + st->lpf_margin_hz; + if (lpf_corner_target < st->cf_hz) + lpf_corner_target = U64_MAX; + + mutex_lock(&st->lock); + + ret = __admv8818_hpf_select(st, hpf_corner_target); + if (ret) + goto exit; + + ret = __admv8818_lpf_select(st, lpf_corner_target); +exit: + mutex_unlock(&st->lock); + return ret; +} + +static int __admv8818_read_hpf_freq(struct admv8818_state *st, u64 *hpf_freq) +{ + unsigned int data, hpf_band, hpf_state; + int ret; + + ret = regmap_read(st->regmap, ADMV8818_REG_WR0_SW, &data); + if (ret) + return ret; + + hpf_band = FIELD_GET(ADMV8818_SW_IN_WR0_MSK, data); + if (!hpf_band || hpf_band > 4) { + *hpf_freq = 0; + return ret; + } + + ret = regmap_read(st->regmap, ADMV8818_REG_WR0_FILTER, &data); + if (ret) + return ret; + + hpf_state = FIELD_GET(ADMV8818_HPF_WR0_MSK, data); + + *hpf_freq = freq_range_hpf[hpf_band][ADMV8818_BAND_CORNER_HIGH] - + freq_range_hpf[hpf_band][ADMV8818_BAND_CORNER_LOW]; + *hpf_freq = div_u64(*hpf_freq, ADMV8818_NUM_STATES - 1); + *hpf_freq = freq_range_hpf[hpf_band][ADMV8818_BAND_CORNER_LOW] + + (*hpf_freq * hpf_state); + + return ret; +} + +static int admv8818_read_hpf_freq(struct admv8818_state *st, u64 *hpf_freq) +{ + int ret; + + mutex_lock(&st->lock); + ret = __admv8818_read_hpf_freq(st, hpf_freq); + mutex_unlock(&st->lock); + + return ret; +} + +static int __admv8818_read_lpf_freq(struct admv8818_state *st, u64 *lpf_freq) +{ + unsigned int data, lpf_band, lpf_state; + int ret; + + ret = regmap_read(st->regmap, ADMV8818_REG_WR0_SW, &data); + if (ret) + return ret; + + lpf_band = FIELD_GET(ADMV8818_SW_OUT_WR0_MSK, data); + if (!lpf_band || lpf_band > 4) { + *lpf_freq = 0; + return ret; + } + + ret = regmap_read(st->regmap, ADMV8818_REG_WR0_FILTER, &data); + if (ret) + return ret; + + lpf_state = FIELD_GET(ADMV8818_LPF_WR0_MSK, data); + + *lpf_freq = freq_range_lpf[lpf_band][ADMV8818_BAND_CORNER_HIGH] - + freq_range_lpf[lpf_band][ADMV8818_BAND_CORNER_LOW]; + *lpf_freq = div_u64(*lpf_freq, ADMV8818_NUM_STATES - 1); + *lpf_freq = freq_range_lpf[lpf_band][ADMV8818_BAND_CORNER_LOW] + + (*lpf_freq * lpf_state); + + return ret; +} + +static int admv8818_read_lpf_freq(struct admv8818_state *st, u64 *lpf_freq) +{ + int ret; + + mutex_lock(&st->lock); + ret = __admv8818_read_lpf_freq(st, lpf_freq); + mutex_unlock(&st->lock); + + return ret; +} + +static int admv8818_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: + return IIO_VAL_INT_64; + default: + return -EINVAL; + } +} + +static int admv8818_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct admv8818_state *st = iio_priv(indio_dev); + + u64 freq = ((u64)val2 << 32 | (u32)val); + + if ((s64)freq < 0) + return -EINVAL; + + switch (info) { + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return admv8818_lpf_select(st, freq); + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: + return admv8818_hpf_select(st, freq); + default: + return -EINVAL; + } +} + +static int admv8818_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct admv8818_state *st = iio_priv(indio_dev); + int ret; + u64 freq; + + switch (info) { + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + ret = admv8818_read_lpf_freq(st, &freq); + if (ret) + return ret; + + *val = (u32)freq; + *val2 = (u32)(freq >> 32); + + return IIO_VAL_INT_64; + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: + ret = admv8818_read_hpf_freq(st, &freq); + if (ret) + return ret; + + *val = (u32)freq; + *val2 = (u32)(freq >> 32); + + return IIO_VAL_INT_64; + default: + return -EINVAL; + } +} + +static int admv8818_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int write_val, + unsigned int *read_val) +{ + struct admv8818_state *st = iio_priv(indio_dev); + + if (read_val) + return regmap_read(st->regmap, reg, read_val); + else + return regmap_write(st->regmap, reg, write_val); +} + +static int admv8818_filter_bypass(struct admv8818_state *st) +{ + int ret; + + mutex_lock(&st->lock); + + ret = regmap_update_bits(st->regmap, ADMV8818_REG_WR0_SW, + ADMV8818_SW_IN_SET_WR0_MSK | + ADMV8818_SW_IN_WR0_MSK | + ADMV8818_SW_OUT_SET_WR0_MSK | + ADMV8818_SW_OUT_WR0_MSK, + FIELD_PREP(ADMV8818_SW_IN_SET_WR0_MSK, 1) | + FIELD_PREP(ADMV8818_SW_IN_WR0_MSK, 0) | + FIELD_PREP(ADMV8818_SW_OUT_SET_WR0_MSK, 1) | + FIELD_PREP(ADMV8818_SW_OUT_WR0_MSK, 0)); + if (ret) + goto exit; + + ret = regmap_update_bits(st->regmap, ADMV8818_REG_WR0_FILTER, + ADMV8818_HPF_WR0_MSK | + ADMV8818_LPF_WR0_MSK, + FIELD_PREP(ADMV8818_HPF_WR0_MSK, 0) | + FIELD_PREP(ADMV8818_LPF_WR0_MSK, 0)); + +exit: + mutex_unlock(&st->lock); + + return ret; +} + +static int admv8818_get_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct admv8818_state *st = iio_priv(indio_dev); + + return st->filter_mode; +} + +static int admv8818_set_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct admv8818_state *st = iio_priv(indio_dev); + int ret = 0; + + if (!st->clkin) { + if (mode == ADMV8818_MANUAL_MODE) + goto set_mode; + + if (mode == ADMV8818_BYPASS_MODE) { + ret = admv8818_filter_bypass(st); + if (ret) + return ret; + + goto set_mode; + } + + return -EINVAL; + } + + switch (mode) { + case ADMV8818_AUTO_MODE: + if (st->filter_mode == ADMV8818_AUTO_MODE) + return 0; + + ret = clk_prepare_enable(st->clkin); + if (ret) + return ret; + + ret = clk_notifier_register(st->clkin, &st->nb); + if (ret) { + clk_disable_unprepare(st->clkin); + + return ret; + } + + break; + case ADMV8818_MANUAL_MODE: + case ADMV8818_BYPASS_MODE: + if (st->filter_mode == ADMV8818_AUTO_MODE) { + clk_disable_unprepare(st->clkin); + + ret = clk_notifier_unregister(st->clkin, &st->nb); + if (ret) + return ret; + } + + if (mode == ADMV8818_BYPASS_MODE) { + ret = admv8818_filter_bypass(st); + if (ret) + return ret; + } + + break; + default: + return -EINVAL; + } + +set_mode: + st->filter_mode = mode; + + return ret; +} + +static const struct iio_info admv8818_info = { + .write_raw = admv8818_write_raw, + .write_raw_get_fmt = admv8818_write_raw_get_fmt, + .read_raw = admv8818_read_raw, + .debugfs_reg_access = &admv8818_reg_access, +}; + +static const struct iio_enum admv8818_mode_enum = { + .items = admv8818_modes, + .num_items = ARRAY_SIZE(admv8818_modes), + .get = admv8818_get_mode, + .set = admv8818_set_mode, +}; + +static const struct iio_chan_spec_ext_info admv8818_ext_info[] = { + IIO_ENUM("filter_mode", IIO_SHARED_BY_ALL, &admv8818_mode_enum), + IIO_ENUM_AVAILABLE("filter_mode", IIO_SHARED_BY_ALL, &admv8818_mode_enum), + { } +}; + +#define ADMV8818_CHAN(_channel) { \ + .type = IIO_ALTVOLTAGE, \ + .output = 1, \ + .indexed = 1, \ + .channel = _channel, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \ + BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY) \ +} + +#define ADMV8818_CHAN_BW_CF(_channel, _admv8818_ext_info) { \ + .type = IIO_ALTVOLTAGE, \ + .output = 1, \ + .indexed = 1, \ + .channel = _channel, \ + .ext_info = _admv8818_ext_info, \ +} + +static const struct iio_chan_spec admv8818_channels[] = { + ADMV8818_CHAN(0), + ADMV8818_CHAN_BW_CF(0, admv8818_ext_info), +}; + +static int admv8818_freq_change(struct notifier_block *nb, unsigned long action, void *data) +{ + struct admv8818_state *st = container_of(nb, struct admv8818_state, nb); + + if (action == POST_RATE_CHANGE) + return notifier_from_errno(admv8818_rfin_band_select(st)); + + return NOTIFY_OK; +} + +static void admv8818_clk_notifier_unreg(void *data) +{ + struct admv8818_state *st = data; + + if (st->filter_mode == 0) + clk_notifier_unregister(st->clkin, &st->nb); +} + +static void admv8818_clk_disable(void *data) +{ + struct admv8818_state *st = data; + + if (st->filter_mode == 0) + clk_disable_unprepare(st->clkin); +} + +static int admv8818_init(struct admv8818_state *st) +{ + int ret; + struct device *dev = &st->spi->dev; + unsigned int chip_id; + + ret = regmap_write(st->regmap, ADMV8818_REG_SPI_CONFIG_A, + ADMV8818_SOFTRESET_N_MSK | ADMV8818_SOFTRESET_MSK); + if (ret) + return dev_err_probe(dev, ret, "ADMV8818 Soft Reset failed.\n"); + + ret = regmap_write(st->regmap, ADMV8818_REG_SPI_CONFIG_A, + ADMV8818_SDOACTIVE_N_MSK | ADMV8818_SDOACTIVE_MSK); + if (ret) + return dev_err_probe(dev, ret, "ADMV8818 SDO Enable failed.\n"); + + ret = regmap_read(st->regmap, ADMV8818_REG_CHIPTYPE, &chip_id); + if (ret) + return dev_err_probe(dev, ret, + "ADMV8818 Chip ID read failed.\n"); + + if (chip_id != 0x1) + return dev_err_probe(dev, -EINVAL, + "ADMV8818 Invalid Chip ID.\n"); + + ret = regmap_update_bits(st->regmap, ADMV8818_REG_SPI_CONFIG_B, + ADMV8818_SINGLE_INSTRUCTION_MSK, + FIELD_PREP(ADMV8818_SINGLE_INSTRUCTION_MSK, 1)); + if (ret) + return dev_err_probe(dev, ret, + "ADMV8818 Single Instruction failed.\n"); + + if (st->clkin) + return admv8818_rfin_band_select(st); + + return 0; +} + +static int admv8818_clk_setup(struct admv8818_state *st) +{ + struct device *dev = &st->spi->dev; + int ret; + + st->clkin = devm_clk_get_optional(dev, "rf_in"); + if (IS_ERR(st->clkin)) + return dev_err_probe(dev, PTR_ERR(st->clkin), + "failed to get the input clock\n"); + else if (!st->clkin) + return 0; + + ret = clk_prepare_enable(st->clkin); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, admv8818_clk_disable, st); + if (ret) + return ret; + + st->nb.notifier_call = admv8818_freq_change; + ret = clk_notifier_register(st->clkin, &st->nb); + if (ret < 0) + return ret; + + return devm_add_action_or_reset(dev, admv8818_clk_notifier_unreg, st); +} + +static int admv8818_read_properties(struct admv8818_state *st) +{ + struct device *dev = &st->spi->dev; + u32 mhz; + int ret; + + ret = device_property_read_u32(dev, "adi,lpf-margin-mhz", &mhz); + if (ret == 0) + st->lpf_margin_hz = (u64)mhz * HZ_PER_MHZ; + else if (ret == -EINVAL) + st->lpf_margin_hz = 0; + else + return ret; + + + ret = device_property_read_u32(dev, "adi,hpf-margin-mhz", &mhz); + if (ret == 0) + st->hpf_margin_hz = (u64)mhz * HZ_PER_MHZ; + else if (ret == -EINVAL) + st->hpf_margin_hz = 0; + else if (ret < 0) + return ret; + + return 0; +} + +static int admv8818_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct regmap *regmap; + struct admv8818_state *st; + struct device *dev = &spi->dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_spi(spi, &admv8818_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + st = iio_priv(indio_dev); + st->regmap = regmap; + + indio_dev->info = &admv8818_info; + indio_dev->name = "admv8818"; + indio_dev->channels = admv8818_channels; + indio_dev->num_channels = ARRAY_SIZE(admv8818_channels); + + st->spi = spi; + + ret = admv8818_clk_setup(st); + if (ret) + return ret; + + mutex_init(&st->lock); + + ret = admv8818_read_properties(st); + if (ret) + return ret; + + ret = admv8818_init(st); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id admv8818_id[] = { + { .name = "admv8818" }, + { } +}; +MODULE_DEVICE_TABLE(spi, admv8818_id); + +static const struct of_device_id admv8818_of_match[] = { + { .compatible = "adi,admv8818" }, + { } +}; +MODULE_DEVICE_TABLE(of, admv8818_of_match); + +static struct spi_driver admv8818_driver = { + .driver = { + .name = "admv8818", + .of_match_table = admv8818_of_match, + }, + .probe = admv8818_probe, + .id_table = admv8818_id, +}; +module_spi_driver(admv8818_driver); + +MODULE_AUTHOR("Antoniu Miclaus + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define SLF3S_CRC8_POLY 0x31 +#define SLF3S_CRC8_INIT 0xff + +#define SLF3S_PRODUCT_ID_LEN 18 +#define SLF3S_PRODUCT_FAMILY_BYTE 1 +#define SLF3S_PRODUCT_SUBTYPE_BYTE 3 +#define SLF3S_PRODUCT_FAMILY_ID 0x03 + +/* Datasheet section 2.2: tPU = 25 ms max from power-on to first cmd. */ +#define SLF3S_POWER_UP_DELAY_US (25 * USEC_PER_MSEC) +/* Datasheet section 2.2: tw = 60 ms typical until first valid sample. */ +#define SLF3S_MEAS_START_DELAY_US (60 * USEC_PER_MSEC) + +static const u8 slf3s_cmd_prep_pid[] = { 0x36, 0x7c }; +static const u8 slf3s_cmd_read_pid[] = { 0xe1, 0x02 }; +static const u8 slf3s_cmd_start_water[] = { 0x36, 0x08 }; +static const u8 slf3s_cmd_start_ipa[] = { 0x36, 0x15 }; +static const u8 slf3s_cmd_stop_meas[] = { 0x3f, 0xf9 }; + +enum slf3s_medium { + SLF3S_MEDIUM_WATER, + SLF3S_MEDIUM_IPA, +}; + +static const char * const slf3s_medium_modes[] = { + [SLF3S_MEDIUM_WATER] = "water", + [SLF3S_MEDIUM_IPA] = "ipa", +}; + +enum slf3s_variant_id { + SLF3S_0600F, + SLF3S_1300F, + SLF3S_4000B, +}; + +/** + * struct slf3s_variant - per-variant calibration constants + * @sub_type: product-info sub-type byte returned by the sensor + * @name: name reported via @iio_dev.name + * @scale: flow scale in l/s per LSB + */ +struct slf3s_variant { + u8 sub_type; + const char *name; + struct s32_fract scale; +}; + +static const struct slf3s_variant slf3s_variants[] = { + [SLF3S_0600F] = { + .sub_type = 0x03, + .name = "slf3s-0600f", + .scale = { .numerator = 1, .denominator = 600 * MICRO }, + }, + [SLF3S_1300F] = { + .sub_type = 0x02, + .name = "slf3s-1300f", + .scale = { .numerator = 1, .denominator = 30 * MICRO }, + }, + [SLF3S_4000B] = { + .sub_type = 0x05, + .name = "slf3s-4000b", + .scale = { .numerator = 1, .denominator = 1920 * MILLI }, + }, +}; + +/** + * struct slf3s_data - per-device state + * @client: I2C client this instance is bound to + * @vdd: supply regulator, disabled while suspended + * @variant: pointer into @slf3s_variants for the detected device + * @medium: currently active calibration medium + * @lock: serialises the multi-step command/response exchanges + * @crc_table: pre-computed CRC-8 lookup table for SLF3S_CRC8_POLY + */ +struct slf3s_data { + struct i2c_client *client; + struct regulator *vdd; + const struct slf3s_variant *variant; + enum slf3s_medium medium; + struct mutex lock; + u8 crc_table[CRC8_TABLE_SIZE]; +}; + +static int slf3s_send_cmd(struct i2c_client *client, const u8 *cmd) +{ + int ret; + + ret = i2c_master_send(client, cmd, 2); + if (ret < 0) + return ret; + if (ret != 2) + return -EIO; + + return 0; +} + +/* Start continuous measurement and wait until the first sample is valid. */ +static int slf3s_start_meas(struct slf3s_data *sf, enum slf3s_medium medium) +{ + const u8 *cmd = (medium == SLF3S_MEDIUM_IPA) ? slf3s_cmd_start_ipa + : slf3s_cmd_start_water; + int ret; + + ret = slf3s_send_cmd(sf->client, cmd); + if (ret) + return ret; + + fsleep(SLF3S_MEAS_START_DELAY_US); + + return 0; +} + +static bool slf3s_crc_valid(const struct slf3s_data *sf, const u8 *block) +{ + return crc8(sf->crc_table, block, 2, SLF3S_CRC8_INIT) == block[2]; +} + +/* + * Read the product-info block and pick the matching variant. The + * sub-type byte returned by the sensor is the source of truth; a + * DT-supplied compatible only seeds an initial guess and is overridden + * on mismatch (with an informational message so misconfigured device + * trees are easy to spot). + * + * Bus / CRC failures are real errors and fail probe. An unknown + * sub-type byte falls back to the variant named in the device tree / + * I2C table, so a drop-in replacement part that lists one of the known + * compatibles keeps working on an older kernel that does not know its + * sub-type yet. Without any match data probe fails since no + * meaningful scale can be published. + */ +static int slf3s_detect_variant(struct slf3s_data *sf) +{ + struct i2c_client *client = sf->client; + u8 buf[SLF3S_PRODUCT_ID_LEN]; + int ret; + + ret = slf3s_send_cmd(client, slf3s_cmd_prep_pid); + if (ret) + return ret; + + ret = slf3s_send_cmd(client, slf3s_cmd_read_pid); + if (ret) + return ret; + + ret = i2c_master_recv(client, buf, sizeof(buf)); + if (ret < 0) + return ret; + if (ret != sizeof(buf)) + return -EIO; + + for (unsigned int i = 0; i < SLF3S_PRODUCT_ID_LEN; i += 3) { + if (!slf3s_crc_valid(sf, &buf[i])) + return -EIO; + } + + if (buf[SLF3S_PRODUCT_FAMILY_BYTE] != SLF3S_PRODUCT_FAMILY_ID) + dev_info(&client->dev, + "unexpected family byte 0x%02x (expected 0x%02x)\n", + buf[SLF3S_PRODUCT_FAMILY_BYTE], + SLF3S_PRODUCT_FAMILY_ID); + + for (unsigned int i = 0; i < ARRAY_SIZE(slf3s_variants); i++) { + if (buf[SLF3S_PRODUCT_SUBTYPE_BYTE] != + slf3s_variants[i].sub_type) + continue; + + if (sf->variant && sf->variant != &slf3s_variants[i]) + dev_info(&client->dev, + "DT compatible says %s but sensor reports %s; using the latter\n", + sf->variant->name, + slf3s_variants[i].name); + + sf->variant = &slf3s_variants[i]; + + return 0; + } + + if (sf->variant) { + dev_warn(&client->dev, + "unknown SLF3S sub-type 0x%02x, assuming %s\n", + buf[SLF3S_PRODUCT_SUBTYPE_BYTE], sf->variant->name); + return 0; + } + + dev_err(&client->dev, "unknown SLF3S sub-type 0x%02x\n", + buf[SLF3S_PRODUCT_SUBTYPE_BYTE]); + + return -ENODEV; +} + +static int slf3s_read_sample(struct slf3s_data *sf, int *flow, int *temp) +{ + /* + * A measurement frame is flow, temperature and a signaling-flags + * word, each followed by a CRC byte. Only flow and temperature are + * used, so the read is stopped after their two words (6 bytes). + */ + u8 buf[6]; + int ret; + + ret = i2c_master_recv(sf->client, buf, sizeof(buf)); + if (ret < 0) + return ret; + if (ret != sizeof(buf)) + return -EIO; + + for (unsigned int i = 0; i < sizeof(buf); i += 3) { + if (!slf3s_crc_valid(sf, &buf[i])) + return -EIO; + } + + *flow = sign_extend32(get_unaligned_be16(&buf[0]), 15); + *temp = sign_extend32(get_unaligned_be16(&buf[3]), 15); + + return 0; +} + +static int slf3s_get_medium(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct slf3s_data *sf = iio_priv(indio_dev); + + return sf->medium; +} + +static int slf3s_set_medium(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int mode) +{ + struct slf3s_data *sf = iio_priv(indio_dev); + int ret; + + guard(mutex)(&sf->lock); + + ret = slf3s_send_cmd(sf->client, slf3s_cmd_stop_meas); + if (ret) + return ret; + + ret = slf3s_start_meas(sf, mode); + if (ret) { + /* + * Try to restart with the previous medium so the sensor is + * not left idle, which would fail all subsequent reads. + */ + if (slf3s_start_meas(sf, sf->medium)) + dev_warn(&sf->client->dev, + "failed to restart measurement, reads will fail until a medium is set\n"); + return ret; + } + + sf->medium = mode; + + return 0; +} + +static const struct iio_enum slf3s_medium_enum = { + .items = slf3s_medium_modes, + .num_items = ARRAY_SIZE(slf3s_medium_modes), + .get = slf3s_get_medium, + .set = slf3s_set_medium, +}; + +static const struct iio_chan_spec_ext_info slf3s_ext_info[] = { + IIO_ENUM("medium", IIO_SHARED_BY_TYPE, &slf3s_medium_enum), + IIO_ENUM_AVAILABLE("medium", IIO_SHARED_BY_TYPE, &slf3s_medium_enum), + { } +}; + +static const struct iio_chan_spec slf3s_channels[] = { + { + .type = IIO_VOLUMEFLOW, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .ext_info = slf3s_ext_info, + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static int slf3s_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct slf3s_data *sf = iio_priv(indio_dev); + int flow, temp, ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + scoped_guard(mutex, &sf->lock) + ret = slf3s_read_sample(sf, &flow, &temp); + if (ret) + return ret; + + *val = (chan->type == IIO_VOLUMEFLOW) ? flow : temp; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_VOLUMEFLOW) { + /* + * The variant scale is the flow per LSB in l/s, but + * IIO reports volume flow in m^3/s (1 l = 1e-3 m^3). + * These values are tiny (~1.67e-12 m^3/s for the + * SLF3S-0600F), so emit a 64-bit fixed-point value with + * femto (1e-15) resolution to preserve precision. + * Converting l/s to m^3/s (/ MILLI) and scaling to femto + * (* FEMTO) leaves a net * (FEMTO / MILLI) factor. + */ + const struct slf3s_variant *v = sf->variant; + s64 num = (s64)v->scale.numerator * (FEMTO / MILLI); + s64 scale = DIV_S64_ROUND_CLOSEST(num, + v->scale.denominator); + + iio_val_s64_decompose(scale, val, val2); + + return IIO_VAL_DECIMAL64_FEMTO; + } + /* Temperature LSB = 1/200 degC; IIO_TEMP wants milli-degC. */ + *val = MILLIDEGREE_PER_DEGREE / 200; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static const struct iio_info slf3s_info = { + .read_raw = slf3s_read_raw, +}; + +static void slf3s_stop_meas(void *data) +{ + struct slf3s_data *sf = data; + + slf3s_send_cmd(sf->client, slf3s_cmd_stop_meas); +} + +static void slf3s_disable_vdd(void *data) +{ + struct slf3s_data *sf = data; + + regulator_disable(sf->vdd); +} + +static int slf3s_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct slf3s_data *sf; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*sf)); + if (!indio_dev) + return -ENOMEM; + + sf = iio_priv(indio_dev); + sf->client = client; + i2c_set_clientdata(client, indio_dev); + sf->variant = i2c_get_match_data(client); + sf->medium = SLF3S_MEDIUM_WATER; + crc8_populate_msb(sf->crc_table, SLF3S_CRC8_POLY); + + ret = devm_mutex_init(dev, &sf->lock); + if (ret) + return ret; + + sf->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(sf->vdd)) + return dev_err_probe(dev, PTR_ERR(sf->vdd), + "failed to get vdd supply\n"); + + ret = regulator_enable(sf->vdd); + if (ret) + return dev_err_probe(dev, ret, "failed to enable vdd supply\n"); + + ret = devm_add_action_or_reset(dev, slf3s_disable_vdd, sf); + if (ret) + return ret; + + fsleep(SLF3S_POWER_UP_DELAY_US); + + /* + * The sensor may still be in continuous measurement mode from a + * previous boot (warm reboot / kexec); in that case it would NACK + * the product-id command below. Stop it first and ignore the error + * if it was already idle. + */ + slf3s_send_cmd(client, slf3s_cmd_stop_meas); + + ret = slf3s_detect_variant(sf); + if (ret) + return dev_err_probe(dev, ret, "product info read failed\n"); + + ret = slf3s_start_meas(sf, sf->medium); + if (ret) + return dev_err_probe(dev, ret, + "failed to start measurement\n"); + + ret = devm_add_action_or_reset(dev, slf3s_stop_meas, sf); + if (ret) + return ret; + + indio_dev->name = sf->variant->name; + indio_dev->channels = slf3s_channels; + indio_dev->num_channels = ARRAY_SIZE(slf3s_channels); + indio_dev->info = &slf3s_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + return devm_iio_device_register(dev, indio_dev); +} + +/* + * The sensor has no low-power state of its own, so stop the measurement + * and cut the supply while suspended. Resume powers it back up, waits + * out the power-up time and restarts with the medium that was active + * before. + */ +static int slf3s_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct slf3s_data *sf = iio_priv(indio_dev); + int ret; + + guard(mutex)(&sf->lock); + + ret = slf3s_send_cmd(sf->client, slf3s_cmd_stop_meas); + if (ret) + return ret; + + return regulator_disable(sf->vdd); +} + +static int slf3s_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct slf3s_data *sf = iio_priv(indio_dev); + int ret; + + guard(mutex)(&sf->lock); + + ret = regulator_enable(sf->vdd); + if (ret) + return ret; + + fsleep(SLF3S_POWER_UP_DELAY_US); + + return slf3s_start_meas(sf, sf->medium); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(slf3s_pm_ops, slf3s_suspend, slf3s_resume); + +static const struct i2c_device_id slf3s_id[] = { + { + .name = "slf3s-0600f", + .driver_data = (kernel_ulong_t)&slf3s_variants[SLF3S_0600F], + }, + { + .name = "slf3s-1300f", + .driver_data = (kernel_ulong_t)&slf3s_variants[SLF3S_1300F], + }, + { + .name = "slf3s-4000b", + .driver_data = (kernel_ulong_t)&slf3s_variants[SLF3S_4000B], + }, + { } +}; +MODULE_DEVICE_TABLE(i2c, slf3s_id); + +static const struct of_device_id slf3s_of_match[] = { + { + .compatible = "sensirion,slf3s-0600f", + .data = &slf3s_variants[SLF3S_0600F], + }, + { + .compatible = "sensirion,slf3s-1300f", + .data = &slf3s_variants[SLF3S_1300F], + }, + { + .compatible = "sensirion,slf3s-4000b", + .data = &slf3s_variants[SLF3S_4000B], + }, + { } +}; +MODULE_DEVICE_TABLE(of, slf3s_of_match); + +static struct i2c_driver slf3s_driver = { + .driver = { + .name = "slf3s", + .of_match_table = slf3s_of_match, + .pm = pm_sleep_ptr(&slf3s_pm_ops), + }, + .probe = slf3s_probe, + .id_table = slf3s_id, +}; +module_i2c_driver(slf3s_driver); + +MODULE_AUTHOR("Wadim Mueller "); +MODULE_DESCRIPTION("Sensirion SLF3S liquid flow sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/frequency/Kconfig b/drivers/iio/frequency/Kconfig new file mode 100644 index 000000000..90c6304c4 --- /dev/null +++ b/drivers/iio/frequency/Kconfig @@ -0,0 +1,127 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Frequency +# Direct Digital Synthesis drivers (DDS) +# Clock Distribution device drivers +# Phase-Locked Loop (PLL) frequency synthesizers +# +# When adding new entries keep the list in alphabetical order + +menu "Frequency Synthesizers DDS/PLL" + +menu "Clock Generator/Distribution" + +config AD9523 + tristate "Analog Devices AD9523 Low Jitter Clock Generator" + depends on SPI + help + Say yes here to build support for Analog Devices AD9523 Low Jitter + Clock Generator. The driver provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called ad9523. + +endmenu + +# +# Phase-Locked Loop (PLL) frequency synthesizers +# + +menu "Phase-Locked Loop (PLL) frequency synthesizers" + +config ADF41513 + tristate "Analog Devices ADF41513 PLL Frequency Synthesizer" + depends on SPI + help + Say yes here to build support for Analog Devices ADF41513 + 26.5 GHz Integer-N/Fractional-N PLL Frequency Synthesizer. + + To compile this driver as a module, choose M here: the + module will be called adf41513. + +config ADF4350 + tristate "Analog Devices ADF4350/ADF4351 Wideband Synthesizers" + depends on SPI + help + Say yes here to build support for Analog Devices ADF4350/ADF4351 + Wideband Synthesizers. The driver provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called adf4350. + +config ADF4371 + tristate "Analog Devices ADF4371/ADF4372 Wideband Synthesizers" + depends on SPI + select REGMAP_SPI + help + Say yes here to build support for Analog Devices ADF4371 and ADF4372 + Wideband Synthesizers. The driver provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called adf4371. + +config ADF4377 + tristate "Analog Devices ADF4377 Microwave Wideband Synthesizer" + depends on SPI && COMMON_CLK + select REGMAP_SPI + help + Say yes here to build support for Analog Devices ADF4377 Microwave + Wideband Synthesizer. + + To compile this driver as a module, choose M here: the + module will be called adf4377. + +config ADMFM2000 + tristate "Analog Devices ADMFM2000 Dual Microwave Down Converter" + depends on GPIOLIB + help + Say yes here to build support for Analog Devices ADMFM2000 Dual + Microwave Down Converter. + + To compile this driver as a module, choose M here: the + module will be called admfm2000. + +config ADMV1013 + tristate "Analog Devices ADMV1013 Microwave Upconverter" + depends on SPI && COMMON_CLK + help + Say yes here to build support for Analog Devices ADMV1013 + 24 GHz to 44 GHz, Wideband, Microwave Upconverter. + + To compile this driver as a module, choose M here: the + module will be called admv1013. + +config ADMV1014 + tristate "Analog Devices ADMV1014 Microwave Downconverter" + depends on SPI && COMMON_CLK && 64BIT + help + Say yes here to build support for Analog Devices ADMV1014 + 24 GHz to 44 GHz, Wideband, Microwave Downconverter. + + To compile this driver as a module, choose M here: the + module will be called admv1014. + +config ADMV4420 + tristate "Analog Devices ADMV4420 K Band Downconverter" + depends on SPI + select REGMAP_SPI + help + Say yes here to build support for Analog Devices K Band + Downconverter with integrated Fractional-N PLL and VCO. + + To compile this driver as a module, choose M here: the + module will be called admv4420. + +config ADRF6780 + tristate "Analog Devices ADRF6780 Microwave Upconverter" + depends on SPI + depends on COMMON_CLK + help + Say yes here to build support for Analog Devices ADRF6780 + 5.9 GHz to 23.6 GHz, Wideband, Microwave Upconverter. + + To compile this driver as a module, choose M here: the + module will be called adrf6780. + +endmenu +endmenu diff --git a/drivers/iio/frequency/Makefile b/drivers/iio/frequency/Makefile new file mode 100644 index 000000000..53b4d0141 --- /dev/null +++ b/drivers/iio/frequency/Makefile @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile iio/frequency +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_AD9523) += ad9523.o +obj-$(CONFIG_ADF41513) += adf41513.o +obj-$(CONFIG_ADF4350) += adf4350.o +obj-$(CONFIG_ADF4371) += adf4371.o +obj-$(CONFIG_ADF4377) += adf4377.o +obj-$(CONFIG_ADMFM2000) += admfm2000.o +obj-$(CONFIG_ADMV1013) += admv1013.o +obj-$(CONFIG_ADMV1014) += admv1014.o +obj-$(CONFIG_ADMV4420) += admv4420.o +obj-$(CONFIG_ADRF6780) += adrf6780.o diff --git a/drivers/iio/frequency/ad9523.c b/drivers/iio/frequency/ad9523.c new file mode 100644 index 000000000..1e393f40a --- /dev/null +++ b/drivers/iio/frequency/ad9523.c @@ -0,0 +1,1027 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD9523 SPI Low Jitter Clock Generator + * + * Copyright 2012 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define AD9523_READ (1 << 15) +#define AD9523_WRITE (0 << 15) +#define AD9523_CNT(x) (((x) - 1) << 13) +#define AD9523_ADDR(x) ((x) & 0xFFF) + +#define AD9523_R1B (1 << 16) +#define AD9523_R2B (2 << 16) +#define AD9523_R3B (3 << 16) +#define AD9523_TRANSF_LEN(x) ((x) >> 16) + +#define AD9523_SERIAL_PORT_CONFIG (AD9523_R1B | 0x0) +#define AD9523_VERSION_REGISTER (AD9523_R1B | 0x2) +#define AD9523_PART_REGISTER (AD9523_R1B | 0x3) +#define AD9523_READBACK_CTRL (AD9523_R1B | 0x4) + +#define AD9523_EEPROM_CUSTOMER_VERSION_ID (AD9523_R2B | 0x6) + +#define AD9523_PLL1_REF_A_DIVIDER (AD9523_R2B | 0x11) +#define AD9523_PLL1_REF_B_DIVIDER (AD9523_R2B | 0x13) +#define AD9523_PLL1_REF_TEST_DIVIDER (AD9523_R1B | 0x14) +#define AD9523_PLL1_FEEDBACK_DIVIDER (AD9523_R2B | 0x17) +#define AD9523_PLL1_CHARGE_PUMP_CTRL (AD9523_R2B | 0x19) +#define AD9523_PLL1_INPUT_RECEIVERS_CTRL (AD9523_R1B | 0x1A) +#define AD9523_PLL1_REF_CTRL (AD9523_R1B | 0x1B) +#define AD9523_PLL1_MISC_CTRL (AD9523_R1B | 0x1C) +#define AD9523_PLL1_LOOP_FILTER_CTRL (AD9523_R1B | 0x1D) + +#define AD9523_PLL2_CHARGE_PUMP (AD9523_R1B | 0xF0) +#define AD9523_PLL2_FEEDBACK_DIVIDER_AB (AD9523_R1B | 0xF1) +#define AD9523_PLL2_CTRL (AD9523_R1B | 0xF2) +#define AD9523_PLL2_VCO_CTRL (AD9523_R1B | 0xF3) +#define AD9523_PLL2_VCO_DIVIDER (AD9523_R1B | 0xF4) +#define AD9523_PLL2_LOOP_FILTER_CTRL (AD9523_R2B | 0xF6) +#define AD9523_PLL2_R2_DIVIDER (AD9523_R1B | 0xF7) + +#define AD9523_CHANNEL_CLOCK_DIST(ch) (AD9523_R3B | (0x192 + 3 * ch)) + +#define AD9523_PLL1_OUTPUT_CTRL (AD9523_R1B | 0x1BA) +#define AD9523_PLL1_OUTPUT_CHANNEL_CTRL (AD9523_R1B | 0x1BB) + +#define AD9523_READBACK_0 (AD9523_R1B | 0x22C) +#define AD9523_READBACK_1 (AD9523_R1B | 0x22D) + +#define AD9523_STATUS_SIGNALS (AD9523_R3B | 0x232) +#define AD9523_POWER_DOWN_CTRL (AD9523_R1B | 0x233) +#define AD9523_IO_UPDATE (AD9523_R1B | 0x234) + +#define AD9523_EEPROM_DATA_XFER_STATUS (AD9523_R1B | 0xB00) +#define AD9523_EEPROM_ERROR_READBACK (AD9523_R1B | 0xB01) +#define AD9523_EEPROM_CTRL1 (AD9523_R1B | 0xB02) +#define AD9523_EEPROM_CTRL2 (AD9523_R1B | 0xB03) + +/* AD9523_SERIAL_PORT_CONFIG */ + +#define AD9523_SER_CONF_SDO_ACTIVE (1 << 7) +#define AD9523_SER_CONF_SOFT_RESET (1 << 5) + +/* AD9523_READBACK_CTRL */ +#define AD9523_READBACK_CTRL_READ_BUFFERED (1 << 0) + +/* AD9523_PLL1_CHARGE_PUMP_CTRL */ +#define AD9523_PLL1_CHARGE_PUMP_CURRENT_nA(x) (((x) / 500) & 0x7F) +#define AD9523_PLL1_CHARGE_PUMP_TRISTATE (1 << 7) +#define AD9523_PLL1_CHARGE_PUMP_MODE_NORMAL (3 << 8) +#define AD9523_PLL1_CHARGE_PUMP_MODE_PUMP_DOWN (2 << 8) +#define AD9523_PLL1_CHARGE_PUMP_MODE_PUMP_UP (1 << 8) +#define AD9523_PLL1_CHARGE_PUMP_MODE_TRISTATE (0 << 8) +#define AD9523_PLL1_BACKLASH_PW_MIN (0 << 10) +#define AD9523_PLL1_BACKLASH_PW_LOW (1 << 10) +#define AD9523_PLL1_BACKLASH_PW_HIGH (2 << 10) +#define AD9523_PLL1_BACKLASH_PW_MAX (3 << 10) + +/* AD9523_PLL1_INPUT_RECEIVERS_CTRL */ +#define AD9523_PLL1_REF_TEST_RCV_EN (1 << 7) +#define AD9523_PLL1_REFB_DIFF_RCV_EN (1 << 6) +#define AD9523_PLL1_REFA_DIFF_RCV_EN (1 << 5) +#define AD9523_PLL1_REFB_RCV_EN (1 << 4) +#define AD9523_PLL1_REFA_RCV_EN (1 << 3) +#define AD9523_PLL1_REFA_REFB_PWR_CTRL_EN (1 << 2) +#define AD9523_PLL1_OSC_IN_CMOS_NEG_INP_EN (1 << 1) +#define AD9523_PLL1_OSC_IN_DIFF_EN (1 << 0) + +/* AD9523_PLL1_REF_CTRL */ +#define AD9523_PLL1_BYPASS_REF_TEST_DIV_EN (1 << 7) +#define AD9523_PLL1_BYPASS_FEEDBACK_DIV_EN (1 << 6) +#define AD9523_PLL1_ZERO_DELAY_MODE_INT (1 << 5) +#define AD9523_PLL1_ZERO_DELAY_MODE_EXT (0 << 5) +#define AD9523_PLL1_OSC_IN_PLL_FEEDBACK_EN (1 << 4) +#define AD9523_PLL1_ZD_IN_CMOS_NEG_INP_EN (1 << 3) +#define AD9523_PLL1_ZD_IN_DIFF_EN (1 << 2) +#define AD9523_PLL1_REFB_CMOS_NEG_INP_EN (1 << 1) +#define AD9523_PLL1_REFA_CMOS_NEG_INP_EN (1 << 0) + +/* AD9523_PLL1_MISC_CTRL */ +#define AD9523_PLL1_REFB_INDEP_DIV_CTRL_EN (1 << 7) +#define AD9523_PLL1_OSC_CTRL_FAIL_VCC_BY2_EN (1 << 6) +#define AD9523_PLL1_REF_MODE(x) ((x) << 2) +#define AD9523_PLL1_BYPASS_REFB_DIV (1 << 1) +#define AD9523_PLL1_BYPASS_REFA_DIV (1 << 0) + +/* AD9523_PLL1_LOOP_FILTER_CTRL */ +#define AD9523_PLL1_LOOP_FILTER_RZERO(x) ((x) & 0xF) + +/* AD9523_PLL2_CHARGE_PUMP */ +#define AD9523_PLL2_CHARGE_PUMP_CURRENT_nA(x) ((x) / 3500) + +/* AD9523_PLL2_FEEDBACK_DIVIDER_AB */ +#define AD9523_PLL2_FB_NDIV_A_CNT(x) (((x) & 0x3) << 6) +#define AD9523_PLL2_FB_NDIV_B_CNT(x) (((x) & 0x3F) << 0) +#define AD9523_PLL2_FB_NDIV(a, b) (4 * (b) + (a)) + +/* AD9523_PLL2_CTRL */ +#define AD9523_PLL2_CHARGE_PUMP_MODE_NORMAL (3 << 0) +#define AD9523_PLL2_CHARGE_PUMP_MODE_PUMP_DOWN (2 << 0) +#define AD9523_PLL2_CHARGE_PUMP_MODE_PUMP_UP (1 << 0) +#define AD9523_PLL2_CHARGE_PUMP_MODE_TRISTATE (0 << 0) +#define AD9523_PLL2_BACKLASH_PW_MIN (0 << 2) +#define AD9523_PLL2_BACKLASH_PW_LOW (1 << 2) +#define AD9523_PLL2_BACKLASH_PW_HIGH (2 << 2) +#define AD9523_PLL2_BACKLASH_PW_MAX (3 << 1) +#define AD9523_PLL2_BACKLASH_CTRL_EN (1 << 4) +#define AD9523_PLL2_FREQ_DOUBLER_EN (1 << 5) +#define AD9523_PLL2_LOCK_DETECT_PWR_DOWN_EN (1 << 7) + +/* AD9523_PLL2_VCO_CTRL */ +#define AD9523_PLL2_VCO_CALIBRATE (1 << 1) +#define AD9523_PLL2_FORCE_VCO_MIDSCALE (1 << 2) +#define AD9523_PLL2_FORCE_REFERENCE_VALID (1 << 3) +#define AD9523_PLL2_FORCE_RELEASE_SYNC (1 << 4) + +/* AD9523_PLL2_VCO_DIVIDER */ +#define AD9523_PLL2_VCO_DIV_M1(x) ((((x) - 3) & 0x3) << 0) +#define AD9523_PLL2_VCO_DIV_M2(x) ((((x) - 3) & 0x3) << 4) +#define AD9523_PLL2_VCO_DIV_M1_PWR_DOWN_EN (1 << 2) +#define AD9523_PLL2_VCO_DIV_M2_PWR_DOWN_EN (1 << 6) + +/* AD9523_PLL2_LOOP_FILTER_CTRL */ +#define AD9523_PLL2_LOOP_FILTER_CPOLE1(x) (((x) & 0x7) << 0) +#define AD9523_PLL2_LOOP_FILTER_RZERO(x) (((x) & 0x7) << 3) +#define AD9523_PLL2_LOOP_FILTER_RPOLE2(x) (((x) & 0x7) << 6) +#define AD9523_PLL2_LOOP_FILTER_RZERO_BYPASS_EN (1 << 8) + +/* AD9523_PLL2_R2_DIVIDER */ +#define AD9523_PLL2_R2_DIVIDER_VAL(x) (((x) & 0x1F) << 0) + +/* AD9523_CHANNEL_CLOCK_DIST */ +#define AD9523_CLK_DIST_DIV_PHASE(x) (((x) & 0x3F) << 18) +#define AD9523_CLK_DIST_DIV_PHASE_REV(x) (((x) >> 18) & 0x3F) +#define AD9523_CLK_DIST_DIV(x) ((((x) - 1) & 0x3FF) << 8) +#define AD9523_CLK_DIST_DIV_REV(x) ((((x) >> 8) & 0x3FF) + 1) +#define AD9523_CLK_DIST_INV_DIV_OUTPUT_EN (1 << 7) +#define AD9523_CLK_DIST_IGNORE_SYNC_EN (1 << 6) +#define AD9523_CLK_DIST_PWR_DOWN_EN (1 << 5) +#define AD9523_CLK_DIST_LOW_PWR_MODE_EN (1 << 4) +#define AD9523_CLK_DIST_DRIVER_MODE(x) (((x) & 0xF) << 0) + +/* AD9523_PLL1_OUTPUT_CTRL */ +#define AD9523_PLL1_OUTP_CTRL_VCO_DIV_SEL_CH6_M2 (1 << 7) +#define AD9523_PLL1_OUTP_CTRL_VCO_DIV_SEL_CH5_M2 (1 << 6) +#define AD9523_PLL1_OUTP_CTRL_VCO_DIV_SEL_CH4_M2 (1 << 5) +#define AD9523_PLL1_OUTP_CTRL_CMOS_DRV_WEAK (1 << 4) +#define AD9523_PLL1_OUTP_CTRL_OUTPUT_DIV_1 (0 << 0) +#define AD9523_PLL1_OUTP_CTRL_OUTPUT_DIV_2 (1 << 0) +#define AD9523_PLL1_OUTP_CTRL_OUTPUT_DIV_4 (2 << 0) +#define AD9523_PLL1_OUTP_CTRL_OUTPUT_DIV_8 (4 << 0) +#define AD9523_PLL1_OUTP_CTRL_OUTPUT_DIV_16 (8 << 0) + +/* AD9523_PLL1_OUTPUT_CHANNEL_CTRL */ +#define AD9523_PLL1_OUTP_CH_CTRL_OUTPUT_PWR_DOWN_EN (1 << 7) +#define AD9523_PLL1_OUTP_CH_CTRL_VCO_DIV_SEL_CH9_M2 (1 << 6) +#define AD9523_PLL1_OUTP_CH_CTRL_VCO_DIV_SEL_CH8_M2 (1 << 5) +#define AD9523_PLL1_OUTP_CH_CTRL_VCO_DIV_SEL_CH7_M2 (1 << 4) +#define AD9523_PLL1_OUTP_CH_CTRL_VCXO_SRC_SEL_CH3 (1 << 3) +#define AD9523_PLL1_OUTP_CH_CTRL_VCXO_SRC_SEL_CH2 (1 << 2) +#define AD9523_PLL1_OUTP_CH_CTRL_VCXO_SRC_SEL_CH1 (1 << 1) +#define AD9523_PLL1_OUTP_CH_CTRL_VCXO_SRC_SEL_CH0 (1 << 0) + +/* AD9523_READBACK_0 */ +#define AD9523_READBACK_0_STAT_PLL2_REF_CLK (1 << 7) +#define AD9523_READBACK_0_STAT_PLL2_FB_CLK (1 << 6) +#define AD9523_READBACK_0_STAT_VCXO (1 << 5) +#define AD9523_READBACK_0_STAT_REF_TEST (1 << 4) +#define AD9523_READBACK_0_STAT_REFB (1 << 3) +#define AD9523_READBACK_0_STAT_REFA (1 << 2) +#define AD9523_READBACK_0_STAT_PLL2_LD (1 << 1) +#define AD9523_READBACK_0_STAT_PLL1_LD (1 << 0) + +/* AD9523_READBACK_1 */ +#define AD9523_READBACK_1_HOLDOVER_ACTIVE (1 << 3) +#define AD9523_READBACK_1_AUTOMODE_SEL_REFB (1 << 2) +#define AD9523_READBACK_1_VCO_CALIB_IN_PROGRESS (1 << 0) + +/* AD9523_STATUS_SIGNALS */ +#define AD9523_STATUS_SIGNALS_SYNC_MAN_CTRL (1 << 16) +#define AD9523_STATUS_MONITOR_01_PLL12_LOCKED (0x302) +/* AD9523_POWER_DOWN_CTRL */ +#define AD9523_POWER_DOWN_CTRL_PLL1_PWR_DOWN (1 << 2) +#define AD9523_POWER_DOWN_CTRL_PLL2_PWR_DOWN (1 << 1) +#define AD9523_POWER_DOWN_CTRL_DIST_PWR_DOWN (1 << 0) + +/* AD9523_IO_UPDATE */ +#define AD9523_IO_UPDATE_EN (1 << 0) + +/* AD9523_EEPROM_DATA_XFER_STATUS */ +#define AD9523_EEPROM_DATA_XFER_IN_PROGRESS (1 << 0) + +/* AD9523_EEPROM_ERROR_READBACK */ +#define AD9523_EEPROM_ERROR_READBACK_FAIL (1 << 0) + +/* AD9523_EEPROM_CTRL1 */ +#define AD9523_EEPROM_CTRL1_SOFT_EEPROM (1 << 1) +#define AD9523_EEPROM_CTRL1_EEPROM_WRITE_PROT_DIS (1 << 0) + +/* AD9523_EEPROM_CTRL2 */ +#define AD9523_EEPROM_CTRL2_REG2EEPROM (1 << 0) + +#define AD9523_NUM_CHAN 14 +#define AD9523_NUM_CHAN_ALT_CLK_SRC 10 + +/* Helpers to avoid excess line breaks */ +#define AD_IFE(_pde, _a, _b) ((pdata->_pde) ? _a : _b) +#define AD_IF(_pde, _a) AD_IFE(_pde, _a, 0) + +enum { + AD9523_STAT_PLL1_LD, + AD9523_STAT_PLL2_LD, + AD9523_STAT_REFA, + AD9523_STAT_REFB, + AD9523_STAT_REF_TEST, + AD9523_STAT_VCXO, + AD9523_STAT_PLL2_FB_CLK, + AD9523_STAT_PLL2_REF_CLK, + AD9523_SYNC, + AD9523_EEPROM, +}; + +enum { + AD9523_VCO1, + AD9523_VCO2, + AD9523_VCXO, + AD9523_NUM_CLK_SRC, +}; + +struct ad9523_state { + struct spi_device *spi; + struct ad9523_platform_data *pdata; + struct iio_chan_spec ad9523_channels[AD9523_NUM_CHAN]; + struct gpio_desc *pwrdown_gpio; + struct gpio_desc *reset_gpio; + struct gpio_desc *sync_gpio; + + unsigned long vcxo_freq; + unsigned long vco_freq; + unsigned long vco_out_freq[AD9523_NUM_CLK_SRC]; + unsigned char vco_out_map[AD9523_NUM_CHAN_ALT_CLK_SRC]; + + /* + * Lock for accessing device registers. Some operations require + * multiple consecutive R/W operations, during which the device + * shouldn't be interrupted. The buffers are also shared across + * all operations so need to be protected on stand alone reads and + * writes. + */ + struct mutex lock; + + /* + * DMA (thus cache coherency maintenance) may require that + * transfer buffers live in their own cache lines. + */ + union { + __be32 d32; + u8 d8[4]; + } data[2] __aligned(IIO_DMA_MINALIGN); +}; + +static int ad9523_read(struct iio_dev *indio_dev, unsigned int addr) +{ + struct ad9523_state *st = iio_priv(indio_dev); + int ret; + + /* We encode the register size 1..3 bytes into the register address. + * On transfer we get the size from the register datum, and make sure + * the result is properly aligned. + */ + + struct spi_transfer t[] = { + { + .tx_buf = &st->data[0].d8[2], + .len = 2, + }, { + .rx_buf = &st->data[1].d8[4 - AD9523_TRANSF_LEN(addr)], + .len = AD9523_TRANSF_LEN(addr), + }, + }; + + st->data[0].d32 = cpu_to_be32(AD9523_READ | + AD9523_CNT(AD9523_TRANSF_LEN(addr)) | + AD9523_ADDR(addr)); + + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); + if (ret < 0) + dev_err(&indio_dev->dev, "read failed (%d)", ret); + else + ret = be32_to_cpu(st->data[1].d32) & (0xFFFFFF >> + (8 * (3 - AD9523_TRANSF_LEN(addr)))); + + return ret; +}; + +static int ad9523_write(struct iio_dev *indio_dev, + unsigned int addr, unsigned int val) +{ + struct ad9523_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer t[] = { + { + .tx_buf = &st->data[0].d8[2], + .len = 2, + }, { + .tx_buf = &st->data[1].d8[4 - AD9523_TRANSF_LEN(addr)], + .len = AD9523_TRANSF_LEN(addr), + }, + }; + + st->data[0].d32 = cpu_to_be32(AD9523_WRITE | + AD9523_CNT(AD9523_TRANSF_LEN(addr)) | + AD9523_ADDR(addr)); + st->data[1].d32 = cpu_to_be32(val); + + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); + + if (ret < 0) + dev_err(&indio_dev->dev, "write failed (%d)", ret); + + return ret; +} + +static int ad9523_io_update(struct iio_dev *indio_dev) +{ + return ad9523_write(indio_dev, AD9523_IO_UPDATE, AD9523_IO_UPDATE_EN); +} + +static int ad9523_vco_out_map(struct iio_dev *indio_dev, + unsigned int ch, unsigned int out) +{ + struct ad9523_state *st = iio_priv(indio_dev); + int ret; + unsigned int mask; + + switch (ch) { + case 0 ... 3: + ret = ad9523_read(indio_dev, AD9523_PLL1_OUTPUT_CHANNEL_CTRL); + if (ret < 0) + break; + mask = AD9523_PLL1_OUTP_CH_CTRL_VCXO_SRC_SEL_CH0 << ch; + if (out) { + ret |= mask; + out = 2; + } else { + ret &= ~mask; + } + ret = ad9523_write(indio_dev, + AD9523_PLL1_OUTPUT_CHANNEL_CTRL, ret); + break; + case 4 ... 6: + ret = ad9523_read(indio_dev, AD9523_PLL1_OUTPUT_CTRL); + if (ret < 0) + break; + mask = AD9523_PLL1_OUTP_CTRL_VCO_DIV_SEL_CH4_M2 << (ch - 4); + if (out) + ret |= mask; + else + ret &= ~mask; + ret = ad9523_write(indio_dev, AD9523_PLL1_OUTPUT_CTRL, ret); + break; + case 7 ... 9: + ret = ad9523_read(indio_dev, AD9523_PLL1_OUTPUT_CHANNEL_CTRL); + if (ret < 0) + break; + mask = AD9523_PLL1_OUTP_CH_CTRL_VCO_DIV_SEL_CH7_M2 << (ch - 7); + if (out) + ret |= mask; + else + ret &= ~mask; + ret = ad9523_write(indio_dev, + AD9523_PLL1_OUTPUT_CHANNEL_CTRL, ret); + break; + default: + return 0; + } + + st->vco_out_map[ch] = out; + + return ret; +} + +static int ad9523_set_clock_provider(struct iio_dev *indio_dev, + unsigned int ch, unsigned long freq) +{ + struct ad9523_state *st = iio_priv(indio_dev); + long tmp1, tmp2; + bool use_alt_clk_src; + + switch (ch) { + case 0 ... 3: + use_alt_clk_src = (freq == st->vco_out_freq[AD9523_VCXO]); + break; + case 4 ... 9: + tmp1 = st->vco_out_freq[AD9523_VCO1] / freq; + tmp2 = st->vco_out_freq[AD9523_VCO2] / freq; + tmp1 *= freq; + tmp2 *= freq; + use_alt_clk_src = (abs(tmp1 - freq) > abs(tmp2 - freq)); + break; + default: + /* Ch 10..14: No action required, return success */ + return 0; + } + + return ad9523_vco_out_map(indio_dev, ch, use_alt_clk_src); +} + +static int ad9523_store_eeprom(struct iio_dev *indio_dev) +{ + int ret, tmp; + + ret = ad9523_write(indio_dev, AD9523_EEPROM_CTRL1, + AD9523_EEPROM_CTRL1_EEPROM_WRITE_PROT_DIS); + if (ret < 0) + return ret; + ret = ad9523_write(indio_dev, AD9523_EEPROM_CTRL2, + AD9523_EEPROM_CTRL2_REG2EEPROM); + if (ret < 0) + return ret; + + tmp = 4; + do { + msleep(20); + ret = ad9523_read(indio_dev, + AD9523_EEPROM_DATA_XFER_STATUS); + if (ret < 0) + return ret; + } while ((ret & AD9523_EEPROM_DATA_XFER_IN_PROGRESS) && tmp--); + + ret = ad9523_write(indio_dev, AD9523_EEPROM_CTRL1, 0); + if (ret < 0) + return ret; + + ret = ad9523_read(indio_dev, AD9523_EEPROM_ERROR_READBACK); + if (ret < 0) + return ret; + + if (ret & AD9523_EEPROM_ERROR_READBACK_FAIL) { + dev_err(&indio_dev->dev, "Verify EEPROM failed"); + ret = -EIO; + } + + return ret; +} + +static int ad9523_sync(struct iio_dev *indio_dev) +{ + int ret, tmp; + + ret = ad9523_read(indio_dev, AD9523_STATUS_SIGNALS); + if (ret < 0) + return ret; + + tmp = ret; + tmp |= AD9523_STATUS_SIGNALS_SYNC_MAN_CTRL; + + ret = ad9523_write(indio_dev, AD9523_STATUS_SIGNALS, tmp); + if (ret < 0) + return ret; + + ad9523_io_update(indio_dev); + tmp &= ~AD9523_STATUS_SIGNALS_SYNC_MAN_CTRL; + + ret = ad9523_write(indio_dev, AD9523_STATUS_SIGNALS, tmp); + if (ret < 0) + return ret; + + return ad9523_io_update(indio_dev); +} + +static ssize_t ad9523_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct ad9523_state *st = iio_priv(indio_dev); + bool state; + int ret; + + ret = kstrtobool(buf, &state); + if (ret < 0) + return ret; + + if (!state) + return len; + + mutex_lock(&st->lock); + switch ((u32)this_attr->address) { + case AD9523_SYNC: + ret = ad9523_sync(indio_dev); + break; + case AD9523_EEPROM: + ret = ad9523_store_eeprom(indio_dev); + break; + default: + ret = -ENODEV; + } + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +static ssize_t ad9523_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct ad9523_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->lock); + ret = ad9523_read(indio_dev, AD9523_READBACK_0); + if (ret >= 0) { + ret = sysfs_emit(buf, "%d\n", !!(ret & (1 << + (u32)this_attr->address))); + } + mutex_unlock(&st->lock); + + return ret; +} + +static IIO_DEVICE_ATTR(pll1_locked, 0444, ad9523_show, NULL, + AD9523_STAT_PLL1_LD); + +static IIO_DEVICE_ATTR(pll2_locked, 0444, ad9523_show, NULL, + AD9523_STAT_PLL2_LD); + +static IIO_DEVICE_ATTR(pll1_reference_clk_a_present, 0444, ad9523_show, NULL, + AD9523_STAT_REFA); + +static IIO_DEVICE_ATTR(pll1_reference_clk_b_present, 0444, ad9523_show, NULL, + AD9523_STAT_REFB); + +static IIO_DEVICE_ATTR(pll1_reference_clk_test_present, 0444, ad9523_show, NULL, + AD9523_STAT_REF_TEST); + +static IIO_DEVICE_ATTR(vcxo_clk_present, 0444, ad9523_show, NULL, + AD9523_STAT_VCXO); + +static IIO_DEVICE_ATTR(pll2_feedback_clk_present, 0444, ad9523_show, NULL, + AD9523_STAT_PLL2_FB_CLK); + +static IIO_DEVICE_ATTR(pll2_reference_clk_present, 0444, ad9523_show, NULL, + AD9523_STAT_PLL2_REF_CLK); + +static IIO_DEVICE_ATTR(sync_dividers, 0200, NULL, ad9523_store, + AD9523_SYNC); + +static IIO_DEVICE_ATTR(store_eeprom, 0200, NULL, ad9523_store, + AD9523_EEPROM); + +static struct attribute *ad9523_attributes[] = { + &iio_dev_attr_sync_dividers.dev_attr.attr, + &iio_dev_attr_store_eeprom.dev_attr.attr, + &iio_dev_attr_pll2_feedback_clk_present.dev_attr.attr, + &iio_dev_attr_pll2_reference_clk_present.dev_attr.attr, + &iio_dev_attr_pll1_reference_clk_a_present.dev_attr.attr, + &iio_dev_attr_pll1_reference_clk_b_present.dev_attr.attr, + &iio_dev_attr_pll1_reference_clk_test_present.dev_attr.attr, + &iio_dev_attr_vcxo_clk_present.dev_attr.attr, + &iio_dev_attr_pll1_locked.dev_attr.attr, + &iio_dev_attr_pll2_locked.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad9523_attribute_group = { + .attrs = ad9523_attributes, +}; + +static int ad9523_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad9523_state *st = iio_priv(indio_dev); + unsigned int code; + int ret; + + mutex_lock(&st->lock); + ret = ad9523_read(indio_dev, AD9523_CHANNEL_CLOCK_DIST(chan->channel)); + mutex_unlock(&st->lock); + + if (ret < 0) + return ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + *val = !(ret & AD9523_CLK_DIST_PWR_DOWN_EN); + return IIO_VAL_INT; + case IIO_CHAN_INFO_FREQUENCY: + *val = st->vco_out_freq[st->vco_out_map[chan->channel]] / + AD9523_CLK_DIST_DIV_REV(ret); + return IIO_VAL_INT; + case IIO_CHAN_INFO_PHASE: + code = (AD9523_CLK_DIST_DIV_PHASE_REV(ret) * 3141592) / + AD9523_CLK_DIST_DIV_REV(ret); + *val = code / 1000000; + *val2 = code % 1000000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +}; + +static int ad9523_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad9523_state *st = iio_priv(indio_dev); + unsigned int reg; + int ret, tmp, code; + + mutex_lock(&st->lock); + ret = ad9523_read(indio_dev, AD9523_CHANNEL_CLOCK_DIST(chan->channel)); + if (ret < 0) + goto out; + + reg = ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val) + reg &= ~AD9523_CLK_DIST_PWR_DOWN_EN; + else + reg |= AD9523_CLK_DIST_PWR_DOWN_EN; + break; + case IIO_CHAN_INFO_FREQUENCY: + if (val <= 0) { + ret = -EINVAL; + goto out; + } + ret = ad9523_set_clock_provider(indio_dev, chan->channel, val); + if (ret < 0) + goto out; + tmp = st->vco_out_freq[st->vco_out_map[chan->channel]] / val; + tmp = clamp(tmp, 1, 1024); + reg &= ~(0x3FF << 8); + reg |= AD9523_CLK_DIST_DIV(tmp); + break; + case IIO_CHAN_INFO_PHASE: + code = val * 1000000 + val2 % 1000000; + tmp = (code * AD9523_CLK_DIST_DIV_REV(ret)) / 3141592; + tmp = clamp(tmp, 0, 63); + reg &= ~AD9523_CLK_DIST_DIV_PHASE(~0); + reg |= AD9523_CLK_DIST_DIV_PHASE(tmp); + break; + default: + ret = -EINVAL; + goto out; + } + + ret = ad9523_write(indio_dev, AD9523_CHANNEL_CLOCK_DIST(chan->channel), + reg); + if (ret < 0) + goto out; + + ad9523_io_update(indio_dev); +out: + mutex_unlock(&st->lock); + return ret; +} + +static int ad9523_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + struct ad9523_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->lock); + if (readval == NULL) { + ret = ad9523_write(indio_dev, reg | AD9523_R1B, writeval); + ad9523_io_update(indio_dev); + } else { + ret = ad9523_read(indio_dev, reg | AD9523_R1B); + if (ret < 0) + goto out_unlock; + *readval = ret; + ret = 0; + } + +out_unlock: + mutex_unlock(&st->lock); + + return ret; +} + +static const struct iio_info ad9523_info = { + .read_raw = &ad9523_read_raw, + .write_raw = &ad9523_write_raw, + .debugfs_reg_access = &ad9523_reg_access, + .attrs = &ad9523_attribute_group, +}; + +static int ad9523_setup(struct iio_dev *indio_dev) +{ + struct ad9523_state *st = iio_priv(indio_dev); + struct ad9523_platform_data *pdata = st->pdata; + struct ad9523_channel_spec *chan; + unsigned long active_mask = 0; + int ret, i; + + ret = ad9523_write(indio_dev, AD9523_SERIAL_PORT_CONFIG, + AD9523_SER_CONF_SOFT_RESET | + (st->spi->mode & SPI_3WIRE ? 0 : + AD9523_SER_CONF_SDO_ACTIVE)); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_READBACK_CTRL, + AD9523_READBACK_CTRL_READ_BUFFERED); + if (ret < 0) + return ret; + + ret = ad9523_io_update(indio_dev); + if (ret < 0) + return ret; + + /* + * PLL1 Setup + */ + ret = ad9523_write(indio_dev, AD9523_PLL1_REF_A_DIVIDER, + pdata->refa_r_div); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_PLL1_REF_B_DIVIDER, + pdata->refb_r_div); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_PLL1_FEEDBACK_DIVIDER, + pdata->pll1_feedback_div); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_PLL1_CHARGE_PUMP_CTRL, + AD9523_PLL1_CHARGE_PUMP_CURRENT_nA(pdata->pll1_charge_pump_current_nA) | + AD9523_PLL1_CHARGE_PUMP_MODE_NORMAL | + AD9523_PLL1_BACKLASH_PW_MIN); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_PLL1_INPUT_RECEIVERS_CTRL, + AD_IF(refa_diff_rcv_en, AD9523_PLL1_REFA_RCV_EN) | + AD_IF(refb_diff_rcv_en, AD9523_PLL1_REFB_RCV_EN) | + AD_IF(osc_in_diff_en, AD9523_PLL1_OSC_IN_DIFF_EN) | + AD_IF(osc_in_cmos_neg_inp_en, + AD9523_PLL1_OSC_IN_CMOS_NEG_INP_EN) | + AD_IF(refa_diff_rcv_en, AD9523_PLL1_REFA_DIFF_RCV_EN) | + AD_IF(refb_diff_rcv_en, AD9523_PLL1_REFB_DIFF_RCV_EN)); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_PLL1_REF_CTRL, + AD_IF(zd_in_diff_en, AD9523_PLL1_ZD_IN_DIFF_EN) | + AD_IF(zd_in_cmos_neg_inp_en, + AD9523_PLL1_ZD_IN_CMOS_NEG_INP_EN) | + AD_IF(zero_delay_mode_internal_en, + AD9523_PLL1_ZERO_DELAY_MODE_INT) | + AD_IF(osc_in_feedback_en, AD9523_PLL1_OSC_IN_PLL_FEEDBACK_EN) | + AD_IF(refa_cmos_neg_inp_en, AD9523_PLL1_REFA_CMOS_NEG_INP_EN) | + AD_IF(refb_cmos_neg_inp_en, AD9523_PLL1_REFB_CMOS_NEG_INP_EN)); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_PLL1_MISC_CTRL, + AD9523_PLL1_REFB_INDEP_DIV_CTRL_EN | + AD9523_PLL1_REF_MODE(pdata->ref_mode)); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_PLL1_LOOP_FILTER_CTRL, + AD9523_PLL1_LOOP_FILTER_RZERO(pdata->pll1_loop_filter_rzero)); + if (ret < 0) + return ret; + /* + * PLL2 Setup + */ + + ret = ad9523_write(indio_dev, AD9523_PLL2_CHARGE_PUMP, + AD9523_PLL2_CHARGE_PUMP_CURRENT_nA(pdata->pll2_charge_pump_current_nA)); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_PLL2_FEEDBACK_DIVIDER_AB, + AD9523_PLL2_FB_NDIV_A_CNT(pdata->pll2_ndiv_a_cnt) | + AD9523_PLL2_FB_NDIV_B_CNT(pdata->pll2_ndiv_b_cnt)); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_PLL2_CTRL, + AD9523_PLL2_CHARGE_PUMP_MODE_NORMAL | + AD9523_PLL2_BACKLASH_CTRL_EN | + AD_IF(pll2_freq_doubler_en, AD9523_PLL2_FREQ_DOUBLER_EN)); + if (ret < 0) + return ret; + + st->vco_freq = div_u64((unsigned long long)pdata->vcxo_freq * + (pdata->pll2_freq_doubler_en ? 2 : 1) * + AD9523_PLL2_FB_NDIV(pdata->pll2_ndiv_a_cnt, + pdata->pll2_ndiv_b_cnt), + pdata->pll2_r2_div); + + ret = ad9523_write(indio_dev, AD9523_PLL2_VCO_CTRL, + AD9523_PLL2_VCO_CALIBRATE); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_PLL2_VCO_DIVIDER, + AD9523_PLL2_VCO_DIV_M1(pdata->pll2_vco_div_m1) | + AD9523_PLL2_VCO_DIV_M2(pdata->pll2_vco_div_m2) | + AD_IFE(pll2_vco_div_m1, 0, + AD9523_PLL2_VCO_DIV_M1_PWR_DOWN_EN) | + AD_IFE(pll2_vco_div_m2, 0, + AD9523_PLL2_VCO_DIV_M2_PWR_DOWN_EN)); + if (ret < 0) + return ret; + + if (pdata->pll2_vco_div_m1) + st->vco_out_freq[AD9523_VCO1] = + st->vco_freq / pdata->pll2_vco_div_m1; + + if (pdata->pll2_vco_div_m2) + st->vco_out_freq[AD9523_VCO2] = + st->vco_freq / pdata->pll2_vco_div_m2; + + st->vco_out_freq[AD9523_VCXO] = pdata->vcxo_freq; + + ret = ad9523_write(indio_dev, AD9523_PLL2_R2_DIVIDER, + AD9523_PLL2_R2_DIVIDER_VAL(pdata->pll2_r2_div)); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_PLL2_LOOP_FILTER_CTRL, + AD9523_PLL2_LOOP_FILTER_CPOLE1(pdata->cpole1) | + AD9523_PLL2_LOOP_FILTER_RZERO(pdata->rzero) | + AD9523_PLL2_LOOP_FILTER_RPOLE2(pdata->rpole2) | + AD_IF(rzero_bypass_en, + AD9523_PLL2_LOOP_FILTER_RZERO_BYPASS_EN)); + if (ret < 0) + return ret; + + for (i = 0; i < pdata->num_channels; i++) { + chan = &pdata->channels[i]; + if (chan->channel_num < AD9523_NUM_CHAN) { + __set_bit(chan->channel_num, &active_mask); + ret = ad9523_write(indio_dev, + AD9523_CHANNEL_CLOCK_DIST(chan->channel_num), + AD9523_CLK_DIST_DRIVER_MODE(chan->driver_mode) | + AD9523_CLK_DIST_DIV(chan->channel_divider) | + AD9523_CLK_DIST_DIV_PHASE(chan->divider_phase) | + (chan->sync_ignore_en ? + AD9523_CLK_DIST_IGNORE_SYNC_EN : 0) | + (chan->divider_output_invert_en ? + AD9523_CLK_DIST_INV_DIV_OUTPUT_EN : 0) | + (chan->low_power_mode_en ? + AD9523_CLK_DIST_LOW_PWR_MODE_EN : 0) | + (chan->output_dis ? + AD9523_CLK_DIST_PWR_DOWN_EN : 0)); + if (ret < 0) + return ret; + + ret = ad9523_vco_out_map(indio_dev, chan->channel_num, + chan->use_alt_clock_src); + if (ret < 0) + return ret; + + st->ad9523_channels[i].type = IIO_ALTVOLTAGE; + st->ad9523_channels[i].output = 1; + st->ad9523_channels[i].indexed = 1; + st->ad9523_channels[i].channel = chan->channel_num; + st->ad9523_channels[i].extend_name = + chan->extended_name; + st->ad9523_channels[i].info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_PHASE) | + BIT(IIO_CHAN_INFO_FREQUENCY); + } + } + + for_each_clear_bit(i, &active_mask, AD9523_NUM_CHAN) { + ret = ad9523_write(indio_dev, + AD9523_CHANNEL_CLOCK_DIST(i), + AD9523_CLK_DIST_DRIVER_MODE(TRISTATE) | + AD9523_CLK_DIST_PWR_DOWN_EN); + if (ret < 0) + return ret; + } + + ret = ad9523_write(indio_dev, AD9523_POWER_DOWN_CTRL, 0); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_STATUS_SIGNALS, + AD9523_STATUS_MONITOR_01_PLL12_LOCKED); + if (ret < 0) + return ret; + + ret = ad9523_io_update(indio_dev); + if (ret < 0) + return ret; + + return 0; +} + +static int ad9523_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct ad9523_platform_data *pdata; + struct iio_dev *indio_dev; + struct ad9523_state *st; + int ret; + + pdata = dev_get_platdata(dev); + if (!pdata) + return dev_err_probe(dev, -EINVAL, "no platform data?\n"); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + mutex_init(&st->lock); + + ret = devm_regulator_get_enable(dev, "vcc"); + if (ret) + return ret; + + st->pwrdown_gpio = devm_gpiod_get_optional(dev, "powerdown", + GPIOD_OUT_HIGH); + if (IS_ERR(st->pwrdown_gpio)) + return PTR_ERR(st->pwrdown_gpio); + + st->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(st->reset_gpio)) + return PTR_ERR(st->reset_gpio); + + if (st->reset_gpio) { + udelay(1); + gpiod_direction_output(st->reset_gpio, 1); + } + + st->sync_gpio = devm_gpiod_get_optional(dev, "sync", + GPIOD_OUT_HIGH); + if (IS_ERR(st->sync_gpio)) + return PTR_ERR(st->sync_gpio); + + st->spi = spi; + st->pdata = pdata; + + indio_dev->name = (pdata->name[0] != 0) ? pdata->name : + spi_get_device_id(spi)->name; + indio_dev->info = &ad9523_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->ad9523_channels; + indio_dev->num_channels = pdata->num_channels; + + ret = ad9523_setup(indio_dev); + if (ret < 0) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id ad9523_id[] = { + { .name = "ad9523-1" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad9523_id); + +static struct spi_driver ad9523_driver = { + .driver = { + .name = "ad9523", + }, + .probe = ad9523_probe, + .id_table = ad9523_id, +}; +module_spi_driver(ad9523_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD9523 CLOCKDIST/PLL"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/frequency/adf41513.c b/drivers/iio/frequency/adf41513.c new file mode 100644 index 000000000..c49702701 --- /dev/null +++ b/drivers/iio/frequency/adf41513.c @@ -0,0 +1,1246 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADF41513 SPI PLL Frequency Synthesizer driver + * + * Copyright 2026 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Registers */ +#define ADF41513_REG0 0 +#define ADF41513_REG1 1 +#define ADF41513_REG2 2 +#define ADF41513_REG3 3 +#define ADF41513_REG4 4 +#define ADF41513_REG5 5 +#define ADF41513_REG6 6 +#define ADF41513_REG7 7 +#define ADF41513_REG8 8 +#define ADF41513_REG9 9 +#define ADF41513_REG10 10 +#define ADF41513_REG11 11 +#define ADF41513_REG12 12 +#define ADF41513_REG13 13 +#define ADF41513_REG_NUM 14 + +#define ADF41513_SYNC_REG0 BIT(ADF41513_REG0) +#define ADF41513_SYNC_REG1 BIT(ADF41513_REG1) +#define ADF41513_SYNC_REG2 BIT(ADF41513_REG2) +#define ADF41513_SYNC_REG3 BIT(ADF41513_REG3) +#define ADF41513_SYNC_REG4 BIT(ADF41513_REG4) +#define ADF41513_SYNC_REG5 BIT(ADF41513_REG5) +#define ADF41513_SYNC_REG6 BIT(ADF41513_REG6) +#define ADF41513_SYNC_REG7 BIT(ADF41513_REG7) +#define ADF41513_SYNC_REG9 BIT(ADF41513_REG9) +#define ADF41513_SYNC_REG11 BIT(ADF41513_REG11) +#define ADF41513_SYNC_REG12 BIT(ADF41513_REG12) +#define ADF41513_SYNC_REG13 BIT(ADF41513_REG13) +#define ADF41513_SYNC_DIFF 0 +#define ADF41513_SYNC_ALL GENMASK(ADF41513_REG13, ADF41513_REG0) + +/* REG0 Bit Definitions */ +#define ADF41513_REG0_CTRL_BITS_MSK GENMASK(3, 0) +#define ADF41513_REG0_INT_MSK GENMASK(19, 4) +#define ADF41513_REG0_VAR_MOD_MSK BIT(28) + +/* REG1 Bit Definitions */ +#define ADF41513_REG1_FRAC1_MSK GENMASK(28, 4) +#define ADF41513_REG1_DITHER2_MSK BIT(31) + +/* REG2 Bit Definitions */ +#define ADF41513_REG2_PHASE_VAL_MSK GENMASK(15, 4) +#define ADF41513_REG2_PHASE_ADJ_MSK BIT(31) + +/* REG3 Bit Definitions */ +#define ADF41513_REG3_FRAC2_MSK GENMASK(27, 4) + +/* REG4 Bit Definitions */ +#define ADF41513_REG4_MOD2_MSK GENMASK(27, 4) + +/* REG5 Bit Definitions */ +#define ADF41513_REG5_CLK1_DIV_MSK GENMASK(15, 4) +#define ADF41513_REG5_R_CNT_MSK GENMASK(20, 16) +#define ADF41513_REG5_REF_DOUBLER_MSK BIT(21) +#define ADF41513_REG5_RDIV2_MSK BIT(22) +#define ADF41513_REG5_PRESCALER_MSK BIT(23) +#define ADF41513_REG5_LSB_P1_MSK BIT(24) +#define ADF41513_REG5_CP_CURRENT_MSK GENMASK(28, 25) +#define ADF41513_REG5_DLD_MODES_MSK GENMASK(31, 30) + +/* REG6 Bit Definitions */ +#define ADF41513_REG6_COUNTER_RESET_MSK BIT(4) +#define ADF41513_REG6_CP_TRISTATE_MSK BIT(5) +#define ADF41513_REG6_POWER_DOWN_MSK BIT(6) +#define ADF41513_REG6_PD_POLARITY_MSK BIT(7) +#define ADF41513_REG6_LDP_MSK GENMASK(9, 8) +#define ADF41513_REG6_CP_TRISTATE_PD_ON_MSK BIT(16) +#define ADF41513_REG6_SD_RESET_MSK BIT(17) +#define ADF41513_REG6_LOL_ENABLE_MSK BIT(18) +#define ADF41513_REG6_ABP_MSK BIT(19) +#define ADF41513_REG6_INT_MODE_MSK BIT(20) +#define ADF41513_REG6_BLEED_ENABLE_MSK BIT(22) +#define ADF41513_REG6_BLEED_POLARITY_MSK BIT(23) +#define ADF41513_REG6_BLEED_CURRENT_MSK GENMASK(31, 24) + +/* REG7 Bit Definitions */ +#define ADF41513_REG7_CLK2_DIV_MSK GENMASK(17, 6) +#define ADF41513_REG7_CLK_DIV_MODE_MSK GENMASK(19, 18) +#define ADF41513_REG7_PS_BIAS_MSK GENMASK(21, 20) +#define ADF41513_REG7_N_DELAY_MSK GENMASK(23, 22) +#define ADF41513_REG7_LD_CLK_SEL_MSK BIT(26) +#define ADF41513_REG7_LD_COUNT_MSK GENMASK(29, 27) + +/* REG9 Bit Definitions */ +#define ADF41513_REG9_LD_BIAS_MSK GENMASK(31, 30) + +/* REG11 Bit Definitions */ +#define ADF41513_REG11_POWER_DOWN_SEL_MSK BIT(31) + +/* REG12 Bit Definitions */ +#define ADF41513_REG12_READBACK_SEL_MSK GENMASK(19, 14) +#define ADF41513_REG12_LE_SELECT_MSK BIT(20) +#define ADF41513_REG12_MASTER_RESET_MSK BIT(22) +#define ADF41513_REG12_LOGIC_LEVEL_MSK BIT(27) +#define ADF41513_REG12_MUXOUT_MSK GENMASK(31, 28) + +/* MUXOUT Selection */ +#define ADF41513_MUXOUT_TRISTATE 0x0 +#define ADF41513_MUXOUT_DVDD 0x1 +#define ADF41513_MUXOUT_DGND 0x2 +#define ADF41513_MUXOUT_R_DIV 0x3 +#define ADF41513_MUXOUT_N_DIV 0x4 +#define ADF41513_MUXOUT_DIG_LD 0x6 +#define ADF41513_MUXOUT_SDO 0x7 +#define ADF41513_MUXOUT_READBACK 0x8 +#define ADF41513_MUXOUT_CLK1_DIV 0xA +#define ADF41513_MUXOUT_R_DIV2 0xD +#define ADF41513_MUXOUT_N_DIV2 0xE + +/* DLD Mode Selection */ +#define ADF41513_DLD_TRISTATE 0x0 +#define ADF41513_DLD_DIG_LD 0x1 +#define ADF41513_DLD_LOW 0x2 +#define ADF41513_DLD_HIGH 0x3 + +/* Prescaler Selection */ +#define ADF41513_PRESCALER_4_5 0 +#define ADF41513_PRESCALER_8_9 1 +#define ADF41513_PRESCALER_AUTO 2 + +/* CLK Divider mode */ +#define ADF41513_CLK_DIV_MODE_OFF 0 +#define ADF41513_CLK_DIV_MODE_PHASE_RESYNC 2 + +/* Specifications */ +#define ADF41510_MAX_RF_FREQ_HZ (10ULL * HZ_PER_GHZ) +#define ADF41513_MIN_RF_FREQ_HZ (1ULL * HZ_PER_GHZ) +#define ADF41513_MAX_RF_FREQ_HZ (26500ULL * HZ_PER_MHZ) + +#define ADF41513_MIN_REF_FREQ_HZ (10 * HZ_PER_MHZ) +#define ADF41513_MAX_REF_FREQ_HZ (800 * HZ_PER_MHZ) +#define ADF41513_MAX_REF_FREQ_DOUBLER_HZ (225 * HZ_PER_MHZ) + +#define ADF41513_MAX_PFD_FREQ_INT_N_UHZ (250ULL * MEGA * MICROHZ_PER_HZ) +#define ADF41513_MAX_PFD_FREQ_FRAC_N_UHZ (125ULL * MEGA * MICROHZ_PER_HZ) +#define ADF41513_MAX_FREQ_RESOLUTION_UHZ (100ULL * KILO * MICROHZ_PER_HZ) + +#define ADF41513_MIN_INT_4_5 20 +#define ADF41513_MAX_INT_4_5 511 +#define ADF41513_MIN_INT_8_9 64 +#define ADF41513_MAX_INT_8_9 1023 + +#define ADF41513_MIN_INT_FRAC_4_5 23 +#define ADF41513_MIN_INT_FRAC_8_9 75 + +#define ADF41513_MIN_R_CNT 1 +#define ADF41513_MAX_R_CNT 32 + +#define ADF41513_MIN_R_SET 1800 +#define ADF41513_DEFAULT_R_SET 2700 +#define ADF41513_MAX_R_SET 10000 + +#define ADF41513_MIN_CP_VOLTAGE_mV 810 +#define ADF41513_DEFAULT_CP_VOLTAGE_mV 6480 +#define ADF41513_MAX_CP_VOLTAGE_mV 12960 + +#define ADF41513_MIN_CP_CURRENT_uA 81 +#define ADF41513_MAX_CP_CURRENT_uA 7200 + +#define ADF41513_LD_COUNT_FAST_MIN 2 +#define ADF41513_LD_COUNT_FAST_LIMIT 64 +#define ADF41513_LD_COUNT_MIN 64 +#define ADF41513_LD_COUNT_MAX 8192 + +#define ADF41513_FIXED_MODULUS BIT(25) +#define ADF41513_MAX_MOD2 (BIT(24) - 1) +#define ADF41513_MAX_PHASE_VAL (BIT(12) - 1) +#define ADF41513_MAX_CLK_DIVIDER (BIT(12) - 1) + +#define ADF41513_HZ_DECIMAL_SCALE 6 +#define ADF41513_PS_BIAS_INIT 0x2 +#define ADF41513_MAX_PHASE_MICRORAD ((2 * 314159265UL) / 100) + +enum adf41513_pll_mode { + ADF41513_MODE_INVALID, + ADF41513_MODE_INTEGER_N, + ADF41513_MODE_FIXED_MODULUS, + ADF41513_MODE_VARIABLE_MODULUS, +}; + +struct adf41513_chip_info { + const char *name; + u64 max_rf_freq_hz; + bool has_prescaler_8_9; +}; + +struct adf41513_data { + u64 power_up_frequency_hz; + u64 freq_resolution_uhz; + u32 phase_resync_period_ns; + u32 charge_pump_voltage_mv; + u32 lock_detect_count; + + u8 ref_div_factor; + bool ref_doubler_en; + bool ref_div2_en; + bool phase_detector_polarity; + + bool logic_lvl_1v8_en; + bool le_sync_en; +}; + +struct adf41513_pll_settings { + enum adf41513_pll_mode mode; + + /* reference path parameters */ + u8 r_counter; + u8 ref_doubler; + u8 ref_div2; + u8 prescaler; + + /* frequency parameters */ + u64 target_frequency_uhz; + u64 actual_frequency_uhz; + u64 pfd_frequency_uhz; + + /* pll parameters */ + u32 frac1; + u32 frac2; + u32 mod2; + u16 int_val; +}; + +struct adf41513_state { + const struct adf41513_chip_info *chip_info; + struct spi_device *spi; + struct gpio_desc *lock_detect; + struct clk *ref_clk; + u32 ref_freq_hz; + + /* + * Lock for accessing device registers. Some operations require + * multiple consecutive R/W operations, during which the device + * shouldn't be interrupted. The buffers are also shared across + * all operations so need to be protected on stand alone reads and + * writes. + */ + struct mutex lock; + + /* Cached register values */ + u32 regs[ADF41513_REG_NUM]; + u32 regs_hw[ADF41513_REG_NUM]; + + struct adf41513_data data; + struct adf41513_pll_settings settings; + + bool powerdown; +}; + +static const u16 adf41513_ld_window_x10_ns[] = { + 9, 12, 16, 17, 21, 28, 29, 35, /* 0 - 7 */ + 43, 47, 49, 52, 70, 79, 115, /* 8 - 14 */ +}; + +static const u8 adf41513_ldp_bias[] = { + 0xC, 0xD, 0xE, 0x8, 0x9, 0x4, 0xA, 0x5, /* 0 - 7 */ + 0x0, 0x6, 0xB, 0x1, 0x2, 0x7, 0x3, /* 8 - 14 */ +}; + +static const char * const adf41513_power_supplies[] = { + "avdd1", "avdd2", "avdd3", "avdd4", "avdd5", "vp", +}; + +static int adf41513_sync_config(struct adf41513_state *st, u16 sync_mask) +{ + __be32 d32; + int ret; + + /* write registers in reverse order (R13 to R0)*/ + for (int i = ADF41513_REG13; i >= ADF41513_REG0; i--) { + if (st->regs_hw[i] == st->regs[i] && !(sync_mask & BIT(i))) + continue; + + d32 = cpu_to_be32(st->regs[i] | i); + ret = spi_write_then_read(st->spi, &d32, sizeof(d32), NULL, 0); + if (ret < 0) + return ret; + st->regs_hw[i] = st->regs[i]; + dev_dbg(&st->spi->dev, "REG%d <= 0x%08X\n", i, st->regs[i] | i); + } + + return 0; +} + +static u64 adf41513_pll_get_rate(struct adf41513_state *st) +{ + struct adf41513_pll_settings *cfg = &st->settings; + + if (cfg->mode != ADF41513_MODE_INVALID) + return cfg->actual_frequency_uhz; + + /* get pll settings from regs_hw */ + cfg->int_val = FIELD_GET(ADF41513_REG0_INT_MSK, st->regs_hw[ADF41513_REG0]); + cfg->frac1 = FIELD_GET(ADF41513_REG1_FRAC1_MSK, st->regs_hw[ADF41513_REG1]); + cfg->frac2 = FIELD_GET(ADF41513_REG3_FRAC2_MSK, st->regs_hw[ADF41513_REG3]); + cfg->mod2 = FIELD_GET(ADF41513_REG4_MOD2_MSK, st->regs_hw[ADF41513_REG4]); + cfg->r_counter = FIELD_GET(ADF41513_REG5_R_CNT_MSK, st->regs_hw[ADF41513_REG5]); + cfg->ref_doubler = FIELD_GET(ADF41513_REG5_REF_DOUBLER_MSK, st->regs_hw[ADF41513_REG5]); + cfg->ref_div2 = FIELD_GET(ADF41513_REG5_RDIV2_MSK, st->regs_hw[ADF41513_REG5]); + cfg->prescaler = FIELD_GET(ADF41513_REG5_PRESCALER_MSK, st->regs_hw[ADF41513_REG5]); + + if (!cfg->mod2) + cfg->mod2 = 1; + if (!cfg->r_counter) + cfg->r_counter = ADF41513_MAX_R_CNT; + + /* calculate pfd frequency */ + cfg->pfd_frequency_uhz = (u64)st->ref_freq_hz * MICRO; + if (cfg->ref_doubler) + cfg->pfd_frequency_uhz <<= 1; + if (cfg->ref_div2) + cfg->pfd_frequency_uhz >>= 1; + cfg->pfd_frequency_uhz = div_u64(cfg->pfd_frequency_uhz, cfg->r_counter); + cfg->actual_frequency_uhz = (u64)cfg->int_val * cfg->pfd_frequency_uhz; + + /* check if int mode is selected */ + if (FIELD_GET(ADF41513_REG6_INT_MODE_MSK, st->regs_hw[ADF41513_REG6])) { + cfg->mode = ADF41513_MODE_INTEGER_N; + } else { + cfg->actual_frequency_uhz += mul_u64_u32_div(cfg->pfd_frequency_uhz, + cfg->frac1, + ADF41513_FIXED_MODULUS); + + /* check if variable modulus is selected */ + if (FIELD_GET(ADF41513_REG0_VAR_MOD_MSK, st->regs_hw[ADF41513_REG0])) { + cfg->actual_frequency_uhz += + mul_u64_u64_div_u64(cfg->frac2, + cfg->pfd_frequency_uhz, + (u64)cfg->mod2 * ADF41513_FIXED_MODULUS); + + cfg->mode = ADF41513_MODE_VARIABLE_MODULUS; + } else { + /* LSB_P1 offset */ + if (!FIELD_GET(ADF41513_REG5_LSB_P1_MSK, st->regs_hw[ADF41513_REG5])) + cfg->actual_frequency_uhz += + div_u64(cfg->pfd_frequency_uhz, + 2 * ADF41513_FIXED_MODULUS); + cfg->mode = ADF41513_MODE_FIXED_MODULUS; + } + } + + cfg->target_frequency_uhz = cfg->actual_frequency_uhz; + + return cfg->actual_frequency_uhz; +} + +static int adf41513_calc_pfd_frequency(struct adf41513_state *st, + struct adf41513_pll_settings *result, + u64 fpfd_limit_uhz) +{ + result->ref_div2 = st->data.ref_div2_en; + result->ref_doubler = st->data.ref_doubler_en; + result->r_counter = st->data.ref_div_factor - 1; + + do { + result->r_counter++; + /* f_PFD = REF_IN × ((1 + D)/(R × (1 + T))) */ + result->pfd_frequency_uhz = (u64)st->ref_freq_hz * MICRO; + if (result->ref_doubler) + result->pfd_frequency_uhz <<= 1; + if (result->ref_div2) + result->pfd_frequency_uhz >>= 1; + result->pfd_frequency_uhz = div_u64(result->pfd_frequency_uhz, + result->r_counter); + } while (result->pfd_frequency_uhz > fpfd_limit_uhz); + + if (result->r_counter > ADF41513_MAX_R_CNT) { + dev_err(&st->spi->dev, "Cannot optimize PFD frequency\n"); + return -ERANGE; + } + + return 0; +} + +static int adf41513_calc_integer_n(struct adf41513_state *st, + struct adf41513_pll_settings *result) +{ + u32 max_int = st->chip_info->has_prescaler_8_9 ? + ADF41513_MAX_INT_8_9 : ADF41513_MAX_INT_4_5; + u64 freq_error_uhz; + u32 int_val = div64_u64_rem(result->target_frequency_uhz, result->pfd_frequency_uhz, + &freq_error_uhz); + + /* check if freq error is within a tolerance of 1/2 resolution */ + if (freq_error_uhz > (result->pfd_frequency_uhz >> 1) && int_val < max_int) { + int_val++; + freq_error_uhz = result->pfd_frequency_uhz - freq_error_uhz; + } + + if (freq_error_uhz > st->data.freq_resolution_uhz) + return -ERANGE; + + /* set prescaler */ + if (st->chip_info->has_prescaler_8_9 && int_val >= ADF41513_MIN_INT_8_9 && + int_val <= ADF41513_MAX_INT_8_9) + result->prescaler = 1; + else if (int_val >= ADF41513_MIN_INT_4_5 && int_val <= ADF41513_MAX_INT_4_5) + result->prescaler = 0; + else + return -ERANGE; + + result->actual_frequency_uhz = (u64)int_val * result->pfd_frequency_uhz; + result->mode = ADF41513_MODE_INTEGER_N; + result->int_val = int_val; + result->frac1 = 0; + result->frac2 = 0; + result->mod2 = 0; + + return 0; +} + +static int adf41513_calc_fixed_mod(struct adf41513_state *st, + struct adf41513_pll_settings *result) +{ + u64 resolution_uhz = div_u64(result->pfd_frequency_uhz, ADF41513_FIXED_MODULUS); + u64 target_frequency_uhz = result->target_frequency_uhz; + u64 freq_error_uhz; + u32 int_val, frac1; + bool lsb_p1_offset = !FIELD_GET(ADF41513_REG5_LSB_P1_MSK, st->regs[ADF41513_REG5]); + + /* LSB_P1 adds a frequency offset of f_pfd/2^26 */ + if (lsb_p1_offset) + target_frequency_uhz -= resolution_uhz >> 1; + + int_val = div64_u64_rem(target_frequency_uhz, result->pfd_frequency_uhz, + &freq_error_uhz); + + if (st->chip_info->has_prescaler_8_9 && int_val >= ADF41513_MIN_INT_FRAC_8_9 && + int_val <= ADF41513_MAX_INT_8_9) + result->prescaler = 1; + else if (int_val >= ADF41513_MIN_INT_FRAC_4_5 && int_val <= ADF41513_MAX_INT_4_5) + result->prescaler = 0; + else + return -ERANGE; + + /* compute frac1 and fixed modulus error */ + frac1 = mul_u64_u64_div_u64(freq_error_uhz, ADF41513_FIXED_MODULUS, + result->pfd_frequency_uhz); + freq_error_uhz -= mul_u64_u32_div(result->pfd_frequency_uhz, frac1, + ADF41513_FIXED_MODULUS); + + /* check if freq error is within a tolerance of 1/2 resolution */ + if (freq_error_uhz > (resolution_uhz >> 1) && frac1 < (ADF41513_FIXED_MODULUS - 1)) { + frac1++; + freq_error_uhz = freq_error_uhz < resolution_uhz ? + resolution_uhz - freq_error_uhz : 0; + } + + if (freq_error_uhz > st->data.freq_resolution_uhz) + return -ERANGE; + + /* integer part */ + result->actual_frequency_uhz = (u64)int_val * result->pfd_frequency_uhz; + /* fractional part */ + if (lsb_p1_offset) + result->actual_frequency_uhz += (resolution_uhz >> 1); + result->actual_frequency_uhz += mul_u64_u32_div(result->pfd_frequency_uhz, frac1, + ADF41513_FIXED_MODULUS); + result->mode = ADF41513_MODE_FIXED_MODULUS; + result->int_val = int_val; + result->frac1 = frac1; + result->frac2 = 0; + result->mod2 = 0; + + return 0; +} + +static int adf41513_calc_variable_mod(struct adf41513_state *st, + struct adf41513_pll_settings *result) +{ + u64 freq_error_uhz, mod2; + u32 frac1, frac2; + u32 int_val = div64_u64_rem(result->target_frequency_uhz, + result->pfd_frequency_uhz, &freq_error_uhz); + + if (st->chip_info->has_prescaler_8_9 && int_val >= ADF41513_MIN_INT_FRAC_8_9 && + int_val <= ADF41513_MAX_INT_8_9) + result->prescaler = 1; + else if (int_val >= ADF41513_MIN_INT_FRAC_4_5 && int_val <= ADF41513_MAX_INT_4_5) + result->prescaler = 0; + else + return -ERANGE; + + /* calculate required mod2 based on target resolution / 2 */ + mod2 = DIV64_U64_ROUND_CLOSEST(result->pfd_frequency_uhz << 1, + st->data.freq_resolution_uhz * ADF41513_FIXED_MODULUS); + /* ensure mod2 is at least 2 for meaningful operation */ + mod2 = clamp(mod2, 2, ADF41513_MAX_MOD2); + + /* calculate frac1 and frac2 */ + frac1 = mul_u64_u64_div_u64(freq_error_uhz, ADF41513_FIXED_MODULUS, + result->pfd_frequency_uhz); + frac2 = mul_u64_u64_div_u64(freq_error_uhz, mod2 * ADF41513_FIXED_MODULUS, + result->pfd_frequency_uhz) - mod2 * frac1; + + /* integer part */ + result->actual_frequency_uhz = (u64)int_val * result->pfd_frequency_uhz; + /* fractional part */ + result->actual_frequency_uhz += mul_u64_u64_div_u64(mod2 * frac1 + frac2, + result->pfd_frequency_uhz, + mod2 * ADF41513_FIXED_MODULUS); + result->mode = ADF41513_MODE_VARIABLE_MODULUS; + result->int_val = int_val; + result->frac1 = frac1; + result->frac2 = frac2; + result->mod2 = mod2; + + return 0; +} + +static int adf41513_calc_pll_settings(struct adf41513_state *st, + struct adf41513_pll_settings *result, + u64 rf_out_uhz) +{ + u64 max_rf_freq_uhz = st->chip_info->max_rf_freq_hz * MICRO; + u64 min_rf_freq_uhz = ADF41513_MIN_RF_FREQ_HZ * MICRO; + u64 pfd_freq_limit_uhz; + int ret; + + if (rf_out_uhz < min_rf_freq_uhz || rf_out_uhz > max_rf_freq_uhz) { + dev_err(&st->spi->dev, "RF frequency %llu uHz out of range [%llu, %llu] uHz\n", + rf_out_uhz, min_rf_freq_uhz, max_rf_freq_uhz); + return -EINVAL; + } + + result->target_frequency_uhz = rf_out_uhz; + + /* try integer-N first (best phase noise performance) */ + pfd_freq_limit_uhz = min(div_u64(rf_out_uhz, ADF41513_MIN_INT_4_5), + ADF41513_MAX_PFD_FREQ_INT_N_UHZ); + ret = adf41513_calc_pfd_frequency(st, result, pfd_freq_limit_uhz); + if (ret) + return ret; + + if (adf41513_calc_integer_n(st, result) == 0) + return 0; + + /* try fractional-N: recompute pfd frequency if necessary */ + pfd_freq_limit_uhz = min(div_u64(rf_out_uhz, ADF41513_MIN_INT_FRAC_4_5), + ADF41513_MAX_PFD_FREQ_FRAC_N_UHZ); + if (pfd_freq_limit_uhz < result->pfd_frequency_uhz) { + ret = adf41513_calc_pfd_frequency(st, result, pfd_freq_limit_uhz); + if (ret) + return ret; + } + + /* fixed-modulus attempt */ + if (adf41513_calc_fixed_mod(st, result) == 0) + return 0; + + /* variable-modulus attempt */ + ret = adf41513_calc_variable_mod(st, result); + if (ret) { + dev_err(&st->spi->dev, + "no valid PLL configuration found for %llu uHz\n", + rf_out_uhz); + return ret; + } + + return 0; +} + +static void adf41513_set_bleed_val(struct adf41513_state *st) +{ + u32 bleed_value, cp_index; + + if (!(st->regs[ADF41513_REG6] & ADF41513_REG6_BLEED_ENABLE_MSK)) + return; + + if (st->data.phase_detector_polarity) + bleed_value = 90; + else + bleed_value = 144; + + cp_index = 1 + FIELD_GET(ADF41513_REG5_CP_CURRENT_MSK, + st->regs[ADF41513_REG5]); + bleed_value = div64_u64(st->settings.pfd_frequency_uhz * cp_index * bleed_value, + 1600ULL * MEGA * MICROHZ_PER_HZ); + + FIELD_MODIFY(ADF41513_REG6_BLEED_CURRENT_MSK, &st->regs[ADF41513_REG6], + bleed_value); +} + +static void adf41513_set_ld_window(struct adf41513_state *st) +{ + /* + * The ideal lock detector window size is halfway between the max + * window, set by the phase comparison period t_PFD = (1 / f_PFD), + * and the minimum is set by (I_BLEED/I_CP) × t_PFD + */ + u16 ld_window_10x_ns = div64_u64(10ULL * NSEC_PER_SEC * MICROHZ_PER_HZ, + st->settings.pfd_frequency_uhz << 1); + u8 ld_idx, ldp, ld_bias; + + if (st->settings.mode != ADF41513_MODE_INTEGER_N) { + /* account for bleed current (deduced from eq.6 and eq.7) */ + if (st->data.phase_detector_polarity) + ld_window_10x_ns += 4; + else + ld_window_10x_ns += 6; + } + + ld_idx = find_closest(ld_window_10x_ns, adf41513_ld_window_x10_ns, + ARRAY_SIZE(adf41513_ld_window_x10_ns)); + ldp = (adf41513_ldp_bias[ld_idx] >> 2) & 0x3; + ld_bias = adf41513_ldp_bias[ld_idx] & 0x3; + + FIELD_MODIFY(ADF41513_REG6_LDP_MSK, &st->regs[ADF41513_REG6], ldp); + FIELD_MODIFY(ADF41513_REG9_LD_BIAS_MSK, &st->regs[ADF41513_REG9], ld_bias); +} + +static void adf41513_set_phase_resync(struct adf41513_state *st) +{ + u32 total_div, clk1_div, clk2_div; + + if (!st->data.phase_resync_period_ns) + return; + + /* assuming both clock dividers hold similar values */ + total_div = mul_u64_u64_div_u64(st->settings.pfd_frequency_uhz, + st->data.phase_resync_period_ns, + 1ULL * MICROHZ_PER_HZ * NSEC_PER_SEC); + clk1_div = clamp(int_sqrt(total_div), 1, + ADF41513_MAX_CLK_DIVIDER); + clk2_div = clamp(DIV_ROUND_CLOSEST(total_div, clk1_div), 1, + ADF41513_MAX_CLK_DIVIDER); + + FIELD_MODIFY(ADF41513_REG5_CLK1_DIV_MSK, &st->regs[ADF41513_REG5], + clk1_div); + FIELD_MODIFY(ADF41513_REG7_CLK2_DIV_MSK, &st->regs[ADF41513_REG7], + clk2_div); + + /* enable phase resync */ + FIELD_MODIFY(ADF41513_REG7_CLK_DIV_MODE_MSK, &st->regs[ADF41513_REG7], + ADF41513_CLK_DIV_MODE_PHASE_RESYNC); +} + +static int adf41513_set_frequency(struct adf41513_state *st, u64 freq_uhz, u16 sync_mask) +{ + struct adf41513_pll_settings result; + bool pfd_change = false; + bool mode_change = false; + int ret; + + ret = adf41513_calc_pll_settings(st, &result, freq_uhz); + if (ret < 0) + return ret; + + /* apply computed results to pll settings */ + pfd_change = st->settings.pfd_frequency_uhz != result.pfd_frequency_uhz; + mode_change = st->settings.mode != result.mode; + st->settings = result; + + dev_dbg(&st->spi->dev, + "%s mode: int=%u, frac1=%u, frac2=%u, mod2=%u, fpdf=%llu Hz, prescaler=%s\n", + (result.mode == ADF41513_MODE_INTEGER_N) ? "integer-n" : + (result.mode == ADF41513_MODE_FIXED_MODULUS) ? "fixed-modulus" : "variable-modulus", + result.int_val, result.frac1, result.frac2, result.mod2, + div64_u64(result.pfd_frequency_uhz, MICRO), + result.prescaler ? "8/9" : "4/5"); + + st->regs[ADF41513_REG0] = FIELD_PREP(ADF41513_REG0_INT_MSK, + st->settings.int_val); + if (st->settings.mode == ADF41513_MODE_VARIABLE_MODULUS) + st->regs[ADF41513_REG0] |= ADF41513_REG0_VAR_MOD_MSK; + + st->regs[ADF41513_REG1] = FIELD_PREP(ADF41513_REG1_FRAC1_MSK, + st->settings.frac1); + if (st->settings.mode != ADF41513_MODE_INTEGER_N) + st->regs[ADF41513_REG1] |= ADF41513_REG1_DITHER2_MSK; + + st->regs[ADF41513_REG3] = FIELD_PREP(ADF41513_REG3_FRAC2_MSK, + st->settings.frac2); + FIELD_MODIFY(ADF41513_REG4_MOD2_MSK, &st->regs[ADF41513_REG4], + st->settings.mod2); + FIELD_MODIFY(ADF41513_REG5_R_CNT_MSK, &st->regs[ADF41513_REG5], + st->settings.r_counter % ADF41513_MAX_R_CNT); + FIELD_MODIFY(ADF41513_REG5_REF_DOUBLER_MSK, &st->regs[ADF41513_REG5], + st->settings.ref_doubler); + FIELD_MODIFY(ADF41513_REG5_RDIV2_MSK, &st->regs[ADF41513_REG5], + st->settings.ref_div2); + FIELD_MODIFY(ADF41513_REG5_PRESCALER_MSK, &st->regs[ADF41513_REG5], + st->settings.prescaler); + + if (st->settings.mode == ADF41513_MODE_INTEGER_N) { + st->regs[ADF41513_REG6] |= ADF41513_REG6_INT_MODE_MSK; + st->regs[ADF41513_REG6] &= ~ADF41513_REG6_BLEED_ENABLE_MSK; + } else { + st->regs[ADF41513_REG6] &= ~ADF41513_REG6_INT_MODE_MSK; + st->regs[ADF41513_REG6] |= ADF41513_REG6_BLEED_ENABLE_MSK; + } + + if (pfd_change) + adf41513_set_phase_resync(st); + + if (pfd_change || mode_change) { + adf41513_set_bleed_val(st); + adf41513_set_ld_window(st); + } + + return adf41513_sync_config(st, sync_mask | ADF41513_SYNC_REG0); +} + +static int adf41513_suspend(struct adf41513_state *st) +{ + st->regs[ADF41513_REG6] |= FIELD_PREP(ADF41513_REG6_POWER_DOWN_MSK, 1); + st->regs[ADF41513_REG12] &= ~ADF41513_REG12_LE_SELECT_MSK; + return adf41513_sync_config(st, ADF41513_SYNC_DIFF); +} + +static int adf41513_resume(struct adf41513_state *st) +{ + int ret; + + st->regs[ADF41513_REG6] &= ~ADF41513_REG6_POWER_DOWN_MSK; + st->regs[ADF41513_REG12] &= ~ADF41513_REG12_LE_SELECT_MSK; + ret = adf41513_sync_config(st, ADF41513_SYNC_ALL); + if (ret) + return ret; + + if (st->data.le_sync_en) { + st->regs[ADF41513_REG12] |= ADF41513_REG12_LE_SELECT_MSK; + ret = adf41513_sync_config(st, ADF41513_SYNC_DIFF); + if (ret) + return ret; + } + + return 0; +} + +static ssize_t adf41513_read_resolution(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct adf41513_state *st = iio_priv(indio_dev); + int vals[2]; + + guard(mutex)(&st->lock); + + iio_val_s64_decompose(st->data.freq_resolution_uhz, &vals[0], &vals[1]); + return iio_format_value(buf, IIO_VAL_DECIMAL64_MICRO, ARRAY_SIZE(vals), vals); +} + +static ssize_t adf41513_read_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct adf41513_state *st = iio_priv(indio_dev); + u32 val; + + guard(mutex)(&st->lock); + + val = FIELD_GET(ADF41513_REG6_POWER_DOWN_MSK, st->regs_hw[ADF41513_REG6]); + return sysfs_emit(buf, "%u\n", val); +} + +static ssize_t adf41513_write_resolution(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct adf41513_state *st = iio_priv(indio_dev); + u64 freq_uhz; + int ret; + + ret = kstrtoudec64(buf, ADF41513_HZ_DECIMAL_SCALE, &freq_uhz); + if (ret) + return ret; + + if (freq_uhz == 0 || freq_uhz > ADF41513_MAX_FREQ_RESOLUTION_UHZ) + return -EINVAL; + + guard(mutex)(&st->lock); + + st->data.freq_resolution_uhz = freq_uhz; + return len; +} + +static ssize_t adf41513_write_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct adf41513_state *st = iio_priv(indio_dev); + bool val; + int ret; + + ret = kstrtobool(buf, &val); + if (ret) + return ret; + + guard(mutex)(&st->lock); + + if (val) + ret = adf41513_suspend(st); + else + ret = adf41513_resume(st); + if (ret) + return ret; + + st->powerdown = val; + return len; +} + +static const struct iio_chan_spec_ext_info adf41513_ext_info[] = { + { + .name = "frequency_resolution", + .read = adf41513_read_resolution, + .write = adf41513_write_resolution, + .shared = IIO_SEPARATE, + }, + { + .name = "powerdown", + .read = adf41513_read_powerdown, + .write = adf41513_write_powerdown, + .shared = IIO_SEPARATE, + }, + { } +}; + +static const struct iio_chan_spec adf41513_chan = { + .type = IIO_ALTVOLTAGE, + .indexed = 1, + .output = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_FREQUENCY) | + BIT(IIO_CHAN_INFO_PHASE), + .ext_info = adf41513_ext_info, +}; + +static int adf41513_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct adf41513_state *st = iio_priv(indio_dev); + u64 tmp64; + + guard(mutex)(&st->lock); + + switch (info) { + case IIO_CHAN_INFO_FREQUENCY: + if (st->lock_detect && + !gpiod_get_value_cansleep(st->lock_detect)) { + dev_dbg(&st->spi->dev, "PLL un-locked\n"); + return -EBUSY; + } + tmp64 = adf41513_pll_get_rate(st); + iio_val_s64_decompose(tmp64, val, val2); + return IIO_VAL_DECIMAL64_MICRO; + case IIO_CHAN_INFO_PHASE: + tmp64 = FIELD_GET(ADF41513_REG2_PHASE_VAL_MSK, + st->regs_hw[ADF41513_REG2]); + tmp64 = (tmp64 * ADF41513_MAX_PHASE_MICRORAD) >> 12; + iio_val_s64_decompose(tmp64, val, val2); + return IIO_VAL_DECIMAL64_MICRO; + default: + return -EINVAL; + } +} + +static int adf41513_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct adf41513_state *st = iio_priv(indio_dev); + u64 tmp64 = iio_val_s64_compose(val, val2); + u16 phase_val; + int ret; + + guard(mutex)(&st->lock); + + switch (info) { + case IIO_CHAN_INFO_FREQUENCY: + return adf41513_set_frequency(st, tmp64, ADF41513_SYNC_DIFF); + case IIO_CHAN_INFO_PHASE: + if (tmp64 >= ADF41513_MAX_PHASE_MICRORAD) + return -EINVAL; + + phase_val = DIV_U64_ROUND_CLOSEST(tmp64 << 12, + ADF41513_MAX_PHASE_MICRORAD); + phase_val = min(phase_val, ADF41513_MAX_PHASE_VAL); + st->regs[ADF41513_REG2] |= ADF41513_REG2_PHASE_ADJ_MSK; + FIELD_MODIFY(ADF41513_REG2_PHASE_VAL_MSK, + &st->regs[ADF41513_REG2], phase_val); + ret = adf41513_sync_config(st, ADF41513_SYNC_REG0); + /* clear phase adjust for the next sync */ + st->regs[ADF41513_REG2] &= ~ADF41513_REG2_PHASE_ADJ_MSK; + return ret; + default: + return -EINVAL; + } +} + +static int adf41513_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_FREQUENCY: + case IIO_CHAN_INFO_PHASE: + return IIO_VAL_DECIMAL64_MICRO; + default: + return -EINVAL; + } +} + +static int adf41513_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct adf41513_state *st = iio_priv(indio_dev); + + if (reg > ADF41513_REG13) + return -EINVAL; + + guard(mutex)(&st->lock); + + if (!readval) { + if (reg <= ADF41513_REG6) + st->settings.mode = ADF41513_MODE_INVALID; + st->regs[reg] = writeval & ~0xF; /* Clear control bits */ + return adf41513_sync_config(st, BIT(reg)); + } + + *readval = st->regs_hw[reg]; + return 0; +} + +static const struct iio_info adf41513_info = { + .read_raw = adf41513_read_raw, + .write_raw = adf41513_write_raw, + .write_raw_get_fmt = adf41513_write_raw_get_fmt, + .debugfs_reg_access = &adf41513_reg_access, +}; + +static int adf41513_parse_fw(struct adf41513_state *st) +{ + struct device *dev = &st->spi->dev; + u32 tmp, cp_resistance, cp_current; + int ret; + + tmp = ADF41510_MAX_RF_FREQ_HZ / MEGA; + device_property_read_u32(dev, "adi,power-up-frequency-mhz", &tmp); + st->data.power_up_frequency_hz = (u64)tmp * MEGA; + if (st->data.power_up_frequency_hz < ADF41513_MIN_RF_FREQ_HZ || + st->data.power_up_frequency_hz > st->chip_info->max_rf_freq_hz) + return dev_err_probe(dev, -ERANGE, + "power-up frequency %llu Hz out of range\n", + st->data.power_up_frequency_hz); + + tmp = ADF41513_MIN_R_CNT; + device_property_read_u32(dev, "adi,reference-div-factor", &tmp); + if (tmp < ADF41513_MIN_R_CNT || tmp > ADF41513_MAX_R_CNT) + return dev_err_probe(dev, -ERANGE, + "invalid reference div factor %u\n", tmp); + st->data.ref_div_factor = tmp; + + st->data.ref_div2_en = device_property_read_bool(dev, "adi,reference-div2-enable"); + st->data.ref_doubler_en = device_property_read_bool(dev, "adi,reference-doubler-enable"); + + if (st->data.ref_doubler_en && + st->ref_freq_hz > ADF41513_MAX_REF_FREQ_DOUBLER_HZ) { + return dev_err_probe(dev, -ERANGE, + "Ref frequency not supported with doubler enabled\n"); + } + + cp_resistance = ADF41513_DEFAULT_R_SET; + device_property_read_u32(dev, "adi,charge-pump-resistor-ohms", &cp_resistance); + if (cp_resistance < ADF41513_MIN_R_SET || cp_resistance > ADF41513_MAX_R_SET) + return dev_err_probe(dev, -ERANGE, "R_SET %u Ohms out of range\n", cp_resistance); + + st->data.charge_pump_voltage_mv = ADF41513_DEFAULT_CP_VOLTAGE_mV; + ret = device_property_read_u32(dev, "adi,charge-pump-current-microamp", &cp_current); + if (!ret) { + if (cp_current < ADF41513_MIN_CP_CURRENT_uA || + cp_current > ADF41513_MAX_CP_CURRENT_uA) + return dev_err_probe(dev, -ERANGE, + "I_CP %u uA out of range\n", cp_current); + + tmp = DIV_ROUND_CLOSEST(cp_current * cp_resistance, MILLI); + if (tmp < ADF41513_MIN_CP_VOLTAGE_mV || tmp > ADF41513_MAX_CP_VOLTAGE_mV) + return dev_err_probe(dev, -ERANGE, "I_CP %u uA (%u Ohms) out of range\n", + cp_current, cp_resistance); + st->data.charge_pump_voltage_mv = tmp; + } + + st->data.phase_detector_polarity = + device_property_read_bool(dev, "adi,phase-detector-polarity-positive-enable"); + + st->data.phase_resync_period_ns = 0; + ret = device_property_read_u32(dev, "adi,phase-resync-period-ns", &tmp); + if (!ret) + st->data.phase_resync_period_ns = tmp; + + st->data.logic_lvl_1v8_en = device_property_read_bool(dev, "adi,logic-level-1v8-enable"); + + tmp = ADF41513_LD_COUNT_MIN; + device_property_read_u32(dev, "adi,lock-detector-count", &tmp); + if (tmp < ADF41513_LD_COUNT_FAST_MIN || tmp > ADF41513_LD_COUNT_MAX || + !is_power_of_2(tmp)) + return dev_err_probe(dev, -ERANGE, + "invalid lock detect count: %u\n", tmp); + st->data.lock_detect_count = tmp; + + /* load enable sync */ + st->data.le_sync_en = device_property_read_bool(dev, "adi,le-sync-enable"); + st->data.freq_resolution_uhz = MICROHZ_PER_HZ; + + return 0; +} + +static void adf41513_chip_disable(void *data) +{ + gpiod_set_value_cansleep(data, 0); +} + +static void adf41513_close(void *data) +{ + adf41513_suspend(data); +} + +static int adf41513_setup(struct device *dev, struct adf41513_state *st) +{ + u32 tmp; + int ret; + + memset(st->regs_hw, 0xFF, sizeof(st->regs_hw)); + + /* assuming DLD pin is used for lock detection */ + st->regs[ADF41513_REG5] = FIELD_PREP(ADF41513_REG5_DLD_MODES_MSK, + ADF41513_DLD_DIG_LD); + + tmp = DIV_ROUND_CLOSEST(st->data.charge_pump_voltage_mv, ADF41513_MIN_CP_VOLTAGE_mV); + st->regs[ADF41513_REG5] |= FIELD_PREP(ADF41513_REG5_CP_CURRENT_MSK, tmp - 1); + + st->regs[ADF41513_REG6] = ADF41513_REG6_ABP_MSK | + ADF41513_REG6_LOL_ENABLE_MSK | + ADF41513_REG6_SD_RESET_MSK; + if (st->data.phase_detector_polarity) + st->regs[ADF41513_REG6] |= ADF41513_REG6_PD_POLARITY_MSK; + + st->regs[ADF41513_REG7] = FIELD_PREP(ADF41513_REG7_PS_BIAS_MSK, + ADF41513_PS_BIAS_INIT); + tmp = ilog2(st->data.lock_detect_count); + if (st->data.lock_detect_count < ADF41513_LD_COUNT_FAST_LIMIT) { + tmp -= const_ilog2(ADF41513_LD_COUNT_FAST_MIN); + st->regs[ADF41513_REG7] |= ADF41513_REG7_LD_CLK_SEL_MSK; + } else { + tmp -= const_ilog2(ADF41513_LD_COUNT_MIN); + } + st->regs[ADF41513_REG7] |= FIELD_PREP(ADF41513_REG7_LD_COUNT_MSK, tmp); + + st->regs[ADF41513_REG11] = ADF41513_REG11_POWER_DOWN_SEL_MSK; + st->regs[ADF41513_REG12] = FIELD_PREP(ADF41513_REG12_LOGIC_LEVEL_MSK, + st->data.logic_lvl_1v8_en ? 0 : 1); + + /* perform initialization sequence with power-up frequency */ + ret = adf41513_set_frequency(st, st->data.power_up_frequency_hz * MICRO, + ADF41513_SYNC_ALL); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, adf41513_close, st); + if (ret) + return ret; + + if (st->data.le_sync_en) { + st->regs[ADF41513_REG12] |= ADF41513_REG12_LE_SELECT_MSK; + ret = adf41513_sync_config(st, ADF41513_SYNC_DIFF); + if (ret) + return ret; + } + + return 0; +} + +static int adf41513_pm_suspend(struct device *dev) +{ + struct adf41513_state *st = dev_get_drvdata(dev); + + guard(mutex)(&st->lock); + return adf41513_suspend(st); +} + +static int adf41513_pm_resume(struct device *dev) +{ + struct adf41513_state *st = dev_get_drvdata(dev); + + guard(mutex)(&st->lock); + if (st->powerdown) + return 0; /* nothing to do */ + + return adf41513_resume(st); +} + +static const struct adf41513_chip_info adf41510_chip_info = { + .name = "adf41510", + .max_rf_freq_hz = ADF41510_MAX_RF_FREQ_HZ, + .has_prescaler_8_9 = false, +}; + +static const struct adf41513_chip_info adf41513_chip_info = { + .name = "adf41513", + .max_rf_freq_hz = ADF41513_MAX_RF_FREQ_HZ, + .has_prescaler_8_9 = true, +}; + +static int adf41513_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct gpio_desc *chip_enable; + struct iio_dev *indio_dev; + struct adf41513_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + st->chip_info = spi_get_device_match_data(spi); + if (!st->chip_info) + return -EINVAL; + + spi_set_drvdata(spi, st); + + st->ref_clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(st->ref_clk)) + return PTR_ERR(st->ref_clk); + + st->ref_freq_hz = clk_get_rate(st->ref_clk); + if (st->ref_freq_hz < ADF41513_MIN_REF_FREQ_HZ || + st->ref_freq_hz > ADF41513_MAX_REF_FREQ_HZ) + return dev_err_probe(dev, -ERANGE, + "reference frequency %u Hz out of range\n", + st->ref_freq_hz); + + ret = adf41513_parse_fw(st); + if (ret) + return ret; + + ret = devm_regulator_bulk_get_enable(dev, + ARRAY_SIZE(adf41513_power_supplies), + adf41513_power_supplies); + if (ret) + return dev_err_probe(dev, ret, + "failed to get and enable regulators\n"); + + st->lock_detect = devm_gpiod_get_optional(dev, "lock-detect", GPIOD_IN); + if (IS_ERR(st->lock_detect)) + return dev_err_probe(dev, PTR_ERR(st->lock_detect), + "fail to request lock detect GPIO\n"); + + chip_enable = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_HIGH); + if (IS_ERR(chip_enable)) + return dev_err_probe(dev, PTR_ERR(chip_enable), + "fail to request chip enable GPIO\n"); + + ret = devm_add_action_or_reset(dev, adf41513_chip_disable, chip_enable); + if (ret) + return dev_err_probe(dev, ret, "Failed to add disable action\n"); + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + indio_dev->name = st->chip_info->name; + indio_dev->info = &adf41513_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = &adf41513_chan; + indio_dev->num_channels = 1; + + ret = adf41513_setup(dev, st); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to setup device\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id adf41513_id[] = { + { .name = "adf41510", .driver_data = (kernel_ulong_t)&adf41510_chip_info }, + { .name = "adf41513", .driver_data = (kernel_ulong_t)&adf41513_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, adf41513_id); + +static const struct of_device_id adf41513_of_match[] = { + { .compatible = "adi,adf41510", .data = &adf41510_chip_info }, + { .compatible = "adi,adf41513", .data = &adf41513_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, adf41513_of_match); + +static DEFINE_SIMPLE_DEV_PM_OPS(adf41513_pm_ops, adf41513_pm_suspend, adf41513_pm_resume); + +static struct spi_driver adf41513_driver = { + .driver = { + .name = "adf41513", + .pm = pm_ptr(&adf41513_pm_ops), + .of_match_table = adf41513_of_match, + }, + .probe = adf41513_probe, + .id_table = adf41513_id, +}; +module_spi_driver(adf41513_driver); + +MODULE_AUTHOR("Rodrigo Alencar "); +MODULE_DESCRIPTION("Analog Devices ADF41513 PLL Frequency Synthesizer"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/frequency/adf4350.c b/drivers/iio/frequency/adf4350.c new file mode 100644 index 000000000..315317d6e --- /dev/null +++ b/drivers/iio/frequency/adf4350.c @@ -0,0 +1,712 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADF4350/ADF4351 SPI Wideband Synthesizer driver + * + * Copyright 2012-2013 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +enum { + ADF4350_FREQ, + ADF4350_FREQ_REFIN, + ADF4350_FREQ_RESOLUTION, + ADF4350_PWRDOWN, +}; + +struct adf4350_state { + struct spi_device *spi; + struct gpio_desc *lock_detect_gpiod; + struct adf4350_platform_data *pdata; + struct clk *clk; + struct clk *clkout; + const char *clk_out_name; + struct clk_hw hw; + unsigned long clkin; + unsigned long chspc; /* Channel Spacing */ + unsigned long fpfd; /* Phase Frequency Detector */ + unsigned long min_out_freq; + unsigned r0_fract; + unsigned r0_int; + unsigned r1_mod; + unsigned r4_rf_div_sel; + unsigned long regs[6]; + unsigned long regs_hw[6]; + unsigned long long freq_req; + /* + * Lock to protect the state of the device from potential concurrent + * writes. The device is configured via a sequence of SPI writes, + * and this lock is meant to prevent the start of another sequence + * before another one has finished. + */ + struct mutex lock; + /* + * DMA (thus cache coherency maintenance) may require that + * transfer buffers live in their own cache lines. + */ + __be32 val __aligned(IIO_DMA_MINALIGN); +}; + +#define to_adf4350_state(_hw) container_of(_hw, struct adf4350_state, hw) + +static struct adf4350_platform_data default_pdata = { + .channel_spacing = 10000, + .r2_user_settings = ADF4350_REG2_PD_POLARITY_POS | + ADF4350_REG2_CHARGE_PUMP_CURR_uA(2500), + .r3_user_settings = ADF4350_REG3_12BIT_CLKDIV_MODE(0), + .r4_user_settings = ADF4350_REG4_OUTPUT_PWR(3) | + ADF4350_REG4_MUTE_TILL_LOCK_EN, +}; + +static int adf4350_sync_config(struct adf4350_state *st) +{ + int ret, i, doublebuf = 0; + + for (i = ADF4350_REG5; i >= ADF4350_REG0; i--) { + if ((st->regs_hw[i] != st->regs[i]) || + ((i == ADF4350_REG0) && doublebuf)) { + switch (i) { + case ADF4350_REG1: + case ADF4350_REG4: + doublebuf = 1; + break; + } + + st->val = cpu_to_be32(st->regs[i] | i); + ret = spi_write(st->spi, &st->val, 4); + if (ret < 0) + return ret; + st->regs_hw[i] = st->regs[i]; + dev_dbg(&st->spi->dev, "[%d] 0x%X\n", + i, (u32)st->regs[i] | i); + } + } + return 0; +} + +static int adf4350_reg_access(struct iio_dev *indio_dev, + unsigned reg, unsigned writeval, + unsigned *readval) +{ + struct adf4350_state *st = iio_priv(indio_dev); + int ret; + + if (reg > ADF4350_REG5) + return -EINVAL; + + mutex_lock(&st->lock); + if (readval == NULL) { + st->regs[reg] = writeval & ~(BIT(0) | BIT(1) | BIT(2)); + ret = adf4350_sync_config(st); + } else { + *readval = st->regs_hw[reg]; + ret = 0; + } + mutex_unlock(&st->lock); + + return ret; +} + +static int adf4350_tune_r_cnt(struct adf4350_state *st, unsigned short r_cnt) +{ + struct adf4350_platform_data *pdata = st->pdata; + + do { + r_cnt++; + st->fpfd = (st->clkin * (pdata->ref_doubler_en ? 2 : 1)) / + (r_cnt * (pdata->ref_div2_en ? 2 : 1)); + } while (st->fpfd > ADF4350_MAX_FREQ_PFD); + + return r_cnt; +} + +static int adf4350_set_freq(struct adf4350_state *st, unsigned long long freq) +{ + struct adf4350_platform_data *pdata = st->pdata; + u64 tmp; + u32 div_gcd, prescaler, chspc; + u16 mdiv, r_cnt = 0; + u8 band_sel_div; + + if (freq > ADF4350_MAX_OUT_FREQ || freq < st->min_out_freq) + return -EINVAL; + + st->r4_rf_div_sel = 0; + + /* + * NOTE: This iteratively shifts freq by a power of 2 + * (st->r4_rf_div_sel) to meet or exceed ADF4350_MIN_VCO_FREQ. + * A constant-time approach using fls_long() was attempted but + * deemed more complex without meaningful benefit for init code. + */ + while (freq < ADF4350_MIN_VCO_FREQ) { + freq <<= 1; + st->r4_rf_div_sel++; + } + + if (freq > ADF4350_MAX_FREQ_45_PRESC) { + prescaler = ADF4350_REG1_PRESCALER; + mdiv = 75; + } else { + prescaler = 0; + mdiv = 23; + } + + /* + * Allow a predefined reference division factor + * if not set, compute our own + */ + if (pdata->ref_div_factor) + r_cnt = pdata->ref_div_factor - 1; + + chspc = st->chspc; + + do { + do { + do { + r_cnt = adf4350_tune_r_cnt(st, r_cnt); + st->r1_mod = st->fpfd / chspc; + if (r_cnt > ADF4350_MAX_R_CNT) { + /* try higher spacing values */ + chspc++; + r_cnt = 0; + } + } while ((st->r1_mod > ADF4350_MAX_MODULUS) && r_cnt); + } while (r_cnt == 0); + + tmp = freq * (u64)st->r1_mod + (st->fpfd >> 1); + do_div(tmp, st->fpfd); /* Div round closest (n + d/2)/d */ + st->r0_fract = do_div(tmp, st->r1_mod); + st->r0_int = tmp; + } while (mdiv > st->r0_int); + + band_sel_div = DIV_ROUND_UP(st->fpfd, ADF4350_MAX_BANDSEL_CLK); + + if (st->r0_fract && st->r1_mod) { + div_gcd = gcd(st->r1_mod, st->r0_fract); + st->r1_mod /= div_gcd; + st->r0_fract /= div_gcd; + } else { + st->r0_fract = 0; + st->r1_mod = 1; + } + + dev_dbg(&st->spi->dev, "VCO: %llu Hz, PFD %lu Hz\n" + "REF_DIV %d, R0_INT %d, R0_FRACT %d\n" + "R1_MOD %d, RF_DIV %d\nPRESCALER %s, BAND_SEL_DIV %d\n", + freq, st->fpfd, r_cnt, st->r0_int, st->r0_fract, st->r1_mod, + 1 << st->r4_rf_div_sel, prescaler ? "8/9" : "4/5", + band_sel_div); + + st->regs[ADF4350_REG0] = ADF4350_REG0_INT(st->r0_int) | + ADF4350_REG0_FRACT(st->r0_fract); + + st->regs[ADF4350_REG1] = ADF4350_REG1_PHASE(1) | + ADF4350_REG1_MOD(st->r1_mod) | + prescaler; + + st->regs[ADF4350_REG2] = + ADF4350_REG2_10BIT_R_CNT(r_cnt) | + ADF4350_REG2_DOUBLE_BUFF_EN | + (pdata->ref_doubler_en ? ADF4350_REG2_RMULT2_EN : 0) | + (pdata->ref_div2_en ? ADF4350_REG2_RDIV2_EN : 0) | + (pdata->r2_user_settings & (ADF4350_REG2_PD_POLARITY_POS | + ADF4350_REG2_LDP_6ns | ADF4350_REG2_LDF_INT_N | + ADF4350_REG2_CHARGE_PUMP_CURR_uA(5000) | + ADF4350_REG2_MUXOUT(0x7) | ADF4350_REG2_NOISE_MODE(0x3))); + + st->regs[ADF4350_REG3] = pdata->r3_user_settings & + (ADF4350_REG3_12BIT_CLKDIV(0xFFF) | + ADF4350_REG3_12BIT_CLKDIV_MODE(0x3) | + ADF4350_REG3_12BIT_CSR_EN | + ADF4351_REG3_CHARGE_CANCELLATION_EN | + ADF4351_REG3_ANTI_BACKLASH_3ns_EN | + ADF4351_REG3_BAND_SEL_CLOCK_MODE_HIGH); + + st->regs[ADF4350_REG4] = + ADF4350_REG4_FEEDBACK_FUND | + ADF4350_REG4_RF_DIV_SEL(st->r4_rf_div_sel) | + ADF4350_REG4_8BIT_BAND_SEL_CLKDIV(band_sel_div) | + ADF4350_REG4_RF_OUT_EN | + (pdata->r4_user_settings & + (ADF4350_REG4_OUTPUT_PWR(0x3) | + ADF4350_REG4_AUX_OUTPUT_PWR(0x3) | + ADF4350_REG4_AUX_OUTPUT_EN | + ADF4350_REG4_AUX_OUTPUT_FUND | + ADF4350_REG4_MUTE_TILL_LOCK_EN)); + + st->regs[ADF4350_REG5] = ADF4350_REG5_LD_PIN_MODE_DIGITAL; + st->freq_req = freq; + + return adf4350_sync_config(st); +} + +static ssize_t adf4350_write(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct adf4350_state *st = iio_priv(indio_dev); + unsigned long long readin; + unsigned long tmp; + int ret; + + ret = kstrtoull(buf, 10, &readin); + if (ret) + return ret; + + mutex_lock(&st->lock); + switch ((u32)private) { + case ADF4350_FREQ: + ret = adf4350_set_freq(st, readin); + break; + case ADF4350_FREQ_REFIN: + if (readin > ADF4350_MAX_FREQ_REFIN) { + ret = -EINVAL; + break; + } + + if (st->clk) { + tmp = clk_round_rate(st->clk, readin); + if (tmp != readin) { + ret = -EINVAL; + break; + } + ret = clk_set_rate(st->clk, tmp); + if (ret < 0) + break; + } + st->clkin = readin; + ret = adf4350_set_freq(st, st->freq_req); + break; + case ADF4350_FREQ_RESOLUTION: + if (readin == 0) + ret = -EINVAL; + else + st->chspc = readin; + break; + case ADF4350_PWRDOWN: + if (readin) + st->regs[ADF4350_REG2] |= ADF4350_REG2_POWER_DOWN_EN; + else + st->regs[ADF4350_REG2] &= ~ADF4350_REG2_POWER_DOWN_EN; + + adf4350_sync_config(st); + break; + default: + ret = -EINVAL; + } + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +static ssize_t adf4350_read(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct adf4350_state *st = iio_priv(indio_dev); + unsigned long long val; + int ret = 0; + + mutex_lock(&st->lock); + switch ((u32)private) { + case ADF4350_FREQ: + val = (u64)((st->r0_int * st->r1_mod) + st->r0_fract) * + (u64)st->fpfd; + do_div(val, st->r1_mod * (1 << st->r4_rf_div_sel)); + /* PLL unlocked? return error */ + if (st->lock_detect_gpiod) + if (!gpiod_get_value(st->lock_detect_gpiod)) { + dev_dbg(&st->spi->dev, "PLL un-locked\n"); + ret = -EBUSY; + } + break; + case ADF4350_FREQ_REFIN: + if (st->clk) + st->clkin = clk_get_rate(st->clk); + + val = st->clkin; + break; + case ADF4350_FREQ_RESOLUTION: + val = st->chspc; + break; + case ADF4350_PWRDOWN: + val = !!(st->regs[ADF4350_REG2] & ADF4350_REG2_POWER_DOWN_EN); + break; + default: + ret = -EINVAL; + val = 0; + } + mutex_unlock(&st->lock); + + return ret < 0 ? ret : sprintf(buf, "%llu\n", val); +} + +#define _ADF4350_EXT_INFO(_name, _ident) { \ + .name = _name, \ + .read = adf4350_read, \ + .write = adf4350_write, \ + .private = _ident, \ + .shared = IIO_SEPARATE, \ +} + +static const struct iio_chan_spec_ext_info adf4350_ext_info[] = { + /* Ideally we use IIO_CHAN_INFO_FREQUENCY, but there are + * values > 2^32 in order to support the entire frequency range + * in Hz. Using scale is a bit ugly. + */ + _ADF4350_EXT_INFO("frequency", ADF4350_FREQ), + _ADF4350_EXT_INFO("frequency_resolution", ADF4350_FREQ_RESOLUTION), + _ADF4350_EXT_INFO("refin_frequency", ADF4350_FREQ_REFIN), + _ADF4350_EXT_INFO("powerdown", ADF4350_PWRDOWN), + { } +}; + +static const struct iio_chan_spec adf4350_chan = { + .type = IIO_ALTVOLTAGE, + .indexed = 1, + .output = 1, + .ext_info = adf4350_ext_info, +}; + +static const struct iio_info adf4350_info = { + .debugfs_reg_access = &adf4350_reg_access, +}; + +static void adf4350_clk_del_provider(void *data) +{ + struct adf4350_state *st = data; + + of_clk_del_provider(st->spi->dev.of_node); +} + +static unsigned long adf4350_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct adf4350_state *st = to_adf4350_state(hw); + unsigned long long tmp; + + tmp = (u64)(st->r0_int * st->r1_mod + st->r0_fract) * st->fpfd; + do_div(tmp, st->r1_mod * (1 << st->r4_rf_div_sel)); + + return tmp; +} + +static int adf4350_clk_set_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct adf4350_state *st = to_adf4350_state(hw); + + if (parent_rate == 0 || parent_rate > ADF4350_MAX_FREQ_REFIN) + return -EINVAL; + + st->clkin = parent_rate; + + return adf4350_set_freq(st, rate); +} + +static int adf4350_clk_prepare(struct clk_hw *hw) +{ + struct adf4350_state *st = to_adf4350_state(hw); + + st->regs[ADF4350_REG2] &= ~ADF4350_REG2_POWER_DOWN_EN; + + return adf4350_sync_config(st); +} + +static void adf4350_clk_unprepare(struct clk_hw *hw) +{ + struct adf4350_state *st = to_adf4350_state(hw); + + st->regs[ADF4350_REG2] |= ADF4350_REG2_POWER_DOWN_EN; + + adf4350_sync_config(st); +} + +static int adf4350_clk_is_enabled(struct clk_hw *hw) +{ + struct adf4350_state *st = to_adf4350_state(hw); + + return (st->regs[ADF4350_REG2] & ADF4350_REG2_POWER_DOWN_EN); +} + +static const struct clk_ops adf4350_clk_ops = { + .recalc_rate = adf4350_clk_recalc_rate, + .set_rate = adf4350_clk_set_rate, + .prepare = adf4350_clk_prepare, + .unprepare = adf4350_clk_unprepare, + .is_enabled = adf4350_clk_is_enabled, +}; + +static int adf4350_clk_register(struct adf4350_state *st) +{ + struct spi_device *spi = st->spi; + struct clk_init_data init; + struct clk *clk; + const char *parent_name; + int ret; + + if (!device_property_present(&spi->dev, "#clock-cells")) + return 0; + + if (device_property_read_string(&spi->dev, "clock-output-names", &init.name)) { + init.name = devm_kasprintf(&spi->dev, GFP_KERNEL, "%s-clk", + fwnode_get_name(dev_fwnode(&spi->dev))); + if (!init.name) + return -ENOMEM; + } + + parent_name = of_clk_get_parent_name(spi->dev.of_node, 0); + if (!parent_name) + return -EINVAL; + + init.ops = &adf4350_clk_ops; + init.parent_names = &parent_name; + init.num_parents = 1; + init.flags = CLK_SET_RATE_PARENT; + + st->hw.init = &init; + clk = devm_clk_register(&spi->dev, &st->hw); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + ret = of_clk_add_provider(spi->dev.of_node, of_clk_src_simple_get, clk); + if (ret) + return ret; + + st->clkout = clk; + + return devm_add_action_or_reset(&spi->dev, adf4350_clk_del_provider, st); +} + +static struct adf4350_platform_data *adf4350_parse_dt(struct device *dev) +{ + struct adf4350_platform_data *pdata; + unsigned int tmp; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + snprintf(pdata->name, sizeof(pdata->name), "%pfw", dev_fwnode(dev)); + + tmp = 10000; + device_property_read_u32(dev, "adi,channel-spacing", &tmp); + pdata->channel_spacing = tmp; + + tmp = 0; + device_property_read_u32(dev, "adi,power-up-frequency", &tmp); + pdata->power_up_frequency = tmp; + + tmp = 0; + device_property_read_u32(dev, "adi,reference-div-factor", &tmp); + pdata->ref_div_factor = tmp; + + pdata->ref_doubler_en = device_property_read_bool(dev, "adi,reference-doubler-enable"); + pdata->ref_div2_en = device_property_read_bool(dev, "adi,reference-div2-enable"); + + /* r2_user_settings */ + pdata->r2_user_settings = 0; + if (device_property_read_bool(dev, "adi,phase-detector-polarity-positive-enable")) + pdata->r2_user_settings |= ADF4350_REG2_PD_POLARITY_POS; + if (device_property_read_bool(dev, "adi,lock-detect-precision-6ns-enable")) + pdata->r2_user_settings |= ADF4350_REG2_LDP_6ns; + if (device_property_read_bool(dev, "adi,lock-detect-function-integer-n-enable")) + pdata->r2_user_settings |= ADF4350_REG2_LDF_INT_N; + + tmp = 2500; + device_property_read_u32(dev, "adi,charge-pump-current", &tmp); + pdata->r2_user_settings |= ADF4350_REG2_CHARGE_PUMP_CURR_uA(tmp); + + tmp = 0; + device_property_read_u32(dev, "adi,muxout-select", &tmp); + pdata->r2_user_settings |= ADF4350_REG2_MUXOUT(tmp); + + if (device_property_read_bool(dev, "adi,low-spur-mode-enable")) + pdata->r2_user_settings |= ADF4350_REG2_NOISE_MODE(0x3); + + /* r3_user_settings */ + + pdata->r3_user_settings = 0; + if (device_property_read_bool(dev, "adi,cycle-slip-reduction-enable")) + pdata->r3_user_settings |= ADF4350_REG3_12BIT_CSR_EN; + if (device_property_read_bool(dev, "adi,charge-cancellation-enable")) + pdata->r3_user_settings |= ADF4351_REG3_CHARGE_CANCELLATION_EN; + if (device_property_read_bool(dev, "adi,anti-backlash-3ns-enable")) + pdata->r3_user_settings |= ADF4351_REG3_ANTI_BACKLASH_3ns_EN; + if (device_property_read_bool(dev, "adi,band-select-clock-mode-high-enable")) + pdata->r3_user_settings |= ADF4351_REG3_BAND_SEL_CLOCK_MODE_HIGH; + + tmp = 0; + device_property_read_u32(dev, "adi,12bit-clk-divider", &tmp); + pdata->r3_user_settings |= ADF4350_REG3_12BIT_CLKDIV(tmp); + + tmp = 0; + device_property_read_u32(dev, "adi,clk-divider-mode", &tmp); + pdata->r3_user_settings |= ADF4350_REG3_12BIT_CLKDIV_MODE(tmp); + + /* r4_user_settings */ + + pdata->r4_user_settings = 0; + if (device_property_read_bool(dev, "adi,aux-output-enable")) + pdata->r4_user_settings |= ADF4350_REG4_AUX_OUTPUT_EN; + if (device_property_read_bool(dev, "adi,aux-output-fundamental-enable")) + pdata->r4_user_settings |= ADF4350_REG4_AUX_OUTPUT_FUND; + if (device_property_read_bool(dev, "adi,mute-till-lock-enable")) + pdata->r4_user_settings |= ADF4350_REG4_MUTE_TILL_LOCK_EN; + + tmp = 0; + device_property_read_u32(dev, "adi,output-power", &tmp); + pdata->r4_user_settings |= ADF4350_REG4_OUTPUT_PWR(tmp); + + tmp = 0; + device_property_read_u32(dev, "adi,aux-output-power", &tmp); + pdata->r4_user_settings |= ADF4350_REG4_AUX_OUTPUT_PWR(tmp); + + return pdata; +} + +static void adf4350_power_down(void *data) +{ + struct iio_dev *indio_dev = data; + struct adf4350_state *st = iio_priv(indio_dev); + + st->regs[ADF4350_REG2] |= ADF4350_REG2_POWER_DOWN_EN; + adf4350_sync_config(st); +} + +static int adf4350_probe(struct spi_device *spi) +{ + struct adf4350_platform_data *pdata; + struct iio_dev *indio_dev; + struct adf4350_state *st; + struct clk *clk = NULL; + int ret; + + if (dev_fwnode(&spi->dev)) { + pdata = adf4350_parse_dt(&spi->dev); + if (pdata == NULL) + return -ENOMEM; + } else { + pdata = dev_get_platdata(&spi->dev); + } + + if (!pdata) { + dev_warn(&spi->dev, "no platform data? using default\n"); + pdata = &default_pdata; + } + + if (!pdata->clkin) { + clk = devm_clk_get_enabled(&spi->dev, "clkin"); + if (IS_ERR(clk)) + return PTR_ERR(clk); + } + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + ret = devm_regulator_get_enable(&spi->dev, "vcc"); + if (ret) + return ret; + + st->spi = spi; + st->pdata = pdata; + + indio_dev->name = (pdata->name[0] != 0) ? pdata->name : + spi_get_device_id(spi)->name; + + indio_dev->info = &adf4350_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + mutex_init(&st->lock); + + st->chspc = pdata->channel_spacing; + if (clk) { + st->clk = clk; + st->clkin = clk_get_rate(clk); + } else { + st->clkin = pdata->clkin; + } + + st->min_out_freq = spi_get_device_id(spi)->driver_data == 4351 ? + ADF4351_MIN_OUT_FREQ : ADF4350_MIN_OUT_FREQ; + + memset(st->regs_hw, 0xFF, sizeof(st->regs_hw)); + + st->lock_detect_gpiod = devm_gpiod_get_optional(&spi->dev, NULL, + GPIOD_IN); + if (IS_ERR(st->lock_detect_gpiod)) + return PTR_ERR(st->lock_detect_gpiod); + + if (pdata->power_up_frequency) { + ret = adf4350_set_freq(st, pdata->power_up_frequency); + if (ret) + return ret; + } + + ret = adf4350_clk_register(st); + if (ret) + return ret; + + if (!st->clkout) { + indio_dev->channels = &adf4350_chan; + indio_dev->num_channels = 1; + } + + ret = devm_add_action_or_reset(&spi->dev, adf4350_power_down, indio_dev); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct of_device_id adf4350_of_match[] = { + { .compatible = "adi,adf4350", }, + { .compatible = "adi,adf4351", }, + { } +}; +MODULE_DEVICE_TABLE(of, adf4350_of_match); + +static const struct spi_device_id adf4350_id[] = { + { .name = "adf4350", .driver_data = 4350 }, + { .name = "adf4351", .driver_data = 4351 }, + { } +}; +MODULE_DEVICE_TABLE(spi, adf4350_id); + +static struct spi_driver adf4350_driver = { + .driver = { + .name = "adf4350", + .of_match_table = adf4350_of_match, + }, + .probe = adf4350_probe, + .id_table = adf4350_id, +}; +module_spi_driver(adf4350_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices ADF4350/ADF4351 PLL"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/frequency/adf4371.c b/drivers/iio/frequency/adf4371.c new file mode 100644 index 000000000..e20ed718d --- /dev/null +++ b/drivers/iio/frequency/adf4371.c @@ -0,0 +1,652 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices ADF4371 SPI Wideband Synthesizer driver + * + * Copyright 2019 Analog Devices Inc. + */ +#include "linux/dev_printk.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Registers address macro */ +#define ADF4371_REG(x) (x) + +/* ADF4371_REG0 */ +#define ADF4371_ADDR_ASC_MSK BIT(2) +#define ADF4371_ADDR_ASC(x) FIELD_PREP(ADF4371_ADDR_ASC_MSK, x) +#define ADF4371_ADDR_ASC_R_MSK BIT(5) +#define ADF4371_ADDR_ASC_R(x) FIELD_PREP(ADF4371_ADDR_ASC_R_MSK, x) +#define ADF4371_RESET_CMD 0x81 + +/* ADF4371_REG17 */ +#define ADF4371_FRAC2WORD_L_MSK GENMASK(7, 1) +#define ADF4371_FRAC2WORD_L(x) FIELD_PREP(ADF4371_FRAC2WORD_L_MSK, x) +#define ADF4371_FRAC1WORD_MSK BIT(0) +#define ADF4371_FRAC1WORD(x) FIELD_PREP(ADF4371_FRAC1WORD_MSK, x) + +/* ADF4371_REG18 */ +#define ADF4371_FRAC2WORD_H_MSK GENMASK(6, 0) +#define ADF4371_FRAC2WORD_H(x) FIELD_PREP(ADF4371_FRAC2WORD_H_MSK, x) + +/* ADF4371_REG1A */ +#define ADF4371_MOD2WORD_MSK GENMASK(5, 0) +#define ADF4371_MOD2WORD(x) FIELD_PREP(ADF4371_MOD2WORD_MSK, x) + +/* ADF4371_REG22 */ +#define ADF4371_REFIN_MODE_MASK BIT(6) +#define ADF4371_REFIN_MODE(x) FIELD_PREP(ADF4371_REFIN_MODE_MASK, x) +#define ADF4371_REF_DOUB_MASK BIT(5) +#define ADF4371_REF_DOUB(x) FIELD_PREP(ADF4371_REF_DOUB_MASK, x)\ + +/* ADF4371_REG24 */ +#define ADF4371_RF_DIV_SEL_MSK GENMASK(6, 4) +#define ADF4371_RF_DIV_SEL(x) FIELD_PREP(ADF4371_RF_DIV_SEL_MSK, x) + +/* ADF4371_REG25 */ +#define ADF4371_MUTE_LD_MSK BIT(7) +#define ADF4371_MUTE_LD(x) FIELD_PREP(ADF4371_MUTE_LD_MSK, x) + +/* ADF4371_REG32 */ +#define ADF4371_TIMEOUT_MSK GENMASK(1, 0) +#define ADF4371_TIMEOUT(x) FIELD_PREP(ADF4371_TIMEOUT_MSK, x) + +/* ADF4371_REG34 */ +#define ADF4371_VCO_ALC_TOUT_MSK GENMASK(4, 0) +#define ADF4371_VCO_ALC_TOUT(x) FIELD_PREP(ADF4371_VCO_ALC_TOUT_MSK, x) + +/* Specifications */ +#define ADF4371_MIN_VCO_FREQ 4000000000ULL /* 4000 MHz */ +#define ADF4371_MAX_VCO_FREQ 8000000000ULL /* 8000 MHz */ +#define ADF4371_MAX_OUT_RF8_FREQ ADF4371_MAX_VCO_FREQ /* Hz */ +#define ADF4371_MIN_OUT_RF8_FREQ (ADF4371_MIN_VCO_FREQ / 64) /* Hz */ +#define ADF4371_MAX_OUT_RF16_FREQ (ADF4371_MAX_VCO_FREQ * 2) /* Hz */ +#define ADF4371_MIN_OUT_RF16_FREQ (ADF4371_MIN_VCO_FREQ * 2) /* Hz */ +#define ADF4371_MAX_OUT_RF32_FREQ (ADF4371_MAX_VCO_FREQ * 4) /* Hz */ +#define ADF4371_MIN_OUT_RF32_FREQ (ADF4371_MIN_VCO_FREQ * 4) /* Hz */ + +#define ADF4371_MAX_FREQ_PFD 250000000UL /* Hz */ +#define ADF4371_MAX_FREQ_REFIN 600000000UL /* Hz */ +#define ADF4371_MAX_FREQ_REFIN_SE 500000000UL /* Hz */ + +#define ADF4371_MIN_CLKIN_DOUB_FREQ 10000000ULL /* Hz */ +#define ADF4371_MAX_CLKIN_DOUB_FREQ 125000000ULL /* Hz */ + +/* MOD1 is a 24-bit primary modulus with fixed value of 2^25 */ +#define ADF4371_MODULUS1 33554432ULL +/* MOD2 is the programmable, 14-bit auxiliary fractional modulus */ +#define ADF4371_MAX_MODULUS2 BIT(14) + +#define ADF4371_CHECK_RANGE(freq, range) \ + ((freq > ADF4371_MAX_ ## range) || (freq < ADF4371_MIN_ ## range)) + +enum { + ADF4371_FREQ, + ADF4371_POWER_DOWN, + ADF4371_CHANNEL_NAME +}; + +enum { + ADF4371_CH_RF8, + ADF4371_CH_RFAUX8, + ADF4371_CH_RF16, + ADF4371_CH_RF32 +}; + +enum adf4371_variant { + ADF4371, + ADF4372 +}; + +struct adf4371_pwrdown { + unsigned int reg; + unsigned int bit; +}; + +static const char * const adf4371_ch_names[] = { + "RF8x", "RFAUX8x", "RF16x", "RF32x" +}; + +static const struct adf4371_pwrdown adf4371_pwrdown_ch[4] = { + [ADF4371_CH_RF8] = { ADF4371_REG(0x25), 2 }, + [ADF4371_CH_RFAUX8] = { ADF4371_REG(0x72), 3 }, + [ADF4371_CH_RF16] = { ADF4371_REG(0x25), 3 }, + [ADF4371_CH_RF32] = { ADF4371_REG(0x25), 4 }, +}; + +static const struct reg_sequence adf4371_reg_defaults[] = { + { ADF4371_REG(0x0), 0x18 }, + { ADF4371_REG(0x12), 0x40 }, + { ADF4371_REG(0x1E), 0x48 }, + { ADF4371_REG(0x20), 0x14 }, + { ADF4371_REG(0x22), 0x00 }, + { ADF4371_REG(0x23), 0x00 }, + { ADF4371_REG(0x24), 0x80 }, + { ADF4371_REG(0x25), 0x07 }, + { ADF4371_REG(0x27), 0xC5 }, + { ADF4371_REG(0x28), 0x83 }, + { ADF4371_REG(0x2C), 0x44 }, + { ADF4371_REG(0x2D), 0x11 }, + { ADF4371_REG(0x2E), 0x12 }, + { ADF4371_REG(0x2F), 0x94 }, + { ADF4371_REG(0x32), 0x04 }, + { ADF4371_REG(0x35), 0xFA }, + { ADF4371_REG(0x36), 0x30 }, + { ADF4371_REG(0x39), 0x07 }, + { ADF4371_REG(0x3A), 0x55 }, + { ADF4371_REG(0x3E), 0x0C }, + { ADF4371_REG(0x3F), 0x80 }, + { ADF4371_REG(0x40), 0x50 }, + { ADF4371_REG(0x41), 0x28 }, + { ADF4371_REG(0x47), 0xC0 }, + { ADF4371_REG(0x52), 0xF4 }, + { ADF4371_REG(0x70), 0x03 }, + { ADF4371_REG(0x71), 0x60 }, + { ADF4371_REG(0x72), 0x32 }, +}; + +static const struct regmap_config adf4371_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .read_flag_mask = BIT(7), +}; + +struct adf4371_chip_info { + const char *name; + unsigned int num_channels; + const struct iio_chan_spec *channels; +}; + +struct adf4371_state { + struct spi_device *spi; + struct regmap *regmap; + /* + * Lock for accessing device registers. Some operations require + * multiple consecutive R/W operations, during which the device + * shouldn't be interrupted. The buffers are also shared across + * all operations so need to be protected on stand alone reads and + * writes. + */ + struct mutex lock; + const struct adf4371_chip_info *chip_info; + unsigned long clkin_freq; + unsigned long fpfd; + unsigned int integer; + unsigned int fract1; + unsigned int fract2; + unsigned int mod2; + unsigned int rf_div_sel; + unsigned int ref_div_factor; + bool ref_diff_en; + u8 buf[10] __aligned(IIO_DMA_MINALIGN); +}; + +static unsigned long long adf4371_pll_fract_n_get_rate(struct adf4371_state *st, + u32 channel) +{ + unsigned long long val, tmp; + unsigned int ref_div_sel; + + val = (((u64)st->integer * ADF4371_MODULUS1) + st->fract1) * st->fpfd; + tmp = (u64)st->fract2 * st->fpfd; + do_div(tmp, st->mod2); + val += tmp + ADF4371_MODULUS1 / 2; + + if (channel == ADF4371_CH_RF8 || channel == ADF4371_CH_RFAUX8) + ref_div_sel = st->rf_div_sel; + else + ref_div_sel = 0; + + do_div(val, ADF4371_MODULUS1 * (1 << ref_div_sel)); + + if (channel == ADF4371_CH_RF16) + val <<= 1; + else if (channel == ADF4371_CH_RF32) + val <<= 2; + + return val; +} + +static void adf4371_pll_fract_n_compute(unsigned long long vco, + unsigned long long pfd, + unsigned int *integer, + unsigned int *fract1, + unsigned int *fract2, + unsigned int *mod2) +{ + unsigned long long tmp; + u32 gcd_div; + + tmp = do_div(vco, pfd); + tmp = tmp * ADF4371_MODULUS1; + *fract2 = do_div(tmp, pfd); + + *integer = vco; + *fract1 = tmp; + + *mod2 = pfd; + + while (*mod2 > ADF4371_MAX_MODULUS2) { + *mod2 >>= 1; + *fract2 >>= 1; + } + + gcd_div = gcd(*fract2, *mod2); + *mod2 /= gcd_div; + *fract2 /= gcd_div; +} + +static int adf4371_set_freq(struct adf4371_state *st, unsigned long long freq, + unsigned int channel) +{ + u32 cp_bleed; + u8 int_mode = 0; + int ret; + + switch (channel) { + case ADF4371_CH_RF8: + case ADF4371_CH_RFAUX8: + if (ADF4371_CHECK_RANGE(freq, OUT_RF8_FREQ)) + return -EINVAL; + + st->rf_div_sel = 0; + + while (freq < ADF4371_MIN_VCO_FREQ) { + freq <<= 1; + st->rf_div_sel++; + } + break; + case ADF4371_CH_RF16: + /* ADF4371 RF16 8000...16000 MHz */ + if (ADF4371_CHECK_RANGE(freq, OUT_RF16_FREQ)) + return -EINVAL; + + freq >>= 1; + break; + case ADF4371_CH_RF32: + /* ADF4371 RF32 16000...32000 MHz */ + if (ADF4371_CHECK_RANGE(freq, OUT_RF32_FREQ)) + return -EINVAL; + + freq >>= 2; + break; + default: + return -EINVAL; + } + + adf4371_pll_fract_n_compute(freq, st->fpfd, &st->integer, &st->fract1, + &st->fract2, &st->mod2); + st->buf[0] = st->integer >> 8; + st->buf[1] = 0x40; /* REG12 default */ + st->buf[2] = 0x00; + st->buf[3] = st->fract1 & 0xFF; + st->buf[4] = st->fract1 >> 8; + st->buf[5] = st->fract1 >> 16; + st->buf[6] = ADF4371_FRAC2WORD_L(st->fract2 & 0x7F) | + ADF4371_FRAC1WORD(st->fract1 >> 24); + st->buf[7] = ADF4371_FRAC2WORD_H(st->fract2 >> 7); + st->buf[8] = st->mod2 & 0xFF; + st->buf[9] = ADF4371_MOD2WORD(st->mod2 >> 8); + + ret = regmap_bulk_write(st->regmap, ADF4371_REG(0x11), st->buf, 10); + if (ret < 0) + return ret; + /* + * The R counter allows the input reference frequency to be + * divided down to produce the reference clock to the PFD + */ + ret = regmap_write(st->regmap, ADF4371_REG(0x1F), st->ref_div_factor); + if (ret < 0) + return ret; + + ret = regmap_update_bits(st->regmap, ADF4371_REG(0x24), + ADF4371_RF_DIV_SEL_MSK, + ADF4371_RF_DIV_SEL(st->rf_div_sel)); + if (ret < 0) + return ret; + + cp_bleed = DIV_ROUND_UP(400 * 1750, st->integer * 375); + cp_bleed = clamp(cp_bleed, 1U, 255U); + ret = regmap_write(st->regmap, ADF4371_REG(0x26), cp_bleed); + if (ret < 0) + return ret; + /* + * Set to 1 when in INT mode (when FRAC1 = FRAC2 = 0), + * and set to 0 when in FRAC mode. + */ + if (st->fract1 == 0 && st->fract2 == 0) + int_mode = 0x01; + + ret = regmap_write(st->regmap, ADF4371_REG(0x2B), int_mode); + if (ret < 0) + return ret; + + return regmap_write(st->regmap, ADF4371_REG(0x10), st->integer & 0xFF); +} + +static ssize_t adf4371_read(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct adf4371_state *st = iio_priv(indio_dev); + unsigned long long val = 0; + unsigned int readval, reg, bit; + int ret; + + switch ((u32)private) { + case ADF4371_FREQ: + val = adf4371_pll_fract_n_get_rate(st, chan->channel); + ret = regmap_read(st->regmap, ADF4371_REG(0x7C), &readval); + if (ret < 0) + break; + + if (readval == 0x00) { + dev_dbg(&st->spi->dev, "PLL un-locked\n"); + ret = -EBUSY; + } + break; + case ADF4371_POWER_DOWN: + reg = adf4371_pwrdown_ch[chan->channel].reg; + bit = adf4371_pwrdown_ch[chan->channel].bit; + + ret = regmap_read(st->regmap, reg, &readval); + if (ret < 0) + break; + + val = !(readval & BIT(bit)); + break; + case ADF4371_CHANNEL_NAME: + return sprintf(buf, "%s\n", adf4371_ch_names[chan->channel]); + default: + ret = -EINVAL; + val = 0; + break; + } + + return ret < 0 ? ret : sprintf(buf, "%llu\n", val); +} + +static ssize_t adf4371_write(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct adf4371_state *st = iio_priv(indio_dev); + unsigned long long freq; + bool power_down; + unsigned int bit, readval, reg; + int ret; + + mutex_lock(&st->lock); + switch ((u32)private) { + case ADF4371_FREQ: + ret = kstrtoull(buf, 10, &freq); + if (ret) + break; + + ret = adf4371_set_freq(st, freq, chan->channel); + break; + case ADF4371_POWER_DOWN: + ret = kstrtobool(buf, &power_down); + if (ret) + break; + + reg = adf4371_pwrdown_ch[chan->channel].reg; + bit = adf4371_pwrdown_ch[chan->channel].bit; + ret = regmap_read(st->regmap, reg, &readval); + if (ret < 0) + break; + + readval &= ~BIT(bit); + readval |= (!power_down << bit); + + ret = regmap_write(st->regmap, reg, readval); + break; + default: + ret = -EINVAL; + break; + } + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +#define _ADF4371_EXT_INFO(_name, _ident) { \ + .name = _name, \ + .read = adf4371_read, \ + .write = adf4371_write, \ + .private = _ident, \ + .shared = IIO_SEPARATE, \ +} + +static const struct iio_chan_spec_ext_info adf4371_ext_info[] = { + /* + * Ideally we use IIO_CHAN_INFO_FREQUENCY, but there are + * values > 2^32 in order to support the entire frequency range + * in Hz. Using scale is a bit ugly. + */ + _ADF4371_EXT_INFO("frequency", ADF4371_FREQ), + _ADF4371_EXT_INFO("powerdown", ADF4371_POWER_DOWN), + _ADF4371_EXT_INFO("name", ADF4371_CHANNEL_NAME), + { } +}; + +#define ADF4371_CHANNEL(index) { \ + .type = IIO_ALTVOLTAGE, \ + .output = 1, \ + .channel = index, \ + .ext_info = adf4371_ext_info, \ + .indexed = 1, \ + } + +static const struct iio_chan_spec adf4371_chan[] = { + ADF4371_CHANNEL(ADF4371_CH_RF8), + ADF4371_CHANNEL(ADF4371_CH_RFAUX8), + ADF4371_CHANNEL(ADF4371_CH_RF16), + ADF4371_CHANNEL(ADF4371_CH_RF32), +}; + +static const struct adf4371_chip_info adf4371_chip_info = { + .name = "adf4371", + .channels = adf4371_chan, + .num_channels = 4, +}; + +static const struct adf4371_chip_info adf4372_chip_info = { + .name = "adf4372", + .channels = adf4371_chan, + .num_channels = 3, +}; + +static int adf4371_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct adf4371_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + else + return regmap_write(st->regmap, reg, writeval); +} + +static const struct iio_info adf4371_info = { + .debugfs_reg_access = &adf4371_reg_access, +}; + +static int adf4371_setup(struct adf4371_state *st) +{ + unsigned int synth_timeout = 2, timeout = 1, vco_alc_timeout = 1; + unsigned int vco_band_div, tmp, ref_doubler_en = 0; + int ret; + + /* Perform a software reset */ + ret = regmap_write(st->regmap, ADF4371_REG(0x0), ADF4371_RESET_CMD); + if (ret < 0) + return ret; + + ret = regmap_multi_reg_write(st->regmap, adf4371_reg_defaults, + ARRAY_SIZE(adf4371_reg_defaults)); + if (ret < 0) + return ret; + + /* Mute to Lock Detect */ + if (device_property_read_bool(&st->spi->dev, "adi,mute-till-lock-en")) { + ret = regmap_update_bits(st->regmap, ADF4371_REG(0x25), + ADF4371_MUTE_LD_MSK, + ADF4371_MUTE_LD(1)); + if (ret < 0) + return ret; + } + + /* Set address in ascending order, so the bulk_write() will work */ + ret = regmap_update_bits(st->regmap, ADF4371_REG(0x0), + ADF4371_ADDR_ASC_MSK | ADF4371_ADDR_ASC_R_MSK, + ADF4371_ADDR_ASC(1) | ADF4371_ADDR_ASC_R(1)); + if (ret < 0) + return ret; + + if ((st->ref_diff_en && st->clkin_freq > ADF4371_MAX_FREQ_REFIN) || + (!st->ref_diff_en && st->clkin_freq > ADF4371_MAX_FREQ_REFIN_SE)) + return -EINVAL; + + if (st->clkin_freq < ADF4371_MAX_CLKIN_DOUB_FREQ && + st->clkin_freq > ADF4371_MIN_CLKIN_DOUB_FREQ) + ref_doubler_en = 1; + + ret = regmap_update_bits(st->regmap, ADF4371_REG(0x22), + ADF4371_REF_DOUB_MASK | + ADF4371_REFIN_MODE_MASK, + ADF4371_REF_DOUB(ref_doubler_en) | + ADF4371_REFIN_MODE(st->ref_diff_en)); + if (ret < 0) + return ret; + + /* + * Calculate and maximize PFD frequency + * fPFD = REFIN × ((1 + D)/(R × (1 + T))) + * Where D is the REFIN doubler bit, T is the reference divide by 2, + * R is the reference division factor + * TODO: it is assumed D and T equal 0. + */ + do { + st->ref_div_factor++; + st->fpfd = st->clkin_freq * (1 + ref_doubler_en) / + st->ref_div_factor; + } while (st->fpfd > ADF4371_MAX_FREQ_PFD); + + /* Calculate Timeouts */ + vco_band_div = DIV_ROUND_UP(st->fpfd, 2400000U); + + tmp = DIV_ROUND_CLOSEST(st->fpfd, 1000000U); + do { + timeout++; + if (timeout > 1023) { + timeout = 2; + synth_timeout++; + } + } while (synth_timeout * 1024 + timeout <= 20 * tmp); + + do { + vco_alc_timeout++; + } while (vco_alc_timeout * 1024 - timeout <= 50 * tmp); + + st->buf[0] = vco_band_div; + st->buf[1] = timeout & 0xFF; + st->buf[2] = ADF4371_TIMEOUT(timeout >> 8) | 0x04; + st->buf[3] = synth_timeout; + st->buf[4] = ADF4371_VCO_ALC_TOUT(vco_alc_timeout); + + return regmap_bulk_write(st->regmap, ADF4371_REG(0x30), st->buf, 5); +} + +static int adf4371_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct adf4371_state *st; + struct regmap *regmap; + struct clk *clkin; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_spi(spi, &adf4371_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(&spi->dev, PTR_ERR(regmap), + "Error initializing spi regmap\n"); + + st = iio_priv(indio_dev); + st->spi = spi; + st->regmap = regmap; + mutex_init(&st->lock); + + st->chip_info = spi_get_device_match_data(spi); + if (!st->chip_info) + return -ENODEV; + + indio_dev->name = st->chip_info->name; + indio_dev->info = &adf4371_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + + st->ref_diff_en = false; + + clkin = devm_clk_get_enabled(&spi->dev, "clkin"); + if (IS_ERR(clkin)) { + clkin = devm_clk_get_enabled(&spi->dev, "clkin-diff"); + if (IS_ERR(clkin)) + return dev_err_probe(&spi->dev, PTR_ERR(clkin), + "Failed to get clkin/clkin-diff\n"); + st->ref_diff_en = true; + } + + st->clkin_freq = clk_get_rate(clkin); + + ret = adf4371_setup(st); + if (ret < 0) + return dev_err_probe(&spi->dev, ret, "ADF4371 setup failed\n"); + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id adf4371_id_table[] = { + { .name = "adf4371", .driver_data = (kernel_ulong_t)&adf4371_chip_info }, + { .name = "adf4372", .driver_data = (kernel_ulong_t)&adf4372_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, adf4371_id_table); + +static const struct of_device_id adf4371_of_match[] = { + { .compatible = "adi,adf4371", .data = &adf4371_chip_info }, + { .compatible = "adi,adf4372", .data = &adf4372_chip_info}, + { } +}; +MODULE_DEVICE_TABLE(of, adf4371_of_match); + +static struct spi_driver adf4371_driver = { + .driver = { + .name = "adf4371", + .of_match_table = adf4371_of_match, + }, + .probe = adf4371_probe, + .id_table = adf4371_id_table, +}; +module_spi_driver(adf4371_driver); + +MODULE_AUTHOR("Stefan Popa "); +MODULE_DESCRIPTION("Analog Devices ADF4371 SPI PLL"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/frequency/adf4377.c b/drivers/iio/frequency/adf4377.c new file mode 100644 index 000000000..4dd19a9aa --- /dev/null +++ b/drivers/iio/frequency/adf4377.c @@ -0,0 +1,1116 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADF4377 driver + * + * Copyright 2022 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* ADF4377 REG0000 Map */ +#define ADF4377_0000_SOFT_RESET_R_MSK BIT(7) +#define ADF4377_0000_LSB_FIRST_R_MSK BIT(6) +#define ADF4377_0000_ADDRESS_ASC_R_MSK BIT(5) +#define ADF4377_0000_SDO_ACTIVE_R_MSK BIT(4) +#define ADF4377_0000_SDO_ACTIVE_MSK BIT(3) +#define ADF4377_0000_ADDRESS_ASC_MSK BIT(2) +#define ADF4377_0000_LSB_FIRST_MSK BIT(1) +#define ADF4377_0000_SOFT_RESET_MSK BIT(0) + +/* ADF4377 REG0000 Bit Definition */ +#define ADF4377_0000_SDO_ACTIVE_SPI_3W 0x0 +#define ADF4377_0000_SDO_ACTIVE_SPI_4W 0x1 + +#define ADF4377_0000_ADDR_ASC_AUTO_DECR 0x0 +#define ADF4377_0000_ADDR_ASC_AUTO_INCR 0x1 + +#define ADF4377_0000_LSB_FIRST_MSB 0x0 +#define ADF4377_0000_LSB_FIRST_LSB 0x1 + +#define ADF4377_0000_SOFT_RESET_N_OP 0x0 +#define ADF4377_0000_SOFT_RESET_EN 0x1 + +/* ADF4377 REG0001 Map */ +#define ADF4377_0001_SINGLE_INSTR_MSK BIT(7) +#define ADF4377_0001_MASTER_RB_CTRL_MSK BIT(5) + +/* ADF4377 REG0003 Bit Definition */ +#define ADF4377_0003_CHIP_TYPE 0x06 + +/* ADF4377 REG0004 Bit Definition */ +#define ADF4377_0004_PRODUCT_ID_LSB 0x0005 + +/* ADF4377 REG0005 Bit Definition */ +#define ADF4377_0005_PRODUCT_ID_MSB 0x0005 + +/* ADF4377 REG000A Map */ +#define ADF4377_000A_SCRATCHPAD_MSK GENMASK(7, 0) + +/* ADF4377 REG000C Bit Definition */ +#define ADF4377_000C_VENDOR_ID_LSB 0x56 + +/* ADF4377 REG000D Bit Definition */ +#define ADF4377_000D_VENDOR_ID_MSB 0x04 + +/* ADF4377 REG000F Bit Definition */ +#define ADF4377_000F_R00F_RSV1_MSK GENMASK(7, 0) + +/* ADF4377 REG0010 Map*/ +#define ADF4377_0010_N_INT_LSB_MSK GENMASK(7, 0) + +/* ADF4377 REG0011 Map*/ +#define ADF4377_0011_EN_AUTOCAL_MSK BIT(7) +#define ADF4377_0011_EN_RDBLR_MSK BIT(6) +#define ADF4377_0011_DCLK_DIV2_MSK GENMASK(5, 4) +#define ADF4377_0011_N_INT_MSB_MSK GENMASK(3, 0) + +/* ADF4377 REG0011 Bit Definition */ +#define ADF4377_0011_DCLK_DIV2_1 0x0 +#define ADF4377_0011_DCLK_DIV2_2 0x1 +#define ADF4377_0011_DCLK_DIV2_4 0x2 +#define ADF4377_0011_DCLK_DIV2_8 0x3 + +/* ADF4377 REG0012 Map*/ +#define ADF4377_0012_CLKOUT_DIV_MSK GENMASK(7, 6) +#define ADF4377_0012_R_DIV_MSK GENMASK(5, 0) + +/* ADF4377 REG0012 Bit Definition */ +#define ADF4377_0012_CLKOUT_DIV_1 0x0 +#define ADF4377_0012_CLKOUT_DIV_2 0x1 +#define ADF4377_0012_CLKOUT_DIV_4 0x2 +#define ADF4377_0012_CLKOUT_DIV_8 0x3 + +/* ADF4377 REG0013 Map */ +#define ADF4377_0013_M_VCO_CORE_MSK GENMASK(5, 4) +#define ADF4377_0013_VCO_BIAS_MSK GENMASK(3, 0) + +/* ADF4377 REG0013 Bit Definition */ +#define ADF4377_0013_M_VCO_0 0x0 +#define ADF4377_0013_M_VCO_1 0x1 +#define ADF4377_0013_M_VCO_2 0x2 +#define ADF4377_0013_M_VCO_3 0x3 + +/* ADF4377 REG0014 Map */ +#define ADF4377_0014_M_VCO_BAND_MSK GENMASK(7, 0) + +/* ADF4377 REG0015 Map */ +#define ADF4377_0015_BLEED_I_LSB_MSK GENMASK(7, 6) +#define ADF4377_0015_BLEED_POL_MSK BIT(5) +#define ADF4377_0015_EN_BLEED_MSK BIT(4) +#define ADF4377_0015_CP_I_MSK GENMASK(3, 0) + +/* ADF4377 REG0015 Bit Definition */ +#define ADF4377_CURRENT_SINK 0x0 +#define ADF4377_CURRENT_SOURCE 0x1 + +#define ADF4377_0015_CP_0MA7 0x0 +#define ADF4377_0015_CP_0MA9 0x1 +#define ADF4377_0015_CP_1MA1 0x2 +#define ADF4377_0015_CP_1MA3 0x3 +#define ADF4377_0015_CP_1MA4 0x4 +#define ADF4377_0015_CP_1MA8 0x5 +#define ADF4377_0015_CP_2MA2 0x6 +#define ADF4377_0015_CP_2MA5 0x7 +#define ADF4377_0015_CP_2MA9 0x8 +#define ADF4377_0015_CP_3MA6 0x9 +#define ADF4377_0015_CP_4MA3 0xA +#define ADF4377_0015_CP_5MA0 0xB +#define ADF4377_0015_CP_5MA7 0xC +#define ADF4377_0015_CP_7MA2 0xD +#define ADF4377_0015_CP_8MA6 0xE +#define ADF4377_0015_CP_10MA1 0xF + +/* ADF4377 REG0016 Map */ +#define ADF4377_0016_BLEED_I_MSB_MSK GENMASK(7, 0) + +/* ADF4377 REG0017 Map */ +#define ADF4377_0016_INV_CLKOUT_MSK BIT(7) +#define ADF4377_0016_N_DEL_MSK GENMASK(6, 0) + +/* ADF4377 REG0018 Map */ +#define ADF4377_0018_CMOS_OV_MSK BIT(7) +#define ADF4377_0018_R_DEL_MSK GENMASK(6, 0) + +/* ADF4377 REG0018 Bit Definition */ +#define ADF4377_0018_1V8_LOGIC 0x0 +#define ADF4377_0018_3V3_LOGIC 0x1 + +/* ADF4377 REG0019 Map */ +#define ADF4377_0019_CLKOUT2_OP_MSK GENMASK(7, 6) +#define ADF4377_0019_CLKOUT1_OP_MSK GENMASK(5, 4) +#define ADF4377_0019_PD_CLK_MSK BIT(3) +#define ADF4377_0019_PD_RDET_MSK BIT(2) +#define ADF4377_0019_PD_ADC_MSK BIT(1) +#define ADF4377_0019_PD_CALADC_MSK BIT(0) + +/* ADF4377 REG0019 Bit Definition */ +#define ADF4377_0019_CLKOUT_320MV 0x0 +#define ADF4377_0019_CLKOUT_420MV 0x1 +#define ADF4377_0019_CLKOUT_530MV 0x2 +#define ADF4377_0019_CLKOUT_640MV 0x3 + +/* ADF4377 REG001A Map */ +#define ADF4377_001A_PD_ALL_MSK BIT(7) +#define ADF4377_001A_PD_RDIV_MSK BIT(6) +#define ADF4377_001A_PD_NDIV_MSK BIT(5) +#define ADF4377_001A_PD_VCO_MSK BIT(4) +#define ADF4377_001A_PD_LD_MSK BIT(3) +#define ADF4377_001A_PD_PFDCP_MSK BIT(2) +#define ADF4377_001A_PD_CLKOUT1_MSK BIT(1) +#define ADF4377_001A_PD_CLKOUT2_MSK BIT(0) + +/* ADF4377 REG001B Map */ +#define ADF4377_001B_EN_LOL_MSK BIT(7) +#define ADF4377_001B_LDWIN_PW_MSK BIT(6) +#define ADF4377_001B_EN_LDWIN_MSK BIT(5) +#define ADF4377_001B_LD_COUNT_MSK GENMASK(4, 0) + +/* ADF4377 REG001B Bit Definition */ +#define ADF4377_001B_LDWIN_PW_NARROW 0x0 +#define ADF4377_001B_LDWIN_PW_WIDE 0x1 + +/* ADF4377 REG001C Map */ +#define ADF4377_001C_EN_DNCLK_MSK BIT(7) +#define ADF4377_001C_EN_DRCLK_MSK BIT(6) +#define ADF4377_001C_RST_LD_MSK BIT(2) +#define ADF4377_001C_R01C_RSV1_MSK BIT(0) + +/* ADF4377 REG001C Bit Definition */ +#define ADF4377_001C_RST_LD_INACTIVE 0x0 +#define ADF4377_001C_RST_LD_ACTIVE 0x1 + +#define ADF4377_001C_R01C_RSV1 0x1 + +/* ADF4377 REG001D Map */ +#define ADF4377_001D_MUXOUT_MSK GENMASK(7, 4) +#define ADF4377_001D_EN_CPTEST_MSK BIT(2) +#define ADF4377_001D_CP_DOWN_MSK BIT(1) +#define ADF4377_001D_CP_UP_MSK BIT(0) + +#define ADF4377_001D_EN_CPTEST_OFF 0x0 +#define ADF4377_001D_EN_CPTEST_ON 0x1 + +#define ADF4377_001D_CP_DOWN_OFF 0x0 +#define ADF4377_001D_CP_DOWN_ON 0x1 + +#define ADF4377_001D_CP_UP_OFF 0x0 +#define ADF4377_001D_CP_UP_ON 0x1 + +/* ADF4377 REG001F Map */ +#define ADF4377_001F_BST_REF_MSK BIT(7) +#define ADF4377_001F_FILT_REF_MSK BIT(6) +#define ADF4377_001F_REF_SEL_MSK BIT(5) +#define ADF4377_001F_R01F_RSV1_MSK GENMASK(4, 0) + +/* ADF4377 REG001F Bit Definition */ +#define ADF4377_001F_BST_LARGE_REF_IN 0x0 +#define ADF4377_001F_BST_SMALL_REF_IN 0x1 + +#define ADF4377_001F_FILT_REF_OFF 0x0 +#define ADF4377_001F_FILT_REF_ON 0x1 + +#define ADF4377_001F_REF_SEL_DMA 0x0 +#define ADF4377_001F_REF_SEL_LNA 0x1 + +#define ADF4377_001F_R01F_RSV1 0x7 + +/* ADF4377 REG0020 Map */ +#define ADF4377_0020_RST_SYS_MSK BIT(4) +#define ADF4377_0020_EN_ADC_CLK_MSK BIT(3) +#define ADF4377_0020_R020_RSV1_MSK BIT(0) + +/* ADF4377 REG0021 Bit Definition */ +#define ADF4377_0021_R021_RSV1 0xD3 + +/* ADF4377 REG0022 Bit Definition */ +#define ADF4377_0022_R022_RSV1 0x32 + +/* ADF4377 REG0023 Map */ +#define ADF4377_0023_CAT_CT_SEL BIT(7) +#define ADF4377_0023_R023_RSV1_MSK GENMASK(6, 0) + +/* ADF4377 REG0023 Bit Definition */ +#define ADF4377_0023_R023_RSV1 0x18 + +/* ADF4377 REG0024 Map */ +#define ADF4377_0024_DCLK_MODE_MSK BIT(2) + +/* ADF4377 REG0025 Map */ +#define ADF4377_0025_CLKODIV_DB_MSK BIT(7) +#define ADF4377_0025_DCLK_DB_MSK BIT(6) +#define ADF4377_0025_R025_RSV1_MSK GENMASK(5, 0) + +/* ADF4377 REG0025 Bit Definition */ +#define ADF4377_0025_R025_RSV1 0x16 + +/* ADF4377 REG0026 Map */ +#define ADF4377_0026_VCO_BAND_DIV_MSK GENMASK(7, 0) + +/* ADF4377 REG0027 Map */ +#define ADF4377_0027_SYNTH_LOCK_TO_LSB_MSK GENMASK(7, 0) + +/* ADF4377 REG0028 Map */ +#define ADF4377_0028_O_VCO_DB_MSK BIT(7) +#define ADF4377_0028_SYNTH_LOCK_TO_MSB_MSK GENMASK(6, 0) + +/* ADF4377 REG0029 Map */ +#define ADF4377_0029_VCO_ALC_TO_LSB_MSK GENMASK(7, 0) + +/* ADF4377 REG002A Map */ +#define ADF4377_002A_DEL_CTRL_DB_MSK BIT(7) +#define ADF4377_002A_VCO_ALC_TO_MSB_MSK GENMASK(6, 0) + +/* ADF4377 REG002C Map */ +#define ADF4377_002C_R02C_RSV1 0xC0 + +/* ADF4377 REG002D Map */ +#define ADF4377_002D_ADC_CLK_DIV_MSK GENMASK(7, 0) + +/* ADF4377 REG002E Map */ +#define ADF4377_002E_EN_ADC_CNV_MSK BIT(7) +#define ADF4377_002E_EN_ADC_MSK BIT(1) +#define ADF4377_002E_ADC_A_CONV_MSK BIT(0) + +/* ADF4377 REG002E Bit Definition */ +#define ADF4377_002E_ADC_A_CONV_ADC_ST_CNV 0x0 +#define ADF4377_002E_ADC_A_CONV_VCO_CALIB 0x1 + +/* ADF4377 REG002F Map */ +#define ADF4377_002F_DCLK_DIV1_MSK GENMASK(1, 0) + +/* ADF4377 REG002F Bit Definition */ +#define ADF4377_002F_DCLK_DIV1_1 0x0 +#define ADF4377_002F_DCLK_DIV1_2 0x1 +#define ADF4377_002F_DCLK_DIV1_8 0x2 +#define ADF4377_002F_DCLK_DIV1_32 0x3 + +/* ADF4377 REG0031 Bit Definition */ +#define ADF4377_0031_R031_RSV1 0x09 + +/* ADF4377 REG0032 Map */ +#define ADF4377_0032_ADC_CLK_SEL_MSK BIT(6) +#define ADF4377_0032_R032_RSV1_MSK GENMASK(5, 0) + +/* ADF4377 REG0032 Bit Definition */ +#define ADF4377_0032_ADC_CLK_SEL_N_OP 0x0 +#define ADF4377_0032_ADC_CLK_SEL_SPI_CLK 0x1 + +#define ADF4377_0032_R032_RSV1 0x9 + +/* ADF4377 REG0033 Bit Definition */ +#define ADF4377_0033_R033_RSV1 0x18 + +/* ADF4377 REG0034 Bit Definition */ +#define ADF4377_0034_R034_RSV1 0x08 + +/* ADF4377 REG003A Bit Definition */ +#define ADF4377_003A_R03A_RSV1 0x5D + +/* ADF4377 REG003B Bit Definition */ +#define ADF4377_003B_R03B_RSV1 0x2B + +/* ADF4377 REG003D Map */ +#define ADF4377_003D_O_VCO_BAND_MSK BIT(3) +#define ADF4377_003D_O_VCO_CORE_MSK BIT(2) +#define ADF4377_003D_O_VCO_BIAS_MSK BIT(1) + +/* ADF4377 REG003D Bit Definition */ +#define ADF4377_003D_O_VCO_BAND_VCO_CALIB 0x0 +#define ADF4377_003D_O_VCO_BAND_M_VCO 0x1 + +#define ADF4377_003D_O_VCO_CORE_VCO_CALIB 0x0 +#define ADF4377_003D_O_VCO_CORE_M_VCO 0x1 + +#define ADF4377_003D_O_VCO_BIAS_VCO_CALIB 0x0 +#define ADF4377_003D_O_VCO_BIAS_M_VCO 0x1 + +/* ADF4377 REG0042 Map */ +#define ADF4377_0042_R042_RSV1 0x05 + +/* ADF4377 REG0045 Map */ +#define ADF4377_0045_ADC_ST_CNV_MSK BIT(0) + +/* ADF4377 REG0049 Map */ +#define ADF4377_0049_EN_CLK2_MSK BIT(7) +#define ADF4377_0049_EN_CLK1_MSK BIT(6) +#define ADF4377_0049_REF_OK_MSK BIT(3) +#define ADF4377_0049_ADC_BUSY_MSK BIT(2) +#define ADF4377_0049_FSM_BUSY_MSK BIT(1) +#define ADF4377_0049_LOCKED_MSK BIT(0) + +/* ADF4377 REG004B Map */ +#define ADF4377_004B_VCO_CORE_MSK GENMASK(1, 0) + +/* ADF4377 REG004C Map */ +#define ADF4377_004C_CHIP_TEMP_LSB_MSK GENMASK(7, 0) + +/* ADF4377 REG004D Map */ +#define ADF4377_004D_CHIP_TEMP_MSB_MSK BIT(0) + +/* ADF4377 REG004F Map */ +#define ADF4377_004F_VCO_BAND_MSK GENMASK(7, 0) + +/* ADF4377 REG0051 Map */ +#define ADF4377_0051_VCO_BIAS_MSK GENMASK(3, 0) + +/* ADF4377 REG0054 Map */ +#define ADF4377_0054_CHIP_VERSION_MSK GENMASK(7, 0) + +/* Specifications */ +#define ADF4377_SPI_READ_CMD BIT(7) +#define ADF4377_MAX_VCO_FREQ (12800ULL * HZ_PER_MHZ) +#define ADF4377_MIN_VCO_FREQ (6400ULL * HZ_PER_MHZ) +#define ADF4377_MAX_REFIN_FREQ (1000 * HZ_PER_MHZ) +#define ADF4377_MIN_REFIN_FREQ (10 * HZ_PER_MHZ) +#define ADF4377_MAX_FREQ_PFD (500 * HZ_PER_MHZ) +#define ADF4377_MIN_FREQ_PFD (3 * HZ_PER_MHZ) +#define ADF4377_MAX_CLKPN_FREQ ADF4377_MAX_VCO_FREQ +#define ADF4377_MIN_CLKPN_FREQ (ADF4377_MIN_VCO_FREQ / 8) +#define ADF4377_FREQ_PFD_80MHZ (80 * HZ_PER_MHZ) +#define ADF4377_FREQ_PFD_125MHZ (125 * HZ_PER_MHZ) +#define ADF4377_FREQ_PFD_160MHZ (160 * HZ_PER_MHZ) +#define ADF4377_FREQ_PFD_250MHZ (250 * HZ_PER_MHZ) +#define ADF4377_FREQ_PFD_320MHZ (320 * HZ_PER_MHZ) + +enum { + ADF4377_FREQ, +}; + +enum muxout_select_mode { + ADF4377_MUXOUT_HIGH_Z = 0x0, + ADF4377_MUXOUT_LKDET = 0x1, + ADF4377_MUXOUT_LOW = 0x2, + ADF4377_MUXOUT_DIV_RCLK_2 = 0x4, + ADF4377_MUXOUT_DIV_NCLK_2 = 0x5, + ADF4377_MUXOUT_HIGH = 0x8, +}; + +struct adf4377_chip_info { + const char *name; + bool has_gpio_enclk2; +}; + +struct adf4377_state { + const struct adf4377_chip_info *chip_info; + struct spi_device *spi; + struct regmap *regmap; + struct clk *clkin; + /* Protect against concurrent accesses to the device and data content */ + struct mutex lock; + struct notifier_block nb; + /* Reference Divider */ + unsigned int ref_div_factor; + /* PFD Frequency */ + unsigned int f_pfd; + /* Input Reference Clock */ + unsigned int clkin_freq; + /* CLKOUT Divider */ + u8 clkout_div_sel; + /* Feedback Divider (N) */ + u16 n_int; + u16 synth_lock_timeout; + u16 vco_alc_timeout; + u16 adc_clk_div; + u16 vco_band_div; + u8 dclk_div1; + u8 dclk_div2; + u8 dclk_mode; + unsigned int f_div_rclk; + enum muxout_select_mode muxout_select; + struct gpio_desc *gpio_ce; + struct gpio_desc *gpio_enclk1; + struct gpio_desc *gpio_enclk2; + struct clk *clk; + struct clk *clkout; + struct clk_hw hw; + u8 buf[2] __aligned(IIO_DMA_MINALIGN); +}; + +#define to_adf4377_state(h) container_of(h, struct adf4377_state, hw) + +static const char * const adf4377_muxout_modes[] = { + [ADF4377_MUXOUT_HIGH_Z] = "high_z", + [ADF4377_MUXOUT_LKDET] = "lock_detect", + [ADF4377_MUXOUT_LOW] = "muxout_low", + [ADF4377_MUXOUT_DIV_RCLK_2] = "f_div_rclk_2", + [ADF4377_MUXOUT_DIV_NCLK_2] = "f_div_nclk_2", + [ADF4377_MUXOUT_HIGH] = "muxout_high", +}; + +static const struct reg_sequence adf4377_reg_defaults[] = { + { 0x42, ADF4377_0042_R042_RSV1 }, + { 0x3B, ADF4377_003B_R03B_RSV1 }, + { 0x3A, ADF4377_003A_R03A_RSV1 }, + { 0x34, ADF4377_0034_R034_RSV1 }, + { 0x33, ADF4377_0033_R033_RSV1 }, + { 0x32, ADF4377_0032_R032_RSV1 }, + { 0x31, ADF4377_0031_R031_RSV1 }, + { 0x2C, ADF4377_002C_R02C_RSV1 }, + { 0x25, ADF4377_0025_R025_RSV1 }, + { 0x23, ADF4377_0023_R023_RSV1 }, + { 0x22, ADF4377_0022_R022_RSV1 }, + { 0x21, ADF4377_0021_R021_RSV1 }, + { 0x1f, ADF4377_001F_R01F_RSV1 }, + { 0x1c, ADF4377_001C_R01C_RSV1 }, +}; + +static const struct regmap_config adf4377_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .read_flag_mask = BIT(7), + .max_register = 0x54, +}; + +static int adf4377_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int write_val, + unsigned int *read_val) +{ + struct adf4377_state *st = iio_priv(indio_dev); + + if (read_val) + return regmap_read(st->regmap, reg, read_val); + + return regmap_write(st->regmap, reg, write_val); +} + +static const struct iio_info adf4377_info = { + .debugfs_reg_access = &adf4377_reg_access, +}; + +static int adf4377_soft_reset(struct adf4377_state *st) +{ + unsigned int read_val; + int ret; + + ret = regmap_update_bits(st->regmap, 0x0, ADF4377_0000_SOFT_RESET_MSK | + ADF4377_0000_SOFT_RESET_R_MSK, + FIELD_PREP(ADF4377_0000_SOFT_RESET_MSK, 1) | + FIELD_PREP(ADF4377_0000_SOFT_RESET_R_MSK, 1)); + if (ret) + return ret; + + return regmap_read_poll_timeout(st->regmap, 0x0, read_val, + !(read_val & (ADF4377_0000_SOFT_RESET_MSK | + ADF4377_0000_SOFT_RESET_R_MSK)), 200, 200 * 100); +} + +static int adf4377_get_freq(struct adf4377_state *st, u64 *freq) +{ + unsigned int ref_div_factor, n_int; + u64 clkin_freq; + int ret; + + guard(mutex)(&st->lock); + + ret = regmap_read(st->regmap, 0x12, &ref_div_factor); + if (ret) + return ret; + + ret = regmap_bulk_read(st->regmap, 0x10, st->buf, sizeof(st->buf)); + if (ret) + return ret; + + clkin_freq = clk_get_rate(st->clkin); + ref_div_factor = FIELD_GET(ADF4377_0012_R_DIV_MSK, ref_div_factor); + n_int = FIELD_GET(ADF4377_0010_N_INT_LSB_MSK | ADF4377_0011_N_INT_MSB_MSK, + get_unaligned_le16(&st->buf)); + + *freq = div_u64(clkin_freq, ref_div_factor) * n_int; + + return 0; +} + +static int adf4377_set_freq(struct adf4377_state *st, u64 freq) +{ + unsigned int read_val; + u64 f_vco; + int ret; + + guard(mutex)(&st->lock); + + if (freq > ADF4377_MAX_CLKPN_FREQ || freq < ADF4377_MIN_CLKPN_FREQ) + return -EINVAL; + + ret = regmap_update_bits(st->regmap, 0x1C, ADF4377_001C_EN_DNCLK_MSK | + ADF4377_001C_EN_DRCLK_MSK, + FIELD_PREP(ADF4377_001C_EN_DNCLK_MSK, 1) | + FIELD_PREP(ADF4377_001C_EN_DRCLK_MSK, 1)); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, 0x11, ADF4377_0011_EN_AUTOCAL_MSK | + ADF4377_0011_DCLK_DIV2_MSK, + FIELD_PREP(ADF4377_0011_EN_AUTOCAL_MSK, 1) | + FIELD_PREP(ADF4377_0011_DCLK_DIV2_MSK, st->dclk_div2)); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, 0x2E, ADF4377_002E_EN_ADC_CNV_MSK | + ADF4377_002E_EN_ADC_MSK | + ADF4377_002E_ADC_A_CONV_MSK, + FIELD_PREP(ADF4377_002E_EN_ADC_CNV_MSK, 1) | + FIELD_PREP(ADF4377_002E_EN_ADC_MSK, 1) | + FIELD_PREP(ADF4377_002E_ADC_A_CONV_MSK, + ADF4377_002E_ADC_A_CONV_VCO_CALIB)); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, 0x20, ADF4377_0020_EN_ADC_CLK_MSK, + FIELD_PREP(ADF4377_0020_EN_ADC_CLK_MSK, 1)); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, 0x2F, ADF4377_002F_DCLK_DIV1_MSK, + FIELD_PREP(ADF4377_002F_DCLK_DIV1_MSK, st->dclk_div1)); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, 0x24, ADF4377_0024_DCLK_MODE_MSK, + FIELD_PREP(ADF4377_0024_DCLK_MODE_MSK, st->dclk_mode)); + if (ret) + return ret; + + ret = regmap_write(st->regmap, 0x27, + FIELD_PREP(ADF4377_0027_SYNTH_LOCK_TO_LSB_MSK, + st->synth_lock_timeout)); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, 0x28, ADF4377_0028_SYNTH_LOCK_TO_MSB_MSK, + FIELD_PREP(ADF4377_0028_SYNTH_LOCK_TO_MSB_MSK, + st->synth_lock_timeout >> 8)); + if (ret) + return ret; + + ret = regmap_write(st->regmap, 0x29, + FIELD_PREP(ADF4377_0029_VCO_ALC_TO_LSB_MSK, + st->vco_alc_timeout)); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, 0x2A, ADF4377_002A_VCO_ALC_TO_MSB_MSK, + FIELD_PREP(ADF4377_002A_VCO_ALC_TO_MSB_MSK, + st->vco_alc_timeout >> 8)); + if (ret) + return ret; + + ret = regmap_write(st->regmap, 0x26, + FIELD_PREP(ADF4377_0026_VCO_BAND_DIV_MSK, st->vco_band_div)); + if (ret) + return ret; + + ret = regmap_write(st->regmap, 0x2D, + FIELD_PREP(ADF4377_002D_ADC_CLK_DIV_MSK, st->adc_clk_div)); + if (ret) + return ret; + + st->clkout_div_sel = 0; + + f_vco = freq; + + while (f_vco < ADF4377_MIN_VCO_FREQ) { + f_vco <<= 1; + st->clkout_div_sel++; + } + + st->n_int = div_u64(freq, st->f_pfd); + + ret = regmap_update_bits(st->regmap, 0x11, ADF4377_0011_EN_RDBLR_MSK | + ADF4377_0011_N_INT_MSB_MSK, + FIELD_PREP(ADF4377_0011_EN_RDBLR_MSK, 0) | + FIELD_PREP(ADF4377_0011_N_INT_MSB_MSK, st->n_int >> 8)); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, 0x12, ADF4377_0012_R_DIV_MSK | + ADF4377_0012_CLKOUT_DIV_MSK, + FIELD_PREP(ADF4377_0012_CLKOUT_DIV_MSK, st->clkout_div_sel) | + FIELD_PREP(ADF4377_0012_R_DIV_MSK, st->ref_div_factor)); + if (ret) + return ret; + + ret = regmap_write(st->regmap, 0x10, + FIELD_PREP(ADF4377_0010_N_INT_LSB_MSK, st->n_int)); + if (ret) + return ret; + + ret = regmap_read_poll_timeout(st->regmap, 0x49, read_val, + !(read_val & (ADF4377_0049_FSM_BUSY_MSK)), 200, 200 * 100); + if (ret) + return ret; + + /* Disable EN_DNCLK, EN_DRCLK */ + ret = regmap_update_bits(st->regmap, 0x1C, ADF4377_001C_EN_DNCLK_MSK | + ADF4377_001C_EN_DRCLK_MSK, + FIELD_PREP(ADF4377_001C_EN_DNCLK_MSK, 0) | + FIELD_PREP(ADF4377_001C_EN_DRCLK_MSK, 0)); + if (ret) + return ret; + + /* Disable EN_ADC_CLK */ + ret = regmap_update_bits(st->regmap, 0x20, ADF4377_0020_EN_ADC_CLK_MSK, + FIELD_PREP(ADF4377_0020_EN_ADC_CLK_MSK, 0)); + if (ret) + return ret; + + /* Set output Amplitude */ + return regmap_update_bits(st->regmap, 0x19, ADF4377_0019_CLKOUT2_OP_MSK | + ADF4377_0019_CLKOUT1_OP_MSK, + FIELD_PREP(ADF4377_0019_CLKOUT1_OP_MSK, + ADF4377_0019_CLKOUT_420MV) | + FIELD_PREP(ADF4377_0019_CLKOUT2_OP_MSK, + ADF4377_0019_CLKOUT_420MV)); +} + +static void adf4377_gpio_init(struct adf4377_state *st) +{ + if (st->gpio_ce) { + gpiod_set_value(st->gpio_ce, 1); + + /* Delay for SPI register bits to settle to their power-on reset state */ + fsleep(200); + } + + if (st->gpio_enclk1) + gpiod_set_value(st->gpio_enclk1, 1); + + if (st->gpio_enclk2) + gpiod_set_value(st->gpio_enclk2, 1); +} + +static int adf4377_init(struct adf4377_state *st) +{ + struct device *dev = &st->spi->dev; + int ret; + + adf4377_gpio_init(st); + + ret = adf4377_soft_reset(st); + if (ret) + return dev_err_probe(dev, ret, "Failed to soft reset.\n"); + + ret = regmap_multi_reg_write(st->regmap, adf4377_reg_defaults, + ARRAY_SIZE(adf4377_reg_defaults)); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set default registers.\n"); + + ret = regmap_update_bits(st->regmap, 0x00, + ADF4377_0000_SDO_ACTIVE_MSK | ADF4377_0000_SDO_ACTIVE_R_MSK, + FIELD_PREP(ADF4377_0000_SDO_ACTIVE_MSK, + ADF4377_0000_SDO_ACTIVE_SPI_4W) | + FIELD_PREP(ADF4377_0000_SDO_ACTIVE_R_MSK, + ADF4377_0000_SDO_ACTIVE_SPI_4W)); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set 4-Wire Operation.\n"); + + st->clkin_freq = clk_get_rate(st->clkin); + + /* Power Up */ + ret = regmap_write(st->regmap, 0x1a, + FIELD_PREP(ADF4377_001A_PD_ALL_MSK, 0) | + FIELD_PREP(ADF4377_001A_PD_RDIV_MSK, 0) | + FIELD_PREP(ADF4377_001A_PD_NDIV_MSK, 0) | + FIELD_PREP(ADF4377_001A_PD_VCO_MSK, 0) | + FIELD_PREP(ADF4377_001A_PD_LD_MSK, 0) | + FIELD_PREP(ADF4377_001A_PD_PFDCP_MSK, 0) | + FIELD_PREP(ADF4377_001A_PD_CLKOUT1_MSK, 0) | + FIELD_PREP(ADF4377_001A_PD_CLKOUT2_MSK, 0)); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set power down registers.\n"); + + /* Set Mux Output */ + ret = regmap_update_bits(st->regmap, 0x1D, + ADF4377_001D_MUXOUT_MSK, + FIELD_PREP(ADF4377_001D_MUXOUT_MSK, st->muxout_select)); + if (ret) + return ret; + + /* Compute PFD */ + st->ref_div_factor = 0; + do { + st->ref_div_factor++; + st->f_pfd = st->clkin_freq / st->ref_div_factor; + } while (st->f_pfd > ADF4377_MAX_FREQ_PFD); + + if (st->f_pfd > ADF4377_MAX_FREQ_PFD || st->f_pfd < ADF4377_MIN_FREQ_PFD) + return -EINVAL; + + st->f_div_rclk = st->f_pfd; + + if (st->f_pfd <= ADF4377_FREQ_PFD_80MHZ) { + st->dclk_div1 = ADF4377_002F_DCLK_DIV1_1; + st->dclk_div2 = ADF4377_0011_DCLK_DIV2_1; + st->dclk_mode = 0; + } else if (st->f_pfd <= ADF4377_FREQ_PFD_125MHZ) { + st->dclk_div1 = ADF4377_002F_DCLK_DIV1_1; + st->dclk_div2 = ADF4377_0011_DCLK_DIV2_1; + st->dclk_mode = 1; + } else if (st->f_pfd <= ADF4377_FREQ_PFD_160MHZ) { + st->dclk_div1 = ADF4377_002F_DCLK_DIV1_2; + st->dclk_div2 = ADF4377_0011_DCLK_DIV2_1; + st->dclk_mode = 0; + st->f_div_rclk /= 2; + } else if (st->f_pfd <= ADF4377_FREQ_PFD_250MHZ) { + st->dclk_div1 = ADF4377_002F_DCLK_DIV1_2; + st->dclk_div2 = ADF4377_0011_DCLK_DIV2_1; + st->dclk_mode = 1; + st->f_div_rclk /= 2; + } else if (st->f_pfd <= ADF4377_FREQ_PFD_320MHZ) { + st->dclk_div1 = ADF4377_002F_DCLK_DIV1_2; + st->dclk_div2 = ADF4377_0011_DCLK_DIV2_2; + st->dclk_mode = 0; + st->f_div_rclk /= 4; + } else { + st->dclk_div1 = ADF4377_002F_DCLK_DIV1_2; + st->dclk_div2 = ADF4377_0011_DCLK_DIV2_2; + st->dclk_mode = 1; + st->f_div_rclk /= 4; + } + + st->synth_lock_timeout = DIV_ROUND_UP(st->f_div_rclk, 50000); + st->vco_alc_timeout = DIV_ROUND_UP(st->f_div_rclk, 20000); + st->vco_band_div = DIV_ROUND_UP(st->f_div_rclk, 150000 * 16 * (1 << st->dclk_mode)); + st->adc_clk_div = DIV_ROUND_UP((st->f_div_rclk / 400000 - 2), 4); + + return 0; +} + +static ssize_t adf4377_read(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *chan, char *buf) +{ + struct adf4377_state *st = iio_priv(indio_dev); + u64 val = 0; + int ret; + + switch ((u32)private) { + case ADF4377_FREQ: + ret = adf4377_get_freq(st, &val); + if (ret) + return ret; + + return sysfs_emit(buf, "%llu\n", val); + default: + return -EINVAL; + } +} + +static ssize_t adf4377_write(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *chan, const char *buf, + size_t len) +{ + struct adf4377_state *st = iio_priv(indio_dev); + unsigned long long freq; + int ret; + + switch ((u32)private) { + case ADF4377_FREQ: + ret = kstrtoull(buf, 10, &freq); + if (ret) + return ret; + + ret = adf4377_set_freq(st, freq); + if (ret) + return ret; + + return len; + default: + return -EINVAL; + } +} + +#define _ADF4377_EXT_INFO(_name, _shared, _ident) { \ + .name = _name, \ + .read = adf4377_read, \ + .write = adf4377_write, \ + .private = _ident, \ + .shared = _shared, \ + } + +static const struct iio_chan_spec_ext_info adf4377_ext_info[] = { + /* + * Usually we use IIO_CHAN_INFO_FREQUENCY, but there are + * values > 2^32 in order to support the entire frequency range + * in Hz. + */ + _ADF4377_EXT_INFO("frequency", IIO_SEPARATE, ADF4377_FREQ), + { } +}; + +static const struct iio_chan_spec adf4377_channels[] = { + { + .type = IIO_ALTVOLTAGE, + .indexed = 1, + .output = 1, + .channel = 0, + .ext_info = adf4377_ext_info, + }, +}; + +static int adf4377_properties_parse(struct adf4377_state *st) +{ + struct device *dev = &st->spi->dev; + int ret; + + st->clkin = devm_clk_get_enabled(dev, "ref_in"); + if (IS_ERR(st->clkin)) + return dev_err_probe(dev, PTR_ERR(st->clkin), + "failed to get the reference input clock\n"); + + st->gpio_ce = devm_gpiod_get_optional(dev, "chip-enable", + GPIOD_OUT_LOW); + if (IS_ERR(st->gpio_ce)) + return dev_err_probe(dev, PTR_ERR(st->gpio_ce), + "failed to get the CE GPIO\n"); + + st->gpio_enclk1 = devm_gpiod_get_optional(dev, "clk1-enable", + GPIOD_OUT_LOW); + if (IS_ERR(st->gpio_enclk1)) + return dev_err_probe(dev, PTR_ERR(st->gpio_enclk1), + "failed to get the CE GPIO\n"); + + if (st->chip_info->has_gpio_enclk2) { + st->gpio_enclk2 = devm_gpiod_get_optional(dev, "clk2-enable", + GPIOD_OUT_LOW); + if (IS_ERR(st->gpio_enclk2)) + return dev_err_probe(dev, PTR_ERR(st->gpio_enclk2), + "failed to get the CE GPIO\n"); + } + + ret = device_property_match_property_string(dev, "adi,muxout-select", + adf4377_muxout_modes, + ARRAY_SIZE(adf4377_muxout_modes)); + if (ret >= 0) + st->muxout_select = ret; + else + st->muxout_select = ADF4377_MUXOUT_HIGH_Z; + + return 0; +} + +static int adf4377_freq_change(struct notifier_block *nb, unsigned long action, void *data) +{ + struct adf4377_state *st = container_of(nb, struct adf4377_state, nb); + + if (action == POST_RATE_CHANGE) { + guard(mutex)(&st->lock); + + return notifier_from_errno(adf4377_init(st)); + } + + return NOTIFY_OK; +} + +static unsigned long adf4377_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct adf4377_state *st = to_adf4377_state(hw); + u64 freq; + int ret; + + ret = adf4377_get_freq(st, &freq); + if (ret) + return 0; + + return freq; +} + +static int adf4377_clk_set_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct adf4377_state *st = to_adf4377_state(hw); + + return adf4377_set_freq(st, rate); +} + +static int adf4377_clk_prepare(struct clk_hw *hw) +{ + struct adf4377_state *st = to_adf4377_state(hw); + + return regmap_update_bits(st->regmap, 0x1a, ADF4377_001A_PD_CLKOUT1_MSK | + ADF4377_001A_PD_CLKOUT2_MSK, + FIELD_PREP(ADF4377_001A_PD_CLKOUT1_MSK, 0) | + FIELD_PREP(ADF4377_001A_PD_CLKOUT2_MSK, 0)); +} + +static void adf4377_clk_unprepare(struct clk_hw *hw) +{ + struct adf4377_state *st = to_adf4377_state(hw); + + regmap_update_bits(st->regmap, 0x1a, ADF4377_001A_PD_CLKOUT1_MSK | + ADF4377_001A_PD_CLKOUT2_MSK, + FIELD_PREP(ADF4377_001A_PD_CLKOUT1_MSK, 1) | + FIELD_PREP(ADF4377_001A_PD_CLKOUT2_MSK, 1)); +} + +static int adf4377_clk_is_prepared(struct clk_hw *hw) +{ + struct adf4377_state *st = to_adf4377_state(hw); + unsigned int readval; + int ret; + + ret = regmap_read(st->regmap, 0x1a, &readval); + if (ret) + return ret; + + return !(readval & (ADF4377_001A_PD_CLKOUT1_MSK | ADF4377_001A_PD_CLKOUT2_MSK)); +} + +static const struct clk_ops adf4377_clk_ops = { + .recalc_rate = adf4377_clk_recalc_rate, + .set_rate = adf4377_clk_set_rate, + .prepare = adf4377_clk_prepare, + .unprepare = adf4377_clk_unprepare, + .is_prepared = adf4377_clk_is_prepared, +}; + +static int adf4377_clk_register(struct adf4377_state *st) +{ + struct spi_device *spi = st->spi; + struct device *dev = &spi->dev; + struct clk_init_data init; + struct clk_parent_data parent_data; + int ret; + + if (!device_property_present(dev, "#clock-cells")) + return 0; + + ret = device_property_read_string(dev, "clock-output-names", &init.name); + if (ret) { + init.name = devm_kasprintf(dev, GFP_KERNEL, "%pfw-clk", + dev_fwnode(dev)); + if (!init.name) + return -ENOMEM; + } + + parent_data.fw_name = "ref_in"; + + init.ops = &adf4377_clk_ops; + init.parent_data = &parent_data; + init.num_parents = 1; + init.flags = CLK_SET_RATE_PARENT; + + st->hw.init = &init; + ret = devm_clk_hw_register(dev, &st->hw); + if (ret) + return ret; + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, &st->hw); + if (ret) + return ret; + + st->clkout = st->hw.clk; + + return 0; +} + +static const struct adf4377_chip_info adf4377_chip_info = { + .name = "adf4377", + .has_gpio_enclk2 = true, +}; + +static const struct adf4377_chip_info adf4378_chip_info = { + .name = "adf4378", + .has_gpio_enclk2 = false, +}; + +static int adf4377_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct regmap *regmap; + struct adf4377_state *st; + struct device *dev = &spi->dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_spi(spi, &adf4377_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + st = iio_priv(indio_dev); + + indio_dev->info = &adf4377_info; + indio_dev->name = "adf4377"; + + st->regmap = regmap; + st->spi = spi; + st->chip_info = spi_get_device_match_data(spi); + mutex_init(&st->lock); + + ret = adf4377_properties_parse(st); + if (ret) + return ret; + + st->nb.notifier_call = adf4377_freq_change; + ret = devm_clk_notifier_register(dev, st->clkin, &st->nb); + if (ret) + return ret; + + ret = adf4377_init(st); + if (ret) + return ret; + + ret = adf4377_clk_register(st); + if (ret) + return ret; + + if (!st->clkout) { + indio_dev->channels = adf4377_channels; + indio_dev->num_channels = ARRAY_SIZE(adf4377_channels); + } + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id adf4377_id[] = { + { .name = "adf4377", .driver_data = (kernel_ulong_t)&adf4377_chip_info }, + { .name = "adf4378", .driver_data = (kernel_ulong_t)&adf4378_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, adf4377_id); + +static const struct of_device_id adf4377_of_match[] = { + { .compatible = "adi,adf4377", .data = &adf4377_chip_info }, + { .compatible = "adi,adf4378", .data = &adf4378_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, adf4377_of_match); + +static struct spi_driver adf4377_driver = { + .driver = { + .name = "adf4377", + .of_match_table = adf4377_of_match, + }, + .probe = adf4377_probe, + .id_table = adf4377_id, +}; +module_spi_driver(adf4377_driver); + +MODULE_AUTHOR("Antoniu Miclaus "); +MODULE_DESCRIPTION("Analog Devices ADF4377"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/frequency/admfm2000.c b/drivers/iio/frequency/admfm2000.c new file mode 100644 index 000000000..0405b53c5 --- /dev/null +++ b/drivers/iio/frequency/admfm2000.c @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ADMFM2000 Dual Microwave Down Converter + * + * Copyright 2024 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define ADMFM2000_MIXER_MODE 0 +#define ADMFM2000_DIRECT_IF_MODE 1 +#define ADMFM2000_DSA_GPIOS 5 +#define ADMFM2000_MODE_GPIOS 2 +#define ADMFM2000_MAX_GAIN 0 +#define ADMFM2000_MIN_GAIN -31000 +#define ADMFM2000_DEFAULT_GAIN -0x20 + +struct admfm2000_state { + struct mutex lock; /* protect sensor state */ + struct gpio_desc *sw1_ch[2]; + struct gpio_desc *sw2_ch[2]; + struct gpio_desc *dsa1_gpios[5]; + struct gpio_desc *dsa2_gpios[5]; + u32 gain[2]; +}; + +static int admfm2000_mode(struct iio_dev *indio_dev, u32 chan, u32 mode) +{ + struct admfm2000_state *st = iio_priv(indio_dev); + int i; + + switch (mode) { + case ADMFM2000_MIXER_MODE: + for (i = 0; i < ADMFM2000_MODE_GPIOS; i++) { + gpiod_set_value_cansleep(st->sw1_ch[i], (chan == 0) ? 1 : 0); + gpiod_set_value_cansleep(st->sw2_ch[i], (chan == 0) ? 0 : 1); + } + return 0; + case ADMFM2000_DIRECT_IF_MODE: + for (i = 0; i < ADMFM2000_MODE_GPIOS; i++) { + gpiod_set_value_cansleep(st->sw1_ch[i], (chan == 0) ? 0 : 1); + gpiod_set_value_cansleep(st->sw2_ch[i], (chan == 0) ? 1 : 0); + } + return 0; + default: + return -EINVAL; + } +} + +static int admfm2000_attenuation(struct iio_dev *indio_dev, u32 chan, u32 value) +{ + struct admfm2000_state *st = iio_priv(indio_dev); + int i; + + switch (chan) { + case 0: + for (i = 0; i < ADMFM2000_DSA_GPIOS; i++) + gpiod_set_value_cansleep(st->dsa1_gpios[i], value & (1 << i)); + return 0; + case 1: + for (i = 0; i < ADMFM2000_DSA_GPIOS; i++) + gpiod_set_value_cansleep(st->dsa2_gpios[i], value & (1 << i)); + return 0; + default: + return -EINVAL; + } +} + +static int admfm2000_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct admfm2000_state *st = iio_priv(indio_dev); + int gain; + + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + mutex_lock(&st->lock); + gain = ~(st->gain[chan->channel]) * -1000; + *val = gain / 1000; + *val2 = (gain % 1000) * 1000; + mutex_unlock(&st->lock); + + return IIO_VAL_INT_PLUS_MICRO_DB; + default: + return -EINVAL; + } +} + +static int admfm2000_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct admfm2000_state *st = iio_priv(indio_dev); + int gain, ret; + + if (val < 0) + gain = (val * 1000) - (val2 / 1000); + else + gain = (val * 1000) + (val2 / 1000); + + if (gain > ADMFM2000_MAX_GAIN || gain < ADMFM2000_MIN_GAIN) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + mutex_lock(&st->lock); + st->gain[chan->channel] = ~((abs(gain) / 1000) & 0x1F); + + ret = admfm2000_attenuation(indio_dev, chan->channel, + st->gain[chan->channel]); + mutex_unlock(&st->lock); + return ret; + default: + return -EINVAL; + } +} + +static int admfm2000_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + return IIO_VAL_INT_PLUS_MICRO_DB; + default: + return -EINVAL; + } +} + +static const struct iio_info admfm2000_info = { + .read_raw = &admfm2000_read_raw, + .write_raw = &admfm2000_write_raw, + .write_raw_get_fmt = &admfm2000_write_raw_get_fmt, +}; + +#define ADMFM2000_CHAN(_channel) { \ + .type = IIO_VOLTAGE, \ + .output = 1, \ + .indexed = 1, \ + .channel = _channel, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_HARDWAREGAIN), \ +} + +static const struct iio_chan_spec admfm2000_channels[] = { + ADMFM2000_CHAN(0), + ADMFM2000_CHAN(1), +}; + +static int admfm2000_channel_config(struct admfm2000_state *st, + struct iio_dev *indio_dev) +{ + struct platform_device *pdev = to_platform_device(indio_dev->dev.parent); + struct device *dev = &pdev->dev; + struct gpio_desc **dsa; + struct gpio_desc **sw; + int ret, i; + bool mode; + u32 reg; + + device_for_each_child_node_scoped(dev, child) { + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get reg property\n"); + + if (reg >= indio_dev->num_channels) + return dev_err_probe(dev, -EINVAL, "reg bigger than: %d\n", + indio_dev->num_channels); + + if (fwnode_property_present(child, "adi,mixer-mode")) + mode = ADMFM2000_MIXER_MODE; + else + mode = ADMFM2000_DIRECT_IF_MODE; + + switch (reg) { + case 0: + sw = st->sw1_ch; + dsa = st->dsa1_gpios; + break; + case 1: + sw = st->sw2_ch; + dsa = st->dsa2_gpios; + break; + default: + return -EINVAL; + } + + for (i = 0; i < ADMFM2000_MODE_GPIOS; i++) { + sw[i] = devm_fwnode_gpiod_get_index(dev, child, "switch", + i, GPIOD_OUT_LOW, NULL); + if (IS_ERR(sw[i])) + return dev_err_probe(dev, PTR_ERR(sw[i]), + "Failed to get gpios\n"); + } + + for (i = 0; i < ADMFM2000_DSA_GPIOS; i++) { + dsa[i] = devm_fwnode_gpiod_get_index(dev, child, + "attenuation", i, + GPIOD_OUT_LOW, NULL); + if (IS_ERR(dsa[i])) + return dev_err_probe(dev, PTR_ERR(dsa[i]), + "Failed to get gpios\n"); + } + + ret = admfm2000_mode(indio_dev, reg, mode); + if (ret) + return ret; + } + + return 0; +} + +static int admfm2000_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct admfm2000_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + indio_dev->name = "admfm2000"; + indio_dev->num_channels = ARRAY_SIZE(admfm2000_channels); + indio_dev->channels = admfm2000_channels; + indio_dev->info = &admfm2000_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + st->gain[0] = ADMFM2000_DEFAULT_GAIN; + st->gain[1] = ADMFM2000_DEFAULT_GAIN; + + mutex_init(&st->lock); + + ret = admfm2000_channel_config(st, indio_dev); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id admfm2000_of_match[] = { + { .compatible = "adi,admfm2000" }, + { } +}; +MODULE_DEVICE_TABLE(of, admfm2000_of_match); + +static struct platform_driver admfm2000_driver = { + .driver = { + .name = "admfm2000", + .of_match_table = admfm2000_of_match, + }, + .probe = admfm2000_probe, +}; +module_platform_driver(admfm2000_driver); + +MODULE_AUTHOR("Kim Seer Paller "); +MODULE_DESCRIPTION("ADMFM2000 Dual Microwave Down Converter"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/frequency/admv1013.c b/drivers/iio/frequency/admv1013.c new file mode 100644 index 000000000..c9baaab57 --- /dev/null +++ b/drivers/iio/frequency/admv1013.c @@ -0,0 +1,645 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADMV1013 driver + * + * Copyright 2021 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* ADMV1013 Register Map */ +#define ADMV1013_REG_SPI_CONTROL 0x00 +#define ADMV1013_REG_ALARM 0x01 +#define ADMV1013_REG_ALARM_MASKS 0x02 +#define ADMV1013_REG_ENABLE 0x03 +#define ADMV1013_REG_LO_AMP_I 0x05 +#define ADMV1013_REG_LO_AMP_Q 0x06 +#define ADMV1013_REG_OFFSET_ADJUST_I 0x07 +#define ADMV1013_REG_OFFSET_ADJUST_Q 0x08 +#define ADMV1013_REG_QUAD 0x09 +#define ADMV1013_REG_VVA_TEMP_COMP 0x0A + +/* ADMV1013_REG_SPI_CONTROL Map */ +#define ADMV1013_PARITY_EN_MSK BIT(15) +#define ADMV1013_SPI_SOFT_RESET_MSK BIT(14) +#define ADMV1013_CHIP_ID_MSK GENMASK(11, 4) +#define ADMV1013_CHIP_ID 0xA +#define ADMV1013_REVISION_ID_MSK GENMASK(3, 0) + +/* ADMV1013_REG_ALARM Map */ +#define ADMV1013_PARITY_ERROR_MSK BIT(15) +#define ADMV1013_TOO_FEW_ERRORS_MSK BIT(14) +#define ADMV1013_TOO_MANY_ERRORS_MSK BIT(13) +#define ADMV1013_ADDRESS_RANGE_ERROR_MSK BIT(12) + +/* ADMV1013_REG_ENABLE Map */ +#define ADMV1013_VGA_PD_MSK BIT(15) +#define ADMV1013_MIXER_PD_MSK BIT(14) +#define ADMV1013_QUAD_PD_MSK GENMASK(13, 11) +#define ADMV1013_BG_PD_MSK BIT(10) +#define ADMV1013_MIXER_IF_EN_MSK BIT(7) +#define ADMV1013_DET_EN_MSK BIT(5) + +/* ADMV1013_REG_LO_AMP Map */ +#define ADMV1013_LOAMP_PH_ADJ_FINE_MSK GENMASK(13, 7) +#define ADMV1013_MIXER_VGATE_MSK GENMASK(6, 0) + +/* ADMV1013_REG_OFFSET_ADJUST Map */ +#define ADMV1013_MIXER_OFF_ADJ_P_MSK GENMASK(15, 9) +#define ADMV1013_MIXER_OFF_ADJ_N_MSK GENMASK(8, 2) + +/* ADMV1013_REG_QUAD Map */ +#define ADMV1013_QUAD_SE_MODE_MSK GENMASK(9, 6) +#define ADMV1013_QUAD_FILTERS_MSK GENMASK(3, 0) + +/* ADMV1013_REG_VVA_TEMP_COMP Map */ +#define ADMV1013_VVA_TEMP_COMP_MSK GENMASK(15, 0) + +/* ADMV1013 Miscellaneous Defines */ +#define ADMV1013_READ BIT(7) +#define ADMV1013_REG_ADDR_READ_MSK GENMASK(6, 1) +#define ADMV1013_REG_ADDR_WRITE_MSK GENMASK(22, 17) +#define ADMV1013_REG_DATA_MSK GENMASK(16, 1) + +enum { + ADMV1013_IQ_MODE, + ADMV1013_IF_MODE +}; + +enum { + ADMV1013_RFMOD_I_CALIBPHASE, + ADMV1013_RFMOD_Q_CALIBPHASE, +}; + +enum { + ADMV1013_SE_MODE_POS, + ADMV1013_SE_MODE_NEG, + ADMV1013_SE_MODE_DIFF, +}; + +struct admv1013_state { + struct spi_device *spi; + struct clk *clkin; + /* Protect against concurrent accesses to the device and to data */ + struct mutex lock; + struct notifier_block nb; + unsigned int input_mode; + unsigned int quad_se_mode; + bool det_en; + u8 data[3] __aligned(IIO_DMA_MINALIGN); +}; + +static int __admv1013_spi_read(struct admv1013_state *st, unsigned int reg, + unsigned int *val) +{ + int ret; + struct spi_transfer t = {0}; + + st->data[0] = ADMV1013_READ | FIELD_PREP(ADMV1013_REG_ADDR_READ_MSK, reg); + st->data[1] = 0x0; + st->data[2] = 0x0; + + t.rx_buf = &st->data[0]; + t.tx_buf = &st->data[0]; + t.len = 3; + + ret = spi_sync_transfer(st->spi, &t, 1); + if (ret) + return ret; + + *val = FIELD_GET(ADMV1013_REG_DATA_MSK, get_unaligned_be24(&st->data[0])); + + return ret; +} + +static int admv1013_spi_read(struct admv1013_state *st, unsigned int reg, + unsigned int *val) +{ + int ret; + + mutex_lock(&st->lock); + ret = __admv1013_spi_read(st, reg, val); + mutex_unlock(&st->lock); + + return ret; +} + +static int __admv1013_spi_write(struct admv1013_state *st, + unsigned int reg, + unsigned int val) +{ + put_unaligned_be24(FIELD_PREP(ADMV1013_REG_DATA_MSK, val) | + FIELD_PREP(ADMV1013_REG_ADDR_WRITE_MSK, reg), &st->data[0]); + + return spi_write(st->spi, &st->data[0], 3); +} + +static int admv1013_spi_write(struct admv1013_state *st, unsigned int reg, + unsigned int val) +{ + int ret; + + mutex_lock(&st->lock); + ret = __admv1013_spi_write(st, reg, val); + mutex_unlock(&st->lock); + + return ret; +} + +static int __admv1013_spi_update_bits(struct admv1013_state *st, unsigned int reg, + unsigned int mask, unsigned int val) +{ + int ret; + unsigned int data, temp; + + ret = __admv1013_spi_read(st, reg, &data); + if (ret) + return ret; + + temp = (data & ~mask) | (val & mask); + + return __admv1013_spi_write(st, reg, temp); +} + +static int admv1013_spi_update_bits(struct admv1013_state *st, unsigned int reg, + unsigned int mask, unsigned int val) +{ + int ret; + + mutex_lock(&st->lock); + ret = __admv1013_spi_update_bits(st, reg, mask, val); + mutex_unlock(&st->lock); + + return ret; +} + +static int admv1013_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct admv1013_state *st = iio_priv(indio_dev); + unsigned int data, addr; + int ret; + + switch (info) { + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->channel) { + case IIO_MOD_I: + addr = ADMV1013_REG_OFFSET_ADJUST_I; + break; + case IIO_MOD_Q: + addr = ADMV1013_REG_OFFSET_ADJUST_Q; + break; + default: + return -EINVAL; + } + + ret = admv1013_spi_read(st, addr, &data); + if (ret) + return ret; + + if (!chan->channel) + *val = FIELD_GET(ADMV1013_MIXER_OFF_ADJ_P_MSK, data); + else + *val = FIELD_GET(ADMV1013_MIXER_OFF_ADJ_N_MSK, data); + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int admv1013_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct admv1013_state *st = iio_priv(indio_dev); + unsigned int addr, data, msk; + + switch (info) { + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->channel2) { + case IIO_MOD_I: + addr = ADMV1013_REG_OFFSET_ADJUST_I; + break; + case IIO_MOD_Q: + addr = ADMV1013_REG_OFFSET_ADJUST_Q; + break; + default: + return -EINVAL; + } + + if (!chan->channel) { + msk = ADMV1013_MIXER_OFF_ADJ_P_MSK; + data = FIELD_PREP(ADMV1013_MIXER_OFF_ADJ_P_MSK, val); + } else { + msk = ADMV1013_MIXER_OFF_ADJ_N_MSK; + data = FIELD_PREP(ADMV1013_MIXER_OFF_ADJ_N_MSK, val); + } + + return admv1013_spi_update_bits(st, addr, msk, data); + default: + return -EINVAL; + } +} + +static ssize_t admv1013_read(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct admv1013_state *st = iio_priv(indio_dev); + unsigned int data, addr; + int ret; + + switch ((u32)private) { + case ADMV1013_RFMOD_I_CALIBPHASE: + addr = ADMV1013_REG_LO_AMP_I; + break; + case ADMV1013_RFMOD_Q_CALIBPHASE: + addr = ADMV1013_REG_LO_AMP_Q; + break; + default: + return -EINVAL; + } + + ret = admv1013_spi_read(st, addr, &data); + if (ret) + return ret; + + data = FIELD_GET(ADMV1013_LOAMP_PH_ADJ_FINE_MSK, data); + + return sysfs_emit(buf, "%u\n", data); +} + +static ssize_t admv1013_write(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct admv1013_state *st = iio_priv(indio_dev); + unsigned int data; + int ret; + + ret = kstrtou32(buf, 10, &data); + if (ret) + return ret; + + data = FIELD_PREP(ADMV1013_LOAMP_PH_ADJ_FINE_MSK, data); + + switch ((u32)private) { + case ADMV1013_RFMOD_I_CALIBPHASE: + ret = admv1013_spi_update_bits(st, ADMV1013_REG_LO_AMP_I, + ADMV1013_LOAMP_PH_ADJ_FINE_MSK, + data); + if (ret) + return ret; + break; + case ADMV1013_RFMOD_Q_CALIBPHASE: + ret = admv1013_spi_update_bits(st, ADMV1013_REG_LO_AMP_Q, + ADMV1013_LOAMP_PH_ADJ_FINE_MSK, + data); + if (ret) + return ret; + break; + default: + return -EINVAL; + } + + return len; +} + +static int admv1013_update_quad_filters(struct admv1013_state *st) +{ + unsigned int filt_raw; + u64 rate = clk_get_rate(st->clkin); + + if (rate >= (5400 * HZ_PER_MHZ) && rate <= (7000 * HZ_PER_MHZ)) + filt_raw = 15; + else if (rate >= (5400 * HZ_PER_MHZ) && rate <= (8000 * HZ_PER_MHZ)) + filt_raw = 10; + else if (rate >= (6600 * HZ_PER_MHZ) && rate <= (9200 * HZ_PER_MHZ)) + filt_raw = 5; + else + filt_raw = 0; + + return __admv1013_spi_update_bits(st, ADMV1013_REG_QUAD, + ADMV1013_QUAD_FILTERS_MSK, + FIELD_PREP(ADMV1013_QUAD_FILTERS_MSK, filt_raw)); +} + +static int admv1013_update_mixer_vgate(struct admv1013_state *st, int vcm) +{ + unsigned int mixer_vgate; + + if (vcm <= 1800000) + mixer_vgate = (2389 * vcm / 1000000 + 8100) / 100; + else if (vcm > 1800000 && vcm <= 2600000) + mixer_vgate = (2375 * vcm / 1000000 + 125) / 100; + else + return -EINVAL; + + return __admv1013_spi_update_bits(st, ADMV1013_REG_LO_AMP_I, + ADMV1013_MIXER_VGATE_MSK, + FIELD_PREP(ADMV1013_MIXER_VGATE_MSK, mixer_vgate)); +} + +static int admv1013_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int write_val, + unsigned int *read_val) +{ + struct admv1013_state *st = iio_priv(indio_dev); + + if (read_val) + return admv1013_spi_read(st, reg, read_val); + else + return admv1013_spi_write(st, reg, write_val); +} + +static const struct iio_info admv1013_info = { + .read_raw = admv1013_read_raw, + .write_raw = admv1013_write_raw, + .debugfs_reg_access = &admv1013_reg_access, +}; + +static const char * const admv1013_vcc_regs[] = { + "vcc-drv", "vcc2-drv", "vcc-vva", "vcc-amp1", "vcc-amp2", + "vcc-env", "vcc-bg", "vcc-bg2", "vcc-mixer", "vcc-quad" +}; + +static int admv1013_freq_change(struct notifier_block *nb, unsigned long action, void *data) +{ + struct admv1013_state *st = container_of(nb, struct admv1013_state, nb); + int ret; + + if (action == POST_RATE_CHANGE) { + mutex_lock(&st->lock); + ret = notifier_from_errno(admv1013_update_quad_filters(st)); + mutex_unlock(&st->lock); + return ret; + } + + return NOTIFY_OK; +} + +#define _ADMV1013_EXT_INFO(_name, _shared, _ident) { \ + .name = _name, \ + .read = admv1013_read, \ + .write = admv1013_write, \ + .private = _ident, \ + .shared = _shared, \ +} + +static const struct iio_chan_spec_ext_info admv1013_ext_info[] = { + _ADMV1013_EXT_INFO("i_calibphase", IIO_SEPARATE, ADMV1013_RFMOD_I_CALIBPHASE), + _ADMV1013_EXT_INFO("q_calibphase", IIO_SEPARATE, ADMV1013_RFMOD_Q_CALIBPHASE), + { } +}; + +#define ADMV1013_CHAN_PHASE(_channel, _channel2, _admv1013_ext_info) { \ + .type = IIO_ALTVOLTAGE, \ + .output = 0, \ + .indexed = 1, \ + .channel2 = _channel2, \ + .channel = _channel, \ + .differential = 1, \ + .ext_info = _admv1013_ext_info, \ + } + +#define ADMV1013_CHAN_CALIB(_channel, rf_comp) { \ + .type = IIO_ALTVOLTAGE, \ + .output = 0, \ + .indexed = 1, \ + .channel = _channel, \ + .channel2 = IIO_MOD_##rf_comp, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_CALIBBIAS), \ + } + +static const struct iio_chan_spec admv1013_channels[] = { + ADMV1013_CHAN_PHASE(0, 1, admv1013_ext_info), + ADMV1013_CHAN_CALIB(0, I), + ADMV1013_CHAN_CALIB(0, Q), + ADMV1013_CHAN_CALIB(1, I), + ADMV1013_CHAN_CALIB(1, Q), +}; + +static int admv1013_init(struct admv1013_state *st, int vcm_uv) +{ + int ret; + unsigned int data; + struct device *dev = &st->spi->dev; + + /* Perform a software reset */ + ret = __admv1013_spi_update_bits(st, ADMV1013_REG_SPI_CONTROL, + ADMV1013_SPI_SOFT_RESET_MSK, + FIELD_PREP(ADMV1013_SPI_SOFT_RESET_MSK, 1)); + if (ret) + return ret; + + ret = __admv1013_spi_update_bits(st, ADMV1013_REG_SPI_CONTROL, + ADMV1013_SPI_SOFT_RESET_MSK, + FIELD_PREP(ADMV1013_SPI_SOFT_RESET_MSK, 0)); + if (ret) + return ret; + + ret = __admv1013_spi_read(st, ADMV1013_REG_SPI_CONTROL, &data); + if (ret) + return ret; + + data = FIELD_GET(ADMV1013_CHIP_ID_MSK, data); + if (data != ADMV1013_CHIP_ID) + return dev_err_probe(dev, -EINVAL, "Invalid Chip ID.\n"); + + ret = __admv1013_spi_write(st, ADMV1013_REG_VVA_TEMP_COMP, 0xE700); + if (ret) + return ret; + + switch (st->quad_se_mode) { + case ADMV1013_SE_MODE_POS: + data = 6; + break; + case ADMV1013_SE_MODE_NEG: + data = 9; + break; + case ADMV1013_SE_MODE_DIFF: + data = 12; + break; + default: + return -EINVAL; + } + + ret = __admv1013_spi_update_bits(st, ADMV1013_REG_QUAD, + ADMV1013_QUAD_SE_MODE_MSK, + FIELD_PREP(ADMV1013_QUAD_SE_MODE_MSK, data)); + if (ret) + return ret; + + ret = admv1013_update_mixer_vgate(st, vcm_uv); + if (ret) + return ret; + + ret = admv1013_update_quad_filters(st); + if (ret) + return ret; + + return __admv1013_spi_update_bits(st, ADMV1013_REG_ENABLE, + ADMV1013_DET_EN_MSK | + ADMV1013_MIXER_IF_EN_MSK, + st->det_en | + st->input_mode); +} + +static void admv1013_powerdown(void *data) +{ + unsigned int enable_reg, enable_reg_msk; + + /* Disable all components in the Enable Register */ + enable_reg_msk = ADMV1013_VGA_PD_MSK | + ADMV1013_MIXER_PD_MSK | + ADMV1013_QUAD_PD_MSK | + ADMV1013_BG_PD_MSK | + ADMV1013_MIXER_IF_EN_MSK | + ADMV1013_DET_EN_MSK; + + enable_reg = FIELD_PREP(ADMV1013_VGA_PD_MSK, 1) | + FIELD_PREP(ADMV1013_MIXER_PD_MSK, 1) | + FIELD_PREP(ADMV1013_QUAD_PD_MSK, 7) | + FIELD_PREP(ADMV1013_BG_PD_MSK, 1) | + FIELD_PREP(ADMV1013_MIXER_IF_EN_MSK, 0) | + FIELD_PREP(ADMV1013_DET_EN_MSK, 0); + + admv1013_spi_update_bits(data, ADMV1013_REG_ENABLE, enable_reg_msk, enable_reg); +} + +static const char * const admv1013_input_modes[] = { + [ADMV1013_IQ_MODE] = "iq", + [ADMV1013_IF_MODE] = "if", +}; + +static const char * const admv1013_quad_se_modes[] = { + [ADMV1013_SE_MODE_POS] = "se-pos", + [ADMV1013_SE_MODE_NEG] = "se-neg", + [ADMV1013_SE_MODE_DIFF] = "diff", +}; + +static int admv1013_properties_parse(struct admv1013_state *st) +{ + int ret; + struct device *dev = &st->spi->dev; + + st->det_en = device_property_read_bool(dev, "adi,detector-enable"); + + ret = device_property_match_property_string(dev, "adi,input-mode", + admv1013_input_modes, + ARRAY_SIZE(admv1013_input_modes)); + st->input_mode = ret >= 0 ? ret : ADMV1013_IQ_MODE; + + ret = device_property_match_property_string(dev, "adi,quad-se-mode", + admv1013_quad_se_modes, + ARRAY_SIZE(admv1013_quad_se_modes)); + st->quad_se_mode = ret >= 0 ? ret : ADMV1013_SE_MODE_DIFF; + + ret = devm_regulator_bulk_get_enable(dev, + ARRAY_SIZE(admv1013_vcc_regs), + admv1013_vcc_regs); + if (ret) { + dev_err_probe(dev, ret, + "Failed to request VCC regulators\n"); + return ret; + } + + return 0; +} + +static int admv1013_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct admv1013_state *st; + struct device *dev = &spi->dev; + int ret, vcm_uv; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + indio_dev->info = &admv1013_info; + indio_dev->name = "admv1013"; + indio_dev->channels = admv1013_channels; + indio_dev->num_channels = ARRAY_SIZE(admv1013_channels); + + st->spi = spi; + + ret = admv1013_properties_parse(st); + if (ret) + return ret; + + ret = devm_regulator_get_enable_read_voltage(dev, "vcm"); + if (ret < 0) + return dev_err_probe(dev, ret, + "failed to get the common-mode voltage\n"); + + vcm_uv = ret; + + st->clkin = devm_clk_get_enabled(dev, "lo_in"); + if (IS_ERR(st->clkin)) + return dev_err_probe(dev, PTR_ERR(st->clkin), + "failed to get the LO input clock\n"); + + st->nb.notifier_call = admv1013_freq_change; + ret = devm_clk_notifier_register(dev, st->clkin, &st->nb); + if (ret) + return ret; + + mutex_init(&st->lock); + + ret = admv1013_init(st, vcm_uv); + if (ret) + return dev_err_probe(dev, ret, "admv1013 init failed\n"); + + ret = devm_add_action_or_reset(dev, admv1013_powerdown, st); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id admv1013_id[] = { + { .name = "admv1013" }, + { } +}; +MODULE_DEVICE_TABLE(spi, admv1013_id); + +static const struct of_device_id admv1013_of_match[] = { + { .compatible = "adi,admv1013" }, + { } +}; +MODULE_DEVICE_TABLE(of, admv1013_of_match); + +static struct spi_driver admv1013_driver = { + .driver = { + .name = "admv1013", + .of_match_table = admv1013_of_match, + }, + .probe = admv1013_probe, + .id_table = admv1013_id, +}; +module_spi_driver(admv1013_driver); + +MODULE_AUTHOR("Antoniu Miclaus +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* ADMV1014 Register Map */ +#define ADMV1014_REG_SPI_CONTROL 0x00 +#define ADMV1014_REG_ALARM 0x01 +#define ADMV1014_REG_ALARM_MASKS 0x02 +#define ADMV1014_REG_ENABLE 0x03 +#define ADMV1014_REG_QUAD 0x04 +#define ADMV1014_REG_LO_AMP_PHASE_ADJUST1 0x05 +#define ADMV1014_REG_MIXER 0x07 +#define ADMV1014_REG_IF_AMP 0x08 +#define ADMV1014_REG_IF_AMP_BB_AMP 0x09 +#define ADMV1014_REG_BB_AMP_AGC 0x0A +#define ADMV1014_REG_VVA_TEMP_COMP 0x0B + +/* ADMV1014_REG_SPI_CONTROL Map */ +#define ADMV1014_PARITY_EN_MSK BIT(15) +#define ADMV1014_SPI_SOFT_RESET_MSK BIT(14) +#define ADMV1014_CHIP_ID_MSK GENMASK(11, 4) +#define ADMV1014_CHIP_ID 0x9 +#define ADMV1014_REVISION_ID_MSK GENMASK(3, 0) + +/* ADMV1014_REG_ALARM Map */ +#define ADMV1014_PARITY_ERROR_MSK BIT(15) +#define ADMV1014_TOO_FEW_ERRORS_MSK BIT(14) +#define ADMV1014_TOO_MANY_ERRORS_MSK BIT(13) +#define ADMV1014_ADDRESS_RANGE_ERROR_MSK BIT(12) + +/* ADMV1014_REG_ENABLE Map */ +#define ADMV1014_IBIAS_PD_MSK BIT(14) +#define ADMV1014_P1DB_COMPENSATION_MSK GENMASK(13, 12) +#define ADMV1014_IF_AMP_PD_MSK BIT(11) +#define ADMV1014_QUAD_BG_PD_MSK BIT(9) +#define ADMV1014_BB_AMP_PD_MSK BIT(8) +#define ADMV1014_QUAD_IBIAS_PD_MSK BIT(7) +#define ADMV1014_DET_EN_MSK BIT(6) +#define ADMV1014_BG_PD_MSK BIT(5) + +/* ADMV1014_REG_QUAD Map */ +#define ADMV1014_QUAD_SE_MODE_MSK GENMASK(9, 6) +#define ADMV1014_QUAD_FILTERS_MSK GENMASK(3, 0) + +/* ADMV1014_REG_LO_AMP_PHASE_ADJUST1 Map */ +#define ADMV1014_LOAMP_PH_ADJ_I_FINE_MSK GENMASK(15, 9) +#define ADMV1014_LOAMP_PH_ADJ_Q_FINE_MSK GENMASK(8, 2) + +/* ADMV1014_REG_MIXER Map */ +#define ADMV1014_MIXER_VGATE_MSK GENMASK(15, 9) +#define ADMV1014_DET_PROG_MSK GENMASK(6, 0) + +/* ADMV1014_REG_IF_AMP Map */ +#define ADMV1014_IF_AMP_COARSE_GAIN_I_MSK GENMASK(11, 8) +#define ADMV1014_IF_AMP_FINE_GAIN_Q_MSK GENMASK(7, 4) +#define ADMV1014_IF_AMP_FINE_GAIN_I_MSK GENMASK(3, 0) + +/* ADMV1014_REG_IF_AMP_BB_AMP Map */ +#define ADMV1014_IF_AMP_COARSE_GAIN_Q_MSK GENMASK(15, 12) +#define ADMV1014_BB_AMP_OFFSET_Q_MSK GENMASK(9, 5) +#define ADMV1014_BB_AMP_OFFSET_I_MSK GENMASK(4, 0) + +/* ADMV1014_REG_BB_AMP_AGC Map */ +#define ADMV1014_BB_AMP_REF_GEN_MSK GENMASK(6, 3) +#define ADMV1014_BB_AMP_GAIN_CTRL_MSK GENMASK(2, 1) +#define ADMV1014_BB_SWITCH_HIGH_LOW_CM_MSK BIT(0) + +/* ADMV1014_REG_VVA_TEMP_COMP Map */ +#define ADMV1014_VVA_TEMP_COMP_MSK GENMASK(15, 0) + +/* ADMV1014 Miscellaneous Defines */ +#define ADMV1014_READ BIT(7) +#define ADMV1014_REG_ADDR_READ_MSK GENMASK(6, 1) +#define ADMV1014_REG_ADDR_WRITE_MSK GENMASK(22, 17) +#define ADMV1014_REG_DATA_MSK GENMASK(16, 1) +#define ADMV1014_NUM_REGULATORS 9 + +enum { + ADMV1014_IQ_MODE, + ADMV1014_IF_MODE, +}; + +enum { + ADMV1014_SE_MODE_POS = 6, + ADMV1014_SE_MODE_NEG = 9, + ADMV1014_SE_MODE_DIFF = 12, +}; + +enum { + ADMV1014_CALIBSCALE_COARSE, + ADMV1014_CALIBSCALE_FINE, +}; + +static const int detector_table[] = {0, 1, 2, 4, 8, 16, 32, 64}; + +static const char * const input_mode_names[] = { "iq", "if" }; + +static const char * const quad_se_mode_names[] = { "se-pos", "se-neg", "diff" }; + +struct admv1014_state { + struct spi_device *spi; + struct clk *clkin; + struct notifier_block nb; + /* Protect against concurrent accesses to the device and to data*/ + struct mutex lock; + struct regulator_bulk_data regulators[ADMV1014_NUM_REGULATORS]; + unsigned int input_mode; + unsigned int quad_se_mode; + unsigned int p1db_comp; + bool det_en; + u8 data[3] __aligned(IIO_DMA_MINALIGN); +}; + +static const int mixer_vgate_table[] = {106, 107, 108, 110, 111, 112, 113, 114, + 117, 118, 119, 120, 122, 123, 44, 45}; + +static int __admv1014_spi_read(struct admv1014_state *st, unsigned int reg, + unsigned int *val) +{ + struct spi_transfer t = {}; + int ret; + + st->data[0] = ADMV1014_READ | FIELD_PREP(ADMV1014_REG_ADDR_READ_MSK, reg); + st->data[1] = 0; + st->data[2] = 0; + + t.rx_buf = &st->data[0]; + t.tx_buf = &st->data[0]; + t.len = sizeof(st->data); + + ret = spi_sync_transfer(st->spi, &t, 1); + if (ret) + return ret; + + *val = FIELD_GET(ADMV1014_REG_DATA_MSK, get_unaligned_be24(&st->data[0])); + + return ret; +} + +static int admv1014_spi_read(struct admv1014_state *st, unsigned int reg, + unsigned int *val) +{ + int ret; + + mutex_lock(&st->lock); + ret = __admv1014_spi_read(st, reg, val); + mutex_unlock(&st->lock); + + return ret; +} + +static int __admv1014_spi_write(struct admv1014_state *st, + unsigned int reg, + unsigned int val) +{ + put_unaligned_be24(FIELD_PREP(ADMV1014_REG_DATA_MSK, val) | + FIELD_PREP(ADMV1014_REG_ADDR_WRITE_MSK, reg), &st->data[0]); + + return spi_write(st->spi, &st->data[0], 3); +} + +static int admv1014_spi_write(struct admv1014_state *st, unsigned int reg, + unsigned int val) +{ + int ret; + + mutex_lock(&st->lock); + ret = __admv1014_spi_write(st, reg, val); + mutex_unlock(&st->lock); + + return ret; +} + +static int __admv1014_spi_update_bits(struct admv1014_state *st, unsigned int reg, + unsigned int mask, unsigned int val) +{ + unsigned int data, temp; + int ret; + + ret = __admv1014_spi_read(st, reg, &data); + if (ret) + return ret; + + temp = (data & ~mask) | (val & mask); + + return __admv1014_spi_write(st, reg, temp); +} + +static int admv1014_spi_update_bits(struct admv1014_state *st, unsigned int reg, + unsigned int mask, unsigned int val) +{ + int ret; + + mutex_lock(&st->lock); + ret = __admv1014_spi_update_bits(st, reg, mask, val); + mutex_unlock(&st->lock); + + return ret; +} + +static int admv1014_update_quad_filters(struct admv1014_state *st) +{ + unsigned int filt_raw; + u64 rate = clk_get_rate(st->clkin); + + if (rate >= (5400 * HZ_PER_MHZ) && rate <= (7000 * HZ_PER_MHZ)) + filt_raw = 15; + else if (rate > (7000 * HZ_PER_MHZ) && rate <= (8000 * HZ_PER_MHZ)) + filt_raw = 10; + else if (rate > (8000 * HZ_PER_MHZ) && rate <= (9200 * HZ_PER_MHZ)) + filt_raw = 5; + else + filt_raw = 0; + + return __admv1014_spi_update_bits(st, ADMV1014_REG_QUAD, + ADMV1014_QUAD_FILTERS_MSK, + FIELD_PREP(ADMV1014_QUAD_FILTERS_MSK, filt_raw)); +} + +static int admv1014_update_vcm_settings(struct admv1014_state *st) +{ + unsigned int i, vcm_mv, vcm_comp, bb_sw_hl_cm; + int ret; + + vcm_mv = regulator_get_voltage(st->regulators[0].consumer) / 1000; + for (i = 0; i < ARRAY_SIZE(mixer_vgate_table); i++) { + vcm_comp = 1050 + mult_frac(i, 450, 8); + if (vcm_mv != vcm_comp) + continue; + + ret = __admv1014_spi_update_bits(st, ADMV1014_REG_MIXER, + ADMV1014_MIXER_VGATE_MSK, + FIELD_PREP(ADMV1014_MIXER_VGATE_MSK, + mixer_vgate_table[i])); + if (ret) + return ret; + + bb_sw_hl_cm = ~(i / 8); + bb_sw_hl_cm = FIELD_PREP(ADMV1014_BB_SWITCH_HIGH_LOW_CM_MSK, bb_sw_hl_cm); + + return __admv1014_spi_update_bits(st, ADMV1014_REG_BB_AMP_AGC, + ADMV1014_BB_AMP_REF_GEN_MSK | + ADMV1014_BB_SWITCH_HIGH_LOW_CM_MSK, + FIELD_PREP(ADMV1014_BB_AMP_REF_GEN_MSK, i) | + bb_sw_hl_cm); + } + + return -EINVAL; +} + +static int admv1014_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct admv1014_state *st = iio_priv(indio_dev); + unsigned int data; + int ret; + + switch (info) { + case IIO_CHAN_INFO_OFFSET: + ret = admv1014_spi_read(st, ADMV1014_REG_IF_AMP_BB_AMP, &data); + if (ret) + return ret; + + if (chan->channel2 == IIO_MOD_I) + *val = FIELD_GET(ADMV1014_BB_AMP_OFFSET_I_MSK, data); + else + *val = FIELD_GET(ADMV1014_BB_AMP_OFFSET_Q_MSK, data); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_PHASE: + ret = admv1014_spi_read(st, ADMV1014_REG_LO_AMP_PHASE_ADJUST1, &data); + if (ret) + return ret; + + if (chan->channel2 == IIO_MOD_I) + *val = FIELD_GET(ADMV1014_LOAMP_PH_ADJ_I_FINE_MSK, data); + else + *val = FIELD_GET(ADMV1014_LOAMP_PH_ADJ_Q_FINE_MSK, data); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = admv1014_spi_read(st, ADMV1014_REG_MIXER, &data); + if (ret) + return ret; + + *val = FIELD_GET(ADMV1014_DET_PROG_MSK, data); + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + ret = admv1014_spi_read(st, ADMV1014_REG_BB_AMP_AGC, &data); + if (ret) + return ret; + + *val = FIELD_GET(ADMV1014_BB_AMP_GAIN_CTRL_MSK, data); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int admv1014_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + int data; + unsigned int msk; + struct admv1014_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_OFFSET: + if (chan->channel2 == IIO_MOD_I) { + msk = ADMV1014_BB_AMP_OFFSET_I_MSK; + data = FIELD_PREP(ADMV1014_BB_AMP_OFFSET_I_MSK, val); + } else { + msk = ADMV1014_BB_AMP_OFFSET_Q_MSK; + data = FIELD_PREP(ADMV1014_BB_AMP_OFFSET_Q_MSK, val); + } + + return admv1014_spi_update_bits(st, ADMV1014_REG_IF_AMP_BB_AMP, msk, data); + case IIO_CHAN_INFO_PHASE: + if (chan->channel2 == IIO_MOD_I) { + msk = ADMV1014_LOAMP_PH_ADJ_I_FINE_MSK; + data = FIELD_PREP(ADMV1014_LOAMP_PH_ADJ_I_FINE_MSK, val); + } else { + msk = ADMV1014_LOAMP_PH_ADJ_Q_FINE_MSK; + data = FIELD_PREP(ADMV1014_LOAMP_PH_ADJ_Q_FINE_MSK, val); + } + + return admv1014_spi_update_bits(st, ADMV1014_REG_LO_AMP_PHASE_ADJUST1, msk, data); + case IIO_CHAN_INFO_SCALE: + return admv1014_spi_update_bits(st, ADMV1014_REG_MIXER, + ADMV1014_DET_PROG_MSK, + FIELD_PREP(ADMV1014_DET_PROG_MSK, val)); + case IIO_CHAN_INFO_CALIBSCALE: + return admv1014_spi_update_bits(st, ADMV1014_REG_BB_AMP_AGC, + ADMV1014_BB_AMP_GAIN_CTRL_MSK, + FIELD_PREP(ADMV1014_BB_AMP_GAIN_CTRL_MSK, val)); + default: + return -EINVAL; + } +} + +static ssize_t admv1014_read(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct admv1014_state *st = iio_priv(indio_dev); + unsigned int data; + int ret; + + switch (private) { + case ADMV1014_CALIBSCALE_COARSE: + if (chan->channel2 == IIO_MOD_I) { + ret = admv1014_spi_read(st, ADMV1014_REG_IF_AMP, &data); + if (ret) + return ret; + + data = FIELD_GET(ADMV1014_IF_AMP_COARSE_GAIN_I_MSK, data); + } else { + ret = admv1014_spi_read(st, ADMV1014_REG_IF_AMP_BB_AMP, &data); + if (ret) + return ret; + + data = FIELD_GET(ADMV1014_IF_AMP_COARSE_GAIN_Q_MSK, data); + } + break; + case ADMV1014_CALIBSCALE_FINE: + ret = admv1014_spi_read(st, ADMV1014_REG_IF_AMP, &data); + if (ret) + return ret; + + if (chan->channel2 == IIO_MOD_I) + data = FIELD_GET(ADMV1014_IF_AMP_FINE_GAIN_I_MSK, data); + else + data = FIELD_GET(ADMV1014_IF_AMP_FINE_GAIN_Q_MSK, data); + break; + default: + return -EINVAL; + } + + return sysfs_emit(buf, "%u\n", data); +} + +static ssize_t admv1014_write(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct admv1014_state *st = iio_priv(indio_dev); + unsigned int data, addr, msk; + int ret; + + ret = kstrtouint(buf, 10, &data); + if (ret) + return ret; + + switch (private) { + case ADMV1014_CALIBSCALE_COARSE: + if (chan->channel2 == IIO_MOD_I) { + addr = ADMV1014_REG_IF_AMP; + msk = ADMV1014_IF_AMP_COARSE_GAIN_I_MSK; + data = FIELD_PREP(ADMV1014_IF_AMP_COARSE_GAIN_I_MSK, data); + } else { + addr = ADMV1014_REG_IF_AMP_BB_AMP; + msk = ADMV1014_IF_AMP_COARSE_GAIN_Q_MSK; + data = FIELD_PREP(ADMV1014_IF_AMP_COARSE_GAIN_Q_MSK, data); + } + break; + case ADMV1014_CALIBSCALE_FINE: + addr = ADMV1014_REG_IF_AMP; + + if (chan->channel2 == IIO_MOD_I) { + msk = ADMV1014_IF_AMP_FINE_GAIN_I_MSK; + data = FIELD_PREP(ADMV1014_IF_AMP_FINE_GAIN_I_MSK, data); + } else { + msk = ADMV1014_IF_AMP_FINE_GAIN_Q_MSK; + data = FIELD_PREP(ADMV1014_IF_AMP_FINE_GAIN_Q_MSK, data); + } + break; + default: + return -EINVAL; + } + + ret = admv1014_spi_update_bits(st, addr, msk, data); + + return ret ? ret : len; +} + +static int admv1014_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long info) +{ + switch (info) { + case IIO_CHAN_INFO_SCALE: + *vals = detector_table; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(detector_table); + + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int admv1014_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int write_val, + unsigned int *read_val) +{ + struct admv1014_state *st = iio_priv(indio_dev); + + if (read_val) + return admv1014_spi_read(st, reg, read_val); + else + return admv1014_spi_write(st, reg, write_val); +} + +static const struct iio_info admv1014_info = { + .read_raw = admv1014_read_raw, + .write_raw = admv1014_write_raw, + .read_avail = &admv1014_read_avail, + .debugfs_reg_access = &admv1014_reg_access, +}; + +static const char * const admv1014_reg_name[] = { + "vcm", "vcc-if-bb", "vcc-vga", "vcc-vva", "vcc-lna-3p3", + "vcc-lna-1p5", "vcc-bg", "vcc-quad", "vcc-mixer" +}; + +static int admv1014_freq_change(struct notifier_block *nb, unsigned long action, void *data) +{ + struct admv1014_state *st = container_of(nb, struct admv1014_state, nb); + int ret; + + if (action == POST_RATE_CHANGE) { + mutex_lock(&st->lock); + ret = notifier_from_errno(admv1014_update_quad_filters(st)); + mutex_unlock(&st->lock); + return ret; + } + + return NOTIFY_OK; +} + +#define _ADMV1014_EXT_INFO(_name, _shared, _ident) { \ + .name = _name, \ + .read = admv1014_read, \ + .write = admv1014_write, \ + .private = _ident, \ + .shared = _shared, \ +} + +static const struct iio_chan_spec_ext_info admv1014_ext_info[] = { + _ADMV1014_EXT_INFO("calibscale_coarse", IIO_SEPARATE, ADMV1014_CALIBSCALE_COARSE), + _ADMV1014_EXT_INFO("calibscale_fine", IIO_SEPARATE, ADMV1014_CALIBSCALE_FINE), + { } +}; + +#define ADMV1014_CHAN_IQ(_channel, rf_comp) { \ + .type = IIO_ALTVOLTAGE, \ + .modified = 1, \ + .output = 0, \ + .indexed = 1, \ + .channel2 = IIO_MOD_##rf_comp, \ + .channel = _channel, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PHASE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBSCALE), \ + } + +#define ADMV1014_CHAN_IF(_channel, rf_comp) { \ + .type = IIO_ALTVOLTAGE, \ + .modified = 1, \ + .output = 0, \ + .indexed = 1, \ + .channel2 = IIO_MOD_##rf_comp, \ + .channel = _channel, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PHASE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + } + +#define ADMV1014_CHAN_POWER(_channel) { \ + .type = IIO_POWER, \ + .output = 0, \ + .indexed = 1, \ + .channel = _channel, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), \ + } + +#define ADMV1014_CHAN_CALIBSCALE(_channel, rf_comp, _admv1014_ext_info) { \ + .type = IIO_ALTVOLTAGE, \ + .modified = 1, \ + .output = 0, \ + .indexed = 1, \ + .channel2 = IIO_MOD_##rf_comp, \ + .channel = _channel, \ + .ext_info = _admv1014_ext_info, \ + } + +static const struct iio_chan_spec admv1014_channels_iq[] = { + ADMV1014_CHAN_IQ(0, I), + ADMV1014_CHAN_IQ(0, Q), + ADMV1014_CHAN_POWER(0), +}; + +static const struct iio_chan_spec admv1014_channels_if[] = { + ADMV1014_CHAN_IF(0, I), + ADMV1014_CHAN_IF(0, Q), + ADMV1014_CHAN_CALIBSCALE(0, I, admv1014_ext_info), + ADMV1014_CHAN_CALIBSCALE(0, Q, admv1014_ext_info), + ADMV1014_CHAN_POWER(0), +}; + +static void admv1014_clk_disable(void *data) +{ + clk_disable_unprepare(data); +} + +static void admv1014_reg_disable(void *data) +{ + regulator_bulk_disable(ADMV1014_NUM_REGULATORS, data); +} + +static void admv1014_powerdown(void *data) +{ + unsigned int enable_reg, enable_reg_msk; + + /* Disable all components in the Enable Register */ + enable_reg_msk = ADMV1014_IBIAS_PD_MSK | + ADMV1014_IF_AMP_PD_MSK | + ADMV1014_QUAD_BG_PD_MSK | + ADMV1014_BB_AMP_PD_MSK | + ADMV1014_QUAD_IBIAS_PD_MSK | + ADMV1014_BG_PD_MSK; + + enable_reg = FIELD_PREP(ADMV1014_IBIAS_PD_MSK, 1) | + FIELD_PREP(ADMV1014_IF_AMP_PD_MSK, 1) | + FIELD_PREP(ADMV1014_QUAD_BG_PD_MSK, 1) | + FIELD_PREP(ADMV1014_BB_AMP_PD_MSK, 1) | + FIELD_PREP(ADMV1014_QUAD_IBIAS_PD_MSK, 1) | + FIELD_PREP(ADMV1014_BG_PD_MSK, 1); + + admv1014_spi_update_bits(data, ADMV1014_REG_ENABLE, + enable_reg_msk, enable_reg); +} + +static int admv1014_init(struct admv1014_state *st) +{ + unsigned int chip_id, enable_reg, enable_reg_msk; + struct spi_device *spi = st->spi; + struct device *dev = &spi->dev; + int ret; + + ret = regulator_bulk_enable(ADMV1014_NUM_REGULATORS, st->regulators); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulators"); + + ret = devm_add_action_or_reset(dev, admv1014_reg_disable, st->regulators); + if (ret) + return ret; + + ret = clk_prepare_enable(st->clkin); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, admv1014_clk_disable, st->clkin); + if (ret) + return ret; + + st->nb.notifier_call = admv1014_freq_change; + ret = devm_clk_notifier_register(dev, st->clkin, &st->nb); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, admv1014_powerdown, st); + if (ret) + return ret; + + /* Perform a software reset */ + ret = __admv1014_spi_update_bits(st, ADMV1014_REG_SPI_CONTROL, + ADMV1014_SPI_SOFT_RESET_MSK, + FIELD_PREP(ADMV1014_SPI_SOFT_RESET_MSK, 1)); + if (ret) + return dev_err_probe(dev, ret, + "ADMV1014 SPI software reset failed.\n"); + + ret = __admv1014_spi_update_bits(st, ADMV1014_REG_SPI_CONTROL, + ADMV1014_SPI_SOFT_RESET_MSK, + FIELD_PREP(ADMV1014_SPI_SOFT_RESET_MSK, 0)); + if (ret) + return dev_err_probe(dev, ret, + "ADMV1014 SPI software reset disable failed.\n"); + + ret = __admv1014_spi_write(st, ADMV1014_REG_VVA_TEMP_COMP, 0x727C); + if (ret) + return dev_err_probe(dev, ret, + "Writing default Temperature Compensation value failed.\n"); + + ret = __admv1014_spi_read(st, ADMV1014_REG_SPI_CONTROL, &chip_id); + if (ret) + return ret; + + chip_id = FIELD_GET(ADMV1014_CHIP_ID_MSK, chip_id); + if (chip_id != ADMV1014_CHIP_ID) + return dev_err_probe(dev, -EINVAL, "Invalid Chip ID.\n"); + + ret = __admv1014_spi_update_bits(st, ADMV1014_REG_QUAD, + ADMV1014_QUAD_SE_MODE_MSK, + FIELD_PREP(ADMV1014_QUAD_SE_MODE_MSK, + st->quad_se_mode)); + if (ret) + return dev_err_probe(dev, ret, + "Writing Quad SE Mode failed.\n"); + + ret = admv1014_update_quad_filters(st); + if (ret) + return dev_err_probe(dev, ret, + "Update Quad Filters failed.\n"); + + ret = admv1014_update_vcm_settings(st); + if (ret) + return dev_err_probe(dev, ret, + "Update VCM Settings failed.\n"); + + enable_reg_msk = ADMV1014_P1DB_COMPENSATION_MSK | + ADMV1014_IF_AMP_PD_MSK | + ADMV1014_BB_AMP_PD_MSK | + ADMV1014_DET_EN_MSK; + + enable_reg = FIELD_PREP(ADMV1014_P1DB_COMPENSATION_MSK, st->p1db_comp ? 3 : 0) | + FIELD_PREP(ADMV1014_IF_AMP_PD_MSK, + (st->input_mode == ADMV1014_IF_MODE) ? 0 : 1) | + FIELD_PREP(ADMV1014_BB_AMP_PD_MSK, + (st->input_mode == ADMV1014_IF_MODE) ? 1 : 0) | + FIELD_PREP(ADMV1014_DET_EN_MSK, st->det_en); + + return __admv1014_spi_update_bits(st, ADMV1014_REG_ENABLE, enable_reg_msk, enable_reg); +} + +static int admv1014_properties_parse(struct admv1014_state *st) +{ + unsigned int i; + struct spi_device *spi = st->spi; + struct device *dev = &spi->dev; + int ret; + + st->det_en = device_property_read_bool(dev, "adi,detector-enable"); + + st->p1db_comp = device_property_read_bool(dev, "adi,p1db-compensation-enable"); + + ret = device_property_match_property_string(dev, "adi,input-mode", + input_mode_names, + ARRAY_SIZE(input_mode_names)); + if (ret >= 0) + st->input_mode = ret; + else + st->input_mode = ADMV1014_IQ_MODE; + + ret = device_property_match_property_string(dev, "adi,quad-se-mode", + quad_se_mode_names, + ARRAY_SIZE(quad_se_mode_names)); + if (ret >= 0) + st->quad_se_mode = ADMV1014_SE_MODE_POS + (ret * 3); + else + st->quad_se_mode = ADMV1014_SE_MODE_POS; + + for (i = 0; i < ADMV1014_NUM_REGULATORS; ++i) + st->regulators[i].supply = admv1014_reg_name[i]; + + ret = devm_regulator_bulk_get(dev, ADMV1014_NUM_REGULATORS, + st->regulators); + if (ret) + return dev_err_probe(dev, ret, "Failed to request regulators"); + + st->clkin = devm_clk_get(dev, "lo_in"); + if (IS_ERR(st->clkin)) + return dev_err_probe(dev, PTR_ERR(st->clkin), + "failed to get the LO input clock\n"); + + return 0; +} + +static int admv1014_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct admv1014_state *st; + struct device *dev = &spi->dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + ret = admv1014_properties_parse(st); + if (ret) + return ret; + + indio_dev->info = &admv1014_info; + indio_dev->name = "admv1014"; + + if (st->input_mode == ADMV1014_IQ_MODE) { + indio_dev->channels = admv1014_channels_iq; + indio_dev->num_channels = ARRAY_SIZE(admv1014_channels_iq); + } else { + indio_dev->channels = admv1014_channels_if; + indio_dev->num_channels = ARRAY_SIZE(admv1014_channels_if); + } + + st->spi = spi; + + mutex_init(&st->lock); + + ret = admv1014_init(st); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id admv1014_id[] = { + { .name = "admv1014" }, + { } +}; +MODULE_DEVICE_TABLE(spi, admv1014_id); + +static const struct of_device_id admv1014_of_match[] = { + { .compatible = "adi,admv1014" }, + { } +}; +MODULE_DEVICE_TABLE(of, admv1014_of_match); + +static struct spi_driver admv1014_driver = { + .driver = { + .name = "admv1014", + .of_match_table = admv1014_of_match, + }, + .probe = admv1014_probe, + .id_table = admv1014_id, +}; +module_spi_driver(admv1014_driver); + +MODULE_AUTHOR("Antoniu Miclaus +#include +#include +#include +#include +#include +#include + +#include + +/* ADMV4420 Register Map */ +#define ADMV4420_SPI_CONFIG_1 0x00 +#define ADMV4420_SPI_CONFIG_2 0x01 +#define ADMV4420_CHIPTYPE 0x03 +#define ADMV4420_PRODUCT_ID_L 0x04 +#define ADMV4420_PRODUCT_ID_H 0x05 +#define ADMV4420_SCRATCHPAD 0x0A +#define ADMV4420_SPI_REV 0x0B +#define ADMV4420_ENABLES 0x103 +#define ADMV4420_SDO_LEVEL 0x108 +#define ADMV4420_INT_L 0x200 +#define ADMV4420_INT_H 0x201 +#define ADMV4420_FRAC_L 0x202 +#define ADMV4420_FRAC_M 0x203 +#define ADMV4420_FRAC_H 0x204 +#define ADMV4420_MOD_L 0x208 +#define ADMV4420_MOD_M 0x209 +#define ADMV4420_MOD_H 0x20A +#define ADMV4420_R_DIV_L 0x20C +#define ADMV4420_R_DIV_H 0x20D +#define ADMV4420_REFERENCE 0x20E +#define ADMV4420_VCO_DATA_READBACK1 0x211 +#define ADMV4420_VCO_DATA_READBACK2 0x212 +#define ADMV4420_PLL_MUX_SEL 0x213 +#define ADMV4420_LOCK_DETECT 0x214 +#define ADMV4420_BAND_SELECT 0x215 +#define ADMV4420_VCO_ALC_TIMEOUT 0x216 +#define ADMV4420_VCO_MANUAL 0x217 +#define ADMV4420_ALC 0x219 +#define ADMV4420_VCO_TIMEOUT1 0x21C +#define ADMV4420_VCO_TIMEOUT2 0x21D +#define ADMV4420_VCO_BAND_DIV 0x21E +#define ADMV4420_VCO_READBACK_SEL 0x21F +#define ADMV4420_AUTOCAL 0x226 +#define ADMV4420_CP_STATE 0x22C +#define ADMV4420_CP_BLEED_EN 0x22D +#define ADMV4420_CP_CURRENT 0x22E +#define ADMV4420_CP_BLEED 0x22F + +#define ADMV4420_SPI_CONFIG_1_SDOACTIVE (BIT(4) | BIT(3)) +#define ADMV4420_SPI_CONFIG_1_ENDIAN (BIT(5) | BIT(2)) +#define ADMV4420_SPI_CONFIG_1_SOFTRESET (BIT(7) | BIT(1)) + +#define ADMV4420_REFERENCE_DIVIDE_BY_2_MASK BIT(0) +#define ADMV4420_REFERENCE_MODE_MASK BIT(1) +#define ADMV4420_REFERENCE_DOUBLER_MASK BIT(2) + +#define ADMV4420_REF_DIVIDER_MAX_VAL GENMASK(9, 0) +#define ADMV4420_N_COUNTER_INT_MAX GENMASK(15, 0) +#define ADMV4420_N_COUNTER_FRAC_MAX GENMASK(23, 0) +#define ADMV4420_N_COUNTER_MOD_MAX GENMASK(23, 0) + +#define ENABLE_PLL BIT(6) +#define ENABLE_LO BIT(5) +#define ENABLE_VCO BIT(3) +#define ENABLE_IFAMP BIT(2) +#define ENABLE_MIXER BIT(1) +#define ENABLE_LNA BIT(0) + +#define ADMV4420_SCRATCH_PAD_VAL_1 0xAD +#define ADMV4420_SCRATCH_PAD_VAL_2 0xEA + +#define ADMV4420_REF_FREQ_HZ 50000000 +#define MAX_N_COUNTER 655360UL +#define MAX_R_DIVIDER 1024 +#define ADMV4420_DEFAULT_LO_FREQ_HZ 16750000000ULL + +enum admv4420_mux_sel { + ADMV4420_LOW = 0, + ADMV4420_LOCK_DTCT = 1, + ADMV4420_R_COUNTER_PER_2 = 4, + ADMV4420_N_CONUTER_PER_2 = 5, + ADMV4420_HIGH = 8, +}; + +struct admv4420_reference_block { + bool doubler_en; + bool divide_by_2_en; + bool ref_single_ended; + u32 divider; +}; + +struct admv4420_n_counter { + u32 int_val; + u32 frac_val; + u32 mod_val; + u32 n_counter; +}; + +struct admv4420_state { + struct spi_device *spi; + struct regmap *regmap; + u64 vco_freq_hz; + u64 lo_freq_hz; + struct admv4420_reference_block ref_block; + struct admv4420_n_counter n_counter; + enum admv4420_mux_sel mux_sel; + struct mutex lock; + u8 transf_buf[4] __aligned(IIO_DMA_MINALIGN); +}; + +static const struct regmap_config admv4420_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .read_flag_mask = BIT(7), +}; + +static int admv4420_reg_access(struct iio_dev *indio_dev, + u32 reg, u32 writeval, + u32 *readval) +{ + struct admv4420_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + else + return regmap_write(st->regmap, reg, writeval); +} + +static int admv4420_set_n_counter(struct admv4420_state *st, u32 int_val, + u32 frac_val, u32 mod_val) +{ + int ret; + + put_unaligned_le32(frac_val, st->transf_buf); + ret = regmap_bulk_write(st->regmap, ADMV4420_FRAC_L, st->transf_buf, 3); + if (ret) + return ret; + + put_unaligned_le32(mod_val, st->transf_buf); + ret = regmap_bulk_write(st->regmap, ADMV4420_MOD_L, st->transf_buf, 3); + if (ret) + return ret; + + put_unaligned_le32(int_val, st->transf_buf); + return regmap_bulk_write(st->regmap, ADMV4420_INT_L, st->transf_buf, 2); +} + +static int admv4420_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct admv4420_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_FREQUENCY: + + *val = div_u64_rem(st->lo_freq_hz, MICRO, val2); + + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static const struct iio_info admv4420_info = { + .read_raw = admv4420_read_raw, + .debugfs_reg_access = &admv4420_reg_access, +}; + +static const struct iio_chan_spec admv4420_channels[] = { + { + .type = IIO_ALTVOLTAGE, + .output = 0, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_FREQUENCY), + }, +}; + +static void admv4420_fw_parse(struct admv4420_state *st) +{ + struct device *dev = &st->spi->dev; + u32 tmp; + int ret; + + ret = device_property_read_u32(dev, "adi,lo-freq-khz", &tmp); + if (!ret) + st->lo_freq_hz = (u64)tmp * KILO; + + st->ref_block.ref_single_ended = device_property_read_bool(dev, + "adi,ref-ext-single-ended-en"); +} + +static inline uint64_t admv4420_calc_pfd_vco(struct admv4420_state *st) +{ + return div_u64(st->vco_freq_hz * 10, st->n_counter.n_counter); +} + +static inline uint32_t admv4420_calc_pfd_ref(struct admv4420_state *st) +{ + uint32_t tmp; + u8 doubler, divide_by_2; + + doubler = st->ref_block.doubler_en ? 2 : 1; + divide_by_2 = st->ref_block.divide_by_2_en ? 2 : 1; + tmp = ADMV4420_REF_FREQ_HZ * doubler; + + return (tmp / (st->ref_block.divider * divide_by_2)); +} + +static int admv4420_calc_parameters(struct admv4420_state *st) +{ + u64 pfd_ref, pfd_vco; + bool sol_found = false; + + st->ref_block.doubler_en = false; + st->ref_block.divide_by_2_en = false; + st->vco_freq_hz = div_u64(st->lo_freq_hz, 2); + + for (st->ref_block.divider = 1; st->ref_block.divider < MAX_R_DIVIDER; + st->ref_block.divider++) { + pfd_ref = admv4420_calc_pfd_ref(st); + for (st->n_counter.n_counter = 1; st->n_counter.n_counter < MAX_N_COUNTER; + st->n_counter.n_counter++) { + pfd_vco = admv4420_calc_pfd_vco(st); + if (pfd_ref == pfd_vco) { + sol_found = true; + break; + } + } + + if (sol_found) + break; + + st->n_counter.n_counter = 1; + } + if (!sol_found) + return -EINVAL; + + st->n_counter.int_val = div_u64_rem(st->n_counter.n_counter, 10, &st->n_counter.frac_val); + st->n_counter.mod_val = 10; + + return 0; +} + +static int admv4420_setup(struct iio_dev *indio_dev) +{ + struct admv4420_state *st = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + u32 val; + int ret; + + ret = regmap_write(st->regmap, ADMV4420_SPI_CONFIG_1, + ADMV4420_SPI_CONFIG_1_SOFTRESET); + if (ret) + return ret; + + ret = regmap_write(st->regmap, ADMV4420_SPI_CONFIG_1, + ADMV4420_SPI_CONFIG_1_SDOACTIVE | + ADMV4420_SPI_CONFIG_1_ENDIAN); + if (ret) + return ret; + + ret = regmap_write(st->regmap, + ADMV4420_SCRATCHPAD, + ADMV4420_SCRATCH_PAD_VAL_1); + if (ret) + return ret; + + ret = regmap_read(st->regmap, ADMV4420_SCRATCHPAD, &val); + if (ret) + return ret; + + if (val != ADMV4420_SCRATCH_PAD_VAL_1) + return dev_err_probe(dev, -EIO, + "Failed ADMV4420 to read/write scratchpad %x\n", val); + + ret = regmap_write(st->regmap, + ADMV4420_SCRATCHPAD, + ADMV4420_SCRATCH_PAD_VAL_2); + if (ret) + return ret; + + ret = regmap_read(st->regmap, ADMV4420_SCRATCHPAD, &val); + if (ret) + return ret; + + if (val != ADMV4420_SCRATCH_PAD_VAL_2) + return dev_err_probe(dev, -EIO, + "Failed to read/write scratchpad %x\n", val); + + st->mux_sel = ADMV4420_LOCK_DTCT; + st->lo_freq_hz = ADMV4420_DEFAULT_LO_FREQ_HZ; + + admv4420_fw_parse(st); + + ret = admv4420_calc_parameters(st); + if (ret) + return dev_err_probe(dev, ret, + "Failed calc parameters for %llu\n", + st->vco_freq_hz); + + ret = regmap_write(st->regmap, ADMV4420_R_DIV_L, + FIELD_GET(0xFF, st->ref_block.divider)); + if (ret) + return ret; + + ret = regmap_write(st->regmap, ADMV4420_R_DIV_H, + FIELD_GET(0xFF00, st->ref_block.divider)); + if (ret) + return ret; + + ret = regmap_write(st->regmap, ADMV4420_REFERENCE, + st->ref_block.divide_by_2_en | + FIELD_PREP(ADMV4420_REFERENCE_MODE_MASK, st->ref_block.ref_single_ended) | + FIELD_PREP(ADMV4420_REFERENCE_DOUBLER_MASK, st->ref_block.doubler_en)); + if (ret) + return ret; + + ret = admv4420_set_n_counter(st, st->n_counter.int_val, + st->n_counter.frac_val, + st->n_counter.mod_val); + if (ret) + return ret; + + ret = regmap_write(st->regmap, ADMV4420_PLL_MUX_SEL, st->mux_sel); + if (ret) + return ret; + + return regmap_write(st->regmap, ADMV4420_ENABLES, + ENABLE_PLL | ENABLE_LO | ENABLE_VCO | + ENABLE_IFAMP | ENABLE_MIXER | ENABLE_LNA); +} + +static int admv4420_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct admv4420_state *st; + struct regmap *regmap; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_spi(spi, &admv4420_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Failed to initializing spi regmap\n"); + + st = iio_priv(indio_dev); + st->spi = spi; + st->regmap = regmap; + + indio_dev->name = "admv4420"; + indio_dev->info = &admv4420_info; + indio_dev->channels = admv4420_channels; + indio_dev->num_channels = ARRAY_SIZE(admv4420_channels); + + ret = admv4420_setup(indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Setup ADMV4420 failed\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id admv4420_of_match[] = { + { .compatible = "adi,admv4420" }, + { } +}; + +MODULE_DEVICE_TABLE(of, admv4420_of_match); + +static struct spi_driver admv4420_driver = { + .driver = { + .name = "admv4420", + .of_match_table = admv4420_of_match, + }, + .probe = admv4420_probe, +}; + +module_spi_driver(admv4420_driver); + +MODULE_AUTHOR("Cristian Pop "); +MODULE_DESCRIPTION("Analog Devices ADMV44200 K Band Downconverter"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/iio/frequency/adrf6780.c b/drivers/iio/frequency/adrf6780.c new file mode 100644 index 000000000..79a615062 --- /dev/null +++ b/drivers/iio/frequency/adrf6780.c @@ -0,0 +1,512 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADRF6780 driver + * + * Copyright 2021 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* ADRF6780 Register Map */ +#define ADRF6780_REG_CONTROL 0x00 +#define ADRF6780_REG_ALARM_READBACK 0x01 +#define ADRF6780_REG_ALARM_MASKS 0x02 +#define ADRF6780_REG_ENABLE 0x03 +#define ADRF6780_REG_LINEARIZE 0x04 +#define ADRF6780_REG_LO_PATH 0x05 +#define ADRF6780_REG_ADC_CONTROL 0x06 +#define ADRF6780_REG_ADC_OUTPUT 0x0C + +/* ADRF6780_REG_CONTROL Map */ +#define ADRF6780_PARITY_EN_MSK BIT(15) +#define ADRF6780_SOFT_RESET_MSK BIT(14) +#define ADRF6780_CHIP_ID_MSK GENMASK(11, 4) +#define ADRF6780_CHIP_ID 0xA +#define ADRF6780_CHIP_REVISION_MSK GENMASK(3, 0) + +/* ADRF6780_REG_ALARM_READBACK Map */ +#define ADRF6780_PARITY_ERROR_MSK BIT(15) +#define ADRF6780_TOO_FEW_ERRORS_MSK BIT(14) +#define ADRF6780_TOO_MANY_ERRORS_MSK BIT(13) +#define ADRF6780_ADDRESS_RANGE_ERROR_MSK BIT(12) + +/* ADRF6780_REG_ENABLE Map */ +#define ADRF6780_VGA_BUFFER_EN_MSK BIT(8) +#define ADRF6780_DETECTOR_EN_MSK BIT(7) +#define ADRF6780_LO_BUFFER_EN_MSK BIT(6) +#define ADRF6780_IF_MODE_EN_MSK BIT(5) +#define ADRF6780_IQ_MODE_EN_MSK BIT(4) +#define ADRF6780_LO_X2_EN_MSK BIT(3) +#define ADRF6780_LO_PPF_EN_MSK BIT(2) +#define ADRF6780_LO_EN_MSK BIT(1) +#define ADRF6780_UC_BIAS_EN_MSK BIT(0) + +/* ADRF6780_REG_LINEARIZE Map */ +#define ADRF6780_RDAC_LINEARIZE_MSK GENMASK(7, 0) + +/* ADRF6780_REG_LO_PATH Map */ +#define ADRF6780_LO_SIDEBAND_MSK BIT(10) +#define ADRF6780_Q_PATH_PHASE_ACCURACY_MSK GENMASK(7, 4) +#define ADRF6780_I_PATH_PHASE_ACCURACY_MSK GENMASK(3, 0) + +/* ADRF6780_REG_ADC_CONTROL Map */ +#define ADRF6780_VDET_OUTPUT_SELECT_MSK BIT(3) +#define ADRF6780_ADC_START_MSK BIT(2) +#define ADRF6780_ADC_EN_MSK BIT(1) +#define ADRF6780_ADC_CLOCK_EN_MSK BIT(0) + +/* ADRF6780_REG_ADC_OUTPUT Map */ +#define ADRF6780_ADC_STATUS_MSK BIT(8) +#define ADRF6780_ADC_VALUE_MSK GENMASK(7, 0) + +struct adrf6780_state { + struct spi_device *spi; + struct clk *clkin; + /* Protect against concurrent accesses to the device */ + struct mutex lock; + bool vga_buff_en; + bool lo_buff_en; + bool if_mode_en; + bool iq_mode_en; + bool lo_x2_en; + bool lo_ppf_en; + bool lo_en; + bool uc_bias_en; + bool lo_sideband; + bool vdet_out_en; + u8 data[3] __aligned(IIO_DMA_MINALIGN); +}; + +static int __adrf6780_spi_read(struct adrf6780_state *st, unsigned int reg, + unsigned int *val) +{ + int ret; + struct spi_transfer t = {0}; + + st->data[0] = 0x80 | (reg << 1); + st->data[1] = 0x0; + st->data[2] = 0x0; + + t.rx_buf = &st->data[0]; + t.tx_buf = &st->data[0]; + t.len = 3; + + ret = spi_sync_transfer(st->spi, &t, 1); + if (ret) + return ret; + + *val = (get_unaligned_be24(&st->data[0]) >> 1) & GENMASK(15, 0); + + return ret; +} + +static int adrf6780_spi_read(struct adrf6780_state *st, unsigned int reg, + unsigned int *val) +{ + int ret; + + mutex_lock(&st->lock); + ret = __adrf6780_spi_read(st, reg, val); + mutex_unlock(&st->lock); + + return ret; +} + +static int __adrf6780_spi_write(struct adrf6780_state *st, + unsigned int reg, + unsigned int val) +{ + put_unaligned_be24((val << 1) | (reg << 17), &st->data[0]); + + return spi_write(st->spi, &st->data[0], 3); +} + +static int adrf6780_spi_write(struct adrf6780_state *st, unsigned int reg, + unsigned int val) +{ + int ret; + + mutex_lock(&st->lock); + ret = __adrf6780_spi_write(st, reg, val); + mutex_unlock(&st->lock); + + return ret; +} + +static int __adrf6780_spi_update_bits(struct adrf6780_state *st, + unsigned int reg, unsigned int mask, + unsigned int val) +{ + int ret; + unsigned int data, temp; + + ret = __adrf6780_spi_read(st, reg, &data); + if (ret) + return ret; + + temp = (data & ~mask) | (val & mask); + + return __adrf6780_spi_write(st, reg, temp); +} + +static int adrf6780_spi_update_bits(struct adrf6780_state *st, unsigned int reg, + unsigned int mask, unsigned int val) +{ + int ret; + + mutex_lock(&st->lock); + ret = __adrf6780_spi_update_bits(st, reg, mask, val); + mutex_unlock(&st->lock); + + return ret; +} + +static int adrf6780_read_adc_raw(struct adrf6780_state *st, unsigned int *read_val) +{ + int ret; + + mutex_lock(&st->lock); + + ret = __adrf6780_spi_update_bits(st, ADRF6780_REG_ADC_CONTROL, + ADRF6780_ADC_EN_MSK | + ADRF6780_ADC_CLOCK_EN_MSK | + ADRF6780_ADC_START_MSK, + FIELD_PREP(ADRF6780_ADC_EN_MSK, 1) | + FIELD_PREP(ADRF6780_ADC_CLOCK_EN_MSK, 1) | + FIELD_PREP(ADRF6780_ADC_START_MSK, 1)); + if (ret) + goto exit; + + /* + * Per ADRF6780 datasheet (Rev. D, page 23, ADC section), + * wait approximately 200 us for the ADC to be ready. + */ + fsleep(200); + + ret = __adrf6780_spi_read(st, ADRF6780_REG_ADC_OUTPUT, read_val); + if (ret) + goto exit; + + if (!(*read_val & ADRF6780_ADC_STATUS_MSK)) { + ret = -EINVAL; + goto exit; + } + + ret = __adrf6780_spi_update_bits(st, ADRF6780_REG_ADC_CONTROL, + ADRF6780_ADC_START_MSK, + FIELD_PREP(ADRF6780_ADC_START_MSK, 0)); + if (ret) + goto exit; + + ret = __adrf6780_spi_read(st, ADRF6780_REG_ADC_OUTPUT, read_val); + +exit: + mutex_unlock(&st->lock); + return ret; +} + +static int adrf6780_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct adrf6780_state *dev = iio_priv(indio_dev); + unsigned int data; + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + ret = adrf6780_read_adc_raw(dev, &data); + if (ret) + return ret; + + *val = data & ADRF6780_ADC_VALUE_MSK; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + ret = adrf6780_spi_read(dev, ADRF6780_REG_LINEARIZE, &data); + if (ret) + return ret; + + *val = data & ADRF6780_RDAC_LINEARIZE_MSK; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_PHASE: + ret = adrf6780_spi_read(dev, ADRF6780_REG_LO_PATH, &data); + if (ret) + return ret; + + switch (chan->channel2) { + case IIO_MOD_I: + *val = data & ADRF6780_I_PATH_PHASE_ACCURACY_MSK; + + return IIO_VAL_INT; + case IIO_MOD_Q: + *val = FIELD_GET(ADRF6780_Q_PATH_PHASE_ACCURACY_MSK, + data); + + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int adrf6780_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct adrf6780_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SCALE: + return adrf6780_spi_write(st, ADRF6780_REG_LINEARIZE, val); + case IIO_CHAN_INFO_PHASE: + switch (chan->channel2) { + case IIO_MOD_I: + return adrf6780_spi_update_bits(st, + ADRF6780_REG_LO_PATH, + ADRF6780_I_PATH_PHASE_ACCURACY_MSK, + FIELD_PREP(ADRF6780_I_PATH_PHASE_ACCURACY_MSK, val)); + case IIO_MOD_Q: + return adrf6780_spi_update_bits(st, + ADRF6780_REG_LO_PATH, + ADRF6780_Q_PATH_PHASE_ACCURACY_MSK, + FIELD_PREP(ADRF6780_Q_PATH_PHASE_ACCURACY_MSK, val)); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int adrf6780_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int write_val, + unsigned int *read_val) +{ + struct adrf6780_state *st = iio_priv(indio_dev); + + if (read_val) + return adrf6780_spi_read(st, reg, read_val); + else + return adrf6780_spi_write(st, reg, write_val); +} + +static const struct iio_info adrf6780_info = { + .read_raw = adrf6780_read_raw, + .write_raw = adrf6780_write_raw, + .debugfs_reg_access = &adrf6780_reg_access, +}; + +#define ADRF6780_CHAN_ADC(_channel) { \ + .type = IIO_ALTVOLTAGE, \ + .output = 0, \ + .indexed = 1, \ + .channel = _channel, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \ +} + +#define ADRF6780_CHAN_RDAC(_channel) { \ + .type = IIO_ALTVOLTAGE, \ + .output = 1, \ + .indexed = 1, \ + .channel = _channel, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE) \ +} + +#define ADRF6780_CHAN_IQ_PHASE(_channel, rf_comp) { \ + .type = IIO_ALTVOLTAGE, \ + .modified = 1, \ + .output = 1, \ + .indexed = 1, \ + .channel2 = IIO_MOD_##rf_comp, \ + .channel = _channel, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PHASE) \ +} + +static const struct iio_chan_spec adrf6780_channels[] = { + ADRF6780_CHAN_ADC(0), + ADRF6780_CHAN_RDAC(0), + ADRF6780_CHAN_IQ_PHASE(0, I), + ADRF6780_CHAN_IQ_PHASE(0, Q), +}; + +static int adrf6780_reset(struct adrf6780_state *st) +{ + int ret; + struct device *dev = &st->spi->dev; + + ret = __adrf6780_spi_update_bits(st, ADRF6780_REG_CONTROL, + ADRF6780_SOFT_RESET_MSK, + FIELD_PREP(ADRF6780_SOFT_RESET_MSK, 1)); + if (ret) + return dev_err_probe(dev, ret, + "ADRF6780 SPI software reset failed.\n"); + + ret = __adrf6780_spi_update_bits(st, ADRF6780_REG_CONTROL, + ADRF6780_SOFT_RESET_MSK, + FIELD_PREP(ADRF6780_SOFT_RESET_MSK, 0)); + if (ret) + return dev_err_probe(dev, ret, + "ADRF6780 SPI software reset disable failed.\n"); + + return 0; +} + +static int adrf6780_init(struct adrf6780_state *st) +{ + int ret; + unsigned int chip_id, enable_reg, enable_reg_msk; + struct device *dev = &st->spi->dev; + + /* Perform a software reset */ + ret = adrf6780_reset(st); + if (ret) + return ret; + + ret = __adrf6780_spi_read(st, ADRF6780_REG_CONTROL, &chip_id); + if (ret) + return ret; + + chip_id = FIELD_GET(ADRF6780_CHIP_ID_MSK, chip_id); + if (chip_id != ADRF6780_CHIP_ID) + return dev_err_probe(dev, -EINVAL, + "ADRF6780 Invalid Chip ID.\n"); + + enable_reg_msk = ADRF6780_VGA_BUFFER_EN_MSK | + ADRF6780_DETECTOR_EN_MSK | + ADRF6780_LO_BUFFER_EN_MSK | + ADRF6780_IF_MODE_EN_MSK | + ADRF6780_IQ_MODE_EN_MSK | + ADRF6780_LO_X2_EN_MSK | + ADRF6780_LO_PPF_EN_MSK | + ADRF6780_LO_EN_MSK | + ADRF6780_UC_BIAS_EN_MSK; + + enable_reg = FIELD_PREP(ADRF6780_VGA_BUFFER_EN_MSK, st->vga_buff_en) | + FIELD_PREP(ADRF6780_DETECTOR_EN_MSK, 1) | + FIELD_PREP(ADRF6780_LO_BUFFER_EN_MSK, st->lo_buff_en) | + FIELD_PREP(ADRF6780_IF_MODE_EN_MSK, st->if_mode_en) | + FIELD_PREP(ADRF6780_IQ_MODE_EN_MSK, st->iq_mode_en) | + FIELD_PREP(ADRF6780_LO_X2_EN_MSK, st->lo_x2_en) | + FIELD_PREP(ADRF6780_LO_PPF_EN_MSK, st->lo_ppf_en) | + FIELD_PREP(ADRF6780_LO_EN_MSK, st->lo_en) | + FIELD_PREP(ADRF6780_UC_BIAS_EN_MSK, st->uc_bias_en); + + ret = __adrf6780_spi_update_bits(st, ADRF6780_REG_ENABLE, + enable_reg_msk, enable_reg); + if (ret) + return ret; + + ret = __adrf6780_spi_update_bits(st, ADRF6780_REG_LO_PATH, + ADRF6780_LO_SIDEBAND_MSK, + FIELD_PREP(ADRF6780_LO_SIDEBAND_MSK, st->lo_sideband)); + if (ret) + return ret; + + return __adrf6780_spi_update_bits(st, ADRF6780_REG_ADC_CONTROL, + ADRF6780_VDET_OUTPUT_SELECT_MSK, + FIELD_PREP(ADRF6780_VDET_OUTPUT_SELECT_MSK, st->vdet_out_en)); +} + +static void adrf6780_properties_parse(struct adrf6780_state *st) +{ + struct device *dev = &st->spi->dev; + + st->vga_buff_en = device_property_read_bool(dev, "adi,vga-buff-en"); + st->lo_buff_en = device_property_read_bool(dev, "adi,lo-buff-en"); + st->if_mode_en = device_property_read_bool(dev, "adi,if-mode-en"); + st->iq_mode_en = device_property_read_bool(dev, "adi,iq-mode-en"); + st->lo_x2_en = device_property_read_bool(dev, "adi,lo-x2-en"); + st->lo_ppf_en = device_property_read_bool(dev, "adi,lo-ppf-en"); + st->lo_en = device_property_read_bool(dev, "adi,lo-en"); + st->uc_bias_en = device_property_read_bool(dev, "adi,uc-bias-en"); + st->lo_sideband = device_property_read_bool(dev, "adi,lo-sideband"); + st->vdet_out_en = device_property_read_bool(dev, "adi,vdet-out-en"); +} + +static void adrf6780_powerdown(void *data) +{ + /* Disable all components in the Enable Register */ + adrf6780_spi_write(data, ADRF6780_REG_ENABLE, 0x0); +} + +static int adrf6780_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct adrf6780_state *st; + struct device *dev = &spi->dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + indio_dev->info = &adrf6780_info; + indio_dev->name = "adrf6780"; + indio_dev->channels = adrf6780_channels; + indio_dev->num_channels = ARRAY_SIZE(adrf6780_channels); + + st->spi = spi; + + adrf6780_properties_parse(st); + + st->clkin = devm_clk_get_enabled(dev, "lo_in"); + if (IS_ERR(st->clkin)) + return dev_err_probe(dev, PTR_ERR(st->clkin), + "failed to get the LO input clock\n"); + + mutex_init(&st->lock); + + ret = adrf6780_init(st); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, adrf6780_powerdown, st); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id adrf6780_id[] = { + { .name = "adrf6780" }, + { } +}; +MODULE_DEVICE_TABLE(spi, adrf6780_id); + +static const struct of_device_id adrf6780_of_match[] = { + { .compatible = "adi,adrf6780" }, + { } +}; +MODULE_DEVICE_TABLE(of, adrf6780_of_match); + +static struct spi_driver adrf6780_driver = { + .driver = { + .name = "adrf6780", + .of_match_table = adrf6780_of_match, + }, + .probe = adrf6780_probe, + .id_table = adrf6780_id, +}; +module_spi_driver(adrf6780_driver); + +MODULE_AUTHOR("Antoniu Miclaus +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define ADIS16080_DIN_GYRO (0 << 10) /* Gyroscope output */ +#define ADIS16080_DIN_TEMP (1 << 10) /* Temperature output */ +#define ADIS16080_DIN_AIN1 (2 << 10) +#define ADIS16080_DIN_AIN2 (3 << 10) + +/* + * 1: Write contents on DIN to control register. + * 0: No changes to control register. + */ + +#define ADIS16080_DIN_WRITE (1 << 15) + +struct adis16080_chip_info { + int scale_val; + int scale_val2; +}; + +/** + * struct adis16080_state - device instance specific data + * @us: actual spi_device to write data + * @info: chip specific parameters + * @buf: transmit or receive buffer + * @lock: lock to protect buffer during reads + **/ +struct adis16080_state { + struct spi_device *us; + const struct adis16080_chip_info *info; + struct mutex lock; + + __be16 buf __aligned(IIO_DMA_MINALIGN); +}; + +static int adis16080_read_sample(struct iio_dev *indio_dev, + u16 addr, int *val) +{ + struct adis16080_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer t[] = { + { + .tx_buf = &st->buf, + .len = 2, + .cs_change = 1, + }, { + .rx_buf = &st->buf, + .len = 2, + }, + }; + + st->buf = cpu_to_be16(addr | ADIS16080_DIN_WRITE); + + ret = spi_sync_transfer(st->us, t, ARRAY_SIZE(t)); + if (ret == 0) + *val = sign_extend32(be16_to_cpu(st->buf), 11); + + return ret; +} + +static int adis16080_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct adis16080_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&st->lock); + ret = adis16080_read_sample(indio_dev, chan->address, val); + mutex_unlock(&st->lock); + return ret ? ret : IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = st->info->scale_val; + *val2 = st->info->scale_val2; + return IIO_VAL_FRACTIONAL; + case IIO_VOLTAGE: + /* VREF = 5V, 12 bits */ + *val = 5000; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_TEMP: + /* 85 C = 585, 25 C = 0 */ + *val = 85000 - 25000; + *val2 = 585; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_VOLTAGE: + /* 2.5 V = 0 */ + *val = 2048; + return IIO_VAL_INT; + case IIO_TEMP: + /* 85 C = 585, 25 C = 0 */ + *val = DIV_ROUND_CLOSEST(25 * 585, 85 - 25); + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + break; + } + + return -EINVAL; +} + +static const struct iio_chan_spec adis16080_channels[] = { + { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .address = ADIS16080_DIN_GYRO, + }, { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = ADIS16080_DIN_AIN1, + }, { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = ADIS16080_DIN_AIN2, + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = ADIS16080_DIN_TEMP, + } +}; + +static const struct iio_info adis16080_info = { + .read_raw = &adis16080_read_raw, +}; + +enum { + ID_ADIS16080, + ID_ADIS16100, +}; + +static const struct adis16080_chip_info adis16080_chip_info[] = { + [ID_ADIS16080] = { + /* 80 degree = 819, 819 rad = 46925 degree */ + .scale_val = 80, + .scale_val2 = 46925, + }, + [ID_ADIS16100] = { + /* 300 degree = 1230, 1230 rad = 70474 degree */ + .scale_val = 300, + .scale_val2 = 70474, + }, +}; + +static int adis16080_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct adis16080_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + st = iio_priv(indio_dev); + + mutex_init(&st->lock); + + /* Allocate the comms buffers */ + st->us = spi; + st->info = &adis16080_chip_info[id->driver_data]; + + indio_dev->name = spi->dev.driver->name; + indio_dev->channels = adis16080_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16080_channels); + indio_dev->info = &adis16080_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id adis16080_ids[] = { + { .name = "adis16080", .driver_data = ID_ADIS16080 }, + { .name = "adis16100", .driver_data = ID_ADIS16100 }, + { } +}; +MODULE_DEVICE_TABLE(spi, adis16080_ids); + +static struct spi_driver adis16080_driver = { + .driver = { + .name = "adis16080", + }, + .probe = adis16080_probe, + .id_table = adis16080_ids, +}; +module_spi_driver(adis16080_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADIS16080/100 Yaw Rate Gyroscope Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/gyro/adis16130.c b/drivers/iio/gyro/adis16130.c new file mode 100644 index 000000000..2535e3c94 --- /dev/null +++ b/drivers/iio/gyro/adis16130.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ADIS16130 Digital Output, High Precision Angular Rate Sensor driver + * + * Copyright 2010 Analog Devices Inc. + */ + +#include +#include +#include +#include + +#include + +#include + +#define ADIS16130_CON 0x0 +#define ADIS16130_CON_RD (1 << 6) +#define ADIS16130_IOP 0x1 + +/* 1 = data-ready signal low when unread data on all channels; */ +#define ADIS16130_IOP_ALL_RDY (1 << 3) +#define ADIS16130_IOP_SYNC (1 << 0) /* 1 = synchronization enabled */ +#define ADIS16130_RATEDATA 0x8 /* Gyroscope output, rate of rotation */ +#define ADIS16130_TEMPDATA 0xA /* Temperature output */ +#define ADIS16130_RATECS 0x28 /* Gyroscope channel setup */ +#define ADIS16130_RATECS_EN (1 << 3) /* 1 = channel enable; */ +#define ADIS16130_TEMPCS 0x2A /* Temperature channel setup */ +#define ADIS16130_TEMPCS_EN (1 << 3) +#define ADIS16130_RATECONV 0x30 +#define ADIS16130_TEMPCONV 0x32 +#define ADIS16130_MODE 0x38 +#define ADIS16130_MODE_24BIT (1 << 1) /* 1 = 24-bit resolution; */ + +/** + * struct adis16130_state - device instance specific data + * @us: actual spi_device to write data + * @buf_lock: mutex to protect tx and rx + * @buf: unified tx/rx buffer + **/ +struct adis16130_state { + struct spi_device *us; + struct mutex buf_lock; + u8 buf[4] __aligned(IIO_DMA_MINALIGN); +}; + +static int adis16130_spi_read(struct iio_dev *indio_dev, u8 reg_addr, u32 *val) +{ + int ret; + struct adis16130_state *st = iio_priv(indio_dev); + struct spi_transfer xfer = { + .tx_buf = st->buf, + .rx_buf = st->buf, + .len = 4, + }; + + mutex_lock(&st->buf_lock); + + st->buf[0] = ADIS16130_CON_RD | reg_addr; + st->buf[1] = st->buf[2] = st->buf[3] = 0; + + ret = spi_sync_transfer(st->us, &xfer, 1); + if (ret == 0) + *val = get_unaligned_be24(&st->buf[1]); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int adis16130_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + int ret; + u32 temp; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* Take the iio_dev status lock */ + ret = adis16130_spi_read(indio_dev, chan->address, &temp); + if (ret) + return ret; + *val = temp; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + /* 0 degree = 838860, 250 degree = 14260608 */ + *val = 250; + *val2 = 336440817; /* RAD_TO_DEGREE(14260608 - 8388608) */ + return IIO_VAL_FRACTIONAL; + case IIO_TEMP: + /* 0C = 8036283, 105C = 9516048 */ + *val = 105000; + *val2 = 9516048 - 8036283; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = -8388608; + return IIO_VAL_INT; + case IIO_TEMP: + *val = -8036283; + return IIO_VAL_INT; + default: + return -EINVAL; + } + } + + return -EINVAL; +} + +static const struct iio_chan_spec adis16130_channels[] = { + { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = ADIS16130_RATEDATA, + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = ADIS16130_TEMPDATA, + } +}; + +static const struct iio_info adis16130_info = { + .read_raw = &adis16130_read_raw, +}; + +static int adis16130_probe(struct spi_device *spi) +{ + struct adis16130_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + st->us = spi; + mutex_init(&st->buf_lock); + indio_dev->name = spi->dev.driver->name; + indio_dev->channels = adis16130_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16130_channels); + indio_dev->info = &adis16130_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static struct spi_driver adis16130_driver = { + .driver = { + .name = "adis16130", + }, + .probe = adis16130_probe, +}; +module_spi_driver(adis16130_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADIS16130 High Precision Angular Rate"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:adis16130"); diff --git a/drivers/iio/gyro/adis16136.c b/drivers/iio/gyro/adis16136.c new file mode 100644 index 000000000..ff335f81e --- /dev/null +++ b/drivers/iio/gyro/adis16136.c @@ -0,0 +1,586 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADIS16133/ADIS16135/ADIS16136 gyroscope driver + * + * Copyright 2012 Analog Devices Inc. + * Author: Lars-Peter Clausen + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#define ADIS16136_REG_FLASH_CNT 0x00 +#define ADIS16136_REG_TEMP_OUT 0x02 +#define ADIS16136_REG_GYRO_OUT2 0x04 +#define ADIS16136_REG_GYRO_OUT 0x06 +#define ADIS16136_REG_GYRO_OFF2 0x08 +#define ADIS16136_REG_GYRO_OFF 0x0A +#define ADIS16136_REG_ALM_MAG1 0x10 +#define ADIS16136_REG_ALM_MAG2 0x12 +#define ADIS16136_REG_ALM_SAMPL1 0x14 +#define ADIS16136_REG_ALM_SAMPL2 0x16 +#define ADIS16136_REG_ALM_CTRL 0x18 +#define ADIS16136_REG_GPIO_CTRL 0x1A +#define ADIS16136_REG_MSC_CTRL 0x1C +#define ADIS16136_REG_SMPL_PRD 0x1E +#define ADIS16136_REG_AVG_CNT 0x20 +#define ADIS16136_REG_DEC_RATE 0x22 +#define ADIS16136_REG_SLP_CTRL 0x24 +#define ADIS16136_REG_DIAG_STAT 0x26 +#define ADIS16136_REG_GLOB_CMD 0x28 +#define ADIS16136_REG_LOT1 0x32 +#define ADIS16136_REG_LOT2 0x34 +#define ADIS16136_REG_LOT3 0x36 +#define ADIS16136_REG_PROD_ID 0x38 +#define ADIS16136_REG_SERIAL_NUM 0x3A + +#define ADIS16136_DIAG_STAT_FLASH_UPDATE_FAIL 2 +#define ADIS16136_DIAG_STAT_SPI_FAIL 3 +#define ADIS16136_DIAG_STAT_SELF_TEST_FAIL 5 +#define ADIS16136_DIAG_STAT_FLASH_CHKSUM_FAIL 6 + +#define ADIS16136_MSC_CTRL_MEMORY_TEST BIT(11) +#define ADIS16136_MSC_CTRL_SELF_TEST BIT(10) + +struct adis16136_chip_info { + unsigned int precision; + unsigned int fullscale; + const struct adis_data adis_data; +}; + +struct adis16136 { + const struct adis16136_chip_info *chip_info; + + struct adis adis; +}; + +#ifdef CONFIG_DEBUG_FS + +static ssize_t adis16136_show_serial(struct file *file, + char __user *userbuf, size_t count, loff_t *ppos) +{ + struct adis16136 *adis16136 = file->private_data; + uint16_t lot1, lot2, lot3, serial; + char buf[20]; + size_t len; + int ret; + + ret = adis_read_reg_16(&adis16136->adis, ADIS16136_REG_SERIAL_NUM, + &serial); + if (ret) + return ret; + + ret = adis_read_reg_16(&adis16136->adis, ADIS16136_REG_LOT1, &lot1); + if (ret) + return ret; + + ret = adis_read_reg_16(&adis16136->adis, ADIS16136_REG_LOT2, &lot2); + if (ret) + return ret; + + ret = adis_read_reg_16(&adis16136->adis, ADIS16136_REG_LOT3, &lot3); + if (ret) + return ret; + + len = snprintf(buf, sizeof(buf), "%.4x%.4x%.4x-%.4x\n", lot1, lot2, + lot3, serial); + + return simple_read_from_buffer(userbuf, count, ppos, buf, len); +} + +static const struct file_operations adis16136_serial_fops = { + .open = simple_open, + .read = adis16136_show_serial, + .llseek = default_llseek, + .owner = THIS_MODULE, +}; + +static int adis16136_show_product_id(void *arg, u64 *val) +{ + struct adis16136 *adis16136 = arg; + u16 prod_id; + int ret; + + ret = adis_read_reg_16(&adis16136->adis, ADIS16136_REG_PROD_ID, + &prod_id); + if (ret) + return ret; + + *val = prod_id; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(adis16136_product_id_fops, + adis16136_show_product_id, NULL, "%llu\n"); + +static int adis16136_show_flash_count(void *arg, u64 *val) +{ + struct adis16136 *adis16136 = arg; + uint16_t flash_count; + int ret; + + ret = adis_read_reg_16(&adis16136->adis, ADIS16136_REG_FLASH_CNT, + &flash_count); + if (ret) + return ret; + + *val = flash_count; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(adis16136_flash_count_fops, + adis16136_show_flash_count, NULL, "%lld\n"); + +static int adis16136_debugfs_init(struct iio_dev *indio_dev) +{ + struct adis16136 *adis16136 = iio_priv(indio_dev); + struct dentry *d = iio_get_debugfs_dentry(indio_dev); + + debugfs_create_file_unsafe("serial_number", 0400, + d, adis16136, &adis16136_serial_fops); + debugfs_create_file_unsafe("product_id", 0400, + d, adis16136, &adis16136_product_id_fops); + debugfs_create_file_unsafe("flash_count", 0400, + d, adis16136, &adis16136_flash_count_fops); + + return 0; +} + +#else + +static int adis16136_debugfs_init(struct iio_dev *indio_dev) +{ + return 0; +} + +#endif + +static int adis16136_set_freq(struct adis16136 *adis16136, unsigned int freq) +{ + unsigned int t; + + t = 32768 / freq; + if (t < 0xf) + t = 0xf; + else if (t > 0xffff) + t = 0xffff; + else + t--; + + return adis_write_reg_16(&adis16136->adis, ADIS16136_REG_SMPL_PRD, t); +} + +static int __adis16136_get_freq(struct adis16136 *adis16136, unsigned int *freq) +{ + uint16_t t; + int ret; + + ret = __adis_read_reg_16(&adis16136->adis, ADIS16136_REG_SMPL_PRD, &t); + if (ret) + return ret; + + *freq = 32768 / (t + 1); + + return 0; +} + +static ssize_t adis16136_write_frequency(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct adis16136 *adis16136 = iio_priv(indio_dev); + unsigned int val; + int ret; + + ret = kstrtouint(buf, 10, &val); + if (ret) + return ret; + + if (val == 0) + return -EINVAL; + + ret = adis16136_set_freq(adis16136, val); + + return ret ? ret : len; +} + +static ssize_t adis16136_read_frequency(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct adis16136 *adis16136 = iio_priv(indio_dev); + unsigned int freq; + int ret; + + adis_dev_auto_lock(&adis16136->adis); + ret = __adis16136_get_freq(adis16136, &freq); + if (ret) + return ret; + + return sysfs_emit(buf, "%d\n", freq); +} + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + adis16136_read_frequency, + adis16136_write_frequency); + +static const unsigned adis16136_3db_divisors[] = { + [0] = 2, /* Special case */ + [1] = 6, + [2] = 12, + [3] = 25, + [4] = 50, + [5] = 100, + [6] = 200, + [7] = 200, /* Not a valid setting */ +}; + +static int adis16136_set_filter(struct iio_dev *indio_dev, int val) +{ + struct adis16136 *adis16136 = iio_priv(indio_dev); + unsigned int freq; + int i, ret; + + adis_dev_auto_lock(&adis16136->adis); + ret = __adis16136_get_freq(adis16136, &freq); + if (ret) + return ret; + + for (i = ARRAY_SIZE(adis16136_3db_divisors) - 1; i >= 1; i--) { + if (freq / adis16136_3db_divisors[i] >= val) + break; + } + + return __adis_write_reg_16(&adis16136->adis, ADIS16136_REG_AVG_CNT, i); +} + +static int adis16136_get_filter(struct iio_dev *indio_dev, int *val) +{ + struct adis16136 *adis16136 = iio_priv(indio_dev); + unsigned int freq; + uint16_t val16; + int ret; + + adis_dev_auto_lock(&adis16136->adis); + + ret = __adis_read_reg_16(&adis16136->adis, ADIS16136_REG_AVG_CNT, + &val16); + if (ret) + return ret; + + ret = __adis16136_get_freq(adis16136, &freq); + if (ret) + return ret; + + *val = freq / adis16136_3db_divisors[val16 & 0x07]; + + return IIO_VAL_INT; +} + +static int adis16136_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, int *val2, long info) +{ + struct adis16136 *adis16136 = iio_priv(indio_dev); + uint32_t val32; + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + return adis_single_conversion(indio_dev, chan, 0, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = adis16136->chip_info->precision; + *val2 = (adis16136->chip_info->fullscale << 16); + return IIO_VAL_FRACTIONAL; + case IIO_TEMP: + *val = 10; + *val2 = 697000; /* 0.010697 degree Celsius */ + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + ret = adis_read_reg_32(&adis16136->adis, + ADIS16136_REG_GYRO_OFF2, &val32); + if (ret) + return ret; + + *val = sign_extend32(val32, 31); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return adis16136_get_filter(indio_dev, val); + default: + return -EINVAL; + } +} + +static int adis16136_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int val, int val2, long info) +{ + struct adis16136 *adis16136 = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_CALIBBIAS: + return adis_write_reg_32(&adis16136->adis, + ADIS16136_REG_GYRO_OFF2, val); + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return adis16136_set_filter(indio_dev, val); + default: + break; + } + + return -EINVAL; +} + +enum { + ADIS16136_SCAN_GYRO, + ADIS16136_SCAN_TEMP, +}; + +static const struct iio_chan_spec adis16136_channels[] = { + { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + + .address = ADIS16136_REG_GYRO_OUT2, + .scan_index = ADIS16136_SCAN_GYRO, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_BE, + }, + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .address = ADIS16136_REG_TEMP_OUT, + .scan_index = ADIS16136_SCAN_TEMP, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static struct attribute *adis16136_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + NULL +}; + +static const struct attribute_group adis16136_attribute_group = { + .attrs = adis16136_attributes, +}; + +static const struct iio_info adis16136_info = { + .attrs = &adis16136_attribute_group, + .read_raw = &adis16136_read_raw, + .write_raw = &adis16136_write_raw, + .update_scan_mode = adis_update_scan_mode, + .debugfs_reg_access = adis_debugfs_reg_access, +}; + +static int adis16136_stop_device(struct iio_dev *indio_dev) +{ + struct adis16136 *adis16136 = iio_priv(indio_dev); + int ret; + + ret = adis_write_reg_16(&adis16136->adis, ADIS16136_REG_SLP_CTRL, 0xff); + if (ret) + dev_err(&indio_dev->dev, + "Could not power down device: %d\n", ret); + + return ret; +} + +static int adis16136_initial_setup(struct iio_dev *indio_dev) +{ + struct adis16136 *adis16136 = iio_priv(indio_dev); + unsigned int device_id; + uint16_t prod_id; + int ret; + + ret = __adis_initial_startup(&adis16136->adis); + if (ret) + return ret; + + ret = adis_read_reg_16(&adis16136->adis, ADIS16136_REG_PROD_ID, + &prod_id); + if (ret) + return ret; + + ret = sscanf(indio_dev->name, "adis%u\n", &device_id); + if (ret != 1) + return -EINVAL; + + if (prod_id != device_id) + dev_warn(&indio_dev->dev, "Device ID(%u) and product ID(%u) do not match.", + device_id, prod_id); + + return 0; +} + +static const char * const adis16136_status_error_msgs[] = { + [ADIS16136_DIAG_STAT_FLASH_UPDATE_FAIL] = "Flash update failed", + [ADIS16136_DIAG_STAT_SPI_FAIL] = "SPI failure", + [ADIS16136_DIAG_STAT_SELF_TEST_FAIL] = "Self test error", + [ADIS16136_DIAG_STAT_FLASH_CHKSUM_FAIL] = "Flash checksum error", +}; + +#define ADIS16136_DATA(_timeouts) \ +{ \ + .diag_stat_reg = ADIS16136_REG_DIAG_STAT, \ + .glob_cmd_reg = ADIS16136_REG_GLOB_CMD, \ + .msc_ctrl_reg = ADIS16136_REG_MSC_CTRL, \ + .self_test_reg = ADIS16136_REG_MSC_CTRL, \ + .self_test_mask = ADIS16136_MSC_CTRL_SELF_TEST, \ + .read_delay = 10, \ + .write_delay = 10, \ + .status_error_msgs = adis16136_status_error_msgs, \ + .status_error_mask = BIT(ADIS16136_DIAG_STAT_FLASH_UPDATE_FAIL) | \ + BIT(ADIS16136_DIAG_STAT_SPI_FAIL) | \ + BIT(ADIS16136_DIAG_STAT_SELF_TEST_FAIL) | \ + BIT(ADIS16136_DIAG_STAT_FLASH_CHKSUM_FAIL), \ + .timeouts = (_timeouts), \ +} + +enum adis16136_id { + ID_ADIS16133, + ID_ADIS16135, + ID_ADIS16136, + ID_ADIS16137, +}; + +static const struct adis_timeout adis16133_timeouts = { + .reset_ms = 75, + .sw_reset_ms = 75, + .self_test_ms = 50, +}; + +static const struct adis_timeout adis16136_timeouts = { + .reset_ms = 128, + .sw_reset_ms = 75, + .self_test_ms = 245, +}; + +static const struct adis16136_chip_info adis16136_chip_info[] = { + [ID_ADIS16133] = { + .precision = IIO_DEGREE_TO_RAD(1200), + .fullscale = 24000, + .adis_data = ADIS16136_DATA(&adis16133_timeouts), + }, + [ID_ADIS16135] = { + .precision = IIO_DEGREE_TO_RAD(300), + .fullscale = 24000, + .adis_data = ADIS16136_DATA(&adis16133_timeouts), + }, + [ID_ADIS16136] = { + .precision = IIO_DEGREE_TO_RAD(450), + .fullscale = 24623, + .adis_data = ADIS16136_DATA(&adis16136_timeouts), + }, + [ID_ADIS16137] = { + .precision = IIO_DEGREE_TO_RAD(1000), + .fullscale = 24609, + .adis_data = ADIS16136_DATA(&adis16136_timeouts), + }, +}; + +static void adis16136_stop(void *data) +{ + adis16136_stop_device(data); +} + +static int adis16136_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct adis16136 *adis16136; + struct iio_dev *indio_dev; + const struct adis_data *adis16136_data; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adis16136)); + if (indio_dev == NULL) + return -ENOMEM; + + spi_set_drvdata(spi, indio_dev); + + adis16136 = iio_priv(indio_dev); + + adis16136->chip_info = &adis16136_chip_info[id->driver_data]; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->channels = adis16136_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16136_channels); + indio_dev->info = &adis16136_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + adis16136_data = &adis16136->chip_info->adis_data; + + ret = adis_init(&adis16136->adis, indio_dev, spi, adis16136_data); + if (ret) + return ret; + + ret = devm_adis_setup_buffer_and_trigger(&adis16136->adis, indio_dev, NULL); + if (ret) + return ret; + + ret = adis16136_initial_setup(indio_dev); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&spi->dev, adis16136_stop, indio_dev); + if (ret) + return ret; + + ret = devm_iio_device_register(&spi->dev, indio_dev); + if (ret) + return ret; + + adis16136_debugfs_init(indio_dev); + + return 0; +} + +static const struct spi_device_id adis16136_ids[] = { + { .name = "adis16133", .driver_data = ID_ADIS16133 }, + { .name = "adis16135", .driver_data = ID_ADIS16135 }, + { .name = "adis16136", .driver_data = ID_ADIS16136 }, + { .name = "adis16137", .driver_data = ID_ADIS16137 }, + { } +}; +MODULE_DEVICE_TABLE(spi, adis16136_ids); + +static struct spi_driver adis16136_driver = { + .driver = { + .name = "adis16136", + }, + .id_table = adis16136_ids, + .probe = adis16136_probe, +}; +module_spi_driver(adis16136_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Analog Devices ADIS16133/ADIS16135/ADIS16136 gyroscope driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ADISLIB"); diff --git a/drivers/iio/gyro/adis16260.c b/drivers/iio/gyro/adis16260.c new file mode 100644 index 000000000..a79a8edf4 --- /dev/null +++ b/drivers/iio/gyro/adis16260.c @@ -0,0 +1,436 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ADIS16260/ADIS16265 Programmable Digital Gyroscope Sensor Driver + * + * Copyright 2010 Analog Devices Inc. + */ + +#include +#include +#include +#include + +#include +#include + +#define ADIS16260_STARTUP_DELAY 220 /* ms */ + +#define ADIS16260_FLASH_CNT 0x00 /* Flash memory write count */ +#define ADIS16260_SUPPLY_OUT 0x02 /* Power supply measurement */ +#define ADIS16260_GYRO_OUT 0x04 /* X-axis gyroscope output */ +#define ADIS16260_AUX_ADC 0x0A /* analog input channel measurement */ +#define ADIS16260_TEMP_OUT 0x0C /* internal temperature measurement */ +#define ADIS16260_ANGL_OUT 0x0E /* angle displacement */ +#define ADIS16260_GYRO_OFF 0x14 /* Calibration, offset/bias adjustment */ +#define ADIS16260_GYRO_SCALE 0x16 /* Calibration, scale adjustment */ +#define ADIS16260_ALM_MAG1 0x20 /* Alarm 1 magnitude/polarity setting */ +#define ADIS16260_ALM_MAG2 0x22 /* Alarm 2 magnitude/polarity setting */ +#define ADIS16260_ALM_SMPL1 0x24 /* Alarm 1 dynamic rate of change setting */ +#define ADIS16260_ALM_SMPL2 0x26 /* Alarm 2 dynamic rate of change setting */ +#define ADIS16260_ALM_CTRL 0x28 /* Alarm control */ +#define ADIS16260_AUX_DAC 0x30 /* Auxiliary DAC data */ +#define ADIS16260_GPIO_CTRL 0x32 /* Control, digital I/O line */ +#define ADIS16260_MSC_CTRL 0x34 /* Control, data ready, self-test settings */ +#define ADIS16260_SMPL_PRD 0x36 /* Control, internal sample rate */ +#define ADIS16260_SENS_AVG 0x38 /* Control, dynamic range, filtering */ +#define ADIS16260_SLP_CNT 0x3A /* Control, sleep mode initiation */ +#define ADIS16260_DIAG_STAT 0x3C /* Diagnostic, error flags */ +#define ADIS16260_GLOB_CMD 0x3E /* Control, global commands */ +#define ADIS16260_LOT_ID1 0x52 /* Lot Identification Code 1 */ +#define ADIS16260_LOT_ID2 0x54 /* Lot Identification Code 2 */ +#define ADIS16260_PROD_ID 0x56 /* Product identifier; + * convert to decimal = 16,265/16,260 */ +#define ADIS16260_SERIAL_NUM 0x58 /* Serial number */ + +#define ADIS16260_ERROR_ACTIVE (1<<14) +#define ADIS16260_NEW_DATA (1<<15) + +/* MSC_CTRL */ +#define ADIS16260_MSC_CTRL_MEM_TEST (1<<11) +/* Internal self-test enable */ +#define ADIS16260_MSC_CTRL_INT_SELF_TEST (1<<10) +#define ADIS16260_MSC_CTRL_NEG_SELF_TEST (1<<9) +#define ADIS16260_MSC_CTRL_POS_SELF_TEST (1<<8) +#define ADIS16260_MSC_CTRL_DATA_RDY_EN (1<<2) +#define ADIS16260_MSC_CTRL_DATA_RDY_POL_HIGH (1<<1) +#define ADIS16260_MSC_CTRL_DATA_RDY_DIO2 (1<<0) + +/* SMPL_PRD */ +/* Time base (tB): 0 = 1.953 ms, 1 = 60.54 ms */ +#define ADIS16260_SMPL_PRD_TIME_BASE (1<<7) +#define ADIS16260_SMPL_PRD_DIV_MASK 0x7F + +/* SLP_CNT */ +#define ADIS16260_SLP_CNT_POWER_OFF 0x80 + +/* DIAG_STAT */ +#define ADIS16260_DIAG_STAT_ALARM2 (1<<9) +#define ADIS16260_DIAG_STAT_ALARM1 (1<<8) +#define ADIS16260_DIAG_STAT_FLASH_CHK_BIT 6 +#define ADIS16260_DIAG_STAT_SELF_TEST_BIT 5 +#define ADIS16260_DIAG_STAT_OVERFLOW_BIT 4 +#define ADIS16260_DIAG_STAT_SPI_FAIL_BIT 3 +#define ADIS16260_DIAG_STAT_FLASH_UPT_BIT 2 +#define ADIS16260_DIAG_STAT_POWER_HIGH_BIT 1 +#define ADIS16260_DIAG_STAT_POWER_LOW_BIT 0 + +/* GLOB_CMD */ +#define ADIS16260_GLOB_CMD_SW_RESET (1<<7) +#define ADIS16260_GLOB_CMD_FLASH_UPD (1<<3) +#define ADIS16260_GLOB_CMD_DAC_LATCH (1<<2) +#define ADIS16260_GLOB_CMD_FAC_CALIB (1<<1) +#define ADIS16260_GLOB_CMD_AUTO_NULL (1<<0) + +#define ADIS16260_SPI_SLOW (u32)(300 * 1000) +#define ADIS16260_SPI_BURST (u32)(1000 * 1000) +#define ADIS16260_SPI_FAST (u32)(2000 * 1000) + +/* At the moment triggers are only used for ring buffer + * filling. This may change! + */ + +#define ADIS16260_SCAN_GYRO 0 +#define ADIS16260_SCAN_SUPPLY 1 +#define ADIS16260_SCAN_AUX_ADC 2 +#define ADIS16260_SCAN_TEMP 3 +#define ADIS16260_SCAN_ANGL 4 + +struct adis16260_chip_info { + unsigned int gyro_max_val; + unsigned int gyro_max_scale; + const struct iio_chan_spec *channels; + unsigned int num_channels; +}; + +struct adis16260 { + const struct adis16260_chip_info *info; + + struct adis adis; +}; + +enum adis16260_type { + ADIS16251, + ADIS16260, + ADIS16266, +}; + +static const struct iio_chan_spec adis16260_channels[] = { + ADIS_GYRO_CHAN(X, ADIS16260_GYRO_OUT, ADIS16260_SCAN_GYRO, + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + BIT(IIO_CHAN_INFO_SAMP_FREQ), 14), + ADIS_INCLI_CHAN(X, ADIS16260_ANGL_OUT, ADIS16260_SCAN_ANGL, 0, + BIT(IIO_CHAN_INFO_SAMP_FREQ), 14), + ADIS_TEMP_CHAN(ADIS16260_TEMP_OUT, ADIS16260_SCAN_TEMP, + BIT(IIO_CHAN_INFO_SAMP_FREQ), 12), + ADIS_SUPPLY_CHAN(ADIS16260_SUPPLY_OUT, ADIS16260_SCAN_SUPPLY, + BIT(IIO_CHAN_INFO_SAMP_FREQ), 12), + ADIS_AUX_ADC_CHAN(ADIS16260_AUX_ADC, ADIS16260_SCAN_AUX_ADC, + BIT(IIO_CHAN_INFO_SAMP_FREQ), 12), + IIO_CHAN_SOFT_TIMESTAMP(5), +}; + +static const struct iio_chan_spec adis16266_channels[] = { + ADIS_GYRO_CHAN(X, ADIS16260_GYRO_OUT, ADIS16260_SCAN_GYRO, + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + BIT(IIO_CHAN_INFO_SAMP_FREQ), 14), + ADIS_TEMP_CHAN(ADIS16260_TEMP_OUT, ADIS16260_SCAN_TEMP, + BIT(IIO_CHAN_INFO_SAMP_FREQ), 12), + ADIS_SUPPLY_CHAN(ADIS16260_SUPPLY_OUT, ADIS16260_SCAN_SUPPLY, + BIT(IIO_CHAN_INFO_SAMP_FREQ), 12), + ADIS_AUX_ADC_CHAN(ADIS16260_AUX_ADC, ADIS16260_SCAN_AUX_ADC, + BIT(IIO_CHAN_INFO_SAMP_FREQ), 12), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const struct adis16260_chip_info adis16260_chip_info_table[] = { + [ADIS16251] = { + .gyro_max_scale = 80, + .gyro_max_val = IIO_RAD_TO_DEGREE(4368), + .channels = adis16260_channels, + .num_channels = ARRAY_SIZE(adis16260_channels), + }, + [ADIS16260] = { + .gyro_max_scale = 320, + .gyro_max_val = IIO_RAD_TO_DEGREE(4368), + .channels = adis16260_channels, + .num_channels = ARRAY_SIZE(adis16260_channels), + }, + [ADIS16266] = { + .gyro_max_scale = 14000, + .gyro_max_val = IIO_RAD_TO_DEGREE(3357), + .channels = adis16266_channels, + .num_channels = ARRAY_SIZE(adis16266_channels), + }, +}; + +/* Power down the device */ +static int adis16260_stop_device(struct iio_dev *indio_dev) +{ + struct adis16260 *adis16260 = iio_priv(indio_dev); + int ret; + u16 val = ADIS16260_SLP_CNT_POWER_OFF; + + ret = adis_write_reg_16(&adis16260->adis, ADIS16260_SLP_CNT, val); + if (ret) + dev_err(&indio_dev->dev, "problem with turning device off: SLP_CNT"); + + return ret; +} + +static const u8 adis16260_addresses[][2] = { + [ADIS16260_SCAN_GYRO] = { ADIS16260_GYRO_OFF, ADIS16260_GYRO_SCALE }, +}; + +static int adis16260_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct adis16260 *adis16260 = iio_priv(indio_dev); + const struct adis16260_chip_info *info = adis16260->info; + struct adis *adis = &adis16260->adis; + int ret; + u8 addr; + s16 val16; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return adis_single_conversion(indio_dev, chan, + ADIS16260_ERROR_ACTIVE, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = info->gyro_max_scale; + *val2 = info->gyro_max_val; + return IIO_VAL_FRACTIONAL; + case IIO_INCLI: + *val = 0; + *val2 = IIO_DEGREE_TO_RAD(36630); + return IIO_VAL_INT_PLUS_MICRO; + case IIO_VOLTAGE: + if (chan->channel == 0) { + *val = 1; + *val2 = 831500; /* 1.8315 mV */ + } else { + *val = 0; + *val2 = 610500; /* 610.5 uV */ + } + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = 145; + *val2 = 300000; /* 0.1453 C */ + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + *val = 250000 / 1453; /* 25 C = 0x00 */ + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + addr = adis16260_addresses[chan->scan_index][0]; + ret = adis_read_reg_16(adis, addr, &val16); + if (ret) + return ret; + + *val = sign_extend32(val16, 11); + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + addr = adis16260_addresses[chan->scan_index][1]; + ret = adis_read_reg_16(adis, addr, &val16); + if (ret) + return ret; + + *val = val16; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = adis_read_reg_16(adis, ADIS16260_SMPL_PRD, &val16); + if (ret) + return ret; + + if (spi_get_device_id(adis->spi)->driver_data) + /* If an adis16251 */ + *val = (val16 & ADIS16260_SMPL_PRD_TIME_BASE) ? + 8 : 256; + else + *val = (val16 & ADIS16260_SMPL_PRD_TIME_BASE) ? + 66 : 2048; + *val /= (val16 & ADIS16260_SMPL_PRD_DIV_MASK) + 1; + return IIO_VAL_INT; + } + return -EINVAL; +} + +static int adis16260_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct adis16260 *adis16260 = iio_priv(indio_dev); + struct adis *adis = &adis16260->adis; + u8 addr; + u8 t; + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + if (val < -2048 || val >= 2048) + return -EINVAL; + + addr = adis16260_addresses[chan->scan_index][0]; + return adis_write_reg_16(adis, addr, val); + case IIO_CHAN_INFO_CALIBSCALE: + if (val < 0 || val >= 4096) + return -EINVAL; + + addr = adis16260_addresses[chan->scan_index][1]; + return adis_write_reg_16(adis, addr, val); + case IIO_CHAN_INFO_SAMP_FREQ: + if (val <= 0) + return -EINVAL; + + if (spi_get_device_id(adis->spi)->driver_data) + t = 256 / val; + else + t = 2048 / val; + + if (t > ADIS16260_SMPL_PRD_DIV_MASK) + t = ADIS16260_SMPL_PRD_DIV_MASK; + else if (t > 0) + t--; + adis_dev_auto_scoped_lock(adis) { + if (t >= 0x0A) + adis->spi->max_speed_hz = ADIS16260_SPI_SLOW; + else + adis->spi->max_speed_hz = ADIS16260_SPI_FAST; + return __adis_write_reg_8(adis, ADIS16260_SMPL_PRD, t); + } + unreachable(); + } + return -EINVAL; +} + +static const struct iio_info adis16260_info = { + .read_raw = &adis16260_read_raw, + .write_raw = &adis16260_write_raw, + .update_scan_mode = adis_update_scan_mode, +}; + +static const char * const adis1620_status_error_msgs[] = { + [ADIS16260_DIAG_STAT_FLASH_CHK_BIT] = "Flash checksum error", + [ADIS16260_DIAG_STAT_SELF_TEST_BIT] = "Self test error", + [ADIS16260_DIAG_STAT_OVERFLOW_BIT] = "Sensor overrange", + [ADIS16260_DIAG_STAT_SPI_FAIL_BIT] = "SPI failure", + [ADIS16260_DIAG_STAT_FLASH_UPT_BIT] = "Flash update failed", + [ADIS16260_DIAG_STAT_POWER_HIGH_BIT] = "Power supply above 5.25", + [ADIS16260_DIAG_STAT_POWER_LOW_BIT] = "Power supply below 4.75", +}; + +static const struct adis_timeout adis16260_timeouts = { + .reset_ms = ADIS16260_STARTUP_DELAY, + .sw_reset_ms = ADIS16260_STARTUP_DELAY, + .self_test_ms = ADIS16260_STARTUP_DELAY, +}; + +static const struct adis_data adis16260_data = { + .write_delay = 30, + .read_delay = 30, + .msc_ctrl_reg = ADIS16260_MSC_CTRL, + .glob_cmd_reg = ADIS16260_GLOB_CMD, + .diag_stat_reg = ADIS16260_DIAG_STAT, + + .self_test_mask = ADIS16260_MSC_CTRL_MEM_TEST, + .self_test_reg = ADIS16260_MSC_CTRL, + .timeouts = &adis16260_timeouts, + + .status_error_msgs = adis1620_status_error_msgs, + .status_error_mask = BIT(ADIS16260_DIAG_STAT_FLASH_CHK_BIT) | + BIT(ADIS16260_DIAG_STAT_SELF_TEST_BIT) | + BIT(ADIS16260_DIAG_STAT_OVERFLOW_BIT) | + BIT(ADIS16260_DIAG_STAT_SPI_FAIL_BIT) | + BIT(ADIS16260_DIAG_STAT_FLASH_UPT_BIT) | + BIT(ADIS16260_DIAG_STAT_POWER_HIGH_BIT) | + BIT(ADIS16260_DIAG_STAT_POWER_LOW_BIT), +}; + +static void adis16260_stop(void *data) +{ + adis16260_stop_device(data); +} + +static int adis16260_probe(struct spi_device *spi) +{ + const struct spi_device_id *id; + struct adis16260 *adis16260; + struct iio_dev *indio_dev; + int ret; + + id = spi_get_device_id(spi); + if (!id) + return -ENODEV; + + /* setup the industrialio driver allocated elements */ + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adis16260)); + if (!indio_dev) + return -ENOMEM; + adis16260 = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + adis16260->info = &adis16260_chip_info_table[id->driver_data]; + + indio_dev->name = id->name; + indio_dev->info = &adis16260_info; + indio_dev->channels = adis16260->info->channels; + indio_dev->num_channels = adis16260->info->num_channels; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = adis_init(&adis16260->adis, indio_dev, spi, &adis16260_data); + if (ret) + return ret; + + ret = devm_adis_setup_buffer_and_trigger(&adis16260->adis, indio_dev, NULL); + if (ret) + return ret; + + /* Get the device into a sane initial state */ + ret = __adis_initial_startup(&adis16260->adis); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&spi->dev, adis16260_stop, indio_dev); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +/* + * These parts do not need to be differentiated until someone adds + * support for the on chip filtering. + */ +static const struct spi_device_id adis16260_id[] = { + { .name = "adis16260", .driver_data = ADIS16260 }, + { .name = "adis16265", .driver_data = ADIS16260 }, + { .name = "adis16266", .driver_data = ADIS16266 }, + { .name = "adis16250", .driver_data = ADIS16260 }, + { .name = "adis16255", .driver_data = ADIS16260 }, + { .name = "adis16251", .driver_data = ADIS16251 }, + { } +}; +MODULE_DEVICE_TABLE(spi, adis16260_id); + +static struct spi_driver adis16260_driver = { + .driver = { + .name = "adis16260", + }, + .probe = adis16260_probe, + .id_table = adis16260_id, +}; +module_spi_driver(adis16260_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADIS16260/5 Digital Gyroscope Sensor"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ADISLIB"); diff --git a/drivers/iio/gyro/adxrs290.c b/drivers/iio/gyro/adxrs290.c new file mode 100644 index 000000000..563de2724 --- /dev/null +++ b/drivers/iio/gyro/adxrs290.c @@ -0,0 +1,692 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ADXRS290 SPI Gyroscope Driver + * + * Copyright (C) 2020 Nishant Malpani + * Copyright (C) 2020 Analog Devices, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define ADXRS290_ADI_ID 0xAD +#define ADXRS290_MEMS_ID 0x1D +#define ADXRS290_DEV_ID 0x92 + +#define ADXRS290_REG_ADI_ID 0x00 +#define ADXRS290_REG_MEMS_ID 0x01 +#define ADXRS290_REG_DEV_ID 0x02 +#define ADXRS290_REG_REV_ID 0x03 +#define ADXRS290_REG_SN0 0x04 /* Serial Number Registers, 4 bytes */ +#define ADXRS290_REG_DATAX0 0x08 /* Roll Rate o/p Data Regs, 2 bytes */ +#define ADXRS290_REG_DATAY0 0x0A /* Pitch Rate o/p Data Regs, 2 bytes */ +#define ADXRS290_REG_TEMP0 0x0C +#define ADXRS290_REG_POWER_CTL 0x10 +#define ADXRS290_REG_FILTER 0x11 +#define ADXRS290_REG_DATA_RDY 0x12 + +#define ADXRS290_READ BIT(7) +#define ADXRS290_TSM BIT(0) +#define ADXRS290_MEASUREMENT BIT(1) +#define ADXRS290_DATA_RDY_OUT BIT(0) +#define ADXRS290_SYNC_MASK GENMASK(1, 0) +#define ADXRS290_SYNC(x) FIELD_PREP(ADXRS290_SYNC_MASK, x) +#define ADXRS290_LPF_MASK GENMASK(2, 0) +#define ADXRS290_LPF(x) FIELD_PREP(ADXRS290_LPF_MASK, x) +#define ADXRS290_HPF_MASK GENMASK(7, 4) +#define ADXRS290_HPF(x) FIELD_PREP(ADXRS290_HPF_MASK, x) + +#define ADXRS290_READ_REG(reg) (ADXRS290_READ | (reg)) + +#define ADXRS290_MAX_TRANSITION_TIME_MS 100 + +enum adxrs290_mode { + ADXRS290_MODE_STANDBY, + ADXRS290_MODE_MEASUREMENT, +}; + +enum adxrs290_scan_index { + ADXRS290_IDX_X, + ADXRS290_IDX_Y, + ADXRS290_IDX_TEMP, + ADXRS290_IDX_TS, +}; + +struct adxrs290_state { + struct spi_device *spi; + /* Serialize reads and their subsequent processing */ + struct mutex lock; + enum adxrs290_mode mode; + unsigned int lpf_3db_freq_idx; + unsigned int hpf_3db_freq_idx; + struct iio_trigger *dready_trig; + /* Ensure correct alignment of timestamp when present */ + struct { + s16 channels[3]; + aligned_s64 ts; + } buffer; +}; + +/* + * Available cut-off frequencies of the low pass filter in Hz. + * The integer part and fractional part are represented separately. + */ +static const int adxrs290_lpf_3db_freq_hz_table[][2] = { + [0] = {480, 0}, + [1] = {320, 0}, + [2] = {160, 0}, + [3] = {80, 0}, + [4] = {56, 600000}, + [5] = {40, 0}, + [6] = {28, 300000}, + [7] = {20, 0}, +}; + +/* + * Available cut-off frequencies of the high pass filter in Hz. + * The integer part and fractional part are represented separately. + */ +static const int adxrs290_hpf_3db_freq_hz_table[][2] = { + [0] = {0, 0}, + [1] = {0, 11000}, + [2] = {0, 22000}, + [3] = {0, 44000}, + [4] = {0, 87000}, + [5] = {0, 175000}, + [6] = {0, 350000}, + [7] = {0, 700000}, + [8] = {1, 400000}, + [9] = {2, 800000}, + [10] = {11, 300000}, +}; + +static int adxrs290_get_rate_data(struct iio_dev *indio_dev, const u8 cmd, int *val) +{ + struct adxrs290_state *st = iio_priv(indio_dev); + int temp; + + guard(mutex)(&st->lock); + + temp = spi_w8r16(st->spi, cmd); + if (temp < 0) + return temp; + + *val = sign_extend32(temp, 15); + + return 0; +} + +static int adxrs290_get_temp_data(struct iio_dev *indio_dev, int *val) +{ + const u8 cmd = ADXRS290_READ_REG(ADXRS290_REG_TEMP0); + struct adxrs290_state *st = iio_priv(indio_dev); + int temp; + + guard(mutex)(&st->lock); + + temp = spi_w8r16(st->spi, cmd); + if (temp < 0) + return temp; + + /* extract lower 12 bits temperature reading */ + *val = sign_extend32(temp, 11); + + return 0; +} + +static int adxrs290_get_3db_freq(struct iio_dev *indio_dev, u8 *val, u8 *val2) +{ + const u8 cmd = ADXRS290_READ_REG(ADXRS290_REG_FILTER); + struct adxrs290_state *st = iio_priv(indio_dev); + short temp; + + guard(mutex)(&st->lock); + + temp = spi_w8r8(st->spi, cmd); + if (temp < 0) + return temp; + + *val = FIELD_GET(ADXRS290_LPF_MASK, temp); + *val2 = FIELD_GET(ADXRS290_HPF_MASK, temp); + + return 0; +} + +static int adxrs290_spi_write_reg(struct spi_device *spi, const u8 reg, + const u8 val) +{ + u8 buf[2]; + + buf[0] = reg; + buf[1] = val; + + return spi_write_then_read(spi, buf, ARRAY_SIZE(buf), NULL, 0); +} + +static int adxrs290_find_match(const int (*freq_tbl)[2], const int n, + const int val, const int val2) +{ + int i; + + for (i = 0; i < n; i++) { + if (freq_tbl[i][0] == val && freq_tbl[i][1] == val2) + return i; + } + + return -EINVAL; +} + +static int adxrs290_set_filter_freq(struct iio_dev *indio_dev, + const unsigned int lpf_idx, + const unsigned int hpf_idx) +{ + struct adxrs290_state *st = iio_priv(indio_dev); + u8 val; + + val = ADXRS290_HPF(hpf_idx) | ADXRS290_LPF(lpf_idx); + + return adxrs290_spi_write_reg(st->spi, ADXRS290_REG_FILTER, val); +} + +static int adxrs290_set_mode(struct iio_dev *indio_dev, enum adxrs290_mode mode) +{ + struct adxrs290_state *st = iio_priv(indio_dev); + int val, ret; + + if (st->mode == mode) + return 0; + + guard(mutex)(&st->lock); + + ret = spi_w8r8(st->spi, ADXRS290_READ_REG(ADXRS290_REG_POWER_CTL)); + if (ret < 0) + return ret; + + val = ret; + + switch (mode) { + case ADXRS290_MODE_STANDBY: + val &= ~ADXRS290_MEASUREMENT; + break; + case ADXRS290_MODE_MEASUREMENT: + val |= ADXRS290_MEASUREMENT; + break; + default: + return -EINVAL; + } + + ret = adxrs290_spi_write_reg(st->spi, ADXRS290_REG_POWER_CTL, val); + if (ret < 0) { + dev_err(&st->spi->dev, "unable to set mode: %d\n", ret); + return ret; + } + + /* update cached mode */ + st->mode = mode; + + return ret; +} + +static void adxrs290_chip_off_action(void *data) +{ + struct iio_dev *indio_dev = data; + + adxrs290_set_mode(indio_dev, ADXRS290_MODE_STANDBY); +} + +static int adxrs290_initial_setup(struct iio_dev *indio_dev) +{ + struct adxrs290_state *st = iio_priv(indio_dev); + struct spi_device *spi = st->spi; + int ret; + + ret = adxrs290_spi_write_reg(spi, ADXRS290_REG_POWER_CTL, + ADXRS290_MEASUREMENT | ADXRS290_TSM); + if (ret < 0) + return ret; + + st->mode = ADXRS290_MODE_MEASUREMENT; + + return devm_add_action_or_reset(&spi->dev, adxrs290_chip_off_action, + indio_dev); +} + +static int adxrs290_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct adxrs290_state *st = iio_priv(indio_dev); + unsigned int t; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + switch (chan->type) { + case IIO_ANGL_VEL: + ret = adxrs290_get_rate_data(indio_dev, + ADXRS290_READ_REG(chan->address), + val); + if (ret < 0) + break; + + ret = IIO_VAL_INT; + break; + case IIO_TEMP: + ret = adxrs290_get_temp_data(indio_dev, val); + if (ret < 0) + break; + + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + break; + } + + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + /* 1 LSB = 0.005 degrees/sec */ + *val = 0; + *val2 = 87266; + return IIO_VAL_INT_PLUS_NANO; + case IIO_TEMP: + /* 1 LSB = 0.1 degrees Celsius */ + *val = 100; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + switch (chan->type) { + case IIO_ANGL_VEL: + t = st->lpf_3db_freq_idx; + *val = adxrs290_lpf_3db_freq_hz_table[t][0]; + *val2 = adxrs290_lpf_3db_freq_hz_table[t][1]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: + switch (chan->type) { + case IIO_ANGL_VEL: + t = st->hpf_3db_freq_idx; + *val = adxrs290_hpf_3db_freq_hz_table[t][0]; + *val2 = adxrs290_hpf_3db_freq_hz_table[t][1]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + } + + return -EINVAL; +} + +static int adxrs290_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct adxrs290_state *st = iio_priv(indio_dev); + int ret, lpf_idx, hpf_idx; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + switch (mask) { + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + lpf_idx = adxrs290_find_match(adxrs290_lpf_3db_freq_hz_table, + ARRAY_SIZE(adxrs290_lpf_3db_freq_hz_table), + val, val2); + if (lpf_idx < 0) { + ret = -EINVAL; + break; + } + + /* caching the updated state of the low-pass filter */ + st->lpf_3db_freq_idx = lpf_idx; + /* retrieving the current state of the high-pass filter */ + hpf_idx = st->hpf_3db_freq_idx; + ret = adxrs290_set_filter_freq(indio_dev, lpf_idx, hpf_idx); + break; + + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: + hpf_idx = adxrs290_find_match(adxrs290_hpf_3db_freq_hz_table, + ARRAY_SIZE(adxrs290_hpf_3db_freq_hz_table), + val, val2); + if (hpf_idx < 0) { + ret = -EINVAL; + break; + } + + /* caching the updated state of the high-pass filter */ + st->hpf_3db_freq_idx = hpf_idx; + /* retrieving the current state of the low-pass filter */ + lpf_idx = st->lpf_3db_freq_idx; + ret = adxrs290_set_filter_freq(indio_dev, lpf_idx, hpf_idx); + break; + + default: + ret = -EINVAL; + break; + } + + iio_device_release_direct(indio_dev); + return ret; +} + +static int adxrs290_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + *vals = (const int *)adxrs290_lpf_3db_freq_hz_table; + *type = IIO_VAL_INT_PLUS_MICRO; + /* Values are stored in a 2D matrix */ + *length = ARRAY_SIZE(adxrs290_lpf_3db_freq_hz_table) * 2; + + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: + *vals = (const int *)adxrs290_hpf_3db_freq_hz_table; + *type = IIO_VAL_INT_PLUS_MICRO; + /* Values are stored in a 2D matrix */ + *length = ARRAY_SIZE(adxrs290_hpf_3db_freq_hz_table) * 2; + + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int adxrs290_reg_access_rw(struct spi_device *spi, unsigned int reg, + unsigned int *readval) +{ + int ret; + + ret = spi_w8r8(spi, ADXRS290_READ_REG(reg)); + if (ret < 0) + return ret; + + *readval = ret; + + return 0; +} + +static int adxrs290_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct adxrs290_state *st = iio_priv(indio_dev); + + if (readval) + return adxrs290_reg_access_rw(st->spi, reg, readval); + else + return adxrs290_spi_write_reg(st->spi, reg, writeval); +} + +static int adxrs290_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct adxrs290_state *st = iio_priv(indio_dev); + int ret; + u8 val; + + val = state ? ADXRS290_SYNC(ADXRS290_DATA_RDY_OUT) : 0; + + ret = adxrs290_spi_write_reg(st->spi, ADXRS290_REG_DATA_RDY, val); + if (ret < 0) + dev_err(&st->spi->dev, "failed to start data rdy interrupt\n"); + + return ret; +} + +static void adxrs290_reset_trig(struct iio_trigger *trig) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + int val; + + /* + * Data ready interrupt is reset after a read of the data registers. + * Here, we only read the 16b DATAY registers as that marks the end of + * a read of the data registers and initiates a reset for the interrupt + * line. + */ + adxrs290_get_rate_data(indio_dev, + ADXRS290_READ_REG(ADXRS290_REG_DATAY0), &val); +} + +static const struct iio_trigger_ops adxrs290_trigger_ops = { + .set_trigger_state = &adxrs290_data_rdy_trigger_set_state, + .validate_device = &iio_trigger_validate_own_device, + .reenable = &adxrs290_reset_trig, +}; + +static irqreturn_t adxrs290_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adxrs290_state *st = iio_priv(indio_dev); + u8 tx = ADXRS290_READ_REG(ADXRS290_REG_DATAX0); + int ret; + + do { + guard(mutex)(&st->lock); + + /* exercise a bulk data capture starting from reg DATAX0... */ + ret = spi_write_then_read(st->spi, &tx, sizeof(tx), + st->buffer.channels, + sizeof(st->buffer.channels)); + if (ret < 0) + break; + + iio_push_to_buffers_with_timestamp(indio_dev, &st->buffer, + pf->timestamp); + } while (0); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +#define ADXRS290_ANGL_VEL_CHANNEL(reg, axis) { \ + .type = IIO_ANGL_VEL, \ + .address = reg, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \ + BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \ + BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY), \ + .scan_index = ADXRS290_IDX_##axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ +} + +static const struct iio_chan_spec adxrs290_channels[] = { + ADXRS290_ANGL_VEL_CHANNEL(ADXRS290_REG_DATAX0, X), + ADXRS290_ANGL_VEL_CHANNEL(ADXRS290_REG_DATAY0, Y), + { + .type = IIO_TEMP, + .address = ADXRS290_REG_TEMP0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = ADXRS290_IDX_TEMP, + .scan_type = { + .sign = 's', + .realbits = 12, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(ADXRS290_IDX_TS), +}; + +static const unsigned long adxrs290_avail_scan_masks[] = { + BIT(ADXRS290_IDX_X) | BIT(ADXRS290_IDX_Y) | BIT(ADXRS290_IDX_TEMP), + 0 +}; + +static const struct iio_info adxrs290_info = { + .read_raw = &adxrs290_read_raw, + .write_raw = &adxrs290_write_raw, + .read_avail = &adxrs290_read_avail, + .debugfs_reg_access = &adxrs290_reg_access, +}; + +static int adxrs290_probe_trigger(struct iio_dev *indio_dev) +{ + struct adxrs290_state *st = iio_priv(indio_dev); + int ret; + + if (!st->spi->irq) { + dev_info(&st->spi->dev, "no irq, using polling\n"); + return 0; + } + + st->dready_trig = devm_iio_trigger_alloc(&st->spi->dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!st->dready_trig) + return -ENOMEM; + + st->dready_trig->ops = &adxrs290_trigger_ops; + iio_trigger_set_drvdata(st->dready_trig, indio_dev); + + ret = devm_request_irq(&st->spi->dev, st->spi->irq, + &iio_trigger_generic_data_rdy_poll, + IRQF_NO_THREAD, "adxrs290_irq", st->dready_trig); + if (ret) + return ret; + + ret = devm_iio_trigger_register(&st->spi->dev, st->dready_trig); + if (ret) { + dev_err(&st->spi->dev, "iio trigger register failed\n"); + return ret; + } + + indio_dev->trig = iio_trigger_get(st->dready_trig); + + return 0; +} + +static int adxrs290_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct adxrs290_state *st; + u8 val, val2; + 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; + + indio_dev->name = "adxrs290"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = adxrs290_channels; + indio_dev->num_channels = ARRAY_SIZE(adxrs290_channels); + indio_dev->info = &adxrs290_info; + indio_dev->available_scan_masks = adxrs290_avail_scan_masks; + + mutex_init(&st->lock); + + val = spi_w8r8(spi, ADXRS290_READ_REG(ADXRS290_REG_ADI_ID)); + if (val != ADXRS290_ADI_ID) { + dev_err(&spi->dev, "Wrong ADI ID 0x%02x\n", val); + return -ENODEV; + } + + val = spi_w8r8(spi, ADXRS290_READ_REG(ADXRS290_REG_MEMS_ID)); + if (val != ADXRS290_MEMS_ID) { + dev_err(&spi->dev, "Wrong MEMS ID 0x%02x\n", val); + return -ENODEV; + } + + val = spi_w8r8(spi, ADXRS290_READ_REG(ADXRS290_REG_DEV_ID)); + if (val != ADXRS290_DEV_ID) { + dev_err(&spi->dev, "Wrong DEV ID 0x%02x\n", val); + return -ENODEV; + } + + /* default mode the gyroscope starts in */ + st->mode = ADXRS290_MODE_STANDBY; + + /* switch to measurement mode and switch on the temperature sensor */ + ret = adxrs290_initial_setup(indio_dev); + if (ret < 0) + return ret; + + /* max transition time to measurement mode */ + msleep(ADXRS290_MAX_TRANSITION_TIME_MS); + + ret = adxrs290_get_3db_freq(indio_dev, &val, &val2); + if (ret < 0) + return ret; + + st->lpf_3db_freq_idx = val; + st->hpf_3db_freq_idx = val2; + + ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, + &iio_pollfunc_store_time, + &adxrs290_trigger_handler, NULL); + if (ret < 0) + return dev_err_probe(&spi->dev, ret, + "iio triggered buffer setup failed\n"); + + ret = adxrs290_probe_trigger(indio_dev); + if (ret < 0) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct of_device_id adxrs290_of_match[] = { + { .compatible = "adi,adxrs290" }, + { } +}; +MODULE_DEVICE_TABLE(of, adxrs290_of_match); + +static struct spi_driver adxrs290_driver = { + .driver = { + .name = "adxrs290", + .of_match_table = adxrs290_of_match, + }, + .probe = adxrs290_probe, +}; +module_spi_driver(adxrs290_driver); + +MODULE_AUTHOR("Nishant Malpani "); +MODULE_DESCRIPTION("Analog Devices ADXRS290 Gyroscope SPI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/gyro/adxrs450.c b/drivers/iio/gyro/adxrs450.c new file mode 100644 index 000000000..030d042ca --- /dev/null +++ b/drivers/iio/gyro/adxrs450.c @@ -0,0 +1,459 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADXRS450/ADXRS453 Digital Output Gyroscope Driver + * + * Copyright 2011 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define ADXRS450_STARTUP_DELAY 50 /* ms */ + +/* The MSB for the spi commands */ +#define ADXRS450_SENSOR_DATA (0x20 << 24) +#define ADXRS450_WRITE_DATA (0x40 << 24) +#define ADXRS450_READ_DATA (0x80 << 24) + +#define ADXRS450_RATE1 0x00 /* Rate Registers */ +#define ADXRS450_TEMP1 0x02 /* Temperature Registers */ +#define ADXRS450_LOCST1 0x04 /* Low CST Memory Registers */ +#define ADXRS450_HICST1 0x06 /* High CST Memory Registers */ +#define ADXRS450_QUAD1 0x08 /* Quad Memory Registers */ +#define ADXRS450_FAULT1 0x0A /* Fault Registers */ +#define ADXRS450_PID1 0x0C /* Part ID Register 1 */ +#define ADXRS450_SNH 0x0E /* Serial Number Registers, 4 bytes */ +#define ADXRS450_SNL 0x10 +#define ADXRS450_DNC1 0x12 /* Dynamic Null Correction Registers */ +/* Check bits */ +#define ADXRS450_P 0x01 +#define ADXRS450_CHK 0x02 +#define ADXRS450_CST 0x04 +#define ADXRS450_PWR 0x08 +#define ADXRS450_POR 0x10 +#define ADXRS450_NVM 0x20 +#define ADXRS450_Q 0x40 +#define ADXRS450_PLL 0x80 +#define ADXRS450_UV 0x100 +#define ADXRS450_OV 0x200 +#define ADXRS450_AMP 0x400 +#define ADXRS450_FAIL 0x800 + +#define ADXRS450_WRERR_MASK (0x7 << 29) + +#define ADXRS450_MAX_RX 4 +#define ADXRS450_MAX_TX 4 + +#define ADXRS450_GET_ST(a) ((a >> 26) & 0x3) + +enum { + ID_ADXRS450, + ID_ADXRS453, +}; + +/** + * struct adxrs450_state - device instance specific data + * @us: actual spi_device + * @buf_lock: mutex to protect tx and rx + * @tx: transmit buffer + * @rx: receive buffer + **/ +struct adxrs450_state { + struct spi_device *us; + struct mutex buf_lock; + __be32 tx __aligned(IIO_DMA_MINALIGN); + __be32 rx; + +}; + +/** + * adxrs450_spi_read_reg_16() - read 2 bytes from a register pair + * @indio_dev: device associated with child of actual iio_dev + * @reg_address: the address of the lower of the two registers, which should be + * an even address, the second register's address is reg_address + 1. + * @val: somewhere to pass back the value read + **/ +static int adxrs450_spi_read_reg_16(struct iio_dev *indio_dev, + u8 reg_address, + u16 *val) +{ + struct adxrs450_state *st = iio_priv(indio_dev); + u32 tx; + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = &st->tx, + .len = sizeof(st->tx), + .cs_change = 1, + }, { + .rx_buf = &st->rx, + .len = sizeof(st->rx), + }, + }; + + mutex_lock(&st->buf_lock); + tx = ADXRS450_READ_DATA | (reg_address << 17); + + if (!(hweight32(tx) & 1)) + tx |= ADXRS450_P; + + st->tx = cpu_to_be32(tx); + ret = spi_sync_transfer(st->us, xfers, ARRAY_SIZE(xfers)); + if (ret) { + dev_err(&st->us->dev, "problem while reading 16 bit register 0x%02x\n", + reg_address); + goto error_ret; + } + + *val = (be32_to_cpu(st->rx) >> 5) & 0xFFFF; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +/** + * adxrs450_spi_write_reg_16() - write 2 bytes data to a register pair + * @indio_dev: device associated with child of actual actual iio_dev + * @reg_address: the address of the lower of the two registers,which should be + * an even address, the second register's address is reg_address + 1. + * @val: value to be written. + **/ +static int adxrs450_spi_write_reg_16(struct iio_dev *indio_dev, + u8 reg_address, + u16 val) +{ + struct adxrs450_state *st = iio_priv(indio_dev); + u32 tx; + int ret; + + mutex_lock(&st->buf_lock); + tx = ADXRS450_WRITE_DATA | (reg_address << 17) | (val << 1); + + if (!(hweight32(tx) & 1)) + tx |= ADXRS450_P; + + st->tx = cpu_to_be32(tx); + ret = spi_write(st->us, &st->tx, sizeof(st->tx)); + if (ret) + dev_err(&st->us->dev, "problem while writing 16 bit register 0x%02x\n", + reg_address); + usleep_range(100, 1000); /* enforce sequential transfer delay 0.1ms */ + mutex_unlock(&st->buf_lock); + return ret; +} + +/** + * adxrs450_spi_sensor_data() - read 2 bytes sensor data + * @indio_dev: device associated with child of actual iio_dev + * @val: somewhere to pass back the value read + **/ +static int adxrs450_spi_sensor_data(struct iio_dev *indio_dev, s16 *val) +{ + struct adxrs450_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = &st->tx, + .len = sizeof(st->tx), + .cs_change = 1, + }, { + .rx_buf = &st->rx, + .len = sizeof(st->rx), + }, + }; + + mutex_lock(&st->buf_lock); + st->tx = cpu_to_be32(ADXRS450_SENSOR_DATA); + + ret = spi_sync_transfer(st->us, xfers, ARRAY_SIZE(xfers)); + if (ret) { + dev_err(&st->us->dev, "Problem while reading sensor data\n"); + goto error_ret; + } + + *val = (be32_to_cpu(st->rx) >> 10) & 0xFFFF; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +/** + * adxrs450_spi_initial() - use for initializing procedure. + * @st: device instance specific data + * @val: somewhere to pass back the value read + * @chk: Whether to perform fault check + **/ +static int adxrs450_spi_initial(struct adxrs450_state *st, + u32 *val, char chk) +{ + int ret; + u32 tx; + struct spi_transfer xfers = { + .tx_buf = &st->tx, + .rx_buf = &st->rx, + .len = sizeof(st->tx), + }; + + mutex_lock(&st->buf_lock); + tx = ADXRS450_SENSOR_DATA; + if (chk) + tx |= (ADXRS450_CHK | ADXRS450_P); + st->tx = cpu_to_be32(tx); + ret = spi_sync_transfer(st->us, &xfers, 1); + if (ret) { + dev_err(&st->us->dev, "Problem while reading initializing data\n"); + goto error_ret; + } + + *val = be32_to_cpu(st->rx); + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +/* Recommended Startup Sequence by spec */ +static int adxrs450_initial_setup(struct iio_dev *indio_dev) +{ + u32 t; + u16 data; + int ret; + struct adxrs450_state *st = iio_priv(indio_dev); + + msleep(ADXRS450_STARTUP_DELAY*2); + ret = adxrs450_spi_initial(st, &t, 1); + if (ret) + return ret; + if (t != 0x01) + dev_warn(&st->us->dev, "The initial power on response is not correct! Restart without reset?\n"); + + msleep(ADXRS450_STARTUP_DELAY); + ret = adxrs450_spi_initial(st, &t, 0); + if (ret) + return ret; + + msleep(ADXRS450_STARTUP_DELAY); + ret = adxrs450_spi_initial(st, &t, 0); + if (ret) + return ret; + if (((t & 0xff) | 0x01) != 0xff || ADXRS450_GET_ST(t) != 2) { + dev_err(&st->us->dev, "The second response is not correct!\n"); + return -EIO; + + } + ret = adxrs450_spi_initial(st, &t, 0); + if (ret) + return ret; + if (((t & 0xff) | 0x01) != 0xff || ADXRS450_GET_ST(t) != 2) { + dev_err(&st->us->dev, "The third response is not correct!\n"); + return -EIO; + + } + ret = adxrs450_spi_read_reg_16(indio_dev, ADXRS450_FAULT1, &data); + if (ret) + return ret; + if (data & 0x0fff) { + dev_err(&st->us->dev, "The device is not in normal status!\n"); + return -EINVAL; + } + + return 0; +} + +static int adxrs450_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + int ret; + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + if (val < -0x400 || val >= 0x400) + return -EINVAL; + ret = adxrs450_spi_write_reg_16(indio_dev, + ADXRS450_DNC1, val); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int adxrs450_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + int ret; + s16 t; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_ANGL_VEL: + ret = adxrs450_spi_sensor_data(indio_dev, &t); + if (ret) + break; + *val = t; + ret = IIO_VAL_INT; + break; + case IIO_TEMP: + ret = adxrs450_spi_read_reg_16(indio_dev, + ADXRS450_TEMP1, &t); + if (ret) + break; + *val = (t >> 6) + 225; + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + break; + } + break; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = 0; + *val2 = 218166; + return IIO_VAL_INT_PLUS_NANO; + case IIO_TEMP: + *val = 200; + *val2 = 0; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_QUADRATURE_CORRECTION_RAW: + ret = adxrs450_spi_read_reg_16(indio_dev, ADXRS450_QUAD1, &t); + if (ret) + break; + *val = t; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_CALIBBIAS: + ret = adxrs450_spi_read_reg_16(indio_dev, ADXRS450_DNC1, &t); + if (ret) + break; + *val = sign_extend32(t, 9); + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct iio_chan_spec adxrs450_channels[2][2] = { + [ID_ADXRS450] = { + { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_QUADRATURE_CORRECTION_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + } + }, + [ID_ADXRS453] = { + { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_QUADRATURE_CORRECTION_RAW), + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + } + }, +}; + +static const struct iio_info adxrs450_info = { + .read_raw = &adxrs450_read_raw, + .write_raw = &adxrs450_write_raw, +}; + +static int adxrs450_probe(struct spi_device *spi) +{ + int ret; + struct adxrs450_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + st = iio_priv(indio_dev); + st->us = spi; + mutex_init(&st->buf_lock); + /* This is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + indio_dev->info = &adxrs450_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = + adxrs450_channels[spi_get_device_id(spi)->driver_data]; + indio_dev->num_channels = ARRAY_SIZE(adxrs450_channels); + indio_dev->name = spi->dev.driver->name; + + ret = devm_iio_device_register(&spi->dev, indio_dev); + if (ret) + return ret; + + /* Get the device into a sane initial state */ + ret = adxrs450_initial_setup(indio_dev); + if (ret) + return ret; + + return 0; +} + +static const struct spi_device_id adxrs450_id[] = { + { .name = "adxrs450", .driver_data = ID_ADXRS450 }, + { .name = "adxrs453", .driver_data = ID_ADXRS453 }, + { } +}; +MODULE_DEVICE_TABLE(spi, adxrs450_id); + +static struct spi_driver adxrs450_driver = { + .driver = { + .name = "adxrs450", + }, + .probe = adxrs450_probe, + .id_table = adxrs450_id, +}; +module_spi_driver(adxrs450_driver); + +MODULE_AUTHOR("Cliff Cai "); +MODULE_DESCRIPTION("Analog Devices ADXRS450/ADXRS453 Gyroscope SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/gyro/bmg160.h b/drivers/iio/gyro/bmg160.h new file mode 100644 index 000000000..6bcff6562 --- /dev/null +++ b/drivers/iio/gyro/bmg160.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef BMG160_H_ +#define BMG160_H_ + +extern const struct dev_pm_ops bmg160_pm_ops; + +int bmg160_core_probe(struct device *dev, struct regmap *regmap, int irq, + const char *name); +void bmg160_core_remove(struct device *dev); + +#endif /* BMG160_H_ */ diff --git a/drivers/iio/gyro/bmg160_core.c b/drivers/iio/gyro/bmg160_core.c new file mode 100644 index 000000000..d611341a0 --- /dev/null +++ b/drivers/iio/gyro/bmg160_core.c @@ -0,0 +1,1279 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * BMG160 Gyro Sensor driver + * Copyright (c) 2014, Intel Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "bmg160.h" + +#define BMG160_REG_CHIP_ID 0x00 +#define BMG160_CHIP_ID_VAL 0x0F + +#define BMG160_REG_PMU_LPW 0x11 +#define BMG160_MODE_NORMAL 0x00 +#define BMG160_MODE_DEEP_SUSPEND 0x20 +#define BMG160_MODE_SUSPEND 0x80 + +#define BMG160_REG_RANGE 0x0F + +#define BMG160_RANGE_2000DPS 0 +#define BMG160_RANGE_1000DPS 1 +#define BMG160_RANGE_500DPS 2 +#define BMG160_RANGE_250DPS 3 +#define BMG160_RANGE_125DPS 4 + +#define BMG160_REG_PMU_BW 0x10 +#define BMG160_NO_FILTER 0 +#define BMG160_DEF_BW 100 +#define BMG160_REG_PMU_BW_RES BIT(7) + +#define BMG160_GYRO_REG_RESET 0x14 +#define BMG160_GYRO_RESET_VAL 0xb6 + +#define BMG160_REG_INT_MAP_0 0x17 +#define BMG160_INT_MAP_0_BIT_ANY BIT(1) + +#define BMG160_REG_INT_MAP_1 0x18 +#define BMG160_INT_MAP_1_BIT_NEW_DATA BIT(0) + +#define BMG160_REG_INT_RST_LATCH 0x21 +#define BMG160_INT_MODE_LATCH_RESET 0x80 +#define BMG160_INT_MODE_LATCH_INT 0x0F +#define BMG160_INT_MODE_NON_LATCH_INT 0x00 + +#define BMG160_REG_INT_EN_0 0x15 +#define BMG160_DATA_ENABLE_INT BIT(7) + +#define BMG160_REG_INT_EN_1 0x16 +#define BMG160_INT1_BIT_OD BIT(1) + +#define BMG160_REG_XOUT_L 0x02 +#define BMG160_AXIS_TO_REG(axis) (BMG160_REG_XOUT_L + (axis * 2)) + +#define BMG160_REG_SLOPE_THRES 0x1B +#define BMG160_SLOPE_THRES_MASK 0x0F + +#define BMG160_REG_MOTION_INTR 0x1C +#define BMG160_INT_MOTION_X BIT(0) +#define BMG160_INT_MOTION_Y BIT(1) +#define BMG160_INT_MOTION_Z BIT(2) +#define BMG160_ANY_DUR_MASK 0x30 +#define BMG160_ANY_DUR_SHIFT 4 + +#define BMG160_REG_INT_STATUS_2 0x0B +#define BMG160_ANY_MOTION_MASK 0x07 +#define BMG160_ANY_MOTION_BIT_X BIT(0) +#define BMG160_ANY_MOTION_BIT_Y BIT(1) +#define BMG160_ANY_MOTION_BIT_Z BIT(2) + +#define BMG160_REG_TEMP 0x08 +#define BMG160_TEMP_CENTER_VAL 23 + +#define BMG160_MAX_STARTUP_TIME_MS 80 + +#define BMG160_AUTO_SUSPEND_DELAY_MS 2000 + +struct bmg160_data { + struct regmap *regmap; + struct iio_trigger *dready_trig; + struct iio_trigger *motion_trig; + struct iio_mount_matrix orientation; + struct mutex mutex; + /* Ensure naturally aligned timestamp */ + struct { + s16 chans[3]; + aligned_s64 timestamp; + } scan; + u32 dps_range; + int ev_enable_state; + int slope_thres; + bool dready_trigger_on; + bool motion_trigger_on; + int irq; +}; + +enum bmg160_axis { + AXIS_X, + AXIS_Y, + AXIS_Z, + AXIS_MAX, +}; + +static const struct { + int odr; + int filter; + int bw_bits; +} bmg160_samp_freq_table[] = { {100, 32, 0x07}, + {200, 64, 0x06}, + {100, 12, 0x05}, + {200, 23, 0x04}, + {400, 47, 0x03}, + {1000, 116, 0x02}, + {2000, 230, 0x01} }; + +static const struct { + int scale; + int dps_range; +} bmg160_scale_table[] = { { 1065, BMG160_RANGE_2000DPS}, + { 532, BMG160_RANGE_1000DPS}, + { 266, BMG160_RANGE_500DPS}, + { 133, BMG160_RANGE_250DPS}, + { 66, BMG160_RANGE_125DPS} }; + +static int bmg160_set_mode(struct bmg160_data *data, u8 mode) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = regmap_write(data->regmap, BMG160_REG_PMU_LPW, mode); + if (ret < 0) { + dev_err(dev, "Error writing reg_pmu_lpw\n"); + return ret; + } + + return 0; +} + +static int bmg160_convert_freq_to_bit(int val) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bmg160_samp_freq_table); ++i) { + if (bmg160_samp_freq_table[i].odr == val) + return bmg160_samp_freq_table[i].bw_bits; + } + + return -EINVAL; +} + +static int bmg160_set_bw(struct bmg160_data *data, int val) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + int bw_bits; + + bw_bits = bmg160_convert_freq_to_bit(val); + if (bw_bits < 0) + return bw_bits; + + ret = regmap_write(data->regmap, BMG160_REG_PMU_BW, bw_bits); + if (ret < 0) { + dev_err(dev, "Error writing reg_pmu_bw\n"); + return ret; + } + + return 0; +} + +static int bmg160_get_filter(struct bmg160_data *data, int *val) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + int i; + unsigned int bw_bits; + + ret = regmap_read(data->regmap, BMG160_REG_PMU_BW, &bw_bits); + if (ret < 0) { + dev_err(dev, "Error reading reg_pmu_bw\n"); + return ret; + } + + /* Ignore the readonly reserved bit. */ + bw_bits &= ~BMG160_REG_PMU_BW_RES; + + for (i = 0; i < ARRAY_SIZE(bmg160_samp_freq_table); ++i) { + if (bmg160_samp_freq_table[i].bw_bits == bw_bits) + break; + } + + if (i == ARRAY_SIZE(bmg160_samp_freq_table)) + return -EINVAL; + + *val = bmg160_samp_freq_table[i].filter; + + return ret ? ret : IIO_VAL_INT; +} + + +static int bmg160_set_filter(struct bmg160_data *data, int val) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + int i; + + for (i = 0; i < ARRAY_SIZE(bmg160_samp_freq_table); ++i) { + if (bmg160_samp_freq_table[i].filter == val) + break; + } + + if (i == ARRAY_SIZE(bmg160_samp_freq_table)) + return -EINVAL; + + ret = regmap_write(data->regmap, BMG160_REG_PMU_BW, + bmg160_samp_freq_table[i].bw_bits); + if (ret < 0) { + dev_err(dev, "Error writing reg_pmu_bw\n"); + return ret; + } + + return 0; +} + +static int bmg160_chip_init(struct bmg160_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + unsigned int val; + + /* + * Reset chip to get it in a known good state. A delay of 30ms after + * reset is required according to the datasheet. + */ + regmap_write(data->regmap, BMG160_GYRO_REG_RESET, + BMG160_GYRO_RESET_VAL); + usleep_range(30000, 30700); + + ret = regmap_read(data->regmap, BMG160_REG_CHIP_ID, &val); + if (ret < 0) { + dev_err(dev, "Error reading reg_chip_id\n"); + return ret; + } + + dev_dbg(dev, "Chip Id %x\n", val); + if (val != BMG160_CHIP_ID_VAL) { + dev_err(dev, "invalid chip %x\n", val); + return -ENODEV; + } + + ret = bmg160_set_mode(data, BMG160_MODE_NORMAL); + if (ret < 0) + return ret; + + /* + * Wait for the chip to be ready after switching to normal mode. + * The BMG160 datasheet (BST-BMG160-DS000-07 Rev. 1.0, May 2013) + * specifies a start-up / wake-up time (tsu, twusm) of 30 ms; use + * BMG160_MAX_STARTUP_TIME_MS (80 ms) as a safety margin, matching + * what bmg160_runtime_resume() already does. + */ + msleep(BMG160_MAX_STARTUP_TIME_MS); + + /* Set Bandwidth */ + ret = bmg160_set_bw(data, BMG160_DEF_BW); + if (ret < 0) + return ret; + + /* Set Default Range */ + ret = regmap_write(data->regmap, BMG160_REG_RANGE, BMG160_RANGE_500DPS); + if (ret < 0) { + dev_err(dev, "Error writing reg_range\n"); + return ret; + } + data->dps_range = BMG160_RANGE_500DPS; + + ret = regmap_read(data->regmap, BMG160_REG_SLOPE_THRES, &val); + if (ret < 0) { + dev_err(dev, "Error reading reg_slope_thres\n"); + return ret; + } + data->slope_thres = val; + + /* Set default interrupt mode */ + ret = regmap_clear_bits(data->regmap, BMG160_REG_INT_EN_1, + BMG160_INT1_BIT_OD); + if (ret < 0) { + dev_err(dev, "Error updating bits in reg_int_en_1\n"); + return ret; + } + + ret = regmap_write(data->regmap, BMG160_REG_INT_RST_LATCH, + BMG160_INT_MODE_LATCH_INT | + BMG160_INT_MODE_LATCH_RESET); + if (ret < 0) { + dev_err(dev, + "Error writing reg_motion_intr\n"); + return ret; + } + + return 0; +} + +static int bmg160_set_power_state(struct bmg160_data *data, bool on) +{ +#ifdef CONFIG_PM + struct device *dev = regmap_get_device(data->regmap); + int ret; + + if (on) + ret = pm_runtime_get_sync(dev); + else + ret = pm_runtime_put_autosuspend(dev); + + if (ret < 0) { + dev_err(dev, "Failed: bmg160_set_power_state for %d\n", on); + + if (on) + pm_runtime_put_noidle(dev); + + return ret; + } +#endif + + return 0; +} + +static int bmg160_setup_any_motion_interrupt(struct bmg160_data *data, + bool status) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + + /* Enable/Disable INT_MAP0 mapping */ + ret = regmap_update_bits(data->regmap, BMG160_REG_INT_MAP_0, + BMG160_INT_MAP_0_BIT_ANY, + (status ? BMG160_INT_MAP_0_BIT_ANY : 0)); + if (ret < 0) { + dev_err(dev, "Error updating bits reg_int_map0\n"); + return ret; + } + + /* Enable/Disable slope interrupts */ + if (status) { + /* Update slope thres */ + ret = regmap_write(data->regmap, BMG160_REG_SLOPE_THRES, + data->slope_thres); + if (ret < 0) { + dev_err(dev, "Error writing reg_slope_thres\n"); + return ret; + } + + ret = regmap_write(data->regmap, BMG160_REG_MOTION_INTR, + BMG160_INT_MOTION_X | BMG160_INT_MOTION_Y | + BMG160_INT_MOTION_Z); + if (ret < 0) { + dev_err(dev, "Error writing reg_motion_intr\n"); + return ret; + } + + /* + * New data interrupt is always non-latched, + * which will have higher priority, so no need + * to set latched mode, we will be flooded anyway with INTR + */ + if (!data->dready_trigger_on) { + ret = regmap_write(data->regmap, + BMG160_REG_INT_RST_LATCH, + BMG160_INT_MODE_LATCH_INT | + BMG160_INT_MODE_LATCH_RESET); + if (ret < 0) { + dev_err(dev, "Error writing reg_rst_latch\n"); + return ret; + } + } + + ret = regmap_write(data->regmap, BMG160_REG_INT_EN_0, + BMG160_DATA_ENABLE_INT); + + } else { + ret = regmap_write(data->regmap, BMG160_REG_INT_EN_0, 0); + } + + if (ret < 0) { + dev_err(dev, "Error writing reg_int_en0\n"); + return ret; + } + + return 0; +} + +static int bmg160_setup_new_data_interrupt(struct bmg160_data *data, + bool status) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + + /* Enable/Disable INT_MAP1 mapping */ + ret = regmap_update_bits(data->regmap, BMG160_REG_INT_MAP_1, + BMG160_INT_MAP_1_BIT_NEW_DATA, + (status ? BMG160_INT_MAP_1_BIT_NEW_DATA : 0)); + if (ret < 0) { + dev_err(dev, "Error updating bits in reg_int_map1\n"); + return ret; + } + + if (status) { + ret = regmap_write(data->regmap, BMG160_REG_INT_RST_LATCH, + BMG160_INT_MODE_NON_LATCH_INT | + BMG160_INT_MODE_LATCH_RESET); + if (ret < 0) { + dev_err(dev, "Error writing reg_rst_latch\n"); + return ret; + } + + ret = regmap_write(data->regmap, BMG160_REG_INT_EN_0, + BMG160_DATA_ENABLE_INT); + + } else { + /* Restore interrupt mode */ + ret = regmap_write(data->regmap, BMG160_REG_INT_RST_LATCH, + BMG160_INT_MODE_LATCH_INT | + BMG160_INT_MODE_LATCH_RESET); + if (ret < 0) { + dev_err(dev, "Error writing reg_rst_latch\n"); + return ret; + } + + ret = regmap_write(data->regmap, BMG160_REG_INT_EN_0, 0); + } + + if (ret < 0) { + dev_err(dev, "Error writing reg_int_en0\n"); + return ret; + } + + return 0; +} + +static int bmg160_get_bw(struct bmg160_data *data, int *val) +{ + struct device *dev = regmap_get_device(data->regmap); + int i; + unsigned int bw_bits; + int ret; + + ret = regmap_read(data->regmap, BMG160_REG_PMU_BW, &bw_bits); + if (ret < 0) { + dev_err(dev, "Error reading reg_pmu_bw\n"); + return ret; + } + + /* Ignore the readonly reserved bit. */ + bw_bits &= ~BMG160_REG_PMU_BW_RES; + + for (i = 0; i < ARRAY_SIZE(bmg160_samp_freq_table); ++i) { + if (bmg160_samp_freq_table[i].bw_bits == bw_bits) { + *val = bmg160_samp_freq_table[i].odr; + return IIO_VAL_INT; + } + } + + return -EINVAL; +} + +static int bmg160_set_scale(struct bmg160_data *data, int val) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret, i; + + for (i = 0; i < ARRAY_SIZE(bmg160_scale_table); ++i) { + if (bmg160_scale_table[i].scale == val) { + ret = regmap_write(data->regmap, BMG160_REG_RANGE, + bmg160_scale_table[i].dps_range); + if (ret < 0) { + dev_err(dev, "Error writing reg_range\n"); + return ret; + } + data->dps_range = bmg160_scale_table[i].dps_range; + return 0; + } + } + + return -EINVAL; +} + +static int bmg160_get_temp(struct bmg160_data *data, int *val) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + unsigned int raw_val; + + mutex_lock(&data->mutex); + ret = bmg160_set_power_state(data, true); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + + ret = regmap_read(data->regmap, BMG160_REG_TEMP, &raw_val); + if (ret < 0) { + dev_err(dev, "Error reading reg_temp\n"); + bmg160_set_power_state(data, false); + mutex_unlock(&data->mutex); + return ret; + } + + *val = sign_extend32(raw_val, 7); + ret = bmg160_set_power_state(data, false); + mutex_unlock(&data->mutex); + if (ret < 0) + return ret; + + return IIO_VAL_INT; +} + +static int bmg160_get_axis(struct bmg160_data *data, int axis, int *val) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + __le16 raw_val; + + mutex_lock(&data->mutex); + ret = bmg160_set_power_state(data, true); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + + ret = regmap_bulk_read(data->regmap, BMG160_AXIS_TO_REG(axis), &raw_val, + sizeof(raw_val)); + if (ret < 0) { + dev_err(dev, "Error reading axis %d\n", axis); + bmg160_set_power_state(data, false); + mutex_unlock(&data->mutex); + return ret; + } + + *val = sign_extend32(le16_to_cpu(raw_val), 15); + ret = bmg160_set_power_state(data, false); + mutex_unlock(&data->mutex); + if (ret < 0) + return ret; + + return IIO_VAL_INT; +} + +static int bmg160_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bmg160_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_TEMP: + return bmg160_get_temp(data, val); + case IIO_ANGL_VEL: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + else + return bmg160_get_axis(data, chan->scan_index, + val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + if (chan->type == IIO_TEMP) { + *val = BMG160_TEMP_CENTER_VAL; + return IIO_VAL_INT; + } else + return -EINVAL; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return bmg160_get_filter(data, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + *val = 500; + return IIO_VAL_INT; + case IIO_ANGL_VEL: + { + int i; + + for (i = 0; i < ARRAY_SIZE(bmg160_scale_table); ++i) { + if (bmg160_scale_table[i].dps_range == + data->dps_range) { + *val = 0; + *val2 = bmg160_scale_table[i].scale; + return IIO_VAL_INT_PLUS_MICRO; + } + } + return -EINVAL; + } + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + *val2 = 0; + mutex_lock(&data->mutex); + ret = bmg160_get_bw(data, val); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } +} + +static int bmg160_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bmg160_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + mutex_lock(&data->mutex); + /* + * Section 4.2 of spec + * In suspend mode, the only supported operations are reading + * registers as well as writing to the (0x14) softreset + * register. Since we will be in suspend mode by default, change + * mode to power on for other writes. + */ + ret = bmg160_set_power_state(data, true); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + ret = bmg160_set_bw(data, val); + if (ret < 0) { + bmg160_set_power_state(data, false); + mutex_unlock(&data->mutex); + return ret; + } + ret = bmg160_set_power_state(data, false); + mutex_unlock(&data->mutex); + return ret; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + if (val2) + return -EINVAL; + + mutex_lock(&data->mutex); + ret = bmg160_set_power_state(data, true); + if (ret < 0) { + bmg160_set_power_state(data, false); + mutex_unlock(&data->mutex); + return ret; + } + ret = bmg160_set_filter(data, val); + if (ret < 0) { + bmg160_set_power_state(data, false); + mutex_unlock(&data->mutex); + return ret; + } + ret = bmg160_set_power_state(data, false); + mutex_unlock(&data->mutex); + return ret; + case IIO_CHAN_INFO_SCALE: + if (val) + return -EINVAL; + + mutex_lock(&data->mutex); + /* Refer to comments above for the suspend mode ops */ + ret = bmg160_set_power_state(data, true); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + ret = bmg160_set_scale(data, val2); + if (ret < 0) { + bmg160_set_power_state(data, false); + mutex_unlock(&data->mutex); + return ret; + } + ret = bmg160_set_power_state(data, false); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static int bmg160_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct bmg160_data *data = iio_priv(indio_dev); + + *val2 = 0; + switch (info) { + case IIO_EV_INFO_VALUE: + *val = data->slope_thres & BMG160_SLOPE_THRES_MASK; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; +} + +static int bmg160_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct bmg160_data *data = iio_priv(indio_dev); + + switch (info) { + case IIO_EV_INFO_VALUE: + if (data->ev_enable_state) + return -EBUSY; + data->slope_thres &= ~BMG160_SLOPE_THRES_MASK; + data->slope_thres |= (val & BMG160_SLOPE_THRES_MASK); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int bmg160_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + + struct bmg160_data *data = iio_priv(indio_dev); + + return data->ev_enable_state; +} + +static int bmg160_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct bmg160_data *data = iio_priv(indio_dev); + int ret; + + if (state && data->ev_enable_state) + return 0; + + mutex_lock(&data->mutex); + + if (!state && data->motion_trigger_on) { + data->ev_enable_state = 0; + mutex_unlock(&data->mutex); + return 0; + } + /* + * We will expect the enable and disable to do operation + * in reverse order. This will happen here anyway as our + * resume operation uses sync mode runtime pm calls, the + * suspend operation will be delayed by autosuspend delay + * So the disable operation will still happen in reverse of + * enable operation. When runtime pm is disabled the mode + * is always on so sequence doesn't matter + */ + ret = bmg160_set_power_state(data, state); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + + ret = bmg160_setup_any_motion_interrupt(data, state); + if (ret < 0) { + bmg160_set_power_state(data, false); + mutex_unlock(&data->mutex); + return ret; + } + + data->ev_enable_state = state; + mutex_unlock(&data->mutex); + + return 0; +} + +static const struct iio_mount_matrix * +bmg160_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct bmg160_data *data = iio_priv(indio_dev); + + return &data->orientation; +} + +static const struct iio_chan_spec_ext_info bmg160_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, bmg160_get_mount_matrix), + { } +}; + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("100 200 400 1000 2000"); + +static IIO_CONST_ATTR(in_anglvel_scale_available, + "0.001065 0.000532 0.000266 0.000133 0.000066"); + +static struct attribute *bmg160_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_const_attr_in_anglvel_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group bmg160_attrs_group = { + .attrs = bmg160_attributes, +}; + +static const struct iio_event_spec bmg160_event = { + .type = IIO_EV_TYPE_ROC, + .dir = IIO_EV_DIR_EITHER, + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE) +}; + +#define BMG160_CHANNEL(_axis) { \ + .type = IIO_ANGL_VEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .scan_index = AXIS_##_axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ + .ext_info = bmg160_ext_info, \ + .event_spec = &bmg160_event, \ + .num_event_specs = 1 \ +} + +static const struct iio_chan_spec bmg160_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = -1, + }, + BMG160_CHANNEL(X), + BMG160_CHANNEL(Y), + BMG160_CHANNEL(Z), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_info bmg160_info = { + .attrs = &bmg160_attrs_group, + .read_raw = bmg160_read_raw, + .write_raw = bmg160_write_raw, + .read_event_value = bmg160_read_event, + .write_event_value = bmg160_write_event, + .write_event_config = bmg160_write_event_config, + .read_event_config = bmg160_read_event_config, +}; + +static const unsigned long bmg160_accel_scan_masks[] = { + BIT(AXIS_X) | BIT(AXIS_Y) | BIT(AXIS_Z), + 0}; + +static irqreturn_t bmg160_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bmg160_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = regmap_bulk_read(data->regmap, BMG160_REG_XOUT_L, + data->scan.chans, AXIS_MAX * 2); + mutex_unlock(&data->mutex); + if (ret < 0) + goto err; + + iio_push_to_buffers_with_timestamp(indio_dev, &data->scan, + pf->timestamp); +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static void bmg160_trig_reen(struct iio_trigger *trig) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct bmg160_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); + int ret; + + /* new data interrupts don't need ack */ + if (data->dready_trigger_on) + return; + + /* Set latched mode interrupt and clear any latched interrupt */ + ret = regmap_write(data->regmap, BMG160_REG_INT_RST_LATCH, + BMG160_INT_MODE_LATCH_INT | + BMG160_INT_MODE_LATCH_RESET); + if (ret < 0) + dev_err(dev, "Error writing reg_rst_latch\n"); +} + +static int bmg160_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct bmg160_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + + if (!state && data->ev_enable_state && data->motion_trigger_on) { + data->motion_trigger_on = false; + mutex_unlock(&data->mutex); + return 0; + } + + /* + * Refer to comment in bmg160_write_event_config for + * enable/disable operation order + */ + ret = bmg160_set_power_state(data, state); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + if (data->motion_trig == trig) + ret = bmg160_setup_any_motion_interrupt(data, state); + else + ret = bmg160_setup_new_data_interrupt(data, state); + if (ret < 0) { + bmg160_set_power_state(data, false); + mutex_unlock(&data->mutex); + return ret; + } + if (data->motion_trig == trig) + data->motion_trigger_on = state; + else + data->dready_trigger_on = state; + + mutex_unlock(&data->mutex); + + return 0; +} + +static const struct iio_trigger_ops bmg160_trigger_ops = { + .set_trigger_state = bmg160_data_rdy_trigger_set_state, + .reenable = bmg160_trig_reen, +}; + +static irqreturn_t bmg160_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct bmg160_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); + int ret; + int dir; + unsigned int val; + + ret = regmap_read(data->regmap, BMG160_REG_INT_STATUS_2, &val); + if (ret < 0) { + dev_err(dev, "Error reading reg_int_status2\n"); + goto ack_intr_status; + } + + if (val & 0x08) + dir = IIO_EV_DIR_RISING; + else + dir = IIO_EV_DIR_FALLING; + + if (val & BMG160_ANY_MOTION_BIT_X) + iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ANGL_VEL, + 0, + IIO_MOD_X, + IIO_EV_TYPE_ROC, + dir), + iio_get_time_ns(indio_dev)); + if (val & BMG160_ANY_MOTION_BIT_Y) + iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ANGL_VEL, + 0, + IIO_MOD_Y, + IIO_EV_TYPE_ROC, + dir), + iio_get_time_ns(indio_dev)); + if (val & BMG160_ANY_MOTION_BIT_Z) + iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ANGL_VEL, + 0, + IIO_MOD_Z, + IIO_EV_TYPE_ROC, + dir), + iio_get_time_ns(indio_dev)); + +ack_intr_status: + if (!data->dready_trigger_on) { + ret = regmap_write(data->regmap, BMG160_REG_INT_RST_LATCH, + BMG160_INT_MODE_LATCH_INT | + BMG160_INT_MODE_LATCH_RESET); + if (ret < 0) + dev_err(dev, "Error writing reg_rst_latch\n"); + } + + return IRQ_HANDLED; +} + +static irqreturn_t bmg160_data_rdy_trig_poll(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct bmg160_data *data = iio_priv(indio_dev); + + if (data->dready_trigger_on) + iio_trigger_poll(data->dready_trig); + else if (data->motion_trigger_on) + iio_trigger_poll(data->motion_trig); + + if (data->ev_enable_state) + return IRQ_WAKE_THREAD; + else + return IRQ_HANDLED; + +} + +static int bmg160_buffer_preenable(struct iio_dev *indio_dev) +{ + struct bmg160_data *data = iio_priv(indio_dev); + + return bmg160_set_power_state(data, true); +} + +static int bmg160_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct bmg160_data *data = iio_priv(indio_dev); + + return bmg160_set_power_state(data, false); +} + +static const struct iio_buffer_setup_ops bmg160_buffer_setup_ops = { + .preenable = bmg160_buffer_preenable, + .postdisable = bmg160_buffer_postdisable, +}; + +int bmg160_core_probe(struct device *dev, struct regmap *regmap, int irq, + const char *name) +{ + static const char * const regulators[] = { "vdd", "vddio" }; + struct bmg160_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + dev_set_drvdata(dev, indio_dev); + data->irq = irq; + data->regmap = regmap; + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulators), + regulators); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + ret = iio_read_mount_matrix(dev, &data->orientation); + if (ret) + return ret; + + ret = bmg160_chip_init(data); + if (ret < 0) + return ret; + + mutex_init(&data->mutex); + + indio_dev->channels = bmg160_channels; + indio_dev->num_channels = ARRAY_SIZE(bmg160_channels); + indio_dev->name = name; + indio_dev->available_scan_masks = bmg160_accel_scan_masks; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &bmg160_info; + + if (data->irq > 0) { + ret = devm_request_threaded_irq(dev, + data->irq, + bmg160_data_rdy_trig_poll, + bmg160_event_handler, + IRQF_TRIGGER_RISING, + "bmg160_event", + indio_dev); + if (ret) + return ret; + + data->dready_trig = devm_iio_trigger_alloc(dev, + "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->dready_trig) + return -ENOMEM; + + data->motion_trig = devm_iio_trigger_alloc(dev, + "%s-any-motion-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->motion_trig) + return -ENOMEM; + + data->dready_trig->ops = &bmg160_trigger_ops; + iio_trigger_set_drvdata(data->dready_trig, indio_dev); + ret = iio_trigger_register(data->dready_trig); + if (ret) + return ret; + + data->motion_trig->ops = &bmg160_trigger_ops; + iio_trigger_set_drvdata(data->motion_trig, indio_dev); + ret = iio_trigger_register(data->motion_trig); + if (ret) { + data->motion_trig = NULL; + goto err_trigger_unregister; + } + } + + ret = iio_triggered_buffer_setup(indio_dev, + iio_pollfunc_store_time, + bmg160_trigger_handler, + &bmg160_buffer_setup_ops); + if (ret < 0) { + dev_err(dev, + "iio triggered buffer setup failed\n"); + goto err_trigger_unregister; + } + + ret = pm_runtime_set_active(dev); + if (ret) + goto err_buffer_cleanup; + + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, + BMG160_AUTO_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(dev, "unable to register iio device\n"); + goto err_pm_cleanup; + } + + return 0; + +err_pm_cleanup: + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_disable(dev); +err_buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); +err_trigger_unregister: + if (data->dready_trig) + iio_trigger_unregister(data->dready_trig); + if (data->motion_trig) + iio_trigger_unregister(data->motion_trig); + + return ret; +} +EXPORT_SYMBOL_GPL(bmg160_core_probe); + +void bmg160_core_remove(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmg160_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_put_noidle(dev); + + iio_triggered_buffer_cleanup(indio_dev); + + if (data->dready_trig) { + iio_trigger_unregister(data->dready_trig); + iio_trigger_unregister(data->motion_trig); + } + + mutex_lock(&data->mutex); + bmg160_set_mode(data, BMG160_MODE_DEEP_SUSPEND); + mutex_unlock(&data->mutex); +} +EXPORT_SYMBOL_GPL(bmg160_core_remove); + +#ifdef CONFIG_PM_SLEEP +static int bmg160_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmg160_data *data = iio_priv(indio_dev); + + mutex_lock(&data->mutex); + bmg160_set_mode(data, BMG160_MODE_SUSPEND); + mutex_unlock(&data->mutex); + + return 0; +} + +static int bmg160_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmg160_data *data = iio_priv(indio_dev); + + mutex_lock(&data->mutex); + if (data->dready_trigger_on || data->motion_trigger_on || + data->ev_enable_state) + bmg160_set_mode(data, BMG160_MODE_NORMAL); + mutex_unlock(&data->mutex); + + return 0; +} +#endif + +#ifdef CONFIG_PM +static int bmg160_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmg160_data *data = iio_priv(indio_dev); + int ret; + + ret = bmg160_set_mode(data, BMG160_MODE_SUSPEND); + if (ret < 0) { + dev_err(dev, "set mode failed\n"); + return -EAGAIN; + } + + return 0; +} + +static int bmg160_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmg160_data *data = iio_priv(indio_dev); + int ret; + + ret = bmg160_set_mode(data, BMG160_MODE_NORMAL); + if (ret < 0) + return ret; + + msleep_interruptible(BMG160_MAX_STARTUP_TIME_MS); + + return 0; +} +#endif + +const struct dev_pm_ops bmg160_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(bmg160_suspend, bmg160_resume) + SET_RUNTIME_PM_OPS(bmg160_runtime_suspend, + bmg160_runtime_resume, NULL) +}; +EXPORT_SYMBOL_GPL(bmg160_pm_ops); + +MODULE_AUTHOR("Srinivas Pandruvada "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("BMG160 Gyro driver"); diff --git a/drivers/iio/gyro/bmg160_i2c.c b/drivers/iio/gyro/bmg160_i2c.c new file mode 100644 index 000000000..be3cfbd28 --- /dev/null +++ b/drivers/iio/gyro/bmg160_i2c.c @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include +#include + +#include "bmg160.h" + +static const struct regmap_config bmg160_regmap_i2c_conf = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x3f +}; + +static int bmg160_i2c_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct regmap *regmap; + const char *name; + + regmap = devm_regmap_init_i2c(client, &bmg160_regmap_i2c_conf); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Failed to register i2c regmap: %pe\n", + regmap); + return PTR_ERR(regmap); + } + + if (id) + name = id->name; + else + name = iio_get_acpi_device_name(&client->dev); + + return bmg160_core_probe(&client->dev, regmap, client->irq, name); +} + +static void bmg160_i2c_remove(struct i2c_client *client) +{ + bmg160_core_remove(&client->dev); +} + +static const struct acpi_device_id bmg160_acpi_match[] = { + {"BMG0160", 0}, + { } +}; + +MODULE_DEVICE_TABLE(acpi, bmg160_acpi_match); + +static const struct i2c_device_id bmg160_i2c_id[] = { + { .name = "bmg160" }, + { .name = "bmi055_gyro" }, + { .name = "bmi088_gyro" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, bmg160_i2c_id); + +static const struct of_device_id bmg160_of_match[] = { + { .compatible = "bosch,bmg160" }, + { .compatible = "bosch,bmi055_gyro" }, + { .compatible = "bosch,bmi088_gyro" }, + { } +}; + +MODULE_DEVICE_TABLE(of, bmg160_of_match); + +static struct i2c_driver bmg160_i2c_driver = { + .driver = { + .name = "bmg160_i2c", + .acpi_match_table = bmg160_acpi_match, + .of_match_table = bmg160_of_match, + .pm = &bmg160_pm_ops, + }, + .probe = bmg160_i2c_probe, + .remove = bmg160_i2c_remove, + .id_table = bmg160_i2c_id, +}; +module_i2c_driver(bmg160_i2c_driver); + +MODULE_AUTHOR("Srinivas Pandruvada "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("BMG160 I2C Gyro driver"); diff --git a/drivers/iio/gyro/bmg160_spi.c b/drivers/iio/gyro/bmg160_spi.c new file mode 100644 index 000000000..14ae1d03b --- /dev/null +++ b/drivers/iio/gyro/bmg160_spi.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include +#include + +#include "bmg160.h" + +static const struct regmap_config bmg160_regmap_spi_conf = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x3f, +}; + +static int bmg160_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + const struct spi_device_id *id = spi_get_device_id(spi); + + regmap = devm_regmap_init_spi(spi, &bmg160_regmap_spi_conf); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "Failed to register spi regmap: %pe\n", + regmap); + return PTR_ERR(regmap); + } + + return bmg160_core_probe(&spi->dev, regmap, spi->irq, id->name); +} + +static void bmg160_spi_remove(struct spi_device *spi) +{ + bmg160_core_remove(&spi->dev); +} + +static const struct spi_device_id bmg160_spi_id[] = { + { .name = "bmg160" }, + { .name = "bmi055_gyro" }, + { .name = "bmi088_gyro" }, + { } +}; +MODULE_DEVICE_TABLE(spi, bmg160_spi_id); + +static const struct of_device_id bmg160_of_match[] = { + { .compatible = "bosch,bmg160" }, + { .compatible = "bosch,bmi055_gyro" }, + { .compatible = "bosch,bmi088_gyro" }, + { } +}; + +MODULE_DEVICE_TABLE(of, bmg160_of_match); + +static struct spi_driver bmg160_spi_driver = { + .driver = { + .name = "bmg160_spi", + .of_match_table = bmg160_of_match, + .pm = &bmg160_pm_ops, + }, + .probe = bmg160_spi_probe, + .remove = bmg160_spi_remove, + .id_table = bmg160_spi_id, +}; +module_spi_driver(bmg160_spi_driver); + +MODULE_AUTHOR("Markus Pargmann "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("BMG160 SPI Gyro driver"); diff --git a/drivers/iio/gyro/fxas21002c.h b/drivers/iio/gyro/fxas21002c.h new file mode 100644 index 000000000..c81cecee1 --- /dev/null +++ b/drivers/iio/gyro/fxas21002c.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Driver for NXP FXAS21002C Gyroscope - Header + * + * Copyright (C) 2019 Linaro Ltd. + */ + +#ifndef FXAS21002C_H_ +#define FXAS21002C_H_ + +#include + +#define FXAS21002C_REG_STATUS 0x00 +#define FXAS21002C_REG_OUT_X_MSB 0x01 +#define FXAS21002C_REG_OUT_X_LSB 0x02 +#define FXAS21002C_REG_OUT_Y_MSB 0x03 +#define FXAS21002C_REG_OUT_Y_LSB 0x04 +#define FXAS21002C_REG_OUT_Z_MSB 0x05 +#define FXAS21002C_REG_OUT_Z_LSB 0x06 +#define FXAS21002C_REG_DR_STATUS 0x07 +#define FXAS21002C_REG_F_STATUS 0x08 +#define FXAS21002C_REG_F_SETUP 0x09 +#define FXAS21002C_REG_F_EVENT 0x0A +#define FXAS21002C_REG_INT_SRC_FLAG 0x0B +#define FXAS21002C_REG_WHO_AM_I 0x0C +#define FXAS21002C_REG_CTRL0 0x0D +#define FXAS21002C_REG_RT_CFG 0x0E +#define FXAS21002C_REG_RT_SRC 0x0F +#define FXAS21002C_REG_RT_THS 0x10 +#define FXAS21002C_REG_RT_COUNT 0x11 +#define FXAS21002C_REG_TEMP 0x12 +#define FXAS21002C_REG_CTRL1 0x13 +#define FXAS21002C_REG_CTRL2 0x14 +#define FXAS21002C_REG_CTRL3 0x15 + +enum fxas21002c_fields { + F_DR_STATUS, + F_OUT_X_MSB, + F_OUT_X_LSB, + F_OUT_Y_MSB, + F_OUT_Y_LSB, + F_OUT_Z_MSB, + F_OUT_Z_LSB, + /* DR_STATUS */ + F_ZYX_OW, F_Z_OW, F_Y_OW, F_X_OW, F_ZYX_DR, F_Z_DR, F_Y_DR, F_X_DR, + /* F_STATUS */ + F_OVF, F_WMKF, F_CNT, + /* F_SETUP */ + F_MODE, F_WMRK, + /* F_EVENT */ + F_EVENT, FE_TIME, + /* INT_SOURCE_FLAG */ + F_BOOTEND, F_SRC_FIFO, F_SRC_RT, F_SRC_DRDY, + /* WHO_AM_I */ + F_WHO_AM_I, + /* CTRL_REG0 */ + F_BW, F_SPIW, F_SEL, F_HPF_EN, F_FS, + /* RT_CFG */ + F_ELE, F_ZTEFE, F_YTEFE, F_XTEFE, + /* RT_SRC */ + F_EA, F_ZRT, F_ZRT_POL, F_YRT, F_YRT_POL, F_XRT, F_XRT_POL, + /* RT_THS */ + F_DBCNTM, F_THS, + /* RT_COUNT */ + F_RT_COUNT, + /* TEMP */ + F_TEMP, + /* CTRL_REG1 */ + F_RST, F_ST, F_DR, F_ACTIVE, F_READY, + /* CTRL_REG2 */ + F_INT_CFG_FIFO, F_INT_EN_FIFO, F_INT_CFG_RT, F_INT_EN_RT, + F_INT_CFG_DRDY, F_INT_EN_DRDY, F_IPOL, F_PP_OD, + /* CTRL_REG3 */ + F_WRAPTOONE, F_EXTCTRLEN, F_FS_DOUBLE, + /* MAX FIELDS */ + F_MAX_FIELDS, +}; + +extern const struct dev_pm_ops fxas21002c_pm_ops; + +int fxas21002c_core_probe(struct device *dev, struct regmap *regmap, int irq, + const char *name); +void fxas21002c_core_remove(struct device *dev); +#endif diff --git a/drivers/iio/gyro/fxas21002c_core.c b/drivers/iio/gyro/fxas21002c_core.c new file mode 100644 index 000000000..a88670207 --- /dev/null +++ b/drivers/iio/gyro/fxas21002c_core.c @@ -0,0 +1,1062 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for NXP FXAS21002C Gyroscope - Core + * + * Copyright (C) 2019 Linaro Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "fxas21002c.h" + +#define FXAS21002C_CHIP_ID_1 0xD6 +#define FXAS21002C_CHIP_ID_2 0xD7 + +enum fxas21002c_mode_state { + FXAS21002C_MODE_STANDBY, + FXAS21002C_MODE_READY, + FXAS21002C_MODE_ACTIVE, +}; + +#define FXAS21002C_STANDBY_ACTIVE_TIME_MS 62 +#define FXAS21002C_READY_ACTIVE_TIME_MS 7 + +#define FXAS21002C_ODR_LIST_MAX 10 + +#define FXAS21002C_SCALE_FRACTIONAL 32 +#define FXAS21002C_RANGE_LIMIT_DOUBLE 2000 + +#define FXAS21002C_AXIS_TO_REG(axis) (FXAS21002C_REG_OUT_X_MSB + ((axis) * 2)) + +static const struct reg_field fxas21002c_reg_fields[] = { + [F_DR_STATUS] = REG_FIELD(FXAS21002C_REG_STATUS, 0, 7), + [F_OUT_X_MSB] = REG_FIELD(FXAS21002C_REG_OUT_X_MSB, 0, 7), + [F_OUT_X_LSB] = REG_FIELD(FXAS21002C_REG_OUT_X_LSB, 0, 7), + [F_OUT_Y_MSB] = REG_FIELD(FXAS21002C_REG_OUT_Y_MSB, 0, 7), + [F_OUT_Y_LSB] = REG_FIELD(FXAS21002C_REG_OUT_Y_LSB, 0, 7), + [F_OUT_Z_MSB] = REG_FIELD(FXAS21002C_REG_OUT_Z_MSB, 0, 7), + [F_OUT_Z_LSB] = REG_FIELD(FXAS21002C_REG_OUT_Z_LSB, 0, 7), + [F_ZYX_OW] = REG_FIELD(FXAS21002C_REG_DR_STATUS, 7, 7), + [F_Z_OW] = REG_FIELD(FXAS21002C_REG_DR_STATUS, 6, 6), + [F_Y_OW] = REG_FIELD(FXAS21002C_REG_DR_STATUS, 5, 5), + [F_X_OW] = REG_FIELD(FXAS21002C_REG_DR_STATUS, 4, 4), + [F_ZYX_DR] = REG_FIELD(FXAS21002C_REG_DR_STATUS, 3, 3), + [F_Z_DR] = REG_FIELD(FXAS21002C_REG_DR_STATUS, 2, 2), + [F_Y_DR] = REG_FIELD(FXAS21002C_REG_DR_STATUS, 1, 1), + [F_X_DR] = REG_FIELD(FXAS21002C_REG_DR_STATUS, 0, 0), + [F_OVF] = REG_FIELD(FXAS21002C_REG_F_STATUS, 7, 7), + [F_WMKF] = REG_FIELD(FXAS21002C_REG_F_STATUS, 6, 6), + [F_CNT] = REG_FIELD(FXAS21002C_REG_F_STATUS, 0, 5), + [F_MODE] = REG_FIELD(FXAS21002C_REG_F_SETUP, 6, 7), + [F_WMRK] = REG_FIELD(FXAS21002C_REG_F_SETUP, 0, 5), + [F_EVENT] = REG_FIELD(FXAS21002C_REG_F_EVENT, 5, 5), + [FE_TIME] = REG_FIELD(FXAS21002C_REG_F_EVENT, 0, 4), + [F_BOOTEND] = REG_FIELD(FXAS21002C_REG_INT_SRC_FLAG, 3, 3), + [F_SRC_FIFO] = REG_FIELD(FXAS21002C_REG_INT_SRC_FLAG, 2, 2), + [F_SRC_RT] = REG_FIELD(FXAS21002C_REG_INT_SRC_FLAG, 1, 1), + [F_SRC_DRDY] = REG_FIELD(FXAS21002C_REG_INT_SRC_FLAG, 0, 0), + [F_WHO_AM_I] = REG_FIELD(FXAS21002C_REG_WHO_AM_I, 0, 7), + [F_BW] = REG_FIELD(FXAS21002C_REG_CTRL0, 6, 7), + [F_SPIW] = REG_FIELD(FXAS21002C_REG_CTRL0, 5, 5), + [F_SEL] = REG_FIELD(FXAS21002C_REG_CTRL0, 3, 4), + [F_HPF_EN] = REG_FIELD(FXAS21002C_REG_CTRL0, 2, 2), + [F_FS] = REG_FIELD(FXAS21002C_REG_CTRL0, 0, 1), + [F_ELE] = REG_FIELD(FXAS21002C_REG_RT_CFG, 3, 3), + [F_ZTEFE] = REG_FIELD(FXAS21002C_REG_RT_CFG, 2, 2), + [F_YTEFE] = REG_FIELD(FXAS21002C_REG_RT_CFG, 1, 1), + [F_XTEFE] = REG_FIELD(FXAS21002C_REG_RT_CFG, 0, 0), + [F_EA] = REG_FIELD(FXAS21002C_REG_RT_SRC, 6, 6), + [F_ZRT] = REG_FIELD(FXAS21002C_REG_RT_SRC, 5, 5), + [F_ZRT_POL] = REG_FIELD(FXAS21002C_REG_RT_SRC, 4, 4), + [F_YRT] = REG_FIELD(FXAS21002C_REG_RT_SRC, 3, 3), + [F_YRT_POL] = REG_FIELD(FXAS21002C_REG_RT_SRC, 2, 2), + [F_XRT] = REG_FIELD(FXAS21002C_REG_RT_SRC, 1, 1), + [F_XRT_POL] = REG_FIELD(FXAS21002C_REG_RT_SRC, 0, 0), + [F_DBCNTM] = REG_FIELD(FXAS21002C_REG_RT_THS, 7, 7), + [F_THS] = REG_FIELD(FXAS21002C_REG_RT_SRC, 0, 6), + [F_RT_COUNT] = REG_FIELD(FXAS21002C_REG_RT_COUNT, 0, 7), + [F_TEMP] = REG_FIELD(FXAS21002C_REG_TEMP, 0, 7), + [F_RST] = REG_FIELD(FXAS21002C_REG_CTRL1, 6, 6), + [F_ST] = REG_FIELD(FXAS21002C_REG_CTRL1, 5, 5), + [F_DR] = REG_FIELD(FXAS21002C_REG_CTRL1, 2, 4), + [F_ACTIVE] = REG_FIELD(FXAS21002C_REG_CTRL1, 1, 1), + [F_READY] = REG_FIELD(FXAS21002C_REG_CTRL1, 0, 0), + [F_INT_CFG_FIFO] = REG_FIELD(FXAS21002C_REG_CTRL2, 7, 7), + [F_INT_EN_FIFO] = REG_FIELD(FXAS21002C_REG_CTRL2, 6, 6), + [F_INT_CFG_RT] = REG_FIELD(FXAS21002C_REG_CTRL2, 5, 5), + [F_INT_EN_RT] = REG_FIELD(FXAS21002C_REG_CTRL2, 4, 4), + [F_INT_CFG_DRDY] = REG_FIELD(FXAS21002C_REG_CTRL2, 3, 3), + [F_INT_EN_DRDY] = REG_FIELD(FXAS21002C_REG_CTRL2, 2, 2), + [F_IPOL] = REG_FIELD(FXAS21002C_REG_CTRL2, 1, 1), + [F_PP_OD] = REG_FIELD(FXAS21002C_REG_CTRL2, 0, 0), + [F_WRAPTOONE] = REG_FIELD(FXAS21002C_REG_CTRL3, 3, 3), + [F_EXTCTRLEN] = REG_FIELD(FXAS21002C_REG_CTRL3, 2, 2), + [F_FS_DOUBLE] = REG_FIELD(FXAS21002C_REG_CTRL3, 0, 0), +}; + +static const int fxas21002c_odr_values[] = { + 800, 400, 200, 100, 50, 25, 12, 12 +}; + +/* + * These values are taken from the low-pass filter cutoff frequency calculated + * ODR * 0.lpf_values. So, for ODR = 800Hz with a lpf value = 0.32 + * => LPF cutoff frequency = 800 * 0.32 = 256 Hz + */ +static const int fxas21002c_lpf_values[] = { + 32, 16, 8 +}; + +/* + * These values are taken from the high-pass filter cutoff frequency calculated + * ODR * 0.0hpf_values. So, for ODR = 800Hz with a hpf value = 0.018750 + * => HPF cutoff frequency = 800 * 0.018750 = 15 Hz + */ +static const int fxas21002c_hpf_values[] = { + 18750, 9625, 4875, 2475 +}; + +static const int fxas21002c_range_values[] = { + 4000, 2000, 1000, 500, 250 +}; + +struct fxas21002c_data { + u8 chip_id; + enum fxas21002c_mode_state mode; + enum fxas21002c_mode_state prev_mode; + + struct mutex lock; /* serialize data access */ + struct regmap *regmap; + struct regmap_field *regmap_fields[F_MAX_FIELDS]; + struct iio_trigger *dready_trig; + s64 timestamp; + int irq; + + struct regulator *vdd; + struct regulator *vddio; + + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers live in their own cache lines. + */ + s16 buffer[8] __aligned(IIO_DMA_MINALIGN); +}; + +enum fxas21002c_channel_index { + CHANNEL_SCAN_INDEX_X, + CHANNEL_SCAN_INDEX_Y, + CHANNEL_SCAN_INDEX_Z, + CHANNEL_SCAN_MAX, +}; + +static int fxas21002c_odr_hz_from_value(struct fxas21002c_data *data, u8 value) +{ + int odr_value_max = ARRAY_SIZE(fxas21002c_odr_values) - 1; + + value = min_t(u8, value, odr_value_max); + + return fxas21002c_odr_values[value]; +} + +static int fxas21002c_odr_value_from_hz(struct fxas21002c_data *data, + unsigned int hz) +{ + int odr_table_size = ARRAY_SIZE(fxas21002c_odr_values); + int i; + + for (i = 0; i < odr_table_size; i++) + if (fxas21002c_odr_values[i] == hz) + return i; + + return -EINVAL; +} + +static int fxas21002c_lpf_bw_from_value(struct fxas21002c_data *data, u8 value) +{ + int lpf_value_max = ARRAY_SIZE(fxas21002c_lpf_values) - 1; + + value = min_t(u8, value, lpf_value_max); + + return fxas21002c_lpf_values[value]; +} + +static int fxas21002c_lpf_value_from_bw(struct fxas21002c_data *data, + unsigned int hz) +{ + int lpf_table_size = ARRAY_SIZE(fxas21002c_lpf_values); + int i; + + for (i = 0; i < lpf_table_size; i++) + if (fxas21002c_lpf_values[i] == hz) + return i; + + return -EINVAL; +} + +static int fxas21002c_hpf_sel_from_value(struct fxas21002c_data *data, u8 value) +{ + int hpf_value_max = ARRAY_SIZE(fxas21002c_hpf_values) - 1; + + value = min_t(u8, value, hpf_value_max); + + return fxas21002c_hpf_values[value]; +} + +static int fxas21002c_hpf_value_from_sel(struct fxas21002c_data *data, + unsigned int hz) +{ + int hpf_table_size = ARRAY_SIZE(fxas21002c_hpf_values); + int i; + + for (i = 0; i < hpf_table_size; i++) + if (fxas21002c_hpf_values[i] == hz) + return i; + + return -EINVAL; +} + +static int fxas21002c_range_fs_from_value(struct fxas21002c_data *data, + u8 value) +{ + int range_value_max = ARRAY_SIZE(fxas21002c_range_values) - 1; + unsigned int fs_double; + int ret; + + /* We need to check if FS_DOUBLE is enabled to offset the value */ + ret = regmap_field_read(data->regmap_fields[F_FS_DOUBLE], &fs_double); + if (ret < 0) + return ret; + + if (!fs_double) + value += 1; + + value = min_t(u8, value, range_value_max); + + return fxas21002c_range_values[value]; +} + +static int fxas21002c_range_value_from_fs(struct fxas21002c_data *data, + unsigned int range) +{ + int range_table_size = ARRAY_SIZE(fxas21002c_range_values); + bool found = false; + int fs_double = 0; + int ret; + int i; + + for (i = 0; i < range_table_size; i++) + if (fxas21002c_range_values[i] == range) { + found = true; + break; + } + + if (!found) + return -EINVAL; + + if (range > FXAS21002C_RANGE_LIMIT_DOUBLE) + fs_double = 1; + + ret = regmap_field_write(data->regmap_fields[F_FS_DOUBLE], fs_double); + if (ret < 0) + return ret; + + return i; +} + +static int fxas21002c_mode_get(struct fxas21002c_data *data) +{ + unsigned int active; + unsigned int ready; + int ret; + + ret = regmap_field_read(data->regmap_fields[F_ACTIVE], &active); + if (ret < 0) + return ret; + if (active) + return FXAS21002C_MODE_ACTIVE; + + ret = regmap_field_read(data->regmap_fields[F_READY], &ready); + if (ret < 0) + return ret; + if (ready) + return FXAS21002C_MODE_READY; + + return FXAS21002C_MODE_STANDBY; +} + +static int fxas21002c_mode_set(struct fxas21002c_data *data, + enum fxas21002c_mode_state mode) +{ + int ret; + + if (mode == data->mode) + return 0; + + if (mode == FXAS21002C_MODE_READY) + ret = regmap_field_write(data->regmap_fields[F_READY], 1); + else + ret = regmap_field_write(data->regmap_fields[F_READY], 0); + if (ret < 0) + return ret; + + if (mode == FXAS21002C_MODE_ACTIVE) + ret = regmap_field_write(data->regmap_fields[F_ACTIVE], 1); + else + ret = regmap_field_write(data->regmap_fields[F_ACTIVE], 0); + if (ret < 0) + return ret; + + /* if going to active wait the setup times */ + if (mode == FXAS21002C_MODE_ACTIVE && + data->mode == FXAS21002C_MODE_STANDBY) + msleep_interruptible(FXAS21002C_STANDBY_ACTIVE_TIME_MS); + + if (data->mode == FXAS21002C_MODE_READY) + msleep_interruptible(FXAS21002C_READY_ACTIVE_TIME_MS); + + data->prev_mode = data->mode; + data->mode = mode; + + return ret; +} + +static int fxas21002c_write(struct fxas21002c_data *data, + enum fxas21002c_fields field, int bits) +{ + int actual_mode; + int ret; + + mutex_lock(&data->lock); + + actual_mode = fxas21002c_mode_get(data); + if (actual_mode < 0) { + ret = actual_mode; + goto out_unlock; + } + + ret = fxas21002c_mode_set(data, FXAS21002C_MODE_READY); + if (ret < 0) + goto out_unlock; + + ret = regmap_field_write(data->regmap_fields[field], bits); + if (ret < 0) + goto out_unlock; + + ret = fxas21002c_mode_set(data, data->prev_mode); + +out_unlock: + mutex_unlock(&data->lock); + + return ret; +} + +static int fxas21002c_pm_get(struct fxas21002c_data *data) +{ + return pm_runtime_resume_and_get(regmap_get_device(data->regmap)); +} + +static int fxas21002c_pm_put(struct fxas21002c_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + + return pm_runtime_put_autosuspend(dev); +} + +static int fxas21002c_temp_get(struct fxas21002c_data *data, int *val) +{ + struct device *dev = regmap_get_device(data->regmap); + unsigned int temp; + int ret; + + mutex_lock(&data->lock); + ret = fxas21002c_pm_get(data); + if (ret < 0) + goto data_unlock; + + ret = regmap_field_read(data->regmap_fields[F_TEMP], &temp); + if (ret < 0) { + dev_err(dev, "failed to read temp: %d\n", ret); + fxas21002c_pm_put(data); + goto data_unlock; + } + + *val = sign_extend32(temp, 7); + + ret = fxas21002c_pm_put(data); + if (ret < 0) + goto data_unlock; + + ret = IIO_VAL_INT; + +data_unlock: + mutex_unlock(&data->lock); + + return ret; +} + +static int fxas21002c_axis_get(struct fxas21002c_data *data, + int index, int *val) +{ + struct device *dev = regmap_get_device(data->regmap); + __be16 axis_be; + int ret; + + mutex_lock(&data->lock); + ret = fxas21002c_pm_get(data); + if (ret < 0) + goto data_unlock; + + ret = regmap_bulk_read(data->regmap, FXAS21002C_AXIS_TO_REG(index), + &axis_be, sizeof(axis_be)); + if (ret < 0) { + dev_err(dev, "failed to read axis: %d: %d\n", index, ret); + fxas21002c_pm_put(data); + goto data_unlock; + } + + *val = sign_extend32(be16_to_cpu(axis_be), 15); + + ret = fxas21002c_pm_put(data); + if (ret < 0) + goto data_unlock; + + ret = IIO_VAL_INT; + +data_unlock: + mutex_unlock(&data->lock); + + return ret; +} + +static int fxas21002c_odr_get(struct fxas21002c_data *data, int *odr) +{ + unsigned int odr_bits; + int ret; + + mutex_lock(&data->lock); + ret = regmap_field_read(data->regmap_fields[F_DR], &odr_bits); + if (ret < 0) + goto data_unlock; + + *odr = fxas21002c_odr_hz_from_value(data, odr_bits); + + ret = IIO_VAL_INT; + +data_unlock: + mutex_unlock(&data->lock); + + return ret; +} + +static int fxas21002c_odr_set(struct fxas21002c_data *data, int odr) +{ + int odr_bits; + + odr_bits = fxas21002c_odr_value_from_hz(data, odr); + if (odr_bits < 0) + return odr_bits; + + return fxas21002c_write(data, F_DR, odr_bits); +} + +static int fxas21002c_lpf_get(struct fxas21002c_data *data, int *val2) +{ + unsigned int bw_bits; + int ret; + + mutex_lock(&data->lock); + ret = regmap_field_read(data->regmap_fields[F_BW], &bw_bits); + if (ret < 0) + goto data_unlock; + + *val2 = fxas21002c_lpf_bw_from_value(data, bw_bits) * 10000; + + ret = IIO_VAL_INT_PLUS_MICRO; + +data_unlock: + mutex_unlock(&data->lock); + + return ret; +} + +static int fxas21002c_lpf_set(struct fxas21002c_data *data, int bw) +{ + int bw_bits; + int odr; + int ret; + + bw_bits = fxas21002c_lpf_value_from_bw(data, bw); + if (bw_bits < 0) + return bw_bits; + + /* + * From table 33 of the device spec, for ODR = 25Hz and 12.5 value 0.08 + * is not allowed and for ODR = 12.5 value 0.16 is also not allowed + */ + ret = fxas21002c_odr_get(data, &odr); + if (ret < 0) + return -EINVAL; + + if ((odr == 25 && bw_bits > 0x01) || (odr == 12 && bw_bits > 0)) + return -EINVAL; + + return fxas21002c_write(data, F_BW, bw_bits); +} + +static int fxas21002c_hpf_get(struct fxas21002c_data *data, int *val2) +{ + unsigned int sel_bits; + int ret; + + mutex_lock(&data->lock); + ret = regmap_field_read(data->regmap_fields[F_SEL], &sel_bits); + if (ret < 0) + goto data_unlock; + + *val2 = fxas21002c_hpf_sel_from_value(data, sel_bits); + + ret = IIO_VAL_INT_PLUS_MICRO; + +data_unlock: + mutex_unlock(&data->lock); + + return ret; +} + +static int fxas21002c_hpf_set(struct fxas21002c_data *data, int sel) +{ + int sel_bits; + + sel_bits = fxas21002c_hpf_value_from_sel(data, sel); + if (sel_bits < 0) + return sel_bits; + + return fxas21002c_write(data, F_SEL, sel_bits); +} + +static int fxas21002c_scale_get(struct fxas21002c_data *data, int *val) +{ + int fs_bits; + int scale; + int ret; + + mutex_lock(&data->lock); + ret = regmap_field_read(data->regmap_fields[F_FS], &fs_bits); + if (ret < 0) + goto data_unlock; + + scale = fxas21002c_range_fs_from_value(data, fs_bits); + if (scale < 0) { + ret = scale; + goto data_unlock; + } + + *val = scale; + +data_unlock: + mutex_unlock(&data->lock); + + return ret; +} + +static int fxas21002c_scale_set(struct fxas21002c_data *data, int range) +{ + int fs_bits; + + fs_bits = fxas21002c_range_value_from_fs(data, range); + if (fs_bits < 0) + return fs_bits; + + return fxas21002c_write(data, F_FS, fs_bits); +} + +static int fxas21002c_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct fxas21002c_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_TEMP: + return fxas21002c_temp_get(data, val); + case IIO_ANGL_VEL: + return fxas21002c_axis_get(data, chan->scan_index, val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *val2 = FXAS21002C_SCALE_FRACTIONAL; + ret = fxas21002c_scale_get(data, val); + if (ret < 0) + return ret; + + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + *val = 0; + return fxas21002c_lpf_get(data, val2); + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: + *val = 0; + return fxas21002c_hpf_get(data, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + *val2 = 0; + return fxas21002c_odr_get(data, val); + default: + return -EINVAL; + } +} + +static int fxas21002c_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct fxas21002c_data *data = iio_priv(indio_dev); + int range; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (val2) + return -EINVAL; + + return fxas21002c_odr_set(data, val); + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + if (val) + return -EINVAL; + + val2 = val2 / 10000; + return fxas21002c_lpf_set(data, val2); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + range = (((val * 1000 + val2 / 1000) * + FXAS21002C_SCALE_FRACTIONAL) / 1000); + return fxas21002c_scale_set(data, range); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: + return fxas21002c_hpf_set(data, val2); + default: + return -EINVAL; + } +} + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("12.5 25 50 100 200 400 800"); + +static IIO_CONST_ATTR(in_anglvel_filter_low_pass_3db_frequency_available, + "0.32 0.16 0.08"); + +static IIO_CONST_ATTR(in_anglvel_filter_high_pass_3db_frequency_available, + "0.018750 0.009625 0.004875 0.002475"); + +static IIO_CONST_ATTR(in_anglvel_scale_available, + "125.0 62.5 31.25 15.625 7.8125"); + +static struct attribute *fxas21002c_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_const_attr_in_anglvel_filter_low_pass_3db_frequency_available.dev_attr.attr, + &iio_const_attr_in_anglvel_filter_high_pass_3db_frequency_available.dev_attr.attr, + &iio_const_attr_in_anglvel_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group fxas21002c_attrs_group = { + .attrs = fxas21002c_attributes, +}; + +#define FXAS21002C_CHANNEL(_axis) { \ + .type = IIO_ANGL_VEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \ + BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = CHANNEL_SCAN_INDEX_##_axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ +} + +static const struct iio_chan_spec fxas21002c_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_index = -1, + }, + FXAS21002C_CHANNEL(X), + FXAS21002C_CHANNEL(Y), + FXAS21002C_CHANNEL(Z), +}; + +static const struct iio_info fxas21002c_info = { + .attrs = &fxas21002c_attrs_group, + .read_raw = &fxas21002c_read_raw, + .write_raw = &fxas21002c_write_raw, +}; + +static irqreturn_t fxas21002c_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct fxas21002c_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->lock); + ret = fxas21002c_pm_get(data); + if (ret < 0) + goto out_unlock; + + ret = regmap_bulk_read(data->regmap, FXAS21002C_REG_OUT_X_MSB, + data->buffer, CHANNEL_SCAN_MAX * sizeof(s16)); + if (ret < 0) + goto out_pm_put; + + iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, + data->timestamp); + +out_pm_put: + fxas21002c_pm_put(data); + +out_unlock: + mutex_unlock(&data->lock); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int fxas21002c_chip_init(struct fxas21002c_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + unsigned int chip_id; + int ret; + + ret = regmap_field_read(data->regmap_fields[F_WHO_AM_I], &chip_id); + if (ret < 0) + return ret; + + if (chip_id != FXAS21002C_CHIP_ID_1 && + chip_id != FXAS21002C_CHIP_ID_2) { + dev_err(dev, "chip id 0x%02x is not supported\n", chip_id); + return -EINVAL; + } + + data->chip_id = chip_id; + + ret = fxas21002c_mode_set(data, FXAS21002C_MODE_STANDBY); + if (ret < 0) + return ret; + + /* Set ODR to 200HZ as default */ + ret = fxas21002c_odr_set(data, 200); + if (ret < 0) + dev_err(dev, "failed to set ODR: %d\n", ret); + + return ret; +} + +static int fxas21002c_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct fxas21002c_data *data = iio_priv(indio_dev); + + return regmap_field_write(data->regmap_fields[F_INT_EN_DRDY], state); +} + +static const struct iio_trigger_ops fxas21002c_trigger_ops = { + .set_trigger_state = &fxas21002c_data_rdy_trigger_set_state, +}; + +static irqreturn_t fxas21002c_data_rdy_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct fxas21002c_data *data = iio_priv(indio_dev); + + data->timestamp = iio_get_time_ns(indio_dev); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t fxas21002c_data_rdy_thread(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct fxas21002c_data *data = iio_priv(indio_dev); + unsigned int data_ready; + int ret; + + ret = regmap_field_read(data->regmap_fields[F_SRC_DRDY], &data_ready); + if (ret < 0) + return IRQ_NONE; + + if (!data_ready) + return IRQ_NONE; + + iio_trigger_poll_nested(data->dready_trig); + + return IRQ_HANDLED; +} + +static int fxas21002c_trigger_probe(struct fxas21002c_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + struct iio_dev *indio_dev = dev_get_drvdata(dev); + unsigned long irq_trig; + bool irq_open_drain; + int irq1; + int ret; + + if (!data->irq) + return 0; + + irq1 = fwnode_irq_get_byname(dev_fwnode(dev), "INT1"); + if (irq1 == data->irq) { + dev_info(dev, "using interrupt line INT1\n"); + ret = regmap_field_write(data->regmap_fields[F_INT_CFG_DRDY], + 1); + if (ret < 0) + return ret; + } + + dev_info(dev, "using interrupt line INT2\n"); + + irq_open_drain = device_property_read_bool(dev, "drive-open-drain"); + + data->dready_trig = devm_iio_trigger_alloc(dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->dready_trig) + return -ENOMEM; + + irq_trig = irq_get_trigger_type(data->irq); + if (irq_trig == IRQF_TRIGGER_RISING) { + ret = regmap_field_write(data->regmap_fields[F_IPOL], 1); + if (ret < 0) + return ret; + } + + if (irq_open_drain) + irq_trig |= IRQF_SHARED; + + ret = devm_request_threaded_irq(dev, data->irq, + fxas21002c_data_rdy_handler, + fxas21002c_data_rdy_thread, + irq_trig, "fxas21002c_data_ready", + indio_dev); + if (ret < 0) + return ret; + + data->dready_trig->ops = &fxas21002c_trigger_ops; + iio_trigger_set_drvdata(data->dready_trig, indio_dev); + + return devm_iio_trigger_register(dev, data->dready_trig); +} + +static int fxas21002c_power_enable(struct fxas21002c_data *data) +{ + int ret; + + ret = regulator_enable(data->vdd); + if (ret < 0) + return ret; + + ret = regulator_enable(data->vddio); + if (ret < 0) { + regulator_disable(data->vdd); + return ret; + } + + return 0; +} + +static void fxas21002c_power_disable(struct fxas21002c_data *data) +{ + regulator_disable(data->vdd); + regulator_disable(data->vddio); +} + +static void fxas21002c_power_disable_action(void *_data) +{ + struct fxas21002c_data *data = _data; + + fxas21002c_power_disable(data); +} + +static int fxas21002c_regulators_get(struct fxas21002c_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + + data->vdd = devm_regulator_get(dev->parent, "vdd"); + if (IS_ERR(data->vdd)) + return PTR_ERR(data->vdd); + + data->vddio = devm_regulator_get(dev->parent, "vddio"); + + return PTR_ERR_OR_ZERO(data->vddio); +} + +int fxas21002c_core_probe(struct device *dev, struct regmap *regmap, int irq, + const char *name) +{ + struct fxas21002c_data *data; + struct iio_dev *indio_dev; + struct regmap_field *f; + int i; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + dev_set_drvdata(dev, indio_dev); + data->irq = irq; + data->regmap = regmap; + + for (i = 0; i < F_MAX_FIELDS; i++) { + f = devm_regmap_field_alloc(dev, data->regmap, + fxas21002c_reg_fields[i]); + if (IS_ERR(f)) + return PTR_ERR(f); + + data->regmap_fields[i] = f; + } + + mutex_init(&data->lock); + + ret = fxas21002c_regulators_get(data); + if (ret < 0) + return ret; + + ret = fxas21002c_power_enable(data); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(dev, fxas21002c_power_disable_action, + data); + if (ret < 0) + return ret; + + ret = fxas21002c_chip_init(data); + if (ret < 0) + return ret; + + indio_dev->channels = fxas21002c_channels; + indio_dev->num_channels = ARRAY_SIZE(fxas21002c_channels); + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &fxas21002c_info; + + ret = fxas21002c_trigger_probe(data); + if (ret < 0) + return ret; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + fxas21002c_trigger_handler, NULL); + if (ret < 0) + return ret; + + ret = pm_runtime_set_active(dev); + if (ret) + return ret; + + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, 2000); + pm_runtime_use_autosuspend(dev); + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto pm_disable; + + return 0; + +pm_disable: + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(fxas21002c_core_probe, "IIO_FXAS21002C"); + +void fxas21002c_core_remove(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + iio_device_unregister(indio_dev); + + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); +} +EXPORT_SYMBOL_NS_GPL(fxas21002c_core_remove, "IIO_FXAS21002C"); + +static int fxas21002c_suspend(struct device *dev) +{ + struct fxas21002c_data *data = iio_priv(dev_get_drvdata(dev)); + + fxas21002c_mode_set(data, FXAS21002C_MODE_STANDBY); + fxas21002c_power_disable(data); + + return 0; +} + +static int fxas21002c_resume(struct device *dev) +{ + struct fxas21002c_data *data = iio_priv(dev_get_drvdata(dev)); + int ret; + + ret = fxas21002c_power_enable(data); + if (ret < 0) + return ret; + + return fxas21002c_mode_set(data, data->prev_mode); +} + +static int fxas21002c_runtime_suspend(struct device *dev) +{ + struct fxas21002c_data *data = iio_priv(dev_get_drvdata(dev)); + + return fxas21002c_mode_set(data, FXAS21002C_MODE_READY); +} + +static int fxas21002c_runtime_resume(struct device *dev) +{ + struct fxas21002c_data *data = iio_priv(dev_get_drvdata(dev)); + + return fxas21002c_mode_set(data, FXAS21002C_MODE_ACTIVE); +} + +EXPORT_NS_GPL_DEV_PM_OPS(fxas21002c_pm_ops, IIO_FXAS21002C) = { + SYSTEM_SLEEP_PM_OPS(fxas21002c_suspend, fxas21002c_resume) + RUNTIME_PM_OPS(fxas21002c_runtime_suspend, fxas21002c_runtime_resume, + NULL) +}; + +MODULE_AUTHOR("Rui Miguel Silva "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("FXAS21002C Gyro driver"); diff --git a/drivers/iio/gyro/fxas21002c_i2c.c b/drivers/iio/gyro/fxas21002c_i2c.c new file mode 100644 index 000000000..634f9019a --- /dev/null +++ b/drivers/iio/gyro/fxas21002c_i2c.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for NXP FXAS21002C Gyroscope - I2C + * + * Copyright (C) 2018 Linaro Ltd. + */ + +#include +#include +#include +#include + +#include "fxas21002c.h" + +static const struct regmap_config fxas21002c_regmap_i2c_conf = { + .reg_bits = 8, + .val_bits = 8, + .max_register = FXAS21002C_REG_CTRL3, +}; + +static int fxas21002c_i2c_probe(struct i2c_client *i2c) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(i2c, &fxas21002c_regmap_i2c_conf); + if (IS_ERR(regmap)) { + dev_err(&i2c->dev, "Failed to register i2c regmap: %ld\n", + PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + return fxas21002c_core_probe(&i2c->dev, regmap, i2c->irq, i2c->name); +} + +static void fxas21002c_i2c_remove(struct i2c_client *i2c) +{ + fxas21002c_core_remove(&i2c->dev); +} + +static const struct i2c_device_id fxas21002c_i2c_id[] = { + { .name = "fxas21002c" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, fxas21002c_i2c_id); + +static const struct of_device_id fxas21002c_i2c_of_match[] = { + { .compatible = "nxp,fxas21002c", }, + { } +}; +MODULE_DEVICE_TABLE(of, fxas21002c_i2c_of_match); + +static struct i2c_driver fxas21002c_i2c_driver = { + .driver = { + .name = "fxas21002c_i2c", + .pm = pm_ptr(&fxas21002c_pm_ops), + .of_match_table = fxas21002c_i2c_of_match, + }, + .probe = fxas21002c_i2c_probe, + .remove = fxas21002c_i2c_remove, + .id_table = fxas21002c_i2c_id, +}; +module_i2c_driver(fxas21002c_i2c_driver); + +MODULE_AUTHOR("Rui Miguel Silva "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("FXAS21002C I2C Gyro driver"); +MODULE_IMPORT_NS("IIO_FXAS21002C"); diff --git a/drivers/iio/gyro/fxas21002c_spi.c b/drivers/iio/gyro/fxas21002c_spi.c new file mode 100644 index 000000000..40b1e02b2 --- /dev/null +++ b/drivers/iio/gyro/fxas21002c_spi.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for NXP Fxas21002c Gyroscope - SPI + * + * Copyright (C) 2019 Linaro Ltd. + */ + +#include +#include +#include +#include + +#include "fxas21002c.h" + +static const struct regmap_config fxas21002c_regmap_spi_conf = { + .reg_bits = 8, + .val_bits = 8, + .max_register = FXAS21002C_REG_CTRL3, +}; + +static int fxas21002c_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct regmap *regmap; + + regmap = devm_regmap_init_spi(spi, &fxas21002c_regmap_spi_conf); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "Failed to register spi regmap: %ld\n", + PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + return fxas21002c_core_probe(&spi->dev, regmap, spi->irq, id->name); +} + +static void fxas21002c_spi_remove(struct spi_device *spi) +{ + fxas21002c_core_remove(&spi->dev); +} + +static const struct spi_device_id fxas21002c_spi_id[] = { + { .name = "fxas21002c" }, + { } +}; +MODULE_DEVICE_TABLE(spi, fxas21002c_spi_id); + +static const struct of_device_id fxas21002c_spi_of_match[] = { + { .compatible = "nxp,fxas21002c", }, + { } +}; +MODULE_DEVICE_TABLE(of, fxas21002c_spi_of_match); + +static struct spi_driver fxas21002c_spi_driver = { + .driver = { + .name = "fxas21002c_spi", + .pm = pm_ptr(&fxas21002c_pm_ops), + .of_match_table = fxas21002c_spi_of_match, + }, + .probe = fxas21002c_spi_probe, + .remove = fxas21002c_spi_remove, + .id_table = fxas21002c_spi_id, +}; +module_spi_driver(fxas21002c_spi_driver); + +MODULE_AUTHOR("Rui Miguel Silva "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("FXAS21002C SPI Gyro driver"); +MODULE_IMPORT_NS("IIO_FXAS21002C"); diff --git a/drivers/iio/gyro/hid-sensor-gyro-3d.c b/drivers/iio/gyro/hid-sensor-gyro-3d.c new file mode 100644 index 000000000..58250a972 --- /dev/null +++ b/drivers/iio/gyro/hid-sensor-gyro-3d.c @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HID Sensors Driver + * Copyright (c) 2012, Intel Corporation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "../common/hid-sensors/hid-sensor-trigger.h" + +enum gyro_3d_channel { + CHANNEL_SCAN_INDEX_X, + CHANNEL_SCAN_INDEX_Y, + CHANNEL_SCAN_INDEX_Z, + GYRO_3D_CHANNEL_MAX, +}; + +#define CHANNEL_SCAN_INDEX_TIMESTAMP GYRO_3D_CHANNEL_MAX +struct gyro_3d_state { + struct hid_sensor_hub_callbacks callbacks; + struct hid_sensor_common common_attributes; + struct hid_sensor_hub_attribute_info gyro[GYRO_3D_CHANNEL_MAX]; + struct { + u32 gyro_val[GYRO_3D_CHANNEL_MAX]; + aligned_s64 timestamp; + } scan; + int scale_pre_decml; + int scale_post_decml; + int scale_precision; + int value_offset; + s64 timestamp; +}; + +static const u32 gyro_3d_addresses[GYRO_3D_CHANNEL_MAX] = { + HID_USAGE_SENSOR_ANGL_VELOCITY_X_AXIS, + HID_USAGE_SENSOR_ANGL_VELOCITY_Y_AXIS, + HID_USAGE_SENSOR_ANGL_VELOCITY_Z_AXIS +}; + +static const u32 gyro_3d_sensitivity_addresses[] = { + HID_USAGE_SENSOR_DATA_ANGL_VELOCITY, +}; + +/* Channel definitions */ +static const struct iio_chan_spec gyro_3d_channels[] = { + { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + .scan_index = CHANNEL_SCAN_INDEX_X, + }, { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + .scan_index = CHANNEL_SCAN_INDEX_Y, + }, { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + .scan_index = CHANNEL_SCAN_INDEX_Z, + }, + IIO_CHAN_SOFT_TIMESTAMP(CHANNEL_SCAN_INDEX_TIMESTAMP) +}; + +/* Channel read_raw handler */ +static int gyro_3d_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct gyro_3d_state *gyro_state = iio_priv(indio_dev); + int report_id = -1; + u32 address; + int ret_type; + s32 min; + + *val = 0; + *val2 = 0; + switch (mask) { + case IIO_CHAN_INFO_RAW: + hid_sensor_power_state(&gyro_state->common_attributes, true); + report_id = gyro_state->gyro[chan->scan_index].report_id; + min = gyro_state->gyro[chan->scan_index].logical_minimum; + address = gyro_3d_addresses[chan->scan_index]; + if (report_id >= 0) + *val = sensor_hub_input_attr_get_raw_value( + gyro_state->common_attributes.hsdev, + HID_USAGE_SENSOR_GYRO_3D, address, + report_id, + SENSOR_HUB_SYNC, + min < 0); + else { + *val = 0; + hid_sensor_power_state(&gyro_state->common_attributes, false); + return -EINVAL; + } + hid_sensor_power_state(&gyro_state->common_attributes, false); + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + *val = gyro_state->scale_pre_decml; + *val2 = gyro_state->scale_post_decml; + ret_type = gyro_state->scale_precision; + break; + case IIO_CHAN_INFO_OFFSET: + *val = gyro_state->value_offset; + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret_type = hid_sensor_read_samp_freq_value( + &gyro_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret_type = hid_sensor_read_raw_hyst_value( + &gyro_state->common_attributes, val, val2); + break; + default: + ret_type = -EINVAL; + break; + } + + return ret_type; +} + +/* Channel write_raw handler */ +static int gyro_3d_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct gyro_3d_state *gyro_state = iio_priv(indio_dev); + int ret = 0; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = hid_sensor_write_samp_freq_value( + &gyro_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret = hid_sensor_write_raw_hyst_value( + &gyro_state->common_attributes, val, val2); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info gyro_3d_info = { + .read_raw = &gyro_3d_read_raw, + .write_raw = &gyro_3d_write_raw, +}; + +/* Callback handler to send event after all samples are received and captured */ +static int gyro_3d_proc_event(struct hid_sensor_hub_device *hsdev, + u32 usage_id, void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct gyro_3d_state *gyro_state = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "gyro_3d_proc_event\n"); + if (atomic_read(&gyro_state->common_attributes.data_ready)) { + if (!gyro_state->timestamp) + gyro_state->timestamp = iio_get_time_ns(indio_dev); + + iio_push_to_buffers_with_timestamp(indio_dev, &gyro_state->scan, + gyro_state->timestamp); + + gyro_state->timestamp = 0; + } + + return 0; +} + +/* Capture samples in local storage */ +static int gyro_3d_capture_sample(struct hid_sensor_hub_device *hsdev, + u32 usage_id, + size_t raw_len, char *raw_data, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct gyro_3d_state *gyro_state = iio_priv(indio_dev); + int offset; + int ret = -EINVAL; + + switch (usage_id) { + case HID_USAGE_SENSOR_ANGL_VELOCITY_X_AXIS: + case HID_USAGE_SENSOR_ANGL_VELOCITY_Y_AXIS: + case HID_USAGE_SENSOR_ANGL_VELOCITY_Z_AXIS: + offset = usage_id - HID_USAGE_SENSOR_ANGL_VELOCITY_X_AXIS; + gyro_state->scan.gyro_val[CHANNEL_SCAN_INDEX_X + offset] = + *(u32 *)raw_data; + ret = 0; + break; + case HID_USAGE_SENSOR_TIME_TIMESTAMP: + gyro_state->timestamp = + hid_sensor_convert_timestamp(&gyro_state->common_attributes, + *(s64 *)raw_data); + ret = 0; + break; + default: + break; + } + + return ret; +} + +/* Parse report which is specific to an usage id*/ +static int gyro_3d_parse_report(struct platform_device *pdev, + struct hid_sensor_hub_device *hsdev, + struct iio_chan_spec *channels, + u32 usage_id, + struct gyro_3d_state *st) +{ + int ret; + + for (unsigned int ch = CHANNEL_SCAN_INDEX_X; ch <= CHANNEL_SCAN_INDEX_Z; ch++) { + ret = sensor_hub_input_get_attribute_info(hsdev, + HID_INPUT_REPORT, + usage_id, + HID_USAGE_SENSOR_ANGL_VELOCITY_X_AXIS + ch, + &st->gyro[ch]); + if (ret < 0) + break; + channels[ch].scan_type = (struct iio_scan_type) { + .format = 's', + .realbits = BYTES_TO_BITS(st->gyro[ch].size), + .storagebits = BITS_PER_TYPE(u32), + }; + } + dev_dbg(&pdev->dev, "gyro_3d %x:%x, %x:%x, %x:%x\n", + st->gyro[0].index, + st->gyro[0].report_id, + st->gyro[1].index, st->gyro[1].report_id, + st->gyro[2].index, st->gyro[2].report_id); + + st->scale_precision = hid_sensor_format_scale( + HID_USAGE_SENSOR_GYRO_3D, + &st->gyro[CHANNEL_SCAN_INDEX_X], + &st->scale_pre_decml, &st->scale_post_decml); + + return ret; +} + +/* Function to initialize the processing for usage id */ +static int hid_gyro_3d_probe(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + int ret = 0; + static const char *name = "gyro_3d"; + struct iio_dev *indio_dev; + struct gyro_3d_state *gyro_state; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*gyro_state)); + if (!indio_dev) + return -ENOMEM; + platform_set_drvdata(pdev, indio_dev); + + gyro_state = iio_priv(indio_dev); + gyro_state->common_attributes.hsdev = hsdev; + gyro_state->common_attributes.pdev = pdev; + + ret = hid_sensor_parse_common_attributes(hsdev, + HID_USAGE_SENSOR_GYRO_3D, + &gyro_state->common_attributes, + gyro_3d_sensitivity_addresses, + ARRAY_SIZE(gyro_3d_sensitivity_addresses)); + if (ret) { + dev_err(&pdev->dev, "failed to setup common attributes\n"); + return ret; + } + + indio_dev->channels = devm_kmemdup(&pdev->dev, gyro_3d_channels, + sizeof(gyro_3d_channels), GFP_KERNEL); + if (!indio_dev->channels) { + dev_err(&pdev->dev, "failed to duplicate channels\n"); + return -ENOMEM; + } + + ret = gyro_3d_parse_report(pdev, hsdev, + (struct iio_chan_spec *)indio_dev->channels, + HID_USAGE_SENSOR_GYRO_3D, gyro_state); + if (ret) { + dev_err(&pdev->dev, "failed to setup attributes\n"); + return ret; + } + + indio_dev->num_channels = ARRAY_SIZE(gyro_3d_channels); + indio_dev->info = &gyro_3d_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + atomic_set(&gyro_state->common_attributes.data_ready, 0); + + ret = hid_sensor_setup_trigger(indio_dev, name, + &gyro_state->common_attributes); + if (ret < 0) { + dev_err(&pdev->dev, "trigger setup failed\n"); + return ret; + } + + gyro_state->callbacks.send_event = gyro_3d_proc_event; + gyro_state->callbacks.capture_sample = gyro_3d_capture_sample; + gyro_state->callbacks.pdev = pdev; + ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_GYRO_3D, + &gyro_state->callbacks); + if (ret < 0) { + dev_err(&pdev->dev, "callback reg failed\n"); + goto error_remove_trigger; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "device register failed\n"); + goto error_remove_callback; + } + + return ret; + +error_remove_callback: + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_GYRO_3D); +error_remove_trigger: + hid_sensor_remove_trigger(indio_dev, &gyro_state->common_attributes); + return ret; +} + +/* Function to deinitialize the processing for usage id */ +static void hid_gyro_3d_remove(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct gyro_3d_state *gyro_state = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_GYRO_3D); + hid_sensor_remove_trigger(indio_dev, &gyro_state->common_attributes); +} + +static const struct platform_device_id hid_gyro_3d_ids[] = { + { + /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-200076", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, hid_gyro_3d_ids); + +static struct platform_driver hid_gyro_3d_platform_driver = { + .id_table = hid_gyro_3d_ids, + .driver = { + .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, + }, + .probe = hid_gyro_3d_probe, + .remove = hid_gyro_3d_remove, +}; +module_platform_driver(hid_gyro_3d_platform_driver); + +MODULE_DESCRIPTION("HID Sensor Gyroscope 3D"); +MODULE_AUTHOR("Srinivas Pandruvada "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HID"); diff --git a/drivers/iio/gyro/itg3200_buffer.c b/drivers/iio/gyro/itg3200_buffer.c new file mode 100644 index 000000000..87efa2c74 --- /dev/null +++ b/drivers/iio/gyro/itg3200_buffer.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * itg3200_buffer.c -- support InvenSense ITG3200 + * Digital 3-Axis Gyroscope driver + * + * Copyright (c) 2011 Christian Strobel + * Copyright (c) 2011 Manuel Stahl + * Copyright (c) 2012 Thorsten Nowak + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +static int itg3200_read_all_channels(struct i2c_client *i2c, __be16 *buf) +{ + u8 tx = 0x80 | ITG3200_REG_TEMP_OUT_H; + struct i2c_msg msg[2] = { + { + .addr = i2c->addr, + .flags = i2c->flags, + .len = 1, + .buf = &tx, + }, + { + .addr = i2c->addr, + .flags = i2c->flags | I2C_M_RD, + .len = ITG3200_SCAN_ELEMENTS * sizeof(s16), + .buf = (char *)buf, + }, + }; + + return i2c_transfer(i2c->adapter, msg, 2); +} + +static irqreturn_t itg3200_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct itg3200 *st = iio_priv(indio_dev); + /* + * Ensure correct alignment and padding including for the + * timestamp that may be inserted. + */ + struct { + __be16 buf[ITG3200_SCAN_ELEMENTS]; + aligned_s64 ts; + } scan; + + int ret = itg3200_read_all_channels(st->i2c, scan.buf); + if (ret < 0) + goto error_ret; + + iio_push_to_buffers_with_timestamp(indio_dev, &scan, pf->timestamp); + +error_ret: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +int itg3200_buffer_configure(struct iio_dev *indio_dev) +{ + return iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + itg3200_trigger_handler, NULL); +} + +void itg3200_buffer_unconfigure(struct iio_dev *indio_dev) +{ + iio_triggered_buffer_cleanup(indio_dev); +} + + +static int itg3200_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + int ret; + u8 msc; + + ret = itg3200_read_reg_8(indio_dev, ITG3200_REG_IRQ_CONFIG, &msc); + if (ret) + goto error_ret; + + if (state) + msc |= ITG3200_IRQ_DATA_RDY_ENABLE; + else + msc &= ~ITG3200_IRQ_DATA_RDY_ENABLE; + + ret = itg3200_write_reg_8(indio_dev, ITG3200_REG_IRQ_CONFIG, msc); + if (ret) + goto error_ret; + +error_ret: + return ret; + +} + +static const struct iio_trigger_ops itg3200_trigger_ops = { + .set_trigger_state = &itg3200_data_rdy_trigger_set_state, +}; + +int itg3200_probe_trigger(struct iio_dev *indio_dev) +{ + int ret; + struct itg3200 *st = iio_priv(indio_dev); + + st->trig = iio_trigger_alloc(&st->i2c->dev, "%s-dev%d", indio_dev->name, + iio_device_id(indio_dev)); + if (!st->trig) + return -ENOMEM; + + ret = request_irq(st->i2c->irq, &iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_RISING | IRQF_NO_THREAD, + "itg3200_data_rdy", st->trig); + if (ret) + goto error_free_trig; + + + st->trig->ops = &itg3200_trigger_ops; + iio_trigger_set_drvdata(st->trig, indio_dev); + ret = iio_trigger_register(st->trig); + if (ret) + goto error_free_irq; + + /* select default trigger */ + indio_dev->trig = iio_trigger_get(st->trig); + + return 0; + +error_free_irq: + free_irq(st->i2c->irq, st->trig); +error_free_trig: + iio_trigger_free(st->trig); + return ret; +} + +void itg3200_remove_trigger(struct iio_dev *indio_dev) +{ + struct itg3200 *st = iio_priv(indio_dev); + + iio_trigger_unregister(st->trig); + free_irq(st->i2c->irq, st->trig); + iio_trigger_free(st->trig); +} diff --git a/drivers/iio/gyro/itg3200_core.c b/drivers/iio/gyro/itg3200_core.c new file mode 100644 index 000000000..6b13d54d9 --- /dev/null +++ b/drivers/iio/gyro/itg3200_core.c @@ -0,0 +1,418 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * itg3200_core.c -- support InvenSense ITG3200 + * Digital 3-Axis Gyroscope driver + * + * Copyright (c) 2011 Christian Strobel + * Copyright (c) 2011 Manuel Stahl + * Copyright (c) 2012 Thorsten Nowak + * + * TODO: + * - Support digital low pass filter + * - Support power management + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + + +int itg3200_write_reg_8(struct iio_dev *indio_dev, + u8 reg_address, u8 val) +{ + struct itg3200 *st = iio_priv(indio_dev); + + return i2c_smbus_write_byte_data(st->i2c, 0x80 | reg_address, val); +} + +int itg3200_read_reg_8(struct iio_dev *indio_dev, + u8 reg_address, u8 *val) +{ + struct itg3200 *st = iio_priv(indio_dev); + int ret; + + ret = i2c_smbus_read_byte_data(st->i2c, reg_address); + if (ret < 0) + return ret; + *val = ret; + return 0; +} + +static int itg3200_read_reg_s16(struct iio_dev *indio_dev, u8 lower_reg_address, + int *val) +{ + struct itg3200 *st = iio_priv(indio_dev); + struct i2c_client *client = st->i2c; + int ret; + s16 out; + + struct i2c_msg msg[2] = { + { + .addr = client->addr, + .flags = client->flags, + .len = 1, + .buf = (char *)&lower_reg_address, + }, + { + .addr = client->addr, + .flags = client->flags | I2C_M_RD, + .len = 2, + .buf = (char *)&out, + }, + }; + + lower_reg_address |= 0x80; + ret = i2c_transfer(client->adapter, msg, 2); + be16_to_cpus(&out); + *val = out; + + return (ret == 2) ? 0 : ret; +} + +static int itg3200_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long info) +{ + int ret = 0; + u8 reg; + u8 regval; + + switch (info) { + case IIO_CHAN_INFO_RAW: + reg = (u8)chan->address; + ret = itg3200_read_reg_s16(indio_dev, reg, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + if (chan->type == IIO_TEMP) + *val2 = 1000000000/280; + else + *val2 = 1214142; /* (1 / 14,375) * (PI / 180) */ + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + /* Only the temperature channel has an offset */ + *val = 23000; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = itg3200_read_reg_8(indio_dev, ITG3200_REG_DLPF, ®val); + if (ret) + return ret; + + *val = (regval & ITG3200_DLPF_CFG_MASK) ? 1000 : 8000; + + ret = itg3200_read_reg_8(indio_dev, + ITG3200_REG_SAMPLE_RATE_DIV, + ®val); + if (ret) + return ret; + + *val /= regval + 1; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int itg3200_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct itg3200 *st = iio_priv(indio_dev); + int ret; + u8 t; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (val == 0 || val2 != 0) + return -EINVAL; + + mutex_lock(&st->lock); + + ret = itg3200_read_reg_8(indio_dev, ITG3200_REG_DLPF, &t); + if (ret) { + mutex_unlock(&st->lock); + return ret; + } + t = ((t & ITG3200_DLPF_CFG_MASK) ? 1000u : 8000u) / val - 1; + + ret = itg3200_write_reg_8(indio_dev, + ITG3200_REG_SAMPLE_RATE_DIV, + t); + + mutex_unlock(&st->lock); + return ret; + + default: + return -EINVAL; + } +} + +/* + * Reset device and internal registers to the power-up-default settings + * Use the gyro clock as reference, as suggested by the datasheet + */ +static int itg3200_reset(struct iio_dev *indio_dev) +{ + struct itg3200 *st = iio_priv(indio_dev); + int ret; + + dev_dbg(&st->i2c->dev, "reset device"); + + ret = itg3200_write_reg_8(indio_dev, + ITG3200_REG_POWER_MANAGEMENT, + ITG3200_RESET); + if (ret) { + dev_err(&st->i2c->dev, "error resetting device"); + goto error_ret; + } + + /* Wait for PLL (1ms according to datasheet) */ + udelay(1500); + + ret = itg3200_write_reg_8(indio_dev, + ITG3200_REG_IRQ_CONFIG, + ITG3200_IRQ_ACTIVE_HIGH | + ITG3200_IRQ_PUSH_PULL | + ITG3200_IRQ_LATCH_50US_PULSE | + ITG3200_IRQ_LATCH_CLEAR_ANY); + + if (ret) + dev_err(&st->i2c->dev, "error init device"); + +error_ret: + return ret; +} + +/* itg3200_enable_full_scale() - Disables the digital low pass filter */ +static int itg3200_enable_full_scale(struct iio_dev *indio_dev) +{ + u8 val; + int ret; + + ret = itg3200_read_reg_8(indio_dev, ITG3200_REG_DLPF, &val); + if (ret) + goto err_ret; + + val |= ITG3200_DLPF_FS_SEL_2000; + return itg3200_write_reg_8(indio_dev, ITG3200_REG_DLPF, val); + +err_ret: + return ret; +} + +static int itg3200_initial_setup(struct iio_dev *indio_dev) +{ + struct itg3200 *st = iio_priv(indio_dev); + int ret; + u8 val; + + ret = itg3200_reset(indio_dev); + if (ret) + goto err_ret; + + ret = itg3200_read_reg_8(indio_dev, ITG3200_REG_ADDRESS, &val); + if (ret) + goto err_ret; + + if (((val >> 1) & 0x3f) != 0x34) { + dev_err(&st->i2c->dev, "invalid reg value 0x%02x", val); + ret = -ENXIO; + goto err_ret; + } + + ret = itg3200_enable_full_scale(indio_dev); +err_ret: + return ret; +} + +static const struct iio_mount_matrix * +itg3200_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct itg3200 *data = iio_priv(indio_dev); + + return &data->orientation; +} + +static const struct iio_chan_spec_ext_info itg3200_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, itg3200_get_mount_matrix), + { } +}; + +#define ITG3200_ST \ + { .sign = 's', .realbits = 16, .storagebits = 16, .endianness = IIO_BE } + +#define ITG3200_GYRO_CHAN(_mod) { \ + .type = IIO_ANGL_VEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## _mod, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = ITG3200_REG_GYRO_ ## _mod ## OUT_H, \ + .scan_index = ITG3200_SCAN_GYRO_ ## _mod, \ + .scan_type = ITG3200_ST, \ + .ext_info = itg3200_ext_info, \ +} + +static const struct iio_chan_spec itg3200_channels[] = { + { + .type = IIO_TEMP, + .channel2 = IIO_NO_MOD, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .address = ITG3200_REG_TEMP_OUT_H, + .scan_index = ITG3200_SCAN_TEMP, + .scan_type = ITG3200_ST, + }, + ITG3200_GYRO_CHAN(X), + ITG3200_GYRO_CHAN(Y), + ITG3200_GYRO_CHAN(Z), + IIO_CHAN_SOFT_TIMESTAMP(ITG3200_SCAN_ELEMENTS), +}; + +static const struct iio_info itg3200_info = { + .read_raw = &itg3200_read_raw, + .write_raw = &itg3200_write_raw, +}; + +static const unsigned long itg3200_available_scan_masks[] = { 0xffffffff, 0x0 }; + +static int itg3200_probe(struct i2c_client *client) +{ + int ret; + struct itg3200 *st; + struct iio_dev *indio_dev; + + dev_dbg(&client->dev, "probe I2C dev with IRQ %i", client->irq); + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + ret = iio_read_mount_matrix(&client->dev, &st->orientation); + if (ret) + return ret; + + i2c_set_clientdata(client, indio_dev); + st->i2c = client; + + indio_dev->name = client->dev.driver->name; + indio_dev->channels = itg3200_channels; + indio_dev->num_channels = ARRAY_SIZE(itg3200_channels); + indio_dev->available_scan_masks = itg3200_available_scan_masks; + indio_dev->info = &itg3200_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = itg3200_buffer_configure(indio_dev); + if (ret) + return ret; + + if (client->irq) { + ret = itg3200_probe_trigger(indio_dev); + if (ret) + goto error_unconfigure_buffer; + } + + ret = itg3200_initial_setup(indio_dev); + if (ret) + goto error_remove_trigger; + + mutex_init(&st->lock); + + ret = iio_device_register(indio_dev); + if (ret) + goto error_remove_trigger; + + return 0; + +error_remove_trigger: + if (client->irq) + itg3200_remove_trigger(indio_dev); +error_unconfigure_buffer: + itg3200_buffer_unconfigure(indio_dev); + return ret; +} + +static void itg3200_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + + if (client->irq) + itg3200_remove_trigger(indio_dev); + + itg3200_buffer_unconfigure(indio_dev); +} + +static int itg3200_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct itg3200 *st = iio_priv(indio_dev); + + dev_dbg(&st->i2c->dev, "suspend device"); + + return itg3200_write_reg_8(indio_dev, ITG3200_REG_POWER_MANAGEMENT, + ITG3200_SLEEP); +} + +static int itg3200_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + return itg3200_initial_setup(indio_dev); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(itg3200_pm_ops, itg3200_suspend, + itg3200_resume); + +static const struct i2c_device_id itg3200_id[] = { + { .name = "itg3200" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, itg3200_id); + +static const struct of_device_id itg3200_of_match[] = { + { .compatible = "invensense,itg3200" }, + { } +}; +MODULE_DEVICE_TABLE(of, itg3200_of_match); + +static struct i2c_driver itg3200_driver = { + .driver = { + .name = "itg3200", + .of_match_table = itg3200_of_match, + .pm = pm_sleep_ptr(&itg3200_pm_ops), + }, + .id_table = itg3200_id, + .probe = itg3200_probe, + .remove = itg3200_remove, +}; + +module_i2c_driver(itg3200_driver); + +MODULE_AUTHOR("Christian Strobel "); +MODULE_DESCRIPTION("ITG3200 Gyroscope I2C driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/gyro/mpu3050-core.c b/drivers/iio/gyro/mpu3050-core.c new file mode 100644 index 000000000..07c0f9011 --- /dev/null +++ b/drivers/iio/gyro/mpu3050-core.c @@ -0,0 +1,1301 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MPU3050 gyroscope driver + * + * Copyright (C) 2016 Linaro Ltd. + * Author: Linus Walleij + * + * Based on the input subsystem driver, Copyright (C) 2011 Wistron Co.Ltd + * Joseph Lai and trimmed down by + * Alan Cox in turn based on bma023.c. + * Device behaviour based on a misc driver posted by Nathan Royer in 2011. + * + * TODO: add support for setting up the low pass 3dB frequency. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mpu3050.h" + +#define MPU3050_CHIP_ID 0x68 +#define MPU3050_CHIP_ID_MASK 0x7E + +/* + * Register map: anything suffixed *_H is a big-endian high byte and always + * followed by the corresponding low byte (*_L) even though these are not + * explicitly included in the register definitions. + */ +#define MPU3050_CHIP_ID_REG 0x00 +#define MPU3050_PRODUCT_ID_REG 0x01 +#define MPU3050_XG_OFFS_TC 0x05 +#define MPU3050_YG_OFFS_TC 0x08 +#define MPU3050_ZG_OFFS_TC 0x0B +#define MPU3050_X_OFFS_USR_H 0x0C +#define MPU3050_Y_OFFS_USR_H 0x0E +#define MPU3050_Z_OFFS_USR_H 0x10 +#define MPU3050_FIFO_EN 0x12 +#define MPU3050_AUX_VDDIO 0x13 +#define MPU3050_SLV_ADDR 0x14 +#define MPU3050_SMPLRT_DIV 0x15 +#define MPU3050_DLPF_FS_SYNC 0x16 +#define MPU3050_INT_CFG 0x17 +#define MPU3050_AUX_ADDR 0x18 +#define MPU3050_INT_STATUS 0x1A +#define MPU3050_TEMP_H 0x1B +#define MPU3050_XOUT_H 0x1D +#define MPU3050_YOUT_H 0x1F +#define MPU3050_ZOUT_H 0x21 +#define MPU3050_DMP_CFG1 0x35 +#define MPU3050_DMP_CFG2 0x36 +#define MPU3050_BANK_SEL 0x37 +#define MPU3050_MEM_START_ADDR 0x38 +#define MPU3050_MEM_R_W 0x39 +#define MPU3050_FIFO_COUNT_H 0x3A +#define MPU3050_FIFO_R 0x3C +#define MPU3050_USR_CTRL 0x3D +#define MPU3050_PWR_MGM 0x3E + +/* MPU memory bank read options */ +#define MPU3050_MEM_PRFTCH BIT(5) +#define MPU3050_MEM_USER_BANK BIT(4) +/* Bits 8-11 select memory bank */ +#define MPU3050_MEM_RAM_BANK_0 0 +#define MPU3050_MEM_RAM_BANK_1 1 +#define MPU3050_MEM_RAM_BANK_2 2 +#define MPU3050_MEM_RAM_BANK_3 3 +#define MPU3050_MEM_OTP_BANK_0 4 + +#define MPU3050_AXIS_REGS(axis) (MPU3050_XOUT_H + (axis * 2)) + +/* Register bits */ + +/* FIFO Enable */ +#define MPU3050_FIFO_EN_FOOTER BIT(0) +#define MPU3050_FIFO_EN_AUX_ZOUT BIT(1) +#define MPU3050_FIFO_EN_AUX_YOUT BIT(2) +#define MPU3050_FIFO_EN_AUX_XOUT BIT(3) +#define MPU3050_FIFO_EN_GYRO_ZOUT BIT(4) +#define MPU3050_FIFO_EN_GYRO_YOUT BIT(5) +#define MPU3050_FIFO_EN_GYRO_XOUT BIT(6) +#define MPU3050_FIFO_EN_TEMP_OUT BIT(7) + +/* + * Digital Low Pass filter (DLPF) + * Full Scale (FS) + * and Synchronization + */ +#define MPU3050_EXT_SYNC_NONE 0x00 +#define MPU3050_EXT_SYNC_TEMP 0x20 +#define MPU3050_EXT_SYNC_GYROX 0x40 +#define MPU3050_EXT_SYNC_GYROY 0x60 +#define MPU3050_EXT_SYNC_GYROZ 0x80 +#define MPU3050_EXT_SYNC_ACCELX 0xA0 +#define MPU3050_EXT_SYNC_ACCELY 0xC0 +#define MPU3050_EXT_SYNC_ACCELZ 0xE0 +#define MPU3050_EXT_SYNC_MASK 0xE0 +#define MPU3050_EXT_SYNC_SHIFT 5 + +#define MPU3050_FS_250DPS 0x00 +#define MPU3050_FS_500DPS 0x08 +#define MPU3050_FS_1000DPS 0x10 +#define MPU3050_FS_2000DPS 0x18 +#define MPU3050_FS_MASK 0x18 +#define MPU3050_FS_SHIFT 3 + +#define MPU3050_DLPF_CFG_256HZ_NOLPF2 0x00 +#define MPU3050_DLPF_CFG_188HZ 0x01 +#define MPU3050_DLPF_CFG_98HZ 0x02 +#define MPU3050_DLPF_CFG_42HZ 0x03 +#define MPU3050_DLPF_CFG_20HZ 0x04 +#define MPU3050_DLPF_CFG_10HZ 0x05 +#define MPU3050_DLPF_CFG_5HZ 0x06 +#define MPU3050_DLPF_CFG_2100HZ_NOLPF 0x07 +#define MPU3050_DLPF_CFG_MASK 0x07 +#define MPU3050_DLPF_CFG_SHIFT 0 + +/* Interrupt config */ +#define MPU3050_INT_RAW_RDY_EN BIT(0) +#define MPU3050_INT_DMP_DONE_EN BIT(1) +#define MPU3050_INT_MPU_RDY_EN BIT(2) +#define MPU3050_INT_ANYRD_2CLEAR BIT(4) +#define MPU3050_INT_LATCH_EN BIT(5) +#define MPU3050_INT_OPEN BIT(6) +#define MPU3050_INT_ACTL BIT(7) +/* Interrupt status */ +#define MPU3050_INT_STATUS_RAW_RDY BIT(0) +#define MPU3050_INT_STATUS_DMP_DONE BIT(1) +#define MPU3050_INT_STATUS_MPU_RDY BIT(2) +#define MPU3050_INT_STATUS_FIFO_OVFLW BIT(7) +/* USR_CTRL */ +#define MPU3050_USR_CTRL_FIFO_EN BIT(6) +#define MPU3050_USR_CTRL_AUX_IF_EN BIT(5) +#define MPU3050_USR_CTRL_AUX_IF_RST BIT(3) +#define MPU3050_USR_CTRL_FIFO_RST BIT(1) +#define MPU3050_USR_CTRL_GYRO_RST BIT(0) +/* PWR_MGM */ +#define MPU3050_PWR_MGM_PLL_X 0x01 +#define MPU3050_PWR_MGM_PLL_Y 0x02 +#define MPU3050_PWR_MGM_PLL_Z 0x03 +#define MPU3050_PWR_MGM_CLKSEL_MASK 0x07 +#define MPU3050_PWR_MGM_STBY_ZG BIT(3) +#define MPU3050_PWR_MGM_STBY_YG BIT(4) +#define MPU3050_PWR_MGM_STBY_XG BIT(5) +#define MPU3050_PWR_MGM_SLEEP BIT(6) +#define MPU3050_PWR_MGM_RESET BIT(7) +#define MPU3050_PWR_MGM_MASK 0xff + +/* + * Fullscale precision is (for finest precision) +/- 250 deg/s, so the full + * scale is actually 500 deg/s. All 16 bits are then used to cover this scale, + * in two's complement. + */ +static unsigned int mpu3050_fs_precision[] = { + IIO_DEGREE_TO_RAD(250), + IIO_DEGREE_TO_RAD(500), + IIO_DEGREE_TO_RAD(1000), + IIO_DEGREE_TO_RAD(2000) +}; + +/* + * Regulator names + */ +static const char mpu3050_reg_vdd[] = "vdd"; +static const char mpu3050_reg_vlogic[] = "vlogic"; + +static unsigned int mpu3050_get_freq(struct mpu3050 *mpu3050) +{ + unsigned int freq; + + if (mpu3050->lpf == MPU3050_DLPF_CFG_256HZ_NOLPF2) + freq = 8000; + else + freq = 1000; + freq /= (mpu3050->divisor + 1); + + return freq; +} + +static int mpu3050_start_sampling(struct mpu3050 *mpu3050) +{ + __be16 raw_val[3]; + int ret; + int i; + + /* Reset */ + ret = regmap_set_bits(mpu3050->map, MPU3050_PWR_MGM, + MPU3050_PWR_MGM_RESET); + if (ret) + return ret; + + /* Turn on the Z-axis PLL */ + ret = regmap_update_bits(mpu3050->map, MPU3050_PWR_MGM, + MPU3050_PWR_MGM_CLKSEL_MASK, + MPU3050_PWR_MGM_PLL_Z); + if (ret) + return ret; + + /* Write calibration offset registers */ + for (i = 0; i < 3; i++) + raw_val[i] = cpu_to_be16(mpu3050->calibration[i]); + + ret = regmap_bulk_write(mpu3050->map, MPU3050_X_OFFS_USR_H, raw_val, + sizeof(raw_val)); + if (ret) + return ret; + + /* Set low pass filter (sample rate), sync and full scale */ + ret = regmap_write(mpu3050->map, MPU3050_DLPF_FS_SYNC, + MPU3050_EXT_SYNC_NONE << MPU3050_EXT_SYNC_SHIFT | + mpu3050->fullscale << MPU3050_FS_SHIFT | + mpu3050->lpf << MPU3050_DLPF_CFG_SHIFT); + if (ret) + return ret; + + /* Set up sampling frequency */ + ret = regmap_write(mpu3050->map, MPU3050_SMPLRT_DIV, mpu3050->divisor); + if (ret) + return ret; + + /* + * Max 50 ms start-up time after setting DLPF_FS_SYNC + * according to the data sheet, then wait for the next sample + * at this frequency T = 1000/f ms. + */ + msleep(50 + 1000 / mpu3050_get_freq(mpu3050)); + + return 0; +} + +static int mpu3050_set_8khz_samplerate(struct mpu3050 *mpu3050) +{ + int ret; + u8 divisor; + enum mpu3050_lpf lpf; + + lpf = mpu3050->lpf; + divisor = mpu3050->divisor; + + mpu3050->lpf = LPF_256_HZ_NOLPF; /* 8 kHz base frequency */ + mpu3050->divisor = 0; /* Divide by 1 */ + ret = mpu3050_start_sampling(mpu3050); + + mpu3050->lpf = lpf; + mpu3050->divisor = divisor; + + return ret; +} + +static int mpu3050_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct mpu3050 *mpu3050 = iio_priv(indio_dev); + int ret; + __be16 raw_val; + + switch (mask) { + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + /* + * The temperature scaling is (x+23000)/280 Celsius + * for the "best fit straight line" temperature range + * of -30C..85C. The 23000 includes room temperature + * offset of +35C, 280 is the precision scale and x is + * the 16-bit signed integer reported by hardware. + * + * Temperature value itself represents temperature of + * the sensor die. + */ + *val = 23000; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = mpu3050->calibration[chan->scan_index-1]; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + *val = mpu3050_get_freq(mpu3050); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + /* Millidegrees, see about temperature scaling above */ + *val = 1000; + *val2 = 280; + return IIO_VAL_FRACTIONAL; + case IIO_ANGL_VEL: + /* + * Convert to the corresponding full scale in + * radians. All 16 bits are used with sign to + * span the available scale: to account for the one + * missing value if we multiply by 1/S16_MAX, instead + * multiply with 2/U16_MAX. + */ + *val = mpu3050_fs_precision[mpu3050->fullscale] * 2; + *val2 = U16_MAX; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_RAW: + /* Resume device */ + ret = pm_runtime_resume_and_get(mpu3050->dev); + if (ret) + return ret; + mutex_lock(&mpu3050->lock); + + ret = mpu3050_set_8khz_samplerate(mpu3050); + if (ret) + goto out_read_raw_unlock; + + switch (chan->type) { + case IIO_TEMP: + ret = regmap_bulk_read(mpu3050->map, MPU3050_TEMP_H, + &raw_val, sizeof(raw_val)); + if (ret) { + dev_err(mpu3050->dev, + "error reading temperature\n"); + goto out_read_raw_unlock; + } + + *val = (s16)be16_to_cpu(raw_val); + ret = IIO_VAL_INT; + + goto out_read_raw_unlock; + case IIO_ANGL_VEL: + ret = regmap_bulk_read(mpu3050->map, + MPU3050_AXIS_REGS(chan->scan_index-1), + &raw_val, + sizeof(raw_val)); + if (ret) { + dev_err(mpu3050->dev, + "error reading axis data\n"); + goto out_read_raw_unlock; + } + + *val = (s16)be16_to_cpu(raw_val); + ret = IIO_VAL_INT; + + goto out_read_raw_unlock; + default: + ret = -EINVAL; + goto out_read_raw_unlock; + } + default: + break; + } + + return -EINVAL; + +out_read_raw_unlock: + mutex_unlock(&mpu3050->lock); + pm_runtime_put_autosuspend(mpu3050->dev); + + return ret; +} + +static int mpu3050_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int val, int val2, long mask) +{ + struct mpu3050 *mpu3050 = iio_priv(indio_dev); + /* + * Couldn't figure out a way to precalculate these at compile time. + */ + unsigned int fs250 = + DIV_ROUND_CLOSEST(mpu3050_fs_precision[0] * 1000000 * 2, + U16_MAX); + unsigned int fs500 = + DIV_ROUND_CLOSEST(mpu3050_fs_precision[1] * 1000000 * 2, + U16_MAX); + unsigned int fs1000 = + DIV_ROUND_CLOSEST(mpu3050_fs_precision[2] * 1000000 * 2, + U16_MAX); + unsigned int fs2000 = + DIV_ROUND_CLOSEST(mpu3050_fs_precision[3] * 1000000 * 2, + U16_MAX); + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + mpu3050->calibration[chan->scan_index-1] = val; + return 0; + case IIO_CHAN_INFO_SAMP_FREQ: + /* + * The max samplerate is 8000 Hz, the minimum + * 1000 / 256 ~= 4 Hz + */ + if (val < 4 || val > 8000) + return -EINVAL; + + /* + * Above 1000 Hz we must turn off the digital low pass filter + * so we get a base frequency of 8kHz to the divider + */ + if (val > 1000) { + mpu3050->lpf = LPF_256_HZ_NOLPF; + mpu3050->divisor = DIV_ROUND_CLOSEST(8000, val) - 1; + return 0; + } + + mpu3050->lpf = LPF_188_HZ; + mpu3050->divisor = DIV_ROUND_CLOSEST(1000, val) - 1; + return 0; + case IIO_CHAN_INFO_SCALE: + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + /* + * We support +/-250, +/-500, +/-1000 and +/2000 deg/s + * which means we need to round to the closest radians + * which will be roughly +/-4.3, +/-8.7, +/-17.5, +/-35 + * rad/s. The scale is then for the 16 bits used to cover + * it 2/(2^16) of that. + */ + + /* Just too large, set the max range */ + if (val != 0) { + mpu3050->fullscale = FS_2000_DPS; + return 0; + } + + /* + * Now we're dealing with fractions below zero in millirad/s + * do some integer interpolation and match with the closest + * fullscale in the table. + */ + if (val2 <= fs250 || + val2 < ((fs500 + fs250) / 2)) + mpu3050->fullscale = FS_250_DPS; + else if (val2 <= fs500 || + val2 < ((fs1000 + fs500) / 2)) + mpu3050->fullscale = FS_500_DPS; + else if (val2 <= fs1000 || + val2 < ((fs2000 + fs1000) / 2)) + mpu3050->fullscale = FS_1000_DPS; + else + /* Catch-all */ + mpu3050->fullscale = FS_2000_DPS; + return 0; + default: + break; + } + + return -EINVAL; +} + +static irqreturn_t mpu3050_trigger_handler(int irq, void *p) +{ + const struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct mpu3050 *mpu3050 = iio_priv(indio_dev); + int ret; + struct { + __be16 chans[4]; + aligned_s64 timestamp; + } scan; + s64 timestamp; + unsigned int datums_from_fifo = 0; + + /* + * If we're using the hardware trigger, get the precise timestamp from + * the top half of the threaded IRQ handler. Otherwise get the + * timestamp here so it will be close in time to the actual values + * read from the registers. + */ + if (iio_trigger_using_own(indio_dev)) + timestamp = mpu3050->hw_timestamp; + else + timestamp = iio_get_time_ns(indio_dev); + + mutex_lock(&mpu3050->lock); + + /* Using the hardware IRQ trigger? Check the buffer then. */ + if (mpu3050->hw_irq_trigger) { + __be16 raw_fifocnt; + u16 fifocnt; + /* X, Y, Z + temperature */ + unsigned int bytes_per_datum = 8; + bool fifo_overflow = false; + + ret = regmap_bulk_read(mpu3050->map, + MPU3050_FIFO_COUNT_H, + &raw_fifocnt, + sizeof(raw_fifocnt)); + if (ret) + goto out_trigger_unlock; + fifocnt = be16_to_cpu(raw_fifocnt); + + if (fifocnt == 512) { + dev_info(mpu3050->dev, + "FIFO overflow! Emptying and resetting FIFO\n"); + fifo_overflow = true; + /* Reset and enable the FIFO */ + ret = regmap_set_bits(mpu3050->map, MPU3050_USR_CTRL, + MPU3050_USR_CTRL_FIFO_EN | + MPU3050_USR_CTRL_FIFO_RST); + if (ret) { + dev_info(mpu3050->dev, "error resetting FIFO\n"); + goto out_trigger_unlock; + } + mpu3050->pending_fifo_footer = false; + } + + if (fifocnt) + dev_dbg(mpu3050->dev, + "%d bytes in the FIFO\n", + fifocnt); + + while (!fifo_overflow && fifocnt > bytes_per_datum) { + unsigned int toread; + unsigned int offset; + __be16 fifo_values[5]; + + /* + * If there is a FIFO footer in the pipe, first clear + * that out. This follows the complex algorithm in the + * datasheet that states that you may never leave the + * FIFO empty after the first reading: you have to + * always leave two footer bytes in it. The footer is + * in practice just two zero bytes. + */ + if (mpu3050->pending_fifo_footer) { + toread = bytes_per_datum + 2; + offset = 0; + } else { + toread = bytes_per_datum; + offset = 1; + /* Put in some dummy value */ + fifo_values[0] = cpu_to_be16(0xAAAA); + } + + ret = regmap_bulk_read(mpu3050->map, + MPU3050_FIFO_R, + &fifo_values[offset], + toread); + if (ret) + goto out_trigger_unlock; + + dev_dbg(mpu3050->dev, + "%04x %04x %04x %04x %04x\n", + fifo_values[0], + fifo_values[1], + fifo_values[2], + fifo_values[3], + fifo_values[4]); + + /* Index past the footer (fifo_values[0]) and push */ + iio_push_to_buffers_with_ts_unaligned(indio_dev, + &fifo_values[1], + sizeof(__be16) * 4, + timestamp); + + fifocnt -= toread; + datums_from_fifo++; + mpu3050->pending_fifo_footer = true; + + /* + * If we're emptying the FIFO, just make sure to + * check if something new appeared. + */ + if (fifocnt < bytes_per_datum) { + ret = regmap_bulk_read(mpu3050->map, + MPU3050_FIFO_COUNT_H, + &raw_fifocnt, + sizeof(raw_fifocnt)); + if (ret) + goto out_trigger_unlock; + fifocnt = be16_to_cpu(raw_fifocnt); + } + + if (fifocnt < bytes_per_datum) + dev_dbg(mpu3050->dev, + "%d bytes left in the FIFO\n", + fifocnt); + + /* + * At this point, the timestamp that triggered the + * hardware interrupt is no longer valid for what + * we are reading (the interrupt likely fired for + * the value on the top of the FIFO), so set the + * timestamp to zero and let userspace deal with it. + */ + timestamp = 0; + } + } + + /* + * If we picked some datums from the FIFO that's enough, else + * fall through and just read from the current value registers. + * This happens in two cases: + * + * - We are using some other trigger (external, like an HRTimer) + * than the sensor's own sample generator. In this case the + * sensor is just set to the max sampling frequency and we give + * the trigger a copy of the latest value every time we get here. + * + * - The hardware trigger is active but unused and we actually use + * another trigger which calls here with a frequency higher + * than what the device provides data. We will then just read + * duplicate values directly from the hardware registers. + */ + if (datums_from_fifo) { + dev_dbg(mpu3050->dev, + "read %d datums from the FIFO\n", + datums_from_fifo); + goto out_trigger_unlock; + } + + ret = regmap_bulk_read(mpu3050->map, MPU3050_TEMP_H, scan.chans, + sizeof(scan.chans)); + if (ret) { + dev_err(mpu3050->dev, + "error reading axis data\n"); + goto out_trigger_unlock; + } + + iio_push_to_buffers_with_timestamp(indio_dev, &scan, timestamp); + +out_trigger_unlock: + mutex_unlock(&mpu3050->lock); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int mpu3050_buffer_preenable(struct iio_dev *indio_dev) +{ + struct mpu3050 *mpu3050 = iio_priv(indio_dev); + int ret; + + ret = pm_runtime_resume_and_get(mpu3050->dev); + if (ret) + return ret; + + /* Unless we have OUR trigger active, run at full speed */ + if (!mpu3050->hw_irq_trigger) { + ret = mpu3050_set_8khz_samplerate(mpu3050); + if (ret) + pm_runtime_put_autosuspend(mpu3050->dev); + } + + return ret; +} + +static int mpu3050_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct mpu3050 *mpu3050 = iio_priv(indio_dev); + + pm_runtime_put_autosuspend(mpu3050->dev); + + return 0; +} + +static const struct iio_buffer_setup_ops mpu3050_buffer_setup_ops = { + .preenable = mpu3050_buffer_preenable, + .postdisable = mpu3050_buffer_postdisable, +}; + +static const struct iio_mount_matrix * +mpu3050_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct mpu3050 *mpu3050 = iio_priv(indio_dev); + + return &mpu3050->orientation; +} + +static const struct iio_chan_spec_ext_info mpu3050_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_TYPE, mpu3050_get_mount_matrix), + { } +}; + +#define MPU3050_AXIS_CHANNEL(axis, index) \ + { \ + .type = IIO_ANGL_VEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\ + .ext_info = mpu3050_ext_info, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + } + +static const struct iio_chan_spec mpu3050_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, + }, + }, + MPU3050_AXIS_CHANNEL(X, 1), + MPU3050_AXIS_CHANNEL(Y, 2), + MPU3050_AXIS_CHANNEL(Z, 3), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +/* Four channels apart from timestamp, scan mask = 0x0f */ +static const unsigned long mpu3050_scan_masks[] = { 0xf, 0 }; + +/* + * These are just the hardcoded factors resulting from the more elaborate + * calculations done with fractions in the scale raw get/set functions. + */ +static IIO_CONST_ATTR(anglevel_scale_available, + "0.000122070 " + "0.000274658 " + "0.000518798 " + "0.001068115"); + +static struct attribute *mpu3050_attributes[] = { + &iio_const_attr_anglevel_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group mpu3050_attribute_group = { + .attrs = mpu3050_attributes, +}; + +static const struct iio_info mpu3050_info = { + .read_raw = mpu3050_read_raw, + .write_raw = mpu3050_write_raw, + .attrs = &mpu3050_attribute_group, +}; + +/** + * mpu3050_read_mem() - read MPU-3050 internal memory + * @mpu3050: device to read from + * @bank: target bank + * @addr: target address + * @len: number of bytes + * @buf: the buffer to store the read bytes in + */ +static int mpu3050_read_mem(struct mpu3050 *mpu3050, + u8 bank, + u8 addr, + u8 len, + u8 *buf) +{ + int ret; + + ret = regmap_write(mpu3050->map, + MPU3050_BANK_SEL, + bank); + if (ret) + return ret; + + ret = regmap_write(mpu3050->map, + MPU3050_MEM_START_ADDR, + addr); + if (ret) + return ret; + + return regmap_bulk_read(mpu3050->map, + MPU3050_MEM_R_W, + buf, + len); +} + +static int mpu3050_hw_init(struct mpu3050 *mpu3050) +{ + int ret; + __le64 otp_le; + u64 otp; + + /* Reset */ + ret = regmap_set_bits(mpu3050->map, MPU3050_PWR_MGM, + MPU3050_PWR_MGM_RESET); + if (ret) + return ret; + + /* Turn on the PLL */ + ret = regmap_update_bits(mpu3050->map, + MPU3050_PWR_MGM, + MPU3050_PWR_MGM_CLKSEL_MASK, + MPU3050_PWR_MGM_PLL_Z); + if (ret) + return ret; + + /* Disable IRQs */ + ret = regmap_write(mpu3050->map, + MPU3050_INT_CFG, + 0); + if (ret) + return ret; + + /* Read out the 8 bytes of OTP (one-time-programmable) memory */ + ret = mpu3050_read_mem(mpu3050, + (MPU3050_MEM_PRFTCH | + MPU3050_MEM_USER_BANK | + MPU3050_MEM_OTP_BANK_0), + 0, + sizeof(otp_le), + (u8 *)&otp_le); + if (ret) + return ret; + + /* This is device-unique data so it goes into the entropy pool */ + add_device_randomness(&otp_le, sizeof(otp_le)); + + otp = le64_to_cpu(otp_le); + + dev_info(mpu3050->dev, + "die ID: %04llX, wafer ID: %02llX, A lot ID: %04llX, " + "W lot ID: %03llX, WP ID: %01llX, rev ID: %02llX\n", + /* Die ID, bits 0-12 */ + FIELD_GET(GENMASK_ULL(12, 0), otp), + /* Wafer ID, bits 13-17 */ + FIELD_GET(GENMASK_ULL(17, 13), otp), + /* A lot ID, bits 18-33 */ + FIELD_GET(GENMASK_ULL(33, 18), otp), + /* W lot ID, bits 34-45 */ + FIELD_GET(GENMASK_ULL(45, 34), otp), + /* WP ID, bits 47-49 */ + FIELD_GET(GENMASK_ULL(49, 47), otp), + /* rev ID, bits 50-55 */ + FIELD_GET(GENMASK_ULL(55, 50), otp)); + + return 0; +} + +static int mpu3050_power_up(struct mpu3050 *mpu3050) +{ + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(mpu3050->regs), mpu3050->regs); + if (ret) { + dev_err(mpu3050->dev, "cannot enable regulators\n"); + return ret; + } + /* + * 20-100 ms start-up time for register read/write according to + * the datasheet, be on the safe side and wait 200 ms. + */ + msleep(200); + + /* Take device out of sleep mode */ + ret = regmap_clear_bits(mpu3050->map, MPU3050_PWR_MGM, + MPU3050_PWR_MGM_SLEEP); + if (ret) { + regulator_bulk_disable(ARRAY_SIZE(mpu3050->regs), mpu3050->regs); + dev_err(mpu3050->dev, "error setting power mode\n"); + return ret; + } + usleep_range(10000, 20000); + + return 0; +} + +static int mpu3050_power_down(struct mpu3050 *mpu3050) +{ + int ret; + + /* + * Put MPU-3050 into sleep mode before cutting regulators. + * This is important, because we may not be the sole user + * of the regulator so the power may stay on after this, and + * then we would be wasting power unless we go to sleep mode + * first. + */ + ret = regmap_set_bits(mpu3050->map, MPU3050_PWR_MGM, + MPU3050_PWR_MGM_SLEEP); + if (ret) + dev_err(mpu3050->dev, "error putting to sleep\n"); + + ret = regulator_bulk_disable(ARRAY_SIZE(mpu3050->regs), mpu3050->regs); + if (ret) + dev_err(mpu3050->dev, "error disabling regulators\n"); + + return 0; +} + +static irqreturn_t mpu3050_irq_handler(int irq, void *p) +{ + struct iio_trigger *trig = p; + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct mpu3050 *mpu3050 = iio_priv(indio_dev); + + if (!mpu3050->hw_irq_trigger) + return IRQ_NONE; + + /* Get the time stamp as close in time as possible */ + mpu3050->hw_timestamp = iio_get_time_ns(indio_dev); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t mpu3050_irq_thread(int irq, void *p) +{ + struct iio_trigger *trig = p; + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct mpu3050 *mpu3050 = iio_priv(indio_dev); + unsigned int val; + int ret; + + /* ACK IRQ and check if it was from us */ + ret = regmap_read(mpu3050->map, MPU3050_INT_STATUS, &val); + if (ret) { + dev_err(mpu3050->dev, "error reading IRQ status\n"); + return IRQ_HANDLED; + } + if (!(val & MPU3050_INT_STATUS_RAW_RDY)) + return IRQ_NONE; + + iio_trigger_poll_nested(p); + + return IRQ_HANDLED; +} + +/** + * mpu3050_drdy_trigger_set_state() - set data ready interrupt state + * @trig: trigger instance + * @enable: true if trigger should be enabled, false to disable + */ +static int mpu3050_drdy_trigger_set_state(struct iio_trigger *trig, + bool enable) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct mpu3050 *mpu3050 = iio_priv(indio_dev); + unsigned int val; + int ret; + + /* Disabling trigger: disable interrupt and return */ + if (!enable) { + /* Disable all interrupts */ + ret = regmap_write(mpu3050->map, + MPU3050_INT_CFG, + 0); + if (ret) + dev_err(mpu3050->dev, "error disabling IRQ\n"); + + /* Clear IRQ flag */ + ret = regmap_read(mpu3050->map, MPU3050_INT_STATUS, &val); + if (ret) + dev_err(mpu3050->dev, "error clearing IRQ status\n"); + + /* Disable all things in the FIFO and reset it */ + ret = regmap_write(mpu3050->map, MPU3050_FIFO_EN, 0); + if (ret) + dev_err(mpu3050->dev, "error disabling FIFO\n"); + + ret = regmap_write(mpu3050->map, MPU3050_USR_CTRL, + MPU3050_USR_CTRL_FIFO_RST); + if (ret) + dev_err(mpu3050->dev, "error resetting FIFO\n"); + + pm_runtime_put_autosuspend(mpu3050->dev); + mpu3050->hw_irq_trigger = false; + + return 0; + } else { + /* Else we're enabling the trigger from this point */ + pm_runtime_get_sync(mpu3050->dev); + mpu3050->hw_irq_trigger = true; + + /* Disable all things in the FIFO */ + ret = regmap_write(mpu3050->map, MPU3050_FIFO_EN, 0); + if (ret) + return ret; + + /* Reset and enable the FIFO */ + ret = regmap_set_bits(mpu3050->map, MPU3050_USR_CTRL, + MPU3050_USR_CTRL_FIFO_EN | + MPU3050_USR_CTRL_FIFO_RST); + if (ret) + return ret; + + mpu3050->pending_fifo_footer = false; + + /* Turn on the FIFO for temp+X+Y+Z */ + ret = regmap_write(mpu3050->map, MPU3050_FIFO_EN, + MPU3050_FIFO_EN_TEMP_OUT | + MPU3050_FIFO_EN_GYRO_XOUT | + MPU3050_FIFO_EN_GYRO_YOUT | + MPU3050_FIFO_EN_GYRO_ZOUT | + MPU3050_FIFO_EN_FOOTER); + if (ret) + return ret; + + /* Configure the sample engine */ + ret = mpu3050_start_sampling(mpu3050); + if (ret) + return ret; + + /* Clear IRQ flag */ + ret = regmap_read(mpu3050->map, MPU3050_INT_STATUS, &val); + if (ret) + dev_err(mpu3050->dev, "error clearing IRQ status\n"); + + /* Give us interrupts whenever there is new data ready */ + val = MPU3050_INT_RAW_RDY_EN; + + if (mpu3050->irq_actl) + val |= MPU3050_INT_ACTL; + if (mpu3050->irq_latch) + val |= MPU3050_INT_LATCH_EN; + if (mpu3050->irq_opendrain) + val |= MPU3050_INT_OPEN; + + ret = regmap_write(mpu3050->map, MPU3050_INT_CFG, val); + if (ret) + return ret; + } + + return 0; +} + +static const struct iio_trigger_ops mpu3050_trigger_ops = { + .set_trigger_state = mpu3050_drdy_trigger_set_state, +}; + +static int mpu3050_trigger_probe(struct iio_dev *indio_dev, int irq) +{ + struct mpu3050 *mpu3050 = iio_priv(indio_dev); + struct device *dev = mpu3050->dev; + unsigned long irq_trig; + int ret; + + mpu3050->trig = devm_iio_trigger_alloc(&indio_dev->dev, + "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!mpu3050->trig) + return -ENOMEM; + + /* Check if IRQ is open drain */ + mpu3050->irq_opendrain = device_property_read_bool(dev, "drive-open-drain"); + + /* + * Configure the interrupt generator hardware to supply whatever + * the interrupt is configured for, edges low/high level low/high, + * we can provide it all. + */ + irq_trig = irq_get_trigger_type(irq); + switch (irq_trig) { + case IRQF_TRIGGER_RISING: + dev_info(&indio_dev->dev, + "pulse interrupts on the rising edge\n"); + break; + case IRQF_TRIGGER_FALLING: + mpu3050->irq_actl = true; + dev_info(&indio_dev->dev, + "pulse interrupts on the falling edge\n"); + break; + case IRQF_TRIGGER_HIGH: + mpu3050->irq_latch = true; + dev_info(&indio_dev->dev, + "interrupts active high level\n"); + /* + * With level IRQs, we mask the IRQ until it is processed, + * but with edge IRQs (pulses) we can queue several interrupts + * in the top half. + */ + irq_trig |= IRQF_ONESHOT; + break; + case IRQF_TRIGGER_LOW: + mpu3050->irq_latch = true; + mpu3050->irq_actl = true; + irq_trig |= IRQF_ONESHOT; + dev_info(&indio_dev->dev, + "interrupts active low level\n"); + break; + default: + /* This is the most preferred mode, if possible */ + dev_err(&indio_dev->dev, + "unsupported IRQ trigger specified (%lx), enforce " + "rising edge\n", irq_trig); + irq_trig = IRQF_TRIGGER_RISING; + break; + } + + /* An open drain line can be shared with several devices */ + if (mpu3050->irq_opendrain) + irq_trig |= IRQF_SHARED; + + ret = request_threaded_irq(irq, + mpu3050_irq_handler, + mpu3050_irq_thread, + irq_trig, + mpu3050->trig->name, + mpu3050->trig); + if (ret) { + dev_err(dev, "can't get IRQ %d, error %d\n", irq, ret); + return ret; + } + + mpu3050->irq = irq; + mpu3050->trig->dev.parent = dev; + mpu3050->trig->ops = &mpu3050_trigger_ops; + iio_trigger_set_drvdata(mpu3050->trig, indio_dev); + + ret = iio_trigger_register(mpu3050->trig); + if (ret) + goto err_iio_trigger; + + indio_dev->trig = iio_trigger_get(mpu3050->trig); + + return 0; + +err_iio_trigger: + free_irq(mpu3050->irq, mpu3050->trig); + + return ret; +} + +int mpu3050_common_probe(struct device *dev, + struct regmap *map, + int irq, + const char *name) +{ + struct iio_dev *indio_dev; + struct mpu3050 *mpu3050; + unsigned int val; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*mpu3050)); + if (!indio_dev) + return -ENOMEM; + mpu3050 = iio_priv(indio_dev); + + mpu3050->dev = dev; + mpu3050->map = map; + mutex_init(&mpu3050->lock); + /* Default fullscale: 2000 degrees per second */ + mpu3050->fullscale = FS_2000_DPS; + /* 1 kHz, divide by 100, default frequency = 10 Hz */ + mpu3050->lpf = MPU3050_DLPF_CFG_188HZ; + mpu3050->divisor = 99; + + /* Read the mounting matrix, if present */ + ret = iio_read_mount_matrix(dev, &mpu3050->orientation); + if (ret) + return ret; + + /* Fetch and turn on regulators */ + mpu3050->regs[0].supply = mpu3050_reg_vdd; + mpu3050->regs[1].supply = mpu3050_reg_vlogic; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(mpu3050->regs), + mpu3050->regs); + if (ret) + return dev_err_probe(dev, ret, "Cannot get regulators\n"); + + ret = mpu3050_power_up(mpu3050); + if (ret) + return ret; + + ret = regmap_read(map, MPU3050_CHIP_ID_REG, &val); + if (ret) { + dev_err(dev, "could not read device ID\n"); + ret = -ENODEV; + + goto err_power_down; + } + + if ((val & MPU3050_CHIP_ID_MASK) != MPU3050_CHIP_ID) { + dev_err(dev, "unsupported chip id %02x\n", + (u8)(val & MPU3050_CHIP_ID_MASK)); + ret = -ENODEV; + goto err_power_down; + } + + ret = regmap_read(map, MPU3050_PRODUCT_ID_REG, &val); + if (ret) { + dev_err(dev, "could not read device ID\n"); + ret = -ENODEV; + + goto err_power_down; + } + dev_info(dev, "found MPU-3050 part no: %d, version: %d\n", + ((val >> 4) & 0xf), (val & 0xf)); + + ret = mpu3050_hw_init(mpu3050); + if (ret) + goto err_power_down; + + indio_dev->channels = mpu3050_channels; + indio_dev->num_channels = ARRAY_SIZE(mpu3050_channels); + indio_dev->info = &mpu3050_info; + indio_dev->available_scan_masks = mpu3050_scan_masks; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = name; + + ret = iio_triggered_buffer_setup(indio_dev, iio_pollfunc_store_time, + mpu3050_trigger_handler, + &mpu3050_buffer_setup_ops); + if (ret) { + dev_err(dev, "triggered buffer setup failed\n"); + goto err_power_down; + } + + dev_set_drvdata(dev, indio_dev); + + /* Check if we have an assigned IRQ to use as trigger */ + if (irq) { + ret = mpu3050_trigger_probe(indio_dev, irq); + if (ret) + dev_err(dev, "failed to register trigger\n"); + } + + /* Enable runtime PM */ + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + /* + * Set autosuspend to two orders of magnitude larger than the + * start-up time. 100ms start-up time means 10000ms autosuspend, + * i.e. 10 seconds. + */ + pm_runtime_set_autosuspend_delay(dev, 10000); + pm_runtime_use_autosuspend(dev); + pm_runtime_put(dev); + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(dev, "device register failed\n"); + goto err_iio_device_register; + } + + return 0; + +err_iio_device_register: + pm_runtime_get_sync(dev); + pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); + if (irq) + free_irq(mpu3050->irq, mpu3050->trig); + iio_triggered_buffer_cleanup(indio_dev); +err_power_down: + mpu3050_power_down(mpu3050); + + return ret; +} + +void mpu3050_common_remove(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct mpu3050 *mpu3050 = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + pm_runtime_get_sync(dev); + pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); + if (mpu3050->irq) + free_irq(mpu3050->irq, mpu3050->trig); + iio_triggered_buffer_cleanup(indio_dev); + mpu3050_power_down(mpu3050); +} + +static int mpu3050_runtime_suspend(struct device *dev) +{ + return mpu3050_power_down(iio_priv(dev_get_drvdata(dev))); +} + +static int mpu3050_runtime_resume(struct device *dev) +{ + return mpu3050_power_up(iio_priv(dev_get_drvdata(dev))); +} + +DEFINE_RUNTIME_DEV_PM_OPS(mpu3050_dev_pm_ops, mpu3050_runtime_suspend, + mpu3050_runtime_resume, NULL); +MODULE_AUTHOR("Linus Walleij"); +MODULE_DESCRIPTION("MPU3050 gyroscope driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/gyro/mpu3050-i2c.c b/drivers/iio/gyro/mpu3050-i2c.c new file mode 100644 index 000000000..2a5bcec72 --- /dev/null +++ b/drivers/iio/gyro/mpu3050-i2c.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include +#include +#include +#include +#include + +#include "mpu3050.h" + +static const struct regmap_config mpu3050_i2c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int mpu3050_i2c_bypass_select(struct i2c_mux_core *mux, u32 chan_id) +{ + struct mpu3050 *mpu3050 = i2c_mux_priv(mux); + + /* Just power up the device, that is all that is needed */ + return pm_runtime_resume_and_get(mpu3050->dev); +} + +static int mpu3050_i2c_bypass_deselect(struct i2c_mux_core *mux, u32 chan_id) +{ + struct mpu3050 *mpu3050 = i2c_mux_priv(mux); + + pm_runtime_put_autosuspend(mpu3050->dev); + return 0; +} + +static int mpu3050_i2c_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct regmap *regmap; + const char *name; + struct mpu3050 *mpu3050; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_I2C_BLOCK)) + return -EOPNOTSUPP; + + if (id) + name = id->name; + else + return -ENODEV; + + regmap = devm_regmap_init_i2c(client, &mpu3050_i2c_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Failed to register i2c regmap: %pe\n", + regmap); + return PTR_ERR(regmap); + } + + ret = mpu3050_common_probe(&client->dev, regmap, client->irq, name); + if (ret) + return ret; + + /* The main driver is up, now register the I2C mux */ + mpu3050 = iio_priv(dev_get_drvdata(&client->dev)); + mpu3050->i2cmux = i2c_mux_alloc(client->adapter, &client->dev, + 1, 0, I2C_MUX_LOCKED | I2C_MUX_GATE, + mpu3050_i2c_bypass_select, + mpu3050_i2c_bypass_deselect); + /* Just fail the mux, there is no point in killing the driver */ + if (!mpu3050->i2cmux) + dev_err(&client->dev, "failed to allocate I2C mux\n"); + else { + mpu3050->i2cmux->priv = mpu3050; + /* Ignore failure, not critical */ + i2c_mux_add_adapter(mpu3050->i2cmux, 0, 0); + } + + return 0; +} + +static void mpu3050_i2c_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = dev_get_drvdata(&client->dev); + struct mpu3050 *mpu3050 = iio_priv(indio_dev); + + if (mpu3050->i2cmux) + i2c_mux_del_adapters(mpu3050->i2cmux); + + mpu3050_common_remove(&client->dev); +} + +/* + * device id table is used to identify what device can be + * supported by this driver + */ +static const struct i2c_device_id mpu3050_i2c_id[] = { + { .name = "mpu3050" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mpu3050_i2c_id); + +static const struct of_device_id mpu3050_i2c_of_match[] = { + { .compatible = "invensense,mpu3050", .data = "mpu3050" }, + /* Deprecated vendor ID from the Input driver */ + { .compatible = "invn,mpu3050", .data = "mpu3050" }, + { } +}; +MODULE_DEVICE_TABLE(of, mpu3050_i2c_of_match); + +static struct i2c_driver mpu3050_i2c_driver = { + .probe = mpu3050_i2c_probe, + .remove = mpu3050_i2c_remove, + .id_table = mpu3050_i2c_id, + .driver = { + .of_match_table = mpu3050_i2c_of_match, + .name = "mpu3050-i2c", + .pm = pm_ptr(&mpu3050_dev_pm_ops), + }, +}; +module_i2c_driver(mpu3050_i2c_driver); + +MODULE_AUTHOR("Linus Walleij"); +MODULE_DESCRIPTION("Invensense MPU3050 gyroscope driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/gyro/mpu3050.h b/drivers/iio/gyro/mpu3050.h new file mode 100644 index 000000000..faf4168a3 --- /dev/null +++ b/drivers/iio/gyro/mpu3050.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include +#include +#include +#include +#include + +/** + * enum mpu3050_fullscale - indicates the full range of the sensor in deg/sec + */ +enum mpu3050_fullscale { + FS_250_DPS = 0, + FS_500_DPS, + FS_1000_DPS, + FS_2000_DPS, +}; + +/** + * enum mpu3050_lpf - indicates the low pass filter width + */ +enum mpu3050_lpf { + /* This implicity sets sample frequency to 8 kHz */ + LPF_256_HZ_NOLPF = 0, + /* All others sets the sample frequency to 1 kHz */ + LPF_188_HZ, + LPF_98_HZ, + LPF_42_HZ, + LPF_20_HZ, + LPF_10_HZ, + LPF_5_HZ, + LPF_2100_HZ_NOLPF, +}; + +enum mpu3050_axis { + AXIS_X = 0, + AXIS_Y, + AXIS_Z, + AXIS_MAX, +}; + +/** + * struct mpu3050 - instance state container for the device + * @dev: parent device for this instance + * @orientation: mounting matrix, flipped axis etc + * @map: regmap to reach the registers + * @lock: serialization lock to marshal all requests + * @irq: the IRQ used for this device + * @regs: the regulators to power this device + * @fullscale: the current fullscale setting for the device + * @lpf: digital low pass filter setting for the device + * @divisor: base frequency divider: divides 8 or 1 kHz + * @calibration: the three signed 16-bit calibration settings that + * get written into the offset registers for each axis to compensate + * for DC offsets + * @trig: trigger for the MPU-3050 interrupt, if present + * @hw_irq_trigger: hardware interrupt trigger is in use + * @irq_actl: interrupt is active low + * @irq_latch: latched IRQ, this means that it is a level IRQ + * @irq_opendrain: the interrupt line shall be configured open drain + * @pending_fifo_footer: tells us if there is a pending footer in the FIFO + * that we have to read out first when handling the FIFO + * @hw_timestamp: latest hardware timestamp from the trigger IRQ, when in + * use + * @i2cmux: an I2C mux reflecting the fact that this sensor is a hub with + * a pass-through I2C interface coming out of it: this device needs to be + * powered up in order to reach devices on the other side of this mux + */ +struct mpu3050 { + struct device *dev; + struct iio_mount_matrix orientation; + struct regmap *map; + struct mutex lock; + int irq; + struct regulator_bulk_data regs[2]; + enum mpu3050_fullscale fullscale; + enum mpu3050_lpf lpf; + u8 divisor; + s16 calibration[3]; + struct iio_trigger *trig; + bool hw_irq_trigger; + bool irq_actl; + bool irq_latch; + bool irq_opendrain; + bool pending_fifo_footer; + s64 hw_timestamp; + struct i2c_mux_core *i2cmux; +}; + +/* Probe called from different transports */ +int mpu3050_common_probe(struct device *dev, + struct regmap *map, + int irq, + const char *name); +void mpu3050_common_remove(struct device *dev); + +/* PM ops */ +extern const struct dev_pm_ops mpu3050_dev_pm_ops; diff --git a/drivers/iio/gyro/ssp_gyro_sensor.c b/drivers/iio/gyro/ssp_gyro_sensor.c new file mode 100644 index 000000000..1acbbc1ee --- /dev/null +++ b/drivers/iio/gyro/ssp_gyro_sensor.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2014, Samsung Electronics Co. Ltd. All Rights Reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../common/ssp_sensors/ssp_iio_sensor.h" + +#define SSP_CHANNEL_COUNT 3 + +#define SSP_GYROSCOPE_NAME "ssp-gyroscope" +static const char ssp_gyro_name[] = SSP_GYROSCOPE_NAME; + +enum ssp_gyro_3d_channel { + SSP_CHANNEL_SCAN_INDEX_X, + SSP_CHANNEL_SCAN_INDEX_Y, + SSP_CHANNEL_SCAN_INDEX_Z, + SSP_CHANNEL_SCAN_INDEX_TIME, +}; + +static int ssp_gyro_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + u32 t; + struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + t = ssp_get_sensor_delay(data, SSP_GYROSCOPE_SENSOR); + ssp_convert_to_freq(t, val, val2); + return IIO_VAL_INT_PLUS_MICRO; + default: + break; + } + + return -EINVAL; +} + +static int ssp_gyro_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + int ret; + struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = ssp_convert_to_time(val, val2); + ret = ssp_change_delay(data, SSP_GYROSCOPE_SENSOR, ret); + if (ret < 0) + dev_err(&indio_dev->dev, "gyro sensor enable fail\n"); + + return ret; + default: + break; + } + + return -EINVAL; +} + +static const struct iio_info ssp_gyro_iio_info = { + .read_raw = &ssp_gyro_read_raw, + .write_raw = &ssp_gyro_write_raw, +}; + +static const unsigned long ssp_gyro_scan_mask[] = { 0x07, 0, }; + +static const struct iio_chan_spec ssp_gyro_channels[] = { + SSP_CHANNEL_AG(IIO_ANGL_VEL, IIO_MOD_X, SSP_CHANNEL_SCAN_INDEX_X), + SSP_CHANNEL_AG(IIO_ANGL_VEL, IIO_MOD_Y, SSP_CHANNEL_SCAN_INDEX_Y), + SSP_CHANNEL_AG(IIO_ANGL_VEL, IIO_MOD_Z, SSP_CHANNEL_SCAN_INDEX_Z), + IIO_CHAN_SOFT_TIMESTAMP(SSP_CHANNEL_SCAN_INDEX_TIME), +}; + +static int ssp_process_gyro_data(struct iio_dev *indio_dev, void *buf, + int64_t timestamp) +{ + return ssp_common_process_data(indio_dev, buf, SSP_GYROSCOPE_SIZE, + timestamp); +} + +static const struct iio_buffer_setup_ops ssp_gyro_buffer_ops = { + .postenable = &ssp_common_buffer_postenable, + .postdisable = &ssp_common_buffer_postdisable, +}; + +static int ssp_gyro_probe(struct platform_device *pdev) +{ + int ret; + struct iio_dev *indio_dev; + struct ssp_sensor_data *spd; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*spd)); + if (!indio_dev) + return -ENOMEM; + + spd = iio_priv(indio_dev); + + spd->process_data = ssp_process_gyro_data; + spd->type = SSP_GYROSCOPE_SENSOR; + + indio_dev->name = ssp_gyro_name; + indio_dev->info = &ssp_gyro_iio_info; + indio_dev->channels = ssp_gyro_channels; + indio_dev->num_channels = ARRAY_SIZE(ssp_gyro_channels); + indio_dev->available_scan_masks = ssp_gyro_scan_mask; + + ret = devm_iio_kfifo_buffer_setup(&pdev->dev, indio_dev, + &ssp_gyro_buffer_ops); + if (ret) + return ret; + + platform_set_drvdata(pdev, indio_dev); + + ret = devm_iio_device_register(&pdev->dev, indio_dev); + if (ret < 0) + return ret; + + /* ssp registering should be done after all iio setup */ + ssp_register_consumer(indio_dev, SSP_GYROSCOPE_SENSOR); + + return 0; +} + +static struct platform_driver ssp_gyro_driver = { + .driver = { + .name = SSP_GYROSCOPE_NAME, + }, + .probe = ssp_gyro_probe, +}; + +module_platform_driver(ssp_gyro_driver); + +MODULE_AUTHOR("Karol Wrona "); +MODULE_DESCRIPTION("Samsung sensorhub gyroscopes driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_SSP_SENSORS"); diff --git a/drivers/iio/gyro/st_gyro.h b/drivers/iio/gyro/st_gyro.h new file mode 100644 index 000000000..f5332b6a0 --- /dev/null +++ b/drivers/iio/gyro/st_gyro.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * STMicroelectronics gyroscopes driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + * v. 1.0.0 + */ + +#ifndef ST_GYRO_H +#define ST_GYRO_H + +#include +#include + +#define L3G4200D_GYRO_DEV_NAME "l3g4200d" +#define LSM330D_GYRO_DEV_NAME "lsm330d_gyro" +#define LSM330DL_GYRO_DEV_NAME "lsm330dl_gyro" +#define LSM330DLC_GYRO_DEV_NAME "lsm330dlc_gyro" +#define L3GD20_GYRO_DEV_NAME "l3gd20" +#define L3GD20H_GYRO_DEV_NAME "l3gd20h" +#define L3G4IS_GYRO_DEV_NAME "l3g4is_ui" +#define LSM330_GYRO_DEV_NAME "lsm330_gyro" +#define LSM9DS0_GYRO_DEV_NAME "lsm9ds0_gyro" + +#ifdef CONFIG_IIO_BUFFER +int st_gyro_allocate_ring(struct iio_dev *indio_dev); +int st_gyro_trig_set_state(struct iio_trigger *trig, bool state); +#define ST_GYRO_TRIGGER_SET_STATE (&st_gyro_trig_set_state) +#else /* CONFIG_IIO_BUFFER */ +static inline int st_gyro_allocate_ring(struct iio_dev *indio_dev) +{ + return 0; +} +#define ST_GYRO_TRIGGER_SET_STATE NULL +#endif /* CONFIG_IIO_BUFFER */ + +#endif /* ST_GYRO_H */ diff --git a/drivers/iio/gyro/st_gyro_buffer.c b/drivers/iio/gyro/st_gyro_buffer.c new file mode 100644 index 000000000..1ebfe7aa6 --- /dev/null +++ b/drivers/iio/gyro/st_gyro_buffer.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics gyroscopes driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include +#include + +#include +#include "st_gyro.h" + +int st_gyro_trig_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + + return st_sensors_set_dataready_irq(indio_dev, state); +} + +static int st_gyro_buffer_postenable(struct iio_dev *indio_dev) +{ + int err; + + err = st_sensors_set_axis_enable(indio_dev, indio_dev->active_scan_mask[0]); + if (err < 0) + return err; + + err = st_sensors_set_enable(indio_dev, true); + if (err < 0) + goto st_gyro_buffer_enable_all_axis; + + return 0; + +st_gyro_buffer_enable_all_axis: + st_sensors_set_axis_enable(indio_dev, ST_SENSORS_ENABLE_ALL_AXIS); + return err; +} + +static int st_gyro_buffer_predisable(struct iio_dev *indio_dev) +{ + int err; + + err = st_sensors_set_enable(indio_dev, false); + if (err < 0) + return err; + + return st_sensors_set_axis_enable(indio_dev, ST_SENSORS_ENABLE_ALL_AXIS); +} + +static const struct iio_buffer_setup_ops st_gyro_buffer_setup_ops = { + .postenable = &st_gyro_buffer_postenable, + .predisable = &st_gyro_buffer_predisable, +}; + +int st_gyro_allocate_ring(struct iio_dev *indio_dev) +{ + return devm_iio_triggered_buffer_setup(indio_dev->dev.parent, indio_dev, + NULL, &st_sensors_trigger_handler, &st_gyro_buffer_setup_ops); +} + diff --git a/drivers/iio/gyro/st_gyro_core.c b/drivers/iio/gyro/st_gyro_core.c new file mode 100644 index 000000000..7fd82cd70 --- /dev/null +++ b/drivers/iio/gyro/st_gyro_core.c @@ -0,0 +1,519 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics gyroscopes driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "st_gyro.h" + +#define ST_GYRO_NUMBER_DATA_CHANNELS 3 + +/* DEFAULT VALUE FOR SENSORS */ +#define ST_GYRO_DEFAULT_OUT_X_L_ADDR 0x28 +#define ST_GYRO_DEFAULT_OUT_Y_L_ADDR 0x2a +#define ST_GYRO_DEFAULT_OUT_Z_L_ADDR 0x2c + +/* FULLSCALE */ +#define ST_GYRO_FS_AVL_245DPS 245 +#define ST_GYRO_FS_AVL_250DPS 250 +#define ST_GYRO_FS_AVL_500DPS 500 +#define ST_GYRO_FS_AVL_2000DPS 2000 + +static const struct iio_mount_matrix * +st_gyro_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct st_sensor_data *gdata = iio_priv(indio_dev); + + return &gdata->mount_matrix; +} + +static const struct iio_chan_spec_ext_info st_gyro_mount_matrix_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, st_gyro_get_mount_matrix), + { } +}; + +static const struct iio_chan_spec st_gyro_16bit_channels[] = { + ST_SENSORS_LSM_CHANNELS_EXT(IIO_ANGL_VEL, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_X, 1, IIO_MOD_X, 's', IIO_LE, 16, 16, + ST_GYRO_DEFAULT_OUT_X_L_ADDR, + st_gyro_mount_matrix_ext_info), + ST_SENSORS_LSM_CHANNELS_EXT(IIO_ANGL_VEL, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Y, 1, IIO_MOD_Y, 's', IIO_LE, 16, 16, + ST_GYRO_DEFAULT_OUT_Y_L_ADDR, + st_gyro_mount_matrix_ext_info), + ST_SENSORS_LSM_CHANNELS_EXT(IIO_ANGL_VEL, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Z, 1, IIO_MOD_Z, 's', IIO_LE, 16, 16, + ST_GYRO_DEFAULT_OUT_Z_L_ADDR, + st_gyro_mount_matrix_ext_info), + IIO_CHAN_SOFT_TIMESTAMP(3) +}; + +static const struct st_sensor_settings st_gyro_sensors_settings[] = { + { + .wai = 0xd3, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = L3G4200D_GYRO_DEV_NAME, + [1] = LSM330DL_GYRO_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_gyro_16bit_channels, + .odr = { + .addr = 0x20, + .mask = 0xc0, + .odr_avl = { + { .hz = 100, .value = 0x00, }, + { .hz = 200, .value = 0x01, }, + { .hz = 400, .value = 0x02, }, + { .hz = 800, .value = 0x03, }, + }, + }, + .pw = { + .addr = 0x20, + .mask = 0x08, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = 0x23, + .mask = 0x30, + .fs_avl = { + [0] = { + .num = ST_GYRO_FS_AVL_250DPS, + .value = 0x00, + .gain = IIO_DEGREE_TO_RAD(8750), + }, + [1] = { + .num = ST_GYRO_FS_AVL_500DPS, + .value = 0x01, + .gain = IIO_DEGREE_TO_RAD(17500), + }, + [2] = { + .num = ST_GYRO_FS_AVL_2000DPS, + .value = 0x02, + .gain = IIO_DEGREE_TO_RAD(70000), + }, + }, + }, + .bdu = { + .addr = 0x23, + .mask = 0x80, + }, + .drdy_irq = { + .int2 = { + .addr = 0x22, + .mask = 0x08, + }, + /* + * The sensor has IHL (active low) and open + * drain settings, but only for INT1 and not + * for the DRDY line on INT2. + */ + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x07, + }, + }, + .sim = { + .addr = 0x23, + .value = BIT(0), + }, + .multi_read_bit = true, + .bootime = 2, + }, + { + .wai = 0xd4, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = L3GD20_GYRO_DEV_NAME, + [1] = LSM330D_GYRO_DEV_NAME, + [2] = LSM330DLC_GYRO_DEV_NAME, + [3] = L3G4IS_GYRO_DEV_NAME, + [4] = LSM330_GYRO_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_gyro_16bit_channels, + .odr = { + .addr = 0x20, + .mask = 0xc0, + .odr_avl = { + { .hz = 95, .value = 0x00, }, + { .hz = 190, .value = 0x01, }, + { .hz = 380, .value = 0x02, }, + { .hz = 760, .value = 0x03, }, + }, + }, + .pw = { + .addr = 0x20, + .mask = 0x08, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = 0x23, + .mask = 0x30, + .fs_avl = { + [0] = { + .num = ST_GYRO_FS_AVL_250DPS, + .value = 0x00, + .gain = IIO_DEGREE_TO_RAD(8750), + }, + [1] = { + .num = ST_GYRO_FS_AVL_500DPS, + .value = 0x01, + .gain = IIO_DEGREE_TO_RAD(17500), + }, + [2] = { + .num = ST_GYRO_FS_AVL_2000DPS, + .value = 0x02, + .gain = IIO_DEGREE_TO_RAD(70000), + }, + }, + }, + .bdu = { + .addr = 0x23, + .mask = 0x80, + }, + .drdy_irq = { + .int2 = { + .addr = 0x22, + .mask = 0x08, + }, + /* + * The sensor has IHL (active low) and open + * drain settings, but only for INT1 and not + * for the DRDY line on INT2. + */ + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x07, + }, + }, + .sim = { + .addr = 0x23, + .value = BIT(0), + }, + .multi_read_bit = true, + .bootime = 2, + }, + { + .wai = 0xd4, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LSM9DS0_GYRO_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_gyro_16bit_channels, + .odr = { + .addr = 0x20, + .mask = GENMASK(7, 6), + .odr_avl = { + { .hz = 95, .value = 0x00, }, + { .hz = 190, .value = 0x01, }, + { .hz = 380, .value = 0x02, }, + { .hz = 760, .value = 0x03, }, + }, + }, + .pw = { + .addr = 0x20, + .mask = BIT(3), + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = 0x23, + .mask = GENMASK(5, 4), + .fs_avl = { + [0] = { + .num = ST_GYRO_FS_AVL_245DPS, + .value = 0x00, + .gain = IIO_DEGREE_TO_RAD(8750), + }, + [1] = { + .num = ST_GYRO_FS_AVL_500DPS, + .value = 0x01, + .gain = IIO_DEGREE_TO_RAD(17500), + }, + [2] = { + .num = ST_GYRO_FS_AVL_2000DPS, + .value = 0x02, + .gain = IIO_DEGREE_TO_RAD(70000), + }, + }, + }, + .bdu = { + .addr = 0x23, + .mask = BIT(7), + }, + .drdy_irq = { + .int2 = { + .addr = 0x22, + .mask = BIT(3), + }, + /* + * The sensor has IHL (active low) and open + * drain settings, but only for INT1 and not + * for the DRDY line on INT2. + */ + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = GENMASK(2, 0), + }, + }, + .sim = { + .addr = 0x23, + .value = BIT(0), + }, + .multi_read_bit = true, + .bootime = 2, + }, + { + .wai = 0xd7, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = L3GD20H_GYRO_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_gyro_16bit_channels, + .odr = { + .addr = 0x20, + .mask = 0xc0, + .odr_avl = { + { .hz = 100, .value = 0x00, }, + { .hz = 200, .value = 0x01, }, + { .hz = 400, .value = 0x02, }, + { .hz = 800, .value = 0x03, }, + }, + }, + .pw = { + .addr = 0x20, + .mask = 0x08, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = 0x23, + .mask = 0x30, + .fs_avl = { + [0] = { + .num = ST_GYRO_FS_AVL_245DPS, + .value = 0x00, + .gain = IIO_DEGREE_TO_RAD(8750), + }, + [1] = { + .num = ST_GYRO_FS_AVL_500DPS, + .value = 0x01, + .gain = IIO_DEGREE_TO_RAD(17500), + }, + [2] = { + .num = ST_GYRO_FS_AVL_2000DPS, + .value = 0x02, + .gain = IIO_DEGREE_TO_RAD(70000), + }, + }, + }, + .bdu = { + .addr = 0x23, + .mask = 0x80, + }, + .drdy_irq = { + .int2 = { + .addr = 0x22, + .mask = 0x08, + }, + /* + * The sensor has IHL (active low) and open + * drain settings, but only for INT1 and not + * for the DRDY line on INT2. + */ + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x07, + }, + }, + .sim = { + .addr = 0x23, + .value = BIT(0), + }, + .multi_read_bit = true, + .bootime = 2, + }, +}; + +/* DRDY on gyros is available only on INT2 pin */ +static const struct st_sensors_platform_data gyro_pdata = { + .drdy_int_pin = 2, +}; + +static int st_gyro_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *ch, int *val, + int *val2, long mask) +{ + int err; + struct st_sensor_data *gdata = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + err = st_sensors_read_info_raw(indio_dev, ch, val); + if (err < 0) + goto read_error; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = gdata->current_fullscale->gain; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = gdata->odr; + return IIO_VAL_INT; + default: + return -EINVAL; + } + +read_error: + return err; +} + +static int st_gyro_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return st_sensors_set_fullscale_by_gain(indio_dev, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + if (val2) + return -EINVAL; + + return st_sensors_set_odr(indio_dev, val); + default: + return -EINVAL; + } +} + +static ST_SENSORS_DEV_ATTR_SAMP_FREQ_AVAIL(); +static ST_SENSORS_DEV_ATTR_SCALE_AVAIL(in_anglvel_scale_available); + +static struct attribute *st_gyro_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_anglvel_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group st_gyro_attribute_group = { + .attrs = st_gyro_attributes, +}; + +static const struct iio_info gyro_info = { + .attrs = &st_gyro_attribute_group, + .read_raw = &st_gyro_read_raw, + .write_raw = &st_gyro_write_raw, + .debugfs_reg_access = &st_sensors_debugfs_reg_access, +}; + +#ifdef CONFIG_IIO_TRIGGER +static const struct iio_trigger_ops st_gyro_trigger_ops = { + .set_trigger_state = ST_GYRO_TRIGGER_SET_STATE, + .validate_device = st_sensors_validate_device, +}; +#define ST_GYRO_TRIGGER_OPS (&st_gyro_trigger_ops) +#else +#define ST_GYRO_TRIGGER_OPS NULL +#endif + +/* + * st_gyro_get_settings() - get sensor settings from device name + * @name: device name buffer reference. + * + * Return: valid reference on success, NULL otherwise. + */ +const struct st_sensor_settings *st_gyro_get_settings(const char *name) +{ + int index = st_sensors_get_settings_index(name, + st_gyro_sensors_settings, + ARRAY_SIZE(st_gyro_sensors_settings)); + if (index < 0) + return NULL; + + return &st_gyro_sensors_settings[index]; +} +EXPORT_SYMBOL_NS(st_gyro_get_settings, "IIO_ST_SENSORS"); + +int st_gyro_common_probe(struct iio_dev *indio_dev) +{ + struct st_sensor_data *gdata = iio_priv(indio_dev); + struct st_sensors_platform_data *pdata; + struct device *parent = indio_dev->dev.parent; + int err; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &gyro_info; + + err = st_sensors_verify_id(indio_dev); + if (err < 0) + return err; + + gdata->num_data_channels = ST_GYRO_NUMBER_DATA_CHANNELS; + indio_dev->channels = gdata->sensor_settings->ch; + indio_dev->num_channels = ST_SENSORS_NUMBER_ALL_CHANNELS; + + err = iio_read_mount_matrix(parent, &gdata->mount_matrix); + if (err) + return err; + + gdata->current_fullscale = &gdata->sensor_settings->fs.fs_avl[0]; + gdata->odr = gdata->sensor_settings->odr.odr_avl[0].hz; + + pdata = (struct st_sensors_platform_data *)&gyro_pdata; + + err = st_sensors_init_sensor(indio_dev, pdata); + if (err < 0) + return err; + + err = st_gyro_allocate_ring(indio_dev); + if (err < 0) + return err; + + if (gdata->irq > 0) { + err = st_sensors_allocate_trigger(indio_dev, + ST_GYRO_TRIGGER_OPS); + if (err < 0) + return err; + } + + return devm_iio_device_register(parent, indio_dev); +} +EXPORT_SYMBOL_NS(st_gyro_common_probe, "IIO_ST_SENSORS"); + +MODULE_AUTHOR("Denis Ciocca "); +MODULE_DESCRIPTION("STMicroelectronics gyroscopes driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ST_SENSORS"); diff --git a/drivers/iio/gyro/st_gyro_i2c.c b/drivers/iio/gyro/st_gyro_i2c.c new file mode 100644 index 000000000..a587e9023 --- /dev/null +++ b/drivers/iio/gyro/st_gyro_i2c.c @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics gyroscopes driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include + +#include +#include +#include "st_gyro.h" + +static const struct of_device_id st_gyro_of_match[] = { + { + .compatible = "st,l3g4200d-gyro", + .data = L3G4200D_GYRO_DEV_NAME, + }, + { + .compatible = "st,lsm330d-gyro", + .data = LSM330D_GYRO_DEV_NAME, + }, + { + .compatible = "st,lsm330dl-gyro", + .data = LSM330DL_GYRO_DEV_NAME, + }, + { + .compatible = "st,lsm330dlc-gyro", + .data = LSM330DLC_GYRO_DEV_NAME, + }, + { + .compatible = "st,l3gd20-gyro", + .data = L3GD20_GYRO_DEV_NAME, + }, + { + .compatible = "st,l3gd20h-gyro", + .data = L3GD20H_GYRO_DEV_NAME, + }, + { + .compatible = "st,l3g4is-gyro", + .data = L3G4IS_GYRO_DEV_NAME, + }, + { + .compatible = "st,lsm330-gyro", + .data = LSM330_GYRO_DEV_NAME, + }, + { + .compatible = "st,lsm9ds0-gyro", + .data = LSM9DS0_GYRO_DEV_NAME, + }, + { } +}; +MODULE_DEVICE_TABLE(of, st_gyro_of_match); + +static int st_gyro_i2c_probe(struct i2c_client *client) +{ + const struct st_sensor_settings *settings; + struct st_sensor_data *gdata; + struct iio_dev *indio_dev; + int err; + + st_sensors_dev_name_probe(&client->dev, client->name, sizeof(client->name)); + + settings = st_gyro_get_settings(client->name); + if (!settings) { + dev_err(&client->dev, "device name %s not recognized.\n", + client->name); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*gdata)); + if (!indio_dev) + return -ENOMEM; + + gdata = iio_priv(indio_dev); + gdata->sensor_settings = (struct st_sensor_settings *)settings; + + err = st_sensors_i2c_configure(indio_dev, client); + if (err < 0) + return err; + + err = st_sensors_power_enable(indio_dev); + if (err) + return err; + + return st_gyro_common_probe(indio_dev); +} + +static const struct i2c_device_id st_gyro_id_table[] = { + { .name = L3G4200D_GYRO_DEV_NAME }, + { .name = LSM330D_GYRO_DEV_NAME }, + { .name = LSM330DL_GYRO_DEV_NAME }, + { .name = LSM330DLC_GYRO_DEV_NAME }, + { .name = L3GD20_GYRO_DEV_NAME }, + { .name = L3GD20H_GYRO_DEV_NAME }, + { .name = L3G4IS_GYRO_DEV_NAME }, + { .name = LSM330_GYRO_DEV_NAME }, + { .name = LSM9DS0_GYRO_DEV_NAME }, + { } +}; +MODULE_DEVICE_TABLE(i2c, st_gyro_id_table); + +static struct i2c_driver st_gyro_driver = { + .driver = { + .name = "st-gyro-i2c", + .of_match_table = st_gyro_of_match, + }, + .probe = st_gyro_i2c_probe, + .id_table = st_gyro_id_table, +}; +module_i2c_driver(st_gyro_driver); + +MODULE_AUTHOR("Denis Ciocca "); +MODULE_DESCRIPTION("STMicroelectronics gyroscopes i2c driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ST_SENSORS"); diff --git a/drivers/iio/gyro/st_gyro_spi.c b/drivers/iio/gyro/st_gyro_spi.c new file mode 100644 index 000000000..c56331755 --- /dev/null +++ b/drivers/iio/gyro/st_gyro_spi.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics gyroscopes driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include + +#include +#include +#include "st_gyro.h" + +/* + * For new single-chip sensors use as compatible string. + * For old single-chip devices keep -gyro to maintain + * compatibility + */ +static const struct of_device_id st_gyro_of_match[] = { + { + .compatible = "st,l3g4200d-gyro", + .data = L3G4200D_GYRO_DEV_NAME, + }, + { + .compatible = "st,lsm330d-gyro", + .data = LSM330D_GYRO_DEV_NAME, + }, + { + .compatible = "st,lsm330dl-gyro", + .data = LSM330DL_GYRO_DEV_NAME, + }, + { + .compatible = "st,lsm330dlc-gyro", + .data = LSM330DLC_GYRO_DEV_NAME, + }, + { + .compatible = "st,l3gd20-gyro", + .data = L3GD20_GYRO_DEV_NAME, + }, + { + .compatible = "st,l3gd20h-gyro", + .data = L3GD20H_GYRO_DEV_NAME, + }, + { + .compatible = "st,l3g4is-gyro", + .data = L3G4IS_GYRO_DEV_NAME, + }, + { + .compatible = "st,lsm330-gyro", + .data = LSM330_GYRO_DEV_NAME, + }, + { + .compatible = "st,lsm9ds0-gyro", + .data = LSM9DS0_GYRO_DEV_NAME, + }, + { } +}; +MODULE_DEVICE_TABLE(of, st_gyro_of_match); + +static int st_gyro_spi_probe(struct spi_device *spi) +{ + const struct st_sensor_settings *settings; + struct st_sensor_data *gdata; + struct iio_dev *indio_dev; + int err; + + st_sensors_dev_name_probe(&spi->dev, spi->modalias, sizeof(spi->modalias)); + + settings = st_gyro_get_settings(spi->modalias); + if (!settings) { + dev_err(&spi->dev, "device name %s not recognized.\n", + spi->modalias); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*gdata)); + if (!indio_dev) + return -ENOMEM; + + gdata = iio_priv(indio_dev); + gdata->sensor_settings = (struct st_sensor_settings *)settings; + + err = st_sensors_spi_configure(indio_dev, spi); + if (err < 0) + return err; + + err = st_sensors_power_enable(indio_dev); + if (err) + return err; + + return st_gyro_common_probe(indio_dev); +} + +static const struct spi_device_id st_gyro_id_table[] = { + { .name = L3G4200D_GYRO_DEV_NAME }, + { .name = LSM330D_GYRO_DEV_NAME }, + { .name = LSM330DL_GYRO_DEV_NAME }, + { .name = LSM330DLC_GYRO_DEV_NAME }, + { .name = L3GD20_GYRO_DEV_NAME }, + { .name = L3GD20H_GYRO_DEV_NAME }, + { .name = L3G4IS_GYRO_DEV_NAME }, + { .name = LSM330_GYRO_DEV_NAME }, + { .name = LSM9DS0_GYRO_DEV_NAME }, + { } +}; +MODULE_DEVICE_TABLE(spi, st_gyro_id_table); + +static struct spi_driver st_gyro_driver = { + .driver = { + .name = "st-gyro-spi", + .of_match_table = st_gyro_of_match, + }, + .probe = st_gyro_spi_probe, + .id_table = st_gyro_id_table, +}; +module_spi_driver(st_gyro_driver); + +MODULE_AUTHOR("Denis Ciocca "); +MODULE_DESCRIPTION("STMicroelectronics gyroscopes spi driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ST_SENSORS"); diff --git a/drivers/iio/health/Kconfig b/drivers/iio/health/Kconfig new file mode 100644 index 000000000..a89f3abf1 --- /dev/null +++ b/drivers/iio/health/Kconfig @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Health sensors +# +# When adding new entries keep the list in alphabetical order + +menu "Health Sensors" + +menu "Heart Rate Monitors" + +config AFE4403 + tristate "TI AFE4403 Heart Rate Monitor" + depends on SPI_MASTER + select REGMAP_SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes to choose the Texas Instruments AFE4403 + heart rate monitor and low-cost pulse oximeter. + + To compile this driver as a module, choose M here: the + module will be called afe4403. + +config AFE4404 + tristate "TI AFE4404 heart rate and pulse oximeter sensor" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes to choose the Texas Instruments AFE4404 + heart rate monitor and low-cost pulse oximeter. + + To compile this driver as a module, choose M here: the + module will be called afe4404. + +config MAX30100 + tristate "MAX30100 heart rate and pulse oximeter sensor" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_KFIFO_BUF + help + Say Y here to build I2C interface support for the Maxim + MAX30100 heart rate, and pulse oximeter sensor. + + To compile this driver as a module, choose M here: the + module will be called max30100. + +config MAX30102 + tristate "MAX30102 heart rate and pulse oximeter sensor" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_KFIFO_BUF + help + Say Y here to build I2C interface support for the Maxim + MAX30102 heart rate, and pulse oximeter sensor. + + To compile this driver as a module, choose M here: the + module will be called max30102. + +endmenu + +endmenu diff --git a/drivers/iio/health/Makefile b/drivers/iio/health/Makefile new file mode 100644 index 000000000..910817112 --- /dev/null +++ b/drivers/iio/health/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for IIO Health sensors +# + +# When adding new entries keep the list in alphabetical order + +obj-$(CONFIG_AFE4403) += afe4403.o +obj-$(CONFIG_AFE4404) += afe4404.o +obj-$(CONFIG_MAX30100) += max30100.o +obj-$(CONFIG_MAX30102) += max30102.o diff --git a/drivers/iio/health/afe4403.c b/drivers/iio/health/afe4403.c new file mode 100644 index 000000000..2a6485322 --- /dev/null +++ b/drivers/iio/health/afe4403.c @@ -0,0 +1,587 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AFE4403 Heart Rate Monitors and Low-Cost Pulse Oximeters + * + * Copyright (C) 2015-2016 Texas Instruments Incorporated - https://www.ti.com/ + * Andrew F. Davis + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "afe440x.h" + +#define AFE4403_DRIVER_NAME "afe4403" + +/* AFE4403 Registers */ +#define AFE4403_TIAGAIN 0x20 +#define AFE4403_TIA_AMB_GAIN 0x21 + +enum afe4403_fields { + /* Gains */ + F_RF_LED1, F_CF_LED1, + F_RF_LED, F_CF_LED, + + /* LED Current */ + F_ILED1, F_ILED2, + + /* sentinel */ + F_MAX_FIELDS +}; + +static const struct reg_field afe4403_reg_fields[] = { + /* Gains */ + [F_RF_LED1] = REG_FIELD(AFE4403_TIAGAIN, 0, 2), + [F_CF_LED1] = REG_FIELD(AFE4403_TIAGAIN, 3, 7), + [F_RF_LED] = REG_FIELD(AFE4403_TIA_AMB_GAIN, 0, 2), + [F_CF_LED] = REG_FIELD(AFE4403_TIA_AMB_GAIN, 3, 7), + /* LED Current */ + [F_ILED1] = REG_FIELD(AFE440X_LEDCNTRL, 0, 7), + [F_ILED2] = REG_FIELD(AFE440X_LEDCNTRL, 8, 15), +}; + +/** + * struct afe4403_data - AFE4403 device instance data + * @spi: SPI device handle + * @regmap: Register map of the device + * @fields: Register fields of the device + * @regulator: Pointer to the regulator for the IC + * @trig: IIO trigger for this device + * @irq: ADC_RDY line interrupt number + * @buffer: Used to construct data layout to push into IIO buffer. + */ +struct afe4403_data { + struct spi_device *spi; + struct regmap *regmap; + struct regmap_field *fields[F_MAX_FIELDS]; + struct regulator *regulator; + struct iio_trigger *trig; + int irq; + /* Ensure suitable alignment for timestamp */ + s32 buffer[8] __aligned(8); +}; + +enum afe4403_chan_id { + LED2 = 1, + ALED2, + LED1, + ALED1, + LED2_ALED2, + LED1_ALED1, +}; + +static const unsigned int afe4403_channel_values[] = { + [LED2] = AFE440X_LED2VAL, + [ALED2] = AFE440X_ALED2VAL, + [LED1] = AFE440X_LED1VAL, + [ALED1] = AFE440X_ALED1VAL, + [LED2_ALED2] = AFE440X_LED2_ALED2VAL, + [LED1_ALED1] = AFE440X_LED1_ALED1VAL, +}; + +static const unsigned int afe4403_channel_leds[] = { + [LED2] = F_ILED2, + [LED1] = F_ILED1, +}; + +static const struct iio_chan_spec afe4403_channels[] = { + /* ADC values */ + AFE440X_INTENSITY_CHAN(LED2, 0), + AFE440X_INTENSITY_CHAN(ALED2, 0), + AFE440X_INTENSITY_CHAN(LED1, 0), + AFE440X_INTENSITY_CHAN(ALED1, 0), + AFE440X_INTENSITY_CHAN(LED2_ALED2, 0), + AFE440X_INTENSITY_CHAN(LED1_ALED1, 0), + /* LED current */ + AFE440X_CURRENT_CHAN(LED2), + AFE440X_CURRENT_CHAN(LED1), +}; + +static const struct afe440x_val_table afe4403_res_table[] = { + { 500000 }, { 250000 }, { 100000 }, { 50000 }, + { 25000 }, { 10000 }, { 1000000 }, { 0 }, +}; +AFE440X_TABLE_ATTR(in_intensity_resistance_available, afe4403_res_table); + +static const struct afe440x_val_table afe4403_cap_table[] = { + { 0, 5000 }, { 0, 10000 }, { 0, 20000 }, { 0, 25000 }, + { 0, 30000 }, { 0, 35000 }, { 0, 45000 }, { 0, 50000 }, + { 0, 55000 }, { 0, 60000 }, { 0, 70000 }, { 0, 75000 }, + { 0, 80000 }, { 0, 85000 }, { 0, 95000 }, { 0, 100000 }, + { 0, 155000 }, { 0, 160000 }, { 0, 170000 }, { 0, 175000 }, + { 0, 180000 }, { 0, 185000 }, { 0, 195000 }, { 0, 200000 }, + { 0, 205000 }, { 0, 210000 }, { 0, 220000 }, { 0, 225000 }, + { 0, 230000 }, { 0, 235000 }, { 0, 245000 }, { 0, 250000 }, +}; +AFE440X_TABLE_ATTR(in_intensity_capacitance_available, afe4403_cap_table); + +static ssize_t afe440x_show_register(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct afe4403_data *afe = iio_priv(indio_dev); + struct afe440x_attr *afe440x_attr = to_afe440x_attr(attr); + unsigned int reg_val; + int vals[2]; + int ret; + + ret = regmap_field_read(afe->fields[afe440x_attr->field], ®_val); + if (ret) + return ret; + + if (reg_val >= afe440x_attr->table_size) + return -EINVAL; + + vals[0] = afe440x_attr->val_table[reg_val].integer; + vals[1] = afe440x_attr->val_table[reg_val].fract; + + return iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, 2, vals); +} + +static ssize_t afe440x_store_register(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct afe4403_data *afe = iio_priv(indio_dev); + struct afe440x_attr *afe440x_attr = to_afe440x_attr(attr); + int val, integer, fract, ret; + + ret = iio_str_to_fixpoint(buf, 100000, &integer, &fract); + if (ret) + return ret; + + for (val = 0; val < afe440x_attr->table_size; val++) + if (afe440x_attr->val_table[val].integer == integer && + afe440x_attr->val_table[val].fract == fract) + break; + if (val == afe440x_attr->table_size) + return -EINVAL; + + ret = regmap_field_write(afe->fields[afe440x_attr->field], val); + if (ret) + return ret; + + return count; +} + +static AFE440X_ATTR(in_intensity1_resistance, F_RF_LED, afe4403_res_table); +static AFE440X_ATTR(in_intensity1_capacitance, F_CF_LED, afe4403_cap_table); + +static AFE440X_ATTR(in_intensity2_resistance, F_RF_LED, afe4403_res_table); +static AFE440X_ATTR(in_intensity2_capacitance, F_CF_LED, afe4403_cap_table); + +static AFE440X_ATTR(in_intensity3_resistance, F_RF_LED1, afe4403_res_table); +static AFE440X_ATTR(in_intensity3_capacitance, F_CF_LED1, afe4403_cap_table); + +static AFE440X_ATTR(in_intensity4_resistance, F_RF_LED1, afe4403_res_table); +static AFE440X_ATTR(in_intensity4_capacitance, F_CF_LED1, afe4403_cap_table); + +static struct attribute *afe440x_attributes[] = { + &dev_attr_in_intensity_resistance_available.attr, + &dev_attr_in_intensity_capacitance_available.attr, + &afe440x_attr_in_intensity1_resistance.dev_attr.attr, + &afe440x_attr_in_intensity1_capacitance.dev_attr.attr, + &afe440x_attr_in_intensity2_resistance.dev_attr.attr, + &afe440x_attr_in_intensity2_capacitance.dev_attr.attr, + &afe440x_attr_in_intensity3_resistance.dev_attr.attr, + &afe440x_attr_in_intensity3_capacitance.dev_attr.attr, + &afe440x_attr_in_intensity4_resistance.dev_attr.attr, + &afe440x_attr_in_intensity4_capacitance.dev_attr.attr, + NULL +}; + +static const struct attribute_group afe440x_attribute_group = { + .attrs = afe440x_attributes +}; + +static int afe4403_read(struct afe4403_data *afe, unsigned int reg, u32 *val) +{ + u8 tx[4] = {AFE440X_CONTROL0, 0x0, 0x0, AFE440X_CONTROL0_READ}; + u8 rx[3]; + int ret; + + /* Enable reading from the device */ + ret = spi_write_then_read(afe->spi, tx, 4, NULL, 0); + if (ret) + return ret; + + ret = spi_write_then_read(afe->spi, ®, 1, rx, sizeof(rx)); + if (ret) + return ret; + + *val = get_unaligned_be24(&rx[0]); + + /* Disable reading from the device */ + tx[3] = AFE440X_CONTROL0_WRITE; + ret = spi_write_then_read(afe->spi, tx, 4, NULL, 0); + if (ret) + return ret; + + return 0; +} + +static int afe4403_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct afe4403_data *afe = iio_priv(indio_dev); + unsigned int reg, field; + int ret; + + switch (chan->type) { + case IIO_INTENSITY: + switch (mask) { + case IIO_CHAN_INFO_RAW: + reg = afe4403_channel_values[chan->address]; + ret = afe4403_read(afe, reg, val); + if (ret) + return ret; + return IIO_VAL_INT; + } + break; + case IIO_CURRENT: + switch (mask) { + case IIO_CHAN_INFO_RAW: + field = afe4403_channel_leds[chan->address]; + ret = regmap_field_read(afe->fields[field], val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = 800000; + return IIO_VAL_INT_PLUS_MICRO; + } + break; + default: + break; + } + + return -EINVAL; +} + +static int afe4403_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct afe4403_data *afe = iio_priv(indio_dev); + unsigned int field = afe4403_channel_leds[chan->address]; + + switch (chan->type) { + case IIO_CURRENT: + switch (mask) { + case IIO_CHAN_INFO_RAW: + return regmap_field_write(afe->fields[field], val); + } + break; + default: + break; + } + + return -EINVAL; +} + +static const struct iio_info afe4403_iio_info = { + .attrs = &afe440x_attribute_group, + .read_raw = afe4403_read_raw, + .write_raw = afe4403_write_raw, +}; + +static irqreturn_t afe4403_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct afe4403_data *afe = iio_priv(indio_dev); + int ret, bit, i = 0; + u8 tx[4] = {AFE440X_CONTROL0, 0x0, 0x0, AFE440X_CONTROL0_READ}; + u8 rx[3]; + + /* Enable reading from the device */ + ret = spi_write_then_read(afe->spi, tx, 4, NULL, 0); + if (ret) + goto err; + + iio_for_each_active_channel(indio_dev, bit) { + ret = spi_write_then_read(afe->spi, + &afe4403_channel_values[bit], 1, + rx, sizeof(rx)); + if (ret) + goto err; + + afe->buffer[i++] = get_unaligned_be24(&rx[0]); + } + + /* Disable reading from the device */ + tx[3] = AFE440X_CONTROL0_WRITE; + ret = spi_write_then_read(afe->spi, tx, 4, NULL, 0); + if (ret) + goto err; + + iio_push_to_buffers_with_timestamp(indio_dev, afe->buffer, + pf->timestamp); +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static void afe4403_regulator_disable(void *data) +{ + struct regulator *regulator = data; + + regulator_disable(regulator); +} + +#define AFE4403_TIMING_PAIRS \ + { AFE440X_LED2STC, 0x000050 }, \ + { AFE440X_LED2ENDC, 0x0003e7 }, \ + { AFE440X_LED1LEDSTC, 0x0007d0 }, \ + { AFE440X_LED1LEDENDC, 0x000bb7 }, \ + { AFE440X_ALED2STC, 0x000438 }, \ + { AFE440X_ALED2ENDC, 0x0007cf }, \ + { AFE440X_LED1STC, 0x000820 }, \ + { AFE440X_LED1ENDC, 0x000bb7 }, \ + { AFE440X_LED2LEDSTC, 0x000000 }, \ + { AFE440X_LED2LEDENDC, 0x0003e7 }, \ + { AFE440X_ALED1STC, 0x000c08 }, \ + { AFE440X_ALED1ENDC, 0x000f9f }, \ + { AFE440X_LED2CONVST, 0x0003ef }, \ + { AFE440X_LED2CONVEND, 0x0007cf }, \ + { AFE440X_ALED2CONVST, 0x0007d7 }, \ + { AFE440X_ALED2CONVEND, 0x000bb7 }, \ + { AFE440X_LED1CONVST, 0x000bbf }, \ + { AFE440X_LED1CONVEND, 0x009c3f }, \ + { AFE440X_ALED1CONVST, 0x000fa7 }, \ + { AFE440X_ALED1CONVEND, 0x001387 }, \ + { AFE440X_ADCRSTSTCT0, 0x0003e8 }, \ + { AFE440X_ADCRSTENDCT0, 0x0003eb }, \ + { AFE440X_ADCRSTSTCT1, 0x0007d0 }, \ + { AFE440X_ADCRSTENDCT1, 0x0007d3 }, \ + { AFE440X_ADCRSTSTCT2, 0x000bb8 }, \ + { AFE440X_ADCRSTENDCT2, 0x000bbb }, \ + { AFE440X_ADCRSTSTCT3, 0x000fa0 }, \ + { AFE440X_ADCRSTENDCT3, 0x000fa3 }, \ + { AFE440X_PRPCOUNT, 0x009c3f }, \ + { AFE440X_PDNCYCLESTC, 0x001518 }, \ + { AFE440X_PDNCYCLEENDC, 0x00991f } + +static const struct reg_sequence afe4403_reg_sequences[] = { + AFE4403_TIMING_PAIRS, + { AFE440X_CONTROL1, AFE440X_CONTROL1_TIMEREN }, + { AFE4403_TIAGAIN, AFE440X_TIAGAIN_ENSEPGAIN }, +}; + +static const struct regmap_range afe4403_yes_ranges[] = { + regmap_reg_range(AFE440X_LED2VAL, AFE440X_LED1_ALED1VAL), +}; + +static const struct regmap_access_table afe4403_volatile_table = { + .yes_ranges = afe4403_yes_ranges, + .n_yes_ranges = ARRAY_SIZE(afe4403_yes_ranges), +}; + +static const struct regmap_config afe4403_regmap_config = { + .reg_bits = 8, + .val_bits = 24, + + .max_register = AFE440X_PDNCYCLEENDC, + .cache_type = REGCACHE_MAPLE, + .volatile_table = &afe4403_volatile_table, +}; + +static const struct of_device_id afe4403_of_match[] = { + { .compatible = "ti,afe4403", }, + { } +}; +MODULE_DEVICE_TABLE(of, afe4403_of_match); + +static int afe4403_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = spi_get_drvdata(to_spi_device(dev)); + struct afe4403_data *afe = iio_priv(indio_dev); + int ret; + + ret = regmap_set_bits(afe->regmap, AFE440X_CONTROL2, + AFE440X_CONTROL2_PDN_AFE); + if (ret) + return ret; + + ret = regulator_disable(afe->regulator); + if (ret) { + dev_err(dev, "Unable to disable regulator\n"); + return ret; + } + + return 0; +} + +static int afe4403_resume(struct device *dev) +{ + struct iio_dev *indio_dev = spi_get_drvdata(to_spi_device(dev)); + struct afe4403_data *afe = iio_priv(indio_dev); + int ret; + + ret = regulator_enable(afe->regulator); + if (ret) { + dev_err(dev, "Unable to enable regulator\n"); + return ret; + } + + ret = regmap_clear_bits(afe->regmap, AFE440X_CONTROL2, + AFE440X_CONTROL2_PDN_AFE); + if (ret) + return ret; + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(afe4403_pm_ops, afe4403_suspend, + afe4403_resume); + +static int afe4403_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct afe4403_data *afe; + int i, ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*afe)); + if (!indio_dev) + return -ENOMEM; + + afe = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + afe->spi = spi; + afe->irq = spi->irq; + + afe->regmap = devm_regmap_init_spi(spi, &afe4403_regmap_config); + if (IS_ERR(afe->regmap)) { + dev_err(dev, "Unable to allocate register map\n"); + return PTR_ERR(afe->regmap); + } + + for (i = 0; i < F_MAX_FIELDS; i++) { + afe->fields[i] = devm_regmap_field_alloc(dev, afe->regmap, + afe4403_reg_fields[i]); + if (IS_ERR(afe->fields[i])) { + dev_err(dev, "Unable to allocate regmap fields\n"); + return PTR_ERR(afe->fields[i]); + } + } + + afe->regulator = devm_regulator_get(dev, "tx_sup"); + if (IS_ERR(afe->regulator)) + return dev_err_probe(dev, PTR_ERR(afe->regulator), + "Unable to get regulator\n"); + + ret = regulator_enable(afe->regulator); + if (ret) { + dev_err(dev, "Unable to enable regulator\n"); + return ret; + } + ret = devm_add_action_or_reset(dev, afe4403_regulator_disable, afe->regulator); + if (ret) { + dev_err(dev, "Unable to add regulator disable action\n"); + return ret; + } + + ret = regmap_write(afe->regmap, AFE440X_CONTROL0, + AFE440X_CONTROL0_SW_RESET); + if (ret) { + dev_err(dev, "Unable to reset device\n"); + return ret; + } + + ret = regmap_multi_reg_write(afe->regmap, afe4403_reg_sequences, + ARRAY_SIZE(afe4403_reg_sequences)); + if (ret) { + dev_err(dev, "Unable to set register defaults\n"); + return ret; + } + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = afe4403_channels; + indio_dev->num_channels = ARRAY_SIZE(afe4403_channels); + indio_dev->name = AFE4403_DRIVER_NAME; + indio_dev->info = &afe4403_iio_info; + + if (afe->irq > 0) { + afe->trig = devm_iio_trigger_alloc(dev, + "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!afe->trig) + return -ENOMEM; + + iio_trigger_set_drvdata(afe->trig, indio_dev); + + ret = devm_iio_trigger_register(dev, afe->trig); + if (ret) { + dev_err(dev, "Unable to register IIO trigger\n"); + return ret; + } + + ret = devm_request_irq(dev, afe->irq, + iio_trigger_generic_data_rdy_poll, + IRQF_NO_THREAD, AFE4403_DRIVER_NAME, + afe->trig); + if (ret) + return ret; + } + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + &iio_pollfunc_store_time, + afe4403_trigger_handler, NULL); + if (ret) { + dev_err(dev, "Unable to setup buffer\n"); + return ret; + } + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) { + dev_err(dev, "Unable to register IIO device\n"); + return ret; + } + + return 0; +} + +static const struct spi_device_id afe4403_ids[] = { + { .name = "afe4403" }, + { } +}; +MODULE_DEVICE_TABLE(spi, afe4403_ids); + +static struct spi_driver afe4403_spi_driver = { + .driver = { + .name = AFE4403_DRIVER_NAME, + .of_match_table = afe4403_of_match, + .pm = pm_sleep_ptr(&afe4403_pm_ops), + }, + .probe = afe4403_probe, + .id_table = afe4403_ids, +}; +module_spi_driver(afe4403_spi_driver); + +MODULE_AUTHOR("Andrew F. Davis "); +MODULE_DESCRIPTION("TI AFE4403 Heart Rate Monitor and Pulse Oximeter AFE"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/health/afe4404.c b/drivers/iio/health/afe4404.c new file mode 100644 index 000000000..22cd46eca --- /dev/null +++ b/drivers/iio/health/afe4404.c @@ -0,0 +1,594 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AFE4404 Heart Rate Monitors and Low-Cost Pulse Oximeters + * + * Copyright (C) 2015-2016 Texas Instruments Incorporated - https://www.ti.com/ + * Andrew F. Davis + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "afe440x.h" + +#define AFE4404_DRIVER_NAME "afe4404" + +/* AFE4404 registers */ +#define AFE4404_TIA_GAIN_SEP 0x20 +#define AFE4404_TIA_GAIN 0x21 +#define AFE4404_PROG_TG_STC 0x34 +#define AFE4404_PROG_TG_ENDC 0x35 +#define AFE4404_LED3LEDSTC 0x36 +#define AFE4404_LED3LEDENDC 0x37 +#define AFE4404_CLKDIV_PRF 0x39 +#define AFE4404_OFFDAC 0x3a +#define AFE4404_DEC 0x3d +#define AFE4404_AVG_LED2_ALED2VAL 0x3f +#define AFE4404_AVG_LED1_ALED1VAL 0x40 + +/* AFE4404 CONTROL2 register fields */ +#define AFE440X_CONTROL2_OSC_ENABLE BIT(9) + +enum afe4404_fields { + /* Gains */ + F_TIA_GAIN_SEP, F_TIA_CF_SEP, + F_TIA_GAIN, TIA_CF, + + /* LED Current */ + F_ILED1, F_ILED2, F_ILED3, + + /* Offset DAC */ + F_OFFDAC_AMB2, F_OFFDAC_LED1, F_OFFDAC_AMB1, F_OFFDAC_LED2, + + /* sentinel */ + F_MAX_FIELDS +}; + +static const struct reg_field afe4404_reg_fields[] = { + /* Gains */ + [F_TIA_GAIN_SEP] = REG_FIELD(AFE4404_TIA_GAIN_SEP, 0, 2), + [F_TIA_CF_SEP] = REG_FIELD(AFE4404_TIA_GAIN_SEP, 3, 5), + [F_TIA_GAIN] = REG_FIELD(AFE4404_TIA_GAIN, 0, 2), + [TIA_CF] = REG_FIELD(AFE4404_TIA_GAIN, 3, 5), + /* LED Current */ + [F_ILED1] = REG_FIELD(AFE440X_LEDCNTRL, 0, 5), + [F_ILED2] = REG_FIELD(AFE440X_LEDCNTRL, 6, 11), + [F_ILED3] = REG_FIELD(AFE440X_LEDCNTRL, 12, 17), + /* Offset DAC */ + [F_OFFDAC_AMB2] = REG_FIELD(AFE4404_OFFDAC, 0, 4), + [F_OFFDAC_LED1] = REG_FIELD(AFE4404_OFFDAC, 5, 9), + [F_OFFDAC_AMB1] = REG_FIELD(AFE4404_OFFDAC, 10, 14), + [F_OFFDAC_LED2] = REG_FIELD(AFE4404_OFFDAC, 15, 19), +}; + +/** + * struct afe4404_data - AFE4404 device instance data + * @regmap: Register map of the device + * @fields: Register fields of the device + * @regulator: Pointer to the regulator for the IC + * @trig: IIO trigger for this device + * @irq: ADC_RDY line interrupt number + * @buffer: Used to construct a scan to push to the iio buffer. + */ +struct afe4404_data { + struct regmap *regmap; + struct regmap_field *fields[F_MAX_FIELDS]; + struct regulator *regulator; + struct iio_trigger *trig; + int irq; + s32 buffer[10] __aligned(8); +}; + +enum afe4404_chan_id { + LED2 = 1, + ALED2, + LED1, + ALED1, + LED2_ALED2, + LED1_ALED1, +}; + +static const unsigned int afe4404_channel_values[] = { + [LED2] = AFE440X_LED2VAL, + [ALED2] = AFE440X_ALED2VAL, + [LED1] = AFE440X_LED1VAL, + [ALED1] = AFE440X_ALED1VAL, + [LED2_ALED2] = AFE440X_LED2_ALED2VAL, + [LED1_ALED1] = AFE440X_LED1_ALED1VAL, +}; + +static const unsigned int afe4404_channel_leds[] = { + [LED2] = F_ILED2, + [ALED2] = F_ILED3, + [LED1] = F_ILED1, +}; + +static const unsigned int afe4404_channel_offdacs[] = { + [LED2] = F_OFFDAC_LED2, + [ALED2] = F_OFFDAC_AMB2, + [LED1] = F_OFFDAC_LED1, + [ALED1] = F_OFFDAC_AMB1, +}; + +static const struct iio_chan_spec afe4404_channels[] = { + /* ADC values */ + AFE440X_INTENSITY_CHAN(LED2, BIT(IIO_CHAN_INFO_OFFSET)), + AFE440X_INTENSITY_CHAN(ALED2, BIT(IIO_CHAN_INFO_OFFSET)), + AFE440X_INTENSITY_CHAN(LED1, BIT(IIO_CHAN_INFO_OFFSET)), + AFE440X_INTENSITY_CHAN(ALED1, BIT(IIO_CHAN_INFO_OFFSET)), + AFE440X_INTENSITY_CHAN(LED2_ALED2, 0), + AFE440X_INTENSITY_CHAN(LED1_ALED1, 0), + /* LED current */ + AFE440X_CURRENT_CHAN(LED2), + AFE440X_CURRENT_CHAN(ALED2), + AFE440X_CURRENT_CHAN(LED1), +}; + +static const struct afe440x_val_table afe4404_res_table[] = { + { .integer = 500000, .fract = 0 }, + { .integer = 250000, .fract = 0 }, + { .integer = 100000, .fract = 0 }, + { .integer = 50000, .fract = 0 }, + { .integer = 25000, .fract = 0 }, + { .integer = 10000, .fract = 0 }, + { .integer = 1000000, .fract = 0 }, + { .integer = 2000000, .fract = 0 }, +}; +AFE440X_TABLE_ATTR(in_intensity_resistance_available, afe4404_res_table); + +static const struct afe440x_val_table afe4404_cap_table[] = { + { .integer = 0, .fract = 5000 }, + { .integer = 0, .fract = 2500 }, + { .integer = 0, .fract = 10000 }, + { .integer = 0, .fract = 7500 }, + { .integer = 0, .fract = 20000 }, + { .integer = 0, .fract = 17500 }, + { .integer = 0, .fract = 25000 }, + { .integer = 0, .fract = 22500 }, +}; +AFE440X_TABLE_ATTR(in_intensity_capacitance_available, afe4404_cap_table); + +static ssize_t afe440x_show_register(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct afe4404_data *afe = iio_priv(indio_dev); + struct afe440x_attr *afe440x_attr = to_afe440x_attr(attr); + unsigned int reg_val; + int vals[2]; + int ret; + + ret = regmap_field_read(afe->fields[afe440x_attr->field], ®_val); + if (ret) + return ret; + + if (reg_val >= afe440x_attr->table_size) + return -EINVAL; + + vals[0] = afe440x_attr->val_table[reg_val].integer; + vals[1] = afe440x_attr->val_table[reg_val].fract; + + return iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, 2, vals); +} + +static ssize_t afe440x_store_register(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct afe4404_data *afe = iio_priv(indio_dev); + struct afe440x_attr *afe440x_attr = to_afe440x_attr(attr); + int val, integer, fract, ret; + + ret = iio_str_to_fixpoint(buf, 100000, &integer, &fract); + if (ret) + return ret; + + for (val = 0; val < afe440x_attr->table_size; val++) + if (afe440x_attr->val_table[val].integer == integer && + afe440x_attr->val_table[val].fract == fract) + break; + if (val == afe440x_attr->table_size) + return -EINVAL; + + ret = regmap_field_write(afe->fields[afe440x_attr->field], val); + if (ret) + return ret; + + return count; +} + +static AFE440X_ATTR(in_intensity1_resistance, F_TIA_GAIN_SEP, afe4404_res_table); +static AFE440X_ATTR(in_intensity1_capacitance, F_TIA_CF_SEP, afe4404_cap_table); + +static AFE440X_ATTR(in_intensity2_resistance, F_TIA_GAIN_SEP, afe4404_res_table); +static AFE440X_ATTR(in_intensity2_capacitance, F_TIA_CF_SEP, afe4404_cap_table); + +static AFE440X_ATTR(in_intensity3_resistance, F_TIA_GAIN, afe4404_res_table); +static AFE440X_ATTR(in_intensity3_capacitance, TIA_CF, afe4404_cap_table); + +static AFE440X_ATTR(in_intensity4_resistance, F_TIA_GAIN, afe4404_res_table); +static AFE440X_ATTR(in_intensity4_capacitance, TIA_CF, afe4404_cap_table); + +static struct attribute *afe440x_attributes[] = { + &dev_attr_in_intensity_resistance_available.attr, + &dev_attr_in_intensity_capacitance_available.attr, + &afe440x_attr_in_intensity1_resistance.dev_attr.attr, + &afe440x_attr_in_intensity1_capacitance.dev_attr.attr, + &afe440x_attr_in_intensity2_resistance.dev_attr.attr, + &afe440x_attr_in_intensity2_capacitance.dev_attr.attr, + &afe440x_attr_in_intensity3_resistance.dev_attr.attr, + &afe440x_attr_in_intensity3_capacitance.dev_attr.attr, + &afe440x_attr_in_intensity4_resistance.dev_attr.attr, + &afe440x_attr_in_intensity4_capacitance.dev_attr.attr, + NULL +}; + +static const struct attribute_group afe440x_attribute_group = { + .attrs = afe440x_attributes +}; + +static int afe4404_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct afe4404_data *afe = iio_priv(indio_dev); + unsigned int value_reg, led_field, offdac_field; + int ret; + + switch (chan->type) { + case IIO_INTENSITY: + switch (mask) { + case IIO_CHAN_INFO_RAW: + value_reg = afe4404_channel_values[chan->address]; + ret = regmap_read(afe->regmap, value_reg, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + offdac_field = afe4404_channel_offdacs[chan->address]; + ret = regmap_field_read(afe->fields[offdac_field], val); + if (ret) + return ret; + return IIO_VAL_INT; + } + break; + case IIO_CURRENT: + switch (mask) { + case IIO_CHAN_INFO_RAW: + led_field = afe4404_channel_leds[chan->address]; + ret = regmap_field_read(afe->fields[led_field], val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = 800000; + return IIO_VAL_INT_PLUS_MICRO; + } + break; + default: + break; + } + + return -EINVAL; +} + +static int afe4404_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct afe4404_data *afe = iio_priv(indio_dev); + unsigned int led_field, offdac_field; + + switch (chan->type) { + case IIO_INTENSITY: + switch (mask) { + case IIO_CHAN_INFO_OFFSET: + offdac_field = afe4404_channel_offdacs[chan->address]; + return regmap_field_write(afe->fields[offdac_field], val); + } + break; + case IIO_CURRENT: + switch (mask) { + case IIO_CHAN_INFO_RAW: + led_field = afe4404_channel_leds[chan->address]; + return regmap_field_write(afe->fields[led_field], val); + } + break; + default: + break; + } + + return -EINVAL; +} + +static const struct iio_info afe4404_iio_info = { + .attrs = &afe440x_attribute_group, + .read_raw = afe4404_read_raw, + .write_raw = afe4404_write_raw, +}; + +static irqreturn_t afe4404_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct afe4404_data *afe = iio_priv(indio_dev); + int ret, bit, i = 0; + + iio_for_each_active_channel(indio_dev, bit) { + ret = regmap_read(afe->regmap, afe4404_channel_values[bit], + &afe->buffer[i++]); + if (ret) + goto err; + } + + iio_push_to_buffers_with_timestamp(indio_dev, afe->buffer, + pf->timestamp); +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static void afe4404_regulator_disable(void *data) +{ + struct regulator *regulator = data; + + regulator_disable(regulator); +} + +/* Default timings from data-sheet */ +#define AFE4404_TIMING_PAIRS \ + { AFE440X_PRPCOUNT, 39999 }, \ + { AFE440X_LED2LEDSTC, 0 }, \ + { AFE440X_LED2LEDENDC, 398 }, \ + { AFE440X_LED2STC, 80 }, \ + { AFE440X_LED2ENDC, 398 }, \ + { AFE440X_ADCRSTSTCT0, 5600 }, \ + { AFE440X_ADCRSTENDCT0, 5606 }, \ + { AFE440X_LED2CONVST, 5607 }, \ + { AFE440X_LED2CONVEND, 6066 }, \ + { AFE4404_LED3LEDSTC, 400 }, \ + { AFE4404_LED3LEDENDC, 798 }, \ + { AFE440X_ALED2STC, 480 }, \ + { AFE440X_ALED2ENDC, 798 }, \ + { AFE440X_ADCRSTSTCT1, 6068 }, \ + { AFE440X_ADCRSTENDCT1, 6074 }, \ + { AFE440X_ALED2CONVST, 6075 }, \ + { AFE440X_ALED2CONVEND, 6534 }, \ + { AFE440X_LED1LEDSTC, 800 }, \ + { AFE440X_LED1LEDENDC, 1198 }, \ + { AFE440X_LED1STC, 880 }, \ + { AFE440X_LED1ENDC, 1198 }, \ + { AFE440X_ADCRSTSTCT2, 6536 }, \ + { AFE440X_ADCRSTENDCT2, 6542 }, \ + { AFE440X_LED1CONVST, 6543 }, \ + { AFE440X_LED1CONVEND, 7003 }, \ + { AFE440X_ALED1STC, 1280 }, \ + { AFE440X_ALED1ENDC, 1598 }, \ + { AFE440X_ADCRSTSTCT3, 7005 }, \ + { AFE440X_ADCRSTENDCT3, 7011 }, \ + { AFE440X_ALED1CONVST, 7012 }, \ + { AFE440X_ALED1CONVEND, 7471 }, \ + { AFE440X_PDNCYCLESTC, 7671 }, \ + { AFE440X_PDNCYCLEENDC, 39199 } + +static const struct reg_sequence afe4404_reg_sequences[] = { + AFE4404_TIMING_PAIRS, + { AFE440X_CONTROL1, AFE440X_CONTROL1_TIMEREN }, + { AFE4404_TIA_GAIN_SEP, AFE440X_TIAGAIN_ENSEPGAIN }, + { AFE440X_CONTROL2, AFE440X_CONTROL2_OSC_ENABLE }, +}; + +static const struct regmap_range afe4404_yes_ranges[] = { + regmap_reg_range(AFE440X_LED2VAL, AFE440X_LED1_ALED1VAL), + regmap_reg_range(AFE4404_AVG_LED2_ALED2VAL, AFE4404_AVG_LED1_ALED1VAL), +}; + +static const struct regmap_access_table afe4404_volatile_table = { + .yes_ranges = afe4404_yes_ranges, + .n_yes_ranges = ARRAY_SIZE(afe4404_yes_ranges), +}; + +static const struct regmap_config afe4404_regmap_config = { + .reg_bits = 8, + .val_bits = 24, + + .max_register = AFE4404_AVG_LED1_ALED1VAL, + .cache_type = REGCACHE_MAPLE, + .volatile_table = &afe4404_volatile_table, +}; + +static const struct of_device_id afe4404_of_match[] = { + { .compatible = "ti,afe4404", }, + { } +}; +MODULE_DEVICE_TABLE(of, afe4404_of_match); + +static int afe4404_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct afe4404_data *afe = iio_priv(indio_dev); + int ret; + + ret = regmap_set_bits(afe->regmap, AFE440X_CONTROL2, + AFE440X_CONTROL2_PDN_AFE); + if (ret) + return ret; + + ret = regulator_disable(afe->regulator); + if (ret) { + dev_err(dev, "Unable to disable regulator\n"); + return ret; + } + + return 0; +} + +static int afe4404_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct afe4404_data *afe = iio_priv(indio_dev); + int ret; + + ret = regulator_enable(afe->regulator); + if (ret) { + dev_err(dev, "Unable to enable regulator\n"); + return ret; + } + + ret = regmap_clear_bits(afe->regmap, AFE440X_CONTROL2, + AFE440X_CONTROL2_PDN_AFE); + if (ret) + return ret; + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(afe4404_pm_ops, afe4404_suspend, + afe4404_resume); + +static int afe4404_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct afe4404_data *afe; + int i, ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*afe)); + if (!indio_dev) + return -ENOMEM; + + afe = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + + afe->irq = client->irq; + + afe->regmap = devm_regmap_init_i2c(client, &afe4404_regmap_config); + if (IS_ERR(afe->regmap)) { + dev_err(dev, "Unable to allocate register map\n"); + return PTR_ERR(afe->regmap); + } + + for (i = 0; i < F_MAX_FIELDS; i++) { + afe->fields[i] = devm_regmap_field_alloc(dev, afe->regmap, + afe4404_reg_fields[i]); + if (IS_ERR(afe->fields[i])) { + dev_err(dev, "Unable to allocate regmap fields\n"); + return PTR_ERR(afe->fields[i]); + } + } + + afe->regulator = devm_regulator_get(dev, "tx_sup"); + if (IS_ERR(afe->regulator)) + return dev_err_probe(dev, PTR_ERR(afe->regulator), + "Unable to get regulator\n"); + + ret = regulator_enable(afe->regulator); + if (ret) { + dev_err(dev, "Unable to enable regulator\n"); + return ret; + } + ret = devm_add_action_or_reset(dev, afe4404_regulator_disable, afe->regulator); + if (ret) { + dev_err(dev, "Unable to enable regulator\n"); + return ret; + } + + ret = regmap_write(afe->regmap, AFE440X_CONTROL0, + AFE440X_CONTROL0_SW_RESET); + if (ret) { + dev_err(dev, "Unable to reset device\n"); + return ret; + } + + ret = regmap_multi_reg_write(afe->regmap, afe4404_reg_sequences, + ARRAY_SIZE(afe4404_reg_sequences)); + if (ret) { + dev_err(dev, "Unable to set register defaults\n"); + return ret; + } + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = afe4404_channels; + indio_dev->num_channels = ARRAY_SIZE(afe4404_channels); + indio_dev->name = AFE4404_DRIVER_NAME; + indio_dev->info = &afe4404_iio_info; + + if (afe->irq > 0) { + afe->trig = devm_iio_trigger_alloc(dev, + "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!afe->trig) + return -ENOMEM; + + iio_trigger_set_drvdata(afe->trig, indio_dev); + + ret = devm_iio_trigger_register(dev, afe->trig); + if (ret) { + dev_err(dev, "Unable to register IIO trigger\n"); + return ret; + } + + ret = devm_request_irq(dev, afe->irq, + iio_trigger_generic_data_rdy_poll, + IRQF_NO_THREAD, AFE4404_DRIVER_NAME, + afe->trig); + if (ret) + return ret; + } + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + &iio_pollfunc_store_time, + afe4404_trigger_handler, NULL); + if (ret) { + dev_err(dev, "Unable to setup buffer\n"); + return ret; + } + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) { + dev_err(dev, "Unable to register IIO device\n"); + return ret; + } + + return 0; +} + +static const struct i2c_device_id afe4404_ids[] = { + { .name = "afe4404" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, afe4404_ids); + +static struct i2c_driver afe4404_i2c_driver = { + .driver = { + .name = AFE4404_DRIVER_NAME, + .of_match_table = afe4404_of_match, + .pm = pm_sleep_ptr(&afe4404_pm_ops), + }, + .probe = afe4404_probe, + .id_table = afe4404_ids, +}; +module_i2c_driver(afe4404_i2c_driver); + +MODULE_AUTHOR("Andrew F. Davis "); +MODULE_DESCRIPTION("TI AFE4404 Heart Rate Monitor and Pulse Oximeter AFE"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/health/afe440x.h b/drivers/iio/health/afe440x.h new file mode 100644 index 000000000..0adea0047 --- /dev/null +++ b/drivers/iio/health/afe440x.h @@ -0,0 +1,155 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AFE440X Heart Rate Monitors and Low-Cost Pulse Oximeters + * + * Copyright (C) 2015 Texas Instruments Incorporated - https://www.ti.com/ + * Andrew F. Davis + */ + +#ifndef _AFE440X_H +#define _AFE440X_H + +/* AFE440X registers */ +#define AFE440X_CONTROL0 0x00 +#define AFE440X_LED2STC 0x01 +#define AFE440X_LED2ENDC 0x02 +#define AFE440X_LED1LEDSTC 0x03 +#define AFE440X_LED1LEDENDC 0x04 +#define AFE440X_ALED2STC 0x05 +#define AFE440X_ALED2ENDC 0x06 +#define AFE440X_LED1STC 0x07 +#define AFE440X_LED1ENDC 0x08 +#define AFE440X_LED2LEDSTC 0x09 +#define AFE440X_LED2LEDENDC 0x0a +#define AFE440X_ALED1STC 0x0b +#define AFE440X_ALED1ENDC 0x0c +#define AFE440X_LED2CONVST 0x0d +#define AFE440X_LED2CONVEND 0x0e +#define AFE440X_ALED2CONVST 0x0f +#define AFE440X_ALED2CONVEND 0x10 +#define AFE440X_LED1CONVST 0x11 +#define AFE440X_LED1CONVEND 0x12 +#define AFE440X_ALED1CONVST 0x13 +#define AFE440X_ALED1CONVEND 0x14 +#define AFE440X_ADCRSTSTCT0 0x15 +#define AFE440X_ADCRSTENDCT0 0x16 +#define AFE440X_ADCRSTSTCT1 0x17 +#define AFE440X_ADCRSTENDCT1 0x18 +#define AFE440X_ADCRSTSTCT2 0x19 +#define AFE440X_ADCRSTENDCT2 0x1a +#define AFE440X_ADCRSTSTCT3 0x1b +#define AFE440X_ADCRSTENDCT3 0x1c +#define AFE440X_PRPCOUNT 0x1d +#define AFE440X_CONTROL1 0x1e +#define AFE440X_LEDCNTRL 0x22 +#define AFE440X_CONTROL2 0x23 +#define AFE440X_ALARM 0x29 +#define AFE440X_LED2VAL 0x2a +#define AFE440X_ALED2VAL 0x2b +#define AFE440X_LED1VAL 0x2c +#define AFE440X_ALED1VAL 0x2d +#define AFE440X_LED2_ALED2VAL 0x2e +#define AFE440X_LED1_ALED1VAL 0x2f +#define AFE440X_CONTROL3 0x31 +#define AFE440X_PDNCYCLESTC 0x32 +#define AFE440X_PDNCYCLEENDC 0x33 + +/* CONTROL0 register fields */ +#define AFE440X_CONTROL0_REG_READ BIT(0) +#define AFE440X_CONTROL0_TM_COUNT_RST BIT(1) +#define AFE440X_CONTROL0_SW_RESET BIT(3) + +/* CONTROL1 register fields */ +#define AFE440X_CONTROL1_TIMEREN BIT(8) + +/* TIAGAIN register fields */ +#define AFE440X_TIAGAIN_ENSEPGAIN BIT(15) + +/* CONTROL2 register fields */ +#define AFE440X_CONTROL2_PDN_AFE BIT(0) +#define AFE440X_CONTROL2_PDN_RX BIT(1) +#define AFE440X_CONTROL2_DYNAMIC4 BIT(3) +#define AFE440X_CONTROL2_DYNAMIC3 BIT(4) +#define AFE440X_CONTROL2_DYNAMIC2 BIT(14) +#define AFE440X_CONTROL2_DYNAMIC1 BIT(20) + +/* CONTROL3 register fields */ +#define AFE440X_CONTROL3_CLKDIV GENMASK(2, 0) + +/* CONTROL0 values */ +#define AFE440X_CONTROL0_WRITE 0x0 +#define AFE440X_CONTROL0_READ 0x1 + +#define AFE440X_INTENSITY_CHAN(_index, _mask) \ + { \ + .type = IIO_INTENSITY, \ + .channel = _index, \ + .address = _index, \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 24, \ + .storagebits = 32, \ + .endianness = IIO_CPU, \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + _mask, \ + .indexed = true, \ + } + +#define AFE440X_CURRENT_CHAN(_index) \ + { \ + .type = IIO_CURRENT, \ + .channel = _index, \ + .address = _index, \ + .scan_index = -1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .indexed = true, \ + .output = true, \ + } + +struct afe440x_val_table { + int integer; + int fract; +}; + +#define AFE440X_TABLE_ATTR(_name, _table) \ +static ssize_t _name ## _show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + ssize_t len = 0; \ + int i; \ + \ + for (i = 0; i < ARRAY_SIZE(_table); i++) \ + len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06u ", \ + _table[i].integer, \ + _table[i].fract); \ + \ + buf[len - 1] = '\n'; \ + \ + return len; \ +} \ +static DEVICE_ATTR_RO(_name) + +struct afe440x_attr { + struct device_attribute dev_attr; + unsigned int field; + const struct afe440x_val_table *val_table; + unsigned int table_size; +}; + +#define to_afe440x_attr(_dev_attr) \ + container_of(_dev_attr, struct afe440x_attr, dev_attr) + +#define AFE440X_ATTR(_name, _field, _table) \ + struct afe440x_attr afe440x_attr_##_name = { \ + .dev_attr = __ATTR(_name, (S_IRUGO | S_IWUSR), \ + afe440x_show_register, \ + afe440x_store_register), \ + .field = _field, \ + .val_table = _table, \ + .table_size = ARRAY_SIZE(_table), \ + } + +#endif /* _AFE440X_H */ diff --git a/drivers/iio/health/max30100.c b/drivers/iio/health/max30100.c new file mode 100644 index 000000000..191540e10 --- /dev/null +++ b/drivers/iio/health/max30100.c @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * max30100.c - Support for MAX30100 heart rate and pulse oximeter sensor + * + * Copyright (C) 2015, 2018 + * Author: Matt Ranostay + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX30100_DRV_NAME "max30100" + +#define MAX30100_REG_INT_STATUS 0x00 +#define MAX30100_REG_INT_STATUS_PWR_RDY BIT(0) +#define MAX30100_REG_INT_STATUS_SPO2_RDY BIT(4) +#define MAX30100_REG_INT_STATUS_HR_RDY BIT(5) +#define MAX30100_REG_INT_STATUS_FIFO_RDY BIT(7) + +#define MAX30100_REG_INT_ENABLE 0x01 +#define MAX30100_REG_INT_ENABLE_SPO2_EN BIT(0) +#define MAX30100_REG_INT_ENABLE_HR_EN BIT(1) +#define MAX30100_REG_INT_ENABLE_FIFO_EN BIT(3) +#define MAX30100_REG_INT_ENABLE_MASK 0xf0 +#define MAX30100_REG_INT_ENABLE_MASK_SHIFT 4 + +#define MAX30100_REG_FIFO_WR_PTR 0x02 +#define MAX30100_REG_FIFO_OVR_CTR 0x03 +#define MAX30100_REG_FIFO_RD_PTR 0x04 +#define MAX30100_REG_FIFO_DATA 0x05 +#define MAX30100_REG_FIFO_DATA_ENTRY_COUNT 16 +#define MAX30100_REG_FIFO_DATA_ENTRY_LEN 4 + +#define MAX30100_REG_MODE_CONFIG 0x06 +#define MAX30100_REG_MODE_CONFIG_MODE_SPO2_EN BIT(0) +#define MAX30100_REG_MODE_CONFIG_MODE_HR_EN BIT(1) +#define MAX30100_REG_MODE_CONFIG_MODE_MASK 0x03 +#define MAX30100_REG_MODE_CONFIG_TEMP_EN BIT(3) +#define MAX30100_REG_MODE_CONFIG_PWR BIT(7) + +#define MAX30100_REG_SPO2_CONFIG 0x07 +#define MAX30100_REG_SPO2_CONFIG_PW_MASK GENMASK(1, 0) +#define MAX30100_REG_SPO2_CONFIG_200US 0x0 +#define MAX30100_REG_SPO2_CONFIG_400US 0x1 +#define MAX30100_REG_SPO2_CONFIG_800US 0x2 +#define MAX30100_REG_SPO2_CONFIG_1600US 0x3 +#define MAX30100_REG_SPO2_CONFIG_100HZ BIT(2) +#define MAX30100_REG_SPO2_CONFIG_HI_RES_EN BIT(6) + +#define MAX30100_REG_LED_CONFIG 0x09 +#define MAX30100_REG_LED_CONFIG_LED_MASK 0x0f +#define MAX30100_REG_LED_CONFIG_RED_LED_SHIFT 4 + +#define MAX30100_REG_LED_CONFIG_24MA 0x07 +#define MAX30100_REG_LED_CONFIG_50MA 0x0f + +#define MAX30100_REG_TEMP_INTEGER 0x16 +#define MAX30100_REG_TEMP_FRACTION 0x17 + +struct max30100_data { + struct i2c_client *client; + struct iio_dev *indio_dev; + struct mutex lock; + struct regmap *regmap; + + __be16 buffer[2]; /* 2 16-bit channels */ +}; + +static bool max30100_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX30100_REG_INT_STATUS: + case MAX30100_REG_MODE_CONFIG: + case MAX30100_REG_FIFO_WR_PTR: + case MAX30100_REG_FIFO_OVR_CTR: + case MAX30100_REG_FIFO_RD_PTR: + case MAX30100_REG_FIFO_DATA: + case MAX30100_REG_TEMP_INTEGER: + case MAX30100_REG_TEMP_FRACTION: + return true; + default: + return false; + } +} + +static const struct regmap_config max30100_regmap_config = { + .name = "max30100_regmap", + + .reg_bits = 8, + .val_bits = 8, + + .max_register = MAX30100_REG_TEMP_FRACTION, + .cache_type = REGCACHE_FLAT, + + .volatile_reg = max30100_is_volatile_reg, +}; + +static const unsigned int max30100_led_current_mapping[] = { + 4400, 7600, 11000, 14200, 17400, + 20800, 24000, 27100, 30600, 33800, + 37000, 40200, 43600, 46800, 50000 +}; + +static const unsigned long max30100_scan_masks[] = {0x3, 0}; + +static const struct iio_chan_spec max30100_channels[] = { + { + .type = IIO_INTENSITY, + .channel2 = IIO_MOD_LIGHT_IR, + .modified = 1, + + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, + }, + }, + { + .type = IIO_INTENSITY, + .channel2 = IIO_MOD_LIGHT_RED, + .modified = 1, + + .scan_index = 1, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, + }, + }, + { + .type = IIO_TEMP, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .scan_index = -1, + }, +}; + +static int max30100_set_powermode(struct max30100_data *data, bool state) +{ + return regmap_update_bits(data->regmap, MAX30100_REG_MODE_CONFIG, + MAX30100_REG_MODE_CONFIG_PWR, + state ? 0 : MAX30100_REG_MODE_CONFIG_PWR); +} + +static int max30100_clear_fifo(struct max30100_data *data) +{ + int ret; + + ret = regmap_write(data->regmap, MAX30100_REG_FIFO_WR_PTR, 0); + if (ret) + return ret; + + ret = regmap_write(data->regmap, MAX30100_REG_FIFO_OVR_CTR, 0); + if (ret) + return ret; + + return regmap_write(data->regmap, MAX30100_REG_FIFO_RD_PTR, 0); +} + +static int max30100_buffer_postenable(struct iio_dev *indio_dev) +{ + struct max30100_data *data = iio_priv(indio_dev); + int ret; + + ret = max30100_set_powermode(data, true); + if (ret) + return ret; + + return max30100_clear_fifo(data); +} + +static int max30100_buffer_predisable(struct iio_dev *indio_dev) +{ + struct max30100_data *data = iio_priv(indio_dev); + + return max30100_set_powermode(data, false); +} + +static const struct iio_buffer_setup_ops max30100_buffer_setup_ops = { + .postenable = max30100_buffer_postenable, + .predisable = max30100_buffer_predisable, +}; + +static inline int max30100_fifo_count(struct max30100_data *data) +{ + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, MAX30100_REG_INT_STATUS, &val); + if (ret) + return ret; + + /* FIFO is almost full */ + if (val & MAX30100_REG_INT_STATUS_FIFO_RDY) + return MAX30100_REG_FIFO_DATA_ENTRY_COUNT - 1; + + return 0; +} + +static int max30100_read_measurement(struct max30100_data *data) +{ + int ret; + + ret = i2c_smbus_read_i2c_block_data(data->client, + MAX30100_REG_FIFO_DATA, + MAX30100_REG_FIFO_DATA_ENTRY_LEN, + (u8 *) &data->buffer); + + return (ret == MAX30100_REG_FIFO_DATA_ENTRY_LEN) ? 0 : ret; +} + +static irqreturn_t max30100_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct max30100_data *data = iio_priv(indio_dev); + int ret, cnt = 0; + + mutex_lock(&data->lock); + + while (cnt || (cnt = max30100_fifo_count(data)) > 0) { + ret = max30100_read_measurement(data); + if (ret) + break; + + iio_push_to_buffers(data->indio_dev, data->buffer); + cnt--; + } + + mutex_unlock(&data->lock); + + return IRQ_HANDLED; +} + +static int max30100_get_current_idx(unsigned int val, int *reg) +{ + int idx; + + /* LED turned off */ + if (val == 0) { + *reg = 0; + return 0; + } + + for (idx = 0; idx < ARRAY_SIZE(max30100_led_current_mapping); idx++) { + if (max30100_led_current_mapping[idx] == val) { + *reg = idx + 1; + return 0; + } + } + + return -EINVAL; +} + +static int max30100_led_init(struct max30100_data *data) +{ + struct device *dev = &data->client->dev; + unsigned int val[2]; + int reg, ret; + + ret = device_property_read_u32_array(dev, "maxim,led-current-microamp", + (unsigned int *) &val, 2); + if (ret) { + /* Default to 24 mA RED LED, 50 mA IR LED */ + reg = (MAX30100_REG_LED_CONFIG_24MA << + MAX30100_REG_LED_CONFIG_RED_LED_SHIFT) | + MAX30100_REG_LED_CONFIG_50MA; + dev_warn(dev, "no led-current-microamp set"); + + return regmap_write(data->regmap, MAX30100_REG_LED_CONFIG, reg); + } + + /* RED LED current */ + ret = max30100_get_current_idx(val[0], ®); + if (ret) { + dev_err(dev, "invalid RED current setting %d", val[0]); + return ret; + } + + ret = regmap_update_bits(data->regmap, MAX30100_REG_LED_CONFIG, + MAX30100_REG_LED_CONFIG_LED_MASK << + MAX30100_REG_LED_CONFIG_RED_LED_SHIFT, + reg << MAX30100_REG_LED_CONFIG_RED_LED_SHIFT); + if (ret) + return ret; + + /* IR LED current */ + ret = max30100_get_current_idx(val[1], ®); + if (ret) { + dev_err(dev, "invalid IR current setting %d", val[1]); + return ret; + } + + return regmap_update_bits(data->regmap, MAX30100_REG_LED_CONFIG, + MAX30100_REG_LED_CONFIG_LED_MASK, reg); +} + +static int max30100_get_pulse_width(unsigned int pwidth_us) +{ + switch (pwidth_us) { + case 200: + return MAX30100_REG_SPO2_CONFIG_200US; + case 400: + return MAX30100_REG_SPO2_CONFIG_400US; + case 800: + return MAX30100_REG_SPO2_CONFIG_800US; + case 1600: + return MAX30100_REG_SPO2_CONFIG_1600US; + default: + return -EINVAL; + } +} + +static int max30100_chip_init(struct max30100_data *data) +{ + int ret; + int pulse_width; + /* set default LED pulse-width to 1600 us */ + unsigned int pulse_us = 1600; + struct device *dev = &data->client->dev; + + /* setup LED current settings */ + ret = max30100_led_init(data); + if (ret) + return ret; + + /* Read LED pulse-width-us from DT */ + device_property_read_u32(dev, "maxim,pulse-width-us", &pulse_us); + + pulse_width = max30100_get_pulse_width(pulse_us); + if (pulse_width < 0) + return dev_err_probe(dev, pulse_width, "invalid LED pulse-width %uus\n", pulse_us); + + /* enable hi-res SPO2 readings at 100Hz */ + ret = regmap_write(data->regmap, MAX30100_REG_SPO2_CONFIG, + MAX30100_REG_SPO2_CONFIG_HI_RES_EN | + MAX30100_REG_SPO2_CONFIG_100HZ | + FIELD_PREP(MAX30100_REG_SPO2_CONFIG_PW_MASK, pulse_width)); + if (ret) + return ret; + + /* enable SPO2 mode */ + ret = regmap_update_bits(data->regmap, MAX30100_REG_MODE_CONFIG, + MAX30100_REG_MODE_CONFIG_MODE_MASK, + MAX30100_REG_MODE_CONFIG_MODE_HR_EN | + MAX30100_REG_MODE_CONFIG_MODE_SPO2_EN); + if (ret) + return ret; + + /* enable FIFO interrupt */ + return regmap_update_bits(data->regmap, MAX30100_REG_INT_ENABLE, + MAX30100_REG_INT_ENABLE_MASK, + MAX30100_REG_INT_ENABLE_FIFO_EN + << MAX30100_REG_INT_ENABLE_MASK_SHIFT); +} + +static int max30100_read_temp(struct max30100_data *data, int *val) +{ + int ret; + unsigned int reg; + + ret = regmap_read(data->regmap, MAX30100_REG_TEMP_INTEGER, ®); + if (ret < 0) + return ret; + *val = reg << 4; + + ret = regmap_read(data->regmap, MAX30100_REG_TEMP_FRACTION, ®); + if (ret < 0) + return ret; + + *val |= reg & 0xf; + *val = sign_extend32(*val, 11); + + return 0; +} + +static int max30100_get_temp(struct max30100_data *data, int *val) +{ + int ret; + + /* start acquisition */ + ret = regmap_set_bits(data->regmap, MAX30100_REG_MODE_CONFIG, + MAX30100_REG_MODE_CONFIG_TEMP_EN); + if (ret) + return ret; + + msleep(35); + + return max30100_read_temp(data, val); +} + +static int max30100_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max30100_data *data = iio_priv(indio_dev); + int ret = -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* + * Temperature reading can only be acquired while engine + * is running + */ + if (!iio_device_try_claim_buffer_mode(indio_dev)) { + ret = -EAGAIN; + } else { + ret = max30100_get_temp(data, val); + if (!ret) + ret = IIO_VAL_INT; + + iio_device_release_buffer_mode(indio_dev); + } + break; + case IIO_CHAN_INFO_SCALE: + *val = 1; /* 0.0625 */ + *val2 = 16; + ret = IIO_VAL_FRACTIONAL; + break; + } + + return ret; +} + +static const struct iio_info max30100_info = { + .read_raw = max30100_read_raw, +}; + +static int max30100_probe(struct i2c_client *client) +{ + struct max30100_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + indio_dev->name = MAX30100_DRV_NAME; + indio_dev->channels = max30100_channels; + indio_dev->info = &max30100_info; + indio_dev->num_channels = ARRAY_SIZE(max30100_channels); + indio_dev->available_scan_masks = max30100_scan_masks; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_iio_kfifo_buffer_setup(&client->dev, indio_dev, + &max30100_buffer_setup_ops); + if (ret) + return ret; + + data = iio_priv(indio_dev); + data->indio_dev = indio_dev; + data->client = client; + + mutex_init(&data->lock); + i2c_set_clientdata(client, indio_dev); + + data->regmap = devm_regmap_init_i2c(client, &max30100_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(&client->dev, "regmap initialization failed.\n"); + return PTR_ERR(data->regmap); + } + max30100_set_powermode(data, false); + + ret = max30100_chip_init(data); + if (ret) + return ret; + + if (client->irq <= 0) { + dev_err(&client->dev, "no valid irq defined\n"); + return -EINVAL; + } + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, max30100_interrupt_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "max30100_irq", indio_dev); + if (ret) + return ret; + + return iio_device_register(indio_dev); +} + +static void max30100_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct max30100_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + max30100_set_powermode(data, false); +} + +static const struct i2c_device_id max30100_id[] = { + { .name = "max30100" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max30100_id); + +static const struct of_device_id max30100_dt_ids[] = { + { .compatible = "maxim,max30100" }, + { } +}; +MODULE_DEVICE_TABLE(of, max30100_dt_ids); + +static struct i2c_driver max30100_driver = { + .driver = { + .name = MAX30100_DRV_NAME, + .of_match_table = max30100_dt_ids, + }, + .probe = max30100_probe, + .remove = max30100_remove, + .id_table = max30100_id, +}; +module_i2c_driver(max30100_driver); + +MODULE_AUTHOR("Matt Ranostay "); +MODULE_DESCRIPTION("MAX30100 heart rate and pulse oximeter sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/health/max30102.c b/drivers/iio/health/max30102.c new file mode 100644 index 000000000..c37316c86 --- /dev/null +++ b/drivers/iio/health/max30102.c @@ -0,0 +1,624 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * max30102.c - Support for MAX30102 heart rate and pulse oximeter sensor + * + * Copyright (C) 2017 Matt Ranostay + * + * Support for MAX30105 optical particle sensor + * Copyright (C) 2017 Peter Meerwald-Stadler + * + * 7-bit I2C chip address: 0x57 + * TODO: proximity power saving feature + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX30102_DRV_NAME "max30102" +#define MAX30102_PART_NUMBER 0x15 + +enum max30102_chip_id { + max30102, + max30105, +}; + +enum max3012_led_idx { + MAX30102_LED_RED, + MAX30102_LED_IR, + MAX30105_LED_GREEN, +}; + +#define MAX30102_REG_INT_STATUS 0x00 +#define MAX30102_REG_INT_STATUS_PWR_RDY BIT(0) +#define MAX30102_REG_INT_STATUS_PROX_INT BIT(4) +#define MAX30102_REG_INT_STATUS_ALC_OVF BIT(5) +#define MAX30102_REG_INT_STATUS_PPG_RDY BIT(6) +#define MAX30102_REG_INT_STATUS_FIFO_RDY BIT(7) + +#define MAX30102_REG_INT_ENABLE 0x02 +#define MAX30102_REG_INT_ENABLE_PROX_INT_EN BIT(4) +#define MAX30102_REG_INT_ENABLE_ALC_OVF_EN BIT(5) +#define MAX30102_REG_INT_ENABLE_PPG_EN BIT(6) +#define MAX30102_REG_INT_ENABLE_FIFO_EN BIT(7) +#define MAX30102_REG_INT_ENABLE_MASK 0xf0 +#define MAX30102_REG_INT_ENABLE_MASK_SHIFT 4 + +#define MAX30102_REG_FIFO_WR_PTR 0x04 +#define MAX30102_REG_FIFO_OVR_CTR 0x05 +#define MAX30102_REG_FIFO_RD_PTR 0x06 +#define MAX30102_REG_FIFO_DATA 0x07 +#define MAX30102_REG_FIFO_DATA_BYTES 3 + +#define MAX30102_REG_FIFO_CONFIG 0x08 +#define MAX30102_REG_FIFO_CONFIG_AVG_4SAMPLES BIT(1) +#define MAX30102_REG_FIFO_CONFIG_AVG_SHIFT 5 +#define MAX30102_REG_FIFO_CONFIG_AFULL BIT(0) + +#define MAX30102_REG_MODE_CONFIG 0x09 +#define MAX30102_REG_MODE_CONFIG_MODE_NONE 0x00 +#define MAX30102_REG_MODE_CONFIG_MODE_HR 0x02 /* red LED */ +#define MAX30102_REG_MODE_CONFIG_MODE_HR_SPO2 0x03 /* red + IR LED */ +#define MAX30102_REG_MODE_CONFIG_MODE_MULTI 0x07 /* multi-LED mode */ +#define MAX30102_REG_MODE_CONFIG_MODE_MASK GENMASK(2, 0) +#define MAX30102_REG_MODE_CONFIG_PWR BIT(7) + +#define MAX30102_REG_MODE_CONTROL_SLOT21 0x11 /* multi-LED control */ +#define MAX30102_REG_MODE_CONTROL_SLOT43 0x12 +#define MAX30102_REG_MODE_CONTROL_SLOT_MASK (GENMASK(6, 4) | GENMASK(2, 0)) +#define MAX30102_REG_MODE_CONTROL_SLOT_SHIFT 4 + +#define MAX30102_REG_SPO2_CONFIG 0x0a +#define MAX30102_REG_SPO2_CONFIG_PULSE_411_US 0x03 +#define MAX30102_REG_SPO2_CONFIG_SR_400HZ 0x03 +#define MAX30102_REG_SPO2_CONFIG_SR_MASK 0x07 +#define MAX30102_REG_SPO2_CONFIG_SR_MASK_SHIFT 2 +#define MAX30102_REG_SPO2_CONFIG_ADC_4096_STEPS BIT(0) +#define MAX30102_REG_SPO2_CONFIG_ADC_MASK_SHIFT 5 + +#define MAX30102_REG_RED_LED_CONFIG 0x0c +#define MAX30102_REG_IR_LED_CONFIG 0x0d +#define MAX30105_REG_GREEN_LED_CONFIG 0x0e + +#define MAX30102_REG_TEMP_CONFIG 0x21 +#define MAX30102_REG_TEMP_CONFIG_TEMP_EN BIT(0) + +#define MAX30102_REG_TEMP_INTEGER 0x1f +#define MAX30102_REG_TEMP_FRACTION 0x20 + +#define MAX30102_REG_REV_ID 0xfe +#define MAX30102_REG_PART_ID 0xff + +struct max30102_data { + struct i2c_client *client; + struct iio_dev *indio_dev; + struct mutex lock; + struct regmap *regmap; + enum max30102_chip_id chip_id; + + u8 buffer[12]; + __be32 processed_buffer[3]; /* 3 x 18-bit (padded to 32-bits) */ +}; + +static const struct regmap_config max30102_regmap_config = { + .name = "max30102_regmap", + + .reg_bits = 8, + .val_bits = 8, +}; + +static const unsigned long max30102_scan_masks[] = { + BIT(MAX30102_LED_RED) | BIT(MAX30102_LED_IR), + 0 +}; + +static const unsigned long max30105_scan_masks[] = { + BIT(MAX30102_LED_RED) | BIT(MAX30102_LED_IR), + BIT(MAX30102_LED_RED) | BIT(MAX30102_LED_IR) | + BIT(MAX30105_LED_GREEN), + 0 +}; + +#define MAX30102_INTENSITY_CHANNEL(_si, _mod) { \ + .type = IIO_INTENSITY, \ + .channel2 = _mod, \ + .modified = 1, \ + .scan_index = _si, \ + .scan_type = { \ + .sign = 'u', \ + .shift = 8, \ + .realbits = 18, \ + .storagebits = 32, \ + .endianness = IIO_BE, \ + }, \ + } + +static const struct iio_chan_spec max30102_channels[] = { + MAX30102_INTENSITY_CHANNEL(MAX30102_LED_RED, IIO_MOD_LIGHT_RED), + MAX30102_INTENSITY_CHANNEL(MAX30102_LED_IR, IIO_MOD_LIGHT_IR), + { + .type = IIO_TEMP, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .scan_index = -1, + }, +}; + +static const struct iio_chan_spec max30105_channels[] = { + MAX30102_INTENSITY_CHANNEL(MAX30102_LED_RED, IIO_MOD_LIGHT_RED), + MAX30102_INTENSITY_CHANNEL(MAX30102_LED_IR, IIO_MOD_LIGHT_IR), + MAX30102_INTENSITY_CHANNEL(MAX30105_LED_GREEN, IIO_MOD_LIGHT_GREEN), + { + .type = IIO_TEMP, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .scan_index = -1, + }, +}; + +static int max30102_set_power(struct max30102_data *data, bool en) +{ + return regmap_update_bits(data->regmap, MAX30102_REG_MODE_CONFIG, + MAX30102_REG_MODE_CONFIG_PWR, + en ? 0 : MAX30102_REG_MODE_CONFIG_PWR); +} + +static int max30102_set_powermode(struct max30102_data *data, u8 mode, bool en) +{ + u8 reg = mode; + + if (!en) + reg |= MAX30102_REG_MODE_CONFIG_PWR; + + return regmap_update_bits(data->regmap, MAX30102_REG_MODE_CONFIG, + MAX30102_REG_MODE_CONFIG_PWR | + MAX30102_REG_MODE_CONFIG_MODE_MASK, reg); +} + +#define MAX30102_MODE_CONTROL_LED_SLOTS(slot2, slot1) \ + ((slot2 << MAX30102_REG_MODE_CONTROL_SLOT_SHIFT) | slot1) + +static int max30102_buffer_postenable(struct iio_dev *indio_dev) +{ + struct max30102_data *data = iio_priv(indio_dev); + int ret; + u8 reg; + + switch (*indio_dev->active_scan_mask) { + case BIT(MAX30102_LED_RED) | BIT(MAX30102_LED_IR): + reg = MAX30102_REG_MODE_CONFIG_MODE_HR_SPO2; + break; + case BIT(MAX30102_LED_RED) | BIT(MAX30102_LED_IR) | + BIT(MAX30105_LED_GREEN): + ret = regmap_update_bits(data->regmap, + MAX30102_REG_MODE_CONTROL_SLOT21, + MAX30102_REG_MODE_CONTROL_SLOT_MASK, + MAX30102_MODE_CONTROL_LED_SLOTS(2, 1)); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, + MAX30102_REG_MODE_CONTROL_SLOT43, + MAX30102_REG_MODE_CONTROL_SLOT_MASK, + MAX30102_MODE_CONTROL_LED_SLOTS(0, 3)); + if (ret) + return ret; + + reg = MAX30102_REG_MODE_CONFIG_MODE_MULTI; + break; + default: + return -EINVAL; + } + + return max30102_set_powermode(data, reg, true); +} + +static int max30102_buffer_predisable(struct iio_dev *indio_dev) +{ + struct max30102_data *data = iio_priv(indio_dev); + + return max30102_set_powermode(data, MAX30102_REG_MODE_CONFIG_MODE_NONE, + false); +} + +static const struct iio_buffer_setup_ops max30102_buffer_setup_ops = { + .postenable = max30102_buffer_postenable, + .predisable = max30102_buffer_predisable, +}; + +static inline int max30102_fifo_count(struct max30102_data *data) +{ + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, MAX30102_REG_INT_STATUS, &val); + if (ret) + return ret; + + /* FIFO has one sample slot left */ + if (val & MAX30102_REG_INT_STATUS_FIFO_RDY) + return 1; + + return 0; +} + +#define MAX30102_COPY_DATA(i) \ + memcpy(&data->processed_buffer[(i)], \ + &buffer[(i) * MAX30102_REG_FIFO_DATA_BYTES], \ + MAX30102_REG_FIFO_DATA_BYTES) + +static int max30102_read_measurement(struct max30102_data *data, + unsigned int measurements) +{ + int ret; + u8 *buffer = (u8 *) &data->buffer; + + ret = i2c_smbus_read_i2c_block_data(data->client, + MAX30102_REG_FIFO_DATA, + measurements * + MAX30102_REG_FIFO_DATA_BYTES, + buffer); + + switch (measurements) { + case 3: + MAX30102_COPY_DATA(2); + fallthrough; + case 2: + MAX30102_COPY_DATA(1); + fallthrough; + case 1: + MAX30102_COPY_DATA(0); + break; + default: + return -EINVAL; + } + + return (ret == measurements * MAX30102_REG_FIFO_DATA_BYTES) ? + 0 : -EINVAL; +} + +static irqreturn_t max30102_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct max30102_data *data = iio_priv(indio_dev); + unsigned int measurements = bitmap_weight(indio_dev->active_scan_mask, + iio_get_masklength(indio_dev)); + int ret, cnt = 0; + + mutex_lock(&data->lock); + + while (cnt || (cnt = max30102_fifo_count(data)) > 0) { + ret = max30102_read_measurement(data, measurements); + if (ret) + break; + + iio_push_to_buffers(data->indio_dev, data->processed_buffer); + cnt--; + } + + mutex_unlock(&data->lock); + + return IRQ_HANDLED; +} + +static int max30102_get_current_idx(unsigned int val, int *reg) +{ + /* each step is 0.200 mA */ + *reg = val / 200; + + return *reg > 0xff ? -EINVAL : 0; +} + +static int max30102_led_init(struct max30102_data *data) +{ + struct device *dev = &data->client->dev; + unsigned int val; + int reg, ret; + + ret = device_property_read_u32(dev, "maxim,red-led-current-microamp", &val); + if (ret) { + dev_info(dev, "no red-led-current-microamp set\n"); + + /* Default to 7 mA RED LED */ + val = 7000; + } + + ret = max30102_get_current_idx(val, ®); + if (ret) { + dev_err(dev, "invalid RED LED current setting %d\n", val); + return ret; + } + + ret = regmap_write(data->regmap, MAX30102_REG_RED_LED_CONFIG, reg); + if (ret) + return ret; + + if (data->chip_id == max30105) { + ret = device_property_read_u32(dev, + "maxim,green-led-current-microamp", &val); + if (ret) { + dev_info(dev, "no green-led-current-microamp set\n"); + + /* Default to 7 mA green LED */ + val = 7000; + } + + ret = max30102_get_current_idx(val, ®); + if (ret) { + dev_err(dev, "invalid green LED current setting %d\n", + val); + return ret; + } + + ret = regmap_write(data->regmap, MAX30105_REG_GREEN_LED_CONFIG, + reg); + if (ret) + return ret; + } + + ret = device_property_read_u32(dev, "maxim,ir-led-current-microamp", &val); + if (ret) { + dev_info(dev, "no ir-led-current-microamp set\n"); + + /* Default to 7 mA IR LED */ + val = 7000; + } + + ret = max30102_get_current_idx(val, ®); + if (ret) { + dev_err(dev, "invalid IR LED current setting %d\n", val); + return ret; + } + + return regmap_write(data->regmap, MAX30102_REG_IR_LED_CONFIG, reg); +} + +static int max30102_chip_init(struct max30102_data *data) +{ + int ret; + + /* setup LED current settings */ + ret = max30102_led_init(data); + if (ret) + return ret; + + /* configure 18-bit HR + SpO2 readings at 400Hz */ + ret = regmap_write(data->regmap, MAX30102_REG_SPO2_CONFIG, + (MAX30102_REG_SPO2_CONFIG_ADC_4096_STEPS + << MAX30102_REG_SPO2_CONFIG_ADC_MASK_SHIFT) | + (MAX30102_REG_SPO2_CONFIG_SR_400HZ + << MAX30102_REG_SPO2_CONFIG_SR_MASK_SHIFT) | + MAX30102_REG_SPO2_CONFIG_PULSE_411_US); + if (ret) + return ret; + + /* average 4 samples + generate FIFO interrupt */ + ret = regmap_write(data->regmap, MAX30102_REG_FIFO_CONFIG, + (MAX30102_REG_FIFO_CONFIG_AVG_4SAMPLES + << MAX30102_REG_FIFO_CONFIG_AVG_SHIFT) | + MAX30102_REG_FIFO_CONFIG_AFULL); + if (ret) + return ret; + + /* enable FIFO interrupt */ + return regmap_update_bits(data->regmap, MAX30102_REG_INT_ENABLE, + MAX30102_REG_INT_ENABLE_MASK, + MAX30102_REG_INT_ENABLE_FIFO_EN); +} + +static int max30102_read_temp(struct max30102_data *data, int *val) +{ + int ret; + unsigned int reg; + + ret = regmap_read(data->regmap, MAX30102_REG_TEMP_INTEGER, ®); + if (ret < 0) + return ret; + *val = reg << 4; + + ret = regmap_read(data->regmap, MAX30102_REG_TEMP_FRACTION, ®); + if (ret < 0) + return ret; + + *val |= reg & 0xf; + *val = sign_extend32(*val, 11); + + return 0; +} + +static int max30102_get_temp(struct max30102_data *data, int *val, bool en) +{ + int ret; + + if (en) { + ret = max30102_set_power(data, true); + if (ret) + return ret; + } + + /* start acquisition */ + ret = regmap_set_bits(data->regmap, MAX30102_REG_TEMP_CONFIG, + MAX30102_REG_TEMP_CONFIG_TEMP_EN); + if (ret) + goto out; + + msleep(35); + ret = max30102_read_temp(data, val); + +out: + if (en) + max30102_set_power(data, false); + + return ret; +} + +static int max30102_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max30102_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + /* + * Temperature reading can only be acquired when not in + * shutdown; leave shutdown briefly when buffer not running + */ + IIO_DEV_GUARD_CURRENT_MODE(indio_dev); + + ret = max30102_get_temp(data, val, !iio_buffer_enabled(indio_dev)); + if (ret) + return ret; + + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_SCALE: + *val = 1000; /* 62.5 */ + *val2 = 16; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } +} + +static const struct iio_info max30102_info = { + .read_raw = max30102_read_raw, +}; + +static int max30102_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct max30102_data *data; + struct iio_dev *indio_dev; + int ret; + unsigned int reg; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + indio_dev->name = MAX30102_DRV_NAME; + indio_dev->info = &max30102_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + data = iio_priv(indio_dev); + data->indio_dev = indio_dev; + data->client = client; + data->chip_id = id->driver_data; + + mutex_init(&data->lock); + i2c_set_clientdata(client, indio_dev); + + switch (data->chip_id) { + case max30105: + indio_dev->channels = max30105_channels; + indio_dev->num_channels = ARRAY_SIZE(max30105_channels); + indio_dev->available_scan_masks = max30105_scan_masks; + break; + case max30102: + indio_dev->channels = max30102_channels; + indio_dev->num_channels = ARRAY_SIZE(max30102_channels); + indio_dev->available_scan_masks = max30102_scan_masks; + break; + default: + return -ENODEV; + } + + ret = devm_iio_kfifo_buffer_setup(&client->dev, indio_dev, + &max30102_buffer_setup_ops); + if (ret) + return ret; + + data->regmap = devm_regmap_init_i2c(client, &max30102_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(&client->dev, "regmap initialization failed\n"); + return PTR_ERR(data->regmap); + } + + /* check part ID */ + ret = regmap_read(data->regmap, MAX30102_REG_PART_ID, ®); + if (ret) + return ret; + if (reg != MAX30102_PART_NUMBER) + return -ENODEV; + + /* show revision ID */ + ret = regmap_read(data->regmap, MAX30102_REG_REV_ID, ®); + if (ret) + return ret; + dev_dbg(&client->dev, "max3010x revision %02x\n", reg); + + /* clear mode setting, chip shutdown */ + ret = max30102_set_powermode(data, MAX30102_REG_MODE_CONFIG_MODE_NONE, + false); + if (ret) + return ret; + + ret = max30102_chip_init(data); + if (ret) + return ret; + + if (client->irq <= 0) { + dev_err(&client->dev, "no valid irq defined\n"); + return -EINVAL; + } + + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, max30102_interrupt_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "max30102_irq", indio_dev); + if (ret) + return ret; + + return iio_device_register(indio_dev); +} + +static void max30102_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct max30102_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + max30102_set_power(data, false); +} + +static const struct i2c_device_id max30102_id[] = { + { .name = "max30101", .driver_data = max30105 }, + { .name = "max30102", .driver_data = max30102 }, + { .name = "max30105", .driver_data = max30105 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max30102_id); + +static const struct of_device_id max30102_dt_ids[] = { + { .compatible = "maxim,max30101" }, + { .compatible = "maxim,max30102" }, + { .compatible = "maxim,max30105" }, + { } +}; +MODULE_DEVICE_TABLE(of, max30102_dt_ids); + +static struct i2c_driver max30102_driver = { + .driver = { + .name = MAX30102_DRV_NAME, + .of_match_table = max30102_dt_ids, + }, + .probe = max30102_probe, + .remove = max30102_remove, + .id_table = max30102_id, +}; +module_i2c_driver(max30102_driver); + +MODULE_AUTHOR("Matt Ranostay "); +MODULE_DESCRIPTION("MAX30102 heart rate/pulse oximeter and MAX30105 particle sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/humidity/Kconfig b/drivers/iio/humidity/Kconfig new file mode 100644 index 000000000..54f11f000 --- /dev/null +++ b/drivers/iio/humidity/Kconfig @@ -0,0 +1,146 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# humidity sensor drivers +# +menu "Humidity sensors" + +config AM2315 + tristate "Aosong AM2315 relative humidity and temperature sensor" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for the Aosong AM2315 + relative humidity and ambient temperature sensor. + + This driver can also be built as a module. If so, the module will + be called am2315. + +config DHT11 + tristate "DHT11 (and compatible sensors) driver" + depends on GPIOLIB || COMPILE_TEST + help + This driver supports reading data via a single interrupt + generating GPIO line. Currently tested are DHT11 and DHT22. + Other sensors should work as well as long as they speak the + same protocol. + +config ENS210 + tristate "ENS210 temperature and humidity sensor" + depends on I2C + select CRC7 + help + Say yes here to get support for the ScioSense ENS210 family of + humidity and temperature sensors. + + This driver can also be built as a module. If so, the module will be + called ens210. + +config HDC100X + tristate "TI HDC100x relative humidity and temperature sensor" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for the Texas Instruments + HDC1000, HDC1008, HDC1010, HDC1050, and HDC1080 relative + humidity and temperature sensors. + + To compile this driver as a module, choose M here: the module + will be called hdc100x. + +config HDC2010 + tristate "TI HDC2010 relative humidity and temperature sensor" + depends on I2C + help + Say yes here to build support for the Texas Instruments + HDC2010 and HDC2080 relative humidity and temperature sensors. + + To compile this driver as a module, choose M here: the module + will be called hdc2010. + +config HDC3020 + tristate "TI HDC3020 relative humidity and temperature sensor" + depends on I2C + select CRC8 + help + Say yes here to build support for the Texas Instruments + HDC3020, HDC3021 and HDC3022 relative humidity and temperature + sensors. + + To compile this driver as a module, choose M here: the module + will be called hdc3020. + +config HID_SENSOR_HUMIDITY + tristate "HID Environmental humidity sensor" + depends on HID_SENSOR_HUB + select IIO_BUFFER + select HID_SENSOR_IIO_COMMON + select HID_SENSOR_IIO_TRIGGER + help + Say yes here to build support for the HID SENSOR + humidity driver + + To compile this driver as a module, choose M here: the module + will be called hid-sensor-humidity. + +config HTS221 + tristate "STMicroelectronics HTS221 sensor Driver" + depends on (I2C || SPI) + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select HTS221_I2C if (I2C) + select HTS221_SPI if (SPI_MASTER) + help + Say yes here to build support for STMicroelectronics HTS221 + temperature-humidity sensor + + To compile this driver as a module, choose M here: the module + will be called hts221. + +config HTS221_I2C + tristate + depends on HTS221 + select REGMAP_I2C + +config HTS221_SPI + tristate + depends on HTS221 + select REGMAP_SPI + +config HTU21 + tristate "Measurement Specialties HTU21 humidity & temperature sensor" + depends on I2C + select IIO_MS_SENSORS_I2C + help + If you say yes here you get support for the Measurement Specialties + HTU21 humidity and temperature sensor. + This driver is also used for MS8607 temperature, pressure & humidity + sensor + + This driver can also be built as a module. If so, the module will + be called htu21. + +config SI7005 + tristate "SI7005 relative humidity and temperature sensor" + depends on I2C + help + Say yes here to build support for the Silabs Si7005 relative + humidity and temperature sensor. + + To compile this driver as a module, choose M here: the module + will be called si7005. This driver also + supports Hoperf TH02 Humidity and Temperature Sensor. + +config SI7020 + tristate "Si7013/20/21 Relative Humidity and Temperature Sensors" + depends on I2C + help + Say yes here to build support for the Silicon Labs Si7013/20/21 + Relative Humidity and Temperature Sensors. This driver also + supports Hoperf TH06 Humidity and Temperature Sensor. + + To compile this driver as a module, choose M here: the module + will be called si7020. + +endmenu diff --git a/drivers/iio/humidity/Makefile b/drivers/iio/humidity/Makefile new file mode 100644 index 000000000..34b3dc749 --- /dev/null +++ b/drivers/iio/humidity/Makefile @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for IIO humidity sensor drivers +# + +obj-$(CONFIG_AM2315) += am2315.o +obj-$(CONFIG_DHT11) += dht11.o +obj-$(CONFIG_ENS210) += ens210.o +obj-$(CONFIG_HDC100X) += hdc100x.o +obj-$(CONFIG_HDC2010) += hdc2010.o +obj-$(CONFIG_HDC3020) += hdc3020.o +obj-$(CONFIG_HID_SENSOR_HUMIDITY) += hid-sensor-humidity.o + +hts221-y := hts221_core.o \ + hts221_buffer.o +obj-$(CONFIG_HTS221) += hts221.o +obj-$(CONFIG_HTS221_I2C) += hts221_i2c.o +obj-$(CONFIG_HTS221_SPI) += hts221_spi.o + +obj-$(CONFIG_HTU21) += htu21.o +obj-$(CONFIG_SI7005) += si7005.o +obj-$(CONFIG_SI7020) += si7020.o + +ccflags-y += -I$(srctree)/drivers/iio/common/hid-sensors diff --git a/drivers/iio/humidity/am2315.c b/drivers/iio/humidity/am2315.c new file mode 100644 index 000000000..f29baa251 --- /dev/null +++ b/drivers/iio/humidity/am2315.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Aosong AM2315 relative humidity and temperature + * + * Copyright (c) 2016, Intel Corporation. + * + * 7-bit I2C address: 0x5C. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AM2315_REG_HUM_MSB 0x00 +#define AM2315_REG_HUM_LSB 0x01 +#define AM2315_REG_TEMP_MSB 0x02 +#define AM2315_REG_TEMP_LSB 0x03 + +#define AM2315_FUNCTION_READ 0x03 +#define AM2315_HUM_OFFSET 2 +#define AM2315_TEMP_OFFSET 4 +#define AM2315_ALL_CHANNEL_MASK GENMASK(1, 0) + +#define AM2315_DRIVER_NAME "am2315" + +struct am2315_data { + struct i2c_client *client; + struct mutex lock; + /* Ensure timestamp is naturally aligned */ + struct { + s16 chans[2]; + aligned_s64 timestamp; + } scan; +}; + +struct am2315_sensor_data { + s16 hum_data; + s16 temp_data; +}; + +static const struct iio_chan_spec am2315_channels[] = { + { + .type = IIO_HUMIDITYRELATIVE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +/* CRC calculation algorithm, as specified in the datasheet (page 13). */ +static u16 am2315_crc(u8 *data, u8 nr_bytes) +{ + int i; + u16 crc = 0xffff; + + while (nr_bytes--) { + crc ^= *data++; + for (i = 0; i < 8; i++) { + if (crc & 0x01) { + crc >>= 1; + crc ^= 0xA001; + } else { + crc >>= 1; + } + } + } + + return crc; +} + +/* Simple function that sends a few bytes to the device to wake it up. */ +static void am2315_ping(struct i2c_client *client) +{ + i2c_smbus_read_byte_data(client, AM2315_REG_HUM_MSB); +} + +static int am2315_read_data(struct am2315_data *data, + struct am2315_sensor_data *sensor_data) +{ + int ret; + /* tx_buf format: */ + u8 tx_buf[3] = { AM2315_FUNCTION_READ, AM2315_REG_HUM_MSB, 4 }; + /* + * rx_buf format: + * + * + * + */ + u8 rx_buf[8]; + u16 crc; + + /* First wake up the device. */ + am2315_ping(data->client); + + mutex_lock(&data->lock); + ret = i2c_master_send(data->client, tx_buf, sizeof(tx_buf)); + if (ret < 0) { + dev_err(&data->client->dev, "failed to send read request\n"); + goto exit_unlock; + } + /* Wait 2-3 ms, then read back the data sent by the device. */ + usleep_range(2000, 3000); + /* Do a bulk data read, then pick out what we need. */ + ret = i2c_master_recv(data->client, rx_buf, sizeof(rx_buf)); + if (ret < 0) { + dev_err(&data->client->dev, "failed to read sensor data\n"); + goto exit_unlock; + } + mutex_unlock(&data->lock); + /* + * Do a CRC check on the data and compare it to the value + * calculated by the device. + */ + crc = am2315_crc(rx_buf, sizeof(rx_buf) - 2); + if ((crc & 0xff) != rx_buf[6] || (crc >> 8) != rx_buf[7]) { + dev_err(&data->client->dev, "failed to verify sensor data\n"); + return -EIO; + } + + sensor_data->hum_data = (rx_buf[AM2315_HUM_OFFSET] << 8) | + rx_buf[AM2315_HUM_OFFSET + 1]; + sensor_data->temp_data = (rx_buf[AM2315_TEMP_OFFSET] << 8) | + rx_buf[AM2315_TEMP_OFFSET + 1]; + + return ret; + +exit_unlock: + mutex_unlock(&data->lock); + return ret; +} + +static irqreturn_t am2315_trigger_handler(int irq, void *p) +{ + int i; + int ret; + int bit; + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct am2315_data *data = iio_priv(indio_dev); + struct am2315_sensor_data sensor_data; + + ret = am2315_read_data(data, &sensor_data); + if (ret < 0) + goto err; + + mutex_lock(&data->lock); + if (*(indio_dev->active_scan_mask) == AM2315_ALL_CHANNEL_MASK) { + data->scan.chans[0] = sensor_data.hum_data; + data->scan.chans[1] = sensor_data.temp_data; + } else { + i = 0; + iio_for_each_active_channel(indio_dev, bit) { + data->scan.chans[i] = (bit ? sensor_data.temp_data : + sensor_data.hum_data); + i++; + } + } + mutex_unlock(&data->lock); + + iio_push_to_buffers_with_timestamp(indio_dev, &data->scan, + pf->timestamp); +err: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int am2315_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct am2315_sensor_data sensor_data; + struct am2315_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = am2315_read_data(data, &sensor_data); + if (ret < 0) + return ret; + *val = (chan->type == IIO_HUMIDITYRELATIVE) ? + sensor_data.hum_data : sensor_data.temp_data; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 100; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static const struct iio_info am2315_info = { + .read_raw = am2315_read_raw, +}; + +static int am2315_probe(struct i2c_client *client) +{ + int ret; + struct iio_dev *indio_dev; + struct am2315_data *data; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + i2c_set_clientdata(client, indio_dev); + mutex_init(&data->lock); + + indio_dev->info = &am2315_info; + indio_dev->name = AM2315_DRIVER_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = am2315_channels; + indio_dev->num_channels = ARRAY_SIZE(am2315_channels); + + ret = devm_iio_triggered_buffer_setup(&client->dev, + indio_dev, iio_pollfunc_store_time, + am2315_trigger_handler, NULL); + if (ret < 0) { + dev_err(&client->dev, "iio triggered buffer setup failed\n"); + return ret; + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id am2315_i2c_id[] = { + { .name = "am2315" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, am2315_i2c_id); + +static struct i2c_driver am2315_driver = { + .driver = { + .name = "am2315", + }, + .probe = am2315_probe, + .id_table = am2315_i2c_id, +}; + +module_i2c_driver(am2315_driver); + +MODULE_AUTHOR("Tiberiu Breana "); +MODULE_DESCRIPTION("Aosong AM2315 relative humidity and temperature"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/humidity/dht11.c b/drivers/iio/humidity/dht11.c new file mode 100644 index 000000000..7690df97f --- /dev/null +++ b/drivers/iio/humidity/dht11.c @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * DHT11/DHT22 bit banging GPIO driver + * + * Copyright (c) Harald Geyer + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DHT11_DATA_VALID_TIME 2000000000 /* 2s in ns */ + +#define DHT11_EDGES_PREAMBLE 2 +#define DHT11_BITS_PER_READ 40 +/* + * Note that when reading the sensor actually 84 edges are detected, but + * since the last edge is not significant, we only store 83: + */ +#define DHT11_EDGES_PER_READ (2 * DHT11_BITS_PER_READ + \ + DHT11_EDGES_PREAMBLE + 1) + +/* + * Data transmission timing: + * Data bits are encoded as pulse length (high time) on the data line. + * 0-bit: 22-30uS -- typically 26uS (AM2302) + * 1-bit: 68-75uS -- typically 70uS (AM2302) + * The acutal timings also depend on the properties of the cable, with + * longer cables typically making pulses shorter. + * + * Our decoding depends on the time resolution of the system: + * timeres > 34uS ... don't know what a 1-tick pulse is + * 34uS > timeres > 30uS ... no problem (30kHz and 32kHz clocks) + * 30uS > timeres > 23uS ... don't know what a 2-tick pulse is + * timeres < 23uS ... no problem + * + * Luckily clocks in the 33-44kHz range are quite uncommon, so we can + * support most systems if the threshold for decoding a pulse as 1-bit + * is chosen carefully. If somebody really wants to support clocks around + * 40kHz, where this driver is most unreliable, there are two options. + * a) select an implementation using busy loop polling on those systems + * b) use the checksum to do some probabilistic decoding + */ +#define DHT11_START_TRANSMISSION_MIN 18000 /* us */ +#define DHT11_START_TRANSMISSION_MAX 20000 /* us */ +#define DHT11_MIN_TIMERES 34000 /* ns */ +#define DHT11_THRESHOLD 49000 /* ns */ +#define DHT11_AMBIG_LOW 23000 /* ns */ +#define DHT11_AMBIG_HIGH 30000 /* ns */ + +struct dht11 { + struct device *dev; + + struct gpio_desc *gpiod; + int irq; + + struct completion completion; + /* The iio sysfs interface doesn't prevent concurrent reads: */ + struct mutex lock; + + s64 timestamp; + int temperature; + int humidity; + + /* num_edges: -1 means "no transmission in progress" */ + int num_edges; + struct {s64 ts; int value; } edges[DHT11_EDGES_PER_READ]; +}; + +#ifdef CONFIG_DYNAMIC_DEBUG +/* + * dht11_edges_print: show the data as actually received by the + * driver. + */ +static void dht11_edges_print(struct dht11 *dht11) +{ + int i; + + dev_dbg(dht11->dev, "%d edges detected:\n", dht11->num_edges); + for (i = 1; i < dht11->num_edges; ++i) { + dev_dbg(dht11->dev, "%d: %lld ns %s\n", i, + dht11->edges[i].ts - dht11->edges[i - 1].ts, + str_high_low(dht11->edges[i - 1].value)); + } +} +#endif /* CONFIG_DYNAMIC_DEBUG */ + +static unsigned char dht11_decode_byte(char *bits) +{ + unsigned char ret = 0; + int i; + + for (i = 0; i < 8; ++i) { + ret <<= 1; + if (bits[i]) + ++ret; + } + + return ret; +} + +static int dht11_decode(struct dht11 *dht11, int offset) +{ + int i, t; + char bits[DHT11_BITS_PER_READ]; + unsigned char temp_int, temp_dec, hum_int, hum_dec, checksum; + + for (i = 0; i < DHT11_BITS_PER_READ; ++i) { + t = dht11->edges[offset + 2 * i + 2].ts - + dht11->edges[offset + 2 * i + 1].ts; + if (!dht11->edges[offset + 2 * i + 1].value) { + dev_dbg(dht11->dev, + "lost synchronisation at edge %d\n", + offset + 2 * i + 1); + return -EIO; + } + bits[i] = t > DHT11_THRESHOLD; + } + + hum_int = dht11_decode_byte(bits); + hum_dec = dht11_decode_byte(&bits[8]); + temp_int = dht11_decode_byte(&bits[16]); + temp_dec = dht11_decode_byte(&bits[24]); + checksum = dht11_decode_byte(&bits[32]); + + if (((hum_int + hum_dec + temp_int + temp_dec) & 0xff) != checksum) { + dev_dbg(dht11->dev, "invalid checksum\n"); + return -EIO; + } + + dht11->timestamp = ktime_get_boottime_ns(); + if (hum_int < 4) { /* DHT22: 100000 = (3*256+232)*100 */ + dht11->temperature = (((temp_int & 0x7f) << 8) + temp_dec) * + ((temp_int & 0x80) ? -100 : 100); + dht11->humidity = ((hum_int << 8) + hum_dec) * 100; + } else if (temp_dec == 0 && hum_dec == 0) { /* DHT11 */ + dht11->temperature = temp_int * 1000; + dht11->humidity = hum_int * 1000; + } else { + dev_err(dht11->dev, + "Don't know how to decode data: %d %d %d %d\n", + hum_int, hum_dec, temp_int, temp_dec); + return -EIO; + } + + return 0; +} + +/* + * IRQ handler called on GPIO edges + */ +static irqreturn_t dht11_handle_irq(int irq, void *data) +{ + struct iio_dev *iio = data; + struct dht11 *dht11 = iio_priv(iio); + + if (dht11->num_edges < DHT11_EDGES_PER_READ && dht11->num_edges >= 0) { + dht11->edges[dht11->num_edges].ts = ktime_get_boottime_ns(); + dht11->edges[dht11->num_edges++].value = + gpiod_get_value(dht11->gpiod); + + if (dht11->num_edges >= DHT11_EDGES_PER_READ) + complete(&dht11->completion); + } + + return IRQ_HANDLED; +} + +static int dht11_read_raw(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long m) +{ + struct dht11 *dht11 = iio_priv(iio_dev); + int ret, timeres, offset; + + mutex_lock(&dht11->lock); + if (dht11->timestamp + DHT11_DATA_VALID_TIME < ktime_get_boottime_ns()) { + timeres = ktime_get_resolution_ns(); + dev_dbg(dht11->dev, "current timeresolution: %dns\n", timeres); + if (timeres > DHT11_MIN_TIMERES) { + dev_err(dht11->dev, "timeresolution %dns too low\n", + timeres); + /* In theory a better clock could become available + * at some point ... and there is no error code + * that really fits better. + */ + ret = -EAGAIN; + goto err; + } + if (timeres > DHT11_AMBIG_LOW && timeres < DHT11_AMBIG_HIGH) + dev_warn(dht11->dev, + "timeresolution: %dns - decoding ambiguous\n", + timeres); + + reinit_completion(&dht11->completion); + + dht11->num_edges = 0; + ret = gpiod_direction_output(dht11->gpiod, 0); + if (ret) + goto err; + usleep_range(DHT11_START_TRANSMISSION_MIN, + DHT11_START_TRANSMISSION_MAX); + ret = gpiod_direction_input(dht11->gpiod); + if (ret) + goto err; + + ret = request_irq(dht11->irq, dht11_handle_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + iio_dev->name, iio_dev); + if (ret) + goto err; + + ret = wait_for_completion_killable_timeout(&dht11->completion, + HZ); + + free_irq(dht11->irq, iio_dev); + +#ifdef CONFIG_DYNAMIC_DEBUG + dht11_edges_print(dht11); +#endif + + if (ret == 0 && dht11->num_edges < DHT11_EDGES_PER_READ - 1) { + dev_err(dht11->dev, "Only %d signal edges detected\n", + dht11->num_edges); + ret = -ETIMEDOUT; + } + if (ret < 0) + goto err; + + offset = DHT11_EDGES_PREAMBLE + + dht11->num_edges - DHT11_EDGES_PER_READ; + for (; offset >= 0; --offset) { + ret = dht11_decode(dht11, offset); + if (!ret) + break; + } + + if (ret) + goto err; + } + + ret = IIO_VAL_INT; + if (chan->type == IIO_TEMP) + *val = dht11->temperature; + else if (chan->type == IIO_HUMIDITYRELATIVE) + *val = dht11->humidity; + else + ret = -EINVAL; +err: + dht11->num_edges = -1; + mutex_unlock(&dht11->lock); + return ret; +} + +static const struct iio_info dht11_iio_info = { + .read_raw = dht11_read_raw, +}; + +static const struct iio_chan_spec dht11_chan_spec[] = { + { .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), }, + { .type = IIO_HUMIDITYRELATIVE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), } +}; + +static const struct of_device_id dht11_dt_ids[] = { + { .compatible = "dht11", }, + { } +}; +MODULE_DEVICE_TABLE(of, dht11_dt_ids); + +static int dht11_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dht11 *dht11; + struct iio_dev *iio; + + iio = devm_iio_device_alloc(dev, sizeof(*dht11)); + if (!iio) + return -ENOMEM; + + dht11 = iio_priv(iio); + dht11->dev = dev; + dht11->gpiod = devm_gpiod_get(dev, NULL, GPIOD_IN); + if (IS_ERR(dht11->gpiod)) + return PTR_ERR(dht11->gpiod); + + dht11->irq = gpiod_to_irq(dht11->gpiod); + if (dht11->irq < 0) { + dev_err(dev, "GPIO %d has no interrupt\n", desc_to_gpio(dht11->gpiod)); + return -EINVAL; + } + + dht11->timestamp = ktime_get_boottime_ns() - DHT11_DATA_VALID_TIME - 1; + dht11->num_edges = -1; + + platform_set_drvdata(pdev, iio); + + init_completion(&dht11->completion); + mutex_init(&dht11->lock); + iio->name = pdev->name; + iio->info = &dht11_iio_info; + iio->modes = INDIO_DIRECT_MODE; + iio->channels = dht11_chan_spec; + iio->num_channels = ARRAY_SIZE(dht11_chan_spec); + + return devm_iio_device_register(dev, iio); +} + +static struct platform_driver dht11_driver = { + .driver = { + .name = "dht11", + .of_match_table = dht11_dt_ids, + }, + .probe = dht11_probe, +}; + +module_platform_driver(dht11_driver); + +MODULE_AUTHOR("Harald Geyer "); +MODULE_DESCRIPTION("DHT11 humidity/temperature sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/humidity/ens210.c b/drivers/iio/humidity/ens210.c new file mode 100644 index 000000000..812761951 --- /dev/null +++ b/drivers/iio/humidity/ens210.c @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ens210.c - Support for ScioSense ens210 temperature & humidity sensor family + * + * (7-bit I2C slave address 0x43 ENS210) + * (7-bit I2C slave address 0x43 ENS210A) + * (7-bit I2C slave address 0x44 ENS211) + * (7-bit I2C slave address 0x45 ENS212) + * (7-bit I2C slave address 0x46 ENS213A) + * (7-bit I2C slave address 0x47 ENS215) + * + * Datasheet: + * https://www.sciosense.com/wp-content/uploads/2024/04/ENS21x-Datasheet.pdf + * https://www.sciosense.com/wp-content/uploads/2023/12/ENS210-Datasheet.pdf + */ + +#include +#include +#include +#include +#include +#include + +#include + +/* register definitions */ +#define ENS210_REG_PART_ID 0x00 +#define ENS210_REG_DIE_REV 0x02 +#define ENS210_REG_UID 0x04 +#define ENS210_REG_SYS_CTRL 0x10 +#define ENS210_REG_SYS_STAT 0x11 +#define ENS210_REG_SENS_RUN 0x21 +#define ENS210_REG_SENS_START 0x22 +#define ENS210_REG_SENS_STOP 0x23 +#define ENS210_REG_SENS_STAT 0x24 +#define ENS210_REG_T_VAL 0x30 +#define ENS210_REG_H_VAL 0x33 + +/* value definitions */ +#define ENS210_SENS_START_T_START BIT(0) +#define ENS210_SENS_START_H_START BIT(1) + +#define ENS210_SENS_STAT_T_ACTIVE BIT(0) +#define ENS210_SENS_STAT_H_ACTIVE BIT(1) + +#define ENS210_SYS_CTRL_LOW_POWER_ENABLE BIT(0) +#define ENS210_SYS_CTRL_SYS_RESET BIT(7) + +#define ENS210_SYS_STAT_SYS_ACTIVE BIT(0) + +enum ens210_partnumber { + ENS210 = 0x0210, + ENS210A = 0xa210, + ENS211 = 0x0211, + ENS212 = 0x0212, + ENS213A = 0xa213, + ENS215 = 0x0215, +}; + +/** + * struct ens210_chip_info - Humidity/Temperature chip specific information + * @name: name of device + * @part_id: chip identifier + * @conv_time_msec: time for conversion calculation in m/s + */ +struct ens210_chip_info { + const char *name; + enum ens210_partnumber part_id; + unsigned int conv_time_msec; +}; + +/** + * struct ens210_data - Humidity/Temperature sensor device structure + * @client: i2c client + * @chip_info: chip specific information + * @lock: lock protecting against simultaneous callers of get_measurement + * since multiple uninterrupted transactions are required + */ +struct ens210_data { + struct i2c_client *client; + const struct ens210_chip_info *chip_info; + struct mutex lock; +}; + +/* calculate 17-bit crc7 */ +static u8 ens210_crc7(u32 val) +{ + unsigned int val_be = (val & 0x1ffff) >> 0x8; + + return crc7_be(0xde, (u8 *)&val_be, 3) >> 1; +} + +static int ens210_get_measurement(struct iio_dev *indio_dev, bool temp, int *val) +{ + struct ens210_data *data = iio_priv(indio_dev); + struct device *dev = &data->client->dev; + u32 regval; + u8 regval_le[3]; + int ret; + + /* assert read */ + ret = i2c_smbus_write_byte_data(data->client, ENS210_REG_SENS_START, + temp ? ENS210_SENS_START_T_START : + ENS210_SENS_START_H_START); + if (ret) + return ret; + + /* wait for conversion to be ready */ + msleep(data->chip_info->conv_time_msec); + + ret = i2c_smbus_read_byte_data(data->client, ENS210_REG_SENS_STAT); + if (ret < 0) + return ret; + + /* perform read */ + ret = i2c_smbus_read_i2c_block_data( + data->client, temp ? ENS210_REG_T_VAL : ENS210_REG_H_VAL, 3, + regval_le); + if (ret < 0) { + dev_err(dev, "failed to read register"); + return -EIO; + } + if (ret != 3) { + dev_err(dev, "expected 3 bytes, received %d\n", ret); + return -EIO; + } + + regval = get_unaligned_le24(regval_le); + if (ens210_crc7(regval) != ((regval >> 17) & 0x7f)) { + dev_err(dev, "invalid crc\n"); + return -EIO; + } + + if (!((regval >> 16) & 0x1)) { + dev_err(dev, "data is not valid"); + return -EIO; + } + + *val = regval & GENMASK(15, 0); + return IIO_VAL_INT; +} + +static int ens210_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct ens210_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + guard(mutex)(&data->lock); + + ret = ens210_get_measurement(indio_dev, channel->type == IIO_TEMP, + val); + if (ret) + return ret; + + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_SCALE: + if (channel->type == IIO_TEMP) { + *val = 15; + *val2 = 625000; + } else { + *val = 1; + *val2 = 953125; + } + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OFFSET: + *val = -17481; + *val2 = 600000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static const struct iio_chan_spec ens210_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + }, + { + .type = IIO_HUMIDITYRELATIVE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + } +}; + +static const struct iio_info ens210_info = { + .read_raw = ens210_read_raw, +}; + +static int ens210_probe(struct i2c_client *client) +{ + struct ens210_data *data; + struct iio_dev *indio_dev; + uint16_t part_id; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_BYTE_DATA | + I2C_FUNC_SMBUS_WRITE_BYTE | + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { + return dev_err_probe(&client->dev, -EOPNOTSUPP, + "adapter does not support some i2c transactions\n"); + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + mutex_init(&data->lock); + data->chip_info = i2c_get_match_data(client); + + ret = devm_regulator_get_enable(&client->dev, "vdd"); + if (ret) + return ret; + + /* reset device */ + ret = i2c_smbus_write_byte_data(client, ENS210_REG_SYS_CTRL, + ENS210_SYS_CTRL_SYS_RESET); + if (ret) + return ret; + + /* wait for device to become active */ + usleep_range(4000, 5000); + + /* disable low power mode */ + ret = i2c_smbus_write_byte_data(client, ENS210_REG_SYS_CTRL, 0x00); + if (ret) + return ret; + + /* wait for device to finish */ + usleep_range(4000, 5000); + + /* get part_id */ + ret = i2c_smbus_read_word_data(client, ENS210_REG_PART_ID); + if (ret < 0) + return ret; + part_id = ret; + + if (part_id != data->chip_info->part_id) { + dev_info(&client->dev, + "Part ID does not match (0x%04x != 0x%04x)\n", part_id, + data->chip_info->part_id); + } + + /* reenable low power */ + ret = i2c_smbus_write_byte_data(client, ENS210_REG_SYS_CTRL, + ENS210_SYS_CTRL_LOW_POWER_ENABLE); + if (ret) + return ret; + + indio_dev->name = data->chip_info->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ens210_channels; + indio_dev->num_channels = ARRAY_SIZE(ens210_channels); + indio_dev->info = &ens210_info; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct ens210_chip_info ens210_chip_info_data = { + .name = "ens210", + .part_id = ENS210, + .conv_time_msec = 130, +}; + +static const struct ens210_chip_info ens210a_chip_info_data = { + .name = "ens210a", + .part_id = ENS210A, + .conv_time_msec = 130, +}; + +static const struct ens210_chip_info ens211_chip_info_data = { + .name = "ens211", + .part_id = ENS211, + .conv_time_msec = 32, +}; + +static const struct ens210_chip_info ens212_chip_info_data = { + .name = "ens212", + .part_id = ENS212, + .conv_time_msec = 32, +}; + +static const struct ens210_chip_info ens213a_chip_info_data = { + .name = "ens213a", + .part_id = ENS213A, + .conv_time_msec = 130, +}; + +static const struct ens210_chip_info ens215_chip_info_data = { + .name = "ens215", + .part_id = ENS215, + .conv_time_msec = 130, +}; + +static const struct of_device_id ens210_of_match[] = { + { .compatible = "sciosense,ens210", .data = &ens210_chip_info_data }, + { .compatible = "sciosense,ens210a", .data = &ens210a_chip_info_data }, + { .compatible = "sciosense,ens211", .data = &ens211_chip_info_data }, + { .compatible = "sciosense,ens212", .data = &ens212_chip_info_data }, + { .compatible = "sciosense,ens213a", .data = &ens213a_chip_info_data }, + { .compatible = "sciosense,ens215", .data = &ens215_chip_info_data }, + { } +}; +MODULE_DEVICE_TABLE(of, ens210_of_match); + +static const struct i2c_device_id ens210_id_table[] = { + { .name = "ens210", .driver_data = (kernel_ulong_t)&ens210_chip_info_data }, + { .name = "ens210a", .driver_data = (kernel_ulong_t)&ens210a_chip_info_data }, + { .name = "ens211", .driver_data = (kernel_ulong_t)&ens211_chip_info_data }, + { .name = "ens212", .driver_data = (kernel_ulong_t)&ens212_chip_info_data }, + { .name = "ens213a", .driver_data = (kernel_ulong_t)&ens213a_chip_info_data }, + { .name = "ens215", .driver_data = (kernel_ulong_t)&ens215_chip_info_data }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ens210_id_table); + +static struct i2c_driver ens210_driver = { + .probe = ens210_probe, + .id_table = ens210_id_table, + .driver = { + .name = "ens210", + .of_match_table = ens210_of_match, + }, +}; +module_i2c_driver(ens210_driver); + +MODULE_DESCRIPTION("ScioSense ENS210 temperature and humidity sensor driver"); +MODULE_AUTHOR("Joshua Felmeden "); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/humidity/hdc100x.c b/drivers/iio/humidity/hdc100x.c new file mode 100644 index 000000000..bc452cc8f --- /dev/null +++ b/drivers/iio/humidity/hdc100x.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * hdc100x.c - Support for the TI HDC100x temperature + humidity sensors + * + * Copyright (C) 2015, 2018 + * Author: Matt Ranostay + * + * Datasheets: + * https://www.ti.com/product/HDC1000/datasheet + * https://www.ti.com/product/HDC1008/datasheet + * https://www.ti.com/product/HDC1010/datasheet + * https://www.ti.com/product/HDC1050/datasheet + * https://www.ti.com/product/HDC1080/datasheet + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#define HDC100X_REG_TEMP 0x00 +#define HDC100X_REG_HUMIDITY 0x01 + +#define HDC100X_REG_CONFIG 0x02 +#define HDC100X_REG_CONFIG_ACQ_MODE BIT(12) +#define HDC100X_REG_CONFIG_HEATER_EN BIT(13) + +struct hdc100x_data { + struct i2c_client *client; + struct mutex lock; + u16 config; + + /* integration time of the sensor */ + int adc_int_us[2]; + /* Ensure natural alignment of timestamp */ + struct { + __be16 channels[2]; + aligned_s64 ts; + } scan; +}; + +/* integration time in us */ +static const int hdc100x_int_time[][3] = { + { 6350, 3650, 0 }, /* IIO_TEMP channel*/ + { 6500, 3850, 2500 }, /* IIO_HUMIDITYRELATIVE channel */ +}; + +/* HDC100X_REG_CONFIG shift and mask values */ +static const struct { + int shift; + int mask; +} hdc100x_resolution_shift[2] = { + { /* IIO_TEMP channel */ + .shift = 10, + .mask = 1 + }, + { /* IIO_HUMIDITYRELATIVE channel */ + .shift = 8, + .mask = 3, + }, +}; + +static IIO_CONST_ATTR(temp_integration_time_available, + "0.00365 0.00635"); + +static IIO_CONST_ATTR(humidityrelative_integration_time_available, + "0.0025 0.00385 0.0065"); + +static IIO_CONST_ATTR(out_current_heater_raw_available, + "0 1"); + +static struct attribute *hdc100x_attributes[] = { + &iio_const_attr_temp_integration_time_available.dev_attr.attr, + &iio_const_attr_humidityrelative_integration_time_available.dev_attr.attr, + &iio_const_attr_out_current_heater_raw_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group hdc100x_attribute_group = { + .attrs = hdc100x_attributes, +}; + +static const struct iio_chan_spec hdc100x_channels[] = { + { + .type = IIO_TEMP, + .address = HDC100X_REG_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, + }, + }, + { + .type = IIO_HUMIDITYRELATIVE, + .address = HDC100X_REG_HUMIDITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME), + .scan_index = 1, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, + }, + }, + { + .type = IIO_CURRENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .extend_name = "heater", + .output = 1, + .scan_index = -1, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static const unsigned long hdc100x_scan_masks[] = {0x3, 0}; + +static int hdc100x_update_config(struct hdc100x_data *data, int mask, int val) +{ + int tmp = (~mask & data->config) | val; + int ret; + + ret = i2c_smbus_write_word_swapped(data->client, + HDC100X_REG_CONFIG, tmp); + if (!ret) + data->config = tmp; + + return ret; +} + +static int hdc100x_set_it_time(struct hdc100x_data *data, int chan, int val2) +{ + int shift = hdc100x_resolution_shift[chan].shift; + int ret = -EINVAL; + int i; + + for (i = 0; i < ARRAY_SIZE(hdc100x_int_time[chan]); i++) { + if (val2 && val2 == hdc100x_int_time[chan][i]) { + ret = hdc100x_update_config(data, + hdc100x_resolution_shift[chan].mask << shift, + i << shift); + if (!ret) + data->adc_int_us[chan] = val2; + break; + } + } + + return ret; +} + +static int hdc100x_get_measurement(struct hdc100x_data *data, + struct iio_chan_spec const *chan) +{ + struct i2c_client *client = data->client; + int delay = data->adc_int_us[chan->address] + 1*USEC_PER_MSEC; + int ret; + __be16 val; + + /* start measurement */ + ret = i2c_smbus_write_byte(client, chan->address); + if (ret < 0) { + dev_err(&client->dev, "cannot start measurement"); + return ret; + } + + /* wait for integration time to pass */ + usleep_range(delay, delay + 1000); + + /* read measurement */ + ret = i2c_master_recv(data->client, (char *)&val, sizeof(val)); + if (ret < 0) { + dev_err(&client->dev, "cannot read sensor data\n"); + return ret; + } + return be16_to_cpu(val); +} + +static int hdc100x_get_heater_status(struct hdc100x_data *data) +{ + return !!(data->config & HDC100X_REG_CONFIG_HEATER_EN); +} + +static int hdc100x_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct hdc100x_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + int ret; + + guard(mutex)(&data->lock); + if (chan->type == IIO_CURRENT) { + *val = hdc100x_get_heater_status(data); + return IIO_VAL_INT; + } + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = hdc100x_get_measurement(data, chan); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = data->adc_int_us[chan->address]; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_TEMP) { + *val = 165000; + *val2 = 65536; + return IIO_VAL_FRACTIONAL; + } else { + *val = 100000; + *val2 = 65536; + return IIO_VAL_FRACTIONAL; + } + break; + case IIO_CHAN_INFO_OFFSET: + *val = -15887; + *val2 = 515151; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int hdc100x_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct hdc100x_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: { + if (val != 0) + return -EINVAL; + + guard(mutex)(&data->lock); + return hdc100x_set_it_time(data, chan->address, val2); + } + case IIO_CHAN_INFO_RAW: { + if (chan->type != IIO_CURRENT || val2 != 0) + return -EINVAL; + + guard(mutex)(&data->lock); + return hdc100x_update_config(data, HDC100X_REG_CONFIG_HEATER_EN, + val ? HDC100X_REG_CONFIG_HEATER_EN : 0); + } + default: + return -EINVAL; + } +} + +static int hdc100x_buffer_postenable(struct iio_dev *indio_dev) +{ + struct hdc100x_data *data = iio_priv(indio_dev); + + /* Buffer is enabled. First set ACQ Mode, then attach poll func */ + guard(mutex)(&data->lock); + return hdc100x_update_config(data, HDC100X_REG_CONFIG_ACQ_MODE, + HDC100X_REG_CONFIG_ACQ_MODE); +} + +static int hdc100x_buffer_predisable(struct iio_dev *indio_dev) +{ + struct hdc100x_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + return hdc100x_update_config(data, HDC100X_REG_CONFIG_ACQ_MODE, 0); +} + +static const struct iio_buffer_setup_ops hdc_buffer_setup_ops = { + .postenable = hdc100x_buffer_postenable, + .predisable = hdc100x_buffer_predisable, +}; + +static irqreturn_t hdc100x_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct hdc100x_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + int delay = data->adc_int_us[0] + data->adc_int_us[1] + 2*USEC_PER_MSEC; + int ret; + + /* dual read starts at temp register */ + mutex_lock(&data->lock); + ret = i2c_smbus_write_byte(client, HDC100X_REG_TEMP); + if (ret < 0) { + dev_err(&client->dev, "cannot start measurement\n"); + goto err; + } + usleep_range(delay, delay + 1000); + + ret = i2c_master_recv(client, (u8 *)data->scan.channels, 4); + if (ret < 0) { + dev_err(&client->dev, "cannot read sensor data\n"); + goto err; + } + + iio_push_to_buffers_with_timestamp(indio_dev, &data->scan, + iio_get_time_ns(indio_dev)); +err: + mutex_unlock(&data->lock); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_info hdc100x_info = { + .read_raw = hdc100x_read_raw, + .write_raw = hdc100x_write_raw, + .attrs = &hdc100x_attribute_group, +}; + +static int hdc100x_probe(struct i2c_client *client) +{ + struct iio_dev *indio_dev; + struct hdc100x_data *data; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_BYTE | I2C_FUNC_I2C)) + return -EOPNOTSUPP; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + mutex_init(&data->lock); + + indio_dev->name = dev_name(&client->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &hdc100x_info; + + indio_dev->channels = hdc100x_channels; + indio_dev->num_channels = ARRAY_SIZE(hdc100x_channels); + indio_dev->available_scan_masks = hdc100x_scan_masks; + + /* be sure we are in a known state */ + hdc100x_set_it_time(data, 0, hdc100x_int_time[0][0]); + hdc100x_set_it_time(data, 1, hdc100x_int_time[1][0]); + hdc100x_update_config(data, HDC100X_REG_CONFIG_ACQ_MODE, 0); + + ret = devm_iio_triggered_buffer_setup(&client->dev, + indio_dev, NULL, + hdc100x_trigger_handler, + &hdc_buffer_setup_ops); + if (ret < 0) { + dev_err(&client->dev, "iio triggered buffer setup failed\n"); + return ret; + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id hdc100x_id[] = { + { .name = "hdc100x" }, + { .name = "hdc1000" }, + { .name = "hdc1008" }, + { .name = "hdc1010" }, + { .name = "hdc1050" }, + { .name = "hdc1080" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, hdc100x_id); + +static const struct of_device_id hdc100x_dt_ids[] = { + { .compatible = "ti,hdc1000" }, + { .compatible = "ti,hdc1008" }, + { .compatible = "ti,hdc1010" }, + { .compatible = "ti,hdc1050" }, + { .compatible = "ti,hdc1080" }, + { } +}; +MODULE_DEVICE_TABLE(of, hdc100x_dt_ids); + +static const struct acpi_device_id hdc100x_acpi_match[] = { + { "TXNW1010" }, + { } +}; +MODULE_DEVICE_TABLE(acpi, hdc100x_acpi_match); + +static struct i2c_driver hdc100x_driver = { + .driver = { + .name = "hdc100x", + .of_match_table = hdc100x_dt_ids, + .acpi_match_table = hdc100x_acpi_match, + }, + .probe = hdc100x_probe, + .id_table = hdc100x_id, +}; +module_i2c_driver(hdc100x_driver); + +MODULE_AUTHOR("Matt Ranostay "); +MODULE_DESCRIPTION("TI HDC100x humidity and temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/humidity/hdc2010.c b/drivers/iio/humidity/hdc2010.c new file mode 100644 index 000000000..82d689875 --- /dev/null +++ b/drivers/iio/humidity/hdc2010.c @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * hdc2010.c - Support for the TI HDC2010 and HDC2080 + * temperature + relative humidity sensors + * + * Copyright (C) 2020 Norphonic AS + * Author: Eugene Zaikonnikov + * + * Datasheet: https://www.ti.com/product/HDC2010/datasheet + * Datasheet: https://www.ti.com/product/HDC2080/datasheet + */ + +#include +#include +#include +#include + +#include +#include + +#define HDC2010_REG_TEMP_LOW 0x00 +#define HDC2010_REG_TEMP_HIGH 0x01 +#define HDC2010_REG_HUMIDITY_LOW 0x02 +#define HDC2010_REG_HUMIDITY_HIGH 0x03 +#define HDC2010_REG_INTERRUPT_DRDY 0x04 +#define HDC2010_REG_TEMP_MAX 0x05 +#define HDC2010_REG_HUMIDITY_MAX 0x06 +#define HDC2010_REG_INTERRUPT_EN 0x07 +#define HDC2010_REG_TEMP_OFFSET_ADJ 0x08 +#define HDC2010_REG_HUMIDITY_OFFSET_ADJ 0x09 +#define HDC2010_REG_TEMP_THR_L 0x0a +#define HDC2010_REG_TEMP_THR_H 0x0b +#define HDC2010_REG_RH_THR_L 0x0c +#define HDC2010_REG_RH_THR_H 0x0d +#define HDC2010_REG_RESET_DRDY_INT_CONF 0x0e +#define HDC2010_REG_MEASUREMENT_CONF 0x0f + +#define HDC2010_MEAS_CONF GENMASK(2, 1) +#define HDC2010_MEAS_TRIG BIT(0) +#define HDC2010_HEATER_EN BIT(3) +#define HDC2010_AMM GENMASK(6, 4) + +struct hdc2010_data { + struct i2c_client *client; + struct mutex lock; + u8 measurement_config; + u8 drdy_config; +}; + +enum hdc2010_addr_groups { + HDC2010_GROUP_TEMP = 0, + HDC2010_GROUP_HUMIDITY, +}; + +struct hdc2010_reg_record { + unsigned long primary; + unsigned long peak; +}; + +static const struct hdc2010_reg_record hdc2010_reg_translation[] = { + [HDC2010_GROUP_TEMP] = { + .primary = HDC2010_REG_TEMP_LOW, + .peak = HDC2010_REG_TEMP_MAX, + }, + [HDC2010_GROUP_HUMIDITY] = { + .primary = HDC2010_REG_HUMIDITY_LOW, + .peak = HDC2010_REG_HUMIDITY_MAX, + }, +}; + +static IIO_CONST_ATTR(out_current_heater_raw_available, "0 1"); + +static struct attribute *hdc2010_attributes[] = { + &iio_const_attr_out_current_heater_raw_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group hdc2010_attribute_group = { + .attrs = hdc2010_attributes, +}; + +static const struct iio_chan_spec hdc2010_channels[] = { + { + .type = IIO_TEMP, + .address = HDC2010_GROUP_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_PEAK) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_HUMIDITYRELATIVE, + .address = HDC2010_GROUP_HUMIDITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_PEAK) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_CURRENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .extend_name = "heater", + .output = 1, + }, +}; + +static int hdc2010_update_drdy_config(struct hdc2010_data *data, + char mask, char val) +{ + u8 tmp = (~mask & data->drdy_config) | val; + int ret; + + ret = i2c_smbus_write_byte_data(data->client, + HDC2010_REG_RESET_DRDY_INT_CONF, tmp); + if (ret) + return ret; + + data->drdy_config = tmp; + + return 0; +} + +static int hdc2010_get_prim_measurement_word(struct hdc2010_data *data, + struct iio_chan_spec const *chan) +{ + struct i2c_client *client = data->client; + s32 ret; + + ret = i2c_smbus_read_word_data(client, + hdc2010_reg_translation[chan->address].primary); + + if (ret < 0) + dev_err(&client->dev, "Could not read sensor measurement word\n"); + + return ret; +} + +static int hdc2010_get_peak_measurement_byte(struct hdc2010_data *data, + struct iio_chan_spec const *chan) +{ + struct i2c_client *client = data->client; + s32 ret; + + ret = i2c_smbus_read_byte_data(client, + hdc2010_reg_translation[chan->address].peak); + + if (ret < 0) + dev_err(&client->dev, "Could not read sensor measurement byte\n"); + + return ret; +} + +static int hdc2010_get_heater_status(struct hdc2010_data *data) +{ + return !!(data->drdy_config & HDC2010_HEATER_EN); +} + +static int hdc2010_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct hdc2010_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + int ret; + + if (chan->type == IIO_CURRENT) { + *val = hdc2010_get_heater_status(data); + return IIO_VAL_INT; + } + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + mutex_lock(&data->lock); + ret = hdc2010_get_prim_measurement_word(data, chan); + mutex_unlock(&data->lock); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_PEAK: { + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + mutex_lock(&data->lock); + ret = hdc2010_get_peak_measurement_byte(data, chan); + mutex_unlock(&data->lock); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + /* Scaling up the value so we can use same offset as RAW */ + *val = ret * 256; + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_SCALE: + *val2 = 65536; + if (chan->type == IIO_TEMP) + *val = 165000; + else + *val = 100000; + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_OFFSET: + *val = -15887; + *val2 = 515151; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int hdc2010_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct hdc2010_data *data = iio_priv(indio_dev); + int new, ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type != IIO_CURRENT || val2 != 0) + return -EINVAL; + + switch (val) { + case 1: + new = HDC2010_HEATER_EN; + break; + case 0: + new = 0; + break; + default: + return -EINVAL; + } + + mutex_lock(&data->lock); + ret = hdc2010_update_drdy_config(data, HDC2010_HEATER_EN, new); + mutex_unlock(&data->lock); + return ret; + default: + return -EINVAL; + } +} + +static const struct iio_info hdc2010_info = { + .read_raw = hdc2010_read_raw, + .write_raw = hdc2010_write_raw, + .attrs = &hdc2010_attribute_group, +}; + +static int hdc2010_probe(struct i2c_client *client) +{ + struct iio_dev *indio_dev; + struct hdc2010_data *data; + u8 tmp; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_I2C)) + return -EOPNOTSUPP; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + mutex_init(&data->lock); + + /* + * As DEVICE ID register does not differentiate between + * HDC2010 and HDC2080, we have the name hardcoded + */ + indio_dev->name = "hdc2010"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &hdc2010_info; + + indio_dev->channels = hdc2010_channels; + indio_dev->num_channels = ARRAY_SIZE(hdc2010_channels); + + /* Enable Automatic Measurement Mode at 5Hz */ + ret = hdc2010_update_drdy_config(data, HDC2010_AMM, HDC2010_AMM); + if (ret) + return ret; + + /* + * We enable both temp and humidity measurement. + * However the measurement won't start even in AMM until triggered. + */ + tmp = (data->measurement_config & ~HDC2010_MEAS_CONF) | + HDC2010_MEAS_TRIG; + + ret = i2c_smbus_write_byte_data(client, HDC2010_REG_MEASUREMENT_CONF, tmp); + if (ret) { + dev_warn(&client->dev, "Unable to set up measurement\n"); + if (hdc2010_update_drdy_config(data, HDC2010_AMM, 0)) + dev_warn(&client->dev, "Unable to restore default AMM\n"); + return ret; + } + + data->measurement_config = tmp; + + return iio_device_register(indio_dev); +} + +static void hdc2010_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct hdc2010_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + /* Disable Automatic Measurement Mode */ + if (hdc2010_update_drdy_config(data, HDC2010_AMM, 0)) + dev_warn(&client->dev, "Unable to restore default AMM\n"); +} + +static const struct i2c_device_id hdc2010_id[] = { + { .name = "hdc2010" }, + { .name = "hdc2080" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, hdc2010_id); + +static const struct of_device_id hdc2010_dt_ids[] = { + { .compatible = "ti,hdc2010" }, + { .compatible = "ti,hdc2080" }, + { } +}; +MODULE_DEVICE_TABLE(of, hdc2010_dt_ids); + +static struct i2c_driver hdc2010_driver = { + .driver = { + .name = "hdc2010", + .of_match_table = hdc2010_dt_ids, + }, + .probe = hdc2010_probe, + .remove = hdc2010_remove, + .id_table = hdc2010_id, +}; +module_i2c_driver(hdc2010_driver); + +MODULE_AUTHOR("Eugene Zaikonnikov "); +MODULE_DESCRIPTION("TI HDC2010 humidity and temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/humidity/hdc3020.c b/drivers/iio/humidity/hdc3020.c new file mode 100644 index 000000000..75971c196 --- /dev/null +++ b/drivers/iio/humidity/hdc3020.c @@ -0,0 +1,904 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * hdc3020.c - Support for the TI HDC3020,HDC3021 and HDC3022 + * temperature + relative humidity sensors + * + * Copyright (C) 2023 + * + * Copyright (C) 2024 Liebherr-Electronics and Drives GmbH + * + * Datasheet: https://www.ti.com/lit/ds/symlink/hdc3020.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#define HDC3020_S_AUTO_10HZ_MOD0 0x2737 +#define HDC3020_S_STATUS 0x3041 +#define HDC3020_HEATER_DISABLE 0x3066 +#define HDC3020_HEATER_ENABLE 0x306D +#define HDC3020_HEATER_CONFIG 0x306E +#define HDC3020_EXIT_AUTO 0x3093 +#define HDC3020_S_T_RH_THRESH_LOW 0x6100 +#define HDC3020_S_T_RH_THRESH_LOW_CLR 0x610B +#define HDC3020_S_T_RH_THRESH_HIGH_CLR 0x6116 +#define HDC3020_S_T_RH_THRESH_HIGH 0x611D +#define HDC3020_R_T_RH_AUTO 0xE000 +#define HDC3020_R_T_LOW_AUTO 0xE002 +#define HDC3020_R_T_HIGH_AUTO 0xE003 +#define HDC3020_R_RH_LOW_AUTO 0xE004 +#define HDC3020_R_RH_HIGH_AUTO 0xE005 +#define HDC3020_R_T_RH_THRESH_LOW 0xE102 +#define HDC3020_R_T_RH_THRESH_LOW_CLR 0xE109 +#define HDC3020_R_T_RH_THRESH_HIGH_CLR 0xE114 +#define HDC3020_R_T_RH_THRESH_HIGH 0xE11F +#define HDC3020_R_STATUS 0xF32D + +#define HDC3020_THRESH_TEMP_MASK GENMASK(8, 0) +#define HDC3020_THRESH_TEMP_TRUNC_SHIFT 7 +#define HDC3020_THRESH_HUM_MASK GENMASK(15, 9) +#define HDC3020_THRESH_HUM_TRUNC_SHIFT 9 + +#define HDC3020_STATUS_T_LOW_ALERT BIT(6) +#define HDC3020_STATUS_T_HIGH_ALERT BIT(7) +#define HDC3020_STATUS_RH_LOW_ALERT BIT(8) +#define HDC3020_STATUS_RH_HIGH_ALERT BIT(9) + +#define HDC3020_READ_RETRY_TIMES 10 +#define HDC3020_BUSY_DELAY_MS 10 + +#define HDC3020_CRC8_POLYNOMIAL 0x31 + +#define HDC3020_MIN_TEMP_MICRO -39872968 +#define HDC3020_MAX_TEMP_MICRO 124875639 +#define HDC3020_MAX_TEMP_HYST_MICRO 164748607 +#define HDC3020_MAX_HUM_MICRO 99220264 + +/* Divide 65535 from the datasheet by 5 to avoid overflows */ +#define HDC3020_THRESH_FRACTION (65535 / 5) + +struct hdc3020_data { + struct i2c_client *client; + struct gpio_desc *reset_gpio; + struct regulator *vdd_supply; + /* + * Ensure that the sensor configuration (currently only heater is + * supported) will not be changed during the process of reading + * sensor data (this driver will try HDC3020_READ_RETRY_TIMES times + * if the device does not respond). + */ + struct mutex lock; +}; + +static const int hdc3020_heater_vals[] = {0, 1, 0x3FFF}; + +static const struct iio_event_spec hdc3020_t_rh_event[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_HYSTERESIS), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_HYSTERESIS), + }, +}; + +static const struct iio_chan_spec hdc3020_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_PEAK) | + BIT(IIO_CHAN_INFO_TROUGH) | BIT(IIO_CHAN_INFO_OFFSET), + .event_spec = hdc3020_t_rh_event, + .num_event_specs = ARRAY_SIZE(hdc3020_t_rh_event), + }, + { + .type = IIO_HUMIDITYRELATIVE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_PEAK) | + BIT(IIO_CHAN_INFO_TROUGH), + .event_spec = hdc3020_t_rh_event, + .num_event_specs = ARRAY_SIZE(hdc3020_t_rh_event), + }, + { + /* + * For setting the internal heater, which can be switched on to + * prevent or remove any condensation that may develop when the + * ambient environment approaches its dew point temperature. + */ + .type = IIO_CURRENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW), + .output = 1, + }, +}; + +DECLARE_CRC8_TABLE(hdc3020_crc8_table); + +static int hdc3020_write_bytes(struct hdc3020_data *data, u8 *buf, u8 len) +{ + struct i2c_client *client = data->client; + struct i2c_msg msg; + int ret, cnt; + + msg.addr = client->addr; + msg.flags = 0; + msg.buf = buf; + msg.len = len; + + /* + * During the measurement process, HDC3020 will not return data. + * So wait for a while and try again + */ + for (cnt = 0; cnt < HDC3020_READ_RETRY_TIMES; cnt++) { + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret == 1) + return 0; + + mdelay(HDC3020_BUSY_DELAY_MS); + } + dev_err(&client->dev, "Could not write sensor command\n"); + + return -ETIMEDOUT; +} + +static +int hdc3020_read_bytes(struct hdc3020_data *data, u16 reg, u8 *buf, int len) +{ + u8 reg_buf[2]; + int ret, cnt; + struct i2c_client *client = data->client; + struct i2c_msg msg[2] = { + [0] = { + .addr = client->addr, + .flags = 0, + .buf = reg_buf, + .len = 2, + }, + [1] = { + .addr = client->addr, + .flags = I2C_M_RD, + .buf = buf, + .len = len, + }, + }; + + put_unaligned_be16(reg, reg_buf); + /* + * During the measurement process, HDC3020 will not return data. + * So wait for a while and try again + */ + for (cnt = 0; cnt < HDC3020_READ_RETRY_TIMES; cnt++) { + ret = i2c_transfer(client->adapter, msg, 2); + if (ret == 2) + return 0; + + mdelay(HDC3020_BUSY_DELAY_MS); + } + dev_err(&client->dev, "Could not read sensor data\n"); + + return -ETIMEDOUT; +} + +static int hdc3020_read_be16(struct hdc3020_data *data, u16 reg) +{ + u8 crc, buf[3]; + int ret; + + ret = hdc3020_read_bytes(data, reg, buf, 3); + if (ret < 0) + return ret; + + crc = crc8(hdc3020_crc8_table, buf, 2, CRC8_INIT_VALUE); + if (crc != buf[2]) + return -EINVAL; + + return get_unaligned_be16(buf); +} + +static int hdc3020_exec_cmd(struct hdc3020_data *data, u16 reg) +{ + u8 reg_buf[2]; + + put_unaligned_be16(reg, reg_buf); + return hdc3020_write_bytes(data, reg_buf, 2); +} + +static int hdc3020_read_measurement(struct hdc3020_data *data, + enum iio_chan_type type, int *val) +{ + u8 crc, buf[6]; + int ret; + + ret = hdc3020_read_bytes(data, HDC3020_R_T_RH_AUTO, buf, 6); + if (ret < 0) + return ret; + + /* CRC check of the temperature measurement */ + crc = crc8(hdc3020_crc8_table, buf, 2, CRC8_INIT_VALUE); + if (crc != buf[2]) + return -EINVAL; + + /* CRC check of the relative humidity measurement */ + crc = crc8(hdc3020_crc8_table, buf + 3, 2, CRC8_INIT_VALUE); + if (crc != buf[5]) + return -EINVAL; + + if (type == IIO_TEMP) + *val = get_unaligned_be16(buf); + else if (type == IIO_HUMIDITYRELATIVE) + *val = get_unaligned_be16(&buf[3]); + else + return -EINVAL; + + return 0; +} + +static int hdc3020_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct hdc3020_data *data = iio_priv(indio_dev); + int ret; + + if (chan->type != IIO_TEMP && chan->type != IIO_HUMIDITYRELATIVE) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + guard(mutex)(&data->lock); + ret = hdc3020_read_measurement(data, chan->type, val); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_PEAK: { + guard(mutex)(&data->lock); + if (chan->type == IIO_TEMP) + ret = hdc3020_read_be16(data, HDC3020_R_T_HIGH_AUTO); + else + ret = hdc3020_read_be16(data, HDC3020_R_RH_HIGH_AUTO); + + if (ret < 0) + return ret; + + *val = ret; + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_TROUGH: { + guard(mutex)(&data->lock); + if (chan->type == IIO_TEMP) + ret = hdc3020_read_be16(data, HDC3020_R_T_LOW_AUTO); + else + ret = hdc3020_read_be16(data, HDC3020_R_RH_LOW_AUTO); + + if (ret < 0) + return ret; + + *val = ret; + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_SCALE: + *val2 = 65536; + if (chan->type == IIO_TEMP) + *val = 175 * MILLI; + else + *val = 100 * MILLI; + return IIO_VAL_FRACTIONAL; + + case IIO_CHAN_INFO_OFFSET: + if (chan->type != IIO_TEMP) + return -EINVAL; + + *val = -16852; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int hdc3020_read_available(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, + int *type, int *length, long mask) +{ + if (mask != IIO_CHAN_INFO_RAW || chan->type != IIO_CURRENT) + return -EINVAL; + + *vals = hdc3020_heater_vals; + *type = IIO_VAL_INT; + + return IIO_AVAIL_RANGE; +} + +static int hdc3020_update_heater(struct hdc3020_data *data, int val) +{ + u8 buf[5]; + int ret; + + if (val < hdc3020_heater_vals[0] || val > hdc3020_heater_vals[2]) + return -EINVAL; + + if (!val) + hdc3020_exec_cmd(data, HDC3020_HEATER_DISABLE); + + put_unaligned_be16(HDC3020_HEATER_CONFIG, buf); + put_unaligned_be16(val & GENMASK(13, 0), &buf[2]); + buf[4] = crc8(hdc3020_crc8_table, buf + 2, 2, CRC8_INIT_VALUE); + ret = hdc3020_write_bytes(data, buf, 5); + if (ret < 0) + return ret; + + return hdc3020_exec_cmd(data, HDC3020_HEATER_ENABLE); +} + +static int hdc3020_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct hdc3020_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type != IIO_CURRENT) + return -EINVAL; + + guard(mutex)(&data->lock); + return hdc3020_update_heater(data, val); + } + + return -EINVAL; +} + +static int hdc3020_thresh_get_temp(u16 thresh) +{ + int temp; + + /* + * Get the temperature threshold from 9 LSBs, shift them to get the + * truncated temperature threshold representation and calculate the + * threshold according to the explicit formula in the datasheet: + * T(C) = -45 + (175 * temp) / 65535. + * Additionally scale by HDC3020_THRESH_FRACTION to avoid precision loss + * when calculating threshold and hysteresis values. Result is degree + * celsius scaled by HDC3020_THRESH_FRACTION. + */ + temp = FIELD_GET(HDC3020_THRESH_TEMP_MASK, thresh) << + HDC3020_THRESH_TEMP_TRUNC_SHIFT; + + return -2949075 / 5 + (175 / 5 * temp); +} + +static int hdc3020_thresh_get_hum(u16 thresh) +{ + int hum; + + /* + * Get the humidity threshold from 7 MSBs, shift them to get the + * truncated humidity threshold representation and calculate the + * threshold according to the explicit formula in the datasheet: + * RH(%) = 100 * hum / 65535. + * Additionally scale by HDC3020_THRESH_FRACTION to avoid precision loss + * when calculating threshold and hysteresis values. Result is percent + * scaled by HDC3020_THRESH_FRACTION. + */ + hum = FIELD_GET(HDC3020_THRESH_HUM_MASK, thresh) << + HDC3020_THRESH_HUM_TRUNC_SHIFT; + + return hum * 100 / 5; +} + +static u16 hdc3020_thresh_set_temp(int s_temp, u16 curr_thresh) +{ + u64 temp; + u16 thresh; + + /* + * Calculate temperature threshold, shift it down to get the + * truncated threshold representation in the 9LSBs while keeping + * the current humidity threshold in the 7 MSBs. + */ + temp = (u64)(s_temp + 45000000) * 65535ULL; + temp = div_u64(temp, 1000000 * 175) >> HDC3020_THRESH_TEMP_TRUNC_SHIFT; + thresh = FIELD_PREP(HDC3020_THRESH_TEMP_MASK, temp); + thresh |= (FIELD_GET(HDC3020_THRESH_HUM_MASK, curr_thresh) << + HDC3020_THRESH_HUM_TRUNC_SHIFT); + + return thresh; +} + +static u16 hdc3020_thresh_set_hum(int s_hum, u16 curr_thresh) +{ + u64 hum; + u16 thresh; + + /* + * Calculate humidity threshold, shift it down and up to get the + * truncated threshold representation in the 7MSBs while keeping + * the current temperature threshold in the 9 LSBs. + */ + hum = (u64)(s_hum) * 65535ULL; + hum = div_u64(hum, 1000000 * 100) >> HDC3020_THRESH_HUM_TRUNC_SHIFT; + thresh = FIELD_PREP(HDC3020_THRESH_HUM_MASK, hum); + thresh |= FIELD_GET(HDC3020_THRESH_TEMP_MASK, curr_thresh); + + return thresh; +} + +static +int hdc3020_thresh_clr(s64 s_thresh, s64 s_hyst, enum iio_event_direction dir) +{ + s64 s_clr; + + /* + * Include directions when calculation the clear value, + * since hysteresis is unsigned by definition and the + * clear value is an absolute value which is signed. + */ + if (dir == IIO_EV_DIR_RISING) + s_clr = s_thresh - s_hyst; + else + s_clr = s_thresh + s_hyst; + + /* Divide by HDC3020_THRESH_FRACTION to get units of micro */ + return div_s64(s_clr, HDC3020_THRESH_FRACTION); +} + +static int _hdc3020_write_thresh(struct hdc3020_data *data, u16 reg, u16 val) +{ + u8 buf[5]; + + put_unaligned_be16(reg, buf); + put_unaligned_be16(val, buf + 2); + buf[4] = crc8(hdc3020_crc8_table, buf + 2, 2, CRC8_INIT_VALUE); + + return hdc3020_write_bytes(data, buf, 5); +} + +static int hdc3020_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct hdc3020_data *data = iio_priv(indio_dev); + u16 reg, reg_val, reg_thresh_rd, reg_clr_rd, reg_thresh_wr, reg_clr_wr; + s64 s_thresh, s_hyst, s_clr; + int s_val, thresh, clr, ret; + + /* Select threshold registers */ + if (dir == IIO_EV_DIR_RISING) { + reg_thresh_rd = HDC3020_R_T_RH_THRESH_HIGH; + reg_thresh_wr = HDC3020_S_T_RH_THRESH_HIGH; + reg_clr_rd = HDC3020_R_T_RH_THRESH_HIGH_CLR; + reg_clr_wr = HDC3020_S_T_RH_THRESH_HIGH_CLR; + } else { + reg_thresh_rd = HDC3020_R_T_RH_THRESH_LOW; + reg_thresh_wr = HDC3020_S_T_RH_THRESH_LOW; + reg_clr_rd = HDC3020_R_T_RH_THRESH_LOW_CLR; + reg_clr_wr = HDC3020_S_T_RH_THRESH_LOW_CLR; + } + + guard(mutex)(&data->lock); + ret = hdc3020_read_be16(data, reg_thresh_rd); + if (ret < 0) + return ret; + + thresh = ret; + ret = hdc3020_read_be16(data, reg_clr_rd); + if (ret < 0) + return ret; + + clr = ret; + /* Scale value to include decimal part into calculations */ + s_val = (val < 0) ? (val * 1000 - val2) : (val * 1000 + val2); + switch (chan->type) { + case IIO_TEMP: + switch (info) { + case IIO_EV_INFO_VALUE: + s_val = max(s_val, HDC3020_MIN_TEMP_MICRO); + s_val = min(s_val, HDC3020_MAX_TEMP_MICRO); + reg = reg_thresh_wr; + reg_val = hdc3020_thresh_set_temp(s_val, thresh); + ret = _hdc3020_write_thresh(data, reg, reg_val); + if (ret < 0) + return ret; + + /* Calculate old hysteresis */ + s_thresh = (s64)hdc3020_thresh_get_temp(thresh) * 1000000; + s_clr = (s64)hdc3020_thresh_get_temp(clr) * 1000000; + s_hyst = div_s64(abs(s_thresh - s_clr), + HDC3020_THRESH_FRACTION); + /* Set new threshold */ + thresh = reg_val; + /* Set old hysteresis */ + s_val = s_hyst; + fallthrough; + case IIO_EV_INFO_HYSTERESIS: + /* + * Function hdc3020_thresh_get_temp returns temperature + * in degree celsius scaled by HDC3020_THRESH_FRACTION. + * Scale by 1000000 to be able to subtract scaled + * hysteresis value. + */ + s_thresh = (s64)hdc3020_thresh_get_temp(thresh) * 1000000; + /* + * Units of s_val are in micro degree celsius, scale by + * HDC3020_THRESH_FRACTION to get same units as s_thresh. + */ + s_val = min(abs(s_val), HDC3020_MAX_TEMP_HYST_MICRO); + s_hyst = (s64)s_val * HDC3020_THRESH_FRACTION; + s_clr = hdc3020_thresh_clr(s_thresh, s_hyst, dir); + s_clr = max(s_clr, HDC3020_MIN_TEMP_MICRO); + s_clr = min(s_clr, HDC3020_MAX_TEMP_MICRO); + reg = reg_clr_wr; + reg_val = hdc3020_thresh_set_temp(s_clr, clr); + break; + default: + return -EOPNOTSUPP; + } + break; + case IIO_HUMIDITYRELATIVE: + s_val = (s_val < 0) ? 0 : min(s_val, HDC3020_MAX_HUM_MICRO); + switch (info) { + case IIO_EV_INFO_VALUE: + reg = reg_thresh_wr; + reg_val = hdc3020_thresh_set_hum(s_val, thresh); + ret = _hdc3020_write_thresh(data, reg, reg_val); + if (ret < 0) + return ret; + + /* Calculate old hysteresis */ + s_thresh = (s64)hdc3020_thresh_get_hum(thresh) * 1000000; + s_clr = (s64)hdc3020_thresh_get_hum(clr) * 1000000; + s_hyst = div_s64(abs(s_thresh - s_clr), + HDC3020_THRESH_FRACTION); + /* Set new threshold */ + thresh = reg_val; + /* Try to set old hysteresis */ + s_val = min(abs(s_hyst), HDC3020_MAX_HUM_MICRO); + fallthrough; + case IIO_EV_INFO_HYSTERESIS: + /* + * Function hdc3020_thresh_get_hum returns relative + * humidity in percent scaled by HDC3020_THRESH_FRACTION. + * Scale by 1000000 to be able to subtract scaled + * hysteresis value. + */ + s_thresh = (s64)hdc3020_thresh_get_hum(thresh) * 1000000; + /* + * Units of s_val are in micro percent, scale by + * HDC3020_THRESH_FRACTION to get same units as s_thresh. + */ + s_hyst = (s64)s_val * HDC3020_THRESH_FRACTION; + s_clr = hdc3020_thresh_clr(s_thresh, s_hyst, dir); + s_clr = max(s_clr, 0); + s_clr = min(s_clr, HDC3020_MAX_HUM_MICRO); + reg = reg_clr_wr; + reg_val = hdc3020_thresh_set_hum(s_clr, clr); + break; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; + } + + return _hdc3020_write_thresh(data, reg, reg_val); +} + +static int hdc3020_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct hdc3020_data *data = iio_priv(indio_dev); + u16 reg_thresh, reg_clr; + int thresh, clr, ret; + + /* Select threshold registers */ + if (dir == IIO_EV_DIR_RISING) { + reg_thresh = HDC3020_R_T_RH_THRESH_HIGH; + reg_clr = HDC3020_R_T_RH_THRESH_HIGH_CLR; + } else { + reg_thresh = HDC3020_R_T_RH_THRESH_LOW; + reg_clr = HDC3020_R_T_RH_THRESH_LOW_CLR; + } + + guard(mutex)(&data->lock); + ret = hdc3020_read_be16(data, reg_thresh); + if (ret < 0) + return ret; + + switch (chan->type) { + case IIO_TEMP: + thresh = hdc3020_thresh_get_temp(ret); + switch (info) { + case IIO_EV_INFO_VALUE: + *val = thresh * MILLI; + break; + case IIO_EV_INFO_HYSTERESIS: + ret = hdc3020_read_be16(data, reg_clr); + if (ret < 0) + return ret; + + clr = hdc3020_thresh_get_temp(ret); + *val = abs(thresh - clr) * MILLI; + break; + default: + return -EOPNOTSUPP; + } + *val2 = HDC3020_THRESH_FRACTION; + return IIO_VAL_FRACTIONAL; + case IIO_HUMIDITYRELATIVE: + thresh = hdc3020_thresh_get_hum(ret); + switch (info) { + case IIO_EV_INFO_VALUE: + *val = thresh * MILLI; + break; + case IIO_EV_INFO_HYSTERESIS: + ret = hdc3020_read_be16(data, reg_clr); + if (ret < 0) + return ret; + + clr = hdc3020_thresh_get_hum(ret); + *val = abs(thresh - clr) * MILLI; + break; + default: + return -EOPNOTSUPP; + } + *val2 = HDC3020_THRESH_FRACTION; + return IIO_VAL_FRACTIONAL; + default: + return -EOPNOTSUPP; + } +} + +static irqreturn_t hdc3020_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct hdc3020_data *data; + s64 time; + int ret; + + data = iio_priv(indio_dev); + ret = hdc3020_read_be16(data, HDC3020_R_STATUS); + if (ret < 0) + return IRQ_HANDLED; + + if (!(ret & (HDC3020_STATUS_T_HIGH_ALERT | HDC3020_STATUS_T_LOW_ALERT | + HDC3020_STATUS_RH_HIGH_ALERT | HDC3020_STATUS_RH_LOW_ALERT))) + return IRQ_NONE; + + time = iio_get_time_ns(indio_dev); + if (ret & HDC3020_STATUS_T_HIGH_ALERT) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_TEMP, 0, + IIO_NO_MOD, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + time); + + if (ret & HDC3020_STATUS_T_LOW_ALERT) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_TEMP, 0, + IIO_NO_MOD, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + time); + + if (ret & HDC3020_STATUS_RH_HIGH_ALERT) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_HUMIDITYRELATIVE, 0, + IIO_NO_MOD, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + time); + + if (ret & HDC3020_STATUS_RH_LOW_ALERT) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_HUMIDITYRELATIVE, 0, + IIO_NO_MOD, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + time); + + return IRQ_HANDLED; +} + +static const struct iio_info hdc3020_info = { + .read_raw = hdc3020_read_raw, + .write_raw = hdc3020_write_raw, + .read_avail = hdc3020_read_available, + .read_event_value = hdc3020_read_thresh, + .write_event_value = hdc3020_write_thresh, +}; + +static int hdc3020_power_off(struct hdc3020_data *data) +{ + hdc3020_exec_cmd(data, HDC3020_EXIT_AUTO); + + if (data->reset_gpio) + gpiod_set_value_cansleep(data->reset_gpio, 1); + + return regulator_disable(data->vdd_supply); +} + +static int hdc3020_power_on(struct hdc3020_data *data) +{ + int ret; + + ret = regulator_enable(data->vdd_supply); + if (ret) + return ret; + + fsleep(5000); + + if (data->reset_gpio) { + gpiod_set_value_cansleep(data->reset_gpio, 0); + fsleep(3000); + } + + if (data->client->irq) { + /* + * The alert output is activated by default upon power up, + * hardware reset, and soft reset. Clear the status register. + */ + ret = hdc3020_exec_cmd(data, HDC3020_S_STATUS); + if (ret) { + hdc3020_power_off(data); + return ret; + } + } + + ret = hdc3020_exec_cmd(data, HDC3020_S_AUTO_10HZ_MOD0); + if (ret) + hdc3020_power_off(data); + + return ret; +} + +static void hdc3020_exit(void *data) +{ + hdc3020_power_off(data); +} + +static int hdc3020_probe(struct i2c_client *client) +{ + struct iio_dev *indio_dev; + struct hdc3020_data *data; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -EOPNOTSUPP; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + dev_set_drvdata(&client->dev, indio_dev); + + data = iio_priv(indio_dev); + data->client = client; + mutex_init(&data->lock); + + crc8_populate_msb(hdc3020_crc8_table, HDC3020_CRC8_POLYNOMIAL); + + indio_dev->name = "hdc3020"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &hdc3020_info; + indio_dev->channels = hdc3020_channels; + indio_dev->num_channels = ARRAY_SIZE(hdc3020_channels); + + data->vdd_supply = devm_regulator_get(&client->dev, "vdd"); + if (IS_ERR(data->vdd_supply)) + return dev_err_probe(&client->dev, PTR_ERR(data->vdd_supply), + "Unable to get VDD regulator\n"); + + data->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(data->reset_gpio)) + return dev_err_probe(&client->dev, PTR_ERR(data->reset_gpio), + "Cannot get reset GPIO\n"); + + ret = hdc3020_power_on(data); + if (ret) + return dev_err_probe(&client->dev, ret, "Power on failed\n"); + + ret = devm_add_action_or_reset(&data->client->dev, hdc3020_exit, data); + if (ret) + return ret; + + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, hdc3020_interrupt_handler, + IRQF_ONESHOT, "hdc3020", + indio_dev); + if (ret) + return ret; + } + + ret = devm_iio_device_register(&data->client->dev, indio_dev); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to add device"); + + return 0; +} + +static int hdc3020_suspend(struct device *dev) +{ + struct iio_dev *iio_dev = dev_get_drvdata(dev); + struct hdc3020_data *data = iio_priv(iio_dev); + + return hdc3020_power_off(data); +} + +static int hdc3020_resume(struct device *dev) +{ + struct iio_dev *iio_dev = dev_get_drvdata(dev); + struct hdc3020_data *data = iio_priv(iio_dev); + + return hdc3020_power_on(data); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(hdc3020_pm_ops, hdc3020_suspend, hdc3020_resume); + +static const struct i2c_device_id hdc3020_id[] = { + { .name = "hdc3020" }, + { .name = "hdc3021" }, + { .name = "hdc3022" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, hdc3020_id); + +static const struct of_device_id hdc3020_dt_ids[] = { + { .compatible = "ti,hdc3020" }, + { .compatible = "ti,hdc3021" }, + { .compatible = "ti,hdc3022" }, + { } +}; +MODULE_DEVICE_TABLE(of, hdc3020_dt_ids); + +static struct i2c_driver hdc3020_driver = { + .driver = { + .name = "hdc3020", + .pm = pm_sleep_ptr(&hdc3020_pm_ops), + .of_match_table = hdc3020_dt_ids, + }, + .probe = hdc3020_probe, + .id_table = hdc3020_id, +}; +module_i2c_driver(hdc3020_driver); + +MODULE_AUTHOR("Javier Carrasco "); +MODULE_AUTHOR("Li peiyu <579lpy@gmail.com>"); +MODULE_DESCRIPTION("TI HDC3020 humidity and temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/humidity/hid-sensor-humidity.c b/drivers/iio/humidity/hid-sensor-humidity.c new file mode 100644 index 000000000..7cec81ff5 --- /dev/null +++ b/drivers/iio/humidity/hid-sensor-humidity.c @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HID Sensors Driver + * Copyright (c) 2017, Intel Corporation. + */ +#include +#include +#include +#include +#include +#include + +#include "hid-sensor-trigger.h" + +struct hid_humidity_state { + struct hid_sensor_common common_attributes; + struct hid_sensor_hub_attribute_info humidity_attr; + struct { + s32 humidity_data; + aligned_s64 timestamp; + } scan; + int scale_pre_decml; + int scale_post_decml; + int scale_precision; + int value_offset; +}; + +static const u32 humidity_sensitivity_addresses[] = { + HID_USAGE_SENSOR_ATMOSPHERIC_HUMIDITY, +}; + +/* Channel definitions */ +static const struct iio_chan_spec humidity_channels[] = { + { + .type = IIO_HUMIDITYRELATIVE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + }, + IIO_CHAN_SOFT_TIMESTAMP(1) +}; + +/* Adjust channel real bits based on report descriptor */ +static void humidity_adjust_channel_bit_mask(struct iio_chan_spec *channels, + int channel, int size) +{ + channels[channel].scan_type.sign = 's'; + /* Real storage bits will change based on the report desc. */ + channels[channel].scan_type.realbits = size * 8; + /* Maximum size of a sample to capture is s32 */ + channels[channel].scan_type.storagebits = sizeof(s32) * 8; +} + +static int humidity_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct hid_humidity_state *humid_st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type != IIO_HUMIDITYRELATIVE) + return -EINVAL; + hid_sensor_power_state(&humid_st->common_attributes, true); + *val = sensor_hub_input_attr_get_raw_value( + humid_st->common_attributes.hsdev, + HID_USAGE_SENSOR_HUMIDITY, + HID_USAGE_SENSOR_ATMOSPHERIC_HUMIDITY, + humid_st->humidity_attr.report_id, + SENSOR_HUB_SYNC, + humid_st->humidity_attr.logical_minimum < 0); + hid_sensor_power_state(&humid_st->common_attributes, false); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = humid_st->scale_pre_decml; + *val2 = humid_st->scale_post_decml; + + return humid_st->scale_precision; + + case IIO_CHAN_INFO_OFFSET: + *val = humid_st->value_offset; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SAMP_FREQ: + return hid_sensor_read_samp_freq_value( + &humid_st->common_attributes, val, val2); + + case IIO_CHAN_INFO_HYSTERESIS: + return hid_sensor_read_raw_hyst_value( + &humid_st->common_attributes, val, val2); + + default: + return -EINVAL; + } +} + +static int humidity_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct hid_humidity_state *humid_st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return hid_sensor_write_samp_freq_value( + &humid_st->common_attributes, val, val2); + + case IIO_CHAN_INFO_HYSTERESIS: + return hid_sensor_write_raw_hyst_value( + &humid_st->common_attributes, val, val2); + + default: + return -EINVAL; + } +} + +static const struct iio_info humidity_info = { + .read_raw = &humidity_read_raw, + .write_raw = &humidity_write_raw, +}; + +/* Callback handler to send event after all samples are received and captured */ +static int humidity_proc_event(struct hid_sensor_hub_device *hsdev, + u32 usage_id, void *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct hid_humidity_state *humid_st = iio_priv(indio_dev); + + if (atomic_read(&humid_st->common_attributes.data_ready)) + iio_push_to_buffers_with_timestamp(indio_dev, &humid_st->scan, + iio_get_time_ns(indio_dev)); + + return 0; +} + +/* Capture samples in local storage */ +static int humidity_capture_sample(struct hid_sensor_hub_device *hsdev, + u32 usage_id, + size_t raw_len, char *raw_data, + void *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct hid_humidity_state *humid_st = iio_priv(indio_dev); + + switch (usage_id) { + case HID_USAGE_SENSOR_ATMOSPHERIC_HUMIDITY: + humid_st->scan.humidity_data = *(s32 *)raw_data; + + return 0; + default: + return -EINVAL; + } +} + +/* Parse report which is specific to an usage id */ +static int humidity_parse_report(struct platform_device *pdev, + struct hid_sensor_hub_device *hsdev, + struct iio_chan_spec *channels, + u32 usage_id, + struct hid_humidity_state *st) +{ + int ret; + + ret = sensor_hub_input_get_attribute_info(hsdev, HID_INPUT_REPORT, + usage_id, + HID_USAGE_SENSOR_ATMOSPHERIC_HUMIDITY, + &st->humidity_attr); + if (ret < 0) + return ret; + + humidity_adjust_channel_bit_mask(channels, 0, st->humidity_attr.size); + + st->scale_precision = hid_sensor_format_scale( + HID_USAGE_SENSOR_HUMIDITY, + &st->humidity_attr, + &st->scale_pre_decml, + &st->scale_post_decml); + + return ret; +} + +static struct hid_sensor_hub_callbacks humidity_callbacks = { + .send_event = &humidity_proc_event, + .capture_sample = &humidity_capture_sample, +}; + +/* Function to initialize the processing for usage id */ +static int hid_humidity_probe(struct platform_device *pdev) +{ + static const char *name = "humidity"; + struct iio_dev *indio_dev; + struct hid_humidity_state *humid_st; + struct iio_chan_spec *humid_chans; + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*humid_st)); + if (!indio_dev) + return -ENOMEM; + + humid_st = iio_priv(indio_dev); + humid_st->common_attributes.hsdev = hsdev; + humid_st->common_attributes.pdev = pdev; + + ret = hid_sensor_parse_common_attributes(hsdev, + HID_USAGE_SENSOR_HUMIDITY, + &humid_st->common_attributes, + humidity_sensitivity_addresses, + ARRAY_SIZE(humidity_sensitivity_addresses)); + if (ret) + return ret; + + humid_chans = devm_kmemdup(&pdev->dev, humidity_channels, + sizeof(humidity_channels), GFP_KERNEL); + if (!humid_chans) + return -ENOMEM; + + ret = humidity_parse_report(pdev, hsdev, humid_chans, + HID_USAGE_SENSOR_HUMIDITY, humid_st); + if (ret) + return ret; + + indio_dev->channels = humid_chans; + indio_dev->num_channels = ARRAY_SIZE(humidity_channels); + indio_dev->info = &humidity_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + atomic_set(&humid_st->common_attributes.data_ready, 0); + + ret = hid_sensor_setup_trigger(indio_dev, name, + &humid_st->common_attributes); + if (ret) + return ret; + + platform_set_drvdata(pdev, indio_dev); + + humidity_callbacks.pdev = pdev; + ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_HUMIDITY, + &humidity_callbacks); + if (ret) + goto error_remove_trigger; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_remove_callback; + + return ret; + +error_remove_callback: + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_HUMIDITY); +error_remove_trigger: + hid_sensor_remove_trigger(indio_dev, &humid_st->common_attributes); + return ret; +} + +/* Function to deinitialize the processing for usage id */ +static void hid_humidity_remove(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct hid_humidity_state *humid_st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_HUMIDITY); + hid_sensor_remove_trigger(indio_dev, &humid_st->common_attributes); +} + +static const struct platform_device_id hid_humidity_ids[] = { + { + /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-200032", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, hid_humidity_ids); + +static struct platform_driver hid_humidity_platform_driver = { + .id_table = hid_humidity_ids, + .driver = { + .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, + }, + .probe = hid_humidity_probe, + .remove = hid_humidity_remove, +}; +module_platform_driver(hid_humidity_platform_driver); + +MODULE_DESCRIPTION("HID Environmental humidity sensor"); +MODULE_AUTHOR("Song Hongyan "); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_HID"); diff --git a/drivers/iio/humidity/hts221.h b/drivers/iio/humidity/hts221.h new file mode 100644 index 000000000..0215f11fc --- /dev/null +++ b/drivers/iio/humidity/hts221.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * STMicroelectronics hts221 sensor driver + * + * Copyright 2016 STMicroelectronics Inc. + * + * Lorenzo Bianconi + */ + +#ifndef HTS221_H +#define HTS221_H + +#define HTS221_DEV_NAME "hts221" + +#include + +enum hts221_sensor_type { + HTS221_SENSOR_H, + HTS221_SENSOR_T, + HTS221_SENSOR_MAX, +}; + +struct hts221_sensor { + u8 cur_avg_idx; + int slope, b_gen; +}; + +struct hts221_hw { + const char *name; + struct device *dev; + struct regmap *regmap; + + struct iio_trigger *trig; + int irq; + + struct hts221_sensor sensors[HTS221_SENSOR_MAX]; + + bool enabled; + u8 odr; + /* Ensure natural alignment of timestamp */ + struct { + __le16 channels[2]; + aligned_s64 ts; + } scan; +}; + +extern const struct dev_pm_ops hts221_pm_ops; + +int hts221_probe(struct device *dev, int irq, const char *name, + struct regmap *regmap); +int hts221_set_enable(struct hts221_hw *hw, bool enable); +int hts221_allocate_buffers(struct iio_dev *iio_dev); +int hts221_allocate_trigger(struct iio_dev *iio_dev); + +#endif /* HTS221_H */ diff --git a/drivers/iio/humidity/hts221_buffer.c b/drivers/iio/humidity/hts221_buffer.c new file mode 100644 index 000000000..df12c7a25 --- /dev/null +++ b/drivers/iio/humidity/hts221_buffer.c @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics hts221 sensor driver + * + * Copyright 2016 STMicroelectronics Inc. + * + * Lorenzo Bianconi + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "hts221.h" + +#define HTS221_REG_DRDY_HL_ADDR 0x22 +#define HTS221_REG_DRDY_HL_MASK BIT(7) +#define HTS221_REG_DRDY_PP_OD_ADDR 0x22 +#define HTS221_REG_DRDY_PP_OD_MASK BIT(6) +#define HTS221_REG_DRDY_EN_ADDR 0x22 +#define HTS221_REG_DRDY_EN_MASK BIT(2) +#define HTS221_REG_STATUS_ADDR 0x27 +#define HTS221_RH_DRDY_MASK BIT(1) +#define HTS221_TEMP_DRDY_MASK BIT(0) + +static int hts221_trig_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *iio_dev = iio_trigger_get_drvdata(trig); + struct hts221_hw *hw = iio_priv(iio_dev); + + return regmap_update_bits(hw->regmap, HTS221_REG_DRDY_EN_ADDR, + HTS221_REG_DRDY_EN_MASK, + FIELD_PREP(HTS221_REG_DRDY_EN_MASK, state)); +} + +static const struct iio_trigger_ops hts221_trigger_ops = { + .set_trigger_state = hts221_trig_set_state, +}; + +static irqreturn_t hts221_trigger_handler_thread(int irq, void *private) +{ + struct hts221_hw *hw = private; + int err, status; + + err = regmap_read(hw->regmap, HTS221_REG_STATUS_ADDR, &status); + if (err < 0) + return IRQ_HANDLED; + + /* + * H_DA bit (humidity data available) is routed to DRDY line. + * Humidity sample is computed after temperature one. + * Here we can assume data channels are both available if H_DA bit + * is set in status register + */ + if (!(status & HTS221_RH_DRDY_MASK)) + return IRQ_NONE; + + iio_trigger_poll_nested(hw->trig); + + return IRQ_HANDLED; +} + +int hts221_allocate_trigger(struct iio_dev *iio_dev) +{ + struct hts221_hw *hw = iio_priv(iio_dev); + struct st_sensors_platform_data *pdata = dev_get_platdata(hw->dev); + bool irq_active_low = false, open_drain = false; + unsigned long irq_type; + int err; + + irq_type = irq_get_trigger_type(hw->irq); + switch (irq_type) { + case IRQF_TRIGGER_HIGH: + case IRQF_TRIGGER_RISING: + break; + case IRQF_TRIGGER_LOW: + case IRQF_TRIGGER_FALLING: + irq_active_low = true; + break; + default: + dev_info(hw->dev, + "mode %lx unsupported, using IRQF_TRIGGER_RISING\n", + irq_type); + irq_type = IRQF_TRIGGER_RISING; + break; + } + + err = regmap_update_bits(hw->regmap, HTS221_REG_DRDY_HL_ADDR, + HTS221_REG_DRDY_HL_MASK, + FIELD_PREP(HTS221_REG_DRDY_HL_MASK, + irq_active_low)); + if (err < 0) + return err; + + if (device_property_read_bool(hw->dev, "drive-open-drain") || + (pdata && pdata->open_drain)) { + irq_type |= IRQF_SHARED; + open_drain = true; + } + + err = regmap_update_bits(hw->regmap, HTS221_REG_DRDY_PP_OD_ADDR, + HTS221_REG_DRDY_PP_OD_MASK, + FIELD_PREP(HTS221_REG_DRDY_PP_OD_MASK, + open_drain)); + if (err < 0) + return err; + + err = devm_request_threaded_irq(hw->dev, hw->irq, NULL, + hts221_trigger_handler_thread, + irq_type | IRQF_ONESHOT, + hw->name, hw); + if (err) + return err; + + hw->trig = devm_iio_trigger_alloc(hw->dev, "%s-trigger", + iio_dev->name); + if (!hw->trig) + return -ENOMEM; + + iio_trigger_set_drvdata(hw->trig, iio_dev); + hw->trig->ops = &hts221_trigger_ops; + + err = devm_iio_trigger_register(hw->dev, hw->trig); + + iio_dev->trig = iio_trigger_get(hw->trig); + + return err; +} + +static int hts221_buffer_preenable(struct iio_dev *iio_dev) +{ + return hts221_set_enable(iio_priv(iio_dev), true); +} + +static int hts221_buffer_postdisable(struct iio_dev *iio_dev) +{ + return hts221_set_enable(iio_priv(iio_dev), false); +} + +static const struct iio_buffer_setup_ops hts221_buffer_ops = { + .preenable = hts221_buffer_preenable, + .postdisable = hts221_buffer_postdisable, +}; + +static irqreturn_t hts221_buffer_handler_thread(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *iio_dev = pf->indio_dev; + struct hts221_hw *hw = iio_priv(iio_dev); + struct iio_chan_spec const *ch; + int err; + + /* humidity data */ + ch = &iio_dev->channels[HTS221_SENSOR_H]; + err = regmap_bulk_read(hw->regmap, ch->address, + &hw->scan.channels[0], + sizeof(hw->scan.channels[0])); + if (err < 0) + goto out; + + /* temperature data */ + ch = &iio_dev->channels[HTS221_SENSOR_T]; + err = regmap_bulk_read(hw->regmap, ch->address, + &hw->scan.channels[1], + sizeof(hw->scan.channels[1])); + if (err < 0) + goto out; + + iio_push_to_buffers_with_timestamp(iio_dev, &hw->scan, + iio_get_time_ns(iio_dev)); + +out: + iio_trigger_notify_done(hw->trig); + + return IRQ_HANDLED; +} + +int hts221_allocate_buffers(struct iio_dev *iio_dev) +{ + struct hts221_hw *hw = iio_priv(iio_dev); + return devm_iio_triggered_buffer_setup(hw->dev, iio_dev, + NULL, hts221_buffer_handler_thread, + &hts221_buffer_ops); +} + +MODULE_AUTHOR("Lorenzo Bianconi "); +MODULE_DESCRIPTION("STMicroelectronics hts221 buffer driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/humidity/hts221_core.c b/drivers/iio/humidity/hts221_core.c new file mode 100644 index 000000000..bfeb0a60d --- /dev/null +++ b/drivers/iio/humidity/hts221_core.c @@ -0,0 +1,682 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics hts221 sensor driver + * + * Copyright 2016 STMicroelectronics Inc. + * + * Lorenzo Bianconi + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hts221.h" + +#define HTS221_REG_WHOAMI_ADDR 0x0f +#define HTS221_REG_WHOAMI_VAL 0xbc + +#define HTS221_REG_CNTRL1_ADDR 0x20 +#define HTS221_REG_CNTRL2_ADDR 0x21 + +#define HTS221_ODR_MASK 0x03 +#define HTS221_BDU_MASK BIT(2) +#define HTS221_ENABLE_MASK BIT(7) + +/* calibration registers */ +#define HTS221_REG_0RH_CAL_X_H 0x36 +#define HTS221_REG_1RH_CAL_X_H 0x3a +#define HTS221_REG_0RH_CAL_Y_H 0x30 +#define HTS221_REG_1RH_CAL_Y_H 0x31 +#define HTS221_REG_0T_CAL_X_L 0x3c +#define HTS221_REG_1T_CAL_X_L 0x3e +#define HTS221_REG_0T_CAL_Y_H 0x32 +#define HTS221_REG_1T_CAL_Y_H 0x33 +#define HTS221_REG_T1_T0_CAL_Y_H 0x35 + +struct hts221_odr { + u8 hz; + u8 val; +}; + +#define HTS221_AVG_DEPTH 8 +struct hts221_avg { + u8 addr; + u8 mask; + u16 avg_avl[HTS221_AVG_DEPTH]; +}; + +static const struct hts221_odr hts221_odr_table[] = { + { 1, 0x01 }, /* 1Hz */ + { 7, 0x02 }, /* 7Hz */ + { 13, 0x03 }, /* 12.5Hz */ +}; + +static const struct hts221_avg hts221_avg_list[] = { + { + .addr = 0x10, + .mask = 0x07, + .avg_avl = { + 4, /* 0.4 %RH */ + 8, /* 0.3 %RH */ + 16, /* 0.2 %RH */ + 32, /* 0.15 %RH */ + 64, /* 0.1 %RH */ + 128, /* 0.07 %RH */ + 256, /* 0.05 %RH */ + 512, /* 0.03 %RH */ + }, + }, + { + .addr = 0x10, + .mask = 0x38, + .avg_avl = { + 2, /* 0.08 degC */ + 4, /* 0.05 degC */ + 8, /* 0.04 degC */ + 16, /* 0.03 degC */ + 32, /* 0.02 degC */ + 64, /* 0.015 degC */ + 128, /* 0.01 degC */ + 256, /* 0.007 degC */ + }, + }, +}; + +static const struct iio_chan_spec hts221_channels[] = { + { + .type = IIO_HUMIDITYRELATIVE, + .address = 0x28, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_TEMP, + .address = 0x2a, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static int hts221_check_whoami(struct hts221_hw *hw) +{ + int err, data; + + err = regmap_read(hw->regmap, HTS221_REG_WHOAMI_ADDR, &data); + if (err < 0) { + dev_err(hw->dev, "failed to read whoami register\n"); + return err; + } + + if (data != HTS221_REG_WHOAMI_VAL) { + dev_err(hw->dev, "wrong whoami {%02x vs %02x}\n", + data, HTS221_REG_WHOAMI_VAL); + return -ENODEV; + } + + return 0; +} + +static int hts221_update_odr(struct hts221_hw *hw, u8 odr) +{ + int i, err; + + for (i = 0; i < ARRAY_SIZE(hts221_odr_table); i++) + if (hts221_odr_table[i].hz == odr) + break; + + if (i == ARRAY_SIZE(hts221_odr_table)) + return -EINVAL; + + err = regmap_update_bits(hw->regmap, HTS221_REG_CNTRL1_ADDR, + HTS221_ODR_MASK, + FIELD_PREP(HTS221_ODR_MASK, + hts221_odr_table[i].val)); + if (err < 0) + return err; + + hw->odr = odr; + + return 0; +} + +static int hts221_update_avg(struct hts221_hw *hw, + enum hts221_sensor_type type, + u16 val) +{ + const struct hts221_avg *avg = &hts221_avg_list[type]; + int i, err, data; + + for (i = 0; i < HTS221_AVG_DEPTH; i++) + if (avg->avg_avl[i] == val) + break; + + if (i == HTS221_AVG_DEPTH) + return -EINVAL; + + data = ((i << __ffs(avg->mask)) & avg->mask); + err = regmap_update_bits(hw->regmap, avg->addr, + avg->mask, data); + if (err < 0) + return err; + + hw->sensors[type].cur_avg_idx = i; + + return 0; +} + +static ssize_t hts221_sysfs_sampling_freq(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int i; + ssize_t len = 0; + + for (i = 0; i < ARRAY_SIZE(hts221_odr_table); i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "%d ", + hts221_odr_table[i].hz); + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t +hts221_sysfs_rh_oversampling_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + const struct hts221_avg *avg = &hts221_avg_list[HTS221_SENSOR_H]; + ssize_t len = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(avg->avg_avl); i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "%d ", + avg->avg_avl[i]); + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t +hts221_sysfs_temp_oversampling_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + const struct hts221_avg *avg = &hts221_avg_list[HTS221_SENSOR_T]; + ssize_t len = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(avg->avg_avl); i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "%d ", + avg->avg_avl[i]); + buf[len - 1] = '\n'; + + return len; +} + +int hts221_set_enable(struct hts221_hw *hw, bool enable) +{ + int err; + + err = regmap_update_bits(hw->regmap, HTS221_REG_CNTRL1_ADDR, + HTS221_ENABLE_MASK, + FIELD_PREP(HTS221_ENABLE_MASK, enable)); + if (err < 0) + return err; + + hw->enabled = enable; + + return 0; +} + +static int hts221_parse_temp_caldata(struct hts221_hw *hw) +{ + int err, *slope, *b_gen, cal0, cal1; + s16 cal_x0, cal_x1, cal_y0, cal_y1; + __le16 val; + + err = regmap_read(hw->regmap, HTS221_REG_0T_CAL_Y_H, &cal0); + if (err < 0) + return err; + + err = regmap_read(hw->regmap, HTS221_REG_T1_T0_CAL_Y_H, &cal1); + if (err < 0) + return err; + cal_y0 = ((cal1 & 0x3) << 8) | cal0; + + err = regmap_read(hw->regmap, HTS221_REG_1T_CAL_Y_H, &cal0); + if (err < 0) + return err; + cal_y1 = (((cal1 & 0xc) >> 2) << 8) | cal0; + + err = regmap_bulk_read(hw->regmap, HTS221_REG_0T_CAL_X_L, + &val, sizeof(val)); + if (err < 0) + return err; + cal_x0 = le16_to_cpu(val); + + err = regmap_bulk_read(hw->regmap, HTS221_REG_1T_CAL_X_L, + &val, sizeof(val)); + if (err < 0) + return err; + cal_x1 = le16_to_cpu(val); + + slope = &hw->sensors[HTS221_SENSOR_T].slope; + b_gen = &hw->sensors[HTS221_SENSOR_T].b_gen; + + *slope = ((cal_y1 - cal_y0) * 8000) / (cal_x1 - cal_x0); + *b_gen = (((s32)cal_x1 * cal_y0 - (s32)cal_x0 * cal_y1) * 1000) / + (cal_x1 - cal_x0); + *b_gen *= 8; + + return 0; +} + +static int hts221_parse_rh_caldata(struct hts221_hw *hw) +{ + int err, *slope, *b_gen, data; + s16 cal_x0, cal_x1, cal_y0, cal_y1; + __le16 val; + + err = regmap_read(hw->regmap, HTS221_REG_0RH_CAL_Y_H, &data); + if (err < 0) + return err; + cal_y0 = data; + + err = regmap_read(hw->regmap, HTS221_REG_1RH_CAL_Y_H, &data); + if (err < 0) + return err; + cal_y1 = data; + + err = regmap_bulk_read(hw->regmap, HTS221_REG_0RH_CAL_X_H, + &val, sizeof(val)); + if (err < 0) + return err; + cal_x0 = le16_to_cpu(val); + + err = regmap_bulk_read(hw->regmap, HTS221_REG_1RH_CAL_X_H, + &val, sizeof(val)); + if (err < 0) + return err; + cal_x1 = le16_to_cpu(val); + + slope = &hw->sensors[HTS221_SENSOR_H].slope; + b_gen = &hw->sensors[HTS221_SENSOR_H].b_gen; + + *slope = ((cal_y1 - cal_y0) * 8000) / (cal_x1 - cal_x0); + *b_gen = (((s32)cal_x1 * cal_y0 - (s32)cal_x0 * cal_y1) * 1000) / + (cal_x1 - cal_x0); + *b_gen *= 8; + + return 0; +} + +static int hts221_get_sensor_scale(struct hts221_hw *hw, + enum iio_chan_type ch_type, + int *val, int *val2) +{ + s64 tmp; + s32 rem, div, data; + + switch (ch_type) { + case IIO_HUMIDITYRELATIVE: + data = hw->sensors[HTS221_SENSOR_H].slope; + div = (1 << 4) * 1000; + break; + case IIO_TEMP: + data = hw->sensors[HTS221_SENSOR_T].slope; + div = (1 << 6) * 1000; + break; + default: + return -EINVAL; + } + + tmp = div_s64(data * 1000000000LL, div); + tmp = div_s64_rem(tmp, 1000000000LL, &rem); + + *val = tmp; + *val2 = rem; + + return IIO_VAL_INT_PLUS_NANO; +} + +static int hts221_get_sensor_offset(struct hts221_hw *hw, + enum iio_chan_type ch_type, + int *val, int *val2) +{ + s64 tmp; + s32 rem, div, data; + + switch (ch_type) { + case IIO_HUMIDITYRELATIVE: + data = hw->sensors[HTS221_SENSOR_H].b_gen; + div = hw->sensors[HTS221_SENSOR_H].slope; + break; + case IIO_TEMP: + data = hw->sensors[HTS221_SENSOR_T].b_gen; + div = hw->sensors[HTS221_SENSOR_T].slope; + break; + default: + return -EINVAL; + } + + tmp = div_s64(data * 1000000000LL, div); + tmp = div_s64_rem(tmp, 1000000000LL, &rem); + + *val = tmp; + *val2 = rem; + + return IIO_VAL_INT_PLUS_NANO; +} + +static int hts221_read_oneshot(struct hts221_hw *hw, u8 addr, int *val) +{ + __le16 data; + int err; + + err = hts221_set_enable(hw, true); + if (err < 0) + return err; + + msleep(50); + + err = regmap_bulk_read(hw->regmap, addr, &data, sizeof(data)); + if (err < 0) + return err; + + hts221_set_enable(hw, false); + + *val = (s16)le16_to_cpu(data); + + return IIO_VAL_INT; +} + +static int __hts221_read_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *ch, + int *val, int *val2, long mask) +{ + struct hts221_hw *hw = iio_priv(iio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return hts221_read_oneshot(hw, ch->address, val); + case IIO_CHAN_INFO_SCALE: + return hts221_get_sensor_scale(hw, ch->type, val, val2); + case IIO_CHAN_INFO_OFFSET: + return hts221_get_sensor_offset(hw, ch->type, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + *val = hw->odr; + return IIO_VAL_INT; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: { + u8 idx; + const struct hts221_avg *avg; + + switch (ch->type) { + case IIO_HUMIDITYRELATIVE: + avg = &hts221_avg_list[HTS221_SENSOR_H]; + idx = hw->sensors[HTS221_SENSOR_H].cur_avg_idx; + *val = avg->avg_avl[idx]; + return IIO_VAL_INT; + case IIO_TEMP: + avg = &hts221_avg_list[HTS221_SENSOR_T]; + idx = hw->sensors[HTS221_SENSOR_T].cur_avg_idx; + *val = avg->avg_avl[idx]; + return IIO_VAL_INT; + default: + return -EINVAL; + } + } + default: + return -EINVAL; + } +} + +static int hts221_read_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *ch, + int *val, int *val2, long mask) +{ + int ret; + + if (!iio_device_claim_direct(iio_dev)) + return -EBUSY; + + ret = __hts221_read_raw(iio_dev, ch, val, val2, mask); + + iio_device_release_direct(iio_dev); + + return ret; +} + +static int __hts221_write_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int val, long mask) +{ + struct hts221_hw *hw = iio_priv(iio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return hts221_update_odr(hw, val); + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_HUMIDITYRELATIVE: + return hts221_update_avg(hw, HTS221_SENSOR_H, val); + case IIO_TEMP: + return hts221_update_avg(hw, HTS221_SENSOR_T, val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int hts221_write_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret; + + if (!iio_device_claim_direct(iio_dev)) + return -EBUSY; + + ret = __hts221_write_raw(iio_dev, chan, val, mask); + + iio_device_release_direct(iio_dev); + + return ret; +} + +static int hts221_validate_trigger(struct iio_dev *iio_dev, + struct iio_trigger *trig) +{ + struct hts221_hw *hw = iio_priv(iio_dev); + + return hw->trig == trig ? 0 : -EINVAL; +} + +static IIO_DEVICE_ATTR(in_humidity_oversampling_ratio_available, S_IRUGO, + hts221_sysfs_rh_oversampling_avail, NULL, 0); +static IIO_DEVICE_ATTR(in_temp_oversampling_ratio_available, S_IRUGO, + hts221_sysfs_temp_oversampling_avail, NULL, 0); +static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(hts221_sysfs_sampling_freq); + +static struct attribute *hts221_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_humidity_oversampling_ratio_available.dev_attr.attr, + &iio_dev_attr_in_temp_oversampling_ratio_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group hts221_attribute_group = { + .attrs = hts221_attributes, +}; + +static const struct iio_info hts221_info = { + .attrs = &hts221_attribute_group, + .read_raw = hts221_read_raw, + .write_raw = hts221_write_raw, + .validate_trigger = hts221_validate_trigger, +}; + +static const unsigned long hts221_scan_masks[] = {0x3, 0x0}; + +static int hts221_init_regulators(struct device *dev) +{ + int err; + + err = devm_regulator_get_enable(dev, "vdd"); + if (err) + return dev_err_probe(dev, err, "failed to get vdd regulator\n"); + + msleep(50); + + return 0; +} + +int hts221_probe(struct device *dev, int irq, const char *name, + struct regmap *regmap) +{ + struct iio_dev *iio_dev; + struct hts221_hw *hw; + int err; + u8 data; + + iio_dev = devm_iio_device_alloc(dev, sizeof(*hw)); + if (!iio_dev) + return -ENOMEM; + + dev_set_drvdata(dev, iio_dev); + + hw = iio_priv(iio_dev); + hw->name = name; + hw->dev = dev; + hw->irq = irq; + hw->regmap = regmap; + + err = hts221_init_regulators(dev); + if (err) + return err; + + err = hts221_check_whoami(hw); + if (err < 0) + return err; + + iio_dev->modes = INDIO_DIRECT_MODE; + iio_dev->available_scan_masks = hts221_scan_masks; + iio_dev->channels = hts221_channels; + iio_dev->num_channels = ARRAY_SIZE(hts221_channels); + iio_dev->name = HTS221_DEV_NAME; + iio_dev->info = &hts221_info; + + /* enable Block Data Update */ + err = regmap_update_bits(hw->regmap, HTS221_REG_CNTRL1_ADDR, + HTS221_BDU_MASK, + FIELD_PREP(HTS221_BDU_MASK, 1)); + if (err < 0) + return err; + + err = hts221_update_odr(hw, hts221_odr_table[0].hz); + if (err < 0) + return err; + + /* configure humidity sensor */ + err = hts221_parse_rh_caldata(hw); + if (err < 0) { + dev_err(hw->dev, "failed to get rh calibration data\n"); + return err; + } + + data = hts221_avg_list[HTS221_SENSOR_H].avg_avl[3]; + err = hts221_update_avg(hw, HTS221_SENSOR_H, data); + if (err < 0) { + dev_err(hw->dev, "failed to set rh oversampling ratio\n"); + return err; + } + + /* configure temperature sensor */ + err = hts221_parse_temp_caldata(hw); + if (err < 0) { + dev_err(hw->dev, + "failed to get temperature calibration data\n"); + return err; + } + + data = hts221_avg_list[HTS221_SENSOR_T].avg_avl[3]; + err = hts221_update_avg(hw, HTS221_SENSOR_T, data); + if (err < 0) { + dev_err(hw->dev, + "failed to set temperature oversampling ratio\n"); + return err; + } + + if (hw->irq > 0) { + err = hts221_allocate_buffers(iio_dev); + if (err < 0) + return err; + + err = hts221_allocate_trigger(iio_dev); + if (err) + return err; + } + + return devm_iio_device_register(hw->dev, iio_dev); +} +EXPORT_SYMBOL_NS(hts221_probe, "IIO_HTS221"); + +static int hts221_suspend(struct device *dev) +{ + struct iio_dev *iio_dev = dev_get_drvdata(dev); + struct hts221_hw *hw = iio_priv(iio_dev); + + return regmap_update_bits(hw->regmap, HTS221_REG_CNTRL1_ADDR, + HTS221_ENABLE_MASK, + FIELD_PREP(HTS221_ENABLE_MASK, false)); +} + +static int hts221_resume(struct device *dev) +{ + struct iio_dev *iio_dev = dev_get_drvdata(dev); + struct hts221_hw *hw = iio_priv(iio_dev); + int err = 0; + + if (hw->enabled) + err = regmap_update_bits(hw->regmap, HTS221_REG_CNTRL1_ADDR, + HTS221_ENABLE_MASK, + FIELD_PREP(HTS221_ENABLE_MASK, + true)); + return err; +} + +EXPORT_NS_SIMPLE_DEV_PM_OPS(hts221_pm_ops, hts221_suspend, hts221_resume, + IIO_HTS221); + +MODULE_AUTHOR("Lorenzo Bianconi "); +MODULE_DESCRIPTION("STMicroelectronics hts221 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/humidity/hts221_i2c.c b/drivers/iio/humidity/hts221_i2c.c new file mode 100644 index 000000000..40276abc5 --- /dev/null +++ b/drivers/iio/humidity/hts221_i2c.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics hts221 i2c driver + * + * Copyright 2016 STMicroelectronics Inc. + * + * Lorenzo Bianconi + */ + +#include +#include +#include +#include +#include + +#include "hts221.h" + +#define HTS221_I2C_AUTO_INCREMENT BIT(7) + +static const struct regmap_config hts221_i2c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .write_flag_mask = HTS221_I2C_AUTO_INCREMENT, + .read_flag_mask = HTS221_I2C_AUTO_INCREMENT, +}; + +static int hts221_i2c_probe(struct i2c_client *client) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, &hts221_i2c_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Failed to register i2c regmap %ld\n", + PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + return hts221_probe(&client->dev, client->irq, + client->name, regmap); +} + +static const struct acpi_device_id hts221_acpi_match[] = { + {"SMO9100", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, hts221_acpi_match); + +static const struct of_device_id hts221_i2c_of_match[] = { + { .compatible = "st,hts221", }, + { } +}; +MODULE_DEVICE_TABLE(of, hts221_i2c_of_match); + +static const struct i2c_device_id hts221_i2c_id_table[] = { + { .name = HTS221_DEV_NAME }, + { } +}; +MODULE_DEVICE_TABLE(i2c, hts221_i2c_id_table); + +static struct i2c_driver hts221_driver = { + .driver = { + .name = "hts221_i2c", + .pm = pm_sleep_ptr(&hts221_pm_ops), + .of_match_table = hts221_i2c_of_match, + .acpi_match_table = hts221_acpi_match, + }, + .probe = hts221_i2c_probe, + .id_table = hts221_i2c_id_table, +}; +module_i2c_driver(hts221_driver); + +MODULE_AUTHOR("Lorenzo Bianconi "); +MODULE_DESCRIPTION("STMicroelectronics hts221 i2c driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_HTS221"); diff --git a/drivers/iio/humidity/hts221_spi.c b/drivers/iio/humidity/hts221_spi.c new file mode 100644 index 000000000..f962842cc --- /dev/null +++ b/drivers/iio/humidity/hts221_spi.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics hts221 spi driver + * + * Copyright 2016 STMicroelectronics Inc. + * + * Lorenzo Bianconi + */ + +#include +#include +#include +#include +#include + +#include "hts221.h" + +#define HTS221_SPI_READ BIT(7) +#define HTS221_SPI_AUTO_INCREMENT BIT(6) + +static const struct regmap_config hts221_spi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .write_flag_mask = HTS221_SPI_AUTO_INCREMENT, + .read_flag_mask = HTS221_SPI_READ | HTS221_SPI_AUTO_INCREMENT, +}; + +static int hts221_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_spi(spi, &hts221_spi_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "Failed to register spi regmap %ld\n", + PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + return hts221_probe(&spi->dev, spi->irq, + spi->modalias, regmap); +} + +static const struct of_device_id hts221_spi_of_match[] = { + { .compatible = "st,hts221", }, + { } +}; +MODULE_DEVICE_TABLE(of, hts221_spi_of_match); + +static const struct spi_device_id hts221_spi_id_table[] = { + { .name = HTS221_DEV_NAME }, + { } +}; +MODULE_DEVICE_TABLE(spi, hts221_spi_id_table); + +static struct spi_driver hts221_driver = { + .driver = { + .name = "hts221_spi", + .pm = pm_sleep_ptr(&hts221_pm_ops), + .of_match_table = hts221_spi_of_match, + }, + .probe = hts221_spi_probe, + .id_table = hts221_spi_id_table, +}; +module_spi_driver(hts221_driver); + +MODULE_AUTHOR("Lorenzo Bianconi "); +MODULE_DESCRIPTION("STMicroelectronics hts221 spi driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_HTS221"); diff --git a/drivers/iio/humidity/htu21.c b/drivers/iio/humidity/htu21.c new file mode 100644 index 000000000..a9dbf08b4 --- /dev/null +++ b/drivers/iio/humidity/htu21.c @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * htu21.c - Support for Measurement-Specialties + * htu21 temperature & humidity sensor + * and humidity part of MS8607 sensor + * + * Copyright (c) 2014 Measurement-Specialties + * + * (7-bit I2C slave address 0x40) + * + * Datasheet: + * http://www.meas-spec.com/downloads/HTU21D.pdf + * Datasheet: + * http://www.meas-spec.com/downloads/MS8607-02BA01.pdf + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "../common/ms_sensors/ms_sensors_i2c.h" + +#define HTU21_RESET 0xFE + +enum { + HTU21, + MS8607 +}; + +static const int htu21_samp_freq[4] = { 20, 40, 70, 120 }; +/* String copy of the above const for readability purpose */ +static const char htu21_show_samp_freq[] = "20 40 70 120"; + +static int htu21_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + int ret, temperature; + unsigned int humidity; + struct ms_ht_dev *dev_data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (channel->type) { + case IIO_TEMP: /* in milli °C */ + ret = ms_sensors_ht_read_temperature(dev_data, + &temperature); + if (ret) + return ret; + *val = temperature; + + return IIO_VAL_INT; + case IIO_HUMIDITYRELATIVE: /* in milli %RH */ + ret = ms_sensors_ht_read_humidity(dev_data, + &humidity); + if (ret) + return ret; + *val = humidity; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + *val = htu21_samp_freq[dev_data->res_index]; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int htu21_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ms_ht_dev *dev_data = iio_priv(indio_dev); + int i, ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + i = ARRAY_SIZE(htu21_samp_freq); + while (i-- > 0) + if (val == htu21_samp_freq[i]) + break; + if (i < 0) + return -EINVAL; + mutex_lock(&dev_data->lock); + dev_data->res_index = i; + ret = ms_sensors_write_resolution(dev_data, i); + mutex_unlock(&dev_data->lock); + + return ret; + default: + return -EINVAL; + } +} + +static const struct iio_chan_spec htu21_channels[] = { + { + .type = IIO_TEMP, + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + }, + { + .type = IIO_HUMIDITYRELATIVE, + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + } +}; + +/* + * Meas Spec recommendation is to not read temperature + * on this driver part for MS8607 + */ +static const struct iio_chan_spec ms8607_channels[] = { + { + .type = IIO_HUMIDITYRELATIVE, + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + } +}; + +static ssize_t htu21_show_battery_low(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ms_ht_dev *dev_data = iio_priv(indio_dev); + + return ms_sensors_show_battery_low(dev_data, buf); +} + +static ssize_t htu21_show_heater(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ms_ht_dev *dev_data = iio_priv(indio_dev); + + return ms_sensors_show_heater(dev_data, buf); +} + +static ssize_t htu21_write_heater(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ms_ht_dev *dev_data = iio_priv(indio_dev); + + return ms_sensors_write_heater(dev_data, buf, len); +} + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL(htu21_show_samp_freq); +static IIO_DEVICE_ATTR(battery_low, S_IRUGO, + htu21_show_battery_low, NULL, 0); +static IIO_DEVICE_ATTR(heater_enable, S_IRUGO | S_IWUSR, + htu21_show_heater, htu21_write_heater, 0); + +static struct attribute *htu21_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_battery_low.dev_attr.attr, + &iio_dev_attr_heater_enable.dev_attr.attr, + NULL, +}; + +static const struct attribute_group htu21_attribute_group = { + .attrs = htu21_attributes, +}; + +static const struct iio_info htu21_info = { + .read_raw = htu21_read_raw, + .write_raw = htu21_write_raw, + .attrs = &htu21_attribute_group, +}; + +static int htu21_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct ms_ht_dev *dev_data; + struct iio_dev *indio_dev; + int ret; + u64 serial_number; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_BYTE_DATA | + I2C_FUNC_SMBUS_WRITE_BYTE | + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { + dev_err(&client->dev, + "Adapter does not support some i2c transaction\n"); + return -EOPNOTSUPP; + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*dev_data)); + if (!indio_dev) + return -ENOMEM; + + dev_data = iio_priv(indio_dev); + dev_data->client = client; + dev_data->res_index = 0; + mutex_init(&dev_data->lock); + + indio_dev->info = &htu21_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (id->driver_data == MS8607) { + indio_dev->channels = ms8607_channels; + indio_dev->num_channels = ARRAY_SIZE(ms8607_channels); + } else { + indio_dev->channels = htu21_channels; + indio_dev->num_channels = ARRAY_SIZE(htu21_channels); + } + + i2c_set_clientdata(client, indio_dev); + + ret = ms_sensors_reset(client, HTU21_RESET, 15000); + if (ret) + return ret; + + ret = ms_sensors_read_serial(client, &serial_number); + if (ret) + return ret; + dev_info(&client->dev, "Serial number : %llx", serial_number); + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id htu21_id[] = { + { .name = "htu21", .driver_data = HTU21 }, + { .name = "ms8607-humidity", .driver_data = MS8607 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, htu21_id); + +static const struct of_device_id htu21_of_match[] = { + { .compatible = "meas,htu21", }, + { .compatible = "meas,ms8607-humidity", }, + { } +}; +MODULE_DEVICE_TABLE(of, htu21_of_match); + +static struct i2c_driver htu21_driver = { + .probe = htu21_probe, + .id_table = htu21_id, + .driver = { + .name = "htu21", + .of_match_table = htu21_of_match, + }, +}; + +module_i2c_driver(htu21_driver); + +MODULE_DESCRIPTION("Measurement-Specialties htu21 temperature and humidity driver"); +MODULE_AUTHOR("William Markezana "); +MODULE_AUTHOR("Ludovic Tancerel "); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_MEAS_SPEC_SENSORS"); diff --git a/drivers/iio/humidity/si7005.c b/drivers/iio/humidity/si7005.c new file mode 100644 index 000000000..0c11d0d3a --- /dev/null +++ b/drivers/iio/humidity/si7005.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * si7005.c - Support for Silabs Si7005 humidity and temperature sensor + * + * Copyright (c) 2014 Peter Meerwald + * + * (7-bit I2C slave address 0x40) + * + * TODO: heater, fast mode, processed mode (temp. / linearity compensation) + */ + +#include +#include +#include +#include +#include + +#include +#include + +#define SI7005_STATUS 0x00 +#define SI7005_DATA 0x01 /* 16-bit, MSB */ +#define SI7005_CONFIG 0x03 +#define SI7005_ID 0x11 + +#define SI7005_STATUS_NRDY BIT(0) +#define SI7005_CONFIG_TEMP BIT(4) +#define SI7005_CONFIG_START BIT(0) + +#define SI7005_ID_7005 0x50 +#define SI7005_ID_7015 0xf0 + +struct si7005_data { + struct i2c_client *client; + struct mutex lock; + u8 config; +}; + +static int si7005_read_measurement(struct si7005_data *data, bool temp) +{ + int tries = 50; + int ret; + + mutex_lock(&data->lock); + + ret = i2c_smbus_write_byte_data(data->client, SI7005_CONFIG, + data->config | SI7005_CONFIG_START | + (temp ? SI7005_CONFIG_TEMP : 0)); + if (ret < 0) + goto done; + + while (tries-- > 0) { + msleep(20); + ret = i2c_smbus_read_byte_data(data->client, SI7005_STATUS); + if (ret < 0) + goto done; + if (!(ret & SI7005_STATUS_NRDY)) + break; + } + if (tries < 0) { + ret = -EIO; + goto done; + } + + ret = i2c_smbus_read_word_swapped(data->client, SI7005_DATA); + +done: + mutex_unlock(&data->lock); + + return ret; +} + +static int si7005_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct si7005_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = si7005_read_measurement(data, chan->type == IIO_TEMP); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_TEMP) { + *val = 7; + *val2 = 812500; + } else { + *val = 3; + *val2 = 906250; + } + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OFFSET: + if (chan->type == IIO_TEMP) + *val = -50 * 32 * 4; + else + *val = -24 * 16 * 16; + return IIO_VAL_INT; + default: + break; + } + + return -EINVAL; +} + +static const struct iio_chan_spec si7005_channels[] = { + { + .type = IIO_HUMIDITYRELATIVE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET), + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET), + } +}; + +static const struct iio_info si7005_info = { + .read_raw = si7005_read_raw, +}; + +static int si7005_probe(struct i2c_client *client) +{ + struct iio_dev *indio_dev; + struct si7005_data *data; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -EOPNOTSUPP; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + mutex_init(&data->lock); + + indio_dev->name = dev_name(&client->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &si7005_info; + + indio_dev->channels = si7005_channels; + indio_dev->num_channels = ARRAY_SIZE(si7005_channels); + + ret = i2c_smbus_read_byte_data(client, SI7005_ID); + if (ret < 0) + return ret; + if (ret != SI7005_ID_7005 && ret != SI7005_ID_7015) + return -ENODEV; + + ret = i2c_smbus_read_byte_data(client, SI7005_CONFIG); + if (ret < 0) + return ret; + data->config = ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id si7005_id[] = { + { .name = "si7005" }, + { .name = "th02" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, si7005_id); + +static const struct of_device_id si7005_dt_ids[] = { + { .compatible = "silabs,si7005" }, + { } +}; +MODULE_DEVICE_TABLE(of, si7005_dt_ids); + +static struct i2c_driver si7005_driver = { + .driver = { + .name = "si7005", + .of_match_table = si7005_dt_ids, + }, + .probe = si7005_probe, + .id_table = si7005_id, +}; +module_i2c_driver(si7005_driver); + +MODULE_AUTHOR("Peter Meerwald "); +MODULE_DESCRIPTION("Silabs Si7005 humidity and temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/humidity/si7020.c b/drivers/iio/humidity/si7020.c new file mode 100644 index 000000000..e51dd7151 --- /dev/null +++ b/drivers/iio/humidity/si7020.c @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * si7020.c - Silicon Labs Si7013/20/21 Relative Humidity and Temp Sensors + * Copyright (c) 2013,2014 Uplogix, Inc. + * David Barksdale + */ + +/* + * The Silicon Labs Si7013/20/21 Relative Humidity and Temperature Sensors + * are i2c devices which have an identical programming interface for + * measuring relative humidity and temperature. The Si7013 has an additional + * temperature input which this driver does not support. + * + * Data Sheets: + * Si7013: http://www.silabs.com/Support%20Documents/TechnicalDocs/Si7013.pdf + * Si7020: http://www.silabs.com/Support%20Documents/TechnicalDocs/Si7020.pdf + * Si7021: http://www.silabs.com/Support%20Documents/TechnicalDocs/Si7021.pdf + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +/* Measure Relative Humidity, Hold Master Mode */ +#define SI7020CMD_RH_HOLD 0xE5 +/* Measure Temperature, Hold Master Mode */ +#define SI7020CMD_TEMP_HOLD 0xE3 +/* Software Reset */ +#define SI7020CMD_RESET 0xFE +#define SI7020CMD_USR_WRITE 0xE6 +/* "Heater Enabled" bit in the User Register */ +#define SI7020_USR_HEATER_EN BIT(2) +#define SI7020CMD_HEATER_WRITE 0x51 +/* Heater current configuration bits */ +#define SI7020_HEATER_VAL GENMASK(3, 0) + +struct si7020_data { + struct i2c_client *client; + /* Lock for cached register values */ + struct mutex lock; + u8 user_reg; + u8 heater_reg; +}; + +static const int si7020_heater_vals[] = { 0, 1, 0xF }; + +static int si7020_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct si7020_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_CURRENT) { + *val = data->heater_reg; + return IIO_VAL_INT; + } + + ret = i2c_smbus_read_word_swapped(data->client, + chan->type == IIO_TEMP ? + SI7020CMD_TEMP_HOLD : + SI7020CMD_RH_HOLD); + if (ret < 0) + return ret; + *val = ret >> 2; + /* + * Humidity values can slightly exceed the 0-100%RH + * range and should be corrected by software + */ + if (chan->type == IIO_HUMIDITYRELATIVE) + *val = clamp_val(*val, 786, 13893); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_TEMP) + *val = 175720; /* = 175.72 * 1000 */ + else + *val = 125 * 1000; + *val2 = 65536 >> 2; + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_OFFSET: + /* + * Since iio_convert_raw_to_processed_unlocked assumes offset + * is an integer we have to round these values and lose + * accuracy. + * Relative humidity will be 0.0032959% too high and + * temperature will be 0.00277344 degrees too high. + * This is no big deal because it's within the accuracy of the + * sensor. + */ + if (chan->type == IIO_TEMP) + *val = -4368; /* = -46.85 * (65536 >> 2) / 175.72 */ + else + *val = -786; /* = -6 * (65536 >> 2) / 125 */ + return IIO_VAL_INT; + default: + break; + } + + return -EINVAL; +} + +static const struct iio_chan_spec si7020_channels[] = { + { + .type = IIO_HUMIDITYRELATIVE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET), + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET), + }, + { + .type = IIO_CURRENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW), + .extend_name = "heater", + } +}; + +static int si7020_update_reg(struct si7020_data *data, + u8 *reg, u8 cmd, u8 mask, u8 val) +{ + u8 new = (*reg & ~mask) | val; + int ret; + + ret = i2c_smbus_write_byte_data(data->client, cmd, new); + if (ret) + return ret; + + *reg = new; + + return 0; +} + +static int si7020_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct si7020_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type != IIO_CURRENT || val2 != 0 || + val < si7020_heater_vals[0] || val > si7020_heater_vals[2]) + return -EINVAL; + + scoped_guard(mutex, &data->lock) + ret = si7020_update_reg(data, &data->heater_reg, + SI7020CMD_HEATER_WRITE, SI7020_HEATER_VAL, val); + return ret; + default: + return -EINVAL; + } +} + +static int si7020_read_available(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, + int *type, int *length, long mask) +{ + if (mask != IIO_CHAN_INFO_RAW || chan->type != IIO_CURRENT) + return -EINVAL; + + *vals = si7020_heater_vals; + *type = IIO_VAL_INT; + + return IIO_AVAIL_RANGE; +} + +static ssize_t si7020_show_heater_en(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct si7020_data *data = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", !!(data->user_reg & SI7020_USR_HEATER_EN)); +} + +static ssize_t si7020_store_heater_en(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct si7020_data *data = iio_priv(indio_dev); + int ret; + bool val; + + ret = kstrtobool(buf, &val); + if (ret) + return ret; + + scoped_guard(mutex, &data->lock) + ret = si7020_update_reg(data, &data->user_reg, SI7020CMD_USR_WRITE, + SI7020_USR_HEATER_EN, val ? SI7020_USR_HEATER_EN : 0); + + return ret < 0 ? ret : len; +} + +static IIO_DEVICE_ATTR(heater_enable, 0644, + si7020_show_heater_en, si7020_store_heater_en, 0); + +static struct attribute *si7020_attributes[] = { + &iio_dev_attr_heater_enable.dev_attr.attr, + NULL +}; + +static const struct attribute_group si7020_attribute_group = { + .attrs = si7020_attributes, +}; + +static const struct iio_info si7020_info = { + .read_raw = si7020_read_raw, + .write_raw = si7020_write_raw, + .read_avail = si7020_read_available, + .attrs = &si7020_attribute_group, +}; + +static int si7020_probe(struct i2c_client *client) +{ + struct iio_dev *indio_dev; + struct si7020_data *data; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_BYTE | + I2C_FUNC_SMBUS_READ_WORD_DATA)) + return -EOPNOTSUPP; + + /* Reset device, loads default settings. */ + ret = i2c_smbus_write_byte(client, SI7020CMD_RESET); + if (ret < 0) + return ret; + /* Wait the maximum power-up time after software reset. */ + msleep(15); + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + mutex_init(&data->lock); + + indio_dev->name = dev_name(&client->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &si7020_info; + indio_dev->channels = si7020_channels; + indio_dev->num_channels = ARRAY_SIZE(si7020_channels); + + /* All the "reserved" bits in the User Register are 1s by default */ + data->user_reg = 0x3A; + data->heater_reg = 0x0; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id si7020_id[] = { + { .name = "si7020" }, + { .name = "th06" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, si7020_id); + +static const struct of_device_id si7020_dt_ids[] = { + { .compatible = "silabs,si7020" }, + { } +}; +MODULE_DEVICE_TABLE(of, si7020_dt_ids); + +static struct i2c_driver si7020_driver = { + .driver = { + .name = "si7020", + .of_match_table = si7020_dt_ids, + }, + .probe = si7020_probe, + .id_table = si7020_id, +}; + +module_i2c_driver(si7020_driver); +MODULE_DESCRIPTION("Silicon Labs Si7013/20/21 Relative Humidity and Temperature Sensors"); +MODULE_AUTHOR("David Barksdale "); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/iio_core.h b/drivers/iio/iio_core.h new file mode 100644 index 000000000..b7d5f4f0f --- /dev/null +++ b/drivers/iio/iio_core.h @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* The industrial I/O core function defs. + * + * Copyright (c) 2008 Jonathan Cameron + * + * These definitions are meant for use only within the IIO core, not individual + * drivers. + */ + +#ifndef _IIO_CORE_H_ +#define _IIO_CORE_H_ +#include +#include + +struct iio_buffer; +struct iio_chan_spec; +struct iio_dev; + +extern const struct device_type iio_device_type; + +struct iio_dev_buffer_pair { + struct iio_dev *indio_dev; + struct iio_buffer *buffer; +}; + +#define IIO_IOCTL_UNHANDLED 1 +struct iio_ioctl_handler { + struct list_head entry; + long (*ioctl)(struct iio_dev *indio_dev, struct file *filp, + unsigned int cmd, unsigned long arg); +}; + +void iio_device_ioctl_handler_register(struct iio_dev *indio_dev, + struct iio_ioctl_handler *h); +void iio_device_ioctl_handler_unregister(struct iio_ioctl_handler *h); + +ssize_t do_iio_read_channel_label(struct iio_dev *indio_dev, + const struct iio_chan_spec *c, + char *buf); + +int __iio_add_chan_devattr(const char *postfix, + struct iio_chan_spec const *chan, + ssize_t (*func)(struct device *dev, + struct device_attribute *attr, + char *buf), + ssize_t (*writefunc)(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len), + u64 mask, + enum iio_shared_by shared_by, + struct device *dev, + struct iio_buffer *buffer, + struct list_head *attr_list); +void iio_free_chan_devattr_list(struct list_head *attr_list); + +int iio_device_register_sysfs_group(struct iio_dev *indio_dev, + const struct attribute_group *group); + +ssize_t iio_format_value(char *buf, unsigned int type, int size, int *vals); + +/* Event interface flags */ +#define IIO_BUSY_BIT_POS 1 + +#ifdef CONFIG_IIO_BUFFER +struct poll_table_struct; + +__poll_t iio_buffer_poll_wrapper(struct file *filp, + struct poll_table_struct *wait); +ssize_t iio_buffer_read_wrapper(struct file *filp, char __user *buf, + size_t n, loff_t *f_ps); +ssize_t iio_buffer_write_wrapper(struct file *filp, const char __user *buf, + size_t n, loff_t *f_ps); + +int iio_buffers_alloc_sysfs_and_mask(struct iio_dev *indio_dev); +void iio_buffers_free_sysfs_and_mask(struct iio_dev *indio_dev); + +#define iio_buffer_poll_addr (&iio_buffer_poll_wrapper) +#define iio_buffer_read_outer_addr (&iio_buffer_read_wrapper) +#define iio_buffer_write_outer_addr (&iio_buffer_write_wrapper) + +void iio_disable_all_buffers(struct iio_dev *indio_dev); +void iio_buffer_wakeup_poll(struct iio_dev *indio_dev); +void iio_device_detach_buffers(struct iio_dev *indio_dev); + +#else + +#define iio_buffer_poll_addr NULL +#define iio_buffer_read_outer_addr NULL +#define iio_buffer_write_outer_addr NULL + +static inline int iio_buffers_alloc_sysfs_and_mask(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline void iio_buffers_free_sysfs_and_mask(struct iio_dev *indio_dev) {} + +static inline void iio_disable_all_buffers(struct iio_dev *indio_dev) {} +static inline void iio_buffer_wakeup_poll(struct iio_dev *indio_dev) {} +static inline void iio_device_detach_buffers(struct iio_dev *indio_dev) {} + +#endif + +int iio_device_register_eventset(struct iio_dev *indio_dev); +void iio_device_unregister_eventset(struct iio_dev *indio_dev); +void iio_device_wakeup_eventset(struct iio_dev *indio_dev); + +struct iio_event_interface; +bool iio_event_enabled(const struct iio_event_interface *ev_int); + +#endif diff --git a/drivers/iio/iio_core_trigger.h b/drivers/iio/iio_core_trigger.h new file mode 100644 index 000000000..4698a8577 --- /dev/null +++ b/drivers/iio/iio_core_trigger.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +/* The industrial I/O core, trigger consumer handling functions + * + * Copyright (c) 2008 Jonathan Cameron + */ + +#ifdef CONFIG_IIO_TRIGGER +/** + * iio_device_register_trigger_consumer() - set up an iio_dev to use triggers + * @indio_dev: iio_dev associated with the device that will consume the trigger + * + * Return 0 if successful, negative otherwise + **/ +int iio_device_register_trigger_consumer(struct iio_dev *indio_dev); + +/** + * iio_device_unregister_trigger_consumer() - reverse the registration process + * @indio_dev: iio_dev associated with the device that consumed the trigger + **/ +void iio_device_unregister_trigger_consumer(struct iio_dev *indio_dev); + + +int iio_trigger_attach_poll_func(struct iio_trigger *trig, + struct iio_poll_func *pf); +int iio_trigger_detach_poll_func(struct iio_trigger *trig, + struct iio_poll_func *pf); + +#else + +/** + * iio_device_register_trigger_consumer() - set up an iio_dev to use triggers + * @indio_dev: iio_dev associated with the device that will consume the trigger + **/ +static inline int iio_device_register_trigger_consumer(struct iio_dev *indio_dev) +{ + return 0; +} + +/** + * iio_device_unregister_trigger_consumer() - reverse the registration process + * @indio_dev: iio_dev associated with the device that consumed the trigger + **/ +static inline void iio_device_unregister_trigger_consumer(struct iio_dev *indio_dev) +{ +} + +static inline int iio_trigger_attach_poll_func(struct iio_trigger *trig, + struct iio_poll_func *pf) +{ + return 0; +} +static inline int iio_trigger_detach_poll_func(struct iio_trigger *trig, + struct iio_poll_func *pf) +{ + return 0; +} + +#endif /* CONFIG_IIO_TRIGGER */ diff --git a/drivers/iio/imu/Kconfig b/drivers/iio/imu/Kconfig new file mode 100644 index 000000000..8bab4616b --- /dev/null +++ b/drivers/iio/imu/Kconfig @@ -0,0 +1,145 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# IIO imu drivers configuration +# +# When adding new entries keep the list in alphabetical order + +menu "Inertial measurement units" + +config ADIS16400 + tristate "Analog Devices ADIS16400 and similar IMU SPI driver" + depends on SPI + select IIO_ADIS_LIB + select IIO_ADIS_LIB_BUFFER if IIO_BUFFER + help + Say yes here to build support for Analog Devices adis16300, adis16344, + adis16350, adis16354, adis16355, adis16360, adis16362, adis16364, + adis16365, adis16400 and adis16405 triaxial inertial sensors + (adis16400 series also have magnetometers). + +config ADIS16460 + tristate "Analog Devices ADIS16460 and similar IMU driver" + depends on SPI + select IIO_ADIS_LIB + select IIO_ADIS_LIB_BUFFER if IIO_BUFFER + help + Say yes here to build support for Analog Devices ADIS16460 inertial + sensor. + + To compile this driver as a module, choose M here: the module will be + called adis16460. + +config ADIS16475 + tristate "Analog Devices ADIS16475 and similar IMU driver" + depends on SPI + select IIO_ADIS_LIB + select IIO_ADIS_LIB_BUFFER if IIO_BUFFER + help + Say yes here to build support for Analog Devices ADIS16470, ADIS16475, + ADIS16477, ADIS16465, ADIS16467, ADIS16500, ADIS16501, ADIS16505, + ADIS16507 inertial sensors. + + To compile this driver as a module, choose M here: the module will be + called adis16475. + +config ADIS16480 + tristate "Analog Devices ADIS16480 and similar IMU driver" + depends on SPI + select IIO_ADIS_LIB + select IIO_ADIS_LIB_BUFFER if IIO_BUFFER + select CRC32 + help + Say yes here to build support for Analog Devices ADIS16375, ADIS16480, + ADIS16485, ADIS16488 inertial sensors. + +config ADIS16550 + tristate "Analog Devices ADIS16550 and similar IMU driver" + depends on SPI + select IIO_ADIS_LIB + select IIO_ADIS_LIB_BUFFER if IIO_BUFFER + select CRC32 + help + Say yes here to build support for Analog Devices ADIS16550 inertial + sensor containing triaxis gyroscope and triaxis accelerometer. + + To compile this driver as a module, choose M here: the module will be + called adis16550. + +source "drivers/iio/imu/bmi160/Kconfig" +source "drivers/iio/imu/bmi270/Kconfig" +source "drivers/iio/imu/bmi323/Kconfig" +source "drivers/iio/imu/bno055/Kconfig" + +config FXOS8700 + tristate + +config FXOS8700_I2C + tristate "NXP FXOS8700 I2C driver" + depends on I2C + select FXOS8700 + select REGMAP_I2C + help + Say yes here to build support for the NXP FXOS8700 m+g combo + sensor on I2C. + + This driver can also be built as a module. If so, the module will be + called fxos8700_i2c. + +config FXOS8700_SPI + tristate "NXP FXOS8700 SPI driver" + depends on SPI + select FXOS8700 + select REGMAP_SPI + help + Say yes here to build support for the NXP FXOS8700 m+g combo + sensor on SPI. + + This driver can also be built as a module. If so, the module will be + called fxos8700_spi. + +config KMX61 + tristate "Kionix KMX61 6-axis accelerometer and magnetometer" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here if you want to build a driver for Kionix KMX61 6-axis + accelerometer and magnetometer. + To compile this driver as module, choose M here: the module will + be called kmx61. + +source "drivers/iio/imu/inv_icm42600/Kconfig" +source "drivers/iio/imu/inv_icm42607/Kconfig" +source "drivers/iio/imu/inv_icm45600/Kconfig" +source "drivers/iio/imu/inv_mpu6050/Kconfig" + +config SMI240 + tristate "Bosch Sensor SMI240 Inertial Measurement Unit" + depends on SPI + select REGMAP_SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for SMI240 IMU on SPI with + accelerometer and gyroscope. + + This driver can also be built as a module. If so, the module will be + called smi240. + +source "drivers/iio/imu/smi330/Kconfig" +source "drivers/iio/imu/st_lsm6dsx/Kconfig" +source "drivers/iio/imu/st_lsm9ds0/Kconfig" + +endmenu + +config IIO_ADIS_LIB + tristate + help + A set of IO helper functions for the Analog Devices ADIS* device family. + +config IIO_ADIS_LIB_BUFFER + bool + select IIO_TRIGGERED_BUFFER + help + A set of buffer helper functions for the Analog Devices ADIS* device + family. diff --git a/drivers/iio/imu/Makefile b/drivers/iio/imu/Makefile new file mode 100644 index 000000000..3268dc237 --- /dev/null +++ b/drivers/iio/imu/Makefile @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for Inertial Measurement Units +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_ADIS16400) += adis16400.o +obj-$(CONFIG_ADIS16460) += adis16460.o +obj-$(CONFIG_ADIS16475) += adis16475.o +obj-$(CONFIG_ADIS16480) += adis16480.o +obj-$(CONFIG_ADIS16550) += adis16550.o + +adis_lib-y += adis.o +adis_lib-$(CONFIG_IIO_ADIS_LIB_BUFFER) += adis_trigger.o +adis_lib-$(CONFIG_IIO_ADIS_LIB_BUFFER) += adis_buffer.o +obj-$(CONFIG_IIO_ADIS_LIB) += adis_lib.o + +obj-y += bmi160/ +obj-y += bmi270/ +obj-y += bmi323/ +obj-y += bno055/ + +obj-$(CONFIG_FXOS8700) += fxos8700_core.o +obj-$(CONFIG_FXOS8700_I2C) += fxos8700_i2c.o +obj-$(CONFIG_FXOS8700_SPI) += fxos8700_spi.o + +obj-y += inv_icm42600/ +obj-y += inv_icm42607/ +obj-y += inv_icm45600/ +obj-y += inv_mpu6050/ + +obj-$(CONFIG_KMX61) += kmx61.o + +obj-$(CONFIG_SMI240) += smi240.o + +obj-y += smi330/ +obj-y += st_lsm6dsx/ +obj-y += st_lsm9ds0/ diff --git a/drivers/iio/imu/adis.c b/drivers/iio/imu/adis.c new file mode 100644 index 000000000..a2bc1d14e --- /dev/null +++ b/drivers/iio/imu/adis.c @@ -0,0 +1,550 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Common library for ADIS16XXX devices + * + * Copyright 2012 Analog Devices Inc. + * Author: Lars-Peter Clausen + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define ADIS_MSC_CTRL_DATA_RDY_EN BIT(2) +#define ADIS_MSC_CTRL_DATA_RDY_POL_HIGH BIT(1) +#define ADIS_MSC_CTRL_DATA_RDY_DIO2 BIT(0) +#define ADIS_GLOB_CMD_SW_RESET BIT(7) + +/** + * __adis_write_reg() - write N bytes to register (unlocked version) + * @adis: The adis device + * @reg: The address of the lower of the two registers + * @value: The value to write to device (up to 4 bytes) + * @size: The size of the @value (in bytes) + */ +int __adis_write_reg(struct adis *adis, unsigned int reg, unsigned int value, + unsigned int size) +{ + unsigned int page = reg / ADIS_PAGE_SIZE; + int ret, i; + struct spi_message msg; + struct spi_transfer xfers[] = { + { + .tx_buf = adis->tx, + .len = 2, + .cs_change = 1, + .delay.value = adis->data->write_delay, + .delay.unit = SPI_DELAY_UNIT_USECS, + }, { + .tx_buf = adis->tx + 2, + .len = 2, + .cs_change = 1, + .delay.value = adis->data->write_delay, + .delay.unit = SPI_DELAY_UNIT_USECS, + }, { + .tx_buf = adis->tx + 4, + .len = 2, + .cs_change = 1, + .delay.value = adis->data->write_delay, + .delay.unit = SPI_DELAY_UNIT_USECS, + }, { + .tx_buf = adis->tx + 6, + .len = 2, + .delay.value = adis->data->write_delay, + .delay.unit = SPI_DELAY_UNIT_USECS, + }, { + .tx_buf = adis->tx + 8, + .len = 2, + .delay.value = adis->data->write_delay, + .delay.unit = SPI_DELAY_UNIT_USECS, + }, + }; + + spi_message_init(&msg); + + if (adis->current_page != page) { + adis->tx[0] = ADIS_WRITE_REG(ADIS_REG_PAGE_ID); + adis->tx[1] = page; + spi_message_add_tail(&xfers[0], &msg); + } + + switch (size) { + case 4: + adis->tx[8] = ADIS_WRITE_REG(reg + 3); + adis->tx[9] = (value >> 24) & 0xff; + adis->tx[6] = ADIS_WRITE_REG(reg + 2); + adis->tx[7] = (value >> 16) & 0xff; + fallthrough; + case 2: + adis->tx[4] = ADIS_WRITE_REG(reg + 1); + adis->tx[5] = (value >> 8) & 0xff; + fallthrough; + case 1: + adis->tx[2] = ADIS_WRITE_REG(reg); + adis->tx[3] = value & 0xff; + break; + default: + return -EINVAL; + } + + xfers[size].cs_change = 0; + + for (i = 1; i <= size; i++) + spi_message_add_tail(&xfers[i], &msg); + + ret = spi_sync(adis->spi, &msg); + if (ret) { + dev_err(&adis->spi->dev, "Failed to write register 0x%02X: %d\n", + reg, ret); + } else { + adis->current_page = page; + } + + return ret; +} +EXPORT_SYMBOL_NS_GPL(__adis_write_reg, "IIO_ADISLIB"); + +/** + * __adis_read_reg() - read N bytes from register (unlocked version) + * @adis: The adis device + * @reg: The address of the lower of the two registers + * @val: The value read back from the device + * @size: The size of the @val buffer + */ +int __adis_read_reg(struct adis *adis, unsigned int reg, unsigned int *val, + unsigned int size) +{ + unsigned int page = reg / ADIS_PAGE_SIZE; + struct spi_message msg; + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = adis->tx, + .len = 2, + .cs_change = 1, + .delay.value = adis->data->write_delay, + .delay.unit = SPI_DELAY_UNIT_USECS, + }, { + .tx_buf = adis->tx + 2, + .len = 2, + .cs_change = 1, + .delay.value = adis->data->read_delay, + .delay.unit = SPI_DELAY_UNIT_USECS, + }, { + .tx_buf = adis->tx + 4, + .rx_buf = adis->rx, + .len = 2, + .cs_change = 1, + .delay.value = adis->data->read_delay, + .delay.unit = SPI_DELAY_UNIT_USECS, + }, { + .rx_buf = adis->rx + 2, + .len = 2, + .delay.value = adis->data->read_delay, + .delay.unit = SPI_DELAY_UNIT_USECS, + }, + }; + + spi_message_init(&msg); + + if (adis->current_page != page) { + adis->tx[0] = ADIS_WRITE_REG(ADIS_REG_PAGE_ID); + adis->tx[1] = page; + spi_message_add_tail(&xfers[0], &msg); + } + + switch (size) { + case 4: + adis->tx[2] = ADIS_READ_REG(reg + 2); + adis->tx[3] = 0; + spi_message_add_tail(&xfers[1], &msg); + fallthrough; + case 2: + adis->tx[4] = ADIS_READ_REG(reg); + adis->tx[5] = 0; + spi_message_add_tail(&xfers[2], &msg); + spi_message_add_tail(&xfers[3], &msg); + break; + default: + return -EINVAL; + } + + ret = spi_sync(adis->spi, &msg); + if (ret) { + dev_err(&adis->spi->dev, "Failed to read register 0x%02X: %d\n", + reg, ret); + return ret; + } + + adis->current_page = page; + + switch (size) { + case 4: + *val = get_unaligned_be32(adis->rx); + break; + case 2: + *val = get_unaligned_be16(adis->rx + 2); + break; + } + + return ret; +} +EXPORT_SYMBOL_NS_GPL(__adis_read_reg, "IIO_ADISLIB"); +/** + * __adis_update_bits_base() - ADIS Update bits function - Unlocked version + * @adis: The adis device + * @reg: The address of the lower of the two registers + * @mask: Bitmask to change + * @val: Value to be written + * @size: Size of the register to update + * + * Updates the desired bits of @reg in accordance with @mask and @val. + */ +int __adis_update_bits_base(struct adis *adis, unsigned int reg, const u32 mask, + const u32 val, u8 size) +{ + int ret; + u32 __val; + + ret = adis->ops->read(adis, reg, &__val, size); + if (ret) + return ret; + + __val = (__val & ~mask) | (val & mask); + + return adis->ops->write(adis, reg, __val, size); +} +EXPORT_SYMBOL_NS_GPL(__adis_update_bits_base, "IIO_ADISLIB"); + +#ifdef CONFIG_DEBUG_FS + +int adis_debugfs_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct adis *adis = iio_device_get_drvdata(indio_dev); + + if (readval) { + u16 val16; + int ret; + + ret = adis_read_reg_16(adis, reg, &val16); + if (ret == 0) + *readval = val16; + + return ret; + } + + return adis_write_reg_16(adis, reg, writeval); +} +EXPORT_SYMBOL_NS(adis_debugfs_reg_access, "IIO_ADISLIB"); + +#endif + +/** + * __adis_enable_irq() - Enable or disable data ready IRQ (unlocked) + * @adis: The adis device + * @enable: Whether to enable the IRQ + * + * Returns 0 on success, negative error code otherwise + */ +int __adis_enable_irq(struct adis *adis, bool enable) +{ + int ret; + u16 msc; + + if (adis->data->enable_irq) + return adis->data->enable_irq(adis, enable); + + if (adis->data->unmasked_drdy) { + if (enable) + enable_irq(adis->spi->irq); + else + disable_irq(adis->spi->irq); + + return 0; + } + + ret = __adis_read_reg_16(adis, adis->data->msc_ctrl_reg, &msc); + if (ret) + return ret; + + msc |= ADIS_MSC_CTRL_DATA_RDY_POL_HIGH; + msc &= ~ADIS_MSC_CTRL_DATA_RDY_DIO2; + if (enable) + msc |= ADIS_MSC_CTRL_DATA_RDY_EN; + else + msc &= ~ADIS_MSC_CTRL_DATA_RDY_EN; + + return __adis_write_reg_16(adis, adis->data->msc_ctrl_reg, msc); +} +EXPORT_SYMBOL_NS(__adis_enable_irq, "IIO_ADISLIB"); + +/** + * __adis_check_status() - Check the device for error conditions (unlocked) + * @adis: The adis device + * + * Returns 0 on success, a negative error code otherwise + */ +int __adis_check_status(struct adis *adis) +{ + unsigned int status; + int diag_stat_bits; + u16 status_16 = 0; + int ret; + int i; + + if (adis->data->diag_stat_size) { + ret = adis->ops->read(adis, adis->data->diag_stat_reg, &status, + adis->data->diag_stat_size); + } else { + ret = __adis_read_reg_16(adis, adis->data->diag_stat_reg, + &status_16); + status = status_16; + } + if (ret) + return ret; + + status &= adis->data->status_error_mask; + + if (status == 0) + return 0; + + diag_stat_bits = BITS_PER_BYTE * (adis->data->diag_stat_size ? + adis->data->diag_stat_size : 2); + + for (i = 0; i < diag_stat_bits; ++i) { + if (status & BIT(i)) { + dev_err(&adis->spi->dev, "%s.\n", + adis->data->status_error_msgs[i]); + } + } + + return -EIO; +} +EXPORT_SYMBOL_NS_GPL(__adis_check_status, "IIO_ADISLIB"); + +/** + * __adis_reset() - Reset the device (unlocked version) + * @adis: The adis device + * + * Returns 0 on success, a negative error code otherwise + */ +int __adis_reset(struct adis *adis) +{ + int ret; + const struct adis_timeout *timeouts = adis->data->timeouts; + + ret = __adis_write_reg_8(adis, adis->data->glob_cmd_reg, + ADIS_GLOB_CMD_SW_RESET); + if (ret) { + dev_err(&adis->spi->dev, "Failed to reset device: %d\n", ret); + return ret; + } + + msleep(timeouts->sw_reset_ms); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(__adis_reset, "IIO_ADIS_LIB"); + +static int adis_self_test(struct adis *adis) +{ + int ret; + const struct adis_timeout *timeouts = adis->data->timeouts; + + ret = __adis_write_reg_16(adis, adis->data->self_test_reg, + adis->data->self_test_mask); + if (ret) { + dev_err(&adis->spi->dev, "Failed to initiate self test: %d\n", + ret); + return ret; + } + + msleep(timeouts->self_test_ms); + + ret = __adis_check_status(adis); + + if (adis->data->self_test_no_autoclear) + __adis_write_reg_16(adis, adis->data->self_test_reg, 0x00); + + return ret; +} + +/** + * __adis_initial_startup() - Device initial setup + * @adis: The adis device + * + * The function performs a HW reset via a reset pin that should be specified + * via GPIOLIB. If no pin is configured a SW reset will be performed. + * The RST pin for the ADIS devices should be configured as ACTIVE_LOW. + * + * After the self-test operation is performed, the function will also check + * that the product ID is as expected. This assumes that drivers providing + * 'prod_id_reg' will also provide the 'prod_id'. + * + * Returns 0 if the device is operational, a negative error code otherwise. + * + * This function should be called early on in the device initialization sequence + * to ensure that the device is in a sane and known state and that it is usable. + */ +int __adis_initial_startup(struct adis *adis) +{ + const struct adis_timeout *timeouts = adis->data->timeouts; + struct gpio_desc *gpio; + u16 prod_id; + int ret; + + /* check if the device has rst pin low */ + gpio = devm_gpiod_get_optional(&adis->spi->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(gpio)) + return PTR_ERR(gpio); + + if (gpio) { + usleep_range(10, 12); + /* bring device out of reset */ + gpiod_set_value_cansleep(gpio, 0); + msleep(timeouts->reset_ms); + } else { + ret = __adis_reset(adis); + if (ret) + return ret; + } + + ret = adis_self_test(adis); + if (ret) + return ret; + + /* + * don't bother calling this if we can't unmask the IRQ as in this case + * the IRQ is most likely not yet requested and we will request it + * with 'IRQF_NO_AUTOEN' anyways. + */ + if (!adis->data->unmasked_drdy) + __adis_enable_irq(adis, false); + + if (!adis->data->prod_id_reg) + return 0; + + ret = adis_read_reg_16(adis, adis->data->prod_id_reg, &prod_id); + if (ret) + return ret; + + if (prod_id != adis->data->prod_id) + dev_warn(&adis->spi->dev, + "Device ID(%u) and product ID(%u) do not match.\n", + adis->data->prod_id, prod_id); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(__adis_initial_startup, "IIO_ADISLIB"); + +/** + * adis_single_conversion() - Performs a single sample conversion + * @indio_dev: The IIO device + * @chan: The IIO channel + * @error_mask: Mask for the error bit + * @val: Result of the conversion + * + * Returns IIO_VAL_INT on success, a negative error code otherwise. + * + * The function performs a single conversion on a given channel and post + * processes the value accordingly to the channel spec. If a error_mask is given + * the function will check if the mask is set in the returned raw value. If it + * is set the function will perform a self-check. If the device does not report + * a error bit in the channels raw value set error_mask to 0. + */ +int adis_single_conversion(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int error_mask, int *val) +{ + struct adis *adis = iio_device_get_drvdata(indio_dev); + unsigned int uval; + int ret; + + guard(mutex)(&adis->state_lock); + + ret = adis->ops->read(adis, chan->address, &uval, + chan->scan_type.storagebits / 8); + if (ret) + return ret; + + if (uval & error_mask) { + ret = __adis_check_status(adis); + if (ret) + return ret; + } + + if (chan->scan_type.sign == 's') + *val = sign_extend32(uval, chan->scan_type.realbits - 1); + else + *val = uval & ((1 << chan->scan_type.realbits) - 1); + + return IIO_VAL_INT; +} +EXPORT_SYMBOL_NS_GPL(adis_single_conversion, "IIO_ADISLIB"); + +static const struct adis_ops adis_default_ops = { + .read = __adis_read_reg, + .write = __adis_write_reg, + .reset = __adis_reset, +}; + +/** + * adis_init() - Initialize adis device structure + * @adis: The adis device + * @indio_dev: The iio device + * @spi: The spi device + * @data: Chip specific data + * + * Returns 0 on success, a negative error code otherwise. + * + * This function must be called, before any other adis helper function may be + * called. + */ +int adis_init(struct adis *adis, struct iio_dev *indio_dev, + struct spi_device *spi, const struct adis_data *data) +{ + if (!data || !data->timeouts) { + dev_err(&spi->dev, "No config data or timeouts not defined!\n"); + return -EINVAL; + } + + mutex_init(&adis->state_lock); + + if (!spi->cs_inactive.value) { + spi->cs_inactive.value = data->cs_change_delay; + spi->cs_inactive.unit = SPI_DELAY_UNIT_USECS; + } + + adis->spi = spi; + adis->data = data; + if (!adis->ops) + adis->ops = &adis_default_ops; + else if (!adis->ops->write || !adis->ops->read || !adis->ops->reset) + return -EINVAL; + + iio_device_set_drvdata(indio_dev, adis); + + if (data->has_paging) { + /* Need to set the page before first read/write */ + adis->current_page = -1; + } else { + /* Page will always be 0 */ + adis->current_page = 0; + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(adis_init, "IIO_ADISLIB"); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Common library code for ADIS16XXX devices"); diff --git a/drivers/iio/imu/adis16400.c b/drivers/iio/imu/adis16400.c new file mode 100644 index 000000000..4842346f9 --- /dev/null +++ b/drivers/iio/imu/adis16400.c @@ -0,0 +1,1223 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * adis16400.c support Analog Devices ADIS16400/5 + * 3d 2g Linear Accelerometers, + * 3d Gyroscopes, + * 3d Magnetometers via SPI + * + * Copyright (c) 2009 Manuel Stahl + * Copyright (c) 2007 Jonathan Cameron + * Copyright (c) 2011 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define ADIS16400_STARTUP_DELAY 290 /* ms */ +#define ADIS16400_MTEST_DELAY 90 /* ms */ + +#define ADIS16400_FLASH_CNT 0x00 /* Flash memory write count */ +#define ADIS16400_SUPPLY_OUT 0x02 /* Power supply measurement */ +#define ADIS16400_XGYRO_OUT 0x04 /* X-axis gyroscope output */ +#define ADIS16400_YGYRO_OUT 0x06 /* Y-axis gyroscope output */ +#define ADIS16400_ZGYRO_OUT 0x08 /* Z-axis gyroscope output */ +#define ADIS16400_XACCL_OUT 0x0A /* X-axis accelerometer output */ +#define ADIS16400_YACCL_OUT 0x0C /* Y-axis accelerometer output */ +#define ADIS16400_ZACCL_OUT 0x0E /* Z-axis accelerometer output */ +#define ADIS16400_XMAGN_OUT 0x10 /* X-axis magnetometer measurement */ +#define ADIS16400_YMAGN_OUT 0x12 /* Y-axis magnetometer measurement */ +#define ADIS16400_ZMAGN_OUT 0x14 /* Z-axis magnetometer measurement */ +#define ADIS16400_TEMP_OUT 0x16 /* Temperature output */ +#define ADIS16400_AUX_ADC 0x18 /* Auxiliary ADC measurement */ + +#define ADIS16350_XTEMP_OUT 0x10 /* X-axis gyroscope temperature measurement */ +#define ADIS16350_YTEMP_OUT 0x12 /* Y-axis gyroscope temperature measurement */ +#define ADIS16350_ZTEMP_OUT 0x14 /* Z-axis gyroscope temperature measurement */ + +#define ADIS16300_PITCH_OUT 0x12 /* X axis inclinometer output measurement */ +#define ADIS16300_ROLL_OUT 0x14 /* Y axis inclinometer output measurement */ +#define ADIS16300_AUX_ADC 0x16 /* Auxiliary ADC measurement */ + +#define ADIS16448_BARO_OUT 0x16 /* Barometric pressure output */ +#define ADIS16448_TEMP_OUT 0x18 /* Temperature output */ + +/* Calibration parameters */ +#define ADIS16400_XGYRO_OFF 0x1A /* X-axis gyroscope bias offset factor */ +#define ADIS16400_YGYRO_OFF 0x1C /* Y-axis gyroscope bias offset factor */ +#define ADIS16400_ZGYRO_OFF 0x1E /* Z-axis gyroscope bias offset factor */ +#define ADIS16400_XACCL_OFF 0x20 /* X-axis acceleration bias offset factor */ +#define ADIS16400_YACCL_OFF 0x22 /* Y-axis acceleration bias offset factor */ +#define ADIS16400_ZACCL_OFF 0x24 /* Z-axis acceleration bias offset factor */ +#define ADIS16400_XMAGN_HIF 0x26 /* X-axis magnetometer, hard-iron factor */ +#define ADIS16400_YMAGN_HIF 0x28 /* Y-axis magnetometer, hard-iron factor */ +#define ADIS16400_ZMAGN_HIF 0x2A /* Z-axis magnetometer, hard-iron factor */ +#define ADIS16400_XMAGN_SIF 0x2C /* X-axis magnetometer, soft-iron factor */ +#define ADIS16400_YMAGN_SIF 0x2E /* Y-axis magnetometer, soft-iron factor */ +#define ADIS16400_ZMAGN_SIF 0x30 /* Z-axis magnetometer, soft-iron factor */ + +#define ADIS16400_GPIO_CTRL 0x32 /* Auxiliary digital input/output control */ +#define ADIS16400_MSC_CTRL 0x34 /* Miscellaneous control */ +#define ADIS16400_SMPL_PRD 0x36 /* Internal sample period (rate) control */ +#define ADIS16400_SENS_AVG 0x38 /* Dynamic range and digital filter control */ +#define ADIS16400_SLP_CNT 0x3A /* Sleep mode control */ +#define ADIS16400_DIAG_STAT 0x3C /* System status */ + +/* Alarm functions */ +#define ADIS16400_GLOB_CMD 0x3E /* System command */ +#define ADIS16400_ALM_MAG1 0x40 /* Alarm 1 amplitude threshold */ +#define ADIS16400_ALM_MAG2 0x42 /* Alarm 2 amplitude threshold */ +#define ADIS16400_ALM_SMPL1 0x44 /* Alarm 1 sample size */ +#define ADIS16400_ALM_SMPL2 0x46 /* Alarm 2 sample size */ +#define ADIS16400_ALM_CTRL 0x48 /* Alarm control */ +#define ADIS16400_AUX_DAC 0x4A /* Auxiliary DAC data */ + +#define ADIS16334_LOT_ID1 0x52 /* Lot identification code 1 */ +#define ADIS16334_LOT_ID2 0x54 /* Lot identification code 2 */ +#define ADIS16400_PRODUCT_ID 0x56 /* Product identifier */ +#define ADIS16334_SERIAL_NUMBER 0x58 /* Serial number, lot specific */ + +#define ADIS16400_ERROR_ACTIVE (1<<14) +#define ADIS16400_NEW_DATA (1<<14) + +/* MSC_CTRL */ +#define ADIS16400_MSC_CTRL_MEM_TEST (1<<11) +#define ADIS16400_MSC_CTRL_INT_SELF_TEST (1<<10) +#define ADIS16400_MSC_CTRL_NEG_SELF_TEST (1<<9) +#define ADIS16400_MSC_CTRL_POS_SELF_TEST (1<<8) +#define ADIS16400_MSC_CTRL_GYRO_BIAS (1<<7) +#define ADIS16400_MSC_CTRL_ACCL_ALIGN (1<<6) +#define ADIS16400_MSC_CTRL_DATA_RDY_EN (1<<2) +#define ADIS16400_MSC_CTRL_DATA_RDY_POL_HIGH (1<<1) +#define ADIS16400_MSC_CTRL_DATA_RDY_DIO2 (1<<0) + +/* SMPL_PRD */ +#define ADIS16400_SMPL_PRD_TIME_BASE (1<<7) +#define ADIS16400_SMPL_PRD_DIV_MASK 0x7F + +/* DIAG_STAT */ +#define ADIS16400_DIAG_STAT_ZACCL_FAIL 15 +#define ADIS16400_DIAG_STAT_YACCL_FAIL 14 +#define ADIS16400_DIAG_STAT_XACCL_FAIL 13 +#define ADIS16400_DIAG_STAT_XGYRO_FAIL 12 +#define ADIS16400_DIAG_STAT_YGYRO_FAIL 11 +#define ADIS16400_DIAG_STAT_ZGYRO_FAIL 10 +#define ADIS16400_DIAG_STAT_ALARM2 9 +#define ADIS16400_DIAG_STAT_ALARM1 8 +#define ADIS16400_DIAG_STAT_FLASH_CHK 6 +#define ADIS16400_DIAG_STAT_SELF_TEST 5 +#define ADIS16400_DIAG_STAT_OVERFLOW 4 +#define ADIS16400_DIAG_STAT_SPI_FAIL 3 +#define ADIS16400_DIAG_STAT_FLASH_UPT 2 +#define ADIS16400_DIAG_STAT_POWER_HIGH 1 +#define ADIS16400_DIAG_STAT_POWER_LOW 0 + +/* GLOB_CMD */ +#define ADIS16400_GLOB_CMD_SW_RESET (1<<7) +#define ADIS16400_GLOB_CMD_P_AUTO_NULL (1<<4) +#define ADIS16400_GLOB_CMD_FLASH_UPD (1<<3) +#define ADIS16400_GLOB_CMD_DAC_LATCH (1<<2) +#define ADIS16400_GLOB_CMD_FAC_CALIB (1<<1) +#define ADIS16400_GLOB_CMD_AUTO_NULL (1<<0) + +/* SLP_CNT */ +#define ADIS16400_SLP_CNT_POWER_OFF (1<<8) + +#define ADIS16334_RATE_DIV_SHIFT 8 +#define ADIS16334_RATE_INT_CLK BIT(0) + +#define ADIS16400_SPI_SLOW (u32)(300 * 1000) +#define ADIS16400_SPI_BURST (u32)(1000 * 1000) +#define ADIS16400_SPI_FAST (u32)(2000 * 1000) + +#define ADIS16400_HAS_PROD_ID BIT(0) +#define ADIS16400_NO_BURST BIT(1) +#define ADIS16400_HAS_SLOW_MODE BIT(2) +#define ADIS16400_HAS_SERIAL_NUMBER BIT(3) +#define ADIS16400_BURST_DIAG_STAT BIT(4) + +struct adis16400_state; + +struct adis16400_chip_info { + const struct iio_chan_spec *channels; + const struct adis_data adis_data; + const int num_channels; + const long flags; + unsigned int gyro_scale_micro; + unsigned int accel_scale_micro; + int temp_scale_nano; + int temp_offset; + /* set_freq() & get_freq() need to avoid using ADIS lib's state lock */ + int (*set_freq)(struct adis16400_state *st, unsigned int freq); + int (*get_freq)(struct adis16400_state *st); +}; + +/** + * struct adis16400_state - device instance specific data + * @variant: chip variant info + * @filt_int: integer part of requested filter frequency + * @adis: adis device + * @avail_scan_mask: NULL terminated array of bitmaps of channels + * that must be enabled together + **/ +struct adis16400_state { + const struct adis16400_chip_info *variant; + int filt_int; + + struct adis adis; + unsigned long avail_scan_mask[2]; +}; + +/* At the moment triggers are only used for ring buffer + * filling. This may change! + */ + +enum { + ADIS16400_SCAN_SUPPLY, + ADIS16400_SCAN_GYRO_X, + ADIS16400_SCAN_GYRO_Y, + ADIS16400_SCAN_GYRO_Z, + ADIS16400_SCAN_ACC_X, + ADIS16400_SCAN_ACC_Y, + ADIS16400_SCAN_ACC_Z, + ADIS16400_SCAN_MAGN_X, + ADIS16400_SCAN_MAGN_Y, + ADIS16400_SCAN_MAGN_Z, + ADIS16400_SCAN_BARO, + ADIS16350_SCAN_TEMP_X, + ADIS16350_SCAN_TEMP_Y, + ADIS16350_SCAN_TEMP_Z, + ADIS16300_SCAN_INCLI_X, + ADIS16300_SCAN_INCLI_Y, + ADIS16400_SCAN_ADC, + ADIS16400_SCAN_TIMESTAMP, +}; + +static ssize_t adis16400_show_serial_number(struct file *file, + char __user *userbuf, size_t count, loff_t *ppos) +{ + struct adis16400_state *st = file->private_data; + u16 lot1, lot2, serial_number; + char buf[16]; + size_t len; + int ret; + + ret = adis_read_reg_16(&st->adis, ADIS16334_LOT_ID1, &lot1); + if (ret) + return ret; + + ret = adis_read_reg_16(&st->adis, ADIS16334_LOT_ID2, &lot2); + if (ret) + return ret; + + ret = adis_read_reg_16(&st->adis, ADIS16334_SERIAL_NUMBER, + &serial_number); + if (ret) + return ret; + + len = snprintf(buf, sizeof(buf), "%.4x-%.4x-%.4x\n", lot1, lot2, + serial_number); + + return simple_read_from_buffer(userbuf, count, ppos, buf, len); +} + +static const struct file_operations adis16400_serial_number_fops = { + .open = simple_open, + .read = adis16400_show_serial_number, + .llseek = default_llseek, + .owner = THIS_MODULE, +}; + +static int adis16400_show_product_id(void *arg, u64 *val) +{ + struct adis16400_state *st = arg; + uint16_t prod_id; + int ret; + + ret = adis_read_reg_16(&st->adis, ADIS16400_PRODUCT_ID, &prod_id); + if (ret) + return ret; + + *val = prod_id; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(adis16400_product_id_fops, + adis16400_show_product_id, NULL, "%lld\n"); + +static int adis16400_show_flash_count(void *arg, u64 *val) +{ + struct adis16400_state *st = arg; + uint16_t flash_count; + int ret; + + ret = adis_read_reg_16(&st->adis, ADIS16400_FLASH_CNT, &flash_count); + if (ret) + return ret; + + *val = flash_count; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(adis16400_flash_count_fops, + adis16400_show_flash_count, NULL, "%lld\n"); + +static void adis16400_debugfs_init(struct iio_dev *indio_dev) +{ + struct adis16400_state *st = iio_priv(indio_dev); + struct dentry *d = iio_get_debugfs_dentry(indio_dev); + + if (!IS_ENABLED(CONFIG_DEBUG_FS)) + return; + + if (st->variant->flags & ADIS16400_HAS_SERIAL_NUMBER) + debugfs_create_file_unsafe("serial_number", 0400, + d, st, &adis16400_serial_number_fops); + if (st->variant->flags & ADIS16400_HAS_PROD_ID) + debugfs_create_file_unsafe("product_id", 0400, + d, st, &adis16400_product_id_fops); + debugfs_create_file_unsafe("flash_count", 0400, + d, st, &adis16400_flash_count_fops); +} + +static int adis16334_get_freq(struct adis16400_state *st) +{ + int ret; + uint16_t t; + + ret = __adis_read_reg_16(&st->adis, ADIS16400_SMPL_PRD, &t); + if (ret) + return ret; + + t >>= ADIS16334_RATE_DIV_SHIFT; + + return 819200 >> t; +} + +static int adis16334_set_freq(struct adis16400_state *st, unsigned int freq) +{ + unsigned int t; + + if (freq < 819200) + t = ilog2(819200 / freq); + else + t = 0; + + if (t > 0x31) + t = 0x31; + + t <<= ADIS16334_RATE_DIV_SHIFT; + t |= ADIS16334_RATE_INT_CLK; + + return __adis_write_reg_16(&st->adis, ADIS16400_SMPL_PRD, t); +} + +static int adis16400_get_freq(struct adis16400_state *st) +{ + int sps, ret; + uint16_t t; + + ret = __adis_read_reg_16(&st->adis, ADIS16400_SMPL_PRD, &t); + if (ret) + return ret; + + sps = (t & ADIS16400_SMPL_PRD_TIME_BASE) ? 52851 : 1638404; + sps /= (t & ADIS16400_SMPL_PRD_DIV_MASK) + 1; + + return sps; +} + +static int adis16400_set_freq(struct adis16400_state *st, unsigned int freq) +{ + unsigned int t; + uint8_t val = 0; + + t = 1638404 / freq; + if (t >= 128) { + val |= ADIS16400_SMPL_PRD_TIME_BASE; + t = 52851 / freq; + if (t >= 128) + t = 127; + } else if (t != 0) { + t--; + } + + val |= t; + + if (t >= 0x0A || (val & ADIS16400_SMPL_PRD_TIME_BASE)) + st->adis.spi->max_speed_hz = ADIS16400_SPI_SLOW; + else + st->adis.spi->max_speed_hz = ADIS16400_SPI_FAST; + + return __adis_write_reg_8(&st->adis, ADIS16400_SMPL_PRD, val); +} + +static const unsigned int adis16400_3db_divisors[] = { + [0] = 2, /* Special case */ + [1] = 6, + [2] = 12, + [3] = 25, + [4] = 50, + [5] = 100, + [6] = 200, + [7] = 200, /* Not a valid setting */ +}; + +static int __adis16400_set_filter(struct iio_dev *indio_dev, int sps, int val) +{ + struct adis16400_state *st = iio_priv(indio_dev); + uint16_t val16; + int i, ret; + + for (i = ARRAY_SIZE(adis16400_3db_divisors) - 1; i >= 1; i--) { + if (sps / adis16400_3db_divisors[i] >= val) + break; + } + + ret = __adis_read_reg_16(&st->adis, ADIS16400_SENS_AVG, &val16); + if (ret) + return ret; + + ret = __adis_write_reg_16(&st->adis, ADIS16400_SENS_AVG, + (val16 & ~0x07) | i); + return ret; +} + +/* Power down the device */ +static int adis16400_stop_device(struct iio_dev *indio_dev) +{ + struct adis16400_state *st = iio_priv(indio_dev); + int ret; + + ret = adis_write_reg_16(&st->adis, ADIS16400_SLP_CNT, + ADIS16400_SLP_CNT_POWER_OFF); + if (ret) + dev_err(&indio_dev->dev, + "problem with turning device off: SLP_CNT"); + + return ret; +} + +static int adis16400_initial_setup(struct iio_dev *indio_dev) +{ + struct adis16400_state *st = iio_priv(indio_dev); + uint16_t prod_id, smp_prd; + unsigned int device_id; + int ret; + + /* use low spi speed for init if the device has a slow mode */ + if (st->variant->flags & ADIS16400_HAS_SLOW_MODE) + st->adis.spi->max_speed_hz = ADIS16400_SPI_SLOW; + else + st->adis.spi->max_speed_hz = ADIS16400_SPI_FAST; + st->adis.spi->mode = SPI_MODE_3; + spi_setup(st->adis.spi); + + ret = __adis_initial_startup(&st->adis); + if (ret) + return ret; + + if (st->variant->flags & ADIS16400_HAS_PROD_ID) { + ret = adis_read_reg_16(&st->adis, + ADIS16400_PRODUCT_ID, &prod_id); + if (ret) + goto err_ret; + + if (sscanf(indio_dev->name, "adis%u\n", &device_id) != 1) { + ret = -EINVAL; + goto err_ret; + } + + if (prod_id != device_id) + dev_warn(&indio_dev->dev, "Device ID(%u) and product ID(%u) do not match.", + device_id, prod_id); + + dev_info(&indio_dev->dev, "%s: prod_id 0x%04x at CS%d (irq %d)\n", + indio_dev->name, prod_id, + spi_get_chipselect(st->adis.spi, 0), st->adis.spi->irq); + } + /* use high spi speed if possible */ + if (st->variant->flags & ADIS16400_HAS_SLOW_MODE) { + ret = adis_read_reg_16(&st->adis, ADIS16400_SMPL_PRD, &smp_prd); + if (ret) + goto err_ret; + + if ((smp_prd & ADIS16400_SMPL_PRD_DIV_MASK) < 0x0A) { + st->adis.spi->max_speed_hz = ADIS16400_SPI_FAST; + spi_setup(st->adis.spi); + } + } + +err_ret: + return ret; +} + +static const uint8_t adis16400_addresses[] = { + [ADIS16400_SCAN_GYRO_X] = ADIS16400_XGYRO_OFF, + [ADIS16400_SCAN_GYRO_Y] = ADIS16400_YGYRO_OFF, + [ADIS16400_SCAN_GYRO_Z] = ADIS16400_ZGYRO_OFF, + [ADIS16400_SCAN_ACC_X] = ADIS16400_XACCL_OFF, + [ADIS16400_SCAN_ACC_Y] = ADIS16400_YACCL_OFF, + [ADIS16400_SCAN_ACC_Z] = ADIS16400_ZACCL_OFF, +}; + +static int adis16400_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long info) +{ + struct adis16400_state *st = iio_priv(indio_dev); + int sps; + + switch (info) { + case IIO_CHAN_INFO_CALIBBIAS: + return adis_write_reg_16(&st->adis, + adis16400_addresses[chan->scan_index], + val); + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + /* + * Need to cache values so we can update if the frequency + * changes. + */ + adis_dev_auto_scoped_lock(&st->adis) { + st->filt_int = val; + /* Work out update to current value */ + sps = st->variant->get_freq(st); + if (sps < 0) + return sps; + + return __adis16400_set_filter(indio_dev, sps, + val * 1000 + val2 / 1000); + } + unreachable(); + case IIO_CHAN_INFO_SAMP_FREQ: + sps = val * 1000 + val2 / 1000; + + if (sps <= 0) + return -EINVAL; + + adis_dev_auto_scoped_lock(&st->adis) + return st->variant->set_freq(st, sps); + unreachable(); + default: + return -EINVAL; + } +} + +static int adis16400_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long info) +{ + struct adis16400_state *st = iio_priv(indio_dev); + int16_t val16; + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + return adis_single_conversion(indio_dev, chan, 0, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = 0; + *val2 = st->variant->gyro_scale_micro; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_VOLTAGE: + *val = 0; + if (chan->channel == 0) { + *val = 2; + *val2 = 418000; /* 2.418 mV */ + } else { + *val = 0; + *val2 = 805800; /* 805.8 uV */ + } + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ACCEL: + *val = 0; + *val2 = st->variant->accel_scale_micro; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_MAGN: + *val = 0; + *val2 = 500; /* 0.5 mgauss */ + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = st->variant->temp_scale_nano / 1000000; + *val2 = (st->variant->temp_scale_nano % 1000000); + return IIO_VAL_INT_PLUS_MICRO; + case IIO_PRESSURE: + /* 20 uBar = 0.002kPascal */ + *val = 0; + *val2 = 2000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + ret = adis_read_reg_16(&st->adis, + adis16400_addresses[chan->scan_index], &val16); + if (ret) + return ret; + val16 = sign_extend32(val16, 11); + *val = val16; + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + /* currently only temperature */ + *val = st->variant->temp_offset; + return IIO_VAL_INT; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + adis_dev_auto_scoped_lock(&st->adis) { + /* + * Need both the number of taps and the sampling + * frequency + */ + ret = __adis_read_reg_16(&st->adis, ADIS16400_SENS_AVG, + &val16); + if (ret) + return ret; + + ret = st->variant->get_freq(st); + if (ret) + return ret; + } + ret /= adis16400_3db_divisors[val16 & 0x07]; + *val = ret / 1000; + *val2 = (ret % 1000) * 1000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + adis_dev_auto_scoped_lock(&st->adis) { + ret = st->variant->get_freq(st); + if (ret) + return ret; + } + *val = ret / 1000; + *val2 = (ret % 1000) * 1000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +#if IS_ENABLED(CONFIG_IIO_BUFFER) +static irqreturn_t adis16400_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adis16400_state *st = iio_priv(indio_dev); + struct adis *adis = &st->adis; + void *buffer; + int ret; + + ret = spi_sync(adis->spi, &adis->msg); + if (ret) + dev_err(&adis->spi->dev, "Failed to read data: %d\n", ret); + + if (st->variant->flags & ADIS16400_BURST_DIAG_STAT) { + buffer = adis->buffer + sizeof(u16); + /* + * The size here is always larger than, or equal to the true + * size of the channel data. This may result in a larger copy + * than necessary, but as the target buffer will be + * buffer->scan_bytes this will be safe. + */ + iio_push_to_buffers_with_ts_unaligned(indio_dev, buffer, + indio_dev->scan_bytes - sizeof(pf->timestamp), + pf->timestamp); + } else { + iio_push_to_buffers_with_timestamp(indio_dev, + adis->buffer, + pf->timestamp); + } + + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} +#else +#define adis16400_trigger_handler NULL +#endif /* IS_ENABLED(CONFIG_IIO_BUFFER) */ + +#define ADIS16400_VOLTAGE_CHAN(addr, bits, name, si, chn) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = chn, \ + .extend_name = name, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = (addr), \ + .scan_index = (si), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADIS16400_SUPPLY_CHAN(addr, bits) \ + ADIS16400_VOLTAGE_CHAN(addr, bits, "supply", ADIS16400_SCAN_SUPPLY, 0) + +#define ADIS16400_AUX_ADC_CHAN(addr, bits) \ + ADIS16400_VOLTAGE_CHAN(addr, bits, NULL, ADIS16400_SCAN_ADC, 1) + +#define ADIS16400_GYRO_CHAN(mod, addr, bits) { \ + .type = IIO_ANGL_VEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## mod, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = addr, \ + .scan_index = ADIS16400_SCAN_GYRO_ ## mod, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADIS16400_ACCEL_CHAN(mod, addr, bits) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## mod, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = (addr), \ + .scan_index = ADIS16400_SCAN_ACC_ ## mod, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADIS16400_MAGN_CHAN(mod, addr, bits) { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## mod, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = (addr), \ + .scan_index = ADIS16400_SCAN_MAGN_ ## mod, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADIS16400_MOD_TEMP_NAME_X "x" +#define ADIS16400_MOD_TEMP_NAME_Y "y" +#define ADIS16400_MOD_TEMP_NAME_Z "z" + +#define ADIS16400_MOD_TEMP_CHAN(mod, addr, bits) { \ + .type = IIO_TEMP, \ + .indexed = 1, \ + .channel = 0, \ + .extend_name = ADIS16400_MOD_TEMP_NAME_ ## mod, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = (addr), \ + .scan_index = ADIS16350_SCAN_TEMP_ ## mod, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADIS16400_TEMP_CHAN(addr, bits) { \ + .type = IIO_TEMP, \ + .indexed = 1, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = (addr), \ + .scan_index = ADIS16350_SCAN_TEMP_X, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADIS16400_INCLI_CHAN(mod, addr, bits) { \ + .type = IIO_INCLI, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## mod, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = (addr), \ + .scan_index = ADIS16300_SCAN_INCLI_ ## mod, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +static const struct iio_chan_spec adis16400_channels[] = { + ADIS16400_SUPPLY_CHAN(ADIS16400_SUPPLY_OUT, 14), + ADIS16400_GYRO_CHAN(X, ADIS16400_XGYRO_OUT, 14), + ADIS16400_GYRO_CHAN(Y, ADIS16400_YGYRO_OUT, 14), + ADIS16400_GYRO_CHAN(Z, ADIS16400_ZGYRO_OUT, 14), + ADIS16400_ACCEL_CHAN(X, ADIS16400_XACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Y, ADIS16400_YACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Z, ADIS16400_ZACCL_OUT, 14), + ADIS16400_MAGN_CHAN(X, ADIS16400_XMAGN_OUT, 14), + ADIS16400_MAGN_CHAN(Y, ADIS16400_YMAGN_OUT, 14), + ADIS16400_MAGN_CHAN(Z, ADIS16400_ZMAGN_OUT, 14), + ADIS16400_TEMP_CHAN(ADIS16400_TEMP_OUT, 12), + ADIS16400_AUX_ADC_CHAN(ADIS16400_AUX_ADC, 12), + IIO_CHAN_SOFT_TIMESTAMP(ADIS16400_SCAN_TIMESTAMP), +}; + +static const struct iio_chan_spec adis16445_channels[] = { + ADIS16400_GYRO_CHAN(X, ADIS16400_XGYRO_OUT, 16), + ADIS16400_GYRO_CHAN(Y, ADIS16400_YGYRO_OUT, 16), + ADIS16400_GYRO_CHAN(Z, ADIS16400_ZGYRO_OUT, 16), + ADIS16400_ACCEL_CHAN(X, ADIS16400_XACCL_OUT, 16), + ADIS16400_ACCEL_CHAN(Y, ADIS16400_YACCL_OUT, 16), + ADIS16400_ACCEL_CHAN(Z, ADIS16400_ZACCL_OUT, 16), + ADIS16400_TEMP_CHAN(ADIS16448_TEMP_OUT, 12), + IIO_CHAN_SOFT_TIMESTAMP(ADIS16400_SCAN_TIMESTAMP), +}; + +static const struct iio_chan_spec adis16448_channels[] = { + ADIS16400_GYRO_CHAN(X, ADIS16400_XGYRO_OUT, 16), + ADIS16400_GYRO_CHAN(Y, ADIS16400_YGYRO_OUT, 16), + ADIS16400_GYRO_CHAN(Z, ADIS16400_ZGYRO_OUT, 16), + ADIS16400_ACCEL_CHAN(X, ADIS16400_XACCL_OUT, 16), + ADIS16400_ACCEL_CHAN(Y, ADIS16400_YACCL_OUT, 16), + ADIS16400_ACCEL_CHAN(Z, ADIS16400_ZACCL_OUT, 16), + ADIS16400_MAGN_CHAN(X, ADIS16400_XMAGN_OUT, 16), + ADIS16400_MAGN_CHAN(Y, ADIS16400_YMAGN_OUT, 16), + ADIS16400_MAGN_CHAN(Z, ADIS16400_ZMAGN_OUT, 16), + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .address = ADIS16448_BARO_OUT, + .scan_index = ADIS16400_SCAN_BARO, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, + }, + }, + ADIS16400_TEMP_CHAN(ADIS16448_TEMP_OUT, 12), + IIO_CHAN_SOFT_TIMESTAMP(ADIS16400_SCAN_TIMESTAMP), +}; + +static const struct iio_chan_spec adis16350_channels[] = { + ADIS16400_SUPPLY_CHAN(ADIS16400_SUPPLY_OUT, 12), + ADIS16400_GYRO_CHAN(X, ADIS16400_XGYRO_OUT, 14), + ADIS16400_GYRO_CHAN(Y, ADIS16400_YGYRO_OUT, 14), + ADIS16400_GYRO_CHAN(Z, ADIS16400_ZGYRO_OUT, 14), + ADIS16400_ACCEL_CHAN(X, ADIS16400_XACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Y, ADIS16400_YACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Z, ADIS16400_ZACCL_OUT, 14), + ADIS16400_MAGN_CHAN(X, ADIS16400_XMAGN_OUT, 14), + ADIS16400_MAGN_CHAN(Y, ADIS16400_YMAGN_OUT, 14), + ADIS16400_MAGN_CHAN(Z, ADIS16400_ZMAGN_OUT, 14), + ADIS16400_AUX_ADC_CHAN(ADIS16300_AUX_ADC, 12), + ADIS16400_MOD_TEMP_CHAN(X, ADIS16350_XTEMP_OUT, 12), + ADIS16400_MOD_TEMP_CHAN(Y, ADIS16350_YTEMP_OUT, 12), + ADIS16400_MOD_TEMP_CHAN(Z, ADIS16350_ZTEMP_OUT, 12), + IIO_CHAN_SOFT_TIMESTAMP(ADIS16400_SCAN_TIMESTAMP), +}; + +static const struct iio_chan_spec adis16300_channels[] = { + ADIS16400_SUPPLY_CHAN(ADIS16400_SUPPLY_OUT, 12), + ADIS16400_GYRO_CHAN(X, ADIS16400_XGYRO_OUT, 14), + ADIS16400_ACCEL_CHAN(X, ADIS16400_XACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Y, ADIS16400_YACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Z, ADIS16400_ZACCL_OUT, 14), + ADIS16400_TEMP_CHAN(ADIS16350_XTEMP_OUT, 12), + ADIS16400_AUX_ADC_CHAN(ADIS16300_AUX_ADC, 12), + ADIS16400_INCLI_CHAN(X, ADIS16300_PITCH_OUT, 13), + ADIS16400_INCLI_CHAN(Y, ADIS16300_ROLL_OUT, 13), + IIO_CHAN_SOFT_TIMESTAMP(ADIS16400_SCAN_TIMESTAMP), +}; + +static const struct iio_chan_spec adis16334_channels[] = { + ADIS16400_GYRO_CHAN(X, ADIS16400_XGYRO_OUT, 14), + ADIS16400_GYRO_CHAN(Y, ADIS16400_YGYRO_OUT, 14), + ADIS16400_GYRO_CHAN(Z, ADIS16400_ZGYRO_OUT, 14), + ADIS16400_ACCEL_CHAN(X, ADIS16400_XACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Y, ADIS16400_YACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Z, ADIS16400_ZACCL_OUT, 14), + ADIS16400_TEMP_CHAN(ADIS16350_XTEMP_OUT, 12), + IIO_CHAN_SOFT_TIMESTAMP(ADIS16400_SCAN_TIMESTAMP), +}; + +static const char * const adis16400_status_error_msgs[] = { + [ADIS16400_DIAG_STAT_ZACCL_FAIL] = "Z-axis accelerometer self-test failure", + [ADIS16400_DIAG_STAT_YACCL_FAIL] = "Y-axis accelerometer self-test failure", + [ADIS16400_DIAG_STAT_XACCL_FAIL] = "X-axis accelerometer self-test failure", + [ADIS16400_DIAG_STAT_XGYRO_FAIL] = "X-axis gyroscope self-test failure", + [ADIS16400_DIAG_STAT_YGYRO_FAIL] = "Y-axis gyroscope self-test failure", + [ADIS16400_DIAG_STAT_ZGYRO_FAIL] = "Z-axis gyroscope self-test failure", + [ADIS16400_DIAG_STAT_ALARM2] = "Alarm 2 active", + [ADIS16400_DIAG_STAT_ALARM1] = "Alarm 1 active", + [ADIS16400_DIAG_STAT_FLASH_CHK] = "Flash checksum error", + [ADIS16400_DIAG_STAT_SELF_TEST] = "Self test error", + [ADIS16400_DIAG_STAT_OVERFLOW] = "Sensor overrange", + [ADIS16400_DIAG_STAT_SPI_FAIL] = "SPI failure", + [ADIS16400_DIAG_STAT_FLASH_UPT] = "Flash update failed", + [ADIS16400_DIAG_STAT_POWER_HIGH] = "Power supply above 5.25V", + [ADIS16400_DIAG_STAT_POWER_LOW] = "Power supply below 4.75V", +}; + +#define ADIS16400_DATA(_timeouts, _burst_len) \ +{ \ + .msc_ctrl_reg = ADIS16400_MSC_CTRL, \ + .glob_cmd_reg = ADIS16400_GLOB_CMD, \ + .diag_stat_reg = ADIS16400_DIAG_STAT, \ + .read_delay = 50, \ + .write_delay = 50, \ + .self_test_mask = ADIS16400_MSC_CTRL_MEM_TEST, \ + .self_test_reg = ADIS16400_MSC_CTRL, \ + .status_error_msgs = adis16400_status_error_msgs, \ + .status_error_mask = BIT(ADIS16400_DIAG_STAT_ZACCL_FAIL) | \ + BIT(ADIS16400_DIAG_STAT_YACCL_FAIL) | \ + BIT(ADIS16400_DIAG_STAT_XACCL_FAIL) | \ + BIT(ADIS16400_DIAG_STAT_XGYRO_FAIL) | \ + BIT(ADIS16400_DIAG_STAT_YGYRO_FAIL) | \ + BIT(ADIS16400_DIAG_STAT_ZGYRO_FAIL) | \ + BIT(ADIS16400_DIAG_STAT_ALARM2) | \ + BIT(ADIS16400_DIAG_STAT_ALARM1) | \ + BIT(ADIS16400_DIAG_STAT_FLASH_CHK) | \ + BIT(ADIS16400_DIAG_STAT_SELF_TEST) | \ + BIT(ADIS16400_DIAG_STAT_OVERFLOW) | \ + BIT(ADIS16400_DIAG_STAT_SPI_FAIL) | \ + BIT(ADIS16400_DIAG_STAT_FLASH_UPT) | \ + BIT(ADIS16400_DIAG_STAT_POWER_HIGH) | \ + BIT(ADIS16400_DIAG_STAT_POWER_LOW), \ + .timeouts = (_timeouts), \ + .burst_reg_cmd = ADIS16400_GLOB_CMD, \ + .burst_len = (_burst_len), \ + .burst_max_speed_hz = ADIS16400_SPI_BURST \ +} + +static const struct adis_timeout adis16300_timeouts = { + .reset_ms = ADIS16400_STARTUP_DELAY, + .sw_reset_ms = ADIS16400_STARTUP_DELAY, + .self_test_ms = ADIS16400_STARTUP_DELAY, +}; + +static const struct adis_timeout adis16334_timeouts = { + .reset_ms = 60, + .sw_reset_ms = 60, + .self_test_ms = 14, +}; + +static const struct adis_timeout adis16362_timeouts = { + .reset_ms = 130, + .sw_reset_ms = 130, + .self_test_ms = 12, +}; + +static const struct adis_timeout adis16400_timeouts = { + .reset_ms = 170, + .sw_reset_ms = 170, + .self_test_ms = 12, +}; + +static const struct adis_timeout adis16445_timeouts = { + .reset_ms = 55, + .sw_reset_ms = 55, + .self_test_ms = 16, +}; + +static const struct adis_timeout adis16448_timeouts = { + .reset_ms = 90, + .sw_reset_ms = 90, + .self_test_ms = 45, +}; + +static const struct adis16400_chip_info adis16300_chip_info = { + .channels = adis16300_channels, + .num_channels = ARRAY_SIZE(adis16300_channels), + .flags = ADIS16400_HAS_PROD_ID | ADIS16400_HAS_SLOW_MODE | + ADIS16400_HAS_SERIAL_NUMBER, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(50000), /* 0.05 deg/s */ + .accel_scale_micro = 5884, + .temp_scale_nano = 140000000, /* 0.14 C */ + .temp_offset = 25000000 / 140000, /* 25 C = 0x00 */ + .set_freq = adis16400_set_freq, + .get_freq = adis16400_get_freq, + .adis_data = ADIS16400_DATA(&adis16300_timeouts, 18), +}; + +static const struct adis16400_chip_info adis16334_chip_info = { + .channels = adis16334_channels, + .num_channels = ARRAY_SIZE(adis16334_channels), + .flags = ADIS16400_HAS_PROD_ID | ADIS16400_NO_BURST | + ADIS16400_HAS_SERIAL_NUMBER, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(50000), /* 0.05 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(1000), /* 1 mg */ + .temp_scale_nano = 67850000, /* 0.06785 C */ + .temp_offset = 25000000 / 67850, /* 25 C = 0x00 */ + .set_freq = adis16334_set_freq, + .get_freq = adis16334_get_freq, + .adis_data = ADIS16400_DATA(&adis16334_timeouts, 0), +}; + +static const struct adis16400_chip_info adis16350_chip_info = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .gyro_scale_micro = IIO_DEGREE_TO_RAD(73260), /* 0.07326 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(2522), /* 0.002522 g */ + .temp_scale_nano = 145300000, /* 0.1453 C */ + .temp_offset = 25000000 / 145300, /* 25 C = 0x00 */ + .flags = ADIS16400_NO_BURST | ADIS16400_HAS_SLOW_MODE, + .set_freq = adis16400_set_freq, + .get_freq = adis16400_get_freq, + .adis_data = ADIS16400_DATA(&adis16300_timeouts, 0), +}; + +static const struct adis16400_chip_info adis16360_chip_info = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .flags = ADIS16400_HAS_PROD_ID | ADIS16400_HAS_SLOW_MODE | + ADIS16400_HAS_SERIAL_NUMBER, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(50000), /* 0.05 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(3333), /* 3.333 mg */ + .temp_scale_nano = 136000000, /* 0.136 C */ + .temp_offset = 25000000 / 136000, /* 25 C = 0x00 */ + .set_freq = adis16400_set_freq, + .get_freq = adis16400_get_freq, + .adis_data = ADIS16400_DATA(&adis16300_timeouts, 28), +}; + +static const struct adis16400_chip_info adis16362_chip_info = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .flags = ADIS16400_HAS_PROD_ID | ADIS16400_HAS_SLOW_MODE | + ADIS16400_HAS_SERIAL_NUMBER, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(50000), /* 0.05 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(333), /* 0.333 mg */ + .temp_scale_nano = 136000000, /* 0.136 C */ + .temp_offset = 25000000 / 136000, /* 25 C = 0x00 */ + .set_freq = adis16400_set_freq, + .get_freq = adis16400_get_freq, + .adis_data = ADIS16400_DATA(&adis16362_timeouts, 28), +}; + +static const struct adis16400_chip_info adis16364_chip_info = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .flags = ADIS16400_HAS_PROD_ID | ADIS16400_HAS_SLOW_MODE | + ADIS16400_HAS_SERIAL_NUMBER, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(50000), /* 0.05 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(1000), /* 1 mg */ + .temp_scale_nano = 136000000, /* 0.136 C */ + .temp_offset = 25000000 / 136000, /* 25 C = 0x00 */ + .set_freq = adis16400_set_freq, + .get_freq = adis16400_get_freq, + .adis_data = ADIS16400_DATA(&adis16362_timeouts, 28), +}; + +static const struct adis16400_chip_info adis16367_chip_info = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .flags = ADIS16400_HAS_PROD_ID | ADIS16400_HAS_SLOW_MODE | + ADIS16400_HAS_SERIAL_NUMBER, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(2000), /* 0.2 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(3333), /* 3.333 mg */ + .temp_scale_nano = 136000000, /* 0.136 C */ + .temp_offset = 25000000 / 136000, /* 25 C = 0x00 */ + .set_freq = adis16400_set_freq, + .get_freq = adis16400_get_freq, + .adis_data = ADIS16400_DATA(&adis16300_timeouts, 28), +}; + +static const struct adis16400_chip_info adis16400_chip_info = { + .channels = adis16400_channels, + .num_channels = ARRAY_SIZE(adis16400_channels), + .flags = ADIS16400_HAS_PROD_ID | ADIS16400_HAS_SLOW_MODE, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(50000), /* 0.05 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(3333), /* 3.333 mg */ + .temp_scale_nano = 140000000, /* 0.14 C */ + .temp_offset = 25000000 / 140000, /* 25 C = 0x00 */ + .set_freq = adis16400_set_freq, + .get_freq = adis16400_get_freq, + .adis_data = ADIS16400_DATA(&adis16400_timeouts, 24), +}; + +static const struct adis16400_chip_info adis16445_chip_info = { + .channels = adis16445_channels, + .num_channels = ARRAY_SIZE(adis16445_channels), + .flags = ADIS16400_HAS_PROD_ID | ADIS16400_HAS_SERIAL_NUMBER | + ADIS16400_BURST_DIAG_STAT, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(10000), /* 0.01 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(250), /* 1/4000 g */ + .temp_scale_nano = 73860000, /* 0.07386 C */ + .temp_offset = 31000000 / 73860, /* 31 C = 0x00 */ + .set_freq = adis16334_set_freq, + .get_freq = adis16334_get_freq, + .adis_data = ADIS16400_DATA(&adis16445_timeouts, 16), +}; + +static const struct adis16400_chip_info adis16448_chip_info = { + .channels = adis16448_channels, + .num_channels = ARRAY_SIZE(adis16448_channels), + .flags = ADIS16400_HAS_PROD_ID | ADIS16400_HAS_SERIAL_NUMBER | + ADIS16400_BURST_DIAG_STAT, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(40000), /* 0.04 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(833), /* 1/1200 g */ + .temp_scale_nano = 73860000, /* 0.07386 C */ + .temp_offset = 31000000 / 73860, /* 31 C = 0x00 */ + .set_freq = adis16334_set_freq, + .get_freq = adis16334_get_freq, + .adis_data = ADIS16400_DATA(&adis16448_timeouts, 24), +}; + +static const struct iio_info adis16400_info = { + .read_raw = &adis16400_read_raw, + .write_raw = &adis16400_write_raw, + .update_scan_mode = adis_update_scan_mode, + .debugfs_reg_access = adis_debugfs_reg_access, +}; + +static void adis16400_setup_chan_mask(struct adis16400_state *st) +{ + const struct adis16400_chip_info *chip_info = st->variant; + unsigned int i; + + for (i = 0; i < chip_info->num_channels; i++) { + const struct iio_chan_spec *ch = &chip_info->channels[i]; + + if (ch->scan_index >= 0 && + ch->scan_index != ADIS16400_SCAN_TIMESTAMP) + st->avail_scan_mask[0] |= BIT(ch->scan_index); + } +} + +static void adis16400_stop(void *data) +{ + adis16400_stop_device(data); +} + +static int adis16400_probe(struct spi_device *spi) +{ + struct adis16400_state *st; + struct iio_dev *indio_dev; + int ret; + const struct adis_data *adis16400_data; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + /* setup the industrialio driver allocated elements */ + st->variant = spi_get_device_match_data(spi); + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->channels = st->variant->channels; + indio_dev->num_channels = st->variant->num_channels; + indio_dev->info = &adis16400_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (!(st->variant->flags & ADIS16400_NO_BURST)) { + adis16400_setup_chan_mask(st); + indio_dev->available_scan_masks = st->avail_scan_mask; + } + + adis16400_data = &st->variant->adis_data; + + ret = adis_init(&st->adis, indio_dev, spi, adis16400_data); + if (ret) + return ret; + + ret = devm_adis_setup_buffer_and_trigger(&st->adis, indio_dev, adis16400_trigger_handler); + if (ret) + return ret; + + /* Get the device into a sane initial state */ + ret = adis16400_initial_setup(indio_dev); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&spi->dev, adis16400_stop, indio_dev); + if (ret) + return ret; + + ret = devm_iio_device_register(&spi->dev, indio_dev); + if (ret) + return ret; + + adis16400_debugfs_init(indio_dev); + return 0; +} + +static const struct spi_device_id adis16400_id[] = { + { .name = "adis16300", .driver_data = (kernel_ulong_t)&adis16300_chip_info }, + { .name = "adis16305", .driver_data = (kernel_ulong_t)&adis16300_chip_info }, + { .name = "adis16334", .driver_data = (kernel_ulong_t)&adis16334_chip_info }, + { .name = "adis16350", .driver_data = (kernel_ulong_t)&adis16350_chip_info }, + { .name = "adis16354", .driver_data = (kernel_ulong_t)&adis16350_chip_info }, + { .name = "adis16355", .driver_data = (kernel_ulong_t)&adis16350_chip_info }, + { .name = "adis16360", .driver_data = (kernel_ulong_t)&adis16360_chip_info }, + { .name = "adis16362", .driver_data = (kernel_ulong_t)&adis16362_chip_info }, + { .name = "adis16364", .driver_data = (kernel_ulong_t)&adis16364_chip_info }, + { .name = "adis16365", .driver_data = (kernel_ulong_t)&adis16360_chip_info }, + { .name = "adis16367", .driver_data = (kernel_ulong_t)&adis16367_chip_info }, + { .name = "adis16400", .driver_data = (kernel_ulong_t)&adis16400_chip_info }, + { .name = "adis16405", .driver_data = (kernel_ulong_t)&adis16400_chip_info }, + { .name = "adis16445", .driver_data = (kernel_ulong_t)&adis16445_chip_info }, + { .name = "adis16448", .driver_data = (kernel_ulong_t)&adis16448_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, adis16400_id); + +static struct spi_driver adis16400_driver = { + .driver = { + .name = "adis16400", + }, + .id_table = adis16400_id, + .probe = adis16400_probe, +}; +module_spi_driver(adis16400_driver); + +MODULE_AUTHOR("Manuel Stahl "); +MODULE_DESCRIPTION("Analog Devices ADIS16400/5 IMU SPI driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ADISLIB"); diff --git a/drivers/iio/imu/adis16460.c b/drivers/iio/imu/adis16460.c new file mode 100644 index 000000000..cadf21c05 --- /dev/null +++ b/drivers/iio/imu/adis16460.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ADIS16460 IMU driver + * + * Copyright 2019 Analog Devices Inc. + */ + +#include +#include + +#include +#include + +#include + +#define ADIS16460_REG_FLASH_CNT 0x00 +#define ADIS16460_REG_DIAG_STAT 0x02 +#define ADIS16460_REG_X_GYRO_LOW 0x04 +#define ADIS16460_REG_X_GYRO_OUT 0x06 +#define ADIS16460_REG_Y_GYRO_LOW 0x08 +#define ADIS16460_REG_Y_GYRO_OUT 0x0A +#define ADIS16460_REG_Z_GYRO_LOW 0x0C +#define ADIS16460_REG_Z_GYRO_OUT 0x0E +#define ADIS16460_REG_X_ACCL_LOW 0x10 +#define ADIS16460_REG_X_ACCL_OUT 0x12 +#define ADIS16460_REG_Y_ACCL_LOW 0x14 +#define ADIS16460_REG_Y_ACCL_OUT 0x16 +#define ADIS16460_REG_Z_ACCL_LOW 0x18 +#define ADIS16460_REG_Z_ACCL_OUT 0x1A +#define ADIS16460_REG_SMPL_CNTR 0x1C +#define ADIS16460_REG_TEMP_OUT 0x1E +#define ADIS16460_REG_X_DELT_ANG 0x24 +#define ADIS16460_REG_Y_DELT_ANG 0x26 +#define ADIS16460_REG_Z_DELT_ANG 0x28 +#define ADIS16460_REG_X_DELT_VEL 0x2A +#define ADIS16460_REG_Y_DELT_VEL 0x2C +#define ADIS16460_REG_Z_DELT_VEL 0x2E +#define ADIS16460_REG_MSC_CTRL 0x32 +#define ADIS16460_REG_SYNC_SCAL 0x34 +#define ADIS16460_REG_DEC_RATE 0x36 +#define ADIS16460_REG_FLTR_CTRL 0x38 +#define ADIS16460_REG_GLOB_CMD 0x3E +#define ADIS16460_REG_X_GYRO_OFF 0x40 +#define ADIS16460_REG_Y_GYRO_OFF 0x42 +#define ADIS16460_REG_Z_GYRO_OFF 0x44 +#define ADIS16460_REG_X_ACCL_OFF 0x46 +#define ADIS16460_REG_Y_ACCL_OFF 0x48 +#define ADIS16460_REG_Z_ACCL_OFF 0x4A +#define ADIS16460_REG_LOT_ID1 0x52 +#define ADIS16460_REG_LOT_ID2 0x54 +#define ADIS16460_REG_PROD_ID 0x56 +#define ADIS16460_REG_SERIAL_NUM 0x58 +#define ADIS16460_REG_CAL_SGNTR 0x60 +#define ADIS16460_REG_CAL_CRC 0x62 +#define ADIS16460_REG_CODE_SGNTR 0x64 +#define ADIS16460_REG_CODE_CRC 0x66 + +struct adis16460_chip_info { + unsigned int num_channels; + const struct iio_chan_spec *channels; + unsigned int gyro_max_val; + unsigned int gyro_max_scale; + unsigned int accel_max_val; + unsigned int accel_max_scale; +}; + +struct adis16460 { + const struct adis16460_chip_info *chip_info; + struct adis adis; +}; + +static int adis16460_show_serial_number(void *arg, u64 *val) +{ + struct adis16460 *adis16460 = arg; + u16 serial; + int ret; + + ret = adis_read_reg_16(&adis16460->adis, ADIS16460_REG_SERIAL_NUM, + &serial); + if (ret) + return ret; + + *val = serial; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(adis16460_serial_number_fops, + adis16460_show_serial_number, NULL, "0x%.4llx\n"); + +static int adis16460_show_product_id(void *arg, u64 *val) +{ + struct adis16460 *adis16460 = arg; + u16 prod_id; + int ret; + + ret = adis_read_reg_16(&adis16460->adis, ADIS16460_REG_PROD_ID, + &prod_id); + if (ret) + return ret; + + *val = prod_id; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(adis16460_product_id_fops, + adis16460_show_product_id, NULL, "%llu\n"); + +static int adis16460_show_flash_count(void *arg, u64 *val) +{ + struct adis16460 *adis16460 = arg; + u32 flash_count; + int ret; + + ret = adis_read_reg_32(&adis16460->adis, ADIS16460_REG_FLASH_CNT, + &flash_count); + if (ret) + return ret; + + *val = flash_count; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(adis16460_flash_count_fops, + adis16460_show_flash_count, NULL, "%lld\n"); + +static void adis16460_debugfs_init(struct iio_dev *indio_dev) +{ + struct adis16460 *adis16460 = iio_priv(indio_dev); + struct dentry *d = iio_get_debugfs_dentry(indio_dev); + + if (!IS_ENABLED(CONFIG_DEBUG_FS)) + return; + + debugfs_create_file_unsafe("serial_number", 0400, + d, adis16460, &adis16460_serial_number_fops); + debugfs_create_file_unsafe("product_id", 0400, + d, adis16460, &adis16460_product_id_fops); + debugfs_create_file_unsafe("flash_count", 0400, + d, adis16460, &adis16460_flash_count_fops); +} + +static int adis16460_set_freq(struct iio_dev *indio_dev, int val, int val2) +{ + struct adis16460 *st = iio_priv(indio_dev); + int t; + + t = val * 1000 + val2 / 1000; + if (t <= 0) + return -EINVAL; + + t = 2048000 / t; + if (t > 2048) + t = 2048; + + if (t != 0) + t--; + + return adis_write_reg_16(&st->adis, ADIS16460_REG_DEC_RATE, t); +} + +static int adis16460_get_freq(struct iio_dev *indio_dev, int *val, int *val2) +{ + struct adis16460 *st = iio_priv(indio_dev); + uint16_t t; + int ret; + unsigned int freq; + + ret = adis_read_reg_16(&st->adis, ADIS16460_REG_DEC_RATE, &t); + if (ret) + return ret; + + freq = 2048000 / (t + 1); + *val = freq / 1000; + *val2 = (freq % 1000) * 1000; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int adis16460_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, int *val2, long info) +{ + struct adis16460 *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_RAW: + return adis_single_conversion(indio_dev, chan, 0, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = st->chip_info->gyro_max_scale; + *val2 = st->chip_info->gyro_max_val; + return IIO_VAL_FRACTIONAL; + case IIO_ACCEL: + *val = st->chip_info->accel_max_scale; + *val2 = st->chip_info->accel_max_val; + return IIO_VAL_FRACTIONAL; + case IIO_TEMP: + *val = 50; /* 50 milli degrees Celsius/LSB */ + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + *val = 500; /* 25 degrees Celsius = 0x0000 */ + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + return adis16460_get_freq(indio_dev, val, val2); + default: + return -EINVAL; + } +} + +static int adis16460_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int val, int val2, long info) +{ + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + return adis16460_set_freq(indio_dev, val, val2); + default: + return -EINVAL; + } +} + +enum { + ADIS16460_SCAN_GYRO_X, + ADIS16460_SCAN_GYRO_Y, + ADIS16460_SCAN_GYRO_Z, + ADIS16460_SCAN_ACCEL_X, + ADIS16460_SCAN_ACCEL_Y, + ADIS16460_SCAN_ACCEL_Z, + ADIS16460_SCAN_TEMP, +}; + +#define ADIS16460_MOD_CHANNEL(_type, _mod, _address, _si, _bits) \ + { \ + .type = (_type), \ + .modified = 1, \ + .channel2 = (_mod), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = (_address), \ + .scan_index = (_si), \ + .scan_type = { \ + .sign = 's', \ + .realbits = (_bits), \ + .storagebits = (_bits), \ + .endianness = IIO_BE, \ + }, \ + } + +#define ADIS16460_GYRO_CHANNEL(_mod) \ + ADIS16460_MOD_CHANNEL(IIO_ANGL_VEL, IIO_MOD_ ## _mod, \ + ADIS16460_REG_ ## _mod ## _GYRO_LOW, ADIS16460_SCAN_GYRO_ ## _mod, \ + 32) + +#define ADIS16460_ACCEL_CHANNEL(_mod) \ + ADIS16460_MOD_CHANNEL(IIO_ACCEL, IIO_MOD_ ## _mod, \ + ADIS16460_REG_ ## _mod ## _ACCL_LOW, ADIS16460_SCAN_ACCEL_ ## _mod, \ + 32) + +#define ADIS16460_TEMP_CHANNEL() { \ + .type = IIO_TEMP, \ + .indexed = 1, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = ADIS16460_REG_TEMP_OUT, \ + .scan_index = ADIS16460_SCAN_TEMP, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + } + +static const struct iio_chan_spec adis16460_channels[] = { + ADIS16460_GYRO_CHANNEL(X), + ADIS16460_GYRO_CHANNEL(Y), + ADIS16460_GYRO_CHANNEL(Z), + ADIS16460_ACCEL_CHANNEL(X), + ADIS16460_ACCEL_CHANNEL(Y), + ADIS16460_ACCEL_CHANNEL(Z), + ADIS16460_TEMP_CHANNEL(), + IIO_CHAN_SOFT_TIMESTAMP(7) +}; + +static const struct adis16460_chip_info adis16460_chip_info = { + .channels = adis16460_channels, + .num_channels = ARRAY_SIZE(adis16460_channels), + /* + * storing the value in rad/degree and the scale in degree + * gives us the result in rad and better precession than + * storing the scale directly in rad. + */ + .gyro_max_val = IIO_RAD_TO_DEGREE(200 << 16), + .gyro_max_scale = 1, + .accel_max_val = IIO_M_S_2_TO_G(20000 << 16), + .accel_max_scale = 5, +}; + +static const struct iio_info adis16460_info = { + .read_raw = &adis16460_read_raw, + .write_raw = &adis16460_write_raw, + .update_scan_mode = adis_update_scan_mode, + .debugfs_reg_access = adis_debugfs_reg_access, +}; + +#define ADIS16460_DIAG_STAT_IN_CLK_OOS 7 +#define ADIS16460_DIAG_STAT_FLASH_MEM 6 +#define ADIS16460_DIAG_STAT_SELF_TEST 5 +#define ADIS16460_DIAG_STAT_OVERRANGE 4 +#define ADIS16460_DIAG_STAT_SPI_COMM 3 +#define ADIS16460_DIAG_STAT_FLASH_UPT 2 + +static const char * const adis16460_status_error_msgs[] = { + [ADIS16460_DIAG_STAT_IN_CLK_OOS] = "Input clock out of sync", + [ADIS16460_DIAG_STAT_FLASH_MEM] = "Flash memory failure", + [ADIS16460_DIAG_STAT_SELF_TEST] = "Self test diagnostic failure", + [ADIS16460_DIAG_STAT_OVERRANGE] = "Sensor overrange", + [ADIS16460_DIAG_STAT_SPI_COMM] = "SPI communication failure", + [ADIS16460_DIAG_STAT_FLASH_UPT] = "Flash update failure", +}; + +static const struct adis_timeout adis16460_timeouts = { + .reset_ms = 225, + .sw_reset_ms = 225, + .self_test_ms = 10, +}; + +static const struct adis_data adis16460_data = { + .diag_stat_reg = ADIS16460_REG_DIAG_STAT, + .glob_cmd_reg = ADIS16460_REG_GLOB_CMD, + .prod_id_reg = ADIS16460_REG_PROD_ID, + .prod_id = 16460, + .self_test_mask = BIT(2), + .self_test_reg = ADIS16460_REG_GLOB_CMD, + .has_paging = false, + .read_delay = 5, + .write_delay = 5, + .cs_change_delay = 16, + .status_error_msgs = adis16460_status_error_msgs, + .status_error_mask = BIT(ADIS16460_DIAG_STAT_IN_CLK_OOS) | + BIT(ADIS16460_DIAG_STAT_FLASH_MEM) | + BIT(ADIS16460_DIAG_STAT_SELF_TEST) | + BIT(ADIS16460_DIAG_STAT_OVERRANGE) | + BIT(ADIS16460_DIAG_STAT_SPI_COMM) | + BIT(ADIS16460_DIAG_STAT_FLASH_UPT), + .unmasked_drdy = true, + .timeouts = &adis16460_timeouts, +}; + +static int adis16460_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct adis16460 *st; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->chip_info = &adis16460_chip_info; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + indio_dev->info = &adis16460_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = adis_init(&st->adis, indio_dev, spi, &adis16460_data); + if (ret) + return ret; + + ret = devm_adis_setup_buffer_and_trigger(&st->adis, indio_dev, NULL); + if (ret) + return ret; + + ret = __adis_initial_startup(&st->adis); + if (ret) + return ret; + + ret = devm_iio_device_register(&spi->dev, indio_dev); + if (ret) + return ret; + + adis16460_debugfs_init(indio_dev); + + return 0; +} + +static const struct spi_device_id adis16460_ids[] = { + { .name = "adis16460" }, + { } +}; +MODULE_DEVICE_TABLE(spi, adis16460_ids); + +static const struct of_device_id adis16460_of_match[] = { + { .compatible = "adi,adis16460" }, + { } +}; +MODULE_DEVICE_TABLE(of, adis16460_of_match); + +static struct spi_driver adis16460_driver = { + .driver = { + .name = "adis16460", + .of_match_table = adis16460_of_match, + }, + .id_table = adis16460_ids, + .probe = adis16460_probe, +}; +module_spi_driver(adis16460_driver); + +MODULE_AUTHOR("Dragos Bogdan "); +MODULE_DESCRIPTION("Analog Devices ADIS16460 IMU driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_ADISLIB"); diff --git a/drivers/iio/imu/adis16475.c b/drivers/iio/imu/adis16475.c new file mode 100644 index 000000000..5f21dceda --- /dev/null +++ b/drivers/iio/imu/adis16475.c @@ -0,0 +1,2108 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ADIS16475 IMU driver + * + * Copyright 2019 Analog Devices Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ADIS16475_REG_DIAG_STAT 0x02 +#define ADIS16475_REG_X_GYRO_L 0x04 +#define ADIS16475_REG_Y_GYRO_L 0x08 +#define ADIS16475_REG_Z_GYRO_L 0x0C +#define ADIS16475_REG_X_ACCEL_L 0x10 +#define ADIS16475_REG_Y_ACCEL_L 0x14 +#define ADIS16475_REG_Z_ACCEL_L 0x18 +#define ADIS16475_REG_TEMP_OUT 0x1c +#define ADIS16475_REG_X_DELTANG_L 0x24 +#define ADIS16475_REG_Y_DELTANG_L 0x28 +#define ADIS16475_REG_Z_DELTANG_L 0x2C +#define ADIS16475_REG_X_DELTVEL_L 0x30 +#define ADIS16475_REG_Y_DELTVEL_L 0x34 +#define ADIS16475_REG_Z_DELTVEL_L 0x38 +#define ADIS16475_REG_X_GYRO_BIAS_L 0x40 +#define ADIS16475_REG_Y_GYRO_BIAS_L 0x44 +#define ADIS16475_REG_Z_GYRO_BIAS_L 0x48 +#define ADIS16475_REG_X_ACCEL_BIAS_L 0x4c +#define ADIS16475_REG_Y_ACCEL_BIAS_L 0x50 +#define ADIS16475_REG_Z_ACCEL_BIAS_L 0x54 +#define ADIS16475_REG_FILT_CTRL 0x5c +#define ADIS16475_FILT_CTRL_MASK GENMASK(2, 0) +#define ADIS16475_FILT_CTRL(x) FIELD_PREP(ADIS16475_FILT_CTRL_MASK, x) +#define ADIS16475_REG_MSG_CTRL 0x60 +#define ADIS16475_MSG_CTRL_DR_POL_MASK BIT(0) +#define ADIS16475_MSG_CTRL_DR_POL(x) \ + FIELD_PREP(ADIS16475_MSG_CTRL_DR_POL_MASK, x) +#define ADIS16475_SYNC_MODE_MASK GENMASK(4, 2) +#define ADIS16475_SYNC_MODE(x) FIELD_PREP(ADIS16475_SYNC_MODE_MASK, x) +#define ADIS16575_SYNC_4KHZ_MASK BIT(11) +#define ADIS16575_SYNC_4KHZ(x) FIELD_PREP(ADIS16575_SYNC_4KHZ_MASK, x) +#define ADIS16475_REG_UP_SCALE 0x62 +#define ADIS16475_REG_DEC_RATE 0x64 +#define ADIS16475_REG_GLOB_CMD 0x68 +#define ADIS16475_REG_FIRM_REV 0x6c +#define ADIS16475_REG_FIRM_DM 0x6e +#define ADIS16475_REG_FIRM_Y 0x70 +#define ADIS16475_REG_PROD_ID 0x72 +#define ADIS16475_REG_SERIAL_NUM 0x74 +#define ADIS16475_REG_FLASH_CNT 0x7c +#define ADIS16500_BURST_DATA_SEL_MASK BIT(8) +#define ADIS16500_BURST32_MASK BIT(9) +#define ADIS16500_BURST32(x) FIELD_PREP(ADIS16500_BURST32_MASK, x) +/* number of data elements in burst mode */ +#define ADIS16475_BURST32_MAX_DATA_NO_TS32 32 +#define ADIS16575_BURST32_DATA_TS32 34 +#define ADIS16475_BURST_MAX_DATA 20 +#define ADIS16475_MAX_SCAN_DATA 20 +/* spi max speed in brust mode */ +#define ADIS16475_BURST_MAX_SPEED 1000000 +#define ADIS16575_BURST_MAX_SPEED 8000000 +#define ADIS16475_LSB_DEC_MASK 0 +#define ADIS16475_LSB_FIR_MASK 1 +#define ADIS16500_BURST_DATA_SEL_0_CHN_MASK GENMASK(5, 0) +#define ADIS16500_BURST_DATA_SEL_1_CHN_MASK GENMASK(12, 7) +#define ADIS16575_MAX_FIFO_WM 511UL +#define ADIS16475_REG_FIFO_CTRL 0x5A +#define ADIS16575_WM_LVL_MASK GENMASK(15, 4) +#define ADIS16575_WM_LVL(x) FIELD_PREP(ADIS16575_WM_LVL_MASK, x) +#define ADIS16575_WM_POL_MASK BIT(3) +#define ADIS16575_WM_POL(x) FIELD_PREP(ADIS16575_WM_POL_MASK, x) +#define ADIS16575_WM_EN_MASK BIT(2) +#define ADIS16575_WM_EN(x) FIELD_PREP(ADIS16575_WM_EN_MASK, x) +#define ADIS16575_OVERFLOW_MASK BIT(1) +#define ADIS16575_STOP_ENQUEUE FIELD_PREP(ADIS16575_OVERFLOW_MASK, 0) +#define ADIS16575_OVERWRITE_OLDEST FIELD_PREP(ADIS16575_OVERFLOW_MASK, 1) +#define ADIS16575_FIFO_EN_MASK BIT(0) +#define ADIS16575_FIFO_EN(x) FIELD_PREP(ADIS16575_FIFO_EN_MASK, x) +#define ADIS16575_FIFO_FLUSH_CMD BIT(5) +#define ADIS16575_REG_FIFO_CNT 0x3C + +enum { + ADIS16475_SYNC_DIRECT = 1, + ADIS16475_SYNC_SCALED, + ADIS16475_SYNC_OUTPUT, + ADIS16475_SYNC_PULSE = 5, +}; + +struct adis16475_sync { + u16 sync_mode; + u16 min_rate; + u16 max_rate; +}; + +struct adis16475_chip_info { + const struct iio_chan_spec *channels; + const struct adis16475_sync *sync; + const struct adis_data adis_data; + const char *name; +#define ADIS16475_HAS_BURST32 BIT(0) +#define ADIS16475_HAS_BURST_DELTA_DATA BIT(1) +#define ADIS16475_HAS_TIMESTAMP32 BIT(2) +#define ADIS16475_NEEDS_BURST_REQUEST BIT(3) + const long flags; + u32 num_channels; + u32 gyro_max_val; + u32 gyro_max_scale; + u32 accel_max_val; + u32 accel_max_scale; + u32 temp_scale; + u32 deltang_max_val; + u32 deltvel_max_val; + u32 int_clk; + u16 max_dec; + u8 num_sync; +}; + +struct adis16475 { + const struct adis16475_chip_info *info; + struct adis adis; + u32 clk_freq; + bool burst32; + unsigned long lsb_flag; + u16 sync_mode; + u16 fifo_watermark; + /* Alignment needed for the timestamp */ + __be16 data[ADIS16475_MAX_SCAN_DATA] __aligned(8); +}; + +enum { + ADIS16475_SCAN_GYRO_X, + ADIS16475_SCAN_GYRO_Y, + ADIS16475_SCAN_GYRO_Z, + ADIS16475_SCAN_ACCEL_X, + ADIS16475_SCAN_ACCEL_Y, + ADIS16475_SCAN_ACCEL_Z, + ADIS16475_SCAN_TEMP, + ADIS16475_SCAN_DELTANG_X, + ADIS16475_SCAN_DELTANG_Y, + ADIS16475_SCAN_DELTANG_Z, + ADIS16475_SCAN_DELTVEL_X, + ADIS16475_SCAN_DELTVEL_Y, + ADIS16475_SCAN_DELTVEL_Z, +}; + +static bool low_rate_allow; +module_param(low_rate_allow, bool, 0444); +MODULE_PARM_DESC(low_rate_allow, + "Allow IMU rates below the minimum advisable when external clk is used in SCALED mode (default: N)"); + +static ssize_t adis16475_show_firmware_revision(struct file *file, + char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct adis16475 *st = file->private_data; + char buf[7]; + size_t len; + u16 rev; + int ret; + + ret = adis_read_reg_16(&st->adis, ADIS16475_REG_FIRM_REV, &rev); + if (ret) + return ret; + + len = scnprintf(buf, sizeof(buf), "%x.%x\n", rev >> 8, rev & 0xff); + + return simple_read_from_buffer(userbuf, count, ppos, buf, len); +} + +static const struct file_operations adis16475_firmware_revision_fops = { + .open = simple_open, + .read = adis16475_show_firmware_revision, + .llseek = default_llseek, + .owner = THIS_MODULE, +}; + +static ssize_t adis16475_show_firmware_date(struct file *file, + char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct adis16475 *st = file->private_data; + u16 md, year; + char buf[12]; + size_t len; + int ret; + + ret = adis_read_reg_16(&st->adis, ADIS16475_REG_FIRM_Y, &year); + if (ret) + return ret; + + ret = adis_read_reg_16(&st->adis, ADIS16475_REG_FIRM_DM, &md); + if (ret) + return ret; + + len = snprintf(buf, sizeof(buf), "%.2x-%.2x-%.4x\n", md >> 8, md & 0xff, + year); + + return simple_read_from_buffer(userbuf, count, ppos, buf, len); +} + +static const struct file_operations adis16475_firmware_date_fops = { + .open = simple_open, + .read = adis16475_show_firmware_date, + .llseek = default_llseek, + .owner = THIS_MODULE, +}; + +static int adis16475_show_serial_number(void *arg, u64 *val) +{ + struct adis16475 *st = arg; + u16 serial; + int ret; + + ret = adis_read_reg_16(&st->adis, ADIS16475_REG_SERIAL_NUM, &serial); + if (ret) + return ret; + + *val = serial; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(adis16475_serial_number_fops, + adis16475_show_serial_number, NULL, "0x%.4llx\n"); + +static int adis16475_show_product_id(void *arg, u64 *val) +{ + struct adis16475 *st = arg; + u16 prod_id; + int ret; + + ret = adis_read_reg_16(&st->adis, ADIS16475_REG_PROD_ID, &prod_id); + if (ret) + return ret; + + *val = prod_id; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(adis16475_product_id_fops, + adis16475_show_product_id, NULL, "%llu\n"); + +static int adis16475_show_flash_count(void *arg, u64 *val) +{ + struct adis16475 *st = arg; + u32 flash_count; + int ret; + + ret = adis_read_reg_32(&st->adis, ADIS16475_REG_FLASH_CNT, + &flash_count); + if (ret) + return ret; + + *val = flash_count; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(adis16475_flash_count_fops, + adis16475_show_flash_count, NULL, "%lld\n"); + +static void adis16475_debugfs_init(struct iio_dev *indio_dev) +{ + struct adis16475 *st = iio_priv(indio_dev); + struct dentry *d = iio_get_debugfs_dentry(indio_dev); + + if (!IS_ENABLED(CONFIG_DEBUG_FS)) + return; + + debugfs_create_file_unsafe("serial_number", 0400, + d, st, &adis16475_serial_number_fops); + debugfs_create_file_unsafe("product_id", 0400, + d, st, &adis16475_product_id_fops); + debugfs_create_file_unsafe("flash_count", 0400, + d, st, &adis16475_flash_count_fops); + debugfs_create_file("firmware_revision", 0400, + d, st, &adis16475_firmware_revision_fops); + debugfs_create_file("firmware_date", 0400, d, + st, &adis16475_firmware_date_fops); +} + +static int adis16475_get_freq(struct adis16475 *st, u32 *freq) +{ + int ret; + u16 dec; + u32 sample_rate = st->clk_freq; + + adis_dev_auto_lock(&st->adis); + + if (st->sync_mode == ADIS16475_SYNC_SCALED) { + u16 sync_scale; + + ret = __adis_read_reg_16(&st->adis, ADIS16475_REG_UP_SCALE, &sync_scale); + if (ret) + return ret; + + sample_rate = st->clk_freq * sync_scale; + } + + ret = __adis_read_reg_16(&st->adis, ADIS16475_REG_DEC_RATE, &dec); + if (ret) + return ret; + + *freq = DIV_ROUND_CLOSEST(sample_rate, dec + 1); + + return 0; +} + +static int adis16475_set_freq(struct adis16475 *st, const u32 freq) +{ + u16 dec; + int ret; + u32 sample_rate = st->clk_freq; + /* The optimal sample rate for the supported IMUs is between int_clk - 100 and int_clk + 100. */ + u32 max_sample_rate = st->info->int_clk * 1000 + 100000; + u32 min_sample_rate = st->info->int_clk * 1000 - 100000; + + if (!freq) + return -EINVAL; + + adis_dev_auto_lock(&st->adis); + /* + * When using sync scaled mode, the input clock needs to be scaled so that we have + * an IMU sample rate between (optimally) int_clk - 100 and int_clk + 100. + * After this, we can use the decimation filter to lower the sampling rate in order + * to get what the user wants. + * Optimally, the user sample rate is a multiple of both the IMU sample rate and + * the input clock. Hence, calculating the sync_scale dynamically gives us better + * chances of achieving a perfect/integer value for DEC_RATE. The math here is: + * 1. lcm of the input clock and the desired output rate. + * 2. get the highest multiple of the previous result lower than the adis max rate. + * 3. The last result becomes the IMU sample rate. Use that to calculate SYNC_SCALE + * and DEC_RATE (to get the user output rate) + */ + if (st->sync_mode == ADIS16475_SYNC_SCALED) { + unsigned long scaled_rate = lcm(st->clk_freq, freq); + int sync_scale; + + /* + * If lcm is bigger than the IMU maximum sampling rate there's no perfect + * solution. In this case, we get the highest multiple of the input clock + * lower than the IMU max sample rate. + */ + if (scaled_rate > max_sample_rate) + scaled_rate = max_sample_rate / st->clk_freq * st->clk_freq; + else + scaled_rate = max_sample_rate / scaled_rate * scaled_rate; + + /* + * This is not an hard requirement but it's not advised to run the IMU + * with a sample rate lower than internal clock frequency, due to possible + * undersampling issues. However, there are users that might really want + * to take the risk. Hence, we provide a module parameter for them. If set, + * we allow sample rates lower than internal clock frequency. + * By default, we won't allow this and we just roundup the rate to the next + * multiple of the input clock. This is done like this as in some cases + * (when DEC_RATE is 0) might give us the closest value to the one desired + * by the user... + */ + if (scaled_rate < min_sample_rate && !low_rate_allow) + scaled_rate = roundup(min_sample_rate, st->clk_freq); + + sync_scale = scaled_rate / st->clk_freq; + ret = __adis_write_reg_16(&st->adis, ADIS16475_REG_UP_SCALE, sync_scale); + if (ret) + return ret; + + sample_rate = scaled_rate; + } + + dec = DIV_ROUND_CLOSEST(sample_rate, freq); + + if (dec) + dec--; + + if (dec > st->info->max_dec) + dec = st->info->max_dec; + + ret = __adis_write_reg_16(&st->adis, ADIS16475_REG_DEC_RATE, dec); + if (ret) + return ret; + + /* + * If decimation is used, then gyro and accel data will have meaningful + * bits on the LSB registers. This info is used on the trigger handler. + */ + assign_bit(ADIS16475_LSB_DEC_MASK, &st->lsb_flag, dec); + + return 0; +} + +/* The values are approximated. */ +static const u32 adis16475_3db_freqs[] = { + [0] = 720, /* Filter disabled, full BW (~720Hz) */ + [1] = 360, + [2] = 164, + [3] = 80, + [4] = 40, + [5] = 20, + [6] = 10, +}; + +static int adis16475_get_filter(struct adis16475 *st, u32 *filter) +{ + u16 filter_sz; + int ret; + const int mask = ADIS16475_FILT_CTRL_MASK; + + ret = adis_read_reg_16(&st->adis, ADIS16475_REG_FILT_CTRL, &filter_sz); + if (ret) + return ret; + + *filter = adis16475_3db_freqs[filter_sz & mask]; + + return 0; +} + +static int adis16475_set_filter(struct adis16475 *st, const u32 filter) +{ + int i = ARRAY_SIZE(adis16475_3db_freqs); + int ret; + + while (--i) { + if (adis16475_3db_freqs[i] >= filter) + break; + } + + ret = adis_write_reg_16(&st->adis, ADIS16475_REG_FILT_CTRL, + ADIS16475_FILT_CTRL(i)); + if (ret) + return ret; + + /* + * If FIR is used, then gyro and accel data will have meaningful + * bits on the LSB registers. This info is used on the trigger handler. + */ + assign_bit(ADIS16475_LSB_FIR_MASK, &st->lsb_flag, i); + + return 0; +} + +static ssize_t adis16475_get_fifo_enabled(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct adis16475 *st = iio_priv(indio_dev); + int ret; + u16 val; + + ret = adis_read_reg_16(&st->adis, ADIS16475_REG_FIFO_CTRL, &val); + if (ret) + return ret; + + return sysfs_emit(buf, "%lu\n", FIELD_GET(ADIS16575_FIFO_EN_MASK, val)); +} + +static ssize_t adis16475_get_fifo_watermark(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct adis16475 *st = iio_priv(indio_dev); + int ret; + u16 val; + + ret = adis_read_reg_16(&st->adis, ADIS16475_REG_FIFO_CTRL, &val); + if (ret) + return ret; + + return sysfs_emit(buf, "%lu\n", FIELD_GET(ADIS16575_WM_LVL_MASK, val) + 1); +} + +static ssize_t hwfifo_watermark_min_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "1\n"); +} + +static ssize_t hwfifo_watermark_max_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%lu\n", ADIS16575_MAX_FIFO_WM); +} + +static IIO_DEVICE_ATTR_RO(hwfifo_watermark_min, 0); +static IIO_DEVICE_ATTR_RO(hwfifo_watermark_max, 0); +static IIO_DEVICE_ATTR(hwfifo_watermark, 0444, + adis16475_get_fifo_watermark, NULL, 0); +static IIO_DEVICE_ATTR(hwfifo_enabled, 0444, + adis16475_get_fifo_enabled, NULL, 0); + +static const struct iio_dev_attr *adis16475_fifo_attributes[] = { + &iio_dev_attr_hwfifo_watermark_min, + &iio_dev_attr_hwfifo_watermark_max, + &iio_dev_attr_hwfifo_watermark, + &iio_dev_attr_hwfifo_enabled, + NULL +}; + +static int adis16475_buffer_postenable(struct iio_dev *indio_dev) +{ + struct adis16475 *st = iio_priv(indio_dev); + struct adis *adis = &st->adis; + + return adis_update_bits(adis, ADIS16475_REG_FIFO_CTRL, + ADIS16575_FIFO_EN_MASK, (u16)ADIS16575_FIFO_EN(1)); +} + +static int adis16475_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct adis16475 *st = iio_priv(indio_dev); + struct adis *adis = &st->adis; + int ret; + + adis_dev_auto_lock(&st->adis); + + ret = __adis_update_bits(adis, ADIS16475_REG_FIFO_CTRL, + ADIS16575_FIFO_EN_MASK, (u16)ADIS16575_FIFO_EN(0)); + if (ret) + return ret; + + return __adis_write_reg_16(adis, ADIS16475_REG_GLOB_CMD, + ADIS16575_FIFO_FLUSH_CMD); +} + +static const struct iio_buffer_setup_ops adis16475_buffer_ops = { + .postenable = adis16475_buffer_postenable, + .postdisable = adis16475_buffer_postdisable, +}; + +static int adis16475_set_watermark(struct iio_dev *indio_dev, unsigned int val) +{ + struct adis16475 *st = iio_priv(indio_dev); + int ret; + u16 wm_lvl; + + adis_dev_auto_lock(&st->adis); + + val = min_t(unsigned int, val, ADIS16575_MAX_FIFO_WM); + + wm_lvl = ADIS16575_WM_LVL(val - 1); + ret = __adis_update_bits(&st->adis, ADIS16475_REG_FIFO_CTRL, ADIS16575_WM_LVL_MASK, wm_lvl); + if (ret) + return ret; + + st->fifo_watermark = val; + + return 0; +} + +static const u32 adis16475_calib_regs[] = { + [ADIS16475_SCAN_GYRO_X] = ADIS16475_REG_X_GYRO_BIAS_L, + [ADIS16475_SCAN_GYRO_Y] = ADIS16475_REG_Y_GYRO_BIAS_L, + [ADIS16475_SCAN_GYRO_Z] = ADIS16475_REG_Z_GYRO_BIAS_L, + [ADIS16475_SCAN_ACCEL_X] = ADIS16475_REG_X_ACCEL_BIAS_L, + [ADIS16475_SCAN_ACCEL_Y] = ADIS16475_REG_Y_ACCEL_BIAS_L, + [ADIS16475_SCAN_ACCEL_Z] = ADIS16475_REG_Z_ACCEL_BIAS_L, +}; + +static int adis16475_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long info) +{ + struct adis16475 *st = iio_priv(indio_dev); + int ret; + u32 tmp; + + switch (info) { + case IIO_CHAN_INFO_RAW: + return adis_single_conversion(indio_dev, chan, 0, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = st->info->gyro_max_val; + *val2 = st->info->gyro_max_scale; + return IIO_VAL_FRACTIONAL; + case IIO_ACCEL: + *val = st->info->accel_max_val; + *val2 = st->info->accel_max_scale; + return IIO_VAL_FRACTIONAL; + case IIO_TEMP: + *val = st->info->temp_scale; + return IIO_VAL_INT; + case IIO_DELTA_ANGL: + *val = st->info->deltang_max_val; + *val2 = 31; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_DELTA_VELOCITY: + *val = st->info->deltvel_max_val; + *val2 = 31; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + ret = adis_read_reg_32(&st->adis, + adis16475_calib_regs[chan->scan_index], + val); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + ret = adis16475_get_filter(st, val); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = adis16475_get_freq(st, &tmp); + if (ret) + return ret; + + *val = tmp / 1000; + *val2 = (tmp % 1000) * 1000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int adis16475_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int val, int val2, long info) +{ + struct adis16475 *st = iio_priv(indio_dev); + u32 tmp; + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + tmp = val * 1000 + val2 / 1000; + return adis16475_set_freq(st, tmp); + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return adis16475_set_filter(st, val); + case IIO_CHAN_INFO_CALIBBIAS: + return adis_write_reg_32(&st->adis, + adis16475_calib_regs[chan->scan_index], + val); + default: + return -EINVAL; + } +} + +#define ADIS16475_MOD_CHAN(_type, _mod, _address, _si, _r_bits, _s_bits) \ + { \ + .type = (_type), \ + .modified = 1, \ + .channel2 = (_mod), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .address = (_address), \ + .scan_index = (_si), \ + .scan_type = { \ + .sign = 's', \ + .realbits = (_r_bits), \ + .storagebits = (_s_bits), \ + .endianness = IIO_BE, \ + }, \ + } + +#define ADIS16475_GYRO_CHANNEL(_mod) \ + ADIS16475_MOD_CHAN(IIO_ANGL_VEL, IIO_MOD_ ## _mod, \ + ADIS16475_REG_ ## _mod ## _GYRO_L, \ + ADIS16475_SCAN_GYRO_ ## _mod, 32, 32) + +#define ADIS16475_ACCEL_CHANNEL(_mod) \ + ADIS16475_MOD_CHAN(IIO_ACCEL, IIO_MOD_ ## _mod, \ + ADIS16475_REG_ ## _mod ## _ACCEL_L, \ + ADIS16475_SCAN_ACCEL_ ## _mod, 32, 32) + +#define ADIS16475_TEMP_CHANNEL() { \ + .type = IIO_TEMP, \ + .indexed = 1, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .address = ADIS16475_REG_TEMP_OUT, \ + .scan_index = ADIS16475_SCAN_TEMP, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + } + +#define ADIS16475_MOD_CHAN_DELTA(_type, _mod, _address, _si, _r_bits, _s_bits) { \ + .type = (_type), \ + .modified = 1, \ + .channel2 = (_mod), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .address = (_address), \ + .scan_index = _si, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (_r_bits), \ + .storagebits = (_s_bits), \ + .endianness = IIO_BE, \ + }, \ + } + +#define ADIS16475_DELTANG_CHAN(_mod) \ + ADIS16475_MOD_CHAN_DELTA(IIO_DELTA_ANGL, IIO_MOD_ ## _mod, \ + ADIS16475_REG_ ## _mod ## _DELTANG_L, ADIS16475_SCAN_DELTANG_ ## _mod, 32, 32) + +#define ADIS16475_DELTVEL_CHAN(_mod) \ + ADIS16475_MOD_CHAN_DELTA(IIO_DELTA_VELOCITY, IIO_MOD_ ## _mod, \ + ADIS16475_REG_ ## _mod ## _DELTVEL_L, ADIS16475_SCAN_DELTVEL_ ## _mod, 32, 32) + +#define ADIS16475_DELTANG_CHAN_NO_SCAN(_mod) \ + ADIS16475_MOD_CHAN_DELTA(IIO_DELTA_ANGL, IIO_MOD_ ## _mod, \ + ADIS16475_REG_ ## _mod ## _DELTANG_L, -1, 32, 32) + +#define ADIS16475_DELTVEL_CHAN_NO_SCAN(_mod) \ + ADIS16475_MOD_CHAN_DELTA(IIO_DELTA_VELOCITY, IIO_MOD_ ## _mod, \ + ADIS16475_REG_ ## _mod ## _DELTVEL_L, -1, 32, 32) + +static const struct iio_chan_spec adis16477_channels[] = { + ADIS16475_GYRO_CHANNEL(X), + ADIS16475_GYRO_CHANNEL(Y), + ADIS16475_GYRO_CHANNEL(Z), + ADIS16475_ACCEL_CHANNEL(X), + ADIS16475_ACCEL_CHANNEL(Y), + ADIS16475_ACCEL_CHANNEL(Z), + ADIS16475_TEMP_CHANNEL(), + ADIS16475_DELTANG_CHAN(X), + ADIS16475_DELTANG_CHAN(Y), + ADIS16475_DELTANG_CHAN(Z), + ADIS16475_DELTVEL_CHAN(X), + ADIS16475_DELTVEL_CHAN(Y), + ADIS16475_DELTVEL_CHAN(Z), + IIO_CHAN_SOFT_TIMESTAMP(13) +}; + +static const struct iio_chan_spec adis16475_channels[] = { + ADIS16475_GYRO_CHANNEL(X), + ADIS16475_GYRO_CHANNEL(Y), + ADIS16475_GYRO_CHANNEL(Z), + ADIS16475_ACCEL_CHANNEL(X), + ADIS16475_ACCEL_CHANNEL(Y), + ADIS16475_ACCEL_CHANNEL(Z), + ADIS16475_TEMP_CHANNEL(), + ADIS16475_DELTANG_CHAN_NO_SCAN(X), + ADIS16475_DELTANG_CHAN_NO_SCAN(Y), + ADIS16475_DELTANG_CHAN_NO_SCAN(Z), + ADIS16475_DELTVEL_CHAN_NO_SCAN(X), + ADIS16475_DELTVEL_CHAN_NO_SCAN(Y), + ADIS16475_DELTVEL_CHAN_NO_SCAN(Z), + IIO_CHAN_SOFT_TIMESTAMP(7) +}; + +static const struct iio_chan_spec adis16575_channels[] = { + ADIS16475_GYRO_CHANNEL(X), + ADIS16475_GYRO_CHANNEL(Y), + ADIS16475_GYRO_CHANNEL(Z), + ADIS16475_ACCEL_CHANNEL(X), + ADIS16475_ACCEL_CHANNEL(Y), + ADIS16475_ACCEL_CHANNEL(Z), + ADIS16475_TEMP_CHANNEL(), + ADIS16475_DELTANG_CHAN(X), + ADIS16475_DELTANG_CHAN(Y), + ADIS16475_DELTANG_CHAN(Z), + ADIS16475_DELTVEL_CHAN(X), + ADIS16475_DELTVEL_CHAN(Y), + ADIS16475_DELTVEL_CHAN(Z), +}; + +enum adis16475_variant { + ADIS16470, + ADIS16475_1, + ADIS16475_2, + ADIS16475_3, + ADIS16477_1, + ADIS16477_2, + ADIS16477_3, + ADIS16465_1, + ADIS16465_2, + ADIS16465_3, + ADIS16467_1, + ADIS16467_2, + ADIS16467_3, + ADIS16500, + ADIS16501, + ADIS16505_1, + ADIS16505_2, + ADIS16505_3, + ADIS16507_1, + ADIS16507_2, + ADIS16507_3, + ADIS16575_2, + ADIS16575_3, + ADIS16576_2, + ADIS16576_3, + ADIS16577_2, + ADIS16577_3, +}; + +enum { + ADIS16475_DIAG_STAT_DATA_PATH = 1, + ADIS16475_DIAG_STAT_FLASH_MEM, + ADIS16475_DIAG_STAT_SPI, + ADIS16475_DIAG_STAT_STANDBY, + ADIS16475_DIAG_STAT_SENSOR, + ADIS16475_DIAG_STAT_MEMORY, + ADIS16475_DIAG_STAT_CLK, +}; + +static const char * const adis16475_status_error_msgs[] = { + [ADIS16475_DIAG_STAT_DATA_PATH] = "Data Path Overrun", + [ADIS16475_DIAG_STAT_FLASH_MEM] = "Flash memory update failure", + [ADIS16475_DIAG_STAT_SPI] = "SPI communication error", + [ADIS16475_DIAG_STAT_STANDBY] = "Standby mode", + [ADIS16475_DIAG_STAT_SENSOR] = "Sensor failure", + [ADIS16475_DIAG_STAT_MEMORY] = "Memory failure", + [ADIS16475_DIAG_STAT_CLK] = "Clock error", +}; + +#define ADIS16475_DATA(_prod_id, _timeouts, _burst_max_len, _burst_max_speed_hz, _has_fifo) \ +{ \ + .msc_ctrl_reg = ADIS16475_REG_MSG_CTRL, \ + .glob_cmd_reg = ADIS16475_REG_GLOB_CMD, \ + .diag_stat_reg = ADIS16475_REG_DIAG_STAT, \ + .prod_id_reg = ADIS16475_REG_PROD_ID, \ + .prod_id = (_prod_id), \ + .self_test_mask = BIT(2), \ + .self_test_reg = ADIS16475_REG_GLOB_CMD, \ + .cs_change_delay = 16, \ + .read_delay = 5, \ + .write_delay = 5, \ + .status_error_msgs = adis16475_status_error_msgs, \ + .status_error_mask = BIT(ADIS16475_DIAG_STAT_DATA_PATH) | \ + BIT(ADIS16475_DIAG_STAT_FLASH_MEM) | \ + BIT(ADIS16475_DIAG_STAT_SPI) | \ + BIT(ADIS16475_DIAG_STAT_STANDBY) | \ + BIT(ADIS16475_DIAG_STAT_SENSOR) | \ + BIT(ADIS16475_DIAG_STAT_MEMORY) | \ + BIT(ADIS16475_DIAG_STAT_CLK), \ + .unmasked_drdy = true, \ + .has_fifo = _has_fifo, \ + .timeouts = (_timeouts), \ + .burst_reg_cmd = ADIS16475_REG_GLOB_CMD, \ + .burst_len = ADIS16475_BURST_MAX_DATA, \ + .burst_max_len = _burst_max_len, \ + .burst_max_speed_hz = _burst_max_speed_hz \ +} + +static const struct adis16475_sync adis16475_sync_mode[] = { + { ADIS16475_SYNC_OUTPUT }, + { ADIS16475_SYNC_DIRECT, 1900, 2100 }, + { ADIS16475_SYNC_SCALED, 1, 128 }, + { ADIS16475_SYNC_PULSE, 1000, 2100 }, +}; + +static const struct adis16475_sync adis16575_sync_mode[] = { + { ADIS16475_SYNC_OUTPUT }, + { ADIS16475_SYNC_DIRECT, 1900, 4100 }, + { ADIS16475_SYNC_SCALED, 1, 400 }, +}; + +static const struct adis_timeout adis16475_timeouts = { + .reset_ms = 200, + .sw_reset_ms = 200, + .self_test_ms = 20, +}; + +static const struct adis_timeout adis1650x_timeouts = { + .reset_ms = 260, + .sw_reset_ms = 260, + .self_test_ms = 30, +}; + +static const struct adis16475_chip_info adis16475_chip_info[] = { + [ADIS16470] = { + .name = "adis16470", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(10 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(800 << 16), + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(2160), + .deltvel_max_val = 400, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .adis_data = ADIS16475_DATA(16470, &adis16475_timeouts, + ADIS16475_BURST32_MAX_DATA_NO_TS32, + ADIS16475_BURST_MAX_SPEED, false), + }, + [ADIS16475_1] = { + .name = "adis16475-1", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(160 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(4000 << 16), + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(360), + .deltvel_max_val = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .adis_data = ADIS16475_DATA(16475, &adis16475_timeouts, + ADIS16475_BURST32_MAX_DATA_NO_TS32, + ADIS16475_BURST_MAX_SPEED, false), + }, + [ADIS16475_2] = { + .name = "adis16475-2", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(40 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(4000 << 16), + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(720), + .deltvel_max_val = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .adis_data = ADIS16475_DATA(16475, &adis16475_timeouts, + ADIS16475_BURST32_MAX_DATA_NO_TS32, + ADIS16475_BURST_MAX_SPEED, false), + }, + [ADIS16475_3] = { + .name = "adis16475-3", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(10 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(4000 << 16), + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(2160), + .deltvel_max_val = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .adis_data = ADIS16475_DATA(16475, &adis16475_timeouts, + ADIS16475_BURST32_MAX_DATA_NO_TS32, + ADIS16475_BURST_MAX_SPEED, false), + }, + [ADIS16477_1] = { + .name = "adis16477-1", + .num_channels = ARRAY_SIZE(adis16477_channels), + .channels = adis16477_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(160 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(800 << 16), + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(360), + .deltvel_max_val = 400, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .flags = ADIS16475_HAS_BURST32 | ADIS16475_HAS_BURST_DELTA_DATA, + .adis_data = ADIS16475_DATA(16477, &adis16475_timeouts, + ADIS16475_BURST32_MAX_DATA_NO_TS32, + ADIS16475_BURST_MAX_SPEED, false), + }, + [ADIS16477_2] = { + .name = "adis16477-2", + .num_channels = ARRAY_SIZE(adis16477_channels), + .channels = adis16477_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(40 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(800 << 16), + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(720), + .deltvel_max_val = 400, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .flags = ADIS16475_HAS_BURST32 | ADIS16475_HAS_BURST_DELTA_DATA, + .adis_data = ADIS16475_DATA(16477, &adis16475_timeouts, + ADIS16475_BURST32_MAX_DATA_NO_TS32, + ADIS16475_BURST_MAX_SPEED, false), + }, + [ADIS16477_3] = { + .name = "adis16477-3", + .num_channels = ARRAY_SIZE(adis16477_channels), + .channels = adis16477_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(10 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(800 << 16), + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(2160), + .deltvel_max_val = 400, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .flags = ADIS16475_HAS_BURST32 | ADIS16475_HAS_BURST_DELTA_DATA, + .adis_data = ADIS16475_DATA(16477, &adis16475_timeouts, + ADIS16475_BURST32_MAX_DATA_NO_TS32, + ADIS16475_BURST_MAX_SPEED, false), + }, + [ADIS16465_1] = { + .name = "adis16465-1", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(160 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(4000 << 16), + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(360), + .deltvel_max_val = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .adis_data = ADIS16475_DATA(16465, &adis16475_timeouts, + ADIS16475_BURST32_MAX_DATA_NO_TS32, + ADIS16475_BURST_MAX_SPEED, false), + }, + [ADIS16465_2] = { + .name = "adis16465-2", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(40 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(4000 << 16), + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(720), + .deltvel_max_val = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .adis_data = ADIS16475_DATA(16465, &adis16475_timeouts, + ADIS16475_BURST32_MAX_DATA_NO_TS32, + ADIS16475_BURST_MAX_SPEED, false), + }, + [ADIS16465_3] = { + .name = "adis16465-3", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(10 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(4000 << 16), + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(2160), + .deltvel_max_val = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .adis_data = ADIS16475_DATA(16465, &adis16475_timeouts, + ADIS16475_BURST32_MAX_DATA_NO_TS32, + ADIS16475_BURST_MAX_SPEED, false), + }, + [ADIS16467_1] = { + .name = "adis16467-1", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(160 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(800 << 16), + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(360), + .deltvel_max_val = 400, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .adis_data = ADIS16475_DATA(16467, &adis16475_timeouts, + ADIS16475_BURST32_MAX_DATA_NO_TS32, + ADIS16475_BURST_MAX_SPEED, false), + }, + [ADIS16467_2] = { + .name = "adis16467-2", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(40 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(800 << 16), + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(720), + .deltvel_max_val = 400, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .adis_data = ADIS16475_DATA(16467, &adis16475_timeouts, + ADIS16475_BURST32_MAX_DATA_NO_TS32, + ADIS16475_BURST_MAX_SPEED, false), + }, + [ADIS16467_3] = { + .name = "adis16467-3", + .num_channels = ARRAY_SIZE(adis16475_channels), + .channels = adis16475_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(10 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(800 << 16), + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(2160), + .deltvel_max_val = 400, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + .num_sync = ARRAY_SIZE(adis16475_sync_mode), + .adis_data = ADIS16475_DATA(16467, &adis16475_timeouts, + ADIS16475_BURST32_MAX_DATA_NO_TS32, + ADIS16475_BURST_MAX_SPEED, false), + }, + [ADIS16500] = { + .name = "adis16500", + .num_channels = ARRAY_SIZE(adis16477_channels), + .channels = adis16477_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(10 << 16), + .accel_max_val = 392, + .accel_max_scale = 32000 << 16, + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(2160), + .deltvel_max_val = 400, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + /* pulse sync not supported */ + .num_sync = ARRAY_SIZE(adis16475_sync_mode) - 1, + .flags = ADIS16475_HAS_BURST32 | ADIS16475_HAS_BURST_DELTA_DATA, + .adis_data = ADIS16475_DATA(16500, &adis1650x_timeouts, + ADIS16475_BURST32_MAX_DATA_NO_TS32, + ADIS16475_BURST_MAX_SPEED, false), + }, + [ADIS16501] = { + .name = "adis16501", + .num_channels = ARRAY_SIZE(adis16477_channels), + .channels = adis16477_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(40 << 16), + .accel_max_val = 1, + .accel_max_scale = IIO_M_S_2_TO_G(800 << 16), + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(720), + .deltvel_max_val = 125, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + /* pulse sync not supported */ + .num_sync = ARRAY_SIZE(adis16475_sync_mode) - 1, + .flags = ADIS16475_HAS_BURST32 | ADIS16475_HAS_BURST_DELTA_DATA, + .adis_data = ADIS16475_DATA(16501, &adis1650x_timeouts, + ADIS16475_BURST32_MAX_DATA_NO_TS32, + ADIS16475_BURST_MAX_SPEED, false), + }, + [ADIS16505_1] = { + .name = "adis16505-1", + .num_channels = ARRAY_SIZE(adis16477_channels), + .channels = adis16477_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(160 << 16), + .accel_max_val = 78, + .accel_max_scale = 32000 << 16, + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(360), + .deltvel_max_val = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + /* pulse sync not supported */ + .num_sync = ARRAY_SIZE(adis16475_sync_mode) - 1, + .flags = ADIS16475_HAS_BURST32 | ADIS16475_HAS_BURST_DELTA_DATA, + .adis_data = ADIS16475_DATA(16505, &adis1650x_timeouts, + ADIS16475_BURST32_MAX_DATA_NO_TS32, + ADIS16475_BURST_MAX_SPEED, false), + }, + [ADIS16505_2] = { + .name = "adis16505-2", + .num_channels = ARRAY_SIZE(adis16477_channels), + .channels = adis16477_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(40 << 16), + .accel_max_val = 78, + .accel_max_scale = 32000 << 16, + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(720), + .deltvel_max_val = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + /* pulse sync not supported */ + .num_sync = ARRAY_SIZE(adis16475_sync_mode) - 1, + .flags = ADIS16475_HAS_BURST32 | ADIS16475_HAS_BURST_DELTA_DATA, + .adis_data = ADIS16475_DATA(16505, &adis1650x_timeouts, + ADIS16475_BURST32_MAX_DATA_NO_TS32, + ADIS16475_BURST_MAX_SPEED, false), + }, + [ADIS16505_3] = { + .name = "adis16505-3", + .num_channels = ARRAY_SIZE(adis16477_channels), + .channels = adis16477_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(10 << 16), + .accel_max_val = 78, + .accel_max_scale = 32000 << 16, + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(2160), + .deltvel_max_val = 100, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + /* pulse sync not supported */ + .num_sync = ARRAY_SIZE(adis16475_sync_mode) - 1, + .flags = ADIS16475_HAS_BURST32 | ADIS16475_HAS_BURST_DELTA_DATA, + .adis_data = ADIS16475_DATA(16505, &adis1650x_timeouts, + ADIS16475_BURST32_MAX_DATA_NO_TS32, + ADIS16475_BURST_MAX_SPEED, false), + }, + [ADIS16507_1] = { + .name = "adis16507-1", + .num_channels = ARRAY_SIZE(adis16477_channels), + .channels = adis16477_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(160 << 16), + .accel_max_val = 392, + .accel_max_scale = 32000 << 16, + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(360), + .deltvel_max_val = 400, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + /* pulse sync not supported */ + .num_sync = ARRAY_SIZE(adis16475_sync_mode) - 1, + .flags = ADIS16475_HAS_BURST32 | ADIS16475_HAS_BURST_DELTA_DATA, + .adis_data = ADIS16475_DATA(16507, &adis1650x_timeouts, + ADIS16475_BURST32_MAX_DATA_NO_TS32, + ADIS16475_BURST_MAX_SPEED, false), + }, + [ADIS16507_2] = { + .name = "adis16507-2", + .num_channels = ARRAY_SIZE(adis16477_channels), + .channels = adis16477_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(40 << 16), + .accel_max_val = 392, + .accel_max_scale = 32000 << 16, + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(720), + .deltvel_max_val = 400, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + /* pulse sync not supported */ + .num_sync = ARRAY_SIZE(adis16475_sync_mode) - 1, + .flags = ADIS16475_HAS_BURST32 | ADIS16475_HAS_BURST_DELTA_DATA, + .adis_data = ADIS16475_DATA(16507, &adis1650x_timeouts, + ADIS16475_BURST32_MAX_DATA_NO_TS32, + ADIS16475_BURST_MAX_SPEED, false), + }, + [ADIS16507_3] = { + .name = "adis16507-3", + .num_channels = ARRAY_SIZE(adis16477_channels), + .channels = adis16477_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(10 << 16), + .accel_max_val = 392, + .accel_max_scale = 32000 << 16, + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(2160), + .deltvel_max_val = 400, + .int_clk = 2000, + .max_dec = 1999, + .sync = adis16475_sync_mode, + /* pulse sync not supported */ + .num_sync = ARRAY_SIZE(adis16475_sync_mode) - 1, + .flags = ADIS16475_HAS_BURST32 | ADIS16475_HAS_BURST_DELTA_DATA, + .adis_data = ADIS16475_DATA(16507, &adis1650x_timeouts, + ADIS16475_BURST32_MAX_DATA_NO_TS32, + ADIS16475_BURST_MAX_SPEED, false), + }, + [ADIS16575_2] = { + .name = "adis16575-2", + .num_channels = ARRAY_SIZE(adis16575_channels), + .channels = adis16575_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(40 << 16), + .accel_max_val = 8, + .accel_max_scale = IIO_M_S_2_TO_G(32000 << 16), + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(450), + .deltvel_max_val = 100, + .int_clk = 4000, + .max_dec = 3999, + .sync = adis16575_sync_mode, + .num_sync = ARRAY_SIZE(adis16575_sync_mode), + .flags = ADIS16475_HAS_BURST32 | + ADIS16475_HAS_BURST_DELTA_DATA | + ADIS16475_NEEDS_BURST_REQUEST | + ADIS16475_HAS_TIMESTAMP32, + .adis_data = ADIS16475_DATA(16575, &adis16475_timeouts, + ADIS16575_BURST32_DATA_TS32, + ADIS16575_BURST_MAX_SPEED, true), + }, + [ADIS16575_3] = { + .name = "adis16575-3", + .num_channels = ARRAY_SIZE(adis16575_channels), + .channels = adis16575_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(10 << 16), + .accel_max_val = 8, + .accel_max_scale = IIO_M_S_2_TO_G(32000 << 16), + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(2000), + .deltvel_max_val = 100, + .int_clk = 4000, + .max_dec = 3999, + .sync = adis16575_sync_mode, + .num_sync = ARRAY_SIZE(adis16575_sync_mode), + .flags = ADIS16475_HAS_BURST32 | + ADIS16475_HAS_BURST_DELTA_DATA | + ADIS16475_NEEDS_BURST_REQUEST | + ADIS16475_HAS_TIMESTAMP32, + .adis_data = ADIS16475_DATA(16575, &adis16475_timeouts, + ADIS16575_BURST32_DATA_TS32, + ADIS16575_BURST_MAX_SPEED, true), + }, + [ADIS16576_2] = { + .name = "adis16576-2", + .num_channels = ARRAY_SIZE(adis16575_channels), + .channels = adis16575_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(40 << 16), + .accel_max_val = 40, + .accel_max_scale = IIO_M_S_2_TO_G(32000 << 16), + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(450), + .deltvel_max_val = 125, + .int_clk = 4000, + .max_dec = 3999, + .sync = adis16575_sync_mode, + .num_sync = ARRAY_SIZE(adis16575_sync_mode), + .flags = ADIS16475_HAS_BURST32 | + ADIS16475_HAS_BURST_DELTA_DATA | + ADIS16475_NEEDS_BURST_REQUEST | + ADIS16475_HAS_TIMESTAMP32, + .adis_data = ADIS16475_DATA(16576, &adis16475_timeouts, + ADIS16575_BURST32_DATA_TS32, + ADIS16575_BURST_MAX_SPEED, true), + }, + [ADIS16576_3] = { + .name = "adis16576-3", + .num_channels = ARRAY_SIZE(adis16575_channels), + .channels = adis16575_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(10 << 16), + .accel_max_val = 40, + .accel_max_scale = IIO_M_S_2_TO_G(32000 << 16), + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(2000), + .deltvel_max_val = 125, + .int_clk = 4000, + .max_dec = 3999, + .sync = adis16575_sync_mode, + .num_sync = ARRAY_SIZE(adis16575_sync_mode), + .flags = ADIS16475_HAS_BURST32 | + ADIS16475_HAS_BURST_DELTA_DATA | + ADIS16475_NEEDS_BURST_REQUEST | + ADIS16475_HAS_TIMESTAMP32, + .adis_data = ADIS16475_DATA(16576, &adis16475_timeouts, + ADIS16575_BURST32_DATA_TS32, + ADIS16575_BURST_MAX_SPEED, true), + }, + [ADIS16577_2] = { + .name = "adis16577-2", + .num_channels = ARRAY_SIZE(adis16575_channels), + .channels = adis16575_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(40 << 16), + .accel_max_val = 40, + .accel_max_scale = IIO_M_S_2_TO_G(32000 << 16), + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(450), + .deltvel_max_val = 400, + .int_clk = 4000, + .max_dec = 3999, + .sync = adis16575_sync_mode, + .num_sync = ARRAY_SIZE(adis16575_sync_mode), + .flags = ADIS16475_HAS_BURST32 | + ADIS16475_HAS_BURST_DELTA_DATA | + ADIS16475_NEEDS_BURST_REQUEST | + ADIS16475_HAS_TIMESTAMP32, + .adis_data = ADIS16475_DATA(16577, &adis16475_timeouts, + ADIS16575_BURST32_DATA_TS32, + ADIS16575_BURST_MAX_SPEED, true), + }, + [ADIS16577_3] = { + .name = "adis16577-3", + .num_channels = ARRAY_SIZE(adis16575_channels), + .channels = adis16575_channels, + .gyro_max_val = 1, + .gyro_max_scale = IIO_RAD_TO_DEGREE(10 << 16), + .accel_max_val = 40, + .accel_max_scale = IIO_M_S_2_TO_G(32000 << 16), + .temp_scale = 100, + .deltang_max_val = IIO_DEGREE_TO_RAD(2000), + .deltvel_max_val = 400, + .int_clk = 4000, + .max_dec = 3999, + .sync = adis16575_sync_mode, + .num_sync = ARRAY_SIZE(adis16575_sync_mode), + .flags = ADIS16475_HAS_BURST32 | + ADIS16475_HAS_BURST_DELTA_DATA | + ADIS16475_NEEDS_BURST_REQUEST | + ADIS16475_HAS_TIMESTAMP32, + .adis_data = ADIS16475_DATA(16577, &adis16475_timeouts, + ADIS16575_BURST32_DATA_TS32, + ADIS16575_BURST_MAX_SPEED, true), + }, +}; + +static int adis16475_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + u16 en; + int ret; + struct adis16475 *st = iio_priv(indio_dev); + + if (st->info->flags & ADIS16475_HAS_BURST_DELTA_DATA) { + if ((*scan_mask & ADIS16500_BURST_DATA_SEL_0_CHN_MASK) && + (*scan_mask & ADIS16500_BURST_DATA_SEL_1_CHN_MASK)) + return -EINVAL; + if (*scan_mask & ADIS16500_BURST_DATA_SEL_0_CHN_MASK) + en = FIELD_PREP(ADIS16500_BURST_DATA_SEL_MASK, 0); + else + en = FIELD_PREP(ADIS16500_BURST_DATA_SEL_MASK, 1); + + ret = __adis_update_bits(&st->adis, ADIS16475_REG_MSG_CTRL, + ADIS16500_BURST_DATA_SEL_MASK, en); + if (ret) + return ret; + } + + return adis_update_scan_mode(indio_dev, scan_mask); +} + +static const struct iio_info adis16475_info = { + .read_raw = &adis16475_read_raw, + .write_raw = &adis16475_write_raw, + .update_scan_mode = adis16475_update_scan_mode, + .debugfs_reg_access = adis_debugfs_reg_access, +}; + +static const struct iio_info adis16575_info = { + .read_raw = &adis16475_read_raw, + .write_raw = &adis16475_write_raw, + .update_scan_mode = adis16475_update_scan_mode, + .debugfs_reg_access = adis_debugfs_reg_access, + .hwfifo_set_watermark = adis16475_set_watermark, +}; + +static bool adis16475_validate_crc(const u8 *buffer, u16 crc, + u16 burst_size, u16 start_idx) +{ + int i; + + for (i = start_idx; i < burst_size - 2; i++) + crc -= buffer[i]; + + return crc == 0; +} + +static void adis16475_burst32_check(struct adis16475 *st) +{ + int ret; + struct adis *adis = &st->adis; + u8 timestamp32 = 0; + + if (!(st->info->flags & ADIS16475_HAS_BURST32)) + return; + + if (st->info->flags & ADIS16475_HAS_TIMESTAMP32) + timestamp32 = 1; + + if (st->lsb_flag && !st->burst32) { + const u16 en = ADIS16500_BURST32(1); + + ret = __adis_update_bits(&st->adis, ADIS16475_REG_MSG_CTRL, + ADIS16500_BURST32_MASK, en); + if (ret) + return; + + st->burst32 = true; + + /* + * In 32-bit mode we need extra 2 bytes for all gyro + * and accel channels. + * If the device has 32-bit timestamp value we need 2 extra + * bytes for it. + */ + adis->burst_extra_len = (6 + timestamp32) * sizeof(u16); + adis->xfer[1].len += (6 + timestamp32) * sizeof(u16); + + dev_dbg(&adis->spi->dev, "Enable burst32 mode, xfer:%d", + adis->xfer[1].len); + + } else if (!st->lsb_flag && st->burst32) { + const u16 en = ADIS16500_BURST32(0); + + ret = __adis_update_bits(&st->adis, ADIS16475_REG_MSG_CTRL, + ADIS16500_BURST32_MASK, en); + if (ret) + return; + + st->burst32 = false; + + /* Remove the extra bits */ + adis->burst_extra_len = 0; + adis->xfer[1].len -= (6 + timestamp32) * sizeof(u16); + dev_dbg(&adis->spi->dev, "Disable burst32 mode, xfer:%d\n", + adis->xfer[1].len); + } +} + +static int adis16475_push_single_sample(struct iio_poll_func *pf) +{ + struct iio_dev *indio_dev = pf->indio_dev; + struct adis16475 *st = iio_priv(indio_dev); + struct adis *adis = &st->adis; + int ret, bit, buff_offset = 0, i = 0; + __be16 *buffer; + u16 crc; + bool valid; + u8 crc_offset = 9; + u16 burst_size = ADIS16475_BURST_MAX_DATA; + u16 start_idx = (st->info->flags & ADIS16475_HAS_TIMESTAMP32) ? 2 : 0; + + /* offset until the first element after gyro and accel */ + const u8 offset = st->burst32 ? 13 : 7; + + if (st->burst32) { + crc_offset = (st->info->flags & ADIS16475_HAS_TIMESTAMP32) ? 16 : 15; + burst_size = adis->data->burst_max_len; + } + + ret = spi_sync(adis->spi, &adis->msg); + if (ret) + return ret; + + buffer = adis->buffer; + + crc = be16_to_cpu(buffer[crc_offset]); + valid = adis16475_validate_crc(adis->buffer, crc, burst_size, start_idx); + if (!valid) { + dev_err(&adis->spi->dev, "Invalid crc\n"); + return -EINVAL; + } + + iio_for_each_active_channel(indio_dev, bit) { + /* + * When burst mode is used, system flags is the first data + * channel in the sequence, but the scan index is 7. + */ + switch (bit) { + case ADIS16475_SCAN_TEMP: + st->data[i++] = buffer[offset]; + /* + * The temperature channel has 16-bit storage size. + * We need to perform the padding to have the buffer + * elements naturally aligned in case there are any + * 32-bit storage size channels enabled which have a + * scan index higher than the temperature channel scan + * index. + */ + if (*indio_dev->active_scan_mask & GENMASK(ADIS16475_SCAN_DELTVEL_Z, ADIS16475_SCAN_DELTANG_X)) + st->data[i++] = 0; + break; + case ADIS16475_SCAN_DELTANG_X ... ADIS16475_SCAN_DELTVEL_Z: + buff_offset = ADIS16475_SCAN_DELTANG_X; + fallthrough; + case ADIS16475_SCAN_GYRO_X ... ADIS16475_SCAN_ACCEL_Z: + /* + * The first 2 bytes on the received data are the + * DIAG_STAT reg, hence the +1 offset here... + */ + if (st->burst32) { + /* upper 16 */ + st->data[i++] = buffer[(bit - buff_offset) * 2 + 2]; + /* lower 16 */ + st->data[i++] = buffer[(bit - buff_offset) * 2 + 1]; + } else { + st->data[i++] = buffer[(bit - buff_offset) + 1]; + /* + * Don't bother in doing the manual read if the + * device supports burst32. burst32 will be + * enabled in the next call to + * adis16475_burst32_check()... + */ + if (st->lsb_flag && !(st->info->flags & ADIS16475_HAS_BURST32)) { + u16 val = 0; + const u32 reg = ADIS16475_REG_X_GYRO_L + + bit * 4; + + adis_read_reg_16(adis, reg, &val); + st->data[i++] = cpu_to_be16(val); + } else { + /* lower not used */ + st->data[i++] = 0; + } + } + break; + } + } + + /* There might not be a timestamp option for some devices. */ + iio_push_to_buffers_with_timestamp(indio_dev, st->data, pf->timestamp); + + return 0; +} + +static irqreturn_t adis16475_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adis16475 *st = iio_priv(indio_dev); + + adis16475_push_single_sample(pf); + /* + * We only check the burst mode at the end of the current capture since + * it takes a full data ready cycle for the device to update the burst + * array. + */ + adis16475_burst32_check(st); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +/* + * This function updates the first tx byte from the adis message based on the + * given burst request. + */ +static void adis16575_update_msg_for_burst(struct adis *adis, u8 burst_req) +{ + unsigned int burst_max_length; + u8 *tx; + + if (adis->data->burst_max_len) + burst_max_length = adis->data->burst_max_len; + else + burst_max_length = adis->data->burst_len + adis->burst_extra_len; + + tx = adis->buffer + burst_max_length; + tx[0] = ADIS_READ_REG(burst_req); +} + +static int adis16575_custom_burst_read(struct iio_poll_func *pf, u8 burst_req) +{ + struct iio_dev *indio_dev = pf->indio_dev; + struct adis16475 *st = iio_priv(indio_dev); + struct adis *adis = &st->adis; + + adis16575_update_msg_for_burst(adis, burst_req); + + if (burst_req) + return spi_sync(adis->spi, &adis->msg); + + return adis16475_push_single_sample(pf); +} + +/* + * This handler is meant to be used for devices which support burst readings + * from FIFO (namely devices from adis1657x family). + * In order to pop the FIFO the 0x68 0x00 FIFO pop burst request has to be sent. + * If the previous device command was not a FIFO pop burst request, the FIFO pop + * burst request will simply pop the FIFO without returning valid data. + * For the nth consecutive burst request, thedevice will send the data popped + * with the (n-1)th consecutive burst request. + * In order to read the data which was popped previously, without popping the + * FIFO, the 0x00 0x00 burst request has to be sent. + * If after a 0x68 0x00 FIFO pop burst request, there is any other device access + * different from a 0x68 0x00 or a 0x00 0x00 burst request, the FIFO data popped + * previously will be lost. + */ +static irqreturn_t adis16475_trigger_handler_with_fifo(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adis16475 *st = iio_priv(indio_dev); + struct adis *adis = &st->adis; + int ret; + u16 fifo_cnt, i; + + adis_dev_auto_lock(&st->adis); + + ret = __adis_read_reg_16(adis, ADIS16575_REG_FIFO_CNT, &fifo_cnt); + if (ret) + goto unlock; + + /* + * If no sample is available, nothing can be read. This can happen if + * a the used trigger has a higher frequency than the selected sample rate. + */ + if (!fifo_cnt) + goto unlock; + + /* + * First burst request - FIFO pop: popped data will be returned in the + * next burst request. + */ + ret = adis16575_custom_burst_read(pf, adis->data->burst_reg_cmd); + if (ret) + goto unlock; + + for (i = 0; i < fifo_cnt - 1; i++) { + ret = adis16475_push_single_sample(pf); + if (ret) + goto unlock; + } + + /* FIFO read without popping */ + ret = adis16575_custom_burst_read(pf, 0); + +unlock: + /* + * We only check the burst mode at the end of the current capture since + * reading data from registers will impact the FIFO reading. + */ + adis16475_burst32_check(st); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int adis16475_config_sync_mode(struct adis16475 *st) +{ + int ret; + struct device *dev = &st->adis.spi->dev; + const struct adis16475_sync *sync; + u32 sync_mode; + u16 max_sample_rate = st->info->int_clk + 100; + u16 val; + + /* if available, enable 4khz internal clock */ + if (st->info->int_clk == 4000) { + ret = __adis_update_bits(&st->adis, ADIS16475_REG_MSG_CTRL, + ADIS16575_SYNC_4KHZ_MASK, + (u16)ADIS16575_SYNC_4KHZ(1)); + if (ret) + return ret; + } + + /* default to internal clk */ + st->clk_freq = st->info->int_clk * 1000; + + ret = device_property_read_u32(dev, "adi,sync-mode", &sync_mode); + if (ret) + return 0; + + if (sync_mode >= st->info->num_sync) { + dev_err(dev, "Invalid sync mode: %u for %s\n", sync_mode, + st->info->name); + return -EINVAL; + } + + sync = &st->info->sync[sync_mode]; + st->sync_mode = sync->sync_mode; + + /* All the other modes require external input signal */ + if (sync->sync_mode != ADIS16475_SYNC_OUTPUT) { + struct clk *clk = devm_clk_get_enabled(dev, NULL); + + if (IS_ERR(clk)) + return PTR_ERR(clk); + + st->clk_freq = clk_get_rate(clk); + if (st->clk_freq < sync->min_rate || + st->clk_freq > sync->max_rate) { + dev_err(dev, + "Clk rate:%u not in a valid range:[%u %u]\n", + st->clk_freq, sync->min_rate, sync->max_rate); + return -EINVAL; + } + + if (sync->sync_mode == ADIS16475_SYNC_SCALED) { + u16 up_scale; + + /* + * In sync scaled mode, the IMU sample rate is the clk_freq * sync_scale. + * Hence, default the IMU sample rate to the highest multiple of the input + * clock lower than the IMU max sample rate. + */ + up_scale = max_sample_rate / st->clk_freq; + + ret = __adis_write_reg_16(&st->adis, + ADIS16475_REG_UP_SCALE, + up_scale); + if (ret) + return ret; + } + + st->clk_freq *= 1000; + } + /* + * Keep in mind that the mask for the clk modes in adis1650* + * chips is different (1100 instead of 11100). However, we + * are not configuring BIT(4) in these chips and the default + * value is 0, so we are fine in doing the below operations. + * I'm keeping this for simplicity and avoiding extra variables + * in chip_info. + */ + val = ADIS16475_SYNC_MODE(sync->sync_mode); + ret = __adis_update_bits(&st->adis, ADIS16475_REG_MSG_CTRL, + ADIS16475_SYNC_MODE_MASK, val); + if (ret) + return ret; + + usleep_range(250, 260); + + return 0; +} + +static int adis16475_config_irq_pin(struct adis16475 *st) +{ + int ret; + u32 irq_type; + u16 val = 0; + u8 polarity; + struct spi_device *spi = st->adis.spi; + + irq_type = irq_get_trigger_type(spi->irq); + + if (st->adis.data->has_fifo) { + /* + * It is possible to configure the fifo watermark pin polarity. + * Furthermore, we need to update the adis struct if we want the + * watermark pin active low. + */ + if (irq_type == IRQ_TYPE_LEVEL_HIGH) { + polarity = 1; + st->adis.irq_flag = IRQF_TRIGGER_HIGH; + } else if (irq_type == IRQ_TYPE_LEVEL_LOW) { + polarity = 0; + st->adis.irq_flag = IRQF_TRIGGER_LOW; + } else { + dev_err(&spi->dev, "Invalid interrupt type 0x%x specified\n", + irq_type); + return -EINVAL; + } + + /* Configure the watermark pin polarity. */ + val = ADIS16575_WM_POL(polarity); + ret = adis_update_bits(&st->adis, ADIS16475_REG_FIFO_CTRL, + ADIS16575_WM_POL_MASK, val); + if (ret) + return ret; + + /* Enable watermark interrupt pin. */ + ret = adis_update_bits(&st->adis, ADIS16475_REG_FIFO_CTRL, + ADIS16575_WM_EN_MASK, + (u16)ADIS16575_WM_EN(1)); + if (ret) + return ret; + + } else { + /* + * It is possible to configure the data ready polarity. Furthermore, we + * need to update the adis struct if we want data ready as active low. + */ + if (irq_type == IRQ_TYPE_EDGE_RISING) { + polarity = 1; + st->adis.irq_flag = IRQF_TRIGGER_RISING; + } else if (irq_type == IRQ_TYPE_EDGE_FALLING) { + polarity = 0; + st->adis.irq_flag = IRQF_TRIGGER_FALLING; + } else { + dev_err(&spi->dev, "Invalid interrupt type 0x%x specified\n", + irq_type); + return -EINVAL; + } + + val = ADIS16475_MSG_CTRL_DR_POL(polarity); + ret = __adis_update_bits(&st->adis, ADIS16475_REG_MSG_CTRL, + ADIS16475_MSG_CTRL_DR_POL_MASK, val); + if (ret) + return ret; + /* + * There is a delay writing to any bits written to the MSC_CTRL + * register. It should not be bigger than 200us, so 250 should be more + * than enough! + */ + usleep_range(250, 260); + } + + return 0; +} + +static int adis16475_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct adis16475 *st; + int ret; + u16 val; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->info = spi_get_device_match_data(spi); + if (!st->info) + return -EINVAL; + + ret = adis_init(&st->adis, indio_dev, spi, &st->info->adis_data); + if (ret) + return ret; + + indio_dev->name = st->info->name; + indio_dev->channels = st->info->channels; + indio_dev->num_channels = st->info->num_channels; + if (st->adis.data->has_fifo) + indio_dev->info = &adis16575_info; + else + indio_dev->info = &adis16475_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = __adis_initial_startup(&st->adis); + if (ret) + return ret; + + ret = adis16475_config_irq_pin(st); + if (ret) + return ret; + + ret = adis16475_config_sync_mode(st); + if (ret) + return ret; + + if (st->adis.data->has_fifo) { + ret = devm_adis_setup_buffer_and_trigger_with_attrs(&st->adis, indio_dev, + adis16475_trigger_handler_with_fifo, + &adis16475_buffer_ops, + adis16475_fifo_attributes); + if (ret) + return ret; + + /* Update overflow behavior to always overwrite the oldest sample. */ + val = ADIS16575_OVERWRITE_OLDEST; + ret = adis_update_bits(&st->adis, ADIS16475_REG_FIFO_CTRL, + ADIS16575_OVERFLOW_MASK, val); + if (ret) + return ret; + } else { + ret = devm_adis_setup_buffer_and_trigger(&st->adis, indio_dev, + adis16475_trigger_handler); + if (ret) + return ret; + } + + ret = devm_iio_device_register(&spi->dev, indio_dev); + if (ret) + return ret; + + adis16475_debugfs_init(indio_dev); + + return 0; +} + +static const struct of_device_id adis16475_of_match[] = { + { .compatible = "adi,adis16470", + .data = &adis16475_chip_info[ADIS16470] }, + { .compatible = "adi,adis16475-1", + .data = &adis16475_chip_info[ADIS16475_1] }, + { .compatible = "adi,adis16475-2", + .data = &adis16475_chip_info[ADIS16475_2] }, + { .compatible = "adi,adis16475-3", + .data = &adis16475_chip_info[ADIS16475_3] }, + { .compatible = "adi,adis16477-1", + .data = &adis16475_chip_info[ADIS16477_1] }, + { .compatible = "adi,adis16477-2", + .data = &adis16475_chip_info[ADIS16477_2] }, + { .compatible = "adi,adis16477-3", + .data = &adis16475_chip_info[ADIS16477_3] }, + { .compatible = "adi,adis16465-1", + .data = &adis16475_chip_info[ADIS16465_1] }, + { .compatible = "adi,adis16465-2", + .data = &adis16475_chip_info[ADIS16465_2] }, + { .compatible = "adi,adis16465-3", + .data = &adis16475_chip_info[ADIS16465_3] }, + { .compatible = "adi,adis16467-1", + .data = &adis16475_chip_info[ADIS16467_1] }, + { .compatible = "adi,adis16467-2", + .data = &adis16475_chip_info[ADIS16467_2] }, + { .compatible = "adi,adis16467-3", + .data = &adis16475_chip_info[ADIS16467_3] }, + { .compatible = "adi,adis16500", + .data = &adis16475_chip_info[ADIS16500] }, + { .compatible = "adi,adis16501", + .data = &adis16475_chip_info[ADIS16501] }, + { .compatible = "adi,adis16505-1", + .data = &adis16475_chip_info[ADIS16505_1] }, + { .compatible = "adi,adis16505-2", + .data = &adis16475_chip_info[ADIS16505_2] }, + { .compatible = "adi,adis16505-3", + .data = &adis16475_chip_info[ADIS16505_3] }, + { .compatible = "adi,adis16507-1", + .data = &adis16475_chip_info[ADIS16507_1] }, + { .compatible = "adi,adis16507-2", + .data = &adis16475_chip_info[ADIS16507_2] }, + { .compatible = "adi,adis16507-3", + .data = &adis16475_chip_info[ADIS16507_3] }, + { .compatible = "adi,adis16575-2", + .data = &adis16475_chip_info[ADIS16575_2] }, + { .compatible = "adi,adis16575-3", + .data = &adis16475_chip_info[ADIS16575_3] }, + { .compatible = "adi,adis16576-2", + .data = &adis16475_chip_info[ADIS16576_2] }, + { .compatible = "adi,adis16576-3", + .data = &adis16475_chip_info[ADIS16576_3] }, + { .compatible = "adi,adis16577-2", + .data = &adis16475_chip_info[ADIS16577_2] }, + { .compatible = "adi,adis16577-3", + .data = &adis16475_chip_info[ADIS16577_3] }, + { } +}; +MODULE_DEVICE_TABLE(of, adis16475_of_match); + +static const struct spi_device_id adis16475_ids[] = { + { .name = "adis16470", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16470] }, + { .name = "adis16475-1", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16475_1] }, + { .name = "adis16475-2", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16475_2] }, + { .name = "adis16475-3", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16475_3] }, + { .name = "adis16477-1", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16477_1] }, + { .name = "adis16477-2", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16477_2] }, + { .name = "adis16477-3", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16477_3] }, + { .name = "adis16465-1", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16465_1] }, + { .name = "adis16465-2", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16465_2] }, + { .name = "adis16465-3", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16465_3] }, + { .name = "adis16467-1", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16467_1] }, + { .name = "adis16467-2", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16467_2] }, + { .name = "adis16467-3", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16467_3] }, + { .name = "adis16500", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16500] }, + { .name = "adis16501", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16501] }, + { .name = "adis16505-1", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16505_1] }, + { .name = "adis16505-2", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16505_2] }, + { .name = "adis16505-3", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16505_3] }, + { .name = "adis16507-1", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16507_1] }, + { .name = "adis16507-2", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16507_2] }, + { .name = "adis16507-3", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16507_3] }, + { .name = "adis16575-2", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16575_2] }, + { .name = "adis16575-3", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16575_3] }, + { .name = "adis16576-2", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16576_2] }, + { .name = "adis16576-3", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16576_3] }, + { .name = "adis16577-2", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16577_2] }, + { .name = "adis16577-3", .driver_data = (kernel_ulong_t)&adis16475_chip_info[ADIS16577_3] }, + { } +}; +MODULE_DEVICE_TABLE(spi, adis16475_ids); + +static struct spi_driver adis16475_driver = { + .driver = { + .name = "adis16475", + .of_match_table = adis16475_of_match, + }, + .probe = adis16475_probe, + .id_table = adis16475_ids, +}; +module_spi_driver(adis16475_driver); + +MODULE_AUTHOR("Nuno Sa "); +MODULE_DESCRIPTION("Analog Devices ADIS16475 IMU driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_ADISLIB"); diff --git a/drivers/iio/imu/adis16480.c b/drivers/iio/imu/adis16480.c new file mode 100644 index 000000000..5187566c1 --- /dev/null +++ b/drivers/iio/imu/adis16480.c @@ -0,0 +1,1871 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ADIS16480 and similar IMUs driver + * + * Copyright 2012 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define ADIS16480_PAGE_SIZE 0x80 + +#define ADIS16480_REG(page, reg) ((page) * ADIS16480_PAGE_SIZE + (reg)) + +#define ADIS16480_REG_PAGE_ID 0x00 /* Same address on each page */ +#define ADIS16480_REG_SEQ_CNT ADIS16480_REG(0x00, 0x06) +#define ADIS16480_REG_SYS_E_FLA ADIS16480_REG(0x00, 0x08) +#define ADIS16480_REG_DIAG_STS ADIS16480_REG(0x00, 0x0A) +#define ADIS16480_REG_ALM_STS ADIS16480_REG(0x00, 0x0C) +#define ADIS16480_REG_TEMP_OUT ADIS16480_REG(0x00, 0x0E) +#define ADIS16480_REG_X_GYRO_OUT ADIS16480_REG(0x00, 0x10) +#define ADIS16480_REG_Y_GYRO_OUT ADIS16480_REG(0x00, 0x14) +#define ADIS16480_REG_Z_GYRO_OUT ADIS16480_REG(0x00, 0x18) +#define ADIS16480_REG_X_ACCEL_OUT ADIS16480_REG(0x00, 0x1C) +#define ADIS16480_REG_Y_ACCEL_OUT ADIS16480_REG(0x00, 0x20) +#define ADIS16480_REG_Z_ACCEL_OUT ADIS16480_REG(0x00, 0x24) +#define ADIS16480_REG_X_MAGN_OUT ADIS16480_REG(0x00, 0x28) +#define ADIS16480_REG_Y_MAGN_OUT ADIS16480_REG(0x00, 0x2A) +#define ADIS16480_REG_Z_MAGN_OUT ADIS16480_REG(0x00, 0x2C) +#define ADIS16480_REG_BAROM_OUT ADIS16480_REG(0x00, 0x2E) +#define ADIS16480_REG_X_DELTAANG_OUT ADIS16480_REG(0x00, 0x40) +#define ADIS16480_REG_Y_DELTAANG_OUT ADIS16480_REG(0x00, 0x44) +#define ADIS16480_REG_Z_DELTAANG_OUT ADIS16480_REG(0x00, 0x48) +#define ADIS16480_REG_X_DELTAVEL_OUT ADIS16480_REG(0x00, 0x4C) +#define ADIS16480_REG_Y_DELTAVEL_OUT ADIS16480_REG(0x00, 0x50) +#define ADIS16480_REG_Z_DELTAVEL_OUT ADIS16480_REG(0x00, 0x54) +#define ADIS16480_REG_PROD_ID ADIS16480_REG(0x00, 0x7E) + +#define ADIS16480_REG_X_GYRO_SCALE ADIS16480_REG(0x02, 0x04) +#define ADIS16480_REG_Y_GYRO_SCALE ADIS16480_REG(0x02, 0x06) +#define ADIS16480_REG_Z_GYRO_SCALE ADIS16480_REG(0x02, 0x08) +#define ADIS16480_REG_X_ACCEL_SCALE ADIS16480_REG(0x02, 0x0A) +#define ADIS16480_REG_Y_ACCEL_SCALE ADIS16480_REG(0x02, 0x0C) +#define ADIS16480_REG_Z_ACCEL_SCALE ADIS16480_REG(0x02, 0x0E) +#define ADIS16480_REG_X_GYRO_BIAS ADIS16480_REG(0x02, 0x10) +#define ADIS16480_REG_Y_GYRO_BIAS ADIS16480_REG(0x02, 0x14) +#define ADIS16480_REG_Z_GYRO_BIAS ADIS16480_REG(0x02, 0x18) +#define ADIS16480_REG_X_ACCEL_BIAS ADIS16480_REG(0x02, 0x1C) +#define ADIS16480_REG_Y_ACCEL_BIAS ADIS16480_REG(0x02, 0x20) +#define ADIS16480_REG_Z_ACCEL_BIAS ADIS16480_REG(0x02, 0x24) +#define ADIS16480_REG_X_HARD_IRON ADIS16480_REG(0x02, 0x28) +#define ADIS16480_REG_Y_HARD_IRON ADIS16480_REG(0x02, 0x2A) +#define ADIS16480_REG_Z_HARD_IRON ADIS16480_REG(0x02, 0x2C) +#define ADIS16480_REG_BAROM_BIAS ADIS16480_REG(0x02, 0x40) +#define ADIS16480_REG_FLASH_CNT ADIS16480_REG(0x02, 0x7C) + +#define ADIS16480_REG_GLOB_CMD ADIS16480_REG(0x03, 0x02) +#define ADIS16480_REG_FNCTIO_CTRL ADIS16480_REG(0x03, 0x06) +#define ADIS16480_REG_GPIO_CTRL ADIS16480_REG(0x03, 0x08) +#define ADIS16480_REG_CONFIG ADIS16480_REG(0x03, 0x0A) +#define ADIS16480_REG_DEC_RATE ADIS16480_REG(0x03, 0x0C) +#define ADIS16480_REG_SLP_CNT ADIS16480_REG(0x03, 0x10) +#define ADIS16480_REG_FILTER_BNK0 ADIS16480_REG(0x03, 0x16) +#define ADIS16480_REG_FILTER_BNK1 ADIS16480_REG(0x03, 0x18) +#define ADIS16480_REG_ALM_CNFG0 ADIS16480_REG(0x03, 0x20) +#define ADIS16480_REG_ALM_CNFG1 ADIS16480_REG(0x03, 0x22) +#define ADIS16480_REG_ALM_CNFG2 ADIS16480_REG(0x03, 0x24) +#define ADIS16480_REG_XG_ALM_MAGN ADIS16480_REG(0x03, 0x28) +#define ADIS16480_REG_YG_ALM_MAGN ADIS16480_REG(0x03, 0x2A) +#define ADIS16480_REG_ZG_ALM_MAGN ADIS16480_REG(0x03, 0x2C) +#define ADIS16480_REG_XA_ALM_MAGN ADIS16480_REG(0x03, 0x2E) +#define ADIS16480_REG_YA_ALM_MAGN ADIS16480_REG(0x03, 0x30) +#define ADIS16480_REG_ZA_ALM_MAGN ADIS16480_REG(0x03, 0x32) +#define ADIS16480_REG_XM_ALM_MAGN ADIS16480_REG(0x03, 0x34) +#define ADIS16480_REG_YM_ALM_MAGN ADIS16480_REG(0x03, 0x36) +#define ADIS16480_REG_ZM_ALM_MAGN ADIS16480_REG(0x03, 0x38) +#define ADIS16480_REG_BR_ALM_MAGN ADIS16480_REG(0x03, 0x3A) +#define ADIS16480_REG_FIRM_REV ADIS16480_REG(0x03, 0x78) +#define ADIS16480_REG_FIRM_DM ADIS16480_REG(0x03, 0x7A) +#define ADIS16480_REG_FIRM_Y ADIS16480_REG(0x03, 0x7C) + +/* + * External clock scaling in PPS mode. + * Available only for ADIS1649x devices + */ +#define ADIS16495_REG_SYNC_SCALE ADIS16480_REG(0x03, 0x10) +#define ADIS16495_REG_BURST_CMD ADIS16480_REG(0x00, 0x7C) +#define ADIS16495_GYRO_ACCEL_BURST_ID 0xA5A5 +#define ADIS16545_DELTA_ANG_VEL_BURST_ID 0xC3C3 +/* total number of segments in burst */ +#define ADIS16495_BURST_MAX_DATA 20 + +#define ADIS16480_REG_SERIAL_NUM ADIS16480_REG(0x04, 0x20) + +/* Each filter coefficient bank spans two pages */ +#define ADIS16480_FIR_COEF(page) (x < 60 ? ADIS16480_REG(page, (x) + 8) : \ + ADIS16480_REG((page) + 1, (x) - 60 + 8)) +#define ADIS16480_FIR_COEF_A(x) ADIS16480_FIR_COEF(0x05, (x)) +#define ADIS16480_FIR_COEF_B(x) ADIS16480_FIR_COEF(0x07, (x)) +#define ADIS16480_FIR_COEF_C(x) ADIS16480_FIR_COEF(0x09, (x)) +#define ADIS16480_FIR_COEF_D(x) ADIS16480_FIR_COEF(0x0B, (x)) + +/* ADIS16480_REG_FNCTIO_CTRL */ +#define ADIS16480_DRDY_SEL_MSK GENMASK(1, 0) +#define ADIS16480_DRDY_SEL(x) FIELD_PREP(ADIS16480_DRDY_SEL_MSK, x) +#define ADIS16480_DRDY_POL_MSK BIT(2) +#define ADIS16480_DRDY_POL(x) FIELD_PREP(ADIS16480_DRDY_POL_MSK, x) +#define ADIS16480_DRDY_EN_MSK BIT(3) +#define ADIS16480_DRDY_EN(x) FIELD_PREP(ADIS16480_DRDY_EN_MSK, x) +#define ADIS16480_SYNC_SEL_MSK GENMASK(5, 4) +#define ADIS16480_SYNC_SEL(x) FIELD_PREP(ADIS16480_SYNC_SEL_MSK, x) +#define ADIS16480_SYNC_EN_MSK BIT(7) +#define ADIS16480_SYNC_EN(x) FIELD_PREP(ADIS16480_SYNC_EN_MSK, x) +#define ADIS16480_SYNC_MODE_MSK BIT(8) +#define ADIS16480_SYNC_MODE(x) FIELD_PREP(ADIS16480_SYNC_MODE_MSK, x) + +#define ADIS16545_BURST_DATA_SEL_0_CHN_MASK GENMASK(5, 0) +#define ADIS16545_BURST_DATA_SEL_1_CHN_MASK GENMASK(16, 11) +#define ADIS16545_BURST_DATA_SEL_MASK BIT(8) + +struct adis16480_chip_info { + unsigned int num_channels; + const struct iio_chan_spec *channels; + unsigned int gyro_max_val; + unsigned int gyro_max_scale; + unsigned int accel_max_val; + unsigned int accel_max_scale; + unsigned int temp_scale; + unsigned int deltang_max_val; + unsigned int deltvel_max_val; + unsigned int int_clk; + unsigned int max_dec_rate; + const unsigned int *filter_freqs; + bool has_pps_clk_mode; + bool has_sleep_cnt; + bool has_burst_delta_data; + const struct adis_data adis_data; +}; + +enum adis16480_int_pin { + ADIS16480_PIN_DIO1, + ADIS16480_PIN_DIO2, + ADIS16480_PIN_DIO3, + ADIS16480_PIN_DIO4 +}; + +enum adis16480_clock_mode { + ADIS16480_CLK_SYNC, + ADIS16480_CLK_PPS, + ADIS16480_CLK_INT +}; + +struct adis16480 { + const struct adis16480_chip_info *chip_info; + + struct adis adis; + struct clk *ext_clk; + enum adis16480_clock_mode clk_mode; + unsigned int clk_freq; + u16 burst_id; + /* Alignment needed for the timestamp */ + __be16 data[ADIS16495_BURST_MAX_DATA] __aligned(8); +}; + +static const char * const adis16480_int_pin_names[4] = { + [ADIS16480_PIN_DIO1] = "DIO1", + [ADIS16480_PIN_DIO2] = "DIO2", + [ADIS16480_PIN_DIO3] = "DIO3", + [ADIS16480_PIN_DIO4] = "DIO4", +}; + +static bool low_rate_allow; +module_param(low_rate_allow, bool, 0444); +MODULE_PARM_DESC(low_rate_allow, + "Allow IMU rates below the minimum advisable when external clk is used in PPS mode (default: N)"); + +static ssize_t adis16480_show_firmware_revision(struct file *file, + char __user *userbuf, size_t count, loff_t *ppos) +{ + struct adis16480 *adis16480 = file->private_data; + char buf[7]; + size_t len; + u16 rev; + int ret; + + ret = adis_read_reg_16(&adis16480->adis, ADIS16480_REG_FIRM_REV, &rev); + if (ret) + return ret; + + len = scnprintf(buf, sizeof(buf), "%x.%x\n", rev >> 8, rev & 0xff); + + return simple_read_from_buffer(userbuf, count, ppos, buf, len); +} + +static const struct file_operations adis16480_firmware_revision_fops = { + .open = simple_open, + .read = adis16480_show_firmware_revision, + .llseek = default_llseek, + .owner = THIS_MODULE, +}; + +static ssize_t adis16480_show_firmware_date(struct file *file, + char __user *userbuf, size_t count, loff_t *ppos) +{ + struct adis16480 *adis16480 = file->private_data; + u16 md, year; + char buf[12]; + size_t len; + int ret; + + ret = adis_read_reg_16(&adis16480->adis, ADIS16480_REG_FIRM_Y, &year); + if (ret) + return ret; + + ret = adis_read_reg_16(&adis16480->adis, ADIS16480_REG_FIRM_DM, &md); + if (ret) + return ret; + + len = snprintf(buf, sizeof(buf), "%.2x-%.2x-%.4x\n", + md >> 8, md & 0xff, year); + + return simple_read_from_buffer(userbuf, count, ppos, buf, len); +} + +static const struct file_operations adis16480_firmware_date_fops = { + .open = simple_open, + .read = adis16480_show_firmware_date, + .llseek = default_llseek, + .owner = THIS_MODULE, +}; + +static int adis16480_show_serial_number(void *arg, u64 *val) +{ + struct adis16480 *adis16480 = arg; + u16 serial; + int ret; + + ret = adis_read_reg_16(&adis16480->adis, ADIS16480_REG_SERIAL_NUM, + &serial); + if (ret) + return ret; + + *val = serial; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(adis16480_serial_number_fops, + adis16480_show_serial_number, NULL, "0x%.4llx\n"); + +static int adis16480_show_product_id(void *arg, u64 *val) +{ + struct adis16480 *adis16480 = arg; + u16 prod_id; + int ret; + + ret = adis_read_reg_16(&adis16480->adis, ADIS16480_REG_PROD_ID, + &prod_id); + if (ret) + return ret; + + *val = prod_id; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(adis16480_product_id_fops, + adis16480_show_product_id, NULL, "%llu\n"); + +static int adis16480_show_flash_count(void *arg, u64 *val) +{ + struct adis16480 *adis16480 = arg; + u32 flash_count; + int ret; + + ret = adis_read_reg_32(&adis16480->adis, ADIS16480_REG_FLASH_CNT, + &flash_count); + if (ret) + return ret; + + *val = flash_count; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(adis16480_flash_count_fops, + adis16480_show_flash_count, NULL, "%lld\n"); + +static void adis16480_debugfs_init(struct iio_dev *indio_dev) +{ + struct adis16480 *adis16480 = iio_priv(indio_dev); + struct dentry *d = iio_get_debugfs_dentry(indio_dev); + + if (!IS_ENABLED(CONFIG_DEBUG_FS)) + return; + + debugfs_create_file_unsafe("firmware_revision", 0400, + d, adis16480, &adis16480_firmware_revision_fops); + debugfs_create_file_unsafe("firmware_date", 0400, + d, adis16480, &adis16480_firmware_date_fops); + debugfs_create_file_unsafe("serial_number", 0400, + d, adis16480, &adis16480_serial_number_fops); + debugfs_create_file_unsafe("product_id", 0400, + d, adis16480, &adis16480_product_id_fops); + debugfs_create_file_unsafe("flash_count", 0400, + d, adis16480, &adis16480_flash_count_fops); +} + +static int adis16480_set_freq(struct iio_dev *indio_dev, int val, int val2) +{ + struct adis16480 *st = iio_priv(indio_dev); + unsigned int t, sample_rate = st->clk_freq; + int ret; + + if (val < 0 || val2 < 0) + return -EINVAL; + + t = val * 1000 + val2 / 1000; + if (t == 0) + return -EINVAL; + + adis_dev_auto_lock(&st->adis); + /* + * When using PPS mode, the input clock needs to be scaled so that we have an IMU + * sample rate between (optimally) 4000 and 4250. After this, we can use the + * decimation filter to lower the sampling rate in order to get what the user wants. + * Optimally, the user sample rate is a multiple of both the IMU sample rate and + * the input clock. Hence, calculating the sync_scale dynamically gives us better + * chances of achieving a perfect/integer value for DEC_RATE. The math here is: + * 1. lcm of the input clock and the desired output rate. + * 2. get the highest multiple of the previous result lower than the adis max rate. + * 3. The last result becomes the IMU sample rate. Use that to calculate SYNC_SCALE + * and DEC_RATE (to get the user output rate) + */ + if (st->clk_mode == ADIS16480_CLK_PPS) { + unsigned long scaled_rate = lcm(st->clk_freq, t); + int sync_scale; + + /* + * If lcm is bigger than the IMU maximum sampling rate there's no perfect + * solution. In this case, we get the highest multiple of the input clock + * lower than the IMU max sample rate. + */ + if (scaled_rate > st->chip_info->int_clk) + scaled_rate = st->chip_info->int_clk / st->clk_freq * st->clk_freq; + else + scaled_rate = st->chip_info->int_clk / scaled_rate * scaled_rate; + + /* + * This is not an hard requirement but it's not advised to run the IMU + * with a sample rate lower than 4000Hz due to possible undersampling + * issues. However, there are users that might really want to take the risk. + * Hence, we provide a module parameter for them. If set, we allow sample + * rates lower than 4KHz. By default, we won't allow this and we just roundup + * the rate to the next multiple of the input clock bigger than 4KHz. This + * is done like this as in some cases (when DEC_RATE is 0) might give + * us the closest value to the one desired by the user... + */ + if (scaled_rate < 4000000 && !low_rate_allow) + scaled_rate = roundup(4000000, st->clk_freq); + + sync_scale = scaled_rate / st->clk_freq; + ret = __adis_write_reg_16(&st->adis, ADIS16495_REG_SYNC_SCALE, sync_scale); + if (ret) + return ret; + + sample_rate = scaled_rate; + } + + t = DIV_ROUND_CLOSEST(sample_rate, t); + if (t) + t--; + + if (t > st->chip_info->max_dec_rate) + t = st->chip_info->max_dec_rate; + + return __adis_write_reg_16(&st->adis, ADIS16480_REG_DEC_RATE, t); +} + +static int adis16480_get_freq(struct iio_dev *indio_dev, int *val, int *val2) +{ + struct adis16480 *st = iio_priv(indio_dev); + uint16_t t; + int ret; + unsigned int freq, sample_rate = st->clk_freq; + + adis_dev_auto_lock(&st->adis); + + if (st->clk_mode == ADIS16480_CLK_PPS) { + u16 sync_scale; + + ret = __adis_read_reg_16(&st->adis, ADIS16495_REG_SYNC_SCALE, &sync_scale); + if (ret) + return ret; + + sample_rate = st->clk_freq * sync_scale; + } + + ret = __adis_read_reg_16(&st->adis, ADIS16480_REG_DEC_RATE, &t); + if (ret) + return ret; + + freq = DIV_ROUND_CLOSEST(sample_rate, (t + 1)); + + *val = freq / 1000; + *val2 = (freq % 1000) * 1000; + + return IIO_VAL_INT_PLUS_MICRO; +} + +enum { + ADIS16480_SCAN_GYRO_X, + ADIS16480_SCAN_GYRO_Y, + ADIS16480_SCAN_GYRO_Z, + ADIS16480_SCAN_ACCEL_X, + ADIS16480_SCAN_ACCEL_Y, + ADIS16480_SCAN_ACCEL_Z, + ADIS16480_SCAN_MAGN_X, + ADIS16480_SCAN_MAGN_Y, + ADIS16480_SCAN_MAGN_Z, + ADIS16480_SCAN_BARO, + ADIS16480_SCAN_TEMP, + ADIS16480_SCAN_DELTANG_X, + ADIS16480_SCAN_DELTANG_Y, + ADIS16480_SCAN_DELTANG_Z, + ADIS16480_SCAN_DELTVEL_X, + ADIS16480_SCAN_DELTVEL_Y, + ADIS16480_SCAN_DELTVEL_Z, +}; + +static const unsigned int adis16480_calibbias_regs[] = { + [ADIS16480_SCAN_GYRO_X] = ADIS16480_REG_X_GYRO_BIAS, + [ADIS16480_SCAN_GYRO_Y] = ADIS16480_REG_Y_GYRO_BIAS, + [ADIS16480_SCAN_GYRO_Z] = ADIS16480_REG_Z_GYRO_BIAS, + [ADIS16480_SCAN_ACCEL_X] = ADIS16480_REG_X_ACCEL_BIAS, + [ADIS16480_SCAN_ACCEL_Y] = ADIS16480_REG_Y_ACCEL_BIAS, + [ADIS16480_SCAN_ACCEL_Z] = ADIS16480_REG_Z_ACCEL_BIAS, + [ADIS16480_SCAN_MAGN_X] = ADIS16480_REG_X_HARD_IRON, + [ADIS16480_SCAN_MAGN_Y] = ADIS16480_REG_Y_HARD_IRON, + [ADIS16480_SCAN_MAGN_Z] = ADIS16480_REG_Z_HARD_IRON, + [ADIS16480_SCAN_BARO] = ADIS16480_REG_BAROM_BIAS, +}; + +static const unsigned int adis16480_calibscale_regs[] = { + [ADIS16480_SCAN_GYRO_X] = ADIS16480_REG_X_GYRO_SCALE, + [ADIS16480_SCAN_GYRO_Y] = ADIS16480_REG_Y_GYRO_SCALE, + [ADIS16480_SCAN_GYRO_Z] = ADIS16480_REG_Z_GYRO_SCALE, + [ADIS16480_SCAN_ACCEL_X] = ADIS16480_REG_X_ACCEL_SCALE, + [ADIS16480_SCAN_ACCEL_Y] = ADIS16480_REG_Y_ACCEL_SCALE, + [ADIS16480_SCAN_ACCEL_Z] = ADIS16480_REG_Z_ACCEL_SCALE, +}; + +static int adis16480_set_calibbias(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int bias) +{ + unsigned int reg = adis16480_calibbias_regs[chan->scan_index]; + struct adis16480 *st = iio_priv(indio_dev); + + switch (chan->type) { + case IIO_MAGN: + case IIO_PRESSURE: + if (bias < -0x8000 || bias >= 0x8000) + return -EINVAL; + return adis_write_reg_16(&st->adis, reg, bias); + case IIO_ANGL_VEL: + case IIO_ACCEL: + return adis_write_reg_32(&st->adis, reg, bias); + default: + break; + } + + return -EINVAL; +} + +static int adis16480_get_calibbias(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *bias) +{ + unsigned int reg = adis16480_calibbias_regs[chan->scan_index]; + struct adis16480 *st = iio_priv(indio_dev); + uint16_t val16; + uint32_t val32; + int ret; + + switch (chan->type) { + case IIO_MAGN: + case IIO_PRESSURE: + ret = adis_read_reg_16(&st->adis, reg, &val16); + if (ret == 0) + *bias = sign_extend32(val16, 15); + break; + case IIO_ANGL_VEL: + case IIO_ACCEL: + ret = adis_read_reg_32(&st->adis, reg, &val32); + if (ret == 0) + *bias = sign_extend32(val32, 31); + break; + default: + ret = -EINVAL; + } + + if (ret) + return ret; + + return IIO_VAL_INT; +} + +static int adis16480_set_calibscale(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int scale) +{ + unsigned int reg = adis16480_calibscale_regs[chan->scan_index]; + struct adis16480 *st = iio_priv(indio_dev); + + if (scale < -0x8000 || scale >= 0x8000) + return -EINVAL; + + return adis_write_reg_16(&st->adis, reg, scale); +} + +static int adis16480_get_calibscale(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *scale) +{ + unsigned int reg = adis16480_calibscale_regs[chan->scan_index]; + struct adis16480 *st = iio_priv(indio_dev); + uint16_t val16; + int ret; + + ret = adis_read_reg_16(&st->adis, reg, &val16); + if (ret) + return ret; + + *scale = sign_extend32(val16, 15); + return IIO_VAL_INT; +} + +static const unsigned int adis16480_def_filter_freqs[] = { + 310, + 55, + 275, + 63, +}; + +static const unsigned int adis16495_def_filter_freqs[] = { + 300, + 100, + 300, + 100, +}; + +static const unsigned int ad16480_filter_data[][2] = { + [ADIS16480_SCAN_GYRO_X] = { ADIS16480_REG_FILTER_BNK0, 0 }, + [ADIS16480_SCAN_GYRO_Y] = { ADIS16480_REG_FILTER_BNK0, 3 }, + [ADIS16480_SCAN_GYRO_Z] = { ADIS16480_REG_FILTER_BNK0, 6 }, + [ADIS16480_SCAN_ACCEL_X] = { ADIS16480_REG_FILTER_BNK0, 9 }, + [ADIS16480_SCAN_ACCEL_Y] = { ADIS16480_REG_FILTER_BNK0, 12 }, + [ADIS16480_SCAN_ACCEL_Z] = { ADIS16480_REG_FILTER_BNK1, 0 }, + [ADIS16480_SCAN_MAGN_X] = { ADIS16480_REG_FILTER_BNK1, 3 }, + [ADIS16480_SCAN_MAGN_Y] = { ADIS16480_REG_FILTER_BNK1, 6 }, + [ADIS16480_SCAN_MAGN_Z] = { ADIS16480_REG_FILTER_BNK1, 9 }, +}; + +static int adis16480_get_filter_freq(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *freq) +{ + struct adis16480 *st = iio_priv(indio_dev); + unsigned int enable_mask, offset, reg; + uint16_t val; + int ret; + + reg = ad16480_filter_data[chan->scan_index][0]; + offset = ad16480_filter_data[chan->scan_index][1]; + enable_mask = BIT(offset + 2); + + ret = adis_read_reg_16(&st->adis, reg, &val); + if (ret) + return ret; + + if (!(val & enable_mask)) + *freq = 0; + else + *freq = st->chip_info->filter_freqs[(val >> offset) & 0x3]; + + return IIO_VAL_INT; +} + +static int adis16480_set_filter_freq(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int freq) +{ + struct adis16480 *st = iio_priv(indio_dev); + unsigned int enable_mask, offset, reg; + unsigned int diff, best_diff; + unsigned int i, best_freq; + uint16_t val; + int ret; + + reg = ad16480_filter_data[chan->scan_index][0]; + offset = ad16480_filter_data[chan->scan_index][1]; + enable_mask = BIT(offset + 2); + + adis_dev_auto_lock(&st->adis); + + ret = __adis_read_reg_16(&st->adis, reg, &val); + if (ret) + return ret; + + if (freq == 0) { + val &= ~enable_mask; + } else { + best_freq = 0; + best_diff = st->chip_info->filter_freqs[0]; + for (i = 0; i < ARRAY_SIZE(adis16480_def_filter_freqs); i++) { + if (st->chip_info->filter_freqs[i] >= freq) { + diff = st->chip_info->filter_freqs[i] - freq; + if (diff < best_diff) { + best_diff = diff; + best_freq = i; + } + } + } + + val &= ~(0x3 << offset); + val |= best_freq << offset; + val |= enable_mask; + } + + return __adis_write_reg_16(&st->adis, reg, val); +} + +static int adis16480_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, int *val2, long info) +{ + struct adis16480 *st = iio_priv(indio_dev); + unsigned int temp; + + switch (info) { + case IIO_CHAN_INFO_RAW: + return adis_single_conversion(indio_dev, chan, 0, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = st->chip_info->gyro_max_scale; + *val2 = st->chip_info->gyro_max_val; + return IIO_VAL_FRACTIONAL; + case IIO_ACCEL: + *val = st->chip_info->accel_max_scale; + *val2 = st->chip_info->accel_max_val; + return IIO_VAL_FRACTIONAL; + case IIO_MAGN: + *val = 0; + *val2 = 100; /* 0.0001 gauss */ + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + /* + * +85 degrees Celsius = temp_max_scale + * +25 degrees Celsius = 0 + * LSB, 25 degrees Celsius = 60 / temp_max_scale + */ + *val = st->chip_info->temp_scale / 1000; + *val2 = (st->chip_info->temp_scale % 1000) * 1000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_PRESSURE: + /* + * max scale is 1310 mbar + * max raw value is 32767 shifted for 32bits + */ + *val = 131; /* 1310mbar = 131 kPa */ + *val2 = 32767 << 16; + return IIO_VAL_FRACTIONAL; + case IIO_DELTA_ANGL: + *val = st->chip_info->deltang_max_val; + *val2 = 31; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_DELTA_VELOCITY: + *val = st->chip_info->deltvel_max_val; + *val2 = 31; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + /* Only the temperature channel has a offset */ + temp = 25 * 1000000LL; /* 25 degree Celsius = 0x0000 */ + *val = DIV_ROUND_CLOSEST_ULL(temp, st->chip_info->temp_scale); + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + return adis16480_get_calibbias(indio_dev, chan, val); + case IIO_CHAN_INFO_CALIBSCALE: + return adis16480_get_calibscale(indio_dev, chan, val); + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return adis16480_get_filter_freq(indio_dev, chan, val); + case IIO_CHAN_INFO_SAMP_FREQ: + return adis16480_get_freq(indio_dev, val, val2); + default: + return -EINVAL; + } +} + +static int adis16480_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int val, int val2, long info) +{ + switch (info) { + case IIO_CHAN_INFO_CALIBBIAS: + return adis16480_set_calibbias(indio_dev, chan, val); + case IIO_CHAN_INFO_CALIBSCALE: + return adis16480_set_calibscale(indio_dev, chan, val); + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return adis16480_set_filter_freq(indio_dev, chan, val); + case IIO_CHAN_INFO_SAMP_FREQ: + return adis16480_set_freq(indio_dev, val, val2); + + default: + return -EINVAL; + } +} + +#define ADIS16480_MOD_CHANNEL(_type, _mod, _address, _si, _info_sep, _bits) \ + { \ + .type = (_type), \ + .modified = 1, \ + .channel2 = (_mod), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS) | \ + _info_sep, \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = (_address), \ + .scan_index = (_si), \ + .scan_type = { \ + .sign = 's', \ + .realbits = (_bits), \ + .storagebits = (_bits), \ + .endianness = IIO_BE, \ + }, \ + } + +#define ADIS16480_GYRO_CHANNEL(_mod) \ + ADIS16480_MOD_CHANNEL(IIO_ANGL_VEL, IIO_MOD_ ## _mod, \ + ADIS16480_REG_ ## _mod ## _GYRO_OUT, ADIS16480_SCAN_GYRO_ ## _mod, \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE), \ + 32) + +#define ADIS16480_ACCEL_CHANNEL(_mod) \ + ADIS16480_MOD_CHANNEL(IIO_ACCEL, IIO_MOD_ ## _mod, \ + ADIS16480_REG_ ## _mod ## _ACCEL_OUT, ADIS16480_SCAN_ACCEL_ ## _mod, \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE), \ + 32) + +#define ADIS16480_DELTANG_CHANNEL(_mod) \ + ADIS16480_MOD_CHANNEL(IIO_DELTA_ANGL, IIO_MOD_ ## _mod, \ + ADIS16480_REG_ ## _mod ## _DELTAANG_OUT, ADIS16480_SCAN_DELTANG_ ## _mod, \ + 0, 32) + +#define ADIS16480_DELTANG_CHANNEL_NO_SCAN(_mod) \ + ADIS16480_MOD_CHANNEL(IIO_DELTA_ANGL, IIO_MOD_ ## _mod, \ + ADIS16480_REG_ ## _mod ## _DELTAANG_OUT, -1, 0, 32) + +#define ADIS16480_DELTVEL_CHANNEL(_mod) \ + ADIS16480_MOD_CHANNEL(IIO_DELTA_VELOCITY, IIO_MOD_ ## _mod, \ + ADIS16480_REG_ ## _mod ## _DELTAVEL_OUT, ADIS16480_SCAN_DELTVEL_ ## _mod, \ + 0, 32) + +#define ADIS16480_DELTVEL_CHANNEL_NO_SCAN(_mod) \ + ADIS16480_MOD_CHANNEL(IIO_DELTA_VELOCITY, IIO_MOD_ ## _mod, \ + ADIS16480_REG_ ## _mod ## _DELTAVEL_OUT, -1, 0, 32) + +#define ADIS16480_MAGN_CHANNEL(_mod) \ + ADIS16480_MOD_CHANNEL(IIO_MAGN, IIO_MOD_ ## _mod, \ + ADIS16480_REG_ ## _mod ## _MAGN_OUT, ADIS16480_SCAN_MAGN_ ## _mod, \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + 16) + +#define ADIS16480_PRESSURE_CHANNEL() \ + { \ + .type = IIO_PRESSURE, \ + .indexed = 1, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = ADIS16480_REG_BAROM_OUT, \ + .scan_index = ADIS16480_SCAN_BARO, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 32, \ + .storagebits = 32, \ + .endianness = IIO_BE, \ + }, \ + } + +#define ADIS16480_TEMP_CHANNEL() { \ + .type = IIO_TEMP, \ + .indexed = 1, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = ADIS16480_REG_TEMP_OUT, \ + .scan_index = ADIS16480_SCAN_TEMP, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + } + +static const struct iio_chan_spec adis16480_channels[] = { + ADIS16480_GYRO_CHANNEL(X), + ADIS16480_GYRO_CHANNEL(Y), + ADIS16480_GYRO_CHANNEL(Z), + ADIS16480_ACCEL_CHANNEL(X), + ADIS16480_ACCEL_CHANNEL(Y), + ADIS16480_ACCEL_CHANNEL(Z), + ADIS16480_MAGN_CHANNEL(X), + ADIS16480_MAGN_CHANNEL(Y), + ADIS16480_MAGN_CHANNEL(Z), + ADIS16480_PRESSURE_CHANNEL(), + ADIS16480_TEMP_CHANNEL(), + IIO_CHAN_SOFT_TIMESTAMP(11), + ADIS16480_DELTANG_CHANNEL_NO_SCAN(X), + ADIS16480_DELTANG_CHANNEL_NO_SCAN(Y), + ADIS16480_DELTANG_CHANNEL_NO_SCAN(Z), + ADIS16480_DELTVEL_CHANNEL_NO_SCAN(X), + ADIS16480_DELTVEL_CHANNEL_NO_SCAN(Y), + ADIS16480_DELTVEL_CHANNEL_NO_SCAN(Z), +}; + +static const struct iio_chan_spec adis16485_channels[] = { + ADIS16480_GYRO_CHANNEL(X), + ADIS16480_GYRO_CHANNEL(Y), + ADIS16480_GYRO_CHANNEL(Z), + ADIS16480_ACCEL_CHANNEL(X), + ADIS16480_ACCEL_CHANNEL(Y), + ADIS16480_ACCEL_CHANNEL(Z), + ADIS16480_TEMP_CHANNEL(), + IIO_CHAN_SOFT_TIMESTAMP(7), + ADIS16480_DELTANG_CHANNEL_NO_SCAN(X), + ADIS16480_DELTANG_CHANNEL_NO_SCAN(Y), + ADIS16480_DELTANG_CHANNEL_NO_SCAN(Z), + ADIS16480_DELTVEL_CHANNEL_NO_SCAN(X), + ADIS16480_DELTVEL_CHANNEL_NO_SCAN(Y), + ADIS16480_DELTVEL_CHANNEL_NO_SCAN(Z), +}; + +static const struct iio_chan_spec adis16545_channels[] = { + ADIS16480_GYRO_CHANNEL(X), + ADIS16480_GYRO_CHANNEL(Y), + ADIS16480_GYRO_CHANNEL(Z), + ADIS16480_ACCEL_CHANNEL(X), + ADIS16480_ACCEL_CHANNEL(Y), + ADIS16480_ACCEL_CHANNEL(Z), + ADIS16480_TEMP_CHANNEL(), + ADIS16480_DELTANG_CHANNEL(X), + ADIS16480_DELTANG_CHANNEL(Y), + ADIS16480_DELTANG_CHANNEL(Z), + ADIS16480_DELTVEL_CHANNEL(X), + ADIS16480_DELTVEL_CHANNEL(Y), + ADIS16480_DELTVEL_CHANNEL(Z), + IIO_CHAN_SOFT_TIMESTAMP(17), +}; + +static const struct iio_chan_spec adis16489_channels[] = { + ADIS16480_GYRO_CHANNEL(X), + ADIS16480_GYRO_CHANNEL(Y), + ADIS16480_GYRO_CHANNEL(Z), + ADIS16480_ACCEL_CHANNEL(X), + ADIS16480_ACCEL_CHANNEL(Y), + ADIS16480_ACCEL_CHANNEL(Z), + ADIS16480_PRESSURE_CHANNEL(), + ADIS16480_TEMP_CHANNEL(), + IIO_CHAN_SOFT_TIMESTAMP(8), + ADIS16480_DELTANG_CHANNEL_NO_SCAN(X), + ADIS16480_DELTANG_CHANNEL_NO_SCAN(Y), + ADIS16480_DELTANG_CHANNEL_NO_SCAN(Z), + ADIS16480_DELTVEL_CHANNEL_NO_SCAN(X), + ADIS16480_DELTVEL_CHANNEL_NO_SCAN(Y), + ADIS16480_DELTVEL_CHANNEL_NO_SCAN(Z), +}; + +enum adis16480_variant { + ADIS16375, + ADIS16480, + ADIS16485, + ADIS16486, + ADIS16487, + ADIS16488, + ADIS16489, + ADIS16490, + ADIS16495_1, + ADIS16495_2, + ADIS16495_3, + ADIS16497_1, + ADIS16497_2, + ADIS16497_3, + ADIS16545_1, + ADIS16545_2, + ADIS16545_3, + ADIS16547_1, + ADIS16547_2, + ADIS16547_3 +}; + +#define ADIS16480_DIAG_STAT_XGYRO_FAIL 0 +#define ADIS16480_DIAG_STAT_YGYRO_FAIL 1 +#define ADIS16480_DIAG_STAT_ZGYRO_FAIL 2 +#define ADIS16480_DIAG_STAT_XACCL_FAIL 3 +#define ADIS16480_DIAG_STAT_YACCL_FAIL 4 +#define ADIS16480_DIAG_STAT_ZACCL_FAIL 5 +#define ADIS16480_DIAG_STAT_XMAGN_FAIL 8 +#define ADIS16480_DIAG_STAT_YMAGN_FAIL 9 +#define ADIS16480_DIAG_STAT_ZMAGN_FAIL 10 +#define ADIS16480_DIAG_STAT_BARO_FAIL 11 + +static const char * const adis16480_status_error_msgs[] = { + [ADIS16480_DIAG_STAT_XGYRO_FAIL] = "X-axis gyroscope self-test failure", + [ADIS16480_DIAG_STAT_YGYRO_FAIL] = "Y-axis gyroscope self-test failure", + [ADIS16480_DIAG_STAT_ZGYRO_FAIL] = "Z-axis gyroscope self-test failure", + [ADIS16480_DIAG_STAT_XACCL_FAIL] = "X-axis accelerometer self-test failure", + [ADIS16480_DIAG_STAT_YACCL_FAIL] = "Y-axis accelerometer self-test failure", + [ADIS16480_DIAG_STAT_ZACCL_FAIL] = "Z-axis accelerometer self-test failure", + [ADIS16480_DIAG_STAT_XMAGN_FAIL] = "X-axis magnetometer self-test failure", + [ADIS16480_DIAG_STAT_YMAGN_FAIL] = "Y-axis magnetometer self-test failure", + [ADIS16480_DIAG_STAT_ZMAGN_FAIL] = "Z-axis magnetometer self-test failure", + [ADIS16480_DIAG_STAT_BARO_FAIL] = "Barometer self-test failure", +}; + +static int adis16480_enable_irq(struct adis *adis, bool enable); + +#define ADIS16480_DATA(_prod_id, _timeouts, _burst_len, _burst_max_speed) \ +{ \ + .diag_stat_reg = ADIS16480_REG_DIAG_STS, \ + .glob_cmd_reg = ADIS16480_REG_GLOB_CMD, \ + .prod_id_reg = ADIS16480_REG_PROD_ID, \ + .prod_id = (_prod_id), \ + .has_paging = true, \ + .read_delay = 5, \ + .write_delay = 5, \ + .self_test_mask = BIT(1), \ + .self_test_reg = ADIS16480_REG_GLOB_CMD, \ + .status_error_msgs = adis16480_status_error_msgs, \ + .status_error_mask = BIT(ADIS16480_DIAG_STAT_XGYRO_FAIL) | \ + BIT(ADIS16480_DIAG_STAT_YGYRO_FAIL) | \ + BIT(ADIS16480_DIAG_STAT_ZGYRO_FAIL) | \ + BIT(ADIS16480_DIAG_STAT_XACCL_FAIL) | \ + BIT(ADIS16480_DIAG_STAT_YACCL_FAIL) | \ + BIT(ADIS16480_DIAG_STAT_ZACCL_FAIL) | \ + BIT(ADIS16480_DIAG_STAT_XMAGN_FAIL) | \ + BIT(ADIS16480_DIAG_STAT_YMAGN_FAIL) | \ + BIT(ADIS16480_DIAG_STAT_ZMAGN_FAIL) | \ + BIT(ADIS16480_DIAG_STAT_BARO_FAIL), \ + .enable_irq = adis16480_enable_irq, \ + .timeouts = (_timeouts), \ + .burst_reg_cmd = ADIS16495_REG_BURST_CMD, \ + .burst_len = (_burst_len), \ + .burst_max_speed_hz = _burst_max_speed \ +} + +static const struct adis_timeout adis16485_timeouts = { + .reset_ms = 560, + .sw_reset_ms = 120, + .self_test_ms = 12, +}; + +static const struct adis_timeout adis16480_timeouts = { + .reset_ms = 560, + .sw_reset_ms = 560, + .self_test_ms = 12, +}; + +static const struct adis_timeout adis16495_timeouts = { + .reset_ms = 170, + .sw_reset_ms = 130, + .self_test_ms = 40, +}; + +static const struct adis_timeout adis16495_1_timeouts = { + .reset_ms = 250, + .sw_reset_ms = 210, + .self_test_ms = 20, +}; + +static const struct adis_timeout adis16545_timeouts = { + .reset_ms = 315, + .sw_reset_ms = 270, + .self_test_ms = 35, +}; + +static const struct adis16480_chip_info adis16480_chip_info[] = { + [ADIS16375] = { + .channels = adis16485_channels, + .num_channels = ARRAY_SIZE(adis16485_channels), + /* + * Typically we do IIO_RAD_TO_DEGREE in the denominator, which + * is exactly the same as IIO_DEGREE_TO_RAD in numerator, since + * it gives better approximation. However, in this case we + * cannot do it since it would not fit in a 32bit variable. + */ + .gyro_max_val = 22887 << 16, + .gyro_max_scale = IIO_DEGREE_TO_RAD(300), + .accel_max_val = IIO_M_S_2_TO_G(21973 << 16), + .accel_max_scale = 18, + .temp_scale = 5650, /* 5.65 milli degree Celsius */ + .deltang_max_val = IIO_DEGREE_TO_RAD(180), + .deltvel_max_val = 100, + .int_clk = 2460000, + .max_dec_rate = 2048, + .has_sleep_cnt = true, + .filter_freqs = adis16480_def_filter_freqs, + .adis_data = ADIS16480_DATA(16375, &adis16485_timeouts, 0, 0), + }, + [ADIS16480] = { + .channels = adis16480_channels, + .num_channels = ARRAY_SIZE(adis16480_channels), + .gyro_max_val = 22500 << 16, + .gyro_max_scale = IIO_DEGREE_TO_RAD(450), + .accel_max_val = IIO_M_S_2_TO_G(12500 << 16), + .accel_max_scale = 10, + .temp_scale = 5650, /* 5.65 milli degree Celsius */ + .deltang_max_val = IIO_DEGREE_TO_RAD(720), + .deltvel_max_val = 200, + .int_clk = 2460000, + .max_dec_rate = 2048, + .has_sleep_cnt = true, + .filter_freqs = adis16480_def_filter_freqs, + .adis_data = ADIS16480_DATA(16480, &adis16480_timeouts, 0, 0), + }, + [ADIS16485] = { + .channels = adis16485_channels, + .num_channels = ARRAY_SIZE(adis16485_channels), + .gyro_max_val = 22500 << 16, + .gyro_max_scale = IIO_DEGREE_TO_RAD(450), + .accel_max_val = IIO_M_S_2_TO_G(20000 << 16), + .accel_max_scale = 5, + .temp_scale = 5650, /* 5.65 milli degree Celsius */ + .deltang_max_val = IIO_DEGREE_TO_RAD(720), + .deltvel_max_val = 50, + .int_clk = 2460000, + .max_dec_rate = 2048, + .has_sleep_cnt = true, + .filter_freqs = adis16480_def_filter_freqs, + .adis_data = ADIS16480_DATA(16485, &adis16485_timeouts, 0, 0), + }, + [ADIS16486] = { + .channels = adis16485_channels, + .num_channels = ARRAY_SIZE(adis16485_channels), + .gyro_max_val = 22500 << 16, + .gyro_max_scale = IIO_DEGREE_TO_RAD(450), + .accel_max_val = IIO_M_S_2_TO_G(20000 << 16), + .accel_max_scale = 18, + .temp_scale = 5650, /* 5.65 milli degree Celsius */ + .deltang_max_val = IIO_DEGREE_TO_RAD(720), + .deltvel_max_val = 200, + .int_clk = 2460000, + .max_dec_rate = 2048, + .has_sleep_cnt = true, + .filter_freqs = adis16480_def_filter_freqs, + .adis_data = ADIS16480_DATA(16486, &adis16480_timeouts, 0, 0), + }, + [ADIS16487] = { + .channels = adis16485_channels, + .num_channels = ARRAY_SIZE(adis16485_channels), + .gyro_max_val = 22500 << 16, + .gyro_max_scale = IIO_DEGREE_TO_RAD(450), + .accel_max_val = IIO_M_S_2_TO_G(20000 << 16), + .accel_max_scale = 5, + .temp_scale = 5650, /* 5.65 milli degree Celsius */ + .deltang_max_val = IIO_DEGREE_TO_RAD(720), + .deltvel_max_val = 50, + .int_clk = 2460000, + .max_dec_rate = 2048, + .has_sleep_cnt = true, + .filter_freqs = adis16480_def_filter_freqs, + .adis_data = ADIS16480_DATA(16487, &adis16485_timeouts, 0, 0), + }, + [ADIS16488] = { + .channels = adis16480_channels, + .num_channels = ARRAY_SIZE(adis16480_channels), + .gyro_max_val = 22500 << 16, + .gyro_max_scale = IIO_DEGREE_TO_RAD(450), + .accel_max_val = IIO_M_S_2_TO_G(22500 << 16), + .accel_max_scale = 18, + .temp_scale = 5650, /* 5.65 milli degree Celsius */ + .deltang_max_val = IIO_DEGREE_TO_RAD(720), + .deltvel_max_val = 200, + .int_clk = 2460000, + .max_dec_rate = 2048, + .has_sleep_cnt = true, + .filter_freqs = adis16480_def_filter_freqs, + .adis_data = ADIS16480_DATA(16488, &adis16485_timeouts, 0, 0), + }, + [ADIS16489] = { + .channels = adis16489_channels, + .num_channels = ARRAY_SIZE(adis16489_channels), + .gyro_max_val = 22500 << 16, + .gyro_max_scale = IIO_DEGREE_TO_RAD(450), + .accel_max_val = IIO_M_S_2_TO_G(20000 << 16), + .accel_max_scale = 18, + .temp_scale = 5650, /* 5.65 milli degree Celsius */ + .deltang_max_val = IIO_DEGREE_TO_RAD(720), + .deltvel_max_val = 200, + .int_clk = 2460000, + .max_dec_rate = 2048, + .has_sleep_cnt = true, + .filter_freqs = adis16480_def_filter_freqs, + .adis_data = ADIS16480_DATA(16489, &adis16480_timeouts, 0, 0), + }, + [ADIS16490] = { + .channels = adis16485_channels, + .num_channels = ARRAY_SIZE(adis16485_channels), + .gyro_max_val = 20000 << 16, + .gyro_max_scale = IIO_DEGREE_TO_RAD(100), + .accel_max_val = IIO_M_S_2_TO_G(16000 << 16), + .accel_max_scale = 8, + .temp_scale = 14285, /* 14.285 milli degree Celsius */ + .deltang_max_val = IIO_DEGREE_TO_RAD(720), + .deltvel_max_val = 200, + .int_clk = 4250000, + .max_dec_rate = 4250, + .filter_freqs = adis16495_def_filter_freqs, + .has_pps_clk_mode = true, + .adis_data = ADIS16480_DATA(16490, &adis16495_timeouts, 0, 0), + }, + [ADIS16495_1] = { + .channels = adis16485_channels, + .num_channels = ARRAY_SIZE(adis16485_channels), + .gyro_max_val = 20000 << 16, + .gyro_max_scale = IIO_DEGREE_TO_RAD(125), + .accel_max_val = IIO_M_S_2_TO_G(32000 << 16), + .accel_max_scale = 8, + .temp_scale = 12500, /* 12.5 milli degree Celsius */ + .deltang_max_val = IIO_DEGREE_TO_RAD(360), + .deltvel_max_val = 100, + .int_clk = 4250000, + .max_dec_rate = 4250, + .filter_freqs = adis16495_def_filter_freqs, + .has_pps_clk_mode = true, + /* 20 elements of 16bits */ + .adis_data = ADIS16480_DATA(16495, &adis16495_1_timeouts, + ADIS16495_BURST_MAX_DATA * 2, + 6000000), + }, + [ADIS16495_2] = { + .channels = adis16485_channels, + .num_channels = ARRAY_SIZE(adis16485_channels), + .gyro_max_val = 18000 << 16, + .gyro_max_scale = IIO_DEGREE_TO_RAD(450), + .accel_max_val = IIO_M_S_2_TO_G(32000 << 16), + .accel_max_scale = 8, + .temp_scale = 12500, /* 12.5 milli degree Celsius */ + .deltang_max_val = IIO_DEGREE_TO_RAD(720), + .deltvel_max_val = 100, + .int_clk = 4250000, + .max_dec_rate = 4250, + .filter_freqs = adis16495_def_filter_freqs, + .has_pps_clk_mode = true, + /* 20 elements of 16bits */ + .adis_data = ADIS16480_DATA(16495, &adis16495_1_timeouts, + ADIS16495_BURST_MAX_DATA * 2, + 6000000), + }, + [ADIS16495_3] = { + .channels = adis16485_channels, + .num_channels = ARRAY_SIZE(adis16485_channels), + .gyro_max_val = 20000 << 16, + .gyro_max_scale = IIO_DEGREE_TO_RAD(2000), + .accel_max_val = IIO_M_S_2_TO_G(32000 << 16), + .accel_max_scale = 8, + .temp_scale = 12500, /* 12.5 milli degree Celsius */ + .deltang_max_val = IIO_DEGREE_TO_RAD(2160), + .deltvel_max_val = 100, + .int_clk = 4250000, + .max_dec_rate = 4250, + .filter_freqs = adis16495_def_filter_freqs, + .has_pps_clk_mode = true, + /* 20 elements of 16bits */ + .adis_data = ADIS16480_DATA(16495, &adis16495_1_timeouts, + ADIS16495_BURST_MAX_DATA * 2, + 6000000), + }, + [ADIS16497_1] = { + .channels = adis16485_channels, + .num_channels = ARRAY_SIZE(adis16485_channels), + .gyro_max_val = 20000 << 16, + .gyro_max_scale = IIO_DEGREE_TO_RAD(125), + .accel_max_val = IIO_M_S_2_TO_G(32000 << 16), + .accel_max_scale = 40, + .temp_scale = 12500, /* 12.5 milli degree Celsius */ + .deltang_max_val = IIO_DEGREE_TO_RAD(360), + .deltvel_max_val = 400, + .int_clk = 4250000, + .max_dec_rate = 4250, + .filter_freqs = adis16495_def_filter_freqs, + .has_pps_clk_mode = true, + /* 20 elements of 16bits */ + .adis_data = ADIS16480_DATA(16497, &adis16495_1_timeouts, + ADIS16495_BURST_MAX_DATA * 2, + 6000000), + }, + [ADIS16497_2] = { + .channels = adis16485_channels, + .num_channels = ARRAY_SIZE(adis16485_channels), + .gyro_max_val = 18000 << 16, + .gyro_max_scale = IIO_DEGREE_TO_RAD(450), + .accel_max_val = IIO_M_S_2_TO_G(32000 << 16), + .accel_max_scale = 40, + .temp_scale = 12500, /* 12.5 milli degree Celsius */ + .deltang_max_val = IIO_DEGREE_TO_RAD(720), + .deltvel_max_val = 400, + .int_clk = 4250000, + .max_dec_rate = 4250, + .filter_freqs = adis16495_def_filter_freqs, + .has_pps_clk_mode = true, + /* 20 elements of 16bits */ + .adis_data = ADIS16480_DATA(16497, &adis16495_1_timeouts, + ADIS16495_BURST_MAX_DATA * 2, + 6000000), + }, + [ADIS16497_3] = { + .channels = adis16485_channels, + .num_channels = ARRAY_SIZE(adis16485_channels), + .gyro_max_val = 20000 << 16, + .gyro_max_scale = IIO_DEGREE_TO_RAD(2000), + .accel_max_val = IIO_M_S_2_TO_G(32000 << 16), + .accel_max_scale = 40, + .temp_scale = 12500, /* 12.5 milli degree Celsius */ + .deltang_max_val = IIO_DEGREE_TO_RAD(2160), + .deltvel_max_val = 400, + .int_clk = 4250000, + .max_dec_rate = 4250, + .filter_freqs = adis16495_def_filter_freqs, + .has_pps_clk_mode = true, + /* 20 elements of 16bits */ + .adis_data = ADIS16480_DATA(16497, &adis16495_1_timeouts, + ADIS16495_BURST_MAX_DATA * 2, + 6000000), + }, + [ADIS16545_1] = { + .channels = adis16545_channels, + .num_channels = ARRAY_SIZE(adis16545_channels), + .gyro_max_val = 20000 << 16, + .gyro_max_scale = IIO_DEGREE_TO_RAD(125), + .accel_max_val = IIO_M_S_2_TO_G(32000 << 16), + .accel_max_scale = 8, + .temp_scale = 7000, /* 7 milli degree Celsius */ + .deltang_max_val = IIO_DEGREE_TO_RAD(360), + .deltvel_max_val = 100, + .int_clk = 4250000, + .max_dec_rate = 4250, + .filter_freqs = adis16495_def_filter_freqs, + .has_pps_clk_mode = true, + .has_burst_delta_data = true, + /* 20 elements of 16bits */ + .adis_data = ADIS16480_DATA(16545, &adis16545_timeouts, + ADIS16495_BURST_MAX_DATA * 2, + 6500000), + }, + [ADIS16545_2] = { + .channels = adis16545_channels, + .num_channels = ARRAY_SIZE(adis16545_channels), + .gyro_max_val = 18000 << 16, + .gyro_max_scale = IIO_DEGREE_TO_RAD(450), + .accel_max_val = IIO_M_S_2_TO_G(32000 << 16), + .accel_max_scale = 8, + .temp_scale = 7000, /* 7 milli degree Celsius */ + .deltang_max_val = IIO_DEGREE_TO_RAD(720), + .deltvel_max_val = 100, + .int_clk = 4250000, + .max_dec_rate = 4250, + .filter_freqs = adis16495_def_filter_freqs, + .has_pps_clk_mode = true, + .has_burst_delta_data = true, + /* 20 elements of 16bits */ + .adis_data = ADIS16480_DATA(16545, &adis16545_timeouts, + ADIS16495_BURST_MAX_DATA * 2, + 6500000), + }, + [ADIS16545_3] = { + .channels = adis16545_channels, + .num_channels = ARRAY_SIZE(adis16545_channels), + .gyro_max_val = 20000 << 16, + .gyro_max_scale = IIO_DEGREE_TO_RAD(2000), + .accel_max_val = IIO_M_S_2_TO_G(32000 << 16), + .accel_max_scale = 8, + .temp_scale = 7000, /* 7 milli degree Celsius */ + .deltang_max_val = IIO_DEGREE_TO_RAD(2160), + .deltvel_max_val = 100, + .int_clk = 4250000, + .max_dec_rate = 4250, + .filter_freqs = adis16495_def_filter_freqs, + .has_pps_clk_mode = true, + .has_burst_delta_data = true, + /* 20 elements of 16bits */ + .adis_data = ADIS16480_DATA(16545, &adis16545_timeouts, + ADIS16495_BURST_MAX_DATA * 2, + 6500000), + }, + [ADIS16547_1] = { + .channels = adis16545_channels, + .num_channels = ARRAY_SIZE(adis16545_channels), + .gyro_max_val = 20000 << 16, + .gyro_max_scale = IIO_DEGREE_TO_RAD(125), + .accel_max_val = IIO_M_S_2_TO_G(32000 << 16), + .accel_max_scale = 40, + .temp_scale = 7000, /* 7 milli degree Celsius */ + .deltang_max_val = IIO_DEGREE_TO_RAD(360), + .deltvel_max_val = 400, + .int_clk = 4250000, + .max_dec_rate = 4250, + .filter_freqs = adis16495_def_filter_freqs, + .has_pps_clk_mode = true, + .has_burst_delta_data = true, + /* 20 elements of 16bits */ + .adis_data = ADIS16480_DATA(16547, &adis16545_timeouts, + ADIS16495_BURST_MAX_DATA * 2, + 6500000), + }, + [ADIS16547_2] = { + .channels = adis16545_channels, + .num_channels = ARRAY_SIZE(adis16545_channels), + .gyro_max_val = 18000 << 16, + .gyro_max_scale = IIO_DEGREE_TO_RAD(450), + .accel_max_val = IIO_M_S_2_TO_G(32000 << 16), + .accel_max_scale = 40, + .temp_scale = 7000, /* 7 milli degree Celsius */ + .deltang_max_val = IIO_DEGREE_TO_RAD(720), + .deltvel_max_val = 400, + .int_clk = 4250000, + .max_dec_rate = 4250, + .filter_freqs = adis16495_def_filter_freqs, + .has_pps_clk_mode = true, + .has_burst_delta_data = true, + /* 20 elements of 16bits */ + .adis_data = ADIS16480_DATA(16547, &adis16545_timeouts, + ADIS16495_BURST_MAX_DATA * 2, + 6500000), + }, + [ADIS16547_3] = { + .channels = adis16545_channels, + .num_channels = ARRAY_SIZE(adis16545_channels), + .gyro_max_val = 20000 << 16, + .gyro_max_scale = IIO_DEGREE_TO_RAD(2000), + .accel_max_val = IIO_M_S_2_TO_G(32000 << 16), + .accel_max_scale = 40, + .temp_scale = 7000, /* 7 milli degree Celsius */ + .deltang_max_val = IIO_DEGREE_TO_RAD(2160), + .deltvel_max_val = 400, + .int_clk = 4250000, + .max_dec_rate = 4250, + .filter_freqs = adis16495_def_filter_freqs, + .has_pps_clk_mode = true, + .has_burst_delta_data = true, + /* 20 elements of 16bits */ + .adis_data = ADIS16480_DATA(16547, &adis16545_timeouts, + ADIS16495_BURST_MAX_DATA * 2, + 6500000), + }, +}; + +static bool adis16480_validate_crc(const u16 *buf, const u8 n_elem, const u32 crc) +{ + u32 crc_calc; + u16 crc_buf[15]; + int j; + + for (j = 0; j < n_elem; j++) + crc_buf[j] = swab16(buf[j]); + + crc_calc = crc32(~0, crc_buf, n_elem * 2); + crc_calc ^= ~0; + + return (crc == crc_calc); +} + +static irqreturn_t adis16480_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adis16480 *st = iio_priv(indio_dev); + struct adis *adis = &st->adis; + struct device *dev = &adis->spi->dev; + int ret, bit, offset, i = 0, buff_offset = 0; + __be16 *buffer; + u32 crc; + bool valid; + + adis_dev_auto_scoped_lock(adis) { + if (adis->current_page != 0) { + adis->tx[0] = ADIS_WRITE_REG(ADIS_REG_PAGE_ID); + adis->tx[1] = 0; + ret = spi_write(adis->spi, adis->tx, 2); + if (ret) { + dev_err(dev, "Failed to change device page: %d\n", ret); + goto irq_done; + } + + adis->current_page = 0; + } + + ret = spi_sync(adis->spi, &adis->msg); + if (ret) { + dev_err(dev, "Failed to read data: %d\n", ret); + goto irq_done; + } + } + + /* + * After making the burst request, the response can have one or two + * 16-bit responses containing the BURST_ID depending on the sclk. If + * clk > 3.6MHz, then we will have two BURST_ID in a row. If clk < 3MHZ, + * we have only one. To manage that variation, we use the transition from the + * BURST_ID to the SYS_E_FLAG register, which will not be equal to 0xA5A5/0xC3C3. + * If we not find this variation in the first 4 segments, then the data should + * not be valid. + */ + buffer = adis->buffer; + for (offset = 0; offset < 4; offset++) { + u16 curr = be16_to_cpu(buffer[offset]); + u16 next = be16_to_cpu(buffer[offset + 1]); + + if (curr == st->burst_id && next != st->burst_id) { + offset++; + break; + } + } + + if (offset == 4) { + dev_err(dev, "Invalid burst data\n"); + goto irq_done; + } + + crc = be16_to_cpu(buffer[offset + 16]) << 16 | be16_to_cpu(buffer[offset + 15]); + valid = adis16480_validate_crc((u16 *)&buffer[offset], 15, crc); + if (!valid) { + dev_err(dev, "Invalid crc\n"); + goto irq_done; + } + + iio_for_each_active_channel(indio_dev, bit) { + /* + * When burst mode is used, temperature is the first data + * channel in the sequence, but the temperature scan index + * is 10. + */ + switch (bit) { + case ADIS16480_SCAN_TEMP: + st->data[i++] = buffer[offset + 1]; + /* + * The temperature channel has 16-bit storage size. + * We need to perform the padding to have the buffer + * elements naturally aligned in case there are any + * 32-bit storage size channels enabled which are added + * in the buffer after the temperature data. In case + * there is no data being added after the temperature + * data, the padding is harmless. + */ + st->data[i++] = 0; + break; + case ADIS16480_SCAN_DELTANG_X ... ADIS16480_SCAN_DELTVEL_Z: + buff_offset = ADIS16480_SCAN_DELTANG_X; + fallthrough; + case ADIS16480_SCAN_GYRO_X ... ADIS16480_SCAN_ACCEL_Z: + /* The lower register data is sequenced first */ + st->data[i++] = buffer[2 * (bit - buff_offset) + offset + 3]; + st->data[i++] = buffer[2 * (bit - buff_offset) + offset + 2]; + break; + } + } + + iio_push_to_buffers_with_timestamp(indio_dev, st->data, pf->timestamp); +irq_done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const unsigned long adis16545_channel_masks[] = { + ADIS16545_BURST_DATA_SEL_0_CHN_MASK | BIT(ADIS16480_SCAN_TEMP) | BIT(17), + ADIS16545_BURST_DATA_SEL_1_CHN_MASK | BIT(ADIS16480_SCAN_TEMP) | BIT(17), + 0, +}; + +static int adis16480_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + u16 en; + int ret; + struct adis16480 *st = iio_priv(indio_dev); + + if (st->chip_info->has_burst_delta_data) { + if (*scan_mask & ADIS16545_BURST_DATA_SEL_0_CHN_MASK) { + en = FIELD_PREP(ADIS16545_BURST_DATA_SEL_MASK, 0); + st->burst_id = ADIS16495_GYRO_ACCEL_BURST_ID; + } else { + en = FIELD_PREP(ADIS16545_BURST_DATA_SEL_MASK, 1); + st->burst_id = ADIS16545_DELTA_ANG_VEL_BURST_ID; + } + + ret = __adis_update_bits(&st->adis, ADIS16480_REG_CONFIG, + ADIS16545_BURST_DATA_SEL_MASK, en); + if (ret) + return ret; + } + + return adis_update_scan_mode(indio_dev, scan_mask); +} + +static const struct iio_info adis16480_info = { + .read_raw = &adis16480_read_raw, + .write_raw = &adis16480_write_raw, + .update_scan_mode = &adis16480_update_scan_mode, + .debugfs_reg_access = adis_debugfs_reg_access, +}; + +static int adis16480_stop_device(struct iio_dev *indio_dev) +{ + struct adis16480 *st = iio_priv(indio_dev); + struct device *dev = &st->adis.spi->dev; + int ret; + + ret = adis_write_reg_16(&st->adis, ADIS16480_REG_SLP_CNT, BIT(9)); + if (ret) + dev_err(dev, "Could not power down device: %d\n", ret); + + return ret; +} + +static int adis16480_enable_irq(struct adis *adis, bool enable) +{ + uint16_t val; + int ret; + + ret = __adis_read_reg_16(adis, ADIS16480_REG_FNCTIO_CTRL, &val); + if (ret) + return ret; + + val &= ~ADIS16480_DRDY_EN_MSK; + val |= ADIS16480_DRDY_EN(enable); + + return __adis_write_reg_16(adis, ADIS16480_REG_FNCTIO_CTRL, val); +} + +static int adis16480_config_irq_pin(struct adis16480 *st) +{ + struct device *dev = &st->adis.spi->dev; + struct fwnode_handle *fwnode = dev_fwnode(dev); + enum adis16480_int_pin pin; + unsigned int irq_type; + uint16_t val; + int i, irq = 0; + + /* Disable data ready since the default after reset is on */ + val = ADIS16480_DRDY_EN(0); + + /* + * Get the interrupt from the devicetre by reading the interrupt-names + * property. If it is not specified, use DIO1 pin as default. + * According to the datasheet, the factory default assigns DIO2 as data + * ready signal. However, in the previous versions of the driver, DIO1 + * pin was used. So, we should leave it as is since some devices might + * be expecting the interrupt on the wrong physical pin. + */ + pin = ADIS16480_PIN_DIO1; + for (i = 0; i < ARRAY_SIZE(adis16480_int_pin_names); i++) { + irq = fwnode_irq_get_byname(fwnode, adis16480_int_pin_names[i]); + if (irq > 0) { + pin = i; + break; + } + } + + val |= ADIS16480_DRDY_SEL(pin); + + /* + * Get the interrupt line behaviour. The data ready polarity can be + * configured as positive or negative, corresponding to + * IRQ_TYPE_EDGE_RISING or IRQ_TYPE_EDGE_FALLING respectively. + */ + irq_type = irq_get_trigger_type(st->adis.spi->irq); + if (irq_type == IRQ_TYPE_EDGE_RISING) { /* Default */ + val |= ADIS16480_DRDY_POL(1); + } else if (irq_type == IRQ_TYPE_EDGE_FALLING) { + val |= ADIS16480_DRDY_POL(0); + } else { + dev_err(dev, "Invalid interrupt type 0x%x specified\n", irq_type); + return -EINVAL; + } + /* Write the data ready configuration to the FNCTIO_CTRL register */ + return adis_write_reg_16(&st->adis, ADIS16480_REG_FNCTIO_CTRL, val); +} + +static int adis16480_fw_get_ext_clk_pin(struct adis16480 *st) +{ + struct device *dev = &st->adis.spi->dev; + const char *ext_clk_pin; + enum adis16480_int_pin pin; + int i; + + pin = ADIS16480_PIN_DIO2; + if (device_property_read_string(dev, "adi,ext-clk-pin", &ext_clk_pin)) + goto clk_input_not_found; + + for (i = 0; i < ARRAY_SIZE(adis16480_int_pin_names); i++) { + if (strcasecmp(ext_clk_pin, adis16480_int_pin_names[i]) == 0) + return i; + } + +clk_input_not_found: + dev_info(dev, "clk input line not specified, using DIO2\n"); + return pin; +} + +static int adis16480_ext_clk_config(struct adis16480 *st, bool enable) +{ + struct device *dev = &st->adis.spi->dev; + unsigned int mode, mask; + enum adis16480_int_pin pin; + uint16_t val; + int ret; + + ret = adis_read_reg_16(&st->adis, ADIS16480_REG_FNCTIO_CTRL, &val); + if (ret) + return ret; + + pin = adis16480_fw_get_ext_clk_pin(st); + /* + * Each DIOx pin supports only one function at a time. When a single pin + * has two assignments, the enable bit for a lower priority function + * automatically resets to zero (disabling the lower priority function). + */ + if (pin == ADIS16480_DRDY_SEL(val)) + dev_warn(dev, "DIO%x pin supports only one function at a time\n", pin + 1); + + mode = ADIS16480_SYNC_EN(enable) | ADIS16480_SYNC_SEL(pin); + mask = ADIS16480_SYNC_EN_MSK | ADIS16480_SYNC_SEL_MSK; + /* Only ADIS1649x devices support pps ext clock mode */ + if (st->chip_info->has_pps_clk_mode) { + mode |= ADIS16480_SYNC_MODE(st->clk_mode); + mask |= ADIS16480_SYNC_MODE_MSK; + } + + val &= ~mask; + val |= mode; + + ret = adis_write_reg_16(&st->adis, ADIS16480_REG_FNCTIO_CTRL, val); + if (ret) + return ret; + + return clk_prepare_enable(st->ext_clk); +} + +static int adis16480_get_ext_clocks(struct adis16480 *st) +{ + struct device *dev = &st->adis.spi->dev; + + st->ext_clk = devm_clk_get_optional(dev, "sync"); + if (IS_ERR(st->ext_clk)) + return dev_err_probe(dev, PTR_ERR(st->ext_clk), "failed to get ext clk\n"); + if (st->ext_clk) { + st->clk_mode = ADIS16480_CLK_SYNC; + return 0; + } + + if (st->chip_info->has_pps_clk_mode) { + st->ext_clk = devm_clk_get_optional(dev, "pps"); + if (IS_ERR(st->ext_clk)) + return dev_err_probe(dev, PTR_ERR(st->ext_clk), "failed to get ext clk\n"); + if (st->ext_clk) { + st->clk_mode = ADIS16480_CLK_PPS; + return 0; + } + } + + st->clk_mode = ADIS16480_CLK_INT; + return 0; +} + +static void adis16480_stop(void *data) +{ + adis16480_stop_device(data); +} + +static void adis16480_clk_disable(void *data) +{ + clk_disable_unprepare(data); +} + +static int adis16480_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + const struct adis_data *adis16480_data; + irq_handler_t trigger_handler = NULL; + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct adis16480 *st; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->chip_info = &adis16480_chip_info[id->driver_data]; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + if (st->chip_info->has_burst_delta_data) + indio_dev->available_scan_masks = adis16545_channel_masks; + indio_dev->info = &adis16480_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + adis16480_data = &st->chip_info->adis_data; + + ret = adis_init(&st->adis, indio_dev, spi, adis16480_data); + if (ret) + return ret; + + ret = __adis_initial_startup(&st->adis); + if (ret) + return ret; + + /* + * By default, use burst id for gyroscope and accelerometer data. + * This is the only option for devices which do not offer delta angle + * and delta velocity burst readings. + */ + st->burst_id = ADIS16495_GYRO_ACCEL_BURST_ID; + + if (st->chip_info->has_sleep_cnt) { + ret = devm_add_action_or_reset(dev, adis16480_stop, indio_dev); + if (ret) + return ret; + } + + ret = adis16480_config_irq_pin(st); + if (ret) + return ret; + + ret = adis16480_get_ext_clocks(st); + if (ret) + return ret; + + if (st->ext_clk) { + ret = adis16480_ext_clk_config(st, true); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, adis16480_clk_disable, st->ext_clk); + if (ret) + return ret; + + st->clk_freq = clk_get_rate(st->ext_clk); + st->clk_freq *= 1000; /* micro */ + if (st->clk_mode == ADIS16480_CLK_PPS) { + u16 sync_scale; + + /* + * In PPS mode, the IMU sample rate is the clk_freq * sync_scale. Hence, + * default the IMU sample rate to the highest multiple of the input clock + * lower than the IMU max sample rate. The internal sample rate is the + * max... + */ + sync_scale = st->chip_info->int_clk / st->clk_freq; + ret = __adis_write_reg_16(&st->adis, ADIS16495_REG_SYNC_SCALE, sync_scale); + if (ret) + return ret; + } + } else { + st->clk_freq = st->chip_info->int_clk; + } + + /* Only use our trigger handler if burst mode is supported */ + if (adis16480_data->burst_len) + trigger_handler = adis16480_trigger_handler; + + ret = devm_adis_setup_buffer_and_trigger(&st->adis, indio_dev, + trigger_handler); + if (ret) + return ret; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ret; + + adis16480_debugfs_init(indio_dev); + + return 0; +} + +static const struct spi_device_id adis16480_ids[] = { + { .name = "adis16375", .driver_data = ADIS16375 }, + { .name = "adis16480", .driver_data = ADIS16480 }, + { .name = "adis16485", .driver_data = ADIS16485 }, + { .name = "adis16486", .driver_data = ADIS16486 }, + { .name = "adis16487", .driver_data = ADIS16487 }, + { .name = "adis16488", .driver_data = ADIS16488 }, + { .name = "adis16489", .driver_data = ADIS16489 }, + { .name = "adis16490", .driver_data = ADIS16490 }, + { .name = "adis16495-1", .driver_data = ADIS16495_1 }, + { .name = "adis16495-2", .driver_data = ADIS16495_2 }, + { .name = "adis16495-3", .driver_data = ADIS16495_3 }, + { .name = "adis16497-1", .driver_data = ADIS16497_1 }, + { .name = "adis16497-2", .driver_data = ADIS16497_2 }, + { .name = "adis16497-3", .driver_data = ADIS16497_3 }, + { .name = "adis16545-1", .driver_data = ADIS16545_1 }, + { .name = "adis16545-2", .driver_data = ADIS16545_2 }, + { .name = "adis16545-3", .driver_data = ADIS16545_3 }, + { .name = "adis16547-1", .driver_data = ADIS16547_1 }, + { .name = "adis16547-2", .driver_data = ADIS16547_2 }, + { .name = "adis16547-3", .driver_data = ADIS16547_3 }, + { } +}; +MODULE_DEVICE_TABLE(spi, adis16480_ids); + +static const struct of_device_id adis16480_of_match[] = { + { .compatible = "adi,adis16375" }, + { .compatible = "adi,adis16480" }, + { .compatible = "adi,adis16485" }, + { .compatible = "adi,adis16486" }, + { .compatible = "adi,adis16487" }, + { .compatible = "adi,adis16488" }, + { .compatible = "adi,adis16489" }, + { .compatible = "adi,adis16490" }, + { .compatible = "adi,adis16495-1" }, + { .compatible = "adi,adis16495-2" }, + { .compatible = "adi,adis16495-3" }, + { .compatible = "adi,adis16497-1" }, + { .compatible = "adi,adis16497-2" }, + { .compatible = "adi,adis16497-3" }, + { .compatible = "adi,adis16545-1" }, + { .compatible = "adi,adis16545-2" }, + { .compatible = "adi,adis16545-3" }, + { .compatible = "adi,adis16547-1" }, + { .compatible = "adi,adis16547-2" }, + { .compatible = "adi,adis16547-3" }, + { } +}; +MODULE_DEVICE_TABLE(of, adis16480_of_match); + +static struct spi_driver adis16480_driver = { + .driver = { + .name = "adis16480", + .of_match_table = adis16480_of_match, + }, + .id_table = adis16480_ids, + .probe = adis16480_probe, +}; +module_spi_driver(adis16480_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Analog Devices ADIS16480 IMU driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ADISLIB"); diff --git a/drivers/iio/imu/adis16550.c b/drivers/iio/imu/adis16550.c new file mode 100644 index 000000000..096a61286 --- /dev/null +++ b/drivers/iio/imu/adis16550.c @@ -0,0 +1,1109 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ADIS16550 IMU driver + * + * Copyright 2024 Analog Devices Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ADIS16550_REG_BURST_GYRO_ACCEL 0x0a +#define ADIS16550_REG_BURST_DELTA_ANG_VEL 0x0b +#define ADIS16550_BURST_DATA_GYRO_ACCEL_MASK GENMASK(6, 1) +#define ADIS16550_BURST_DATA_DELTA_ANG_VEL_MASK GENMASK(12, 7) + +#define ADIS16550_REG_STATUS 0x0e +#define ADIS16550_REG_TEMP 0x10 +#define ADIS16550_REG_X_GYRO 0x12 +#define ADIS16550_REG_Y_GYRO 0x14 +#define ADIS16550_REG_Z_GYRO 0x16 +#define ADIS16550_REG_X_ACCEL 0x18 +#define ADIS16550_REG_Y_ACCEL 0x1a +#define ADIS16550_REG_Z_ACCEL 0x1c +#define ADIS16550_REG_X_DELTANG_L 0x1E +#define ADIS16550_REG_Y_DELTANG_L 0x20 +#define ADIS16550_REG_Z_DELTANG_L 0x22 +#define ADIS16550_REG_X_DELTVEL_L 0x24 +#define ADIS16550_REG_Y_DELTVEL_L 0x26 +#define ADIS16550_REG_Z_DELTVEL_L 0x28 +#define ADIS16550_REG_X_GYRO_SCALE 0x30 +#define ADIS16550_REG_Y_GYRO_SCALE 0x32 +#define ADIS16550_REG_Z_GYRO_SCALE 0x34 +#define ADIS16550_REG_X_ACCEL_SCALE 0x36 +#define ADIS16550_REG_Y_ACCEL_SCALE 0x38 +#define ADIS16550_REG_Z_ACCEL_SCALE 0x3a +#define ADIS16550_REG_X_GYRO_BIAS 0x40 +#define ADIS16550_REG_Y_GYRO_BIAS 0x42 +#define ADIS16550_REG_Z_GYRO_BIAS 0x44 +#define ADIS16550_REG_X_ACCEL_BIAS 0x46 +#define ADIS16550_REG_Y_ACCEL_BIAS 0x48 +#define ADIS16550_REG_Z_ACCEL_BIAS 0x4a +#define ADIS16550_REG_COMMAND 0x50 +#define ADIS16550_REG_CONFIG 0x52 +#define ADIS16550_GYRO_FIR_EN_MASK BIT(3) +#define ADIS16550_ACCL_FIR_EN_MASK BIT(2) +#define ADIS16550_SYNC_MASK \ + (ADIS16550_SYNC_EN_MASK | ADIS16550_SYNC_MODE_MASK) +#define ADIS16550_SYNC_MODE_MASK BIT(1) +#define ADIS16550_SYNC_EN_MASK BIT(0) +/* max of 4000 SPS in scale sync */ +#define ADIS16550_SYNC_SCALE_MAX_RATE (4000 * 1000) +#define ADIS16550_REG_DEC_RATE 0x54 +#define ADIS16550_REG_SYNC_SCALE 0x56 +#define ADIS16550_REG_SERIAL_NUM 0x76 +#define ADIS16550_REG_FW_REV 0x7A +#define ADIS16550_REG_FW_DATE 0x7C +#define ADIS16550_REG_PROD_ID 0x7E +#define ADIS16550_REG_FLASH_CNT 0x72 +/* SPI protocol*/ +#define ADIS16550_SPI_DATA_MASK GENMASK(31, 16) +#define ADIS16550_SPI_REG_MASK GENMASK(14, 8) +#define ADIS16550_SPI_R_W_MASK BIT(7) +#define ADIS16550_SPI_CRC_MASK GENMASK(3, 0) +#define ADIS16550_SPI_SV_MASK GENMASK(7, 6) +/* burst read */ +#define ADIS16550_BURST_N_ELEM 12 +#define ADIS16550_BURST_DATA_LEN (ADIS16550_BURST_N_ELEM * 4) +#define ADIS16550_MAX_SCAN_DATA 12 + +struct adis16550_sync { + u16 sync_mode; + u16 min_rate; + u16 max_rate; +}; + +struct adis16550 { + struct adis adis; + unsigned long clk_freq_hz; + u32 sync_mode; + struct spi_transfer xfer[2]; + u8 buffer[ADIS16550_BURST_DATA_LEN + sizeof(u32)] __aligned(IIO_DMA_MINALIGN); + __be32 din[2]; + __be32 dout[2]; +}; + +enum { + ADIS16550_SV_INIT, + ADIS16550_SV_OK, + ADIS16550_SV_NOK, + ADIS16550_SV_SPI_ERROR, +}; + +/* + * This is a simplified implementation of lib/crc4.c. It could not be used + * directly since the polynomial used is different from the one used by the + * 16550 which is 0b10001 + */ +static u8 spi_crc4(const u32 val) +{ + int i; + const int bits = 28; + u8 crc = 0xa; + /* ignore 4lsb */ + const u32 __val = val >> 4; + + /* Calculate crc4 over four-bit nibbles, starting at the MSbit */ + for (i = bits - 4; i >= 0; i -= 4) + crc = crc ^ ((__val >> i) & 0xf); + + return crc; +} + +static int adis16550_spi_validate(const struct adis *adis, __be32 dout, + u16 *data) +{ + u32 __dout; + u8 crc, crc_rcv, sv; + + __dout = be32_to_cpu(dout); + + /* validate received message */ + crc_rcv = FIELD_GET(ADIS16550_SPI_CRC_MASK, __dout); + crc = spi_crc4(__dout); + if (crc_rcv != crc) { + dev_err(&adis->spi->dev, + "Invalid crc, rcv: 0x%02x, calc: 0x%02x!\n", + crc_rcv, crc); + return -EIO; + } + sv = FIELD_GET(ADIS16550_SPI_SV_MASK, __dout); + if (sv >= ADIS16550_SV_NOK) { + dev_err(&adis->spi->dev, + "State vector error detected: %02X", sv); + return -EIO; + } + *data = FIELD_GET(ADIS16550_SPI_DATA_MASK, __dout); + + return 0; +} + +static void adis16550_spi_msg_prepare(const u32 reg, const bool write, + const u16 data, __be32 *din) +{ + u8 crc; + u32 __din; + + __din = FIELD_PREP(ADIS16550_SPI_REG_MASK, reg); + + if (write) { + __din |= FIELD_PREP(ADIS16550_SPI_R_W_MASK, 1); + __din |= FIELD_PREP(ADIS16550_SPI_DATA_MASK, data); + } + + crc = spi_crc4(__din); + __din |= FIELD_PREP(ADIS16550_SPI_CRC_MASK, crc); + + *din = cpu_to_be32(__din); +} + +static int adis16550_spi_xfer(const struct adis *adis, u32 reg, u32 len, + u32 *readval, u32 writeval) +{ + int ret; + u16 data = 0; + struct spi_message msg; + bool wr = readval ? false : true; + struct spi_device *spi = adis->spi; + struct adis16550 *st = container_of(adis, struct adis16550, adis); + struct spi_transfer xfers[] = { + { + .tx_buf = &st->din[0], + .len = 4, + .cs_change = 1, + }, { + .tx_buf = &st->din[1], + .len = 4, + .cs_change = 1, + .rx_buf = st->dout, + }, { + .tx_buf = &st->din[1], + .rx_buf = &st->dout[1], + .len = 4, + }, + }; + + spi_message_init(&msg); + + switch (len) { + case 4: + adis16550_spi_msg_prepare(reg + 1, wr, writeval >> 16, + &st->din[0]); + spi_message_add_tail(&xfers[0], &msg); + fallthrough; + case 2: + adis16550_spi_msg_prepare(reg, wr, writeval, &st->din[1]); + spi_message_add_tail(&xfers[1], &msg); + spi_message_add_tail(&xfers[2], &msg); + break; + default: + return -EINVAL; + } + + ret = spi_sync(spi, &msg); + if (ret) { + dev_err(&spi->dev, "Spi failure %d\n", ret); + return ret; + } + /* + * When writing a register, the device will reply with a readback on the + * transfer so that we can validate if our data was actually written.. + */ + switch (len) { + case 4: + ret = adis16550_spi_validate(adis, st->dout[0], &data); + if (ret) + return ret; + + if (readval) { + *readval = data << 16; + } else if ((writeval >> 16) != data && reg != ADIS16550_REG_COMMAND) { + dev_err(&spi->dev, + "Data not written: wr: 0x%04X, rcv: 0x%04X\n", + writeval >> 16, data); + return -EIO; + } + + fallthrough; + case 2: + ret = adis16550_spi_validate(adis, st->dout[1], &data); + if (ret) + return ret; + + if (readval) { + *readval = (*readval & GENMASK(31, 16)) | data; + } else if ((writeval & GENMASK(15, 0)) != data && reg != ADIS16550_REG_COMMAND) { + dev_err(&spi->dev, + "Data not written: wr: 0x%04X, rcv: 0x%04X\n", + (u16)writeval, data); + return -EIO; + } + } + + return 0; +} + +static int adis16550_spi_read(struct adis *adis, const u32 reg, + u32 *value, const u32 len) +{ + return adis16550_spi_xfer(adis, reg, len, value, 0); +} + +static int adis16550_spi_write(struct adis *adis, const u32 reg, + const u32 value, const u32 len) +{ + return adis16550_spi_xfer(adis, reg, len, NULL, value); +} + +static ssize_t adis16550_show_firmware_revision(struct file *file, + char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct adis16550 *st = file->private_data; + char buf[7]; + size_t len; + u16 rev; + int ret; + + ret = adis_read_reg_16(&st->adis, ADIS16550_REG_FW_REV, &rev); + if (ret) + return ret; + + len = scnprintf(buf, sizeof(buf), "%x.%x\n", rev >> 8, rev & 0xff); + + return simple_read_from_buffer(userbuf, count, ppos, buf, len); +} + +static const struct file_operations adis16550_firmware_revision_fops = { + .open = simple_open, + .read = adis16550_show_firmware_revision, + .llseek = default_llseek, + .owner = THIS_MODULE, +}; + +static ssize_t adis16550_show_firmware_date(struct file *file, + char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct adis16550 *st = file->private_data; + char buf[12]; + size_t len; + u32 date; + int ret; + + ret = adis_read_reg_32(&st->adis, ADIS16550_REG_FW_DATE, &date); + if (ret) + return ret; + + len = scnprintf(buf, sizeof(buf), "%.2x-%.2x-%.4x\n", date & 0xff, + (date >> 8) & 0xff, date >> 16); + + return simple_read_from_buffer(userbuf, count, ppos, buf, len); +} + +static const struct file_operations adis16550_firmware_date_fops = { + .open = simple_open, + .read = adis16550_show_firmware_date, + .llseek = default_llseek, + .owner = THIS_MODULE, +}; + +static int adis16550_show_serial_number(void *arg, u64 *val) +{ + struct adis16550 *st = arg; + u32 serial; + int ret; + + ret = adis_read_reg_32(&st->adis, ADIS16550_REG_SERIAL_NUM, &serial); + if (ret) + return ret; + + *val = serial; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(adis16550_serial_number_fops, + adis16550_show_serial_number, NULL, "0x%.8llx\n"); + +static int adis16550_show_product_id(void *arg, u64 *val) +{ + struct adis16550 *st = arg; + u16 prod_id; + int ret; + + ret = adis_read_reg_16(&st->adis, ADIS16550_REG_PROD_ID, &prod_id); + if (ret) + return ret; + + *val = prod_id; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(adis16550_product_id_fops, + adis16550_show_product_id, NULL, "%llu\n"); + +static int adis16550_show_flash_count(void *arg, u64 *val) +{ + struct adis16550 *st = arg; + u16 flash_count; + int ret; + + ret = adis_read_reg_16(&st->adis, ADIS16550_REG_FLASH_CNT, &flash_count); + if (ret) + return ret; + + *val = flash_count; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(adis16550_flash_count_fops, + adis16550_show_flash_count, NULL, "%lld\n"); + +static void adis16550_debugfs_init(struct iio_dev *indio_dev) +{ + struct adis16550 *st = iio_priv(indio_dev); + struct dentry *d = iio_get_debugfs_dentry(indio_dev); + + debugfs_create_file_unsafe("serial_number", 0400, d, st, + &adis16550_serial_number_fops); + debugfs_create_file_unsafe("product_id", 0400, d, st, + &adis16550_product_id_fops); + debugfs_create_file("firmware_revision", 0400, d, st, + &adis16550_firmware_revision_fops); + debugfs_create_file("firmware_date", 0400, d, st, + &adis16550_firmware_date_fops); + debugfs_create_file_unsafe("flash_count", 0400, d, st, + &adis16550_flash_count_fops); +} + +enum { + ADIS16550_SYNC_MODE_DIRECT, + ADIS16550_SYNC_MODE_SCALED, +}; + +static int adis16550_get_freq(struct adis16550 *st, u32 *freq) +{ + int ret; + u16 dec = 0; + u32 sample_rate = st->clk_freq_hz; + + adis_dev_auto_lock(&st->adis); + + if (st->sync_mode == ADIS16550_SYNC_MODE_SCALED) { + u16 sync_scale; + + ret = __adis_read_reg_16(&st->adis, ADIS16550_REG_SYNC_SCALE, &sync_scale); + if (ret) + return ret; + + sample_rate = st->clk_freq_hz * sync_scale; + } + + ret = __adis_read_reg_16(&st->adis, ADIS16550_REG_DEC_RATE, &dec); + if (ret) + return -EINVAL; + *freq = DIV_ROUND_CLOSEST(sample_rate, dec + 1); + + return 0; +} + +static int adis16550_set_freq_hz(struct adis16550 *st, u32 freq_hz) +{ + u16 dec; + int ret; + u32 sample_rate = st->clk_freq_hz; + /* + * The optimal sample rate for the supported IMUs is between + * int_clk - 1000 and int_clk + 500. + */ + u32 max_sample_rate = 4000 * 1000 + 500000; + u32 min_sample_rate = 4000 * 1000 - 1000000; + + if (!freq_hz) + return -EINVAL; + + adis_dev_auto_lock(&st->adis); + + if (st->sync_mode == ADIS16550_SYNC_MODE_SCALED) { + unsigned long scaled_rate = lcm(st->clk_freq_hz, freq_hz); + int sync_scale; + + if (scaled_rate > max_sample_rate) + scaled_rate = max_sample_rate / st->clk_freq_hz * st->clk_freq_hz; + else + scaled_rate = max_sample_rate / scaled_rate * scaled_rate; + + if (scaled_rate < min_sample_rate) + scaled_rate = roundup(min_sample_rate, st->clk_freq_hz); + + sync_scale = scaled_rate / st->clk_freq_hz; + ret = __adis_write_reg_16(&st->adis, ADIS16550_REG_SYNC_SCALE, + sync_scale); + if (ret) + return ret; + + sample_rate = scaled_rate; + } + + dec = DIV_ROUND_CLOSEST(sample_rate, freq_hz); + + if (dec) + dec--; + + dec = min(dec, 4095); + + return __adis_write_reg_16(&st->adis, ADIS16550_REG_DEC_RATE, dec); +} + +static int adis16550_get_accl_filter_freq(struct adis16550 *st, int *freq_hz) +{ + int ret; + u16 config = 0; + + ret = adis_read_reg_16(&st->adis, ADIS16550_REG_CONFIG, &config); + if (ret) + return -EINVAL; + + if (FIELD_GET(ADIS16550_ACCL_FIR_EN_MASK, config)) + *freq_hz = 100; + else + *freq_hz = 0; + + return 0; +} + +static int adis16550_set_accl_filter_freq(struct adis16550 *st, int freq_hz) +{ + u8 en = freq_hz ? 1 : 0; + u16 val = FIELD_PREP(ADIS16550_ACCL_FIR_EN_MASK, en); + + return __adis_update_bits(&st->adis, ADIS16550_REG_CONFIG, + ADIS16550_ACCL_FIR_EN_MASK, val); +} + +static int adis16550_get_gyro_filter_freq(struct adis16550 *st, int *freq_hz) +{ + int ret; + u16 config = 0; + + ret = adis_read_reg_16(&st->adis, ADIS16550_REG_CONFIG, &config); + if (ret) + return -EINVAL; + + if (FIELD_GET(ADIS16550_GYRO_FIR_EN_MASK, config)) + *freq_hz = 100; + else + *freq_hz = 0; + + return 0; +} + +static int adis16550_set_gyro_filter_freq(struct adis16550 *st, int freq_hz) +{ + u8 en = freq_hz ? 1 : 0; + u16 val = FIELD_PREP(ADIS16550_GYRO_FIR_EN_MASK, en); + + return __adis_update_bits(&st->adis, ADIS16550_REG_CONFIG, + ADIS16550_GYRO_FIR_EN_MASK, val); +} + +enum { + ADIS16550_SCAN_TEMP, + ADIS16550_SCAN_GYRO_X, + ADIS16550_SCAN_GYRO_Y, + ADIS16550_SCAN_GYRO_Z, + ADIS16550_SCAN_ACCEL_X, + ADIS16550_SCAN_ACCEL_Y, + ADIS16550_SCAN_ACCEL_Z, + ADIS16550_SCAN_DELTANG_X, + ADIS16550_SCAN_DELTANG_Y, + ADIS16550_SCAN_DELTANG_Z, + ADIS16550_SCAN_DELTVEL_X, + ADIS16550_SCAN_DELTVEL_Y, + ADIS16550_SCAN_DELTVEL_Z, +}; + +static const u32 adis16550_calib_bias[] = { + [ADIS16550_SCAN_GYRO_X] = ADIS16550_REG_X_GYRO_BIAS, + [ADIS16550_SCAN_GYRO_Y] = ADIS16550_REG_Y_GYRO_BIAS, + [ADIS16550_SCAN_GYRO_Z] = ADIS16550_REG_Z_GYRO_BIAS, + [ADIS16550_SCAN_ACCEL_X] = ADIS16550_REG_X_ACCEL_BIAS, + [ADIS16550_SCAN_ACCEL_Y] = ADIS16550_REG_Y_ACCEL_BIAS, + [ADIS16550_SCAN_ACCEL_Z] = ADIS16550_REG_Z_ACCEL_BIAS, + +}; + +static const u32 adis16550_calib_scale[] = { + [ADIS16550_SCAN_GYRO_X] = ADIS16550_REG_X_GYRO_SCALE, + [ADIS16550_SCAN_GYRO_Y] = ADIS16550_REG_Y_GYRO_SCALE, + [ADIS16550_SCAN_GYRO_Z] = ADIS16550_REG_Z_GYRO_SCALE, + [ADIS16550_SCAN_ACCEL_X] = ADIS16550_REG_X_ACCEL_SCALE, + [ADIS16550_SCAN_ACCEL_Y] = ADIS16550_REG_Y_ACCEL_SCALE, + [ADIS16550_SCAN_ACCEL_Z] = ADIS16550_REG_Z_ACCEL_SCALE, +}; + +static int adis16550_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long info) +{ + struct adis16550 *st = iio_priv(indio_dev); + const int idx = chan->scan_index; + u16 scale; + int ret; + u32 tmp; + + switch (info) { + case IIO_CHAN_INFO_RAW: + return adis_single_conversion(indio_dev, chan, 0, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = 1; + *val2 = IIO_RAD_TO_DEGREE(80 << 16); + return IIO_VAL_FRACTIONAL; + case IIO_ACCEL: + *val = 1; + *val2 = IIO_M_S_2_TO_G(102400000); + return IIO_VAL_FRACTIONAL; + case IIO_TEMP: + *val = 4; + return IIO_VAL_INT; + case IIO_DELTA_ANGL: + *val = IIO_DEGREE_TO_RAD(720); + *val2 = 31; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_DELTA_VELOCITY: + *val = 125; + *val2 = 31; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + /* temperature centered at 25°C divided by temp scale */ + *val = 25000 / 4; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + ret = adis_read_reg_32(&st->adis, + adis16550_calib_bias[idx], val); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + ret = adis_read_reg_16(&st->adis, + adis16550_calib_scale[idx], &scale); + if (ret) + return ret; + + *val = scale; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = adis16550_get_freq(st, &tmp); + if (ret) + return ret; + + *val = tmp / 1000; + *val2 = (tmp % 1000) * 1000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + switch (chan->type) { + case IIO_ANGL_VEL: + ret = adis16550_get_gyro_filter_freq(st, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_ACCEL: + ret = adis16550_get_accl_filter_freq(st, val); + if (ret) + return ret; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int adis16550_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int val, int val2, long info) +{ + struct adis16550 *st = iio_priv(indio_dev); + const int idx = chan->scan_index; + u32 tmp; + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + tmp = val * 1000 + val2 / 1000; + return adis16550_set_freq_hz(st, tmp); + case IIO_CHAN_INFO_CALIBBIAS: + return adis_write_reg_32(&st->adis, adis16550_calib_bias[idx], + val); + case IIO_CHAN_INFO_CALIBSCALE: + return adis_write_reg_16(&st->adis, adis16550_calib_scale[idx], + val); + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + switch (chan->type) { + case IIO_ANGL_VEL: + return adis16550_set_gyro_filter_freq(st, val); + case IIO_ACCEL: + return adis16550_set_accl_filter_freq(st, val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +#define ADIS16550_MOD_CHAN(_type, _mod, _address, _si) \ + { \ + .type = (_type), \ + .modified = 1, \ + .channel2 = (_mod), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = (_address), \ + .scan_index = (_si), \ + .scan_type = { \ + .sign = 's', \ + .realbits = 32, \ + .storagebits = 32, \ + .endianness = IIO_BE, \ + }, \ + } + +#define ADIS16550_GYRO_CHANNEL(_mod) \ + ADIS16550_MOD_CHAN(IIO_ANGL_VEL, IIO_MOD_ ## _mod, \ + ADIS16550_REG_ ## _mod ## _GYRO, ADIS16550_SCAN_GYRO_ ## _mod) + +#define ADIS16550_ACCEL_CHANNEL(_mod) \ + ADIS16550_MOD_CHAN(IIO_ACCEL, IIO_MOD_ ## _mod, \ + ADIS16550_REG_ ## _mod ## _ACCEL, ADIS16550_SCAN_ACCEL_ ## _mod) + +#define ADIS16550_TEMP_CHANNEL() { \ + .type = IIO_TEMP, \ + .indexed = 1, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = ADIS16550_REG_TEMP, \ + .scan_index = ADIS16550_SCAN_TEMP, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 32, \ + .endianness = IIO_BE, \ + }, \ + } + +#define ADIS16550_MOD_CHAN_DELTA(_type, _mod, _address, _si) { \ + .type = (_type), \ + .modified = 1, \ + .channel2 = (_mod), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = (_address), \ + .scan_index = _si, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 32, \ + .storagebits = 32, \ + .endianness = IIO_BE, \ + }, \ + } + +#define ADIS16550_DELTANG_CHAN(_mod) \ + ADIS16550_MOD_CHAN_DELTA(IIO_DELTA_ANGL, IIO_MOD_ ## _mod, \ + ADIS16550_REG_ ## _mod ## _DELTANG_L, ADIS16550_SCAN_DELTANG_ ## _mod) + +#define ADIS16550_DELTVEL_CHAN(_mod) \ + ADIS16550_MOD_CHAN_DELTA(IIO_DELTA_VELOCITY, IIO_MOD_ ## _mod, \ + ADIS16550_REG_ ## _mod ## _DELTVEL_L, ADIS16550_SCAN_DELTVEL_ ## _mod) + +#define ADIS16550_DELTANG_CHAN_NO_SCAN(_mod) \ + ADIS16550_MOD_CHAN_DELTA(IIO_DELTA_ANGL, IIO_MOD_ ## _mod, \ + ADIS16550_REG_ ## _mod ## _DELTANG_L, -1) + +#define ADIS16550_DELTVEL_CHAN_NO_SCAN(_mod) \ + ADIS16550_MOD_CHAN_DELTA(IIO_DELTA_VELOCITY, IIO_MOD_ ## _mod, \ + ADIS16550_REG_ ## _mod ## _DELTVEL_L, -1) + +static const struct iio_chan_spec adis16550_channels[] = { + ADIS16550_TEMP_CHANNEL(), + ADIS16550_GYRO_CHANNEL(X), + ADIS16550_GYRO_CHANNEL(Y), + ADIS16550_GYRO_CHANNEL(Z), + ADIS16550_ACCEL_CHANNEL(X), + ADIS16550_ACCEL_CHANNEL(Y), + ADIS16550_ACCEL_CHANNEL(Z), + ADIS16550_DELTANG_CHAN(X), + ADIS16550_DELTANG_CHAN(Y), + ADIS16550_DELTANG_CHAN(Z), + ADIS16550_DELTVEL_CHAN(X), + ADIS16550_DELTVEL_CHAN(Y), + ADIS16550_DELTVEL_CHAN(Z), + IIO_CHAN_SOFT_TIMESTAMP(13), +}; + +static const struct adis16550_sync adis16550_sync_modes[] = { + { ADIS16550_SYNC_MODE_DIRECT, 3000, 4500 }, + { ADIS16550_SYNC_MODE_SCALED, 1, 128 }, +}; + +static u32 adis16550_validate_crc(__be32 *buffer, const u8 n_elem) +{ + int i; + u32 crc_calc; + u32 crc_buf[ADIS16550_BURST_N_ELEM - 2]; + u32 crc = be32_to_cpu(buffer[ADIS16550_BURST_N_ELEM - 1]); + /* + * The crc calculation of the data is done in little endian. Hence, we + * always swap the 32bit elements making sure that the data LSB is + * always on address 0... + */ + for (i = 0; i < n_elem; i++) + crc_buf[i] = be32_to_cpu(buffer[i]); + + crc_calc = crc32(~0, crc_buf, n_elem * 4); + crc_calc ^= ~0; + + return (crc_calc == crc); +} + +static irqreturn_t adis16550_trigger_handler(int irq, void *p) +{ + int ret; + u16 dummy; + bool valid; + struct iio_poll_func *pf = p; + __be32 data[ADIS16550_MAX_SCAN_DATA] __aligned(8) = { }; + struct iio_dev *indio_dev = pf->indio_dev; + struct adis16550 *st = iio_priv(indio_dev); + struct adis *adis = iio_device_get_drvdata(indio_dev); + __be32 *buffer = (__be32 *)st->buffer; + + ret = spi_sync(adis->spi, &adis->msg); + if (ret) + goto done; + /* + * Validate the header. The header is a normal spi reply with state + * vector and crc4. + */ + ret = adis16550_spi_validate(&st->adis, buffer[0], &dummy); + if (ret) + goto done; + + /* the header is not included in the crc */ + valid = adis16550_validate_crc(buffer, ADIS16550_BURST_N_ELEM - 2); + if (!valid) { + dev_err(&adis->spi->dev, "Burst Invalid crc!\n"); + goto done; + } + + /* copy the temperature together with sensor data */ + memcpy(data, &buffer[3], + (ADIS16550_SCAN_ACCEL_Z - ADIS16550_SCAN_GYRO_X + 2) * + sizeof(__be32)); + iio_push_to_buffers_with_timestamp(indio_dev, data, pf->timestamp); +done: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static const unsigned long adis16550_channel_masks[] = { + ADIS16550_BURST_DATA_GYRO_ACCEL_MASK | BIT(ADIS16550_SCAN_TEMP), + ADIS16550_BURST_DATA_DELTA_ANG_VEL_MASK | BIT(ADIS16550_SCAN_TEMP), + 0 +}; + +static int adis16550_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + u16 burst_length = ADIS16550_BURST_DATA_LEN; + struct adis16550 *st = iio_priv(indio_dev); + u8 burst_cmd; + u8 *tx; + + memset(st->buffer, 0, burst_length + sizeof(u32)); + + if (*scan_mask & ADIS16550_BURST_DATA_GYRO_ACCEL_MASK) + burst_cmd = ADIS16550_REG_BURST_GYRO_ACCEL; + else + burst_cmd = ADIS16550_REG_BURST_DELTA_ANG_VEL; + + tx = st->buffer + burst_length; + tx[0] = 0x00; + tx[1] = 0x00; + tx[2] = burst_cmd; + /* crc4 is 0 on burst command */ + tx[3] = spi_crc4(get_unaligned_le32(tx)); + + return 0; +} + +static int adis16550_reset(struct adis *adis) +{ + return __adis_write_reg_16(adis, ADIS16550_REG_COMMAND, BIT(15)); +} + +static int adis16550_config_sync(struct adis16550 *st) +{ + struct device *dev = &st->adis.spi->dev; + const struct adis16550_sync *sync_mode_data; + struct clk *clk; + int ret, i; + u16 mode; + + clk = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(clk)) + return PTR_ERR(clk); + if (!clk) { + st->clk_freq_hz = 4000000; + return 0; + } + + st->clk_freq_hz = clk_get_rate(clk); + + for (i = 0; i < ARRAY_SIZE(adis16550_sync_modes); i++) { + if (st->clk_freq_hz >= adis16550_sync_modes[i].min_rate && + st->clk_freq_hz <= adis16550_sync_modes[i].max_rate) { + sync_mode_data = &adis16550_sync_modes[i]; + break; + } + } + + if (i == ARRAY_SIZE(adis16550_sync_modes)) + return dev_err_probe(dev, -EINVAL, "Clk rate: %lu not in a valid range", + st->clk_freq_hz); + + if (sync_mode_data->sync_mode == ADIS16550_SYNC_MODE_SCALED) { + u16 sync_scale; + /* + * In sps scaled sync we must scale the input clock to a range + * of [3000 4500]. + */ + + sync_scale = DIV_ROUND_CLOSEST(4000, st->clk_freq_hz); + + if (3000 > sync_scale || 4500 < sync_scale) + return dev_err_probe(dev, -EINVAL, + "Invalid value:%u for sync_scale", + sync_scale); + + ret = adis_write_reg_16(&st->adis, ADIS16550_REG_SYNC_SCALE, + sync_scale); + if (ret) + return ret; + + st->clk_freq_hz = 4000; + } + + st->clk_freq_hz *= 1000; + + mode = FIELD_PREP(ADIS16550_SYNC_MODE_MASK, sync_mode_data->sync_mode) | + FIELD_PREP(ADIS16550_SYNC_EN_MASK, true); + + return __adis_update_bits(&st->adis, ADIS16550_REG_CONFIG, + ADIS16550_SYNC_MASK, mode); +} + +static const struct iio_info adis16550_info = { + .read_raw = &adis16550_read_raw, + .write_raw = &adis16550_write_raw, + .update_scan_mode = adis16550_update_scan_mode, + .debugfs_reg_access = adis_debugfs_reg_access, +}; + +enum { + ADIS16550_STATUS_CRC_CODE, + ADIS16550_STATUS_CRC_CONFIG, + ADIS16550_STATUS_FLASH_UPDATE, + ADIS16550_STATUS_INERIAL, + ADIS16550_STATUS_SENSOR, + ADIS16550_STATUS_TEMPERATURE, + ADIS16550_STATUS_SPI, + ADIS16550_STATUS_PROCESSING, + ADIS16550_STATUS_POWER, + ADIS16550_STATUS_BOOT, + ADIS16550_STATUS_WATCHDOG = 15, + ADIS16550_STATUS_REGULATOR = 28, + ADIS16550_STATUS_SENSOR_SUPPLY, + ADIS16550_STATUS_CPU_SUPPLY, + ADIS16550_STATUS_5V_SUPPLY, +}; + +static const char * const adis16550_status_error_msgs[] = { + [ADIS16550_STATUS_CRC_CODE] = "Code CRC Error", + [ADIS16550_STATUS_CRC_CONFIG] = "Configuration/Calibration CRC Error", + [ADIS16550_STATUS_FLASH_UPDATE] = "Flash Update Error", + [ADIS16550_STATUS_INERIAL] = "Overrange for Inertial Signals", + [ADIS16550_STATUS_SENSOR] = "Sensor failure", + [ADIS16550_STATUS_TEMPERATURE] = "Temperature Error", + [ADIS16550_STATUS_SPI] = "SPI Communication Error", + [ADIS16550_STATUS_PROCESSING] = "Processing Overrun Error", + [ADIS16550_STATUS_POWER] = "Power Supply Failure", + [ADIS16550_STATUS_BOOT] = "Boot Memory Failure", + [ADIS16550_STATUS_WATCHDOG] = "Watchdog timer flag", + [ADIS16550_STATUS_REGULATOR] = "Internal Regulator Error", + [ADIS16550_STATUS_SENSOR_SUPPLY] = "Internal Sensor Supply Error.", + [ADIS16550_STATUS_CPU_SUPPLY] = "Internal Processor Supply Error.", + [ADIS16550_STATUS_5V_SUPPLY] = "External 5V Supply Error", +}; + +static const struct adis_timeout adis16550_timeouts = { + .reset_ms = 1000, + .sw_reset_ms = 1000, + .self_test_ms = 1000, +}; + +static const struct adis_data adis16550_data = { + .diag_stat_reg = ADIS16550_REG_STATUS, + .diag_stat_size = 4, + .prod_id_reg = ADIS16550_REG_PROD_ID, + .prod_id = 16550, + .self_test_mask = BIT(1), + .self_test_reg = ADIS16550_REG_COMMAND, + .cs_change_delay = 5, + .unmasked_drdy = true, + .status_error_msgs = adis16550_status_error_msgs, + .status_error_mask = BIT(ADIS16550_STATUS_CRC_CODE) | + BIT(ADIS16550_STATUS_CRC_CONFIG) | + BIT(ADIS16550_STATUS_FLASH_UPDATE) | + BIT(ADIS16550_STATUS_INERIAL) | + BIT(ADIS16550_STATUS_SENSOR) | + BIT(ADIS16550_STATUS_TEMPERATURE) | + BIT(ADIS16550_STATUS_SPI) | + BIT(ADIS16550_STATUS_PROCESSING) | + BIT(ADIS16550_STATUS_POWER) | + BIT(ADIS16550_STATUS_BOOT) | + BIT(ADIS16550_STATUS_WATCHDOG) | + BIT(ADIS16550_STATUS_REGULATOR) | + BIT(ADIS16550_STATUS_SENSOR_SUPPLY) | + BIT(ADIS16550_STATUS_CPU_SUPPLY) | + BIT(ADIS16550_STATUS_5V_SUPPLY), + .timeouts = &adis16550_timeouts, +}; + +static const struct adis_ops adis16550_ops = { + .write = adis16550_spi_write, + .read = adis16550_spi_read, + .reset = adis16550_reset, +}; + +static int adis16550_probe(struct spi_device *spi) +{ + u16 burst_length = ADIS16550_BURST_DATA_LEN; + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct adis16550 *st; + struct adis *adis; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + adis = &st->adis; + indio_dev->name = "adis16550"; + indio_dev->channels = adis16550_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16550_channels); + indio_dev->available_scan_masks = adis16550_channel_masks; + indio_dev->info = &adis16550_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + st->adis.ops = &adis16550_ops; + st->xfer[0].tx_buf = st->buffer + burst_length; + st->xfer[0].len = 4; + st->xfer[0].cs_change = 1; + st->xfer[0].delay.value = 8; + st->xfer[0].delay.unit = SPI_DELAY_UNIT_USECS; + st->xfer[1].rx_buf = st->buffer; + st->xfer[1].len = burst_length; + + spi_message_init_with_transfers(&adis->msg, st->xfer, 2); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to get vdd regulator\n"); + + ret = adis_init(&st->adis, indio_dev, spi, &adis16550_data); + if (ret) + return ret; + + ret = __adis_initial_startup(&st->adis); + if (ret) + return ret; + + ret = adis16550_config_sync(st); + if (ret) + return ret; + + ret = devm_adis_setup_buffer_and_trigger(&st->adis, indio_dev, + adis16550_trigger_handler); + if (ret) + return ret; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ret; + + adis16550_debugfs_init(indio_dev); + + return 0; +} + +static const struct spi_device_id adis16550_id[] = { + { .name = "adis16550" }, + { } +}; +MODULE_DEVICE_TABLE(spi, adis16550_id); + +static const struct of_device_id adis16550_of_match[] = { + { .compatible = "adi,adis16550" }, + { } +}; +MODULE_DEVICE_TABLE(of, adis16550_of_match); + +static struct spi_driver adis16550_driver = { + .driver = { + .name = "adis16550", + .of_match_table = adis16550_of_match, + }, + .probe = adis16550_probe, + .id_table = adis16550_id, +}; +module_spi_driver(adis16550_driver); + +MODULE_AUTHOR("Nuno Sa "); +MODULE_AUTHOR("Ramona Gradinariu "); +MODULE_AUTHOR("Antoniu Miclaus "); +MODULE_AUTHOR("Robert Budai "); +MODULE_DESCRIPTION("Analog Devices ADIS16550 IMU driver"); +MODULE_IMPORT_NS("IIO_ADISLIB"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/imu/adis_buffer.c b/drivers/iio/imu/adis_buffer.c new file mode 100644 index 000000000..501da4eae --- /dev/null +++ b/drivers/iio/imu/adis_buffer.c @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Common library for ADIS16XXX devices + * + * Copyright 2012 Analog Devices Inc. + * Author: Lars-Peter Clausen + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +static int adis_update_scan_mode_burst(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct adis *adis = iio_device_get_drvdata(indio_dev); + unsigned int burst_length, burst_max_length; + u8 *tx; + + burst_length = adis->data->burst_len + adis->burst_extra_len; + + if (adis->data->burst_max_len) + burst_max_length = adis->data->burst_max_len; + else + burst_max_length = burst_length; + + adis->xfer = kzalloc_objs(*adis->xfer, 2); + if (!adis->xfer) + return -ENOMEM; + + adis->buffer = kzalloc(burst_max_length + sizeof(u16), GFP_KERNEL); + if (!adis->buffer) { + kfree(adis->xfer); + adis->xfer = NULL; + return -ENOMEM; + } + + tx = adis->buffer + burst_max_length; + tx[0] = ADIS_READ_REG(adis->data->burst_reg_cmd); + tx[1] = 0; + + adis->xfer[0].tx_buf = tx; + adis->xfer[0].len = 2; + if (adis->data->burst_max_speed_hz) + adis->xfer[0].speed_hz = adis->data->burst_max_speed_hz; + adis->xfer[1].rx_buf = adis->buffer; + adis->xfer[1].len = burst_length; + if (adis->data->burst_max_speed_hz) + adis->xfer[1].speed_hz = adis->data->burst_max_speed_hz; + + spi_message_init(&adis->msg); + spi_message_add_tail(&adis->xfer[0], &adis->msg); + spi_message_add_tail(&adis->xfer[1], &adis->msg); + + return 0; +} + +int adis_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct adis *adis = iio_device_get_drvdata(indio_dev); + const struct iio_chan_spec *chan; + unsigned int scan_count; + unsigned int i, j; + __be16 *tx, *rx; + + kfree(adis->xfer); + kfree(adis->buffer); + + if (adis->data->burst_len) + return adis_update_scan_mode_burst(indio_dev, scan_mask); + + scan_count = indio_dev->scan_bytes / 2; + + adis->xfer = kzalloc_objs(*adis->xfer, scan_count + 1); + if (!adis->xfer) + return -ENOMEM; + + adis->buffer = kcalloc(indio_dev->scan_bytes, 2, GFP_KERNEL); + if (!adis->buffer) { + kfree(adis->xfer); + adis->xfer = NULL; + return -ENOMEM; + } + + rx = adis->buffer; + tx = rx + scan_count; + + spi_message_init(&adis->msg); + + for (j = 0; j <= scan_count; j++) { + if (j != scan_count) + adis->xfer[j].cs_change = 1; + adis->xfer[j].len = 2; + adis->xfer[j].delay.value = adis->data->read_delay; + adis->xfer[j].delay.unit = SPI_DELAY_UNIT_USECS; + if (j < scan_count) + adis->xfer[j].tx_buf = &tx[j]; + if (j >= 1) + adis->xfer[j].rx_buf = &rx[j - 1]; + spi_message_add_tail(&adis->xfer[j], &adis->msg); + } + + chan = indio_dev->channels; + for (i = 0; i < indio_dev->num_channels; i++, chan++) { + if (!test_bit(chan->scan_index, scan_mask)) + continue; + if (chan->scan_type.storagebits == 32) + *tx++ = cpu_to_be16((chan->address + 2) << 8); + *tx++ = cpu_to_be16(chan->address << 8); + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(adis_update_scan_mode, "IIO_ADISLIB"); + +static int adis_paging_trigger_handler(struct adis *adis) +{ + int ret; + + guard(mutex)(&adis->state_lock); + if (adis->current_page != 0) { + adis->tx[0] = ADIS_WRITE_REG(ADIS_REG_PAGE_ID); + adis->tx[1] = 0; + ret = spi_write(adis->spi, adis->tx, 2); + if (ret) { + dev_err(&adis->spi->dev, "Failed to change device page: %d\n", ret); + return ret; + } + + adis->current_page = 0; + } + + return spi_sync(adis->spi, &adis->msg); +} + +static irqreturn_t adis_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adis *adis = iio_device_get_drvdata(indio_dev); + int ret; + + if (adis->data->has_paging) + ret = adis_paging_trigger_handler(adis); + else + ret = spi_sync(adis->spi, &adis->msg); + if (ret) { + dev_err(&adis->spi->dev, "Failed to read data: %d", ret); + goto irq_done; + } + + iio_push_to_buffers_with_timestamp(indio_dev, adis->buffer, + pf->timestamp); + +irq_done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static void adis_buffer_cleanup(void *arg) +{ + struct adis *adis = arg; + + kfree(adis->buffer); + kfree(adis->xfer); +} + +/** + * devm_adis_setup_buffer_and_trigger_with_attrs() - Sets up buffer and trigger + * for the managed adis device with buffer attributes. + * @adis: The adis device + * @indio_dev: The IIO device + * @trigger_handler: Trigger handler: should handle the buffer readings. + * @ops: Optional buffer setup functions, may be NULL. + * @buffer_attrs: Extra buffer attributes. + * + * Returns 0 on success, a negative error code otherwise. + * + * This function sets up the buffer (with buffer setup functions and extra + * buffer attributes) and trigger for a adis devices with buffer attributes. + */ +int +devm_adis_setup_buffer_and_trigger_with_attrs(struct adis *adis, struct iio_dev *indio_dev, + irq_handler_t trigger_handler, + const struct iio_buffer_setup_ops *ops, + const struct iio_dev_attr **buffer_attrs) +{ + int ret; + + if (!trigger_handler) + trigger_handler = adis_trigger_handler; + + ret = devm_iio_triggered_buffer_setup_ext(&adis->spi->dev, indio_dev, + &iio_pollfunc_store_time, + trigger_handler, + IIO_BUFFER_DIRECTION_IN, + ops, + buffer_attrs); + if (ret) + return ret; + + if (adis->spi->irq) { + ret = devm_adis_probe_trigger(adis, indio_dev); + if (ret) + return ret; + } + + return devm_add_action_or_reset(&adis->spi->dev, adis_buffer_cleanup, + adis); +} +EXPORT_SYMBOL_NS_GPL(devm_adis_setup_buffer_and_trigger_with_attrs, "IIO_ADISLIB"); diff --git a/drivers/iio/imu/adis_trigger.c b/drivers/iio/imu/adis_trigger.c new file mode 100644 index 000000000..3e6a7af6a --- /dev/null +++ b/drivers/iio/imu/adis_trigger.c @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Common library for ADIS16XXX devices + * + * Copyright 2012 Analog Devices Inc. + * Author: Lars-Peter Clausen + */ + +#include +#include +#include +#include + +#include +#include +#include + +static int adis_data_rdy_trigger_set_state(struct iio_trigger *trig, bool state) +{ + struct adis *adis = iio_trigger_get_drvdata(trig); + + return adis_enable_irq(adis, state); +} + +static const struct iio_trigger_ops adis_trigger_ops = { + .set_trigger_state = &adis_data_rdy_trigger_set_state, +}; + +static int adis_validate_irq_flag(struct adis *adis) +{ + unsigned long direction = adis->irq_flag & IRQF_TRIGGER_MASK; + + /* We cannot mask the interrupt so ensure it's not enabled at request */ + if (adis->data->unmasked_drdy) + adis->irq_flag |= IRQF_NO_AUTOEN; + /* + * Typically adis devices without FIFO have data ready either on the + * rising edge or on the falling edge of the data ready pin. + * IMU devices with FIFO support have the watermark pin level driven + * either high or low when the FIFO is filled with the desired number + * of samples. + * It defaults to IRQF_TRIGGER_RISING for backward compatibility with + * devices that don't support changing the pin polarity. + */ + if (direction == IRQF_TRIGGER_NONE) { + adis->irq_flag |= IRQF_TRIGGER_RISING; + return 0; + } else if (direction != IRQF_TRIGGER_RISING && + direction != IRQF_TRIGGER_FALLING && !adis->data->has_fifo) { + dev_err(&adis->spi->dev, "Invalid IRQ mask: %08lx\n", + adis->irq_flag); + return -EINVAL; + } else if (direction != IRQF_TRIGGER_HIGH && + direction != IRQF_TRIGGER_LOW && adis->data->has_fifo) { + dev_err(&adis->spi->dev, "Invalid IRQ mask: %08lx\n", + adis->irq_flag); + return -EINVAL; + } + + return 0; +} + +/** + * devm_adis_probe_trigger() - Sets up trigger for a managed adis device + * @adis: The adis device + * @indio_dev: The IIO device + * + * Returns 0 on success or a negative error code + */ +int devm_adis_probe_trigger(struct adis *adis, struct iio_dev *indio_dev) +{ + int ret; + + adis->trig = devm_iio_trigger_alloc(&adis->spi->dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!adis->trig) + return -ENOMEM; + + adis->trig->ops = &adis_trigger_ops; + iio_trigger_set_drvdata(adis->trig, adis); + + ret = adis_validate_irq_flag(adis); + if (ret) + return ret; + + if (adis->data->has_fifo) + ret = devm_request_threaded_irq(&adis->spi->dev, adis->spi->irq, + NULL, + &iio_trigger_generic_data_rdy_poll, + adis->irq_flag | IRQF_ONESHOT, + indio_dev->name, + adis->trig); + else + ret = devm_request_irq(&adis->spi->dev, adis->spi->irq, + &iio_trigger_generic_data_rdy_poll, + adis->irq_flag | IRQF_NO_THREAD, + indio_dev->name, + adis->trig); + if (ret) + return ret; + + return devm_iio_trigger_register(&adis->spi->dev, adis->trig); +} +EXPORT_SYMBOL_NS_GPL(devm_adis_probe_trigger, "IIO_ADISLIB"); + diff --git a/drivers/iio/imu/bmi160/Kconfig b/drivers/iio/imu/bmi160/Kconfig new file mode 100644 index 000000000..9d14d85cc --- /dev/null +++ b/drivers/iio/imu/bmi160/Kconfig @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# BMI160 IMU driver +# + +config BMI160 + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config BMI160_I2C + tristate "Bosch BMI160 I2C driver" + depends on I2C + select BMI160 + select REGMAP_I2C + help + If you say yes here you get support for BMI160 IMU on I2C with + accelerometer, gyroscope and external BMG160 magnetometer. + + This driver can also be built as a module. If so, the module will be + called bmi160_i2c. + +config BMI160_SPI + tristate "Bosch BMI160 SPI driver" + depends on SPI + select BMI160 + select REGMAP_SPI + help + If you say yes here you get support for BMI160 IMU on SPI with + accelerometer, gyroscope and external BMG160 magnetometer. + + This driver can also be built as a module. If so, the module will be + called bmi160_spi. diff --git a/drivers/iio/imu/bmi160/Makefile b/drivers/iio/imu/bmi160/Makefile new file mode 100644 index 000000000..fdcfeddf4 --- /dev/null +++ b/drivers/iio/imu/bmi160/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for Bosch BMI160 IMU +# +obj-$(CONFIG_BMI160) += bmi160_core.o +obj-$(CONFIG_BMI160_I2C) += bmi160_i2c.o +obj-$(CONFIG_BMI160_SPI) += bmi160_spi.o diff --git a/drivers/iio/imu/bmi160/bmi160.h b/drivers/iio/imu/bmi160/bmi160.h new file mode 100644 index 000000000..ffbe8205e --- /dev/null +++ b/drivers/iio/imu/bmi160/bmi160.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef BMI160_H_ +#define BMI160_H_ + +#include +#include + +struct bmi160_data { + struct regmap *regmap; + struct iio_trigger *trig; + struct regulator_bulk_data supplies[2]; + struct iio_mount_matrix orientation; + /* + * Ensure natural alignment for timestamp if present. + * Max length needed: 2 * 3 channels + 4 bytes padding + 8 byte ts. + * If fewer channels are enabled, less space may be needed, as + * long as the timestamp is still aligned to 8 bytes. + */ + __le16 buf[12] __aligned(8); +}; + +extern const struct regmap_config bmi160_regmap_config; + +int bmi160_core_probe(struct device *dev, struct regmap *regmap, + const char *name, bool use_spi); + +int bmi160_enable_irq(struct regmap *regmap, bool enable); + +int bmi160_probe_trigger(struct iio_dev *indio_dev, int irq, u32 irq_type); + +extern const struct dev_pm_ops bmi160_core_pm_ops; + +#endif /* BMI160_H_ */ diff --git a/drivers/iio/imu/bmi160/bmi160_core.c b/drivers/iio/imu/bmi160/bmi160_core.c new file mode 100644 index 000000000..1e73a7f1f --- /dev/null +++ b/drivers/iio/imu/bmi160/bmi160_core.c @@ -0,0 +1,910 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * BMI160 - Bosch IMU (accel, gyro plus external magnetometer) + * + * Copyright (c) 2016, Intel Corporation. + * Copyright (c) 2019, Martin Kelly. + * + * IIO core driver for BMI160, with support for I2C/SPI buses + * + * TODO: magnetometer, hardware FIFO + */ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "bmi160.h" + +#define BMI160_REG_CHIP_ID 0x00 +#define BMI120_CHIP_ID_VAL 0xD3 +#define BMI160_CHIP_ID_VAL 0xD1 + +#define BMI160_REG_PMU_STATUS 0x03 + +/* X axis data low byte address, the rest can be obtained using axis offset */ +#define BMI160_REG_DATA_MAGN_XOUT_L 0x04 +#define BMI160_REG_DATA_GYRO_XOUT_L 0x0C +#define BMI160_REG_DATA_ACCEL_XOUT_L 0x12 + +#define BMI160_REG_ACCEL_CONFIG 0x40 +#define BMI160_ACCEL_CONFIG_ODR_MASK GENMASK(3, 0) +#define BMI160_ACCEL_CONFIG_BWP_MASK GENMASK(6, 4) + +#define BMI160_REG_ACCEL_RANGE 0x41 +#define BMI160_ACCEL_RANGE_2G 0x03 +#define BMI160_ACCEL_RANGE_4G 0x05 +#define BMI160_ACCEL_RANGE_8G 0x08 +#define BMI160_ACCEL_RANGE_16G 0x0C + +#define BMI160_REG_GYRO_CONFIG 0x42 +#define BMI160_GYRO_CONFIG_ODR_MASK GENMASK(3, 0) +#define BMI160_GYRO_CONFIG_BWP_MASK GENMASK(5, 4) + +#define BMI160_REG_GYRO_RANGE 0x43 +#define BMI160_GYRO_RANGE_2000DPS 0x00 +#define BMI160_GYRO_RANGE_1000DPS 0x01 +#define BMI160_GYRO_RANGE_500DPS 0x02 +#define BMI160_GYRO_RANGE_250DPS 0x03 +#define BMI160_GYRO_RANGE_125DPS 0x04 + +#define BMI160_REG_CMD 0x7E +#define BMI160_CMD_ACCEL_PM_SUSPEND 0x10 +#define BMI160_CMD_ACCEL_PM_NORMAL 0x11 +#define BMI160_CMD_ACCEL_PM_LOW_POWER 0x12 +#define BMI160_CMD_GYRO_PM_SUSPEND 0x14 +#define BMI160_CMD_GYRO_PM_NORMAL 0x15 +#define BMI160_CMD_GYRO_PM_FAST_STARTUP 0x17 +#define BMI160_CMD_SOFTRESET 0xB6 + +#define BMI160_REG_INT_EN 0x51 +#define BMI160_DRDY_INT_EN BIT(4) + +#define BMI160_REG_INT_OUT_CTRL 0x53 +#define BMI160_INT_OUT_CTRL_MASK 0x0f +#define BMI160_INT1_OUT_CTRL_SHIFT 0 +#define BMI160_INT2_OUT_CTRL_SHIFT 4 +#define BMI160_EDGE_TRIGGERED BIT(0) +#define BMI160_ACTIVE_HIGH BIT(1) +#define BMI160_OPEN_DRAIN BIT(2) +#define BMI160_OUTPUT_EN BIT(3) + +#define BMI160_REG_INT_LATCH 0x54 +#define BMI160_INT1_LATCH_MASK BIT(4) +#define BMI160_INT2_LATCH_MASK BIT(5) + +/* INT1 and INT2 are in the opposite order as in INT_OUT_CTRL! */ +#define BMI160_REG_INT_MAP 0x56 +#define BMI160_INT1_MAP_DRDY_EN 0x80 +#define BMI160_INT2_MAP_DRDY_EN 0x08 + +#define BMI160_REG_DUMMY 0x7F + +#define BMI160_NORMAL_WRITE_USLEEP 2 +#define BMI160_SUSPENDED_WRITE_USLEEP 450 + +#define BMI160_ACCEL_PMU_MIN_USLEEP 3800 +#define BMI160_GYRO_PMU_MIN_USLEEP 80000 +#define BMI160_SOFTRESET_USLEEP 1000 + +#define BMI160_CHANNEL(_type, _axis, _index) { \ + .type = _type, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ + .ext_info = bmi160_ext_info, \ +} + +static const u8 bmi_chip_ids[] = { + BMI120_CHIP_ID_VAL, + BMI160_CHIP_ID_VAL, +}; + +/* scan indexes follow DATA register order */ +enum bmi160_scan_axis { + BMI160_SCAN_EXT_MAGN_X = 0, + BMI160_SCAN_EXT_MAGN_Y, + BMI160_SCAN_EXT_MAGN_Z, + BMI160_SCAN_RHALL, + BMI160_SCAN_GYRO_X, + BMI160_SCAN_GYRO_Y, + BMI160_SCAN_GYRO_Z, + BMI160_SCAN_ACCEL_X, + BMI160_SCAN_ACCEL_Y, + BMI160_SCAN_ACCEL_Z, + BMI160_SCAN_TIMESTAMP, +}; + +enum bmi160_sensor_type { + BMI160_ACCEL = 0, + BMI160_GYRO, + BMI160_EXT_MAGN, + BMI160_NUM_SENSORS /* must be last */ +}; + +enum bmi160_int_pin { + BMI160_PIN_INT1, + BMI160_PIN_INT2 +}; + +const struct regmap_config bmi160_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; +EXPORT_SYMBOL_NS(bmi160_regmap_config, "IIO_BMI160"); + +struct bmi160_regs { + u8 data; /* LSB byte register for X-axis */ + u8 config; + u8 config_odr_mask; + u8 config_bwp_mask; + u8 range; + u8 pmu_cmd_normal; + u8 pmu_cmd_suspend; +}; + +static const struct bmi160_regs bmi160_regs[] = { + [BMI160_ACCEL] = { + .data = BMI160_REG_DATA_ACCEL_XOUT_L, + .config = BMI160_REG_ACCEL_CONFIG, + .config_odr_mask = BMI160_ACCEL_CONFIG_ODR_MASK, + .config_bwp_mask = BMI160_ACCEL_CONFIG_BWP_MASK, + .range = BMI160_REG_ACCEL_RANGE, + .pmu_cmd_normal = BMI160_CMD_ACCEL_PM_NORMAL, + .pmu_cmd_suspend = BMI160_CMD_ACCEL_PM_SUSPEND, + }, + [BMI160_GYRO] = { + .data = BMI160_REG_DATA_GYRO_XOUT_L, + .config = BMI160_REG_GYRO_CONFIG, + .config_odr_mask = BMI160_GYRO_CONFIG_ODR_MASK, + .config_bwp_mask = BMI160_GYRO_CONFIG_BWP_MASK, + .range = BMI160_REG_GYRO_RANGE, + .pmu_cmd_normal = BMI160_CMD_GYRO_PM_NORMAL, + .pmu_cmd_suspend = BMI160_CMD_GYRO_PM_SUSPEND, + }, +}; + +static unsigned long bmi160_pmu_time[] = { + [BMI160_ACCEL] = BMI160_ACCEL_PMU_MIN_USLEEP, + [BMI160_GYRO] = BMI160_GYRO_PMU_MIN_USLEEP, +}; + +struct bmi160_scale { + u8 bits; + int uscale; +}; + +struct bmi160_odr { + u8 bits; + int odr; + int uodr; +}; + +static const struct bmi160_scale bmi160_accel_scale[] = { + { BMI160_ACCEL_RANGE_2G, 598}, + { BMI160_ACCEL_RANGE_4G, 1197}, + { BMI160_ACCEL_RANGE_8G, 2394}, + { BMI160_ACCEL_RANGE_16G, 4788}, +}; + +static const struct bmi160_scale bmi160_gyro_scale[] = { + { BMI160_GYRO_RANGE_2000DPS, 1065}, + { BMI160_GYRO_RANGE_1000DPS, 532}, + { BMI160_GYRO_RANGE_500DPS, 266}, + { BMI160_GYRO_RANGE_250DPS, 133}, + { BMI160_GYRO_RANGE_125DPS, 66}, +}; + +struct bmi160_scale_item { + const struct bmi160_scale *tbl; + int num; +}; + +static const struct bmi160_scale_item bmi160_scale_table[] = { + [BMI160_ACCEL] = { + .tbl = bmi160_accel_scale, + .num = ARRAY_SIZE(bmi160_accel_scale), + }, + [BMI160_GYRO] = { + .tbl = bmi160_gyro_scale, + .num = ARRAY_SIZE(bmi160_gyro_scale), + }, +}; + +static const struct bmi160_odr bmi160_accel_odr[] = { + {0x01, 0, 781250}, + {0x02, 1, 562500}, + {0x03, 3, 125000}, + {0x04, 6, 250000}, + {0x05, 12, 500000}, + {0x06, 25, 0}, + {0x07, 50, 0}, + {0x08, 100, 0}, + {0x09, 200, 0}, + {0x0A, 400, 0}, + {0x0B, 800, 0}, + {0x0C, 1600, 0}, +}; + +static const struct bmi160_odr bmi160_gyro_odr[] = { + {0x06, 25, 0}, + {0x07, 50, 0}, + {0x08, 100, 0}, + {0x09, 200, 0}, + {0x0A, 400, 0}, + {0x0B, 800, 0}, + {0x0C, 1600, 0}, + {0x0D, 3200, 0}, +}; + +struct bmi160_odr_item { + const struct bmi160_odr *tbl; + int num; +}; + +static const struct bmi160_odr_item bmi160_odr_table[] = { + [BMI160_ACCEL] = { + .tbl = bmi160_accel_odr, + .num = ARRAY_SIZE(bmi160_accel_odr), + }, + [BMI160_GYRO] = { + .tbl = bmi160_gyro_odr, + .num = ARRAY_SIZE(bmi160_gyro_odr), + }, +}; + +static const struct iio_mount_matrix * +bmi160_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct bmi160_data *data = iio_priv(indio_dev); + + return &data->orientation; +} + +static const struct iio_chan_spec_ext_info bmi160_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, bmi160_get_mount_matrix), + { } +}; + +static const struct iio_chan_spec bmi160_channels[] = { + BMI160_CHANNEL(IIO_ACCEL, X, BMI160_SCAN_ACCEL_X), + BMI160_CHANNEL(IIO_ACCEL, Y, BMI160_SCAN_ACCEL_Y), + BMI160_CHANNEL(IIO_ACCEL, Z, BMI160_SCAN_ACCEL_Z), + BMI160_CHANNEL(IIO_ANGL_VEL, X, BMI160_SCAN_GYRO_X), + BMI160_CHANNEL(IIO_ANGL_VEL, Y, BMI160_SCAN_GYRO_Y), + BMI160_CHANNEL(IIO_ANGL_VEL, Z, BMI160_SCAN_GYRO_Z), + IIO_CHAN_SOFT_TIMESTAMP(BMI160_SCAN_TIMESTAMP), +}; + +static enum bmi160_sensor_type bmi160_to_sensor(enum iio_chan_type iio_type) +{ + switch (iio_type) { + case IIO_ACCEL: + return BMI160_ACCEL; + case IIO_ANGL_VEL: + return BMI160_GYRO; + default: + return -EINVAL; + } +} + +static +int bmi160_set_mode(struct bmi160_data *data, enum bmi160_sensor_type t, + bool mode) +{ + int ret; + u8 cmd; + + if (mode) + cmd = bmi160_regs[t].pmu_cmd_normal; + else + cmd = bmi160_regs[t].pmu_cmd_suspend; + + ret = regmap_write(data->regmap, BMI160_REG_CMD, cmd); + if (ret) + return ret; + + usleep_range(bmi160_pmu_time[t], bmi160_pmu_time[t] + 1000); + + return 0; +} + +static +int bmi160_set_scale(struct bmi160_data *data, enum bmi160_sensor_type t, + int uscale) +{ + int i; + + for (i = 0; i < bmi160_scale_table[t].num; i++) + if (bmi160_scale_table[t].tbl[i].uscale == uscale) + break; + + if (i == bmi160_scale_table[t].num) + return -EINVAL; + + return regmap_write(data->regmap, bmi160_regs[t].range, + bmi160_scale_table[t].tbl[i].bits); +} + +static +int bmi160_get_scale(struct bmi160_data *data, enum bmi160_sensor_type t, + int *uscale) +{ + int i, ret, val; + + ret = regmap_read(data->regmap, bmi160_regs[t].range, &val); + if (ret) + return ret; + + for (i = 0; i < bmi160_scale_table[t].num; i++) + if (bmi160_scale_table[t].tbl[i].bits == val) { + *uscale = bmi160_scale_table[t].tbl[i].uscale; + return 0; + } + + return -EINVAL; +} + +static int bmi160_get_data(struct bmi160_data *data, int chan_type, + int axis, int *val) +{ + u8 reg; + int ret; + __le16 sample; + enum bmi160_sensor_type t = bmi160_to_sensor(chan_type); + + reg = bmi160_regs[t].data + (axis - IIO_MOD_X) * sizeof(sample); + + ret = regmap_bulk_read(data->regmap, reg, &sample, sizeof(sample)); + if (ret) + return ret; + + *val = sign_extend32(le16_to_cpu(sample), 15); + + return 0; +} + +static +int bmi160_set_odr(struct bmi160_data *data, enum bmi160_sensor_type t, + int odr, int uodr) +{ + int i; + + for (i = 0; i < bmi160_odr_table[t].num; i++) + if (bmi160_odr_table[t].tbl[i].odr == odr && + bmi160_odr_table[t].tbl[i].uodr == uodr) + break; + + if (i >= bmi160_odr_table[t].num) + return -EINVAL; + + return regmap_update_bits(data->regmap, + bmi160_regs[t].config, + bmi160_regs[t].config_odr_mask, + bmi160_odr_table[t].tbl[i].bits); +} + +static int bmi160_get_odr(struct bmi160_data *data, enum bmi160_sensor_type t, + int *odr, int *uodr) +{ + int i, val, ret; + + ret = regmap_read(data->regmap, bmi160_regs[t].config, &val); + if (ret) + return ret; + + val &= bmi160_regs[t].config_odr_mask; + + for (i = 0; i < bmi160_odr_table[t].num; i++) + if (val == bmi160_odr_table[t].tbl[i].bits) + break; + + if (i >= bmi160_odr_table[t].num) + return -EINVAL; + + *odr = bmi160_odr_table[t].tbl[i].odr; + *uodr = bmi160_odr_table[t].tbl[i].uodr; + + return 0; +} + +static irqreturn_t bmi160_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bmi160_data *data = iio_priv(indio_dev); + int i, ret, j = 0, base = BMI160_REG_DATA_MAGN_XOUT_L; + __le16 sample; + + iio_for_each_active_channel(indio_dev, i) { + ret = regmap_bulk_read(data->regmap, base + i * sizeof(sample), + &sample, sizeof(sample)); + if (ret) + goto done; + data->buf[j++] = sample; + } + + iio_push_to_buffers_with_timestamp(indio_dev, data->buf, pf->timestamp); +done: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int bmi160_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct bmi160_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = bmi160_get_data(data, chan->type, chan->channel2, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + ret = bmi160_get_scale(data, + bmi160_to_sensor(chan->type), val2); + return ret ? ret : IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = bmi160_get_odr(data, bmi160_to_sensor(chan->type), + val, val2); + return ret ? ret : IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + + return 0; +} + +static int bmi160_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bmi160_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return bmi160_set_scale(data, + bmi160_to_sensor(chan->type), val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return bmi160_set_odr(data, bmi160_to_sensor(chan->type), + val, val2); + default: + return -EINVAL; + } + + return 0; +} + +static +IIO_CONST_ATTR(in_accel_sampling_frequency_available, + "0.78125 1.5625 3.125 6.25 12.5 25 50 100 200 400 800 1600"); +static +IIO_CONST_ATTR(in_anglvel_sampling_frequency_available, + "25 50 100 200 400 800 1600 3200"); +static +IIO_CONST_ATTR(in_accel_scale_available, + "0.000598 0.001197 0.002394 0.004788"); +static +IIO_CONST_ATTR(in_anglvel_scale_available, + "0.001065 0.000532 0.000266 0.000133 0.000066"); + +static struct attribute *bmi160_attrs[] = { + &iio_const_attr_in_accel_sampling_frequency_available.dev_attr.attr, + &iio_const_attr_in_anglvel_sampling_frequency_available.dev_attr.attr, + &iio_const_attr_in_accel_scale_available.dev_attr.attr, + &iio_const_attr_in_anglvel_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group bmi160_attrs_group = { + .attrs = bmi160_attrs, +}; + +static const struct iio_info bmi160_info = { + .read_raw = bmi160_read_raw, + .write_raw = bmi160_write_raw, + .attrs = &bmi160_attrs_group, +}; + +static int bmi160_write_conf_reg(struct regmap *regmap, unsigned int reg, + unsigned int mask, unsigned int bits, + unsigned int write_usleep) +{ + int ret; + unsigned int val; + + ret = regmap_read(regmap, reg, &val); + if (ret) + return ret; + + val = (val & ~mask) | bits; + + ret = regmap_write(regmap, reg, val); + if (ret) + return ret; + + /* + * We need to wait after writing before we can write again. See the + * datasheet, page 93. + */ + usleep_range(write_usleep, write_usleep + 1000); + + return 0; +} + +static int bmi160_config_pin(struct regmap *regmap, enum bmi160_int_pin pin, + bool open_drain, u8 irq_mask, + unsigned long write_usleep) +{ + int ret; + struct device *dev = regmap_get_device(regmap); + u8 int_out_ctrl_shift; + u8 int_latch_mask; + u8 int_map_mask; + u8 int_out_ctrl_mask; + u8 int_out_ctrl_bits; + const char *pin_name; + + switch (pin) { + case BMI160_PIN_INT1: + int_out_ctrl_shift = BMI160_INT1_OUT_CTRL_SHIFT; + int_latch_mask = BMI160_INT1_LATCH_MASK; + int_map_mask = BMI160_INT1_MAP_DRDY_EN; + pin_name = "INT1"; + break; + case BMI160_PIN_INT2: + int_out_ctrl_shift = BMI160_INT2_OUT_CTRL_SHIFT; + int_latch_mask = BMI160_INT2_LATCH_MASK; + int_map_mask = BMI160_INT2_MAP_DRDY_EN; + pin_name = "INT2"; + break; + default: + return -EINVAL; + } + int_out_ctrl_mask = BMI160_INT_OUT_CTRL_MASK << int_out_ctrl_shift; + + /* + * Enable the requested pin with the right settings: + * - Push-pull/open-drain + * - Active low/high + * - Edge/level triggered + */ + int_out_ctrl_bits = BMI160_OUTPUT_EN; + if (open_drain) + /* Default is push-pull. */ + int_out_ctrl_bits |= BMI160_OPEN_DRAIN; + int_out_ctrl_bits |= irq_mask; + int_out_ctrl_bits <<= int_out_ctrl_shift; + + ret = bmi160_write_conf_reg(regmap, BMI160_REG_INT_OUT_CTRL, + int_out_ctrl_mask, int_out_ctrl_bits, + write_usleep); + if (ret) + return ret; + + /* Set the pin to input mode with no latching. */ + ret = bmi160_write_conf_reg(regmap, BMI160_REG_INT_LATCH, + int_latch_mask, int_latch_mask, + write_usleep); + if (ret) + return ret; + + /* Map interrupts to the requested pin. */ + ret = bmi160_write_conf_reg(regmap, BMI160_REG_INT_MAP, + int_map_mask, int_map_mask, + write_usleep); + if (ret) + dev_err(dev, "Failed to configure %s IRQ pin", pin_name); + + return ret; +} + +int bmi160_enable_irq(struct regmap *regmap, bool enable) +{ + unsigned int enable_bit = 0; + + if (enable) + enable_bit = BMI160_DRDY_INT_EN; + + return bmi160_write_conf_reg(regmap, BMI160_REG_INT_EN, + BMI160_DRDY_INT_EN, enable_bit, + BMI160_NORMAL_WRITE_USLEEP); +} +EXPORT_SYMBOL_NS(bmi160_enable_irq, "IIO_BMI160"); + +static int bmi160_get_irq(struct fwnode_handle *fwnode, enum bmi160_int_pin *pin) +{ + int irq; + + /* Use INT1 if possible, otherwise fall back to INT2. */ + irq = fwnode_irq_get_byname(fwnode, "INT1"); + if (irq > 0) { + *pin = BMI160_PIN_INT1; + return irq; + } + + irq = fwnode_irq_get_byname(fwnode, "INT2"); + if (irq > 0) + *pin = BMI160_PIN_INT2; + + return irq; +} + +static int bmi160_config_device_irq(struct iio_dev *indio_dev, int irq_type, + enum bmi160_int_pin pin) +{ + bool open_drain; + u8 irq_mask; + struct bmi160_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); + + /* Level-triggered, active-low is the default if we set all zeroes. */ + if (irq_type == IRQF_TRIGGER_RISING) + irq_mask = BMI160_ACTIVE_HIGH | BMI160_EDGE_TRIGGERED; + else if (irq_type == IRQF_TRIGGER_FALLING) + irq_mask = BMI160_EDGE_TRIGGERED; + else if (irq_type == IRQF_TRIGGER_HIGH) + irq_mask = BMI160_ACTIVE_HIGH; + else if (irq_type == IRQF_TRIGGER_LOW) + irq_mask = 0; + else { + dev_err(&indio_dev->dev, + "Invalid interrupt type 0x%x specified\n", irq_type); + return -EINVAL; + } + + open_drain = device_property_read_bool(dev, "drive-open-drain"); + + return bmi160_config_pin(data->regmap, pin, open_drain, irq_mask, + BMI160_NORMAL_WRITE_USLEEP); +} + +static int bmi160_setup_irq(struct iio_dev *indio_dev, int irq, + enum bmi160_int_pin pin) +{ + u32 irq_type = irq_get_trigger_type(irq); + int ret; + + ret = bmi160_config_device_irq(indio_dev, irq_type, pin); + if (ret) + return ret; + + return bmi160_probe_trigger(indio_dev, irq, irq_type); +} + +static int bmi160_check_chip_id(const u8 chip_id) +{ + for (int i = 0; i < ARRAY_SIZE(bmi_chip_ids); i++) { + if (chip_id == bmi_chip_ids[i]) + return 0; + } + + return -ENODEV; +} + +static int bmi160_chip_init(struct bmi160_data *data, bool use_spi) +{ + int ret; + unsigned int val; + struct device *dev = regmap_get_device(data->regmap); + + ret = regulator_bulk_enable(ARRAY_SIZE(data->supplies), data->supplies); + if (ret) { + dev_err(dev, "Failed to enable regulators: %d\n", ret); + return ret; + } + + ret = regmap_write(data->regmap, BMI160_REG_CMD, BMI160_CMD_SOFTRESET); + if (ret) + goto disable_regulator; + + usleep_range(BMI160_SOFTRESET_USLEEP, BMI160_SOFTRESET_USLEEP + 1); + + /* + * CS rising edge is needed before starting SPI, so do a dummy read + * See Section 3.2.1, page 86 of the datasheet + */ + if (use_spi) { + ret = regmap_read(data->regmap, BMI160_REG_DUMMY, &val); + if (ret) + goto disable_regulator; + } + + ret = regmap_read(data->regmap, BMI160_REG_CHIP_ID, &val); + if (ret) { + dev_err(dev, "Error reading chip id\n"); + goto disable_regulator; + } + + ret = bmi160_check_chip_id(val); + if (ret) + dev_warn(dev, "Chip id not found: %x\n", val); + + ret = bmi160_set_mode(data, BMI160_ACCEL, true); + if (ret) + goto disable_regulator; + + ret = bmi160_set_mode(data, BMI160_GYRO, true); + if (ret) + goto disable_accel; + + return 0; + +disable_accel: + bmi160_set_mode(data, BMI160_ACCEL, false); + +disable_regulator: + regulator_bulk_disable(ARRAY_SIZE(data->supplies), data->supplies); + return ret; +} + +static int bmi160_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool enable) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct bmi160_data *data = iio_priv(indio_dev); + + return bmi160_enable_irq(data->regmap, enable); +} + +static const struct iio_trigger_ops bmi160_trigger_ops = { + .set_trigger_state = &bmi160_data_rdy_trigger_set_state, +}; + +int bmi160_probe_trigger(struct iio_dev *indio_dev, int irq, u32 irq_type) +{ + struct bmi160_data *data = iio_priv(indio_dev); + int ret; + + data->trig = devm_iio_trigger_alloc(&indio_dev->dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + + if (data->trig == NULL) + return -ENOMEM; + + ret = devm_request_irq(&indio_dev->dev, irq, + &iio_trigger_generic_data_rdy_poll, + irq_type | IRQF_NO_THREAD, + "bmi160", data->trig); + if (ret) + return ret; + + data->trig->dev.parent = regmap_get_device(data->regmap); + data->trig->ops = &bmi160_trigger_ops; + iio_trigger_set_drvdata(data->trig, indio_dev); + + ret = devm_iio_trigger_register(&indio_dev->dev, data->trig); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(data->trig); + + return 0; +} + +static void bmi160_chip_uninit(void *data) +{ + struct bmi160_data *bmi_data = data; + struct device *dev = regmap_get_device(bmi_data->regmap); + int ret; + + bmi160_set_mode(bmi_data, BMI160_GYRO, false); + bmi160_set_mode(bmi_data, BMI160_ACCEL, false); + + ret = regulator_bulk_disable(ARRAY_SIZE(bmi_data->supplies), + bmi_data->supplies); + if (ret) + dev_err(dev, "Failed to disable regulators: %d\n", ret); +} + +int bmi160_core_probe(struct device *dev, struct regmap *regmap, + const char *name, bool use_spi) +{ + struct iio_dev *indio_dev; + struct bmi160_data *data; + int irq; + enum bmi160_int_pin int_pin; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + dev_set_drvdata(dev, indio_dev); + data->regmap = regmap; + + data->supplies[0].supply = "vdd"; + data->supplies[1].supply = "vddio"; + ret = devm_regulator_bulk_get(dev, + ARRAY_SIZE(data->supplies), + data->supplies); + if (ret) { + dev_err(dev, "Failed to get regulators: %d\n", ret); + return ret; + } + + ret = iio_read_mount_matrix(dev, &data->orientation); + if (ret) + return ret; + + ret = bmi160_chip_init(data, use_spi); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, bmi160_chip_uninit, data); + if (ret) + return ret; + + indio_dev->channels = bmi160_channels; + indio_dev->num_channels = ARRAY_SIZE(bmi160_channels); + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &bmi160_info; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + bmi160_trigger_handler, NULL); + if (ret) + return ret; + + irq = bmi160_get_irq(dev_fwnode(dev), &int_pin); + if (irq > 0) { + ret = bmi160_setup_irq(indio_dev, irq, int_pin); + if (ret) + dev_err(&indio_dev->dev, "Failed to setup IRQ %d\n", + irq); + } else { + dev_info(&indio_dev->dev, "Not setting up IRQ trigger\n"); + } + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS_GPL(bmi160_core_probe, "IIO_BMI160"); + +static int bmi160_core_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + return iio_device_suspend_triggering(indio_dev); +} + +static int bmi160_core_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + return iio_device_resume_triggering(indio_dev); +} + +const struct dev_pm_ops bmi160_core_pm_ops = { + RUNTIME_PM_OPS(bmi160_core_runtime_suspend, bmi160_core_runtime_resume, NULL) +}; +EXPORT_SYMBOL_NS_GPL(bmi160_core_pm_ops, "IIO_BMI160"); + +MODULE_AUTHOR("Daniel Baluta "); +MODULE_DESCRIPTION("Bosch BMI160 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/imu/bmi160/bmi160_i2c.c b/drivers/iio/imu/bmi160/bmi160_i2c.c new file mode 100644 index 000000000..3f3ee044a --- /dev/null +++ b/drivers/iio/imu/bmi160/bmi160_i2c.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * BMI160 - Bosch IMU, I2C bits + * + * Copyright (c) 2016, Intel Corporation. + * + * 7-bit I2C slave address is: + * - 0x68 if SDO is pulled to GND + * - 0x69 if SDO is pulled to VDDIO + */ +#include +#include +#include +#include + +#include "bmi160.h" + +static int bmi160_i2c_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct regmap *regmap; + const char *name; + + regmap = devm_regmap_init_i2c(client, &bmi160_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Failed to register i2c regmap: %pe\n", + regmap); + return PTR_ERR(regmap); + } + + if (id) + name = id->name; + else + name = dev_name(&client->dev); + + return bmi160_core_probe(&client->dev, regmap, name, false); +} + +static const struct i2c_device_id bmi160_i2c_id[] = { + { .name = "bmi120" }, + { .name = "bmi160" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bmi160_i2c_id); + +static const struct acpi_device_id bmi160_acpi_match[] = { + /* + * FIRMWARE BUG WORKAROUND + * Some manufacturers like GPD, Lenovo or Aya used the incorrect + * ID "10EC5280" for bmi160 in their DSDT. A fixed firmware is not + * available as of Feb 2024 after trying to work with OEMs, and + * this is not expected to change anymore since at least some of + * the affected devices are from 2021/2022. + */ + {"10EC5280", 0}, + {"BMI0120", 0}, + {"BMI0160", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, bmi160_acpi_match); + +static const struct of_device_id bmi160_of_match[] = { + { .compatible = "bosch,bmi120" }, + { .compatible = "bosch,bmi160" }, + { } +}; +MODULE_DEVICE_TABLE(of, bmi160_of_match); + +static struct i2c_driver bmi160_i2c_driver = { + .driver = { + .name = "bmi160_i2c", + .pm = pm_ptr(&bmi160_core_pm_ops), + .acpi_match_table = bmi160_acpi_match, + .of_match_table = bmi160_of_match, + }, + .probe = bmi160_i2c_probe, + .id_table = bmi160_i2c_id, +}; +module_i2c_driver(bmi160_i2c_driver); + +MODULE_AUTHOR("Daniel Baluta "); +MODULE_DESCRIPTION("BMI160 I2C driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_BMI160"); diff --git a/drivers/iio/imu/bmi160/bmi160_spi.c b/drivers/iio/imu/bmi160/bmi160_spi.c new file mode 100644 index 000000000..107eb3374 --- /dev/null +++ b/drivers/iio/imu/bmi160/bmi160_spi.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * BMI160 - Bosch IMU, SPI bits + * + * Copyright (c) 2016, Intel Corporation. + * + */ +#include +#include +#include +#include + +#include "bmi160.h" + +static int bmi160_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + const struct spi_device_id *id = spi_get_device_id(spi); + const char *name; + + regmap = devm_regmap_init_spi(spi, &bmi160_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "Failed to register spi regmap: %pe\n", + regmap); + return PTR_ERR(regmap); + } + + if (id) + name = id->name; + else + name = dev_name(&spi->dev); + + return bmi160_core_probe(&spi->dev, regmap, name, true); +} + +static const struct spi_device_id bmi160_spi_id[] = { + { .name = "bmi120" }, + { .name = "bmi160" }, + { } +}; +MODULE_DEVICE_TABLE(spi, bmi160_spi_id); + +static const struct acpi_device_id bmi160_acpi_match[] = { + {"BMI0120", 0}, + {"BMI0160", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, bmi160_acpi_match); + +static const struct of_device_id bmi160_of_match[] = { + { .compatible = "bosch,bmi120" }, + { .compatible = "bosch,bmi160" }, + { } +}; +MODULE_DEVICE_TABLE(of, bmi160_of_match); + +static struct spi_driver bmi160_spi_driver = { + .probe = bmi160_spi_probe, + .id_table = bmi160_spi_id, + .driver = { + .acpi_match_table = bmi160_acpi_match, + .of_match_table = bmi160_of_match, + .name = "bmi160_spi", + .pm = pm_ptr(&bmi160_core_pm_ops), + }, +}; +module_spi_driver(bmi160_spi_driver); + +MODULE_AUTHOR("Daniel Baluta +#include + +struct bmi270_chip_info { + const char *name; + int chip_id; + const char *fw_name; +}; + +extern const struct regmap_config bmi270_regmap_config; +extern const struct bmi270_chip_info bmi260_chip_info; +extern const struct bmi270_chip_info bmi270_chip_info; + +struct device; +int bmi270_core_probe(struct device *dev, struct regmap *regmap, + const struct bmi270_chip_info *chip_info); + +extern const struct dev_pm_ops bmi270_core_pm_ops; + +#endif /* BMI270_H_ */ diff --git a/drivers/iio/imu/bmi270/bmi270_core.c b/drivers/iio/imu/bmi270/bmi270_core.c new file mode 100644 index 000000000..fbcf6bb87 --- /dev/null +++ b/drivers/iio/imu/bmi270/bmi270_core.c @@ -0,0 +1,1664 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "bmi270.h" + +#define BMI270_CHIP_ID_REG 0x00 + +/* Checked to prevent sending incompatible firmware to BMI160 devices */ +#define BMI160_CHIP_ID_VAL 0xD1 + +#define BMI260_CHIP_ID_VAL 0x27 +#define BMI270_CHIP_ID_VAL 0x24 +#define BMI270_CHIP_ID_MSK GENMASK(7, 0) + +#define BMI270_ACCEL_X_REG 0x0c +#define BMI270_ANG_VEL_X_REG 0x12 + +#define BMI270_INT_STATUS_0_REG 0x1c +#define BMI270_INT_STATUS_0_STEP_CNT_MSK BIT(1) +#define BMI270_INT_STATUS_0_NOMOTION_MSK BIT(5) +#define BMI270_INT_STATUS_0_MOTION_MSK BIT(6) + +#define BMI270_INT_STATUS_1_REG 0x1d +#define BMI270_INT_STATUS_1_ACC_GYR_DRDY_MSK GENMASK(7, 6) + +#define BMI270_SC_OUT_0_REG 0x1e + +#define BMI270_INTERNAL_STATUS_REG 0x21 +#define BMI270_INTERNAL_STATUS_MSG_MSK GENMASK(3, 0) +#define BMI270_INTERNAL_STATUS_MSG_INIT_OK 0x01 +#define BMI270_INTERNAL_STATUS_AXES_REMAP_ERR_MSK BIT(5) +#define BMI270_INTERNAL_STATUS_ODR_50HZ_ERR_MSK BIT(6) + +#define BMI270_TEMPERATURE_0_REG 0x22 + +#define BMI270_FEAT_PAGE_REG 0x2f + +#define BMI270_ACC_CONF_REG 0x40 +#define BMI270_ACC_CONF_ODR_MSK GENMASK(3, 0) +#define BMI270_ACC_CONF_ODR_100HZ 0x08 +#define BMI270_ACC_CONF_BWP_MSK GENMASK(6, 4) +#define BMI270_ACC_CONF_BWP_NORMAL_MODE 0x02 +#define BMI270_ACC_CONF_FILTER_PERF_MSK BIT(7) + +#define BMI270_ACC_CONF_RANGE_REG 0x41 +#define BMI270_ACC_CONF_RANGE_MSK GENMASK(1, 0) + +#define BMI270_GYR_CONF_REG 0x42 +#define BMI270_GYR_CONF_ODR_MSK GENMASK(3, 0) +#define BMI270_GYR_CONF_ODR_200HZ 0x09 +#define BMI270_GYR_CONF_BWP_MSK GENMASK(5, 4) +#define BMI270_GYR_CONF_BWP_NORMAL_MODE 0x02 +#define BMI270_GYR_CONF_NOISE_PERF_MSK BIT(6) +#define BMI270_GYR_CONF_FILTER_PERF_MSK BIT(7) + +#define BMI270_GYR_CONF_RANGE_REG 0x43 +#define BMI270_GYR_CONF_RANGE_MSK GENMASK(2, 0) + +#define BMI270_INT1_IO_CTRL_REG 0x53 +#define BMI270_INT2_IO_CTRL_REG 0x54 +#define BMI270_INT_IO_CTRL_LVL_MSK BIT(1) +#define BMI270_INT_IO_CTRL_OD_MSK BIT(2) +#define BMI270_INT_IO_CTRL_OP_MSK BIT(3) +#define BMI270_INT_IO_LVL_OD_OP_MSK GENMASK(3, 1) + +#define BMI270_INT_LATCH_REG 0x55 +#define BMI270_INT_LATCH_REG_MSK BIT(0) + +#define BMI270_INT1_MAP_FEAT_REG 0x56 +#define BMI270_INT2_MAP_FEAT_REG 0x57 +#define BMI270_INT_MAP_FEAT_STEP_CNT_WTRMRK_MSK BIT(1) +#define BMI270_INT_MAP_FEAT_NOMOTION_MSK BIT(5) +#define BMI270_INT_MAP_FEAT_ANYMOTION_MSK BIT(6) + +#define BMI270_INT_MAP_DATA_REG 0x58 +#define BMI270_INT_MAP_DATA_DRDY_INT1_MSK BIT(2) +#define BMI270_INT_MAP_DATA_DRDY_INT2_MSK BIT(6) + +#define BMI270_INIT_CTRL_REG 0x59 +#define BMI270_INIT_CTRL_LOAD_DONE_MSK BIT(0) + +#define BMI270_INIT_DATA_REG 0x5e + +#define BMI270_PWR_CONF_REG 0x7c +#define BMI270_PWR_CONF_ADV_PWR_SAVE_MSK BIT(0) +#define BMI270_PWR_CONF_FIFO_WKUP_MSK BIT(1) +#define BMI270_PWR_CONF_FUP_EN_MSK BIT(2) + +#define BMI270_PWR_CTRL_REG 0x7d +#define BMI270_PWR_CTRL_AUX_EN_MSK BIT(0) +#define BMI270_PWR_CTRL_GYR_EN_MSK BIT(1) +#define BMI270_PWR_CTRL_ACCEL_EN_MSK BIT(2) +#define BMI270_PWR_CTRL_TEMP_EN_MSK BIT(3) + +#define BMI270_STEP_SC26_WTRMRK_MSK GENMASK(9, 0) +#define BMI270_STEP_SC26_RST_CNT_MSK BIT(10) +#define BMI270_STEP_SC26_EN_CNT_MSK BIT(12) + +#define BMI270_FEAT_MOTION_DURATION_MSK GENMASK(12, 0) +#define BMI270_FEAT_MOTION_X_EN_MSK BIT(13) +#define BMI270_FEAT_MOTION_Y_EN_MSK BIT(14) +#define BMI270_FEAT_MOTION_Z_EN_MSK BIT(15) +#define BMI270_FEAT_MOTION_XYZ_EN_MSK GENMASK(15, 13) +#define BMI270_FEAT_MOTION_THRESHOLD_MSK GENMASK(10, 0) +#define BMI270_FEAT_MOTION_OUT_CONF_MSK GENMASK(14, 11) +#define BMI270_FEAT_MOTION_ENABLE_MSK BIT(15) + +#define BMI270_MOTION_XYZ_MSK GENMASK(2, 0) + +/* See pages 92 and 93 of the datasheet */ +#define BMI270_MOTION_THRES_FULL_SCALE GENMASK(10, 0) +#define BMI270_MOTION_DURAT_SCALE 50 +#define BMI270_MOTION_DURAT_MAX 162 + +/* 9.81 * 1000000 m/s^2 */ +#define BMI270_G_MICRO_M_S_2 9810000 + +/* See datasheet section 4.6.14, Temperature Sensor */ +#define BMI270_TEMP_OFFSET 11776 +#define BMI270_TEMP_SCALE 1953125 + +/* See page 90 of datasheet. The step counter "holds implicitly a 20x factor" */ +#define BMI270_STEP_COUNTER_FACTOR 20 +#define BMI270_STEP_COUNTER_MAX 20460 + +#define BMI270_INT_MICRO_TO_RAW(val, val2, scale) \ + ((val) * (scale) + ((val2) * (scale)) / MEGA) +#define BMI270_RAW_TO_MICRO(raw, scale) \ + ((((raw) % (scale)) * MEGA) / scale) + +#define BMI260_INIT_DATA_FILE "bmi260-init-data.fw" +#define BMI270_INIT_DATA_FILE "bmi270-init-data.fw" + +enum bmi270_irq_pin { + BMI270_IRQ_DISABLED, + BMI270_IRQ_INT1, + BMI270_IRQ_INT2, +}; + +struct bmi270_data { + struct device *dev; + struct regmap *regmap; + const struct bmi270_chip_info *chip_info; + enum bmi270_irq_pin irq_pin; + struct iio_trigger *trig; + /* Protect device's private data from concurrent access */ + struct mutex mutex; + bool steps_enabled; + + /* + * Where IIO_DMA_MINALIGN may be larger than 8 bytes, align to + * that to ensure a DMA safe buffer. + */ + struct { + __le16 channels[6]; + aligned_s64 timestamp; + } buffer __aligned(IIO_DMA_MINALIGN); + /* + * Variable to access feature registers. It can be accessed concurrently + * with the 'buffer' variable + */ + __le16 regval __aligned(IIO_DMA_MINALIGN); +}; + +enum bmi270_scan { + BMI270_SCAN_ACCEL_X, + BMI270_SCAN_ACCEL_Y, + BMI270_SCAN_ACCEL_Z, + BMI270_SCAN_GYRO_X, + BMI270_SCAN_GYRO_Y, + BMI270_SCAN_GYRO_Z, + BMI270_SCAN_TIMESTAMP, +}; + +static const unsigned long bmi270_avail_scan_masks[] = { + (BIT(BMI270_SCAN_ACCEL_X) | + BIT(BMI270_SCAN_ACCEL_Y) | + BIT(BMI270_SCAN_ACCEL_Z) | + BIT(BMI270_SCAN_GYRO_X) | + BIT(BMI270_SCAN_GYRO_Y) | + BIT(BMI270_SCAN_GYRO_Z)), + 0 +}; + +const struct bmi270_chip_info bmi260_chip_info = { + .name = "bmi260", + .chip_id = BMI260_CHIP_ID_VAL, + .fw_name = BMI260_INIT_DATA_FILE, +}; +EXPORT_SYMBOL_NS_GPL(bmi260_chip_info, "IIO_BMI270"); + +const struct bmi270_chip_info bmi270_chip_info = { + .name = "bmi270", + .chip_id = BMI270_CHIP_ID_VAL, + .fw_name = BMI270_INIT_DATA_FILE, +}; +EXPORT_SYMBOL_NS_GPL(bmi270_chip_info, "IIO_BMI270"); + +enum bmi270_sensor_type { + BMI270_ACCEL = 0, + BMI270_GYRO, + BMI270_TEMP, +}; + +struct bmi270_scale { + int scale; + int uscale; +}; + +struct bmi270_odr { + int odr; + int uodr; +}; + +static const struct bmi270_scale bmi270_accel_scale[] = { + { 0, 598 }, + { 0, 1197 }, + { 0, 2394 }, + { 0, 4788 }, +}; + +static const struct bmi270_scale bmi270_gyro_scale[] = { + { 0, 1065 }, + { 0, 532 }, + { 0, 266 }, + { 0, 133 }, + { 0, 66 }, +}; + +static const struct bmi270_scale bmi270_temp_scale[] = { + { BMI270_TEMP_SCALE / MICRO, BMI270_TEMP_SCALE % MICRO }, +}; + +struct bmi270_scale_item { + const struct bmi270_scale *tbl; + int num; +}; + +static const struct bmi270_scale_item bmi270_scale_table[] = { + [BMI270_ACCEL] = { + .tbl = bmi270_accel_scale, + .num = ARRAY_SIZE(bmi270_accel_scale), + }, + [BMI270_GYRO] = { + .tbl = bmi270_gyro_scale, + .num = ARRAY_SIZE(bmi270_gyro_scale), + }, + [BMI270_TEMP] = { + .tbl = bmi270_temp_scale, + .num = ARRAY_SIZE(bmi270_temp_scale), + }, +}; + +static const struct bmi270_odr bmi270_accel_odr[] = { + { 0, 781250 }, + { 1, 562500 }, + { 3, 125000 }, + { 6, 250000 }, + { 12, 500000 }, + { 25, 0 }, + { 50, 0 }, + { 100, 0 }, + { 200, 0 }, + { 400, 0 }, + { 800, 0 }, + { 1600, 0 }, +}; + +static const u8 bmi270_accel_odr_vals[] = { + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0A, + 0x0B, + 0x0C, +}; + +static const struct bmi270_odr bmi270_gyro_odr[] = { + { 25, 0 }, + { 50, 0 }, + { 100, 0 }, + { 200, 0 }, + { 400, 0 }, + { 800, 0 }, + { 1600, 0 }, + { 3200, 0 }, +}; + +static const u8 bmi270_gyro_odr_vals[] = { + 0x06, + 0x07, + 0x08, + 0x09, + 0x0A, + 0x0B, + 0x0C, + 0x0D, +}; + +struct bmi270_odr_item { + const struct bmi270_odr *tbl; + const u8 *vals; + int num; +}; + +static const struct bmi270_odr_item bmi270_odr_table[] = { + [BMI270_ACCEL] = { + .tbl = bmi270_accel_odr, + .vals = bmi270_accel_odr_vals, + .num = ARRAY_SIZE(bmi270_accel_odr), + }, + [BMI270_GYRO] = { + .tbl = bmi270_gyro_odr, + .vals = bmi270_gyro_odr_vals, + .num = ARRAY_SIZE(bmi270_gyro_odr), + }, +}; + +enum bmi270_feature_reg_id { + /* Page 1 registers */ + BMI270_ANYMO1_REG, + BMI270_ANYMO2_REG, + /* Page 2 registers */ + BMI270_NOMO1_REG, + BMI270_NOMO2_REG, + /* Page 6 registers */ + BMI270_SC_26_REG, +}; + +struct bmi270_feature_reg { + u8 page; + u8 addr; +}; + +static const struct bmi270_feature_reg bmi270_feature_regs[] = { + [BMI270_ANYMO1_REG] = { + .page = 1, + .addr = 0x3c, + }, + [BMI270_ANYMO2_REG] = { + .page = 1, + .addr = 0x3e, + }, + [BMI270_NOMO1_REG] = { + .page = 2, + .addr = 0x30, + }, + [BMI270_NOMO2_REG] = { + .page = 2, + .addr = 0x32, + }, + [BMI270_SC_26_REG] = { + .page = 6, + .addr = 0x32, + }, +}; + +static int bmi270_write_feature_reg(struct bmi270_data *data, + enum bmi270_feature_reg_id id, + u16 val) +{ + const struct bmi270_feature_reg *reg = &bmi270_feature_regs[id]; + int ret; + + ret = regmap_write(data->regmap, BMI270_FEAT_PAGE_REG, reg->page); + if (ret) + return ret; + + data->regval = cpu_to_le16(val); + return regmap_bulk_write(data->regmap, reg->addr, &data->regval, + sizeof(data->regval)); +} + +static int bmi270_read_feature_reg(struct bmi270_data *data, + enum bmi270_feature_reg_id id, + u16 *val) +{ + const struct bmi270_feature_reg *reg = &bmi270_feature_regs[id]; + int ret; + + ret = regmap_write(data->regmap, BMI270_FEAT_PAGE_REG, reg->page); + if (ret) + return ret; + + ret = regmap_bulk_read(data->regmap, reg->addr, &data->regval, + sizeof(data->regval)); + if (ret) + return ret; + + *val = le16_to_cpu(data->regval); + return 0; +} + +static int bmi270_update_feature_reg(struct bmi270_data *data, + enum bmi270_feature_reg_id id, + u16 mask, u16 val) +{ + u16 regval; + int ret; + + ret = bmi270_read_feature_reg(data, id, ®val); + if (ret) + return ret; + + regval = (regval & ~mask) | (val & mask); + + return bmi270_write_feature_reg(data, id, regval); +} + +static int bmi270_enable_steps(struct bmi270_data *data, int val) +{ + int ret; + + guard(mutex)(&data->mutex); + if (data->steps_enabled) + return 0; + + ret = bmi270_update_feature_reg(data, BMI270_SC_26_REG, + BMI270_STEP_SC26_EN_CNT_MSK, + FIELD_PREP(BMI270_STEP_SC26_EN_CNT_MSK, + val ? 1 : 0)); + if (ret) + return ret; + + data->steps_enabled = true; + return 0; +} + +static int bmi270_read_steps(struct bmi270_data *data, int *val) +{ + __le16 steps_count; + int ret; + + ret = regmap_bulk_read(data->regmap, BMI270_SC_OUT_0_REG, &steps_count, + sizeof(steps_count)); + if (ret) + return ret; + + *val = sign_extend32(le16_to_cpu(steps_count), 15); + return IIO_VAL_INT; +} + +static int bmi270_int_map_reg(enum bmi270_irq_pin pin) +{ + switch (pin) { + case BMI270_IRQ_INT1: + return BMI270_INT1_MAP_FEAT_REG; + case BMI270_IRQ_INT2: + return BMI270_INT2_MAP_FEAT_REG; + default: + return -EINVAL; + } +} + +static int bmi270_step_wtrmrk_en(struct bmi270_data *data, bool state) +{ + int reg; + + guard(mutex)(&data->mutex); + if (!data->steps_enabled) + return -EINVAL; + + reg = bmi270_int_map_reg(data->irq_pin); + if (reg < 0) + return reg; + + return regmap_update_bits(data->regmap, reg, + BMI270_INT_MAP_FEAT_STEP_CNT_WTRMRK_MSK, + FIELD_PREP(BMI270_INT_MAP_FEAT_STEP_CNT_WTRMRK_MSK, + state)); +} + +static int bmi270_motion_reg(enum iio_event_type type, enum iio_event_info info) +{ + switch (info) { + case IIO_EV_INFO_PERIOD: + switch (type) { + case IIO_EV_TYPE_MAG_ADAPTIVE: + return BMI270_ANYMO1_REG; + case IIO_EV_TYPE_ROC: + return BMI270_NOMO1_REG; + default: + return -EINVAL; + } + case IIO_EV_INFO_VALUE: + switch (type) { + case IIO_EV_TYPE_MAG_ADAPTIVE: + return BMI270_ANYMO2_REG; + case IIO_EV_TYPE_ROC: + return BMI270_NOMO2_REG; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int bmi270_anymotion_event_en(struct bmi270_data *data, + struct iio_chan_spec const *chan, + bool state) +{ + u16 axis_msk, axis_field_val, regval; + int ret, irq_reg; + bool axis_en; + + irq_reg = bmi270_int_map_reg(data->irq_pin); + if (irq_reg < 0) + return irq_reg; + + guard(mutex)(&data->mutex); + + ret = bmi270_read_feature_reg(data, BMI270_ANYMO1_REG, ®val); + if (ret) + return ret; + + switch (chan->channel2) { + case IIO_MOD_X: + axis_msk = BMI270_FEAT_MOTION_X_EN_MSK; + axis_field_val = FIELD_PREP(BMI270_FEAT_MOTION_X_EN_MSK, state); + axis_en = FIELD_GET(BMI270_FEAT_MOTION_Y_EN_MSK, regval) | + FIELD_GET(BMI270_FEAT_MOTION_Z_EN_MSK, regval); + break; + case IIO_MOD_Y: + axis_msk = BMI270_FEAT_MOTION_Y_EN_MSK; + axis_field_val = FIELD_PREP(BMI270_FEAT_MOTION_Y_EN_MSK, state); + axis_en = FIELD_GET(BMI270_FEAT_MOTION_X_EN_MSK, regval) | + FIELD_GET(BMI270_FEAT_MOTION_Z_EN_MSK, regval); + break; + case IIO_MOD_Z: + axis_msk = BMI270_FEAT_MOTION_Z_EN_MSK; + axis_field_val = FIELD_PREP(BMI270_FEAT_MOTION_Z_EN_MSK, state); + axis_en = FIELD_GET(BMI270_FEAT_MOTION_X_EN_MSK, regval) | + FIELD_GET(BMI270_FEAT_MOTION_Y_EN_MSK, regval); + break; + default: + return -EINVAL; + } + + ret = bmi270_update_feature_reg(data, BMI270_ANYMO1_REG, axis_msk, + axis_field_val); + if (ret) + return ret; + + ret = bmi270_update_feature_reg(data, BMI270_ANYMO2_REG, + BMI270_FEAT_MOTION_ENABLE_MSK, + FIELD_PREP(BMI270_FEAT_MOTION_ENABLE_MSK, + state || axis_en)); + if (ret) + return ret; + + return regmap_update_bits(data->regmap, irq_reg, + BMI270_INT_MAP_FEAT_ANYMOTION_MSK, + FIELD_PREP(BMI270_INT_MAP_FEAT_ANYMOTION_MSK, + state || axis_en)); +} + +static int bmi270_nomotion_event_en(struct bmi270_data *data, bool state) +{ + int ret, irq_reg; + + irq_reg = bmi270_int_map_reg(data->irq_pin); + if (irq_reg < 0) + return irq_reg; + + guard(mutex)(&data->mutex); + + ret = bmi270_update_feature_reg(data, BMI270_NOMO1_REG, + BMI270_FEAT_MOTION_XYZ_EN_MSK, + FIELD_PREP(BMI270_FEAT_MOTION_XYZ_EN_MSK, + state ? BMI270_MOTION_XYZ_MSK : 0)); + if (ret) + return ret; + + ret = bmi270_update_feature_reg(data, BMI270_NOMO2_REG, + BMI270_FEAT_MOTION_ENABLE_MSK, + FIELD_PREP(BMI270_FEAT_MOTION_ENABLE_MSK, + state)); + if (ret) + return ret; + + return regmap_update_bits(data->regmap, irq_reg, + BMI270_INT_MAP_FEAT_NOMOTION_MSK, + FIELD_PREP(BMI270_INT_MAP_FEAT_NOMOTION_MSK, + state)); +} + +static int bmi270_set_scale(struct bmi270_data *data, int chan_type, int uscale) +{ + int i; + int reg, mask; + struct bmi270_scale_item bmi270_scale_item; + + switch (chan_type) { + case IIO_ACCEL: + reg = BMI270_ACC_CONF_RANGE_REG; + mask = BMI270_ACC_CONF_RANGE_MSK; + bmi270_scale_item = bmi270_scale_table[BMI270_ACCEL]; + break; + case IIO_ANGL_VEL: + reg = BMI270_GYR_CONF_RANGE_REG; + mask = BMI270_GYR_CONF_RANGE_MSK; + bmi270_scale_item = bmi270_scale_table[BMI270_GYRO]; + break; + default: + return -EINVAL; + } + + guard(mutex)(&data->mutex); + + for (i = 0; i < bmi270_scale_item.num; i++) { + if (bmi270_scale_item.tbl[i].uscale != uscale) + continue; + + return regmap_update_bits(data->regmap, reg, mask, i); + } + + return -EINVAL; +} + +static int bmi270_get_scale(struct bmi270_data *data, int chan_type, int *scale, + int *uscale) +{ + int ret; + unsigned int val; + struct bmi270_scale_item bmi270_scale_item; + + switch (chan_type) { + case IIO_ACCEL: + ret = regmap_read(data->regmap, BMI270_ACC_CONF_RANGE_REG, &val); + if (ret) + return ret; + + val = FIELD_GET(BMI270_ACC_CONF_RANGE_MSK, val); + bmi270_scale_item = bmi270_scale_table[BMI270_ACCEL]; + break; + case IIO_ANGL_VEL: + ret = regmap_read(data->regmap, BMI270_GYR_CONF_RANGE_REG, &val); + if (ret) + return ret; + + val = FIELD_GET(BMI270_GYR_CONF_RANGE_MSK, val); + bmi270_scale_item = bmi270_scale_table[BMI270_GYRO]; + break; + case IIO_TEMP: + val = 0; + bmi270_scale_item = bmi270_scale_table[BMI270_TEMP]; + break; + default: + return -EINVAL; + } + + if (val >= bmi270_scale_item.num) + return -EINVAL; + + *scale = bmi270_scale_item.tbl[val].scale; + *uscale = bmi270_scale_item.tbl[val].uscale; + return 0; +} + +static int bmi270_set_odr(struct bmi270_data *data, int chan_type, int odr, + int uodr) +{ + int i; + int reg, mask; + struct bmi270_odr_item bmi270_odr_item; + + switch (chan_type) { + case IIO_ACCEL: + reg = BMI270_ACC_CONF_REG; + mask = BMI270_ACC_CONF_ODR_MSK; + bmi270_odr_item = bmi270_odr_table[BMI270_ACCEL]; + break; + case IIO_ANGL_VEL: + reg = BMI270_GYR_CONF_REG; + mask = BMI270_GYR_CONF_ODR_MSK; + bmi270_odr_item = bmi270_odr_table[BMI270_GYRO]; + break; + default: + return -EINVAL; + } + + guard(mutex)(&data->mutex); + + for (i = 0; i < bmi270_odr_item.num; i++) { + if (bmi270_odr_item.tbl[i].odr != odr || + bmi270_odr_item.tbl[i].uodr != uodr) + continue; + + return regmap_update_bits(data->regmap, reg, mask, + bmi270_odr_item.vals[i]); + } + + return -EINVAL; +} + +static int bmi270_get_odr(struct bmi270_data *data, int chan_type, int *odr, + int *uodr) +{ + int i, val, ret; + struct bmi270_odr_item bmi270_odr_item; + + guard(mutex)(&data->mutex); + + switch (chan_type) { + case IIO_ACCEL: + ret = regmap_read(data->regmap, BMI270_ACC_CONF_REG, &val); + if (ret) + return ret; + + val = FIELD_GET(BMI270_ACC_CONF_ODR_MSK, val); + bmi270_odr_item = bmi270_odr_table[BMI270_ACCEL]; + break; + case IIO_ANGL_VEL: + ret = regmap_read(data->regmap, BMI270_GYR_CONF_REG, &val); + if (ret) + return ret; + + val = FIELD_GET(BMI270_GYR_CONF_ODR_MSK, val); + bmi270_odr_item = bmi270_odr_table[BMI270_GYRO]; + break; + default: + return -EINVAL; + } + + for (i = 0; i < bmi270_odr_item.num; i++) { + if (val != bmi270_odr_item.vals[i]) + continue; + + *odr = bmi270_odr_item.tbl[i].odr; + *uodr = bmi270_odr_item.tbl[i].uodr; + return 0; + } + + return -EINVAL; +} + +static irqreturn_t bmi270_irq_thread_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct bmi270_data *data = iio_priv(indio_dev); + unsigned int status0, status1; + s64 timestamp = iio_get_time_ns(indio_dev); + int ret; + + scoped_guard(mutex, &data->mutex) { + ret = regmap_read(data->regmap, BMI270_INT_STATUS_0_REG, + &status0); + if (ret) + return IRQ_NONE; + + ret = regmap_read(data->regmap, BMI270_INT_STATUS_1_REG, + &status1); + if (ret) + return IRQ_NONE; + } + + if (FIELD_GET(BMI270_INT_STATUS_1_ACC_GYR_DRDY_MSK, status1)) + iio_trigger_poll_nested(data->trig); + + if (FIELD_GET(BMI270_INT_STATUS_0_MOTION_MSK, status0)) + iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_MAG_ADAPTIVE, + IIO_EV_DIR_RISING), + timestamp); + + if (FIELD_GET(BMI270_INT_STATUS_0_NOMOTION_MSK, status0)) + iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_AND_Y_AND_Z, + IIO_EV_TYPE_ROC, + IIO_EV_DIR_RISING), + timestamp); + + if (FIELD_GET(BMI270_INT_STATUS_0_STEP_CNT_MSK, status0)) + iio_push_event(indio_dev, IIO_UNMOD_EVENT_CODE(IIO_STEPS, 0, + IIO_EV_TYPE_CHANGE, + IIO_EV_DIR_NONE), + timestamp); + + return IRQ_HANDLED; +} + +static int bmi270_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct bmi270_data *data = iio_trigger_get_drvdata(trig); + unsigned int field_value = 0; + unsigned int mask; + + guard(mutex)(&data->mutex); + + switch (data->irq_pin) { + case BMI270_IRQ_INT1: + mask = BMI270_INT_MAP_DATA_DRDY_INT1_MSK; + set_mask_bits(&field_value, BMI270_INT_MAP_DATA_DRDY_INT1_MSK, + FIELD_PREP(BMI270_INT_MAP_DATA_DRDY_INT1_MSK, + state)); + break; + case BMI270_IRQ_INT2: + mask = BMI270_INT_MAP_DATA_DRDY_INT2_MSK; + set_mask_bits(&field_value, BMI270_INT_MAP_DATA_DRDY_INT2_MSK, + FIELD_PREP(BMI270_INT_MAP_DATA_DRDY_INT2_MSK, + state)); + break; + default: + return -EINVAL; + } + + return regmap_update_bits(data->regmap, BMI270_INT_MAP_DATA_REG, mask, + field_value); +} + +static const struct iio_trigger_ops bmi270_trigger_ops = { + .set_trigger_state = &bmi270_data_rdy_trigger_set_state, +}; + +static irqreturn_t bmi270_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bmi270_data *data = iio_priv(indio_dev); + int ret; + + guard(mutex)(&data->mutex); + + ret = regmap_bulk_read(data->regmap, BMI270_ACCEL_X_REG, + &data->buffer.channels, + sizeof(data->buffer.channels)); + + if (ret) + goto done; + + iio_push_to_buffers_with_timestamp(indio_dev, &data->buffer, + pf->timestamp); +done: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int bmi270_get_data(struct bmi270_data *data, int chan_type, int axis, + int *val) +{ + __le16 sample; + int reg; + int ret; + + switch (chan_type) { + case IIO_ACCEL: + reg = BMI270_ACCEL_X_REG + (axis - IIO_MOD_X) * 2; + break; + case IIO_ANGL_VEL: + reg = BMI270_ANG_VEL_X_REG + (axis - IIO_MOD_X) * 2; + break; + case IIO_TEMP: + reg = BMI270_TEMPERATURE_0_REG; + break; + default: + return -EINVAL; + } + + guard(mutex)(&data->mutex); + + ret = regmap_bulk_read(data->regmap, reg, &sample, sizeof(sample)); + if (ret) + return ret; + + *val = sign_extend32(le16_to_cpu(sample), 15); + + return IIO_VAL_INT; +} + +static int bmi270_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct bmi270_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + return bmi270_read_steps(data, val); + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = bmi270_get_data(data, chan->type, chan->channel2, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SCALE: + ret = bmi270_get_scale(data, chan->type, val, val2); + return ret ? ret : IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + *val = BMI270_TEMP_OFFSET; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + ret = bmi270_get_odr(data, chan->type, val, val2); + return ret ? ret : IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_ENABLE: + *val = data->steps_enabled ? 1 : 0; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int bmi270_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bmi270_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = bmi270_set_scale(data, chan->type, val2); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = bmi270_set_odr(data, chan->type, val, val2); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_ENABLE: + return bmi270_enable_steps(data, val); + case IIO_CHAN_INFO_PROCESSED: { + if (val || !data->steps_enabled) + return -EINVAL; + + guard(mutex)(&data->mutex); + /* Clear step counter value */ + return bmi270_update_feature_reg(data, BMI270_SC_26_REG, + BMI270_STEP_SC26_RST_CNT_MSK, + FIELD_PREP(BMI270_STEP_SC26_RST_CNT_MSK, + 1)); + } + default: + return -EINVAL; + } +} + +static int bmi270_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *type = IIO_VAL_INT_PLUS_MICRO; + switch (chan->type) { + case IIO_ANGL_VEL: + *vals = (const int *)bmi270_gyro_scale; + *length = ARRAY_SIZE(bmi270_gyro_scale) * 2; + return IIO_AVAIL_LIST; + case IIO_ACCEL: + *vals = (const int *)bmi270_accel_scale; + *length = ARRAY_SIZE(bmi270_accel_scale) * 2; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + *type = IIO_VAL_INT_PLUS_MICRO; + switch (chan->type) { + case IIO_ANGL_VEL: + *vals = (const int *)bmi270_gyro_odr; + *length = ARRAY_SIZE(bmi270_gyro_odr) * 2; + return IIO_AVAIL_LIST; + case IIO_ACCEL: + *vals = (const int *)bmi270_accel_odr; + *length = ARRAY_SIZE(bmi270_accel_odr) * 2; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static ssize_t in_accel_value_available_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct bmi270_data *data = iio_priv(indio_dev); + int ret, scale, uscale; + unsigned int step, max; + + ret = bmi270_get_scale(data, IIO_ACCEL, &scale, &uscale); + if (ret) + return ret; + + max = BMI270_G_MICRO_M_S_2 / uscale; + step = max / BMI270_MOTION_THRES_FULL_SCALE; + + return sysfs_emit(buf, "[0 %u %u]\n", step, max); +} + +static IIO_DEVICE_ATTR_RO(in_accel_value_available, 0); + +static IIO_CONST_ATTR(in_accel_period_available, "[0.0 0.02 162.0]"); + +static struct attribute *bmi270_event_attributes[] = { + &iio_dev_attr_in_accel_value_available.dev_attr.attr, + &iio_const_attr_in_accel_period_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group bmi270_event_attribute_group = { + .attrs = bmi270_event_attributes, +}; + +static int bmi270_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct bmi270_data *data = iio_priv(indio_dev); + + switch (type) { + case IIO_EV_TYPE_MAG_ADAPTIVE: + return bmi270_anymotion_event_en(data, chan, state); + case IIO_EV_TYPE_ROC: + return bmi270_nomotion_event_en(data, state); + case IIO_EV_TYPE_CHANGE: + return bmi270_step_wtrmrk_en(data, state); + default: + return -EINVAL; + } +} + +static int bmi270_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct bmi270_data *data = iio_priv(indio_dev); + bool feat_en, axis_en; + int ret, reg, regval; + u16 motion_reg; + + guard(mutex)(&data->mutex); + + reg = bmi270_int_map_reg(data->irq_pin); + if (reg < 0) + return reg; + + ret = regmap_read(data->regmap, reg, ®val); + if (ret) + return ret; + + switch (chan->type) { + case IIO_STEPS: + return !!FIELD_GET(BMI270_INT_MAP_FEAT_STEP_CNT_WTRMRK_MSK, regval); + case IIO_ACCEL: + switch (type) { + case IIO_EV_TYPE_ROC: + return !!FIELD_GET(BMI270_INT_MAP_FEAT_NOMOTION_MSK, regval); + case IIO_EV_TYPE_MAG_ADAPTIVE: + ret = bmi270_read_feature_reg(data, BMI270_ANYMO1_REG, + &motion_reg); + if (ret) + return ret; + + feat_en = FIELD_GET(BMI270_INT_MAP_FEAT_ANYMOTION_MSK, + regval); + switch (chan->channel2) { + case IIO_MOD_X: + axis_en = FIELD_GET(BMI270_FEAT_MOTION_X_EN_MSK, + motion_reg); + break; + case IIO_MOD_Y: + axis_en = FIELD_GET(BMI270_FEAT_MOTION_Y_EN_MSK, + motion_reg); + break; + case IIO_MOD_Z: + axis_en = FIELD_GET(BMI270_FEAT_MOTION_Z_EN_MSK, + motion_reg); + break; + default: + return -EINVAL; + } + return axis_en && feat_en; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int bmi270_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct bmi270_data *data = iio_priv(indio_dev); + unsigned int raw, mask, regval; + int ret, reg, scale, uscale; + u64 tmp; + + guard(mutex)(&data->mutex); + + if (type == IIO_EV_TYPE_CHANGE) { + if (!in_range(val, 0, BMI270_STEP_COUNTER_MAX + 1)) + return -EINVAL; + + raw = val / BMI270_STEP_COUNTER_FACTOR; + mask = BMI270_STEP_SC26_WTRMRK_MSK; + regval = FIELD_PREP(BMI270_STEP_SC26_WTRMRK_MSK, raw); + return bmi270_update_feature_reg(data, BMI270_SC_26_REG, mask, + regval); + } + + reg = bmi270_motion_reg(type, info); + if (reg < 0) + return reg; + + switch (info) { + case IIO_EV_INFO_VALUE: + ret = bmi270_get_scale(data, IIO_ACCEL, &scale, &uscale); + if (ret) + return ret; + + if (!in_range(val, 0, (BMI270_G_MICRO_M_S_2 / uscale) + 1)) + return -EINVAL; + + tmp = (u64)val * BMI270_MOTION_THRES_FULL_SCALE * uscale; + raw = DIV_ROUND_CLOSEST_ULL(tmp, BMI270_G_MICRO_M_S_2); + mask = BMI270_FEAT_MOTION_THRESHOLD_MSK; + regval = FIELD_PREP(BMI270_FEAT_MOTION_THRESHOLD_MSK, raw); + return bmi270_update_feature_reg(data, reg, mask, regval); + case IIO_EV_INFO_PERIOD: + if (!in_range(val, 0, BMI270_MOTION_DURAT_MAX + 1)) + return -EINVAL; + + raw = BMI270_INT_MICRO_TO_RAW(val, val2, + BMI270_MOTION_DURAT_SCALE); + mask = BMI270_FEAT_MOTION_DURATION_MSK; + regval = FIELD_PREP(BMI270_FEAT_MOTION_DURATION_MSK, raw); + return bmi270_update_feature_reg(data, reg, mask, regval); + default: + return -EINVAL; + } +} + +static int bmi270_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct bmi270_data *data = iio_priv(indio_dev); + int ret, reg, scale, uscale; + unsigned int raw; + u16 regval; + u64 tmp; + + guard(mutex)(&data->mutex); + + if (type == IIO_EV_TYPE_CHANGE) { + ret = bmi270_read_feature_reg(data, BMI270_SC_26_REG, ®val); + if (ret) + return ret; + + raw = FIELD_GET(BMI270_STEP_SC26_WTRMRK_MSK, regval); + *val = raw * BMI270_STEP_COUNTER_FACTOR; + return IIO_VAL_INT; + } + + reg = bmi270_motion_reg(type, info); + if (reg < 0) + return reg; + + switch (info) { + case IIO_EV_INFO_VALUE: + ret = bmi270_read_feature_reg(data, reg, ®val); + if (ret) + return ret; + + ret = bmi270_get_scale(data, IIO_ACCEL, &scale, &uscale); + if (ret) + return ret; + + raw = FIELD_GET(BMI270_FEAT_MOTION_THRESHOLD_MSK, regval); + tmp = (u64)raw * BMI270_G_MICRO_M_S_2; + *val = DIV_ROUND_CLOSEST_ULL(tmp, + BMI270_MOTION_THRES_FULL_SCALE * uscale); + return IIO_VAL_INT; + case IIO_EV_INFO_PERIOD: + ret = bmi270_read_feature_reg(data, reg, ®val); + if (ret) + return ret; + + raw = FIELD_GET(BMI270_FEAT_MOTION_DURATION_MSK, regval); + *val = raw / BMI270_MOTION_DURAT_SCALE; + *val2 = BMI270_RAW_TO_MICRO(raw, BMI270_MOTION_DURAT_SCALE); + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static const struct iio_event_spec bmi270_step_wtrmrk_event = { + .type = IIO_EV_TYPE_CHANGE, + .dir = IIO_EV_DIR_NONE, + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) | BIT(IIO_EV_INFO_VALUE), +}; + +static const struct iio_event_spec bmi270_anymotion_event = { + .type = IIO_EV_TYPE_MAG_ADAPTIVE, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_PERIOD), +}; + +static const struct iio_event_spec bmi270_nomotion_event = { + .type = IIO_EV_TYPE_ROC, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_PERIOD), +}; + +static const struct iio_info bmi270_info = { + .read_raw = bmi270_read_raw, + .write_raw = bmi270_write_raw, + .read_avail = bmi270_read_avail, + .write_event_config = bmi270_write_event_config, + .read_event_config = bmi270_read_event_config, + .write_event_value = bmi270_write_event_value, + .read_event_value = bmi270_read_event_value, + .event_attrs = &bmi270_event_attribute_group, +}; + +#define BMI270_ACCEL_CHANNEL(_axis) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = BMI270_SCAN_ACCEL_##_axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ + .event_spec = &bmi270_anymotion_event, \ + .num_event_specs = 1, \ +} + +#define BMI270_ANG_VEL_CHANNEL(_axis) { \ + .type = IIO_ANGL_VEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = BMI270_SCAN_GYRO_##_axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ +} + +static const struct iio_chan_spec bmi270_channels[] = { + BMI270_ACCEL_CHANNEL(X), + BMI270_ACCEL_CHANNEL(Y), + BMI270_ACCEL_CHANNEL(Z), + BMI270_ANG_VEL_CHANNEL(X), + BMI270_ANG_VEL_CHANNEL(Y), + BMI270_ANG_VEL_CHANNEL(Z), + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = -1, /* No buffer support */ + }, + { + .type = IIO_STEPS, + .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = -1, /* No buffer support */ + .event_spec = &bmi270_step_wtrmrk_event, + .num_event_specs = 1, + }, + IIO_CHAN_SOFT_TIMESTAMP(BMI270_SCAN_TIMESTAMP), + { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_X_AND_Y_AND_Z, + .scan_index = -1, /* Fake channel */ + .event_spec = &bmi270_nomotion_event, + .num_event_specs = 1, + }, +}; + +static int bmi270_int_pin_config(struct bmi270_data *data, + enum bmi270_irq_pin irq_pin, + bool active_high, bool open_drain, bool latch) +{ + unsigned int reg, field_value; + int ret; + + ret = regmap_update_bits(data->regmap, BMI270_INT_LATCH_REG, + BMI270_INT_LATCH_REG_MSK, + FIELD_PREP(BMI270_INT_LATCH_REG_MSK, latch)); + if (ret) + return ret; + + switch (irq_pin) { + case BMI270_IRQ_INT1: + reg = BMI270_INT1_IO_CTRL_REG; + break; + case BMI270_IRQ_INT2: + reg = BMI270_INT2_IO_CTRL_REG; + break; + default: + return -EINVAL; + } + + field_value = FIELD_PREP(BMI270_INT_IO_CTRL_LVL_MSK, active_high) | + FIELD_PREP(BMI270_INT_IO_CTRL_OD_MSK, open_drain) | + FIELD_PREP(BMI270_INT_IO_CTRL_OP_MSK, 1); + return regmap_update_bits(data->regmap, reg, + BMI270_INT_IO_LVL_OD_OP_MSK, field_value); +} + +static int bmi270_trigger_probe(struct bmi270_data *data, + struct iio_dev *indio_dev) +{ + bool open_drain, active_high, latch; + struct fwnode_handle *fwnode; + enum bmi270_irq_pin irq_pin; + int ret, irq, irq_type; + + fwnode = dev_fwnode(data->dev); + if (!fwnode) + return -ENODEV; + + irq = fwnode_irq_get_byname(fwnode, "INT1"); + if (irq > 0) { + irq_pin = BMI270_IRQ_INT1; + } else { + irq = fwnode_irq_get_byname(fwnode, "INT2"); + if (irq < 0) + return 0; + + irq_pin = BMI270_IRQ_INT2; + } + + irq_type = irq_get_trigger_type(irq); + switch (irq_type) { + case IRQF_TRIGGER_RISING: + latch = false; + active_high = true; + break; + case IRQF_TRIGGER_HIGH: + latch = true; + active_high = true; + break; + case IRQF_TRIGGER_FALLING: + latch = false; + active_high = false; + break; + case IRQF_TRIGGER_LOW: + latch = true; + active_high = false; + break; + default: + return dev_err_probe(data->dev, -EINVAL, + "Invalid interrupt type 0x%x specified\n", + irq_type); + } + + open_drain = fwnode_property_read_bool(fwnode, "drive-open-drain"); + + ret = bmi270_int_pin_config(data, irq_pin, active_high, open_drain, + latch); + if (ret) + return dev_err_probe(data->dev, ret, + "Failed to configure irq line\n"); + + data->trig = devm_iio_trigger_alloc(data->dev, "%s-trig-%d", + indio_dev->name, irq_pin); + if (!data->trig) + return -ENOMEM; + + data->trig->ops = &bmi270_trigger_ops; + iio_trigger_set_drvdata(data->trig, data); + + ret = devm_request_threaded_irq(data->dev, irq, NULL, + bmi270_irq_thread_handler, + IRQF_ONESHOT, "bmi270-int", indio_dev); + if (ret) + return ret; + + ret = devm_iio_trigger_register(data->dev, data->trig); + if (ret) + return dev_err_probe(data->dev, ret, + "Trigger registration failed\n"); + + /* Disable axes for motion events */ + ret = bmi270_update_feature_reg(data, BMI270_ANYMO1_REG, + BMI270_FEAT_MOTION_XYZ_EN_MSK, + FIELD_PREP(BMI270_FEAT_MOTION_XYZ_EN_MSK, 0)); + if (ret) + return ret; + + data->irq_pin = irq_pin; + + return 0; +} + +static int bmi270_validate_chip_id(struct bmi270_data *data) +{ + int chip_id; + int ret; + struct device *dev = data->dev; + struct regmap *regmap = data->regmap; + + ret = regmap_read(regmap, BMI270_CHIP_ID_REG, &chip_id); + if (ret) + return dev_err_probe(dev, ret, "Failed to read chip id"); + + /* + * Some manufacturers use "BMI0160" for both the BMI160 and + * BMI260. If the device is actually a BMI160, the bmi160 + * driver should handle it and this driver should not. + */ + if (chip_id == BMI160_CHIP_ID_VAL) + return -ENODEV; + + if (chip_id != data->chip_info->chip_id) + dev_info(dev, "Unexpected chip id 0x%x", chip_id); + + if (chip_id == bmi260_chip_info.chip_id) + data->chip_info = &bmi260_chip_info; + else if (chip_id == bmi270_chip_info.chip_id) + data->chip_info = &bmi270_chip_info; + + return 0; +} + +static int bmi270_write_calibration_data(struct bmi270_data *data) +{ + int ret; + int status = 0; + const struct firmware *init_data; + struct device *dev = data->dev; + struct regmap *regmap = data->regmap; + + ret = regmap_clear_bits(regmap, BMI270_PWR_CONF_REG, + BMI270_PWR_CONF_ADV_PWR_SAVE_MSK); + if (ret) + return dev_err_probe(dev, ret, + "Failed to write power configuration"); + + /* + * After disabling advanced power save, all registers are accessible + * after a 450us delay. This delay is specified in table A of the + * datasheet. + */ + usleep_range(450, 1000); + + ret = regmap_clear_bits(regmap, BMI270_INIT_CTRL_REG, + BMI270_INIT_CTRL_LOAD_DONE_MSK); + if (ret) + return dev_err_probe(dev, ret, + "Failed to prepare device to load init data"); + + ret = request_firmware(&init_data, data->chip_info->fw_name, dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to load init data file"); + + ret = regmap_bulk_write(regmap, BMI270_INIT_DATA_REG, + init_data->data, init_data->size); + release_firmware(init_data); + if (ret) + return dev_err_probe(dev, ret, "Failed to write init data"); + + ret = regmap_set_bits(regmap, BMI270_INIT_CTRL_REG, + BMI270_INIT_CTRL_LOAD_DONE_MSK); + if (ret) + return dev_err_probe(dev, ret, + "Failed to stop device initialization"); + + /* + * Wait at least 140ms for the device to complete configuration. + * This delay is specified in table C of the datasheet. + */ + usleep_range(140000, 160000); + + ret = regmap_read(regmap, BMI270_INTERNAL_STATUS_REG, &status); + if (ret) + return dev_err_probe(dev, ret, "Failed to read internal status"); + + if (status != BMI270_INTERNAL_STATUS_MSG_INIT_OK) + return dev_err_probe(dev, -ENODEV, "Device failed to initialize"); + + return 0; +} + +static int bmi270_configure_imu(struct bmi270_data *data) +{ + int ret; + struct device *dev = data->dev; + struct regmap *regmap = data->regmap; + + ret = regmap_set_bits(regmap, BMI270_PWR_CTRL_REG, + BMI270_PWR_CTRL_AUX_EN_MSK | + BMI270_PWR_CTRL_GYR_EN_MSK | + BMI270_PWR_CTRL_ACCEL_EN_MSK | + BMI270_PWR_CTRL_TEMP_EN_MSK); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable accelerometer and gyroscope"); + + ret = regmap_set_bits(regmap, BMI270_ACC_CONF_REG, + FIELD_PREP(BMI270_ACC_CONF_ODR_MSK, + BMI270_ACC_CONF_ODR_100HZ) | + FIELD_PREP(BMI270_ACC_CONF_BWP_MSK, + BMI270_ACC_CONF_BWP_NORMAL_MODE)); + if (ret) + return dev_err_probe(dev, ret, "Failed to configure accelerometer"); + + ret = regmap_set_bits(regmap, BMI270_GYR_CONF_REG, + FIELD_PREP(BMI270_GYR_CONF_ODR_MSK, + BMI270_GYR_CONF_ODR_200HZ) | + FIELD_PREP(BMI270_GYR_CONF_BWP_MSK, + BMI270_GYR_CONF_BWP_NORMAL_MODE)); + if (ret) + return dev_err_probe(dev, ret, "Failed to configure gyroscope"); + + /* Enable FIFO_WKUP, Disable ADV_PWR_SAVE and FUP_EN */ + ret = regmap_write(regmap, BMI270_PWR_CONF_REG, + BMI270_PWR_CONF_FIFO_WKUP_MSK); + if (ret) + return dev_err_probe(dev, ret, "Failed to set power configuration"); + + return 0; +} + +static int bmi270_chip_init(struct bmi270_data *data) +{ + int ret; + + ret = bmi270_validate_chip_id(data); + if (ret) + return ret; + + ret = bmi270_write_calibration_data(data); + if (ret) + return ret; + + return bmi270_configure_imu(data); +} + +int bmi270_core_probe(struct device *dev, struct regmap *regmap, + const struct bmi270_chip_info *chip_info) +{ + int ret; + struct bmi270_data *data; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->dev = dev; + data->regmap = regmap; + data->chip_info = chip_info; + data->irq_pin = BMI270_IRQ_DISABLED; + mutex_init(&data->mutex); + + ret = bmi270_chip_init(data); + if (ret) + return ret; + + indio_dev->channels = bmi270_channels; + indio_dev->num_channels = ARRAY_SIZE(bmi270_channels); + indio_dev->name = chip_info->name; + indio_dev->available_scan_masks = bmi270_avail_scan_masks; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &bmi270_info; + dev_set_drvdata(data->dev, indio_dev); + + ret = bmi270_trigger_probe(data, indio_dev); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + bmi270_trigger_handler, NULL); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS_GPL(bmi270_core_probe, "IIO_BMI270"); + +static int bmi270_core_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + return iio_device_suspend_triggering(indio_dev); +} + +static int bmi270_core_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + return iio_device_resume_triggering(indio_dev); +} + +const struct dev_pm_ops bmi270_core_pm_ops = { + RUNTIME_PM_OPS(bmi270_core_runtime_suspend, bmi270_core_runtime_resume, NULL) +}; +EXPORT_SYMBOL_NS_GPL(bmi270_core_pm_ops, "IIO_BMI270"); + +MODULE_AUTHOR("Alex Lanzano"); +MODULE_DESCRIPTION("BMI270 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/imu/bmi270/bmi270_i2c.c b/drivers/iio/imu/bmi270/bmi270_i2c.c new file mode 100644 index 000000000..7c035e82e --- /dev/null +++ b/drivers/iio/imu/bmi270/bmi270_i2c.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) + +#include +#include +#include +#include +#include + +#include "bmi270.h" + +static const struct regmap_config bmi270_i2c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int bmi270_i2c_probe(struct i2c_client *client) +{ + struct regmap *regmap; + struct device *dev = &client->dev; + const struct bmi270_chip_info *chip_info; + + chip_info = i2c_get_match_data(client); + if (!chip_info) + return -ENODEV; + + regmap = devm_regmap_init_i2c(client, &bmi270_i2c_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Failed to init i2c regmap"); + + return bmi270_core_probe(dev, regmap, chip_info); +} + +static const struct i2c_device_id bmi270_i2c_id[] = { + { .name = "bmi260", .driver_data = (kernel_ulong_t)&bmi260_chip_info }, + { .name = "bmi270", .driver_data = (kernel_ulong_t)&bmi270_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bmi270_i2c_id); + +static const struct acpi_device_id bmi270_acpi_match[] = { + /* GPD Win Mini, Aya Neo AIR Pro, OXP Mini Pro, etc. */ + { "BMI0160", (kernel_ulong_t)&bmi260_chip_info }, + /* GPD Win Max 2 2023(sincice BIOS v0.40), etc. */ + { "BMI0260", (kernel_ulong_t)&bmi260_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(acpi, bmi270_acpi_match); + +static const struct of_device_id bmi270_of_match[] = { + { .compatible = "bosch,bmi260", .data = &bmi260_chip_info }, + { .compatible = "bosch,bmi270", .data = &bmi270_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, bmi270_of_match); + +static struct i2c_driver bmi270_i2c_driver = { + .driver = { + .name = "bmi270_i2c", + .pm = pm_ptr(&bmi270_core_pm_ops), + .acpi_match_table = bmi270_acpi_match, + .of_match_table = bmi270_of_match, + }, + .probe = bmi270_i2c_probe, + .id_table = bmi270_i2c_id, +}; +module_i2c_driver(bmi270_i2c_driver); + +MODULE_AUTHOR("Alex Lanzano"); +MODULE_DESCRIPTION("BMI270 driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_BMI270"); diff --git a/drivers/iio/imu/bmi270/bmi270_spi.c b/drivers/iio/imu/bmi270/bmi270_spi.c new file mode 100644 index 000000000..759b56b59 --- /dev/null +++ b/drivers/iio/imu/bmi270/bmi270_spi.c @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) + +#include +#include +#include +#include +#include + +#include "bmi270.h" + +/* + * The following two functions are taken from the BMI323 spi driver code. + * In section 6.4 of the BMI270 data it specifies that after a read + * operation the first data byte from the device is a dummy byte + */ +static int bmi270_regmap_spi_read(void *spi, const void *reg_buf, + size_t reg_size, void *val_buf, + size_t val_size) +{ + return spi_write_then_read(spi, reg_buf, reg_size, val_buf, val_size); +} + +static int bmi270_regmap_spi_write(void *spi, const void *data, + size_t count) +{ + u8 *data_buff = (u8 *)data; + + /* + * Remove the extra pad byte since its only needed for the read + * operation + */ + data_buff[1] = data_buff[0]; + return spi_write_then_read(spi, data_buff + 1, count - 1, NULL, 0); +} + +static const struct regmap_bus bmi270_regmap_bus = { + .read = bmi270_regmap_spi_read, + .write = bmi270_regmap_spi_write, +}; + +static const struct regmap_config bmi270_spi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .pad_bits = 8, + .read_flag_mask = BIT(7), +}; + +static int bmi270_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + struct device *dev = &spi->dev; + const struct bmi270_chip_info *chip_info; + + chip_info = spi_get_device_match_data(spi); + if (!chip_info) + return -ENODEV; + + regmap = devm_regmap_init(dev, &bmi270_regmap_bus, dev, + &bmi270_spi_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Failed to init spi regmap\n"); + + return bmi270_core_probe(dev, regmap, chip_info); +} + +static const struct spi_device_id bmi270_spi_id[] = { + { .name = "bmi260", .driver_data = (kernel_ulong_t)&bmi260_chip_info }, + { .name = "bmi270", .driver_data = (kernel_ulong_t)&bmi270_chip_info }, + { } +}; + +static const struct of_device_id bmi270_of_match[] = { + { .compatible = "bosch,bmi260", .data = &bmi260_chip_info }, + { .compatible = "bosch,bmi270", .data = &bmi270_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, bmi270_of_match); + +static struct spi_driver bmi270_spi_driver = { + .driver = { + .name = "bmi270", + .pm = pm_ptr(&bmi270_core_pm_ops), + .of_match_table = bmi270_of_match, + }, + .probe = bmi270_spi_probe, + .id_table = bmi270_spi_id, +}; +module_spi_driver(bmi270_spi_driver); + +MODULE_AUTHOR("Alex Lanzano"); +MODULE_DESCRIPTION("BMI270 driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_BMI270"); diff --git a/drivers/iio/imu/bmi323/Kconfig b/drivers/iio/imu/bmi323/Kconfig new file mode 100644 index 000000000..ab37b2853 --- /dev/null +++ b/drivers/iio/imu/bmi323/Kconfig @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# BMI323 IMU driver +# + +config BMI323 + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config BMI323_I2C + tristate "Bosch BMI323 I2C driver" + depends on I2C + select BMI323 + select REGMAP_I2C + help + Enable support for the Bosch BMI323 6-Axis IMU connected to I2C + interface. + + This driver can also be built as a module. If so, the module will be + called bmi323_i2c. + +config BMI323_SPI + tristate "Bosch BMI323 SPI driver" + depends on SPI + select BMI323 + select REGMAP_SPI + help + Enable support for the Bosch BMI323 6-Axis IMU connected to SPI + interface. + + This driver can also be built as a module. If so, the module will be + called bmi323_spi. diff --git a/drivers/iio/imu/bmi323/Makefile b/drivers/iio/imu/bmi323/Makefile new file mode 100644 index 000000000..a6a6dc020 --- /dev/null +++ b/drivers/iio/imu/bmi323/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for Bosch BMI323 IMU +# +obj-$(CONFIG_BMI323) += bmi323_core.o +obj-$(CONFIG_BMI323_I2C) += bmi323_i2c.o +obj-$(CONFIG_BMI323_SPI) += bmi323_spi.o diff --git a/drivers/iio/imu/bmi323/bmi323.h b/drivers/iio/imu/bmi323/bmi323.h new file mode 100644 index 000000000..b4cfe9260 --- /dev/null +++ b/drivers/iio/imu/bmi323/bmi323.h @@ -0,0 +1,209 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * IIO driver for Bosch BMI323 6-Axis IMU + * + * Copyright (C) 2023, Jagath Jog J + */ + +#ifndef _BMI323_H_ +#define _BMI323_H_ + +#include +#include +#include + +#define BMI323_I2C_DUMMY 2 +#define BMI323_SPI_DUMMY 1 + +/* Register map */ + +#define BMI323_CHIP_ID_REG 0x00 +#define BMI323_CHIP_ID_VAL 0x0043 +#define BMI323_CHIP_ID_MSK GENMASK(7, 0) +#define BMI323_ERR_REG 0x01 +#define BMI323_STATUS_REG 0x02 +#define BMI323_STATUS_POR_MSK BIT(0) + +/* Accelero/Gyro/Temp data registers */ +#define BMI323_ACCEL_X_REG 0x03 +#define BMI323_GYRO_X_REG 0x06 +#define BMI323_TEMP_REG 0x09 +#define BMI323_ALL_CHAN_MSK GENMASK(5, 0) + +/* Status registers */ +#define BMI323_STATUS_INT1_REG 0x0D +#define BMI323_STATUS_INT2_REG 0x0E +#define BMI323_STATUS_NOMOTION_MSK BIT(0) +#define BMI323_STATUS_MOTION_MSK BIT(1) +#define BMI323_STATUS_STP_WTR_MSK BIT(5) +#define BMI323_STATUS_TAP_MSK BIT(8) +#define BMI323_STATUS_ERROR_MSK BIT(10) +#define BMI323_STATUS_TMP_DRDY_MSK BIT(11) +#define BMI323_STATUS_GYR_DRDY_MSK BIT(12) +#define BMI323_STATUS_ACC_DRDY_MSK BIT(13) +#define BMI323_STATUS_ACC_GYR_DRDY_MSK GENMASK(13, 12) +#define BMI323_STATUS_FIFO_WTRMRK_MSK BIT(14) +#define BMI323_STATUS_FIFO_FULL_MSK BIT(15) + +/* Feature registers */ +#define BMI323_FEAT_IO0_REG 0x10 +#define BMI323_FEAT_IO0_XYZ_NOMOTION_MSK GENMASK(2, 0) +#define BMI323_FEAT_IO0_XYZ_MOTION_MSK GENMASK(5, 3) +#define BMI323_FEAT_XYZ_MSK GENMASK(2, 0) +#define BMI323_FEAT_IO0_STP_CNT_MSK BIT(9) +#define BMI323_FEAT_IO0_S_TAP_MSK BIT(12) +#define BMI323_FEAT_IO0_D_TAP_MSK BIT(13) +#define BMI323_FEAT_IO1_REG 0x11 +#define BMI323_FEAT_IO1_ERR_MSK GENMASK(3, 0) +#define BMI323_FEAT_IO2_REG 0x12 +#define BMI323_FEAT_IO_STATUS_REG 0x14 +#define BMI323_FEAT_IO_STATUS_MSK BIT(0) +#define BMI323_FEAT_ENG_POLL 2000 +#define BMI323_FEAT_ENG_TIMEOUT 10000 + +/* FIFO registers */ +#define BMI323_FIFO_FILL_LEVEL_REG 0x15 +#define BMI323_FIFO_DATA_REG 0x16 + +/* Accelero/Gyro config registers */ +#define BMI323_ACC_CONF_REG 0x20 +#define BMI323_GYRO_CONF_REG 0x21 +#define BMI323_ACC_GYRO_CONF_MODE_MSK GENMASK(14, 12) +#define BMI323_ACC_GYRO_CONF_ODR_MSK GENMASK(3, 0) +#define BMI323_ACC_GYRO_CONF_SCL_MSK GENMASK(6, 4) +#define BMI323_ACC_GYRO_CONF_BW_MSK BIT(7) +#define BMI323_ACC_GYRO_CONF_AVG_MSK GENMASK(10, 8) + +/* FIFO registers */ +#define BMI323_FIFO_WTRMRK_REG 0x35 +#define BMI323_FIFO_CONF_REG 0x36 +#define BMI323_FIFO_CONF_STP_FUL_MSK BIT(0) +#define BMI323_FIFO_CONF_ACC_GYR_EN_MSK GENMASK(10, 9) +#define BMI323_FIFO_ACC_GYR_MSK GENMASK(1, 0) +#define BMI323_FIFO_CTRL_REG 0x37 +#define BMI323_FIFO_FLUSH_MSK BIT(0) + +/* Interrupt pin config registers */ +#define BMI323_IO_INT_CTR_REG 0x38 +#define BMI323_IO_INT1_LVL_MSK BIT(0) +#define BMI323_IO_INT1_OD_MSK BIT(1) +#define BMI323_IO_INT1_OP_EN_MSK BIT(2) +#define BMI323_IO_INT1_LVL_OD_OP_MSK GENMASK(2, 0) +#define BMI323_IO_INT2_LVL_MSK BIT(8) +#define BMI323_IO_INT2_OD_MSK BIT(9) +#define BMI323_IO_INT2_OP_EN_MSK BIT(10) +#define BMI323_IO_INT2_LVL_OD_OP_MSK GENMASK(10, 8) +#define BMI323_IO_INT_CONF_REG 0x39 +#define BMI323_IO_INT_LTCH_MSK BIT(0) +#define BMI323_INT_MAP1_REG 0x3A +#define BMI323_INT_MAP2_REG 0x3B +#define BMI323_NOMOTION_MSK GENMASK(1, 0) +#define BMI323_MOTION_MSK GENMASK(3, 2) +#define BMI323_STEP_CNT_MSK GENMASK(11, 10) +#define BMI323_TAP_MSK GENMASK(1, 0) +#define BMI323_TMP_DRDY_MSK GENMASK(7, 6) +#define BMI323_GYR_DRDY_MSK GENMASK(9, 8) +#define BMI323_ACC_DRDY_MSK GENMASK(11, 10) +#define BMI323_FIFO_WTRMRK_MSK GENMASK(13, 12) +#define BMI323_FIFO_FULL_MSK GENMASK(15, 14) + +/* Feature registers */ +#define BMI323_FEAT_CTRL_REG 0x40 +#define BMI323_FEAT_ENG_EN_MSK BIT(0) +#define BMI323_FEAT_DATA_ADDR 0x41 +#define BMI323_FEAT_DATA_TX 0x42 +#define BMI323_FEAT_DATA_STATUS 0x43 +#define BMI323_FEAT_DATA_TX_RDY_MSK BIT(1) +#define BMI323_FEAT_EVNT_EXT_REG 0x47 +#define BMI323_FEAT_EVNT_EXT_S_MSK BIT(3) +#define BMI323_FEAT_EVNT_EXT_D_MSK BIT(4) + +#define BMI323_CMD_REG 0x7E +#define BMI323_RST_VAL 0xDEAF +#define BMI323_CFG_RES_REG 0x7F + +/* Extended registers */ +#define BMI323_GEN_SET1_REG 0x02 +#define BMI323_GEN_SET1_MODE_MSK BIT(0) +#define BMI323_GEN_HOLD_DUR_MSK GENMASK(4, 1) + +/* Any Motion/No Motion config registers */ +#define BMI323_ANYMO1_REG 0x05 +#define BMI323_NOMO1_REG 0x08 +#define BMI323_MO2_OFFSET 0x01 +#define BMI323_MO3_OFFSET 0x02 +#define BMI323_MO1_REF_UP_MSK BIT(12) +#define BMI323_MO1_SLOPE_TH_MSK GENMASK(11, 0) +#define BMI323_MO2_HYSTR_MSK GENMASK(9, 0) +#define BMI323_MO3_DURA_MSK GENMASK(12, 0) + +/* Step counter config registers */ +#define BMI323_STEP_SC1_REG 0x10 +#define BMI323_STEP_SC1_WTRMRK_MSK GENMASK(9, 0) +#define BMI323_STEP_SC1_RST_CNT_MSK BIT(10) +#define BMI323_STEP_LEN 2 + +/* Tap gesture config registers */ +#define BMI323_TAP1_REG 0x1E +#define BMI323_TAP1_AXIS_SEL_MSK GENMASK(1, 0) +#define BMI323_AXIS_XYZ_MSK GENMASK(1, 0) +#define BMI323_TAP1_TIMOUT_MSK BIT(2) +#define BMI323_TAP1_MAX_PEAKS_MSK GENMASK(5, 3) +#define BMI323_TAP1_MODE_MSK GENMASK(7, 6) +#define BMI323_TAP2_REG 0x1F +#define BMI323_TAP2_THRES_MSK GENMASK(9, 0) +#define BMI323_TAP2_MAX_DUR_MSK GENMASK(15, 10) +#define BMI323_TAP3_REG 0x20 +#define BMI323_TAP3_QUIET_TIM_MSK GENMASK(15, 12) +#define BMI323_TAP3_QT_BW_TAP_MSK GENMASK(11, 8) +#define BMI323_TAP3_QT_AFT_GES_MSK GENMASK(15, 12) + +#define BMI323_MOTION_THRES_SCALE 512 +#define BMI323_MOTION_HYSTR_SCALE 512 +#define BMI323_MOTION_DURAT_SCALE 50 +#define BMI323_TAP_THRES_SCALE 512 +#define BMI323_DUR_BW_TAP_SCALE 200 +#define BMI323_QUITE_TIM_GES_SCALE 25 +#define BMI323_MAX_GES_DUR_SCALE 25 + +/* + * The formula to calculate temperature in C. + * See datasheet section 6.1.1, Register Map Overview + * + * T_C = (temp_raw / 512) + 23 + */ +#define BMI323_TEMP_OFFSET 11776 +#define BMI323_TEMP_SCALE 1953125 + +/* + * The BMI323 features a FIFO with a capacity of 2048 bytes. Each frame + * consists of accelerometer (X, Y, Z) data and gyroscope (X, Y, Z) data, + * totaling 6 words or 12 bytes. The FIFO buffer can hold a total of + * 170 frames. + * + * If a watermark interrupt is configured for 170 frames, the interrupt will + * trigger when the FIFO reaches 169 frames, so limit the maximum watermark + * level to 169 frames. In terms of data, 169 frames would equal 1014 bytes, + * which is approximately 2 frames before the FIFO reaches its full capacity. + * See datasheet section 5.7.3 FIFO Buffer Interrupts + */ +#define BMI323_BYTES_PER_SAMPLE 2 +#define BMI323_FIFO_LENGTH_IN_BYTES 2048 +#define BMI323_FIFO_FRAME_LENGTH 6 +#define BMI323_FIFO_FULL_IN_FRAMES \ + ((BMI323_FIFO_LENGTH_IN_BYTES / \ + (BMI323_BYTES_PER_SAMPLE * BMI323_FIFO_FRAME_LENGTH)) - 1) +#define BMI323_FIFO_FULL_IN_WORDS \ + (BMI323_FIFO_FULL_IN_FRAMES * BMI323_FIFO_FRAME_LENGTH) + +#define BMI323_INT_MICRO_TO_RAW(val, val2, scale) ((val) * (scale) + \ + ((val2) * (scale)) / MEGA) + +#define BMI323_RAW_TO_MICRO(raw, scale) ((((raw) % (scale)) * MEGA) / scale) + +struct device; +int bmi323_core_probe(struct device *dev); +extern const struct regmap_config bmi323_regmap_config; +extern const struct dev_pm_ops bmi323_core_pm_ops; + +#endif diff --git a/drivers/iio/imu/bmi323/bmi323_core.c b/drivers/iio/imu/bmi323/bmi323_core.c new file mode 100644 index 000000000..ebeb1b10c --- /dev/null +++ b/drivers/iio/imu/bmi323/bmi323_core.c @@ -0,0 +1,2303 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * IIO core driver for Bosch BMI323 6-Axis IMU. + * + * Copyright (C) 2023, Jagath Jog J + * + * Datasheet: https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmi323-ds000.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "bmi323.h" + +enum bmi323_sensor_type { + BMI323_ACCEL, + BMI323_GYRO, + BMI323_SENSORS_CNT, +}; + +enum bmi323_opr_mode { + ACC_GYRO_MODE_DISABLE = 0x00, + GYRO_DRIVE_MODE_ENABLED = 0x01, + ACC_GYRO_MODE_DUTYCYCLE = 0x03, + ACC_GYRO_MODE_CONTINOUS = 0x04, + ACC_GYRO_MODE_HIGH_PERF = 0x07, +}; + +enum bmi323_state { + BMI323_IDLE, + BMI323_BUFFER_DRDY_TRIGGERED, + BMI323_BUFFER_FIFO, +}; + +enum bmi323_irq_pin { + BMI323_IRQ_DISABLED, + BMI323_IRQ_INT1, + BMI323_IRQ_INT2, +}; + +enum bmi323_3db_bw { + BMI323_BW_ODR_BY_2, + BMI323_BW_ODR_BY_4, +}; + +enum bmi323_scan { + BMI323_ACCEL_X, + BMI323_ACCEL_Y, + BMI323_ACCEL_Z, + BMI323_GYRO_X, + BMI323_GYRO_Y, + BMI323_GYRO_Z, + BMI323_CHAN_MAX +}; + +struct bmi323_hw { + u8 data; + u8 config; + const int (*scale_table)[2]; + int scale_table_len; +}; + +/* + * The accelerometer supports +-2G/4G/8G/16G ranges, and the resolution of + * each sample is 16 bits, signed. + * At +-8G the scale can calculated by + * ((8 + 8) * 9.80665 / (2^16 - 1)) * 10^6 = 2394.23819 scale in micro + * + */ +static const int bmi323_accel_scale[][2] = { + { 0, 598 }, + { 0, 1197 }, + { 0, 2394 }, + { 0, 4788 }, +}; + +static const int bmi323_gyro_scale[][2] = { + { 0, 66 }, + { 0, 133 }, + { 0, 266 }, + { 0, 532 }, + { 0, 1065 }, +}; + +static const int bmi323_accel_gyro_avrg[] = {0, 2, 4, 8, 16, 32, 64}; + +static const struct bmi323_hw bmi323_hw[2] = { + [BMI323_ACCEL] = { + .data = BMI323_ACCEL_X_REG, + .config = BMI323_ACC_CONF_REG, + .scale_table = bmi323_accel_scale, + .scale_table_len = ARRAY_SIZE(bmi323_accel_scale), + }, + [BMI323_GYRO] = { + .data = BMI323_GYRO_X_REG, + .config = BMI323_GYRO_CONF_REG, + .scale_table = bmi323_gyro_scale, + .scale_table_len = ARRAY_SIZE(bmi323_gyro_scale), + }, +}; + +static const unsigned int bmi323_reg_savestate[] = { + BMI323_INT_MAP1_REG, + BMI323_INT_MAP2_REG, + BMI323_IO_INT_CTR_REG, + BMI323_IO_INT_CONF_REG, + BMI323_ACC_CONF_REG, + BMI323_GYRO_CONF_REG, + BMI323_FEAT_IO0_REG, + BMI323_FIFO_WTRMRK_REG, + BMI323_FIFO_CONF_REG +}; + +static const unsigned int bmi323_ext_reg_savestate[] = { + BMI323_GEN_SET1_REG, + BMI323_TAP1_REG, + BMI323_TAP2_REG, + BMI323_TAP3_REG, + BMI323_FEAT_IO0_S_TAP_MSK, + BMI323_STEP_SC1_REG, + BMI323_ANYMO1_REG, + BMI323_NOMO1_REG, + BMI323_ANYMO1_REG + BMI323_MO2_OFFSET, + BMI323_NOMO1_REG + BMI323_MO2_OFFSET, + BMI323_ANYMO1_REG + BMI323_MO3_OFFSET, + BMI323_NOMO1_REG + BMI323_MO3_OFFSET +}; + +struct bmi323_regs_runtime_pm { + unsigned int reg_settings[ARRAY_SIZE(bmi323_reg_savestate)]; + unsigned int ext_reg_settings[ARRAY_SIZE(bmi323_ext_reg_savestate)]; +}; + +struct bmi323_data { + struct device *dev; + struct regmap *regmap; + struct iio_mount_matrix orientation; + enum bmi323_irq_pin irq_pin; + struct iio_trigger *trig; + enum bmi323_state state; + s64 fifo_tstamp, old_fifo_tstamp; + u32 odrns[BMI323_SENSORS_CNT]; + u32 odrhz[BMI323_SENSORS_CNT]; + unsigned int feature_events; + struct bmi323_regs_runtime_pm runtime_pm_status; + + /* + * Lock to protect the members of device's private data from concurrent + * access and also to serialize the access of extended registers. + * See bmi323_write_ext_reg(..) for more info. + */ + struct mutex mutex; + int watermark; + __le16 fifo_buff[BMI323_FIFO_FULL_IN_WORDS] __aligned(IIO_DMA_MINALIGN); + struct { + __le16 channels[BMI323_CHAN_MAX]; + aligned_s64 ts; + } buffer; + __le16 steps_count[BMI323_STEP_LEN]; +}; + +static const struct iio_mount_matrix * +bmi323_get_mount_matrix(const struct iio_dev *idev, + const struct iio_chan_spec *chan) +{ + struct bmi323_data *data = iio_priv(idev); + + return &data->orientation; +} + +static const struct iio_chan_spec_ext_info bmi323_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_TYPE, bmi323_get_mount_matrix), + { } +}; + +static const struct iio_event_spec bmi323_step_wtrmrk_event = { + .type = IIO_EV_TYPE_CHANGE, + .dir = IIO_EV_DIR_NONE, + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE), +}; + +static const struct iio_event_spec bmi323_accel_event[] = { + { + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_FALLING, + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD) | + BIT(IIO_EV_INFO_HYSTERESIS) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD) | + BIT(IIO_EV_INFO_HYSTERESIS) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_GESTURE, + .dir = IIO_EV_DIR_SINGLETAP, + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_RESET_TIMEOUT), + }, + { + .type = IIO_EV_TYPE_GESTURE, + .dir = IIO_EV_DIR_DOUBLETAP, + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_RESET_TIMEOUT) | + BIT(IIO_EV_INFO_TAP2_MIN_DELAY), + }, +}; + +#define BMI323_ACCEL_CHANNEL(_type, _axis, _index) { \ + .type = _type, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ + .ext_info = bmi323_ext_info, \ + .event_spec = bmi323_accel_event, \ + .num_event_specs = ARRAY_SIZE(bmi323_accel_event), \ +} + +#define BMI323_GYRO_CHANNEL(_type, _axis, _index) { \ + .type = _type, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ + .ext_info = bmi323_ext_info, \ +} + +static const struct iio_chan_spec bmi323_channels[] = { + BMI323_ACCEL_CHANNEL(IIO_ACCEL, X, BMI323_ACCEL_X), + BMI323_ACCEL_CHANNEL(IIO_ACCEL, Y, BMI323_ACCEL_Y), + BMI323_ACCEL_CHANNEL(IIO_ACCEL, Z, BMI323_ACCEL_Z), + BMI323_GYRO_CHANNEL(IIO_ANGL_VEL, X, BMI323_GYRO_X), + BMI323_GYRO_CHANNEL(IIO_ANGL_VEL, Y, BMI323_GYRO_Y), + BMI323_GYRO_CHANNEL(IIO_ANGL_VEL, Z, BMI323_GYRO_Z), + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = -1, + }, + { + .type = IIO_STEPS, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_ENABLE), + .scan_index = -1, + .event_spec = &bmi323_step_wtrmrk_event, + .num_event_specs = 1, + + }, + IIO_CHAN_SOFT_TIMESTAMP(BMI323_CHAN_MAX), +}; + +static const int bmi323_acc_gyro_odr[][2] = { + { 0, 781250 }, + { 1, 562500 }, + { 3, 125000 }, + { 6, 250000 }, + { 12, 500000 }, + { 25, 0 }, + { 50, 0 }, + { 100, 0 }, + { 200, 0 }, + { 400, 0 }, + { 800, 0 }, +}; + +static const int bmi323_acc_gyro_odrns[] = { + 1280 * MEGA, + 640 * MEGA, + 320 * MEGA, + 160 * MEGA, + 80 * MEGA, + 40 * MEGA, + 20 * MEGA, + 10 * MEGA, + 5 * MEGA, + 2500 * KILO, + 1250 * KILO, +}; + +static enum bmi323_sensor_type bmi323_iio_to_sensor(enum iio_chan_type iio_type) +{ + switch (iio_type) { + case IIO_ACCEL: + return BMI323_ACCEL; + case IIO_ANGL_VEL: + return BMI323_GYRO; + default: + return -EINVAL; + } +} + +static int bmi323_set_mode(struct bmi323_data *data, + enum bmi323_sensor_type sensor, + enum bmi323_opr_mode mode) +{ + guard(mutex)(&data->mutex); + return regmap_update_bits(data->regmap, bmi323_hw[sensor].config, + BMI323_ACC_GYRO_CONF_MODE_MSK, + FIELD_PREP(BMI323_ACC_GYRO_CONF_MODE_MSK, + mode)); +} + +/* + * When writing data to extended register there must be no communication to + * any other register before write transaction is complete. + * See datasheet section 6.2 Extended Register Map Description. + */ +static int bmi323_write_ext_reg(struct bmi323_data *data, unsigned int ext_addr, + unsigned int ext_data) +{ + int ret, feature_status; + + ret = regmap_read(data->regmap, BMI323_FEAT_DATA_STATUS, + &feature_status); + if (ret) + return ret; + + if (!FIELD_GET(BMI323_FEAT_DATA_TX_RDY_MSK, feature_status)) + return -EBUSY; + + ret = regmap_write(data->regmap, BMI323_FEAT_DATA_ADDR, ext_addr); + if (ret) + return ret; + + return regmap_write(data->regmap, BMI323_FEAT_DATA_TX, ext_data); +} + +/* + * When reading data from extended register there must be no communication to + * any other register before read transaction is complete. + * See datasheet section 6.2 Extended Register Map Description. + */ +static int bmi323_read_ext_reg(struct bmi323_data *data, unsigned int ext_addr, + unsigned int *ext_data) +{ + int ret, feature_status; + + ret = regmap_read(data->regmap, BMI323_FEAT_DATA_STATUS, + &feature_status); + if (ret) + return ret; + + if (!FIELD_GET(BMI323_FEAT_DATA_TX_RDY_MSK, feature_status)) + return -EBUSY; + + ret = regmap_write(data->regmap, BMI323_FEAT_DATA_ADDR, ext_addr); + if (ret) + return ret; + + return regmap_read(data->regmap, BMI323_FEAT_DATA_TX, ext_data); +} + +static int bmi323_update_ext_reg(struct bmi323_data *data, + unsigned int ext_addr, + unsigned int mask, unsigned int ext_data) +{ + unsigned int value; + int ret; + + ret = bmi323_read_ext_reg(data, ext_addr, &value); + if (ret) + return ret; + + set_mask_bits(&value, mask, ext_data); + + return bmi323_write_ext_reg(data, ext_addr, value); +} + +static int bmi323_get_error_status(struct bmi323_data *data) +{ + int error, ret; + + guard(mutex)(&data->mutex); + ret = regmap_read(data->regmap, BMI323_ERR_REG, &error); + if (ret) + return ret; + + if (error) + dev_err(data->dev, "Sensor error 0x%x\n", error); + + return error; +} + +static int bmi323_feature_engine_events(struct bmi323_data *data, + const unsigned int event_mask, + bool state) +{ + unsigned int value; + int ret; + + ret = regmap_read(data->regmap, BMI323_FEAT_IO0_REG, &value); + if (ret) + return ret; + + /* Register must be cleared before changing an active config */ + ret = regmap_write(data->regmap, BMI323_FEAT_IO0_REG, 0); + if (ret) + return ret; + + if (state) + value |= event_mask; + else + value &= ~event_mask; + + ret = regmap_write(data->regmap, BMI323_FEAT_IO0_REG, value); + if (ret) + return ret; + + return regmap_write(data->regmap, BMI323_FEAT_IO_STATUS_REG, + BMI323_FEAT_IO_STATUS_MSK); +} + +static int bmi323_step_wtrmrk_en(struct bmi323_data *data, bool state) +{ + enum bmi323_irq_pin step_irq; + int ret; + + guard(mutex)(&data->mutex); + if (!FIELD_GET(BMI323_FEAT_IO0_STP_CNT_MSK, data->feature_events)) + return -EINVAL; + + if (state) + step_irq = data->irq_pin; + else + step_irq = BMI323_IRQ_DISABLED; + + ret = bmi323_update_ext_reg(data, BMI323_STEP_SC1_REG, + BMI323_STEP_SC1_WTRMRK_MSK, + FIELD_PREP(BMI323_STEP_SC1_WTRMRK_MSK, + state)); + if (ret) + return ret; + + return regmap_update_bits(data->regmap, BMI323_INT_MAP1_REG, + BMI323_STEP_CNT_MSK, + FIELD_PREP(BMI323_STEP_CNT_MSK, step_irq)); +} + +static int bmi323_motion_config_reg(enum iio_event_direction dir) +{ + switch (dir) { + case IIO_EV_DIR_RISING: + return BMI323_ANYMO1_REG; + case IIO_EV_DIR_FALLING: + return BMI323_NOMO1_REG; + default: + return -EINVAL; + } +} + +static int bmi323_motion_event_en(struct bmi323_data *data, + enum iio_event_direction dir, bool state) +{ + unsigned int state_value = state ? BMI323_FEAT_XYZ_MSK : 0; + int config, ret, msk, raw, field_value; + enum bmi323_irq_pin motion_irq; + int irq_msk, irq_field_val; + + if (state) + motion_irq = data->irq_pin; + else + motion_irq = BMI323_IRQ_DISABLED; + + switch (dir) { + case IIO_EV_DIR_RISING: + msk = BMI323_FEAT_IO0_XYZ_MOTION_MSK; + raw = 512; + config = BMI323_ANYMO1_REG; + irq_msk = BMI323_MOTION_MSK; + irq_field_val = FIELD_PREP(BMI323_MOTION_MSK, motion_irq); + field_value = FIELD_PREP(BMI323_FEAT_IO0_XYZ_MOTION_MSK, + state_value); + break; + case IIO_EV_DIR_FALLING: + msk = BMI323_FEAT_IO0_XYZ_NOMOTION_MSK; + raw = 0; + config = BMI323_NOMO1_REG; + irq_msk = BMI323_NOMOTION_MSK; + irq_field_val = FIELD_PREP(BMI323_NOMOTION_MSK, motion_irq); + field_value = FIELD_PREP(BMI323_FEAT_IO0_XYZ_NOMOTION_MSK, + state_value); + break; + default: + return -EINVAL; + } + + guard(mutex)(&data->mutex); + ret = bmi323_feature_engine_events(data, msk, state); + if (ret) + return ret; + + ret = bmi323_update_ext_reg(data, config, + BMI323_MO1_REF_UP_MSK, + FIELD_PREP(BMI323_MO1_REF_UP_MSK, 0)); + if (ret) + return ret; + + /* Set initial value to avoid interrupts while enabling*/ + ret = bmi323_update_ext_reg(data, config, + BMI323_MO1_SLOPE_TH_MSK, + FIELD_PREP(BMI323_MO1_SLOPE_TH_MSK, raw)); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, BMI323_INT_MAP1_REG, irq_msk, + irq_field_val); + if (ret) + return ret; + + set_mask_bits(&data->feature_events, msk, field_value); + + return 0; +} + +static int bmi323_tap_event_en(struct bmi323_data *data, + enum iio_event_direction dir, bool state) +{ + enum bmi323_irq_pin tap_irq; + int ret, tap_enabled; + + guard(mutex)(&data->mutex); + + if (data->odrhz[BMI323_ACCEL] < 200) { + dev_err(data->dev, "Invalid accelerometer parameter\n"); + return -EINVAL; + } + + switch (dir) { + case IIO_EV_DIR_SINGLETAP: + ret = bmi323_feature_engine_events(data, + BMI323_FEAT_IO0_S_TAP_MSK, + state); + if (ret) + return ret; + + set_mask_bits(&data->feature_events, BMI323_FEAT_IO0_S_TAP_MSK, + FIELD_PREP(BMI323_FEAT_IO0_S_TAP_MSK, state)); + break; + case IIO_EV_DIR_DOUBLETAP: + ret = bmi323_feature_engine_events(data, + BMI323_FEAT_IO0_D_TAP_MSK, + state); + if (ret) + return ret; + + set_mask_bits(&data->feature_events, BMI323_FEAT_IO0_D_TAP_MSK, + FIELD_PREP(BMI323_FEAT_IO0_D_TAP_MSK, state)); + break; + default: + return -EINVAL; + } + + tap_enabled = FIELD_GET(BMI323_FEAT_IO0_S_TAP_MSK | + BMI323_FEAT_IO0_D_TAP_MSK, + data->feature_events); + + if (tap_enabled) + tap_irq = data->irq_pin; + else + tap_irq = BMI323_IRQ_DISABLED; + + ret = regmap_update_bits(data->regmap, BMI323_INT_MAP2_REG, + BMI323_TAP_MSK, + FIELD_PREP(BMI323_TAP_MSK, tap_irq)); + if (ret) + return ret; + + if (!state) + return 0; + + ret = bmi323_update_ext_reg(data, BMI323_TAP1_REG, + BMI323_TAP1_MAX_PEAKS_MSK, + FIELD_PREP(BMI323_TAP1_MAX_PEAKS_MSK, + 0x04)); + if (ret) + return ret; + + ret = bmi323_update_ext_reg(data, BMI323_TAP1_REG, + BMI323_TAP1_AXIS_SEL_MSK, + FIELD_PREP(BMI323_TAP1_AXIS_SEL_MSK, + BMI323_AXIS_XYZ_MSK)); + if (ret) + return ret; + + return bmi323_update_ext_reg(data, BMI323_TAP1_REG, + BMI323_TAP1_TIMOUT_MSK, + FIELD_PREP(BMI323_TAP1_TIMOUT_MSK, + 0)); +} + +static ssize_t in_accel_gesture_tap_wait_dur_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct bmi323_data *data = iio_priv(indio_dev); + unsigned int reg_value, raw; + int ret, val[2]; + + scoped_guard(mutex, &data->mutex) { + ret = bmi323_read_ext_reg(data, BMI323_TAP2_REG, ®_value); + if (ret) + return ret; + } + + raw = FIELD_GET(BMI323_TAP2_MAX_DUR_MSK, reg_value); + val[0] = raw / BMI323_MAX_GES_DUR_SCALE; + val[1] = BMI323_RAW_TO_MICRO(raw, BMI323_MAX_GES_DUR_SCALE); + + return iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, ARRAY_SIZE(val), + val); +} + +static ssize_t in_accel_gesture_tap_wait_dur_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct bmi323_data *data = iio_priv(indio_dev); + int ret, val_int, val_fract, raw; + + ret = iio_str_to_fixpoint(buf, 100000, &val_int, &val_fract); + if (ret) + return ret; + + raw = BMI323_INT_MICRO_TO_RAW(val_int, val_fract, + BMI323_MAX_GES_DUR_SCALE); + if (!in_range(raw, 0, 64)) + return -EINVAL; + + guard(mutex)(&data->mutex); + ret = bmi323_update_ext_reg(data, BMI323_TAP2_REG, + BMI323_TAP2_MAX_DUR_MSK, + FIELD_PREP(BMI323_TAP2_MAX_DUR_MSK, raw)); + if (ret) + return ret; + + return len; +} + +/* + * Maximum duration from first tap within the second tap is expected to happen. + * This timeout is applicable only if gesture_tap_wait_timeout is enabled. + */ +static IIO_DEVICE_ATTR_RW(in_accel_gesture_tap_wait_dur, 0); + +static ssize_t in_accel_gesture_tap_wait_timeout_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct bmi323_data *data = iio_priv(indio_dev); + unsigned int reg_value, raw; + int ret; + + scoped_guard(mutex, &data->mutex) { + ret = bmi323_read_ext_reg(data, BMI323_TAP1_REG, ®_value); + if (ret) + return ret; + } + + raw = FIELD_GET(BMI323_TAP1_TIMOUT_MSK, reg_value); + + return iio_format_value(buf, IIO_VAL_INT, 1, &raw); +} + +static ssize_t in_accel_gesture_tap_wait_timeout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct bmi323_data *data = iio_priv(indio_dev); + bool val; + int ret; + + ret = kstrtobool(buf, &val); + if (ret) + return ret; + + guard(mutex)(&data->mutex); + ret = bmi323_update_ext_reg(data, BMI323_TAP1_REG, + BMI323_TAP1_TIMOUT_MSK, + FIELD_PREP(BMI323_TAP1_TIMOUT_MSK, val)); + if (ret) + return ret; + + return len; +} + +/* Enable/disable gesture confirmation with wait time */ +static IIO_DEVICE_ATTR_RW(in_accel_gesture_tap_wait_timeout, 0); + +static IIO_CONST_ATTR(in_accel_gesture_tap_wait_dur_available, + "[0.0 0.04 2.52]"); + +static IIO_CONST_ATTR(in_accel_gesture_doubletap_tap2_min_delay_available, + "[0.005 0.005 0.075]"); + +static IIO_CONST_ATTR(in_accel_gesture_tap_reset_timeout_available, + "[0.04 0.04 0.6]"); + +static IIO_CONST_ATTR(in_accel_gesture_tap_value_available, "[0.0 0.002 1.99]"); + +static IIO_CONST_ATTR(in_accel_mag_value_available, "[0.0 0.002 7.99]"); + +static IIO_CONST_ATTR(in_accel_mag_period_available, "[0.0 0.02 162.0]"); + +static IIO_CONST_ATTR(in_accel_mag_hysteresis_available, "[0.0 0.002 1.99]"); + +static struct attribute *bmi323_event_attributes[] = { + &iio_const_attr_in_accel_gesture_tap_value_available.dev_attr.attr, + &iio_const_attr_in_accel_gesture_tap_reset_timeout_available.dev_attr.attr, + &iio_const_attr_in_accel_gesture_doubletap_tap2_min_delay_available.dev_attr.attr, + &iio_const_attr_in_accel_gesture_tap_wait_dur_available.dev_attr.attr, + &iio_dev_attr_in_accel_gesture_tap_wait_timeout.dev_attr.attr, + &iio_dev_attr_in_accel_gesture_tap_wait_dur.dev_attr.attr, + &iio_const_attr_in_accel_mag_value_available.dev_attr.attr, + &iio_const_attr_in_accel_mag_period_available.dev_attr.attr, + &iio_const_attr_in_accel_mag_hysteresis_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group bmi323_event_attribute_group = { + .attrs = bmi323_event_attributes, +}; + +static int bmi323_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct bmi323_data *data = iio_priv(indio_dev); + + switch (type) { + case IIO_EV_TYPE_MAG: + return bmi323_motion_event_en(data, dir, state); + case IIO_EV_TYPE_GESTURE: + return bmi323_tap_event_en(data, dir, state); + case IIO_EV_TYPE_CHANGE: + return bmi323_step_wtrmrk_en(data, state); + default: + return -EINVAL; + } +} + +static int bmi323_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct bmi323_data *data = iio_priv(indio_dev); + int ret, value, reg_val; + + guard(mutex)(&data->mutex); + + switch (chan->type) { + case IIO_ACCEL: + switch (dir) { + case IIO_EV_DIR_SINGLETAP: + ret = FIELD_GET(BMI323_FEAT_IO0_S_TAP_MSK, + data->feature_events); + break; + case IIO_EV_DIR_DOUBLETAP: + ret = FIELD_GET(BMI323_FEAT_IO0_D_TAP_MSK, + data->feature_events); + break; + case IIO_EV_DIR_RISING: + value = FIELD_GET(BMI323_FEAT_IO0_XYZ_MOTION_MSK, + data->feature_events); + ret = value ? 1 : 0; + break; + case IIO_EV_DIR_FALLING: + value = FIELD_GET(BMI323_FEAT_IO0_XYZ_NOMOTION_MSK, + data->feature_events); + ret = value ? 1 : 0; + break; + default: + ret = -EINVAL; + break; + } + return ret; + case IIO_STEPS: + ret = regmap_read(data->regmap, BMI323_INT_MAP1_REG, ®_val); + if (ret) + return ret; + + return FIELD_GET(BMI323_STEP_CNT_MSK, reg_val) ? 1 : 0; + default: + return -EINVAL; + } +} + +static int bmi323_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct bmi323_data *data = iio_priv(indio_dev); + unsigned int raw; + int reg; + + guard(mutex)(&data->mutex); + + switch (type) { + case IIO_EV_TYPE_GESTURE: + switch (info) { + case IIO_EV_INFO_VALUE: + if (!in_range(val, 0, 2)) + return -EINVAL; + + raw = BMI323_INT_MICRO_TO_RAW(val, val2, + BMI323_TAP_THRES_SCALE); + + return bmi323_update_ext_reg(data, BMI323_TAP2_REG, + BMI323_TAP2_THRES_MSK, + FIELD_PREP(BMI323_TAP2_THRES_MSK, + raw)); + case IIO_EV_INFO_RESET_TIMEOUT: + if (val || !in_range(val2, 40000, 560001)) + return -EINVAL; + + raw = BMI323_INT_MICRO_TO_RAW(val, val2, + BMI323_QUITE_TIM_GES_SCALE); + + return bmi323_update_ext_reg(data, BMI323_TAP3_REG, + BMI323_TAP3_QT_AFT_GES_MSK, + FIELD_PREP(BMI323_TAP3_QT_AFT_GES_MSK, + raw)); + case IIO_EV_INFO_TAP2_MIN_DELAY: + if (val || !in_range(val2, 5000, 70001)) + return -EINVAL; + + raw = BMI323_INT_MICRO_TO_RAW(val, val2, + BMI323_DUR_BW_TAP_SCALE); + + return bmi323_update_ext_reg(data, BMI323_TAP3_REG, + BMI323_TAP3_QT_BW_TAP_MSK, + FIELD_PREP(BMI323_TAP3_QT_BW_TAP_MSK, + raw)); + default: + return -EINVAL; + } + case IIO_EV_TYPE_MAG: + reg = bmi323_motion_config_reg(dir); + if (reg < 0) + return -EINVAL; + + switch (info) { + case IIO_EV_INFO_VALUE: + if (!in_range(val, 0, 8)) + return -EINVAL; + + raw = BMI323_INT_MICRO_TO_RAW(val, val2, + BMI323_MOTION_THRES_SCALE); + + return bmi323_update_ext_reg(data, reg, + BMI323_MO1_SLOPE_TH_MSK, + FIELD_PREP(BMI323_MO1_SLOPE_TH_MSK, + raw)); + case IIO_EV_INFO_PERIOD: + if (!in_range(val, 0, 163)) + return -EINVAL; + + raw = BMI323_INT_MICRO_TO_RAW(val, val2, + BMI323_MOTION_DURAT_SCALE); + + return bmi323_update_ext_reg(data, + reg + BMI323_MO3_OFFSET, + BMI323_MO3_DURA_MSK, + FIELD_PREP(BMI323_MO3_DURA_MSK, + raw)); + case IIO_EV_INFO_HYSTERESIS: + if (!in_range(val, 0, 2)) + return -EINVAL; + + raw = BMI323_INT_MICRO_TO_RAW(val, val2, + BMI323_MOTION_HYSTR_SCALE); + + return bmi323_update_ext_reg(data, + reg + BMI323_MO2_OFFSET, + BMI323_MO2_HYSTR_MSK, + FIELD_PREP(BMI323_MO2_HYSTR_MSK, + raw)); + default: + return -EINVAL; + } + case IIO_EV_TYPE_CHANGE: + if (!in_range(val, 0, 20461)) + return -EINVAL; + + raw = val / 20; + return bmi323_update_ext_reg(data, BMI323_STEP_SC1_REG, + BMI323_STEP_SC1_WTRMRK_MSK, + FIELD_PREP(BMI323_STEP_SC1_WTRMRK_MSK, + raw)); + default: + return -EINVAL; + } +} + +static int bmi323_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct bmi323_data *data = iio_priv(indio_dev); + unsigned int raw, reg_value; + int ret, reg; + + guard(mutex)(&data->mutex); + + switch (type) { + case IIO_EV_TYPE_GESTURE: + switch (info) { + case IIO_EV_INFO_VALUE: + ret = bmi323_read_ext_reg(data, BMI323_TAP2_REG, + ®_value); + if (ret) + return ret; + + raw = FIELD_GET(BMI323_TAP2_THRES_MSK, reg_value); + *val = raw / BMI323_TAP_THRES_SCALE; + *val2 = BMI323_RAW_TO_MICRO(raw, BMI323_TAP_THRES_SCALE); + return IIO_VAL_INT_PLUS_MICRO; + case IIO_EV_INFO_RESET_TIMEOUT: + ret = bmi323_read_ext_reg(data, BMI323_TAP3_REG, + ®_value); + if (ret) + return ret; + + raw = FIELD_GET(BMI323_TAP3_QT_AFT_GES_MSK, reg_value); + *val = 0; + *val2 = BMI323_RAW_TO_MICRO(raw, + BMI323_QUITE_TIM_GES_SCALE); + return IIO_VAL_INT_PLUS_MICRO; + case IIO_EV_INFO_TAP2_MIN_DELAY: + ret = bmi323_read_ext_reg(data, BMI323_TAP3_REG, + ®_value); + if (ret) + return ret; + + raw = FIELD_GET(BMI323_TAP3_QT_BW_TAP_MSK, reg_value); + *val = 0; + *val2 = BMI323_RAW_TO_MICRO(raw, + BMI323_DUR_BW_TAP_SCALE); + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_EV_TYPE_MAG: + reg = bmi323_motion_config_reg(dir); + if (reg < 0) + return -EINVAL; + + switch (info) { + case IIO_EV_INFO_VALUE: + ret = bmi323_read_ext_reg(data, reg, ®_value); + if (ret) + return ret; + + raw = FIELD_GET(BMI323_MO1_SLOPE_TH_MSK, reg_value); + *val = raw / BMI323_MOTION_THRES_SCALE; + *val2 = BMI323_RAW_TO_MICRO(raw, + BMI323_MOTION_THRES_SCALE); + return IIO_VAL_INT_PLUS_MICRO; + case IIO_EV_INFO_PERIOD: + ret = bmi323_read_ext_reg(data, + reg + BMI323_MO3_OFFSET, + ®_value); + if (ret) + return ret; + + raw = FIELD_GET(BMI323_MO3_DURA_MSK, reg_value); + *val = raw / BMI323_MOTION_DURAT_SCALE; + *val2 = BMI323_RAW_TO_MICRO(raw, + BMI323_MOTION_DURAT_SCALE); + return IIO_VAL_INT_PLUS_MICRO; + case IIO_EV_INFO_HYSTERESIS: + ret = bmi323_read_ext_reg(data, + reg + BMI323_MO2_OFFSET, + ®_value); + if (ret) + return ret; + + raw = FIELD_GET(BMI323_MO2_HYSTR_MSK, reg_value); + *val = raw / BMI323_MOTION_HYSTR_SCALE; + *val2 = BMI323_RAW_TO_MICRO(raw, + BMI323_MOTION_HYSTR_SCALE); + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_EV_TYPE_CHANGE: + ret = bmi323_read_ext_reg(data, BMI323_STEP_SC1_REG, + ®_value); + if (ret) + return ret; + + raw = FIELD_GET(BMI323_STEP_SC1_WTRMRK_MSK, reg_value); + *val = raw * 20; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int __bmi323_fifo_flush(struct iio_dev *indio_dev) +{ + struct bmi323_data *data = iio_priv(indio_dev); + int i, ret, fifo_lvl, frame_count, bit, index; + __le16 *frame, *pchannels; + u64 sample_period; + s64 tstamp; + + guard(mutex)(&data->mutex); + ret = regmap_read(data->regmap, BMI323_FIFO_FILL_LEVEL_REG, &fifo_lvl); + if (ret) + return ret; + + fifo_lvl = min(fifo_lvl, BMI323_FIFO_FULL_IN_WORDS); + + frame_count = fifo_lvl / BMI323_FIFO_FRAME_LENGTH; + if (!frame_count) + return -EINVAL; + + if (fifo_lvl % BMI323_FIFO_FRAME_LENGTH) + dev_warn(data->dev, "Bad FIFO alignment\n"); + + /* + * Approximate timestamps for each of the sample based on the sampling + * frequency, timestamp for last sample and number of samples. + */ + if (data->old_fifo_tstamp) { + sample_period = data->fifo_tstamp - data->old_fifo_tstamp; + do_div(sample_period, frame_count); + } else { + sample_period = data->odrns[BMI323_ACCEL]; + } + + tstamp = data->fifo_tstamp - (frame_count - 1) * sample_period; + + ret = regmap_noinc_read(data->regmap, BMI323_FIFO_DATA_REG, + &data->fifo_buff[0], + fifo_lvl * BMI323_BYTES_PER_SAMPLE); + if (ret) + return ret; + + for (i = 0; i < frame_count; i++) { + frame = &data->fifo_buff[i * BMI323_FIFO_FRAME_LENGTH]; + pchannels = &data->buffer.channels[0]; + + index = 0; + for_each_set_bit(bit, indio_dev->active_scan_mask, + BMI323_CHAN_MAX) + pchannels[index++] = frame[bit]; + + iio_push_to_buffers_with_timestamp(indio_dev, &data->buffer, + tstamp); + + tstamp += sample_period; + } + + return frame_count; +} + +static int bmi323_set_watermark(struct iio_dev *indio_dev, unsigned int val) +{ + struct bmi323_data *data = iio_priv(indio_dev); + + val = min(val, BMI323_FIFO_FULL_IN_FRAMES); + + guard(mutex)(&data->mutex); + data->watermark = val; + + return 0; +} + +static int bmi323_fifo_disable(struct bmi323_data *data) +{ + int ret; + + guard(mutex)(&data->mutex); + ret = regmap_write(data->regmap, BMI323_FIFO_CONF_REG, 0); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, BMI323_INT_MAP2_REG, + BMI323_FIFO_WTRMRK_MSK, + FIELD_PREP(BMI323_FIFO_WTRMRK_MSK, 0)); + if (ret) + return ret; + + data->fifo_tstamp = 0; + data->state = BMI323_IDLE; + + return 0; +} + +static int bmi323_buffer_predisable(struct iio_dev *indio_dev) +{ + struct bmi323_data *data = iio_priv(indio_dev); + + if (iio_device_get_current_mode(indio_dev) == INDIO_BUFFER_TRIGGERED) + return 0; + + return bmi323_fifo_disable(data); +} + +static int bmi323_update_watermark(struct bmi323_data *data) +{ + int wtrmrk; + + wtrmrk = data->watermark * BMI323_FIFO_FRAME_LENGTH; + + return regmap_write(data->regmap, BMI323_FIFO_WTRMRK_REG, wtrmrk); +} + +static int bmi323_fifo_enable(struct bmi323_data *data) +{ + int ret; + + guard(mutex)(&data->mutex); + ret = regmap_update_bits(data->regmap, BMI323_FIFO_CONF_REG, + BMI323_FIFO_CONF_ACC_GYR_EN_MSK, + FIELD_PREP(BMI323_FIFO_CONF_ACC_GYR_EN_MSK, + BMI323_FIFO_ACC_GYR_MSK)); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, BMI323_INT_MAP2_REG, + BMI323_FIFO_WTRMRK_MSK, + FIELD_PREP(BMI323_FIFO_WTRMRK_MSK, + data->irq_pin)); + if (ret) + return ret; + + ret = bmi323_update_watermark(data); + if (ret) + return ret; + + ret = regmap_write(data->regmap, BMI323_FIFO_CTRL_REG, + BMI323_FIFO_FLUSH_MSK); + if (ret) + return ret; + + data->state = BMI323_BUFFER_FIFO; + + return 0; +} + +static int bmi323_buffer_preenable(struct iio_dev *indio_dev) +{ + struct bmi323_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->mutex); + /* + * When the ODR of the accelerometer and gyroscope do not match, the + * maximum ODR value between the accelerometer and gyroscope is used + * for FIFO and the signal with lower ODR will insert dummy frame. + * So allow buffer read only when ODR's of accelero and gyro are equal. + * See datasheet section 5.7 "FIFO Data Buffering". + */ + if (data->odrns[BMI323_ACCEL] != data->odrns[BMI323_GYRO]) { + dev_err(data->dev, "Accelero and Gyro ODR doesn't match\n"); + return -EINVAL; + } + + return 0; +} + +static int bmi323_buffer_postenable(struct iio_dev *indio_dev) +{ + struct bmi323_data *data = iio_priv(indio_dev); + + if (iio_device_get_current_mode(indio_dev) == INDIO_BUFFER_TRIGGERED) + return 0; + + return bmi323_fifo_enable(data); +} + +static ssize_t hwfifo_watermark_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct bmi323_data *data = iio_priv(indio_dev); + int wm; + + scoped_guard(mutex, &data->mutex) + wm = data->watermark; + + return sysfs_emit(buf, "%d\n", wm); +} +static IIO_DEVICE_ATTR_RO(hwfifo_watermark, 0); + +static ssize_t hwfifo_enabled_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct bmi323_data *data = iio_priv(indio_dev); + bool state; + + scoped_guard(mutex, &data->mutex) + state = data->state == BMI323_BUFFER_FIFO; + + return sysfs_emit(buf, "%d\n", state); +} +static IIO_DEVICE_ATTR_RO(hwfifo_enabled, 0); + +static const struct iio_dev_attr *bmi323_fifo_attributes[] = { + &iio_dev_attr_hwfifo_watermark, + &iio_dev_attr_hwfifo_enabled, + NULL +}; + +static const struct iio_buffer_setup_ops bmi323_buffer_ops = { + .preenable = bmi323_buffer_preenable, + .postenable = bmi323_buffer_postenable, + .predisable = bmi323_buffer_predisable, +}; + +static irqreturn_t bmi323_irq_thread_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct bmi323_data *data = iio_priv(indio_dev); + unsigned int status_addr, status, feature_event; + s64 timestamp = iio_get_time_ns(indio_dev); + int ret; + + if (data->irq_pin == BMI323_IRQ_INT1) + status_addr = BMI323_STATUS_INT1_REG; + else + status_addr = BMI323_STATUS_INT2_REG; + + scoped_guard(mutex, &data->mutex) { + ret = regmap_read(data->regmap, status_addr, &status); + if (ret) + return IRQ_NONE; + } + + if (!status || FIELD_GET(BMI323_STATUS_ERROR_MSK, status)) + return IRQ_NONE; + + if (FIELD_GET(BMI323_STATUS_FIFO_WTRMRK_MSK, status)) { + data->old_fifo_tstamp = data->fifo_tstamp; + data->fifo_tstamp = iio_get_time_ns(indio_dev); + ret = __bmi323_fifo_flush(indio_dev); + if (ret < 0) + return IRQ_NONE; + } + + if (FIELD_GET(BMI323_STATUS_ACC_GYR_DRDY_MSK, status)) + iio_trigger_poll_nested(data->trig); + + if (FIELD_GET(BMI323_STATUS_MOTION_MSK, status)) + iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_RISING), + timestamp); + + if (FIELD_GET(BMI323_STATUS_NOMOTION_MSK, status)) + iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_FALLING), + timestamp); + + if (FIELD_GET(BMI323_STATUS_STP_WTR_MSK, status)) + iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_STEPS, 0, + IIO_NO_MOD, + IIO_EV_TYPE_CHANGE, + IIO_EV_DIR_NONE), + timestamp); + + if (FIELD_GET(BMI323_STATUS_TAP_MSK, status)) { + scoped_guard(mutex, &data->mutex) { + ret = regmap_read(data->regmap, + BMI323_FEAT_EVNT_EXT_REG, + &feature_event); + if (ret) + return IRQ_NONE; + } + + if (FIELD_GET(BMI323_FEAT_EVNT_EXT_S_MSK, feature_event)) { + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_GESTURE, + IIO_EV_DIR_SINGLETAP), + timestamp); + } + + if (FIELD_GET(BMI323_FEAT_EVNT_EXT_D_MSK, feature_event)) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_GESTURE, + IIO_EV_DIR_DOUBLETAP), + timestamp); + } + + return IRQ_HANDLED; +} + +static int bmi323_set_drdy_irq(struct bmi323_data *data, + enum bmi323_irq_pin irq_pin) +{ + int ret; + + ret = regmap_update_bits(data->regmap, BMI323_INT_MAP2_REG, + BMI323_GYR_DRDY_MSK, + FIELD_PREP(BMI323_GYR_DRDY_MSK, irq_pin)); + if (ret) + return ret; + + return regmap_update_bits(data->regmap, BMI323_INT_MAP2_REG, + BMI323_ACC_DRDY_MSK, + FIELD_PREP(BMI323_ACC_DRDY_MSK, irq_pin)); +} + +static int bmi323_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct bmi323_data *data = iio_trigger_get_drvdata(trig); + enum bmi323_irq_pin irq_pin; + + guard(mutex)(&data->mutex); + + if (data->state == BMI323_BUFFER_FIFO) { + dev_warn(data->dev, "Can't set trigger when FIFO enabled\n"); + return -EBUSY; + } + + if (state) { + data->state = BMI323_BUFFER_DRDY_TRIGGERED; + irq_pin = data->irq_pin; + } else { + data->state = BMI323_IDLE; + irq_pin = BMI323_IRQ_DISABLED; + } + + return bmi323_set_drdy_irq(data, irq_pin); +} + +static const struct iio_trigger_ops bmi323_trigger_ops = { + .set_trigger_state = &bmi323_data_rdy_trigger_set_state, +}; + +static irqreturn_t bmi323_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bmi323_data *data = iio_priv(indio_dev); + int ret, bit, index = 0; + + /* Lock to protect the data->buffer */ + guard(mutex)(&data->mutex); + + if (*indio_dev->active_scan_mask == BMI323_ALL_CHAN_MSK) { + ret = regmap_bulk_read(data->regmap, BMI323_ACCEL_X_REG, + &data->buffer.channels, + ARRAY_SIZE(data->buffer.channels)); + if (ret) + goto out; + } else { + for_each_set_bit(bit, indio_dev->active_scan_mask, + BMI323_CHAN_MAX) { + ret = regmap_raw_read(data->regmap, + BMI323_ACCEL_X_REG + bit, + &data->buffer.channels[index++], + BMI323_BYTES_PER_SAMPLE); + if (ret) + goto out; + } + } + + iio_push_to_buffers_with_timestamp(indio_dev, &data->buffer, + iio_get_time_ns(indio_dev)); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int bmi323_set_average(struct bmi323_data *data, + enum bmi323_sensor_type sensor, int avg) +{ + int raw = ARRAY_SIZE(bmi323_accel_gyro_avrg); + + while (raw--) + if (avg == bmi323_accel_gyro_avrg[raw]) + break; + if (raw < 0) + return -EINVAL; + + guard(mutex)(&data->mutex); + return regmap_update_bits(data->regmap, bmi323_hw[sensor].config, + BMI323_ACC_GYRO_CONF_AVG_MSK, + FIELD_PREP(BMI323_ACC_GYRO_CONF_AVG_MSK, + raw)); +} + +static int bmi323_get_average(struct bmi323_data *data, + enum bmi323_sensor_type sensor, int *avg) +{ + int ret, value, raw; + + scoped_guard(mutex, &data->mutex) { + ret = regmap_read(data->regmap, bmi323_hw[sensor].config, &value); + if (ret) + return ret; + } + + raw = FIELD_GET(BMI323_ACC_GYRO_CONF_AVG_MSK, value); + *avg = bmi323_accel_gyro_avrg[raw]; + + return IIO_VAL_INT; +} + +static int bmi323_enable_steps(struct bmi323_data *data, int val) +{ + int ret; + + guard(mutex)(&data->mutex); + if (data->odrhz[BMI323_ACCEL] < 200) { + dev_err(data->dev, "Invalid accelerometer parameter\n"); + return -EINVAL; + } + + ret = bmi323_feature_engine_events(data, BMI323_FEAT_IO0_STP_CNT_MSK, + val ? 1 : 0); + if (ret) + return ret; + + set_mask_bits(&data->feature_events, BMI323_FEAT_IO0_STP_CNT_MSK, + FIELD_PREP(BMI323_FEAT_IO0_STP_CNT_MSK, val ? 1 : 0)); + + return 0; +} + +static int bmi323_read_steps(struct bmi323_data *data, int *val) +{ + int ret; + + guard(mutex)(&data->mutex); + if (!FIELD_GET(BMI323_FEAT_IO0_STP_CNT_MSK, data->feature_events)) + return -EINVAL; + + ret = regmap_bulk_read(data->regmap, BMI323_FEAT_IO2_REG, + data->steps_count, + ARRAY_SIZE(data->steps_count)); + if (ret) + return ret; + + *val = get_unaligned_le32(data->steps_count); + + return IIO_VAL_INT; +} + +static int bmi323_read_axis(struct bmi323_data *data, + struct iio_chan_spec const *chan, int *val) +{ + enum bmi323_sensor_type sensor; + unsigned int value; + u8 addr; + int ret; + + ret = bmi323_get_error_status(data); + if (ret) + return -EINVAL; + + sensor = bmi323_iio_to_sensor(chan->type); + addr = bmi323_hw[sensor].data + (chan->channel2 - IIO_MOD_X); + + scoped_guard(mutex, &data->mutex) { + ret = regmap_read(data->regmap, addr, &value); + if (ret) + return ret; + } + + *val = sign_extend32(value, chan->scan_type.realbits - 1); + + return IIO_VAL_INT; +} + +static int bmi323_get_temp_data(struct bmi323_data *data, int *val) +{ + unsigned int value; + int ret; + + ret = bmi323_get_error_status(data); + if (ret) + return -EINVAL; + + scoped_guard(mutex, &data->mutex) { + ret = regmap_read(data->regmap, BMI323_TEMP_REG, &value); + if (ret) + return ret; + } + + *val = sign_extend32(value, 15); + + return IIO_VAL_INT; +} + +static int bmi323_get_odr(struct bmi323_data *data, + enum bmi323_sensor_type sensor, int *odr, int *uodr) +{ + int ret, value, odr_raw; + + scoped_guard(mutex, &data->mutex) { + ret = regmap_read(data->regmap, bmi323_hw[sensor].config, &value); + if (ret) + return ret; + } + + odr_raw = FIELD_GET(BMI323_ACC_GYRO_CONF_ODR_MSK, value); + *odr = bmi323_acc_gyro_odr[odr_raw - 1][0]; + *uodr = bmi323_acc_gyro_odr[odr_raw - 1][1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int bmi323_configure_power_mode(struct bmi323_data *data, + enum bmi323_sensor_type sensor, + int odr_index) +{ + enum bmi323_opr_mode mode; + + if (bmi323_acc_gyro_odr[odr_index][0] > 25) + mode = ACC_GYRO_MODE_CONTINOUS; + else + mode = ACC_GYRO_MODE_DUTYCYCLE; + + return bmi323_set_mode(data, sensor, mode); +} + +static int bmi323_set_odr(struct bmi323_data *data, + enum bmi323_sensor_type sensor, int odr, int uodr) +{ + int odr_raw, ret; + + odr_raw = ARRAY_SIZE(bmi323_acc_gyro_odr); + + while (odr_raw--) + if (odr == bmi323_acc_gyro_odr[odr_raw][0] && + uodr == bmi323_acc_gyro_odr[odr_raw][1]) + break; + if (odr_raw < 0) + return -EINVAL; + + ret = bmi323_configure_power_mode(data, sensor, odr_raw); + if (ret) + return -EINVAL; + + guard(mutex)(&data->mutex); + data->odrhz[sensor] = bmi323_acc_gyro_odr[odr_raw][0]; + data->odrns[sensor] = bmi323_acc_gyro_odrns[odr_raw]; + + odr_raw++; + + return regmap_update_bits(data->regmap, bmi323_hw[sensor].config, + BMI323_ACC_GYRO_CONF_ODR_MSK, + FIELD_PREP(BMI323_ACC_GYRO_CONF_ODR_MSK, + odr_raw)); +} + +static int bmi323_get_scale(struct bmi323_data *data, + enum bmi323_sensor_type sensor, int *val2) +{ + int ret, value, scale_raw; + + scoped_guard(mutex, &data->mutex) { + ret = regmap_read(data->regmap, bmi323_hw[sensor].config, + &value); + if (ret) + return ret; + } + + scale_raw = FIELD_GET(BMI323_ACC_GYRO_CONF_SCL_MSK, value); + *val2 = bmi323_hw[sensor].scale_table[scale_raw][1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int bmi323_set_scale(struct bmi323_data *data, + enum bmi323_sensor_type sensor, int val, int val2) +{ + int scale_raw; + + scale_raw = bmi323_hw[sensor].scale_table_len; + + while (scale_raw--) + if (val == bmi323_hw[sensor].scale_table[scale_raw][0] && + val2 == bmi323_hw[sensor].scale_table[scale_raw][1]) + break; + if (scale_raw < 0) + return -EINVAL; + + guard(mutex)(&data->mutex); + return regmap_update_bits(data->regmap, bmi323_hw[sensor].config, + BMI323_ACC_GYRO_CONF_SCL_MSK, + FIELD_PREP(BMI323_ACC_GYRO_CONF_SCL_MSK, + scale_raw)); +} + +static int bmi323_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + enum bmi323_sensor_type sensor; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *type = IIO_VAL_INT_PLUS_MICRO; + *vals = (const int *)bmi323_acc_gyro_odr; + *length = ARRAY_SIZE(bmi323_acc_gyro_odr) * 2; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SCALE: + sensor = bmi323_iio_to_sensor(chan->type); + *type = IIO_VAL_INT_PLUS_MICRO; + *vals = (const int *)bmi323_hw[sensor].scale_table; + *length = bmi323_hw[sensor].scale_table_len * 2; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *type = IIO_VAL_INT; + *vals = (const int *)bmi323_accel_gyro_avrg; + *length = ARRAY_SIZE(bmi323_accel_gyro_avrg); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int bmi323_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct bmi323_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = bmi323_set_odr(data, bmi323_iio_to_sensor(chan->type), + val, val2); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SCALE: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = bmi323_set_scale(data, bmi323_iio_to_sensor(chan->type), + val, val2); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = bmi323_set_average(data, bmi323_iio_to_sensor(chan->type), + val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_ENABLE: + return bmi323_enable_steps(data, val); + case IIO_CHAN_INFO_PROCESSED: { + guard(mutex)(&data->mutex); + + if (val || !FIELD_GET(BMI323_FEAT_IO0_STP_CNT_MSK, + data->feature_events)) + return -EINVAL; + + /* Clear step counter value */ + return bmi323_update_ext_reg(data, BMI323_STEP_SC1_REG, + BMI323_STEP_SC1_RST_CNT_MSK, + FIELD_PREP(BMI323_STEP_SC1_RST_CNT_MSK, + 1)); + } + default: + return -EINVAL; + } +} + +static int bmi323_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct bmi323_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + return bmi323_read_steps(data, val); + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_ACCEL: + case IIO_ANGL_VEL: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = bmi323_read_axis(data, chan, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_TEMP: + return bmi323_get_temp_data(data, val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + return bmi323_get_odr(data, bmi323_iio_to_sensor(chan->type), + val, val2); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ACCEL: + case IIO_ANGL_VEL: + *val = 0; + return bmi323_get_scale(data, + bmi323_iio_to_sensor(chan->type), + val2); + case IIO_TEMP: + *val = BMI323_TEMP_SCALE / MEGA; + *val2 = BMI323_TEMP_SCALE % MEGA; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return bmi323_get_average(data, + bmi323_iio_to_sensor(chan->type), + val); + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + *val = BMI323_TEMP_OFFSET; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_ENABLE: + scoped_guard(mutex, &data->mutex) + *val = FIELD_GET(BMI323_FEAT_IO0_STP_CNT_MSK, + data->feature_events); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static const struct iio_info bmi323_info = { + .read_raw = bmi323_read_raw, + .write_raw = bmi323_write_raw, + .read_avail = bmi323_read_avail, + .hwfifo_set_watermark = bmi323_set_watermark, + .write_event_config = bmi323_write_event_config, + .read_event_config = bmi323_read_event_config, + .write_event_value = bmi323_write_event_value, + .read_event_value = bmi323_read_event_value, + .event_attrs = &bmi323_event_attribute_group, +}; + +#define BMI323_SCAN_MASK_ACCEL_3AXIS \ + (BIT(BMI323_ACCEL_X) | BIT(BMI323_ACCEL_Y) | BIT(BMI323_ACCEL_Z)) + +#define BMI323_SCAN_MASK_GYRO_3AXIS \ + (BIT(BMI323_GYRO_X) | BIT(BMI323_GYRO_Y) | BIT(BMI323_GYRO_Z)) + +static const unsigned long bmi323_avail_scan_masks[] = { + /* 3-axis accel */ + BMI323_SCAN_MASK_ACCEL_3AXIS, + /* 3-axis gyro */ + BMI323_SCAN_MASK_GYRO_3AXIS, + /* 3-axis accel + 3-axis gyro */ + BMI323_SCAN_MASK_ACCEL_3AXIS | BMI323_SCAN_MASK_GYRO_3AXIS, + 0 +}; + +static int bmi323_int_pin_config(struct bmi323_data *data, + enum bmi323_irq_pin irq_pin, + bool active_high, bool open_drain, bool latch) +{ + unsigned int mask, field_value; + int ret; + + ret = regmap_update_bits(data->regmap, BMI323_IO_INT_CONF_REG, + BMI323_IO_INT_LTCH_MSK, + FIELD_PREP(BMI323_IO_INT_LTCH_MSK, latch)); + if (ret) + return ret; + + ret = bmi323_update_ext_reg(data, BMI323_GEN_SET1_REG, + BMI323_GEN_HOLD_DUR_MSK, + FIELD_PREP(BMI323_GEN_HOLD_DUR_MSK, 0)); + if (ret) + return ret; + + switch (irq_pin) { + case BMI323_IRQ_INT1: + mask = BMI323_IO_INT1_LVL_OD_OP_MSK; + + field_value = FIELD_PREP(BMI323_IO_INT1_LVL_MSK, active_high) | + FIELD_PREP(BMI323_IO_INT1_OD_MSK, open_drain) | + FIELD_PREP(BMI323_IO_INT1_OP_EN_MSK, 1); + break; + case BMI323_IRQ_INT2: + mask = BMI323_IO_INT2_LVL_OD_OP_MSK; + + field_value = FIELD_PREP(BMI323_IO_INT2_LVL_MSK, active_high) | + FIELD_PREP(BMI323_IO_INT2_OD_MSK, open_drain) | + FIELD_PREP(BMI323_IO_INT2_OP_EN_MSK, 1); + break; + default: + return -EINVAL; + } + + return regmap_update_bits(data->regmap, BMI323_IO_INT_CTR_REG, mask, + field_value); +} + +static int bmi323_trigger_probe(struct bmi323_data *data, + struct iio_dev *indio_dev) +{ + bool open_drain, active_high, latch; + struct fwnode_handle *fwnode; + enum bmi323_irq_pin irq_pin; + int ret, irq, irq_type; + + fwnode = dev_fwnode(data->dev); + if (!fwnode) + return -ENODEV; + + irq = fwnode_irq_get_byname(fwnode, "INT1"); + if (irq > 0) { + irq_pin = BMI323_IRQ_INT1; + } else { + irq = fwnode_irq_get_byname(fwnode, "INT2"); + if (irq < 0) + return 0; + + irq_pin = BMI323_IRQ_INT2; + } + + irq_type = irq_get_trigger_type(irq); + switch (irq_type) { + case IRQF_TRIGGER_RISING: + latch = false; + active_high = true; + break; + case IRQF_TRIGGER_HIGH: + latch = true; + active_high = true; + break; + case IRQF_TRIGGER_FALLING: + latch = false; + active_high = false; + break; + case IRQF_TRIGGER_LOW: + latch = true; + active_high = false; + break; + default: + return dev_err_probe(data->dev, -EINVAL, + "Invalid interrupt type 0x%x specified\n", + irq_type); + } + + open_drain = fwnode_property_read_bool(fwnode, "drive-open-drain"); + + ret = bmi323_int_pin_config(data, irq_pin, active_high, open_drain, + latch); + if (ret) + return dev_err_probe(data->dev, ret, + "Failed to configure irq line\n"); + + data->trig = devm_iio_trigger_alloc(data->dev, "%s-trig-%d", + indio_dev->name, irq_pin); + if (!data->trig) + return -ENOMEM; + + data->trig->ops = &bmi323_trigger_ops; + iio_trigger_set_drvdata(data->trig, data); + + ret = devm_request_threaded_irq(data->dev, irq, NULL, + bmi323_irq_thread_handler, + IRQF_ONESHOT, "bmi323-int", indio_dev); + if (ret) + return ret; + + ret = devm_iio_trigger_register(data->dev, data->trig); + if (ret) + return dev_err_probe(data->dev, ret, + "Trigger registration failed\n"); + + data->irq_pin = irq_pin; + + return 0; +} + +static int bmi323_feature_engine_enable(struct bmi323_data *data, bool en) +{ + unsigned int feature_status; + int ret; + + if (!en) + return regmap_write(data->regmap, BMI323_FEAT_CTRL_REG, 0); + + ret = regmap_write(data->regmap, BMI323_FEAT_IO2_REG, 0x012c); + if (ret) + return ret; + + ret = regmap_write(data->regmap, BMI323_FEAT_IO_STATUS_REG, + BMI323_FEAT_IO_STATUS_MSK); + if (ret) + return ret; + + ret = regmap_write(data->regmap, BMI323_FEAT_CTRL_REG, + BMI323_FEAT_ENG_EN_MSK); + if (ret) + return ret; + + /* + * It takes around 4 msec to enable the Feature engine, so check + * the status of the feature engine every 2 msec for a maximum + * of 5 trials. + */ + ret = regmap_read_poll_timeout(data->regmap, BMI323_FEAT_IO1_REG, + feature_status, + FIELD_GET(BMI323_FEAT_IO1_ERR_MSK, + feature_status) == 1, + BMI323_FEAT_ENG_POLL, + BMI323_FEAT_ENG_TIMEOUT); + if (ret) + return dev_err_probe(data->dev, -EINVAL, + "Failed to enable feature engine\n"); + + return 0; +} + +static void bmi323_disable(void *data_ptr) +{ + struct bmi323_data *data = data_ptr; + + bmi323_set_mode(data, BMI323_ACCEL, ACC_GYRO_MODE_DISABLE); + bmi323_set_mode(data, BMI323_GYRO, ACC_GYRO_MODE_DISABLE); + + /* + * Place the peripheral in its lowest power consuming state. + */ + regmap_write(data->regmap, BMI323_CMD_REG, BMI323_RST_VAL); +} + +static int bmi323_set_bw(struct bmi323_data *data, + enum bmi323_sensor_type sensor, enum bmi323_3db_bw bw) +{ + return regmap_update_bits(data->regmap, bmi323_hw[sensor].config, + BMI323_ACC_GYRO_CONF_BW_MSK, + FIELD_PREP(BMI323_ACC_GYRO_CONF_BW_MSK, bw)); +} + +static int bmi323_init(struct bmi323_data *data) +{ + int ret, val; + + /* + * Perform soft reset to make sure the device is in a known state after + * start up. A delay of 1.5 ms is required after reset. + * See datasheet section 5.17 "Soft Reset". + */ + ret = regmap_write(data->regmap, BMI323_CMD_REG, BMI323_RST_VAL); + if (ret) + return ret; + + usleep_range(1500, 2000); + + /* + * Dummy read is required to enable SPI interface after reset. + * See datasheet section 7.2.1 "Protocol Selection". + */ + regmap_read(data->regmap, BMI323_CHIP_ID_REG, &val); + + ret = regmap_read(data->regmap, BMI323_STATUS_REG, &val); + if (ret) + return ret; + + if (!FIELD_GET(BMI323_STATUS_POR_MSK, val)) + return dev_err_probe(data->dev, -EINVAL, + "Sensor initialization error\n"); + + ret = regmap_read(data->regmap, BMI323_CHIP_ID_REG, &val); + if (ret) + return ret; + + if (FIELD_GET(BMI323_CHIP_ID_MSK, val) != BMI323_CHIP_ID_VAL) + return dev_err_probe(data->dev, -EINVAL, "Chip ID mismatch\n"); + + ret = bmi323_feature_engine_enable(data, true); + if (ret) + return ret; + + ret = regmap_read(data->regmap, BMI323_ERR_REG, &val); + if (ret) + return ret; + + if (val) + return dev_err_probe(data->dev, -EINVAL, + "Sensor power error = 0x%x\n", val); + + return 0; +} + +static int bmi323_init_reset(struct bmi323_data *data) +{ + int ret; + + /* + * Set the Bandwidth coefficient which defines the 3 dB cutoff + * frequency in relation to the ODR. + */ + ret = bmi323_set_bw(data, BMI323_ACCEL, BMI323_BW_ODR_BY_2); + if (ret) + return ret; + + ret = bmi323_set_bw(data, BMI323_GYRO, BMI323_BW_ODR_BY_2); + if (ret) + return ret; + + ret = bmi323_set_odr(data, BMI323_ACCEL, 25, 0); + if (ret) + return ret; + + ret = bmi323_set_odr(data, BMI323_GYRO, 25, 0); + if (ret) + return ret; + + return devm_add_action_or_reset(data->dev, bmi323_disable, data); +} + +int bmi323_core_probe(struct device *dev) +{ + static const char * const regulator_names[] = { "vdd", "vddio" }; + struct iio_dev *indio_dev; + struct bmi323_data *data; + struct regmap *regmap; + int ret; + + regmap = dev_get_regmap(dev, NULL); + if (!regmap) + return dev_err_probe(dev, -ENODEV, "Failed to get regmap\n"); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulator_names), + regulator_names); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulators\n"); + + data = iio_priv(indio_dev); + data->dev = dev; + data->regmap = regmap; + data->irq_pin = BMI323_IRQ_DISABLED; + data->state = BMI323_IDLE; + mutex_init(&data->mutex); + + ret = bmi323_init(data); + if (ret) + return -EINVAL; + + ret = bmi323_init_reset(data); + if (ret) + return -EINVAL; + + if (!iio_read_acpi_mount_matrix(dev, &data->orientation, "ROTM")) { + ret = iio_read_mount_matrix(dev, &data->orientation); + if (ret) + return ret; + } + + indio_dev->name = "bmi323-imu"; + indio_dev->info = &bmi323_info; + indio_dev->channels = bmi323_channels; + indio_dev->num_channels = ARRAY_SIZE(bmi323_channels); + indio_dev->available_scan_masks = bmi323_avail_scan_masks; + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + dev_set_drvdata(data->dev, indio_dev); + + ret = bmi323_trigger_probe(data, indio_dev); + if (ret) + return -EINVAL; + + ret = devm_iio_triggered_buffer_setup_ext(data->dev, indio_dev, + &iio_pollfunc_store_time, + bmi323_trigger_handler, + IIO_BUFFER_DIRECTION_IN, + &bmi323_buffer_ops, + bmi323_fifo_attributes); + if (ret) + return dev_err_probe(data->dev, ret, + "Failed to setup trigger buffer\n"); + + ret = devm_iio_device_register(data->dev, indio_dev); + if (ret) + return dev_err_probe(data->dev, ret, + "Unable to register iio device\n"); + + return bmi323_fifo_disable(data); +} +EXPORT_SYMBOL_NS_GPL(bmi323_core_probe, "IIO_BMI323"); + +static int bmi323_core_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmi323_data *data = iio_priv(indio_dev); + struct bmi323_regs_runtime_pm *savestate = &data->runtime_pm_status; + int ret; + + guard(mutex)(&data->mutex); + + ret = iio_device_suspend_triggering(indio_dev); + if (ret) + return ret; + + /* Save registers meant to be restored by resume pm callback. */ + for (unsigned int i = 0; i < ARRAY_SIZE(bmi323_reg_savestate); i++) { + ret = regmap_read(data->regmap, bmi323_reg_savestate[i], + &savestate->reg_settings[i]); + if (ret) { + dev_err(data->dev, + "Error reading bmi323 reg 0x%x: %d\n", + bmi323_reg_savestate[i], ret); + return ret; + } + } + + for (unsigned int i = 0; i < ARRAY_SIZE(bmi323_ext_reg_savestate); i++) { + ret = bmi323_read_ext_reg(data, bmi323_ext_reg_savestate[i], + &savestate->ext_reg_settings[i]); + if (ret) { + dev_err(data->dev, + "Error reading bmi323 external reg 0x%x: %d\n", + bmi323_ext_reg_savestate[i], ret); + return ret; + } + } + + /* Perform soft reset to place the device in its lowest power state. */ + ret = regmap_write(data->regmap, BMI323_CMD_REG, BMI323_RST_VAL); + if (ret) + return ret; + + return 0; +} + +static int bmi323_core_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmi323_data *data = iio_priv(indio_dev); + struct bmi323_regs_runtime_pm *savestate = &data->runtime_pm_status; + unsigned int val; + int ret; + + guard(mutex)(&data->mutex); + + /* + * Perform the device power-on and initial setup once again + * after being reset in the lower power state by runtime-pm. + */ + ret = bmi323_init(data); + if (ret) { + dev_err(data->dev, "Device power-on and init failed: %d", ret); + return ret; + } + + /* Register must be cleared before changing an active config */ + ret = regmap_write(data->regmap, BMI323_FEAT_IO0_REG, 0); + if (ret) { + dev_err(data->dev, "Error stopping feature engine\n"); + return ret; + } + + for (unsigned int i = 0; i < ARRAY_SIZE(bmi323_ext_reg_savestate); i++) { + ret = bmi323_write_ext_reg(data, bmi323_ext_reg_savestate[i], + savestate->ext_reg_settings[i]); + if (ret) { + dev_err(data->dev, + "Error writing bmi323 external reg 0x%x: %d\n", + bmi323_ext_reg_savestate[i], ret); + return ret; + } + } + + for (unsigned int i = 0; i < ARRAY_SIZE(bmi323_reg_savestate); i++) { + ret = regmap_write(data->regmap, bmi323_reg_savestate[i], + savestate->reg_settings[i]); + if (ret) { + dev_err(data->dev, + "Error writing bmi323 reg 0x%x: %d\n", + bmi323_reg_savestate[i], ret); + return ret; + } + } + + /* + * Clear old FIFO samples that might be generated before suspend + * or generated from a peripheral state not equal to the saved one. + */ + if (data->state == BMI323_BUFFER_FIFO) { + ret = regmap_write(data->regmap, BMI323_FIFO_CTRL_REG, + BMI323_FIFO_FLUSH_MSK); + if (ret) { + dev_err(data->dev, "Error flushing FIFO buffer: %d\n", ret); + return ret; + } + } + + ret = regmap_read(data->regmap, BMI323_ERR_REG, &val); + if (ret) { + dev_err(data->dev, + "Error reading bmi323 error register: %d\n", ret); + return ret; + } + + if (val) { + dev_err(data->dev, + "Sensor power error in PM = 0x%x\n", val); + return -EINVAL; + } + + return iio_device_resume_triggering(indio_dev); +} + +const struct dev_pm_ops bmi323_core_pm_ops = { + RUNTIME_PM_OPS(bmi323_core_runtime_suspend, + bmi323_core_runtime_resume, NULL) +}; +EXPORT_SYMBOL_NS_GPL(bmi323_core_pm_ops, "IIO_BMI323"); + +MODULE_DESCRIPTION("Bosch BMI323 IMU driver"); +MODULE_AUTHOR("Jagath Jog J "); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/imu/bmi323/bmi323_i2c.c b/drivers/iio/imu/bmi323/bmi323_i2c.c new file mode 100644 index 000000000..e835b91ea --- /dev/null +++ b/drivers/iio/imu/bmi323/bmi323_i2c.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * I2C driver for Bosch BMI323 6-Axis IMU. + * + * Copyright (C) 2023, Jagath Jog J + */ + +#include +#include +#include + +#include "bmi323.h" + +struct bmi323_i2c_priv { + struct i2c_client *i2c; + u8 i2c_rx_buffer[BMI323_FIFO_LENGTH_IN_BYTES + BMI323_I2C_DUMMY]; +}; + +/* + * From BMI323 datasheet section 4: Notes on the Serial Interface Support. + * Each I2C register read operation requires to read two dummy bytes before + * the actual payload. + */ +static int bmi323_regmap_i2c_read(void *context, const void *reg_buf, + size_t reg_size, void *val_buf, + size_t val_size) +{ + struct bmi323_i2c_priv *priv = context; + struct i2c_msg msgs[2]; + int ret; + + msgs[0].addr = priv->i2c->addr; + msgs[0].flags = priv->i2c->flags; + msgs[0].len = reg_size; + msgs[0].buf = (u8 *)reg_buf; + + msgs[1].addr = priv->i2c->addr; + msgs[1].len = val_size + BMI323_I2C_DUMMY; + msgs[1].buf = priv->i2c_rx_buffer; + msgs[1].flags = priv->i2c->flags | I2C_M_RD; + + ret = i2c_transfer(priv->i2c->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + return -EIO; + + memcpy(val_buf, priv->i2c_rx_buffer + BMI323_I2C_DUMMY, val_size); + + return 0; +} + +static int bmi323_regmap_i2c_write(void *context, const void *data, + size_t count) +{ + struct bmi323_i2c_priv *priv = context; + u8 reg; + + reg = *(u8 *)data; + return i2c_smbus_write_i2c_block_data(priv->i2c, reg, + count - sizeof(u8), + data + sizeof(u8)); +} + +static const struct regmap_bus bmi323_regmap_bus = { + .read = bmi323_regmap_i2c_read, + .write = bmi323_regmap_i2c_write, +}; + +static const struct regmap_config bmi323_i2c_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = BMI323_CFG_RES_REG, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + +static int bmi323_i2c_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct bmi323_i2c_priv *priv; + struct regmap *regmap; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->i2c = i2c; + regmap = devm_regmap_init(dev, &bmi323_regmap_bus, priv, + &bmi323_i2c_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Failed to initialize I2C Regmap\n"); + + return bmi323_core_probe(dev); +} + +static const struct acpi_device_id bmi323_acpi_match[] = { + /* + * The "BOSC0200" identifier used here is not unique to bmi323 devices. + * The same "BOSC0200" identifier is found in the ACPI tables of devices + * using the bmc150 chip. This creates a conflict with duplicate ACPI + * identifiers which multiple drivers want to use. If a non-bmi323 + * device starts to load with this "BOSC0200" ACPI match here, then the + * chip ID check portion should fail because the chip IDs received (via + * i2c) are unique between bmc150 and bmi323 and the driver should + * relinquish the device. If and when a different driver (such as + * bmc150) starts to load with the "BOSC0200" ACPI match, a short reset + * should ensure that the device is not in a bad state during that + * driver initialization. This device reset does occur in both the + * bmi323 and bmc150 init sequences. + */ + { "BOSC0200" }, + { } +}; +MODULE_DEVICE_TABLE(acpi, bmi323_acpi_match); + +static const struct i2c_device_id bmi323_i2c_ids[] = { + { .name = "bmi323" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bmi323_i2c_ids); + +static const struct of_device_id bmi323_of_i2c_match[] = { + { .compatible = "bosch,bmi323" }, + { } +}; +MODULE_DEVICE_TABLE(of, bmi323_of_i2c_match); + +static struct i2c_driver bmi323_i2c_driver = { + .driver = { + .name = "bmi323", + .pm = pm_ptr(&bmi323_core_pm_ops), + .of_match_table = bmi323_of_i2c_match, + .acpi_match_table = bmi323_acpi_match, + }, + .probe = bmi323_i2c_probe, + .id_table = bmi323_i2c_ids, +}; +module_i2c_driver(bmi323_i2c_driver); + +MODULE_DESCRIPTION("Bosch BMI323 IMU driver"); +MODULE_AUTHOR("Jagath Jog J "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_BMI323"); diff --git a/drivers/iio/imu/bmi323/bmi323_spi.c b/drivers/iio/imu/bmi323/bmi323_spi.c new file mode 100644 index 000000000..ed0235cc4 --- /dev/null +++ b/drivers/iio/imu/bmi323/bmi323_spi.c @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SPI driver for Bosch BMI323 6-Axis IMU. + * + * Copyright (C) 2023, Jagath Jog J + */ + +#include +#include +#include + +#include "bmi323.h" + +/* + * From BMI323 datasheet section 4: Notes on the Serial Interface Support. + * Each SPI register read operation requires to read one dummy byte before + * the actual payload. + */ +static int bmi323_regmap_spi_read(void *context, const void *reg_buf, + size_t reg_size, void *val_buf, + size_t val_size) +{ + struct spi_device *spi = context; + + return spi_write_then_read(spi, reg_buf, reg_size, val_buf, val_size); +} + +static int bmi323_regmap_spi_write(void *context, const void *data, + size_t count) +{ + struct spi_device *spi = context; + u8 *data_buff = (u8 *)data; + + data_buff[1] = data_buff[0]; + return spi_write(spi, data_buff + 1, count - 1); +} + +static const struct regmap_bus bmi323_regmap_bus = { + .read = bmi323_regmap_spi_read, + .write = bmi323_regmap_spi_write, +}; + +static const struct regmap_config bmi323_spi_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .pad_bits = 8, + .read_flag_mask = BIT(7), + .max_register = BMI323_CFG_RES_REG, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + +static int bmi323_spi_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct regmap *regmap; + + regmap = devm_regmap_init(dev, &bmi323_regmap_bus, dev, + &bmi323_spi_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Failed to initialize SPI Regmap\n"); + + return bmi323_core_probe(dev); +} + +static const struct spi_device_id bmi323_spi_ids[] = { + { .name = "bmi323" }, + { } +}; +MODULE_DEVICE_TABLE(spi, bmi323_spi_ids); + +static const struct of_device_id bmi323_of_spi_match[] = { + { .compatible = "bosch,bmi323" }, + { } +}; +MODULE_DEVICE_TABLE(of, bmi323_of_spi_match); + +static struct spi_driver bmi323_spi_driver = { + .driver = { + .name = "bmi323", + .pm = pm_ptr(&bmi323_core_pm_ops), + .of_match_table = bmi323_of_spi_match, + }, + .probe = bmi323_spi_probe, + .id_table = bmi323_spi_ids, +}; +module_spi_driver(bmi323_spi_driver); + +MODULE_DESCRIPTION("Bosch BMI323 IMU driver"); +MODULE_AUTHOR("Jagath Jog J "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_BMI323"); diff --git a/drivers/iio/imu/bno055/Kconfig b/drivers/iio/imu/bno055/Kconfig new file mode 100644 index 000000000..c7f5866a1 --- /dev/null +++ b/drivers/iio/imu/bno055/Kconfig @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-2.0 + +config BOSCH_BNO055 + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config BOSCH_BNO055_SERIAL + tristate "Bosch BNO055 attached via UART" + depends on SERIAL_DEV_BUS + select REGMAP + select BOSCH_BNO055 + help + Enable this to support Bosch BNO055 IMUs attached via UART. + + This driver can also be built as a module. If so, the module will be + called bno055_sl. + +config BOSCH_BNO055_I2C + tristate "Bosch BNO055 attached via I2C bus" + depends on I2C + select REGMAP_I2C + select BOSCH_BNO055 + help + Enable this to support Bosch BNO055 IMUs attached via I2C bus. + + This driver can also be built as a module. If so, the module will be + called bno055_i2c. diff --git a/drivers/iio/imu/bno055/Makefile b/drivers/iio/imu/bno055/Makefile new file mode 100644 index 000000000..98c624730 --- /dev/null +++ b/drivers/iio/imu/bno055/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_BOSCH_BNO055) += bno055.o +obj-$(CONFIG_BOSCH_BNO055_SERIAL) += bno055_ser.o +bno055_ser-y := bno055_ser_core.o +# define_trace.h needs to know how to find our header +CFLAGS_bno055_ser_trace.o := -I$(src) +bno055_ser-$(CONFIG_TRACING) += bno055_ser_trace.o + +obj-$(CONFIG_BOSCH_BNO055_I2C) += bno055_i2c.o diff --git a/drivers/iio/imu/bno055/bno055.c b/drivers/iio/imu/bno055/bno055.c new file mode 100644 index 000000000..d0101607a --- /dev/null +++ b/drivers/iio/imu/bno055/bno055.c @@ -0,0 +1,1714 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * IIO driver for Bosch BNO055 IMU + * + * Copyright (C) 2021-2022 Istituto Italiano di Tecnologia + * Electronic Design Laboratory + * Written by Andrea Merello + * + * Portions of this driver are taken from the BNO055 driver patch + * from Vlad Dogaru which is Copyright (c) 2016, Intel Corporation. + * + * This driver is also based on BMI160 driver, which is: + * Copyright (c) 2016, Intel Corporation. + * Copyright (c) 2019, Martin Kelly. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "bno055.h" + +#define BNO055_FW_UID_FMT "bno055-caldata-%*phN.dat" +#define BNO055_FW_GENERIC_NAME "bno055-caldata.dat" + +/* common registers */ +#define BNO055_PAGESEL_REG 0x7 + +/* page 0 registers */ +#define BNO055_CHIP_ID_REG 0x0 +#define BNO055_CHIP_ID_MAGIC 0xA0 +#define BNO055_SW_REV_LSB_REG 0x4 +#define BNO055_SW_REV_MSB_REG 0x5 +#define BNO055_ACC_DATA_X_LSB_REG 0x8 +#define BNO055_ACC_DATA_Y_LSB_REG 0xA +#define BNO055_ACC_DATA_Z_LSB_REG 0xC +#define BNO055_MAG_DATA_X_LSB_REG 0xE +#define BNO055_MAG_DATA_Y_LSB_REG 0x10 +#define BNO055_MAG_DATA_Z_LSB_REG 0x12 +#define BNO055_GYR_DATA_X_LSB_REG 0x14 +#define BNO055_GYR_DATA_Y_LSB_REG 0x16 +#define BNO055_GYR_DATA_Z_LSB_REG 0x18 +#define BNO055_EUL_DATA_X_LSB_REG 0x1A +#define BNO055_EUL_DATA_Y_LSB_REG 0x1C +#define BNO055_EUL_DATA_Z_LSB_REG 0x1E +#define BNO055_QUAT_DATA_W_LSB_REG 0x20 +#define BNO055_LIA_DATA_X_LSB_REG 0x28 +#define BNO055_LIA_DATA_Y_LSB_REG 0x2A +#define BNO055_LIA_DATA_Z_LSB_REG 0x2C +#define BNO055_GRAVITY_DATA_X_LSB_REG 0x2E +#define BNO055_GRAVITY_DATA_Y_LSB_REG 0x30 +#define BNO055_GRAVITY_DATA_Z_LSB_REG 0x32 +#define BNO055_SCAN_CH_COUNT ((BNO055_GRAVITY_DATA_Z_LSB_REG - BNO055_ACC_DATA_X_LSB_REG) / 2 + 1) +#define BNO055_TEMP_REG 0x34 +#define BNO055_CALIB_STAT_REG 0x35 +#define BNO055_CALIB_STAT_MAGN_SHIFT 0 +#define BNO055_CALIB_STAT_ACCEL_SHIFT 2 +#define BNO055_CALIB_STAT_GYRO_SHIFT 4 +#define BNO055_CALIB_STAT_SYS_SHIFT 6 +#define BNO055_SYS_ERR_REG 0x3A +#define BNO055_POWER_MODE_REG 0x3E +#define BNO055_POWER_MODE_NORMAL 0 +#define BNO055_SYS_TRIGGER_REG 0x3F +#define BNO055_SYS_TRIGGER_RST_SYS BIT(5) +#define BNO055_SYS_TRIGGER_CLK_SEL BIT(7) +#define BNO055_OPR_MODE_REG 0x3D +#define BNO055_OPR_MODE_CONFIG 0x0 +#define BNO055_OPR_MODE_AMG 0x7 +#define BNO055_OPR_MODE_FUSION_FMC_OFF 0xB +#define BNO055_OPR_MODE_FUSION 0xC +#define BNO055_UNIT_SEL_REG 0x3B +/* Android orientation mode means: pitch value decreases turning clockwise */ +#define BNO055_UNIT_SEL_ANDROID BIT(7) +#define BNO055_UNIT_SEL_GYR_RPS BIT(1) +#define BNO055_CALDATA_START 0x55 +#define BNO055_CALDATA_END 0x6A +#define BNO055_CALDATA_LEN 22 + +/* + * The difference in address between the register that contains the + * value and the register that contains the offset. This applies for + * accel, gyro and magn channels. + */ +#define BNO055_REG_OFFSET_ADDR 0x4D + +/* page 1 registers */ +#define BNO055_PG1(x) ((x) | 0x80) +#define BNO055_ACC_CONFIG_REG BNO055_PG1(0x8) +#define BNO055_ACC_CONFIG_LPF_MASK GENMASK(4, 2) +#define BNO055_ACC_CONFIG_RANGE_MASK GENMASK(1, 0) +#define BNO055_MAG_CONFIG_REG BNO055_PG1(0x9) +#define BNO055_MAG_CONFIG_HIGHACCURACY 0x18 +#define BNO055_MAG_CONFIG_ODR_MASK GENMASK(2, 0) +#define BNO055_GYR_CONFIG_REG BNO055_PG1(0xA) +#define BNO055_GYR_CONFIG_RANGE_MASK GENMASK(2, 0) +#define BNO055_GYR_CONFIG_LPF_MASK GENMASK(5, 3) +#define BNO055_GYR_AM_SET_REG BNO055_PG1(0x1F) +#define BNO055_UID_LOWER_REG BNO055_PG1(0x50) +#define BNO055_UID_HIGHER_REG BNO055_PG1(0x5F) +#define BNO055_UID_LEN 16 + +struct bno055_sysfs_attr { + const int *vals; + int len; + const int *fusion_vals; + const int *hw_xlate; + int hw_xlate_len; + int type; +}; + +static const int bno055_acc_lpf_vals[] = { + 7, 810000, 15, 630000, 31, 250000, 62, 500000, + 125, 0, 250, 0, 500, 0, 1000, 0, +}; + +static const struct bno055_sysfs_attr bno055_acc_lpf = { + .vals = bno055_acc_lpf_vals, + .len = ARRAY_SIZE(bno055_acc_lpf_vals), + .fusion_vals = (const int[]){62, 500000}, + .type = IIO_VAL_INT_PLUS_MICRO, +}; + +static const int bno055_acc_range_vals[] = { + /* G: 2, 4, 8, 16 */ + 1962, 3924, 7848, 15696 +}; + +static const struct bno055_sysfs_attr bno055_acc_range = { + .vals = bno055_acc_range_vals, + .len = ARRAY_SIZE(bno055_acc_range_vals), + .fusion_vals = (const int[]){3924}, /* 4G */ + .type = IIO_VAL_INT, +}; + +/* + * Theoretically the IMU should return data in a given (i.e. fixed) unit + * regardless of the range setting. This happens for the accelerometer, but not + * for the gyroscope; the gyroscope range setting affects the scale. + * This is probably due to this[0] bug. + * For this reason we map the internal range setting onto the standard IIO scale + * attribute for gyro. + * Since the bug[0] may be fixed in future, we check for the IMU FW version and + * eventually warn the user. + * Currently we just don't care about "range" attributes for gyro. + * + * [0] https://community.bosch-sensortec.com/t5/MEMS-sensors-forum/BNO055-Wrong-sensitivity-resolution-in-datasheet/td-p/10266 + */ + +/* + * dps = hwval * (dps_range/2^15) + * rps = hwval * (rps_range/2^15) + * = hwval * (dps_range/(2^15 * k)) + * where k is rad-to-deg factor + */ +static const int bno055_gyr_scale_vals[] = { + 125, 1877467, 250, 1877467, 500, 1877467, + 1000, 1877467, 2000, 1877467, +}; + +static const int bno055_gyr_scale_hw_xlate[] = {0, 1, 2, 3, 4}; +static const struct bno055_sysfs_attr bno055_gyr_scale = { + .vals = bno055_gyr_scale_vals, + .len = ARRAY_SIZE(bno055_gyr_scale_vals), + .fusion_vals = (const int[]){1, 900}, + .hw_xlate = bno055_gyr_scale_hw_xlate, + .hw_xlate_len = ARRAY_SIZE(bno055_gyr_scale_hw_xlate), + .type = IIO_VAL_FRACTIONAL, +}; + +static const int bno055_gyr_lpf_vals[] = {12, 23, 32, 47, 64, 116, 230, 523}; +static const int bno055_gyr_lpf_hw_xlate[] = {5, 4, 7, 3, 6, 2, 1, 0}; +static const struct bno055_sysfs_attr bno055_gyr_lpf = { + .vals = bno055_gyr_lpf_vals, + .len = ARRAY_SIZE(bno055_gyr_lpf_vals), + .fusion_vals = (const int[]){32}, + .hw_xlate = bno055_gyr_lpf_hw_xlate, + .hw_xlate_len = ARRAY_SIZE(bno055_gyr_lpf_hw_xlate), + .type = IIO_VAL_INT, +}; + +static const int bno055_mag_odr_vals[] = {2, 6, 8, 10, 15, 20, 25, 30}; +static const struct bno055_sysfs_attr bno055_mag_odr = { + .vals = bno055_mag_odr_vals, + .len = ARRAY_SIZE(bno055_mag_odr_vals), + .fusion_vals = (const int[]){20}, + .type = IIO_VAL_INT, +}; + +struct bno055_priv { + struct regmap *regmap; + struct device *dev; + struct clk *clk; + int operation_mode; + int xfer_burst_break_thr; + struct mutex lock; + u8 uid[BNO055_UID_LEN]; + struct gpio_desc *reset_gpio; + bool sw_reset; + union { + IIO_DECLARE_BUFFER_WITH_TS(__le16, chans, BNO055_SCAN_CH_COUNT); + /* + * This struct is not used, but it is here to ensure proper size + * and alignment of the scan buffer above (because of the extra + * requirements of the quaternion field). Technically it is not + * needed in this case, because other fields just happen to make + * things correctly aligned already. But it is better to be + * explicit about the requirements anyway. The actual contents + * of the scan buffer will vary depending on which channels are + * enabled. + */ + struct { + __le16 acc[3]; + __le16 magn[3]; + __le16 gyr[3]; + __le16 yaw; + __le16 pitch; + __le16 roll; + IIO_DECLARE_QUATERNION(__le16, quaternion); + __le16 lia[3]; + __le16 gravity[3]; + aligned_s64 timestamp; + }; + } buf; + struct dentry *debugfs; +}; + +static bool bno055_regmap_volatile(struct device *dev, unsigned int reg) +{ + /* data and status registers */ + if (reg >= BNO055_ACC_DATA_X_LSB_REG && reg <= BNO055_SYS_ERR_REG) + return true; + + /* when in fusion mode, config is updated by chip */ + if (reg == BNO055_MAG_CONFIG_REG || + reg == BNO055_ACC_CONFIG_REG || + reg == BNO055_GYR_CONFIG_REG) + return true; + + /* calibration data may be updated by the IMU */ + if (reg >= BNO055_CALDATA_START && reg <= BNO055_CALDATA_END) + return true; + + return false; +} + +static bool bno055_regmap_readable(struct device *dev, unsigned int reg) +{ + /* unnamed PG0 reserved areas */ + if ((reg < BNO055_PG1(0) && reg > BNO055_CALDATA_END) || + reg == 0x3C) + return false; + + /* unnamed PG1 reserved areas */ + if (reg > BNO055_PG1(BNO055_UID_HIGHER_REG) || + (reg < BNO055_PG1(BNO055_UID_LOWER_REG) && reg > BNO055_PG1(BNO055_GYR_AM_SET_REG)) || + reg == BNO055_PG1(0xE) || + (reg < BNO055_PG1(BNO055_PAGESEL_REG) && reg >= BNO055_PG1(0x0))) + return false; + return true; +} + +static bool bno055_regmap_writeable(struct device *dev, unsigned int reg) +{ + /* + * Unreadable registers are indeed reserved; there are no WO regs + * (except for a single bit in SYS_TRIGGER register) + */ + if (!bno055_regmap_readable(dev, reg)) + return false; + + /* data and status registers */ + if (reg >= BNO055_ACC_DATA_X_LSB_REG && reg <= BNO055_SYS_ERR_REG) + return false; + + /* ID areas */ + if (reg < BNO055_PAGESEL_REG || + (reg <= BNO055_UID_HIGHER_REG && reg >= BNO055_UID_LOWER_REG)) + return false; + + return true; +} + +static const struct regmap_range_cfg bno055_regmap_ranges[] = { + { + .range_min = 0, + .range_max = 0x7f * 2, + .selector_reg = BNO055_PAGESEL_REG, + .selector_mask = GENMASK(7, 0), + .selector_shift = 0, + .window_start = 0, + .window_len = 0x80, + }, +}; + +const struct regmap_config bno055_regmap_config = { + .name = "bno055", + .reg_bits = 8, + .val_bits = 8, + .ranges = bno055_regmap_ranges, + .num_ranges = 1, + .volatile_reg = bno055_regmap_volatile, + .max_register = 0x80 * 2, + .writeable_reg = bno055_regmap_writeable, + .readable_reg = bno055_regmap_readable, + .cache_type = REGCACHE_MAPLE, +}; +EXPORT_SYMBOL_NS_GPL(bno055_regmap_config, "IIO_BNO055"); + +/* must be called in configuration mode */ +static int bno055_calibration_load(struct bno055_priv *priv, const u8 *data, int len) +{ + if (len != BNO055_CALDATA_LEN) { + dev_dbg(priv->dev, "Invalid calibration file size %d (expected %d)", + len, BNO055_CALDATA_LEN); + return -EINVAL; + } + + dev_dbg(priv->dev, "loading cal data: %*ph", BNO055_CALDATA_LEN, data); + return regmap_bulk_write(priv->regmap, BNO055_CALDATA_START, + data, BNO055_CALDATA_LEN); +} + +static int bno055_operation_mode_do_set(struct bno055_priv *priv, + int operation_mode) +{ + int ret; + + ret = regmap_write(priv->regmap, BNO055_OPR_MODE_REG, + operation_mode); + if (ret) + return ret; + + /* Following datasheet specifications: sensor takes 7mS up to 19 mS to switch mode */ + msleep(20); + + return 0; +} + +static int bno055_system_reset(struct bno055_priv *priv) +{ + int ret; + + if (priv->reset_gpio) { + gpiod_set_value_cansleep(priv->reset_gpio, 0); + usleep_range(5000, 10000); + gpiod_set_value_cansleep(priv->reset_gpio, 1); + } else if (priv->sw_reset) { + ret = regmap_write(priv->regmap, BNO055_SYS_TRIGGER_REG, + BNO055_SYS_TRIGGER_RST_SYS); + if (ret) + return ret; + } else { + return 0; + } + + regcache_drop_region(priv->regmap, 0x0, 0xff); + usleep_range(650000, 700000); + + return 0; +} + +static int bno055_init(struct bno055_priv *priv, const u8 *caldata, int len) +{ + int ret; + + ret = bno055_operation_mode_do_set(priv, BNO055_OPR_MODE_CONFIG); + if (ret) + return ret; + + ret = regmap_write(priv->regmap, BNO055_POWER_MODE_REG, + BNO055_POWER_MODE_NORMAL); + if (ret) + return ret; + + ret = regmap_write(priv->regmap, BNO055_SYS_TRIGGER_REG, + priv->clk ? BNO055_SYS_TRIGGER_CLK_SEL : 0); + if (ret) + return ret; + + /* use standard SI units */ + ret = regmap_write(priv->regmap, BNO055_UNIT_SEL_REG, + BNO055_UNIT_SEL_ANDROID | BNO055_UNIT_SEL_GYR_RPS); + if (ret) + return ret; + + if (caldata) { + ret = bno055_calibration_load(priv, caldata, len); + if (ret) + dev_warn(priv->dev, "failed to load calibration data with error %d\n", + ret); + } + + return 0; +} + +static ssize_t bno055_operation_mode_set(struct bno055_priv *priv, + int operation_mode) +{ + u8 caldata[BNO055_CALDATA_LEN]; + int ret; + + mutex_lock(&priv->lock); + + ret = bno055_operation_mode_do_set(priv, BNO055_OPR_MODE_CONFIG); + if (ret) + goto exit_unlock; + + if (operation_mode == BNO055_OPR_MODE_FUSION || + operation_mode == BNO055_OPR_MODE_FUSION_FMC_OFF) { + /* for entering fusion mode, reset the chip to clear the algo state */ + ret = regmap_bulk_read(priv->regmap, BNO055_CALDATA_START, caldata, + BNO055_CALDATA_LEN); + if (ret) + goto exit_unlock; + + ret = bno055_system_reset(priv); + if (ret) + goto exit_unlock; + + ret = bno055_init(priv, caldata, BNO055_CALDATA_LEN); + if (ret) + goto exit_unlock; + } + + ret = bno055_operation_mode_do_set(priv, operation_mode); + if (ret) + goto exit_unlock; + + priv->operation_mode = operation_mode; + +exit_unlock: + mutex_unlock(&priv->lock); + return ret; +} + +static void bno055_uninit(void *arg) +{ + struct bno055_priv *priv = arg; + + /* stop the IMU */ + bno055_operation_mode_do_set(priv, BNO055_OPR_MODE_CONFIG); +} + +#define BNO055_CHANNEL(_type, _axis, _index, _address, _sep, _sh, _avail) { \ + .address = _address, \ + .type = _type, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | (_sep), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | (_sh), \ + .info_mask_shared_by_type_available = _avail, \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + .repeat = IIO_MOD_##_axis == IIO_MOD_QUATERNION ? 4 : 0, \ + }, \ +} + +/* scan indexes follow DATA register order */ +enum bno055_scan_axis { + BNO055_SCAN_ACCEL_X, + BNO055_SCAN_ACCEL_Y, + BNO055_SCAN_ACCEL_Z, + BNO055_SCAN_MAGN_X, + BNO055_SCAN_MAGN_Y, + BNO055_SCAN_MAGN_Z, + BNO055_SCAN_GYRO_X, + BNO055_SCAN_GYRO_Y, + BNO055_SCAN_GYRO_Z, + BNO055_SCAN_YAW, + BNO055_SCAN_ROLL, + BNO055_SCAN_PITCH, + BNO055_SCAN_QUATERNION, + BNO055_SCAN_LIA_X, + BNO055_SCAN_LIA_Y, + BNO055_SCAN_LIA_Z, + BNO055_SCAN_GRAVITY_X, + BNO055_SCAN_GRAVITY_Y, + BNO055_SCAN_GRAVITY_Z, + BNO055_SCAN_TIMESTAMP, + _BNO055_SCAN_MAX +}; + +static const struct iio_chan_spec bno055_channels[] = { + /* accelerometer */ + BNO055_CHANNEL(IIO_ACCEL, X, BNO055_SCAN_ACCEL_X, + BNO055_ACC_DATA_X_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY)), + BNO055_CHANNEL(IIO_ACCEL, Y, BNO055_SCAN_ACCEL_Y, + BNO055_ACC_DATA_Y_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY)), + BNO055_CHANNEL(IIO_ACCEL, Z, BNO055_SCAN_ACCEL_Z, + BNO055_ACC_DATA_Z_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY)), + /* gyroscope */ + BNO055_CHANNEL(IIO_ANGL_VEL, X, BNO055_SCAN_GYRO_X, + BNO055_GYR_DATA_X_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | + BIT(IIO_CHAN_INFO_SCALE)), + BNO055_CHANNEL(IIO_ANGL_VEL, Y, BNO055_SCAN_GYRO_Y, + BNO055_GYR_DATA_Y_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | + BIT(IIO_CHAN_INFO_SCALE)), + BNO055_CHANNEL(IIO_ANGL_VEL, Z, BNO055_SCAN_GYRO_Z, + BNO055_GYR_DATA_Z_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | + BIT(IIO_CHAN_INFO_SCALE)), + /* magnetometer */ + BNO055_CHANNEL(IIO_MAGN, X, BNO055_SCAN_MAGN_X, + BNO055_MAG_DATA_X_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), + BIT(IIO_CHAN_INFO_SAMP_FREQ), BIT(IIO_CHAN_INFO_SAMP_FREQ)), + BNO055_CHANNEL(IIO_MAGN, Y, BNO055_SCAN_MAGN_Y, + BNO055_MAG_DATA_Y_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), + BIT(IIO_CHAN_INFO_SAMP_FREQ), BIT(IIO_CHAN_INFO_SAMP_FREQ)), + BNO055_CHANNEL(IIO_MAGN, Z, BNO055_SCAN_MAGN_Z, + BNO055_MAG_DATA_Z_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), + BIT(IIO_CHAN_INFO_SAMP_FREQ), BIT(IIO_CHAN_INFO_SAMP_FREQ)), + /* euler angle */ + BNO055_CHANNEL(IIO_ROT, YAW, BNO055_SCAN_YAW, + BNO055_EUL_DATA_X_LSB_REG, 0, 0, 0), + BNO055_CHANNEL(IIO_ROT, ROLL, BNO055_SCAN_ROLL, + BNO055_EUL_DATA_Y_LSB_REG, 0, 0, 0), + BNO055_CHANNEL(IIO_ROT, PITCH, BNO055_SCAN_PITCH, + BNO055_EUL_DATA_Z_LSB_REG, 0, 0, 0), + /* quaternion */ + BNO055_CHANNEL(IIO_ROT, QUATERNION, BNO055_SCAN_QUATERNION, + BNO055_QUAT_DATA_W_LSB_REG, 0, 0, 0), + + /* linear acceleration */ + BNO055_CHANNEL(IIO_ACCEL, LINEAR_X, BNO055_SCAN_LIA_X, + BNO055_LIA_DATA_X_LSB_REG, 0, 0, 0), + BNO055_CHANNEL(IIO_ACCEL, LINEAR_Y, BNO055_SCAN_LIA_Y, + BNO055_LIA_DATA_Y_LSB_REG, 0, 0, 0), + BNO055_CHANNEL(IIO_ACCEL, LINEAR_Z, BNO055_SCAN_LIA_Z, + BNO055_LIA_DATA_Z_LSB_REG, 0, 0, 0), + + /* gravity vector */ + BNO055_CHANNEL(IIO_GRAVITY, X, BNO055_SCAN_GRAVITY_X, + BNO055_GRAVITY_DATA_X_LSB_REG, 0, 0, 0), + BNO055_CHANNEL(IIO_GRAVITY, Y, BNO055_SCAN_GRAVITY_Y, + BNO055_GRAVITY_DATA_Y_LSB_REG, 0, 0, 0), + BNO055_CHANNEL(IIO_GRAVITY, Z, BNO055_SCAN_GRAVITY_Z, + BNO055_GRAVITY_DATA_Z_LSB_REG, 0, 0, 0), + + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = -1, + }, + IIO_CHAN_SOFT_TIMESTAMP(BNO055_SCAN_TIMESTAMP), +}; + +static int bno055_get_regmask(struct bno055_priv *priv, int *val, int *val2, + int reg, int mask, + const struct bno055_sysfs_attr *attr) +{ + const int shift = __ffs(mask); + int hwval, idx; + int ret; + int i; + + ret = regmap_read(priv->regmap, reg, &hwval); + if (ret) + return ret; + + idx = (hwval & mask) >> shift; + if (attr->hw_xlate) + for (i = 0; i < attr->hw_xlate_len; i++) + if (attr->hw_xlate[i] == idx) { + idx = i; + break; + } + if (attr->type == IIO_VAL_INT) { + *val = attr->vals[idx]; + } else { /* IIO_VAL_INT_PLUS_MICRO or IIO_VAL_FRACTIONAL */ + *val = attr->vals[idx * 2]; + *val2 = attr->vals[idx * 2 + 1]; + } + + return attr->type; +} + +static int bno055_set_regmask(struct bno055_priv *priv, int val, int val2, + int reg, int mask, + const struct bno055_sysfs_attr *attr) +{ + const int shift = __ffs(mask); + int best_delta; + int req_val; + int tbl_val; + bool first; + int delta; + int hwval; + int ret; + int len; + int i; + + /* + * The closest value the HW supports is only one in fusion mode, + * and it is autoselected, so don't do anything, just return OK, + * as the closest possible value has been (virtually) selected + */ + if (priv->operation_mode != BNO055_OPR_MODE_AMG) + return 0; + + len = attr->len; + + /* + * We always get a request in INT_PLUS_MICRO, but we + * take care of the micro part only when we really have + * non-integer tables. This prevents 32-bit overflow with + * larger integers contained in integer tables. + */ + req_val = val; + if (attr->type != IIO_VAL_INT) { + len /= 2; + req_val = min(val, 2147) * 1000000 + val2; + } + + first = true; + for (i = 0; i < len; i++) { + switch (attr->type) { + case IIO_VAL_INT: + tbl_val = attr->vals[i]; + break; + case IIO_VAL_INT_PLUS_MICRO: + WARN_ON(attr->vals[i * 2] > 2147); + tbl_val = attr->vals[i * 2] * 1000000 + + attr->vals[i * 2 + 1]; + break; + case IIO_VAL_FRACTIONAL: + WARN_ON(attr->vals[i * 2] > 4294); + tbl_val = attr->vals[i * 2] * 1000000 / + attr->vals[i * 2 + 1]; + break; + default: + return -EINVAL; + } + delta = abs(tbl_val - req_val); + if (first || delta < best_delta) { + best_delta = delta; + hwval = i; + first = false; + } + } + + if (attr->hw_xlate) + hwval = attr->hw_xlate[hwval]; + + ret = bno055_operation_mode_do_set(priv, BNO055_OPR_MODE_CONFIG); + if (ret) + return ret; + + ret = regmap_update_bits(priv->regmap, reg, mask, hwval << shift); + if (ret) + return ret; + + return bno055_operation_mode_do_set(priv, BNO055_OPR_MODE_AMG); +} + +static int bno055_read_simple_chan(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bno055_priv *priv = iio_priv(indio_dev); + __le16 raw_val; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_bulk_read(priv->regmap, chan->address, + &raw_val, sizeof(raw_val)); + if (ret < 0) + return ret; + *val = sign_extend32(le16_to_cpu(raw_val), 15); + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + if (priv->operation_mode != BNO055_OPR_MODE_AMG) { + *val = 0; + } else { + ret = regmap_bulk_read(priv->regmap, + chan->address + + BNO055_REG_OFFSET_ADDR, + &raw_val, sizeof(raw_val)); + if (ret < 0) + return ret; + /* + * IMU reports sensor offsets; IIO wants correction + * offsets, thus we need the 'minus' here. + */ + *val = -sign_extend32(le16_to_cpu(raw_val), 15); + } + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 1; + switch (chan->type) { + case IIO_GRAVITY: + /* Table 3-35: 1 m/s^2 = 100 LSB */ + case IIO_ACCEL: + /* Table 3-17: 1 m/s^2 = 100 LSB */ + *val2 = 100; + break; + case IIO_MAGN: + /* + * Table 3-19: 1 uT = 16 LSB. But we need + * Gauss: 1G = 0.1 uT. + */ + *val2 = 160; + break; + case IIO_ANGL_VEL: + /* + * Table 3-22: 1 Rps = 900 LSB + * .. but this is not exactly true. See comment at the + * beginning of this file. + */ + if (priv->operation_mode != BNO055_OPR_MODE_AMG) { + *val = bno055_gyr_scale.fusion_vals[0]; + *val2 = bno055_gyr_scale.fusion_vals[1]; + return IIO_VAL_FRACTIONAL; + } + + return bno055_get_regmask(priv, val, val2, + BNO055_GYR_CONFIG_REG, + BNO055_GYR_CONFIG_RANGE_MASK, + &bno055_gyr_scale); + break; + case IIO_ROT: + /* Table 3-28: 1 degree = 16 LSB */ + *val2 = 16; + break; + default: + return -EINVAL; + } + return IIO_VAL_FRACTIONAL; + + case IIO_CHAN_INFO_SAMP_FREQ: + if (chan->type != IIO_MAGN) + return -EINVAL; + + return bno055_get_regmask(priv, val, val2, + BNO055_MAG_CONFIG_REG, + BNO055_MAG_CONFIG_ODR_MASK, + &bno055_mag_odr); + + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + switch (chan->type) { + case IIO_ANGL_VEL: + return bno055_get_regmask(priv, val, val2, + BNO055_GYR_CONFIG_REG, + BNO055_GYR_CONFIG_LPF_MASK, + &bno055_gyr_lpf); + case IIO_ACCEL: + return bno055_get_regmask(priv, val, val2, + BNO055_ACC_CONFIG_REG, + BNO055_ACC_CONFIG_LPF_MASK, + &bno055_acc_lpf); + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +static int bno055_sysfs_attr_avail(struct bno055_priv *priv, + const struct bno055_sysfs_attr *attr, + const int **vals, int *length) +{ + if (priv->operation_mode != BNO055_OPR_MODE_AMG) { + /* locked when fusion enabled */ + *vals = attr->fusion_vals; + if (attr->type == IIO_VAL_INT) + *length = 1; + else + *length = 2; /* IIO_VAL_INT_PLUS_MICRO or IIO_VAL_FRACTIONAL*/ + } else { + *vals = attr->vals; + *length = attr->len; + } + + return attr->type; +} + +static int bno055_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct bno055_priv *priv = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *type = bno055_sysfs_attr_avail(priv, &bno055_gyr_scale, + vals, length); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + switch (chan->type) { + case IIO_ANGL_VEL: + *type = bno055_sysfs_attr_avail(priv, &bno055_gyr_lpf, + vals, length); + return IIO_AVAIL_LIST; + case IIO_ACCEL: + *type = bno055_sysfs_attr_avail(priv, &bno055_acc_lpf, + vals, length); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + + break; + case IIO_CHAN_INFO_SAMP_FREQ: + switch (chan->type) { + case IIO_MAGN: + *type = bno055_sysfs_attr_avail(priv, &bno055_mag_odr, + vals, length); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int bno055_read_temp_chan(struct iio_dev *indio_dev, int *val) +{ + struct bno055_priv *priv = iio_priv(indio_dev); + unsigned int raw_val; + int ret; + + ret = regmap_read(priv->regmap, BNO055_TEMP_REG, &raw_val); + if (ret < 0) + return ret; + + /* + * Tables 3-36 and 3-37: one byte of priv, signed, 1 LSB = 1C. + * ABI wants milliC. + */ + *val = raw_val * 1000; + + return IIO_VAL_INT; +} + +static int bno055_read_quaternion(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int size, int *vals, int *val_len, + long mask) +{ + struct bno055_priv *priv = iio_priv(indio_dev); + __le16 raw_vals[4]; + int i, ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (size < 4) + return -EINVAL; + ret = regmap_bulk_read(priv->regmap, + BNO055_QUAT_DATA_W_LSB_REG, + raw_vals, sizeof(raw_vals)); + if (ret < 0) + return ret; + for (i = 0; i < 4; i++) + vals[i] = sign_extend32(le16_to_cpu(raw_vals[i]), 15); + *val_len = 4; + return IIO_VAL_INT_MULTIPLE; + case IIO_CHAN_INFO_SCALE: + /* Table 3-31: 1 quaternion = 2^14 LSB */ + if (size < 2) + return -EINVAL; + vals[0] = 1; + vals[1] = 14; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static bool bno055_is_chan_readable(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + struct bno055_priv *priv = iio_priv(indio_dev); + + if (priv->operation_mode != BNO055_OPR_MODE_AMG) + return true; + + switch (chan->type) { + case IIO_GRAVITY: + case IIO_ROT: + return false; + case IIO_ACCEL: + if (chan->channel2 == IIO_MOD_LINEAR_X || + chan->channel2 == IIO_MOD_LINEAR_Y || + chan->channel2 == IIO_MOD_LINEAR_Z) + return false; + return true; + default: + return true; + } +} + +static int _bno055_read_raw_multi(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int size, int *vals, int *val_len, + long mask) +{ + if (!bno055_is_chan_readable(indio_dev, chan)) + return -EBUSY; + + switch (chan->type) { + case IIO_MAGN: + case IIO_ACCEL: + case IIO_ANGL_VEL: + case IIO_GRAVITY: + if (size < 2) + return -EINVAL; + *val_len = 2; + return bno055_read_simple_chan(indio_dev, chan, + &vals[0], &vals[1], + mask); + case IIO_TEMP: + *val_len = 1; + return bno055_read_temp_chan(indio_dev, &vals[0]); + case IIO_ROT: + /* + * Rotation is exposed as either a quaternion or three + * Euler angles. + */ + if (chan->channel2 == IIO_MOD_QUATERNION) + return bno055_read_quaternion(indio_dev, chan, + size, vals, + val_len, mask); + if (size < 2) + return -EINVAL; + *val_len = 2; + return bno055_read_simple_chan(indio_dev, chan, + &vals[0], &vals[1], + mask); + default: + return -EINVAL; + } +} + +static int bno055_read_raw_multi(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int size, int *vals, int *val_len, + long mask) +{ + struct bno055_priv *priv = iio_priv(indio_dev); + int ret; + + mutex_lock(&priv->lock); + ret = _bno055_read_raw_multi(indio_dev, chan, size, + vals, val_len, mask); + mutex_unlock(&priv->lock); + return ret; +} + +static int _bno055_write_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bno055_priv *priv = iio_priv(iio_dev); + + switch (chan->type) { + case IIO_MAGN: + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return bno055_set_regmask(priv, val, val2, + BNO055_MAG_CONFIG_REG, + BNO055_MAG_CONFIG_ODR_MASK, + &bno055_mag_odr); + default: + return -EINVAL; + } + case IIO_ACCEL: + switch (mask) { + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return bno055_set_regmask(priv, val, val2, + BNO055_ACC_CONFIG_REG, + BNO055_ACC_CONFIG_LPF_MASK, + &bno055_acc_lpf); + + default: + return -EINVAL; + } + case IIO_ANGL_VEL: + switch (mask) { + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return bno055_set_regmask(priv, val, val2, + BNO055_GYR_CONFIG_REG, + BNO055_GYR_CONFIG_LPF_MASK, + &bno055_gyr_lpf); + case IIO_CHAN_INFO_SCALE: + return bno055_set_regmask(priv, val, val2, + BNO055_GYR_CONFIG_REG, + BNO055_GYR_CONFIG_RANGE_MASK, + &bno055_gyr_scale); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int bno055_write_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bno055_priv *priv = iio_priv(iio_dev); + int ret; + + mutex_lock(&priv->lock); + ret = _bno055_write_raw(iio_dev, chan, val, val2, mask); + mutex_unlock(&priv->lock); + + return ret; +} + +static ssize_t in_accel_range_raw_available_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); + int len = 0; + int i; + + if (priv->operation_mode != BNO055_OPR_MODE_AMG) + return sysfs_emit(buf, "%d\n", bno055_acc_range.fusion_vals[0]); + + for (i = 0; i < bno055_acc_range.len; i++) + len += sysfs_emit_at(buf, len, "%d ", bno055_acc_range.vals[i]); + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t fusion_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); + + return sysfs_emit(buf, "%d\n", + priv->operation_mode != BNO055_OPR_MODE_AMG); +} + +static ssize_t fusion_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct bno055_priv *priv = iio_priv(indio_dev); + bool en; + int ret; + + if (indio_dev->active_scan_mask && + !bitmap_empty(indio_dev->active_scan_mask, _BNO055_SCAN_MAX)) + return -EBUSY; + + ret = kstrtobool(buf, &en); + if (ret) + return -EINVAL; + + if (!en) + return bno055_operation_mode_set(priv, BNO055_OPR_MODE_AMG) ?: len; + + /* + * Coming from AMG means the FMC was off, just switch to fusion but + * don't change anything that doesn't belong to us (i.e let FMC stay off). + * Coming from any other fusion mode means we don't need to do anything. + */ + if (priv->operation_mode == BNO055_OPR_MODE_AMG) + return bno055_operation_mode_set(priv, BNO055_OPR_MODE_FUSION_FMC_OFF) ?: len; + + return len; +} + +static ssize_t in_magn_calibration_fast_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); + + return sysfs_emit(buf, "%d\n", + priv->operation_mode == BNO055_OPR_MODE_FUSION); +} + +static ssize_t in_magn_calibration_fast_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct bno055_priv *priv = iio_priv(indio_dev); + int ret; + + if (indio_dev->active_scan_mask && + !bitmap_empty(indio_dev->active_scan_mask, _BNO055_SCAN_MAX)) + return -EBUSY; + + if (sysfs_streq(buf, "0")) { + if (priv->operation_mode == BNO055_OPR_MODE_FUSION) { + ret = bno055_operation_mode_set(priv, BNO055_OPR_MODE_FUSION_FMC_OFF); + if (ret) + return ret; + } + } else { + if (priv->operation_mode == BNO055_OPR_MODE_AMG) + return -EINVAL; + + if (priv->operation_mode != BNO055_OPR_MODE_FUSION) { + ret = bno055_operation_mode_set(priv, BNO055_OPR_MODE_FUSION); + if (ret) + return ret; + } + } + + return len; +} + +static ssize_t in_accel_range_raw_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); + int val; + int ret; + + ret = bno055_get_regmask(priv, &val, NULL, + BNO055_ACC_CONFIG_REG, + BNO055_ACC_CONFIG_RANGE_MASK, + &bno055_acc_range); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%d\n", val); +} + +static ssize_t in_accel_range_raw_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&priv->lock); + ret = bno055_set_regmask(priv, val, 0, + BNO055_ACC_CONFIG_REG, + BNO055_ACC_CONFIG_RANGE_MASK, + &bno055_acc_range); + mutex_unlock(&priv->lock); + + return ret ?: len; +} + +static ssize_t bno055_get_calib_status(struct device *dev, char *buf, int which) +{ + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); + int calib; + int ret; + int val; + + if (priv->operation_mode == BNO055_OPR_MODE_AMG || + (priv->operation_mode == BNO055_OPR_MODE_FUSION_FMC_OFF && + which == BNO055_CALIB_STAT_MAGN_SHIFT)) { + calib = 0; + } else { + mutex_lock(&priv->lock); + ret = regmap_read(priv->regmap, BNO055_CALIB_STAT_REG, &val); + mutex_unlock(&priv->lock); + + if (ret) + return -EIO; + + calib = ((val >> which) & GENMASK(1, 0)) + 1; + } + + return sysfs_emit(buf, "%d\n", calib); +} + +static ssize_t serialnumber_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); + + return sysfs_emit(buf, "%*ph\n", BNO055_UID_LEN, priv->uid); +} + +static ssize_t calibration_data_read(struct file *filp, struct kobject *kobj, + const struct bin_attribute *bin_attr, char *buf, + loff_t pos, size_t count) +{ + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(kobj_to_dev(kobj))); + u8 data[BNO055_CALDATA_LEN]; + int ret; + + /* + * Calibration data is volatile; reading it in chunks will possibly + * results in inconsistent data. We require the user to read the whole + * blob in a single chunk + */ + if (count < BNO055_CALDATA_LEN || pos) + return -EINVAL; + + mutex_lock(&priv->lock); + ret = bno055_operation_mode_do_set(priv, BNO055_OPR_MODE_CONFIG); + if (ret) + goto exit_unlock; + + ret = regmap_bulk_read(priv->regmap, BNO055_CALDATA_START, data, + BNO055_CALDATA_LEN); + if (ret) + goto exit_unlock; + + ret = bno055_operation_mode_do_set(priv, priv->operation_mode); + if (ret) + goto exit_unlock; + + memcpy(buf, data, BNO055_CALDATA_LEN); + + ret = BNO055_CALDATA_LEN; +exit_unlock: + mutex_unlock(&priv->lock); + return ret; +} + +static ssize_t sys_calibration_auto_status_show(struct device *dev, + struct device_attribute *a, + char *buf) +{ + return bno055_get_calib_status(dev, buf, BNO055_CALIB_STAT_SYS_SHIFT); +} + +static ssize_t in_accel_calibration_auto_status_show(struct device *dev, + struct device_attribute *a, + char *buf) +{ + return bno055_get_calib_status(dev, buf, BNO055_CALIB_STAT_ACCEL_SHIFT); +} + +static ssize_t in_gyro_calibration_auto_status_show(struct device *dev, + struct device_attribute *a, + char *buf) +{ + return bno055_get_calib_status(dev, buf, BNO055_CALIB_STAT_GYRO_SHIFT); +} + +static ssize_t in_magn_calibration_auto_status_show(struct device *dev, + struct device_attribute *a, + char *buf) +{ + return bno055_get_calib_status(dev, buf, BNO055_CALIB_STAT_MAGN_SHIFT); +} + +static int bno055_debugfs_reg_access(struct iio_dev *iio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct bno055_priv *priv = iio_priv(iio_dev); + + if (readval) + return regmap_read(priv->regmap, reg, readval); + else + return regmap_write(priv->regmap, reg, writeval); +} + +static ssize_t bno055_show_fw_version(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct bno055_priv *priv = file->private_data; + int rev, ver; + char *buf; + int ret; + + ret = regmap_read(priv->regmap, BNO055_SW_REV_LSB_REG, &rev); + if (ret) + return ret; + + ret = regmap_read(priv->regmap, BNO055_SW_REV_MSB_REG, &ver); + if (ret) + return ret; + + buf = kasprintf(GFP_KERNEL, "ver: 0x%x, rev: 0x%x\n", ver, rev); + if (!buf) + return -ENOMEM; + + ret = simple_read_from_buffer(userbuf, count, ppos, buf, strlen(buf)); + kfree(buf); + + return ret; +} + +static const struct file_operations bno055_fw_version_ops = { + .open = simple_open, + .read = bno055_show_fw_version, + .llseek = default_llseek, + .owner = THIS_MODULE, +}; + +static void bno055_debugfs_remove(void *_priv) +{ + struct bno055_priv *priv = _priv; + + debugfs_remove(priv->debugfs); + priv->debugfs = NULL; +} + +static void bno055_debugfs_init(struct iio_dev *iio_dev) +{ + struct bno055_priv *priv = iio_priv(iio_dev); + + priv->debugfs = debugfs_create_file("firmware_version", 0400, + iio_get_debugfs_dentry(iio_dev), + priv, &bno055_fw_version_ops); + if (!IS_ERR(priv->debugfs)) + devm_add_action_or_reset(priv->dev, bno055_debugfs_remove, + priv); + if (IS_ERR_OR_NULL(priv->debugfs)) + dev_warn(priv->dev, "failed to setup debugfs"); +} + +static IIO_DEVICE_ATTR_RW(fusion_enable, 0); +static IIO_DEVICE_ATTR_RW(in_magn_calibration_fast_enable, 0); +static IIO_DEVICE_ATTR_RW(in_accel_range_raw, 0); + +static IIO_DEVICE_ATTR_RO(in_accel_range_raw_available, 0); +static IIO_DEVICE_ATTR_RO(sys_calibration_auto_status, 0); +static IIO_DEVICE_ATTR_RO(in_accel_calibration_auto_status, 0); +static IIO_DEVICE_ATTR_RO(in_gyro_calibration_auto_status, 0); +static IIO_DEVICE_ATTR_RO(in_magn_calibration_auto_status, 0); +static IIO_DEVICE_ATTR_RO(serialnumber, 0); + +static struct attribute *bno055_attrs[] = { + &iio_dev_attr_in_accel_range_raw_available.dev_attr.attr, + &iio_dev_attr_in_accel_range_raw.dev_attr.attr, + &iio_dev_attr_fusion_enable.dev_attr.attr, + &iio_dev_attr_in_magn_calibration_fast_enable.dev_attr.attr, + &iio_dev_attr_sys_calibration_auto_status.dev_attr.attr, + &iio_dev_attr_in_accel_calibration_auto_status.dev_attr.attr, + &iio_dev_attr_in_gyro_calibration_auto_status.dev_attr.attr, + &iio_dev_attr_in_magn_calibration_auto_status.dev_attr.attr, + &iio_dev_attr_serialnumber.dev_attr.attr, + NULL +}; + +static const BIN_ATTR_RO(calibration_data, BNO055_CALDATA_LEN); + +static const struct bin_attribute *const bno055_bin_attrs[] = { + &bin_attr_calibration_data, + NULL +}; + +static const struct attribute_group bno055_attrs_group = { + .attrs = bno055_attrs, + .bin_attrs = bno055_bin_attrs, +}; + +static const struct iio_info bno055_info = { + .read_raw_multi = bno055_read_raw_multi, + .read_avail = bno055_read_avail, + .write_raw = bno055_write_raw, + .attrs = &bno055_attrs_group, + .debugfs_reg_access = bno055_debugfs_reg_access, +}; + +/* + * Reads len samples from the HW, stores them in buf starting from buf_idx, + * and applies mask to cull (skip) unneeded samples. + * Updates buf_idx incrementing with the number of stored samples. + * Samples from HW are transferred into buf, then in-place copy on buf is + * performed in order to cull samples that need to be skipped. + * This avoids copies of the first samples until we hit the 1st sample to skip, + * and also avoids having an extra bounce buffer. + * buf must be able to contain len elements in spite of how many samples we are + * going to cull. + */ +static int bno055_scan_xfer(struct bno055_priv *priv, + int start_ch, int len, unsigned long mask, + __le16 *buf, int *buf_idx) +{ + const int base = BNO055_ACC_DATA_X_LSB_REG; + bool quat_in_read = false; + int buf_base = *buf_idx; + __le16 *dst, *src; + int offs_fixup = 0; + int xfer_len = len; + int ret; + int i, n; + + if (!mask) + return 0; + + /* + * All channels are made up 1 16-bit sample, except for quaternion that + * is made up 4 16-bit values. + * For us the quaternion CH is just like 4 regular CHs. + * If our read starts past the quaternion make sure to adjust the + * starting offset; if the quaternion is contained in our scan then make + * sure to adjust the read len. + */ + if (start_ch > BNO055_SCAN_QUATERNION) { + start_ch += 3; + } else if ((start_ch <= BNO055_SCAN_QUATERNION) && + ((start_ch + len) > BNO055_SCAN_QUATERNION)) { + quat_in_read = true; + xfer_len += 3; + } + + ret = regmap_bulk_read(priv->regmap, + base + start_ch * sizeof(__le16), + buf + buf_base, + xfer_len * sizeof(__le16)); + if (ret) + return ret; + + for_each_set_bit(i, &mask, len) { + if (quat_in_read && ((start_ch + i) > BNO055_SCAN_QUATERNION)) + offs_fixup = 3; + + dst = buf + *buf_idx; + src = buf + buf_base + offs_fixup + i; + + n = (start_ch + i == BNO055_SCAN_QUATERNION) ? 4 : 1; + + if (dst != src) + memcpy(dst, src, n * sizeof(__le16)); + + *buf_idx += n; + } + return 0; +} + +static irqreturn_t bno055_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *iio_dev = pf->indio_dev; + struct bno055_priv *priv = iio_priv(iio_dev); + int xfer_start, start, end, prev_end; + unsigned long mask; + int quat_extra_len; + bool first = true; + int buf_idx = 0; + bool thr_hit; + int ret; + + mutex_lock(&priv->lock); + + /* + * Walk the bitmap and eventually perform several transfers. + * Bitmap ones-fields that are separated by gaps <= xfer_burst_break_thr + * will be included in same transfer. + * Every time the bitmap contains a gap wider than xfer_burst_break_thr + * then we split the transfer, skipping the gap. + */ + for_each_set_bitrange(start, end, iio_dev->active_scan_mask, + iio_get_masklength(iio_dev)) { + /* + * First transfer will start from the beginning of the first + * ones-field in the bitmap + */ + if (first) { + xfer_start = start; + } else { + /* + * We found the next ones-field; check whether to + * include it in * the current transfer or not (i.e. + * let's perform the current * transfer and prepare for + * another one). + */ + + /* + * In case the zeros-gap contains the quaternion bit, + * then its length is actually 4 words instead of 1 + * (i.e. +3 wrt other channels). + */ + quat_extra_len = ((start > BNO055_SCAN_QUATERNION) && + (prev_end <= BNO055_SCAN_QUATERNION)) ? 3 : 0; + + /* If the gap is wider than xfer_burst_break_thr then.. */ + thr_hit = (start - prev_end + quat_extra_len) > + priv->xfer_burst_break_thr; + + /* + * .. transfer all the data up to the gap. Then set the + * next transfer start index at right after the gap + * (i.e. at the start of this ones-field). + */ + if (thr_hit) { + mask = *iio_dev->active_scan_mask >> xfer_start; + ret = bno055_scan_xfer(priv, xfer_start, + prev_end - xfer_start, + mask, priv->buf.chans, &buf_idx); + if (ret) + goto done; + xfer_start = start; + } + } + first = false; + prev_end = end; + } + + /* + * We finished walking the bitmap; no more gaps to check for. Just + * perform the current transfer. + */ + mask = *iio_dev->active_scan_mask >> xfer_start; + ret = bno055_scan_xfer(priv, xfer_start, + prev_end - xfer_start, + mask, priv->buf.chans, &buf_idx); + + if (!ret) + iio_push_to_buffers_with_timestamp(iio_dev, + &priv->buf, pf->timestamp); +done: + mutex_unlock(&priv->lock); + iio_trigger_notify_done(iio_dev->trig); + return IRQ_HANDLED; +} + +static int bno055_buffer_preenable(struct iio_dev *indio_dev) +{ + struct bno055_priv *priv = iio_priv(indio_dev); + const unsigned long fusion_mask = + BIT(BNO055_SCAN_YAW) | + BIT(BNO055_SCAN_ROLL) | + BIT(BNO055_SCAN_PITCH) | + BIT(BNO055_SCAN_QUATERNION) | + BIT(BNO055_SCAN_LIA_X) | + BIT(BNO055_SCAN_LIA_Y) | + BIT(BNO055_SCAN_LIA_Z) | + BIT(BNO055_SCAN_GRAVITY_X) | + BIT(BNO055_SCAN_GRAVITY_Y) | + BIT(BNO055_SCAN_GRAVITY_Z); + + if (priv->operation_mode == BNO055_OPR_MODE_AMG && + bitmap_intersects(indio_dev->active_scan_mask, &fusion_mask, + _BNO055_SCAN_MAX)) + return -EBUSY; + return 0; +} + +static const struct iio_buffer_setup_ops bno055_buffer_setup_ops = { + .preenable = bno055_buffer_preenable, +}; + +int bno055_probe(struct device *dev, struct regmap *regmap, + int xfer_burst_break_thr, bool sw_reset) +{ + const struct firmware *caldata = NULL; + struct bno055_priv *priv; + struct iio_dev *iio_dev; + char *fw_name_buf; + unsigned int val; + int rev, ver; + int ret; + + iio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); + if (!iio_dev) + return -ENOMEM; + + iio_dev->name = "bno055"; + priv = iio_priv(iio_dev); + mutex_init(&priv->lock); + priv->regmap = regmap; + priv->dev = dev; + priv->xfer_burst_break_thr = xfer_burst_break_thr; + priv->sw_reset = sw_reset; + + priv->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(priv->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(priv->reset_gpio), "Failed to get reset GPIO\n"); + + priv->clk = devm_clk_get_optional_enabled(dev, "clk"); + if (IS_ERR(priv->clk)) + return dev_err_probe(dev, PTR_ERR(priv->clk), "Failed to get CLK\n"); + + if (priv->reset_gpio) { + usleep_range(5000, 10000); + gpiod_set_value_cansleep(priv->reset_gpio, 1); + usleep_range(650000, 750000); + } else if (!sw_reset) { + dev_warn(dev, "No usable reset method; IMU may be unreliable\n"); + } + + ret = regmap_read(priv->regmap, BNO055_CHIP_ID_REG, &val); + if (ret) + return ret; + + if (val != BNO055_CHIP_ID_MAGIC) + dev_warn(dev, "Unrecognized chip ID 0x%x\n", val); + + /* + * In case we haven't a HW reset pin, we can still reset the chip via + * register write. This is probably nonsense in case we can't even + * communicate with the chip or the chip isn't the one we expect (i.e. + * we don't write to unknown chips), so we perform SW reset only after + * chip magic ID check + */ + if (!priv->reset_gpio) { + ret = bno055_system_reset(priv); + if (ret) + return ret; + } + + ret = regmap_read(priv->regmap, BNO055_SW_REV_LSB_REG, &rev); + if (ret) + return ret; + + ret = regmap_read(priv->regmap, BNO055_SW_REV_MSB_REG, &ver); + if (ret) + return ret; + + /* + * The stock FW version contains a bug (see comment at the beginning of + * this file) that causes the anglvel scale to be changed depending on + * the chip range setting. We workaround this, but we don't know what + * other FW versions might do. + */ + if (ver != 0x3 || rev != 0x11) + dev_warn(dev, "Untested firmware version. Anglvel scale may not work as expected\n"); + + ret = regmap_bulk_read(priv->regmap, BNO055_UID_LOWER_REG, + priv->uid, BNO055_UID_LEN); + if (ret) + return ret; + + /* Sensor calibration data */ + fw_name_buf = kasprintf(GFP_KERNEL, BNO055_FW_UID_FMT, + BNO055_UID_LEN, priv->uid); + if (!fw_name_buf) + return -ENOMEM; + + ret = request_firmware(&caldata, fw_name_buf, dev); + kfree(fw_name_buf); + if (ret) + ret = request_firmware(&caldata, BNO055_FW_GENERIC_NAME, dev); + if (ret) { + dev_notice(dev, "Calibration file load failed. See instruction in kernel Documentation/iio/bno055.rst\n"); + ret = bno055_init(priv, NULL, 0); + } else { + ret = bno055_init(priv, caldata->data, caldata->size); + release_firmware(caldata); + } + if (ret) + return ret; + + priv->operation_mode = BNO055_OPR_MODE_FUSION; + ret = bno055_operation_mode_do_set(priv, priv->operation_mode); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, bno055_uninit, priv); + if (ret) + return ret; + + iio_dev->channels = bno055_channels; + iio_dev->num_channels = ARRAY_SIZE(bno055_channels); + iio_dev->info = &bno055_info; + iio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_iio_triggered_buffer_setup(dev, iio_dev, + iio_pollfunc_store_time, + bno055_trigger_handler, + &bno055_buffer_setup_ops); + if (ret) + return ret; + + ret = devm_iio_device_register(dev, iio_dev); + if (ret) + return ret; + + bno055_debugfs_init(iio_dev); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(bno055_probe, "IIO_BNO055"); + +MODULE_AUTHOR("Andrea Merello "); +MODULE_DESCRIPTION("Bosch BNO055 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/imu/bno055/bno055.h b/drivers/iio/imu/bno055/bno055.h new file mode 100644 index 000000000..64f9fc95c --- /dev/null +++ b/drivers/iio/imu/bno055/bno055.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __BNO055_H__ +#define __BNO055_H__ + +#include +#include + +struct device; +int bno055_probe(struct device *dev, struct regmap *regmap, + int xfer_burst_break_thr, bool sw_reset); +extern const struct regmap_config bno055_regmap_config; + +#endif diff --git a/drivers/iio/imu/bno055/bno055_i2c.c b/drivers/iio/imu/bno055/bno055_i2c.c new file mode 100644 index 000000000..7117ec682 --- /dev/null +++ b/drivers/iio/imu/bno055/bno055_i2c.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Support for I2C-interfaced Bosch BNO055 IMU. + * + * Copyright (C) 2021-2022 Istituto Italiano di Tecnologia + * Electronic Design Laboratory + * Written by Andrea Merello + */ + +#include +#include +#include + +#include "bno055.h" + +#define BNO055_I2C_XFER_BURST_BREAK_THRESHOLD 3 + +static int bno055_i2c_probe(struct i2c_client *client) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, &bno055_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(&client->dev, PTR_ERR(regmap), + "Unable to init register map"); + + return bno055_probe(&client->dev, regmap, + BNO055_I2C_XFER_BURST_BREAK_THRESHOLD, true); +} + +static const struct i2c_device_id bno055_i2c_id[] = { + { .name = "bno055" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bno055_i2c_id); + +static const struct of_device_id __maybe_unused bno055_i2c_of_match[] = { + { .compatible = "bosch,bno055" }, + { } +}; +MODULE_DEVICE_TABLE(of, bno055_i2c_of_match); + +static struct i2c_driver bno055_driver = { + .driver = { + .name = "bno055-i2c", + .of_match_table = bno055_i2c_of_match, + }, + .probe = bno055_i2c_probe, + .id_table = bno055_i2c_id, +}; +module_i2c_driver(bno055_driver); + +MODULE_AUTHOR("Andrea Merello"); +MODULE_DESCRIPTION("Bosch BNO055 I2C interface"); +MODULE_IMPORT_NS("IIO_BNO055"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/imu/bno055/bno055_ser_core.c b/drivers/iio/imu/bno055/bno055_ser_core.c new file mode 100644 index 000000000..01f05feaa --- /dev/null +++ b/drivers/iio/imu/bno055/bno055_ser_core.c @@ -0,0 +1,559 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Serial line interface for Bosh BNO055 IMU (via serdev). + * This file implements serial communication up to the register read/write + * level. + * + * Copyright (C) 2021-2022 Istituto Italiano di Tecnologia + * Electronic Design Laboratory + * Written by Andrea Merello + * + * This driver is based on + * Plantower PMS7003 particulate matter sensor driver + * Which is + * Copyright (c) Tomasz Duszynski + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bno055_ser_trace.h" +#include "bno055.h" + +/* + * Register writes cmd have the following format + * +------+------+-----+-----+----- ... ----+ + * | 0xAA | 0xOO | REG | LEN | payload[LEN] | + * +------+------+-----+-----+----- ... ----+ + * + * Register write responses have the following format + * +------+----------+ + * | 0xEE | ERROCODE | + * +------+----------+ + * + * .. except when writing the SYS_RST bit (i.e. triggering a system reset); in + * case the IMU accepts the command, then it resets without responding. We don't + * handle this (yet) here (so we inform the common bno055 code not to perform + * sw resets - bno055 on serial bus basically requires the hw reset pin). + * + * Register read have the following format + * +------+------+-----+-----+ + * | 0xAA | 0xO1 | REG | LEN | + * +------+------+-----+-----+ + * + * Successful register read response have the following format + * +------+-----+----- ... ----+ + * | 0xBB | LEN | payload[LEN] | + * +------+-----+----- ... ----+ + * + * Failed register read response have the following format + * +------+--------+ + * | 0xEE | ERRCODE| (ERRCODE always > 1) + * +------+--------+ + * + * Error codes are + * 01: OK + * 02: read/write FAIL + * 04: invalid address + * 05: write on RO + * 06: wrong start byte + * 07: bus overrun + * 08: len too high + * 09: len too low + * 10: bus RX byte timeout (timeout is 30mS) + * + * + * **WORKAROUND ALERT** + * + * Serial communication seems very fragile: the BNO055 buffer seems to overflow + * very easy; BNO055 seems able to sink few bytes, then it needs a brief pause. + * On the other hand, it is also picky on timeout: if there is a pause > 30mS in + * between two bytes then the transaction fails (IMU internal RX FSM resets). + * + * BNO055 has been seen also failing to process commands in case we send them + * too close each other (or if it is somehow busy?) + * + * In particular I saw these scenarios: + * 1) If we send 2 bytes per time, then the IMU never(?) overflows. + * 2) If we send 4 bytes per time (i.e. the full header), then the IMU could + * overflow, but it seem to sink all 4 bytes, then it returns error. + * 3) If we send more than 4 bytes, the IMU could overflow, and I saw it sending + * error after 4 bytes are sent; we have troubles in synchronizing again, + * because we are still sending data, and the IMU interprets it as the 1st + * byte of a new command. + * + * While we must avoid case 3, we could send 4 bytes per time and eventually + * retry in case of failure; this seemed convenient for reads (which requires + * TXing exactly 4 bytes), however it has been seen that, depending by the IMU + * settings (e.g. LPF), failures became less or more frequent; in certain IMU + * configurations they are very rare, but in certain others we keeps failing + * even after like 30 retries. + * + * So, we just split TXes in [2-bytes + delay] steps, and still keep an eye on + * the IMU response; in case it overflows (which is now unlikely), we retry. + */ + +/* + * Read operation overhead: + * 4 bytes req + 2byte resp hdr. + * 6 bytes = 60 bit (considering 1start + 1stop bits). + * 60/115200 = ~520uS + about 2500mS delay -> ~3mS + * In 3mS we could read back about 34 bytes that means 17 samples, this means + * that in case of scattered reads in which the gap is 17 samples or less it is + * still convenient to go for a burst. + * We have to take into account also IMU response time - IMU seems to be often + * reasonably quick to respond, but sometimes it seems to be in some "critical + * section" in which it delays handling of serial protocol. Because of this we + * round-up to 22, which is the max number of samples, always bursting indeed. + */ +#define BNO055_SER_XFER_BURST_BREAK_THRESHOLD 22 + +struct bno055_ser_priv { + enum { + CMD_NONE, + CMD_READ, + CMD_WRITE, + } expect_response; + int expected_data_len; + u8 *response_buf; + + /** + * enum cmd_status - represent the status of a command sent to the HW. + * @STATUS_CRIT: The command failed: the serial communication failed. + * @STATUS_OK: The command executed successfully. + * @STATUS_FAIL: The command failed: HW responded with an error. + */ + enum { + STATUS_CRIT = -1, + STATUS_OK = 0, + STATUS_FAIL = 1, + } cmd_status; + + /* + * Protects all the above fields, which are accessed in behalf of both + * the serdev RX callback and the regmap side + */ + struct mutex lock; + + /* Only accessed in serdev RX callback context*/ + struct { + enum { + RX_IDLE, + RX_START, + RX_DATA, + } state; + int databuf_count; + int expected_len; + int type; + } rx; + + /* Never accessed in behalf of serdev RX callback context */ + bool cmd_stale; + + struct completion cmd_complete; + struct serdev_device *serdev; +}; + +static int bno055_ser_send_chunk(struct bno055_ser_priv *priv, const u8 *data, int len) +{ + int ret; + + trace_send_chunk(len, data); + ret = serdev_device_write(priv->serdev, data, len, msecs_to_jiffies(25)); + if (ret < 0) + return ret; + + if (ret < len) + return -EIO; + + return 0; +} + +/* + * Send a read or write command. + * 'data' can be NULL (used in read case). 'len' parameter is always valid; in + * case 'data' is non-NULL then it must match 'data' size. + */ +static int bno055_ser_do_send_cmd(struct bno055_ser_priv *priv, + bool read, int addr, int len, const u8 *data) +{ + u8 hdr[] = {0xAA, read, addr, len}; + int chunk_len; + int ret; + + ret = bno055_ser_send_chunk(priv, hdr, 2); + if (ret) + goto fail; + usleep_range(2000, 3000); + ret = bno055_ser_send_chunk(priv, hdr + 2, 2); + if (ret) + goto fail; + + if (read) + return 0; + + while (len) { + chunk_len = min(len, 2); + usleep_range(2000, 3000); + ret = bno055_ser_send_chunk(priv, data, chunk_len); + if (ret) + goto fail; + data += chunk_len; + len -= chunk_len; + } + + return 0; +fail: + /* waiting more than 30mS should clear the BNO055 internal state */ + usleep_range(40000, 50000); + return ret; +} + +static int bno055_ser_send_cmd(struct bno055_ser_priv *priv, + bool read, int addr, int len, const u8 *data) +{ + const int retry_max = 5; + int retry = retry_max; + int ret = 0; + + /* + * In case previous command was interrupted we still need to wait it to + * complete before we can issue new commands + */ + if (priv->cmd_stale) { + ret = wait_for_completion_interruptible_timeout(&priv->cmd_complete, + msecs_to_jiffies(100)); + if (ret == -ERESTARTSYS) + return -ERESTARTSYS; + + priv->cmd_stale = false; + /* if serial protocol broke, bail out */ + if (priv->cmd_status == STATUS_CRIT) + return -EIO; + } + + /* + * Try to convince the IMU to cooperate.. as explained in the comments + * at the top of this file, the IMU could also refuse the command (i.e. + * it is not ready yet); retry in this case. + */ + do { + mutex_lock(&priv->lock); + priv->expect_response = read ? CMD_READ : CMD_WRITE; + reinit_completion(&priv->cmd_complete); + mutex_unlock(&priv->lock); + + if (retry != retry_max) + trace_cmd_retry(read, addr, retry_max - retry); + ret = bno055_ser_do_send_cmd(priv, read, addr, len, data); + if (ret) + continue; + + ret = wait_for_completion_interruptible_timeout(&priv->cmd_complete, + msecs_to_jiffies(100)); + if (ret == -ERESTARTSYS) { + priv->cmd_stale = true; + return -ERESTARTSYS; + } + + if (!ret) + return -ETIMEDOUT; + + if (priv->cmd_status == STATUS_OK) + return 0; + if (priv->cmd_status == STATUS_CRIT) + return -EIO; + + /* loop in case priv->cmd_status == STATUS_FAIL */ + } while (--retry); + + if (ret < 0) + return ret; + if (priv->cmd_status == STATUS_FAIL) + return -EINVAL; + return 0; +} + +static int bno055_ser_write_reg(void *context, const void *_data, size_t count) +{ + const u8 *data = _data; + struct bno055_ser_priv *priv = context; + + if (count < 2) { + dev_err(&priv->serdev->dev, "Invalid write count %zu\n", count); + return -EINVAL; + } + + trace_write_reg(data[0], data[1]); + return bno055_ser_send_cmd(priv, 0, data[0], count - 1, data + 1); +} + +static int bno055_ser_read_reg(void *context, + const void *_reg, size_t reg_size, + void *val, size_t val_size) +{ + int ret; + int reg_addr; + const u8 *reg = _reg; + struct bno055_ser_priv *priv = context; + + if (val_size > 128) { + dev_err(&priv->serdev->dev, "Invalid read valsize %zu\n", val_size); + return -EINVAL; + } + + reg_addr = *reg; + trace_read_reg(reg_addr, val_size); + mutex_lock(&priv->lock); + priv->expected_data_len = val_size; + priv->response_buf = val; + mutex_unlock(&priv->lock); + + ret = bno055_ser_send_cmd(priv, 1, reg_addr, val_size, NULL); + + mutex_lock(&priv->lock); + priv->response_buf = NULL; + mutex_unlock(&priv->lock); + + return ret; +} + +/* + * Handler for received data; this is called from the receiver callback whenever + * it got some packet from the serial bus. The status tells us whether the + * packet is valid (i.e. header ok && received payload len consistent wrt the + * header). It's now our responsibility to check whether this is what we + * expected, of whether we got some unexpected, yet valid, packet. + */ +static void bno055_ser_handle_rx(struct bno055_ser_priv *priv, int status) +{ + mutex_lock(&priv->lock); + switch (priv->expect_response) { + case CMD_NONE: + dev_warn(&priv->serdev->dev, "received unexpected, yet valid, data from sensor"); + mutex_unlock(&priv->lock); + return; + + case CMD_READ: + priv->cmd_status = status; + if (status == STATUS_OK && + priv->rx.databuf_count != priv->expected_data_len) { + /* + * If we got here, then the lower layer serial protocol + * seems consistent with itself; if we got an unexpected + * amount of data then signal it as a non critical error + */ + priv->cmd_status = STATUS_FAIL; + dev_warn(&priv->serdev->dev, + "received an unexpected amount of, yet valid, data from sensor"); + } + break; + + case CMD_WRITE: + priv->cmd_status = status; + break; + } + + priv->expect_response = CMD_NONE; + mutex_unlock(&priv->lock); + complete(&priv->cmd_complete); +} + +/* + * Serdev receiver FSM. This tracks the serial communication and parse the + * header. It pushes packets to bno055_ser_handle_rx(), eventually communicating + * failures (i.e. malformed packets). + * Ideally it doesn't know anything about upper layer (i.e. if this is the + * packet we were really expecting), but since we copies the payload into the + * receiver buffer (that is not valid when i.e. we don't expect data), we + * snoop a bit in the upper layer.. + * Also, we assume to RX one pkt per time (i.e. the HW doesn't send anything + * unless we require to AND we don't queue more than one request per time). + */ +static size_t bno055_ser_receive_buf(struct serdev_device *serdev, + const u8 *buf, size_t size) +{ + int status; + struct bno055_ser_priv *priv = serdev_device_get_drvdata(serdev); + size_t remaining = size; + + if (size == 0) + return 0; + + trace_recv(size, buf); + switch (priv->rx.state) { + case RX_IDLE: + /* + * New packet. + * Check for its 1st byte that identifies the pkt type. + */ + if (buf[0] != 0xEE && buf[0] != 0xBB) { + dev_err(&priv->serdev->dev, + "Invalid packet start %x", buf[0]); + bno055_ser_handle_rx(priv, STATUS_CRIT); + break; + } + priv->rx.type = buf[0]; + priv->rx.state = RX_START; + remaining--; + buf++; + priv->rx.databuf_count = 0; + fallthrough; + + case RX_START: + /* + * Packet RX in progress, we expect either 1-byte len or 1-byte + * status depending by the packet type. + */ + if (remaining == 0) + break; + + if (priv->rx.type == 0xEE) { + if (remaining > 1) { + dev_err(&priv->serdev->dev, "EE pkt. Extra data received"); + status = STATUS_CRIT; + } else { + status = (buf[0] == 1) ? STATUS_OK : STATUS_FAIL; + } + bno055_ser_handle_rx(priv, status); + priv->rx.state = RX_IDLE; + break; + + } else { + /*priv->rx.type == 0xBB */ + priv->rx.state = RX_DATA; + priv->rx.expected_len = buf[0]; + remaining--; + buf++; + } + fallthrough; + + case RX_DATA: + /* Header parsed; now receiving packet data payload */ + if (remaining == 0) + break; + + if (priv->rx.databuf_count + remaining > priv->rx.expected_len) { + /* + * This is an inconsistency in serial protocol, we lost + * sync and we don't know how to handle further data + */ + dev_err(&priv->serdev->dev, "BB pkt. Extra data received"); + bno055_ser_handle_rx(priv, STATUS_CRIT); + priv->rx.state = RX_IDLE; + break; + } + + mutex_lock(&priv->lock); + /* + * NULL e.g. when read cmd is stale or when no read cmd is + * actually pending. + */ + if (priv->response_buf && + /* + * Snoop on the upper layer protocol stuff to make sure not + * to write to an invalid memory. Apart for this, let's the + * upper layer manage any inconsistency wrt expected data + * len (as long as the serial protocol is consistent wrt + * itself (i.e. response header is consistent with received + * response len. + */ + (priv->rx.databuf_count + remaining <= priv->expected_data_len)) + memcpy(priv->response_buf + priv->rx.databuf_count, + buf, remaining); + mutex_unlock(&priv->lock); + + priv->rx.databuf_count += remaining; + + /* + * Reached expected len advertised by the IMU for the current + * packet. Pass it to the upper layer (for us it is just valid). + */ + if (priv->rx.databuf_count == priv->rx.expected_len) { + bno055_ser_handle_rx(priv, STATUS_OK); + priv->rx.state = RX_IDLE; + } + break; + } + + return size; +} + +static const struct serdev_device_ops bno055_ser_serdev_ops = { + .receive_buf = bno055_ser_receive_buf, + .write_wakeup = serdev_device_write_wakeup, +}; + +static const struct regmap_bus bno055_ser_regmap_bus = { + .write = bno055_ser_write_reg, + .read = bno055_ser_read_reg, +}; + +static int bno055_ser_probe(struct serdev_device *serdev) +{ + struct bno055_ser_priv *priv; + struct regmap *regmap; + int ret; + + priv = devm_kzalloc(&serdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + serdev_device_set_drvdata(serdev, priv); + priv->serdev = serdev; + mutex_init(&priv->lock); + init_completion(&priv->cmd_complete); + + serdev_device_set_client_ops(serdev, &bno055_ser_serdev_ops); + ret = devm_serdev_device_open(&serdev->dev, serdev); + if (ret) + return ret; + + if (serdev_device_set_baudrate(serdev, 115200) != 115200) { + dev_err(&serdev->dev, "Cannot set required baud rate"); + return -EIO; + } + + ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); + if (ret) { + dev_err(&serdev->dev, "Cannot set required parity setting"); + return ret; + } + serdev_device_set_flow_control(serdev, false); + + regmap = devm_regmap_init(&serdev->dev, &bno055_ser_regmap_bus, + priv, &bno055_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(&serdev->dev, PTR_ERR(regmap), + "Unable to init register map"); + + return bno055_probe(&serdev->dev, regmap, + BNO055_SER_XFER_BURST_BREAK_THRESHOLD, false); +} + +static const struct of_device_id bno055_ser_of_match[] = { + { .compatible = "bosch,bno055" }, + { } +}; +MODULE_DEVICE_TABLE(of, bno055_ser_of_match); + +static struct serdev_device_driver bno055_ser_driver = { + .driver = { + .name = "bno055-ser", + .of_match_table = bno055_ser_of_match, + }, + .probe = bno055_ser_probe, +}; +module_serdev_device_driver(bno055_ser_driver); + +MODULE_AUTHOR("Andrea Merello "); +MODULE_DESCRIPTION("Bosch BNO055 serdev interface"); +MODULE_IMPORT_NS("IIO_BNO055"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/imu/bno055/bno055_ser_trace.c b/drivers/iio/imu/bno055/bno055_ser_trace.c new file mode 100644 index 000000000..ab564186d --- /dev/null +++ b/drivers/iio/imu/bno055/bno055_ser_trace.c @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * bno055_ser Trace Support + * Copyright (C) 2022 Istituto Italiano di Tecnologia + * Electronic Design Laboratory + * + * Based on: + * Device core Trace Support + * Copyright (C) 2021, Intel Corporation + */ + +#define CREATE_TRACE_POINTS +#include "bno055_ser_trace.h" diff --git a/drivers/iio/imu/bno055/bno055_ser_trace.h b/drivers/iio/imu/bno055/bno055_ser_trace.h new file mode 100644 index 000000000..7d9eae166 --- /dev/null +++ b/drivers/iio/imu/bno055/bno055_ser_trace.h @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#if !defined(__BNO055_SERDEV_TRACE_H__) || defined(TRACE_HEADER_MULTI_READ) +#define __BNO055_SERDEV_TRACE_H__ + +#include + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM bno055_ser + +TRACE_EVENT(send_chunk, + TP_PROTO(int len, const u8 *data), + TP_ARGS(len, data), + TP_STRUCT__entry( + __field(int, len) + __dynamic_array(u8, chunk, len) + ), + TP_fast_assign( + __entry->len = len; + memcpy(__get_dynamic_array(chunk), + data, __entry->len); + ), + TP_printk("len: %d, data: = %*ph", + __entry->len, __entry->len, __get_dynamic_array(chunk) + ) +); + +TRACE_EVENT(cmd_retry, + TP_PROTO(bool read, int addr, int retry), + TP_ARGS(read, addr, retry), + TP_STRUCT__entry( + __field(bool, read) + __field(int, addr) + __field(int, retry) + ), + TP_fast_assign( + __entry->read = read; + __entry->addr = addr; + __entry->retry = retry; + ), + TP_printk("%s addr 0x%x retry #%d", + __entry->read ? "read" : "write", + __entry->addr, __entry->retry + ) +); + +TRACE_EVENT(write_reg, + TP_PROTO(u8 addr, u8 value), + TP_ARGS(addr, value), + TP_STRUCT__entry( + __field(u8, addr) + __field(u8, value) + ), + TP_fast_assign( + __entry->addr = addr; + __entry->value = value; + ), + TP_printk("reg 0x%x = 0x%x", + __entry->addr, __entry->value + ) +); + +TRACE_EVENT(read_reg, + TP_PROTO(int addr, size_t len), + TP_ARGS(addr, len), + TP_STRUCT__entry( + __field(int, addr) + __field(size_t, len) + ), + TP_fast_assign( + __entry->addr = addr; + __entry->len = len; + ), + TP_printk("reg 0x%x (len %zu)", + __entry->addr, __entry->len + ) +); + +TRACE_EVENT(recv, + TP_PROTO(size_t len, const unsigned char *buf), + TP_ARGS(len, buf), + TP_STRUCT__entry( + __field(size_t, len) + __dynamic_array(unsigned char, buf, len) + ), + TP_fast_assign( + __entry->len = len; + memcpy(__get_dynamic_array(buf), + buf, __entry->len); + ), + TP_printk("len: %zu, data: = %*ph", + __entry->len, (int)__entry->len, __get_dynamic_array(buf) + ) +); + +#endif /* __BNO055_SERDEV_TRACE_H__ || TRACE_HEADER_MULTI_READ */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE bno055_ser_trace + +/* This part must be outside protection */ +#include diff --git a/drivers/iio/imu/fxos8700.h b/drivers/iio/imu/fxos8700.h new file mode 100644 index 000000000..6dfb8d709 --- /dev/null +++ b/drivers/iio/imu/fxos8700.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef FXOS8700_H_ +#define FXOS8700_H_ + +extern const struct regmap_config fxos8700_regmap_config; + +int fxos8700_core_probe(struct device *dev, struct regmap *regmap, + const char *name, bool use_spi); + +#endif /* FXOS8700_H_ */ diff --git a/drivers/iio/imu/fxos8700_core.c b/drivers/iio/imu/fxos8700_core.c new file mode 100644 index 000000000..9d2eb6fba --- /dev/null +++ b/drivers/iio/imu/fxos8700_core.c @@ -0,0 +1,714 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FXOS8700 - NXP IMU (accelerometer plus magnetometer) + * + * IIO core driver for FXOS8700, with support for I2C/SPI buses + * + * TODO: Buffer, trigger, and IRQ support + */ +#include +#include +#include +#include + +#include +#include + +#include "fxos8700.h" + +/* Register Definitions */ +#define FXOS8700_STATUS 0x00 +#define FXOS8700_OUT_X_MSB 0x01 +#define FXOS8700_OUT_X_LSB 0x02 +#define FXOS8700_OUT_Y_MSB 0x03 +#define FXOS8700_OUT_Y_LSB 0x04 +#define FXOS8700_OUT_Z_MSB 0x05 +#define FXOS8700_OUT_Z_LSB 0x06 +#define FXOS8700_F_SETUP 0x09 +#define FXOS8700_TRIG_CFG 0x0a +#define FXOS8700_SYSMOD 0x0b +#define FXOS8700_INT_SOURCE 0x0c +#define FXOS8700_WHO_AM_I 0x0d +#define FXOS8700_XYZ_DATA_CFG 0x0e +#define FXOS8700_HP_FILTER_CUTOFF 0x0f +#define FXOS8700_PL_STATUS 0x10 +#define FXOS8700_PL_CFG 0x11 +#define FXOS8700_PL_COUNT 0x12 +#define FXOS8700_PL_BF_ZCOMP 0x13 +#define FXOS8700_PL_THS_REG 0x14 +#define FXOS8700_A_FFMT_CFG 0x15 +#define FXOS8700_A_FFMT_SRC 0x16 +#define FXOS8700_A_FFMT_THS 0x17 +#define FXOS8700_A_FFMT_COUNT 0x18 +#define FXOS8700_TRANSIENT_CFG 0x1d +#define FXOS8700_TRANSIENT_SRC 0x1e +#define FXOS8700_TRANSIENT_THS 0x1f +#define FXOS8700_TRANSIENT_COUNT 0x20 +#define FXOS8700_PULSE_CFG 0x21 +#define FXOS8700_PULSE_SRC 0x22 +#define FXOS8700_PULSE_THSX 0x23 +#define FXOS8700_PULSE_THSY 0x24 +#define FXOS8700_PULSE_THSZ 0x25 +#define FXOS8700_PULSE_TMLT 0x26 +#define FXOS8700_PULSE_LTCY 0x27 +#define FXOS8700_PULSE_WIND 0x28 +#define FXOS8700_ASLP_COUNT 0x29 +#define FXOS8700_CTRL_REG1 0x2a +#define FXOS8700_CTRL_REG2 0x2b +#define FXOS8700_CTRL_REG3 0x2c +#define FXOS8700_CTRL_REG4 0x2d +#define FXOS8700_CTRL_REG5 0x2e +#define FXOS8700_OFF_X 0x2f +#define FXOS8700_OFF_Y 0x30 +#define FXOS8700_OFF_Z 0x31 +#define FXOS8700_M_DR_STATUS 0x32 +#define FXOS8700_M_OUT_X_MSB 0x33 +#define FXOS8700_M_OUT_X_LSB 0x34 +#define FXOS8700_M_OUT_Y_MSB 0x35 +#define FXOS8700_M_OUT_Y_LSB 0x36 +#define FXOS8700_M_OUT_Z_MSB 0x37 +#define FXOS8700_M_OUT_Z_LSB 0x38 +#define FXOS8700_CMP_X_MSB 0x39 +#define FXOS8700_CMP_X_LSB 0x3a +#define FXOS8700_CMP_Y_MSB 0x3b +#define FXOS8700_CMP_Y_LSB 0x3c +#define FXOS8700_CMP_Z_MSB 0x3d +#define FXOS8700_CMP_Z_LSB 0x3e +#define FXOS8700_M_OFF_X_MSB 0x3f +#define FXOS8700_M_OFF_X_LSB 0x40 +#define FXOS8700_M_OFF_Y_MSB 0x41 +#define FXOS8700_M_OFF_Y_LSB 0x42 +#define FXOS8700_M_OFF_Z_MSB 0x43 +#define FXOS8700_M_OFF_Z_LSB 0x44 +#define FXOS8700_MAX_X_MSB 0x45 +#define FXOS8700_MAX_X_LSB 0x46 +#define FXOS8700_MAX_Y_MSB 0x47 +#define FXOS8700_MAX_Y_LSB 0x48 +#define FXOS8700_MAX_Z_MSB 0x49 +#define FXOS8700_MAX_Z_LSB 0x4a +#define FXOS8700_MIN_X_MSB 0x4b +#define FXOS8700_MIN_X_LSB 0x4c +#define FXOS8700_MIN_Y_MSB 0x4d +#define FXOS8700_MIN_Y_LSB 0x4e +#define FXOS8700_MIN_Z_MSB 0x4f +#define FXOS8700_MIN_Z_LSB 0x50 +#define FXOS8700_TEMP 0x51 +#define FXOS8700_M_THS_CFG 0x52 +#define FXOS8700_M_THS_SRC 0x53 +#define FXOS8700_M_THS_X_MSB 0x54 +#define FXOS8700_M_THS_X_LSB 0x55 +#define FXOS8700_M_THS_Y_MSB 0x56 +#define FXOS8700_M_THS_Y_LSB 0x57 +#define FXOS8700_M_THS_Z_MSB 0x58 +#define FXOS8700_M_THS_Z_LSB 0x59 +#define FXOS8700_M_THS_COUNT 0x5a +#define FXOS8700_M_CTRL_REG1 0x5b +#define FXOS8700_M_CTRL_REG2 0x5c +#define FXOS8700_M_CTRL_REG3 0x5d +#define FXOS8700_M_INT_SRC 0x5e +#define FXOS8700_A_VECM_CFG 0x5f +#define FXOS8700_A_VECM_THS_MSB 0x60 +#define FXOS8700_A_VECM_THS_LSB 0x61 +#define FXOS8700_A_VECM_CNT 0x62 +#define FXOS8700_A_VECM_INITX_MSB 0x63 +#define FXOS8700_A_VECM_INITX_LSB 0x64 +#define FXOS8700_A_VECM_INITY_MSB 0x65 +#define FXOS8700_A_VECM_INITY_LSB 0x66 +#define FXOS8700_A_VECM_INITZ_MSB 0x67 +#define FXOS8700_A_VECM_INITZ_LSB 0x68 +#define FXOS8700_M_VECM_CFG 0x69 +#define FXOS8700_M_VECM_THS_MSB 0x6a +#define FXOS8700_M_VECM_THS_LSB 0x6b +#define FXOS8700_M_VECM_CNT 0x6c +#define FXOS8700_M_VECM_INITX_MSB 0x6d +#define FXOS8700_M_VECM_INITX_LSB 0x6e +#define FXOS8700_M_VECM_INITY_MSB 0x6f +#define FXOS8700_M_VECM_INITY_LSB 0x70 +#define FXOS8700_M_VECM_INITZ_MSB 0x71 +#define FXOS8700_M_VECM_INITZ_LSB 0x72 +#define FXOS8700_A_FFMT_THS_X_MSB 0x73 +#define FXOS8700_A_FFMT_THS_X_LSB 0x74 +#define FXOS8700_A_FFMT_THS_Y_MSB 0x75 +#define FXOS8700_A_FFMT_THS_Y_LSB 0x76 +#define FXOS8700_A_FFMT_THS_Z_MSB 0x77 +#define FXOS8700_A_FFMT_THS_Z_LSB 0x78 +#define FXOS8700_A_TRAN_INIT_MSB 0x79 +#define FXOS8700_A_TRAN_INIT_LSB_X 0x7a +#define FXOS8700_A_TRAN_INIT_LSB_Y 0x7b +#define FXOS8700_A_TRAN_INIT_LSB_Z 0x7d +#define FXOS8700_TM_NVM_LOCK 0x7e +#define FXOS8700_NVM_DATA0_35 0x80 +#define FXOS8700_NVM_DATA_BNK3 0xa4 +#define FXOS8700_NVM_DATA_BNK2 0xa5 +#define FXOS8700_NVM_DATA_BNK1 0xa6 +#define FXOS8700_NVM_DATA_BNK0 0xa7 + +/* Bit definitions for FXOS8700_CTRL_REG1 */ +#define FXOS8700_CTRL_ODR_MAX 0x00 +#define FXOS8700_CTRL_ODR_MSK GENMASK(5, 3) + +/* Bit definitions for FXOS8700_M_CTRL_REG1 */ +#define FXOS8700_HMS_MASK GENMASK(1, 0) +#define FXOS8700_OS_MASK GENMASK(4, 2) + +/* Bit definitions for FXOS8700_M_CTRL_REG2 */ +#define FXOS8700_MAXMIN_RST BIT(2) +#define FXOS8700_MAXMIN_DIS_THS BIT(3) +#define FXOS8700_MAXMIN_DIS BIT(4) + +#define FXOS8700_ACTIVE 0x01 +#define FXOS8700_ACTIVE_MIN_USLEEP 4000 /* from table 6 in datasheet */ + +#define FXOS8700_DEVICE_ID 0xC7 +#define FXOS8700_PRE_DEVICE_ID 0xC4 +#define FXOS8700_DATA_BUF_SIZE 3 + +struct fxos8700_data { + struct regmap *regmap; + struct iio_trigger *trig; + __be16 buf[FXOS8700_DATA_BUF_SIZE] __aligned(IIO_DMA_MINALIGN); +}; + +/* Regmap info */ +static const struct regmap_range read_range[] = { + { + .range_min = FXOS8700_STATUS, + .range_max = FXOS8700_A_FFMT_COUNT, + }, { + .range_min = FXOS8700_TRANSIENT_CFG, + .range_max = FXOS8700_A_FFMT_THS_Z_LSB, + }, +}; + +static const struct regmap_range write_range[] = { + { + .range_min = FXOS8700_F_SETUP, + .range_max = FXOS8700_TRIG_CFG, + }, { + .range_min = FXOS8700_XYZ_DATA_CFG, + .range_max = FXOS8700_HP_FILTER_CUTOFF, + }, { + .range_min = FXOS8700_PL_CFG, + .range_max = FXOS8700_A_FFMT_CFG, + }, { + .range_min = FXOS8700_A_FFMT_THS, + .range_max = FXOS8700_TRANSIENT_CFG, + }, { + .range_min = FXOS8700_TRANSIENT_THS, + .range_max = FXOS8700_PULSE_CFG, + }, { + .range_min = FXOS8700_PULSE_THSX, + .range_max = FXOS8700_OFF_Z, + }, { + .range_min = FXOS8700_M_OFF_X_MSB, + .range_max = FXOS8700_M_OFF_Z_LSB, + }, { + .range_min = FXOS8700_M_THS_CFG, + .range_max = FXOS8700_M_THS_CFG, + }, { + .range_min = FXOS8700_M_THS_X_MSB, + .range_max = FXOS8700_M_CTRL_REG3, + }, { + .range_min = FXOS8700_A_VECM_CFG, + .range_max = FXOS8700_A_FFMT_THS_Z_LSB, + }, +}; + +static const struct regmap_access_table driver_read_table = { + .yes_ranges = read_range, + .n_yes_ranges = ARRAY_SIZE(read_range), +}; + +static const struct regmap_access_table driver_write_table = { + .yes_ranges = write_range, + .n_yes_ranges = ARRAY_SIZE(write_range), +}; + +const struct regmap_config fxos8700_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = FXOS8700_NVM_DATA_BNK0, + .rd_table = &driver_read_table, + .wr_table = &driver_write_table, +}; +EXPORT_SYMBOL(fxos8700_regmap_config); + +#define FXOS8700_CHANNEL(_type, _axis) { \ + .type = _type, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ +} + +enum fxos8700_accel_scale_bits { + MODE_2G = 0, + MODE_4G, + MODE_8G, +}; + +/* scan indexes follow DATA register order */ +enum fxos8700_scan_axis { + FXOS8700_SCAN_ACCEL_X = 0, + FXOS8700_SCAN_ACCEL_Y, + FXOS8700_SCAN_ACCEL_Z, + FXOS8700_SCAN_MAGN_X, + FXOS8700_SCAN_MAGN_Y, + FXOS8700_SCAN_MAGN_Z, + FXOS8700_SCAN_RHALL, + FXOS8700_SCAN_TIMESTAMP, +}; + +enum fxos8700_sensor { + FXOS8700_ACCEL = 0, + FXOS8700_MAGN, + FXOS8700_NUM_SENSORS /* must be last */ +}; + +enum fxos8700_int_pin { + FXOS8700_PIN_INT1, + FXOS8700_PIN_INT2 +}; + +struct fxos8700_scale { + u8 bits; + int uscale; +}; + +struct fxos8700_odr { + u8 bits; + int odr; + int uodr; +}; + +static const struct fxos8700_scale fxos8700_accel_scale[] = { + { MODE_2G, 244}, + { MODE_4G, 488}, + { MODE_8G, 976}, +}; + +/* + * Accellerometer and magnetometer have the same ODR options, set in the + * CTRL_REG1 register. ODR is halved when using both sensors at once in + * hybrid mode. + */ +static const struct fxos8700_odr fxos8700_odr[] = { + {0x00, 800, 0}, + {0x01, 400, 0}, + {0x02, 200, 0}, + {0x03, 100, 0}, + {0x04, 50, 0}, + {0x05, 12, 500000}, + {0x06, 6, 250000}, + {0x07, 1, 562500}, +}; + +static const struct iio_chan_spec fxos8700_channels[] = { + FXOS8700_CHANNEL(IIO_ACCEL, X), + FXOS8700_CHANNEL(IIO_ACCEL, Y), + FXOS8700_CHANNEL(IIO_ACCEL, Z), + FXOS8700_CHANNEL(IIO_MAGN, X), + FXOS8700_CHANNEL(IIO_MAGN, Y), + FXOS8700_CHANNEL(IIO_MAGN, Z), + IIO_CHAN_SOFT_TIMESTAMP(FXOS8700_SCAN_TIMESTAMP), +}; + +static enum fxos8700_sensor fxos8700_to_sensor(enum iio_chan_type iio_type) +{ + switch (iio_type) { + case IIO_ACCEL: + return FXOS8700_ACCEL; + case IIO_MAGN: + return FXOS8700_MAGN; + default: + return -EINVAL; + } +} + +static int fxos8700_set_active_mode(struct fxos8700_data *data, + enum fxos8700_sensor t, bool mode) +{ + int ret; + + ret = regmap_write(data->regmap, FXOS8700_CTRL_REG1, mode); + if (ret) + return ret; + + usleep_range(FXOS8700_ACTIVE_MIN_USLEEP, + FXOS8700_ACTIVE_MIN_USLEEP + 1000); + + return 0; +} + +static int fxos8700_set_scale(struct fxos8700_data *data, + enum fxos8700_sensor t, int uscale) +{ + int i, ret, val; + bool active_mode; + static const int scale_num = ARRAY_SIZE(fxos8700_accel_scale); + struct device *dev = regmap_get_device(data->regmap); + + if (t == FXOS8700_MAGN) { + dev_err(dev, "Magnetometer scale is locked at 0.001Gs\n"); + return -EINVAL; + } + + /* + * When device is in active mode, it failed to set an ACCEL + * full-scale range(2g/4g/8g) in FXOS8700_XYZ_DATA_CFG. + * This is not align with the datasheet, but it is a fxos8700 + * chip behavier. Set the device in standby mode before setting + * an ACCEL full-scale range. + */ + ret = regmap_read(data->regmap, FXOS8700_CTRL_REG1, &val); + if (ret) + return ret; + + active_mode = val & FXOS8700_ACTIVE; + if (active_mode) { + ret = regmap_write(data->regmap, FXOS8700_CTRL_REG1, + val & ~FXOS8700_ACTIVE); + if (ret) + return ret; + } + + for (i = 0; i < scale_num; i++) + if (fxos8700_accel_scale[i].uscale == uscale) + break; + + if (i == scale_num) + return -EINVAL; + + ret = regmap_write(data->regmap, FXOS8700_XYZ_DATA_CFG, + fxos8700_accel_scale[i].bits); + if (ret) + return ret; + return regmap_write(data->regmap, FXOS8700_CTRL_REG1, + active_mode); +} + +static int fxos8700_get_scale(struct fxos8700_data *data, + enum fxos8700_sensor t, int *uscale) +{ + int i, ret, val; + static const int scale_num = ARRAY_SIZE(fxos8700_accel_scale); + + if (t == FXOS8700_MAGN) { + *uscale = 1000; /* Magnetometer is locked at 0.001Gs */ + return 0; + } + + ret = regmap_read(data->regmap, FXOS8700_XYZ_DATA_CFG, &val); + if (ret) + return ret; + + for (i = 0; i < scale_num; i++) { + if (fxos8700_accel_scale[i].bits == (val & 0x3)) { + *uscale = fxos8700_accel_scale[i].uscale; + return 0; + } + } + + return -EINVAL; +} + +static int fxos8700_get_data(struct fxos8700_data *data, int chan_type, + int axis, int *val) +{ + u8 base, reg; + s16 tmp; + int ret; + + /* + * Different register base addresses varies with channel types. + * This bug hasn't been noticed before because using an enum is + * really hard to read. Use an a switch statement to take over that. + */ + switch (chan_type) { + case IIO_ACCEL: + base = FXOS8700_OUT_X_MSB; + break; + case IIO_MAGN: + base = FXOS8700_M_OUT_X_MSB; + break; + default: + return -EINVAL; + } + + /* Block read 6 bytes of device output registers to avoid data loss */ + ret = regmap_bulk_read(data->regmap, base, data->buf, + sizeof(data->buf)); + if (ret) + return ret; + + /* Convert axis to buffer index */ + reg = axis - IIO_MOD_X; + + /* + * Convert to native endianness. The accel data and magn data + * are signed, so a forced type conversion is needed. + */ + tmp = be16_to_cpu(data->buf[reg]); + + /* + * ACCEL output data registers contain the X-axis, Y-axis, and Z-axis + * 14-bit left-justified sample data and MAGN output data registers + * contain the X-axis, Y-axis, and Z-axis 16-bit sample data. Apply + * a signed 2 bits right shift to the readback raw data from ACCEL + * output data register and keep that from MAGN sensor as the origin. + * Value should be extended to 32 bit. + */ + switch (chan_type) { + case IIO_ACCEL: + tmp = tmp >> 2; + break; + case IIO_MAGN: + /* Nothing to do */ + break; + default: + return -EINVAL; + } + + /* Convert to native endianness */ + *val = sign_extend32(tmp, 15); + + return 0; +} + +static int fxos8700_set_odr(struct fxos8700_data *data, enum fxos8700_sensor t, + int odr, int uodr) +{ + int i, ret, val; + bool active_mode; + static const int odr_num = ARRAY_SIZE(fxos8700_odr); + + ret = regmap_read(data->regmap, FXOS8700_CTRL_REG1, &val); + if (ret) + return ret; + + active_mode = val & FXOS8700_ACTIVE; + + if (active_mode) { + /* + * The device must be in standby mode to change any of the + * other fields within CTRL_REG1 + */ + ret = regmap_write(data->regmap, FXOS8700_CTRL_REG1, + val & ~FXOS8700_ACTIVE); + if (ret) + return ret; + } + + for (i = 0; i < odr_num; i++) + if (fxos8700_odr[i].odr == odr && fxos8700_odr[i].uodr == uodr) + break; + + if (i >= odr_num) + return -EINVAL; + + val &= ~FXOS8700_CTRL_ODR_MSK; + val |= FIELD_PREP(FXOS8700_CTRL_ODR_MSK, fxos8700_odr[i].bits) | FXOS8700_ACTIVE; + return regmap_write(data->regmap, FXOS8700_CTRL_REG1, val); +} + +static int fxos8700_get_odr(struct fxos8700_data *data, enum fxos8700_sensor t, + int *odr, int *uodr) +{ + int i, val, ret; + static const int odr_num = ARRAY_SIZE(fxos8700_odr); + + ret = regmap_read(data->regmap, FXOS8700_CTRL_REG1, &val); + if (ret) + return ret; + + val = FIELD_GET(FXOS8700_CTRL_ODR_MSK, val); + + for (i = 0; i < odr_num; i++) + if (val == fxos8700_odr[i].bits) + break; + + if (i >= odr_num) + return -EINVAL; + + *odr = fxos8700_odr[i].odr; + *uodr = fxos8700_odr[i].uodr; + + return 0; +} + +static int fxos8700_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct fxos8700_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = fxos8700_get_data(data, chan->type, chan->channel2, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + ret = fxos8700_get_scale(data, fxos8700_to_sensor(chan->type), + val2); + return ret ? ret : IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = fxos8700_get_odr(data, fxos8700_to_sensor(chan->type), + val, val2); + return ret ? ret : IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int fxos8700_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct fxos8700_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return fxos8700_set_scale(data, fxos8700_to_sensor(chan->type), + val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return fxos8700_set_odr(data, fxos8700_to_sensor(chan->type), + val, val2); + default: + return -EINVAL; + } +} + +static IIO_CONST_ATTR(in_accel_sampling_frequency_available, + "1.5625 6.25 12.5 50 100 200 400 800"); +static IIO_CONST_ATTR(in_magn_sampling_frequency_available, + "1.5625 6.25 12.5 50 100 200 400 800"); +static IIO_CONST_ATTR(in_accel_scale_available, "0.000244 0.000488 0.000976"); +static IIO_CONST_ATTR(in_magn_scale_available, "0.001000"); + +static struct attribute *fxos8700_attrs[] = { + &iio_const_attr_in_accel_sampling_frequency_available.dev_attr.attr, + &iio_const_attr_in_magn_sampling_frequency_available.dev_attr.attr, + &iio_const_attr_in_accel_scale_available.dev_attr.attr, + &iio_const_attr_in_magn_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group fxos8700_attrs_group = { + .attrs = fxos8700_attrs, +}; + +static const struct iio_info fxos8700_info = { + .read_raw = fxos8700_read_raw, + .write_raw = fxos8700_write_raw, + .attrs = &fxos8700_attrs_group, +}; + +static int fxos8700_chip_init(struct fxos8700_data *data, bool use_spi) +{ + int ret; + unsigned int val; + struct device *dev = regmap_get_device(data->regmap); + + ret = regmap_read(data->regmap, FXOS8700_WHO_AM_I, &val); + if (ret) { + dev_err(dev, "Error reading chip id\n"); + return ret; + } + if (val != FXOS8700_DEVICE_ID && val != FXOS8700_PRE_DEVICE_ID) { + dev_err(dev, "Wrong chip id, got %x expected %x or %x\n", + val, FXOS8700_DEVICE_ID, FXOS8700_PRE_DEVICE_ID); + return -ENODEV; + } + + ret = fxos8700_set_active_mode(data, FXOS8700_ACCEL, true); + if (ret) + return ret; + + ret = fxos8700_set_active_mode(data, FXOS8700_MAGN, true); + if (ret) + return ret; + + /* + * The device must be in standby mode to change any of the other fields + * within CTRL_REG1 + */ + ret = regmap_write(data->regmap, FXOS8700_CTRL_REG1, 0x00); + if (ret) + return ret; + + /* Set max oversample ratio (OSR) and both devices active */ + ret = regmap_write(data->regmap, FXOS8700_M_CTRL_REG1, + FXOS8700_HMS_MASK | FXOS8700_OS_MASK); + if (ret) + return ret; + + /* Disable and rst min/max measurements & threshold */ + ret = regmap_write(data->regmap, FXOS8700_M_CTRL_REG2, + FXOS8700_MAXMIN_RST | FXOS8700_MAXMIN_DIS_THS | + FXOS8700_MAXMIN_DIS); + if (ret) + return ret; + + /* + * Set max full-scale range (+/-8G) for ACCEL sensor in chip + * initialization then activate the device. + */ + ret = regmap_write(data->regmap, FXOS8700_XYZ_DATA_CFG, MODE_8G); + if (ret) + return ret; + + /* Max ODR (800Hz individual or 400Hz hybrid), active mode */ + return regmap_update_bits(data->regmap, FXOS8700_CTRL_REG1, + FXOS8700_CTRL_ODR_MSK | FXOS8700_ACTIVE, + FIELD_PREP(FXOS8700_CTRL_ODR_MSK, FXOS8700_CTRL_ODR_MAX) | + FXOS8700_ACTIVE); +} + +static void fxos8700_chip_uninit(void *data) +{ + struct fxos8700_data *fxos8700_data = data; + + fxos8700_set_active_mode(fxos8700_data, FXOS8700_ACCEL, false); + fxos8700_set_active_mode(fxos8700_data, FXOS8700_MAGN, false); +} + +int fxos8700_core_probe(struct device *dev, struct regmap *regmap, + const char *name, bool use_spi) +{ + struct iio_dev *indio_dev; + struct fxos8700_data *data; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + dev_set_drvdata(dev, indio_dev); + data->regmap = regmap; + + ret = fxos8700_chip_init(data, use_spi); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, fxos8700_chip_uninit, data); + if (ret) + return ret; + + indio_dev->channels = fxos8700_channels; + indio_dev->num_channels = ARRAY_SIZE(fxos8700_channels); + indio_dev->name = name ? name : "fxos8700"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &fxos8700_info; + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_GPL(fxos8700_core_probe); + +MODULE_AUTHOR("Robert Jones "); +MODULE_DESCRIPTION("FXOS8700 6-Axis Acc and Mag Combo Sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/imu/fxos8700_i2c.c b/drivers/iio/imu/fxos8700_i2c.c new file mode 100644 index 000000000..ba47e9260 --- /dev/null +++ b/drivers/iio/imu/fxos8700_i2c.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FXOS8700 - NXP IMU, I2C bits + * + * 7-bit I2C slave address determined by SA1 and SA0 logic level + * inputs represented in the following table: + * SA1 | SA0 | Slave Address + * 0 | 0 | 0x1E + * 0 | 1 | 0x1D + * 1 | 0 | 0x1C + * 1 | 1 | 0x1F + */ +#include +#include +#include + +#include "fxos8700.h" + +static int fxos8700_i2c_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct regmap *regmap; + const char *name = NULL; + + regmap = devm_regmap_init_i2c(client, &fxos8700_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Failed to register i2c regmap %ld\n", PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + if (id) + name = id->name; + + return fxos8700_core_probe(&client->dev, regmap, name, false); +} + +static const struct i2c_device_id fxos8700_i2c_id[] = { + { .name = "fxos8700" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, fxos8700_i2c_id); + +static const struct acpi_device_id fxos8700_acpi_match[] = { + {"FXOS8700", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, fxos8700_acpi_match); + +static const struct of_device_id fxos8700_of_match[] = { + { .compatible = "nxp,fxos8700" }, + { } +}; +MODULE_DEVICE_TABLE(of, fxos8700_of_match); + +static struct i2c_driver fxos8700_i2c_driver = { + .driver = { + .name = "fxos8700_i2c", + .acpi_match_table = fxos8700_acpi_match, + .of_match_table = fxos8700_of_match, + }, + .probe = fxos8700_i2c_probe, + .id_table = fxos8700_i2c_id, +}; +module_i2c_driver(fxos8700_i2c_driver); + +MODULE_AUTHOR("Robert Jones "); +MODULE_DESCRIPTION("FXOS8700 I2C driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/imu/fxos8700_spi.c b/drivers/iio/imu/fxos8700_spi.c new file mode 100644 index 000000000..56ad0c591 --- /dev/null +++ b/drivers/iio/imu/fxos8700_spi.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FXOS8700 - NXP IMU, SPI bits + */ +#include +#include +#include + +#include "fxos8700.h" + +static int fxos8700_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + const struct spi_device_id *id = spi_get_device_id(spi); + + regmap = devm_regmap_init_spi(spi, &fxos8700_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "Failed to register spi regmap %ld\n", PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + return fxos8700_core_probe(&spi->dev, regmap, id->name, true); +} + +static const struct spi_device_id fxos8700_spi_id[] = { + { .name = "fxos8700" }, + { } +}; +MODULE_DEVICE_TABLE(spi, fxos8700_spi_id); + +static const struct acpi_device_id fxos8700_acpi_match[] = { + {"FXOS8700", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, fxos8700_acpi_match); + +static const struct of_device_id fxos8700_of_match[] = { + { .compatible = "nxp,fxos8700" }, + { } +}; +MODULE_DEVICE_TABLE(of, fxos8700_of_match); + +static struct spi_driver fxos8700_spi_driver = { + .probe = fxos8700_spi_probe, + .id_table = fxos8700_spi_id, + .driver = { + .acpi_match_table = fxos8700_acpi_match, + .of_match_table = fxos8700_of_match, + .name = "fxos8700_spi", + }, +}; +module_spi_driver(fxos8700_spi_driver); + +MODULE_AUTHOR("Robert Jones "); +MODULE_DESCRIPTION("FXOS8700 SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/imu/inv_icm42600/Kconfig b/drivers/iio/imu/inv_icm42600/Kconfig new file mode 100644 index 000000000..f56b0816c --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/Kconfig @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +config INV_ICM42600 + tristate + select IIO_BUFFER + select IIO_INV_SENSORS_TIMESTAMP + +config INV_ICM42600_I2C + tristate "InvenSense ICM-426xx I2C driver" + depends on I2C + select INV_ICM42600 + select REGMAP_I2C + help + This driver supports the InvenSense ICM-426xx motion tracking + devices over I2C. + + This driver can be built as a module. The module will be called + inv-icm42600-i2c. + +config INV_ICM42600_SPI + tristate "InvenSense ICM-426xx SPI driver" + depends on SPI_MASTER + select INV_ICM42600 + select REGMAP_SPI + help + This driver supports the InvenSense ICM-426xx motion tracking + devices over SPI. + + This driver can be built as a module. The module will be called + inv-icm42600-spi. diff --git a/drivers/iio/imu/inv_icm42600/Makefile b/drivers/iio/imu/inv_icm42600/Makefile new file mode 100644 index 000000000..0f49f6df3 --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +obj-$(CONFIG_INV_ICM42600) += inv-icm42600.o +inv-icm42600-y += inv_icm42600_core.o +inv-icm42600-y += inv_icm42600_gyro.o +inv-icm42600-y += inv_icm42600_accel.o +inv-icm42600-y += inv_icm42600_temp.o +inv-icm42600-y += inv_icm42600_buffer.o + +obj-$(CONFIG_INV_ICM42600_I2C) += inv-icm42600-i2c.o +inv-icm42600-i2c-y += inv_icm42600_i2c.o + +obj-$(CONFIG_INV_ICM42600_SPI) += inv-icm42600-spi.o +inv-icm42600-spi-y += inv_icm42600_spi.o diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600.h b/drivers/iio/imu/inv_icm42600/inv_icm42600.h new file mode 100644 index 000000000..b55d993f0 --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600.h @@ -0,0 +1,491 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020 Invensense, Inc. + */ + +#ifndef INV_ICM42600_H_ +#define INV_ICM42600_H_ + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "inv_icm42600_buffer.h" + +enum inv_icm42600_chip { + INV_CHIP_INVALID, + INV_CHIP_ICM42600, + INV_CHIP_ICM42602, + INV_CHIP_ICM42605, + INV_CHIP_ICM42686, + INV_CHIP_ICM42622, + INV_CHIP_ICM42688, + INV_CHIP_ICM42631, + INV_CHIP_NB, +}; + +/* serial bus slew rates */ +enum inv_icm42600_slew_rate { + INV_ICM42600_SLEW_RATE_20_60NS, + INV_ICM42600_SLEW_RATE_12_36NS, + INV_ICM42600_SLEW_RATE_6_18NS, + INV_ICM42600_SLEW_RATE_4_12NS, + INV_ICM42600_SLEW_RATE_2_6NS, + INV_ICM42600_SLEW_RATE_INF_2NS, +}; + +enum inv_icm42600_sensor_mode { + INV_ICM42600_SENSOR_MODE_OFF, + INV_ICM42600_SENSOR_MODE_STANDBY, + INV_ICM42600_SENSOR_MODE_LOW_POWER, + INV_ICM42600_SENSOR_MODE_LOW_NOISE, + INV_ICM42600_SENSOR_MODE_NB, +}; + +/* gyroscope fullscale values */ +enum inv_icm42600_gyro_fs { + INV_ICM42600_GYRO_FS_2000DPS, + INV_ICM42600_GYRO_FS_1000DPS, + INV_ICM42600_GYRO_FS_500DPS, + INV_ICM42600_GYRO_FS_250DPS, + INV_ICM42600_GYRO_FS_125DPS, + INV_ICM42600_GYRO_FS_62_5DPS, + INV_ICM42600_GYRO_FS_31_25DPS, + INV_ICM42600_GYRO_FS_15_625DPS, + INV_ICM42600_GYRO_FS_NB, +}; +enum inv_icm42686_gyro_fs { + INV_ICM42686_GYRO_FS_4000DPS, + INV_ICM42686_GYRO_FS_2000DPS, + INV_ICM42686_GYRO_FS_1000DPS, + INV_ICM42686_GYRO_FS_500DPS, + INV_ICM42686_GYRO_FS_250DPS, + INV_ICM42686_GYRO_FS_125DPS, + INV_ICM42686_GYRO_FS_62_5DPS, + INV_ICM42686_GYRO_FS_31_25DPS, + INV_ICM42686_GYRO_FS_NB, +}; + +/* accelerometer fullscale values */ +enum inv_icm42600_accel_fs { + INV_ICM42600_ACCEL_FS_16G, + INV_ICM42600_ACCEL_FS_8G, + INV_ICM42600_ACCEL_FS_4G, + INV_ICM42600_ACCEL_FS_2G, + INV_ICM42600_ACCEL_FS_NB, +}; +enum inv_icm42686_accel_fs { + INV_ICM42686_ACCEL_FS_32G, + INV_ICM42686_ACCEL_FS_16G, + INV_ICM42686_ACCEL_FS_8G, + INV_ICM42686_ACCEL_FS_4G, + INV_ICM42686_ACCEL_FS_2G, + INV_ICM42686_ACCEL_FS_NB, +}; + +/* ODR suffixed by LN or LP are Low-Noise or Low-Power mode only */ +enum inv_icm42600_odr { + INV_ICM42600_ODR_8KHZ_LN = 3, + INV_ICM42600_ODR_4KHZ_LN, + INV_ICM42600_ODR_2KHZ_LN, + INV_ICM42600_ODR_1KHZ_LN, + INV_ICM42600_ODR_200HZ, + INV_ICM42600_ODR_100HZ, + INV_ICM42600_ODR_50HZ, + INV_ICM42600_ODR_25HZ, + INV_ICM42600_ODR_12_5HZ, + INV_ICM42600_ODR_6_25HZ_LP, + INV_ICM42600_ODR_3_125HZ_LP, + INV_ICM42600_ODR_1_5625HZ_LP, + INV_ICM42600_ODR_500HZ, + INV_ICM42600_ODR_NB, +}; + +enum inv_icm42600_filter { + /* Low-Noise mode sensor data filter (3rd order filter by default) */ + INV_ICM42600_FILTER_BW_ODR_DIV_2, + + /* Low-Power mode sensor data filter (averaging) */ + INV_ICM42600_FILTER_AVG_1X = 1, + INV_ICM42600_FILTER_AVG_16X = 6, +}; + +struct inv_icm42600_sensor_conf { + int mode; + int fs; + int odr; + int filter; +}; +#define INV_ICM42600_SENSOR_CONF_INIT {-1, -1, -1, -1} + +struct inv_icm42600_conf { + struct inv_icm42600_sensor_conf gyro; + struct inv_icm42600_sensor_conf accel; + bool temp_en; +}; + +struct inv_icm42600_suspended { + enum inv_icm42600_sensor_mode gyro; + enum inv_icm42600_sensor_mode accel; + bool temp; +}; + +struct inv_icm42600_apex { + unsigned int on; + struct { + u64 value; + bool enable; + } wom; +}; + +/** + * struct inv_icm42600_state - driver state variables + * @lock: lock for serializing multiple registers access. + * @chip: chip identifier. + * @name: chip name. + * @map: regmap pointer. + * @vdd_supply: VDD voltage regulator for the chip. + * @vddio_supply: I/O voltage regulator for the chip. + * @irq: chip irq, required to enable/disable and set wakeup + * @orientation: sensor chip orientation relative to main hardware. + * @conf: chip sensors configurations. + * @suspended: suspended sensors configuration. + * @indio_gyro: gyroscope IIO device. + * @indio_accel: accelerometer IIO device. + * @timestamp: interrupt timestamps. + * @apex: APEX (Advanced Pedometer and Event detection) management + * @fifo: FIFO management structure. + * @buffer: data transfer buffer aligned for DMA. + */ +struct inv_icm42600_state { + struct mutex lock; + enum inv_icm42600_chip chip; + const char *name; + struct regmap *map; + struct regulator *vddio_supply; + int irq; + struct iio_mount_matrix orientation; + struct inv_icm42600_conf conf; + struct inv_icm42600_suspended suspended; + struct iio_dev *indio_gyro; + struct iio_dev *indio_accel; + struct { + s64 gyro; + s64 accel; + } timestamp; + struct inv_icm42600_apex apex; + struct inv_icm42600_fifo fifo; + u8 buffer[3] __aligned(IIO_DMA_MINALIGN); +}; + + +/** + * struct inv_icm42600_sensor_state - sensor state variables + * @scales: table of scales. + * @scales_len: length (nb of items) of the scales table. + * @power_mode: sensor requested power mode (for common frequencies) + * @filter: sensor filter. + * @ts: timestamp module states. + */ +struct inv_icm42600_sensor_state { + const int *scales; + size_t scales_len; + enum inv_icm42600_sensor_mode power_mode; + enum inv_icm42600_filter filter; + struct inv_sensors_timestamp ts; +}; + +/* Virtual register addresses: @bank on MSB (4 upper bits), @address on LSB */ + +/* Bank selection register, available in all banks */ +#define INV_ICM42600_REG_BANK_SEL 0x76 +#define INV_ICM42600_BANK_SEL_MASK GENMASK(2, 0) + +/* User bank 0 (MSB 0x00) */ +#define INV_ICM42600_REG_DEVICE_CONFIG 0x0011 +#define INV_ICM42600_DEVICE_CONFIG_SOFT_RESET BIT(0) + +#define INV_ICM42600_REG_DRIVE_CONFIG 0x0013 +#define INV_ICM42600_DRIVE_CONFIG_I2C_MASK GENMASK(5, 3) +#define INV_ICM42600_DRIVE_CONFIG_I2C(_rate) \ + FIELD_PREP(INV_ICM42600_DRIVE_CONFIG_I2C_MASK, (_rate)) +#define INV_ICM42600_DRIVE_CONFIG_SPI_MASK GENMASK(2, 0) +#define INV_ICM42600_DRIVE_CONFIG_SPI(_rate) \ + FIELD_PREP(INV_ICM42600_DRIVE_CONFIG_SPI_MASK, (_rate)) + +#define INV_ICM42600_REG_INT_CONFIG 0x0014 +#define INV_ICM42600_INT_CONFIG_INT2_LATCHED BIT(5) +#define INV_ICM42600_INT_CONFIG_INT2_PUSH_PULL BIT(4) +#define INV_ICM42600_INT_CONFIG_INT2_ACTIVE_HIGH BIT(3) +#define INV_ICM42600_INT_CONFIG_INT2_ACTIVE_LOW 0x00 +#define INV_ICM42600_INT_CONFIG_INT1_LATCHED BIT(2) +#define INV_ICM42600_INT_CONFIG_INT1_PUSH_PULL BIT(1) +#define INV_ICM42600_INT_CONFIG_INT1_ACTIVE_HIGH BIT(0) +#define INV_ICM42600_INT_CONFIG_INT1_ACTIVE_LOW 0x00 + +#define INV_ICM42600_REG_FIFO_CONFIG 0x0016 +#define INV_ICM42600_FIFO_CONFIG_MASK GENMASK(7, 6) +#define INV_ICM42600_FIFO_CONFIG_BYPASS \ + FIELD_PREP(INV_ICM42600_FIFO_CONFIG_MASK, 0) +#define INV_ICM42600_FIFO_CONFIG_STREAM \ + FIELD_PREP(INV_ICM42600_FIFO_CONFIG_MASK, 1) +#define INV_ICM42600_FIFO_CONFIG_STOP_ON_FULL \ + FIELD_PREP(INV_ICM42600_FIFO_CONFIG_MASK, 2) + +/* all sensor data are 16 bits (2 registers wide) in big-endian */ +#define INV_ICM42600_REG_TEMP_DATA 0x001D +#define INV_ICM42600_REG_ACCEL_DATA_X 0x001F +#define INV_ICM42600_REG_ACCEL_DATA_Y 0x0021 +#define INV_ICM42600_REG_ACCEL_DATA_Z 0x0023 +#define INV_ICM42600_REG_GYRO_DATA_X 0x0025 +#define INV_ICM42600_REG_GYRO_DATA_Y 0x0027 +#define INV_ICM42600_REG_GYRO_DATA_Z 0x0029 +#define INV_ICM42600_DATA_INVALID -32768 + +#define INV_ICM42600_REG_INT_STATUS 0x002D +#define INV_ICM42600_INT_STATUS_UI_FSYNC BIT(6) +#define INV_ICM42600_INT_STATUS_PLL_RDY BIT(5) +#define INV_ICM42600_INT_STATUS_RESET_DONE BIT(4) +#define INV_ICM42600_INT_STATUS_DATA_RDY BIT(3) +#define INV_ICM42600_INT_STATUS_FIFO_THS BIT(2) +#define INV_ICM42600_INT_STATUS_FIFO_FULL BIT(1) +#define INV_ICM42600_INT_STATUS_AGC_RDY BIT(0) + +/* + * FIFO access registers + * FIFO count is 16 bits (2 registers) big-endian + * FIFO data is a continuous read register to read FIFO content + */ +#define INV_ICM42600_REG_FIFO_COUNT 0x002E +#define INV_ICM42600_REG_FIFO_DATA 0x0030 + +#define INV_ICM42600_REG_INT_STATUS2 0x0037 +#define INV_ICM42600_INT_STATUS2_SMD_INT BIT(3) +#define INV_ICM42600_INT_STATUS2_WOM_INT GENMASK(2, 0) + +#define INV_ICM42600_REG_INT_STATUS3 0x0038 +#define INV_ICM42600_INT_STATUS3_STEP_DET_INT BIT(5) +#define INV_ICM42600_INT_STATUS3_STEP_CNT_OVF_INT BIT(4) +#define INV_ICM42600_INT_STATUS3_TILT_DET_INT BIT(3) +#define INV_ICM42600_INT_STATUS3_WAKE_INT BIT(2) +#define INV_ICM42600_INT_STATUS3_SLEEP_INT BIT(1) +#define INV_ICM42600_INT_STATUS3_TAP_DET_INT BIT(0) + +#define INV_ICM42600_REG_SIGNAL_PATH_RESET 0x004B +#define INV_ICM42600_SIGNAL_PATH_RESET_DMP_INIT_EN BIT(6) +#define INV_ICM42600_SIGNAL_PATH_RESET_DMP_MEM_RESET BIT(5) +#define INV_ICM42600_SIGNAL_PATH_RESET_RESET BIT(3) +#define INV_ICM42600_SIGNAL_PATH_RESET_TMST_STROBE BIT(2) +#define INV_ICM42600_SIGNAL_PATH_RESET_FIFO_FLUSH BIT(1) + +/* default configuration: all data big-endian and fifo count in bytes */ +#define INV_ICM42600_REG_INTF_CONFIG0 0x004C +#define INV_ICM42600_INTF_CONFIG0_FIFO_HOLD_LAST_DATA BIT(7) +#define INV_ICM42600_INTF_CONFIG0_FIFO_COUNT_REC BIT(6) +#define INV_ICM42600_INTF_CONFIG0_FIFO_COUNT_ENDIAN BIT(5) +#define INV_ICM42600_INTF_CONFIG0_SENSOR_DATA_ENDIAN BIT(4) +#define INV_ICM42600_INTF_CONFIG0_UI_SIFS_CFG_MASK GENMASK(1, 0) +#define INV_ICM42600_INTF_CONFIG0_UI_SIFS_CFG_SPI_DIS \ + FIELD_PREP(INV_ICM42600_INTF_CONFIG0_UI_SIFS_CFG_MASK, 2) +#define INV_ICM42600_INTF_CONFIG0_UI_SIFS_CFG_I2C_DIS \ + FIELD_PREP(INV_ICM42600_INTF_CONFIG0_UI_SIFS_CFG_MASK, 3) + +#define INV_ICM42600_REG_INTF_CONFIG1 0x004D +#define INV_ICM42600_INTF_CONFIG1_ACCEL_LP_CLK_RC BIT(3) + +#define INV_ICM42600_REG_PWR_MGMT0 0x004E +#define INV_ICM42600_PWR_MGMT0_TEMP_DIS BIT(5) +#define INV_ICM42600_PWR_MGMT0_IDLE BIT(4) +#define INV_ICM42600_PWR_MGMT0_GYRO(_mode) \ + FIELD_PREP(GENMASK(3, 2), (_mode)) +#define INV_ICM42600_PWR_MGMT0_ACCEL(_mode) \ + FIELD_PREP(GENMASK(1, 0), (_mode)) + +#define INV_ICM42600_REG_GYRO_CONFIG0 0x004F +#define INV_ICM42600_GYRO_CONFIG0_FS(_fs) \ + FIELD_PREP(GENMASK(7, 5), (_fs)) +#define INV_ICM42600_GYRO_CONFIG0_ODR(_odr) \ + FIELD_PREP(GENMASK(3, 0), (_odr)) + +#define INV_ICM42600_REG_ACCEL_CONFIG0 0x0050 +#define INV_ICM42600_ACCEL_CONFIG0_FS(_fs) \ + FIELD_PREP(GENMASK(7, 5), (_fs)) +#define INV_ICM42600_ACCEL_CONFIG0_ODR(_odr) \ + FIELD_PREP(GENMASK(3, 0), (_odr)) + +#define INV_ICM42600_REG_GYRO_ACCEL_CONFIG0 0x0052 +#define INV_ICM42600_GYRO_ACCEL_CONFIG0_ACCEL_FILT(_f) \ + FIELD_PREP(GENMASK(7, 4), (_f)) +#define INV_ICM42600_GYRO_ACCEL_CONFIG0_GYRO_FILT(_f) \ + FIELD_PREP(GENMASK(3, 0), (_f)) + +#define INV_ICM42600_REG_TMST_CONFIG 0x0054 +#define INV_ICM42600_TMST_CONFIG_MASK GENMASK(4, 0) +#define INV_ICM42600_TMST_CONFIG_TMST_TO_REGS_EN BIT(4) +#define INV_ICM42600_TMST_CONFIG_TMST_RES_16US BIT(3) +#define INV_ICM42600_TMST_CONFIG_TMST_DELTA_EN BIT(2) +#define INV_ICM42600_TMST_CONFIG_TMST_FSYNC_EN BIT(1) +#define INV_ICM42600_TMST_CONFIG_TMST_EN BIT(0) + +#define INV_ICM42600_REG_SMD_CONFIG 0x0057 +#define INV_ICM42600_SMD_CONFIG_WOM_INT_MODE BIT(3) +#define INV_ICM42600_SMD_CONFIG_WOM_MODE BIT(2) +#define INV_ICM42600_SMD_CONFIG_SMD_MODE_OFF 0x00 +#define INV_ICM42600_SMD_CONFIG_SMD_MODE_WOM 0x01 +#define INV_ICM42600_SMD_CONFIG_SMD_MODE_SHORT 0x02 +#define INV_ICM42600_SMD_CONFIG_SMD_MODE_LONG 0x03 + +#define INV_ICM42600_REG_FIFO_CONFIG1 0x005F +#define INV_ICM42600_FIFO_CONFIG1_RESUME_PARTIAL_RD BIT(6) +#define INV_ICM42600_FIFO_CONFIG1_WM_GT_TH BIT(5) +#define INV_ICM42600_FIFO_CONFIG1_TMST_FSYNC_EN BIT(3) +#define INV_ICM42600_FIFO_CONFIG1_TEMP_EN BIT(2) +#define INV_ICM42600_FIFO_CONFIG1_GYRO_EN BIT(1) +#define INV_ICM42600_FIFO_CONFIG1_ACCEL_EN BIT(0) + +/* FIFO watermark is 16 bits (2 registers wide) in little-endian */ +#define INV_ICM42600_REG_FIFO_WATERMARK 0x0060 +#define INV_ICM42600_FIFO_WATERMARK_VAL(_wm) \ + cpu_to_le16((_wm) & GENMASK(11, 0)) +/* FIFO is 2048 bytes, let 12 samples for reading latency */ +#define INV_ICM42600_FIFO_WATERMARK_MAX (2048 - 12 * 16) +/* INV_ICM42600_FIFO_WATERMARK_MAX / 8 = 232 */ +#define INV_ICM42600_FIFO_WATERMARK_MAX_SAMPLES 232 + +#define INV_ICM42600_REG_INT_CONFIG1 0x0064 +#define INV_ICM42600_INT_CONFIG1_TPULSE_DURATION BIT(6) +#define INV_ICM42600_INT_CONFIG1_TDEASSERT_DISABLE BIT(5) +#define INV_ICM42600_INT_CONFIG1_ASYNC_RESET BIT(4) + +#define INV_ICM42600_REG_INT_SOURCE0 0x0065 +#define INV_ICM42600_INT_SOURCE0_UI_FSYNC_INT1_EN BIT(6) +#define INV_ICM42600_INT_SOURCE0_PLL_RDY_INT1_EN BIT(5) +#define INV_ICM42600_INT_SOURCE0_RESET_DONE_INT1_EN BIT(4) +#define INV_ICM42600_INT_SOURCE0_UI_DRDY_INT1_EN BIT(3) +#define INV_ICM42600_INT_SOURCE0_FIFO_THS_INT1_EN BIT(2) +#define INV_ICM42600_INT_SOURCE0_FIFO_FULL_INT1_EN BIT(1) +#define INV_ICM42600_INT_SOURCE0_UI_AGC_RDY_INT1_EN BIT(0) + +#define INV_ICM42600_REG_INT_SOURCE1 0x0066 +#define INV_ICM42600_INT_SOURCE1_I3C_ERROR_INT1_EN BIT(6) +#define INV_ICM42600_INT_SOURCE1_SMD_INT1_EN BIT(3) +#define INV_ICM42600_INT_SOURCE1_WOM_INT1_EN GENMASK(2, 0) + +#define INV_ICM42600_REG_WHOAMI 0x0075 +#define INV_ICM42600_WHOAMI_ICM42600 0x40 +#define INV_ICM42600_WHOAMI_ICM42602 0x41 +#define INV_ICM42600_WHOAMI_ICM42605 0x42 +#define INV_ICM42600_WHOAMI_ICM42686 0x44 +#define INV_ICM42600_WHOAMI_ICM42622 0x46 +#define INV_ICM42600_WHOAMI_ICM42688 0x47 +#define INV_ICM42600_WHOAMI_ICM42631 0x5C + +/* User bank 1 (MSB 0x10) */ +#define INV_ICM42600_REG_SENSOR_CONFIG0 0x1003 +#define INV_ICM42600_SENSOR_CONFIG0_ZG_DISABLE BIT(5) +#define INV_ICM42600_SENSOR_CONFIG0_YG_DISABLE BIT(4) +#define INV_ICM42600_SENSOR_CONFIG0_XG_DISABLE BIT(3) +#define INV_ICM42600_SENSOR_CONFIG0_ZA_DISABLE BIT(2) +#define INV_ICM42600_SENSOR_CONFIG0_YA_DISABLE BIT(1) +#define INV_ICM42600_SENSOR_CONFIG0_XA_DISABLE BIT(0) + +/* Timestamp value is 20 bits (3 registers) in little-endian */ +#define INV_ICM42600_REG_TMSTVAL 0x1062 +#define INV_ICM42600_TMSTVAL_MASK GENMASK(19, 0) + +#define INV_ICM42600_REG_INTF_CONFIG4 0x107A +#define INV_ICM42600_INTF_CONFIG4_I3C_BUS_ONLY BIT(6) +#define INV_ICM42600_INTF_CONFIG4_SPI_AP_4WIRE BIT(1) + +#define INV_ICM42600_REG_INTF_CONFIG6 0x107C +#define INV_ICM42600_INTF_CONFIG6_MASK GENMASK(4, 0) +#define INV_ICM42600_INTF_CONFIG6_I3C_EN BIT(4) +#define INV_ICM42600_INTF_CONFIG6_I3C_IBI_BYTE_EN BIT(3) +#define INV_ICM42600_INTF_CONFIG6_I3C_IBI_EN BIT(2) +#define INV_ICM42600_INTF_CONFIG6_I3C_DDR_EN BIT(1) +#define INV_ICM42600_INTF_CONFIG6_I3C_SDR_EN BIT(0) + +/* User bank 4 (MSB 0x40) */ +#define INV_ICM42600_REG_ACCEL_WOM_X_THR 0x404A +#define INV_ICM42600_REG_ACCEL_WOM_Y_THR 0x404B +#define INV_ICM42600_REG_ACCEL_WOM_Z_THR 0x404C + +#define INV_ICM42600_REG_INT_SOURCE8 0x404F +#define INV_ICM42600_INT_SOURCE8_FSYNC_IBI_EN BIT(5) +#define INV_ICM42600_INT_SOURCE8_PLL_RDY_IBI_EN BIT(4) +#define INV_ICM42600_INT_SOURCE8_UI_DRDY_IBI_EN BIT(3) +#define INV_ICM42600_INT_SOURCE8_FIFO_THS_IBI_EN BIT(2) +#define INV_ICM42600_INT_SOURCE8_FIFO_FULL_IBI_EN BIT(1) +#define INV_ICM42600_INT_SOURCE8_AGC_RDY_IBI_EN BIT(0) + +#define INV_ICM42600_REG_OFFSET_USER0 0x4077 +#define INV_ICM42600_REG_OFFSET_USER1 0x4078 +#define INV_ICM42600_REG_OFFSET_USER2 0x4079 +#define INV_ICM42600_REG_OFFSET_USER3 0x407A +#define INV_ICM42600_REG_OFFSET_USER4 0x407B +#define INV_ICM42600_REG_OFFSET_USER5 0x407C +#define INV_ICM42600_REG_OFFSET_USER6 0x407D +#define INV_ICM42600_REG_OFFSET_USER7 0x407E +#define INV_ICM42600_REG_OFFSET_USER8 0x407F + +/* Sleep times required by the driver */ +#define INV_ICM42600_POWER_UP_TIME_MS 100 +#define INV_ICM42600_RESET_TIME_MS 1 +#define INV_ICM42600_ACCEL_STARTUP_TIME_MS 20 +#define INV_ICM42600_GYRO_STARTUP_TIME_MS 60 +#define INV_ICM42600_GYRO_STOP_TIME_MS 150 +#define INV_ICM42600_TEMP_STARTUP_TIME_MS 14 +#define INV_ICM42600_SUSPEND_DELAY_MS 2000 + +typedef int (*inv_icm42600_bus_setup)(struct inv_icm42600_state *); + +extern const struct regmap_config inv_icm42600_regmap_config; +extern const struct regmap_config inv_icm42600_spi_regmap_config; +extern const struct dev_pm_ops inv_icm42600_pm_ops; + +const struct iio_mount_matrix * +inv_icm42600_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan); + +u32 inv_icm42600_odr_to_period(enum inv_icm42600_odr odr); + +int inv_icm42600_set_accel_conf(struct inv_icm42600_state *st, + struct inv_icm42600_sensor_conf *conf, + unsigned int *sleep_ms); + +int inv_icm42600_set_gyro_conf(struct inv_icm42600_state *st, + struct inv_icm42600_sensor_conf *conf, + unsigned int *sleep_ms); + +int inv_icm42600_set_temp_conf(struct inv_icm42600_state *st, bool enable, + unsigned int *sleep_ms); + +int inv_icm42600_enable_wom(struct inv_icm42600_state *st); +int inv_icm42600_disable_wom(struct inv_icm42600_state *st); + +int inv_icm42600_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval); + +int inv_icm42600_core_probe(struct regmap *regmap, int chip, + inv_icm42600_bus_setup bus_setup); + +struct iio_dev *inv_icm42600_gyro_init(struct inv_icm42600_state *st); + +int inv_icm42600_gyro_parse_fifo(struct iio_dev *indio_dev); + +struct iio_dev *inv_icm42600_accel_init(struct inv_icm42600_state *st); + +int inv_icm42600_accel_parse_fifo(struct iio_dev *indio_dev); + +void inv_icm42600_accel_handle_events(struct iio_dev *indio_dev, + unsigned int status2, unsigned int status3, + s64 timestamp); + +#endif diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c new file mode 100644 index 000000000..4b0e3cd8a --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c @@ -0,0 +1,1247 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Invensense, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "inv_icm42600.h" +#include "inv_icm42600_buffer.h" +#include "inv_icm42600_temp.h" + +#define INV_ICM42600_ACCEL_CHAN(_modifier, _index, _ext_info) \ + { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = _modifier, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_all = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + .ext_info = _ext_info, \ + } + +#define INV_ICM42600_ACCEL_EVENT_CHAN(_modifier, _events, _events_nb) \ + { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = _modifier, \ + .event_spec = _events, \ + .num_event_specs = _events_nb, \ + .scan_index = -1, \ + } + +enum inv_icm42600_accel_scan { + INV_ICM42600_ACCEL_SCAN_X, + INV_ICM42600_ACCEL_SCAN_Y, + INV_ICM42600_ACCEL_SCAN_Z, + INV_ICM42600_ACCEL_SCAN_TEMP, + INV_ICM42600_ACCEL_SCAN_TIMESTAMP, +}; + +static const char * const inv_icm42600_accel_power_mode_items[] = { + "low-noise", + "low-power", +}; +static const int inv_icm42600_accel_power_mode_values[] = { + INV_ICM42600_SENSOR_MODE_LOW_NOISE, + INV_ICM42600_SENSOR_MODE_LOW_POWER, +}; +static const int inv_icm42600_accel_filter_values[] = { + INV_ICM42600_FILTER_BW_ODR_DIV_2, + INV_ICM42600_FILTER_AVG_16X, +}; + +static int inv_icm42600_accel_power_mode_set(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int idx) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_sensor_state *accel_st = iio_priv(indio_dev); + int power_mode, filter; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + if (idx >= ARRAY_SIZE(inv_icm42600_accel_power_mode_values)) + return -EINVAL; + + power_mode = inv_icm42600_accel_power_mode_values[idx]; + filter = inv_icm42600_accel_filter_values[idx]; + + guard(mutex)(&st->lock); + + /* cannot change if accel sensor is on */ + if (st->conf.accel.mode != INV_ICM42600_SENSOR_MODE_OFF) + return -EBUSY; + + /* prevent change if power mode is not supported by the ODR */ + switch (power_mode) { + case INV_ICM42600_SENSOR_MODE_LOW_NOISE: + if (st->conf.accel.odr >= INV_ICM42600_ODR_6_25HZ_LP && + st->conf.accel.odr <= INV_ICM42600_ODR_1_5625HZ_LP) + return -EPERM; + break; + case INV_ICM42600_SENSOR_MODE_LOW_POWER: + default: + if (st->conf.accel.odr <= INV_ICM42600_ODR_1KHZ_LN) + return -EPERM; + break; + } + + accel_st->power_mode = power_mode; + accel_st->filter = filter; + + return 0; +} + +static int inv_icm42600_accel_power_mode_get(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_sensor_state *accel_st = iio_priv(indio_dev); + unsigned int idx; + int power_mode; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + guard(mutex)(&st->lock); + + /* if sensor is on, returns actual power mode and not configured one */ + switch (st->conf.accel.mode) { + case INV_ICM42600_SENSOR_MODE_LOW_POWER: + case INV_ICM42600_SENSOR_MODE_LOW_NOISE: + power_mode = st->conf.accel.mode; + break; + default: + power_mode = accel_st->power_mode; + break; + } + + for (idx = 0; idx < ARRAY_SIZE(inv_icm42600_accel_power_mode_values); ++idx) { + if (power_mode == inv_icm42600_accel_power_mode_values[idx]) + break; + } + if (idx >= ARRAY_SIZE(inv_icm42600_accel_power_mode_values)) + return -EINVAL; + + return idx; +} + +static const struct iio_enum inv_icm42600_accel_power_mode_enum = { + .items = inv_icm42600_accel_power_mode_items, + .num_items = ARRAY_SIZE(inv_icm42600_accel_power_mode_items), + .set = inv_icm42600_accel_power_mode_set, + .get = inv_icm42600_accel_power_mode_get, +}; + +static const struct iio_chan_spec_ext_info inv_icm42600_accel_ext_infos[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, inv_icm42600_get_mount_matrix), + IIO_ENUM_AVAILABLE("power_mode", IIO_SHARED_BY_TYPE, + &inv_icm42600_accel_power_mode_enum), + IIO_ENUM("power_mode", IIO_SHARED_BY_TYPE, + &inv_icm42600_accel_power_mode_enum), + { } +}; + +/* WoM event: rising ROC */ +static const struct iio_event_spec inv_icm42600_wom_events[] = { + { + .type = IIO_EV_TYPE_ROC, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE), + }, +}; + +static const struct iio_chan_spec inv_icm42600_accel_channels[] = { + INV_ICM42600_ACCEL_CHAN(IIO_MOD_X, INV_ICM42600_ACCEL_SCAN_X, + inv_icm42600_accel_ext_infos), + INV_ICM42600_ACCEL_CHAN(IIO_MOD_Y, INV_ICM42600_ACCEL_SCAN_Y, + inv_icm42600_accel_ext_infos), + INV_ICM42600_ACCEL_CHAN(IIO_MOD_Z, INV_ICM42600_ACCEL_SCAN_Z, + inv_icm42600_accel_ext_infos), + INV_ICM42600_TEMP_CHAN(INV_ICM42600_ACCEL_SCAN_TEMP), + IIO_CHAN_SOFT_TIMESTAMP(INV_ICM42600_ACCEL_SCAN_TIMESTAMP), + INV_ICM42600_ACCEL_EVENT_CHAN(IIO_MOD_X_OR_Y_OR_Z, inv_icm42600_wom_events, + ARRAY_SIZE(inv_icm42600_wom_events)), +}; + +/* + * IIO buffer data: size must be a power of 2 and timestamp aligned + * 16 bytes: 6 bytes acceleration, 2 bytes temperature, 8 bytes timestamp + */ +struct inv_icm42600_accel_buffer { + struct inv_icm42600_fifo_sensor_data accel; + s16 temp; + aligned_s64 timestamp; +}; + +#define INV_ICM42600_SCAN_MASK_ACCEL_3AXIS \ + (BIT(INV_ICM42600_ACCEL_SCAN_X) | \ + BIT(INV_ICM42600_ACCEL_SCAN_Y) | \ + BIT(INV_ICM42600_ACCEL_SCAN_Z)) + +#define INV_ICM42600_SCAN_MASK_TEMP BIT(INV_ICM42600_ACCEL_SCAN_TEMP) + +static const unsigned long inv_icm42600_accel_scan_masks[] = { + /* 3-axis accel + temperature */ + INV_ICM42600_SCAN_MASK_ACCEL_3AXIS | INV_ICM42600_SCAN_MASK_TEMP, + 0, +}; + +/* enable accelerometer sensor and FIFO write */ +static int inv_icm42600_accel_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_sensor_state *accel_st = iio_priv(indio_dev); + struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; + unsigned int fifo_en = 0; + unsigned int sleep_temp = 0; + unsigned int sleep_accel = 0; + unsigned int sleep; + int ret; + + mutex_lock(&st->lock); + + if (*scan_mask & INV_ICM42600_SCAN_MASK_TEMP) { + /* enable temp sensor */ + ret = inv_icm42600_set_temp_conf(st, true, &sleep_temp); + if (ret) + goto out_unlock; + fifo_en |= INV_ICM42600_SENSOR_TEMP; + } + + if (*scan_mask & INV_ICM42600_SCAN_MASK_ACCEL_3AXIS) { + /* enable accel sensor */ + conf.mode = accel_st->power_mode; + conf.filter = accel_st->filter; + ret = inv_icm42600_set_accel_conf(st, &conf, &sleep_accel); + if (ret) + goto out_unlock; + fifo_en |= INV_ICM42600_SENSOR_ACCEL; + } + + /* update data FIFO write */ + ret = inv_icm42600_buffer_set_fifo_en(st, fifo_en | st->fifo.en); + +out_unlock: + mutex_unlock(&st->lock); + /* sleep maximum required time */ + sleep = max(sleep_accel, sleep_temp); + if (sleep) + msleep(sleep); + return ret; +} + +static int inv_icm42600_accel_read_sensor(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + s16 *val) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_sensor_state *accel_st = iio_priv(indio_dev); + struct device *dev = regmap_get_device(st->map); + struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; + unsigned int reg; + __be16 *data; + int ret; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM42600_REG_ACCEL_DATA_X; + break; + case IIO_MOD_Y: + reg = INV_ICM42600_REG_ACCEL_DATA_Y; + break; + case IIO_MOD_Z: + reg = INV_ICM42600_REG_ACCEL_DATA_Z; + break; + default: + return -EINVAL; + } + + pm_runtime_get_sync(dev); + mutex_lock(&st->lock); + + /* enable accel sensor */ + conf.mode = accel_st->power_mode; + conf.filter = accel_st->filter; + ret = inv_icm42600_set_accel_conf(st, &conf, NULL); + if (ret) + goto exit; + + /* read accel register data */ + data = (__be16 *)&st->buffer[0]; + ret = regmap_bulk_read(st->map, reg, data, sizeof(*data)); + if (ret) + goto exit; + + *val = (s16)be16_to_cpup(data); + if (*val == INV_ICM42600_DATA_INVALID) + ret = -EINVAL; +exit: + mutex_unlock(&st->lock); + pm_runtime_put_autosuspend(dev); + return ret; +} + +static unsigned int inv_icm42600_accel_convert_roc_to_wom(u64 roc, + int accel_hz, int accel_uhz) +{ + /* 1000/256mg per LSB converted in µm/s² */ + const unsigned int convert = (9807U * (MICRO / MILLI)) / 256U; + u64 value; + u64 freq_uhz; + + /* return 0 only if roc is 0 */ + if (roc == 0) + return 0; + + freq_uhz = (u64)accel_hz * MICRO + (u64)accel_uhz; + value = div64_u64(roc * MICRO, freq_uhz * (u64)convert); + + /* limit value to 8 bits and prevent 0 */ + return clamp(value, 1, 255); +} + +static u64 inv_icm42600_accel_convert_wom_to_roc(unsigned int threshold, + int accel_hz, int accel_uhz) +{ + /* 1000/256mg per LSB converted in µm/s² */ + const unsigned int convert = (9807U * (MICRO / MILLI)) / 256U; + u64 value; + u64 freq_uhz; + + value = threshold * convert; + freq_uhz = (u64)accel_hz * MICRO + (u64)accel_uhz; + + /* compute the differential by multiplying by the frequency */ + return div_u64(value * freq_uhz, MICRO); +} + +static int inv_icm42600_accel_set_wom_threshold(struct inv_icm42600_state *st, + u64 value, + int accel_hz, int accel_uhz) +{ + unsigned int threshold; + int ret; + + /* convert roc to wom threshold and convert back to handle clipping */ + threshold = inv_icm42600_accel_convert_roc_to_wom(value, accel_hz, accel_uhz); + value = inv_icm42600_accel_convert_wom_to_roc(threshold, accel_hz, accel_uhz); + + dev_dbg(regmap_get_device(st->map), "wom_threshold: 0x%x\n", threshold); + + /* set accel WoM threshold for the 3 axes */ + st->buffer[0] = threshold; + st->buffer[1] = threshold; + st->buffer[2] = threshold; + ret = regmap_bulk_write(st->map, INV_ICM42600_REG_ACCEL_WOM_X_THR, st->buffer, 3); + if (ret) + return ret; + + st->apex.wom.value = value; + + return 0; +} + +static int _inv_icm42600_accel_enable_wom(struct iio_dev *indio_dev) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_sensor_state *accel_st = iio_priv(indio_dev); + struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; + unsigned int sleep_ms = 0; + int ret; + + scoped_guard(mutex, &st->lock) { + /* turn on accel sensor */ + conf.mode = accel_st->power_mode; + conf.filter = accel_st->filter; + ret = inv_icm42600_set_accel_conf(st, &conf, &sleep_ms); + if (ret) + return ret; + } + + if (sleep_ms) + msleep(sleep_ms); + + scoped_guard(mutex, &st->lock) { + ret = inv_icm42600_enable_wom(st); + if (ret) + return ret; + st->apex.on++; + st->apex.wom.enable = true; + } + + return 0; +} + +static int inv_icm42600_accel_enable_wom(struct iio_dev *indio_dev) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct device *pdev = regmap_get_device(st->map); + int ret; + + ret = pm_runtime_resume_and_get(pdev); + if (ret) + return ret; + + ret = _inv_icm42600_accel_enable_wom(indio_dev); + if (ret) { + pm_runtime_mark_last_busy(pdev); + pm_runtime_put_autosuspend(pdev); + return ret; + } + + return 0; +} + +static int _inv_icm42600_accel_disable_wom(struct iio_dev *indio_dev) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; + unsigned int sleep_ms = 0; + int ret; + + scoped_guard(mutex, &st->lock) { + /* + * Consider that turning off WoM is always working to avoid + * blocking the chip in on mode and prevent going back to sleep. + * If there is an error, the chip will anyway go back to sleep + * and the feature will not work anymore. + */ + st->apex.wom.enable = false; + st->apex.on--; + ret = inv_icm42600_disable_wom(st); + if (ret) + return ret; + /* turn off accel sensor if not used */ + if (!st->apex.on && !iio_buffer_enabled(indio_dev)) { + conf.mode = INV_ICM42600_SENSOR_MODE_OFF; + ret = inv_icm42600_set_accel_conf(st, &conf, &sleep_ms); + if (ret) + return ret; + } + } + + if (sleep_ms) + msleep(sleep_ms); + + return 0; +} + +static int inv_icm42600_accel_disable_wom(struct iio_dev *indio_dev) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct device *pdev = regmap_get_device(st->map); + int ret; + + ret = _inv_icm42600_accel_disable_wom(indio_dev); + + pm_runtime_mark_last_busy(pdev); + pm_runtime_put_autosuspend(pdev); + + return ret; +} + +void inv_icm42600_accel_handle_events(struct iio_dev *indio_dev, + unsigned int status2, unsigned int status3, + s64 timestamp) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + u64 ev_code; + + /* handle WoM event */ + if (st->apex.wom.enable && (status2 & INV_ICM42600_INT_STATUS2_WOM_INT)) { + ev_code = IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_ROC, IIO_EV_DIR_RISING); + iio_push_event(indio_dev, ev_code, timestamp); + } +} + +/* IIO format int + nano */ +static const int inv_icm42600_accel_scale[] = { + /* +/- 16G => 0.004788403 m/s-2 */ + [2 * INV_ICM42600_ACCEL_FS_16G] = 0, + [2 * INV_ICM42600_ACCEL_FS_16G + 1] = 4788403, + /* +/- 8G => 0.002394202 m/s-2 */ + [2 * INV_ICM42600_ACCEL_FS_8G] = 0, + [2 * INV_ICM42600_ACCEL_FS_8G + 1] = 2394202, + /* +/- 4G => 0.001197101 m/s-2 */ + [2 * INV_ICM42600_ACCEL_FS_4G] = 0, + [2 * INV_ICM42600_ACCEL_FS_4G + 1] = 1197101, + /* +/- 2G => 0.000598550 m/s-2 */ + [2 * INV_ICM42600_ACCEL_FS_2G] = 0, + [2 * INV_ICM42600_ACCEL_FS_2G + 1] = 598550, +}; +static const int inv_icm42686_accel_scale[] = { + /* +/- 32G => 0.009576807 m/s-2 */ + [2 * INV_ICM42686_ACCEL_FS_32G] = 0, + [2 * INV_ICM42686_ACCEL_FS_32G + 1] = 9576807, + /* +/- 16G => 0.004788403 m/s-2 */ + [2 * INV_ICM42686_ACCEL_FS_16G] = 0, + [2 * INV_ICM42686_ACCEL_FS_16G + 1] = 4788403, + /* +/- 8G => 0.002394202 m/s-2 */ + [2 * INV_ICM42686_ACCEL_FS_8G] = 0, + [2 * INV_ICM42686_ACCEL_FS_8G + 1] = 2394202, + /* +/- 4G => 0.001197101 m/s-2 */ + [2 * INV_ICM42686_ACCEL_FS_4G] = 0, + [2 * INV_ICM42686_ACCEL_FS_4G + 1] = 1197101, + /* +/- 2G => 0.000598550 m/s-2 */ + [2 * INV_ICM42686_ACCEL_FS_2G] = 0, + [2 * INV_ICM42686_ACCEL_FS_2G + 1] = 598550, +}; + +static int inv_icm42600_accel_read_scale(struct iio_dev *indio_dev, + int *val, int *val2) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_sensor_state *accel_st = iio_priv(indio_dev); + unsigned int idx; + + idx = st->conf.accel.fs; + + *val = accel_st->scales[2 * idx]; + *val2 = accel_st->scales[2 * idx + 1]; + return IIO_VAL_INT_PLUS_NANO; +} + +static int inv_icm42600_accel_write_scale(struct iio_dev *indio_dev, + int val, int val2) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_sensor_state *accel_st = iio_priv(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; + int ret; + + for (idx = 0; idx < accel_st->scales_len; idx += 2) { + if (val == accel_st->scales[idx] && + val2 == accel_st->scales[idx + 1]) + break; + } + if (idx >= accel_st->scales_len) + return -EINVAL; + + conf.fs = idx / 2; + + pm_runtime_get_sync(dev); + + scoped_guard(mutex, &st->lock) + ret = inv_icm42600_set_accel_conf(st, &conf, NULL); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +/* IIO format int + micro */ +static const int inv_icm42600_accel_odr[] = { + /* 1.5625Hz */ + 1, 562500, + /* 3.125Hz */ + 3, 125000, + /* 6.25Hz */ + 6, 250000, + /* 12.5Hz */ + 12, 500000, + /* 25Hz */ + 25, 0, + /* 50Hz */ + 50, 0, + /* 100Hz */ + 100, 0, + /* 200Hz */ + 200, 0, + /* 1kHz */ + 1000, 0, + /* 2kHz */ + 2000, 0, + /* 4kHz */ + 4000, 0, +}; + +static const int inv_icm42600_accel_odr_conv[] = { + INV_ICM42600_ODR_1_5625HZ_LP, + INV_ICM42600_ODR_3_125HZ_LP, + INV_ICM42600_ODR_6_25HZ_LP, + INV_ICM42600_ODR_12_5HZ, + INV_ICM42600_ODR_25HZ, + INV_ICM42600_ODR_50HZ, + INV_ICM42600_ODR_100HZ, + INV_ICM42600_ODR_200HZ, + INV_ICM42600_ODR_1KHZ_LN, + INV_ICM42600_ODR_2KHZ_LN, + INV_ICM42600_ODR_4KHZ_LN, +}; + +static int inv_icm42600_accel_read_odr(struct inv_icm42600_state *st, + int *val, int *val2) +{ + unsigned int odr; + unsigned int i; + + odr = st->conf.accel.odr; + + for (i = 0; i < ARRAY_SIZE(inv_icm42600_accel_odr_conv); ++i) { + if (inv_icm42600_accel_odr_conv[i] == odr) + break; + } + if (i >= ARRAY_SIZE(inv_icm42600_accel_odr_conv)) + return -EINVAL; + + *val = inv_icm42600_accel_odr[2 * i]; + *val2 = inv_icm42600_accel_odr[2 * i + 1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int inv_icm42600_accel_write_odr(struct iio_dev *indio_dev, + int val, int val2) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_sensor_state *accel_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &accel_st->ts; + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; + int ret; + + for (idx = 0; idx < ARRAY_SIZE(inv_icm42600_accel_odr); idx += 2) { + if (val == inv_icm42600_accel_odr[idx] && + val2 == inv_icm42600_accel_odr[idx + 1]) + break; + } + if (idx >= ARRAY_SIZE(inv_icm42600_accel_odr)) + return -EINVAL; + + conf.odr = inv_icm42600_accel_odr_conv[idx / 2]; + if (conf.odr == st->conf.accel.odr) + return 0; + + pm_runtime_get_sync(dev); + mutex_lock(&st->lock); + + ret = inv_sensors_timestamp_update_odr(ts, inv_icm42600_odr_to_period(conf.odr), + iio_buffer_enabled(indio_dev)); + if (ret) + goto out_unlock; + + ret = inv_icm42600_set_accel_conf(st, &conf, NULL); + if (ret) + goto out_unlock; + /* update wom threshold since roc is dependent on sampling frequency */ + ret = inv_icm42600_accel_set_wom_threshold(st, st->apex.wom.value, val, val2); + if (ret) + goto out_unlock; + inv_icm42600_buffer_update_fifo_period(st); + inv_icm42600_buffer_update_watermark(st); + +out_unlock: + mutex_unlock(&st->lock); + pm_runtime_put_autosuspend(dev); + + return ret; +} + +/* + * Calibration bias values, IIO range format int + micro. + * Value is limited to +/-1g coded on 12 bits signed. Step is 0.5mg. + */ +static int inv_icm42600_accel_calibbias[] = { + -10, 42010, /* min: -10.042010 m/s² */ + 0, 4903, /* step: 0.004903 m/s² */ + 10, 37106, /* max: 10.037106 m/s² */ +}; + +static int inv_icm42600_accel_read_offset(struct inv_icm42600_state *st, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + struct device *dev = regmap_get_device(st->map); + s64 val64; + s32 bias; + unsigned int reg; + s16 offset; + u8 data[2]; + int ret; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM42600_REG_OFFSET_USER4; + break; + case IIO_MOD_Y: + reg = INV_ICM42600_REG_OFFSET_USER6; + break; + case IIO_MOD_Z: + reg = INV_ICM42600_REG_OFFSET_USER7; + break; + default: + return -EINVAL; + } + + pm_runtime_get_sync(dev); + mutex_lock(&st->lock); + + ret = regmap_bulk_read(st->map, reg, st->buffer, sizeof(data)); + memcpy(data, st->buffer, sizeof(data)); + + mutex_unlock(&st->lock); + pm_runtime_put_autosuspend(dev); + if (ret) + return ret; + + /* 12 bits signed value */ + switch (chan->channel2) { + case IIO_MOD_X: + offset = sign_extend32(((data[0] & 0xF0) << 4) | data[1], 11); + break; + case IIO_MOD_Y: + offset = sign_extend32(((data[1] & 0x0F) << 8) | data[0], 11); + break; + case IIO_MOD_Z: + offset = sign_extend32(((data[0] & 0xF0) << 4) | data[1], 11); + break; + default: + return -EINVAL; + } + + /* + * convert raw offset to g then to m/s² + * 12 bits signed raw step 0.5mg to g: 5 / 10000 + * g to m/s²: 9.806650 + * result in micro (1000000) + * (offset * 5 * 9.806650 * 1000000) / 10000 + */ + val64 = (s64)offset * 5LL * 9806650LL; + /* for rounding, add + or - divisor (10000) divided by 2 */ + if (val64 >= 0) + val64 += 10000LL / 2LL; + else + val64 -= 10000LL / 2LL; + bias = div_s64(val64, 10000L); + *val = bias / 1000000L; + *val2 = bias % 1000000L; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int inv_icm42600_accel_write_offset(struct inv_icm42600_state *st, + struct iio_chan_spec const *chan, + int val, int val2) +{ + struct device *dev = regmap_get_device(st->map); + s64 val64; + s32 min, max; + unsigned int reg, regval; + s16 offset; + int ret; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM42600_REG_OFFSET_USER4; + break; + case IIO_MOD_Y: + reg = INV_ICM42600_REG_OFFSET_USER6; + break; + case IIO_MOD_Z: + reg = INV_ICM42600_REG_OFFSET_USER7; + break; + default: + return -EINVAL; + } + + /* inv_icm42600_accel_calibbias: min - step - max in micro */ + min = inv_icm42600_accel_calibbias[0] * 1000000L + + inv_icm42600_accel_calibbias[1]; + max = inv_icm42600_accel_calibbias[4] * 1000000L + + inv_icm42600_accel_calibbias[5]; + val64 = (s64)val * 1000000LL + (s64)val2; + if (val64 < min || val64 > max) + return -EINVAL; + + /* + * convert m/s² to g then to raw value + * m/s² to g: 1 / 9.806650 + * g to raw 12 bits signed, step 0.5mg: 10000 / 5 + * val in micro (1000000) + * val * 10000 / (9.806650 * 1000000 * 5) + */ + val64 = val64 * 10000LL; + /* for rounding, add + or - divisor (9806650 * 5) divided by 2 */ + if (val64 >= 0) + val64 += 9806650 * 5 / 2; + else + val64 -= 9806650 * 5 / 2; + offset = div_s64(val64, 9806650 * 5); + + /* clamp value limited to 12 bits signed */ + if (offset < -2048) + offset = -2048; + else if (offset > 2047) + offset = 2047; + + pm_runtime_get_sync(dev); + mutex_lock(&st->lock); + + switch (chan->channel2) { + case IIO_MOD_X: + /* OFFSET_USER4 register is shared */ + ret = regmap_read(st->map, INV_ICM42600_REG_OFFSET_USER4, + ®val); + if (ret) + goto out_unlock; + st->buffer[0] = ((offset & 0xF00) >> 4) | (regval & 0x0F); + st->buffer[1] = offset & 0xFF; + break; + case IIO_MOD_Y: + /* OFFSET_USER7 register is shared */ + ret = regmap_read(st->map, INV_ICM42600_REG_OFFSET_USER7, + ®val); + if (ret) + goto out_unlock; + st->buffer[0] = offset & 0xFF; + st->buffer[1] = ((offset & 0xF00) >> 8) | (regval & 0xF0); + break; + case IIO_MOD_Z: + /* OFFSET_USER7 register is shared */ + ret = regmap_read(st->map, INV_ICM42600_REG_OFFSET_USER7, + ®val); + if (ret) + goto out_unlock; + st->buffer[0] = ((offset & 0xF00) >> 4) | (regval & 0x0F); + st->buffer[1] = offset & 0xFF; + break; + default: + ret = -EINVAL; + goto out_unlock; + } + + ret = regmap_bulk_write(st->map, reg, st->buffer, 2); + +out_unlock: + mutex_unlock(&st->lock); + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int inv_icm42600_accel_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + s16 data; + int ret; + + switch (chan->type) { + case IIO_ACCEL: + break; + case IIO_TEMP: + return inv_icm42600_temp_read_raw(indio_dev, chan, val, val2, mask); + default: + return -EINVAL; + } + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = inv_icm42600_accel_read_sensor(indio_dev, chan, &data); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + *val = data; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + return inv_icm42600_accel_read_scale(indio_dev, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return inv_icm42600_accel_read_odr(st, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + return inv_icm42600_accel_read_offset(st, chan, val, val2); + default: + return -EINVAL; + } +} + +static int inv_icm42600_accel_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, + int *type, int *length, long mask) +{ + struct inv_icm42600_sensor_state *accel_st = iio_priv(indio_dev); + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = accel_st->scales; + *type = IIO_VAL_INT_PLUS_NANO; + *length = accel_st->scales_len; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = inv_icm42600_accel_odr; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = ARRAY_SIZE(inv_icm42600_accel_odr); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_CALIBBIAS: + *vals = inv_icm42600_accel_calibbias; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_RANGE; + default: + return -EINVAL; + } +} + +static int inv_icm42600_accel_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = inv_icm42600_accel_write_scale(indio_dev, val, val2); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + return inv_icm42600_accel_write_odr(indio_dev, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = inv_icm42600_accel_write_offset(st, chan, val, val2); + iio_device_release_direct(indio_dev); + return ret; + default: + return -EINVAL; + } +} + +static int inv_icm42600_accel_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBBIAS: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int inv_icm42600_accel_hwfifo_set_watermark(struct iio_dev *indio_dev, + unsigned int val) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + + guard(mutex)(&st->lock); + + st->fifo.watermark.accel = val; + return inv_icm42600_buffer_update_watermark(st); +} + +static int inv_icm42600_accel_hwfifo_flush(struct iio_dev *indio_dev, + unsigned int count) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + if (count == 0) + return 0; + + guard(mutex)(&st->lock); + + ret = inv_icm42600_buffer_hwfifo_flush(st, count); + if (ret) + return ret; + + return st->fifo.nb.accel; +} + +static int inv_icm42600_accel_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + + /* handle only WoM (roc rising) event */ + if (type != IIO_EV_TYPE_ROC || dir != IIO_EV_DIR_RISING) + return -EINVAL; + + guard(mutex)(&st->lock); + + return st->apex.wom.enable ? 1 : 0; +} + +static int inv_icm42600_accel_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + + /* handle only WoM (roc rising) event */ + if (type != IIO_EV_TYPE_ROC || dir != IIO_EV_DIR_RISING) + return -EINVAL; + + scoped_guard(mutex, &st->lock) { + if (st->apex.wom.enable == state) + return 0; + } + + if (state) + return inv_icm42600_accel_enable_wom(indio_dev); + + return inv_icm42600_accel_disable_wom(indio_dev); +} + +static int inv_icm42600_accel_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + u32 rem; + + /* handle only WoM (roc rising) event value */ + if (type != IIO_EV_TYPE_ROC || dir != IIO_EV_DIR_RISING) + return -EINVAL; + + guard(mutex)(&st->lock); + + /* return value in micro */ + *val = div_u64_rem(st->apex.wom.value, MICRO, &rem); + *val2 = rem; + return IIO_VAL_INT_PLUS_MICRO; +} + +static int _inv_icm42600_accel_wom_value(struct inv_icm42600_state *st, + int val, int val2) +{ + u64 value; + unsigned int accel_hz, accel_uhz; + int ret; + + guard(mutex)(&st->lock); + + ret = inv_icm42600_accel_read_odr(st, &accel_hz, &accel_uhz); + if (ret < 0) + return ret; + + value = (u64)val * MICRO + (u64)val2; + + return inv_icm42600_accel_set_wom_threshold(st, value, + accel_hz, accel_uhz); +} + +static int inv_icm42600_accel_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct device *dev = regmap_get_device(st->map); + int ret; + + /* handle only WoM (roc rising) event value */ + if (type != IIO_EV_TYPE_ROC || dir != IIO_EV_DIR_RISING) + return -EINVAL; + + if (val < 0 || val2 < 0) + return -EINVAL; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = _inv_icm42600_accel_wom_value(st, val, val2); + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static const struct iio_info inv_icm42600_accel_info = { + .read_raw = inv_icm42600_accel_read_raw, + .read_avail = inv_icm42600_accel_read_avail, + .write_raw = inv_icm42600_accel_write_raw, + .write_raw_get_fmt = inv_icm42600_accel_write_raw_get_fmt, + .debugfs_reg_access = inv_icm42600_debugfs_reg, + .update_scan_mode = inv_icm42600_accel_update_scan_mode, + .hwfifo_set_watermark = inv_icm42600_accel_hwfifo_set_watermark, + .hwfifo_flush_to_buffer = inv_icm42600_accel_hwfifo_flush, + .read_event_config = inv_icm42600_accel_read_event_config, + .write_event_config = inv_icm42600_accel_write_event_config, + .read_event_value = inv_icm42600_accel_read_event_value, + .write_event_value = inv_icm42600_accel_write_event_value, +}; + +struct iio_dev *inv_icm42600_accel_init(struct inv_icm42600_state *st) +{ + struct device *dev = regmap_get_device(st->map); + const char *name; + struct inv_icm42600_sensor_state *accel_st; + struct inv_sensors_timestamp_chip ts_chip; + struct iio_dev *indio_dev; + int ret; + + name = devm_kasprintf(dev, GFP_KERNEL, "%s-accel", st->name); + if (!name) + return ERR_PTR(-ENOMEM); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*accel_st)); + if (!indio_dev) + return ERR_PTR(-ENOMEM); + accel_st = iio_priv(indio_dev); + + switch (st->chip) { + case INV_CHIP_ICM42686: + accel_st->scales = inv_icm42686_accel_scale; + accel_st->scales_len = ARRAY_SIZE(inv_icm42686_accel_scale); + break; + default: + accel_st->scales = inv_icm42600_accel_scale; + accel_st->scales_len = ARRAY_SIZE(inv_icm42600_accel_scale); + break; + } + /* low-power by default at init */ + accel_st->power_mode = INV_ICM42600_SENSOR_MODE_LOW_POWER; + accel_st->filter = INV_ICM42600_FILTER_AVG_16X; + + /* + * clock period is 8kHz (125000ns) + * jitter is +/- 2% (20 per mille) + */ + ts_chip.clock_period = 125000; + ts_chip.jitter = 20; + ts_chip.init_period = inv_icm42600_odr_to_period(st->conf.accel.odr); + inv_sensors_timestamp_init(&accel_st->ts, &ts_chip); + + iio_device_set_drvdata(indio_dev, st); + indio_dev->name = name; + indio_dev->info = &inv_icm42600_accel_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = inv_icm42600_accel_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_icm42600_accel_channels); + indio_dev->available_scan_masks = inv_icm42600_accel_scan_masks; + + ret = devm_iio_kfifo_buffer_setup_ext(dev, indio_dev, + &inv_icm42600_buffer_ops, + inv_icm42600_buffer_attrs); + if (ret) + return ERR_PTR(ret); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ERR_PTR(ret); + + /* accel events are wakeup capable */ + ret = devm_device_init_wakeup(&indio_dev->dev); + if (ret) + return ERR_PTR(ret); + + return indio_dev; +} + +int inv_icm42600_accel_parse_fifo(struct iio_dev *indio_dev) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_sensor_state *accel_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &accel_st->ts; + ssize_t i, size; + unsigned int no; + const void *accel, *gyro, *timestamp; + const s8 *temp; + unsigned int odr; + int64_t ts_val; + /* buffer is copied to userspace, zeroing it to avoid any data leak */ + struct inv_icm42600_accel_buffer buffer = { }; + + /* parse all fifo packets */ + for (i = 0, no = 0; i < st->fifo.count; i += size, ++no) { + size = inv_icm42600_fifo_decode_packet(&st->fifo.data[i], + &accel, &gyro, &temp, ×tamp, &odr); + /* quit if error or FIFO is empty */ + if (size <= 0) + return size; + + /* skip packet if no accel data or data is invalid */ + if (accel == NULL || !inv_icm42600_fifo_is_data_valid(accel)) + continue; + + /* update odr */ + if (odr & INV_ICM42600_SENSOR_ACCEL) + inv_sensors_timestamp_apply_odr(ts, st->fifo.period, + st->fifo.nb.total, no); + + memcpy(&buffer.accel, accel, sizeof(buffer.accel)); + /* convert 8 bits FIFO temperature in high resolution format */ + buffer.temp = temp ? (*temp * 64) : 0; + ts_val = inv_sensors_timestamp_pop(ts); + iio_push_to_buffers_with_timestamp(indio_dev, &buffer, ts_val); + } + + return 0; +} diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c new file mode 100644 index 000000000..998d312f7 --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c @@ -0,0 +1,636 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Invensense, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "inv_icm42600.h" +#include "inv_icm42600_buffer.h" + +/* FIFO header: 1 byte */ +#define INV_ICM42600_FIFO_HEADER_MSG BIT(7) +#define INV_ICM42600_FIFO_HEADER_ACCEL BIT(6) +#define INV_ICM42600_FIFO_HEADER_GYRO BIT(5) +#define INV_ICM42600_FIFO_HEADER_TMST_FSYNC GENMASK(3, 2) +#define INV_ICM42600_FIFO_HEADER_ODR_ACCEL BIT(1) +#define INV_ICM42600_FIFO_HEADER_ODR_GYRO BIT(0) + +struct inv_icm42600_fifo_1sensor_packet { + u8 header; + struct inv_icm42600_fifo_sensor_data data; + s8 temp; +} __packed; +#define INV_ICM42600_FIFO_1SENSOR_PACKET_SIZE 8 + +struct inv_icm42600_fifo_2sensors_packet { + u8 header; + struct inv_icm42600_fifo_sensor_data accel; + struct inv_icm42600_fifo_sensor_data gyro; + s8 temp; + __be16 timestamp; +} __packed; +#define INV_ICM42600_FIFO_2SENSORS_PACKET_SIZE 16 + +ssize_t inv_icm42600_fifo_decode_packet(const void *packet, const void **accel, + const void **gyro, const s8 **temp, + const void **timestamp, unsigned int *odr) +{ + const struct inv_icm42600_fifo_1sensor_packet *pack1 = packet; + const struct inv_icm42600_fifo_2sensors_packet *pack2 = packet; + u8 header = *((const u8 *)packet); + + /* FIFO empty */ + if (header & INV_ICM42600_FIFO_HEADER_MSG) { + *accel = NULL; + *gyro = NULL; + *temp = NULL; + *timestamp = NULL; + *odr = 0; + return 0; + } + + /* handle odr flags */ + *odr = 0; + if (header & INV_ICM42600_FIFO_HEADER_ODR_GYRO) + *odr |= INV_ICM42600_SENSOR_GYRO; + if (header & INV_ICM42600_FIFO_HEADER_ODR_ACCEL) + *odr |= INV_ICM42600_SENSOR_ACCEL; + + /* accel + gyro */ + if ((header & INV_ICM42600_FIFO_HEADER_ACCEL) && + (header & INV_ICM42600_FIFO_HEADER_GYRO)) { + *accel = &pack2->accel; + *gyro = &pack2->gyro; + *temp = &pack2->temp; + *timestamp = &pack2->timestamp; + return INV_ICM42600_FIFO_2SENSORS_PACKET_SIZE; + } + + /* accel only */ + if (header & INV_ICM42600_FIFO_HEADER_ACCEL) { + *accel = &pack1->data; + *gyro = NULL; + *temp = &pack1->temp; + *timestamp = NULL; + return INV_ICM42600_FIFO_1SENSOR_PACKET_SIZE; + } + + /* gyro only */ + if (header & INV_ICM42600_FIFO_HEADER_GYRO) { + *accel = NULL; + *gyro = &pack1->data; + *temp = &pack1->temp; + *timestamp = NULL; + return INV_ICM42600_FIFO_1SENSOR_PACKET_SIZE; + } + + /* invalid packet if here */ + return -EINVAL; +} + +void inv_icm42600_buffer_update_fifo_period(struct inv_icm42600_state *st) +{ + u32 period_gyro, period_accel; + + if (st->fifo.en & INV_ICM42600_SENSOR_GYRO) + period_gyro = inv_icm42600_odr_to_period(st->conf.gyro.odr); + else + period_gyro = U32_MAX; + + if (st->fifo.en & INV_ICM42600_SENSOR_ACCEL) + period_accel = inv_icm42600_odr_to_period(st->conf.accel.odr); + else + period_accel = U32_MAX; + + st->fifo.period = min(period_gyro, period_accel); +} + +int inv_icm42600_buffer_set_fifo_en(struct inv_icm42600_state *st, + unsigned int fifo_en) +{ + unsigned int mask, val; + int ret; + + /* update only FIFO EN bits */ + mask = INV_ICM42600_FIFO_CONFIG1_TMST_FSYNC_EN | + INV_ICM42600_FIFO_CONFIG1_TEMP_EN | + INV_ICM42600_FIFO_CONFIG1_GYRO_EN | + INV_ICM42600_FIFO_CONFIG1_ACCEL_EN; + + val = 0; + if (fifo_en & INV_ICM42600_SENSOR_GYRO) + val |= INV_ICM42600_FIFO_CONFIG1_GYRO_EN; + if (fifo_en & INV_ICM42600_SENSOR_ACCEL) + val |= INV_ICM42600_FIFO_CONFIG1_ACCEL_EN; + if (fifo_en & INV_ICM42600_SENSOR_TEMP) + val |= INV_ICM42600_FIFO_CONFIG1_TEMP_EN; + + ret = regmap_update_bits(st->map, INV_ICM42600_REG_FIFO_CONFIG1, mask, val); + if (ret) + return ret; + + st->fifo.en = fifo_en; + inv_icm42600_buffer_update_fifo_period(st); + + return 0; +} + +static size_t inv_icm42600_get_packet_size(unsigned int fifo_en) +{ + size_t packet_size; + + if ((fifo_en & INV_ICM42600_SENSOR_GYRO) && + (fifo_en & INV_ICM42600_SENSOR_ACCEL)) + packet_size = INV_ICM42600_FIFO_2SENSORS_PACKET_SIZE; + else + packet_size = INV_ICM42600_FIFO_1SENSOR_PACKET_SIZE; + + return packet_size; +} + +static unsigned int inv_icm42600_wm_truncate(unsigned int watermark, + size_t packet_size) +{ + size_t wm_size; + unsigned int wm; + + wm_size = watermark * packet_size; + if (wm_size > INV_ICM42600_FIFO_WATERMARK_MAX) + wm_size = INV_ICM42600_FIFO_WATERMARK_MAX; + + wm = wm_size / packet_size; + + return wm; +} + +/** + * inv_icm42600_buffer_update_watermark - update watermark FIFO threshold + * @st: driver internal state + * + * Returns 0 on success, a negative error code otherwise. + * + * FIFO watermark threshold is computed based on the required watermark values + * set for gyro and accel sensors. Since watermark is all about acceptable data + * latency, use the smallest setting between the 2. It means choosing the + * smallest latency but this is not as simple as choosing the smallest watermark + * value. Latency depends on watermark and ODR. It requires several steps: + * 1) compute gyro and accel latencies and choose the smallest value. + * 2) adapt the chosen latency so that it is a multiple of both gyro and accel + * ones. Otherwise it is possible that you don't meet a requirement. (for + * example with gyro @100Hz wm 4 and accel @100Hz with wm 6, choosing the + * value of 4 will not meet accel latency requirement because 6 is not a + * multiple of 4. You need to use the value 2.) + * 3) Since all periods are multiple of each others, watermark is computed by + * dividing this computed latency by the smallest period, which corresponds + * to the FIFO frequency. Beware that this is only true because we are not + * using 500Hz frequency which is not a multiple of the others. + */ +int inv_icm42600_buffer_update_watermark(struct inv_icm42600_state *st) +{ + size_t packet_size, wm_size; + unsigned int wm_gyro, wm_accel, watermark; + u32 period_gyro, period_accel; + u32 latency_gyro, latency_accel, latency; + bool restore; + __le16 raw_wm; + int ret; + + packet_size = inv_icm42600_get_packet_size(st->fifo.en); + + /* compute sensors latency, depending on sensor watermark and odr */ + wm_gyro = inv_icm42600_wm_truncate(st->fifo.watermark.gyro, packet_size); + wm_accel = inv_icm42600_wm_truncate(st->fifo.watermark.accel, packet_size); + /* use us for odr to avoid overflow using 32 bits values */ + period_gyro = inv_icm42600_odr_to_period(st->conf.gyro.odr) / 1000UL; + period_accel = inv_icm42600_odr_to_period(st->conf.accel.odr) / 1000UL; + latency_gyro = period_gyro * wm_gyro; + latency_accel = period_accel * wm_accel; + + /* 0 value for watermark means that the sensor is turned off */ + if (wm_gyro == 0 && wm_accel == 0) + return 0; + + if (latency_gyro == 0) { + watermark = wm_accel; + st->fifo.watermark.eff_accel = wm_accel; + } else if (latency_accel == 0) { + watermark = wm_gyro; + st->fifo.watermark.eff_gyro = wm_gyro; + } else { + /* compute the smallest latency that is a multiple of both */ + if (latency_gyro <= latency_accel) + latency = latency_gyro - (latency_accel % latency_gyro); + else + latency = latency_accel - (latency_gyro % latency_accel); + /* all this works because periods are multiple of each others */ + watermark = latency / min(period_gyro, period_accel); + if (watermark < 1) + watermark = 1; + /* update effective watermark */ + st->fifo.watermark.eff_gyro = latency / period_gyro; + if (st->fifo.watermark.eff_gyro < 1) + st->fifo.watermark.eff_gyro = 1; + st->fifo.watermark.eff_accel = latency / period_accel; + if (st->fifo.watermark.eff_accel < 1) + st->fifo.watermark.eff_accel = 1; + } + + /* compute watermark value in bytes */ + wm_size = watermark * packet_size; + st->fifo.watermark.value = watermark; + + /* changing FIFO watermark requires to turn off watermark interrupt */ + ret = regmap_update_bits_check(st->map, INV_ICM42600_REG_INT_SOURCE0, + INV_ICM42600_INT_SOURCE0_FIFO_THS_INT1_EN, + 0, &restore); + if (ret) + return ret; + + raw_wm = INV_ICM42600_FIFO_WATERMARK_VAL(wm_size); + memcpy(st->buffer, &raw_wm, sizeof(raw_wm)); + ret = regmap_bulk_write(st->map, INV_ICM42600_REG_FIFO_WATERMARK, + st->buffer, sizeof(raw_wm)); + if (ret) + return ret; + + /* restore watermark interrupt */ + if (restore) { + ret = regmap_set_bits(st->map, INV_ICM42600_REG_INT_SOURCE0, + INV_ICM42600_INT_SOURCE0_FIFO_THS_INT1_EN); + if (ret) + return ret; + } + + return 0; +} + +static int inv_icm42600_buffer_preenable(struct iio_dev *indio_dev) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct device *dev = regmap_get_device(st->map); + struct inv_icm42600_sensor_state *sensor_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &sensor_st->ts; + + pm_runtime_get_sync(dev); + + guard(mutex)(&st->lock); + inv_sensors_timestamp_reset(ts); + + return 0; +} + +/* + * update_scan_mode callback is turning sensors on and setting data FIFO enable + * bits. + */ +static int inv_icm42600_buffer_postenable(struct iio_dev *indio_dev) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + guard(mutex)(&st->lock); + + if (st->fifo.on) { + st->fifo.on++; + return 0; + } + + /* set FIFO threshold interrupt */ + ret = regmap_set_bits(st->map, INV_ICM42600_REG_INT_SOURCE0, + INV_ICM42600_INT_SOURCE0_FIFO_THS_INT1_EN); + if (ret) + return ret; + + /* flush FIFO data */ + ret = regmap_write(st->map, INV_ICM42600_REG_SIGNAL_PATH_RESET, + INV_ICM42600_SIGNAL_PATH_RESET_FIFO_FLUSH); + if (ret) + return ret; + + /* set FIFO in streaming mode */ + ret = regmap_write(st->map, INV_ICM42600_REG_FIFO_CONFIG, + INV_ICM42600_FIFO_CONFIG_STREAM); + if (ret) + return ret; + + /* workaround: first read of FIFO count after reset is always 0 */ + ret = regmap_bulk_read(st->map, INV_ICM42600_REG_FIFO_COUNT, st->buffer, 2); + if (ret) + return ret; + + st->fifo.on++; + + return 0; +} + +static int inv_icm42600_buffer_predisable(struct iio_dev *indio_dev) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + guard(mutex)(&st->lock); + + if (st->fifo.on > 1) { + st->fifo.on--; + return 0; + } + + /* set FIFO in bypass mode */ + ret = regmap_write(st->map, INV_ICM42600_REG_FIFO_CONFIG, + INV_ICM42600_FIFO_CONFIG_BYPASS); + if (ret) + return ret; + + /* flush FIFO data */ + ret = regmap_write(st->map, INV_ICM42600_REG_SIGNAL_PATH_RESET, + INV_ICM42600_SIGNAL_PATH_RESET_FIFO_FLUSH); + if (ret) + return ret; + + /* disable FIFO threshold interrupt */ + ret = regmap_clear_bits(st->map, INV_ICM42600_REG_INT_SOURCE0, + INV_ICM42600_INT_SOURCE0_FIFO_THS_INT1_EN); + if (ret) + return ret; + + st->fifo.on--; + + return 0; +} + +static int inv_icm42600_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_sensor_state *sensor_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &sensor_st->ts; + struct device *dev = regmap_get_device(st->map); + unsigned int sensor; + unsigned int *watermark; + struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; + unsigned int sleep_temp = 0; + unsigned int sleep_sensor = 0; + unsigned int sleep; + int ret; + + if (indio_dev == st->indio_gyro) { + sensor = INV_ICM42600_SENSOR_GYRO; + watermark = &st->fifo.watermark.gyro; + } else if (indio_dev == st->indio_accel) { + sensor = INV_ICM42600_SENSOR_ACCEL; + watermark = &st->fifo.watermark.accel; + } else { + return -EINVAL; + } + + mutex_lock(&st->lock); + + inv_sensors_timestamp_apply_odr(ts, 0, 0, 0); + + ret = inv_icm42600_buffer_set_fifo_en(st, st->fifo.en & ~sensor); + if (ret) + goto out_unlock; + + *watermark = 0; + ret = inv_icm42600_buffer_update_watermark(st); + if (ret) + goto out_unlock; + + conf.mode = INV_ICM42600_SENSOR_MODE_OFF; + if (sensor == INV_ICM42600_SENSOR_GYRO) + ret = inv_icm42600_set_gyro_conf(st, &conf, &sleep_sensor); + else if (!st->apex.on) + ret = inv_icm42600_set_accel_conf(st, &conf, &sleep_sensor); + if (ret) + goto out_unlock; + + /* if FIFO is off, turn temperature off */ + if (!st->fifo.on) + ret = inv_icm42600_set_temp_conf(st, false, &sleep_temp); + +out_unlock: + mutex_unlock(&st->lock); + + /* sleep maximum required time */ + sleep = max(sleep_sensor, sleep_temp); + if (sleep) + msleep(sleep); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +const struct iio_buffer_setup_ops inv_icm42600_buffer_ops = { + .preenable = inv_icm42600_buffer_preenable, + .postenable = inv_icm42600_buffer_postenable, + .predisable = inv_icm42600_buffer_predisable, + .postdisable = inv_icm42600_buffer_postdisable, +}; + +static ssize_t hwfifo_watermark_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + unsigned int wm; + + guard(mutex)(&st->lock); + + if (indio_dev == st->indio_accel) + wm = st->fifo.watermark.eff_accel; + else if (indio_dev == st->indio_gyro) + wm = st->fifo.watermark.eff_gyro; + else + return -EINVAL; + + return sysfs_emit(buf, "%u\n", wm); +} + +IIO_STATIC_CONST_DEVICE_ATTR(hwfifo_watermark_min, "1"); +IIO_STATIC_CONST_DEVICE_ATTR(hwfifo_watermark_max, + __stringify(INV_ICM42600_FIFO_WATERMARK_MAX_SAMPLES)); +static IIO_DEVICE_ATTR_RO(hwfifo_watermark, 0); +IIO_STATIC_CONST_DEVICE_ATTR(hwfifo_enabled, "1"); + +const struct iio_dev_attr *inv_icm42600_buffer_attrs[] = { + &iio_dev_attr_hwfifo_watermark_min, + &iio_dev_attr_hwfifo_watermark_max, + &iio_dev_attr_hwfifo_watermark, + &iio_dev_attr_hwfifo_enabled, + NULL +}; + +int inv_icm42600_buffer_fifo_read(struct inv_icm42600_state *st, + unsigned int max) +{ + size_t max_count; + __be16 *raw_fifo_count; + ssize_t i, size; + const void *accel, *gyro, *timestamp; + const s8 *temp; + unsigned int odr; + int ret; + + /* reset all samples counters */ + st->fifo.count = 0; + st->fifo.nb.gyro = 0; + st->fifo.nb.accel = 0; + st->fifo.nb.total = 0; + + /* compute maximum FIFO read size (watermark for max = 0 interrupt case) */ + if (max == 0) + max = st->fifo.watermark.value; + max_count = max * inv_icm42600_get_packet_size(st->fifo.en); + + /* read FIFO count value */ + raw_fifo_count = (__be16 *)st->buffer; + ret = regmap_bulk_read(st->map, INV_ICM42600_REG_FIFO_COUNT, + raw_fifo_count, sizeof(*raw_fifo_count)); + if (ret) + return ret; + st->fifo.count = be16_to_cpup(raw_fifo_count); + + /* check and clamp FIFO count value */ + if (st->fifo.count == 0) + return 0; + if (st->fifo.count > max_count) + st->fifo.count = max_count; + + /* read all FIFO data in internal buffer */ + ret = regmap_noinc_read(st->map, INV_ICM42600_REG_FIFO_DATA, + st->fifo.data, st->fifo.count); + if (ret) + return ret; + + /* compute number of samples for each sensor */ + for (i = 0; i < st->fifo.count; i += size) { + size = inv_icm42600_fifo_decode_packet(&st->fifo.data[i], + &accel, &gyro, &temp, ×tamp, &odr); + if (size <= 0) + break; + if (gyro != NULL && inv_icm42600_fifo_is_data_valid(gyro)) + st->fifo.nb.gyro++; + if (accel != NULL && inv_icm42600_fifo_is_data_valid(accel)) + st->fifo.nb.accel++; + st->fifo.nb.total++; + } + + return 0; +} + +int inv_icm42600_buffer_fifo_parse(struct inv_icm42600_state *st) +{ + struct inv_icm42600_sensor_state *gyro_st = iio_priv(st->indio_gyro); + struct inv_icm42600_sensor_state *accel_st = iio_priv(st->indio_accel); + struct inv_sensors_timestamp *ts; + int ret; + + if (st->fifo.nb.total == 0) + return 0; + + /* handle gyroscope timestamp and FIFO data parsing */ + if (st->fifo.nb.gyro > 0) { + ts = &gyro_st->ts; + inv_sensors_timestamp_interrupt(ts, st->fifo.watermark.eff_gyro, + st->timestamp.gyro); + ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro); + if (ret) + return ret; + } + + /* handle accelerometer timestamp and FIFO data parsing */ + if (st->fifo.nb.accel > 0) { + ts = &accel_st->ts; + inv_sensors_timestamp_interrupt(ts, st->fifo.watermark.eff_accel, + st->timestamp.accel); + ret = inv_icm42600_accel_parse_fifo(st->indio_accel); + if (ret) + return ret; + } + + return 0; +} + +int inv_icm42600_buffer_hwfifo_flush(struct inv_icm42600_state *st, + unsigned int count) +{ + struct inv_icm42600_sensor_state *gyro_st = iio_priv(st->indio_gyro); + struct inv_icm42600_sensor_state *accel_st = iio_priv(st->indio_accel); + struct inv_sensors_timestamp *ts; + s64 gyro_ts, accel_ts; + int ret; + + gyro_ts = iio_get_time_ns(st->indio_gyro); + accel_ts = iio_get_time_ns(st->indio_accel); + + ret = inv_icm42600_buffer_fifo_read(st, count); + if (ret) + return ret; + + if (st->fifo.nb.total == 0) + return 0; + + if (st->fifo.nb.gyro > 0) { + ts = &gyro_st->ts; + inv_sensors_timestamp_interrupt(ts, st->fifo.nb.gyro, gyro_ts); + ret = inv_icm42600_gyro_parse_fifo(st->indio_gyro); + if (ret) + return ret; + } + + if (st->fifo.nb.accel > 0) { + ts = &accel_st->ts; + inv_sensors_timestamp_interrupt(ts, st->fifo.nb.accel, accel_ts); + ret = inv_icm42600_accel_parse_fifo(st->indio_accel); + if (ret) + return ret; + } + + return 0; +} + +int inv_icm42600_buffer_init(struct inv_icm42600_state *st) +{ + unsigned int val; + int ret; + + st->fifo.watermark.eff_gyro = 1; + st->fifo.watermark.eff_accel = 1; + st->fifo.watermark.value = 1; + + /* + * Default FIFO configuration (bits 7 to 5) + * - use invalid value + * - FIFO count in bytes + * - FIFO count in big endian + */ + val = INV_ICM42600_INTF_CONFIG0_FIFO_COUNT_ENDIAN; + ret = regmap_update_bits(st->map, INV_ICM42600_REG_INTF_CONFIG0, + GENMASK(7, 5), val); + if (ret) + return ret; + + /* + * Enable FIFO partial read and continuous watermark interrupt. + * Disable all FIFO EN bits. + */ + val = INV_ICM42600_FIFO_CONFIG1_RESUME_PARTIAL_RD | + INV_ICM42600_FIFO_CONFIG1_WM_GT_TH; + return regmap_update_bits(st->map, INV_ICM42600_REG_FIFO_CONFIG1, + GENMASK(6, 5) | GENMASK(3, 0), val); +} diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h new file mode 100644 index 000000000..e37ee3416 --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020 Invensense, Inc. + */ + +#ifndef INV_ICM42600_BUFFER_H_ +#define INV_ICM42600_BUFFER_H_ + +#include +#include + +struct inv_icm42600_state; + +#define INV_ICM42600_SENSOR_GYRO BIT(0) +#define INV_ICM42600_SENSOR_ACCEL BIT(1) +#define INV_ICM42600_SENSOR_TEMP BIT(2) + +/** + * struct inv_icm42600_fifo - FIFO state variables + * @on: reference counter for FIFO on. + * @en: bits field of INV_ICM42600_SENSOR_* for FIFO EN bits. + * @period: FIFO internal period. + * @watermark: watermark configuration values for accel and gyro. + * @count: number of bytes in the FIFO data buffer. + * @nb: gyro, accel and total samples in the FIFO data buffer. + * @data: FIFO data buffer aligned for DMA (2kB + 32 bytes of read cache). + */ +struct inv_icm42600_fifo { + unsigned int on; + unsigned int en; + u32 period; + struct { + unsigned int gyro; + unsigned int accel; + unsigned int eff_gyro; + unsigned int eff_accel; + unsigned int value; + } watermark; + size_t count; + struct { + size_t gyro; + size_t accel; + size_t total; + } nb; + u8 data[2080] __aligned(IIO_DMA_MINALIGN); +}; + +/* FIFO data packet */ +struct inv_icm42600_fifo_sensor_data { + __be16 x; + __be16 y; + __be16 z; +} __packed; +#define INV_ICM42600_FIFO_DATA_INVALID -32768 + +static inline s16 inv_icm42600_fifo_get_sensor_data(__be16 d) +{ + return be16_to_cpu(d); +} + +static inline bool +inv_icm42600_fifo_is_data_valid(const struct inv_icm42600_fifo_sensor_data *s) +{ + s16 x, y, z; + + x = inv_icm42600_fifo_get_sensor_data(s->x); + y = inv_icm42600_fifo_get_sensor_data(s->y); + z = inv_icm42600_fifo_get_sensor_data(s->z); + + if (x == INV_ICM42600_FIFO_DATA_INVALID && + y == INV_ICM42600_FIFO_DATA_INVALID && + z == INV_ICM42600_FIFO_DATA_INVALID) + return false; + + return true; +} + +ssize_t inv_icm42600_fifo_decode_packet(const void *packet, const void **accel, + const void **gyro, const s8 **temp, + const void **timestamp, unsigned int *odr); + +extern const struct iio_buffer_setup_ops inv_icm42600_buffer_ops; +extern const struct iio_dev_attr *inv_icm42600_buffer_attrs[]; + +int inv_icm42600_buffer_init(struct inv_icm42600_state *st); + +void inv_icm42600_buffer_update_fifo_period(struct inv_icm42600_state *st); + +int inv_icm42600_buffer_set_fifo_en(struct inv_icm42600_state *st, + unsigned int fifo_en); + +int inv_icm42600_buffer_update_watermark(struct inv_icm42600_state *st); + +int inv_icm42600_buffer_fifo_read(struct inv_icm42600_state *st, + unsigned int max); + +int inv_icm42600_buffer_fifo_parse(struct inv_icm42600_state *st); + +int inv_icm42600_buffer_hwfifo_flush(struct inv_icm42600_state *st, + unsigned int count); + +#endif diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c new file mode 100644 index 000000000..dc97d8a27 --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c @@ -0,0 +1,961 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Invensense, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "inv_icm42600.h" +#include "inv_icm42600_buffer.h" + +static const struct regmap_range_cfg inv_icm42600_regmap_ranges[] = { + { + .name = "user banks", + .range_min = 0x0000, + .range_max = 0x4FFF, + .selector_reg = INV_ICM42600_REG_BANK_SEL, + .selector_mask = INV_ICM42600_BANK_SEL_MASK, + .selector_shift = 0, + .window_start = 0, + .window_len = 0x1000, + }, +}; + +static const struct regmap_range inv_icm42600_regmap_volatile_yes_ranges[] = { + /* Sensor data registers */ + regmap_reg_range(0x001D, 0x002A), + /* INT status, FIFO, APEX data */ + regmap_reg_range(0x002D, 0x0038), + /* Signal path reset */ + regmap_reg_range(0x004B, 0x004B), + /* FIFO lost packets */ + regmap_reg_range(0x006C, 0x006D), + /* Timestamp value */ + regmap_reg_range(0x1062, 0x1064), +}; + +static const struct regmap_range inv_icm42600_regmap_volatile_no_ranges[] = { + regmap_reg_range(0x0000, 0x001C), + regmap_reg_range(0x006E, 0x1061), + regmap_reg_range(0x1065, 0x4FFF), +}; + +static const struct regmap_access_table inv_icm42600_regmap_volatile_accesses[] = { + { + .yes_ranges = inv_icm42600_regmap_volatile_yes_ranges, + .n_yes_ranges = ARRAY_SIZE(inv_icm42600_regmap_volatile_yes_ranges), + .no_ranges = inv_icm42600_regmap_volatile_no_ranges, + .n_no_ranges = ARRAY_SIZE(inv_icm42600_regmap_volatile_no_ranges), + }, +}; + +static const struct regmap_range inv_icm42600_regmap_rd_noinc_no_ranges[] = { + regmap_reg_range(0x0000, INV_ICM42600_REG_FIFO_DATA - 1), + regmap_reg_range(INV_ICM42600_REG_FIFO_DATA + 1, 0x4FFF), +}; + +static const struct regmap_access_table inv_icm42600_regmap_rd_noinc_accesses[] = { + { + .no_ranges = inv_icm42600_regmap_rd_noinc_no_ranges, + .n_no_ranges = ARRAY_SIZE(inv_icm42600_regmap_rd_noinc_no_ranges), + }, +}; + +const struct regmap_config inv_icm42600_regmap_config = { + .name = "inv_icm42600", + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x4FFF, + .ranges = inv_icm42600_regmap_ranges, + .num_ranges = ARRAY_SIZE(inv_icm42600_regmap_ranges), + .volatile_table = inv_icm42600_regmap_volatile_accesses, + .rd_noinc_table = inv_icm42600_regmap_rd_noinc_accesses, + .cache_type = REGCACHE_MAPLE, +}; +EXPORT_SYMBOL_NS_GPL(inv_icm42600_regmap_config, "IIO_ICM42600"); + +/* define specific regmap for SPI not supporting burst write */ +const struct regmap_config inv_icm42600_spi_regmap_config = { + .name = "inv_icm42600", + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x4FFF, + .ranges = inv_icm42600_regmap_ranges, + .num_ranges = ARRAY_SIZE(inv_icm42600_regmap_ranges), + .volatile_table = inv_icm42600_regmap_volatile_accesses, + .rd_noinc_table = inv_icm42600_regmap_rd_noinc_accesses, + .cache_type = REGCACHE_MAPLE, + .use_single_write = true, +}; +EXPORT_SYMBOL_NS_GPL(inv_icm42600_spi_regmap_config, "IIO_ICM42600"); + +struct inv_icm42600_hw { + u8 whoami; + const char *name; + const struct inv_icm42600_conf *conf; +}; + +/* chip initial default configuration */ +static const struct inv_icm42600_conf inv_icm42600_default_conf = { + .gyro = { + .mode = INV_ICM42600_SENSOR_MODE_OFF, + .fs = INV_ICM42600_GYRO_FS_2000DPS, + .odr = INV_ICM42600_ODR_50HZ, + .filter = INV_ICM42600_FILTER_BW_ODR_DIV_2, + }, + .accel = { + .mode = INV_ICM42600_SENSOR_MODE_OFF, + .fs = INV_ICM42600_ACCEL_FS_16G, + .odr = INV_ICM42600_ODR_50HZ, + .filter = INV_ICM42600_FILTER_BW_ODR_DIV_2, + }, + .temp_en = false, +}; + +static const struct inv_icm42600_conf inv_icm42686_default_conf = { + .gyro = { + .mode = INV_ICM42600_SENSOR_MODE_OFF, + .fs = INV_ICM42686_GYRO_FS_4000DPS, + .odr = INV_ICM42600_ODR_50HZ, + .filter = INV_ICM42600_FILTER_BW_ODR_DIV_2, + }, + .accel = { + .mode = INV_ICM42600_SENSOR_MODE_OFF, + .fs = INV_ICM42686_ACCEL_FS_32G, + .odr = INV_ICM42600_ODR_50HZ, + .filter = INV_ICM42600_FILTER_BW_ODR_DIV_2, + }, + .temp_en = false, +}; + +static const struct inv_icm42600_hw inv_icm42600_hw[INV_CHIP_NB] = { + [INV_CHIP_ICM42600] = { + .whoami = INV_ICM42600_WHOAMI_ICM42600, + .name = "icm42600", + .conf = &inv_icm42600_default_conf, + }, + [INV_CHIP_ICM42602] = { + .whoami = INV_ICM42600_WHOAMI_ICM42602, + .name = "icm42602", + .conf = &inv_icm42600_default_conf, + }, + [INV_CHIP_ICM42605] = { + .whoami = INV_ICM42600_WHOAMI_ICM42605, + .name = "icm42605", + .conf = &inv_icm42600_default_conf, + }, + [INV_CHIP_ICM42686] = { + .whoami = INV_ICM42600_WHOAMI_ICM42686, + .name = "icm42686", + .conf = &inv_icm42686_default_conf, + }, + [INV_CHIP_ICM42622] = { + .whoami = INV_ICM42600_WHOAMI_ICM42622, + .name = "icm42622", + .conf = &inv_icm42600_default_conf, + }, + [INV_CHIP_ICM42688] = { + .whoami = INV_ICM42600_WHOAMI_ICM42688, + .name = "icm42688", + .conf = &inv_icm42600_default_conf, + }, + [INV_CHIP_ICM42631] = { + .whoami = INV_ICM42600_WHOAMI_ICM42631, + .name = "icm42631", + .conf = &inv_icm42600_default_conf, + }, +}; + +const struct iio_mount_matrix * +inv_icm42600_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + const struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + + return &st->orientation; +} + +u32 inv_icm42600_odr_to_period(enum inv_icm42600_odr odr) +{ + static u32 odr_periods[INV_ICM42600_ODR_NB] = { + /* reserved values */ + 0, 0, 0, + /* 8kHz */ + 125000, + /* 4kHz */ + 250000, + /* 2kHz */ + 500000, + /* 1kHz */ + 1000000, + /* 200Hz */ + 5000000, + /* 100Hz */ + 10000000, + /* 50Hz */ + 20000000, + /* 25Hz */ + 40000000, + /* 12.5Hz */ + 80000000, + /* 6.25Hz */ + 160000000, + /* 3.125Hz */ + 320000000, + /* 1.5625Hz */ + 640000000, + /* 500Hz */ + 2000000, + }; + + return odr_periods[odr]; +} + +static int inv_icm42600_set_pwr_mgmt0(struct inv_icm42600_state *st, + enum inv_icm42600_sensor_mode gyro, + enum inv_icm42600_sensor_mode accel, + bool temp, unsigned int *sleep_ms) +{ + enum inv_icm42600_sensor_mode oldgyro = st->conf.gyro.mode; + enum inv_icm42600_sensor_mode oldaccel = st->conf.accel.mode; + bool oldtemp = st->conf.temp_en; + unsigned int sleepval; + unsigned int val; + int ret; + + /* if nothing changed, exit */ + if (gyro == oldgyro && accel == oldaccel && temp == oldtemp) + return 0; + + val = INV_ICM42600_PWR_MGMT0_GYRO(gyro) | + INV_ICM42600_PWR_MGMT0_ACCEL(accel); + if (!temp) + val |= INV_ICM42600_PWR_MGMT0_TEMP_DIS; + ret = regmap_write(st->map, INV_ICM42600_REG_PWR_MGMT0, val); + if (ret) + return ret; + + st->conf.gyro.mode = gyro; + st->conf.accel.mode = accel; + st->conf.temp_en = temp; + + /* compute required wait time for sensors to stabilize */ + sleepval = 0; + /* temperature stabilization time */ + if (temp && !oldtemp) { + if (sleepval < INV_ICM42600_TEMP_STARTUP_TIME_MS) + sleepval = INV_ICM42600_TEMP_STARTUP_TIME_MS; + } + /* accel startup time */ + if (accel != oldaccel && oldaccel == INV_ICM42600_SENSOR_MODE_OFF) { + /* block any register write for at least 200 µs */ + usleep_range(200, 300); + if (sleepval < INV_ICM42600_ACCEL_STARTUP_TIME_MS) + sleepval = INV_ICM42600_ACCEL_STARTUP_TIME_MS; + } + if (gyro != oldgyro) { + /* gyro startup time */ + if (oldgyro == INV_ICM42600_SENSOR_MODE_OFF) { + /* block any register write for at least 200 µs */ + usleep_range(200, 300); + if (sleepval < INV_ICM42600_GYRO_STARTUP_TIME_MS) + sleepval = INV_ICM42600_GYRO_STARTUP_TIME_MS; + /* gyro stop time */ + } else if (gyro == INV_ICM42600_SENSOR_MODE_OFF) { + if (sleepval < INV_ICM42600_GYRO_STOP_TIME_MS) + sleepval = INV_ICM42600_GYRO_STOP_TIME_MS; + } + } + + /* deferred sleep value if sleep pointer is provided or direct sleep */ + if (sleep_ms) + *sleep_ms = sleepval; + else if (sleepval) + msleep(sleepval); + + return 0; +} + +int inv_icm42600_set_accel_conf(struct inv_icm42600_state *st, + struct inv_icm42600_sensor_conf *conf, + unsigned int *sleep_ms) +{ + struct inv_icm42600_sensor_conf *oldconf = &st->conf.accel; + unsigned int val; + int ret; + + /* Sanitize missing values with current values */ + if (conf->mode < 0) + conf->mode = oldconf->mode; + if (conf->fs < 0) + conf->fs = oldconf->fs; + if (conf->odr < 0) + conf->odr = oldconf->odr; + if (conf->filter < 0) + conf->filter = oldconf->filter; + + /* force power mode against ODR when sensor is on */ + switch (conf->mode) { + case INV_ICM42600_SENSOR_MODE_LOW_POWER: + case INV_ICM42600_SENSOR_MODE_LOW_NOISE: + if (conf->odr <= INV_ICM42600_ODR_1KHZ_LN) { + conf->mode = INV_ICM42600_SENSOR_MODE_LOW_NOISE; + conf->filter = INV_ICM42600_FILTER_BW_ODR_DIV_2; + } else if (conf->odr >= INV_ICM42600_ODR_6_25HZ_LP && + conf->odr <= INV_ICM42600_ODR_1_5625HZ_LP) { + conf->mode = INV_ICM42600_SENSOR_MODE_LOW_POWER; + conf->filter = INV_ICM42600_FILTER_AVG_16X; + } + break; + default: + break; + } + + /* set ACCEL_CONFIG0 register (accel fullscale & odr) */ + if (conf->fs != oldconf->fs || conf->odr != oldconf->odr) { + val = INV_ICM42600_ACCEL_CONFIG0_FS(conf->fs) | + INV_ICM42600_ACCEL_CONFIG0_ODR(conf->odr); + ret = regmap_write(st->map, INV_ICM42600_REG_ACCEL_CONFIG0, val); + if (ret) + return ret; + oldconf->fs = conf->fs; + oldconf->odr = conf->odr; + } + + /* set GYRO_ACCEL_CONFIG0 register (accel filter) */ + if (conf->filter != oldconf->filter) { + val = INV_ICM42600_GYRO_ACCEL_CONFIG0_ACCEL_FILT(conf->filter) | + INV_ICM42600_GYRO_ACCEL_CONFIG0_GYRO_FILT(st->conf.gyro.filter); + ret = regmap_write(st->map, INV_ICM42600_REG_GYRO_ACCEL_CONFIG0, val); + if (ret) + return ret; + oldconf->filter = conf->filter; + } + + /* set PWR_MGMT0 register (accel sensor mode) */ + return inv_icm42600_set_pwr_mgmt0(st, st->conf.gyro.mode, conf->mode, + st->conf.temp_en, sleep_ms); +} + +int inv_icm42600_set_gyro_conf(struct inv_icm42600_state *st, + struct inv_icm42600_sensor_conf *conf, + unsigned int *sleep_ms) +{ + struct inv_icm42600_sensor_conf *oldconf = &st->conf.gyro; + unsigned int val; + int ret; + + /* sanitize missing values with current values */ + if (conf->mode < 0) + conf->mode = oldconf->mode; + if (conf->fs < 0) + conf->fs = oldconf->fs; + if (conf->odr < 0) + conf->odr = oldconf->odr; + if (conf->filter < 0) + conf->filter = oldconf->filter; + + /* set GYRO_CONFIG0 register (gyro fullscale & odr) */ + if (conf->fs != oldconf->fs || conf->odr != oldconf->odr) { + val = INV_ICM42600_GYRO_CONFIG0_FS(conf->fs) | + INV_ICM42600_GYRO_CONFIG0_ODR(conf->odr); + ret = regmap_write(st->map, INV_ICM42600_REG_GYRO_CONFIG0, val); + if (ret) + return ret; + oldconf->fs = conf->fs; + oldconf->odr = conf->odr; + } + + /* set GYRO_ACCEL_CONFIG0 register (gyro filter) */ + if (conf->filter != oldconf->filter) { + val = INV_ICM42600_GYRO_ACCEL_CONFIG0_ACCEL_FILT(st->conf.accel.filter) | + INV_ICM42600_GYRO_ACCEL_CONFIG0_GYRO_FILT(conf->filter); + ret = regmap_write(st->map, INV_ICM42600_REG_GYRO_ACCEL_CONFIG0, val); + if (ret) + return ret; + oldconf->filter = conf->filter; + } + + /* set PWR_MGMT0 register (gyro sensor mode) */ + return inv_icm42600_set_pwr_mgmt0(st, conf->mode, st->conf.accel.mode, + st->conf.temp_en, sleep_ms); + + return 0; +} + +int inv_icm42600_set_temp_conf(struct inv_icm42600_state *st, bool enable, + unsigned int *sleep_ms) +{ + return inv_icm42600_set_pwr_mgmt0(st, st->conf.gyro.mode, + st->conf.accel.mode, enable, + sleep_ms); +} + +int inv_icm42600_enable_wom(struct inv_icm42600_state *st) +{ + int ret; + + /* enable WoM hardware */ + ret = regmap_write(st->map, INV_ICM42600_REG_SMD_CONFIG, + INV_ICM42600_SMD_CONFIG_SMD_MODE_WOM | + INV_ICM42600_SMD_CONFIG_WOM_MODE); + if (ret) + return ret; + + /* enable WoM interrupt */ + return regmap_set_bits(st->map, INV_ICM42600_REG_INT_SOURCE1, + INV_ICM42600_INT_SOURCE1_WOM_INT1_EN); +} + +int inv_icm42600_disable_wom(struct inv_icm42600_state *st) +{ + int ret; + + /* disable WoM interrupt */ + ret = regmap_clear_bits(st->map, INV_ICM42600_REG_INT_SOURCE1, + INV_ICM42600_INT_SOURCE1_WOM_INT1_EN); + if (ret) + return ret; + + /* disable WoM hardware */ + return regmap_write(st->map, INV_ICM42600_REG_SMD_CONFIG, + INV_ICM42600_SMD_CONFIG_SMD_MODE_OFF); +} + +int inv_icm42600_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + + guard(mutex)(&st->lock); + + if (readval) + return regmap_read(st->map, reg, readval); + + return regmap_write(st->map, reg, writeval); +} + +static int inv_icm42600_set_conf(struct inv_icm42600_state *st, + const struct inv_icm42600_conf *conf) +{ + unsigned int val; + int ret; + + /* set PWR_MGMT0 register (gyro & accel sensor mode, temp enabled) */ + val = INV_ICM42600_PWR_MGMT0_GYRO(conf->gyro.mode) | + INV_ICM42600_PWR_MGMT0_ACCEL(conf->accel.mode); + if (!conf->temp_en) + val |= INV_ICM42600_PWR_MGMT0_TEMP_DIS; + ret = regmap_write(st->map, INV_ICM42600_REG_PWR_MGMT0, val); + if (ret) + return ret; + + /* set GYRO_CONFIG0 register (gyro fullscale & odr) */ + val = INV_ICM42600_GYRO_CONFIG0_FS(conf->gyro.fs) | + INV_ICM42600_GYRO_CONFIG0_ODR(conf->gyro.odr); + ret = regmap_write(st->map, INV_ICM42600_REG_GYRO_CONFIG0, val); + if (ret) + return ret; + + /* set ACCEL_CONFIG0 register (accel fullscale & odr) */ + val = INV_ICM42600_ACCEL_CONFIG0_FS(conf->accel.fs) | + INV_ICM42600_ACCEL_CONFIG0_ODR(conf->accel.odr); + ret = regmap_write(st->map, INV_ICM42600_REG_ACCEL_CONFIG0, val); + if (ret) + return ret; + + /* set GYRO_ACCEL_CONFIG0 register (gyro & accel filters) */ + val = INV_ICM42600_GYRO_ACCEL_CONFIG0_ACCEL_FILT(conf->accel.filter) | + INV_ICM42600_GYRO_ACCEL_CONFIG0_GYRO_FILT(conf->gyro.filter); + ret = regmap_write(st->map, INV_ICM42600_REG_GYRO_ACCEL_CONFIG0, val); + if (ret) + return ret; + + /* update internal conf */ + st->conf = *conf; + + return 0; +} + +/** + * inv_icm42600_setup() - check and setup chip + * @st: driver internal state + * @bus_setup: callback for setting up bus specific registers + * + * Returns 0 on success, a negative error code otherwise. + */ +static int inv_icm42600_setup(struct inv_icm42600_state *st, + inv_icm42600_bus_setup bus_setup) +{ + const struct inv_icm42600_hw *hw = &inv_icm42600_hw[st->chip]; + const struct device *dev = regmap_get_device(st->map); + unsigned int val; + int ret; + + /* check chip self-identification value */ + ret = regmap_read(st->map, INV_ICM42600_REG_WHOAMI, &val); + if (ret) + return ret; + if (val != hw->whoami) { + dev_err(dev, "invalid whoami %#02x expected %#02x (%s)\n", + val, hw->whoami, hw->name); + return -ENODEV; + } + st->name = hw->name; + + /* reset to make sure previous state are not there */ + ret = regmap_write(st->map, INV_ICM42600_REG_DEVICE_CONFIG, + INV_ICM42600_DEVICE_CONFIG_SOFT_RESET); + if (ret) + return ret; + msleep(INV_ICM42600_RESET_TIME_MS); + + ret = regmap_read(st->map, INV_ICM42600_REG_INT_STATUS, &val); + if (ret) + return ret; + if (!(val & INV_ICM42600_INT_STATUS_RESET_DONE)) { + dev_err(dev, "reset error, reset done bit not set\n"); + return -ENODEV; + } + + /* set chip bus configuration */ + ret = bus_setup(st); + if (ret) + return ret; + + /* sensor data in big-endian (default) */ + ret = regmap_set_bits(st->map, INV_ICM42600_REG_INTF_CONFIG0, + INV_ICM42600_INTF_CONFIG0_SENSOR_DATA_ENDIAN); + if (ret) + return ret; + + /* + * Use RC clock for accel low-power to fix glitches when switching + * gyro on/off while accel low-power is on. + */ + ret = regmap_update_bits(st->map, INV_ICM42600_REG_INTF_CONFIG1, + INV_ICM42600_INTF_CONFIG1_ACCEL_LP_CLK_RC, + INV_ICM42600_INTF_CONFIG1_ACCEL_LP_CLK_RC); + if (ret) + return ret; + + return inv_icm42600_set_conf(st, hw->conf); +} + +static irqreturn_t inv_icm42600_irq_timestamp(int irq, void *_data) +{ + struct inv_icm42600_state *st = _data; + + st->timestamp.gyro = iio_get_time_ns(st->indio_gyro); + st->timestamp.accel = iio_get_time_ns(st->indio_accel); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t inv_icm42600_irq_handler(int irq, void *_data) +{ + struct inv_icm42600_state *st = _data; + struct device *dev = regmap_get_device(st->map); + unsigned int status; + int ret; + + mutex_lock(&st->lock); + + if (st->apex.on) { + unsigned int status2, status3; + + /* read INT_STATUS2 and INT_STATUS3 in 1 operation */ + ret = regmap_bulk_read(st->map, INV_ICM42600_REG_INT_STATUS2, st->buffer, 2); + if (ret) + goto out_unlock; + status2 = st->buffer[0]; + status3 = st->buffer[1]; + inv_icm42600_accel_handle_events(st->indio_accel, status2, status3, + st->timestamp.accel); + } + + ret = regmap_read(st->map, INV_ICM42600_REG_INT_STATUS, &status); + if (ret) + goto out_unlock; + + /* FIFO full */ + if (status & INV_ICM42600_INT_STATUS_FIFO_FULL) + dev_warn(dev, "FIFO full data lost!\n"); + + /* FIFO threshold reached */ + if (status & INV_ICM42600_INT_STATUS_FIFO_THS) { + ret = inv_icm42600_buffer_fifo_read(st, 0); + if (ret) { + dev_err(dev, "FIFO read error %d\n", ret); + goto out_unlock; + } + ret = inv_icm42600_buffer_fifo_parse(st); + if (ret) + dev_err(dev, "FIFO parsing error %d\n", ret); + } + +out_unlock: + mutex_unlock(&st->lock); + return IRQ_HANDLED; +} + +/** + * inv_icm42600_irq_init() - initialize int pin and interrupt handler + * @st: driver internal state + * @irq: irq number + * @irq_type: irq trigger type + * @open_drain: true if irq is open drain, false for push-pull + * + * Returns 0 on success, a negative error code otherwise. + */ +static int inv_icm42600_irq_init(struct inv_icm42600_state *st, int irq, + int irq_type, bool open_drain) +{ + struct device *dev = regmap_get_device(st->map); + unsigned int val; + int ret; + + /* configure INT1 interrupt: default is active low on edge */ + switch (irq_type) { + case IRQF_TRIGGER_RISING: + case IRQF_TRIGGER_HIGH: + val = INV_ICM42600_INT_CONFIG_INT1_ACTIVE_HIGH; + break; + default: + val = INV_ICM42600_INT_CONFIG_INT1_ACTIVE_LOW; + break; + } + + switch (irq_type) { + case IRQF_TRIGGER_LOW: + case IRQF_TRIGGER_HIGH: + val |= INV_ICM42600_INT_CONFIG_INT1_LATCHED; + break; + default: + break; + } + + if (!open_drain) + val |= INV_ICM42600_INT_CONFIG_INT1_PUSH_PULL; + + ret = regmap_write(st->map, INV_ICM42600_REG_INT_CONFIG, val); + if (ret) + return ret; + + /* Deassert async reset for proper INT pin operation (cf datasheet) */ + ret = regmap_clear_bits(st->map, INV_ICM42600_REG_INT_CONFIG1, + INV_ICM42600_INT_CONFIG1_ASYNC_RESET); + if (ret) + return ret; + + irq_type |= IRQF_ONESHOT; + return devm_request_threaded_irq(dev, irq, inv_icm42600_irq_timestamp, + inv_icm42600_irq_handler, irq_type, + "inv_icm42600", st); +} + +static int inv_icm42600_timestamp_setup(struct inv_icm42600_state *st) +{ + unsigned int val; + + /* enable timestamp register */ + val = INV_ICM42600_TMST_CONFIG_TMST_TO_REGS_EN | + INV_ICM42600_TMST_CONFIG_TMST_EN; + return regmap_update_bits(st->map, INV_ICM42600_REG_TMST_CONFIG, + INV_ICM42600_TMST_CONFIG_MASK, val); +} + +static int inv_icm42600_enable_regulator_vddio(struct inv_icm42600_state *st) +{ + int ret; + + ret = regulator_enable(st->vddio_supply); + if (ret) + return ret; + + /* wait a little for supply ramp */ + usleep_range(3000, 4000); + + return 0; +} + +static void inv_icm42600_disable_vddio_reg(void *_data) +{ + struct inv_icm42600_state *st = _data; + struct device *dev = regmap_get_device(st->map); + + if (pm_runtime_status_suspended(dev)) + return; + + regulator_disable(st->vddio_supply); +} + +int inv_icm42600_core_probe(struct regmap *regmap, int chip, + inv_icm42600_bus_setup bus_setup) +{ + struct device *dev = regmap_get_device(regmap); + struct fwnode_handle *fwnode = dev_fwnode(dev); + struct inv_icm42600_state *st; + int irq, irq_type; + bool open_drain; + int ret; + + if (chip <= INV_CHIP_INVALID || chip >= INV_CHIP_NB) { + dev_err(dev, "invalid chip = %d\n", chip); + return -ENODEV; + } + + /* get INT1 only supported interrupt or fallback to first interrupt */ + irq = fwnode_irq_get_byname(fwnode, "INT1"); + if (irq < 0 && irq != -EPROBE_DEFER) { + dev_info(dev, "no INT1 interrupt defined, fallback to first interrupt\n"); + irq = fwnode_irq_get(fwnode, 0); + } + if (irq < 0) + return dev_err_probe(dev, irq, "error missing INT1 interrupt\n"); + + irq_type = irq_get_trigger_type(irq); + if (!irq_type) + irq_type = IRQF_TRIGGER_FALLING; + + open_drain = device_property_read_bool(dev, "drive-open-drain"); + + st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL); + if (!st) + return -ENOMEM; + + dev_set_drvdata(dev, st); + mutex_init(&st->lock); + st->chip = chip; + st->map = regmap; + st->irq = irq; + + ret = iio_read_mount_matrix(dev, &st->orientation); + if (ret) { + dev_err(dev, "failed to retrieve mounting matrix %d\n", ret); + return ret; + } + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get vdd regulator\n"); + + msleep(INV_ICM42600_POWER_UP_TIME_MS); + + st->vddio_supply = devm_regulator_get(dev, "vddio"); + if (IS_ERR(st->vddio_supply)) + return PTR_ERR(st->vddio_supply); + + ret = inv_icm42600_enable_regulator_vddio(st); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, inv_icm42600_disable_vddio_reg, st); + if (ret) + return ret; + + /* setup chip registers */ + ret = inv_icm42600_setup(st, bus_setup); + if (ret) + return ret; + + ret = inv_icm42600_timestamp_setup(st); + if (ret) + return ret; + + ret = inv_icm42600_buffer_init(st); + if (ret) + return ret; + + st->indio_gyro = inv_icm42600_gyro_init(st); + if (IS_ERR(st->indio_gyro)) + return PTR_ERR(st->indio_gyro); + + st->indio_accel = inv_icm42600_accel_init(st); + if (IS_ERR(st->indio_accel)) + return PTR_ERR(st->indio_accel); + + ret = inv_icm42600_irq_init(st, irq, irq_type, open_drain); + if (ret) + return ret; + + /* setup runtime power management */ + ret = devm_pm_runtime_set_active_enabled(dev); + if (ret) + return ret; + + pm_runtime_set_autosuspend_delay(dev, INV_ICM42600_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(inv_icm42600_core_probe, "IIO_ICM42600"); + +/* + * Suspend saves sensors state and turns everything off. + * Check first if runtime suspend has not already done the job. + */ +static int inv_icm42600_suspend(struct device *dev) +{ + struct inv_icm42600_state *st = dev_get_drvdata(dev); + struct device *accel_dev; + bool wakeup; + int accel_conf; + int ret; + + guard(mutex)(&st->lock); + + st->suspended.gyro = st->conf.gyro.mode; + st->suspended.accel = st->conf.accel.mode; + st->suspended.temp = st->conf.temp_en; + if (pm_runtime_suspended(dev)) + return 0; + + /* disable FIFO data streaming */ + if (st->fifo.on) { + ret = regmap_write(st->map, INV_ICM42600_REG_FIFO_CONFIG, + INV_ICM42600_FIFO_CONFIG_BYPASS); + if (ret) + return ret; + } + + /* keep chip on and wake-up capable if APEX and wakeup on */ + accel_dev = &st->indio_accel->dev; + wakeup = st->apex.on && device_may_wakeup(accel_dev); + if (wakeup) { + /* keep accel on and setup irq for wakeup */ + accel_conf = st->conf.accel.mode; + enable_irq_wake(st->irq); + disable_irq(st->irq); + } else { + /* disable APEX features and accel if wakeup disabled */ + if (st->apex.wom.enable) { + ret = inv_icm42600_disable_wom(st); + if (ret) + return ret; + } + accel_conf = INV_ICM42600_SENSOR_MODE_OFF; + } + + ret = inv_icm42600_set_pwr_mgmt0(st, INV_ICM42600_SENSOR_MODE_OFF, + accel_conf, false, NULL); + if (ret) + return ret; + + /* disable vddio regulator if chip is sleeping */ + if (!wakeup) + regulator_disable(st->vddio_supply); + + return 0; +} + +/* + * System resume gets the system back on and restores the sensors state. + * Manually put runtime power management in system active state. + */ +static int inv_icm42600_resume(struct device *dev) +{ + struct inv_icm42600_state *st = dev_get_drvdata(dev); + struct inv_icm42600_sensor_state *gyro_st = iio_priv(st->indio_gyro); + struct inv_icm42600_sensor_state *accel_st = iio_priv(st->indio_accel); + struct device *accel_dev; + bool wakeup; + int ret; + + guard(mutex)(&st->lock); + + if (pm_runtime_suspended(dev)) + return 0; + + /* check wakeup capability */ + accel_dev = &st->indio_accel->dev; + wakeup = st->apex.on && device_may_wakeup(accel_dev); + /* restore irq state or vddio if cut off */ + if (wakeup) { + enable_irq(st->irq); + disable_irq_wake(st->irq); + } else { + ret = inv_icm42600_enable_regulator_vddio(st); + if (ret) + return ret; + } + + /* restore sensors state */ + ret = inv_icm42600_set_pwr_mgmt0(st, st->suspended.gyro, + st->suspended.accel, + st->suspended.temp, NULL); + if (ret) + return ret; + + /* restore APEX features if disabled */ + if (!wakeup && st->apex.wom.enable) { + ret = inv_icm42600_enable_wom(st); + if (ret) + return ret; + } + + /* restore FIFO data streaming */ + if (st->fifo.on) { + inv_sensors_timestamp_reset(&gyro_st->ts); + inv_sensors_timestamp_reset(&accel_st->ts); + ret = regmap_write(st->map, INV_ICM42600_REG_FIFO_CONFIG, + INV_ICM42600_FIFO_CONFIG_STREAM); + } + + return 0; +} + +/* Runtime suspend will turn off sensors that are enabled by iio devices. */ +static int inv_icm42600_runtime_suspend(struct device *dev) +{ + struct inv_icm42600_state *st = dev_get_drvdata(dev); + int ret; + + guard(mutex)(&st->lock); + + /* disable all sensors */ + ret = inv_icm42600_set_pwr_mgmt0(st, INV_ICM42600_SENSOR_MODE_OFF, + INV_ICM42600_SENSOR_MODE_OFF, false, + NULL); + if (ret) + return ret; + + regulator_disable(st->vddio_supply); + + return 0; +} + +/* Sensors are enabled by iio devices, no need to turn them back on here. */ +static int inv_icm42600_runtime_resume(struct device *dev) +{ + struct inv_icm42600_state *st = dev_get_drvdata(dev); + + guard(mutex)(&st->lock); + + return inv_icm42600_enable_regulator_vddio(st); +} + +EXPORT_NS_GPL_DEV_PM_OPS(inv_icm42600_pm_ops, IIO_ICM42600) = { + SYSTEM_SLEEP_PM_OPS(inv_icm42600_suspend, inv_icm42600_resume) + RUNTIME_PM_OPS(inv_icm42600_runtime_suspend, + inv_icm42600_runtime_resume, NULL) +}; + +MODULE_AUTHOR("InvenSense, Inc."); +MODULE_DESCRIPTION("InvenSense ICM-426xx device driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_INV_SENSORS_TIMESTAMP"); diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c new file mode 100644 index 000000000..253bf5714 --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c @@ -0,0 +1,828 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Invensense, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "inv_icm42600.h" +#include "inv_icm42600_buffer.h" +#include "inv_icm42600_temp.h" + +#define INV_ICM42600_GYRO_CHAN(_modifier, _index, _ext_info) \ + { \ + .type = IIO_ANGL_VEL, \ + .modified = 1, \ + .channel2 = _modifier, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_all = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + .ext_info = _ext_info, \ + } + +enum inv_icm42600_gyro_scan { + INV_ICM42600_GYRO_SCAN_X, + INV_ICM42600_GYRO_SCAN_Y, + INV_ICM42600_GYRO_SCAN_Z, + INV_ICM42600_GYRO_SCAN_TEMP, + INV_ICM42600_GYRO_SCAN_TIMESTAMP, +}; + +static const struct iio_chan_spec_ext_info inv_icm42600_gyro_ext_infos[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, inv_icm42600_get_mount_matrix), + { } +}; + +static const struct iio_chan_spec inv_icm42600_gyro_channels[] = { + INV_ICM42600_GYRO_CHAN(IIO_MOD_X, INV_ICM42600_GYRO_SCAN_X, + inv_icm42600_gyro_ext_infos), + INV_ICM42600_GYRO_CHAN(IIO_MOD_Y, INV_ICM42600_GYRO_SCAN_Y, + inv_icm42600_gyro_ext_infos), + INV_ICM42600_GYRO_CHAN(IIO_MOD_Z, INV_ICM42600_GYRO_SCAN_Z, + inv_icm42600_gyro_ext_infos), + INV_ICM42600_TEMP_CHAN(INV_ICM42600_GYRO_SCAN_TEMP), + IIO_CHAN_SOFT_TIMESTAMP(INV_ICM42600_GYRO_SCAN_TIMESTAMP), +}; + +/* + * IIO buffer data: size must be a power of 2 and timestamp aligned + * 16 bytes: 6 bytes angular velocity, 2 bytes temperature, 8 bytes timestamp + */ +struct inv_icm42600_gyro_buffer { + struct inv_icm42600_fifo_sensor_data gyro; + s16 temp; + aligned_s64 timestamp; +}; + +#define INV_ICM42600_SCAN_MASK_GYRO_3AXIS \ + (BIT(INV_ICM42600_GYRO_SCAN_X) | \ + BIT(INV_ICM42600_GYRO_SCAN_Y) | \ + BIT(INV_ICM42600_GYRO_SCAN_Z)) + +#define INV_ICM42600_SCAN_MASK_TEMP BIT(INV_ICM42600_GYRO_SCAN_TEMP) + +static const unsigned long inv_icm42600_gyro_scan_masks[] = { + /* 3-axis gyro + temperature */ + INV_ICM42600_SCAN_MASK_GYRO_3AXIS | INV_ICM42600_SCAN_MASK_TEMP, + 0, +}; + +/* enable gyroscope sensor and FIFO write */ +static int inv_icm42600_gyro_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; + unsigned int fifo_en = 0; + unsigned int sleep_gyro = 0; + unsigned int sleep_temp = 0; + unsigned int sleep; + int ret; + + mutex_lock(&st->lock); + + if (*scan_mask & INV_ICM42600_SCAN_MASK_TEMP) { + /* enable temp sensor */ + ret = inv_icm42600_set_temp_conf(st, true, &sleep_temp); + if (ret) + goto out_unlock; + fifo_en |= INV_ICM42600_SENSOR_TEMP; + } + + if (*scan_mask & INV_ICM42600_SCAN_MASK_GYRO_3AXIS) { + /* enable gyro sensor */ + conf.mode = INV_ICM42600_SENSOR_MODE_LOW_NOISE; + ret = inv_icm42600_set_gyro_conf(st, &conf, &sleep_gyro); + if (ret) + goto out_unlock; + fifo_en |= INV_ICM42600_SENSOR_GYRO; + } + + /* update data FIFO write */ + ret = inv_icm42600_buffer_set_fifo_en(st, fifo_en | st->fifo.en); + +out_unlock: + mutex_unlock(&st->lock); + /* sleep maximum required time */ + sleep = max(sleep_gyro, sleep_temp); + if (sleep) + msleep(sleep); + return ret; +} + +static int inv_icm42600_gyro_read_sensor(struct inv_icm42600_state *st, + struct iio_chan_spec const *chan, + s16 *val) +{ + struct device *dev = regmap_get_device(st->map); + struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; + unsigned int reg; + __be16 *data; + int ret; + + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM42600_REG_GYRO_DATA_X; + break; + case IIO_MOD_Y: + reg = INV_ICM42600_REG_GYRO_DATA_Y; + break; + case IIO_MOD_Z: + reg = INV_ICM42600_REG_GYRO_DATA_Z; + break; + default: + return -EINVAL; + } + + pm_runtime_get_sync(dev); + mutex_lock(&st->lock); + + /* enable gyro sensor */ + conf.mode = INV_ICM42600_SENSOR_MODE_LOW_NOISE; + ret = inv_icm42600_set_gyro_conf(st, &conf, NULL); + if (ret) + goto exit; + + /* read gyro register data */ + data = (__be16 *)&st->buffer[0]; + ret = regmap_bulk_read(st->map, reg, data, sizeof(*data)); + if (ret) + goto exit; + + *val = (s16)be16_to_cpup(data); + if (*val == INV_ICM42600_DATA_INVALID) + ret = -EINVAL; +exit: + mutex_unlock(&st->lock); + pm_runtime_put_autosuspend(dev); + return ret; +} + +/* IIO format int + nano */ +static const int inv_icm42600_gyro_scale[] = { + /* +/- 2000dps => 0.001065264 rad/s */ + [2 * INV_ICM42600_GYRO_FS_2000DPS] = 0, + [2 * INV_ICM42600_GYRO_FS_2000DPS + 1] = 1065264, + /* +/- 1000dps => 0.000532632 rad/s */ + [2 * INV_ICM42600_GYRO_FS_1000DPS] = 0, + [2 * INV_ICM42600_GYRO_FS_1000DPS + 1] = 532632, + /* +/- 500dps => 0.000266316 rad/s */ + [2 * INV_ICM42600_GYRO_FS_500DPS] = 0, + [2 * INV_ICM42600_GYRO_FS_500DPS + 1] = 266316, + /* +/- 250dps => 0.000133158 rad/s */ + [2 * INV_ICM42600_GYRO_FS_250DPS] = 0, + [2 * INV_ICM42600_GYRO_FS_250DPS + 1] = 133158, + /* +/- 125dps => 0.000066579 rad/s */ + [2 * INV_ICM42600_GYRO_FS_125DPS] = 0, + [2 * INV_ICM42600_GYRO_FS_125DPS + 1] = 66579, + /* +/- 62.5dps => 0.000033290 rad/s */ + [2 * INV_ICM42600_GYRO_FS_62_5DPS] = 0, + [2 * INV_ICM42600_GYRO_FS_62_5DPS + 1] = 33290, + /* +/- 31.25dps => 0.000016645 rad/s */ + [2 * INV_ICM42600_GYRO_FS_31_25DPS] = 0, + [2 * INV_ICM42600_GYRO_FS_31_25DPS + 1] = 16645, + /* +/- 15.625dps => 0.000008322 rad/s */ + [2 * INV_ICM42600_GYRO_FS_15_625DPS] = 0, + [2 * INV_ICM42600_GYRO_FS_15_625DPS + 1] = 8322, +}; +static const int inv_icm42686_gyro_scale[] = { + /* +/- 4000dps => 0.002130529 rad/s */ + [2 * INV_ICM42686_GYRO_FS_4000DPS] = 0, + [2 * INV_ICM42686_GYRO_FS_4000DPS + 1] = 2130529, + /* +/- 2000dps => 0.001065264 rad/s */ + [2 * INV_ICM42686_GYRO_FS_2000DPS] = 0, + [2 * INV_ICM42686_GYRO_FS_2000DPS + 1] = 1065264, + /* +/- 1000dps => 0.000532632 rad/s */ + [2 * INV_ICM42686_GYRO_FS_1000DPS] = 0, + [2 * INV_ICM42686_GYRO_FS_1000DPS + 1] = 532632, + /* +/- 500dps => 0.000266316 rad/s */ + [2 * INV_ICM42686_GYRO_FS_500DPS] = 0, + [2 * INV_ICM42686_GYRO_FS_500DPS + 1] = 266316, + /* +/- 250dps => 0.000133158 rad/s */ + [2 * INV_ICM42686_GYRO_FS_250DPS] = 0, + [2 * INV_ICM42686_GYRO_FS_250DPS + 1] = 133158, + /* +/- 125dps => 0.000066579 rad/s */ + [2 * INV_ICM42686_GYRO_FS_125DPS] = 0, + [2 * INV_ICM42686_GYRO_FS_125DPS + 1] = 66579, + /* +/- 62.5dps => 0.000033290 rad/s */ + [2 * INV_ICM42686_GYRO_FS_62_5DPS] = 0, + [2 * INV_ICM42686_GYRO_FS_62_5DPS + 1] = 33290, + /* +/- 31.25dps => 0.000016645 rad/s */ + [2 * INV_ICM42686_GYRO_FS_31_25DPS] = 0, + [2 * INV_ICM42686_GYRO_FS_31_25DPS + 1] = 16645, +}; + +static int inv_icm42600_gyro_read_scale(struct iio_dev *indio_dev, + int *val, int *val2) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_sensor_state *gyro_st = iio_priv(indio_dev); + unsigned int idx; + + idx = st->conf.gyro.fs; + + *val = gyro_st->scales[2 * idx]; + *val2 = gyro_st->scales[2 * idx + 1]; + return IIO_VAL_INT_PLUS_NANO; +} + +static int inv_icm42600_gyro_write_scale(struct iio_dev *indio_dev, + int val, int val2) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_sensor_state *gyro_st = iio_priv(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; + int ret; + + for (idx = 0; idx < gyro_st->scales_len; idx += 2) { + if (val == gyro_st->scales[idx] && + val2 == gyro_st->scales[idx + 1]) + break; + } + if (idx >= gyro_st->scales_len) + return -EINVAL; + + conf.fs = idx / 2; + + pm_runtime_get_sync(dev); + + scoped_guard(mutex, &st->lock) + ret = inv_icm42600_set_gyro_conf(st, &conf, NULL); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +/* IIO format int + micro */ +static const int inv_icm42600_gyro_odr[] = { + /* 12.5Hz */ + 12, 500000, + /* 25Hz */ + 25, 0, + /* 50Hz */ + 50, 0, + /* 100Hz */ + 100, 0, + /* 200Hz */ + 200, 0, + /* 1kHz */ + 1000, 0, + /* 2kHz */ + 2000, 0, + /* 4kHz */ + 4000, 0, +}; + +static const int inv_icm42600_gyro_odr_conv[] = { + INV_ICM42600_ODR_12_5HZ, + INV_ICM42600_ODR_25HZ, + INV_ICM42600_ODR_50HZ, + INV_ICM42600_ODR_100HZ, + INV_ICM42600_ODR_200HZ, + INV_ICM42600_ODR_1KHZ_LN, + INV_ICM42600_ODR_2KHZ_LN, + INV_ICM42600_ODR_4KHZ_LN, +}; + +static int inv_icm42600_gyro_read_odr(struct inv_icm42600_state *st, + int *val, int *val2) +{ + unsigned int odr; + unsigned int i; + + odr = st->conf.gyro.odr; + + for (i = 0; i < ARRAY_SIZE(inv_icm42600_gyro_odr_conv); ++i) { + if (inv_icm42600_gyro_odr_conv[i] == odr) + break; + } + if (i >= ARRAY_SIZE(inv_icm42600_gyro_odr_conv)) + return -EINVAL; + + *val = inv_icm42600_gyro_odr[2 * i]; + *val2 = inv_icm42600_gyro_odr[2 * i + 1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int inv_icm42600_gyro_write_odr(struct iio_dev *indio_dev, + int val, int val2) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_sensor_state *gyro_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &gyro_st->ts; + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + struct inv_icm42600_sensor_conf conf = INV_ICM42600_SENSOR_CONF_INIT; + int ret; + + for (idx = 0; idx < ARRAY_SIZE(inv_icm42600_gyro_odr); idx += 2) { + if (val == inv_icm42600_gyro_odr[idx] && + val2 == inv_icm42600_gyro_odr[idx + 1]) + break; + } + if (idx >= ARRAY_SIZE(inv_icm42600_gyro_odr)) + return -EINVAL; + + conf.odr = inv_icm42600_gyro_odr_conv[idx / 2]; + if (conf.odr == st->conf.gyro.odr) + return 0; + + pm_runtime_get_sync(dev); + mutex_lock(&st->lock); + + ret = inv_sensors_timestamp_update_odr(ts, inv_icm42600_odr_to_period(conf.odr), + iio_buffer_enabled(indio_dev)); + if (ret) + goto out_unlock; + + ret = inv_icm42600_set_gyro_conf(st, &conf, NULL); + if (ret) + goto out_unlock; + inv_icm42600_buffer_update_fifo_period(st); + inv_icm42600_buffer_update_watermark(st); + +out_unlock: + mutex_unlock(&st->lock); + pm_runtime_put_autosuspend(dev); + + return ret; +} + +/* + * Calibration bias values, IIO range format int + nano. + * Value is limited to +/-64dps coded on 12 bits signed. Step is 1/32 dps. + */ +static int inv_icm42600_gyro_calibbias[] = { + -1, 117010721, /* min: -1.117010721 rad/s */ + 0, 545415, /* step: 0.000545415 rad/s */ + 1, 116465306, /* max: 1.116465306 rad/s */ +}; + +static int inv_icm42600_gyro_read_offset(struct inv_icm42600_state *st, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + struct device *dev = regmap_get_device(st->map); + s64 val64; + s32 bias; + unsigned int reg; + s16 offset; + u8 data[2]; + int ret; + + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM42600_REG_OFFSET_USER0; + break; + case IIO_MOD_Y: + reg = INV_ICM42600_REG_OFFSET_USER1; + break; + case IIO_MOD_Z: + reg = INV_ICM42600_REG_OFFSET_USER3; + break; + default: + return -EINVAL; + } + + pm_runtime_get_sync(dev); + mutex_lock(&st->lock); + + ret = regmap_bulk_read(st->map, reg, st->buffer, sizeof(data)); + memcpy(data, st->buffer, sizeof(data)); + + mutex_unlock(&st->lock); + pm_runtime_put_autosuspend(dev); + if (ret) + return ret; + + /* 12 bits signed value */ + switch (chan->channel2) { + case IIO_MOD_X: + offset = sign_extend32(((data[1] & 0x0F) << 8) | data[0], 11); + break; + case IIO_MOD_Y: + offset = sign_extend32(((data[0] & 0xF0) << 4) | data[1], 11); + break; + case IIO_MOD_Z: + offset = sign_extend32(((data[1] & 0x0F) << 8) | data[0], 11); + break; + default: + return -EINVAL; + } + + /* + * convert raw offset to dps then to rad/s + * 12 bits signed raw max 64 to dps: 64 / 2048 + * dps to rad: Pi / 180 + * result in nano (1000000000) + * (offset * 64 * Pi * 1000000000) / (2048 * 180) + */ + val64 = (s64)offset * 64LL * 3141592653LL; + /* for rounding, add + or - divisor (2048 * 180) divided by 2 */ + if (val64 >= 0) + val64 += 2048 * 180 / 2; + else + val64 -= 2048 * 180 / 2; + bias = div_s64(val64, 2048 * 180); + *val = bias / 1000000000L; + *val2 = bias % 1000000000L; + + return IIO_VAL_INT_PLUS_NANO; +} + +static int inv_icm42600_gyro_write_offset(struct inv_icm42600_state *st, + struct iio_chan_spec const *chan, + int val, int val2) +{ + struct device *dev = regmap_get_device(st->map); + s64 val64, min, max; + unsigned int reg, regval; + s16 offset; + int ret; + + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM42600_REG_OFFSET_USER0; + break; + case IIO_MOD_Y: + reg = INV_ICM42600_REG_OFFSET_USER1; + break; + case IIO_MOD_Z: + reg = INV_ICM42600_REG_OFFSET_USER3; + break; + default: + return -EINVAL; + } + + /* inv_icm42600_gyro_calibbias: min - step - max in nano */ + min = (s64)inv_icm42600_gyro_calibbias[0] * 1000000000LL + + (s64)inv_icm42600_gyro_calibbias[1]; + max = (s64)inv_icm42600_gyro_calibbias[4] * 1000000000LL + + (s64)inv_icm42600_gyro_calibbias[5]; + val64 = (s64)val * 1000000000LL + (s64)val2; + if (val64 < min || val64 > max) + return -EINVAL; + + /* + * convert rad/s to dps then to raw value + * rad to dps: 180 / Pi + * dps to raw 12 bits signed, max 64: 2048 / 64 + * val in nano (1000000000) + * val * 180 * 2048 / (Pi * 1000000000 * 64) + */ + val64 = val64 * 180LL * 2048LL; + /* for rounding, add + or - divisor (3141592653 * 64) divided by 2 */ + if (val64 >= 0) + val64 += 3141592653LL * 64LL / 2LL; + else + val64 -= 3141592653LL * 64LL / 2LL; + offset = div64_s64(val64, 3141592653LL * 64LL); + + /* clamp value limited to 12 bits signed */ + if (offset < -2048) + offset = -2048; + else if (offset > 2047) + offset = 2047; + + pm_runtime_get_sync(dev); + mutex_lock(&st->lock); + + switch (chan->channel2) { + case IIO_MOD_X: + /* OFFSET_USER1 register is shared */ + ret = regmap_read(st->map, INV_ICM42600_REG_OFFSET_USER1, + ®val); + if (ret) + goto out_unlock; + st->buffer[0] = offset & 0xFF; + st->buffer[1] = (regval & 0xF0) | ((offset & 0xF00) >> 8); + break; + case IIO_MOD_Y: + /* OFFSET_USER1 register is shared */ + ret = regmap_read(st->map, INV_ICM42600_REG_OFFSET_USER1, + ®val); + if (ret) + goto out_unlock; + st->buffer[0] = ((offset & 0xF00) >> 4) | (regval & 0x0F); + st->buffer[1] = offset & 0xFF; + break; + case IIO_MOD_Z: + /* OFFSET_USER4 register is shared */ + ret = regmap_read(st->map, INV_ICM42600_REG_OFFSET_USER4, + ®val); + if (ret) + goto out_unlock; + st->buffer[0] = offset & 0xFF; + st->buffer[1] = (regval & 0xF0) | ((offset & 0xF00) >> 8); + break; + default: + ret = -EINVAL; + goto out_unlock; + } + + ret = regmap_bulk_write(st->map, reg, st->buffer, 2); + +out_unlock: + mutex_unlock(&st->lock); + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int inv_icm42600_gyro_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + s16 data; + int ret; + + switch (chan->type) { + case IIO_ANGL_VEL: + break; + case IIO_TEMP: + return inv_icm42600_temp_read_raw(indio_dev, chan, val, val2, mask); + default: + return -EINVAL; + } + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = inv_icm42600_gyro_read_sensor(st, chan, &data); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + *val = data; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + return inv_icm42600_gyro_read_scale(indio_dev, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return inv_icm42600_gyro_read_odr(st, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + return inv_icm42600_gyro_read_offset(st, chan, val, val2); + default: + return -EINVAL; + } +} + +static int inv_icm42600_gyro_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, + int *type, int *length, long mask) +{ + struct inv_icm42600_sensor_state *gyro_st = iio_priv(indio_dev); + + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = gyro_st->scales; + *type = IIO_VAL_INT_PLUS_NANO; + *length = gyro_st->scales_len; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = inv_icm42600_gyro_odr; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = ARRAY_SIZE(inv_icm42600_gyro_odr); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_CALIBBIAS: + *vals = inv_icm42600_gyro_calibbias; + *type = IIO_VAL_INT_PLUS_NANO; + return IIO_AVAIL_RANGE; + default: + return -EINVAL; + } +} + +static int inv_icm42600_gyro_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = inv_icm42600_gyro_write_scale(indio_dev, val, val2); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + return inv_icm42600_gyro_write_odr(indio_dev, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = inv_icm42600_gyro_write_offset(st, chan, val, val2); + iio_device_release_direct(indio_dev); + return ret; + default: + return -EINVAL; + } +} + +static int inv_icm42600_gyro_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBBIAS: + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static int inv_icm42600_gyro_hwfifo_set_watermark(struct iio_dev *indio_dev, + unsigned int val) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + + guard(mutex)(&st->lock); + + st->fifo.watermark.gyro = val; + return inv_icm42600_buffer_update_watermark(st); +} + +static int inv_icm42600_gyro_hwfifo_flush(struct iio_dev *indio_dev, + unsigned int count) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + if (count == 0) + return 0; + + guard(mutex)(&st->lock); + + ret = inv_icm42600_buffer_hwfifo_flush(st, count); + if (ret) + return ret; + + return st->fifo.nb.gyro; +} + +static const struct iio_info inv_icm42600_gyro_info = { + .read_raw = inv_icm42600_gyro_read_raw, + .read_avail = inv_icm42600_gyro_read_avail, + .write_raw = inv_icm42600_gyro_write_raw, + .write_raw_get_fmt = inv_icm42600_gyro_write_raw_get_fmt, + .debugfs_reg_access = inv_icm42600_debugfs_reg, + .update_scan_mode = inv_icm42600_gyro_update_scan_mode, + .hwfifo_set_watermark = inv_icm42600_gyro_hwfifo_set_watermark, + .hwfifo_flush_to_buffer = inv_icm42600_gyro_hwfifo_flush, +}; + +struct iio_dev *inv_icm42600_gyro_init(struct inv_icm42600_state *st) +{ + struct device *dev = regmap_get_device(st->map); + const char *name; + struct inv_icm42600_sensor_state *gyro_st; + struct inv_sensors_timestamp_chip ts_chip; + struct iio_dev *indio_dev; + int ret; + + name = devm_kasprintf(dev, GFP_KERNEL, "%s-gyro", st->name); + if (!name) + return ERR_PTR(-ENOMEM); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*gyro_st)); + if (!indio_dev) + return ERR_PTR(-ENOMEM); + gyro_st = iio_priv(indio_dev); + + switch (st->chip) { + case INV_CHIP_ICM42686: + gyro_st->scales = inv_icm42686_gyro_scale; + gyro_st->scales_len = ARRAY_SIZE(inv_icm42686_gyro_scale); + break; + default: + gyro_st->scales = inv_icm42600_gyro_scale; + gyro_st->scales_len = ARRAY_SIZE(inv_icm42600_gyro_scale); + break; + } + + /* + * clock period is 8kHz (125000ns) + * jitter is +/- 2% (20 per mille) + */ + ts_chip.clock_period = 125000; + ts_chip.jitter = 20; + ts_chip.init_period = inv_icm42600_odr_to_period(st->conf.accel.odr); + inv_sensors_timestamp_init(&gyro_st->ts, &ts_chip); + + iio_device_set_drvdata(indio_dev, st); + indio_dev->name = name; + indio_dev->info = &inv_icm42600_gyro_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = inv_icm42600_gyro_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_icm42600_gyro_channels); + indio_dev->available_scan_masks = inv_icm42600_gyro_scan_masks; + indio_dev->setup_ops = &inv_icm42600_buffer_ops; + + ret = devm_iio_kfifo_buffer_setup_ext(dev, indio_dev, + &inv_icm42600_buffer_ops, + inv_icm42600_buffer_attrs); + if (ret) + return ERR_PTR(ret); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ERR_PTR(ret); + + return indio_dev; +} + +int inv_icm42600_gyro_parse_fifo(struct iio_dev *indio_dev) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42600_sensor_state *gyro_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &gyro_st->ts; + ssize_t i, size; + unsigned int no; + const void *accel, *gyro, *timestamp; + const s8 *temp; + unsigned int odr; + s64 ts_val; + /* buffer is copied to userspace, zeroing it to avoid any data leak */ + struct inv_icm42600_gyro_buffer buffer = { }; + + /* parse all fifo packets */ + for (i = 0, no = 0; i < st->fifo.count; i += size, ++no) { + size = inv_icm42600_fifo_decode_packet(&st->fifo.data[i], + &accel, &gyro, &temp, ×tamp, &odr); + /* quit if error or FIFO is empty */ + if (size <= 0) + return size; + + /* skip packet if no gyro data or data is invalid */ + if (gyro == NULL || !inv_icm42600_fifo_is_data_valid(gyro)) + continue; + + /* update odr */ + if (odr & INV_ICM42600_SENSOR_GYRO) + inv_sensors_timestamp_apply_odr(ts, st->fifo.period, + st->fifo.nb.total, no); + + memcpy(&buffer.gyro, gyro, sizeof(buffer.gyro)); + /* convert 8 bits FIFO temperature in high resolution format */ + buffer.temp = temp ? (*temp * 64) : 0; + ts_val = inv_sensors_timestamp_pop(ts); + iio_push_to_buffers_with_timestamp(indio_dev, &buffer, ts_val); + } + + return 0; +} diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c new file mode 100644 index 000000000..28552d2db --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 InvenSense, Inc. + */ + +#include +#include +#include +#include +#include +#include + +#include "inv_icm42600.h" + +static int inv_icm42600_i2c_bus_setup(struct inv_icm42600_state *st) +{ + unsigned int mask, val; + int ret; + + /* + * setup interface registers + * This register write to REG_INTF_CONFIG6 enables a spike filter that + * is impacting the line and can prevent the I2C ACK to be seen by the + * controller. So we don't test the return value. + */ + regmap_update_bits(st->map, INV_ICM42600_REG_INTF_CONFIG6, + INV_ICM42600_INTF_CONFIG6_MASK, + INV_ICM42600_INTF_CONFIG6_I3C_EN); + + ret = regmap_clear_bits(st->map, INV_ICM42600_REG_INTF_CONFIG4, + INV_ICM42600_INTF_CONFIG4_I3C_BUS_ONLY); + if (ret) + return ret; + + /* set slew rates for I2C and SPI */ + mask = INV_ICM42600_DRIVE_CONFIG_I2C_MASK | + INV_ICM42600_DRIVE_CONFIG_SPI_MASK; + val = INV_ICM42600_DRIVE_CONFIG_I2C(INV_ICM42600_SLEW_RATE_12_36NS) | + INV_ICM42600_DRIVE_CONFIG_SPI(INV_ICM42600_SLEW_RATE_12_36NS); + ret = regmap_update_bits(st->map, INV_ICM42600_REG_DRIVE_CONFIG, + mask, val); + if (ret) + return ret; + + /* disable SPI bus */ + return regmap_update_bits(st->map, INV_ICM42600_REG_INTF_CONFIG0, + INV_ICM42600_INTF_CONFIG0_UI_SIFS_CFG_MASK, + INV_ICM42600_INTF_CONFIG0_UI_SIFS_CFG_SPI_DIS); +} + +static int inv_icm42600_probe(struct i2c_client *client) +{ + const void *match; + enum inv_icm42600_chip chip; + struct regmap *regmap; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) + return -ENOTSUPP; + + match = device_get_match_data(&client->dev); + if (!match) + return -EINVAL; + chip = (uintptr_t)match; + + regmap = devm_regmap_init_i2c(client, &inv_icm42600_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return inv_icm42600_core_probe(regmap, chip, inv_icm42600_i2c_bus_setup); +} + +/* + * device id table is used to identify what device can be + * supported by this driver + */ +static const struct i2c_device_id inv_icm42600_id[] = { + { .name = "icm42600", .driver_data = INV_CHIP_ICM42600 }, + { .name = "icm42602", .driver_data = INV_CHIP_ICM42602 }, + { .name = "icm42605", .driver_data = INV_CHIP_ICM42605 }, + { .name = "icm42686", .driver_data = INV_CHIP_ICM42686 }, + { .name = "icm42622", .driver_data = INV_CHIP_ICM42622 }, + { .name = "icm42688", .driver_data = INV_CHIP_ICM42688 }, + { .name = "icm42631", .driver_data = INV_CHIP_ICM42631 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, inv_icm42600_id); + +static const struct of_device_id inv_icm42600_of_matches[] = { + { + .compatible = "invensense,icm42600", + .data = (void *)INV_CHIP_ICM42600, + }, { + .compatible = "invensense,icm42602", + .data = (void *)INV_CHIP_ICM42602, + }, { + .compatible = "invensense,icm42605", + .data = (void *)INV_CHIP_ICM42605, + }, { + .compatible = "invensense,icm42686", + .data = (void *)INV_CHIP_ICM42686, + }, { + .compatible = "invensense,icm42622", + .data = (void *)INV_CHIP_ICM42622, + }, { + .compatible = "invensense,icm42688", + .data = (void *)INV_CHIP_ICM42688, + }, { + .compatible = "invensense,icm42631", + .data = (void *)INV_CHIP_ICM42631, + }, + { } +}; +MODULE_DEVICE_TABLE(of, inv_icm42600_of_matches); + +static struct i2c_driver inv_icm42600_driver = { + .driver = { + .name = "inv-icm42600-i2c", + .of_match_table = inv_icm42600_of_matches, + .pm = pm_ptr(&inv_icm42600_pm_ops), + }, + .id_table = inv_icm42600_id, + .probe = inv_icm42600_probe, +}; +module_i2c_driver(inv_icm42600_driver); + +MODULE_AUTHOR("InvenSense, Inc."); +MODULE_DESCRIPTION("InvenSense ICM-426xx I2C driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_ICM42600"); diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c new file mode 100644 index 000000000..faf743bc6 --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 InvenSense, Inc. + */ + +#include +#include +#include +#include +#include +#include + +#include "inv_icm42600.h" + +static int inv_icm42600_spi_bus_setup(struct inv_icm42600_state *st) +{ + unsigned int mask, val; + int ret; + + /* setup interface registers */ + val = INV_ICM42600_INTF_CONFIG6_I3C_EN | + INV_ICM42600_INTF_CONFIG6_I3C_SDR_EN | + INV_ICM42600_INTF_CONFIG6_I3C_DDR_EN; + ret = regmap_update_bits(st->map, INV_ICM42600_REG_INTF_CONFIG6, + INV_ICM42600_INTF_CONFIG6_MASK, val); + if (ret) + return ret; + + ret = regmap_clear_bits(st->map, INV_ICM42600_REG_INTF_CONFIG4, + INV_ICM42600_INTF_CONFIG4_I3C_BUS_ONLY); + if (ret) + return ret; + + /* set slew rates for I2C and SPI */ + mask = INV_ICM42600_DRIVE_CONFIG_I2C_MASK | + INV_ICM42600_DRIVE_CONFIG_SPI_MASK; + val = INV_ICM42600_DRIVE_CONFIG_I2C(INV_ICM42600_SLEW_RATE_20_60NS) | + INV_ICM42600_DRIVE_CONFIG_SPI(INV_ICM42600_SLEW_RATE_INF_2NS); + ret = regmap_update_bits(st->map, INV_ICM42600_REG_DRIVE_CONFIG, + mask, val); + if (ret) + return ret; + + /* disable i2c bus */ + return regmap_update_bits(st->map, INV_ICM42600_REG_INTF_CONFIG0, + INV_ICM42600_INTF_CONFIG0_UI_SIFS_CFG_MASK, + INV_ICM42600_INTF_CONFIG0_UI_SIFS_CFG_I2C_DIS); +} + +static int inv_icm42600_probe(struct spi_device *spi) +{ + const void *match; + enum inv_icm42600_chip chip; + struct regmap *regmap; + + match = device_get_match_data(&spi->dev); + if (!match) + return -EINVAL; + chip = (uintptr_t)match; + + /* use SPI specific regmap */ + regmap = devm_regmap_init_spi(spi, &inv_icm42600_spi_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return inv_icm42600_core_probe(regmap, chip, inv_icm42600_spi_bus_setup); +} + +/* + * device id table is used to identify what device can be + * supported by this driver + */ +static const struct spi_device_id inv_icm42600_id[] = { + { .name = "icm42600", .driver_data = INV_CHIP_ICM42600 }, + { .name = "icm42602", .driver_data = INV_CHIP_ICM42602 }, + { .name = "icm42605", .driver_data = INV_CHIP_ICM42605 }, + { .name = "icm42686", .driver_data = INV_CHIP_ICM42686 }, + { .name = "icm42622", .driver_data = INV_CHIP_ICM42622 }, + { .name = "icm42688", .driver_data = INV_CHIP_ICM42688 }, + { .name = "icm42631", .driver_data = INV_CHIP_ICM42631 }, + { } +}; +MODULE_DEVICE_TABLE(spi, inv_icm42600_id); + +static const struct of_device_id inv_icm42600_of_matches[] = { + { + .compatible = "invensense,icm42600", + .data = (void *)INV_CHIP_ICM42600, + }, { + .compatible = "invensense,icm42602", + .data = (void *)INV_CHIP_ICM42602, + }, { + .compatible = "invensense,icm42605", + .data = (void *)INV_CHIP_ICM42605, + }, { + .compatible = "invensense,icm42686", + .data = (void *)INV_CHIP_ICM42686, + }, { + .compatible = "invensense,icm42622", + .data = (void *)INV_CHIP_ICM42622, + }, { + .compatible = "invensense,icm42688", + .data = (void *)INV_CHIP_ICM42688, + }, { + .compatible = "invensense,icm42631", + .data = (void *)INV_CHIP_ICM42631, + }, + { } +}; +MODULE_DEVICE_TABLE(of, inv_icm42600_of_matches); + +static struct spi_driver inv_icm42600_driver = { + .driver = { + .name = "inv-icm42600-spi", + .of_match_table = inv_icm42600_of_matches, + .pm = pm_ptr(&inv_icm42600_pm_ops), + }, + .id_table = inv_icm42600_id, + .probe = inv_icm42600_probe, +}; +module_spi_driver(inv_icm42600_driver); + +MODULE_AUTHOR("InvenSense, Inc."); +MODULE_DESCRIPTION("InvenSense ICM-426xx SPI driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_ICM42600"); diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c new file mode 100644 index 000000000..7f80bda47 --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Invensense, Inc. + */ + +#include +#include +#include +#include +#include + +#include + +#include "inv_icm42600.h" +#include "inv_icm42600_temp.h" + +static int inv_icm42600_temp_read(struct inv_icm42600_state *st, s16 *temp) +{ + struct device *dev = regmap_get_device(st->map); + __be16 *raw; + int ret; + + pm_runtime_get_sync(dev); + mutex_lock(&st->lock); + + ret = inv_icm42600_set_temp_conf(st, true, NULL); + if (ret) + goto exit; + + raw = (__be16 *)&st->buffer[0]; + ret = regmap_bulk_read(st->map, INV_ICM42600_REG_TEMP_DATA, raw, sizeof(*raw)); + if (ret) + goto exit; + + *temp = (s16)be16_to_cpup(raw); + /* + * Temperature data is invalid if both accel and gyro are off. + * Return -EBUSY in this case. + */ + if (*temp == INV_ICM42600_DATA_INVALID) + ret = -EBUSY; + +exit: + mutex_unlock(&st->lock); + pm_runtime_put_autosuspend(dev); + + return ret; +} + +int inv_icm42600_temp_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); + s16 temp; + int ret; + + if (chan->type != IIO_TEMP) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = inv_icm42600_temp_read(st, &temp); + if (ret) + return ret; + *val = temp; + return IIO_VAL_INT; + /* + * T°C = (temp / 132.48) + 25 + * Tm°C = 1000 * ((temp / 132.48) + 25) + * Tm°C = 7.548309 * temp + 25000 + * Tm°C = (temp + 3312) * 7.548309 + * scale: 100000 / 13248 ~= 7.548309 + * offset: 3312 + */ + case IIO_CHAN_INFO_SCALE: + *val = 7; + *val2 = 548309; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OFFSET: + *val = 3312; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.h b/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.h new file mode 100644 index 000000000..394118651 --- /dev/null +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020 Invensense, Inc. + */ + +#ifndef INV_ICM42600_TEMP_H_ +#define INV_ICM42600_TEMP_H_ + +#include + +#define INV_ICM42600_TEMP_CHAN(_index) \ + { \ + .type = IIO_TEMP, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + }, \ + } + +int inv_icm42600_temp_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask); + +#endif diff --git a/drivers/iio/imu/inv_icm42607/Kconfig b/drivers/iio/imu/inv_icm42607/Kconfig new file mode 100644 index 000000000..23f461f57 --- /dev/null +++ b/drivers/iio/imu/inv_icm42607/Kconfig @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +config INV_ICM42607 + tristate + select IIO_BUFFER + select IIO_INV_SENSORS_TIMESTAMP + +config INV_ICM42607_I2C + tristate "InvenSense ICM-42607 I2C driver" + depends on I2C + select INV_ICM42607 + select REGMAP_I2C + help + This driver supports the InvenSense ICM-42607 motion tracking + device over I2C. + + This driver can be built as a module. The module will be called + inv-icm42607-i2c. + +config INV_ICM42607_SPI + tristate "InvenSense ICM-42607 SPI driver" + depends on SPI_MASTER + select INV_ICM42607 + select REGMAP_SPI + help + This driver supports the InvenSense ICM-42607 motion tracking + device over SPI. + + This driver can be built as a module. The module will be called + inv-icm42607-spi. diff --git a/drivers/iio/imu/inv_icm42607/Makefile b/drivers/iio/imu/inv_icm42607/Makefile new file mode 100644 index 000000000..7b907e019 --- /dev/null +++ b/drivers/iio/imu/inv_icm42607/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +obj-$(CONFIG_INV_ICM42607) += inv-icm42607.o +inv-icm42607-y += inv_icm42607_core.o +inv-icm42607-y += inv_icm42607_gyro.o +inv-icm42607-y += inv_icm42607_accel.o +inv-icm42607-y += inv_icm42607_temp.o + +obj-$(CONFIG_INV_ICM42607_I2C) += inv-icm42607-i2c.o +inv-icm42607-i2c-y += inv_icm42607_i2c.o + +obj-$(CONFIG_INV_ICM42607_SPI) += inv-icm42607-spi.o +inv-icm42607-spi-y += inv_icm42607_spi.o diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607.h b/drivers/iio/imu/inv_icm42607/inv_icm42607.h new file mode 100644 index 000000000..4d51b0da1 --- /dev/null +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607.h @@ -0,0 +1,421 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2026 InvenSense, Inc. + */ + +#ifndef INV_ICM42607_H_ +#define INV_ICM42607_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* + * Serial bus slew rates. Rates are expressed as range between the two + * values with the midpoint as the typical rate. For the final value of + * 2ns, 2ns is considered the max value with no expressed minimum or + * typical value. + */ +enum inv_icm42607_slew_rate { + INV_ICM42607_SLEW_RATE_20_60NS = 0, + INV_ICM42607_SLEW_RATE_12_36NS = 1, + INV_ICM42607_SLEW_RATE_6_19NS = 2, + INV_ICM42607_SLEW_RATE_4_14NS = 3, + INV_ICM42607_SLEW_RATE_2_6NS = 4, + INV_ICM42607_SLEW_RATE_2NS = 5, + INV_ICM42607_SLEW_RATE_NB +}; + +enum inv_icm42607_sensor_mode { + INV_ICM42607_SENSOR_MODE_OFF = 0, + INV_ICM42607_SENSOR_MODE_STANDBY = 1, + INV_ICM42607_SENSOR_MODE_LOW_POWER = 2, + INV_ICM42607_SENSOR_MODE_LOW_NOISE = 3, + INV_ICM42607_SENSOR_MODE_NB +}; + +/* gyroscope fullscale values */ +enum inv_icm42607_gyro_fs { + INV_ICM42607_GYRO_FS_2000DPS = 0, + INV_ICM42607_GYRO_FS_1000DPS = 1, + INV_ICM42607_GYRO_FS_500DPS = 2, + INV_ICM42607_GYRO_FS_250DPS = 3, + INV_ICM42607_GYRO_FS_NB +}; + +/* accelerometer fullscale values */ +enum inv_icm42607_accel_fs { + INV_ICM42607_ACCEL_FS_16G = 0, + INV_ICM42607_ACCEL_FS_8G = 1, + INV_ICM42607_ACCEL_FS_4G = 2, + INV_ICM42607_ACCEL_FS_2G = 3, + INV_ICM42607_ACCEL_FS_NB +}; + +/* ODR values - Note Gyro does not support ODR less than 12.5Hz */ +enum inv_icm42607_odr { + INV_ICM42607_ODR_1600HZ = 5, + INV_ICM42607_ODR_800HZ = 6, + INV_ICM42607_ODR_400HZ = 7, + INV_ICM42607_ODR_200HZ = 8, + INV_ICM42607_ODR_100HZ = 9, + INV_ICM42607_ODR_50HZ = 10, + INV_ICM42607_ODR_25HZ = 11, + INV_ICM42607_ODR_12_5HZ = 12, + INV_ICM42607_ODR_6_25HZ_LP = 13, + INV_ICM42607_ODR_3_125HZ_LP = 14, + INV_ICM42607_ODR_1_5625HZ_LP = 15, + INV_ICM42607_ODR_NB +}; + +/* Low-Noise mode sensor data filter (bandwidth) */ +enum inv_icm42607_filter_bw { + INV_ICM42607_FILTER_BYPASS = 0, + INV_ICM42607_FILTER_BW_180HZ = 1, + INV_ICM42607_FILTER_BW_121HZ = 2, + INV_ICM42607_FILTER_BW_73HZ = 3, + INV_ICM42607_FILTER_BW_53HZ = 4, + INV_ICM42607_FILTER_BW_34HZ = 5, + INV_ICM42607_FILTER_BW_25HZ = 6, + INV_ICM42607_FILTER_BW_16HZ = 7, + INV_ICM42607_FILTER_BW_NB +}; + +/* Low-Power mode sensor data filter (averaging) */ +enum inv_icm42607_filter_avg { + INV_ICM42607_FILTER_AVG_2X = 0, + INV_ICM42607_FILTER_AVG_4X = 1, + INV_ICM42607_FILTER_AVG_8X = 2, + INV_ICM42607_FILTER_AVG_16X = 3, + INV_ICM42607_FILTER_AVG_32X = 4, + INV_ICM42607_FILTER_AVG_64X = 5, + /* values 6 and 7 also correspond to 64x. */ +}; + +/* Temperature sensor data filter (bandwidth) */ +enum inv_icm42607_temp_filter_bw { + INV_ICM42607_TEMP_FILTER_BYPASS = 0, + INV_ICM42607_TEMP_FILTER_BW_180HZ = 1, + INV_ICM42607_TEMP_FILTER_BW_72HZ = 2, + INV_ICM42607_TEMP_FILTER_BW_34HZ = 3, + INV_ICM42607_TEMP_FILTER_BW_16HZ = 4, + INV_ICM42607_TEMP_FILTER_BW_8HZ = 5, + INV_ICM42607_TEMP_FILTER_BW_4HZ = 6, + /* value 7 also corresponds to 4Hz */ +}; + +/* Signed so that negative values can signify an invalid condition. */ +struct inv_icm42607_sensor_conf { + int mode; + int fs; + int odr; + int filter; +}; +#define INV_ICM42607_SENSOR_CONF_INIT { -1, -1, -1, -1 } + +struct inv_icm42607_conf { + struct inv_icm42607_sensor_conf gyro; + struct inv_icm42607_sensor_conf accel; + ktime_t gyro_stop; /* earliest time to stop the gyro */ +}; + +struct inv_icm42607_hw { + const char *name; + const struct inv_icm42607_conf *conf; + u8 whoami; +}; + +/** + * struct inv_icm42607_state - driver state variables + * @hw: Hardware specific data. + * @lock: lock for serializing multiple registers access. + * @map: regmap pointer. + * @indio_accel: accelerometer IIO device. + * @indio_gyro: gyroscope IIO device. + * @vddio_supply: I/O voltage regulator for the chip. + * @vddio_en: I/O voltage status for runtime PM. + * @conf: chip sensors configurations. + * @orientation: sensor chip orientation relative to main hardware. + */ +struct inv_icm42607_state { + const struct inv_icm42607_hw *hw; + struct mutex lock; + struct regmap *map; + struct iio_dev *indio_accel; + struct iio_dev *indio_gyro; + struct regulator *vddio_supply; + bool vddio_en; + struct inv_icm42607_conf conf; + struct iio_mount_matrix orientation; +}; + +/** + * struct inv_icm42607_sensor_state - sensor state variables + * @power_mode: sensor requested power mode (for common frequencies) + * @filter: sensor filter. + */ +struct inv_icm42607_sensor_state { + enum inv_icm42607_sensor_mode power_mode; + int filter; +}; + +/* Virtual register addresses: @bank on MSB (4 upper bits), @address on LSB */ + +/* Register Map for User Bank 0 */ +#define INV_ICM42607_REG_MCLK_RDY 0x00 + +#define INV_ICM42607_REG_DEVICE_CONFIG 0x01 +#define INV_ICM42607_DEVICE_CONFIG_SPI_AP_4WIRE BIT(2) +#define INV_ICM42607_DEVICE_CONFIG_SPI_MODE BIT(0) + +#define INV_ICM42607_REG_SIGNAL_PATH_RESET 0x02 +#define INV_ICM42607_SIGNAL_PATH_RESET_SOFT_RESET BIT(4) +#define INV_ICM42607_SIGNAL_PATH_RESET_FIFO_FLUSH BIT(2) + +#define INV_ICM42607_REG_DRIVE_CONFIG1 0x03 +#define INV_ICM42607_DRIVE_CONFIG1_I3C_DDR_MASK GENMASK(5, 3) +#define INV_ICM42607_DRIVE_CONFIG1_I3C_SDR_MASK GENMASK(2, 0) + +#define INV_ICM42607_REG_DRIVE_CONFIG2 0x04 +#define INV_ICM42607_DRIVE_CONFIG2_I2C_MASK GENMASK(5, 3) +#define INV_ICM42607_DRIVE_CONFIG2_ALL_MASK GENMASK(2, 0) + +#define INV_ICM42607_REG_DRIVE_CONFIG3 0x05 +#define INV_ICM42607_DRIVE_CONFIG3_SPI_MASK GENMASK(2, 0) + +#define INV_ICM42607_REG_INT_CONFIG 0x06 +#define INV_ICM42607_INT_CONFIG_INT2_LATCHED BIT(5) +#define INV_ICM42607_INT_CONFIG_INT2_PUSH_PULL BIT(4) +#define INV_ICM42607_INT_CONFIG_INT2_ACTIVE_HIGH BIT(3) +#define INV_ICM42607_INT_CONFIG_INT2_ACTIVE_LOW 0x00 +#define INV_ICM42607_INT_CONFIG_INT1_LATCHED BIT(2) +#define INV_ICM42607_INT_CONFIG_INT1_PUSH_PULL BIT(1) +#define INV_ICM42607_INT_CONFIG_INT1_ACTIVE_HIGH BIT(0) +#define INV_ICM42607_INT_CONFIG_INT1_ACTIVE_LOW 0x00 + +/* all sensor data are 16 bits (2 registers wide) in big-endian */ +#define INV_ICM42607_REG_TEMP_DATA1 0x09 +#define INV_ICM42607_REG_TEMP_DATA0 0x0A +#define INV_ICM42607_REG_ACCEL_DATA_X1 0x0B +#define INV_ICM42607_REG_ACCEL_DATA_X0 0x0C +#define INV_ICM42607_REG_ACCEL_DATA_Y1 0x0D +#define INV_ICM42607_REG_ACCEL_DATA_Y0 0x0E +#define INV_ICM42607_REG_ACCEL_DATA_Z1 0x0F +#define INV_ICM42607_REG_ACCEL_DATA_Z0 0x10 +#define INV_ICM42607_REG_GYRO_DATA_X1 0x11 +#define INV_ICM42607_REG_GYRO_DATA_X0 0x12 +#define INV_ICM42607_REG_GYRO_DATA_Y1 0x13 +#define INV_ICM42607_REG_GYRO_DATA_Y0 0x14 +#define INV_ICM42607_REG_GYRO_DATA_Z1 0x15 +#define INV_ICM42607_REG_GYRO_DATA_Z0 0x16 +#define INV_ICM42607_DATA_INVALID -32768 + +#define INV_ICM42607_REG_TMST_FSYNCH 0x17 +#define INV_ICM42607_REG_TMST_FSYNCL 0x18 + +/* APEX Data Registers */ +#define INV_ICM42607_REG_APEX_DATA0 0x31 +#define INV_ICM42607_REG_APEX_DATA1 0x32 +#define INV_ICM42607_REG_APEX_DATA2 0x33 +#define INV_ICM42607_REG_APEX_DATA3 0x34 +#define INV_ICM42607_REG_APEX_DATA4 0x1D +#define INV_ICM42607_REG_APEX_DATA5 0x1E + +#define INV_ICM42607_REG_PWR_MGMT0 0x1F +#define INV_ICM42607_PWR_MGMT0_ACCEL_LP_CLK_SEL BIT(7) +#define INV_ICM42607_PWR_MGMT0_IDLE BIT(4) +#define INV_ICM42607_PWR_MGMT0_GYRO_MODE_MASK GENMASK(3, 2) +#define INV_ICM42607_PWR_MGMT0_ACCEL_MODE_MASK GENMASK(1, 0) + +#define INV_ICM42607_REG_GYRO_CONFIG0 0x20 +#define INV_ICM42607_GYRO_CONFIG0_FS_SEL_MASK GENMASK(6, 5) +#define INV_ICM42607_GYRO_CONFIG0_ODR_MASK GENMASK(3, 0) + +#define INV_ICM42607_REG_ACCEL_CONFIG0 0x21 +#define INV_ICM42607_ACCEL_CONFIG0_FS_SEL_MASK GENMASK(6, 5) +#define INV_ICM42607_ACCEL_CONFIG0_ODR_MASK GENMASK(3, 0) + +#define INV_ICM42607_REG_TEMP_CONFIG0 0x22 +#define INV_ICM42607_TEMP_CONFIG0_FILTER_MASK GENMASK(6, 4) + +#define INV_ICM42607_REG_GYRO_CONFIG1 0x23 +#define INV_ICM42607_GYRO_CONFIG1_FILTER_MASK GENMASK(2, 0) + +#define INV_ICM42607_REG_ACCEL_CONFIG1 0x24 +#define INV_ICM42607_ACCEL_CONFIG1_AVG_MASK GENMASK(6, 4) +#define INV_ICM42607_ACCEL_CONFIG1_FILTER_MASK GENMASK(2, 0) + +#define INV_ICM42607_REG_APEX_CONFIG0 0x25 +#define INV_ICM42607_APEX_CONFIG0_DMP_POWER_SAVE_EN BIT(3) +#define INV_ICM42607_APEX_CONFIG0_DMP_INIT_EN BIT(2) +#define INV_ICM42607_APEX_CONFIG0_DMP_MEM_RESET_EN BIT(0) + +#define INV_ICM42607_REG_APEX_CONFIG1 0x26 +#define INV_ICM42607_APEX_CONFIG1_SMD_ENABLE BIT(6) +#define INV_ICM42607_APEX_CONFIG1_FF_ENABLE BIT(5) +#define INV_ICM42607_APEX_CONFIG1_TILT_ENABLE BIT(4) +#define INV_ICM42607_APEX_CONFIG1_PED_ENABLE BIT(3) +#define INV_ICM42607_APEX_CONFIG1_DMP_ODR_MASK GENMASK(1, 0) + +#define INV_ICM42607_REG_WOM_CONFIG 0x27 +#define INV_ICM42607_WOM_CONFIG_INT_DUR_MASK GENMASK(4, 3) +#define INV_ICM42607_WOM_CONFIG_INT_MODE BIT(2) +#define INV_ICM42607_WOM_CONFIG_MODE BIT(1) +#define INV_ICM42607_WOM_CONFIG_EN BIT(0) + +#define INV_ICM42607_REG_FIFO_CONFIG1 0x28 +#define INV_ICM42607_FIFO_CONFIG1_MODE BIT(1) +#define INV_ICM42607_FIFO_CONFIG1_BYPASS BIT(0) + +#define INV_ICM42607_REG_FIFO_CONFIG2 0x29 +#define INV_ICM42607_REG_FIFO_CONFIG3 0x2A +#define INV_ICM42607_FIFO_WATERMARK_VAL(_wm) \ + cpu_to_le16((_wm) & GENMASK(11, 0)) +/* FIFO is 2048 bytes, let 12 samples for reading latency */ +#define INV_ICM42607_FIFO_WATERMARK_MAX (2048 - 12 * 16) +#define INV_ICM42607_FIFO_1SENSOR_PACKET_SIZE 8 +#define INV_ICM42607_FIFO_2SENSORS_PACKET_SIZE 16 + +#define INV_ICM42607_REG_INT_SOURCE0 0x2B +#define INV_ICM42607_INT_SOURCE0_ST_INT1_EN BIT(7) +#define INV_ICM42607_INT_SOURCE0_FSYNC_INT1_EN BIT(6) +#define INV_ICM42607_INT_SOURCE0_PLL_RDY_INT1_EN BIT(5) +#define INV_ICM42607_INT_SOURCE0_RESET_DONE_INT1_EN BIT(4) +#define INV_ICM42607_INT_SOURCE0_DRDY_INT1_EN BIT(3) +#define INV_ICM42607_INT_SOURCE0_FIFO_THS_INT1_EN BIT(2) +#define INV_ICM42607_INT_SOURCE0_FIFO_FULL_INT1_EN BIT(1) +#define INV_ICM42607_INT_SOURCE0_AGC_RDY_INT1_EN BIT(0) + +#define INV_ICM42607_REG_INT_SOURCE1 0x2C +#define INV_ICM42607_INT_SOURCE1_I3C_ERROR_INT1_EN BIT(6) +#define INV_ICM42607_INT_SOURCE1_SMD_INT1_EN BIT(3) +#define INV_ICM42607_INT_SOURCE1_WOM_INT1_EN GENMASK(2, 0) + +#define INV_ICM42607_REG_INT_SOURCE3 0x2D +#define INV_ICM42607_INT_SOURCE3_ST_INT2_EN BIT(7) +#define INV_ICM42607_INT_SOURCE3_FSYNC_INT2_EN BIT(6) +#define INV_ICM42607_INT_SOURCE3_PLL_RDY_INT2_EN BIT(5) +#define INV_ICM42607_INT_SOURCE3_RESET_DONE_INT2_EN BIT(4) +#define INV_ICM42607_INT_SOURCE3_DRDY_INT2_EN BIT(3) +#define INV_ICM42607_INT_SOURCE3_FIFO_THS_INT2_EN BIT(2) +#define INV_ICM42607_INT_SOURCE3_FIFO_FULL_INT2_EN BIT(1) +#define INV_ICM42607_INT_SOURCE3_AGC_RDY_INT2_EN BIT(0) + +#define INV_ICM42607_REG_INT_SOURCE4 0x2E +#define INV_ICM42607_INT_SOURCE4_I3C_ERROR_INT2_EN BIT(6) +#define INV_ICM42607_INT_SOURCE4_SMD_INT2_EN BIT(3) +#define INV_ICM42607_INT_SOURCE4_WOM_Z_INT2_EN BIT(2) +#define INV_ICM42607_INT_SOURCE4_WOM_Y_INT2_EN BIT(1) +#define INV_ICM42607_INT_SOURCE4_WOM_X_INT2_EN BIT(0) + +#define INV_ICM42607_REG_FIFO_LOST_PKT0 0x2F +#define INV_ICM42607_REG_FIFO_LOST_PKT1 0x30 + +#define INV_ICM42607_REG_INTF_CONFIG0 0x35 +#define INV_ICM42607_INTF_CONFIG0_FIFO_COUNT_FORMAT BIT(6) +#define INV_ICM42607_INTF_CONFIG0_FIFO_COUNT_ENDIAN BIT(5) +#define INV_ICM42607_INTF_CONFIG0_SENSOR_DATA_ENDIAN BIT(4) +#define INV_ICM42607_INTF_CONFIG0_UI_SIFS_CFG_MASK GENMASK(1, 0) +#define INV_ICM42607_INTF_CONFIG0_UI_SIFS_CFG_SPI_DIS 2 +#define INV_ICM42607_INTF_CONFIG0_UI_SIFS_CFG_I2C_DIS 3 + +#define INV_ICM42607_REG_INTF_CONFIG1 0x36 +#define INV_ICM42607_INTF_CONFIG1_I3C_SDR_EN BIT(3) +#define INV_ICM42607_INTF_CONFIG1_I3C_DDR_EN BIT(2) +#define INV_ICM42607_INTF_CONFIG1_CLKSEL_MASK GENMASK(1, 0) +#define INV_ICM42607_INTF_CONFIG1_CLKSEL_INT 0 +#define INV_ICM42607_INTF_CONFIG1_CLKSEL_PLL 1 +#define INV_ICM42607_INTF_CONFIG1_CLKSEL_OFF 2 + +#define INV_ICM42607_REG_INT_STATUS_DRDY 0x39 +#define INV_ICM42607_INT_STATUS_DRDY_DATA_RDY BIT(0) + +#define INV_ICM42607_REG_INT_STATUS 0x3A +#define INV_ICM42607_INT_STATUS_ST BIT(7) +#define INV_ICM42607_INT_STATUS_FSYNC BIT(6) +#define INV_ICM42607_INT_STATUS_PLL_RDY BIT(5) +#define INV_ICM42607_INT_STATUS_RESET_DONE BIT(4) +#define INV_ICM42607_INT_STATUS_FIFO_THS BIT(2) +#define INV_ICM42607_INT_STATUS_FIFO_FULL BIT(1) +#define INV_ICM42607_INT_STATUS_AGC_RDY BIT(0) + +#define INV_ICM42607_REG_INT_STATUS2 0x3B +#define INV_ICM42607_INT_STATUS2_SMD BIT(3) +#define INV_ICM42607_INT_STATUS2_WOM_INT GENMASK(2, 0) + +#define INV_ICM42607_REG_INT_STATUS3 0x3C +#define INV_ICM42607_INT_STATUS3_STEP_DET BIT(5) +#define INV_ICM42607_INT_STATUS3_STEP_CNT_OVF BIT(4) +#define INV_ICM42607_INT_STATUS3_TILT_DET BIT(3) +#define INV_ICM42607_INT_STATUS3_FF_DET BIT(2) + +/* + * FIFO access registers + * FIFO count is 16 bits (2 registers) big-endian + * FIFO data is a continuous read register to read FIFO content + */ +#define INV_ICM42607_REG_FIFO_COUNTH 0x3D +#define INV_ICM42607_REG_FIFO_COUNTL 0x3E +#define INV_ICM42607_REG_FIFO_DATA 0x3F + +#define INV_ICM42607_REG_WHOAMI 0x75 +#define INV_ICM42607P_WHOAMI 0x60 +#define INV_ICM42607_WHOAMI 0x67 + +/* + * Timings as listed in section 3 of datasheet, all values listed in datasheet + * in ms except temp startup time... setting all values in us and using + * USEC_PER_MSEC to convert from values displayed in datasheet. + */ +#define INV_ICM42607_POWER_UP_TIME_US (100 * USEC_PER_MSEC) +#define INV_ICM42607_RESET_TIME_US (1 * USEC_PER_MSEC) +#define INV_ICM42607_ACCEL_STARTUP_TIME_US (10 * USEC_PER_MSEC) +#define INV_ICM42607_GYRO_STARTUP_TIME_US (30 * USEC_PER_MSEC) +#define INV_ICM42607_GYRO_STOP_TIME_US (45 * USEC_PER_MSEC) +#define INV_ICM42607_TEMP_STARTUP_TIME_US 77 + +/* + * Suspend delay assumed from other icm42600 series device, not + * documented in datasheet. + */ +#define INV_ICM42607_SUSPEND_DELAY_MS (2 * MSEC_PER_SEC) + +typedef int (*inv_icm42607_bus_setup)(struct inv_icm42607_state *); + +extern const struct regmap_config inv_icm42607_regmap_config; +extern const struct inv_icm42607_hw inv_icm42607_hw_data; +extern const struct inv_icm42607_hw inv_icm42607p_hw_data; +extern const struct dev_pm_ops inv_icm42607_pm_ops; + +const struct iio_mount_matrix * +inv_icm42607_get_mount_matrix(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan); + +int inv_icm42607_get_pwr_mgmt0(struct inv_icm42607_state *st, + enum inv_icm42607_sensor_mode *gyro, + enum inv_icm42607_sensor_mode *accel); + +int inv_icm42607_set_sensor_conf(struct inv_icm42607_state *st, + struct inv_icm42607_sensor_conf *conf, + enum iio_chan_type chan_type); + +int inv_icm42607_read_sensor(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + s16 *val); + +int inv_icm42607_core_probe(struct regmap *regmap, + const struct inv_icm42607_hw *hw, + inv_icm42607_bus_setup bus_setup); + +struct iio_dev *inv_icm42607_gyro_init(struct inv_icm42607_state *st); + +struct iio_dev *inv_icm42607_accel_init(struct inv_icm42607_state *st); + +#endif diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_accel.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_accel.c new file mode 100644 index 000000000..0b3f035c2 --- /dev/null +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_accel.c @@ -0,0 +1,316 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 InvenSense, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "inv_icm42607.h" +#include "inv_icm42607_temp.h" + +#define INV_ICM42607_ACCEL_CHAN(_modifier, _index, _ext_info) \ +{ \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = _modifier, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + .ext_info = _ext_info, \ +} + +enum inv_icm42607_accel_scan { + INV_ICM42607_ACCEL_SCAN_X, + INV_ICM42607_ACCEL_SCAN_Y, + INV_ICM42607_ACCEL_SCAN_Z, + INV_ICM42607_ACCEL_SCAN_TEMP, +}; + +static const struct iio_chan_spec_ext_info inv_icm42607_accel_ext_infos[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, inv_icm42607_get_mount_matrix), + { } +}; + +static const struct iio_chan_spec inv_icm42607_accel_channels[] = { + INV_ICM42607_ACCEL_CHAN(IIO_MOD_X, INV_ICM42607_ACCEL_SCAN_X, + inv_icm42607_accel_ext_infos), + INV_ICM42607_ACCEL_CHAN(IIO_MOD_Y, INV_ICM42607_ACCEL_SCAN_Y, + inv_icm42607_accel_ext_infos), + INV_ICM42607_ACCEL_CHAN(IIO_MOD_Z, INV_ICM42607_ACCEL_SCAN_Z, + inv_icm42607_accel_ext_infos), + INV_ICM42607_TEMP_CHAN(INV_ICM42607_ACCEL_SCAN_TEMP), +}; + +static const int inv_icm42607_accel_scale_nano[][2] = { + [INV_ICM42607_ACCEL_FS_16G] = { 0, 4788403 }, + [INV_ICM42607_ACCEL_FS_8G] = { 0, 2394202 }, + [INV_ICM42607_ACCEL_FS_4G] = { 0, 1197101 }, + [INV_ICM42607_ACCEL_FS_2G] = { 0, 598550 }, +}; + +static int inv_icm42607_accel_read_scale(struct iio_dev *indio_dev, + int *val, int *val2) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + unsigned int idx; + + guard(mutex)(&st->lock); + + idx = st->conf.accel.fs; + + *val = inv_icm42607_accel_scale_nano[idx][0]; + *val2 = inv_icm42607_accel_scale_nano[idx][1]; + return IIO_VAL_INT_PLUS_NANO; +} + +static int inv_icm42607_accel_write_scale(struct iio_dev *indio_dev, + int val, int val2) +{ + struct inv_icm42607_sensor_conf conf = INV_ICM42607_SENSOR_CONF_INIT; + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + size_t scales_len = ARRAY_SIZE(inv_icm42607_accel_scale_nano); + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + int ret; + + for (idx = 0; idx < scales_len; idx++) { + if (val == inv_icm42607_accel_scale_nano[idx][0] && + val2 == inv_icm42607_accel_scale_nano[idx][1]) + break; + } + if (idx == scales_len) + return -EINVAL; + + conf.fs = idx; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + guard(mutex)(&st->lock); + + return inv_icm42607_set_sensor_conf(st, &conf, IIO_ACCEL); +} + +/* IIO format int + micro , values 0-4 reserved. */ +static const int inv_icm42607_accel_odr[][2] = { + [INV_ICM42607_ODR_1600HZ] = { 1600, 0 }, + [INV_ICM42607_ODR_800HZ] = { 800, 0 }, + [INV_ICM42607_ODR_400HZ] = { 400, 0 }, + [INV_ICM42607_ODR_200HZ] = { 200, 0 }, + [INV_ICM42607_ODR_100HZ] = { 100, 0 }, + [INV_ICM42607_ODR_50HZ] = { 50, 0 }, + [INV_ICM42607_ODR_25HZ] = { 25, 0 }, + [INV_ICM42607_ODR_12_5HZ] = { 12, 500000 }, + [INV_ICM42607_ODR_6_25HZ_LP] = { 6, 250000 }, + [INV_ICM42607_ODR_3_125HZ_LP] = { 3, 125000 }, + [INV_ICM42607_ODR_1_5625HZ_LP] = { 1, 562500 }, +}; + +static int inv_icm42607_accel_read_odr(struct inv_icm42607_state *st, + int *val, int *val2) +{ + unsigned int odr; + unsigned int i; + + guard(mutex)(&st->lock); + + odr = st->conf.accel.odr; + + for (i = INV_ICM42607_ODR_1600HZ; i < ARRAY_SIZE(inv_icm42607_accel_odr); i++) { + if (i == odr) + break; + } + if (i == ARRAY_SIZE(inv_icm42607_accel_odr)) + return -EINVAL; + + *val = inv_icm42607_accel_odr[i][0]; + *val2 = inv_icm42607_accel_odr[i][1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int inv_icm42607_accel_write_odr(struct iio_dev *indio_dev, + int val, int val2) +{ + struct inv_icm42607_sensor_conf conf = INV_ICM42607_SENSOR_CONF_INIT; + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + int ret; + + for (idx = INV_ICM42607_ODR_1600HZ; + idx < ARRAY_SIZE(inv_icm42607_accel_odr); idx++) { + if (val == inv_icm42607_accel_odr[idx][0] && + val2 == inv_icm42607_accel_odr[idx][1]) + break; + } + if (idx == ARRAY_SIZE(inv_icm42607_accel_odr)) + return -EINVAL; + + conf.odr = idx; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + guard(mutex)(&st->lock); + + return inv_icm42607_set_sensor_conf(st, &conf, IIO_ACCEL); +} + +static int inv_icm42607_accel_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + s16 data; + int ret; + + switch (chan->type) { + case IIO_ACCEL: + break; + case IIO_TEMP: + if (mask != IIO_CHAN_INFO_SAMP_FREQ) + return inv_icm42607_temp_read_raw(indio_dev, chan, + val, val2, mask); + break; + default: + return -EINVAL; + } + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = inv_icm42607_read_sensor(indio_dev, chan, &data); + if (ret) + return ret; + *val = data; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + return inv_icm42607_accel_read_scale(indio_dev, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return inv_icm42607_accel_read_odr(st, val, val2); + default: + return -EINVAL; + } +} + +static int inv_icm42607_accel_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, + int *type, int *length, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (chan->type != IIO_ACCEL) + return -EINVAL; + *vals = (const int *)inv_icm42607_accel_scale_nano; + *type = IIO_VAL_INT_PLUS_NANO; + *length = ARRAY_SIZE(inv_icm42607_accel_scale_nano) * 2; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = (const int *)inv_icm42607_accel_odr[INV_ICM42607_ODR_1600HZ]; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = (ARRAY_SIZE(inv_icm42607_accel_odr) - + INV_ICM42607_ODR_1600HZ) * 2; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int inv_icm42607_accel_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (chan->type != IIO_ACCEL) + return -EINVAL; + ret = inv_icm42607_accel_write_scale(indio_dev, val, val2); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + return inv_icm42607_accel_write_odr(indio_dev, val, val2); + default: + return -EINVAL; + } +} + +static int inv_icm42607_accel_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (chan->type != IIO_ACCEL) + return -EINVAL; + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static const struct iio_info inv_icm42607_accel_info = { + .read_raw = inv_icm42607_accel_read_raw, + .read_avail = inv_icm42607_accel_read_avail, + .write_raw = inv_icm42607_accel_write_raw, + .write_raw_get_fmt = inv_icm42607_accel_write_raw_get_fmt, +}; + +struct iio_dev *inv_icm42607_accel_init(struct inv_icm42607_state *st) +{ + struct device *dev = regmap_get_device(st->map); + struct inv_icm42607_sensor_state *accel_st; + struct iio_dev *indio_dev; + const char *name; + int ret; + + name = devm_kasprintf(dev, GFP_KERNEL, "%s-accel", st->hw->name); + if (!name) + return ERR_PTR(-ENOMEM); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*accel_st)); + if (!indio_dev) + return ERR_PTR(-ENOMEM); + + accel_st = iio_priv(indio_dev); + accel_st->power_mode = INV_ICM42607_SENSOR_MODE_LOW_NOISE; + accel_st->filter = INV_ICM42607_FILTER_BW_73HZ; + + indio_dev->name = name; + indio_dev->info = &inv_icm42607_accel_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = inv_icm42607_accel_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_icm42607_accel_channels); + iio_device_set_drvdata(indio_dev, st); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ERR_PTR(ret); + + return indio_dev; +} diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c new file mode 100644 index 000000000..190e998f7 --- /dev/null +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c @@ -0,0 +1,702 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 InvenSense, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "inv_icm42607.h" + +static bool inv_icm42607_is_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case INV_ICM42607_REG_MCLK_RDY ... INV_ICM42607_REG_INT_CONFIG: + case INV_ICM42607_REG_TEMP_DATA1 ... INV_ICM42607_REG_TMST_FSYNCL: + case INV_ICM42607_REG_APEX_DATA4 ... INV_ICM42607_REG_INTF_CONFIG1: + case INV_ICM42607_REG_INT_STATUS_DRDY ... INV_ICM42607_REG_FIFO_DATA: + case INV_ICM42607_REG_WHOAMI: + return true; + } + + return false; +} + +static bool inv_icm42607_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case INV_ICM42607_REG_DEVICE_CONFIG ... INV_ICM42607_REG_INT_CONFIG: + case INV_ICM42607_REG_PWR_MGMT0 ... INV_ICM42607_REG_INT_SOURCE4: + case INV_ICM42607_REG_INTF_CONFIG0 ... INV_ICM42607_REG_INTF_CONFIG1: + return true; + } + + return false; +} + +static bool inv_icm42607_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case INV_ICM42607_REG_MCLK_RDY: + case INV_ICM42607_REG_SIGNAL_PATH_RESET: + case INV_ICM42607_REG_TEMP_DATA1 ... INV_ICM42607_REG_APEX_DATA5: + case INV_ICM42607_REG_APEX_CONFIG0: + case INV_ICM42607_REG_FIFO_LOST_PKT0 ... INV_ICM42607_REG_APEX_DATA3: + case INV_ICM42607_REG_INT_STATUS_DRDY: + case INV_ICM42607_REG_INT_STATUS ... INV_ICM42607_REG_FIFO_DATA: + return true; + } + + return false; +} + +const struct regmap_config inv_icm42607_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = inv_icm42607_is_writeable_reg, + .readable_reg = inv_icm42607_is_readable_reg, + .volatile_reg = inv_icm42607_is_volatile_reg, + .max_register = INV_ICM42607_REG_WHOAMI, + .cache_type = REGCACHE_MAPLE, +}; +EXPORT_SYMBOL_NS_GPL(inv_icm42607_regmap_config, "IIO_ICM42607"); + +/* chip initial default configuration */ +static const struct inv_icm42607_conf inv_icm42607_default_conf = { + .gyro = { + .mode = INV_ICM42607_SENSOR_MODE_OFF, + .fs = INV_ICM42607_GYRO_FS_1000DPS, + .odr = INV_ICM42607_ODR_100HZ, + .filter = INV_ICM42607_FILTER_BW_25HZ, + }, + .accel = { + .mode = INV_ICM42607_SENSOR_MODE_OFF, + .fs = INV_ICM42607_ACCEL_FS_4G, + .odr = INV_ICM42607_ODR_100HZ, + .filter = INV_ICM42607_FILTER_BW_25HZ, + }, +}; + +const struct inv_icm42607_hw inv_icm42607_hw_data = { + .whoami = INV_ICM42607_WHOAMI, + .name = "icm42607", + .conf = &inv_icm42607_default_conf, +}; +EXPORT_SYMBOL_NS_GPL(inv_icm42607_hw_data, "IIO_ICM42607"); + +const struct inv_icm42607_hw inv_icm42607p_hw_data = { + .whoami = INV_ICM42607P_WHOAMI, + .name = "icm42607p", + .conf = &inv_icm42607_default_conf, +}; +EXPORT_SYMBOL_NS_GPL(inv_icm42607p_hw_data, "IIO_ICM42607"); + +const struct iio_mount_matrix * +inv_icm42607_get_mount_matrix(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + const struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + + return &st->orientation; +} + +static u32 inv_icm42607_odr_to_period_us(enum inv_icm42607_odr odr) +{ + static const u32 odr_periods[INV_ICM42607_ODR_NB] = { + /* Reserved values */ + 0, 0, 0, 0, 0, + /* 1600Hz */ + 625, + /* 800Hz */ + 1250, + /* 400Hz */ + 2500, + /* 200Hz */ + 5000, + /* 100 Hz */ + 10000, + /* 50Hz */ + 20000, + /* 25Hz */ + 40000, + /* 12.5Hz */ + 80000, + /* 6.25Hz */ + 160000, + /* 3.125Hz */ + 320000, + /* 1.5625Hz */ + 640000, + }; + + odr = clamp(odr, INV_ICM42607_ODR_1600HZ, INV_ICM42607_ODR_1_5625HZ_LP); + + return odr_periods[odr]; +} + +int inv_icm42607_get_pwr_mgmt0(struct inv_icm42607_state *st, + enum inv_icm42607_sensor_mode *gyro, + enum inv_icm42607_sensor_mode *accel) +{ + unsigned int val; + int ret; + + ret = regmap_read(st->map, INV_ICM42607_REG_PWR_MGMT0, &val); + if (ret) + return ret; + + *gyro = FIELD_GET(INV_ICM42607_PWR_MGMT0_GYRO_MODE_MASK, val); + *accel = FIELD_GET(INV_ICM42607_PWR_MGMT0_ACCEL_MODE_MASK, val); + + return 0; +} + +static int inv_icm42607_set_pwr_mgmt0(struct inv_icm42607_state *st, + enum inv_icm42607_sensor_mode gyro, + enum inv_icm42607_sensor_mode accel) +{ + enum inv_icm42607_sensor_mode oldaccel, oldgyro; + unsigned int sleepval_us, odr_us; + unsigned int val; + s64 disable_wait; + int ret; + + ret = inv_icm42607_get_pwr_mgmt0(st, &oldgyro, &oldaccel); + if (ret) + return ret; + + if (gyro == oldgyro && accel == oldaccel) + return 0; + + /* + * Datasheet on page 14.26 says we need to ensure the gyro sensor is on + * for a minimum of 45ms. So if we transition from an on state to an + * off state make sure at least 45ms have passed before power off and + * wait if it hasn't. In case some platforms don't respond well to a + * sleep of 0, make sure the fsleep duration is > 0. + */ + if (!gyro && oldgyro) { + disable_wait = clamp(ktime_us_delta(st->conf.gyro_stop, ktime_get()), + 0, INV_ICM42607_GYRO_STOP_TIME_US); + + if (disable_wait > 0) + fsleep(disable_wait); + } + + val = FIELD_PREP(INV_ICM42607_PWR_MGMT0_GYRO_MODE_MASK, gyro) | + FIELD_PREP(INV_ICM42607_PWR_MGMT0_ACCEL_MODE_MASK, accel); + ret = regmap_write(st->map, INV_ICM42607_REG_PWR_MGMT0, val); + if (ret) + return ret; + + /* + * If a state change occurs from off to on, sleep for the startup time + * of the sensor. Since more than one sensor can be transitioned from + * off to on, select the maximum time from each of the sensors changing + * from off to on. Also account for the time it takes for the first + * sample to be ready by looking up the odr value and adding it to the + * sleep time. The startup time for the temp sensor is considerably + * smaller than the startup time for the other sensors and one or more + * are required to be on for the temp sensor to function, so any start + * delay should be enough. + */ + sleepval_us = 0; + odr_us = 0; + if (accel && !oldaccel) { + sleepval_us = max(sleepval_us, INV_ICM42607_ACCEL_STARTUP_TIME_US); + odr_us = max(odr_us, inv_icm42607_odr_to_period_us(st->conf.accel.odr)); + } + + if (gyro && !oldgyro) { + sleepval_us = max(sleepval_us, INV_ICM42607_GYRO_STARTUP_TIME_US); + odr_us = max(odr_us, inv_icm42607_odr_to_period_us(st->conf.gyro.odr)); + /* Track the earliest we can turn off the gyroscope. */ + st->conf.gyro_stop = ktime_add_us(ktime_get(), + INV_ICM42607_GYRO_STOP_TIME_US); + } + + sleepval_us += odr_us; + + /* + * Only sleep if sleepval_us is greater than 0 in case some platforms + * have issues with a 0 delay. The 0 delay can happen if one or both + * sensors is shut down. + */ + if (sleepval_us > 0) + fsleep(sleepval_us); + + return 0; +} + +/* + * Sanity test between old and new config values, and note if we need + * to update register config0 or config1. + */ +static void inv_icm42607_update_config(struct inv_icm42607_sensor_conf *conf, + struct inv_icm42607_sensor_conf *oldconf, + bool *config0, bool *config1) +{ + if (conf->mode < 0) + conf->mode = oldconf->mode; + if (conf->fs < 0) + conf->fs = oldconf->fs; + if (conf->odr < 0) + conf->odr = oldconf->odr; + if (conf->filter < 0) + conf->filter = oldconf->filter; + + *config0 = (conf->fs != oldconf->fs) || (conf->odr != oldconf->odr); + + *config1 = (conf->filter != oldconf->filter); +} + +int inv_icm42607_set_sensor_conf(struct inv_icm42607_state *st, + struct inv_icm42607_sensor_conf *conf, + enum iio_chan_type chan_type) +{ + enum inv_icm42607_sensor_mode accel_mode, gyro_mode; + struct inv_icm42607_sensor_conf *oldconf; + bool config0, config1; + unsigned int val; + int ret; + + switch (chan_type) { + case IIO_ACCEL: + oldconf = &st->conf.accel; + break; + case IIO_ANGL_VEL: + oldconf = &st->conf.gyro; + break; + default: + return -EINVAL; + } + + /* + * Since we are only making one-shot calls today, we can avoid needless + * on/off cycles if we rely on the runtime power management to handle + * shutting off the sensors when not in use. The downside is if we + * enable both sensors then only read from one, we have to wait for + * runtime PM to turn it off. So just keep the mode of the sensor we + * are not actively using to whatever it's already set as. + */ + ret = inv_icm42607_get_pwr_mgmt0(st, &gyro_mode, &accel_mode); + if (ret) + return ret; + + inv_icm42607_update_config(conf, oldconf, &config0, &config1); + + if (config0) { + if (chan_type == IIO_ANGL_VEL) { + val = FIELD_PREP(INV_ICM42607_GYRO_CONFIG0_FS_SEL_MASK, conf->fs); + val |= FIELD_PREP(INV_ICM42607_GYRO_CONFIG0_ODR_MASK, conf->odr); + ret = regmap_write(st->map, INV_ICM42607_REG_GYRO_CONFIG0, val); + } else { + val = FIELD_PREP(INV_ICM42607_ACCEL_CONFIG0_FS_SEL_MASK, conf->fs); + val |= FIELD_PREP(INV_ICM42607_ACCEL_CONFIG0_ODR_MASK, conf->odr); + ret = regmap_write(st->map, INV_ICM42607_REG_ACCEL_CONFIG0, val); + } + if (ret) + return ret; + + oldconf->fs = conf->fs; + oldconf->odr = conf->odr; + } + + if (config1) { + if (chan_type == IIO_ANGL_VEL) { + val = FIELD_PREP(INV_ICM42607_GYRO_CONFIG1_FILTER_MASK, + conf->filter); + ret = regmap_update_bits(st->map, INV_ICM42607_REG_GYRO_CONFIG1, + INV_ICM42607_GYRO_CONFIG1_FILTER_MASK, val); + } else { + val = FIELD_PREP(INV_ICM42607_ACCEL_CONFIG1_FILTER_MASK, + conf->filter); + ret = regmap_update_bits(st->map, INV_ICM42607_REG_ACCEL_CONFIG1, + INV_ICM42607_ACCEL_CONFIG1_FILTER_MASK, val); + } + if (ret) + return ret; + + oldconf->filter = conf->filter; + } + + oldconf->mode = conf->mode; + + switch (chan_type) { + case IIO_ACCEL: + return inv_icm42607_set_pwr_mgmt0(st, gyro_mode, conf->mode); + case IIO_ANGL_VEL: + return inv_icm42607_set_pwr_mgmt0(st, conf->mode, accel_mode); + default: + return -EINVAL; + } +} + +int inv_icm42607_read_sensor(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + s16 *val) +{ + struct inv_icm42607_sensor_conf conf = INV_ICM42607_SENSOR_CONF_INIT; + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42607_sensor_state *sensor_st = iio_priv(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int reg; + __be16 data; + int ret; + + if ((chan->type != IIO_ANGL_VEL) && (chan->type != IIO_ACCEL)) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + if (chan->type == IIO_ANGL_VEL) + reg = INV_ICM42607_REG_GYRO_DATA_X1; + else + reg = INV_ICM42607_REG_ACCEL_DATA_X1; + break; + case IIO_MOD_Y: + if (chan->type == IIO_ANGL_VEL) + reg = INV_ICM42607_REG_GYRO_DATA_Y1; + else + reg = INV_ICM42607_REG_ACCEL_DATA_Y1; + break; + case IIO_MOD_Z: + if (chan->type == IIO_ANGL_VEL) + reg = INV_ICM42607_REG_GYRO_DATA_Z1; + else + reg = INV_ICM42607_REG_ACCEL_DATA_Z1; + break; + default: + return -EINVAL; + } + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + guard(mutex)(&st->lock); + + /* enable sensor */ + conf.mode = sensor_st->power_mode; + conf.filter = sensor_st->filter; + ret = inv_icm42607_set_sensor_conf(st, &conf, chan->type); + if (ret) + return ret; + + /* read sensor register data */ + ret = regmap_bulk_read(st->map, reg, &data, sizeof(data)); + if (ret) + return ret; + + *val = be16_to_cpu(data); + if (*val == INV_ICM42607_DATA_INVALID) + return -EINVAL; + + return 0; +} + +static int inv_icm42607_set_init_conf(struct inv_icm42607_state *st, + const struct inv_icm42607_conf *conf) +{ + unsigned int val; + int ret; + + val = FIELD_PREP(INV_ICM42607_PWR_MGMT0_GYRO_MODE_MASK, conf->gyro.mode); + val |= FIELD_PREP(INV_ICM42607_PWR_MGMT0_ACCEL_MODE_MASK, conf->accel.mode); + ret = regmap_write(st->map, INV_ICM42607_REG_PWR_MGMT0, val); + if (ret) + return ret; + + val = FIELD_PREP(INV_ICM42607_GYRO_CONFIG0_FS_SEL_MASK, conf->gyro.fs); + val |= FIELD_PREP(INV_ICM42607_GYRO_CONFIG0_ODR_MASK, conf->gyro.odr); + ret = regmap_write(st->map, INV_ICM42607_REG_GYRO_CONFIG0, val); + if (ret) + return ret; + + val = FIELD_PREP(INV_ICM42607_ACCEL_CONFIG0_FS_SEL_MASK, conf->accel.fs); + val |= FIELD_PREP(INV_ICM42607_ACCEL_CONFIG0_ODR_MASK, conf->accel.odr); + ret = regmap_write(st->map, INV_ICM42607_REG_ACCEL_CONFIG0, val); + if (ret) + return ret; + + val = FIELD_PREP(INV_ICM42607_GYRO_CONFIG1_FILTER_MASK, conf->gyro.filter); + ret = regmap_update_bits(st->map, INV_ICM42607_REG_GYRO_CONFIG1, + INV_ICM42607_GYRO_CONFIG1_FILTER_MASK, val); + if (ret) + return ret; + + val = FIELD_PREP(INV_ICM42607_ACCEL_CONFIG1_FILTER_MASK, conf->accel.filter); + ret = regmap_update_bits(st->map, INV_ICM42607_REG_ACCEL_CONFIG1, + INV_ICM42607_ACCEL_CONFIG1_FILTER_MASK, val); + if (ret) + return ret; + + val = FIELD_PREP(INV_ICM42607_TEMP_CONFIG0_FILTER_MASK, + INV_ICM42607_TEMP_FILTER_BW_34HZ); + ret = regmap_update_bits(st->map, INV_ICM42607_REG_TEMP_CONFIG0, + INV_ICM42607_TEMP_CONFIG0_FILTER_MASK, val); + if (ret) + return ret; + + st->conf = *conf; + + return 0; +} + +static int inv_icm42607_setup(struct inv_icm42607_state *st, + inv_icm42607_bus_setup inv_icm42607_bus_setup) +{ + const struct device *dev = regmap_get_device(st->map); + unsigned int val; + int ret; + + ret = regmap_read(st->map, INV_ICM42607_REG_WHOAMI, &val); + if (ret) + return ret; + + /* Warn, but don't fail. */ + if (val != st->hw->whoami) + dev_warn(dev, "Unknown whoami %#02x expected %#02x (%s)\n", + val, st->hw->whoami, st->hw->name); + + ret = regmap_write(st->map, INV_ICM42607_REG_SIGNAL_PATH_RESET, + INV_ICM42607_SIGNAL_PATH_RESET_SOFT_RESET); + if (ret) + return ret; + + fsleep(1 * USEC_PER_MSEC); + + /* + * No polling interval specified in datasheet, so use reset time as + * polling interval and 10x reset time as timeout period. + */ + ret = regmap_read_poll_timeout(st->map, INV_ICM42607_REG_INT_STATUS, + val, val & INV_ICM42607_INT_STATUS_RESET_DONE, + 1 * USEC_PER_MSEC, 10 * USEC_PER_MSEC); + if (ret) + return dev_err_probe(dev, ret, + "reset error, reset done bit not set\n"); + + /* Sync the regcache again after a reset. */ + regcache_mark_dirty(st->map); + ret = regcache_sync(st->map); + if (ret) + return ret; + + ret = inv_icm42607_bus_setup(st); + if (ret) + return ret; + + ret = regmap_set_bits(st->map, INV_ICM42607_REG_INTF_CONFIG0, + INV_ICM42607_INTF_CONFIG0_SENSOR_DATA_ENDIAN); + if (ret) + return ret; + + val = FIELD_PREP(INV_ICM42607_INTF_CONFIG1_CLKSEL_MASK, + INV_ICM42607_INTF_CONFIG1_CLKSEL_PLL); + ret = regmap_update_bits(st->map, INV_ICM42607_REG_INTF_CONFIG1, + INV_ICM42607_INTF_CONFIG1_CLKSEL_MASK, + val); + if (ret) + return ret; + + return inv_icm42607_set_init_conf(st, st->hw->conf); +} + +static int inv_icm42607_enable_vddio_reg(struct inv_icm42607_state *st) +{ + int ret; + + if (st->vddio_en) + return 0; + + ret = regulator_enable(st->vddio_supply); + if (ret) + return ret; + + fsleep(INV_ICM42607_POWER_UP_TIME_US); + + st->vddio_en = true; + + return 0; +} + +static void inv_icm42607_sensors_off(void *_data) +{ + struct inv_icm42607_state *st = _data; + const struct device *dev = regmap_get_device(st->map); + int ret; + + guard(mutex)(&st->lock); + + st->conf.gyro.mode = INV_ICM42607_SENSOR_MODE_OFF; + st->conf.accel.mode = INV_ICM42607_SENSOR_MODE_OFF; + + ret = inv_icm42607_set_pwr_mgmt0(st, st->conf.gyro.mode, + st->conf.accel.mode); + if (ret) + dev_err(dev, "Unable to turn off sensors\n"); +} + +static void inv_icm42607_disable_vddio_reg(void *_data) +{ + struct inv_icm42607_state *st = _data; + + if (!st->vddio_en) + return; + + regulator_disable(st->vddio_supply); + + st->vddio_en = false; +} + +int inv_icm42607_core_probe(struct regmap *regmap, + const struct inv_icm42607_hw *hw, + inv_icm42607_bus_setup inv_icm42607_bus_setup) +{ + struct device *dev = regmap_get_device(regmap); + struct inv_icm42607_state *st; + int ret; + + st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL); + if (!st) + return -ENOMEM; + + dev_set_drvdata(dev, st); + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + st->hw = hw; + st->map = regmap; + + ret = iio_read_mount_matrix(dev, &st->orientation); + if (ret) + return dev_err_probe(dev, ret, + "failed to retrieve mounting matrix\n"); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get vdd regulator\n"); + + st->vddio_supply = devm_regulator_get(dev, "vddio"); + if (IS_ERR(st->vddio_supply)) + return dev_err_probe(dev, PTR_ERR(st->vddio_supply), + "Failed to get vddio regulator\n"); + + ret = inv_icm42607_enable_vddio_reg(st); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, inv_icm42607_disable_vddio_reg, st); + if (ret) + return ret; + + /* Setup chip registers (includes WHOAMI check, reset check, bus setup) */ + ret = inv_icm42607_setup(st, inv_icm42607_bus_setup); + if (ret) + return ret; + + /* + * Ensure if sensors get turned on at some point, they're turned off + * as part of teardown. + */ + ret = devm_add_action_or_reset(dev, inv_icm42607_sensors_off, st); + if (ret) + return ret; + + ret = devm_pm_runtime_set_active_enabled(dev); + if (ret) + return ret; + + pm_runtime_set_autosuspend_delay(dev, INV_ICM42607_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + + /* Initialize IIO device for Accel */ + st->indio_accel = inv_icm42607_accel_init(st); + if (IS_ERR(st->indio_accel)) + return PTR_ERR(st->indio_accel); + + /* Initialize IIO device for Gyro */ + st->indio_gyro = inv_icm42607_gyro_init(st); + if (IS_ERR(st->indio_gyro)) + return PTR_ERR(st->indio_gyro); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(inv_icm42607_core_probe, "IIO_ICM42607"); + +static int inv_icm42607_suspend(struct device *dev) +{ + struct inv_icm42607_state *st = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_force_suspend(dev); + if (ret) + return ret; + + inv_icm42607_disable_vddio_reg(st); + + return 0; +} + +static int inv_icm42607_resume(struct device *dev) +{ + struct inv_icm42607_state *st = dev_get_drvdata(dev); + int ret; + + ret = inv_icm42607_enable_vddio_reg(st); + if (ret) + return ret; + + /* Sync the regcache again after regulator shutdown. */ + regcache_mark_dirty(st->map); + ret = regcache_sync(st->map); + if (ret) + return ret; + + return pm_runtime_force_resume(dev); +} + +static int inv_icm42607_runtime_suspend(struct device *dev) +{ + struct inv_icm42607_state *st = dev_get_drvdata(dev); + + /* + * Set sensors state to off. Since we only support one-shot + * today we can use runtime PM to turn sensors off when not + * in use, and then when needed the reads/writes will + * re-enable the sensors as needed. This reduces complexity, + * however the tradeoff is that an unused sensor won't be + * turned off until the entire chip is no longer in use. + */ + inv_icm42607_sensors_off(st); + return 0; +} + +EXPORT_NS_GPL_DEV_PM_OPS(inv_icm42607_pm_ops, IIO_ICM42607) = { + SYSTEM_SLEEP_PM_OPS(inv_icm42607_suspend, inv_icm42607_resume) + RUNTIME_PM_OPS(inv_icm42607_runtime_suspend, NULL, NULL) +}; + +MODULE_AUTHOR("InvenSense, Inc."); +MODULE_DESCRIPTION("InvenSense ICM-42607 device driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_gyro.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_gyro.c new file mode 100644 index 000000000..5b4683c2d --- /dev/null +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_gyro.c @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 InvenSense, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "inv_icm42607.h" +#include "inv_icm42607_temp.h" + +#define INV_ICM42607_GYRO_CHAN(_modifier, _index, _ext_info) \ +{ \ + .type = IIO_ANGL_VEL, \ + .modified = 1, \ + .channel2 = _modifier, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + .ext_info = _ext_info, \ +} + +enum inv_icm42607_gyro_scan { + INV_ICM42607_GYRO_SCAN_X, + INV_ICM42607_GYRO_SCAN_Y, + INV_ICM42607_GYRO_SCAN_Z, + INV_ICM42607_GYRO_SCAN_TEMP, +}; + +static const struct iio_chan_spec_ext_info inv_icm42607_gyro_ext_infos[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, inv_icm42607_get_mount_matrix), + { } +}; + +static const struct iio_chan_spec inv_icm42607_gyro_channels[] = { + INV_ICM42607_GYRO_CHAN(IIO_MOD_X, INV_ICM42607_GYRO_SCAN_X, + inv_icm42607_gyro_ext_infos), + INV_ICM42607_GYRO_CHAN(IIO_MOD_Y, INV_ICM42607_GYRO_SCAN_Y, + inv_icm42607_gyro_ext_infos), + INV_ICM42607_GYRO_CHAN(IIO_MOD_Z, INV_ICM42607_GYRO_SCAN_Z, + inv_icm42607_gyro_ext_infos), + INV_ICM42607_TEMP_CHAN(INV_ICM42607_GYRO_SCAN_TEMP), +}; + +static const int inv_icm42607_gyro_scale_nano[][2] = { + [INV_ICM42607_GYRO_FS_2000DPS] = { 0, 1065264 }, + [INV_ICM42607_GYRO_FS_1000DPS] = { 0, 532632 }, + [INV_ICM42607_GYRO_FS_500DPS] = { 0, 266316 }, + [INV_ICM42607_GYRO_FS_250DPS] = { 0, 133158 }, +}; + +static int inv_icm42607_gyro_read_scale(struct iio_dev *indio_dev, + int *val, int *val2) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + unsigned int idx; + + guard(mutex)(&st->lock); + + idx = st->conf.gyro.fs; + + *val = inv_icm42607_gyro_scale_nano[idx][0]; + *val2 = inv_icm42607_gyro_scale_nano[idx][1]; + return IIO_VAL_INT_PLUS_NANO; +} + +static int inv_icm42607_gyro_write_scale(struct iio_dev *indio_dev, + int val, int val2) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + struct inv_icm42607_sensor_conf conf = INV_ICM42607_SENSOR_CONF_INIT; + size_t scales_len = ARRAY_SIZE(inv_icm42607_gyro_scale_nano); + int ret; + + for (idx = 0; idx < scales_len; idx++) { + if (val == inv_icm42607_gyro_scale_nano[idx][0] && + val2 == inv_icm42607_gyro_scale_nano[idx][1]) + break; + } + if (idx == scales_len) + return -EINVAL; + + conf.fs = idx; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + guard(mutex)(&st->lock); + + return inv_icm42607_set_sensor_conf(st, &conf, IIO_ANGL_VEL); +} + +/* IIO format int + micro , values 0-4 reserved. */ +static const int inv_icm42607_gyro_odr[][2] = { + [INV_ICM42607_ODR_1600HZ] = { 1600, 0 }, + [INV_ICM42607_ODR_800HZ] = { 800, 0 }, + [INV_ICM42607_ODR_400HZ] = { 400, 0 }, + [INV_ICM42607_ODR_200HZ] = { 200, 0 }, + [INV_ICM42607_ODR_100HZ] = { 100, 0 }, + [INV_ICM42607_ODR_50HZ] = { 50, 0 }, + [INV_ICM42607_ODR_25HZ] = { 25, 0 }, + [INV_ICM42607_ODR_12_5HZ] = { 12, 500000 }, +}; + +static int inv_icm42607_gyro_read_odr(struct inv_icm42607_state *st, + int *val, int *val2) +{ + unsigned int odr; + unsigned int i; + + guard(mutex)(&st->lock); + + odr = st->conf.gyro.odr; + + for (i = INV_ICM42607_ODR_1600HZ; i < ARRAY_SIZE(inv_icm42607_gyro_odr); i++) { + if (i == odr) + break; + } + if (i == ARRAY_SIZE(inv_icm42607_gyro_odr)) + return -EINVAL; + + *val = inv_icm42607_gyro_odr[i][0]; + *val2 = inv_icm42607_gyro_odr[i][1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int inv_icm42607_gyro_write_odr(struct iio_dev *indio_dev, + int val, int val2) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + struct inv_icm42607_sensor_conf conf = INV_ICM42607_SENSOR_CONF_INIT; + int ret; + + for (idx = INV_ICM42607_ODR_1600HZ; + idx < ARRAY_SIZE(inv_icm42607_gyro_odr); idx++) { + if (val == inv_icm42607_gyro_odr[idx][0] && + val2 == inv_icm42607_gyro_odr[idx][1]) + break; + } + if (idx == ARRAY_SIZE(inv_icm42607_gyro_odr)) + return -EINVAL; + + conf.odr = idx; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + guard(mutex)(&st->lock); + + return inv_icm42607_set_sensor_conf(st, &conf, IIO_ANGL_VEL); +} + +static int inv_icm42607_gyro_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + s16 data; + int ret; + + switch (chan->type) { + case IIO_ANGL_VEL: + break; + case IIO_TEMP: + if (mask != IIO_CHAN_INFO_SAMP_FREQ) + return inv_icm42607_temp_read_raw(indio_dev, chan, + val, val2, mask); + break; + default: + return -EINVAL; + } + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = inv_icm42607_read_sensor(indio_dev, chan, &data); + if (ret) + return ret; + *val = data; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + return inv_icm42607_gyro_read_scale(indio_dev, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return inv_icm42607_gyro_read_odr(st, val, val2); + default: + return -EINVAL; + } +} + +static int inv_icm42607_gyro_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, + int *type, int *length, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + *vals = (const int *)inv_icm42607_gyro_scale_nano; + *type = IIO_VAL_INT_PLUS_NANO; + *length = ARRAY_SIZE(inv_icm42607_gyro_scale_nano) * 2; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = (const int *)inv_icm42607_gyro_odr[INV_ICM42607_ODR_1600HZ]; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = (ARRAY_SIZE(inv_icm42607_gyro_odr) - + INV_ICM42607_ODR_1600HZ) * 2; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int inv_icm42607_gyro_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + ret = inv_icm42607_gyro_write_scale(indio_dev, val, val2); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + return inv_icm42607_gyro_write_odr(indio_dev, val, val2); + default: + return -EINVAL; + } +} + +static int inv_icm42607_gyro_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static const struct iio_info inv_icm42607_gyro_info = { + .read_raw = inv_icm42607_gyro_read_raw, + .read_avail = inv_icm42607_gyro_read_avail, + .write_raw = inv_icm42607_gyro_write_raw, + .write_raw_get_fmt = inv_icm42607_gyro_write_raw_get_fmt, +}; + +struct iio_dev *inv_icm42607_gyro_init(struct inv_icm42607_state *st) +{ + struct device *dev = regmap_get_device(st->map); + const char *name; + struct inv_icm42607_sensor_state *gyro_st; + struct iio_dev *indio_dev; + int ret; + + name = devm_kasprintf(dev, GFP_KERNEL, "%s-gyro", st->hw->name); + if (!name) + return ERR_PTR(-ENOMEM); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*gyro_st)); + if (!indio_dev) + return ERR_PTR(-ENOMEM); + + gyro_st = iio_priv(indio_dev); + gyro_st->power_mode = INV_ICM42607_SENSOR_MODE_LOW_NOISE; + gyro_st->filter = INV_ICM42607_FILTER_BW_73HZ; + + indio_dev->name = name; + indio_dev->info = &inv_icm42607_gyro_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = inv_icm42607_gyro_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_icm42607_gyro_channels); + iio_device_set_drvdata(indio_dev, st); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ERR_PTR(ret); + + return indio_dev; +} diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_i2c.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_i2c.c new file mode 100644 index 000000000..e903106af --- /dev/null +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_i2c.c @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 InvenSense, Inc. + */ + +#include +#include +#include +#include +#include +#include + +#include "inv_icm42607.h" + +static int inv_icm42607_i2c_bus_setup(struct inv_icm42607_state *st) +{ + unsigned int val; + int ret; + + ret = regmap_clear_bits(st->map, INV_ICM42607_REG_INTF_CONFIG1, + INV_ICM42607_INTF_CONFIG1_I3C_DDR_EN | + INV_ICM42607_INTF_CONFIG1_I3C_SDR_EN); + if (ret) + return ret; + + val = FIELD_PREP(INV_ICM42607_DRIVE_CONFIG2_I2C_MASK, + INV_ICM42607_SLEW_RATE_12_36NS); + ret = regmap_update_bits(st->map, INV_ICM42607_REG_DRIVE_CONFIG2, + INV_ICM42607_DRIVE_CONFIG2_I2C_MASK, val); + if (ret) + return ret; + + val = FIELD_PREP(INV_ICM42607_INTF_CONFIG0_UI_SIFS_CFG_MASK, + INV_ICM42607_INTF_CONFIG0_UI_SIFS_CFG_SPI_DIS); + + return regmap_update_bits(st->map, INV_ICM42607_REG_INTF_CONFIG0, + INV_ICM42607_INTF_CONFIG0_UI_SIFS_CFG_MASK, + val); +} + +static int inv_icm42607_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + const struct inv_icm42607_hw *hw; + struct regmap *regmap; + + hw = i2c_get_match_data(client); + if (!hw) + return dev_err_probe(dev, -ENODEV, "Failed to get i2c data\n"); + + regmap = devm_regmap_init_i2c(client, &inv_icm42607_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Failed to register i2c regmap\n"); + + return inv_icm42607_core_probe(regmap, hw, inv_icm42607_i2c_bus_setup); +} + +static const struct i2c_device_id inv_icm42607_id[] = { + { + .name = "icm42607", + .driver_data = (kernel_ulong_t)&inv_icm42607_hw_data, + }, { + .name = "icm42607p", + .driver_data = (kernel_ulong_t)&inv_icm42607p_hw_data, + }, + { } +}; +MODULE_DEVICE_TABLE(i2c, inv_icm42607_id); + +static const struct of_device_id inv_icm42607_of_matches[] = { + { + .compatible = "invensense,icm42607", + .data = &inv_icm42607_hw_data, + }, { + .compatible = "invensense,icm42607p", + .data = &inv_icm42607p_hw_data, + }, + { } +}; +MODULE_DEVICE_TABLE(of, inv_icm42607_of_matches); + +static struct i2c_driver inv_icm42607_driver = { + .driver = { + .name = "inv-icm42607-i2c", + .of_match_table = inv_icm42607_of_matches, + .pm = pm_ptr(&inv_icm42607_pm_ops), + }, + .id_table = inv_icm42607_id, + .probe = inv_icm42607_probe, +}; +module_i2c_driver(inv_icm42607_driver); + +MODULE_AUTHOR("InvenSense, Inc."); +MODULE_DESCRIPTION("InvenSense ICM-42607 I2C driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_ICM42607"); diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_spi.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_spi.c new file mode 100644 index 000000000..cd739375f --- /dev/null +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_spi.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 InvenSense, Inc. + */ + +#include +#include +#include +#include +#include +#include + +#include "inv_icm42607.h" + +static int inv_icm42607_spi_bus_setup(struct inv_icm42607_state *st) +{ + unsigned int val; + int ret; + + /* Only support 4-wire mode for now. */ + ret = regmap_set_bits(st->map, INV_ICM42607_REG_DEVICE_CONFIG, + INV_ICM42607_DEVICE_CONFIG_SPI_AP_4WIRE); + if (ret) + return ret; + + ret = regmap_clear_bits(st->map, INV_ICM42607_REG_INTF_CONFIG1, + INV_ICM42607_INTF_CONFIG1_I3C_DDR_EN | + INV_ICM42607_INTF_CONFIG1_I3C_SDR_EN); + if (ret) + return ret; + + val = FIELD_PREP(INV_ICM42607_DRIVE_CONFIG3_SPI_MASK, + INV_ICM42607_SLEW_RATE_2NS); + ret = regmap_update_bits(st->map, INV_ICM42607_REG_DRIVE_CONFIG3, + INV_ICM42607_DRIVE_CONFIG3_SPI_MASK, val); + if (ret) + return ret; + + val = FIELD_PREP(INV_ICM42607_INTF_CONFIG0_UI_SIFS_CFG_MASK, + INV_ICM42607_INTF_CONFIG0_UI_SIFS_CFG_I2C_DIS); + + return regmap_update_bits(st->map, INV_ICM42607_REG_INTF_CONFIG0, + INV_ICM42607_INTF_CONFIG0_UI_SIFS_CFG_MASK, + val); +} + +static int inv_icm42607_probe(struct spi_device *spi) +{ + const struct inv_icm42607_hw *hw; + struct device *dev = &spi->dev; + struct regmap *regmap; + + hw = spi_get_device_match_data(spi); + if (!hw) + return dev_err_probe(dev, -ENODATA, "Failed to get SPI data\n"); + + if (spi->mode & SPI_3WIRE) + return dev_err_probe(dev, -ENODEV, "SPI 3-wire mode not supported\n"); + + regmap = devm_regmap_init_spi(spi, &inv_icm42607_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Failed to register spi regmap\n"); + + return inv_icm42607_core_probe(regmap, hw, inv_icm42607_spi_bus_setup); +} + +static const struct spi_device_id inv_icm42607_spi_id_table[] = { + { + .name = "icm42607", + .driver_data = (kernel_ulong_t)&inv_icm42607_hw_data, + }, { + .name = "icm42607p", + .driver_data = (kernel_ulong_t)&inv_icm42607p_hw_data, + }, + { } +}; +MODULE_DEVICE_TABLE(spi, inv_icm42607_spi_id_table); + +static const struct of_device_id inv_icm42607_of_matches[] = { + { + .compatible = "invensense,icm42607", + .data = &inv_icm42607_hw_data, + }, + { + .compatible = "invensense,icm42607p", + .data = &inv_icm42607p_hw_data, + }, + { } +}; +MODULE_DEVICE_TABLE(of, inv_icm42607_of_matches); + +static struct spi_driver inv_icm42607_driver = { + .driver = { + .name = "inv-icm42607-spi", + .of_match_table = inv_icm42607_of_matches, + .pm = pm_ptr(&inv_icm42607_pm_ops), + }, + .id_table = inv_icm42607_spi_id_table, + .probe = inv_icm42607_probe, +}; +module_spi_driver(inv_icm42607_driver); + +MODULE_AUTHOR("InvenSense, Inc."); +MODULE_DESCRIPTION("InvenSense ICM-42607 SPI driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_ICM42607"); diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_temp.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_temp.c new file mode 100644 index 000000000..53f0484c0 --- /dev/null +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_temp.c @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 InvenSense, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "inv_icm42607.h" +#include "inv_icm42607_temp.h" + +static int inv_icm42607_temp_read(struct inv_icm42607_state *st, s16 *temp) +{ + struct inv_icm42607_sensor_conf conf = INV_ICM42607_SENSOR_CONF_INIT; + struct device *dev = regmap_get_device(st->map); + int ret, gyro_mode, accel_mode; + unsigned int val; + u8 raw[2]; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + guard(mutex)(&st->lock); + + /* + * Check if both the gyro and accel are off and if so, enable one + * of them. The temp sensor cannot be read if both the gyro and + * accel sensor are off. Prefer to enable the accel over the gyro + * as the datasheet says the gyro uses 5x more power and it has + * a minimum run time of 45ms. + */ + ret = regmap_read(st->map, INV_ICM42607_REG_PWR_MGMT0, &val); + if (ret) + return ret; + + accel_mode = FIELD_GET(INV_ICM42607_PWR_MGMT0_ACCEL_MODE_MASK, val); + gyro_mode = FIELD_GET(INV_ICM42607_PWR_MGMT0_GYRO_MODE_MASK, val); + if (!gyro_mode && !accel_mode) { + /* enable accel sensor */ + conf.mode = INV_ICM42607_SENSOR_MODE_LOW_NOISE; + ret = inv_icm42607_set_sensor_conf(st, &conf, IIO_ACCEL); + if (ret) + return ret; + } + + ret = regmap_bulk_read(st->map, INV_ICM42607_REG_TEMP_DATA1, + raw, sizeof(raw)); + if (ret) + return ret; + + *temp = get_unaligned_be16(raw); + if (*temp == INV_ICM42607_DATA_INVALID) + return -EINVAL; + + return 0; +} + +int inv_icm42607_temp_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + s16 temp; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = inv_icm42607_temp_read(st, &temp); + if (ret) + return ret; + *val = temp; + return IIO_VAL_INT; + /* + * T°C = (temp / 128) + 25 + * Tm°C = 1000 * ((temp * 100 / 12800) + 25) + * scale: 100000 / 12800 ~= 7.8125 + * offset: 3200 + */ + case IIO_CHAN_INFO_SCALE: + *val = 7; + *val2 = 812500000; + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + *val = 3200; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_temp.h b/drivers/iio/imu/inv_icm42607/inv_icm42607_temp.h new file mode 100644 index 000000000..18499b4d0 --- /dev/null +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_temp.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2026 InvenSense, Inc. + */ + +#ifndef INV_ICM42607_TEMP_H_ +#define INV_ICM42607_TEMP_H_ + +#include + +struct iio_dev; +struct iio_chan_spec; + +#define INV_ICM42607_TEMP_CHAN(_index) \ +{ \ + .type = IIO_TEMP, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ +} + +int inv_icm42607_temp_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask); + +#endif diff --git a/drivers/iio/imu/inv_icm45600/Kconfig b/drivers/iio/imu/inv_icm45600/Kconfig new file mode 100644 index 000000000..dc133402f --- /dev/null +++ b/drivers/iio/imu/inv_icm45600/Kconfig @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +config INV_ICM45600 + tristate + select IIO_BUFFER + select IIO_KFIFO_BUF + select IIO_INV_SENSORS_TIMESTAMP + +config INV_ICM45600_I2C + tristate "InvenSense ICM-456xx I2C driver" + depends on I2C + select INV_ICM45600 + select REGMAP_I2C + help + This driver supports the InvenSense ICM-456xx motion tracking + devices over I2C. + Supported devices: + - ICM-45605 + - ICM-45606 + - ICM-45608 + - ICM-45634 + - ICM-45686 + - ICM-45687 + - ICM-45688-P + - ICM-45689 + + This driver can be built as a module. The module will be called + inv-icm45600-i2c. + +config INV_ICM45600_SPI + tristate "InvenSense ICM-456xx SPI driver" + depends on SPI_MASTER + select INV_ICM45600 + select REGMAP_SPI + help + This driver supports the InvenSense ICM-456xx motion tracking + devices over SPI. + Supported devices: + - ICM-45605 + - ICM-45606 + - ICM-45608 + - ICM-45634 + - ICM-45686 + - ICM-45687 + - ICM-45688-P + - ICM-45689 + + This driver can be built as a module. The module will be called + inv-icm45600-spi. + +config INV_ICM45600_I3C + tristate "InvenSense ICM-456xx I3C driver" + depends on I3C + select INV_ICM45600 + select REGMAP_I3C + help + This driver supports the InvenSense ICM-456xx motion tracking + devices over I3C. + Supported devices: + - ICM-45605 + - ICM-45606 + - ICM-45608 + - ICM-45634 + - ICM-45686 + - ICM-45687 + - ICM-45688-P + - ICM-45689 + + This driver can be built as a module. The module will be called + inv-icm45600-i3c. diff --git a/drivers/iio/imu/inv_icm45600/Makefile b/drivers/iio/imu/inv_icm45600/Makefile new file mode 100644 index 000000000..c98b8365b --- /dev/null +++ b/drivers/iio/imu/inv_icm45600/Makefile @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +obj-$(CONFIG_INV_ICM45600) += inv-icm45600.o +inv-icm45600-y += inv_icm45600_core.o +inv-icm45600-y += inv_icm45600_buffer.o +inv-icm45600-y += inv_icm45600_gyro.o +inv-icm45600-y += inv_icm45600_accel.o + +obj-$(CONFIG_INV_ICM45600_I2C) += inv-icm45600-i2c.o +inv-icm45600-i2c-y += inv_icm45600_i2c.o + +obj-$(CONFIG_INV_ICM45600_SPI) += inv-icm45600-spi.o +inv-icm45600-spi-y += inv_icm45600_spi.o + +obj-$(CONFIG_INV_ICM45600_I3C) += inv-icm45600-i3c.o +inv-icm45600-i3c-y += inv_icm45600_i3c.o diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600.h b/drivers/iio/imu/inv_icm45600/inv_icm45600.h new file mode 100644 index 000000000..95fa934a4 --- /dev/null +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600.h @@ -0,0 +1,385 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2025 Invensense, Inc. */ + +#ifndef INV_ICM45600_H_ +#define INV_ICM45600_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "inv_icm45600_buffer.h" + +#define INV_ICM45600_REG_BANK_MASK GENMASK(15, 8) +#define INV_ICM45600_REG_ADDR_MASK GENMASK(7, 0) + +enum inv_icm45600_sensor_mode { + INV_ICM45600_SENSOR_MODE_OFF, + INV_ICM45600_SENSOR_MODE_STANDBY, + INV_ICM45600_SENSOR_MODE_LOW_POWER, + INV_ICM45600_SENSOR_MODE_LOW_NOISE, + INV_ICM45600_SENSOR_MODE_MAX +}; + +/* gyroscope fullscale values */ +enum inv_icm45600_gyro_fs { + INV_ICM45600_GYRO_FS_2000DPS, + INV_ICM45600_GYRO_FS_1000DPS, + INV_ICM45600_GYRO_FS_500DPS, + INV_ICM45600_GYRO_FS_250DPS, + INV_ICM45600_GYRO_FS_125DPS, + INV_ICM45600_GYRO_FS_62_5DPS, + INV_ICM45600_GYRO_FS_31_25DPS, + INV_ICM45600_GYRO_FS_15_625DPS, + INV_ICM45600_GYRO_FS_MAX +}; + +enum inv_icm45686_gyro_fs { + INV_ICM45686_GYRO_FS_4000DPS, + INV_ICM45686_GYRO_FS_2000DPS, + INV_ICM45686_GYRO_FS_1000DPS, + INV_ICM45686_GYRO_FS_500DPS, + INV_ICM45686_GYRO_FS_250DPS, + INV_ICM45686_GYRO_FS_125DPS, + INV_ICM45686_GYRO_FS_62_5DPS, + INV_ICM45686_GYRO_FS_31_25DPS, + INV_ICM45686_GYRO_FS_15_625DPS, + INV_ICM45686_GYRO_FS_MAX +}; + +/* accelerometer fullscale values */ +enum inv_icm45600_accel_fs { + INV_ICM45600_ACCEL_FS_16G, + INV_ICM45600_ACCEL_FS_8G, + INV_ICM45600_ACCEL_FS_4G, + INV_ICM45600_ACCEL_FS_2G, + INV_ICM45600_ACCEL_FS_MAX +}; + +enum inv_icm45686_accel_fs { + INV_ICM45686_ACCEL_FS_32G, + INV_ICM45686_ACCEL_FS_16G, + INV_ICM45686_ACCEL_FS_8G, + INV_ICM45686_ACCEL_FS_4G, + INV_ICM45686_ACCEL_FS_2G, + INV_ICM45686_ACCEL_FS_MAX +}; + +/* ODR suffixed by LN or LP are Low-Noise or Low-Power mode only */ +enum inv_icm45600_odr { + INV_ICM45600_ODR_6400HZ_LN = 0x03, + INV_ICM45600_ODR_3200HZ_LN, + INV_ICM45600_ODR_1600HZ_LN, + INV_ICM45600_ODR_800HZ_LN, + INV_ICM45600_ODR_400HZ, + INV_ICM45600_ODR_200HZ, + INV_ICM45600_ODR_100HZ, + INV_ICM45600_ODR_50HZ, + INV_ICM45600_ODR_25HZ, + INV_ICM45600_ODR_12_5HZ, + INV_ICM45600_ODR_6_25HZ_LP, + INV_ICM45600_ODR_3_125HZ_LP, + INV_ICM45600_ODR_1_5625HZ_LP, + INV_ICM45600_ODR_MAX +}; + +struct inv_icm45600_sensor_conf { + u8 mode; + u8 fs; + u8 odr; + u8 filter; +}; + +#define INV_ICM45600_SENSOR_CONF_KEEP_VALUES { U8_MAX, U8_MAX, U8_MAX, U8_MAX } + +struct inv_icm45600_conf { + struct inv_icm45600_sensor_conf gyro; + struct inv_icm45600_sensor_conf accel; +}; + +struct inv_icm45600_suspended { + enum inv_icm45600_sensor_mode gyro; + enum inv_icm45600_sensor_mode accel; +}; + +struct inv_icm45600_chip_info { + u8 whoami; + const char *name; + const struct inv_icm45600_conf *conf; + const int *accel_scales; + const int accel_scales_len; + const int *gyro_scales; + const int gyro_scales_len; +}; + +extern const struct inv_icm45600_chip_info inv_icm45605_chip_info; +extern const struct inv_icm45600_chip_info inv_icm45606_chip_info; +extern const struct inv_icm45600_chip_info inv_icm45608_chip_info; +extern const struct inv_icm45600_chip_info inv_icm45634_chip_info; +extern const struct inv_icm45600_chip_info inv_icm45686_chip_info; +extern const struct inv_icm45600_chip_info inv_icm45687_chip_info; +extern const struct inv_icm45600_chip_info inv_icm45688p_chip_info; +extern const struct inv_icm45600_chip_info inv_icm45689_chip_info; + +extern const int inv_icm45600_accel_scale[][2]; +extern const int inv_icm45686_accel_scale[][2]; +extern const int inv_icm45600_gyro_scale[][2]; +extern const int inv_icm45686_gyro_scale[][2]; + +/** + * struct inv_icm45600_state - driver state variables + * @lock: lock for serializing multiple registers access. + * @map: regmap pointer. + * @vddio_supply: I/O voltage regulator for the chip. + * @orientation: sensor chip orientation relative to main hardware. + * @conf: chip sensors configurations. + * @suspended: suspended sensors configuration. + * @indio_gyro: gyroscope IIO device. + * @indio_accel: accelerometer IIO device. + * @chip_info: chip driver data. + * @timestamp: interrupt timestamps. + * @fifo: FIFO management structure. + * @buffer: data transfer buffer aligned for DMA. + */ +struct inv_icm45600_state { + struct mutex lock; + struct regmap *map; + struct regulator *vddio_supply; + struct iio_mount_matrix orientation; + struct inv_icm45600_conf conf; + struct inv_icm45600_suspended suspended; + struct iio_dev *indio_gyro; + struct iio_dev *indio_accel; + const struct inv_icm45600_chip_info *chip_info; + struct { + s64 gyro; + s64 accel; + } timestamp; + struct inv_icm45600_fifo fifo; + union { + u8 buff[2]; + __le16 u16; + u8 ireg[3]; + } buffer __aligned(IIO_DMA_MINALIGN); +}; + +/** + * struct inv_icm45600_sensor_state - sensor state variables + * @scales: table of scales. + * @scales_len: length (nb of items) of the scales table. + * @power_mode: sensor requested power mode (for common frequencies) + * @ts: timestamp module states. + */ +struct inv_icm45600_sensor_state { + const int *scales; + size_t scales_len; + enum inv_icm45600_sensor_mode power_mode; + struct inv_sensors_timestamp ts; +}; + +/* Virtual register addresses: @bank on MSB (16 bits), @address on LSB */ + +/* Indirect register access */ +#define INV_ICM45600_REG_IREG_ADDR 0x7C +#define INV_ICM45600_REG_IREG_DATA 0x7E + +/* Direct access registers */ +#define INV_ICM45600_REG_MISC2 0x007F +#define INV_ICM45600_MISC2_SOFT_RESET BIT(1) + +#define INV_ICM45600_REG_DRIVE_CONFIG0 0x0032 +#define INV_ICM45600_DRIVE_CONFIG0_SPI_MASK GENMASK(3, 1) +#define INV_ICM45600_SPI_SLEW_RATE_0_5NS 6 +#define INV_ICM45600_SPI_SLEW_RATE_4NS 5 +#define INV_ICM45600_SPI_SLEW_RATE_5NS 4 +#define INV_ICM45600_SPI_SLEW_RATE_7NS 3 +#define INV_ICM45600_SPI_SLEW_RATE_10NS 2 +#define INV_ICM45600_SPI_SLEW_RATE_14NS 1 +#define INV_ICM45600_SPI_SLEW_RATE_38NS 0 + +#define INV_ICM45600_REG_INT1_CONFIG2 0x0018 +#define INV_ICM45600_INT1_CONFIG2_OPEN_DRAIN BIT(2) +#define INV_ICM45600_INT1_CONFIG2_LATCHED BIT(1) +#define INV_ICM45600_INT1_CONFIG2_ACTIVE_HIGH BIT(0) +#define INV_ICM45600_INT1_CONFIG2_ACTIVE_LOW 0x00 + +#define INV_ICM45600_REG_FIFO_CONFIG0 0x001D +#define INV_ICM45600_FIFO_CONFIG0_MODE_MASK GENMASK(7, 6) +#define INV_ICM45600_FIFO_CONFIG0_MODE_BYPASS 0 +#define INV_ICM45600_FIFO_CONFIG0_MODE_STREAM 1 +#define INV_ICM45600_FIFO_CONFIG0_MODE_STOP_ON_FULL 2 +#define INV_ICM45600_FIFO_CONFIG0_FIFO_DEPTH_MASK GENMASK(5, 0) +#define INV_ICM45600_FIFO_CONFIG0_FIFO_DEPTH_MAX 0x1F + +#define INV_ICM45600_REG_FIFO_CONFIG2 0x0020 +#define INV_ICM45600_REG_FIFO_CONFIG2_FIFO_FLUSH BIT(7) +#define INV_ICM45600_REG_FIFO_CONFIG2_WM_GT_TH BIT(3) + +#define INV_ICM45600_REG_FIFO_CONFIG3 0x0021 +#define INV_ICM45600_FIFO_CONFIG3_ES1_EN BIT(5) +#define INV_ICM45600_FIFO_CONFIG3_ES0_EN BIT(4) +#define INV_ICM45600_FIFO_CONFIG3_HIRES_EN BIT(3) +#define INV_ICM45600_FIFO_CONFIG3_GYRO_EN BIT(2) +#define INV_ICM45600_FIFO_CONFIG3_ACCEL_EN BIT(1) +#define INV_ICM45600_FIFO_CONFIG3_IF_EN BIT(0) + +#define INV_ICM45600_REG_FIFO_CONFIG4 0x0022 +#define INV_ICM45600_FIFO_CONFIG4_COMP_EN BIT(2) +#define INV_ICM45600_FIFO_CONFIG4_TMST_FSYNC_EN BIT(1) +#define INV_ICM45600_FIFO_CONFIG4_ES0_9B BIT(0) + +/* all sensor data are 16 bits (2 registers wide) in big-endian */ +#define INV_ICM45600_REG_TEMP_DATA 0x000C +#define INV_ICM45600_REG_ACCEL_DATA_X 0x0000 +#define INV_ICM45600_REG_ACCEL_DATA_Y 0x0002 +#define INV_ICM45600_REG_ACCEL_DATA_Z 0x0004 +#define INV_ICM45600_REG_GYRO_DATA_X 0x0006 +#define INV_ICM45600_REG_GYRO_DATA_Y 0x0008 +#define INV_ICM45600_REG_GYRO_DATA_Z 0x000A + +#define INV_ICM45600_REG_INT_STATUS 0x0019 +#define INV_ICM45600_INT_STATUS_RESET_DONE BIT(7) +#define INV_ICM45600_INT_STATUS_AUX1_AGC_RDY BIT(6) +#define INV_ICM45600_INT_STATUS_AP_AGC_RDY BIT(5) +#define INV_ICM45600_INT_STATUS_AP_FSYNC BIT(4) +#define INV_ICM45600_INT_STATUS_AUX1_DRDY BIT(3) +#define INV_ICM45600_INT_STATUS_DATA_RDY BIT(2) +#define INV_ICM45600_INT_STATUS_FIFO_THS BIT(1) +#define INV_ICM45600_INT_STATUS_FIFO_FULL BIT(0) + +/* + * FIFO access registers + * FIFO count is 16 bits (2 registers) + * FIFO data is a continuous read register to read FIFO content + */ +#define INV_ICM45600_REG_FIFO_COUNT 0x0012 +#define INV_ICM45600_REG_FIFO_DATA 0x0014 + +#define INV_ICM45600_REG_PWR_MGMT0 0x0010 +#define INV_ICM45600_PWR_MGMT0_GYRO_MODE_MASK GENMASK(3, 2) +#define INV_ICM45600_PWR_MGMT0_ACCEL_MODE_MASK GENMASK(1, 0) + +#define INV_ICM45600_REG_ACCEL_CONFIG0 0x001B +#define INV_ICM45600_ACCEL_CONFIG0_FS_MASK GENMASK(6, 4) +#define INV_ICM45600_ACCEL_CONFIG0_ODR_MASK GENMASK(3, 0) +#define INV_ICM45600_REG_GYRO_CONFIG0 0x001C +#define INV_ICM45600_GYRO_CONFIG0_FS_MASK GENMASK(7, 4) +#define INV_ICM45600_GYRO_CONFIG0_ODR_MASK GENMASK(3, 0) + +#define INV_ICM45600_REG_SMC_CONTROL_0 0xA258 +#define INV_ICM45600_SMC_CONTROL_0_ACCEL_LP_CLK_SEL BIT(4) +#define INV_ICM45600_SMC_CONTROL_0_TMST_EN BIT(0) + +/* FIFO watermark is 16 bits (2 registers wide) in little-endian */ +#define INV_ICM45600_REG_FIFO_WATERMARK 0x001E + +/* FIFO is configured for 8kb */ +#define INV_ICM45600_FIFO_SIZE_MAX SZ_8K + +#define INV_ICM45600_REG_INT1_CONFIG0 0x0016 +#define INV_ICM45600_INT1_CONFIG0_RESET_DONE_EN BIT(7) +#define INV_ICM45600_INT1_CONFIG0_AUX1_AGC_RDY_EN BIT(6) +#define INV_ICM45600_INT1_CONFIG0_AP_AGC_RDY_EN BIT(5) +#define INV_ICM45600_INT1_CONFIG0_AP_FSYNC_EN BIT(4) +#define INV_ICM45600_INT1_CONFIG0_AUX1_DRDY_EN BIT(3) +#define INV_ICM45600_INT1_CONFIG0_DRDY_EN BIT(2) +#define INV_ICM45600_INT1_CONFIG0_FIFO_THS_EN BIT(1) +#define INV_ICM45600_INT1_CONFIG0_FIFO_FULL_EN BIT(0) + +#define INV_ICM45600_REG_WHOAMI 0x0072 +#define INV_ICM45600_WHOAMI_ICM45605 0xE5 +#define INV_ICM45600_WHOAMI_ICM45686 0xE9 +#define INV_ICM45600_WHOAMI_ICM45688P 0xE7 +#define INV_ICM45600_WHOAMI_ICM45608 0x81 +#define INV_ICM45600_WHOAMI_ICM45634 0x82 +#define INV_ICM45600_WHOAMI_ICM45689 0x83 +#define INV_ICM45600_WHOAMI_ICM45606 0x84 +#define INV_ICM45600_WHOAMI_ICM45687 0x85 + +/* Gyro USER offset */ +#define INV_ICM45600_IPREG_SYS1_REG_42 0xA42A +#define INV_ICM45600_IPREG_SYS1_REG_56 0xA438 +#define INV_ICM45600_IPREG_SYS1_REG_70 0xA446 +#define INV_ICM45600_GYRO_OFFUSER_MASK GENMASK(13, 0) +/* Gyro Averaging filter */ +#define INV_ICM45600_IPREG_SYS1_REG_170 0xA4AA +#define INV_ICM45600_IPREG_SYS1_170_GYRO_LP_AVG_MASK GENMASK(4, 1) +#define INV_ICM45600_GYRO_LP_AVG_SEL_8X 5 +#define INV_ICM45600_GYRO_LP_AVG_SEL_2X 1 +/* Accel USER offset */ +#define INV_ICM45600_IPREG_SYS2_REG_24 0xA518 +#define INV_ICM45600_IPREG_SYS2_REG_32 0xA520 +#define INV_ICM45600_IPREG_SYS2_REG_40 0xA528 +#define INV_ICM45600_ACCEL_OFFUSER_MASK GENMASK(13, 0) +/* Accel averaging filter */ +#define INV_ICM45600_IPREG_SYS2_REG_129 0xA581 +#define INV_ICM45600_ACCEL_LP_AVG_SEL_1X 0x0000 +#define INV_ICM45600_ACCEL_LP_AVG_SEL_4X 0x0002 + +/* Sleep times required by the driver */ +#define INV_ICM45600_ACCEL_STARTUP_TIME_MS 60 +#define INV_ICM45600_GYRO_STARTUP_TIME_MS 60 +#define INV_ICM45600_GYRO_STOP_TIME_MS 150 +#define INV_ICM45600_IREG_DELAY_US 4 + +typedef int (*inv_icm45600_bus_setup)(struct inv_icm45600_state *); + +extern const struct dev_pm_ops inv_icm45600_pm_ops; + +const struct iio_mount_matrix * +inv_icm45600_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan); + +#define INV_ICM45600_TEMP_CHAN(_index) \ + { \ + .type = IIO_TEMP, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ + } + +int inv_icm45600_temp_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask); + +u32 inv_icm45600_odr_to_period(enum inv_icm45600_odr odr); + +int inv_icm45600_set_accel_conf(struct inv_icm45600_state *st, + struct inv_icm45600_sensor_conf *conf, + unsigned int *sleep_ms); + +int inv_icm45600_set_gyro_conf(struct inv_icm45600_state *st, + struct inv_icm45600_sensor_conf *conf, + unsigned int *sleep_ms); + +int inv_icm45600_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval); + +int inv_icm45600_core_probe(struct regmap *regmap, + const struct inv_icm45600_chip_info *chip_info, + bool reset, inv_icm45600_bus_setup bus_setup); + +struct iio_dev *inv_icm45600_gyro_init(struct inv_icm45600_state *st); + +int inv_icm45600_gyro_parse_fifo(struct iio_dev *indio_dev); + +struct iio_dev *inv_icm45600_accel_init(struct inv_icm45600_state *st); + +int inv_icm45600_accel_parse_fifo(struct iio_dev *indio_dev); + +#endif diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_accel.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_accel.c new file mode 100644 index 000000000..efa22e026 --- /dev/null +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_accel.c @@ -0,0 +1,782 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2025 Invensense, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "inv_icm45600_buffer.h" +#include "inv_icm45600.h" + +enum inv_icm45600_accel_scan { + INV_ICM45600_ACCEL_SCAN_X, + INV_ICM45600_ACCEL_SCAN_Y, + INV_ICM45600_ACCEL_SCAN_Z, + INV_ICM45600_ACCEL_SCAN_TEMP, + INV_ICM45600_ACCEL_SCAN_TIMESTAMP, +}; + +static const struct iio_chan_spec_ext_info inv_icm45600_accel_ext_infos[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, inv_icm45600_get_mount_matrix), + { } +}; + +#define INV_ICM45600_ACCEL_CHAN(_modifier, _index, _ext_info) \ + { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = _modifier, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_all = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ + .ext_info = _ext_info, \ + } + +static const struct iio_chan_spec inv_icm45600_accel_channels[] = { + INV_ICM45600_ACCEL_CHAN(IIO_MOD_X, INV_ICM45600_ACCEL_SCAN_X, + inv_icm45600_accel_ext_infos), + INV_ICM45600_ACCEL_CHAN(IIO_MOD_Y, INV_ICM45600_ACCEL_SCAN_Y, + inv_icm45600_accel_ext_infos), + INV_ICM45600_ACCEL_CHAN(IIO_MOD_Z, INV_ICM45600_ACCEL_SCAN_Z, + inv_icm45600_accel_ext_infos), + INV_ICM45600_TEMP_CHAN(INV_ICM45600_ACCEL_SCAN_TEMP), + IIO_CHAN_SOFT_TIMESTAMP(INV_ICM45600_ACCEL_SCAN_TIMESTAMP), +}; + +/* + * IIO buffer data: size must be a power of 2 and timestamp aligned + * 16 bytes: 6 bytes acceleration, 2 bytes temperature, 8 bytes timestamp + */ +struct inv_icm45600_accel_buffer { + struct inv_icm45600_fifo_sensor_data accel; + s16 temp; + aligned_s64 timestamp; +}; + +static const unsigned long inv_icm45600_accel_scan_masks[] = { + /* 3-axis accel + temperature */ + BIT(INV_ICM45600_ACCEL_SCAN_X) | + BIT(INV_ICM45600_ACCEL_SCAN_Y) | + BIT(INV_ICM45600_ACCEL_SCAN_Z) | + BIT(INV_ICM45600_ACCEL_SCAN_TEMP), + 0 +}; + +/* enable accelerometer sensor and FIFO write */ +static int inv_icm45600_accel_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev); + struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES; + unsigned int fifo_en = 0; + unsigned int sleep = 0; + int ret; + + scoped_guard(mutex, &st->lock) { + if (*scan_mask & BIT(INV_ICM45600_ACCEL_SCAN_TEMP)) + fifo_en |= INV_ICM45600_SENSOR_TEMP; + + if (*scan_mask & (BIT(INV_ICM45600_ACCEL_SCAN_X) | + BIT(INV_ICM45600_ACCEL_SCAN_Y) | + BIT(INV_ICM45600_ACCEL_SCAN_Z))) { + /* enable accel sensor */ + conf.mode = accel_st->power_mode; + ret = inv_icm45600_set_accel_conf(st, &conf, &sleep); + if (ret) + return ret; + fifo_en |= INV_ICM45600_SENSOR_ACCEL; + } + + /* Update data FIFO write. */ + ret = inv_icm45600_buffer_set_fifo_en(st, fifo_en | st->fifo.en); + } + + /* Sleep required time. */ + if (sleep) + msleep(sleep); + + return ret; +} + +static int _inv_icm45600_accel_read_sensor(struct inv_icm45600_state *st, + struct inv_icm45600_sensor_state *accel_st, + unsigned int reg, int *val) +{ + struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES; + int ret; + + /* enable accel sensor */ + conf.mode = accel_st->power_mode; + ret = inv_icm45600_set_accel_conf(st, &conf, NULL); + if (ret) + return ret; + + /* read accel register data */ + ret = regmap_bulk_read(st->map, reg, &st->buffer.u16, sizeof(st->buffer.u16)); + if (ret) + return ret; + + *val = sign_extend32(le16_to_cpup(&st->buffer.u16), 15); + if (*val == INV_ICM45600_DATA_INVALID) + return -ENODATA; + + return 0; +} + +static int inv_icm45600_accel_read_sensor(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int reg; + int ret; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM45600_REG_ACCEL_DATA_X; + break; + case IIO_MOD_Y: + reg = INV_ICM45600_REG_ACCEL_DATA_Y; + break; + case IIO_MOD_Z: + reg = INV_ICM45600_REG_ACCEL_DATA_Z; + break; + default: + return -EINVAL; + } + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + scoped_guard(mutex, &st->lock) + ret = _inv_icm45600_accel_read_sensor(st, accel_st, reg, val); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +/* IIO format int + nano */ +const int inv_icm45600_accel_scale[][2] = { + /* +/- 16G => 0.004788403 m/s-2 */ + [INV_ICM45600_ACCEL_FS_16G] = { 0, 4788403 }, + /* +/- 8G => 0.002394202 m/s-2 */ + [INV_ICM45600_ACCEL_FS_8G] = { 0, 2394202 }, + /* +/- 4G => 0.001197101 m/s-2 */ + [INV_ICM45600_ACCEL_FS_4G] = { 0, 1197101 }, + /* +/- 2G => 0.000598550 m/s-2 */ + [INV_ICM45600_ACCEL_FS_2G] = { 0, 598550 }, +}; + +const int inv_icm45686_accel_scale[][2] = { + /* +/- 32G => 0.009576806 m/s-2 */ + [INV_ICM45686_ACCEL_FS_32G] = { 0, 9576806 }, + /* +/- 16G => 0.004788403 m/s-2 */ + [INV_ICM45686_ACCEL_FS_16G] = { 0, 4788403 }, + /* +/- 8G => 0.002394202 m/s-2 */ + [INV_ICM45686_ACCEL_FS_8G] = { 0, 2394202 }, + /* +/- 4G => 0.001197101 m/s-2 */ + [INV_ICM45686_ACCEL_FS_4G] = { 0, 1197101 }, + /* +/- 2G => 0.000598550 m/s-2 */ + [INV_ICM45686_ACCEL_FS_2G] = { 0, 598550 }, +}; + +static int inv_icm45600_accel_read_scale(struct iio_dev *indio_dev, + int *val, int *val2) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev); + unsigned int idx; + + idx = st->conf.accel.fs; + + /* Full scale register starts at 1 for not High FSR parts */ + if (accel_st->scales == (const int *)&inv_icm45600_accel_scale) + idx--; + + *val = accel_st->scales[2 * idx]; + *val2 = accel_st->scales[2 * idx + 1]; + return IIO_VAL_INT_PLUS_NANO; +} + +static int inv_icm45600_accel_write_scale(struct iio_dev *indio_dev, + int val, int val2) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES; + int ret; + + for (idx = 0; idx < accel_st->scales_len; idx += 2) { + if (val == accel_st->scales[idx] && + val2 == accel_st->scales[idx + 1]) + break; + } + if (idx == accel_st->scales_len) + return -EINVAL; + + conf.fs = idx / 2; + + /* Full scale register starts at 1 for not High FSR parts */ + if (accel_st->scales == (const int *)&inv_icm45600_accel_scale) + conf.fs++; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + scoped_guard(mutex, &st->lock) + ret = inv_icm45600_set_accel_conf(st, &conf, NULL); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +/* IIO format int + micro */ +static const int inv_icm45600_accel_odr[] = { + 1, 562500, /* 1.5625Hz */ + 3, 125000, /* 3.125Hz */ + 6, 250000, /* 6.25Hz */ + 12, 500000, /* 12.5Hz */ + 25, 0, /* 25Hz */ + 50, 0, /* 50Hz */ + 100, 0, /* 100Hz */ + 200, 0, /* 200Hz */ + 400, 0, /* 400Hz */ + 800, 0, /* 800Hz */ + 1600, 0, /* 1.6kHz */ + 3200, 0, /* 3.2kHz */ + 6400, 0, /* 6.4kHz */ +}; + +static const int inv_icm45600_accel_odr_conv[] = { + INV_ICM45600_ODR_1_5625HZ_LP, + INV_ICM45600_ODR_3_125HZ_LP, + INV_ICM45600_ODR_6_25HZ_LP, + INV_ICM45600_ODR_12_5HZ, + INV_ICM45600_ODR_25HZ, + INV_ICM45600_ODR_50HZ, + INV_ICM45600_ODR_100HZ, + INV_ICM45600_ODR_200HZ, + INV_ICM45600_ODR_400HZ, + INV_ICM45600_ODR_800HZ_LN, + INV_ICM45600_ODR_1600HZ_LN, + INV_ICM45600_ODR_3200HZ_LN, + INV_ICM45600_ODR_6400HZ_LN, +}; + +static int inv_icm45600_accel_read_odr(struct inv_icm45600_state *st, + int *val, int *val2) +{ + unsigned int odr; + unsigned int i; + + odr = st->conf.accel.odr; + + for (i = 0; i < ARRAY_SIZE(inv_icm45600_accel_odr_conv); ++i) { + if (inv_icm45600_accel_odr_conv[i] == odr) + break; + } + if (i >= ARRAY_SIZE(inv_icm45600_accel_odr_conv)) + return -EINVAL; + + *val = inv_icm45600_accel_odr[2 * i]; + *val2 = inv_icm45600_accel_odr[2 * i + 1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int _inv_icm45600_accel_write_odr(struct iio_dev *indio_dev, int odr) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &accel_st->ts; + struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES; + int ret; + + conf.odr = odr; + ret = inv_sensors_timestamp_update_odr(ts, inv_icm45600_odr_to_period(conf.odr), + iio_buffer_enabled(indio_dev)); + if (ret) + return ret; + + if (st->conf.accel.mode != INV_ICM45600_SENSOR_MODE_OFF) + conf.mode = accel_st->power_mode; + + ret = inv_icm45600_set_accel_conf(st, &conf, NULL); + if (ret) + return ret; + + inv_icm45600_buffer_update_fifo_period(st); + inv_icm45600_buffer_update_watermark(st); + + return 0; +} + +static int inv_icm45600_accel_write_odr(struct iio_dev *indio_dev, + int val, int val2) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + int odr; + int ret; + + for (idx = 0; idx < ARRAY_SIZE(inv_icm45600_accel_odr); idx += 2) { + if (val == inv_icm45600_accel_odr[idx] && + val2 == inv_icm45600_accel_odr[idx + 1]) + break; + } + if (idx >= ARRAY_SIZE(inv_icm45600_accel_odr)) + return -EINVAL; + + odr = inv_icm45600_accel_odr_conv[idx / 2]; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + scoped_guard(mutex, &st->lock) + ret = _inv_icm45600_accel_write_odr(indio_dev, odr); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +/* + * Calibration bias values, IIO range format int + micro. + * Value is limited to +/-1g coded on 14 bits signed. Step is 0.125mg. + */ +static int inv_icm45600_accel_calibbias[] = { + -9, 806650, /* min: -9.806650 m/s² */ + 0, 1197, /* step: 0.001197 m/s² */ + 9, 805453, /* max: 9.805453 m/s² */ +}; + +static int inv_icm45600_accel_read_offset(struct inv_icm45600_state *st, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + struct device *dev = regmap_get_device(st->map); + s64 val64; + s32 bias; + unsigned int reg; + s16 offset; + int ret; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM45600_IPREG_SYS2_REG_24; + break; + case IIO_MOD_Y: + reg = INV_ICM45600_IPREG_SYS2_REG_32; + break; + case IIO_MOD_Z: + reg = INV_ICM45600_IPREG_SYS2_REG_40; + break; + default: + return -EINVAL; + } + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + scoped_guard(mutex, &st->lock) + ret = regmap_bulk_read(st->map, reg, &st->buffer.u16, sizeof(st->buffer.u16)); + + pm_runtime_put_autosuspend(dev); + if (ret) + return ret; + + offset = le16_to_cpup(&st->buffer.u16) & INV_ICM45600_ACCEL_OFFUSER_MASK; + /* 14 bits signed value */ + offset = sign_extend32(offset, 13); + + /* + * convert raw offset to g then to m/s² + * 14 bits signed raw step 1/8192g + * g to m/s²: 9.806650 + * result in micro (* 1000000) + * (offset * 9806650) / 8192 + */ + val64 = (s64)offset * 9806650LL; + /* for rounding, add + or - divisor (8192) divided by 2 */ + if (val64 >= 0) + val64 += 8192LL / 2LL; + else + val64 -= 8192LL / 2LL; + bias = div_s64(val64, 8192L); + *val = bias / 1000000L; + *val2 = bias % 1000000L; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int inv_icm45600_accel_write_offset(struct inv_icm45600_state *st, + struct iio_chan_spec const *chan, + int val, int val2) +{ + struct device *dev = regmap_get_device(st->map); + s64 val64; + s32 min, max; + unsigned int reg; + s16 offset; + int ret; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM45600_IPREG_SYS2_REG_24; + break; + case IIO_MOD_Y: + reg = INV_ICM45600_IPREG_SYS2_REG_32; + break; + case IIO_MOD_Z: + reg = INV_ICM45600_IPREG_SYS2_REG_40; + break; + default: + return -EINVAL; + } + + /* inv_icm45600_accel_calibbias: min - step - max in micro */ + min = inv_icm45600_accel_calibbias[0] * 1000000L - + inv_icm45600_accel_calibbias[1]; + max = inv_icm45600_accel_calibbias[4] * 1000000L + + inv_icm45600_accel_calibbias[5]; + val64 = (s64)val * 1000000LL; + if (val >= 0) + val64 += (s64)val2; + else + val64 -= (s64)val2; + if (val64 < min || val64 > max) + return -EINVAL; + + /* + * convert m/s² to g then to raw value + * m/s² to g: 1 / 9.806650 + * g to raw 14 bits signed, step 1/8192g: * 8192 + * val in micro (1000000) + * val * 8192 / (9.806650 * 1000000) + */ + val64 = val64 * 8192LL; + /* for rounding, add + or - divisor (9806650) divided by 2 */ + if (val64 >= 0) + val64 += 9806650 / 2; + else + val64 -= 9806650 / 2; + offset = div_s64(val64, 9806650); + + /* clamp value limited to 14 bits signed */ + offset = clamp(offset, -8192, 8191); + + st->buffer.u16 = cpu_to_le16(offset & INV_ICM45600_ACCEL_OFFUSER_MASK); + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + scoped_guard(mutex, &st->lock) + ret = regmap_bulk_write(st->map, reg, &st->buffer.u16, sizeof(st->buffer.u16)); + + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int inv_icm45600_accel_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + switch (chan->type) { + case IIO_ACCEL: + break; + case IIO_TEMP: + return inv_icm45600_temp_read_raw(indio_dev, chan, val, val2, mask); + default: + return -EINVAL; + } + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = inv_icm45600_accel_read_sensor(indio_dev, chan, val); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + return inv_icm45600_accel_read_scale(indio_dev, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return inv_icm45600_accel_read_odr(st, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + return inv_icm45600_accel_read_offset(st, chan, val, val2); + default: + return -EINVAL; + } +} + +static int inv_icm45600_accel_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, + int *type, int *length, long mask) +{ + struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev); + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = accel_st->scales; + *type = IIO_VAL_INT_PLUS_NANO; + *length = accel_st->scales_len; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = inv_icm45600_accel_odr; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = ARRAY_SIZE(inv_icm45600_accel_odr); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_CALIBBIAS: + *vals = inv_icm45600_accel_calibbias; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_RANGE; + default: + return -EINVAL; + } +} + +static int inv_icm45600_accel_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = inv_icm45600_accel_write_scale(indio_dev, val, val2); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + return inv_icm45600_accel_write_odr(indio_dev, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = inv_icm45600_accel_write_offset(st, chan, val, val2); + iio_device_release_direct(indio_dev); + return ret; + default: + return -EINVAL; + } +} + +static int inv_icm45600_accel_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBBIAS: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int inv_icm45600_accel_hwfifo_set_watermark(struct iio_dev *indio_dev, + unsigned int val) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + + guard(mutex)(&st->lock); + + st->fifo.watermark.accel = val; + return inv_icm45600_buffer_update_watermark(st); +} + +static int inv_icm45600_accel_hwfifo_flush(struct iio_dev *indio_dev, + unsigned int count) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + if (count == 0) + return 0; + + guard(mutex)(&st->lock); + + ret = inv_icm45600_buffer_hwfifo_flush(st, count); + if (ret) + return ret; + + return st->fifo.nb.accel; +} + +static const struct iio_info inv_icm45600_accel_info = { + .read_raw = inv_icm45600_accel_read_raw, + .read_avail = inv_icm45600_accel_read_avail, + .write_raw = inv_icm45600_accel_write_raw, + .write_raw_get_fmt = inv_icm45600_accel_write_raw_get_fmt, + .debugfs_reg_access = inv_icm45600_debugfs_reg, + .update_scan_mode = inv_icm45600_accel_update_scan_mode, + .hwfifo_set_watermark = inv_icm45600_accel_hwfifo_set_watermark, + .hwfifo_flush_to_buffer = inv_icm45600_accel_hwfifo_flush, +}; + +struct iio_dev *inv_icm45600_accel_init(struct inv_icm45600_state *st) +{ + struct device *dev = regmap_get_device(st->map); + struct inv_icm45600_sensor_state *accel_st; + struct inv_sensors_timestamp_chip ts_chip; + struct iio_dev *indio_dev; + const char *name; + int ret; + + name = devm_kasprintf(dev, GFP_KERNEL, "%s-accel", st->chip_info->name); + if (!name) + return ERR_PTR(-ENOMEM); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*accel_st)); + if (!indio_dev) + return ERR_PTR(-ENOMEM); + accel_st = iio_priv(indio_dev); + + accel_st->scales = st->chip_info->accel_scales; + accel_st->scales_len = st->chip_info->accel_scales_len * 2; + + /* low-power (LP) mode by default at init, no ULP mode */ + accel_st->power_mode = INV_ICM45600_SENSOR_MODE_LOW_POWER; + ret = regmap_set_bits(st->map, INV_ICM45600_REG_SMC_CONTROL_0, + INV_ICM45600_SMC_CONTROL_0_ACCEL_LP_CLK_SEL); + if (ret) + return ERR_PTR(ret); + + /* + * clock period is 32kHz (31250ns) + * jitter is +/- 2% (20 per mille) + */ + ts_chip.clock_period = 31250; + ts_chip.jitter = 20; + ts_chip.init_period = inv_icm45600_odr_to_period(st->conf.accel.odr); + inv_sensors_timestamp_init(&accel_st->ts, &ts_chip); + + iio_device_set_drvdata(indio_dev, st); + indio_dev->name = name; + indio_dev->info = &inv_icm45600_accel_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = inv_icm45600_accel_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_icm45600_accel_channels); + indio_dev->available_scan_masks = inv_icm45600_accel_scan_masks; + + ret = devm_iio_kfifo_buffer_setup(dev, indio_dev, + &inv_icm45600_buffer_ops); + if (ret) + return ERR_PTR(ret); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ERR_PTR(ret); + + return indio_dev; +} + +int inv_icm45600_accel_parse_fifo(struct iio_dev *indio_dev) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &accel_st->ts; + ssize_t i, size; + unsigned int no; + + /* parse all fifo packets */ + for (i = 0, no = 0; i < st->fifo.count; i += size, ++no) { + struct inv_icm45600_accel_buffer buffer = { }; + const struct inv_icm45600_fifo_sensor_data *accel, *gyro; + const __le16 *timestamp; + const s8 *temp; + unsigned int odr; + s64 ts_val; + + size = inv_icm45600_fifo_decode_packet(&st->fifo.data[i], + &accel, &gyro, &temp, ×tamp, &odr); + /* quit if error or FIFO is empty */ + if (size <= 0) + return size; + + /* skip packet if no accel data or data is invalid */ + if (accel == NULL || !inv_icm45600_fifo_is_data_valid(accel)) + continue; + + /* update odr */ + if (odr & INV_ICM45600_SENSOR_ACCEL) + inv_sensors_timestamp_apply_odr(ts, st->fifo.period, + st->fifo.nb.total, no); + + memcpy(&buffer.accel, accel, sizeof(buffer.accel)); + /* convert 8 bits FIFO temperature in high resolution format */ + buffer.temp = temp ? (*temp * 64) : 0; + ts_val = inv_sensors_timestamp_pop(ts); + iio_push_to_buffers_with_ts(indio_dev, &buffer, sizeof(buffer), ts_val); + } + + return 0; +} diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c new file mode 100644 index 000000000..42111c543 --- /dev/null +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c @@ -0,0 +1,561 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Copyright (C) 2025 Invensense, Inc. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "inv_icm45600_buffer.h" +#include "inv_icm45600.h" + +/* FIFO header: 1 byte */ +#define INV_ICM45600_FIFO_EXT_HEADER BIT(7) +#define INV_ICM45600_FIFO_HEADER_ACCEL BIT(6) +#define INV_ICM45600_FIFO_HEADER_GYRO BIT(5) +#define INV_ICM45600_FIFO_HEADER_HIGH_RES BIT(4) +#define INV_ICM45600_FIFO_HEADER_TMST_FSYNC GENMASK(3, 2) +#define INV_ICM45600_FIFO_HEADER_ODR_ACCEL BIT(1) +#define INV_ICM45600_FIFO_HEADER_ODR_GYRO BIT(0) + +struct inv_icm45600_fifo_1sensor_packet { + u8 header; + struct inv_icm45600_fifo_sensor_data data; + s8 temp; +} __packed; + +struct inv_icm45600_fifo_2sensors_packet { + u8 header; + struct inv_icm45600_fifo_sensor_data accel; + struct inv_icm45600_fifo_sensor_data gyro; + s8 temp; + __le16 timestamp; +} __packed; + +ssize_t inv_icm45600_fifo_decode_packet(const void *packet, + const struct inv_icm45600_fifo_sensor_data **accel, + const struct inv_icm45600_fifo_sensor_data **gyro, + const s8 **temp, + const __le16 **timestamp, unsigned int *odr) +{ + const struct inv_icm45600_fifo_1sensor_packet *pack1 = packet; + const struct inv_icm45600_fifo_2sensors_packet *pack2 = packet; + u8 header = *((const u8 *)packet); + + /* FIFO extended header */ + if (header & INV_ICM45600_FIFO_EXT_HEADER) { + /* Not yet supported */ + return 0; + } + + /* handle odr flags. */ + *odr = 0; + if (header & INV_ICM45600_FIFO_HEADER_ODR_GYRO) + *odr |= INV_ICM45600_SENSOR_GYRO; + if (header & INV_ICM45600_FIFO_HEADER_ODR_ACCEL) + *odr |= INV_ICM45600_SENSOR_ACCEL; + + /* Accel + Gyro data are present. */ + if ((header & INV_ICM45600_FIFO_HEADER_ACCEL) && + (header & INV_ICM45600_FIFO_HEADER_GYRO)) { + *accel = &pack2->accel; + *gyro = &pack2->gyro; + *temp = &pack2->temp; + *timestamp = &pack2->timestamp; + return sizeof(*pack2); + } + + /* Accel data only. */ + if (header & INV_ICM45600_FIFO_HEADER_ACCEL) { + *accel = &pack1->data; + *gyro = NULL; + *temp = &pack1->temp; + *timestamp = NULL; + return sizeof(*pack1); + } + + /* Gyro data only. */ + if (header & INV_ICM45600_FIFO_HEADER_GYRO) { + *accel = NULL; + *gyro = &pack1->data; + *temp = &pack1->temp; + *timestamp = NULL; + return sizeof(*pack1); + } + + /* Invalid packet if here. */ + return -EINVAL; +} + +void inv_icm45600_buffer_update_fifo_period(struct inv_icm45600_state *st) +{ + u32 period_gyro, period_accel; + + if (st->fifo.en & INV_ICM45600_SENSOR_GYRO) + period_gyro = inv_icm45600_odr_to_period(st->conf.gyro.odr); + else + period_gyro = U32_MAX; + + if (st->fifo.en & INV_ICM45600_SENSOR_ACCEL) + period_accel = inv_icm45600_odr_to_period(st->conf.accel.odr); + else + period_accel = U32_MAX; + + st->fifo.period = min(period_gyro, period_accel); +} + +int inv_icm45600_buffer_set_fifo_en(struct inv_icm45600_state *st, + unsigned int fifo_en) +{ + unsigned int mask; + int ret; + + mask = INV_ICM45600_FIFO_CONFIG3_GYRO_EN | + INV_ICM45600_FIFO_CONFIG3_ACCEL_EN; + + ret = regmap_assign_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG3, mask, + (fifo_en & INV_ICM45600_SENSOR_GYRO) || + (fifo_en & INV_ICM45600_SENSOR_ACCEL)); + if (ret) + return ret; + + st->fifo.en = fifo_en; + inv_icm45600_buffer_update_fifo_period(st); + + return 0; +} + +static unsigned int inv_icm45600_wm_truncate(unsigned int watermark, size_t packet_size, + unsigned int fifo_period) +{ + size_t watermark_max, grace_samples; + + /* Keep 20ms for processing FIFO.*/ + grace_samples = (20U * NSEC_PER_MSEC) / fifo_period; + if (grace_samples < 1) + grace_samples = 1; + + watermark_max = INV_ICM45600_FIFO_SIZE_MAX / packet_size; + watermark_max -= grace_samples; + + return min(watermark, watermark_max); +} + +/** + * inv_icm45600_buffer_update_watermark - update watermark FIFO threshold + * @st: driver internal state + * + * FIFO watermark threshold is computed based on the required watermark values + * set for gyro and accel sensors. Since watermark is all about acceptable data + * latency, use the smallest setting between the 2. It means choosing the + * smallest latency but this is not as simple as choosing the smallest watermark + * value. Latency depends on watermark and ODR. It requires several steps: + * 1) compute gyro and accel latencies and choose the smallest value. + * 2) adapt the chosen latency so that it is a multiple of both gyro and accel + * ones. Otherwise it is possible that you don't meet a requirement. (for + * example with gyro @100Hz wm 4 and accel @100Hz with wm 6, choosing the + * value of 4 will not meet accel latency requirement because 6 is not a + * multiple of 4. You need to use the value 2.) + * 3) Since all periods are multiple of each others, watermark is computed by + * dividing this computed latency by the smallest period, which corresponds + * to the FIFO frequency. + * + * Returns: 0 on success, a negative error code otherwise. + */ +int inv_icm45600_buffer_update_watermark(struct inv_icm45600_state *st) +{ + const size_t packet_size = sizeof(struct inv_icm45600_fifo_2sensors_packet); + unsigned int wm_gyro, wm_accel, watermark; + u32 period_gyro, period_accel, period; + u32 latency_gyro, latency_accel, latency; + + /* Compute sensors latency, depending on sensor watermark and odr. */ + wm_gyro = inv_icm45600_wm_truncate(st->fifo.watermark.gyro, packet_size, + st->fifo.period); + wm_accel = inv_icm45600_wm_truncate(st->fifo.watermark.accel, packet_size, + st->fifo.period); + /* Use us for odr to avoid overflow using 32 bits values. */ + period_gyro = inv_icm45600_odr_to_period(st->conf.gyro.odr) / NSEC_PER_USEC; + period_accel = inv_icm45600_odr_to_period(st->conf.accel.odr) / NSEC_PER_USEC; + latency_gyro = period_gyro * wm_gyro; + latency_accel = period_accel * wm_accel; + + /* 0 value for watermark means that the sensor is turned off. */ + if (wm_gyro == 0 && wm_accel == 0) + return 0; + + if (latency_gyro == 0) { + watermark = wm_accel; + st->fifo.watermark.eff_accel = wm_accel; + } else if (latency_accel == 0) { + watermark = wm_gyro; + st->fifo.watermark.eff_gyro = wm_gyro; + } else { + /* Compute the smallest latency that is a multiple of both. */ + if (latency_gyro <= latency_accel) + latency = latency_gyro - (latency_accel % latency_gyro); + else + latency = latency_accel - (latency_gyro % latency_accel); + /* Use the shortest period. */ + period = min(period_gyro, period_accel); + /* All this works because periods are multiple of each others. */ + watermark = max(latency / period, 1); + /* Update effective watermark. */ + st->fifo.watermark.eff_gyro = max(latency / period_gyro, 1); + st->fifo.watermark.eff_accel = max(latency / period_accel, 1); + } + + st->buffer.u16 = cpu_to_le16(watermark); + return regmap_bulk_write(st->map, INV_ICM45600_REG_FIFO_WATERMARK, + &st->buffer.u16, sizeof(st->buffer.u16)); +} + +static int inv_icm45600_buffer_preenable(struct iio_dev *indio_dev) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct device *dev = regmap_get_device(st->map); + struct inv_icm45600_sensor_state *sensor_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &sensor_st->ts; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + guard(mutex)(&st->lock); + inv_sensors_timestamp_reset(ts); + + return 0; +} + +/* + * Update_scan_mode callback is turning sensors on and setting data FIFO enable + * bits. + */ +static int inv_icm45600_buffer_postenable(struct iio_dev *indio_dev) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + unsigned int val; + int ret; + + guard(mutex)(&st->lock); + + /* Exit if FIFO is already on. */ + if (st->fifo.on) { + st->fifo.on++; + return 0; + } + + ret = regmap_set_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG2, + INV_ICM45600_REG_FIFO_CONFIG2_FIFO_FLUSH); + if (ret) + return ret; + + ret = regmap_set_bits(st->map, INV_ICM45600_REG_INT1_CONFIG0, + INV_ICM45600_INT1_CONFIG0_FIFO_THS_EN | + INV_ICM45600_INT1_CONFIG0_FIFO_FULL_EN); + if (ret) + return ret; + + val = FIELD_PREP(INV_ICM45600_FIFO_CONFIG0_MODE_MASK, + INV_ICM45600_FIFO_CONFIG0_MODE_STREAM); + ret = regmap_update_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG0, + INV_ICM45600_FIFO_CONFIG0_MODE_MASK, val); + if (ret) + return ret; + + /* Enable writing sensor data to FIFO. */ + ret = regmap_set_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG3, + INV_ICM45600_FIFO_CONFIG3_IF_EN); + if (ret) + return ret; + + st->fifo.on++; + return 0; +} + +static int inv_icm45600_buffer_predisable(struct iio_dev *indio_dev) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + unsigned int val; + int ret; + + guard(mutex)(&st->lock); + + /* Exit if there are several sensors using the FIFO. */ + if (st->fifo.on > 1) { + st->fifo.on--; + return 0; + } + + /* Disable writing sensor data to FIFO. */ + ret = regmap_clear_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG3, + INV_ICM45600_FIFO_CONFIG3_IF_EN); + if (ret) + return ret; + + val = FIELD_PREP(INV_ICM45600_FIFO_CONFIG0_MODE_MASK, + INV_ICM45600_FIFO_CONFIG0_MODE_BYPASS); + ret = regmap_update_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG0, + INV_ICM45600_FIFO_CONFIG0_MODE_MASK, val); + if (ret) + return ret; + + ret = regmap_clear_bits(st->map, INV_ICM45600_REG_INT1_CONFIG0, + INV_ICM45600_INT1_CONFIG0_FIFO_THS_EN | + INV_ICM45600_INT1_CONFIG0_FIFO_FULL_EN); + if (ret) + return ret; + + ret = regmap_set_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG2, + INV_ICM45600_REG_FIFO_CONFIG2_FIFO_FLUSH); + if (ret) + return ret; + + st->fifo.on--; + return 0; +} + +static int _inv_icm45600_buffer_postdisable(struct inv_icm45600_state *st, + unsigned int sensor, unsigned int *watermark, + unsigned int *sleep) +{ + struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES; + int ret; + + ret = inv_icm45600_buffer_set_fifo_en(st, st->fifo.en & ~sensor); + if (ret) + return ret; + + *watermark = 0; + ret = inv_icm45600_buffer_update_watermark(st); + if (ret) + return ret; + + conf.mode = INV_ICM45600_SENSOR_MODE_OFF; + if (sensor == INV_ICM45600_SENSOR_GYRO) + return inv_icm45600_set_gyro_conf(st, &conf, sleep); + else + return inv_icm45600_set_accel_conf(st, &conf, sleep); +} + +static int inv_icm45600_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int sensor; + unsigned int *watermark; + unsigned int sleep; + int ret; + + if (indio_dev == st->indio_gyro) { + sensor = INV_ICM45600_SENSOR_GYRO; + watermark = &st->fifo.watermark.gyro; + } else if (indio_dev == st->indio_accel) { + sensor = INV_ICM45600_SENSOR_ACCEL; + watermark = &st->fifo.watermark.accel; + } else { + return -EINVAL; + } + + sleep = 0; + scoped_guard(mutex, &st->lock) + ret = _inv_icm45600_buffer_postdisable(st, sensor, watermark, &sleep); + + /* Sleep required time. */ + if (sleep) + msleep(sleep); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +const struct iio_buffer_setup_ops inv_icm45600_buffer_ops = { + .preenable = inv_icm45600_buffer_preenable, + .postenable = inv_icm45600_buffer_postenable, + .predisable = inv_icm45600_buffer_predisable, + .postdisable = inv_icm45600_buffer_postdisable, +}; + +int inv_icm45600_buffer_fifo_read(struct inv_icm45600_state *st, + unsigned int max) +{ + const ssize_t packet_size = sizeof(struct inv_icm45600_fifo_2sensors_packet); + __le16 *raw_fifo_count; + size_t fifo_nb, i; + ssize_t size; + const struct inv_icm45600_fifo_sensor_data *accel, *gyro; + const __le16 *timestamp; + const s8 *temp; + unsigned int odr; + int ret; + + /* Reset all samples counters. */ + st->fifo.count = 0; + st->fifo.nb.gyro = 0; + st->fifo.nb.accel = 0; + st->fifo.nb.total = 0; + + raw_fifo_count = &st->buffer.u16; + ret = regmap_bulk_read(st->map, INV_ICM45600_REG_FIFO_COUNT, + raw_fifo_count, sizeof(*raw_fifo_count)); + if (ret) + return ret; + + /* Check and limit number of samples if requested. */ + fifo_nb = le16_to_cpup(raw_fifo_count); + if (fifo_nb == 0) + return 0; + if (max > 0 && fifo_nb > max) + fifo_nb = max; + + /* + * Read all FIFO data into the internal buffer, clamping the + * device-reported count to the buffer capacity. + */ + st->fifo.count = min(fifo_nb * packet_size, INV_ICM45600_FIFO_SIZE_MAX); + ret = regmap_noinc_read(st->map, INV_ICM45600_REG_FIFO_DATA, + st->fifo.data, st->fifo.count); + if (ret == -ENOTSUPP || ret == -EFBIG) { + /* Read full fifo is not supported, read samples one by one. */ + ret = 0; + for (i = 0; i < st->fifo.count && ret == 0; i += packet_size) + ret = regmap_noinc_read(st->map, INV_ICM45600_REG_FIFO_DATA, + &st->fifo.data[i], packet_size); + } + if (ret) + return ret; + + for (i = 0; i < st->fifo.count; i += size) { + size = inv_icm45600_fifo_decode_packet(&st->fifo.data[i], &accel, &gyro, + &temp, ×tamp, &odr); + if (size <= 0) + /* No more sample in buffer */ + break; + if (gyro && inv_icm45600_fifo_is_data_valid(gyro)) + st->fifo.nb.gyro++; + if (accel && inv_icm45600_fifo_is_data_valid(accel)) + st->fifo.nb.accel++; + st->fifo.nb.total++; + } + + return 0; +} + +int inv_icm45600_buffer_fifo_parse(struct inv_icm45600_state *st) +{ + struct inv_icm45600_sensor_state *gyro_st = iio_priv(st->indio_gyro); + struct inv_icm45600_sensor_state *accel_st = iio_priv(st->indio_accel); + struct inv_sensors_timestamp *ts; + int ret; + + if (st->fifo.nb.total == 0) + return 0; + + /* Handle gyroscope timestamp and FIFO data parsing. */ + if (st->fifo.nb.gyro > 0) { + ts = &gyro_st->ts; + inv_sensors_timestamp_interrupt(ts, st->fifo.watermark.eff_gyro, + st->timestamp.gyro); + ret = inv_icm45600_gyro_parse_fifo(st->indio_gyro); + if (ret) + return ret; + } + + /* Handle accelerometer timestamp and FIFO data parsing. */ + if (st->fifo.nb.accel > 0) { + ts = &accel_st->ts; + inv_sensors_timestamp_interrupt(ts, st->fifo.watermark.eff_accel, + st->timestamp.accel); + ret = inv_icm45600_accel_parse_fifo(st->indio_accel); + if (ret) + return ret; + } + + return 0; +} + +int inv_icm45600_buffer_hwfifo_flush(struct inv_icm45600_state *st, + unsigned int count) +{ + struct inv_icm45600_sensor_state *gyro_st = iio_priv(st->indio_gyro); + struct inv_icm45600_sensor_state *accel_st = iio_priv(st->indio_accel); + struct inv_sensors_timestamp *ts; + s64 gyro_ts, accel_ts; + int ret; + + gyro_ts = iio_get_time_ns(st->indio_gyro); + accel_ts = iio_get_time_ns(st->indio_accel); + + ret = inv_icm45600_buffer_fifo_read(st, count); + if (ret) + return ret; + + if (st->fifo.nb.total == 0) + return 0; + + if (st->fifo.nb.gyro > 0) { + ts = &gyro_st->ts; + inv_sensors_timestamp_interrupt(ts, st->fifo.nb.gyro, gyro_ts); + ret = inv_icm45600_gyro_parse_fifo(st->indio_gyro); + if (ret) + return ret; + } + + if (st->fifo.nb.accel > 0) { + ts = &accel_st->ts; + inv_sensors_timestamp_interrupt(ts, st->fifo.nb.accel, accel_ts); + ret = inv_icm45600_accel_parse_fifo(st->indio_accel); + if (ret) + return ret; + } + + return 0; +} + +int inv_icm45600_buffer_init(struct inv_icm45600_state *st) +{ + int ret; + unsigned int val; + + st->fifo.watermark.eff_gyro = 1; + st->fifo.watermark.eff_accel = 1; + + /* Disable all FIFO EN bits. */ + ret = regmap_write(st->map, INV_ICM45600_REG_FIFO_CONFIG3, 0); + if (ret) + return ret; + + /* Disable FIFO and set depth. */ + val = FIELD_PREP(INV_ICM45600_FIFO_CONFIG0_MODE_MASK, + INV_ICM45600_FIFO_CONFIG0_MODE_BYPASS) | + FIELD_PREP(INV_ICM45600_FIFO_CONFIG0_FIFO_DEPTH_MASK, + INV_ICM45600_FIFO_CONFIG0_FIFO_DEPTH_MAX); + + ret = regmap_write(st->map, INV_ICM45600_REG_FIFO_CONFIG0, val); + if (ret) + return ret; + + /* Enable only timestamp in fifo, disable compression. */ + ret = regmap_write(st->map, INV_ICM45600_REG_FIFO_CONFIG4, + INV_ICM45600_FIFO_CONFIG4_TMST_FSYNC_EN); + if (ret) + return ret; + + /* Enable FIFO continuous watermark interrupt. */ + return regmap_set_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG2, + INV_ICM45600_REG_FIFO_CONFIG2_WM_GT_TH); +} diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.h b/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.h new file mode 100644 index 000000000..e047871cd --- /dev/null +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2025 Invensense, Inc. */ + +#ifndef INV_ICM45600_BUFFER_H_ +#define INV_ICM45600_BUFFER_H_ + +#include +#include +#include + +#include + +#include + +struct inv_icm45600_state; + +#define INV_ICM45600_SENSOR_GYRO BIT(0) +#define INV_ICM45600_SENSOR_ACCEL BIT(1) +#define INV_ICM45600_SENSOR_TEMP BIT(2) + +/** + * struct inv_icm45600_fifo - FIFO state variables + * @on: reference counter for FIFO on. + * @en: bits field of INV_ICM45600_SENSOR_* for FIFO EN bits. + * @period: FIFO internal period. + * @watermark: watermark configuration values for accel and gyro. + * @watermark.gyro: requested watermark for gyro. + * @watermark.accel: requested watermark for accel. + * @watermark.eff_gyro: effective watermark for gyro. + * @watermark.eff_accel: effective watermark for accel. + * @count: number of bytes in the FIFO data buffer. + * @nb: gyro, accel and total samples in the FIFO data buffer. + * @data: FIFO data buffer aligned for DMA (8kB) + */ +struct inv_icm45600_fifo { + unsigned int on; + unsigned int en; + u32 period; + struct { + unsigned int gyro; + unsigned int accel; + unsigned int eff_gyro; + unsigned int eff_accel; + } watermark; + size_t count; + struct { + size_t gyro; + size_t accel; + size_t total; + } nb; + u8 *data; +}; + +/* FIFO data packet */ +struct inv_icm45600_fifo_sensor_data { + __le16 x; + __le16 y; + __le16 z; +} __packed; +#define INV_ICM45600_DATA_INVALID S16_MIN + +static inline bool +inv_icm45600_fifo_is_data_valid(const struct inv_icm45600_fifo_sensor_data *s) +{ + s16 x, y, z; + + x = le16_to_cpu(s->x); + y = le16_to_cpu(s->y); + z = le16_to_cpu(s->z); + + return (x != INV_ICM45600_DATA_INVALID || + y != INV_ICM45600_DATA_INVALID || + z != INV_ICM45600_DATA_INVALID); +} + +ssize_t inv_icm45600_fifo_decode_packet(const void *packet, + const struct inv_icm45600_fifo_sensor_data **accel, + const struct inv_icm45600_fifo_sensor_data **gyro, + const s8 **temp, + const __le16 **timestamp, unsigned int *odr); + +extern const struct iio_buffer_setup_ops inv_icm45600_buffer_ops; + +int inv_icm45600_buffer_init(struct inv_icm45600_state *st); + +void inv_icm45600_buffer_update_fifo_period(struct inv_icm45600_state *st); + +int inv_icm45600_buffer_set_fifo_en(struct inv_icm45600_state *st, + unsigned int fifo_en); + +int inv_icm45600_buffer_update_watermark(struct inv_icm45600_state *st); + +int inv_icm45600_buffer_fifo_read(struct inv_icm45600_state *st, + unsigned int max); + +int inv_icm45600_buffer_fifo_parse(struct inv_icm45600_state *st); + +int inv_icm45600_buffer_hwfifo_flush(struct inv_icm45600_state *st, + unsigned int count); + +#endif diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_core.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_core.c new file mode 100644 index 000000000..c1d7aa7e9 --- /dev/null +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_core.c @@ -0,0 +1,994 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Copyright (C) 2025 Invensense, Inc. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "inv_icm45600_buffer.h" +#include "inv_icm45600.h" + +static int inv_icm45600_ireg_read(struct regmap *map, unsigned int reg, + u8 *data, size_t count) +{ + const struct device *dev = regmap_get_device(map); + struct inv_icm45600_state *st = dev_get_drvdata(dev); + unsigned int d; + size_t i; + int ret; + + st->buffer.ireg[0] = FIELD_GET(INV_ICM45600_REG_BANK_MASK, reg); + st->buffer.ireg[1] = FIELD_GET(INV_ICM45600_REG_ADDR_MASK, reg); + + /* Burst write address. */ + ret = regmap_bulk_write(map, INV_ICM45600_REG_IREG_ADDR, st->buffer.ireg, 2); + /* + * Wait while the device is busy processing the address. + * Datasheet: 13.3 MINIMUM WAIT TIME-GAP + */ + fsleep(INV_ICM45600_IREG_DELAY_US); + if (ret) + return ret; + + /* Read the data. */ + for (i = 0; i < count; i++) { + ret = regmap_read(map, INV_ICM45600_REG_IREG_DATA, &d); + /* + * Wait while the device is busy processing the address. + * Datasheet: 13.3 MINIMUM WAIT TIME-GAP + */ + fsleep(INV_ICM45600_IREG_DELAY_US); + if (ret) + return ret; + data[i] = d; + } + + return 0; +} + +static int inv_icm45600_ireg_write(struct regmap *map, unsigned int reg, + const u8 *data, size_t count) +{ + const struct device *dev = regmap_get_device(map); + struct inv_icm45600_state *st = dev_get_drvdata(dev); + size_t i; + int ret; + + st->buffer.ireg[0] = FIELD_GET(INV_ICM45600_REG_BANK_MASK, reg); + st->buffer.ireg[1] = FIELD_GET(INV_ICM45600_REG_ADDR_MASK, reg); + st->buffer.ireg[2] = data[0]; + + /* Burst write address and first byte. */ + ret = regmap_bulk_write(map, INV_ICM45600_REG_IREG_ADDR, st->buffer.ireg, 3); + /* + * Wait while the device is busy processing the address. + * Datasheet: 13.3 MINIMUM WAIT TIME-GAP + */ + fsleep(INV_ICM45600_IREG_DELAY_US); + if (ret) + return ret; + + /* Write the remaining bytes. */ + for (i = 1; i < count; i++) { + ret = regmap_write(map, INV_ICM45600_REG_IREG_DATA, data[i]); + /* + * Wait while the device is busy processing the address. + * Datasheet: 13.3 MINIMUM WAIT TIME-GAP + */ + fsleep(INV_ICM45600_IREG_DELAY_US); + if (ret) + return ret; + } + + return 0; +} + +static int inv_icm45600_read(void *context, const void *reg_buf, size_t reg_size, + void *val_buf, size_t val_size) +{ + unsigned int reg = be16_to_cpup(reg_buf); + struct regmap *map = context; + + if (FIELD_GET(INV_ICM45600_REG_BANK_MASK, reg)) + return inv_icm45600_ireg_read(map, reg, val_buf, val_size); + + return regmap_bulk_read(map, FIELD_GET(INV_ICM45600_REG_ADDR_MASK, reg), + val_buf, val_size); +} + +static int inv_icm45600_write(void *context, const void *data, size_t count) +{ + const u8 *d = data; + unsigned int reg = be16_to_cpup(data); + struct regmap *map = context; + + if (FIELD_GET(INV_ICM45600_REG_BANK_MASK, reg)) + return inv_icm45600_ireg_write(map, reg, d + 2, count - 2); + + return regmap_bulk_write(map, FIELD_GET(INV_ICM45600_REG_ADDR_MASK, reg), + d + 2, count - 2); +} + +static const struct regmap_bus inv_icm45600_regmap_bus = { + .read = inv_icm45600_read, + .write = inv_icm45600_write, +}; + +static const struct regmap_config inv_icm45600_regmap_config = { + .reg_bits = 16, + .val_bits = 8, +}; + +/* These are the chip initial default configurations (default FS value is based on icm45686) */ +static const struct inv_icm45600_conf inv_icm45600_default_conf = { + .gyro = { + .mode = INV_ICM45600_SENSOR_MODE_OFF, + .fs = INV_ICM45686_GYRO_FS_2000DPS, + .odr = INV_ICM45600_ODR_800HZ_LN, + .filter = INV_ICM45600_GYRO_LP_AVG_SEL_8X, + }, + .accel = { + .mode = INV_ICM45600_SENSOR_MODE_OFF, + .fs = INV_ICM45686_ACCEL_FS_16G, + .odr = INV_ICM45600_ODR_800HZ_LN, + .filter = INV_ICM45600_ACCEL_LP_AVG_SEL_4X, + }, +}; + +static const struct inv_icm45600_conf inv_icm45686_default_conf = { + .gyro = { + .mode = INV_ICM45600_SENSOR_MODE_OFF, + .fs = INV_ICM45686_GYRO_FS_4000DPS, + .odr = INV_ICM45600_ODR_800HZ_LN, + .filter = INV_ICM45600_GYRO_LP_AVG_SEL_8X, + }, + .accel = { + .mode = INV_ICM45600_SENSOR_MODE_OFF, + .fs = INV_ICM45686_ACCEL_FS_32G, + .odr = INV_ICM45600_ODR_800HZ_LN, + .filter = INV_ICM45600_ACCEL_LP_AVG_SEL_4X, + }, +}; + +const struct inv_icm45600_chip_info inv_icm45605_chip_info = { + .whoami = INV_ICM45600_WHOAMI_ICM45605, + .name = "icm45605", + .conf = &inv_icm45600_default_conf, + .accel_scales = (const int *)inv_icm45600_accel_scale, + .accel_scales_len = INV_ICM45600_ACCEL_FS_MAX, + .gyro_scales = (const int *)inv_icm45600_gyro_scale, + .gyro_scales_len = INV_ICM45600_GYRO_FS_MAX, +}; +EXPORT_SYMBOL_NS_GPL(inv_icm45605_chip_info, "IIO_ICM45600"); + +const struct inv_icm45600_chip_info inv_icm45606_chip_info = { + .whoami = INV_ICM45600_WHOAMI_ICM45606, + .name = "icm45606", + .conf = &inv_icm45600_default_conf, + .accel_scales = (const int *)inv_icm45600_accel_scale, + .accel_scales_len = INV_ICM45600_ACCEL_FS_MAX, + .gyro_scales = (const int *)inv_icm45600_gyro_scale, + .gyro_scales_len = INV_ICM45600_GYRO_FS_MAX, +}; +EXPORT_SYMBOL_NS_GPL(inv_icm45606_chip_info, "IIO_ICM45600"); + +const struct inv_icm45600_chip_info inv_icm45608_chip_info = { + .whoami = INV_ICM45600_WHOAMI_ICM45608, + .name = "icm45608", + .conf = &inv_icm45600_default_conf, + .accel_scales = (const int *)inv_icm45600_accel_scale, + .accel_scales_len = INV_ICM45600_ACCEL_FS_MAX, + .gyro_scales = (const int *)inv_icm45600_gyro_scale, + .gyro_scales_len = INV_ICM45600_GYRO_FS_MAX, +}; +EXPORT_SYMBOL_NS_GPL(inv_icm45608_chip_info, "IIO_ICM45600"); + +const struct inv_icm45600_chip_info inv_icm45634_chip_info = { + .whoami = INV_ICM45600_WHOAMI_ICM45634, + .name = "icm45634", + .conf = &inv_icm45600_default_conf, + .accel_scales = (const int *)inv_icm45600_accel_scale, + .accel_scales_len = INV_ICM45600_ACCEL_FS_MAX, + .gyro_scales = (const int *)inv_icm45600_gyro_scale, + .gyro_scales_len = INV_ICM45600_GYRO_FS_MAX, +}; +EXPORT_SYMBOL_NS_GPL(inv_icm45634_chip_info, "IIO_ICM45600"); + +const struct inv_icm45600_chip_info inv_icm45686_chip_info = { + .whoami = INV_ICM45600_WHOAMI_ICM45686, + .name = "icm45686", + .conf = &inv_icm45686_default_conf, + .accel_scales = (const int *)inv_icm45686_accel_scale, + .accel_scales_len = INV_ICM45686_ACCEL_FS_MAX, + .gyro_scales = (const int *)inv_icm45686_gyro_scale, + .gyro_scales_len = INV_ICM45686_GYRO_FS_MAX, +}; +EXPORT_SYMBOL_NS_GPL(inv_icm45686_chip_info, "IIO_ICM45600"); + +const struct inv_icm45600_chip_info inv_icm45687_chip_info = { + .whoami = INV_ICM45600_WHOAMI_ICM45687, + .name = "icm45687", + .conf = &inv_icm45686_default_conf, + .accel_scales = (const int *)inv_icm45686_accel_scale, + .accel_scales_len = INV_ICM45686_ACCEL_FS_MAX, + .gyro_scales = (const int *)inv_icm45686_gyro_scale, + .gyro_scales_len = INV_ICM45686_GYRO_FS_MAX, +}; +EXPORT_SYMBOL_NS_GPL(inv_icm45687_chip_info, "IIO_ICM45600"); + +const struct inv_icm45600_chip_info inv_icm45688p_chip_info = { + .whoami = INV_ICM45600_WHOAMI_ICM45688P, + .name = "icm45688p", + .conf = &inv_icm45686_default_conf, + .accel_scales = (const int *)inv_icm45686_accel_scale, + .accel_scales_len = INV_ICM45686_ACCEL_FS_MAX, + .gyro_scales = (const int *)inv_icm45686_gyro_scale, + .gyro_scales_len = INV_ICM45686_GYRO_FS_MAX, +}; +EXPORT_SYMBOL_NS_GPL(inv_icm45688p_chip_info, "IIO_ICM45600"); + +const struct inv_icm45600_chip_info inv_icm45689_chip_info = { + .whoami = INV_ICM45600_WHOAMI_ICM45689, + .name = "icm45689", + .conf = &inv_icm45686_default_conf, + .accel_scales = (const int *)inv_icm45686_accel_scale, + .accel_scales_len = INV_ICM45686_ACCEL_FS_MAX, + .gyro_scales = (const int *)inv_icm45686_gyro_scale, + .gyro_scales_len = INV_ICM45686_GYRO_FS_MAX, +}; +EXPORT_SYMBOL_NS_GPL(inv_icm45689_chip_info, "IIO_ICM45600"); + +const struct iio_mount_matrix * +inv_icm45600_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + const struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + + return &st->orientation; +} + +u32 inv_icm45600_odr_to_period(enum inv_icm45600_odr odr) +{ + static const u32 odr_periods[INV_ICM45600_ODR_MAX] = { + /* 3 first values are reserved, left to 0 */ + [INV_ICM45600_ODR_6400HZ_LN] = 156250, + [INV_ICM45600_ODR_3200HZ_LN] = 312500, + [INV_ICM45600_ODR_1600HZ_LN] = 625000, + [INV_ICM45600_ODR_800HZ_LN] = 1250000, + [INV_ICM45600_ODR_400HZ] = 2500000, + [INV_ICM45600_ODR_200HZ] = 5000000, + [INV_ICM45600_ODR_100HZ] = 10000000, + [INV_ICM45600_ODR_50HZ] = 20000000, + [INV_ICM45600_ODR_25HZ] = 40000000, + [INV_ICM45600_ODR_12_5HZ] = 80000000, + [INV_ICM45600_ODR_6_25HZ_LP] = 160000000, + [INV_ICM45600_ODR_3_125HZ_LP] = 320000000, + [INV_ICM45600_ODR_1_5625HZ_LP] = 640000000, + }; + + return odr_periods[odr]; +} + +static int inv_icm45600_set_pwr_mgmt0(struct inv_icm45600_state *st, + enum inv_icm45600_sensor_mode gyro, + enum inv_icm45600_sensor_mode accel, + unsigned int *sleep_ms) +{ + enum inv_icm45600_sensor_mode oldgyro = st->conf.gyro.mode; + enum inv_icm45600_sensor_mode oldaccel = st->conf.accel.mode; + unsigned int sleepval; + unsigned int val; + int ret; + + /* if nothing changed, exit */ + if (gyro == oldgyro && accel == oldaccel) + return 0; + + val = FIELD_PREP(INV_ICM45600_PWR_MGMT0_GYRO_MODE_MASK, gyro) | + FIELD_PREP(INV_ICM45600_PWR_MGMT0_ACCEL_MODE_MASK, accel); + ret = regmap_write(st->map, INV_ICM45600_REG_PWR_MGMT0, val); + if (ret) + return ret; + + st->conf.gyro.mode = gyro; + st->conf.accel.mode = accel; + + /* Compute the required wait time for sensors to stabilize. */ + sleepval = 0; + if (accel != oldaccel && oldaccel == INV_ICM45600_SENSOR_MODE_OFF) + sleepval = max(sleepval, INV_ICM45600_ACCEL_STARTUP_TIME_MS); + + if (gyro != oldgyro) { + if (oldgyro == INV_ICM45600_SENSOR_MODE_OFF) + sleepval = max(sleepval, INV_ICM45600_GYRO_STARTUP_TIME_MS); + else if (gyro == INV_ICM45600_SENSOR_MODE_OFF) + sleepval = max(sleepval, INV_ICM45600_GYRO_STOP_TIME_MS); + } + + /* Deferred sleep value if sleep pointer is provided or direct sleep */ + if (sleep_ms) + *sleep_ms = sleepval; + else if (sleepval) + msleep(sleepval); + + return 0; +} + +static void inv_icm45600_set_default_conf(struct inv_icm45600_sensor_conf *conf, + struct inv_icm45600_sensor_conf *oldconf) +{ + /* Sanitize missing values with current values. */ + if (conf->mode == U8_MAX) + conf->mode = oldconf->mode; + if (conf->fs == U8_MAX) + conf->fs = oldconf->fs; + if (conf->odr == U8_MAX) + conf->odr = oldconf->odr; + if (conf->filter == U8_MAX) + conf->filter = oldconf->filter; +} + +int inv_icm45600_set_accel_conf(struct inv_icm45600_state *st, + struct inv_icm45600_sensor_conf *conf, + unsigned int *sleep_ms) +{ + struct inv_icm45600_sensor_conf *oldconf = &st->conf.accel; + unsigned int val; + int ret; + + inv_icm45600_set_default_conf(conf, oldconf); + + /* Force the power mode against the ODR when sensor is on. */ + if (conf->mode > INV_ICM45600_SENSOR_MODE_STANDBY) { + if (conf->odr <= INV_ICM45600_ODR_800HZ_LN) { + conf->mode = INV_ICM45600_SENSOR_MODE_LOW_NOISE; + } else { + conf->mode = INV_ICM45600_SENSOR_MODE_LOW_POWER; + /* sanitize averaging value depending on ODR for low-power mode */ + /* maximum 1x @400Hz */ + if (conf->odr == INV_ICM45600_ODR_400HZ) + conf->filter = INV_ICM45600_ACCEL_LP_AVG_SEL_1X; + else + conf->filter = INV_ICM45600_ACCEL_LP_AVG_SEL_4X; + } + } + + /* Set accel fullscale & odr. */ + if (conf->fs != oldconf->fs || conf->odr != oldconf->odr) { + val = FIELD_PREP(INV_ICM45600_ACCEL_CONFIG0_FS_MASK, conf->fs) | + FIELD_PREP(INV_ICM45600_ACCEL_CONFIG0_ODR_MASK, conf->odr); + ret = regmap_write(st->map, INV_ICM45600_REG_ACCEL_CONFIG0, val); + if (ret) + return ret; + oldconf->fs = conf->fs; + oldconf->odr = conf->odr; + } + + /* Set accel low-power average filter. */ + if (conf->filter != oldconf->filter) { + ret = regmap_write(st->map, INV_ICM45600_IPREG_SYS2_REG_129, + conf->filter); + if (ret) + return ret; + oldconf->filter = conf->filter; + } + + /* Update the sensor accel mode. */ + return inv_icm45600_set_pwr_mgmt0(st, st->conf.gyro.mode, conf->mode, + sleep_ms); +} + +int inv_icm45600_set_gyro_conf(struct inv_icm45600_state *st, + struct inv_icm45600_sensor_conf *conf, + unsigned int *sleep_ms) +{ + struct inv_icm45600_sensor_conf *oldconf = &st->conf.gyro; + unsigned int val; + int ret; + + inv_icm45600_set_default_conf(conf, oldconf); + + /* Force the power mode against ODR when sensor is on. */ + if (conf->mode > INV_ICM45600_SENSOR_MODE_STANDBY) { + if (conf->odr >= INV_ICM45600_ODR_6_25HZ_LP) { + conf->mode = INV_ICM45600_SENSOR_MODE_LOW_POWER; + conf->filter = INV_ICM45600_GYRO_LP_AVG_SEL_8X; + } else { + conf->mode = INV_ICM45600_SENSOR_MODE_LOW_NOISE; + } + } + + /* Set gyro fullscale & odr. */ + if (conf->fs != oldconf->fs || conf->odr != oldconf->odr) { + val = FIELD_PREP(INV_ICM45600_GYRO_CONFIG0_FS_MASK, conf->fs) | + FIELD_PREP(INV_ICM45600_GYRO_CONFIG0_ODR_MASK, conf->odr); + ret = regmap_write(st->map, INV_ICM45600_REG_GYRO_CONFIG0, val); + if (ret) + return ret; + oldconf->fs = conf->fs; + oldconf->odr = conf->odr; + } + + /* Set gyro low-power average filter. */ + if (conf->filter != oldconf->filter) { + val = FIELD_PREP(INV_ICM45600_IPREG_SYS1_170_GYRO_LP_AVG_MASK, conf->filter); + ret = regmap_update_bits(st->map, INV_ICM45600_IPREG_SYS1_REG_170, + INV_ICM45600_IPREG_SYS1_170_GYRO_LP_AVG_MASK, val); + if (ret) + return ret; + oldconf->filter = conf->filter; + } + + /* Update the sensor gyro mode. */ + return inv_icm45600_set_pwr_mgmt0(st, conf->mode, st->conf.accel.mode, + sleep_ms); +} + +int inv_icm45600_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + + guard(mutex)(&st->lock); + + if (readval) + return regmap_read(st->map, reg, readval); + else + return regmap_write(st->map, reg, writeval); +} + +static int inv_icm45600_set_conf(struct inv_icm45600_state *st, + const struct inv_icm45600_conf *conf) +{ + unsigned int val; + int ret; + + val = FIELD_PREP(INV_ICM45600_PWR_MGMT0_GYRO_MODE_MASK, conf->gyro.mode) | + FIELD_PREP(INV_ICM45600_PWR_MGMT0_ACCEL_MODE_MASK, conf->accel.mode); + ret = regmap_write(st->map, INV_ICM45600_REG_PWR_MGMT0, val); + if (ret) + return ret; + + val = FIELD_PREP(INV_ICM45600_GYRO_CONFIG0_FS_MASK, conf->gyro.fs) | + FIELD_PREP(INV_ICM45600_GYRO_CONFIG0_ODR_MASK, conf->gyro.odr); + ret = regmap_write(st->map, INV_ICM45600_REG_GYRO_CONFIG0, val); + if (ret) + return ret; + + val = FIELD_PREP(INV_ICM45600_ACCEL_CONFIG0_FS_MASK, conf->accel.fs) | + FIELD_PREP(INV_ICM45600_ACCEL_CONFIG0_ODR_MASK, conf->accel.odr); + ret = regmap_write(st->map, INV_ICM45600_REG_ACCEL_CONFIG0, val); + if (ret) + return ret; + + /* Save configuration. */ + st->conf = *conf; + + return 0; +} + +/** + * inv_icm45600_setup() - check and setup chip + * @st: driver internal state + * @chip_info: detected chip description + * @reset: define whether a reset is required or not + * @bus_setup: callback for setting up bus specific registers + * + * Returns: 0 on success, a negative error code otherwise. + */ +static int inv_icm45600_setup(struct inv_icm45600_state *st, + const struct inv_icm45600_chip_info *chip_info, + bool reset, inv_icm45600_bus_setup bus_setup) +{ + const struct device *dev = regmap_get_device(st->map); + unsigned int val; + int ret; + + /* Set chip bus configuration if specified. */ + if (bus_setup) { + ret = bus_setup(st); + if (ret) + return ret; + } + + /* Check chip self-identification value. */ + ret = regmap_read(st->map, INV_ICM45600_REG_WHOAMI, &val); + if (ret) + return ret; + if (val != chip_info->whoami) { + /* + * SPI interface has no ack mechanism. + * 0xFF or 0x00 whoami means no response from the device. + */ + if (val == U8_MAX || val == 0) + return dev_err_probe(dev, -ENODEV, + "Invalid whoami %#02x expected %#02x (%s)\n", + val, chip_info->whoami, chip_info->name); + + dev_warn(dev, "Unexpected whoami %#02x expected %#02x (%s)\n", + val, chip_info->whoami, chip_info->name); + } + + st->chip_info = chip_info; + + if (reset) { + /* Reset previous state. */ + ret = regmap_write(st->map, INV_ICM45600_REG_MISC2, + INV_ICM45600_MISC2_SOFT_RESET); + if (ret) + return ret; + /* + * IMU reset time. + * Datasheet: 16.84 REG_MISC2 + */ + fsleep(USEC_PER_MSEC); + + if (bus_setup) { + ret = bus_setup(st); + if (ret) + return ret; + } + + ret = regmap_read(st->map, INV_ICM45600_REG_INT_STATUS, &val); + if (ret) + return ret; + if (!(val & INV_ICM45600_INT_STATUS_RESET_DONE)) { + dev_err(dev, "reset error, reset done bit not set\n"); + return -ENODEV; + } + } + + return inv_icm45600_set_conf(st, chip_info->conf); +} + +static irqreturn_t inv_icm45600_irq_timestamp(int irq, void *_data) +{ + struct inv_icm45600_state *st = _data; + + st->timestamp.gyro = iio_get_time_ns(st->indio_gyro); + st->timestamp.accel = iio_get_time_ns(st->indio_accel); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t inv_icm45600_irq_handler(int irq, void *_data) +{ + struct inv_icm45600_state *st = _data; + struct device *dev = regmap_get_device(st->map); + unsigned int mask, status; + int ret; + + guard(mutex)(&st->lock); + + ret = regmap_read(st->map, INV_ICM45600_REG_INT_STATUS, &status); + if (ret) + return IRQ_HANDLED; + + /* Read the FIFO data. */ + mask = INV_ICM45600_INT_STATUS_FIFO_THS | INV_ICM45600_INT_STATUS_FIFO_FULL; + if (status & mask) { + ret = inv_icm45600_buffer_fifo_read(st, 0); + if (ret) { + dev_err(dev, "FIFO read error %d\n", ret); + return IRQ_HANDLED; + } + ret = inv_icm45600_buffer_fifo_parse(st); + if (ret) + dev_err(dev, "FIFO parsing error %d\n", ret); + } + + /* FIFO full warning. */ + if (status & INV_ICM45600_INT_STATUS_FIFO_FULL) + dev_warn(dev, "FIFO full possible data lost!\n"); + + return IRQ_HANDLED; +} + +/** + * inv_icm45600_irq_init() - initialize int pin and interrupt handler + * @st: driver internal state + * @irq: irq number + * @irq_type: irq trigger type + * @open_drain: true if irq is open drain, false for push-pull + * + * Returns: 0 on success, a negative error code otherwise. + */ +static int inv_icm45600_irq_init(struct inv_icm45600_state *st, int irq, + int irq_type, bool open_drain) +{ + struct device *dev = regmap_get_device(st->map); + unsigned int val; + int ret; + + /* Configure INT1 interrupt: default is active low on edge. */ + switch (irq_type) { + case IRQF_TRIGGER_RISING: + case IRQF_TRIGGER_HIGH: + val = INV_ICM45600_INT1_CONFIG2_ACTIVE_HIGH; + break; + default: + val = INV_ICM45600_INT1_CONFIG2_ACTIVE_LOW; + break; + } + + switch (irq_type) { + case IRQF_TRIGGER_LOW: + case IRQF_TRIGGER_HIGH: + val |= INV_ICM45600_INT1_CONFIG2_LATCHED; + break; + default: + break; + } + + if (open_drain) + val |= INV_ICM45600_INT1_CONFIG2_OPEN_DRAIN; + + ret = regmap_write(st->map, INV_ICM45600_REG_INT1_CONFIG2, val); + if (ret) + return ret; + + return devm_request_threaded_irq(dev, irq, inv_icm45600_irq_timestamp, + inv_icm45600_irq_handler, irq_type | IRQF_ONESHOT, + "inv_icm45600", st); +} + +static int inv_icm45600_timestamp_setup(struct inv_icm45600_state *st) +{ + /* Enable timestamps. */ + return regmap_set_bits(st->map, INV_ICM45600_REG_SMC_CONTROL_0, + INV_ICM45600_SMC_CONTROL_0_TMST_EN); +} + +static int inv_icm45600_enable_regulator_vddio(struct inv_icm45600_state *st) +{ + int ret; + + ret = regulator_enable(st->vddio_supply); + if (ret) + return ret; + + /* + * Wait a little for supply ramp. + * Duration is empirically defined. + */ + fsleep(3 * USEC_PER_MSEC); + + return 0; +} + +static void inv_icm45600_disable_vddio_reg(void *_data) +{ + struct inv_icm45600_state *st = _data; + struct device *dev = regmap_get_device(st->map); + + if (pm_runtime_status_suspended(dev)) + return; + + regulator_disable(st->vddio_supply); +} + +int inv_icm45600_core_probe(struct regmap *regmap, const struct inv_icm45600_chip_info *chip_info, + bool reset, inv_icm45600_bus_setup bus_setup) +{ + struct device *dev = regmap_get_device(regmap); + struct inv_icm45600_state *st; + struct regmap *regmap_custom; + struct fwnode_handle *fwnode; + int irq, irq_type; + bool open_drain; + int ret; + + /* Get INT1 only supported interrupt. */ + fwnode = dev_fwnode(dev); + irq = fwnode_irq_get_byname(fwnode, "int1"); + if (irq < 0) + return dev_err_probe(dev, irq, "Missing int1 interrupt\n"); + + irq_type = irq_get_trigger_type(irq); + + open_drain = device_property_read_bool(dev, "drive-open-drain"); + + regmap_custom = devm_regmap_init(dev, &inv_icm45600_regmap_bus, regmap, + &inv_icm45600_regmap_config); + if (IS_ERR(regmap_custom)) + return dev_err_probe(dev, PTR_ERR(regmap_custom), "Failed to register regmap\n"); + + st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL); + if (!st) + return -ENOMEM; + + dev_set_drvdata(dev, st); + + st->fifo.data = devm_kzalloc(dev, INV_ICM45600_FIFO_SIZE_MAX, GFP_KERNEL); + if (!st->fifo.data) + return -ENOMEM; + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + st->map = regmap_custom; + + ret = iio_read_mount_matrix(dev, &st->orientation); + if (ret) + return dev_err_probe(dev, ret, "Failed to retrieve mounting matrix\n"); + + st->vddio_supply = devm_regulator_get(dev, "vddio"); + if (IS_ERR(st->vddio_supply)) + return PTR_ERR(st->vddio_supply); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to get vdd regulator\n"); + + /* + * Supply ramp time + Start-up time. + * Datasheet: 3.3.2 A.C. Electrical Characteristics + */ + fsleep(5 * USEC_PER_MSEC); + + /* set pm_runtime active early for disable vddio resource cleanup */ + ret = pm_runtime_set_active(dev); + if (ret) + return ret; + + ret = inv_icm45600_enable_regulator_vddio(st); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, inv_icm45600_disable_vddio_reg, st); + if (ret) + return ret; + + ret = inv_icm45600_setup(st, chip_info, reset, bus_setup); + if (ret) + return ret; + + ret = inv_icm45600_timestamp_setup(st); + if (ret) + return ret; + + ret = inv_icm45600_buffer_init(st); + if (ret) + return ret; + + st->indio_gyro = inv_icm45600_gyro_init(st); + if (IS_ERR(st->indio_gyro)) + return PTR_ERR(st->indio_gyro); + + st->indio_accel = inv_icm45600_accel_init(st); + if (IS_ERR(st->indio_accel)) + return PTR_ERR(st->indio_accel); + + ret = inv_icm45600_irq_init(st, irq, irq_type, open_drain); + if (ret) + return ret; + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + pm_runtime_get_noresume(dev); + pm_runtime_set_autosuspend_delay(dev, 2 * USEC_PER_MSEC); + pm_runtime_use_autosuspend(dev); + pm_runtime_put(dev); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(inv_icm45600_core_probe, "IIO_ICM45600"); + +/* + * Suspend saves sensors state and turns everything off. + */ +static int inv_icm45600_suspend(struct device *dev) +{ + struct inv_icm45600_state *st = dev_get_drvdata(dev); + int ret; + + scoped_guard(mutex, &st->lock) { + /* Disable FIFO data streaming. */ + if (st->fifo.on) { + unsigned int val; + + /* Clear FIFO_CONFIG3_IF_EN before changing the FIFO configuration */ + ret = regmap_clear_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG3, + INV_ICM45600_FIFO_CONFIG3_IF_EN); + if (ret) + return ret; + val = FIELD_PREP(INV_ICM45600_FIFO_CONFIG0_MODE_MASK, + INV_ICM45600_FIFO_CONFIG0_MODE_BYPASS); + ret = regmap_update_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG0, + INV_ICM45600_FIFO_CONFIG0_MODE_MASK, val); + if (ret) + return ret; + } + + /* Save sensors states */ + st->suspended.gyro = st->conf.gyro.mode; + st->suspended.accel = st->conf.accel.mode; + } + + return pm_runtime_force_suspend(dev); +} + +/* + * System resume gets the system back on and restores the sensors state. + * Manually put runtime power management in system active state. + */ +static int inv_icm45600_resume(struct device *dev) +{ + struct inv_icm45600_state *st = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_force_resume(dev); + if (ret) + return ret; + + scoped_guard(mutex, &st->lock) { + /* Restore sensors state. */ + ret = inv_icm45600_set_pwr_mgmt0(st, st->suspended.gyro, + st->suspended.accel, NULL); + if (ret) + return ret; + + /* Restore FIFO data streaming. */ + if (st->fifo.on) { + struct inv_icm45600_sensor_state *gyro_st = iio_priv(st->indio_gyro); + struct inv_icm45600_sensor_state *accel_st = iio_priv(st->indio_accel); + unsigned int val; + + inv_sensors_timestamp_reset(&gyro_st->ts); + inv_sensors_timestamp_reset(&accel_st->ts); + val = FIELD_PREP(INV_ICM45600_FIFO_CONFIG0_MODE_MASK, + INV_ICM45600_FIFO_CONFIG0_MODE_STREAM); + ret = regmap_update_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG0, + INV_ICM45600_FIFO_CONFIG0_MODE_MASK, val); + if (ret) + return ret; + /* FIFO_CONFIG3_IF_EN must only be set at end of FIFO the configuration */ + ret = regmap_set_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG3, + INV_ICM45600_FIFO_CONFIG3_IF_EN); + if (ret) + return ret; + } + } + + return ret; +} + +/* Runtime suspend will turn off sensors that are enabled by iio devices. */ +static int inv_icm45600_runtime_suspend(struct device *dev) +{ + struct inv_icm45600_state *st = dev_get_drvdata(dev); + int ret; + + guard(mutex)(&st->lock); + + /* disable all sensors */ + ret = inv_icm45600_set_pwr_mgmt0(st, INV_ICM45600_SENSOR_MODE_OFF, + INV_ICM45600_SENSOR_MODE_OFF, NULL); + if (ret) + return ret; + + regulator_disable(st->vddio_supply); + + return 0; +} + +/* Sensors are enabled by iio devices, no need to turn them back on here. */ +static int inv_icm45600_runtime_resume(struct device *dev) +{ + struct inv_icm45600_state *st = dev_get_drvdata(dev); + + guard(mutex)(&st->lock); + + return inv_icm45600_enable_regulator_vddio(st); +} + +static int _inv_icm45600_temp_read(struct inv_icm45600_state *st, s16 *temp) +{ + struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES; + int ret; + + /* Make sure a sensor is on. */ + if (st->conf.gyro.mode == INV_ICM45600_SENSOR_MODE_OFF && + st->conf.accel.mode == INV_ICM45600_SENSOR_MODE_OFF) { + conf.mode = INV_ICM45600_SENSOR_MODE_LOW_POWER; + ret = inv_icm45600_set_accel_conf(st, &conf, NULL); + if (ret) + return ret; + } + + ret = regmap_bulk_read(st->map, INV_ICM45600_REG_TEMP_DATA, + &st->buffer.u16, sizeof(st->buffer.u16)); + if (ret) + return ret; + + *temp = (s16)le16_to_cpup(&st->buffer.u16); + if (*temp == INV_ICM45600_DATA_INVALID) + return -EINVAL; + + return 0; +} + +static int inv_icm45600_temp_read(struct inv_icm45600_state *st, s16 *temp) +{ + struct device *dev = regmap_get_device(st->map); + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + scoped_guard(mutex, &st->lock) + ret = _inv_icm45600_temp_read(st, temp); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +int inv_icm45600_temp_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + s16 temp; + int ret; + + if (chan->type != IIO_TEMP) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = inv_icm45600_temp_read(st, &temp); + if (ret) + return ret; + *val = temp; + return IIO_VAL_INT; + /* + * T°C = (temp / 128) + 25 + * Tm°C = ((temp + 25 * 128) / 128)) * 1000 + * Tm°C = (temp + 3200) * (1000 / 128) + * scale: 1000 / 128 = 7.8125 + * offset: 3200 + */ + case IIO_CHAN_INFO_SCALE: + *val = 7; + *val2 = 812500; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OFFSET: + *val = 3200; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +EXPORT_NS_GPL_DEV_PM_OPS(inv_icm45600_pm_ops, IIO_ICM45600) = { + SYSTEM_SLEEP_PM_OPS(inv_icm45600_suspend, inv_icm45600_resume) + RUNTIME_PM_OPS(inv_icm45600_runtime_suspend, + inv_icm45600_runtime_resume, NULL) +}; + +MODULE_AUTHOR("InvenSense, Inc."); +MODULE_DESCRIPTION("InvenSense ICM-456xx device driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_INV_SENSORS_TIMESTAMP"); diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_gyro.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_gyro.c new file mode 100644 index 000000000..1e85fd0e4 --- /dev/null +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_gyro.c @@ -0,0 +1,791 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2025 Invensense, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "inv_icm45600_buffer.h" +#include "inv_icm45600.h" + +enum inv_icm45600_gyro_scan { + INV_ICM45600_GYRO_SCAN_X, + INV_ICM45600_GYRO_SCAN_Y, + INV_ICM45600_GYRO_SCAN_Z, + INV_ICM45600_GYRO_SCAN_TEMP, + INV_ICM45600_GYRO_SCAN_TIMESTAMP, +}; + +static const struct iio_chan_spec_ext_info inv_icm45600_gyro_ext_infos[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, inv_icm45600_get_mount_matrix), + { } +}; + +#define INV_ICM45600_GYRO_CHAN(_modifier, _index, _ext_info) \ + { \ + .type = IIO_ANGL_VEL, \ + .modified = 1, \ + .channel2 = _modifier, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_all = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ + .ext_info = _ext_info, \ + } + +static const struct iio_chan_spec inv_icm45600_gyro_channels[] = { + INV_ICM45600_GYRO_CHAN(IIO_MOD_X, INV_ICM45600_GYRO_SCAN_X, + inv_icm45600_gyro_ext_infos), + INV_ICM45600_GYRO_CHAN(IIO_MOD_Y, INV_ICM45600_GYRO_SCAN_Y, + inv_icm45600_gyro_ext_infos), + INV_ICM45600_GYRO_CHAN(IIO_MOD_Z, INV_ICM45600_GYRO_SCAN_Z, + inv_icm45600_gyro_ext_infos), + INV_ICM45600_TEMP_CHAN(INV_ICM45600_GYRO_SCAN_TEMP), + IIO_CHAN_SOFT_TIMESTAMP(INV_ICM45600_GYRO_SCAN_TIMESTAMP), +}; + +/* + * IIO buffer data: size must be a power of 2 and timestamp aligned + * 16 bytes: 6 bytes angular velocity, 2 bytes temperature, 8 bytes timestamp + */ +struct inv_icm45600_gyro_buffer { + struct inv_icm45600_fifo_sensor_data gyro; + s16 temp; + aligned_s64 timestamp; +}; + +static const unsigned long inv_icm45600_gyro_scan_masks[] = { + /* 3-axis gyro + temperature */ + BIT(INV_ICM45600_GYRO_SCAN_X) | + BIT(INV_ICM45600_GYRO_SCAN_Y) | + BIT(INV_ICM45600_GYRO_SCAN_Z) | + BIT(INV_ICM45600_GYRO_SCAN_TEMP), + 0 +}; + +/* enable gyroscope sensor and FIFO write */ +static int inv_icm45600_gyro_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev); + struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES; + unsigned int fifo_en = 0; + unsigned int sleep = 0; + int ret; + + scoped_guard(mutex, &st->lock) { + if (*scan_mask & BIT(INV_ICM45600_GYRO_SCAN_TEMP)) + fifo_en |= INV_ICM45600_SENSOR_TEMP; + + if (*scan_mask & (BIT(INV_ICM45600_GYRO_SCAN_X) | + BIT(INV_ICM45600_GYRO_SCAN_Y) | + BIT(INV_ICM45600_GYRO_SCAN_Z))) { + /* enable gyro sensor */ + conf.mode = gyro_st->power_mode; + ret = inv_icm45600_set_gyro_conf(st, &conf, &sleep); + if (ret) + return ret; + fifo_en |= INV_ICM45600_SENSOR_GYRO; + } + ret = inv_icm45600_buffer_set_fifo_en(st, fifo_en | st->fifo.en); + } + if (sleep) + msleep(sleep); + + return ret; +} + +static int _inv_icm45600_gyro_read_sensor(struct inv_icm45600_state *st, + struct inv_icm45600_sensor_state *gyro_st, + unsigned int reg, int *val) +{ + struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES; + int ret; + + /* enable gyro sensor */ + conf.mode = gyro_st->power_mode; + ret = inv_icm45600_set_gyro_conf(st, &conf, NULL); + if (ret) + return ret; + + /* read gyro register data */ + ret = regmap_bulk_read(st->map, reg, &st->buffer.u16, sizeof(st->buffer.u16)); + if (ret) + return ret; + + *val = sign_extend32(le16_to_cpup(&st->buffer.u16), 15); + if (*val == INV_ICM45600_DATA_INVALID) + return -ENODATA; + + return 0; +} + +static int inv_icm45600_gyro_read_sensor(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int reg; + int ret; + + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM45600_REG_GYRO_DATA_X; + break; + case IIO_MOD_Y: + reg = INV_ICM45600_REG_GYRO_DATA_Y; + break; + case IIO_MOD_Z: + reg = INV_ICM45600_REG_GYRO_DATA_Z; + break; + default: + return -EINVAL; + } + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + scoped_guard(mutex, &st->lock) + ret = _inv_icm45600_gyro_read_sensor(st, gyro_st, reg, val); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +/* IIO format int + nano */ +const int inv_icm45600_gyro_scale[][2] = { + /* +/- 2000dps => 0.001065264 rad/s */ + [INV_ICM45600_GYRO_FS_2000DPS] = { 0, 1065264 }, + /* +/- 1000dps => 0.000532632 rad/s */ + [INV_ICM45600_GYRO_FS_1000DPS] = { 0, 532632 }, + /* +/- 500dps => 0.000266316 rad/s */ + [INV_ICM45600_GYRO_FS_500DPS] = { 0, 266316 }, + /* +/- 250dps => 0.000133158 rad/s */ + [INV_ICM45600_GYRO_FS_250DPS] = { 0, 133158 }, + /* +/- 125dps => 0.000066579 rad/s */ + [INV_ICM45600_GYRO_FS_125DPS] = { 0, 66579 }, + /* +/- 62.5dps => 0.000033290 rad/s */ + [INV_ICM45600_GYRO_FS_62_5DPS] = { 0, 33290 }, + /* +/- 31.25dps => 0.000016645 rad/s */ + [INV_ICM45600_GYRO_FS_31_25DPS] = { 0, 16645 }, + /* +/- 15.625dps => 0.000008322 rad/s */ + [INV_ICM45600_GYRO_FS_15_625DPS] = { 0, 8322 }, +}; + +/* IIO format int + nano */ +const int inv_icm45686_gyro_scale[][2] = { + /* +/- 4000dps => 0.002130529 rad/s */ + [INV_ICM45686_GYRO_FS_4000DPS] = { 0, 2130529 }, + /* +/- 2000dps => 0.001065264 rad/s */ + [INV_ICM45686_GYRO_FS_2000DPS] = { 0, 1065264 }, + /* +/- 1000dps => 0.000532632 rad/s */ + [INV_ICM45686_GYRO_FS_1000DPS] = { 0, 532632 }, + /* +/- 500dps => 0.000266316 rad/s */ + [INV_ICM45686_GYRO_FS_500DPS] = { 0, 266316 }, + /* +/- 250dps => 0.000133158 rad/s */ + [INV_ICM45686_GYRO_FS_250DPS] = { 0, 133158 }, + /* +/- 125dps => 0.000066579 rad/s */ + [INV_ICM45686_GYRO_FS_125DPS] = { 0, 66579 }, + /* +/- 62.5dps => 0.000033290 rad/s */ + [INV_ICM45686_GYRO_FS_62_5DPS] = { 0, 33290 }, + /* +/- 31.25dps => 0.000016645 rad/s */ + [INV_ICM45686_GYRO_FS_31_25DPS] = { 0, 16645 }, + /* +/- 15.625dps => 0.000008322 rad/s */ + [INV_ICM45686_GYRO_FS_15_625DPS] = { 0, 8322 }, +}; + +static int inv_icm45600_gyro_read_scale(struct iio_dev *indio_dev, + int *val, int *val2) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev); + unsigned int idx; + + idx = st->conf.gyro.fs; + + /* Full scale register starts at 1 for not High FSR parts */ + if (gyro_st->scales == (const int *)&inv_icm45600_gyro_scale) + idx--; + + *val = gyro_st->scales[2 * idx]; + *val2 = gyro_st->scales[2 * idx + 1]; + return IIO_VAL_INT_PLUS_NANO; +} + +static int inv_icm45600_gyro_write_scale(struct iio_dev *indio_dev, + int val, int val2) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES; + int ret; + + for (idx = 0; idx < gyro_st->scales_len; idx += 2) { + if (val == gyro_st->scales[idx] && + val2 == gyro_st->scales[idx + 1]) + break; + } + if (idx == gyro_st->scales_len) + return -EINVAL; + + conf.fs = idx / 2; + + /* Full scale register starts at 1 for not High FSR parts */ + if (gyro_st->scales == (const int *)&inv_icm45600_gyro_scale) + conf.fs++; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + scoped_guard(mutex, &st->lock) + ret = inv_icm45600_set_gyro_conf(st, &conf, NULL); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +/* IIO format int + micro */ +static const int inv_icm45600_gyro_odr[] = { + 1, 562500, /* 1.5625Hz */ + 3, 125000, /* 3.125Hz */ + 6, 250000, /* 6.25Hz */ + 12, 500000, /* 12.5Hz */ + 25, 0, /* 25Hz */ + 50, 0, /* 50Hz */ + 100, 0, /* 100Hz */ + 200, 0, /* 200Hz */ + 400, 0, /* 400Hz */ + 800, 0, /* 800Hz */ + 1600, 0, /* 1.6kHz */ + 3200, 0, /* 3.2kHz */ + 6400, 0, /* 6.4kHz */ +}; + +static const int inv_icm45600_gyro_odr_conv[] = { + INV_ICM45600_ODR_1_5625HZ_LP, + INV_ICM45600_ODR_3_125HZ_LP, + INV_ICM45600_ODR_6_25HZ_LP, + INV_ICM45600_ODR_12_5HZ, + INV_ICM45600_ODR_25HZ, + INV_ICM45600_ODR_50HZ, + INV_ICM45600_ODR_100HZ, + INV_ICM45600_ODR_200HZ, + INV_ICM45600_ODR_400HZ, + INV_ICM45600_ODR_800HZ_LN, + INV_ICM45600_ODR_1600HZ_LN, + INV_ICM45600_ODR_3200HZ_LN, + INV_ICM45600_ODR_6400HZ_LN, +}; + +static int inv_icm45600_gyro_read_odr(struct inv_icm45600_state *st, + int *val, int *val2) +{ + unsigned int odr; + unsigned int i; + + odr = st->conf.gyro.odr; + + for (i = 0; i < ARRAY_SIZE(inv_icm45600_gyro_odr_conv); ++i) { + if (inv_icm45600_gyro_odr_conv[i] == odr) + break; + } + if (i >= ARRAY_SIZE(inv_icm45600_gyro_odr_conv)) + return -EINVAL; + + *val = inv_icm45600_gyro_odr[2 * i]; + *val2 = inv_icm45600_gyro_odr[2 * i + 1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int _inv_icm45600_gyro_write_odr(struct iio_dev *indio_dev, int odr) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &gyro_st->ts; + struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES; + int ret; + + conf.odr = odr; + ret = inv_sensors_timestamp_update_odr(ts, inv_icm45600_odr_to_period(conf.odr), + iio_buffer_enabled(indio_dev)); + if (ret) + return ret; + + if (st->conf.gyro.mode != INV_ICM45600_SENSOR_MODE_OFF) + conf.mode = gyro_st->power_mode; + + ret = inv_icm45600_set_gyro_conf(st, &conf, NULL); + if (ret) + return ret; + + inv_icm45600_buffer_update_fifo_period(st); + inv_icm45600_buffer_update_watermark(st); + + return 0; +} + +static int inv_icm45600_gyro_write_odr(struct iio_dev *indio_dev, + int val, int val2) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + int odr; + int ret; + + for (idx = 0; idx < ARRAY_SIZE(inv_icm45600_gyro_odr); idx += 2) { + if (val == inv_icm45600_gyro_odr[idx] && + val2 == inv_icm45600_gyro_odr[idx + 1]) + break; + } + if (idx >= ARRAY_SIZE(inv_icm45600_gyro_odr)) + return -EINVAL; + + odr = inv_icm45600_gyro_odr_conv[idx / 2]; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + scoped_guard(mutex, &st->lock) + ret = _inv_icm45600_gyro_write_odr(indio_dev, odr); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +/* + * Calibration bias values, IIO range format int + nano. + * Value is limited to +/-62.5dps coded on 14 bits signed. Step is 7.5mdps. + */ +static int inv_icm45600_gyro_calibbias[] = { + -1, 90830336, /* min: -1.090830336 rad/s */ + 0, 133158, /* step: 0.000133158 rad/s */ + 1, 90697178, /* max: 1.090697178 rad/s */ +}; + +static int inv_icm45600_gyro_read_offset(struct inv_icm45600_state *st, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + struct device *dev = regmap_get_device(st->map); + s64 val64; + s32 bias; + unsigned int reg; + s16 offset; + int ret; + + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM45600_IPREG_SYS1_REG_42; + break; + case IIO_MOD_Y: + reg = INV_ICM45600_IPREG_SYS1_REG_56; + break; + case IIO_MOD_Z: + reg = INV_ICM45600_IPREG_SYS1_REG_70; + break; + default: + return -EINVAL; + } + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + scoped_guard(mutex, &st->lock) + ret = regmap_bulk_read(st->map, reg, &st->buffer.u16, sizeof(st->buffer.u16)); + + pm_runtime_put_autosuspend(dev); + if (ret) + return ret; + + offset = le16_to_cpup(&st->buffer.u16) & INV_ICM45600_GYRO_OFFUSER_MASK; + /* 14 bits signed value */ + offset = sign_extend32(offset, 13); + + /* + * convert raw offset to dps then to rad/s + * 14 bits signed raw max 62.5 to dps: 625 / 81920 + * dps to rad: Pi / 180 + * result in nano (1000000000) + * (offset * 625 * Pi * 1000000000) / (81920 * 180) + */ + val64 = (s64)offset * 625LL * 3141592653LL; + /* for rounding, add + or - divisor (81920 * 180) divided by 2 */ + if (val64 >= 0) + val64 += 81920 * 180 / 2; + else + val64 -= 81920 * 180 / 2; + bias = div_s64(val64, 81920 * 180); + *val = bias / 1000000000L; + *val2 = bias % 1000000000L; + + return IIO_VAL_INT_PLUS_NANO; +} + +static int inv_icm45600_gyro_write_offset(struct inv_icm45600_state *st, + struct iio_chan_spec const *chan, + int val, int val2) +{ + struct device *dev = regmap_get_device(st->map); + s64 val64, min, max; + unsigned int reg; + s16 offset; + int ret; + + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM45600_IPREG_SYS1_REG_42; + break; + case IIO_MOD_Y: + reg = INV_ICM45600_IPREG_SYS1_REG_56; + break; + case IIO_MOD_Z: + reg = INV_ICM45600_IPREG_SYS1_REG_70; + break; + default: + return -EINVAL; + } + + /* inv_icm45600_gyro_calibbias: min - step - max in nano */ + min = (s64)inv_icm45600_gyro_calibbias[0] * 1000000000LL - + (s64)inv_icm45600_gyro_calibbias[1]; + max = (s64)inv_icm45600_gyro_calibbias[4] * 1000000000LL + + (s64)inv_icm45600_gyro_calibbias[5]; + val64 = (s64)val * 1000000000LL; + if (val >= 0) + val64 += (s64)val2; + else + val64 -= (s64)val2; + if (val64 < min || val64 > max) + return -EINVAL; + + /* + * convert rad/s to dps then to raw value + * rad to dps: 180 / Pi + * dps to raw 14 bits signed, max 62.5: 8192 / 62.5 + * val in nano (1000000000) + * val * 180 * 8192 / (Pi * 1000000000 * 62.5) + */ + val64 = val64 * 180LL * 8192; + /* for rounding, add + or - divisor (314159265 * 625) divided by 2 */ + if (val64 >= 0) + val64 += 314159265LL * 625LL / 2LL; + else + val64 -= 314159265LL * 625LL / 2LL; + offset = div64_s64(val64, 314159265LL * 625LL); + + /* clamp value limited to 14 bits signed */ + offset = clamp(offset, -8192, 8191); + + st->buffer.u16 = cpu_to_le16(offset & INV_ICM45600_GYRO_OFFUSER_MASK); + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + scoped_guard(mutex, &st->lock) + ret = regmap_bulk_write(st->map, reg, &st->buffer.u16, sizeof(st->buffer.u16)); + + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int inv_icm45600_gyro_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + switch (chan->type) { + case IIO_ANGL_VEL: + break; + case IIO_TEMP: + return inv_icm45600_temp_read_raw(indio_dev, chan, val, val2, mask); + default: + return -EINVAL; + } + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = inv_icm45600_gyro_read_sensor(indio_dev, chan, val); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + return inv_icm45600_gyro_read_scale(indio_dev, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return inv_icm45600_gyro_read_odr(st, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + return inv_icm45600_gyro_read_offset(st, chan, val, val2); + default: + return -EINVAL; + } +} + +static int inv_icm45600_gyro_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, + int *type, int *length, long mask) +{ + struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev); + + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = gyro_st->scales; + *type = IIO_VAL_INT_PLUS_NANO; + *length = gyro_st->scales_len; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = inv_icm45600_gyro_odr; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = ARRAY_SIZE(inv_icm45600_gyro_odr); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_CALIBBIAS: + *vals = inv_icm45600_gyro_calibbias; + *type = IIO_VAL_INT_PLUS_NANO; + return IIO_AVAIL_RANGE; + default: + return -EINVAL; + } +} + +static int inv_icm45600_gyro_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = inv_icm45600_gyro_write_scale(indio_dev, val, val2); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + return inv_icm45600_gyro_write_odr(indio_dev, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = inv_icm45600_gyro_write_offset(st, chan, val, val2); + iio_device_release_direct(indio_dev); + return ret; + default: + return -EINVAL; + } +} + +static int inv_icm45600_gyro_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBBIAS: + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static int inv_icm45600_gyro_hwfifo_set_watermark(struct iio_dev *indio_dev, + unsigned int val) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + + guard(mutex)(&st->lock); + + st->fifo.watermark.gyro = val; + return inv_icm45600_buffer_update_watermark(st); +} + +static int inv_icm45600_gyro_hwfifo_flush(struct iio_dev *indio_dev, + unsigned int count) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + if (count == 0) + return 0; + + guard(mutex)(&st->lock); + + ret = inv_icm45600_buffer_hwfifo_flush(st, count); + if (ret) + return ret; + + return st->fifo.nb.gyro; +} + +static const struct iio_info inv_icm45600_gyro_info = { + .read_raw = inv_icm45600_gyro_read_raw, + .read_avail = inv_icm45600_gyro_read_avail, + .write_raw = inv_icm45600_gyro_write_raw, + .write_raw_get_fmt = inv_icm45600_gyro_write_raw_get_fmt, + .debugfs_reg_access = inv_icm45600_debugfs_reg, + .update_scan_mode = inv_icm45600_gyro_update_scan_mode, + .hwfifo_set_watermark = inv_icm45600_gyro_hwfifo_set_watermark, + .hwfifo_flush_to_buffer = inv_icm45600_gyro_hwfifo_flush, +}; + +struct iio_dev *inv_icm45600_gyro_init(struct inv_icm45600_state *st) +{ + struct device *dev = regmap_get_device(st->map); + struct inv_icm45600_sensor_state *gyro_st; + struct inv_sensors_timestamp_chip ts_chip; + struct iio_dev *indio_dev; + const char *name; + int ret; + + name = devm_kasprintf(dev, GFP_KERNEL, "%s-gyro", st->chip_info->name); + if (!name) + return ERR_PTR(-ENOMEM); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*gyro_st)); + if (!indio_dev) + return ERR_PTR(-ENOMEM); + gyro_st = iio_priv(indio_dev); + + gyro_st->scales = st->chip_info->gyro_scales; + gyro_st->scales_len = st->chip_info->gyro_scales_len * 2; + + /* low-noise by default at init */ + gyro_st->power_mode = INV_ICM45600_SENSOR_MODE_LOW_NOISE; + + /* + * clock period is 32kHz (31250ns) + * jitter is +/- 2% (20 per mille) + */ + ts_chip.clock_period = 31250; + ts_chip.jitter = 20; + ts_chip.init_period = inv_icm45600_odr_to_period(st->conf.gyro.odr); + inv_sensors_timestamp_init(&gyro_st->ts, &ts_chip); + + iio_device_set_drvdata(indio_dev, st); + indio_dev->name = name; + indio_dev->info = &inv_icm45600_gyro_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = inv_icm45600_gyro_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_icm45600_gyro_channels); + indio_dev->available_scan_masks = inv_icm45600_gyro_scan_masks; + indio_dev->setup_ops = &inv_icm45600_buffer_ops; + + ret = devm_iio_kfifo_buffer_setup(dev, indio_dev, + &inv_icm45600_buffer_ops); + if (ret) + return ERR_PTR(ret); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ERR_PTR(ret); + + return indio_dev; +} + +int inv_icm45600_gyro_parse_fifo(struct iio_dev *indio_dev) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &gyro_st->ts; + ssize_t i, size; + unsigned int no; + + /* parse all fifo packets */ + for (i = 0, no = 0; i < st->fifo.count; i += size, ++no) { + struct inv_icm45600_gyro_buffer buffer = { }; + const struct inv_icm45600_fifo_sensor_data *accel, *gyro; + const __le16 *timestamp; + const s8 *temp; + unsigned int odr; + s64 ts_val; + + size = inv_icm45600_fifo_decode_packet(&st->fifo.data[i], + &accel, &gyro, &temp, ×tamp, &odr); + /* quit if error or FIFO is empty */ + if (size <= 0) + return size; + + /* skip packet if no gyro data or data is invalid */ + if (gyro == NULL || !inv_icm45600_fifo_is_data_valid(gyro)) + continue; + + /* update odr */ + if (odr & INV_ICM45600_SENSOR_GYRO) + inv_sensors_timestamp_apply_odr(ts, st->fifo.period, + st->fifo.nb.total, no); + + memcpy(&buffer.gyro, gyro, sizeof(buffer.gyro)); + /* convert 8 bits FIFO temperature in high resolution format */ + buffer.temp = temp ? (*temp * 64) : 0; + ts_val = inv_sensors_timestamp_pop(ts); + iio_push_to_buffers_with_ts(indio_dev, &buffer, sizeof(buffer), ts_val); + } + + return 0; +} diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_i2c.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_i2c.c new file mode 100644 index 000000000..69acf9bac --- /dev/null +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_i2c.c @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Copyright (C) 2025 InvenSense, Inc. */ + +#include +#include +#include +#include +#include + +#include "inv_icm45600.h" + +static const struct regmap_config inv_icm45600_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int inv_icm45600_probe(struct i2c_client *client) +{ + const struct inv_icm45600_chip_info *chip_info; + struct regmap *regmap; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) + return -ENODEV; + + chip_info = i2c_get_match_data(client); + if (!chip_info) + return -ENODEV; + + regmap = devm_regmap_init_i2c(client, &inv_icm45600_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return inv_icm45600_core_probe(regmap, chip_info, true, NULL); +} + +/* + * The device id table is used to identify which device is + * supported by this driver. + */ +static const struct i2c_device_id inv_icm45600_id[] = { + { .name = "icm45605", .driver_data = (kernel_ulong_t)&inv_icm45605_chip_info }, + { .name = "icm45606", .driver_data = (kernel_ulong_t)&inv_icm45606_chip_info }, + { .name = "icm45608", .driver_data = (kernel_ulong_t)&inv_icm45608_chip_info }, + { .name = "icm45634", .driver_data = (kernel_ulong_t)&inv_icm45634_chip_info }, + { .name = "icm45686", .driver_data = (kernel_ulong_t)&inv_icm45686_chip_info }, + { .name = "icm45687", .driver_data = (kernel_ulong_t)&inv_icm45687_chip_info }, + { .name = "icm45688p", .driver_data = (kernel_ulong_t)&inv_icm45688p_chip_info }, + { .name = "icm45689", .driver_data = (kernel_ulong_t)&inv_icm45689_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, inv_icm45600_id); + +static const struct of_device_id inv_icm45600_of_matches[] = { + { + .compatible = "invensense,icm45605", + .data = &inv_icm45605_chip_info, + }, { + .compatible = "invensense,icm45606", + .data = &inv_icm45606_chip_info, + }, { + .compatible = "invensense,icm45608", + .data = &inv_icm45608_chip_info, + }, { + .compatible = "invensense,icm45634", + .data = &inv_icm45634_chip_info, + }, { + .compatible = "invensense,icm45686", + .data = &inv_icm45686_chip_info, + }, { + .compatible = "invensense,icm45687", + .data = &inv_icm45687_chip_info, + }, { + .compatible = "invensense,icm45688p", + .data = &inv_icm45688p_chip_info, + }, { + .compatible = "invensense,icm45689", + .data = &inv_icm45689_chip_info, + }, + { } +}; +MODULE_DEVICE_TABLE(of, inv_icm45600_of_matches); + +static struct i2c_driver inv_icm45600_driver = { + .driver = { + .name = "inv-icm45600-i2c", + .of_match_table = inv_icm45600_of_matches, + .pm = pm_ptr(&inv_icm45600_pm_ops), + }, + .id_table = inv_icm45600_id, + .probe = inv_icm45600_probe, +}; +module_i2c_driver(inv_icm45600_driver); + +MODULE_AUTHOR("InvenSense, Inc."); +MODULE_DESCRIPTION("InvenSense ICM-456xx I2C driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_ICM45600"); diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_i3c.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_i3c.c new file mode 100644 index 000000000..8fb2e519b --- /dev/null +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_i3c.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Copyright (C) 2025 InvenSense, Inc. */ + +#include +#include +#include + +#include +#include + +#include "inv_icm45600.h" + +static const struct regmap_config inv_icm45600_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static const struct i3c_device_id inv_icm45600_i3c_ids[] = { + I3C_DEVICE_EXTRA_INFO(0x0235, 0x0000, 0x0011, (void *)NULL), + I3C_DEVICE_EXTRA_INFO(0x0235, 0x0000, 0x0084, (void *)NULL), + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i3c, inv_icm45600_i3c_ids); + +static const struct inv_icm45600_chip_info *i3c_chip_info[] = { + &inv_icm45605_chip_info, + &inv_icm45606_chip_info, + &inv_icm45608_chip_info, + &inv_icm45634_chip_info, + &inv_icm45686_chip_info, + &inv_icm45687_chip_info, + &inv_icm45688p_chip_info, + &inv_icm45689_chip_info, +}; + +static int inv_icm45600_i3c_probe(struct i3c_device *i3cdev) +{ + int ret; + unsigned int whoami; + struct regmap *regmap; + const int nb_chip = ARRAY_SIZE(i3c_chip_info); + int chip; + + regmap = devm_regmap_init_i3c(i3cdev, &inv_icm45600_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(&i3cdev->dev, PTR_ERR(regmap), + "Failed to register i3c regmap %ld\n", PTR_ERR(regmap)); + + ret = regmap_read(regmap, INV_ICM45600_REG_WHOAMI, &whoami); + if (ret) + return dev_err_probe(&i3cdev->dev, ret, "Failed to read part id %d\n", whoami); + + for (chip = 0; chip < nb_chip; chip++) { + if (whoami == i3c_chip_info[chip]->whoami) + break; + } + + if (chip == nb_chip) + return dev_err_probe(&i3cdev->dev, -ENODEV, + "Failed to match part id %d\n", whoami); + + return inv_icm45600_core_probe(regmap, i3c_chip_info[chip], false, NULL); +} + +static struct i3c_driver inv_icm45600_driver = { + .driver = { + .name = "inv_icm45600_i3c", + .pm = pm_sleep_ptr(&inv_icm45600_pm_ops), + }, + .probe = inv_icm45600_i3c_probe, + .id_table = inv_icm45600_i3c_ids, +}; +module_i3c_driver(inv_icm45600_driver); + +MODULE_AUTHOR("Remi Buisson "); +MODULE_DESCRIPTION("InvenSense ICM-456xx i3c driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_ICM45600"); diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_spi.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_spi.c new file mode 100644 index 000000000..087e590e0 --- /dev/null +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_spi.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Copyright (C) 2025 InvenSense, Inc. */ + +#include +#include +#include +#include +#include + +#include + +#include "inv_icm45600.h" + +static const struct regmap_config inv_icm45600_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int inv_icm45600_spi_bus_setup(struct inv_icm45600_state *st) +{ + /* Set slew rates for SPI. */ + return regmap_update_bits(st->map, INV_ICM45600_REG_DRIVE_CONFIG0, + INV_ICM45600_DRIVE_CONFIG0_SPI_MASK, + FIELD_PREP(INV_ICM45600_DRIVE_CONFIG0_SPI_MASK, + INV_ICM45600_SPI_SLEW_RATE_5NS)); +} + +static int inv_icm45600_probe(struct spi_device *spi) +{ + const struct inv_icm45600_chip_info *chip_info; + struct regmap *regmap; + + chip_info = spi_get_device_match_data(spi); + if (!chip_info) + return -ENODEV; + + /* Use SPI specific regmap. */ + regmap = devm_regmap_init_spi(spi, &inv_icm45600_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return inv_icm45600_core_probe(regmap, chip_info, true, + inv_icm45600_spi_bus_setup); +} + +/* + * The device id table is used to identify which device is + * supported by this driver. + */ +static const struct spi_device_id inv_icm45600_id[] = { + { .name = "icm45605", .driver_data = (kernel_ulong_t)&inv_icm45605_chip_info }, + { .name = "icm45606", .driver_data = (kernel_ulong_t)&inv_icm45606_chip_info }, + { .name = "icm45608", .driver_data = (kernel_ulong_t)&inv_icm45608_chip_info }, + { .name = "icm45634", .driver_data = (kernel_ulong_t)&inv_icm45634_chip_info }, + { .name = "icm45686", .driver_data = (kernel_ulong_t)&inv_icm45686_chip_info }, + { .name = "icm45687", .driver_data = (kernel_ulong_t)&inv_icm45687_chip_info }, + { .name = "icm45688p", .driver_data = (kernel_ulong_t)&inv_icm45688p_chip_info }, + { .name = "icm45689", .driver_data = (kernel_ulong_t)&inv_icm45689_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, inv_icm45600_id); + +static const struct of_device_id inv_icm45600_of_matches[] = { + { + .compatible = "invensense,icm45605", + .data = &inv_icm45605_chip_info, + }, { + .compatible = "invensense,icm45606", + .data = &inv_icm45606_chip_info, + }, { + .compatible = "invensense,icm45608", + .data = &inv_icm45608_chip_info, + }, { + .compatible = "invensense,icm45634", + .data = &inv_icm45634_chip_info, + }, { + .compatible = "invensense,icm45686", + .data = &inv_icm45686_chip_info, + }, { + .compatible = "invensense,icm45687", + .data = &inv_icm45687_chip_info, + }, { + .compatible = "invensense,icm45688p", + .data = &inv_icm45688p_chip_info, + }, { + .compatible = "invensense,icm45689", + .data = &inv_icm45689_chip_info, + }, + { } +}; +MODULE_DEVICE_TABLE(of, inv_icm45600_of_matches); + +static struct spi_driver inv_icm45600_driver = { + .driver = { + .name = "inv-icm45600-spi", + .of_match_table = inv_icm45600_of_matches, + .pm = pm_ptr(&inv_icm45600_pm_ops), + }, + .id_table = inv_icm45600_id, + .probe = inv_icm45600_probe, +}; +module_spi_driver(inv_icm45600_driver); + +MODULE_AUTHOR("InvenSense, Inc."); +MODULE_DESCRIPTION("InvenSense ICM-456xx SPI driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_ICM45600"); diff --git a/drivers/iio/imu/inv_mpu6050/Kconfig b/drivers/iio/imu/inv_mpu6050/Kconfig new file mode 100644 index 000000000..5f62e4fd4 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/Kconfig @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# inv-mpu6050 drivers for Invensense MPU devices and combos +# + +config INV_MPU6050_IIO + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select IIO_INV_SENSORS_TIMESTAMP + +config INV_MPU6050_I2C + tristate "Invensense MPU6050 devices (I2C)" + depends on I2C + select I2C_MUX + select INV_MPU6050_IIO + select REGMAP_I2C + help + This driver supports the Invensense MPU6050/9150, + MPU6500/6515/6880/9250/9255, ICM20608(D)/20609/20689, ICM20600/20602/20690 + and IAM20680 motion tracking devices over I2C. + This driver can be built as a module. The module will be called + inv-mpu6050-i2c. + +config INV_MPU6050_SPI + tristate "Invensense MPU6050 devices (SPI)" + depends on SPI_MASTER + select INV_MPU6050_IIO + select REGMAP_SPI + help + This driver supports the Invensense MPU6000, + MPU6500/6515/6880/9250/9255, ICM20608(D)/20609/20689, ICM20600/20602/20690 + and IAM20680 motion tracking devices over SPI. + This driver can be built as a module. The module will be called + inv-mpu6050-spi. diff --git a/drivers/iio/imu/inv_mpu6050/Makefile b/drivers/iio/imu/inv_mpu6050/Makefile new file mode 100644 index 000000000..c103441a9 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for Invensense MPU6050 device. +# + +obj-$(CONFIG_INV_MPU6050_IIO) += inv-mpu6050.o +inv-mpu6050-y := inv_mpu_core.o inv_mpu_ring.o inv_mpu_trigger.o \ + inv_mpu_aux.o inv_mpu_magn.o + +obj-$(CONFIG_INV_MPU6050_I2C) += inv-mpu6050-i2c.o +inv-mpu6050-i2c-y := inv_mpu_i2c.o inv_mpu_acpi.o + +obj-$(CONFIG_INV_MPU6050_SPI) += inv-mpu6050-spi.o +inv-mpu6050-spi-y := inv_mpu_spi.o diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c new file mode 100644 index 000000000..460792ed2 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * inv_mpu_acpi: ACPI processing for creating client devices + * Copyright (c) 2015, Intel Corporation. + */ + +#ifdef CONFIG_ACPI + +#include +#include +#include +#include +#include + +#include "inv_mpu_iio.h" + +enum inv_mpu_product_name { + INV_MPU_NOT_MATCHED, + INV_MPU_ASUS_T100TA, +}; + +static enum inv_mpu_product_name matched_product_name; + +static int __init asus_t100_matched(const struct dmi_system_id *d) +{ + matched_product_name = INV_MPU_ASUS_T100TA; + + return 0; +} + +static const struct dmi_system_id inv_mpu_dev_list[] = { + { + .callback = asus_t100_matched, + .ident = "Asus Transformer Book T100", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC"), + DMI_MATCH(DMI_PRODUCT_NAME, "T100TA"), + DMI_MATCH(DMI_PRODUCT_VERSION, "1.0"), + }, + }, + /* Add more matching tables here..*/ + { } +}; + +static int asus_acpi_get_sensor_info(struct acpi_device *adev, + struct i2c_client *client, + struct i2c_board_info *info) +{ + struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; + int i; + acpi_status status; + union acpi_object *cpm; + int ret; + + status = acpi_evaluate_object(adev->handle, "CNF0", NULL, &buffer); + if (ACPI_FAILURE(status)) + return -ENODEV; + + cpm = buffer.pointer; + for (i = 0; i < cpm->package.count; ++i) { + union acpi_object *elem; + int j; + + elem = &cpm->package.elements[i]; + for (j = 0; j < elem->package.count; ++j) { + union acpi_object *sub_elem; + + sub_elem = &elem->package.elements[j]; + if (sub_elem->type == ACPI_TYPE_STRING) + strscpy(info->type, sub_elem->string.pointer, + sizeof(info->type)); + else if (sub_elem->type == ACPI_TYPE_INTEGER) { + if (sub_elem->integer.value != client->addr) { + info->addr = sub_elem->integer.value; + break; /* Not a MPU6500 primary */ + } + } + } + } + ret = cpm->package.count; + kfree(buffer.pointer); + + return ret; +} + +static int acpi_i2c_check_resource(struct acpi_resource *ares, void *data) +{ + struct acpi_resource_i2c_serialbus *sb; + u32 *addr = data; + + if (i2c_acpi_get_i2c_resource(ares, &sb)) { + if (*addr) + *addr |= (sb->slave_address << 16); + else + *addr = sb->slave_address; + } + + /* Tell the ACPI core that we already copied this address */ + return 1; +} + +static int inv_mpu_process_acpi_config(struct i2c_client *client, + unsigned short *primary_addr, + unsigned short *secondary_addr) +{ + struct acpi_device *adev = ACPI_COMPANION(&client->dev); + u32 i2c_addr = 0; + LIST_HEAD(resources); + int ret; + + if (!is_acpi_device_node(dev_fwnode(&client->dev))) + return -ENODEV; + + ret = acpi_dev_get_resources(adev, &resources, + acpi_i2c_check_resource, &i2c_addr); + if (ret < 0) + return ret; + + acpi_dev_free_resource_list(&resources); + *primary_addr = lower_16_bits(i2c_addr); + *secondary_addr = upper_16_bits(i2c_addr); + + return 0; +} + +int inv_mpu_acpi_create_mux_client(struct i2c_client *client) +{ + struct inv_mpu6050_state *st = iio_priv(dev_get_drvdata(&client->dev)); + struct acpi_device *adev = ACPI_COMPANION(&client->dev); + + st->mux_client = NULL; + if (adev) { + struct i2c_board_info info = { }; + struct i2c_client *mux_client; + int ret = -1; + + dmi_check_system(inv_mpu_dev_list); + switch (matched_product_name) { + case INV_MPU_ASUS_T100TA: + ret = asus_acpi_get_sensor_info(adev, client, + &info); + break; + /* Add more matched product processing here */ + default: + break; + } + + if (ret < 0) { + /* No matching DMI, so create device on INV6XX type */ + unsigned short primary, secondary; + + ret = inv_mpu_process_acpi_config(client, &primary, + &secondary); + if (!ret && secondary) { + char *name; + + info.addr = secondary; + strscpy(info.type, dev_name(&adev->dev), + sizeof(info.type)); + name = strchr(info.type, ':'); + if (name) + *name = '\0'; + strlcat(info.type, "-client", + sizeof(info.type)); + } else + return 0; /* no secondary addr, which is OK */ + } + mux_client = i2c_new_client_device(st->muxc->adapter[0], &info); + if (IS_ERR(mux_client)) + return PTR_ERR(mux_client); + st->mux_client = mux_client; + } + + return 0; +} + +void inv_mpu_acpi_delete_mux_client(struct i2c_client *client) +{ + struct inv_mpu6050_state *st = iio_priv(dev_get_drvdata(&client->dev)); + + i2c_unregister_device(st->mux_client); +} +#else + +#include "inv_mpu_iio.h" + +int inv_mpu_acpi_create_mux_client(struct i2c_client *client) +{ + return 0; +} + +void inv_mpu_acpi_delete_mux_client(struct i2c_client *client) +{ +} +#endif diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_aux.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_aux.c new file mode 100644 index 000000000..970cf5c47 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_aux.c @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 TDK-InvenSense, Inc. + */ + +#include +#include +#include +#include + +#include "inv_mpu_aux.h" +#include "inv_mpu_iio.h" + +/* + * i2c master auxiliary bus transfer function. + * Requires the i2c operations to be correctly setup before. + * Disables SLV0 and checks for NACK status internally. + * Assumes that only SLV0 is used for transfers. + */ +static int inv_mpu_i2c_master_xfer(const struct inv_mpu6050_state *st) +{ + /* use 50hz frequency for xfer */ + const unsigned int freq = 50; + const unsigned int period_ms = 1000 / freq; + uint8_t d; + unsigned int user_ctrl; + int ret; + unsigned int status; + + /* set sample rate */ + d = INV_MPU6050_FIFO_RATE_TO_DIVIDER(freq); + ret = regmap_write(st->map, st->reg->sample_rate_div, d); + if (ret) + return ret; + + /* start i2c master */ + user_ctrl = st->chip_config.user_ctrl | INV_MPU6050_BIT_I2C_MST_EN; + ret = regmap_write(st->map, st->reg->user_ctrl, user_ctrl); + if (ret) + goto error_restore_rate; + + /* wait for xfer: 1 period + half-period margin */ + msleep(period_ms + period_ms / 2); + + /* stop i2c master */ + user_ctrl = st->chip_config.user_ctrl; + ret = regmap_write(st->map, st->reg->user_ctrl, user_ctrl); + if (ret) + goto error_stop_i2c; + + /* restore sample rate */ + d = st->chip_config.divider; + ret = regmap_write(st->map, st->reg->sample_rate_div, d); + if (ret) + goto error_restore_rate; + + /* disable i2c slave */ + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_CTRL(0), 0); + if (ret) + goto error_disable_i2c; + + /* check i2c status */ + ret = regmap_read(st->map, INV_MPU6050_REG_I2C_MST_STATUS, &status); + if (ret) + return ret; + + if (status & INV_MPU6050_BIT_I2C_SLV0_NACK) + return -EIO; + + return 0; + +error_stop_i2c: + regmap_write(st->map, st->reg->user_ctrl, st->chip_config.user_ctrl); +error_restore_rate: + regmap_write(st->map, st->reg->sample_rate_div, st->chip_config.divider); +error_disable_i2c: + regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_CTRL(0), 0); + return ret; +} + +/** + * inv_mpu_aux_init() - init i2c auxiliary bus + * @st: driver internal state + * + * Returns 0 on success, a negative error code otherwise. + */ +int inv_mpu_aux_init(const struct inv_mpu6050_state *st) +{ + unsigned int val; + int ret; + + /* + * Code based on the vendor Linux kernel v3.0, + * the exact meaning is unknown. + */ + if (st->chip_type == INV_MPU9150) { + unsigned int mask = BIT(7); + + val = st->level_shifter ? mask : 0; + ret = regmap_update_bits(st->map, 0x1, mask, val); + if (ret) + return ret; + } + + /* configure i2c master */ + val = INV_MPU6050_BITS_I2C_MST_CLK_400KHZ | + INV_MPU6050_BIT_WAIT_FOR_ES; + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_MST_CTRL, val); + if (ret) + return ret; + + /* configure i2c master delay */ + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV4_CTRL, 0); + if (ret) + return ret; + + val = INV_MPU6050_BIT_I2C_SLV0_DLY_EN | + INV_MPU6050_BIT_I2C_SLV1_DLY_EN | + INV_MPU6050_BIT_I2C_SLV2_DLY_EN | + INV_MPU6050_BIT_I2C_SLV3_DLY_EN | + INV_MPU6050_BIT_DELAY_ES_SHADOW; + return regmap_write(st->map, INV_MPU6050_REG_I2C_MST_DELAY_CTRL, val); +} + +/** + * inv_mpu_aux_read() - read register function for i2c auxiliary bus + * @st: driver internal state. + * @addr: chip i2c Address + * @reg: chip register address + * @val: buffer for storing read bytes + * @size: number of bytes to read + * + * Returns 0 on success, a negative error code otherwise. + */ +int inv_mpu_aux_read(const struct inv_mpu6050_state *st, uint8_t addr, + uint8_t reg, uint8_t *val, size_t size) +{ + int ret; + + if (size > 0x0F) + return -EINVAL; + + /* setup i2c SLV0 control: i2c addr, register, enable + size */ + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_ADDR(0), + INV_MPU6050_BIT_I2C_SLV_RNW | addr); + if (ret) + return ret; + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_REG(0), reg); + if (ret) + return ret; + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_CTRL(0), + INV_MPU6050_BIT_SLV_EN | size); + if (ret) + return ret; + + /* do i2c xfer, disable i2c slave and check status*/ + ret = inv_mpu_i2c_master_xfer(st); + if (ret) + return ret; + + /* read data in registers */ + return regmap_bulk_read(st->map, INV_MPU6050_REG_EXT_SENS_DATA, + val, size); +} + +/** + * inv_mpu_aux_write() - write register function for i2c auxiliary bus + * @st: driver internal state. + * @addr: chip i2c Address + * @reg: chip register address + * @val: 1 byte value to write + * + * Returns 0 on success, a negative error code otherwise. + */ +int inv_mpu_aux_write(const struct inv_mpu6050_state *st, uint8_t addr, + uint8_t reg, uint8_t val) +{ + int ret; + + /* setup i2c SLV0 control: i2c addr, register, value, enable + size */ + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_ADDR(0), addr); + if (ret) + return ret; + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_REG(0), reg); + if (ret) + return ret; + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_DO(0), val); + if (ret) + return ret; + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_CTRL(0), + INV_MPU6050_BIT_SLV_EN | 1); + if (ret) + return ret; + + /* do i2c xfer, disable i2c slave and check status*/ + ret = inv_mpu_i2c_master_xfer(st); + if (ret) + return ret; + + return 0; +} diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_aux.h b/drivers/iio/imu/inv_mpu6050/inv_mpu_aux.h new file mode 100644 index 000000000..b66997545 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_aux.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2019 TDK-InvenSense, Inc. + */ + +#ifndef INV_MPU_AUX_H_ +#define INV_MPU_AUX_H_ + +#include "inv_mpu_iio.h" + +int inv_mpu_aux_init(const struct inv_mpu6050_state *st); + +int inv_mpu_aux_read(const struct inv_mpu6050_state *st, uint8_t addr, + uint8_t reg, uint8_t *val, size_t size); + +int inv_mpu_aux_write(const struct inv_mpu6050_state *st, uint8_t addr, + uint8_t reg, uint8_t val); + +#endif /* INV_MPU_AUX_H_ */ diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c new file mode 100644 index 000000000..5796896d5 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c @@ -0,0 +1,2270 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* +* Copyright (C) 2012 Invensense, Inc. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "inv_mpu_iio.h" +#include "inv_mpu_magn.h" + +/* + * this is the gyro scale translated from dynamic range plus/minus + * {250, 500, 1000, 2000} to rad/s + */ +static const int gyro_scale_6050[] = {133090, 266181, 532362, 1064724}; + +/* + * this is the accel scale translated from dynamic range plus/minus + * {2, 4, 8, 16} to m/s^2 + */ +static const int accel_scale[] = {598, 1196, 2392, 4785}; + +static const struct inv_mpu6050_reg_map reg_set_icm20602 = { + .sample_rate_div = INV_MPU6050_REG_SAMPLE_RATE_DIV, + .lpf = INV_MPU6050_REG_CONFIG, + .accel_lpf = INV_MPU6500_REG_ACCEL_CONFIG_2, + .user_ctrl = INV_MPU6050_REG_USER_CTRL, + .fifo_en = INV_MPU6050_REG_FIFO_EN, + .gyro_config = INV_MPU6050_REG_GYRO_CONFIG, + .accl_config = INV_MPU6050_REG_ACCEL_CONFIG, + .fifo_count_h = INV_MPU6050_REG_FIFO_COUNT_H, + .fifo_r_w = INV_MPU6050_REG_FIFO_R_W, + .raw_gyro = INV_MPU6050_REG_RAW_GYRO, + .raw_accl = INV_MPU6050_REG_RAW_ACCEL, + .temperature = INV_MPU6050_REG_TEMPERATURE, + .int_enable = INV_MPU6050_REG_INT_ENABLE, + .int_status = INV_MPU6050_REG_INT_STATUS, + .pwr_mgmt_1 = INV_MPU6050_REG_PWR_MGMT_1, + .pwr_mgmt_2 = INV_MPU6050_REG_PWR_MGMT_2, + .int_pin_cfg = INV_MPU6050_REG_INT_PIN_CFG, + .accl_offset = INV_MPU6500_REG_ACCEL_OFFSET, + .gyro_offset = INV_MPU6050_REG_GYRO_OFFSET, + .i2c_if = INV_ICM20602_REG_I2C_IF, +}; + +static const struct inv_mpu6050_reg_map reg_set_6500 = { + .sample_rate_div = INV_MPU6050_REG_SAMPLE_RATE_DIV, + .lpf = INV_MPU6050_REG_CONFIG, + .accel_lpf = INV_MPU6500_REG_ACCEL_CONFIG_2, + .user_ctrl = INV_MPU6050_REG_USER_CTRL, + .fifo_en = INV_MPU6050_REG_FIFO_EN, + .gyro_config = INV_MPU6050_REG_GYRO_CONFIG, + .accl_config = INV_MPU6050_REG_ACCEL_CONFIG, + .fifo_count_h = INV_MPU6050_REG_FIFO_COUNT_H, + .fifo_r_w = INV_MPU6050_REG_FIFO_R_W, + .raw_gyro = INV_MPU6050_REG_RAW_GYRO, + .raw_accl = INV_MPU6050_REG_RAW_ACCEL, + .temperature = INV_MPU6050_REG_TEMPERATURE, + .int_enable = INV_MPU6050_REG_INT_ENABLE, + .int_status = INV_MPU6050_REG_INT_STATUS, + .pwr_mgmt_1 = INV_MPU6050_REG_PWR_MGMT_1, + .pwr_mgmt_2 = INV_MPU6050_REG_PWR_MGMT_2, + .int_pin_cfg = INV_MPU6050_REG_INT_PIN_CFG, + .accl_offset = INV_MPU6500_REG_ACCEL_OFFSET, + .gyro_offset = INV_MPU6050_REG_GYRO_OFFSET, + .i2c_if = 0, +}; + +static const struct inv_mpu6050_reg_map reg_set_6050 = { + .sample_rate_div = INV_MPU6050_REG_SAMPLE_RATE_DIV, + .lpf = INV_MPU6050_REG_CONFIG, + .user_ctrl = INV_MPU6050_REG_USER_CTRL, + .fifo_en = INV_MPU6050_REG_FIFO_EN, + .gyro_config = INV_MPU6050_REG_GYRO_CONFIG, + .accl_config = INV_MPU6050_REG_ACCEL_CONFIG, + .fifo_count_h = INV_MPU6050_REG_FIFO_COUNT_H, + .fifo_r_w = INV_MPU6050_REG_FIFO_R_W, + .raw_gyro = INV_MPU6050_REG_RAW_GYRO, + .raw_accl = INV_MPU6050_REG_RAW_ACCEL, + .temperature = INV_MPU6050_REG_TEMPERATURE, + .int_enable = INV_MPU6050_REG_INT_ENABLE, + .pwr_mgmt_1 = INV_MPU6050_REG_PWR_MGMT_1, + .pwr_mgmt_2 = INV_MPU6050_REG_PWR_MGMT_2, + .int_pin_cfg = INV_MPU6050_REG_INT_PIN_CFG, + .accl_offset = INV_MPU6050_REG_ACCEL_OFFSET, + .gyro_offset = INV_MPU6050_REG_GYRO_OFFSET, + .i2c_if = 0, +}; + +static const struct inv_mpu6050_chip_config chip_config_6050 = { + .clk = INV_CLK_INTERNAL, + .fsr = INV_MPU6050_FSR_2000DPS, + .lpf = INV_MPU6050_FILTER_20HZ, + .divider = INV_MPU6050_FIFO_RATE_TO_DIVIDER(50), + .gyro_en = true, + .accl_en = true, + .temp_en = true, + .magn_en = false, + .gyro_fifo_enable = false, + .accl_fifo_enable = false, + .temp_fifo_enable = false, + .magn_fifo_enable = false, + .accl_fs = INV_MPU6050_FS_02G, + .user_ctrl = 0, +}; + +static const struct inv_mpu6050_chip_config chip_config_6500 = { + .clk = INV_CLK_PLL, + .fsr = INV_MPU6050_FSR_2000DPS, + .lpf = INV_MPU6050_FILTER_20HZ, + .divider = INV_MPU6050_FIFO_RATE_TO_DIVIDER(50), + .gyro_en = true, + .accl_en = true, + .temp_en = true, + .magn_en = false, + .gyro_fifo_enable = false, + .accl_fifo_enable = false, + .temp_fifo_enable = false, + .magn_fifo_enable = false, + .accl_fs = INV_MPU6050_FS_02G, + .user_ctrl = 0, +}; + +/* Indexed by enum inv_devices */ +static const struct inv_mpu6050_hw hw_info[] = { + { + .whoami = INV_MPU6050_WHOAMI_VALUE, + .name = "MPU6050", + .reg = ®_set_6050, + .config = &chip_config_6050, + .fifo_size = 1024, + .temp = {INV_MPU6050_TEMP_OFFSET, INV_MPU6050_TEMP_SCALE}, + .startup_time = {INV_MPU6050_GYRO_STARTUP_TIME, INV_MPU6050_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_MPU6500_WHOAMI_VALUE, + .name = "MPU6500", + .reg = ®_set_6500, + .config = &chip_config_6500, + .fifo_size = 512, + .temp = {INV_MPU6500_TEMP_OFFSET, INV_MPU6500_TEMP_SCALE}, + .startup_time = {INV_MPU6500_GYRO_STARTUP_TIME, INV_MPU6500_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_MPU6515_WHOAMI_VALUE, + .name = "MPU6515", + .reg = ®_set_6500, + .config = &chip_config_6500, + .fifo_size = 512, + .temp = {INV_MPU6500_TEMP_OFFSET, INV_MPU6500_TEMP_SCALE}, + .startup_time = {INV_MPU6500_GYRO_STARTUP_TIME, INV_MPU6500_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_MPU6880_WHOAMI_VALUE, + .name = "MPU6880", + .reg = ®_set_6500, + .config = &chip_config_6500, + .fifo_size = 4096, + .temp = {INV_MPU6500_TEMP_OFFSET, INV_MPU6500_TEMP_SCALE}, + .startup_time = {INV_MPU6500_GYRO_STARTUP_TIME, INV_MPU6500_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_MPU6000_WHOAMI_VALUE, + .name = "MPU6000", + .reg = ®_set_6050, + .config = &chip_config_6050, + .fifo_size = 1024, + .temp = {INV_MPU6050_TEMP_OFFSET, INV_MPU6050_TEMP_SCALE}, + .startup_time = {INV_MPU6050_GYRO_STARTUP_TIME, INV_MPU6050_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_MPU9150_WHOAMI_VALUE, + .name = "MPU9150", + .reg = ®_set_6050, + .config = &chip_config_6050, + .fifo_size = 1024, + .temp = {INV_MPU6050_TEMP_OFFSET, INV_MPU6050_TEMP_SCALE}, + .startup_time = {INV_MPU6050_GYRO_STARTUP_TIME, INV_MPU6050_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_MPU9250_WHOAMI_VALUE, + .name = "MPU9250", + .reg = ®_set_6500, + .config = &chip_config_6500, + .fifo_size = 512, + .temp = {INV_MPU6500_TEMP_OFFSET, INV_MPU6500_TEMP_SCALE}, + .startup_time = {INV_MPU6500_GYRO_STARTUP_TIME, INV_MPU6500_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_MPU9255_WHOAMI_VALUE, + .name = "MPU9255", + .reg = ®_set_6500, + .config = &chip_config_6500, + .fifo_size = 512, + .temp = {INV_MPU6500_TEMP_OFFSET, INV_MPU6500_TEMP_SCALE}, + .startup_time = {INV_MPU6500_GYRO_STARTUP_TIME, INV_MPU6500_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_ICM20608_WHOAMI_VALUE, + .name = "ICM20608", + .reg = ®_set_6500, + .config = &chip_config_6500, + .fifo_size = 512, + .temp = {INV_ICM20608_TEMP_OFFSET, INV_ICM20608_TEMP_SCALE}, + .startup_time = {INV_MPU6500_GYRO_STARTUP_TIME, INV_MPU6500_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_ICM20608D_WHOAMI_VALUE, + .name = "ICM20608D", + .reg = ®_set_6500, + .config = &chip_config_6500, + .fifo_size = 512, + .temp = {INV_ICM20608_TEMP_OFFSET, INV_ICM20608_TEMP_SCALE}, + .startup_time = {INV_MPU6500_GYRO_STARTUP_TIME, INV_MPU6500_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_ICM20609_WHOAMI_VALUE, + .name = "ICM20609", + .reg = ®_set_6500, + .config = &chip_config_6500, + .fifo_size = 4 * 1024, + .temp = {INV_ICM20608_TEMP_OFFSET, INV_ICM20608_TEMP_SCALE}, + .startup_time = {INV_MPU6500_GYRO_STARTUP_TIME, INV_MPU6500_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_ICM20689_WHOAMI_VALUE, + .name = "ICM20689", + .reg = ®_set_6500, + .config = &chip_config_6500, + .fifo_size = 4 * 1024, + .temp = {INV_ICM20608_TEMP_OFFSET, INV_ICM20608_TEMP_SCALE}, + .startup_time = {INV_MPU6500_GYRO_STARTUP_TIME, INV_MPU6500_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_ICM20600_WHOAMI_VALUE, + .name = "ICM20600", + .reg = ®_set_icm20602, + .config = &chip_config_6500, + .fifo_size = 1008, + .temp = {INV_ICM20608_TEMP_OFFSET, INV_ICM20608_TEMP_SCALE}, + .startup_time = {INV_ICM20602_GYRO_STARTUP_TIME, INV_ICM20602_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_ICM20602_WHOAMI_VALUE, + .name = "ICM20602", + .reg = ®_set_icm20602, + .config = &chip_config_6500, + .fifo_size = 1008, + .temp = {INV_ICM20608_TEMP_OFFSET, INV_ICM20608_TEMP_SCALE}, + .startup_time = {INV_ICM20602_GYRO_STARTUP_TIME, INV_ICM20602_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_ICM20690_WHOAMI_VALUE, + .name = "ICM20690", + .reg = ®_set_6500, + .config = &chip_config_6500, + .fifo_size = 1024, + .temp = {INV_ICM20608_TEMP_OFFSET, INV_ICM20608_TEMP_SCALE}, + .startup_time = {INV_ICM20690_GYRO_STARTUP_TIME, INV_ICM20690_ACCEL_STARTUP_TIME}, + }, + { .whoami = INV_IAM20380_WHOAMI_VALUE, + .name = "IAM20380", + .reg = ®_set_6500, + .config = &chip_config_6500, + .fifo_size = 512, + .temp = {INV_ICM20608_TEMP_OFFSET, INV_ICM20608_TEMP_SCALE}, + .startup_time = {INV_MPU6500_GYRO_STARTUP_TIME, INV_MPU6500_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_IAM20680_WHOAMI_VALUE, + .name = "IAM20680", + .reg = ®_set_6500, + .config = &chip_config_6500, + .fifo_size = 512, + .temp = {INV_ICM20608_TEMP_OFFSET, INV_ICM20608_TEMP_SCALE}, + .startup_time = {INV_MPU6500_GYRO_STARTUP_TIME, INV_MPU6500_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_IAM20680HP_WHOAMI_VALUE, + .name = "IAM20680HP", + .reg = ®_set_6500, + .config = &chip_config_6500, + .fifo_size = 4 * 1024, + .temp = {INV_ICM20608_TEMP_OFFSET, INV_ICM20608_TEMP_SCALE}, + .startup_time = {INV_MPU6500_GYRO_STARTUP_TIME, INV_MPU6500_ACCEL_STARTUP_TIME}, + }, + { + .whoami = INV_IAM20680HT_WHOAMI_VALUE, + .name = "IAM20680HT", + .reg = ®_set_6500, + .config = &chip_config_6500, + .fifo_size = 4 * 1024, + .temp = {INV_ICM20608_TEMP_OFFSET, INV_ICM20608_TEMP_SCALE}, + .startup_time = {INV_MPU6500_GYRO_STARTUP_TIME, INV_MPU6500_ACCEL_STARTUP_TIME}, + }, +}; + +static int inv_mpu6050_pwr_mgmt_1_write(struct inv_mpu6050_state *st, bool sleep, + bool cycle, int clock, int temp_dis) +{ + u8 val; + + if (clock < 0) + clock = st->chip_config.clk; + if (temp_dis < 0) + temp_dis = !st->chip_config.temp_en; + + val = clock & INV_MPU6050_BIT_CLK_MASK; + if (temp_dis) + val |= INV_MPU6050_BIT_TEMP_DIS; + if (sleep) + val |= INV_MPU6050_BIT_SLEEP; + if (cycle) + val |= INV_MPU6050_BIT_CYCLE; + + dev_dbg(regmap_get_device(st->map), "pwr_mgmt_1: 0x%x\n", val); + return regmap_write(st->map, st->reg->pwr_mgmt_1, val); +} + +static int inv_mpu6050_clock_switch(struct inv_mpu6050_state *st, + unsigned int clock) +{ + int ret; + + switch (st->chip_type) { + case INV_MPU6050: + case INV_MPU6000: + case INV_MPU9150: + /* old chips: switch clock manually */ + ret = inv_mpu6050_pwr_mgmt_1_write(st, false, false, clock, -1); + if (ret) + return ret; + st->chip_config.clk = clock; + break; + default: + /* automatic clock switching, nothing to do */ + break; + } + + return 0; +} + +int inv_mpu6050_switch_engine(struct inv_mpu6050_state *st, bool en, + unsigned int mask) +{ + unsigned int sleep, val; + u8 pwr_mgmt2, user_ctrl; + int ret; + + /* delete useless requests */ + if (mask & INV_MPU6050_SENSOR_ACCL && en == st->chip_config.accl_en) + mask &= ~INV_MPU6050_SENSOR_ACCL; + if (mask & INV_MPU6050_SENSOR_GYRO && en == st->chip_config.gyro_en) + mask &= ~INV_MPU6050_SENSOR_GYRO; + if (mask & INV_MPU6050_SENSOR_TEMP && en == st->chip_config.temp_en) + mask &= ~INV_MPU6050_SENSOR_TEMP; + if (mask & INV_MPU6050_SENSOR_MAGN && en == st->chip_config.magn_en) + mask &= ~INV_MPU6050_SENSOR_MAGN; + if (mask & INV_MPU6050_SENSOR_WOM && en == st->chip_config.wom_en) + mask &= ~INV_MPU6050_SENSOR_WOM; + + /* force accel on if WoM is on and not going off */ + if (!en && (mask & INV_MPU6050_SENSOR_ACCL) && st->chip_config.wom_en && + !(mask & INV_MPU6050_SENSOR_WOM)) + mask &= ~INV_MPU6050_SENSOR_ACCL; + + if (mask == 0) + return 0; + + /* turn on/off temperature sensor */ + if (mask & INV_MPU6050_SENSOR_TEMP) { + ret = inv_mpu6050_pwr_mgmt_1_write(st, false, false, -1, !en); + if (ret) + return ret; + st->chip_config.temp_en = en; + } + + /* update user_crtl for driving magnetometer */ + if (mask & INV_MPU6050_SENSOR_MAGN) { + user_ctrl = st->chip_config.user_ctrl; + if (en) + user_ctrl |= INV_MPU6050_BIT_I2C_MST_EN; + else + user_ctrl &= ~INV_MPU6050_BIT_I2C_MST_EN; + ret = regmap_write(st->map, st->reg->user_ctrl, user_ctrl); + if (ret) + return ret; + st->chip_config.user_ctrl = user_ctrl; + st->chip_config.magn_en = en; + } + + /* manage accel & gyro engines */ + if (mask & (INV_MPU6050_SENSOR_ACCL | INV_MPU6050_SENSOR_GYRO)) { + /* compute power management 2 current value */ + pwr_mgmt2 = 0; + if (!st->chip_config.accl_en) + pwr_mgmt2 |= INV_MPU6050_BIT_PWR_ACCL_STBY; + if (!st->chip_config.gyro_en) + pwr_mgmt2 |= INV_MPU6050_BIT_PWR_GYRO_STBY; + + /* update to new requested value */ + if (mask & INV_MPU6050_SENSOR_ACCL) { + if (en) + pwr_mgmt2 &= ~INV_MPU6050_BIT_PWR_ACCL_STBY; + else + pwr_mgmt2 |= INV_MPU6050_BIT_PWR_ACCL_STBY; + } + if (mask & INV_MPU6050_SENSOR_GYRO) { + if (en) + pwr_mgmt2 &= ~INV_MPU6050_BIT_PWR_GYRO_STBY; + else + pwr_mgmt2 |= INV_MPU6050_BIT_PWR_GYRO_STBY; + } + + /* switch clock to internal when turning gyro off */ + if (mask & INV_MPU6050_SENSOR_GYRO && !en) { + ret = inv_mpu6050_clock_switch(st, INV_CLK_INTERNAL); + if (ret) + return ret; + } + + /* update sensors engine */ + dev_dbg(regmap_get_device(st->map), "pwr_mgmt_2: 0x%x\n", + pwr_mgmt2); + ret = regmap_write(st->map, st->reg->pwr_mgmt_2, pwr_mgmt2); + if (ret) + return ret; + if (mask & INV_MPU6050_SENSOR_ACCL) + st->chip_config.accl_en = en; + if (mask & INV_MPU6050_SENSOR_GYRO) + st->chip_config.gyro_en = en; + + /* compute required time to have sensors stabilized */ + sleep = 0; + if (en) { + if (mask & INV_MPU6050_SENSOR_ACCL) { + if (sleep < st->hw->startup_time.accel) + sleep = st->hw->startup_time.accel; + } + if (mask & INV_MPU6050_SENSOR_GYRO) { + if (sleep < st->hw->startup_time.gyro) + sleep = st->hw->startup_time.gyro; + } + } else { + if (mask & INV_MPU6050_SENSOR_GYRO) { + if (sleep < INV_MPU6050_GYRO_DOWN_TIME) + sleep = INV_MPU6050_GYRO_DOWN_TIME; + } + } + if (sleep) + msleep(sleep); + + /* switch clock to PLL when turning gyro on */ + if (mask & INV_MPU6050_SENSOR_GYRO && en) { + ret = inv_mpu6050_clock_switch(st, INV_CLK_PLL); + if (ret) + return ret; + } + } + + /* enable/disable accel intelligence control */ + if (mask & INV_MPU6050_SENSOR_WOM) { + val = en ? INV_MPU6500_BIT_ACCEL_INTEL_EN | + INV_MPU6500_BIT_ACCEL_INTEL_MODE : 0; + ret = regmap_write(st->map, INV_MPU6500_REG_ACCEL_INTEL_CTRL, val); + if (ret) + return ret; + st->chip_config.wom_en = en; + } + + return 0; +} + +static int inv_mpu6050_set_power_itg(struct inv_mpu6050_state *st, + bool power_on) +{ + int result; + + result = inv_mpu6050_pwr_mgmt_1_write(st, !power_on, false, -1, -1); + if (result) + return result; + + if (power_on) + usleep_range(INV_MPU6050_REG_UP_TIME_MIN, + INV_MPU6050_REG_UP_TIME_MAX); + + return 0; +} + +static int inv_mpu6050_set_gyro_fsr(struct inv_mpu6050_state *st, + enum inv_mpu6050_fsr_e val) +{ + unsigned int gyro_shift; + u8 data; + + switch (st->chip_type) { + case INV_ICM20690: + gyro_shift = INV_ICM20690_GYRO_CONFIG_FSR_SHIFT; + break; + default: + gyro_shift = INV_MPU6050_GYRO_CONFIG_FSR_SHIFT; + break; + } + + data = val << gyro_shift; + return regmap_write(st->map, st->reg->gyro_config, data); +} + +static int inv_mpu6050_set_accel_lpf_regs(struct inv_mpu6050_state *st, + enum inv_mpu6050_filter_e val) +{ + switch (st->chip_type) { + case INV_MPU6050: + case INV_MPU6000: + case INV_MPU9150: + /* old chips, nothing to do */ + return 0; + case INV_ICM20689: + case INV_ICM20690: + case INV_IAM20680HT: + case INV_IAM20680HP: + /* set FIFO size to maximum value */ + val |= INV_ICM20689_BITS_FIFO_SIZE_MAX; + break; + default: + break; + } + + return regmap_write(st->map, st->reg->accel_lpf, val); +} + +/* + * inv_mpu6050_set_lpf_regs() - set low pass filter registers, chip dependent + * + * MPU60xx/MPU9150 use only 1 register for accelerometer + gyroscope + * MPU6500 and above have a dedicated register for accelerometer + */ +static int inv_mpu6050_set_lpf_regs(struct inv_mpu6050_state *st, + enum inv_mpu6050_filter_e val) +{ + int result; + + result = regmap_write(st->map, st->reg->lpf, val); + if (result) + return result; + + /* set accel lpf */ + return inv_mpu6050_set_accel_lpf_regs(st, val); +} + +/* + * inv_mpu6050_init_config() - Initialize hardware, disable FIFO. + * + * Initial configuration: + * FSR: ± 2000DPS + * DLPF: 20Hz + * FIFO rate: 50Hz + * Clock source: Gyro PLL + */ +static int inv_mpu6050_init_config(struct iio_dev *indio_dev) +{ + int result; + u8 d; + struct inv_mpu6050_state *st = iio_priv(indio_dev); + struct inv_sensors_timestamp_chip timestamp; + + result = inv_mpu6050_set_gyro_fsr(st, st->chip_config.fsr); + if (result) + return result; + + result = inv_mpu6050_set_lpf_regs(st, st->chip_config.lpf); + if (result) + return result; + + d = st->chip_config.divider; + result = regmap_write(st->map, st->reg->sample_rate_div, d); + if (result) + return result; + + d = (st->chip_config.accl_fs << INV_MPU6050_ACCL_CONFIG_FSR_SHIFT); + result = regmap_write(st->map, st->reg->accl_config, d); + if (result) + return result; + + result = regmap_write(st->map, st->reg->int_pin_cfg, st->irq_mask); + if (result) + return result; + + /* clock jitter is +/- 2% */ + timestamp.clock_period = NSEC_PER_SEC / INV_MPU6050_INTERNAL_FREQ_HZ; + timestamp.jitter = 20; + timestamp.init_period = + NSEC_PER_SEC / INV_MPU6050_DIVIDER_TO_FIFO_RATE(st->chip_config.divider); + inv_sensors_timestamp_init(&st->timestamp, ×tamp); + + /* magn chip init, noop if not present in the chip */ + result = inv_mpu_magn_probe(st); + if (result) + return result; + + return 0; +} + +static int inv_mpu6050_sensor_set(struct inv_mpu6050_state *st, int reg, + int axis, int val) +{ + int ind; + __be16 d = cpu_to_be16(val); + + ind = (axis - IIO_MOD_X) * 2; + + return regmap_bulk_write(st->map, reg + ind, &d, sizeof(d)); +} + +static int inv_mpu6050_sensor_show(struct inv_mpu6050_state *st, int reg, + int axis, int *val) +{ + int ind, result; + __be16 d; + + ind = (axis - IIO_MOD_X) * 2; + result = regmap_bulk_read(st->map, reg + ind, &d, sizeof(d)); + if (result) + return result; + *val = (short)be16_to_cpup(&d); + + return IIO_VAL_INT; +} + +static int inv_mpu6050_read_channel_data(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + struct device *pdev = regmap_get_device(st->map); + unsigned int freq_hz, period_us, min_sleep_us, max_sleep_us; + int result; + int ret; + + /* compute sample period */ + freq_hz = INV_MPU6050_DIVIDER_TO_FIFO_RATE(st->chip_config.divider); + period_us = 1000000 / freq_hz; + + result = pm_runtime_resume_and_get(pdev); + if (result) + return result; + + switch (chan->type) { + case IIO_ANGL_VEL: + if (!st->chip_config.gyro_en) { + result = inv_mpu6050_switch_engine(st, true, + INV_MPU6050_SENSOR_GYRO); + if (result) + goto error_power_off; + /* need to wait 2 periods to have first valid sample */ + min_sleep_us = 2 * period_us; + max_sleep_us = 2 * (period_us + period_us / 2); + usleep_range(min_sleep_us, max_sleep_us); + } + ret = inv_mpu6050_sensor_show(st, st->reg->raw_gyro, + chan->channel2, val); + break; + case IIO_ACCEL: + if (!st->chip_config.accl_en) { + result = inv_mpu6050_switch_engine(st, true, + INV_MPU6050_SENSOR_ACCL); + if (result) + goto error_power_off; + /* wait 1 period for first sample availability */ + min_sleep_us = period_us; + max_sleep_us = period_us + period_us / 2; + usleep_range(min_sleep_us, max_sleep_us); + } + ret = inv_mpu6050_sensor_show(st, st->reg->raw_accl, + chan->channel2, val); + break; + case IIO_TEMP: + /* temperature sensor work only with accel and/or gyro */ + if (!st->chip_config.accl_en && !st->chip_config.gyro_en) { + result = -EBUSY; + goto error_power_off; + } + if (!st->chip_config.temp_en) { + result = inv_mpu6050_switch_engine(st, true, + INV_MPU6050_SENSOR_TEMP); + if (result) + goto error_power_off; + /* wait 1 period for first sample availability */ + min_sleep_us = period_us; + max_sleep_us = period_us + period_us / 2; + usleep_range(min_sleep_us, max_sleep_us); + } + ret = inv_mpu6050_sensor_show(st, st->reg->temperature, + IIO_MOD_X, val); + break; + case IIO_MAGN: + if (!st->chip_config.magn_en) { + result = inv_mpu6050_switch_engine(st, true, + INV_MPU6050_SENSOR_MAGN); + if (result) + goto error_power_off; + /* frequency is limited for magnetometer */ + if (freq_hz > INV_MPU_MAGN_FREQ_HZ_MAX) { + freq_hz = INV_MPU_MAGN_FREQ_HZ_MAX; + period_us = 1000000 / freq_hz; + } + /* need to wait 2 periods to have first valid sample */ + min_sleep_us = 2 * period_us; + max_sleep_us = 2 * (period_us + period_us / 2); + usleep_range(min_sleep_us, max_sleep_us); + } + ret = inv_mpu_magn_read(st, chan->channel2, val); + break; + default: + ret = -EINVAL; + break; + } + + pm_runtime_put_autosuspend(pdev); + + return ret; + +error_power_off: + pm_runtime_put_autosuspend(pdev); + return result; +} + +static int +inv_mpu6050_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + int ret = 0; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + mutex_lock(&st->lock); + ret = inv_mpu6050_read_channel_data(indio_dev, chan, val); + mutex_unlock(&st->lock); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + mutex_lock(&st->lock); + *val = 0; + *val2 = gyro_scale_6050[st->chip_config.fsr]; + mutex_unlock(&st->lock); + + return IIO_VAL_INT_PLUS_NANO; + case IIO_ACCEL: + mutex_lock(&st->lock); + *val = 0; + *val2 = accel_scale[st->chip_config.accl_fs]; + mutex_unlock(&st->lock); + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = st->hw->temp.scale / 1000000; + *val2 = st->hw->temp.scale % 1000000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_MAGN: + return inv_mpu_magn_get_scale(st, chan, val, val2); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + *val = st->hw->temp.offset; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_ANGL_VEL: + mutex_lock(&st->lock); + ret = inv_mpu6050_sensor_show(st, st->reg->gyro_offset, + chan->channel2, val); + mutex_unlock(&st->lock); + return ret; + case IIO_ACCEL: + mutex_lock(&st->lock); + ret = inv_mpu6050_sensor_show(st, st->reg->accl_offset, + chan->channel2, val); + mutex_unlock(&st->lock); + return ret; + + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int inv_mpu6050_write_gyro_scale(struct inv_mpu6050_state *st, int val, + int val2) +{ + int result, i; + + if (val != 0) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(gyro_scale_6050); ++i) { + if (gyro_scale_6050[i] == val2) { + result = inv_mpu6050_set_gyro_fsr(st, i); + if (result) + return result; + + st->chip_config.fsr = i; + return 0; + } + } + + return -EINVAL; +} + +static int inv_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT_PLUS_MICRO; + } + default: + return IIO_VAL_INT_PLUS_MICRO; + } + + return -EINVAL; +} + +static int inv_mpu6050_write_accel_scale(struct inv_mpu6050_state *st, int val, + int val2) +{ + int result, i; + u8 d; + + if (val != 0) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(accel_scale); ++i) { + if (accel_scale[i] == val2) { + d = (i << INV_MPU6050_ACCL_CONFIG_FSR_SHIFT); + result = regmap_write(st->map, st->reg->accl_config, d); + if (result) + return result; + + st->chip_config.accl_fs = i; + return 0; + } + } + + return -EINVAL; +} + +static int inv_mpu6050_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + struct device *pdev = regmap_get_device(st->map); + int result; + + /* + * we should only update scale when the chip is disabled, i.e. + * not running + */ + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + mutex_lock(&st->lock); + result = pm_runtime_resume_and_get(pdev); + if (result) + goto error_write_raw_unlock; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + result = inv_mpu6050_write_gyro_scale(st, val, val2); + break; + case IIO_ACCEL: + result = inv_mpu6050_write_accel_scale(st, val, val2); + break; + default: + result = -EINVAL; + break; + } + break; + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_ANGL_VEL: + result = inv_mpu6050_sensor_set(st, + st->reg->gyro_offset, + chan->channel2, val); + break; + case IIO_ACCEL: + result = inv_mpu6050_sensor_set(st, + st->reg->accl_offset, + chan->channel2, val); + break; + default: + result = -EINVAL; + break; + } + break; + default: + result = -EINVAL; + break; + } + + pm_runtime_put_autosuspend(pdev); +error_write_raw_unlock: + mutex_unlock(&st->lock); + iio_device_release_direct(indio_dev); + + return result; +} + +static u64 inv_mpu6050_convert_wom_to_roc(unsigned int threshold, unsigned int freq_div) +{ + /* 4mg per LSB converted in m/s² in micro (1000000) */ + const unsigned int convert = 4U * 9807U; + u64 value; + + value = threshold * convert; + + /* compute the differential by multiplying by the frequency */ + return div_u64(value * INV_MPU6050_INTERNAL_FREQ_HZ, freq_div); +} + +static unsigned int inv_mpu6050_convert_roc_to_wom(u64 roc, unsigned int freq_div) +{ + /* 4mg per LSB converted in m/s² in micro (1000000) */ + const unsigned int convert = 4U * 9807U; + u64 value; + + /* return 0 only if roc is 0 */ + if (roc == 0) + return 0; + + value = div_u64(roc * freq_div, convert * INV_MPU6050_INTERNAL_FREQ_HZ); + + /* limit value to 8 bits and prevent 0 */ + return min(255, max(1, value)); +} + +static int inv_mpu6050_set_wom_int(struct inv_mpu6050_state *st, bool on) +{ + unsigned int reg_val, val; + + switch (st->chip_type) { + case INV_MPU6050: + case INV_MPU6500: + case INV_MPU6515: + case INV_MPU6880: + case INV_MPU6000: + case INV_MPU9150: + case INV_MPU9250: + case INV_MPU9255: + reg_val = INV_MPU6500_BIT_WOM_INT_EN; + break; + default: + reg_val = INV_ICM20608_BIT_WOM_INT_EN; + break; + } + + val = on ? reg_val : 0; + + return regmap_update_bits(st->map, st->reg->int_enable, reg_val, val); +} + +static int inv_mpu6050_set_wom_threshold(struct inv_mpu6050_state *st, u64 value, + unsigned int freq_div) +{ + unsigned int threshold; + int result; + + /* convert roc to wom threshold and convert back to handle clipping */ + threshold = inv_mpu6050_convert_roc_to_wom(value, freq_div); + value = inv_mpu6050_convert_wom_to_roc(threshold, freq_div); + + dev_dbg(regmap_get_device(st->map), "wom_threshold: 0x%x\n", threshold); + + switch (st->chip_type) { + case INV_ICM20609: + case INV_ICM20689: + case INV_ICM20600: + case INV_ICM20602: + case INV_ICM20690: + st->data[0] = threshold; + st->data[1] = threshold; + st->data[2] = threshold; + result = regmap_bulk_write(st->map, INV_ICM20609_REG_ACCEL_WOM_X_THR, + st->data, 3); + break; + default: + result = regmap_write(st->map, INV_MPU6500_REG_WOM_THRESHOLD, threshold); + break; + } + if (result) + return result; + + st->chip_config.roc_threshold = value; + + return 0; +} + +static int inv_mpu6050_set_lp_odr(struct inv_mpu6050_state *st, unsigned int freq_div, + unsigned int *lp_div) +{ + static const unsigned int freq_dividers[] = {2, 4, 8, 16, 32, 64, 128, 256}; + static const unsigned int reg_values[] = { + INV_MPU6050_LPOSC_500HZ, INV_MPU6050_LPOSC_250HZ, + INV_MPU6050_LPOSC_125HZ, INV_MPU6050_LPOSC_62HZ, + INV_MPU6050_LPOSC_31HZ, INV_MPU6050_LPOSC_16HZ, + INV_MPU6050_LPOSC_8HZ, INV_MPU6050_LPOSC_4HZ, + }; + unsigned int val, i; + + switch (st->chip_type) { + case INV_ICM20609: + case INV_ICM20689: + case INV_ICM20600: + case INV_ICM20602: + case INV_ICM20690: + /* nothing to do */ + *lp_div = INV_MPU6050_FREQ_DIVIDER(st); + return 0; + default: + break; + } + + /* found the nearest superior frequency divider */ + i = ARRAY_SIZE(reg_values) - 1; + val = reg_values[i]; + *lp_div = freq_dividers[i]; + for (i = 0; i < ARRAY_SIZE(freq_dividers); ++i) { + if (freq_div <= freq_dividers[i]) { + val = reg_values[i]; + *lp_div = freq_dividers[i]; + break; + } + } + + dev_dbg(regmap_get_device(st->map), "lp_odr: 0x%x\n", val); + return regmap_write(st->map, INV_MPU6500_REG_LP_ODR, val); +} + +static int inv_mpu6050_set_wom_lp(struct inv_mpu6050_state *st, bool on) +{ + unsigned int lp_div; + int result; + + if (on) { + /* set low power ODR */ + result = inv_mpu6050_set_lp_odr(st, INV_MPU6050_FREQ_DIVIDER(st), &lp_div); + if (result) + return result; + /* disable accel low pass filter */ + result = inv_mpu6050_set_accel_lpf_regs(st, INV_MPU6050_FILTER_NOLPF); + if (result) + return result; + /* update wom threshold with new low-power frequency divider */ + result = inv_mpu6050_set_wom_threshold(st, st->chip_config.roc_threshold, lp_div); + if (result) + return result; + /* set cycle mode */ + result = inv_mpu6050_pwr_mgmt_1_write(st, false, true, -1, -1); + } else { + /* disable cycle mode */ + result = inv_mpu6050_pwr_mgmt_1_write(st, false, false, -1, -1); + if (result) + return result; + /* restore wom threshold */ + result = inv_mpu6050_set_wom_threshold(st, st->chip_config.roc_threshold, + INV_MPU6050_FREQ_DIVIDER(st)); + if (result) + return result; + /* restore accel low pass filter */ + result = inv_mpu6050_set_accel_lpf_regs(st, st->chip_config.lpf); + } + + return result; +} + +static int inv_mpu6050_enable_wom(struct inv_mpu6050_state *st, bool en) +{ + struct device *pdev = regmap_get_device(st->map); + unsigned int mask; + int result; + + if (en) { + result = pm_runtime_resume_and_get(pdev); + if (result) + return result; + + mask = INV_MPU6050_SENSOR_ACCL | INV_MPU6050_SENSOR_WOM; + result = inv_mpu6050_switch_engine(st, true, mask); + if (result) + goto error_suspend; + + result = inv_mpu6050_set_wom_int(st, true); + if (result) + goto error_suspend; + } else { + result = inv_mpu6050_set_wom_int(st, false); + if (result) + dev_err(pdev, "error %d disabling WoM interrupt bit", result); + + /* disable only WoM and let accel be disabled by autosuspend */ + result = inv_mpu6050_switch_engine(st, false, INV_MPU6050_SENSOR_WOM); + if (result) { + dev_err(pdev, "error %d disabling WoM force off", result); + /* force WoM off */ + st->chip_config.wom_en = false; + } + + pm_runtime_put_autosuspend(pdev); + } + + return result; + +error_suspend: + pm_runtime_put_autosuspend(pdev); + return result; +} + +static int inv_mpu6050_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + /* support only WoM (accel roc rising) event */ + if (chan->type != IIO_ACCEL || type != IIO_EV_TYPE_ROC || + dir != IIO_EV_DIR_RISING) + return -EINVAL; + + guard(mutex)(&st->lock); + + return st->chip_config.wom_en ? 1 : 0; +} + +static int inv_mpu6050_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + /* support only WoM (accel roc rising) event */ + if (chan->type != IIO_ACCEL || type != IIO_EV_TYPE_ROC || + dir != IIO_EV_DIR_RISING) + return -EINVAL; + + guard(mutex)(&st->lock); + + if (st->chip_config.wom_en == state) + return 0; + + return inv_mpu6050_enable_wom(st, state); +} + +static int inv_mpu6050_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + u32 rem; + + /* support only WoM (accel roc rising) event value */ + if (chan->type != IIO_ACCEL || type != IIO_EV_TYPE_ROC || + dir != IIO_EV_DIR_RISING || info != IIO_EV_INFO_VALUE) + return -EINVAL; + + guard(mutex)(&st->lock); + + /* return value in micro */ + *val = div_u64_rem(st->chip_config.roc_threshold, 1000000U, &rem); + *val2 = rem; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int inv_mpu6050_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + struct device *pdev = regmap_get_device(st->map); + u64 value; + int result; + + /* support only WoM (accel roc rising) event value */ + if (chan->type != IIO_ACCEL || type != IIO_EV_TYPE_ROC || + dir != IIO_EV_DIR_RISING || info != IIO_EV_INFO_VALUE) + return -EINVAL; + + if (val < 0 || val2 < 0) + return -EINVAL; + + guard(mutex)(&st->lock); + + result = pm_runtime_resume_and_get(pdev); + if (result) + return result; + + value = (u64)val * 1000000ULL + (u64)val2; + result = inv_mpu6050_set_wom_threshold(st, value, INV_MPU6050_FREQ_DIVIDER(st)); + + pm_runtime_put_autosuspend(pdev); + + return result; +} + +/* + * inv_mpu6050_set_lpf() - set low pass filer based on fifo rate. + * + * Based on the Nyquist principle, the bandwidth of the low + * pass filter must not exceed the signal sampling rate divided + * by 2, or there would be aliasing. + * This function basically search for the correct low pass + * parameters based on the fifo rate, e.g, sampling frequency. + * + * lpf is set automatically when setting sampling rate to avoid any aliases. + */ +static int inv_mpu6050_set_lpf(struct inv_mpu6050_state *st, int rate) +{ + static const int hz[] = {400, 200, 90, 40, 20, 10}; + static const int d[] = { + INV_MPU6050_FILTER_200HZ, INV_MPU6050_FILTER_100HZ, + INV_MPU6050_FILTER_45HZ, INV_MPU6050_FILTER_20HZ, + INV_MPU6050_FILTER_10HZ, INV_MPU6050_FILTER_5HZ + }; + int i, result; + u8 data; + + data = INV_MPU6050_FILTER_5HZ; + for (i = 0; i < ARRAY_SIZE(hz); ++i) { + if (rate >= hz[i]) { + data = d[i]; + break; + } + } + result = inv_mpu6050_set_lpf_regs(st, data); + if (result) + return result; + st->chip_config.lpf = data; + + return 0; +} + +/* + * inv_mpu6050_fifo_rate_store() - Set fifo rate. + */ +static ssize_t +inv_mpu6050_fifo_rate_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int fifo_rate; + u32 fifo_period; + bool fifo_on; + u8 d; + int result; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct inv_mpu6050_state *st = iio_priv(indio_dev); + struct device *pdev = regmap_get_device(st->map); + + if (kstrtoint(buf, 10, &fifo_rate)) + return -EINVAL; + if (fifo_rate < INV_MPU6050_MIN_FIFO_RATE || + fifo_rate > INV_MPU6050_MAX_FIFO_RATE) + return -EINVAL; + + /* compute the chip sample rate divider */ + d = INV_MPU6050_FIFO_RATE_TO_DIVIDER(fifo_rate); + /* compute back the fifo rate to handle truncation cases */ + fifo_rate = INV_MPU6050_DIVIDER_TO_FIFO_RATE(d); + fifo_period = NSEC_PER_SEC / fifo_rate; + + mutex_lock(&st->lock); + if (d == st->chip_config.divider) { + result = 0; + goto fifo_rate_fail_unlock; + } + + fifo_on = st->chip_config.accl_fifo_enable || + st->chip_config.gyro_fifo_enable || + st->chip_config.magn_fifo_enable; + result = inv_sensors_timestamp_update_odr(&st->timestamp, fifo_period, fifo_on); + if (result) + goto fifo_rate_fail_unlock; + + result = pm_runtime_resume_and_get(pdev); + if (result) + goto fifo_rate_fail_unlock; + + result = regmap_write(st->map, st->reg->sample_rate_div, d); + if (result) + goto fifo_rate_fail_power_off; + st->chip_config.divider = d; + + result = inv_mpu6050_set_lpf(st, fifo_rate); + if (result) + goto fifo_rate_fail_power_off; + + /* update rate for magn, noop if not present in chip */ + result = inv_mpu_magn_set_rate(st, fifo_rate); + if (result) + goto fifo_rate_fail_power_off; + + /* update wom threshold since roc is dependent on sampling frequency */ + result = inv_mpu6050_set_wom_threshold(st, st->chip_config.roc_threshold, + INV_MPU6050_FREQ_DIVIDER(st)); + if (result) + goto fifo_rate_fail_power_off; + +fifo_rate_fail_power_off: + pm_runtime_put_autosuspend(pdev); +fifo_rate_fail_unlock: + mutex_unlock(&st->lock); + if (result) + return result; + + return count; +} + +/* + * inv_fifo_rate_show() - Get the current sampling rate. + */ +static ssize_t +inv_fifo_rate_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct inv_mpu6050_state *st = iio_priv(dev_to_iio_dev(dev)); + unsigned fifo_rate; + + mutex_lock(&st->lock); + fifo_rate = INV_MPU6050_DIVIDER_TO_FIFO_RATE(st->chip_config.divider); + mutex_unlock(&st->lock); + + return sysfs_emit(buf, "%u\n", fifo_rate); +} + +/* + * inv_attr_show() - calling this function will show current + * parameters. + * + * Deprecated in favor of IIO mounting matrix API. + * + * See inv_get_mount_matrix() + */ +static ssize_t inv_attr_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct inv_mpu6050_state *st = iio_priv(dev_to_iio_dev(dev)); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + s8 *m; + + switch (this_attr->address) { + /* + * In MPU6050, the two matrix are the same because gyro and accel + * are integrated in one chip + */ + case ATTR_GYRO_MATRIX: + case ATTR_ACCL_MATRIX: + m = st->plat_data.orientation; + + return sysfs_emit(buf, "%d, %d, %d; %d, %d, %d; %d, %d, %d\n", + m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]); + default: + return -EINVAL; + } +} + +/** + * inv_mpu6050_validate_trigger() - validate_trigger callback for invensense + * MPU6050 device. + * @indio_dev: The IIO device + * @trig: The new trigger + * + * Returns: 0 if the 'trig' matches the trigger registered by the MPU6050 + * device, -EINVAL otherwise. + */ +static int inv_mpu6050_validate_trigger(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + if (st->trig != trig) + return -EINVAL; + + return 0; +} + +static const struct iio_mount_matrix * +inv_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct inv_mpu6050_state *data = iio_priv(indio_dev); + const struct iio_mount_matrix *matrix; + + if (chan->type == IIO_MAGN) + matrix = &data->magn_orient; + else + matrix = &data->orientation; + + return matrix; +} + +static const struct iio_chan_spec_ext_info inv_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_TYPE, inv_get_mount_matrix), + { } +}; + +static const struct iio_event_spec inv_wom_events[] = { + { + .type = IIO_EV_TYPE_ROC, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE), + }, +}; + +#define INV_MPU6050_CHAN(_type, _channel2, _index) \ + { \ + .type = _type, \ + .modified = 1, \ + .channel2 = _channel2, \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ + .ext_info = inv_ext_info, \ + } + +#define INV_MPU6050_TEMP_CHAN(_index) \ + { \ + .type = IIO_TEMP, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \ + | BIT(IIO_CHAN_INFO_OFFSET) \ + | BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ + } + +#define INV_MPU6050_EVENT_CHAN(_type, _channel2, _events, _events_nb) \ +{ \ + .type = _type, \ + .modified = 1, \ + .channel2 = _channel2, \ + .event_spec = _events, \ + .num_event_specs = _events_nb, \ + .scan_index = -1, \ +} + +static const struct iio_chan_spec inv_mpu6050_channels[] = { + IIO_CHAN_SOFT_TIMESTAMP(INV_MPU6050_SCAN_TIMESTAMP), + + INV_MPU6050_TEMP_CHAN(INV_MPU6050_SCAN_TEMP), + + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_X, INV_MPU6050_SCAN_GYRO_X), + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, INV_MPU6050_SCAN_GYRO_Y), + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, INV_MPU6050_SCAN_GYRO_Z), + + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_X, INV_MPU6050_SCAN_ACCL_X), + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_MPU6050_SCAN_ACCL_Y), + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_MPU6050_SCAN_ACCL_Z), +}; + +static const struct iio_chan_spec inv_iam20380_channels[] = { + IIO_CHAN_SOFT_TIMESTAMP(INV_MPU6050_SCAN_TIMESTAMP), + + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_X, INV_MPU6050_SCAN_GYRO_X), + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, INV_MPU6050_SCAN_GYRO_Y), + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, INV_MPU6050_SCAN_GYRO_Z), +}; + +static const struct iio_chan_spec inv_mpu6500_channels[] = { + IIO_CHAN_SOFT_TIMESTAMP(INV_MPU6050_SCAN_TIMESTAMP), + + INV_MPU6050_TEMP_CHAN(INV_MPU6050_SCAN_TEMP), + + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_X, INV_MPU6050_SCAN_GYRO_X), + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, INV_MPU6050_SCAN_GYRO_Y), + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, INV_MPU6050_SCAN_GYRO_Z), + + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_X, INV_MPU6050_SCAN_ACCL_X), + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_MPU6050_SCAN_ACCL_Y), + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_MPU6050_SCAN_ACCL_Z), + + INV_MPU6050_EVENT_CHAN(IIO_ACCEL, IIO_MOD_X_OR_Y_OR_Z, + inv_wom_events, ARRAY_SIZE(inv_wom_events)), +}; + +#define INV_MPU6050_SCAN_MASK_3AXIS_ACCEL \ + (BIT(INV_MPU6050_SCAN_ACCL_X) \ + | BIT(INV_MPU6050_SCAN_ACCL_Y) \ + | BIT(INV_MPU6050_SCAN_ACCL_Z)) + +#define INV_MPU6050_SCAN_MASK_3AXIS_GYRO \ + (BIT(INV_MPU6050_SCAN_GYRO_X) \ + | BIT(INV_MPU6050_SCAN_GYRO_Y) \ + | BIT(INV_MPU6050_SCAN_GYRO_Z)) + +#define INV_MPU6050_SCAN_MASK_TEMP (BIT(INV_MPU6050_SCAN_TEMP)) + +static const unsigned long inv_mpu_scan_masks[] = { + /* 3-axis accel */ + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL, + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL | INV_MPU6050_SCAN_MASK_TEMP, + /* 3-axis gyro */ + INV_MPU6050_SCAN_MASK_3AXIS_GYRO, + INV_MPU6050_SCAN_MASK_3AXIS_GYRO | INV_MPU6050_SCAN_MASK_TEMP, + /* 6-axis accel + gyro */ + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL | INV_MPU6050_SCAN_MASK_3AXIS_GYRO, + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL | INV_MPU6050_SCAN_MASK_3AXIS_GYRO + | INV_MPU6050_SCAN_MASK_TEMP, + 0, +}; + +#define INV_MPU9X50_MAGN_CHAN(_chan2, _bits, _index) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = _chan2, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_RAW), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = _bits, \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ + .ext_info = inv_ext_info, \ + } + +static const struct iio_chan_spec inv_mpu9150_channels[] = { + IIO_CHAN_SOFT_TIMESTAMP(INV_MPU9X50_SCAN_TIMESTAMP), + + INV_MPU6050_TEMP_CHAN(INV_MPU6050_SCAN_TEMP), + + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_X, INV_MPU6050_SCAN_GYRO_X), + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, INV_MPU6050_SCAN_GYRO_Y), + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, INV_MPU6050_SCAN_GYRO_Z), + + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_X, INV_MPU6050_SCAN_ACCL_X), + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_MPU6050_SCAN_ACCL_Y), + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_MPU6050_SCAN_ACCL_Z), + + /* Magnetometer resolution is 13 bits */ + INV_MPU9X50_MAGN_CHAN(IIO_MOD_X, 13, INV_MPU9X50_SCAN_MAGN_X), + INV_MPU9X50_MAGN_CHAN(IIO_MOD_Y, 13, INV_MPU9X50_SCAN_MAGN_Y), + INV_MPU9X50_MAGN_CHAN(IIO_MOD_Z, 13, INV_MPU9X50_SCAN_MAGN_Z), +}; + +static const struct iio_chan_spec inv_mpu9250_channels[] = { + IIO_CHAN_SOFT_TIMESTAMP(INV_MPU9X50_SCAN_TIMESTAMP), + + INV_MPU6050_TEMP_CHAN(INV_MPU6050_SCAN_TEMP), + + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_X, INV_MPU6050_SCAN_GYRO_X), + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, INV_MPU6050_SCAN_GYRO_Y), + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, INV_MPU6050_SCAN_GYRO_Z), + + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_X, INV_MPU6050_SCAN_ACCL_X), + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_MPU6050_SCAN_ACCL_Y), + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_MPU6050_SCAN_ACCL_Z), + + /* Magnetometer resolution is 16 bits */ + INV_MPU9X50_MAGN_CHAN(IIO_MOD_X, 16, INV_MPU9X50_SCAN_MAGN_X), + INV_MPU9X50_MAGN_CHAN(IIO_MOD_Y, 16, INV_MPU9X50_SCAN_MAGN_Y), + INV_MPU9X50_MAGN_CHAN(IIO_MOD_Z, 16, INV_MPU9X50_SCAN_MAGN_Z), +}; + +#define INV_MPU9X50_SCAN_MASK_3AXIS_MAGN \ + (BIT(INV_MPU9X50_SCAN_MAGN_X) \ + | BIT(INV_MPU9X50_SCAN_MAGN_Y) \ + | BIT(INV_MPU9X50_SCAN_MAGN_Z)) + +static const unsigned long inv_iam20380_scan_masks[] = { + INV_MPU6050_SCAN_MASK_3AXIS_GYRO, +}; + +static const unsigned long inv_mpu9x50_scan_masks[] = { + /* 3-axis accel */ + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL, + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL | INV_MPU6050_SCAN_MASK_TEMP, + /* 3-axis gyro */ + INV_MPU6050_SCAN_MASK_3AXIS_GYRO, + INV_MPU6050_SCAN_MASK_3AXIS_GYRO | INV_MPU6050_SCAN_MASK_TEMP, + /* 3-axis magn */ + INV_MPU9X50_SCAN_MASK_3AXIS_MAGN, + INV_MPU9X50_SCAN_MASK_3AXIS_MAGN | INV_MPU6050_SCAN_MASK_TEMP, + /* 6-axis accel + gyro */ + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL | INV_MPU6050_SCAN_MASK_3AXIS_GYRO, + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL | INV_MPU6050_SCAN_MASK_3AXIS_GYRO + | INV_MPU6050_SCAN_MASK_TEMP, + /* 6-axis accel + magn */ + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL | INV_MPU9X50_SCAN_MASK_3AXIS_MAGN, + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL | INV_MPU9X50_SCAN_MASK_3AXIS_MAGN + | INV_MPU6050_SCAN_MASK_TEMP, + /* 6-axis gyro + magn */ + INV_MPU6050_SCAN_MASK_3AXIS_GYRO | INV_MPU9X50_SCAN_MASK_3AXIS_MAGN, + INV_MPU6050_SCAN_MASK_3AXIS_GYRO | INV_MPU9X50_SCAN_MASK_3AXIS_MAGN + | INV_MPU6050_SCAN_MASK_TEMP, + /* 9-axis accel + gyro + magn */ + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL | INV_MPU6050_SCAN_MASK_3AXIS_GYRO + | INV_MPU9X50_SCAN_MASK_3AXIS_MAGN, + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL | INV_MPU6050_SCAN_MASK_3AXIS_GYRO + | INV_MPU9X50_SCAN_MASK_3AXIS_MAGN + | INV_MPU6050_SCAN_MASK_TEMP, + 0, +}; + +static const unsigned long inv_icm20602_scan_masks[] = { + /* 3-axis accel + temp (mandatory) */ + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL | INV_MPU6050_SCAN_MASK_TEMP, + /* 3-axis gyro + temp (mandatory) */ + INV_MPU6050_SCAN_MASK_3AXIS_GYRO | INV_MPU6050_SCAN_MASK_TEMP, + /* 6-axis accel + gyro + temp (mandatory) */ + INV_MPU6050_SCAN_MASK_3AXIS_ACCEL | INV_MPU6050_SCAN_MASK_3AXIS_GYRO + | INV_MPU6050_SCAN_MASK_TEMP, + 0, +}; + +/* + * The user can choose any frequency between INV_MPU6050_MIN_FIFO_RATE and + * INV_MPU6050_MAX_FIFO_RATE, but only these frequencies are matched by the + * low-pass filter. Specifically, each of these sampling rates are about twice + * the bandwidth of a corresponding low-pass filter, which should eliminate + * aliasing following the Nyquist principle. By picking a frequency different + * from these, the user risks aliasing effects. + */ +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("10 20 50 100 200 500"); +static IIO_CONST_ATTR(in_anglvel_scale_available, + "0.000133090 0.000266181 0.000532362 0.001064724"); +static IIO_CONST_ATTR(in_accel_scale_available, + "0.000598 0.001196 0.002392 0.004785"); +static IIO_DEV_ATTR_SAMP_FREQ(S_IRUGO | S_IWUSR, inv_fifo_rate_show, + inv_mpu6050_fifo_rate_store); + +/* Deprecated: kept for userspace backward compatibility. */ +static IIO_DEVICE_ATTR(in_gyro_matrix, S_IRUGO, inv_attr_show, NULL, + ATTR_GYRO_MATRIX); +static IIO_DEVICE_ATTR(in_accel_matrix, S_IRUGO, inv_attr_show, NULL, + ATTR_ACCL_MATRIX); + +static struct attribute *inv_attributes[] = { + &iio_dev_attr_in_gyro_matrix.dev_attr.attr, /* deprecated */ + &iio_dev_attr_in_accel_matrix.dev_attr.attr, /* deprecated */ + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_const_attr_in_accel_scale_available.dev_attr.attr, + &iio_const_attr_in_anglvel_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group inv_attribute_group = { + .attrs = inv_attributes +}; + +static int inv_mpu6050_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->lock); + if (readval) + ret = regmap_read(st->map, reg, readval); + else + ret = regmap_write(st->map, reg, writeval); + mutex_unlock(&st->lock); + + return ret; +} + +static const struct iio_info mpu_info = { + .read_raw = &inv_mpu6050_read_raw, + .write_raw = &inv_mpu6050_write_raw, + .write_raw_get_fmt = &inv_write_raw_get_fmt, + .attrs = &inv_attribute_group, + .read_event_config = inv_mpu6050_read_event_config, + .write_event_config = inv_mpu6050_write_event_config, + .read_event_value = inv_mpu6050_read_event_value, + .write_event_value = inv_mpu6050_write_event_value, + .validate_trigger = inv_mpu6050_validate_trigger, + .debugfs_reg_access = &inv_mpu6050_reg_access, +}; + +/* + * inv_check_and_setup_chip() - check and setup chip. + */ +static int inv_check_and_setup_chip(struct inv_mpu6050_state *st) +{ + int result; + unsigned int regval, mask; + int i; + + st->hw = &hw_info[st->chip_type]; + st->reg = hw_info[st->chip_type].reg; + memcpy(&st->chip_config, hw_info[st->chip_type].config, + sizeof(st->chip_config)); + st->data = devm_kzalloc(regmap_get_device(st->map), st->hw->fifo_size, GFP_KERNEL); + if (st->data == NULL) + return -ENOMEM; + + /* check chip self-identification */ + result = regmap_read(st->map, INV_MPU6050_REG_WHOAMI, ®val); + if (result) + return result; + if (regval != st->hw->whoami) { + /* check whoami against all possible values */ + for (i = 0; i < INV_NUM_PARTS; ++i) { + if (regval == hw_info[i].whoami) { + dev_warn(regmap_get_device(st->map), + "whoami mismatch got 0x%02x (%s) expected 0x%02x (%s)\n", + regval, hw_info[i].name, + st->hw->whoami, st->hw->name); + break; + } + } + if (i >= INV_NUM_PARTS) { + dev_err(regmap_get_device(st->map), + "invalid whoami 0x%02x expected 0x%02x (%s)\n", + regval, st->hw->whoami, st->hw->name); + return -ENODEV; + } + } + + /* reset to make sure previous state are not there */ + result = regmap_write(st->map, st->reg->pwr_mgmt_1, + INV_MPU6050_BIT_H_RESET); + if (result) + return result; + msleep(INV_MPU6050_POWER_UP_TIME); + switch (st->chip_type) { + case INV_MPU6000: + case INV_MPU6500: + case INV_MPU6515: + case INV_MPU6880: + case INV_MPU9250: + case INV_MPU9255: + /* reset signal path (required for spi connection) */ + regval = INV_MPU6050_BIT_TEMP_RST | INV_MPU6050_BIT_ACCEL_RST | + INV_MPU6050_BIT_GYRO_RST; + result = regmap_write(st->map, INV_MPU6050_REG_SIGNAL_PATH_RESET, + regval); + if (result) + return result; + msleep(INV_MPU6050_POWER_UP_TIME); + break; + default: + break; + } + + /* + * Turn power on. After reset, the sleep bit could be on + * or off depending on the OTP settings. Turning power on + * make it in a definite state as well as making the hardware + * state align with the software state + */ + result = inv_mpu6050_set_power_itg(st, true); + if (result) + return result; + mask = INV_MPU6050_SENSOR_ACCL | INV_MPU6050_SENSOR_GYRO | + INV_MPU6050_SENSOR_TEMP | INV_MPU6050_SENSOR_MAGN; + result = inv_mpu6050_switch_engine(st, false, mask); + if (result) + goto error_power_off; + + return 0; + +error_power_off: + inv_mpu6050_set_power_itg(st, false); + return result; +} + +static int inv_mpu_core_enable_regulator_vddio(struct inv_mpu6050_state *st) +{ + int result; + + result = regulator_enable(st->vddio_supply); + if (result) { + dev_err(regmap_get_device(st->map), + "Failed to enable vddio regulator: %d\n", result); + } else { + /* Give the device a little bit of time to start up. */ + usleep_range(3000, 5000); + } + + return result; +} + +static int inv_mpu_core_disable_regulator_vddio(struct inv_mpu6050_state *st) +{ + int result; + + result = regulator_disable(st->vddio_supply); + if (result) + dev_err(regmap_get_device(st->map), + "Failed to disable vddio regulator: %d\n", result); + + return result; +} + +static void inv_mpu_core_disable_regulator_action(void *_data) +{ + struct inv_mpu6050_state *st = _data; + int result; + + result = regulator_disable(st->vdd_supply); + if (result) + dev_err(regmap_get_device(st->map), + "Failed to disable vdd regulator: %d\n", result); + + inv_mpu_core_disable_regulator_vddio(st); +} + +static void inv_mpu_pm_disable(void *data) +{ + struct device *dev = data; + + pm_runtime_disable(dev); +} + +int inv_mpu_core_probe(struct regmap *regmap, int irq, const char *name, + int (*inv_mpu_bus_setup)(struct iio_dev *), int chip_type) +{ + struct inv_mpu6050_state *st; + struct iio_dev *indio_dev; + struct inv_mpu6050_platform_data *pdata; + struct device *dev = regmap_get_device(regmap); + int result; + int irq_type; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + BUILD_BUG_ON(ARRAY_SIZE(hw_info) != INV_NUM_PARTS); + if (chip_type < 0 || chip_type >= INV_NUM_PARTS) { + dev_err(dev, "Bad invensense chip_type=%d name=%s\n", + chip_type, name); + return -ENODEV; + } + st = iio_priv(indio_dev); + mutex_init(&st->lock); + st->chip_type = chip_type; + st->irq = irq; + st->map = regmap; + + st->level_shifter = device_property_read_bool(dev, + "invensense,level-shifter"); + pdata = dev_get_platdata(dev); + if (!pdata) { + result = iio_read_mount_matrix(dev, &st->orientation); + if (result) { + dev_err(dev, "Failed to retrieve mounting matrix %d\n", + result); + return result; + } + } else { + st->plat_data = *pdata; + } + + if (irq > 0) { + irq_type = irq_get_trigger_type(irq); + if (!irq_type) + irq_type = IRQF_TRIGGER_RISING; + } else { + /* Doesn't really matter, use the default */ + irq_type = IRQF_TRIGGER_RISING; + } + + if (irq_type & IRQF_TRIGGER_RISING) // rising or both-edge + st->irq_mask = INV_MPU6050_ACTIVE_HIGH; + else if (irq_type == IRQF_TRIGGER_FALLING) + st->irq_mask = INV_MPU6050_ACTIVE_LOW; + else if (irq_type == IRQF_TRIGGER_HIGH) + st->irq_mask = INV_MPU6050_ACTIVE_HIGH | + INV_MPU6050_LATCH_INT_EN; + else if (irq_type == IRQF_TRIGGER_LOW) + st->irq_mask = INV_MPU6050_ACTIVE_LOW | + INV_MPU6050_LATCH_INT_EN; + else { + dev_err(dev, "Invalid interrupt type 0x%x specified\n", + irq_type); + return -EINVAL; + } + + /* + * Acking interrupts by status register does not work reliably + * but seem to work when this bit is set. + */ + if (st->chip_type == INV_MPU9150) + st->irq_mask |= INV_MPU6050_INT_RD_CLEAR; + + device_set_wakeup_capable(dev, true); + + st->vdd_supply = devm_regulator_get(dev, "vdd"); + if (IS_ERR(st->vdd_supply)) + return dev_err_probe(dev, PTR_ERR(st->vdd_supply), + "Failed to get vdd regulator\n"); + + st->vddio_supply = devm_regulator_get(dev, "vddio"); + if (IS_ERR(st->vddio_supply)) + return dev_err_probe(dev, PTR_ERR(st->vddio_supply), + "Failed to get vddio regulator\n"); + + result = regulator_enable(st->vdd_supply); + if (result) { + dev_err(dev, "Failed to enable vdd regulator: %d\n", result); + return result; + } + msleep(INV_MPU6050_POWER_UP_TIME); + + result = inv_mpu_core_enable_regulator_vddio(st); + if (result) { + regulator_disable(st->vdd_supply); + return result; + } + + result = devm_add_action_or_reset(dev, inv_mpu_core_disable_regulator_action, + st); + if (result) { + dev_err(dev, "Failed to setup regulator cleanup action %d\n", + result); + return result; + } + + /* fill magnetometer orientation */ + result = inv_mpu_magn_set_orient(st); + if (result) + return result; + + /* power is turned on inside check chip type*/ + result = inv_check_and_setup_chip(st); + if (result) + return result; + + result = inv_mpu6050_init_config(indio_dev); + if (result) { + dev_err(dev, "Could not initialize device.\n"); + goto error_power_off; + } + + dev_set_drvdata(dev, indio_dev); + /* name will be NULL when enumerated via ACPI */ + if (name) + indio_dev->name = name; + else + indio_dev->name = dev_name(dev); + + /* requires parent device set in indio_dev */ + if (inv_mpu_bus_setup) { + result = inv_mpu_bus_setup(indio_dev); + if (result) + goto error_power_off; + } + + /* chip init is done, turning on runtime power management */ + result = pm_runtime_set_active(dev); + if (result) + goto error_power_off; + pm_runtime_get_noresume(dev); + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, INV_MPU6050_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_put(dev); + result = devm_add_action_or_reset(dev, inv_mpu_pm_disable, dev); + if (result) + return result; + + switch (chip_type) { + case INV_MPU6000: + case INV_MPU6050: + indio_dev->channels = inv_mpu6050_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_mpu6050_channels); + indio_dev->available_scan_masks = inv_mpu_scan_masks; + break; + case INV_MPU9150: + indio_dev->channels = inv_mpu9150_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_mpu9150_channels); + indio_dev->available_scan_masks = inv_mpu9x50_scan_masks; + break; + case INV_MPU9250: + case INV_MPU9255: + indio_dev->channels = inv_mpu9250_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_mpu9250_channels); + indio_dev->available_scan_masks = inv_mpu9x50_scan_masks; + break; + case INV_IAM20380: + indio_dev->channels = inv_iam20380_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_iam20380_channels); + indio_dev->available_scan_masks = inv_iam20380_scan_masks; + break; + case INV_ICM20600: + case INV_ICM20602: + indio_dev->channels = inv_mpu6500_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_mpu6500_channels); + indio_dev->available_scan_masks = inv_icm20602_scan_masks; + break; + default: + indio_dev->channels = inv_mpu6500_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_mpu6500_channels); + indio_dev->available_scan_masks = inv_mpu_scan_masks; + break; + } + /* + * Use magnetometer inside the chip only if there is no i2c + * auxiliary device in use. Otherwise Going back to 6-axis only. + */ + if (st->magn_disabled) { + switch (chip_type) { + case INV_MPU9150: + indio_dev->channels = inv_mpu6050_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_mpu6050_channels); + indio_dev->available_scan_masks = inv_mpu_scan_masks; + break; + default: + indio_dev->channels = inv_mpu6500_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_mpu6500_channels); + indio_dev->available_scan_masks = inv_mpu_scan_masks; + break; + } + } + + indio_dev->info = &mpu_info; + + if (irq > 0) { + /* + * The driver currently only supports buffered capture with its + * own trigger. So no IRQ, no trigger, no buffer + */ + result = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + inv_mpu6050_read_fifo, + NULL); + if (result) { + dev_err(dev, "configure buffer fail %d\n", result); + return result; + } + + result = inv_mpu6050_probe_trigger(indio_dev, irq_type); + if (result) { + dev_err(dev, "trigger probe fail %d\n", result); + return result; + } + } + + result = devm_iio_device_register(dev, indio_dev); + if (result) { + dev_err(dev, "IIO register fail %d\n", result); + return result; + } + + return 0; + +error_power_off: + inv_mpu6050_set_power_itg(st, false); + return result; +} +EXPORT_SYMBOL_NS_GPL(inv_mpu_core_probe, "IIO_MPU6050"); + +static int inv_mpu_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu6050_state *st = iio_priv(indio_dev); + bool wakeup; + int result; + + guard(mutex)(&st->lock); + + wakeup = device_may_wakeup(dev) && st->chip_config.wom_en; + + if (wakeup) { + enable_irq(st->irq); + disable_irq_wake(st->irq); + result = inv_mpu6050_set_wom_lp(st, false); + if (result) + return result; + } else { + result = inv_mpu_core_enable_regulator_vddio(st); + if (result) + return result; + result = inv_mpu6050_set_power_itg(st, true); + if (result) + return result; + } + + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + result = inv_mpu6050_switch_engine(st, true, st->suspended_sensors); + if (result) + return result; + + if (st->chip_config.wom_en && !wakeup) { + result = inv_mpu6050_set_wom_int(st, true); + if (result) + return result; + } + + if (iio_buffer_enabled(indio_dev)) + result = inv_mpu6050_prepare_fifo(st, true); + + return result; +} + +static int inv_mpu_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu6050_state *st = iio_priv(indio_dev); + bool wakeup; + int result; + + guard(mutex)(&st->lock); + + st->suspended_sensors = 0; + if (pm_runtime_suspended(dev)) + return 0; + + if (iio_buffer_enabled(indio_dev)) { + result = inv_mpu6050_prepare_fifo(st, false); + if (result) + return result; + } + + wakeup = device_may_wakeup(dev) && st->chip_config.wom_en; + + if (st->chip_config.wom_en && !wakeup) { + result = inv_mpu6050_set_wom_int(st, false); + if (result) + return result; + } + + if (st->chip_config.accl_en && !wakeup) + st->suspended_sensors |= INV_MPU6050_SENSOR_ACCL; + if (st->chip_config.gyro_en) + st->suspended_sensors |= INV_MPU6050_SENSOR_GYRO; + if (st->chip_config.temp_en) + st->suspended_sensors |= INV_MPU6050_SENSOR_TEMP; + if (st->chip_config.magn_en) + st->suspended_sensors |= INV_MPU6050_SENSOR_MAGN; + if (st->chip_config.wom_en && !wakeup) + st->suspended_sensors |= INV_MPU6050_SENSOR_WOM; + result = inv_mpu6050_switch_engine(st, false, st->suspended_sensors); + if (result) + return result; + + if (wakeup) { + result = inv_mpu6050_set_wom_lp(st, true); + if (result) + return result; + enable_irq_wake(st->irq); + disable_irq(st->irq); + } else { + result = inv_mpu6050_set_power_itg(st, false); + if (result) + return result; + inv_mpu_core_disable_regulator_vddio(st); + } + + return 0; +} + +static int inv_mpu_runtime_suspend(struct device *dev) +{ + struct inv_mpu6050_state *st = iio_priv(dev_get_drvdata(dev)); + unsigned int sensors; + int ret; + + mutex_lock(&st->lock); + + sensors = INV_MPU6050_SENSOR_ACCL | INV_MPU6050_SENSOR_GYRO | + INV_MPU6050_SENSOR_TEMP | INV_MPU6050_SENSOR_MAGN | + INV_MPU6050_SENSOR_WOM; + ret = inv_mpu6050_switch_engine(st, false, sensors); + if (ret) + goto out_unlock; + + ret = inv_mpu6050_set_power_itg(st, false); + if (ret) + goto out_unlock; + + inv_mpu_core_disable_regulator_vddio(st); + +out_unlock: + mutex_unlock(&st->lock); + return ret; +} + +static int inv_mpu_runtime_resume(struct device *dev) +{ + struct inv_mpu6050_state *st = iio_priv(dev_get_drvdata(dev)); + int ret; + + ret = inv_mpu_core_enable_regulator_vddio(st); + if (ret) + return ret; + + return inv_mpu6050_set_power_itg(st, true); +} + +EXPORT_NS_GPL_DEV_PM_OPS(inv_mpu_pmops, IIO_MPU6050) = { + SYSTEM_SLEEP_PM_OPS(inv_mpu_suspend, inv_mpu_resume) + RUNTIME_PM_OPS(inv_mpu_runtime_suspend, inv_mpu_runtime_resume, NULL) +}; + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Invensense device MPU6050 driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_INV_SENSORS_TIMESTAMP"); diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c new file mode 100644 index 000000000..9ef6ab74a --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* +* Copyright (C) 2012 Invensense, Inc. +*/ + +#include +#include +#include +#include +#include +#include + +#include "inv_mpu_iio.h" + +static const struct regmap_config inv_mpu_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int inv_mpu6050_select_bypass(struct i2c_mux_core *muxc, u32 chan_id) +{ + return 0; +} + +static bool inv_mpu_i2c_aux_bus(struct device *dev) +{ + struct inv_mpu6050_state *st = iio_priv(dev_get_drvdata(dev)); + + switch (st->chip_type) { + case INV_ICM20608: + case INV_ICM20608D: + case INV_ICM20609: + case INV_ICM20689: + case INV_ICM20600: + case INV_ICM20602: + case INV_IAM20380: + case INV_IAM20680: + /* no i2c auxiliary bus on the chip */ + return false; + case INV_MPU9150: + case INV_MPU9250: + case INV_MPU9255: + if (st->magn_disabled) + return true; + else + return false; + default: + return true; + } +} + +static int inv_mpu_i2c_aux_setup(struct iio_dev *indio_dev) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + struct device *dev = indio_dev->dev.parent; + struct fwnode_handle *mux_node; + int ret; + + /* + * MPU9xxx magnetometer support requires to disable i2c auxiliary bus. + * To ensure backward compatibility with existing setups, do not disable + * i2c auxiliary bus if it used. + * Check for i2c-gate node in devicetree and set magnetometer disabled. + * Only MPU6500 is supported by ACPI, no need to check. + */ + switch (st->chip_type) { + case INV_MPU9150: + case INV_MPU9250: + case INV_MPU9255: + mux_node = device_get_named_child_node(dev, "i2c-gate"); + if (mux_node != NULL) { + st->magn_disabled = true; + dev_warn(dev, "disable internal use of magnetometer\n"); + } + fwnode_handle_put(mux_node); + break; + default: + break; + } + + /* enable i2c bypass when using i2c auxiliary bus */ + if (inv_mpu_i2c_aux_bus(dev)) { + ret = regmap_write(st->map, st->reg->int_pin_cfg, + st->irq_mask | INV_MPU6050_BIT_BYPASS_EN); + if (ret) + return ret; + } + + return 0; +} + +/** + * inv_mpu_probe() - probe function. + * @client: i2c client. + * + * Returns 0 on success, a negative error code otherwise. + */ +static int inv_mpu_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + const void *match; + struct inv_mpu6050_state *st; + int result; + enum inv_devices chip_type; + struct regmap *regmap; + const char *name; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_I2C_BLOCK)) + return -EOPNOTSUPP; + + match = device_get_match_data(&client->dev); + if (match) { + chip_type = (uintptr_t)match; + name = client->name; + } else if (id) { + chip_type = (enum inv_devices) + id->driver_data; + name = id->name; + } else { + return -ENOSYS; + } + + regmap = devm_regmap_init_i2c(client, &inv_mpu_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Failed to register i2c regmap: %pe\n", + regmap); + return PTR_ERR(regmap); + } + + result = inv_mpu_core_probe(regmap, client->irq, name, + inv_mpu_i2c_aux_setup, chip_type); + if (result < 0) + return result; + + st = iio_priv(dev_get_drvdata(&client->dev)); + if (inv_mpu_i2c_aux_bus(&client->dev)) { + /* declare i2c auxiliary bus */ + st->muxc = i2c_mux_alloc(client->adapter, &client->dev, + 1, 0, I2C_MUX_LOCKED | I2C_MUX_GATE, + inv_mpu6050_select_bypass, NULL); + if (!st->muxc) + return -ENOMEM; + st->muxc->priv = dev_get_drvdata(&client->dev); + result = i2c_mux_add_adapter(st->muxc, 0, 0); + if (result) + return result; + result = inv_mpu_acpi_create_mux_client(client); + if (result) + goto out_del_mux; + } + + return 0; + +out_del_mux: + i2c_mux_del_adapters(st->muxc); + return result; +} + +static void inv_mpu_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + if (st->muxc) { + inv_mpu_acpi_delete_mux_client(client); + i2c_mux_del_adapters(st->muxc); + } +} + +/* + * device id table is used to identify what device can be + * supported by this driver + */ +static const struct i2c_device_id inv_mpu_id[] = { + { .name = "mpu6050", .driver_data = INV_MPU6050 }, + { .name = "mpu6500", .driver_data = INV_MPU6500 }, + { .name = "mpu6515", .driver_data = INV_MPU6515 }, + { .name = "mpu6880", .driver_data = INV_MPU6880 }, + { .name = "mpu9150", .driver_data = INV_MPU9150 }, + { .name = "mpu9250", .driver_data = INV_MPU9250 }, + { .name = "mpu9255", .driver_data = INV_MPU9255 }, + { .name = "icm20608", .driver_data = INV_ICM20608 }, + { .name = "icm20608d", .driver_data = INV_ICM20608D }, + { .name = "icm20609", .driver_data = INV_ICM20609 }, + { .name = "icm20689", .driver_data = INV_ICM20689 }, + { .name = "icm20600", .driver_data = INV_ICM20600 }, + { .name = "icm20602", .driver_data = INV_ICM20602 }, + { .name = "icm20690", .driver_data = INV_ICM20690 }, + { .name = "iam20380", .driver_data = INV_IAM20380 }, + { .name = "iam20680", .driver_data = INV_IAM20680 }, + { .name = "iam20680hp", .driver_data = INV_IAM20680HP }, + { .name = "iam20680ht", .driver_data = INV_IAM20680HT }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, inv_mpu_id); + +static const struct of_device_id inv_of_match[] = { + { + .compatible = "invensense,mpu6050", + .data = (void *)INV_MPU6050 + }, + { + .compatible = "invensense,mpu6500", + .data = (void *)INV_MPU6500 + }, + { + .compatible = "invensense,mpu6515", + .data = (void *)INV_MPU6515 + }, + { + .compatible = "invensense,mpu6880", + .data = (void *)INV_MPU6880 + }, + { + .compatible = "invensense,mpu9150", + .data = (void *)INV_MPU9150 + }, + { + .compatible = "invensense,mpu9250", + .data = (void *)INV_MPU9250 + }, + { + .compatible = "invensense,mpu9255", + .data = (void *)INV_MPU9255 + }, + { + .compatible = "invensense,icm20608", + .data = (void *)INV_ICM20608 + }, + { + .compatible = "invensense,icm20608d", + .data = (void *)INV_ICM20608D + }, + { + .compatible = "invensense,icm20609", + .data = (void *)INV_ICM20609 + }, + { + .compatible = "invensense,icm20689", + .data = (void *)INV_ICM20689 + }, + { + .compatible = "invensense,icm20600", + .data = (void *)INV_ICM20600 + }, + { + .compatible = "invensense,icm20602", + .data = (void *)INV_ICM20602 + }, + { + .compatible = "invensense,icm20690", + .data = (void *)INV_ICM20690 + }, + { + .compatible = "invensense,iam20380", + .data = (void *)INV_IAM20380 + }, + { + .compatible = "invensense,iam20680", + .data = (void *)INV_IAM20680 + }, + { + .compatible = "invensense,iam20680hp", + .data = (void *)INV_IAM20680HP + }, + { + .compatible = "invensense,iam20680ht", + .data = (void *)INV_IAM20680HT + }, + { } +}; +MODULE_DEVICE_TABLE(of, inv_of_match); + +static const struct acpi_device_id inv_acpi_match[] = { + {"INVN6500", INV_MPU6500}, + { } +}; +MODULE_DEVICE_TABLE(acpi, inv_acpi_match); + +static struct i2c_driver inv_mpu_driver = { + .probe = inv_mpu_probe, + .remove = inv_mpu_remove, + .id_table = inv_mpu_id, + .driver = { + .of_match_table = inv_of_match, + .acpi_match_table = inv_acpi_match, + .name = "inv-mpu6050-i2c", + .pm = pm_ptr(&inv_mpu_pmops), + }, +}; + +module_i2c_driver(inv_mpu_driver); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Invensense device MPU6050 driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_MPU6050"); diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h new file mode 100644 index 000000000..6239b1a80 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h @@ -0,0 +1,516 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* +* Copyright (C) 2012 Invensense, Inc. +*/ + +#ifndef INV_MPU_IIO_H_ +#define INV_MPU_IIO_H_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * struct inv_mpu6050_reg_map - Notable registers. + * @sample_rate_div: Divider applied to gyro output rate. + * @lpf: Configures internal low pass filter. + * @accel_lpf: Configures accelerometer low pass filter. + * @user_ctrl: Enables/resets the FIFO. + * @fifo_en: Determines which data will appear in FIFO. + * @gyro_config: gyro config register. + * @accl_config: accel config register + * @fifo_count_h: Upper byte of FIFO count. + * @fifo_r_w: FIFO register. + * @raw_gyro: Address of first gyro register. + * @raw_accl: Address of first accel register. + * @temperature: temperature register + * @int_enable: Interrupt enable register. + * @int_status: Interrupt status register. + * @pwr_mgmt_1: Controls chip's power state and clock source. + * @pwr_mgmt_2: Controls power state of individual sensors. + * @int_pin_cfg; Controls interrupt pin configuration. + * @accl_offset: Controls the accelerometer calibration offset. + * @gyro_offset: Controls the gyroscope calibration offset. + * @i2c_if: Controls the i2c interface + */ +struct inv_mpu6050_reg_map { + u8 sample_rate_div; + u8 lpf; + u8 accel_lpf; + u8 user_ctrl; + u8 fifo_en; + u8 gyro_config; + u8 accl_config; + u8 fifo_count_h; + u8 fifo_r_w; + u8 raw_gyro; + u8 raw_accl; + u8 temperature; + u8 int_enable; + u8 int_status; + u8 pwr_mgmt_1; + u8 pwr_mgmt_2; + u8 int_pin_cfg; + u8 accl_offset; + u8 gyro_offset; + u8 i2c_if; +}; + +/*device enum */ +enum inv_devices { + INV_MPU6050, + INV_MPU6500, + INV_MPU6515, + INV_MPU6880, + INV_MPU6000, + INV_MPU9150, + INV_MPU9250, + INV_MPU9255, + INV_ICM20608, + INV_ICM20608D, + INV_ICM20609, + INV_ICM20689, + INV_ICM20600, + INV_ICM20602, + INV_ICM20690, + INV_IAM20380, + INV_IAM20680, + INV_IAM20680HP, + INV_IAM20680HT, + INV_NUM_PARTS +}; + +/* chip sensors mask: accelerometer, gyroscope, temperature, magnetometer, WoM */ +#define INV_MPU6050_SENSOR_ACCL BIT(0) +#define INV_MPU6050_SENSOR_GYRO BIT(1) +#define INV_MPU6050_SENSOR_TEMP BIT(2) +#define INV_MPU6050_SENSOR_MAGN BIT(3) +#define INV_MPU6050_SENSOR_WOM BIT(4) + +/** + * struct inv_mpu6050_chip_config - Cached chip configuration data. + * @clk: selected chip clock + * @fsr: Full scale range. + * @lpf: Digital low pass filter frequency. + * @accl_fs: accel full scale range. + * @accl_en: accel engine enabled + * @gyro_en: gyro engine enabled + * @temp_en: temperature sensor enabled + * @magn_en: magn engine (i2c master) enabled + * @wom_en: Wake-on-Motion enabled + * @accl_fifo_enable: enable accel data output + * @gyro_fifo_enable: enable gyro data output + * @temp_fifo_enable: enable temp data output + * @magn_fifo_enable: enable magn data output + * @divider: chip sample rate divider (sample rate divider - 1) + * @roc_threshold: save ROC threshold (WoM) set value + */ +struct inv_mpu6050_chip_config { + unsigned int clk:3; + unsigned int fsr:2; + unsigned int lpf:3; + unsigned int accl_fs:2; + unsigned int accl_en:1; + unsigned int gyro_en:1; + unsigned int temp_en:1; + unsigned int magn_en:1; + unsigned int wom_en:1; + unsigned int accl_fifo_enable:1; + unsigned int gyro_fifo_enable:1; + unsigned int temp_fifo_enable:1; + unsigned int magn_fifo_enable:1; + u8 divider; + u8 user_ctrl; + u64 roc_threshold; +}; + +/* + * Maximum of 6 + 6 + 2 + 7 (for MPU9x50) = 21 round up to 24 and plus 8. + * May be less if fewer channels are enabled, as long as the timestamp + * remains 8 byte aligned + */ +#define INV_MPU6050_OUTPUT_DATA_SIZE 32 + +/** + * struct inv_mpu6050_hw - Other important hardware information. + * @whoami: Self identification byte from WHO_AM_I register + * @name: name of the chip. + * @reg: register map of the chip. + * @config: configuration of the chip. + * @fifo_size: size of the FIFO in bytes. + * @temp: offset and scale to apply to raw temperature. + */ +struct inv_mpu6050_hw { + u8 whoami; + u8 *name; + const struct inv_mpu6050_reg_map *reg; + const struct inv_mpu6050_chip_config *config; + size_t fifo_size; + struct { + int offset; + int scale; + } temp; + struct { + unsigned int accel; + unsigned int gyro; + } startup_time; +}; + +/* + * struct inv_mpu6050_state - Driver state variables. + * @lock: Chip access lock. + * @trig: IIO trigger. + * @chip_config: Cached attribute information. + * @reg: Map of important registers. + * @hw: Other hardware-specific information. + * @chip_type: chip type. + * @plat_data: platform data (deprecated in favor of @orientation). + * @orientation: sensor chip orientation relative to main hardware. + * @map regmap pointer. + * @irq interrupt number. + * @irq_mask the int_pin_cfg mask to configure interrupt type. + * @timestamp: timestamping module + * @vdd_supply: VDD voltage regulator for the chip. + * @vddio_supply I/O voltage regulator for the chip. + * @magn_disabled: magnetometer disabled for backward compatibility reason. + * @magn_raw_to_gauss: coefficient to convert mag raw value to Gauss. + * @magn_orient: magnetometer sensor chip orientation if available. + * @suspended_sensors: sensors mask of sensors turned off for suspend + * @data: read buffer used for bulk reads. + * @it_timestamp: interrupt timestamp. + */ +struct inv_mpu6050_state { + struct mutex lock; + struct iio_trigger *trig; + struct inv_mpu6050_chip_config chip_config; + const struct inv_mpu6050_reg_map *reg; + const struct inv_mpu6050_hw *hw; + enum inv_devices chip_type; + struct i2c_mux_core *muxc; + struct i2c_client *mux_client; + struct inv_mpu6050_platform_data plat_data; + struct iio_mount_matrix orientation; + struct regmap *map; + int irq; + u8 irq_mask; + unsigned skip_samples; + struct inv_sensors_timestamp timestamp; + struct regulator *vdd_supply; + struct regulator *vddio_supply; + bool magn_disabled; + s32 magn_raw_to_gauss[3]; + struct iio_mount_matrix magn_orient; + unsigned int suspended_sensors; + bool level_shifter; + u8 *data; + s64 it_timestamp; +}; + +/*register and associated bit definition*/ +#define INV_MPU6050_REG_ACCEL_OFFSET 0x06 +#define INV_MPU6050_REG_GYRO_OFFSET 0x13 + +#define INV_MPU6050_REG_SAMPLE_RATE_DIV 0x19 +#define INV_MPU6050_REG_CONFIG 0x1A +#define INV_MPU6050_REG_GYRO_CONFIG 0x1B +#define INV_MPU6050_REG_ACCEL_CONFIG 0x1C + +#define INV_MPU6050_REG_FIFO_EN 0x23 +#define INV_MPU6050_BIT_SLAVE_0 0x01 +#define INV_MPU6050_BIT_SLAVE_1 0x02 +#define INV_MPU6050_BIT_SLAVE_2 0x04 +#define INV_MPU6050_BIT_ACCEL_OUT 0x08 +#define INV_MPU6050_BITS_GYRO_OUT 0x70 +#define INV_MPU6050_BIT_TEMP_OUT 0x80 + +#define INV_MPU6050_REG_I2C_MST_CTRL 0x24 +#define INV_MPU6050_BITS_I2C_MST_CLK_400KHZ 0x0D +#define INV_MPU6050_BIT_I2C_MST_P_NSR 0x10 +#define INV_MPU6050_BIT_SLV3_FIFO_EN 0x20 +#define INV_MPU6050_BIT_WAIT_FOR_ES 0x40 +#define INV_MPU6050_BIT_MULT_MST_EN 0x80 + +/* control I2C slaves from 0 to 3 */ +#define INV_MPU6050_REG_I2C_SLV_ADDR(_x) (0x25 + 3 * (_x)) +#define INV_MPU6050_BIT_I2C_SLV_RNW 0x80 + +#define INV_MPU6050_REG_I2C_SLV_REG(_x) (0x26 + 3 * (_x)) + +#define INV_MPU6050_REG_I2C_SLV_CTRL(_x) (0x27 + 3 * (_x)) +#define INV_MPU6050_BIT_SLV_GRP 0x10 +#define INV_MPU6050_BIT_SLV_REG_DIS 0x20 +#define INV_MPU6050_BIT_SLV_BYTE_SW 0x40 +#define INV_MPU6050_BIT_SLV_EN 0x80 + +/* I2C master delay register */ +#define INV_MPU6050_REG_I2C_SLV4_CTRL 0x34 +#define INV_MPU6050_BITS_I2C_MST_DLY(_x) ((_x) & 0x1F) + +#define INV_MPU6050_REG_I2C_MST_STATUS 0x36 +#define INV_MPU6050_BIT_I2C_SLV0_NACK 0x01 +#define INV_MPU6050_BIT_I2C_SLV1_NACK 0x02 +#define INV_MPU6050_BIT_I2C_SLV2_NACK 0x04 +#define INV_MPU6050_BIT_I2C_SLV3_NACK 0x08 + +#define INV_MPU6050_REG_INT_ENABLE 0x38 +#define INV_MPU6050_BIT_DATA_RDY_EN 0x01 +#define INV_MPU6050_BIT_DMP_INT_EN 0x02 +#define INV_MPU6500_BIT_WOM_INT_EN BIT(6) +#define INV_ICM20608_BIT_WOM_INT_EN GENMASK(7, 5) + +#define INV_MPU6050_REG_RAW_ACCEL 0x3B +#define INV_MPU6050_REG_TEMPERATURE 0x41 +#define INV_MPU6050_REG_RAW_GYRO 0x43 + +#define INV_MPU6050_REG_INT_STATUS 0x3A +#define INV_MPU6500_BIT_WOM_INT BIT(6) +#define INV_ICM20608_BIT_WOM_INT GENMASK(7, 5) +#define INV_MPU6050_BIT_FIFO_OVERFLOW_INT 0x10 +#define INV_MPU6050_BIT_RAW_DATA_RDY_INT 0x01 + +#define INV_MPU6050_REG_EXT_SENS_DATA 0x49 + +/* I2C slaves data output from 0 to 3 */ +#define INV_MPU6050_REG_I2C_SLV_DO(_x) (0x63 + (_x)) + +#define INV_MPU6050_REG_I2C_MST_DELAY_CTRL 0x67 +#define INV_MPU6050_BIT_I2C_SLV0_DLY_EN 0x01 +#define INV_MPU6050_BIT_I2C_SLV1_DLY_EN 0x02 +#define INV_MPU6050_BIT_I2C_SLV2_DLY_EN 0x04 +#define INV_MPU6050_BIT_I2C_SLV3_DLY_EN 0x08 +#define INV_MPU6050_BIT_DELAY_ES_SHADOW 0x80 + +#define INV_MPU6050_REG_SIGNAL_PATH_RESET 0x68 +#define INV_MPU6050_BIT_TEMP_RST BIT(0) +#define INV_MPU6050_BIT_ACCEL_RST BIT(1) +#define INV_MPU6050_BIT_GYRO_RST BIT(2) + +#define INV_MPU6050_REG_USER_CTRL 0x6A +#define INV_MPU6050_BIT_SIG_COND_RST 0x01 +#define INV_MPU6050_BIT_FIFO_RST 0x04 +#define INV_MPU6050_BIT_DMP_RST 0x08 +#define INV_MPU6050_BIT_I2C_MST_EN 0x20 +#define INV_MPU6050_BIT_FIFO_EN 0x40 +#define INV_MPU6050_BIT_DMP_EN 0x80 +#define INV_MPU6050_BIT_I2C_IF_DIS 0x10 + +#define INV_MPU6050_REG_PWR_MGMT_1 0x6B +#define INV_MPU6050_BIT_H_RESET 0x80 +#define INV_MPU6050_BIT_SLEEP 0x40 +#define INV_MPU6050_BIT_CYCLE 0x20 +#define INV_MPU6050_BIT_TEMP_DIS 0x08 +#define INV_MPU6050_BIT_CLK_MASK 0x7 + +#define INV_MPU6050_REG_PWR_MGMT_2 0x6C +#define INV_MPU6050_BIT_PWR_ACCL_STBY 0x38 +#define INV_MPU6050_BIT_PWR_GYRO_STBY 0x07 + +/* ICM20609 registers */ +#define INV_ICM20609_REG_ACCEL_WOM_X_THR 0x20 +#define INV_ICM20609_REG_ACCEL_WOM_Y_THR 0x21 +#define INV_ICM20609_REG_ACCEL_WOM_Z_THR 0x22 + +/* ICM20602 register */ +#define INV_ICM20602_REG_I2C_IF 0x70 +#define INV_ICM20602_BIT_I2C_IF_DIS 0x40 + +#define INV_MPU6050_REG_FIFO_COUNT_H 0x72 +#define INV_MPU6050_REG_FIFO_R_W 0x74 + +#define INV_MPU6050_BYTES_PER_3AXIS_SENSOR 6 +#define INV_MPU6050_FIFO_COUNT_BYTE 2 + +/* MPU9X50 9-axis magnetometer */ +#define INV_MPU9X50_BYTES_MAGN 7 + +/* FIFO temperature sample size */ +#define INV_MPU6050_BYTES_PER_TEMP_SENSOR 2 + +/* mpu6500 registers */ +#define INV_MPU6500_REG_ACCEL_CONFIG_2 0x1D +#define INV_ICM20689_BITS_FIFO_SIZE_MAX 0xC0 +#define INV_MPU6500_REG_LP_ODR 0x1E +#define INV_MPU6500_REG_WOM_THRESHOLD 0x1F +#define INV_MPU6500_REG_ACCEL_INTEL_CTRL 0x69 +#define INV_MPU6500_BIT_ACCEL_INTEL_EN BIT(7) +#define INV_MPU6500_BIT_ACCEL_INTEL_MODE BIT(6) +#define INV_MPU6500_REG_ACCEL_OFFSET 0x77 + +/* delay time in milliseconds */ +#define INV_MPU6050_POWER_UP_TIME 100 +#define INV_MPU6050_TEMP_UP_TIME 100 +#define INV_MPU6050_ACCEL_STARTUP_TIME 20 +#define INV_MPU6050_GYRO_STARTUP_TIME 60 +#define INV_MPU6050_GYRO_DOWN_TIME 150 +#define INV_MPU6050_SUSPEND_DELAY_MS 2000 + +#define INV_MPU6500_GYRO_STARTUP_TIME 70 +#define INV_MPU6500_ACCEL_STARTUP_TIME 30 + +#define INV_ICM20602_GYRO_STARTUP_TIME 100 +#define INV_ICM20602_ACCEL_STARTUP_TIME 20 + +#define INV_ICM20690_GYRO_STARTUP_TIME 80 +#define INV_ICM20690_ACCEL_STARTUP_TIME 10 + + +/* delay time in microseconds */ +#define INV_MPU6050_REG_UP_TIME_MIN 5000 +#define INV_MPU6050_REG_UP_TIME_MAX 10000 + +#define INV_MPU6050_TEMP_OFFSET 12420 +#define INV_MPU6050_TEMP_SCALE 2941176 +#define INV_MPU6050_MAX_GYRO_FS_PARAM 3 +#define INV_MPU6050_MAX_ACCL_FS_PARAM 3 +#define INV_MPU6050_THREE_AXIS 3 +#define INV_MPU6050_GYRO_CONFIG_FSR_SHIFT 3 +#define INV_ICM20690_GYRO_CONFIG_FSR_SHIFT 2 +#define INV_MPU6050_ACCL_CONFIG_FSR_SHIFT 3 + +#define INV_MPU6500_TEMP_OFFSET 7011 +#define INV_MPU6500_TEMP_SCALE 2995178 + +#define INV_ICM20608_TEMP_OFFSET 8170 +#define INV_ICM20608_TEMP_SCALE 3059976 + +#define INV_MPU6050_REG_INT_PIN_CFG 0x37 +#define INV_MPU6050_ACTIVE_HIGH 0x00 +#define INV_MPU6050_ACTIVE_LOW 0x80 +/* enable level triggering */ +#define INV_MPU6050_LATCH_INT_EN 0x20 +#define INV_MPU6050_BIT_BYPASS_EN 0x2 +/* allow acking interrupts by any register read */ +#define INV_MPU6050_INT_RD_CLEAR 0x10 + +/* Allowed timestamp period jitter in percent */ +#define INV_MPU6050_TS_PERIOD_JITTER 4 + +/* init parameters */ +#define INV_MPU6050_MAX_FIFO_RATE 1000 +#define INV_MPU6050_MIN_FIFO_RATE 4 + +/* chip internal frequency: 1KHz */ +#define INV_MPU6050_INTERNAL_FREQ_HZ 1000 +/* return the frequency divider (chip sample rate divider + 1) */ +#define INV_MPU6050_FREQ_DIVIDER(st) \ + ((st)->chip_config.divider + 1) +/* chip sample rate divider to fifo rate */ +#define INV_MPU6050_FIFO_RATE_TO_DIVIDER(fifo_rate) \ + ((INV_MPU6050_INTERNAL_FREQ_HZ / (fifo_rate)) - 1) +#define INV_MPU6050_DIVIDER_TO_FIFO_RATE(divider) \ + (INV_MPU6050_INTERNAL_FREQ_HZ / ((divider) + 1)) + +#define INV_MPU6050_REG_WHOAMI 117 + +#define INV_MPU6000_WHOAMI_VALUE 0x68 +#define INV_MPU6050_WHOAMI_VALUE 0x68 +#define INV_MPU6500_WHOAMI_VALUE 0x70 +#define INV_MPU6880_WHOAMI_VALUE 0x78 +#define INV_MPU9150_WHOAMI_VALUE 0x68 +#define INV_MPU9250_WHOAMI_VALUE 0x71 +#define INV_MPU9255_WHOAMI_VALUE 0x73 +#define INV_MPU6515_WHOAMI_VALUE 0x74 +#define INV_ICM20608_WHOAMI_VALUE 0xAF +#define INV_ICM20608D_WHOAMI_VALUE 0xAE +#define INV_ICM20609_WHOAMI_VALUE 0xA6 +#define INV_ICM20689_WHOAMI_VALUE 0x98 +#define INV_ICM20600_WHOAMI_VALUE 0x11 +#define INV_ICM20602_WHOAMI_VALUE 0x12 +#define INV_ICM20690_WHOAMI_VALUE 0x20 +#define INV_IAM20380_WHOAMI_VALUE 0xB5 +#define INV_IAM20680_WHOAMI_VALUE 0xA9 +#define INV_IAM20680HP_WHOAMI_VALUE 0xF8 +#define INV_IAM20680HT_WHOAMI_VALUE 0xFA + +/* scan element definition for generic MPU6xxx devices */ +enum inv_mpu6050_scan { + INV_MPU6050_SCAN_ACCL_X, + INV_MPU6050_SCAN_ACCL_Y, + INV_MPU6050_SCAN_ACCL_Z, + INV_MPU6050_SCAN_TEMP, + INV_MPU6050_SCAN_GYRO_X, + INV_MPU6050_SCAN_GYRO_Y, + INV_MPU6050_SCAN_GYRO_Z, + INV_MPU6050_SCAN_TIMESTAMP, + + INV_MPU9X50_SCAN_MAGN_X = INV_MPU6050_SCAN_GYRO_Z + 1, + INV_MPU9X50_SCAN_MAGN_Y, + INV_MPU9X50_SCAN_MAGN_Z, + INV_MPU9X50_SCAN_TIMESTAMP, +}; + +enum inv_mpu6050_filter_e { + INV_MPU6050_FILTER_NOLPF2 = 0, + INV_MPU6050_FILTER_200HZ, + INV_MPU6050_FILTER_100HZ, + INV_MPU6050_FILTER_45HZ, + INV_MPU6050_FILTER_20HZ, + INV_MPU6050_FILTER_10HZ, + INV_MPU6050_FILTER_5HZ, + INV_MPU6050_FILTER_NOLPF, + NUM_MPU6050_FILTER +}; + +enum inv_mpu6050_lposc_e { + INV_MPU6050_LPOSC_4HZ = 4, + INV_MPU6050_LPOSC_8HZ, + INV_MPU6050_LPOSC_16HZ, + INV_MPU6050_LPOSC_31HZ, + INV_MPU6050_LPOSC_62HZ, + INV_MPU6050_LPOSC_125HZ, + INV_MPU6050_LPOSC_250HZ, + INV_MPU6050_LPOSC_500HZ, + NUM_MPU6050_LPOSC, +}; + +/* IIO attribute address */ +enum INV_MPU6050_IIO_ATTR_ADDR { + ATTR_GYRO_MATRIX, + ATTR_ACCL_MATRIX, +}; + +enum inv_mpu6050_accl_fs_e { + INV_MPU6050_FS_02G = 0, + INV_MPU6050_FS_04G, + INV_MPU6050_FS_08G, + INV_MPU6050_FS_16G, + NUM_ACCL_FSR +}; + +enum inv_mpu6050_fsr_e { + INV_MPU6050_FSR_250DPS = 0, + INV_MPU6050_FSR_500DPS, + INV_MPU6050_FSR_1000DPS, + INV_MPU6050_FSR_2000DPS, + NUM_MPU6050_FSR +}; + +enum inv_mpu6050_clock_sel_e { + INV_CLK_INTERNAL = 0, + INV_CLK_PLL, + NUM_CLK +}; + +irqreturn_t inv_mpu6050_read_fifo(int irq, void *p); +int inv_mpu6050_probe_trigger(struct iio_dev *indio_dev, int irq_type); +int inv_mpu6050_prepare_fifo(struct inv_mpu6050_state *st, bool enable); +int inv_mpu6050_switch_engine(struct inv_mpu6050_state *st, bool en, + unsigned int mask); +int inv_mpu_acpi_create_mux_client(struct i2c_client *client); +void inv_mpu_acpi_delete_mux_client(struct i2c_client *client); +int inv_mpu_core_probe(struct regmap *regmap, int irq, const char *name, + int (*inv_mpu_bus_setup)(struct iio_dev *), int chip_type); +extern const struct dev_pm_ops inv_mpu_pmops; + +#endif diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_magn.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_magn.c new file mode 100644 index 000000000..6b858fdfd --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_magn.c @@ -0,0 +1,365 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 TDK-InvenSense, Inc. + */ + +#include +#include +#include + +#include "inv_mpu_aux.h" +#include "inv_mpu_iio.h" +#include "inv_mpu_magn.h" + +/* + * MPU9xxx magnetometer are AKM chips on I2C aux bus + * MPU9150 is AK8975 + * MPU9250 is AK8963 + */ +#define INV_MPU_MAGN_I2C_ADDR 0x0C + +#define INV_MPU_MAGN_REG_WIA 0x00 +#define INV_MPU_MAGN_BITS_WIA 0x48 + +#define INV_MPU_MAGN_REG_ST1 0x02 +#define INV_MPU_MAGN_BIT_DRDY 0x01 +#define INV_MPU_MAGN_BIT_DOR 0x02 + +#define INV_MPU_MAGN_REG_DATA 0x03 + +#define INV_MPU_MAGN_REG_ST2 0x09 +#define INV_MPU_MAGN_BIT_HOFL 0x08 +#define INV_MPU_MAGN_BIT_BITM 0x10 + +#define INV_MPU_MAGN_REG_CNTL1 0x0A +#define INV_MPU_MAGN_BITS_MODE_PWDN 0x00 +#define INV_MPU_MAGN_BITS_MODE_SINGLE 0x01 +#define INV_MPU_MAGN_BITS_MODE_FUSE 0x0F +#define INV_MPU9250_MAGN_BIT_OUTPUT_BIT 0x10 + +#define INV_MPU9250_MAGN_REG_CNTL2 0x0B +#define INV_MPU9250_MAGN_BIT_SRST 0x01 + +#define INV_MPU_MAGN_REG_ASAX 0x10 +#define INV_MPU_MAGN_REG_ASAY 0x11 +#define INV_MPU_MAGN_REG_ASAZ 0x12 + +static bool inv_magn_supported(const struct inv_mpu6050_state *st) +{ + switch (st->chip_type) { + case INV_MPU9150: + case INV_MPU9250: + case INV_MPU9255: + return true; + default: + return false; + } +} + +/* init magnetometer chip */ +static int inv_magn_init(struct inv_mpu6050_state *st) +{ + uint8_t val; + uint8_t asa[3]; + int32_t sensitivity; + int ret; + + /* check whoami */ + ret = inv_mpu_aux_read(st, INV_MPU_MAGN_I2C_ADDR, INV_MPU_MAGN_REG_WIA, + &val, sizeof(val)); + if (ret) + return ret; + if (val != INV_MPU_MAGN_BITS_WIA) + return -ENODEV; + + /* software reset for MPU925x only */ + switch (st->chip_type) { + case INV_MPU9250: + case INV_MPU9255: + ret = inv_mpu_aux_write(st, INV_MPU_MAGN_I2C_ADDR, + INV_MPU9250_MAGN_REG_CNTL2, + INV_MPU9250_MAGN_BIT_SRST); + if (ret) + return ret; + break; + default: + break; + } + + /* read fuse ROM data */ + ret = inv_mpu_aux_write(st, INV_MPU_MAGN_I2C_ADDR, + INV_MPU_MAGN_REG_CNTL1, + INV_MPU_MAGN_BITS_MODE_FUSE); + if (ret) + return ret; + + ret = inv_mpu_aux_read(st, INV_MPU_MAGN_I2C_ADDR, INV_MPU_MAGN_REG_ASAX, + asa, sizeof(asa)); + if (ret) + return ret; + + /* switch back to power-down */ + ret = inv_mpu_aux_write(st, INV_MPU_MAGN_I2C_ADDR, + INV_MPU_MAGN_REG_CNTL1, + INV_MPU_MAGN_BITS_MODE_PWDN); + if (ret) + return ret; + + /* + * Sensor sensitivity + * 1 uT = 0.01 G and value is in micron (1e6) + * sensitivity = x uT * 0.01 * 1e6 + */ + switch (st->chip_type) { + case INV_MPU9150: + /* sensor sensitivity is 0.3 uT */ + sensitivity = 3000; + break; + case INV_MPU9250: + case INV_MPU9255: + /* sensor sensitivity in 16 bits mode: 0.15 uT */ + sensitivity = 1500; + break; + default: + return -EINVAL; + } + + /* + * Sensitivity adjustment and scale to Gauss + * + * Hadj = H * (((ASA - 128) * 0.5 / 128) + 1) + * Factor simplification: + * Hadj = H * ((ASA + 128) / 256) + * + * raw_to_gauss = Hadj * sensitivity + */ + st->magn_raw_to_gauss[0] = (((int32_t)asa[0] + 128) * sensitivity) / 256; + st->magn_raw_to_gauss[1] = (((int32_t)asa[1] + 128) * sensitivity) / 256; + st->magn_raw_to_gauss[2] = (((int32_t)asa[2] + 128) * sensitivity) / 256; + + return 0; +} + +/** + * inv_mpu_magn_probe() - probe and setup magnetometer chip + * @st: driver internal state + * + * Returns 0 on success, a negative error code otherwise + * + * It is probing the chip and setting up all needed i2c transfers. + * Noop if there is no magnetometer in the chip. + */ +int inv_mpu_magn_probe(struct inv_mpu6050_state *st) +{ + uint8_t val; + int ret; + + /* quit if chip is not supported */ + if (!inv_magn_supported(st)) + return 0; + + /* configure i2c master aux port */ + ret = inv_mpu_aux_init(st); + if (ret) + return ret; + + /* check and init mag chip */ + ret = inv_magn_init(st); + if (ret) + return ret; + + /* + * configure mpu i2c master accesses + * i2c SLV0: read sensor data, 7 bytes data(6)-ST2 + * Byte swap data to store them in big-endian in impair address groups + */ + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_ADDR(0), + INV_MPU6050_BIT_I2C_SLV_RNW | INV_MPU_MAGN_I2C_ADDR); + if (ret) + return ret; + + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_REG(0), + INV_MPU_MAGN_REG_DATA); + if (ret) + return ret; + + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_CTRL(0), + INV_MPU6050_BIT_SLV_EN | + INV_MPU6050_BIT_SLV_BYTE_SW | + INV_MPU6050_BIT_SLV_GRP | + INV_MPU9X50_BYTES_MAGN); + if (ret) + return ret; + + /* i2c SLV1: launch single measurement */ + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_ADDR(1), + INV_MPU_MAGN_I2C_ADDR); + if (ret) + return ret; + + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_REG(1), + INV_MPU_MAGN_REG_CNTL1); + if (ret) + return ret; + + /* add 16 bits mode for MPU925x */ + val = INV_MPU_MAGN_BITS_MODE_SINGLE; + switch (st->chip_type) { + case INV_MPU9250: + case INV_MPU9255: + val |= INV_MPU9250_MAGN_BIT_OUTPUT_BIT; + break; + default: + break; + } + ret = regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_DO(1), val); + if (ret) + return ret; + + return regmap_write(st->map, INV_MPU6050_REG_I2C_SLV_CTRL(1), + INV_MPU6050_BIT_SLV_EN | 1); +} + +/** + * inv_mpu_magn_set_rate() - set magnetometer sampling rate + * @st: driver internal state + * @fifo_rate: mpu set fifo rate + * + * Returns 0 on success, a negative error code otherwise + * + * Limit sampling frequency to the maximum value supported by the + * magnetometer chip. Resulting in duplicated data for higher frequencies. + * Noop if there is no magnetometer in the chip. + */ +int inv_mpu_magn_set_rate(const struct inv_mpu6050_state *st, int fifo_rate) +{ + uint8_t d; + + /* quit if chip is not supported */ + if (!inv_magn_supported(st)) + return 0; + + /* + * update i2c master delay to limit mag sampling to max frequency + * compute fifo_rate divider d: rate = fifo_rate / (d + 1) + */ + if (fifo_rate > INV_MPU_MAGN_FREQ_HZ_MAX) + d = fifo_rate / INV_MPU_MAGN_FREQ_HZ_MAX - 1; + else + d = 0; + + return regmap_write(st->map, INV_MPU6050_REG_I2C_SLV4_CTRL, d); +} + +/** + * inv_mpu_magn_set_orient() - fill magnetometer mounting matrix + * @st: driver internal state + * + * Returns 0 on success, a negative error code otherwise + * + * Fill magnetometer mounting matrix using the provided chip matrix. + */ +int inv_mpu_magn_set_orient(struct inv_mpu6050_state *st) +{ + struct device *dev = regmap_get_device(st->map); + const char *orient; + char *str; + int i; + + /* fill magnetometer orientation */ + switch (st->chip_type) { + case INV_MPU9150: + case INV_MPU9250: + case INV_MPU9255: + /* x <- y */ + st->magn_orient.rotation[0] = st->orientation.rotation[3]; + st->magn_orient.rotation[1] = st->orientation.rotation[4]; + st->magn_orient.rotation[2] = st->orientation.rotation[5]; + /* y <- x */ + st->magn_orient.rotation[3] = st->orientation.rotation[0]; + st->magn_orient.rotation[4] = st->orientation.rotation[1]; + st->magn_orient.rotation[5] = st->orientation.rotation[2]; + /* z <- -z */ + for (i = 6; i < 9; ++i) { + orient = st->orientation.rotation[i]; + + /* + * The value is negated according to one of the following + * rules: + * + * 1) Drop leading minus. + * 2) Leave 0 as is. + * 3) Add leading minus. + */ + if (orient[0] == '-') + str = devm_kstrdup(dev, orient + 1, GFP_KERNEL); + else if (!strcmp(orient, "0")) + str = devm_kstrdup(dev, orient, GFP_KERNEL); + else + str = devm_kasprintf(dev, GFP_KERNEL, "-%s", orient); + if (!str) + return -ENOMEM; + + st->magn_orient.rotation[i] = str; + } + break; + default: + st->magn_orient = st->orientation; + break; + } + + return 0; +} + +/** + * inv_mpu_magn_read() - read magnetometer data + * @st: driver internal state + * @axis: IIO modifier axis value + * @val: store corresponding axis value + * + * Returns 0 on success, a negative error code otherwise + */ +int inv_mpu_magn_read(struct inv_mpu6050_state *st, int axis, int *val) +{ + unsigned int status; + __be16 data; + uint8_t addr; + int ret; + + /* quit if chip is not supported */ + if (!inv_magn_supported(st)) + return -ENODEV; + + /* Mag data: XH,XL,YH,YL,ZH,ZL */ + switch (axis) { + case IIO_MOD_X: + addr = 0; + break; + case IIO_MOD_Y: + addr = 2; + break; + case IIO_MOD_Z: + addr = 4; + break; + default: + return -EINVAL; + } + addr += INV_MPU6050_REG_EXT_SENS_DATA; + + /* check i2c status and read raw data */ + ret = regmap_read(st->map, INV_MPU6050_REG_I2C_MST_STATUS, &status); + if (ret) + return ret; + + if (status & INV_MPU6050_BIT_I2C_SLV0_NACK || + status & INV_MPU6050_BIT_I2C_SLV1_NACK) + return -EIO; + + ret = regmap_bulk_read(st->map, addr, &data, sizeof(data)); + if (ret) + return ret; + + *val = (int16_t)be16_to_cpu(data); + + return IIO_VAL_INT; +} diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_magn.h b/drivers/iio/imu/inv_mpu6050/inv_mpu_magn.h new file mode 100644 index 000000000..185c000c6 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_magn.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2019 TDK-InvenSense, Inc. + */ + +#ifndef INV_MPU_MAGN_H_ +#define INV_MPU_MAGN_H_ + +#include + +#include "inv_mpu_iio.h" + +/* Magnetometer maximum frequency */ +#define INV_MPU_MAGN_FREQ_HZ_MAX 50 + +int inv_mpu_magn_probe(struct inv_mpu6050_state *st); + +/** + * inv_mpu_magn_get_scale() - get magnetometer scale value + * @st: driver internal state + * + * Returns IIO data format. + */ +static inline int inv_mpu_magn_get_scale(const struct inv_mpu6050_state *st, + const struct iio_chan_spec *chan, + int *val, int *val2) +{ + *val = 0; + *val2 = st->magn_raw_to_gauss[chan->address]; + return IIO_VAL_INT_PLUS_MICRO; +} + +int inv_mpu_magn_set_rate(const struct inv_mpu6050_state *st, int fifo_rate); + +int inv_mpu_magn_set_orient(struct inv_mpu6050_state *st); + +int inv_mpu_magn_read(struct inv_mpu6050_state *st, int axis, int *val); + +#endif /* INV_MPU_MAGN_H_ */ diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c new file mode 100644 index 000000000..c4c11124f --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* +* Copyright (C) 2012 Invensense, Inc. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "inv_mpu_iio.h" + +static int inv_reset_fifo(struct iio_dev *indio_dev) +{ + int result; + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + /* disable fifo and reenable it */ + inv_mpu6050_prepare_fifo(st, false); + result = inv_mpu6050_prepare_fifo(st, true); + if (result) + goto reset_fifo_fail; + + return 0; + +reset_fifo_fail: + dev_err(regmap_get_device(st->map), "reset fifo failed %d\n", result); + return regmap_update_bits(st->map, st->reg->int_enable, + INV_MPU6050_BIT_DATA_RDY_EN, INV_MPU6050_BIT_DATA_RDY_EN); +} + +/* + * inv_mpu6050_read_fifo() - Transfer data from hardware FIFO to KFIFO. + */ +irqreturn_t inv_mpu6050_read_fifo(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct inv_mpu6050_state *st = iio_priv(indio_dev); + size_t bytes_per_datum; + int result; + u16 fifo_count; + u32 fifo_period; + s64 timestamp; + /* clear internal data buffer for avoiding kernel data leak */ + u8 data[INV_MPU6050_OUTPUT_DATA_SIZE] __aligned(8) = { }; + size_t i, nb; + + mutex_lock(&st->lock); + + if (!(st->chip_config.accl_fifo_enable | + st->chip_config.gyro_fifo_enable | + st->chip_config.magn_fifo_enable)) + goto end_session; + bytes_per_datum = 0; + if (st->chip_config.accl_fifo_enable) + bytes_per_datum += INV_MPU6050_BYTES_PER_3AXIS_SENSOR; + + if (st->chip_config.gyro_fifo_enable) + bytes_per_datum += INV_MPU6050_BYTES_PER_3AXIS_SENSOR; + + if (st->chip_config.temp_fifo_enable) + bytes_per_datum += INV_MPU6050_BYTES_PER_TEMP_SENSOR; + + if (st->chip_config.magn_fifo_enable) + bytes_per_datum += INV_MPU9X50_BYTES_MAGN; + + /* + * read fifo_count register to know how many bytes are inside the FIFO + * right now + */ + result = regmap_bulk_read(st->map, st->reg->fifo_count_h, + st->data, INV_MPU6050_FIFO_COUNT_BYTE); + if (result) + goto end_session; + fifo_count = be16_to_cpup((__be16 *)&st->data[0]); + + /* + * Handle fifo overflow by resetting fifo. + * Reset if there is only 3 data set free remaining to mitigate + * possible delay between reading fifo count and fifo data. + */ + nb = 3 * bytes_per_datum; + if (fifo_count >= st->hw->fifo_size - nb) { + dev_warn(regmap_get_device(st->map), "fifo overflow reset\n"); + goto flush_fifo; + } + + /* compute and process only all complete datum */ + nb = fifo_count / bytes_per_datum; + fifo_count = nb * bytes_per_datum; + if (nb == 0) + goto end_session; + /* Each FIFO data contains all sensors, so same number for FIFO and sensor data */ + fifo_period = NSEC_PER_SEC / INV_MPU6050_DIVIDER_TO_FIFO_RATE(st->chip_config.divider); + inv_sensors_timestamp_interrupt(&st->timestamp, 1, pf->timestamp); + inv_sensors_timestamp_apply_odr(&st->timestamp, fifo_period, 1, 0); + + /* read all data once and process every samples */ + result = regmap_noinc_read(st->map, st->reg->fifo_r_w, st->data, fifo_count); + if (result) + goto flush_fifo; + for (i = 0; i < nb; ++i) { + /* skip first samples if needed */ + if (st->skip_samples) { + st->skip_samples--; + continue; + } + memcpy(data, &st->data[i * bytes_per_datum], bytes_per_datum); + timestamp = inv_sensors_timestamp_pop(&st->timestamp); + iio_push_to_buffers_with_timestamp(indio_dev, data, timestamp); + } + +end_session: + mutex_unlock(&st->lock); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; + +flush_fifo: + /* Flush HW and SW FIFOs. */ + inv_reset_fifo(indio_dev); + mutex_unlock(&st->lock); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c new file mode 100644 index 000000000..71e90ddc1 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* +* Copyright (C) 2015 Intel Corporation Inc. +*/ +#include +#include +#include +#include +#include +#include "inv_mpu_iio.h" + +static const struct regmap_config inv_mpu_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int inv_mpu_i2c_disable(struct iio_dev *indio_dev) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + int ret = 0; + + if (st->reg->i2c_if) { + ret = regmap_write(st->map, st->reg->i2c_if, + INV_ICM20602_BIT_I2C_IF_DIS); + } else { + st->chip_config.user_ctrl |= INV_MPU6050_BIT_I2C_IF_DIS; + ret = regmap_write(st->map, st->reg->user_ctrl, + st->chip_config.user_ctrl); + } + + return ret; +} + +static int inv_mpu_probe(struct spi_device *spi) +{ + const void *match; + struct regmap *regmap; + const struct spi_device_id *spi_id; + const char *name = NULL; + enum inv_devices chip_type; + + if ((spi_id = spi_get_device_id(spi))) { + chip_type = (enum inv_devices)spi_id->driver_data; + name = spi_id->name; + } else if ((match = device_get_match_data(&spi->dev))) { + chip_type = (uintptr_t)match; + name = dev_name(&spi->dev); + } else { + return -ENODEV; + } + + regmap = devm_regmap_init_spi(spi, &inv_mpu_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "Failed to register spi regmap: %pe\n", + regmap); + return PTR_ERR(regmap); + } + + return inv_mpu_core_probe(regmap, spi->irq, name, + inv_mpu_i2c_disable, chip_type); +} + +/* + * device id table is used to identify what device can be + * supported by this driver + */ +static const struct spi_device_id inv_mpu_id[] = { + { .name = "mpu6000", .driver_data = INV_MPU6000 }, + { .name = "mpu6500", .driver_data = INV_MPU6500 }, + { .name = "mpu6515", .driver_data = INV_MPU6515 }, + { .name = "mpu6880", .driver_data = INV_MPU6880 }, + { .name = "mpu9250", .driver_data = INV_MPU9250 }, + { .name = "mpu9255", .driver_data = INV_MPU9255 }, + { .name = "icm20608", .driver_data = INV_ICM20608 }, + { .name = "icm20608d", .driver_data = INV_ICM20608D }, + { .name = "icm20609", .driver_data = INV_ICM20609 }, + { .name = "icm20689", .driver_data = INV_ICM20689 }, + { .name = "icm20600", .driver_data = INV_ICM20600 }, + { .name = "icm20602", .driver_data = INV_ICM20602 }, + { .name = "icm20690", .driver_data = INV_ICM20690 }, + { .name = "iam20380", .driver_data = INV_IAM20380 }, + { .name = "iam20680", .driver_data = INV_IAM20680 }, + { .name = "iam20680hp", .driver_data = INV_IAM20680HP }, + { .name = "iam20680ht", .driver_data = INV_IAM20680HT }, + { } +}; + +MODULE_DEVICE_TABLE(spi, inv_mpu_id); + +static const struct of_device_id inv_of_match[] = { + { + .compatible = "invensense,mpu6000", + .data = (void *)INV_MPU6000 + }, + { + .compatible = "invensense,mpu6500", + .data = (void *)INV_MPU6500 + }, + { + .compatible = "invensense,mpu6515", + .data = (void *)INV_MPU6515 + }, + { + .compatible = "invensense,mpu6880", + .data = (void *)INV_MPU6880 + }, + { + .compatible = "invensense,mpu9250", + .data = (void *)INV_MPU9250 + }, + { + .compatible = "invensense,mpu9255", + .data = (void *)INV_MPU9255 + }, + { + .compatible = "invensense,icm20608", + .data = (void *)INV_ICM20608 + }, + { + .compatible = "invensense,icm20608d", + .data = (void *)INV_ICM20608D + }, + { + .compatible = "invensense,icm20609", + .data = (void *)INV_ICM20609 + }, + { + .compatible = "invensense,icm20689", + .data = (void *)INV_ICM20689 + }, + { + .compatible = "invensense,icm20600", + .data = (void *)INV_ICM20600 + }, + { + .compatible = "invensense,icm20602", + .data = (void *)INV_ICM20602 + }, + { + .compatible = "invensense,icm20690", + .data = (void *)INV_ICM20690 + }, + { + .compatible = "invensense,iam20380", + .data = (void *)INV_IAM20380 + }, + { + .compatible = "invensense,iam20680", + .data = (void *)INV_IAM20680 + }, + { + .compatible = "invensense,iam20680hp", + .data = (void *)INV_IAM20680HP + }, + { + .compatible = "invensense,iam20680ht", + .data = (void *)INV_IAM20680HT + }, + { } +}; +MODULE_DEVICE_TABLE(of, inv_of_match); + +static const struct acpi_device_id inv_acpi_match[] = { + {"INVN6000", INV_MPU6000}, + { } +}; +MODULE_DEVICE_TABLE(acpi, inv_acpi_match); + +static struct spi_driver inv_mpu_driver = { + .probe = inv_mpu_probe, + .id_table = inv_mpu_id, + .driver = { + .of_match_table = inv_of_match, + .acpi_match_table = inv_acpi_match, + .name = "inv-mpu6000-spi", + .pm = pm_ptr(&inv_mpu_pmops), + }, +}; + +module_spi_driver(inv_mpu_driver); + +MODULE_AUTHOR("Adriana Reus "); +MODULE_DESCRIPTION("Invensense device MPU6000 driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_MPU6050"); diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c new file mode 100644 index 000000000..22c1ce66f --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* +* Copyright (C) 2012 Invensense, Inc. +*/ + +#include + +#include +#include + +#include "inv_mpu_iio.h" + +static unsigned int inv_scan_query_mpu6050(struct iio_dev *indio_dev) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + unsigned int mask; + + /* + * If the MPU6050 is just used as a trigger, then the scan mask + * is not allocated so we simply enable the temperature channel + * as a dummy and bail out. + */ + if (!indio_dev->active_scan_mask) { + st->chip_config.temp_fifo_enable = true; + return INV_MPU6050_SENSOR_TEMP; + } + + st->chip_config.gyro_fifo_enable = + test_bit(INV_MPU6050_SCAN_GYRO_X, + indio_dev->active_scan_mask) || + test_bit(INV_MPU6050_SCAN_GYRO_Y, + indio_dev->active_scan_mask) || + test_bit(INV_MPU6050_SCAN_GYRO_Z, + indio_dev->active_scan_mask); + + st->chip_config.accl_fifo_enable = + test_bit(INV_MPU6050_SCAN_ACCL_X, + indio_dev->active_scan_mask) || + test_bit(INV_MPU6050_SCAN_ACCL_Y, + indio_dev->active_scan_mask) || + test_bit(INV_MPU6050_SCAN_ACCL_Z, + indio_dev->active_scan_mask); + + st->chip_config.temp_fifo_enable = + test_bit(INV_MPU6050_SCAN_TEMP, indio_dev->active_scan_mask); + + mask = 0; + if (st->chip_config.gyro_fifo_enable) + mask |= INV_MPU6050_SENSOR_GYRO; + if (st->chip_config.accl_fifo_enable) + mask |= INV_MPU6050_SENSOR_ACCL; + if (st->chip_config.temp_fifo_enable) + mask |= INV_MPU6050_SENSOR_TEMP; + + return mask; +} + +static unsigned int inv_scan_query_mpu9x50(struct iio_dev *indio_dev) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + unsigned int mask; + + mask = inv_scan_query_mpu6050(indio_dev); + + /* no magnetometer if i2c auxiliary bus is used */ + if (st->magn_disabled) + return mask; + + st->chip_config.magn_fifo_enable = + test_bit(INV_MPU9X50_SCAN_MAGN_X, + indio_dev->active_scan_mask) || + test_bit(INV_MPU9X50_SCAN_MAGN_Y, + indio_dev->active_scan_mask) || + test_bit(INV_MPU9X50_SCAN_MAGN_Z, + indio_dev->active_scan_mask); + if (st->chip_config.magn_fifo_enable) + mask |= INV_MPU6050_SENSOR_MAGN; + + return mask; +} + +static unsigned int inv_scan_query(struct iio_dev *indio_dev) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + switch (st->chip_type) { + case INV_MPU9150: + case INV_MPU9250: + case INV_MPU9255: + return inv_scan_query_mpu9x50(indio_dev); + default: + return inv_scan_query_mpu6050(indio_dev); + } +} + +static unsigned int inv_compute_skip_samples(const struct inv_mpu6050_state *st) +{ + unsigned int skip_samples = 0; + + /* mag first sample is always not ready, skip it */ + if (st->chip_config.magn_fifo_enable) + skip_samples = 1; + + return skip_samples; +} + +int inv_mpu6050_prepare_fifo(struct inv_mpu6050_state *st, bool enable) +{ + uint8_t d; + int ret; + + if (enable) { + /* reset timestamping */ + inv_sensors_timestamp_reset(&st->timestamp); + /* reset FIFO */ + d = st->chip_config.user_ctrl | INV_MPU6050_BIT_FIFO_RST; + ret = regmap_write(st->map, st->reg->user_ctrl, d); + if (ret) + return ret; + /* enable sensor output to FIFO */ + d = 0; + if (st->chip_config.gyro_fifo_enable) + d |= INV_MPU6050_BITS_GYRO_OUT; + if (st->chip_config.accl_fifo_enable) + d |= INV_MPU6050_BIT_ACCEL_OUT; + if (st->chip_config.temp_fifo_enable) + d |= INV_MPU6050_BIT_TEMP_OUT; + if (st->chip_config.magn_fifo_enable) + d |= INV_MPU6050_BIT_SLAVE_0; + ret = regmap_write(st->map, st->reg->fifo_en, d); + if (ret) + return ret; + /* enable FIFO reading */ + d = st->chip_config.user_ctrl | INV_MPU6050_BIT_FIFO_EN; + ret = regmap_write(st->map, st->reg->user_ctrl, d); + if (ret) + return ret; + /* enable data interrupt */ + ret = regmap_update_bits(st->map, st->reg->int_enable, + INV_MPU6050_BIT_DATA_RDY_EN, INV_MPU6050_BIT_DATA_RDY_EN); + } else { + /* disable data interrupt */ + ret = regmap_update_bits(st->map, st->reg->int_enable, + INV_MPU6050_BIT_DATA_RDY_EN, 0); + if (ret) + return ret; + ret = regmap_write(st->map, st->reg->fifo_en, 0); + if (ret) + return ret; + /* restore user_ctrl for disabling FIFO reading */ + ret = regmap_write(st->map, st->reg->user_ctrl, + st->chip_config.user_ctrl); + } + + return ret; +} + +/** + * inv_mpu6050_set_enable() - enable chip functions. + * @indio_dev: Device driver instance. + * @enable: enable/disable + */ +static int inv_mpu6050_set_enable(struct iio_dev *indio_dev, bool enable) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + struct device *pdev = regmap_get_device(st->map); + unsigned int scan; + int result; + + if (enable) { + scan = inv_scan_query(indio_dev); + result = pm_runtime_resume_and_get(pdev); + if (result) + return result; + /* + * In case autosuspend didn't trigger, turn off first not + * required sensors excepted WoM + */ + result = inv_mpu6050_switch_engine(st, false, ~scan & ~INV_MPU6050_SENSOR_WOM); + if (result) + goto error_power_off; + result = inv_mpu6050_switch_engine(st, true, scan); + if (result) + goto error_power_off; + st->skip_samples = inv_compute_skip_samples(st); + result = inv_mpu6050_prepare_fifo(st, true); + if (result) + goto error_power_off; + } else { + st->chip_config.gyro_fifo_enable = 0; + st->chip_config.accl_fifo_enable = 0; + st->chip_config.temp_fifo_enable = 0; + st->chip_config.magn_fifo_enable = 0; + result = inv_mpu6050_prepare_fifo(st, false); + if (result) + goto error_power_off; + pm_runtime_put_autosuspend(pdev); + } + + return 0; + +error_power_off: + pm_runtime_put_autosuspend(pdev); + return result; +} + +/** + * inv_mpu_data_rdy_trigger_set_state() - set data ready interrupt state + * @trig: Trigger instance + * @state: Desired trigger state + */ +static int inv_mpu_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct inv_mpu6050_state *st = iio_priv(indio_dev); + int result; + + mutex_lock(&st->lock); + result = inv_mpu6050_set_enable(indio_dev, state); + mutex_unlock(&st->lock); + + return result; +} + +static const struct iio_trigger_ops inv_mpu_trigger_ops = { + .set_trigger_state = &inv_mpu_data_rdy_trigger_set_state, +}; + +static irqreturn_t inv_mpu6050_interrupt_timestamp(int irq, void *p) +{ + struct iio_dev *indio_dev = p; + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + st->it_timestamp = iio_get_time_ns(indio_dev); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t inv_mpu6050_interrupt_handle(int irq, void *p) +{ + struct iio_dev *indio_dev = p; + struct inv_mpu6050_state *st = iio_priv(indio_dev); + unsigned int int_status, wom_bits; + u64 ev_code; + int result; + + switch (st->chip_type) { + case INV_MPU6000: + case INV_MPU6050: + /* + * WoM is not supported and interrupt status read seems to be broken for + * some chips. Since data ready is the only interrupt, bypass interrupt + * status read and always assert data ready bit. + */ + wom_bits = 0; + int_status = INV_MPU6050_BIT_RAW_DATA_RDY_INT; + goto data_ready_interrupt; + case INV_MPU9150: + /* IRQ needs to be acked */ + wom_bits = 0; + break; + case INV_MPU6500: + case INV_MPU6515: + case INV_MPU6880: + case INV_MPU9250: + case INV_MPU9255: + wom_bits = INV_MPU6500_BIT_WOM_INT; + break; + default: + wom_bits = INV_ICM20608_BIT_WOM_INT; + break; + } + + scoped_guard(mutex, &st->lock) { + /* ack interrupt and check status */ + result = regmap_read(st->map, st->reg->int_status, &int_status); + if (result) { + dev_err(regmap_get_device(st->map), "failed to ack interrupt\n"); + return IRQ_HANDLED; + } + + /* handle WoM event */ + if (st->chip_config.wom_en && (int_status & wom_bits)) { + ev_code = IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_ROC, IIO_EV_DIR_RISING); + iio_push_event(indio_dev, ev_code, st->it_timestamp); + } + } + +data_ready_interrupt: + /* handle raw data interrupt */ + if (int_status & INV_MPU6050_BIT_RAW_DATA_RDY_INT) { + indio_dev->pollfunc->timestamp = st->it_timestamp; + iio_trigger_poll_nested(st->trig); + } + + return IRQ_HANDLED; +} + +int inv_mpu6050_probe_trigger(struct iio_dev *indio_dev, int irq_type) +{ + int ret; + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + st->trig = devm_iio_trigger_alloc(&indio_dev->dev, + "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!st->trig) + return -ENOMEM; + + irq_type |= IRQF_ONESHOT; + ret = devm_request_threaded_irq(&indio_dev->dev, st->irq, + &inv_mpu6050_interrupt_timestamp, + &inv_mpu6050_interrupt_handle, + irq_type, "inv_mpu", indio_dev); + if (ret) + return ret; + + st->trig->dev.parent = regmap_get_device(st->map); + st->trig->ops = &inv_mpu_trigger_ops; + iio_trigger_set_drvdata(st->trig, indio_dev); + + ret = devm_iio_trigger_register(&indio_dev->dev, st->trig); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(st->trig); + + return 0; +} diff --git a/drivers/iio/imu/kmx61.c b/drivers/iio/imu/kmx61.c new file mode 100644 index 000000000..c288437d5 --- /dev/null +++ b/drivers/iio/imu/kmx61.c @@ -0,0 +1,1508 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * KMX61 - Kionix 6-axis Accelerometer/Magnetometer + * + * Copyright (c) 2014, Intel Corporation. + * + * IIO driver for KMX61 (7-bit I2C slave address 0x0E or 0x0F). + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define KMX61_REG_WHO_AM_I 0x00 +#define KMX61_REG_INS1 0x01 +#define KMX61_REG_INS2 0x02 + +/* + * three 16-bit accelerometer output registers for X/Y/Z axis + * we use only XOUT_L as a base register, all other addresses + * can be obtained by applying an offset and are provided here + * only for clarity. + */ +#define KMX61_ACC_XOUT_L 0x0A +#define KMX61_ACC_XOUT_H 0x0B +#define KMX61_ACC_YOUT_L 0x0C +#define KMX61_ACC_YOUT_H 0x0D +#define KMX61_ACC_ZOUT_L 0x0E +#define KMX61_ACC_ZOUT_H 0x0F + +/* + * one 16-bit temperature output register + */ +#define KMX61_TEMP_L 0x10 +#define KMX61_TEMP_H 0x11 + +/* + * three 16-bit magnetometer output registers for X/Y/Z axis + */ +#define KMX61_MAG_XOUT_L 0x12 +#define KMX61_MAG_XOUT_H 0x13 +#define KMX61_MAG_YOUT_L 0x14 +#define KMX61_MAG_YOUT_H 0x15 +#define KMX61_MAG_ZOUT_L 0x16 +#define KMX61_MAG_ZOUT_H 0x17 + +#define KMX61_REG_INL 0x28 +#define KMX61_REG_STBY 0x29 +#define KMX61_REG_CTRL1 0x2A +#define KMX61_REG_CTRL2 0x2B +#define KMX61_REG_ODCNTL 0x2C +#define KMX61_REG_INC1 0x2D + +#define KMX61_REG_WUF_THRESH 0x3D +#define KMX61_REG_WUF_TIMER 0x3E + +#define KMX61_ACC_STBY_BIT BIT(0) +#define KMX61_MAG_STBY_BIT BIT(1) +#define KMX61_ACT_STBY_BIT BIT(7) + +#define KMX61_ALL_STBY (KMX61_ACC_STBY_BIT | KMX61_MAG_STBY_BIT) + +#define KMX61_REG_INS1_BIT_WUFS BIT(1) + +#define KMX61_REG_INS2_BIT_ZP BIT(0) +#define KMX61_REG_INS2_BIT_ZN BIT(1) +#define KMX61_REG_INS2_BIT_YP BIT(2) +#define KMX61_REG_INS2_BIT_YN BIT(3) +#define KMX61_REG_INS2_BIT_XP BIT(4) +#define KMX61_REG_INS2_BIT_XN BIT(5) + +#define KMX61_REG_CTRL1_GSEL_MASK 0x03 + +#define KMX61_REG_CTRL1_BIT_RES BIT(4) +#define KMX61_REG_CTRL1_BIT_DRDYE BIT(5) +#define KMX61_REG_CTRL1_BIT_WUFE BIT(6) +#define KMX61_REG_CTRL1_BIT_BTSE BIT(7) + +#define KMX61_REG_INC1_BIT_WUFS BIT(0) +#define KMX61_REG_INC1_BIT_DRDYM BIT(1) +#define KMX61_REG_INC1_BIT_DRDYA BIT(2) +#define KMX61_REG_INC1_BIT_IEN BIT(5) + +#define KMX61_ACC_ODR_SHIFT 0 +#define KMX61_MAG_ODR_SHIFT 4 +#define KMX61_ACC_ODR_MASK 0x0F +#define KMX61_MAG_ODR_MASK 0xF0 + +#define KMX61_OWUF_MASK 0x7 + +#define KMX61_DEFAULT_WAKE_THRESH 1 +#define KMX61_DEFAULT_WAKE_DURATION 1 + +#define KMX61_SLEEP_DELAY_MS 2000 + +#define KMX61_CHIP_ID 0x12 + +/* KMX61 devices */ +#define KMX61_ACC 0x01 +#define KMX61_MAG 0x02 + +struct kmx61_data { + struct i2c_client *client; + + /* serialize access to non-atomic ops, e.g set_mode */ + struct mutex lock; + + /* standby state */ + bool acc_stby; + bool mag_stby; + + /* power state */ + bool acc_ps; + bool mag_ps; + + /* config bits */ + u8 range; + u8 odr_bits; + u8 wake_thresh; + u8 wake_duration; + + /* accelerometer specific data */ + struct iio_dev *acc_indio_dev; + struct iio_trigger *acc_dready_trig; + struct iio_trigger *motion_trig; + bool acc_dready_trig_on; + bool motion_trig_on; + bool ev_enable_state; + + /* magnetometer specific data */ + struct iio_dev *mag_indio_dev; + struct iio_trigger *mag_dready_trig; + bool mag_dready_trig_on; +}; + +enum kmx61_range { + KMX61_RANGE_2G, + KMX61_RANGE_4G, + KMX61_RANGE_8G, +}; + +enum kmx61_axis { + KMX61_AXIS_X, + KMX61_AXIS_Y, + KMX61_AXIS_Z, +}; + +static const u16 kmx61_uscale_table[] = {9582, 19163, 38326}; + +static const struct { + int val; + int val2; +} kmx61_samp_freq_table[] = { {12, 500000}, + {25, 0}, + {50, 0}, + {100, 0}, + {200, 0}, + {400, 0}, + {800, 0}, + {1600, 0}, + {0, 781000}, + {1, 563000}, + {3, 125000}, + {6, 250000} }; + +static const struct { + int val; + int val2; + int odr_bits; +} kmx61_wake_up_odr_table[] = { {0, 781000, 0x00}, + {1, 563000, 0x01}, + {3, 125000, 0x02}, + {6, 250000, 0x03}, + {12, 500000, 0x04}, + {25, 0, 0x05}, + {50, 0, 0x06}, + {100, 0, 0x06}, + {200, 0, 0x06}, + {400, 0, 0x06}, + {800, 0, 0x06}, + {1600, 0, 0x06} }; + +static IIO_CONST_ATTR(accel_scale_available, "0.009582 0.019163 0.038326"); +static IIO_CONST_ATTR(magn_scale_available, "0.001465"); +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL( + "0.781000 1.563000 3.125000 6.250000 12.500000 25 50 100 200 400 800"); + +static struct attribute *kmx61_acc_attributes[] = { + &iio_const_attr_accel_scale_available.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static struct attribute *kmx61_mag_attributes[] = { + &iio_const_attr_magn_scale_available.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group kmx61_acc_attribute_group = { + .attrs = kmx61_acc_attributes, +}; + +static const struct attribute_group kmx61_mag_attribute_group = { + .attrs = kmx61_mag_attributes, +}; + +static const struct iio_event_spec kmx61_event = { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_PERIOD), +}; + +#define KMX61_ACC_CHAN(_axis) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## _axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = KMX61_ACC, \ + .scan_index = KMX61_AXIS_ ## _axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 12, \ + .storagebits = 16, \ + .shift = 4, \ + .endianness = IIO_LE, \ + }, \ + .event_spec = &kmx61_event, \ + .num_event_specs = 1 \ +} + +#define KMX61_MAG_CHAN(_axis) { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## _axis, \ + .address = KMX61_MAG, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = KMX61_AXIS_ ## _axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 14, \ + .storagebits = 16, \ + .shift = 2, \ + .endianness = IIO_LE, \ + }, \ +} + +static const struct iio_chan_spec kmx61_acc_channels[] = { + KMX61_ACC_CHAN(X), + KMX61_ACC_CHAN(Y), + KMX61_ACC_CHAN(Z), +}; + +static const struct iio_chan_spec kmx61_mag_channels[] = { + KMX61_MAG_CHAN(X), + KMX61_MAG_CHAN(Y), + KMX61_MAG_CHAN(Z), +}; + +static void kmx61_set_data(struct iio_dev *indio_dev, struct kmx61_data *data) +{ + struct kmx61_data **priv = iio_priv(indio_dev); + + *priv = data; +} + +static struct kmx61_data *kmx61_get_data(struct iio_dev *indio_dev) +{ + return *(struct kmx61_data **)iio_priv(indio_dev); +} + +static int kmx61_convert_freq_to_bit(int val, int val2) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(kmx61_samp_freq_table); i++) + if (val == kmx61_samp_freq_table[i].val && + val2 == kmx61_samp_freq_table[i].val2) + return i; + return -EINVAL; +} + +static int kmx61_convert_wake_up_odr_to_bit(int val, int val2) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(kmx61_wake_up_odr_table); ++i) + if (kmx61_wake_up_odr_table[i].val == val && + kmx61_wake_up_odr_table[i].val2 == val2) + return kmx61_wake_up_odr_table[i].odr_bits; + return -EINVAL; +} + +/** + * kmx61_set_mode() - set KMX61 device operating mode + * @data: kmx61 device private data pointer + * @mode: bitmask, indicating operating mode for @device + * @device: bitmask, indicating device for which @mode needs to be set + * @update: update stby bits stored in device's private @data + * + * For each sensor (accelerometer/magnetometer) there are two operating modes + * STANDBY and OPERATION. Neither accel nor magn can be disabled independently + * if they are both enabled. Internal sensors state is saved in acc_stby and + * mag_stby members of driver's private @data. + */ +static int kmx61_set_mode(struct kmx61_data *data, u8 mode, u8 device, + bool update) +{ + int ret; + int acc_stby = -1, mag_stby = -1; + + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_STBY); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_stby\n"); + return ret; + } + if (device & KMX61_ACC) { + if (mode & KMX61_ACC_STBY_BIT) { + ret |= KMX61_ACC_STBY_BIT; + acc_stby = 1; + } else { + ret &= ~KMX61_ACC_STBY_BIT; + acc_stby = 0; + } + } + + if (device & KMX61_MAG) { + if (mode & KMX61_MAG_STBY_BIT) { + ret |= KMX61_MAG_STBY_BIT; + mag_stby = 1; + } else { + ret &= ~KMX61_MAG_STBY_BIT; + mag_stby = 0; + } + } + + if (mode & KMX61_ACT_STBY_BIT) + ret |= KMX61_ACT_STBY_BIT; + + ret = i2c_smbus_write_byte_data(data->client, KMX61_REG_STBY, ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_stby\n"); + return ret; + } + + if (acc_stby != -1 && update) + data->acc_stby = acc_stby; + if (mag_stby != -1 && update) + data->mag_stby = mag_stby; + + return 0; +} + +static int kmx61_get_mode(struct kmx61_data *data, u8 *mode, u8 device) +{ + int ret; + + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_STBY); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_stby\n"); + return ret; + } + *mode = 0; + + if (device & KMX61_ACC) { + if (ret & KMX61_ACC_STBY_BIT) + *mode |= KMX61_ACC_STBY_BIT; + else + *mode &= ~KMX61_ACC_STBY_BIT; + } + + if (device & KMX61_MAG) { + if (ret & KMX61_MAG_STBY_BIT) + *mode |= KMX61_MAG_STBY_BIT; + else + *mode &= ~KMX61_MAG_STBY_BIT; + } + + return 0; +} + +static int kmx61_set_wake_up_odr(struct kmx61_data *data, int val, int val2) +{ + int ret, odr_bits; + + odr_bits = kmx61_convert_wake_up_odr_to_bit(val, val2); + if (odr_bits < 0) + return odr_bits; + + ret = i2c_smbus_write_byte_data(data->client, KMX61_REG_CTRL2, + odr_bits); + if (ret < 0) + dev_err(&data->client->dev, "Error writing reg_ctrl2\n"); + return ret; +} + +static int kmx61_set_odr(struct kmx61_data *data, int val, int val2, u8 device) +{ + int ret; + u8 mode; + int lodr_bits, odr_bits; + + ret = kmx61_get_mode(data, &mode, KMX61_ACC | KMX61_MAG); + if (ret < 0) + return ret; + + lodr_bits = kmx61_convert_freq_to_bit(val, val2); + if (lodr_bits < 0) + return lodr_bits; + + /* To change ODR, accel and magn must be in STDBY */ + ret = kmx61_set_mode(data, KMX61_ALL_STBY, KMX61_ACC | KMX61_MAG, + true); + if (ret < 0) + return ret; + + odr_bits = 0; + if (device & KMX61_ACC) + odr_bits |= lodr_bits << KMX61_ACC_ODR_SHIFT; + if (device & KMX61_MAG) + odr_bits |= lodr_bits << KMX61_MAG_ODR_SHIFT; + + ret = i2c_smbus_write_byte_data(data->client, KMX61_REG_ODCNTL, + odr_bits); + if (ret < 0) + return ret; + + data->odr_bits = odr_bits; + + if (device & KMX61_ACC) { + ret = kmx61_set_wake_up_odr(data, val, val2); + if (ret) + return ret; + } + + return kmx61_set_mode(data, mode, KMX61_ACC | KMX61_MAG, true); +} + +static int kmx61_get_odr(struct kmx61_data *data, int *val, int *val2, + u8 device) +{ + u8 lodr_bits; + + if (device & KMX61_ACC) + lodr_bits = (data->odr_bits >> KMX61_ACC_ODR_SHIFT) & + KMX61_ACC_ODR_MASK; + else if (device & KMX61_MAG) + lodr_bits = (data->odr_bits >> KMX61_MAG_ODR_SHIFT) & + KMX61_MAG_ODR_MASK; + else + return -EINVAL; + + if (lodr_bits >= ARRAY_SIZE(kmx61_samp_freq_table)) + return -EINVAL; + + *val = kmx61_samp_freq_table[lodr_bits].val; + *val2 = kmx61_samp_freq_table[lodr_bits].val2; + + return 0; +} + +static int kmx61_set_range(struct kmx61_data *data, u8 range) +{ + int ret; + + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_CTRL1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_ctrl1\n"); + return ret; + } + + ret &= ~KMX61_REG_CTRL1_GSEL_MASK; + ret |= range & KMX61_REG_CTRL1_GSEL_MASK; + + ret = i2c_smbus_write_byte_data(data->client, KMX61_REG_CTRL1, ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_ctrl1\n"); + return ret; + } + + data->range = range; + + return 0; +} + +static int kmx61_set_scale(struct kmx61_data *data, u16 uscale) +{ + int ret, i; + u8 mode; + + for (i = 0; i < ARRAY_SIZE(kmx61_uscale_table); i++) { + if (kmx61_uscale_table[i] == uscale) { + ret = kmx61_get_mode(data, &mode, + KMX61_ACC | KMX61_MAG); + if (ret < 0) + return ret; + + ret = kmx61_set_mode(data, KMX61_ALL_STBY, + KMX61_ACC | KMX61_MAG, true); + if (ret < 0) + return ret; + + ret = kmx61_set_range(data, i); + if (ret < 0) + return ret; + + return kmx61_set_mode(data, mode, + KMX61_ACC | KMX61_MAG, true); + } + } + return -EINVAL; +} + +static int kmx61_chip_init(struct kmx61_data *data) +{ + int ret, val, val2; + + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_WHO_AM_I); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading who_am_i\n"); + return ret; + } + + if (ret != KMX61_CHIP_ID) { + dev_err(&data->client->dev, + "Wrong chip id, got %x expected %x\n", + ret, KMX61_CHIP_ID); + return -EINVAL; + } + + /* set accel 12bit, 4g range */ + ret = kmx61_set_range(data, KMX61_RANGE_4G); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_ODCNTL); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_odcntl\n"); + return ret; + } + data->odr_bits = ret; + + /* + * set output data rate for wake up (motion detection) function + * to match data rate for accelerometer sampling + */ + ret = kmx61_get_odr(data, &val, &val2, KMX61_ACC); + if (ret < 0) + return ret; + + ret = kmx61_set_wake_up_odr(data, val, val2); + if (ret < 0) + return ret; + + /* set acc/magn to OPERATION mode */ + ret = kmx61_set_mode(data, 0, KMX61_ACC | KMX61_MAG, true); + if (ret < 0) + return ret; + + data->wake_thresh = KMX61_DEFAULT_WAKE_THRESH; + data->wake_duration = KMX61_DEFAULT_WAKE_DURATION; + + return 0; +} + +static int kmx61_setup_new_data_interrupt(struct kmx61_data *data, + bool status, u8 device) +{ + u8 mode; + int ret; + + ret = kmx61_get_mode(data, &mode, KMX61_ACC | KMX61_MAG); + if (ret < 0) + return ret; + + ret = kmx61_set_mode(data, KMX61_ALL_STBY, KMX61_ACC | KMX61_MAG, true); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_INC1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_ctrl1\n"); + return ret; + } + + if (status) { + ret |= KMX61_REG_INC1_BIT_IEN; + if (device & KMX61_ACC) + ret |= KMX61_REG_INC1_BIT_DRDYA; + if (device & KMX61_MAG) + ret |= KMX61_REG_INC1_BIT_DRDYM; + } else { + ret &= ~KMX61_REG_INC1_BIT_IEN; + if (device & KMX61_ACC) + ret &= ~KMX61_REG_INC1_BIT_DRDYA; + if (device & KMX61_MAG) + ret &= ~KMX61_REG_INC1_BIT_DRDYM; + } + ret = i2c_smbus_write_byte_data(data->client, KMX61_REG_INC1, ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_int_ctrl1\n"); + return ret; + } + + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_CTRL1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_ctrl1\n"); + return ret; + } + + if (status) + ret |= KMX61_REG_CTRL1_BIT_DRDYE; + else + ret &= ~KMX61_REG_CTRL1_BIT_DRDYE; + + ret = i2c_smbus_write_byte_data(data->client, KMX61_REG_CTRL1, ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_ctrl1\n"); + return ret; + } + + return kmx61_set_mode(data, mode, KMX61_ACC | KMX61_MAG, true); +} + +static int kmx61_chip_update_thresholds(struct kmx61_data *data) +{ + int ret; + + ret = i2c_smbus_write_byte_data(data->client, + KMX61_REG_WUF_TIMER, + data->wake_duration); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_wuf_timer\n"); + return ret; + } + + ret = i2c_smbus_write_byte_data(data->client, + KMX61_REG_WUF_THRESH, + data->wake_thresh); + if (ret < 0) + dev_err(&data->client->dev, "Error writing reg_wuf_thresh\n"); + + return ret; +} + +static int kmx61_setup_any_motion_interrupt(struct kmx61_data *data, + bool status) +{ + u8 mode; + int ret; + + ret = kmx61_get_mode(data, &mode, KMX61_ACC | KMX61_MAG); + if (ret < 0) + return ret; + + ret = kmx61_set_mode(data, KMX61_ALL_STBY, KMX61_ACC | KMX61_MAG, true); + if (ret < 0) + return ret; + + ret = kmx61_chip_update_thresholds(data); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_INC1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_inc1\n"); + return ret; + } + if (status) + ret |= (KMX61_REG_INC1_BIT_IEN | KMX61_REG_INC1_BIT_WUFS); + else + ret &= ~(KMX61_REG_INC1_BIT_IEN | KMX61_REG_INC1_BIT_WUFS); + + ret = i2c_smbus_write_byte_data(data->client, KMX61_REG_INC1, ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_inc1\n"); + return ret; + } + + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_CTRL1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_ctrl1\n"); + return ret; + } + + if (status) + ret |= KMX61_REG_CTRL1_BIT_WUFE | KMX61_REG_CTRL1_BIT_BTSE; + else + ret &= ~(KMX61_REG_CTRL1_BIT_WUFE | KMX61_REG_CTRL1_BIT_BTSE); + + ret = i2c_smbus_write_byte_data(data->client, KMX61_REG_CTRL1, ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_ctrl1\n"); + return ret; + } + mode |= KMX61_ACT_STBY_BIT; + return kmx61_set_mode(data, mode, KMX61_ACC | KMX61_MAG, true); +} + +/** + * kmx61_set_power_state() - set power state for kmx61 @device + * @data: kmx61 device private pointer + * @on: power state to be set for @device + * @device: bitmask indicating device for which @on state needs to be set + * + * Notice that when ACC power state needs to be set to ON and MAG is in + * OPERATION then we know that kmx61_runtime_resume was already called + * so we must set ACC OPERATION mode here. The same happens when MAG power + * state needs to be set to ON and ACC is in OPERATION. + */ +static int kmx61_set_power_state(struct kmx61_data *data, bool on, u8 device) +{ +#ifdef CONFIG_PM + int ret; + + if (device & KMX61_ACC) { + if (on && !data->acc_ps && !data->mag_stby) { + ret = kmx61_set_mode(data, 0, KMX61_ACC, true); + if (ret < 0) + return ret; + } + data->acc_ps = on; + } + if (device & KMX61_MAG) { + if (on && !data->mag_ps && !data->acc_stby) { + ret = kmx61_set_mode(data, 0, KMX61_MAG, true); + if (ret < 0) + return ret; + } + data->mag_ps = on; + } + + if (on) + ret = pm_runtime_resume_and_get(&data->client->dev); + else + ret = pm_runtime_put_autosuspend(&data->client->dev); + if (ret < 0) { + dev_err(&data->client->dev, + "Failed: kmx61_set_power_state for %d, ret %d\n", + on, ret); + + return ret; + } +#endif + return 0; +} + +static int kmx61_read_measurement(struct kmx61_data *data, u8 base, u8 offset) +{ + int ret; + u8 reg = base + offset * 2; + + ret = i2c_smbus_read_word_data(data->client, reg); + if (ret < 0) + dev_err(&data->client->dev, "failed to read reg at %x\n", reg); + + return ret; +} + +static int kmx61_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int ret; + u8 base_reg; + struct kmx61_data *data = kmx61_get_data(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + switch (chan->type) { + case IIO_ACCEL: + base_reg = KMX61_ACC_XOUT_L; + break; + case IIO_MAGN: + base_reg = KMX61_MAG_XOUT_L; + break; + default: + return -EINVAL; + } + guard(mutex)(&data->lock); + + ret = kmx61_set_power_state(data, true, chan->address); + if (ret) + return ret; + + ret = kmx61_read_measurement(data, base_reg, chan->scan_index); + if (ret < 0) { + kmx61_set_power_state(data, false, chan->address); + return ret; + } + *val = sign_extend32(ret >> chan->scan_type.shift, + chan->scan_type.realbits - 1); + ret = kmx61_set_power_state(data, false, chan->address); + if (ret) + return ret; + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ACCEL: + *val = 0; + *val2 = kmx61_uscale_table[data->range]; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_MAGN: + /* 14 bits res, 1465 microGauss per magn count */ + *val = 0; + *val2 = 1465; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: { + if (chan->type != IIO_ACCEL && chan->type != IIO_MAGN) + return -EINVAL; + + guard(mutex)(&data->lock); + + ret = kmx61_get_odr(data, val, val2, chan->address); + if (ret) + return -EINVAL; + return IIO_VAL_INT_PLUS_MICRO; + } + default: + return -EINVAL; + } +} + +static int kmx61_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct kmx61_data *data = kmx61_get_data(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: { + if (chan->type != IIO_ACCEL && chan->type != IIO_MAGN) + return -EINVAL; + + guard(mutex)(&data->lock); + + return kmx61_set_odr(data, val, val2, chan->address); + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ACCEL: { + if (val != 0) + return -EINVAL; + guard(mutex)(&data->lock); + + return kmx61_set_scale(data, val2); + } + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int kmx61_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct kmx61_data *data = kmx61_get_data(indio_dev); + + *val2 = 0; + switch (info) { + case IIO_EV_INFO_VALUE: + *val = data->wake_thresh; + return IIO_VAL_INT; + case IIO_EV_INFO_PERIOD: + *val = data->wake_duration; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int kmx61_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct kmx61_data *data = kmx61_get_data(indio_dev); + + if (data->ev_enable_state) + return -EBUSY; + + switch (info) { + case IIO_EV_INFO_VALUE: + data->wake_thresh = val; + return IIO_VAL_INT; + case IIO_EV_INFO_PERIOD: + data->wake_duration = val; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int kmx61_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct kmx61_data *data = kmx61_get_data(indio_dev); + + return data->ev_enable_state; +} + +static int kmx61_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct kmx61_data *data = kmx61_get_data(indio_dev); + int ret = 0; + + if (state && data->ev_enable_state) + return 0; + + guard(mutex)(&data->lock); + + if (!state && data->motion_trig_on) { + data->ev_enable_state = false; + return ret; + } + + ret = kmx61_set_power_state(data, state, KMX61_ACC); + if (ret < 0) + return ret; + + ret = kmx61_setup_any_motion_interrupt(data, state); + if (ret < 0) { + kmx61_set_power_state(data, false, KMX61_ACC); + return ret; + } + + data->ev_enable_state = state; + + return 0; +} + +static int kmx61_acc_validate_trigger(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + struct kmx61_data *data = kmx61_get_data(indio_dev); + + if (data->acc_dready_trig != trig && data->motion_trig != trig) + return -EINVAL; + + return 0; +} + +static int kmx61_mag_validate_trigger(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + struct kmx61_data *data = kmx61_get_data(indio_dev); + + if (data->mag_dready_trig != trig) + return -EINVAL; + + return 0; +} + +static const struct iio_info kmx61_acc_info = { + .read_raw = kmx61_read_raw, + .write_raw = kmx61_write_raw, + .attrs = &kmx61_acc_attribute_group, + .read_event_value = kmx61_read_event, + .write_event_value = kmx61_write_event, + .read_event_config = kmx61_read_event_config, + .write_event_config = kmx61_write_event_config, + .validate_trigger = kmx61_acc_validate_trigger, +}; + +static const struct iio_info kmx61_mag_info = { + .read_raw = kmx61_read_raw, + .write_raw = kmx61_write_raw, + .attrs = &kmx61_mag_attribute_group, + .validate_trigger = kmx61_mag_validate_trigger, +}; + + +static int kmx61_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + int ret = 0; + u8 device; + + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct kmx61_data *data = kmx61_get_data(indio_dev); + + guard(mutex)(&data->lock); + + if (!state && data->ev_enable_state && data->motion_trig_on) { + data->motion_trig_on = false; + return ret; + } + + if (data->acc_dready_trig == trig || data->motion_trig == trig) + device = KMX61_ACC; + else + device = KMX61_MAG; + + ret = kmx61_set_power_state(data, state, device); + if (ret < 0) + return ret; + + if (data->acc_dready_trig == trig || data->mag_dready_trig == trig) + ret = kmx61_setup_new_data_interrupt(data, state, device); + else + ret = kmx61_setup_any_motion_interrupt(data, state); + if (ret < 0) { + kmx61_set_power_state(data, false, device); + return ret; + } + + if (data->acc_dready_trig == trig) + data->acc_dready_trig_on = state; + else if (data->mag_dready_trig == trig) + data->mag_dready_trig_on = state; + else + data->motion_trig_on = state; + + return 0; +} + +static void kmx61_trig_reenable(struct iio_trigger *trig) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct kmx61_data *data = kmx61_get_data(indio_dev); + int ret; + + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_INL); + if (ret < 0) + dev_err(&data->client->dev, "Error reading reg_inl\n"); +} + +static const struct iio_trigger_ops kmx61_trigger_ops = { + .set_trigger_state = kmx61_data_rdy_trigger_set_state, + .reenable = kmx61_trig_reenable, +}; + +static irqreturn_t kmx61_event_handler(int irq, void *private) +{ + struct kmx61_data *data = private; + struct iio_dev *indio_dev = data->acc_indio_dev; + int ret; + + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_INS1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_ins1\n"); + goto ack_intr; + } + + if (ret & KMX61_REG_INS1_BIT_WUFS) { + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_INS2); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_ins2\n"); + goto ack_intr; + } + + if (ret & KMX61_REG_INS2_BIT_XN) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_X, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + 0); + + if (ret & KMX61_REG_INS2_BIT_XP) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_X, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + 0); + + if (ret & KMX61_REG_INS2_BIT_YN) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Y, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + 0); + + if (ret & KMX61_REG_INS2_BIT_YP) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Y, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + 0); + + if (ret & KMX61_REG_INS2_BIT_ZN) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Z, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + 0); + + if (ret & KMX61_REG_INS2_BIT_ZP) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Z, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + 0); + } + +ack_intr: + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_CTRL1); + if (ret < 0) + dev_err(&data->client->dev, "Error reading reg_ctrl1\n"); + + ret |= KMX61_REG_CTRL1_BIT_RES; + ret = i2c_smbus_write_byte_data(data->client, KMX61_REG_CTRL1, ret); + if (ret < 0) + dev_err(&data->client->dev, "Error writing reg_ctrl1\n"); + + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_INL); + if (ret < 0) + dev_err(&data->client->dev, "Error reading reg_inl\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t kmx61_data_rdy_trig_poll(int irq, void *private) +{ + struct kmx61_data *data = private; + + if (data->acc_dready_trig_on) + iio_trigger_poll(data->acc_dready_trig); + if (data->mag_dready_trig_on) + iio_trigger_poll(data->mag_dready_trig); + + if (data->motion_trig_on) + iio_trigger_poll(data->motion_trig); + + if (data->ev_enable_state) + return IRQ_WAKE_THREAD; + return IRQ_HANDLED; +} + +/** + * kmx61_read_for_each_active_channel() - Read each active channel into a buffer + * + * @indio_dev: IIO Device struct to read from + * @buffer: Destination buffer to write to, the array must be of at least size 8 + * + * Return: + * 0 on success, negative errno on failure. + */ +static int kmx61_read_for_each_active_channel(struct iio_dev *indio_dev, s16 *buffer) +{ + struct kmx61_data *data = kmx61_get_data(indio_dev); + u8 base; + int ret, bit; + int i = 0; + + if (indio_dev == data->acc_indio_dev) + base = KMX61_ACC_XOUT_L; + else + base = KMX61_MAG_XOUT_L; + + guard(mutex)(&data->lock); + + iio_for_each_active_channel(indio_dev, bit) { + ret = kmx61_read_measurement(data, base, bit); + if (ret < 0) + return ret; + buffer[i++] = ret; + } + + return 0; +} + +static irqreturn_t kmx61_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + int ret; + s16 buffer[8] = { }; + + ret = kmx61_read_for_each_active_channel(indio_dev, buffer); + if (ret < 0) + goto err; + + iio_push_to_buffers(indio_dev, buffer); +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static struct iio_dev *kmx61_indiodev_setup(struct kmx61_data *data, + const struct iio_info *info, + const struct iio_chan_spec *chan, + int num_channels, + const char *name) +{ + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(&data->client->dev, sizeof(data)); + if (!indio_dev) + return ERR_PTR(-ENOMEM); + + kmx61_set_data(indio_dev, data); + + indio_dev->channels = chan; + indio_dev->num_channels = num_channels; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = info; + + return indio_dev; +} + +static struct iio_trigger *kmx61_trigger_setup(struct kmx61_data *data, + struct iio_dev *indio_dev, + const char *tag) +{ + struct iio_trigger *trig; + int ret; + + trig = devm_iio_trigger_alloc(&data->client->dev, + "%s-%s-dev%d", + indio_dev->name, + tag, + iio_device_id(indio_dev)); + if (!trig) + return ERR_PTR(-ENOMEM); + + trig->ops = &kmx61_trigger_ops; + iio_trigger_set_drvdata(trig, indio_dev); + + ret = iio_trigger_register(trig); + if (ret) + return ERR_PTR(ret); + + return trig; +} + +static int kmx61_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + int ret; + struct kmx61_data *data; + const char *name = NULL; + + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + data->client = client; + + mutex_init(&data->lock); + + if (id) + name = id->name; + else + return -ENODEV; + + data->acc_indio_dev = + kmx61_indiodev_setup(data, &kmx61_acc_info, + kmx61_acc_channels, + ARRAY_SIZE(kmx61_acc_channels), + name); + if (IS_ERR(data->acc_indio_dev)) + return PTR_ERR(data->acc_indio_dev); + + data->mag_indio_dev = + kmx61_indiodev_setup(data, &kmx61_mag_info, + kmx61_mag_channels, + ARRAY_SIZE(kmx61_mag_channels), + name); + if (IS_ERR(data->mag_indio_dev)) + return PTR_ERR(data->mag_indio_dev); + + ret = kmx61_chip_init(data); + if (ret < 0) + return ret; + + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + kmx61_data_rdy_trig_poll, + kmx61_event_handler, + IRQF_TRIGGER_RISING, + "kmx61_event", + data); + if (ret) + goto err_chip_uninit; + + data->acc_dready_trig = + kmx61_trigger_setup(data, data->acc_indio_dev, + "dready"); + if (IS_ERR(data->acc_dready_trig)) { + ret = PTR_ERR(data->acc_dready_trig); + goto err_chip_uninit; + } + + data->mag_dready_trig = + kmx61_trigger_setup(data, data->mag_indio_dev, + "dready"); + if (IS_ERR(data->mag_dready_trig)) { + ret = PTR_ERR(data->mag_dready_trig); + goto err_trigger_unregister_acc_dready; + } + + data->motion_trig = + kmx61_trigger_setup(data, data->acc_indio_dev, + "any-motion"); + if (IS_ERR(data->motion_trig)) { + ret = PTR_ERR(data->motion_trig); + goto err_trigger_unregister_mag_dready; + } + + ret = iio_triggered_buffer_setup(data->acc_indio_dev, + &iio_pollfunc_store_time, + kmx61_trigger_handler, + NULL); + if (ret < 0) { + dev_err(&data->client->dev, + "Failed to setup acc triggered buffer\n"); + goto err_trigger_unregister_motion; + } + + ret = iio_triggered_buffer_setup(data->mag_indio_dev, + &iio_pollfunc_store_time, + kmx61_trigger_handler, + NULL); + if (ret < 0) { + dev_err(&data->client->dev, + "Failed to setup mag triggered buffer\n"); + goto err_buffer_cleanup_acc; + } + } + + ret = pm_runtime_set_active(&client->dev); + if (ret < 0) + goto err_buffer_cleanup_mag; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, KMX61_SLEEP_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + + ret = iio_device_register(data->acc_indio_dev); + if (ret < 0) { + dev_err(&client->dev, "Failed to register acc iio device\n"); + goto err_pm_cleanup; + } + + ret = iio_device_register(data->mag_indio_dev); + if (ret < 0) { + dev_err(&client->dev, "Failed to register mag iio device\n"); + goto err_iio_unregister_acc; + } + + return 0; + +err_iio_unregister_acc: + iio_device_unregister(data->acc_indio_dev); +err_pm_cleanup: + pm_runtime_dont_use_autosuspend(&client->dev); + pm_runtime_disable(&client->dev); +err_buffer_cleanup_mag: + if (client->irq > 0) + iio_triggered_buffer_cleanup(data->mag_indio_dev); +err_buffer_cleanup_acc: + if (client->irq > 0) + iio_triggered_buffer_cleanup(data->acc_indio_dev); +err_trigger_unregister_motion: + iio_trigger_unregister(data->motion_trig); +err_trigger_unregister_mag_dready: + iio_trigger_unregister(data->mag_dready_trig); +err_trigger_unregister_acc_dready: + iio_trigger_unregister(data->acc_dready_trig); +err_chip_uninit: + kmx61_set_mode(data, KMX61_ALL_STBY, KMX61_ACC | KMX61_MAG, true); + return ret; +} + +static void kmx61_remove(struct i2c_client *client) +{ + struct kmx61_data *data = i2c_get_clientdata(client); + + iio_device_unregister(data->acc_indio_dev); + iio_device_unregister(data->mag_indio_dev); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + if (client->irq > 0) { + iio_triggered_buffer_cleanup(data->acc_indio_dev); + iio_triggered_buffer_cleanup(data->mag_indio_dev); + iio_trigger_unregister(data->acc_dready_trig); + iio_trigger_unregister(data->mag_dready_trig); + iio_trigger_unregister(data->motion_trig); + } + + guard(mutex)(&data->lock); + + kmx61_set_mode(data, KMX61_ALL_STBY, KMX61_ACC | KMX61_MAG, true); +} + +static int kmx61_suspend(struct device *dev) +{ + struct kmx61_data *data = i2c_get_clientdata(to_i2c_client(dev)); + + guard(mutex)(&data->lock); + + return kmx61_set_mode(data, KMX61_ALL_STBY, KMX61_ACC | KMX61_MAG, false); +} + +static int kmx61_resume(struct device *dev) +{ + u8 stby = 0; + struct kmx61_data *data = i2c_get_clientdata(to_i2c_client(dev)); + + if (data->acc_stby) + stby |= KMX61_ACC_STBY_BIT; + if (data->mag_stby) + stby |= KMX61_MAG_STBY_BIT; + + return kmx61_set_mode(data, stby, KMX61_ACC | KMX61_MAG, true); +} + +static int kmx61_runtime_suspend(struct device *dev) +{ + struct kmx61_data *data = i2c_get_clientdata(to_i2c_client(dev)); + + guard(mutex)(&data->lock); + + return kmx61_set_mode(data, KMX61_ALL_STBY, KMX61_ACC | KMX61_MAG, true); +} + +static int kmx61_runtime_resume(struct device *dev) +{ + struct kmx61_data *data = i2c_get_clientdata(to_i2c_client(dev)); + u8 stby = 0; + + if (!data->acc_ps) + stby |= KMX61_ACC_STBY_BIT; + if (!data->mag_ps) + stby |= KMX61_MAG_STBY_BIT; + + return kmx61_set_mode(data, stby, KMX61_ACC | KMX61_MAG, true); +} + +static const struct dev_pm_ops kmx61_pm_ops = { + SYSTEM_SLEEP_PM_OPS(kmx61_suspend, kmx61_resume) + RUNTIME_PM_OPS(kmx61_runtime_suspend, kmx61_runtime_resume, NULL) +}; + +static const struct i2c_device_id kmx61_id[] = { + { .name = "kmx611021" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, kmx61_id); + +static struct i2c_driver kmx61_driver = { + .driver = { + .name = "kmx61", + .pm = pm_ptr(&kmx61_pm_ops), + }, + .probe = kmx61_probe, + .remove = kmx61_remove, + .id_table = kmx61_id, +}; + +module_i2c_driver(kmx61_driver); + +MODULE_AUTHOR("Daniel Baluta "); +MODULE_DESCRIPTION("KMX61 accelerometer/magnetometer driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/imu/smi240.c b/drivers/iio/imu/smi240.c new file mode 100644 index 000000000..f419296bb --- /dev/null +++ b/drivers/iio/imu/smi240.c @@ -0,0 +1,620 @@ +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 +/* + * Copyright (c) 2024 Robert Bosch GmbH. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define SMI240_CHIP_ID 0x0024 + +#define SMI240_SOFT_CONFIG_EOC_MASK BIT(0) +#define SMI240_SOFT_CONFIG_GYR_BW_MASK BIT(1) +#define SMI240_SOFT_CONFIG_ACC_BW_MASK BIT(2) +#define SMI240_SOFT_CONFIG_BITE_AUTO_MASK BIT(3) +#define SMI240_SOFT_CONFIG_BITE_REP_MASK GENMASK(6, 4) + +#define SMI240_CHIP_ID_REG 0x00 +#define SMI240_SOFT_CONFIG_REG 0x0A +#define SMI240_TEMP_CUR_REG 0x10 +#define SMI240_ACCEL_X_CUR_REG 0x11 +#define SMI240_GYRO_X_CUR_REG 0x14 +#define SMI240_DATA_CAP_FIRST_REG 0x17 +#define SMI240_CMD_REG 0x2F + +#define SMI240_SOFT_RESET_CMD 0xB6 + +#define SMI240_BITE_SEQUENCE_DELAY_US 140000 +#define SMI240_FILTER_FLUSH_DELAY_US 60000 +#define SMI240_DIGITAL_STARTUP_DELAY_US 120000 +#define SMI240_MECH_STARTUP_DELAY_US 100000 + +#define SMI240_BUS_ID 0x00 +#define SMI240_CRC_INIT 0x05 +#define SMI240_CRC_POLY 0x0B +#define SMI240_CRC_MASK GENMASK(2, 0) + +#define SMI240_READ_SD_BIT_MASK BIT(31) +#define SMI240_READ_DATA_MASK GENMASK(19, 4) +#define SMI240_READ_CS_BIT_MASK BIT(3) + +#define SMI240_WRITE_BUS_ID_MASK GENMASK(31, 30) +#define SMI240_WRITE_ADDR_MASK GENMASK(29, 22) +#define SMI240_WRITE_BIT_MASK BIT(21) +#define SMI240_WRITE_CAP_BIT_MASK BIT(20) +#define SMI240_WRITE_DATA_MASK GENMASK(18, 3) + +/* T°C = (temp / 256) + 25 */ +#define SMI240_TEMP_OFFSET 6400 /* 25 * 256 */ +#define SMI240_TEMP_SCALE 3906250 /* (1 / 256) * 1e9 */ + +#define SMI240_ACCEL_SCALE 500 /* (1 / 2000) * 1e6 */ +#define SMI240_GYRO_SCALE 10000 /* (1 / 100) * 1e6 */ + +#define SMI240_LOW_BANDWIDTH_HZ 50 +#define SMI240_HIGH_BANDWIDTH_HZ 400 + +#define SMI240_BUILT_IN_SELF_TEST_COUNT 3 + +#define SMI240_DATA_CHANNEL(_type, _axis, _index) { \ + .type = _type, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ +} + +#define SMI240_TEMP_CHANNEL(_index) { \ + .type = IIO_TEMP, \ + .modified = 1, \ + .channel2 = IIO_MOD_TEMP_OBJECT, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ +} + +enum capture_mode { SMI240_CAPTURE_OFF = 0, SMI240_CAPTURE_ON = 1 }; + +struct smi240_data { + struct regmap *regmap; + u16 accel_filter_freq; + u16 anglvel_filter_freq; + u8 built_in_self_test_count; + enum capture_mode capture; + /* + * Ensure natural alignment for timestamp if present. + * Channel size: 2 bytes. + * Max length needed: 2 * 3 channels + temp channel + 2 bytes padding + 8 byte ts. + * If fewer channels are enabled, less space may be needed, as + * long as the timestamp is still aligned to 8 bytes. + */ + s16 buf[12] __aligned(8); + + __be32 spi_buf __aligned(IIO_DMA_MINALIGN); +}; + +enum { + SMI240_TEMP_OBJECT, + SMI240_SCAN_ACCEL_X, + SMI240_SCAN_ACCEL_Y, + SMI240_SCAN_ACCEL_Z, + SMI240_SCAN_GYRO_X, + SMI240_SCAN_GYRO_Y, + SMI240_SCAN_GYRO_Z, + SMI240_SCAN_TIMESTAMP, +}; + +static const struct iio_chan_spec smi240_channels[] = { + SMI240_TEMP_CHANNEL(SMI240_TEMP_OBJECT), + SMI240_DATA_CHANNEL(IIO_ACCEL, X, SMI240_SCAN_ACCEL_X), + SMI240_DATA_CHANNEL(IIO_ACCEL, Y, SMI240_SCAN_ACCEL_Y), + SMI240_DATA_CHANNEL(IIO_ACCEL, Z, SMI240_SCAN_ACCEL_Z), + SMI240_DATA_CHANNEL(IIO_ANGL_VEL, X, SMI240_SCAN_GYRO_X), + SMI240_DATA_CHANNEL(IIO_ANGL_VEL, Y, SMI240_SCAN_GYRO_Y), + SMI240_DATA_CHANNEL(IIO_ANGL_VEL, Z, SMI240_SCAN_GYRO_Z), + IIO_CHAN_SOFT_TIMESTAMP(SMI240_SCAN_TIMESTAMP), +}; + +static const int smi240_low_pass_freqs[] = { SMI240_LOW_BANDWIDTH_HZ, + SMI240_HIGH_BANDWIDTH_HZ }; + +static u8 smi240_crc3(u32 data, u8 init, u8 poly) +{ + u8 crc = init; + u8 do_xor; + s8 i = 31; + + do { + do_xor = crc & 0x04; + crc <<= 1; + crc |= 0x01 & (data >> i); + if (do_xor) + crc ^= poly; + + crc &= SMI240_CRC_MASK; + } while (--i >= 0); + + return crc; +} + +static bool smi240_sensor_data_is_valid(u32 data) +{ + if (smi240_crc3(data, SMI240_CRC_INIT, SMI240_CRC_POLY) != 0) + return false; + + if (FIELD_GET(SMI240_READ_SD_BIT_MASK, data) & + FIELD_GET(SMI240_READ_CS_BIT_MASK, data)) + return false; + + return true; +} + +static int smi240_regmap_spi_read(void *context, const void *reg_buf, + size_t reg_size, void *val_buf, + size_t val_size) +{ + int ret; + u32 request, response; + u16 *val = val_buf; + struct spi_device *spi = context; + struct iio_dev *indio_dev = dev_get_drvdata(&spi->dev); + struct smi240_data *iio_priv_data = iio_priv(indio_dev); + + if (reg_size != 1 || val_size != 2) + return -EINVAL; + + request = FIELD_PREP(SMI240_WRITE_BUS_ID_MASK, SMI240_BUS_ID); + request |= FIELD_PREP(SMI240_WRITE_CAP_BIT_MASK, iio_priv_data->capture); + request |= FIELD_PREP(SMI240_WRITE_ADDR_MASK, *(u8 *)reg_buf); + request |= smi240_crc3(request, SMI240_CRC_INIT, SMI240_CRC_POLY); + + iio_priv_data->spi_buf = cpu_to_be32(request); + + /* + * SMI240 module consists of a 32Bit Out Of Frame (OOF) + * SPI protocol, where the slave interface responds to + * the Master request in the next frame. + * CS signal must toggle (> 700 ns) between the frames. + */ + ret = spi_write(spi, &iio_priv_data->spi_buf, sizeof(request)); + if (ret) + return ret; + + ret = spi_read(spi, &iio_priv_data->spi_buf, sizeof(response)); + if (ret) + return ret; + + response = be32_to_cpu(iio_priv_data->spi_buf); + + if (!smi240_sensor_data_is_valid(response)) + return -EIO; + + *val = FIELD_GET(SMI240_READ_DATA_MASK, response); + + return 0; +} + +static int smi240_regmap_spi_write(void *context, const void *data, + size_t count) +{ + u8 reg_addr; + u16 reg_data; + u32 request; + const u8 *data_ptr = data; + struct spi_device *spi = context; + struct iio_dev *indio_dev = dev_get_drvdata(&spi->dev); + struct smi240_data *iio_priv_data = iio_priv(indio_dev); + + if (count < 2) + return -EINVAL; + + reg_addr = data_ptr[0]; + memcpy(®_data, &data_ptr[1], sizeof(reg_data)); + + request = FIELD_PREP(SMI240_WRITE_BUS_ID_MASK, SMI240_BUS_ID); + request |= FIELD_PREP(SMI240_WRITE_BIT_MASK, 1); + request |= FIELD_PREP(SMI240_WRITE_ADDR_MASK, reg_addr); + request |= FIELD_PREP(SMI240_WRITE_DATA_MASK, reg_data); + request |= smi240_crc3(request, SMI240_CRC_INIT, SMI240_CRC_POLY); + + iio_priv_data->spi_buf = cpu_to_be32(request); + + return spi_write(spi, &iio_priv_data->spi_buf, sizeof(request)); +} + +static const struct regmap_bus smi240_regmap_bus = { + .read = smi240_regmap_spi_read, + .write = smi240_regmap_spi_write, +}; + +static const struct regmap_config smi240_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .val_format_endian = REGMAP_ENDIAN_NATIVE, +}; + +static int smi240_soft_reset(struct smi240_data *data) +{ + int ret; + + ret = regmap_write(data->regmap, SMI240_CMD_REG, SMI240_SOFT_RESET_CMD); + if (ret) + return ret; + fsleep(SMI240_DIGITAL_STARTUP_DELAY_US); + + return 0; +} + +static int smi240_soft_config(struct smi240_data *data) +{ + int ret; + u8 acc_bw, gyr_bw; + u16 request; + + switch (data->accel_filter_freq) { + case SMI240_LOW_BANDWIDTH_HZ: + acc_bw = 0x1; + break; + case SMI240_HIGH_BANDWIDTH_HZ: + acc_bw = 0x0; + break; + default: + return -EINVAL; + } + + switch (data->anglvel_filter_freq) { + case SMI240_LOW_BANDWIDTH_HZ: + gyr_bw = 0x1; + break; + case SMI240_HIGH_BANDWIDTH_HZ: + gyr_bw = 0x0; + break; + default: + return -EINVAL; + } + + request = FIELD_PREP(SMI240_SOFT_CONFIG_EOC_MASK, 1); + request |= FIELD_PREP(SMI240_SOFT_CONFIG_GYR_BW_MASK, gyr_bw); + request |= FIELD_PREP(SMI240_SOFT_CONFIG_ACC_BW_MASK, acc_bw); + request |= FIELD_PREP(SMI240_SOFT_CONFIG_BITE_AUTO_MASK, 1); + request |= FIELD_PREP(SMI240_SOFT_CONFIG_BITE_REP_MASK, + data->built_in_self_test_count - 1); + + ret = regmap_write(data->regmap, SMI240_SOFT_CONFIG_REG, request); + if (ret) + return ret; + + fsleep(SMI240_MECH_STARTUP_DELAY_US + + data->built_in_self_test_count * SMI240_BITE_SEQUENCE_DELAY_US + + SMI240_FILTER_FLUSH_DELAY_US); + + return 0; +} + +static int smi240_get_low_pass_filter_freq(struct smi240_data *data, + int chan_type, int *val) +{ + switch (chan_type) { + case IIO_ACCEL: + *val = data->accel_filter_freq; + return 0; + case IIO_ANGL_VEL: + *val = data->anglvel_filter_freq; + return 0; + default: + return -EINVAL; + } +} + +static int smi240_get_data(struct smi240_data *data, int chan_type, int axis, + int *val) +{ + u8 reg; + int ret, sample; + + switch (chan_type) { + case IIO_TEMP: + reg = SMI240_TEMP_CUR_REG; + break; + case IIO_ACCEL: + reg = SMI240_ACCEL_X_CUR_REG + (axis - IIO_MOD_X); + break; + case IIO_ANGL_VEL: + reg = SMI240_GYRO_X_CUR_REG + (axis - IIO_MOD_X); + break; + default: + return -EINVAL; + } + + ret = regmap_read(data->regmap, reg, &sample); + if (ret) + return ret; + + *val = sign_extend32(sample, 15); + + return 0; +} + +static irqreturn_t smi240_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct smi240_data *data = iio_priv(indio_dev); + int base = SMI240_DATA_CAP_FIRST_REG, i = 0; + int ret, chan, sample; + + data->capture = SMI240_CAPTURE_ON; + + iio_for_each_active_channel(indio_dev, chan) { + ret = regmap_read(data->regmap, base + chan, &sample); + data->capture = SMI240_CAPTURE_OFF; + if (ret) + goto out; + data->buf[i++] = sample; + } + + iio_push_to_buffers_with_timestamp(indio_dev, data->buf, pf->timestamp); + +out: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int smi240_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, const int **vals, + int *type, int *length, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + *vals = smi240_low_pass_freqs; + *length = ARRAY_SIZE(smi240_low_pass_freqs); + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int smi240_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int ret; + struct smi240_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = smi240_get_data(data, chan->type, chan->channel2, val); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + ret = smi240_get_low_pass_filter_freq(data, chan->type, val); + if (ret) + return ret; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + *val = SMI240_TEMP_SCALE / GIGA; + *val2 = SMI240_TEMP_SCALE % GIGA; + return IIO_VAL_INT_PLUS_NANO; + case IIO_ACCEL: + *val = 0; + *val2 = SMI240_ACCEL_SCALE; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ANGL_VEL: + *val = 0; + *val2 = SMI240_GYRO_SCALE; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_OFFSET: + if (chan->type == IIO_TEMP) { + *val = SMI240_TEMP_OFFSET; + return IIO_VAL_INT; + } else { + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +static int smi240_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, + long mask) +{ + int ret, i; + struct smi240_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + for (i = 0; i < ARRAY_SIZE(smi240_low_pass_freqs); i++) { + if (val == smi240_low_pass_freqs[i]) + break; + } + + if (i == ARRAY_SIZE(smi240_low_pass_freqs)) + return -EINVAL; + + switch (chan->type) { + case IIO_ACCEL: + data->accel_filter_freq = val; + break; + case IIO_ANGL_VEL: + data->anglvel_filter_freq = val; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + /* Write access to soft config is locked until hard/soft reset */ + ret = smi240_soft_reset(data); + if (ret) + return ret; + + return smi240_soft_config(data); +} + +static int smi240_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long info) +{ + switch (info) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT_PLUS_MICRO; + } + default: + return IIO_VAL_INT_PLUS_MICRO; + } +} + +static int smi240_init(struct smi240_data *data) +{ + int ret; + + data->accel_filter_freq = SMI240_HIGH_BANDWIDTH_HZ; + data->anglvel_filter_freq = SMI240_HIGH_BANDWIDTH_HZ; + data->built_in_self_test_count = SMI240_BUILT_IN_SELF_TEST_COUNT; + + ret = smi240_soft_reset(data); + if (ret) + return ret; + + return smi240_soft_config(data); +} + +static const struct iio_info smi240_info = { + .read_avail = smi240_read_avail, + .read_raw = smi240_read_raw, + .write_raw = smi240_write_raw, + .write_raw_get_fmt = smi240_write_raw_get_fmt, +}; + +static int smi240_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct regmap *regmap; + struct smi240_data *data; + int ret, response; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init(dev, &smi240_regmap_bus, dev, + &smi240_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Failed to initialize SPI Regmap\n"); + + data = iio_priv(indio_dev); + dev_set_drvdata(dev, indio_dev); + data->regmap = regmap; + data->capture = SMI240_CAPTURE_OFF; + + ret = regmap_read(data->regmap, SMI240_CHIP_ID_REG, &response); + if (ret) + return dev_err_probe(dev, ret, "Read chip id failed\n"); + + if (response != SMI240_CHIP_ID) + dev_info(dev, "Unknown chip id: 0x%04x\n", response); + + ret = smi240_init(data); + if (ret) + return dev_err_probe(dev, ret, + "Device initialization failed\n"); + + indio_dev->channels = smi240_channels; + indio_dev->num_channels = ARRAY_SIZE(smi240_channels); + indio_dev->name = "smi240"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &smi240_info; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + smi240_trigger_handler, NULL); + if (ret) + return dev_err_probe(dev, ret, + "Setup triggered buffer failed\n"); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Register IIO device failed\n"); + + return 0; +} + +static const struct spi_device_id smi240_spi_id[] = { + { .name = "smi240" }, + { } +}; +MODULE_DEVICE_TABLE(spi, smi240_spi_id); + +static const struct of_device_id smi240_of_match[] = { + { .compatible = "bosch,smi240" }, + { } +}; +MODULE_DEVICE_TABLE(of, smi240_of_match); + +static struct spi_driver smi240_spi_driver = { + .probe = smi240_probe, + .id_table = smi240_spi_id, + .driver = { + .of_match_table = smi240_of_match, + .name = "smi240", + }, +}; +module_spi_driver(smi240_spi_driver); + +MODULE_AUTHOR("Markus Lochmann "); +MODULE_AUTHOR("Stefan Gutmann "); +MODULE_DESCRIPTION("Bosch SMI240 SPI driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/iio/imu/smi330/Kconfig b/drivers/iio/imu/smi330/Kconfig new file mode 100644 index 000000000..856a315e1 --- /dev/null +++ b/drivers/iio/imu/smi330/Kconfig @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# SMI330 IMU driver +# + +config SMI330 + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config SMI330_I2C + tristate "Bosch SMI330 I2C driver" + depends on I2C + select SMI330 + select REGMAP_I2C + help + Enable support for the Bosch SMI330 6-Axis IMU connected to I2C + interface. + + This driver can also be built as a module. If so, the module will be + called smi330_i2c. + +config SMI330_SPI + tristate "Bosch SMI330 SPI driver" + depends on SPI + select SMI330 + select REGMAP_SPI + help + Enable support for the Bosch SMI330 6-Axis IMU connected to SPI + interface. + + This driver can also be built as a module. If so, the module will be + called smi330_spi. diff --git a/drivers/iio/imu/smi330/Makefile b/drivers/iio/imu/smi330/Makefile new file mode 100644 index 000000000..c663dcb5a --- /dev/null +++ b/drivers/iio/imu/smi330/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for Bosch SMI330 IMU +# +obj-$(CONFIG_SMI330) += smi330_core.o +obj-$(CONFIG_SMI330_I2C) += smi330_i2c.o +obj-$(CONFIG_SMI330_SPI) += smi330_spi.o diff --git a/drivers/iio/imu/smi330/smi330.h b/drivers/iio/imu/smi330/smi330.h new file mode 100644 index 000000000..a5c765645 --- /dev/null +++ b/drivers/iio/imu/smi330/smi330.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 */ +/* + * Copyright (c) 2025 Robert Bosch GmbH. + */ +#ifndef _SMI330_H +#define _SMI330_H + +#include + +enum { + SMI330_SCAN_ACCEL_X, + SMI330_SCAN_ACCEL_Y, + SMI330_SCAN_ACCEL_Z, + SMI330_SCAN_GYRO_X, + SMI330_SCAN_GYRO_Y, + SMI330_SCAN_GYRO_Z, + SMI330_SCAN_TIMESTAMP, + SMI330_SCAN_LEN = SMI330_SCAN_TIMESTAMP, +}; + +extern const struct regmap_config smi330_regmap_config; + +int smi330_core_probe(struct device *dev, struct regmap *regmap); + +#endif /* _SMI330_H */ diff --git a/drivers/iio/imu/smi330/smi330_core.c b/drivers/iio/imu/smi330/smi330_core.c new file mode 100644 index 000000000..7ec5ae7f5 --- /dev/null +++ b/drivers/iio/imu/smi330/smi330_core.c @@ -0,0 +1,913 @@ +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 +/* + * Copyright (c) 2025 Robert Bosch GmbH. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "smi330.h" + +/* Register map */ +#define SMI330_CHIP_ID_REG 0x00 +#define SMI330_ERR_REG 0x01 +#define SMI330_STATUS_REG 0x02 +#define SMI330_ACCEL_X_REG 0x03 +#define SMI330_GYRO_X_REG 0x06 +#define SMI330_TEMP_REG 0x09 +#define SMI330_INT1_STATUS_REG 0x0D +#define SMI330_ACCEL_CFG_REG 0x20 +#define SMI330_GYRO_CFG_REG 0x21 +#define SMI330_IO_INT_CTRL_REG 0x38 +#define SMI330_INT_CONF_REG 0x39 +#define SMI330_INT_MAP1_REG 0x3A +#define SMI330_INT_MAP2_REG 0x3B +#define SMI330_CMD_REG 0x7E + +/* Register mask */ +#define SMI330_CHIP_ID_MASK GENMASK(7, 0) +#define SMI330_ERR_FATAL_MASK BIT(0) +#define SMI330_ERR_ACC_CONF_MASK BIT(5) +#define SMI330_ERR_GYR_CONF_MASK BIT(6) +#define SMI330_STATUS_POR_MASK BIT(0) +#define SMI330_INT_STATUS_ACC_GYR_DRDY_MASK GENMASK(13, 12) +#define SMI330_CFG_ODR_MASK GENMASK(3, 0) +#define SMI330_CFG_RANGE_MASK GENMASK(6, 4) +#define SMI330_CFG_BW_MASK BIT(7) +#define SMI330_CFG_AVG_NUM_MASK GENMASK(10, 8) +#define SMI330_CFG_MODE_MASK GENMASK(14, 12) +#define SMI330_IO_INT_CTRL_INT1_MASK GENMASK(2, 0) +#define SMI330_IO_INT_CTRL_INT2_MASK GENMASK(10, 8) +#define SMI330_INT_CONF_LATCH_MASK BIT(0) +#define SMI330_INT_MAP2_ACC_DRDY_MASK GENMASK(11, 10) +#define SMI330_INT_MAP2_GYR_DRDY_MASK GENMASK(9, 8) + +/* Register values */ +#define SMI330_IO_INT_CTRL_LVL BIT(0) +#define SMI330_IO_INT_CTRL_OD BIT(1) +#define SMI330_IO_INT_CTRL_EN BIT(2) +#define SMI330_CMD_SOFT_RESET 0xDEAF + +/* T°C = (temp / 512) + 23 */ +#define SMI330_TEMP_OFFSET 11776 /* 23 * 512 */ +#define SMI330_TEMP_SCALE 1953125 /* (1 / 512) * 1e9 */ + +#define SMI330_CHIP_ID 0x42 +#define SMI330_SOFT_RESET_DELAY 2000 + +#define SMI330_ACCEL_CHANNEL(_axis) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_dir_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = SMI330_SCAN_ACCEL_##_axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ +} + +#define SMI330_GYRO_CHANNEL(_axis) { \ + .type = IIO_ANGL_VEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_dir_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = SMI330_SCAN_GYRO_##_axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ +} + +#define SMI330_TEMP_CHANNEL(_index) { \ + .type = IIO_TEMP, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ +} + +enum smi330_accel_range { + SMI330_ACCEL_RANGE_2G = 0x00, + SMI330_ACCEL_RANGE_4G = 0x01, + SMI330_ACCEL_RANGE_8G = 0x02, + SMI330_ACCEL_RANGE_16G = 0x03 +}; + +enum smi330_gyro_range { + SMI330_GYRO_RANGE_125 = 0x0, + SMI330_GYRO_RANGE_250 = 0x01, + SMI330_GYRO_RANGE_500 = 0x02 +}; + +enum smi330_odr { + SMI330_ODR_12_5_HZ = 0x05, + SMI330_ODR_25_HZ = 0x06, + SMI330_ODR_50_HZ = 0x07, + SMI330_ODR_100_HZ = 0x08, + SMI330_ODR_200_HZ = 0x09, + SMI330_ODR_400_HZ = 0x0A, + SMI330_ODR_800_HZ = 0x0B, + SMI330_ODR_1600_HZ = 0x0C, + SMI330_ODR_3200_HZ = 0x0D, + SMI330_ODR_6400_HZ = 0x0E +}; + +enum smi330_avg_num { + SMI330_AVG_NUM_1 = 0x00, + SMI330_AVG_NUM_2 = 0x01, + SMI330_AVG_NUM_4 = 0x02, + SMI330_AVG_NUM_8 = 0x03, + SMI330_AVG_NUM_16 = 0x04, + SMI330_AVG_NUM_32 = 0x05, + SMI330_AVG_NUM_64 = 0x06 +}; + +enum smi330_mode { + SMI330_MODE_SUSPEND = 0x00, + SMI330_MODE_GYRO_DRIVE = 0x01, + SMI330_MODE_LOW_POWER = 0x03, + SMI330_MODE_NORMAL = 0x04, + SMI330_MODE_HIGH_PERF = 0x07 +}; + +enum smi330_bw { + SMI330_BW_2 = 0x00, /* ODR/2 */ + SMI330_BW_4 = 0x01 /* ODR/4 */ +}; + +enum smi330_operation_mode { + SMI330_POLLING, + SMI330_DATA_READY, +}; + +enum smi330_sensor { + SMI330_ACCEL, + SMI330_GYRO, +}; + +enum smi330_sensor_conf_select { + SMI330_ODR, + SMI330_RANGE, + SMI330_BW, + SMI330_AVG_NUM, +}; + +enum smi330_int_out { + SMI330_INT_DISABLED, + SMI330_INT_1, + SMI330_INT_2, +}; + +struct smi330_attributes { + int *reg_vals; + int *vals; + int len; + int type; + int mask; +}; + +struct smi330_cfg { + enum smi330_operation_mode op_mode; + enum smi330_int_out data_irq; +}; + +struct smi330_data { + struct regmap *regmap; + struct smi330_cfg cfg; + struct iio_trigger *trig; + IIO_DECLARE_BUFFER_WITH_TS(__le16, buf, SMI330_SCAN_LEN); +}; + +const struct regmap_config smi330_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; +EXPORT_SYMBOL_NS_GPL(smi330_regmap_config, "IIO_SMI330"); + +static const struct iio_chan_spec smi330_channels[] = { + SMI330_ACCEL_CHANNEL(X), + SMI330_ACCEL_CHANNEL(Y), + SMI330_ACCEL_CHANNEL(Z), + SMI330_GYRO_CHANNEL(X), + SMI330_GYRO_CHANNEL(Y), + SMI330_GYRO_CHANNEL(Z), + SMI330_TEMP_CHANNEL(-1), /* No buffer support */ + IIO_CHAN_SOFT_TIMESTAMP(SMI330_SCAN_TIMESTAMP), +}; + +static const unsigned long smi330_avail_scan_masks[] = { + (BIT(SMI330_SCAN_ACCEL_X) | BIT(SMI330_SCAN_ACCEL_Y) | + BIT(SMI330_SCAN_ACCEL_Z) | BIT(SMI330_SCAN_GYRO_X) | + BIT(SMI330_SCAN_GYRO_Y) | BIT(SMI330_SCAN_GYRO_Z)), + 0 +}; + +static const struct smi330_attributes smi330_accel_scale_attr = { + .reg_vals = (int[]){ SMI330_ACCEL_RANGE_2G, SMI330_ACCEL_RANGE_4G, + SMI330_ACCEL_RANGE_8G, SMI330_ACCEL_RANGE_16G }, + .vals = (int[]){ 0, 61035, 0, 122070, 0, 244140, 0, 488281 }, + .len = 8, + .type = IIO_VAL_INT_PLUS_NANO, + .mask = SMI330_CFG_RANGE_MASK +}; + +static const struct smi330_attributes smi330_gyro_scale_attr = { + .reg_vals = (int[]){ SMI330_GYRO_RANGE_125, SMI330_GYRO_RANGE_250, + SMI330_GYRO_RANGE_500 }, + .vals = (int[]){ 0, 3814697, 0, 7629395, 0, 15258789 }, + .len = 6, + .type = IIO_VAL_INT_PLUS_NANO, + .mask = SMI330_CFG_RANGE_MASK +}; + +static const struct smi330_attributes smi330_average_attr = { + .reg_vals = (int[]){ SMI330_AVG_NUM_1, SMI330_AVG_NUM_2, + SMI330_AVG_NUM_4, SMI330_AVG_NUM_8, + SMI330_AVG_NUM_16, SMI330_AVG_NUM_32, + SMI330_AVG_NUM_64 }, + .vals = (int[]){ 1, 2, 4, 8, 16, 32, 64 }, + .len = 7, + .type = IIO_VAL_INT, + .mask = SMI330_CFG_AVG_NUM_MASK +}; + +static const struct smi330_attributes smi330_bandwidth_attr = { + .reg_vals = (int[]){ SMI330_BW_2, SMI330_BW_4 }, + .vals = (int[]){ 2, 4 }, + .len = 2, + .type = IIO_VAL_INT, + .mask = SMI330_CFG_BW_MASK +}; + +static const struct smi330_attributes smi330_odr_attr = { + .reg_vals = (int[]){ SMI330_ODR_12_5_HZ, SMI330_ODR_25_HZ, + SMI330_ODR_50_HZ, SMI330_ODR_100_HZ, + SMI330_ODR_200_HZ, SMI330_ODR_400_HZ, + SMI330_ODR_800_HZ, SMI330_ODR_1600_HZ, + SMI330_ODR_3200_HZ, SMI330_ODR_6400_HZ }, + .vals = (int[]){ 12, 25, 50, 100, 200, 400, 800, 1600, 3200, 6400 }, + .len = 10, + .type = IIO_VAL_INT, + .mask = SMI330_CFG_ODR_MASK +}; + +static int smi330_get_attributes(enum smi330_sensor_conf_select config, + enum smi330_sensor sensor, + const struct smi330_attributes **attr) +{ + switch (config) { + case SMI330_ODR: + *attr = &smi330_odr_attr; + return 0; + case SMI330_RANGE: + if (sensor == SMI330_ACCEL) + *attr = &smi330_accel_scale_attr; + else + *attr = &smi330_gyro_scale_attr; + return 0; + case SMI330_BW: + *attr = &smi330_bandwidth_attr; + return 0; + case SMI330_AVG_NUM: + *attr = &smi330_average_attr; + return 0; + default: + return -EINVAL; + } +} + +static int smi330_get_config_reg(enum smi330_sensor sensor, int *reg) +{ + switch (sensor) { + case SMI330_ACCEL: + *reg = SMI330_ACCEL_CFG_REG; + return 0; + case SMI330_GYRO: + *reg = SMI330_GYRO_CFG_REG; + return 0; + default: + return -EINVAL; + } +} + +static int smi330_get_sensor_config(struct smi330_data *data, + enum smi330_sensor sensor, + enum smi330_sensor_conf_select config, + int *value) + +{ + int ret, reg, reg_val, i; + const struct smi330_attributes *attr; + + ret = smi330_get_config_reg(sensor, ®); + if (ret) + return ret; + + ret = regmap_read(data->regmap, reg, ®_val); + if (ret) + return ret; + + ret = smi330_get_attributes(config, sensor, &attr); + if (ret) + return ret; + + reg_val = field_get(attr->mask, reg_val); + + if (attr->type == IIO_VAL_INT) { + for (i = 0; i < attr->len; i++) { + if (attr->reg_vals[i] == reg_val) { + *value = attr->vals[i]; + return 0; + } + } + } else { + for (i = 0; i < attr->len / 2; i++) { + if (attr->reg_vals[i] == reg_val) { + *value = attr->vals[2 * i + 1]; + return 0; + } + } + } + + return -EINVAL; +} + +static int smi330_set_sensor_config(struct smi330_data *data, + enum smi330_sensor sensor, + enum smi330_sensor_conf_select config, + int value) +{ + int ret, i, reg, reg_val, error; + const struct smi330_attributes *attr; + + ret = smi330_get_attributes(config, sensor, &attr); + if (ret) + return ret; + + for (i = 0; i < attr->len; i++) { + if (attr->vals[i] == value) { + if (attr->type == IIO_VAL_INT) + reg_val = attr->reg_vals[i]; + else + reg_val = attr->reg_vals[i / 2]; + break; + } + } + if (i == attr->len) + return -EINVAL; + + ret = smi330_get_config_reg(sensor, ®); + if (ret) + return ret; + + reg_val = field_prep(attr->mask, reg_val); + ret = regmap_update_bits(data->regmap, reg, attr->mask, reg_val); + if (ret) + return ret; + + ret = regmap_read(data->regmap, SMI330_ERR_REG, &error); + if (ret) + return ret; + + if (FIELD_GET(SMI330_ERR_ACC_CONF_MASK, error) || + FIELD_GET(SMI330_ERR_GYR_CONF_MASK, error)) + return -EIO; + + return 0; +} + +static int smi330_get_data(struct smi330_data *data, int chan_type, int axis, + int *val) +{ + u8 reg; + int ret, sample; + + switch (chan_type) { + case IIO_ACCEL: + reg = SMI330_ACCEL_X_REG + (axis - IIO_MOD_X); + break; + case IIO_ANGL_VEL: + reg = SMI330_GYRO_X_REG + (axis - IIO_MOD_X); + break; + case IIO_TEMP: + reg = SMI330_TEMP_REG; + break; + default: + return -EINVAL; + } + + ret = regmap_read(data->regmap, reg, &sample); + if (ret) + return ret; + + *val = sign_extend32(sample, 15); + + return 0; +} + +static int smi330_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, const int **vals, + int *type, int *length, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_ACCEL) { + *vals = smi330_accel_scale_attr.vals; + *length = smi330_accel_scale_attr.len; + *type = smi330_accel_scale_attr.type; + } else { + *vals = smi330_gyro_scale_attr.vals; + *length = smi330_gyro_scale_attr.len; + *type = smi330_gyro_scale_attr.type; + } + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = smi330_average_attr.vals; + *length = smi330_average_attr.len; + *type = smi330_average_attr.type; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + *vals = smi330_bandwidth_attr.vals; + *length = smi330_bandwidth_attr.len; + *type = smi330_bandwidth_attr.type; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = smi330_odr_attr.vals; + *length = smi330_odr_attr.len; + *type = smi330_odr_attr.type; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int smi330_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int ret; + struct smi330_data *data = iio_priv(indio_dev); + enum smi330_sensor sensor; + + /* valid for all channel types */ + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = smi330_get_data(data, chan->type, chan->channel2, val); + iio_device_release_direct(indio_dev); + return ret ? ret : IIO_VAL_INT; + default: + break; + } + + switch (chan->type) { + case IIO_ACCEL: + sensor = SMI330_ACCEL; + break; + case IIO_ANGL_VEL: + sensor = SMI330_GYRO; + break; + case IIO_TEMP: + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *val = SMI330_TEMP_SCALE / GIGA; + *val2 = SMI330_TEMP_SCALE % GIGA; + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + *val = SMI330_TEMP_OFFSET; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } + + /* valid for acc and gyro channels */ + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + ret = smi330_get_sensor_config(data, sensor, SMI330_AVG_NUM, + val); + return ret ? ret : IIO_VAL_INT; + + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + ret = smi330_get_sensor_config(data, sensor, SMI330_BW, val); + return ret ? ret : IIO_VAL_INT; + + case IIO_CHAN_INFO_SAMP_FREQ: + ret = smi330_get_sensor_config(data, sensor, SMI330_ODR, val); + return ret ? ret : IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = 0; + ret = smi330_get_sensor_config(data, sensor, SMI330_RANGE, + val2); + return ret ? ret : IIO_VAL_INT_PLUS_NANO; + + default: + return -EINVAL; + } +} + +static int smi330_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, + long mask) +{ + struct smi330_data *data = iio_priv(indio_dev); + enum smi330_sensor sensor; + + switch (chan->type) { + case IIO_ACCEL: + sensor = SMI330_ACCEL; + break; + case IIO_ANGL_VEL: + sensor = SMI330_GYRO; + break; + default: + return -EINVAL; + } + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return smi330_set_sensor_config(data, sensor, SMI330_RANGE, + val2); + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return smi330_set_sensor_config(data, sensor, SMI330_AVG_NUM, + val); + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return smi330_set_sensor_config(data, sensor, SMI330_BW, val); + case IIO_CHAN_INFO_SAMP_FREQ: + return smi330_set_sensor_config(data, sensor, SMI330_ODR, val); + default: + return -EINVAL; + } +} + +static int smi330_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long info) +{ + switch (info) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT_PLUS_MICRO; + } +} + +static int smi330_soft_reset(struct smi330_data *data) +{ + int ret, dummy_byte; + + ret = regmap_write(data->regmap, SMI330_CMD_REG, SMI330_CMD_SOFT_RESET); + if (ret) + return ret; + fsleep(SMI330_SOFT_RESET_DELAY); + + /* Performing a dummy read after a soft-reset */ + regmap_read(data->regmap, SMI330_CHIP_ID_REG, &dummy_byte); + + return 0; +} + +static irqreturn_t smi330_trigger_handler(int irq, void *p) +{ + int ret; + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct smi330_data *data = iio_priv(indio_dev); + + ret = regmap_bulk_read(data->regmap, SMI330_ACCEL_X_REG, data->buf, + SMI330_SCAN_LEN); + if (ret) + goto out; + + iio_push_to_buffers_with_timestamp(indio_dev, data->buf, pf->timestamp); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static irqreturn_t smi330_irq_thread_handler(int irq, void *indio_dev_) +{ + int ret, int_stat; + s16 int_status[2] = { 0 }; + struct iio_dev *indio_dev = indio_dev_; + struct smi330_data *data = iio_priv(indio_dev); + + ret = regmap_bulk_read(data->regmap, SMI330_INT1_STATUS_REG, int_status, 2); + if (ret) + return IRQ_NONE; + + int_stat = int_status[0] | int_status[1]; + + if (FIELD_GET(SMI330_INT_STATUS_ACC_GYR_DRDY_MASK, int_stat)) { + indio_dev->pollfunc->timestamp = iio_get_time_ns(indio_dev); + iio_trigger_poll_nested(data->trig); + } + + return IRQ_HANDLED; +} + +static int smi330_set_int_pin_config(struct smi330_data *data, + enum smi330_int_out irq_num, + bool active_high, bool open_drain, + bool latch) +{ + int ret, val; + + val = active_high ? SMI330_IO_INT_CTRL_LVL : 0; + val |= open_drain ? SMI330_IO_INT_CTRL_OD : 0; + val |= SMI330_IO_INT_CTRL_EN; + + switch (irq_num) { + case SMI330_INT_1: + val = FIELD_PREP(SMI330_IO_INT_CTRL_INT1_MASK, val); + ret = regmap_update_bits(data->regmap, SMI330_IO_INT_CTRL_REG, + SMI330_IO_INT_CTRL_INT1_MASK, val); + if (ret) + return ret; + break; + case SMI330_INT_2: + val = FIELD_PREP(SMI330_IO_INT_CTRL_INT2_MASK, val); + ret = regmap_update_bits(data->regmap, SMI330_IO_INT_CTRL_REG, + SMI330_IO_INT_CTRL_INT2_MASK, val); + if (ret) + return ret; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(data->regmap, SMI330_INT_CONF_REG, + SMI330_INT_CONF_LATCH_MASK, + FIELD_PREP(SMI330_INT_CONF_LATCH_MASK, + latch)); +} + +static int smi330_setup_irq(struct device *dev, struct iio_dev *indio_dev, + int irq, enum smi330_int_out irq_num) +{ + int ret, irq_type; + bool open_drain, active_high, latch; + struct smi330_data *data = iio_priv(indio_dev); + struct irq_data *desc; + + desc = irq_get_irq_data(irq); + if (!desc) + return -EINVAL; + + irq_type = irqd_get_trigger_type(desc); + switch (irq_type) { + case IRQF_TRIGGER_RISING: + latch = false; + active_high = true; + break; + case IRQF_TRIGGER_HIGH: + latch = true; + active_high = true; + break; + case IRQF_TRIGGER_FALLING: + latch = false; + active_high = false; + break; + case IRQF_TRIGGER_LOW: + latch = true; + active_high = false; + break; + default: + return -EINVAL; + } + + open_drain = device_property_read_bool(dev, "drive-open-drain"); + + ret = smi330_set_int_pin_config(data, irq_num, active_high, open_drain, + latch); + if (ret) + return ret; + + return devm_request_threaded_irq(dev, irq, NULL, + smi330_irq_thread_handler, + irq_type | IRQF_ONESHOT, + indio_dev->name, indio_dev); +} + +static int smi330_register_irq(struct device *dev, struct iio_dev *indio_dev) +{ + int ret, irq; + struct smi330_data *data = iio_priv(indio_dev); + struct fwnode_handle *fwnode; + + fwnode = dev_fwnode(dev); + if (!fwnode) + return -ENODEV; + + data->cfg.data_irq = SMI330_INT_DISABLED; + + irq = fwnode_irq_get_byname(fwnode, "INT1"); + if (irq > 0) { + ret = smi330_setup_irq(dev, indio_dev, irq, SMI330_INT_1); + if (ret) + return ret; + data->cfg.data_irq = SMI330_INT_1; + } else { + irq = fwnode_irq_get_byname(fwnode, "INT2"); + if (irq > 0) { + ret = smi330_setup_irq(dev, indio_dev, irq, + SMI330_INT_2); + if (ret) + return ret; + data->cfg.data_irq = SMI330_INT_2; + } + } + + return 0; +} + +static int smi330_set_drdy_trigger_state(struct iio_trigger *trig, bool enable) +{ + int val; + struct smi330_data *data = iio_trigger_get_drvdata(trig); + + if (enable) + data->cfg.op_mode = SMI330_DATA_READY; + else + data->cfg.op_mode = SMI330_POLLING; + + val = FIELD_PREP(SMI330_INT_MAP2_ACC_DRDY_MASK, + enable ? data->cfg.data_irq : 0); + val |= FIELD_PREP(SMI330_INT_MAP2_GYR_DRDY_MASK, + enable ? data->cfg.data_irq : 0); + return regmap_update_bits(data->regmap, SMI330_INT_MAP2_REG, + SMI330_INT_MAP2_ACC_DRDY_MASK | + SMI330_INT_MAP2_GYR_DRDY_MASK, + val); +} + +static const struct iio_trigger_ops smi330_trigger_ops = { + .set_trigger_state = &smi330_set_drdy_trigger_state, +}; + +static struct iio_info smi330_info = { + .read_avail = smi330_read_avail, + .read_raw = smi330_read_raw, + .write_raw = smi330_write_raw, + .write_raw_get_fmt = smi330_write_raw_get_fmt, +}; + +static int smi330_dev_init(struct smi330_data *data) +{ + int ret, chip_id, val, mode; + struct device *dev = regmap_get_device(data->regmap); + + ret = regmap_read(data->regmap, SMI330_CHIP_ID_REG, &chip_id); + if (ret) + return ret; + + chip_id = FIELD_GET(SMI330_CHIP_ID_MASK, chip_id); + if (chip_id != SMI330_CHIP_ID) + dev_info(dev, "Unknown chip id: 0x%04x\n", chip_id); + + ret = regmap_read(data->regmap, SMI330_ERR_REG, &val); + if (ret) + return ret; + if (FIELD_GET(SMI330_ERR_FATAL_MASK, val)) + return -ENODEV; + + ret = regmap_read(data->regmap, SMI330_STATUS_REG, &val); + if (ret) + return ret; + if (FIELD_GET(SMI330_STATUS_POR_MASK, val) == 0) + return -ENODEV; + + mode = FIELD_PREP(SMI330_CFG_MODE_MASK, SMI330_MODE_NORMAL); + + ret = regmap_update_bits(data->regmap, SMI330_ACCEL_CFG_REG, + SMI330_CFG_MODE_MASK, mode); + if (ret) + return ret; + + return regmap_update_bits(data->regmap, SMI330_GYRO_CFG_REG, + SMI330_CFG_MODE_MASK, mode); +} + +int smi330_core_probe(struct device *dev, struct regmap *regmap) +{ + int ret; + struct iio_dev *indio_dev; + struct smi330_data *data; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->regmap = regmap; + + ret = smi330_soft_reset(data); + if (ret) + return dev_err_probe(dev, ret, "Soft reset failed\n"); + + indio_dev->channels = smi330_channels; + indio_dev->num_channels = ARRAY_SIZE(smi330_channels); + indio_dev->available_scan_masks = smi330_avail_scan_masks; + indio_dev->name = "smi330"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &smi330_info; + + data->cfg.op_mode = SMI330_POLLING; + + ret = smi330_dev_init(data); + if (ret) + return dev_err_probe(dev, ret, "Init failed\n"); + + ret = smi330_register_irq(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Register IRQ failed\n"); + + if (data->cfg.data_irq != SMI330_INT_DISABLED) { + data->trig = devm_iio_trigger_alloc(dev, "%s-drdy-trigger", + indio_dev->name); + if (!data->trig) + return -ENOMEM; + + data->trig->ops = &smi330_trigger_ops; + iio_trigger_set_drvdata(data->trig, data); + + ret = devm_iio_trigger_register(dev, data->trig); + if (ret) + return dev_err_probe(dev, ret, + "IIO register trigger failed\n"); + + /* Set default operation mode to data ready. */ + indio_dev->trig = iio_trigger_get(data->trig); + } + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + smi330_trigger_handler, NULL); + if (ret) + return dev_err_probe(dev, ret, "IIO buffer setup failed\n"); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Register IIO device failed\n"); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(smi330_core_probe, "IIO_SMI330"); + +MODULE_AUTHOR("Stefan Gutmann "); +MODULE_AUTHOR("Roman Huber "); +MODULE_AUTHOR("Filip Andrei "); +MODULE_AUTHOR("Drimbarean Avram Andrei "); +MODULE_DESCRIPTION("Bosch SMI330 IMU driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/iio/imu/smi330/smi330_i2c.c b/drivers/iio/imu/smi330/smi330_i2c.c new file mode 100644 index 000000000..eb9413e22 --- /dev/null +++ b/drivers/iio/imu/smi330/smi330_i2c.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 +/* + * Copyright (c) 2025 Robert Bosch GmbH. + */ +#include +#include +#include + +#include "smi330.h" + +#define SMI330_NUM_DUMMY_BYTES 2 +#define SMI330_I2C_MAX_RX_BUFFER_SIZE \ + (SMI330_NUM_DUMMY_BYTES + SMI330_SCAN_LEN * sizeof(s16)) + +struct smi330_i2c_priv { + struct i2c_client *i2c; + u8 rx_buffer[SMI330_I2C_MAX_RX_BUFFER_SIZE]; +}; + +static int smi330_regmap_i2c_read(void *context, const void *reg_buf, + size_t reg_size, void *val_buf, + size_t val_size) +{ + struct smi330_i2c_priv *priv = context; + int ret; + + if (SMI330_NUM_DUMMY_BYTES + val_size > SMI330_I2C_MAX_RX_BUFFER_SIZE) + return -EINVAL; + + /* + * SMI330 I2C read frame: + * + * ... + * + * Remark: Slave address is not considered part of the frame in the following definitions + */ + struct i2c_msg msgs[] = { + { + .addr = priv->i2c->addr, + .flags = priv->i2c->flags, + .len = reg_size, + .buf = (u8 *)reg_buf, + }, + { + .addr = priv->i2c->addr, + .flags = priv->i2c->flags | I2C_M_RD, + .len = SMI330_NUM_DUMMY_BYTES + val_size, + .buf = priv->rx_buffer, + }, + }; + + ret = i2c_transfer(priv->i2c->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + return ret; + + memcpy(val_buf, priv->rx_buffer + SMI330_NUM_DUMMY_BYTES, val_size); + + return 0; +} + +static int smi330_regmap_i2c_write(void *context, const void *data, + size_t count) +{ + struct smi330_i2c_priv *priv = context; + u8 reg; + + /* + * SMI330 I2C write frame: + * ... + * + * Remark: Slave address is not considered part of the frame in the following definitions + */ + reg = *(u8 *)data; + return i2c_smbus_write_i2c_block_data(priv->i2c, reg, + count - sizeof(u8), + data + sizeof(u8)); +} + +static const struct regmap_bus smi330_regmap_bus = { + .read = smi330_regmap_i2c_read, + .write = smi330_regmap_i2c_write, +}; + +static int smi330_i2c_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct smi330_i2c_priv *priv; + struct regmap *regmap; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->i2c = i2c; + regmap = devm_regmap_init(dev, &smi330_regmap_bus, priv, + &smi330_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Failed to initialize I2C Regmap\n"); + + return smi330_core_probe(dev, regmap); +} + +static const struct i2c_device_id smi330_i2c_device_id[] = { + { .name = "smi330" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, smi330_i2c_device_id); + +static const struct of_device_id smi330_of_match[] = { + { .compatible = "bosch,smi330" }, + { } +}; +MODULE_DEVICE_TABLE(of, smi330_of_match); + +static struct i2c_driver smi330_i2c_driver = { + .probe = smi330_i2c_probe, + .id_table = smi330_i2c_device_id, + .driver = { + .of_match_table = smi330_of_match, + .name = "smi330_i2c", + }, +}; +module_i2c_driver(smi330_i2c_driver); + +MODULE_AUTHOR("Stefan Gutmann "); +MODULE_AUTHOR("Roman Huber "); +MODULE_AUTHOR("Filip Andrei "); +MODULE_AUTHOR("Drimbarean Avram Andrei "); +MODULE_DESCRIPTION("Bosch SMI330 I2C driver"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_IMPORT_NS("IIO_SMI330"); diff --git a/drivers/iio/imu/smi330/smi330_spi.c b/drivers/iio/imu/smi330/smi330_spi.c new file mode 100644 index 000000000..78c2bfb15 --- /dev/null +++ b/drivers/iio/imu/smi330/smi330_spi.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 +/* + * Copyright (c) 2025 Robert Bosch GmbH. + */ +#include +#include +#include + +#include "smi330.h" + +static int smi330_regmap_spi_read(void *context, const void *reg_buf, + size_t reg_size, void *val_buf, + size_t val_size) +{ + struct spi_device *spi = context; + + /* Insert pad byte for reading */ + u8 reg[] = { *(u8 *)reg_buf, 0 }; + + if (reg_size + 1 != ARRAY_SIZE(reg)) { + dev_err(&spi->dev, "Invalid register size %zu\n", reg_size); + return -EINVAL; + } + + return spi_write_then_read(spi, reg, ARRAY_SIZE(reg), val_buf, + val_size); +} + +static int smi330_regmap_spi_write(void *context, const void *data, + size_t count) +{ + struct spi_device *spi = context; + + return spi_write(spi, data, count); +} + +static const struct regmap_bus smi330_regmap_bus = { + .read = smi330_regmap_spi_read, + .write = smi330_regmap_spi_write, + .read_flag_mask = 0x80, +}; + +static int smi330_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + + regmap = devm_regmap_init(&spi->dev, &smi330_regmap_bus, &spi->dev, + &smi330_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(&spi->dev, PTR_ERR(regmap), + "Failed to initialize SPI Regmap\n"); + + return smi330_core_probe(&spi->dev, regmap); +} + +static const struct spi_device_id smi330_spi_device_id[] = { + { .name = "smi330" }, + { } +}; +MODULE_DEVICE_TABLE(spi, smi330_spi_device_id); + +static const struct of_device_id smi330_of_match[] = { + { .compatible = "bosch,smi330" }, + { } +}; +MODULE_DEVICE_TABLE(of, smi330_of_match); + +static struct spi_driver smi330_spi_driver = { + .probe = smi330_spi_probe, + .id_table = smi330_spi_device_id, + .driver = { + .of_match_table = smi330_of_match, + .name = "smi330_spi", + }, +}; +module_spi_driver(smi330_spi_driver); + +MODULE_AUTHOR("Stefan Gutmann "); +MODULE_AUTHOR("Roman Huber "); +MODULE_AUTHOR("Filip Andrei "); +MODULE_AUTHOR("Drimbarean Avram Andrei "); +MODULE_DESCRIPTION("Bosch SMI330 SPI driver"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_IMPORT_NS("IIO_SMI330"); diff --git a/drivers/iio/imu/st_lsm6dsx/Kconfig b/drivers/iio/imu/st_lsm6dsx/Kconfig new file mode 100644 index 000000000..3cabec3b1 --- /dev/null +++ b/drivers/iio/imu/st_lsm6dsx/Kconfig @@ -0,0 +1,57 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config IIO_ST_LSM6DSX + tristate "ST_LSM6DSx driver for STM 6-axis IMU MEMS sensors" + depends on (I2C || SPI || I3C) + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select IIO_KFIFO_BUF + help + Say yes here to build support for STMicroelectronics LSM6DSx imu + sensor. + Supported devices: + - asm330lhb + - asm330lhh + - asm330lhhx + - asm330lhhxg1 + - ism330dhcx + - ism330dlc + - ism330is + - lsm6ds0 + - lsm6ds3 + - lsm6ds3h + - lsm6ds3tr-c + - lsm6dsl + - lsm6dsm + - lsm6dso + - lsm6dso16is + - lsm6dsop + - lsm6dsox + - lsm6dsr + - lsm6dsrx + - lsm6dst + - lsm6dstx + - lsm6dsv + - lsm6dsv16x + - lsm9ds1 + + To compile this driver as a module, choose M here: the module + will be called st_lsm6dsx. + +config IIO_ST_LSM6DSX_I2C + tristate "ST_LSM6DSx driver for STM 6-axis IMU MEMS sensors I2C Interface" + depends on I2C && IIO_ST_LSM6DSX + default I2C && IIO_ST_LSM6DSX + select REGMAP_I2C + +config IIO_ST_LSM6DSX_SPI + tristate "ST_LSM6DSx driver for STM 6-axis IMU MEMS sensors SPI Interface" + depends on SPI_MASTER && IIO_ST_LSM6DSX + default SPI_MASTER && IIO_ST_LSM6DSX + select REGMAP_SPI + +config IIO_ST_LSM6DSX_I3C + tristate "ST_LSM6DSx driver for STM 6-axis IMU MEMS sensors I3C Interface" + depends on I3C && IIO_ST_LSM6DSX + default I3C && IIO_ST_LSM6DSX + select REGMAP_I3C diff --git a/drivers/iio/imu/st_lsm6dsx/Makefile b/drivers/iio/imu/st_lsm6dsx/Makefile new file mode 100644 index 000000000..19a488254 --- /dev/null +++ b/drivers/iio/imu/st_lsm6dsx/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only +st_lsm6dsx-y := st_lsm6dsx_core.o st_lsm6dsx_buffer.o \ + st_lsm6dsx_shub.o st_lsm6dsx_fusion.o + +obj-$(CONFIG_IIO_ST_LSM6DSX) += st_lsm6dsx.o +obj-$(CONFIG_IIO_ST_LSM6DSX_I2C) += st_lsm6dsx_i2c.o +obj-$(CONFIG_IIO_ST_LSM6DSX_SPI) += st_lsm6dsx_spi.o +obj-$(CONFIG_IIO_ST_LSM6DSX_I3C) += st_lsm6dsx_i3c.o diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h new file mode 100644 index 000000000..767aadfe7 --- /dev/null +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h @@ -0,0 +1,599 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * STMicroelectronics st_lsm6dsx sensor driver + * + * Copyright 2016 STMicroelectronics Inc. + * + * Lorenzo Bianconi + * Denis Ciocca + */ + +#ifndef ST_LSM6DSX_H +#define ST_LSM6DSX_H + +#include +#include +#include + +#define ST_LSM6DS3_DEV_NAME "lsm6ds3" +#define ST_LSM6DS3H_DEV_NAME "lsm6ds3h" +#define ST_LSM6DSL_DEV_NAME "lsm6dsl" +#define ST_LSM6DSM_DEV_NAME "lsm6dsm" +#define ST_ISM330DLC_DEV_NAME "ism330dlc" +#define ST_LSM6DSO_DEV_NAME "lsm6dso" +#define ST_ASM330LHH_DEV_NAME "asm330lhh" +#define ST_LSM6DSOX_DEV_NAME "lsm6dsox" +#define ST_LSM6DSR_DEV_NAME "lsm6dsr" +#define ST_LSM6DS3TRC_DEV_NAME "lsm6ds3tr-c" +#define ST_ISM330DHCX_DEV_NAME "ism330dhcx" +#define ST_LSM9DS1_DEV_NAME "lsm9ds1-imu" +#define ST_LSM6DS0_DEV_NAME "lsm6ds0" +#define ST_LSM6DSRX_DEV_NAME "lsm6dsrx" +#define ST_LSM6DST_DEV_NAME "lsm6dst" +#define ST_LSM6DSOP_DEV_NAME "lsm6dsop" +#define ST_ASM330LHHX_DEV_NAME "asm330lhhx" +#define ST_LSM6DSTX_DEV_NAME "lsm6dstx" +#define ST_LSM6DSV_DEV_NAME "lsm6dsv" +#define ST_LSM6DSV16X_DEV_NAME "lsm6dsv16x" +#define ST_LSM6DSO16IS_DEV_NAME "lsm6dso16is" +#define ST_ISM330IS_DEV_NAME "ism330is" +#define ST_ASM330LHB_DEV_NAME "asm330lhb" +#define ST_ASM330LHHXG1_DEV_NAME "asm330lhhxg1" + +enum st_lsm6dsx_hw_id { + ST_LSM6DS3_ID = 1, + ST_LSM6DS3H_ID, + ST_LSM6DSL_ID, + ST_LSM6DSM_ID, + ST_ISM330DLC_ID, + ST_LSM6DSO_ID, + ST_ASM330LHH_ID, + ST_LSM6DSOX_ID, + ST_LSM6DSR_ID, + ST_LSM6DS3TRC_ID, + ST_ISM330DHCX_ID, + ST_LSM9DS1_ID, + ST_LSM6DS0_ID, + ST_LSM6DSRX_ID, + ST_LSM6DST_ID, + ST_LSM6DSOP_ID, + ST_ASM330LHHX_ID, + ST_LSM6DSTX_ID, + ST_LSM6DSV_ID, + ST_LSM6DSV16X_ID, + ST_LSM6DSO16IS_ID, + ST_ISM330IS_ID, + ST_ASM330LHB_ID, + ST_ASM330LHHXG1_ID, + ST_LSM6DSX_MAX_ID, +}; + +#define ST_LSM6DSX_BUFF_SIZE 512 +#define ST_LSM6DSX_CHAN_SIZE 2 +#define ST_LSM6DSX_SAMPLE_SIZE 6 +#define ST_LSM6DSX_TAG_SIZE 1 +#define ST_LSM6DSX_TAGGED_SAMPLE_SIZE (ST_LSM6DSX_SAMPLE_SIZE + \ + ST_LSM6DSX_TAG_SIZE) +#define ST_LSM6DSX_MAX_WORD_LEN ((32 / ST_LSM6DSX_SAMPLE_SIZE) * \ + ST_LSM6DSX_SAMPLE_SIZE) +#define ST_LSM6DSX_MAX_TAGGED_WORD_LEN ((32 / ST_LSM6DSX_TAGGED_SAMPLE_SIZE) \ + * ST_LSM6DSX_TAGGED_SAMPLE_SIZE) +#define ST_LSM6DSX_SHIFT_VAL(val, mask) (((val) << __ffs(mask)) & (mask)) +#define st_lsm6dsx_field_get(mask, reg) ((reg & mask) >> __ffs(mask)) + +#define ST_LSM6DSX_CHANNEL_ACC(addr, mod, scan_idx, events) \ +{ \ + .type = IIO_ACCEL, \ + .address = addr, \ + .modified = 1, \ + .channel2 = mod, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = scan_idx, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ + .event_spec = events, \ + .num_event_specs = ARRAY_SIZE(events), \ + .ext_info = st_lsm6dsx_ext_info, \ +} + +#define ST_LSM6DSX_CHANNEL(chan_type, addr, mod, scan_idx) \ +{ \ + .type = chan_type, \ + .address = addr, \ + .modified = 1, \ + .channel2 = mod, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = scan_idx, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ + .ext_info = st_lsm6dsx_ext_info, \ +} + +struct st_lsm6dsx_reg { + u8 addr; + u8 mask; +}; + +struct st_lsm6dsx_sensor; +struct st_lsm6dsx_hw; + +struct st_lsm6dsx_odr { + u32 milli_hz; + u8 val; +}; + +#define ST_LSM6DSX_ODR_LIST_SIZE 8 +struct st_lsm6dsx_odr_table_entry { + struct st_lsm6dsx_reg reg; + + struct st_lsm6dsx_odr odr_avl[ST_LSM6DSX_ODR_LIST_SIZE]; + int odr_len; +}; + +struct st_lsm6dsx_samples_to_discard { + struct { + u32 milli_hz; + u16 samples; + } val[ST_LSM6DSX_ODR_LIST_SIZE]; +}; + +struct st_lsm6dsx_fs { + u32 gain; + u8 val; +}; + +#define ST_LSM6DSX_FS_LIST_SIZE 4 +struct st_lsm6dsx_fs_table_entry { + struct st_lsm6dsx_reg reg; + + struct st_lsm6dsx_fs fs_avl[ST_LSM6DSX_FS_LIST_SIZE]; + int fs_len; +}; + +/** + * struct st_lsm6dsx_fifo_ops - ST IMU FIFO settings + * @update_fifo: Update FIFO configuration callback. + * @read_fifo: Read FIFO callback. + * @fifo_th: FIFO threshold register info (addr + mask). + * @fifo_diff: FIFO diff status register info (addr + mask). + * @max_size: Sensor max fifo length in FIFO words. + * @th_wl: FIFO threshold word length. + */ +struct st_lsm6dsx_fifo_ops { + int (*update_fifo)(struct st_lsm6dsx_sensor *sensor, bool enable); + int (*read_fifo)(struct st_lsm6dsx_hw *hw); + struct { + u8 addr; + u16 mask; + } fifo_th; + struct { + u8 addr; + u16 mask; + } fifo_diff; + u16 max_size; + u8 th_wl; +}; + +/** + * struct st_lsm6dsx_hw_ts_settings - ST IMU hw timer settings + * @timer_en: Hw timer enable register info (addr + mask). + * @hr_timer: Hw timer resolution register info (addr + mask). + * @fifo_en: Hw timer FIFO enable register info (addr + mask). + * @decimator: Hw timer FIFO decimator register info (addr + mask). + * @freq_fine: Difference in % of ODR with respect to the typical. + * @ts_sensitivity: Nominal timestamp sensitivity. + * @ts_trim_coeff: Coefficient for calculating the calibrated timestamp gain. + * This coefficient comes into play when linearizing the formula + * used to calculate the calibrated timestamp (please see the + * relevant formula in the AN for the specific IMU). + * For example, in the case of LSM6DSO we have: + * + * 1 / (1 + x) ~= 1 - x (Taylor’s Series) + * ttrim[s] = 1 / (40000 * (1 + 0.0015 * val)) (from AN5192) + * ttrim[ns] ~= 25000 - 37.5 * val + * ttrim[ns] ~= 25000 - (37500 * val) / 1000 + * + * so, replacing ts_sensitivity = 25000 and + * ts_trim_coeff = 37500 + * + * ttrim[ns] ~= ts_sensitivity - (ts_trim_coeff * val) / 1000 + */ +struct st_lsm6dsx_hw_ts_settings { + struct st_lsm6dsx_reg timer_en; + struct st_lsm6dsx_reg hr_timer; + struct st_lsm6dsx_reg fifo_en; + struct st_lsm6dsx_reg decimator; + u8 freq_fine; + u16 ts_sensitivity; + u16 ts_trim_coeff; +}; + +/** + * struct st_lsm6dsx_shub_settings - ST IMU hw i2c controller settings + * @page_mux: register page mux info (addr + mask). + * @master_en: master config register info (addr + mask). + * @pullup_en: i2c controller pull-up register info (addr + mask). + * @aux_sens: aux sensor register info (addr + mask). + * @wr_once: write_once register info (addr + mask). + * @emb_func: embedded function register info (addr + mask). + * @num_ext_dev: max number of slave devices. + * @shub_out: sensor hub first output register info. + * @slv0_addr: slave0 address in secondary page. + * @dw_slv0_addr: slave0 write register address in secondary page. + * @batch_en: Enable/disable FIFO batching. + * @pause: controller pause value. + */ +struct st_lsm6dsx_shub_settings { + struct st_lsm6dsx_reg page_mux; + struct { + bool sec_page; + u8 addr; + u8 mask; + } master_en; + struct { + bool sec_page; + u8 addr; + u8 mask; + } pullup_en; + struct st_lsm6dsx_reg aux_sens; + struct st_lsm6dsx_reg wr_once; + struct st_lsm6dsx_reg emb_func; + u8 num_ext_dev; + struct { + bool sec_page; + u8 addr; + } shub_out; + u8 slv0_addr; + u8 dw_slv0_addr; + u8 batch_en; + u8 pause; +}; + +enum st_lsm6dsx_event_id { + ST_LSM6DSX_EVENT_WAKEUP, + ST_LSM6DSX_EVENT_TAP, + ST_LSM6DSX_EVENT_MAX +}; + +struct st_lsm6dsx_event_src { + struct st_lsm6dsx_reg value; + struct st_lsm6dsx_reg x_value; + struct st_lsm6dsx_reg y_value; + struct st_lsm6dsx_reg z_value; + u8 enable_mask; + u8 enable_axis_reg; + u8 enable_x_mask; + u8 enable_y_mask; + u8 enable_z_mask; + struct st_lsm6dsx_reg status; + u8 status_x_mask; + u8 status_y_mask; + u8 status_z_mask; +}; + +struct st_lsm6dsx_event_settings { + struct st_lsm6dsx_reg enable_reg; + struct st_lsm6dsx_event_src sources[ST_LSM6DSX_EVENT_MAX]; +}; + +enum st_lsm6dsx_sensor_id { + ST_LSM6DSX_ID_GYRO, + ST_LSM6DSX_ID_ACC, + ST_LSM6DSX_ID_EXT0, + ST_LSM6DSX_ID_EXT1, + ST_LSM6DSX_ID_EXT2, + ST_LSM6DSX_ID_FUSION, + ST_LSM6DSX_ID_MAX +}; + +enum st_lsm6dsx_ext_sensor_id { + ST_LSM6DSX_ID_MAGN, +}; + +struct st_lsm6dsx_fusion_settings { + const struct iio_chan_spec *chan; + int chan_len; + struct st_lsm6dsx_reg odr_reg; + int odr_hz[ST_LSM6DSX_ODR_LIST_SIZE]; + int odr_len; + struct st_lsm6dsx_reg fifo_enable; + struct st_lsm6dsx_reg page_mux; + struct st_lsm6dsx_reg enable; +}; + +/** + * struct st_lsm6dsx_ext_dev_settings - i2c controller slave settings + * @i2c_addr: I2c slave address list. + * @wai: Wai address info. + * @id: external sensor id. + * @odr_table: Output data rate of the sensor [Hz]. + * @fs_table: Configured sensor sensitivity table depending on full scale. + * @temp_comp: Temperature compensation register info (addr + mask). + * @pwr_table: Power on register info (addr + mask). + * @off_canc: Offset cancellation register info (addr + mask). + * @bdu: Block data update register info (addr + mask). + * @out: Output register info. + */ +struct st_lsm6dsx_ext_dev_settings { + u8 i2c_addr[2]; + struct { + u8 addr; + u8 val; + } wai; + enum st_lsm6dsx_ext_sensor_id id; + struct st_lsm6dsx_odr_table_entry odr_table; + struct st_lsm6dsx_fs_table_entry fs_table; + struct st_lsm6dsx_reg temp_comp; + struct { + struct st_lsm6dsx_reg reg; + u8 off_val; + u8 on_val; + } pwr_table; + struct st_lsm6dsx_reg off_canc; + struct st_lsm6dsx_reg bdu; + struct { + u8 addr; + u8 len; + } out; +}; + +/** + * struct st_lsm6dsx_settings - ST IMU sensor settings + * @reset: register address for reset. + * @boot: register address for boot. + * @bdu: register address for Block Data Update. + * @id: List of hw id/device name supported by the driver configuration. + * @channels: IIO channels supported by the device. + * @irq_config: interrupts related registers. + * @drdy_mask: register info for data-ready mask (addr + mask). + * @odr_table: Hw sensors odr table (Hz + val). + * @samples_to_discard: Number of samples to discard for filters settling time. + * @fs_table: Hw sensors gain table (gain + val). + * @decimator: List of decimator register info (addr + mask). + * @batch: List of FIFO batching register info (addr + mask). + * @fifo_ops: Sensor hw FIFO parameters. + * @ts_settings: Hw timer related settings. + * @shub_settings: i2c controller related settings. + */ +struct st_lsm6dsx_settings { + struct st_lsm6dsx_reg reset; + struct st_lsm6dsx_reg boot; + struct st_lsm6dsx_reg bdu; + struct { + enum st_lsm6dsx_hw_id hw_id; + const char *name; + u8 wai; + } id[ST_LSM6DSX_MAX_ID]; + struct { + const struct iio_chan_spec *chan; + int len; + } channels[2]; + struct { + struct st_lsm6dsx_reg irq1; + struct st_lsm6dsx_reg irq2; + u8 irq1_func; + u8 irq2_func; + struct st_lsm6dsx_reg lir; + struct st_lsm6dsx_reg clear_on_read; + struct st_lsm6dsx_reg hla; + struct st_lsm6dsx_reg od; + } irq_config; + struct st_lsm6dsx_reg drdy_mask; + struct st_lsm6dsx_odr_table_entry odr_table[2]; + struct st_lsm6dsx_samples_to_discard samples_to_discard[2]; + struct st_lsm6dsx_fs_table_entry fs_table[2]; + struct st_lsm6dsx_reg decimator[ST_LSM6DSX_ID_MAX]; + struct st_lsm6dsx_reg batch[2]; + struct st_lsm6dsx_fifo_ops fifo_ops; + struct st_lsm6dsx_hw_ts_settings ts_settings; + struct st_lsm6dsx_shub_settings shub_settings; + struct st_lsm6dsx_event_settings event_settings; + struct st_lsm6dsx_fusion_settings fusion_settings; +}; + +enum st_lsm6dsx_fifo_mode { + ST_LSM6DSX_FIFO_BYPASS = 0x0, + ST_LSM6DSX_FIFO_CONT = 0x6, +}; + +/** + * struct st_lsm6dsx_sensor - ST IMU sensor instance + * @name: Sensor name. + * @id: Sensor identifier. + * @hw: Pointer to instance of struct st_lsm6dsx_hw. + * @gain: Configured sensor sensitivity. + * @odr: Output data rate of the sensor [mHz]. + * hwfifo_odr_mHz: Batch data rate for hardware FIFO [mHz] + * @samples_to_discard: Number of samples to discard for filters settling time. + * @watermark: Sensor watermark level. + * @decimator: Sensor decimation factor. + * @sip: Number of samples in a given pattern. + * @ts_ref: Sensor timestamp reference for hw one. + * @ext_info: Sensor settings if it is connected to i2c controller + */ +struct st_lsm6dsx_sensor { + char name[32]; + enum st_lsm6dsx_sensor_id id; + struct st_lsm6dsx_hw *hw; + + u32 gain; + u32 odr; + u32 hwfifo_odr_mHz; + + u16 samples_to_discard; + u16 watermark; + u8 decimator; + u8 sip; + s64 ts_ref; + + struct { + const struct st_lsm6dsx_ext_dev_settings *settings; + u32 slv_odr; + u8 addr; + } ext_info; +}; + +/** + * struct st_lsm6dsx_hw - ST IMU MEMS hw instance + * @dev: Pointer to instance of struct device (I2C or SPI). + * @regmap: Register map of the device. + * @irq: Device interrupt line (I2C or SPI). + * @fifo_lock: Mutex to prevent concurrent access to the hw FIFO. + * @conf_lock: Mutex to prevent concurrent FIFO configuration update. + * @page_lock: Mutex to prevent concurrent memory page configuration. + * @suspend_mask: Suspended sensor bitmask. + * @enable_mask: Enabled sensor bitmask. + * @fifo_mask: Enabled hw FIFO bitmask. + * @ts_gain: Hw timestamp rate after internal calibration. + * @ts_sip: Total number of timestamp samples in a given pattern. + * @sip: Total number of samples (acc/gyro/ts) in a given pattern. + * @buff: Device read buffer. + * @irq_routing: pointer to interrupt routing configuration. + * @enable_event: enabled event bitmask. + * @iio_devs: Pointers to acc/gyro iio_dev instances. + * @settings: Pointer to the specific sensor settings in use. + * @orientation: sensor chip orientation relative to main hardware. + * @scan: Temporary buffers used to align data before iio_push_to_buffers() + */ +struct st_lsm6dsx_hw { + struct device *dev; + struct regmap *regmap; + int irq; + + struct mutex fifo_lock; + struct mutex conf_lock; + struct mutex page_lock; + + u8 suspend_mask; + u8 enable_mask; + u8 fifo_mask; + s64 ts_gain; + u8 ts_sip; + u8 sip; + + u8 irq_routing; + u8 enable_event[ST_LSM6DSX_EVENT_MAX]; + + u8 *buff; + + struct iio_dev *iio_devs[ST_LSM6DSX_ID_MAX]; + + const struct st_lsm6dsx_settings *settings; + + struct iio_mount_matrix orientation; + /* Ensure natural alignment of buffer elements */ + struct { + __le16 channels[3]; + aligned_s64 ts; + } scan[ST_LSM6DSX_ID_MAX]; +}; + +static __maybe_unused const unsigned long st_lsm6dsx_available_scan_masks[] = { + 0x7, 0x0, +}; + +extern const struct dev_pm_ops st_lsm6dsx_pm_ops; + +int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, + struct regmap *regmap); +int st_lsm6dsx_sensor_set_enable(struct st_lsm6dsx_sensor *sensor, + bool enable); +int st_lsm6dsx_fifo_setup(struct st_lsm6dsx_hw *hw); +int st_lsm6dsx_set_watermark(struct iio_dev *iio_dev, unsigned int val); +int st_lsm6dsx_update_watermark(struct st_lsm6dsx_sensor *sensor, + u16 watermark); +int st_lsm6dsx_update_fifo(struct st_lsm6dsx_sensor *sensor, bool enable); +int st_lsm6dsx_flush_fifo(struct st_lsm6dsx_hw *hw); +int st_lsm6dsx_resume_fifo(struct st_lsm6dsx_hw *hw); +int st_lsm6dsx_read_fifo(struct st_lsm6dsx_hw *hw); +int st_lsm6dsx_read_tagged_fifo(struct st_lsm6dsx_hw *hw); +int st_lsm6dsx_check_odr(struct st_lsm6dsx_sensor *sensor, u32 odr, u8 *val); +int st_lsm6dsx_shub_probe(struct st_lsm6dsx_hw *hw, const char *name); +int st_lsm6dsx_shub_set_enable(struct st_lsm6dsx_sensor *sensor, bool enable); +int st_lsm6dsx_shub_read_output(struct st_lsm6dsx_hw *hw, u8 *data, int len); +int st_lsm6dsx_fusion_probe(struct st_lsm6dsx_hw *hw, const char *name); +int st_lsm6dsx_fusion_set_enable(struct st_lsm6dsx_sensor *sensor, bool enable); +int st_lsm6dsx_fusion_set_odr(struct st_lsm6dsx_sensor *sensor, bool enable); +int st_lsm6dsx_set_page(struct st_lsm6dsx_hw *hw, bool enable); + +static inline int +st_lsm6dsx_update_bits_locked(struct st_lsm6dsx_hw *hw, unsigned int addr, + unsigned int mask, unsigned int val) +{ + int err; + + mutex_lock(&hw->page_lock); + err = regmap_update_bits(hw->regmap, addr, mask, val); + mutex_unlock(&hw->page_lock); + + return err; +} + +static inline int +st_lsm6dsx_read_locked(struct st_lsm6dsx_hw *hw, unsigned int addr, + void *val, unsigned int len) +{ + int err; + + mutex_lock(&hw->page_lock); + err = regmap_bulk_read(hw->regmap, addr, val, len); + mutex_unlock(&hw->page_lock); + + return err; +} + +static inline int +st_lsm6dsx_write_locked(struct st_lsm6dsx_hw *hw, unsigned int addr, + unsigned int val) +{ + int err; + + mutex_lock(&hw->page_lock); + err = regmap_write(hw->regmap, addr, val); + mutex_unlock(&hw->page_lock); + + return err; +} + +static inline const struct iio_mount_matrix * +st_lsm6dsx_get_mount_matrix(const struct iio_dev *iio_dev, + const struct iio_chan_spec *chan) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + struct st_lsm6dsx_hw *hw = sensor->hw; + + return &hw->orientation; +} + +static inline int +st_lsm6dsx_device_set_enable(struct st_lsm6dsx_sensor *sensor, bool enable) +{ + switch (sensor->id) { + case ST_LSM6DSX_ID_EXT0 ... ST_LSM6DSX_ID_EXT2: + return st_lsm6dsx_shub_set_enable(sensor, enable); + case ST_LSM6DSX_ID_FUSION: + return st_lsm6dsx_fusion_set_enable(sensor, enable); + default: + return st_lsm6dsx_sensor_set_enable(sensor, enable); + } +} + +static const +struct iio_chan_spec_ext_info __maybe_unused st_lsm6dsx_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, st_lsm6dsx_get_mount_matrix), + { } +}; + +#endif /* ST_LSM6DSX_H */ diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c new file mode 100644 index 000000000..777d479f1 --- /dev/null +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c @@ -0,0 +1,904 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics st_lsm6dsx FIFO buffer library driver + * + * Pattern FIFO: + * The FIFO buffer can be configured to store data from gyroscope and + * accelerometer. Samples are queued without any tag according to a + * specific pattern based on 'FIFO data sets' (6 bytes each): + * - 1st data set is reserved for gyroscope data + * - 2nd data set is reserved for accelerometer data + * The FIFO pattern changes depending on the ODRs and decimation factors + * assigned to the FIFO data sets. The first sequence of data stored in FIFO + * buffer contains the data of all the enabled FIFO data sets + * (e.g. Gx, Gy, Gz, Ax, Ay, Az), then data are repeated depending on the + * value of the decimation factor and ODR set for each FIFO data set. + * + * Supported devices: + * - ISM330DLC + * - LSM6DS3 + * - LSM6DS3H + * - LSM6DS3TR-C + * - LSM6DSL + * - LSM6DSM + * + * Tagged FIFO: + * The FIFO buffer can be configured to store data from gyroscope and + * accelerometer. Each sample is queued with a tag (1B) indicating data + * source (gyroscope, accelerometer, hw timer). + * + * Supported devices: + * - ASM330LHB + * - ASM330LHH + * - ASM330LHHX + * - ASM330LHHXG1 + * - ISM330DHCX + * - LSM6DSO + * - LSM6DSOP + * - LSM6DSOX + * - LSM6DSR + * - LSM6DSRX + * - LSM6DST + * - LSM6DSTX + * - LSM6DSV + * + * FIFO supported modes: + * - BYPASS: FIFO disabled + * - CONTINUOUS: FIFO enabled. When the buffer is full, the FIFO index + * restarts from the beginning and the oldest sample is overwritten + * + * Copyright 2016 STMicroelectronics Inc. + * + * Lorenzo Bianconi + * Denis Ciocca + */ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "st_lsm6dsx.h" + +#define ST_LSM6DSX_REG_FIFO_MODE_ADDR 0x0a +#define ST_LSM6DSX_FIFO_MODE_MASK GENMASK(2, 0) +#define ST_LSM6DSX_FIFO_ODR_MASK GENMASK(6, 3) +#define ST_LSM6DSX_FIFO_EMPTY_MASK BIT(12) +#define ST_LSM6DSX_REG_FIFO_OUTL_ADDR 0x3e +#define ST_LSM6DSX_REG_FIFO_OUT_TAG_ADDR 0x78 +#define ST_LSM6DSX_REG_TS_RESET_ADDR 0x42 + +#define ST_LSM6DSX_MAX_FIFO_ODR_VAL 0x08 + +#define ST_LSM6DSX_TS_RESET_VAL 0xaa + +struct st_lsm6dsx_decimator_entry { + u8 decimator; + u8 val; +}; + +enum st_lsm6dsx_fifo_tag { + ST_LSM6DSX_GYRO_TAG = 0x01, + ST_LSM6DSX_ACC_TAG = 0x02, + ST_LSM6DSX_TS_TAG = 0x04, + ST_LSM6DSX_EXT0_TAG = 0x0f, + ST_LSM6DSX_EXT1_TAG = 0x10, + ST_LSM6DSX_EXT2_TAG = 0x11, + ST_LSM6DSX_ROT_TAG = 0x13, +}; + +static const +struct st_lsm6dsx_decimator_entry st_lsm6dsx_decimator_table[] = { + { 0, 0x0 }, + { 1, 0x1 }, + { 2, 0x2 }, + { 3, 0x3 }, + { 4, 0x4 }, + { 8, 0x5 }, + { 16, 0x6 }, + { 32, 0x7 }, +}; + +static int +st_lsm6dsx_get_decimator_val(struct st_lsm6dsx_sensor *sensor, u32 max_odr) +{ + const int max_size = ARRAY_SIZE(st_lsm6dsx_decimator_table); + u32 decimator = max_odr / sensor->hwfifo_odr_mHz; + int i; + + if (decimator > 1) + decimator = round_down(decimator, 2); + + for (i = 0; i < max_size; i++) { + if (st_lsm6dsx_decimator_table[i].decimator == decimator) + break; + } + + sensor->decimator = decimator; + return i == max_size ? 0 : st_lsm6dsx_decimator_table[i].val; +} + +static void st_lsm6dsx_get_max_min_odr(struct st_lsm6dsx_hw *hw, + u32 *max_odr, u32 *min_odr) +{ + struct st_lsm6dsx_sensor *sensor; + int i; + + *max_odr = 0, *min_odr = ~0; + for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) { + if (!hw->iio_devs[i]) + continue; + + sensor = iio_priv(hw->iio_devs[i]); + + if (!(hw->enable_mask & BIT(sensor->id))) + continue; + + *max_odr = max(*max_odr, sensor->hwfifo_odr_mHz); + *min_odr = min(*min_odr, sensor->hwfifo_odr_mHz); + } +} + +static u8 st_lsm6dsx_get_sip(struct st_lsm6dsx_sensor *sensor, u32 min_odr) +{ + u8 sip = sensor->hwfifo_odr_mHz / min_odr; + + return sip > 1 ? round_down(sip, 2) : sip; +} + +static int st_lsm6dsx_update_decimators(struct st_lsm6dsx_hw *hw) +{ + const struct st_lsm6dsx_reg *ts_dec_reg; + struct st_lsm6dsx_sensor *sensor; + u16 sip = 0, ts_sip = 0; + u32 max_odr, min_odr; + int err = 0, i; + u8 data; + + st_lsm6dsx_get_max_min_odr(hw, &max_odr, &min_odr); + + for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) { + const struct st_lsm6dsx_reg *dec_reg; + + if (!hw->iio_devs[i]) + continue; + + sensor = iio_priv(hw->iio_devs[i]); + /* update fifo decimators and sample in pattern */ + if (hw->enable_mask & BIT(sensor->id)) { + sensor->sip = st_lsm6dsx_get_sip(sensor, min_odr); + data = st_lsm6dsx_get_decimator_val(sensor, max_odr); + } else { + sensor->sip = 0; + data = 0; + } + ts_sip = max_t(u16, ts_sip, sensor->sip); + + dec_reg = &hw->settings->decimator[sensor->id]; + if (dec_reg->addr) { + int val = ST_LSM6DSX_SHIFT_VAL(data, dec_reg->mask); + + err = st_lsm6dsx_update_bits_locked(hw, dec_reg->addr, + dec_reg->mask, + val); + if (err < 0) + return err; + } + sip += sensor->sip; + } + hw->sip = sip + ts_sip; + hw->ts_sip = ts_sip; + + /* + * update hw ts decimator if necessary. Decimator for hw timestamp + * is always 1 or 0 in order to have a ts sample for each data + * sample in FIFO + */ + ts_dec_reg = &hw->settings->ts_settings.decimator; + if (ts_dec_reg->addr) { + int val, ts_dec = !!hw->ts_sip; + + val = ST_LSM6DSX_SHIFT_VAL(ts_dec, ts_dec_reg->mask); + err = st_lsm6dsx_update_bits_locked(hw, ts_dec_reg->addr, + ts_dec_reg->mask, val); + } + return err; +} + +static int st_lsm6dsx_set_fifo_mode(struct st_lsm6dsx_hw *hw, + enum st_lsm6dsx_fifo_mode fifo_mode) +{ + unsigned int data; + + data = FIELD_PREP(ST_LSM6DSX_FIFO_MODE_MASK, fifo_mode); + return st_lsm6dsx_update_bits_locked(hw, ST_LSM6DSX_REG_FIFO_MODE_ADDR, + ST_LSM6DSX_FIFO_MODE_MASK, data); +} + +static int st_lsm6dsx_set_fifo_odr(struct st_lsm6dsx_sensor *sensor, + bool enable) +{ + struct st_lsm6dsx_hw *hw = sensor->hw; + const struct st_lsm6dsx_reg *batch_reg; + u8 data; + + /* Only internal sensors have a FIFO ODR configuration register. */ + if (sensor->id >= ARRAY_SIZE(hw->settings->batch)) { + if (sensor->id == ST_LSM6DSX_ID_FUSION) + return st_lsm6dsx_fusion_set_odr(sensor, enable); + return 0; + } + + batch_reg = &hw->settings->batch[sensor->id]; + if (batch_reg->addr) { + int val; + + if (enable) { + int err; + + err = st_lsm6dsx_check_odr(sensor, sensor->hwfifo_odr_mHz, + &data); + if (err < 0) + return err; + } else { + data = 0; + } + val = ST_LSM6DSX_SHIFT_VAL(data, batch_reg->mask); + return st_lsm6dsx_update_bits_locked(hw, batch_reg->addr, + batch_reg->mask, val); + } else { + data = hw->enable_mask ? ST_LSM6DSX_MAX_FIFO_ODR_VAL : 0; + return st_lsm6dsx_update_bits_locked(hw, + ST_LSM6DSX_REG_FIFO_MODE_ADDR, + ST_LSM6DSX_FIFO_ODR_MASK, + FIELD_PREP(ST_LSM6DSX_FIFO_ODR_MASK, + data)); + } +} + +int st_lsm6dsx_update_watermark(struct st_lsm6dsx_sensor *sensor, u16 watermark) +{ + u16 fifo_watermark = ~0, cur_watermark, fifo_th_mask; + struct st_lsm6dsx_hw *hw = sensor->hw; + struct st_lsm6dsx_sensor *cur_sensor; + int i, err, data; + __le16 wdata; + + if (!hw->sip) + return 0; + + for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) { + if (!hw->iio_devs[i]) + continue; + + cur_sensor = iio_priv(hw->iio_devs[i]); + + if (!(hw->enable_mask & BIT(cur_sensor->id))) + continue; + + cur_watermark = (cur_sensor == sensor) ? watermark + : cur_sensor->watermark; + + fifo_watermark = min_t(u16, fifo_watermark, cur_watermark); + } + + fifo_watermark = max_t(u16, fifo_watermark, hw->sip); + fifo_watermark = (fifo_watermark / hw->sip) * hw->sip; + fifo_watermark = fifo_watermark * hw->settings->fifo_ops.th_wl; + + mutex_lock(&hw->page_lock); + err = regmap_read(hw->regmap, hw->settings->fifo_ops.fifo_th.addr + 1, + &data); + if (err < 0) + goto out; + + fifo_th_mask = hw->settings->fifo_ops.fifo_th.mask; + fifo_watermark = ((data << 8) & ~fifo_th_mask) | + (fifo_watermark & fifo_th_mask); + + wdata = cpu_to_le16(fifo_watermark); + err = regmap_bulk_write(hw->regmap, + hw->settings->fifo_ops.fifo_th.addr, + &wdata, sizeof(wdata)); +out: + mutex_unlock(&hw->page_lock); + return err; +} + +static int st_lsm6dsx_reset_hw_ts(struct st_lsm6dsx_hw *hw) +{ + struct st_lsm6dsx_sensor *sensor; + int i, err; + + /* reset hw ts counter */ + err = st_lsm6dsx_write_locked(hw, ST_LSM6DSX_REG_TS_RESET_ADDR, + ST_LSM6DSX_TS_RESET_VAL); + if (err < 0) + return err; + + for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) { + if (!hw->iio_devs[i]) + continue; + + sensor = iio_priv(hw->iio_devs[i]); + /* + * store enable buffer timestamp as reference for + * hw timestamp + */ + sensor->ts_ref = iio_get_time_ns(hw->iio_devs[i]); + } + return 0; +} + +int st_lsm6dsx_resume_fifo(struct st_lsm6dsx_hw *hw) +{ + int err; + + /* reset hw ts counter */ + err = st_lsm6dsx_reset_hw_ts(hw); + if (err < 0) + return err; + + return st_lsm6dsx_set_fifo_mode(hw, ST_LSM6DSX_FIFO_CONT); +} + +/* + * Set max bulk read to ST_LSM6DSX_MAX_WORD_LEN/ST_LSM6DSX_MAX_TAGGED_WORD_LEN + * in order to avoid a kmalloc for each bus access + */ +static inline int st_lsm6dsx_read_block(struct st_lsm6dsx_hw *hw, u8 addr, + u8 *data, unsigned int data_len, + unsigned int max_word_len) +{ + unsigned int word_len, read_len = 0; + int err; + + while (read_len < data_len) { + word_len = min_t(unsigned int, data_len - read_len, + max_word_len); + err = st_lsm6dsx_read_locked(hw, addr, data + read_len, + word_len); + if (err < 0) + return err; + read_len += word_len; + } + return 0; +} + +/** + * st_lsm6dsx_read_fifo() - hw FIFO read routine + * @hw: Pointer to instance of struct st_lsm6dsx_hw. + * + * Read samples from the hw FIFO and push them to IIO buffers. + * + * Return: Number of bytes read from the FIFO + */ +int st_lsm6dsx_read_fifo(struct st_lsm6dsx_hw *hw) +{ + struct st_lsm6dsx_sensor *acc_sensor, *gyro_sensor, *ext_sensor = NULL; + int err, sip, acc_sip, gyro_sip, ts_sip, ext_sip, read_len, offset; + u16 fifo_len, pattern_len = hw->sip * ST_LSM6DSX_SAMPLE_SIZE; + u16 fifo_diff_mask = hw->settings->fifo_ops.fifo_diff.mask; + bool reset_ts = false; + __le16 fifo_status; + s64 ts = 0; + + err = st_lsm6dsx_read_locked(hw, + hw->settings->fifo_ops.fifo_diff.addr, + &fifo_status, sizeof(fifo_status)); + if (err < 0) { + dev_err(hw->dev, "failed to read fifo status (err=%d)\n", + err); + return err; + } + + if (fifo_status & cpu_to_le16(ST_LSM6DSX_FIFO_EMPTY_MASK)) + return 0; + + if (!pattern_len) + pattern_len = ST_LSM6DSX_SAMPLE_SIZE; + + fifo_len = (le16_to_cpu(fifo_status) & fifo_diff_mask) * + ST_LSM6DSX_CHAN_SIZE; + fifo_len = (fifo_len / pattern_len) * pattern_len; + + acc_sensor = iio_priv(hw->iio_devs[ST_LSM6DSX_ID_ACC]); + gyro_sensor = iio_priv(hw->iio_devs[ST_LSM6DSX_ID_GYRO]); + if (hw->iio_devs[ST_LSM6DSX_ID_EXT0]) + ext_sensor = iio_priv(hw->iio_devs[ST_LSM6DSX_ID_EXT0]); + + for (read_len = 0; read_len < fifo_len; read_len += pattern_len) { + err = st_lsm6dsx_read_block(hw, ST_LSM6DSX_REG_FIFO_OUTL_ADDR, + hw->buff, pattern_len, + ST_LSM6DSX_MAX_WORD_LEN); + if (err < 0) { + dev_err(hw->dev, + "failed to read pattern from fifo (err=%d)\n", + err); + return err; + } + + /* + * Data are written to the FIFO with a specific pattern + * depending on the configured ODRs. The first sequence of data + * stored in FIFO contains the data of all enabled sensors + * (e.g. Gx, Gy, Gz, Ax, Ay, Az, Ts), then data are repeated + * depending on the value of the decimation factor set for each + * sensor. + * + * Supposing the FIFO is storing data from gyroscope and + * accelerometer at different ODRs: + * - gyroscope ODR = 208Hz, accelerometer ODR = 104Hz + * Since the gyroscope ODR is twice the accelerometer one, the + * following pattern is repeated every 9 samples: + * - Gx, Gy, Gz, Ax, Ay, Az, Ts, Gx, Gy, Gz, Ts, Gx, .. + */ + ext_sip = ext_sensor ? ext_sensor->sip : 0; + gyro_sip = gyro_sensor->sip; + acc_sip = acc_sensor->sip; + ts_sip = hw->ts_sip; + offset = 0; + sip = 0; + + while (acc_sip > 0 || gyro_sip > 0 || ext_sip > 0) { + if (gyro_sip > 0 && !(sip % gyro_sensor->decimator)) { + memcpy(hw->scan[ST_LSM6DSX_ID_GYRO].channels, + &hw->buff[offset], + sizeof(hw->scan[ST_LSM6DSX_ID_GYRO].channels)); + offset += sizeof(hw->scan[ST_LSM6DSX_ID_GYRO].channels); + } + if (acc_sip > 0 && !(sip % acc_sensor->decimator)) { + memcpy(hw->scan[ST_LSM6DSX_ID_ACC].channels, + &hw->buff[offset], + sizeof(hw->scan[ST_LSM6DSX_ID_ACC].channels)); + offset += sizeof(hw->scan[ST_LSM6DSX_ID_ACC].channels); + } + if (ext_sip > 0 && !(sip % ext_sensor->decimator)) { + memcpy(hw->scan[ST_LSM6DSX_ID_EXT0].channels, + &hw->buff[offset], + sizeof(hw->scan[ST_LSM6DSX_ID_EXT0].channels)); + offset += sizeof(hw->scan[ST_LSM6DSX_ID_EXT0].channels); + } + + if (ts_sip-- > 0) { + u8 data[ST_LSM6DSX_SAMPLE_SIZE]; + + memcpy(data, &hw->buff[offset], sizeof(data)); + /* + * hw timestamp is 3B long and it is stored + * in FIFO using 6B as 4th FIFO data set + * according to this schema: + * B0 = ts[15:8], B1 = ts[23:16], B3 = ts[7:0] + */ + ts = data[1] << 16 | data[0] << 8 | data[3]; + /* + * check if hw timestamp engine is going to + * reset (the sensor generates an interrupt + * to signal the hw timestamp will reset in + * 1.638s) + */ + if (!reset_ts && ts >= 0xff0000) + reset_ts = true; + ts *= hw->ts_gain; + + offset += ST_LSM6DSX_SAMPLE_SIZE; + } + + if (gyro_sip > 0 && !(sip % gyro_sensor->decimator)) { + /* + * We need to discards gyro samples during + * filters settling time + */ + if (gyro_sensor->samples_to_discard > 0) + gyro_sensor->samples_to_discard--; + else + iio_push_to_buffers_with_timestamp( + hw->iio_devs[ST_LSM6DSX_ID_GYRO], + &hw->scan[ST_LSM6DSX_ID_GYRO], + gyro_sensor->ts_ref + ts); + gyro_sip--; + } + if (acc_sip > 0 && !(sip % acc_sensor->decimator)) { + /* + * We need to discards accel samples during + * filters settling time + */ + if (acc_sensor->samples_to_discard > 0) + acc_sensor->samples_to_discard--; + else + iio_push_to_buffers_with_timestamp( + hw->iio_devs[ST_LSM6DSX_ID_ACC], + &hw->scan[ST_LSM6DSX_ID_ACC], + acc_sensor->ts_ref + ts); + acc_sip--; + } + if (ext_sip > 0 && !(sip % ext_sensor->decimator)) { + iio_push_to_buffers_with_timestamp( + hw->iio_devs[ST_LSM6DSX_ID_EXT0], + &hw->scan[ST_LSM6DSX_ID_EXT0], + ext_sensor->ts_ref + ts); + ext_sip--; + } + sip++; + } + } + + if (unlikely(reset_ts)) { + err = st_lsm6dsx_reset_hw_ts(hw); + if (err < 0) { + dev_err(hw->dev, "failed to reset hw ts (err=%d)\n", + err); + return err; + } + } + return read_len; +} + +#define ST_LSM6DSX_INVALID_SAMPLE 0x7ffd +static bool st_lsm6dsx_check_data(u8 tag, __le16 *data) +{ + if ((tag == ST_LSM6DSX_GYRO_TAG || tag == ST_LSM6DSX_ACC_TAG) && + (s16)le16_to_cpup(data) >= ST_LSM6DSX_INVALID_SAMPLE) + return false; + + return true; +} + +static int st_lsm6dsx_push_tagged_data(struct st_lsm6dsx_hw *hw, u8 tag, + __le16 *data, s64 ts) +{ + struct st_lsm6dsx_sensor *sensor; + struct iio_dev *iio_dev; + + /* invalid sample during bootstrap phase */ + if (!st_lsm6dsx_check_data(tag, data)) + return -EINVAL; + + /* + * EXT_TAG are managed in FIFO fashion so ST_LSM6DSX_EXT0_TAG + * corresponds to the first enabled channel, ST_LSM6DSX_EXT1_TAG + * to the second one and ST_LSM6DSX_EXT2_TAG to the last enabled + * channel + */ + switch (tag) { + case ST_LSM6DSX_GYRO_TAG: + iio_dev = hw->iio_devs[ST_LSM6DSX_ID_GYRO]; + break; + case ST_LSM6DSX_ACC_TAG: + iio_dev = hw->iio_devs[ST_LSM6DSX_ID_ACC]; + break; + case ST_LSM6DSX_EXT0_TAG: + if (hw->enable_mask & BIT(ST_LSM6DSX_ID_EXT0)) + iio_dev = hw->iio_devs[ST_LSM6DSX_ID_EXT0]; + else if (hw->enable_mask & BIT(ST_LSM6DSX_ID_EXT1)) + iio_dev = hw->iio_devs[ST_LSM6DSX_ID_EXT1]; + else + iio_dev = hw->iio_devs[ST_LSM6DSX_ID_EXT2]; + break; + case ST_LSM6DSX_EXT1_TAG: + if ((hw->enable_mask & BIT(ST_LSM6DSX_ID_EXT0)) && + (hw->enable_mask & BIT(ST_LSM6DSX_ID_EXT1))) + iio_dev = hw->iio_devs[ST_LSM6DSX_ID_EXT1]; + else + iio_dev = hw->iio_devs[ST_LSM6DSX_ID_EXT2]; + break; + case ST_LSM6DSX_EXT2_TAG: + iio_dev = hw->iio_devs[ST_LSM6DSX_ID_EXT2]; + break; + case ST_LSM6DSX_ROT_TAG: + iio_dev = hw->iio_devs[ST_LSM6DSX_ID_FUSION]; + break; + default: + return -EINVAL; + } + + sensor = iio_priv(iio_dev); + iio_push_to_buffers_with_timestamp(iio_dev, data, + ts + sensor->ts_ref); + + return 0; +} + +/** + * st_lsm6dsx_read_tagged_fifo() - tagged hw FIFO read routine + * @hw: Pointer to instance of struct st_lsm6dsx_hw. + * + * Read samples from the hw FIFO and push them to IIO buffers. + * + * Return: Number of bytes read from the FIFO + */ +int st_lsm6dsx_read_tagged_fifo(struct st_lsm6dsx_hw *hw) +{ + u16 pattern_len = hw->sip * ST_LSM6DSX_TAGGED_SAMPLE_SIZE; + u16 fifo_len, fifo_diff_mask; + /* + * Alignment needed as this can ultimately be passed to a + * call to iio_push_to_buffers_with_timestamp() which + * must be passed a buffer that is aligned to 8 bytes so + * as to allow insertion of a naturally aligned timestamp. + */ + struct { + union { + __le16 data[3]; + __le32 fifo_ts; + }; + aligned_s64 timestamp; + } iio_buff = { }; + u8 tag; + bool reset_ts = false; + int i, err, read_len; + __le16 fifo_status; + s64 ts = 0; + + err = st_lsm6dsx_read_locked(hw, + hw->settings->fifo_ops.fifo_diff.addr, + &fifo_status, sizeof(fifo_status)); + if (err < 0) { + dev_err(hw->dev, "failed to read fifo status (err=%d)\n", + err); + return err; + } + + fifo_diff_mask = hw->settings->fifo_ops.fifo_diff.mask; + fifo_len = (le16_to_cpu(fifo_status) & fifo_diff_mask) * + ST_LSM6DSX_TAGGED_SAMPLE_SIZE; + if (!fifo_len) + return 0; + + if (!pattern_len) + pattern_len = ST_LSM6DSX_TAGGED_SAMPLE_SIZE; + + for (read_len = 0; read_len < fifo_len; read_len += pattern_len) { + err = st_lsm6dsx_read_block(hw, + ST_LSM6DSX_REG_FIFO_OUT_TAG_ADDR, + hw->buff, pattern_len, + ST_LSM6DSX_MAX_TAGGED_WORD_LEN); + if (err < 0) { + dev_err(hw->dev, + "failed to read pattern from fifo (err=%d)\n", + err); + return err; + } + + for (i = 0; i < pattern_len; + i += ST_LSM6DSX_TAGGED_SAMPLE_SIZE) { + memcpy(&iio_buff, &hw->buff[i + ST_LSM6DSX_TAG_SIZE], + ST_LSM6DSX_SAMPLE_SIZE); + + tag = hw->buff[i] >> 3; + if (tag == ST_LSM6DSX_TS_TAG) { + /* + * hw timestamp is 4B long and it is stored + * in FIFO according to this schema: + * B0 = ts[7:0], B1 = ts[15:8], B2 = ts[23:16], + * B3 = ts[31:24] + */ + ts = le32_to_cpu(iio_buff.fifo_ts); + /* + * check if hw timestamp engine is going to + * reset (the sensor generates an interrupt + * to signal the hw timestamp will reset in + * 1.638s) + */ + if (!reset_ts && ts >= 0xffff0000) + reset_ts = true; + ts *= hw->ts_gain; + } else { + st_lsm6dsx_push_tagged_data(hw, tag, + iio_buff.data, + ts); + } + } + } + + if (unlikely(reset_ts)) { + err = st_lsm6dsx_reset_hw_ts(hw); + if (err < 0) + return err; + } + return read_len; +} + +int st_lsm6dsx_flush_fifo(struct st_lsm6dsx_hw *hw) +{ + int err; + + if (!hw->settings->fifo_ops.read_fifo) + return -ENOTSUPP; + + mutex_lock(&hw->fifo_lock); + + hw->settings->fifo_ops.read_fifo(hw); + err = st_lsm6dsx_set_fifo_mode(hw, ST_LSM6DSX_FIFO_BYPASS); + + mutex_unlock(&hw->fifo_lock); + + return err; +} + +static void +st_lsm6dsx_update_samples_to_discard(struct st_lsm6dsx_sensor *sensor) +{ + const struct st_lsm6dsx_samples_to_discard *data; + struct st_lsm6dsx_hw *hw = sensor->hw; + int i; + + if (sensor->id != ST_LSM6DSX_ID_GYRO && + sensor->id != ST_LSM6DSX_ID_ACC) + return; + + /* check if drdy mask is supported in hw */ + if (hw->settings->drdy_mask.addr) + return; + + data = &hw->settings->samples_to_discard[sensor->id]; + for (i = 0; i < ST_LSM6DSX_ODR_LIST_SIZE; i++) { + if (data->val[i].milli_hz == sensor->hwfifo_odr_mHz) { + sensor->samples_to_discard = data->val[i].samples; + return; + } + } +} + +int st_lsm6dsx_update_fifo(struct st_lsm6dsx_sensor *sensor, bool enable) +{ + struct st_lsm6dsx_hw *hw = sensor->hw; + u8 fifo_mask; + int err; + + mutex_lock(&hw->conf_lock); + + if (enable) + fifo_mask = hw->fifo_mask | BIT(sensor->id); + else + fifo_mask = hw->fifo_mask & ~BIT(sensor->id); + + if (hw->fifo_mask) { + err = st_lsm6dsx_flush_fifo(hw); + if (err < 0) + goto out; + } + + if (enable) + st_lsm6dsx_update_samples_to_discard(sensor); + + err = st_lsm6dsx_device_set_enable(sensor, enable); + if (err < 0) + goto out; + + err = st_lsm6dsx_set_fifo_odr(sensor, enable); + if (err < 0) + goto out; + + err = st_lsm6dsx_update_decimators(hw); + if (err < 0) + goto out; + + err = st_lsm6dsx_update_watermark(sensor, sensor->watermark); + if (err < 0) + goto out; + + if (fifo_mask) { + err = st_lsm6dsx_resume_fifo(hw); + if (err < 0) + goto out; + } + + hw->fifo_mask = fifo_mask; + +out: + mutex_unlock(&hw->conf_lock); + + return err; +} + +static int st_lsm6dsx_buffer_preenable(struct iio_dev *iio_dev) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + struct st_lsm6dsx_hw *hw = sensor->hw; + + if (!hw->settings->fifo_ops.update_fifo) + return -ENOTSUPP; + + return hw->settings->fifo_ops.update_fifo(sensor, true); +} + +static int st_lsm6dsx_buffer_postdisable(struct iio_dev *iio_dev) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + struct st_lsm6dsx_hw *hw = sensor->hw; + + if (!hw->settings->fifo_ops.update_fifo) + return -ENOTSUPP; + + return hw->settings->fifo_ops.update_fifo(sensor, false); +} + +static const struct iio_buffer_setup_ops st_lsm6dsx_buffer_ops = { + .preenable = st_lsm6dsx_buffer_preenable, + .postdisable = st_lsm6dsx_buffer_postdisable, +}; + +static ssize_t st_lsm6dsx_hwfifo_odr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(dev_to_iio_dev(dev)); + + return sysfs_emit(buf, "%d.%03d\n", sensor->hwfifo_odr_mHz / 1000, + sensor->hwfifo_odr_mHz % 1000); +} + +static ssize_t st_lsm6dsx_hwfifo_odr_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *iio_dev = dev_to_iio_dev(dev); + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + int integer, milli; + int ret; + u32 hwfifo_odr; + u8 data; + + if (!iio_device_claim_direct(iio_dev)) + return -EBUSY; + + ret = iio_str_to_fixpoint(buf, 100, &integer, &milli); + if (ret) + goto out; + + hwfifo_odr = integer * 1000 + milli; + ret = st_lsm6dsx_check_odr(sensor, hwfifo_odr, &data); + if (ret < 0) + goto out; + + hwfifo_odr = ret; + + /* the batch data rate must not exceed the sensor output data rate */ + if (hwfifo_odr <= sensor->odr) + sensor->hwfifo_odr_mHz = hwfifo_odr; + else + ret = -EINVAL; + +out: + iio_device_release_direct(iio_dev); + + return ret < 0 ? ret : len; +} + +static IIO_DEV_ATTR_SAMP_FREQ(0664, st_lsm6dsx_hwfifo_odr_show, st_lsm6dsx_hwfifo_odr_store); + +static const struct iio_dev_attr *st_lsm6dsx_buffer_attrs[] = { + &iio_dev_attr_sampling_frequency, + NULL +}; + +int st_lsm6dsx_fifo_setup(struct st_lsm6dsx_hw *hw) +{ + int i, ret; + + for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) { + const struct iio_dev_attr **attrs; + + if (!hw->iio_devs[i]) + continue; + + /* + * For the accelerometer, allow setting FIFO sampling frequency + * values different from the sensor sampling frequency, which + * may be needed to keep FIFO data rate low while sampling + * acceleration data at high rates for accurate event detection. + */ + attrs = i == ST_LSM6DSX_ID_ACC ? st_lsm6dsx_buffer_attrs : NULL; + ret = devm_iio_kfifo_buffer_setup_ext(hw->dev, hw->iio_devs[i], + &st_lsm6dsx_buffer_ops, + attrs); + if (ret) + return ret; + } + + return 0; +} diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c new file mode 100644 index 000000000..360f1365f --- /dev/null +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -0,0 +1,3093 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics st_lsm6dsx sensor driver + * + * The ST LSM6DSx IMU MEMS series consists of 3D digital accelerometer + * and 3D digital gyroscope system-in-package with a digital I2C/SPI serial + * interface standard output. + * LSM6DSx IMU MEMS series has a dynamic user-selectable full-scale + * acceleration range of +-2/+-4/+-8/+-16 g and an angular rate range of + * +-125/+-245/+-500/+-1000/+-2000 dps + * LSM6DSx series has an integrated First-In-First-Out (FIFO) buffer + * allowing dynamic batching of sensor data. + * LSM9DSx series is similar but includes an additional magnetometer, handled + * by a different driver. + * + * Supported sensors: + * + * - LSM6DS3 + * - Accelerometer/Gyroscope supported ODR [Hz]: 12.5, 26, 52, 104, 208, 416 + * - Accelerometer supported full-scale [g]: +-2/+-4/+-8/+-16 + * - Gyroscope supported full-scale [dps]: +-125/+-245/+-500/+-1000/+-2000 + * - FIFO size: 8KB + * + * - ISM330DLC + * - LSM6DS3H + * - LSM6DS3TR-C + * - LSM6DSL + * - LSM6DSM + * - Accelerometer/Gyroscope supported ODR [Hz]: 12.5, 26, 52, 104, 208, 416 + * - Accelerometer supported full-scale [g]: +-2/+-4/+-8/+-16 + * - Gyroscope supported full-scale [dps]: +-125/+-245/+-500/+-1000/+-2000 + * - FIFO size: 4KB + * + * - ASM330LHH + * - ASM330LHHX + * - ASM330LHHXG1 + * - ISM330DHCX + * - ISM330IS + * - LSM6DSO + * - LSM6DSO16IS + * - LSM6DSOP + * - LSM6DSOX + * - LSM6DSR + * - LSM6DST + * - LSM6DSTX + * - Accelerometer/Gyroscope supported ODR [Hz]: 12.5, 26, 52, 104, 208, 416, + * 833 + * - Accelerometer supported full-scale [g]: +-2/+-4/+-8/+-16 + * - Gyroscope supported full-scale [dps]: +-125/+-245/+-500/+-1000/+-2000 + * - FIFO size: 3KB + * + * - LSM6DSV + * - LSM6DSV16X + * - Accelerometer/Gyroscope supported ODR [Hz]: 7.5, 15, 30, 60, 120, 240, + * 480, 960 + * - Accelerometer supported full-scale [g]: +-2/+-4/+-8/+-16 + * - Gyroscope supported full-scale [dps]: +-125/+-250/+-500/+-1000/+-2000 + * - FIFO size: 3KB + * + * - LSM6DS0 + * - LSM9DS1 + * - Accelerometer supported ODR [Hz]: 10, 50, 119, 238, 476, 952 + * - Accelerometer supported full-scale [g]: +-2/+-4/+-8/+-16 + * - Gyroscope supported ODR [Hz]: 15, 60, 119, 238, 476, 952 + * - Gyroscope supported full-scale [dps]: +-245/+-500/+-2000 + * - FIFO size: 32 + * + * Copyright 2016 STMicroelectronics Inc. + * + * Lorenzo Bianconi + * Denis Ciocca + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "st_lsm6dsx.h" + +#define ST_LSM6DSX_REG_WHOAMI_ADDR 0x0f + +/* Raw values from the IMU are 16-bit half-precision floating-point numbers. */ +#define ST_LSM6DSX_CHANNEL_ROT \ +{ \ + .type = IIO_ROT, \ + .modified = 1, \ + .channel2 = IIO_MOD_QUATERNION_AXIS, \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = 0, \ + .scan_type = { \ + .format = IIO_SCAN_FORMAT_FLOAT, \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + .repeat = 3, \ + }, \ + .ext_info = st_lsm6dsx_ext_info, \ +} + +static const struct iio_event_spec st_lsm6dsx_ev_motion[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_event_spec st_lsm6dsx_ev_motion_tap[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_GESTURE, + .dir = IIO_EV_DIR_SINGLETAP, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec st_lsm6dsx_acc_channels[] = { + ST_LSM6DSX_CHANNEL_ACC(0x28, IIO_MOD_X, 0, st_lsm6dsx_ev_motion), + ST_LSM6DSX_CHANNEL_ACC(0x2a, IIO_MOD_Y, 1, st_lsm6dsx_ev_motion), + ST_LSM6DSX_CHANNEL_ACC(0x2c, IIO_MOD_Z, 2, st_lsm6dsx_ev_motion), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_chan_spec st_lsm6dsx_acc_tap_channels[] = { + ST_LSM6DSX_CHANNEL_ACC(0x28, IIO_MOD_X, 0, st_lsm6dsx_ev_motion_tap), + ST_LSM6DSX_CHANNEL_ACC(0x2a, IIO_MOD_Y, 1, st_lsm6dsx_ev_motion_tap), + ST_LSM6DSX_CHANNEL_ACC(0x2c, IIO_MOD_Z, 2, st_lsm6dsx_ev_motion_tap), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_chan_spec st_lsm6ds0_acc_channels[] = { + ST_LSM6DSX_CHANNEL(IIO_ACCEL, 0x28, IIO_MOD_X, 0), + ST_LSM6DSX_CHANNEL(IIO_ACCEL, 0x2a, IIO_MOD_Y, 1), + ST_LSM6DSX_CHANNEL(IIO_ACCEL, 0x2c, IIO_MOD_Z, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_chan_spec st_lsm6dsx_gyro_channels[] = { + ST_LSM6DSX_CHANNEL(IIO_ANGL_VEL, 0x22, IIO_MOD_X, 0), + ST_LSM6DSX_CHANNEL(IIO_ANGL_VEL, 0x24, IIO_MOD_Y, 1), + ST_LSM6DSX_CHANNEL(IIO_ANGL_VEL, 0x26, IIO_MOD_Z, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_chan_spec st_lsm6ds0_gyro_channels[] = { + ST_LSM6DSX_CHANNEL(IIO_ANGL_VEL, 0x18, IIO_MOD_X, 0), + ST_LSM6DSX_CHANNEL(IIO_ANGL_VEL, 0x1a, IIO_MOD_Y, 1), + ST_LSM6DSX_CHANNEL(IIO_ANGL_VEL, 0x1c, IIO_MOD_Z, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_chan_spec st_lsm6dsx_fusion_channels[] = { + ST_LSM6DSX_CHANNEL_ROT, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { + { + .reset = { + .addr = 0x22, + .mask = BIT(0), + }, + .boot = { + .addr = 0x22, + .mask = BIT(7), + }, + .bdu = { + .addr = 0x22, + .mask = BIT(6), + }, + .id = { + { + .hw_id = ST_LSM9DS1_ID, + .name = ST_LSM9DS1_DEV_NAME, + .wai = 0x68, + }, { + .hw_id = ST_LSM6DS0_ID, + .name = ST_LSM6DS0_DEV_NAME, + .wai = 0x68, + }, + }, + .channels = { + [ST_LSM6DSX_ID_ACC] = { + .chan = st_lsm6ds0_acc_channels, + .len = ARRAY_SIZE(st_lsm6ds0_acc_channels), + }, + [ST_LSM6DSX_ID_GYRO] = { + .chan = st_lsm6ds0_gyro_channels, + .len = ARRAY_SIZE(st_lsm6ds0_gyro_channels), + }, + }, + .odr_table = { + [ST_LSM6DSX_ID_ACC] = { + .reg = { + .addr = 0x20, + .mask = GENMASK(7, 5), + }, + .odr_avl[0] = { 10000, 0x01 }, + .odr_avl[1] = { 50000, 0x02 }, + .odr_avl[2] = { 119000, 0x03 }, + .odr_avl[3] = { 238000, 0x04 }, + .odr_avl[4] = { 476000, 0x05 }, + .odr_avl[5] = { 952000, 0x06 }, + .odr_len = 6, + }, + [ST_LSM6DSX_ID_GYRO] = { + .reg = { + .addr = 0x10, + .mask = GENMASK(7, 5), + }, + .odr_avl[0] = { 14900, 0x01 }, + .odr_avl[1] = { 59500, 0x02 }, + .odr_avl[2] = { 119000, 0x03 }, + .odr_avl[3] = { 238000, 0x04 }, + .odr_avl[4] = { 476000, 0x05 }, + .odr_avl[5] = { 952000, 0x06 }, + .odr_len = 6, + }, + }, + .fs_table = { + [ST_LSM6DSX_ID_ACC] = { + .reg = { + .addr = 0x20, + .mask = GENMASK(4, 3), + }, + .fs_avl[0] = { IIO_G_TO_M_S_2(61000), 0x0 }, + .fs_avl[1] = { IIO_G_TO_M_S_2(122000), 0x2 }, + .fs_avl[2] = { IIO_G_TO_M_S_2(244000), 0x3 }, + .fs_avl[3] = { IIO_G_TO_M_S_2(732000), 0x1 }, + .fs_len = 4, + }, + [ST_LSM6DSX_ID_GYRO] = { + .reg = { + .addr = 0x10, + .mask = GENMASK(4, 3), + }, + + .fs_avl[0] = { IIO_DEGREE_TO_RAD(8750000), 0x0 }, + .fs_avl[1] = { IIO_DEGREE_TO_RAD(17500000), 0x1 }, + .fs_avl[2] = { IIO_DEGREE_TO_RAD(70000000), 0x3 }, + .fs_len = 3, + }, + }, + .irq_config = { + .irq1 = { + .addr = 0x0c, + .mask = BIT(3), + }, + .irq2 = { + .addr = 0x0d, + .mask = BIT(3), + }, + .hla = { + .addr = 0x22, + .mask = BIT(5), + }, + .od = { + .addr = 0x22, + .mask = BIT(4), + }, + }, + .fifo_ops = { + .max_size = 32, + }, + }, + { + .reset = { + .addr = 0x12, + .mask = BIT(0), + }, + .boot = { + .addr = 0x12, + .mask = BIT(7), + }, + .bdu = { + .addr = 0x12, + .mask = BIT(6), + }, + .id = { + { + .hw_id = ST_LSM6DS3_ID, + .name = ST_LSM6DS3_DEV_NAME, + .wai = 0x69, + }, + }, + .channels = { + [ST_LSM6DSX_ID_ACC] = { + .chan = st_lsm6dsx_acc_channels, + .len = ARRAY_SIZE(st_lsm6dsx_acc_channels), + }, + [ST_LSM6DSX_ID_GYRO] = { + .chan = st_lsm6dsx_gyro_channels, + .len = ARRAY_SIZE(st_lsm6dsx_gyro_channels), + }, + }, + .odr_table = { + [ST_LSM6DSX_ID_ACC] = { + .reg = { + .addr = 0x10, + .mask = GENMASK(7, 4), + }, + .odr_avl[0] = { 12500, 0x01 }, + .odr_avl[1] = { 26000, 0x02 }, + .odr_avl[2] = { 52000, 0x03 }, + .odr_avl[3] = { 104000, 0x04 }, + .odr_avl[4] = { 208000, 0x05 }, + .odr_avl[5] = { 416000, 0x06 }, + .odr_len = 6, + }, + [ST_LSM6DSX_ID_GYRO] = { + .reg = { + .addr = 0x11, + .mask = GENMASK(7, 4), + }, + .odr_avl[0] = { 12500, 0x01 }, + .odr_avl[1] = { 26000, 0x02 }, + .odr_avl[2] = { 52000, 0x03 }, + .odr_avl[3] = { 104000, 0x04 }, + .odr_avl[4] = { 208000, 0x05 }, + .odr_avl[5] = { 416000, 0x06 }, + .odr_len = 6, + }, + }, + .fs_table = { + [ST_LSM6DSX_ID_ACC] = { + .reg = { + .addr = 0x10, + .mask = GENMASK(3, 2), + }, + .fs_avl[0] = { IIO_G_TO_M_S_2(61000), 0x0 }, + .fs_avl[1] = { IIO_G_TO_M_S_2(122000), 0x2 }, + .fs_avl[2] = { IIO_G_TO_M_S_2(244000), 0x3 }, + .fs_avl[3] = { IIO_G_TO_M_S_2(488000), 0x1 }, + .fs_len = 4, + }, + [ST_LSM6DSX_ID_GYRO] = { + .reg = { + .addr = 0x11, + .mask = GENMASK(3, 2), + }, + .fs_avl[0] = { IIO_DEGREE_TO_RAD(8750000), 0x0 }, + .fs_avl[1] = { IIO_DEGREE_TO_RAD(17500000), 0x1 }, + .fs_avl[2] = { IIO_DEGREE_TO_RAD(35000000), 0x2 }, + .fs_avl[3] = { IIO_DEGREE_TO_RAD(70000000), 0x3 }, + .fs_len = 4, + }, + }, + .irq_config = { + .irq1 = { + .addr = 0x0d, + .mask = BIT(3), + }, + .irq2 = { + .addr = 0x0e, + .mask = BIT(3), + }, + .lir = { + .addr = 0x58, + .mask = BIT(0), + }, + .irq1_func = 0x5e, + .irq2_func = 0x5f, + .hla = { + .addr = 0x12, + .mask = BIT(5), + }, + .od = { + .addr = 0x12, + .mask = BIT(4), + }, + }, + .decimator = { + [ST_LSM6DSX_ID_ACC] = { + .addr = 0x08, + .mask = GENMASK(2, 0), + }, + [ST_LSM6DSX_ID_GYRO] = { + .addr = 0x08, + .mask = GENMASK(5, 3), + }, + }, + .fifo_ops = { + .update_fifo = st_lsm6dsx_update_fifo, + .read_fifo = st_lsm6dsx_read_fifo, + .fifo_th = { + .addr = 0x06, + .mask = GENMASK(11, 0), + }, + .fifo_diff = { + .addr = 0x3a, + .mask = GENMASK(11, 0), + }, + .max_size = 1365, + .th_wl = 3, /* 1LSB = 2B */ + }, + .ts_settings = { + .timer_en = { + .addr = 0x58, + .mask = BIT(7), + }, + .hr_timer = { + .addr = 0x5c, + .mask = BIT(4), + }, + .fifo_en = { + .addr = 0x07, + .mask = BIT(7), + }, + .decimator = { + .addr = 0x09, + .mask = GENMASK(5, 3), + }, + }, + .event_settings = { + .sources = { + [ST_LSM6DSX_EVENT_WAKEUP] = { + .value = { + .addr = 0x5b, + .mask = GENMASK(5, 0), + }, + .enable_mask = BIT(5), + .status = { + .addr = 0x1b, + .mask = BIT(3), + }, + .status_z_mask = BIT(0), + .status_y_mask = BIT(1), + .status_x_mask = BIT(2), + }, + }, + }, + }, + { + .reset = { + .addr = 0x12, + .mask = BIT(0), + }, + .boot = { + .addr = 0x12, + .mask = BIT(7), + }, + .bdu = { + .addr = 0x12, + .mask = BIT(6), + }, + .id = { + { + .hw_id = ST_LSM6DS3H_ID, + .name = ST_LSM6DS3H_DEV_NAME, + .wai = 0x69, + }, + }, + .channels = { + [ST_LSM6DSX_ID_ACC] = { + .chan = st_lsm6dsx_acc_channels, + .len = ARRAY_SIZE(st_lsm6dsx_acc_channels), + }, + [ST_LSM6DSX_ID_GYRO] = { + .chan = st_lsm6dsx_gyro_channels, + .len = ARRAY_SIZE(st_lsm6dsx_gyro_channels), + }, + }, + .odr_table = { + [ST_LSM6DSX_ID_ACC] = { + .reg = { + .addr = 0x10, + .mask = GENMASK(7, 4), + }, + .odr_avl[0] = { 12500, 0x01 }, + .odr_avl[1] = { 26000, 0x02 }, + .odr_avl[2] = { 52000, 0x03 }, + .odr_avl[3] = { 104000, 0x04 }, + .odr_avl[4] = { 208000, 0x05 }, + .odr_avl[5] = { 416000, 0x06 }, + .odr_len = 6, + }, + [ST_LSM6DSX_ID_GYRO] = { + .reg = { + .addr = 0x11, + .mask = GENMASK(7, 4), + }, + .odr_avl[0] = { 12500, 0x01 }, + .odr_avl[1] = { 26000, 0x02 }, + .odr_avl[2] = { 52000, 0x03 }, + .odr_avl[3] = { 104000, 0x04 }, + .odr_avl[4] = { 208000, 0x05 }, + .odr_avl[5] = { 416000, 0x06 }, + .odr_len = 6, + }, + }, + .fs_table = { + [ST_LSM6DSX_ID_ACC] = { + .reg = { + .addr = 0x10, + .mask = GENMASK(3, 2), + }, + .fs_avl[0] = { IIO_G_TO_M_S_2(61000), 0x0 }, + .fs_avl[1] = { IIO_G_TO_M_S_2(122000), 0x2 }, + .fs_avl[2] = { IIO_G_TO_M_S_2(244000), 0x3 }, + .fs_avl[3] = { IIO_G_TO_M_S_2(488000), 0x1 }, + .fs_len = 4, + }, + [ST_LSM6DSX_ID_GYRO] = { + .reg = { + .addr = 0x11, + .mask = GENMASK(3, 2), + }, + .fs_avl[0] = { IIO_DEGREE_TO_RAD(8750000), 0x0 }, + .fs_avl[1] = { IIO_DEGREE_TO_RAD(17500000), 0x1 }, + .fs_avl[2] = { IIO_DEGREE_TO_RAD(35000000), 0x2 }, + .fs_avl[3] = { IIO_DEGREE_TO_RAD(70000000), 0x3 }, + .fs_len = 4, + }, + }, + .irq_config = { + .irq1 = { + .addr = 0x0d, + .mask = BIT(3), + }, + .irq2 = { + .addr = 0x0e, + .mask = BIT(3), + }, + .lir = { + .addr = 0x58, + .mask = BIT(0), + }, + .irq1_func = 0x5e, + .irq2_func = 0x5f, + .hla = { + .addr = 0x12, + .mask = BIT(5), + }, + .od = { + .addr = 0x12, + .mask = BIT(4), + }, + }, + .decimator = { + [ST_LSM6DSX_ID_ACC] = { + .addr = 0x08, + .mask = GENMASK(2, 0), + }, + [ST_LSM6DSX_ID_GYRO] = { + .addr = 0x08, + .mask = GENMASK(5, 3), + }, + }, + .fifo_ops = { + .update_fifo = st_lsm6dsx_update_fifo, + .read_fifo = st_lsm6dsx_read_fifo, + .fifo_th = { + .addr = 0x06, + .mask = GENMASK(11, 0), + }, + .fifo_diff = { + .addr = 0x3a, + .mask = GENMASK(11, 0), + }, + .max_size = 682, + .th_wl = 3, /* 1LSB = 2B */ + }, + .ts_settings = { + .timer_en = { + .addr = 0x58, + .mask = BIT(7), + }, + .hr_timer = { + .addr = 0x5c, + .mask = BIT(4), + }, + .fifo_en = { + .addr = 0x07, + .mask = BIT(7), + }, + .decimator = { + .addr = 0x09, + .mask = GENMASK(5, 3), + }, + }, + .event_settings = { + .sources = { + [ST_LSM6DSX_EVENT_WAKEUP] = { + .value = { + .addr = 0x5b, + .mask = GENMASK(5, 0), + }, + .enable_mask = BIT(5), + .status = { + .addr = 0x1b, + .mask = BIT(3), + }, + .status_z_mask = BIT(0), + .status_y_mask = BIT(1), + .status_x_mask = BIT(2), + }, + }, + }, + }, + { + .reset = { + .addr = 0x12, + .mask = BIT(0), + }, + .boot = { + .addr = 0x12, + .mask = BIT(7), + }, + .bdu = { + .addr = 0x12, + .mask = BIT(6), + }, + .id = { + { + .hw_id = ST_LSM6DSL_ID, + .name = ST_LSM6DSL_DEV_NAME, + .wai = 0x6a, + }, { + .hw_id = ST_LSM6DSM_ID, + .name = ST_LSM6DSM_DEV_NAME, + .wai = 0x6a, + }, { + .hw_id = ST_ISM330DLC_ID, + .name = ST_ISM330DLC_DEV_NAME, + .wai = 0x6a, + }, { + .hw_id = ST_LSM6DS3TRC_ID, + .name = ST_LSM6DS3TRC_DEV_NAME, + .wai = 0x6a, + }, + }, + .channels = { + [ST_LSM6DSX_ID_ACC] = { + .chan = st_lsm6dsx_acc_channels, + .len = ARRAY_SIZE(st_lsm6dsx_acc_channels), + }, + [ST_LSM6DSX_ID_GYRO] = { + .chan = st_lsm6dsx_gyro_channels, + .len = ARRAY_SIZE(st_lsm6dsx_gyro_channels), + }, + }, + .odr_table = { + [ST_LSM6DSX_ID_ACC] = { + .reg = { + .addr = 0x10, + .mask = GENMASK(7, 4), + }, + .odr_avl[0] = { 12500, 0x01 }, + .odr_avl[1] = { 26000, 0x02 }, + .odr_avl[2] = { 52000, 0x03 }, + .odr_avl[3] = { 104000, 0x04 }, + .odr_avl[4] = { 208000, 0x05 }, + .odr_avl[5] = { 416000, 0x06 }, + .odr_len = 6, + }, + [ST_LSM6DSX_ID_GYRO] = { + .reg = { + .addr = 0x11, + .mask = GENMASK(7, 4), + }, + .odr_avl[0] = { 12500, 0x01 }, + .odr_avl[1] = { 26000, 0x02 }, + .odr_avl[2] = { 52000, 0x03 }, + .odr_avl[3] = { 104000, 0x04 }, + .odr_avl[4] = { 208000, 0x05 }, + .odr_avl[5] = { 416000, 0x06 }, + .odr_len = 6, + }, + }, + .fs_table = { + [ST_LSM6DSX_ID_ACC] = { + .reg = { + .addr = 0x10, + .mask = GENMASK(3, 2), + }, + .fs_avl[0] = { IIO_G_TO_M_S_2(61000), 0x0 }, + .fs_avl[1] = { IIO_G_TO_M_S_2(122000), 0x2 }, + .fs_avl[2] = { IIO_G_TO_M_S_2(244000), 0x3 }, + .fs_avl[3] = { IIO_G_TO_M_S_2(488000), 0x1 }, + .fs_len = 4, + }, + [ST_LSM6DSX_ID_GYRO] = { + .reg = { + .addr = 0x11, + .mask = GENMASK(3, 2), + }, + .fs_avl[0] = { IIO_DEGREE_TO_RAD(8750000), 0x0 }, + .fs_avl[1] = { IIO_DEGREE_TO_RAD(17500000), 0x1 }, + .fs_avl[2] = { IIO_DEGREE_TO_RAD(35000000), 0x2 }, + .fs_avl[3] = { IIO_DEGREE_TO_RAD(70000000), 0x3 }, + .fs_len = 4, + }, + }, + .samples_to_discard = { + [ST_LSM6DSX_ID_ACC] = { + .val[0] = { 12500, 1 }, + .val[1] = { 26000, 1 }, + .val[2] = { 52000, 1 }, + .val[3] = { 104000, 2 }, + .val[4] = { 208000, 2 }, + .val[5] = { 416000, 2 }, + }, + [ST_LSM6DSX_ID_GYRO] = { + .val[0] = { 12500, 2 }, + .val[1] = { 26000, 5 }, + .val[2] = { 52000, 7 }, + .val[3] = { 104000, 12 }, + .val[4] = { 208000, 20 }, + .val[5] = { 416000, 36 }, + }, + }, + .irq_config = { + .irq1 = { + .addr = 0x0d, + .mask = BIT(3), + }, + .irq2 = { + .addr = 0x0e, + .mask = BIT(3), + }, + .lir = { + .addr = 0x58, + .mask = BIT(0), + }, + .irq1_func = 0x5e, + .irq2_func = 0x5f, + .hla = { + .addr = 0x12, + .mask = BIT(5), + }, + .od = { + .addr = 0x12, + .mask = BIT(4), + }, + }, + .decimator = { + [ST_LSM6DSX_ID_ACC] = { + .addr = 0x08, + .mask = GENMASK(2, 0), + }, + [ST_LSM6DSX_ID_GYRO] = { + .addr = 0x08, + .mask = GENMASK(5, 3), + }, + [ST_LSM6DSX_ID_EXT0] = { + .addr = 0x09, + .mask = GENMASK(2, 0), + }, + }, + .fifo_ops = { + .update_fifo = st_lsm6dsx_update_fifo, + .read_fifo = st_lsm6dsx_read_fifo, + .fifo_th = { + .addr = 0x06, + .mask = GENMASK(10, 0), + }, + .fifo_diff = { + .addr = 0x3a, + .mask = GENMASK(10, 0), + }, + .max_size = 682, + .th_wl = 3, /* 1LSB = 2B */ + }, + .ts_settings = { + .timer_en = { + .addr = 0x19, + .mask = BIT(5), + }, + .hr_timer = { + .addr = 0x5c, + .mask = BIT(4), + }, + .fifo_en = { + .addr = 0x07, + .mask = BIT(7), + }, + .decimator = { + .addr = 0x09, + .mask = GENMASK(5, 3), + }, + }, + .shub_settings = { + .page_mux = { + .addr = 0x01, + .mask = BIT(7), + }, + .master_en = { + .addr = 0x1a, + .mask = BIT(0), + }, + .pullup_en = { + .addr = 0x1a, + .mask = BIT(3), + }, + .aux_sens = { + .addr = 0x04, + .mask = GENMASK(5, 4), + }, + .wr_once = { + .addr = 0x07, + .mask = BIT(5), + }, + .emb_func = { + .addr = 0x19, + .mask = BIT(2), + }, + .num_ext_dev = 1, + .shub_out = { + .addr = 0x2e, + }, + .slv0_addr = 0x02, + .dw_slv0_addr = 0x0e, + .pause = 0x7, + }, + .event_settings = { + .enable_reg = { + .addr = 0x58, + .mask = BIT(7), + }, + .sources = { + [ST_LSM6DSX_EVENT_WAKEUP] = { + .value = { + .addr = 0x5b, + .mask = GENMASK(5, 0), + }, + .enable_mask = BIT(5), + .status = { + .addr = 0x1b, + .mask = BIT(3), + }, + .status_z_mask = BIT(0), + .status_y_mask = BIT(1), + .status_x_mask = BIT(2), + }, + }, + }, + }, + { + .reset = { + .addr = 0x12, + .mask = BIT(0), + }, + .boot = { + .addr = 0x12, + .mask = BIT(7), + }, + .bdu = { + .addr = 0x12, + .mask = BIT(6), + }, + .id = { + { + .hw_id = ST_LSM6DSR_ID, + .name = ST_LSM6DSR_DEV_NAME, + .wai = 0x6b, + }, { + .hw_id = ST_ISM330DHCX_ID, + .name = ST_ISM330DHCX_DEV_NAME, + .wai = 0x6b, + }, { + .hw_id = ST_LSM6DSRX_ID, + .name = ST_LSM6DSRX_DEV_NAME, + .wai = 0x6b, + }, { + .hw_id = ST_LSM6DSO_ID, + .name = ST_LSM6DSO_DEV_NAME, + .wai = 0x6c, + }, { + .hw_id = ST_LSM6DSOX_ID, + .name = ST_LSM6DSOX_DEV_NAME, + .wai = 0x6c, + }, { + .hw_id = ST_LSM6DST_ID, + .name = ST_LSM6DST_DEV_NAME, + .wai = 0x6d, + }, { + .hw_id = ST_ASM330LHHX_ID, + .name = ST_ASM330LHHX_DEV_NAME, + .wai = 0x6b, + }, { + .hw_id = ST_ASM330LHHXG1_ID, + .name = ST_ASM330LHHXG1_DEV_NAME, + .wai = 0x6b, + }, { + .hw_id = ST_LSM6DSTX_ID, + .name = ST_LSM6DSTX_DEV_NAME, + .wai = 0x6d, + }, + }, + .channels = { + [ST_LSM6DSX_ID_ACC] = { + .chan = st_lsm6dsx_acc_channels, + .len = ARRAY_SIZE(st_lsm6dsx_acc_channels), + }, + [ST_LSM6DSX_ID_GYRO] = { + .chan = st_lsm6dsx_gyro_channels, + .len = ARRAY_SIZE(st_lsm6dsx_gyro_channels), + }, + }, + .drdy_mask = { + .addr = 0x13, + .mask = BIT(3), + }, + .odr_table = { + [ST_LSM6DSX_ID_ACC] = { + .reg = { + .addr = 0x10, + .mask = GENMASK(7, 4), + }, + .odr_avl[0] = { 12500, 0x01 }, + .odr_avl[1] = { 26000, 0x02 }, + .odr_avl[2] = { 52000, 0x03 }, + .odr_avl[3] = { 104000, 0x04 }, + .odr_avl[4] = { 208000, 0x05 }, + .odr_avl[5] = { 416000, 0x06 }, + .odr_avl[6] = { 833000, 0x07 }, + .odr_len = 7, + }, + [ST_LSM6DSX_ID_GYRO] = { + .reg = { + .addr = 0x11, + .mask = GENMASK(7, 4), + }, + .odr_avl[0] = { 12500, 0x01 }, + .odr_avl[1] = { 26000, 0x02 }, + .odr_avl[2] = { 52000, 0x03 }, + .odr_avl[3] = { 104000, 0x04 }, + .odr_avl[4] = { 208000, 0x05 }, + .odr_avl[5] = { 416000, 0x06 }, + .odr_avl[6] = { 833000, 0x07 }, + .odr_len = 7, + }, + }, + .fs_table = { + [ST_LSM6DSX_ID_ACC] = { + .reg = { + .addr = 0x10, + .mask = GENMASK(3, 2), + }, + .fs_avl[0] = { IIO_G_TO_M_S_2(61000), 0x0 }, + .fs_avl[1] = { IIO_G_TO_M_S_2(122000), 0x2 }, + .fs_avl[2] = { IIO_G_TO_M_S_2(244000), 0x3 }, + .fs_avl[3] = { IIO_G_TO_M_S_2(488000), 0x1 }, + .fs_len = 4, + }, + [ST_LSM6DSX_ID_GYRO] = { + .reg = { + .addr = 0x11, + .mask = GENMASK(3, 2), + }, + .fs_avl[0] = { IIO_DEGREE_TO_RAD(8750000), 0x0 }, + .fs_avl[1] = { IIO_DEGREE_TO_RAD(17500000), 0x1 }, + .fs_avl[2] = { IIO_DEGREE_TO_RAD(35000000), 0x2 }, + .fs_avl[3] = { IIO_DEGREE_TO_RAD(70000000), 0x3 }, + .fs_len = 4, + }, + }, + .irq_config = { + .irq1 = { + .addr = 0x0d, + .mask = BIT(3), + }, + .irq2 = { + .addr = 0x0e, + .mask = BIT(3), + }, + .lir = { + .addr = 0x56, + .mask = BIT(0), + }, + .clear_on_read = { + .addr = 0x56, + .mask = BIT(6), + }, + .irq1_func = 0x5e, + .irq2_func = 0x5f, + .hla = { + .addr = 0x12, + .mask = BIT(5), + }, + .od = { + .addr = 0x12, + .mask = BIT(4), + }, + }, + .batch = { + [ST_LSM6DSX_ID_ACC] = { + .addr = 0x09, + .mask = GENMASK(3, 0), + }, + [ST_LSM6DSX_ID_GYRO] = { + .addr = 0x09, + .mask = GENMASK(7, 4), + }, + }, + .fifo_ops = { + .update_fifo = st_lsm6dsx_update_fifo, + .read_fifo = st_lsm6dsx_read_tagged_fifo, + .fifo_th = { + .addr = 0x07, + .mask = GENMASK(8, 0), + }, + .fifo_diff = { + .addr = 0x3a, + .mask = GENMASK(9, 0), + }, + .max_size = 512, + .th_wl = 1, + }, + .ts_settings = { + .timer_en = { + .addr = 0x19, + .mask = BIT(5), + }, + .decimator = { + .addr = 0x0a, + .mask = GENMASK(7, 6), + }, + .freq_fine = 0x63, + .ts_sensitivity = 25000, + .ts_trim_coeff = 37500, + }, + .shub_settings = { + .page_mux = { + .addr = 0x01, + .mask = BIT(6), + }, + .master_en = { + .sec_page = true, + .addr = 0x14, + .mask = BIT(2), + }, + .pullup_en = { + .sec_page = true, + .addr = 0x14, + .mask = BIT(3), + }, + .aux_sens = { + .addr = 0x14, + .mask = GENMASK(1, 0), + }, + .wr_once = { + .addr = 0x14, + .mask = BIT(6), + }, + .num_ext_dev = 3, + .shub_out = { + .sec_page = true, + .addr = 0x02, + }, + .slv0_addr = 0x15, + .dw_slv0_addr = 0x21, + .batch_en = BIT(3), + }, + .event_settings = { + .enable_reg = { + .addr = 0x58, + .mask = BIT(7), + }, + .sources = { + [ST_LSM6DSX_EVENT_WAKEUP] = { + .value = { + .addr = 0x5b, + .mask = GENMASK(5, 0), + }, + .enable_mask = BIT(5), + .status = { + .addr = 0x1b, + .mask = BIT(3), + }, + .status_z_mask = BIT(0), + .status_y_mask = BIT(1), + .status_x_mask = BIT(2), + }, + }, + }, + }, + { + .reset = { + .addr = 0x12, + .mask = BIT(0), + }, + .boot = { + .addr = 0x12, + .mask = BIT(7), + }, + .bdu = { + .addr = 0x12, + .mask = BIT(6), + }, + .id = { + { + .hw_id = ST_ASM330LHH_ID, + .name = ST_ASM330LHH_DEV_NAME, + .wai = 0x6b, + }, { + .hw_id = ST_LSM6DSOP_ID, + .name = ST_LSM6DSOP_DEV_NAME, + .wai = 0x6c, + }, { + .hw_id = ST_ASM330LHB_ID, + .name = ST_ASM330LHB_DEV_NAME, + .wai = 0x6b, + }, + }, + .channels = { + [ST_LSM6DSX_ID_ACC] = { + .chan = st_lsm6dsx_acc_channels, + .len = ARRAY_SIZE(st_lsm6dsx_acc_channels), + }, + [ST_LSM6DSX_ID_GYRO] = { + .chan = st_lsm6dsx_gyro_channels, + .len = ARRAY_SIZE(st_lsm6dsx_gyro_channels), + }, + }, + .drdy_mask = { + .addr = 0x13, + .mask = BIT(3), + }, + .odr_table = { + [ST_LSM6DSX_ID_ACC] = { + .reg = { + .addr = 0x10, + .mask = GENMASK(7, 4), + }, + .odr_avl[0] = { 12500, 0x01 }, + .odr_avl[1] = { 26000, 0x02 }, + .odr_avl[2] = { 52000, 0x03 }, + .odr_avl[3] = { 104000, 0x04 }, + .odr_avl[4] = { 208000, 0x05 }, + .odr_avl[5] = { 416000, 0x06 }, + .odr_avl[6] = { 833000, 0x07 }, + .odr_len = 7, + }, + [ST_LSM6DSX_ID_GYRO] = { + .reg = { + .addr = 0x11, + .mask = GENMASK(7, 4), + }, + .odr_avl[0] = { 12500, 0x01 }, + .odr_avl[1] = { 26000, 0x02 }, + .odr_avl[2] = { 52000, 0x03 }, + .odr_avl[3] = { 104000, 0x04 }, + .odr_avl[4] = { 208000, 0x05 }, + .odr_avl[5] = { 416000, 0x06 }, + .odr_avl[6] = { 833000, 0x07 }, + .odr_len = 7, + }, + }, + .fs_table = { + [ST_LSM6DSX_ID_ACC] = { + .reg = { + .addr = 0x10, + .mask = GENMASK(3, 2), + }, + .fs_avl[0] = { IIO_G_TO_M_S_2(61000), 0x0 }, + .fs_avl[1] = { IIO_G_TO_M_S_2(122000), 0x2 }, + .fs_avl[2] = { IIO_G_TO_M_S_2(244000), 0x3 }, + .fs_avl[3] = { IIO_G_TO_M_S_2(488000), 0x1 }, + .fs_len = 4, + }, + [ST_LSM6DSX_ID_GYRO] = { + .reg = { + .addr = 0x11, + .mask = GENMASK(3, 2), + }, + .fs_avl[0] = { IIO_DEGREE_TO_RAD(8750000), 0x0 }, + .fs_avl[1] = { IIO_DEGREE_TO_RAD(17500000), 0x1 }, + .fs_avl[2] = { IIO_DEGREE_TO_RAD(35000000), 0x2 }, + .fs_avl[3] = { IIO_DEGREE_TO_RAD(70000000), 0x3 }, + .fs_len = 4, + }, + }, + .irq_config = { + .irq1 = { + .addr = 0x0d, + .mask = BIT(3), + }, + .irq2 = { + .addr = 0x0e, + .mask = BIT(3), + }, + .lir = { + .addr = 0x56, + .mask = BIT(0), + }, + .clear_on_read = { + .addr = 0x56, + .mask = BIT(6), + }, + .irq1_func = 0x5e, + .irq2_func = 0x5f, + .hla = { + .addr = 0x12, + .mask = BIT(5), + }, + .od = { + .addr = 0x12, + .mask = BIT(4), + }, + }, + .batch = { + [ST_LSM6DSX_ID_ACC] = { + .addr = 0x09, + .mask = GENMASK(3, 0), + }, + [ST_LSM6DSX_ID_GYRO] = { + .addr = 0x09, + .mask = GENMASK(7, 4), + }, + }, + .fifo_ops = { + .update_fifo = st_lsm6dsx_update_fifo, + .read_fifo = st_lsm6dsx_read_tagged_fifo, + .fifo_th = { + .addr = 0x07, + .mask = GENMASK(8, 0), + }, + .fifo_diff = { + .addr = 0x3a, + .mask = GENMASK(9, 0), + }, + .max_size = 512, + .th_wl = 1, + }, + .ts_settings = { + .timer_en = { + .addr = 0x19, + .mask = BIT(5), + }, + .decimator = { + .addr = 0x0a, + .mask = GENMASK(7, 6), + }, + .freq_fine = 0x63, + .ts_sensitivity = 25000, + .ts_trim_coeff = 37500, + }, + .event_settings = { + .enable_reg = { + .addr = 0x58, + .mask = BIT(7), + }, + .sources = { + [ST_LSM6DSX_EVENT_WAKEUP] = { + .value = { + .addr = 0x5b, + .mask = GENMASK(5, 0), + }, + .enable_mask = BIT(5), + .status = { + .addr = 0x1b, + .mask = BIT(3), + }, + .status_z_mask = BIT(0), + .status_y_mask = BIT(1), + .status_x_mask = BIT(2), + }, + }, + }, + }, + { + .reset = { + .addr = 0x12, + .mask = BIT(0), + }, + .boot = { + .addr = 0x12, + .mask = BIT(7), + }, + .bdu = { + .addr = 0x12, + .mask = BIT(6), + }, + .id = { + { + .hw_id = ST_LSM6DSV_ID, + .name = ST_LSM6DSV_DEV_NAME, + .wai = 0x70, + }, { + .hw_id = ST_LSM6DSV16X_ID, + .name = ST_LSM6DSV16X_DEV_NAME, + .wai = 0x70, + }, + }, + .channels = { + [ST_LSM6DSX_ID_ACC] = { + .chan = st_lsm6dsx_acc_tap_channels, + .len = ARRAY_SIZE(st_lsm6dsx_acc_tap_channels), + }, + [ST_LSM6DSX_ID_GYRO] = { + .chan = st_lsm6dsx_gyro_channels, + .len = ARRAY_SIZE(st_lsm6dsx_gyro_channels), + }, + }, + .drdy_mask = { + .addr = 0x13, + .mask = BIT(3), + }, + .odr_table = { + [ST_LSM6DSX_ID_ACC] = { + .reg = { + .addr = 0x10, + .mask = GENMASK(3, 0), + }, + .odr_avl[0] = { 7500, 0x02 }, + .odr_avl[1] = { 15000, 0x03 }, + .odr_avl[2] = { 30000, 0x04 }, + .odr_avl[3] = { 60000, 0x05 }, + .odr_avl[4] = { 120000, 0x06 }, + .odr_avl[5] = { 240000, 0x07 }, + .odr_avl[6] = { 480000, 0x08 }, + .odr_avl[7] = { 960000, 0x09 }, + .odr_len = 8, + }, + [ST_LSM6DSX_ID_GYRO] = { + .reg = { + .addr = 0x11, + .mask = GENMASK(3, 0), + }, + .odr_avl[0] = { 7500, 0x02 }, + .odr_avl[1] = { 15000, 0x03 }, + .odr_avl[2] = { 30000, 0x04 }, + .odr_avl[3] = { 60000, 0x05 }, + .odr_avl[4] = { 120000, 0x06 }, + .odr_avl[5] = { 240000, 0x07 }, + .odr_avl[6] = { 480000, 0x08 }, + .odr_avl[7] = { 960000, 0x09 }, + .odr_len = 8, + }, + }, + .fs_table = { + [ST_LSM6DSX_ID_ACC] = { + .reg = { + .addr = 0x17, + .mask = GENMASK(1, 0), + }, + .fs_avl[0] = { IIO_G_TO_M_S_2(61000), 0x0 }, + .fs_avl[1] = { IIO_G_TO_M_S_2(122000), 0x1 }, + .fs_avl[2] = { IIO_G_TO_M_S_2(244000), 0x2 }, + .fs_avl[3] = { IIO_G_TO_M_S_2(488000), 0x3 }, + .fs_len = 4, + }, + [ST_LSM6DSX_ID_GYRO] = { + .reg = { + .addr = 0x15, + .mask = GENMASK(3, 0), + }, + .fs_avl[0] = { IIO_DEGREE_TO_RAD(8750000), 0x1 }, + .fs_avl[1] = { IIO_DEGREE_TO_RAD(17500000), 0x2 }, + .fs_avl[2] = { IIO_DEGREE_TO_RAD(35000000), 0x3 }, + .fs_avl[3] = { IIO_DEGREE_TO_RAD(70000000), 0x4 }, + .fs_len = 4, + }, + }, + .irq_config = { + .irq1 = { + .addr = 0x0d, + .mask = BIT(3), + }, + .irq2 = { + .addr = 0x0e, + .mask = BIT(3), + }, + .lir = { + .addr = 0x56, + .mask = BIT(0), + }, + .irq1_func = 0x5e, + .irq2_func = 0x5f, + .hla = { + .addr = 0x03, + .mask = BIT(4), + }, + .od = { + .addr = 0x03, + .mask = BIT(3), + }, + }, + .batch = { + [ST_LSM6DSX_ID_ACC] = { + .addr = 0x09, + .mask = GENMASK(3, 0), + }, + [ST_LSM6DSX_ID_GYRO] = { + .addr = 0x09, + .mask = GENMASK(7, 4), + }, + }, + .fifo_ops = { + .update_fifo = st_lsm6dsx_update_fifo, + .read_fifo = st_lsm6dsx_read_tagged_fifo, + .fifo_th = { + .addr = 0x07, + .mask = GENMASK(7, 0), + }, + .fifo_diff = { + .addr = 0x1b, + .mask = GENMASK(8, 0), + }, + .max_size = 512, + .th_wl = 1, + }, + .ts_settings = { + .timer_en = { + .addr = 0x50, + .mask = BIT(6), + }, + .decimator = { + .addr = 0x0a, + .mask = GENMASK(7, 6), + }, + .freq_fine = 0x4f, + .ts_sensitivity = 21701, + .ts_trim_coeff = 28212, + }, + .shub_settings = { + .page_mux = { + .addr = 0x01, + .mask = BIT(6), + }, + .master_en = { + .sec_page = true, + .addr = 0x14, + .mask = BIT(2), + }, + .pullup_en = { + .addr = 0x03, + .mask = BIT(6), + }, + .aux_sens = { + .addr = 0x14, + .mask = GENMASK(1, 0), + }, + .wr_once = { + .addr = 0x14, + .mask = BIT(6), + }, + .num_ext_dev = 3, + .shub_out = { + .sec_page = true, + .addr = 0x02, + }, + .slv0_addr = 0x15, + .dw_slv0_addr = 0x21, + .batch_en = BIT(3), + }, + .event_settings = { + .enable_reg = { + .addr = 0x50, + .mask = BIT(7), + }, + .sources = { + [ST_LSM6DSX_EVENT_WAKEUP] = { + .value = { + .addr = 0x5b, + .mask = GENMASK(5, 0), + }, + .enable_mask = BIT(5), + .status = { + .addr = 0x45, + .mask = BIT(3), + }, + .status_z_mask = BIT(0), + .status_y_mask = BIT(1), + .status_x_mask = BIT(2), + }, + [ST_LSM6DSX_EVENT_TAP] = { + .x_value = { + .addr = 0x57, + .mask = GENMASK(4, 0), + }, + .y_value = { + .addr = 0x58, + .mask = GENMASK(4, 0), + }, + .z_value = { + .addr = 0x59, + .mask = GENMASK(4, 0), + }, + .enable_mask = BIT(6), + .enable_axis_reg = 0x56, + .enable_x_mask = BIT(3), + .enable_y_mask = BIT(2), + .enable_z_mask = BIT(1), + .status = { + .addr = 0x46, + .mask = BIT(5), + }, + .status_x_mask = BIT(2), + .status_y_mask = BIT(1), + .status_z_mask = BIT(0), + }, + }, + }, + .fusion_settings = { + .chan = st_lsm6dsx_fusion_channels, + .chan_len = ARRAY_SIZE(st_lsm6dsx_fusion_channels), + .odr_reg = { + .addr = 0x5e, + .mask = GENMASK(5, 3), + }, + .odr_hz[0] = 15, + .odr_hz[1] = 30, + .odr_hz[2] = 60, + .odr_hz[3] = 120, + .odr_hz[4] = 240, + .odr_hz[5] = 480, + .odr_len = 6, + .fifo_enable = { + .addr = 0x44, + .mask = BIT(1), + }, + .page_mux = { + .addr = 0x01, + .mask = BIT(7), + }, + .enable = { + .addr = 0x04, + .mask = BIT(1), + }, + }, + }, + { + .reset = { + .addr = 0x12, + .mask = BIT(0), + }, + .boot = { + .addr = 0x12, + .mask = BIT(7), + }, + .bdu = { + .addr = 0x12, + .mask = BIT(6), + }, + .id = { + { + .hw_id = ST_LSM6DSO16IS_ID, + .name = ST_LSM6DSO16IS_DEV_NAME, + .wai = 0x22, + }, { + .hw_id = ST_ISM330IS_ID, + .name = ST_ISM330IS_DEV_NAME, + .wai = 0x22, + } + }, + .channels = { + [ST_LSM6DSX_ID_ACC] = { + .chan = st_lsm6ds0_acc_channels, + .len = ARRAY_SIZE(st_lsm6ds0_acc_channels), + }, + [ST_LSM6DSX_ID_GYRO] = { + .chan = st_lsm6dsx_gyro_channels, + .len = ARRAY_SIZE(st_lsm6dsx_gyro_channels), + }, + }, + .odr_table = { + [ST_LSM6DSX_ID_ACC] = { + .reg = { + .addr = 0x10, + .mask = GENMASK(7, 4), + }, + .odr_avl[0] = { 12500, 0x01 }, + .odr_avl[1] = { 26000, 0x02 }, + .odr_avl[2] = { 52000, 0x03 }, + .odr_avl[3] = { 104000, 0x04 }, + .odr_avl[4] = { 208000, 0x05 }, + .odr_avl[5] = { 416000, 0x06 }, + .odr_avl[6] = { 833000, 0x07 }, + .odr_len = 7, + }, + [ST_LSM6DSX_ID_GYRO] = { + .reg = { + .addr = 0x11, + .mask = GENMASK(7, 4), + }, + .odr_avl[0] = { 12500, 0x01 }, + .odr_avl[1] = { 26000, 0x02 }, + .odr_avl[2] = { 52000, 0x03 }, + .odr_avl[3] = { 104000, 0x04 }, + .odr_avl[4] = { 208000, 0x05 }, + .odr_avl[5] = { 416000, 0x06 }, + .odr_avl[6] = { 833000, 0x07 }, + .odr_len = 7, + }, + }, + .fs_table = { + [ST_LSM6DSX_ID_ACC] = { + .reg = { + .addr = 0x10, + .mask = GENMASK(3, 2), + }, + .fs_avl[0] = { IIO_G_TO_M_S_2(61000), 0x0 }, + .fs_avl[1] = { IIO_G_TO_M_S_2(122000), 0x2 }, + .fs_avl[2] = { IIO_G_TO_M_S_2(244000), 0x3 }, + .fs_avl[3] = { IIO_G_TO_M_S_2(488000), 0x1 }, + .fs_len = 4, + }, + [ST_LSM6DSX_ID_GYRO] = { + .reg = { + .addr = 0x11, + .mask = GENMASK(3, 2), + }, + .fs_avl[0] = { IIO_DEGREE_TO_RAD(8750000), 0x0 }, + .fs_avl[1] = { IIO_DEGREE_TO_RAD(17500000), 0x1 }, + .fs_avl[2] = { IIO_DEGREE_TO_RAD(35000000), 0x2 }, + .fs_avl[3] = { IIO_DEGREE_TO_RAD(70000000), 0x3 }, + .fs_len = 4, + }, + }, + .irq_config = { + .hla = { + .addr = 0x12, + .mask = BIT(5), + }, + .od = { + .addr = 0x12, + .mask = BIT(4), + }, + }, + .shub_settings = { + .page_mux = { + .addr = 0x01, + .mask = BIT(6), + }, + .master_en = { + .sec_page = true, + .addr = 0x14, + .mask = BIT(2), + }, + .pullup_en = { + .sec_page = true, + .addr = 0x14, + .mask = BIT(3), + }, + .aux_sens = { + .addr = 0x14, + .mask = GENMASK(1, 0), + }, + .wr_once = { + .addr = 0x14, + .mask = BIT(6), + }, + .num_ext_dev = 3, + .shub_out = { + .sec_page = true, + .addr = 0x02, + }, + .slv0_addr = 0x15, + .dw_slv0_addr = 0x21, + }, + }, +}; + +int st_lsm6dsx_set_page(struct st_lsm6dsx_hw *hw, bool enable) +{ + const struct st_lsm6dsx_shub_settings *hub_settings; + unsigned int data; + int err; + + hub_settings = &hw->settings->shub_settings; + data = ST_LSM6DSX_SHIFT_VAL(enable, hub_settings->page_mux.mask); + err = regmap_update_bits(hw->regmap, hub_settings->page_mux.addr, + hub_settings->page_mux.mask, data); + usleep_range(100, 150); + + return err; +} + +static int st_lsm6dsx_check_whoami(struct st_lsm6dsx_hw *hw, int id, + const char **name) +{ + int err, i, j, data; + + for (i = 0; i < ARRAY_SIZE(st_lsm6dsx_sensor_settings); i++) { + for (j = 0; j < ST_LSM6DSX_MAX_ID; j++) { + if (st_lsm6dsx_sensor_settings[i].id[j].name && + id == st_lsm6dsx_sensor_settings[i].id[j].hw_id) + break; + } + if (j < ST_LSM6DSX_MAX_ID) + break; + } + + if (i == ARRAY_SIZE(st_lsm6dsx_sensor_settings)) { + dev_err(hw->dev, "unsupported hw id [%02x]\n", id); + return -ENODEV; + } + + hw->settings = &st_lsm6dsx_sensor_settings[i]; + + if (hw->settings->shub_settings.page_mux.addr) { + /* + * If the IMU has the shub page selected on init, for example + * after a CPU watchdog reset while the page is selected, the + * regular register space is shadowed. While the regular + * register space is shadowed, the registers needed for + * initializing the IMU are not available. + * + * Unconditionally clear the shub page selection to ensure + * normal register access. + */ + err = st_lsm6dsx_set_page(hw, false); + if (err < 0) { + dev_err(hw->dev, "failed to clear shub page\n"); + return err; + } + } + + err = regmap_read(hw->regmap, ST_LSM6DSX_REG_WHOAMI_ADDR, &data); + if (err < 0) { + dev_err(hw->dev, "failed to read whoami register\n"); + return err; + } + + if (data != st_lsm6dsx_sensor_settings[i].id[j].wai) { + dev_err(hw->dev, "unsupported whoami [%02x]\n", data); + return -ENODEV; + } + + *name = st_lsm6dsx_sensor_settings[i].id[j].name; + + return 0; +} + +static int st_lsm6dsx_set_full_scale(struct st_lsm6dsx_sensor *sensor, + u32 gain) +{ + const struct st_lsm6dsx_fs_table_entry *fs_table; + unsigned int data; + int i, err; + + fs_table = &sensor->hw->settings->fs_table[sensor->id]; + for (i = 0; i < fs_table->fs_len; i++) { + if (fs_table->fs_avl[i].gain == gain) + break; + } + + if (i == fs_table->fs_len) + return -EINVAL; + + data = ST_LSM6DSX_SHIFT_VAL(fs_table->fs_avl[i].val, + fs_table->reg.mask); + err = st_lsm6dsx_update_bits_locked(sensor->hw, fs_table->reg.addr, + fs_table->reg.mask, data); + if (err < 0) + return err; + + sensor->gain = gain; + + return 0; +} + +int st_lsm6dsx_check_odr(struct st_lsm6dsx_sensor *sensor, u32 odr, u8 *val) +{ + const struct st_lsm6dsx_odr_table_entry *odr_table; + int i; + + odr_table = &sensor->hw->settings->odr_table[sensor->id]; + for (i = 0; i < odr_table->odr_len; i++) { + /* + * ext devices can run at different odr respect to + * accel sensor + */ + if (odr_table->odr_avl[i].milli_hz >= odr) + break; + } + + if (i == odr_table->odr_len) + return -EINVAL; + + *val = odr_table->odr_avl[i].val; + return odr_table->odr_avl[i].milli_hz; +} + +static int +st_lsm6dsx_check_odr_dependency(struct st_lsm6dsx_hw *hw, u32 odr, + enum st_lsm6dsx_sensor_id id) +{ + struct st_lsm6dsx_sensor *ref = iio_priv(hw->iio_devs[id]); + + if (odr > 0) { + if (hw->enable_mask & BIT(id)) + return max_t(u32, ref->odr, odr); + else + return odr; + } else { + return (hw->enable_mask & BIT(id)) ? ref->odr : 0; + } +} + +static int +st_lsm6dsx_set_odr(struct st_lsm6dsx_sensor *sensor, u32 req_odr) +{ + struct st_lsm6dsx_sensor *ref_sensor = sensor; + struct st_lsm6dsx_hw *hw = sensor->hw; + const struct st_lsm6dsx_reg *reg; + unsigned int data; + u8 val = 0; + int err; + + switch (sensor->id) { + case ST_LSM6DSX_ID_GYRO: + break; + case ST_LSM6DSX_ID_EXT0: + case ST_LSM6DSX_ID_EXT1: + case ST_LSM6DSX_ID_EXT2: + case ST_LSM6DSX_ID_ACC: { + u32 odr; + int i; + + /* + * i2c embedded controller relies on the accelerometer sensor as + * bus read/write trigger so we need to enable accel device + * at odr = max(accel_odr, ext_odr) in order to properly + * communicate with i2c slave devices + */ + ref_sensor = iio_priv(hw->iio_devs[ST_LSM6DSX_ID_ACC]); + for (i = ST_LSM6DSX_ID_ACC; i < ST_LSM6DSX_ID_MAX; i++) { + if (!hw->iio_devs[i] || i == sensor->id) + continue; + + odr = st_lsm6dsx_check_odr_dependency(hw, req_odr, i); + if (odr != req_odr) + /* device already configured */ + return 0; + } + break; + } + default: /* should never occur */ + return -EINVAL; + } + + if (req_odr > 0) { + err = st_lsm6dsx_check_odr(ref_sensor, req_odr, &val); + if (err < 0) + return err; + } + + reg = &hw->settings->odr_table[ref_sensor->id].reg; + data = ST_LSM6DSX_SHIFT_VAL(val, reg->mask); + return st_lsm6dsx_update_bits_locked(hw, reg->addr, reg->mask, data); +} + +static int +__st_lsm6dsx_sensor_set_enable(struct st_lsm6dsx_sensor *sensor, + bool enable) +{ + struct st_lsm6dsx_hw *hw = sensor->hw; + u32 odr = enable ? sensor->odr : 0; + int err; + + err = st_lsm6dsx_set_odr(sensor, odr); + if (err < 0) + return err; + + if (enable) + hw->enable_mask |= BIT(sensor->id); + else + hw->enable_mask &= ~BIT(sensor->id); + + return 0; +} + +static int +st_lsm6dsx_check_events(struct st_lsm6dsx_sensor *sensor) +{ + struct st_lsm6dsx_hw *hw = sensor->hw; + int event; + + if (sensor->id != ST_LSM6DSX_ID_ACC) + return 0; + + for (event = 0; event < ST_LSM6DSX_EVENT_MAX; event++) { + if (hw->enable_event[event]) + return true; + } + return false; +} + +int st_lsm6dsx_sensor_set_enable(struct st_lsm6dsx_sensor *sensor, + bool enable) +{ + if (st_lsm6dsx_check_events(sensor)) + return 0; + + return __st_lsm6dsx_sensor_set_enable(sensor, enable); +} + +static int st_lsm6dsx_read_oneshot(struct st_lsm6dsx_sensor *sensor, + u8 addr, int *val) +{ + struct st_lsm6dsx_hw *hw = sensor->hw; + int err, delay; + __le16 data; + + err = st_lsm6dsx_sensor_set_enable(sensor, true); + if (err < 0) + return err; + + /* + * we need to wait for sensor settling time before + * reading data in order to avoid corrupted samples + */ + delay = 1000000000 / sensor->odr; + usleep_range(3 * delay, 4 * delay); + + err = st_lsm6dsx_read_locked(hw, addr, &data, sizeof(data)); + if (err < 0) + return err; + + err = st_lsm6dsx_sensor_set_enable(sensor, false); + if (err < 0) + return err; + + *val = (s16)le16_to_cpu(data); + + return IIO_VAL_INT; +} + +static int st_lsm6dsx_read_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *ch, + int *val, int *val2, long mask) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(iio_dev)) + return -EBUSY; + + ret = st_lsm6dsx_read_oneshot(sensor, ch->address, val); + iio_device_release_direct(iio_dev); + break; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = sensor->odr / 1000; + *val2 = (sensor->odr % 1000) * 1000; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = sensor->gain; + ret = IIO_VAL_INT_PLUS_NANO; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int st_lsm6dsx_write_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + int err = 0; + + if (!iio_device_claim_direct(iio_dev)) + return -EBUSY; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + err = st_lsm6dsx_set_full_scale(sensor, val2); + break; + case IIO_CHAN_INFO_SAMP_FREQ: { + u8 data; + + val = val * 1000 + val2 / 1000; + val = st_lsm6dsx_check_odr(sensor, val, &data); + if (val < 0) { + err = val; + } else { + sensor->odr = val; + sensor->hwfifo_odr_mHz = val; + } + break; + } + default: + err = -EINVAL; + break; + } + + iio_device_release_direct(iio_dev); + + return err; +} + +static int st_lsm6dsx_event_setup(struct st_lsm6dsx_hw *hw, + enum st_lsm6dsx_event_id event, int axis, + bool state) +{ + const struct st_lsm6dsx_event_src *src; + unsigned int data; + int err; + u8 old_enable, new_enable; + + if (!hw->irq_routing) + return -ENOTSUPP; + + /* Enable/disable event interrupt */ + src = &hw->settings->event_settings.sources[event]; + if (src->enable_axis_reg) { + u8 enable_mask; + + switch (axis) { + case IIO_MOD_X: + enable_mask = src->enable_x_mask; + break; + case IIO_MOD_Y: + enable_mask = src->enable_y_mask; + break; + case IIO_MOD_Z: + enable_mask = src->enable_z_mask; + break; + default: + enable_mask = 0; + } + if (enable_mask) { + data = ST_LSM6DSX_SHIFT_VAL(state, enable_mask); + err = st_lsm6dsx_update_bits_locked(hw, + src->enable_axis_reg, + enable_mask, data); + if (err < 0) + return err; + } + } + + /* + * If the set of axes for which the event source is enabled does not + * change from empty to non-empty or vice versa, there is nothing else + * to do. + */ + old_enable = hw->enable_event[event]; + new_enable = state ? (old_enable | BIT(axis)) : + (old_enable & ~BIT(axis)); + if (!old_enable == !new_enable) + return 0; + + data = ST_LSM6DSX_SHIFT_VAL(state, src->enable_mask); + return st_lsm6dsx_update_bits_locked(hw, hw->irq_routing, + src->enable_mask, data); +} + +static enum st_lsm6dsx_event_id +st_lsm6dsx_get_event_id(enum iio_event_type type) +{ + switch (type) { + case IIO_EV_TYPE_THRESH: + return ST_LSM6DSX_EVENT_WAKEUP; + case IIO_EV_TYPE_GESTURE: + return ST_LSM6DSX_EVENT_TAP; + default: + return ST_LSM6DSX_EVENT_MAX; + } +} + +static const struct st_lsm6dsx_reg * +st_lsm6dsx_get_event_reg(struct st_lsm6dsx_hw *hw, + enum st_lsm6dsx_event_id event, + const struct iio_chan_spec *chan) +{ + const struct st_lsm6dsx_event_src *src; + const struct st_lsm6dsx_reg *reg; + + src = &hw->settings->event_settings.sources[event]; + switch (chan->channel2) { + case IIO_MOD_X: + reg = &src->x_value; + break; + case IIO_MOD_Y: + reg = &src->y_value; + break; + case IIO_MOD_Z: + reg = &src->z_value; + break; + default: + return NULL; + } + if (reg->addr) + return reg; + + /* + * The sensor does not support configuring this event source on a per + * axis basis: return the register to configure the event source for all + * axes. + */ + return &src->value; +} + +static int st_lsm6dsx_read_event(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + enum st_lsm6dsx_event_id event = st_lsm6dsx_get_event_id(type); + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + struct st_lsm6dsx_hw *hw = sensor->hw; + const struct st_lsm6dsx_reg *reg; + u8 data; + int err; + + if (event == ST_LSM6DSX_EVENT_MAX) + return -EINVAL; + + reg = st_lsm6dsx_get_event_reg(hw, event, chan); + if (!reg) + return -EINVAL; + + err = st_lsm6dsx_read_locked(hw, reg->addr, &data, sizeof(data)); + if (err < 0) + return err; + + *val2 = 0; + *val = st_lsm6dsx_field_get(reg->mask, data); + + return IIO_VAL_INT; +} + +static int +st_lsm6dsx_write_event(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + enum st_lsm6dsx_event_id event = st_lsm6dsx_get_event_id(type); + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + struct st_lsm6dsx_hw *hw = sensor->hw; + const struct st_lsm6dsx_reg *reg; + unsigned int data; + int err; + + if (event == ST_LSM6DSX_EVENT_MAX) + return -EINVAL; + + if (val < 0 || val > 31) + return -EINVAL; + + reg = st_lsm6dsx_get_event_reg(hw, event, chan); + if (!reg) + return -EINVAL; + + data = ST_LSM6DSX_SHIFT_VAL(val, reg->mask); + err = st_lsm6dsx_update_bits_locked(hw, reg->addr, + reg->mask, data); + if (err < 0) + return -EINVAL; + + return 0; +} + +static int +st_lsm6dsx_read_event_config(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + enum st_lsm6dsx_event_id event = st_lsm6dsx_get_event_id(type); + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + struct st_lsm6dsx_hw *hw = sensor->hw; + + if (event == ST_LSM6DSX_EVENT_MAX) + return -EINVAL; + + return !!(hw->enable_event[event] & BIT(chan->channel2)); +} + +/** + * st_lsm6dsx_check_other_events - Check for enabled sensor events. + * @hw: Sensor hardware instance. + * @curr: Current event type. + * + * Return: whether any events other than @curr are enabled. + */ +static bool st_lsm6dsx_check_other_events(struct st_lsm6dsx_hw *hw, + enum st_lsm6dsx_event_id curr) +{ + enum st_lsm6dsx_event_id other; + + for (other = 0; other < ST_LSM6DSX_EVENT_MAX; other++) { + if (other != curr && hw->enable_event[other]) + return true; + } + + return false; +} + +static int st_lsm6dsx_events_enable(struct st_lsm6dsx_sensor *sensor, + bool state) +{ + struct st_lsm6dsx_hw *hw = sensor->hw; + const struct st_lsm6dsx_reg *reg; + + reg = &hw->settings->event_settings.enable_reg; + if (reg->addr) { + int err; + + err = regmap_update_bits(hw->regmap, reg->addr, reg->mask, + ST_LSM6DSX_SHIFT_VAL(state, reg->mask)); + if (err) + return err; + } + + if (state || !(hw->fifo_mask & BIT(sensor->id))) + return __st_lsm6dsx_sensor_set_enable(sensor, state); + + return 0; +} + +static int +st_lsm6dsx_write_event_config(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + enum st_lsm6dsx_event_id event = st_lsm6dsx_get_event_id(type); + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + struct st_lsm6dsx_hw *hw = sensor->hw; + u8 enable_event; + int err; + + if (event == ST_LSM6DSX_EVENT_MAX) + return -EINVAL; + + if (state) + enable_event = hw->enable_event[event] | BIT(chan->channel2); + else + enable_event = hw->enable_event[event] & ~BIT(chan->channel2); + + /* stop here if no changes have been made */ + if (hw->enable_event[event] == enable_event) + return 0; + + err = st_lsm6dsx_event_setup(hw, event, chan->channel2, state); + if (err < 0) + return err; + + mutex_lock(&hw->conf_lock); + if (enable_event) + err = st_lsm6dsx_events_enable(sensor, true); + else if (!st_lsm6dsx_check_other_events(hw, event)) + err = st_lsm6dsx_events_enable(sensor, false); + mutex_unlock(&hw->conf_lock); + if (err < 0) + return err; + + hw->enable_event[event] = enable_event; + + return 0; +} + +int st_lsm6dsx_set_watermark(struct iio_dev *iio_dev, unsigned int val) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + struct st_lsm6dsx_hw *hw = sensor->hw; + int err; + + val = clamp_val(val, 1, hw->settings->fifo_ops.max_size); + + mutex_lock(&hw->conf_lock); + + err = st_lsm6dsx_update_watermark(sensor, val); + + mutex_unlock(&hw->conf_lock); + + if (err < 0) + return err; + + sensor->watermark = val; + + return 0; +} + +static ssize_t +st_lsm6dsx_sysfs_sampling_frequency_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(dev_to_iio_dev(dev)); + const struct st_lsm6dsx_odr_table_entry *odr_table; + int i, len = 0; + + odr_table = &sensor->hw->settings->odr_table[sensor->id]; + for (i = 0; i < odr_table->odr_len; i++) + len += sysfs_emit_at(buf, len, "%d.%03d%c", + odr_table->odr_avl[i].milli_hz / 1000, + odr_table->odr_avl[i].milli_hz % 1000, + (i == odr_table->odr_len - 1) ? '\n' : ' '); + + return len; +} + +static ssize_t st_lsm6dsx_sysfs_scale_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(dev_to_iio_dev(dev)); + const struct st_lsm6dsx_fs_table_entry *fs_table; + struct st_lsm6dsx_hw *hw = sensor->hw; + int i, len = 0; + + fs_table = &hw->settings->fs_table[sensor->id]; + for (i = 0; i < fs_table->fs_len; i++) + len += sysfs_emit_at(buf, len, "0.%09u%c", + fs_table->fs_avl[i].gain, + (i == fs_table->fs_len - 1) ? '\n' : ' '); + + return len; +} + +static int st_lsm6dsx_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + case IIO_ACCEL: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT_PLUS_MICRO; + } + default: + return IIO_VAL_INT_PLUS_MICRO; + } +} + +static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(st_lsm6dsx_sysfs_sampling_frequency_avail); +static IIO_DEVICE_ATTR(in_accel_scale_available, 0444, + st_lsm6dsx_sysfs_scale_avail, NULL, 0); +static IIO_DEVICE_ATTR(in_anglvel_scale_available, 0444, + st_lsm6dsx_sysfs_scale_avail, NULL, 0); + +static struct attribute *st_lsm6dsx_acc_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_accel_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group st_lsm6dsx_acc_attribute_group = { + .attrs = st_lsm6dsx_acc_attributes, +}; + +static const struct iio_info st_lsm6dsx_acc_info = { + .attrs = &st_lsm6dsx_acc_attribute_group, + .read_raw = st_lsm6dsx_read_raw, + .write_raw = st_lsm6dsx_write_raw, + .read_event_value = st_lsm6dsx_read_event, + .write_event_value = st_lsm6dsx_write_event, + .read_event_config = st_lsm6dsx_read_event_config, + .write_event_config = st_lsm6dsx_write_event_config, + .hwfifo_set_watermark = st_lsm6dsx_set_watermark, + .write_raw_get_fmt = st_lsm6dsx_write_raw_get_fmt, +}; + +static struct attribute *st_lsm6dsx_gyro_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_anglvel_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group st_lsm6dsx_gyro_attribute_group = { + .attrs = st_lsm6dsx_gyro_attributes, +}; + +static const struct iio_info st_lsm6dsx_gyro_info = { + .attrs = &st_lsm6dsx_gyro_attribute_group, + .read_raw = st_lsm6dsx_read_raw, + .write_raw = st_lsm6dsx_write_raw, + .hwfifo_set_watermark = st_lsm6dsx_set_watermark, + .write_raw_get_fmt = st_lsm6dsx_write_raw_get_fmt, +}; + +static int +st_lsm6dsx_get_drdy_reg(struct st_lsm6dsx_hw *hw, + const struct st_lsm6dsx_reg **drdy_reg) +{ + struct device *dev = hw->dev; + const struct st_sensors_platform_data *pdata = dev_get_platdata(dev); + int err = 0, drdy_pin; + + if (device_property_read_u32(dev, "st,drdy-int-pin", &drdy_pin) < 0) + drdy_pin = pdata ? pdata->drdy_int_pin : 1; + + switch (drdy_pin) { + case 1: + hw->irq_routing = hw->settings->irq_config.irq1_func; + *drdy_reg = &hw->settings->irq_config.irq1; + break; + case 2: + hw->irq_routing = hw->settings->irq_config.irq2_func; + *drdy_reg = &hw->settings->irq_config.irq2; + break; + default: + dev_err(hw->dev, "unsupported data ready pin\n"); + err = -EINVAL; + break; + } + + return err; +} + +static int st_lsm6dsx_init_shub(struct st_lsm6dsx_hw *hw) +{ + const struct st_lsm6dsx_shub_settings *hub_settings; + struct device *dev = hw->dev; + const struct st_sensors_platform_data *pdata = dev_get_platdata(dev); + unsigned int data; + int err = 0; + + hub_settings = &hw->settings->shub_settings; + + if (device_property_read_bool(dev, "st,pullups") || + (pdata && pdata->pullups)) { + if (hub_settings->pullup_en.sec_page) { + err = st_lsm6dsx_set_page(hw, true); + if (err < 0) + return err; + } + + data = ST_LSM6DSX_SHIFT_VAL(1, hub_settings->pullup_en.mask); + err = regmap_update_bits(hw->regmap, + hub_settings->pullup_en.addr, + hub_settings->pullup_en.mask, data); + + if (hub_settings->pullup_en.sec_page) + st_lsm6dsx_set_page(hw, false); + + if (err < 0) + return err; + } + + if (hub_settings->aux_sens.addr) { + /* configure aux sensors */ + err = st_lsm6dsx_set_page(hw, true); + if (err < 0) + return err; + + data = ST_LSM6DSX_SHIFT_VAL(3, hub_settings->aux_sens.mask); + err = regmap_update_bits(hw->regmap, + hub_settings->aux_sens.addr, + hub_settings->aux_sens.mask, data); + + st_lsm6dsx_set_page(hw, false); + + if (err < 0) + return err; + } + + if (hub_settings->emb_func.addr) { + data = ST_LSM6DSX_SHIFT_VAL(1, hub_settings->emb_func.mask); + err = regmap_update_bits(hw->regmap, + hub_settings->emb_func.addr, + hub_settings->emb_func.mask, data); + } + + return err; +} + +static int st_lsm6dsx_init_hw_timer(struct st_lsm6dsx_hw *hw) +{ + const struct st_lsm6dsx_hw_ts_settings *ts_settings; + int err, val; + + ts_settings = &hw->settings->ts_settings; + /* enable hw timestamp generation if necessary */ + if (ts_settings->timer_en.addr) { + val = ST_LSM6DSX_SHIFT_VAL(1, ts_settings->timer_en.mask); + err = regmap_update_bits(hw->regmap, + ts_settings->timer_en.addr, + ts_settings->timer_en.mask, val); + if (err < 0) + return err; + } + + /* enable high resolution for hw ts timer if necessary */ + if (ts_settings->hr_timer.addr) { + val = ST_LSM6DSX_SHIFT_VAL(1, ts_settings->hr_timer.mask); + err = regmap_update_bits(hw->regmap, + ts_settings->hr_timer.addr, + ts_settings->hr_timer.mask, val); + if (err < 0) + return err; + } + + /* enable ts queueing in FIFO if necessary */ + if (ts_settings->fifo_en.addr) { + val = ST_LSM6DSX_SHIFT_VAL(1, ts_settings->fifo_en.mask); + err = regmap_update_bits(hw->regmap, + ts_settings->fifo_en.addr, + ts_settings->fifo_en.mask, val); + if (err < 0) + return err; + } + + /* calibrate timestamp sensitivity */ + hw->ts_gain = ts_settings->ts_sensitivity; + if (ts_settings->freq_fine) { + err = regmap_read(hw->regmap, ts_settings->freq_fine, &val); + if (err < 0) + return err; + + hw->ts_gain -= ((s8)val * ts_settings->ts_trim_coeff) / 1000; + } + + return 0; +} + +static int st_lsm6dsx_reset_device(struct st_lsm6dsx_hw *hw) +{ + const struct st_lsm6dsx_reg *reg; + int err; + + /* + * flush hw FIFO before device reset in order to avoid + * possible races on interrupt line 1. If the first interrupt + * line is asserted during hw reset the device will work in + * I3C-only mode (if it is supported) + */ + err = st_lsm6dsx_flush_fifo(hw); + if (err < 0 && err != -ENOTSUPP) + return err; + + /* device sw reset */ + reg = &hw->settings->reset; + err = regmap_update_bits(hw->regmap, reg->addr, reg->mask, + ST_LSM6DSX_SHIFT_VAL(1, reg->mask)); + if (err < 0) + return err; + + msleep(50); + + /* reload trimming parameter */ + reg = &hw->settings->boot; + err = regmap_update_bits(hw->regmap, reg->addr, reg->mask, + ST_LSM6DSX_SHIFT_VAL(1, reg->mask)); + if (err < 0) + return err; + + msleep(50); + + return 0; +} + +static int st_lsm6dsx_init_device(struct st_lsm6dsx_hw *hw) +{ + const struct st_lsm6dsx_reg *reg; + int err; + + err = st_lsm6dsx_reset_device(hw); + if (err < 0) + return err; + + /* enable Block Data Update */ + reg = &hw->settings->bdu; + err = regmap_update_bits(hw->regmap, reg->addr, reg->mask, + ST_LSM6DSX_SHIFT_VAL(1, reg->mask)); + if (err < 0) + return err; + + /* enable FIFO watermak interrupt */ + err = st_lsm6dsx_get_drdy_reg(hw, ®); + if (err < 0) + return err; + + err = regmap_update_bits(hw->regmap, reg->addr, reg->mask, + ST_LSM6DSX_SHIFT_VAL(1, reg->mask)); + if (err < 0) + return err; + + /* enable Latched interrupts for device events */ + if (hw->settings->irq_config.lir.addr) { + reg = &hw->settings->irq_config.lir; + err = regmap_update_bits(hw->regmap, reg->addr, reg->mask, + ST_LSM6DSX_SHIFT_VAL(1, reg->mask)); + if (err < 0) + return err; + + /* enable clear on read for latched interrupts */ + if (hw->settings->irq_config.clear_on_read.addr) { + reg = &hw->settings->irq_config.clear_on_read; + err = regmap_update_bits(hw->regmap, + reg->addr, reg->mask, + ST_LSM6DSX_SHIFT_VAL(1, reg->mask)); + if (err < 0) + return err; + } + } + + /* enable drdy-mas if available */ + if (hw->settings->drdy_mask.addr) { + reg = &hw->settings->drdy_mask; + err = regmap_update_bits(hw->regmap, reg->addr, reg->mask, + ST_LSM6DSX_SHIFT_VAL(1, reg->mask)); + if (err < 0) + return err; + } + + err = st_lsm6dsx_init_shub(hw); + if (err < 0) + return err; + + return st_lsm6dsx_init_hw_timer(hw); +} + +static struct iio_dev *st_lsm6dsx_alloc_iiodev(struct st_lsm6dsx_hw *hw, + enum st_lsm6dsx_sensor_id id, + const char *name) +{ + struct st_lsm6dsx_sensor *sensor; + struct iio_dev *iio_dev; + + iio_dev = devm_iio_device_alloc(hw->dev, sizeof(*sensor)); + if (!iio_dev) + return NULL; + + iio_dev->modes = INDIO_DIRECT_MODE; + iio_dev->available_scan_masks = st_lsm6dsx_available_scan_masks; + iio_dev->channels = hw->settings->channels[id].chan; + iio_dev->num_channels = hw->settings->channels[id].len; + + sensor = iio_priv(iio_dev); + sensor->id = id; + sensor->hw = hw; + sensor->odr = hw->settings->odr_table[id].odr_avl[0].milli_hz; + sensor->hwfifo_odr_mHz = sensor->odr; + sensor->gain = hw->settings->fs_table[id].fs_avl[0].gain; + sensor->watermark = 1; + + switch (id) { + case ST_LSM6DSX_ID_ACC: + iio_dev->info = &st_lsm6dsx_acc_info; + scnprintf(sensor->name, sizeof(sensor->name), "%s_accel", + name); + break; + case ST_LSM6DSX_ID_GYRO: + iio_dev->info = &st_lsm6dsx_gyro_info; + scnprintf(sensor->name, sizeof(sensor->name), "%s_gyro", + name); + break; + default: + return NULL; + } + iio_dev->name = sensor->name; + + return iio_dev; +} + +static bool +st_lsm6dsx_report_events(struct st_lsm6dsx_hw *hw, enum st_lsm6dsx_event_id id, + enum iio_event_type type, enum iio_event_direction dir) +{ + const struct st_lsm6dsx_event_settings *event_settings; + const struct st_lsm6dsx_event_src *src; + int err, data; + s64 timestamp; + + if (!hw->enable_event[id]) + return false; + + event_settings = &hw->settings->event_settings; + src = &event_settings->sources[id]; + err = st_lsm6dsx_read_locked(hw, src->status.addr, + &data, sizeof(data)); + if (err < 0) + return false; + + timestamp = iio_get_time_ns(hw->iio_devs[ST_LSM6DSX_ID_ACC]); + if ((data & src->status_z_mask) && + (hw->enable_event[id] & BIT(IIO_MOD_Z))) + iio_push_event(hw->iio_devs[ST_LSM6DSX_ID_ACC], + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Z, + type, + dir), + timestamp); + + if ((data & src->status_y_mask) && + (hw->enable_event[id] & BIT(IIO_MOD_Y))) + iio_push_event(hw->iio_devs[ST_LSM6DSX_ID_ACC], + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Y, + type, + dir), + timestamp); + + if ((data & src->status_x_mask) && + (hw->enable_event[id] & BIT(IIO_MOD_X))) + iio_push_event(hw->iio_devs[ST_LSM6DSX_ID_ACC], + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_X, + type, + dir), + timestamp); + + return data & src->status.mask; +} + +static bool st_lsm6dsx_report_motion_event(struct st_lsm6dsx_hw *hw) +{ + bool events_found; + + events_found = st_lsm6dsx_report_events(hw, ST_LSM6DSX_EVENT_WAKEUP, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER); + events_found |= st_lsm6dsx_report_events(hw, ST_LSM6DSX_EVENT_TAP, + IIO_EV_TYPE_GESTURE, + IIO_EV_DIR_SINGLETAP); + + return events_found; +} + +static irqreturn_t st_lsm6dsx_handler_thread(int irq, void *private) +{ + struct st_lsm6dsx_hw *hw = private; + int fifo_len = 0, len; + bool event; + + event = st_lsm6dsx_report_motion_event(hw); + + if (!hw->settings->fifo_ops.read_fifo) + return event ? IRQ_HANDLED : IRQ_NONE; + + /* + * If we are using edge IRQs, new samples can arrive while + * processing current interrupt since there are no hw + * guarantees the irq line stays "low" long enough to properly + * detect the new interrupt. In this case the new sample will + * be missed. + * Polling FIFO status register allow us to read new + * samples even if the interrupt arrives while processing + * previous data and the timeslot where the line is "low" is + * too short to be properly detected. + */ + do { + mutex_lock(&hw->fifo_lock); + len = hw->settings->fifo_ops.read_fifo(hw); + mutex_unlock(&hw->fifo_lock); + + if (len > 0) + fifo_len += len; + } while (len > 0); + + return fifo_len || event ? IRQ_HANDLED : IRQ_NONE; +} + +static irqreturn_t st_lsm6dsx_sw_trigger_handler_thread(int irq, + void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *iio_dev = pf->indio_dev; + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + struct st_lsm6dsx_hw *hw = sensor->hw; + + if (sensor->id == ST_LSM6DSX_ID_EXT0 || + sensor->id == ST_LSM6DSX_ID_EXT1 || + sensor->id == ST_LSM6DSX_ID_EXT2) + st_lsm6dsx_shub_read_output(hw, + (u8 *)hw->scan[sensor->id].channels, + sizeof(hw->scan[sensor->id].channels)); + else + st_lsm6dsx_read_locked(hw, iio_dev->channels[0].address, + hw->scan[sensor->id].channels, + sizeof(hw->scan[sensor->id].channels)); + + iio_push_to_buffers_with_timestamp(iio_dev, &hw->scan[sensor->id], + iio_get_time_ns(iio_dev)); + iio_trigger_notify_done(iio_dev->trig); + + return IRQ_HANDLED; +} + +static int st_lsm6dsx_irq_setup(struct st_lsm6dsx_hw *hw) +{ + const struct st_lsm6dsx_reg *reg; + struct device *dev = hw->dev; + const struct st_sensors_platform_data *pdata = dev_get_platdata(dev); + unsigned long irq_type; + bool irq_active_low; + int err; + + irq_type = irq_get_trigger_type(hw->irq); + switch (irq_type) { + case IRQF_TRIGGER_HIGH: + case IRQF_TRIGGER_RISING: + irq_active_low = false; + break; + case IRQF_TRIGGER_LOW: + case IRQF_TRIGGER_FALLING: + irq_active_low = true; + break; + default: + dev_info(hw->dev, "mode %lx unsupported\n", irq_type); + return -EINVAL; + } + + reg = &hw->settings->irq_config.hla; + err = regmap_update_bits(hw->regmap, reg->addr, reg->mask, + ST_LSM6DSX_SHIFT_VAL(irq_active_low, + reg->mask)); + if (err < 0) + return err; + + if (device_property_read_bool(dev, "drive-open-drain") || + (pdata && pdata->open_drain)) { + reg = &hw->settings->irq_config.od; + err = regmap_update_bits(hw->regmap, reg->addr, reg->mask, + ST_LSM6DSX_SHIFT_VAL(1, reg->mask)); + if (err < 0) + return err; + + irq_type |= IRQF_SHARED; + } + + err = devm_request_threaded_irq(hw->dev, hw->irq, + NULL, + st_lsm6dsx_handler_thread, + irq_type | IRQF_ONESHOT, + "lsm6dsx", hw); + if (err) + return err; + + return 0; +} + +static int st_lsm6dsx_sw_buffer_preenable(struct iio_dev *iio_dev) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + + return st_lsm6dsx_device_set_enable(sensor, true); +} + +static int st_lsm6dsx_sw_buffer_postdisable(struct iio_dev *iio_dev) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + + return st_lsm6dsx_device_set_enable(sensor, false); +} + +static const struct iio_buffer_setup_ops st_lsm6dsx_sw_buffer_ops = { + .preenable = st_lsm6dsx_sw_buffer_preenable, + .postdisable = st_lsm6dsx_sw_buffer_postdisable, +}; + +static int st_lsm6dsx_sw_buffers_setup(struct st_lsm6dsx_hw *hw) +{ + int i; + + for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) { + int err; + + if (!hw->iio_devs[i]) + continue; + + err = devm_iio_triggered_buffer_setup(hw->dev, + hw->iio_devs[i], NULL, + st_lsm6dsx_sw_trigger_handler_thread, + &st_lsm6dsx_sw_buffer_ops); + if (err) + return err; + } + + return 0; +} + +static int st_lsm6dsx_init_regulators(struct device *dev) +{ + /* vdd-vddio power regulators */ + static const char * const regulators[] = { "vdd", "vddio" }; + int err; + + err = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulators), + regulators); + if (err) + return dev_err_probe(dev, err, "failed to enable regulators\n"); + + msleep(50); + + return 0; +} + +int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, + struct regmap *regmap) +{ + const struct st_sensors_platform_data *pdata = dev_get_platdata(dev); + const struct st_lsm6dsx_shub_settings *hub_settings; + struct st_lsm6dsx_hw *hw; + const char *name = NULL; + int i, err; + + hw = devm_kzalloc(dev, sizeof(*hw), GFP_KERNEL); + if (!hw) + return -ENOMEM; + + dev_set_drvdata(dev, hw); + + mutex_init(&hw->fifo_lock); + mutex_init(&hw->conf_lock); + mutex_init(&hw->page_lock); + + err = st_lsm6dsx_init_regulators(dev); + if (err) + return err; + + hw->buff = devm_kzalloc(dev, ST_LSM6DSX_BUFF_SIZE, GFP_KERNEL); + if (!hw->buff) + return -ENOMEM; + + hw->dev = dev; + hw->irq = irq; + hw->regmap = regmap; + + err = st_lsm6dsx_check_whoami(hw, hw_id, &name); + if (err < 0) + return err; + + for (i = 0; i < ST_LSM6DSX_ID_EXT0; i++) { + hw->iio_devs[i] = st_lsm6dsx_alloc_iiodev(hw, i, name); + if (!hw->iio_devs[i]) + return -ENOMEM; + } + + err = st_lsm6dsx_init_device(hw); + if (err < 0) + return err; + + hub_settings = &hw->settings->shub_settings; + if (hub_settings->master_en.addr && + !device_property_read_bool(dev, "st,disable-sensor-hub")) { + err = st_lsm6dsx_shub_probe(hw, name); + if (err < 0) + return err; + } + + if (hw->settings->fusion_settings.chan) { + err = st_lsm6dsx_fusion_probe(hw, name); + if (err) + return err; + } + + if (hw->irq > 0) { + err = st_lsm6dsx_irq_setup(hw); + if (err < 0) + return err; + + err = st_lsm6dsx_fifo_setup(hw); + if (err < 0) + return err; + } + + if (!hw->irq || !hw->settings->fifo_ops.read_fifo) { + /* + * Rely on sw triggers (e.g. hr-timers) if irq pin is not + * connected of if the device does not support HW FIFO + */ + err = st_lsm6dsx_sw_buffers_setup(hw); + if (err) + return err; + } + + if (!iio_read_acpi_mount_matrix(hw->dev, &hw->orientation, "ROTM")) { + err = iio_read_mount_matrix(hw->dev, &hw->orientation); + if (err) + return err; + } + + for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) { + if (!hw->iio_devs[i]) + continue; + + err = devm_iio_device_register(hw->dev, hw->iio_devs[i]); + if (err) + return err; + } + + if (device_property_read_bool(dev, "wakeup-source") || + (pdata && pdata->wakeup_source)) { + err = devm_device_init_wakeup(dev); + if (err) + return dev_err_probe(dev, err, "Failed to init wakeup\n"); + } + + return 0; +} +EXPORT_SYMBOL_NS(st_lsm6dsx_probe, "IIO_LSM6DSX"); + +static int st_lsm6dsx_suspend(struct device *dev) +{ + struct st_lsm6dsx_hw *hw = dev_get_drvdata(dev); + struct st_lsm6dsx_sensor *sensor; + int i, err = 0; + + for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) { + if (!hw->iio_devs[i]) + continue; + + sensor = iio_priv(hw->iio_devs[i]); + if (!(hw->enable_mask & BIT(sensor->id))) + continue; + + if (device_may_wakeup(dev) && + st_lsm6dsx_check_events(sensor)) { + /* Enable wake from IRQ */ + enable_irq_wake(hw->irq); + continue; + } + + err = st_lsm6dsx_device_set_enable(sensor, false); + if (err < 0) + return err; + + hw->suspend_mask |= BIT(sensor->id); + } + + if (hw->fifo_mask) + err = st_lsm6dsx_flush_fifo(hw); + + return err; +} + +static int st_lsm6dsx_resume(struct device *dev) +{ + struct st_lsm6dsx_hw *hw = dev_get_drvdata(dev); + struct st_lsm6dsx_sensor *sensor; + int i, err = 0; + + for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) { + if (!hw->iio_devs[i]) + continue; + + sensor = iio_priv(hw->iio_devs[i]); + if (device_may_wakeup(dev) && + st_lsm6dsx_check_events(sensor)) + disable_irq_wake(hw->irq); + + if (!(hw->suspend_mask & BIT(sensor->id))) + continue; + + err = st_lsm6dsx_device_set_enable(sensor, true); + if (err < 0) + return err; + + hw->suspend_mask &= ~BIT(sensor->id); + } + + if (hw->fifo_mask) + err = st_lsm6dsx_resume_fifo(hw); + + return err; +} + +EXPORT_NS_SIMPLE_DEV_PM_OPS(st_lsm6dsx_pm_ops, st_lsm6dsx_suspend, + st_lsm6dsx_resume, IIO_LSM6DSX); + +MODULE_AUTHOR("Lorenzo Bianconi "); +MODULE_AUTHOR("Denis Ciocca "); +MODULE_DESCRIPTION("STMicroelectronics st_lsm6dsx driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_fusion.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_fusion.c new file mode 100644 index 000000000..5321033da --- /dev/null +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_fusion.c @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics st_lsm6dsx IMU sensor fusion + * + * Copyright 2026 BayLibre, SAS + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "st_lsm6dsx.h" + +static int +st_lsm6dsx_fusion_get_odr_val(const struct st_lsm6dsx_fusion_settings *settings, + u32 odr_mHz, u8 *val) +{ + int odr_hz = odr_mHz / MILLI; + int i; + + for (i = 0; i < settings->odr_len; i++) { + if (settings->odr_hz[i] == odr_hz) + break; + } + if (i == settings->odr_len) + return -EINVAL; + + *val = i; + return 0; +} + +/** + * st_lsm6dsx_fusion_page_enable - Enable access to sensor fusion configuration + * registers. + * @hw: Sensor hardware instance. + * + * Return: 0 on success, negative value on error. + */ +static int st_lsm6dsx_fusion_page_enable(struct st_lsm6dsx_hw *hw) +{ + const struct st_lsm6dsx_reg *mux; + + mux = &hw->settings->fusion_settings.page_mux; + + return regmap_set_bits(hw->regmap, mux->addr, mux->mask); +} + +/** + * st_lsm6dsx_fusion_page_disable - Disable access to sensor fusion + * configuration registers. + * @hw: Sensor hardware instance. + * + * Return: 0 on success, negative value on error. + */ +static int st_lsm6dsx_fusion_page_disable(struct st_lsm6dsx_hw *hw) +{ + const struct st_lsm6dsx_reg *mux; + + mux = &hw->settings->fusion_settings.page_mux; + + return regmap_clear_bits(hw->regmap, mux->addr, mux->mask); +} + +static int st_lsm6dsx_fusion_set_odr_locked(struct st_lsm6dsx_sensor *sensor, + bool enable) +{ + const struct st_lsm6dsx_fusion_settings *settings; + struct st_lsm6dsx_hw *hw = sensor->hw; + int err; + + settings = &hw->settings->fusion_settings; + if (enable) { + const struct st_lsm6dsx_reg *reg = &settings->odr_reg; + u8 odr_val; + u8 data; + + st_lsm6dsx_fusion_get_odr_val(settings, sensor->hwfifo_odr_mHz, + &odr_val); + data = ST_LSM6DSX_SHIFT_VAL(odr_val, reg->mask); + err = regmap_update_bits(hw->regmap, reg->addr, reg->mask, + data); + if (err) + return err; + } + + return regmap_assign_bits(hw->regmap, settings->fifo_enable.addr, + settings->fifo_enable.mask, enable); +} + +int st_lsm6dsx_fusion_set_enable(struct st_lsm6dsx_sensor *sensor, bool enable) +{ + struct st_lsm6dsx_hw *hw = sensor->hw; + const struct st_lsm6dsx_reg *en_reg; + int err; + + guard(mutex)(&hw->page_lock); + + en_reg = &hw->settings->fusion_settings.enable; + err = st_lsm6dsx_fusion_page_enable(hw); + if (err) + return err; + + err = regmap_assign_bits(hw->regmap, en_reg->addr, en_reg->mask, enable); + if (err) { + st_lsm6dsx_fusion_page_disable(hw); + return err; + } + + if (enable) + hw->enable_mask |= BIT(ST_LSM6DSX_ID_FUSION); + else + hw->enable_mask &= ~BIT(ST_LSM6DSX_ID_FUSION); + + return st_lsm6dsx_fusion_page_disable(hw); +} + +int st_lsm6dsx_fusion_set_odr(struct st_lsm6dsx_sensor *sensor, bool enable) +{ + struct st_lsm6dsx_hw *hw = sensor->hw; + int err; + + guard(mutex)(&hw->page_lock); + + err = st_lsm6dsx_fusion_page_enable(hw); + if (err) + return err; + + err = st_lsm6dsx_fusion_set_odr_locked(sensor, enable); + if (err) { + st_lsm6dsx_fusion_page_disable(hw); + return err; + } + + return st_lsm6dsx_fusion_page_disable(hw); +} + +static int st_lsm6dsx_fusion_read_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *ch, + int *val, int *val2, long mask) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *val = sensor->hwfifo_odr_mHz / MILLI; + *val2 = (sensor->hwfifo_odr_mHz % MILLI) * (MICRO / MILLI); + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int st_lsm6dsx_fusion_write_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + const struct st_lsm6dsx_fusion_settings *settings; + int err; + + settings = &sensor->hw->settings->fusion_settings; + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: { + u32 odr_mHz = val * MILLI + val2 * (MILLI / MICRO); + u8 odr_val; + + /* check that the requested frequency is supported */ + err = st_lsm6dsx_fusion_get_odr_val(settings, odr_mHz, &odr_val); + if (err) + return err; + + sensor->hwfifo_odr_mHz = odr_mHz; + return 0; + } + default: + return -EINVAL; + } +} + +static int st_lsm6dsx_fusion_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, + int *length, long mask) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(indio_dev); + const struct st_lsm6dsx_fusion_settings *settings; + + settings = &sensor->hw->settings->fusion_settings; + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = settings->odr_hz; + *type = IIO_VAL_INT; + *length = settings->odr_len; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const struct iio_info st_lsm6dsx_fusion_info = { + .read_raw = st_lsm6dsx_fusion_read_raw, + .read_avail = st_lsm6dsx_fusion_read_avail, + .write_raw = st_lsm6dsx_fusion_write_raw, + .hwfifo_set_watermark = st_lsm6dsx_set_watermark, +}; + +int st_lsm6dsx_fusion_probe(struct st_lsm6dsx_hw *hw, const char *name) +{ + const struct st_lsm6dsx_fusion_settings *settings; + struct st_lsm6dsx_sensor *sensor; + struct iio_dev *iio_dev; + int ret; + + iio_dev = devm_iio_device_alloc(hw->dev, sizeof(*sensor)); + if (!iio_dev) + return -ENOMEM; + + settings = &hw->settings->fusion_settings; + sensor = iio_priv(iio_dev); + sensor->id = ST_LSM6DSX_ID_FUSION; + sensor->hw = hw; + sensor->hwfifo_odr_mHz = settings->odr_hz[0] * MILLI; + sensor->watermark = 1; + iio_dev->modes = INDIO_DIRECT_MODE; + iio_dev->info = &st_lsm6dsx_fusion_info; + iio_dev->channels = settings->chan; + iio_dev->num_channels = settings->chan_len; + ret = snprintf(sensor->name, sizeof(sensor->name), "%s_fusion", name); + if (ret >= sizeof(sensor->name)) + return -E2BIG; + iio_dev->name = sensor->name; + + /* + * Put the IIO device pointer in the iio_devs array so that the caller + * can set up a buffer and register this IIO device. + */ + hw->iio_devs[ST_LSM6DSX_ID_FUSION] = iio_dev; + + return 0; +} diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i2c.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i2c.c new file mode 100644 index 000000000..edec898cb --- /dev/null +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i2c.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics st_lsm6dsx i2c driver + * + * Copyright 2016 STMicroelectronics Inc. + * + * Lorenzo Bianconi + * Denis Ciocca + */ + +#include +#include +#include +#include +#include + +#include "st_lsm6dsx.h" + +static const struct regmap_config st_lsm6dsx_i2c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int st_lsm6dsx_i2c_probe(struct i2c_client *client) +{ + int hw_id; + struct regmap *regmap; + + hw_id = (kernel_ulong_t)device_get_match_data(&client->dev); + if (!hw_id) + hw_id = i2c_client_get_device_id(client)->driver_data; + if (!hw_id) + return -EINVAL; + + regmap = devm_regmap_init_i2c(client, &st_lsm6dsx_i2c_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Failed to register i2c regmap %ld\n", PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + return st_lsm6dsx_probe(&client->dev, client->irq, hw_id, regmap); +} + +static const struct of_device_id st_lsm6dsx_i2c_of_match[] = { + { + .compatible = "st,lsm6ds3", + .data = (void *)ST_LSM6DS3_ID, + }, + { + .compatible = "st,lsm6ds3h", + .data = (void *)ST_LSM6DS3H_ID, + }, + { + .compatible = "st,lsm6dsl", + .data = (void *)ST_LSM6DSL_ID, + }, + { + .compatible = "st,lsm6dsm", + .data = (void *)ST_LSM6DSM_ID, + }, + { + .compatible = "st,ism330dlc", + .data = (void *)ST_ISM330DLC_ID, + }, + { + .compatible = "st,lsm6dso", + .data = (void *)ST_LSM6DSO_ID, + }, + { + .compatible = "st,asm330lhh", + .data = (void *)ST_ASM330LHH_ID, + }, + { + .compatible = "st,lsm6dsox", + .data = (void *)ST_LSM6DSOX_ID, + }, + { + .compatible = "st,lsm6dsr", + .data = (void *)ST_LSM6DSR_ID, + }, + { + .compatible = "st,lsm6ds3tr-c", + .data = (void *)ST_LSM6DS3TRC_ID, + }, + { + .compatible = "st,ism330dhcx", + .data = (void *)ST_ISM330DHCX_ID, + }, + { + .compatible = "st,lsm9ds1-imu", + .data = (void *)ST_LSM9DS1_ID, + }, + { + .compatible = "st,lsm6ds0", + .data = (void *)ST_LSM6DS0_ID, + }, + { + .compatible = "st,lsm6dsrx", + .data = (void *)ST_LSM6DSRX_ID, + }, + { + .compatible = "st,lsm6dst", + .data = (void *)ST_LSM6DST_ID, + }, + { + .compatible = "st,lsm6dsop", + .data = (void *)ST_LSM6DSOP_ID, + }, + { + .compatible = "st,asm330lhhx", + .data = (void *)ST_ASM330LHHX_ID, + }, + { + .compatible = "st,lsm6dstx", + .data = (void *)ST_LSM6DSTX_ID, + }, + { + .compatible = "st,lsm6dsv", + .data = (void *)ST_LSM6DSV_ID, + }, + { + .compatible = "st,lsm6dsv16x", + .data = (void *)ST_LSM6DSV16X_ID, + }, + { + .compatible = "st,lsm6dso16is", + .data = (void *)ST_LSM6DSO16IS_ID, + }, + { + .compatible = "st,ism330is", + .data = (void *)ST_ISM330IS_ID, + }, + { + .compatible = "st,asm330lhb", + .data = (void *)ST_ASM330LHB_ID, + }, + { + .compatible = "st,asm330lhhxg1", + .data = (void *)ST_ASM330LHHXG1_ID, + }, + { } +}; +MODULE_DEVICE_TABLE(of, st_lsm6dsx_i2c_of_match); + +static const struct acpi_device_id st_lsm6dsx_i2c_acpi_match[] = { + { "SMO8B30", ST_LSM6DS3TRC_ID, }, + { "SMOCF00", ST_LSM6DSO_ID, }, + { } +}; +MODULE_DEVICE_TABLE(acpi, st_lsm6dsx_i2c_acpi_match); + +static const struct i2c_device_id st_lsm6dsx_i2c_id_table[] = { + { .name = ST_LSM6DS3_DEV_NAME, .driver_data = ST_LSM6DS3_ID }, + { .name = ST_LSM6DS3H_DEV_NAME, .driver_data = ST_LSM6DS3H_ID }, + { .name = ST_LSM6DSL_DEV_NAME, .driver_data = ST_LSM6DSL_ID }, + { .name = ST_LSM6DSM_DEV_NAME, .driver_data = ST_LSM6DSM_ID }, + { .name = ST_ISM330DLC_DEV_NAME, .driver_data = ST_ISM330DLC_ID }, + { .name = ST_LSM6DSO_DEV_NAME, .driver_data = ST_LSM6DSO_ID }, + { .name = ST_ASM330LHH_DEV_NAME, .driver_data = ST_ASM330LHH_ID }, + { .name = ST_LSM6DSOX_DEV_NAME, .driver_data = ST_LSM6DSOX_ID }, + { .name = ST_LSM6DSR_DEV_NAME, .driver_data = ST_LSM6DSR_ID }, + { .name = ST_LSM6DS3TRC_DEV_NAME, .driver_data = ST_LSM6DS3TRC_ID }, + { .name = ST_ISM330DHCX_DEV_NAME, .driver_data = ST_ISM330DHCX_ID }, + { .name = ST_LSM9DS1_DEV_NAME, .driver_data = ST_LSM9DS1_ID }, + { .name = ST_LSM6DS0_DEV_NAME, .driver_data = ST_LSM6DS0_ID }, + { .name = ST_LSM6DSRX_DEV_NAME, .driver_data = ST_LSM6DSRX_ID }, + { .name = ST_LSM6DST_DEV_NAME, .driver_data = ST_LSM6DST_ID }, + { .name = ST_LSM6DSOP_DEV_NAME, .driver_data = ST_LSM6DSOP_ID }, + { .name = ST_ASM330LHHX_DEV_NAME, .driver_data = ST_ASM330LHHX_ID }, + { .name = ST_LSM6DSTX_DEV_NAME, .driver_data = ST_LSM6DSTX_ID }, + { .name = ST_LSM6DSV_DEV_NAME, .driver_data = ST_LSM6DSV_ID }, + { .name = ST_LSM6DSV16X_DEV_NAME, .driver_data = ST_LSM6DSV16X_ID }, + { .name = ST_LSM6DSO16IS_DEV_NAME, .driver_data = ST_LSM6DSO16IS_ID }, + { .name = ST_ISM330IS_DEV_NAME, .driver_data = ST_ISM330IS_ID }, + { .name = ST_ASM330LHB_DEV_NAME, .driver_data = ST_ASM330LHB_ID }, + { .name = ST_ASM330LHHXG1_DEV_NAME, .driver_data = ST_ASM330LHHXG1_ID }, + { } +}; +MODULE_DEVICE_TABLE(i2c, st_lsm6dsx_i2c_id_table); + +static struct i2c_driver st_lsm6dsx_driver = { + .driver = { + .name = "st_lsm6dsx_i2c", + .pm = pm_sleep_ptr(&st_lsm6dsx_pm_ops), + .of_match_table = st_lsm6dsx_i2c_of_match, + .acpi_match_table = st_lsm6dsx_i2c_acpi_match, + }, + .probe = st_lsm6dsx_i2c_probe, + .id_table = st_lsm6dsx_i2c_id_table, +}; +module_i2c_driver(st_lsm6dsx_driver); + +MODULE_AUTHOR("Lorenzo Bianconi "); +MODULE_AUTHOR("Denis Ciocca "); +MODULE_DESCRIPTION("STMicroelectronics st_lsm6dsx i2c driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_LSM6DSX"); diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i3c.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i3c.c new file mode 100644 index 000000000..cd59edcf6 --- /dev/null +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i3c.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018 Synopsys, Inc. and/or its affiliates. + * + * Author: Vitor Soares + */ + +#include +#include +#include +#include +#include + +#include "st_lsm6dsx.h" + +static const struct i3c_device_id st_lsm6dsx_i3c_ids[] = { + I3C_DEVICE(0x0104, 0x006C, (void *)ST_LSM6DSO_ID), + I3C_DEVICE(0x0104, 0x006B, (void *)ST_LSM6DSR_ID), + { } +}; +MODULE_DEVICE_TABLE(i3c, st_lsm6dsx_i3c_ids); + +static int st_lsm6dsx_i3c_probe(struct i3c_device *i3cdev) +{ + struct regmap_config st_lsm6dsx_i3c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + }; + const struct i3c_device_id *id = i3c_device_match_id(i3cdev, + st_lsm6dsx_i3c_ids); + struct device *dev = i3cdev_to_dev(i3cdev); + struct regmap *regmap; + + regmap = devm_regmap_init_i3c(i3cdev, &st_lsm6dsx_i3c_regmap_config); + if (IS_ERR(regmap)) { + dev_err(dev, "Failed to register i3c regmap %ld\n", PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + return st_lsm6dsx_probe(dev, 0, (uintptr_t)id->data, regmap); +} + +static struct i3c_driver st_lsm6dsx_driver = { + .driver = { + .name = "st_lsm6dsx_i3c", + .pm = pm_sleep_ptr(&st_lsm6dsx_pm_ops), + }, + .probe = st_lsm6dsx_i3c_probe, + .id_table = st_lsm6dsx_i3c_ids, +}; +module_i3c_driver(st_lsm6dsx_driver); + +MODULE_AUTHOR("Vitor Soares "); +MODULE_DESCRIPTION("STMicroelectronics st_lsm6dsx i3c driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_LSM6DSX"); diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c new file mode 100644 index 000000000..d6a1eeb15 --- /dev/null +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c @@ -0,0 +1,922 @@ +/* + * STMicroelectronics st_lsm6dsx i2c controller driver + * + * i2c controller embedded in lsm6dx series can connect up to four + * slave devices using accelerometer sensor as trigger for i2c + * read/write operations. Current implementation relies on SLV0 channel + * for slave configuration and SLV{1,2,3} to read data and push them into + * the hw FIFO + * + * Copyright (C) 2018 Lorenzo Bianconi + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ +#include +#include +#include +#include +#include + +#include "st_lsm6dsx.h" + +#define ST_LSM6DSX_SLV_ADDR(n, base) ((base) + (n) * 3) +#define ST_LSM6DSX_SLV_SUB_ADDR(n, base) ((base) + 1 + (n) * 3) +#define ST_LSM6DSX_SLV_CONFIG(n, base) ((base) + 2 + (n) * 3) + +#define ST_LS6DSX_READ_OP_MASK GENMASK(2, 0) + +static const struct st_lsm6dsx_ext_dev_settings st_lsm6dsx_ext_dev_table[] = { + /* LIS2MDL */ + { + .i2c_addr = { 0x1e }, + .wai = { + .addr = 0x4f, + .val = 0x40, + }, + .id = ST_LSM6DSX_ID_MAGN, + .odr_table = { + .reg = { + .addr = 0x60, + .mask = GENMASK(3, 2), + }, + .odr_avl[0] = { 10000, 0x0 }, + .odr_avl[1] = { 20000, 0x1 }, + .odr_avl[2] = { 50000, 0x2 }, + .odr_avl[3] = { 100000, 0x3 }, + .odr_len = 4, + }, + .fs_table = { + .fs_avl[0] = { + .gain = 1500, + .val = 0x0, + }, /* 1500 uG/LSB */ + .fs_len = 1, + }, + .temp_comp = { + .addr = 0x60, + .mask = BIT(7), + }, + .pwr_table = { + .reg = { + .addr = 0x60, + .mask = GENMASK(1, 0), + }, + .off_val = 0x2, + .on_val = 0x0, + }, + .off_canc = { + .addr = 0x61, + .mask = BIT(1), + }, + .bdu = { + .addr = 0x62, + .mask = BIT(4), + }, + .out = { + .addr = 0x68, + .len = 6, + }, + }, + /* LIS3MDL */ + { + .i2c_addr = { 0x1e }, + .wai = { + .addr = 0x0f, + .val = 0x3d, + }, + .id = ST_LSM6DSX_ID_MAGN, + .odr_table = { + .reg = { + .addr = 0x20, + .mask = GENMASK(4, 2), + }, + .odr_avl[0] = { 1000, 0x0 }, + .odr_avl[1] = { 2000, 0x1 }, + .odr_avl[2] = { 3000, 0x2 }, + .odr_avl[3] = { 5000, 0x3 }, + .odr_avl[4] = { 10000, 0x4 }, + .odr_avl[5] = { 20000, 0x5 }, + .odr_avl[6] = { 40000, 0x6 }, + .odr_avl[7] = { 80000, 0x7 }, + .odr_len = 8, + }, + .fs_table = { + .reg = { + .addr = 0x21, + .mask = GENMASK(6, 5), + }, + .fs_avl[0] = { + .gain = 146, + .val = 0x00, + }, /* 4000 uG/LSB */ + .fs_avl[1] = { + .gain = 292, + .val = 0x01, + }, /* 8000 uG/LSB */ + .fs_avl[2] = { + .gain = 438, + .val = 0x02, + }, /* 12000 uG/LSB */ + .fs_avl[3] = { + .gain = 584, + .val = 0x03, + }, /* 16000 uG/LSB */ + .fs_len = 4, + }, + .pwr_table = { + .reg = { + .addr = 0x22, + .mask = GENMASK(1, 0), + }, + .off_val = 0x2, + .on_val = 0x0, + }, + .bdu = { + .addr = 0x24, + .mask = BIT(6), + }, + .out = { + .addr = 0x28, + .len = 6, + }, + }, +}; + +static void st_lsm6dsx_shub_wait_complete(struct st_lsm6dsx_hw *hw) +{ + struct st_lsm6dsx_sensor *sensor; + u32 odr, timeout; + + sensor = iio_priv(hw->iio_devs[ST_LSM6DSX_ID_ACC]); + odr = (hw->enable_mask & BIT(ST_LSM6DSX_ID_ACC)) ? sensor->odr : 12500; + /* set 10ms as minimum timeout for i2c slave configuration */ + timeout = max_t(u32, 2000000U / odr + 1, 10); + msleep(timeout); +} + +/* + * st_lsm6dsx_shub_read_output - read i2c controller register + * + * Read st_lsm6dsx i2c controller register + */ +int st_lsm6dsx_shub_read_output(struct st_lsm6dsx_hw *hw, u8 *data, int len) +{ + const struct st_lsm6dsx_shub_settings *hub_settings; + int err; + + mutex_lock(&hw->page_lock); + + hub_settings = &hw->settings->shub_settings; + if (hub_settings->shub_out.sec_page) { + err = st_lsm6dsx_set_page(hw, true); + if (err < 0) + goto out; + } + + err = regmap_bulk_read(hw->regmap, hub_settings->shub_out.addr, + data, len); + + if (hub_settings->shub_out.sec_page) + st_lsm6dsx_set_page(hw, false); +out: + mutex_unlock(&hw->page_lock); + + return err; +} + +/* + * st_lsm6dsx_shub_write_reg - write i2c controller register + * + * Write st_lsm6dsx i2c controller register + */ +static int st_lsm6dsx_shub_write_reg(struct st_lsm6dsx_hw *hw, u8 addr, + u8 *data, int len) +{ + int err; + + mutex_lock(&hw->page_lock); + err = st_lsm6dsx_set_page(hw, true); + if (err < 0) + goto out; + + err = regmap_bulk_write(hw->regmap, addr, data, len); + + st_lsm6dsx_set_page(hw, false); +out: + mutex_unlock(&hw->page_lock); + + return err; +} + +static int +st_lsm6dsx_shub_write_reg_with_mask(struct st_lsm6dsx_hw *hw, u8 addr, + u8 mask, u8 val) +{ + int err; + + mutex_lock(&hw->page_lock); + err = st_lsm6dsx_set_page(hw, true); + if (err < 0) + goto out; + + err = regmap_update_bits(hw->regmap, addr, mask, val); + + st_lsm6dsx_set_page(hw, false); +out: + mutex_unlock(&hw->page_lock); + + return err; +} + +static int st_lsm6dsx_shub_master_enable(struct st_lsm6dsx_sensor *sensor, + bool enable) +{ + const struct st_lsm6dsx_shub_settings *hub_settings; + struct st_lsm6dsx_hw *hw = sensor->hw; + unsigned int data; + int err; + + /* enable acc sensor as trigger */ + err = st_lsm6dsx_sensor_set_enable(sensor, enable); + if (err < 0) + return err; + + mutex_lock(&hw->page_lock); + + hub_settings = &hw->settings->shub_settings; + if (hub_settings->master_en.sec_page) { + err = st_lsm6dsx_set_page(hw, true); + if (err < 0) + goto out; + } + + data = ST_LSM6DSX_SHIFT_VAL(enable, hub_settings->master_en.mask); + err = regmap_update_bits(hw->regmap, hub_settings->master_en.addr, + hub_settings->master_en.mask, data); + + if (hub_settings->master_en.sec_page) + st_lsm6dsx_set_page(hw, false); +out: + mutex_unlock(&hw->page_lock); + + return err; +} + +/* + * st_lsm6dsx_shub_read - read data from slave device register + * + * Read data from slave device register. SLV0 is used for + * one-shot read operation + */ +static int +st_lsm6dsx_shub_read(struct st_lsm6dsx_sensor *sensor, u8 addr, + u8 *data, int len) +{ + const struct st_lsm6dsx_shub_settings *hub_settings; + u8 config[3], slv_addr, slv_config = 0; + struct st_lsm6dsx_hw *hw = sensor->hw; + const struct st_lsm6dsx_reg *aux_sens; + int err; + + hub_settings = &hw->settings->shub_settings; + slv_addr = ST_LSM6DSX_SLV_ADDR(0, hub_settings->slv0_addr); + aux_sens = &hw->settings->shub_settings.aux_sens; + /* do not overwrite aux_sens */ + if (slv_addr + 2 == aux_sens->addr) + slv_config = ST_LSM6DSX_SHIFT_VAL(3, aux_sens->mask); + + config[0] = (sensor->ext_info.addr << 1) | 1; + config[1] = addr; + config[2] = (len & ST_LS6DSX_READ_OP_MASK) | slv_config; + + err = st_lsm6dsx_shub_write_reg(hw, slv_addr, config, + sizeof(config)); + if (err < 0) + return err; + + err = st_lsm6dsx_shub_master_enable(sensor, true); + if (err < 0) + return err; + + st_lsm6dsx_shub_wait_complete(hw); + + err = st_lsm6dsx_shub_read_output(hw, data, + len & ST_LS6DSX_READ_OP_MASK); + if (err < 0) + return err; + + st_lsm6dsx_shub_master_enable(sensor, false); + + config[0] = hub_settings->pause; + config[1] = 0; + config[2] = slv_config; + return st_lsm6dsx_shub_write_reg(hw, slv_addr, config, + sizeof(config)); +} + +/* + * st_lsm6dsx_shub_write - write data to slave device register + * + * Write data from slave device register. SLV0 is used for + * one-shot write operation + */ +static int +st_lsm6dsx_shub_write(struct st_lsm6dsx_sensor *sensor, u8 addr, + u8 *data, int len) +{ + const struct st_lsm6dsx_shub_settings *hub_settings; + struct st_lsm6dsx_hw *hw = sensor->hw; + u8 config[2], slv_addr; + int err, i; + + hub_settings = &hw->settings->shub_settings; + if (hub_settings->wr_once.addr) { + unsigned int data; + + data = ST_LSM6DSX_SHIFT_VAL(1, hub_settings->wr_once.mask); + err = st_lsm6dsx_shub_write_reg_with_mask(hw, + hub_settings->wr_once.addr, + hub_settings->wr_once.mask, + data); + if (err < 0) + return err; + } + + slv_addr = ST_LSM6DSX_SLV_ADDR(0, hub_settings->slv0_addr); + config[0] = sensor->ext_info.addr << 1; + for (i = 0 ; i < len; i++) { + config[1] = addr + i; + + err = st_lsm6dsx_shub_write_reg(hw, slv_addr, config, + sizeof(config)); + if (err < 0) + return err; + + err = st_lsm6dsx_shub_write_reg(hw, hub_settings->dw_slv0_addr, + &data[i], 1); + if (err < 0) + return err; + + err = st_lsm6dsx_shub_master_enable(sensor, true); + if (err < 0) + return err; + + st_lsm6dsx_shub_wait_complete(hw); + + st_lsm6dsx_shub_master_enable(sensor, false); + } + + config[0] = hub_settings->pause; + config[1] = 0; + return st_lsm6dsx_shub_write_reg(hw, slv_addr, config, sizeof(config)); +} + +static int +st_lsm6dsx_shub_write_with_mask(struct st_lsm6dsx_sensor *sensor, + u8 addr, u8 mask, u8 val) +{ + int err; + u8 data; + + err = st_lsm6dsx_shub_read(sensor, addr, &data, sizeof(data)); + if (err < 0) + return err; + + data = ((data & ~mask) | (val << __ffs(mask) & mask)); + + return st_lsm6dsx_shub_write(sensor, addr, &data, sizeof(data)); +} + +static int +st_lsm6dsx_shub_get_odr_val(struct st_lsm6dsx_sensor *sensor, + u32 odr, u16 *val) +{ + const struct st_lsm6dsx_ext_dev_settings *settings; + int i; + + settings = sensor->ext_info.settings; + for (i = 0; i < settings->odr_table.odr_len; i++) { + if (settings->odr_table.odr_avl[i].milli_hz == odr) + break; + } + + if (i == settings->odr_table.odr_len) + return -EINVAL; + + *val = settings->odr_table.odr_avl[i].val; + return 0; +} + +static int +st_lsm6dsx_shub_set_odr(struct st_lsm6dsx_sensor *sensor, u32 odr) +{ + const struct st_lsm6dsx_ext_dev_settings *settings; + u16 val; + int err; + + err = st_lsm6dsx_shub_get_odr_val(sensor, odr, &val); + if (err < 0) + return err; + + settings = sensor->ext_info.settings; + return st_lsm6dsx_shub_write_with_mask(sensor, + settings->odr_table.reg.addr, + settings->odr_table.reg.mask, + val); +} + +/* use SLV{1,2,3} for FIFO read operations */ +static int +st_lsm6dsx_shub_config_channels(struct st_lsm6dsx_sensor *sensor, + bool enable) +{ + const struct st_lsm6dsx_shub_settings *hub_settings; + const struct st_lsm6dsx_ext_dev_settings *settings; + u8 config[9] = {}, enable_mask, slv_addr; + struct st_lsm6dsx_hw *hw = sensor->hw; + struct st_lsm6dsx_sensor *cur_sensor; + int i, j = 0; + + hub_settings = &hw->settings->shub_settings; + if (enable) + enable_mask = hw->enable_mask | BIT(sensor->id); + else + enable_mask = hw->enable_mask & ~BIT(sensor->id); + + for (i = ST_LSM6DSX_ID_EXT0; i <= ST_LSM6DSX_ID_EXT2; i++) { + if (!hw->iio_devs[i]) + continue; + + cur_sensor = iio_priv(hw->iio_devs[i]); + if (!(enable_mask & BIT(cur_sensor->id))) + continue; + + settings = cur_sensor->ext_info.settings; + config[j] = (sensor->ext_info.addr << 1) | 1; + config[j + 1] = settings->out.addr; + config[j + 2] = (settings->out.len & ST_LS6DSX_READ_OP_MASK) | + hub_settings->batch_en; + j += 3; + } + + slv_addr = ST_LSM6DSX_SLV_ADDR(1, hub_settings->slv0_addr); + return st_lsm6dsx_shub_write_reg(hw, slv_addr, config, + sizeof(config)); +} + +int st_lsm6dsx_shub_set_enable(struct st_lsm6dsx_sensor *sensor, bool enable) +{ + const struct st_lsm6dsx_ext_dev_settings *settings; + int err; + + err = st_lsm6dsx_shub_config_channels(sensor, enable); + if (err < 0) + return err; + + settings = sensor->ext_info.settings; + if (enable) { + err = st_lsm6dsx_shub_set_odr(sensor, + sensor->ext_info.slv_odr); + if (err < 0) + return err; + } else { + err = st_lsm6dsx_shub_write_with_mask(sensor, + settings->odr_table.reg.addr, + settings->odr_table.reg.mask, 0); + if (err < 0) + return err; + } + + if (settings->pwr_table.reg.addr) { + u8 val; + + val = enable ? settings->pwr_table.on_val + : settings->pwr_table.off_val; + err = st_lsm6dsx_shub_write_with_mask(sensor, + settings->pwr_table.reg.addr, + settings->pwr_table.reg.mask, val); + if (err < 0) + return err; + } + + return st_lsm6dsx_shub_master_enable(sensor, enable); +} + +static int +st_lsm6dsx_shub_read_oneshot(struct st_lsm6dsx_sensor *sensor, + struct iio_chan_spec const *ch, + int *val) +{ + int err, delay, len; + u8 data[4]; + + err = st_lsm6dsx_shub_set_enable(sensor, true); + if (err < 0) + return err; + + delay = 1000000000 / sensor->ext_info.slv_odr; + usleep_range(delay, 2 * delay); + + len = min_t(int, sizeof(data), ch->scan_type.realbits >> 3); + err = st_lsm6dsx_shub_read(sensor, ch->address, data, len); + if (err < 0) + return err; + + err = st_lsm6dsx_shub_set_enable(sensor, false); + if (err < 0) + return err; + + switch (len) { + case 2: + *val = (s16)le16_to_cpu(*((__le16 *)data)); + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; +} + +static int +st_lsm6dsx_shub_read_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *ch, + int *val, int *val2, long mask) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(iio_dev)) + return -EBUSY; + + ret = st_lsm6dsx_shub_read_oneshot(sensor, ch, val); + iio_device_release_direct(iio_dev); + break; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = sensor->ext_info.slv_odr / 1000; + *val2 = (sensor->ext_info.slv_odr % 1000) * 1000; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = sensor->gain; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int +st_lsm6dsx_shub_set_full_scale(struct st_lsm6dsx_sensor *sensor, + u32 gain) +{ + const struct st_lsm6dsx_fs_table_entry *fs_table; + int i, err; + + fs_table = &sensor->ext_info.settings->fs_table; + if (!fs_table->reg.addr) + return -ENOTSUPP; + + for (i = 0; i < fs_table->fs_len; i++) { + if (fs_table->fs_avl[i].gain == gain) + break; + } + + if (i == fs_table->fs_len) + return -EINVAL; + + err = st_lsm6dsx_shub_write_with_mask(sensor, fs_table->reg.addr, + fs_table->reg.mask, + fs_table->fs_avl[i].val); + if (err < 0) + return err; + + sensor->gain = gain; + + return 0; +} + +static int +__st_lsm6dsx_shub_write_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + int err; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: { + struct st_lsm6dsx_hw *hw = sensor->hw; + struct st_lsm6dsx_sensor *ref_sensor; + u8 odr_val; + u16 data; + int odr; + + val = val * 1000 + val2 / 1000; + err = st_lsm6dsx_shub_get_odr_val(sensor, val, &data); + if (err) + return err; + + ref_sensor = iio_priv(hw->iio_devs[ST_LSM6DSX_ID_ACC]); + odr = st_lsm6dsx_check_odr(ref_sensor, val, &odr_val); + if (odr < 0) + return odr; + + sensor->ext_info.slv_odr = val; + sensor->odr = odr; + sensor->hwfifo_odr_mHz = odr; + return 0; + } + case IIO_CHAN_INFO_SCALE: + return st_lsm6dsx_shub_set_full_scale(sensor, val2); + default: + return -EINVAL; + } +} + +static int +st_lsm6dsx_shub_write_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret; + + if (!iio_device_claim_direct(iio_dev)) + return -EBUSY; + + ret = __st_lsm6dsx_shub_write_raw(iio_dev, chan, val, val2, mask); + + iio_device_release_direct(iio_dev); + + return ret; +} + +static ssize_t +st_lsm6dsx_shub_sampling_freq_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(dev_get_drvdata(dev)); + const struct st_lsm6dsx_ext_dev_settings *settings; + int i, len = 0; + + settings = sensor->ext_info.settings; + for (i = 0; i < settings->odr_table.odr_len; i++) { + u32 val = settings->odr_table.odr_avl[i].milli_hz; + + len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%03d ", + val / 1000, val % 1000); + } + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t st_lsm6dsx_shub_scale_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(dev_get_drvdata(dev)); + const struct st_lsm6dsx_ext_dev_settings *settings; + int i, len = 0; + + settings = sensor->ext_info.settings; + for (i = 0; i < settings->fs_table.fs_len; i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06u ", + settings->fs_table.fs_avl[i].gain); + buf[len - 1] = '\n'; + + return len; +} + +static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(st_lsm6dsx_shub_sampling_freq_avail); +static IIO_DEVICE_ATTR(in_scale_available, 0444, + st_lsm6dsx_shub_scale_avail, NULL, 0); +static struct attribute *st_lsm6dsx_shub_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group st_lsm6dsx_shub_attribute_group = { + .attrs = st_lsm6dsx_shub_attributes, +}; + +static const struct iio_info st_lsm6dsx_shub_info = { + .attrs = &st_lsm6dsx_shub_attribute_group, + .read_raw = st_lsm6dsx_shub_read_raw, + .write_raw = st_lsm6dsx_shub_write_raw, + .hwfifo_set_watermark = st_lsm6dsx_set_watermark, +}; + +static struct iio_dev * +st_lsm6dsx_shub_alloc_iiodev(struct st_lsm6dsx_hw *hw, + enum st_lsm6dsx_sensor_id id, + const struct st_lsm6dsx_ext_dev_settings *info, + u8 i2c_addr, const char *name) +{ + enum st_lsm6dsx_sensor_id ref_id = ST_LSM6DSX_ID_ACC; + struct iio_chan_spec *ext_channels; + struct st_lsm6dsx_sensor *sensor; + struct iio_dev *iio_dev; + + iio_dev = devm_iio_device_alloc(hw->dev, sizeof(*sensor)); + if (!iio_dev) + return NULL; + + iio_dev->modes = INDIO_DIRECT_MODE; + iio_dev->info = &st_lsm6dsx_shub_info; + + sensor = iio_priv(iio_dev); + sensor->id = id; + sensor->hw = hw; + sensor->odr = hw->settings->odr_table[ref_id].odr_avl[0].milli_hz; + sensor->hwfifo_odr_mHz = sensor->odr; + sensor->ext_info.slv_odr = info->odr_table.odr_avl[0].milli_hz; + sensor->gain = info->fs_table.fs_avl[0].gain; + sensor->ext_info.settings = info; + sensor->ext_info.addr = i2c_addr; + sensor->watermark = 1; + + switch (info->id) { + case ST_LSM6DSX_ID_MAGN: { + const struct iio_chan_spec magn_channels[] = { + ST_LSM6DSX_CHANNEL(IIO_MAGN, info->out.addr, + IIO_MOD_X, 0), + ST_LSM6DSX_CHANNEL(IIO_MAGN, info->out.addr + 2, + IIO_MOD_Y, 1), + ST_LSM6DSX_CHANNEL(IIO_MAGN, info->out.addr + 4, + IIO_MOD_Z, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), + }; + + ext_channels = devm_kzalloc(hw->dev, sizeof(magn_channels), + GFP_KERNEL); + if (!ext_channels) + return NULL; + + memcpy(ext_channels, magn_channels, sizeof(magn_channels)); + iio_dev->available_scan_masks = st_lsm6dsx_available_scan_masks; + iio_dev->channels = ext_channels; + iio_dev->num_channels = ARRAY_SIZE(magn_channels); + + scnprintf(sensor->name, sizeof(sensor->name), "%s_magn", + name); + break; + } + default: + return NULL; + } + iio_dev->name = sensor->name; + + return iio_dev; +} + +static int st_lsm6dsx_shub_init_device(struct st_lsm6dsx_sensor *sensor) +{ + const struct st_lsm6dsx_ext_dev_settings *settings; + int err; + + settings = sensor->ext_info.settings; + if (settings->bdu.addr) { + err = st_lsm6dsx_shub_write_with_mask(sensor, + settings->bdu.addr, + settings->bdu.mask, 1); + if (err < 0) + return err; + } + + if (settings->temp_comp.addr) { + err = st_lsm6dsx_shub_write_with_mask(sensor, + settings->temp_comp.addr, + settings->temp_comp.mask, 1); + if (err < 0) + return err; + } + + if (settings->off_canc.addr) { + err = st_lsm6dsx_shub_write_with_mask(sensor, + settings->off_canc.addr, + settings->off_canc.mask, 1); + if (err < 0) + return err; + } + + return 0; +} + +static int +st_lsm6dsx_shub_check_wai(struct st_lsm6dsx_hw *hw, u8 *i2c_addr, + const struct st_lsm6dsx_ext_dev_settings *settings) +{ + const struct st_lsm6dsx_shub_settings *hub_settings; + u8 config[3], data, slv_addr, slv_config = 0; + const struct st_lsm6dsx_reg *aux_sens; + struct st_lsm6dsx_sensor *sensor; + bool found = false; + int i, err; + + sensor = iio_priv(hw->iio_devs[ST_LSM6DSX_ID_ACC]); + hub_settings = &hw->settings->shub_settings; + aux_sens = &hw->settings->shub_settings.aux_sens; + slv_addr = ST_LSM6DSX_SLV_ADDR(0, hub_settings->slv0_addr); + /* do not overwrite aux_sens */ + if (slv_addr + 2 == aux_sens->addr) + slv_config = ST_LSM6DSX_SHIFT_VAL(3, aux_sens->mask); + + for (i = 0; i < ARRAY_SIZE(settings->i2c_addr); i++) { + if (!settings->i2c_addr[i]) + continue; + + /* read wai slave register */ + config[0] = (settings->i2c_addr[i] << 1) | 0x1; + config[1] = settings->wai.addr; + config[2] = 0x1 | slv_config; + + err = st_lsm6dsx_shub_write_reg(hw, slv_addr, config, + sizeof(config)); + if (err < 0) + return err; + + err = st_lsm6dsx_shub_master_enable(sensor, true); + if (err < 0) + return err; + + st_lsm6dsx_shub_wait_complete(hw); + + err = st_lsm6dsx_shub_read_output(hw, &data, sizeof(data)); + + st_lsm6dsx_shub_master_enable(sensor, false); + + if (err < 0) + return err; + + if (data != settings->wai.val) + continue; + + *i2c_addr = settings->i2c_addr[i]; + found = true; + break; + } + + /* reset SLV0 channel */ + config[0] = hub_settings->pause; + config[1] = 0; + config[2] = slv_config; + err = st_lsm6dsx_shub_write_reg(hw, slv_addr, config, + sizeof(config)); + if (err < 0) + return err; + + return found ? 0 : -ENODEV; +} + +int st_lsm6dsx_shub_probe(struct st_lsm6dsx_hw *hw, const char *name) +{ + enum st_lsm6dsx_sensor_id id = ST_LSM6DSX_ID_EXT0; + struct st_lsm6dsx_sensor *sensor; + int err, i, num_ext_dev = 0; + u8 i2c_addr = 0; + + for (i = 0; i < ARRAY_SIZE(st_lsm6dsx_ext_dev_table); i++) { + err = st_lsm6dsx_shub_check_wai(hw, &i2c_addr, + &st_lsm6dsx_ext_dev_table[i]); + if (err == -ENODEV) + continue; + else if (err < 0) + return err; + + hw->iio_devs[id] = st_lsm6dsx_shub_alloc_iiodev(hw, id, + &st_lsm6dsx_ext_dev_table[i], + i2c_addr, name); + if (!hw->iio_devs[id]) + return -ENOMEM; + + sensor = iio_priv(hw->iio_devs[id]); + err = st_lsm6dsx_shub_init_device(sensor); + if (err < 0) + return err; + + if (++num_ext_dev >= hw->settings->shub_settings.num_ext_dev) + break; + id++; + } + + return 0; +} diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_spi.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_spi.c new file mode 100644 index 000000000..ed47d5a6f --- /dev/null +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_spi.c @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics st_lsm6dsx spi driver + * + * Copyright 2016 STMicroelectronics Inc. + * + * Lorenzo Bianconi + * Denis Ciocca + */ + +#include +#include +#include +#include +#include + +#include "st_lsm6dsx.h" + +static const struct regmap_config st_lsm6dsx_spi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int st_lsm6dsx_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + int hw_id = id->driver_data; + struct regmap *regmap; + + regmap = devm_regmap_init_spi(spi, &st_lsm6dsx_spi_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "Failed to register spi regmap %ld\n", PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + return st_lsm6dsx_probe(&spi->dev, spi->irq, hw_id, regmap); +} + +static const struct of_device_id st_lsm6dsx_spi_of_match[] = { + { + .compatible = "st,lsm6ds3", + .data = (void *)ST_LSM6DS3_ID, + }, + { + .compatible = "st,lsm6ds3h", + .data = (void *)ST_LSM6DS3H_ID, + }, + { + .compatible = "st,lsm6dsl", + .data = (void *)ST_LSM6DSL_ID, + }, + { + .compatible = "st,lsm6dsm", + .data = (void *)ST_LSM6DSM_ID, + }, + { + .compatible = "st,ism330dlc", + .data = (void *)ST_ISM330DLC_ID, + }, + { + .compatible = "st,lsm6dso", + .data = (void *)ST_LSM6DSO_ID, + }, + { + .compatible = "st,asm330lhh", + .data = (void *)ST_ASM330LHH_ID, + }, + { + .compatible = "st,lsm6dsox", + .data = (void *)ST_LSM6DSOX_ID, + }, + { + .compatible = "st,lsm6dsr", + .data = (void *)ST_LSM6DSR_ID, + }, + { + .compatible = "st,lsm6ds3tr-c", + .data = (void *)ST_LSM6DS3TRC_ID, + }, + { + .compatible = "st,ism330dhcx", + .data = (void *)ST_ISM330DHCX_ID, + }, + { + .compatible = "st,lsm9ds1-imu", + .data = (void *)ST_LSM9DS1_ID, + }, + { + .compatible = "st,lsm6ds0", + .data = (void *)ST_LSM6DS0_ID, + }, + { + .compatible = "st,lsm6dsrx", + .data = (void *)ST_LSM6DSRX_ID, + }, + { + .compatible = "st,lsm6dst", + .data = (void *)ST_LSM6DST_ID, + }, + { + .compatible = "st,lsm6dsop", + .data = (void *)ST_LSM6DSOP_ID, + }, + { + .compatible = "st,asm330lhhx", + .data = (void *)ST_ASM330LHHX_ID, + }, + { + .compatible = "st,lsm6dstx", + .data = (void *)ST_LSM6DSTX_ID, + }, + { + .compatible = "st,lsm6dsv", + .data = (void *)ST_LSM6DSV_ID, + }, + { + .compatible = "st,lsm6dsv16x", + .data = (void *)ST_LSM6DSV16X_ID, + }, + { + .compatible = "st,lsm6dso16is", + .data = (void *)ST_LSM6DSO16IS_ID, + }, + { + .compatible = "st,ism330is", + .data = (void *)ST_ISM330IS_ID, + }, + { + .compatible = "st,asm330lhb", + .data = (void *)ST_ASM330LHB_ID, + }, + { + .compatible = "st,asm330lhhxg1", + .data = (void *)ST_ASM330LHHXG1_ID, + }, + { } +}; +MODULE_DEVICE_TABLE(of, st_lsm6dsx_spi_of_match); + +static const struct spi_device_id st_lsm6dsx_spi_id_table[] = { + { .name = ST_LSM6DS3_DEV_NAME, .driver_data = ST_LSM6DS3_ID }, + { .name = ST_LSM6DS3H_DEV_NAME, .driver_data = ST_LSM6DS3H_ID }, + { .name = ST_LSM6DSL_DEV_NAME, .driver_data = ST_LSM6DSL_ID }, + { .name = ST_LSM6DSM_DEV_NAME, .driver_data = ST_LSM6DSM_ID }, + { .name = ST_ISM330DLC_DEV_NAME, .driver_data = ST_ISM330DLC_ID }, + { .name = ST_LSM6DSO_DEV_NAME, .driver_data = ST_LSM6DSO_ID }, + { .name = ST_ASM330LHH_DEV_NAME, .driver_data = ST_ASM330LHH_ID }, + { .name = ST_LSM6DSOX_DEV_NAME, .driver_data = ST_LSM6DSOX_ID }, + { .name = ST_LSM6DSR_DEV_NAME, .driver_data = ST_LSM6DSR_ID }, + { .name = ST_LSM6DS3TRC_DEV_NAME, .driver_data = ST_LSM6DS3TRC_ID }, + { .name = ST_ISM330DHCX_DEV_NAME, .driver_data = ST_ISM330DHCX_ID }, + { .name = ST_LSM9DS1_DEV_NAME, .driver_data = ST_LSM9DS1_ID }, + { .name = ST_LSM6DS0_DEV_NAME, .driver_data = ST_LSM6DS0_ID }, + { .name = ST_LSM6DSRX_DEV_NAME, .driver_data = ST_LSM6DSRX_ID }, + { .name = ST_LSM6DST_DEV_NAME, .driver_data = ST_LSM6DST_ID }, + { .name = ST_LSM6DSOP_DEV_NAME, .driver_data = ST_LSM6DSOP_ID }, + { .name = ST_ASM330LHHX_DEV_NAME, .driver_data = ST_ASM330LHHX_ID }, + { .name = ST_LSM6DSTX_DEV_NAME, .driver_data = ST_LSM6DSTX_ID }, + { .name = ST_LSM6DSV_DEV_NAME, .driver_data = ST_LSM6DSV_ID }, + { .name = ST_LSM6DSV16X_DEV_NAME, .driver_data = ST_LSM6DSV16X_ID }, + { .name = ST_LSM6DSO16IS_DEV_NAME, .driver_data = ST_LSM6DSO16IS_ID }, + { .name = ST_ISM330IS_DEV_NAME, .driver_data = ST_ISM330IS_ID }, + { .name = ST_ASM330LHB_DEV_NAME, .driver_data = ST_ASM330LHB_ID }, + { .name = ST_ASM330LHHXG1_DEV_NAME, .driver_data = ST_ASM330LHHXG1_ID }, + { } +}; +MODULE_DEVICE_TABLE(spi, st_lsm6dsx_spi_id_table); + +static struct spi_driver st_lsm6dsx_driver = { + .driver = { + .name = "st_lsm6dsx_spi", + .pm = pm_sleep_ptr(&st_lsm6dsx_pm_ops), + .of_match_table = st_lsm6dsx_spi_of_match, + }, + .probe = st_lsm6dsx_spi_probe, + .id_table = st_lsm6dsx_spi_id_table, +}; +module_spi_driver(st_lsm6dsx_driver); + +MODULE_AUTHOR("Lorenzo Bianconi "); +MODULE_AUTHOR("Denis Ciocca "); +MODULE_DESCRIPTION("STMicroelectronics st_lsm6dsx spi driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_LSM6DSX"); diff --git a/drivers/iio/imu/st_lsm9ds0/Kconfig b/drivers/iio/imu/st_lsm9ds0/Kconfig new file mode 100644 index 000000000..7aef714b6 --- /dev/null +++ b/drivers/iio/imu/st_lsm9ds0/Kconfig @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config IIO_ST_LSM9DS0 + tristate "STMicroelectronics LSM9DS0 IMU driver" + depends on (I2C || SPI_MASTER) && SYSFS + depends on !SENSORS_LIS3_I2C + depends on !SENSORS_LIS3_SPI + select IIO_ST_ACCEL_3AXIS + select IIO_ST_MAGN_3AXIS + + help + Say yes here to build support for STMicroelectronics LSM9DS0 IMU + sensor. Supported devices: accelerometer/magnetometer of lsm9ds0 + and lsm303d. + + To compile this driver as a module, choose M here: the module + will be called st_lsm9ds0. + + Also need to enable at least one of I2C and SPI interface drivers + +config IIO_ST_LSM9DS0_I2C + tristate "STMicroelectronics LSM9DS0 IMU I2C interface" + depends on I2C && IIO_ST_LSM9DS0 + default I2C && IIO_ST_LSM9DS0 + select IIO_ST_ACCEL_I2C_3AXIS + select IIO_ST_MAGN_I2C_3AXIS + select REGMAP_I2C + help + Build support for STMicroelectronics LSM9DS0 IMU I2C interface. + + To compile this driver as a module, choose M here. The module + will be called st_lsm9ds0_i2c. + +config IIO_ST_LSM9DS0_SPI + tristate "STMicroelectronics LSM9DS0 IMU SPI interface" + depends on SPI_MASTER && IIO_ST_LSM9DS0 + default SPI_MASTER && IIO_ST_LSM9DS0 + select IIO_ST_ACCEL_SPI_3AXIS + select IIO_ST_MAGN_SPI_3AXIS + select REGMAP_SPI + help + Build support for STMicroelectronics LSM9DS0 IMU I2C interface. + + To compile this driver as a module, choose M here. The module + will be called st_lsm9ds0_spi. diff --git a/drivers/iio/imu/st_lsm9ds0/Makefile b/drivers/iio/imu/st_lsm9ds0/Makefile new file mode 100644 index 000000000..488af523f --- /dev/null +++ b/drivers/iio/imu/st_lsm9ds0/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_IIO_ST_LSM9DS0) += st_lsm9ds0.o +st_lsm9ds0-y := st_lsm9ds0_core.o +obj-$(CONFIG_IIO_ST_LSM9DS0_I2C) += st_lsm9ds0_i2c.o +obj-$(CONFIG_IIO_ST_LSM9DS0_SPI) += st_lsm9ds0_spi.o diff --git a/drivers/iio/imu/st_lsm9ds0/st_lsm9ds0.h b/drivers/iio/imu/st_lsm9ds0/st_lsm9ds0.h new file mode 100644 index 000000000..e67d31b48 --- /dev/null +++ b/drivers/iio/imu/st_lsm9ds0/st_lsm9ds0.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +// STMicroelectronics LSM9DS0 IMU driver + +#ifndef ST_LSM9DS0_H +#define ST_LSM9DS0_H + +struct device; +struct regmap; +struct regulator; + +struct iio_dev; + +struct st_lsm9ds0 { + struct device *dev; + const char *name; + int irq; + struct iio_dev *accel; + struct iio_dev *magn; + struct regulator *vdd; + struct regulator *vdd_io; +}; + +int st_lsm9ds0_probe(struct st_lsm9ds0 *lsm9ds0, struct regmap *regmap); + +#endif /* ST_LSM9DS0_H */ diff --git a/drivers/iio/imu/st_lsm9ds0/st_lsm9ds0_core.c b/drivers/iio/imu/st_lsm9ds0/st_lsm9ds0_core.c new file mode 100644 index 000000000..8f4a67edb --- /dev/null +++ b/drivers/iio/imu/st_lsm9ds0/st_lsm9ds0_core.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics LSM9DS0 IMU driver + * + * Copyright (C) 2021, Intel Corporation + * + * Author: Andy Shevchenko + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include "st_lsm9ds0.h" + +static int st_lsm9ds0_probe_accel(struct st_lsm9ds0 *lsm9ds0, struct regmap *regmap) +{ + const struct st_sensor_settings *settings; + struct device *dev = lsm9ds0->dev; + struct st_sensor_data *data; + + settings = st_accel_get_settings(lsm9ds0->name); + if (!settings) + return dev_err_probe(dev, -ENODEV, "device name %s not recognized.\n", + lsm9ds0->name); + + lsm9ds0->accel = devm_iio_device_alloc(dev, sizeof(*data)); + if (!lsm9ds0->accel) + return -ENOMEM; + + lsm9ds0->accel->name = lsm9ds0->name; + + data = iio_priv(lsm9ds0->accel); + data->sensor_settings = (struct st_sensor_settings *)settings; + data->irq = lsm9ds0->irq; + data->regmap = regmap; + + return st_accel_common_probe(lsm9ds0->accel); +} + +static int st_lsm9ds0_probe_magn(struct st_lsm9ds0 *lsm9ds0, struct regmap *regmap) +{ + const struct st_sensor_settings *settings; + struct device *dev = lsm9ds0->dev; + struct st_sensor_data *data; + + settings = st_magn_get_settings(lsm9ds0->name); + if (!settings) + return dev_err_probe(dev, -ENODEV, "device name %s not recognized.\n", + lsm9ds0->name); + + lsm9ds0->magn = devm_iio_device_alloc(dev, sizeof(*data)); + if (!lsm9ds0->magn) + return -ENOMEM; + + lsm9ds0->magn->name = lsm9ds0->name; + + data = iio_priv(lsm9ds0->magn); + data->sensor_settings = (struct st_sensor_settings *)settings; + data->irq = lsm9ds0->irq; + data->regmap = regmap; + + return st_magn_common_probe(lsm9ds0->magn); +} + +int st_lsm9ds0_probe(struct st_lsm9ds0 *lsm9ds0, struct regmap *regmap) +{ + struct device *dev = lsm9ds0->dev; + static const char * const regulator_names[] = { "vdd", "vddio" }; + int ret; + + /* Regulators not mandatory, but if requested we should enable them. */ + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulator_names), + regulator_names); + if (ret) + return dev_err_probe(dev, ret, "unable to enable Vdd supply\n"); + + /* Setup accelerometer device */ + ret = st_lsm9ds0_probe_accel(lsm9ds0, regmap); + if (ret) + return ret; + + /* Setup magnetometer device */ + return st_lsm9ds0_probe_magn(lsm9ds0, regmap); +} +EXPORT_SYMBOL_NS_GPL(st_lsm9ds0_probe, "IIO_ST_SENSORS"); + +MODULE_AUTHOR("Andy Shevchenko "); +MODULE_DESCRIPTION("STMicroelectronics LSM9DS0 IMU core driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ST_SENSORS"); diff --git a/drivers/iio/imu/st_lsm9ds0/st_lsm9ds0_i2c.c b/drivers/iio/imu/st_lsm9ds0/st_lsm9ds0_i2c.c new file mode 100644 index 000000000..6581d14e2 --- /dev/null +++ b/drivers/iio/imu/st_lsm9ds0/st_lsm9ds0_i2c.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics LSM9DS0 IMU driver + * + * Copyright (C) 2021, Intel Corporation + * + * Author: Andy Shevchenko + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "st_lsm9ds0.h" + +static const struct of_device_id st_lsm9ds0_of_match[] = { + { + .compatible = "st,lsm303d-imu", + .data = LSM303D_IMU_DEV_NAME, + }, + { + .compatible = "st,lsm9ds0-imu", + .data = LSM9DS0_IMU_DEV_NAME, + }, + { } +}; +MODULE_DEVICE_TABLE(of, st_lsm9ds0_of_match); + +static const struct i2c_device_id st_lsm9ds0_id_table[] = { + { .name = LSM303D_IMU_DEV_NAME }, + { .name = LSM9DS0_IMU_DEV_NAME }, + { } +}; +MODULE_DEVICE_TABLE(i2c, st_lsm9ds0_id_table); + +static const struct acpi_device_id st_lsm9ds0_acpi_match[] = { + {"ACCL0001", (kernel_ulong_t)LSM303D_IMU_DEV_NAME}, + { } +}; +MODULE_DEVICE_TABLE(acpi, st_lsm9ds0_acpi_match); + +static const struct regmap_config st_lsm9ds0_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .read_flag_mask = 0x80, +}; + +static int st_lsm9ds0_i2c_probe(struct i2c_client *client) +{ + const struct regmap_config *config = &st_lsm9ds0_regmap_config; + struct device *dev = &client->dev; + struct st_lsm9ds0 *lsm9ds0; + struct regmap *regmap; + + st_sensors_dev_name_probe(dev, client->name, sizeof(client->name)); + + lsm9ds0 = devm_kzalloc(dev, sizeof(*lsm9ds0), GFP_KERNEL); + if (!lsm9ds0) + return -ENOMEM; + + lsm9ds0->dev = dev; + lsm9ds0->name = client->name; + lsm9ds0->irq = client->irq; + + regmap = devm_regmap_init_i2c(client, config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + i2c_set_clientdata(client, lsm9ds0); + + return st_lsm9ds0_probe(lsm9ds0, regmap); +} + +static struct i2c_driver st_lsm9ds0_driver = { + .driver = { + .name = "st-lsm9ds0-i2c", + .of_match_table = st_lsm9ds0_of_match, + .acpi_match_table = st_lsm9ds0_acpi_match, + }, + .probe = st_lsm9ds0_i2c_probe, + .id_table = st_lsm9ds0_id_table, +}; +module_i2c_driver(st_lsm9ds0_driver); + +MODULE_AUTHOR("Andy Shevchenko "); +MODULE_DESCRIPTION("STMicroelectronics LSM9DS0 IMU I2C driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ST_SENSORS"); diff --git a/drivers/iio/imu/st_lsm9ds0/st_lsm9ds0_spi.c b/drivers/iio/imu/st_lsm9ds0/st_lsm9ds0_spi.c new file mode 100644 index 000000000..e67f09e1f --- /dev/null +++ b/drivers/iio/imu/st_lsm9ds0/st_lsm9ds0_spi.c @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics LSM9DS0 IMU driver + * + * Copyright (C) 2021, Intel Corporation + * + * Author: Andy Shevchenko + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "st_lsm9ds0.h" + +static const struct of_device_id st_lsm9ds0_of_match[] = { + { + .compatible = "st,lsm303d-imu", + .data = LSM303D_IMU_DEV_NAME, + }, + { + .compatible = "st,lsm9ds0-imu", + .data = LSM9DS0_IMU_DEV_NAME, + }, + { } +}; +MODULE_DEVICE_TABLE(of, st_lsm9ds0_of_match); + +static const struct spi_device_id st_lsm9ds0_id_table[] = { + { .name = LSM303D_IMU_DEV_NAME }, + { .name = LSM9DS0_IMU_DEV_NAME }, + { } +}; +MODULE_DEVICE_TABLE(spi, st_lsm9ds0_id_table); + +static const struct regmap_config st_lsm9ds0_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .read_flag_mask = 0xc0, +}; + +static int st_lsm9ds0_spi_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct st_lsm9ds0 *lsm9ds0; + struct regmap *regmap; + + st_sensors_dev_name_probe(dev, spi->modalias, sizeof(spi->modalias)); + + lsm9ds0 = devm_kzalloc(dev, sizeof(*lsm9ds0), GFP_KERNEL); + if (!lsm9ds0) + return -ENOMEM; + + lsm9ds0->dev = dev; + lsm9ds0->name = spi->modalias; + lsm9ds0->irq = spi->irq; + + regmap = devm_regmap_init_spi(spi, &st_lsm9ds0_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + spi_set_drvdata(spi, lsm9ds0); + + return st_lsm9ds0_probe(lsm9ds0, regmap); +} + +static struct spi_driver st_lsm9ds0_driver = { + .driver = { + .name = "st-lsm9ds0-spi", + .of_match_table = st_lsm9ds0_of_match, + }, + .probe = st_lsm9ds0_spi_probe, + .id_table = st_lsm9ds0_id_table, +}; +module_spi_driver(st_lsm9ds0_driver); + +MODULE_AUTHOR("Andy Shevchenko "); +MODULE_DESCRIPTION("STMicroelectronics LSM9DS0 IMU SPI driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ST_SENSORS"); diff --git a/drivers/iio/industrialio-acpi.c b/drivers/iio/industrialio-acpi.c new file mode 100644 index 000000000..d67a43843 --- /dev/null +++ b/drivers/iio/industrialio-acpi.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* IIO ACPI helper functions */ + +#include +#include +#include +#include +#include + +/** + * iio_read_acpi_mount_matrix() - Read accelerometer mount matrix info from ACPI + * @dev: Device structure + * @orientation: iio_mount_matrix struct to fill + * @acpi_method: ACPI method name to read the matrix from, usually "ROTM" + * + * Try to read the mount-matrix by calling the specified method on the device's + * ACPI firmware-node. If the device has no ACPI firmware-node; or the method + * does not exist then this will fail silently. This expects the method to + * return data in the ACPI "ROTM" format defined by Microsoft: + * https://learn.microsoft.com/en-us/windows-hardware/drivers/sensors/sensors-acpi-entries + * This is a Microsoft extension and not part of the official ACPI spec. + * The method name is configurable because some dual-accel setups define 2 mount + * matrices in a single ACPI device using separate "ROMK" and "ROMS" methods. + * + * Returns: true if the matrix was successfully, false otherwise. + */ +bool iio_read_acpi_mount_matrix(struct device *dev, + struct iio_mount_matrix *orientation, + char *acpi_method) +{ + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + char *str; + union acpi_object *obj, *elements; + acpi_handle handle; + acpi_status status; + int i, j, val[3]; + bool ret = false; + + handle = ACPI_HANDLE(dev); + if (!handle) + return false; + + if (!acpi_has_method(handle, acpi_method)) + return false; + + status = acpi_evaluate_object(handle, acpi_method, NULL, &buffer); + if (ACPI_FAILURE(status)) { + dev_err(dev, "Failed to get ACPI mount matrix: %d\n", status); + return false; + } + + obj = buffer.pointer; + if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 3) { + dev_err(dev, "Unknown ACPI mount matrix package format\n"); + goto out_free_buffer; + } + + elements = obj->package.elements; + for (i = 0; i < 3; i++) { + if (elements[i].type != ACPI_TYPE_STRING) { + dev_err(dev, "Unknown ACPI mount matrix element format\n"); + goto out_free_buffer; + } + + str = elements[i].string.pointer; + if (sscanf(str, "%d %d %d", &val[0], &val[1], &val[2]) != 3) { + dev_err(dev, "Incorrect ACPI mount matrix string format\n"); + goto out_free_buffer; + } + + for (j = 0; j < 3; j++) { + switch (val[j]) { + case -1: str = "-1"; break; + case 0: str = "0"; break; + case 1: str = "1"; break; + default: + dev_err(dev, "Invalid value in ACPI mount matrix: %d\n", val[j]); + goto out_free_buffer; + } + orientation->rotation[i * 3 + j] = str; + } + } + + ret = true; + +out_free_buffer: + kfree(buffer.pointer); + return ret; +} +EXPORT_SYMBOL_GPL(iio_read_acpi_mount_matrix); + +/** + * iio_get_acpi_device_name_and_data() - Return ACPI device instance name and driver data + * @dev: Device structure + * @data: Optional pointer to return driver data + * + * When device was enumerated by ACPI ID matching, the user might + * want to set description for the physical chip. In such cases + * the ACPI device instance name might be used. This call may be + * performed to retrieve this information. + * + * NOTE: This helper function exists only for backward compatibility, + * do not use in a new code! + * + * Returns: ACPI device instance name or %NULL. + */ +const char *iio_get_acpi_device_name_and_data(struct device *dev, const void **data) +{ + const struct acpi_device_id *id; + acpi_handle handle; + + handle = ACPI_HANDLE(dev); + if (!handle) + return NULL; + + id = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!id) + return NULL; + + if (data) + *data = (const void *)id->driver_data; + + return dev_name(dev); +} +EXPORT_SYMBOL_GPL(iio_get_acpi_device_name_and_data); diff --git a/drivers/iio/industrialio-backend.c b/drivers/iio/industrialio-backend.c new file mode 100644 index 000000000..f7a4be8ec --- /dev/null +++ b/drivers/iio/industrialio-backend.c @@ -0,0 +1,1166 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Framework to handle complex IIO aggregate devices. + * + * The typical architecture is to have one device as the frontend device which + * can be "linked" against one or multiple backend devices. All the IIO and + * userspace interface is expected to be registers/managed by the frontend + * device which will callback into the backends when needed (to get/set some + * configuration that it does not directly control). + * + * ------------------------------------------------------- + * ------------------ | ------------ ------------ ------- FPGA| + * | ADC |------------------------| | ADC CORE |---------| DMA CORE |------| RAM | | + * | (Frontend/IIO) | Serial Data (eg: LVDS) | |(backend) |---------| |------| | | + * | |------------------------| ------------ ------------ ------- | + * ------------------ ------------------------------------------------------- + * + * The framework interface is pretty simple: + * - Backends should register themselves with devm_iio_backend_register() + * - Frontend devices should get backends with devm_iio_backend_get() + * + * Also to note that the primary target for this framework are converters like + * ADC/DACs so iio_backend_ops will have some operations typical of converter + * devices. On top of that, this is "generic" for all IIO which means any kind + * of device can make use of the framework. That said, If the iio_backend_ops + * struct begins to grow out of control, we can always refactor things so that + * the industrialio-backend.c is only left with the really generic stuff. Then, + * we can build on top of it depending on the needs. + * + * Copyright (C) 2023-2024 Analog Devices Inc. + */ +#define dev_fmt(fmt) "iio-backend: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct iio_backend { + struct list_head entry; + const struct iio_backend_ops *ops; + struct device *frontend_dev; + struct device *dev; + struct module *owner; + void *priv; + const char *name; + unsigned int cached_reg_addr; + u32 caps; + /* + * This index is relative to the frontend. Meaning that for + * frontends with multiple backends, this will be the index of this + * backend. Used for the debugfs directory name. + */ + u8 idx; +}; + +/* + * Helper struct for requesting buffers. This ensures that we have all data + * that we need to free the buffer in a device managed action. + */ +struct iio_backend_buffer_pair { + struct iio_backend *back; + struct iio_buffer *buffer; +}; + +static LIST_HEAD(iio_back_list); +static DEFINE_MUTEX(iio_back_lock); + +/* + * Helper macros to call backend ops. Makes sure the option is supported. + */ +#define iio_backend_check_op(back, op) ({ \ + struct iio_backend *____back = back; \ + int ____ret = 0; \ + \ + if (!____back->ops->op) \ + ____ret = -EOPNOTSUPP; \ + \ + ____ret; \ +}) + +#define iio_backend_op_call(back, op, args...) ({ \ + struct iio_backend *__back = back; \ + int __ret; \ + \ + __ret = iio_backend_check_op(__back, op); \ + if (!__ret) \ + __ret = __back->ops->op(__back, ##args); \ + \ + __ret; \ +}) + +#define iio_backend_ptr_op_call(back, op, args...) ({ \ + struct iio_backend *__back = back; \ + void *ptr_err; \ + int __ret; \ + \ + __ret = iio_backend_check_op(__back, op); \ + if (__ret) \ + ptr_err = ERR_PTR(__ret); \ + else \ + ptr_err = __back->ops->op(__back, ##args); \ + \ + ptr_err; \ +}) + +#define iio_backend_void_op_call(back, op, args...) { \ + struct iio_backend *__back = back; \ + int __ret; \ + \ + __ret = iio_backend_check_op(__back, op); \ + if (!__ret) \ + __back->ops->op(__back, ##args); \ + else \ + dev_dbg(__back->dev, "Op(%s) not implemented\n",\ + __stringify(op)); \ +} + +static ssize_t iio_backend_debugfs_read_reg(struct file *file, + char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct iio_backend *back = file->private_data; + char read_buf[20]; + unsigned int val; + int ret, len; + + ret = iio_backend_op_call(back, debugfs_reg_access, + back->cached_reg_addr, 0, &val); + if (ret) + return ret; + + len = scnprintf(read_buf, sizeof(read_buf), "0x%X\n", val); + + return simple_read_from_buffer(userbuf, count, ppos, read_buf, len); +} + +static ssize_t iio_backend_debugfs_write_reg(struct file *file, + const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct iio_backend *back = file->private_data; + unsigned int val; + char buf[80]; + ssize_t rc; + int ret; + + if (*ppos != 0 || count >= sizeof(buf)) + return -ENOSPC; + + rc = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, userbuf, count); + if (rc < 0) + return rc; + + buf[rc] = '\0'; + + ret = sscanf(buf, "%i %i", &back->cached_reg_addr, &val); + + switch (ret) { + case 1: + return count; + case 2: + ret = iio_backend_op_call(back, debugfs_reg_access, + back->cached_reg_addr, val, NULL); + if (ret) + return ret; + return count; + default: + return -EINVAL; + } +} + +static const struct file_operations iio_backend_debugfs_reg_fops = { + .open = simple_open, + .read = iio_backend_debugfs_read_reg, + .write = iio_backend_debugfs_write_reg, +}; + +static ssize_t iio_backend_debugfs_read_name(struct file *file, + char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct iio_backend *back = file->private_data; + char name[128]; + int len; + + len = scnprintf(name, sizeof(name), "%s\n", back->name); + + return simple_read_from_buffer(userbuf, count, ppos, name, len); +} + +static const struct file_operations iio_backend_debugfs_name_fops = { + .open = simple_open, + .read = iio_backend_debugfs_read_name, +}; + +/** + * iio_backend_debugfs_add - Add debugfs interfaces for Backends + * @back: Backend device + * @indio_dev: IIO device + */ +void iio_backend_debugfs_add(struct iio_backend *back, + struct iio_dev *indio_dev) +{ + struct dentry *d = iio_get_debugfs_dentry(indio_dev); + struct dentry *back_d; + char name[128]; + + if (!IS_ENABLED(CONFIG_DEBUG_FS) || !d) + return; + if (!back->ops->debugfs_reg_access && !back->name) + return; + + snprintf(name, sizeof(name), "backend%d", back->idx); + + back_d = debugfs_create_dir(name, d); + if (IS_ERR(back_d)) + return; + + if (back->ops->debugfs_reg_access) + debugfs_create_file("direct_reg_access", 0600, back_d, back, + &iio_backend_debugfs_reg_fops); + + if (back->name) + debugfs_create_file("name", 0400, back_d, back, + &iio_backend_debugfs_name_fops); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_debugfs_add, "IIO_BACKEND"); + +/** + * iio_backend_debugfs_print_chan_status - Print channel status + * @back: Backend device + * @chan: Channel number + * @buf: Buffer where to print the status + * @len: Available space + * + * One usecase where this is useful is for testing test tones in a digital + * interface and "ask" the backend to dump more details on why a test tone might + * have errors. + * + * RETURNS: + * Number of copied bytes on success, negative error code on failure. + */ +ssize_t iio_backend_debugfs_print_chan_status(struct iio_backend *back, + unsigned int chan, char *buf, + size_t len) +{ + if (!IS_ENABLED(CONFIG_DEBUG_FS)) + return -ENODEV; + + return iio_backend_op_call(back, debugfs_print_chan_status, chan, buf, + len); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_debugfs_print_chan_status, "IIO_BACKEND"); + +/** + * iio_backend_chan_enable - Enable a backend channel + * @back: Backend device + * @chan: Channel number + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_chan_enable(struct iio_backend *back, unsigned int chan) +{ + return iio_backend_op_call(back, chan_enable, chan); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_chan_enable, "IIO_BACKEND"); + +/** + * iio_backend_chan_disable - Disable a backend channel + * @back: Backend device + * @chan: Channel number + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_chan_disable(struct iio_backend *back, unsigned int chan) +{ + return iio_backend_op_call(back, chan_disable, chan); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_chan_disable, "IIO_BACKEND"); + +static void __iio_backend_disable(void *back) +{ + iio_backend_void_op_call(back, disable); +} + +/** + * iio_backend_disable - Backend disable + * @back: Backend device + */ +void iio_backend_disable(struct iio_backend *back) +{ + __iio_backend_disable(back); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_disable, "IIO_BACKEND"); + +/** + * iio_backend_enable - Backend enable + * @back: Backend device + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_enable(struct iio_backend *back) +{ + return iio_backend_op_call(back, enable); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_enable, "IIO_BACKEND"); + +/** + * devm_iio_backend_enable - Device managed backend enable + * @dev: Consumer device for the backend + * @back: Backend device + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int devm_iio_backend_enable(struct device *dev, struct iio_backend *back) +{ + int ret; + + ret = iio_backend_enable(back); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, __iio_backend_disable, back); +} +EXPORT_SYMBOL_NS_GPL(devm_iio_backend_enable, "IIO_BACKEND"); + +/** + * iio_backend_data_format_set - Configure the channel data format + * @back: Backend device + * @chan: Channel number + * @data: Data format + * + * Properly configure a channel with respect to the expected data format. A + * @struct iio_backend_data_fmt must be passed with the settings. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_data_format_set(struct iio_backend *back, unsigned int chan, + const struct iio_backend_data_fmt *data) +{ + if (!data || data->type >= IIO_BACKEND_DATA_TYPE_MAX) + return -EINVAL; + + return iio_backend_op_call(back, data_format_set, chan, data); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_data_format_set, "IIO_BACKEND"); + +/** + * iio_backend_data_source_set - Select data source + * @back: Backend device + * @chan: Channel number + * @data: Data source + * + * A given backend may have different sources to stream/sync data. This allows + * to choose that source. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_data_source_set(struct iio_backend *back, unsigned int chan, + enum iio_backend_data_source data) +{ + if (data >= IIO_BACKEND_DATA_SOURCE_MAX) + return -EINVAL; + + return iio_backend_op_call(back, data_source_set, chan, data); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_data_source_set, "IIO_BACKEND"); + +/** + * iio_backend_data_source_get - Get current data source + * @back: Backend device + * @chan: Channel number + * @data: Pointer to receive the current source value + * + * A given backend may have different sources to stream/sync data. This allows + * to know what source is in use. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_data_source_get(struct iio_backend *back, unsigned int chan, + enum iio_backend_data_source *data) +{ + int ret; + + ret = iio_backend_op_call(back, data_source_get, chan, data); + if (ret) + return ret; + + if (*data >= IIO_BACKEND_DATA_SOURCE_MAX) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(iio_backend_data_source_get, "IIO_BACKEND"); + +/** + * iio_backend_set_sampling_freq - Set channel sampling rate + * @back: Backend device + * @chan: Channel number + * @sample_rate_hz: Sample rate + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_set_sampling_freq(struct iio_backend *back, unsigned int chan, + u64 sample_rate_hz) +{ + return iio_backend_op_call(back, set_sample_rate, chan, sample_rate_hz); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_set_sampling_freq, "IIO_BACKEND"); + +/** + * iio_backend_test_pattern_set - Configure a test pattern + * @back: Backend device + * @chan: Channel number + * @pattern: Test pattern + * + * Configure a test pattern on the backend. This is typically used for + * calibrating the timings on the data digital interface. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_test_pattern_set(struct iio_backend *back, + unsigned int chan, + enum iio_backend_test_pattern pattern) +{ + if (pattern >= IIO_BACKEND_TEST_PATTERN_MAX) + return -EINVAL; + + return iio_backend_op_call(back, test_pattern_set, chan, pattern); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_test_pattern_set, "IIO_BACKEND"); + +/** + * iio_backend_chan_status - Get the channel status + * @back: Backend device + * @chan: Channel number + * @error: Error indication + * + * Get the current state of the backend channel. Typically used to check if + * there were any errors sending/receiving data. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_chan_status(struct iio_backend *back, unsigned int chan, + bool *error) +{ + return iio_backend_op_call(back, chan_status, chan, error); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_chan_status, "IIO_BACKEND"); + +/** + * iio_backend_iodelay_set - Set digital I/O delay + * @back: Backend device + * @lane: Lane number + * @taps: Number of taps + * + * Controls delays on sending/receiving data. One usecase for this is to + * calibrate the data digital interface so we get the best results when + * transferring data. Note that @taps has no unit since the actual delay per tap + * is very backend specific. Hence, frontend devices typically should go through + * an array of @taps (the size of that array should typically match the size of + * calibration points on the frontend device) and call this API. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_iodelay_set(struct iio_backend *back, unsigned int lane, + unsigned int taps) +{ + return iio_backend_op_call(back, iodelay_set, lane, taps); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_iodelay_set, "IIO_BACKEND"); + +/** + * iio_backend_data_sample_trigger - Control when to sample data + * @back: Backend device + * @trigger: Data trigger + * + * Mostly useful for input backends. Configures the backend for when to sample + * data (eg: rising vs falling edge). + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_data_sample_trigger(struct iio_backend *back, + enum iio_backend_sample_trigger trigger) +{ + if (trigger >= IIO_BACKEND_SAMPLE_TRIGGER_MAX) + return -EINVAL; + + return iio_backend_op_call(back, data_sample_trigger, trigger); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_data_sample_trigger, "IIO_BACKEND"); + +static void iio_backend_free_buffer(void *arg) +{ + struct iio_backend_buffer_pair *pair = arg; + + iio_backend_void_op_call(pair->back, free_buffer, pair->buffer); +} + +/** + * devm_iio_backend_request_buffer - Device managed buffer request + * @dev: Consumer device for the backend + * @back: Backend device + * @indio_dev: IIO device + * + * Request an IIO buffer from the backend. The type of the buffer (typically + * INDIO_BUFFER_HARDWARE) is up to the backend to decide. This is because, + * normally, the backend dictates what kind of buffering we can get. + * + * The backend .free_buffer() hooks is automatically called on @dev detach. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int devm_iio_backend_request_buffer(struct device *dev, + struct iio_backend *back, + struct iio_dev *indio_dev) +{ + struct iio_backend_buffer_pair *pair; + struct iio_buffer *buffer; + + pair = devm_kzalloc(dev, sizeof(*pair), GFP_KERNEL); + if (!pair) + return -ENOMEM; + + buffer = iio_backend_ptr_op_call(back, request_buffer, indio_dev); + if (IS_ERR(buffer)) + return PTR_ERR(buffer); + + /* weak reference should be all what we need */ + pair->back = back; + pair->buffer = buffer; + + return devm_add_action_or_reset(dev, iio_backend_free_buffer, pair); +} +EXPORT_SYMBOL_NS_GPL(devm_iio_backend_request_buffer, "IIO_BACKEND"); + +/** + * iio_backend_read_raw - Read a channel attribute from a backend device. + * @back: Backend device + * @chan: IIO channel reference + * @val: First returned value + * @val2: Second returned value + * @mask: Specify the attribute to return + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_read_raw(struct iio_backend *back, + struct iio_chan_spec const *chan, int *val, int *val2, + long mask) +{ + return iio_backend_op_call(back, read_raw, chan, val, val2, mask); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_read_raw, "IIO_BACKEND"); + +static struct iio_backend *iio_backend_from_indio_dev_parent(const struct device *dev) +{ + struct iio_backend *back = ERR_PTR(-ENODEV), *iter; + + /* + * We deliberately go through all backends even after finding a match. + * The reason is that we want to catch frontend devices which have more + * than one backend in which case returning the first we find is bogus. + * For those cases, frontends need to explicitly define + * get_iio_backend() in struct iio_info. + */ + guard(mutex)(&iio_back_lock); + list_for_each_entry(iter, &iio_back_list, entry) { + if (dev == iter->frontend_dev) { + if (!IS_ERR(back)) { + dev_warn(dev, + "Multiple backends! get_iio_backend() needs to be implemented"); + return ERR_PTR(-ENODEV); + } + + back = iter; + } + } + + return back; +} + +/** + * iio_backend_ext_info_get - IIO ext_info read callback + * @indio_dev: IIO device + * @private: Data private to the driver + * @chan: IIO channel + * @buf: Buffer where to place the attribute data + * + * This helper is intended to be used by backends that extend an IIO channel + * (through iio_backend_extend_chan_spec()) with extended info. In that case, + * backends are not supposed to give their own callbacks (as they would not have + * a way to get the backend from indio_dev). This is the getter. + * + * RETURNS: + * Number of bytes written to buf, negative error number on failure. + */ +ssize_t iio_backend_ext_info_get(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *chan, char *buf) +{ + struct iio_backend *back; + + /* + * The below should work for the majority of the cases. It will not work + * when one frontend has multiple backends in which case we'll need a + * new callback in struct iio_info so we can directly request the proper + * backend from the frontend. Anyways, let's only introduce new options + * when really needed... + */ + back = iio_backend_from_indio_dev_parent(indio_dev->dev.parent); + if (IS_ERR(back)) + return PTR_ERR(back); + + return iio_backend_op_call(back, ext_info_get, private, chan, buf); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_ext_info_get, "IIO_BACKEND"); + +/** + * iio_backend_ext_info_set - IIO ext_info write callback + * @indio_dev: IIO device + * @private: Data private to the driver + * @chan: IIO channel + * @buf: Buffer holding the sysfs attribute + * @len: Buffer length + * + * This helper is intended to be used by backends that extend an IIO channel + * (through iio_backend_extend_chan_spec()) with extended info. In that case, + * backends are not supposed to give their own callbacks (as they would not have + * a way to get the backend from indio_dev). This is the setter. + * + * RETURNS: + * Buffer length on success, negative error number on failure. + */ +ssize_t iio_backend_ext_info_set(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct iio_backend *back; + + back = iio_backend_from_indio_dev_parent(indio_dev->dev.parent); + if (IS_ERR(back)) + return PTR_ERR(back); + + return iio_backend_op_call(back, ext_info_set, private, chan, buf, len); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_ext_info_set, "IIO_BACKEND"); + +/** + * iio_backend_interface_type_get - get the interface 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_MAX) + 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, unsigned int 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_oversampling_ratio_set - set the oversampling ratio + * @back: Backend device + * @chan: Channel number + * @ratio: The oversampling ratio - value 1 corresponds to no oversampling. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_oversampling_ratio_set(struct iio_backend *back, + unsigned int chan, + unsigned int ratio) +{ + return iio_backend_op_call(back, oversampling_ratio_set, chan, ratio); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_oversampling_ratio_set, "IIO_BACKEND"); + +/** + * iio_backend_extend_chan_spec - Extend an IIO channel + * @back: Backend device + * @chan: IIO channel + * + * Some backends may have their own functionalities and hence capable of + * extending a frontend's channel. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_extend_chan_spec(struct iio_backend *back, + struct iio_chan_spec *chan) +{ + const struct iio_chan_spec_ext_info *frontend_ext_info = chan->ext_info; + const struct iio_chan_spec_ext_info *back_ext_info; + int ret; + + ret = iio_backend_op_call(back, extend_chan_spec, chan); + if (ret) + return ret; + /* + * Let's keep things simple for now. Don't allow to overwrite the + * frontend's extended info. If ever needed, we can support appending + * it. + */ + if (frontend_ext_info && chan->ext_info != frontend_ext_info) + return -EOPNOTSUPP; + if (!chan->ext_info) + return 0; + + /* Don't allow backends to get creative and force their own handlers */ + for (back_ext_info = chan->ext_info; back_ext_info->name; back_ext_info++) { + if (back_ext_info->read != iio_backend_ext_info_get) + return -EINVAL; + if (back_ext_info->write != iio_backend_ext_info_set) + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(iio_backend_extend_chan_spec, "IIO_BACKEND"); + +/** + * iio_backend_has_caps - Check if backend has specific capabilities + * @back: Backend device + * @caps: Capabilities to check + * + * RETURNS: + * True if backend has all the requested capabilities, false otherwise. + */ +bool iio_backend_has_caps(struct iio_backend *back, u32 caps) +{ + return (back->caps & caps) == caps; +} +EXPORT_SYMBOL_NS_GPL(iio_backend_has_caps, "IIO_BACKEND"); + +static void iio_backend_release(void *arg) +{ + struct iio_backend *back = arg; + + module_put(back->owner); +} + +static int __devm_iio_backend_get(struct device *dev, struct iio_backend *back) +{ + struct device_link *link; + int ret; + + /* + * Make sure the provider cannot be unloaded before the consumer module. + * Note that device_links would still guarantee that nothing is + * accessible (and breaks) but this makes it explicit that the consumer + * module must be also unloaded. + */ + if (!try_module_get(back->owner)) + return dev_err_probe(dev, -ENODEV, + "Cannot get module reference\n"); + + ret = devm_add_action_or_reset(dev, iio_backend_release, back); + if (ret) + return ret; + + link = device_link_add(dev, back->dev, DL_FLAG_AUTOREMOVE_CONSUMER); + if (!link) + return dev_err_probe(dev, -EINVAL, + "Could not link to supplier(%s)\n", + dev_name(back->dev)); + + back->frontend_dev = dev; + + dev_dbg(dev, "Found backend(%s) device\n", dev_name(back->dev)); + + return 0; +} + +/** + * iio_backend_filter_type_set - Set filter type + * @back: Backend device + * @type: Filter type. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_filter_type_set(struct iio_backend *back, + enum iio_backend_filter_type type) +{ + if (type >= IIO_BACKEND_FILTER_TYPE_MAX) + return -EINVAL; + + return iio_backend_op_call(back, filter_type_set, type); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_filter_type_set, "IIO_BACKEND"); + +/** + * iio_backend_interface_data_align - Perform the data alignment process. + * @back: Backend device + * @timeout_us: Timeout value in us. + * + * When activated, it initiates a process that aligns the sample's most + * significant bit (MSB) based solely on the captured data, without + * considering any other external signals. + * + * The timeout_us value must be greater than 0. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_interface_data_align(struct iio_backend *back, u32 timeout_us) +{ + if (!timeout_us) + return -EINVAL; + + return iio_backend_op_call(back, interface_data_align, timeout_us); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_interface_data_align, "IIO_BACKEND"); + +/** + * iio_backend_num_lanes_set - Number of lanes enabled. + * @back: Backend device + * @num_lanes: Number of lanes. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_num_lanes_set(struct iio_backend *back, unsigned int num_lanes) +{ + if (!num_lanes) + return -EINVAL; + + return iio_backend_op_call(back, num_lanes_set, num_lanes); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_num_lanes_set, "IIO_BACKEND"); + +/** + * iio_backend_ddr_enable - Enable interface DDR (Double Data Rate) mode + * @back: Backend device + * + * Enable DDR, data is generated by the IP at each front (raising and falling) + * of the bus clock signal. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_ddr_enable(struct iio_backend *back) +{ + return iio_backend_op_call(back, ddr_enable); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_ddr_enable, "IIO_BACKEND"); + +/** + * iio_backend_ddr_disable - Disable interface DDR (Double Data Rate) mode + * @back: Backend device + * + * Disable DDR, setting into SDR mode (Single Data Rate). + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_ddr_disable(struct iio_backend *back) +{ + return iio_backend_op_call(back, ddr_disable); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_ddr_disable, "IIO_BACKEND"); + +/** + * iio_backend_data_stream_enable - Enable data stream + * @back: Backend device + * + * Enable data stream over the bus interface. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_data_stream_enable(struct iio_backend *back) +{ + return iio_backend_op_call(back, data_stream_enable); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_data_stream_enable, "IIO_BACKEND"); + +/** + * iio_backend_data_stream_disable - Disable data stream + * @back: Backend device + * + * Disable data stream over the bus interface. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_data_stream_disable(struct iio_backend *back) +{ + return iio_backend_op_call(back, data_stream_disable); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_data_stream_disable, "IIO_BACKEND"); + +/** + * iio_backend_data_transfer_addr - Set data address. + * @back: Backend device + * @address: Data register address + * + * Some devices may need to inform the backend about an address + * where to read or write the data. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int iio_backend_data_transfer_addr(struct iio_backend *back, u32 address) +{ + return iio_backend_op_call(back, data_transfer_addr, address); +} +EXPORT_SYMBOL_NS_GPL(iio_backend_data_transfer_addr, "IIO_BACKEND"); + +static struct iio_backend *__devm_iio_backend_fwnode_get_by_index(struct device *dev, + struct fwnode_handle *fwnode, + unsigned int index) +{ + struct iio_backend *back; + int ret; + + struct fwnode_handle *fwnode_back __free(fwnode_handle) = + fwnode_find_reference(fwnode, "io-backends", index); + if (IS_ERR(fwnode_back)) + return dev_err_cast_probe(dev, fwnode_back, + "Cannot get Firmware reference\n"); + + guard(mutex)(&iio_back_lock); + list_for_each_entry(back, &iio_back_list, entry) { + if (!device_match_fwnode(back->dev, fwnode_back)) + continue; + + ret = __devm_iio_backend_get(dev, back); + if (ret) + return ERR_PTR(ret); + + back->idx = index; + + return back; + } + + return ERR_PTR(-EPROBE_DEFER); +} + +static struct iio_backend *__devm_iio_backend_fwnode_get(struct device *dev, const char *name, + struct fwnode_handle *fwnode) +{ + unsigned int index; + int ret; + + if (name) { + ret = device_property_match_string(dev, "io-backend-names", name); + if (ret < 0) + return ERR_PTR(ret); + index = ret; + } else { + index = 0; + } + + return __devm_iio_backend_fwnode_get_by_index(dev, fwnode, index); +} + +/** + * devm_iio_backend_get - Device managed backend device get + * @dev: Consumer device for the backend + * @name: Backend name + * + * Gets the backend associated with @dev. + * + * RETURNS: + * A backend pointer, negative error pointer otherwise. + */ +struct iio_backend *devm_iio_backend_get(struct device *dev, const char *name) +{ + return __devm_iio_backend_fwnode_get(dev, name, dev_fwnode(dev)); +} +EXPORT_SYMBOL_NS_GPL(devm_iio_backend_get, "IIO_BACKEND"); + +/** + * devm_iio_backend_get_by_index - Device managed backend device get by index + * @dev: Consumer device for the backend + * @index: Index of the backend in the io-backends property + * + * Gets the backend at @index associated with @dev. + * + * RETURNS: + * A backend pointer, negative error pointer otherwise. + */ +struct iio_backend *devm_iio_backend_get_by_index(struct device *dev, unsigned int index) +{ + return __devm_iio_backend_fwnode_get_by_index(dev, dev_fwnode(dev), index); +} +EXPORT_SYMBOL_NS_GPL(devm_iio_backend_get_by_index, "IIO_BACKEND"); + +/** + * devm_iio_backend_fwnode_get - Device managed backend firmware node get + * @dev: Consumer device for the backend + * @name: Backend name + * @fwnode: Firmware node of the backend consumer + * + * Gets the backend associated with a firmware node. + * + * RETURNS: + * A backend pointer, negative error pointer otherwise. + */ +struct iio_backend *devm_iio_backend_fwnode_get(struct device *dev, + const char *name, + struct fwnode_handle *fwnode) +{ + return __devm_iio_backend_fwnode_get(dev, name, fwnode); +} +EXPORT_SYMBOL_NS_GPL(devm_iio_backend_fwnode_get, "IIO_BACKEND"); + +/** + * __devm_iio_backend_get_from_fwnode_lookup - Device managed fwnode backend device get + * @dev: Consumer device for the backend + * @fwnode: Firmware node of the backend device + * + * Search the backend list for a device matching @fwnode. + * This API should not be used and it's only present for preventing the first + * user of this framework to break it's DT ABI. + * + * RETURNS: + * A backend pointer, negative error pointer otherwise. + */ +struct iio_backend * +__devm_iio_backend_get_from_fwnode_lookup(struct device *dev, + struct fwnode_handle *fwnode) +{ + struct iio_backend *back; + int ret; + + guard(mutex)(&iio_back_lock); + list_for_each_entry(back, &iio_back_list, entry) { + if (!device_match_fwnode(back->dev, fwnode)) + continue; + + ret = __devm_iio_backend_get(dev, back); + if (ret) + return ERR_PTR(ret); + + return back; + } + + return ERR_PTR(-EPROBE_DEFER); +} +EXPORT_SYMBOL_NS_GPL(__devm_iio_backend_get_from_fwnode_lookup, "IIO_BACKEND"); + +/** + * iio_backend_get_priv - Get driver private data + * @back: Backend device + * + * RETURNS: + * Pointer to the driver private data associated with the backend. + */ +void *iio_backend_get_priv(const struct iio_backend *back) +{ + return back->priv; +} +EXPORT_SYMBOL_NS_GPL(iio_backend_get_priv, "IIO_BACKEND"); + +static void iio_backend_unregister(void *arg) +{ + struct iio_backend *back = arg; + + guard(mutex)(&iio_back_lock); + list_del(&back->entry); +} + +/** + * devm_iio_backend_register - Device managed backend device register + * @dev: Backend device being registered + * @info: Backend info + * @priv: Device private data + * + * @info is mandatory. Not providing it results in -EINVAL. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int devm_iio_backend_register(struct device *dev, + const struct iio_backend_info *info, void *priv) +{ + struct iio_backend *back; + + if (!info || !info->ops) + return dev_err_probe(dev, -EINVAL, "No backend ops given\n"); + + /* + * Through device_links, we guarantee that a frontend device cannot be + * bound/exist if the backend driver is not around. Hence, we can bind + * the backend object lifetime with the device being passed since + * removing it will tear the frontend/consumer down. + */ + back = devm_kzalloc(dev, sizeof(*back), GFP_KERNEL); + if (!back) + return -ENOMEM; + + back->ops = info->ops; + back->name = info->name; + back->caps = info->caps; + back->owner = dev->driver->owner; + back->dev = dev; + back->priv = priv; + scoped_guard(mutex, &iio_back_lock) + list_add(&back->entry, &iio_back_list); + + return devm_add_action_or_reset(dev, iio_backend_unregister, back); +} +EXPORT_SYMBOL_NS_GPL(devm_iio_backend_register, "IIO_BACKEND"); + +MODULE_AUTHOR("Nuno Sa "); +MODULE_DESCRIPTION("Framework to handle complex IIO aggregate devices"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c new file mode 100644 index 000000000..2c9ec93df --- /dev/null +++ b/drivers/iio/industrialio-buffer.c @@ -0,0 +1,2559 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* The industrial I/O core + * + * Copyright (c) 2008 Jonathan Cameron + * + * Handling of buffer allocation / resizing. + * + * Things to look at here. + * - Better memory allocation techniques? + * - Alternative access techniques? + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "iio_core.h" +#include "iio_core_trigger.h" +#include +#include +#include + +#define DMABUF_ENQUEUE_TIMEOUT_MS 5000 + +MODULE_IMPORT_NS("DMA_BUF"); + +struct iio_dmabuf_priv { + struct list_head entry; + struct kref ref; + + struct iio_buffer *buffer; + struct iio_dma_buffer_block *block; + + u64 context; + + struct dma_buf_attachment *attach; + struct sg_table *sgt; + enum dma_data_direction dir; + atomic_t seqno; +}; + +struct iio_dma_fence { + /* + * Must remain the first member so the default release callback can pass + * the fence directly to dma_fence_free(). + */ + struct dma_fence base; + spinlock_t lock; /* protects base */ + struct iio_dmabuf_priv *priv; + struct work_struct work; +}; + +static const char * const iio_endian_prefix[] = { + [IIO_BE] = "be", + [IIO_LE] = "le", +}; + +static bool iio_buffer_is_active(struct iio_buffer *buf) +{ + return !list_empty(&buf->buffer_list); +} + +static size_t iio_buffer_data_available(struct iio_buffer *buf) +{ + return buf->access->data_available(buf); +} + +static int iio_buffer_flush_hwfifo(struct iio_dev *indio_dev, + struct iio_buffer *buf, size_t required) +{ + if (!indio_dev->info->hwfifo_flush_to_buffer) + return -ENODEV; + + return indio_dev->info->hwfifo_flush_to_buffer(indio_dev, required); +} + +static bool iio_buffer_ready(struct iio_dev *indio_dev, struct iio_buffer *buf, + size_t to_wait, int to_flush) +{ + size_t avail; + int flushed = 0; + + /* wakeup if the device was unregistered */ + if (!indio_dev->info) + return true; + + /* drain the buffer if it was disabled */ + if (!iio_buffer_is_active(buf)) { + to_wait = min_t(size_t, to_wait, 1); + to_flush = 0; + } + + avail = iio_buffer_data_available(buf); + + if (avail >= to_wait) { + /* force a flush for non-blocking reads */ + if (!to_wait && avail < to_flush) + iio_buffer_flush_hwfifo(indio_dev, buf, + to_flush - avail); + return true; + } + + if (to_flush) + flushed = iio_buffer_flush_hwfifo(indio_dev, buf, + to_wait - avail); + if (flushed <= 0) + return false; + + if (avail + flushed >= to_wait) + return true; + + return false; +} + +/** + * iio_buffer_read() - chrdev read for buffer access + * @filp: File structure pointer for the char device + * @buf: Destination buffer for iio buffer read + * @n: First n bytes to read + * @f_ps: Long offset provided by the user as a seek position + * + * This function relies on all buffer implementations having an + * iio_buffer as their first element. + * + * Return: negative values corresponding to error codes or ret != 0 + * for ending the reading activity + **/ +static ssize_t iio_buffer_read(struct file *filp, char __user *buf, + size_t n, loff_t *f_ps) +{ + struct iio_dev_buffer_pair *ib = filp->private_data; + struct iio_buffer *rb = ib->buffer; + struct iio_dev *indio_dev = ib->indio_dev; + DEFINE_WAIT_FUNC(wait, woken_wake_function); + size_t datum_size; + size_t to_wait; + int ret = 0; + + if (!indio_dev->info) + return -ENODEV; + + if (!rb || !rb->access->read) + return -EINVAL; + + if (rb->direction != IIO_BUFFER_DIRECTION_IN) + return -EPERM; + + datum_size = rb->bytes_per_datum; + + /* + * If datum_size is 0 there will never be anything to read from the + * buffer, so signal end of file now. + */ + if (!datum_size) + return 0; + + if (filp->f_flags & O_NONBLOCK) + to_wait = 0; + else + to_wait = min_t(size_t, n / datum_size, rb->watermark); + + add_wait_queue(&rb->pollq, &wait); + do { + if (!indio_dev->info) { + ret = -ENODEV; + break; + } + + if (!iio_buffer_ready(indio_dev, rb, to_wait, n / datum_size)) { + if (signal_pending(current)) { + ret = -ERESTARTSYS; + break; + } + + wait_woken(&wait, TASK_INTERRUPTIBLE, + MAX_SCHEDULE_TIMEOUT); + continue; + } + + ret = rb->access->read(rb, n, buf); + if (ret == 0 && (filp->f_flags & O_NONBLOCK)) + ret = -EAGAIN; + } while (ret == 0); + remove_wait_queue(&rb->pollq, &wait); + + return ret; +} + +static size_t iio_buffer_space_available(struct iio_buffer *buf) +{ + if (buf->access->space_available) + return buf->access->space_available(buf); + + return SIZE_MAX; +} + +static ssize_t iio_buffer_write(struct file *filp, const char __user *buf, + size_t n, loff_t *f_ps) +{ + struct iio_dev_buffer_pair *ib = filp->private_data; + struct iio_buffer *rb = ib->buffer; + struct iio_dev *indio_dev = ib->indio_dev; + DEFINE_WAIT_FUNC(wait, woken_wake_function); + int ret = 0; + size_t written; + + if (!indio_dev->info) + return -ENODEV; + + if (!rb || !rb->access->write) + return -EINVAL; + + if (rb->direction != IIO_BUFFER_DIRECTION_OUT) + return -EPERM; + + written = 0; + add_wait_queue(&rb->pollq, &wait); + do { + if (!indio_dev->info) { + ret = -ENODEV; + break; + } + + if (!iio_buffer_space_available(rb)) { + if (signal_pending(current)) { + ret = -ERESTARTSYS; + break; + } + + if (filp->f_flags & O_NONBLOCK) { + if (!written) + ret = -EAGAIN; + break; + } + + wait_woken(&wait, TASK_INTERRUPTIBLE, + MAX_SCHEDULE_TIMEOUT); + continue; + } + + ret = rb->access->write(rb, n - written, buf + written); + if (ret < 0) + break; + + written += ret; + + } while (written != n); + remove_wait_queue(&rb->pollq, &wait); + + return ret < 0 ? ret : written; +} + +/** + * iio_buffer_poll() - poll the buffer to find out if it has data + * @filp: File structure pointer for device access + * @wait: Poll table structure pointer for which the driver adds + * a wait queue + * + * Return: (EPOLLIN | EPOLLRDNORM) if data is available for reading + * or 0 for other cases + */ +static __poll_t iio_buffer_poll(struct file *filp, + struct poll_table_struct *wait) +{ + struct iio_dev_buffer_pair *ib = filp->private_data; + struct iio_buffer *rb = ib->buffer; + struct iio_dev *indio_dev = ib->indio_dev; + + if (!indio_dev->info || !rb) + return 0; + + poll_wait(filp, &rb->pollq, wait); + + switch (rb->direction) { + case IIO_BUFFER_DIRECTION_IN: + if (iio_buffer_ready(indio_dev, rb, rb->watermark, 0)) + return EPOLLIN | EPOLLRDNORM; + break; + case IIO_BUFFER_DIRECTION_OUT: + if (iio_buffer_space_available(rb)) + return EPOLLOUT | EPOLLWRNORM; + break; + } + + return 0; +} + +ssize_t iio_buffer_read_wrapper(struct file *filp, char __user *buf, + size_t n, loff_t *f_ps) +{ + struct iio_dev_buffer_pair *ib = filp->private_data; + struct iio_buffer *rb = ib->buffer; + + /* check if buffer was opened through new API */ + if (test_bit(IIO_BUSY_BIT_POS, &rb->flags)) + return -EBUSY; + + return iio_buffer_read(filp, buf, n, f_ps); +} + +ssize_t iio_buffer_write_wrapper(struct file *filp, const char __user *buf, + size_t n, loff_t *f_ps) +{ + struct iio_dev_buffer_pair *ib = filp->private_data; + struct iio_buffer *rb = ib->buffer; + + /* check if buffer was opened through new API */ + if (test_bit(IIO_BUSY_BIT_POS, &rb->flags)) + return -EBUSY; + + return iio_buffer_write(filp, buf, n, f_ps); +} + +__poll_t iio_buffer_poll_wrapper(struct file *filp, + struct poll_table_struct *wait) +{ + struct iio_dev_buffer_pair *ib = filp->private_data; + struct iio_buffer *rb = ib->buffer; + + /* check if buffer was opened through new API */ + if (test_bit(IIO_BUSY_BIT_POS, &rb->flags)) + return 0; + + return iio_buffer_poll(filp, wait); +} + +/** + * iio_buffer_wakeup_poll - Wakes up the buffer waitqueue + * @indio_dev: The IIO device + * + * Wakes up the event waitqueue used for poll(). Should usually + * be called when the device is unregistered. + */ +void iio_buffer_wakeup_poll(struct iio_dev *indio_dev) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + struct iio_buffer *buffer; + unsigned int i; + + for (i = 0; i < iio_dev_opaque->attached_buffers_cnt; i++) { + buffer = iio_dev_opaque->attached_buffers[i]; + wake_up(&buffer->pollq); + } +} + +int iio_pop_from_buffer(struct iio_buffer *buffer, void *data) +{ + if (!buffer || !buffer->access || !buffer->access->remove_from) + return -EINVAL; + + return buffer->access->remove_from(buffer, data); +} +EXPORT_SYMBOL_GPL(iio_pop_from_buffer); + +void iio_buffer_init(struct iio_buffer *buffer) +{ + INIT_LIST_HEAD(&buffer->demux_list); + INIT_LIST_HEAD(&buffer->buffer_list); + INIT_LIST_HEAD(&buffer->dmabufs); + mutex_init(&buffer->dmabufs_mutex); + init_waitqueue_head(&buffer->pollq); + kref_init(&buffer->ref); + if (!buffer->watermark) + buffer->watermark = 1; +} +EXPORT_SYMBOL(iio_buffer_init); + +void iio_device_detach_buffers(struct iio_dev *indio_dev) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + struct iio_buffer *buffer; + unsigned int i; + + for (i = 0; i < iio_dev_opaque->attached_buffers_cnt; i++) { + buffer = iio_dev_opaque->attached_buffers[i]; + iio_buffer_put(buffer); + } + + kfree(iio_dev_opaque->attached_buffers); +} + +static ssize_t iio_show_scan_index(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%u\n", to_iio_dev_attr(attr)->c->scan_index); +} + +static ssize_t iio_show_fixed_type(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + const struct iio_scan_type *scan_type; + u8 type; + + scan_type = iio_get_current_scan_type(indio_dev, this_attr->c); + if (IS_ERR(scan_type)) + return PTR_ERR(scan_type); + + type = scan_type->endianness; + + if (type == IIO_CPU) { +#ifdef __LITTLE_ENDIAN + type = IIO_LE; +#else + type = IIO_BE; +#endif + } + if (scan_type->repeat > 1) + return sysfs_emit(buf, "%s:%c%d/%dX%d>>%u\n", + iio_endian_prefix[type], + scan_type->sign, + scan_type->realbits, + scan_type->storagebits, + scan_type->repeat, + scan_type->shift); + else + return sysfs_emit(buf, "%s:%c%d/%d>>%u\n", + iio_endian_prefix[type], + scan_type->sign, + scan_type->realbits, + scan_type->storagebits, + scan_type->shift); +} + +static ssize_t iio_scan_el_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + struct iio_buffer *buffer = to_iio_dev_attr(attr)->buffer; + + /* Ensure ret is 0 or 1. */ + ret = !!test_bit(to_iio_dev_attr(attr)->address, + buffer->scan_mask); + + return sysfs_emit(buf, "%d\n", ret); +} + +/* Note NULL used as error indicator as it doesn't make sense. */ +static const unsigned long *iio_scan_mask_match(const unsigned long *av_masks, + unsigned int masklength, + const unsigned long *mask, + bool strict) +{ + if (bitmap_empty(mask, masklength)) + return NULL; + /* + * The condition here do not handle multi-long masks correctly. + * It only checks the first long to be zero, and will use such mask + * as a terminator even if there was bits set after the first long. + * + * Correct check would require using: + * while (!bitmap_empty(av_masks, masklength)) + * instead. This is potentially hazardous because the + * avaliable_scan_masks is a zero terminated array of longs - and + * using the proper bitmap_empty() check for multi-long wide masks + * would require the array to be terminated with multiple zero longs - + * which is not such an usual pattern. + * + * As writing of this no multi-long wide masks were found in-tree, so + * the simple while (*av_masks) check is working. + */ + while (*av_masks) { + if (strict) { + if (bitmap_equal(mask, av_masks, masklength)) + return av_masks; + } else { + if (bitmap_subset(mask, av_masks, masklength)) + return av_masks; + } + av_masks += BITS_TO_LONGS(masklength); + } + return NULL; +} + +static bool iio_validate_scan_mask(struct iio_dev *indio_dev, + const unsigned long *mask) +{ + if (!indio_dev->setup_ops->validate_scan_mask) + return true; + + return indio_dev->setup_ops->validate_scan_mask(indio_dev, mask); +} + +/** + * iio_scan_mask_set() - set particular bit in the scan mask + * @indio_dev: the iio device + * @buffer: the buffer whose scan mask we are interested in + * @bit: the bit to be set. + * + * Note that at this point we have no way of knowing what other + * buffers might request, hence this code only verifies that the + * individual buffers request is plausible. + */ +static int iio_scan_mask_set(struct iio_dev *indio_dev, + struct iio_buffer *buffer, int bit) +{ + unsigned int masklength = iio_get_masklength(indio_dev); + const unsigned long *mask; + unsigned long *trialmask; + + if (!masklength) { + WARN(1, "Trying to set scanmask prior to registering buffer\n"); + return -EINVAL; + } + + trialmask = bitmap_alloc(masklength, GFP_KERNEL); + if (!trialmask) + return -ENOMEM; + bitmap_copy(trialmask, buffer->scan_mask, masklength); + set_bit(bit, trialmask); + + if (!iio_validate_scan_mask(indio_dev, trialmask)) + goto err_invalid_mask; + + if (indio_dev->available_scan_masks) { + mask = iio_scan_mask_match(indio_dev->available_scan_masks, + masklength, trialmask, false); + if (!mask) + goto err_invalid_mask; + } + bitmap_copy(buffer->scan_mask, trialmask, masklength); + + bitmap_free(trialmask); + + return 0; + +err_invalid_mask: + bitmap_free(trialmask); + return -EINVAL; +} + +static int iio_scan_mask_clear(struct iio_buffer *buffer, int bit) +{ + clear_bit(bit, buffer->scan_mask); + return 0; +} + +static int iio_scan_mask_query(struct iio_dev *indio_dev, + struct iio_buffer *buffer, int bit) +{ + if (bit > iio_get_masklength(indio_dev)) + return -EINVAL; + + if (!buffer->scan_mask) + return 0; + + /* Ensure return value is 0 or 1. */ + return !!test_bit(bit, buffer->scan_mask); +}; + +static ssize_t iio_scan_el_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret; + bool state; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct iio_buffer *buffer = this_attr->buffer; + + ret = kstrtobool(buf, &state); + if (ret < 0) + return ret; + + guard(mutex)(&iio_dev_opaque->mlock); + if (iio_buffer_is_active(buffer)) + return -EBUSY; + + ret = iio_scan_mask_query(indio_dev, buffer, this_attr->address); + if (ret < 0) + return ret; + + if (state && ret) + return len; + + if (state) + ret = iio_scan_mask_set(indio_dev, buffer, this_attr->address); + else + ret = iio_scan_mask_clear(buffer, this_attr->address); + if (ret) + return ret; + + return len; +} + +static ssize_t iio_scan_el_ts_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_buffer *buffer = to_iio_dev_attr(attr)->buffer; + + return sysfs_emit(buf, "%d\n", buffer->scan_timestamp); +} + +static ssize_t iio_scan_el_ts_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + struct iio_buffer *buffer = to_iio_dev_attr(attr)->buffer; + bool state; + + ret = kstrtobool(buf, &state); + if (ret < 0) + return ret; + + guard(mutex)(&iio_dev_opaque->mlock); + if (iio_buffer_is_active(buffer)) + return -EBUSY; + + buffer->scan_timestamp = state; + + return len; +} + +static int iio_buffer_add_channel_sysfs(struct iio_dev *indio_dev, + struct iio_buffer *buffer, + const struct iio_chan_spec *chan) +{ + int ret, attrcount = 0; + + ret = __iio_add_chan_devattr("index", + chan, + &iio_show_scan_index, + NULL, + 0, + IIO_SEPARATE, + &indio_dev->dev, + buffer, + &buffer->buffer_attr_list); + if (ret) + return ret; + attrcount++; + ret = __iio_add_chan_devattr("type", + chan, + &iio_show_fixed_type, + NULL, + 0, + IIO_SEPARATE, + &indio_dev->dev, + buffer, + &buffer->buffer_attr_list); + if (ret) + return ret; + attrcount++; + if (chan->type != IIO_TIMESTAMP) + ret = __iio_add_chan_devattr("en", + chan, + &iio_scan_el_show, + &iio_scan_el_store, + chan->scan_index, + IIO_SEPARATE, + &indio_dev->dev, + buffer, + &buffer->buffer_attr_list); + else + ret = __iio_add_chan_devattr("en", + chan, + &iio_scan_el_ts_show, + &iio_scan_el_ts_store, + chan->scan_index, + IIO_SEPARATE, + &indio_dev->dev, + buffer, + &buffer->buffer_attr_list); + if (ret) + return ret; + attrcount++; + ret = attrcount; + return ret; +} + +static ssize_t length_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct iio_buffer *buffer = to_iio_dev_attr(attr)->buffer; + + return sysfs_emit(buf, "%d\n", buffer->length); +} + +static ssize_t length_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + struct iio_buffer *buffer = to_iio_dev_attr(attr)->buffer; + unsigned int val; + int ret; + + ret = kstrtouint(buf, 10, &val); + if (ret) + return ret; + + if (val == buffer->length) + return len; + + guard(mutex)(&iio_dev_opaque->mlock); + if (iio_buffer_is_active(buffer)) + return -EBUSY; + + buffer->access->set_length(buffer, val); + + if (buffer->length && buffer->length < buffer->watermark) + buffer->watermark = buffer->length; + + return len; +} + +static ssize_t enable_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct iio_buffer *buffer = to_iio_dev_attr(attr)->buffer; + + return sysfs_emit(buf, "%d\n", iio_buffer_is_active(buffer)); +} + +static int iio_storage_bytes_for_si(struct iio_dev *indio_dev, + unsigned int scan_index) +{ + const struct iio_chan_spec *ch; + const struct iio_scan_type *scan_type; + unsigned int bytes; + + ch = iio_find_channel_from_si(indio_dev, scan_index); + scan_type = iio_get_current_scan_type(indio_dev, ch); + if (IS_ERR(scan_type)) + return PTR_ERR(scan_type); + + bytes = scan_type->storagebits / 8; + + if (scan_type->repeat > 1) + bytes *= roundup_pow_of_two(scan_type->repeat); + + return bytes; +} + +static int iio_storage_bytes_for_timestamp(struct iio_dev *indio_dev) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + + return iio_storage_bytes_for_si(indio_dev, + iio_dev_opaque->scan_index_timestamp); +} + +static int iio_compute_scan_bytes(struct iio_dev *indio_dev, + const unsigned long *mask, bool timestamp, + unsigned int *scan_bytes, + unsigned int *timestamp_offset) +{ + unsigned int bytes = 0; + int length, i, largest = 0; + + /* How much space will the demuxed element take? */ + for_each_set_bit(i, mask, iio_get_masklength(indio_dev)) { + length = iio_storage_bytes_for_si(indio_dev, i); + if (length < 0) + return length; + + bytes = ALIGN(bytes, length); + bytes += length; + largest = max(largest, length); + } + + if (timestamp) { + length = iio_storage_bytes_for_timestamp(indio_dev); + if (length < 0) + return length; + + bytes = ALIGN(bytes, length); + + if (timestamp_offset) + *timestamp_offset = bytes; + + bytes += length; + largest = max(largest, length); + } + + *scan_bytes = ALIGN(bytes, largest); + + return 0; +} + +static void iio_buffer_activate(struct iio_dev *indio_dev, + struct iio_buffer *buffer) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + + iio_buffer_get(buffer); + list_add(&buffer->buffer_list, &iio_dev_opaque->buffer_list); +} + +static void iio_buffer_deactivate(struct iio_buffer *buffer) +{ + list_del_init(&buffer->buffer_list); + wake_up_interruptible(&buffer->pollq); + iio_buffer_put(buffer); +} + +static void iio_buffer_deactivate_all(struct iio_dev *indio_dev) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + struct iio_buffer *buffer, *_buffer; + + list_for_each_entry_safe(buffer, _buffer, + &iio_dev_opaque->buffer_list, buffer_list) + iio_buffer_deactivate(buffer); +} + +static int iio_buffer_enable(struct iio_buffer *buffer, + struct iio_dev *indio_dev) +{ + if (!buffer->access->enable) + return 0; + return buffer->access->enable(buffer, indio_dev); +} + +static int iio_buffer_disable(struct iio_buffer *buffer, + struct iio_dev *indio_dev) +{ + if (!buffer->access->disable) + return 0; + return buffer->access->disable(buffer, indio_dev); +} + +static int iio_buffer_update_bytes_per_datum(struct iio_dev *indio_dev, + struct iio_buffer *buffer) +{ + unsigned int bytes; + int ret; + + if (!buffer->access->set_bytes_per_datum) + return 0; + + ret = iio_compute_scan_bytes(indio_dev, buffer->scan_mask, + buffer->scan_timestamp, &bytes, NULL); + if (ret) + return ret; + + buffer->access->set_bytes_per_datum(buffer, bytes); + + return 0; +} + +static int iio_buffer_request_update(struct iio_dev *indio_dev, + struct iio_buffer *buffer) +{ + int ret; + + ret = iio_buffer_update_bytes_per_datum(indio_dev, buffer); + if (ret) + return ret; + + if (buffer->access->request_update) { + ret = buffer->access->request_update(buffer); + if (ret) { + dev_dbg(&indio_dev->dev, + "Buffer not started: buffer parameter update failed (%d)\n", + ret); + return ret; + } + } + + return 0; +} + +static void iio_free_scan_mask(struct iio_dev *indio_dev, + const unsigned long *mask) +{ + /* If the mask is dynamically allocated free it, otherwise do nothing */ + if (!indio_dev->available_scan_masks) + bitmap_free(mask); +} + +struct iio_device_config { + unsigned int mode; + unsigned int watermark; + const unsigned long *scan_mask; + unsigned int scan_bytes; + unsigned int scan_timestamp_offset; + bool scan_timestamp; +}; + +static int iio_verify_update(struct iio_dev *indio_dev, + struct iio_buffer *insert_buffer, + struct iio_buffer *remove_buffer, + struct iio_device_config *config) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + unsigned int masklength = iio_get_masklength(indio_dev); + unsigned long *compound_mask; + const unsigned long *scan_mask; + bool strict_scanmask = false; + struct iio_buffer *buffer; + bool scan_timestamp; + unsigned int modes; + int ret; + + if (insert_buffer && + bitmap_empty(insert_buffer->scan_mask, masklength)) { + dev_dbg(&indio_dev->dev, + "At least one scan element must be enabled first\n"); + return -EINVAL; + } + + memset(config, 0, sizeof(*config)); + config->watermark = ~0; + + /* + * If there is just one buffer and we are removing it there is nothing + * to verify. + */ + if (remove_buffer && !insert_buffer && + list_is_singular(&iio_dev_opaque->buffer_list)) + return 0; + + modes = indio_dev->modes; + + list_for_each_entry(buffer, &iio_dev_opaque->buffer_list, buffer_list) { + if (buffer == remove_buffer) + continue; + modes &= buffer->access->modes; + config->watermark = min(config->watermark, buffer->watermark); + } + + if (insert_buffer) { + modes &= insert_buffer->access->modes; + config->watermark = min(config->watermark, + insert_buffer->watermark); + } + + /* Definitely possible for devices to support both of these. */ + if ((modes & INDIO_BUFFER_TRIGGERED) && indio_dev->trig) { + config->mode = INDIO_BUFFER_TRIGGERED; + } else if (modes & INDIO_BUFFER_HARDWARE) { + /* + * Keep things simple for now and only allow a single buffer to + * be connected in hardware mode. + */ + if (insert_buffer && !list_empty(&iio_dev_opaque->buffer_list)) + return -EINVAL; + config->mode = INDIO_BUFFER_HARDWARE; + strict_scanmask = true; + } else if (modes & INDIO_BUFFER_SOFTWARE) { + config->mode = INDIO_BUFFER_SOFTWARE; + } else { + /* Can only occur on first buffer */ + if (indio_dev->modes & INDIO_BUFFER_TRIGGERED) + dev_dbg(&indio_dev->dev, "Buffer not started: no trigger\n"); + return -EINVAL; + } + + /* What scan mask do we actually have? */ + compound_mask = bitmap_zalloc(masklength, GFP_KERNEL); + if (!compound_mask) + return -ENOMEM; + + scan_timestamp = false; + + list_for_each_entry(buffer, &iio_dev_opaque->buffer_list, buffer_list) { + if (buffer == remove_buffer) + continue; + bitmap_or(compound_mask, compound_mask, buffer->scan_mask, + masklength); + scan_timestamp |= buffer->scan_timestamp; + } + + if (insert_buffer) { + bitmap_or(compound_mask, compound_mask, + insert_buffer->scan_mask, masklength); + scan_timestamp |= insert_buffer->scan_timestamp; + } + + if (indio_dev->available_scan_masks) { + scan_mask = iio_scan_mask_match(indio_dev->available_scan_masks, + masklength, compound_mask, + strict_scanmask); + bitmap_free(compound_mask); + if (!scan_mask) + return -EINVAL; + } else { + scan_mask = compound_mask; + } + + ret = iio_compute_scan_bytes(indio_dev, scan_mask, scan_timestamp, + &config->scan_bytes, + &config->scan_timestamp_offset); + if (ret) + return ret; + + config->scan_mask = scan_mask; + config->scan_timestamp = scan_timestamp; + + return 0; +} + +/** + * struct iio_demux_table - table describing demux memcpy ops + * @from: index to copy from + * @to: index to copy to + * @length: how many bytes to copy + * @l: list head used for management + */ +struct iio_demux_table { + unsigned int from; + unsigned int to; + unsigned int length; + struct list_head l; +}; + +static void iio_buffer_demux_free(struct iio_buffer *buffer) +{ + struct iio_demux_table *p, *q; + + list_for_each_entry_safe(p, q, &buffer->demux_list, l) { + list_del(&p->l); + kfree(p); + } +} + +static int iio_buffer_add_demux(struct iio_buffer *buffer, + struct iio_demux_table **p, unsigned int in_loc, + unsigned int out_loc, + unsigned int length) +{ + if (*p && (*p)->from + (*p)->length == in_loc && + (*p)->to + (*p)->length == out_loc) { + (*p)->length += length; + } else { + *p = kmalloc_obj(**p); + if (!(*p)) + return -ENOMEM; + (*p)->from = in_loc; + (*p)->to = out_loc; + (*p)->length = length; + list_add_tail(&(*p)->l, &buffer->demux_list); + } + + return 0; +} + +static int iio_buffer_update_demux(struct iio_dev *indio_dev, + struct iio_buffer *buffer) +{ + unsigned int masklength = iio_get_masklength(indio_dev); + int ret, in_ind = -1, out_ind, length; + unsigned int in_loc = 0, out_loc = 0; + struct iio_demux_table *p = NULL; + + /* Clear out any old demux */ + iio_buffer_demux_free(buffer); + kfree(buffer->demux_bounce); + buffer->demux_bounce = NULL; + + /* First work out which scan mode we will actually have */ + if (bitmap_equal(indio_dev->active_scan_mask, + buffer->scan_mask, masklength)) + return 0; + + /* Now we have the two masks, work from least sig and build up sizes */ + for_each_set_bit(out_ind, buffer->scan_mask, masklength) { + in_ind = find_next_bit(indio_dev->active_scan_mask, + masklength, in_ind + 1); + while (in_ind != out_ind) { + ret = iio_storage_bytes_for_si(indio_dev, in_ind); + if (ret < 0) + goto error_clear_mux_table; + + length = ret; + /* Make sure we are aligned */ + in_loc = roundup(in_loc, length) + length; + in_ind = find_next_bit(indio_dev->active_scan_mask, + masklength, in_ind + 1); + } + ret = iio_storage_bytes_for_si(indio_dev, in_ind); + if (ret < 0) + goto error_clear_mux_table; + + length = ret; + out_loc = roundup(out_loc, length); + in_loc = roundup(in_loc, length); + ret = iio_buffer_add_demux(buffer, &p, in_loc, out_loc, length); + if (ret) + goto error_clear_mux_table; + out_loc += length; + in_loc += length; + } + /* Relies on scan_timestamp being last */ + if (buffer->scan_timestamp) { + ret = iio_storage_bytes_for_timestamp(indio_dev); + if (ret < 0) + goto error_clear_mux_table; + + length = ret; + out_loc = roundup(out_loc, length); + in_loc = roundup(in_loc, length); + ret = iio_buffer_add_demux(buffer, &p, in_loc, out_loc, length); + if (ret) + goto error_clear_mux_table; + out_loc += length; + } + buffer->demux_bounce = kzalloc(out_loc, GFP_KERNEL); + if (!buffer->demux_bounce) { + ret = -ENOMEM; + goto error_clear_mux_table; + } + return 0; + +error_clear_mux_table: + iio_buffer_demux_free(buffer); + + return ret; +} + +static int iio_update_demux(struct iio_dev *indio_dev) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + struct iio_buffer *buffer; + int ret; + + list_for_each_entry(buffer, &iio_dev_opaque->buffer_list, buffer_list) { + ret = iio_buffer_update_demux(indio_dev, buffer); + if (ret < 0) + goto error_clear_mux_table; + } + return 0; + +error_clear_mux_table: + list_for_each_entry(buffer, &iio_dev_opaque->buffer_list, buffer_list) + iio_buffer_demux_free(buffer); + + return ret; +} + +static int iio_enable_buffers(struct iio_dev *indio_dev, + struct iio_device_config *config) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + struct iio_buffer *buffer, *tmp = NULL; + int ret; + + indio_dev->active_scan_mask = config->scan_mask; + ACCESS_PRIVATE(indio_dev, scan_timestamp) = config->scan_timestamp; + indio_dev->scan_bytes = config->scan_bytes; + ACCESS_PRIVATE(indio_dev, scan_timestamp_offset) = config->scan_timestamp_offset; + iio_dev_opaque->currentmode = config->mode; + + iio_update_demux(indio_dev); + + /* Wind up again */ + if (indio_dev->setup_ops->preenable) { + ret = indio_dev->setup_ops->preenable(indio_dev); + if (ret) { + dev_dbg(&indio_dev->dev, + "Buffer not started: buffer preenable failed (%d)\n", ret); + goto err_undo_config; + } + } + + if (indio_dev->info->update_scan_mode) { + ret = indio_dev->info + ->update_scan_mode(indio_dev, + indio_dev->active_scan_mask); + if (ret < 0) { + dev_dbg(&indio_dev->dev, + "Buffer not started: update scan mode failed (%d)\n", + ret); + goto err_run_postdisable; + } + } + + if (indio_dev->info->hwfifo_set_watermark) + indio_dev->info->hwfifo_set_watermark(indio_dev, + config->watermark); + + list_for_each_entry(buffer, &iio_dev_opaque->buffer_list, buffer_list) { + ret = iio_buffer_enable(buffer, indio_dev); + if (ret) { + tmp = buffer; + goto err_disable_buffers; + } + } + + if (iio_dev_opaque->currentmode == INDIO_BUFFER_TRIGGERED) { + ret = iio_trigger_attach_poll_func(indio_dev->trig, + indio_dev->pollfunc); + if (ret) + goto err_disable_buffers; + } + + if (indio_dev->setup_ops->postenable) { + ret = indio_dev->setup_ops->postenable(indio_dev); + if (ret) { + dev_dbg(&indio_dev->dev, + "Buffer not started: postenable failed (%d)\n", ret); + goto err_detach_pollfunc; + } + } + + return 0; + +err_detach_pollfunc: + if (iio_dev_opaque->currentmode == INDIO_BUFFER_TRIGGERED) { + iio_trigger_detach_poll_func(indio_dev->trig, + indio_dev->pollfunc); + } +err_disable_buffers: + buffer = list_prepare_entry(tmp, &iio_dev_opaque->buffer_list, buffer_list); + list_for_each_entry_continue_reverse(buffer, &iio_dev_opaque->buffer_list, + buffer_list) + iio_buffer_disable(buffer, indio_dev); +err_run_postdisable: + if (indio_dev->setup_ops->postdisable) + indio_dev->setup_ops->postdisable(indio_dev); +err_undo_config: + iio_dev_opaque->currentmode = INDIO_DIRECT_MODE; + indio_dev->active_scan_mask = NULL; + + return ret; +} + +static int iio_disable_buffers(struct iio_dev *indio_dev) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + struct iio_buffer *buffer; + int ret = 0; + int ret2; + + /* Wind down existing buffers - iff there are any */ + if (list_empty(&iio_dev_opaque->buffer_list)) + return 0; + + /* + * If things go wrong at some step in disable we still need to continue + * to perform the other steps, otherwise we leave the device in a + * inconsistent state. We return the error code for the first error we + * encountered. + */ + + if (indio_dev->setup_ops->predisable) { + ret2 = indio_dev->setup_ops->predisable(indio_dev); + if (ret2 && !ret) + ret = ret2; + } + + if (iio_dev_opaque->currentmode == INDIO_BUFFER_TRIGGERED) { + iio_trigger_detach_poll_func(indio_dev->trig, + indio_dev->pollfunc); + } + + list_for_each_entry(buffer, &iio_dev_opaque->buffer_list, buffer_list) { + ret2 = iio_buffer_disable(buffer, indio_dev); + if (ret2 && !ret) + ret = ret2; + } + + if (indio_dev->setup_ops->postdisable) { + ret2 = indio_dev->setup_ops->postdisable(indio_dev); + if (ret2 && !ret) + ret = ret2; + } + + iio_free_scan_mask(indio_dev, indio_dev->active_scan_mask); + indio_dev->active_scan_mask = NULL; + iio_dev_opaque->currentmode = INDIO_DIRECT_MODE; + + return ret; +} + +static int __iio_update_buffers(struct iio_dev *indio_dev, + struct iio_buffer *insert_buffer, + struct iio_buffer *remove_buffer) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + struct iio_device_config new_config; + int ret; + + ret = iio_verify_update(indio_dev, insert_buffer, remove_buffer, + &new_config); + if (ret) + return ret; + + if (insert_buffer) { + ret = iio_buffer_request_update(indio_dev, insert_buffer); + if (ret) + goto err_free_config; + } + + ret = iio_disable_buffers(indio_dev); + if (ret) + goto err_deactivate_all; + + if (remove_buffer) + iio_buffer_deactivate(remove_buffer); + if (insert_buffer) + iio_buffer_activate(indio_dev, insert_buffer); + + /* If no buffers in list, we are done */ + if (list_empty(&iio_dev_opaque->buffer_list)) + return 0; + + ret = iio_enable_buffers(indio_dev, &new_config); + if (ret) + goto err_deactivate_all; + + return 0; + +err_deactivate_all: + /* + * We've already verified that the config is valid earlier. If things go + * wrong in either enable or disable the most likely reason is an IO + * error from the device. In this case there is no good recovery + * strategy. Just make sure to disable everything and leave the device + * in a sane state. With a bit of luck the device might come back to + * life again later and userspace can try again. + */ + iio_buffer_deactivate_all(indio_dev); + +err_free_config: + iio_free_scan_mask(indio_dev, new_config.scan_mask); + return ret; +} + +int iio_update_buffers(struct iio_dev *indio_dev, + struct iio_buffer *insert_buffer, + struct iio_buffer *remove_buffer) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + + if (insert_buffer == remove_buffer) + return 0; + + if (insert_buffer && + insert_buffer->direction == IIO_BUFFER_DIRECTION_OUT) + return -EINVAL; + + guard(mutex)(&iio_dev_opaque->info_exist_lock); + guard(mutex)(&iio_dev_opaque->mlock); + + if (insert_buffer && iio_buffer_is_active(insert_buffer)) + insert_buffer = NULL; + + if (remove_buffer && !iio_buffer_is_active(remove_buffer)) + remove_buffer = NULL; + + if (!insert_buffer && !remove_buffer) + return 0; + + if (!indio_dev->info) + return -ENODEV; + + return __iio_update_buffers(indio_dev, insert_buffer, remove_buffer); +} +EXPORT_SYMBOL_GPL(iio_update_buffers); + +void iio_disable_all_buffers(struct iio_dev *indio_dev) +{ + iio_disable_buffers(indio_dev); + iio_buffer_deactivate_all(indio_dev); +} + +static ssize_t enable_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + int ret; + bool requested_state; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + struct iio_buffer *buffer = to_iio_dev_attr(attr)->buffer; + bool inlist; + + ret = kstrtobool(buf, &requested_state); + if (ret < 0) + return ret; + + guard(mutex)(&iio_dev_opaque->mlock); + + /* Find out if it is in the list */ + inlist = iio_buffer_is_active(buffer); + /* Already in desired state */ + if (inlist == requested_state) + return len; + + if (requested_state) + ret = __iio_update_buffers(indio_dev, buffer, NULL); + else + ret = __iio_update_buffers(indio_dev, NULL, buffer); + if (ret) + return ret; + + return len; +} + +static ssize_t watermark_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct iio_buffer *buffer = to_iio_dev_attr(attr)->buffer; + + return sysfs_emit(buf, "%u\n", buffer->watermark); +} + +static ssize_t watermark_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + struct iio_buffer *buffer = to_iio_dev_attr(attr)->buffer; + unsigned int val; + int ret; + + ret = kstrtouint(buf, 10, &val); + if (ret) + return ret; + if (!val) + return -EINVAL; + + guard(mutex)(&iio_dev_opaque->mlock); + + if (val > buffer->length) + return -EINVAL; + + if (iio_buffer_is_active(buffer)) + return -EBUSY; + + buffer->watermark = val; + + return len; +} + +static ssize_t data_available_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_buffer *buffer = to_iio_dev_attr(attr)->buffer; + + return sysfs_emit(buf, "%zu\n", iio_buffer_data_available(buffer)); +} + +static ssize_t direction_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_buffer *buffer = to_iio_dev_attr(attr)->buffer; + + switch (buffer->direction) { + case IIO_BUFFER_DIRECTION_IN: + return sysfs_emit(buf, "in\n"); + case IIO_BUFFER_DIRECTION_OUT: + return sysfs_emit(buf, "out\n"); + default: + return -EINVAL; + } +} + +static DEVICE_ATTR_RW(length); +static struct device_attribute dev_attr_length_ro = __ATTR_RO(length); +static DEVICE_ATTR_RW(enable); +static DEVICE_ATTR_RW(watermark); +static struct device_attribute dev_attr_watermark_ro = __ATTR_RO(watermark); +static DEVICE_ATTR_RO(data_available); +static DEVICE_ATTR_RO(direction); + +/* + * When adding new attributes here, put the at the end, at least until + * the code that handles the length/length_ro & watermark/watermark_ro + * assignments gets cleaned up. Otherwise these can create some weird + * duplicate attributes errors under some setups. + */ +static struct attribute *iio_buffer_attrs[] = { + &dev_attr_length.attr, + &dev_attr_enable.attr, + &dev_attr_watermark.attr, + &dev_attr_data_available.attr, + &dev_attr_direction.attr, +}; + +#define to_dev_attr(_attr) container_of(_attr, struct device_attribute, attr) + +static struct attribute *iio_buffer_wrap_attr(struct iio_buffer *buffer, + struct attribute *attr) +{ + struct device_attribute *dattr = to_dev_attr(attr); + struct iio_dev_attr *iio_attr; + + iio_attr = kzalloc_obj(*iio_attr); + if (!iio_attr) + return NULL; + + iio_attr->buffer = buffer; + memcpy(&iio_attr->dev_attr, dattr, sizeof(iio_attr->dev_attr)); + iio_attr->dev_attr.attr.name = kstrdup_const(attr->name, GFP_KERNEL); + if (!iio_attr->dev_attr.attr.name) { + kfree(iio_attr); + return NULL; + } + + sysfs_attr_init(&iio_attr->dev_attr.attr); + + list_add(&iio_attr->l, &buffer->buffer_attr_list); + + return &iio_attr->dev_attr.attr; +} + +static int iio_buffer_register_legacy_sysfs_groups(struct iio_dev *indio_dev, + struct attribute **buffer_attrs, + int buffer_attrcount, + int scan_el_attrcount) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + struct attribute_group *group; + struct attribute **attrs; + int ret; + + attrs = kzalloc_objs(*attrs, buffer_attrcount + 1); + if (!attrs) + return -ENOMEM; + + memcpy(attrs, buffer_attrs, buffer_attrcount * sizeof(*attrs)); + + group = &iio_dev_opaque->legacy_buffer_group; + group->attrs = attrs; + group->name = "buffer"; + + ret = iio_device_register_sysfs_group(indio_dev, group); + if (ret) + goto error_free_buffer_attrs; + + attrs = kzalloc_objs(*attrs, scan_el_attrcount + 1); + if (!attrs) { + ret = -ENOMEM; + goto error_free_buffer_attrs; + } + + memcpy(attrs, &buffer_attrs[buffer_attrcount], + scan_el_attrcount * sizeof(*attrs)); + + group = &iio_dev_opaque->legacy_scan_el_group; + group->attrs = attrs; + group->name = "scan_elements"; + + ret = iio_device_register_sysfs_group(indio_dev, group); + if (ret) + goto error_free_scan_el_attrs; + + return 0; + +error_free_scan_el_attrs: + kfree(iio_dev_opaque->legacy_scan_el_group.attrs); +error_free_buffer_attrs: + kfree(iio_dev_opaque->legacy_buffer_group.attrs); + + return ret; +} + +static void iio_buffer_unregister_legacy_sysfs_groups(struct iio_dev *indio_dev) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + + kfree(iio_dev_opaque->legacy_buffer_group.attrs); + kfree(iio_dev_opaque->legacy_scan_el_group.attrs); +} + +static void iio_buffer_dmabuf_release(struct kref *ref) +{ + struct iio_dmabuf_priv *priv = container_of(ref, struct iio_dmabuf_priv, ref); + struct dma_buf_attachment *attach = priv->attach; + struct iio_buffer *buffer = priv->buffer; + struct dma_buf *dmabuf = attach->dmabuf; + + dma_buf_unmap_attachment_unlocked(attach, priv->sgt, priv->dir); + + buffer->access->detach_dmabuf(buffer, priv->block); + + dma_buf_detach(attach->dmabuf, attach); + dma_buf_put(dmabuf); + kfree(priv); +} + +static void iio_buffer_dmabuf_get(struct dma_buf_attachment *attach) +{ + struct iio_dmabuf_priv *priv = attach->importer_priv; + + kref_get(&priv->ref); +} + +static void iio_buffer_dmabuf_put(struct dma_buf_attachment *attach) +{ + struct iio_dmabuf_priv *priv = attach->importer_priv; + + kref_put(&priv->ref, iio_buffer_dmabuf_release); +} + +static int iio_buffer_chrdev_release(struct inode *inode, struct file *filep) +{ + struct iio_dev_buffer_pair *ib = filep->private_data; + struct iio_dev *indio_dev = ib->indio_dev; + struct iio_buffer *buffer = ib->buffer; + struct iio_dmabuf_priv *priv, *tmp; + + wake_up(&buffer->pollq); + + /* + * The mutex must be unlocked before iio_device_put(), which might drop the + * last reference and free the buffer. + */ + scoped_guard(mutex, &buffer->dmabufs_mutex) { + /* Close all attached DMABUFs */ + list_for_each_entry_safe(priv, tmp, &buffer->dmabufs, entry) { + list_del_init(&priv->entry); + iio_buffer_dmabuf_put(priv->attach); + } + } + + kfree(ib); + clear_bit(IIO_BUSY_BIT_POS, &buffer->flags); + iio_device_put(indio_dev); + + return 0; +} + +static int iio_dma_resv_lock(struct dma_buf *dmabuf, bool nonblock) +{ + if (!nonblock) + return dma_resv_lock_interruptible(dmabuf->resv, NULL); + + if (!dma_resv_trylock(dmabuf->resv)) + return -EBUSY; + + return 0; +} + +static struct device *iio_buffer_get_dma_dev(const struct iio_dev *indio_dev, + struct iio_buffer *buffer) +{ + if (buffer->access->get_dma_dev) + return buffer->access->get_dma_dev(buffer); + + return indio_dev->dev.parent; +} + +static struct dma_buf_attachment * +iio_buffer_find_attachment(struct iio_dev_buffer_pair *ib, + struct dma_buf *dmabuf, bool nonblock) +{ + struct iio_buffer *buffer = ib->buffer; + struct device *dma_dev = iio_buffer_get_dma_dev(ib->indio_dev, buffer); + struct dma_buf_attachment *attach = NULL; + struct iio_dmabuf_priv *priv; + + guard(mutex)(&buffer->dmabufs_mutex); + + list_for_each_entry(priv, &buffer->dmabufs, entry) { + if (priv->attach->dev == dma_dev + && priv->attach->dmabuf == dmabuf) { + attach = priv->attach; + break; + } + } + + if (attach) + iio_buffer_dmabuf_get(attach); + + return attach ?: ERR_PTR(-EPERM); +} + +static int iio_buffer_attach_dmabuf(struct iio_dev_buffer_pair *ib, + int __user *user_fd, bool nonblock) +{ + struct iio_dev *indio_dev = ib->indio_dev; + struct iio_buffer *buffer = ib->buffer; + struct device *dma_dev = iio_buffer_get_dma_dev(indio_dev, buffer); + struct dma_buf_attachment *attach; + struct iio_dmabuf_priv *priv, *each; + struct dma_buf *dmabuf; + int err, fd; + + if (!buffer->access->attach_dmabuf + || !buffer->access->detach_dmabuf + || !buffer->access->enqueue_dmabuf) + return -EPERM; + + if (copy_from_user(&fd, user_fd, sizeof(fd))) + return -EFAULT; + + priv = kzalloc_obj(*priv); + if (!priv) + return -ENOMEM; + + priv->context = dma_fence_context_alloc(1); + + dmabuf = dma_buf_get(fd); + if (IS_ERR(dmabuf)) { + err = PTR_ERR(dmabuf); + goto err_free_priv; + } + + attach = dma_buf_attach(dmabuf, dma_dev); + if (IS_ERR(attach)) { + err = PTR_ERR(attach); + goto err_dmabuf_put; + } + + err = iio_dma_resv_lock(dmabuf, nonblock); + if (err) + goto err_dmabuf_detach; + + priv->dir = buffer->direction == IIO_BUFFER_DIRECTION_IN + ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + + priv->sgt = dma_buf_map_attachment(attach, priv->dir); + if (IS_ERR(priv->sgt)) { + err = PTR_ERR(priv->sgt); + dev_err(&indio_dev->dev, "Unable to map attachment: %d\n", err); + goto err_resv_unlock; + } + + kref_init(&priv->ref); + priv->buffer = buffer; + priv->attach = attach; + attach->importer_priv = priv; + + priv->block = buffer->access->attach_dmabuf(buffer, attach); + if (IS_ERR(priv->block)) { + err = PTR_ERR(priv->block); + goto err_dmabuf_unmap_attachment; + } + + dma_resv_unlock(dmabuf->resv); + + mutex_lock(&buffer->dmabufs_mutex); + + /* + * Check whether we already have an attachment for this driver/DMABUF + * combo. If we do, refuse to attach. + */ + list_for_each_entry(each, &buffer->dmabufs, entry) { + if (each->attach->dev == dma_dev + && each->attach->dmabuf == dmabuf) { + /* + * We unlocked the reservation object, so going through + * the cleanup code would mean re-locking it first. + * At this stage it is simpler to free the attachment + * using iio_buffer_dma_put(). + */ + mutex_unlock(&buffer->dmabufs_mutex); + iio_buffer_dmabuf_put(attach); + return -EBUSY; + } + } + + /* Otherwise, add the new attachment to our dmabufs list. */ + list_add(&priv->entry, &buffer->dmabufs); + mutex_unlock(&buffer->dmabufs_mutex); + + return 0; + +err_dmabuf_unmap_attachment: + dma_buf_unmap_attachment(attach, priv->sgt, priv->dir); +err_resv_unlock: + dma_resv_unlock(dmabuf->resv); +err_dmabuf_detach: + dma_buf_detach(dmabuf, attach); +err_dmabuf_put: + dma_buf_put(dmabuf); +err_free_priv: + kfree(priv); + + return err; +} + +static int iio_buffer_detach_dmabuf(struct iio_dev_buffer_pair *ib, + int __user *user_req, bool nonblock) +{ + struct iio_buffer *buffer = ib->buffer; + struct iio_dev *indio_dev = ib->indio_dev; + struct device *dma_dev = iio_buffer_get_dma_dev(indio_dev, buffer); + struct iio_dmabuf_priv *priv; + struct dma_buf *dmabuf; + int dmabuf_fd, ret = -EPERM; + + if (copy_from_user(&dmabuf_fd, user_req, sizeof(dmabuf_fd))) + return -EFAULT; + + dmabuf = dma_buf_get(dmabuf_fd); + if (IS_ERR(dmabuf)) + return PTR_ERR(dmabuf); + + guard(mutex)(&buffer->dmabufs_mutex); + + list_for_each_entry(priv, &buffer->dmabufs, entry) { + if (priv->attach->dev == dma_dev + && priv->attach->dmabuf == dmabuf) { + list_del(&priv->entry); + + /* Unref the reference from iio_buffer_attach_dmabuf() */ + iio_buffer_dmabuf_put(priv->attach); + ret = 0; + break; + } + } + + dma_buf_put(dmabuf); + + return ret; +} + +static const char * +iio_buffer_dma_fence_get_driver_name(struct dma_fence *fence) +{ + return "iio"; +} + +static const struct dma_fence_ops iio_buffer_dma_fence_ops = { + .get_driver_name = iio_buffer_dma_fence_get_driver_name, + .get_timeline_name = iio_buffer_dma_fence_get_driver_name, +}; + +static int iio_buffer_enqueue_dmabuf(struct iio_dev_buffer_pair *ib, + struct iio_dmabuf __user *iio_dmabuf_req, + bool nonblock) +{ + struct iio_buffer *buffer = ib->buffer; + struct iio_dmabuf iio_dmabuf; + struct dma_buf_attachment *attach; + struct iio_dmabuf_priv *priv; + struct iio_dma_fence *fence; + struct dma_buf *dmabuf; + unsigned long timeout; + bool cookie, cyclic, dma_to_ram; + long retl; + u32 seqno; + int ret; + + if (copy_from_user(&iio_dmabuf, iio_dmabuf_req, sizeof(iio_dmabuf))) + return -EFAULT; + + if (iio_dmabuf.flags & ~IIO_BUFFER_DMABUF_SUPPORTED_FLAGS) + return -EINVAL; + + cyclic = iio_dmabuf.flags & IIO_BUFFER_DMABUF_CYCLIC; + + /* Cyclic flag is only supported on output buffers */ + if (cyclic && buffer->direction != IIO_BUFFER_DIRECTION_OUT) + return -EINVAL; + + dmabuf = dma_buf_get(iio_dmabuf.fd); + if (IS_ERR(dmabuf)) + return PTR_ERR(dmabuf); + + if (!iio_dmabuf.bytes_used || iio_dmabuf.bytes_used > dmabuf->size) { + ret = -EINVAL; + goto err_dmabuf_put; + } + + attach = iio_buffer_find_attachment(ib, dmabuf, nonblock); + if (IS_ERR(attach)) { + ret = PTR_ERR(attach); + goto err_dmabuf_put; + } + + priv = attach->importer_priv; + + fence = kmalloc_obj(*fence); + if (!fence) { + ret = -ENOMEM; + goto err_attachment_put; + } + + spin_lock_init(&fence->lock); + + fence->priv = priv; + + seqno = atomic_add_return(1, &priv->seqno); + + /* + * The transfers are guaranteed to be processed in the order they are + * enqueued, so we can use a simple incrementing sequence number for + * the dma_fence. + */ + dma_fence_init(&fence->base, &iio_buffer_dma_fence_ops, + &fence->lock, priv->context, seqno); + + ret = iio_dma_resv_lock(dmabuf, nonblock); + if (ret) + goto err_fence_put; + + timeout = nonblock ? 0 : msecs_to_jiffies(DMABUF_ENQUEUE_TIMEOUT_MS); + dma_to_ram = buffer->direction == IIO_BUFFER_DIRECTION_IN; + + /* Make sure we don't have writers */ + retl = dma_resv_wait_timeout(dmabuf->resv, + dma_resv_usage_rw(dma_to_ram), + true, timeout); + if (retl == 0) + retl = -EBUSY; + if (retl < 0) { + ret = (int)retl; + goto err_resv_unlock; + } + + if (buffer->access->lock_queue) + buffer->access->lock_queue(buffer); + + ret = dma_resv_reserve_fences(dmabuf->resv, 1); + if (ret) + goto err_queue_unlock; + + dma_resv_add_fence(dmabuf->resv, &fence->base, + dma_to_ram ? DMA_RESV_USAGE_WRITE : DMA_RESV_USAGE_READ); + dma_fence_put(&fence->base); + dma_resv_unlock(dmabuf->resv); + + cookie = dma_fence_begin_signalling(); + + ret = buffer->access->enqueue_dmabuf(buffer, priv->block, &fence->base, + priv->sgt, iio_dmabuf.bytes_used, + cyclic); + if (ret) { + /* + * DMABUF enqueue failed, but we already added the fence. + * Signal the error through the fence completion mechanism. + */ + iio_buffer_signal_dmabuf_done(&fence->base, ret); + } + + if (buffer->access->unlock_queue) + buffer->access->unlock_queue(buffer); + + dma_fence_end_signalling(cookie); + dma_buf_put(dmabuf); + + return ret; + +err_queue_unlock: + if (buffer->access->unlock_queue) + buffer->access->unlock_queue(buffer); +err_resv_unlock: + dma_resv_unlock(dmabuf->resv); +err_fence_put: + dma_fence_put(&fence->base); +err_attachment_put: + iio_buffer_dmabuf_put(attach); +err_dmabuf_put: + dma_buf_put(dmabuf); + + return ret; +} + +static void iio_buffer_cleanup(struct work_struct *work) +{ + struct iio_dma_fence *fence = + container_of(work, struct iio_dma_fence, work); + struct iio_dmabuf_priv *priv = fence->priv; + struct dma_buf_attachment *attach = priv->attach; + + dma_fence_put(&fence->base); + iio_buffer_dmabuf_put(attach); +} + +void iio_buffer_signal_dmabuf_done(struct dma_fence *fence, int ret) +{ + struct iio_dma_fence *iio_fence = + container_of(fence, struct iio_dma_fence, base); + bool cookie = dma_fence_begin_signalling(); + + /* + * Get a reference to the fence, so that it's not freed as soon as + * it's signaled. + */ + dma_fence_get(fence); + + fence->error = ret; + dma_fence_signal(fence); + dma_fence_end_signalling(cookie); + + /* + * The fence will be unref'd in iio_buffer_cleanup. + * It can't be done here, as the unref functions might try to lock the + * resv object, which can deadlock. + */ + INIT_WORK(&iio_fence->work, iio_buffer_cleanup); + schedule_work(&iio_fence->work); +} +EXPORT_SYMBOL_GPL(iio_buffer_signal_dmabuf_done); + +static long iio_buffer_chrdev_ioctl(struct file *filp, + unsigned int cmd, unsigned long arg) +{ + struct iio_dev_buffer_pair *ib = filp->private_data; + void __user *_arg = (void __user *)arg; + bool nonblock = filp->f_flags & O_NONBLOCK; + + switch (cmd) { + case IIO_BUFFER_DMABUF_ATTACH_IOCTL: + return iio_buffer_attach_dmabuf(ib, _arg, nonblock); + case IIO_BUFFER_DMABUF_DETACH_IOCTL: + return iio_buffer_detach_dmabuf(ib, _arg, nonblock); + case IIO_BUFFER_DMABUF_ENQUEUE_IOCTL: + return iio_buffer_enqueue_dmabuf(ib, _arg, nonblock); + default: + return -EINVAL; + } +} + +static const struct file_operations iio_buffer_chrdev_fileops = { + .owner = THIS_MODULE, + .llseek = noop_llseek, + .read = iio_buffer_read, + .write = iio_buffer_write, + .unlocked_ioctl = iio_buffer_chrdev_ioctl, + .compat_ioctl = compat_ptr_ioctl, + .poll = iio_buffer_poll, + .release = iio_buffer_chrdev_release, +}; + +static long iio_device_buffer_getfd(struct iio_dev *indio_dev, unsigned long arg) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + int __user *ival = (int __user *)arg; + struct iio_dev_buffer_pair *ib; + struct iio_buffer *buffer; + int fd, idx, ret; + + if (copy_from_user(&idx, ival, sizeof(idx))) + return -EFAULT; + + if (idx >= iio_dev_opaque->attached_buffers_cnt) + return -ENODEV; + + iio_device_get(indio_dev); + + buffer = iio_dev_opaque->attached_buffers[idx]; + + if (test_and_set_bit(IIO_BUSY_BIT_POS, &buffer->flags)) { + ret = -EBUSY; + goto error_iio_dev_put; + } + + ib = kzalloc_obj(*ib); + if (!ib) { + ret = -ENOMEM; + goto error_clear_busy_bit; + } + + ib->indio_dev = indio_dev; + ib->buffer = buffer; + + fd = anon_inode_getfd("iio:buffer", &iio_buffer_chrdev_fileops, + ib, O_RDWR | O_CLOEXEC); + if (fd < 0) { + ret = fd; + goto error_free_ib; + } + + if (copy_to_user(ival, &fd, sizeof(fd))) { + /* + * "Leak" the fd, as there's not much we can do about this + * anyway. 'fd' might have been closed already, as + * anon_inode_getfd() called fd_install() on it, which made + * it reachable by userland. + * + * Instead of allowing a malicious user to play tricks with + * us, rely on the process exit path to do any necessary + * cleanup, as in releasing the file, if still needed. + */ + return -EFAULT; + } + + return 0; + +error_free_ib: + kfree(ib); +error_clear_busy_bit: + clear_bit(IIO_BUSY_BIT_POS, &buffer->flags); +error_iio_dev_put: + iio_device_put(indio_dev); + return ret; +} + +static long iio_device_buffer_ioctl(struct iio_dev *indio_dev, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case IIO_BUFFER_GET_FD_IOCTL: + return iio_device_buffer_getfd(indio_dev, arg); + default: + return IIO_IOCTL_UNHANDLED; + } +} + +static int iio_channel_validate_scan_type(struct device *dev, int ch, + const struct iio_scan_type *scan_type) +{ + /* Verify that sample bits fit into storage */ + if (scan_type->storagebits < scan_type->realbits + scan_type->shift) { + dev_err(dev, + "Channel %d storagebits (%d) < shifted realbits (%d + %d)\n", + ch, scan_type->storagebits, + scan_type->realbits, + scan_type->shift); + return -EINVAL; + } + + return 0; +} + +static int __iio_buffer_alloc_sysfs_and_mask(struct iio_buffer *buffer, + struct iio_dev *indio_dev, + int index) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + unsigned int masklength = iio_get_masklength(indio_dev); + struct iio_dev_attr *p; + const struct iio_dev_attr *id_attr; + struct attribute **attr; + int ret, i, attrn, scan_el_attrcount, buffer_attrcount; + const struct iio_chan_spec *channels; + + buffer_attrcount = 0; + if (buffer->attrs) { + while (buffer->attrs[buffer_attrcount]) + buffer_attrcount++; + } + buffer_attrcount += ARRAY_SIZE(iio_buffer_attrs); + + scan_el_attrcount = 0; + INIT_LIST_HEAD(&buffer->buffer_attr_list); + channels = indio_dev->channels; + if (channels) { + /* new magic */ + for (i = 0; i < indio_dev->num_channels; i++) { + const struct iio_scan_type *scan_type; + + if (channels[i].scan_index < 0) + continue; + + if (channels[i].has_ext_scan_type) { + int j; + + /* + * get_current_scan_type is required when using + * extended scan types. + */ + if (!indio_dev->info->get_current_scan_type) { + ret = -EINVAL; + goto error_cleanup_dynamic; + } + + for (j = 0; j < channels[i].num_ext_scan_type; j++) { + scan_type = &channels[i].ext_scan_type[j]; + + ret = iio_channel_validate_scan_type( + &indio_dev->dev, i, scan_type); + if (ret) + goto error_cleanup_dynamic; + } + } else { + scan_type = &channels[i].scan_type; + + ret = iio_channel_validate_scan_type( + &indio_dev->dev, i, scan_type); + if (ret) + goto error_cleanup_dynamic; + } + + ret = iio_buffer_add_channel_sysfs(indio_dev, buffer, + &channels[i]); + if (ret < 0) + goto error_cleanup_dynamic; + scan_el_attrcount += ret; + if (channels[i].type == IIO_TIMESTAMP) + iio_dev_opaque->scan_index_timestamp = + channels[i].scan_index; + } + if (masklength && !buffer->scan_mask) { + buffer->scan_mask = bitmap_zalloc(masklength, + GFP_KERNEL); + if (!buffer->scan_mask) { + ret = -ENOMEM; + goto error_cleanup_dynamic; + } + } + } + + attrn = buffer_attrcount + scan_el_attrcount; + attr = kzalloc_objs(*attr, attrn + 1); + if (!attr) { + ret = -ENOMEM; + goto error_free_scan_mask; + } + + memcpy(attr, iio_buffer_attrs, sizeof(iio_buffer_attrs)); + if (!buffer->access->set_length) + attr[0] = &dev_attr_length_ro.attr; + + if (buffer->access->flags & INDIO_BUFFER_FLAG_FIXED_WATERMARK) + attr[2] = &dev_attr_watermark_ro.attr; + + if (buffer->attrs) + for (i = 0, id_attr = buffer->attrs[i]; + (id_attr = buffer->attrs[i]); i++) + attr[ARRAY_SIZE(iio_buffer_attrs) + i] = + (struct attribute *)&id_attr->dev_attr.attr; + + buffer->buffer_group.attrs = attr; + + for (i = 0; i < buffer_attrcount; i++) { + struct attribute *wrapped; + + wrapped = iio_buffer_wrap_attr(buffer, attr[i]); + if (!wrapped) { + ret = -ENOMEM; + goto error_free_buffer_attrs; + } + attr[i] = wrapped; + } + + attrn = 0; + list_for_each_entry(p, &buffer->buffer_attr_list, l) + attr[attrn++] = &p->dev_attr.attr; + + buffer->buffer_group.name = kasprintf(GFP_KERNEL, "buffer%d", index); + if (!buffer->buffer_group.name) { + ret = -ENOMEM; + goto error_free_buffer_attrs; + } + + ret = iio_device_register_sysfs_group(indio_dev, &buffer->buffer_group); + if (ret) + goto error_free_buffer_attr_group_name; + + /* we only need to register the legacy groups for the first buffer */ + if (index > 0) + return 0; + + ret = iio_buffer_register_legacy_sysfs_groups(indio_dev, attr, + buffer_attrcount, + scan_el_attrcount); + if (ret) + goto error_free_buffer_attr_group_name; + + return 0; + +error_free_buffer_attr_group_name: + kfree(buffer->buffer_group.name); +error_free_buffer_attrs: + kfree(buffer->buffer_group.attrs); +error_free_scan_mask: + bitmap_free(buffer->scan_mask); +error_cleanup_dynamic: + iio_free_chan_devattr_list(&buffer->buffer_attr_list); + + return ret; +} + +static void __iio_buffer_free_sysfs_and_mask(struct iio_buffer *buffer, + struct iio_dev *indio_dev, + int index) +{ + if (index == 0) + iio_buffer_unregister_legacy_sysfs_groups(indio_dev); + bitmap_free(buffer->scan_mask); + kfree(buffer->buffer_group.name); + kfree(buffer->buffer_group.attrs); + iio_free_chan_devattr_list(&buffer->buffer_attr_list); +} + +int iio_buffers_alloc_sysfs_and_mask(struct iio_dev *indio_dev) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + const struct iio_chan_spec *channels; + struct iio_buffer *buffer; + int ret, i, idx; + size_t sz; + + channels = indio_dev->channels; + if (channels) { + int ml = 0; + + for (i = 0; i < indio_dev->num_channels; i++) + ml = max(ml, channels[i].scan_index + 1); + ACCESS_PRIVATE(indio_dev, masklength) = ml; + } + + if (!iio_dev_opaque->attached_buffers_cnt) + return 0; + + for (idx = 0; idx < iio_dev_opaque->attached_buffers_cnt; idx++) { + buffer = iio_dev_opaque->attached_buffers[idx]; + ret = __iio_buffer_alloc_sysfs_and_mask(buffer, indio_dev, idx); + if (ret) + goto error_unwind_sysfs_and_mask; + } + + sz = sizeof(*iio_dev_opaque->buffer_ioctl_handler); + iio_dev_opaque->buffer_ioctl_handler = kzalloc(sz, GFP_KERNEL); + if (!iio_dev_opaque->buffer_ioctl_handler) { + ret = -ENOMEM; + goto error_unwind_sysfs_and_mask; + } + + iio_dev_opaque->buffer_ioctl_handler->ioctl = iio_device_buffer_ioctl; + iio_device_ioctl_handler_register(indio_dev, + iio_dev_opaque->buffer_ioctl_handler); + + return 0; + +error_unwind_sysfs_and_mask: + while (idx--) { + buffer = iio_dev_opaque->attached_buffers[idx]; + __iio_buffer_free_sysfs_and_mask(buffer, indio_dev, idx); + } + return ret; +} + +void iio_buffers_free_sysfs_and_mask(struct iio_dev *indio_dev) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + struct iio_buffer *buffer; + int i; + + if (!iio_dev_opaque->attached_buffers_cnt) + return; + + iio_device_ioctl_handler_unregister(iio_dev_opaque->buffer_ioctl_handler); + kfree(iio_dev_opaque->buffer_ioctl_handler); + + for (i = iio_dev_opaque->attached_buffers_cnt - 1; i >= 0; i--) { + buffer = iio_dev_opaque->attached_buffers[i]; + __iio_buffer_free_sysfs_and_mask(buffer, indio_dev, i); + } +} + +/** + * iio_validate_scan_mask_onehot() - Validates that exactly one channel is selected + * @indio_dev: the iio device + * @mask: scan mask to be checked + * + * Return true if exactly one bit is set in the scan mask, false otherwise. It + * can be used for devices where only one channel can be active for sampling at + * a time. + */ +bool iio_validate_scan_mask_onehot(struct iio_dev *indio_dev, + const unsigned long *mask) +{ + return bitmap_weight(mask, iio_get_masklength(indio_dev)) == 1; +} +EXPORT_SYMBOL_GPL(iio_validate_scan_mask_onehot); + +static const void *iio_demux(struct iio_buffer *buffer, + const void *datain) +{ + struct iio_demux_table *t; + + if (list_empty(&buffer->demux_list)) + return datain; + list_for_each_entry(t, &buffer->demux_list, l) + memcpy(buffer->demux_bounce + t->to, + datain + t->from, t->length); + + return buffer->demux_bounce; +} + +static int iio_push_to_buffer(struct iio_buffer *buffer, const void *data) +{ + const void *dataout = iio_demux(buffer, data); + int ret; + + ret = buffer->access->store_to(buffer, dataout); + if (ret) + return ret; + + /* + * We can't just test for watermark to decide if we wake the poll queue + * because read may request less samples than the watermark. + */ + wake_up_interruptible_poll(&buffer->pollq, EPOLLIN | EPOLLRDNORM); + return 0; +} + +/** + * iio_push_to_buffers() - push to a registered buffer. + * @indio_dev: iio_dev structure for device. + * @data: Full scan. + * + * Context: Any context. + * Return: 0 on success, negative error code on failure. + */ +int iio_push_to_buffers(struct iio_dev *indio_dev, const void *data) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + int ret; + struct iio_buffer *buf; + + list_for_each_entry(buf, &iio_dev_opaque->buffer_list, buffer_list) { + ret = iio_push_to_buffer(buf, data); + if (ret < 0) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(iio_push_to_buffers); + +/** + * iio_push_to_buffers_with_ts_unaligned() - push to registered buffer, + * no alignment or space requirements. + * @indio_dev: iio_dev structure for device. + * @data: channel data excluding the timestamp. + * @data_sz: size of data. + * @timestamp: timestamp for the sample data. + * + * This special variant of iio_push_to_buffers_with_timestamp() does + * not require space for the timestamp, or 8 byte alignment of data. + * It does however require an allocation on first call and additional + * copies on all calls, so should be avoided if possible. + * + * Context: May sleep. + * Return: 0 on success, negative error code on failure. + */ +int iio_push_to_buffers_with_ts_unaligned(struct iio_dev *indio_dev, + const void *data, + size_t data_sz, + s64 timestamp) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + + might_sleep(); + + /* + * Conservative estimate - we can always safely copy the minimum + * of either the data provided or the length of the destination buffer. + * This relaxed limit allows the calling drivers to be lax about + * tracking the size of the data they are pushing, at the cost of + * unnecessary copying of padding. + */ + data_sz = min_t(size_t, indio_dev->scan_bytes, data_sz); + if (iio_dev_opaque->bounce_buffer_size != indio_dev->scan_bytes) { + void *bb; + + bb = devm_krealloc(&indio_dev->dev, + iio_dev_opaque->bounce_buffer, + indio_dev->scan_bytes, GFP_KERNEL); + if (!bb) + return -ENOMEM; + iio_dev_opaque->bounce_buffer = bb; + iio_dev_opaque->bounce_buffer_size = indio_dev->scan_bytes; + } + memcpy(iio_dev_opaque->bounce_buffer, data, data_sz); + return iio_push_to_buffers_with_timestamp(indio_dev, + iio_dev_opaque->bounce_buffer, + timestamp); +} +EXPORT_SYMBOL_GPL(iio_push_to_buffers_with_ts_unaligned); + +/** + * iio_buffer_release() - Free a buffer's resources + * @ref: Pointer to the kref embedded in the iio_buffer struct + * + * This function is called when the last reference to the buffer has been + * dropped. It will typically free all resources allocated by the buffer. Do not + * call this function manually, always use iio_buffer_put() when done using a + * buffer. + */ +static void iio_buffer_release(struct kref *ref) +{ + struct iio_buffer *buffer = container_of(ref, struct iio_buffer, ref); + + mutex_destroy(&buffer->dmabufs_mutex); + buffer->access->release(buffer); +} + +/** + * iio_buffer_get() - Grab a reference to the buffer + * @buffer: The buffer to grab a reference for, may be NULL + * + * Returns the pointer to the buffer that was passed into the function. + */ +struct iio_buffer *iio_buffer_get(struct iio_buffer *buffer) +{ + if (buffer) + kref_get(&buffer->ref); + + return buffer; +} +EXPORT_SYMBOL_GPL(iio_buffer_get); + +/** + * iio_buffer_put() - Release the reference to the buffer + * @buffer: The buffer to release the reference for, may be NULL + */ +void iio_buffer_put(struct iio_buffer *buffer) +{ + if (buffer) + kref_put(&buffer->ref, iio_buffer_release); +} +EXPORT_SYMBOL_GPL(iio_buffer_put); + +/** + * iio_device_attach_buffer - Attach a buffer to a IIO device + * @indio_dev: The device the buffer should be attached to + * @buffer: The buffer to attach to the device + * + * Return 0 if successful, negative if error. + * + * This function attaches a buffer to a IIO device. The buffer stays attached to + * the device until the device is freed. For legacy reasons, the first attached + * buffer will also be assigned to 'indio_dev->buffer'. + * The array allocated here, will be free'd via the iio_device_detach_buffers() + * call which is handled by the iio_device_free(). + */ +int iio_device_attach_buffer(struct iio_dev *indio_dev, + struct iio_buffer *buffer) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + struct iio_buffer **new, **old = iio_dev_opaque->attached_buffers; + unsigned int cnt = iio_dev_opaque->attached_buffers_cnt; + + cnt++; + + new = krealloc(old, sizeof(*new) * cnt, GFP_KERNEL); + if (!new) + return -ENOMEM; + iio_dev_opaque->attached_buffers = new; + + buffer = iio_buffer_get(buffer); + + /* first buffer is legacy; attach it to the IIO device directly */ + if (!indio_dev->buffer) + indio_dev->buffer = buffer; + + iio_dev_opaque->attached_buffers[cnt - 1] = buffer; + iio_dev_opaque->attached_buffers_cnt = cnt; + + return 0; +} +EXPORT_SYMBOL_GPL(iio_device_attach_buffer); diff --git a/drivers/iio/industrialio-configfs.c b/drivers/iio/industrialio-configfs.c new file mode 100644 index 000000000..47900de1f --- /dev/null +++ b/drivers/iio/industrialio-configfs.c @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Industrial I/O configfs bits + * + * Copyright (c) 2015 Intel Corporation + */ + +#include +#include +#include +#include +#include + +#include +#include + +static const struct config_item_type iio_root_group_type = { + .ct_owner = THIS_MODULE, +}; + +struct configfs_subsystem iio_configfs_subsys = { + .su_group = { + .cg_item = { + .ci_namebuf = "iio", + .ci_type = &iio_root_group_type, + }, + }, + .su_mutex = __MUTEX_INITIALIZER(iio_configfs_subsys.su_mutex), +}; +EXPORT_SYMBOL(iio_configfs_subsys); + +static int __init iio_configfs_init(void) +{ + config_group_init(&iio_configfs_subsys.su_group); + + return configfs_register_subsystem(&iio_configfs_subsys); +} +module_init(iio_configfs_init); + +static void __exit iio_configfs_exit(void) +{ + configfs_unregister_subsystem(&iio_configfs_subsys); +} +module_exit(iio_configfs_exit); + +MODULE_AUTHOR("Daniel Baluta "); +MODULE_DESCRIPTION("Industrial I/O configfs support"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c new file mode 100644 index 000000000..767a77946 --- /dev/null +++ b/drivers/iio/industrialio-core.c @@ -0,0 +1,2257 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * The industrial I/O core + * + * Copyright (c) 2008 Jonathan Cameron + * + * Based on elements of hwmon and input subsystems. + */ + +#define pr_fmt(fmt) "iio-core: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "iio_core.h" +#include "iio_core_trigger.h" + +/* IDA to assign each registered device a unique id */ +static DEFINE_IDA(iio_ida); + +static dev_t iio_devt; + +#define IIO_DEV_MAX 256 +const struct bus_type iio_bus_type = { + .name = "iio", +}; +EXPORT_SYMBOL(iio_bus_type); + +static struct dentry *iio_debugfs_dentry; + +static const char * const iio_direction[] = { + [0] = "in", + [1] = "out", +}; + +static const char * const iio_chan_type_name_spec[] = { + [IIO_VOLTAGE] = "voltage", + [IIO_CURRENT] = "current", + [IIO_POWER] = "power", + [IIO_ACCEL] = "accel", + [IIO_ANGL_VEL] = "anglvel", + [IIO_MAGN] = "magn", + [IIO_LIGHT] = "illuminance", + [IIO_INTENSITY] = "intensity", + [IIO_PROXIMITY] = "proximity", + [IIO_TEMP] = "temp", + [IIO_INCLI] = "incli", + [IIO_ROT] = "rot", + [IIO_ANGL] = "angl", + [IIO_TIMESTAMP] = "timestamp", + [IIO_CAPACITANCE] = "capacitance", + [IIO_ALTVOLTAGE] = "altvoltage", + [IIO_CCT] = "cct", + [IIO_PRESSURE] = "pressure", + [IIO_HUMIDITYRELATIVE] = "humidityrelative", + [IIO_ACTIVITY] = "activity", + [IIO_STEPS] = "steps", + [IIO_ENERGY] = "energy", + [IIO_DISTANCE] = "distance", + [IIO_VELOCITY] = "velocity", + [IIO_CONCENTRATION] = "concentration", + [IIO_RESISTANCE] = "resistance", + [IIO_PH] = "ph", + [IIO_UVINDEX] = "uvindex", + [IIO_ELECTRICALCONDUCTIVITY] = "electricalconductivity", + [IIO_COUNT] = "count", + [IIO_INDEX] = "index", + [IIO_GRAVITY] = "gravity", + [IIO_POSITIONRELATIVE] = "positionrelative", + [IIO_PHASE] = "phase", + [IIO_MASSCONCENTRATION] = "massconcentration", + [IIO_DELTA_ANGL] = "deltaangl", + [IIO_DELTA_VELOCITY] = "deltavelocity", + [IIO_COLORTEMP] = "colortemp", + [IIO_CHROMATICITY] = "chromaticity", + [IIO_ATTENTION] = "attention", + [IIO_ALTCURRENT] = "altcurrent", + [IIO_COVERAGE] = "coverage", + [IIO_VOLUMEFLOW] = "volumeflow", +}; + +static const char * const iio_modifier_names[] = { + [IIO_MOD_X] = "x", + [IIO_MOD_Y] = "y", + [IIO_MOD_Z] = "z", + [IIO_MOD_X_AND_Y] = "x&y", + [IIO_MOD_X_AND_Z] = "x&z", + [IIO_MOD_Y_AND_Z] = "y&z", + [IIO_MOD_X_AND_Y_AND_Z] = "x&y&z", + [IIO_MOD_X_OR_Y] = "x|y", + [IIO_MOD_X_OR_Z] = "x|z", + [IIO_MOD_Y_OR_Z] = "y|z", + [IIO_MOD_X_OR_Y_OR_Z] = "x|y|z", + [IIO_MOD_ROOT_SUM_SQUARED_X_Y] = "sqrt(x^2+y^2)", + [IIO_MOD_SUM_SQUARED_X_Y_Z] = "x^2+y^2+z^2", + [IIO_MOD_LIGHT_BOTH] = "both", + [IIO_MOD_LIGHT_IR] = "ir", + [IIO_MOD_LIGHT_CLEAR] = "clear", + [IIO_MOD_LIGHT_RED] = "red", + [IIO_MOD_LIGHT_GREEN] = "green", + [IIO_MOD_LIGHT_BLUE] = "blue", + [IIO_MOD_LIGHT_UV] = "uv", + [IIO_MOD_LIGHT_UVA] = "uva", + [IIO_MOD_LIGHT_UVB] = "uvb", + [IIO_MOD_LIGHT_DUV] = "duv", + [IIO_MOD_QUATERNION] = "quaternion", + [IIO_MOD_TEMP_AMBIENT] = "ambient", + [IIO_MOD_TEMP_OBJECT] = "object", + [IIO_MOD_NORTH_MAGN] = "from_north_magnetic", + [IIO_MOD_NORTH_TRUE] = "from_north_true", + [IIO_MOD_NORTH_MAGN_TILT_COMP] = "from_north_magnetic_tilt_comp", + [IIO_MOD_NORTH_TRUE_TILT_COMP] = "from_north_true_tilt_comp", + [IIO_MOD_RUNNING] = "running", + [IIO_MOD_JOGGING] = "jogging", + [IIO_MOD_WALKING] = "walking", + [IIO_MOD_STILL] = "still", + [IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z] = "sqrt(x^2+y^2+z^2)", + [IIO_MOD_I] = "i", + [IIO_MOD_Q] = "q", + [IIO_MOD_CO2] = "co2", + [IIO_MOD_VOC] = "voc", + [IIO_MOD_PM1] = "pm1", + [IIO_MOD_PM2P5] = "pm2p5", + [IIO_MOD_PM4] = "pm4", + [IIO_MOD_PM10] = "pm10", + [IIO_MOD_ETHANOL] = "ethanol", + [IIO_MOD_H2] = "h2", + [IIO_MOD_O2] = "o2", + [IIO_MOD_LINEAR_X] = "linear_x", + [IIO_MOD_LINEAR_Y] = "linear_y", + [IIO_MOD_LINEAR_Z] = "linear_z", + [IIO_MOD_PITCH] = "pitch", + [IIO_MOD_YAW] = "yaw", + [IIO_MOD_ROLL] = "roll", + [IIO_MOD_RMS] = "rms", + [IIO_MOD_ACTIVE] = "active", + [IIO_MOD_REACTIVE] = "reactive", + [IIO_MOD_APPARENT] = "apparent", + [IIO_MOD_QUATERNION_AXIS] = "quaternionaxis", +}; + +/* relies on pairs of these shared then separate */ +static const char * const iio_chan_info_postfix[] = { + [IIO_CHAN_INFO_RAW] = "raw", + [IIO_CHAN_INFO_PROCESSED] = "input", + [IIO_CHAN_INFO_SCALE] = "scale", + [IIO_CHAN_INFO_OFFSET] = "offset", + [IIO_CHAN_INFO_CALIBSCALE] = "calibscale", + [IIO_CHAN_INFO_CALIBBIAS] = "calibbias", + [IIO_CHAN_INFO_PEAK] = "peak_raw", + [IIO_CHAN_INFO_PEAK_SCALE] = "peak_scale", + [IIO_CHAN_INFO_QUADRATURE_CORRECTION_RAW] = "quadrature_correction_raw", + [IIO_CHAN_INFO_AVERAGE_RAW] = "mean_raw", + [IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY] + = "filter_low_pass_3db_frequency", + [IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY] + = "filter_high_pass_3db_frequency", + [IIO_CHAN_INFO_SAMP_FREQ] = "sampling_frequency", + [IIO_CHAN_INFO_FREQUENCY] = "frequency", + [IIO_CHAN_INFO_PHASE] = "phase", + [IIO_CHAN_INFO_HARDWAREGAIN] = "hardwaregain", + [IIO_CHAN_INFO_HYSTERESIS] = "hysteresis", + [IIO_CHAN_INFO_HYSTERESIS_RELATIVE] = "hysteresis_relative", + [IIO_CHAN_INFO_INT_TIME] = "integration_time", + [IIO_CHAN_INFO_ENABLE] = "en", + [IIO_CHAN_INFO_CALIBHEIGHT] = "calibheight", + [IIO_CHAN_INFO_CALIBWEIGHT] = "calibweight", + [IIO_CHAN_INFO_DEBOUNCE_COUNT] = "debounce_count", + [IIO_CHAN_INFO_DEBOUNCE_TIME] = "debounce_time", + [IIO_CHAN_INFO_CALIBEMISSIVITY] = "calibemissivity", + [IIO_CHAN_INFO_OVERSAMPLING_RATIO] = "oversampling_ratio", + [IIO_CHAN_INFO_THERMOCOUPLE_TYPE] = "thermocouple_type", + [IIO_CHAN_INFO_CALIBAMBIENT] = "calibambient", + [IIO_CHAN_INFO_ZEROPOINT] = "zeropoint", + [IIO_CHAN_INFO_TROUGH] = "trough_raw", + [IIO_CHAN_INFO_CONVDELAY] = "convdelay", + [IIO_CHAN_INFO_POWERFACTOR] = "powerfactor", +}; +/** + * iio_device_id() - query the unique ID for the device + * @indio_dev: Device structure whose ID is being queried + * + * The IIO device ID is a unique index used for example for the naming + * of the character device /dev/iio\:device[ID]. + * + * Returns: Unique ID for the device. + */ +int iio_device_id(struct iio_dev *indio_dev) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + + return iio_dev_opaque->id; +} +EXPORT_SYMBOL_GPL(iio_device_id); + +/** + * iio_buffer_enabled() - helper function to test if the buffer is enabled + * @indio_dev: IIO device structure for device + * + * Returns: True, if the buffer is enabled. + */ +bool iio_buffer_enabled(struct iio_dev *indio_dev) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + + return iio_dev_opaque->currentmode & INDIO_ALL_BUFFER_MODES; +} +EXPORT_SYMBOL_GPL(iio_buffer_enabled); + +#if defined(CONFIG_DEBUG_FS) +/* + * There's also a CONFIG_DEBUG_FS guard in include/linux/iio/iio.h for + * iio_get_debugfs_dentry() to make it inline if CONFIG_DEBUG_FS is undefined + */ +struct dentry *iio_get_debugfs_dentry(struct iio_dev *indio_dev) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + + return iio_dev_opaque->debugfs_dentry; +} +EXPORT_SYMBOL_GPL(iio_get_debugfs_dentry); +#endif + +/** + * iio_find_channel_from_si() - get channel from its scan index + * @indio_dev: device + * @si: scan index to match + * + * Returns: + * Constant pointer to iio_chan_spec, if scan index matches, NULL on failure. + */ +const struct iio_chan_spec +*iio_find_channel_from_si(struct iio_dev *indio_dev, int si) +{ + int i; + + for (i = 0; i < indio_dev->num_channels; i++) + if (indio_dev->channels[i].scan_index == si) + return &indio_dev->channels[i]; + return NULL; +} + +/* This turns up an awful lot */ +ssize_t iio_read_const_attr(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%s\n", to_iio_const_attr(attr)->string); +} +EXPORT_SYMBOL(iio_read_const_attr); + +/** + * iio_device_set_clock() - Set current timestamping clock for the device + * @indio_dev: IIO device structure containing the device + * @clock_id: timestamping clock POSIX identifier to set. + * + * Returns: 0 on success, or a negative error code. + */ +int iio_device_set_clock(struct iio_dev *indio_dev, clockid_t clock_id) +{ + int ret; + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + const struct iio_event_interface *ev_int = iio_dev_opaque->event_interface; + + ret = mutex_lock_interruptible(&iio_dev_opaque->mlock); + if (ret) + return ret; + if ((ev_int && iio_event_enabled(ev_int)) || + iio_buffer_enabled(indio_dev)) { + mutex_unlock(&iio_dev_opaque->mlock); + return -EBUSY; + } + iio_dev_opaque->clock_id = clock_id; + mutex_unlock(&iio_dev_opaque->mlock); + + return 0; +} +EXPORT_SYMBOL(iio_device_set_clock); + +/** + * iio_device_get_clock() - Retrieve current timestamping clock for the device + * @indio_dev: IIO device structure containing the device + * + * Returns: Clock ID of the current timestamping clock for the device. + */ +clockid_t iio_device_get_clock(const struct iio_dev *indio_dev) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + + return iio_dev_opaque->clock_id; +} +EXPORT_SYMBOL(iio_device_get_clock); + +/** + * iio_get_time_ns() - utility function to get a time stamp for events etc + * @indio_dev: device + * + * Returns: Timestamp of the event in nanoseconds. + */ +s64 iio_get_time_ns(const struct iio_dev *indio_dev) +{ + struct timespec64 tp; + + switch (iio_device_get_clock(indio_dev)) { + case CLOCK_REALTIME: + return ktime_get_real_ns(); + case CLOCK_MONOTONIC: + return ktime_get_ns(); + case CLOCK_MONOTONIC_RAW: + return ktime_get_raw_ns(); + case CLOCK_REALTIME_COARSE: + return ktime_to_ns(ktime_get_coarse_real()); + case CLOCK_MONOTONIC_COARSE: + ktime_get_coarse_ts64(&tp); + return timespec64_to_ns(&tp); + case CLOCK_BOOTTIME: + return ktime_get_boottime_ns(); + case CLOCK_TAI: + return ktime_get_clocktai_ns(); + default: + BUG(); + } +} +EXPORT_SYMBOL(iio_get_time_ns); + +static int __init iio_init(void) +{ + int ret; + + /* Register sysfs bus */ + ret = bus_register(&iio_bus_type); + if (ret < 0) { + pr_err("could not register bus type\n"); + goto error_nothing; + } + + ret = alloc_chrdev_region(&iio_devt, 0, IIO_DEV_MAX, "iio"); + if (ret < 0) { + pr_err("failed to allocate char dev region\n"); + goto error_unregister_bus_type; + } + + iio_debugfs_dentry = debugfs_create_dir("iio", NULL); + + return 0; + +error_unregister_bus_type: + bus_unregister(&iio_bus_type); +error_nothing: + return ret; +} + +static void __exit iio_exit(void) +{ + if (iio_devt) + unregister_chrdev_region(iio_devt, IIO_DEV_MAX); + bus_unregister(&iio_bus_type); + debugfs_remove(iio_debugfs_dentry); +} + +#if defined(CONFIG_DEBUG_FS) +static ssize_t iio_debugfs_read_reg(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct iio_dev *indio_dev = file->private_data; + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + unsigned int val = 0; + int ret; + + if (*ppos > 0) + return simple_read_from_buffer(userbuf, count, ppos, + iio_dev_opaque->read_buf, + iio_dev_opaque->read_buf_len); + + ret = indio_dev->info->debugfs_reg_access(indio_dev, + iio_dev_opaque->cached_reg_addr, + 0, &val); + if (ret) { + dev_err(indio_dev->dev.parent, "%s: read failed\n", __func__); + return ret; + } + + iio_dev_opaque->read_buf_len = snprintf(iio_dev_opaque->read_buf, + sizeof(iio_dev_opaque->read_buf), + "0x%X\n", val); + + return simple_read_from_buffer(userbuf, count, ppos, + iio_dev_opaque->read_buf, + iio_dev_opaque->read_buf_len); +} + +static ssize_t iio_debugfs_write_reg(struct file *file, + const char __user *userbuf, size_t count, loff_t *ppos) +{ + struct iio_dev *indio_dev = file->private_data; + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + unsigned int reg, val; + char buf[80]; + int ret; + + if (*ppos != 0 || count >= sizeof(buf)) + return -EINVAL; + + ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, userbuf, + count); + if (ret < 0) + return ret; + + buf[ret] = '\0'; + + ret = sscanf(buf, "%i %i", ®, &val); + + switch (ret) { + case 1: + iio_dev_opaque->cached_reg_addr = reg; + break; + case 2: + iio_dev_opaque->cached_reg_addr = reg; + ret = indio_dev->info->debugfs_reg_access(indio_dev, reg, + val, NULL); + if (ret) { + dev_err(indio_dev->dev.parent, "%s: write failed\n", + __func__); + return ret; + } + break; + default: + return -EINVAL; + } + + return count; +} + +static const struct file_operations iio_debugfs_reg_fops = { + .open = simple_open, + .read = iio_debugfs_read_reg, + .write = iio_debugfs_write_reg, +}; + +static void iio_device_unregister_debugfs(struct iio_dev *indio_dev) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + + debugfs_remove_recursive(iio_dev_opaque->debugfs_dentry); +} + +static void iio_device_register_debugfs(struct iio_dev *indio_dev) +{ + struct iio_dev_opaque *iio_dev_opaque; + + if (indio_dev->info->debugfs_reg_access == NULL) + return; + + if (!iio_debugfs_dentry) + return; + + iio_dev_opaque = to_iio_dev_opaque(indio_dev); + + iio_dev_opaque->debugfs_dentry = + debugfs_create_dir(dev_name(&indio_dev->dev), + iio_debugfs_dentry); + + debugfs_create_file("direct_reg_access", 0644, + iio_dev_opaque->debugfs_dentry, indio_dev, + &iio_debugfs_reg_fops); +} +#else +static void iio_device_register_debugfs(struct iio_dev *indio_dev) +{ +} + +static void iio_device_unregister_debugfs(struct iio_dev *indio_dev) +{ +} +#endif /* CONFIG_DEBUG_FS */ + +static ssize_t iio_read_channel_ext_info(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + const struct iio_chan_spec_ext_info *ext_info; + + ext_info = &this_attr->c->ext_info[this_attr->address]; + + return ext_info->read(indio_dev, ext_info->private, this_attr->c, buf); +} + +static ssize_t iio_write_channel_ext_info(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + const struct iio_chan_spec_ext_info *ext_info; + + ext_info = &this_attr->c->ext_info[this_attr->address]; + + return ext_info->write(indio_dev, ext_info->private, + this_attr->c, buf, len); +} + +ssize_t iio_enum_available_read(struct iio_dev *indio_dev, + uintptr_t priv, const struct iio_chan_spec *chan, char *buf) +{ + const struct iio_enum *e = (const struct iio_enum *)priv; + unsigned int i; + size_t len = 0; + + if (!e->num_items) + return 0; + + for (i = 0; i < e->num_items; ++i) { + if (!e->items[i]) + continue; + len += sysfs_emit_at(buf, len, "%s ", e->items[i]); + } + + /* replace last space with a newline */ + buf[len - 1] = '\n'; + + return len; +} +EXPORT_SYMBOL_GPL(iio_enum_available_read); + +ssize_t iio_enum_read(struct iio_dev *indio_dev, + uintptr_t priv, const struct iio_chan_spec *chan, char *buf) +{ + const struct iio_enum *e = (const struct iio_enum *)priv; + int i; + + if (!e->get) + return -EINVAL; + + i = e->get(indio_dev, chan); + if (i < 0) + return i; + if (i >= e->num_items || !e->items[i]) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", e->items[i]); +} +EXPORT_SYMBOL_GPL(iio_enum_read); + +ssize_t iio_enum_write(struct iio_dev *indio_dev, + uintptr_t priv, const struct iio_chan_spec *chan, const char *buf, + size_t len) +{ + const struct iio_enum *e = (const struct iio_enum *)priv; + int ret; + + if (!e->set) + return -EINVAL; + + ret = __sysfs_match_string(e->items, e->num_items, buf); + if (ret < 0) + return ret; + + ret = e->set(indio_dev, chan, ret); + return ret ? ret : len; +} +EXPORT_SYMBOL_GPL(iio_enum_write); + +static const struct iio_mount_matrix iio_mount_idmatrix = { + .rotation = { + "1", "0", "0", + "0", "1", "0", + "0", "0", "1" + } +}; + +static int iio_setup_mount_idmatrix(const struct device *dev, + struct iio_mount_matrix *matrix) +{ + *matrix = iio_mount_idmatrix; + dev_info(dev, "mounting matrix not found: using identity...\n"); + return 0; +} + +ssize_t iio_show_mount_matrix(struct iio_dev *indio_dev, uintptr_t priv, + const struct iio_chan_spec *chan, char *buf) +{ + const struct iio_mount_matrix *mtx; + + mtx = ((iio_get_mount_matrix_t *)priv)(indio_dev, chan); + if (IS_ERR(mtx)) + return PTR_ERR(mtx); + + if (!mtx) + mtx = &iio_mount_idmatrix; + + return sysfs_emit(buf, "%s, %s, %s; %s, %s, %s; %s, %s, %s\n", + mtx->rotation[0], mtx->rotation[1], mtx->rotation[2], + mtx->rotation[3], mtx->rotation[4], mtx->rotation[5], + mtx->rotation[6], mtx->rotation[7], mtx->rotation[8]); +} +EXPORT_SYMBOL_GPL(iio_show_mount_matrix); + +/** + * iio_read_mount_matrix() - retrieve iio device mounting matrix from + * device "mount-matrix" property + * @dev: device the mounting matrix property is assigned to + * @matrix: where to store retrieved matrix + * + * If device is assigned no mounting matrix property, a default 3x3 identity + * matrix will be filled in. + * + * Returns: 0 if success, or a negative error code on failure. + */ +int iio_read_mount_matrix(struct device *dev, struct iio_mount_matrix *matrix) +{ + size_t len = ARRAY_SIZE(iio_mount_idmatrix.rotation); + int err; + + err = device_property_read_string_array(dev, "mount-matrix", matrix->rotation, len); + if (err == len) + return 0; + + if (err >= 0) + /* Invalid number of matrix entries. */ + return -EINVAL; + + if (err != -EINVAL) + /* Invalid matrix declaration format. */ + return err; + + /* Matrix was not declared at all: fallback to identity. */ + return iio_setup_mount_idmatrix(dev, matrix); +} +EXPORT_SYMBOL(iio_read_mount_matrix); + +static ssize_t __iio_format_value(char *buf, size_t offset, unsigned int type, + int size, const int *vals) +{ + int tmp0, tmp1; + int l = 0; + s64 tmp2; + bool scale_db = false; + + switch (type) { + case IIO_VAL_INT: + return sysfs_emit_at(buf, offset, "%d", vals[0]); + case IIO_VAL_INT_PLUS_MICRO_DB: + scale_db = true; + fallthrough; + case IIO_VAL_INT_PLUS_MICRO: + if (vals[1] < 0) + return sysfs_emit_at(buf, offset, "-%d.%06u%s", + abs(vals[0]), -vals[1], + scale_db ? " dB" : ""); + else + return sysfs_emit_at(buf, offset, "%d.%06u%s", vals[0], + vals[1], scale_db ? " dB" : ""); + case IIO_VAL_INT_PLUS_NANO: + if (vals[1] < 0) + return sysfs_emit_at(buf, offset, "-%d.%09u", + abs(vals[0]), -vals[1]); + else + return sysfs_emit_at(buf, offset, "%d.%09u", vals[0], + vals[1]); + case IIO_VAL_FRACTIONAL: + tmp2 = div_s64((s64)vals[0] * 1000000000LL, vals[1]); + tmp0 = (int)div_s64_rem(tmp2, 1000000000, &tmp1); + if ((tmp2 < 0) && (tmp0 == 0)) + return sysfs_emit_at(buf, offset, "-0.%09u", abs(tmp1)); + else + return sysfs_emit_at(buf, offset, "%d.%09u", tmp0, + abs(tmp1)); + case IIO_VAL_FRACTIONAL_LOG2: + tmp2 = shift_right((s64)vals[0] * 1000000000LL, vals[1]); + tmp0 = (int)div_s64_rem(tmp2, 1000000000LL, &tmp1); + if (tmp0 == 0 && tmp2 < 0) + return sysfs_emit_at(buf, offset, "-0.%09u", abs(tmp1)); + else + return sysfs_emit_at(buf, offset, "%d.%09u", tmp0, + abs(tmp1)); + case IIO_VAL_INT_MULTIPLE: + { + int i; + + for (i = 0; i < size; ++i) + l += sysfs_emit_at(buf, offset + l, "%d ", vals[i]); + return l; + } + case IIO_VAL_CHAR: + return sysfs_emit_at(buf, offset, "%c", (char)vals[0]); + case IIO_VAL_INT_64: + return sysfs_emit_at(buf, offset, "%lld", + iio_val_s64_compose(vals[0], vals[1])); + case IIO_VAL_DECIMAL64_MILLI: + case IIO_VAL_DECIMAL64_MICRO: + case IIO_VAL_DECIMAL64_NANO: + case IIO_VAL_DECIMAL64_PICO: + case IIO_VAL_DECIMAL64_FEMTO: + { + int scale = type - IIO_VAL_DECIMAL64_BASE; + s64 frac; + + tmp2 = div64_s64_rem(iio_val_s64_compose(vals[0], vals[1]), + int_pow(10, scale), &frac); + if (tmp2 == 0 && frac < 0) + l += sysfs_emit_at(buf, offset, "-"); + + l += sysfs_emit_at(buf, offset + l, "%lld.%0*lld", tmp2, scale, + abs(frac)); + return l; + } + default: + return 0; + } +} + +/** + * iio_format_value() - Formats a IIO value into its string representation + * @buf: The buffer to which the formatted value gets written + * which is assumed to be big enough (i.e. PAGE_SIZE). + * @type: One of the IIO_VAL_* constants. This decides how the val + * and val2 parameters are formatted. + * @size: Number of IIO value entries contained in vals + * @vals: Pointer to the values, exact meaning depends on the + * type parameter. + * + * Returns: + * 0 by default, a negative number on failure or the total number of characters + * written for a type that belongs to the IIO_VAL_* constant. + */ +ssize_t iio_format_value(char *buf, unsigned int type, int size, int *vals) +{ + ssize_t len; + + len = __iio_format_value(buf, 0, type, size, vals); + if (len >= PAGE_SIZE - 1) + return -EFBIG; + + return len + sysfs_emit_at(buf, len, "\n"); +} +EXPORT_SYMBOL_GPL(iio_format_value); + +ssize_t do_iio_read_channel_label(struct iio_dev *indio_dev, + const struct iio_chan_spec *c, + char *buf) +{ + if (indio_dev->info->read_label) + return indio_dev->info->read_label(indio_dev, c, buf); + + if (c->extend_name) + return sysfs_emit(buf, "%s\n", c->extend_name); + + return -EINVAL; +} + +static ssize_t iio_read_channel_label(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return do_iio_read_channel_label(dev_to_iio_dev(dev), + to_iio_dev_attr(attr)->c, buf); +} + +static ssize_t iio_read_channel_info(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int vals[INDIO_MAX_RAW_ELEMENTS]; + int ret; + int val_len = 2; + + if (indio_dev->info->read_raw_multi) + ret = indio_dev->info->read_raw_multi(indio_dev, this_attr->c, + INDIO_MAX_RAW_ELEMENTS, + vals, &val_len, + this_attr->address); + else if (indio_dev->info->read_raw) + ret = indio_dev->info->read_raw(indio_dev, this_attr->c, + &vals[0], &vals[1], this_attr->address); + else + return -EINVAL; + + if (ret < 0) + return ret; + + return iio_format_value(buf, ret, val_len, vals); +} + +static ssize_t iio_format_list(char *buf, const int *vals, int type, int length, + const char *prefix, const char *suffix) +{ + ssize_t len; + int stride; + int i; + + switch (type) { + case IIO_VAL_INT: + case IIO_VAL_CHAR: + stride = 1; + break; + default: + stride = 2; + break; + } + + len = sysfs_emit(buf, prefix); + + for (i = 0; i <= length - stride; i += stride) { + if (i != 0) { + len += sysfs_emit_at(buf, len, " "); + if (len >= PAGE_SIZE) + return -EFBIG; + } + + len += __iio_format_value(buf, len, type, stride, &vals[i]); + if (len >= PAGE_SIZE) + return -EFBIG; + } + + len += sysfs_emit_at(buf, len, "%s\n", suffix); + + return len; +} + +static ssize_t iio_format_avail_list(char *buf, const int *vals, + int type, int length) +{ + + return iio_format_list(buf, vals, type, length, "", ""); +} + +static ssize_t iio_format_avail_range(char *buf, const int *vals, int type) +{ + int length; + + /* + * length refers to the array size , not the number of elements. + * The purpose is to print the range [min , step ,max] so length should + * be 3 in case of int, and 6 for other types. + */ + switch (type) { + case IIO_VAL_INT: + length = 3; + break; + default: + length = 6; + break; + } + + return iio_format_list(buf, vals, type, length, "[", "]"); +} + +static ssize_t iio_read_channel_info_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + const int *vals; + int ret; + int length; + int type; + + if (!indio_dev->info->read_avail) + return -EINVAL; + + ret = indio_dev->info->read_avail(indio_dev, this_attr->c, + &vals, &type, &length, + this_attr->address); + + if (ret < 0) + return ret; + switch (ret) { + case IIO_AVAIL_LIST: + return iio_format_avail_list(buf, vals, type, length); + case IIO_AVAIL_RANGE: + return iio_format_avail_range(buf, vals, type); + default: + return -EINVAL; + } +} + +/** + * __iio_str_to_fixpoint() - Parse a fixed-point number from a string + * @str: The string to parse + * @fract_mult: Multiplier for the first decimal place, should be a power of 10 + * @integer: The integer part of the number + * @fract: The fractional part of the number + * @scale_db: True if this should parse as dB + * + * Returns: + * 0 on success, or a negative error code if the string could not be parsed. + */ +static int __iio_str_to_fixpoint(const char *str, int fract_mult, + int *integer, int *fract, bool scale_db) +{ + int i = 0, f = 0; + bool integer_part = true, negative = false; + + if (fract_mult == 0) { + *fract = 0; + + return kstrtoint(str, 0, integer); + } + + if (str[0] == '-') { + negative = true; + str++; + } else if (str[0] == '+') { + str++; + } + + while (*str) { + if ('0' <= *str && *str <= '9') { + if (integer_part) { + i = i * 10 + *str - '0'; + } else { + f += fract_mult * (*str - '0'); + fract_mult /= 10; + } + } else if (*str == '\n') { + if (*(str + 1) == '\0') + break; + return -EINVAL; + } else if (!strncmp(str, " dB", sizeof(" dB") - 1) && scale_db) { + /* Ignore the dB suffix */ + str += sizeof(" dB") - 1; + continue; + } else if (!strncmp(str, "dB", sizeof("dB") - 1) && scale_db) { + /* Ignore the dB suffix */ + str += sizeof("dB") - 1; + continue; + } else if (*str == '.' && integer_part) { + integer_part = false; + } else { + return -EINVAL; + } + str++; + } + + if (negative) { + if (i) + i = -i; + else + f = -f; + } + + *integer = i; + *fract = f; + + return 0; +} + +/** + * iio_str_to_fixpoint() - Parse a fixed-point number from a string + * @str: The string to parse + * @fract_mult: Multiplier for the first decimal place, should be a power of 10 + * @integer: The integer part of the number + * @fract: The fractional part of the number + * + * Returns: + * 0 on success, or a negative error code if the string could not be parsed. + */ +int iio_str_to_fixpoint(const char *str, int fract_mult, + int *integer, int *fract) +{ + return __iio_str_to_fixpoint(str, fract_mult, integer, fract, false); +} +EXPORT_SYMBOL_GPL(iio_str_to_fixpoint); + +static ssize_t iio_write_channel_info(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret, fract_mult = 100000; + int type, dec_scale = 0; + int integer, fract = 0; + long long integer64; + bool is_char = false; + bool scale_db = false; + bool is_64bit = false; + + /* Assumes decimal - precision based on number of digits */ + if (!indio_dev->info->write_raw) + return -EINVAL; + + if (indio_dev->info->write_raw_get_fmt) { + type = indio_dev->info->write_raw_get_fmt(indio_dev, + this_attr->c, + this_attr->address); + switch (type) { + case IIO_VAL_INT: + fract_mult = 0; + break; + case IIO_VAL_INT_PLUS_MICRO_DB: + scale_db = true; + fallthrough; + case IIO_VAL_INT_PLUS_MICRO: + fract_mult = 100000; + break; + case IIO_VAL_INT_PLUS_NANO: + fract_mult = 100000000; + break; + case IIO_VAL_CHAR: + is_char = true; + break; + case IIO_VAL_DECIMAL64_MILLI: + case IIO_VAL_DECIMAL64_MICRO: + case IIO_VAL_DECIMAL64_NANO: + case IIO_VAL_DECIMAL64_PICO: + case IIO_VAL_DECIMAL64_FEMTO: + dec_scale = type - IIO_VAL_DECIMAL64_BASE; + fallthrough; + case IIO_VAL_INT_64: + is_64bit = true; + break; + default: + return -EINVAL; + } + } + + if (is_char) { + char ch; + + if (sscanf(buf, "%c", &ch) != 1) + return -EINVAL; + integer = ch; + } else if (is_64bit) { + if (dec_scale) + ret = kstrtodec64(buf, dec_scale, &integer64); + else + ret = kstrtoll(buf, 0, &integer64); + if (ret) + return ret; + + iio_val_s64_decompose(integer64, &integer, &fract); + } else { + ret = __iio_str_to_fixpoint(buf, fract_mult, &integer, &fract, + scale_db); + if (ret) + return ret; + } + + ret = indio_dev->info->write_raw(indio_dev, this_attr->c, + integer, fract, this_attr->address); + if (ret) + return ret; + + return len; +} + +static +int __iio_device_attr_init(struct device_attribute *dev_attr, + const char *postfix, + struct iio_chan_spec const *chan, + ssize_t (*readfunc)(struct device *dev, + struct device_attribute *attr, + char *buf), + ssize_t (*writefunc)(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len), + enum iio_shared_by shared_by) +{ + int ret = 0; + char *name = NULL; + char *full_postfix; + + sysfs_attr_init(&dev_attr->attr); + + /* Build up postfix of __postfix */ + if (chan->modified && (shared_by == IIO_SEPARATE)) { + if (chan->extend_name) + full_postfix = kasprintf(GFP_KERNEL, "%s_%s_%s", + iio_modifier_names[chan->channel2], + chan->extend_name, + postfix); + else + full_postfix = kasprintf(GFP_KERNEL, "%s_%s", + iio_modifier_names[chan->channel2], + postfix); + } else { + if (chan->extend_name == NULL || shared_by != IIO_SEPARATE) + full_postfix = kstrdup(postfix, GFP_KERNEL); + else + full_postfix = kasprintf(GFP_KERNEL, + "%s_%s", + chan->extend_name, + postfix); + } + if (full_postfix == NULL) + return -ENOMEM; + + if (chan->differential) { /* Differential can not have modifier */ + switch (shared_by) { + case IIO_SHARED_BY_ALL: + name = kasprintf(GFP_KERNEL, "%s", full_postfix); + break; + case IIO_SHARED_BY_DIR: + name = kasprintf(GFP_KERNEL, "%s_%s", + iio_direction[chan->output], + full_postfix); + break; + case IIO_SHARED_BY_TYPE: + name = kasprintf(GFP_KERNEL, "%s_%s-%s_%s", + iio_direction[chan->output], + iio_chan_type_name_spec[chan->type], + iio_chan_type_name_spec[chan->type], + full_postfix); + break; + case IIO_SEPARATE: + if (!chan->indexed) { + WARN(1, "Differential channels must be indexed\n"); + ret = -EINVAL; + goto error_free_full_postfix; + } + name = kasprintf(GFP_KERNEL, + "%s_%s%d-%s%d_%s", + iio_direction[chan->output], + iio_chan_type_name_spec[chan->type], + chan->channel, + iio_chan_type_name_spec[chan->type], + chan->channel2, + full_postfix); + break; + } + } else { /* Single ended */ + switch (shared_by) { + case IIO_SHARED_BY_ALL: + name = kasprintf(GFP_KERNEL, "%s", full_postfix); + break; + case IIO_SHARED_BY_DIR: + name = kasprintf(GFP_KERNEL, "%s_%s", + iio_direction[chan->output], + full_postfix); + break; + case IIO_SHARED_BY_TYPE: + name = kasprintf(GFP_KERNEL, "%s_%s_%s", + iio_direction[chan->output], + iio_chan_type_name_spec[chan->type], + full_postfix); + break; + + case IIO_SEPARATE: + if (chan->indexed) + name = kasprintf(GFP_KERNEL, "%s_%s%d_%s", + iio_direction[chan->output], + iio_chan_type_name_spec[chan->type], + chan->channel, + full_postfix); + else + name = kasprintf(GFP_KERNEL, "%s_%s_%s", + iio_direction[chan->output], + iio_chan_type_name_spec[chan->type], + full_postfix); + break; + } + } + if (name == NULL) { + ret = -ENOMEM; + goto error_free_full_postfix; + } + dev_attr->attr.name = name; + + if (readfunc) { + dev_attr->attr.mode |= 0444; + dev_attr->show = readfunc; + } + + if (writefunc) { + dev_attr->attr.mode |= 0200; + dev_attr->store = writefunc; + } + +error_free_full_postfix: + kfree(full_postfix); + + return ret; +} + +static void __iio_device_attr_deinit(struct device_attribute *dev_attr) +{ + kfree(dev_attr->attr.name); +} + +int __iio_add_chan_devattr(const char *postfix, + struct iio_chan_spec const *chan, + ssize_t (*readfunc)(struct device *dev, + struct device_attribute *attr, + char *buf), + ssize_t (*writefunc)(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len), + u64 mask, + enum iio_shared_by shared_by, + struct device *dev, + struct iio_buffer *buffer, + struct list_head *attr_list) +{ + int ret; + struct iio_dev_attr *iio_attr, *t; + + iio_attr = kzalloc_obj(*iio_attr); + if (iio_attr == NULL) + return -ENOMEM; + ret = __iio_device_attr_init(&iio_attr->dev_attr, + postfix, chan, + readfunc, writefunc, shared_by); + if (ret) + goto error_iio_dev_attr_free; + iio_attr->c = chan; + iio_attr->address = mask; + iio_attr->buffer = buffer; + list_for_each_entry(t, attr_list, l) + if (strcmp(t->dev_attr.attr.name, + iio_attr->dev_attr.attr.name) == 0) { + if (shared_by == IIO_SEPARATE) + dev_err(dev, "tried to double register : %s\n", + t->dev_attr.attr.name); + ret = -EBUSY; + goto error_device_attr_deinit; + } + list_add(&iio_attr->l, attr_list); + + return 0; + +error_device_attr_deinit: + __iio_device_attr_deinit(&iio_attr->dev_attr); +error_iio_dev_attr_free: + kfree(iio_attr); + return ret; +} + +static int iio_device_add_channel_label(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + int ret; + + if (!indio_dev->info->read_label && !chan->extend_name) + return 0; + + ret = __iio_add_chan_devattr("label", + chan, + &iio_read_channel_label, + NULL, + 0, + IIO_SEPARATE, + &indio_dev->dev, + NULL, + &iio_dev_opaque->channel_attr_list); + if (ret < 0) + return ret; + + return 1; +} + +static int iio_device_add_info_mask_type(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + enum iio_shared_by shared_by, + const unsigned long *infomask) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + int i, ret, attrcount = 0; + + for_each_set_bit(i, infomask, sizeof(*infomask)*8) { + if (i >= ARRAY_SIZE(iio_chan_info_postfix)) + return -EINVAL; + ret = __iio_add_chan_devattr(iio_chan_info_postfix[i], + chan, + &iio_read_channel_info, + &iio_write_channel_info, + i, + shared_by, + &indio_dev->dev, + NULL, + &iio_dev_opaque->channel_attr_list); + if ((ret == -EBUSY) && (shared_by != IIO_SEPARATE)) + continue; + if (ret < 0) + return ret; + attrcount++; + } + + return attrcount; +} + +static int iio_device_add_info_mask_type_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + enum iio_shared_by shared_by, + const unsigned long *infomask) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + int i, ret, attrcount = 0; + char *avail_postfix; + + for_each_set_bit(i, infomask, sizeof(*infomask) * 8) { + if (i >= ARRAY_SIZE(iio_chan_info_postfix)) + return -EINVAL; + avail_postfix = kasprintf(GFP_KERNEL, + "%s_available", + iio_chan_info_postfix[i]); + if (!avail_postfix) + return -ENOMEM; + + ret = __iio_add_chan_devattr(avail_postfix, + chan, + &iio_read_channel_info_avail, + NULL, + i, + shared_by, + &indio_dev->dev, + NULL, + &iio_dev_opaque->channel_attr_list); + kfree(avail_postfix); + if ((ret == -EBUSY) && (shared_by != IIO_SEPARATE)) + continue; + if (ret < 0) + return ret; + attrcount++; + } + + return attrcount; +} + +static int iio_device_add_channel_sysfs(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + int ret, attrcount = 0; + const struct iio_chan_spec_ext_info *ext_info; + + if (chan->channel < 0) + return 0; + ret = iio_device_add_info_mask_type(indio_dev, chan, + IIO_SEPARATE, + &chan->info_mask_separate); + if (ret < 0) + return ret; + attrcount += ret; + + ret = iio_device_add_info_mask_type_avail(indio_dev, chan, + IIO_SEPARATE, + &chan->info_mask_separate_available); + if (ret < 0) + return ret; + attrcount += ret; + + ret = iio_device_add_info_mask_type(indio_dev, chan, + IIO_SHARED_BY_TYPE, + &chan->info_mask_shared_by_type); + if (ret < 0) + return ret; + attrcount += ret; + + ret = iio_device_add_info_mask_type_avail(indio_dev, chan, + IIO_SHARED_BY_TYPE, + &chan->info_mask_shared_by_type_available); + if (ret < 0) + return ret; + attrcount += ret; + + ret = iio_device_add_info_mask_type(indio_dev, chan, + IIO_SHARED_BY_DIR, + &chan->info_mask_shared_by_dir); + if (ret < 0) + return ret; + attrcount += ret; + + ret = iio_device_add_info_mask_type_avail(indio_dev, chan, + IIO_SHARED_BY_DIR, + &chan->info_mask_shared_by_dir_available); + if (ret < 0) + return ret; + attrcount += ret; + + ret = iio_device_add_info_mask_type(indio_dev, chan, + IIO_SHARED_BY_ALL, + &chan->info_mask_shared_by_all); + if (ret < 0) + return ret; + attrcount += ret; + + ret = iio_device_add_info_mask_type_avail(indio_dev, chan, + IIO_SHARED_BY_ALL, + &chan->info_mask_shared_by_all_available); + if (ret < 0) + return ret; + attrcount += ret; + + ret = iio_device_add_channel_label(indio_dev, chan); + if (ret < 0) + return ret; + attrcount += ret; + + if (chan->ext_info) { + unsigned int i = 0; + + for (ext_info = chan->ext_info; ext_info->name; ext_info++) { + ret = __iio_add_chan_devattr(ext_info->name, + chan, + ext_info->read ? + &iio_read_channel_ext_info : NULL, + ext_info->write ? + &iio_write_channel_ext_info : NULL, + i, + ext_info->shared, + &indio_dev->dev, + NULL, + &iio_dev_opaque->channel_attr_list); + i++; + if (ret == -EBUSY && ext_info->shared) + continue; + + if (ret) + return ret; + + attrcount++; + } + } + + return attrcount; +} + +/** + * iio_free_chan_devattr_list() - Free a list of IIO device attributes + * @attr_list: List of IIO device attributes + * + * This function frees the memory allocated for each of the IIO device + * attributes in the list. + */ +void iio_free_chan_devattr_list(struct list_head *attr_list) +{ + struct iio_dev_attr *p, *n; + + list_for_each_entry_safe(p, n, attr_list, l) { + kfree_const(p->dev_attr.attr.name); + list_del(&p->l); + kfree(p); + } +} + +static ssize_t name_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + + return sysfs_emit(buf, "%s\n", indio_dev->name); +} + +static DEVICE_ATTR_RO(name); + +static ssize_t label_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + + return sysfs_emit(buf, "%s\n", indio_dev->label); +} + +static DEVICE_ATTR_RO(label); + +static const char * const clock_names[] = { + [CLOCK_REALTIME] = "realtime", + [CLOCK_MONOTONIC] = "monotonic", + [CLOCK_PROCESS_CPUTIME_ID] = "process_cputime_id", + [CLOCK_THREAD_CPUTIME_ID] = "thread_cputime_id", + [CLOCK_MONOTONIC_RAW] = "monotonic_raw", + [CLOCK_REALTIME_COARSE] = "realtime_coarse", + [CLOCK_MONOTONIC_COARSE] = "monotonic_coarse", + [CLOCK_BOOTTIME] = "boottime", + [CLOCK_REALTIME_ALARM] = "realtime_alarm", + [CLOCK_BOOTTIME_ALARM] = "boottime_alarm", + [CLOCK_SGI_CYCLE] = "sgi_cycle", + [CLOCK_TAI] = "tai", +}; + +static ssize_t current_timestamp_clock_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + const struct iio_dev *indio_dev = dev_to_iio_dev(dev); + const clockid_t clk = iio_device_get_clock(indio_dev); + + switch (clk) { + case CLOCK_REALTIME: + case CLOCK_MONOTONIC: + case CLOCK_MONOTONIC_RAW: + case CLOCK_REALTIME_COARSE: + case CLOCK_MONOTONIC_COARSE: + case CLOCK_BOOTTIME: + case CLOCK_TAI: + break; + default: + BUG(); + } + + return sysfs_emit(buf, "%s\n", clock_names[clk]); +} + +static ssize_t current_timestamp_clock_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + clockid_t clk; + int ret; + + ret = sysfs_match_string(clock_names, buf); + if (ret < 0) + return ret; + clk = ret; + + switch (clk) { + case CLOCK_REALTIME: + case CLOCK_MONOTONIC: + case CLOCK_MONOTONIC_RAW: + case CLOCK_REALTIME_COARSE: + case CLOCK_MONOTONIC_COARSE: + case CLOCK_BOOTTIME: + case CLOCK_TAI: + break; + default: + return -EINVAL; + } + + ret = iio_device_set_clock(dev_to_iio_dev(dev), clk); + if (ret) + return ret; + + return len; +} + +int iio_device_register_sysfs_group(struct iio_dev *indio_dev, + const struct attribute_group *group) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + const struct attribute_group **new, **old = iio_dev_opaque->groups; + unsigned int cnt = iio_dev_opaque->groupcounter; + + new = krealloc_array(old, cnt + 2, sizeof(*new), GFP_KERNEL); + if (!new) + return -ENOMEM; + + new[iio_dev_opaque->groupcounter++] = group; + new[iio_dev_opaque->groupcounter] = NULL; + + iio_dev_opaque->groups = new; + + return 0; +} + +static DEVICE_ATTR_RW(current_timestamp_clock); + +static int iio_device_register_sysfs(struct iio_dev *indio_dev) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + int i, ret = 0, attrcount, attrn, attrcount_orig = 0; + struct iio_dev_attr *p; + struct attribute **attr, *clk = NULL; + + /* First count elements in any existing group */ + if (indio_dev->info->attrs) { + attr = indio_dev->info->attrs->attrs; + while (*attr++ != NULL) + attrcount_orig++; + } + attrcount = attrcount_orig; + /* + * New channel registration method - relies on the fact a group does + * not need to be initialized if its name is NULL. + */ + if (indio_dev->channels) + for (i = 0; i < indio_dev->num_channels; i++) { + const struct iio_chan_spec *chan = + &indio_dev->channels[i]; + + if (chan->type == IIO_TIMESTAMP) + clk = &dev_attr_current_timestamp_clock.attr; + + ret = iio_device_add_channel_sysfs(indio_dev, chan); + if (ret < 0) + goto error_clear_attrs; + attrcount += ret; + } + + if (iio_dev_opaque->event_interface) + clk = &dev_attr_current_timestamp_clock.attr; + + if (indio_dev->name) + attrcount++; + if (indio_dev->label) + attrcount++; + if (clk) + attrcount++; + + iio_dev_opaque->chan_attr_group.attrs = + kzalloc_objs(iio_dev_opaque->chan_attr_group.attrs[0], + attrcount + 1); + if (iio_dev_opaque->chan_attr_group.attrs == NULL) { + ret = -ENOMEM; + goto error_clear_attrs; + } + /* Copy across original attributes, and point to original binary attributes */ + if (indio_dev->info->attrs) { + memcpy(iio_dev_opaque->chan_attr_group.attrs, + indio_dev->info->attrs->attrs, + sizeof(iio_dev_opaque->chan_attr_group.attrs[0]) + *attrcount_orig); + iio_dev_opaque->chan_attr_group.is_visible = + indio_dev->info->attrs->is_visible; + iio_dev_opaque->chan_attr_group.bin_attrs = + indio_dev->info->attrs->bin_attrs; + } + attrn = attrcount_orig; + /* Add all elements from the list. */ + list_for_each_entry(p, &iio_dev_opaque->channel_attr_list, l) + iio_dev_opaque->chan_attr_group.attrs[attrn++] = &p->dev_attr.attr; + if (indio_dev->name) + iio_dev_opaque->chan_attr_group.attrs[attrn++] = &dev_attr_name.attr; + if (indio_dev->label) + iio_dev_opaque->chan_attr_group.attrs[attrn++] = &dev_attr_label.attr; + if (clk) + iio_dev_opaque->chan_attr_group.attrs[attrn++] = clk; + + ret = iio_device_register_sysfs_group(indio_dev, + &iio_dev_opaque->chan_attr_group); + if (ret) + goto error_free_chan_attrs; + + return 0; + +error_free_chan_attrs: + kfree(iio_dev_opaque->chan_attr_group.attrs); + iio_dev_opaque->chan_attr_group.attrs = NULL; +error_clear_attrs: + iio_free_chan_devattr_list(&iio_dev_opaque->channel_attr_list); + + return ret; +} + +static void iio_device_unregister_sysfs(struct iio_dev *indio_dev) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + + iio_free_chan_devattr_list(&iio_dev_opaque->channel_attr_list); + kfree(iio_dev_opaque->chan_attr_group.attrs); + iio_dev_opaque->chan_attr_group.attrs = NULL; + kfree(iio_dev_opaque->groups); + iio_dev_opaque->groups = NULL; +} + +static void iio_dev_release(struct device *device) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(device); + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + + if (indio_dev->modes & INDIO_ALL_TRIGGERED_MODES) + iio_device_unregister_trigger_consumer(indio_dev); + iio_device_unregister_eventset(indio_dev); + iio_device_unregister_sysfs(indio_dev); + + iio_device_detach_buffers(indio_dev); + + mutex_destroy(&iio_dev_opaque->info_exist_lock); + mutex_destroy(&iio_dev_opaque->mlock); + + lockdep_unregister_key(&iio_dev_opaque->info_exist_key); + lockdep_unregister_key(&iio_dev_opaque->mlock_key); + + ida_free(&iio_ida, iio_dev_opaque->id); + kfree(iio_dev_opaque); +} + +const struct device_type iio_device_type = { + .name = "iio_device", + .release = iio_dev_release, +}; + +/** + * iio_device_alloc() - allocate an iio_dev from a driver + * @parent: Parent device. + * @sizeof_priv: Space to allocate for private structure. + * + * Returns: + * Pointer to allocated iio_dev on success, NULL on failure. + */ +struct iio_dev *iio_device_alloc(struct device *parent, int sizeof_priv) +{ + struct iio_dev_opaque *iio_dev_opaque; + struct iio_dev *indio_dev; + size_t alloc_size; + + if (sizeof_priv) + alloc_size = ALIGN(sizeof(*iio_dev_opaque), IIO_DMA_MINALIGN) + sizeof_priv; + else + alloc_size = sizeof(*iio_dev_opaque); + + iio_dev_opaque = kzalloc(alloc_size, GFP_KERNEL); + if (!iio_dev_opaque) + return NULL; + + indio_dev = &iio_dev_opaque->indio_dev; + + if (sizeof_priv) + ACCESS_PRIVATE(indio_dev, priv) = (char *)iio_dev_opaque + + ALIGN(sizeof(*iio_dev_opaque), IIO_DMA_MINALIGN); + + INIT_LIST_HEAD(&iio_dev_opaque->channel_attr_list); + + iio_dev_opaque->id = ida_alloc(&iio_ida, GFP_KERNEL); + if (iio_dev_opaque->id < 0) { + /* cannot use a dev_err as the name isn't available */ + pr_err("failed to get device id\n"); + kfree(iio_dev_opaque); + return NULL; + } + + if (dev_set_name(&indio_dev->dev, "iio:device%d", iio_dev_opaque->id)) { + ida_free(&iio_ida, iio_dev_opaque->id); + kfree(iio_dev_opaque); + return NULL; + } + + INIT_LIST_HEAD(&iio_dev_opaque->buffer_list); + INIT_LIST_HEAD(&iio_dev_opaque->ioctl_handlers); + + lockdep_register_key(&iio_dev_opaque->mlock_key); + lockdep_register_key(&iio_dev_opaque->info_exist_key); + + mutex_init_with_key(&iio_dev_opaque->mlock, &iio_dev_opaque->mlock_key); + mutex_init_with_key(&iio_dev_opaque->info_exist_lock, &iio_dev_opaque->info_exist_key); + + indio_dev->dev.parent = parent; + indio_dev->dev.type = &iio_device_type; + indio_dev->dev.bus = &iio_bus_type; + device_initialize(&indio_dev->dev); + + return indio_dev; +} +EXPORT_SYMBOL(iio_device_alloc); + +/** + * iio_device_free() - free an iio_dev from a driver + * @dev: the iio_dev associated with the device + */ +void iio_device_free(struct iio_dev *dev) +{ + if (dev) + put_device(&dev->dev); +} +EXPORT_SYMBOL(iio_device_free); + +static void devm_iio_device_release(void *iio_dev) +{ + iio_device_free(iio_dev); +} + +/** + * devm_iio_device_alloc - Resource-managed iio_device_alloc() + * @parent: Device to allocate iio_dev for, and parent for this IIO device + * @sizeof_priv: Space to allocate for private structure. + * + * Managed iio_device_alloc. iio_dev allocated with this function is + * automatically freed on driver detach. + * + * Returns: + * Pointer to allocated iio_dev on success, NULL on failure. + */ +struct iio_dev *devm_iio_device_alloc(struct device *parent, int sizeof_priv) +{ + struct iio_dev *iio_dev; + int ret; + + iio_dev = iio_device_alloc(parent, sizeof_priv); + if (!iio_dev) + return NULL; + + ret = devm_add_action_or_reset(parent, devm_iio_device_release, + iio_dev); + if (ret) + return NULL; + + return iio_dev; +} +EXPORT_SYMBOL_GPL(devm_iio_device_alloc); + +/** + * iio_chrdev_open() - chrdev file open for buffer access and ioctls + * @inode: Inode structure for identifying the device in the file system + * @filp: File structure for iio device used to keep and later access + * private data + * + * Returns: 0 on success or -EBUSY if the device is already opened + */ +static int iio_chrdev_open(struct inode *inode, struct file *filp) +{ + struct iio_dev_opaque *iio_dev_opaque = + container_of(inode->i_cdev, struct iio_dev_opaque, chrdev); + struct iio_dev *indio_dev = &iio_dev_opaque->indio_dev; + struct iio_dev_buffer_pair *ib; + + if (test_and_set_bit(IIO_BUSY_BIT_POS, &iio_dev_opaque->flags)) + return -EBUSY; + + iio_device_get(indio_dev); + + ib = kmalloc_obj(*ib); + if (!ib) { + iio_device_put(indio_dev); + clear_bit(IIO_BUSY_BIT_POS, &iio_dev_opaque->flags); + return -ENOMEM; + } + + ib->indio_dev = indio_dev; + ib->buffer = indio_dev->buffer; + + filp->private_data = ib; + + return 0; +} + +/** + * iio_chrdev_release() - chrdev file close buffer access and ioctls + * @inode: Inode structure pointer for the char device + * @filp: File structure pointer for the char device + * + * Returns: 0 for successful release. + */ +static int iio_chrdev_release(struct inode *inode, struct file *filp) +{ + struct iio_dev_buffer_pair *ib = filp->private_data; + struct iio_dev_opaque *iio_dev_opaque = + container_of(inode->i_cdev, struct iio_dev_opaque, chrdev); + struct iio_dev *indio_dev = &iio_dev_opaque->indio_dev; + + kfree(ib); + clear_bit(IIO_BUSY_BIT_POS, &iio_dev_opaque->flags); + iio_device_put(indio_dev); + + return 0; +} + +void iio_device_ioctl_handler_register(struct iio_dev *indio_dev, + struct iio_ioctl_handler *h) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + + list_add_tail(&h->entry, &iio_dev_opaque->ioctl_handlers); +} + +void iio_device_ioctl_handler_unregister(struct iio_ioctl_handler *h) +{ + list_del(&h->entry); +} + +static long iio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct iio_dev_buffer_pair *ib = filp->private_data; + struct iio_dev *indio_dev = ib->indio_dev; + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + struct iio_ioctl_handler *h; + int ret; + + guard(mutex)(&iio_dev_opaque->info_exist_lock); + /* + * The NULL check here is required to prevent crashing when a device + * is being removed while userspace would still have open file handles + * to try to access this device. + */ + if (!indio_dev->info) + return -ENODEV; + + list_for_each_entry(h, &iio_dev_opaque->ioctl_handlers, entry) { + ret = h->ioctl(indio_dev, filp, cmd, arg); + if (ret != IIO_IOCTL_UNHANDLED) + return ret; + } + + return -ENODEV; +} + +static const struct file_operations iio_buffer_fileops = { + .owner = THIS_MODULE, + .llseek = noop_llseek, + .read = iio_buffer_read_outer_addr, + .write = iio_buffer_write_outer_addr, + .poll = iio_buffer_poll_addr, + .unlocked_ioctl = iio_ioctl, + .compat_ioctl = compat_ptr_ioctl, + .open = iio_chrdev_open, + .release = iio_chrdev_release, +}; + +static const struct file_operations iio_event_fileops = { + .owner = THIS_MODULE, + .llseek = noop_llseek, + .unlocked_ioctl = iio_ioctl, + .compat_ioctl = compat_ptr_ioctl, + .open = iio_chrdev_open, + .release = iio_chrdev_release, +}; + +static int iio_check_unique_scan_index(struct iio_dev *indio_dev) +{ + int i, j; + const struct iio_chan_spec *channels = indio_dev->channels; + + if (!(indio_dev->modes & INDIO_ALL_BUFFER_MODES)) + return 0; + + for (i = 0; i < indio_dev->num_channels - 1; i++) { + if (channels[i].scan_index < 0) + continue; + for (j = i + 1; j < indio_dev->num_channels; j++) + if (channels[i].scan_index == channels[j].scan_index) { + dev_err(&indio_dev->dev, + "Duplicate scan index %d\n", + channels[i].scan_index); + return -EINVAL; + } + } + + return 0; +} + +static int iio_check_extended_name(const struct iio_dev *indio_dev) +{ + unsigned int i; + + if (!indio_dev->info->read_label) + return 0; + + for (i = 0; i < indio_dev->num_channels; i++) { + if (indio_dev->channels[i].extend_name) { + dev_err(&indio_dev->dev, + "Cannot use labels and extend_name at the same time\n"); + return -EINVAL; + } + } + + return 0; +} + +static const struct iio_buffer_setup_ops noop_ring_setup_ops; + +static void iio_sanity_check_avail_scan_masks(struct iio_dev *indio_dev) +{ + unsigned int num_masks, masklength, longs_per_mask; + const unsigned long *av_masks; + int i; + + av_masks = indio_dev->available_scan_masks; + masklength = iio_get_masklength(indio_dev); + longs_per_mask = BITS_TO_LONGS(masklength); + + /* + * The code determining how many available_scan_masks is in the array + * will be assuming the end of masks when first long with all bits + * zeroed is encountered. This is incorrect for masks where mask + * consists of more than one long, and where some of the available masks + * has long worth of bits zeroed (but has subsequent bit(s) set). This + * is a safety measure against bug where array of masks is terminated by + * a single zero while mask width is greater than width of a long. + */ + if (longs_per_mask > 1) + dev_warn(indio_dev->dev.parent, + "multi long available scan masks not fully supported\n"); + + if (bitmap_empty(av_masks, masklength)) + dev_warn(indio_dev->dev.parent, "empty scan mask\n"); + + for (num_masks = 0; *av_masks; num_masks++) + av_masks += longs_per_mask; + + if (num_masks < 2) + return; + + av_masks = indio_dev->available_scan_masks; + + /* + * Go through all the masks from first to one before the last, and see + * that no mask found later from the available_scan_masks array is a + * subset of mask found earlier. If this happens, then the mask found + * later will never get used because scanning the array is stopped when + * the first suitable mask is found. Drivers should order the array of + * available masks in the order of preference (presumably the least + * costy to access masks first). + */ + for (i = 0; i < num_masks - 1; i++) { + const unsigned long *mask1; + int j; + + mask1 = av_masks + i * longs_per_mask; + for (j = i + 1; j < num_masks; j++) { + const unsigned long *mask2; + + mask2 = av_masks + j * longs_per_mask; + if (bitmap_subset(mask2, mask1, masklength)) + dev_warn(indio_dev->dev.parent, + "available_scan_mask %d subset of %d. Never used\n", + j, i); + } + } +} + +/** + * iio_active_scan_mask_index - Get index of the active scan mask inside the + * available scan masks array + * @indio_dev: the IIO device containing the active and available scan masks + * + * Returns: the index or -EINVAL if active_scan_mask is not set + */ +int iio_active_scan_mask_index(struct iio_dev *indio_dev) + +{ + const unsigned long *av_masks; + unsigned int masklength = iio_get_masklength(indio_dev); + int i = 0; + + if (!indio_dev->active_scan_mask) + return -EINVAL; + + /* + * As in iio_scan_mask_match and iio_sanity_check_avail_scan_masks, + * the condition here do not handle multi-long masks correctly. + * It only checks the first long to be zero, and will use such mask + * as a terminator even if there was bits set after the first long. + * + * This should be fine since the available_scan_mask has already been + * sanity tested using iio_sanity_check_avail_scan_masks. + * + * See iio_scan_mask_match and iio_sanity_check_avail_scan_masks for + * more details + */ + av_masks = indio_dev->available_scan_masks; + while (*av_masks) { + if (indio_dev->active_scan_mask == av_masks) + return i; + av_masks += BITS_TO_LONGS(masklength); + i++; + } + + dev_warn(indio_dev->dev.parent, + "active scan mask is not part of the available scan masks\n"); + return -EINVAL; +} +EXPORT_SYMBOL_GPL(iio_active_scan_mask_index); + +int __iio_device_register(struct iio_dev *indio_dev, struct module *this_mod) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + struct fwnode_handle *fwnode = NULL; + int ret; + + if (!indio_dev->info) + return -EINVAL; + + iio_dev_opaque->driver_module = this_mod; + + /* If the calling driver did not initialize firmware node, do it here */ + if (dev_fwnode(&indio_dev->dev)) + fwnode = dev_fwnode(&indio_dev->dev); + /* The default dummy IIO device has no parent */ + else if (indio_dev->dev.parent) + fwnode = dev_fwnode(indio_dev->dev.parent); + device_set_node(&indio_dev->dev, fwnode); + + fwnode_property_read_string(fwnode, "label", &indio_dev->label); + + ret = iio_check_unique_scan_index(indio_dev); + if (ret < 0) + return ret; + + ret = iio_check_extended_name(indio_dev); + if (ret < 0) + return ret; + + iio_device_register_debugfs(indio_dev); + + ret = iio_buffers_alloc_sysfs_and_mask(indio_dev); + if (ret) { + dev_err(indio_dev->dev.parent, + "Failed to create buffer sysfs interfaces\n"); + goto error_unreg_debugfs; + } + + if (indio_dev->available_scan_masks) + iio_sanity_check_avail_scan_masks(indio_dev); + + ret = iio_device_register_sysfs(indio_dev); + if (ret) { + dev_err(indio_dev->dev.parent, + "Failed to register sysfs interfaces\n"); + goto error_buffer_free_sysfs; + } + ret = iio_device_register_eventset(indio_dev); + if (ret) { + dev_err(indio_dev->dev.parent, + "Failed to register event set\n"); + goto error_free_sysfs; + } + if (indio_dev->modes & INDIO_ALL_TRIGGERED_MODES) + iio_device_register_trigger_consumer(indio_dev); + + if ((indio_dev->modes & INDIO_ALL_BUFFER_MODES) && + indio_dev->setup_ops == NULL) + indio_dev->setup_ops = &noop_ring_setup_ops; + + if (iio_dev_opaque->attached_buffers_cnt) + cdev_init(&iio_dev_opaque->chrdev, &iio_buffer_fileops); + else if (iio_dev_opaque->event_interface) + cdev_init(&iio_dev_opaque->chrdev, &iio_event_fileops); + + if (iio_dev_opaque->attached_buffers_cnt || iio_dev_opaque->event_interface) { + indio_dev->dev.devt = MKDEV(MAJOR(iio_devt), iio_dev_opaque->id); + iio_dev_opaque->chrdev.owner = this_mod; + } + + /* assign device groups now; they should be all registered now */ + indio_dev->dev.groups = iio_dev_opaque->groups; + + ret = cdev_device_add(&iio_dev_opaque->chrdev, &indio_dev->dev); + if (ret < 0) + goto error_unreg_eventset; + + return 0; + +error_unreg_eventset: + iio_device_unregister_eventset(indio_dev); +error_free_sysfs: + iio_device_unregister_sysfs(indio_dev); +error_buffer_free_sysfs: + iio_buffers_free_sysfs_and_mask(indio_dev); +error_unreg_debugfs: + iio_device_unregister_debugfs(indio_dev); + return ret; +} +EXPORT_SYMBOL(__iio_device_register); + +/** + * iio_device_unregister() - unregister a device from the IIO subsystem + * @indio_dev: Device structure representing the device. + */ +void iio_device_unregister(struct iio_dev *indio_dev) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + + cdev_device_del(&iio_dev_opaque->chrdev, &indio_dev->dev); + + scoped_guard(mutex, &iio_dev_opaque->info_exist_lock) { + iio_device_unregister_debugfs(indio_dev); + + iio_disable_all_buffers(indio_dev); + + indio_dev->info = NULL; + + iio_device_wakeup_eventset(indio_dev); + iio_buffer_wakeup_poll(indio_dev); + } + + iio_buffers_free_sysfs_and_mask(indio_dev); +} +EXPORT_SYMBOL(iio_device_unregister); + +static void devm_iio_device_unreg(void *indio_dev) +{ + iio_device_unregister(indio_dev); +} + +int __devm_iio_device_register(struct device *dev, struct iio_dev *indio_dev, + struct module *this_mod) +{ + int ret; + + ret = __iio_device_register(indio_dev, this_mod); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, devm_iio_device_unreg, indio_dev); +} +EXPORT_SYMBOL_GPL(__devm_iio_device_register); + +/** + * __iio_dev_mode_lock() - Locks the current IIO device mode + * @indio_dev: the iio_dev associated with the device + * + * If the device is either in direct or buffer mode, it's guaranteed to stay + * that way until __iio_dev_mode_unlock() is called. + * + * This function is not meant to be used directly by drivers to protect internal + * state; a driver should have it's own mechanisms for that matter. + * + * There are very few cases where a driver actually needs to lock the current + * mode unconditionally. It's recommended to use iio_device_claim_direct() or + * iio_device_try_claim_buffer_mode() pairs or related helpers instead. + */ +void __iio_dev_mode_lock(struct iio_dev *indio_dev) +{ + mutex_lock(&to_iio_dev_opaque(indio_dev)->mlock); +} +EXPORT_SYMBOL_GPL(__iio_dev_mode_lock); + +/** + * __iio_dev_mode_unlock() - Unlocks the current IIO device mode + * @indio_dev: the iio_dev associated with the device + */ +void __iio_dev_mode_unlock(struct iio_dev *indio_dev) +{ + mutex_unlock(&to_iio_dev_opaque(indio_dev)->mlock); +} +EXPORT_SYMBOL_GPL(__iio_dev_mode_unlock); + +/** + * iio_device_get_current_mode() - helper function providing read-only access to + * the opaque @currentmode variable + * @indio_dev: IIO device structure for device + */ +int iio_device_get_current_mode(struct iio_dev *indio_dev) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + + return iio_dev_opaque->currentmode; +} +EXPORT_SYMBOL_GPL(iio_device_get_current_mode); + +subsys_initcall(iio_init); +module_exit(iio_exit); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("Industrial I/O core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/industrialio-event.c b/drivers/iio/industrialio-event.c new file mode 100644 index 000000000..e6730f522 --- /dev/null +++ b/drivers/iio/industrialio-event.c @@ -0,0 +1,672 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Industrial I/O event handling + * + * Copyright (c) 2008 Jonathan Cameron + * + * Based on elements of hwmon and input subsystems. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "iio_core.h" +#include +#include + +/** + * struct iio_event_interface - chrdev interface for an event line + * @wait: wait queue to allow blocking reads of events + * @det_events: list of detected events + * @dev_attr_list: list of event interface sysfs attribute + * @flags: file operations related flags including busy flag. + * @group: event interface sysfs attribute group + * @read_lock: lock to protect kfifo read operations + * @ioctl_handler: handler for event ioctl() calls + */ +struct iio_event_interface { + wait_queue_head_t wait; + DECLARE_KFIFO(det_events, struct iio_event_data, 16); + + struct list_head dev_attr_list; + unsigned long flags; + struct attribute_group group; + struct mutex read_lock; + struct iio_ioctl_handler ioctl_handler; +}; + +bool iio_event_enabled(const struct iio_event_interface *ev_int) +{ + return !!test_bit(IIO_BUSY_BIT_POS, &ev_int->flags); +} + +/** + * iio_push_event() - try to add event to the list for userspace reading + * @indio_dev: IIO device structure + * @ev_code: What event + * @timestamp: When the event occurred + * + * Note: The caller must make sure that this function is not running + * concurrently for the same indio_dev more than once. + * + * This function may be safely used as soon as a valid reference to iio_dev has + * been obtained via iio_device_alloc(), but any events that are submitted + * before iio_device_register() has successfully completed will be silently + * discarded. + **/ +int iio_push_event(struct iio_dev *indio_dev, u64 ev_code, s64 timestamp) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + struct iio_event_interface *ev_int = iio_dev_opaque->event_interface; + struct iio_event_data ev; + int copied; + + if (!ev_int) + return 0; + + /* Does anyone care? */ + if (iio_event_enabled(ev_int)) { + + ev.id = ev_code; + ev.timestamp = timestamp; + + copied = kfifo_put(&ev_int->det_events, ev); + if (copied != 0) + wake_up_poll(&ev_int->wait, EPOLLIN); + } + + return 0; +} +EXPORT_SYMBOL(iio_push_event); + +/** + * iio_event_poll() - poll the event queue to find out if it has data + * @filep: File structure pointer to identify the device + * @wait: Poll table pointer to add the wait queue on + * + * Return: (EPOLLIN | EPOLLRDNORM) if data is available for reading + * or a negative error code on failure + */ +static __poll_t iio_event_poll(struct file *filep, + struct poll_table_struct *wait) +{ + struct iio_dev *indio_dev = filep->private_data; + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + struct iio_event_interface *ev_int = iio_dev_opaque->event_interface; + __poll_t events = 0; + + if (!indio_dev->info) + return events; + + poll_wait(filep, &ev_int->wait, wait); + + if (!kfifo_is_empty(&ev_int->det_events)) + events = EPOLLIN | EPOLLRDNORM; + + return events; +} + +static ssize_t iio_event_chrdev_read(struct file *filep, + char __user *buf, + size_t count, + loff_t *f_ps) +{ + struct iio_dev *indio_dev = filep->private_data; + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + struct iio_event_interface *ev_int = iio_dev_opaque->event_interface; + unsigned int copied; + int ret; + + if (!indio_dev->info) + return -ENODEV; + + if (count < sizeof(struct iio_event_data)) + return -EINVAL; + + do { + if (kfifo_is_empty(&ev_int->det_events)) { + if (filep->f_flags & O_NONBLOCK) + return -EAGAIN; + + ret = wait_event_interruptible(ev_int->wait, + !kfifo_is_empty(&ev_int->det_events) || + indio_dev->info == NULL); + if (ret) + return ret; + if (indio_dev->info == NULL) + return -ENODEV; + } + + if (mutex_lock_interruptible(&ev_int->read_lock)) + return -ERESTARTSYS; + ret = kfifo_to_user(&ev_int->det_events, buf, count, &copied); + mutex_unlock(&ev_int->read_lock); + + if (ret) + return ret; + + /* + * If we couldn't read anything from the fifo (a different + * thread might have been faster) we either return -EAGAIN if + * the file descriptor is non-blocking, otherwise we go back to + * sleep and wait for more data to arrive. + */ + if (copied == 0 && (filep->f_flags & O_NONBLOCK)) + return -EAGAIN; + + } while (copied == 0); + + return copied; +} + +static int iio_event_chrdev_release(struct inode *inode, struct file *filep) +{ + struct iio_dev *indio_dev = filep->private_data; + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + struct iio_event_interface *ev_int = iio_dev_opaque->event_interface; + + clear_bit(IIO_BUSY_BIT_POS, &ev_int->flags); + + iio_device_put(indio_dev); + + return 0; +} + +static const struct file_operations iio_event_chrdev_fileops = { + .read = iio_event_chrdev_read, + .poll = iio_event_poll, + .release = iio_event_chrdev_release, + .owner = THIS_MODULE, + .llseek = noop_llseek, +}; + +static int iio_event_getfd(struct iio_dev *indio_dev) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + struct iio_event_interface *ev_int = iio_dev_opaque->event_interface; + int fd; + + if (ev_int == NULL) + return -ENODEV; + + fd = mutex_lock_interruptible(&iio_dev_opaque->mlock); + if (fd) + return fd; + + if (test_and_set_bit(IIO_BUSY_BIT_POS, &ev_int->flags)) { + fd = -EBUSY; + goto unlock; + } + + kfifo_reset_out(&ev_int->det_events); + + iio_device_get(indio_dev); + + fd = anon_inode_getfd("iio:event", &iio_event_chrdev_fileops, + indio_dev, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + clear_bit(IIO_BUSY_BIT_POS, &ev_int->flags); + iio_device_put(indio_dev); + } +unlock: + mutex_unlock(&iio_dev_opaque->mlock); + return fd; +} + +static const char * const iio_ev_type_text[] = { + [IIO_EV_TYPE_THRESH] = "thresh", + [IIO_EV_TYPE_MAG] = "mag", + [IIO_EV_TYPE_ROC] = "roc", + [IIO_EV_TYPE_THRESH_ADAPTIVE] = "thresh_adaptive", + [IIO_EV_TYPE_MAG_ADAPTIVE] = "mag_adaptive", + [IIO_EV_TYPE_CHANGE] = "change", + [IIO_EV_TYPE_MAG_REFERENCED] = "mag_referenced", + [IIO_EV_TYPE_GESTURE] = "gesture", + [IIO_EV_TYPE_FAULT] = "fault", +}; + +static const char * const iio_ev_dir_text[] = { + [IIO_EV_DIR_EITHER] = "either", + [IIO_EV_DIR_RISING] = "rising", + [IIO_EV_DIR_FALLING] = "falling", + [IIO_EV_DIR_SINGLETAP] = "singletap", + [IIO_EV_DIR_DOUBLETAP] = "doubletap", + [IIO_EV_DIR_FAULT_OPENWIRE] = "openwire", +}; + +static const char * const iio_ev_info_text[] = { + [IIO_EV_INFO_ENABLE] = "en", + [IIO_EV_INFO_VALUE] = "value", + [IIO_EV_INFO_HYSTERESIS] = "hysteresis", + [IIO_EV_INFO_PERIOD] = "period", + [IIO_EV_INFO_HIGH_PASS_FILTER_3DB] = "high_pass_filter_3db", + [IIO_EV_INFO_LOW_PASS_FILTER_3DB] = "low_pass_filter_3db", + [IIO_EV_INFO_TIMEOUT] = "timeout", + [IIO_EV_INFO_RESET_TIMEOUT] = "reset_timeout", + [IIO_EV_INFO_TAP2_MIN_DELAY] = "tap2_min_delay", + [IIO_EV_INFO_RUNNING_PERIOD] = "runningperiod", + [IIO_EV_INFO_RUNNING_COUNT] = "runningcount", + [IIO_EV_INFO_SCALE] = "scale", +}; + +static enum iio_event_direction iio_ev_attr_dir(struct iio_dev_attr *attr) +{ + return attr->c->event_spec[attr->address & 0xffff].dir; +} + +static enum iio_event_type iio_ev_attr_type(struct iio_dev_attr *attr) +{ + return attr->c->event_spec[attr->address & 0xffff].type; +} + +static enum iio_event_info iio_ev_attr_info(struct iio_dev_attr *attr) +{ + return (attr->address >> 16) & 0xffff; +} + +static ssize_t iio_ev_state_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + bool val; + + ret = kstrtobool(buf, &val); + if (ret < 0) + return ret; + + if (!indio_dev->info->write_event_config) + return -EINVAL; + + ret = indio_dev->info->write_event_config(indio_dev, + this_attr->c, iio_ev_attr_type(this_attr), + iio_ev_attr_dir(this_attr), val); + + return (ret < 0) ? ret : len; +} + +static ssize_t iio_ev_state_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int val; + + if (!indio_dev->info->read_event_config) + return -EINVAL; + + val = indio_dev->info->read_event_config(indio_dev, + this_attr->c, iio_ev_attr_type(this_attr), + iio_ev_attr_dir(this_attr)); + if (val < 0) + return val; + else + return sysfs_emit(buf, "%d\n", val); +} + +static ssize_t iio_ev_value_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int val, val2, val_arr[2]; + int ret; + + if (!indio_dev->info->read_event_value) + return -EINVAL; + + ret = indio_dev->info->read_event_value(indio_dev, + this_attr->c, iio_ev_attr_type(this_attr), + iio_ev_attr_dir(this_attr), iio_ev_attr_info(this_attr), + &val, &val2); + if (ret < 0) + return ret; + val_arr[0] = val; + val_arr[1] = val2; + return iio_format_value(buf, ret, 2, val_arr); +} + +static ssize_t iio_ev_value_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int val, val2; + int ret; + + if (!indio_dev->info->write_event_value) + return -EINVAL; + + ret = iio_str_to_fixpoint(buf, 100000, &val, &val2); + if (ret) + return ret; + ret = indio_dev->info->write_event_value(indio_dev, + this_attr->c, iio_ev_attr_type(this_attr), + iio_ev_attr_dir(this_attr), iio_ev_attr_info(this_attr), + val, val2); + if (ret < 0) + return ret; + + return len; +} + +static ssize_t iio_ev_label_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + if (indio_dev->info->read_event_label) + return indio_dev->info->read_event_label(indio_dev, + this_attr->c, iio_ev_attr_type(this_attr), + iio_ev_attr_dir(this_attr), buf); + + return -EINVAL; +} + +static int iio_device_add_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int spec_index, + enum iio_event_type type, enum iio_event_direction dir, + enum iio_shared_by shared_by, const unsigned long *mask) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + ssize_t (*show)(struct device *dev, struct device_attribute *attr, + char *buf); + ssize_t (*store)(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len); + unsigned int attrcount = 0; + unsigned int i; + char *postfix; + int ret; + + for_each_set_bit(i, mask, sizeof(*mask)*8) { + if (i >= ARRAY_SIZE(iio_ev_info_text)) + return -EINVAL; + if (dir != IIO_EV_DIR_NONE) + postfix = kasprintf(GFP_KERNEL, "%s_%s_%s", + iio_ev_type_text[type], + iio_ev_dir_text[dir], + iio_ev_info_text[i]); + else + postfix = kasprintf(GFP_KERNEL, "%s_%s", + iio_ev_type_text[type], + iio_ev_info_text[i]); + if (postfix == NULL) + return -ENOMEM; + + if (i == IIO_EV_INFO_ENABLE) { + show = iio_ev_state_show; + store = iio_ev_state_store; + } else { + show = iio_ev_value_show; + store = iio_ev_value_store; + } + + ret = __iio_add_chan_devattr(postfix, chan, show, store, + (i << 16) | spec_index, shared_by, &indio_dev->dev, + NULL, + &iio_dev_opaque->event_interface->dev_attr_list); + kfree(postfix); + + if ((ret == -EBUSY) && (shared_by != IIO_SEPARATE)) + continue; + + if (ret) + return ret; + + attrcount++; + } + + return attrcount; +} + +static int iio_device_add_event_label(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int spec_index, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + char *postfix; + int ret; + + if (!indio_dev->info->read_event_label) + return 0; + + if (dir != IIO_EV_DIR_NONE) + postfix = kasprintf(GFP_KERNEL, "%s_%s_label", + iio_ev_type_text[type], + iio_ev_dir_text[dir]); + else + postfix = kasprintf(GFP_KERNEL, "%s_label", + iio_ev_type_text[type]); + if (postfix == NULL) + return -ENOMEM; + + ret = __iio_add_chan_devattr(postfix, chan, &iio_ev_label_show, NULL, + spec_index, IIO_SEPARATE, &indio_dev->dev, NULL, + &iio_dev_opaque->event_interface->dev_attr_list); + + kfree(postfix); + + if (ret < 0) + return ret; + + return 1; +} + +static int iio_device_add_event_sysfs(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + int ret = 0, i, attrcount = 0; + enum iio_event_direction dir; + enum iio_event_type type; + + for (i = 0; i < chan->num_event_specs; i++) { + type = chan->event_spec[i].type; + dir = chan->event_spec[i].dir; + + ret = iio_device_add_event(indio_dev, chan, i, type, dir, + IIO_SEPARATE, &chan->event_spec[i].mask_separate); + if (ret < 0) + return ret; + attrcount += ret; + + ret = iio_device_add_event(indio_dev, chan, i, type, dir, + IIO_SHARED_BY_TYPE, + &chan->event_spec[i].mask_shared_by_type); + if (ret < 0) + return ret; + attrcount += ret; + + ret = iio_device_add_event(indio_dev, chan, i, type, dir, + IIO_SHARED_BY_DIR, + &chan->event_spec[i].mask_shared_by_dir); + if (ret < 0) + return ret; + attrcount += ret; + + ret = iio_device_add_event(indio_dev, chan, i, type, dir, + IIO_SHARED_BY_ALL, + &chan->event_spec[i].mask_shared_by_all); + if (ret < 0) + return ret; + attrcount += ret; + + ret = iio_device_add_event_label(indio_dev, chan, i, type, dir); + if (ret < 0) + return ret; + attrcount += ret; + } + ret = attrcount; + return ret; +} + +static inline int __iio_add_event_config_attrs(struct iio_dev *indio_dev) +{ + int j, ret, attrcount = 0; + + /* Dynamically created from the channels array */ + for (j = 0; j < indio_dev->num_channels; j++) { + ret = iio_device_add_event_sysfs(indio_dev, + &indio_dev->channels[j]); + if (ret < 0) + return ret; + attrcount += ret; + } + return attrcount; +} + +static bool iio_check_for_dynamic_events(struct iio_dev *indio_dev) +{ + int j; + + for (j = 0; j < indio_dev->num_channels; j++) { + if (indio_dev->channels[j].num_event_specs != 0) + return true; + } + return false; +} + +static void iio_setup_ev_int(struct iio_event_interface *ev_int) +{ + INIT_KFIFO(ev_int->det_events); + init_waitqueue_head(&ev_int->wait); + mutex_init(&ev_int->read_lock); +} + +static long iio_event_ioctl(struct iio_dev *indio_dev, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + int __user *ip = (int __user *)arg; + int fd; + + if (cmd == IIO_GET_EVENT_FD_IOCTL) { + fd = iio_event_getfd(indio_dev); + if (fd < 0) + return fd; + if (copy_to_user(ip, &fd, sizeof(fd))) + return -EFAULT; + return 0; + } + + return IIO_IOCTL_UNHANDLED; +} + +static const char *iio_event_group_name = "events"; +int iio_device_register_eventset(struct iio_dev *indio_dev) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + struct iio_event_interface *ev_int; + struct iio_dev_attr *p; + int ret = 0, attrcount_orig = 0, attrcount, attrn; + struct attribute **attr; + + if (!(indio_dev->info->event_attrs || + iio_check_for_dynamic_events(indio_dev))) + return 0; + + ev_int = kzalloc_obj(*ev_int); + if (!ev_int) + return -ENOMEM; + + iio_dev_opaque->event_interface = ev_int; + + INIT_LIST_HEAD(&ev_int->dev_attr_list); + + iio_setup_ev_int(ev_int); + if (indio_dev->info->event_attrs != NULL) { + attr = indio_dev->info->event_attrs->attrs; + while (*attr++ != NULL) + attrcount_orig++; + } + attrcount = attrcount_orig; + if (indio_dev->channels) { + ret = __iio_add_event_config_attrs(indio_dev); + if (ret < 0) + goto error_free_setup_event_lines; + attrcount += ret; + } + + ev_int->group.name = iio_event_group_name; + ev_int->group.attrs = kzalloc_objs(ev_int->group.attrs[0], + attrcount + 1); + if (ev_int->group.attrs == NULL) { + ret = -ENOMEM; + goto error_free_setup_event_lines; + } + if (indio_dev->info->event_attrs) + memcpy(ev_int->group.attrs, + indio_dev->info->event_attrs->attrs, + sizeof(ev_int->group.attrs[0]) * attrcount_orig); + attrn = attrcount_orig; + /* Add all elements from the list. */ + list_for_each_entry(p, &ev_int->dev_attr_list, l) + ev_int->group.attrs[attrn++] = &p->dev_attr.attr; + + ret = iio_device_register_sysfs_group(indio_dev, &ev_int->group); + if (ret) + goto error_free_group_attrs; + + ev_int->ioctl_handler.ioctl = iio_event_ioctl; + iio_device_ioctl_handler_register(&iio_dev_opaque->indio_dev, + &ev_int->ioctl_handler); + + return 0; + +error_free_group_attrs: + kfree(ev_int->group.attrs); +error_free_setup_event_lines: + iio_free_chan_devattr_list(&ev_int->dev_attr_list); + kfree(ev_int); + iio_dev_opaque->event_interface = NULL; + return ret; +} + +/** + * iio_device_wakeup_eventset - Wakes up the event waitqueue + * @indio_dev: The IIO device + * + * Wakes up the event waitqueue used for poll() and blocking read(). + * Should usually be called when the device is unregistered. + */ +void iio_device_wakeup_eventset(struct iio_dev *indio_dev) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + + if (iio_dev_opaque->event_interface == NULL) + return; + wake_up(&iio_dev_opaque->event_interface->wait); +} + +void iio_device_unregister_eventset(struct iio_dev *indio_dev) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + struct iio_event_interface *ev_int = iio_dev_opaque->event_interface; + + if (ev_int == NULL) + return; + + iio_device_ioctl_handler_unregister(&ev_int->ioctl_handler); + iio_free_chan_devattr_list(&ev_int->dev_attr_list); + kfree(ev_int->group.attrs); + kfree(ev_int); + iio_dev_opaque->event_interface = NULL; +} diff --git a/drivers/iio/industrialio-gts-helper.c b/drivers/iio/industrialio-gts-helper.c new file mode 100644 index 000000000..4f52dc373 --- /dev/null +++ b/drivers/iio/industrialio-gts-helper.c @@ -0,0 +1,1253 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* gain-time-scale conversion helpers for IIO light sensors + * + * Copyright (c) 2023 Matti Vaittinen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/** + * iio_gts_get_gain - Convert scale to total gain + * + * Internal helper for converting scale to total gain. + * + * @max: Maximum linearized scale. As an example, when scale is created + * in magnitude of NANOs and max scale is 64.1 - The linearized + * scale is 64 100 000 000. + * @scale: Linearized scale to compute the gain for. + * + * Return: (floored) gain corresponding to the scale. -EINVAL if scale + * is invalid. + */ +static int iio_gts_get_gain(const u64 max, const u64 scale) +{ + u64 full = max; + + if (scale > full || !scale) + return -EINVAL; + + return div64_u64(full, scale); +} + +/** + * gain_get_scale_fraction - get the gain or time based on scale and known one + * + * @max: Maximum linearized scale. As an example, when scale is created + * in magnitude of NANOs and max scale is 64.1 - The linearized + * scale is 64 100 000 000. + * @scale: Linearized scale to compute the gain/time for. + * @known: Either integration time or gain depending on which one is known + * @unknown: Pointer to variable where the computed gain/time is stored + * + * Internal helper for computing unknown fraction of total gain. + * Compute either gain or time based on scale and either the gain or time + * depending on which one is known. + * + * Return: 0 on success. + */ +static int gain_get_scale_fraction(const u64 max, u64 scale, int known, + int *unknown) +{ + int tot_gain; + + tot_gain = iio_gts_get_gain(max, scale); + if (tot_gain < 0) + return tot_gain; + + *unknown = tot_gain / known; + + /* We require total gain to be exact multiple of known * unknown */ + if (!*unknown || *unknown * known != tot_gain) + return -EINVAL; + + return 0; +} + +static int iio_gts_delinearize(u64 lin_scale, unsigned long scaler, + int *scale_whole, int *scale_nano) +{ + int frac; + + if (scaler > NANO) + return -EOVERFLOW; + + if (!scaler) + return -EINVAL; + + frac = do_div(lin_scale, scaler); + + *scale_whole = lin_scale; + *scale_nano = frac * (NANO / scaler); + + return 0; +} + +static int iio_gts_linearize(int scale_whole, int scale_nano, + unsigned long scaler, u64 *lin_scale) +{ + /* + * Expect scale to be (mostly) NANO or MICRO. Divide divider instead of + * multiplication followed by division to avoid overflow. + */ + if (scaler > NANO || !scaler) + return -EINVAL; + + *lin_scale = (u64)scale_whole * (u64)scaler + + (u64)(scale_nano / (NANO / scaler)); + + return 0; +} + +/** + * iio_gts_total_gain_to_scale - convert gain to scale + * @gts: Gain time scale descriptor + * @total_gain: the gain to be converted + * @scale_int: Pointer to integral part of the scale (typically val1) + * @scale_nano: Pointer to fractional part of the scale (nano or ppb) + * + * Convert the total gain value to scale. NOTE: This does not separate gain + * generated by HW-gain or integration time. It is up to caller to decide what + * part of the total gain is due to integration time and what due to HW-gain. + * + * Return: 0 on success. Negative errno on failure. + */ +int iio_gts_total_gain_to_scale(struct iio_gts *gts, int total_gain, + int *scale_int, int *scale_nano) +{ + u64 tmp; + + tmp = gts->max_scale; + + do_div(tmp, total_gain); + + return iio_gts_delinearize(tmp, NANO, scale_int, scale_nano); +} +EXPORT_SYMBOL_NS_GPL(iio_gts_total_gain_to_scale, "IIO_GTS_HELPER"); + +/** + * iio_gts_purge_avail_scale_table - free-up the available scale tables + * @gts: Gain time scale descriptor + * + * Free the space reserved by iio_gts_build_avail_scale_table(). + */ +static void iio_gts_purge_avail_scale_table(struct iio_gts *gts) +{ + int i; + + if (gts->per_time_avail_scale_tables) { + for (i = 0; i < gts->num_itime; i++) + kfree(gts->per_time_avail_scale_tables[i]); + + kfree(gts->per_time_avail_scale_tables); + gts->per_time_avail_scale_tables = NULL; + } + + kfree(gts->avail_all_scales_table); + gts->avail_all_scales_table = NULL; + + gts->num_avail_all_scales = 0; +} + +static int scale_eq(int *sc1, int *sc2) +{ + return sc1[0] == sc2[0] && sc1[1] == sc2[1]; +} + +static int scale_smaller(int *sc1, int *sc2) +{ + if (sc1[0] != sc2[0]) + return sc1[0] < sc2[0]; + + /* If integer parts are equal, fixp parts */ + return sc1[1] < sc2[1]; +} + +/* + * Do a single table listing all the unique scales that any combination of + * supported gains and times can provide. + */ +static int do_combined_scaletable(struct iio_gts *gts, + size_t all_scales_tbl_bytes) +{ + int t_idx, i, new_idx; + int **scales = gts->per_time_avail_scale_tables; + int *all_scales = kcalloc(gts->num_itime, all_scales_tbl_bytes, + GFP_KERNEL); + + if (!all_scales) + return -ENOMEM; + /* + * Create table containing all of the supported scales by looping + * through all of the per-time scales and copying the unique scales + * into one sorted table. + * + * We assume all the gains for same integration time were unique. + * It is likely the first time table had greatest time multiplier as + * the times are in the order of preference and greater times are + * usually preferred. Hence we start from the last table which is likely + * to have the smallest total gains. + */ + t_idx = gts->num_itime - 1; + memcpy(all_scales, scales[t_idx], all_scales_tbl_bytes); + new_idx = gts->num_hwgain * 2; + + while (t_idx-- > 0) { + for (i = 0; i < gts->num_hwgain ; i++) { + int *candidate = &scales[t_idx][i * 2]; + int chk; + + if (scale_smaller(candidate, &all_scales[new_idx - 2])) { + all_scales[new_idx] = candidate[0]; + all_scales[new_idx + 1] = candidate[1]; + new_idx += 2; + + continue; + } + for (chk = 0; chk < new_idx; chk += 2) + if (!scale_smaller(candidate, &all_scales[chk])) + break; + + if (scale_eq(candidate, &all_scales[chk])) + continue; + + memmove(&all_scales[chk + 2], &all_scales[chk], + (new_idx - chk) * sizeof(int)); + all_scales[chk] = candidate[0]; + all_scales[chk + 1] = candidate[1]; + new_idx += 2; + } + } + + gts->num_avail_all_scales = new_idx / 2; + gts->avail_all_scales_table = all_scales; + + return 0; +} + +static void iio_gts_free_int_table_array(int **arr, int num_tables) +{ + int i; + + for (i = 0; i < num_tables; i++) + kfree(arr[i]); + + kfree(arr); +} + +static int iio_gts_alloc_int_table_array(int ***arr, int num_tables, int num_table_items) +{ + int i, **tmp; + + tmp = kzalloc_objs(**arr, num_tables); + if (!tmp) + return -ENOMEM; + + for (i = 0; i < num_tables; i++) { + tmp[i] = kzalloc_objs(int, num_table_items); + if (!tmp[i]) + goto err_free; + } + + *arr = tmp; + + return 0; +err_free: + iio_gts_free_int_table_array(tmp, i); + + return -ENOMEM; +} + +static int iio_gts_gain_cmp(const void *a, const void *b) +{ + return *(int *)a - *(int *)b; +} + +static int fill_and_sort_scaletables(struct iio_gts *gts, int **gains, int **scales) +{ + int i, j, ret; + + for (i = 0; i < gts->num_itime; i++) { + /* + * Sort the tables for nice output and for easier finding of + * unique values. + */ + sort(gains[i], gts->num_hwgain, sizeof(int), iio_gts_gain_cmp, + NULL); + + /* Convert gains to scales */ + for (j = 0; j < gts->num_hwgain; j++) { + ret = iio_gts_total_gain_to_scale(gts, gains[i][j], + &scales[i][2 * j], + &scales[i][2 * j + 1]); + if (ret) + return ret; + } + } + + return 0; +} + +static void compute_per_time_gains(struct iio_gts *gts, int **gains) +{ + int i, j; + + for (i = 0; i < gts->num_itime; i++) { + for (j = 0; j < gts->num_hwgain; j++) + gains[i][j] = gts->hwgain_table[j].gain * + gts->itime_table[i].mul; + } +} + +static int compute_per_time_tables(struct iio_gts *gts, int **scales) +{ + int **per_time_gains; + int ret; + + /* + * Create a temporary array of the 'total gains' for each integration + * time. + */ + ret = iio_gts_alloc_int_table_array(&per_time_gains, gts->num_itime, + gts->num_hwgain); + if (ret) + return ret; + + compute_per_time_gains(gts, per_time_gains); + + /* Convert the gains to scales and populate the scale tables */ + ret = fill_and_sort_scaletables(gts, per_time_gains, scales); + + iio_gts_free_int_table_array(per_time_gains, gts->num_itime); + + return ret; +} + +/* + * Create a table of supported scales for each supported integration time. + * This can be used as available_scales by drivers which don't allow scale + * setting to change the integration time to display correct set of scales + * depending on the used integration time. + */ +static int **create_per_time_scales(struct iio_gts *gts) +{ + int **per_time_scales, ret; + + ret = iio_gts_alloc_int_table_array(&per_time_scales, gts->num_itime, + gts->num_hwgain * 2); + if (ret) + return ERR_PTR(ret); + + ret = compute_per_time_tables(gts, per_time_scales); + if (ret) + goto err_out; + + return per_time_scales; + +err_out: + iio_gts_free_int_table_array(per_time_scales, gts->num_itime); + + return ERR_PTR(ret); +} + +/** + * iio_gts_build_avail_scale_table - create tables of available scales + * @gts: Gain time scale descriptor + * + * Build the tables which can represent the available scales based on the + * originally given gain and time tables. When both time and gain tables are + * given this results: + * 1. A set of tables representing available scales for each supported + * integration time. + * 2. A single table listing all the unique scales that any combination of + * supported gains and times can provide. + * + * NOTE: Space allocated for the tables must be freed using + * iio_gts_purge_avail_scale_table() when the tables are no longer needed. + * + * Return: 0 on success. + */ +static int iio_gts_build_avail_scale_table(struct iio_gts *gts) +{ + int ret, all_scales_tbl_bytes; + int **per_time_scales; + + if (unlikely(check_mul_overflow(gts->num_hwgain, 2 * sizeof(int), + &all_scales_tbl_bytes))) + return -EOVERFLOW; + + per_time_scales = create_per_time_scales(gts); + if (IS_ERR(per_time_scales)) + return PTR_ERR(per_time_scales); + + gts->per_time_avail_scale_tables = per_time_scales; + + ret = do_combined_scaletable(gts, all_scales_tbl_bytes); + if (ret) { + iio_gts_free_int_table_array(per_time_scales, gts->num_itime); + return ret; + } + + return 0; +} + +static void iio_gts_us_to_int_micro(int *time_us, int *int_micro_times, + int num_times) +{ + int i; + + for (i = 0; i < num_times; i++) { + int_micro_times[i * 2] = time_us[i] / 1000000; + int_micro_times[i * 2 + 1] = time_us[i] % 1000000; + } +} + +/** + * iio_gts_build_avail_time_table - build table of available integration times + * @gts: Gain time scale descriptor + * + * Build the table which can represent the available times to be returned + * to users using the read_avail-callback. + * + * NOTE: Space allocated for the tables must be freed using + * iio_gts_purge_avail_time_table() when the tables are no longer needed. + * + * Return: 0 on success. + */ +static int iio_gts_build_avail_time_table(struct iio_gts *gts) +{ + int *times, i, j, idx = 0, *int_micro_times; + + if (!gts->num_itime) + return 0; + + times = kzalloc_objs(int, gts->num_itime); + if (!times) + return -ENOMEM; + + /* Sort times from all tables to one and remove duplicates */ + for (i = gts->num_itime - 1; i >= 0; i--) { + int new = gts->itime_table[i].time_us; + + if (idx == 0 || times[idx - 1] < new) { + times[idx++] = new; + continue; + } + + for (j = 0; j < idx; j++) { + if (times[j] == new) + break; + if (times[j] > new) { + memmove(×[j + 1], ×[j], + (idx - j) * sizeof(int)); + times[j] = new; + idx++; + break; + } + } + } + + /* create a list of times formatted as list of IIO_VAL_INT_PLUS_MICRO */ + int_micro_times = kcalloc(idx, sizeof(int) * 2, GFP_KERNEL); + if (int_micro_times) { + /* + * This is just to survive a unlikely corner-case where times in + * the given time table were not unique. Else we could just + * trust the gts->num_itime. + */ + gts->num_avail_time_tables = idx; + iio_gts_us_to_int_micro(times, int_micro_times, idx); + } + + gts->avail_time_tables = int_micro_times; + kfree(times); + + if (!int_micro_times) + return -ENOMEM; + + return 0; +} + +/** + * iio_gts_purge_avail_time_table - free-up the available integration time table + * @gts: Gain time scale descriptor + * + * Free the space reserved by iio_gts_build_avail_time_table(). + */ +static void iio_gts_purge_avail_time_table(struct iio_gts *gts) +{ + if (gts->num_avail_time_tables) { + kfree(gts->avail_time_tables); + gts->avail_time_tables = NULL; + gts->num_avail_time_tables = 0; + } +} + +/** + * iio_gts_build_avail_tables - create tables of available scales and int times + * @gts: Gain time scale descriptor + * + * Build the tables which can represent the available scales and available + * integration times. Availability tables are built based on the originally + * given gain and given time tables. + * + * When both time and gain tables are + * given this results: + * 1. A set of sorted tables representing available scales for each supported + * integration time. + * 2. A single sorted table listing all the unique scales that any combination + * of supported gains and times can provide. + * 3. A sorted table of supported integration times + * + * After these tables are built one can use the iio_gts_all_avail_scales(), + * iio_gts_avail_scales_for_time() and iio_gts_avail_times() helpers to + * implement the read_avail operations. + * + * NOTE: Space allocated for the tables must be freed using + * iio_gts_purge_avail_tables() when the tables are no longer needed. + * + * Return: 0 on success. + */ +static int iio_gts_build_avail_tables(struct iio_gts *gts) +{ + int ret; + + ret = iio_gts_build_avail_scale_table(gts); + if (ret) + return ret; + + ret = iio_gts_build_avail_time_table(gts); + if (ret) + iio_gts_purge_avail_scale_table(gts); + + return ret; +} + +/** + * iio_gts_purge_avail_tables - free-up the availability tables + * @gts: Gain time scale descriptor + * + * Free the space reserved by iio_gts_build_avail_tables(). Frees both the + * integration time and scale tables. + */ +static void iio_gts_purge_avail_tables(struct iio_gts *gts) +{ + iio_gts_purge_avail_time_table(gts); + iio_gts_purge_avail_scale_table(gts); +} + +static void devm_iio_gts_avail_all_drop(void *res) +{ + iio_gts_purge_avail_tables(res); +} + +/** + * devm_iio_gts_build_avail_tables - manged add availability tables + * @dev: Pointer to the device whose lifetime tables are bound + * @gts: Gain time scale descriptor + * + * Build the tables which can represent the available scales and available + * integration times. Availability tables are built based on the originally + * given gain and given time tables. + * + * When both time and gain tables are given this results: + * 1. A set of sorted tables representing available scales for each supported + * integration time. + * 2. A single sorted table listing all the unique scales that any combination + * of supported gains and times can provide. + * 3. A sorted table of supported integration times + * + * After these tables are built one can use the iio_gts_all_avail_scales(), + * iio_gts_avail_scales_for_time() and iio_gts_avail_times() helpers to + * implement the read_avail operations. + * + * The tables are automatically released upon device detach. + * + * Return: 0 on success. + */ +static int devm_iio_gts_build_avail_tables(struct device *dev, + struct iio_gts *gts) +{ + int ret; + + ret = iio_gts_build_avail_tables(gts); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, devm_iio_gts_avail_all_drop, gts); +} + +static int sanity_check_time(const struct iio_itime_sel_mul *t) +{ + if (t->sel < 0 || t->time_us < 0 || t->mul <= 0) + return -EINVAL; + + return 0; +} + +static int sanity_check_gain(const struct iio_gain_sel_pair *g) +{ + if (g->sel < 0 || g->gain <= 0) + return -EINVAL; + + return 0; +} + +static int iio_gts_sanity_check(struct iio_gts *gts) +{ + int g, t, ret; + + if (!gts->num_hwgain && !gts->num_itime) + return -EINVAL; + + for (t = 0; t < gts->num_itime; t++) { + ret = sanity_check_time(>s->itime_table[t]); + if (ret) + return ret; + } + + for (g = 0; g < gts->num_hwgain; g++) { + ret = sanity_check_gain(>s->hwgain_table[g]); + if (ret) + return ret; + } + + for (g = 0; g < gts->num_hwgain; g++) { + for (t = 0; t < gts->num_itime; t++) { + int gain, mul, res; + + gain = gts->hwgain_table[g].gain; + mul = gts->itime_table[t].mul; + + if (check_mul_overflow(gain, mul, &res)) + return -EOVERFLOW; + } + } + + return 0; +} + +static int iio_init_iio_gts(int max_scale_int, int max_scale_nano, + const struct iio_gain_sel_pair *gain_tbl, int num_gain, + const struct iio_itime_sel_mul *tim_tbl, int num_times, + struct iio_gts *gts) +{ + int ret; + + memset(gts, 0, sizeof(*gts)); + + ret = iio_gts_linearize(max_scale_int, max_scale_nano, NANO, + >s->max_scale); + if (ret) + return ret; + + gts->hwgain_table = gain_tbl; + gts->num_hwgain = num_gain; + gts->itime_table = tim_tbl; + gts->num_itime = num_times; + + return iio_gts_sanity_check(gts); +} + +/** + * devm_iio_init_iio_gts - Initialize the gain-time-scale helper + * @dev: Pointer to the device whose lifetime gts resources are + * bound + * @max_scale_int: integer part of the maximum scale value + * @max_scale_nano: fraction part of the maximum scale value + * @gain_tbl: table describing supported gains + * @num_gain: number of gains in the gain table + * @tim_tbl: table describing supported integration times. Provide + * the integration time table sorted so that the preferred + * integration time is in the first array index. The search + * functions like the + * iio_gts_find_time_and_gain_sel_for_scale() start search + * from first provided time. + * @num_times: number of times in the time table + * @gts: pointer to the helper struct + * + * Initialize the gain-time-scale helper for use. Note, gains, times, selectors + * and multipliers must be positive. Negative values are reserved for error + * checking. The total gain (maximum gain * maximum time multiplier) must not + * overflow int. The allocated resources will be released upon device detach. + * + * Return: 0 on success. + */ +int devm_iio_init_iio_gts(struct device *dev, int max_scale_int, int max_scale_nano, + const struct iio_gain_sel_pair *gain_tbl, int num_gain, + const struct iio_itime_sel_mul *tim_tbl, int num_times, + struct iio_gts *gts) +{ + int ret; + + ret = iio_init_iio_gts(max_scale_int, max_scale_nano, gain_tbl, + num_gain, tim_tbl, num_times, gts); + if (ret) + return ret; + + return devm_iio_gts_build_avail_tables(dev, gts); +} +EXPORT_SYMBOL_NS_GPL(devm_iio_init_iio_gts, "IIO_GTS_HELPER"); + +/** + * iio_gts_all_avail_scales - helper for listing all available scales + * @gts: Gain time scale descriptor + * @vals: Returned array of supported scales + * @type: Type of returned scale values + * @length: Amount of returned values in array + * + * Return: a value suitable to be returned from read_avail or a negative error. + */ +int iio_gts_all_avail_scales(struct iio_gts *gts, const int **vals, int *type, + int *length) +{ + if (!gts->num_avail_all_scales) + return -EINVAL; + + *vals = gts->avail_all_scales_table; + *type = IIO_VAL_INT_PLUS_NANO; + *length = gts->num_avail_all_scales * 2; + + return IIO_AVAIL_LIST; +} +EXPORT_SYMBOL_NS_GPL(iio_gts_all_avail_scales, "IIO_GTS_HELPER"); + +/** + * iio_gts_avail_scales_for_time - list scales for integration time + * @gts: Gain time scale descriptor + * @time: Integration time for which the scales are listed + * @vals: Returned array of supported scales + * @type: Type of returned scale values + * @length: Amount of returned values in array + * + * Drivers which do not allow scale setting to change integration time can + * use this helper to list only the scales which are valid for given integration + * time. + * + * Return: a value suitable to be returned from read_avail or a negative error. + */ +int iio_gts_avail_scales_for_time(struct iio_gts *gts, int time, + const int **vals, int *type, int *length) +{ + int i; + + for (i = 0; i < gts->num_itime; i++) + if (gts->itime_table[i].time_us == time) + break; + + if (i == gts->num_itime) + return -EINVAL; + + *vals = gts->per_time_avail_scale_tables[i]; + *type = IIO_VAL_INT_PLUS_NANO; + *length = gts->num_hwgain * 2; + + return IIO_AVAIL_LIST; +} +EXPORT_SYMBOL_NS_GPL(iio_gts_avail_scales_for_time, "IIO_GTS_HELPER"); + +/** + * iio_gts_avail_times - helper for listing available integration times + * @gts: Gain time scale descriptor + * @vals: Returned array of supported times + * @type: Type of returned scale values + * @length: Amount of returned values in array + * + * Return: a value suitable to be returned from read_avail or a negative error. + */ +int iio_gts_avail_times(struct iio_gts *gts, const int **vals, int *type, + int *length) +{ + if (!gts->num_avail_time_tables) + return -EINVAL; + + *vals = gts->avail_time_tables; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = gts->num_avail_time_tables * 2; + + return IIO_AVAIL_LIST; +} +EXPORT_SYMBOL_NS_GPL(iio_gts_avail_times, "IIO_GTS_HELPER"); + +/** + * iio_gts_find_sel_by_gain - find selector corresponding to a HW-gain + * @gts: Gain time scale descriptor + * @gain: HW-gain for which matching selector is searched for + * + * Return: a selector matching given HW-gain or -EINVAL if selector was + * not found. + */ +int iio_gts_find_sel_by_gain(struct iio_gts *gts, int gain) +{ + int i; + + for (i = 0; i < gts->num_hwgain; i++) + if (gts->hwgain_table[i].gain == gain) + return gts->hwgain_table[i].sel; + + return -EINVAL; +} +EXPORT_SYMBOL_NS_GPL(iio_gts_find_sel_by_gain, "IIO_GTS_HELPER"); + +/** + * iio_gts_find_gain_by_sel - find HW-gain corresponding to a selector + * @gts: Gain time scale descriptor + * @sel: selector for which matching HW-gain is searched for + * + * Return: a HW-gain matching given selector or -EINVAL if HW-gain was not + * found. + */ +int iio_gts_find_gain_by_sel(struct iio_gts *gts, int sel) +{ + int i; + + for (i = 0; i < gts->num_hwgain; i++) + if (gts->hwgain_table[i].sel == sel) + return gts->hwgain_table[i].gain; + + return -EINVAL; +} +EXPORT_SYMBOL_NS_GPL(iio_gts_find_gain_by_sel, "IIO_GTS_HELPER"); + +/** + * iio_gts_get_min_gain - find smallest valid HW-gain + * @gts: Gain time scale descriptor + * + * Return: The smallest HW-gain -EINVAL if no HW-gains were in the tables. + */ +int iio_gts_get_min_gain(struct iio_gts *gts) +{ + int i, min = -EINVAL; + + for (i = 0; i < gts->num_hwgain; i++) { + int gain = gts->hwgain_table[i].gain; + + if (min == -EINVAL) + min = gain; + else + min = min(min, gain); + } + + return min; +} +EXPORT_SYMBOL_NS_GPL(iio_gts_get_min_gain, "IIO_GTS_HELPER"); + +/** + * iio_find_closest_gain_low - Find the closest lower matching gain + * @gts: Gain time scale descriptor + * @gain: HW-gain for which the closest match is searched + * @in_range: indicate if the @gain was actually in the range of + * supported gains. + * + * Search for closest supported gain that is lower than or equal to the + * gain given as a parameter. This is usable for drivers which do not require + * user to request exact matching gain but rather for rounding to a supported + * gain value which is equal or lower (setting lower gain is typical for + * avoiding saturation) + * + * Return: The closest matching supported gain or -EINVAL if @gain + * was smaller than the smallest supported gain. + */ +int iio_find_closest_gain_low(struct iio_gts *gts, int gain, bool *in_range) +{ + int i, diff = 0; + int best = -1; + + *in_range = false; + + for (i = 0; i < gts->num_hwgain; i++) { + if (gain == gts->hwgain_table[i].gain) { + *in_range = true; + return gain; + } + + if (gain > gts->hwgain_table[i].gain) { + if (!diff) { + diff = gain - gts->hwgain_table[i].gain; + best = i; + } else { + int tmp = gain - gts->hwgain_table[i].gain; + + if (tmp < diff) { + diff = tmp; + best = i; + } + } + } else { + /* + * We found valid HW-gain which is greater than + * reference. So, unless we return a failure below we + * will have found an in-range gain + */ + *in_range = true; + } + } + /* The requested gain was smaller than anything we support */ + if (!diff) { + *in_range = false; + + return -EINVAL; + } + + return gts->hwgain_table[best].gain; +} +EXPORT_SYMBOL_NS_GPL(iio_find_closest_gain_low, "IIO_GTS_HELPER"); + +static int iio_gts_get_int_time_gain_multiplier_by_sel(struct iio_gts *gts, + int sel) +{ + const struct iio_itime_sel_mul *time; + + time = iio_gts_find_itime_by_sel(gts, sel); + if (!time) + return -EINVAL; + + return time->mul; +} + +/** + * iio_gts_find_gain_for_scale_using_time - Find gain by time and scale + * @gts: Gain time scale descriptor + * @time_sel: Integration time selector corresponding to the time gain is + * searched for + * @scale_int: Integral part of the scale (typically val1) + * @scale_nano: Fractional part of the scale (nano or ppb) + * @gain: Pointer to value where gain is stored. + * + * In some cases the light sensors may want to find a gain setting which + * corresponds given scale and integration time. Sensors which fill the + * gain and time tables may use this helper to retrieve the gain. + * + * Return: 0 on success. -EINVAL if gain matching the parameters is not + * found. + */ +static int iio_gts_find_gain_for_scale_using_time(struct iio_gts *gts, int time_sel, + int scale_int, int scale_nano, + int *gain) +{ + u64 scale_linear; + int ret, mul; + + ret = iio_gts_linearize(scale_int, scale_nano, NANO, &scale_linear); + if (ret) + return ret; + + ret = iio_gts_get_int_time_gain_multiplier_by_sel(gts, time_sel); + if (ret < 0) + return ret; + + mul = ret; + + ret = gain_get_scale_fraction(gts->max_scale, scale_linear, mul, gain); + if (ret) + return ret; + + if (!iio_gts_valid_gain(gts, *gain)) + return -EINVAL; + + return 0; +} + +/** + * iio_gts_find_gain_sel_for_scale_using_time - Fetch gain selector. + * @gts: Gain time scale descriptor + * @time_sel: Integration time selector corresponding to the time gain is + * searched for + * @scale_int: Integral part of the scale (typically val1) + * @scale_nano: Fractional part of the scale (nano or ppb) + * @gain_sel: Pointer to value where gain selector is stored. + * + * See iio_gts_find_gain_for_scale_using_time() for more information + */ +int iio_gts_find_gain_sel_for_scale_using_time(struct iio_gts *gts, int time_sel, + int scale_int, int scale_nano, + int *gain_sel) +{ + int gain, ret; + + ret = iio_gts_find_gain_for_scale_using_time(gts, time_sel, scale_int, + scale_nano, &gain); + if (ret) + return ret; + + ret = iio_gts_find_sel_by_gain(gts, gain); + if (ret < 0) + return ret; + + *gain_sel = ret; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(iio_gts_find_gain_sel_for_scale_using_time, "IIO_GTS_HELPER"); + +/** + * iio_gts_find_gain_time_sel_for_scale - Fetch gain and time selectors for scale + * @gts: Gain time scale descriptor + * @scale_int: Integral part of the scale (typically val1) + * @scale_nano: Fractional part of the scale (nano or ppb) + * @gain_sel: Pointer to value where gain selector is stored. + * @time_sel: Pointer to value where time selector is stored. + * + * Wrapper around iio_gts_find_gain_for_scale_using_time() to fetch the + * gain and time selectors for a given scale. + * + * Return: 0 on success and -EINVAL on error. + */ +int iio_gts_find_gain_time_sel_for_scale(struct iio_gts *gts, int scale_int, + int scale_nano, int *gain_sel, + int *time_sel) +{ + int i, ret; + + for (i = 0; i < gts->num_itime; i++) { + *time_sel = gts->itime_table[i].sel; + ret = iio_gts_find_gain_sel_for_scale_using_time(gts, *time_sel, + scale_int, + scale_nano, + gain_sel); + if (ret) + continue; + + return 0; + } + + return -EINVAL; +} +EXPORT_SYMBOL_NS_GPL(iio_gts_find_gain_time_sel_for_scale, "IIO_GTS_HELPER"); + +/** + * iio_gts_get_total_gain - Fetch total gain for given HW-gain and time + * @gts: Gain time scale descriptor + * @gain: HW-gain for which the total gain is searched for + * @time: Integration time for which the total gain is searched for + * + * Return: total gain on success and -EINVAL on error. + */ +int iio_gts_get_total_gain(struct iio_gts *gts, int gain, int time) +{ + const struct iio_itime_sel_mul *itime; + + if (!iio_gts_valid_gain(gts, gain)) + return -EINVAL; + + if (!gts->num_itime) + return gain; + + itime = iio_gts_find_itime_by_time(gts, time); + if (!itime) + return -EINVAL; + + return gain * itime->mul; +} +EXPORT_SYMBOL_NS_GPL(iio_gts_get_total_gain, "IIO_GTS_HELPER"); + +static int iio_gts_get_scale_linear(struct iio_gts *gts, int gain, int time, + u64 *scale) +{ + int total_gain; + u64 tmp; + + total_gain = iio_gts_get_total_gain(gts, gain, time); + if (total_gain < 0) + return total_gain; + + tmp = gts->max_scale; + + do_div(tmp, total_gain); + + *scale = tmp; + + return 0; +} + +/** + * iio_gts_get_scale - get scale based on integration time and HW-gain + * @gts: Gain time scale descriptor + * @gain: HW-gain for which the scale is computed + * @time: Integration time for which the scale is computed + * @scale_int: Integral part of the scale (typically val1) + * @scale_nano: Fractional part of the scale (nano or ppb) + * + * Compute scale matching the integration time and HW-gain given as parameter. + * + * Return: 0 on success. + */ +int iio_gts_get_scale(struct iio_gts *gts, int gain, int time, int *scale_int, + int *scale_nano) +{ + u64 lin_scale; + int ret; + + ret = iio_gts_get_scale_linear(gts, gain, time, &lin_scale); + if (ret) + return ret; + + return iio_gts_delinearize(lin_scale, NANO, scale_int, scale_nano); +} +EXPORT_SYMBOL_NS_GPL(iio_gts_get_scale, "IIO_GTS_HELPER"); + +/** + * iio_gts_find_new_gain_sel_by_old_gain_time - compensate for time change + * @gts: Gain time scale descriptor + * @old_gain: Previously set gain + * @old_time_sel: Selector corresponding previously set time + * @new_time_sel: Selector corresponding new time to be set + * @new_gain: Pointer to value where new gain is to be written + * + * We may want to mitigate the scale change caused by setting a new integration + * time (for a light sensor) by also updating the (HW)gain. This helper computes + * new gain value to maintain the scale with new integration time. + * + * Return: 0 if an exactly matching supported new gain was found. When a + * non-zero value is returned, the @new_gain will be set to a negative or + * positive value. The negative value means that no gain could be computed. + * Positive value will be the "best possible new gain there could be". There + * can be two reasons why finding the "best possible" new gain is not deemed + * successful. 1) This new value cannot be supported by the hardware. 2) The new + * gain required to maintain the scale would not be an integer. In this case, + * the "best possible" new gain will be a floored optimal gain, which may or + * may not be supported by the hardware. + */ +int iio_gts_find_new_gain_sel_by_old_gain_time(struct iio_gts *gts, + int old_gain, int old_time_sel, + int new_time_sel, int *new_gain) +{ + const struct iio_itime_sel_mul *itime_old, *itime_new; + u64 scale; + int ret; + + *new_gain = -1; + + itime_old = iio_gts_find_itime_by_sel(gts, old_time_sel); + if (!itime_old) + return -EINVAL; + + itime_new = iio_gts_find_itime_by_sel(gts, new_time_sel); + if (!itime_new) + return -EINVAL; + + ret = iio_gts_get_scale_linear(gts, old_gain, itime_old->time_us, + &scale); + if (ret) + return ret; + + ret = gain_get_scale_fraction(gts->max_scale, scale, itime_new->mul, + new_gain); + if (ret) + return ret; + + if (!iio_gts_valid_gain(gts, *new_gain)) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(iio_gts_find_new_gain_sel_by_old_gain_time, "IIO_GTS_HELPER"); + +/** + * iio_gts_find_new_gain_by_old_gain_time - compensate for time change + * @gts: Gain time scale descriptor + * @old_gain: Previously set gain + * @old_time: Selector corresponding previously set time + * @new_time: Selector corresponding new time to be set + * @new_gain: Pointer to value where new gain is to be written + * + * We may want to mitigate the scale change caused by setting a new integration + * time (for a light sensor) by also updating the (HW)gain. This helper computes + * new gain value to maintain the scale with new integration time. + * + * Return: 0 if an exactly matching supported new gain was found. When a + * non-zero value is returned, the @new_gain will be set to a negative or + * positive value. The negative value means that no gain could be computed. + * Positive value will be the "best possible new gain there could be". There + * can be two reasons why finding the "best possible" new gain is not deemed + * successful. 1) This new value cannot be supported by the hardware. 2) The new + * gain required to maintain the scale would not be an integer. In this case, + * the "best possible" new gain will be a floored optimal gain, which may or + * may not be supported by the hardware. + */ +int iio_gts_find_new_gain_by_old_gain_time(struct iio_gts *gts, int old_gain, + int old_time, int new_time, + int *new_gain) +{ + const struct iio_itime_sel_mul *itime_new; + u64 scale; + int ret; + + *new_gain = -1; + + itime_new = iio_gts_find_itime_by_time(gts, new_time); + if (!itime_new) + return -EINVAL; + + ret = iio_gts_get_scale_linear(gts, old_gain, old_time, &scale); + if (ret) + return ret; + + ret = gain_get_scale_fraction(gts->max_scale, scale, itime_new->mul, + new_gain); + if (ret) + return ret; + + if (!iio_gts_valid_gain(gts, *new_gain)) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(iio_gts_find_new_gain_by_old_gain_time, "IIO_GTS_HELPER"); + +/** + * iio_gts_find_new_gain_by_gain_time_min - compensate for time change + * @gts: Gain time scale descriptor + * @old_gain: Previously set gain + * @old_time: Selector corresponding previously set time + * @new_time: Selector corresponding new time to be set + * @new_gain: Pointer to value where new gain is to be written + * @in_range: Indicate if the @new_gain was in the range of + * supported gains. + * + * Wrapper around iio_gts_find_new_gain_by_old_gain_time() that tries to + * set an optimal value if no exact match was found, defaulting to the + * minimum gain to avoid saturations if the optimal value is not in the + * range of supported gains. + * + * Return: 0 on success and a negative value if no gain was found. + */ +int iio_gts_find_new_gain_by_gain_time_min(struct iio_gts *gts, int old_gain, + int old_time, int new_time, + int *new_gain, bool *in_range) +{ + int ret; + + *in_range = true; + ret = iio_gts_find_new_gain_by_old_gain_time(gts, old_gain, old_time, + new_time, new_gain); + if (*new_gain < 0) + return -EINVAL; + + if (ret) { + *new_gain = iio_find_closest_gain_low(gts, *new_gain, in_range); + if (*new_gain < 0) { + *new_gain = iio_gts_get_min_gain(gts); + if (*new_gain < 0) + return -EINVAL; + } + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(iio_gts_find_new_gain_by_gain_time_min, "IIO_GTS_HELPER"); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Matti Vaittinen "); +MODULE_DESCRIPTION("IIO light sensor gain-time-scale helpers"); diff --git a/drivers/iio/industrialio-sw-device.c b/drivers/iio/industrialio-sw-device.c new file mode 100644 index 000000000..3582524de --- /dev/null +++ b/drivers/iio/industrialio-sw-device.c @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * The Industrial I/O core, software IIO devices functions + * + * Copyright (c) 2016 Intel Corporation + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +static struct config_group *iio_devices_group; +static const struct config_item_type iio_device_type_group_type; + +static const struct config_item_type iio_devices_group_type = { + .ct_owner = THIS_MODULE, +}; + +static LIST_HEAD(iio_device_types_list); +static DEFINE_MUTEX(iio_device_types_lock); + +static +struct iio_sw_device_type *__iio_find_sw_device_type(const char *name, + unsigned int len) +{ + struct iio_sw_device_type *d = NULL, *iter; + + list_for_each_entry(iter, &iio_device_types_list, list) + if (!strcmp(iter->name, name)) { + d = iter; + break; + } + + return d; +} + +int iio_register_sw_device_type(struct iio_sw_device_type *d) +{ + struct iio_sw_device_type *iter; + int ret = 0; + + mutex_lock(&iio_device_types_lock); + iter = __iio_find_sw_device_type(d->name, strlen(d->name)); + if (iter) + ret = -EBUSY; + else + list_add_tail(&d->list, &iio_device_types_list); + mutex_unlock(&iio_device_types_lock); + + if (ret) + return ret; + + d->group = configfs_register_default_group(iio_devices_group, d->name, + &iio_device_type_group_type); + if (IS_ERR(d->group)) + ret = PTR_ERR(d->group); + + return ret; +} +EXPORT_SYMBOL(iio_register_sw_device_type); + +void iio_unregister_sw_device_type(struct iio_sw_device_type *dt) +{ + struct iio_sw_device_type *iter; + + mutex_lock(&iio_device_types_lock); + iter = __iio_find_sw_device_type(dt->name, strlen(dt->name)); + if (iter) + list_del(&dt->list); + mutex_unlock(&iio_device_types_lock); + + configfs_unregister_default_group(dt->group); +} +EXPORT_SYMBOL(iio_unregister_sw_device_type); + +static +struct iio_sw_device_type *iio_get_sw_device_type(const char *name) +{ + struct iio_sw_device_type *dt; + + mutex_lock(&iio_device_types_lock); + dt = __iio_find_sw_device_type(name, strlen(name)); + if (dt && !try_module_get(dt->owner)) + dt = NULL; + mutex_unlock(&iio_device_types_lock); + + return dt; +} + +struct iio_sw_device *iio_sw_device_create(const char *type, const char *name) +{ + struct iio_sw_device *d; + struct iio_sw_device_type *dt; + + dt = iio_get_sw_device_type(type); + if (!dt) { + pr_err("Invalid device type: %s\n", type); + return ERR_PTR(-EINVAL); + } + d = dt->ops->probe(name); + if (IS_ERR(d)) + goto out_module_put; + + d->device_type = dt; + + return d; +out_module_put: + module_put(dt->owner); + return d; +} +EXPORT_SYMBOL(iio_sw_device_create); + +void iio_sw_device_destroy(struct iio_sw_device *d) +{ + struct iio_sw_device_type *dt = d->device_type; + + dt->ops->remove(d); + module_put(dt->owner); +} +EXPORT_SYMBOL(iio_sw_device_destroy); + +static struct config_group *device_make_group(struct config_group *group, + const char *name) +{ + struct iio_sw_device *d; + + d = iio_sw_device_create(group->cg_item.ci_name, name); + if (IS_ERR(d)) + return ERR_CAST(d); + + config_item_set_name(&d->group.cg_item, "%s", name); + + return &d->group; +} + +static void device_drop_group(struct config_group *group, + struct config_item *item) +{ + struct iio_sw_device *d = to_iio_sw_device(item); + + iio_sw_device_destroy(d); + config_item_put(item); +} + +static const struct configfs_group_operations device_ops = { + .make_group = &device_make_group, + .drop_item = &device_drop_group, +}; + +static const struct config_item_type iio_device_type_group_type = { + .ct_group_ops = &device_ops, + .ct_owner = THIS_MODULE, +}; + +static int __init iio_sw_device_init(void) +{ + iio_devices_group = + configfs_register_default_group(&iio_configfs_subsys.su_group, + "devices", + &iio_devices_group_type); + return PTR_ERR_OR_ZERO(iio_devices_group); +} +module_init(iio_sw_device_init); + +static void __exit iio_sw_device_exit(void) +{ + configfs_unregister_default_group(iio_devices_group); +} +module_exit(iio_sw_device_exit); + +MODULE_AUTHOR("Daniel Baluta "); +MODULE_DESCRIPTION("Industrial I/O software devices support"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/industrialio-sw-trigger.c b/drivers/iio/industrialio-sw-trigger.c new file mode 100644 index 000000000..334b6b10a --- /dev/null +++ b/drivers/iio/industrialio-sw-trigger.c @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * The Industrial I/O core, software trigger functions + * + * Copyright (c) 2015 Intel Corporation + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +static struct config_group *iio_triggers_group; +static const struct config_item_type iio_trigger_type_group_type; + +static const struct config_item_type iio_triggers_group_type = { + .ct_owner = THIS_MODULE, +}; + +static LIST_HEAD(iio_trigger_types_list); +static DEFINE_MUTEX(iio_trigger_types_lock); + +static +struct iio_sw_trigger_type *__iio_find_sw_trigger_type(const char *name, + unsigned int len) +{ + struct iio_sw_trigger_type *t = NULL, *iter; + + list_for_each_entry(iter, &iio_trigger_types_list, list) + if (!strcmp(iter->name, name)) { + t = iter; + break; + } + + return t; +} + +int iio_register_sw_trigger_type(struct iio_sw_trigger_type *t) +{ + struct iio_sw_trigger_type *iter; + int ret = 0; + + mutex_lock(&iio_trigger_types_lock); + iter = __iio_find_sw_trigger_type(t->name, strlen(t->name)); + if (iter) + ret = -EBUSY; + else + list_add_tail(&t->list, &iio_trigger_types_list); + mutex_unlock(&iio_trigger_types_lock); + + if (ret) + return ret; + + t->group = configfs_register_default_group(iio_triggers_group, t->name, + &iio_trigger_type_group_type); + if (IS_ERR(t->group)) { + mutex_lock(&iio_trigger_types_lock); + list_del(&t->list); + mutex_unlock(&iio_trigger_types_lock); + ret = PTR_ERR(t->group); + } + + return ret; +} +EXPORT_SYMBOL(iio_register_sw_trigger_type); + +void iio_unregister_sw_trigger_type(struct iio_sw_trigger_type *t) +{ + struct iio_sw_trigger_type *iter; + + mutex_lock(&iio_trigger_types_lock); + iter = __iio_find_sw_trigger_type(t->name, strlen(t->name)); + if (iter) + list_del(&t->list); + mutex_unlock(&iio_trigger_types_lock); + + configfs_unregister_default_group(t->group); +} +EXPORT_SYMBOL(iio_unregister_sw_trigger_type); + +static +struct iio_sw_trigger_type *iio_get_sw_trigger_type(const char *name) +{ + struct iio_sw_trigger_type *t; + + mutex_lock(&iio_trigger_types_lock); + t = __iio_find_sw_trigger_type(name, strlen(name)); + if (t && !try_module_get(t->owner)) + t = NULL; + mutex_unlock(&iio_trigger_types_lock); + + return t; +} + +struct iio_sw_trigger *iio_sw_trigger_create(const char *type, const char *name) +{ + struct iio_sw_trigger *t; + struct iio_sw_trigger_type *tt; + + tt = iio_get_sw_trigger_type(type); + if (!tt) { + pr_err("Invalid trigger type: %s\n", type); + return ERR_PTR(-EINVAL); + } + t = tt->ops->probe(name); + if (IS_ERR(t)) + goto out_module_put; + + t->trigger_type = tt; + + return t; +out_module_put: + module_put(tt->owner); + return t; +} +EXPORT_SYMBOL(iio_sw_trigger_create); + +void iio_sw_trigger_destroy(struct iio_sw_trigger *t) +{ + struct iio_sw_trigger_type *tt = t->trigger_type; + + tt->ops->remove(t); + module_put(tt->owner); +} +EXPORT_SYMBOL(iio_sw_trigger_destroy); + +static struct config_group *trigger_make_group(struct config_group *group, + const char *name) +{ + struct iio_sw_trigger *t; + + t = iio_sw_trigger_create(group->cg_item.ci_name, name); + if (IS_ERR(t)) + return ERR_CAST(t); + + config_item_set_name(&t->group.cg_item, "%s", name); + + return &t->group; +} + +static void trigger_drop_group(struct config_group *group, + struct config_item *item) +{ + struct iio_sw_trigger *t = to_iio_sw_trigger(item); + + iio_sw_trigger_destroy(t); + config_item_put(item); +} + +static const struct configfs_group_operations trigger_ops = { + .make_group = &trigger_make_group, + .drop_item = &trigger_drop_group, +}; + +static const struct config_item_type iio_trigger_type_group_type = { + .ct_group_ops = &trigger_ops, + .ct_owner = THIS_MODULE, +}; + +static int __init iio_sw_trigger_init(void) +{ + iio_triggers_group = + configfs_register_default_group(&iio_configfs_subsys.su_group, + "triggers", + &iio_triggers_group_type); + return PTR_ERR_OR_ZERO(iio_triggers_group); +} +module_init(iio_sw_trigger_init); + +static void __exit iio_sw_trigger_exit(void) +{ + configfs_unregister_default_group(iio_triggers_group); +} +module_exit(iio_sw_trigger_exit); + +MODULE_AUTHOR("Daniel Baluta "); +MODULE_DESCRIPTION("Industrial I/O software triggers support"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/industrialio-trigger.c b/drivers/iio/industrialio-trigger.c new file mode 100644 index 000000000..17781c12b --- /dev/null +++ b/drivers/iio/industrialio-trigger.c @@ -0,0 +1,796 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* The industrial I/O core, trigger handling functions + * + * Copyright (c) 2008 Jonathan Cameron + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "iio_core.h" +#include "iio_core_trigger.h" +#include + +/* RFC - Question of approach + * Make the common case (single sensor single trigger) + * simple by starting trigger capture from when first sensors + * is added. + * + * Complex simultaneous start requires use of 'hold' functionality + * of the trigger. (not implemented) + * + * Any other suggestions? + */ + +static DEFINE_IDA(iio_trigger_ida); + +/* Single list of all available triggers */ +static LIST_HEAD(iio_trigger_list); +static DEFINE_MUTEX(iio_trigger_list_lock); + +/** + * name_show() - retrieve useful identifying name + * @dev: device associated with the iio_trigger + * @attr: pointer to the device_attribute structure that is + * being processed + * @buf: buffer to print the name into + * + * Return: a negative number on failure or the number of written + * characters on success. + */ +static ssize_t name_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + + return sysfs_emit(buf, "%s\n", trig->name); +} + +static DEVICE_ATTR_RO(name); + +static struct attribute *iio_trig_dev_attrs[] = { + &dev_attr_name.attr, + NULL, +}; +ATTRIBUTE_GROUPS(iio_trig_dev); + +static struct iio_trigger *__iio_trigger_find_by_name(const char *name); + +int iio_trigger_register(struct iio_trigger *trig_info) +{ + int ret; + + trig_info->id = ida_alloc(&iio_trigger_ida, GFP_KERNEL); + if (trig_info->id < 0) + return trig_info->id; + + /* Set the name used for the sysfs directory etc */ + dev_set_name(&trig_info->dev, "trigger%d", trig_info->id); + + ret = device_add(&trig_info->dev); + if (ret) + goto error_unregister_id; + + /* Add to list of available triggers held by the IIO core */ + scoped_guard(mutex, &iio_trigger_list_lock) { + if (__iio_trigger_find_by_name(trig_info->name)) { + pr_err("Duplicate trigger name '%s'\n", trig_info->name); + ret = -EEXIST; + goto error_device_del; + } + list_add_tail(&trig_info->list, &iio_trigger_list); + } + + return 0; + +error_device_del: + device_del(&trig_info->dev); +error_unregister_id: + ida_free(&iio_trigger_ida, trig_info->id); + return ret; +} +EXPORT_SYMBOL(iio_trigger_register); + +void iio_trigger_unregister(struct iio_trigger *trig_info) +{ + scoped_guard(mutex, &iio_trigger_list_lock) + list_del(&trig_info->list); + + ida_free(&iio_trigger_ida, trig_info->id); + /* Possible issue in here */ + device_del(&trig_info->dev); +} +EXPORT_SYMBOL(iio_trigger_unregister); + +int iio_trigger_set_immutable(struct iio_dev *indio_dev, struct iio_trigger *trig) +{ + struct iio_dev_opaque *iio_dev_opaque; + + if (!indio_dev || !trig) + return -EINVAL; + + iio_dev_opaque = to_iio_dev_opaque(indio_dev); + guard(mutex)(&iio_dev_opaque->mlock); + WARN_ON(iio_dev_opaque->trig_readonly); + + indio_dev->trig = iio_trigger_get(trig); + iio_dev_opaque->trig_readonly = true; + + return 0; +} +EXPORT_SYMBOL(iio_trigger_set_immutable); + +/* Search for trigger by name, assuming iio_trigger_list_lock held */ +static struct iio_trigger *__iio_trigger_find_by_name(const char *name) +{ + struct iio_trigger *iter; + + list_for_each_entry(iter, &iio_trigger_list, list) + if (!strcmp(iter->name, name)) + return iter; + + return NULL; +} + +static struct iio_trigger *iio_trigger_acquire_by_name(const char *name) +{ + struct iio_trigger *iter; + + guard(mutex)(&iio_trigger_list_lock); + list_for_each_entry(iter, &iio_trigger_list, list) + if (sysfs_streq(iter->name, name)) + return iio_trigger_get(iter); + + return NULL; +} + +static void iio_reenable_work_fn(struct work_struct *work) +{ + struct iio_trigger *trig = container_of(work, struct iio_trigger, + reenable_work); + + /* + * This 'might' occur after the trigger state is set to disabled - + * in that case the driver should skip reenabling. + */ + trig->ops->reenable(trig); +} + +/* + * In general, reenable callbacks may need to sleep and this path is + * not performance sensitive, so just queue up a work item + * to reneable the trigger for us. + * + * Races that can cause this. + * 1) A handler occurs entirely in interrupt context so the counter + * the final decrement is still in this interrupt. + * 2) The trigger has been removed, but one last interrupt gets through. + * + * For (1) we must call reenable, but not in atomic context. + * For (2) it should be safe to call reenanble, if drivers never blindly + * reenable after state is off. + */ +static void iio_trigger_notify_done_atomic(struct iio_trigger *trig) +{ + if (atomic_dec_and_test(&trig->use_count) && trig->ops && + trig->ops->reenable) + schedule_work(&trig->reenable_work); +} + +/** + * iio_trigger_poll() - Call the IRQ trigger handler of the consumers + * @trig: trigger which occurred + * + * This function should only be called from a hard IRQ context. + */ +void iio_trigger_poll(struct iio_trigger *trig) +{ + int i; + + if (!atomic_read(&trig->use_count)) { + atomic_set(&trig->use_count, CONFIG_IIO_CONSUMERS_PER_TRIGGER); + + for (i = 0; i < CONFIG_IIO_CONSUMERS_PER_TRIGGER; i++) { + if (trig->subirqs[i].enabled) + generic_handle_irq(trig->subirq_base + i); + else + iio_trigger_notify_done_atomic(trig); + } + } +} +EXPORT_SYMBOL(iio_trigger_poll); + +irqreturn_t iio_trigger_generic_data_rdy_poll(int irq, void *private) +{ + iio_trigger_poll(private); + return IRQ_HANDLED; +} +EXPORT_SYMBOL(iio_trigger_generic_data_rdy_poll); + +/** + * iio_trigger_poll_nested() - Call the threaded trigger handler of the + * consumers + * @trig: trigger which occurred + * + * This function should only be called from a kernel thread context. + */ +void iio_trigger_poll_nested(struct iio_trigger *trig) +{ + int i; + + if (!atomic_read(&trig->use_count)) { + atomic_set(&trig->use_count, CONFIG_IIO_CONSUMERS_PER_TRIGGER); + + for (i = 0; i < CONFIG_IIO_CONSUMERS_PER_TRIGGER; i++) { + if (trig->subirqs[i].enabled) + handle_nested_irq(trig->subirq_base + i); + else + iio_trigger_notify_done(trig); + } + } +} +EXPORT_SYMBOL(iio_trigger_poll_nested); + +void iio_trigger_notify_done(struct iio_trigger *trig) +{ + if (atomic_dec_and_test(&trig->use_count) && trig->ops && + trig->ops->reenable) + trig->ops->reenable(trig); +} +EXPORT_SYMBOL(iio_trigger_notify_done); + +/* Trigger Consumer related functions */ +static int iio_trigger_get_irq(struct iio_trigger *trig) +{ + int ret; + + scoped_guard(mutex, &trig->pool_lock) { + ret = bitmap_find_free_region(trig->pool, + CONFIG_IIO_CONSUMERS_PER_TRIGGER, + ilog2(1)); + if (ret < 0) + return ret; + } + + return ret + trig->subirq_base; +} + +static void iio_trigger_put_irq(struct iio_trigger *trig, int irq) +{ + guard(mutex)(&trig->pool_lock); + clear_bit(irq - trig->subirq_base, trig->pool); +} + +/* Complexity in here. With certain triggers (datardy) an acknowledgement + * may be needed if the pollfuncs do not include the data read for the + * triggering device. + * This is not currently handled. Alternative of not enabling trigger unless + * the relevant function is in there may be the best option. + */ +/* Worth protecting against double additions? */ +int iio_trigger_attach_poll_func(struct iio_trigger *trig, + struct iio_poll_func *pf) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(pf->indio_dev); + bool notinuse = + bitmap_empty(trig->pool, CONFIG_IIO_CONSUMERS_PER_TRIGGER); + int ret = 0; + + /* Prevent the module from being removed whilst attached to a trigger */ + __module_get(iio_dev_opaque->driver_module); + + /* Get irq number */ + pf->irq = iio_trigger_get_irq(trig); + if (pf->irq < 0) { + pr_err("Could not find an available irq for trigger %s, CONFIG_IIO_CONSUMERS_PER_TRIGGER=%d limit might be exceeded\n", + trig->name, CONFIG_IIO_CONSUMERS_PER_TRIGGER); + goto out_put_module; + } + + /* Request irq */ + ret = request_threaded_irq(pf->irq, pf->h, pf->thread, + pf->type, pf->name, + pf); + if (ret < 0) + goto out_put_irq; + + /* Enable trigger in driver */ + if (trig->ops && trig->ops->set_trigger_state && notinuse) { + ret = trig->ops->set_trigger_state(trig, true); + if (ret) + goto out_free_irq; + } + + /* + * Check if we just registered to our own trigger: we determine that + * this is the case if the IIO device and the trigger device share the + * same parent device. + */ + if (!iio_validate_own_trigger(pf->indio_dev, trig)) + trig->attached_own_device = true; + + return ret; + +out_free_irq: + free_irq(pf->irq, pf); +out_put_irq: + iio_trigger_put_irq(trig, pf->irq); +out_put_module: + module_put(iio_dev_opaque->driver_module); + return ret; +} + +int iio_trigger_detach_poll_func(struct iio_trigger *trig, + struct iio_poll_func *pf) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(pf->indio_dev); + bool no_other_users = + bitmap_weight(trig->pool, CONFIG_IIO_CONSUMERS_PER_TRIGGER) == 1; + int ret = 0; + + if (trig->ops && trig->ops->set_trigger_state && no_other_users) { + ret = trig->ops->set_trigger_state(trig, false); + if (ret) + return ret; + } + if (pf->indio_dev->dev.parent == trig->dev.parent) + trig->attached_own_device = false; + iio_trigger_put_irq(trig, pf->irq); + free_irq(pf->irq, pf); + module_put(iio_dev_opaque->driver_module); + pf->irq = 0; + + return ret; +} + +irqreturn_t iio_pollfunc_store_time(int irq, void *p) +{ + struct iio_poll_func *pf = p; + + pf->timestamp = iio_get_time_ns(pf->indio_dev); + return IRQ_WAKE_THREAD; +} +EXPORT_SYMBOL(iio_pollfunc_store_time); + +struct iio_poll_func +*iio_alloc_pollfunc(irqreturn_t (*h)(int irq, void *p), + irqreturn_t (*thread)(int irq, void *p), + int type, + struct iio_dev *indio_dev, + const char *fmt, + ...) +{ + va_list vargs; + struct iio_poll_func *pf; + + pf = kmalloc_obj(*pf); + if (!pf) + return NULL; + va_start(vargs, fmt); + pf->name = kvasprintf(GFP_KERNEL, fmt, vargs); + va_end(vargs); + if (pf->name == NULL) { + kfree(pf); + return NULL; + } + pf->h = h; + pf->thread = thread; + pf->type = type; + pf->indio_dev = indio_dev; + + return pf; +} +EXPORT_SYMBOL_GPL(iio_alloc_pollfunc); + +void iio_dealloc_pollfunc(struct iio_poll_func *pf) +{ + kfree(pf->name); + kfree(pf); +} +EXPORT_SYMBOL_GPL(iio_dealloc_pollfunc); + +/** + * current_trigger_show() - trigger consumer sysfs query current trigger + * @dev: device associated with an industrial I/O device + * @attr: pointer to the device_attribute structure that + * is being processed + * @buf: buffer where the current trigger name will be printed into + * + * For trigger consumers the current_trigger interface allows the trigger + * used by the device to be queried. + * + * Return: a negative number on failure, the number of characters written + * on success or 0 if no trigger is available + */ +static ssize_t current_trigger_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + + if (indio_dev->trig) + return sysfs_emit(buf, "%s\n", indio_dev->trig->name); + return 0; +} + +/** + * current_trigger_store() - trigger consumer sysfs set current trigger + * @dev: device associated with an industrial I/O device + * @attr: device attribute that is being processed + * @buf: string buffer that holds the name of the trigger + * @len: length of the trigger name held by buf + * + * For trigger consumers the current_trigger interface allows the trigger + * used for this device to be specified at run time based on the trigger's + * name. + * + * Return: negative error code on failure or length of the buffer + * on success + */ +static ssize_t current_trigger_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + struct iio_trigger *oldtrig = indio_dev->trig; + struct iio_trigger *trig; + int ret; + + scoped_guard(mutex, &iio_dev_opaque->mlock) { + if (iio_dev_opaque->currentmode == INDIO_BUFFER_TRIGGERED) + return -EBUSY; + if (iio_dev_opaque->trig_readonly) + return -EPERM; + } + + trig = iio_trigger_acquire_by_name(buf); + if (oldtrig == trig) { + ret = len; + goto out_trigger_put; + } + + if (trig && indio_dev->info->validate_trigger) { + ret = indio_dev->info->validate_trigger(indio_dev, trig); + if (ret) + goto out_trigger_put; + } + + if (trig && trig->ops && trig->ops->validate_device) { + ret = trig->ops->validate_device(trig, indio_dev); + if (ret) + goto out_trigger_put; + } + + indio_dev->trig = trig; + + if (oldtrig) { + if (indio_dev->modes & INDIO_EVENT_TRIGGERED) + iio_trigger_detach_poll_func(oldtrig, + indio_dev->pollfunc_event); + iio_trigger_put(oldtrig); + } + if (indio_dev->trig) { + if (indio_dev->modes & INDIO_EVENT_TRIGGERED) + iio_trigger_attach_poll_func(indio_dev->trig, + indio_dev->pollfunc_event); + } + + return len; + +out_trigger_put: + if (trig) + iio_trigger_put(trig); + return ret; +} + +static DEVICE_ATTR_RW(current_trigger); + +static struct attribute *iio_trigger_consumer_attrs[] = { + &dev_attr_current_trigger.attr, + NULL, +}; + +static const struct attribute_group iio_trigger_consumer_attr_group = { + .name = "trigger", + .attrs = iio_trigger_consumer_attrs, +}; + +static void iio_trig_release(struct device *device) +{ + struct iio_trigger *trig = to_iio_trigger(device); + int i; + + if (trig->subirq_base) { + for (i = 0; i < CONFIG_IIO_CONSUMERS_PER_TRIGGER; i++) { + irq_modify_status(trig->subirq_base + i, + IRQ_NOAUTOEN, + IRQ_NOREQUEST | IRQ_NOPROBE); + irq_set_chip(trig->subirq_base + i, + NULL); + irq_set_handler(trig->subirq_base + i, + NULL); + } + + irq_free_descs(trig->subirq_base, + CONFIG_IIO_CONSUMERS_PER_TRIGGER); + } + kfree(trig->name); + kfree(trig); +} + +static const struct device_type iio_trig_type = { + .release = iio_trig_release, + .groups = iio_trig_dev_groups, +}; + +static void iio_trig_subirqmask(struct irq_data *d) +{ + struct irq_chip *chip = irq_data_get_irq_chip(d); + struct iio_trigger *trig = container_of(chip, struct iio_trigger, subirq_chip); + + trig->subirqs[d->irq - trig->subirq_base].enabled = false; +} + +static void iio_trig_subirqunmask(struct irq_data *d) +{ + struct irq_chip *chip = irq_data_get_irq_chip(d); + struct iio_trigger *trig = container_of(chip, struct iio_trigger, subirq_chip); + + trig->subirqs[d->irq - trig->subirq_base].enabled = true; +} + +static __printf(3, 0) +struct iio_trigger *viio_trigger_alloc(struct device *parent, + struct module *this_mod, + const char *fmt, + va_list vargs) +{ + struct iio_trigger *trig; + int i; + + trig = kzalloc_obj(*trig); + if (!trig) + return NULL; + + INIT_WORK(&trig->reenable_work, iio_reenable_work_fn); + + mutex_init(&trig->pool_lock); + trig->subirq_base = irq_alloc_descs(-1, 0, + CONFIG_IIO_CONSUMERS_PER_TRIGGER, + 0); + if (trig->subirq_base < 0) + goto free_trig; + + trig->name = kvasprintf(GFP_KERNEL, fmt, vargs); + if (trig->name == NULL) + goto free_descs; + + INIT_LIST_HEAD(&trig->list); + + trig->owner = this_mod; + + trig->subirq_chip.name = trig->name; + trig->subirq_chip.irq_mask = &iio_trig_subirqmask; + trig->subirq_chip.irq_unmask = &iio_trig_subirqunmask; + for (i = 0; i < CONFIG_IIO_CONSUMERS_PER_TRIGGER; i++) { + irq_set_chip(trig->subirq_base + i, &trig->subirq_chip); + irq_set_handler(trig->subirq_base + i, &handle_simple_irq); + irq_modify_status(trig->subirq_base + i, + IRQ_NOREQUEST | IRQ_NOAUTOEN, IRQ_NOPROBE); + } + + trig->dev.parent = parent; + trig->dev.type = &iio_trig_type; + trig->dev.bus = &iio_bus_type; + device_initialize(&trig->dev); + + return trig; + +free_descs: + irq_free_descs(trig->subirq_base, CONFIG_IIO_CONSUMERS_PER_TRIGGER); +free_trig: + kfree(trig); + return NULL; +} + +/** + * __iio_trigger_alloc - Allocate a trigger + * @parent: Device to allocate iio_trigger for + * @this_mod: module allocating the trigger + * @fmt: trigger name format. If it includes format + * specifiers, the additional arguments following + * format are formatted and inserted in the resulting + * string replacing their respective specifiers. + * RETURNS: + * Pointer to allocated iio_trigger on success, NULL on failure. + */ +struct iio_trigger *__iio_trigger_alloc(struct device *parent, + struct module *this_mod, + const char *fmt, ...) +{ + struct iio_trigger *trig; + va_list vargs; + + va_start(vargs, fmt); + trig = viio_trigger_alloc(parent, this_mod, fmt, vargs); + va_end(vargs); + + return trig; +} +EXPORT_SYMBOL(__iio_trigger_alloc); + +void iio_trigger_free(struct iio_trigger *trig) +{ + if (trig) + put_device(&trig->dev); +} +EXPORT_SYMBOL(iio_trigger_free); + +static void devm_iio_trigger_release(void *trig) +{ + iio_trigger_free(trig); +} + +/** + * __devm_iio_trigger_alloc - Resource-managed iio_trigger_alloc() + * Managed iio_trigger_alloc. iio_trigger allocated with this function is + * automatically freed on driver detach. + * @parent: Device to allocate iio_trigger for + * @this_mod: module allocating the trigger + * @fmt: trigger name format. If it includes format + * specifiers, the additional arguments following + * format are formatted and inserted in the resulting + * string replacing their respective specifiers. + * + * + * RETURNS: + * Pointer to allocated iio_trigger on success, NULL on failure. + */ +struct iio_trigger *__devm_iio_trigger_alloc(struct device *parent, + struct module *this_mod, + const char *fmt, ...) +{ + struct iio_trigger *trig; + va_list vargs; + int ret; + + /* use raw alloc_dr for kmalloc caller tracing */ + va_start(vargs, fmt); + trig = viio_trigger_alloc(parent, this_mod, fmt, vargs); + va_end(vargs); + if (!trig) + return NULL; + + ret = devm_add_action_or_reset(parent, devm_iio_trigger_release, trig); + if (ret) + return NULL; + + return trig; +} +EXPORT_SYMBOL_GPL(__devm_iio_trigger_alloc); + +static void devm_iio_trigger_unreg(void *trigger_info) +{ + iio_trigger_unregister(trigger_info); +} + +/** + * devm_iio_trigger_register - Resource-managed iio_trigger_register() + * @dev: device this trigger was allocated for + * @trig_info: trigger to register + * + * Managed iio_trigger_register(). The IIO trigger registered with this + * function is automatically unregistered on driver detach. This function + * calls iio_trigger_register() internally. Refer to that function for more + * information. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int devm_iio_trigger_register(struct device *dev, + struct iio_trigger *trig_info) +{ + int ret; + + ret = iio_trigger_register(trig_info); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, devm_iio_trigger_unreg, trig_info); +} +EXPORT_SYMBOL_GPL(devm_iio_trigger_register); + +bool iio_trigger_using_own(struct iio_dev *indio_dev) +{ + return indio_dev->trig->attached_own_device; +} +EXPORT_SYMBOL(iio_trigger_using_own); + +/** + * iio_validate_own_trigger - Check if a trigger and IIO device belong to + * the same device + * @idev: the IIO device to check + * @trig: the IIO trigger to check + * + * This function can be used as the validate_trigger callback for triggers that + * can only be attached to their own device. + * + * Return: 0 if both the trigger and the IIO device belong to the same + * device, -EINVAL otherwise. + */ +int iio_validate_own_trigger(struct iio_dev *idev, struct iio_trigger *trig) +{ + if (idev->dev.parent != trig->dev.parent) + return -EINVAL; + return 0; +} +EXPORT_SYMBOL_GPL(iio_validate_own_trigger); + +/** + * iio_trigger_validate_own_device - Check if a trigger and IIO device belong to + * the same device + * @trig: The IIO trigger to check + * @indio_dev: the IIO device to check + * + * This function can be used as the validate_device callback for triggers that + * can only be attached to their own device. + * + * Return: 0 if both the trigger and the IIO device belong to the same + * device, -EINVAL otherwise. + */ +int iio_trigger_validate_own_device(struct iio_trigger *trig, + struct iio_dev *indio_dev) +{ + if (indio_dev->dev.parent != trig->dev.parent) + return -EINVAL; + return 0; +} +EXPORT_SYMBOL(iio_trigger_validate_own_device); + +int iio_device_register_trigger_consumer(struct iio_dev *indio_dev) +{ + return iio_device_register_sysfs_group(indio_dev, + &iio_trigger_consumer_attr_group); +} + +void iio_device_unregister_trigger_consumer(struct iio_dev *indio_dev) +{ + /* Clean up an associated but not attached trigger reference */ + if (indio_dev->trig) + iio_trigger_put(indio_dev->trig); +} + +int iio_device_suspend_triggering(struct iio_dev *indio_dev) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + + guard(mutex)(&iio_dev_opaque->mlock); + + if ((indio_dev->pollfunc) && (indio_dev->pollfunc->irq > 0)) + disable_irq(indio_dev->pollfunc->irq); + + return 0; +} +EXPORT_SYMBOL(iio_device_suspend_triggering); + +int iio_device_resume_triggering(struct iio_dev *indio_dev) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + + guard(mutex)(&iio_dev_opaque->mlock); + + if ((indio_dev->pollfunc) && (indio_dev->pollfunc->irq > 0)) + enable_irq(indio_dev->pollfunc->irq); + + return 0; +} +EXPORT_SYMBOL(iio_device_resume_triggering); diff --git a/drivers/iio/industrialio-triggered-event.c b/drivers/iio/industrialio-triggered-event.c new file mode 100644 index 000000000..4bedc65c9 --- /dev/null +++ b/drivers/iio/industrialio-triggered-event.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2015 Cogent Embedded, Inc. + */ + +#include +#include +#include +#include +#include +#include + +/** + * iio_triggered_event_setup() - Setup pollfunc_event for triggered event + * @indio_dev: IIO device structure + * @h: Function which will be used as pollfunc_event top half + * @thread: Function which will be used as pollfunc_event bottom half + * + * This function combines some common tasks which will normally be performed + * when setting up a triggered event. It will allocate the pollfunc_event and + * set mode to use it for triggered event. + * + * Before calling this function the indio_dev structure should already be + * completely initialized, but not yet registered. In practice this means that + * this function should be called right before iio_device_register(). + * + * To free the resources allocated by this function call + * iio_triggered_event_cleanup(). + */ +int iio_triggered_event_setup(struct iio_dev *indio_dev, + irqreturn_t (*h)(int irq, void *p), + irqreturn_t (*thread)(int irq, void *p)) +{ + indio_dev->pollfunc_event = iio_alloc_pollfunc(h, + thread, + IRQF_ONESHOT, + indio_dev, + "%s_consumer%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (indio_dev->pollfunc_event == NULL) + return -ENOMEM; + + /* Flag that events polling is possible */ + indio_dev->modes |= INDIO_EVENT_TRIGGERED; + + return 0; +} +EXPORT_SYMBOL(iio_triggered_event_setup); + +/** + * iio_triggered_event_cleanup() - Free resources allocated by iio_triggered_event_setup() + * @indio_dev: IIO device structure + */ +void iio_triggered_event_cleanup(struct iio_dev *indio_dev) +{ + indio_dev->modes &= ~INDIO_EVENT_TRIGGERED; + iio_dealloc_pollfunc(indio_dev->pollfunc_event); +} +EXPORT_SYMBOL(iio_triggered_event_cleanup); + +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_DESCRIPTION("IIO helper functions for setting up triggered events"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c new file mode 100644 index 000000000..1f22275de --- /dev/null +++ b/drivers/iio/inkern.c @@ -0,0 +1,1045 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* The industrial I/O core in kernel channel mapping + * + * Copyright (c) 2011 Jonathan Cameron + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "iio_core.h" +#include +#include +#include + +struct iio_map_internal { + struct iio_dev *indio_dev; + const struct iio_map *map; + struct list_head l; +}; + +static LIST_HEAD(iio_map_list); +static DEFINE_MUTEX(iio_map_list_lock); + +static int iio_map_array_unregister_locked(struct iio_dev *indio_dev) +{ + int ret = -ENODEV; + struct iio_map_internal *mapi, *next; + + list_for_each_entry_safe(mapi, next, &iio_map_list, l) { + if (indio_dev == mapi->indio_dev) { + list_del(&mapi->l); + kfree(mapi); + ret = 0; + } + } + return ret; +} + +int iio_map_array_register(struct iio_dev *indio_dev, const struct iio_map *maps) +{ + struct iio_map_internal *mapi; + int i = 0; + int ret; + + if (!maps) + return 0; + + guard(mutex)(&iio_map_list_lock); + while (maps[i].consumer_dev_name) { + mapi = kzalloc_obj(*mapi); + if (!mapi) { + ret = -ENOMEM; + goto error_ret; + } + mapi->map = &maps[i]; + mapi->indio_dev = indio_dev; + list_add_tail(&mapi->l, &iio_map_list); + i++; + } + + return 0; +error_ret: + iio_map_array_unregister_locked(indio_dev); + return ret; +} +EXPORT_SYMBOL_GPL(iio_map_array_register); + +/* + * Remove all map entries associated with the given iio device + */ +int iio_map_array_unregister(struct iio_dev *indio_dev) +{ + guard(mutex)(&iio_map_list_lock); + return iio_map_array_unregister_locked(indio_dev); +} +EXPORT_SYMBOL_GPL(iio_map_array_unregister); + +static void iio_map_array_unregister_cb(void *indio_dev) +{ + iio_map_array_unregister(indio_dev); +} + +int devm_iio_map_array_register(struct device *dev, struct iio_dev *indio_dev, + const struct iio_map *maps) +{ + int ret; + + ret = iio_map_array_register(indio_dev, maps); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, iio_map_array_unregister_cb, indio_dev); +} +EXPORT_SYMBOL_GPL(devm_iio_map_array_register); + +static const struct iio_chan_spec +*iio_chan_spec_from_name(const struct iio_dev *indio_dev, const char *name) +{ + int i; + const struct iio_chan_spec *chan = NULL; + + for (i = 0; i < indio_dev->num_channels; i++) + if (indio_dev->channels[i].datasheet_name && + strcmp(name, indio_dev->channels[i].datasheet_name) == 0) { + chan = &indio_dev->channels[i]; + break; + } + return chan; +} + +/** + * __fwnode_iio_simple_xlate - translate iiospec to the IIO channel index + * @indio_dev: pointer to the iio_dev structure + * @iiospec: IIO specifier as found in the device tree + * + * This is simple translation function, suitable for the most 1:1 mapped + * channels in IIO chips. This function performs only one sanity check: + * whether IIO index is less than num_channels (that is specified in the + * iio_dev). + */ +static int __fwnode_iio_simple_xlate(struct iio_dev *indio_dev, + const struct fwnode_reference_args *iiospec) +{ + if (!iiospec->nargs) + return 0; + + if (iiospec->args[0] >= indio_dev->num_channels) { + dev_err(&indio_dev->dev, "invalid channel index %llu\n", + iiospec->args[0]); + return -EINVAL; + } + + return iiospec->args[0]; +} + +static int __fwnode_iio_channel_get(struct iio_channel *channel, + struct fwnode_handle *fwnode, int index) +{ + struct fwnode_reference_args iiospec; + struct device *idev; + struct iio_dev *indio_dev; + int err; + + err = fwnode_property_get_reference_args(fwnode, "io-channels", + "#io-channel-cells", 0, + index, &iiospec); + if (err) + return err; + + idev = bus_find_device_by_fwnode(&iio_bus_type, iiospec.fwnode); + if (!idev) { + fwnode_handle_put(iiospec.fwnode); + return -EPROBE_DEFER; + } + + indio_dev = dev_to_iio_dev(idev); + channel->indio_dev = indio_dev; + if (indio_dev->info->fwnode_xlate) + index = indio_dev->info->fwnode_xlate(indio_dev, &iiospec); + else + index = __fwnode_iio_simple_xlate(indio_dev, &iiospec); + fwnode_handle_put(iiospec.fwnode); + if (index < 0) + goto err_put; + channel->channel = &indio_dev->channels[index]; + + return 0; + +err_put: + iio_device_put(indio_dev); + return index; +} + +static struct iio_channel *fwnode_iio_channel_get(struct fwnode_handle *fwnode, + int index) +{ + int err; + + if (index < 0) + return ERR_PTR(-EINVAL); + + struct iio_channel *channel __free(kfree) = + kzalloc_obj(*channel); + if (!channel) + return ERR_PTR(-ENOMEM); + + err = __fwnode_iio_channel_get(channel, fwnode, index); + if (err) + return ERR_PTR(err); + + return_ptr(channel); +} + +static struct iio_channel * +__fwnode_iio_channel_get_by_name(struct fwnode_handle *fwnode, const char *name) +{ + struct iio_channel *chan; + int index = 0; + + /* + * For named iio channels, first look up the name in the + * "io-channel-names" property. If it cannot be found, the + * index will be an error code, and fwnode_iio_channel_get() + * will fail. + */ + if (name) + index = fwnode_property_match_string(fwnode, "io-channel-names", + name); + + chan = fwnode_iio_channel_get(fwnode, index); + if (!IS_ERR(chan) || PTR_ERR(chan) == -EPROBE_DEFER) + return chan; + if (name) { + if (index >= 0) { + pr_err("ERROR: could not get IIO channel %pfw:%s(%i)\n", + fwnode, name, index); + /* + * In this case, we found 'name' in 'io-channel-names' + * but somehow we still fail so that we should not proceed + * with any other lookup. Hence, explicitly return -EINVAL + * (maybe not the better error code) so that the caller + * won't do a system lookup. + */ + return ERR_PTR(-EINVAL); + } + /* + * If index < 0, then fwnode_property_get_reference_args() fails + * with -EINVAL or -ENOENT (ACPI case) which is expected. We + * should not proceed if we get any other error. + */ + if (PTR_ERR(chan) != -EINVAL && PTR_ERR(chan) != -ENOENT) + return chan; + } else if (PTR_ERR(chan) != -ENOENT) { + /* + * if !name, then we should only proceed the lookup if + * fwnode_property_get_reference_args() returns -ENOENT. + */ + return chan; + } + + /* so we continue the lookup */ + return ERR_PTR(-ENODEV); +} + +struct iio_channel *fwnode_iio_channel_get_by_name(struct fwnode_handle *fwnode, + const char *name) +{ + struct fwnode_handle *parent; + struct iio_channel *chan; + + /* Walk up the tree of devices looking for a matching iio channel */ + chan = __fwnode_iio_channel_get_by_name(fwnode, name); + if (!IS_ERR(chan) || PTR_ERR(chan) != -ENODEV) + return chan; + + /* + * No matching IIO channel found on this node. + * If the parent node has a "io-channel-ranges" property, + * then we can try one of its channels. + */ + fwnode_for_each_parent_node(fwnode, parent) { + if (!fwnode_property_present(parent, "io-channel-ranges")) { + fwnode_handle_put(parent); + return ERR_PTR(-ENODEV); + } + + chan = __fwnode_iio_channel_get_by_name(parent, name); + if (!IS_ERR(chan) || PTR_ERR(chan) != -ENODEV) { + fwnode_handle_put(parent); + return chan; + } + } + + return ERR_PTR(-ENODEV); +} +EXPORT_SYMBOL_NS_GPL(fwnode_iio_channel_get_by_name, "IIO_CONSUMER"); + +static struct iio_channel *fwnode_iio_channel_get_all(struct device *dev) +{ + struct fwnode_handle *fwnode = dev_fwnode(dev); + int i, mapind, nummaps = 0; + int ret; + + do { + ret = fwnode_property_get_reference_args(fwnode, "io-channels", + "#io-channel-cells", 0, + nummaps, NULL); + if (ret < 0) + break; + } while (++nummaps); + + if (nummaps == 0) + return ERR_PTR(-ENODEV); + + /* NULL terminated array to save passing size */ + struct iio_channel *chans __free(kfree) = + kzalloc_objs(*chans, nummaps + 1); + if (!chans) + return ERR_PTR(-ENOMEM); + + /* Search for FW matches */ + for (mapind = 0; mapind < nummaps; mapind++) { + ret = __fwnode_iio_channel_get(&chans[mapind], fwnode, mapind); + if (ret) + goto error_free_chans; + } + return_ptr(chans); + +error_free_chans: + for (i = 0; i < mapind; i++) + iio_device_put(chans[i].indio_dev); + return ERR_PTR(ret); +} + +static struct iio_channel *iio_channel_get_sys(const char *name, + const char *channel_name) +{ + struct iio_map_internal *c_i = NULL, *c = NULL; + int err; + + if (!(name || channel_name)) + return ERR_PTR(-ENODEV); + + /* first find matching entry the channel map */ + scoped_guard(mutex, &iio_map_list_lock) { + list_for_each_entry(c_i, &iio_map_list, l) { + if ((name && strcmp(name, c_i->map->consumer_dev_name) != 0) || + (channel_name && + strcmp(channel_name, c_i->map->consumer_channel) != 0)) + continue; + c = c_i; + iio_device_get(c->indio_dev); + break; + } + } + if (!c) + return ERR_PTR(-ENODEV); + + struct iio_channel *channel __free(kfree) = + kzalloc_obj(*channel); + if (!channel) { + err = -ENOMEM; + goto error_no_mem; + } + + channel->indio_dev = c->indio_dev; + + if (c->map->adc_channel_label) { + channel->channel = + iio_chan_spec_from_name(channel->indio_dev, + c->map->adc_channel_label); + + if (!channel->channel) { + err = -EINVAL; + goto error_no_mem; + } + } + + return_ptr(channel); + +error_no_mem: + iio_device_put(c->indio_dev); + return ERR_PTR(err); +} + +struct iio_channel *iio_channel_get(struct device *dev, + const char *channel_name) +{ + const char *name = dev ? dev_name(dev) : NULL; + struct iio_channel *channel; + + if (dev) { + channel = fwnode_iio_channel_get_by_name(dev_fwnode(dev), + channel_name); + if (!IS_ERR(channel) || PTR_ERR(channel) != -ENODEV) + return channel; + } + + return iio_channel_get_sys(name, channel_name); +} +EXPORT_SYMBOL_NS_GPL(iio_channel_get, "IIO_CONSUMER"); + +void iio_channel_release(struct iio_channel *channel) +{ + if (!channel) + return; + iio_device_put(channel->indio_dev); + kfree(channel); +} +EXPORT_SYMBOL_NS_GPL(iio_channel_release, "IIO_CONSUMER"); + +static void devm_iio_channel_free(void *iio_channel) +{ + iio_channel_release(iio_channel); +} + +struct iio_channel *devm_iio_channel_get(struct device *dev, + const char *channel_name) +{ + struct iio_channel *channel; + int ret; + + channel = iio_channel_get(dev, channel_name); + if (IS_ERR(channel)) + return channel; + + ret = devm_add_action_or_reset(dev, devm_iio_channel_free, channel); + if (ret) + return ERR_PTR(ret); + + return channel; +} +EXPORT_SYMBOL_NS_GPL(devm_iio_channel_get, "IIO_CONSUMER"); + +struct iio_channel *devm_fwnode_iio_channel_get_by_name(struct device *dev, + struct fwnode_handle *fwnode, + const char *channel_name) +{ + struct iio_channel *channel; + int ret; + + channel = fwnode_iio_channel_get_by_name(fwnode, channel_name); + if (IS_ERR(channel)) + return channel; + + ret = devm_add_action_or_reset(dev, devm_iio_channel_free, channel); + if (ret) + return ERR_PTR(ret); + + return channel; +} +EXPORT_SYMBOL_NS_GPL(devm_fwnode_iio_channel_get_by_name, "IIO_CONSUMER"); + +struct iio_channel *iio_channel_get_all(struct device *dev) +{ + const char *name; + struct iio_map_internal *c = NULL; + struct iio_channel *fw_chans; + int nummaps = 0; + int mapind = 0; + int i, ret; + + if (!dev) + return ERR_PTR(-EINVAL); + + fw_chans = fwnode_iio_channel_get_all(dev); + /* + * We only want to carry on if the error is -ENODEV. Anything else + * should be reported up the stack. + */ + if (!IS_ERR(fw_chans) || PTR_ERR(fw_chans) != -ENODEV) + return fw_chans; + + name = dev_name(dev); + + guard(mutex)(&iio_map_list_lock); + /* first count the matching maps */ + list_for_each_entry(c, &iio_map_list, l) + if (name && strcmp(name, c->map->consumer_dev_name) != 0) + continue; + else + nummaps++; + + if (nummaps == 0) + return ERR_PTR(-ENODEV); + + /* NULL terminated array to save passing size */ + struct iio_channel *chans __free(kfree) = + kzalloc_objs(*chans, nummaps + 1); + if (!chans) + return ERR_PTR(-ENOMEM); + + /* for each map fill in the chans element */ + list_for_each_entry(c, &iio_map_list, l) { + if (name && strcmp(name, c->map->consumer_dev_name) != 0) + continue; + chans[mapind].indio_dev = c->indio_dev; + chans[mapind].data = c->map->consumer_data; + chans[mapind].channel = + iio_chan_spec_from_name(chans[mapind].indio_dev, + c->map->adc_channel_label); + if (!chans[mapind].channel) { + ret = -EINVAL; + goto error_free_chans; + } + iio_device_get(chans[mapind].indio_dev); + mapind++; + } + if (mapind == 0) { + ret = -ENODEV; + goto error_free_chans; + } + + return_ptr(chans); + +error_free_chans: + for (i = 0; i < mapind; i++) + iio_device_put(chans[i].indio_dev); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_NS_GPL(iio_channel_get_all, "IIO_CONSUMER"); + +void iio_channel_release_all(struct iio_channel *channels) +{ + struct iio_channel *chan = &channels[0]; + + while (chan->indio_dev) { + iio_device_put(chan->indio_dev); + chan++; + } + kfree(channels); +} +EXPORT_SYMBOL_NS_GPL(iio_channel_release_all, "IIO_CONSUMER"); + +static void devm_iio_channel_free_all(void *iio_channels) +{ + iio_channel_release_all(iio_channels); +} + +struct iio_channel *devm_iio_channel_get_all(struct device *dev) +{ + struct iio_channel *channels; + int ret; + + channels = iio_channel_get_all(dev); + if (IS_ERR(channels)) + return channels; + + ret = devm_add_action_or_reset(dev, devm_iio_channel_free_all, + channels); + if (ret) + return ERR_PTR(ret); + + return channels; +} +EXPORT_SYMBOL_NS_GPL(devm_iio_channel_get_all, "IIO_CONSUMER"); + +static int iio_channel_read(struct iio_channel *chan, int *val, int *val2, + enum iio_chan_info_enum info) +{ + const struct iio_info *iio_info = chan->indio_dev->info; + int unused; + int vals[INDIO_MAX_RAW_ELEMENTS]; + int ret; + int val_len = 2; + + if (!val2) + val2 = &unused; + + if (!iio_channel_has_info(chan->channel, info)) + return -EINVAL; + + if (iio_info->read_raw_multi) { + ret = iio_info->read_raw_multi(chan->indio_dev, + chan->channel, + INDIO_MAX_RAW_ELEMENTS, + vals, &val_len, info); + *val = vals[0]; + *val2 = vals[1]; + } else if (iio_info->read_raw) { + ret = iio_info->read_raw(chan->indio_dev, + chan->channel, val, val2, info); + } else { + return -EINVAL; + } + + return ret; +} + +int iio_read_channel_raw(struct iio_channel *chan, int *val) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev); + + guard(mutex)(&iio_dev_opaque->info_exist_lock); + if (!chan->indio_dev->info) + return -ENODEV; + + return iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_RAW); +} +EXPORT_SYMBOL_NS_GPL(iio_read_channel_raw, "IIO_CONSUMER"); + +int iio_read_channel_average_raw(struct iio_channel *chan, int *val) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev); + + guard(mutex)(&iio_dev_opaque->info_exist_lock); + if (!chan->indio_dev->info) + return -ENODEV; + + return iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_AVERAGE_RAW); +} +EXPORT_SYMBOL_NS_GPL(iio_read_channel_average_raw, "IIO_CONSUMER"); + +int iio_multiply_value(int *result, s64 multiplier, + unsigned int type, int val, int val2) +{ + s64 denominator; + + switch (type) { + case IIO_VAL_INT: + *result = multiplier * val; + return IIO_VAL_INT; + case IIO_VAL_INT_PLUS_MICRO: + case IIO_VAL_INT_PLUS_NANO: + switch (type) { + case IIO_VAL_INT_PLUS_MICRO: + denominator = MICRO; + break; + case IIO_VAL_INT_PLUS_NANO: + denominator = NANO; + break; + } + *result = multiplier * abs(val); + *result += div_s64(multiplier * abs(val2), denominator); + if (val < 0 || val2 < 0) + *result *= -1; + return IIO_VAL_INT; + case IIO_VAL_FRACTIONAL: + *result = div_s64(multiplier * val, val2); + return IIO_VAL_INT; + case IIO_VAL_FRACTIONAL_LOG2: + *result = (multiplier * val) >> val2; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} +EXPORT_SYMBOL_NS_GPL(iio_multiply_value, "IIO_UNIT_TEST"); + +static int iio_convert_raw_to_processed_unlocked(struct iio_channel *chan, + int raw, int *processed, + unsigned int scale) +{ + int scale_type, scale_val, scale_val2; + int offset_type, offset_val, offset_val2; + s64 raw64 = raw; + int ret; + + offset_type = iio_channel_read(chan, &offset_val, &offset_val2, + IIO_CHAN_INFO_OFFSET); + if (offset_type >= 0) { + switch (offset_type) { + case IIO_VAL_INT: + break; + case IIO_VAL_INT_PLUS_MICRO: + case IIO_VAL_INT_PLUS_NANO: + /* + * Both IIO_VAL_INT_PLUS_MICRO and IIO_VAL_INT_PLUS_NANO + * implicitely truncate the offset to it's integer form. + */ + break; + case IIO_VAL_FRACTIONAL: + offset_val /= offset_val2; + break; + case IIO_VAL_FRACTIONAL_LOG2: + offset_val >>= offset_val2; + break; + default: + return -EINVAL; + } + + raw64 += offset_val; + } + + scale_type = iio_channel_read(chan, &scale_val, &scale_val2, + IIO_CHAN_INFO_SCALE); + if (scale_type < 0) { + /* + * If no channel scaling is available apply consumer scale to + * raw value and return. + */ + *processed = raw64 * scale; + return 0; + } + + ret = iio_multiply_value(processed, raw64 * scale, + scale_type, scale_val, scale_val2); + if (ret < 0) + return ret; + + return 0; +} + +int iio_convert_raw_to_processed(struct iio_channel *chan, int raw, + int *processed, unsigned int scale) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev); + + guard(mutex)(&iio_dev_opaque->info_exist_lock); + if (!chan->indio_dev->info) + return -ENODEV; + + return iio_convert_raw_to_processed_unlocked(chan, raw, processed, + scale); +} +EXPORT_SYMBOL_NS_GPL(iio_convert_raw_to_processed, "IIO_CONSUMER"); + +int iio_read_channel_attribute(struct iio_channel *chan, int *val, int *val2, + enum iio_chan_info_enum attribute) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev); + + guard(mutex)(&iio_dev_opaque->info_exist_lock); + if (!chan->indio_dev->info) + return -ENODEV; + + return iio_channel_read(chan, val, val2, attribute); +} +EXPORT_SYMBOL_NS_GPL(iio_read_channel_attribute, "IIO_CONSUMER"); + +int iio_read_channel_offset(struct iio_channel *chan, int *val, int *val2) +{ + return iio_read_channel_attribute(chan, val, val2, IIO_CHAN_INFO_OFFSET); +} +EXPORT_SYMBOL_NS_GPL(iio_read_channel_offset, "IIO_CONSUMER"); + +int iio_read_channel_processed_scale(struct iio_channel *chan, int *val, + unsigned int scale) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev); + int ret, pval, pval2; + + guard(mutex)(&iio_dev_opaque->info_exist_lock); + if (!chan->indio_dev->info) + return -ENODEV; + + if (iio_channel_has_info(chan->channel, IIO_CHAN_INFO_PROCESSED)) { + ret = iio_channel_read(chan, &pval, &pval2, + IIO_CHAN_INFO_PROCESSED); + if (ret < 0) + return ret; + + ret = iio_multiply_value(val, scale, ret, pval, pval2); + if (ret < 0) + return ret; + + return 0; + } else { + ret = iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_RAW); + if (ret < 0) + return ret; + + return iio_convert_raw_to_processed_unlocked(chan, *val, val, + scale); + } +} +EXPORT_SYMBOL_NS_GPL(iio_read_channel_processed_scale, "IIO_CONSUMER"); + +int iio_read_channel_processed(struct iio_channel *chan, int *val) +{ + /* This is just a special case with scale factor 1 */ + return iio_read_channel_processed_scale(chan, val, 1); +} +EXPORT_SYMBOL_NS_GPL(iio_read_channel_processed, "IIO_CONSUMER"); + +int iio_read_channel_scale(struct iio_channel *chan, int *val, int *val2) +{ + return iio_read_channel_attribute(chan, val, val2, IIO_CHAN_INFO_SCALE); +} +EXPORT_SYMBOL_NS_GPL(iio_read_channel_scale, "IIO_CONSUMER"); + +static int iio_channel_read_avail(struct iio_channel *chan, + const int **vals, int *type, int *length, + enum iio_chan_info_enum info) +{ + const struct iio_info *iio_info = chan->indio_dev->info; + + if (!iio_channel_has_available(chan->channel, info)) + return -EINVAL; + + if (iio_info->read_avail) + return iio_info->read_avail(chan->indio_dev, chan->channel, + vals, type, length, info); + return -EINVAL; +} + +int iio_read_avail_channel_attribute(struct iio_channel *chan, + const int **vals, int *type, int *length, + enum iio_chan_info_enum attribute) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev); + + guard(mutex)(&iio_dev_opaque->info_exist_lock); + if (!chan->indio_dev->info) + return -ENODEV; + + return iio_channel_read_avail(chan, vals, type, length, attribute); +} +EXPORT_SYMBOL_NS_GPL(iio_read_avail_channel_attribute, "IIO_CONSUMER"); + +int iio_read_avail_channel_raw(struct iio_channel *chan, + const int **vals, int *length) +{ + int ret; + int type; + + ret = iio_read_avail_channel_attribute(chan, vals, &type, length, + IIO_CHAN_INFO_RAW); + + if (ret >= 0 && type != IIO_VAL_INT) + /* raw values are assumed to be IIO_VAL_INT */ + ret = -EINVAL; + + return ret; +} +EXPORT_SYMBOL_NS_GPL(iio_read_avail_channel_raw, "IIO_CONSUMER"); + +static int iio_channel_read_max(struct iio_channel *chan, + int *val, int *val2, int *type, + enum iio_chan_info_enum info) +{ + const int *vals; + int length; + int ret; + + ret = iio_channel_read_avail(chan, &vals, type, &length, info); + if (ret < 0) + return ret; + + switch (ret) { + case IIO_AVAIL_RANGE: + switch (*type) { + case IIO_VAL_INT: + *val = vals[2]; + break; + default: + *val = vals[4]; + if (val2) + *val2 = vals[5]; + } + return 0; + + case IIO_AVAIL_LIST: + if (length <= 0) + return -EINVAL; + switch (*type) { + case IIO_VAL_INT: + *val = max_array(vals, length); + break; + default: + /* TODO: learn about max for other iio values */ + return -EINVAL; + } + return 0; + + default: + return -EINVAL; + } +} + +int iio_read_max_channel_raw(struct iio_channel *chan, int *val) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev); + int type; + + guard(mutex)(&iio_dev_opaque->info_exist_lock); + if (!chan->indio_dev->info) + return -ENODEV; + + return iio_channel_read_max(chan, val, NULL, &type, IIO_CHAN_INFO_RAW); +} +EXPORT_SYMBOL_NS_GPL(iio_read_max_channel_raw, "IIO_CONSUMER"); + +static int iio_channel_read_min(struct iio_channel *chan, + int *val, int *val2, int *type, + enum iio_chan_info_enum info) +{ + const int *vals; + int length; + int ret; + + ret = iio_channel_read_avail(chan, &vals, type, &length, info); + if (ret < 0) + return ret; + + switch (ret) { + case IIO_AVAIL_RANGE: + switch (*type) { + case IIO_VAL_INT: + *val = vals[0]; + break; + default: + *val = vals[0]; + if (val2) + *val2 = vals[1]; + } + return 0; + + case IIO_AVAIL_LIST: + if (length <= 0) + return -EINVAL; + switch (*type) { + case IIO_VAL_INT: + *val = min_array(vals, length); + break; + default: + /* TODO: learn about min for other iio values */ + return -EINVAL; + } + return 0; + + default: + return -EINVAL; + } +} + +int iio_read_min_channel_raw(struct iio_channel *chan, int *val) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev); + int type; + + guard(mutex)(&iio_dev_opaque->info_exist_lock); + if (!chan->indio_dev->info) + return -ENODEV; + + return iio_channel_read_min(chan, val, NULL, &type, IIO_CHAN_INFO_RAW); +} +EXPORT_SYMBOL_NS_GPL(iio_read_min_channel_raw, "IIO_CONSUMER"); + +int iio_get_channel_type(struct iio_channel *chan, enum iio_chan_type *type) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev); + + guard(mutex)(&iio_dev_opaque->info_exist_lock); + if (!chan->indio_dev->info) + return -ENODEV; + + *type = chan->channel->type; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(iio_get_channel_type, "IIO_CONSUMER"); + +static int iio_channel_write(struct iio_channel *chan, int val, int val2, + enum iio_chan_info_enum info) +{ + const struct iio_info *iio_info = chan->indio_dev->info; + + if (iio_info->write_raw) + return iio_info->write_raw(chan->indio_dev, + chan->channel, val, val2, info); + return -EINVAL; +} + +int iio_write_channel_attribute(struct iio_channel *chan, int val, int val2, + enum iio_chan_info_enum attribute) +{ + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev); + + guard(mutex)(&iio_dev_opaque->info_exist_lock); + if (!chan->indio_dev->info) + return -ENODEV; + + return iio_channel_write(chan, val, val2, attribute); +} +EXPORT_SYMBOL_NS_GPL(iio_write_channel_attribute, "IIO_CONSUMER"); + +int iio_write_channel_raw(struct iio_channel *chan, int val) +{ + return iio_write_channel_attribute(chan, val, 0, IIO_CHAN_INFO_RAW); +} +EXPORT_SYMBOL_NS_GPL(iio_write_channel_raw, "IIO_CONSUMER"); + +unsigned int iio_get_channel_ext_info_count(struct iio_channel *chan) +{ + const struct iio_chan_spec_ext_info *ext_info; + unsigned int i = 0; + + if (!chan->channel->ext_info) + return i; + + for (ext_info = chan->channel->ext_info; ext_info->name; ext_info++) + ++i; + + return i; +} +EXPORT_SYMBOL_NS_GPL(iio_get_channel_ext_info_count, "IIO_CONSUMER"); + +static const struct iio_chan_spec_ext_info * +iio_lookup_ext_info(const struct iio_channel *chan, const char *attr) +{ + const struct iio_chan_spec_ext_info *ext_info; + + if (!chan->channel->ext_info) + return NULL; + + for (ext_info = chan->channel->ext_info; ext_info->name; ++ext_info) { + if (!strcmp(attr, ext_info->name)) + return ext_info; + } + + return NULL; +} + +ssize_t iio_read_channel_ext_info(struct iio_channel *chan, + const char *attr, char *buf) +{ + const struct iio_chan_spec_ext_info *ext_info; + + if (!buf || offset_in_page(buf)) { + pr_err("iio: invalid ext_info read buffer\n"); + return -EINVAL; + } + + ext_info = iio_lookup_ext_info(chan, attr); + if (!ext_info) + return -EINVAL; + + return ext_info->read(chan->indio_dev, ext_info->private, + chan->channel, buf); +} +EXPORT_SYMBOL_NS_GPL(iio_read_channel_ext_info, "IIO_CONSUMER"); + +ssize_t iio_write_channel_ext_info(struct iio_channel *chan, const char *attr, + const char *buf, size_t len) +{ + const struct iio_chan_spec_ext_info *ext_info; + + ext_info = iio_lookup_ext_info(chan, attr); + if (!ext_info) + return -EINVAL; + + return ext_info->write(chan->indio_dev, ext_info->private, + chan->channel, buf, len); +} +EXPORT_SYMBOL_NS_GPL(iio_write_channel_ext_info, "IIO_CONSUMER"); + +ssize_t iio_read_channel_label(struct iio_channel *chan, char *buf) +{ + if (!buf || offset_in_page(buf)) { + pr_err("iio: invalid label read buffer\n"); + return -EINVAL; + } + + return do_iio_read_channel_label(chan->indio_dev, chan->channel, buf); +} +EXPORT_SYMBOL_NS_GPL(iio_read_channel_label, "IIO_CONSUMER"); diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig new file mode 100644 index 000000000..f23bbce12 --- /dev/null +++ b/drivers/iio/light/Kconfig @@ -0,0 +1,809 @@ +# SPDX-License-Identifier: GPL-2.0-only + +# +# Light sensors +# +# When adding new entries keep the list in alphabetical order + +menu "Light sensors" + +config ACPI_ALS + tristate "ACPI Ambient Light Sensor" + depends on ACPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select IIO_KFIFO_BUF + help + Say Y here if you want to build a driver for the ACPI0008 + Ambient Light Sensor. + + To compile this driver as a module, choose M here: the module will + be called acpi-als. + +config ADJD_S311 + tristate "ADJD-S311-CR999 digital color sensor" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + depends on I2C + help + If you say yes here you get support for the Avago ADJD-S311-CR999 + digital color light sensor. + + This driver can also be built as a module. If so, the module + will be called adjd_s311. + +config ADUX1020 + tristate "ADUX1020 photometric sensor" + select REGMAP_I2C + depends on I2C + help + Say Y here if you want to build a driver for the Analog Devices + ADUX1020 photometric sensor. + + To compile this driver as a module, choose M here: the + module will be called adux1020. + +config AL3000A + tristate "AL3000a ambient light sensor" + select REGMAP_I2C + depends on I2C + help + Say Y here if you want to build a driver for the Dyna Image AL3000a + ambient light sensor. + + To compile this driver as a module, choose M here: the + module will be called al3000a. + +config AL3010 + tristate "AL3010 ambient light sensor" + select REGMAP_I2C + depends on I2C + help + Say Y here if you want to build a driver for the Dyna Image AL3010 + ambient light sensor. + + To compile this driver as a module, choose M here: the + module will be called al3010. + +config AL3320A + tristate "AL3320A ambient light sensor" + select REGMAP_I2C + depends on I2C + help + Say Y here if you want to build a driver for the Dyna Image AL3320A + ambient light sensor. + + To compile this driver as a module, choose M here: the + module will be called al3320a. + +config APDS9160 + tristate "APDS9160 combined als and proximity sensor" + depends on I2C + select REGMAP_I2C + help + Say Y here if you want to build support for a Broadcom APDS9160 + combined ambient light and proximity sensor. + + To compile this driver as a module, choose M here: the + module will be called apds9160. + +config APDS9300 + tristate "APDS9300 ambient light sensor" + depends on I2C + help + Say Y here if you want to build a driver for the Avago APDS9300 + ambient light sensor. + + To compile this driver as a module, choose M here: the + module will be called apds9300. + +config APDS9306 + tristate "Avago APDS9306 Ambient Light Sensor" + depends on I2C + select REGMAP_I2C + select IIO_GTS_HELPER + help + If you say Y or M here, you get support for Avago APDS9306 + Ambient Light Sensor. + + If built as a dynamically linked module, it will be called + apds9306. + +config APDS9960 + tristate "Avago APDS9960 gesture/RGB/ALS/proximity sensor" + select REGMAP_I2C + select IIO_BUFFER + select IIO_KFIFO_BUF + depends on I2C + help + Say Y here to build I2C interface support for the Avago + APDS9960 gesture/RGB/ALS/proximity sensor. + + To compile this driver as a module, choose M here: the + module will be called apds9960 + +config APDS9999 + tristate "Broadcom APDS9999 ALS, RGB and proximity sensor" + depends on I2C + help + Say Y here if you want to build support for the Broadcom APDS9999 + ALS, RGB and proximity sensor with I2C interface. + + This driver provides ambient light sensing (ALS/Lux), raw + intensity data for red, green, blue and IR channels, plus + proximity detection support. + + To compile this driver as a module, choose M here: the + module will be called apds9999. + +config AS73211 + tristate "AMS AS73211 XYZ color sensor and AMS AS7331 UV sensor" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for the AMS AS73211 + JENCOLOR(R) Digital XYZ and the AMS AS7331 UVA, UVB and UVC + ultraviolet sensors. + + For triggered measurements, you will need an additional trigger driver + like IIO_HRTIMER_TRIGGER or IIO_SYSFS_TRIGGER. + + This driver can also be built as a module. If so, the module + will be called as73211. + +config BH1745 + tristate "ROHM BH1745 colour sensor" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select IIO_GTS_HELPER + help + Say Y here to build support for the ROHM bh1745 colour sensor. + + To compile this driver as a module, choose M here: the module will + be called bh1745. + +config BH1750 + tristate "ROHM BH1750 ambient light sensor" + depends on I2C + help + Say Y here to build support for the ROHM BH1710, BH1715, BH1721, + BH1750, BH1751 ambient light sensors. + + To compile this driver as a module, choose M here: the module will + be called bh1750. + +config BH1780 + tristate "ROHM BH1780 ambient light sensor" + depends on I2C + help + Say Y here to build support for the ROHM BH1780GLI ambient + light sensor. + + To compile this driver as a module, choose M here: the module will + be called bh1780. + +config CM32181 + depends on I2C + tristate "CM32181 driver" + help + Say Y here if you use cm32181. + This option enables ambient light sensor using + Capella cm32181 device driver. + + To compile this driver as a module, choose M here: + the module will be called cm32181. + +config CM3232 + depends on I2C + tristate "CM3232 ambient light sensor" + help + Say Y here if you use cm3232. + This option enables ambient light sensor using + Capella Microsystems cm3232 device driver. + + To compile this driver as a module, choose M here: + the module will be called cm3232. + +config CM3323 + depends on I2C + tristate "Capella CM3323 color light sensor" + help + Say Y here if you want to build a driver for Capella CM3323 + color sensor. + + To compile this driver as a module, choose M here: the module will + be called cm3323. + +config CM3605 + tristate "Capella CM3605 ambient light and proximity sensor" + help + Say Y here if you want to build a driver for Capella CM3605 + ambient light and short range proximity sensor. + + To compile this driver as a module, choose M here: the module will + be called cm3605. + +config CM36651 + depends on I2C + tristate "CM36651 driver" + help + Say Y here if you use cm36651. + This option enables proximity & RGB sensor using + Capella cm36651 device driver. + + To compile this driver as a module, choose M here: + the module will be called cm36651. + +config IIO_CROS_EC_LIGHT_PROX + tristate "ChromeOS EC Light and Proximity Sensors" + depends on IIO_CROS_EC_SENSORS_CORE + help + Say Y here if you use the light and proximity sensors + presented by the ChromeOS EC Sensor hub. + + To compile this driver as a module, choose M here: + the module will be called cros_ec_light_prox. + +config GP2AP002 + tristate "Sharp GP2AP002 Proximity/ALS sensor" + depends on I2C + select REGMAP + help + Say Y here if you have a Sharp GP2AP002 proximity/ALS combo-chip + hooked to an I2C bus. + + To compile this driver as a module, choose M here: the + module will be called gp2ap002. + +config GP2AP020A00F + tristate "Sharp GP2AP020A00F Proximity/ALS sensor" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select IRQ_WORK + help + Say Y here if you have a Sharp GP2AP020A00F proximity/ALS combo-chip + hooked to an I2C bus. + + To compile this driver as a module, choose M here: the + module will be called gp2ap020a00f. + +config IQS621_ALS + tristate "Azoteq IQS621/622 ambient light sensors" + depends on MFD_IQS62X || COMPILE_TEST + help + Say Y here if you want to build support for the Azoteq IQS621 + and IQS622 ambient light sensors. + + To compile this driver as a module, choose M here: the module + will be called iqs621-als. + +config SENSORS_ISL29018 + tristate "Intersil 29018 light and proximity sensor" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for ambient light sensing and + proximity infrared sensing from Intersil ISL29018. + This driver will provide the measurements of ambient light intensity + in lux, proximity infrared sensing and normal infrared sensing. + Data from sensor is accessible via sysfs. + +config SENSORS_ISL29028 + tristate "Intersil ISL29028 Concurrent Light and Proximity Sensor" + depends on I2C + select REGMAP_I2C + help + Provides driver for the Intersil's ISL29028 device. + This driver supports the sysfs interface to get the ALS, IR intensity, + Proximity value via iio. The ISL29028 provides the concurrent sensing + of ambient light and proximity. + +config ISL29125 + tristate "Intersil ISL29125 digital color light sensor" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here if you want to build a driver for the Intersil ISL29125 + RGB light sensor for I2C. + + To compile this driver as a module, choose M here: the module will be + called isl29125. + +config ISL76682 + tristate "Intersil ISL76682 Light Sensor" + depends on I2C + select REGMAP_I2C + help + Say Y here if you want to build a driver for the Intersil ISL76682 + Ambient Light Sensor and IR Intensity sensor. This driver provides + the readouts via standard IIO sysfs and device interface. Both ALS + illuminance and IR illuminance are provided raw with separate scale + setting which can be configured via sysfs, the default scale is 1000 + lux, other options are 4000/16000/64000 lux. + + To compile this driver as a module, choose M here: the module will be + called isl76682. + +config HID_SENSOR_ALS + depends on HID_SENSOR_HUB + select IIO_BUFFER + select HID_SENSOR_IIO_COMMON + select HID_SENSOR_IIO_TRIGGER + tristate "HID ALS" + help + Say yes here to build support for the HID SENSOR + Ambient light sensor. + + To compile this driver as a module, choose M here: the + module will be called hid-sensor-als. + +config HID_SENSOR_PROX + depends on HID_SENSOR_HUB + select IIO_BUFFER + select HID_SENSOR_IIO_COMMON + select HID_SENSOR_IIO_TRIGGER + tristate "HID PROX" + help + Say yes here to build support for the HID SENSOR + Proximity sensor. + + To compile this driver as a module, choose M here: the + module will be called hid-sensor-prox. + +config JSA1212 + tristate "JSA1212 ALS and proximity sensor driver" + depends on I2C + select REGMAP_I2C + help + Say Y here if you want to build a IIO driver for JSA1212 + proximity & ALS sensor device. + + To compile this driver as a module, choose M here: + the module will be called jsa1212. + +config ROHM_BU27034 + tristate "ROHM BU27034 ambient light sensor" + depends on I2C + select REGMAP_I2C + select IIO_GTS_HELPER + select IIO_BUFFER + select IIO_KFIFO_BUF + help + Enable support for the ROHM BU27034 ambient light sensor. ROHM BU27034 + is an ambient light sensor with 3 channels and 3 photo diodes capable + of detecting a very wide range of illuminance. + Typical application is adjusting LCD and backlight power of TVs and + mobile phones. + +config RPR0521 + tristate "ROHM RPR0521 ALS and proximity sensor driver" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here if you want to build support for ROHM's RPR0521 + ambient light and proximity sensor device. + + To compile this driver as a module, choose M here: + the module will be called rpr0521. + +config SENSORS_LM3533 + tristate "LM3533 ambient light sensor" + depends on MFD_LM3533 + help + If you say yes here you get support for the ambient light sensor + interface on National Semiconductor / TI LM3533 Lighting Power + chips. + + The sensor interface can be used to control the LEDs and backlights + of the chip through defining five light zones and three sets of + corresponding output-current values. + + The driver provides raw and mean adc readings along with the current + light zone through sysfs. A threshold event can be generated on zone + changes. The ALS-control output values can be set per zone for the + three current output channels. + +config LTR390 + tristate "LTR-390UV-01 ambient light and UV sensor" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for the Lite-On LTR-390UV-01 + ambient light and UV sensor. + + This driver can also be built as a module. If so, the module + will be called ltr390. + +config LTR501 + tristate "LTR-501ALS-01 light sensor" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for the Lite-On LTR-501ALS-01 + ambient light and proximity sensor. This driver also supports LTR-559 + ALS/PS or LTR-301 ALS sensors. + + This driver can also be built as a module. If so, the module + will be called ltr501. + +config LTRF216A + tristate "Liteon LTRF216A Light Sensor" + depends on I2C + select REGMAP_I2C + help + If you say Y or M here, you get support for Liteon LTRF216A + Ambient Light Sensor. + + If built as a dynamically linked module, it will be called + ltrf216a. + +config LV0104CS + tristate "LV0104CS Ambient Light Sensor" + depends on I2C + help + Say Y here if you want to build support for the On Semiconductor + LV0104CS ambient light sensor. + + To compile this driver as a module, choose M here: + the module will be called lv0104cs. + +config MAX44000 + tristate "MAX44000 Ambient and Infrared Proximity Sensor" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here if you want to build support for Maxim Integrated's + MAX44000 ambient and infrared proximity sensor device. + + To compile this driver as a module, choose M here: + the module will be called max44000. + +config MAX44009 + tristate "MAX44009 Ambient Light Sensor" + depends on I2C + select REGMAP_I2C + help + Say Y here if you want to build support for Maxim Integrated's + MAX44009 ambient light sensor device. + + To compile this driver as a module, choose M here: + the module will be called max44009. + +config NOA1305 + tristate "ON Semiconductor NOA1305 ambient light sensor" + depends on I2C + select REGMAP_I2C + help + Say Y here if you want to build support for the ON Semiconductor + NOA1305 ambient light sensor. + + To compile this driver as a module, choose M here: + The module will be called noa1305. + +config OPT3001 + tristate "Texas Instruments OPT3001 Light Sensor" + depends on I2C + help + If you say Y or M here, you get support for Texas Instruments + OPT3001 Ambient Light Sensor, OPT3002 Light-to-Digital Sensor. + + If built as a dynamically linked module, it will be called + opt3001. + +config OPT4001 + tristate "Texas Instruments OPT4001 Light Sensor" + depends on I2C + select REGMAP_I2C + help + If you say Y or M here, you get support for Texas Instruments + OPT4001 Ambient Light Sensor. + + If built as a dynamically linked module, it will be called + opt4001. + +config OPT4060 + tristate "Texas Instruments OPT4060 RGBW Color Sensor" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say Y or M here, you get support for Texas Instruments + OPT4060 RGBW Color Sensor. + + If built as a dynamically linked module, it will be called + opt4060. + +config PA12203001 + tristate "TXC PA12203001 light and proximity sensor" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for the TXC PA12203001 + ambient light and proximity sensor. + + This driver can also be built as a module. If so, the module + will be called pa12203001. + +config SI1133 + tristate "SI1133 UV Index Sensor and Ambient Light Sensor" + depends on I2C + select REGMAP_I2C + help + Say Y here if you want to build a driver for the Silicon Labs SI1133 + UV Index Sensor and Ambient Light Sensor chip. + + To compile this driver as a module, choose M here: the module will be + called si1133. + +config SI1145 + tristate "SI1132 and SI1141/2/3/5/6/7 combined ALS, UV index and proximity sensor" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here if you want to build a driver for the Silicon Labs SI1132 or + SI1141/2/3/5/6/7 combined ambient light, UV index and proximity sensor + chips. + + To compile this driver as a module, choose M here: the module will be + called si1145. + +config STK3310 + tristate "STK3310 ALS and proximity sensor" + depends on I2C + select REGMAP_I2C + help + Say yes here to get support for the Sensortek STK3310 ambient light + and proximity sensor. The STK3311 model is also supported by this + driver. + + Choosing M will build the driver as a module. If so, the module + will be called stk3310. + +config ST_UVIS25 + tristate "STMicroelectronics UVIS25 sensor driver" + depends on (I2C || SPI) + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select ST_UVIS25_I2C if (I2C) + select ST_UVIS25_SPI if (SPI_MASTER) + help + Say yes here to build support for STMicroelectronics UVIS25 + uv sensor + + To compile this driver as a module, choose M here: the module + will be called st_uvis25. + +config ST_UVIS25_I2C + tristate + depends on ST_UVIS25 + select REGMAP_I2C + +config ST_UVIS25_SPI + tristate + depends on ST_UVIS25 + select REGMAP_SPI + +config TCS3414 + tristate "TAOS TCS3414 digital color sensor" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for the TAOS TCS3414 + family of digital color sensors. + + This driver can also be built as a module. If so, the module + will be called tcs3414. + +config TCS3472 + tristate "TAOS TCS3472 color light-to-digital converter" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for the TAOS TCS3472 + family of color light-to-digital converters with IR filter. + + This driver can also be built as a module. If so, the module + will be called tcs3472. + +config SENSORS_TSL2563 + tristate "TAOS TSL2560, TSL2561, TSL2562 and TSL2563 ambient light sensors" + depends on I2C + help + If you say yes here you get support for the Taos TSL2560, + TSL2561, TSL2562 and TSL2563 ambient light sensors. + + This driver can also be built as a module. If so, the module + will be called tsl2563. + +config TSL2583 + tristate "TAOS TSL2580, TSL2581 and TSL2583 light-to-digital converters" + depends on I2C + help + Provides support for the TAOS tsl2580, tsl2581 and tsl2583 devices. + Access ALS data via iio, sysfs. + +config TSL2591 + tristate "TAOS TSL2591 ambient light sensor" + depends on I2C + help + Select Y here for support of the AMS/TAOS TSL2591 ambient light sensor, + featuring channels for combined visible + IR intensity and lux illuminance. + Access data via iio and sysfs. Supports iio_events. + + To compile this driver as a module, select M: the + module will be called tsl2591. + +config TSL2772 + tristate "TAOS TSL/TMD2x71 and TSL/TMD2x72 Family of light and proximity sensors" + depends on I2C + help + Support for: tsl2571, tsl2671, tmd2671, tsl2771, tmd2771, tsl2572, tsl2672, + tmd2672, tsl2772, tmd2772 devices. + Provides iio_events and direct access via sysfs. + +config TSL4531 + tristate "TAOS TSL4531 ambient light sensors" + depends on I2C + help + Say Y here if you want to build a driver for the TAOS TSL4531 family + of ambient light sensors with direct lux output. + + To compile this driver as a module, choose M here: the + module will be called tsl4531. + +config US5182D + tristate "UPISEMI light and proximity sensor" + depends on I2C + help + If you say yes here you get support for the UPISEMI US5182D + ambient light and proximity sensor. + + This driver can also be built as a module. If so, the module + will be called us5182d. + +config VCNL4000 + tristate "VCNL4000/4010/4020/4200 combined ALS and proximity sensor" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + depends on I2C + help + Say Y here if you want to build a driver for the Vishay VCNL4000, + VCNL4010, VCNL4020, VCNL4200 combined ambient light and proximity + sensor. + + To compile this driver as a module, choose M here: the + module will be called vcnl4000. + +config VCNL4035 + tristate "VCNL4035 combined ALS and proximity sensor" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select REGMAP_I2C + depends on I2C + help + Say Y here if you want to build a driver for the Vishay VCNL4035, + combined ambient light (ALS) and proximity sensor. Currently only ALS + function is available. + + To compile this driver as a module, choose M here: the + module will be called vcnl4035. + +config VEML3235 + tristate "VEML3235 ambient light sensor" + select REGMAP_I2C + select IIO_GTS_HELPER + depends on I2C + help + Say Y here if you want to build a driver for the Vishay VEML3235 + ambient light sensor. + + To compile this driver as a module, choose M here: the + module will be called veml3235. + +config VEML3328 + tristate "VEML3328 RGBCIR light sensor" + select REGMAP_I2C + depends on I2C + help + Say Y here if you want to build a driver for the Vishay VEML3328 + RGB IR light sensor. + + To compile this driver as a module, choose M here: the + module will be called veml3328 + +config VEML6030 + tristate "VEML6030 and VEML6035 ambient light sensors" + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select IIO_GTS_HELPER + depends on I2C + help + Say Y here if you want to build a driver for the Vishay VEML6030 + and VEML6035 ambient light sensors (ALS). + + To compile this driver as a module, choose M here: the + module will be called veml6030. + +config VEML6040 + tristate "VEML6040 RGBW light sensor" + select REGMAP_I2C + depends on I2C + help + Say Y here if you want to build a driver for the Vishay VEML6040 + RGBW light sensor. + + To compile this driver as a module, choose M here: the + module will be called veml6040. + +config VEML6046X00 + tristate "VEML6046X00 RGBIR color sensor" + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + depends on I2C + help + Say Y here if you want to build a driver for the Vishay VEML6046X00 + high accuracy RGBIR color sensor. + + To compile this driver as a module, choose M here: the + module will be called veml6046x00. + +config VEML6070 + tristate "VEML6070 UV A light sensor" + depends on I2C + help + Say Y here if you want to build a driver for the Vishay VEML6070 UV A + light sensor. + + To compile this driver as a module, choose M here: the + module will be called veml6070. + +config VEML6075 + tristate "VEML6075 UVA and UVB light sensor" + select REGMAP_I2C + depends on I2C + help + Say Y here if you want to build a driver for the Vishay VEML6075 UVA + and UVB light sensor. + + To compile this driver as a module, choose M here: the + module will be called veml6075. + +config VL6180 + tristate "VL6180 ALS, range and proximity sensor" + depends on I2C + help + Say Y here if you want to build a driver for the STMicroelectronics + VL6180 combined ambient light, range and proximity sensor. + + To compile this driver as a module, choose M here: the + module will be called vl6180. + +config ZOPT2201 + tristate "ZOPT2201 ALS and UV B sensor" + depends on I2C + help + Say Y here if you want to build a driver for the IDT + ZOPT2201 ambient light and UV B sensor. + + To compile this driver as a module, choose M here: the + module will be called zopt2201. + +endmenu diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile new file mode 100644 index 000000000..64e354c49 --- /dev/null +++ b/drivers/iio/light/Makefile @@ -0,0 +1,76 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for IIO Light sensors +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_ACPI_ALS) += acpi-als.o +obj-$(CONFIG_ADJD_S311) += adjd_s311.o +obj-$(CONFIG_ADUX1020) += adux1020.o +obj-$(CONFIG_AL3000A) += al3000a.o +obj-$(CONFIG_AL3010) += al3010.o +obj-$(CONFIG_AL3320A) += al3320a.o +obj-$(CONFIG_APDS9160) += apds9160.o +obj-$(CONFIG_APDS9300) += apds9300.o +obj-$(CONFIG_APDS9306) += apds9306.o +obj-$(CONFIG_APDS9960) += apds9960.o +obj-$(CONFIG_APDS9999) += apds9999.o +obj-$(CONFIG_AS73211) += as73211.o +obj-$(CONFIG_BH1745) += bh1745.o +obj-$(CONFIG_BH1750) += bh1750.o +obj-$(CONFIG_BH1780) += bh1780.o +obj-$(CONFIG_CM32181) += cm32181.o +obj-$(CONFIG_CM3232) += cm3232.o +obj-$(CONFIG_CM3323) += cm3323.o +obj-$(CONFIG_CM3605) += cm3605.o +obj-$(CONFIG_CM36651) += cm36651.o +obj-$(CONFIG_IIO_CROS_EC_LIGHT_PROX) += cros_ec_light_prox.o +obj-$(CONFIG_GP2AP002) += gp2ap002.o +obj-$(CONFIG_GP2AP020A00F) += gp2ap020a00f.o +obj-$(CONFIG_HID_SENSOR_ALS) += hid-sensor-als.o +obj-$(CONFIG_HID_SENSOR_PROX) += hid-sensor-prox.o +obj-$(CONFIG_IQS621_ALS) += iqs621-als.o +obj-$(CONFIG_SENSORS_ISL29018) += isl29018.o +obj-$(CONFIG_SENSORS_ISL29028) += isl29028.o +obj-$(CONFIG_ISL29125) += isl29125.o +obj-$(CONFIG_ISL76682) += isl76682.o +obj-$(CONFIG_JSA1212) += jsa1212.o +obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o +obj-$(CONFIG_LTR390) += ltr390.o +obj-$(CONFIG_LTR501) += ltr501.o +obj-$(CONFIG_LTRF216A) += ltrf216a.o +obj-$(CONFIG_LV0104CS) += lv0104cs.o +obj-$(CONFIG_MAX44000) += max44000.o +obj-$(CONFIG_MAX44009) += max44009.o +obj-$(CONFIG_NOA1305) += noa1305.o +obj-$(CONFIG_OPT3001) += opt3001.o +obj-$(CONFIG_OPT4001) += opt4001.o +obj-$(CONFIG_OPT4060) += opt4060.o +obj-$(CONFIG_PA12203001) += pa12203001.o +obj-$(CONFIG_ROHM_BU27034) += rohm-bu27034.o +obj-$(CONFIG_RPR0521) += rpr0521.o +obj-$(CONFIG_SI1133) += si1133.o +obj-$(CONFIG_SI1145) += si1145.o +obj-$(CONFIG_STK3310) += stk3310.o +obj-$(CONFIG_ST_UVIS25) += st_uvis25_core.o +obj-$(CONFIG_ST_UVIS25_I2C) += st_uvis25_i2c.o +obj-$(CONFIG_ST_UVIS25_SPI) += st_uvis25_spi.o +obj-$(CONFIG_TCS3414) += tcs3414.o +obj-$(CONFIG_TCS3472) += tcs3472.o +obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o +obj-$(CONFIG_TSL2583) += tsl2583.o +obj-$(CONFIG_TSL2591) += tsl2591.o +obj-$(CONFIG_TSL2772) += tsl2772.o +obj-$(CONFIG_TSL4531) += tsl4531.o +obj-$(CONFIG_US5182D) += us5182d.o +obj-$(CONFIG_VCNL4000) += vcnl4000.o +obj-$(CONFIG_VCNL4035) += vcnl4035.o +obj-$(CONFIG_VEML3235) += veml3235.o +obj-$(CONFIG_VEML3328) += veml3328.o +obj-$(CONFIG_VEML6030) += veml6030.o +obj-$(CONFIG_VEML6040) += veml6040.o +obj-$(CONFIG_VEML6046X00) += veml6046x00.o +obj-$(CONFIG_VEML6070) += veml6070.o +obj-$(CONFIG_VEML6075) += veml6075.o +obj-$(CONFIG_VL6180) += vl6180.o +obj-$(CONFIG_ZOPT2201) += zopt2201.o diff --git a/drivers/iio/light/acpi-als.c b/drivers/iio/light/acpi-als.c new file mode 100644 index 000000000..1983a7f17 --- /dev/null +++ b/drivers/iio/light/acpi-als.c @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ACPI Ambient Light Sensor Driver + * + * Based on ALS driver: + * Copyright (C) 2009 Zhang Rui + * + * Rework for IIO subsystem: + * Copyright (C) 2012-2013 Martin Liska + * + * Final cleanup and debugging: + * Copyright (C) 2013-2014 Marek Vasut + * Copyright (C) 2015 Gabriele Mazzotta + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define ACPI_ALS_CLASS "als" +#define ACPI_ALS_DEVICE_NAME "acpi-als" +#define ACPI_ALS_NOTIFY_ILLUMINANCE 0x80 + +/* + * So far, there's only one channel in here, but the specification for + * ACPI0008 says there can be more to what the block can report. Like + * chromaticity and such. We are ready for incoming additions! + */ +static const struct iio_chan_spec acpi_als_channels[] = { + { + .type = IIO_LIGHT, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + }, + /* _RAW is here for backward ABI compatibility */ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_PROCESSED), + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +struct acpi_als { + struct acpi_device *device; + struct mutex lock; + struct iio_trigger *trig; +}; + +/* + * All types of properties the ACPI0008 block can report. The ALI, ALC, ALT + * and ALP can all be handled by acpi_als_read_value() below, while the ALR is + * special. + * + * The _ALR property returns tables that can be used to fine-tune the values + * reported by the other props based on the particular hardware type and it's + * location (it contains tables for "rainy", "bright inhouse lighting" etc.). + * + * So far, we support only ALI (illuminance). + */ +#define ACPI_ALS_ILLUMINANCE "_ALI" +#define ACPI_ALS_CHROMATICITY "_ALC" +#define ACPI_ALS_COLOR_TEMP "_ALT" +#define ACPI_ALS_POLLING "_ALP" +#define ACPI_ALS_TABLES "_ALR" + +static int acpi_als_read_value(struct acpi_als *als, char *prop, s32 *val) +{ + unsigned long long temp_val; + acpi_status status; + + status = acpi_evaluate_integer(als->device->handle, prop, NULL, + &temp_val); + + if (ACPI_FAILURE(status)) { + acpi_evaluation_failure_warn(als->device->handle, prop, status); + return -EIO; + } + + *val = temp_val; + + return 0; +} + +static void acpi_als_notify(acpi_handle handle, u32 event, void *data) +{ + struct iio_dev *indio_dev = data; + struct acpi_als *als = iio_priv(indio_dev); + + if (iio_buffer_enabled(indio_dev) && iio_trigger_using_own(indio_dev)) { + switch (event) { + case ACPI_ALS_NOTIFY_ILLUMINANCE: + iio_trigger_poll_nested(als->trig); + break; + default: + /* Unhandled event */ + dev_dbg(&als->device->dev, + "Unhandled ACPI ALS event (%08x)!\n", + event); + } + } +} + +static int acpi_als_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct acpi_als *als = iio_priv(indio_dev); + s32 temp_val; + int ret; + + if ((mask != IIO_CHAN_INFO_PROCESSED) && (mask != IIO_CHAN_INFO_RAW)) + return -EINVAL; + + /* we support only illumination (_ALI) so far. */ + if (chan->type != IIO_LIGHT) + return -EINVAL; + + ret = acpi_als_read_value(als, ACPI_ALS_ILLUMINANCE, &temp_val); + if (ret < 0) + return ret; + + *val = temp_val; + + return IIO_VAL_INT; +} + +static const struct iio_info acpi_als_info = { + .read_raw = acpi_als_read_raw, +}; + +static irqreturn_t acpi_als_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct acpi_als *als = iio_priv(indio_dev); + struct { + s32 light; + aligned_s64 ts; + } scan = { }; + s32 val; + int ret; + + mutex_lock(&als->lock); + + ret = acpi_als_read_value(als, ACPI_ALS_ILLUMINANCE, &val); + if (ret < 0) + goto out; + scan.light = val; + + /* + * When coming from own trigger via polls, set polling function + * timestamp here. Given ACPI notifier is already in a thread and call + * function directly, there is no need to set the timestamp in the + * notify function. + * + * If the timestamp was actually 0, the timestamp is set one more time. + */ + if (!pf->timestamp) + pf->timestamp = iio_get_time_ns(indio_dev); + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), pf->timestamp); +out: + mutex_unlock(&als->lock); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int acpi_als_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct acpi_device *device; + struct iio_dev *indio_dev; + struct acpi_als *als; + int ret; + + device = ACPI_COMPANION(dev); + if (!device) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*als)); + if (!indio_dev) + return -ENOMEM; + + als = iio_priv(indio_dev); + + als->device = device; + mutex_init(&als->lock); + + indio_dev->name = ACPI_ALS_DEVICE_NAME; + indio_dev->info = &acpi_als_info; + indio_dev->channels = acpi_als_channels; + indio_dev->num_channels = ARRAY_SIZE(acpi_als_channels); + + als->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, + iio_device_id(indio_dev)); + if (!als->trig) + return -ENOMEM; + + ret = devm_iio_trigger_register(dev, als->trig); + if (ret) + return ret; + /* + * Set hardware trigger by default to let events flow when + * BIOS support notification. + */ + indio_dev->trig = iio_trigger_get(als->trig); + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + acpi_als_trigger_handler, + NULL); + if (ret) + return ret; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ret; + + return acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY, + acpi_als_notify, indio_dev); +} + +static void acpi_als_remove(struct platform_device *pdev) +{ + acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev), + ACPI_DEVICE_NOTIFY, acpi_als_notify); +} + +static const struct acpi_device_id acpi_als_device_ids[] = { + {"ACPI0008", 0}, + { } +}; + +MODULE_DEVICE_TABLE(acpi, acpi_als_device_ids); + +static struct platform_driver acpi_als_driver = { + .probe = acpi_als_probe, + .remove = acpi_als_remove, + .driver = { + .name = "acpi_als", + .acpi_match_table = acpi_als_device_ids, + }, +}; +module_platform_driver(acpi_als_driver); + +MODULE_AUTHOR("Zhang Rui "); +MODULE_AUTHOR("Martin Liska "); +MODULE_AUTHOR("Marek Vasut "); +MODULE_DESCRIPTION("ACPI Ambient Light Sensor Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/adjd_s311.c b/drivers/iio/light/adjd_s311.c new file mode 100644 index 000000000..088b9431d --- /dev/null +++ b/drivers/iio/light/adjd_s311.c @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * adjd_s311.c - Support for ADJD-S311-CR999 digital color sensor + * + * Copyright (C) 2012 Peter Meerwald + * + * driver for ADJD-S311-CR999 digital color sensor (10-bit channels for + * red, green, blue, clear); 7-bit I2C slave address 0x74 + * + * limitations: no calibration, no offset mode, no sleep mode + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define ADJD_S311_DRV_NAME "adjd_s311" + +#define ADJD_S311_CTRL 0x00 +#define ADJD_S311_CONFIG 0x01 +#define ADJD_S311_CAP_RED 0x06 +#define ADJD_S311_CAP_GREEN 0x07 +#define ADJD_S311_CAP_BLUE 0x08 +#define ADJD_S311_CAP_CLEAR 0x09 +#define ADJD_S311_INT_RED 0x0a +#define ADJD_S311_INT_GREEN 0x0c +#define ADJD_S311_INT_BLUE 0x0e +#define ADJD_S311_INT_CLEAR 0x10 +#define ADJD_S311_DATA_RED 0x40 +#define ADJD_S311_DATA_GREEN 0x42 +#define ADJD_S311_DATA_BLUE 0x44 +#define ADJD_S311_DATA_CLEAR 0x46 +#define ADJD_S311_OFFSET_RED 0x48 +#define ADJD_S311_OFFSET_GREEN 0x49 +#define ADJD_S311_OFFSET_BLUE 0x4a +#define ADJD_S311_OFFSET_CLEAR 0x4b + +#define ADJD_S311_CTRL_GOFS 0x02 +#define ADJD_S311_CTRL_GSSR 0x01 +#define ADJD_S311_CAP_MASK 0x0f +#define ADJD_S311_INT_MASK 0x0fff +#define ADJD_S311_DATA_MASK 0x03ff + +struct adjd_s311_data { + struct i2c_client *client; +}; + +enum adjd_s311_channel_idx { + IDX_RED, IDX_GREEN, IDX_BLUE, IDX_CLEAR +}; + +#define ADJD_S311_DATA_REG(chan) (ADJD_S311_DATA_RED + (chan) * 2) +#define ADJD_S311_INT_REG(chan) (ADJD_S311_INT_RED + (chan) * 2) +#define ADJD_S311_CAP_REG(chan) (ADJD_S311_CAP_RED + (chan)) + +static int adjd_s311_req_data(struct iio_dev *indio_dev) +{ + struct adjd_s311_data *data = iio_priv(indio_dev); + int tries = 10; + + int ret = i2c_smbus_write_byte_data(data->client, ADJD_S311_CTRL, + ADJD_S311_CTRL_GSSR); + if (ret < 0) + return ret; + + while (tries--) { + ret = i2c_smbus_read_byte_data(data->client, ADJD_S311_CTRL); + if (ret < 0) + return ret; + if (!(ret & ADJD_S311_CTRL_GSSR)) + break; + msleep(20); + } + + if (tries < 0) { + dev_err(&data->client->dev, + "adjd_s311_req_data() failed, data not ready\n"); + return -EIO; + } + + return 0; +} + +static int adjd_s311_read_data(struct iio_dev *indio_dev, u8 reg, int *val) +{ + struct adjd_s311_data *data = iio_priv(indio_dev); + + int ret = adjd_s311_req_data(indio_dev); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_word_data(data->client, reg); + if (ret < 0) + return ret; + + *val = ret & ADJD_S311_DATA_MASK; + + return 0; +} + +static irqreturn_t adjd_s311_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adjd_s311_data *data = iio_priv(indio_dev); + s64 time_ns = iio_get_time_ns(indio_dev); + int i, j = 0; + struct { + s16 chans[4]; + aligned_s64 ts; + } scan = { }; + + int ret = adjd_s311_req_data(indio_dev); + if (ret < 0) + goto done; + + iio_for_each_active_channel(indio_dev, i) { + ret = i2c_smbus_read_word_data(data->client, + ADJD_S311_DATA_REG(i)); + if (ret < 0) + goto done; + + scan.chans[j++] = ret & ADJD_S311_DATA_MASK; + } + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), time_ns); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +#define ADJD_S311_CHANNEL(_color, _scan_idx) { \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .address = (IDX_##_color), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_HARDWAREGAIN) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .channel2 = (IIO_MOD_LIGHT_##_color), \ + .scan_index = (_scan_idx), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 10, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ +} + +static const struct iio_chan_spec adjd_s311_channels[] = { + ADJD_S311_CHANNEL(RED, 0), + ADJD_S311_CHANNEL(GREEN, 1), + ADJD_S311_CHANNEL(BLUE, 2), + ADJD_S311_CHANNEL(CLEAR, 3), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static int adjd_s311_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct adjd_s311_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = adjd_s311_read_data(indio_dev, + ADJD_S311_DATA_REG(chan->address), val); + if (ret < 0) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_HARDWAREGAIN: + ret = i2c_smbus_read_byte_data(data->client, + ADJD_S311_CAP_REG(chan->address)); + if (ret < 0) + return ret; + *val = ret & ADJD_S311_CAP_MASK; + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + ret = i2c_smbus_read_word_data(data->client, + ADJD_S311_INT_REG(chan->address)); + if (ret < 0) + return ret; + *val = 0; + /* + * not documented, based on measurement: + * 4095 LSBs correspond to roughly 4 ms + */ + *val2 = ret & ADJD_S311_INT_MASK; + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static int adjd_s311_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct adjd_s311_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + if (val < 0 || val > ADJD_S311_CAP_MASK) + return -EINVAL; + + return i2c_smbus_write_byte_data(data->client, + ADJD_S311_CAP_REG(chan->address), val); + case IIO_CHAN_INFO_INT_TIME: + if (val != 0 || val2 < 0 || val2 > ADJD_S311_INT_MASK) + return -EINVAL; + + return i2c_smbus_write_word_data(data->client, + ADJD_S311_INT_REG(chan->address), val2); + } + return -EINVAL; +} + +static const struct iio_info adjd_s311_info = { + .read_raw = adjd_s311_read_raw, + .write_raw = adjd_s311_write_raw, +}; + +static int adjd_s311_probe(struct i2c_client *client) +{ + struct adjd_s311_data *data; + struct iio_dev *indio_dev; + int err; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (indio_dev == NULL) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + + indio_dev->info = &adjd_s311_info; + indio_dev->name = ADJD_S311_DRV_NAME; + indio_dev->channels = adjd_s311_channels; + indio_dev->num_channels = ARRAY_SIZE(adjd_s311_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + err = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, NULL, + adjd_s311_trigger_handler, NULL); + if (err < 0) + return err; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id adjd_s311_id[] = { + { .name = "adjd_s311" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adjd_s311_id); + +static struct i2c_driver adjd_s311_driver = { + .driver = { + .name = ADJD_S311_DRV_NAME, + }, + .probe = adjd_s311_probe, + .id_table = adjd_s311_id, +}; +module_i2c_driver(adjd_s311_driver); + +MODULE_AUTHOR("Peter Meerwald "); +MODULE_DESCRIPTION("ADJD-S311 color sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/adux1020.c b/drivers/iio/light/adux1020.c new file mode 100644 index 000000000..0ea282ecc --- /dev/null +++ b/drivers/iio/light/adux1020.c @@ -0,0 +1,842 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * adux1020.c - Support for Analog Devices ADUX1020 photometric sensor + * + * Copyright (C) 2019 Linaro Ltd. + * Author: Manivannan Sadhasivam + * + * TODO: Triggered buffer support + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define ADUX1020_DRV_NAME "adux1020" + +/* System registers */ +#define ADUX1020_REG_CHIP_ID 0x08 +#define ADUX1020_REG_SLAVE_ADDRESS 0x09 + +#define ADUX1020_REG_SW_RESET 0x0f +#define ADUX1020_REG_INT_ENABLE 0x1c +#define ADUX1020_REG_INT_POLARITY 0x1d +#define ADUX1020_REG_PROX_TH_ON1 0x2a +#define ADUX1020_REG_PROX_TH_OFF1 0x2b +#define ADUX1020_REG_PROX_TYPE 0x2f +#define ADUX1020_REG_TEST_MODES_3 0x32 +#define ADUX1020_REG_FORCE_MODE 0x33 +#define ADUX1020_REG_FREQUENCY 0x40 +#define ADUX1020_REG_LED_CURRENT 0x41 +#define ADUX1020_REG_OP_MODE 0x45 +#define ADUX1020_REG_INT_MASK 0x48 +#define ADUX1020_REG_INT_STATUS 0x49 +#define ADUX1020_REG_DATA_BUFFER 0x60 + +/* Chip ID bits */ +#define ADUX1020_CHIP_ID_MASK GENMASK(11, 0) +#define ADUX1020_CHIP_ID 0x03fc + +#define ADUX1020_SW_RESET BIT(1) +#define ADUX1020_FIFO_FLUSH BIT(15) +#define ADUX1020_OP_MODE_MASK GENMASK(3, 0) +#define ADUX1020_DATA_OUT_MODE_MASK GENMASK(7, 4) +#define ADUX1020_DATA_OUT_PROX_I FIELD_PREP(ADUX1020_DATA_OUT_MODE_MASK, 1) + +#define ADUX1020_MODE_INT_MASK GENMASK(7, 0) +#define ADUX1020_INT_ENABLE 0x2094 +#define ADUX1020_INT_DISABLE 0x2090 +#define ADUX1020_PROX_INT_ENABLE 0x00f0 +#define ADUX1020_PROX_ON1_INT BIT(0) +#define ADUX1020_PROX_OFF1_INT BIT(1) +#define ADUX1020_FIFO_INT_ENABLE 0x7f +#define ADUX1020_MODE_INT_DISABLE 0xff +#define ADUX1020_MODE_INT_STATUS_MASK GENMASK(7, 0) +#define ADUX1020_FIFO_STATUS_MASK GENMASK(15, 8) +#define ADUX1020_INT_CLEAR 0xff +#define ADUX1020_PROX_TYPE BIT(15) + +#define ADUX1020_INT_PROX_ON1 BIT(0) +#define ADUX1020_INT_PROX_OFF1 BIT(1) + +#define ADUX1020_FORCE_CLOCK_ON 0x0f4f +#define ADUX1020_FORCE_CLOCK_RESET 0x0040 +#define ADUX1020_ACTIVE_4_STATE 0x0008 + +#define ADUX1020_PROX_FREQ_MASK GENMASK(7, 4) +#define ADUX1020_PROX_FREQ(x) FIELD_PREP(ADUX1020_PROX_FREQ_MASK, x) + +#define ADUX1020_LED_CURRENT_MASK GENMASK(3, 0) +#define ADUX1020_LED_PIREF_EN BIT(12) + +/* Operating modes */ +enum adux1020_op_modes { + ADUX1020_MODE_STANDBY, + ADUX1020_MODE_PROX_I, + ADUX1020_MODE_PROX_XY, + ADUX1020_MODE_GEST, + ADUX1020_MODE_SAMPLE, + ADUX1020_MODE_FORCE = 0x0e, + ADUX1020_MODE_IDLE = 0x0f, +}; + +struct adux1020_data { + struct i2c_client *client; + struct iio_dev *indio_dev; + struct mutex lock; + struct regmap *regmap; +}; + +struct adux1020_mode_data { + u8 bytes; + u8 buf_len; + u16 int_en; +}; + +static const struct adux1020_mode_data adux1020_modes[] = { + [ADUX1020_MODE_PROX_I] = { + .bytes = 2, + .buf_len = 1, + .int_en = ADUX1020_PROX_INT_ENABLE, + }, +}; + +static const struct regmap_config adux1020_regmap_config = { + .name = "adux1020_regmap", + .reg_bits = 8, + .val_bits = 16, + .max_register = 0x6F, +}; + +static const struct reg_sequence adux1020_def_conf[] = { + { 0x000c, 0x000f }, + { 0x0010, 0x1010 }, + { 0x0011, 0x004c }, + { 0x0012, 0x5f0c }, + { 0x0013, 0xada5 }, + { 0x0014, 0x0080 }, + { 0x0015, 0x0000 }, + { 0x0016, 0x0600 }, + { 0x0017, 0x0000 }, + { 0x0018, 0x2693 }, + { 0x0019, 0x0004 }, + { 0x001a, 0x4280 }, + { 0x001b, 0x0060 }, + { 0x001c, 0x2094 }, + { 0x001d, 0x0020 }, + { 0x001e, 0x0001 }, + { 0x001f, 0x0100 }, + { 0x0020, 0x0320 }, + { 0x0021, 0x0A13 }, + { 0x0022, 0x0320 }, + { 0x0023, 0x0113 }, + { 0x0024, 0x0000 }, + { 0x0025, 0x2412 }, + { 0x0026, 0x2412 }, + { 0x0027, 0x0022 }, + { 0x0028, 0x0000 }, + { 0x0029, 0x0300 }, + { 0x002a, 0x0700 }, + { 0x002b, 0x0600 }, + { 0x002c, 0x6000 }, + { 0x002d, 0x4000 }, + { 0x002e, 0x0000 }, + { 0x002f, 0x0000 }, + { 0x0030, 0x0000 }, + { 0x0031, 0x0000 }, + { 0x0032, 0x0040 }, + { 0x0033, 0x0008 }, + { 0x0034, 0xE400 }, + { 0x0038, 0x8080 }, + { 0x0039, 0x8080 }, + { 0x003a, 0x2000 }, + { 0x003b, 0x1f00 }, + { 0x003c, 0x2000 }, + { 0x003d, 0x2000 }, + { 0x003e, 0x0000 }, + { 0x0040, 0x8069 }, + { 0x0041, 0x1f2f }, + { 0x0042, 0x4000 }, + { 0x0043, 0x0000 }, + { 0x0044, 0x0008 }, + { 0x0046, 0x0000 }, + { 0x0048, 0x00ef }, + { 0x0049, 0x0000 }, + { 0x0045, 0x0000 }, +}; + +static const int adux1020_rates[][2] = { + { 0, 100000 }, + { 0, 200000 }, + { 0, 500000 }, + { 1, 0 }, + { 2, 0 }, + { 5, 0 }, + { 10, 0 }, + { 20, 0 }, + { 50, 0 }, + { 100, 0 }, + { 190, 0 }, + { 450, 0 }, + { 820, 0 }, + { 1400, 0 }, +}; + +static const int adux1020_led_currents[][2] = { + { 0, 25000 }, + { 0, 40000 }, + { 0, 55000 }, + { 0, 70000 }, + { 0, 85000 }, + { 0, 100000 }, + { 0, 115000 }, + { 0, 130000 }, + { 0, 145000 }, + { 0, 160000 }, + { 0, 175000 }, + { 0, 190000 }, + { 0, 205000 }, + { 0, 220000 }, + { 0, 235000 }, + { 0, 250000 }, +}; + +static int adux1020_flush_fifo(struct adux1020_data *data) +{ + int ret; + + /* Force Idle mode */ + ret = regmap_write(data->regmap, ADUX1020_REG_FORCE_MODE, + ADUX1020_ACTIVE_4_STATE); + if (ret < 0) + return ret; + + ret = regmap_update_bits(data->regmap, ADUX1020_REG_OP_MODE, + ADUX1020_OP_MODE_MASK, ADUX1020_MODE_FORCE); + if (ret < 0) + return ret; + + ret = regmap_update_bits(data->regmap, ADUX1020_REG_OP_MODE, + ADUX1020_OP_MODE_MASK, ADUX1020_MODE_IDLE); + if (ret < 0) + return ret; + + /* Flush FIFO */ + ret = regmap_write(data->regmap, ADUX1020_REG_TEST_MODES_3, + ADUX1020_FORCE_CLOCK_ON); + if (ret < 0) + return ret; + + ret = regmap_write(data->regmap, ADUX1020_REG_INT_STATUS, + ADUX1020_FIFO_FLUSH); + if (ret < 0) + return ret; + + return regmap_write(data->regmap, ADUX1020_REG_TEST_MODES_3, + ADUX1020_FORCE_CLOCK_RESET); +} + +static int adux1020_read_fifo(struct adux1020_data *data, u16 *buf, u8 buf_len) +{ + unsigned int regval; + int i, ret; + + /* Enable 32MHz clock */ + ret = regmap_write(data->regmap, ADUX1020_REG_TEST_MODES_3, + ADUX1020_FORCE_CLOCK_ON); + if (ret < 0) + return ret; + + for (i = 0; i < buf_len; i++) { + ret = regmap_read(data->regmap, ADUX1020_REG_DATA_BUFFER, + ®val); + if (ret < 0) + return ret; + + buf[i] = regval; + } + + /* Set 32MHz clock to be controlled by internal state machine */ + return regmap_write(data->regmap, ADUX1020_REG_TEST_MODES_3, + ADUX1020_FORCE_CLOCK_RESET); +} + +static int adux1020_set_mode(struct adux1020_data *data, + enum adux1020_op_modes mode) +{ + int ret; + + /* Switch to standby mode before changing the mode */ + ret = regmap_write(data->regmap, ADUX1020_REG_OP_MODE, + ADUX1020_MODE_STANDBY); + if (ret < 0) + return ret; + + /* Set data out and switch to the desired mode */ + switch (mode) { + case ADUX1020_MODE_PROX_I: + ret = regmap_update_bits(data->regmap, ADUX1020_REG_OP_MODE, + ADUX1020_DATA_OUT_MODE_MASK, + ADUX1020_DATA_OUT_PROX_I); + if (ret < 0) + return ret; + + ret = regmap_update_bits(data->regmap, ADUX1020_REG_OP_MODE, + ADUX1020_OP_MODE_MASK, + ADUX1020_MODE_PROX_I); + if (ret < 0) + return ret; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int adux1020_measure(struct adux1020_data *data, + enum adux1020_op_modes mode, + u16 *val) +{ + unsigned int status; + int ret, tries = 50; + + /* Disable INT pin as polling is going to be used */ + ret = regmap_write(data->regmap, ADUX1020_REG_INT_ENABLE, + ADUX1020_INT_DISABLE); + if (ret < 0) + return ret; + + /* Enable mode interrupt */ + ret = regmap_update_bits(data->regmap, ADUX1020_REG_INT_MASK, + ADUX1020_MODE_INT_MASK, + adux1020_modes[mode].int_en); + if (ret < 0) + return ret; + + while (tries--) { + ret = regmap_read(data->regmap, ADUX1020_REG_INT_STATUS, + &status); + if (ret < 0) + return ret; + + status &= ADUX1020_FIFO_STATUS_MASK; + if (status >= adux1020_modes[mode].bytes) + break; + msleep(20); + } + + if (tries < 0) + return -EIO; + + ret = adux1020_read_fifo(data, val, adux1020_modes[mode].buf_len); + if (ret < 0) + return ret; + + /* Clear mode interrupt */ + ret = regmap_write(data->regmap, ADUX1020_REG_INT_STATUS, + (~adux1020_modes[mode].int_en)); + if (ret < 0) + return ret; + + /* Disable mode interrupts */ + return regmap_update_bits(data->regmap, ADUX1020_REG_INT_MASK, + ADUX1020_MODE_INT_MASK, + ADUX1020_MODE_INT_DISABLE); +} + +static int adux1020_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct adux1020_data *data = iio_priv(indio_dev); + u16 buf[3]; + int ret = -EINVAL; + unsigned int regval; + + mutex_lock(&data->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_PROXIMITY: + ret = adux1020_set_mode(data, ADUX1020_MODE_PROX_I); + if (ret < 0) + goto fail; + + ret = adux1020_measure(data, ADUX1020_MODE_PROX_I, buf); + if (ret < 0) + goto fail; + + *val = buf[0]; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_CURRENT: + ret = regmap_read(data->regmap, + ADUX1020_REG_LED_CURRENT, ®val); + if (ret < 0) + goto fail; + + regval = regval & ADUX1020_LED_CURRENT_MASK; + + *val = adux1020_led_currents[regval][0]; + *val2 = adux1020_led_currents[regval][1]; + + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + break; + } + break; + case IIO_CHAN_INFO_SAMP_FREQ: + switch (chan->type) { + case IIO_PROXIMITY: + ret = regmap_read(data->regmap, ADUX1020_REG_FREQUENCY, + ®val); + if (ret < 0) + goto fail; + + regval = FIELD_GET(ADUX1020_PROX_FREQ_MASK, regval); + + *val = adux1020_rates[regval][0]; + *val2 = adux1020_rates[regval][1]; + + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + break; + } + break; + default: + break; + } + +fail: + mutex_unlock(&data->lock); + + return ret; +}; + +static inline int adux1020_find_index(const int array[][2], int count, int val, + int val2) +{ + int i; + + for (i = 0; i < count; i++) + if (val == array[i][0] && val2 == array[i][1]) + return i; + + return -EINVAL; +} + +static int adux1020_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct adux1020_data *data = iio_priv(indio_dev); + int i, ret = -EINVAL; + + mutex_lock(&data->lock); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (chan->type == IIO_PROXIMITY) { + i = adux1020_find_index(adux1020_rates, + ARRAY_SIZE(adux1020_rates), + val, val2); + if (i < 0) { + ret = i; + goto fail; + } + + ret = regmap_update_bits(data->regmap, + ADUX1020_REG_FREQUENCY, + ADUX1020_PROX_FREQ_MASK, + ADUX1020_PROX_FREQ(i)); + } + break; + case IIO_CHAN_INFO_PROCESSED: + if (chan->type == IIO_CURRENT) { + i = adux1020_find_index(adux1020_led_currents, + ARRAY_SIZE(adux1020_led_currents), + val, val2); + if (i < 0) { + ret = i; + goto fail; + } + + ret = regmap_update_bits(data->regmap, + ADUX1020_REG_LED_CURRENT, + ADUX1020_LED_CURRENT_MASK, i); + } + break; + default: + break; + } + +fail: + mutex_unlock(&data->lock); + + return ret; +} + +static int adux1020_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct adux1020_data *data = iio_priv(indio_dev); + int ret, mask; + + mutex_lock(&data->lock); + + ret = regmap_write(data->regmap, ADUX1020_REG_INT_ENABLE, + ADUX1020_INT_ENABLE); + if (ret < 0) + goto fail; + + ret = regmap_write(data->regmap, ADUX1020_REG_INT_POLARITY, 0); + if (ret < 0) + goto fail; + + switch (chan->type) { + case IIO_PROXIMITY: + if (dir == IIO_EV_DIR_RISING) + mask = ADUX1020_PROX_ON1_INT; + else + mask = ADUX1020_PROX_OFF1_INT; + + if (state) + ret = regmap_clear_bits(data->regmap, + ADUX1020_REG_INT_MASK, mask); + else + ret = regmap_set_bits(data->regmap, + ADUX1020_REG_INT_MASK, mask); + if (ret < 0) + goto fail; + + /* + * Trigger proximity interrupt when the intensity is above + * or below threshold + */ + ret = regmap_set_bits(data->regmap, ADUX1020_REG_PROX_TYPE, + ADUX1020_PROX_TYPE); + if (ret < 0) + goto fail; + + /* Set proximity mode */ + ret = adux1020_set_mode(data, ADUX1020_MODE_PROX_I); + break; + default: + ret = -EINVAL; + break; + } + +fail: + mutex_unlock(&data->lock); + + return ret; +} + +static int adux1020_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct adux1020_data *data = iio_priv(indio_dev); + int ret, mask; + unsigned int regval; + + switch (chan->type) { + case IIO_PROXIMITY: + if (dir == IIO_EV_DIR_RISING) + mask = ADUX1020_PROX_ON1_INT; + else + mask = ADUX1020_PROX_OFF1_INT; + break; + default: + return -EINVAL; + } + + ret = regmap_read(data->regmap, ADUX1020_REG_INT_MASK, ®val); + if (ret < 0) + return ret; + + return !(regval & mask); +} + +static int adux1020_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, int *val2) +{ + struct adux1020_data *data = iio_priv(indio_dev); + u8 reg; + int ret; + unsigned int regval; + + switch (chan->type) { + case IIO_PROXIMITY: + if (dir == IIO_EV_DIR_RISING) + reg = ADUX1020_REG_PROX_TH_ON1; + else + reg = ADUX1020_REG_PROX_TH_OFF1; + break; + default: + return -EINVAL; + } + + ret = regmap_read(data->regmap, reg, ®val); + if (ret < 0) + return ret; + + *val = regval; + + return IIO_VAL_INT; +} + +static int adux1020_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, int val2) +{ + struct adux1020_data *data = iio_priv(indio_dev); + u8 reg; + + switch (chan->type) { + case IIO_PROXIMITY: + if (dir == IIO_EV_DIR_RISING) + reg = ADUX1020_REG_PROX_TH_ON1; + else + reg = ADUX1020_REG_PROX_TH_OFF1; + break; + default: + return -EINVAL; + } + + /* Full scale threshold value is 0-65535 */ + if (val < 0 || val > 65535) + return -EINVAL; + + return regmap_write(data->regmap, reg, val); +} + +static const struct iio_event_spec adux1020_proximity_event[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec adux1020_channels[] = { + { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .event_spec = adux1020_proximity_event, + .num_event_specs = ARRAY_SIZE(adux1020_proximity_event), + }, + { + .type = IIO_CURRENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .extend_name = "led", + .output = 1, + }, +}; + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL( + "0.1 0.2 0.5 1 2 5 10 20 50 100 190 450 820 1400"); + +static struct attribute *adux1020_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group adux1020_attribute_group = { + .attrs = adux1020_attributes, +}; + +static const struct iio_info adux1020_info = { + .attrs = &adux1020_attribute_group, + .read_raw = adux1020_read_raw, + .write_raw = adux1020_write_raw, + .read_event_config = adux1020_read_event_config, + .write_event_config = adux1020_write_event_config, + .read_event_value = adux1020_read_thresh, + .write_event_value = adux1020_write_thresh, +}; + +static irqreturn_t adux1020_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct adux1020_data *data = iio_priv(indio_dev); + int ret, status; + + ret = regmap_read(data->regmap, ADUX1020_REG_INT_STATUS, &status); + if (ret < 0) + return IRQ_HANDLED; + + status &= ADUX1020_MODE_INT_STATUS_MASK; + + if (status & ADUX1020_INT_PROX_ON1) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + } + + if (status & ADUX1020_INT_PROX_OFF1) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns(indio_dev)); + } + + regmap_update_bits(data->regmap, ADUX1020_REG_INT_STATUS, + ADUX1020_MODE_INT_MASK, ADUX1020_INT_CLEAR); + + return IRQ_HANDLED; +} + +static int adux1020_chip_init(struct adux1020_data *data) +{ + struct i2c_client *client = data->client; + int ret; + unsigned int val; + + ret = regmap_read(data->regmap, ADUX1020_REG_CHIP_ID, &val); + if (ret < 0) + return ret; + + if ((val & ADUX1020_CHIP_ID_MASK) != ADUX1020_CHIP_ID) { + dev_err(&client->dev, "invalid chip id 0x%04x\n", val); + return -ENODEV; + } + + dev_dbg(&client->dev, "Detected ADUX1020 with chip id: 0x%04x\n", val); + + ret = regmap_set_bits(data->regmap, ADUX1020_REG_SW_RESET, + ADUX1020_SW_RESET); + if (ret < 0) + return ret; + + /* Load default configuration */ + ret = regmap_multi_reg_write(data->regmap, adux1020_def_conf, + ARRAY_SIZE(adux1020_def_conf)); + if (ret < 0) + return ret; + + ret = adux1020_flush_fifo(data); + if (ret < 0) + return ret; + + /* Use LED_IREF for proximity mode */ + ret = regmap_clear_bits(data->regmap, ADUX1020_REG_LED_CURRENT, + ADUX1020_LED_PIREF_EN); + if (ret < 0) + return ret; + + /* Mask all interrupts */ + return regmap_update_bits(data->regmap, ADUX1020_REG_INT_MASK, + ADUX1020_MODE_INT_MASK, ADUX1020_MODE_INT_DISABLE); +} + +static int adux1020_probe(struct i2c_client *client) +{ + struct adux1020_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + indio_dev->info = &adux1020_info; + indio_dev->name = ADUX1020_DRV_NAME; + indio_dev->channels = adux1020_channels; + indio_dev->num_channels = ARRAY_SIZE(adux1020_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + data = iio_priv(indio_dev); + + data->regmap = devm_regmap_init_i2c(client, &adux1020_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(&client->dev, "regmap initialization failed.\n"); + return PTR_ERR(data->regmap); + } + + data->client = client; + data->indio_dev = indio_dev; + mutex_init(&data->lock); + + ret = adux1020_chip_init(data); + if (ret) + return ret; + + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, adux1020_interrupt_handler, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + ADUX1020_DRV_NAME, indio_dev); + if (ret) + return ret; + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id adux1020_id[] = { + { .name = "adux1020" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adux1020_id); + +static const struct of_device_id adux1020_of_match[] = { + { .compatible = "adi,adux1020" }, + { } +}; +MODULE_DEVICE_TABLE(of, adux1020_of_match); + +static struct i2c_driver adux1020_driver = { + .driver = { + .name = ADUX1020_DRV_NAME, + .of_match_table = adux1020_of_match, + }, + .probe = adux1020_probe, + .id_table = adux1020_id, +}; +module_i2c_driver(adux1020_driver); + +MODULE_AUTHOR("Manivannan Sadhasivam "); +MODULE_DESCRIPTION("ADUX1020 photometric sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/al3000a.c b/drivers/iio/light/al3000a.c new file mode 100644 index 000000000..957ec4233 --- /dev/null +++ b/drivers/iio/light/al3000a.c @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define AL3000A_REG_SYSTEM 0x00 +#define AL3000A_REG_DATA 0x05 + +#define AL3000A_CONFIG_ENABLE 0x00 +#define AL3000A_CONFIG_DISABLE 0x0b +#define AL3000A_CONFIG_RESET 0x0f +#define AL3000A_GAIN_MASK GENMASK(5, 0) + +/* + * These are pre-calculated lux values based on possible output of sensor + * (range 0x00 - 0x3F) + */ +static const u32 lux_table[] = { + 1, 1, 1, 2, 2, 2, 3, 4, /* 0 - 7 */ + 4, 5, 6, 7, 9, 11, 13, 16, /* 8 - 15 */ + 19, 22, 27, 32, 39, 46, 56, 67, /* 16 - 23 */ + 80, 96, 116, 139, 167, 200, 240, 289, /* 24 - 31 */ + 347, 416, 499, 600, 720, 864, 1037, 1245, /* 32 - 39 */ + 1495, 1795, 2155, 2587, 3105, 3728, 4475, 5373, /* 40 - 47 */ + 6450, 7743, 9296, 11160, 13397, 16084, 19309, 23180, /* 48 - 55 */ + 27828, 33408, 40107, 48148, 57803, 69393, 83306, 100000 /* 56 - 63 */ +}; + +static const struct regmap_config al3000a_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = AL3000A_REG_DATA, +}; + +struct al3000a_data { + struct regmap *regmap; + struct regulator *vdd_supply; +}; + +static const struct iio_chan_spec al3000a_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, +}; + +static int al3000a_set_pwr_on(struct al3000a_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = regulator_enable(data->vdd_supply); + if (ret) { + dev_err(dev, "failed to enable vdd power supply\n"); + return ret; + } + + return regmap_write(data->regmap, AL3000A_REG_SYSTEM, AL3000A_CONFIG_ENABLE); +} + +static void al3000a_set_pwr_off(void *_data) +{ + struct al3000a_data *data = _data; + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = regmap_write(data->regmap, AL3000A_REG_SYSTEM, AL3000A_CONFIG_DISABLE); + if (ret) + dev_err(dev, "failed to write system register\n"); + + ret = regulator_disable(data->vdd_supply); + if (ret) + dev_err(dev, "failed to disable vdd power supply\n"); +} + +static int al3000a_init(struct al3000a_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = al3000a_set_pwr_on(data); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, al3000a_set_pwr_off, data); + if (ret) + return ret; + + ret = regmap_write(data->regmap, AL3000A_REG_SYSTEM, AL3000A_CONFIG_RESET); + if (ret) + return ret; + + return regmap_write(data->regmap, AL3000A_REG_SYSTEM, AL3000A_CONFIG_ENABLE); +} + +static int al3000a_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct al3000a_data *data = iio_priv(indio_dev); + int ret, gain; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ret = regmap_read(data->regmap, AL3000A_REG_DATA, &gain); + if (ret) + return ret; + + *val = lux_table[gain & AL3000A_GAIN_MASK]; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static const struct iio_info al3000a_info = { + .read_raw = al3000a_read_raw, +}; + +static int al3000a_probe(struct i2c_client *client) +{ + struct al3000a_data *data; + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + + data->regmap = devm_regmap_init_i2c(client, &al3000a_regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "cannot allocate regmap\n"); + + data->vdd_supply = devm_regulator_get(dev, "vdd"); + if (IS_ERR(data->vdd_supply)) + return dev_err_probe(dev, PTR_ERR(data->vdd_supply), + "failed to get vdd regulator\n"); + + indio_dev->info = &al3000a_info; + indio_dev->name = "al3000a"; + indio_dev->channels = al3000a_channels; + indio_dev->num_channels = ARRAY_SIZE(al3000a_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = al3000a_init(data); + if (ret) + return dev_err_probe(dev, ret, "failed to init ALS\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static int al3000a_suspend(struct device *dev) +{ + struct al3000a_data *data = iio_priv(dev_get_drvdata(dev)); + + al3000a_set_pwr_off(data); + return 0; +} + +static int al3000a_resume(struct device *dev) +{ + struct al3000a_data *data = iio_priv(dev_get_drvdata(dev)); + + return al3000a_set_pwr_on(data); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(al3000a_pm_ops, al3000a_suspend, al3000a_resume); + +static const struct i2c_device_id al3000a_id[] = { + { .name = "al3000a" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, al3000a_id); + +static const struct of_device_id al3000a_of_match[] = { + { .compatible = "dynaimage,al3000a" }, + { } +}; +MODULE_DEVICE_TABLE(of, al3000a_of_match); + +static struct i2c_driver al3000a_driver = { + .driver = { + .name = "al3000a", + .of_match_table = al3000a_of_match, + .pm = pm_sleep_ptr(&al3000a_pm_ops), + }, + .probe = al3000a_probe, + .id_table = al3000a_id, +}; +module_i2c_driver(al3000a_driver); + +MODULE_AUTHOR("Svyatolsav Ryhel "); +MODULE_DESCRIPTION("al3000a Ambient Light Sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/al3010.c b/drivers/iio/light/al3010.c new file mode 100644 index 000000000..49f6d8336 --- /dev/null +++ b/drivers/iio/light/al3010.c @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AL3010 - Dyna Image Ambient Light Sensor + * + * Copyright (c) 2014, Intel Corporation. + * Copyright (c) 2016, Dyna-Image Corp. + * Copyright (c) 2020, David Heidelberg, Michał Mirosław, Dmitry Osipenko + * + * IIO driver for AL3010 (7-bit I2C slave address 0x1C). + * + * TODO: interrupt support, thresholds + * When the driver will get support for interrupt handling, then interrupt + * will need to be disabled before turning sensor OFF in order to avoid + * potential races with the interrupt handling. + */ + +#include +#include +#include +#include + +#include +#include + +#define AL3010_REG_SYSTEM 0x00 +#define AL3010_REG_DATA_LOW 0x0c +#define AL3010_REG_CONFIG 0x10 + +#define AL3010_CONFIG_DISABLE 0x00 +#define AL3010_CONFIG_ENABLE 0x01 + +#define AL3010_GAIN_MASK GENMASK(6,4) + +#define AL3010_SCALE_AVAILABLE "1.1872 0.2968 0.0742 0.018" + +enum al3xxxx_range { + AL3XXX_RANGE_1, /* 77806 lx */ + AL3XXX_RANGE_2, /* 19542 lx */ + AL3XXX_RANGE_3, /* 4863 lx */ + AL3XXX_RANGE_4 /* 1216 lx */ +}; + +static const int al3010_scales[][2] = { + { 1, 187200 }, { 0, 296800 }, { 0, 74200 }, { 0, 18600 }, +}; + +static const struct regmap_config al3010_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = AL3010_REG_CONFIG, +}; + +struct al3010_data { + struct regmap *regmap; +}; + +static const struct iio_chan_spec al3010_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + } +}; + +static IIO_CONST_ATTR(in_illuminance_scale_available, AL3010_SCALE_AVAILABLE); + +static struct attribute *al3010_attributes[] = { + &iio_const_attr_in_illuminance_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group al3010_attribute_group = { + .attrs = al3010_attributes, +}; + +static int al3010_set_pwr_on(struct al3010_data *data) +{ + return regmap_write(data->regmap, AL3010_REG_SYSTEM, AL3010_CONFIG_ENABLE); +} + +static void al3010_set_pwr_off(void *_data) +{ + struct al3010_data *data = _data; + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = regmap_write(data->regmap, AL3010_REG_SYSTEM, AL3010_CONFIG_DISABLE); + if (ret) + dev_err(dev, "failed to write system register\n"); +} + +static int al3010_init(struct al3010_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = al3010_set_pwr_on(data); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, al3010_set_pwr_off, data); + if (ret) + return ret; + return regmap_write(data->regmap, AL3010_REG_CONFIG, + FIELD_PREP(AL3010_GAIN_MASK, AL3XXX_RANGE_3)); +} + +static int al3010_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct al3010_data *data = iio_priv(indio_dev); + int ret, gain; + __le16 raw; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* + * ALS ADC value is stored in two adjacent registers: + * - low byte of output is stored at AL3010_REG_DATA_LOW + * - high byte of output is stored at AL3010_REG_DATA_LOW + 1 + */ + ret = regmap_bulk_read(data->regmap, AL3010_REG_DATA_LOW, + &raw, sizeof(raw)); + if (ret) + return ret; + + *val = le16_to_cpu(raw); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = regmap_read(data->regmap, AL3010_REG_CONFIG, &gain); + if (ret) + return ret; + + gain = FIELD_GET(AL3010_GAIN_MASK, gain); + *val = al3010_scales[gain][0]; + *val2 = al3010_scales[gain][1]; + + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static int al3010_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct al3010_data *data = iio_priv(indio_dev); + unsigned int i; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + for (i = 0; i < ARRAY_SIZE(al3010_scales); i++) { + if (val != al3010_scales[i][0] || + val2 != al3010_scales[i][1]) + continue; + + return regmap_write(data->regmap, AL3010_REG_CONFIG, + FIELD_PREP(AL3010_GAIN_MASK, i)); + } + break; + } + return -EINVAL; +} + +static const struct iio_info al3010_info = { + .read_raw = al3010_read_raw, + .write_raw = al3010_write_raw, + .attrs = &al3010_attribute_group, +}; + +static int al3010_probe(struct i2c_client *client) +{ + struct al3010_data *data; + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->regmap = devm_regmap_init_i2c(client, &al3010_regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "cannot allocate regmap\n"); + + indio_dev->info = &al3010_info; + indio_dev->name = "al3010"; + indio_dev->channels = al3010_channels; + indio_dev->num_channels = ARRAY_SIZE(al3010_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = al3010_init(data); + if (ret) + return dev_err_probe(dev, ret, "failed to init ALS\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static int al3010_suspend(struct device *dev) +{ + struct al3010_data *data = iio_priv(dev_get_drvdata(dev)); + + al3010_set_pwr_off(data); + return 0; +} + +static int al3010_resume(struct device *dev) +{ + struct al3010_data *data = iio_priv(dev_get_drvdata(dev)); + + return al3010_set_pwr_on(data); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(al3010_pm_ops, al3010_suspend, al3010_resume); + +static const struct i2c_device_id al3010_id[] = { + { .name = "al3010" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, al3010_id); + +static const struct of_device_id al3010_of_match[] = { + { .compatible = "dynaimage,al3010", }, + { } +}; +MODULE_DEVICE_TABLE(of, al3010_of_match); + +static struct i2c_driver al3010_driver = { + .driver = { + .name = "al3010", + .of_match_table = al3010_of_match, + .pm = pm_sleep_ptr(&al3010_pm_ops), + }, + .probe = al3010_probe, + .id_table = al3010_id, +}; +module_i2c_driver(al3010_driver); + +MODULE_AUTHOR("Daniel Baluta "); +MODULE_AUTHOR("David Heidelberg "); +MODULE_DESCRIPTION("AL3010 Ambient Light Sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/al3320a.c b/drivers/iio/light/al3320a.c new file mode 100644 index 000000000..8bb7f7e87 --- /dev/null +++ b/drivers/iio/light/al3320a.c @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AL3320A - Dyna Image Ambient Light Sensor + * + * Copyright (c) 2014, Intel Corporation. + * + * IIO driver for AL3320A (7-bit I2C slave address 0x1C). + * + * TODO: interrupt support, thresholds + * When the driver will get support for interrupt handling, then interrupt + * will need to be disabled before turning sensor OFF in order to avoid + * potential races with the interrupt handling. + */ + +#include +#include +#include +#include + +#include +#include + +#define AL3320A_REG_CONFIG 0x00 +#define AL3320A_REG_STATUS 0x01 +#define AL3320A_REG_INT 0x02 +#define AL3320A_REG_WAIT 0x06 +#define AL3320A_REG_CONFIG_RANGE 0x07 +#define AL3320A_REG_PERSIST 0x08 +#define AL3320A_REG_MEAN_TIME 0x09 +#define AL3320A_REG_ADUMMY 0x0A +#define AL3320A_REG_DATA_LOW 0x22 + +#define AL3320A_REG_LOW_THRESH_LOW 0x30 +#define AL3320A_REG_LOW_THRESH_HIGH 0x31 +#define AL3320A_REG_HIGH_THRESH_LOW 0x32 +#define AL3320A_REG_HIGH_THRESH_HIGH 0x33 + +#define AL3320A_CONFIG_DISABLE 0x00 +#define AL3320A_CONFIG_ENABLE 0x01 + +#define AL3320A_GAIN_MASK GENMASK(2, 1) + +/* chip params default values */ +#define AL3320A_DEFAULT_MEAN_TIME 4 +#define AL3320A_DEFAULT_WAIT_TIME 0 /* no waiting */ + +#define AL3320A_SCALE_AVAILABLE "0.512 0.128 0.032 0.01" + +enum al3320a_range { + AL3320A_RANGE_1, /* 33.28 Klx */ + AL3320A_RANGE_2, /* 8.32 Klx */ + AL3320A_RANGE_3, /* 2.08 Klx */ + AL3320A_RANGE_4 /* 0.65 Klx */ +}; + +static const int al3320a_scales[][2] = { + {0, 512000}, {0, 128000}, {0, 32000}, {0, 10000} +}; + +static const struct regmap_config al3320a_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = AL3320A_REG_HIGH_THRESH_HIGH, +}; + +struct al3320a_data { + struct regmap *regmap; +}; + +static const struct iio_chan_spec al3320a_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + } +}; + +static IIO_CONST_ATTR(in_illuminance_scale_available, AL3320A_SCALE_AVAILABLE); + +static struct attribute *al3320a_attributes[] = { + &iio_const_attr_in_illuminance_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group al3320a_attribute_group = { + .attrs = al3320a_attributes, +}; + +static int al3320a_set_pwr_on(struct al3320a_data *data) +{ + return regmap_write(data->regmap, AL3320A_REG_CONFIG, AL3320A_CONFIG_ENABLE); +} + +static void al3320a_set_pwr_off(void *_data) +{ + struct al3320a_data *data = _data; + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = regmap_write(data->regmap, AL3320A_REG_CONFIG, AL3320A_CONFIG_DISABLE); + if (ret) + dev_err(dev, "failed to write system register\n"); +} + +static int al3320a_init(struct al3320a_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = al3320a_set_pwr_on(data); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, al3320a_set_pwr_off, data); + if (ret) + return ret; + + ret = regmap_write(data->regmap, AL3320A_REG_CONFIG_RANGE, + FIELD_PREP(AL3320A_GAIN_MASK, AL3320A_RANGE_3)); + if (ret) + return ret; + + ret = regmap_write(data->regmap, AL3320A_REG_MEAN_TIME, + AL3320A_DEFAULT_MEAN_TIME); + if (ret) + return ret; + + return regmap_write(data->regmap, AL3320A_REG_WAIT, + AL3320A_DEFAULT_WAIT_TIME); +} + +static int al3320a_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct al3320a_data *data = iio_priv(indio_dev); + int ret, gain; + __le16 raw; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* + * ALS ADC value is stored in two adjacent registers: + * - low byte of output is stored at AL3320A_REG_DATA_LOW + * - high byte of output is stored at AL3320A_REG_DATA_LOW + 1 + */ + ret = regmap_bulk_read(data->regmap, AL3320A_REG_DATA_LOW, + &raw, sizeof(raw)); + if (ret) + return ret; + + *val = le16_to_cpu(raw); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = regmap_read(data->regmap, AL3320A_REG_CONFIG_RANGE, &gain); + if (ret) + return ret; + + gain = FIELD_GET(AL3320A_GAIN_MASK, gain); + *val = al3320a_scales[gain][0]; + *val2 = al3320a_scales[gain][1]; + + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static int al3320a_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct al3320a_data *data = iio_priv(indio_dev); + unsigned int i; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + for (i = 0; i < ARRAY_SIZE(al3320a_scales); i++) { + if (val != al3320a_scales[i][0] || + val2 != al3320a_scales[i][1]) + continue; + + return regmap_write(data->regmap, AL3320A_REG_CONFIG_RANGE, + FIELD_PREP(AL3320A_GAIN_MASK, i)); + } + break; + } + return -EINVAL; +} + +static const struct iio_info al3320a_info = { + .read_raw = al3320a_read_raw, + .write_raw = al3320a_write_raw, + .attrs = &al3320a_attribute_group, +}; + +static int al3320a_probe(struct i2c_client *client) +{ + struct al3320a_data *data; + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + + data->regmap = devm_regmap_init_i2c(client, &al3320a_regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "cannot allocate regmap\n"); + + indio_dev->info = &al3320a_info; + indio_dev->name = "al3320a"; + indio_dev->channels = al3320a_channels; + indio_dev->num_channels = ARRAY_SIZE(al3320a_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = al3320a_init(data); + if (ret < 0) { + dev_err(dev, "al3320a chip init failed\n"); + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} + +static int al3320a_suspend(struct device *dev) +{ + struct al3320a_data *data = iio_priv(dev_get_drvdata(dev)); + + al3320a_set_pwr_off(data); + return 0; +} + +static int al3320a_resume(struct device *dev) +{ + struct al3320a_data *data = iio_priv(dev_get_drvdata(dev)); + + return al3320a_set_pwr_on(data); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(al3320a_pm_ops, al3320a_suspend, + al3320a_resume); + +static const struct i2c_device_id al3320a_id[] = { + { .name = "al3320a" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, al3320a_id); + +static const struct of_device_id al3320a_of_match[] = { + { .compatible = "dynaimage,al3320a", }, + { } +}; +MODULE_DEVICE_TABLE(of, al3320a_of_match); + +static const struct acpi_device_id al3320a_acpi_match[] = { + {"CALS0001"}, + { } +}; +MODULE_DEVICE_TABLE(acpi, al3320a_acpi_match); + +static struct i2c_driver al3320a_driver = { + .driver = { + .name = "al3320a", + .of_match_table = al3320a_of_match, + .pm = pm_sleep_ptr(&al3320a_pm_ops), + .acpi_match_table = al3320a_acpi_match, + }, + .probe = al3320a_probe, + .id_table = al3320a_id, +}; + +module_i2c_driver(al3320a_driver); + +MODULE_AUTHOR("Daniel Baluta "); +MODULE_DESCRIPTION("AL3320A Ambient Light Sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/apds9160.c b/drivers/iio/light/apds9160.c new file mode 100644 index 000000000..d7e0924b8 --- /dev/null +++ b/drivers/iio/light/apds9160.c @@ -0,0 +1,1589 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * APDS9160 sensor driver. + * Chip is combined proximity and ambient light sensor. + * Author: 2024 Mikael Gonella-Bolduc + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +/* Main control register */ +#define APDS9160_REG_CTRL 0x00 +#define APDS9160_CTRL_SWRESET BIT(4) /* 1: Activate reset */ +#define APDS9160_CTRL_MODE_RGB BIT(2) /* 0: ALS & IR, 1: RGB & IR */ +#define APDS9160_CTRL_EN_ALS BIT(1) /* 1: ALS active */ +#define APDS9160_CTLR_EN_PS BIT(0) /* 1: PS active */ + +/* Status register */ +#define APDS9160_SR_LS_INT BIT(4) +#define APDS9160_SR_LS_NEW_DATA BIT(3) +#define APDS9160_SR_PS_INT BIT(1) +#define APDS9160_SR_PS_NEW_DATA BIT(0) + +/* Interrupt configuration registers */ +#define APDS9160_REG_INT_CFG 0x19 +#define APDS9160_REG_INT_PST 0x1A +#define APDS9160_INT_CFG_EN_LS BIT(2) /* LS int enable */ +#define APDS9160_INT_CFG_EN_PS BIT(0) /* PS int enable */ + +/* Proximity registers */ +#define APDS9160_REG_PS_LED 0x01 +#define APDS9160_REG_PS_PULSES 0x02 +#define APDS9160_REG_PS_MEAS_RATE 0x03 +#define APDS9160_REG_PS_THRES_HI_LSB 0x1B +#define APDS9160_REG_PS_THRES_HI_MSB 0x1C +#define APDS9160_REG_PS_THRES_LO_LSB 0x1D +#define APDS9160_REG_PS_THRES_LO_MSB 0x1E +#define APDS9160_REG_PS_DATA_LSB 0x08 +#define APDS9160_REG_PS_DATA_MSB 0x09 +#define APDS9160_REG_PS_CAN_LEVEL_DIG_LSB 0x1F +#define APDS9160_REG_PS_CAN_LEVEL_DIG_MSB 0x20 +#define APDS9160_REG_PS_CAN_LEVEL_ANA_DUR 0x21 +#define APDS9160_REG_PS_CAN_LEVEL_ANA_CURRENT 0x22 + +/* Light sensor registers */ +#define APDS9160_REG_LS_MEAS_RATE 0x04 +#define APDS9160_REG_LS_GAIN 0x05 +#define APDS9160_REG_LS_DATA_CLEAR_LSB 0x0A +#define APDS9160_REG_LS_DATA_CLEAR 0x0B +#define APDS9160_REG_LS_DATA_CLEAR_MSB 0x0C +#define APDS9160_REG_LS_DATA_ALS_LSB 0x0D +#define APDS9160_REG_LS_DATA_ALS 0x0E +#define APDS9160_REG_LS_DATA_ALS_MSB 0x0F +#define APDS9160_REG_LS_THRES_UP_LSB 0x24 +#define APDS9160_REG_LS_THRES_UP 0x25 +#define APDS9160_REG_LS_THRES_UP_MSB 0x26 +#define APDS9160_REG_LS_THRES_LO_LSB 0x27 +#define APDS9160_REG_LS_THRES_LO 0x28 +#define APDS9160_REG_LS_THRES_LO_MSB 0x29 +#define APDS9160_REG_LS_THRES_VAR 0x2A + +/* Part identification number register */ +#define APDS9160_REG_ID 0x06 + +/* Status register */ +#define APDS9160_REG_SR 0x07 +#define APDS9160_SR_DATA_ALS BIT(3) +#define APDS9160_SR_DATA_PS BIT(0) + +/* Supported ID:s */ +#define APDS9160_PART_ID_0 0x03 + +#define APDS9160_PS_THRES_MAX 0x7FF +#define APDS9160_LS_THRES_MAX 0xFFFFF +#define APDS9160_CMD_LS_RESOLUTION_25MS 0x04 +#define APDS9160_CMD_LS_RESOLUTION_50MS 0x03 +#define APDS9160_CMD_LS_RESOLUTION_100MS 0x02 +#define APDS9160_CMD_LS_RESOLUTION_200MS 0x01 +#define APDS9160_PS_DATA_MASK 0x7FF + +#define APDS9160_DEFAULT_LS_GAIN 3 +#define APDS9160_DEFAULT_LS_RATE 100 +#define APDS9160_DEFAULT_PS_RATE 100 +#define APDS9160_DEFAULT_PS_CANCELLATION_LEVEL 0 +#define APDS9160_DEFAULT_PS_ANALOG_CANCELLATION 0 +#define APDS9160_DEFAULT_PS_GAIN 1 +#define APDS9160_DEFAULT_PS_CURRENT 100 +#define APDS9160_DEFAULT_PS_RESOLUTION_11BITS 0x03 + +static const struct reg_default apds9160_reg_defaults[] = { + { APDS9160_REG_CTRL, 0x00 }, /* Sensors disabled by default */ + { APDS9160_REG_PS_LED, 0x33 }, /* 60 kHz frequency, 100 mA */ + { APDS9160_REG_PS_PULSES, 0x08 }, /* 8 pulses */ + { APDS9160_REG_PS_MEAS_RATE, 0x05 }, /* 100ms */ + { APDS9160_REG_LS_MEAS_RATE, 0x22 }, /* 100ms */ + { APDS9160_REG_LS_GAIN, 0x01 }, /* 3x */ + { APDS9160_REG_INT_CFG, 0x10 }, /* Interrupts disabled */ + { APDS9160_REG_INT_PST, 0x00 }, + { APDS9160_REG_PS_THRES_HI_LSB, 0xFF }, + { APDS9160_REG_PS_THRES_HI_MSB, 0x07 }, + { APDS9160_REG_PS_THRES_LO_LSB, 0x00 }, + { APDS9160_REG_PS_THRES_LO_MSB, 0x00 }, + { APDS9160_REG_PS_CAN_LEVEL_DIG_LSB, 0x00 }, + { APDS9160_REG_PS_CAN_LEVEL_DIG_MSB, 0x00 }, + { APDS9160_REG_PS_CAN_LEVEL_ANA_DUR, 0x00 }, + { APDS9160_REG_PS_CAN_LEVEL_ANA_CURRENT, 0x00 }, + { APDS9160_REG_LS_THRES_UP_LSB, 0xFF }, + { APDS9160_REG_LS_THRES_UP, 0xFF }, + { APDS9160_REG_LS_THRES_UP_MSB, 0x0F }, + { APDS9160_REG_LS_THRES_LO_LSB, 0x00 }, + { APDS9160_REG_LS_THRES_LO, 0x00 }, + { APDS9160_REG_LS_THRES_LO_MSB, 0x00 }, + { APDS9160_REG_LS_THRES_VAR, 0x00 }, +}; + +static const struct regmap_range apds9160_readable_ranges[] = { + regmap_reg_range(APDS9160_REG_CTRL, APDS9160_REG_LS_THRES_VAR), +}; + +static const struct regmap_access_table apds9160_readable_table = { + .yes_ranges = apds9160_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9160_readable_ranges), +}; + +static const struct regmap_range apds9160_writeable_ranges[] = { + regmap_reg_range(APDS9160_REG_CTRL, APDS9160_REG_LS_GAIN), + regmap_reg_range(APDS9160_REG_INT_CFG, APDS9160_REG_LS_THRES_VAR), +}; + +static const struct regmap_access_table apds9160_writeable_table = { + .yes_ranges = apds9160_writeable_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9160_writeable_ranges), +}; + +static const struct regmap_range apds9160_volatile_ranges[] = { + regmap_reg_range(APDS9160_REG_SR, APDS9160_REG_LS_DATA_ALS_MSB), +}; + +static const struct regmap_access_table apds9160_volatile_table = { + .yes_ranges = apds9160_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9160_volatile_ranges), +}; + +static const struct regmap_config apds9160_regmap_config = { + .name = "apds9160_regmap", + .reg_bits = 8, + .val_bits = 8, + .use_single_read = true, + .use_single_write = true, + + .rd_table = &apds9160_readable_table, + .wr_table = &apds9160_writeable_table, + .volatile_table = &apds9160_volatile_table, + + .reg_defaults = apds9160_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(apds9160_reg_defaults), + .max_register = 37, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct iio_event_spec apds9160_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec apds9160_channels[] = { + { + /* Proximity sensor channel */ + .type = IIO_PROXIMITY, + .address = APDS9160_REG_PS_DATA_LSB, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .event_spec = apds9160_event_spec, + .num_event_specs = ARRAY_SIZE(apds9160_event_spec), + }, + { + /* Proximity sensor led current */ + .type = IIO_CURRENT, + .output = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW), + }, + { + /* Illuminance */ + .type = IIO_LIGHT, + .address = APDS9160_REG_LS_DATA_ALS_LSB, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_HARDWAREGAIN) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .event_spec = apds9160_event_spec, + .num_event_specs = ARRAY_SIZE(apds9160_event_spec), + }, + { + /* Clear channel */ + .type = IIO_INTENSITY, + .address = APDS9160_REG_LS_DATA_CLEAR_LSB, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .channel2 = IIO_MOD_LIGHT_CLEAR, + .modified = 1, + }, +}; + +static const struct iio_chan_spec apds9160_channels_without_events[] = { + { + /* Proximity sensor channel */ + .type = IIO_PROXIMITY, + .address = APDS9160_REG_PS_DATA_LSB, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + /* Proximity sensor led current */ + .type = IIO_CURRENT, + .output = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW), + }, + { + /* Illuminance */ + .type = IIO_LIGHT, + .address = APDS9160_REG_LS_DATA_ALS_LSB, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_HARDWAREGAIN) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + /* Clear channel */ + .type = IIO_INTENSITY, + .address = APDS9160_REG_LS_DATA_CLEAR_LSB, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .channel2 = IIO_MOD_LIGHT_CLEAR, + .modified = 1, + }, +}; + +static const int apds9160_als_rate_avail[] = { + 25, 50, 100, 200 +}; + +static const int apds9160_als_rate_map[][2] = { + { 25, 0x00 }, + { 50, 0x01 }, + { 100, 0x02 }, + { 200, 0x03 }, +}; + +static const int apds9160_als_gain_map[][2] = { + { 1, 0x00 }, + { 3, 0x01 }, + { 6, 0x02 }, + { 18, 0x03 }, + { 54, 0x04 }, +}; + +static const int apds9160_ps_gain_avail[] = { + 1, 2, 4, 8 +}; + +static const int apds9160_ps_gain_map[][2] = { + { 1, 0x00 }, + { 2, 0x01 }, + { 4, 0x02 }, + { 8, 0x03 }, +}; + +static const int apds9160_ps_rate_avail[] = { + 25, 50, 100, 200, 400 +}; + +static const int apds9160_ps_rate_map[][2] = { + { 25, 0x03 }, + { 50, 0x04 }, + { 100, 0x05 }, + { 200, 0x06 }, + { 400, 0x07 }, +}; + +static const int apds9160_ps_led_current_avail[] = { + 10, 25, 50, 100, 150, 175, 200 +}; + +static const int apds9160_ps_led_current_map[][2] = { + { 10, 0x00 }, + { 25, 0x01 }, + { 50, 0x02 }, + { 100, 0x03 }, + { 150, 0x04 }, + { 175, 0x05 }, + { 200, 0x06 }, +}; + +/** + * struct apds9160_scale - apds9160 scale mapping definition + * + * @itime: Integration time in ms + * @gain: Gain multiplier + * @scale1: lux/count resolution + * @scale2: micro lux/count + */ +struct apds9160_scale { + int itime; + int gain; + int scale1; + int scale2; +}; + +/* Scale mapping extracted from datasheet */ +static const struct apds9160_scale apds9160_als_scale_map[] = { + { + .itime = 25, + .gain = 1, + .scale1 = 3, + .scale2 = 272000, + }, + { + .itime = 25, + .gain = 3, + .scale1 = 1, + .scale2 = 77000, + }, + { + .itime = 25, + .gain = 6, + .scale1 = 0, + .scale2 = 525000, + }, + { + .itime = 25, + .gain = 18, + .scale1 = 0, + .scale2 = 169000, + }, + { + .itime = 25, + .gain = 54, + .scale1 = 0, + .scale2 = 49000, + }, + { + .itime = 50, + .gain = 1, + .scale1 = 1, + .scale2 = 639000, + }, + { + .itime = 50, + .gain = 3, + .scale1 = 0, + .scale2 = 538000, + }, + { + .itime = 50, + .gain = 6, + .scale1 = 0, + .scale2 = 263000, + }, + { + .itime = 50, + .gain = 18, + .scale1 = 0, + .scale2 = 84000, + }, + { + .itime = 50, + .gain = 54, + .scale1 = 0, + .scale2 = 25000, + }, + { + .itime = 100, + .gain = 1, + .scale1 = 0, + .scale2 = 819000, + }, + { + .itime = 100, + .gain = 3, + .scale1 = 0, + .scale2 = 269000, + }, + { + .itime = 100, + .gain = 6, + .scale1 = 0, + .scale2 = 131000, + }, + { + .itime = 100, + .gain = 18, + .scale1 = 0, + .scale2 = 42000, + }, + { + .itime = 100, + .gain = 54, + .scale1 = 0, + .scale2 = 12000, + }, + { + .itime = 200, + .gain = 1, + .scale1 = 0, + .scale2 = 409000, + }, + { + .itime = 200, + .gain = 3, + .scale1 = 0, + .scale2 = 135000, + }, + { + .itime = 200, + .gain = 6, + .scale1 = 0, + .scale2 = 66000, + }, + { + .itime = 200, + .gain = 18, + .scale1 = 0, + .scale2 = 21000, + }, + { + .itime = 200, + .gain = 54, + .scale1 = 0, + .scale2 = 6000, + }, +}; + +static const int apds9160_25ms_avail[][2] = { + { 3, 272000 }, + { 1, 77000 }, + { 0, 525000 }, + { 0, 169000 }, + { 0, 49000 }, +}; + +static const int apds9160_50ms_avail[][2] = { + { 1, 639000 }, + { 0, 538000 }, + { 0, 263000 }, + { 0, 84000 }, + { 0, 25000 }, +}; + +static const int apds9160_100ms_avail[][2] = { + { 0, 819000 }, + { 0, 269000 }, + { 0, 131000 }, + { 0, 42000 }, + { 0, 12000 }, +}; + +static const int apds9160_200ms_avail[][2] = { + { 0, 409000 }, + { 0, 135000 }, + { 0, 66000 }, + { 0, 21000 }, + { 0, 6000 }, +}; + +static const struct reg_field apds9160_reg_field_ls_en = + REG_FIELD(APDS9160_REG_CTRL, 1, 1); + +static const struct reg_field apds9160_reg_field_ps_en = + REG_FIELD(APDS9160_REG_CTRL, 0, 0); + +static const struct reg_field apds9160_reg_field_int_ps = + REG_FIELD(APDS9160_REG_INT_CFG, 0, 0); + +static const struct reg_field apds9160_reg_field_int_als = + REG_FIELD(APDS9160_REG_INT_CFG, 2, 2); + +static const struct reg_field apds9160_reg_field_ps_overflow = + REG_FIELD(APDS9160_REG_PS_DATA_MSB, 3, 3); + +static const struct reg_field apds9160_reg_field_als_rate = + REG_FIELD(APDS9160_REG_LS_MEAS_RATE, 0, 2); + +static const struct reg_field apds9160_reg_field_als_gain = + REG_FIELD(APDS9160_REG_LS_GAIN, 0, 2); + +static const struct reg_field apds9160_reg_field_ps_rate = + REG_FIELD(APDS9160_REG_PS_MEAS_RATE, 0, 2); + +static const struct reg_field apds9160_reg_field_als_res = + REG_FIELD(APDS9160_REG_LS_MEAS_RATE, 4, 6); + +static const struct reg_field apds9160_reg_field_ps_current = + REG_FIELD(APDS9160_REG_PS_LED, 0, 2); + +static const struct reg_field apds9160_reg_field_ps_gain = + REG_FIELD(APDS9160_REG_PS_MEAS_RATE, 6, 7); + +static const struct reg_field apds9160_reg_field_ps_resolution = + REG_FIELD(APDS9160_REG_PS_MEAS_RATE, 3, 4); + +struct apds9160_chip { + struct i2c_client *client; + struct regmap *regmap; + + struct regmap_field *reg_enable_ps; + struct regmap_field *reg_enable_als; + struct regmap_field *reg_int_ps; + struct regmap_field *reg_int_als; + struct regmap_field *reg_ps_overflow; + struct regmap_field *reg_als_rate; + struct regmap_field *reg_als_resolution; + struct regmap_field *reg_ps_rate; + struct regmap_field *reg_als_gain; + struct regmap_field *reg_ps_current; + struct regmap_field *reg_ps_gain; + struct regmap_field *reg_ps_resolution; + + struct mutex lock; /* protects state and config data */ + + /* State data */ + int als_int; + int ps_int; + + /* Configuration values */ + int als_itime; + int als_hwgain; + int als_scale1; + int als_scale2; + int ps_rate; + int ps_cancellation_level; + int ps_current; + int ps_gain; +}; + +static int apds9160_set_ps_rate(struct apds9160_chip *data, int val) +{ + int idx; + + for (idx = 0; idx < ARRAY_SIZE(apds9160_ps_rate_map); idx++) { + int ret; + + if (apds9160_ps_rate_map[idx][0] != val) + continue; + + ret = regmap_field_write(data->reg_ps_rate, + apds9160_ps_rate_map[idx][1]); + if (ret) + return ret; + data->ps_rate = val; + + return ret; + } + + return -EINVAL; +} + +static int apds9160_set_ps_gain(struct apds9160_chip *data, int val) +{ + int idx; + + for (idx = 0; idx < ARRAY_SIZE(apds9160_ps_gain_map); idx++) { + int ret; + + if (apds9160_ps_gain_map[idx][0] != val) + continue; + + ret = regmap_field_write(data->reg_ps_gain, + apds9160_ps_gain_map[idx][1]); + if (ret) + return ret; + data->ps_gain = val; + + return ret; + } + + return -EINVAL; +} + +/* + * The PS intelligent cancellation level register allows + * for an on-chip subtraction of the ADC count caused by + * unwanted reflected light from PS ADC output. + */ +static int apds9160_set_ps_cancellation_level(struct apds9160_chip *data, + int val) +{ + int ret; + __le16 buf; + + if (val < 0 || val > 0xFFFF) + return -EINVAL; + + buf = cpu_to_le16(val); + ret = regmap_bulk_write(data->regmap, APDS9160_REG_PS_CAN_LEVEL_DIG_LSB, + &buf, 2); + if (ret) + return ret; + + data->ps_cancellation_level = val; + + return ret; +} + +/* + * This parameter determines the cancellation pulse duration + * in each of the PWM pulse. The cancellation is applied during the + * integration phase of the PS measurement. + * Duration is programmed in half clock cycles + * A duration value of 0 or 1 will not generate any cancellation pulse + */ +static int apds9160_set_ps_analog_cancellation(struct apds9160_chip *data, + int val) +{ + if (val < 0 || val > 63) + return -EINVAL; + + return regmap_write(data->regmap, APDS9160_REG_PS_CAN_LEVEL_ANA_DUR, + val); +} + +/* + * This parameter works in conjunction with the cancellation pulse duration + * The value determines the current used for crosstalk cancellation + * Coarse value is in steps of 60 nA + * Fine value is in steps of 2.4 nA + */ +static int apds9160_set_ps_cancellation_current(struct apds9160_chip *data, + int coarse_val, + int fine_val) +{ + int val; + + if (coarse_val < 0 || coarse_val > 4) + return -EINVAL; + + if (fine_val < 0 || fine_val > 15) + return -EINVAL; + + /* Coarse value at B4:B5 and fine value at B0:B3 */ + val = (coarse_val << 4) | fine_val; + + return regmap_write(data->regmap, APDS9160_REG_PS_CAN_LEVEL_ANA_CURRENT, + val); +} + +static int apds9160_ps_init_analog_cancellation(struct device *dev, + struct apds9160_chip *data) +{ + int ret, duration, picoamp, idx, coarse, fine; + + ret = device_property_read_u32(dev, + "ps-cancellation-duration", &duration); + if (ret || duration == 0) { + /* Don't fail since this is not required */ + return 0; + } + + ret = device_property_read_u32(dev, + "ps-cancellation-current-picoamp", &picoamp); + if (ret) + return ret; + + if (picoamp < 60000 || picoamp > 276000 || picoamp % 2400 != 0) + return dev_err_probe(dev, -EINVAL, + "Invalid cancellation current\n"); + + /* Compute required coarse and fine value from requested current */ + fine = 0; + coarse = 0; + for (idx = 60000; idx < picoamp; idx += 2400) { + if (fine == 15) { + fine = 0; + coarse++; + idx += 21600; + } else { + fine++; + } + } + + if (picoamp != idx) + dev_warn(dev, + "Invalid cancellation current %i, rounding to %i\n", + picoamp, idx); + + ret = apds9160_set_ps_analog_cancellation(data, duration); + if (ret) + return ret; + + return apds9160_set_ps_cancellation_current(data, coarse, fine); +} + +static int apds9160_set_ps_current(struct apds9160_chip *data, int val) +{ + int idx; + + for (idx = 0; idx < ARRAY_SIZE(apds9160_ps_led_current_map); idx++) { + int ret; + + if (apds9160_ps_led_current_map[idx][0] != val) + continue; + + ret = regmap_field_write( + data->reg_ps_current, + apds9160_ps_led_current_map[idx][1]); + if (ret) + return ret; + data->ps_current = val; + + return ret; + } + + return -EINVAL; +} + +static int apds9160_set_als_gain(struct apds9160_chip *data, int gain) +{ + int idx; + + for (idx = 0; idx < ARRAY_SIZE(apds9160_als_gain_map); idx++) { + int ret; + + if (gain != apds9160_als_gain_map[idx][0]) + continue; + + ret = regmap_field_write(data->reg_als_gain, + apds9160_als_gain_map[idx][1]); + if (ret) + return ret; + data->als_hwgain = gain; + + return ret; + } + + return -EINVAL; +} + +static int apds9160_set_als_scale(struct apds9160_chip *data, int val, int val2) +{ + int idx; + + for (idx = 0; idx < ARRAY_SIZE(apds9160_als_scale_map); idx++) { + if (apds9160_als_scale_map[idx].itime == data->als_itime && + apds9160_als_scale_map[idx].scale1 == val && + apds9160_als_scale_map[idx].scale2 == val2) { + int ret = apds9160_set_als_gain(data, + apds9160_als_scale_map[idx].gain); + if (ret) + return ret; + data->als_scale1 = val; + data->als_scale2 = val2; + + return ret; + } + } + + return -EINVAL; +} + +static int apds9160_set_als_resolution(struct apds9160_chip *data, int val) +{ + switch (val) { + case 25: + return regmap_field_write(data->reg_als_resolution, + APDS9160_CMD_LS_RESOLUTION_25MS); + case 50: + return regmap_field_write(data->reg_als_resolution, + APDS9160_CMD_LS_RESOLUTION_50MS); + case 200: + return regmap_field_write(data->reg_als_resolution, + APDS9160_CMD_LS_RESOLUTION_200MS); + default: + return regmap_field_write(data->reg_als_resolution, + APDS9160_CMD_LS_RESOLUTION_100MS); + } +} + +static int apds9160_set_als_rate(struct apds9160_chip *data, int val) +{ + int idx; + + for (idx = 0; idx < ARRAY_SIZE(apds9160_als_rate_map); idx++) { + if (apds9160_als_rate_map[idx][0] != val) + continue; + + return regmap_field_write(data->reg_als_rate, + apds9160_als_rate_map[idx][1]); + } + + return -EINVAL; +} + +/* + * Setting the integration time ajusts resolution, rate, scale and gain + */ +static int apds9160_set_als_int_time(struct apds9160_chip *data, int val) +{ + int ret; + int idx; + + ret = apds9160_set_als_rate(data, val); + if (ret) + return ret; + + /* Match resolution register with rate */ + ret = apds9160_set_als_resolution(data, val); + if (ret) + return ret; + + data->als_itime = val; + + /* Set the scale minimum gain */ + for (idx = 0; idx < ARRAY_SIZE(apds9160_als_scale_map); idx++) { + if (data->als_itime != apds9160_als_scale_map[idx].itime) + continue; + + return apds9160_set_als_scale(data, + apds9160_als_scale_map[idx].scale1, + apds9160_als_scale_map[idx].scale2); + } + + return -EINVAL; +} + +static int apds9160_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct apds9160_chip *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_LIGHT: + *length = ARRAY_SIZE(apds9160_als_rate_avail); + *vals = (const int *)apds9160_als_rate_avail; + *type = IIO_VAL_INT; + + return IIO_AVAIL_LIST; + case IIO_PROXIMITY: + *length = ARRAY_SIZE(apds9160_ps_rate_avail); + *vals = (const int *)apds9160_ps_rate_avail; + *type = IIO_VAL_INT; + + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_PROXIMITY: + *length = ARRAY_SIZE(apds9160_ps_gain_avail); + *vals = (const int *)apds9160_ps_gain_avail; + *type = IIO_VAL_INT; + + return IIO_AVAIL_LIST; + case IIO_LIGHT: + /* The available scales changes depending on itime */ + switch (data->als_itime) { + case 25: + *length = ARRAY_SIZE(apds9160_25ms_avail) * 2; + *vals = (const int *)apds9160_25ms_avail; + *type = IIO_VAL_INT_PLUS_MICRO; + + return IIO_AVAIL_LIST; + case 50: + *length = ARRAY_SIZE(apds9160_50ms_avail) * 2; + *vals = (const int *)apds9160_50ms_avail; + *type = IIO_VAL_INT_PLUS_MICRO; + + return IIO_AVAIL_LIST; + case 100: + *length = ARRAY_SIZE(apds9160_100ms_avail) * 2; + *vals = (const int *)apds9160_100ms_avail; + *type = IIO_VAL_INT_PLUS_MICRO; + + return IIO_AVAIL_LIST; + case 200: + *length = ARRAY_SIZE(apds9160_200ms_avail) * 2; + *vals = (const int *)apds9160_200ms_avail; + *type = IIO_VAL_INT_PLUS_MICRO; + + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + default: + return -EINVAL; + } + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_CURRENT: + *length = ARRAY_SIZE(apds9160_ps_led_current_avail); + *vals = (const int *)apds9160_ps_led_current_avail; + *type = IIO_VAL_INT; + + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +static int apds9160_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + return IIO_VAL_INT; + case IIO_CHAN_INFO_HARDWAREGAIN: + return IIO_VAL_INT; + case IIO_CHAN_INFO_RAW: + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int apds9160_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct apds9160_chip *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_PROXIMITY: { + __le16 buf; + + ret = regmap_bulk_read(data->regmap, chan->address, + &buf, 2); + if (ret) + return ret; + *val = le16_to_cpu(buf); + /* Remove overflow bits from result */ + *val = FIELD_GET(APDS9160_PS_DATA_MASK, *val); + + return IIO_VAL_INT; + } + case IIO_LIGHT: + case IIO_INTENSITY: { + u8 buf[3]; + + ret = regmap_bulk_read(data->regmap, chan->address, + &buf, 3); + if (ret) + return ret; + *val = get_unaligned_le24(buf); + + return IIO_VAL_INT; + } + case IIO_CURRENT: + *val = data->ps_current; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_HARDWAREGAIN: + switch (chan->type) { + case IIO_LIGHT: + *val = data->als_hwgain; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_PROXIMITY: + *val = data->ps_rate; + + return IIO_VAL_INT; + case IIO_LIGHT: + *val = data->als_itime; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_PROXIMITY: + *val = data->ps_cancellation_level; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_PROXIMITY: + *val = data->ps_gain; + + return IIO_VAL_INT; + case IIO_LIGHT: + *val = data->als_scale1; + *val2 = data->als_scale2; + + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +}; + +static int apds9160_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct apds9160_chip *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val2 != 0) + return -EINVAL; + switch (chan->type) { + case IIO_PROXIMITY: + return apds9160_set_ps_rate(data, val); + case IIO_LIGHT: + return apds9160_set_als_int_time(data, val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_PROXIMITY: + return apds9160_set_ps_gain(data, val); + case IIO_LIGHT: + return apds9160_set_als_scale(data, val, val2); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + if (val2 != 0) + return -EINVAL; + switch (chan->type) { + case IIO_PROXIMITY: + return apds9160_set_ps_cancellation_level(data, val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_RAW: + if (val2 != 0) + return -EINVAL; + switch (chan->type) { + case IIO_CURRENT: + return apds9160_set_ps_current(data, val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static inline int apds9160_get_thres_reg(const struct iio_chan_spec *chan, + enum iio_event_direction dir, u8 *reg) +{ + switch (dir) { + case IIO_EV_DIR_RISING: + switch (chan->type) { + case IIO_PROXIMITY: + *reg = APDS9160_REG_PS_THRES_HI_LSB; + break; + case IIO_LIGHT: + *reg = APDS9160_REG_LS_THRES_UP_LSB; + break; + default: + return -EINVAL; + } break; + case IIO_EV_DIR_FALLING: + switch (chan->type) { + case IIO_PROXIMITY: + *reg = APDS9160_REG_PS_THRES_LO_LSB; + break; + case IIO_LIGHT: + *reg = APDS9160_REG_LS_THRES_LO_LSB; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int apds9160_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, int *val2) +{ + u8 reg; + int ret; + struct apds9160_chip *data = iio_priv(indio_dev); + + if (info != IIO_EV_INFO_VALUE) + return -EINVAL; + + ret = apds9160_get_thres_reg(chan, dir, ®); + if (ret < 0) + return ret; + + switch (chan->type) { + case IIO_PROXIMITY: { + __le16 buf; + + ret = regmap_bulk_read(data->regmap, reg, &buf, 2); + if (ret < 0) + return ret; + *val = le16_to_cpu(buf); + return IIO_VAL_INT; + } + case IIO_LIGHT: { + u8 buf[3]; + + ret = regmap_bulk_read(data->regmap, reg, &buf, 3); + if (ret < 0) + return ret; + *val = get_unaligned_le24(buf); + return IIO_VAL_INT; + } + default: + return -EINVAL; + } +} + +static int apds9160_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, int val2) +{ + u8 reg; + int ret = 0; + struct apds9160_chip *data = iio_priv(indio_dev); + + if (info != IIO_EV_INFO_VALUE) + return -EINVAL; + + ret = apds9160_get_thres_reg(chan, dir, ®); + if (ret < 0) + return ret; + + switch (chan->type) { + case IIO_PROXIMITY: { + __le16 buf; + + if (val < 0 || val > APDS9160_PS_THRES_MAX) + return -EINVAL; + + buf = cpu_to_le16(val); + return regmap_bulk_write(data->regmap, reg, &buf, 2); + } + case IIO_LIGHT: { + u8 buf[3]; + + if (val < 0 || val > APDS9160_LS_THRES_MAX) + return -EINVAL; + + put_unaligned_le24(val, buf); + return regmap_bulk_write(data->regmap, reg, &buf, 3); + } + default: + return -EINVAL; + } +} + +static int apds9160_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct apds9160_chip *data = iio_priv(indio_dev); + + switch (chan->type) { + case IIO_PROXIMITY: + return data->ps_int; + case IIO_LIGHT: + return data->als_int; + default: + return -EINVAL; + } +} + +static int apds9160_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct apds9160_chip *data = iio_priv(indio_dev); + int ret; + + switch (chan->type) { + case IIO_PROXIMITY: + ret = regmap_field_write(data->reg_int_ps, state); + if (ret) + return ret; + data->ps_int = state; + + return 0; + case IIO_LIGHT: + ret = regmap_field_write(data->reg_int_als, state); + if (ret) + return ret; + data->als_int = state; + + return 0; + default: + return -EINVAL; + } +} + +static irqreturn_t apds9160_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct apds9160_chip *data = iio_priv(indio_dev); + int ret, status; + + /* Reading status register clears the interrupt flag */ + ret = regmap_read(data->regmap, APDS9160_REG_SR, &status); + if (ret < 0) { + dev_err_ratelimited(&data->client->dev, + "irq status reg read failed\n"); + return IRQ_HANDLED; + } + + if ((status & APDS9160_SR_LS_INT) && + (status & APDS9160_SR_LS_NEW_DATA) && data->als_int) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + } + + if ((status & APDS9160_SR_PS_INT) && + (status & APDS9160_SR_PS_NEW_DATA) && data->ps_int) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + } + + return IRQ_HANDLED; +} + +static int apds9160_detect(struct apds9160_chip *chip) +{ + struct i2c_client *client = chip->client; + int ret; + u32 val; + + ret = regmap_read(chip->regmap, APDS9160_REG_ID, &val); + if (ret < 0) { + dev_err(&client->dev, "ID read failed\n"); + return ret; + } + + if (val != APDS9160_PART_ID_0) + dev_info(&client->dev, "Unknown part id %u\n", val); + + return 0; +} + +static void apds9160_disable(void *chip) +{ + struct apds9160_chip *data = chip; + int ret; + + ret = regmap_field_write(data->reg_enable_als, 0); + if (ret) + return; + + regmap_field_write(data->reg_enable_ps, 0); +} + +static int apds9160_chip_init(struct apds9160_chip *chip) +{ + int ret; + + /* Write default values to interrupt register */ + ret = regmap_field_write(chip->reg_int_ps, 0); + chip->ps_int = 0; + if (ret) + return ret; + + ret = regmap_field_write(chip->reg_int_als, 0); + chip->als_int = 0; + if (ret) + return ret; + + /* Write default values to control register */ + ret = regmap_field_write(chip->reg_enable_als, 1); + if (ret) + return ret; + + ret = regmap_field_write(chip->reg_enable_ps, 1); + if (ret) + return ret; + + /* Write other default values */ + ret = regmap_field_write(chip->reg_ps_resolution, + APDS9160_DEFAULT_PS_RESOLUTION_11BITS); + if (ret) + return ret; + + /* Write default values to configuration registers */ + ret = apds9160_set_ps_current(chip, APDS9160_DEFAULT_PS_CURRENT); + if (ret) + return ret; + + ret = apds9160_set_ps_rate(chip, APDS9160_DEFAULT_PS_RATE); + if (ret) + return ret; + + ret = apds9160_set_als_int_time(chip, APDS9160_DEFAULT_LS_RATE); + if (ret) + return ret; + + ret = apds9160_set_als_scale(chip, + apds9160_100ms_avail[0][0], + apds9160_100ms_avail[0][1]); + if (ret) + return ret; + + ret = apds9160_set_ps_gain(chip, APDS9160_DEFAULT_PS_GAIN); + if (ret) + return ret; + + ret = apds9160_set_ps_analog_cancellation( + chip, APDS9160_DEFAULT_PS_ANALOG_CANCELLATION); + if (ret) + return ret; + + ret = apds9160_set_ps_cancellation_level( + chip, APDS9160_DEFAULT_PS_CANCELLATION_LEVEL); + if (ret) + return ret; + + return devm_add_action_or_reset(&chip->client->dev, apds9160_disable, + chip); +} + +static int apds9160_regfield_init(struct apds9160_chip *data) +{ + struct device *dev = &data->client->dev; + struct regmap *regmap = data->regmap; + struct regmap_field *tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9160_reg_field_int_als); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + data->reg_int_als = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9160_reg_field_int_ps); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + data->reg_int_ps = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9160_reg_field_ls_en); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + data->reg_enable_als = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9160_reg_field_ps_en); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + data->reg_enable_ps = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, + apds9160_reg_field_ps_overflow); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + data->reg_ps_overflow = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9160_reg_field_als_rate); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + data->reg_als_rate = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9160_reg_field_als_res); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + data->reg_als_resolution = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9160_reg_field_ps_rate); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + data->reg_ps_rate = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9160_reg_field_als_gain); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + data->reg_als_gain = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, + apds9160_reg_field_ps_current); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + data->reg_ps_current = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9160_reg_field_ps_gain); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + data->reg_ps_gain = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, + apds9160_reg_field_ps_resolution); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + data->reg_ps_resolution = tmp; + + return 0; +} + +static const struct iio_info apds9160_info = { + .read_avail = apds9160_read_avail, + .read_raw = apds9160_read_raw, + .write_raw = apds9160_write_raw, + .write_raw_get_fmt = apds9160_write_raw_get_fmt, + .read_event_value = apds9160_read_event, + .write_event_value = apds9160_write_event, + .read_event_config = apds9160_read_event_config, + .write_event_config = apds9160_write_event_config, +}; + +static const struct iio_info apds9160_info_no_events = { + .read_avail = apds9160_read_avail, + .read_raw = apds9160_read_raw, + .write_raw = apds9160_write_raw, + .write_raw_get_fmt = apds9160_write_raw_get_fmt, +}; + +static int apds9160_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct apds9160_chip *chip; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable vdd supply\n"); + + indio_dev->name = "apds9160"; + indio_dev->modes = INDIO_DIRECT_MODE; + + chip = iio_priv(indio_dev); + chip->client = client; + chip->regmap = devm_regmap_init_i2c(client, &apds9160_regmap_config); + if (IS_ERR(chip->regmap)) + return dev_err_probe(dev, PTR_ERR(chip->regmap), + "regmap initialization failed.\n"); + + chip->client = client; + mutex_init(&chip->lock); + + ret = apds9160_detect(chip); + if (ret < 0) + return dev_err_probe(dev, ret, "apds9160 not found\n"); + + ret = apds9160_regfield_init(chip); + if (ret) + return ret; + + ret = apds9160_chip_init(chip); + if (ret) + return ret; + + ret = apds9160_ps_init_analog_cancellation(dev, chip); + if (ret) + return ret; + + if (client->irq > 0) { + indio_dev->info = &apds9160_info; + indio_dev->channels = apds9160_channels; + indio_dev->num_channels = ARRAY_SIZE(apds9160_channels); + ret = devm_request_threaded_irq(dev, client->irq, NULL, + apds9160_irq_handler, + IRQF_ONESHOT, "apds9160_event", + indio_dev); + if (ret) + return ret; + } else { + indio_dev->info = &apds9160_info_no_events; + indio_dev->channels = apds9160_channels_without_events; + indio_dev->num_channels = + ARRAY_SIZE(apds9160_channels_without_events); + } + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, + "failed iio device registration\n"); + + return ret; +} + +static const struct of_device_id apds9160_of_match[] = { + { .compatible = "brcm,apds9160" }, + { } +}; +MODULE_DEVICE_TABLE(of, apds9160_of_match); + +static const struct i2c_device_id apds9160_id[] = { + { .name = "apds9160" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, apds9160_id); + +static struct i2c_driver apds9160_driver = { + .driver = { + .name = "apds9160", + .of_match_table = apds9160_of_match, + }, + .probe = apds9160_probe, + .id_table = apds9160_id, +}; +module_i2c_driver(apds9160_driver); + +MODULE_DESCRIPTION("APDS9160 combined ALS and proximity sensor"); +MODULE_AUTHOR("Mikael Gonella-Bolduc "); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/apds9300.c b/drivers/iio/light/apds9300.c new file mode 100644 index 000000000..f0abbc4a8 --- /dev/null +++ b/drivers/iio/light/apds9300.c @@ -0,0 +1,514 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * apds9300.c - IIO driver for Avago APDS9300 ambient light sensor + * + * Copyright 2013 Oleksandr Kravchenko + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define APDS9300_DRV_NAME "apds9300" + +/* Command register bits */ +#define APDS9300_CMD BIT(7) /* Select command register. Must write as 1 */ +#define APDS9300_WORD BIT(5) /* I2C write/read: if 1 word, if 0 byte */ +#define APDS9300_CLEAR BIT(6) /* Interrupt clear. Clears pending interrupt */ + +/* Register set */ +#define APDS9300_CONTROL 0x00 /* Control of basic functions */ +#define APDS9300_THRESHLOWLOW 0x02 /* Low byte of low interrupt threshold */ +#define APDS9300_THRESHHIGHLOW 0x04 /* Low byte of high interrupt threshold */ +#define APDS9300_INTERRUPT 0x06 /* Interrupt control */ +#define APDS9300_DATA0LOW 0x0c /* Low byte of ADC channel 0 */ +#define APDS9300_DATA1LOW 0x0e /* Low byte of ADC channel 1 */ + +/* Power on/off value for APDS9300_CONTROL register */ +#define APDS9300_POWER_ON 0x03 +#define APDS9300_POWER_OFF 0x00 + +/* Interrupts */ +#define APDS9300_INTR_ENABLE 0x10 +/* Interrupt Persist Function: Any value outside of threshold range */ +#define APDS9300_THRESH_INTR 0x01 + +#define APDS9300_THRESH_MAX 0xffff /* Max threshold value */ + +struct apds9300_data { + struct i2c_client *client; + struct mutex mutex; + bool power_state; + int thresh_low; + int thresh_hi; + bool intr_en; +}; + +/* Lux calculation */ + +/* Calculated values 1000 * (CH1/CH0)^1.4 for CH1/CH0 from 0 to 0.52 */ +static const u16 apds9300_lux_ratio[] = { + 0, 2, 4, 7, 11, 15, 19, 24, 29, 34, 40, 45, 51, 57, 64, 70, 77, 84, 91, + 98, 105, 112, 120, 128, 136, 144, 152, 160, 168, 177, 185, 194, 203, + 212, 221, 230, 239, 249, 258, 268, 277, 287, 297, 307, 317, 327, 337, + 347, 358, 368, 379, 390, 400, +}; + +static unsigned long apds9300_calculate_lux(u16 ch0, u16 ch1) +{ + unsigned long lux, tmp; + + /* avoid division by zero */ + if (ch0 == 0) + return 0; + + tmp = DIV_ROUND_UP(ch1 * 100, ch0); + if (tmp <= 52) { + lux = 3150 * ch0 - (unsigned long)DIV_ROUND_UP_ULL(ch0 + * apds9300_lux_ratio[tmp] * 5930ull, 1000); + } else if (tmp <= 65) { + lux = 2290 * ch0 - 2910 * ch1; + } else if (tmp <= 80) { + lux = 1570 * ch0 - 1800 * ch1; + } else if (tmp <= 130) { + lux = 338 * ch0 - 260 * ch1; + } else { + lux = 0; + } + + return lux / 100000; +} + +static int apds9300_get_adc_val(struct apds9300_data *data, int adc_number) +{ + int ret; + u8 flags = APDS9300_CMD | APDS9300_WORD; + + if (!data->power_state) + return -EBUSY; + + /* Select ADC0 or ADC1 data register */ + flags |= adc_number ? APDS9300_DATA1LOW : APDS9300_DATA0LOW; + + ret = i2c_smbus_read_word_data(data->client, flags); + if (ret < 0) + dev_err(&data->client->dev, + "failed to read ADC%d value\n", adc_number); + + return ret; +} + +static int apds9300_set_thresh_low(struct apds9300_data *data, int value) +{ + int ret; + + if (!data->power_state) + return -EBUSY; + + if (value > APDS9300_THRESH_MAX) + return -EINVAL; + + ret = i2c_smbus_write_word_data(data->client, APDS9300_THRESHLOWLOW + | APDS9300_CMD | APDS9300_WORD, value); + if (ret) { + dev_err(&data->client->dev, "failed to set thresh_low\n"); + return ret; + } + data->thresh_low = value; + + return 0; +} + +static int apds9300_set_thresh_hi(struct apds9300_data *data, int value) +{ + int ret; + + if (!data->power_state) + return -EBUSY; + + if (value > APDS9300_THRESH_MAX) + return -EINVAL; + + ret = i2c_smbus_write_word_data(data->client, APDS9300_THRESHHIGHLOW + | APDS9300_CMD | APDS9300_WORD, value); + if (ret) { + dev_err(&data->client->dev, "failed to set thresh_hi\n"); + return ret; + } + data->thresh_hi = value; + + return 0; +} + +static int apds9300_set_intr_state(struct apds9300_data *data, bool state) +{ + int ret; + u8 cmd; + + if (!data->power_state) + return -EBUSY; + + cmd = state ? APDS9300_INTR_ENABLE | APDS9300_THRESH_INTR : 0x00; + ret = i2c_smbus_write_byte_data(data->client, + APDS9300_INTERRUPT | APDS9300_CMD, cmd); + if (ret) { + dev_err(&data->client->dev, + "failed to set interrupt state %d\n", state); + return ret; + } + data->intr_en = state; + + return 0; +} + +static int apds9300_set_power_state(struct apds9300_data *data, bool state) +{ + int ret; + u8 cmd; + + cmd = state ? APDS9300_POWER_ON : APDS9300_POWER_OFF; + ret = i2c_smbus_write_byte_data(data->client, + APDS9300_CONTROL | APDS9300_CMD, cmd); + if (ret) { + dev_err(&data->client->dev, + "failed to set power state %d\n", state); + return ret; + } + data->power_state = state; + + return 0; +} + +static void apds9300_clear_intr(struct apds9300_data *data) +{ + int ret; + + ret = i2c_smbus_write_byte(data->client, APDS9300_CLEAR | APDS9300_CMD); + if (ret < 0) + dev_err(&data->client->dev, "failed to clear interrupt\n"); +} + +static int apds9300_chip_init(struct apds9300_data *data) +{ + int ret; + + /* Need to set power off to ensure that the chip is off */ + ret = apds9300_set_power_state(data, 0); + if (ret < 0) + goto err; + /* + * Probe the chip. To do so we try to power up the device and then to + * read back the 0x03 code + */ + ret = apds9300_set_power_state(data, 1); + if (ret < 0) + goto err; + ret = i2c_smbus_read_byte_data(data->client, + APDS9300_CONTROL | APDS9300_CMD); + if (ret != APDS9300_POWER_ON) { + ret = -ENODEV; + goto err; + } + /* + * Disable interrupt to ensure thai it is doesn't enable + * i.e. after device soft reset + */ + ret = apds9300_set_intr_state(data, false); + if (ret < 0) + goto err; + + return 0; + +err: + dev_err(&data->client->dev, "failed to init the chip\n"); + return ret; +} + +static int apds9300_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, + long mask) +{ + int ch0, ch1, ret = -EINVAL; + struct apds9300_data *data = iio_priv(indio_dev); + + mutex_lock(&data->mutex); + switch (chan->type) { + case IIO_LIGHT: + ch0 = apds9300_get_adc_val(data, 0); + if (ch0 < 0) { + ret = ch0; + break; + } + ch1 = apds9300_get_adc_val(data, 1); + if (ch1 < 0) { + ret = ch1; + break; + } + *val = apds9300_calculate_lux(ch0, ch1); + ret = IIO_VAL_INT; + break; + case IIO_INTENSITY: + ret = apds9300_get_adc_val(data, chan->channel); + if (ret < 0) + break; + *val = ret; + ret = IIO_VAL_INT; + break; + default: + break; + } + mutex_unlock(&data->mutex); + + return ret; +} + +static int apds9300_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, + int *val, int *val2) +{ + struct apds9300_data *data = iio_priv(indio_dev); + + switch (dir) { + case IIO_EV_DIR_RISING: + *val = data->thresh_hi; + break; + case IIO_EV_DIR_FALLING: + *val = data->thresh_low; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; +} + +static int apds9300_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, int val, + int val2) +{ + struct apds9300_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + if (dir == IIO_EV_DIR_RISING) + ret = apds9300_set_thresh_hi(data, val); + else + ret = apds9300_set_thresh_low(data, val); + mutex_unlock(&data->mutex); + + return ret; +} + +static int apds9300_read_interrupt_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct apds9300_data *data = iio_priv(indio_dev); + + return data->intr_en; +} + +static int apds9300_write_interrupt_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct apds9300_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = apds9300_set_intr_state(data, state); + mutex_unlock(&data->mutex); + + return ret; +} + +static const struct iio_info apds9300_info_no_irq = { + .read_raw = apds9300_read_raw, +}; + +static const struct iio_info apds9300_info = { + .read_raw = apds9300_read_raw, + .read_event_value = apds9300_read_thresh, + .write_event_value = apds9300_write_thresh, + .read_event_config = apds9300_read_interrupt_config, + .write_event_config = apds9300_write_interrupt_config, +}; + +static const struct iio_event_spec apds9300_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec apds9300_channels[] = { + { + .type = IIO_LIGHT, + .channel = 0, + .indexed = true, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, { + .type = IIO_INTENSITY, + .channel = 0, + .channel2 = IIO_MOD_LIGHT_BOTH, + .indexed = true, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = apds9300_event_spec, + .num_event_specs = ARRAY_SIZE(apds9300_event_spec), + }, { + .type = IIO_INTENSITY, + .channel = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .indexed = true, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, +}; + +static irqreturn_t apds9300_interrupt_handler(int irq, void *private) +{ + struct iio_dev *dev_info = private; + struct apds9300_data *data = iio_priv(dev_info); + + iio_push_event(dev_info, + IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(dev_info)); + + apds9300_clear_intr(data); + + return IRQ_HANDLED; +} + +static int apds9300_probe(struct i2c_client *client) +{ + struct apds9300_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + ret = apds9300_chip_init(data); + if (ret < 0) + goto err; + + mutex_init(&data->mutex); + + indio_dev->channels = apds9300_channels; + indio_dev->num_channels = ARRAY_SIZE(apds9300_channels); + indio_dev->name = APDS9300_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (client->irq) + indio_dev->info = &apds9300_info; + else + indio_dev->info = &apds9300_info_no_irq; + + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, apds9300_interrupt_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "apds9300_event", indio_dev); + if (ret) + goto err; + } + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto err; + + return 0; + +err: + /* Ensure that power off in case of error */ + apds9300_set_power_state(data, 0); + return ret; +} + +static void apds9300_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct apds9300_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + /* Ensure that power off and interrupts are disabled */ + apds9300_set_intr_state(data, false); + apds9300_set_power_state(data, false); +} + +static int apds9300_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct apds9300_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = apds9300_set_power_state(data, false); + mutex_unlock(&data->mutex); + + return ret; +} + +static int apds9300_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct apds9300_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = apds9300_set_power_state(data, true); + mutex_unlock(&data->mutex); + + return ret; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(apds9300_pm_ops, apds9300_suspend, + apds9300_resume); + +static const struct i2c_device_id apds9300_id[] = { + { .name = APDS9300_DRV_NAME }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, apds9300_id); + +static struct i2c_driver apds9300_driver = { + .driver = { + .name = APDS9300_DRV_NAME, + .pm = pm_sleep_ptr(&apds9300_pm_ops), + }, + .probe = apds9300_probe, + .remove = apds9300_remove, + .id_table = apds9300_id, +}; + +module_i2c_driver(apds9300_driver); + +MODULE_AUTHOR("Kravchenko Oleksandr "); +MODULE_AUTHOR("GlobalLogic inc."); +MODULE_DESCRIPTION("APDS9300 ambient light photo sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/apds9306.c b/drivers/iio/light/apds9306.c new file mode 100644 index 000000000..f681b02e4 --- /dev/null +++ b/drivers/iio/light/apds9306.c @@ -0,0 +1,1354 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * APDS-9306/APDS-9306-065 Ambient Light Sensor + * I2C Address: 0x52 + * Datasheet: https://docs.broadcom.com/doc/AV02-4755EN + * + * Copyright (C) 2024 Subhajit Ghosh + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define APDS9306_MAIN_CTRL_REG 0x00 +#define APDS9306_ALS_MEAS_RATE_REG 0x04 +#define APDS9306_ALS_GAIN_REG 0x05 +#define APDS9306_PART_ID_REG 0x06 +#define APDS9306_MAIN_STATUS_REG 0x07 +#define APDS9306_CLEAR_DATA_0_REG 0x0A +#define APDS9306_CLEAR_DATA_1_REG 0x0B +#define APDS9306_CLEAR_DATA_2_REG 0x0C +#define APDS9306_ALS_DATA_0_REG 0x0D +#define APDS9306_ALS_DATA_1_REG 0x0E +#define APDS9306_ALS_DATA_2_REG 0x0F +#define APDS9306_INT_CFG_REG 0x19 +#define APDS9306_INT_PERSISTENCE_REG 0x1A +#define APDS9306_ALS_THRES_UP_0_REG 0x21 +#define APDS9306_ALS_THRES_UP_1_REG 0x22 +#define APDS9306_ALS_THRES_UP_2_REG 0x23 +#define APDS9306_ALS_THRES_LOW_0_REG 0x24 +#define APDS9306_ALS_THRES_LOW_1_REG 0x25 +#define APDS9306_ALS_THRES_LOW_2_REG 0x26 +#define APDS9306_ALS_THRES_VAR_REG 0x27 + +#define APDS9306_ALS_INT_STAT_MASK BIT(4) +#define APDS9306_ALS_DATA_STAT_MASK BIT(3) + +#define APDS9306_ALS_THRES_VAL_MAX (BIT(20) - 1) +#define APDS9306_ALS_THRES_VAR_NUM_VALS 8 +#define APDS9306_ALS_PERSIST_NUM_VALS 16 +#define APDS9306_ALS_READ_DATA_DELAY_US (20 * USEC_PER_MSEC) +#define APDS9306_NUM_REPEAT_RATES 7 +#define APDS9306_INT_SRC_CLEAR 0 +#define APDS9306_INT_SRC_ALS 1 +#define APDS9306_SAMP_FREQ_10HZ 0 + +/** + * struct part_id_gts_multiplier - Part no. and corresponding gts multiplier + * + * GTS (Gain Time Scale) are helper functions for Light sensors which along + * with hardware gains also has gains associated with Integration times. + * + * There are two variants of the device with slightly different characteristics, + * they have same ADC count for different Lux levels as mentioned in the + * datasheet. This multiplier array is used to store the derived Lux per count + * value for the two variants to be used by the GTS helper functions. + * + * @part_id: Part ID of the device + * @max_scale_int: Multiplier for iio_init_iio_gts() + * @max_scale_nano: Multiplier for iio_init_iio_gts() + */ +struct part_id_gts_multiplier { + int part_id; + int max_scale_int; + int max_scale_nano; +}; + +/* + * As per the datasheet, at HW Gain = 3x, Integration time 100mS (32x), + * typical 2000 ADC counts are observed for 49.8 uW per sq cm (340.134 lux) + * for apds9306 and 43 uW per sq cm (293.69 lux) for apds9306-065. + * Assuming lux per count is linear across all integration time ranges. + * + * Lux = (raw + offset) * scale; offset can be any value by userspace. + * HG = Hardware Gain; ITG = Gain by changing integration time. + * Scale table by IIO GTS Helpers = (1 / HG) * (1 / ITG) * Multiplier. + * + * The Lux values provided in the datasheet are at ITG=32x and HG=3x, + * at typical 2000 count for both variants of the device. + * + * Lux per ADC count at 3x and 32x for apds9306 = 340.134 / 2000 + * Lux per ADC count at 3x and 32x for apds9306-065 = 293.69 / 2000 + * + * The Multiplier for the scale table provided to userspace: + * IIO GTS scale Multiplier for apds9306 = (340.134 / 2000) * 32 * 3 = 16.326432 + * and for apds9306-065 = (293.69 / 2000) * 32 * 3 = 14.09712 + */ +static const struct part_id_gts_multiplier apds9306_gts_mul[] = { + { + .part_id = 0xB1, + .max_scale_int = 16, + .max_scale_nano = 326432000, + }, { + .part_id = 0xB3, + .max_scale_int = 14, + .max_scale_nano = 97120000, + }, +}; + +static const int apds9306_repeat_rate_freq[APDS9306_NUM_REPEAT_RATES][2] = { + { 40, 0 }, + { 20, 0 }, + { 10, 0 }, + { 5, 0 }, + { 2, 0 }, + { 1, 0 }, + { 0, 500000 }, +}; + +static const int apds9306_repeat_rate_period[APDS9306_NUM_REPEAT_RATES] = { + 25000, 50000, 100000, 200000, 500000, 1000000, 2000000, +}; + +/** + * struct apds9306_regfields - apds9306 regmap fields definitions + * + * @sw_reset: Software reset regfield + * @en: Enable regfield + * @intg_time: Resolution regfield + * @repeat_rate: Measurement Rate regfield + * @gain: Hardware gain regfield + * @int_src: Interrupt channel regfield + * @int_thresh_var_en: Interrupt variance threshold regfield + * @int_en: Interrupt enable regfield + * @int_persist_val: Interrupt persistence regfield + * @int_thresh_var_val: Interrupt threshold variance value regfield + */ +struct apds9306_regfields { + struct regmap_field *sw_reset; + struct regmap_field *en; + struct regmap_field *intg_time; + struct regmap_field *repeat_rate; + struct regmap_field *gain; + struct regmap_field *int_src; + struct regmap_field *int_thresh_var_en; + struct regmap_field *int_en; + struct regmap_field *int_persist_val; + struct regmap_field *int_thresh_var_val; +}; + +/** + * struct apds9306_data - apds9306 private data and registers definitions + * + * @dev: Pointer to the device structure + * @gts: IIO Gain Time Scale structure + * @mutex: Lock for protecting adc reads, device settings changes where + * some calculations are required before or after setting or + * getting the raw settings values from regmap writes or reads + * respectively. + * @regmap: Regmap structure pointer + * @rf: Regmap register fields structure + * @read_data_available: Flag set by IRQ handler for ADC data available + */ +struct apds9306_data { + struct device *dev; + struct iio_gts gts; + + struct mutex mutex; + + struct regmap *regmap; + struct apds9306_regfields rf; + + int read_data_available; +}; + +/* + * Available scales with gain 1x - 18x, timings 3.125, 25, 50, 100, 200, 400 ms + * Time impacts to gain: 1x, 8x, 16x, 32x, 64x, 128x + */ +#define APDS9306_GSEL_1X 0x00 +#define APDS9306_GSEL_3X 0x01 +#define APDS9306_GSEL_6X 0x02 +#define APDS9306_GSEL_9X 0x03 +#define APDS9306_GSEL_18X 0x04 + +static const struct iio_gain_sel_pair apds9306_gains[] = { + GAIN_SCALE_GAIN(1, APDS9306_GSEL_1X), + GAIN_SCALE_GAIN(3, APDS9306_GSEL_3X), + GAIN_SCALE_GAIN(6, APDS9306_GSEL_6X), + GAIN_SCALE_GAIN(9, APDS9306_GSEL_9X), + GAIN_SCALE_GAIN(18, APDS9306_GSEL_18X), +}; + +#define APDS9306_MEAS_MODE_400MS 0x00 +#define APDS9306_MEAS_MODE_200MS 0x01 +#define APDS9306_MEAS_MODE_100MS 0x02 +#define APDS9306_MEAS_MODE_50MS 0x03 +#define APDS9306_MEAS_MODE_25MS 0x04 +#define APDS9306_MEAS_MODE_3125US 0x05 + +static const struct iio_itime_sel_mul apds9306_itimes[] = { + GAIN_SCALE_ITIME_US(400000, APDS9306_MEAS_MODE_400MS, BIT(7)), + GAIN_SCALE_ITIME_US(200000, APDS9306_MEAS_MODE_200MS, BIT(6)), + GAIN_SCALE_ITIME_US(100000, APDS9306_MEAS_MODE_100MS, BIT(5)), + GAIN_SCALE_ITIME_US(50000, APDS9306_MEAS_MODE_50MS, BIT(4)), + GAIN_SCALE_ITIME_US(25000, APDS9306_MEAS_MODE_25MS, BIT(3)), + GAIN_SCALE_ITIME_US(3125, APDS9306_MEAS_MODE_3125US, BIT(0)), +}; + +static const struct iio_event_spec apds9306_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_shared_by_all = BIT(IIO_EV_INFO_PERIOD), + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH_ADAPTIVE, + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec apds9306_channels_with_events[] = { + { + .type = IIO_LIGHT, + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE), + .event_spec = apds9306_event_spec, + .num_event_specs = ARRAY_SIZE(apds9306_event_spec), + }, { + .type = IIO_INTENSITY, + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .channel2 = IIO_MOD_LIGHT_CLEAR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .modified = 1, + .event_spec = apds9306_event_spec, + .num_event_specs = ARRAY_SIZE(apds9306_event_spec), + }, +}; + +static const struct iio_chan_spec apds9306_channels_without_events[] = { + { + .type = IIO_LIGHT, + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE), + }, { + .type = IIO_INTENSITY, + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .modified = 1, + .channel2 = IIO_MOD_LIGHT_CLEAR, + }, +}; + +/* INT_PERSISTENCE available */ +static IIO_CONST_ATTR(thresh_either_period_available, "[0 1 15]"); + +/* ALS_THRESH_VAR available */ +static IIO_CONST_ATTR(thresh_adaptive_either_values_available, "[0 1 7]"); + +static struct attribute *apds9306_event_attributes[] = { + &iio_const_attr_thresh_either_period_available.dev_attr.attr, + &iio_const_attr_thresh_adaptive_either_values_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group apds9306_event_attr_group = { + .attrs = apds9306_event_attributes, +}; + +static const struct regmap_range apds9306_readable_ranges[] = { + regmap_reg_range(APDS9306_MAIN_CTRL_REG, APDS9306_ALS_THRES_VAR_REG) +}; + +static const struct regmap_range apds9306_writable_ranges[] = { + regmap_reg_range(APDS9306_MAIN_CTRL_REG, APDS9306_ALS_GAIN_REG), + regmap_reg_range(APDS9306_INT_CFG_REG, APDS9306_ALS_THRES_VAR_REG) +}; + +static const struct regmap_range apds9306_volatile_ranges[] = { + regmap_reg_range(APDS9306_MAIN_STATUS_REG, APDS9306_MAIN_STATUS_REG), + regmap_reg_range(APDS9306_CLEAR_DATA_0_REG, APDS9306_ALS_DATA_2_REG) +}; + +static const struct regmap_range apds9306_precious_ranges[] = { + regmap_reg_range(APDS9306_MAIN_STATUS_REG, APDS9306_MAIN_STATUS_REG) +}; + +static const struct regmap_access_table apds9306_readable_table = { + .yes_ranges = apds9306_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9306_readable_ranges) +}; + +static const struct regmap_access_table apds9306_writable_table = { + .yes_ranges = apds9306_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9306_writable_ranges) +}; + +static const struct regmap_access_table apds9306_volatile_table = { + .yes_ranges = apds9306_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9306_volatile_ranges) +}; + +static const struct regmap_access_table apds9306_precious_table = { + .yes_ranges = apds9306_precious_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9306_precious_ranges) +}; + +static const struct regmap_config apds9306_regmap = { + .name = "apds9306_regmap", + .reg_bits = 8, + .val_bits = 8, + .rd_table = &apds9306_readable_table, + .wr_table = &apds9306_writable_table, + .volatile_table = &apds9306_volatile_table, + .precious_table = &apds9306_precious_table, + .max_register = APDS9306_ALS_THRES_VAR_REG, + .cache_type = REGCACHE_MAPLE, +}; + +static const struct reg_field apds9306_rf_sw_reset = + REG_FIELD(APDS9306_MAIN_CTRL_REG, 4, 4); + +static const struct reg_field apds9306_rf_en = + REG_FIELD(APDS9306_MAIN_CTRL_REG, 1, 1); + +static const struct reg_field apds9306_rf_intg_time = + REG_FIELD(APDS9306_ALS_MEAS_RATE_REG, 4, 6); + +static const struct reg_field apds9306_rf_repeat_rate = + REG_FIELD(APDS9306_ALS_MEAS_RATE_REG, 0, 2); + +static const struct reg_field apds9306_rf_gain = + REG_FIELD(APDS9306_ALS_GAIN_REG, 0, 2); + +static const struct reg_field apds9306_rf_int_src = + REG_FIELD(APDS9306_INT_CFG_REG, 4, 5); + +static const struct reg_field apds9306_rf_int_thresh_var_en = + REG_FIELD(APDS9306_INT_CFG_REG, 3, 3); + +static const struct reg_field apds9306_rf_int_en = + REG_FIELD(APDS9306_INT_CFG_REG, 2, 2); + +static const struct reg_field apds9306_rf_int_persist_val = + REG_FIELD(APDS9306_INT_PERSISTENCE_REG, 4, 7); + +static const struct reg_field apds9306_rf_int_thresh_var_val = + REG_FIELD(APDS9306_ALS_THRES_VAR_REG, 0, 2); + +static int apds9306_regfield_init(struct apds9306_data *data) +{ + struct device *dev = data->dev; + struct regmap *regmap = data->regmap; + struct regmap_field *tmp; + struct apds9306_regfields *rf = &data->rf; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_sw_reset); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->sw_reset = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_en); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->en = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_intg_time); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->intg_time = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_repeat_rate); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->repeat_rate = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_gain); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->gain = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_int_src); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->int_src = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_int_thresh_var_en); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->int_thresh_var_en = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_int_en); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->int_en = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_int_persist_val); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->int_persist_val = tmp; + + tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_int_thresh_var_val); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + rf->int_thresh_var_val = tmp; + + return 0; +} + +static int apds9306_power_state(struct apds9306_data *data, bool state) +{ + struct apds9306_regfields *rf = &data->rf; + int ret; + + /* Reset not included as it causes ugly I2C bus error */ + if (state) { + ret = regmap_field_write(rf->en, 1); + if (ret) + return ret; + /* 5ms wake up time */ + fsleep(5000); + return 0; + } + + return regmap_field_write(rf->en, 0); +} + +static int apds9306_read_data(struct apds9306_data *data, int *val, int reg) +{ + struct device *dev = data->dev; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct apds9306_regfields *rf = &data->rf; + u64 ev_code; + int ret, delay, intg_time, intg_time_idx, repeat_rate_idx, int_src; + int status = 0; + u8 buff[3]; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(data->dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return PM_RUNTIME_ACQUIRE_ERR(&pm); + + ret = regmap_field_read(rf->intg_time, &intg_time_idx); + if (ret) + return ret; + + ret = regmap_field_read(rf->repeat_rate, &repeat_rate_idx); + if (ret) + return ret; + + ret = regmap_field_read(rf->int_src, &int_src); + if (ret) + return ret; + + intg_time = iio_gts_find_int_time_by_sel(&data->gts, intg_time_idx); + if (intg_time < 0) + return intg_time; + + /* Whichever is greater - integration time period or sampling period. */ + delay = max(intg_time, apds9306_repeat_rate_period[repeat_rate_idx]); + + /* + * Clear stale data flag that might have been set by the interrupt + * handler if it got data available flag set in the status reg. + */ + data->read_data_available = 0; + + /* + * If this function runs parallel with the interrupt handler, either + * this reads and clears the status registers or the interrupt handler + * does. The interrupt handler sets a flag for read data available + * in our private structure which we read here. + */ + ret = regmap_read_poll_timeout(data->regmap, APDS9306_MAIN_STATUS_REG, + status, data->read_data_available || + (status & (APDS9306_ALS_DATA_STAT_MASK | + APDS9306_ALS_INT_STAT_MASK)), + APDS9306_ALS_READ_DATA_DELAY_US, delay * 2); + if (ret) + return ret; + + /* If we reach here before the interrupt handler we push an event */ + if ((status & APDS9306_ALS_INT_STAT_MASK)) { + if (int_src == APDS9306_INT_SRC_ALS) + ev_code = IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER); + else + ev_code = IIO_MOD_EVENT_CODE(IIO_INTENSITY, 0, + IIO_MOD_LIGHT_CLEAR, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER); + + iio_push_event(indio_dev, ev_code, iio_get_time_ns(indio_dev)); + } + + ret = regmap_bulk_read(data->regmap, reg, buff, sizeof(buff)); + if (ret) { + dev_err_ratelimited(dev, "read data failed\n"); + return ret; + } + + *val = get_unaligned_le24(&buff); + + return 0; +} + +static int apds9306_intg_time_get(struct apds9306_data *data, int *val2) +{ + struct apds9306_regfields *rf = &data->rf; + int ret, intg_time_idx; + + ret = regmap_field_read(rf->intg_time, &intg_time_idx); + if (ret) + return ret; + + ret = iio_gts_find_int_time_by_sel(&data->gts, intg_time_idx); + if (ret < 0) + return ret; + + *val2 = ret; + + return 0; +} + +static int apds9306_intg_time_set(struct apds9306_data *data, int val2) +{ + struct device *dev = data->dev; + struct apds9306_regfields *rf = &data->rf; + int ret, intg_old, gain_old, gain_new, gain_new_closest, intg_time_idx; + int gain_idx; + bool ok; + + if (!iio_gts_valid_time(&data->gts, val2)) { + dev_err_ratelimited(dev, "Unsupported integration time %u\n", val2); + return -EINVAL; + } + + ret = regmap_field_read(rf->intg_time, &intg_time_idx); + if (ret) + return ret; + + ret = regmap_field_read(rf->gain, &gain_idx); + if (ret) + return ret; + + intg_old = iio_gts_find_int_time_by_sel(&data->gts, intg_time_idx); + if (intg_old < 0) + return intg_old; + + if (intg_old == val2) + return 0; + + gain_old = iio_gts_find_gain_by_sel(&data->gts, gain_idx); + if (gain_old < 0) + return gain_old; + + iio_gts_find_new_gain_by_old_gain_time(&data->gts, gain_old, intg_old, + val2, &gain_new); + + if (gain_new < 0) { + dev_err_ratelimited(dev, "Unsupported gain with time\n"); + return gain_new; + } + + gain_new_closest = iio_find_closest_gain_low(&data->gts, gain_new, &ok); + if (gain_new_closest < 0) { + gain_new_closest = iio_gts_get_min_gain(&data->gts); + if (gain_new_closest < 0) + return gain_new_closest; + } + if (!ok) + dev_dbg(dev, "Unable to find optimum gain, setting minimum"); + + ret = iio_gts_find_sel_by_int_time(&data->gts, val2); + if (ret < 0) + return ret; + + ret = regmap_field_write(rf->intg_time, ret); + if (ret) + return ret; + + ret = iio_gts_find_sel_by_gain(&data->gts, gain_new_closest); + if (ret < 0) + return ret; + + return regmap_field_write(rf->gain, ret); +} + +static int apds9306_sampling_freq_get(struct apds9306_data *data, int *val, + int *val2) +{ + struct apds9306_regfields *rf = &data->rf; + int ret, repeat_rate_idx; + + ret = regmap_field_read(rf->repeat_rate, &repeat_rate_idx); + if (ret) + return ret; + + if (repeat_rate_idx >= ARRAY_SIZE(apds9306_repeat_rate_freq)) + return -EINVAL; + + *val = apds9306_repeat_rate_freq[repeat_rate_idx][0]; + *val2 = apds9306_repeat_rate_freq[repeat_rate_idx][1]; + + return 0; +} + +static int apds9306_sampling_freq_set(struct apds9306_data *data, int val, + int val2) +{ + struct apds9306_regfields *rf = &data->rf; + int i; + + for (i = 0; i < ARRAY_SIZE(apds9306_repeat_rate_freq); i++) { + if (apds9306_repeat_rate_freq[i][0] == val && + apds9306_repeat_rate_freq[i][1] == val2) + return regmap_field_write(rf->repeat_rate, i); + } + + return -EINVAL; +} + +static int apds9306_scale_get(struct apds9306_data *data, int *val, int *val2) +{ + struct apds9306_regfields *rf = &data->rf; + int gain, intg, ret, intg_time_idx, gain_idx; + + ret = regmap_field_read(rf->gain, &gain_idx); + if (ret) + return ret; + + ret = regmap_field_read(rf->intg_time, &intg_time_idx); + if (ret) + return ret; + + gain = iio_gts_find_gain_by_sel(&data->gts, gain_idx); + if (gain < 0) + return gain; + + intg = iio_gts_find_int_time_by_sel(&data->gts, intg_time_idx); + if (intg < 0) + return intg; + + return iio_gts_get_scale(&data->gts, gain, intg, val, val2); +} + +static int apds9306_scale_set(struct apds9306_data *data, int val, int val2) +{ + struct apds9306_regfields *rf = &data->rf; + int i, ret, time_sel, gain_sel, intg_time_idx; + + ret = regmap_field_read(rf->intg_time, &intg_time_idx); + if (ret) + return ret; + + ret = iio_gts_find_gain_sel_for_scale_using_time(&data->gts, + intg_time_idx, val, val2, &gain_sel); + if (ret) { + for (i = 0; i < data->gts.num_itime; i++) { + time_sel = data->gts.itime_table[i].sel; + + if (time_sel == intg_time_idx) + continue; + + ret = iio_gts_find_gain_sel_for_scale_using_time(&data->gts, + time_sel, val, val2, &gain_sel); + if (!ret) + break; + } + if (ret) + return -EINVAL; + + ret = regmap_field_write(rf->intg_time, time_sel); + if (ret) + return ret; + } + + return regmap_field_write(rf->gain, gain_sel); +} + +static int apds9306_event_period_get(struct apds9306_data *data, int *val) +{ + struct apds9306_regfields *rf = &data->rf; + int period, ret; + + ret = regmap_field_read(rf->int_persist_val, &period); + if (ret) + return ret; + + if (!in_range(period, 0, APDS9306_ALS_PERSIST_NUM_VALS)) + return -EINVAL; + + *val = period; + + return ret; +} + +static int apds9306_event_period_set(struct apds9306_data *data, int val) +{ + struct apds9306_regfields *rf = &data->rf; + + if (!in_range(val, 0, APDS9306_ALS_PERSIST_NUM_VALS)) + return -EINVAL; + + return regmap_field_write(rf->int_persist_val, val); +} + +static int apds9306_get_thresh_reg(int dir) +{ + if (dir == IIO_EV_DIR_RISING) + return APDS9306_ALS_THRES_UP_0_REG; + else if (dir == IIO_EV_DIR_FALLING) + return APDS9306_ALS_THRES_LOW_0_REG; + else + return -EINVAL; +} + +static int apds9306_event_thresh_get(struct apds9306_data *data, int dir, + int *val) +{ + int reg, ret; + u8 buff[3]; + + reg = apds9306_get_thresh_reg(dir); + if (reg < 0) + return reg; + + ret = regmap_bulk_read(data->regmap, reg, buff, sizeof(buff)); + if (ret) + return ret; + + *val = get_unaligned_le24(&buff); + + return 0; +} + +static int apds9306_event_thresh_set(struct apds9306_data *data, int dir, + int val) +{ + int reg; + u8 buff[3]; + + reg = apds9306_get_thresh_reg(dir); + if (reg < 0) + return reg; + + if (!in_range(val, 0, APDS9306_ALS_THRES_VAL_MAX)) + return -EINVAL; + + put_unaligned_le24(val, buff); + + return regmap_bulk_write(data->regmap, reg, buff, sizeof(buff)); +} + +static int apds9306_event_thresh_adaptive_get(struct apds9306_data *data, int *val) +{ + struct apds9306_regfields *rf = &data->rf; + int thr_adpt, ret; + + ret = regmap_field_read(rf->int_thresh_var_val, &thr_adpt); + if (ret) + return ret; + + if (!in_range(thr_adpt, 0, APDS9306_ALS_THRES_VAR_NUM_VALS)) + return -EINVAL; + + *val = thr_adpt; + + return ret; +} + +static int apds9306_event_thresh_adaptive_set(struct apds9306_data *data, int val) +{ + struct apds9306_regfields *rf = &data->rf; + + if (!in_range(val, 0, APDS9306_ALS_THRES_VAR_NUM_VALS)) + return -EINVAL; + + return regmap_field_write(rf->int_thresh_var_val, val); +} + +static int apds9306_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct apds9306_data *data = iio_priv(indio_dev); + int ret, reg; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->channel2 == IIO_MOD_LIGHT_CLEAR) + reg = APDS9306_CLEAR_DATA_0_REG; + else + reg = APDS9306_ALS_DATA_0_REG; + /* + * Changing device parameters during adc operation, resets + * the ADC which has to avoided. + */ + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = apds9306_read_data(data, val, reg); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + ret = apds9306_intg_time_get(data, val2); + if (ret) + return ret; + *val = 0; + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = apds9306_sampling_freq_get(data, val, val2); + if (ret) + return ret; + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SCALE: + ret = apds9306_scale_get(data, val, val2); + if (ret) + return ret; + + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +}; + +static int apds9306_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct apds9306_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return iio_gts_avail_times(&data->gts, vals, type, length); + case IIO_CHAN_INFO_SCALE: + return iio_gts_all_avail_scales(&data->gts, vals, type, length); + case IIO_CHAN_INFO_SAMP_FREQ: + *length = ARRAY_SIZE(apds9306_repeat_rate_freq) * 2; + *vals = (const int *)apds9306_repeat_rate_freq; + *type = IIO_VAL_INT_PLUS_MICRO; + + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int apds9306_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_INT_TIME: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int apds9306_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct apds9306_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->mutex); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val) + return -EINVAL; + return apds9306_intg_time_set(data, val2); + case IIO_CHAN_INFO_SCALE: + return apds9306_scale_set(data, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return apds9306_sampling_freq_set(data, val, val2); + default: + return -EINVAL; + } +} + +static irqreturn_t apds9306_irq_handler(int irq, void *priv) +{ + struct iio_dev *indio_dev = priv; + struct apds9306_data *data = iio_priv(indio_dev); + struct apds9306_regfields *rf = &data->rf; + u64 ev_code; + int ret, status, int_src; + + /* + * The interrupt line is released and the interrupt flag is + * cleared as a result of reading the status register. All the + * status flags are cleared as a result of this read. + */ + ret = regmap_read(data->regmap, APDS9306_MAIN_STATUS_REG, &status); + if (ret < 0) { + dev_err_ratelimited(data->dev, "status reg read failed\n"); + return IRQ_HANDLED; + } + + ret = regmap_field_read(rf->int_src, &int_src); + if (ret) + return ret; + + if ((status & APDS9306_ALS_INT_STAT_MASK)) { + if (int_src == APDS9306_INT_SRC_ALS) + ev_code = IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER); + else + ev_code = IIO_MOD_EVENT_CODE(IIO_INTENSITY, 0, + IIO_MOD_LIGHT_CLEAR, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER); + + iio_push_event(indio_dev, ev_code, iio_get_time_ns(indio_dev)); + } + + /* + * If a one-shot read through sysfs is underway at the same time + * as this interrupt handler is executing and a read data available + * flag was set, this flag is set to inform read_poll_timeout() + * to exit. + */ + if ((status & APDS9306_ALS_DATA_STAT_MASK)) + data->read_data_available = 1; + + return IRQ_HANDLED; +} + +static int apds9306_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct apds9306_data *data = iio_priv(indio_dev); + int ret; + + switch (type) { + case IIO_EV_TYPE_THRESH: + if (dir == IIO_EV_DIR_EITHER && info == IIO_EV_INFO_PERIOD) + ret = apds9306_event_period_get(data, val); + else + ret = apds9306_event_thresh_get(data, dir, val); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_EV_TYPE_THRESH_ADAPTIVE: + ret = apds9306_event_thresh_adaptive_get(data, val); + if (ret) + return ret; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int apds9306_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct apds9306_data *data = iio_priv(indio_dev); + + switch (type) { + case IIO_EV_TYPE_THRESH: + if (dir == IIO_EV_DIR_EITHER && info == IIO_EV_INFO_PERIOD) + return apds9306_event_period_set(data, val); + + return apds9306_event_thresh_set(data, dir, val); + case IIO_EV_TYPE_THRESH_ADAPTIVE: + return apds9306_event_thresh_adaptive_set(data, val); + default: + return -EINVAL; + } +} + +static int apds9306_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct apds9306_data *data = iio_priv(indio_dev); + struct apds9306_regfields *rf = &data->rf; + int int_en, int_src, ret; + + switch (type) { + case IIO_EV_TYPE_THRESH: { + guard(mutex)(&data->mutex); + + ret = regmap_field_read(rf->int_src, &int_src); + if (ret) + return ret; + + ret = regmap_field_read(rf->int_en, &int_en); + if (ret) + return ret; + + if (chan->type == IIO_LIGHT) + return int_en && (int_src == APDS9306_INT_SRC_ALS); + + if (chan->type == IIO_INTENSITY) + return int_en && (int_src == APDS9306_INT_SRC_CLEAR); + + return -EINVAL; + } + case IIO_EV_TYPE_THRESH_ADAPTIVE: + ret = regmap_field_read(rf->int_thresh_var_en, &int_en); + if (ret) + return ret; + + return int_en; + default: + return -EINVAL; + } +} + +static int apds9306_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct apds9306_data *data = iio_priv(indio_dev); + struct apds9306_regfields *rf = &data->rf; + int ret, enabled; + + switch (type) { + case IIO_EV_TYPE_THRESH: { + guard(mutex)(&data->mutex); + + ret = regmap_field_read(rf->int_en, &enabled); + if (ret) + return ret; + + /* + * If interrupt is enabled, the channel is set before enabling + * the interrupt. In case of disable, no need to switch + * channels. In case of different channel is selected while + * interrupt in on, just change the channel. + */ + if (state) { + if (chan->type == IIO_LIGHT) + ret = regmap_field_write(rf->int_src, 1); + else if (chan->type == IIO_INTENSITY) + ret = regmap_field_write(rf->int_src, 0); + else + return -EINVAL; + + if (ret) + return ret; + + if (enabled) + return 0; + + ret = regmap_field_write(rf->int_en, 1); + if (ret) + return ret; + + return pm_runtime_resume_and_get(data->dev); + } else { + if (!enabled) + return 0; + + ret = regmap_field_write(rf->int_en, 0); + if (ret) + return ret; + + pm_runtime_put_autosuspend(data->dev); + + return 0; + } + } + case IIO_EV_TYPE_THRESH_ADAPTIVE: + return regmap_field_write(rf->int_thresh_var_en, state); + default: + return -EINVAL; + } +} + +static const struct iio_info apds9306_info_no_events = { + .read_avail = apds9306_read_avail, + .read_raw = apds9306_read_raw, + .write_raw = apds9306_write_raw, + .write_raw_get_fmt = apds9306_write_raw_get_fmt, +}; + +static const struct iio_info apds9306_info = { + .read_avail = apds9306_read_avail, + .read_raw = apds9306_read_raw, + .write_raw = apds9306_write_raw, + .write_raw_get_fmt = apds9306_write_raw_get_fmt, + .read_event_value = apds9306_read_event, + .write_event_value = apds9306_write_event, + .read_event_config = apds9306_read_event_config, + .write_event_config = apds9306_write_event_config, + .event_attrs = &apds9306_event_attr_group, +}; + +static int apds9306_init_iio_gts(struct apds9306_data *data) +{ + int i, ret, part_id; + + ret = regmap_read(data->regmap, APDS9306_PART_ID_REG, &part_id); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(apds9306_gts_mul); i++) + if (part_id == apds9306_gts_mul[i].part_id) + break; + + if (i == ARRAY_SIZE(apds9306_gts_mul)) + return -ENOENT; + + return devm_iio_init_iio_gts(data->dev, + apds9306_gts_mul[i].max_scale_int, + apds9306_gts_mul[i].max_scale_nano, + apds9306_gains, ARRAY_SIZE(apds9306_gains), + apds9306_itimes, ARRAY_SIZE(apds9306_itimes), + &data->gts); +} + +static void apds9306_powerdown(void *ptr) +{ + struct apds9306_data *data = ptr; + struct apds9306_regfields *rf = &data->rf; + int ret; + + ret = regmap_field_write(rf->int_thresh_var_en, 0); + if (ret) + return; + + ret = regmap_field_write(rf->int_en, 0); + if (ret) + return; + + apds9306_power_state(data, false); +} + +static int apds9306_device_init(struct apds9306_data *data) +{ + struct apds9306_regfields *rf = &data->rf; + int ret; + + ret = apds9306_init_iio_gts(data); + if (ret) + return ret; + + ret = regmap_field_write(rf->intg_time, APDS9306_MEAS_MODE_100MS); + if (ret) + return ret; + + ret = regmap_field_write(rf->repeat_rate, APDS9306_SAMP_FREQ_10HZ); + if (ret) + return ret; + + ret = regmap_field_write(rf->gain, APDS9306_GSEL_3X); + if (ret) + return ret; + + ret = regmap_field_write(rf->int_src, APDS9306_INT_SRC_ALS); + if (ret) + return ret; + + ret = regmap_field_write(rf->int_en, 0); + if (ret) + return ret; + + return regmap_field_write(rf->int_thresh_var_en, 0); +} + +static int apds9306_pm_init(struct apds9306_data *data) +{ + struct device *dev = data->dev; + int ret; + + ret = apds9306_power_state(data, true); + if (ret) + return ret; + + ret = pm_runtime_set_active(dev); + if (ret) + return ret; + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + pm_runtime_set_autosuspend_delay(dev, 5000); + pm_runtime_use_autosuspend(dev); + pm_runtime_get(dev); + + return 0; +} + +static int apds9306_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct apds9306_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + + mutex_init(&data->mutex); + + data->regmap = devm_regmap_init_i2c(client, &apds9306_regmap); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "regmap initialization failed\n"); + + data->dev = dev; + i2c_set_clientdata(client, indio_dev); + + ret = apds9306_regfield_init(data); + if (ret) + return dev_err_probe(dev, ret, "regfield initialization failed\n"); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulator\n"); + + indio_dev->name = "apds9306"; + indio_dev->modes = INDIO_DIRECT_MODE; + if (client->irq) { + indio_dev->info = &apds9306_info; + indio_dev->channels = apds9306_channels_with_events; + indio_dev->num_channels = ARRAY_SIZE(apds9306_channels_with_events); + ret = devm_request_threaded_irq(dev, client->irq, NULL, + apds9306_irq_handler, IRQF_ONESHOT, + "apds9306_event", indio_dev); + if (ret) + return ret; + } else { + indio_dev->info = &apds9306_info_no_events; + indio_dev->channels = apds9306_channels_without_events; + indio_dev->num_channels = + ARRAY_SIZE(apds9306_channels_without_events); + } + + ret = apds9306_pm_init(data); + if (ret) + return dev_err_probe(dev, ret, "failed pm init\n"); + + ret = apds9306_device_init(data); + if (ret) + return dev_err_probe(dev, ret, "failed to init device\n"); + + ret = devm_add_action_or_reset(dev, apds9306_powerdown, data); + if (ret) + return ret; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "failed iio device registration\n"); + + pm_runtime_put_autosuspend(dev); + + return 0; +} + +static int apds9306_runtime_suspend(struct device *dev) +{ + struct apds9306_data *data = iio_priv(dev_get_drvdata(dev)); + + return apds9306_power_state(data, false); +} + +static int apds9306_runtime_resume(struct device *dev) +{ + struct apds9306_data *data = iio_priv(dev_get_drvdata(dev)); + + return apds9306_power_state(data, true); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(apds9306_pm_ops, + apds9306_runtime_suspend, + apds9306_runtime_resume, + NULL); + +static const struct of_device_id apds9306_of_match[] = { + { .compatible = "avago,apds9306" }, + { } +}; +MODULE_DEVICE_TABLE(of, apds9306_of_match); + +static struct i2c_driver apds9306_driver = { + .driver = { + .name = "apds9306", + .pm = pm_ptr(&apds9306_pm_ops), + .of_match_table = apds9306_of_match, + }, + .probe = apds9306_probe, +}; +module_i2c_driver(apds9306_driver); + +MODULE_AUTHOR("Subhajit Ghosh "); +MODULE_DESCRIPTION("APDS9306 Ambient Light Sensor driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_GTS_HELPER"); diff --git a/drivers/iio/light/apds9960.c b/drivers/iio/light/apds9960.c new file mode 100644 index 000000000..afe520de6 --- /dev/null +++ b/drivers/iio/light/apds9960.c @@ -0,0 +1,1187 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * apds9960.c - Support for Avago APDS9960 gesture/RGB/ALS/proximity sensor + * + * Copyright (C) 2015, 2018 + * Author: Matt Ranostay + * + * TODO: gesture + proximity calib offsets + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define APDS9960_DRV_NAME "apds9960" + +#define APDS9960_REG_RAM_START 0x00 +#define APDS9960_REG_RAM_END 0x7f + +#define APDS9960_REG_ENABLE 0x80 +#define APDS9960_REG_ATIME 0x81 +#define APDS9960_REG_WTIME 0x83 + +#define APDS9960_REG_AILTL 0x84 +#define APDS9960_REG_AILTH 0x85 +#define APDS9960_REG_AIHTL 0x86 +#define APDS9960_REG_AIHTH 0x87 + +#define APDS9960_REG_PILT 0x89 +#define APDS9960_REG_PIHT 0x8b +#define APDS9960_REG_PERS 0x8c + +#define APDS9960_REG_CONFIG_1 0x8d +#define APDS9960_REG_PPULSE 0x8e + +#define APDS9960_REG_CONTROL 0x8f +#define APDS9960_REG_CONTROL_AGAIN_MASK 0x03 +#define APDS9960_REG_CONTROL_PGAIN_MASK 0x0c +#define APDS9960_REG_CONTROL_AGAIN_MASK_SHIFT 0 +#define APDS9960_REG_CONTROL_PGAIN_MASK_SHIFT 2 + +#define APDS9960_REG_CONFIG_2 0x90 +#define APDS9960_REG_ID 0x92 + +#define APDS9960_REG_STATUS 0x93 +#define APDS9960_REG_STATUS_PS_INT BIT(5) +#define APDS9960_REG_STATUS_ALS_INT BIT(4) +#define APDS9960_REG_STATUS_GINT BIT(2) + +#define APDS9960_REG_PDATA 0x9c +#define APDS9960_REG_POFFSET_UR 0x9d +#define APDS9960_REG_POFFSET_DL 0x9e +#define APDS9960_REG_CONFIG_3 0x9f + +#define APDS9960_REG_GPENTH 0xa0 +#define APDS9960_REG_GEXTH 0xa1 + +#define APDS9960_REG_GCONF_1 0xa2 +#define APDS9960_REG_GCONF_1_GFIFO_THRES_MASK 0xc0 +#define APDS9960_REG_GCONF_1_GFIFO_THRES_MASK_SHIFT 6 + +#define APDS9960_REG_GCONF_2 0xa3 +#define APDS9960_REG_GCONF_2_GGAIN_MASK 0x60 +#define APDS9960_REG_GCONF_2_GGAIN_MASK_SHIFT 5 + +#define APDS9960_REG_GOFFSET_U 0xa4 +#define APDS9960_REG_GOFFSET_D 0xa5 +#define APDS9960_REG_GPULSE 0xa6 +#define APDS9960_REG_GOFFSET_L 0xa7 +#define APDS9960_REG_GOFFSET_R 0xa9 +#define APDS9960_REG_GCONF_3 0xaa + +#define APDS9960_REG_GCONF_4 0xab +#define APDS9960_REG_GFLVL 0xae +#define APDS9960_REG_GSTATUS 0xaf + +#define APDS9960_REG_IFORCE 0xe4 +#define APDS9960_REG_PICLEAR 0xe5 +#define APDS9960_REG_CICLEAR 0xe6 +#define APDS9960_REG_AICLEAR 0xe7 + +#define APDS9960_DEFAULT_PERS 0x33 +#define APDS9960_DEFAULT_GPENTH 0x50 +#define APDS9960_DEFAULT_GEXTH 0x40 + +#define APDS9960_MAX_PXS_THRES_VAL 255 +#define APDS9960_MAX_ALS_THRES_VAL 0xffff +#define APDS9960_MAX_INT_TIME_IN_US 1000000 + +enum apds9960_als_channel_idx { + IDX_ALS_CLEAR, IDX_ALS_RED, IDX_ALS_GREEN, IDX_ALS_BLUE, +}; + +#define APDS9960_REG_ALS_BASE 0x94 +#define APDS9960_REG_ALS_CHANNEL(_colour) \ + (APDS9960_REG_ALS_BASE + (IDX_ALS_##_colour * 2)) + +enum apds9960_gesture_channel_idx { + IDX_DIR_UP, IDX_DIR_DOWN, IDX_DIR_LEFT, IDX_DIR_RIGHT, +}; + +#define APDS9960_REG_GFIFO_BASE 0xfc +#define APDS9960_REG_GFIFO_DIR(_dir) \ + (APDS9960_REG_GFIFO_BASE + IDX_DIR_##_dir) + +struct apds9960_data { + struct i2c_client *client; + struct iio_dev *indio_dev; + struct mutex lock; + + /* regmap fields */ + struct regmap *regmap; + struct regmap_field *reg_int_als; + struct regmap_field *reg_int_ges; + struct regmap_field *reg_int_pxs; + + struct regmap_field *reg_enable_als; + struct regmap_field *reg_enable_ges; + struct regmap_field *reg_enable_pxs; + + /* state */ + bool als_int; + bool pxs_int; + int gesture_mode_running; + + /* gain values */ + int als_gain; + int pxs_gain; + + /* integration time value in us */ + int als_adc_int_us; + + /* gesture buffer */ + u8 buffer[4]; /* 4 8-bit channels */ + + /* calibration value buffer */ + int calibbias[5]; +}; + +enum { + APDS9960_CHAN_PROXIMITY, + APDS9960_CHAN_GESTURE_UP, + APDS9960_CHAN_GESTURE_DOWN, + APDS9960_CHAN_GESTURE_LEFT, + APDS9960_CHAN_GESTURE_RIGHT, +}; + +static const unsigned int apds9960_offset_regs[][2] = { + [APDS9960_CHAN_PROXIMITY] = {APDS9960_REG_POFFSET_UR, APDS9960_REG_POFFSET_DL}, + [APDS9960_CHAN_GESTURE_UP] = {APDS9960_REG_GOFFSET_U, 0}, + [APDS9960_CHAN_GESTURE_DOWN] = {APDS9960_REG_GOFFSET_D, 0}, + [APDS9960_CHAN_GESTURE_LEFT] = {APDS9960_REG_GOFFSET_L, 0}, + [APDS9960_CHAN_GESTURE_RIGHT] = {APDS9960_REG_GOFFSET_R, 0}, +}; + +static const struct reg_default apds9960_reg_defaults[] = { + /* Default ALS integration time = 2.48ms */ + { APDS9960_REG_ATIME, 0xff }, +}; + +static const struct regmap_range apds9960_volatile_ranges[] = { + regmap_reg_range(APDS9960_REG_STATUS, + APDS9960_REG_PDATA), + regmap_reg_range(APDS9960_REG_GFLVL, + APDS9960_REG_GSTATUS), + regmap_reg_range(APDS9960_REG_GFIFO_DIR(UP), + APDS9960_REG_GFIFO_DIR(RIGHT)), + regmap_reg_range(APDS9960_REG_IFORCE, + APDS9960_REG_AICLEAR), +}; + +static const struct regmap_access_table apds9960_volatile_table = { + .yes_ranges = apds9960_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9960_volatile_ranges), +}; + +static const struct regmap_range apds9960_precious_ranges[] = { + regmap_reg_range(APDS9960_REG_RAM_START, APDS9960_REG_RAM_END), +}; + +static const struct regmap_access_table apds9960_precious_table = { + .yes_ranges = apds9960_precious_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9960_precious_ranges), +}; + +static const struct regmap_range apds9960_readable_ranges[] = { + regmap_reg_range(APDS9960_REG_ENABLE, + APDS9960_REG_GSTATUS), + regmap_reg_range(APDS9960_REG_GFIFO_DIR(UP), + APDS9960_REG_GFIFO_DIR(RIGHT)), +}; + +static const struct regmap_access_table apds9960_readable_table = { + .yes_ranges = apds9960_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9960_readable_ranges), +}; + +static const struct regmap_range apds9960_writeable_ranges[] = { + regmap_reg_range(APDS9960_REG_ENABLE, APDS9960_REG_CONFIG_2), + regmap_reg_range(APDS9960_REG_POFFSET_UR, APDS9960_REG_GCONF_4), + regmap_reg_range(APDS9960_REG_IFORCE, APDS9960_REG_AICLEAR), +}; + +static const struct regmap_access_table apds9960_writeable_table = { + .yes_ranges = apds9960_writeable_ranges, + .n_yes_ranges = ARRAY_SIZE(apds9960_writeable_ranges), +}; + +static const struct regmap_config apds9960_regmap_config = { + .name = "apds9960_regmap", + .reg_bits = 8, + .val_bits = 8, + .use_single_read = true, + .use_single_write = true, + + .volatile_table = &apds9960_volatile_table, + .precious_table = &apds9960_precious_table, + .rd_table = &apds9960_readable_table, + .wr_table = &apds9960_writeable_table, + + .reg_defaults = apds9960_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(apds9960_reg_defaults), + .max_register = APDS9960_REG_GFIFO_DIR(RIGHT), + .cache_type = REGCACHE_MAPLE, +}; + +static const struct iio_event_spec apds9960_pxs_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_event_spec apds9960_als_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +#define APDS9960_GESTURE_CHANNEL(_dir, _si) { \ + .type = IIO_PROXIMITY, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .channel = _si + 1, \ + .scan_index = _si, \ + .indexed = 1, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 8, \ + .storagebits = 8, \ + }, \ +} + +#define APDS9960_INTENSITY_CHANNEL(_colour) { \ + .type = IIO_INTENSITY, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .channel2 = IIO_MOD_LIGHT_##_colour, \ + .address = APDS9960_REG_ALS_CHANNEL(_colour), \ + .modified = 1, \ + .scan_index = -1, \ +} + +static const unsigned long apds9960_scan_masks[] = {0xf, 0}; + +static const struct iio_chan_spec apds9960_channels[] = { + { + .type = IIO_PROXIMITY, + .address = APDS9960_REG_PDATA, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .channel = 0, + .indexed = 0, + .scan_index = -1, + + .event_spec = apds9960_pxs_event_spec, + .num_event_specs = ARRAY_SIZE(apds9960_pxs_event_spec), + }, + /* Gesture Sensor */ + APDS9960_GESTURE_CHANNEL(UP, 0), + APDS9960_GESTURE_CHANNEL(DOWN, 1), + APDS9960_GESTURE_CHANNEL(LEFT, 2), + APDS9960_GESTURE_CHANNEL(RIGHT, 3), + /* ALS */ + { + .type = IIO_INTENSITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME), + .channel2 = IIO_MOD_LIGHT_CLEAR, + .address = APDS9960_REG_ALS_CHANNEL(CLEAR), + .modified = 1, + .scan_index = -1, + + .event_spec = apds9960_als_event_spec, + .num_event_specs = ARRAY_SIZE(apds9960_als_event_spec), + }, + /* RGB Sensor */ + APDS9960_INTENSITY_CHANNEL(RED), + APDS9960_INTENSITY_CHANNEL(GREEN), + APDS9960_INTENSITY_CHANNEL(BLUE), +}; + +static int apds9960_set_calibbias(struct apds9960_data *data, + struct iio_chan_spec const *chan, int calibbias) +{ + int ret, i; + + if (calibbias < S8_MIN || calibbias > S8_MAX) + return -EINVAL; + + guard(mutex)(&data->lock); + for (i = 0; i < 2; i++) { + if (apds9960_offset_regs[chan->channel][i] == 0) + break; + + ret = regmap_write(data->regmap, apds9960_offset_regs[chan->channel][i], calibbias); + if (ret < 0) + return ret; + } + data->calibbias[chan->channel] = calibbias; + + return 0; +} + +/* integration time in us */ +static const int apds9960_int_time[][2] = { + { 28000, 246}, + {100000, 219}, + {200000, 182}, + {700000, 0} +}; + +/* gain mapping */ +static const int apds9960_pxs_gain_map[] = {1, 2, 4, 8}; +static const int apds9960_als_gain_map[] = {1, 4, 16, 64}; + +static IIO_CONST_ATTR(proximity_scale_available, "1 2 4 8"); +static IIO_CONST_ATTR(intensity_scale_available, "1 4 16 64"); +static IIO_CONST_ATTR_INT_TIME_AVAIL("0.028 0.1 0.2 0.7"); + +static struct attribute *apds9960_attributes[] = { + &iio_const_attr_proximity_scale_available.dev_attr.attr, + &iio_const_attr_intensity_scale_available.dev_attr.attr, + &iio_const_attr_integration_time_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group apds9960_attribute_group = { + .attrs = apds9960_attributes, +}; + +static const struct reg_field apds9960_reg_field_int_als = + REG_FIELD(APDS9960_REG_ENABLE, 4, 4); + +static const struct reg_field apds9960_reg_field_int_ges = + REG_FIELD(APDS9960_REG_GCONF_4, 1, 1); + +static const struct reg_field apds9960_reg_field_int_pxs = + REG_FIELD(APDS9960_REG_ENABLE, 5, 5); + +static const struct reg_field apds9960_reg_field_enable_als = + REG_FIELD(APDS9960_REG_ENABLE, 1, 1); + +static const struct reg_field apds9960_reg_field_enable_ges = + REG_FIELD(APDS9960_REG_ENABLE, 6, 6); + +static const struct reg_field apds9960_reg_field_enable_pxs = + REG_FIELD(APDS9960_REG_ENABLE, 2, 2); + +static int apds9960_set_it_time(struct apds9960_data *data, int val2) +{ + int ret = -EINVAL; + int idx; + + for (idx = 0; idx < ARRAY_SIZE(apds9960_int_time); idx++) { + if (apds9960_int_time[idx][0] == val2) { + mutex_lock(&data->lock); + ret = regmap_write(data->regmap, APDS9960_REG_ATIME, + apds9960_int_time[idx][1]); + if (!ret) + data->als_adc_int_us = val2; + mutex_unlock(&data->lock); + break; + } + } + + return ret; +} + +static int apds9960_set_pxs_gain(struct apds9960_data *data, int val) +{ + int ret = -EINVAL; + int idx; + + for (idx = 0; idx < ARRAY_SIZE(apds9960_pxs_gain_map); idx++) { + if (apds9960_pxs_gain_map[idx] == val) { + /* pxs + gesture gains are mirrored */ + mutex_lock(&data->lock); + ret = regmap_update_bits(data->regmap, + APDS9960_REG_CONTROL, + APDS9960_REG_CONTROL_PGAIN_MASK, + idx << APDS9960_REG_CONTROL_PGAIN_MASK_SHIFT); + if (ret) { + mutex_unlock(&data->lock); + break; + } + + ret = regmap_update_bits(data->regmap, + APDS9960_REG_GCONF_2, + APDS9960_REG_GCONF_2_GGAIN_MASK, + idx << APDS9960_REG_GCONF_2_GGAIN_MASK_SHIFT); + if (!ret) + data->pxs_gain = idx; + mutex_unlock(&data->lock); + break; + } + } + + return ret; +} + +static int apds9960_set_als_gain(struct apds9960_data *data, int val) +{ + int ret = -EINVAL; + int idx; + + for (idx = 0; idx < ARRAY_SIZE(apds9960_als_gain_map); idx++) { + if (apds9960_als_gain_map[idx] == val) { + mutex_lock(&data->lock); + ret = regmap_update_bits(data->regmap, + APDS9960_REG_CONTROL, + APDS9960_REG_CONTROL_AGAIN_MASK, idx); + if (!ret) + data->als_gain = idx; + mutex_unlock(&data->lock); + break; + } + } + + return ret; +} + +#ifdef CONFIG_PM +static int apds9960_set_power_state(struct apds9960_data *data, bool on) +{ + struct device *dev = &data->client->dev; + int ret = 0; + + mutex_lock(&data->lock); + + if (on) { + int suspended; + + suspended = pm_runtime_suspended(dev); + ret = pm_runtime_get_sync(dev); + + /* Allow one integration cycle before allowing a reading */ + if (suspended) + usleep_range(data->als_adc_int_us, + APDS9960_MAX_INT_TIME_IN_US); + } else { + ret = pm_runtime_put_autosuspend(dev); + } + + mutex_unlock(&data->lock); + + return ret; +} +#else +static int apds9960_set_power_state(struct apds9960_data *data, bool on) +{ + return 0; +} +#endif + +static int apds9960_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct apds9960_data *data = iio_priv(indio_dev); + __le16 buf; + int ret = -EINVAL; + + if (data->gesture_mode_running) + return -EBUSY; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + apds9960_set_power_state(data, true); + switch (chan->type) { + case IIO_PROXIMITY: + ret = regmap_read(data->regmap, chan->address, val); + if (!ret) + ret = IIO_VAL_INT; + break; + case IIO_INTENSITY: + ret = regmap_bulk_read(data->regmap, chan->address, + &buf, 2); + if (!ret) { + ret = IIO_VAL_INT; + *val = le16_to_cpu(buf); + } + break; + default: + ret = -EINVAL; + } + apds9960_set_power_state(data, false); + break; + case IIO_CHAN_INFO_INT_TIME: + /* RGB + ALS sensors only have integration time */ + mutex_lock(&data->lock); + switch (chan->type) { + case IIO_INTENSITY: + *val = 0; + *val2 = data->als_adc_int_us; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + ret = -EINVAL; + } + mutex_unlock(&data->lock); + break; + case IIO_CHAN_INFO_SCALE: + mutex_lock(&data->lock); + switch (chan->type) { + case IIO_PROXIMITY: + *val = apds9960_pxs_gain_map[data->pxs_gain]; + ret = IIO_VAL_INT; + break; + case IIO_INTENSITY: + *val = apds9960_als_gain_map[data->als_gain]; + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + } + mutex_unlock(&data->lock); + break; + case IIO_CHAN_INFO_CALIBBIAS: + mutex_lock(&data->lock); + *val = data->calibbias[chan->channel]; + ret = IIO_VAL_INT; + mutex_unlock(&data->lock); + break; + } + + return ret; +}; + +static int apds9960_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct apds9960_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + /* RGB + ALS sensors only have int time */ + switch (chan->type) { + case IIO_INTENSITY: + if (val != 0) + return -EINVAL; + return apds9960_set_it_time(data, val2); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + if (val2 != 0) + return -EINVAL; + switch (chan->type) { + case IIO_PROXIMITY: + return apds9960_set_pxs_gain(data, val); + case IIO_INTENSITY: + return apds9960_set_als_gain(data, val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + if (val2 != 0) + return -EINVAL; + return apds9960_set_calibbias(data, chan, val); + default: + return -EINVAL; + } + + return 0; +} + +static inline int apds9960_get_thres_reg(const struct iio_chan_spec *chan, + enum iio_event_direction dir, + u8 *reg) +{ + switch (dir) { + case IIO_EV_DIR_RISING: + switch (chan->type) { + case IIO_PROXIMITY: + *reg = APDS9960_REG_PIHT; + break; + case IIO_INTENSITY: + *reg = APDS9960_REG_AIHTL; + break; + default: + return -EINVAL; + } + break; + case IIO_EV_DIR_FALLING: + switch (chan->type) { + case IIO_PROXIMITY: + *reg = APDS9960_REG_PILT; + break; + case IIO_INTENSITY: + *reg = APDS9960_REG_AILTL; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int apds9960_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + u8 reg; + __le16 buf; + int ret = 0; + struct apds9960_data *data = iio_priv(indio_dev); + + if (info != IIO_EV_INFO_VALUE) + return -EINVAL; + + ret = apds9960_get_thres_reg(chan, dir, ®); + if (ret < 0) + return ret; + + if (chan->type == IIO_PROXIMITY) { + ret = regmap_read(data->regmap, reg, val); + if (ret < 0) + return ret; + } else if (chan->type == IIO_INTENSITY) { + ret = regmap_bulk_read(data->regmap, reg, &buf, 2); + if (ret < 0) + return ret; + *val = le16_to_cpu(buf); + } else + return -EINVAL; + + *val2 = 0; + + return IIO_VAL_INT; +} + +static int apds9960_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + u8 reg; + __le16 buf; + int ret = 0; + struct apds9960_data *data = iio_priv(indio_dev); + + if (info != IIO_EV_INFO_VALUE) + return -EINVAL; + + ret = apds9960_get_thres_reg(chan, dir, ®); + if (ret < 0) + return ret; + + if (chan->type == IIO_PROXIMITY) { + if (val < 0 || val > APDS9960_MAX_PXS_THRES_VAL) + return -EINVAL; + ret = regmap_write(data->regmap, reg, val); + if (ret < 0) + return ret; + } else if (chan->type == IIO_INTENSITY) { + if (val < 0 || val > APDS9960_MAX_ALS_THRES_VAL) + return -EINVAL; + buf = cpu_to_le16(val); + ret = regmap_bulk_write(data->regmap, reg, &buf, 2); + if (ret < 0) + return ret; + } else + return -EINVAL; + + return 0; +} + +static int apds9960_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct apds9960_data *data = iio_priv(indio_dev); + + switch (chan->type) { + case IIO_PROXIMITY: + return data->pxs_int; + case IIO_INTENSITY: + return data->als_int; + default: + return -EINVAL; + } +} + +static int apds9960_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct apds9960_data *data = iio_priv(indio_dev); + int ret; + + switch (chan->type) { + case IIO_PROXIMITY: + if (data->pxs_int == state) + return -EINVAL; + + ret = regmap_field_write(data->reg_int_pxs, state); + if (ret) + return ret; + data->pxs_int = state; + apds9960_set_power_state(data, state); + break; + case IIO_INTENSITY: + if (data->als_int == state) + return -EINVAL; + + ret = regmap_field_write(data->reg_int_als, state); + if (ret) + return ret; + data->als_int = state; + apds9960_set_power_state(data, state); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct iio_info apds9960_info = { + .attrs = &apds9960_attribute_group, + .read_raw = apds9960_read_raw, + .write_raw = apds9960_write_raw, + .read_event_value = apds9960_read_event, + .write_event_value = apds9960_write_event, + .read_event_config = apds9960_read_event_config, + .write_event_config = apds9960_write_event_config, + +}; + +static inline int apds9660_fifo_is_empty(struct apds9960_data *data) +{ + int cnt; + int ret; + + ret = regmap_read(data->regmap, APDS9960_REG_GFLVL, &cnt); + if (ret) + return ret; + + return cnt; +} + +static void apds9960_read_gesture_fifo(struct apds9960_data *data) +{ + int ret, cnt = 0; + + mutex_lock(&data->lock); + data->gesture_mode_running = 1; + + while (cnt || (cnt = apds9660_fifo_is_empty(data) > 0)) { + ret = regmap_bulk_read(data->regmap, APDS9960_REG_GFIFO_BASE, + &data->buffer, 4); + + if (ret) + goto err_read; + + iio_push_to_buffers(data->indio_dev, data->buffer); + cnt--; + } + +err_read: + data->gesture_mode_running = 0; + mutex_unlock(&data->lock); +} + +static irqreturn_t apds9960_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct apds9960_data *data = iio_priv(indio_dev); + int ret, status; + + ret = regmap_read(data->regmap, APDS9960_REG_STATUS, &status); + if (ret < 0) { + dev_err(&data->client->dev, "irq status reg read failed\n"); + return IRQ_HANDLED; + } + + if ((status & APDS9960_REG_STATUS_ALS_INT) && data->als_int) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + regmap_write(data->regmap, APDS9960_REG_CICLEAR, 1); + } + + if ((status & APDS9960_REG_STATUS_PS_INT) && data->pxs_int) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + regmap_write(data->regmap, APDS9960_REG_PICLEAR, 1); + } + + if (status & APDS9960_REG_STATUS_GINT) + apds9960_read_gesture_fifo(data); + + return IRQ_HANDLED; +} + +static int apds9960_set_powermode(struct apds9960_data *data, bool state) +{ + return regmap_update_bits(data->regmap, APDS9960_REG_ENABLE, 1, state); +} + +static int apds9960_buffer_postenable(struct iio_dev *indio_dev) +{ + struct apds9960_data *data = iio_priv(indio_dev); + int ret; + + ret = regmap_field_write(data->reg_int_ges, 1); + if (ret) + return ret; + + ret = regmap_field_write(data->reg_enable_ges, 1); + if (ret) + return ret; + + pm_runtime_get_sync(&data->client->dev); + + return 0; +} + +static int apds9960_buffer_predisable(struct iio_dev *indio_dev) +{ + struct apds9960_data *data = iio_priv(indio_dev); + int ret; + + ret = regmap_field_write(data->reg_enable_ges, 0); + if (ret) + return ret; + + ret = regmap_field_write(data->reg_int_ges, 0); + if (ret) + return ret; + + pm_runtime_put_autosuspend(&data->client->dev); + + return 0; +} + +static const struct iio_buffer_setup_ops apds9960_buffer_setup_ops = { + .postenable = apds9960_buffer_postenable, + .predisable = apds9960_buffer_predisable, +}; + +static int apds9960_regfield_init(struct apds9960_data *data) +{ + struct device *dev = &data->client->dev; + struct regmap *regmap = data->regmap; + + data->reg_int_als = devm_regmap_field_alloc(dev, regmap, + apds9960_reg_field_int_als); + if (IS_ERR(data->reg_int_als)) { + dev_err(dev, "INT ALS reg field init failed\n"); + return PTR_ERR(data->reg_int_als); + } + + data->reg_int_ges = devm_regmap_field_alloc(dev, regmap, + apds9960_reg_field_int_ges); + if (IS_ERR(data->reg_int_ges)) { + dev_err(dev, "INT gesture reg field init failed\n"); + return PTR_ERR(data->reg_int_ges); + } + + data->reg_int_pxs = devm_regmap_field_alloc(dev, regmap, + apds9960_reg_field_int_pxs); + if (IS_ERR(data->reg_int_pxs)) { + dev_err(dev, "INT pxs reg field init failed\n"); + return PTR_ERR(data->reg_int_pxs); + } + + data->reg_enable_als = devm_regmap_field_alloc(dev, regmap, + apds9960_reg_field_enable_als); + if (IS_ERR(data->reg_enable_als)) { + dev_err(dev, "Enable ALS reg field init failed\n"); + return PTR_ERR(data->reg_enable_als); + } + + data->reg_enable_ges = devm_regmap_field_alloc(dev, regmap, + apds9960_reg_field_enable_ges); + if (IS_ERR(data->reg_enable_ges)) { + dev_err(dev, "Enable gesture reg field init failed\n"); + return PTR_ERR(data->reg_enable_ges); + } + + data->reg_enable_pxs = devm_regmap_field_alloc(dev, regmap, + apds9960_reg_field_enable_pxs); + if (IS_ERR(data->reg_enable_pxs)) { + dev_err(dev, "Enable PXS reg field init failed\n"); + return PTR_ERR(data->reg_enable_pxs); + } + + return 0; +} + +static int apds9960_chip_init(struct apds9960_data *data) +{ + int ret; + + /* Default IT for ALS of 28 ms */ + ret = apds9960_set_it_time(data, 28000); + if (ret) + return ret; + + /* Ensure gesture interrupt is OFF */ + ret = regmap_field_write(data->reg_int_ges, 0); + if (ret) + return ret; + + /* Disable gesture sensor, since polling is useless from user-space */ + ret = regmap_field_write(data->reg_enable_ges, 0); + if (ret) + return ret; + + /* Ensure proximity interrupt is OFF */ + ret = regmap_field_write(data->reg_int_pxs, 0); + if (ret) + return ret; + + /* Enable proximity sensor for polling */ + ret = regmap_field_write(data->reg_enable_pxs, 1); + if (ret) + return ret; + + /* Ensure ALS interrupt is OFF */ + ret = regmap_field_write(data->reg_int_als, 0); + if (ret) + return ret; + + /* Enable ALS sensor for polling */ + ret = regmap_field_write(data->reg_enable_als, 1); + if (ret) + return ret; + /* + * When enabled trigger an interrupt after 3 readings + * outside threshold for ALS + PXS + */ + ret = regmap_write(data->regmap, APDS9960_REG_PERS, + APDS9960_DEFAULT_PERS); + if (ret) + return ret; + + /* + * Wait for 4 event outside gesture threshold to prevent interrupt + * flooding. + */ + ret = regmap_update_bits(data->regmap, APDS9960_REG_GCONF_1, + APDS9960_REG_GCONF_1_GFIFO_THRES_MASK, + BIT(0) << APDS9960_REG_GCONF_1_GFIFO_THRES_MASK_SHIFT); + if (ret) + return ret; + + /* Default ENTER and EXIT thresholds for the GESTURE engine. */ + ret = regmap_write(data->regmap, APDS9960_REG_GPENTH, + APDS9960_DEFAULT_GPENTH); + if (ret) + return ret; + + ret = regmap_write(data->regmap, APDS9960_REG_GEXTH, + APDS9960_DEFAULT_GEXTH); + if (ret) + return ret; + + return apds9960_set_powermode(data, 1); +} + +static int apds9960_probe(struct i2c_client *client) +{ + struct apds9960_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + indio_dev->info = &apds9960_info; + indio_dev->name = APDS9960_DRV_NAME; + indio_dev->channels = apds9960_channels; + indio_dev->num_channels = ARRAY_SIZE(apds9960_channels); + indio_dev->available_scan_masks = apds9960_scan_masks; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_iio_kfifo_buffer_setup(&client->dev, indio_dev, + &apds9960_buffer_setup_ops); + if (ret) + return ret; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + + data->regmap = devm_regmap_init_i2c(client, &apds9960_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(&client->dev, "regmap initialization failed.\n"); + return PTR_ERR(data->regmap); + } + + data->client = client; + data->indio_dev = indio_dev; + mutex_init(&data->lock); + + ret = pm_runtime_set_active(&client->dev); + if (ret) + goto error_power_down; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, 5000); + pm_runtime_use_autosuspend(&client->dev); + + apds9960_set_power_state(data, true); + + ret = apds9960_regfield_init(data); + if (ret) + goto error_power_down; + + ret = apds9960_chip_init(data); + if (ret) + goto error_power_down; + + if (client->irq <= 0) { + dev_err(&client->dev, "no valid irq defined\n"); + ret = -EINVAL; + goto error_power_down; + } + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, apds9960_interrupt_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "apds9960_event", + indio_dev); + if (ret) + goto error_power_down; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_power_down; + + apds9960_set_power_state(data, false); + + return 0; + +error_power_down: + apds9960_set_power_state(data, false); + + return ret; +} + +static void apds9960_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct apds9960_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + apds9960_set_powermode(data, 0); +} + +#ifdef CONFIG_PM +static int apds9960_runtime_suspend(struct device *dev) +{ + struct apds9960_data *data = + iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + return apds9960_set_powermode(data, 0); +} + +static int apds9960_runtime_resume(struct device *dev) +{ + struct apds9960_data *data = + iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + return apds9960_set_powermode(data, 1); +} +#endif + +static const struct dev_pm_ops apds9960_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(apds9960_runtime_suspend, + apds9960_runtime_resume, NULL) +}; + +static const struct i2c_device_id apds9960_id[] = { + { .name = "apds9960" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, apds9960_id); + +static const struct acpi_device_id apds9960_acpi_match[] = { + { "MSHW0184" }, + { } +}; +MODULE_DEVICE_TABLE(acpi, apds9960_acpi_match); + +static const struct of_device_id apds9960_of_match[] = { + { .compatible = "avago,apds9960" }, + { } +}; +MODULE_DEVICE_TABLE(of, apds9960_of_match); + +static struct i2c_driver apds9960_driver = { + .driver = { + .name = APDS9960_DRV_NAME, + .of_match_table = apds9960_of_match, + .pm = &apds9960_pm_ops, + .acpi_match_table = apds9960_acpi_match, + }, + .probe = apds9960_probe, + .remove = apds9960_remove, + .id_table = apds9960_id, +}; +module_i2c_driver(apds9960_driver); + +MODULE_AUTHOR("Matt Ranostay "); +MODULE_DESCRIPTION("APDS9960 Gesture/RGB/ALS/Proximity sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/apds9999.c b/drivers/iio/light/apds9999.c new file mode 100644 index 000000000..43fa9992c --- /dev/null +++ b/drivers/iio/light/apds9999.c @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * IIO driver for Broadcom APDS9999 Lux Light Sensor + * + * Copyright (C) 2026 + * Author: Jose A. Perez de Azpillaga + * + * TODO: proximity sensor + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define APDS9999_REG_MAIN_CTRL 0x00 +#define APDS9999_MAIN_CTRL_LS_EN BIT(1) +#define APDS9999_REG_LS_MEAS_RATE 0x04 +#define APDS9999_LS_RES_MASK GENMASK(6, 4) +#define APDS9999_LS_RATE_MASK GENMASK(2, 0) +#define APDS9999_REG_LS_GAIN 0x05 +#define APDS9999_REG_PART_ID 0x06 +#define APDS9999_REG_MAIN_STATUS 0x07 +#define APDS9999_MAIN_STATUS_LS_DATA BIT(3) +#define APDS9999_REG_LS_DATA_IR_0 0x0A +#define APDS9999_REG_LS_DATA_GREEN_0 0x0D +#define APDS9999_REG_LS_DATA_BLUE_0 0x10 +#define APDS9999_REG_LS_DATA_RED_0 0x13 + +#define APDS9999_PART_ID 0xC2 + +#define APDS9999_GAIN_1X 0 +#define APDS9999_GAIN_3X 1 +#define APDS9999_GAIN_6X 2 +#define APDS9999_GAIN_9X 3 +#define APDS9999_GAIN_18X 4 + +static const int apds9999_gains[] = { + [APDS9999_GAIN_1X] = 1, + [APDS9999_GAIN_3X] = 3, + [APDS9999_GAIN_6X] = 6, + [APDS9999_GAIN_9X] = 9, + [APDS9999_GAIN_18X] = 18, +}; + +#define APDS9999_RES_20BIT 0 +#define APDS9999_RES_19BIT 1 +#define APDS9999_RES_18BIT 2 +#define APDS9999_RES_17BIT 3 +#define APDS9999_RES_16BIT 4 +#define APDS9999_RES_13BIT 5 + +static const int apds9999_itimes_us[] = { + [APDS9999_RES_20BIT] = 400 * USEC_PER_MSEC, + [APDS9999_RES_19BIT] = 200 * USEC_PER_MSEC, + [APDS9999_RES_18BIT] = 100 * USEC_PER_MSEC, + [APDS9999_RES_17BIT] = 50 * USEC_PER_MSEC, + [APDS9999_RES_16BIT] = 25 * USEC_PER_MSEC, + [APDS9999_RES_13BIT] = 3125, +}; + +#define APDS9999_RATE_25_MS 0 +#define APDS9999_RATE_50_MS 1 +#define APDS9999_RATE_100_MS 2 +#define APDS9999_RATE_200_MS 3 +#define APDS9999_RATE_500_MS 4 +#define APDS9999_RATE_1000_MS 5 +#define APDS9999_RATE_2000_MS 6 + +struct apds9999_data { + struct i2c_client *client; + /* lock: serializes access to device registers and cached values */ + struct mutex lock; + int als_gain_idx; + int als_res; + int als_rate; +}; + +static void apds9999_standby(void *client) +{ + i2c_smbus_write_byte_data(client, APDS9999_REG_MAIN_CTRL, 0); +} + +/* + * Apply power-on defaults: 18-bit / 100 ms resolution and rate, + * 3x gain. These match the datasheet reset values. + */ +static int apds9999_init(struct apds9999_data *data) +{ + struct device *dev = &data->client->dev; + struct i2c_client *client = data->client; + u8 regval; + int ret; + + ret = devm_add_action_or_reset(dev, apds9999_standby, client); + if (ret) + return ret; + + guard(mutex)(&data->lock); + + regval = FIELD_PREP(APDS9999_LS_RES_MASK, APDS9999_RES_18BIT) | + FIELD_PREP(APDS9999_LS_RATE_MASK, APDS9999_RATE_100_MS); + ret = i2c_smbus_write_byte_data(client, APDS9999_REG_LS_MEAS_RATE, + regval); + if (ret) + return ret; + data->als_res = APDS9999_RES_18BIT; + data->als_rate = APDS9999_RATE_100_MS; + + ret = i2c_smbus_write_byte_data(client, APDS9999_REG_LS_GAIN, + APDS9999_GAIN_3X); + if (ret) + return ret; + data->als_gain_idx = APDS9999_GAIN_3X; + + return i2c_smbus_write_byte_data(client, APDS9999_REG_MAIN_CTRL, + APDS9999_MAIN_CTRL_LS_EN); +} + +static int apds9999_read_channel(struct apds9999_data *data, u8 reg, + u32 *counts) +{ + struct i2c_client *client = data->client; + u8 buf[3]; + int ret, tries; + + guard(mutex)(&data->lock); + + /* + * Poll MAIN_STATUS for new data. Timeout: ~2 integration periods + * plus margin. Each try sleeps 20 ms. + */ + tries = max(2, (apds9999_itimes_us[data->als_res] * 2) / 20000); + + while (tries--) { + ret = i2c_smbus_read_byte_data(client, + APDS9999_REG_MAIN_STATUS); + if (ret < 0) + return ret; + if (ret & APDS9999_MAIN_STATUS_LS_DATA) + break; + fsleep(20000); + } + + if (tries < 0) + return -ETIMEDOUT; + + ret = i2c_smbus_read_i2c_block_data(client, reg, sizeof(buf), buf); + if (ret < 0) + return ret; + if (ret != sizeof(buf)) + return -EIO; + + *counts = get_unaligned_le24(buf) & GENMASK(19, 0); + return 0; +} + +static int apds9999_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct apds9999_data *data = iio_priv(indio_dev); + int gain, itime_us; + u64 scale_nano; + u32 counts; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = apds9999_read_channel(data, chan->address, &counts); + if (ret) + return ret; + *val = (int)counts; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: { + u32 remainder; + + /* + * Scale (lux per count) = 54 / (gain * integration_time_ms) + * + * The constant 54 is derived from the datasheet table: + * at gain = 3x, itime = 100 ms -> 0.180 lux/count + * -> C = 0.180 * 3 * 100 = 54 + * + * Expressed as IIO_VAL_INT_PLUS_NANO. + */ + gain = apds9999_gains[data->als_gain_idx]; + itime_us = apds9999_itimes_us[data->als_res]; + + /* scale_nano = 54 * 1e12 / (gain * itime_us) nano-lux/count */ + scale_nano = div_u64(54ULL * NSEC_PER_SEC * USEC_PER_MSEC, (u32)(gain * itime_us)); + *val = (int)div_u64_rem(scale_nano, NSEC_PER_SEC, &remainder); + *val2 = (int)remainder; + return IIO_VAL_INT_PLUS_NANO; + } + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = apds9999_itimes_us[data->als_res]; + return IIO_VAL_INT_PLUS_MICRO; + + default: + return -EINVAL; + } +} + +static const struct iio_info apds9999_info = { + .read_raw = apds9999_read_raw, +}; + +/* + * The green channel uses optical coating to approximate the human eye + * spectral response. IIO_INTENSITY channels provide raw ADC data for + * red, green, blue, and IR so userspace can compute weighted lux. + */ +static const struct iio_chan_spec apds9999_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + .address = APDS9999_REG_LS_DATA_GREEN_0, + }, + { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_RED, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + .address = APDS9999_REG_LS_DATA_RED_0, + }, + { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_GREEN, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + .address = APDS9999_REG_LS_DATA_GREEN_0, + }, + { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BLUE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + .address = APDS9999_REG_LS_DATA_BLUE_0, + }, + { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + .address = APDS9999_REG_LS_DATA_IR_0, + }, +}; + +static int apds9999_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct apds9999_data *data; + struct iio_dev *indio_dev; + int ret, part_id; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; + + part_id = i2c_smbus_read_byte_data(client, APDS9999_REG_PART_ID); + if (part_id < 0) + return dev_err_probe(dev, part_id, "failed to read PART_ID\n"); + if (part_id != APDS9999_PART_ID) + dev_info(dev, "unexpected PART_ID 0x%02x (expected 0x%02x)\n", + part_id, APDS9999_PART_ID); + + ret = apds9999_init(data); + if (ret) + return dev_err_probe(dev, ret, "failed to initialize device\n"); + + indio_dev->name = "apds9999"; + indio_dev->info = &apds9999_info; + indio_dev->channels = apds9999_channels; + indio_dev->num_channels = ARRAY_SIZE(apds9999_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "failed to register IIO device\n"); + + return 0; +} + +static const struct i2c_device_id apds9999_id[] = { + { .name = "apds9999" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, apds9999_id); + +static const struct of_device_id apds9999_of_match[] = { + { .compatible = "brcm,apds9999" }, + { } +}; +MODULE_DEVICE_TABLE(of, apds9999_of_match); + +static struct i2c_driver apds9999_driver = { + .driver = { + .name = "apds9999", + .of_match_table = apds9999_of_match, + }, + .probe = apds9999_probe, + .id_table = apds9999_id, +}; +module_i2c_driver(apds9999_driver); + +MODULE_AUTHOR("Jose A. Perez de Azpillaga "); +MODULE_DESCRIPTION("APDS-9999 Lux Light Sensor IIO Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/as73211.c b/drivers/iio/light/as73211.c new file mode 100644 index 000000000..67f33bbe1 --- /dev/null +++ b/drivers/iio/light/as73211.c @@ -0,0 +1,897 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Support for AMS AS73211 JENCOLOR(R) Digital XYZ Sensor and AMS AS7331 + * UVA, UVB and UVC (DUV) Ultraviolet Sensor + * + * Author: Christian Eggers + * + * Copyright (c) 2020 ARRI Lighting + * + * Color light sensor with 16-bit channels for x, y, z and temperature); + * 7-bit I2C slave address 0x74 .. 0x77. + * + * Datasheets: + * AS73211: https://ams.com/documents/20143/36005/AS73211_DS000556_3-01.pdf + * AS7331: https://ams.com/documents/20143/9106314/AS7331_DS001047_4-00.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AS73211_DRV_NAME "as73211" + +/* AS73211 configuration registers */ +#define AS73211_REG_OSR 0x0 +#define AS73211_REG_AGEN 0x2 +#define AS73211_REG_CREG1 0x6 +#define AS73211_REG_CREG2 0x7 +#define AS73211_REG_CREG3 0x8 + +/* AS73211 output register bank */ +#define AS73211_OUT_OSR_STATUS 0 +#define AS73211_OUT_TEMP 1 +#define AS73211_OUT_MRES1 2 +#define AS73211_OUT_MRES2 3 +#define AS73211_OUT_MRES3 4 + +#define AS73211_OSR_SS BIT(7) +#define AS73211_OSR_PD BIT(6) +#define AS73211_OSR_SW_RES BIT(3) +#define AS73211_OSR_DOS_MASK GENMASK(2, 0) +#define AS73211_OSR_DOS_CONFIG FIELD_PREP(AS73211_OSR_DOS_MASK, 0x2) +#define AS73211_OSR_DOS_MEASURE FIELD_PREP(AS73211_OSR_DOS_MASK, 0x3) + +#define AS73211_AGEN_DEVID_MASK GENMASK(7, 4) +#define AS73211_AGEN_DEVID(x) FIELD_PREP(AS73211_AGEN_DEVID_MASK, (x)) +#define AS73211_AGEN_MUT_MASK GENMASK(3, 0) +#define AS73211_AGEN_MUT(x) FIELD_PREP(AS73211_AGEN_MUT_MASK, (x)) + +#define AS73211_CREG1_GAIN_MASK GENMASK(7, 4) +#define AS73211_CREG1_GAIN_1 11 +#define AS73211_CREG1_TIME_MASK GENMASK(3, 0) + +#define AS73211_CREG3_CCLK_MASK GENMASK(1, 0) + +#define AS73211_OSR_STATUS_OUTCONVOF BIT(15) +#define AS73211_OSR_STATUS_MRESOF BIT(14) +#define AS73211_OSR_STATUS_ADCOF BIT(13) +#define AS73211_OSR_STATUS_LDATA BIT(12) +#define AS73211_OSR_STATUS_NDATA BIT(11) +#define AS73211_OSR_STATUS_NOTREADY BIT(10) + +#define AS73211_SAMPLE_FREQ_BASE 1024000 + +#define AS73211_SAMPLE_TIME_NUM 15 +#define AS73211_SAMPLE_TIME_MAX_MS BIT(AS73211_SAMPLE_TIME_NUM - 1) + +/* Available sample frequencies are 1.024MHz multiplied by powers of two. */ +static const int as73211_samp_freq_avail[] = { + AS73211_SAMPLE_FREQ_BASE * 1, + AS73211_SAMPLE_FREQ_BASE * 2, + AS73211_SAMPLE_FREQ_BASE * 4, + AS73211_SAMPLE_FREQ_BASE * 8, +}; + +static const int as73211_hardwaregain_avail[] = { + 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, +}; + +struct as73211_data; + +/** + * struct as73211_spec_dev_data - device-specific data + * @intensity_scale: Function to retrieve intensity scale values. + * @channels: Device channels. + * @num_channels: Number of channels of the device. + */ +struct as73211_spec_dev_data { + int (*intensity_scale)(struct as73211_data *data, int chan, int *val, int *val2); + struct iio_chan_spec const *channels; + int num_channels; +}; + +/** + * struct as73211_data - Instance data for one AS73211 + * @client: I2C client. + * @osr: Cached Operational State Register. + * @creg1: Cached Configuration Register 1. + * @creg2: Cached Configuration Register 2. + * @creg3: Cached Configuration Register 3. + * @mutex: Keeps cached registers in sync with the device. + * @completion: Completion to wait for interrupt. + * @int_time_avail: Available integration times (depend on sampling frequency). + * @spec_dev: device-specific configuration. + */ +struct as73211_data { + struct i2c_client *client; + u8 osr; + u8 creg1; + u8 creg2; + u8 creg3; + struct mutex mutex; + struct completion completion; + int int_time_avail[AS73211_SAMPLE_TIME_NUM * 2]; + const struct as73211_spec_dev_data *spec_dev; +}; + +#define AS73211_COLOR_CHANNEL(_color, _si, _addr) { \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_HARDWAREGAIN) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_HARDWAREGAIN) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .channel2 = IIO_MOD_##_color, \ + .address = _addr, \ + .scan_index = _si, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ +} + +#define AS73211_OFFSET_TEMP_INT (-66) +#define AS73211_OFFSET_TEMP_MICRO 900000 +#define AS73211_SCALE_TEMP_INT 0 +#define AS73211_SCALE_TEMP_MICRO 50000 + +#define AS73211_SCALE_X 277071108 /* nW/m^2 */ +#define AS73211_SCALE_Y 298384270 /* nW/m^2 */ +#define AS73211_SCALE_Z 160241927 /* nW/m^2 */ + +#define AS7331_SCALE_UVA 340000 /* nW/cm^2 */ +#define AS7331_SCALE_UVB 378000 /* nW/cm^2 */ +#define AS7331_SCALE_UVC 166000 /* nW/cm^2 */ + +/* Channel order MUST match devices result register order */ +#define AS73211_SCAN_INDEX_TEMP 0 +#define AS73211_SCAN_INDEX_X 1 +#define AS73211_SCAN_INDEX_Y 2 +#define AS73211_SCAN_INDEX_Z 3 +#define AS73211_SCAN_INDEX_TS 4 + +#define AS73211_SCAN_MASK_COLOR ( \ + BIT(AS73211_SCAN_INDEX_X) | \ + BIT(AS73211_SCAN_INDEX_Y) | \ + BIT(AS73211_SCAN_INDEX_Z)) + +#define AS73211_SCAN_MASK_ALL ( \ + BIT(AS73211_SCAN_INDEX_TEMP) | \ + AS73211_SCAN_MASK_COLOR) + +static const unsigned long as73211_scan_masks[] = { + AS73211_SCAN_MASK_COLOR, + AS73211_SCAN_MASK_ALL, + 0 +}; + +static const struct iio_chan_spec as73211_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .address = AS73211_OUT_TEMP, + .scan_index = AS73211_SCAN_INDEX_TEMP, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + } + }, + AS73211_COLOR_CHANNEL(X, AS73211_SCAN_INDEX_X, AS73211_OUT_MRES1), + AS73211_COLOR_CHANNEL(Y, AS73211_SCAN_INDEX_Y, AS73211_OUT_MRES2), + AS73211_COLOR_CHANNEL(Z, AS73211_SCAN_INDEX_Z, AS73211_OUT_MRES3), + IIO_CHAN_SOFT_TIMESTAMP(AS73211_SCAN_INDEX_TS), +}; + +static const struct iio_chan_spec as7331_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .address = AS73211_OUT_TEMP, + .scan_index = AS73211_SCAN_INDEX_TEMP, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + } + }, + AS73211_COLOR_CHANNEL(LIGHT_UVA, AS73211_SCAN_INDEX_X, AS73211_OUT_MRES1), + AS73211_COLOR_CHANNEL(LIGHT_UVB, AS73211_SCAN_INDEX_Y, AS73211_OUT_MRES2), + AS73211_COLOR_CHANNEL(LIGHT_DUV, AS73211_SCAN_INDEX_Z, AS73211_OUT_MRES3), + IIO_CHAN_SOFT_TIMESTAMP(AS73211_SCAN_INDEX_TS), +}; + +static unsigned int as73211_integration_time_1024cyc(struct as73211_data *data) +{ + /* + * Return integration time in units of 1024 clock cycles. Integration time + * in CREG1 is in powers of 2 (x 1024 cycles). + */ + return BIT(FIELD_GET(AS73211_CREG1_TIME_MASK, data->creg1)); +} + +static unsigned int as73211_integration_time_us(struct as73211_data *data, + unsigned int integration_time_1024cyc) +{ + /* + * f_samp is configured in CREG3 in powers of 2 (x 1.024 MHz) + * t_cycl is configured in CREG1 in powers of 2 (x 1024 cycles) + * t_int_us = 1 / (f_samp) * t_cycl * US_PER_SEC + * = 1 / (2^CREG3_CCLK * 1,024,000) * 2^CREG1_CYCLES * 1,024 * US_PER_SEC + * = 2^(-CREG3_CCLK) * 2^CREG1_CYCLES * 1,000 + * In order to get rid of negative exponents, we extend the "fraction" + * by 2^3 (CREG3_CCLK,max = 3) + * t_int_us = 2^(3-CREG3_CCLK) * 2^CREG1_CYCLES * 125 + */ + return BIT(3 - FIELD_GET(AS73211_CREG3_CCLK_MASK, data->creg3)) * + integration_time_1024cyc * 125; +} + +static void as73211_integration_time_calc_avail(struct as73211_data *data) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(data->int_time_avail) / 2; i++) { + unsigned int time_us = as73211_integration_time_us(data, BIT(i)); + + data->int_time_avail[i * 2 + 0] = time_us / USEC_PER_SEC; + data->int_time_avail[i * 2 + 1] = time_us % USEC_PER_SEC; + } +} + +static unsigned int as73211_gain(struct as73211_data *data) +{ + /* gain can be calculated from CREG1 as 2^(11 - CREG1_GAIN) */ + return BIT(AS73211_CREG1_GAIN_1 - FIELD_GET(AS73211_CREG1_GAIN_MASK, data->creg1)); +} + +/* must be called with as73211_data::mutex held. */ +static int as73211_req_data(struct as73211_data *data) +{ + unsigned int time_us = as73211_integration_time_us(data, + as73211_integration_time_1024cyc(data)); + struct device *dev = &data->client->dev; + union i2c_smbus_data smbus_data; + u16 osr_status; + int ret; + + if (data->client->irq) + reinit_completion(&data->completion); + + /* + * During measurement, there should be no traffic on the i2c bus as the + * electrical noise would disturb the measurement process. + */ + i2c_lock_bus(data->client->adapter, I2C_LOCK_SEGMENT); + + data->osr &= ~AS73211_OSR_DOS_MASK; + data->osr |= AS73211_OSR_DOS_MEASURE | AS73211_OSR_SS; + + smbus_data.byte = data->osr; + ret = __i2c_smbus_xfer(data->client->adapter, data->client->addr, + data->client->flags, I2C_SMBUS_WRITE, + AS73211_REG_OSR, I2C_SMBUS_BYTE_DATA, &smbus_data); + if (ret < 0) { + i2c_unlock_bus(data->client->adapter, I2C_LOCK_SEGMENT); + return ret; + } + + /* + * Reset AS73211_OSR_SS (is self clearing) in order to avoid unintentional + * triggering of further measurements later. + */ + data->osr &= ~AS73211_OSR_SS; + + /* + * Add 33% extra margin for the timeout. fclk,min = fclk,typ - 27%. + */ + time_us += time_us / 3; + if (data->client->irq) { + ret = wait_for_completion_timeout(&data->completion, usecs_to_jiffies(time_us)); + if (!ret) { + dev_err(dev, "timeout waiting for READY IRQ\n"); + i2c_unlock_bus(data->client->adapter, I2C_LOCK_SEGMENT); + return -ETIMEDOUT; + } + } else { + /* Wait integration time */ + usleep_range(time_us, 2 * time_us); + } + + i2c_unlock_bus(data->client->adapter, I2C_LOCK_SEGMENT); + + ret = i2c_smbus_read_word_data(data->client, AS73211_OUT_OSR_STATUS); + if (ret < 0) + return ret; + + osr_status = ret; + if (osr_status != (AS73211_OSR_DOS_MEASURE | AS73211_OSR_STATUS_NDATA)) { + if (osr_status & AS73211_OSR_SS) { + dev_err(dev, "%s() Measurement has not stopped\n", __func__); + return -ETIME; + } + if (osr_status & AS73211_OSR_STATUS_NOTREADY) { + dev_err(dev, "%s() Data is not ready\n", __func__); + return -ENODATA; + } + if (!(osr_status & AS73211_OSR_STATUS_NDATA)) { + dev_err(dev, "%s() No new data available\n", __func__); + return -ENODATA; + } + if (osr_status & AS73211_OSR_STATUS_LDATA) { + dev_err(dev, "%s() Result buffer overrun\n", __func__); + return -ENOBUFS; + } + if (osr_status & AS73211_OSR_STATUS_ADCOF) { + dev_err(dev, "%s() ADC overflow\n", __func__); + return -EOVERFLOW; + } + if (osr_status & AS73211_OSR_STATUS_MRESOF) { + dev_err(dev, "%s() Measurement result overflow\n", __func__); + return -EOVERFLOW; + } + if (osr_status & AS73211_OSR_STATUS_OUTCONVOF) { + dev_err(dev, "%s() Timer overflow\n", __func__); + return -EOVERFLOW; + } + dev_err(dev, "%s() Unexpected status value\n", __func__); + return -EIO; + } + + return 0; +} + +static int as73211_intensity_scale(struct as73211_data *data, int chan, + int *val, int *val2) +{ + switch (chan) { + case IIO_MOD_X: + *val = AS73211_SCALE_X; + break; + case IIO_MOD_Y: + *val = AS73211_SCALE_Y; + break; + case IIO_MOD_Z: + *val = AS73211_SCALE_Z; + break; + default: + return -EINVAL; + } + *val2 = as73211_integration_time_1024cyc(data) * as73211_gain(data); + + return IIO_VAL_FRACTIONAL; +} + +static int as7331_intensity_scale(struct as73211_data *data, int chan, + int *val, int *val2) +{ + switch (chan) { + case IIO_MOD_LIGHT_UVA: + *val = AS7331_SCALE_UVA; + break; + case IIO_MOD_LIGHT_UVB: + *val = AS7331_SCALE_UVB; + break; + case IIO_MOD_LIGHT_DUV: + *val = AS7331_SCALE_UVC; + break; + default: + return -EINVAL; + } + *val2 = as73211_integration_time_1024cyc(data) * as73211_gain(data); + + return IIO_VAL_FRACTIONAL; +} + +static int as73211_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct as73211_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = as73211_req_data(data); + if (ret < 0) { + iio_device_release_direct(indio_dev); + return ret; + } + + ret = i2c_smbus_read_word_data(data->client, chan->address); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + + *val = ret; + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_OFFSET: + *val = AS73211_OFFSET_TEMP_INT; + *val2 = AS73211_OFFSET_TEMP_MICRO; + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + *val = AS73211_SCALE_TEMP_INT; + *val2 = AS73211_SCALE_TEMP_MICRO; + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_INTENSITY: + return data->spec_dev->intensity_scale(data, chan->channel2, + val, val2); + + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_SAMP_FREQ: + /* f_samp is configured in CREG3 in powers of 2 (x 1.024 MHz) */ + *val = BIT(FIELD_GET(AS73211_CREG3_CCLK_MASK, data->creg3)) * + AS73211_SAMPLE_FREQ_BASE; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_HARDWAREGAIN: + *val = as73211_gain(data); + return IIO_VAL_INT; + + case IIO_CHAN_INFO_INT_TIME: { + unsigned int time_us; + + mutex_lock(&data->mutex); + time_us = as73211_integration_time_us(data, as73211_integration_time_1024cyc(data)); + mutex_unlock(&data->mutex); + *val = time_us / USEC_PER_SEC; + *val2 = time_us % USEC_PER_SEC; + return IIO_VAL_INT_PLUS_MICRO; + + default: + return -EINVAL; + }} +} + +static int as73211_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, long mask) +{ + struct as73211_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *length = ARRAY_SIZE(as73211_samp_freq_avail); + *vals = as73211_samp_freq_avail; + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + + case IIO_CHAN_INFO_HARDWAREGAIN: + *length = ARRAY_SIZE(as73211_hardwaregain_avail); + *vals = as73211_hardwaregain_avail; + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + + case IIO_CHAN_INFO_INT_TIME: + *length = ARRAY_SIZE(data->int_time_avail); + *vals = data->int_time_avail; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + + default: + return -EINVAL; + } +} + +static int _as73211_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan __always_unused, + int val, int val2, long mask) +{ + struct as73211_data *data = iio_priv(indio_dev); + int ret; + + /* Need to switch to config mode ... */ + if ((data->osr & AS73211_OSR_DOS_MASK) != AS73211_OSR_DOS_CONFIG) { + data->osr &= ~AS73211_OSR_DOS_MASK; + data->osr |= AS73211_OSR_DOS_CONFIG; + + ret = i2c_smbus_write_byte_data(data->client, AS73211_REG_OSR, data->osr); + if (ret < 0) + return ret; + } + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: { + int reg_bits, freq_kHz = val / HZ_PER_KHZ; /* 1024, 2048, ... */ + + /* val must be 1024 * 2^x */ + if (val < 0 || (freq_kHz * HZ_PER_KHZ) != val || + !is_power_of_2(freq_kHz) || val2) + return -EINVAL; + + /* f_samp is configured in CREG3 in powers of 2 (x 1.024 MHz (=2^10)) */ + reg_bits = ilog2(freq_kHz) - 10; + if (!FIELD_FIT(AS73211_CREG3_CCLK_MASK, reg_bits)) + return -EINVAL; + + data->creg3 &= ~AS73211_CREG3_CCLK_MASK; + data->creg3 |= FIELD_PREP(AS73211_CREG3_CCLK_MASK, reg_bits); + as73211_integration_time_calc_avail(data); + + ret = i2c_smbus_write_byte_data(data->client, AS73211_REG_CREG3, data->creg3); + if (ret < 0) + return ret; + + return 0; + } + case IIO_CHAN_INFO_HARDWAREGAIN: { + unsigned int reg_bits; + + if (val < 0 || !is_power_of_2(val) || val2) + return -EINVAL; + + /* gain can be calculated from CREG1 as 2^(11 - CREG1_GAIN) */ + reg_bits = AS73211_CREG1_GAIN_1 - ilog2(val); + if (!FIELD_FIT(AS73211_CREG1_GAIN_MASK, reg_bits)) + return -EINVAL; + + data->creg1 &= ~AS73211_CREG1_GAIN_MASK; + data->creg1 |= FIELD_PREP(AS73211_CREG1_GAIN_MASK, reg_bits); + + ret = i2c_smbus_write_byte_data(data->client, AS73211_REG_CREG1, data->creg1); + if (ret < 0) + return ret; + + return 0; + } + case IIO_CHAN_INFO_INT_TIME: { + int val_us = val * USEC_PER_SEC + val2; + int time_ms; + int reg_bits; + + /* f_samp is configured in CREG3 in powers of 2 (x 1.024 MHz) */ + int f_samp_1_024mhz = BIT(FIELD_GET(AS73211_CREG3_CCLK_MASK, data->creg3)); + + /* + * time_ms = time_us * US_PER_MS * f_samp_1_024mhz / MHZ_PER_HZ + * = time_us * f_samp_1_024mhz / 1000 + */ + time_ms = (val_us * f_samp_1_024mhz) / 1000; /* 1 ms, 2 ms, ... (power of two) */ + if (time_ms < 0 || !is_power_of_2(time_ms) || time_ms > AS73211_SAMPLE_TIME_MAX_MS) + return -EINVAL; + + reg_bits = ilog2(time_ms); + if (!FIELD_FIT(AS73211_CREG1_TIME_MASK, reg_bits)) + return -EINVAL; /* not possible due to previous tests */ + + data->creg1 &= ~AS73211_CREG1_TIME_MASK; + data->creg1 |= FIELD_PREP(AS73211_CREG1_TIME_MASK, reg_bits); + + ret = i2c_smbus_write_byte_data(data->client, AS73211_REG_CREG1, data->creg1); + if (ret < 0) + return ret; + + return 0; + + default: + return -EINVAL; + }} +} + +static int as73211_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct as73211_data *data = iio_priv(indio_dev); + int ret; + + guard(mutex)(&data->mutex); + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = _as73211_write_raw(indio_dev, chan, val, val2, mask); + iio_device_release_direct(indio_dev); + + return ret; +} + +static irqreturn_t as73211_ready_handler(int irq __always_unused, void *priv) +{ + struct as73211_data *data = iio_priv(priv); + + complete(&data->completion); + + return IRQ_HANDLED; +} + +static irqreturn_t as73211_trigger_handler(int irq __always_unused, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct as73211_data *data = iio_priv(indio_dev); + struct { + __le16 chan[4]; + aligned_s64 ts; + } scan = { }; + int data_result, ret; + + mutex_lock(&data->mutex); + + data_result = as73211_req_data(data); + if (data_result < 0 && data_result != -EOVERFLOW) + goto done; /* don't push any data for errors other than EOVERFLOW */ + + if (*indio_dev->active_scan_mask == AS73211_SCAN_MASK_ALL) { + /* Optimization for reading all (color + temperature) channels */ + u8 addr = as73211_channels[0].address; + struct i2c_msg msgs[] = { + { + .addr = data->client->addr, + .flags = 0, + .len = 1, + .buf = &addr, + }, + { + .addr = data->client->addr, + .flags = I2C_M_RD, + .len = sizeof(scan.chan), + .buf = (u8 *)&scan.chan, + }, + }; + + ret = i2c_transfer(data->client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + goto done; + } else { + /* Optimization for reading only color channels */ + + /* AS73211 starts reading at address 2 */ + ret = i2c_master_recv(data->client, + (char *)&scan.chan[0], 3 * sizeof(scan.chan[0])); + if (ret < 0) + goto done; + } + + if (data_result) { + /* + * Saturate all channels (in case of overflows). Temperature channel + * is not affected by overflows. + */ + if (*indio_dev->active_scan_mask == AS73211_SCAN_MASK_ALL) { + scan.chan[1] = cpu_to_le16(U16_MAX); + scan.chan[2] = cpu_to_le16(U16_MAX); + scan.chan[3] = cpu_to_le16(U16_MAX); + } else { + scan.chan[0] = cpu_to_le16(U16_MAX); + scan.chan[1] = cpu_to_le16(U16_MAX); + scan.chan[2] = cpu_to_le16(U16_MAX); + } + } + + iio_push_to_buffers_with_timestamp(indio_dev, &scan, iio_get_time_ns(indio_dev)); + +done: + mutex_unlock(&data->mutex); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_info as73211_info = { + .read_raw = as73211_read_raw, + .read_avail = as73211_read_avail, + .write_raw = as73211_write_raw, +}; + +static int as73211_power(struct iio_dev *indio_dev, bool state) +{ + struct as73211_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + + if (state) + data->osr &= ~AS73211_OSR_PD; + else + data->osr |= AS73211_OSR_PD; + + ret = i2c_smbus_write_byte_data(data->client, AS73211_REG_OSR, data->osr); + + mutex_unlock(&data->mutex); + + if (ret < 0) + return ret; + + return 0; +} + +static void as73211_power_disable(void *data) +{ + struct iio_dev *indio_dev = data; + + as73211_power(indio_dev, false); +} + +static int as73211_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct as73211_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + data->spec_dev = i2c_get_match_data(client); + if (!data->spec_dev) + return -EINVAL; + + mutex_init(&data->mutex); + init_completion(&data->completion); + + indio_dev->info = &as73211_info; + indio_dev->name = AS73211_DRV_NAME; + indio_dev->channels = data->spec_dev->channels; + indio_dev->num_channels = data->spec_dev->num_channels; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->available_scan_masks = as73211_scan_masks; + + ret = i2c_smbus_read_byte_data(data->client, AS73211_REG_OSR); + if (ret < 0) + return ret; + data->osr = ret; + + /* reset device */ + data->osr |= AS73211_OSR_SW_RES; + ret = i2c_smbus_write_byte_data(data->client, AS73211_REG_OSR, data->osr); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(data->client, AS73211_REG_OSR); + if (ret < 0) + return ret; + data->osr = ret; + + /* + * Reading AGEN is only possible after reset (AGEN is not available if + * device is in measurement mode). + */ + ret = i2c_smbus_read_byte_data(data->client, AS73211_REG_AGEN); + if (ret < 0) + return ret; + + /* At the time of writing this driver, only DEVID 2 and MUT 1 are known. */ + if ((ret & AS73211_AGEN_DEVID_MASK) != AS73211_AGEN_DEVID(2) || + (ret & AS73211_AGEN_MUT_MASK) != AS73211_AGEN_MUT(1)) + return -ENODEV; + + ret = i2c_smbus_read_byte_data(data->client, AS73211_REG_CREG1); + if (ret < 0) + return ret; + data->creg1 = ret; + + ret = i2c_smbus_read_byte_data(data->client, AS73211_REG_CREG2); + if (ret < 0) + return ret; + data->creg2 = ret; + + ret = i2c_smbus_read_byte_data(data->client, AS73211_REG_CREG3); + if (ret < 0) + return ret; + data->creg3 = ret; + as73211_integration_time_calc_avail(data); + + ret = as73211_power(indio_dev, true); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(dev, as73211_power_disable, indio_dev); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, as73211_trigger_handler, NULL); + if (ret) + return ret; + + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, + as73211_ready_handler, + IRQF_ONESHOT, + client->name, indio_dev); + if (ret) + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} + +static int as73211_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + + return as73211_power(indio_dev, false); +} + +static int as73211_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + + return as73211_power(indio_dev, true); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(as73211_pm_ops, as73211_suspend, + as73211_resume); + +static const struct as73211_spec_dev_data as73211_spec = { + .intensity_scale = as73211_intensity_scale, + .channels = as73211_channels, + .num_channels = ARRAY_SIZE(as73211_channels), +}; + +static const struct as73211_spec_dev_data as7331_spec = { + .intensity_scale = as7331_intensity_scale, + .channels = as7331_channels, + .num_channels = ARRAY_SIZE(as7331_channels), +}; + +static const struct of_device_id as73211_of_match[] = { + { .compatible = "ams,as73211", &as73211_spec }, + { .compatible = "ams,as7331", &as7331_spec }, + { } +}; +MODULE_DEVICE_TABLE(of, as73211_of_match); + +static const struct i2c_device_id as73211_id[] = { + { .name = "as73211", .driver_data = (kernel_ulong_t)&as73211_spec }, + { .name = "as7331", .driver_data = (kernel_ulong_t)&as7331_spec }, + { } +}; +MODULE_DEVICE_TABLE(i2c, as73211_id); + +static struct i2c_driver as73211_driver = { + .driver = { + .name = AS73211_DRV_NAME, + .of_match_table = as73211_of_match, + .pm = pm_sleep_ptr(&as73211_pm_ops), + }, + .probe = as73211_probe, + .id_table = as73211_id, +}; +module_i2c_driver(as73211_driver); + +MODULE_AUTHOR("Christian Eggers "); +MODULE_DESCRIPTION("AS73211 XYZ True Color Sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/bh1745.c b/drivers/iio/light/bh1745.c new file mode 100644 index 000000000..2b8ff3607 --- /dev/null +++ b/drivers/iio/light/bh1745.c @@ -0,0 +1,900 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ROHM BH1745 digital colour sensor driver + * + * Copyright (C) Mudit Sharma + * + * 7-bit I2C slave addresses: + * 0x38 (ADDR pin low) + * 0x39 (ADDR pin high) + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* BH1745 configuration registers */ + +/* System control */ +#define BH1745_SYS_CTRL 0x40 +#define BH1745_SYS_CTRL_SW_RESET BIT(7) +#define BH1745_SYS_CTRL_INTR_RESET BIT(6) +#define BH1745_SYS_CTRL_PART_ID_MASK GENMASK(5, 0) +#define BH1745_PART_ID 0x0B + +/* Mode control 1 */ +#define BH1745_MODE_CTRL1 0x41 +#define BH1745_CTRL1_MEASUREMENT_TIME_MASK GENMASK(2, 0) + +/* Mode control 2 */ +#define BH1745_MODE_CTRL2 0x42 +#define BH1745_CTRL2_RGBC_EN BIT(4) +#define BH1745_CTRL2_ADC_GAIN_MASK GENMASK(1, 0) + +/* Interrupt */ +#define BH1745_INTR 0x60 +#define BH1745_INTR_STATUS BIT(7) +#define BH1745_INTR_SOURCE_MASK GENMASK(3, 2) +#define BH1745_INTR_ENABLE BIT(0) + +#define BH1745_PERSISTENCE 0x61 + +/* Threshold high */ +#define BH1745_TH_LSB 0x62 +#define BH1745_TH_MSB 0x63 + +/* Threshold low */ +#define BH1745_TL_LSB 0x64 +#define BH1745_TL_MSB 0x65 + +/* BH1745 data output regs */ +#define BH1745_RED_LSB 0x50 +#define BH1745_RED_MSB 0x51 +#define BH1745_GREEN_LSB 0x52 +#define BH1745_GREEN_MSB 0x53 +#define BH1745_BLUE_LSB 0x54 +#define BH1745_BLUE_MSB 0x55 +#define BH1745_CLEAR_LSB 0x56 +#define BH1745_CLEAR_MSB 0x57 + +#define BH1745_MANU_ID_REG 0x92 + +/* From 16x max HW gain and 32x max integration time */ +#define BH1745_MAX_GAIN 512 + +enum bh1745_int_source { + BH1745_INTR_SOURCE_RED, + BH1745_INTR_SOURCE_GREEN, + BH1745_INTR_SOURCE_BLUE, + BH1745_INTR_SOURCE_CLEAR, +}; + +enum bh1745_gain { + BH1745_ADC_GAIN_1X, + BH1745_ADC_GAIN_2X, + BH1745_ADC_GAIN_16X, +}; + +enum bh1745_measurement_time { + BH1745_MEASUREMENT_TIME_160MS, + BH1745_MEASUREMENT_TIME_320MS, + BH1745_MEASUREMENT_TIME_640MS, + BH1745_MEASUREMENT_TIME_1280MS, + BH1745_MEASUREMENT_TIME_2560MS, + BH1745_MEASUREMENT_TIME_5120MS, +}; + +enum bh1745_presistence_value { + BH1745_PRESISTENCE_UPDATE_TOGGLE, + BH1745_PRESISTENCE_UPDATE_EACH_MEASUREMENT, + BH1745_PRESISTENCE_UPDATE_FOUR_MEASUREMENT, + BH1745_PRESISTENCE_UPDATE_EIGHT_MEASUREMENT, +}; + +static const struct iio_gain_sel_pair bh1745_gain[] = { + GAIN_SCALE_GAIN(1, BH1745_ADC_GAIN_1X), + GAIN_SCALE_GAIN(2, BH1745_ADC_GAIN_2X), + GAIN_SCALE_GAIN(16, BH1745_ADC_GAIN_16X), +}; + +static const struct iio_itime_sel_mul bh1745_itimes[] = { + GAIN_SCALE_ITIME_US(5120000, BH1745_MEASUREMENT_TIME_5120MS, 32), + GAIN_SCALE_ITIME_US(2560000, BH1745_MEASUREMENT_TIME_2560MS, 16), + GAIN_SCALE_ITIME_US(1280000, BH1745_MEASUREMENT_TIME_1280MS, 8), + GAIN_SCALE_ITIME_US(640000, BH1745_MEASUREMENT_TIME_640MS, 4), + GAIN_SCALE_ITIME_US(320000, BH1745_MEASUREMENT_TIME_320MS, 2), + GAIN_SCALE_ITIME_US(160000, BH1745_MEASUREMENT_TIME_160MS, 1), +}; + +struct bh1745_data { + /* + * Lock to prevent device setting update or read before + * related calculations are completed + */ + struct mutex lock; + struct regmap *regmap; + struct device *dev; + struct iio_trigger *trig; + struct iio_gts gts; +}; + +static const struct regmap_range bh1745_volatile_ranges[] = { + regmap_reg_range(BH1745_MODE_CTRL2, BH1745_MODE_CTRL2), /* VALID */ + regmap_reg_range(BH1745_RED_LSB, BH1745_CLEAR_MSB), /* Data */ + regmap_reg_range(BH1745_INTR, BH1745_INTR), /* Interrupt */ +}; + +static const struct regmap_access_table bh1745_volatile_regs = { + .yes_ranges = bh1745_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(bh1745_volatile_ranges), +}; + +static const struct regmap_range bh1745_readable_ranges[] = { + regmap_reg_range(BH1745_SYS_CTRL, BH1745_MODE_CTRL2), + regmap_reg_range(BH1745_RED_LSB, BH1745_CLEAR_MSB), + regmap_reg_range(BH1745_INTR, BH1745_INTR), + regmap_reg_range(BH1745_PERSISTENCE, BH1745_TL_MSB), + regmap_reg_range(BH1745_MANU_ID_REG, BH1745_MANU_ID_REG), +}; + +static const struct regmap_access_table bh1745_readable_regs = { + .yes_ranges = bh1745_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(bh1745_readable_ranges), +}; + +static const struct regmap_range bh1745_writable_ranges[] = { + regmap_reg_range(BH1745_SYS_CTRL, BH1745_MODE_CTRL2), + regmap_reg_range(BH1745_INTR, BH1745_INTR), + regmap_reg_range(BH1745_PERSISTENCE, BH1745_TL_MSB), +}; + +static const struct regmap_access_table bh1745_writable_regs = { + .yes_ranges = bh1745_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(bh1745_writable_ranges), +}; + +static const struct regmap_config bh1745_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = BH1745_MANU_ID_REG, + .cache_type = REGCACHE_RBTREE, + .volatile_table = &bh1745_volatile_regs, + .wr_table = &bh1745_writable_regs, + .rd_table = &bh1745_readable_regs, +}; + +static const struct iio_event_spec bh1745_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_shared_by_type = BIT(IIO_EV_INFO_PERIOD), + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +#define BH1745_CHANNEL(_colour, _si, _addr) \ + { \ + .type = IIO_INTENSITY, .modified = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .event_spec = bh1745_event_spec, \ + .num_event_specs = ARRAY_SIZE(bh1745_event_spec), \ + .channel2 = IIO_MOD_LIGHT_##_colour, .address = _addr, \ + .scan_index = _si, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ + } + +static const struct iio_chan_spec bh1745_channels[] = { + BH1745_CHANNEL(RED, 0, BH1745_RED_LSB), + BH1745_CHANNEL(GREEN, 1, BH1745_GREEN_LSB), + BH1745_CHANNEL(BLUE, 2, BH1745_BLUE_LSB), + BH1745_CHANNEL(CLEAR, 3, BH1745_CLEAR_LSB), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static int bh1745_reset(struct bh1745_data *data) +{ + return regmap_set_bits(data->regmap, BH1745_SYS_CTRL, + BH1745_SYS_CTRL_SW_RESET | + BH1745_SYS_CTRL_INTR_RESET); +} + +static int bh1745_power_on(struct bh1745_data *data) +{ + return regmap_set_bits(data->regmap, BH1745_MODE_CTRL2, + BH1745_CTRL2_RGBC_EN); +} + +static void bh1745_power_off(void *data_ptr) +{ + struct bh1745_data *data = data_ptr; + struct device *dev = data->dev; + int ret; + + ret = regmap_clear_bits(data->regmap, BH1745_MODE_CTRL2, + BH1745_CTRL2_RGBC_EN); + if (ret) + dev_err(dev, "Failed to turn off device\n"); +} + +static int bh1745_get_scale(struct bh1745_data *data, int *val, int *val2) +{ + int ret; + int value; + int gain_sel, int_time_sel; + int gain; + const struct iio_itime_sel_mul *int_time; + + ret = regmap_read(data->regmap, BH1745_MODE_CTRL2, &value); + if (ret) + return ret; + + gain_sel = FIELD_GET(BH1745_CTRL2_ADC_GAIN_MASK, value); + gain = iio_gts_find_gain_by_sel(&data->gts, gain_sel); + + ret = regmap_read(data->regmap, BH1745_MODE_CTRL1, &value); + if (ret) + return ret; + + int_time_sel = FIELD_GET(BH1745_CTRL1_MEASUREMENT_TIME_MASK, value); + int_time = iio_gts_find_itime_by_sel(&data->gts, int_time_sel); + + return iio_gts_get_scale(&data->gts, gain, int_time->time_us, val, + val2); +} + +static int bh1745_set_scale(struct bh1745_data *data, int val) +{ + struct device *dev = data->dev; + int ret; + int value; + int hw_gain_sel, current_int_time_sel, new_int_time_sel; + + ret = regmap_read(data->regmap, BH1745_MODE_CTRL1, &value); + if (ret) + return ret; + + current_int_time_sel = FIELD_GET(BH1745_CTRL1_MEASUREMENT_TIME_MASK, + value); + ret = iio_gts_find_gain_sel_for_scale_using_time(&data->gts, + current_int_time_sel, + val, 0, &hw_gain_sel); + if (ret) { + for (int i = 0; i < data->gts.num_itime; i++) { + new_int_time_sel = data->gts.itime_table[i].sel; + + if (new_int_time_sel == current_int_time_sel) + continue; + + ret = iio_gts_find_gain_sel_for_scale_using_time(&data->gts, + new_int_time_sel, + val, 0, + &hw_gain_sel); + if (!ret) + break; + } + + if (ret) { + dev_dbg(dev, "Unsupported scale value requested: %d\n", + val); + return -EINVAL; + } + + ret = regmap_write_bits(data->regmap, BH1745_MODE_CTRL1, + BH1745_CTRL1_MEASUREMENT_TIME_MASK, + new_int_time_sel); + if (ret) + return ret; + } + + return regmap_write_bits(data->regmap, BH1745_MODE_CTRL2, + BH1745_CTRL2_ADC_GAIN_MASK, hw_gain_sel); +} + +static int bh1745_get_int_time(struct bh1745_data *data, int *val) +{ + int ret; + int value; + int int_time, int_time_sel; + + ret = regmap_read(data->regmap, BH1745_MODE_CTRL1, &value); + if (ret) + return ret; + + int_time_sel = FIELD_GET(BH1745_CTRL1_MEASUREMENT_TIME_MASK, value); + int_time = iio_gts_find_int_time_by_sel(&data->gts, int_time_sel); + if (int_time < 0) + return int_time; + + *val = int_time; + + return 0; +} + +static int bh1745_set_int_time(struct bh1745_data *data, int val, int val2) +{ + struct device *dev = data->dev; + int ret; + int value; + int current_int_time, current_hwgain_sel, current_hwgain; + int new_hwgain, new_hwgain_sel, new_int_time_sel; + int req_int_time = (1000000 * val) + val2; + + if (!iio_gts_valid_time(&data->gts, req_int_time)) { + dev_dbg(dev, "Unsupported integration time requested: %d\n", + req_int_time); + return -EINVAL; + } + + ret = bh1745_get_int_time(data, ¤t_int_time); + if (ret) + return ret; + + if (current_int_time == req_int_time) + return 0; + + ret = regmap_read(data->regmap, BH1745_MODE_CTRL2, &value); + if (ret) + return ret; + + current_hwgain_sel = FIELD_GET(BH1745_CTRL2_ADC_GAIN_MASK, value); + current_hwgain = iio_gts_find_gain_by_sel(&data->gts, + current_hwgain_sel); + ret = iio_gts_find_new_gain_by_old_gain_time(&data->gts, current_hwgain, + current_int_time, + req_int_time, + &new_hwgain); + if (new_hwgain < 0) { + dev_dbg(dev, "No corresponding gain for requested integration time\n"); + return ret; + } + + if (ret) { + bool in_range; + + new_hwgain = iio_find_closest_gain_low(&data->gts, new_hwgain, + &in_range); + if (new_hwgain < 0) { + new_hwgain = iio_gts_get_min_gain(&data->gts); + if (new_hwgain < 0) + return ret; + } + + if (!in_range) + dev_dbg(dev, "Optimal gain out of range\n"); + + dev_dbg(dev, "Scale changed, new hw_gain %d\n", new_hwgain); + } + + new_hwgain_sel = iio_gts_find_sel_by_gain(&data->gts, new_hwgain); + if (new_hwgain_sel < 0) + return new_hwgain_sel; + + ret = regmap_write_bits(data->regmap, BH1745_MODE_CTRL2, + BH1745_CTRL2_ADC_GAIN_MASK, + new_hwgain_sel); + if (ret) + return ret; + + new_int_time_sel = iio_gts_find_sel_by_int_time(&data->gts, + req_int_time); + if (new_int_time_sel < 0) + return new_int_time_sel; + + return regmap_write_bits(data->regmap, BH1745_MODE_CTRL1, + BH1745_CTRL1_MEASUREMENT_TIME_MASK, + new_int_time_sel); +} + +static int bh1745_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bh1745_data *data = iio_priv(indio_dev); + int ret; + int value; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = regmap_bulk_read(data->regmap, chan->address, &value, 2); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + *val = value; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: { + guard(mutex)(&data->lock); + ret = bh1745_get_scale(data, val, val2); + if (ret) + return ret; + + return IIO_VAL_INT; + } + + case IIO_CHAN_INFO_INT_TIME: { + guard(mutex)(&data->lock); + *val = 0; + ret = bh1745_get_int_time(data, val2); + if (ret) + return 0; + + return IIO_VAL_INT_PLUS_MICRO; + } + + default: + return -EINVAL; + } +} + +static int bh1745_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bh1745_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return bh1745_set_scale(data, val); + + case IIO_CHAN_INFO_INT_TIME: + return bh1745_set_int_time(data, val, val2); + + default: + return -EINVAL; + } +} + +static int bh1745_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT; + + case IIO_CHAN_INFO_INT_TIME: + return IIO_VAL_INT_PLUS_MICRO; + + default: + return -EINVAL; + } +} + +static int bh1745_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, int *val2) +{ + struct bh1745_data *data = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + ret = regmap_bulk_read(data->regmap, BH1745_TH_LSB, + val, 2); + if (ret) + return ret; + + return IIO_VAL_INT; + + case IIO_EV_DIR_FALLING: + ret = regmap_bulk_read(data->regmap, BH1745_TL_LSB, + val, 2); + if (ret) + return ret; + + return IIO_VAL_INT; + + default: + return -EINVAL; + } + + case IIO_EV_INFO_PERIOD: + ret = regmap_read(data->regmap, BH1745_PERSISTENCE, val); + if (ret) + return ret; + + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int bh1745_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, int val2) +{ + struct bh1745_data *data = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + if (val < 0x0 || val > 0xFFFF) + return -EINVAL; + + switch (dir) { + case IIO_EV_DIR_RISING: + ret = regmap_bulk_write(data->regmap, BH1745_TH_LSB, + &val, 2); + if (ret) + return ret; + + return IIO_VAL_INT; + + case IIO_EV_DIR_FALLING: + ret = regmap_bulk_write(data->regmap, BH1745_TL_LSB, + &val, 2); + if (ret) + return ret; + + return IIO_VAL_INT; + + default: + return -EINVAL; + } + + case IIO_EV_INFO_PERIOD: + if (val < BH1745_PRESISTENCE_UPDATE_TOGGLE || + val > BH1745_PRESISTENCE_UPDATE_EIGHT_MEASUREMENT) + return -EINVAL; + ret = regmap_write(data->regmap, BH1745_PERSISTENCE, val); + if (ret) + return ret; + + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int bh1745_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct bh1745_data *data = iio_priv(indio_dev); + int ret; + int value; + int int_src; + + ret = regmap_read(data->regmap, BH1745_INTR, &value); + if (ret) + return ret; + + if (!FIELD_GET(BH1745_INTR_ENABLE, value)) + return 0; + + int_src = FIELD_GET(BH1745_INTR_SOURCE_MASK, value); + + switch (chan->channel2) { + case IIO_MOD_LIGHT_RED: + if (int_src == BH1745_INTR_SOURCE_RED) + return 1; + return 0; + + case IIO_MOD_LIGHT_GREEN: + if (int_src == BH1745_INTR_SOURCE_GREEN) + return 1; + return 0; + + case IIO_MOD_LIGHT_BLUE: + if (int_src == BH1745_INTR_SOURCE_BLUE) + return 1; + return 0; + + case IIO_MOD_LIGHT_CLEAR: + if (int_src == BH1745_INTR_SOURCE_CLEAR) + return 1; + return 0; + + default: + return -EINVAL; + } +} + +static int bh1745_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct bh1745_data *data = iio_priv(indio_dev); + int value; + + if (!state) + return regmap_clear_bits(data->regmap, + BH1745_INTR, BH1745_INTR_ENABLE); + + /* Latch is always enabled when enabling interrupt */ + value = BH1745_INTR_ENABLE; + + switch (chan->channel2) { + case IIO_MOD_LIGHT_RED: + return regmap_write(data->regmap, BH1745_INTR, + value | FIELD_PREP(BH1745_INTR_SOURCE_MASK, + BH1745_INTR_SOURCE_RED)); + + case IIO_MOD_LIGHT_GREEN: + return regmap_write(data->regmap, BH1745_INTR, + value | FIELD_PREP(BH1745_INTR_SOURCE_MASK, + BH1745_INTR_SOURCE_GREEN)); + + case IIO_MOD_LIGHT_BLUE: + return regmap_write(data->regmap, BH1745_INTR, + value | FIELD_PREP(BH1745_INTR_SOURCE_MASK, + BH1745_INTR_SOURCE_BLUE)); + + case IIO_MOD_LIGHT_CLEAR: + return regmap_write(data->regmap, BH1745_INTR, + value | FIELD_PREP(BH1745_INTR_SOURCE_MASK, + BH1745_INTR_SOURCE_CLEAR)); + + default: + return -EINVAL; + } +} + +static int bh1745_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, const int **vals, + int *type, int *length, long mask) +{ + struct bh1745_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return iio_gts_avail_times(&data->gts, vals, type, length); + + case IIO_CHAN_INFO_SCALE: + return iio_gts_all_avail_scales(&data->gts, vals, type, length); + + default: + return -EINVAL; + } +} + +static const struct iio_info bh1745_info = { + .read_raw = bh1745_read_raw, + .write_raw = bh1745_write_raw, + .write_raw_get_fmt = bh1745_write_raw_get_fmt, + .read_event_value = bh1745_read_thresh, + .write_event_value = bh1745_write_thresh, + .read_event_config = bh1745_read_event_config, + .write_event_config = bh1745_write_event_config, + .read_avail = bh1745_read_avail, +}; + +static irqreturn_t bh1745_interrupt_handler(int interrupt, void *p) +{ + struct iio_dev *indio_dev = p; + struct bh1745_data *data = iio_priv(indio_dev); + int ret; + int value; + int int_src; + + ret = regmap_read(data->regmap, BH1745_INTR, &value); + if (ret) + return IRQ_NONE; + + int_src = FIELD_GET(BH1745_INTR_SOURCE_MASK, value); + + if (value & BH1745_INTR_STATUS) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, int_src, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static irqreturn_t bh1745_trigger_handler(int interrupt, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bh1745_data *data = iio_priv(indio_dev); + struct { + u16 chans[4]; + aligned_s64 timestamp; + } scan = { }; + u16 value; + int ret; + int i; + int j = 0; + + iio_for_each_active_channel(indio_dev, i) { + ret = regmap_bulk_read(data->regmap, BH1745_RED_LSB + 2 * i, + &value, 2); + if (ret) + goto err; + + scan.chans[j++] = value; + } + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); + +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int bh1745_setup_triggered_buffer(struct iio_dev *indio_dev, + struct device *parent, + int irq) +{ + struct bh1745_data *data = iio_priv(indio_dev); + struct device *dev = data->dev; + int ret; + + ret = devm_iio_triggered_buffer_setup(parent, indio_dev, NULL, + bh1745_trigger_handler, NULL); + if (ret) + return dev_err_probe(dev, ret, + "Triggered buffer setup failed\n"); + + if (irq) { + ret = devm_request_threaded_irq(dev, irq, NULL, + bh1745_interrupt_handler, + IRQF_ONESHOT, + "bh1745_interrupt", indio_dev); + if (ret) + return ret; + } + + return 0; +} + +static int bh1745_init(struct bh1745_data *data) +{ + int ret; + struct device *dev = data->dev; + + mutex_init(&data->lock); + + ret = devm_iio_init_iio_gts(dev, BH1745_MAX_GAIN, 0, bh1745_gain, + ARRAY_SIZE(bh1745_gain), bh1745_itimes, + ARRAY_SIZE(bh1745_itimes), &data->gts); + if (ret) + return ret; + + ret = bh1745_reset(data); + if (ret) + return dev_err_probe(dev, ret, "Failed to reset sensor\n"); + + ret = bh1745_power_on(data); + if (ret) + return dev_err_probe(dev, ret, "Failed to turn on sensor\n"); + + ret = devm_add_action_or_reset(dev, bh1745_power_off, data); + if (ret) + return ret; + + return 0; +} + +static int bh1745_probe(struct i2c_client *client) +{ + int ret; + int value; + int part_id; + struct bh1745_data *data; + struct iio_dev *indio_dev; + struct device *dev = &client->dev; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + indio_dev->info = &bh1745_info; + indio_dev->name = "bh1745"; + indio_dev->channels = bh1745_channels; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = ARRAY_SIZE(bh1745_channels); + data = iio_priv(indio_dev); + data->dev = &client->dev; + data->regmap = devm_regmap_init_i2c(client, &bh1745_regmap); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "Failed to initialize Regmap\n"); + + ret = regmap_read(data->regmap, BH1745_SYS_CTRL, &value); + if (ret) + return ret; + + part_id = FIELD_GET(BH1745_SYS_CTRL_PART_ID_MASK, value); + if (part_id != BH1745_PART_ID) + dev_warn(dev, "Unknown part ID 0x%x\n", part_id); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get and enable regulator\n"); + + ret = bh1745_init(data); + if (ret) + return ret; + + ret = bh1745_setup_triggered_buffer(indio_dev, indio_dev->dev.parent, + client->irq); + if (ret) + return ret; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to register device\n"); + + return 0; +} + +static const struct i2c_device_id bh1745_idtable[] = { + { .name = "bh1745" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bh1745_idtable); + +static const struct of_device_id bh1745_of_match[] = { + { .compatible = "rohm,bh1745" }, + { } +}; +MODULE_DEVICE_TABLE(of, bh1745_of_match); + +static struct i2c_driver bh1745_driver = { + .driver = { + .name = "bh1745", + .of_match_table = bh1745_of_match, + }, + .probe = bh1745_probe, + .id_table = bh1745_idtable, +}; +module_i2c_driver(bh1745_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mudit Sharma "); +MODULE_DESCRIPTION("BH1745 colour sensor driver"); +MODULE_IMPORT_NS("IIO_GTS_HELPER"); diff --git a/drivers/iio/light/bh1750.c b/drivers/iio/light/bh1750.c new file mode 100644 index 000000000..a51ac98c8 --- /dev/null +++ b/drivers/iio/light/bh1750.c @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ROHM BH1710/BH1715/BH1721/BH1750/BH1751 ambient light sensor driver + * + * Copyright (c) Tomasz Duszynski + * + * Data sheets: + * http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/light/bh1710fvc-e.pdf + * http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/light/bh1715fvc-e.pdf + * http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/light/bh1721fvc-e.pdf + * http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/light/bh1750fvi-e.pdf + * http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/light/bh1751fvi-e.pdf + * + * 7-bit I2C slave addresses: + * 0x23 (ADDR pin low) + * 0x5C (ADDR pin high) + * + */ + +#include +#include +#include +#include +#include +#include + +#define BH1750_POWER_DOWN 0x00 +#define BH1750_ONE_TIME_H_RES_MODE 0x20 /* auto-mode for BH1721 */ +#define BH1750_CHANGE_INT_TIME_H_BIT 0x40 +#define BH1750_CHANGE_INT_TIME_L_BIT 0x60 + +/* Define the reset delay time in microseconds */ +#define BH1750_RESET_DELAY_US 10000 /* 10ms */ + +enum { + BH1710, + BH1721, + BH1750, +}; + +struct bh1750_chip_info; +struct bh1750_data { + struct i2c_client *client; + struct mutex lock; + const struct bh1750_chip_info *chip_info; + u16 mtreg; + struct gpio_desc *reset_gpio; +}; + +struct bh1750_chip_info { + u16 mtreg_min; + u16 mtreg_max; + u16 mtreg_default; + int mtreg_to_usec; + int mtreg_to_scale; + + /* + * For BH1710/BH1721 all possible integration time values won't fit + * into one page so displaying is limited to every second one. + * Note, that user can still write proper values which were not + * listed. + */ + int inc; + + u16 int_time_low_mask; + u16 int_time_high_mask; +}; + +static const struct bh1750_chip_info bh1750_chip_info_tbl[] = { + [BH1710] = { 140, 1022, 300, 400, 250000000, 2, 0x001F, 0x03E0 }, + [BH1721] = { 140, 1020, 300, 400, 250000000, 2, 0x0010, 0x03E0 }, + [BH1750] = { 31, 254, 69, 1740, 57500000, 1, 0x001F, 0x00E0 }, +}; + +static int bh1750_change_int_time(struct bh1750_data *data, int usec) +{ + int ret; + u16 val; + u8 regval; + const struct bh1750_chip_info *chip_info = data->chip_info; + + if ((usec % chip_info->mtreg_to_usec) != 0) + return -EINVAL; + + val = usec / chip_info->mtreg_to_usec; + if (val < chip_info->mtreg_min || val > chip_info->mtreg_max) + return -EINVAL; + + ret = i2c_smbus_write_byte(data->client, BH1750_POWER_DOWN); + if (ret < 0) + return ret; + + regval = (val & chip_info->int_time_high_mask) >> 5; + ret = i2c_smbus_write_byte(data->client, + BH1750_CHANGE_INT_TIME_H_BIT | regval); + if (ret < 0) + return ret; + + regval = val & chip_info->int_time_low_mask; + ret = i2c_smbus_write_byte(data->client, + BH1750_CHANGE_INT_TIME_L_BIT | regval); + if (ret < 0) + return ret; + + data->mtreg = val; + + return 0; +} + +static int bh1750_read(struct bh1750_data *data, int *val) +{ + int ret; + __be16 result; + const struct bh1750_chip_info *chip_info = data->chip_info; + unsigned long delay = chip_info->mtreg_to_usec * data->mtreg; + + /* + * BH1721 will enter continuous mode on receiving this command. + * Note, that this eliminates need for bh1750_resume(). + */ + ret = i2c_smbus_write_byte(data->client, BH1750_ONE_TIME_H_RES_MODE); + if (ret < 0) + return ret; + + usleep_range(delay + 15000, delay + 40000); + + ret = i2c_master_recv(data->client, (char *)&result, 2); + if (ret < 0) + return ret; + + *val = be16_to_cpu(result); + + return 0; +} + +static int bh1750_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret, tmp; + struct bh1750_data *data = iio_priv(indio_dev); + const struct bh1750_chip_info *chip_info = data->chip_info; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_LIGHT: + mutex_lock(&data->lock); + ret = bh1750_read(data, val); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + tmp = chip_info->mtreg_to_scale / data->mtreg; + *val = tmp / 1000000; + *val2 = tmp % 1000000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = chip_info->mtreg_to_usec * data->mtreg; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int bh1750_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret; + struct bh1750_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val != 0) + return -EINVAL; + + mutex_lock(&data->lock); + ret = bh1750_change_int_time(data, val2); + mutex_unlock(&data->lock); + return ret; + default: + return -EINVAL; + } +} + +static ssize_t bh1750_show_int_time_available(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i; + size_t len = 0; + struct bh1750_data *data = iio_priv(dev_to_iio_dev(dev)); + const struct bh1750_chip_info *chip_info = data->chip_info; + + for (i = chip_info->mtreg_min; i <= chip_info->mtreg_max; i += chip_info->inc) + len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06d ", + chip_info->mtreg_to_usec * i); + + buf[len - 1] = '\n'; + + return len; +} + +static IIO_DEV_ATTR_INT_TIME_AVAIL(bh1750_show_int_time_available); + +static struct attribute *bh1750_attributes[] = { + &iio_dev_attr_integration_time_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group bh1750_attribute_group = { + .attrs = bh1750_attributes, +}; + +static const struct iio_info bh1750_info = { + .attrs = &bh1750_attribute_group, + .read_raw = bh1750_read_raw, + .write_raw = bh1750_write_raw, +}; + +static const struct iio_chan_spec bh1750_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME) + } +}; + +static int bh1750_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + int ret, usec; + struct bh1750_data *data; + struct iio_dev *indio_dev; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C | + I2C_FUNC_SMBUS_WRITE_BYTE)) + return -EOPNOTSUPP; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->chip_info = &bh1750_chip_info_tbl[id->driver_data]; + + /* Get reset GPIO from device tree */ + data->reset_gpio = devm_gpiod_get_optional(&client->dev, + "reset", GPIOD_OUT_HIGH); + + if (IS_ERR(data->reset_gpio)) + return dev_err_probe(&client->dev, PTR_ERR(data->reset_gpio), + "Failed to get reset GPIO\n"); + + /* Perform hardware reset if GPIO is provided */ + if (data->reset_gpio) { + /* Perform reset sequence: low-high */ + gpiod_set_value_cansleep(data->reset_gpio, 1); + fsleep(BH1750_RESET_DELAY_US); + gpiod_set_value_cansleep(data->reset_gpio, 0); + fsleep(BH1750_RESET_DELAY_US); + + dev_dbg(&client->dev, "BH1750 reset completed via GPIO\n"); + } + + usec = data->chip_info->mtreg_to_usec * data->chip_info->mtreg_default; + ret = bh1750_change_int_time(data, usec); + if (ret < 0) + return ret; + + mutex_init(&data->lock); + indio_dev->info = &bh1750_info; + indio_dev->name = id->name; + indio_dev->channels = bh1750_channels; + indio_dev->num_channels = ARRAY_SIZE(bh1750_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + return iio_device_register(indio_dev); +} + +static void bh1750_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct bh1750_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + mutex_lock(&data->lock); + i2c_smbus_write_byte(client, BH1750_POWER_DOWN); + mutex_unlock(&data->lock); +} + +static int bh1750_suspend(struct device *dev) +{ + int ret; + struct bh1750_data *data = + iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + /* + * This is mainly for BH1721 which doesn't enter power down + * mode automatically. + */ + mutex_lock(&data->lock); + ret = i2c_smbus_write_byte(data->client, BH1750_POWER_DOWN); + mutex_unlock(&data->lock); + + return ret; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(bh1750_pm_ops, bh1750_suspend, NULL); + +static const struct i2c_device_id bh1750_id[] = { + { .name = "bh1710", .driver_data = BH1710 }, + { .name = "bh1715", .driver_data = BH1750 }, + { .name = "bh1721", .driver_data = BH1721 }, + { .name = "bh1750", .driver_data = BH1750 }, + { .name = "bh1751", .driver_data = BH1750 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bh1750_id); + +static const struct of_device_id bh1750_of_match[] = { + { .compatible = "rohm,bh1710", }, + { .compatible = "rohm,bh1715", }, + { .compatible = "rohm,bh1721", }, + { .compatible = "rohm,bh1750", }, + { .compatible = "rohm,bh1751", }, + { } +}; +MODULE_DEVICE_TABLE(of, bh1750_of_match); + +static struct i2c_driver bh1750_driver = { + .driver = { + .name = "bh1750", + .of_match_table = bh1750_of_match, + .pm = pm_sleep_ptr(&bh1750_pm_ops), + }, + .probe = bh1750_probe, + .remove = bh1750_remove, + .id_table = bh1750_id, + +}; +module_i2c_driver(bh1750_driver); + +MODULE_AUTHOR("Tomasz Duszynski "); +MODULE_DESCRIPTION("ROHM BH1710/BH1715/BH1721/BH1750/BH1751 als driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/bh1780.c b/drivers/iio/light/bh1780.c new file mode 100644 index 000000000..5447b990f --- /dev/null +++ b/drivers/iio/light/bh1780.c @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ROHM 1780GLI Ambient Light Sensor Driver + * + * Copyright (C) 2016 Linaro Ltd. + * Author: Linus Walleij + * Loosely based on the previous BH1780 ALS misc driver + * Copyright (C) 2010 Texas Instruments + * Author: Hemanth V + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BH1780_CMD_BIT BIT(7) +#define BH1780_REG_CONTROL 0x00 +#define BH1780_REG_PARTID 0x0A +#define BH1780_REG_MANFID 0x0B +#define BH1780_REG_DLOW 0x0C +#define BH1780_REG_DHIGH 0x0D + +#define BH1780_REVMASK GENMASK(3,0) +#define BH1780_POWMASK GENMASK(1,0) +#define BH1780_POFF (0x0) +#define BH1780_PON (0x3) + +/* power on settling time in ms */ +#define BH1780_PON_DELAY 2 +/* max time before value available in ms */ +#define BH1780_INTERVAL 250 + +struct bh1780_data { + struct i2c_client *client; +}; + +static int bh1780_write(struct bh1780_data *bh1780, u8 reg, u8 val) +{ + int ret = i2c_smbus_write_byte_data(bh1780->client, + BH1780_CMD_BIT | reg, + val); + if (ret < 0) + dev_err(&bh1780->client->dev, + "i2c_smbus_write_byte_data failed error " + "%d, register %01x\n", + ret, reg); + return ret; +} + +static int bh1780_read(struct bh1780_data *bh1780, u8 reg) +{ + int ret = i2c_smbus_read_byte_data(bh1780->client, + BH1780_CMD_BIT | reg); + if (ret < 0) + dev_err(&bh1780->client->dev, + "i2c_smbus_read_byte_data failed error " + "%d, register %01x\n", + ret, reg); + return ret; +} + +static int bh1780_read_word(struct bh1780_data *bh1780, u8 reg) +{ + int ret = i2c_smbus_read_word_data(bh1780->client, + BH1780_CMD_BIT | reg); + if (ret < 0) + dev_err(&bh1780->client->dev, + "i2c_smbus_read_word_data failed error " + "%d, register %01x\n", + ret, reg); + return ret; +} + +static int bh1780_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + struct bh1780_data *bh1780 = iio_priv(indio_dev); + int ret; + + if (!readval) + return bh1780_write(bh1780, (u8)reg, (u8)writeval); + + ret = bh1780_read(bh1780, (u8)reg); + if (ret < 0) + return ret; + + *readval = ret; + + return 0; +} + +static int bh1780_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bh1780_data *bh1780 = iio_priv(indio_dev); + int value; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_LIGHT: + pm_runtime_get_sync(&bh1780->client->dev); + value = bh1780_read_word(bh1780, BH1780_REG_DLOW); + pm_runtime_put_autosuspend(&bh1780->client->dev); + if (value < 0) + return value; + *val = value; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = BH1780_INTERVAL * 1000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static const struct iio_info bh1780_info = { + .read_raw = bh1780_read_raw, + .debugfs_reg_access = bh1780_debugfs_reg_access, +}; + +static const struct iio_chan_spec bh1780_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) + } +}; + +static int bh1780_probe(struct i2c_client *client) +{ + int ret; + struct bh1780_data *bh1780; + struct i2c_adapter *adapter = client->adapter; + struct iio_dev *indio_dev; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*bh1780)); + if (!indio_dev) + return -ENOMEM; + + bh1780 = iio_priv(indio_dev); + bh1780->client = client; + i2c_set_clientdata(client, indio_dev); + + /* Power up the device */ + ret = bh1780_write(bh1780, BH1780_REG_CONTROL, BH1780_PON); + if (ret < 0) + return ret; + msleep(BH1780_PON_DELAY); + pm_runtime_get_noresume(&client->dev); + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + + ret = bh1780_read(bh1780, BH1780_REG_PARTID); + if (ret < 0) + goto out_disable_pm; + dev_info(&client->dev, + "Ambient Light Sensor, Rev : %lu\n", + (ret & BH1780_REVMASK)); + + /* + * As the device takes 250 ms to even come up with a fresh + * measurement after power-on, do not shut it down unnecessarily. + * Set autosuspend to a five seconds. + */ + pm_runtime_set_autosuspend_delay(&client->dev, 5000); + pm_runtime_use_autosuspend(&client->dev); + pm_runtime_put(&client->dev); + + indio_dev->info = &bh1780_info; + indio_dev->name = "bh1780"; + indio_dev->channels = bh1780_channels; + indio_dev->num_channels = ARRAY_SIZE(bh1780_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(indio_dev); + if (ret) + goto out_disable_pm; + return 0; + +out_disable_pm: + pm_runtime_put_noidle(&client->dev); + pm_runtime_disable(&client->dev); + return ret; +} + +static void bh1780_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct bh1780_data *bh1780 = iio_priv(indio_dev); + int ret; + + iio_device_unregister(indio_dev); + pm_runtime_get_sync(&client->dev); + pm_runtime_put_noidle(&client->dev); + pm_runtime_disable(&client->dev); + ret = bh1780_write(bh1780, BH1780_REG_CONTROL, BH1780_POFF); + if (ret < 0) + dev_err(&client->dev, "failed to power off (%pe)\n", + ERR_PTR(ret)); +} + +static int bh1780_runtime_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct bh1780_data *bh1780 = iio_priv(indio_dev); + int ret; + + ret = bh1780_write(bh1780, BH1780_REG_CONTROL, BH1780_POFF); + if (ret < 0) { + dev_err(dev, "failed to runtime suspend\n"); + return ret; + } + + return 0; +} + +static int bh1780_runtime_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct bh1780_data *bh1780 = iio_priv(indio_dev); + int ret; + + ret = bh1780_write(bh1780, BH1780_REG_CONTROL, BH1780_PON); + if (ret < 0) { + dev_err(dev, "failed to runtime resume\n"); + return ret; + } + + /* Wait for power on, then for a value to be available */ + msleep(BH1780_PON_DELAY + BH1780_INTERVAL); + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(bh1780_dev_pm_ops, bh1780_runtime_suspend, + bh1780_runtime_resume, NULL); + +static const struct i2c_device_id bh1780_id[] = { + { .name = "bh1780" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, bh1780_id); + +static const struct of_device_id of_bh1780_match[] = { + { .compatible = "rohm,bh1780gli", }, + { } +}; +MODULE_DEVICE_TABLE(of, of_bh1780_match); + +static struct i2c_driver bh1780_driver = { + .probe = bh1780_probe, + .remove = bh1780_remove, + .id_table = bh1780_id, + .driver = { + .name = "bh1780", + .pm = pm_ptr(&bh1780_dev_pm_ops), + .of_match_table = of_bh1780_match, + }, +}; + +module_i2c_driver(bh1780_driver); + +MODULE_DESCRIPTION("ROHM BH1780GLI Ambient Light Sensor Driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Linus Walleij "); diff --git a/drivers/iio/light/cm32181.c b/drivers/iio/light/cm32181.c new file mode 100644 index 000000000..b32a94028 --- /dev/null +++ b/drivers/iio/light/cm32181.c @@ -0,0 +1,550 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 Capella Microsystems Inc. + * Author: Kevin Tsai + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Registers Address */ +#define CM32181_REG_ADDR_CMD 0x00 +#define CM32181_REG_ADDR_WH 0x01 +#define CM32181_REG_ADDR_WL 0x02 +#define CM32181_REG_ADDR_TEST 0x03 +#define CM32181_REG_ADDR_ALS 0x04 +#define CM32181_REG_ADDR_STATUS 0x06 +#define CM32181_REG_ADDR_ID 0x07 + +/* Number of Configurable Registers */ +#define CM32181_CONF_REG_NUM 4 + +/* CMD register */ +#define CM32181_CMD_ALS_DISABLE BIT(0) +#define CM32181_CMD_ALS_INT_EN BIT(1) +#define CM32181_CMD_ALS_THRES_WINDOW BIT(2) + +#define CM32181_CMD_ALS_PERS_SHIFT 4 +#define CM32181_CMD_ALS_PERS_MASK (0x03 << CM32181_CMD_ALS_PERS_SHIFT) +#define CM32181_CMD_ALS_PERS_DEFAULT (0x01 << CM32181_CMD_ALS_PERS_SHIFT) + +#define CM32181_CMD_ALS_IT_SHIFT 6 +#define CM32181_CMD_ALS_IT_MASK (0x0F << CM32181_CMD_ALS_IT_SHIFT) +#define CM32181_CMD_ALS_IT_DEFAULT (0x00 << CM32181_CMD_ALS_IT_SHIFT) + +#define CM32181_CMD_ALS_SM_SHIFT 11 +#define CM32181_CMD_ALS_SM_MASK (0x03 << CM32181_CMD_ALS_SM_SHIFT) +#define CM32181_CMD_ALS_SM_DEFAULT (0x01 << CM32181_CMD_ALS_SM_SHIFT) + +#define CM32181_LUX_PER_BIT 500 /* ALS_SM=01 IT=800ms */ +#define CM32181_LUX_PER_BIT_RESOLUTION 100000 +#define CM32181_LUX_PER_BIT_BASE_IT 800000 /* Based on IT=800ms */ +#define CM32181_CALIBSCALE_DEFAULT 100000 +#define CM32181_CALIBSCALE_RESOLUTION 100000 + +#define SMBUS_ALERT_RESPONSE_ADDRESS 0x0c + +/* CPM0 Index 0: device-id (3218 or 32181), 1: Unknown, 2: init_regs_bitmap */ +#define CPM0_REGS_BITMAP 2 +#define CPM0_HEADER_SIZE 3 + +/* CPM1 Index 0: lux_per_bit, 1: calibscale, 2: resolution (100000) */ +#define CPM1_LUX_PER_BIT 0 +#define CPM1_CALIBSCALE 1 +#define CPM1_SIZE 3 + +/* CM3218 Family */ +static const int cm3218_als_it_bits[] = { 0, 1, 2, 3 }; +static const int cm3218_als_it_values[] = { 100000, 200000, 400000, 800000 }; + +/* CM32181 Family */ +static const int cm32181_als_it_bits[] = { 12, 8, 0, 1, 2, 3 }; +static const int cm32181_als_it_values[] = { + 25000, 50000, 100000, 200000, 400000, 800000 +}; + +struct cm32181_chip { + struct i2c_client *client; + struct device *dev; + struct mutex lock; + u16 conf_regs[CM32181_CONF_REG_NUM]; + unsigned long init_regs_bitmap; + int calibscale; + int lux_per_bit; + int lux_per_bit_base_it; + int num_als_it; + const int *als_it_bits; + const int *als_it_values; +}; + +static int cm32181_read_als_it(struct cm32181_chip *cm32181, int *val2); + +#ifdef CONFIG_ACPI +/** + * cm32181_acpi_get_cpm() - Get CPM object from ACPI + * @dev: pointer of struct device. + * @obj_name: pointer of ACPI object name. + * @values: pointer of array for return elements. + * @count: maximum size of return array. + * + * Convert ACPI CPM table to array. + * + * Return: -ENODEV for fail. Otherwise is number of elements. + */ +static int cm32181_acpi_get_cpm(struct device *dev, char *obj_name, + u64 *values, int count) +{ + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *cpm, *elem; + acpi_handle handle; + acpi_status status; + int i; + + handle = ACPI_HANDLE(dev); + if (!handle) + return -ENODEV; + + status = acpi_evaluate_object(handle, obj_name, NULL, &buffer); + if (ACPI_FAILURE(status)) { + dev_err(dev, "object %s not found\n", obj_name); + return -ENODEV; + } + + cpm = buffer.pointer; + if (cpm->package.count > count) + dev_warn(dev, "%s table contains %u values, only using first %d values\n", + obj_name, cpm->package.count, count); + + count = min_t(int, cpm->package.count, count); + for (i = 0; i < count; i++) { + elem = &(cpm->package.elements[i]); + values[i] = elem->integer.value; + } + + kfree(buffer.pointer); + + return count; +} + +static void cm32181_acpi_parse_cpm_tables(struct cm32181_chip *cm32181) +{ + u64 vals[CPM0_HEADER_SIZE + CM32181_CONF_REG_NUM]; + struct device *dev = cm32181->dev; + int i, count; + + count = cm32181_acpi_get_cpm(dev, "CPM0", vals, ARRAY_SIZE(vals)); + if (count <= CPM0_HEADER_SIZE) + return; + + count -= CPM0_HEADER_SIZE; + + cm32181->init_regs_bitmap = vals[CPM0_REGS_BITMAP]; + cm32181->init_regs_bitmap &= GENMASK(count - 1, 0); + for_each_set_bit(i, &cm32181->init_regs_bitmap, count) + cm32181->conf_regs[i] = vals[CPM0_HEADER_SIZE + i]; + + count = cm32181_acpi_get_cpm(dev, "CPM1", vals, ARRAY_SIZE(vals)); + if (count != CPM1_SIZE) + return; + + cm32181->lux_per_bit = vals[CPM1_LUX_PER_BIT]; + + /* Check for uncalibrated devices */ + if (vals[CPM1_CALIBSCALE] == CM32181_CALIBSCALE_DEFAULT) + return; + + cm32181->calibscale = vals[CPM1_CALIBSCALE]; + /* CPM1 lux_per_bit is for the current it value */ + cm32181_read_als_it(cm32181, &cm32181->lux_per_bit_base_it); +} +#else +static void cm32181_acpi_parse_cpm_tables(struct cm32181_chip *cm32181) +{ +} +#endif /* CONFIG_ACPI */ + +/** + * cm32181_reg_init() - Initialize CM32181 registers + * @cm32181: pointer of struct cm32181. + * + * Initialize CM32181 ambient light sensor register to default values. + * + * Return: 0 for success; otherwise for error code. + */ +static int cm32181_reg_init(struct cm32181_chip *cm32181) +{ + struct i2c_client *client = cm32181->client; + int i; + s32 ret; + + ret = i2c_smbus_read_word_data(client, CM32181_REG_ADDR_ID); + if (ret < 0) + return ret; + + /* check device ID */ + switch (ret & 0xFF) { + case 0x18: /* CM3218 */ + cm32181->num_als_it = ARRAY_SIZE(cm3218_als_it_bits); + cm32181->als_it_bits = cm3218_als_it_bits; + cm32181->als_it_values = cm3218_als_it_values; + break; + case 0x81: /* CM32181 */ + case 0x82: /* CM32182, fully compat. with CM32181 */ + cm32181->num_als_it = ARRAY_SIZE(cm32181_als_it_bits); + cm32181->als_it_bits = cm32181_als_it_bits; + cm32181->als_it_values = cm32181_als_it_values; + break; + default: + return -ENODEV; + } + + /* Default Values */ + cm32181->conf_regs[CM32181_REG_ADDR_CMD] = + CM32181_CMD_ALS_IT_DEFAULT | CM32181_CMD_ALS_SM_DEFAULT; + cm32181->init_regs_bitmap = BIT(CM32181_REG_ADDR_CMD); + cm32181->calibscale = CM32181_CALIBSCALE_DEFAULT; + cm32181->lux_per_bit = CM32181_LUX_PER_BIT; + cm32181->lux_per_bit_base_it = CM32181_LUX_PER_BIT_BASE_IT; + + cm32181_acpi_parse_cpm_tables(cm32181); + + /* Initialize registers*/ + for_each_set_bit(i, &cm32181->init_regs_bitmap, CM32181_CONF_REG_NUM) { + ret = i2c_smbus_write_word_data(client, i, + cm32181->conf_regs[i]); + if (ret < 0) + return ret; + } + + return 0; +} + +/** + * cm32181_read_als_it() - Get sensor integration time (ms) + * @cm32181: pointer of struct cm32181 + * @val2: pointer of int to load the als_it value. + * + * Report the current integration time in milliseconds. + * + * Return: IIO_VAL_INT_PLUS_MICRO for success, otherwise -EINVAL. + */ +static int cm32181_read_als_it(struct cm32181_chip *cm32181, int *val2) +{ + u16 als_it; + int i; + + als_it = cm32181->conf_regs[CM32181_REG_ADDR_CMD]; + als_it &= CM32181_CMD_ALS_IT_MASK; + als_it >>= CM32181_CMD_ALS_IT_SHIFT; + for (i = 0; i < cm32181->num_als_it; i++) { + if (als_it == cm32181->als_it_bits[i]) { + *val2 = cm32181->als_it_values[i]; + return IIO_VAL_INT_PLUS_MICRO; + } + } + + return -EINVAL; +} + +/** + * cm32181_write_als_it() - Write sensor integration time + * @cm32181: pointer of struct cm32181. + * @val: integration time by millisecond. + * + * Convert integration time (ms) to sensor value. + * + * Return: i2c_smbus_write_word_data command return value. + */ +static int cm32181_write_als_it(struct cm32181_chip *cm32181, int val) +{ + struct i2c_client *client = cm32181->client; + u16 als_it; + int ret, i, n; + + n = cm32181->num_als_it; + for (i = 0; i < n; i++) + if (val <= cm32181->als_it_values[i]) + break; + if (i >= n) + i = n - 1; + + als_it = cm32181->als_it_bits[i]; + als_it <<= CM32181_CMD_ALS_IT_SHIFT; + + mutex_lock(&cm32181->lock); + cm32181->conf_regs[CM32181_REG_ADDR_CMD] &= + ~CM32181_CMD_ALS_IT_MASK; + cm32181->conf_regs[CM32181_REG_ADDR_CMD] |= + als_it; + ret = i2c_smbus_write_word_data(client, CM32181_REG_ADDR_CMD, + cm32181->conf_regs[CM32181_REG_ADDR_CMD]); + mutex_unlock(&cm32181->lock); + + return ret; +} + +/** + * cm32181_get_lux() - report current lux value + * @cm32181: pointer of struct cm32181. + * + * Convert sensor raw data to lux. It depends on integration + * time and calibscale variable. + * + * Return: Positive value is lux, otherwise is error code. + */ +static int cm32181_get_lux(struct cm32181_chip *cm32181) +{ + struct i2c_client *client = cm32181->client; + int ret; + int als_it; + u64 lux; + + ret = cm32181_read_als_it(cm32181, &als_it); + if (ret < 0) + return -EINVAL; + + lux = cm32181->lux_per_bit; + lux *= cm32181->lux_per_bit_base_it; + lux = div_u64(lux, als_it); + + ret = i2c_smbus_read_word_data(client, CM32181_REG_ADDR_ALS); + if (ret < 0) + return ret; + + lux *= ret; + lux *= cm32181->calibscale; + lux = div_u64(lux, CM32181_CALIBSCALE_RESOLUTION); + lux = div_u64(lux, CM32181_LUX_PER_BIT_RESOLUTION); + + if (lux > 0xFFFF) + lux = 0xFFFF; + + return lux; +} + +static int cm32181_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cm32181_chip *cm32181 = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ret = cm32181_get_lux(cm32181); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + *val = cm32181->calibscale; + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + ret = cm32181_read_als_it(cm32181, val2); + return ret; + } + + return -EINVAL; +} + +static int cm32181_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct cm32181_chip *cm32181 = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + cm32181->calibscale = val; + return 0; + case IIO_CHAN_INFO_INT_TIME: + ret = cm32181_write_als_it(cm32181, val2); + return ret; + } + + return -EINVAL; +} + +/** + * cm32181_get_it_available() - Get available ALS IT value + * @dev: pointer of struct device. + * @attr: pointer of struct device_attribute. + * @buf: pointer of return string buffer. + * + * Display the available integration time values by millisecond. + * + * Return: string length. + */ +static ssize_t cm32181_get_it_available(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cm32181_chip *cm32181 = iio_priv(dev_to_iio_dev(dev)); + int i, n, len; + + n = cm32181->num_als_it; + for (i = 0, len = 0; i < n; i++) + len += sprintf(buf + len, "0.%06u ", cm32181->als_it_values[i]); + return len + sprintf(buf + len, "\n"); +} + +static const struct iio_chan_spec cm32181_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = + BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_INT_TIME), + } +}; + +static IIO_DEVICE_ATTR(in_illuminance_integration_time_available, + S_IRUGO, cm32181_get_it_available, NULL, 0); + +static struct attribute *cm32181_attributes[] = { + &iio_dev_attr_in_illuminance_integration_time_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group cm32181_attribute_group = { + .attrs = cm32181_attributes +}; + +static const struct iio_info cm32181_info = { + .read_raw = &cm32181_read_raw, + .write_raw = &cm32181_write_raw, + .attrs = &cm32181_attribute_group, +}; + +static void cm32181_unregister_dummy_client(void *data) +{ + struct i2c_client *client = data; + + /* Unregister the dummy client */ + i2c_unregister_device(client); +} + +static int cm32181_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct cm32181_chip *cm32181; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*cm32181)); + if (!indio_dev) + return -ENOMEM; + + i2c_set_clientdata(client, indio_dev); + + /* + * Some ACPI systems list 2 I2C resources for the CM3218 sensor, the + * SMBus Alert Response Address (ARA, 0x0c) and the actual I2C address. + * Detect this and take the following step to deal with it: + * 1. When a SMBus Alert capable sensor has an Alert asserted, it will + * not respond on its actual I2C address. Read a byte from the ARA + * to clear any pending Alerts. + * 2. Create a "dummy" client for the actual I2C address and + * use that client to communicate with the sensor. + */ + if (ACPI_HANDLE(dev) && client->addr == SMBUS_ALERT_RESPONSE_ADDRESS) { + struct i2c_board_info board_info = { .type = "dummy" }; + + i2c_smbus_read_byte(client); + + client = i2c_acpi_new_device(dev, 1, &board_info); + if (IS_ERR(client)) + return PTR_ERR(client); + + ret = devm_add_action_or_reset(dev, cm32181_unregister_dummy_client, client); + if (ret) + return ret; + } + + cm32181 = iio_priv(indio_dev); + cm32181->client = client; + cm32181->dev = dev; + + mutex_init(&cm32181->lock); + indio_dev->channels = cm32181_channels; + indio_dev->num_channels = ARRAY_SIZE(cm32181_channels); + indio_dev->info = &cm32181_info; + indio_dev->name = dev_name(dev); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = cm32181_reg_init(cm32181); + if (ret) { + dev_err(dev, "%s: register init failed\n", __func__); + return ret; + } + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) { + dev_err(dev, "%s: register device failed\n", __func__); + return ret; + } + + return 0; +} + +static int cm32181_suspend(struct device *dev) +{ + struct cm32181_chip *cm32181 = iio_priv(dev_get_drvdata(dev)); + struct i2c_client *client = cm32181->client; + + return i2c_smbus_write_word_data(client, CM32181_REG_ADDR_CMD, + CM32181_CMD_ALS_DISABLE); +} + +static int cm32181_resume(struct device *dev) +{ + struct cm32181_chip *cm32181 = iio_priv(dev_get_drvdata(dev)); + struct i2c_client *client = cm32181->client; + + return i2c_smbus_write_word_data(client, CM32181_REG_ADDR_CMD, + cm32181->conf_regs[CM32181_REG_ADDR_CMD]); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(cm32181_pm_ops, cm32181_suspend, cm32181_resume); + +static const struct of_device_id cm32181_of_match[] = { + { .compatible = "capella,cm3218" }, + { .compatible = "capella,cm32181" }, + { } +}; +MODULE_DEVICE_TABLE(of, cm32181_of_match); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id cm32181_acpi_match[] = { + { "CPLM3218", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, cm32181_acpi_match); +#endif + +static struct i2c_driver cm32181_driver = { + .driver = { + .name = "cm32181", + .acpi_match_table = ACPI_PTR(cm32181_acpi_match), + .of_match_table = cm32181_of_match, + .pm = pm_sleep_ptr(&cm32181_pm_ops), + }, + .probe = cm32181_probe, +}; + +module_i2c_driver(cm32181_driver); + +MODULE_AUTHOR("Kevin Tsai "); +MODULE_DESCRIPTION("CM32181 ambient light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/cm3232.c b/drivers/iio/light/cm3232.c new file mode 100644 index 000000000..5f2545263 --- /dev/null +++ b/drivers/iio/light/cm3232.c @@ -0,0 +1,424 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * CM3232 Ambient Light Sensor + * + * Copyright (C) 2014-2015 Capella Microsystems Inc. + * Author: Kevin Tsai + * + * IIO driver for CM3232 (7-bit I2C slave address 0x10). + */ + +#include +#include +#include +#include +#include + +/* Registers Address */ +#define CM3232_REG_ADDR_CMD 0x00 +#define CM3232_REG_ADDR_ALS 0x50 +#define CM3232_REG_ADDR_ID 0x53 + +#define CM3232_CMD_ALS_DISABLE BIT(0) + +#define CM3232_CMD_ALS_IT_SHIFT 2 +#define CM3232_CMD_ALS_IT_MASK (BIT(2) | BIT(3) | BIT(4)) +#define CM3232_CMD_ALS_IT_DEFAULT (0x01 << CM3232_CMD_ALS_IT_SHIFT) + +#define CM3232_CMD_ALS_RESET BIT(6) + +#define CM3232_CMD_DEFAULT CM3232_CMD_ALS_IT_DEFAULT + +#define CM3232_HW_ID 0x32 +#define CM3232_CALIBSCALE_DEFAULT 100000 +#define CM3232_CALIBSCALE_RESOLUTION 100000 +#define CM3232_MLUX_PER_LUX 1000 + +#define CM3232_MLUX_PER_BIT_DEFAULT 64 +#define CM3232_MLUX_PER_BIT_BASE_IT 100000 + +static const struct { + int val; + int val2; + u8 it; +} cm3232_als_it_scales[] = { + {0, 100000, 0}, /* 0.100000 */ + {0, 200000, 1}, /* 0.200000 */ + {0, 400000, 2}, /* 0.400000 */ + {0, 800000, 3}, /* 0.800000 */ + {1, 600000, 4}, /* 1.600000 */ + {3, 200000, 5}, /* 3.200000 */ +}; + +struct cm3232_als_info { + u8 regs_cmd_default; + u8 hw_id; + int mlux_per_bit; + int mlux_per_bit_base_it; +}; + +static const struct cm3232_als_info cm3232_als_info_default = { + .regs_cmd_default = CM3232_CMD_DEFAULT, + .hw_id = CM3232_HW_ID, + .mlux_per_bit = CM3232_MLUX_PER_BIT_DEFAULT, + .mlux_per_bit_base_it = CM3232_MLUX_PER_BIT_BASE_IT, +}; + +struct cm3232_chip { + struct i2c_client *client; + const struct cm3232_als_info *als_info; + int calibscale; + u8 regs_cmd; + u16 regs_als; +}; + +/** + * cm3232_reg_init() - Initialize CM3232 + * @chip: pointer of struct cm3232_chip. + * + * Check and initialize CM3232 ambient light sensor. + * + * Return: 0 for success; otherwise for error code. + */ +static int cm3232_reg_init(struct cm3232_chip *chip) +{ + struct i2c_client *client = chip->client; + s32 ret; + + chip->als_info = &cm3232_als_info_default; + + /* Disable and reset device */ + chip->regs_cmd = CM3232_CMD_ALS_DISABLE | CM3232_CMD_ALS_RESET; + ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, + chip->regs_cmd); + if (ret < 0) { + dev_err(&chip->client->dev, "Error writing reg_cmd\n"); + return ret; + } + + /* Identify device */ + ret = i2c_smbus_read_word_data(client, CM3232_REG_ADDR_ID); + if (ret < 0) { + dev_err(&chip->client->dev, "Error reading addr_id\n"); + return ret; + } + + if ((ret & 0xFF) != chip->als_info->hw_id) + return -ENODEV; + + /* Register default value */ + chip->regs_cmd = chip->als_info->regs_cmd_default; + + /* Configure register */ + ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, + chip->regs_cmd); + if (ret < 0) + dev_err(&chip->client->dev, "Error writing reg_cmd\n"); + + return ret; +} + +/** + * cm3232_read_als_it() - Get sensor integration time + * @chip: pointer of struct cm3232_chip + * @val: pointer of int to load the integration (sec). + * @val2: pointer of int to load the integration time (microsecond). + * + * Report the current integration time. + * + * Return: IIO_VAL_INT_PLUS_MICRO for success, otherwise -EINVAL. + */ +static int cm3232_read_als_it(struct cm3232_chip *chip, int *val, int *val2) +{ + u16 als_it; + int i; + + als_it = chip->regs_cmd; + als_it &= CM3232_CMD_ALS_IT_MASK; + als_it >>= CM3232_CMD_ALS_IT_SHIFT; + for (i = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) { + if (als_it == cm3232_als_it_scales[i].it) { + *val = cm3232_als_it_scales[i].val; + *val2 = cm3232_als_it_scales[i].val2; + return IIO_VAL_INT_PLUS_MICRO; + } + } + + return -EINVAL; +} + +/** + * cm3232_write_als_it() - Write sensor integration time + * @chip: pointer of struct cm3232_chip. + * @val: integration time in second. + * @val2: integration time in microsecond. + * + * Convert integration time to sensor value. + * + * Return: i2c_smbus_write_byte_data command return value. + */ +static int cm3232_write_als_it(struct cm3232_chip *chip, int val, int val2) +{ + struct i2c_client *client = chip->client; + u16 als_it, cmd; + int i; + s32 ret; + + for (i = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) { + if (val == cm3232_als_it_scales[i].val && + val2 == cm3232_als_it_scales[i].val2) { + + als_it = cm3232_als_it_scales[i].it; + als_it <<= CM3232_CMD_ALS_IT_SHIFT; + + cmd = chip->regs_cmd & ~CM3232_CMD_ALS_IT_MASK; + cmd |= als_it; + ret = i2c_smbus_write_byte_data(client, + CM3232_REG_ADDR_CMD, + cmd); + if (ret < 0) + return ret; + chip->regs_cmd = cmd; + return 0; + } + } + return -EINVAL; +} + +/** + * cm3232_get_lux() - report current lux value + * @chip: pointer of struct cm3232_chip. + * + * Convert sensor data to lux. It depends on integration + * time and calibscale variable. + * + * Return: Zero or positive value is lux, otherwise error code. + */ +static int cm3232_get_lux(struct cm3232_chip *chip) +{ + struct i2c_client *client = chip->client; + const struct cm3232_als_info *als_info = chip->als_info; + int ret; + int val, val2; + int als_it; + u64 lux; + + /* Calculate mlux per bit based on als_it */ + ret = cm3232_read_als_it(chip, &val, &val2); + if (ret < 0) + return -EINVAL; + als_it = val * 1000000 + val2; + lux = (__force u64)als_info->mlux_per_bit; + lux *= als_info->mlux_per_bit_base_it; + lux = div_u64(lux, als_it); + + ret = i2c_smbus_read_word_data(client, CM3232_REG_ADDR_ALS); + if (ret < 0) { + dev_err(&client->dev, "Error reading reg_addr_als\n"); + return ret; + } + + chip->regs_als = (u16)ret; + lux *= chip->regs_als; + lux *= chip->calibscale; + lux = div_u64(lux, CM3232_CALIBSCALE_RESOLUTION); + lux = div_u64(lux, CM3232_MLUX_PER_LUX); + + if (lux > 0xFFFF) + lux = 0xFFFF; + + return (int)lux; +} + +static int cm3232_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cm3232_chip *chip = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ret = cm3232_get_lux(chip); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + *val = chip->calibscale; + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + return cm3232_read_als_it(chip, val, val2); + } + + return -EINVAL; +} + +static int cm3232_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct cm3232_chip *chip = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + chip->calibscale = val; + return 0; + case IIO_CHAN_INFO_INT_TIME: + return cm3232_write_als_it(chip, val, val2); + } + + return -EINVAL; +} + +/** + * cm3232_get_it_available() - Get available ALS IT value + * @dev: pointer of struct device. + * @attr: pointer of struct device_attribute. + * @buf: pointer of return string buffer. + * + * Display the available integration time in second. + * + * Return: string length. + */ +static ssize_t cm3232_get_it_available(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, len; + + for (i = 0, len = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "%u.%06u ", + cm3232_als_it_scales[i].val, + cm3232_als_it_scales[i].val2); + return len + scnprintf(buf + len, PAGE_SIZE - len, "\n"); +} + +static const struct iio_chan_spec cm3232_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = + BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_INT_TIME), + } +}; + +static IIO_DEVICE_ATTR(in_illuminance_integration_time_available, + S_IRUGO, cm3232_get_it_available, NULL, 0); + +static struct attribute *cm3232_attributes[] = { + &iio_dev_attr_in_illuminance_integration_time_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group cm3232_attribute_group = { + .attrs = cm3232_attributes +}; + +static const struct iio_info cm3232_info = { + .read_raw = &cm3232_read_raw, + .write_raw = &cm3232_write_raw, + .attrs = &cm3232_attribute_group, +}; + +static int cm3232_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct cm3232_chip *chip; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + chip->client = client; + chip->calibscale = CM3232_CALIBSCALE_DEFAULT; + + indio_dev->channels = cm3232_channels; + indio_dev->num_channels = ARRAY_SIZE(cm3232_channels); + indio_dev->info = &cm3232_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = cm3232_reg_init(chip); + if (ret) { + dev_err(&client->dev, + "%s: register init failed\n", + __func__); + return ret; + } + + return iio_device_register(indio_dev); +} + +static void cm3232_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, + CM3232_CMD_ALS_DISABLE); + + iio_device_unregister(indio_dev); +} + +static const struct i2c_device_id cm3232_id[] = { + { .name = "cm3232" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cm3232_id); + +static int cm3232_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct cm3232_chip *chip = iio_priv(indio_dev); + struct i2c_client *client = chip->client; + int ret; + + chip->regs_cmd |= CM3232_CMD_ALS_DISABLE; + ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, + chip->regs_cmd); + + return ret; +} + +static int cm3232_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct cm3232_chip *chip = iio_priv(indio_dev); + struct i2c_client *client = chip->client; + int ret; + + chip->regs_cmd &= ~CM3232_CMD_ALS_DISABLE; + ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, + chip->regs_cmd | CM3232_CMD_ALS_RESET); + + return ret; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(cm3232_pm_ops, cm3232_suspend, cm3232_resume); + +static const struct of_device_id cm3232_of_match[] = { + {.compatible = "capella,cm3232"}, + { } +}; +MODULE_DEVICE_TABLE(of, cm3232_of_match); + +static struct i2c_driver cm3232_driver = { + .driver = { + .name = "cm3232", + .of_match_table = cm3232_of_match, + .pm = pm_sleep_ptr(&cm3232_pm_ops), + }, + .id_table = cm3232_id, + .probe = cm3232_probe, + .remove = cm3232_remove, +}; + +module_i2c_driver(cm3232_driver); + +MODULE_AUTHOR("Kevin Tsai "); +MODULE_DESCRIPTION("CM3232 ambient light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/cm3323.c b/drivers/iio/light/cm3323.c new file mode 100644 index 000000000..506c91f44 --- /dev/null +++ b/drivers/iio/light/cm3323.c @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * CM3323 - Capella Color Light Sensor + * + * Copyright (c) 2015, Intel Corporation. + * + * IIO driver for CM3323 (7-bit I2C slave address 0x10) + * + * TODO: calibscale to correct the lens factor + */ +#include +#include +#include +#include + +#include +#include + +#define CM3323_DRV_NAME "cm3323" + +#define CM3323_CMD_CONF 0x00 +#define CM3323_CMD_RED_DATA 0x08 +#define CM3323_CMD_GREEN_DATA 0x09 +#define CM3323_CMD_BLUE_DATA 0x0A +#define CM3323_CMD_CLEAR_DATA 0x0B + +#define CM3323_CONF_SD_BIT BIT(0) /* sensor disable */ +#define CM3323_CONF_AF_BIT BIT(1) /* auto/manual force mode */ +#define CM3323_CONF_IT_MASK GENMASK(6, 4) +#define CM3323_CONF_IT_SHIFT 4 + +#define CM3323_INT_TIME_AVAILABLE "0.04 0.08 0.16 0.32 0.64 1.28" + +static const struct { + int val; + int val2; +} cm3323_int_time[] = { + {0, 40000}, /* 40 ms */ + {0, 80000}, /* 80 ms */ + {0, 160000}, /* 160 ms */ + {0, 320000}, /* 320 ms */ + {0, 640000}, /* 640 ms */ + {1, 280000}, /* 1280 ms */ +}; + +struct cm3323_data { + struct i2c_client *client; + u16 reg_conf; + struct mutex mutex; +}; + +#define CM3323_COLOR_CHANNEL(_color, _addr) { \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), \ + .channel2 = IIO_MOD_LIGHT_##_color, \ + .address = _addr, \ +} + +static const struct iio_chan_spec cm3323_channels[] = { + CM3323_COLOR_CHANNEL(RED, CM3323_CMD_RED_DATA), + CM3323_COLOR_CHANNEL(GREEN, CM3323_CMD_GREEN_DATA), + CM3323_COLOR_CHANNEL(BLUE, CM3323_CMD_BLUE_DATA), + CM3323_COLOR_CHANNEL(CLEAR, CM3323_CMD_CLEAR_DATA), +}; + +static IIO_CONST_ATTR_INT_TIME_AVAIL(CM3323_INT_TIME_AVAILABLE); + +static struct attribute *cm3323_attributes[] = { + &iio_const_attr_integration_time_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group cm3323_attribute_group = { + .attrs = cm3323_attributes, +}; + +static int cm3323_init(struct iio_dev *indio_dev) +{ + int ret; + struct cm3323_data *data = iio_priv(indio_dev); + struct device *dev = &data->client->dev; + + ret = i2c_smbus_read_word_data(data->client, CM3323_CMD_CONF); + if (ret < 0) + return dev_err_probe(dev, ret, "Error reading reg_conf\n"); + + /* enable sensor and set auto force mode */ + ret &= ~(CM3323_CONF_SD_BIT | CM3323_CONF_AF_BIT); + data->reg_conf = ret; + + ret = i2c_smbus_write_word_data(data->client, CM3323_CMD_CONF, data->reg_conf); + if (ret < 0) + return dev_err_probe(dev, ret, "Error writing reg_conf\n"); + + return 0; +} + +static void cm3323_disable(void *data) +{ + int ret; + struct iio_dev *indio_dev = data; + struct cm3323_data *cm_data = iio_priv(indio_dev); + + ret = i2c_smbus_write_word_data(cm_data->client, CM3323_CMD_CONF, + CM3323_CONF_SD_BIT); + if (ret < 0) + dev_err(&cm_data->client->dev, "Error writing reg_conf\n"); +} + +static int cm3323_set_it_bits(struct cm3323_data *data, int val, int val2) +{ + int i, ret; + u16 reg_conf; + + for (i = 0; i < ARRAY_SIZE(cm3323_int_time); i++) { + if (val == cm3323_int_time[i].val && + val2 == cm3323_int_time[i].val2) { + reg_conf = data->reg_conf & ~CM3323_CONF_IT_MASK; + reg_conf |= i << CM3323_CONF_IT_SHIFT; + + ret = i2c_smbus_write_word_data(data->client, + CM3323_CMD_CONF, + reg_conf); + if (ret < 0) + return ret; + + data->reg_conf = reg_conf; + + return 0; + } + } + + return -EINVAL; +} + +static int cm3323_get_it_bits(struct cm3323_data *data) +{ + int bits; + + bits = (data->reg_conf & CM3323_CONF_IT_MASK) >> + CM3323_CONF_IT_SHIFT; + + if (bits >= ARRAY_SIZE(cm3323_int_time)) + return -EINVAL; + + return bits; +} + +static int cm3323_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int ret; + struct cm3323_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&data->mutex); + ret = i2c_smbus_read_word_data(data->client, chan->address); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + *val = ret; + mutex_unlock(&data->mutex); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + mutex_lock(&data->mutex); + ret = cm3323_get_it_bits(data); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + + *val = cm3323_int_time[ret].val; + *val2 = cm3323_int_time[ret].val2; + mutex_unlock(&data->mutex); + + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int cm3323_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct cm3323_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + mutex_lock(&data->mutex); + ret = cm3323_set_it_bits(data, val, val2); + mutex_unlock(&data->mutex); + + return ret; + default: + return -EINVAL; + } +} + +static const struct iio_info cm3323_info = { + .read_raw = cm3323_read_raw, + .write_raw = cm3323_write_raw, + .attrs = &cm3323_attribute_group, +}; + +static int cm3323_probe(struct i2c_client *client) +{ + struct cm3323_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + mutex_init(&data->mutex); + + indio_dev->info = &cm3323_info; + indio_dev->name = CM3323_DRV_NAME; + indio_dev->channels = cm3323_channels; + indio_dev->num_channels = ARRAY_SIZE(cm3323_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = cm3323_init(indio_dev); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(&client->dev, cm3323_disable, indio_dev); + if (ret < 0) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id cm3323_id[] = { + { .name = "cm3323" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cm3323_id); + +static const struct of_device_id cm3323_of_match[] = { + { .compatible = "capella,cm3323", }, + { } +}; +MODULE_DEVICE_TABLE(of, cm3323_of_match); + +static struct i2c_driver cm3323_driver = { + .driver = { + .name = CM3323_DRV_NAME, + .of_match_table = cm3323_of_match, + }, + .probe = cm3323_probe, + .id_table = cm3323_id, +}; + +module_i2c_driver(cm3323_driver); + +MODULE_AUTHOR("Daniel Baluta "); +MODULE_DESCRIPTION("Capella CM3323 Color Light Sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/cm3605.c b/drivers/iio/light/cm3605.c new file mode 100644 index 000000000..34c127593 --- /dev/null +++ b/drivers/iio/light/cm3605.c @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * CM3605 Ambient Light and Proximity Sensor + * + * Copyright (C) 2016 Linaro Ltd. + * Author: Linus Walleij + * + * This hardware was found in the very first Nexus One handset from Google/HTC + * and an early endavour into mobile light and proximity sensors. + */ + +#include +#include +#include +#include +#include /* To get our ADC channel */ +#include /* To deal with our ADC channel */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CM3605_PROX_CHANNEL 0 +#define CM3605_ALS_CHANNEL 1 +#define CM3605_AOUT_TYP_MAX_MV 1550 +/* It should not go above 1.650V according to the data sheet */ +#define CM3605_AOUT_MAX_MV 1650 + +/** + * struct cm3605 - CM3605 state + * @dev: pointer to parent device + * @vdd: regulator controlling VDD + * @aset: sleep enable GPIO, high = sleep + * @aout: IIO ADC channel to convert the AOUT signal + * @als_max: maximum LUX detection (depends on RSET) + * @dir: proximity direction: start as FALLING + * @led: trigger for the infrared LED used by the proximity sensor + */ +struct cm3605 { + struct device *dev; + struct regulator *vdd; + struct gpio_desc *aset; + struct iio_channel *aout; + s32 als_max; + enum iio_event_direction dir; + struct led_trigger *led; +}; + +static irqreturn_t cm3605_prox_irq(int irq, void *d) +{ + struct iio_dev *indio_dev = d; + struct cm3605 *cm3605 = iio_priv(indio_dev); + u64 ev; + + ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, CM3605_PROX_CHANNEL, + IIO_EV_TYPE_THRESH, cm3605->dir); + iio_push_event(indio_dev, ev, iio_get_time_ns(indio_dev)); + + /* Invert the edge for each event */ + if (cm3605->dir == IIO_EV_DIR_RISING) + cm3605->dir = IIO_EV_DIR_FALLING; + else + cm3605->dir = IIO_EV_DIR_RISING; + + return IRQ_HANDLED; +} + +static int cm3605_get_lux(struct cm3605 *cm3605) +{ + int ret, res; + s64 lux; + + ret = iio_read_channel_processed(cm3605->aout, &res); + if (ret < 0) + return ret; + + dev_dbg(cm3605->dev, "read %d mV from ADC\n", res); + + /* + * AOUT has an offset of ~30mV then linear at dark + * then goes from 2.54 up to 650 LUX yielding 1.55V + * (1550 mV) so scale the returned value to this interval + * using simple linear interpolation. + */ + if (res < 30) + return 0; + if (res > CM3605_AOUT_MAX_MV) + dev_err(cm3605->dev, "device out of range\n"); + + /* Remove bias */ + lux = res - 30; + + /* Linear interpolation between 0 and ALS typ max */ + lux *= cm3605->als_max; + lux = div64_s64(lux, CM3605_AOUT_TYP_MAX_MV); + + return lux; +} + +static int cm3605_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cm3605 *cm3605 = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_LIGHT: + ret = cm3605_get_lux(cm3605); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static const struct iio_info cm3605_info = { + .read_raw = cm3605_read_raw, +}; + +static const struct iio_event_spec cm3605_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec cm3605_channels[] = { + { + .type = IIO_PROXIMITY, + .event_spec = cm3605_events, + .num_event_specs = ARRAY_SIZE(cm3605_events), + }, + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .channel = CM3605_ALS_CHANNEL, + }, +}; + +static int cm3605_probe(struct platform_device *pdev) +{ + struct cm3605 *cm3605; + struct iio_dev *indio_dev; + struct device *dev = &pdev->dev; + enum iio_chan_type ch_type; + u32 rset; + int irq; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*cm3605)); + if (!indio_dev) + return -ENOMEM; + platform_set_drvdata(pdev, indio_dev); + + cm3605 = iio_priv(indio_dev); + cm3605->dev = dev; + cm3605->dir = IIO_EV_DIR_FALLING; + + ret = device_property_read_u32(dev, "capella,aset-resistance-ohms", &rset); + if (ret) { + dev_info(dev, "no RSET specified, assuming 100K\n"); + rset = 100000; + } + switch (rset) { + case 50000: + cm3605->als_max = 650; + break; + case 100000: + cm3605->als_max = 300; + break; + case 300000: + cm3605->als_max = 100; + break; + case 600000: + cm3605->als_max = 50; + break; + default: + dev_info(dev, "non-standard resistance\n"); + return -EINVAL; + } + + cm3605->aout = devm_iio_channel_get(dev, "aout"); + if (IS_ERR(cm3605->aout)) { + ret = PTR_ERR(cm3605->aout); + ret = (ret == -ENODEV) ? -EPROBE_DEFER : ret; + return dev_err_probe(dev, ret, "failed to get AOUT ADC channel\n"); + } + ret = iio_get_channel_type(cm3605->aout, &ch_type); + if (ret < 0) + return ret; + if (ch_type != IIO_VOLTAGE) { + dev_err(dev, "wrong type of IIO channel specified for AOUT\n"); + return -EINVAL; + } + + cm3605->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(cm3605->vdd)) + return dev_err_probe(dev, PTR_ERR(cm3605->vdd), + "failed to get VDD regulator\n"); + + ret = regulator_enable(cm3605->vdd); + if (ret) { + dev_err(dev, "failed to enable VDD regulator\n"); + return ret; + } + + cm3605->aset = devm_gpiod_get(dev, "aset", GPIOD_OUT_HIGH); + if (IS_ERR(cm3605->aset)) { + ret = dev_err_probe(dev, PTR_ERR(cm3605->aset), "no ASET GPIO\n"); + goto out_disable_vdd; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = irq; + goto out_disable_aset; + } + + ret = devm_request_threaded_irq(dev, irq, cm3605_prox_irq, + NULL, 0, "cm3605", indio_dev); + if (ret) + goto out_disable_aset; + + /* Just name the trigger the same as the driver */ + led_trigger_register_simple("cm3605", &cm3605->led); + led_trigger_event(cm3605->led, LED_FULL); + + indio_dev->info = &cm3605_info; + indio_dev->name = "cm3605"; + indio_dev->channels = cm3605_channels; + indio_dev->num_channels = ARRAY_SIZE(cm3605_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(indio_dev); + if (ret) + goto out_remove_trigger; + dev_info(dev, "Capella Microsystems CM3605 enabled range 0..%d LUX\n", + cm3605->als_max); + + return 0; + +out_remove_trigger: + led_trigger_event(cm3605->led, LED_OFF); + led_trigger_unregister_simple(cm3605->led); +out_disable_aset: + gpiod_set_value_cansleep(cm3605->aset, 0); +out_disable_vdd: + regulator_disable(cm3605->vdd); + return ret; +} + +static void cm3605_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct cm3605 *cm3605 = iio_priv(indio_dev); + + led_trigger_event(cm3605->led, LED_OFF); + led_trigger_unregister_simple(cm3605->led); + gpiod_set_value_cansleep(cm3605->aset, 0); + iio_device_unregister(indio_dev); + regulator_disable(cm3605->vdd); +} + +static int cm3605_pm_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct cm3605 *cm3605 = iio_priv(indio_dev); + + led_trigger_event(cm3605->led, LED_OFF); + regulator_disable(cm3605->vdd); + + return 0; +} + +static int cm3605_pm_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct cm3605 *cm3605 = iio_priv(indio_dev); + int ret; + + ret = regulator_enable(cm3605->vdd); + if (ret) + dev_err(dev, "failed to enable regulator in resume path\n"); + led_trigger_event(cm3605->led, LED_FULL); + + return 0; +} +static DEFINE_SIMPLE_DEV_PM_OPS(cm3605_dev_pm_ops, cm3605_pm_suspend, + cm3605_pm_resume); + +static const struct of_device_id cm3605_of_match[] = { + {.compatible = "capella,cm3605"}, + { } +}; +MODULE_DEVICE_TABLE(of, cm3605_of_match); + +static struct platform_driver cm3605_driver = { + .driver = { + .name = "cm3605", + .of_match_table = cm3605_of_match, + .pm = pm_sleep_ptr(&cm3605_dev_pm_ops), + }, + .probe = cm3605_probe, + .remove = cm3605_remove, +}; +module_platform_driver(cm3605_driver); + +MODULE_AUTHOR("Linus Walleij "); +MODULE_DESCRIPTION("CM3605 ambient light and proximity sensor driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_CONSUMER"); diff --git a/drivers/iio/light/cm36651.c b/drivers/iio/light/cm36651.c new file mode 100644 index 000000000..6ebd8ce4c --- /dev/null +++ b/drivers/iio/light/cm36651.c @@ -0,0 +1,742 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Beomho Seo + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Slave address 0x19 for PS of 7 bit addressing protocol for I2C */ +#define CM36651_I2C_ADDR_PS 0x19 +/* Alert Response Address */ +#define CM36651_ARA 0x0C + +/* Ambient light sensor */ +#define CM36651_CS_CONF1 0x00 +#define CM36651_CS_CONF2 0x01 +#define CM36651_ALS_WH_M 0x02 +#define CM36651_ALS_WH_L 0x03 +#define CM36651_ALS_WL_M 0x04 +#define CM36651_ALS_WL_L 0x05 +#define CM36651_CS_CONF3 0x06 +#define CM36651_CS_CONF_REG_NUM 0x02 + +/* Proximity sensor */ +#define CM36651_PS_CONF1 0x00 +#define CM36651_PS_THD 0x01 +#define CM36651_PS_CANC 0x02 +#define CM36651_PS_CONF2 0x03 +#define CM36651_PS_REG_NUM 0x04 + +/* CS_CONF1 command code */ +#define CM36651_ALS_ENABLE 0x00 +#define CM36651_ALS_DISABLE 0x01 +#define CM36651_ALS_INT_EN 0x02 +#define CM36651_ALS_THRES 0x04 + +/* CS_CONF2 command code */ +#define CM36651_CS_CONF2_DEFAULT_BIT 0x08 + +/* CS_CONF3 channel integration time */ +#define CM36651_CS_IT1 0x00 /* Integration time 80 msec */ +#define CM36651_CS_IT2 0x40 /* Integration time 160 msec */ +#define CM36651_CS_IT3 0x80 /* Integration time 320 msec */ +#define CM36651_CS_IT4 0xC0 /* Integration time 640 msec */ + +/* PS_CONF1 command code */ +#define CM36651_PS_ENABLE 0x00 +#define CM36651_PS_DISABLE 0x01 +#define CM36651_PS_INT_EN 0x02 +#define CM36651_PS_PERS2 0x04 +#define CM36651_PS_PERS3 0x08 +#define CM36651_PS_PERS4 0x0C + +/* PS_CONF1 command code: integration time */ +#define CM36651_PS_IT1 0x00 /* Integration time 0.32 msec */ +#define CM36651_PS_IT2 0x10 /* Integration time 0.42 msec */ +#define CM36651_PS_IT3 0x20 /* Integration time 0.52 msec */ +#define CM36651_PS_IT4 0x30 /* Integration time 0.64 msec */ + +/* PS_CONF1 command code: duty ratio */ +#define CM36651_PS_DR1 0x00 /* Duty ratio 1/80 */ +#define CM36651_PS_DR2 0x40 /* Duty ratio 1/160 */ +#define CM36651_PS_DR3 0x80 /* Duty ratio 1/320 */ +#define CM36651_PS_DR4 0xC0 /* Duty ratio 1/640 */ + +/* PS_THD command code */ +#define CM36651_PS_INITIAL_THD 0x05 + +/* PS_CANC command code */ +#define CM36651_PS_CANC_DEFAULT 0x00 + +/* PS_CONF2 command code */ +#define CM36651_PS_HYS1 0x00 +#define CM36651_PS_HYS2 0x01 +#define CM36651_PS_SMART_PERS_EN 0x02 +#define CM36651_PS_DIR_INT 0x04 +#define CM36651_PS_MS 0x10 + +#define CM36651_CS_COLOR_NUM 4 + +#define CM36651_CLOSE_PROXIMITY 0x32 +#define CM36651_FAR_PROXIMITY 0x33 + +#define CM36651_CS_INT_TIME_AVAIL "0.08 0.16 0.32 0.64" +#define CM36651_PS_INT_TIME_AVAIL "0.000320 0.000420 0.000520 0.000640" + +enum cm36651_operation_mode { + CM36651_LIGHT_EN, + CM36651_PROXIMITY_EN, + CM36651_PROXIMITY_EV_EN, +}; + +enum cm36651_light_channel_idx { + CM36651_LIGHT_CHANNEL_IDX_RED, + CM36651_LIGHT_CHANNEL_IDX_GREEN, + CM36651_LIGHT_CHANNEL_IDX_BLUE, + CM36651_LIGHT_CHANNEL_IDX_CLEAR, +}; + +enum cm36651_command { + CM36651_CMD_READ_RAW_LIGHT, + CM36651_CMD_READ_RAW_PROXIMITY, + CM36651_CMD_PROX_EV_EN, + CM36651_CMD_PROX_EV_DIS, +}; + +static const u8 cm36651_cs_reg[CM36651_CS_CONF_REG_NUM] = { + CM36651_CS_CONF1, + CM36651_CS_CONF2, +}; + +static const u8 cm36651_ps_reg[CM36651_PS_REG_NUM] = { + CM36651_PS_CONF1, + CM36651_PS_THD, + CM36651_PS_CANC, + CM36651_PS_CONF2, +}; + +struct cm36651_data { + const struct cm36651_platform_data *pdata; + struct i2c_client *client; + struct i2c_client *ps_client; + struct i2c_client *ara_client; + struct mutex lock; + struct regulator *vled_reg; + unsigned long flags; + int cs_int_time[CM36651_CS_COLOR_NUM]; + int ps_int_time; + u8 cs_ctrl_regs[CM36651_CS_CONF_REG_NUM]; + u8 ps_ctrl_regs[CM36651_PS_REG_NUM]; + u16 color[CM36651_CS_COLOR_NUM]; +}; + +static int cm36651_setup_reg(struct cm36651_data *cm36651) +{ + struct i2c_client *client = cm36651->client; + struct i2c_client *ps_client = cm36651->ps_client; + int i, ret; + + /* CS initialization */ + cm36651->cs_ctrl_regs[CM36651_CS_CONF1] = CM36651_ALS_ENABLE | + CM36651_ALS_THRES; + cm36651->cs_ctrl_regs[CM36651_CS_CONF2] = CM36651_CS_CONF2_DEFAULT_BIT; + + for (i = 0; i < CM36651_CS_CONF_REG_NUM; i++) { + ret = i2c_smbus_write_byte_data(client, cm36651_cs_reg[i], + cm36651->cs_ctrl_regs[i]); + if (ret < 0) + return ret; + } + + /* PS initialization */ + cm36651->ps_ctrl_regs[CM36651_PS_CONF1] = CM36651_PS_ENABLE | + CM36651_PS_IT2; + cm36651->ps_ctrl_regs[CM36651_PS_THD] = CM36651_PS_INITIAL_THD; + cm36651->ps_ctrl_regs[CM36651_PS_CANC] = CM36651_PS_CANC_DEFAULT; + cm36651->ps_ctrl_regs[CM36651_PS_CONF2] = CM36651_PS_HYS2 | + CM36651_PS_DIR_INT | CM36651_PS_SMART_PERS_EN; + + for (i = 0; i < CM36651_PS_REG_NUM; i++) { + ret = i2c_smbus_write_byte_data(ps_client, cm36651_ps_reg[i], + cm36651->ps_ctrl_regs[i]); + if (ret < 0) + return ret; + } + + /* Set shutdown mode */ + ret = i2c_smbus_write_byte_data(client, CM36651_CS_CONF1, + CM36651_ALS_DISABLE); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(cm36651->ps_client, + CM36651_PS_CONF1, CM36651_PS_DISABLE); + if (ret < 0) + return ret; + + return 0; +} + +static int cm36651_read_output(struct cm36651_data *cm36651, + struct iio_chan_spec const *chan, int *val) +{ + struct i2c_client *client = cm36651->client; + int ret = -EINVAL; + + switch (chan->type) { + case IIO_LIGHT: + *val = i2c_smbus_read_word_data(client, chan->address); + if (*val < 0) + return ret; + + ret = i2c_smbus_write_byte_data(client, CM36651_CS_CONF1, + CM36651_ALS_DISABLE); + if (ret < 0) + return ret; + + ret = IIO_VAL_INT; + break; + case IIO_PROXIMITY: + *val = i2c_smbus_read_byte(cm36651->ps_client); + if (*val < 0) + return ret; + + if (!test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags)) { + ret = i2c_smbus_write_byte_data(cm36651->ps_client, + CM36651_PS_CONF1, CM36651_PS_DISABLE); + if (ret < 0) + return ret; + } + + ret = IIO_VAL_INT; + break; + default: + break; + } + + return ret; +} + +static irqreturn_t cm36651_irq_handler(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct cm36651_data *cm36651 = iio_priv(indio_dev); + struct i2c_client *client = cm36651->client; + int ev_dir, ret; + u64 ev_code; + + /* + * The PS INT pin is an active low signal that PS INT move logic low + * when the object is detect. Once the MCU host received the PS INT + * "LOW" signal, the Host needs to read the data at Alert Response + * Address(ARA) to clear the PS INT signal. After clearing the PS + * INT pin, the PS INT signal toggles from low to high. + */ + ret = i2c_smbus_read_byte(cm36651->ara_client); + if (ret < 0) { + dev_err(&client->dev, + "%s: Data read failed: %d\n", __func__, ret); + return IRQ_HANDLED; + } + switch (ret) { + case CM36651_CLOSE_PROXIMITY: + ev_dir = IIO_EV_DIR_RISING; + break; + case CM36651_FAR_PROXIMITY: + ev_dir = IIO_EV_DIR_FALLING; + break; + default: + dev_err(&client->dev, + "%s: Data read wrong: %d\n", __func__, ret); + return IRQ_HANDLED; + } + + ev_code = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, + CM36651_CMD_READ_RAW_PROXIMITY, + IIO_EV_TYPE_THRESH, ev_dir); + + iio_push_event(indio_dev, ev_code, iio_get_time_ns(indio_dev)); + + return IRQ_HANDLED; +} + +static int cm36651_set_operation_mode(struct cm36651_data *cm36651, int cmd) +{ + struct i2c_client *client = cm36651->client; + struct i2c_client *ps_client = cm36651->ps_client; + int ret = -EINVAL; + + switch (cmd) { + case CM36651_CMD_READ_RAW_LIGHT: + ret = i2c_smbus_write_byte_data(client, CM36651_CS_CONF1, + cm36651->cs_ctrl_regs[CM36651_CS_CONF1]); + break; + case CM36651_CMD_READ_RAW_PROXIMITY: + if (test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags)) + return CM36651_PROXIMITY_EV_EN; + + ret = i2c_smbus_write_byte_data(ps_client, CM36651_PS_CONF1, + cm36651->ps_ctrl_regs[CM36651_PS_CONF1]); + break; + case CM36651_CMD_PROX_EV_EN: + if (test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags)) { + dev_err(&client->dev, + "Already proximity event enable state\n"); + return ret; + } + set_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags); + + ret = i2c_smbus_write_byte_data(ps_client, + cm36651_ps_reg[CM36651_PS_CONF1], + CM36651_PS_INT_EN | CM36651_PS_PERS2 | CM36651_PS_IT2); + + if (ret < 0) { + dev_err(&client->dev, "Proximity enable event failed\n"); + return ret; + } + break; + case CM36651_CMD_PROX_EV_DIS: + if (!test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags)) { + dev_err(&client->dev, + "Already proximity event disable state\n"); + return ret; + } + clear_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags); + ret = i2c_smbus_write_byte_data(ps_client, + CM36651_PS_CONF1, CM36651_PS_DISABLE); + break; + } + + if (ret < 0) + dev_err(&client->dev, "Write register failed\n"); + + return ret; +} + +static int cm36651_read_channel(struct cm36651_data *cm36651, + struct iio_chan_spec const *chan, int *val) +{ + struct i2c_client *client = cm36651->client; + int cmd, ret; + + if (chan->type == IIO_LIGHT) + cmd = CM36651_CMD_READ_RAW_LIGHT; + else if (chan->type == IIO_PROXIMITY) + cmd = CM36651_CMD_READ_RAW_PROXIMITY; + else + return -EINVAL; + + ret = cm36651_set_operation_mode(cm36651, cmd); + if (ret < 0) { + dev_err(&client->dev, "CM36651 set operation mode failed\n"); + return ret; + } + /* Delay for work after enable operation */ + msleep(50); + ret = cm36651_read_output(cm36651, chan, val); + if (ret < 0) { + dev_err(&client->dev, "CM36651 read output failed\n"); + return ret; + } + + return ret; +} + +static int cm36651_read_int_time(struct cm36651_data *cm36651, + struct iio_chan_spec const *chan, int *val2) +{ + switch (chan->type) { + case IIO_LIGHT: + if (cm36651->cs_int_time[chan->address] == CM36651_CS_IT1) + *val2 = 80000; + else if (cm36651->cs_int_time[chan->address] == CM36651_CS_IT2) + *val2 = 160000; + else if (cm36651->cs_int_time[chan->address] == CM36651_CS_IT3) + *val2 = 320000; + else if (cm36651->cs_int_time[chan->address] == CM36651_CS_IT4) + *val2 = 640000; + else + return -EINVAL; + break; + case IIO_PROXIMITY: + if (cm36651->ps_int_time == CM36651_PS_IT1) + *val2 = 320; + else if (cm36651->ps_int_time == CM36651_PS_IT2) + *val2 = 420; + else if (cm36651->ps_int_time == CM36651_PS_IT3) + *val2 = 520; + else if (cm36651->ps_int_time == CM36651_PS_IT4) + *val2 = 640; + else + return -EINVAL; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int cm36651_write_int_time(struct cm36651_data *cm36651, + struct iio_chan_spec const *chan, int val) +{ + struct i2c_client *client = cm36651->client; + struct i2c_client *ps_client = cm36651->ps_client; + int int_time, ret; + + switch (chan->type) { + case IIO_LIGHT: + if (val == 80000) + int_time = CM36651_CS_IT1; + else if (val == 160000) + int_time = CM36651_CS_IT2; + else if (val == 320000) + int_time = CM36651_CS_IT3; + else if (val == 640000) + int_time = CM36651_CS_IT4; + else + return -EINVAL; + + ret = i2c_smbus_write_byte_data(client, CM36651_CS_CONF3, + int_time >> 2 * (chan->address)); + if (ret < 0) { + dev_err(&client->dev, "CS integration time write failed\n"); + return ret; + } + cm36651->cs_int_time[chan->address] = int_time; + break; + case IIO_PROXIMITY: + if (val == 320) + int_time = CM36651_PS_IT1; + else if (val == 420) + int_time = CM36651_PS_IT2; + else if (val == 520) + int_time = CM36651_PS_IT3; + else if (val == 640) + int_time = CM36651_PS_IT4; + else + return -EINVAL; + + ret = i2c_smbus_write_byte_data(ps_client, + CM36651_PS_CONF1, int_time); + if (ret < 0) { + dev_err(&client->dev, "PS integration time write failed\n"); + return ret; + } + cm36651->ps_int_time = int_time; + break; + default: + return -EINVAL; + } + + return ret; +} + +static int cm36651_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cm36651_data *cm36651 = iio_priv(indio_dev); + int ret; + + mutex_lock(&cm36651->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = cm36651_read_channel(cm36651, chan, val); + break; + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + ret = cm36651_read_int_time(cm36651, chan, val2); + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&cm36651->lock); + + return ret; +} + +static int cm36651_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct cm36651_data *cm36651 = iio_priv(indio_dev); + struct i2c_client *client = cm36651->client; + int ret = -EINVAL; + + if (mask == IIO_CHAN_INFO_INT_TIME) { + ret = cm36651_write_int_time(cm36651, chan, val2); + if (ret < 0) + dev_err(&client->dev, "Integration time write failed\n"); + } + + return ret; +} + +static int cm36651_read_prox_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct cm36651_data *cm36651 = iio_priv(indio_dev); + + *val = cm36651->ps_ctrl_regs[CM36651_PS_THD]; + + return 0; +} + +static int cm36651_write_prox_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct cm36651_data *cm36651 = iio_priv(indio_dev); + struct i2c_client *client = cm36651->client; + int ret; + + if (val < 3 || val > 255) + return -EINVAL; + + cm36651->ps_ctrl_regs[CM36651_PS_THD] = val; + ret = i2c_smbus_write_byte_data(cm36651->ps_client, CM36651_PS_THD, + cm36651->ps_ctrl_regs[CM36651_PS_THD]); + + if (ret < 0) { + dev_err(&client->dev, "PS threshold write failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int cm36651_write_prox_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct cm36651_data *cm36651 = iio_priv(indio_dev); + int cmd, ret; + + mutex_lock(&cm36651->lock); + + cmd = state ? CM36651_CMD_PROX_EV_EN : CM36651_CMD_PROX_EV_DIS; + ret = cm36651_set_operation_mode(cm36651, cmd); + + mutex_unlock(&cm36651->lock); + + return ret; +} + +static int cm36651_read_prox_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct cm36651_data *cm36651 = iio_priv(indio_dev); + int event_en; + + mutex_lock(&cm36651->lock); + + event_en = test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags); + + mutex_unlock(&cm36651->lock); + + return event_en; +} + +#define CM36651_LIGHT_CHANNEL(_color, _idx) { \ + .type = IIO_LIGHT, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .address = _idx, \ + .modified = 1, \ + .channel2 = IIO_MOD_LIGHT_##_color, \ +} \ + +static const struct iio_event_spec cm36651_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + } +}; + +static const struct iio_chan_spec cm36651_channels[] = { + { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME), + .event_spec = cm36651_event_spec, + .num_event_specs = ARRAY_SIZE(cm36651_event_spec), + }, + CM36651_LIGHT_CHANNEL(RED, CM36651_LIGHT_CHANNEL_IDX_RED), + CM36651_LIGHT_CHANNEL(GREEN, CM36651_LIGHT_CHANNEL_IDX_GREEN), + CM36651_LIGHT_CHANNEL(BLUE, CM36651_LIGHT_CHANNEL_IDX_BLUE), + CM36651_LIGHT_CHANNEL(CLEAR, CM36651_LIGHT_CHANNEL_IDX_CLEAR), +}; + +static IIO_CONST_ATTR(in_illuminance_integration_time_available, + CM36651_CS_INT_TIME_AVAIL); +static IIO_CONST_ATTR(in_proximity_integration_time_available, + CM36651_PS_INT_TIME_AVAIL); + +static struct attribute *cm36651_attributes[] = { + &iio_const_attr_in_illuminance_integration_time_available.dev_attr.attr, + &iio_const_attr_in_proximity_integration_time_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group cm36651_attribute_group = { + .attrs = cm36651_attributes +}; + +static const struct iio_info cm36651_info = { + .read_raw = &cm36651_read_raw, + .write_raw = &cm36651_write_raw, + .read_event_value = &cm36651_read_prox_thresh, + .write_event_value = &cm36651_write_prox_thresh, + .read_event_config = &cm36651_read_prox_event_config, + .write_event_config = &cm36651_write_prox_event_config, + .attrs = &cm36651_attribute_group, +}; + +static int cm36651_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct cm36651_data *cm36651; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*cm36651)); + if (!indio_dev) + return -ENOMEM; + + cm36651 = iio_priv(indio_dev); + + cm36651->vled_reg = devm_regulator_get(&client->dev, "vled"); + if (IS_ERR(cm36651->vled_reg)) + return dev_err_probe(&client->dev, PTR_ERR(cm36651->vled_reg), + "get regulator vled failed\n"); + + ret = regulator_enable(cm36651->vled_reg); + if (ret) { + dev_err(&client->dev, "enable regulator vled failed\n"); + return ret; + } + + i2c_set_clientdata(client, indio_dev); + + cm36651->client = client; + cm36651->ps_client = i2c_new_dummy_device(client->adapter, + CM36651_I2C_ADDR_PS); + if (IS_ERR(cm36651->ps_client)) { + dev_err(&client->dev, "%s: new i2c device failed\n", __func__); + ret = PTR_ERR(cm36651->ps_client); + goto error_disable_reg; + } + + cm36651->ara_client = i2c_new_dummy_device(client->adapter, CM36651_ARA); + if (IS_ERR(cm36651->ara_client)) { + dev_err(&client->dev, "%s: new i2c device failed\n", __func__); + ret = PTR_ERR(cm36651->ara_client); + goto error_i2c_unregister_ps; + } + + mutex_init(&cm36651->lock); + indio_dev->channels = cm36651_channels; + indio_dev->num_channels = ARRAY_SIZE(cm36651_channels); + indio_dev->info = &cm36651_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = cm36651_setup_reg(cm36651); + if (ret) { + dev_err(&client->dev, "%s: register setup failed\n", __func__); + goto error_i2c_unregister_ara; + } + + ret = request_threaded_irq(client->irq, NULL, cm36651_irq_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "cm36651", indio_dev); + if (ret) { + dev_err(&client->dev, "%s: request irq failed\n", __func__); + goto error_i2c_unregister_ara; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&client->dev, "%s: register device failed\n", __func__); + goto error_free_irq; + } + + return 0; + +error_free_irq: + free_irq(client->irq, indio_dev); +error_i2c_unregister_ara: + i2c_unregister_device(cm36651->ara_client); +error_i2c_unregister_ps: + i2c_unregister_device(cm36651->ps_client); +error_disable_reg: + regulator_disable(cm36651->vled_reg); + return ret; +} + +static void cm36651_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct cm36651_data *cm36651 = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + regulator_disable(cm36651->vled_reg); + free_irq(client->irq, indio_dev); + i2c_unregister_device(cm36651->ps_client); + i2c_unregister_device(cm36651->ara_client); +} + +static const struct i2c_device_id cm36651_id[] = { + { .name = "cm36651" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, cm36651_id); + +static const struct of_device_id cm36651_of_match[] = { + { .compatible = "capella,cm36651" }, + { } +}; +MODULE_DEVICE_TABLE(of, cm36651_of_match); + +static struct i2c_driver cm36651_driver = { + .driver = { + .name = "cm36651", + .of_match_table = cm36651_of_match, + }, + .probe = cm36651_probe, + .remove = cm36651_remove, + .id_table = cm36651_id, +}; + +module_i2c_driver(cm36651_driver); + +MODULE_AUTHOR("Beomho Seo "); +MODULE_DESCRIPTION("CM36651 proximity/ambient light sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/cros_ec_light_prox.c b/drivers/iio/light/cros_ec_light_prox.c new file mode 100644 index 000000000..7ab565b1f --- /dev/null +++ b/drivers/iio/light/cros_ec_light_prox.c @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * cros_ec_light_prox - Driver for light and prox sensors behing CrosEC. + * + * Copyright (C) 2017 Google, Inc + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * We only represent one entry for light or proximity. EC is merging different + * light sensors to return the what the eye would see. For proximity, we + * currently support only one light source. + */ +#define CROS_EC_LIGHT_PROX_MAX_CHANNELS (1 + 1) + +/* State data for ec_sensors iio driver. */ +struct cros_ec_light_prox_state { + /* Shared by all sensors */ + struct cros_ec_sensors_core_state core; + + struct iio_chan_spec channels[CROS_EC_LIGHT_PROX_MAX_CHANNELS]; +}; + +static int cros_ec_light_prox_read(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cros_ec_light_prox_state *st = iio_priv(indio_dev); + u16 data = 0; + s64 val64; + int ret; + int idx = chan->scan_index; + + mutex_lock(&st->core.cmd_lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_PROXIMITY) { + ret = cros_ec_sensors_read_cmd(indio_dev, 1 << idx, + (s16 *)&data); + if (ret) + break; + *val = data; + ret = IIO_VAL_INT; + } else { + ret = -EINVAL; + } + break; + case IIO_CHAN_INFO_PROCESSED: + if (chan->type == IIO_LIGHT) { + ret = cros_ec_sensors_read_cmd(indio_dev, 1 << idx, + (s16 *)&data); + if (ret) + break; + /* + * The data coming from the light sensor is + * pre-processed and represents the ambient light + * illuminance reading expressed in lux. + */ + *val = data; + ret = IIO_VAL_INT; + } else { + ret = -EINVAL; + } + break; + case IIO_CHAN_INFO_CALIBBIAS: + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_OFFSET; + st->core.param.sensor_offset.flags = 0; + + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + if (ret) + break; + + /* Save values */ + st->core.calib[0].offset = + st->core.resp->sensor_offset.offset[0]; + + *val = st->core.calib[idx].offset; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_CALIBSCALE: + /* + * RANGE is used for calibration + * scale is a number x.y, where x is coded on 16 bits, + * y coded on 16 bits, between 0 and 9999. + */ + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE; + st->core.param.sensor_range.data = EC_MOTION_SENSE_NO_VALUE; + + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + if (ret) + break; + + val64 = st->core.resp->sensor_range.ret; + *val = val64 >> 16; + *val2 = (val64 & 0xffff) * 100; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + ret = cros_ec_sensors_core_read(&st->core, chan, val, val2, + mask); + break; + } + + mutex_unlock(&st->core.cmd_lock); + + return ret; +} + +static int cros_ec_light_prox_write(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct cros_ec_light_prox_state *st = iio_priv(indio_dev); + int ret; + int idx = chan->scan_index; + + mutex_lock(&st->core.cmd_lock); + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + st->core.calib[idx].offset = val; + /* Send to EC for each axis, even if not complete */ + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_OFFSET; + st->core.param.sensor_offset.flags = MOTION_SENSE_SET_OFFSET; + st->core.param.sensor_offset.offset[0] = + st->core.calib[0].offset; + st->core.param.sensor_offset.temp = + EC_MOTION_SENSE_INVALID_CALIB_TEMP; + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + break; + case IIO_CHAN_INFO_CALIBSCALE: + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE; + st->core.curr_range = (val << 16) | (val2 / 100); + st->core.param.sensor_range.data = st->core.curr_range; + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + if (ret == 0) + st->core.range_updated = true; + break; + default: + ret = cros_ec_sensors_core_write(&st->core, chan, val, val2, + mask); + break; + } + + mutex_unlock(&st->core.cmd_lock); + + return ret; +} + +static const struct iio_info cros_ec_light_prox_info = { + .read_raw = &cros_ec_light_prox_read, + .write_raw = &cros_ec_light_prox_write, + .read_avail = &cros_ec_sensors_core_read_avail, +}; + +static int cros_ec_light_prox_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct cros_ec_light_prox_state *state; + struct iio_chan_spec *channel; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*state)); + if (!indio_dev) + return -ENOMEM; + + ret = cros_ec_sensors_core_init(pdev, indio_dev, true, + cros_ec_sensors_capture); + if (ret) + return ret; + + indio_dev->info = &cros_ec_light_prox_info; + state = iio_priv(indio_dev); + channel = state->channels; + + /* Common part */ + channel->info_mask_shared_by_all = + BIT(IIO_CHAN_INFO_SAMP_FREQ); + channel->info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_SAMP_FREQ); + channel->scan_type.realbits = CROS_EC_SENSOR_BITS; + channel->scan_type.storagebits = CROS_EC_SENSOR_BITS; + channel->scan_type.shift = 0; + channel->scan_index = 0; + channel->ext_info = cros_ec_sensors_ext_info; + channel->scan_type.sign = 'u'; + + /* Sensor specific */ + switch (state->core.type) { + case MOTIONSENSE_TYPE_LIGHT: + channel->type = IIO_LIGHT; + channel->info_mask_separate = + BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_CALIBSCALE); + break; + case MOTIONSENSE_TYPE_PROX: + channel->type = IIO_PROXIMITY; + channel->info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_CALIBSCALE); + break; + default: + dev_warn(dev, "Unknown motion sensor\n"); + return -EINVAL; + } + + channel++; + *channel = IIO_CHAN_SOFT_TIMESTAMP(1); + + indio_dev->channels = state->channels; + + indio_dev->num_channels = CROS_EC_LIGHT_PROX_MAX_CHANNELS; + + state->core.read_ec_sensors_data = cros_ec_sensors_read_cmd; + + return cros_ec_sensors_core_register(dev, indio_dev, + cros_ec_sensors_push_data); +} + +static const struct platform_device_id cros_ec_light_prox_ids[] = { + { + .name = "cros-ec-prox", + }, + { + .name = "cros-ec-light", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, cros_ec_light_prox_ids); + +static struct platform_driver cros_ec_light_prox_platform_driver = { + .driver = { + .name = "cros-ec-light-prox", + .pm = &cros_ec_sensors_pm_ops, + }, + .probe = cros_ec_light_prox_probe, + .id_table = cros_ec_light_prox_ids, +}; +module_platform_driver(cros_ec_light_prox_platform_driver); + +MODULE_DESCRIPTION("ChromeOS EC light/proximity sensors driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/gp2ap002.c b/drivers/iio/light/gp2ap002.c new file mode 100644 index 000000000..1fd40d601 --- /dev/null +++ b/drivers/iio/light/gp2ap002.c @@ -0,0 +1,732 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * These are the two Sharp GP2AP002 variants supported by this driver: + * GP2AP002A00F Ambient Light and Proximity Sensor + * GP2AP002S00F Proximity Sensor + * + * Copyright (C) 2020 Linaro Ltd. + * Author: Linus Walleij + * + * Based partly on the code in Sony Ericssons GP2AP00200F driver by + * Courtney Cavin and Oskar Andero in drivers/input/misc/gp2ap002a00f.c + * Based partly on a Samsung misc driver submitted by + * Donggeun Kim & Minkyu Kang in 2011: + * https://lore.kernel.org/lkml/1315556546-7445-1-git-send-email-dg77.kim@samsung.com/ + * Based partly on a submission by + * Jonathan Bakker and Paweł Chmiel in january 2019: + * https://lore.kernel.org/linux-input/20190125175045.22576-1-pawel.mikolaj.chmiel@gmail.com/ + * Based partly on code from the Samsung GT-S7710 by + * Based partly on the code in LG Electronics GP2AP00200F driver by + * Kenobi Lee and EunYoung Cho + */ +#include +#include +#include +#include +#include +#include +#include /* To get our ADC channel */ +#include /* To deal with our ADC channel */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define GP2AP002_PROX_CHANNEL 0 +#define GP2AP002_ALS_CHANNEL 1 + +/* ------------------------------------------------------------------------ */ +/* ADDRESS SYMBOL DATA Init R/W */ +/* D7 D6 D5 D4 D3 D2 D1 D0 */ +/* ------------------------------------------------------------------------ */ +/* 0 PROX X X X X X X X VO H'00 R */ +/* 1 GAIN X X X X LED0 X X X H'00 W */ +/* 2 HYS HYSD HYSC1 HYSC0 X HYSF3 HYSF2 HYSF1 HYSF0 H'00 W */ +/* 3 CYCLE X X CYCL2 CYCL1 CYCL0 OSC2 X X H'00 W */ +/* 4 OPMOD X X X ASD X X VCON SSD H'00 W */ +/* 6 CON X X X OCON1 OCON0 X X X H'00 W */ +/* ------------------------------------------------------------------------ */ +/* VO :Proximity sensing result(0: no detection, 1: detection) */ +/* LED0 :Select switch for LED driver's On-registence(0:2x higher, 1:normal)*/ +/* HYSD/HYSF :Adjusts the receiver sensitivity */ +/* OSC :Select switch internal clocl frequency hoppling(0:effective) */ +/* CYCL :Determine the detection cycle(typically 8ms, up to 128x) */ +/* SSD :Software Shutdown function(0:shutdown, 1:operating) */ +/* VCON :VOUT output method control(0:normal, 1:interrupt) */ +/* ASD :Select switch for analog sleep function(0:ineffective, 1:effective)*/ +/* OCON :Select switch for enabling/disabling VOUT (00:enable, 11:disable) */ + +#define GP2AP002_PROX 0x00 +#define GP2AP002_GAIN 0x01 +#define GP2AP002_HYS 0x02 +#define GP2AP002_CYCLE 0x03 +#define GP2AP002_OPMOD 0x04 +#define GP2AP002_CON 0x06 + +#define GP2AP002_PROX_VO_DETECT BIT(0) + +/* Setting this bit to 0 means 2x higher LED resistance */ +#define GP2AP002_GAIN_LED_NORMAL BIT(3) + +/* + * These bits adjusts the proximity sensitivity, determining characteristics + * of the detection distance and its hysteresis. + */ +#define GP2AP002_HYS_HYSD_SHIFT 7 +#define GP2AP002_HYS_HYSD_MASK BIT(7) +#define GP2AP002_HYS_HYSC_SHIFT 5 +#define GP2AP002_HYS_HYSC_MASK GENMASK(6, 5) +#define GP2AP002_HYS_HYSF_SHIFT 0 +#define GP2AP002_HYS_HYSF_MASK GENMASK(3, 0) +#define GP2AP002_HYS_MASK (GP2AP002_HYS_HYSD_MASK | \ + GP2AP002_HYS_HYSC_MASK | \ + GP2AP002_HYS_HYSF_MASK) + +/* + * These values determine the detection cycle response time + * 0: 8ms, 1: 16ms, 2: 32ms, 3: 64ms, 4: 128ms, + * 5: 256ms, 6: 512ms, 7: 1024ms + */ +#define GP2AP002_CYCLE_CYCL_SHIFT 3 +#define GP2AP002_CYCLE_CYCL_MASK GENMASK(5, 3) + +/* + * Select switch for internal clock frequency hopping + * 0: effective, + * 1: ineffective + */ +#define GP2AP002_CYCLE_OSC_EFFECTIVE 0 +#define GP2AP002_CYCLE_OSC_INEFFECTIVE BIT(2) +#define GP2AP002_CYCLE_OSC_MASK BIT(2) + +/* Analog sleep effective */ +#define GP2AP002_OPMOD_ASD BIT(4) +/* Enable chip */ +#define GP2AP002_OPMOD_SSD_OPERATING BIT(0) +/* IRQ mode */ +#define GP2AP002_OPMOD_VCON_IRQ BIT(1) +#define GP2AP002_OPMOD_MASK (BIT(0) | BIT(1) | BIT(4)) + +/* + * Select switch for enabling/disabling Vout pin + * 0: enable + * 2: force to go Low + * 3: force to go High + */ +#define GP2AP002_CON_OCON_SHIFT 3 +#define GP2AP002_CON_OCON_ENABLE (0x0 << GP2AP002_CON_OCON_SHIFT) +#define GP2AP002_CON_OCON_LOW (0x2 << GP2AP002_CON_OCON_SHIFT) +#define GP2AP002_CON_OCON_HIGH (0x3 << GP2AP002_CON_OCON_SHIFT) +#define GP2AP002_CON_OCON_MASK (0x3 << GP2AP002_CON_OCON_SHIFT) + +/** + * struct gp2ap002 - GP2AP002 state + * @map: regmap pointer for the i2c regmap + * @dev: pointer to parent device + * @vdd: regulator controlling VDD + * @vio: regulator controlling VIO + * @alsout: IIO ADC channel to convert the ALSOUT signal + * @hys_far: hysteresis control from device tree + * @hys_close: hysteresis control from device tree + * @is_gp2ap002s00f: this is the GP2AP002F variant of the chip + * @irq: the IRQ line used by this device + * @enabled: we cannot read the status of the hardware so we need to + * keep track of whether the event is enabled using this state variable + */ +struct gp2ap002 { + struct regmap *map; + struct device *dev; + struct regulator *vdd; + struct regulator *vio; + struct iio_channel *alsout; + u8 hys_far; + u8 hys_close; + bool is_gp2ap002s00f; + int irq; + bool enabled; +}; + +static irqreturn_t gp2ap002_prox_irq(int irq, void *d) +{ + struct iio_dev *indio_dev = d; + struct gp2ap002 *gp2ap002 = iio_priv(indio_dev); + u64 ev; + int val; + int ret; + + if (!gp2ap002->enabled) + goto err_retrig; + + ret = regmap_read(gp2ap002->map, GP2AP002_PROX, &val); + if (ret) { + dev_err(gp2ap002->dev, "error reading proximity\n"); + goto err_retrig; + } + + if (val & GP2AP002_PROX_VO_DETECT) { + /* Close */ + dev_dbg(gp2ap002->dev, "close\n"); + ret = regmap_write(gp2ap002->map, GP2AP002_HYS, + gp2ap002->hys_far); + if (ret) + dev_err(gp2ap002->dev, + "error setting up proximity hysteresis\n"); + ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, GP2AP002_PROX_CHANNEL, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING); + } else { + /* Far */ + dev_dbg(gp2ap002->dev, "far\n"); + ret = regmap_write(gp2ap002->map, GP2AP002_HYS, + gp2ap002->hys_close); + if (ret) + dev_err(gp2ap002->dev, + "error setting up proximity hysteresis\n"); + ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, GP2AP002_PROX_CHANNEL, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING); + } + iio_push_event(indio_dev, ev, iio_get_time_ns(indio_dev)); + + /* + * After changing hysteresis, we need to wait for one detection + * cycle to see if anything changed, or we will just trigger the + * previous interrupt again. A detection cycle depends on the CYCLE + * register, we are hard-coding ~8 ms in probe() so wait some more + * than this, 20-30 ms. + */ + usleep_range(20000, 30000); + +err_retrig: + ret = regmap_write(gp2ap002->map, GP2AP002_CON, + GP2AP002_CON_OCON_ENABLE); + if (ret) + dev_err(gp2ap002->dev, "error setting up VOUT control\n"); + + return IRQ_HANDLED; +} + +/* + * This array maps current and lux. + * + * Ambient light sensing range is 3 to 55000 lux. + * + * This mapping is based on the following formula. + * illuminance = 10 ^ (current[mA] / 10) + * + * When the ADC measures 0, return 0 lux. + */ +static const u16 gp2ap002_illuminance_table[] = { + 0, 1, 1, 2, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 25, 32, 40, 50, 63, 79, + 100, 126, 158, 200, 251, 316, 398, 501, 631, 794, 1000, 1259, 1585, + 1995, 2512, 3162, 3981, 5012, 6310, 7943, 10000, 12589, 15849, 19953, + 25119, 31623, 39811, 50119, +}; + +static int gp2ap002_get_lux(struct gp2ap002 *gp2ap002) +{ + int ret, res; + u16 lux; + + ret = iio_read_channel_processed(gp2ap002->alsout, &res); + if (ret < 0) + return ret; + + dev_dbg(gp2ap002->dev, "read %d mA from ADC\n", res); + + /* ensure we don't under/overflow */ + res = clamp(res, 0, (int)ARRAY_SIZE(gp2ap002_illuminance_table) - 1); + lux = gp2ap002_illuminance_table[res]; + + return (int)lux; +} + +static int gp2ap002_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct gp2ap002 *gp2ap002 = iio_priv(indio_dev); + int ret; + + pm_runtime_get_sync(gp2ap002->dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_LIGHT: + ret = gp2ap002_get_lux(gp2ap002); + if (ret < 0) + goto out; + *val = ret; + ret = IIO_VAL_INT; + goto out; + default: + ret = -EINVAL; + goto out; + } + default: + ret = -EINVAL; + } + +out: + pm_runtime_put_autosuspend(gp2ap002->dev); + + return ret; +} + +static int gp2ap002_init(struct gp2ap002 *gp2ap002) +{ + int ret; + + /* Set up the IR LED resistance */ + ret = regmap_write(gp2ap002->map, GP2AP002_GAIN, + GP2AP002_GAIN_LED_NORMAL); + if (ret) { + dev_err(gp2ap002->dev, "error setting up LED gain\n"); + return ret; + } + ret = regmap_write(gp2ap002->map, GP2AP002_HYS, gp2ap002->hys_far); + if (ret) { + dev_err(gp2ap002->dev, + "error setting up proximity hysteresis\n"); + return ret; + } + + /* Disable internal frequency hopping */ + ret = regmap_write(gp2ap002->map, GP2AP002_CYCLE, + GP2AP002_CYCLE_OSC_INEFFECTIVE); + if (ret) { + dev_err(gp2ap002->dev, + "error setting up internal frequency hopping\n"); + return ret; + } + + /* Enable chip and IRQ, disable analog sleep */ + ret = regmap_write(gp2ap002->map, GP2AP002_OPMOD, + GP2AP002_OPMOD_SSD_OPERATING | + GP2AP002_OPMOD_VCON_IRQ); + if (ret) { + dev_err(gp2ap002->dev, "error setting up operation mode\n"); + return ret; + } + + /* Interrupt on VOUT enabled */ + ret = regmap_write(gp2ap002->map, GP2AP002_CON, + GP2AP002_CON_OCON_ENABLE); + if (ret) + dev_err(gp2ap002->dev, "error setting up VOUT control\n"); + + return ret; +} + +static int gp2ap002_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct gp2ap002 *gp2ap002 = iio_priv(indio_dev); + + /* + * We just keep track of this internally, as it is not possible to + * query the hardware. + */ + return gp2ap002->enabled; +} + +static int gp2ap002_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct gp2ap002 *gp2ap002 = iio_priv(indio_dev); + int ret; + + if (state == gp2ap002->enabled) + return 0; + + if (state) { + /* + * This will bring the regulators up (unless they are on + * already) and reintialize the sensor by using runtime_pm + * callbacks. + */ + ret = pm_runtime_resume_and_get(gp2ap002->dev); + if (ret) + return ret; + + } else { + pm_runtime_put_autosuspend(gp2ap002->dev); + } + + gp2ap002->enabled = state; + + return 0; +} + +static const struct iio_info gp2ap002_info = { + .read_raw = gp2ap002_read_raw, + .read_event_config = gp2ap002_read_event_config, + .write_event_config = gp2ap002_write_event_config, +}; + +static const struct iio_event_spec gp2ap002_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec gp2ap002_channels[] = { + { + .type = IIO_PROXIMITY, + .event_spec = gp2ap002_events, + .num_event_specs = ARRAY_SIZE(gp2ap002_events), + }, + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .channel = GP2AP002_ALS_CHANNEL, + }, +}; + +/* + * We need a special regmap because this hardware expects to + * write single bytes to registers but read a 16bit word on some + * variants and discard the lower 8 bits so combine + * i2c_smbus_read_word_data() with i2c_smbus_write_byte_data() + * selectively like this. + */ +static int gp2ap002_regmap_i2c_read(void *context, unsigned int reg, + unsigned int *val) +{ + struct device *dev = context; + struct i2c_client *i2c = to_i2c_client(dev); + int ret; + + ret = i2c_smbus_read_word_data(i2c, reg); + if (ret < 0) + return ret; + + *val = (ret >> 8) & 0xFF; + + return 0; +} + +static int gp2ap002_regmap_i2c_write(void *context, unsigned int reg, + unsigned int val) +{ + struct device *dev = context; + struct i2c_client *i2c = to_i2c_client(dev); + + return i2c_smbus_write_byte_data(i2c, reg, val); +} + +static const struct regmap_bus gp2ap002_regmap_bus = { + .reg_read = gp2ap002_regmap_i2c_read, + .reg_write = gp2ap002_regmap_i2c_write, +}; + +static int gp2ap002_probe(struct i2c_client *client) +{ + struct gp2ap002 *gp2ap002; + struct iio_dev *indio_dev; + struct device *dev = &client->dev; + enum iio_chan_type ch_type; + static const struct regmap_config config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = GP2AP002_CON, + }; + struct regmap *regmap; + int num_chan; + const char *compat; + u8 val; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*gp2ap002)); + if (!indio_dev) + return -ENOMEM; + i2c_set_clientdata(client, indio_dev); + + gp2ap002 = iio_priv(indio_dev); + gp2ap002->dev = dev; + + /* + * Check the device compatible like this makes it possible to use + * ACPI PRP0001 for registering the sensor using device tree + * properties. + */ + ret = device_property_read_string(dev, "compatible", &compat); + if (ret) { + dev_err(dev, "cannot check compatible\n"); + return ret; + } + gp2ap002->is_gp2ap002s00f = !strcmp(compat, "sharp,gp2ap002s00f"); + + regmap = devm_regmap_init(dev, &gp2ap002_regmap_bus, dev, &config); + if (IS_ERR(regmap)) { + dev_err(dev, "Failed to register i2c regmap %ld\n", PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + gp2ap002->map = regmap; + + /* + * The hysteresis settings are coded into the device tree as values + * to be written into the hysteresis register. The datasheet defines + * modes "A", "B1" and "B2" with fixed values to be use but vendor + * code trees for actual devices are tweaking these values and refer to + * modes named things like "B1.5". To be able to support any devices, + * we allow passing an arbitrary hysteresis setting for "near" and + * "far". + */ + + /* Check the device tree for the IR LED hysteresis */ + ret = device_property_read_u8(dev, "sharp,proximity-far-hysteresis", + &val); + if (ret) { + dev_err(dev, "failed to obtain proximity far setting\n"); + return ret; + } + dev_dbg(dev, "proximity far setting %02x\n", val); + gp2ap002->hys_far = val; + + ret = device_property_read_u8(dev, "sharp,proximity-close-hysteresis", + &val); + if (ret) { + dev_err(dev, "failed to obtain proximity close setting\n"); + return ret; + } + dev_dbg(dev, "proximity close setting %02x\n", val); + gp2ap002->hys_close = val; + + /* The GP2AP002A00F has a light sensor too */ + if (!gp2ap002->is_gp2ap002s00f) { + gp2ap002->alsout = devm_iio_channel_get(dev, "alsout"); + if (IS_ERR(gp2ap002->alsout)) { + ret = PTR_ERR(gp2ap002->alsout); + ret = (ret == -ENODEV) ? -EPROBE_DEFER : ret; + return dev_err_probe(dev, ret, "failed to get ALSOUT ADC channel\n"); + } + ret = iio_get_channel_type(gp2ap002->alsout, &ch_type); + if (ret < 0) + return ret; + if (ch_type != IIO_CURRENT) { + dev_err(dev, + "wrong type of IIO channel specified for ALSOUT\n"); + return -EINVAL; + } + } + + gp2ap002->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(gp2ap002->vdd)) + return dev_err_probe(dev, PTR_ERR(gp2ap002->vdd), + "failed to get VDD regulator\n"); + + gp2ap002->vio = devm_regulator_get(dev, "vio"); + if (IS_ERR(gp2ap002->vio)) + return dev_err_probe(dev, PTR_ERR(gp2ap002->vio), + "failed to get VIO regulator\n"); + + /* Operating voltage 2.4V .. 3.6V according to datasheet */ + ret = regulator_set_voltage(gp2ap002->vdd, 2400000, 3600000); + if (ret) { + dev_err(dev, "failed to sett VDD voltage\n"); + return ret; + } + + /* VIO should be between 1.65V and VDD */ + ret = regulator_get_voltage(gp2ap002->vdd); + if (ret < 0) { + dev_err(dev, "failed to get VDD voltage\n"); + return ret; + } + ret = regulator_set_voltage(gp2ap002->vio, 1650000, ret); + if (ret) { + dev_err(dev, "failed to set VIO voltage\n"); + return ret; + } + + ret = regulator_enable(gp2ap002->vdd); + if (ret) { + dev_err(dev, "failed to enable VDD regulator\n"); + return ret; + } + ret = regulator_enable(gp2ap002->vio); + if (ret) { + dev_err(dev, "failed to enable VIO regulator\n"); + goto out_disable_vdd; + } + + msleep(20); + + /* + * Initialize the device and signal to runtime PM that now we are + * definitely up and using power. + */ + ret = gp2ap002_init(gp2ap002); + if (ret) { + dev_err(dev, "initialization failed\n"); + goto out_disable_vio; + } + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + gp2ap002->enabled = false; + + ret = devm_request_threaded_irq(dev, client->irq, NULL, + gp2ap002_prox_irq, IRQF_ONESHOT, + "gp2ap002", indio_dev); + if (ret) + goto out_put_pm; + gp2ap002->irq = client->irq; + + /* + * As the device takes 20 ms + regulator delay to come up with a fresh + * measurement after power-on, do not shut it down unnecessarily. + * Set autosuspend to a one second. + */ + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + pm_runtime_put(dev); + + indio_dev->info = &gp2ap002_info; + indio_dev->name = "gp2ap002"; + indio_dev->channels = gp2ap002_channels; + /* Skip light channel for the proximity-only sensor */ + num_chan = ARRAY_SIZE(gp2ap002_channels); + if (gp2ap002->is_gp2ap002s00f) + num_chan--; + indio_dev->num_channels = num_chan; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(indio_dev); + if (ret) + goto out_disable_pm; + dev_dbg(dev, "Sharp GP2AP002 probed successfully\n"); + + return 0; + +out_put_pm: + pm_runtime_put_noidle(dev); +out_disable_pm: + pm_runtime_disable(dev); +out_disable_vio: + regulator_disable(gp2ap002->vio); +out_disable_vdd: + regulator_disable(gp2ap002->vdd); + return ret; +} + +static void gp2ap002_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct gp2ap002 *gp2ap002 = iio_priv(indio_dev); + struct device *dev = &client->dev; + + pm_runtime_get_sync(dev); + pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); + iio_device_unregister(indio_dev); + regulator_disable(gp2ap002->vio); + regulator_disable(gp2ap002->vdd); +} + +static int gp2ap002_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct gp2ap002 *gp2ap002 = iio_priv(indio_dev); + int ret; + + /* Deactivate the IRQ */ + disable_irq(gp2ap002->irq); + + /* Disable chip and IRQ, everything off */ + ret = regmap_write(gp2ap002->map, GP2AP002_OPMOD, 0x00); + if (ret) { + enable_irq(gp2ap002->irq); + dev_err(gp2ap002->dev, "error setting up operation mode\n"); + return ret; + } + /* + * As these regulators may be shared, at least we are now in + * sleep even if the regulators aren't really turned off. + */ + regulator_disable(gp2ap002->vio); + regulator_disable(gp2ap002->vdd); + + return 0; +} + +static int gp2ap002_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct gp2ap002 *gp2ap002 = iio_priv(indio_dev); + int ret; + + ret = regulator_enable(gp2ap002->vdd); + if (ret) { + dev_err(dev, "failed to enable VDD regulator in resume path\n"); + return ret; + } + ret = regulator_enable(gp2ap002->vio); + if (ret) { + dev_err(dev, "failed to enable VIO regulator in resume path\n"); + goto out_disable_vdd; + } + + msleep(20); + + ret = gp2ap002_init(gp2ap002); + if (ret) { + dev_err(dev, "re-initialization failed\n"); + goto out_disable_vio; + } + + /* Re-activate the IRQ */ + enable_irq(gp2ap002->irq); + + return 0; + +out_disable_vio: + regulator_disable(gp2ap002->vio); +out_disable_vdd: + regulator_disable(gp2ap002->vdd); + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(gp2ap002_dev_pm_ops, gp2ap002_runtime_suspend, + gp2ap002_runtime_resume, NULL); + +static const struct i2c_device_id gp2ap002_id_table[] = { + { .name = "gp2ap002" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, gp2ap002_id_table); + +static const struct of_device_id gp2ap002_of_match[] = { + { .compatible = "sharp,gp2ap002a00f" }, + { .compatible = "sharp,gp2ap002s00f" }, + { } +}; +MODULE_DEVICE_TABLE(of, gp2ap002_of_match); + +static struct i2c_driver gp2ap002_driver = { + .driver = { + .name = "gp2ap002", + .of_match_table = gp2ap002_of_match, + .pm = pm_ptr(&gp2ap002_dev_pm_ops), + }, + .probe = gp2ap002_probe, + .remove = gp2ap002_remove, + .id_table = gp2ap002_id_table, +}; +module_i2c_driver(gp2ap002_driver); + +MODULE_AUTHOR("Linus Walleij "); +MODULE_DESCRIPTION("GP2AP002 ambient light and proximity sensor driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_CONSUMER"); diff --git a/drivers/iio/light/gp2ap020a00f.c b/drivers/iio/light/gp2ap020a00f.c new file mode 100644 index 000000000..63591b7ec --- /dev/null +++ b/drivers/iio/light/gp2ap020a00f.c @@ -0,0 +1,1551 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Jacek Anaszewski + * + * IIO features supported by the driver: + * + * Read-only raw channels: + * - illuminance_clear [lux] + * - illuminance_ir + * - proximity + * + * Triggered buffer: + * - illuminance_clear + * - illuminance_ir + * - proximity + * + * Events: + * - illuminance_clear (rising and falling) + * - proximity (rising and falling) + * - both falling and rising thresholds for the proximity events + * must be set to the values greater than 0. + * + * The driver supports triggered buffers for all the three + * channels as well as high and low threshold events for the + * illuminance_clear and proxmimity channels. Triggers + * can be enabled simultaneously with both illuminance_clear + * events. Proximity events cannot be enabled simultaneously + * with any triggers or illuminance events. Enabling/disabling + * one of the proximity events automatically enables/disables + * the other one. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* Registers */ +#define GP2AP020A00F_OP_REG 0x00 /* Basic operations */ +#define GP2AP020A00F_ALS_REG 0x01 /* ALS related settings */ +#define GP2AP020A00F_PS_REG 0x02 /* PS related settings */ +#define GP2AP020A00F_LED_REG 0x03 /* LED reg */ +#define GP2AP020A00F_TL_L_REG 0x04 /* ALS: Threshold low LSB */ +#define GP2AP020A00F_TL_H_REG 0x05 /* ALS: Threshold low MSB */ +#define GP2AP020A00F_TH_L_REG 0x06 /* ALS: Threshold high LSB */ +#define GP2AP020A00F_TH_H_REG 0x07 /* ALS: Threshold high MSB */ +#define GP2AP020A00F_PL_L_REG 0x08 /* PS: Threshold low LSB */ +#define GP2AP020A00F_PL_H_REG 0x09 /* PS: Threshold low MSB */ +#define GP2AP020A00F_PH_L_REG 0x0a /* PS: Threshold high LSB */ +#define GP2AP020A00F_PH_H_REG 0x0b /* PS: Threshold high MSB */ +#define GP2AP020A00F_D0_L_REG 0x0c /* ALS result: Clear/Illuminance LSB */ +#define GP2AP020A00F_D0_H_REG 0x0d /* ALS result: Clear/Illuminance MSB */ +#define GP2AP020A00F_D1_L_REG 0x0e /* ALS result: IR LSB */ +#define GP2AP020A00F_D1_H_REG 0x0f /* ALS result: IR LSB */ +#define GP2AP020A00F_D2_L_REG 0x10 /* PS result LSB */ +#define GP2AP020A00F_D2_H_REG 0x11 /* PS result MSB */ +#define GP2AP020A00F_NUM_REGS 0x12 /* Number of registers */ + +/* OP_REG bits */ +#define GP2AP020A00F_OP3_MASK 0x80 /* Software shutdown */ +#define GP2AP020A00F_OP3_SHUTDOWN 0x00 +#define GP2AP020A00F_OP3_OPERATION 0x80 +#define GP2AP020A00F_OP2_MASK 0x40 /* Auto shutdown/Continuous mode */ +#define GP2AP020A00F_OP2_AUTO_SHUTDOWN 0x00 +#define GP2AP020A00F_OP2_CONT_OPERATION 0x40 +#define GP2AP020A00F_OP_MASK 0x30 /* Operating mode selection */ +#define GP2AP020A00F_OP_ALS_AND_PS 0x00 +#define GP2AP020A00F_OP_ALS 0x10 +#define GP2AP020A00F_OP_PS 0x20 +#define GP2AP020A00F_OP_DEBUG 0x30 +#define GP2AP020A00F_PROX_MASK 0x08 /* PS: detection/non-detection */ +#define GP2AP020A00F_PROX_NON_DETECT 0x00 +#define GP2AP020A00F_PROX_DETECT 0x08 +#define GP2AP020A00F_FLAG_P 0x04 /* PS: interrupt result */ +#define GP2AP020A00F_FLAG_A 0x02 /* ALS: interrupt result */ +#define GP2AP020A00F_TYPE_MASK 0x01 /* Output data type selection */ +#define GP2AP020A00F_TYPE_MANUAL_CALC 0x00 +#define GP2AP020A00F_TYPE_AUTO_CALC 0x01 + +/* ALS_REG bits */ +#define GP2AP020A00F_PRST_MASK 0xc0 /* Number of measurement cycles */ +#define GP2AP020A00F_PRST_ONCE 0x00 +#define GP2AP020A00F_PRST_4_CYCLES 0x40 +#define GP2AP020A00F_PRST_8_CYCLES 0x80 +#define GP2AP020A00F_PRST_16_CYCLES 0xc0 +#define GP2AP020A00F_RES_A_MASK 0x38 /* ALS: Resolution */ +#define GP2AP020A00F_RES_A_800ms 0x00 +#define GP2AP020A00F_RES_A_400ms 0x08 +#define GP2AP020A00F_RES_A_200ms 0x10 +#define GP2AP020A00F_RES_A_100ms 0x18 +#define GP2AP020A00F_RES_A_25ms 0x20 +#define GP2AP020A00F_RES_A_6_25ms 0x28 +#define GP2AP020A00F_RES_A_1_56ms 0x30 +#define GP2AP020A00F_RES_A_0_39ms 0x38 +#define GP2AP020A00F_RANGE_A_MASK 0x07 /* ALS: Max measurable range */ +#define GP2AP020A00F_RANGE_A_x1 0x00 +#define GP2AP020A00F_RANGE_A_x2 0x01 +#define GP2AP020A00F_RANGE_A_x4 0x02 +#define GP2AP020A00F_RANGE_A_x8 0x03 +#define GP2AP020A00F_RANGE_A_x16 0x04 +#define GP2AP020A00F_RANGE_A_x32 0x05 +#define GP2AP020A00F_RANGE_A_x64 0x06 +#define GP2AP020A00F_RANGE_A_x128 0x07 + +/* PS_REG bits */ +#define GP2AP020A00F_ALC_MASK 0x80 /* Auto light cancel */ +#define GP2AP020A00F_ALC_ON 0x80 +#define GP2AP020A00F_ALC_OFF 0x00 +#define GP2AP020A00F_INTTYPE_MASK 0x40 /* Interrupt type setting */ +#define GP2AP020A00F_INTTYPE_LEVEL 0x00 +#define GP2AP020A00F_INTTYPE_PULSE 0x40 +#define GP2AP020A00F_RES_P_MASK 0x38 /* PS: Resolution */ +#define GP2AP020A00F_RES_P_800ms_x2 0x00 +#define GP2AP020A00F_RES_P_400ms_x2 0x08 +#define GP2AP020A00F_RES_P_200ms_x2 0x10 +#define GP2AP020A00F_RES_P_100ms_x2 0x18 +#define GP2AP020A00F_RES_P_25ms_x2 0x20 +#define GP2AP020A00F_RES_P_6_25ms_x2 0x28 +#define GP2AP020A00F_RES_P_1_56ms_x2 0x30 +#define GP2AP020A00F_RES_P_0_39ms_x2 0x38 +#define GP2AP020A00F_RANGE_P_MASK 0x07 /* PS: Max measurable range */ +#define GP2AP020A00F_RANGE_P_x1 0x00 +#define GP2AP020A00F_RANGE_P_x2 0x01 +#define GP2AP020A00F_RANGE_P_x4 0x02 +#define GP2AP020A00F_RANGE_P_x8 0x03 +#define GP2AP020A00F_RANGE_P_x16 0x04 +#define GP2AP020A00F_RANGE_P_x32 0x05 +#define GP2AP020A00F_RANGE_P_x64 0x06 +#define GP2AP020A00F_RANGE_P_x128 0x07 + +/* LED reg bits */ +#define GP2AP020A00F_INTVAL_MASK 0xc0 /* Intermittent operating */ +#define GP2AP020A00F_INTVAL_0 0x00 +#define GP2AP020A00F_INTVAL_4 0x40 +#define GP2AP020A00F_INTVAL_8 0x80 +#define GP2AP020A00F_INTVAL_16 0xc0 +#define GP2AP020A00F_IS_MASK 0x30 /* ILED drive peak current */ +#define GP2AP020A00F_IS_13_8mA 0x00 +#define GP2AP020A00F_IS_27_5mA 0x10 +#define GP2AP020A00F_IS_55mA 0x20 +#define GP2AP020A00F_IS_110mA 0x30 +#define GP2AP020A00F_PIN_MASK 0x0c /* INT terminal setting */ +#define GP2AP020A00F_PIN_ALS_OR_PS 0x00 +#define GP2AP020A00F_PIN_ALS 0x04 +#define GP2AP020A00F_PIN_PS 0x08 +#define GP2AP020A00F_PIN_PS_DETECT 0x0c +#define GP2AP020A00F_FREQ_MASK 0x02 /* LED modulation frequency */ +#define GP2AP020A00F_FREQ_327_5kHz 0x00 +#define GP2AP020A00F_FREQ_81_8kHz 0x02 +#define GP2AP020A00F_RST 0x01 /* Software reset */ + +#define GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR 0 +#define GP2AP020A00F_SCAN_MODE_LIGHT_IR 1 +#define GP2AP020A00F_SCAN_MODE_PROXIMITY 2 +#define GP2AP020A00F_CHAN_TIMESTAMP 3 + +#define GP2AP020A00F_DATA_READY_TIMEOUT msecs_to_jiffies(1000) +#define GP2AP020A00F_DATA_REG(chan) (GP2AP020A00F_D0_L_REG + (chan) * 2) +#define GP2AP020A00F_THRESH_REG(th_val_id) (GP2AP020A00F_TL_L_REG + (th_val_id) * 2) +#define GP2AP020A00F_THRESH_VAL_ID(reg_addr) ((reg_addr - 4) / 2) + +#define GP2AP020A00F_SUBTRACT_MODE 0 +#define GP2AP020A00F_ADD_MODE 1 + +#define GP2AP020A00F_MAX_CHANNELS 3 + +enum gp2ap020a00f_opmode { + GP2AP020A00F_OPMODE_READ_RAW_CLEAR, + GP2AP020A00F_OPMODE_READ_RAW_IR, + GP2AP020A00F_OPMODE_READ_RAW_PROXIMITY, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_OPMODE_PS, + GP2AP020A00F_OPMODE_ALS_AND_PS, + GP2AP020A00F_OPMODE_PROX_DETECT, + GP2AP020A00F_OPMODE_SHUTDOWN, + GP2AP020A00F_NUM_OPMODES +}; + +enum gp2ap020a00f_cmd { + GP2AP020A00F_CMD_READ_RAW_CLEAR, + GP2AP020A00F_CMD_READ_RAW_IR, + GP2AP020A00F_CMD_READ_RAW_PROXIMITY, + GP2AP020A00F_CMD_TRIGGER_CLEAR_EN, + GP2AP020A00F_CMD_TRIGGER_CLEAR_DIS, + GP2AP020A00F_CMD_TRIGGER_IR_EN, + GP2AP020A00F_CMD_TRIGGER_IR_DIS, + GP2AP020A00F_CMD_TRIGGER_PROX_EN, + GP2AP020A00F_CMD_TRIGGER_PROX_DIS, + GP2AP020A00F_CMD_ALS_HIGH_EV_EN, + GP2AP020A00F_CMD_ALS_HIGH_EV_DIS, + GP2AP020A00F_CMD_ALS_LOW_EV_EN, + GP2AP020A00F_CMD_ALS_LOW_EV_DIS, + GP2AP020A00F_CMD_PROX_HIGH_EV_EN, + GP2AP020A00F_CMD_PROX_HIGH_EV_DIS, + GP2AP020A00F_CMD_PROX_LOW_EV_EN, + GP2AP020A00F_CMD_PROX_LOW_EV_DIS, +}; + +enum gp2ap020a00f_flags { + GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER, + GP2AP020A00F_FLAG_ALS_IR_TRIGGER, + GP2AP020A00F_FLAG_PROX_TRIGGER, + GP2AP020A00F_FLAG_PROX_RISING_EV, + GP2AP020A00F_FLAG_PROX_FALLING_EV, + GP2AP020A00F_FLAG_ALS_RISING_EV, + GP2AP020A00F_FLAG_ALS_FALLING_EV, + GP2AP020A00F_FLAG_LUX_MODE_HI, + GP2AP020A00F_FLAG_DATA_READY, +}; + +enum gp2ap020a00f_thresh_val_id { + GP2AP020A00F_THRESH_TL, + GP2AP020A00F_THRESH_TH, + GP2AP020A00F_THRESH_PL, + GP2AP020A00F_THRESH_PH, +}; + +struct gp2ap020a00f_data { + struct i2c_client *client; + struct mutex lock; + char *buffer; + struct regulator *vled_reg; + unsigned long flags; + enum gp2ap020a00f_opmode cur_opmode; + struct iio_trigger *trig; + struct regmap *regmap; + unsigned int thresh_val[4]; + struct irq_work work; + wait_queue_head_t data_ready_queue; +}; + +static const u8 gp2ap020a00f_reg_init_tab[] = { + [GP2AP020A00F_OP_REG] = GP2AP020A00F_OP3_SHUTDOWN, + [GP2AP020A00F_ALS_REG] = GP2AP020A00F_RES_A_25ms | + GP2AP020A00F_RANGE_A_x8, + [GP2AP020A00F_PS_REG] = GP2AP020A00F_ALC_ON | + GP2AP020A00F_RES_P_1_56ms_x2 | + GP2AP020A00F_RANGE_P_x4, + [GP2AP020A00F_LED_REG] = GP2AP020A00F_INTVAL_0 | + GP2AP020A00F_IS_110mA | + GP2AP020A00F_FREQ_327_5kHz, + [GP2AP020A00F_TL_L_REG] = 0, + [GP2AP020A00F_TL_H_REG] = 0, + [GP2AP020A00F_TH_L_REG] = 0, + [GP2AP020A00F_TH_H_REG] = 0, + [GP2AP020A00F_PL_L_REG] = 0, + [GP2AP020A00F_PL_H_REG] = 0, + [GP2AP020A00F_PH_L_REG] = 0, + [GP2AP020A00F_PH_H_REG] = 0, +}; + +static bool gp2ap020a00f_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case GP2AP020A00F_OP_REG: + case GP2AP020A00F_D0_L_REG: + case GP2AP020A00F_D0_H_REG: + case GP2AP020A00F_D1_L_REG: + case GP2AP020A00F_D1_H_REG: + case GP2AP020A00F_D2_L_REG: + case GP2AP020A00F_D2_H_REG: + return true; + default: + return false; + } +} + +static const struct regmap_config gp2ap020a00f_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = GP2AP020A00F_D2_H_REG, + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = gp2ap020a00f_is_volatile_reg, +}; + +static const struct gp2ap020a00f_mutable_config_regs { + u8 op_reg; + u8 als_reg; + u8 ps_reg; + u8 led_reg; +} opmode_regs_settings[GP2AP020A00F_NUM_OPMODES] = { + [GP2AP020A00F_OPMODE_READ_RAW_CLEAR] = { + GP2AP020A00F_OP_ALS | GP2AP020A00F_OP2_CONT_OPERATION + | GP2AP020A00F_OP3_OPERATION + | GP2AP020A00F_TYPE_AUTO_CALC, + GP2AP020A00F_PRST_ONCE, + GP2AP020A00F_INTTYPE_LEVEL, + GP2AP020A00F_PIN_ALS + }, + [GP2AP020A00F_OPMODE_READ_RAW_IR] = { + GP2AP020A00F_OP_ALS | GP2AP020A00F_OP2_CONT_OPERATION + | GP2AP020A00F_OP3_OPERATION + | GP2AP020A00F_TYPE_MANUAL_CALC, + GP2AP020A00F_PRST_ONCE, + GP2AP020A00F_INTTYPE_LEVEL, + GP2AP020A00F_PIN_ALS + }, + [GP2AP020A00F_OPMODE_READ_RAW_PROXIMITY] = { + GP2AP020A00F_OP_PS | GP2AP020A00F_OP2_CONT_OPERATION + | GP2AP020A00F_OP3_OPERATION + | GP2AP020A00F_TYPE_MANUAL_CALC, + GP2AP020A00F_PRST_ONCE, + GP2AP020A00F_INTTYPE_LEVEL, + GP2AP020A00F_PIN_PS + }, + [GP2AP020A00F_OPMODE_PROX_DETECT] = { + GP2AP020A00F_OP_PS | GP2AP020A00F_OP2_CONT_OPERATION + | GP2AP020A00F_OP3_OPERATION + | GP2AP020A00F_TYPE_MANUAL_CALC, + GP2AP020A00F_PRST_4_CYCLES, + GP2AP020A00F_INTTYPE_PULSE, + GP2AP020A00F_PIN_PS_DETECT + }, + [GP2AP020A00F_OPMODE_ALS] = { + GP2AP020A00F_OP_ALS | GP2AP020A00F_OP2_CONT_OPERATION + | GP2AP020A00F_OP3_OPERATION + | GP2AP020A00F_TYPE_AUTO_CALC, + GP2AP020A00F_PRST_ONCE, + GP2AP020A00F_INTTYPE_LEVEL, + GP2AP020A00F_PIN_ALS + }, + [GP2AP020A00F_OPMODE_PS] = { + GP2AP020A00F_OP_PS | GP2AP020A00F_OP2_CONT_OPERATION + | GP2AP020A00F_OP3_OPERATION + | GP2AP020A00F_TYPE_MANUAL_CALC, + GP2AP020A00F_PRST_4_CYCLES, + GP2AP020A00F_INTTYPE_LEVEL, + GP2AP020A00F_PIN_PS + }, + [GP2AP020A00F_OPMODE_ALS_AND_PS] = { + GP2AP020A00F_OP_ALS_AND_PS + | GP2AP020A00F_OP2_CONT_OPERATION + | GP2AP020A00F_OP3_OPERATION + | GP2AP020A00F_TYPE_AUTO_CALC, + GP2AP020A00F_PRST_4_CYCLES, + GP2AP020A00F_INTTYPE_LEVEL, + GP2AP020A00F_PIN_ALS_OR_PS + }, + [GP2AP020A00F_OPMODE_SHUTDOWN] = { GP2AP020A00F_OP3_SHUTDOWN, }, +}; + +static int gp2ap020a00f_set_operation_mode(struct gp2ap020a00f_data *data, + enum gp2ap020a00f_opmode op) +{ + unsigned int op_reg_val; + int err; + + if (op != GP2AP020A00F_OPMODE_SHUTDOWN) { + err = regmap_read(data->regmap, GP2AP020A00F_OP_REG, + &op_reg_val); + if (err < 0) + return err; + /* + * Shutdown the device if the operation being executed entails + * mode transition. + */ + if ((opmode_regs_settings[op].op_reg & GP2AP020A00F_OP_MASK) != + (op_reg_val & GP2AP020A00F_OP_MASK)) { + /* set shutdown mode */ + err = regmap_update_bits(data->regmap, + GP2AP020A00F_OP_REG, GP2AP020A00F_OP3_MASK, + GP2AP020A00F_OP3_SHUTDOWN); + if (err < 0) + return err; + } + + err = regmap_update_bits(data->regmap, GP2AP020A00F_ALS_REG, + GP2AP020A00F_PRST_MASK, opmode_regs_settings[op].als_reg); + if (err < 0) + return err; + + err = regmap_update_bits(data->regmap, GP2AP020A00F_PS_REG, + GP2AP020A00F_INTTYPE_MASK, opmode_regs_settings[op].ps_reg); + if (err < 0) + return err; + + err = regmap_update_bits(data->regmap, GP2AP020A00F_LED_REG, + GP2AP020A00F_PIN_MASK, opmode_regs_settings[op].led_reg); + if (err < 0) + return err; + } + + /* Set OP_REG and apply operation mode (power on / off) */ + err = regmap_update_bits(data->regmap, + GP2AP020A00F_OP_REG, + GP2AP020A00F_OP_MASK | GP2AP020A00F_OP2_MASK | + GP2AP020A00F_OP3_MASK | GP2AP020A00F_TYPE_MASK, + opmode_regs_settings[op].op_reg); + if (err < 0) + return err; + + data->cur_opmode = op; + + return 0; +} + +static bool gp2ap020a00f_als_enabled(struct gp2ap020a00f_data *data) +{ + return test_bit(GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER, &data->flags) || + test_bit(GP2AP020A00F_FLAG_ALS_IR_TRIGGER, &data->flags) || + test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags) || + test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags); +} + +static bool gp2ap020a00f_prox_detect_enabled(struct gp2ap020a00f_data *data) +{ + return test_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags) || + test_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags); +} + +static int gp2ap020a00f_write_event_threshold(struct gp2ap020a00f_data *data, + enum gp2ap020a00f_thresh_val_id th_val_id, + bool enable) +{ + __le16 thresh_buf = 0; + unsigned int thresh_reg_val; + + if (!enable) + thresh_reg_val = 0; + else if (test_bit(GP2AP020A00F_FLAG_LUX_MODE_HI, &data->flags) && + th_val_id != GP2AP020A00F_THRESH_PL && + th_val_id != GP2AP020A00F_THRESH_PH) + /* + * For the high lux mode ALS threshold has to be scaled down + * to allow for proper comparison with the output value. + */ + thresh_reg_val = data->thresh_val[th_val_id] / 16; + else + thresh_reg_val = min(data->thresh_val[th_val_id], 16000U); + + thresh_buf = cpu_to_le16(thresh_reg_val); + + return regmap_bulk_write(data->regmap, + GP2AP020A00F_THRESH_REG(th_val_id), + &thresh_buf, sizeof(thresh_buf)); +} + +static int gp2ap020a00f_alter_opmode(struct gp2ap020a00f_data *data, + enum gp2ap020a00f_opmode diff_mode, int add_sub) +{ + enum gp2ap020a00f_opmode new_mode; + + if (diff_mode != GP2AP020A00F_OPMODE_ALS && + diff_mode != GP2AP020A00F_OPMODE_PS) + return -EINVAL; + + if (add_sub == GP2AP020A00F_ADD_MODE) { + if (data->cur_opmode == GP2AP020A00F_OPMODE_SHUTDOWN) + new_mode = diff_mode; + else + new_mode = GP2AP020A00F_OPMODE_ALS_AND_PS; + } else { + if (data->cur_opmode == GP2AP020A00F_OPMODE_ALS_AND_PS) + new_mode = (diff_mode == GP2AP020A00F_OPMODE_ALS) ? + GP2AP020A00F_OPMODE_PS : + GP2AP020A00F_OPMODE_ALS; + else + new_mode = GP2AP020A00F_OPMODE_SHUTDOWN; + } + + return gp2ap020a00f_set_operation_mode(data, new_mode); +} + +static int gp2ap020a00f_exec_cmd(struct gp2ap020a00f_data *data, + enum gp2ap020a00f_cmd cmd) +{ + int err; + + switch (cmd) { + case GP2AP020A00F_CMD_READ_RAW_CLEAR: + if (data->cur_opmode != GP2AP020A00F_OPMODE_SHUTDOWN) + return -EBUSY; + return gp2ap020a00f_set_operation_mode(data, + GP2AP020A00F_OPMODE_READ_RAW_CLEAR); + case GP2AP020A00F_CMD_READ_RAW_IR: + if (data->cur_opmode != GP2AP020A00F_OPMODE_SHUTDOWN) + return -EBUSY; + return gp2ap020a00f_set_operation_mode(data, + GP2AP020A00F_OPMODE_READ_RAW_IR); + case GP2AP020A00F_CMD_READ_RAW_PROXIMITY: + if (data->cur_opmode != GP2AP020A00F_OPMODE_SHUTDOWN) + return -EBUSY; + return gp2ap020a00f_set_operation_mode(data, + GP2AP020A00F_OPMODE_READ_RAW_PROXIMITY); + case GP2AP020A00F_CMD_TRIGGER_CLEAR_EN: + if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT) + return -EBUSY; + if (!gp2ap020a00f_als_enabled(data)) + err = gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_ADD_MODE); + else + err = 0; + set_bit(GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER, &data->flags); + return err; + case GP2AP020A00F_CMD_TRIGGER_CLEAR_DIS: + clear_bit(GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER, &data->flags); + if (gp2ap020a00f_als_enabled(data)) + break; + return gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_SUBTRACT_MODE); + case GP2AP020A00F_CMD_TRIGGER_IR_EN: + if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT) + return -EBUSY; + if (!gp2ap020a00f_als_enabled(data)) + err = gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_ADD_MODE); + else + err = 0; + set_bit(GP2AP020A00F_FLAG_ALS_IR_TRIGGER, &data->flags); + return err; + case GP2AP020A00F_CMD_TRIGGER_IR_DIS: + clear_bit(GP2AP020A00F_FLAG_ALS_IR_TRIGGER, &data->flags); + if (gp2ap020a00f_als_enabled(data)) + break; + return gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_SUBTRACT_MODE); + case GP2AP020A00F_CMD_TRIGGER_PROX_EN: + if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT) + return -EBUSY; + err = gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_PS, + GP2AP020A00F_ADD_MODE); + set_bit(GP2AP020A00F_FLAG_PROX_TRIGGER, &data->flags); + return err; + case GP2AP020A00F_CMD_TRIGGER_PROX_DIS: + clear_bit(GP2AP020A00F_FLAG_PROX_TRIGGER, &data->flags); + return gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_PS, + GP2AP020A00F_SUBTRACT_MODE); + case GP2AP020A00F_CMD_ALS_HIGH_EV_EN: + if (test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags)) + return 0; + if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT) + return -EBUSY; + if (!gp2ap020a00f_als_enabled(data)) { + err = gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_ADD_MODE); + if (err < 0) + return err; + } + set_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags); + return gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_TH, true); + case GP2AP020A00F_CMD_ALS_HIGH_EV_DIS: + if (!test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags)) + return 0; + clear_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags); + if (!gp2ap020a00f_als_enabled(data)) { + err = gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_SUBTRACT_MODE); + if (err < 0) + return err; + } + return gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_TH, false); + case GP2AP020A00F_CMD_ALS_LOW_EV_EN: + if (test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags)) + return 0; + if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT) + return -EBUSY; + if (!gp2ap020a00f_als_enabled(data)) { + err = gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_ADD_MODE); + if (err < 0) + return err; + } + set_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags); + return gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_TL, true); + case GP2AP020A00F_CMD_ALS_LOW_EV_DIS: + if (!test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags)) + return 0; + clear_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags); + if (!gp2ap020a00f_als_enabled(data)) { + err = gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_SUBTRACT_MODE); + if (err < 0) + return err; + } + return gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_TL, false); + case GP2AP020A00F_CMD_PROX_HIGH_EV_EN: + if (test_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags)) + return 0; + if (gp2ap020a00f_als_enabled(data) || + data->cur_opmode == GP2AP020A00F_OPMODE_PS) + return -EBUSY; + if (!gp2ap020a00f_prox_detect_enabled(data)) { + err = gp2ap020a00f_set_operation_mode(data, + GP2AP020A00F_OPMODE_PROX_DETECT); + if (err < 0) + return err; + } + set_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags); + return gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_PH, true); + case GP2AP020A00F_CMD_PROX_HIGH_EV_DIS: + if (!test_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags)) + return 0; + clear_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags); + err = gp2ap020a00f_set_operation_mode(data, + GP2AP020A00F_OPMODE_SHUTDOWN); + if (err < 0) + return err; + return gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_PH, false); + case GP2AP020A00F_CMD_PROX_LOW_EV_EN: + if (test_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags)) + return 0; + if (gp2ap020a00f_als_enabled(data) || + data->cur_opmode == GP2AP020A00F_OPMODE_PS) + return -EBUSY; + if (!gp2ap020a00f_prox_detect_enabled(data)) { + err = gp2ap020a00f_set_operation_mode(data, + GP2AP020A00F_OPMODE_PROX_DETECT); + if (err < 0) + return err; + } + set_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags); + return gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_PL, true); + case GP2AP020A00F_CMD_PROX_LOW_EV_DIS: + if (!test_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags)) + return 0; + clear_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags); + err = gp2ap020a00f_set_operation_mode(data, + GP2AP020A00F_OPMODE_SHUTDOWN); + if (err < 0) + return err; + return gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_PL, false); + } + + return 0; +} + +static int wait_conversion_complete_irq(struct gp2ap020a00f_data *data) +{ + int ret; + + ret = wait_event_timeout(data->data_ready_queue, + test_bit(GP2AP020A00F_FLAG_DATA_READY, + &data->flags), + GP2AP020A00F_DATA_READY_TIMEOUT); + clear_bit(GP2AP020A00F_FLAG_DATA_READY, &data->flags); + + return ret > 0 ? 0 : -ETIME; +} + +static int gp2ap020a00f_read_output(struct gp2ap020a00f_data *data, + unsigned int output_reg, int *val) +{ + __le16 reg_buf; + int err; + + err = wait_conversion_complete_irq(data); + if (err < 0) + dev_dbg(&data->client->dev, "data ready timeout\n"); + + err = regmap_bulk_read(data->regmap, output_reg, ®_buf, sizeof(reg_buf)); + if (err < 0) + return err; + + *val = le16_to_cpu(reg_buf); + + return err; +} + +static bool gp2ap020a00f_adjust_lux_mode(struct gp2ap020a00f_data *data, + int output_val) +{ + u8 new_range = 0xff; + int err; + + if (!test_bit(GP2AP020A00F_FLAG_LUX_MODE_HI, &data->flags)) { + if (output_val > 16000) { + set_bit(GP2AP020A00F_FLAG_LUX_MODE_HI, &data->flags); + new_range = GP2AP020A00F_RANGE_A_x128; + } + } else { + if (output_val < 1000) { + clear_bit(GP2AP020A00F_FLAG_LUX_MODE_HI, &data->flags); + new_range = GP2AP020A00F_RANGE_A_x8; + } + } + + if (new_range != 0xff) { + /* Clear als threshold registers to avoid spurious + * events caused by lux mode transition. + */ + err = gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_TH, false); + if (err < 0) { + dev_err(&data->client->dev, + "Clearing als threshold register failed.\n"); + return false; + } + + err = gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_TL, false); + if (err < 0) { + dev_err(&data->client->dev, + "Clearing als threshold register failed.\n"); + return false; + } + + /* Change lux mode */ + err = regmap_update_bits(data->regmap, + GP2AP020A00F_OP_REG, + GP2AP020A00F_OP3_MASK, + GP2AP020A00F_OP3_SHUTDOWN); + + if (err < 0) { + dev_err(&data->client->dev, + "Shutting down the device failed.\n"); + return false; + } + + err = regmap_update_bits(data->regmap, + GP2AP020A00F_ALS_REG, + GP2AP020A00F_RANGE_A_MASK, + new_range); + + if (err < 0) { + dev_err(&data->client->dev, + "Adjusting device lux mode failed.\n"); + return false; + } + + err = regmap_update_bits(data->regmap, + GP2AP020A00F_OP_REG, + GP2AP020A00F_OP3_MASK, + GP2AP020A00F_OP3_OPERATION); + + if (err < 0) { + dev_err(&data->client->dev, + "Powering up the device failed.\n"); + return false; + } + + /* Adjust als threshold register values to the new lux mode */ + if (test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags)) { + err = gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_TH, true); + if (err < 0) { + dev_err(&data->client->dev, + "Adjusting als threshold value failed.\n"); + return false; + } + } + + if (test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags)) { + err = gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_TL, true); + if (err < 0) { + dev_err(&data->client->dev, + "Adjusting als threshold value failed.\n"); + return false; + } + } + + return true; + } + + return false; +} + +static void gp2ap020a00f_output_to_lux(struct gp2ap020a00f_data *data, + int *output_val) +{ + if (test_bit(GP2AP020A00F_FLAG_LUX_MODE_HI, &data->flags)) + *output_val *= 16; +} + +static void gp2ap020a00f_iio_trigger_work(struct irq_work *work) +{ + struct gp2ap020a00f_data *data = + container_of(work, struct gp2ap020a00f_data, work); + + iio_trigger_poll(data->trig); +} + +static irqreturn_t gp2ap020a00f_prox_sensing_handler(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct gp2ap020a00f_data *priv = iio_priv(indio_dev); + unsigned int op_reg_val; + int ret; + + /* Read interrupt flags */ + ret = regmap_read(priv->regmap, GP2AP020A00F_OP_REG, &op_reg_val); + if (ret < 0) + return IRQ_HANDLED; + + if (gp2ap020a00f_prox_detect_enabled(priv)) { + if (op_reg_val & GP2AP020A00F_PROX_DETECT) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE( + IIO_PROXIMITY, + GP2AP020A00F_SCAN_MODE_PROXIMITY, + IIO_EV_TYPE_ROC, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + } else { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE( + IIO_PROXIMITY, + GP2AP020A00F_SCAN_MODE_PROXIMITY, + IIO_EV_TYPE_ROC, + IIO_EV_DIR_FALLING), + iio_get_time_ns(indio_dev)); + } + } + + return IRQ_HANDLED; +} + +static irqreturn_t gp2ap020a00f_thresh_event_handler(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct gp2ap020a00f_data *priv = iio_priv(indio_dev); + unsigned int output_val, op_reg_val; + __le16 d0_reg_buf; + u8 op_reg_flags; + int thresh_val_id, ret; + + /* Read interrupt flags */ + ret = regmap_read(priv->regmap, GP2AP020A00F_OP_REG, &op_reg_val); + if (ret < 0) + goto done; + + op_reg_flags = op_reg_val & (GP2AP020A00F_FLAG_A | GP2AP020A00F_FLAG_P + | GP2AP020A00F_PROX_DETECT); + + op_reg_val &= (~GP2AP020A00F_FLAG_A & ~GP2AP020A00F_FLAG_P + & ~GP2AP020A00F_PROX_DETECT); + + /* Clear interrupt flags (if not in INTTYPE_PULSE mode) */ + if (priv->cur_opmode != GP2AP020A00F_OPMODE_PROX_DETECT) { + ret = regmap_write(priv->regmap, GP2AP020A00F_OP_REG, op_reg_val); + if (ret < 0) + goto done; + } + + if (op_reg_flags & GP2AP020A00F_FLAG_A) { + /* Check D0 register to assess if the lux mode + * transition is required. + */ + ret = regmap_bulk_read(priv->regmap, GP2AP020A00F_D0_L_REG, + &d0_reg_buf, sizeof(d0_reg_buf)); + if (ret < 0) + goto done; + + output_val = le16_to_cpu(d0_reg_buf); + + if (gp2ap020a00f_adjust_lux_mode(priv, output_val)) + goto done; + + gp2ap020a00f_output_to_lux(priv, &output_val); + + /* + * We need to check output value to distinguish + * between high and low ambient light threshold event. + */ + if (test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &priv->flags)) { + thresh_val_id = + GP2AP020A00F_THRESH_VAL_ID(GP2AP020A00F_TH_L_REG); + if (output_val > priv->thresh_val[thresh_val_id]) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE( + IIO_LIGHT, + GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR, + IIO_MOD_LIGHT_CLEAR, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + } + + if (test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &priv->flags)) { + thresh_val_id = + GP2AP020A00F_THRESH_VAL_ID(GP2AP020A00F_TL_L_REG); + if (output_val < priv->thresh_val[thresh_val_id]) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE( + IIO_LIGHT, + GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR, + IIO_MOD_LIGHT_CLEAR, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns(indio_dev)); + } + } + + if (priv->cur_opmode == GP2AP020A00F_OPMODE_READ_RAW_CLEAR || + priv->cur_opmode == GP2AP020A00F_OPMODE_READ_RAW_IR || + priv->cur_opmode == GP2AP020A00F_OPMODE_READ_RAW_PROXIMITY) { + set_bit(GP2AP020A00F_FLAG_DATA_READY, &priv->flags); + wake_up(&priv->data_ready_queue); + goto done; + } + + if (test_bit(GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER, &priv->flags) || + test_bit(GP2AP020A00F_FLAG_ALS_IR_TRIGGER, &priv->flags) || + test_bit(GP2AP020A00F_FLAG_PROX_TRIGGER, &priv->flags)) + /* This fires off the trigger. */ + irq_work_queue(&priv->work); + +done: + return IRQ_HANDLED; +} + +static irqreturn_t gp2ap020a00f_trigger_handler(int irq, void *data) +{ + struct iio_poll_func *pf = data; + struct iio_dev *indio_dev = pf->indio_dev; + struct gp2ap020a00f_data *priv = iio_priv(indio_dev); + size_t d_size = 0; + int i, out_val, ret; + + iio_for_each_active_channel(indio_dev, i) { + ret = regmap_bulk_read(priv->regmap, GP2AP020A00F_DATA_REG(i), + &priv->buffer[d_size], 2); + if (ret < 0) + goto done; + + if (i == GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR || + i == GP2AP020A00F_SCAN_MODE_LIGHT_IR) { + out_val = get_unaligned_le16(&priv->buffer[d_size]); + gp2ap020a00f_output_to_lux(priv, &out_val); + put_unaligned_le32(out_val, &priv->buffer[d_size]); + d_size += 4; + } else { + d_size += 2; + } + } + + iio_push_to_buffers_with_timestamp(indio_dev, priv->buffer, pf->timestamp); +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int gp2ap020a00f_get_thresh_reg(const struct iio_chan_spec *chan, + enum iio_event_direction event_dir) +{ + switch (chan->type) { + case IIO_PROXIMITY: + if (event_dir == IIO_EV_DIR_RISING) + return GP2AP020A00F_PH_L_REG; + else + return GP2AP020A00F_PL_L_REG; + case IIO_LIGHT: + if (event_dir == IIO_EV_DIR_RISING) + return GP2AP020A00F_TH_L_REG; + else + return GP2AP020A00F_TL_L_REG; + default: + return -EINVAL; + } +} + +static int gp2ap020a00f_write_event_val(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct gp2ap020a00f_data *data = iio_priv(indio_dev); + bool event_en = false; + u8 thresh_val_id; + int thresh_reg_l; + + guard(mutex)(&data->lock); + + thresh_reg_l = gp2ap020a00f_get_thresh_reg(chan, dir); + if (thresh_reg_l < 0) + return thresh_reg_l; + + thresh_val_id = GP2AP020A00F_THRESH_VAL_ID(thresh_reg_l); + if (thresh_val_id > GP2AP020A00F_THRESH_PH) + return -EINVAL; + + switch (thresh_reg_l) { + case GP2AP020A00F_TH_L_REG: + event_en = test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags); + break; + case GP2AP020A00F_TL_L_REG: + event_en = test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags); + break; + case GP2AP020A00F_PH_L_REG: + if (val == 0) + return -EINVAL; + + event_en = test_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags); + break; + case GP2AP020A00F_PL_L_REG: + if (val == 0) + return -EINVAL; + + event_en = test_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags); + break; + } + + data->thresh_val[thresh_val_id] = val; + return gp2ap020a00f_write_event_threshold(data, thresh_val_id, event_en); +} + +static int gp2ap020a00f_read_event_val(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct gp2ap020a00f_data *data = iio_priv(indio_dev); + int thresh_reg_l; + + guard(mutex)(&data->lock); + + thresh_reg_l = gp2ap020a00f_get_thresh_reg(chan, dir); + if (thresh_reg_l < 0) + return thresh_reg_l; + if (thresh_reg_l > GP2AP020A00F_PH_L_REG) + return -EINVAL; + + *val = data->thresh_val[GP2AP020A00F_THRESH_VAL_ID(thresh_reg_l)]; + + return IIO_VAL_INT; +} + +static int gp2ap020a00f_write_prox_event_config(struct iio_dev *indio_dev, + int state) +{ + struct gp2ap020a00f_data *data = iio_priv(indio_dev); + enum gp2ap020a00f_cmd cmd_high_ev, cmd_low_ev; + int err; + + cmd_high_ev = state ? GP2AP020A00F_CMD_PROX_HIGH_EV_EN : + GP2AP020A00F_CMD_PROX_HIGH_EV_DIS; + cmd_low_ev = state ? GP2AP020A00F_CMD_PROX_LOW_EV_EN : + GP2AP020A00F_CMD_PROX_LOW_EV_DIS; + + /* + * In order to enable proximity detection feature in the device + * both high and low threshold registers have to be written + * with different values, greater than zero. + */ + if (state) { + if (data->thresh_val[GP2AP020A00F_THRESH_PL] == 0) + return -EINVAL; + + if (data->thresh_val[GP2AP020A00F_THRESH_PH] == 0) + return -EINVAL; + } + + err = gp2ap020a00f_exec_cmd(data, cmd_high_ev); + if (err < 0) + return err; + + err = gp2ap020a00f_exec_cmd(data, cmd_low_ev); + if (err < 0) + return err; + + free_irq(data->client->irq, indio_dev); + + if (state) + err = request_threaded_irq(data->client->irq, NULL, + &gp2ap020a00f_prox_sensing_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "gp2ap020a00f_prox_sensing", + indio_dev); + else { + err = request_threaded_irq(data->client->irq, NULL, + &gp2ap020a00f_thresh_event_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "gp2ap020a00f_thresh_event", + indio_dev); + } + + return err; +} + +static int gp2ap020a00f_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct gp2ap020a00f_data *data = iio_priv(indio_dev); + enum gp2ap020a00f_cmd cmd; + + guard(mutex)(&data->lock); + + switch (chan->type) { + case IIO_PROXIMITY: + return gp2ap020a00f_write_prox_event_config(indio_dev, state); + case IIO_LIGHT: + if (dir == IIO_EV_DIR_RISING) { + cmd = state ? GP2AP020A00F_CMD_ALS_HIGH_EV_EN : + GP2AP020A00F_CMD_ALS_HIGH_EV_DIS; + return gp2ap020a00f_exec_cmd(data, cmd); + } else { + cmd = state ? GP2AP020A00F_CMD_ALS_LOW_EV_EN : + GP2AP020A00F_CMD_ALS_LOW_EV_DIS; + return gp2ap020a00f_exec_cmd(data, cmd); + } + default: + return -EINVAL; + } +} + +static int gp2ap020a00f_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct gp2ap020a00f_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + + switch (chan->type) { + case IIO_PROXIMITY: + if (dir == IIO_EV_DIR_RISING) + return test_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags); + else + return test_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags); + case IIO_LIGHT: + if (dir == IIO_EV_DIR_RISING) + return test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags); + else + return test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags); + default: + return -EINVAL; + } +} + +static int gp2ap020a00f_read_channel(struct gp2ap020a00f_data *data, + struct iio_chan_spec const *chan, int *val) +{ + struct device *dev = &data->client->dev; + enum gp2ap020a00f_cmd cmd; + int err; + + switch (chan->scan_index) { + case GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR: + cmd = GP2AP020A00F_CMD_READ_RAW_CLEAR; + break; + case GP2AP020A00F_SCAN_MODE_LIGHT_IR: + cmd = GP2AP020A00F_CMD_READ_RAW_IR; + break; + case GP2AP020A00F_SCAN_MODE_PROXIMITY: + cmd = GP2AP020A00F_CMD_READ_RAW_PROXIMITY; + break; + default: + return -EINVAL; + } + + err = gp2ap020a00f_exec_cmd(data, cmd); + if (err < 0) { + dev_err(dev, "gp2ap020a00f_exec_cmd failed\n"); + return err; + } + + err = gp2ap020a00f_read_output(data, chan->address, val); + if (err < 0) + dev_err(dev, "gp2ap020a00f_read_output failed\n"); + + err = gp2ap020a00f_set_operation_mode(data, + GP2AP020A00F_OPMODE_SHUTDOWN); + if (err < 0) + dev_err(dev, "Failed to shut down the device.\n"); + + if (cmd == GP2AP020A00F_CMD_READ_RAW_CLEAR || + cmd == GP2AP020A00F_CMD_READ_RAW_IR) + gp2ap020a00f_output_to_lux(data, val); + + return err; +} + +static int gp2ap020a00f_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct gp2ap020a00f_data *data = iio_priv(indio_dev); + int err = -EINVAL; + + if (mask == IIO_CHAN_INFO_RAW) { + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + err = gp2ap020a00f_read_channel(data, chan, val); + iio_device_release_direct(indio_dev); + } + return err < 0 ? err : IIO_VAL_INT; +} + +static const struct iio_event_spec gp2ap020a00f_event_spec_light[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_event_spec gp2ap020a00f_event_spec_prox[] = { + { + .type = IIO_EV_TYPE_ROC, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_ROC, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec gp2ap020a00f_channels[] = { + { + .type = IIO_LIGHT, + .channel2 = IIO_MOD_LIGHT_CLEAR, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_type = { + .sign = 'u', + .realbits = 24, + .shift = 0, + .storagebits = 32, + .endianness = IIO_LE, + }, + .scan_index = GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR, + .address = GP2AP020A00F_D0_L_REG, + .event_spec = gp2ap020a00f_event_spec_light, + .num_event_specs = ARRAY_SIZE(gp2ap020a00f_event_spec_light), + }, + { + .type = IIO_LIGHT, + .channel2 = IIO_MOD_LIGHT_IR, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_type = { + .sign = 'u', + .realbits = 24, + .shift = 0, + .storagebits = 32, + .endianness = IIO_LE, + }, + .scan_index = GP2AP020A00F_SCAN_MODE_LIGHT_IR, + .address = GP2AP020A00F_D1_L_REG, + }, + { + .type = IIO_PROXIMITY, + .modified = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_type = { + .sign = 'u', + .realbits = 16, + .shift = 0, + .storagebits = 16, + .endianness = IIO_LE, + }, + .scan_index = GP2AP020A00F_SCAN_MODE_PROXIMITY, + .address = GP2AP020A00F_D2_L_REG, + .event_spec = gp2ap020a00f_event_spec_prox, + .num_event_specs = ARRAY_SIZE(gp2ap020a00f_event_spec_prox), + }, + IIO_CHAN_SOFT_TIMESTAMP(GP2AP020A00F_CHAN_TIMESTAMP), +}; + +static const struct iio_info gp2ap020a00f_info = { + .read_raw = &gp2ap020a00f_read_raw, + .read_event_value = &gp2ap020a00f_read_event_val, + .read_event_config = &gp2ap020a00f_read_event_config, + .write_event_value = &gp2ap020a00f_write_event_val, + .write_event_config = &gp2ap020a00f_write_event_config, +}; + +static int gp2ap020a00f_buffer_postenable(struct iio_dev *indio_dev) +{ + struct gp2ap020a00f_data *data = iio_priv(indio_dev); + int i, err; + + guard(mutex)(&data->lock); + + /* + * Enable triggers according to the scan_mask. Enabling either + * LIGHT_CLEAR or LIGHT_IR scan mode results in enabling ALS + * module in the device, which generates samples in both D0 (clear) + * and D1 (ir) registers. As the two registers are bound to the + * two separate IIO channels they are treated in the driver logic + * as if they were controlled independently. + */ + iio_for_each_active_channel(indio_dev, i) { + switch (i) { + case GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR: + err = gp2ap020a00f_exec_cmd(data, + GP2AP020A00F_CMD_TRIGGER_CLEAR_EN); + break; + case GP2AP020A00F_SCAN_MODE_LIGHT_IR: + err = gp2ap020a00f_exec_cmd(data, + GP2AP020A00F_CMD_TRIGGER_IR_EN); + break; + case GP2AP020A00F_SCAN_MODE_PROXIMITY: + err = gp2ap020a00f_exec_cmd(data, + GP2AP020A00F_CMD_TRIGGER_PROX_EN); + break; + default: + err = -EINVAL; + break; + } + if (err) + return err; + } + + data->buffer = kmalloc(indio_dev->scan_bytes, GFP_KERNEL); + if (!data->buffer) + return -ENOMEM; + + return 0; +} + +static int gp2ap020a00f_buffer_predisable(struct iio_dev *indio_dev) +{ + struct gp2ap020a00f_data *data = iio_priv(indio_dev); + int i, err; + + guard(mutex)(&data->lock); + + iio_for_each_active_channel(indio_dev, i) { + switch (i) { + case GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR: + err = gp2ap020a00f_exec_cmd(data, + GP2AP020A00F_CMD_TRIGGER_CLEAR_DIS); + break; + case GP2AP020A00F_SCAN_MODE_LIGHT_IR: + err = gp2ap020a00f_exec_cmd(data, + GP2AP020A00F_CMD_TRIGGER_IR_DIS); + break; + case GP2AP020A00F_SCAN_MODE_PROXIMITY: + err = gp2ap020a00f_exec_cmd(data, + GP2AP020A00F_CMD_TRIGGER_PROX_DIS); + break; + default: + err = -EINVAL; + break; + } + if (err) + return err; + } + + kfree(data->buffer); + return 0; +} + +static const struct iio_buffer_setup_ops gp2ap020a00f_buffer_setup_ops = { + .postenable = &gp2ap020a00f_buffer_postenable, + .predisable = &gp2ap020a00f_buffer_predisable, +}; + +static int gp2ap020a00f_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct device *dev = &client->dev; + struct gp2ap020a00f_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + int err; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + + data->vled_reg = devm_regulator_get(dev, "vled"); + if (IS_ERR(data->vled_reg)) + return PTR_ERR(data->vled_reg); + + err = regulator_enable(data->vled_reg); + if (err) + return err; + + regmap = devm_regmap_init_i2c(client, &gp2ap020a00f_regmap_config); + if (IS_ERR(regmap)) { + dev_err(dev, "Regmap initialization failed.\n"); + err = PTR_ERR(regmap); + goto error_regulator_disable; + } + + /* Initialize device registers */ + err = regmap_bulk_write(regmap, GP2AP020A00F_OP_REG, + gp2ap020a00f_reg_init_tab, + ARRAY_SIZE(gp2ap020a00f_reg_init_tab)); + + if (err < 0) { + dev_err(dev, "Device initialization failed.\n"); + goto error_regulator_disable; + } + + i2c_set_clientdata(client, indio_dev); + + data->client = client; + data->cur_opmode = GP2AP020A00F_OPMODE_SHUTDOWN; + data->regmap = regmap; + init_waitqueue_head(&data->data_ready_queue); + + mutex_init(&data->lock); + indio_dev->channels = gp2ap020a00f_channels; + indio_dev->num_channels = ARRAY_SIZE(gp2ap020a00f_channels); + indio_dev->info = &gp2ap020a00f_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + + /* Allocate buffer */ + err = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + &gp2ap020a00f_trigger_handler, &gp2ap020a00f_buffer_setup_ops); + if (err < 0) + goto error_regulator_disable; + + /* Allocate trigger */ + data->trig = devm_iio_trigger_alloc(dev, "%s-trigger", indio_dev->name); + if (data->trig == NULL) { + err = -ENOMEM; + dev_err(dev, "Failed to allocate iio trigger.\n"); + goto error_uninit_buffer; + } + + /* This needs to be requested here for read_raw calls to work. */ + err = request_threaded_irq(client->irq, NULL, + &gp2ap020a00f_thresh_event_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "gp2ap020a00f_als_event", + indio_dev); + if (err < 0) { + dev_err(dev, "Irq request failed.\n"); + goto error_uninit_buffer; + } + + init_irq_work(&data->work, gp2ap020a00f_iio_trigger_work); + + err = iio_trigger_register(data->trig); + if (err < 0) { + dev_err(dev, "Failed to register iio trigger.\n"); + goto error_free_irq; + } + + err = iio_device_register(indio_dev); + if (err < 0) + goto error_trigger_unregister; + + return 0; + +error_trigger_unregister: + iio_trigger_unregister(data->trig); +error_free_irq: + free_irq(client->irq, indio_dev); +error_uninit_buffer: + iio_triggered_buffer_cleanup(indio_dev); +error_regulator_disable: + regulator_disable(data->vled_reg); + + return err; +} + +static void gp2ap020a00f_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct gp2ap020a00f_data *data = iio_priv(indio_dev); + struct device *dev = &client->dev; + int err; + + err = gp2ap020a00f_set_operation_mode(data, GP2AP020A00F_OPMODE_SHUTDOWN); + if (err < 0) + dev_err(dev, "Failed to power off the device.\n"); + + iio_device_unregister(indio_dev); + iio_trigger_unregister(data->trig); + free_irq(client->irq, indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + regulator_disable(data->vled_reg); +} + +static const struct i2c_device_id gp2ap020a00f_id[] = { + { .name = "gp2ap020a00f" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, gp2ap020a00f_id); + +static const struct of_device_id gp2ap020a00f_of_match[] = { + { .compatible = "sharp,gp2ap020a00f" }, + { } +}; +MODULE_DEVICE_TABLE(of, gp2ap020a00f_of_match); + +static struct i2c_driver gp2ap020a00f_driver = { + .driver = { + .name = "gp2ap020a00f", + .of_match_table = gp2ap020a00f_of_match, + }, + .probe = gp2ap020a00f_probe, + .remove = gp2ap020a00f_remove, + .id_table = gp2ap020a00f_id, +}; +module_i2c_driver(gp2ap020a00f_driver); + +MODULE_AUTHOR("Jacek Anaszewski "); +MODULE_DESCRIPTION("Sharp GP2AP020A00F Proximity/ALS sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/hid-sensor-als.c b/drivers/iio/light/hid-sensor-als.c new file mode 100644 index 000000000..929343e42 --- /dev/null +++ b/drivers/iio/light/hid-sensor-als.c @@ -0,0 +1,469 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HID Sensors Driver + * Copyright (c) 2012, Intel Corporation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "../common/hid-sensors/hid-sensor-trigger.h" + +enum { + CHANNEL_SCAN_INDEX_INTENSITY, + CHANNEL_SCAN_INDEX_ILLUM, + CHANNEL_SCAN_INDEX_COLOR_TEMP, + CHANNEL_SCAN_INDEX_CHROMATICITY_X, + CHANNEL_SCAN_INDEX_CHROMATICITY_Y, + CHANNEL_SCAN_INDEX_MAX +}; + +#define CHANNEL_SCAN_INDEX_TIMESTAMP CHANNEL_SCAN_INDEX_MAX + +struct als_state { + struct hid_sensor_hub_callbacks callbacks; + struct hid_sensor_common common_attributes; + struct hid_sensor_hub_attribute_info als[CHANNEL_SCAN_INDEX_MAX]; + struct iio_chan_spec channels[CHANNEL_SCAN_INDEX_MAX + 1]; + struct { + u32 illum[CHANNEL_SCAN_INDEX_MAX]; + aligned_s64 timestamp; + } scan; + int scale_pre_decml[CHANNEL_SCAN_INDEX_MAX]; + int scale_post_decml[CHANNEL_SCAN_INDEX_MAX]; + int scale_precision[CHANNEL_SCAN_INDEX_MAX]; + int value_offset; + int num_channels; + s64 timestamp; + unsigned long als_scan_mask[2]; +}; + +/* The order of usage ids must match scan index starting from CHANNEL_SCAN_INDEX_INTENSITY */ +static const u32 als_usage_ids[] = { + HID_USAGE_SENSOR_LIGHT_ILLUM, + HID_USAGE_SENSOR_LIGHT_ILLUM, + HID_USAGE_SENSOR_LIGHT_COLOR_TEMPERATURE, + HID_USAGE_SENSOR_LIGHT_CHROMATICITY_X, + HID_USAGE_SENSOR_LIGHT_CHROMATICITY_Y, +}; + +static const u32 als_sensitivity_addresses[] = { + HID_USAGE_SENSOR_DATA_LIGHT, + HID_USAGE_SENSOR_LIGHT_ILLUM, +}; + +/* Channel definitions */ +static const struct iio_chan_spec als_channels[] = { + { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BOTH, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS) | + BIT(IIO_CHAN_INFO_HYSTERESIS_RELATIVE), + .scan_index = CHANNEL_SCAN_INDEX_INTENSITY, + }, + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS) | + BIT(IIO_CHAN_INFO_HYSTERESIS_RELATIVE), + .scan_index = CHANNEL_SCAN_INDEX_ILLUM, + }, + { + .type = IIO_COLORTEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS) | + BIT(IIO_CHAN_INFO_HYSTERESIS_RELATIVE), + .scan_index = CHANNEL_SCAN_INDEX_COLOR_TEMP, + }, + { + .type = IIO_CHROMATICITY, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS) | + BIT(IIO_CHAN_INFO_HYSTERESIS_RELATIVE), + .scan_index = CHANNEL_SCAN_INDEX_CHROMATICITY_X, + }, + { + .type = IIO_CHROMATICITY, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS) | + BIT(IIO_CHAN_INFO_HYSTERESIS_RELATIVE), + .scan_index = CHANNEL_SCAN_INDEX_CHROMATICITY_Y, + }, + IIO_CHAN_SOFT_TIMESTAMP(CHANNEL_SCAN_INDEX_TIMESTAMP) +}; + +/* Channel read_raw handler */ +static int als_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct als_state *als_state = iio_priv(indio_dev); + struct hid_sensor_hub_device *hsdev = als_state->common_attributes.hsdev; + int report_id = -1; + u32 address; + int ret_type; + s32 min; + + *val = 0; + *val2 = 0; + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->scan_index) { + case CHANNEL_SCAN_INDEX_INTENSITY: + case CHANNEL_SCAN_INDEX_ILLUM: + report_id = als_state->als[chan->scan_index].report_id; + min = als_state->als[chan->scan_index].logical_minimum; + address = HID_USAGE_SENSOR_LIGHT_ILLUM; + break; + case CHANNEL_SCAN_INDEX_COLOR_TEMP: + report_id = als_state->als[chan->scan_index].report_id; + min = als_state->als[chan->scan_index].logical_minimum; + address = HID_USAGE_SENSOR_LIGHT_COLOR_TEMPERATURE; + break; + case CHANNEL_SCAN_INDEX_CHROMATICITY_X: + report_id = als_state->als[chan->scan_index].report_id; + min = als_state->als[chan->scan_index].logical_minimum; + address = HID_USAGE_SENSOR_LIGHT_CHROMATICITY_X; + break; + case CHANNEL_SCAN_INDEX_CHROMATICITY_Y: + report_id = als_state->als[chan->scan_index].report_id; + min = als_state->als[chan->scan_index].logical_minimum; + address = HID_USAGE_SENSOR_LIGHT_CHROMATICITY_Y; + break; + default: + report_id = -1; + break; + } + if (report_id >= 0) { + hid_sensor_power_state(&als_state->common_attributes, + true); + *val = sensor_hub_input_attr_get_raw_value( + hsdev, hsdev->usage, address, report_id, + SENSOR_HUB_SYNC, min < 0); + hid_sensor_power_state(&als_state->common_attributes, + false); + } else { + *val = 0; + return -EINVAL; + } + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + if (chan->scan_index >= CHANNEL_SCAN_INDEX_MAX) + return -EINVAL; + *val = als_state->scale_pre_decml[chan->scan_index]; + *val2 = als_state->scale_post_decml[chan->scan_index]; + ret_type = als_state->scale_precision[chan->scan_index]; + break; + case IIO_CHAN_INFO_OFFSET: + *val = als_state->value_offset; + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret_type = hid_sensor_read_samp_freq_value( + &als_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret_type = hid_sensor_read_raw_hyst_value( + &als_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS_RELATIVE: + ret_type = hid_sensor_read_raw_hyst_rel_value( + &als_state->common_attributes, val, val2); + break; + default: + ret_type = -EINVAL; + break; + } + + return ret_type; +} + +/* Channel write_raw handler */ +static int als_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct als_state *als_state = iio_priv(indio_dev); + int ret = 0; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = hid_sensor_write_samp_freq_value( + &als_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret = hid_sensor_write_raw_hyst_value( + &als_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS_RELATIVE: + ret = hid_sensor_write_raw_hyst_rel_value( + &als_state->common_attributes, val, val2); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info als_info = { + .read_raw = &als_read_raw, + .write_raw = &als_write_raw, +}; + +/* Callback handler to send event after all samples are received and captured */ +static int als_proc_event(struct hid_sensor_hub_device *hsdev, + u32 usage_id, void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct als_state *als_state = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "als_proc_event\n"); + if (atomic_read(&als_state->common_attributes.data_ready)) { + if (!als_state->timestamp) + als_state->timestamp = iio_get_time_ns(indio_dev); + + iio_push_to_buffers_with_ts(indio_dev, &als_state->scan, + sizeof(als_state->scan), + als_state->timestamp); + als_state->timestamp = 0; + } + + return 0; +} + +/* Capture samples in local storage */ +static int als_capture_sample(struct hid_sensor_hub_device *hsdev, + u32 usage_id, + size_t raw_len, char *raw_data, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct als_state *als_state = iio_priv(indio_dev); + int ret = -EINVAL; + u32 sample_data = *(u32 *)raw_data; + + switch (usage_id) { + case HID_USAGE_SENSOR_LIGHT_ILLUM: + als_state->scan.illum[CHANNEL_SCAN_INDEX_INTENSITY] = sample_data; + als_state->scan.illum[CHANNEL_SCAN_INDEX_ILLUM] = sample_data; + ret = 0; + break; + case HID_USAGE_SENSOR_LIGHT_COLOR_TEMPERATURE: + als_state->scan.illum[CHANNEL_SCAN_INDEX_COLOR_TEMP] = sample_data; + ret = 0; + break; + case HID_USAGE_SENSOR_LIGHT_CHROMATICITY_X: + als_state->scan.illum[CHANNEL_SCAN_INDEX_CHROMATICITY_X] = sample_data; + ret = 0; + break; + case HID_USAGE_SENSOR_LIGHT_CHROMATICITY_Y: + als_state->scan.illum[CHANNEL_SCAN_INDEX_CHROMATICITY_Y] = sample_data; + ret = 0; + break; + case HID_USAGE_SENSOR_TIME_TIMESTAMP: + als_state->timestamp = hid_sensor_convert_timestamp(&als_state->common_attributes, + *(s64 *)raw_data); + ret = 0; + break; + default: + break; + } + + return ret; +} + +/* Parse report which is specific to an usage id*/ +static int als_parse_report(struct platform_device *pdev, + struct hid_sensor_hub_device *hsdev, + u32 usage_id, + struct als_state *st) +{ + struct iio_chan_spec *channels; + int ret, index = 0; + int i; + + channels = st->channels; + + for (i = 0; i < CHANNEL_SCAN_INDEX_MAX; ++i) { + ret = sensor_hub_input_get_attribute_info(hsdev, + HID_INPUT_REPORT, + usage_id, + als_usage_ids[i], + &st->als[i]); + if (ret < 0) + continue; + + channels[index] = als_channels[i]; + st->als_scan_mask[0] |= BIT(i); + channels[index].scan_type = (struct iio_scan_type) { + .format = 's', + .realbits = BYTES_TO_BITS(st->als[i].size), + .storagebits = BITS_PER_TYPE(u32), + }; + ++index; + + st->scale_precision[i] = hid_sensor_format_scale(usage_id, + &st->als[i], &st->scale_pre_decml[i], + &st->scale_post_decml[i]); + + dev_dbg(&pdev->dev, "als %x:%x\n", st->als[i].index, + st->als[i].report_id); + } + + st->num_channels = index; + /* Return success even if one usage id is present */ + if (index) + ret = 0; + + return ret; +} + +/* Function to initialize the processing for usage id */ +static int hid_als_probe(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + int ret = 0; + static const char *name = "als"; + struct iio_dev *indio_dev; + struct als_state *als_state; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct als_state)); + if (!indio_dev) + return -ENOMEM; + platform_set_drvdata(pdev, indio_dev); + + als_state = iio_priv(indio_dev); + als_state->common_attributes.hsdev = hsdev; + als_state->common_attributes.pdev = pdev; + + ret = hid_sensor_parse_common_attributes(hsdev, + hsdev->usage, + &als_state->common_attributes, + als_sensitivity_addresses, + ARRAY_SIZE(als_sensitivity_addresses)); + if (ret) { + dev_err(&pdev->dev, "failed to setup common attributes\n"); + return ret; + } + + ret = als_parse_report(pdev, hsdev, + hsdev->usage, + als_state); + if (ret) { + dev_err(&pdev->dev, "failed to setup attributes\n"); + return ret; + } + + /* Add timestamp channel */ + als_state->channels[als_state->num_channels] = als_channels[CHANNEL_SCAN_INDEX_TIMESTAMP]; + + /* +1 for adding timestamp channel */ + indio_dev->num_channels = als_state->num_channels + 1; + + indio_dev->channels = als_state->channels; + indio_dev->available_scan_masks = als_state->als_scan_mask; + + indio_dev->info = &als_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + atomic_set(&als_state->common_attributes.data_ready, 0); + + ret = hid_sensor_setup_trigger(indio_dev, name, + &als_state->common_attributes); + if (ret < 0) { + dev_err(&pdev->dev, "trigger setup failed\n"); + return ret; + } + + als_state->callbacks.send_event = als_proc_event; + als_state->callbacks.capture_sample = als_capture_sample; + als_state->callbacks.pdev = pdev; + ret = sensor_hub_register_callback(hsdev, hsdev->usage, &als_state->callbacks); + if (ret < 0) { + dev_err(&pdev->dev, "callback reg failed\n"); + goto error_remove_trigger; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "device register failed\n"); + goto error_remove_callback; + } + + return ret; + +error_remove_callback: + sensor_hub_remove_callback(hsdev, hsdev->usage); +error_remove_trigger: + hid_sensor_remove_trigger(indio_dev, &als_state->common_attributes); + return ret; +} + +/* Function to deinitialize the processing for usage id */ +static void hid_als_remove(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct als_state *als_state = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + sensor_hub_remove_callback(hsdev, hsdev->usage); + hid_sensor_remove_trigger(indio_dev, &als_state->common_attributes); +} + +static const struct platform_device_id hid_als_ids[] = { + { + /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-200041", + }, + { + /* Format: HID-SENSOR-custom_sensor_tag-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-LISS-0041", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, hid_als_ids); + +static struct platform_driver hid_als_platform_driver = { + .id_table = hid_als_ids, + .driver = { + .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, + }, + .probe = hid_als_probe, + .remove = hid_als_remove, +}; +module_platform_driver(hid_als_platform_driver); + +MODULE_DESCRIPTION("HID Sensor ALS"); +MODULE_AUTHOR("Srinivas Pandruvada "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HID"); diff --git a/drivers/iio/light/hid-sensor-prox.c b/drivers/iio/light/hid-sensor-prox.c new file mode 100644 index 000000000..63fd3eff1 --- /dev/null +++ b/drivers/iio/light/hid-sensor-prox.c @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HID Sensors Driver + * Copyright (c) 2014, Intel Corporation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "../common/hid-sensors/hid-sensor-trigger.h" + +static const u32 prox_usage_ids[] = { + HID_USAGE_SENSOR_HUMAN_PRESENCE, + HID_USAGE_SENSOR_HUMAN_PROXIMITY, + HID_USAGE_SENSOR_HUMAN_ATTENTION, +}; + +#define MAX_CHANNELS ARRAY_SIZE(prox_usage_ids) + +enum { + HID_HUMAN_PRESENCE, + HID_HUMAN_PROXIMITY, + HID_HUMAN_ATTENTION, +}; + +struct prox_state { + struct hid_sensor_hub_callbacks callbacks; + struct hid_sensor_common common_attributes; + struct hid_sensor_hub_attribute_info prox_attr[MAX_CHANNELS]; + struct iio_chan_spec channels[MAX_CHANNELS]; + u32 channel2usage[MAX_CHANNELS]; + u32 human_presence[MAX_CHANNELS]; + int scale_pre_decml[MAX_CHANNELS]; + int scale_post_decml[MAX_CHANNELS]; + int scale_precision[MAX_CHANNELS]; + unsigned long scan_mask[2]; /* One entry plus one terminator. */ + int num_channels; +}; + +static const u32 prox_sensitivity_addresses[] = { + HID_USAGE_SENSOR_HUMAN_PRESENCE, + HID_USAGE_SENSOR_DATA_PRESENCE, +}; + +#define PROX_CHANNEL(_is_proximity, _channel) \ + {\ + .type = _is_proximity ? IIO_PROXIMITY : IIO_ATTENTION,\ + .info_mask_separate = \ + (_is_proximity ? BIT(IIO_CHAN_INFO_RAW) :\ + BIT(IIO_CHAN_INFO_PROCESSED)) |\ + BIT(IIO_CHAN_INFO_OFFSET) |\ + BIT(IIO_CHAN_INFO_SCALE) |\ + BIT(IIO_CHAN_INFO_SAMP_FREQ) |\ + BIT(IIO_CHAN_INFO_HYSTERESIS),\ + .indexed = _is_proximity,\ + .channel = _channel,\ + } + +/* Channel definitions (same order as prox_usage_ids) */ +static const struct iio_chan_spec prox_channels[] = { + PROX_CHANNEL(true, HID_HUMAN_PRESENCE), + PROX_CHANNEL(true, HID_HUMAN_PROXIMITY), + PROX_CHANNEL(false, 0), +}; + +/* Channel read_raw handler */ +static int prox_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct prox_state *prox_state = iio_priv(indio_dev); + struct hid_sensor_hub_device *hsdev; + int report_id; + u32 address; + int ret_type; + s32 min; + + *val = 0; + *val2 = 0; + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + if (chan->scan_index >= prox_state->num_channels) + return -EINVAL; + address = prox_state->channel2usage[chan->scan_index]; + report_id = prox_state->prox_attr[chan->scan_index].report_id; + hsdev = prox_state->common_attributes.hsdev; + min = prox_state->prox_attr[chan->scan_index].logical_minimum; + hid_sensor_power_state(&prox_state->common_attributes, true); + *val = sensor_hub_input_attr_get_raw_value(hsdev, + hsdev->usage, + address, + report_id, + SENSOR_HUB_SYNC, + min < 0); + if (prox_state->channel2usage[chan->scan_index] == + HID_USAGE_SENSOR_HUMAN_ATTENTION) + *val *= 100; + hid_sensor_power_state(&prox_state->common_attributes, false); + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + if (chan->scan_index >= prox_state->num_channels) + return -EINVAL; + + *val = prox_state->scale_pre_decml[chan->scan_index]; + *val2 = prox_state->scale_post_decml[chan->scan_index]; + ret_type = prox_state->scale_precision[chan->scan_index]; + break; + case IIO_CHAN_INFO_OFFSET: + *val = 0; + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret_type = hid_sensor_read_samp_freq_value( + &prox_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret_type = hid_sensor_read_raw_hyst_value( + &prox_state->common_attributes, val, val2); + break; + default: + ret_type = -EINVAL; + break; + } + + return ret_type; +} + +/* Channel write_raw handler */ +static int prox_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct prox_state *prox_state = iio_priv(indio_dev); + int ret = 0; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = hid_sensor_write_samp_freq_value( + &prox_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret = hid_sensor_write_raw_hyst_value( + &prox_state->common_attributes, val, val2); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info prox_info = { + .read_raw = &prox_read_raw, + .write_raw = &prox_write_raw, +}; + +/* Callback handler to send event after all samples are received and captured */ +static int prox_proc_event(struct hid_sensor_hub_device *hsdev, + u32 usage_id, void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct prox_state *prox_state = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "prox_proc_event\n"); + if (atomic_read(&prox_state->common_attributes.data_ready)) { + dev_dbg(&indio_dev->dev, "hid_sensor_push_data\n"); + iio_push_to_buffers(indio_dev, &prox_state->human_presence); + } + + return 0; +} + +/* Capture samples in local storage */ +static int prox_capture_sample(struct hid_sensor_hub_device *hsdev, + u32 usage_id, + size_t raw_len, char *raw_data, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct prox_state *prox_state = iio_priv(indio_dev); + int multiplier = 1; + int chan; + + for (chan = 0; chan < prox_state->num_channels; chan++) + if (prox_state->channel2usage[chan] == usage_id) + break; + if (chan == prox_state->num_channels) + return -EINVAL; + + if (usage_id == HID_USAGE_SENSOR_HUMAN_ATTENTION) + multiplier = 100; + + switch (raw_len) { + case 1: + prox_state->human_presence[chan] = *(u8 *)raw_data * multiplier; + return 0; + case 2: + prox_state->human_presence[chan] = *(u16 *)raw_data * multiplier; + return 0; + case 4: + prox_state->human_presence[chan] = *(u32 *)raw_data * multiplier; + return 0; + } + + return -EINVAL; +} + +/* Parse report which is specific to an usage id*/ +static int prox_parse_report(struct platform_device *pdev, + struct hid_sensor_hub_device *hsdev, + struct prox_state *st) +{ + struct iio_chan_spec *channels = st->channels; + int index = 0; + int ret; + int i; + + for (i = 0; i < MAX_CHANNELS; i++) { + u32 usage_id = prox_usage_ids[i]; + + ret = sensor_hub_input_get_attribute_info(hsdev, + HID_INPUT_REPORT, + hsdev->usage, + usage_id, + &st->prox_attr[index]); + if (ret < 0) + continue; + st->channel2usage[index] = usage_id; + st->scan_mask[0] |= BIT(index); + channels[index] = prox_channels[i]; + channels[index].scan_index = index; + channels[index].scan_type = (struct iio_scan_type) { + .format = 's', + .realbits = BYTES_TO_BITS(st->prox_attr[index].size), + .storagebits = BITS_PER_TYPE(u32), + }; + dev_dbg(&pdev->dev, "prox %x:%x\n", st->prox_attr[index].index, + st->prox_attr[index].report_id); + st->scale_precision[index] = + hid_sensor_format_scale(usage_id, &st->prox_attr[index], + &st->scale_pre_decml[index], + &st->scale_post_decml[index]); + index++; + } + + if (!index) + return ret; + + st->num_channels = index; + + return 0; +} + +/* Function to initialize the processing for usage id */ +static int hid_prox_probe(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + int ret = 0; + static const char *name = "prox"; + struct iio_dev *indio_dev; + struct prox_state *prox_state; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct prox_state)); + if (!indio_dev) + return -ENOMEM; + platform_set_drvdata(pdev, indio_dev); + + prox_state = iio_priv(indio_dev); + prox_state->common_attributes.hsdev = hsdev; + prox_state->common_attributes.pdev = pdev; + + ret = hid_sensor_parse_common_attributes(hsdev, hsdev->usage, + &prox_state->common_attributes, + prox_sensitivity_addresses, + ARRAY_SIZE(prox_sensitivity_addresses)); + if (ret) { + dev_err(&pdev->dev, "failed to setup common attributes\n"); + return ret; + } + + ret = prox_parse_report(pdev, hsdev, prox_state); + if (ret) { + dev_err(&pdev->dev, "failed to setup attributes\n"); + return ret; + } + + indio_dev->num_channels = prox_state->num_channels; + indio_dev->channels = prox_state->channels; + indio_dev->available_scan_masks = prox_state->scan_mask; + indio_dev->info = &prox_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + atomic_set(&prox_state->common_attributes.data_ready, 0); + + ret = hid_sensor_setup_trigger(indio_dev, name, + &prox_state->common_attributes); + if (ret) { + dev_err(&pdev->dev, "trigger setup failed\n"); + return ret; + } + + prox_state->callbacks.send_event = prox_proc_event; + prox_state->callbacks.capture_sample = prox_capture_sample; + prox_state->callbacks.pdev = pdev; + ret = sensor_hub_register_callback(hsdev, hsdev->usage, + &prox_state->callbacks); + if (ret < 0) { + dev_err(&pdev->dev, "callback reg failed\n"); + goto error_remove_trigger; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "device register failed\n"); + goto error_remove_callback; + } + + return ret; + +error_remove_callback: + sensor_hub_remove_callback(hsdev, hsdev->usage); +error_remove_trigger: + hid_sensor_remove_trigger(indio_dev, &prox_state->common_attributes); + return ret; +} + +/* Function to deinitialize the processing for usage id */ +static void hid_prox_remove(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct prox_state *prox_state = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + sensor_hub_remove_callback(hsdev, hsdev->usage); + hid_sensor_remove_trigger(indio_dev, &prox_state->common_attributes); +} + +static const struct platform_device_id hid_prox_ids[] = { + { + /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-200011", + }, + { + /* Format: HID-SENSOR-tag-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-LISS-0226", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, hid_prox_ids); + +static struct platform_driver hid_prox_platform_driver = { + .id_table = hid_prox_ids, + .driver = { + .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, + }, + .probe = hid_prox_probe, + .remove = hid_prox_remove, +}; +module_platform_driver(hid_prox_platform_driver); + +MODULE_DESCRIPTION("HID Sensor Proximity"); +MODULE_AUTHOR("Archana Patni "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HID"); diff --git a/drivers/iio/light/iqs621-als.c b/drivers/iio/light/iqs621-als.c new file mode 100644 index 000000000..cd5843e3e --- /dev/null +++ b/drivers/iio/light/iqs621-als.c @@ -0,0 +1,593 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS621/622 Ambient Light Sensors + * + * Copyright (C) 2019 Jeff LaBundy + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IQS621_ALS_FLAGS_LIGHT BIT(7) +#define IQS621_ALS_FLAGS_RANGE GENMASK(3, 0) + +#define IQS621_ALS_UI_OUT 0x17 + +#define IQS621_ALS_THRESH_DARK 0x80 +#define IQS621_ALS_THRESH_LIGHT 0x81 + +#define IQS622_IR_RANGE 0x15 +#define IQS622_IR_FLAGS 0x16 +#define IQS622_IR_FLAGS_TOUCH BIT(1) +#define IQS622_IR_FLAGS_PROX BIT(0) + +#define IQS622_IR_UI_OUT 0x17 + +#define IQS622_IR_THRESH_PROX 0x91 +#define IQS622_IR_THRESH_TOUCH 0x92 + +struct iqs621_als_private { + struct iqs62x_core *iqs62x; + struct iio_dev *indio_dev; + struct notifier_block notifier; + struct mutex lock; + bool light_en; + bool range_en; + bool prox_en; + u8 als_flags; + u8 ir_flags_mask; + u8 ir_flags; + u8 thresh_light; + u8 thresh_dark; + u8 thresh_prox; +}; + +static int iqs621_als_init(struct iqs621_als_private *iqs621_als) +{ + struct iqs62x_core *iqs62x = iqs621_als->iqs62x; + unsigned int event_mask = 0; + int ret; + + switch (iqs621_als->ir_flags_mask) { + case IQS622_IR_FLAGS_TOUCH: + ret = regmap_write(iqs62x->regmap, IQS622_IR_THRESH_TOUCH, + iqs621_als->thresh_prox); + break; + + case IQS622_IR_FLAGS_PROX: + ret = regmap_write(iqs62x->regmap, IQS622_IR_THRESH_PROX, + iqs621_als->thresh_prox); + break; + + default: + ret = regmap_write(iqs62x->regmap, IQS621_ALS_THRESH_LIGHT, + iqs621_als->thresh_light); + if (ret) + return ret; + + ret = regmap_write(iqs62x->regmap, IQS621_ALS_THRESH_DARK, + iqs621_als->thresh_dark); + } + + if (ret) + return ret; + + if (iqs621_als->light_en || iqs621_als->range_en) + event_mask |= iqs62x->dev_desc->als_mask; + + if (iqs621_als->prox_en) + event_mask |= iqs62x->dev_desc->ir_mask; + + return regmap_clear_bits(iqs62x->regmap, IQS620_GLBL_EVENT_MASK, + event_mask); +} + +static int iqs621_als_notifier(struct notifier_block *notifier, + unsigned long event_flags, void *context) +{ + struct iqs62x_event_data *event_data = context; + struct iqs621_als_private *iqs621_als; + struct iio_dev *indio_dev; + bool light_new, light_old; + bool prox_new, prox_old; + u8 range_new, range_old; + s64 timestamp; + int ret; + + iqs621_als = container_of(notifier, struct iqs621_als_private, + notifier); + indio_dev = iqs621_als->indio_dev; + timestamp = iio_get_time_ns(indio_dev); + + guard(mutex)(&iqs621_als->lock); + + if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) { + ret = iqs621_als_init(iqs621_als); + if (ret) { + dev_err(indio_dev->dev.parent, + "Failed to re-initialize device: %d\n", ret); + return NOTIFY_BAD; + } + return NOTIFY_OK; + } + + if (!iqs621_als->light_en && !iqs621_als->range_en && !iqs621_als->prox_en) + return NOTIFY_DONE; + + /* IQS621 only */ + light_new = event_data->als_flags & IQS621_ALS_FLAGS_LIGHT; + light_old = iqs621_als->als_flags & IQS621_ALS_FLAGS_LIGHT; + + if (iqs621_als->light_en && light_new && !light_old) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + else if (iqs621_als->light_en && !light_new && light_old) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + + /* IQS621 and IQS622 */ + range_new = event_data->als_flags & IQS621_ALS_FLAGS_RANGE; + range_old = iqs621_als->als_flags & IQS621_ALS_FLAGS_RANGE; + + if (iqs621_als->range_en && (range_new > range_old)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0, + IIO_EV_TYPE_CHANGE, + IIO_EV_DIR_RISING), + timestamp); + else if (iqs621_als->range_en && (range_new < range_old)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0, + IIO_EV_TYPE_CHANGE, + IIO_EV_DIR_FALLING), + timestamp); + + /* IQS622 only */ + prox_new = event_data->ir_flags & iqs621_als->ir_flags_mask; + prox_old = iqs621_als->ir_flags & iqs621_als->ir_flags_mask; + + if (iqs621_als->prox_en && prox_new && !prox_old) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + else if (iqs621_als->prox_en && !prox_new && prox_old) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + + iqs621_als->als_flags = event_data->als_flags; + iqs621_als->ir_flags = event_data->ir_flags; + return NOTIFY_OK; +} + +static void iqs621_als_notifier_unregister(void *context) +{ + struct iqs621_als_private *iqs621_als = context; + struct iio_dev *indio_dev = iqs621_als->indio_dev; + int ret; + + ret = blocking_notifier_chain_unregister(&iqs621_als->iqs62x->nh, + &iqs621_als->notifier); + if (ret) + dev_err(indio_dev->dev.parent, + "Failed to unregister notifier: %d\n", ret); +} + +static int iqs621_als_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct iqs621_als_private *iqs621_als = iio_priv(indio_dev); + struct iqs62x_core *iqs62x = iqs621_als->iqs62x; + int ret; + __le16 val_buf; + + switch (chan->type) { + case IIO_INTENSITY: + ret = regmap_read(iqs62x->regmap, chan->address, val); + if (ret) + return ret; + + *val &= IQS621_ALS_FLAGS_RANGE; + return IIO_VAL_INT; + + case IIO_PROXIMITY: + case IIO_LIGHT: + ret = regmap_raw_read(iqs62x->regmap, chan->address, &val_buf, + sizeof(val_buf)); + if (ret) + return ret; + + *val = le16_to_cpu(val_buf); + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int iqs621_als_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct iqs621_als_private *iqs621_als = iio_priv(indio_dev); + + guard(mutex)(&iqs621_als->lock); + + switch (chan->type) { + case IIO_LIGHT: + return iqs621_als->light_en; + + case IIO_INTENSITY: + return iqs621_als->range_en; + + case IIO_PROXIMITY: + return iqs621_als->prox_en; + + default: + return -EINVAL; + } +} + +static int iqs621_als_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct iqs621_als_private *iqs621_als = iio_priv(indio_dev); + struct iqs62x_core *iqs62x = iqs621_als->iqs62x; + unsigned int val; + int ret; + + guard(mutex)(&iqs621_als->lock); + + ret = regmap_read(iqs62x->regmap, iqs62x->dev_desc->als_flags, &val); + if (ret) + return ret; + iqs621_als->als_flags = val; + + switch (chan->type) { + case IIO_LIGHT: + ret = regmap_update_bits(iqs62x->regmap, IQS620_GLBL_EVENT_MASK, + iqs62x->dev_desc->als_mask, + iqs621_als->range_en || state ? 0 : + 0xFF); + if (ret) + return ret; + iqs621_als->light_en = state; + + return 0; + + case IIO_INTENSITY: + ret = regmap_update_bits(iqs62x->regmap, IQS620_GLBL_EVENT_MASK, + iqs62x->dev_desc->als_mask, + iqs621_als->light_en || state ? 0 : + 0xFF); + if (ret) + return ret; + iqs621_als->range_en = state; + + return 0; + + case IIO_PROXIMITY: + ret = regmap_read(iqs62x->regmap, IQS622_IR_FLAGS, &val); + if (ret) + return ret; + iqs621_als->ir_flags = val; + + ret = regmap_update_bits(iqs62x->regmap, IQS620_GLBL_EVENT_MASK, + iqs62x->dev_desc->ir_mask, + state ? 0 : 0xFF); + if (ret) + return ret; + iqs621_als->prox_en = state; + + return 0; + + default: + return -EINVAL; + } +} + +static int iqs621_als_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct iqs621_als_private *iqs621_als = iio_priv(indio_dev); + + guard(mutex)(&iqs621_als->lock); + + switch (dir) { + case IIO_EV_DIR_RISING: + *val = iqs621_als->thresh_light * 16; + return IIO_VAL_INT; + + case IIO_EV_DIR_FALLING: + *val = iqs621_als->thresh_dark * 4; + return IIO_VAL_INT; + + case IIO_EV_DIR_EITHER: + if (iqs621_als->ir_flags_mask == IQS622_IR_FLAGS_TOUCH) + *val = iqs621_als->thresh_prox * 4; + else + *val = iqs621_als->thresh_prox; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int iqs621_als_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct iqs621_als_private *iqs621_als = iio_priv(indio_dev); + struct iqs62x_core *iqs62x = iqs621_als->iqs62x; + unsigned int thresh_reg, thresh_val; + u8 ir_flags_mask, *thresh_cache; + int ret; + + guard(mutex)(&iqs621_als->lock); + + switch (dir) { + case IIO_EV_DIR_RISING: + thresh_reg = IQS621_ALS_THRESH_LIGHT; + thresh_val = val / 16; + + thresh_cache = &iqs621_als->thresh_light; + ir_flags_mask = 0; + break; + + case IIO_EV_DIR_FALLING: + thresh_reg = IQS621_ALS_THRESH_DARK; + thresh_val = val / 4; + + thresh_cache = &iqs621_als->thresh_dark; + ir_flags_mask = 0; + break; + + case IIO_EV_DIR_EITHER: + /* + * The IQS622 supports two detection thresholds, both measured + * in the same arbitrary units reported by read_raw: proximity + * (0 through 255 in steps of 1), and touch (0 through 1020 in + * steps of 4). + * + * Based on the single detection threshold chosen by the user, + * select the hardware threshold that gives the best trade-off + * between range and resolution. + * + * By default, the close-range (but coarse) touch threshold is + * chosen during probe. + */ + switch (val) { + case 0 ... 255: + thresh_reg = IQS622_IR_THRESH_PROX; + thresh_val = val; + + ir_flags_mask = IQS622_IR_FLAGS_PROX; + break; + + case 256 ... 1020: + thresh_reg = IQS622_IR_THRESH_TOUCH; + thresh_val = val / 4; + + ir_flags_mask = IQS622_IR_FLAGS_TOUCH; + break; + + default: + return -EINVAL; + } + + thresh_cache = &iqs621_als->thresh_prox; + break; + + default: + return -EINVAL; + } + + if (thresh_val > 0xFF) + return -EINVAL; + + ret = regmap_write(iqs62x->regmap, thresh_reg, thresh_val); + if (ret) + return ret; + + *thresh_cache = thresh_val; + iqs621_als->ir_flags_mask = ir_flags_mask; + + return 0; +} + +static const struct iio_info iqs621_als_info = { + .read_raw = &iqs621_als_read_raw, + .read_event_config = iqs621_als_read_event_config, + .write_event_config = iqs621_als_write_event_config, + .read_event_value = iqs621_als_read_event_value, + .write_event_value = iqs621_als_write_event_value, +}; + +static const struct iio_event_spec iqs621_als_range_events[] = { + { + .type = IIO_EV_TYPE_CHANGE, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_event_spec iqs621_als_light_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, +}; + +static const struct iio_chan_spec iqs621_als_channels[] = { + { + .type = IIO_INTENSITY, + .address = IQS621_ALS_FLAGS, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = iqs621_als_range_events, + .num_event_specs = ARRAY_SIZE(iqs621_als_range_events), + }, + { + .type = IIO_LIGHT, + .address = IQS621_ALS_UI_OUT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .event_spec = iqs621_als_light_events, + .num_event_specs = ARRAY_SIZE(iqs621_als_light_events), + }, +}; + +static const struct iio_event_spec iqs622_als_prox_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE), + }, +}; + +static const struct iio_chan_spec iqs622_als_channels[] = { + { + .type = IIO_INTENSITY, + .channel2 = IIO_MOD_LIGHT_BOTH, + .address = IQS622_ALS_FLAGS, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = iqs621_als_range_events, + .num_event_specs = ARRAY_SIZE(iqs621_als_range_events), + .modified = true, + }, + { + .type = IIO_INTENSITY, + .channel2 = IIO_MOD_LIGHT_IR, + .address = IQS622_IR_RANGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .modified = true, + }, + { + .type = IIO_PROXIMITY, + .address = IQS622_IR_UI_OUT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = iqs622_als_prox_events, + .num_event_specs = ARRAY_SIZE(iqs622_als_prox_events), + }, +}; + +static int iqs621_als_probe(struct platform_device *pdev) +{ + struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent); + struct iqs621_als_private *iqs621_als; + struct iio_dev *indio_dev; + unsigned int val; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*iqs621_als)); + if (!indio_dev) + return -ENOMEM; + + iqs621_als = iio_priv(indio_dev); + iqs621_als->iqs62x = iqs62x; + iqs621_als->indio_dev = indio_dev; + + if (iqs62x->dev_desc->prod_num == IQS622_PROD_NUM) { + ret = regmap_read(iqs62x->regmap, IQS622_IR_THRESH_TOUCH, + &val); + if (ret) + return ret; + iqs621_als->thresh_prox = val; + iqs621_als->ir_flags_mask = IQS622_IR_FLAGS_TOUCH; + + indio_dev->channels = iqs622_als_channels; + indio_dev->num_channels = ARRAY_SIZE(iqs622_als_channels); + } else { + ret = regmap_read(iqs62x->regmap, IQS621_ALS_THRESH_LIGHT, + &val); + if (ret) + return ret; + iqs621_als->thresh_light = val; + + ret = regmap_read(iqs62x->regmap, IQS621_ALS_THRESH_DARK, + &val); + if (ret) + return ret; + iqs621_als->thresh_dark = val; + + indio_dev->channels = iqs621_als_channels; + indio_dev->num_channels = ARRAY_SIZE(iqs621_als_channels); + } + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = iqs62x->dev_desc->dev_name; + indio_dev->info = &iqs621_als_info; + + mutex_init(&iqs621_als->lock); + + iqs621_als->notifier.notifier_call = iqs621_als_notifier; + ret = blocking_notifier_chain_register(&iqs621_als->iqs62x->nh, + &iqs621_als->notifier); + if (ret) { + dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret); + return ret; + } + + ret = devm_add_action_or_reset(&pdev->dev, + iqs621_als_notifier_unregister, + iqs621_als); + if (ret) + return ret; + + return devm_iio_device_register(&pdev->dev, indio_dev); +} + +static struct platform_driver iqs621_als_platform_driver = { + .driver = { + .name = "iqs621-als", + }, + .probe = iqs621_als_probe, +}; +module_platform_driver(iqs621_als_platform_driver); + +MODULE_AUTHOR("Jeff LaBundy "); +MODULE_DESCRIPTION("Azoteq IQS621/622 Ambient Light Sensors"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:iqs621-als"); diff --git a/drivers/iio/light/isl29018.c b/drivers/iio/light/isl29018.c new file mode 100644 index 000000000..759cb71ed --- /dev/null +++ b/drivers/iio/light/isl29018.c @@ -0,0 +1,859 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * A iio driver for the light sensor ISL 29018/29023/29035. + * + * IIO driver for monitoring ambient light intensity in luxi, proximity + * sensing and infrared sensing. + * + * Copyright (c) 2010, NVIDIA Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define ISL29018_CONV_TIME_MS 100 + +#define ISL29018_REG_ADD_COMMAND1 0x00 +#define ISL29018_CMD1_OPMODE_SHIFT 5 +#define ISL29018_CMD1_OPMODE_MASK (7 << ISL29018_CMD1_OPMODE_SHIFT) +#define ISL29018_CMD1_OPMODE_POWER_DOWN 0 +#define ISL29018_CMD1_OPMODE_ALS_ONCE 1 +#define ISL29018_CMD1_OPMODE_IR_ONCE 2 +#define ISL29018_CMD1_OPMODE_PROX_ONCE 3 + +#define ISL29018_REG_ADD_COMMAND2 0x01 +#define ISL29018_CMD2_RESOLUTION_SHIFT 2 +#define ISL29018_CMD2_RESOLUTION_MASK (0x3 << ISL29018_CMD2_RESOLUTION_SHIFT) + +#define ISL29018_CMD2_RANGE_SHIFT 0 +#define ISL29018_CMD2_RANGE_MASK (0x3 << ISL29018_CMD2_RANGE_SHIFT) + +#define ISL29018_CMD2_SCHEME_SHIFT 7 +#define ISL29018_CMD2_SCHEME_MASK (0x1 << ISL29018_CMD2_SCHEME_SHIFT) + +#define ISL29018_REG_ADD_DATA_LSB 0x02 +#define ISL29018_REG_ADD_DATA_MSB 0x03 + +#define ISL29018_REG_TEST 0x08 +#define ISL29018_TEST_SHIFT 0 +#define ISL29018_TEST_MASK (0xFF << ISL29018_TEST_SHIFT) + +#define ISL29035_REG_DEVICE_ID 0x0F +#define ISL29035_DEVICE_ID_SHIFT 0x03 +#define ISL29035_DEVICE_ID_MASK (0x7 << ISL29035_DEVICE_ID_SHIFT) +#define ISL29035_DEVICE_ID 0x5 +#define ISL29035_BOUT_SHIFT 0x07 +#define ISL29035_BOUT_MASK (0x01 << ISL29035_BOUT_SHIFT) + +enum isl29018_int_time { + ISL29018_INT_TIME_16, + ISL29018_INT_TIME_12, + ISL29018_INT_TIME_8, + ISL29018_INT_TIME_4, +}; + +static const unsigned int isl29018_int_utimes[3][4] = { + {90000, 5630, 351, 21}, + {90000, 5600, 352, 22}, + {105000, 6500, 410, 25}, +}; + +static const struct isl29018_scale { + unsigned int scale; + unsigned int uscale; +} isl29018_scales[4][4] = { + { {0, 15258}, {0, 61035}, {0, 244140}, {0, 976562} }, + { {0, 244140}, {0, 976562}, {3, 906250}, {15, 625000} }, + { {3, 906250}, {15, 625000}, {62, 500000}, {250, 0} }, + { {62, 500000}, {250, 0}, {1000, 0}, {4000, 0} } +}; + +struct isl29018_chip { + struct regmap *regmap; + struct mutex lock; + int type; + unsigned int calibscale; + unsigned int ucalibscale; + unsigned int int_time; + struct isl29018_scale scale; + int prox_scheme; + bool suspended; + struct regulator *vcc_reg; +}; + +static int isl29018_set_integration_time(struct isl29018_chip *chip, + unsigned int utime) +{ + unsigned int i; + int ret; + unsigned int int_time, new_int_time; + + for (i = 0; i < ARRAY_SIZE(isl29018_int_utimes[chip->type]); ++i) { + if (utime == isl29018_int_utimes[chip->type][i]) { + new_int_time = i; + break; + } + } + + if (i >= ARRAY_SIZE(isl29018_int_utimes[chip->type])) + return -EINVAL; + + ret = regmap_update_bits(chip->regmap, ISL29018_REG_ADD_COMMAND2, + ISL29018_CMD2_RESOLUTION_MASK, + i << ISL29018_CMD2_RESOLUTION_SHIFT); + if (ret < 0) + return ret; + + /* Keep the same range when integration time changes */ + int_time = chip->int_time; + for (i = 0; i < ARRAY_SIZE(isl29018_scales[int_time]); ++i) { + if (chip->scale.scale == isl29018_scales[int_time][i].scale && + chip->scale.uscale == isl29018_scales[int_time][i].uscale) { + chip->scale = isl29018_scales[new_int_time][i]; + break; + } + } + chip->int_time = new_int_time; + + return 0; +} + +static int isl29018_set_scale(struct isl29018_chip *chip, int scale, int uscale) +{ + unsigned int i; + int ret; + struct isl29018_scale new_scale; + + for (i = 0; i < ARRAY_SIZE(isl29018_scales[chip->int_time]); ++i) { + if (scale == isl29018_scales[chip->int_time][i].scale && + uscale == isl29018_scales[chip->int_time][i].uscale) { + new_scale = isl29018_scales[chip->int_time][i]; + break; + } + } + + if (i >= ARRAY_SIZE(isl29018_scales[chip->int_time])) + return -EINVAL; + + ret = regmap_update_bits(chip->regmap, ISL29018_REG_ADD_COMMAND2, + ISL29018_CMD2_RANGE_MASK, + i << ISL29018_CMD2_RANGE_SHIFT); + if (ret < 0) + return ret; + + chip->scale = new_scale; + + return 0; +} + +static int isl29018_read_sensor_input(struct isl29018_chip *chip, int mode) +{ + int status; + unsigned int lsb; + unsigned int msb; + struct device *dev = regmap_get_device(chip->regmap); + + /* Set mode */ + status = regmap_write(chip->regmap, ISL29018_REG_ADD_COMMAND1, + mode << ISL29018_CMD1_OPMODE_SHIFT); + if (status) { + dev_err(dev, + "Error in setting operating mode err %d\n", status); + return status; + } + msleep(ISL29018_CONV_TIME_MS); + status = regmap_read(chip->regmap, ISL29018_REG_ADD_DATA_LSB, &lsb); + if (status < 0) { + dev_err(dev, + "Error in reading LSB DATA with err %d\n", status); + return status; + } + + status = regmap_read(chip->regmap, ISL29018_REG_ADD_DATA_MSB, &msb); + if (status < 0) { + dev_err(dev, + "Error in reading MSB DATA with error %d\n", status); + return status; + } + dev_vdbg(dev, "MSB 0x%x and LSB 0x%x\n", msb, lsb); + + return (msb << 8) | lsb; +} + +static int isl29018_read_lux(struct isl29018_chip *chip, int *lux) +{ + int lux_data; + unsigned int data_x_range; + + lux_data = isl29018_read_sensor_input(chip, + ISL29018_CMD1_OPMODE_ALS_ONCE); + if (lux_data < 0) + return lux_data; + + data_x_range = lux_data * chip->scale.scale + + lux_data * chip->scale.uscale / 1000000; + *lux = data_x_range * chip->calibscale + + data_x_range * chip->ucalibscale / 1000000; + + return 0; +} + +static int isl29018_read_ir(struct isl29018_chip *chip, int *ir) +{ + int ir_data; + + ir_data = isl29018_read_sensor_input(chip, + ISL29018_CMD1_OPMODE_IR_ONCE); + if (ir_data < 0) + return ir_data; + + *ir = ir_data; + + return 0; +} + +static int isl29018_read_proximity_ir(struct isl29018_chip *chip, int scheme, + int *near_ir) +{ + int status; + int prox_data = -1; + int ir_data = -1; + struct device *dev = regmap_get_device(chip->regmap); + + /* Do proximity sensing with required scheme */ + status = regmap_update_bits(chip->regmap, ISL29018_REG_ADD_COMMAND2, + ISL29018_CMD2_SCHEME_MASK, + scheme << ISL29018_CMD2_SCHEME_SHIFT); + if (status) { + dev_err(dev, "Error in setting operating mode\n"); + return status; + } + + prox_data = isl29018_read_sensor_input(chip, + ISL29018_CMD1_OPMODE_PROX_ONCE); + if (prox_data < 0) + return prox_data; + + if (scheme == 1) { + *near_ir = prox_data; + return 0; + } + + ir_data = isl29018_read_sensor_input(chip, + ISL29018_CMD1_OPMODE_IR_ONCE); + if (ir_data < 0) + return ir_data; + + if (prox_data >= ir_data) + *near_ir = prox_data - ir_data; + else + *near_ir = 0; + + return 0; +} + +static ssize_t in_illuminance_scale_available_show + (struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct isl29018_chip *chip = iio_priv(indio_dev); + unsigned int i; + int len = 0; + + mutex_lock(&chip->lock); + for (i = 0; i < ARRAY_SIZE(isl29018_scales[chip->int_time]); ++i) + len += sysfs_emit_at(buf, len, "%d.%06d ", + isl29018_scales[chip->int_time][i].scale, + isl29018_scales[chip->int_time][i].uscale); + mutex_unlock(&chip->lock); + + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t in_illuminance_integration_time_available_show + (struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct isl29018_chip *chip = iio_priv(indio_dev); + unsigned int i; + int len = 0; + + for (i = 0; i < ARRAY_SIZE(isl29018_int_utimes[chip->type]); ++i) + len += sysfs_emit_at(buf, len, "0.%06d ", + isl29018_int_utimes[chip->type][i]); + + buf[len - 1] = '\n'; + + return len; +} + +/* + * From ISL29018 Data Sheet (FN6619.4, Oct 8, 2012) regarding the + * infrared suppression: + * + * Proximity Sensing Scheme: Bit 7. This bit programs the function + * of the proximity detection. Logic 0 of this bit, Scheme 0, makes + * full n (4, 8, 12, 16) bits (unsigned) proximity detection. The range + * of Scheme 0 proximity count is from 0 to 2^n. Logic 1 of this bit, + * Scheme 1, makes n-1 (3, 7, 11, 15) bits (2's complementary) + * proximity_less_ambient detection. The range of Scheme 1 + * proximity count is from -2^(n-1) to 2^(n-1) . The sign bit is extended + * for resolutions less than 16. While Scheme 0 has wider dynamic + * range, Scheme 1 proximity detection is less affected by the + * ambient IR noise variation. + * + * 0 Sensing IR from LED and ambient + * 1 Sensing IR from LED with ambient IR rejection + */ +static ssize_t proximity_on_chip_ambient_infrared_suppression_show + (struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct isl29018_chip *chip = iio_priv(indio_dev); + + /* + * Return the "proximity scheme" i.e. if the chip does on chip + * infrared suppression (1 means perform on chip suppression) + */ + return sysfs_emit(buf, "%d\n", chip->prox_scheme); +} + +static ssize_t proximity_on_chip_ambient_infrared_suppression_store + (struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct isl29018_chip *chip = iio_priv(indio_dev); + int val; + + if (kstrtoint(buf, 10, &val)) + return -EINVAL; + if (!(val == 0 || val == 1)) + return -EINVAL; + + /* + * Get the "proximity scheme" i.e. if the chip does on chip + * infrared suppression (1 means perform on chip suppression) + */ + mutex_lock(&chip->lock); + chip->prox_scheme = val; + mutex_unlock(&chip->lock); + + return count; +} + +static int isl29018_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct isl29018_chip *chip = iio_priv(indio_dev); + int ret = -EINVAL; + + mutex_lock(&chip->lock); + if (chip->suspended) { + ret = -EBUSY; + goto write_done; + } + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->type == IIO_LIGHT) { + chip->calibscale = val; + chip->ucalibscale = val2; + ret = 0; + } + break; + case IIO_CHAN_INFO_INT_TIME: + if (chan->type == IIO_LIGHT && !val) + ret = isl29018_set_integration_time(chip, val2); + break; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_LIGHT) + ret = isl29018_set_scale(chip, val, val2); + break; + default: + break; + } + +write_done: + mutex_unlock(&chip->lock); + + return ret; +} + +static int isl29018_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + int ret = -EINVAL; + struct isl29018_chip *chip = iio_priv(indio_dev); + + mutex_lock(&chip->lock); + if (chip->suspended) { + ret = -EBUSY; + goto read_done; + } + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_LIGHT: + ret = isl29018_read_lux(chip, val); + break; + case IIO_INTENSITY: + ret = isl29018_read_ir(chip, val); + break; + case IIO_PROXIMITY: + ret = isl29018_read_proximity_ir(chip, + chip->prox_scheme, + val); + break; + default: + break; + } + if (!ret) + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_INT_TIME: + if (chan->type == IIO_LIGHT) { + *val = 0; + *val2 = isl29018_int_utimes[chip->type][chip->int_time]; + ret = IIO_VAL_INT_PLUS_MICRO; + } + break; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_LIGHT) { + *val = chip->scale.scale; + *val2 = chip->scale.uscale; + ret = IIO_VAL_INT_PLUS_MICRO; + } + break; + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->type == IIO_LIGHT) { + *val = chip->calibscale; + *val2 = chip->ucalibscale; + ret = IIO_VAL_INT_PLUS_MICRO; + } + break; + default: + break; + } + +read_done: + mutex_unlock(&chip->lock); + + return ret; +} + +#define ISL29018_LIGHT_CHANNEL { \ + .type = IIO_LIGHT, \ + .indexed = 1, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ +} + +#define ISL29018_IR_CHANNEL { \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .channel2 = IIO_MOD_LIGHT_IR, \ +} + +#define ISL29018_PROXIMITY_CHANNEL { \ + .type = IIO_PROXIMITY, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ +} + +static const struct iio_chan_spec isl29018_channels[] = { + ISL29018_LIGHT_CHANNEL, + ISL29018_IR_CHANNEL, + ISL29018_PROXIMITY_CHANNEL, +}; + +static const struct iio_chan_spec isl29023_channels[] = { + ISL29018_LIGHT_CHANNEL, + ISL29018_IR_CHANNEL, +}; + +static IIO_DEVICE_ATTR_RO(in_illuminance_integration_time_available, 0); +static IIO_DEVICE_ATTR_RO(in_illuminance_scale_available, 0); +static IIO_DEVICE_ATTR_RW(proximity_on_chip_ambient_infrared_suppression, 0); + +#define ISL29018_DEV_ATTR(name) (&iio_dev_attr_##name.dev_attr.attr) + +static struct attribute *isl29018_attributes[] = { + ISL29018_DEV_ATTR(in_illuminance_scale_available), + ISL29018_DEV_ATTR(in_illuminance_integration_time_available), + ISL29018_DEV_ATTR(proximity_on_chip_ambient_infrared_suppression), + NULL +}; + +static struct attribute *isl29023_attributes[] = { + ISL29018_DEV_ATTR(in_illuminance_scale_available), + ISL29018_DEV_ATTR(in_illuminance_integration_time_available), + NULL +}; + +static const struct attribute_group isl29018_group = { + .attrs = isl29018_attributes, +}; + +static const struct attribute_group isl29023_group = { + .attrs = isl29023_attributes, +}; + +enum { + isl29018, + isl29023, + isl29035, +}; + +static int isl29018_chip_init(struct isl29018_chip *chip) +{ + int status; + struct device *dev = regmap_get_device(chip->regmap); + + if (chip->type == isl29035) { + unsigned int id; + + status = regmap_read(chip->regmap, ISL29035_REG_DEVICE_ID, &id); + if (status < 0) { + dev_err(dev, + "Error reading ID register with error %d\n", + status); + return status; + } + + id = (id & ISL29035_DEVICE_ID_MASK) >> ISL29035_DEVICE_ID_SHIFT; + + if (id != ISL29035_DEVICE_ID) + return -ENODEV; + + /* Clear brownout bit */ + status = regmap_clear_bits(chip->regmap, + ISL29035_REG_DEVICE_ID, + ISL29035_BOUT_MASK); + if (status < 0) + return status; + } + + /* + * Code added per Intersil Application Note 1534: + * When VDD sinks to approximately 1.8V or below, some of + * the part's registers may change their state. When VDD + * recovers to 2.25V (or greater), the part may thus be in an + * unknown mode of operation. The user can return the part to + * a known mode of operation either by (a) setting VDD = 0V for + * 1 second or more and then powering back up with a slew rate + * of 0.5V/ms or greater, or (b) via I2C disable all ALS/PROX + * conversions, clear the test registers, and then rewrite all + * registers to the desired values. + * ... + * For ISL29011, ISL29018, ISL29021, ISL29023 + * 1. Write 0x00 to register 0x08 (TEST) + * 2. Write 0x00 to register 0x00 (CMD1) + * 3. Rewrite all registers to the desired values + * + * ISL29018 Data Sheet (FN6619.1, Feb 11, 2010) essentially says + * the same thing EXCEPT the data sheet asks for a 1ms delay after + * writing the CMD1 register. + */ + status = regmap_write(chip->regmap, ISL29018_REG_TEST, 0x0); + if (status < 0) { + dev_err(dev, "Failed to clear isl29018 TEST reg.(%d)\n", + status); + return status; + } + + /* + * See Intersil AN1534 comments above. + * "Operating Mode" (COMMAND1) register is reprogrammed when + * data is read from the device. + */ + status = regmap_write(chip->regmap, ISL29018_REG_ADD_COMMAND1, 0); + if (status < 0) { + dev_err(dev, "Failed to clear isl29018 CMD1 reg.(%d)\n", + status); + return status; + } + + usleep_range(1000, 2000); /* per data sheet, page 10 */ + + /* Set defaults */ + status = isl29018_set_scale(chip, chip->scale.scale, + chip->scale.uscale); + if (status < 0) { + dev_err(dev, "Init of isl29018 fails\n"); + return status; + } + + status = isl29018_set_integration_time(chip, + isl29018_int_utimes[chip->type][chip->int_time]); + if (status < 0) + dev_err(dev, "Init of isl29018 fails\n"); + + return status; +} + +static const struct iio_info isl29018_info = { + .attrs = &isl29018_group, + .read_raw = isl29018_read_raw, + .write_raw = isl29018_write_raw, +}; + +static const struct iio_info isl29023_info = { + .attrs = &isl29023_group, + .read_raw = isl29018_read_raw, + .write_raw = isl29018_write_raw, +}; + +static bool isl29018_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ISL29018_REG_ADD_DATA_LSB: + case ISL29018_REG_ADD_DATA_MSB: + case ISL29018_REG_ADD_COMMAND1: + case ISL29018_REG_TEST: + case ISL29035_REG_DEVICE_ID: + return true; + default: + return false; + } +} + +static const struct regmap_config isl29018_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = isl29018_is_volatile_reg, + .max_register = ISL29018_REG_TEST, + .num_reg_defaults_raw = ISL29018_REG_TEST + 1, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct regmap_config isl29035_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = isl29018_is_volatile_reg, + .max_register = ISL29035_REG_DEVICE_ID, + .num_reg_defaults_raw = ISL29035_REG_DEVICE_ID + 1, + .cache_type = REGCACHE_RBTREE, +}; + +struct isl29018_chip_info { + const struct iio_chan_spec *channels; + int num_channels; + const struct iio_info *indio_info; + const struct regmap_config *regmap_cfg; +}; + +static const struct isl29018_chip_info isl29018_chip_info_tbl[] = { + [isl29018] = { + .channels = isl29018_channels, + .num_channels = ARRAY_SIZE(isl29018_channels), + .indio_info = &isl29018_info, + .regmap_cfg = &isl29018_regmap_config, + }, + [isl29023] = { + .channels = isl29023_channels, + .num_channels = ARRAY_SIZE(isl29023_channels), + .indio_info = &isl29023_info, + .regmap_cfg = &isl29018_regmap_config, + }, + [isl29035] = { + .channels = isl29023_channels, + .num_channels = ARRAY_SIZE(isl29023_channels), + .indio_info = &isl29023_info, + .regmap_cfg = &isl29035_regmap_config, + }, +}; + +static void isl29018_disable_regulator_action(void *_data) +{ + struct isl29018_chip *chip = _data; + int err; + + err = regulator_disable(chip->vcc_reg); + if (err) + pr_err("failed to disable isl29018's VCC regulator!\n"); +} + +static int isl29018_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct isl29018_chip *chip; + struct iio_dev *indio_dev; + const void *ddata = NULL; + const char *name; + int dev_id; + int err; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + + i2c_set_clientdata(client, indio_dev); + + if (id) { + name = id->name; + dev_id = id->driver_data; + } else { + name = iio_get_acpi_device_name_and_data(&client->dev, &ddata); + dev_id = (intptr_t)ddata; + } + + mutex_init(&chip->lock); + + chip->type = dev_id; + chip->calibscale = 1; + chip->ucalibscale = 0; + chip->int_time = ISL29018_INT_TIME_16; + chip->scale = isl29018_scales[chip->int_time][0]; + chip->suspended = false; + + chip->vcc_reg = devm_regulator_get(&client->dev, "vcc"); + if (IS_ERR(chip->vcc_reg)) + return dev_err_probe(&client->dev, PTR_ERR(chip->vcc_reg), + "failed to get VCC regulator!\n"); + + err = regulator_enable(chip->vcc_reg); + if (err) { + dev_err(&client->dev, "failed to enable VCC regulator!\n"); + return err; + } + + err = devm_add_action_or_reset(&client->dev, isl29018_disable_regulator_action, + chip); + if (err) { + dev_err(&client->dev, "failed to setup regulator cleanup action!\n"); + return err; + } + + chip->regmap = devm_regmap_init_i2c(client, + isl29018_chip_info_tbl[dev_id].regmap_cfg); + if (IS_ERR(chip->regmap)) { + err = PTR_ERR(chip->regmap); + dev_err(&client->dev, "regmap initialization fails: %d\n", err); + return err; + } + + err = isl29018_chip_init(chip); + if (err) + return err; + + indio_dev->info = isl29018_chip_info_tbl[dev_id].indio_info; + indio_dev->channels = isl29018_chip_info_tbl[dev_id].channels; + indio_dev->num_channels = isl29018_chip_info_tbl[dev_id].num_channels; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static int isl29018_suspend(struct device *dev) +{ + struct isl29018_chip *chip = iio_priv(dev_get_drvdata(dev)); + int ret; + + mutex_lock(&chip->lock); + + /* + * Since this driver uses only polling commands, we are by default in + * auto shutdown (ie, power-down) mode. + * So we do not have much to do here. + */ + chip->suspended = true; + ret = regulator_disable(chip->vcc_reg); + if (ret) + dev_err(dev, "failed to disable VCC regulator\n"); + + mutex_unlock(&chip->lock); + + return ret; +} + +static int isl29018_resume(struct device *dev) +{ + struct isl29018_chip *chip = iio_priv(dev_get_drvdata(dev)); + int err; + + mutex_lock(&chip->lock); + + err = regulator_enable(chip->vcc_reg); + if (err) { + dev_err(dev, "failed to enable VCC regulator\n"); + mutex_unlock(&chip->lock); + return err; + } + + err = isl29018_chip_init(chip); + if (!err) + chip->suspended = false; + + mutex_unlock(&chip->lock); + + return err; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(isl29018_pm_ops, isl29018_suspend, + isl29018_resume); + +static const struct acpi_device_id isl29018_acpi_match[] = { + {"ISL29018", isl29018}, + {"ISL29023", isl29023}, + {"ISL29035", isl29035}, + { } +}; +MODULE_DEVICE_TABLE(acpi, isl29018_acpi_match); + +static const struct i2c_device_id isl29018_id[] = { + { .name = "isl29018", .driver_data = isl29018 }, + { .name = "isl29023", .driver_data = isl29023 }, + { .name = "isl29035", .driver_data = isl29035 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, isl29018_id); + +static const struct of_device_id isl29018_of_match[] = { + { .compatible = "isil,isl29018", }, + { .compatible = "isil,isl29023", }, + { .compatible = "isil,isl29035", }, + { } +}; +MODULE_DEVICE_TABLE(of, isl29018_of_match); + +static struct i2c_driver isl29018_driver = { + .driver = { + .name = "isl29018", + .acpi_match_table = isl29018_acpi_match, + .pm = pm_sleep_ptr(&isl29018_pm_ops), + .of_match_table = isl29018_of_match, + }, + .probe = isl29018_probe, + .id_table = isl29018_id, +}; +module_i2c_driver(isl29018_driver); + +MODULE_DESCRIPTION("ISL29018 Ambient Light Sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/isl29028.c b/drivers/iio/light/isl29028.c new file mode 100644 index 000000000..33deb1726 --- /dev/null +++ b/drivers/iio/light/isl29028.c @@ -0,0 +1,695 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * IIO driver for the light sensor ISL29028. + * ISL29028 is Concurrent Ambient Light and Proximity Sensor + * + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + * Copyright (c) 2016-2017 Brian Masney + * + * Datasheets: + * - http://www.intersil.com/content/dam/Intersil/documents/isl2/isl29028.pdf + * - http://www.intersil.com/content/dam/Intersil/documents/isl2/isl29030.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ISL29028_CONV_TIME_MS 100 + +#define ISL29028_REG_CONFIGURE 0x01 + +#define ISL29028_CONF_ALS_IR_MODE_ALS 0 +#define ISL29028_CONF_ALS_IR_MODE_IR BIT(0) +#define ISL29028_CONF_ALS_IR_MODE_MASK BIT(0) + +#define ISL29028_CONF_ALS_RANGE_LOW_LUX 0 +#define ISL29028_CONF_ALS_RANGE_HIGH_LUX BIT(1) +#define ISL29028_CONF_ALS_RANGE_MASK BIT(1) + +#define ISL29028_CONF_ALS_DIS 0 +#define ISL29028_CONF_ALS_EN BIT(2) +#define ISL29028_CONF_ALS_EN_MASK BIT(2) + +#define ISL29028_CONF_PROX_SLP_SH 4 +#define ISL29028_CONF_PROX_SLP_MASK (7 << ISL29028_CONF_PROX_SLP_SH) + +#define ISL29028_CONF_PROX_EN BIT(7) +#define ISL29028_CONF_PROX_EN_MASK BIT(7) + +#define ISL29028_REG_INTERRUPT 0x02 + +#define ISL29028_REG_PROX_DATA 0x08 +#define ISL29028_REG_ALSIR_L 0x09 +#define ISL29028_REG_ALSIR_U 0x0A + +#define ISL29028_REG_TEST1_MODE 0x0E +#define ISL29028_REG_TEST2_MODE 0x0F + +#define ISL29028_NUM_REGS (ISL29028_REG_TEST2_MODE + 1) + +#define ISL29028_POWER_OFF_DELAY_MS 2000 + +struct isl29028_prox_data { + int sampling_int; + int sampling_fract; + int sleep_time; +}; + +static const struct isl29028_prox_data isl29028_prox_data[] = { + { 1, 250000, 800 }, + { 2, 500000, 400 }, + { 5, 0, 200 }, + { 10, 0, 100 }, + { 13, 300000, 75 }, + { 20, 0, 50 }, + { 80, 0, 13 }, /* + * Note: Data sheet lists 12.5 ms sleep time. + * Round up a half millisecond for msleep(). + */ + { 100, 0, 0 } +}; + +enum isl29028_als_ir_mode { + ISL29028_MODE_NONE = 0, + ISL29028_MODE_ALS, + ISL29028_MODE_IR, +}; + +struct isl29028_chip { + struct mutex lock; + struct regmap *regmap; + int prox_sampling_int; + int prox_sampling_frac; + bool enable_prox; + int lux_scale; + enum isl29028_als_ir_mode als_ir_mode; +}; + +static int isl29028_find_prox_sleep_index(int sampling_int, int sampling_fract) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(isl29028_prox_data); ++i) { + if (isl29028_prox_data[i].sampling_int == sampling_int && + isl29028_prox_data[i].sampling_fract == sampling_fract) + return i; + } + + return -EINVAL; +} + +static int isl29028_set_proxim_sampling(struct isl29028_chip *chip, + int sampling_int, int sampling_fract) +{ + struct device *dev = regmap_get_device(chip->regmap); + int sleep_index, ret; + + sleep_index = isl29028_find_prox_sleep_index(sampling_int, + sampling_fract); + if (sleep_index < 0) + return sleep_index; + + ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE, + ISL29028_CONF_PROX_SLP_MASK, + sleep_index << ISL29028_CONF_PROX_SLP_SH); + + if (ret < 0) { + dev_err(dev, "%s(): Error %d setting the proximity sampling\n", + __func__, ret); + return ret; + } + + chip->prox_sampling_int = sampling_int; + chip->prox_sampling_frac = sampling_fract; + + return ret; +} + +static int isl29028_enable_proximity(struct isl29028_chip *chip) +{ + int prox_index, ret; + + ret = isl29028_set_proxim_sampling(chip, chip->prox_sampling_int, + chip->prox_sampling_frac); + if (ret < 0) + return ret; + + ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE, + ISL29028_CONF_PROX_EN_MASK, + ISL29028_CONF_PROX_EN); + if (ret < 0) + return ret; + + /* Wait for conversion to be complete for first sample */ + prox_index = isl29028_find_prox_sleep_index(chip->prox_sampling_int, + chip->prox_sampling_frac); + if (prox_index < 0) + return prox_index; + + msleep(isl29028_prox_data[prox_index].sleep_time); + + return 0; +} + +static int isl29028_set_als_scale(struct isl29028_chip *chip, int lux_scale) +{ + struct device *dev = regmap_get_device(chip->regmap); + int val = (lux_scale == 2000) ? ISL29028_CONF_ALS_RANGE_HIGH_LUX : + ISL29028_CONF_ALS_RANGE_LOW_LUX; + int ret; + + ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE, + ISL29028_CONF_ALS_RANGE_MASK, val); + if (ret < 0) { + dev_err(dev, "%s(): Error %d setting the ALS scale\n", __func__, + ret); + return ret; + } + + chip->lux_scale = lux_scale; + + return ret; +} + +static int isl29028_set_als_ir_mode(struct isl29028_chip *chip, + enum isl29028_als_ir_mode mode) +{ + int ret; + + if (chip->als_ir_mode == mode) + return 0; + + ret = isl29028_set_als_scale(chip, chip->lux_scale); + if (ret < 0) + return ret; + + switch (mode) { + case ISL29028_MODE_ALS: + ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE, + ISL29028_CONF_ALS_IR_MODE_MASK, + ISL29028_CONF_ALS_IR_MODE_ALS); + if (ret < 0) + return ret; + + ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE, + ISL29028_CONF_ALS_RANGE_MASK, + ISL29028_CONF_ALS_RANGE_HIGH_LUX); + break; + case ISL29028_MODE_IR: + ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE, + ISL29028_CONF_ALS_IR_MODE_MASK, + ISL29028_CONF_ALS_IR_MODE_IR); + break; + case ISL29028_MODE_NONE: + return regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE, + ISL29028_CONF_ALS_EN_MASK, + ISL29028_CONF_ALS_DIS); + } + + if (ret < 0) + return ret; + + /* Enable the ALS/IR */ + ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE, + ISL29028_CONF_ALS_EN_MASK, + ISL29028_CONF_ALS_EN); + if (ret < 0) + return ret; + + /* Need to wait for conversion time if ALS/IR mode enabled */ + msleep(ISL29028_CONV_TIME_MS); + + chip->als_ir_mode = mode; + + return 0; +} + +static int isl29028_read_als_ir(struct isl29028_chip *chip, int *als_ir) +{ + struct device *dev = regmap_get_device(chip->regmap); + unsigned int lsb; + unsigned int msb; + int ret; + + ret = regmap_read(chip->regmap, ISL29028_REG_ALSIR_L, &lsb); + if (ret < 0) { + dev_err(dev, + "%s(): Error %d reading register ALSIR_L\n", + __func__, ret); + return ret; + } + + ret = regmap_read(chip->regmap, ISL29028_REG_ALSIR_U, &msb); + if (ret < 0) { + dev_err(dev, + "%s(): Error %d reading register ALSIR_U\n", + __func__, ret); + return ret; + } + + *als_ir = ((msb & 0xF) << 8) | (lsb & 0xFF); + + return 0; +} + +static int isl29028_read_proxim(struct isl29028_chip *chip, int *prox) +{ + struct device *dev = regmap_get_device(chip->regmap); + unsigned int data; + int ret; + + if (!chip->enable_prox) { + ret = isl29028_enable_proximity(chip); + if (ret < 0) + return ret; + + chip->enable_prox = true; + } + + ret = regmap_read(chip->regmap, ISL29028_REG_PROX_DATA, &data); + if (ret < 0) { + dev_err(dev, "%s(): Error %d reading register PROX_DATA\n", + __func__, ret); + return ret; + } + + *prox = data; + + return 0; +} + +static int isl29028_als_get(struct isl29028_chip *chip, int *als_data) +{ + struct device *dev = regmap_get_device(chip->regmap); + int ret; + int als_ir_data; + + ret = isl29028_set_als_ir_mode(chip, ISL29028_MODE_ALS); + if (ret < 0) { + dev_err(dev, "%s(): Error %d enabling ALS mode\n", __func__, + ret); + return ret; + } + + ret = isl29028_read_als_ir(chip, &als_ir_data); + if (ret < 0) + return ret; + + /* + * convert als data count to lux. + * if lux_scale = 125, lux = count * 0.031 + * if lux_scale = 2000, lux = count * 0.49 + */ + if (chip->lux_scale == 125) + als_ir_data = (als_ir_data * 31) / 1000; + else + als_ir_data = (als_ir_data * 49) / 100; + + *als_data = als_ir_data; + + return 0; +} + +static int isl29028_ir_get(struct isl29028_chip *chip, int *ir_data) +{ + struct device *dev = regmap_get_device(chip->regmap); + int ret; + + ret = isl29028_set_als_ir_mode(chip, ISL29028_MODE_IR); + if (ret < 0) { + dev_err(dev, "%s(): Error %d enabling IR mode\n", __func__, + ret); + return ret; + } + + return isl29028_read_als_ir(chip, ir_data); +} + +/* Channel IO */ +static int isl29028_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct isl29028_chip *chip = iio_priv(indio_dev); + struct device *dev = regmap_get_device(chip->regmap); + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + mutex_lock(&chip->lock); + + ret = -EINVAL; + switch (chan->type) { + case IIO_PROXIMITY: + if (mask != IIO_CHAN_INFO_SAMP_FREQ) { + dev_err(dev, + "%s(): proximity: Mask value 0x%08lx is not supported\n", + __func__, mask); + break; + } + + if (val < 1 || val > 100) { + dev_err(dev, + "%s(): proximity: Sampling frequency %d is not in the range [1:100]\n", + __func__, val); + break; + } + + ret = isl29028_set_proxim_sampling(chip, val, val2); + break; + case IIO_LIGHT: + if (mask != IIO_CHAN_INFO_SCALE) { + dev_err(dev, + "%s(): light: Mask value 0x%08lx is not supported\n", + __func__, mask); + break; + } + + if (val != 125 && val != 2000) { + dev_err(dev, + "%s(): light: Lux scale %d is not in the set {125, 2000}\n", + __func__, val); + break; + } + + ret = isl29028_set_als_scale(chip, val); + break; + default: + dev_err(dev, "%s(): Unsupported channel type %x\n", + __func__, chan->type); + break; + } + + mutex_unlock(&chip->lock); + + if (ret < 0) + return ret; + + ret = pm_runtime_put_autosuspend(dev); + if (ret < 0) + return ret; + + return 0; +} + +static int isl29028_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct isl29028_chip *chip = iio_priv(indio_dev); + struct device *dev = regmap_get_device(chip->regmap); + int ret, pm_ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + mutex_lock(&chip->lock); + + ret = -EINVAL; + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_LIGHT: + ret = isl29028_als_get(chip, val); + break; + case IIO_INTENSITY: + ret = isl29028_ir_get(chip, val); + break; + case IIO_PROXIMITY: + ret = isl29028_read_proxim(chip, val); + break; + default: + break; + } + + if (ret < 0) + break; + + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + if (chan->type != IIO_PROXIMITY) + break; + + *val = chip->prox_sampling_int; + *val2 = chip->prox_sampling_frac; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + if (chan->type != IIO_LIGHT) + break; + *val = chip->lux_scale; + ret = IIO_VAL_INT; + break; + default: + dev_err(dev, "%s(): mask value 0x%08lx is not supported\n", + __func__, mask); + break; + } + + mutex_unlock(&chip->lock); + + if (ret < 0) + return ret; + + /** + * Preserve the ret variable if the call to + * pm_runtime_put_autosuspend() is successful so the reading + * (if applicable) is returned to user space. + */ + pm_ret = pm_runtime_put_autosuspend(dev); + if (pm_ret < 0) + return pm_ret; + + return ret; +} + +static IIO_CONST_ATTR(in_proximity_sampling_frequency_available, + "1.25 2.5 5 10 13.3 20 80 100"); +static IIO_CONST_ATTR(in_illuminance_scale_available, "125 2000"); + +#define ISL29028_CONST_ATTR(name) (&iio_const_attr_##name.dev_attr.attr) +static struct attribute *isl29028_attributes[] = { + ISL29028_CONST_ATTR(in_proximity_sampling_frequency_available), + ISL29028_CONST_ATTR(in_illuminance_scale_available), + NULL, +}; + +static const struct attribute_group isl29108_group = { + .attrs = isl29028_attributes, +}; + +static const struct iio_chan_spec isl29028_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_SCALE), + }, { + .type = IIO_INTENSITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + } +}; + +static const struct iio_info isl29028_info = { + .attrs = &isl29108_group, + .read_raw = isl29028_read_raw, + .write_raw = isl29028_write_raw, +}; + +static int isl29028_clear_configure_reg(struct isl29028_chip *chip) +{ + struct device *dev = regmap_get_device(chip->regmap); + int ret; + + ret = regmap_write(chip->regmap, ISL29028_REG_CONFIGURE, 0x0); + if (ret < 0) + dev_err(dev, "%s(): Error %d clearing the CONFIGURE register\n", + __func__, ret); + + chip->als_ir_mode = ISL29028_MODE_NONE; + chip->enable_prox = false; + + return ret; +} + +static bool isl29028_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ISL29028_REG_INTERRUPT: + case ISL29028_REG_PROX_DATA: + case ISL29028_REG_ALSIR_L: + case ISL29028_REG_ALSIR_U: + return true; + default: + return false; + } +} + +static const struct regmap_config isl29028_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = isl29028_is_volatile_reg, + .max_register = ISL29028_NUM_REGS - 1, + .num_reg_defaults_raw = ISL29028_NUM_REGS, + .cache_type = REGCACHE_MAPLE, +}; + +static int isl29028_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct isl29028_chip *chip; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + + i2c_set_clientdata(client, indio_dev); + mutex_init(&chip->lock); + + chip->regmap = devm_regmap_init_i2c(client, &isl29028_regmap_config); + if (IS_ERR(chip->regmap)) { + ret = PTR_ERR(chip->regmap); + dev_err(&client->dev, "%s: Error %d initializing regmap\n", + __func__, ret); + return ret; + } + + chip->enable_prox = false; + chip->prox_sampling_int = 20; + chip->prox_sampling_frac = 0; + chip->lux_scale = 2000; + + ret = regmap_write(chip->regmap, ISL29028_REG_TEST1_MODE, 0x0); + if (ret < 0) { + dev_err(&client->dev, + "%s(): Error %d writing to TEST1_MODE register\n", + __func__, ret); + return ret; + } + + ret = regmap_write(chip->regmap, ISL29028_REG_TEST2_MODE, 0x0); + if (ret < 0) { + dev_err(&client->dev, + "%s(): Error %d writing to TEST2_MODE register\n", + __func__, ret); + return ret; + } + + ret = isl29028_clear_configure_reg(chip); + if (ret < 0) + return ret; + + indio_dev->info = &isl29028_info; + indio_dev->channels = isl29028_channels; + indio_dev->num_channels = ARRAY_SIZE(isl29028_channels); + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, + ISL29028_POWER_OFF_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, + "%s(): iio registration failed with error %d\n", + __func__, ret); + return ret; + } + + return 0; +} + +static void isl29028_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct isl29028_chip *chip = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + isl29028_clear_configure_reg(chip); +} + +static int isl29028_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct isl29028_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->lock); + + ret = isl29028_clear_configure_reg(chip); + + mutex_unlock(&chip->lock); + + return ret; +} + +static int isl29028_resume(struct device *dev) +{ + /** + * The specific component (ALS/IR or proximity) will enable itself as + * needed the next time that the user requests a reading. This is done + * above in isl29028_set_als_ir_mode() and isl29028_enable_proximity(). + */ + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(isl29028_pm_ops, isl29028_suspend, + isl29028_resume, NULL); + +static const struct i2c_device_id isl29028_id[] = { + { .name = "isl29028" }, + { .name = "isl29030" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, isl29028_id); + +static const struct of_device_id isl29028_of_match[] = { + { .compatible = "isl,isl29028", }, /* for backward compat., don't use */ + { .compatible = "isil,isl29028", }, + { .compatible = "isil,isl29030", }, + { } +}; +MODULE_DEVICE_TABLE(of, isl29028_of_match); + +static struct i2c_driver isl29028_driver = { + .driver = { + .name = "isl29028", + .pm = pm_ptr(&isl29028_pm_ops), + .of_match_table = isl29028_of_match, + }, + .probe = isl29028_probe, + .remove = isl29028_remove, + .id_table = isl29028_id, +}; + +module_i2c_driver(isl29028_driver); + +MODULE_DESCRIPTION("ISL29028 Ambient Light and Proximity Sensor driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Laxman Dewangan "); diff --git a/drivers/iio/light/isl29125.c b/drivers/iio/light/isl29125.c new file mode 100644 index 000000000..9e0b7e6a3 --- /dev/null +++ b/drivers/iio/light/isl29125.c @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * isl29125.c - Support for Intersil ISL29125 RGB light sensor + * + * Copyright (c) 2014 Peter Meerwald + * + * RGB light sensor with 16-bit channels for red, green, blue); + * 7-bit I2C slave address 0x44 + * + * TODO: interrupt support, IR compensation, thresholds, 12bit + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define ISL29125_DRV_NAME "isl29125" + +#define ISL29125_DEVICE_ID 0x00 +#define ISL29125_CONF1 0x01 +#define ISL29125_CONF2 0x02 +#define ISL29125_CONF3 0x03 +#define ISL29125_STATUS 0x08 +#define ISL29125_GREEN_DATA 0x09 +#define ISL29125_RED_DATA 0x0b +#define ISL29125_BLUE_DATA 0x0d + +#define ISL29125_ID 0x7d + +#define ISL29125_MODE_MASK GENMASK(2, 0) +#define ISL29125_MODE_PD 0x0 +#define ISL29125_MODE_G 0x1 +#define ISL29125_MODE_R 0x2 +#define ISL29125_MODE_B 0x3 +#define ISL29125_MODE_RGB 0x5 + +#define ISL29125_SENSING_RANGE_0 5722 /* 375 lux full range */ +#define ISL29125_SENSING_RANGE_1 152590 /* 10k lux full range */ + +#define ISL29125_MODE_RANGE BIT(3) + +#define ISL29125_STATUS_CONV BIT(1) + +struct isl29125_data { + struct i2c_client *client; + u8 conf1; +}; + +#define ISL29125_CHANNEL(_color, _si) { \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .channel2 = IIO_MOD_LIGHT_##_color, \ + .scan_index = _si, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ +} + +static const struct iio_chan_spec isl29125_channels[] = { + ISL29125_CHANNEL(GREEN, 0), + ISL29125_CHANNEL(RED, 1), + ISL29125_CHANNEL(BLUE, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct { + u8 mode, data; +} isl29125_regs[] = { + {ISL29125_MODE_G, ISL29125_GREEN_DATA}, + {ISL29125_MODE_R, ISL29125_RED_DATA}, + {ISL29125_MODE_B, ISL29125_BLUE_DATA}, +}; + +static int isl29125_read_data(struct isl29125_data *data, int si) +{ + int tries = 5; + int ret; + + ret = i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, + data->conf1 | isl29125_regs[si].mode); + if (ret < 0) + return ret; + + msleep(101); + + while (tries--) { + ret = i2c_smbus_read_byte_data(data->client, ISL29125_STATUS); + if (ret < 0) + goto fail; + if (ret & ISL29125_STATUS_CONV) + break; + msleep(20); + } + + if (tries < 0) { + dev_err(&data->client->dev, "data not ready\n"); + ret = -EIO; + goto fail; + } + + ret = i2c_smbus_read_word_data(data->client, isl29125_regs[si].data); + +fail: + i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, data->conf1); + return ret; +} + +static int isl29125_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct isl29125_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = isl29125_read_data(data, chan->scan_index); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + if (data->conf1 & ISL29125_MODE_RANGE) + *val2 = ISL29125_SENSING_RANGE_1; /*10k lux full range*/ + else + *val2 = ISL29125_SENSING_RANGE_0; /*375 lux full range*/ + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static int isl29125_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct isl29125_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (val != 0) + return -EINVAL; + if (val2 == ISL29125_SENSING_RANGE_1) + data->conf1 |= ISL29125_MODE_RANGE; + else if (val2 == ISL29125_SENSING_RANGE_0) + data->conf1 &= ~ISL29125_MODE_RANGE; + else + return -EINVAL; + return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, + data->conf1); + default: + return -EINVAL; + } +} + +static irqreturn_t isl29125_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct isl29125_data *data = iio_priv(indio_dev); + int i, j = 0; + /* Ensure timestamp is naturally aligned */ + struct { + u16 chans[3]; + aligned_s64 timestamp; + } scan = { }; + + iio_for_each_active_channel(indio_dev, i) { + int ret = i2c_smbus_read_word_data(data->client, + isl29125_regs[i].data); + if (ret < 0) + goto done; + + scan.chans[j++] = ret; + } + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static IIO_CONST_ATTR(scale_available, "0.005722 0.152590"); + +static struct attribute *isl29125_attributes[] = { + &iio_const_attr_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group isl29125_attribute_group = { + .attrs = isl29125_attributes, +}; + +static const struct iio_info isl29125_info = { + .read_raw = isl29125_read_raw, + .write_raw = isl29125_write_raw, + .attrs = &isl29125_attribute_group, +}; + +static int isl29125_buffer_postenable(struct iio_dev *indio_dev) +{ + struct isl29125_data *data = iio_priv(indio_dev); + + data->conf1 |= ISL29125_MODE_RGB; + return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, + data->conf1); +} + +static int isl29125_buffer_predisable(struct iio_dev *indio_dev) +{ + struct isl29125_data *data = iio_priv(indio_dev); + + data->conf1 &= ~ISL29125_MODE_MASK; + data->conf1 |= ISL29125_MODE_PD; + return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, + data->conf1); +} + +static const struct iio_buffer_setup_ops isl29125_buffer_setup_ops = { + .postenable = isl29125_buffer_postenable, + .predisable = isl29125_buffer_predisable, +}; + +static int isl29125_probe(struct i2c_client *client) +{ + struct isl29125_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (indio_dev == NULL) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + indio_dev->info = &isl29125_info; + indio_dev->name = ISL29125_DRV_NAME; + indio_dev->channels = isl29125_channels; + indio_dev->num_channels = ARRAY_SIZE(isl29125_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = i2c_smbus_read_byte_data(data->client, ISL29125_DEVICE_ID); + if (ret < 0) + return ret; + if (ret != ISL29125_ID) + return -ENODEV; + + data->conf1 = ISL29125_MODE_PD | ISL29125_MODE_RANGE; + ret = i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, + data->conf1); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(data->client, ISL29125_STATUS, 0); + if (ret < 0) + return ret; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + isl29125_trigger_handler, &isl29125_buffer_setup_ops); + if (ret < 0) + return ret; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto buffer_cleanup; + + return 0; + +buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); + return ret; +} + +static int isl29125_powerdown(struct isl29125_data *data) +{ + return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, + (data->conf1 & ~ISL29125_MODE_MASK) | ISL29125_MODE_PD); +} + +static void isl29125_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + isl29125_powerdown(iio_priv(indio_dev)); +} + +static int isl29125_suspend(struct device *dev) +{ + struct isl29125_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + return isl29125_powerdown(data); +} + +static int isl29125_resume(struct device *dev) +{ + struct isl29125_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, + data->conf1); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(isl29125_pm_ops, isl29125_suspend, + isl29125_resume); + +static const struct i2c_device_id isl29125_id[] = { + { .name = "isl29125" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, isl29125_id); + +static struct i2c_driver isl29125_driver = { + .driver = { + .name = ISL29125_DRV_NAME, + .pm = pm_sleep_ptr(&isl29125_pm_ops), + }, + .probe = isl29125_probe, + .remove = isl29125_remove, + .id_table = isl29125_id, +}; +module_i2c_driver(isl29125_driver); + +MODULE_AUTHOR("Peter Meerwald "); +MODULE_DESCRIPTION("ISL29125 RGB light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/isl76682.c b/drivers/iio/light/isl76682.c new file mode 100644 index 000000000..9b9052d08 --- /dev/null +++ b/drivers/iio/light/isl76682.c @@ -0,0 +1,345 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * IIO driver for the light sensor ISL76682. + * ISL76682 is Ambient Light Sensor + * + * Copyright (c) 2023 Marek Vasut + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define ISL76682_REG_COMMAND 0x00 + +#define ISL76682_COMMAND_EN BIT(7) +#define ISL76682_COMMAND_MODE_CONTINUOUS BIT(6) +#define ISL76682_COMMAND_LIGHT_IR BIT(5) + +#define ISL76682_COMMAND_RANGE_LUX_1K 0x0 +#define ISL76682_COMMAND_RANGE_LUX_4K 0x1 +#define ISL76682_COMMAND_RANGE_LUX_16K 0x2 +#define ISL76682_COMMAND_RANGE_LUX_64K 0x3 +#define ISL76682_COMMAND_RANGE_LUX_MASK GENMASK(1, 0) + +#define ISL76682_REG_ALSIR_L 0x01 + +#define ISL76682_REG_ALSIR_U 0x02 + +#define ISL76682_NUM_REGS (ISL76682_REG_ALSIR_U + 1) + +#define ISL76682_CONV_TIME_MS 100 +#define ISL76682_INT_TIME_US 90000 + +#define ISL76682_ADC_MAX (BIT(16) - 1) + +struct isl76682_chip { + /* + * Lock to synchronize access to device command register + * and the content of range variable below. + */ + struct mutex lock; + struct regmap *regmap; + u8 range; + u8 command; +}; + +struct isl76682_range { + u8 range; + u32 als; + u32 ir; +}; + +static const struct isl76682_range isl76682_range_table[] = { + { ISL76682_COMMAND_RANGE_LUX_1K, 15000, 10500 }, + { ISL76682_COMMAND_RANGE_LUX_4K, 60000, 42000 }, + { ISL76682_COMMAND_RANGE_LUX_16K, 240000, 168000 }, + { ISL76682_COMMAND_RANGE_LUX_64K, 960000, 673000 } +}; + +static int isl76682_get(struct isl76682_chip *chip, bool mode_ir, int *data) +{ + u8 command; + int ret; + + command = ISL76682_COMMAND_EN | ISL76682_COMMAND_MODE_CONTINUOUS | + chip->range; + + if (mode_ir) + command |= ISL76682_COMMAND_LIGHT_IR; + + if (command != chip->command) { + ret = regmap_write(chip->regmap, ISL76682_REG_COMMAND, command); + if (ret) + return ret; + + /* Need to wait for conversion time if ALS/IR mode enabled */ + msleep(ISL76682_CONV_TIME_MS); + + chip->command = command; + } + + ret = regmap_bulk_read(chip->regmap, ISL76682_REG_ALSIR_L, data, 2); + *data &= ISL76682_ADC_MAX; + return ret; +} + +static int isl76682_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct isl76682_chip *chip = iio_priv(indio_dev); + int i; + + if (mask != IIO_CHAN_INFO_SCALE) + return -EINVAL; + + if (val != 0) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(isl76682_range_table); i++) { + if (chan->type == IIO_LIGHT && val2 != isl76682_range_table[i].als) + continue; + if (chan->type == IIO_INTENSITY && val2 != isl76682_range_table[i].ir) + continue; + + scoped_guard(mutex, &chip->lock) + chip->range = isl76682_range_table[i].range; + return 0; + } + + return -EINVAL; +} + +static int isl76682_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct isl76682_chip *chip = iio_priv(indio_dev); + int ret; + int i; + + guard(mutex)(&chip->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_LIGHT: + ret = isl76682_get(chip, false, val); + return (ret < 0) ? ret : IIO_VAL_INT; + case IIO_INTENSITY: + ret = isl76682_get(chip, true, val); + return (ret < 0) ? ret : IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + for (i = 0; i < ARRAY_SIZE(isl76682_range_table); i++) { + if (chip->range != isl76682_range_table[i].range) + continue; + + *val = 0; + switch (chan->type) { + case IIO_LIGHT: + *val2 = isl76682_range_table[i].als; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_INTENSITY: + *val2 = isl76682_range_table[i].ir; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + } + return -EINVAL; + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = ISL76682_INT_TIME_US; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int illuminance_scale_available[] = { + 0, 15000, + 0, 60000, + 0, 240000, + 0, 960000, +}; + +static int intensity_scale_available[] = { + 0, 10500, + 0, 42000, + 0, 168000, + 0, 673000, +}; + +static int isl76682_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, + int *length, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_LIGHT: + *vals = illuminance_scale_available; + *length = ARRAY_SIZE(illuminance_scale_available); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + case IIO_INTENSITY: + *vals = intensity_scale_available; + *length = ARRAY_SIZE(intensity_scale_available); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static const struct iio_chan_spec isl76682_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + }, { + .type = IIO_INTENSITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + } +}; + +static const struct iio_info isl76682_info = { + .read_avail = isl76682_read_avail, + .read_raw = isl76682_read_raw, + .write_raw = isl76682_write_raw, +}; + +static int isl76682_clear_configure_reg(struct isl76682_chip *chip) +{ + struct device *dev = regmap_get_device(chip->regmap); + int ret; + + ret = regmap_write(chip->regmap, ISL76682_REG_COMMAND, 0x0); + if (ret < 0) + dev_err(dev, "Error %d clearing the CONFIGURE register\n", ret); + + /* + * In the success case, the command register was zeroed out. + * + * In the error case, we do not know in which state the command + * register is, so we assume it is zeroed out, so that it would + * be reprogrammed at the next data read out, and at that time + * we hope it would be reprogrammed successfully. That is very + * much a best effort approach. + */ + chip->command = 0; + + return ret; +} + +static void isl76682_reset_action(void *chip) +{ + isl76682_clear_configure_reg(chip); +} + +static bool isl76682_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ISL76682_REG_ALSIR_L: + case ISL76682_REG_ALSIR_U: + return true; + default: + return false; + } +} + +static const struct regmap_config isl76682_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = isl76682_is_volatile_reg, + .max_register = ISL76682_NUM_REGS - 1, + .num_reg_defaults_raw = ISL76682_NUM_REGS, + .cache_type = REGCACHE_FLAT, +}; + +static int isl76682_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct isl76682_chip *chip; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + + mutex_init(&chip->lock); + + chip->regmap = devm_regmap_init_i2c(client, &isl76682_regmap_config); + ret = PTR_ERR_OR_ZERO(chip->regmap); + if (ret) + return dev_err_probe(dev, ret, "Error initializing regmap\n"); + + chip->range = ISL76682_COMMAND_RANGE_LUX_1K; + + ret = isl76682_clear_configure_reg(chip); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(dev, isl76682_reset_action, chip); + if (ret) + return ret; + + indio_dev->info = &isl76682_info; + indio_dev->channels = isl76682_channels; + indio_dev->num_channels = ARRAY_SIZE(isl76682_channels); + indio_dev->name = "isl76682"; + indio_dev->modes = INDIO_DIRECT_MODE; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct i2c_device_id isl76682_id[] = { + { .name = "isl76682" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, isl76682_id); + +static const struct of_device_id isl76682_of_match[] = { + { .compatible = "isil,isl76682" }, + { } +}; +MODULE_DEVICE_TABLE(of, isl76682_of_match); + +static struct i2c_driver isl76682_driver = { + .driver = { + .name = "isl76682", + .of_match_table = isl76682_of_match, + }, + .probe = isl76682_probe, + .id_table = isl76682_id, +}; +module_i2c_driver(isl76682_driver); + +MODULE_DESCRIPTION("ISL76682 Ambient Light Sensor driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Marek Vasut "); diff --git a/drivers/iio/light/jsa1212.c b/drivers/iio/light/jsa1212.c new file mode 100644 index 000000000..038ee4972 --- /dev/null +++ b/drivers/iio/light/jsa1212.c @@ -0,0 +1,449 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * JSA1212 Ambient Light & Proximity Sensor Driver + * + * Copyright (c) 2014, Intel Corporation. + * + * JSA1212 I2C slave address: 0x44(ADDR tied to GND), 0x45(ADDR tied to VDD) + * + * TODO: Interrupt support, thresholds, range support. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* JSA1212 reg address */ +#define JSA1212_CONF_REG 0x01 +#define JSA1212_INT_REG 0x02 +#define JSA1212_PXS_LT_REG 0x03 +#define JSA1212_PXS_HT_REG 0x04 +#define JSA1212_ALS_TH1_REG 0x05 +#define JSA1212_ALS_TH2_REG 0x06 +#define JSA1212_ALS_TH3_REG 0x07 +#define JSA1212_PXS_DATA_REG 0x08 +#define JSA1212_ALS_DT1_REG 0x09 +#define JSA1212_ALS_DT2_REG 0x0A +#define JSA1212_ALS_RNG_REG 0x0B +#define JSA1212_MAX_REG 0x0C + +/* JSA1212 reg masks */ +#define JSA1212_CONF_MASK 0xFF +#define JSA1212_INT_MASK 0xFF +#define JSA1212_PXS_LT_MASK 0xFF +#define JSA1212_PXS_HT_MASK 0xFF +#define JSA1212_ALS_TH1_MASK 0xFF +#define JSA1212_ALS_TH2_LT_MASK 0x0F +#define JSA1212_ALS_TH2_HT_MASK 0xF0 +#define JSA1212_ALS_TH3_MASK 0xFF +#define JSA1212_PXS_DATA_MASK 0xFF +#define JSA1212_ALS_DATA_MASK 0x0FFF +#define JSA1212_ALS_DT1_MASK 0xFF +#define JSA1212_ALS_DT2_MASK 0x0F +#define JSA1212_ALS_RNG_MASK 0x07 + +/* JSA1212 CONF REG bits */ +#define JSA1212_CONF_PXS_MASK 0x80 +#define JSA1212_CONF_PXS_ENABLE 0x80 +#define JSA1212_CONF_PXS_DISABLE 0x00 +#define JSA1212_CONF_ALS_MASK 0x04 +#define JSA1212_CONF_ALS_ENABLE 0x04 +#define JSA1212_CONF_ALS_DISABLE 0x00 +#define JSA1212_CONF_IRDR_MASK 0x08 +/* Proxmity sensing IRDR current sink settings */ +#define JSA1212_CONF_IRDR_200MA 0x08 +#define JSA1212_CONF_IRDR_100MA 0x00 +#define JSA1212_CONF_PXS_SLP_MASK 0x70 +#define JSA1212_CONF_PXS_SLP_0MS 0x70 +#define JSA1212_CONF_PXS_SLP_12MS 0x60 +#define JSA1212_CONF_PXS_SLP_50MS 0x50 +#define JSA1212_CONF_PXS_SLP_75MS 0x40 +#define JSA1212_CONF_PXS_SLP_100MS 0x30 +#define JSA1212_CONF_PXS_SLP_200MS 0x20 +#define JSA1212_CONF_PXS_SLP_400MS 0x10 +#define JSA1212_CONF_PXS_SLP_800MS 0x00 + +/* JSA1212 INT REG bits */ +#define JSA1212_INT_CTRL_MASK 0x01 +#define JSA1212_INT_CTRL_EITHER 0x00 +#define JSA1212_INT_CTRL_BOTH 0x01 +#define JSA1212_INT_ALS_PRST_MASK 0x06 +#define JSA1212_INT_ALS_PRST_1CONV 0x00 +#define JSA1212_INT_ALS_PRST_4CONV 0x02 +#define JSA1212_INT_ALS_PRST_8CONV 0x04 +#define JSA1212_INT_ALS_PRST_16CONV 0x06 +#define JSA1212_INT_ALS_FLAG_MASK 0x08 +#define JSA1212_INT_ALS_FLAG_CLR 0x00 +#define JSA1212_INT_PXS_PRST_MASK 0x60 +#define JSA1212_INT_PXS_PRST_1CONV 0x00 +#define JSA1212_INT_PXS_PRST_4CONV 0x20 +#define JSA1212_INT_PXS_PRST_8CONV 0x40 +#define JSA1212_INT_PXS_PRST_16CONV 0x60 +#define JSA1212_INT_PXS_FLAG_MASK 0x80 +#define JSA1212_INT_PXS_FLAG_CLR 0x00 + +/* JSA1212 ALS RNG REG bits */ +#define JSA1212_ALS_RNG_0_2048 0x00 +#define JSA1212_ALS_RNG_0_1024 0x01 +#define JSA1212_ALS_RNG_0_512 0x02 +#define JSA1212_ALS_RNG_0_256 0x03 +#define JSA1212_ALS_RNG_0_128 0x04 + +/* JSA1212 INT threshold range */ +#define JSA1212_ALS_TH_MIN 0x0000 +#define JSA1212_ALS_TH_MAX 0x0FFF +#define JSA1212_PXS_TH_MIN 0x00 +#define JSA1212_PXS_TH_MAX 0xFF + +#define JSA1212_ALS_DELAY_MS 200 +#define JSA1212_PXS_DELAY_MS 100 + +#define JSA1212_DRIVER_NAME "jsa1212" + +enum jsa1212_op_mode { + JSA1212_OPMODE_ALS_EN, + JSA1212_OPMODE_PXS_EN, +}; + +struct jsa1212_data { + struct i2c_client *client; + struct mutex lock; + u8 als_rng_idx; + bool als_en; /* ALS enable status */ + bool pxs_en; /* proximity enable status */ + struct regmap *regmap; +}; + +/* ALS range idx to val mapping */ +static const int jsa1212_als_range_val[] = {2048, 1024, 512, 256, 128, + 128, 128, 128}; + +/* Enables or disables ALS function based on status */ +static int jsa1212_als_enable(struct jsa1212_data *data, u8 status) +{ + int ret; + + ret = regmap_update_bits(data->regmap, JSA1212_CONF_REG, + JSA1212_CONF_ALS_MASK, + status); + if (ret < 0) + return ret; + + data->als_en = !!status; + + return 0; +} + +/* Enables or disables PXS function based on status */ +static int jsa1212_pxs_enable(struct jsa1212_data *data, u8 status) +{ + int ret; + + ret = regmap_update_bits(data->regmap, JSA1212_CONF_REG, + JSA1212_CONF_PXS_MASK, + status); + if (ret < 0) + return ret; + + data->pxs_en = !!status; + + return 0; +} + +static int jsa1212_read_als_data(struct jsa1212_data *data, + unsigned int *val) +{ + int ret; + __le16 als_data; + + ret = jsa1212_als_enable(data, JSA1212_CONF_ALS_ENABLE); + if (ret < 0) + return ret; + + /* Delay for data output */ + msleep(JSA1212_ALS_DELAY_MS); + + /* Read 12 bit data */ + ret = regmap_bulk_read(data->regmap, JSA1212_ALS_DT1_REG, &als_data, 2); + if (ret < 0) { + dev_err(&data->client->dev, "als data read err\n"); + goto als_data_read_err; + } + + *val = le16_to_cpu(als_data); + +als_data_read_err: + return jsa1212_als_enable(data, JSA1212_CONF_ALS_DISABLE); +} + +static int jsa1212_read_pxs_data(struct jsa1212_data *data, + unsigned int *val) +{ + int ret; + unsigned int pxs_data; + + ret = jsa1212_pxs_enable(data, JSA1212_CONF_PXS_ENABLE); + if (ret < 0) + return ret; + + /* Delay for data output */ + msleep(JSA1212_PXS_DELAY_MS); + + /* Read out all data */ + ret = regmap_read(data->regmap, JSA1212_PXS_DATA_REG, &pxs_data); + if (ret < 0) { + dev_err(&data->client->dev, "pxs data read err\n"); + goto pxs_data_read_err; + } + + *val = pxs_data & JSA1212_PXS_DATA_MASK; + +pxs_data_read_err: + return jsa1212_pxs_enable(data, JSA1212_CONF_PXS_DISABLE); +} + +static int jsa1212_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct jsa1212_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&data->lock); + switch (chan->type) { + case IIO_LIGHT: + ret = jsa1212_read_als_data(data, val); + break; + case IIO_PROXIMITY: + ret = jsa1212_read_pxs_data(data, val); + break; + default: + ret = -EINVAL; + break; + } + mutex_unlock(&data->lock); + return ret < 0 ? ret : IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_LIGHT: + *val = jsa1212_als_range_val[data->als_rng_idx]; + *val2 = BIT(12); /* Max 12 bit value */ + return IIO_VAL_FRACTIONAL; + default: + break; + } + break; + default: + break; + } + + return -EINVAL; +} + +static const struct iio_chan_spec jsa1212_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + } +}; + +static const struct iio_info jsa1212_info = { + .read_raw = &jsa1212_read_raw, +}; + +static int jsa1212_chip_init(struct jsa1212_data *data) +{ + int ret; + + ret = regmap_write(data->regmap, JSA1212_CONF_REG, + (JSA1212_CONF_PXS_SLP_50MS | + JSA1212_CONF_IRDR_200MA)); + if (ret < 0) + return ret; + + ret = regmap_write(data->regmap, JSA1212_INT_REG, + JSA1212_INT_ALS_PRST_4CONV); + if (ret < 0) + return ret; + + data->als_rng_idx = JSA1212_ALS_RNG_0_2048; + + return 0; +} + +static bool jsa1212_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case JSA1212_PXS_DATA_REG: + case JSA1212_ALS_DT1_REG: + case JSA1212_ALS_DT2_REG: + case JSA1212_INT_REG: + return true; + default: + return false; + } +} + +static const struct regmap_config jsa1212_regmap_config = { + .name = "jsa1212_regmap", + .reg_bits = 8, + .val_bits = 8, + .max_register = JSA1212_MAX_REG, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = jsa1212_is_volatile_reg, +}; + +static int jsa1212_probe(struct i2c_client *client) +{ + struct jsa1212_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, &jsa1212_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Regmap initialization failed.\n"); + return PTR_ERR(regmap); + } + + data = iio_priv(indio_dev); + + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->regmap = regmap; + + mutex_init(&data->lock); + + ret = jsa1212_chip_init(data); + if (ret < 0) + return ret; + + indio_dev->channels = jsa1212_channels; + indio_dev->num_channels = ARRAY_SIZE(jsa1212_channels); + indio_dev->name = JSA1212_DRIVER_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + indio_dev->info = &jsa1212_info; + + ret = iio_device_register(indio_dev); + if (ret < 0) + dev_err(&client->dev, "%s: register device failed\n", __func__); + + return ret; +} + + /* power off the device */ +static int jsa1212_power_off(struct jsa1212_data *data) +{ + int ret; + + mutex_lock(&data->lock); + + ret = regmap_update_bits(data->regmap, JSA1212_CONF_REG, + JSA1212_CONF_ALS_MASK | + JSA1212_CONF_PXS_MASK, + JSA1212_CONF_ALS_DISABLE | + JSA1212_CONF_PXS_DISABLE); + + if (ret < 0) + dev_err(&data->client->dev, "power off cmd failed\n"); + + mutex_unlock(&data->lock); + + return ret; +} + +static void jsa1212_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct jsa1212_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + jsa1212_power_off(data); +} + +static int jsa1212_suspend(struct device *dev) +{ + struct jsa1212_data *data; + + data = iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + return jsa1212_power_off(data); +} + +static int jsa1212_resume(struct device *dev) +{ + int ret = 0; + struct jsa1212_data *data; + + data = iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + mutex_lock(&data->lock); + + if (data->als_en) { + ret = jsa1212_als_enable(data, JSA1212_CONF_ALS_ENABLE); + if (ret < 0) { + dev_err(dev, "als resume failed\n"); + goto unlock_and_ret; + } + } + + if (data->pxs_en) { + ret = jsa1212_pxs_enable(data, JSA1212_CONF_PXS_ENABLE); + if (ret < 0) + dev_err(dev, "pxs resume failed\n"); + } + +unlock_and_ret: + mutex_unlock(&data->lock); + return ret; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(jsa1212_pm_ops, jsa1212_suspend, + jsa1212_resume); + +static const struct acpi_device_id jsa1212_acpi_match[] = { + {"JSA1212", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, jsa1212_acpi_match); + +static const struct i2c_device_id jsa1212_id[] = { + { .name = JSA1212_DRIVER_NAME }, + { } +}; +MODULE_DEVICE_TABLE(i2c, jsa1212_id); + +static struct i2c_driver jsa1212_driver = { + .driver = { + .name = JSA1212_DRIVER_NAME, + .pm = pm_sleep_ptr(&jsa1212_pm_ops), + .acpi_match_table = jsa1212_acpi_match, + }, + .probe = jsa1212_probe, + .remove = jsa1212_remove, + .id_table = jsa1212_id, +}; +module_i2c_driver(jsa1212_driver); + +MODULE_AUTHOR("Sathya Kuppuswamy "); +MODULE_DESCRIPTION("JSA1212 proximity/ambient light sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/lm3533-als.c b/drivers/iio/light/lm3533-als.c new file mode 100644 index 000000000..99f0b9030 --- /dev/null +++ b/drivers/iio/light/lm3533-als.c @@ -0,0 +1,922 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * lm3533-als.c -- LM3533 Ambient Light Sensor driver + * + * Copyright (C) 2011-2012 Texas Instruments + * + * Author: Johan Hovold + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +#define LM3533_ALS_RESISTOR_MIN 1 +#define LM3533_ALS_RESISTOR_MAX 127 +#define LM3533_ALS_CHANNEL_CURRENT_MAX 2 +#define LM3533_ALS_THRESH_MAX 3 +#define LM3533_ALS_ZONE_MAX 4 + +#define LM3533_REG_ALS_RESISTOR_SELECT 0x30 +#define LM3533_REG_ALS_CONF 0x31 +#define LM3533_REG_ALS_ZONE_INFO 0x34 +#define LM3533_REG_ALS_READ_ADC_RAW 0x37 +#define LM3533_REG_ALS_READ_ADC_AVERAGE 0x38 +#define LM3533_REG_ALS_BOUNDARY_BASE 0x50 +#define LM3533_REG_ALS_TARGET_BASE 0x60 + +#define LM3533_ALS_ENABLE_MASK 0x01 +#define LM3533_ALS_INPUT_MODE_MASK 0x02 +#define LM3533_ALS_INT_ENABLE_MASK 0x01 + +#define LM3533_ALS_ZONE_SHIFT 2 +#define LM3533_ALS_ZONE_MASK 0x1c + +#define LM3533_ALS_FLAG_INT_ENABLED 1 + + +struct lm3533_als { + struct lm3533 *lm3533; + struct platform_device *pdev; + + unsigned long flags; + int irq; + + atomic_t zone; + struct mutex thresh_mutex; +}; + + +static int lm3533_als_get_adc(struct iio_dev *indio_dev, bool average, + int *adc) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 reg; + u8 val; + int ret; + + if (average) + reg = LM3533_REG_ALS_READ_ADC_AVERAGE; + else + reg = LM3533_REG_ALS_READ_ADC_RAW; + + ret = lm3533_read(als->lm3533, reg, &val); + if (ret) { + dev_err(&indio_dev->dev, "failed to read adc\n"); + return ret; + } + + *adc = val; + + return 0; +} + +static int _lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 val; + int ret; + + ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val); + if (ret) { + dev_err(&indio_dev->dev, "failed to read zone\n"); + return ret; + } + + val = (val & LM3533_ALS_ZONE_MASK) >> LM3533_ALS_ZONE_SHIFT; + *zone = min_t(u8, val, LM3533_ALS_ZONE_MAX); + + return 0; +} + +static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone) +{ + struct lm3533_als *als = iio_priv(indio_dev); + int ret; + + if (test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags)) { + *zone = atomic_read(&als->zone); + } else { + ret = _lm3533_als_get_zone(indio_dev, zone); + if (ret) + return ret; + } + + return 0; +} + +/* + * channel output channel 0..2 + * zone zone 0..4 + */ +static inline u8 lm3533_als_get_target_reg(unsigned channel, unsigned zone) +{ + return LM3533_REG_ALS_TARGET_BASE + 5 * channel + zone; +} + +static int lm3533_als_get_target(struct iio_dev *indio_dev, unsigned channel, + unsigned zone, u8 *val) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 reg; + int ret; + + if (channel > LM3533_ALS_CHANNEL_CURRENT_MAX) + return -EINVAL; + + if (zone > LM3533_ALS_ZONE_MAX) + return -EINVAL; + + reg = lm3533_als_get_target_reg(channel, zone); + ret = lm3533_read(als->lm3533, reg, val); + if (ret) + dev_err(&indio_dev->dev, "failed to get target current\n"); + + return ret; +} + +static int lm3533_als_set_target(struct iio_dev *indio_dev, unsigned channel, + unsigned zone, u8 val) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 reg; + int ret; + + if (channel > LM3533_ALS_CHANNEL_CURRENT_MAX) + return -EINVAL; + + if (zone > LM3533_ALS_ZONE_MAX) + return -EINVAL; + + reg = lm3533_als_get_target_reg(channel, zone); + ret = lm3533_write(als->lm3533, reg, val); + if (ret) + dev_err(&indio_dev->dev, "failed to set target current\n"); + + return ret; +} + +static int lm3533_als_get_current(struct iio_dev *indio_dev, unsigned channel, + int *val) +{ + u8 zone; + u8 target; + int ret; + + ret = lm3533_als_get_zone(indio_dev, &zone); + if (ret) + return ret; + + ret = lm3533_als_get_target(indio_dev, channel, zone, &target); + if (ret) + return ret; + + *val = target; + + return 0; +} + +static int lm3533_als_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_LIGHT: + ret = lm3533_als_get_adc(indio_dev, false, val); + break; + case IIO_CURRENT: + ret = lm3533_als_get_current(indio_dev, chan->channel, + val); + break; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_AVERAGE_RAW: + ret = lm3533_als_get_adc(indio_dev, true, val); + break; + default: + return -EINVAL; + } + + if (ret) + return ret; + + return IIO_VAL_INT; +} + +#define CHANNEL_CURRENT(_channel) \ + { \ + .type = IIO_CURRENT, \ + .channel = _channel, \ + .indexed = true, \ + .output = true, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + } + +static const struct iio_chan_spec lm3533_als_channels[] = { + { + .type = IIO_LIGHT, + .channel = 0, + .indexed = true, + .info_mask_separate = BIT(IIO_CHAN_INFO_AVERAGE_RAW) | + BIT(IIO_CHAN_INFO_RAW), + }, + CHANNEL_CURRENT(0), + CHANNEL_CURRENT(1), + CHANNEL_CURRENT(2), +}; + +static irqreturn_t lm3533_als_isr(int irq, void *dev_id) +{ + + struct iio_dev *indio_dev = dev_id; + struct lm3533_als *als = iio_priv(indio_dev); + u8 zone; + int ret; + + /* Clear interrupt by reading the ALS zone register. */ + ret = _lm3533_als_get_zone(indio_dev, &zone); + if (ret) + goto out; + + atomic_set(&als->zone, zone); + + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); +out: + return IRQ_HANDLED; +} + +static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 mask = LM3533_ALS_INT_ENABLE_MASK; + u8 val; + int ret; + + if (enable) + val = mask; + else + val = 0; + + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask); + if (ret) { + dev_err(&indio_dev->dev, "failed to set int mode %d\n", + enable); + return ret; + } + + return 0; +} + +static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 mask = LM3533_ALS_INT_ENABLE_MASK; + u8 val; + int ret; + + ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val); + if (ret) { + dev_err(&indio_dev->dev, "failed to get int mode\n"); + return ret; + } + + *enable = !!(val & mask); + + return 0; +} + +static inline u8 lm3533_als_get_threshold_reg(unsigned nr, bool raising) +{ + u8 offset = !raising; + + return LM3533_REG_ALS_BOUNDARY_BASE + 2 * nr + offset; +} + +static int lm3533_als_get_threshold(struct iio_dev *indio_dev, unsigned nr, + bool raising, u8 *val) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 reg; + int ret; + + if (nr > LM3533_ALS_THRESH_MAX) + return -EINVAL; + + reg = lm3533_als_get_threshold_reg(nr, raising); + ret = lm3533_read(als->lm3533, reg, val); + if (ret) + dev_err(&indio_dev->dev, "failed to get threshold\n"); + + return ret; +} + +static int lm3533_als_set_threshold(struct iio_dev *indio_dev, unsigned nr, + bool raising, u8 val) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 val2; + u8 reg, reg2; + int ret; + + if (nr > LM3533_ALS_THRESH_MAX) + return -EINVAL; + + reg = lm3533_als_get_threshold_reg(nr, raising); + reg2 = lm3533_als_get_threshold_reg(nr, !raising); + + mutex_lock(&als->thresh_mutex); + ret = lm3533_read(als->lm3533, reg2, &val2); + if (ret) { + dev_err(&indio_dev->dev, "failed to get threshold\n"); + goto out; + } + /* + * This device does not allow negative hysteresis (in fact, it uses + * whichever value is smaller as the lower bound) so we need to make + * sure that thresh_falling <= thresh_raising. + */ + if ((raising && (val < val2)) || (!raising && (val > val2))) { + ret = -EINVAL; + goto out; + } + + ret = lm3533_write(als->lm3533, reg, val); + if (ret) { + dev_err(&indio_dev->dev, "failed to set threshold\n"); + goto out; + } +out: + mutex_unlock(&als->thresh_mutex); + + return ret; +} + +static int lm3533_als_get_hysteresis(struct iio_dev *indio_dev, unsigned nr, + u8 *val) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 falling; + u8 raising; + int ret; + + if (nr > LM3533_ALS_THRESH_MAX) + return -EINVAL; + + mutex_lock(&als->thresh_mutex); + ret = lm3533_als_get_threshold(indio_dev, nr, false, &falling); + if (ret) + goto out; + ret = lm3533_als_get_threshold(indio_dev, nr, true, &raising); + if (ret) + goto out; + + *val = raising - falling; +out: + mutex_unlock(&als->thresh_mutex); + + return ret; +} + +static ssize_t show_thresh_either_en(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct lm3533_als *als = iio_priv(indio_dev); + int enable; + int ret; + + if (als->irq) { + ret = lm3533_als_get_int_mode(indio_dev, &enable); + if (ret) + return ret; + } else { + enable = 0; + } + + return sysfs_emit(buf, "%u\n", enable); +} + +static ssize_t store_thresh_either_en(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct lm3533_als *als = iio_priv(indio_dev); + unsigned long enable; + bool int_enabled; + u8 zone; + int ret; + + if (!als->irq) + return -EBUSY; + + if (kstrtoul(buf, 0, &enable)) + return -EINVAL; + + int_enabled = test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags); + + if (enable && !int_enabled) { + ret = lm3533_als_get_zone(indio_dev, &zone); + if (ret) + return ret; + + atomic_set(&als->zone, zone); + + set_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags); + } + + ret = lm3533_als_set_int_mode(indio_dev, enable); + if (ret) { + if (!int_enabled) + clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags); + + return ret; + } + + if (!enable) + clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags); + + return len; +} + +static ssize_t show_zone(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + u8 zone; + int ret; + + ret = lm3533_als_get_zone(indio_dev, &zone); + if (ret) + return ret; + + return sysfs_emit(buf, "%u\n", zone); +} + +enum lm3533_als_attribute_type { + LM3533_ATTR_TYPE_HYSTERESIS, + LM3533_ATTR_TYPE_TARGET, + LM3533_ATTR_TYPE_THRESH_FALLING, + LM3533_ATTR_TYPE_THRESH_RAISING, +}; + +struct lm3533_als_attribute { + struct device_attribute dev_attr; + enum lm3533_als_attribute_type type; + u8 val1; + u8 val2; +}; + +static inline struct lm3533_als_attribute * +to_lm3533_als_attr(struct device_attribute *attr) +{ + return container_of(attr, struct lm3533_als_attribute, dev_attr); +} + +static ssize_t show_als_attr(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr); + u8 val; + int ret; + + switch (als_attr->type) { + case LM3533_ATTR_TYPE_HYSTERESIS: + ret = lm3533_als_get_hysteresis(indio_dev, als_attr->val1, + &val); + break; + case LM3533_ATTR_TYPE_TARGET: + ret = lm3533_als_get_target(indio_dev, als_attr->val1, + als_attr->val2, &val); + break; + case LM3533_ATTR_TYPE_THRESH_FALLING: + ret = lm3533_als_get_threshold(indio_dev, als_attr->val1, + false, &val); + break; + case LM3533_ATTR_TYPE_THRESH_RAISING: + ret = lm3533_als_get_threshold(indio_dev, als_attr->val1, + true, &val); + break; + default: + ret = -ENXIO; + } + + if (ret) + return ret; + + return sysfs_emit(buf, "%u\n", val); +} + +static ssize_t store_als_attr(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr); + u8 val; + int ret; + + if (kstrtou8(buf, 0, &val)) + return -EINVAL; + + switch (als_attr->type) { + case LM3533_ATTR_TYPE_TARGET: + ret = lm3533_als_set_target(indio_dev, als_attr->val1, + als_attr->val2, val); + break; + case LM3533_ATTR_TYPE_THRESH_FALLING: + ret = lm3533_als_set_threshold(indio_dev, als_attr->val1, + false, val); + break; + case LM3533_ATTR_TYPE_THRESH_RAISING: + ret = lm3533_als_set_threshold(indio_dev, als_attr->val1, + true, val); + break; + default: + ret = -ENXIO; + } + + if (ret) + return ret; + + return len; +} + +#define ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) \ + { .dev_attr = __ATTR(_name, _mode, _show, _store), \ + .type = _type, \ + .val1 = _val1, \ + .val2 = _val2 } + +#define LM3533_ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) \ + struct lm3533_als_attribute lm3533_als_attr_##_name = \ + ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) + +#define ALS_TARGET_ATTR_RW(_channel, _zone) \ + LM3533_ALS_ATTR(out_current##_channel##_current##_zone##_raw, \ + S_IRUGO | S_IWUSR, \ + show_als_attr, store_als_attr, \ + LM3533_ATTR_TYPE_TARGET, _channel, _zone) +/* + * ALS output current values (ALS mapper targets) + * + * out_current[0-2]_current[0-4]_raw 0-255 + */ +static ALS_TARGET_ATTR_RW(0, 0); +static ALS_TARGET_ATTR_RW(0, 1); +static ALS_TARGET_ATTR_RW(0, 2); +static ALS_TARGET_ATTR_RW(0, 3); +static ALS_TARGET_ATTR_RW(0, 4); + +static ALS_TARGET_ATTR_RW(1, 0); +static ALS_TARGET_ATTR_RW(1, 1); +static ALS_TARGET_ATTR_RW(1, 2); +static ALS_TARGET_ATTR_RW(1, 3); +static ALS_TARGET_ATTR_RW(1, 4); + +static ALS_TARGET_ATTR_RW(2, 0); +static ALS_TARGET_ATTR_RW(2, 1); +static ALS_TARGET_ATTR_RW(2, 2); +static ALS_TARGET_ATTR_RW(2, 3); +static ALS_TARGET_ATTR_RW(2, 4); + +#define ALS_THRESH_FALLING_ATTR_RW(_nr) \ + LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_falling_value, \ + S_IRUGO | S_IWUSR, \ + show_als_attr, store_als_attr, \ + LM3533_ATTR_TYPE_THRESH_FALLING, _nr, 0) + +#define ALS_THRESH_RAISING_ATTR_RW(_nr) \ + LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_raising_value, \ + S_IRUGO | S_IWUSR, \ + show_als_attr, store_als_attr, \ + LM3533_ATTR_TYPE_THRESH_RAISING, _nr, 0) +/* + * ALS Zone thresholds (boundaries) + * + * in_illuminance0_thresh[0-3]_falling_value 0-255 + * in_illuminance0_thresh[0-3]_raising_value 0-255 + */ +static ALS_THRESH_FALLING_ATTR_RW(0); +static ALS_THRESH_FALLING_ATTR_RW(1); +static ALS_THRESH_FALLING_ATTR_RW(2); +static ALS_THRESH_FALLING_ATTR_RW(3); + +static ALS_THRESH_RAISING_ATTR_RW(0); +static ALS_THRESH_RAISING_ATTR_RW(1); +static ALS_THRESH_RAISING_ATTR_RW(2); +static ALS_THRESH_RAISING_ATTR_RW(3); + +#define ALS_HYSTERESIS_ATTR_RO(_nr) \ + LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_hysteresis, \ + S_IRUGO, show_als_attr, NULL, \ + LM3533_ATTR_TYPE_HYSTERESIS, _nr, 0) +/* + * ALS Zone threshold hysteresis + * + * threshY_hysteresis = threshY_raising - threshY_falling + * + * in_illuminance0_thresh[0-3]_hysteresis 0-255 + * in_illuminance0_thresh[0-3]_hysteresis 0-255 + */ +static ALS_HYSTERESIS_ATTR_RO(0); +static ALS_HYSTERESIS_ATTR_RO(1); +static ALS_HYSTERESIS_ATTR_RO(2); +static ALS_HYSTERESIS_ATTR_RO(3); + +#define ILLUMINANCE_ATTR_RO(_name) \ + DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO, show_##_name, NULL) +#define ILLUMINANCE_ATTR_RW(_name) \ + DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO | S_IWUSR, \ + show_##_name, store_##_name) +/* + * ALS Zone threshold-event enable + * + * in_illuminance0_thresh_either_en 0,1 + */ +static ILLUMINANCE_ATTR_RW(thresh_either_en); + +/* + * ALS Current Zone + * + * in_illuminance0_zone 0-4 + */ +static ILLUMINANCE_ATTR_RO(zone); + +static struct attribute *lm3533_als_event_attributes[] = { + &dev_attr_in_illuminance0_thresh_either_en.attr, + &lm3533_als_attr_in_illuminance0_thresh0_falling_value.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh0_hysteresis.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh0_raising_value.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh1_falling_value.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh1_hysteresis.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh1_raising_value.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh2_falling_value.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh2_hysteresis.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh2_raising_value.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh3_falling_value.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh3_hysteresis.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh3_raising_value.dev_attr.attr, + NULL +}; + +static const struct attribute_group lm3533_als_event_attribute_group = { + .attrs = lm3533_als_event_attributes +}; + +static struct attribute *lm3533_als_attributes[] = { + &dev_attr_in_illuminance0_zone.attr, + &lm3533_als_attr_out_current0_current0_raw.dev_attr.attr, + &lm3533_als_attr_out_current0_current1_raw.dev_attr.attr, + &lm3533_als_attr_out_current0_current2_raw.dev_attr.attr, + &lm3533_als_attr_out_current0_current3_raw.dev_attr.attr, + &lm3533_als_attr_out_current0_current4_raw.dev_attr.attr, + &lm3533_als_attr_out_current1_current0_raw.dev_attr.attr, + &lm3533_als_attr_out_current1_current1_raw.dev_attr.attr, + &lm3533_als_attr_out_current1_current2_raw.dev_attr.attr, + &lm3533_als_attr_out_current1_current3_raw.dev_attr.attr, + &lm3533_als_attr_out_current1_current4_raw.dev_attr.attr, + &lm3533_als_attr_out_current2_current0_raw.dev_attr.attr, + &lm3533_als_attr_out_current2_current1_raw.dev_attr.attr, + &lm3533_als_attr_out_current2_current2_raw.dev_attr.attr, + &lm3533_als_attr_out_current2_current3_raw.dev_attr.attr, + &lm3533_als_attr_out_current2_current4_raw.dev_attr.attr, + NULL +}; + +static const struct attribute_group lm3533_als_attribute_group = { + .attrs = lm3533_als_attributes +}; + +static int lm3533_als_set_input_mode(struct lm3533_als *als, bool pwm_mode) +{ + u8 mask = LM3533_ALS_INPUT_MODE_MASK; + u8 val; + int ret; + + if (pwm_mode) + val = mask; /* pwm input */ + else + val = 0; /* analog input */ + + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, val, mask); + if (ret) { + dev_err(&als->pdev->dev, "failed to set input mode %d\n", + pwm_mode); + return ret; + } + + return 0; +} + +static int lm3533_als_set_resistor(struct lm3533_als *als, u8 val) +{ + int ret; + + if (val < LM3533_ALS_RESISTOR_MIN || val > LM3533_ALS_RESISTOR_MAX) { + dev_err(&als->pdev->dev, "invalid resistor value\n"); + return -EINVAL; + } + + ret = lm3533_write(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val); + if (ret) { + dev_err(&als->pdev->dev, "failed to set resistor\n"); + return ret; + } + + return 0; +} + +static int lm3533_als_setup(struct lm3533_als *als, + const struct lm3533_als_platform_data *pdata) +{ + int ret; + + ret = lm3533_als_set_input_mode(als, pdata->pwm_mode); + if (ret) + return ret; + + /* ALS input is always high impedance in PWM-mode. */ + if (!pdata->pwm_mode) { + ret = lm3533_als_set_resistor(als, pdata->r_select); + if (ret) + return ret; + } + + return 0; +} + +static int lm3533_als_setup_irq(struct lm3533_als *als, void *dev) +{ + u8 mask = LM3533_ALS_INT_ENABLE_MASK; + int ret; + + /* Make sure interrupts are disabled. */ + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, 0, mask); + if (ret) { + dev_err(&als->pdev->dev, "failed to disable interrupts\n"); + return ret; + } + + ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + dev_name(&als->pdev->dev), dev); + if (ret) { + dev_err(&als->pdev->dev, "failed to request irq %d\n", + als->irq); + return ret; + } + + return 0; +} + +static int lm3533_als_enable(struct lm3533_als *als) +{ + u8 mask = LM3533_ALS_ENABLE_MASK; + int ret; + + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask); + if (ret) + dev_err(&als->pdev->dev, "failed to enable ALS\n"); + + return ret; +} + +static int lm3533_als_disable(struct lm3533_als *als) +{ + u8 mask = LM3533_ALS_ENABLE_MASK; + int ret; + + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, 0, mask); + if (ret) + dev_err(&als->pdev->dev, "failed to disable ALS\n"); + + return ret; +} + +static const struct iio_info lm3533_als_info = { + .attrs = &lm3533_als_attribute_group, + .event_attrs = &lm3533_als_event_attribute_group, + .read_raw = &lm3533_als_read_raw, +}; + +static int lm3533_als_probe(struct platform_device *pdev) +{ + const struct lm3533_als_platform_data *pdata; + struct lm3533 *lm3533; + struct lm3533_als *als; + struct iio_dev *indio_dev; + int ret; + + lm3533 = dev_get_drvdata(pdev->dev.parent); + if (!lm3533) + return -EINVAL; + + pdata = dev_get_platdata(&pdev->dev); + if (!pdata) { + dev_err(&pdev->dev, "no platform data\n"); + return -EINVAL; + } + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*als)); + if (!indio_dev) + return -ENOMEM; + + indio_dev->info = &lm3533_als_info; + indio_dev->channels = lm3533_als_channels; + indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels); + indio_dev->name = dev_name(&pdev->dev); + iio_device_set_parent(indio_dev, pdev->dev.parent); + indio_dev->modes = INDIO_DIRECT_MODE; + + als = iio_priv(indio_dev); + als->lm3533 = lm3533; + als->pdev = pdev; + als->irq = lm3533->irq; + atomic_set(&als->zone, 0); + mutex_init(&als->thresh_mutex); + + platform_set_drvdata(pdev, indio_dev); + + if (als->irq) { + ret = lm3533_als_setup_irq(als, indio_dev); + if (ret) + return ret; + } + + ret = lm3533_als_setup(als, pdata); + if (ret) + goto err_free_irq; + + ret = lm3533_als_enable(als); + if (ret) + goto err_free_irq; + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "failed to register ALS\n"); + goto err_disable; + } + + return 0; + +err_disable: + lm3533_als_disable(als); +err_free_irq: + if (als->irq) + free_irq(als->irq, indio_dev); + + return ret; +} + +static void lm3533_als_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct lm3533_als *als = iio_priv(indio_dev); + + lm3533_als_set_int_mode(indio_dev, false); + iio_device_unregister(indio_dev); + lm3533_als_disable(als); + if (als->irq) + free_irq(als->irq, indio_dev); +} + +static struct platform_driver lm3533_als_driver = { + .driver = { + .name = "lm3533-als", + }, + .probe = lm3533_als_probe, + .remove = lm3533_als_remove, +}; +module_platform_driver(lm3533_als_driver); + +MODULE_AUTHOR("Johan Hovold "); +MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:lm3533-als"); diff --git a/drivers/iio/light/ltr390.c b/drivers/iio/light/ltr390.c new file mode 100644 index 000000000..bc031f2c3 --- /dev/null +++ b/drivers/iio/light/ltr390.c @@ -0,0 +1,915 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IIO driver for Lite-On LTR390 ALS and UV sensor + * (7-bit I2C slave address 0x53) + * + * Based on the work of: + * Shreeya Patel and Shi Zhigang (LTRF216 Driver) + * + * Copyright (C) 2023 Anshul Dalal + * + * Datasheet: + * https://optoelectronics.liteon.com/upload/download/DS86-2015-0004/LTR-390UV_Final_%20DS_V1%201.pdf + * + * TODO: + * - Support for configurable gain and resolution + * - Sensor suspend/resume support + * - Add support for reading the ALS + * - Interrupt support + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#define LTR390_MAIN_CTRL 0x00 +#define LTR390_ALS_UVS_MEAS_RATE 0x04 +#define LTR390_ALS_UVS_GAIN 0x05 +#define LTR390_PART_ID 0x06 +#define LTR390_MAIN_STATUS 0x07 + +#define LTR390_ALS_DATA 0x0D +#define LTR390_ALS_DATA_BYTE(n) (LTR390_ALS_DATA + (n)) + +#define LTR390_UVS_DATA 0x10 +#define LTR390_UVS_DATA_BYTE(n) (LTR390_UVS_DATA + (n)) + +#define LTR390_INT_CFG 0x19 +#define LTR390_INT_PST 0x1A + +#define LTR390_THRESH_UP 0x21 +#define LTR390_THRESH_UP_BYTE(n) (LTR390_THRESH_UP + (n)) + +#define LTR390_THRESH_LOW 0x24 +#define LTR390_THRESH_LOW_BYTE(n) (LTR390_THRESH_LOW + (n)) + +#define LTR390_PART_NUMBER_ID 0xb +#define LTR390_ALS_UVS_GAIN_MASK GENMASK(2, 0) +#define LTR390_ALS_UVS_MEAS_RATE_MASK GENMASK(2, 0) +#define LTR390_ALS_UVS_INT_TIME_MASK GENMASK(6, 4) +#define LTR390_ALS_UVS_INT_TIME(x) FIELD_PREP(LTR390_ALS_UVS_INT_TIME_MASK, (x)) +#define LTR390_INT_PST_MASK GENMASK(7, 4) +#define LTR390_INT_PST_VAL(x) FIELD_PREP(LTR390_INT_PST_MASK, (x)) + +#define LTR390_SW_RESET BIT(4) +#define LTR390_UVS_MODE BIT(3) +#define LTR390_SENSOR_ENABLE BIT(1) +#define LTR390_LS_INT_EN BIT(2) +#define LTR390_LS_INT_SEL_UVS BIT(5) + +#define LTR390_FRACTIONAL_PRECISION 100 + +/* + * At 20-bit resolution (integration time: 400ms) and 18x gain, 2300 counts of + * the sensor are equal to 1 UV Index [Datasheet Page#8]. + * + * For the default resolution of 18-bit (integration time: 100ms) and default + * gain of 3x, the counts/uvi are calculated as follows: + * 2300 / ((3/18) * (100/400)) = 95.83 + */ +#define LTR390_COUNTS_PER_UVI 96 + +/* + * Window Factor is needed when the device is under Window glass with coated + * tinted ink. This is to compensate for the light loss due to the lower + * transmission rate of the window glass and helps * in calculating lux. + */ +#define LTR390_WINDOW_FACTOR 1 + +enum ltr390_mode { + LTR390_SET_ALS_MODE, + LTR390_SET_UVS_MODE, +}; + +enum ltr390_meas_rate { + LTR390_GET_FREQ, + LTR390_GET_PERIOD, +}; + +struct ltr390_data { + struct regmap *regmap; + struct i2c_client *client; + /* Protects device from simultaneous reads */ + struct mutex lock; + enum ltr390_mode mode; + int gain; + int int_time_us; + bool irq_enabled; +}; + +static const struct regmap_range ltr390_readable_reg_ranges[] = { + regmap_reg_range(LTR390_MAIN_CTRL, LTR390_MAIN_CTRL), + regmap_reg_range(LTR390_ALS_UVS_MEAS_RATE, LTR390_MAIN_STATUS), + regmap_reg_range(LTR390_ALS_DATA_BYTE(0), LTR390_UVS_DATA_BYTE(2)), + regmap_reg_range(LTR390_INT_CFG, LTR390_INT_PST), + regmap_reg_range(LTR390_THRESH_UP_BYTE(0), LTR390_THRESH_LOW_BYTE(2)), +}; + +static const struct regmap_access_table ltr390_readable_reg_table = { + .yes_ranges = ltr390_readable_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(ltr390_readable_reg_ranges), +}; + +static const struct regmap_range ltr390_writeable_reg_ranges[] = { + regmap_reg_range(LTR390_MAIN_CTRL, LTR390_MAIN_CTRL), + regmap_reg_range(LTR390_ALS_UVS_MEAS_RATE, LTR390_ALS_UVS_GAIN), + regmap_reg_range(LTR390_INT_CFG, LTR390_INT_PST), + regmap_reg_range(LTR390_THRESH_UP_BYTE(0), LTR390_THRESH_LOW_BYTE(2)), +}; + +static const struct regmap_access_table ltr390_writeable_reg_table = { + .yes_ranges = ltr390_writeable_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(ltr390_writeable_reg_ranges), +}; + +static const struct regmap_config ltr390_regmap_config = { + .name = "ltr390", + .reg_bits = 8, + .reg_stride = 1, + .val_bits = 8, + .max_register = LTR390_THRESH_LOW_BYTE(2), + .rd_table = <r390_readable_reg_table, + .wr_table = <r390_writeable_reg_table, +}; + +/* Sampling frequency is in mili Hz and mili Seconds */ +static const int ltr390_samp_freq_table[][2] = { + [0] = { 40000, 25 }, + [1] = { 20000, 50 }, + [2] = { 10000, 100 }, + [3] = { 5000, 200 }, + [4] = { 2000, 500 }, + [5] = { 1000, 1000 }, + [6] = { 500, 2000 }, + [7] = { 500, 2000 }, +}; + +static int ltr390_register_read(struct ltr390_data *data, u8 register_address) +{ + struct device *dev = &data->client->dev; + int ret; + u8 receive_buffer[3]; + + ret = regmap_bulk_read(data->regmap, register_address, receive_buffer, + sizeof(receive_buffer)); + if (ret) { + dev_err(dev, "failed to read measurement data"); + return ret; + } + + return get_unaligned_le24(receive_buffer); +} + +static int ltr390_set_mode(struct ltr390_data *data, enum ltr390_mode mode) +{ + int ret; + + if (data->mode == mode) + return 0; + + switch (mode) { + case LTR390_SET_ALS_MODE: + ret = regmap_clear_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_UVS_MODE); + break; + + case LTR390_SET_UVS_MODE: + ret = regmap_set_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_UVS_MODE); + break; + } + + if (ret) + return ret; + + data->mode = mode; + return 0; +} + +static int ltr390_counts_per_uvi(struct ltr390_data *data) +{ + const int orig_gain = 18; + const int orig_int_time = 400; + + return DIV_ROUND_CLOSEST(23 * data->gain * data->int_time_us, 10 * orig_gain * orig_int_time); +} + +static int ltr390_get_samp_freq_or_period(struct ltr390_data *data, + enum ltr390_meas_rate option) +{ + int ret, value; + + ret = regmap_read(data->regmap, LTR390_ALS_UVS_MEAS_RATE, &value); + if (ret < 0) + return ret; + value = FIELD_GET(LTR390_ALS_UVS_MEAS_RATE_MASK, value); + + return ltr390_samp_freq_table[value][option]; +} + + +static int ltr390_do_read_raw(struct iio_dev *iio_device, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int ret; + struct ltr390_data *data = iio_priv(iio_device); + + guard(mutex)(&data->lock); + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_UVINDEX: + ret = ltr390_set_mode(data, LTR390_SET_UVS_MODE); + if (ret < 0) + return ret; + + ret = ltr390_register_read(data, LTR390_UVS_DATA); + if (ret < 0) + return ret; + break; + + case IIO_LIGHT: + ret = ltr390_set_mode(data, LTR390_SET_ALS_MODE); + if (ret < 0) + return ret; + + ret = ltr390_register_read(data, LTR390_ALS_DATA); + if (ret < 0) + return ret; + break; + + default: + return -EINVAL; + } + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_UVINDEX: + *val = LTR390_WINDOW_FACTOR * LTR390_FRACTIONAL_PRECISION; + *val2 = ltr390_counts_per_uvi(data); + return IIO_VAL_FRACTIONAL; + + case IIO_LIGHT: + *val = LTR390_WINDOW_FACTOR * 6 * 100; + *val2 = data->gain * data->int_time_us; + return IIO_VAL_FRACTIONAL; + + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_INT_TIME: + *val = data->int_time_us; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = ltr390_get_samp_freq_or_period(data, LTR390_GET_FREQ); + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int ltr390_read_raw(struct iio_dev *iio_device, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct ltr390_data *data = iio_priv(iio_device); + struct device *dev = &data->client->dev; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + dev_err(dev, "runtime PM failed to resume: %d\n", ret); + return ret; + } + + ret = ltr390_do_read_raw(iio_device, chan, val, val2, mask); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +/* integration time in us */ +static const int ltr390_int_time_map_us[] = { 400000, 200000, 100000, 50000, 25000, 12500 }; +static const int ltr390_gain_map[] = { 1, 3, 6, 9, 18 }; +static const int ltr390_freq_map[] = { 40000, 20000, 10000, 5000, 2000, 1000, 500, 500 }; + +static const struct iio_event_spec ltr390_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_PERIOD), + } +}; + +static const struct iio_chan_spec ltr390_channels[] = { + /* UV sensor */ + { + .type = IIO_UVINDEX, + .scan_index = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .event_spec = ltr390_event_spec, + .num_event_specs = ARRAY_SIZE(ltr390_event_spec), + }, + /* ALS sensor */ + { + .type = IIO_LIGHT, + .scan_index = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .event_spec = ltr390_event_spec, + .num_event_specs = ARRAY_SIZE(ltr390_event_spec), + }, +}; + +static int ltr390_set_gain(struct ltr390_data *data, int val) +{ + int ret, idx; + + for (idx = 0; idx < ARRAY_SIZE(ltr390_gain_map); idx++) { + if (ltr390_gain_map[idx] != val) + continue; + + guard(mutex)(&data->lock); + ret = regmap_update_bits(data->regmap, + LTR390_ALS_UVS_GAIN, + LTR390_ALS_UVS_GAIN_MASK, idx); + if (ret) + return ret; + + data->gain = ltr390_gain_map[idx]; + return 0; + } + + return -EINVAL; +} + +static int ltr390_set_int_time(struct ltr390_data *data, int val) +{ + int ret, idx; + + for (idx = 0; idx < ARRAY_SIZE(ltr390_int_time_map_us); idx++) { + if (ltr390_int_time_map_us[idx] != val) + continue; + + guard(mutex)(&data->lock); + ret = regmap_update_bits(data->regmap, + LTR390_ALS_UVS_MEAS_RATE, + LTR390_ALS_UVS_INT_TIME_MASK, + LTR390_ALS_UVS_INT_TIME(idx)); + if (ret) + return ret; + + data->int_time_us = ltr390_int_time_map_us[idx]; + return 0; + } + + return -EINVAL; +} + +static int ltr390_set_samp_freq(struct ltr390_data *data, int val) +{ + int idx; + + for (idx = 0; idx < ARRAY_SIZE(ltr390_samp_freq_table); idx++) { + if (ltr390_samp_freq_table[idx][0] != val) + continue; + + guard(mutex)(&data->lock); + return regmap_update_bits(data->regmap, + LTR390_ALS_UVS_MEAS_RATE, + LTR390_ALS_UVS_MEAS_RATE_MASK, idx); + } + + return -EINVAL; +} + +static int ltr390_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *length = ARRAY_SIZE(ltr390_gain_map); + *type = IIO_VAL_INT; + *vals = ltr390_gain_map; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_INT_TIME: + *length = ARRAY_SIZE(ltr390_int_time_map_us); + *type = IIO_VAL_INT; + *vals = ltr390_int_time_map_us; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *length = ARRAY_SIZE(ltr390_freq_map); + *type = IIO_VAL_INT; + *vals = ltr390_freq_map; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int ltr390_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ltr390_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (val2 != 0) + return -EINVAL; + + return ltr390_set_gain(data, val); + + case IIO_CHAN_INFO_INT_TIME: + if (val2 != 0) + return -EINVAL; + + return ltr390_set_int_time(data, val); + + case IIO_CHAN_INFO_SAMP_FREQ: + if (val2 != 0) + return -EINVAL; + + return ltr390_set_samp_freq(data, val); + + default: + return -EINVAL; + } +} + +static int ltr390_read_intr_prst(struct ltr390_data *data, int *val) +{ + int ret, prst, samp_period; + + samp_period = ltr390_get_samp_freq_or_period(data, LTR390_GET_PERIOD); + ret = regmap_read(data->regmap, LTR390_INT_PST, &prst); + if (ret < 0) + return ret; + *val = prst * samp_period; + + return IIO_VAL_INT; +} + +static int ltr390_write_intr_prst(struct ltr390_data *data, int val) +{ + int ret, samp_period, new_val; + + samp_period = ltr390_get_samp_freq_or_period(data, LTR390_GET_PERIOD); + + /* persist period should be greater than or equal to samp period */ + if (val < samp_period) + return -EINVAL; + + new_val = DIV_ROUND_UP(val, samp_period); + if (new_val < 0 || new_val > 0x0f) + return -EINVAL; + + guard(mutex)(&data->lock); + ret = regmap_update_bits(data->regmap, + LTR390_INT_PST, + LTR390_INT_PST_MASK, + LTR390_INT_PST_VAL(new_val)); + if (ret) + return ret; + + return 0; +} + +static int ltr390_read_threshold(struct iio_dev *indio_dev, + enum iio_event_direction dir, + int *val, int *val2) +{ + struct ltr390_data *data = iio_priv(indio_dev); + int ret; + + switch (dir) { + case IIO_EV_DIR_RISING: + ret = ltr390_register_read(data, LTR390_THRESH_UP); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + + case IIO_EV_DIR_FALLING: + ret = ltr390_register_read(data, LTR390_THRESH_LOW); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ltr390_write_threshold(struct iio_dev *indio_dev, + enum iio_event_direction dir, + int val, int val2) +{ + struct ltr390_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + switch (dir) { + case IIO_EV_DIR_RISING: + return regmap_bulk_write(data->regmap, LTR390_THRESH_UP, &val, 3); + + case IIO_EV_DIR_FALLING: + return regmap_bulk_write(data->regmap, LTR390_THRESH_LOW, &val, 3); + + default: + return -EINVAL; + } +} + +static int ltr390_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + switch (info) { + case IIO_EV_INFO_VALUE: + return ltr390_read_threshold(indio_dev, dir, val, val2); + + case IIO_EV_INFO_PERIOD: + return ltr390_read_intr_prst(iio_priv(indio_dev), val); + + default: + return -EINVAL; + } +} + +static int ltr390_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + switch (info) { + case IIO_EV_INFO_VALUE: + if (val2 != 0) + return -EINVAL; + + return ltr390_write_threshold(indio_dev, dir, val, val2); + + case IIO_EV_INFO_PERIOD: + if (val2 != 0) + return -EINVAL; + + return ltr390_write_intr_prst(iio_priv(indio_dev), val); + + default: + return -EINVAL; + } +} + +static int ltr390_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct ltr390_data *data = iio_priv(indio_dev); + int ret, status; + + ret = regmap_read(data->regmap, LTR390_INT_CFG, &status); + if (ret < 0) + return ret; + + return FIELD_GET(LTR390_LS_INT_EN, status); +} + +static int ltr390_do_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct ltr390_data *data = iio_priv(indio_dev); + int ret; + + if (!state) + return regmap_clear_bits(data->regmap, LTR390_INT_CFG, LTR390_LS_INT_EN); + + ret = regmap_set_bits(data->regmap, LTR390_INT_CFG, LTR390_LS_INT_EN); + if (ret < 0) + return ret; + + switch (chan->type) { + case IIO_LIGHT: + ret = ltr390_set_mode(data, LTR390_SET_ALS_MODE); + if (ret < 0) + return ret; + + return regmap_clear_bits(data->regmap, LTR390_INT_CFG, LTR390_LS_INT_SEL_UVS); + + case IIO_UVINDEX: + ret = ltr390_set_mode(data, LTR390_SET_UVS_MODE); + if (ret < 0) + return ret; + + return regmap_set_bits(data->regmap, LTR390_INT_CFG, LTR390_LS_INT_SEL_UVS); + + default: + return -EINVAL; + } +} + +static int ltr390_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + int ret; + struct ltr390_data *data = iio_priv(indio_dev); + struct device *dev = &data->client->dev; + + guard(mutex)(&data->lock); + + if (state && !data->irq_enabled) { + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + dev_err(dev, "runtime PM failed to resume: %d\n", ret); + return ret; + } + data->irq_enabled = true; + } + + ret = ltr390_do_event_config(indio_dev, chan, type, dir, state); + + if (!state && data->irq_enabled) { + data->irq_enabled = false; + pm_runtime_put_autosuspend(dev); + } + + return ret; +} + +static int ltr390_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + struct ltr390_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + + if (readval) + return regmap_read(data->regmap, reg, readval); + + return regmap_write(data->regmap, reg, writeval); +} + +static const struct iio_info ltr390_info = { + .read_raw = ltr390_read_raw, + .write_raw = ltr390_write_raw, + .read_avail = ltr390_read_avail, + .read_event_value = ltr390_read_event_value, + .read_event_config = ltr390_read_event_config, + .write_event_value = ltr390_write_event_value, + .write_event_config = ltr390_write_event_config, + .debugfs_reg_access = ltr390_debugfs_reg_access, +}; + +static irqreturn_t ltr390_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ltr390_data *data = iio_priv(indio_dev); + int ret, status; + + /* Reading the status register to clear the interrupt flag, Datasheet pg: 17*/ + ret = regmap_read(data->regmap, LTR390_MAIN_STATUS, &status); + if (ret < 0) + return ret; + + switch (data->mode) { + case LTR390_SET_ALS_MODE: + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + break; + + case LTR390_SET_UVS_MODE: + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_UVINDEX, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + break; + } + + return IRQ_HANDLED; +} + +static void ltr390_powerdown(void *priv) +{ + struct ltr390_data *data = priv; + struct device *dev = &data->client->dev; + int ret; + + guard(mutex)(&data->lock); + + /* Ensure that power off and interrupts are disabled */ + if (data->irq_enabled) { + ret = regmap_clear_bits(data->regmap, LTR390_INT_CFG, LTR390_LS_INT_EN); + if (ret < 0) + dev_err(dev, "failed to disable interrupts\n"); + + data->irq_enabled = false; + pm_runtime_put_autosuspend(dev); + } + + ret = regmap_clear_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_SENSOR_ENABLE); + if (ret < 0) + dev_err(dev, "failed to disable sensor\n"); +} + +static int ltr390_pm_init(struct ltr390_data *data) +{ + int ret; + struct device *dev = &data->client->dev; + + ret = devm_pm_runtime_set_active_enabled(dev); + if (ret) + return dev_err_probe(dev, ret, "failed to enable runtime PM\n"); + + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + return 0; +} + +static int ltr390_probe(struct i2c_client *client) +{ + struct ltr390_data *data; + struct iio_dev *indio_dev; + struct device *dev; + int ret, part_number; + + dev = &client->dev; + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + i2c_set_clientdata(client, indio_dev); + + data = iio_priv(indio_dev); + data->regmap = devm_regmap_init_i2c(client, <r390_regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "regmap initialization failed\n"); + + data->client = client; + /* default value of integration time from pg: 15 of the datasheet */ + data->int_time_us = 100000; + /* default value of gain from pg: 16 of the datasheet */ + data->gain = 3; + /* default mode for ltr390 is ALS mode */ + data->mode = LTR390_SET_ALS_MODE; + /* default value of irq_enabled is false */ + data->irq_enabled = false; + + mutex_init(&data->lock); + + indio_dev->info = <r390_info; + indio_dev->channels = ltr390_channels; + indio_dev->num_channels = ARRAY_SIZE(ltr390_channels); + indio_dev->name = "ltr390"; + + ret = regmap_read(data->regmap, LTR390_PART_ID, &part_number); + if (ret) + return dev_err_probe(dev, ret, + "failed to get sensor's part id\n"); + /* Lower 4 bits of `part_number` change with hardware revisions */ + if (part_number >> 4 != LTR390_PART_NUMBER_ID) + dev_info(dev, "received invalid product id: 0x%x", part_number); + dev_dbg(dev, "LTR390, product id: 0x%x\n", part_number); + + /* reset sensor, chip fails to respond to this, so ignore any errors */ + regmap_set_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_SW_RESET); + + /* Wait for the registers to reset before proceeding */ + usleep_range(1000, 2000); + + ret = regmap_set_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_SENSOR_ENABLE); + if (ret) + return dev_err_probe(dev, ret, "failed to enable the sensor\n"); + + ret = devm_add_action_or_reset(dev, ltr390_powerdown, data); + if (ret) + return dev_err_probe(dev, ret, "failed to add action or reset\n"); + + if (client->irq) { + ret = devm_request_threaded_irq(dev, client->irq, + NULL, ltr390_interrupt_handler, + IRQF_ONESHOT, + "ltr390_thresh_event", + indio_dev); + if (ret) + return ret; + } + + ret = ltr390_pm_init(data); + if (ret) + return dev_err_probe(dev, ret, "failed to initialize runtime PM\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static int ltr390_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ltr390_data *data = iio_priv(indio_dev); + + return regmap_clear_bits(data->regmap, LTR390_MAIN_CTRL, + LTR390_SENSOR_ENABLE); +} + +static int ltr390_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ltr390_data *data = iio_priv(indio_dev); + + return regmap_set_bits(data->regmap, LTR390_MAIN_CTRL, + LTR390_SENSOR_ENABLE); +} + +static int ltr390_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ltr390_data *data = iio_priv(indio_dev); + + return regmap_clear_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_SENSOR_ENABLE); +} + +static int ltr390_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ltr390_data *data = iio_priv(indio_dev); + + return regmap_set_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_SENSOR_ENABLE); +} + +static const struct dev_pm_ops ltr390_pm_ops = { + SYSTEM_SLEEP_PM_OPS(ltr390_suspend, ltr390_resume) + RUNTIME_PM_OPS(ltr390_runtime_suspend, ltr390_runtime_resume, NULL) +}; + +static const struct i2c_device_id ltr390_id[] = { + { .name = "ltr390" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltr390_id); + +static const struct of_device_id ltr390_of_table[] = { + { .compatible = "liteon,ltr390" }, + { } +}; +MODULE_DEVICE_TABLE(of, ltr390_of_table); + +static struct i2c_driver ltr390_driver = { + .driver = { + .name = "ltr390", + .of_match_table = ltr390_of_table, + .pm = pm_ptr(<r390_pm_ops), + }, + .probe = ltr390_probe, + .id_table = ltr390_id, +}; +module_i2c_driver(ltr390_driver); + +MODULE_AUTHOR("Anshul Dalal "); +MODULE_DESCRIPTION("Lite-On LTR390 ALS and UV sensor Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/ltr501.c b/drivers/iio/light/ltr501.c new file mode 100644 index 000000000..fa0d16890 --- /dev/null +++ b/drivers/iio/light/ltr501.c @@ -0,0 +1,1633 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Support for Lite-On LTR501 and similar ambient light and proximity sensors. + * + * Copyright 2014 Peter Meerwald + * + * 7-bit I2C slave address 0x23 + * + * TODO: IR LED characteristics + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define LTR501_ALS_CONTR 0x80 /* ALS operation mode, SW reset */ +#define LTR501_PS_CONTR 0x81 /* PS operation mode */ +#define LTR501_PS_MEAS_RATE 0x84 /* measurement rate*/ +#define LTR501_ALS_MEAS_RATE 0x85 /* ALS integ time, measurement rate*/ +#define LTR501_PART_ID 0x86 +#define LTR501_MANUFAC_ID 0x87 +#define LTR501_ALS_DATA1 0x88 /* 16-bit, little endian */ +#define LTR501_ALS_DATA1_UPPER 0x89 /* upper 8 bits of LTR501_ALS_DATA1 */ +#define LTR501_ALS_DATA0 0x8a /* 16-bit, little endian */ +#define LTR501_ALS_DATA0_UPPER 0x8b /* upper 8 bits of LTR501_ALS_DATA0 */ +#define LTR501_ALS_PS_STATUS 0x8c +#define LTR501_PS_DATA 0x8d /* 16-bit, little endian */ +#define LTR501_PS_DATA_UPPER 0x8e /* upper 8 bits of LTR501_PS_DATA */ +#define LTR501_INTR 0x8f /* output mode, polarity, mode */ +#define LTR501_PS_THRESH_UP 0x90 /* 11 bit, ps upper threshold */ +#define LTR501_PS_THRESH_LOW 0x92 /* 11 bit, ps lower threshold */ +#define LTR501_ALS_THRESH_UP 0x97 /* 16 bit, ALS upper threshold */ +#define LTR501_ALS_THRESH_LOW 0x99 /* 16 bit, ALS lower threshold */ +#define LTR501_INTR_PRST 0x9e /* ps thresh, als thresh */ +#define LTR501_MAX_REG 0x9f + +#define LTR501_ALS_CONTR_SW_RESET BIT(2) +#define LTR501_CONTR_PS_GAIN_MASK (BIT(3) | BIT(2)) +#define LTR501_CONTR_PS_GAIN_SHIFT 2 +#define LTR501_CONTR_ALS_GAIN_MASK BIT(3) +#define LTR501_CONTR_ACTIVE BIT(1) + +#define LTR501_STATUS_ALS_INTR BIT(3) +#define LTR501_STATUS_ALS_RDY BIT(2) +#define LTR501_STATUS_PS_INTR BIT(1) +#define LTR501_STATUS_PS_RDY BIT(0) + +#define LTR501_PS_DATA_MASK 0x7ff +#define LTR501_PS_THRESH_MASK 0x7ff +#define LTR501_ALS_THRESH_MASK 0xffff + +#define LTR501_ALS_DEF_PERIOD 500000 +#define LTR501_PS_DEF_PERIOD 100000 + +#define LTR501_LUX_CONV(vis_coeff, vis_data, ir_coeff, ir_data) \ + ((vis_coeff * vis_data) - (ir_coeff * ir_data)) + +static const int int_time_mapping[] = {100000, 50000, 200000, 400000}; + +static const struct reg_field reg_field_it = + REG_FIELD(LTR501_ALS_MEAS_RATE, 3, 4); +static const struct reg_field reg_field_als_intr = + REG_FIELD(LTR501_INTR, 1, 1); +static const struct reg_field reg_field_ps_intr = + REG_FIELD(LTR501_INTR, 0, 0); +static const struct reg_field reg_field_als_rate = + REG_FIELD(LTR501_ALS_MEAS_RATE, 0, 2); +static const struct reg_field reg_field_ps_rate = + REG_FIELD(LTR501_PS_MEAS_RATE, 0, 3); +static const struct reg_field reg_field_als_prst = + REG_FIELD(LTR501_INTR_PRST, 0, 3); +static const struct reg_field reg_field_ps_prst = + REG_FIELD(LTR501_INTR_PRST, 4, 7); + +struct ltr501_samp_table { + int freq_val; /* repetition frequency in micro HZ*/ + int time_val; /* repetition rate in micro seconds */ +}; + +#define LTR501_RESERVED_GAIN -1 + +enum { + ltr501 = 0, + ltr559, + ltr301, + ltr303, +}; + +struct ltr501_gain { + int scale; + int uscale; +}; + +static const struct ltr501_gain ltr501_als_gain_tbl[] = { + {1, 0}, + {0, 5000}, +}; + +static const struct ltr501_gain ltr559_als_gain_tbl[] = { + {1, 0}, + {0, 500000}, + {0, 250000}, + {0, 125000}, + {LTR501_RESERVED_GAIN, LTR501_RESERVED_GAIN}, + {LTR501_RESERVED_GAIN, LTR501_RESERVED_GAIN}, + {0, 20000}, + {0, 10000}, +}; + +static const struct ltr501_gain ltr501_ps_gain_tbl[] = { + {1, 0}, + {0, 250000}, + {0, 125000}, + {0, 62500}, +}; + +static const struct ltr501_gain ltr559_ps_gain_tbl[] = { + {0, 62500}, /* x16 gain */ + {0, 31250}, /* x32 gain */ + {0, 15625}, /* bits X1 are for x64 gain */ + {0, 15624}, +}; + +struct ltr501_chip_info { + u8 partid; + const struct ltr501_gain *als_gain; + int als_gain_tbl_size; + const struct ltr501_gain *ps_gain; + int ps_gain_tbl_size; + u8 als_mode_active; + u8 als_gain_mask; + u8 als_gain_shift; + struct iio_chan_spec const *channels; + const int no_channels; + const struct iio_info *info; + const struct iio_info *info_no_irq; +}; + +struct ltr501_data { + struct i2c_client *client; + struct mutex lock_als, lock_ps; + const struct ltr501_chip_info *chip_info; + u8 als_contr, ps_contr; + int als_period, ps_period; /* period in micro seconds */ + struct regmap *regmap; + struct regmap_field *reg_it; + struct regmap_field *reg_als_intr; + struct regmap_field *reg_ps_intr; + struct regmap_field *reg_als_rate; + struct regmap_field *reg_ps_rate; + struct regmap_field *reg_als_prst; + struct regmap_field *reg_ps_prst; + uint32_t near_level; +}; + +static const struct ltr501_samp_table ltr501_als_samp_table[] = { + {20000000, 50000}, {10000000, 100000}, + {5000000, 200000}, {2000000, 500000}, + {1000000, 1000000}, {500000, 2000000}, + {500000, 2000000}, {500000, 2000000} +}; + +static const struct ltr501_samp_table ltr501_ps_samp_table[] = { + {20000000, 50000}, {14285714, 70000}, + {10000000, 100000}, {5000000, 200000}, + {2000000, 500000}, {1000000, 1000000}, + {500000, 2000000}, {500000, 2000000}, + {500000, 2000000} +}; + +static int ltr501_match_samp_freq(const struct ltr501_samp_table *tab, + int len, int val, int val2) +{ + int i, freq; + + freq = val * 1000000 + val2; + + for (i = 0; i < len; i++) { + if (tab[i].freq_val == freq) + return i; + } + + return -EINVAL; +} + +static int ltr501_als_read_samp_freq(const struct ltr501_data *data, + int *val, int *val2) +{ + int ret, i; + + ret = regmap_field_read(data->reg_als_rate, &i); + if (ret < 0) + return ret; + + if (i < 0 || i >= ARRAY_SIZE(ltr501_als_samp_table)) + return -EINVAL; + + *val = ltr501_als_samp_table[i].freq_val / 1000000; + *val2 = ltr501_als_samp_table[i].freq_val % 1000000; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int ltr501_ps_read_samp_freq(const struct ltr501_data *data, + int *val, int *val2) +{ + int ret, i; + + ret = regmap_field_read(data->reg_ps_rate, &i); + if (ret < 0) + return ret; + + if (i < 0 || i >= ARRAY_SIZE(ltr501_ps_samp_table)) + return -EINVAL; + + *val = ltr501_ps_samp_table[i].freq_val / 1000000; + *val2 = ltr501_ps_samp_table[i].freq_val % 1000000; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int ltr501_als_write_samp_freq(struct ltr501_data *data, + int val, int val2) +{ + int i, ret; + + i = ltr501_match_samp_freq(ltr501_als_samp_table, + ARRAY_SIZE(ltr501_als_samp_table), + val, val2); + + if (i < 0) + return i; + + mutex_lock(&data->lock_als); + ret = regmap_field_write(data->reg_als_rate, i); + mutex_unlock(&data->lock_als); + + return ret; +} + +static int ltr501_ps_write_samp_freq(struct ltr501_data *data, + int val, int val2) +{ + int i, ret; + + i = ltr501_match_samp_freq(ltr501_ps_samp_table, + ARRAY_SIZE(ltr501_ps_samp_table), + val, val2); + + if (i < 0) + return i; + + mutex_lock(&data->lock_ps); + ret = regmap_field_write(data->reg_ps_rate, i); + mutex_unlock(&data->lock_ps); + + return ret; +} + +static int ltr501_als_read_samp_period(const struct ltr501_data *data, int *val) +{ + int ret, i; + + ret = regmap_field_read(data->reg_als_rate, &i); + if (ret < 0) + return ret; + + if (i < 0 || i >= ARRAY_SIZE(ltr501_als_samp_table)) + return -EINVAL; + + *val = ltr501_als_samp_table[i].time_val; + + return IIO_VAL_INT; +} + +static int ltr501_ps_read_samp_period(const struct ltr501_data *data, int *val) +{ + int ret, i; + + ret = regmap_field_read(data->reg_ps_rate, &i); + if (ret < 0) + return ret; + + if (i < 0 || i >= ARRAY_SIZE(ltr501_ps_samp_table)) + return -EINVAL; + + *val = ltr501_ps_samp_table[i].time_val; + + return IIO_VAL_INT; +} + +/* IR and visible spectrum coeff's are given in data sheet */ +static unsigned long ltr501_calculate_lux(u16 vis_data, u16 ir_data) +{ + unsigned long ratio, lux; + + if (vis_data == 0) + return 0; + + /* multiply numerator by 100 to avoid handling ratio < 1 */ + ratio = DIV_ROUND_UP(ir_data * 100, ir_data + vis_data); + + if (ratio < 45) + lux = LTR501_LUX_CONV(1774, vis_data, -1105, ir_data); + else if (ratio >= 45 && ratio < 64) + lux = LTR501_LUX_CONV(3772, vis_data, 1336, ir_data); + else if (ratio >= 64 && ratio < 85) + lux = LTR501_LUX_CONV(1690, vis_data, 169, ir_data); + else + lux = 0; + + return lux / 1000; +} + +static int ltr501_drdy(const struct ltr501_data *data, u8 drdy_mask) +{ + int tries = 100; + int ret, status; + + while (tries--) { + ret = regmap_read(data->regmap, LTR501_ALS_PS_STATUS, &status); + if (ret < 0) + return ret; + if ((status & drdy_mask) == drdy_mask) + return 0; + msleep(25); + } + + dev_err(&data->client->dev, "ltr501_drdy() failed, data not ready\n"); + return -EIO; +} + +static int ltr501_set_it_time(struct ltr501_data *data, int it) +{ + int ret, i, index = -1, status; + + for (i = 0; i < ARRAY_SIZE(int_time_mapping); i++) { + if (int_time_mapping[i] == it) { + index = i; + break; + } + } + /* Make sure integ time index is valid */ + if (index < 0) + return -EINVAL; + + ret = regmap_read(data->regmap, LTR501_ALS_CONTR, &status); + if (ret < 0) + return ret; + + if (status & LTR501_CONTR_ALS_GAIN_MASK) { + /* + * 200 ms and 400 ms integ time can only be + * used in dynamic range 1 + */ + if (index > 1) + return -EINVAL; + } else + /* 50 ms integ time can only be used in dynamic range 2 */ + if (index == 1) + return -EINVAL; + + return regmap_field_write(data->reg_it, index); +} + +/* read int time in micro seconds */ +static int ltr501_read_it_time(const struct ltr501_data *data, + int *val, int *val2) +{ + int ret, index; + + ret = regmap_field_read(data->reg_it, &index); + if (ret < 0) + return ret; + + /* Make sure integ time index is valid */ + if (index < 0 || index >= ARRAY_SIZE(int_time_mapping)) + return -EINVAL; + + *val2 = int_time_mapping[index]; + *val = 0; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int ltr501_read_als(const struct ltr501_data *data, __le16 buf[2]) +{ + int ret; + + ret = ltr501_drdy(data, LTR501_STATUS_ALS_RDY); + if (ret < 0) + return ret; + /* always read both ALS channels in given order */ + return regmap_bulk_read(data->regmap, LTR501_ALS_DATA1, + buf, 2 * sizeof(__le16)); +} + +static int ltr501_read_ps(const struct ltr501_data *data) +{ + __le16 status; + int ret; + + ret = ltr501_drdy(data, LTR501_STATUS_PS_RDY); + if (ret < 0) + return ret; + + ret = regmap_bulk_read(data->regmap, LTR501_PS_DATA, + &status, sizeof(status)); + if (ret < 0) + return ret; + + return le16_to_cpu(status); +} + +static int ltr501_read_intr_prst(const struct ltr501_data *data, + enum iio_chan_type type, + int *val2) +{ + int ret, samp_period, prst; + + switch (type) { + case IIO_INTENSITY: + ret = regmap_field_read(data->reg_als_prst, &prst); + if (ret < 0) + return ret; + + ret = ltr501_als_read_samp_period(data, &samp_period); + + if (ret < 0) + return ret; + *val2 = samp_period * prst; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_PROXIMITY: + ret = regmap_field_read(data->reg_ps_prst, &prst); + if (ret < 0) + return ret; + + ret = ltr501_ps_read_samp_period(data, &samp_period); + + if (ret < 0) + return ret; + + *val2 = samp_period * prst; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static int ltr501_write_intr_prst(struct ltr501_data *data, + enum iio_chan_type type, + int val, int val2) +{ + int ret, samp_period, new_val; + unsigned long period; + + if (val < 0 || val2 < 0) + return -EINVAL; + + /* period in microseconds */ + period = ((val * 1000000) + val2); + + switch (type) { + case IIO_INTENSITY: + ret = ltr501_als_read_samp_period(data, &samp_period); + if (ret < 0) + return ret; + + /* period should be atleast equal to sampling period */ + if (period < samp_period) + return -EINVAL; + + new_val = DIV_ROUND_UP(period, samp_period); + if (new_val < 0 || new_val > 0x0f) + return -EINVAL; + + mutex_lock(&data->lock_als); + ret = regmap_field_write(data->reg_als_prst, new_val); + mutex_unlock(&data->lock_als); + if (ret >= 0) + data->als_period = period; + + return ret; + case IIO_PROXIMITY: + ret = ltr501_ps_read_samp_period(data, &samp_period); + if (ret < 0) + return ret; + + /* period should be atleast equal to rate */ + if (period < samp_period) + return -EINVAL; + + new_val = DIV_ROUND_UP(period, samp_period); + if (new_val < 0 || new_val > 0x0f) + return -EINVAL; + + mutex_lock(&data->lock_ps); + ret = regmap_field_write(data->reg_ps_prst, new_val); + mutex_unlock(&data->lock_ps); + if (ret >= 0) + data->ps_period = period; + + return ret; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static ssize_t ltr501_read_near_level(struct iio_dev *indio_dev, + uintptr_t priv, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ltr501_data *data = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", data->near_level); +} + +static const struct iio_chan_spec_ext_info ltr501_ext_info[] = { + { + .name = "nearlevel", + .shared = IIO_SEPARATE, + .read = ltr501_read_near_level, + }, + { } +}; + +static const struct iio_event_spec ltr501_als_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_PERIOD), + }, + +}; + +static const struct iio_event_spec ltr501_pxs_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_PERIOD), + }, +}; + +#define LTR501_INTENSITY_CHANNEL(_idx, _addr, _mod, _shared, \ + _evspec, _evsize) { \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .address = (_addr), \ + .channel2 = (_mod), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = (_shared), \ + .scan_index = (_idx), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ + .event_spec = _evspec,\ + .num_event_specs = _evsize,\ +} + +#define LTR501_LIGHT_CHANNEL() { \ + .type = IIO_LIGHT, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ + .scan_index = -1, \ +} + +static const struct iio_chan_spec ltr501_channels[] = { + LTR501_LIGHT_CHANNEL(), + LTR501_INTENSITY_CHANNEL(0, LTR501_ALS_DATA0, IIO_MOD_LIGHT_BOTH, 0, + ltr501_als_event_spec, + ARRAY_SIZE(ltr501_als_event_spec)), + LTR501_INTENSITY_CHANNEL(1, LTR501_ALS_DATA1, IIO_MOD_LIGHT_IR, + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + NULL, 0), + { + .type = IIO_PROXIMITY, + .address = LTR501_PS_DATA, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 2, + .scan_type = { + .sign = 'u', + .realbits = 11, + .storagebits = 16, + .endianness = IIO_CPU, + }, + .event_spec = ltr501_pxs_event_spec, + .num_event_specs = ARRAY_SIZE(ltr501_pxs_event_spec), + .ext_info = ltr501_ext_info, + }, + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_chan_spec ltr301_channels[] = { + LTR501_LIGHT_CHANNEL(), + LTR501_INTENSITY_CHANNEL(0, LTR501_ALS_DATA0, IIO_MOD_LIGHT_BOTH, 0, + ltr501_als_event_spec, + ARRAY_SIZE(ltr501_als_event_spec)), + LTR501_INTENSITY_CHANNEL(1, LTR501_ALS_DATA1, IIO_MOD_LIGHT_IR, + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + NULL, 0), + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static int ltr501_read_info_raw(struct ltr501_data *data, + struct iio_chan_spec const *chan, + int *val) +{ + __le16 buf[2]; + int ret; + + switch (chan->type) { + case IIO_INTENSITY: + mutex_lock(&data->lock_als); + ret = ltr501_read_als(data, buf); + mutex_unlock(&data->lock_als); + if (ret < 0) + return ret; + *val = le16_to_cpu(chan->address == LTR501_ALS_DATA1 ? + buf[0] : buf[1]); + return IIO_VAL_INT; + case IIO_PROXIMITY: + mutex_lock(&data->lock_ps); + ret = ltr501_read_ps(data); + mutex_unlock(&data->lock_ps); + if (ret < 0) + return ret; + *val = ret & LTR501_PS_DATA_MASK; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ltr501_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ltr501_data *data = iio_priv(indio_dev); + __le16 buf[2]; + int ret, i; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_LIGHT: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + mutex_lock(&data->lock_als); + ret = ltr501_read_als(data, buf); + mutex_unlock(&data->lock_als); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + *val = ltr501_calculate_lux(le16_to_cpu(buf[1]), + le16_to_cpu(buf[0])); + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ltr501_read_info_raw(data, chan, val); + + iio_device_release_direct(indio_dev); + return ret; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_INTENSITY: + i = (data->als_contr & data->chip_info->als_gain_mask) + >> data->chip_info->als_gain_shift; + *val = data->chip_info->als_gain[i].scale; + *val2 = data->chip_info->als_gain[i].uscale; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_PROXIMITY: + i = (data->ps_contr & LTR501_CONTR_PS_GAIN_MASK) >> + LTR501_CONTR_PS_GAIN_SHIFT; + *val = data->chip_info->ps_gain[i].scale; + *val2 = data->chip_info->ps_gain[i].uscale; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_INTENSITY: + return ltr501_read_it_time(data, val, val2); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + switch (chan->type) { + case IIO_INTENSITY: + return ltr501_als_read_samp_freq(data, val, val2); + case IIO_PROXIMITY: + return ltr501_ps_read_samp_freq(data, val, val2); + default: + return -EINVAL; + } + } + return -EINVAL; +} + +static int ltr501_get_gain_index(const struct ltr501_gain *gain, int size, + int val, int val2) +{ + int i; + + for (i = 0; i < size; i++) + if (val == gain[i].scale && val2 == gain[i].uscale) + return i; + + return -EINVAL; +} + +static int __ltr501_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ltr501_data *data = iio_priv(indio_dev); + int i, ret, freq_val, freq_val2; + const struct ltr501_chip_info *info = data->chip_info; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_INTENSITY: + i = ltr501_get_gain_index(info->als_gain, + info->als_gain_tbl_size, + val, val2); + if (i < 0) + return i; + + data->als_contr &= ~info->als_gain_mask; + data->als_contr |= i << info->als_gain_shift; + + return regmap_write(data->regmap, LTR501_ALS_CONTR, + data->als_contr); + case IIO_PROXIMITY: + i = ltr501_get_gain_index(info->ps_gain, + info->ps_gain_tbl_size, + val, val2); + if (i < 0) + return i; + + data->ps_contr &= ~LTR501_CONTR_PS_GAIN_MASK; + data->ps_contr |= i << LTR501_CONTR_PS_GAIN_SHIFT; + + return regmap_write(data->regmap, LTR501_PS_CONTR, + data->ps_contr); + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_INTENSITY: + if (val != 0) + return -EINVAL; + + mutex_lock(&data->lock_als); + ret = ltr501_set_it_time(data, val2); + mutex_unlock(&data->lock_als); + return ret; + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_SAMP_FREQ: + switch (chan->type) { + case IIO_INTENSITY: + ret = ltr501_als_read_samp_freq(data, &freq_val, + &freq_val2); + if (ret < 0) + return ret; + + ret = ltr501_als_write_samp_freq(data, val, val2); + if (ret < 0) + return ret; + + /* update persistence count when changing frequency */ + ret = ltr501_write_intr_prst(data, chan->type, + 0, data->als_period); + + if (ret < 0) + /* Do not ovewrite error */ + ltr501_als_write_samp_freq(data, freq_val, + freq_val2); + return ret; + case IIO_PROXIMITY: + ret = ltr501_ps_read_samp_freq(data, &freq_val, + &freq_val2); + if (ret < 0) + return ret; + + ret = ltr501_ps_write_samp_freq(data, val, val2); + if (ret < 0) + return ret; + + /* update persistence count when changing frequency */ + ret = ltr501_write_intr_prst(data, chan->type, + 0, data->ps_period); + + if (ret < 0) + /* Do not overwrite error */ + ltr501_ps_write_samp_freq(data, freq_val, + freq_val2); + return ret; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ltr501_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = __ltr501_write_raw(indio_dev, chan, val, val2, mask); + + iio_device_release_direct(indio_dev); + + return ret; +} + +static int ltr501_read_thresh(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + const struct ltr501_data *data = iio_priv(indio_dev); + int ret, thresh_data; + + switch (chan->type) { + case IIO_INTENSITY: + switch (dir) { + case IIO_EV_DIR_RISING: + ret = regmap_bulk_read(data->regmap, + LTR501_ALS_THRESH_UP, + &thresh_data, 2); + if (ret < 0) + return ret; + *val = thresh_data & LTR501_ALS_THRESH_MASK; + return IIO_VAL_INT; + case IIO_EV_DIR_FALLING: + ret = regmap_bulk_read(data->regmap, + LTR501_ALS_THRESH_LOW, + &thresh_data, 2); + if (ret < 0) + return ret; + *val = thresh_data & LTR501_ALS_THRESH_MASK; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_PROXIMITY: + switch (dir) { + case IIO_EV_DIR_RISING: + ret = regmap_bulk_read(data->regmap, + LTR501_PS_THRESH_UP, + &thresh_data, 2); + if (ret < 0) + return ret; + *val = thresh_data & LTR501_PS_THRESH_MASK; + return IIO_VAL_INT; + case IIO_EV_DIR_FALLING: + ret = regmap_bulk_read(data->regmap, + LTR501_PS_THRESH_LOW, + &thresh_data, 2); + if (ret < 0) + return ret; + *val = thresh_data & LTR501_PS_THRESH_MASK; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } + + return -EINVAL; +} + +static int ltr501_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct ltr501_data *data = iio_priv(indio_dev); + int ret; + + if (val < 0) + return -EINVAL; + + switch (chan->type) { + case IIO_INTENSITY: + if (val > LTR501_ALS_THRESH_MASK) + return -EINVAL; + switch (dir) { + case IIO_EV_DIR_RISING: + mutex_lock(&data->lock_als); + ret = regmap_bulk_write(data->regmap, + LTR501_ALS_THRESH_UP, + &val, 2); + mutex_unlock(&data->lock_als); + return ret; + case IIO_EV_DIR_FALLING: + mutex_lock(&data->lock_als); + ret = regmap_bulk_write(data->regmap, + LTR501_ALS_THRESH_LOW, + &val, 2); + mutex_unlock(&data->lock_als); + return ret; + default: + return -EINVAL; + } + case IIO_PROXIMITY: + if (val > LTR501_PS_THRESH_MASK) + return -EINVAL; + switch (dir) { + case IIO_EV_DIR_RISING: + mutex_lock(&data->lock_ps); + ret = regmap_bulk_write(data->regmap, + LTR501_PS_THRESH_UP, + &val, 2); + mutex_unlock(&data->lock_ps); + return ret; + case IIO_EV_DIR_FALLING: + mutex_lock(&data->lock_ps); + ret = regmap_bulk_write(data->regmap, + LTR501_PS_THRESH_LOW, + &val, 2); + mutex_unlock(&data->lock_ps); + return ret; + default: + return -EINVAL; + } + default: + return -EINVAL; + } + + return -EINVAL; +} + +static int ltr501_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + return ltr501_read_thresh(indio_dev, chan, type, dir, + info, val, val2); + case IIO_EV_INFO_PERIOD: + ret = ltr501_read_intr_prst(iio_priv(indio_dev), + chan->type, val2); + *val = *val2 / 1000000; + *val2 = *val2 % 1000000; + return ret; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static int ltr501_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + switch (info) { + case IIO_EV_INFO_VALUE: + if (val2 != 0) + return -EINVAL; + return ltr501_write_thresh(indio_dev, chan, type, dir, + info, val, val2); + case IIO_EV_INFO_PERIOD: + return ltr501_write_intr_prst(iio_priv(indio_dev), chan->type, + val, val2); + default: + return -EINVAL; + } + + return -EINVAL; +} + +static int ltr501_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct ltr501_data *data = iio_priv(indio_dev); + int ret, status; + + switch (chan->type) { + case IIO_INTENSITY: + ret = regmap_field_read(data->reg_als_intr, &status); + if (ret < 0) + return ret; + return status; + case IIO_PROXIMITY: + ret = regmap_field_read(data->reg_ps_intr, &status); + if (ret < 0) + return ret; + return status; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static int ltr501_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct ltr501_data *data = iio_priv(indio_dev); + int ret; + + switch (chan->type) { + case IIO_INTENSITY: + mutex_lock(&data->lock_als); + ret = regmap_field_write(data->reg_als_intr, state); + mutex_unlock(&data->lock_als); + return ret; + case IIO_PROXIMITY: + mutex_lock(&data->lock_ps); + ret = regmap_field_write(data->reg_ps_intr, state); + mutex_unlock(&data->lock_ps); + return ret; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static ssize_t ltr501_show_proximity_scale_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ltr501_data *data = iio_priv(dev_to_iio_dev(dev)); + const struct ltr501_chip_info *info = data->chip_info; + ssize_t len = 0; + int i; + + for (i = 0; i < info->ps_gain_tbl_size; i++) { + if (info->ps_gain[i].scale == LTR501_RESERVED_GAIN) + continue; + len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06d ", + info->ps_gain[i].scale, + info->ps_gain[i].uscale); + } + + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t ltr501_show_intensity_scale_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ltr501_data *data = iio_priv(dev_to_iio_dev(dev)); + const struct ltr501_chip_info *info = data->chip_info; + ssize_t len = 0; + int i; + + for (i = 0; i < info->als_gain_tbl_size; i++) { + if (info->als_gain[i].scale == LTR501_RESERVED_GAIN) + continue; + len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06d ", + info->als_gain[i].scale, + info->als_gain[i].uscale); + } + + buf[len - 1] = '\n'; + + return len; +} + +static IIO_CONST_ATTR_INT_TIME_AVAIL("0.05 0.1 0.2 0.4"); +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("20 10 5 2 1 0.5"); + +static IIO_DEVICE_ATTR(in_proximity_scale_available, S_IRUGO, + ltr501_show_proximity_scale_avail, NULL, 0); +static IIO_DEVICE_ATTR(in_intensity_scale_available, S_IRUGO, + ltr501_show_intensity_scale_avail, NULL, 0); + +static struct attribute *ltr501_attributes[] = { + &iio_dev_attr_in_proximity_scale_available.dev_attr.attr, + &iio_dev_attr_in_intensity_scale_available.dev_attr.attr, + &iio_const_attr_integration_time_available.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static struct attribute *ltr301_attributes[] = { + &iio_dev_attr_in_intensity_scale_available.dev_attr.attr, + &iio_const_attr_integration_time_available.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group ltr501_attribute_group = { + .attrs = ltr501_attributes, +}; + +static const struct attribute_group ltr301_attribute_group = { + .attrs = ltr301_attributes, +}; + +static const struct iio_info ltr501_info_no_irq = { + .read_raw = ltr501_read_raw, + .write_raw = ltr501_write_raw, + .attrs = <r501_attribute_group, +}; + +static const struct iio_info ltr501_info = { + .read_raw = ltr501_read_raw, + .write_raw = ltr501_write_raw, + .attrs = <r501_attribute_group, + .read_event_value = <r501_read_event, + .write_event_value = <r501_write_event, + .read_event_config = <r501_read_event_config, + .write_event_config = <r501_write_event_config, +}; + +static const struct iio_info ltr301_info_no_irq = { + .read_raw = ltr501_read_raw, + .write_raw = ltr501_write_raw, + .attrs = <r301_attribute_group, +}; + +static const struct iio_info ltr301_info = { + .read_raw = ltr501_read_raw, + .write_raw = ltr501_write_raw, + .attrs = <r301_attribute_group, + .read_event_value = <r501_read_event, + .write_event_value = <r501_write_event, + .read_event_config = <r501_read_event_config, + .write_event_config = <r501_write_event_config, +}; + +static const struct ltr501_chip_info ltr501_chip_info_tbl[] = { + [ltr501] = { + .partid = 0x08, + .als_gain = ltr501_als_gain_tbl, + .als_gain_tbl_size = ARRAY_SIZE(ltr501_als_gain_tbl), + .ps_gain = ltr501_ps_gain_tbl, + .ps_gain_tbl_size = ARRAY_SIZE(ltr501_ps_gain_tbl), + .als_mode_active = BIT(0) | BIT(1), + .als_gain_mask = BIT(3), + .als_gain_shift = 3, + .info = <r501_info, + .info_no_irq = <r501_info_no_irq, + .channels = ltr501_channels, + .no_channels = ARRAY_SIZE(ltr501_channels), + }, + [ltr559] = { + .partid = 0x09, + .als_gain = ltr559_als_gain_tbl, + .als_gain_tbl_size = ARRAY_SIZE(ltr559_als_gain_tbl), + .ps_gain = ltr559_ps_gain_tbl, + .ps_gain_tbl_size = ARRAY_SIZE(ltr559_ps_gain_tbl), + .als_mode_active = BIT(0), + .als_gain_mask = BIT(2) | BIT(3) | BIT(4), + .als_gain_shift = 2, + .info = <r501_info, + .info_no_irq = <r501_info_no_irq, + .channels = ltr501_channels, + .no_channels = ARRAY_SIZE(ltr501_channels), + }, + [ltr301] = { + .partid = 0x08, + .als_gain = ltr501_als_gain_tbl, + .als_gain_tbl_size = ARRAY_SIZE(ltr501_als_gain_tbl), + .als_mode_active = BIT(0) | BIT(1), + .als_gain_mask = BIT(3), + .als_gain_shift = 3, + .info = <r301_info, + .info_no_irq = <r301_info_no_irq, + .channels = ltr301_channels, + .no_channels = ARRAY_SIZE(ltr301_channels), + }, + [ltr303] = { + .partid = 0x0A, + .als_gain = ltr559_als_gain_tbl, + .als_gain_tbl_size = ARRAY_SIZE(ltr559_als_gain_tbl), + .als_mode_active = BIT(0), + .als_gain_mask = BIT(2) | BIT(3) | BIT(4), + .als_gain_shift = 2, + .info = <r301_info, + .info_no_irq = <r301_info_no_irq, + .channels = ltr301_channels, + .no_channels = ARRAY_SIZE(ltr301_channels), + }, +}; + +static int ltr501_write_contr(struct ltr501_data *data, u8 als_val, u8 ps_val) +{ + int ret; + + ret = regmap_write(data->regmap, LTR501_ALS_CONTR, als_val); + if (ret < 0) + return ret; + + return regmap_write(data->regmap, LTR501_PS_CONTR, ps_val); +} + +static irqreturn_t ltr501_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ltr501_data *data = iio_priv(indio_dev); + struct { + u16 channels[3]; + aligned_s64 ts; + } scan = { }; + __le16 als_buf[2]; + u8 mask = 0; + int j = 0; + int ret, psdata; + + /* figure out which data needs to be ready */ + if (test_bit(0, indio_dev->active_scan_mask) || + test_bit(1, indio_dev->active_scan_mask)) + mask |= LTR501_STATUS_ALS_RDY; + if (test_bit(2, indio_dev->active_scan_mask)) + mask |= LTR501_STATUS_PS_RDY; + + ret = ltr501_drdy(data, mask); + if (ret < 0) + goto done; + + if (mask & LTR501_STATUS_ALS_RDY) { + ret = regmap_bulk_read(data->regmap, LTR501_ALS_DATA1, + als_buf, sizeof(als_buf)); + if (ret < 0) + goto done; + if (test_bit(0, indio_dev->active_scan_mask)) + scan.channels[j++] = le16_to_cpu(als_buf[1]); + if (test_bit(1, indio_dev->active_scan_mask)) + scan.channels[j++] = le16_to_cpu(als_buf[0]); + } + + if (mask & LTR501_STATUS_PS_RDY) { + ret = regmap_bulk_read(data->regmap, LTR501_PS_DATA, + &psdata, 2); + if (ret < 0) + goto done; + scan.channels[j++] = psdata & LTR501_PS_DATA_MASK; + } + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static irqreturn_t ltr501_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ltr501_data *data = iio_priv(indio_dev); + int ret, status; + + ret = regmap_read(data->regmap, LTR501_ALS_PS_STATUS, &status); + if (ret < 0) { + dev_err(&data->client->dev, + "irq read int reg failed\n"); + return IRQ_HANDLED; + } + + if (status & LTR501_STATUS_ALS_INTR) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + + if (status & LTR501_STATUS_PS_INTR) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + + return IRQ_HANDLED; +} + +static int ltr501_init(struct ltr501_data *data) +{ + int ret, status; + + ret = regmap_read(data->regmap, LTR501_ALS_CONTR, &status); + if (ret < 0) + return ret; + + data->als_contr = status | data->chip_info->als_mode_active; + + ret = regmap_read(data->regmap, LTR501_PS_CONTR, &status); + if (ret < 0) + return ret; + + data->ps_contr = status | LTR501_CONTR_ACTIVE; + + ret = ltr501_read_intr_prst(data, IIO_INTENSITY, &data->als_period); + if (ret < 0) + return ret; + + ret = ltr501_read_intr_prst(data, IIO_PROXIMITY, &data->ps_period); + if (ret < 0) + return ret; + + return ltr501_write_contr(data, data->als_contr, data->ps_contr); +} + +static bool ltr501_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LTR501_ALS_DATA1: + case LTR501_ALS_DATA1_UPPER: + case LTR501_ALS_DATA0: + case LTR501_ALS_DATA0_UPPER: + case LTR501_ALS_PS_STATUS: + case LTR501_PS_DATA: + case LTR501_PS_DATA_UPPER: + return true; + default: + return false; + } +} + +static const struct regmap_config ltr501_regmap_config = { + .name = "ltr501_regmap", + .reg_bits = 8, + .val_bits = 8, + .max_register = LTR501_MAX_REG, + .cache_type = REGCACHE_MAPLE, + .volatile_reg = ltr501_is_volatile_reg, +}; + +static int ltr501_powerdown(struct ltr501_data *data) +{ + return ltr501_write_contr(data, data->als_contr & + ~data->chip_info->als_mode_active, + data->ps_contr & ~LTR501_CONTR_ACTIVE); +} + +static int ltr501_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + static const char * const regulator_names[] = { "vdd", "vddio" }; + struct ltr501_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + const void *ddata = NULL; + int partid, chip_idx; + const char *name; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, <r501_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Regmap initialization failed.\n"); + return PTR_ERR(regmap); + } + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->regmap = regmap; + mutex_init(&data->lock_als); + mutex_init(&data->lock_ps); + + ret = devm_regulator_bulk_get_enable(&client->dev, + ARRAY_SIZE(regulator_names), + regulator_names); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to get regulators\n"); + + data->reg_it = devm_regmap_field_alloc(&client->dev, regmap, + reg_field_it); + if (IS_ERR(data->reg_it)) { + dev_err(&client->dev, "Integ time reg field init failed.\n"); + return PTR_ERR(data->reg_it); + } + + data->reg_als_intr = devm_regmap_field_alloc(&client->dev, regmap, + reg_field_als_intr); + if (IS_ERR(data->reg_als_intr)) { + dev_err(&client->dev, "ALS intr mode reg field init failed\n"); + return PTR_ERR(data->reg_als_intr); + } + + data->reg_ps_intr = devm_regmap_field_alloc(&client->dev, regmap, + reg_field_ps_intr); + if (IS_ERR(data->reg_ps_intr)) { + dev_err(&client->dev, "PS intr mode reg field init failed.\n"); + return PTR_ERR(data->reg_ps_intr); + } + + data->reg_als_rate = devm_regmap_field_alloc(&client->dev, regmap, + reg_field_als_rate); + if (IS_ERR(data->reg_als_rate)) { + dev_err(&client->dev, "ALS samp rate field init failed.\n"); + return PTR_ERR(data->reg_als_rate); + } + + data->reg_ps_rate = devm_regmap_field_alloc(&client->dev, regmap, + reg_field_ps_rate); + if (IS_ERR(data->reg_ps_rate)) { + dev_err(&client->dev, "PS samp rate field init failed.\n"); + return PTR_ERR(data->reg_ps_rate); + } + + data->reg_als_prst = devm_regmap_field_alloc(&client->dev, regmap, + reg_field_als_prst); + if (IS_ERR(data->reg_als_prst)) { + dev_err(&client->dev, "ALS prst reg field init failed\n"); + return PTR_ERR(data->reg_als_prst); + } + + data->reg_ps_prst = devm_regmap_field_alloc(&client->dev, regmap, + reg_field_ps_prst); + if (IS_ERR(data->reg_ps_prst)) { + dev_err(&client->dev, "PS prst reg field init failed.\n"); + return PTR_ERR(data->reg_ps_prst); + } + + ret = regmap_read(data->regmap, LTR501_PART_ID, &partid); + if (ret < 0) + return ret; + + if (id) { + name = id->name; + chip_idx = id->driver_data; + } else { + name = iio_get_acpi_device_name_and_data(&client->dev, &ddata); + chip_idx = (intptr_t)ddata; + } + if (!name) + return -ENODEV; + + data->chip_info = <r501_chip_info_tbl[chip_idx]; + + if ((partid >> 4) != data->chip_info->partid) + return -ENODEV; + + if (device_property_read_u32(&client->dev, "proximity-near-level", + &data->near_level)) + data->near_level = 0; + + indio_dev->info = data->chip_info->info; + indio_dev->channels = data->chip_info->channels; + indio_dev->num_channels = data->chip_info->no_channels; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = ltr501_init(data); + if (ret < 0) + return ret; + + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, ltr501_interrupt_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "ltr501_thresh_event", + indio_dev); + if (ret) + return ret; + } else { + indio_dev->info = data->chip_info->info_no_irq; + } + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + ltr501_trigger_handler, NULL); + if (ret) + goto powerdown_on_error; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_unreg_buffer; + + return 0; + +error_unreg_buffer: + iio_triggered_buffer_cleanup(indio_dev); +powerdown_on_error: + ltr501_powerdown(data); + return ret; +} + +static void ltr501_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + ltr501_powerdown(iio_priv(indio_dev)); +} + +static int ltr501_suspend(struct device *dev) +{ + struct ltr501_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + return ltr501_powerdown(data); +} + +static int ltr501_resume(struct device *dev) +{ + struct ltr501_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + + return ltr501_write_contr(data, data->als_contr, + data->ps_contr); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ltr501_pm_ops, ltr501_suspend, ltr501_resume); + +static const struct acpi_device_id ltr_acpi_match[] = { + { "LTER0301", ltr301 }, + /* https://www.catalog.update.microsoft.com/Search.aspx?q=lter0303 */ + { "LTER0303", ltr303 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, ltr_acpi_match); + +static const struct i2c_device_id ltr501_id[] = { + { .name = "ltr501", .driver_data = ltr501 }, + { .name = "ltr559", .driver_data = ltr559 }, + { .name = "ltr301", .driver_data = ltr301 }, + { .name = "ltr303", .driver_data = ltr303 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltr501_id); + +static const struct of_device_id ltr501_of_match[] = { + { .compatible = "liteon,ltr501", }, + { .compatible = "liteon,ltr559", }, + { .compatible = "liteon,ltr301", }, + { .compatible = "liteon,ltr303", }, + { } +}; +MODULE_DEVICE_TABLE(of, ltr501_of_match); + +static struct i2c_driver ltr501_driver = { + .driver = { + .name = "ltr501", + .of_match_table = ltr501_of_match, + .pm = pm_sleep_ptr(<r501_pm_ops), + .acpi_match_table = ltr_acpi_match, + }, + .probe = ltr501_probe, + .remove = ltr501_remove, + .id_table = ltr501_id, +}; + +module_i2c_driver(ltr501_driver); + +MODULE_AUTHOR("Peter Meerwald "); +MODULE_DESCRIPTION("Lite-On LTR501 ambient light and proximity sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/ltrf216a.c b/drivers/iio/light/ltrf216a.c new file mode 100644 index 000000000..0a8851342 --- /dev/null +++ b/drivers/iio/light/ltrf216a.c @@ -0,0 +1,581 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * LTRF216A Ambient Light Sensor + * + * Copyright (C) 2022 Collabora, Ltd. + * Author: Shreeya Patel + * + * Copyright (C) 2021 Lite-On Technology Corp (Singapore) + * Author: Shi Zhigang + * + * IIO driver for LTRF216A (7-bit I2C slave address 0x53). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#define LTRF216A_ALS_RESET_MASK BIT(4) +#define LTRF216A_ALS_DATA_STATUS BIT(3) +#define LTRF216A_ALS_ENABLE_MASK BIT(1) +#define LTRF216A_MAIN_CTRL 0x00 +#define LTRF216A_ALS_MEAS_RES 0x04 +#define LTRF216A_ALS_GAIN 0x05 +#define LTRF216A_PART_ID 0x06 +#define LTRF216A_MAIN_STATUS 0x07 +#define LTRF216A_ALS_CLEAR_DATA_0 0x0a +#define LTRF216A_ALS_CLEAR_DATA_1 0x0b +#define LTRF216A_ALS_CLEAR_DATA_2 0x0c +#define LTRF216A_ALS_DATA_0 0x0d +#define LTRF216A_ALS_DATA_1 0x0e +#define LTRF216A_ALS_DATA_2 0x0f +#define LTRF216A_INT_CFG 0x19 +#define LTRF216A_INT_PST 0x1a +#define LTRF216A_ALS_THRES_UP_0 0x21 +#define LTRF216A_ALS_THRES_UP_1 0x22 +#define LTRF216A_ALS_THRES_UP_2 0x23 +#define LTRF216A_ALS_THRES_LOW_0 0x24 +#define LTRF216A_ALS_THRES_LOW_1 0x25 +#define LTRF216A_ALS_THRES_LOW_2 0x26 +#define LTRF216A_ALS_READ_DATA_DELAY_US 20000 + +static const int ltrf216a_int_time_available[][2] = { + { 0, 400000 }, + { 0, 200000 }, + { 0, 100000 }, + { 0, 50000 }, + { 0, 25000 }, +}; + +static const int ltrf216a_int_time_reg[][2] = { + { 400, 0x03 }, + { 200, 0x13 }, + { 100, 0x22 }, + { 50, 0x31 }, + { 25, 0x40 }, +}; + +struct ltr_chip_info { + /* Chip contains CLEAR_DATA_0/1/2 registers at offset 0xa..0xc */ + bool has_clear_data; + /* Lux calculation multiplier for ALS data */ + int lux_multiplier; +}; + +/* + * Window Factor is needed when the device is under Window glass + * with coated tinted ink. This is to compensate for the light loss + * due to the lower transmission rate of the window glass and helps + * in calculating lux. + */ +#define LTRF216A_WIN_FAC 1 + +struct ltrf216a_data { + struct regmap *regmap; + struct i2c_client *client; + const struct ltr_chip_info *info; + u32 int_time; + u16 int_time_fac; + u8 als_gain_fac; + /* + * Protects regmap accesses and makes sure integration time + * remains constant during the measurement of lux. + */ + struct mutex lock; +}; + +static const struct iio_chan_spec ltrf216a_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_INT_TIME), + }, +}; + +static void ltrf216a_reset(struct iio_dev *indio_dev) +{ + struct ltrf216a_data *data = iio_priv(indio_dev); + + /* reset sensor, chip fails to respond to this, so ignore any errors */ + regmap_write(data->regmap, LTRF216A_MAIN_CTRL, LTRF216A_ALS_RESET_MASK); + + /* reset time */ + usleep_range(1000, 2000); +} + +static int ltrf216a_enable(struct iio_dev *indio_dev) +{ + struct ltrf216a_data *data = iio_priv(indio_dev); + struct device *dev = &data->client->dev; + int ret; + + /* enable sensor */ + ret = regmap_set_bits(data->regmap, + LTRF216A_MAIN_CTRL, LTRF216A_ALS_ENABLE_MASK); + if (ret) { + dev_err(dev, "failed to enable sensor: %d\n", ret); + return ret; + } + + /* sleep for one integration cycle after enabling the device */ + msleep(ltrf216a_int_time_reg[0][0]); + + return 0; +} + +static int ltrf216a_disable(struct iio_dev *indio_dev) +{ + struct ltrf216a_data *data = iio_priv(indio_dev); + struct device *dev = &data->client->dev; + int ret; + + ret = regmap_write(data->regmap, LTRF216A_MAIN_CTRL, 0); + if (ret) + dev_err(dev, "failed to disable sensor: %d\n", ret); + + return ret; +} + +static void ltrf216a_cleanup(void *data) +{ + struct iio_dev *indio_dev = data; + + ltrf216a_disable(indio_dev); +} + +static int ltrf216a_set_int_time(struct ltrf216a_data *data, int itime) +{ + struct device *dev = &data->client->dev; + unsigned int i; + u8 reg_val; + int ret; + + for (i = 0; i < ARRAY_SIZE(ltrf216a_int_time_available); i++) { + if (ltrf216a_int_time_available[i][1] == itime) + break; + } + if (i == ARRAY_SIZE(ltrf216a_int_time_available)) + return -EINVAL; + + reg_val = ltrf216a_int_time_reg[i][1]; + + ret = regmap_write(data->regmap, LTRF216A_ALS_MEAS_RES, reg_val); + if (ret) { + dev_err(dev, "failed to set integration time: %d\n", ret); + return ret; + } + + data->int_time_fac = ltrf216a_int_time_reg[i][0]; + data->int_time = itime; + + return 0; +} + +static int ltrf216a_get_int_time(struct ltrf216a_data *data, + int *val, int *val2) +{ + *val = 0; + *val2 = data->int_time; + return IIO_VAL_INT_PLUS_MICRO; +} + +static int ltrf216a_set_power_state(struct ltrf216a_data *data, bool on) +{ + struct device *dev = &data->client->dev; + int ret = 0; + + if (on) { + ret = pm_runtime_resume_and_get(dev); + if (ret) { + dev_err(dev, "failed to resume runtime PM: %d\n", ret); + return ret; + } + } else { + pm_runtime_put_autosuspend(dev); + } + + return ret; +} + +static int ltrf216a_read_data(struct ltrf216a_data *data, u8 addr) +{ + struct device *dev = &data->client->dev; + int ret, val; + u8 buf[3]; + + ret = regmap_read_poll_timeout(data->regmap, LTRF216A_MAIN_STATUS, + val, val & LTRF216A_ALS_DATA_STATUS, + LTRF216A_ALS_READ_DATA_DELAY_US, + LTRF216A_ALS_READ_DATA_DELAY_US * 50); + if (ret) { + dev_err(dev, "failed to wait for measurement data: %d\n", ret); + return ret; + } + + ret = regmap_bulk_read(data->regmap, addr, buf, sizeof(buf)); + if (ret) { + dev_err(dev, "failed to read measurement data: %d\n", ret); + return ret; + } + + return get_unaligned_le24(&buf[0]); +} + +static int ltrf216a_get_lux(struct ltrf216a_data *data) +{ + int ret, greendata; + u64 lux; + + ret = ltrf216a_set_power_state(data, true); + if (ret) + return ret; + + greendata = ltrf216a_read_data(data, LTRF216A_ALS_DATA_0); + ltrf216a_set_power_state(data, false); + if (greendata < 0) + return greendata; + + lux = greendata * data->info->lux_multiplier * LTRF216A_WIN_FAC; + + return lux; +} + +static int ltrf216a_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct ltrf216a_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = ltrf216a_set_power_state(data, true); + if (ret) + return ret; + mutex_lock(&data->lock); + ret = ltrf216a_read_data(data, LTRF216A_ALS_DATA_0); + mutex_unlock(&data->lock); + ltrf216a_set_power_state(data, false); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_PROCESSED: + mutex_lock(&data->lock); + ret = ltrf216a_get_lux(data); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + *val = ret; + *val2 = data->als_gain_fac * data->int_time_fac; + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_INT_TIME: + mutex_lock(&data->lock); + ret = ltrf216a_get_int_time(data, val, val2); + mutex_unlock(&data->lock); + return ret; + default: + return -EINVAL; + } +} + +static int ltrf216a_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct ltrf216a_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val != 0) + return -EINVAL; + mutex_lock(&data->lock); + ret = ltrf216a_set_int_time(data, val2); + mutex_unlock(&data->lock); + return ret; + default: + return -EINVAL; + } +} + +static int ltrf216a_read_available(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + *length = ARRAY_SIZE(ltrf216a_int_time_available) * 2; + *vals = (const int *)ltrf216a_int_time_available; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const struct iio_info ltrf216a_info = { + .read_raw = ltrf216a_read_raw, + .write_raw = ltrf216a_write_raw, + .read_avail = ltrf216a_read_available, +}; + +static bool ltrf216a_readable_reg(struct device *dev, unsigned int reg) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct ltrf216a_data *data = iio_priv(indio_dev); + + switch (reg) { + case LTRF216A_MAIN_CTRL: + case LTRF216A_ALS_MEAS_RES: + case LTRF216A_ALS_GAIN: + case LTRF216A_PART_ID: + case LTRF216A_MAIN_STATUS: + case LTRF216A_ALS_DATA_0: + case LTRF216A_ALS_DATA_1: + case LTRF216A_ALS_DATA_2: + case LTRF216A_INT_CFG: + case LTRF216A_INT_PST: + case LTRF216A_ALS_THRES_UP_0: + case LTRF216A_ALS_THRES_UP_1: + case LTRF216A_ALS_THRES_UP_2: + case LTRF216A_ALS_THRES_LOW_0: + case LTRF216A_ALS_THRES_LOW_1: + case LTRF216A_ALS_THRES_LOW_2: + return true; + case LTRF216A_ALS_CLEAR_DATA_0: + case LTRF216A_ALS_CLEAR_DATA_1: + case LTRF216A_ALS_CLEAR_DATA_2: + return data->info->has_clear_data; + default: + return false; + } +} + +static bool ltrf216a_writable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LTRF216A_MAIN_CTRL: + case LTRF216A_ALS_MEAS_RES: + case LTRF216A_ALS_GAIN: + case LTRF216A_INT_CFG: + case LTRF216A_INT_PST: + case LTRF216A_ALS_THRES_UP_0: + case LTRF216A_ALS_THRES_UP_1: + case LTRF216A_ALS_THRES_UP_2: + case LTRF216A_ALS_THRES_LOW_0: + case LTRF216A_ALS_THRES_LOW_1: + case LTRF216A_ALS_THRES_LOW_2: + return true; + default: + return false; + } +} + +static bool ltrf216a_volatile_reg(struct device *dev, unsigned int reg) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct ltrf216a_data *data = iio_priv(indio_dev); + + switch (reg) { + case LTRF216A_MAIN_STATUS: + case LTRF216A_ALS_DATA_0: + case LTRF216A_ALS_DATA_1: + case LTRF216A_ALS_DATA_2: + return true; + /* + * If these registers are not present on a chip (like LTR-308), + * the missing registers are not considered volatile. + */ + case LTRF216A_ALS_CLEAR_DATA_0: + case LTRF216A_ALS_CLEAR_DATA_1: + case LTRF216A_ALS_CLEAR_DATA_2: + return data->info->has_clear_data; + default: + return false; + } +} + +static bool ltrf216a_precious_reg(struct device *dev, unsigned int reg) +{ + return reg == LTRF216A_MAIN_STATUS; +} + +static const struct regmap_config ltrf216a_regmap_config = { + .name = "ltrf216a", + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_RBTREE, + .max_register = LTRF216A_ALS_THRES_LOW_2, + .readable_reg = ltrf216a_readable_reg, + .writeable_reg = ltrf216a_writable_reg, + .volatile_reg = ltrf216a_volatile_reg, + .precious_reg = ltrf216a_precious_reg, + .disable_locking = true, +}; + +static int ltrf216a_probe(struct i2c_client *client) +{ + struct ltrf216a_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + + data->regmap = devm_regmap_init_i2c(client, <rf216a_regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(&client->dev, PTR_ERR(data->regmap), + "regmap initialization failed\n"); + + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->info = i2c_get_match_data(client); + + mutex_init(&data->lock); + + indio_dev->info = <rf216a_info; + indio_dev->name = "ltrf216a"; + indio_dev->channels = ltrf216a_channels; + indio_dev->num_channels = ARRAY_SIZE(ltrf216a_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = pm_runtime_set_active(&client->dev); + if (ret) + return ret; + + /* reset sensor, chip fails to respond to this, so ignore any errors */ + ltrf216a_reset(indio_dev); + + ret = regmap_reinit_cache(data->regmap, <rf216a_regmap_config); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to reinit regmap cache\n"); + + ret = ltrf216a_enable(indio_dev); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&client->dev, ltrf216a_cleanup, + indio_dev); + if (ret) + return ret; + + ret = devm_pm_runtime_enable(&client->dev); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to enable runtime PM\n"); + + pm_runtime_set_autosuspend_delay(&client->dev, 1000); + pm_runtime_use_autosuspend(&client->dev); + + data->int_time = 100000; + data->int_time_fac = 100; + data->als_gain_fac = 3; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static int ltrf216a_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct ltrf216a_data *data = iio_priv(indio_dev); + int ret; + + ret = ltrf216a_disable(indio_dev); + if (ret) + return ret; + + regcache_cache_only(data->regmap, true); + + return 0; +} + +static int ltrf216a_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct ltrf216a_data *data = iio_priv(indio_dev); + int ret; + + regcache_cache_only(data->regmap, false); + regcache_mark_dirty(data->regmap); + ret = regcache_sync(data->regmap); + if (ret) + goto cache_only; + + ret = ltrf216a_enable(indio_dev); + if (ret) + goto cache_only; + + return 0; + +cache_only: + regcache_cache_only(data->regmap, true); + + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(ltrf216a_pm_ops, ltrf216a_runtime_suspend, + ltrf216a_runtime_resume, NULL); + +static const struct ltr_chip_info ltr308_chip_info = { + .has_clear_data = false, + .lux_multiplier = 60, +}; + +static const struct ltr_chip_info ltrf216a_chip_info = { + .has_clear_data = true, + .lux_multiplier = 45, +}; + +static const struct i2c_device_id ltrf216a_id[] = { + { .name = "ltr308", .driver_data = (kernel_ulong_t)<r308_chip_info }, + { .name = "ltrf216a", .driver_data = (kernel_ulong_t)<rf216a_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltrf216a_id); + +static const struct of_device_id ltrf216a_of_match[] = { + { .compatible = "liteon,ltr308", .data = <r308_chip_info }, + { .compatible = "liteon,ltrf216a", .data = <rf216a_chip_info }, + /* For Valve's Steamdeck device, an ACPI platform using PRP0001 */ + { .compatible = "ltr,ltrf216a", .data = <rf216a_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ltrf216a_of_match); + +static struct i2c_driver ltrf216a_driver = { + .driver = { + .name = "ltrf216a", + .pm = pm_ptr(<rf216a_pm_ops), + .of_match_table = ltrf216a_of_match, + }, + .probe = ltrf216a_probe, + .id_table = ltrf216a_id, +}; +module_i2c_driver(ltrf216a_driver); + +MODULE_AUTHOR("Shreeya Patel "); +MODULE_AUTHOR("Shi Zhigang "); +MODULE_DESCRIPTION("LTRF216A ambient light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/lv0104cs.c b/drivers/iio/light/lv0104cs.c new file mode 100644 index 000000000..eba82c334 --- /dev/null +++ b/drivers/iio/light/lv0104cs.c @@ -0,0 +1,529 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * lv0104cs.c: LV0104CS Ambient Light Sensor Driver + * + * Copyright (C) 2018 + * Author: Jeff LaBundy + * + * 7-bit I2C slave address: 0x13 + * + * Link to data sheet: https://www.onsemi.com/pub/Collateral/LV0104CS-D.PDF + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define LV0104CS_REGVAL_MEASURE 0xE0 +#define LV0104CS_REGVAL_SLEEP 0x00 + +#define LV0104CS_SCALE_0_25X 0 +#define LV0104CS_SCALE_1X 1 +#define LV0104CS_SCALE_2X 2 +#define LV0104CS_SCALE_8X 3 +#define LV0104CS_SCALE_SHIFT 3 + +#define LV0104CS_INTEG_12_5MS 0 +#define LV0104CS_INTEG_100MS 1 +#define LV0104CS_INTEG_200MS 2 +#define LV0104CS_INTEG_SHIFT 1 + +#define LV0104CS_CALIBSCALE_UNITY 31 + +struct lv0104cs_private { + struct i2c_client *client; + struct mutex lock; + u8 calibscale; + u8 scale; + u8 int_time; +}; + +struct lv0104cs_mapping { + int val; + int val2; + u8 regval; +}; + +static const struct lv0104cs_mapping lv0104cs_calibscales[] = { + { 0, 666666, 0x81 }, + { 0, 800000, 0x82 }, + { 0, 857142, 0x83 }, + { 0, 888888, 0x84 }, + { 0, 909090, 0x85 }, + { 0, 923076, 0x86 }, + { 0, 933333, 0x87 }, + { 0, 941176, 0x88 }, + { 0, 947368, 0x89 }, + { 0, 952380, 0x8A }, + { 0, 956521, 0x8B }, + { 0, 960000, 0x8C }, + { 0, 962962, 0x8D }, + { 0, 965517, 0x8E }, + { 0, 967741, 0x8F }, + { 0, 969696, 0x90 }, + { 0, 971428, 0x91 }, + { 0, 972972, 0x92 }, + { 0, 974358, 0x93 }, + { 0, 975609, 0x94 }, + { 0, 976744, 0x95 }, + { 0, 977777, 0x96 }, + { 0, 978723, 0x97 }, + { 0, 979591, 0x98 }, + { 0, 980392, 0x99 }, + { 0, 981132, 0x9A }, + { 0, 981818, 0x9B }, + { 0, 982456, 0x9C }, + { 0, 983050, 0x9D }, + { 0, 983606, 0x9E }, + { 0, 984126, 0x9F }, + { 1, 0, 0x80 }, + { 1, 16129, 0xBF }, + { 1, 16666, 0xBE }, + { 1, 17241, 0xBD }, + { 1, 17857, 0xBC }, + { 1, 18518, 0xBB }, + { 1, 19230, 0xBA }, + { 1, 20000, 0xB9 }, + { 1, 20833, 0xB8 }, + { 1, 21739, 0xB7 }, + { 1, 22727, 0xB6 }, + { 1, 23809, 0xB5 }, + { 1, 24999, 0xB4 }, + { 1, 26315, 0xB3 }, + { 1, 27777, 0xB2 }, + { 1, 29411, 0xB1 }, + { 1, 31250, 0xB0 }, + { 1, 33333, 0xAF }, + { 1, 35714, 0xAE }, + { 1, 38461, 0xAD }, + { 1, 41666, 0xAC }, + { 1, 45454, 0xAB }, + { 1, 50000, 0xAA }, + { 1, 55555, 0xA9 }, + { 1, 62500, 0xA8 }, + { 1, 71428, 0xA7 }, + { 1, 83333, 0xA6 }, + { 1, 100000, 0xA5 }, + { 1, 125000, 0xA4 }, + { 1, 166666, 0xA3 }, + { 1, 250000, 0xA2 }, + { 1, 500000, 0xA1 }, +}; + +static const struct lv0104cs_mapping lv0104cs_scales[] = { + { 0, 250000, LV0104CS_SCALE_0_25X << LV0104CS_SCALE_SHIFT }, + { 1, 0, LV0104CS_SCALE_1X << LV0104CS_SCALE_SHIFT }, + { 2, 0, LV0104CS_SCALE_2X << LV0104CS_SCALE_SHIFT }, + { 8, 0, LV0104CS_SCALE_8X << LV0104CS_SCALE_SHIFT }, +}; + +static const struct lv0104cs_mapping lv0104cs_int_times[] = { + { 0, 12500, LV0104CS_INTEG_12_5MS << LV0104CS_INTEG_SHIFT }, + { 0, 100000, LV0104CS_INTEG_100MS << LV0104CS_INTEG_SHIFT }, + { 0, 200000, LV0104CS_INTEG_200MS << LV0104CS_INTEG_SHIFT }, +}; + +static int lv0104cs_write_reg(struct i2c_client *client, u8 regval) +{ + int ret; + + ret = i2c_master_send(client, (char *)®val, sizeof(regval)); + if (ret < 0) + return ret; + if (ret != sizeof(regval)) + return -EIO; + + return 0; +} + +static int lv0104cs_read_adc(struct i2c_client *client, u16 *adc_output) +{ + __be16 regval; + int ret; + + ret = i2c_master_recv(client, (char *)®val, sizeof(regval)); + if (ret < 0) + return ret; + if (ret != sizeof(regval)) + return -EIO; + + *adc_output = be16_to_cpu(regval); + + return 0; +} + +static int lv0104cs_get_lux(struct lv0104cs_private *lv0104cs, + int *val, int *val2) +{ + u8 regval = LV0104CS_REGVAL_MEASURE; + u16 adc_output; + int ret; + + regval |= lv0104cs_scales[lv0104cs->scale].regval; + regval |= lv0104cs_int_times[lv0104cs->int_time].regval; + ret = lv0104cs_write_reg(lv0104cs->client, regval); + if (ret) + return ret; + + /* wait for integration time to pass (with margin) */ + switch (lv0104cs->int_time) { + case LV0104CS_INTEG_12_5MS: + msleep(50); + break; + + case LV0104CS_INTEG_100MS: + msleep(150); + break; + + case LV0104CS_INTEG_200MS: + msleep(250); + break; + + default: + return -EINVAL; + } + + ret = lv0104cs_read_adc(lv0104cs->client, &adc_output); + if (ret) + return ret; + + ret = lv0104cs_write_reg(lv0104cs->client, LV0104CS_REGVAL_SLEEP); + if (ret) + return ret; + + /* convert ADC output to lux */ + switch (lv0104cs->scale) { + case LV0104CS_SCALE_0_25X: + *val = adc_output * 4; + *val2 = 0; + return 0; + + case LV0104CS_SCALE_1X: + *val = adc_output; + *val2 = 0; + return 0; + + case LV0104CS_SCALE_2X: + *val = adc_output / 2; + *val2 = (adc_output % 2) * 500000; + return 0; + + case LV0104CS_SCALE_8X: + *val = adc_output / 8; + *val2 = (adc_output % 8) * 125000; + return 0; + + default: + return -EINVAL; + } +} + +static int lv0104cs_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct lv0104cs_private *lv0104cs = iio_priv(indio_dev); + int ret; + + if (chan->type != IIO_LIGHT) + return -EINVAL; + + mutex_lock(&lv0104cs->lock); + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ret = lv0104cs_get_lux(lv0104cs, val, val2); + if (ret) + goto err_mutex; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + + case IIO_CHAN_INFO_CALIBSCALE: + *val = lv0104cs_calibscales[lv0104cs->calibscale].val; + *val2 = lv0104cs_calibscales[lv0104cs->calibscale].val2; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + + case IIO_CHAN_INFO_SCALE: + *val = lv0104cs_scales[lv0104cs->scale].val; + *val2 = lv0104cs_scales[lv0104cs->scale].val2; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + + case IIO_CHAN_INFO_INT_TIME: + *val = lv0104cs_int_times[lv0104cs->int_time].val; + *val2 = lv0104cs_int_times[lv0104cs->int_time].val2; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + + default: + ret = -EINVAL; + } + +err_mutex: + mutex_unlock(&lv0104cs->lock); + + return ret; +} + +static int lv0104cs_set_calibscale(struct lv0104cs_private *lv0104cs, + int val, int val2) +{ + int calibscale = val * 1000000 + val2; + int floor, ceil, mid; + int ret, i, index; + + /* round to nearest quantized calibscale (sensitivity) */ + for (i = 0; i < ARRAY_SIZE(lv0104cs_calibscales) - 1; i++) { + floor = lv0104cs_calibscales[i].val * 1000000 + + lv0104cs_calibscales[i].val2; + ceil = lv0104cs_calibscales[i + 1].val * 1000000 + + lv0104cs_calibscales[i + 1].val2; + mid = (floor + ceil) / 2; + + /* round down */ + if (calibscale >= floor && calibscale < mid) { + index = i; + break; + } + + /* round up */ + if (calibscale >= mid && calibscale <= ceil) { + index = i + 1; + break; + } + } + + if (i == ARRAY_SIZE(lv0104cs_calibscales) - 1) + return -EINVAL; + + mutex_lock(&lv0104cs->lock); + + /* set calibscale (sensitivity) */ + ret = lv0104cs_write_reg(lv0104cs->client, + lv0104cs_calibscales[index].regval); + if (ret) + goto err_mutex; + + lv0104cs->calibscale = index; + +err_mutex: + mutex_unlock(&lv0104cs->lock); + + return ret; +} + +static int lv0104cs_set_scale(struct lv0104cs_private *lv0104cs, + int val, int val2) +{ + int i; + + /* hard matching */ + for (i = 0; i < ARRAY_SIZE(lv0104cs_scales); i++) { + if (val != lv0104cs_scales[i].val) + continue; + + if (val2 == lv0104cs_scales[i].val2) + break; + } + + if (i == ARRAY_SIZE(lv0104cs_scales)) + return -EINVAL; + + mutex_lock(&lv0104cs->lock); + lv0104cs->scale = i; + mutex_unlock(&lv0104cs->lock); + + return 0; +} + +static int lv0104cs_set_int_time(struct lv0104cs_private *lv0104cs, + int val, int val2) +{ + int i; + + /* hard matching */ + for (i = 0; i < ARRAY_SIZE(lv0104cs_int_times); i++) { + if (val != lv0104cs_int_times[i].val) + continue; + + if (val2 == lv0104cs_int_times[i].val2) + break; + } + + if (i == ARRAY_SIZE(lv0104cs_int_times)) + return -EINVAL; + + mutex_lock(&lv0104cs->lock); + lv0104cs->int_time = i; + mutex_unlock(&lv0104cs->lock); + + return 0; +} + +static int lv0104cs_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct lv0104cs_private *lv0104cs = iio_priv(indio_dev); + + if (chan->type != IIO_LIGHT) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + return lv0104cs_set_calibscale(lv0104cs, val, val2); + + case IIO_CHAN_INFO_SCALE: + return lv0104cs_set_scale(lv0104cs, val, val2); + + case IIO_CHAN_INFO_INT_TIME: + return lv0104cs_set_int_time(lv0104cs, val, val2); + + default: + return -EINVAL; + } +} + +static ssize_t lv0104cs_show_calibscale_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t len = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(lv0104cs_calibscales); i++) { + len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06d ", + lv0104cs_calibscales[i].val, + lv0104cs_calibscales[i].val2); + } + + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t lv0104cs_show_scale_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t len = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(lv0104cs_scales); i++) { + len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06d ", + lv0104cs_scales[i].val, + lv0104cs_scales[i].val2); + } + + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t lv0104cs_show_int_time_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t len = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(lv0104cs_int_times); i++) { + len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06d ", + lv0104cs_int_times[i].val, + lv0104cs_int_times[i].val2); + } + + buf[len - 1] = '\n'; + + return len; +} + +static IIO_DEVICE_ATTR(calibscale_available, 0444, + lv0104cs_show_calibscale_avail, NULL, 0); +static IIO_DEVICE_ATTR(scale_available, 0444, + lv0104cs_show_scale_avail, NULL, 0); +static IIO_DEV_ATTR_INT_TIME_AVAIL(lv0104cs_show_int_time_avail); + +static struct attribute *lv0104cs_attributes[] = { + &iio_dev_attr_calibscale_available.dev_attr.attr, + &iio_dev_attr_scale_available.dev_attr.attr, + &iio_dev_attr_integration_time_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group lv0104cs_attribute_group = { + .attrs = lv0104cs_attributes, +}; + +static const struct iio_info lv0104cs_info = { + .attrs = &lv0104cs_attribute_group, + .read_raw = &lv0104cs_read_raw, + .write_raw = &lv0104cs_write_raw, +}; + +static const struct iio_chan_spec lv0104cs_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME), + }, +}; + +static int lv0104cs_probe(struct i2c_client *client) +{ + struct iio_dev *indio_dev; + struct lv0104cs_private *lv0104cs; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*lv0104cs)); + if (!indio_dev) + return -ENOMEM; + + lv0104cs = iio_priv(indio_dev); + + i2c_set_clientdata(client, lv0104cs); + lv0104cs->client = client; + + mutex_init(&lv0104cs->lock); + + lv0104cs->calibscale = LV0104CS_CALIBSCALE_UNITY; + lv0104cs->scale = LV0104CS_SCALE_1X; + lv0104cs->int_time = LV0104CS_INTEG_200MS; + + ret = lv0104cs_write_reg(lv0104cs->client, + lv0104cs_calibscales[LV0104CS_CALIBSCALE_UNITY].regval); + if (ret) + return ret; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = lv0104cs_channels; + indio_dev->num_channels = ARRAY_SIZE(lv0104cs_channels); + indio_dev->name = client->name; + indio_dev->info = &lv0104cs_info; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id lv0104cs_id[] = { + { .name = "lv0104cs" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lv0104cs_id); + +static struct i2c_driver lv0104cs_i2c_driver = { + .driver = { + .name = "lv0104cs", + }, + .id_table = lv0104cs_id, + .probe = lv0104cs_probe, +}; +module_i2c_driver(lv0104cs_i2c_driver); + +MODULE_AUTHOR("Jeff LaBundy "); +MODULE_DESCRIPTION("LV0104CS Ambient Light Sensor Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/max44000.c b/drivers/iio/light/max44000.c new file mode 100644 index 000000000..8c344f1b3 --- /dev/null +++ b/drivers/iio/light/max44000.c @@ -0,0 +1,624 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MAX44000 Ambient and Infrared Proximity Sensor + * + * Copyright (c) 2016, Intel Corporation. + * + * Data sheet: https://datasheets.maximintegrated.com/en/ds/MAX44000.pdf + * + * 7-bit I2C slave address 0x4a + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX44000_DRV_NAME "max44000" + +/* Registers in datasheet order */ +#define MAX44000_REG_STATUS 0x00 +#define MAX44000_REG_CFG_MAIN 0x01 +#define MAX44000_REG_CFG_RX 0x02 +#define MAX44000_REG_CFG_TX 0x03 +#define MAX44000_REG_ALS_DATA_HI 0x04 +#define MAX44000_REG_ALS_DATA_LO 0x05 +#define MAX44000_REG_PRX_DATA 0x16 +#define MAX44000_REG_ALS_UPTHR_HI 0x06 +#define MAX44000_REG_ALS_UPTHR_LO 0x07 +#define MAX44000_REG_ALS_LOTHR_HI 0x08 +#define MAX44000_REG_ALS_LOTHR_LO 0x09 +#define MAX44000_REG_PST 0x0a +#define MAX44000_REG_PRX_IND 0x0b +#define MAX44000_REG_PRX_THR 0x0c +#define MAX44000_REG_TRIM_GAIN_GREEN 0x0f +#define MAX44000_REG_TRIM_GAIN_IR 0x10 + +/* REG_CFG bits */ +#define MAX44000_CFG_ALSINTE 0x01 +#define MAX44000_CFG_PRXINTE 0x02 +#define MAX44000_CFG_MASK 0x1c +#define MAX44000_CFG_MODE_SHUTDOWN 0x00 +#define MAX44000_CFG_MODE_ALS_GIR 0x04 +#define MAX44000_CFG_MODE_ALS_G 0x08 +#define MAX44000_CFG_MODE_ALS_IR 0x0c +#define MAX44000_CFG_MODE_ALS_PRX 0x10 +#define MAX44000_CFG_MODE_PRX 0x14 +#define MAX44000_CFG_TRIM 0x20 + +/* + * Upper 4 bits are not documented but start as 1 on powerup + * Setting them to 0 causes proximity to misbehave so set them to 1 + */ +#define MAX44000_REG_CFG_RX_DEFAULT 0xf0 + +/* REG_RX bits */ +#define MAX44000_CFG_RX_ALSTIM_MASK 0x0c +#define MAX44000_CFG_RX_ALSTIM_SHIFT 2 +#define MAX44000_CFG_RX_ALSPGA_MASK 0x03 +#define MAX44000_CFG_RX_ALSPGA_SHIFT 0 + +/* REG_TX bits */ +#define MAX44000_LED_CURRENT_MASK 0xf +#define MAX44000_LED_CURRENT_MAX 11 +#define MAX44000_LED_CURRENT_DEFAULT 6 + +#define MAX44000_ALSDATA_OVERFLOW 0x4000 + +struct max44000_data { + struct mutex lock; + struct regmap *regmap; +}; + +/* Default scale is set to the minimum of 0.03125 or 1 / (1 << 5) lux */ +#define MAX44000_ALS_TO_LUX_DEFAULT_FRACTION_LOG2 5 + +/* Scale can be multiplied by up to 128x via ALSPGA for measurement gain */ +static const int max44000_alspga_shift[] = {0, 2, 4, 7}; +#define MAX44000_ALSPGA_MAX_SHIFT 7 + +/* + * Scale can be multiplied by up to 64x via ALSTIM because of lost resolution + * + * This scaling factor is hidden from userspace and instead accounted for when + * reading raw values from the device. + * + * This makes it possible to cleanly expose ALSPGA as IIO_CHAN_INFO_SCALE and + * ALSTIM as IIO_CHAN_INFO_INT_TIME without the values affecting each other. + * + * Handling this internally is also required for buffer support because the + * channel's scan_type can't be modified dynamically. + */ +#define MAX44000_ALSTIM_SHIFT(alstim) (2 * (alstim)) + +/* Available integration times with pretty manual alignment: */ +static const int max44000_int_time_avail_ns_array[] = { + 100000000, + 25000000, + 6250000, + 1562500, +}; +static const char max44000_int_time_avail_str[] = + "0.100 " + "0.025 " + "0.00625 " + "0.0015625"; + +/* Available scales (internal to ulux) with pretty manual alignment: */ +static const int max44000_scale_avail_ulux_array[] = { + 31250, + 125000, + 500000, + 4000000, +}; +static const char max44000_scale_avail_str[] = + "0.03125 " + "0.125 " + "0.5 " + "4"; + +#define MAX44000_SCAN_INDEX_ALS 0 +#define MAX44000_SCAN_INDEX_PRX 1 + +static const struct iio_chan_spec max44000_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME), + .scan_index = MAX44000_SCAN_INDEX_ALS, + .scan_type = { + .sign = 'u', + .realbits = 14, + .storagebits = 16, + } + }, + { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_index = MAX44000_SCAN_INDEX_PRX, + .scan_type = { + .sign = 'u', + .realbits = 8, + .storagebits = 16, + } + }, + IIO_CHAN_SOFT_TIMESTAMP(2), + { + .type = IIO_CURRENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .extend_name = "led", + .output = 1, + .scan_index = -1, + }, +}; + +static int max44000_read_alstim(struct max44000_data *data) +{ + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, MAX44000_REG_CFG_RX, &val); + if (ret < 0) + return ret; + return (val & MAX44000_CFG_RX_ALSTIM_MASK) >> MAX44000_CFG_RX_ALSTIM_SHIFT; +} + +static int max44000_write_alstim(struct max44000_data *data, int val) +{ + return regmap_write_bits(data->regmap, MAX44000_REG_CFG_RX, + MAX44000_CFG_RX_ALSTIM_MASK, + val << MAX44000_CFG_RX_ALSTIM_SHIFT); +} + +static int max44000_read_alspga(struct max44000_data *data) +{ + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, MAX44000_REG_CFG_RX, &val); + if (ret < 0) + return ret; + return (val & MAX44000_CFG_RX_ALSPGA_MASK) >> MAX44000_CFG_RX_ALSPGA_SHIFT; +} + +static int max44000_write_alspga(struct max44000_data *data, int val) +{ + return regmap_write_bits(data->regmap, MAX44000_REG_CFG_RX, + MAX44000_CFG_RX_ALSPGA_MASK, + val << MAX44000_CFG_RX_ALSPGA_SHIFT); +} + +static int max44000_read_alsval(struct max44000_data *data) +{ + u16 regval; + __be16 val; + int alstim, ret; + + ret = regmap_bulk_read(data->regmap, MAX44000_REG_ALS_DATA_HI, + &val, sizeof(val)); + if (ret < 0) + return ret; + alstim = ret = max44000_read_alstim(data); + if (ret < 0) + return ret; + + regval = be16_to_cpu(val); + + /* + * Overflow is explained on datasheet page 17. + * + * It's a warning that either the G or IR channel has become saturated + * and that the value in the register is likely incorrect. + * + * The recommendation is to change the scale (ALSPGA). + * The driver just returns the max representable value. + */ + if (regval & MAX44000_ALSDATA_OVERFLOW) + return 0x3FFF; + + return regval << MAX44000_ALSTIM_SHIFT(alstim); +} + +static int max44000_write_led_current_raw(struct max44000_data *data, int val) +{ + /* Maybe we should clamp the value instead? */ + if (val < 0 || val > MAX44000_LED_CURRENT_MAX) + return -ERANGE; + if (val >= 8) + val += 4; + return regmap_write_bits(data->regmap, MAX44000_REG_CFG_TX, + MAX44000_LED_CURRENT_MASK, val); +} + +static int max44000_read_led_current_raw(struct max44000_data *data) +{ + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, MAX44000_REG_CFG_TX, ®val); + if (ret < 0) + return ret; + regval &= MAX44000_LED_CURRENT_MASK; + if (regval >= 8) + regval -= 4; + return regval; +} + +static int max44000_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max44000_data *data = iio_priv(indio_dev); + int alstim, alspga; + unsigned int regval; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_LIGHT: + mutex_lock(&data->lock); + ret = max44000_read_alsval(data); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + + case IIO_PROXIMITY: + mutex_lock(&data->lock); + ret = regmap_read(data->regmap, MAX44000_REG_PRX_DATA, ®val); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + *val = regval; + return IIO_VAL_INT; + + case IIO_CURRENT: + mutex_lock(&data->lock); + ret = max44000_read_led_current_raw(data); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_CURRENT: + /* Output register is in 10s of miliamps */ + *val = 10; + return IIO_VAL_INT; + + case IIO_LIGHT: + mutex_lock(&data->lock); + alspga = ret = max44000_read_alspga(data); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + + /* Avoid negative shifts */ + *val = (1 << MAX44000_ALSPGA_MAX_SHIFT); + *val2 = MAX44000_ALS_TO_LUX_DEFAULT_FRACTION_LOG2 + + MAX44000_ALSPGA_MAX_SHIFT + - max44000_alspga_shift[alspga]; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_INT_TIME: + mutex_lock(&data->lock); + alstim = ret = max44000_read_alstim(data); + mutex_unlock(&data->lock); + + if (ret < 0) + return ret; + *val = 0; + *val2 = max44000_int_time_avail_ns_array[alstim]; + return IIO_VAL_INT_PLUS_NANO; + + default: + return -EINVAL; + } +} + +static int max44000_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct max44000_data *data = iio_priv(indio_dev); + int ret; + + if (mask == IIO_CHAN_INFO_RAW && chan->type == IIO_CURRENT) { + mutex_lock(&data->lock); + ret = max44000_write_led_current_raw(data, val); + mutex_unlock(&data->lock); + return ret; + } else if (mask == IIO_CHAN_INFO_INT_TIME && chan->type == IIO_LIGHT) { + s64 valns = val * NSEC_PER_SEC + val2; + int alstim = find_closest_descending(valns, + max44000_int_time_avail_ns_array, + ARRAY_SIZE(max44000_int_time_avail_ns_array)); + mutex_lock(&data->lock); + ret = max44000_write_alstim(data, alstim); + mutex_unlock(&data->lock); + return ret; + } else if (mask == IIO_CHAN_INFO_SCALE && chan->type == IIO_LIGHT) { + s64 valus = val * USEC_PER_SEC + val2; + int alspga = find_closest(valus, + max44000_scale_avail_ulux_array, + ARRAY_SIZE(max44000_scale_avail_ulux_array)); + mutex_lock(&data->lock); + ret = max44000_write_alspga(data, alspga); + mutex_unlock(&data->lock); + return ret; + } + + return -EINVAL; +} + +static int max44000_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + if (mask == IIO_CHAN_INFO_INT_TIME && chan->type == IIO_LIGHT) + return IIO_VAL_INT_PLUS_NANO; + else if (mask == IIO_CHAN_INFO_SCALE && chan->type == IIO_LIGHT) + return IIO_VAL_INT_PLUS_MICRO; + else + return IIO_VAL_INT; +} + +static IIO_CONST_ATTR(illuminance_integration_time_available, max44000_int_time_avail_str); +static IIO_CONST_ATTR(illuminance_scale_available, max44000_scale_avail_str); + +static struct attribute *max44000_attributes[] = { + &iio_const_attr_illuminance_integration_time_available.dev_attr.attr, + &iio_const_attr_illuminance_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group max44000_attribute_group = { + .attrs = max44000_attributes, +}; + +static const struct iio_info max44000_info = { + .read_raw = max44000_read_raw, + .write_raw = max44000_write_raw, + .write_raw_get_fmt = max44000_write_raw_get_fmt, + .attrs = &max44000_attribute_group, +}; + +static bool max44000_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX44000_REG_STATUS: + case MAX44000_REG_CFG_MAIN: + case MAX44000_REG_CFG_RX: + case MAX44000_REG_CFG_TX: + case MAX44000_REG_ALS_DATA_HI: + case MAX44000_REG_ALS_DATA_LO: + case MAX44000_REG_PRX_DATA: + case MAX44000_REG_ALS_UPTHR_HI: + case MAX44000_REG_ALS_UPTHR_LO: + case MAX44000_REG_ALS_LOTHR_HI: + case MAX44000_REG_ALS_LOTHR_LO: + case MAX44000_REG_PST: + case MAX44000_REG_PRX_IND: + case MAX44000_REG_PRX_THR: + case MAX44000_REG_TRIM_GAIN_GREEN: + case MAX44000_REG_TRIM_GAIN_IR: + return true; + default: + return false; + } +} + +static bool max44000_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX44000_REG_CFG_MAIN: + case MAX44000_REG_CFG_RX: + case MAX44000_REG_CFG_TX: + case MAX44000_REG_ALS_UPTHR_HI: + case MAX44000_REG_ALS_UPTHR_LO: + case MAX44000_REG_ALS_LOTHR_HI: + case MAX44000_REG_ALS_LOTHR_LO: + case MAX44000_REG_PST: + case MAX44000_REG_PRX_IND: + case MAX44000_REG_PRX_THR: + case MAX44000_REG_TRIM_GAIN_GREEN: + case MAX44000_REG_TRIM_GAIN_IR: + return true; + default: + return false; + } +} + +static bool max44000_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX44000_REG_STATUS: + case MAX44000_REG_ALS_DATA_HI: + case MAX44000_REG_ALS_DATA_LO: + case MAX44000_REG_PRX_DATA: + return true; + default: + return false; + } +} + +static bool max44000_precious_reg(struct device *dev, unsigned int reg) +{ + return reg == MAX44000_REG_STATUS; +} + +static const struct regmap_config max44000_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = MAX44000_REG_PRX_DATA, + .readable_reg = max44000_readable_reg, + .writeable_reg = max44000_writeable_reg, + .volatile_reg = max44000_volatile_reg, + .precious_reg = max44000_precious_reg, + + .use_single_read = true, + .use_single_write = true, + .cache_type = REGCACHE_RBTREE, +}; + +static irqreturn_t max44000_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct max44000_data *data = iio_priv(indio_dev); + int index = 0; + unsigned int regval; + int ret; + struct { + u16 channels[2]; + aligned_s64 ts; + } scan = { }; + + + mutex_lock(&data->lock); + if (test_bit(MAX44000_SCAN_INDEX_ALS, indio_dev->active_scan_mask)) { + ret = max44000_read_alsval(data); + if (ret < 0) + goto out_unlock; + scan.channels[index++] = ret; + } + if (test_bit(MAX44000_SCAN_INDEX_PRX, indio_dev->active_scan_mask)) { + ret = regmap_read(data->regmap, MAX44000_REG_PRX_DATA, ®val); + if (ret < 0) + goto out_unlock; + scan.channels[index] = regval; + } + mutex_unlock(&data->lock); + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; + +out_unlock: + mutex_unlock(&data->lock); + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int max44000_probe(struct i2c_client *client) +{ + struct max44000_data *data; + struct iio_dev *indio_dev; + int ret, reg; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + data = iio_priv(indio_dev); + data->regmap = devm_regmap_init_i2c(client, &max44000_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(&client->dev, "regmap_init failed!\n"); + return PTR_ERR(data->regmap); + } + + mutex_init(&data->lock); + indio_dev->info = &max44000_info; + indio_dev->name = MAX44000_DRV_NAME; + indio_dev->channels = max44000_channels; + indio_dev->num_channels = ARRAY_SIZE(max44000_channels); + + /* + * The device doesn't have a reset function so we just clear some + * important bits at probe time to ensure sane operation. + * + * Since we don't support interrupts/events the threshold values are + * not important. We also don't touch trim values. + */ + + /* Reset ALS scaling bits */ + ret = regmap_write(data->regmap, MAX44000_REG_CFG_RX, + MAX44000_REG_CFG_RX_DEFAULT); + if (ret < 0) { + dev_err(&client->dev, "failed to write default CFG_RX: %d\n", + ret); + return ret; + } + + /* + * By default the LED pulse used for the proximity sensor is disabled. + * Set a middle value so that we get some sort of valid data by default. + */ + ret = max44000_write_led_current_raw(data, MAX44000_LED_CURRENT_DEFAULT); + if (ret < 0) { + dev_err(&client->dev, "failed to write init config: %d\n", ret); + return ret; + } + + /* Reset CFG bits to ALS_PRX mode which allows easy reading of both values. */ + reg = MAX44000_CFG_TRIM | MAX44000_CFG_MODE_ALS_PRX; + ret = regmap_write(data->regmap, MAX44000_REG_CFG_MAIN, reg); + if (ret < 0) { + dev_err(&client->dev, "failed to write init config: %d\n", ret); + return ret; + } + + /* Read status at least once to clear any stale interrupt bits. */ + ret = regmap_read(data->regmap, MAX44000_REG_STATUS, ®); + if (ret < 0) { + dev_err(&client->dev, "failed to read init status: %d\n", ret); + return ret; + } + + ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, NULL, + max44000_trigger_handler, NULL); + if (ret < 0) { + dev_err(&client->dev, "iio triggered buffer setup failed\n"); + return ret; + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id max44000_id[] = { + { .name = "max44000" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max44000_id); + +static const struct acpi_device_id max44000_acpi_match[] = { + {"MAX44000", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, max44000_acpi_match); + +static struct i2c_driver max44000_driver = { + .driver = { + .name = MAX44000_DRV_NAME, + .acpi_match_table = max44000_acpi_match, + }, + .probe = max44000_probe, + .id_table = max44000_id, +}; + +module_i2c_driver(max44000_driver); + +MODULE_AUTHOR("Crestez Dan Leonard "); +MODULE_DESCRIPTION("MAX44000 Ambient and Infrared Proximity Sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/max44009.c b/drivers/iio/light/max44009.c new file mode 100644 index 000000000..82b8a806c --- /dev/null +++ b/drivers/iio/light/max44009.c @@ -0,0 +1,554 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * max44009.c - Support for MAX44009 Ambient Light Sensor + * + * Copyright (c) 2019 Robert Eshleman + * + * Datasheet: https://datasheets.maximintegrated.com/en/ds/MAX44009.pdf + * + * TODO: Support continuous mode and configuring from manual mode to + * automatic mode. + * + * Default I2C address: 0x4a + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX44009_DRV_NAME "max44009" + +/* Registers in datasheet order */ +#define MAX44009_REG_INT_STATUS 0x0 +#define MAX44009_REG_INT_EN 0x1 +#define MAX44009_REG_CFG 0x2 +#define MAX44009_REG_LUX_HI 0x3 +#define MAX44009_REG_LUX_LO 0x4 +#define MAX44009_REG_UPPER_THR 0x5 +#define MAX44009_REG_LOWER_THR 0x6 +#define MAX44009_REG_THR_TIMER 0x7 + +#define MAX44009_CFG_TIM_MASK GENMASK(2, 0) +#define MAX44009_CFG_MAN_MODE_MASK BIT(6) + +/* The maximum rising threshold for the max44009 */ +#define MAX44009_MAXIMUM_THRESHOLD 7520256 + +#define MAX44009_THRESH_EXP_MASK (0xf << 4) +#define MAX44009_THRESH_EXP_RSHIFT 4 +#define MAX44009_THRESH_MANT_LSHIFT 4 +#define MAX44009_THRESH_MANT_MASK 0xf + +#define MAX44009_UPPER_THR_MINIMUM 15 + +/* The max44009 always scales raw readings by 0.045 and is non-configurable */ +#define MAX44009_SCALE_NUMERATOR 45 +#define MAX44009_SCALE_DENOMINATOR 1000 + +/* The fixed-point fractional multiplier for de-scaling threshold values */ +#define MAX44009_FRACT_MULT 1000000 + +static const u32 max44009_int_time_ns_array[] = { + 800000000, + 400000000, + 200000000, + 100000000, + 50000000, /* Manual mode only */ + 25000000, /* Manual mode only */ + 12500000, /* Manual mode only */ + 6250000, /* Manual mode only */ +}; + +static const char max44009_int_time_str[] = + "0.8 " + "0.4 " + "0.2 " + "0.1 " + "0.05 " + "0.025 " + "0.0125 " + "0.00625"; + +struct max44009_data { + struct i2c_client *client; + struct mutex lock; +}; + +static const struct iio_event_spec max44009_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec max44009_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_INT_TIME), + .event_spec = max44009_event_spec, + .num_event_specs = ARRAY_SIZE(max44009_event_spec), + }, +}; + +static int max44009_read_int_time(struct max44009_data *data) +{ + + int ret = i2c_smbus_read_byte_data(data->client, MAX44009_REG_CFG); + + if (ret < 0) + return ret; + + return max44009_int_time_ns_array[ret & MAX44009_CFG_TIM_MASK]; +} + +static int max44009_write_int_time(struct max44009_data *data, + int val, int val2) +{ + struct i2c_client *client = data->client; + int ret, int_time, config; + s64 ns; + + ns = val * NSEC_PER_SEC + val2; + int_time = find_closest_descending( + ns, + max44009_int_time_ns_array, + ARRAY_SIZE(max44009_int_time_ns_array)); + + ret = i2c_smbus_read_byte_data(client, MAX44009_REG_CFG); + if (ret < 0) + return ret; + + config = ret; + config &= int_time; + + /* + * To set the integration time, the device must also be in manual + * mode. + */ + config |= MAX44009_CFG_MAN_MODE_MASK; + + return i2c_smbus_write_byte_data(client, MAX44009_REG_CFG, config); +} + +static int max44009_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct max44009_data *data = iio_priv(indio_dev); + int ret; + + if (mask == IIO_CHAN_INFO_INT_TIME && chan->type == IIO_LIGHT) { + mutex_lock(&data->lock); + ret = max44009_write_int_time(data, val, val2); + mutex_unlock(&data->lock); + return ret; + } + return -EINVAL; +} + +static int max44009_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + return IIO_VAL_INT_PLUS_NANO; +} + +static int max44009_lux_raw(u8 hi, u8 lo) +{ + int mantissa; + int exponent; + + /* + * The mantissa consists of the low nibble of the Lux High Byte + * and the low nibble of the Lux Low Byte. + */ + mantissa = ((hi & 0xf) << 4) | (lo & 0xf); + + /* The exponent byte is just the upper nibble of the Lux High Byte */ + exponent = (hi >> 4) & 0xf; + + /* + * The exponent value is base 2 to the power of the raw exponent byte. + */ + exponent = 1 << exponent; + + return exponent * mantissa; +} + +#define MAX44009_READ_LUX_XFER_LEN (4) + +static int max44009_read_lux_raw(struct max44009_data *data) +{ + int ret; + u8 hireg = MAX44009_REG_LUX_HI; + u8 loreg = MAX44009_REG_LUX_LO; + u8 lo = 0; + u8 hi = 0; + + struct i2c_msg msgs[] = { + { + .addr = data->client->addr, + .flags = 0, + .len = sizeof(hireg), + .buf = &hireg, + }, + { + .addr = data->client->addr, + .flags = I2C_M_RD, + .len = sizeof(hi), + .buf = &hi, + }, + { + .addr = data->client->addr, + .flags = 0, + .len = sizeof(loreg), + .buf = &loreg, + }, + { + .addr = data->client->addr, + .flags = I2C_M_RD, + .len = sizeof(lo), + .buf = &lo, + } + }; + + /* + * Use i2c_transfer instead of smbus read because i2c_transfer + * does NOT use a stop bit between address write and data read. + * Using a stop bit causes disjoint upper/lower byte reads and + * reduces accuracy. + */ + ret = i2c_transfer(data->client->adapter, + msgs, MAX44009_READ_LUX_XFER_LEN); + + if (ret != MAX44009_READ_LUX_XFER_LEN) + return -EIO; + + return max44009_lux_raw(hi, lo); +} + +static int max44009_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct max44009_data *data = iio_priv(indio_dev); + int lux_raw; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_LIGHT: + ret = max44009_read_lux_raw(data); + if (ret < 0) + return ret; + lux_raw = ret; + + *val = lux_raw * MAX44009_SCALE_NUMERATOR; + *val2 = MAX44009_SCALE_DENOMINATOR; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_LIGHT: + ret = max44009_read_int_time(data); + if (ret < 0) + return ret; + + *val2 = ret; + *val = 0; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static IIO_CONST_ATTR(illuminance_integration_time_available, + max44009_int_time_str); + +static struct attribute *max44009_attributes[] = { + &iio_const_attr_illuminance_integration_time_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group max44009_attribute_group = { + .attrs = max44009_attributes, +}; + +static int max44009_threshold_byte_from_fraction(int integral, int fractional) +{ + int mantissa, exp; + + if ((integral <= 0 && fractional <= 0) || + integral > MAX44009_MAXIMUM_THRESHOLD || + (integral == MAX44009_MAXIMUM_THRESHOLD && fractional != 0)) + return -EINVAL; + + /* Reverse scaling of fixed-point integral */ + mantissa = integral * MAX44009_SCALE_DENOMINATOR; + mantissa /= MAX44009_SCALE_NUMERATOR; + + /* Reverse scaling of fixed-point fractional */ + mantissa += fractional / MAX44009_FRACT_MULT * + (MAX44009_SCALE_DENOMINATOR / MAX44009_SCALE_NUMERATOR); + + for (exp = 0; mantissa > 0xff; exp++) + mantissa >>= 1; + + mantissa >>= 4; + mantissa &= 0xf; + exp <<= 4; + + return exp | mantissa; +} + +static int max44009_get_thr_reg(enum iio_event_direction dir) +{ + switch (dir) { + case IIO_EV_DIR_RISING: + return MAX44009_REG_UPPER_THR; + case IIO_EV_DIR_FALLING: + return MAX44009_REG_LOWER_THR; + default: + return -EINVAL; + } +} + +static int max44009_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct max44009_data *data = iio_priv(indio_dev); + int reg, threshold; + + if (info != IIO_EV_INFO_VALUE || chan->type != IIO_LIGHT) + return -EINVAL; + + threshold = max44009_threshold_byte_from_fraction(val, val2); + if (threshold < 0) + return threshold; + + reg = max44009_get_thr_reg(dir); + if (reg < 0) + return reg; + + return i2c_smbus_write_byte_data(data->client, reg, threshold); +} + +static int max44009_read_threshold(struct iio_dev *indio_dev, + enum iio_event_direction dir) +{ + struct max44009_data *data = iio_priv(indio_dev); + int byte, reg; + int mantissa, exponent; + + reg = max44009_get_thr_reg(dir); + if (reg < 0) + return reg; + + byte = i2c_smbus_read_byte_data(data->client, reg); + if (byte < 0) + return byte; + + mantissa = byte & MAX44009_THRESH_MANT_MASK; + mantissa <<= MAX44009_THRESH_MANT_LSHIFT; + + /* + * To get the upper threshold, always adds the minimum upper threshold + * value to the shifted byte value (see datasheet). + */ + if (dir == IIO_EV_DIR_RISING) + mantissa += MAX44009_UPPER_THR_MINIMUM; + + /* + * Exponent is base 2 to the power of the threshold exponent byte + * value + */ + exponent = byte & MAX44009_THRESH_EXP_MASK; + exponent >>= MAX44009_THRESH_EXP_RSHIFT; + + return (1 << exponent) * mantissa; +} + +static int max44009_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + int ret; + int threshold; + + if (chan->type != IIO_LIGHT || type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + ret = max44009_read_threshold(indio_dev, dir); + if (ret < 0) + return ret; + threshold = ret; + + *val = threshold * MAX44009_SCALE_NUMERATOR; + *val2 = MAX44009_SCALE_DENOMINATOR; + + return IIO_VAL_FRACTIONAL; +} + +static int max44009_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct max44009_data *data = iio_priv(indio_dev); + int ret; + + if (chan->type != IIO_LIGHT || type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + ret = i2c_smbus_write_byte_data(data->client, + MAX44009_REG_INT_EN, state); + if (ret < 0) + return ret; + + /* + * Set device to trigger interrupt immediately upon exceeding + * the threshold limit. + */ + return i2c_smbus_write_byte_data(data->client, + MAX44009_REG_THR_TIMER, 0); +} + +static int max44009_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct max44009_data *data = iio_priv(indio_dev); + + if (chan->type != IIO_LIGHT || type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + return i2c_smbus_read_byte_data(data->client, MAX44009_REG_INT_EN); +} + +static const struct iio_info max44009_info = { + .read_raw = max44009_read_raw, + .write_raw = max44009_write_raw, + .write_raw_get_fmt = max44009_write_raw_get_fmt, + .read_event_value = max44009_read_event_value, + .read_event_config = max44009_read_event_config, + .write_event_value = max44009_write_event_value, + .write_event_config = max44009_write_event_config, + .attrs = &max44009_attribute_group, +}; + +static irqreturn_t max44009_threaded_irq_handler(int irq, void *p) +{ + struct iio_dev *indio_dev = p; + struct max44009_data *data = iio_priv(indio_dev); + int ret; + + ret = i2c_smbus_read_byte_data(data->client, MAX44009_REG_INT_STATUS); + if (ret) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static int max44009_probe(struct i2c_client *client) +{ + struct max44009_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + indio_dev->info = &max44009_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = MAX44009_DRV_NAME; + indio_dev->channels = max44009_channels; + indio_dev->num_channels = ARRAY_SIZE(max44009_channels); + mutex_init(&data->lock); + + /* Clear any stale interrupt bit */ + ret = i2c_smbus_read_byte_data(client, MAX44009_REG_CFG); + if (ret < 0) + return ret; + + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, + max44009_threaded_irq_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT | IRQF_SHARED, + "max44009_event", + indio_dev); + if (ret < 0) + return ret; + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct of_device_id max44009_of_match[] = { + { .compatible = "maxim,max44009" }, + { } +}; +MODULE_DEVICE_TABLE(of, max44009_of_match); + +static const struct i2c_device_id max44009_id[] = { + { .name = "max44009" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max44009_id); + +static struct i2c_driver max44009_driver = { + .driver = { + .name = MAX44009_DRV_NAME, + .of_match_table = max44009_of_match, + }, + .probe = max44009_probe, + .id_table = max44009_id, +}; +module_i2c_driver(max44009_driver); + +MODULE_AUTHOR("Robert Eshleman "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MAX44009 ambient light sensor driver"); diff --git a/drivers/iio/light/noa1305.c b/drivers/iio/light/noa1305.c new file mode 100644 index 000000000..6731f1d9e --- /dev/null +++ b/drivers/iio/light/noa1305.c @@ -0,0 +1,337 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Support for ON Semiconductor NOA1305 ambient light sensor + * + * Copyright (C) 2016 Emcraft Systems + * Copyright (C) 2019 Collabora Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define NOA1305_REG_POWER_CONTROL 0x0 +#define NOA1305_POWER_CONTROL_DOWN 0x00 +#define NOA1305_POWER_CONTROL_ON 0x08 +#define NOA1305_REG_RESET 0x1 +#define NOA1305_RESET_RESET 0x10 +#define NOA1305_REG_INTEGRATION_TIME 0x2 +#define NOA1305_INTEGR_TIME_800MS 0x00 +#define NOA1305_INTEGR_TIME_400MS 0x01 +#define NOA1305_INTEGR_TIME_200MS 0x02 +#define NOA1305_INTEGR_TIME_100MS 0x03 +#define NOA1305_INTEGR_TIME_50MS 0x04 +#define NOA1305_INTEGR_TIME_25MS 0x05 +#define NOA1305_INTEGR_TIME_12_5MS 0x06 +#define NOA1305_INTEGR_TIME_6_25MS 0x07 +#define NOA1305_INTEGR_TIME_MASK 0x07 +#define NOA1305_REG_INT_SELECT 0x3 +#define NOA1305_INT_SEL_ACTIVE_HIGH 0x01 +#define NOA1305_INT_SEL_ACTIVE_LOW 0x02 +#define NOA1305_INT_SEL_INACTIVE 0x03 +#define NOA1305_REG_INT_THRESH_LSB 0x4 +#define NOA1305_REG_INT_THRESH_MSB 0x5 +#define NOA1305_REG_ALS_DATA_LSB 0x6 +#define NOA1305_REG_ALS_DATA_MSB 0x7 +#define NOA1305_REG_DEVICE_ID_LSB 0x8 +#define NOA1305_REG_DEVICE_ID_MSB 0x9 + +#define NOA1305_DEVICE_ID 0x0519 +#define NOA1305_DRIVER_NAME "noa1305" + +static int noa1305_scale_available[] = { + 100, 8 * 77, /* 800 ms */ + 100, 4 * 77, /* 400 ms */ + 100, 2 * 77, /* 200 ms */ + 100, 1 * 77, /* 100 ms */ + 1000, 5 * 77, /* 50 ms */ + 10000, 25 * 77, /* 25 ms */ + 100000, 125 * 77, /* 12.5 ms */ + 1000000, 625 * 77, /* 6.25 ms */ +}; + +static int noa1305_int_time_available[] = { + 0, 800000, /* 800 ms */ + 0, 400000, /* 400 ms */ + 0, 200000, /* 200 ms */ + 0, 100000, /* 100 ms */ + 0, 50000, /* 50 ms */ + 0, 25000, /* 25 ms */ + 0, 12500, /* 12.5 ms */ + 0, 6250, /* 6.25 ms */ +}; + +struct noa1305_priv { + struct i2c_client *client; + struct regmap *regmap; +}; + +static int noa1305_measure(struct noa1305_priv *priv, int *val) +{ + __le16 data; + int ret; + + ret = regmap_bulk_read(priv->regmap, NOA1305_REG_ALS_DATA_LSB, &data, + 2); + if (ret < 0) + return ret; + + *val = le16_to_cpu(data); + + return IIO_VAL_INT; +} + +static int noa1305_scale(struct noa1305_priv *priv, int *val, int *val2) +{ + int data; + int ret; + + ret = regmap_read(priv->regmap, NOA1305_REG_INTEGRATION_TIME, &data); + if (ret < 0) + return ret; + + /* + * Lux = count / ( * ) + * + * Integration Constant = 7.7 + * Integration Time in Seconds + */ + data &= NOA1305_INTEGR_TIME_MASK; + *val = noa1305_scale_available[2 * data + 0]; + *val2 = noa1305_scale_available[2 * data + 1]; + + return IIO_VAL_FRACTIONAL; +} + +static int noa1305_int_time(struct noa1305_priv *priv, int *val, int *val2) +{ + int data; + int ret; + + ret = regmap_read(priv->regmap, NOA1305_REG_INTEGRATION_TIME, &data); + if (ret < 0) + return ret; + + data &= NOA1305_INTEGR_TIME_MASK; + *val = noa1305_int_time_available[2 * data + 0]; + *val2 = noa1305_int_time_available[2 * data + 1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static const struct iio_chan_spec noa1305_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), + } +}; + +static int noa1305_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, + int *length, long mask) +{ + if (chan->type != IIO_LIGHT) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = noa1305_scale_available; + *length = ARRAY_SIZE(noa1305_scale_available); + *type = IIO_VAL_FRACTIONAL; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_INT_TIME: + *vals = noa1305_int_time_available; + *length = ARRAY_SIZE(noa1305_int_time_available); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int noa1305_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct noa1305_priv *priv = iio_priv(indio_dev); + + if (chan->type != IIO_LIGHT) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return noa1305_measure(priv, val); + case IIO_CHAN_INFO_SCALE: + return noa1305_scale(priv, val, val2); + case IIO_CHAN_INFO_INT_TIME: + return noa1305_int_time(priv, val, val2); + default: + return -EINVAL; + } +} + +static int noa1305_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct noa1305_priv *priv = iio_priv(indio_dev); + int i; + + if (chan->type != IIO_LIGHT) + return -EINVAL; + + if (mask != IIO_CHAN_INFO_INT_TIME) + return -EINVAL; + + if (val) /* >= 1s integration time not supported */ + return -EINVAL; + + /* Look up integration time register settings and write it if found. */ + for (i = 0; i < ARRAY_SIZE(noa1305_int_time_available) / 2; i++) + if (noa1305_int_time_available[2 * i + 1] == val2) + return regmap_write(priv->regmap, NOA1305_REG_INTEGRATION_TIME, i); + + return -EINVAL; +} + +static const struct iio_info noa1305_info = { + .read_avail = noa1305_read_avail, + .read_raw = noa1305_read_raw, + .write_raw = noa1305_write_raw, +}; + +static bool noa1305_writable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NOA1305_REG_POWER_CONTROL: + case NOA1305_REG_RESET: + case NOA1305_REG_INTEGRATION_TIME: + case NOA1305_REG_INT_SELECT: + case NOA1305_REG_INT_THRESH_LSB: + case NOA1305_REG_INT_THRESH_MSB: + return true; + default: + return false; + } +} + +static const struct regmap_config noa1305_regmap_config = { + .name = NOA1305_DRIVER_NAME, + .reg_bits = 8, + .val_bits = 8, + .max_register = NOA1305_REG_DEVICE_ID_MSB, + .writeable_reg = noa1305_writable_reg, +}; + +static int noa1305_probe(struct i2c_client *client) +{ + struct noa1305_priv *priv; + struct iio_dev *indio_dev; + struct regmap *regmap; + __le16 data; + unsigned int dev_id; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, &noa1305_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Regmap initialization failed.\n"); + return PTR_ERR(regmap); + } + + priv = iio_priv(indio_dev); + + ret = devm_regulator_get_enable(&client->dev, "vin"); + if (ret) + return dev_err_probe(&client->dev, ret, + "get regulator vin failed\n"); + + i2c_set_clientdata(client, indio_dev); + priv->client = client; + priv->regmap = regmap; + + ret = regmap_bulk_read(regmap, NOA1305_REG_DEVICE_ID_LSB, &data, 2); + if (ret < 0) { + dev_err(&client->dev, "ID reading failed: %d\n", ret); + return ret; + } + + dev_id = le16_to_cpu(data); + if (dev_id != NOA1305_DEVICE_ID) { + dev_err(&client->dev, "Unknown device ID: 0x%x\n", dev_id); + return -ENODEV; + } + + ret = regmap_write(regmap, NOA1305_REG_POWER_CONTROL, + NOA1305_POWER_CONTROL_ON); + if (ret < 0) { + dev_err(&client->dev, "Enabling power control failed\n"); + return ret; + } + + ret = regmap_write(regmap, NOA1305_REG_RESET, NOA1305_RESET_RESET); + if (ret < 0) { + dev_err(&client->dev, "Device reset failed\n"); + return ret; + } + + ret = regmap_write(regmap, NOA1305_REG_INTEGRATION_TIME, + NOA1305_INTEGR_TIME_800MS); + if (ret < 0) { + dev_err(&client->dev, "Setting integration time failed\n"); + return ret; + } + + indio_dev->info = &noa1305_info; + indio_dev->channels = noa1305_channels; + indio_dev->num_channels = ARRAY_SIZE(noa1305_channels); + indio_dev->name = NOA1305_DRIVER_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_iio_device_register(&client->dev, indio_dev); + if (ret) + dev_err(&client->dev, "registering device failed\n"); + + return ret; +} + +static const struct of_device_id noa1305_of_match[] = { + { .compatible = "onnn,noa1305" }, + { } +}; +MODULE_DEVICE_TABLE(of, noa1305_of_match); + +static const struct i2c_device_id noa1305_ids[] = { + { .name = "noa1305" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, noa1305_ids); + +static struct i2c_driver noa1305_driver = { + .driver = { + .name = NOA1305_DRIVER_NAME, + .of_match_table = noa1305_of_match, + }, + .probe = noa1305_probe, + .id_table = noa1305_ids, +}; + +module_i2c_driver(noa1305_driver); + +MODULE_AUTHOR("Sergei Miroshnichenko "); +MODULE_AUTHOR("Martyn Welch + * Based on previous work from: Felipe Balbi + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define OPT3001_RESULT 0x00 +#define OPT3001_CONFIGURATION 0x01 +#define OPT3001_LOW_LIMIT 0x02 +#define OPT3001_HIGH_LIMIT 0x03 +#define OPT3001_MANUFACTURER_ID 0x7e +#define OPT3001_DEVICE_ID 0x7f + +#define OPT3001_CONFIGURATION_RN_MASK GENMASK(15, 12) +#define OPT3001_CONFIGURATION_RN_AUTO (0xc << 12) + +#define OPT3001_CONFIGURATION_CT BIT(11) + +#define OPT3001_CONFIGURATION_M_MASK GENMASK(10, 9) +#define OPT3001_CONFIGURATION_M_SHUTDOWN (0 << 9) +#define OPT3001_CONFIGURATION_M_SINGLE (1 << 9) +#define OPT3001_CONFIGURATION_M_CONTINUOUS (2 << 9) /* also 3 << 9 */ + +#define OPT3001_CONFIGURATION_CRF BIT(7) +#define OPT3001_CONFIGURATION_FH BIT(6) +#define OPT3001_CONFIGURATION_FL BIT(5) +#define OPT3001_CONFIGURATION_L BIT(4) +#define OPT3001_CONFIGURATION_POL BIT(3) +#define OPT3001_CONFIGURATION_ME BIT(2) + +#define OPT3001_CONFIGURATION_FC_MASK GENMASK(1, 0) + +/* The end-of-conversion enable is located in the low-limit register */ +#define OPT3001_LOW_LIMIT_EOC_ENABLE 0xc000 + +#define OPT3001_REG_EXPONENT(n) ((n) >> 12) +#define OPT3001_REG_MANTISSA(n) ((n) & 0xfff) + +#define OPT3001_INT_TIME_LONG 800000 +#define OPT3001_INT_TIME_SHORT 100000 + +/* + * Time to wait for conversion result to be ready. The device datasheet + * sect. 6.5 states results are ready after total integration time plus 3ms. + * This results in worst-case max values of 113ms or 883ms, respectively. + * Add some slack to be on the safe side. + */ +#define OPT3001_RESULT_READY_SHORT 150 +#define OPT3001_RESULT_READY_LONG 1000 + +struct opt3001_scale { + int val; + int val2; +}; + +struct opt3001_chip_info { + const struct iio_chan_spec (*channels)[2]; + enum iio_chan_type chan_type; + int num_channels; + + const struct opt3001_scale (*scales)[12]; + /* + * Factor as specified by conversion equation in datasheet. + * eg. 0.01 (scaled to integer 10) for opt3001. + */ + int factor_whole; + /* + * Factor to compensate for potentially scaled factor_whole. + */ + int factor_integer; + /* + * Factor used to align decimal part of processed value to six decimal + * places. + */ + int factor_decimal; + + bool has_id; +}; + +struct opt3001 { + struct i2c_client *client; + + /* + * Ensure data capture and read-modify-write sequences are + * not interrupted. + */ + struct mutex lock; + + /* Allows for IRQs to bypass locking mechanism */ + bool ok_to_ignore_lock; + bool result_ready; + wait_queue_head_t result_ready_queue; + u16 result; + const struct opt3001_chip_info *chip_info; + + u32 int_time; + u32 mode; + + u16 high_thresh_mantissa; + u16 low_thresh_mantissa; + + u8 high_thresh_exp; + u8 low_thresh_exp; + + bool use_irq; +}; + +static const struct opt3001_scale opt3001_scales[] = { + { + .val = 40, + .val2 = 950000, + }, + { + .val = 81, + .val2 = 900000, + }, + { + .val = 163, + .val2 = 800000, + }, + { + .val = 327, + .val2 = 600000, + }, + { + .val = 655, + .val2 = 200000, + }, + { + .val = 1310, + .val2 = 400000, + }, + { + .val = 2620, + .val2 = 800000, + }, + { + .val = 5241, + .val2 = 600000, + }, + { + .val = 10483, + .val2 = 200000, + }, + { + .val = 20966, + .val2 = 400000, + }, + { + .val = 41932, + .val2 = 800000, + }, + { + .val = 83865, + .val2 = 600000, + }, +}; + +static const struct opt3001_scale opt3002_scales[] = { + { + .val = 4914, + .val2 = 0, + }, + { + .val = 9828, + .val2 = 0, + }, + { + .val = 19656, + .val2 = 0, + }, + { + .val = 39312, + .val2 = 0, + }, + { + .val = 78624, + .val2 = 0, + }, + { + .val = 157248, + .val2 = 0, + }, + { + .val = 314496, + .val2 = 0, + }, + { + .val = 628992, + .val2 = 0, + }, + { + .val = 1257984, + .val2 = 0, + }, + { + .val = 2515968, + .val2 = 0, + }, + { + .val = 5031936, + .val2 = 0, + }, + { + .val = 10063872, + .val2 = 0, + }, +}; + +static int opt3001_find_scale(const struct opt3001 *opt, int val, int val2, + u8 *exponent) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(*opt->chip_info->scales); i++) { + const struct opt3001_scale *scale = &(*opt->chip_info->scales)[i]; + /* + * Compare the integer and micro parts to determine value scale. + */ + if (val < scale->val || + (val == scale->val && val2 <= scale->val2)) { + *exponent = i; + return 0; + } + } + + return -EINVAL; +} + +static void opt3001_to_iio_ret(struct opt3001 *opt, u8 exponent, u16 mantissa, + int *val, int *val2) +{ + int ret; + int whole = opt->chip_info->factor_whole; + int integer = opt->chip_info->factor_integer; + int decimal = opt->chip_info->factor_decimal; + + ret = whole * (mantissa << exponent); + *val = ret / integer; + *val2 = (ret - (*val * integer)) * decimal; +} + +static void opt3001_set_mode(struct opt3001 *opt, u16 *reg, u16 mode) +{ + *reg &= ~OPT3001_CONFIGURATION_M_MASK; + *reg |= mode; + opt->mode = mode; +} + +static IIO_CONST_ATTR_INT_TIME_AVAIL("0.1 0.8"); + +static struct attribute *opt3001_attributes[] = { + &iio_const_attr_integration_time_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group opt3001_attribute_group = { + .attrs = opt3001_attributes, +}; + +static const struct iio_event_spec opt3001_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec opt3001_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_INT_TIME), + .event_spec = opt3001_event_spec, + .num_event_specs = ARRAY_SIZE(opt3001_event_spec), + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct iio_chan_spec opt3002_channels[] = { + { + .type = IIO_INTENSITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME), + .event_spec = opt3001_event_spec, + .num_event_specs = ARRAY_SIZE(opt3001_event_spec), + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static int opt3001_start_conversion(struct opt3001 *opt) +{ + struct i2c_client *client = opt->client; + struct device *dev = &client->dev; + u16 reg; + int ret; + + /* Reset data-ready indicator flag */ + opt->result_ready = false; + + /* Configure for single-conversion mode and start a new conversion */ + ret = i2c_smbus_read_word_swapped(client, OPT3001_CONFIGURATION); + if (ret < 0) { + dev_err(dev, "failed to read register %02x\n", + OPT3001_CONFIGURATION); + return ret; + } + + reg = ret; + opt3001_set_mode(opt, ®, OPT3001_CONFIGURATION_M_SINGLE); + + ret = i2c_smbus_write_word_swapped(client, OPT3001_CONFIGURATION, reg); + if (ret) + dev_err(dev, "failed to write register %02x\n", + OPT3001_CONFIGURATION); + + return ret; +} + +static int opt3001_get_processed_irq(struct opt3001 *opt) +{ + struct i2c_client *client = opt->client; + struct device *dev = &client->dev; + u16 value; + int ret; + + /* + * Enable the end-of-conversion interrupt mechanism. Note that doing so + * will overwrite the low-level limit value however we will restore this + * value later on. + */ + ret = i2c_smbus_write_word_swapped(client, + OPT3001_LOW_LIMIT, + OPT3001_LOW_LIMIT_EOC_ENABLE); + if (ret < 0) { + dev_err(dev, "failed to write register %02x\n", + OPT3001_LOW_LIMIT); + return ret; + } + + /* Allow IRQ to access the device despite lock being set */ + opt->ok_to_ignore_lock = true; + + ret = opt3001_start_conversion(opt); + if (ret) + goto err; + + if (wait_event_timeout(opt->result_ready_queue, opt->result_ready, + msecs_to_jiffies(OPT3001_RESULT_READY_LONG))) + ret = 0; + else + ret = -ETIMEDOUT; + +err: + opt->ok_to_ignore_lock = false; + + if (ret < 0) + return ret; + + /* + * Disable the end-of-conversion interrupt mechanism by restoring the + * low-level limit value (clearing OPT3001_LOW_LIMIT_EOC_ENABLE). Note + * that selectively clearing those enable bits would affect the actual + * limit value due to bit-overlap and therefore can't be done. + */ + value = (opt->low_thresh_exp << 12) | opt->low_thresh_mantissa; + ret = i2c_smbus_write_word_swapped(client, OPT3001_LOW_LIMIT, value); + if (ret) + dev_err(dev, "failed to write register %02x\n", + OPT3001_LOW_LIMIT); + + return ret; +} + +static int opt3001_get_processed_noirq(struct opt3001 *opt) +{ + struct i2c_client *client = opt->client; + struct device *dev = &client->dev; + int ret; + + ret = opt3001_start_conversion(opt); + if (ret) + return ret; + + if (opt->int_time == OPT3001_INT_TIME_SHORT) + msleep(OPT3001_RESULT_READY_SHORT); + else + msleep(OPT3001_RESULT_READY_LONG); + + /* Check result ready flag */ + ret = i2c_smbus_read_word_swapped(client, OPT3001_CONFIGURATION); + if (ret < 0) { + dev_err(dev, "failed to read register %02x\n", + OPT3001_CONFIGURATION); + return ret; + } + + if (!(ret & OPT3001_CONFIGURATION_CRF)) + return -ETIMEDOUT; + + /* Obtain value */ + ret = i2c_smbus_read_word_swapped(client, OPT3001_RESULT); + if (ret < 0) { + dev_err(dev, "failed to read register %02x\n", + OPT3001_RESULT); + return ret; + } + + opt->result = ret; + opt->result_ready = true; + + return 0; +} + +static int opt3001_get_processed(struct opt3001 *opt, int *val, int *val2) +{ + u16 mantissa; + u8 exponent; + int ret; + + if (opt->use_irq) + ret = opt3001_get_processed_irq(opt); + else + ret = opt3001_get_processed_noirq(opt); + if (ret) + return ret; + + exponent = OPT3001_REG_EXPONENT(opt->result); + mantissa = OPT3001_REG_MANTISSA(opt->result); + + opt3001_to_iio_ret(opt, exponent, mantissa, val, val2); + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int opt3001_get_int_time(struct opt3001 *opt, int *val, int *val2) +{ + *val = 0; + *val2 = opt->int_time; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int opt3001_set_int_time(struct opt3001 *opt, int time) +{ + struct i2c_client *client = opt->client; + struct device *dev = &client->dev; + int ret; + u16 reg; + + ret = i2c_smbus_read_word_swapped(client, OPT3001_CONFIGURATION); + if (ret < 0) { + dev_err(dev, "failed to read register %02x\n", + OPT3001_CONFIGURATION); + return ret; + } + + reg = ret; + + switch (time) { + case OPT3001_INT_TIME_SHORT: + reg &= ~OPT3001_CONFIGURATION_CT; + opt->int_time = OPT3001_INT_TIME_SHORT; + break; + case OPT3001_INT_TIME_LONG: + reg |= OPT3001_CONFIGURATION_CT; + opt->int_time = OPT3001_INT_TIME_LONG; + break; + default: + return -EINVAL; + } + + return i2c_smbus_write_word_swapped(client, OPT3001_CONFIGURATION, reg); +} + +static int opt3001_read_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct opt3001 *opt = iio_priv(iio); + + if (opt->mode == OPT3001_CONFIGURATION_M_CONTINUOUS) + return -EBUSY; + + if (chan->type != opt->chip_info->chan_type) + return -EINVAL; + + guard(mutex)(&opt->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + return opt3001_get_processed(opt, val, val2); + case IIO_CHAN_INFO_INT_TIME: + return opt3001_get_int_time(opt, val, val2); + default: + return -EINVAL; + } +} + +static int opt3001_write_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct opt3001 *opt = iio_priv(iio); + + if (opt->mode == OPT3001_CONFIGURATION_M_CONTINUOUS) + return -EBUSY; + + if (chan->type != opt->chip_info->chan_type) + return -EINVAL; + + if (mask != IIO_CHAN_INFO_INT_TIME) + return -EINVAL; + + if (val != 0) + return -EINVAL; + + guard(mutex)(&opt->lock); + + return opt3001_set_int_time(opt, val2); +} + +static int opt3001_read_event_value(struct iio_dev *iio, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct opt3001 *opt = iio_priv(iio); + + guard(mutex)(&opt->lock); + + switch (dir) { + case IIO_EV_DIR_RISING: + opt3001_to_iio_ret(opt, opt->high_thresh_exp, + opt->high_thresh_mantissa, val, val2); + return IIO_VAL_INT_PLUS_MICRO; + case IIO_EV_DIR_FALLING: + opt3001_to_iio_ret(opt, opt->low_thresh_exp, + opt->low_thresh_mantissa, val, val2); + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int opt3001_write_event_value(struct iio_dev *iio, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct opt3001 *opt = iio_priv(iio); + struct i2c_client *client = opt->client; + struct device *dev = &client->dev; + int ret; + int whole; + int integer; + int decimal; + + u16 mantissa; + u16 value; + u16 reg; + + u8 exponent; + + if (val < 0) + return -EINVAL; + + guard(mutex)(&opt->lock); + + ret = opt3001_find_scale(opt, val, val2, &exponent); + if (ret < 0) { + dev_err(dev, "can't find scale for %d.%06u\n", val, val2); + return ret; + } + + whole = opt->chip_info->factor_whole; + integer = opt->chip_info->factor_integer; + decimal = opt->chip_info->factor_decimal; + + mantissa = (((val * integer) + (val2 / decimal)) / whole) >> exponent; + + value = (exponent << 12) | mantissa; + + switch (dir) { + case IIO_EV_DIR_RISING: + reg = OPT3001_HIGH_LIMIT; + opt->high_thresh_mantissa = mantissa; + opt->high_thresh_exp = exponent; + break; + case IIO_EV_DIR_FALLING: + reg = OPT3001_LOW_LIMIT; + opt->low_thresh_mantissa = mantissa; + opt->low_thresh_exp = exponent; + break; + default: + return -EINVAL; + } + + ret = i2c_smbus_write_word_swapped(client, reg, value); + if (ret < 0) { + dev_err(dev, "failed to write register %02x\n", reg); + return ret; + } + + return 0; +} + +static int opt3001_read_event_config(struct iio_dev *iio, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct opt3001 *opt = iio_priv(iio); + + return opt->mode == OPT3001_CONFIGURATION_M_CONTINUOUS; +} + +static int opt3001_write_event_config(struct iio_dev *iio, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct opt3001 *opt = iio_priv(iio); + struct i2c_client *client = opt->client; + struct device *dev = &client->dev; + int ret; + u16 mode; + u16 reg; + + if (state && opt->mode == OPT3001_CONFIGURATION_M_CONTINUOUS) + return 0; + + if (!state && opt->mode == OPT3001_CONFIGURATION_M_SHUTDOWN) + return 0; + + guard(mutex)(&opt->lock); + + mode = state ? OPT3001_CONFIGURATION_M_CONTINUOUS + : OPT3001_CONFIGURATION_M_SHUTDOWN; + + ret = i2c_smbus_read_word_swapped(client, OPT3001_CONFIGURATION); + if (ret < 0) { + dev_err(dev, "failed to read register %02x\n", + OPT3001_CONFIGURATION); + return ret; + } + + reg = ret; + opt3001_set_mode(opt, ®, mode); + + ret = i2c_smbus_write_word_swapped(client, OPT3001_CONFIGURATION, reg); + if (ret < 0) { + dev_err(dev, "failed to write register %02x\n", + OPT3001_CONFIGURATION); + return ret; + } + + return 0; +} + +static const struct iio_info opt3001_info = { + .attrs = &opt3001_attribute_group, + .read_raw = opt3001_read_raw, + .write_raw = opt3001_write_raw, + .read_event_value = opt3001_read_event_value, + .write_event_value = opt3001_write_event_value, + .read_event_config = opt3001_read_event_config, + .write_event_config = opt3001_write_event_config, +}; + +static int opt3001_read_id(struct opt3001 *opt) +{ + struct i2c_client *client = opt->client; + struct device *dev = &client->dev; + char manufacturer[2]; + u16 device_id; + int ret; + + ret = i2c_smbus_read_word_swapped(client, OPT3001_MANUFACTURER_ID); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to read register %02x\n", + OPT3001_MANUFACTURER_ID); + + manufacturer[0] = ret >> 8; + manufacturer[1] = ret & 0xff; + + ret = i2c_smbus_read_word_swapped(client, OPT3001_DEVICE_ID); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to read register %02x\n", + OPT3001_DEVICE_ID); + + device_id = ret; + + dev_info(dev, "Found %c%c OPT%04x\n", manufacturer[0], manufacturer[1], + device_id); + + return 0; +} + +static void opt3001_power_off(void *data) +{ + struct opt3001 *opt = data; + struct i2c_client *client = opt->client; + struct device *dev = &client->dev; + u16 reg_val; + int ret; + + ret = i2c_smbus_read_word_swapped(client, OPT3001_CONFIGURATION); + if (ret < 0) { + dev_err(dev, "failed to read register %02x\n", + OPT3001_CONFIGURATION); + return; + } + + reg_val = ret; + opt3001_set_mode(opt, ®_val, OPT3001_CONFIGURATION_M_SHUTDOWN); + + ret = i2c_smbus_write_word_swapped(client, OPT3001_CONFIGURATION, reg_val); + if (ret < 0) + dev_err(dev, "failed to write to register %02x\n", + OPT3001_CONFIGURATION); +} + +static int opt3001_configure(struct opt3001 *opt) +{ + struct i2c_client *client = opt->client; + struct device *dev = &client->dev; + int ret; + u16 reg; + + ret = i2c_smbus_read_word_swapped(client, OPT3001_CONFIGURATION); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to read register %02x\n", + OPT3001_CONFIGURATION); + + reg = ret; + + /* Enable automatic full-scale setting mode */ + reg &= ~OPT3001_CONFIGURATION_RN_MASK; + reg |= OPT3001_CONFIGURATION_RN_AUTO; + + /* Reflect status of the device's integration time setting */ + if (reg & OPT3001_CONFIGURATION_CT) + opt->int_time = OPT3001_INT_TIME_LONG; + else + opt->int_time = OPT3001_INT_TIME_SHORT; + + /* Ensure device is in shutdown initially */ + opt3001_set_mode(opt, ®, OPT3001_CONFIGURATION_M_SHUTDOWN); + + /* Configure for latched window-style comparison operation */ + reg |= OPT3001_CONFIGURATION_L; + reg &= ~OPT3001_CONFIGURATION_POL; + reg &= ~OPT3001_CONFIGURATION_ME; + reg &= ~OPT3001_CONFIGURATION_FC_MASK; + + ret = i2c_smbus_write_word_swapped(client, OPT3001_CONFIGURATION, reg); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to write register %02x\n", + OPT3001_CONFIGURATION); + + ret = devm_add_action_or_reset(dev, opt3001_power_off, opt); + if (ret) + return dev_err_probe(dev, ret, + "failed to register power off function\n"); + + ret = i2c_smbus_read_word_swapped(client, OPT3001_LOW_LIMIT); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to read register %02x\n", + OPT3001_LOW_LIMIT); + + opt->low_thresh_mantissa = OPT3001_REG_MANTISSA(ret); + opt->low_thresh_exp = OPT3001_REG_EXPONENT(ret); + + ret = i2c_smbus_read_word_swapped(client, OPT3001_HIGH_LIMIT); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to read register %02x\n", + OPT3001_HIGH_LIMIT); + + opt->high_thresh_mantissa = OPT3001_REG_MANTISSA(ret); + opt->high_thresh_exp = OPT3001_REG_EXPONENT(ret); + + return 0; +} + +static irqreturn_t opt3001_irq(int irq, void *_iio) +{ + struct iio_dev *iio = _iio; + struct opt3001 *opt = iio_priv(iio); + struct i2c_client *client = opt->client; + struct device *dev = &client->dev; + int ret; + bool wake_result_ready_queue = false; + enum iio_chan_type chan_type = opt->chip_info->chan_type; + bool ok_to_ignore_lock = opt->ok_to_ignore_lock; + + if (!ok_to_ignore_lock) + mutex_lock(&opt->lock); + + ret = i2c_smbus_read_word_swapped(client, OPT3001_CONFIGURATION); + if (ret < 0) { + dev_err(dev, "failed to read register %02x\n", + OPT3001_CONFIGURATION); + goto out; + } + + if ((ret & OPT3001_CONFIGURATION_M_MASK) == + OPT3001_CONFIGURATION_M_CONTINUOUS) { + if (ret & OPT3001_CONFIGURATION_FH) + iio_push_event(iio, + IIO_UNMOD_EVENT_CODE(chan_type, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(iio)); + if (ret & OPT3001_CONFIGURATION_FL) + iio_push_event(iio, + IIO_UNMOD_EVENT_CODE(chan_type, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns(iio)); + } else if (ret & OPT3001_CONFIGURATION_CRF) { + ret = i2c_smbus_read_word_swapped(client, OPT3001_RESULT); + if (ret < 0) { + dev_err(dev, "failed to read register %02x\n", + OPT3001_RESULT); + goto out; + } + opt->result = ret; + opt->result_ready = true; + wake_result_ready_queue = true; + } + +out: + if (!ok_to_ignore_lock) + mutex_unlock(&opt->lock); + + if (wake_result_ready_queue) + wake_up(&opt->result_ready_queue); + + return IRQ_HANDLED; +} + +static int opt3001_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + + struct iio_dev *iio; + struct opt3001 *opt; + int irq = client->irq; + int ret; + + iio = devm_iio_device_alloc(dev, sizeof(*opt)); + if (!iio) + return -ENOMEM; + + opt = iio_priv(iio); + opt->client = client; + opt->chip_info = i2c_get_match_data(client); + + ret = devm_mutex_init(dev, &opt->lock); + if (ret) + return ret; + + init_waitqueue_head(&opt->result_ready_queue); + + if (opt->chip_info->has_id) { + ret = opt3001_read_id(opt); + if (ret) + return ret; + } + + ret = opt3001_configure(opt); + if (ret) + return ret; + + iio->name = client->name; + iio->channels = *opt->chip_info->channels; + iio->num_channels = opt->chip_info->num_channels; + iio->modes = INDIO_DIRECT_MODE; + iio->info = &opt3001_info; + + /* Make use of INT pin only if valid IRQ no. is given */ + if (irq > 0) { + ret = devm_request_threaded_irq(dev, irq, NULL, opt3001_irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "opt3001", iio); + if (ret) + return ret; + + opt->use_irq = true; + } else { + dev_dbg(dev, "enabling interrupt-less operation\n"); + } + + return devm_iio_device_register(dev, iio); +} + +static const struct opt3001_chip_info opt3001_chip_information = { + .channels = &opt3001_channels, + .chan_type = IIO_LIGHT, + .num_channels = ARRAY_SIZE(opt3001_channels), + .scales = &opt3001_scales, + .factor_whole = 10, + .factor_integer = 1000, + .factor_decimal = 1000, + .has_id = true, +}; + +static const struct opt3001_chip_info opt3002_chip_information = { + .channels = &opt3002_channels, + .chan_type = IIO_INTENSITY, + .num_channels = ARRAY_SIZE(opt3002_channels), + .scales = &opt3002_scales, + .factor_whole = 12, + .factor_integer = 10, + .factor_decimal = 100000, + .has_id = false, +}; + +static const struct i2c_device_id opt3001_id[] = { + { .name = "opt3001", .driver_data = (kernel_ulong_t)&opt3001_chip_information }, + { .name = "opt3002", .driver_data = (kernel_ulong_t)&opt3002_chip_information }, + { } /* Terminating Entry */ +}; +MODULE_DEVICE_TABLE(i2c, opt3001_id); + +static const struct of_device_id opt3001_of_match[] = { + { .compatible = "ti,opt3001", .data = &opt3001_chip_information }, + { .compatible = "ti,opt3002", .data = &opt3002_chip_information }, + { } +}; +MODULE_DEVICE_TABLE(of, opt3001_of_match); + +static struct i2c_driver opt3001_driver = { + .probe = opt3001_probe, + .id_table = opt3001_id, + + .driver = { + .name = "opt3001", + .of_match_table = opt3001_of_match, + }, +}; + +module_i2c_driver(opt3001_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Andreas Dannenberg "); +MODULE_DESCRIPTION("Texas Instruments OPT3001 Light Sensor Driver"); diff --git a/drivers/iio/light/opt4001.c b/drivers/iio/light/opt4001.c new file mode 100644 index 000000000..aa3d87995 --- /dev/null +++ b/drivers/iio/light/opt4001.c @@ -0,0 +1,451 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023 Axis Communications AB + * + * Datasheet: https://www.ti.com/lit/gpn/opt4001 + * + * Device driver for the Texas Instruments OPT4001. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* OPT4001 register set */ +#define OPT4001_LIGHT1_MSB 0x00 +#define OPT4001_LIGHT1_LSB 0x01 +#define OPT4001_CTRL 0x0A +#define OPT4001_DEVICE_ID 0x11 + +/* OPT4001 register mask */ +#define OPT4001_EXPONENT_MASK GENMASK(15, 12) +#define OPT4001_MSB_MASK GENMASK(11, 0) +#define OPT4001_LSB_MASK GENMASK(15, 8) +#define OPT4001_COUNTER_MASK GENMASK(7, 4) +#define OPT4001_CRC_MASK GENMASK(3, 0) + +/* OPT4001 device id mask */ +#define OPT4001_DEVICE_ID_MASK GENMASK(11, 0) + +/* OPT4001 control registers mask */ +#define OPT4001_CTRL_QWAKE_MASK GENMASK(15, 15) +#define OPT4001_CTRL_RANGE_MASK GENMASK(13, 10) +#define OPT4001_CTRL_CONV_TIME_MASK GENMASK(9, 6) +#define OPT4001_CTRL_OPER_MODE_MASK GENMASK(5, 4) +#define OPT4001_CTRL_LATCH_MASK GENMASK(3, 3) +#define OPT4001_CTRL_INT_POL_MASK GENMASK(2, 2) +#define OPT4001_CTRL_FAULT_COUNT_MASK GENMASK(1, 0) + +/* OPT4001 constants */ +#define OPT4001_DEVICE_ID_VAL 0x121 + +/* OPT4001 operating modes */ +#define OPT4001_CTRL_OPER_MODE_OFF 0x0 +#define OPT4001_CTRL_OPER_MODE_FORCED 0x1 +#define OPT4001_CTRL_OPER_MODE_ONE_SHOT 0x2 +#define OPT4001_CTRL_OPER_MODE_CONTINUOUS 0x3 + +/* OPT4001 conversion control register definitions */ +#define OPT4001_CTRL_CONVERSION_0_6MS 0x0 +#define OPT4001_CTRL_CONVERSION_1MS 0x1 +#define OPT4001_CTRL_CONVERSION_1_8MS 0x2 +#define OPT4001_CTRL_CONVERSION_3_4MS 0x3 +#define OPT4001_CTRL_CONVERSION_6_5MS 0x4 +#define OPT4001_CTRL_CONVERSION_12_7MS 0x5 +#define OPT4001_CTRL_CONVERSION_25MS 0x6 +#define OPT4001_CTRL_CONVERSION_50MS 0x7 +#define OPT4001_CTRL_CONVERSION_100MS 0x8 +#define OPT4001_CTRL_CONVERSION_200MS 0x9 +#define OPT4001_CTRL_CONVERSION_400MS 0xa +#define OPT4001_CTRL_CONVERSION_800MS 0xb + +/* OPT4001 scale light level range definitions */ +#define OPT4001_CTRL_LIGHT_SCALE_AUTO 12 + +/* OPT4001 default values */ +#define OPT4001_DEFAULT_CONVERSION_TIME OPT4001_CTRL_CONVERSION_800MS + +/* + * The different packaging of OPT4001 has different constants used when calculating + * lux values. + */ +struct opt4001_chip_info { + int mul; + int div; + const char *name; +}; + +struct opt4001_chip { + struct regmap *regmap; + struct i2c_client *client; + u8 int_time; + const struct opt4001_chip_info *chip_info; +}; + +static const struct opt4001_chip_info opt4001_sot_5x3_info = { + .mul = 4375, + .div = 10000000, + .name = "opt4001-sot-5x3" +}; + +static const struct opt4001_chip_info opt4001_picostar_info = { + .mul = 3125, + .div = 10000000, + .name = "opt4001-picostar" +}; + +static const int opt4001_int_time_available[][2] = { + { 0, 600 }, + { 0, 1000 }, + { 0, 1800 }, + { 0, 3400 }, + { 0, 6500 }, + { 0, 12700 }, + { 0, 25000 }, + { 0, 50000 }, + { 0, 100000 }, + { 0, 200000 }, + { 0, 400000 }, + { 0, 800000 }, +}; + +/* + * Conversion time is integration time + time to set register + * this is used as integration time. + */ +static const int opt4001_int_time_reg[][2] = { + { 600, OPT4001_CTRL_CONVERSION_0_6MS }, + { 1000, OPT4001_CTRL_CONVERSION_1MS }, + { 1800, OPT4001_CTRL_CONVERSION_1_8MS }, + { 3400, OPT4001_CTRL_CONVERSION_3_4MS }, + { 6500, OPT4001_CTRL_CONVERSION_6_5MS }, + { 12700, OPT4001_CTRL_CONVERSION_12_7MS }, + { 25000, OPT4001_CTRL_CONVERSION_25MS }, + { 50000, OPT4001_CTRL_CONVERSION_50MS }, + { 100000, OPT4001_CTRL_CONVERSION_100MS }, + { 200000, OPT4001_CTRL_CONVERSION_200MS }, + { 400000, OPT4001_CTRL_CONVERSION_400MS }, + { 800000, OPT4001_CTRL_CONVERSION_800MS }, +}; + +static int opt4001_als_time_to_index(const u32 als_integration_time) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(opt4001_int_time_available); i++) { + if (als_integration_time == opt4001_int_time_available[i][1]) + return i; + } + + return -EINVAL; +} + +static u8 opt4001_calculate_crc(u8 exp, u32 mantissa, u8 count) +{ + u8 crc; + + crc = (hweight32(mantissa) + hweight32(exp) + hweight32(count)) % 2; + crc |= ((hweight32(mantissa & 0xAAAAA) + hweight32(exp & 0xA) + + hweight32(count & 0xA)) % 2) << 1; + crc |= ((hweight32(mantissa & 0x88888) + hweight32(exp & 0x8) + + hweight32(count & 0x8)) % 2) << 2; + crc |= (hweight32(mantissa & 0x80808) % 2) << 3; + + return crc; +} + +static int opt4001_read_lux_value(struct iio_dev *indio_dev, + int *val, int *val2) +{ + struct opt4001_chip *chip = iio_priv(indio_dev); + struct device *dev = &chip->client->dev; + unsigned int light1; + unsigned int light2; + u16 msb; + u16 lsb; + u8 exp; + u8 count; + u8 crc; + u8 calc_crc; + u64 lux_raw; + u32 rem; + int ret; + + ret = regmap_read(chip->regmap, OPT4001_LIGHT1_MSB, &light1); + if (ret < 0) { + dev_err(dev, "Failed to read data bytes"); + return ret; + } + + ret = regmap_read(chip->regmap, OPT4001_LIGHT1_LSB, &light2); + if (ret < 0) { + dev_err(dev, "Failed to read data bytes"); + return ret; + } + + count = FIELD_GET(OPT4001_COUNTER_MASK, light2); + exp = FIELD_GET(OPT4001_EXPONENT_MASK, light1); + crc = FIELD_GET(OPT4001_CRC_MASK, light2); + msb = FIELD_GET(OPT4001_MSB_MASK, light1); + lsb = FIELD_GET(OPT4001_LSB_MASK, light2); + lux_raw = (msb << 8) + lsb; + calc_crc = opt4001_calculate_crc(exp, lux_raw, count); + if (calc_crc != crc) + return -EIO; + + lux_raw = lux_raw << exp; + lux_raw = lux_raw * chip->chip_info->mul; + *val = div_u64_rem(lux_raw, chip->chip_info->div, &rem); + *val2 = rem * 100; + + return IIO_VAL_INT_PLUS_NANO; +} + +static int opt4001_set_conf(struct opt4001_chip *chip) +{ + struct device *dev = &chip->client->dev; + u16 reg; + int ret; + + reg = FIELD_PREP(OPT4001_CTRL_RANGE_MASK, OPT4001_CTRL_LIGHT_SCALE_AUTO); + reg |= FIELD_PREP(OPT4001_CTRL_CONV_TIME_MASK, chip->int_time); + reg |= FIELD_PREP(OPT4001_CTRL_OPER_MODE_MASK, OPT4001_CTRL_OPER_MODE_CONTINUOUS); + + ret = regmap_write(chip->regmap, OPT4001_CTRL, reg); + if (ret) + dev_err(dev, "Failed to set configuration\n"); + + return ret; +} + +static void opt4001_chip_off_action(void *data) +{ + struct opt4001_chip *chip = data; + int ret; + + ret = regmap_clear_bits(chip->regmap, OPT4001_CTRL, OPT4001_CTRL_OPER_MODE_MASK); + if (ret) + dev_err(&chip->client->dev, "Failed to power down\n"); +} + +static const struct iio_chan_spec opt4001_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) + }, +}; + +static int opt4001_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct opt4001_chip *chip = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + return opt4001_read_lux_value(indio_dev, val, val2); + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = opt4001_int_time_reg[chip->int_time][0]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int opt4001_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct opt4001_chip *chip = iio_priv(indio_dev); + int int_time; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val) + return -EINVAL; + + int_time = opt4001_als_time_to_index(val2); + if (int_time < 0) + return int_time; + chip->int_time = int_time; + return opt4001_set_conf(chip); + default: + return -EINVAL; + } +} + +static int opt4001_read_available(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + *length = ARRAY_SIZE(opt4001_int_time_available) * 2; + *vals = (const int *)opt4001_int_time_available; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + + default: + return -EINVAL; + } +} + +static const struct iio_info opt4001_info_no_irq = { + .read_raw = opt4001_read_raw, + .write_raw = opt4001_write_raw, + .read_avail = opt4001_read_available, +}; + +static int opt4001_load_defaults(struct opt4001_chip *chip) +{ + chip->int_time = OPT4001_DEFAULT_CONVERSION_TIME; + + return opt4001_set_conf(chip); +} + +static bool opt4001_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case OPT4001_LIGHT1_MSB: + case OPT4001_LIGHT1_LSB: + case OPT4001_CTRL: + case OPT4001_DEVICE_ID: + return true; + default: + return false; + } +} + +static bool opt4001_writable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case OPT4001_CTRL: + return true; + default: + return false; + } +} + +static bool opt4001_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case OPT4001_LIGHT1_MSB: + case OPT4001_LIGHT1_LSB: + return true; + default: + return false; + } +} + +static const struct regmap_config opt4001_regmap_config = { + .name = "opt4001", + .reg_bits = 8, + .val_bits = 16, + .cache_type = REGCACHE_RBTREE, + .max_register = OPT4001_DEVICE_ID, + .readable_reg = opt4001_readable_reg, + .writeable_reg = opt4001_writable_reg, + .volatile_reg = opt4001_volatile_reg, + .val_format_endian = REGMAP_ENDIAN_BIG, +}; + +static int opt4001_probe(struct i2c_client *client) +{ + struct opt4001_chip *chip; + struct iio_dev *indio_dev; + int ret; + uint dev_id; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + + ret = devm_regulator_get_enable(&client->dev, "vdd"); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to enable vdd supply\n"); + + chip->regmap = devm_regmap_init_i2c(client, &opt4001_regmap_config); + if (IS_ERR(chip->regmap)) + return dev_err_probe(&client->dev, PTR_ERR(chip->regmap), + "regmap initialization failed\n"); + chip->client = client; + + indio_dev->info = &opt4001_info_no_irq; + + ret = regmap_reinit_cache(chip->regmap, &opt4001_regmap_config); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to reinit regmap cache\n"); + + ret = regmap_read(chip->regmap, OPT4001_DEVICE_ID, &dev_id); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to read the device ID register\n"); + + dev_id = FIELD_GET(OPT4001_DEVICE_ID_MASK, dev_id); + if (dev_id != OPT4001_DEVICE_ID_VAL) + dev_warn(&client->dev, "Device ID: %#04x unknown\n", dev_id); + + chip->chip_info = i2c_get_match_data(client); + + indio_dev->channels = opt4001_channels; + indio_dev->num_channels = ARRAY_SIZE(opt4001_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = chip->chip_info->name; + + ret = opt4001_load_defaults(chip); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to set sensor defaults\n"); + + ret = devm_add_action_or_reset(&client->dev, + opt4001_chip_off_action, + chip); + if (ret < 0) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +/* + * The compatible string determines which constants to use depending on + * opt4001 packaging + */ +static const struct i2c_device_id opt4001_id[] = { + { .name = "opt4001-sot-5x3", .driver_data = (kernel_ulong_t)&opt4001_sot_5x3_info }, + { .name = "opt4001-picostar", .driver_data = (kernel_ulong_t)&opt4001_picostar_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, opt4001_id); + +static const struct of_device_id opt4001_of_match[] = { + { .compatible = "ti,opt4001-sot-5x3", .data = &opt4001_sot_5x3_info}, + { .compatible = "ti,opt4001-picostar", .data = &opt4001_picostar_info}, + { } +}; +MODULE_DEVICE_TABLE(of, opt4001_of_match); + +static struct i2c_driver opt4001_driver = { + .driver = { + .name = "opt4001", + .of_match_table = opt4001_of_match, + }, + .probe = opt4001_probe, + .id_table = opt4001_id, +}; +module_i2c_driver(opt4001_driver); + +MODULE_AUTHOR("Stefan Windfeldt-Prytz "); +MODULE_DESCRIPTION("Texas Instruments opt4001 ambient light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/opt4060.c b/drivers/iio/light/opt4060.c new file mode 100644 index 000000000..8253a6a8b --- /dev/null +++ b/drivers/iio/light/opt4060.c @@ -0,0 +1,1328 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 Axis Communications AB + * + * Datasheet: https://www.ti.com/lit/gpn/opt4060 + * + * Device driver for the Texas Instruments OPT4060 RGBW Color Sensor. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* OPT4060 register set */ +#define OPT4060_RED_MSB 0x00 +#define OPT4060_RED_LSB 0x01 +#define OPT4060_GREEN_MSB 0x02 +#define OPT4060_GREEN_LSB 0x03 +#define OPT4060_BLUE_MSB 0x04 +#define OPT4060_BLUE_LSB 0x05 +#define OPT4060_CLEAR_MSB 0x06 +#define OPT4060_CLEAR_LSB 0x07 +#define OPT4060_THRESHOLD_LOW 0x08 +#define OPT4060_THRESHOLD_HIGH 0x09 +#define OPT4060_CTRL 0x0a +#define OPT4060_INT_CTRL 0x0b +#define OPT4060_RES_CTRL 0x0c +#define OPT4060_DEVICE_ID 0x11 + +/* OPT4060 register mask */ +#define OPT4060_EXPONENT_MASK GENMASK(15, 12) +#define OPT4060_MSB_MASK GENMASK(11, 0) +#define OPT4060_LSB_MASK GENMASK(15, 8) +#define OPT4060_COUNTER_MASK GENMASK(7, 4) +#define OPT4060_CRC_MASK GENMASK(3, 0) + +/* OPT4060 device id mask */ +#define OPT4060_DEVICE_ID_MASK GENMASK(11, 0) + +/* OPT4060 control register masks */ +#define OPT4060_CTRL_QWAKE_MASK BIT(15) +#define OPT4060_CTRL_RANGE_MASK GENMASK(13, 10) +#define OPT4060_CTRL_CONV_TIME_MASK GENMASK(9, 6) +#define OPT4060_CTRL_OPER_MODE_MASK GENMASK(5, 4) +#define OPT4060_CTRL_LATCH_MASK BIT(3) +#define OPT4060_CTRL_INT_POL_MASK BIT(2) +#define OPT4060_CTRL_FAULT_COUNT_MASK GENMASK(1, 0) + +/* OPT4060 interrupt control register masks */ +#define OPT4060_INT_CTRL_THRESH_SEL GENMASK(6, 5) +#define OPT4060_INT_CTRL_OUTPUT BIT(4) +#define OPT4060_INT_CTRL_INT_CFG GENMASK(3, 2) +#define OPT4060_INT_CTRL_THRESHOLD 0x0 +#define OPT4060_INT_CTRL_NEXT_CH 0x1 +#define OPT4060_INT_CTRL_ALL_CH 0x3 + +/* OPT4060 result control register masks */ +#define OPT4060_RES_CTRL_OVERLOAD BIT(3) +#define OPT4060_RES_CTRL_CONV_READY BIT(2) +#define OPT4060_RES_CTRL_FLAG_H BIT(1) +#define OPT4060_RES_CTRL_FLAG_L BIT(0) + +/* OPT4060 constants */ +#define OPT4060_DEVICE_ID_VAL 0x821 + +/* OPT4060 operating modes */ +#define OPT4060_CTRL_OPER_MODE_OFF 0x0 +#define OPT4060_CTRL_OPER_MODE_FORCED 0x1 +#define OPT4060_CTRL_OPER_MODE_ONE_SHOT 0x2 +#define OPT4060_CTRL_OPER_MODE_CONTINUOUS 0x3 + +/* OPT4060 conversion control register definitions */ +#define OPT4060_CTRL_CONVERSION_0_6MS 0x0 +#define OPT4060_CTRL_CONVERSION_1MS 0x1 +#define OPT4060_CTRL_CONVERSION_1_8MS 0x2 +#define OPT4060_CTRL_CONVERSION_3_4MS 0x3 +#define OPT4060_CTRL_CONVERSION_6_5MS 0x4 +#define OPT4060_CTRL_CONVERSION_12_7MS 0x5 +#define OPT4060_CTRL_CONVERSION_25MS 0x6 +#define OPT4060_CTRL_CONVERSION_50MS 0x7 +#define OPT4060_CTRL_CONVERSION_100MS 0x8 +#define OPT4060_CTRL_CONVERSION_200MS 0x9 +#define OPT4060_CTRL_CONVERSION_400MS 0xa +#define OPT4060_CTRL_CONVERSION_800MS 0xb + +/* OPT4060 fault count control register definitions */ +#define OPT4060_CTRL_FAULT_COUNT_1 0x0 +#define OPT4060_CTRL_FAULT_COUNT_2 0x1 +#define OPT4060_CTRL_FAULT_COUNT_4 0x2 +#define OPT4060_CTRL_FAULT_COUNT_8 0x3 + +/* OPT4060 scale light level range definitions */ +#define OPT4060_CTRL_LIGHT_SCALE_AUTO 12 + +/* OPT4060 default values */ +#define OPT4060_DEFAULT_CONVERSION_TIME OPT4060_CTRL_CONVERSION_50MS + +/* + * enum opt4060_chan_type - OPT4060 channel types + * @OPT4060_RED: Red channel. + * @OPT4060_GREEN: Green channel. + * @OPT4060_BLUE: Blue channel. + * @OPT4060_CLEAR: Clear (white) channel. + * @OPT4060_ILLUM: Calculated illuminance channel. + * @OPT4060_NUM_CHANS: Number of channel types. + */ +enum opt4060_chan_type { + OPT4060_RED, + OPT4060_GREEN, + OPT4060_BLUE, + OPT4060_CLEAR, + OPT4060_ILLUM, + OPT4060_NUM_CHANS +}; + +struct opt4060_chip { + struct regmap *regmap; + struct device *dev; + struct iio_trigger *trig; + u8 int_time; + int irq; + /* + * Mutex for protecting sensor irq settings. Switching between interrupt + * on each sample and on thresholds needs to be synchronized. + */ + struct mutex irq_setting_lock; + /* + * Mutex for protecting event enabling. + */ + struct mutex event_enabling_lock; + struct completion completion; + bool thresh_event_lo_active; + bool thresh_event_hi_active; +}; + +struct opt4060_channel_factor { + u32 mul; + u32 div; +}; + +static const int opt4060_int_time_available[][2] = { + { 0, 600 }, + { 0, 1000 }, + { 0, 1800 }, + { 0, 3400 }, + { 0, 6500 }, + { 0, 12700 }, + { 0, 25000 }, + { 0, 50000 }, + { 0, 100000 }, + { 0, 200000 }, + { 0, 400000 }, + { 0, 800000 }, +}; + +/* + * Conversion time is integration time + time to set register + * this is used as integration time. + */ +static const int opt4060_int_time_reg[][2] = { + { 600, OPT4060_CTRL_CONVERSION_0_6MS }, + { 1000, OPT4060_CTRL_CONVERSION_1MS }, + { 1800, OPT4060_CTRL_CONVERSION_1_8MS }, + { 3400, OPT4060_CTRL_CONVERSION_3_4MS }, + { 6500, OPT4060_CTRL_CONVERSION_6_5MS }, + { 12700, OPT4060_CTRL_CONVERSION_12_7MS }, + { 25000, OPT4060_CTRL_CONVERSION_25MS }, + { 50000, OPT4060_CTRL_CONVERSION_50MS }, + { 100000, OPT4060_CTRL_CONVERSION_100MS }, + { 200000, OPT4060_CTRL_CONVERSION_200MS }, + { 400000, OPT4060_CTRL_CONVERSION_400MS }, + { 800000, OPT4060_CTRL_CONVERSION_800MS }, +}; + +static int opt4060_als_time_to_index(const u32 als_integration_time) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(opt4060_int_time_available); i++) { + if (als_integration_time == opt4060_int_time_available[i][1]) + return i; + } + + return -EINVAL; +} + +static u8 opt4060_calculate_crc(u8 exp, u32 mantissa, u8 count) +{ + u8 crc; + + /* + * Calculates a 4-bit CRC from a 20-bit mantissa, 4-bit exponent and a 4-bit counter. + * crc[0] = XOR(mantissa[19:0], exp[3:0], count[3:0]) + * crc[1] = XOR(mantissa[1,3,5,7,9,11,13,15,17,19], exp[1,3], count[1,3]) + * crc[2] = XOR(mantissa[3,7,11,15,19], exp[3], count[3]) + * crc[3] = XOR(mantissa[3,11,19]) + */ + crc = (hweight32(mantissa) + hweight32(exp) + hweight32(count)) % 2; + crc |= ((hweight32(mantissa & 0xAAAAA) + hweight32(exp & 0xA) + + hweight32(count & 0xA)) % 2) << 1; + crc |= ((hweight32(mantissa & 0x88888) + hweight32(exp & 0x8) + + hweight32(count & 0x8)) % 2) << 2; + crc |= (hweight32(mantissa & 0x80808) % 2) << 3; + + return crc; +} + +static int opt4060_set_int_state(struct opt4060_chip *chip, u32 state) +{ + int ret; + unsigned int regval; + + guard(mutex)(&chip->irq_setting_lock); + + regval = FIELD_PREP(OPT4060_INT_CTRL_INT_CFG, state); + ret = regmap_update_bits(chip->regmap, OPT4060_INT_CTRL, + OPT4060_INT_CTRL_INT_CFG, regval); + if (ret) + dev_err(chip->dev, "Failed to set interrupt config\n"); + return ret; +} + +static int opt4060_set_sampling_mode(struct opt4060_chip *chip, + bool continuous) +{ + unsigned int reg; + int ret; + + ret = regmap_read(chip->regmap, OPT4060_CTRL, ®); + if (ret < 0) { + dev_err(chip->dev, "Failed to read ctrl register\n"); + return ret; + } + reg &= ~OPT4060_CTRL_OPER_MODE_MASK; + if (continuous) + reg |= FIELD_PREP(OPT4060_CTRL_OPER_MODE_MASK, + OPT4060_CTRL_OPER_MODE_CONTINUOUS); + else + reg |= FIELD_PREP(OPT4060_CTRL_OPER_MODE_MASK, + OPT4060_CTRL_OPER_MODE_ONE_SHOT); + + /* + * Trigger a new conversions by writing to CRTL register. It is not + * possible to use regmap_update_bits() since that will only write when + * data is modified. + */ + ret = regmap_write(chip->regmap, OPT4060_CTRL, reg); + if (ret) + dev_err(chip->dev, "Failed to set ctrl register\n"); + return ret; +} + +static bool opt4060_event_active(struct opt4060_chip *chip) +{ + return chip->thresh_event_lo_active || chip->thresh_event_hi_active; +} + +static int opt4060_set_state_common(struct opt4060_chip *chip, + bool continuous_sampling, + bool continuous_irq) +{ + int ret = 0; + + /* It is important to setup irq before sampling to avoid missing samples. */ + if (continuous_irq) + ret = opt4060_set_int_state(chip, OPT4060_INT_CTRL_ALL_CH); + else + ret = opt4060_set_int_state(chip, OPT4060_INT_CTRL_THRESHOLD); + if (ret) { + dev_err(chip->dev, "Failed to set irq state.\n"); + return ret; + } + + if (continuous_sampling || opt4060_event_active(chip)) + ret = opt4060_set_sampling_mode(chip, true); + else + ret = opt4060_set_sampling_mode(chip, false); + if (ret) + dev_err(chip->dev, "Failed to set sampling state.\n"); + return ret; +} + +/* + * Function for setting the driver state for sampling and irq. Either direct + * mode of buffer mode will be claimed during the transition to prevent races + * between sysfs read, buffer or events. + */ +static int opt4060_set_driver_state(struct iio_dev *indio_dev, + bool continuous_sampling, + bool continuous_irq) +{ + struct opt4060_chip *chip = iio_priv(indio_dev); + + IIO_DEV_GUARD_CURRENT_MODE(indio_dev); + + /* + * If we manage to claim buffer mode and we are using our own trigger, + * IRQ and sampling must go to or remain continuous. + */ + if (iio_buffer_enabled(indio_dev) && + iio_trigger_validate_own_device(indio_dev->trig, indio_dev)) + return opt4060_set_state_common(chip, true, true); + + /* + * This path means that we managed to claim direct mode. In this case + * the buffer isn't enabled and it's okay to leave continuous mode for + * sampling and/or irq. + */ + return opt4060_set_state_common(chip, continuous_sampling, continuous_irq); +} + +/* + * This function is called with framework mutex locked. + */ +static int opt4060_trigger_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct opt4060_chip *chip = iio_priv(indio_dev); + + return opt4060_set_state_common(chip, state, state); +} + +static int opt4060_read_raw_value(struct opt4060_chip *chip, + unsigned long address, u32 *raw) +{ + int ret; + u16 result[2]; + u32 mantissa_raw; + u16 msb, lsb; + u8 exp, count, crc, calc_crc; + + ret = regmap_bulk_read(chip->regmap, address, result, 2); + if (ret) { + dev_err(chip->dev, "Reading channel data failed\n"); + return ret; + } + exp = FIELD_GET(OPT4060_EXPONENT_MASK, result[0]); + msb = FIELD_GET(OPT4060_MSB_MASK, result[0]); + count = FIELD_GET(OPT4060_COUNTER_MASK, result[1]); + crc = FIELD_GET(OPT4060_CRC_MASK, result[1]); + lsb = FIELD_GET(OPT4060_LSB_MASK, result[1]); + mantissa_raw = (msb << 8) + lsb; + calc_crc = opt4060_calculate_crc(exp, mantissa_raw, count); + if (calc_crc != crc) + return -EIO; + *raw = mantissa_raw << exp; + return 0; +} + +static int opt4060_trigger_new_samples(struct iio_dev *indio_dev) +{ + struct opt4060_chip *chip = iio_priv(indio_dev); + int ret; + + /* + * The conversion time should be 500us startup time plus the integration time + * times the number of channels. An exact timeout isn't critical, it's better + * not to get incorrect errors in the log. Setting the timeout to double the + * theoretical time plus and extra 100ms margin. + */ + unsigned int timeout_us = (500 + OPT4060_NUM_CHANS * + opt4060_int_time_reg[chip->int_time][0]) * 2 + 100000; + + /* Setting the state in one shot mode with irq on each sample. */ + ret = opt4060_set_driver_state(indio_dev, false, true); + if (ret) + return ret; + + if (chip->irq) { + guard(mutex)(&chip->irq_setting_lock); + reinit_completion(&chip->completion); + if (wait_for_completion_timeout(&chip->completion, + usecs_to_jiffies(timeout_us)) == 0) { + dev_err(chip->dev, "Completion timed out.\n"); + return -ETIME; + } + } else { + unsigned int ready; + + ret = regmap_read_poll_timeout(chip->regmap, OPT4060_RES_CTRL, + ready, (ready & OPT4060_RES_CTRL_CONV_READY), + 1000, timeout_us); + if (ret) + dev_err(chip->dev, "Conversion ready did not finish within timeout.\n"); + } + /* Setting the state in one shot mode with irq on thresholds. */ + return opt4060_set_driver_state(indio_dev, false, false); +} + +static int opt4060_read_chan_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val) +{ + struct opt4060_chip *chip = iio_priv(indio_dev); + u32 adc_raw; + int ret; + + ret = opt4060_trigger_new_samples(indio_dev); + if (ret) { + dev_err(chip->dev, "Failed to trigger new samples.\n"); + return ret; + } + + ret = opt4060_read_raw_value(chip, chan->address, &adc_raw); + if (ret) { + dev_err(chip->dev, "Reading raw channel data failed.\n"); + return ret; + } + *val = adc_raw; + return IIO_VAL_INT; +} + +/* + * Returns the scale values used for red, green and blue. Scales the raw value + * so that for a particular test light source, typically white, the measurement + * intensity is the same across different color channels. + */ +static int opt4060_get_chan_scale(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + struct opt4060_chip *chip = iio_priv(indio_dev); + + switch (chan->scan_index) { + case OPT4060_RED: + /* 2.4 */ + *val = 2; + *val2 = 400000; + break; + case OPT4060_GREEN: + /* 1.0 */ + *val = 1; + *val2 = 0; + break; + case OPT4060_BLUE: + /* 1.3 */ + *val = 1; + *val2 = 300000; + break; + default: + dev_err(chip->dev, "Unexpected channel index.\n"); + return -EINVAL; + } + return IIO_VAL_INT_PLUS_MICRO; +} + +static int opt4060_calc_illuminance(struct opt4060_chip *chip, int *val) +{ + u32 lux_raw; + int ret; + + /* The green wide spectral channel is used for illuminance. */ + ret = opt4060_read_raw_value(chip, OPT4060_GREEN_MSB, &lux_raw); + if (ret) { + dev_err(chip->dev, "Reading raw channel data failed\n"); + return ret; + } + + /* Illuminance is calculated by ADC_RAW * 2.15e-3. */ + *val = DIV_U64_ROUND_CLOSEST((u64)(lux_raw * 215), 1000); + return ret; +} + +static int opt4060_read_illuminance(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct opt4060_chip *chip = iio_priv(indio_dev); + int ret; + + ret = opt4060_trigger_new_samples(indio_dev); + if (ret) { + dev_err(chip->dev, "Failed to trigger new samples.\n"); + return ret; + } + ret = opt4060_calc_illuminance(chip, val); + if (ret) { + dev_err(chip->dev, "Failed to calculate illuminance.\n"); + return ret; + } + + return IIO_VAL_INT; +} + +static int opt4060_set_int_time(struct opt4060_chip *chip) +{ + unsigned int regval; + int ret; + + regval = FIELD_PREP(OPT4060_CTRL_CONV_TIME_MASK, chip->int_time); + ret = regmap_update_bits(chip->regmap, OPT4060_CTRL, + OPT4060_CTRL_CONV_TIME_MASK, regval); + if (ret) + dev_err(chip->dev, "Failed to set integration time.\n"); + + return ret; +} + +static int opt4060_power_down(struct opt4060_chip *chip) +{ + int ret; + + ret = regmap_clear_bits(chip->regmap, OPT4060_CTRL, OPT4060_CTRL_OPER_MODE_MASK); + if (ret) + dev_err(chip->dev, "Failed to power down\n"); + + return ret; +} + +static void opt4060_chip_off_action(void *chip) +{ + opt4060_power_down(chip); +} + +#define _OPT4060_COLOR_CHANNEL(_color, _mask, _ev_spec, _num_ev_spec) \ +{ \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .channel2 = IIO_MOD_LIGHT_##_color, \ + .info_mask_separate = _mask, \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), \ + .address = OPT4060_##_color##_MSB, \ + .scan_index = OPT4060_##_color, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 32, \ + .storagebits = 32, \ + .endianness = IIO_CPU, \ + }, \ + .event_spec = _ev_spec, \ + .num_event_specs = _num_ev_spec, \ +} + +#define OPT4060_COLOR_CHANNEL(_color, _mask) \ + _OPT4060_COLOR_CHANNEL(_color, _mask, opt4060_event_spec, \ + ARRAY_SIZE(opt4060_event_spec)) \ + +#define OPT4060_COLOR_CHANNEL_NO_EVENTS(_color, _mask) \ + _OPT4060_COLOR_CHANNEL(_color, _mask, NULL, 0) \ + +#define OPT4060_LIGHT_CHANNEL(_channel) \ +{ \ + .type = IIO_LIGHT, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), \ + .scan_index = OPT4060_##_channel, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 32, \ + .storagebits = 32, \ + .endianness = IIO_CPU, \ + }, \ +} + +static const struct iio_event_spec opt4060_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_PERIOD), + }, +}; + +static const struct iio_chan_spec opt4060_channels[] = { + OPT4060_COLOR_CHANNEL(RED, BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE)), + OPT4060_COLOR_CHANNEL(GREEN, BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE)), + OPT4060_COLOR_CHANNEL(BLUE, BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE)), + OPT4060_COLOR_CHANNEL(CLEAR, BIT(IIO_CHAN_INFO_RAW)), + OPT4060_LIGHT_CHANNEL(ILLUM), + IIO_CHAN_SOFT_TIMESTAMP(OPT4060_NUM_CHANS), +}; + +static const struct iio_chan_spec opt4060_channels_no_events[] = { + OPT4060_COLOR_CHANNEL_NO_EVENTS(RED, BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE)), + OPT4060_COLOR_CHANNEL_NO_EVENTS(GREEN, BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE)), + OPT4060_COLOR_CHANNEL_NO_EVENTS(BLUE, BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE)), + OPT4060_COLOR_CHANNEL_NO_EVENTS(CLEAR, BIT(IIO_CHAN_INFO_RAW)), + OPT4060_LIGHT_CHANNEL(ILLUM), + IIO_CHAN_SOFT_TIMESTAMP(OPT4060_NUM_CHANS), +}; + +static int opt4060_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct opt4060_chip *chip = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return opt4060_read_chan_raw(indio_dev, chan, val); + case IIO_CHAN_INFO_SCALE: + return opt4060_get_chan_scale(indio_dev, chan, val, val2); + case IIO_CHAN_INFO_PROCESSED: + return opt4060_read_illuminance(indio_dev, chan, val); + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = opt4060_int_time_reg[chip->int_time][0]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int opt4060_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct opt4060_chip *chip = iio_priv(indio_dev); + int int_time; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val) + return -EINVAL; + + int_time = opt4060_als_time_to_index(val2); + if (int_time < 0) + return int_time; + chip->int_time = int_time; + return opt4060_set_int_time(chip); + default: + return -EINVAL; + } +} + +static int opt4060_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static u32 opt4060_calc_th_reg(u32 adc_val) +{ + u32 th_val, th_exp, bits; + /* + * The threshold registers take 4 bits of exponent and 12 bits of data + * ADC = TH_VAL << (8 + TH_EXP) + */ + bits = fls(adc_val); + + if (bits > 31) + th_exp = 11; /* Maximum exponent */ + else if (bits > 20) + th_exp = bits - 20; + else + th_exp = 0; + th_val = (adc_val >> (8 + th_exp)) & 0xfff; + + return (th_exp << 12) + th_val; +} + +static u32 opt4060_calc_val_from_th_reg(u32 th_reg) +{ + /* + * The threshold registers take 4 bits of exponent and 12 bits of data + * ADC = TH_VAL << (8 + TH_EXP) + */ + u32 th_val, th_exp; + + th_exp = (th_reg >> 12) & 0xf; + th_val = th_reg & 0xfff; + + return th_val << (8 + th_exp); +} + +static int opt4060_read_available(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + *length = ARRAY_SIZE(opt4060_int_time_available) * 2; + *vals = (const int *)opt4060_int_time_available; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + + default: + return -EINVAL; + } +} + +static ssize_t opt4060_read_ev_period(struct opt4060_chip *chip, int *val, + int *val2) +{ + int ret, pers, fault_count, int_time; + u64 uval; + u32 rem; + + int_time = opt4060_int_time_reg[chip->int_time][0]; + + ret = regmap_read(chip->regmap, OPT4060_CTRL, &fault_count); + if (ret < 0) + return ret; + + fault_count = fault_count & OPT4060_CTRL_FAULT_COUNT_MASK; + switch (fault_count) { + case OPT4060_CTRL_FAULT_COUNT_2: + pers = 2; + break; + case OPT4060_CTRL_FAULT_COUNT_4: + pers = 4; + break; + case OPT4060_CTRL_FAULT_COUNT_8: + pers = 8; + break; + + default: + pers = 1; + break; + } + + uval = mul_u32_u32(int_time, pers); + *val = div_u64_rem(uval, MICRO, &rem); + *val2 = rem; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static ssize_t opt4060_write_ev_period(struct opt4060_chip *chip, int val, + int val2) +{ + u64 uval, int_time; + unsigned int regval, fault_count_val; + + uval = mul_u32_u32(val, MICRO) + val2; + int_time = opt4060_int_time_reg[chip->int_time][0]; + + /* Check if the period is closest to 1, 2, 4 or 8 times integration time.*/ + if (uval <= int_time) + fault_count_val = OPT4060_CTRL_FAULT_COUNT_1; + else if (uval <= int_time * 2) + fault_count_val = OPT4060_CTRL_FAULT_COUNT_2; + else if (uval <= int_time * 4) + fault_count_val = OPT4060_CTRL_FAULT_COUNT_4; + else + fault_count_val = OPT4060_CTRL_FAULT_COUNT_8; + + regval = FIELD_PREP(OPT4060_CTRL_FAULT_COUNT_MASK, fault_count_val); + return regmap_update_bits(chip->regmap, OPT4060_CTRL, + OPT4060_CTRL_FAULT_COUNT_MASK, regval); +} + +static int opt4060_get_channel_sel(struct opt4060_chip *chip, int *ch_sel) +{ + int ret; + u32 regval; + + ret = regmap_read(chip->regmap, OPT4060_INT_CTRL, ®val); + if (ret) { + dev_err(chip->dev, "Failed to get channel selection.\n"); + return ret; + } + *ch_sel = FIELD_GET(OPT4060_INT_CTRL_THRESH_SEL, regval); + return ret; +} + +static int opt4060_set_channel_sel(struct opt4060_chip *chip, int ch_sel) +{ + int ret; + u32 regval; + + regval = FIELD_PREP(OPT4060_INT_CTRL_THRESH_SEL, ch_sel); + ret = regmap_update_bits(chip->regmap, OPT4060_INT_CTRL, + OPT4060_INT_CTRL_THRESH_SEL, regval); + if (ret) + dev_err(chip->dev, "Failed to set channel selection.\n"); + return ret; +} + +static int opt4060_get_thresholds(struct opt4060_chip *chip, u32 *th_lo, u32 *th_hi) +{ + int ret; + u32 regval; + + ret = regmap_read(chip->regmap, OPT4060_THRESHOLD_LOW, ®val); + if (ret) { + dev_err(chip->dev, "Failed to read THRESHOLD_LOW.\n"); + return ret; + } + *th_lo = opt4060_calc_val_from_th_reg(regval); + + ret = regmap_read(chip->regmap, OPT4060_THRESHOLD_HIGH, ®val); + if (ret) { + dev_err(chip->dev, "Failed to read THRESHOLD_HIGH.\n"); + return ret; + } + *th_hi = opt4060_calc_val_from_th_reg(regval); + + return ret; +} + +static int opt4060_set_thresholds(struct opt4060_chip *chip, u32 th_lo, u32 th_hi) +{ + int ret; + + ret = regmap_write(chip->regmap, OPT4060_THRESHOLD_LOW, opt4060_calc_th_reg(th_lo)); + if (ret) { + dev_err(chip->dev, "Failed to write THRESHOLD_LOW.\n"); + return ret; + } + + ret = regmap_write(chip->regmap, OPT4060_THRESHOLD_HIGH, opt4060_calc_th_reg(th_hi)); + if (ret) + dev_err(chip->dev, "Failed to write THRESHOLD_HIGH.\n"); + + return ret; +} + +static int opt4060_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct opt4060_chip *chip = iio_priv(indio_dev); + u32 th_lo, th_hi; + int ret; + + if (chan->type != IIO_INTENSITY) + return -EINVAL; + if (type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + switch (info) { + case IIO_EV_INFO_VALUE: + ret = opt4060_get_thresholds(chip, &th_lo, &th_hi); + if (ret) + return ret; + if (dir == IIO_EV_DIR_FALLING) { + *val = th_lo; + ret = IIO_VAL_INT; + } else if (dir == IIO_EV_DIR_RISING) { + *val = th_hi; + ret = IIO_VAL_INT; + } + return ret; + case IIO_EV_INFO_PERIOD: + return opt4060_read_ev_period(chip, val, val2); + default: + return -EINVAL; + } +} + +static int opt4060_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct opt4060_chip *chip = iio_priv(indio_dev); + u32 th_lo, th_hi; + int ret; + + if (chan->type != IIO_INTENSITY) + return -EINVAL; + if (type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + switch (info) { + case IIO_EV_INFO_VALUE: + ret = opt4060_get_thresholds(chip, &th_lo, &th_hi); + if (ret) + return ret; + if (dir == IIO_EV_DIR_FALLING) + th_lo = val; + else if (dir == IIO_EV_DIR_RISING) + th_hi = val; + return opt4060_set_thresholds(chip, th_lo, th_hi); + case IIO_EV_INFO_PERIOD: + return opt4060_write_ev_period(chip, val, val2); + default: + return -EINVAL; + } +} + +static int opt4060_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + int ch_sel, ch_idx = chan->scan_index; + struct opt4060_chip *chip = iio_priv(indio_dev); + int ret; + + if (chan->type != IIO_INTENSITY) + return -EINVAL; + if (type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + ret = opt4060_get_channel_sel(chip, &ch_sel); + if (ret) + return ret; + + if (((dir == IIO_EV_DIR_FALLING) && chip->thresh_event_lo_active) || + ((dir == IIO_EV_DIR_RISING) && chip->thresh_event_hi_active)) + return ch_sel == ch_idx; + + return ret; +} + +static int opt4060_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + int ch_sel, ch_idx = chan->scan_index; + struct opt4060_chip *chip = iio_priv(indio_dev); + int ret; + + guard(mutex)(&chip->event_enabling_lock); + + if (chan->type != IIO_INTENSITY) + return -EINVAL; + if (type != IIO_EV_TYPE_THRESH) + return -EINVAL; + + ret = opt4060_get_channel_sel(chip, &ch_sel); + if (ret) + return ret; + + if (state) { + /* Only one channel can be active at the same time */ + if ((chip->thresh_event_lo_active || chip->thresh_event_hi_active) && + (ch_idx != ch_sel)) + return -EBUSY; + if (dir == IIO_EV_DIR_FALLING) + chip->thresh_event_lo_active = true; + else if (dir == IIO_EV_DIR_RISING) + chip->thresh_event_hi_active = true; + ret = opt4060_set_channel_sel(chip, ch_idx); + if (ret) + return ret; + } else { + if (ch_idx == ch_sel) { + if (dir == IIO_EV_DIR_FALLING) + chip->thresh_event_lo_active = false; + else if (dir == IIO_EV_DIR_RISING) + chip->thresh_event_hi_active = false; + } + } + + return opt4060_set_driver_state(indio_dev, + chip->thresh_event_hi_active | + chip->thresh_event_lo_active, + false); +} + +static const struct iio_info opt4060_info = { + .read_raw = opt4060_read_raw, + .write_raw = opt4060_write_raw, + .write_raw_get_fmt = opt4060_write_raw_get_fmt, + .read_avail = opt4060_read_available, + .read_event_value = opt4060_read_event, + .write_event_value = opt4060_write_event, + .read_event_config = opt4060_read_event_config, + .write_event_config = opt4060_write_event_config, +}; + +static const struct iio_info opt4060_info_no_irq = { + .read_raw = opt4060_read_raw, + .write_raw = opt4060_write_raw, + .write_raw_get_fmt = opt4060_write_raw_get_fmt, + .read_avail = opt4060_read_available, +}; + +static int opt4060_load_defaults(struct opt4060_chip *chip) +{ + u16 reg; + int ret; + + chip->int_time = OPT4060_DEFAULT_CONVERSION_TIME; + + /* Set initial MIN/MAX thresholds */ + ret = opt4060_set_thresholds(chip, 0, UINT_MAX); + if (ret) + return ret; + + /* + * Setting auto-range, latched window for thresholds, one-shot conversion + * and quick wake-up mode as default. + */ + reg = FIELD_PREP(OPT4060_CTRL_RANGE_MASK, + OPT4060_CTRL_LIGHT_SCALE_AUTO); + reg |= FIELD_PREP(OPT4060_CTRL_CONV_TIME_MASK, chip->int_time); + reg |= FIELD_PREP(OPT4060_CTRL_OPER_MODE_MASK, + OPT4060_CTRL_OPER_MODE_ONE_SHOT); + reg |= OPT4060_CTRL_QWAKE_MASK | OPT4060_CTRL_LATCH_MASK; + + ret = regmap_write(chip->regmap, OPT4060_CTRL, reg); + if (ret) + dev_err(chip->dev, "Failed to set configuration\n"); + + return ret; +} + +static bool opt4060_volatile_reg(struct device *dev, unsigned int reg) +{ + return reg <= OPT4060_CLEAR_LSB || reg == OPT4060_RES_CTRL; +} + +static bool opt4060_writable_reg(struct device *dev, unsigned int reg) +{ + return reg >= OPT4060_THRESHOLD_LOW || reg >= OPT4060_INT_CTRL; +} + +static bool opt4060_readonly_reg(struct device *dev, unsigned int reg) +{ + return reg == OPT4060_DEVICE_ID; +} + +static bool opt4060_readable_reg(struct device *dev, unsigned int reg) +{ + /* Volatile, writable and read-only registers are readable. */ + return opt4060_volatile_reg(dev, reg) || opt4060_writable_reg(dev, reg) || + opt4060_readonly_reg(dev, reg); +} + +static const struct regmap_config opt4060_regmap_config = { + .name = "opt4060", + .reg_bits = 8, + .val_bits = 16, + .cache_type = REGCACHE_MAPLE, + .max_register = OPT4060_DEVICE_ID, + .readable_reg = opt4060_readable_reg, + .writeable_reg = opt4060_writable_reg, + .volatile_reg = opt4060_volatile_reg, + .val_format_endian = REGMAP_ENDIAN_BIG, +}; + +static const struct iio_trigger_ops opt4060_trigger_ops = { + .set_trigger_state = opt4060_trigger_set_state, +}; + +static irqreturn_t opt4060_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *idev = pf->indio_dev; + struct opt4060_chip *chip = iio_priv(idev); + struct { + u32 chan[OPT4060_NUM_CHANS]; + aligned_s64 ts; + } raw = { }; + int i = 0; + int chan, ret; + + /* If the trigger is not from this driver, a new sample is needed.*/ + if (iio_trigger_validate_own_device(idev->trig, idev)) + opt4060_trigger_new_samples(idev); + + iio_for_each_active_channel(idev, chan) { + if (chan == OPT4060_ILLUM) + ret = opt4060_calc_illuminance(chip, &raw.chan[i++]); + else + ret = opt4060_read_raw_value(chip, + idev->channels[chan].address, + &raw.chan[i++]); + if (ret) { + dev_err(chip->dev, "Reading channel data failed\n"); + goto err_read; + } + } + + iio_push_to_buffers_with_ts(idev, &raw, sizeof(raw), pf->timestamp); +err_read: + iio_trigger_notify_done(idev->trig); + return IRQ_HANDLED; +} + +static irqreturn_t opt4060_irq_thread(int irq, void *private) +{ + struct iio_dev *idev = private; + struct opt4060_chip *chip = iio_priv(idev); + int ret, dummy; + unsigned int int_res; + + ret = regmap_read(chip->regmap, OPT4060_RES_CTRL, &int_res); + if (ret < 0) { + dev_err(chip->dev, "Failed to read interrupt reasons.\n"); + return IRQ_NONE; + } + + /* Read OPT4060_CTRL to clear interrupt */ + ret = regmap_read(chip->regmap, OPT4060_CTRL, &dummy); + if (ret < 0) { + dev_err(chip->dev, "Failed to clear interrupt\n"); + return IRQ_NONE; + } + + /* Handle events */ + if (int_res & (OPT4060_RES_CTRL_FLAG_H | OPT4060_RES_CTRL_FLAG_L)) { + u64 code; + int chan = 0; + + ret = opt4060_get_channel_sel(chip, &chan); + if (ret) { + dev_err(chip->dev, "Failed to read threshold channel.\n"); + return IRQ_NONE; + } + + /* Check if the interrupt is from the lower threshold */ + if (int_res & OPT4060_RES_CTRL_FLAG_L) { + code = IIO_MOD_EVENT_CODE(IIO_INTENSITY, + chan, + idev->channels[chan].channel2, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING); + iio_push_event(idev, code, iio_get_time_ns(idev)); + } + /* Check if the interrupt is from the upper threshold */ + if (int_res & OPT4060_RES_CTRL_FLAG_H) { + code = IIO_MOD_EVENT_CODE(IIO_INTENSITY, + chan, + idev->channels[chan].channel2, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING); + iio_push_event(idev, code, iio_get_time_ns(idev)); + } + } + + /* Handle conversion ready */ + if (int_res & OPT4060_RES_CTRL_CONV_READY) { + /* Signal completion for potentially waiting reads */ + complete(&chip->completion); + + /* Handle data ready triggers */ + if (iio_buffer_enabled(idev)) + iio_trigger_poll_nested(chip->trig); + } + return IRQ_HANDLED; +} + +static int opt4060_setup_buffer(struct opt4060_chip *chip, struct iio_dev *idev) +{ + int ret; + + ret = devm_iio_triggered_buffer_setup(chip->dev, idev, + &iio_pollfunc_store_time, + opt4060_trigger_handler, NULL); + if (ret) + return dev_err_probe(chip->dev, ret, + "Buffer setup failed.\n"); + return ret; +} + +static int opt4060_setup_trigger(struct opt4060_chip *chip, struct iio_dev *idev) +{ + struct iio_trigger *data_trigger; + char *name; + int ret; + + data_trigger = devm_iio_trigger_alloc(chip->dev, "%s-data-ready-dev%d", + idev->name, iio_device_id(idev)); + if (!data_trigger) + return -ENOMEM; + + /* + * The data trigger allows for sample capture on each new conversion + * ready interrupt. + */ + chip->trig = data_trigger; + data_trigger->ops = &opt4060_trigger_ops; + iio_trigger_set_drvdata(data_trigger, idev); + ret = devm_iio_trigger_register(chip->dev, data_trigger); + if (ret) + return dev_err_probe(chip->dev, ret, + "Data ready trigger registration failed\n"); + + name = devm_kasprintf(chip->dev, GFP_KERNEL, "%s-opt4060", + dev_name(chip->dev)); + if (!name) + return -ENOMEM; + + ret = devm_request_threaded_irq(chip->dev, chip->irq, NULL, opt4060_irq_thread, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + name, idev); + if (ret) + return ret; + + init_completion(&chip->completion); + + ret = devm_mutex_init(chip->dev, &chip->irq_setting_lock); + if (ret) + return ret; + + ret = devm_mutex_init(chip->dev, &chip->event_enabling_lock); + if (ret) + return ret; + + ret = regmap_write_bits(chip->regmap, OPT4060_INT_CTRL, + OPT4060_INT_CTRL_OUTPUT, + OPT4060_INT_CTRL_OUTPUT); + if (ret) + return dev_err_probe(chip->dev, ret, + "Failed to set interrupt as output\n"); + + return 0; +} + +static int opt4060_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct opt4060_chip *chip; + struct iio_dev *indio_dev; + int ret; + unsigned int regval, dev_id; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable vdd supply\n"); + + chip->regmap = devm_regmap_init_i2c(client, &opt4060_regmap_config); + if (IS_ERR(chip->regmap)) + return dev_err_probe(dev, PTR_ERR(chip->regmap), + "regmap initialization failed\n"); + + chip->dev = dev; + chip->irq = client->irq; + + ret = regmap_reinit_cache(chip->regmap, &opt4060_regmap_config); + if (ret) + return dev_err_probe(dev, ret, + "failed to reinit regmap cache\n"); + + ret = regmap_read(chip->regmap, OPT4060_DEVICE_ID, ®val); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to read the device ID register\n"); + + dev_id = FIELD_GET(OPT4060_DEVICE_ID_MASK, regval); + if (dev_id != OPT4060_DEVICE_ID_VAL) + dev_info(dev, "Device ID: %#04x unknown\n", dev_id); + + if (chip->irq) { + indio_dev->info = &opt4060_info; + indio_dev->channels = opt4060_channels; + indio_dev->num_channels = ARRAY_SIZE(opt4060_channels); + } else { + indio_dev->info = &opt4060_info_no_irq; + indio_dev->channels = opt4060_channels_no_events; + indio_dev->num_channels = ARRAY_SIZE(opt4060_channels_no_events); + } + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = "opt4060"; + + ret = opt4060_load_defaults(chip); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to set sensor defaults\n"); + + ret = devm_add_action_or_reset(dev, opt4060_chip_off_action, chip); + if (ret < 0) + return ret; + + ret = opt4060_setup_buffer(chip, indio_dev); + if (ret) + return ret; + + if (chip->irq) { + ret = opt4060_setup_trigger(chip, indio_dev); + if (ret) + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct i2c_device_id opt4060_id[] = { + { .name = "opt4060" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, opt4060_id); + +static const struct of_device_id opt4060_of_match[] = { + { .compatible = "ti,opt4060" }, + { } +}; +MODULE_DEVICE_TABLE(of, opt4060_of_match); + +static struct i2c_driver opt4060_driver = { + .driver = { + .name = "opt4060", + .of_match_table = opt4060_of_match, + }, + .probe = opt4060_probe, + .id_table = opt4060_id, +}; +module_i2c_driver(opt4060_driver); + +MODULE_AUTHOR("Per-Daniel Olsson "); +MODULE_DESCRIPTION("Texas Instruments OPT4060 RGBW color sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/pa12203001.c b/drivers/iio/light/pa12203001.c new file mode 100644 index 000000000..c0f4db8d9 --- /dev/null +++ b/drivers/iio/light/pa12203001.c @@ -0,0 +1,481 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015 Intel Corporation + * + * Driver for TXC PA12203001 Proximity and Ambient Light Sensor. + * + * To do: Interrupt support. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PA12203001_DRIVER_NAME "pa12203001" + +#define PA12203001_REG_CFG0 0x00 +#define PA12203001_REG_CFG1 0x01 +#define PA12203001_REG_CFG2 0x02 +#define PA12203001_REG_CFG3 0x03 + +#define PA12203001_REG_ADL 0x0b +#define PA12203001_REG_PDH 0x0e + +#define PA12203001_REG_POFS 0x10 +#define PA12203001_REG_PSET 0x11 + +#define PA12203001_ALS_EN_MASK BIT(0) +#define PA12203001_PX_EN_MASK BIT(1) +#define PA12203001_PX_NORMAL_MODE_MASK GENMASK(7, 6) +#define PA12203001_AFSR_MASK GENMASK(5, 4) +#define PA12203001_AFSR_SHIFT 4 + +#define PA12203001_PSCAN 0x03 + +/* als range 31000, ps, als disabled */ +#define PA12203001_REG_CFG0_DEFAULT 0x30 + +/* led current: 100 mA */ +#define PA12203001_REG_CFG1_DEFAULT 0x20 + +/* ps mode: normal, interrupts not active */ +#define PA12203001_REG_CFG2_DEFAULT 0xcc + +#define PA12203001_REG_CFG3_DEFAULT 0x00 + +#define PA12203001_SLEEP_DELAY_MS 3000 + +#define PA12203001_CHIP_ENABLE 0xff +#define PA12203001_CHIP_DISABLE 0x00 + +/* available scales: corresponding to [500, 4000, 7000, 31000] lux */ +static const int pa12203001_scales[] = { 7629, 61036, 106813, 473029}; + +struct pa12203001_data { + struct i2c_client *client; + + /* protect device states */ + struct mutex lock; + + bool als_enabled; + bool px_enabled; + bool als_needs_enable; + bool px_needs_enable; + + struct regmap *map; +}; + +static const struct { + u8 reg; + u8 val; +} regvals[] = { + {PA12203001_REG_CFG0, PA12203001_REG_CFG0_DEFAULT}, + {PA12203001_REG_CFG1, PA12203001_REG_CFG1_DEFAULT}, + {PA12203001_REG_CFG2, PA12203001_REG_CFG2_DEFAULT}, + {PA12203001_REG_CFG3, PA12203001_REG_CFG3_DEFAULT}, + {PA12203001_REG_PSET, PA12203001_PSCAN}, +}; + +static IIO_CONST_ATTR(in_illuminance_scale_available, + "0.007629 0.061036 0.106813 0.473029"); + +static struct attribute *pa12203001_attrs[] = { + &iio_const_attr_in_illuminance_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group pa12203001_attr_group = { + .attrs = pa12203001_attrs, +}; + +static const struct iio_chan_spec pa12203001_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + } +}; + +static const struct regmap_range pa12203001_volatile_regs_ranges[] = { + regmap_reg_range(PA12203001_REG_ADL, PA12203001_REG_ADL + 1), + regmap_reg_range(PA12203001_REG_PDH, PA12203001_REG_PDH), +}; + +static const struct regmap_access_table pa12203001_volatile_regs = { + .yes_ranges = pa12203001_volatile_regs_ranges, + .n_yes_ranges = ARRAY_SIZE(pa12203001_volatile_regs_ranges), +}; + +static const struct regmap_config pa12203001_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = PA12203001_REG_PSET, + .cache_type = REGCACHE_RBTREE, + .volatile_table = &pa12203001_volatile_regs, +}; + +static inline int pa12203001_als_enable(struct pa12203001_data *data, u8 enable) +{ + int ret; + + ret = regmap_update_bits(data->map, PA12203001_REG_CFG0, + PA12203001_ALS_EN_MASK, enable); + if (ret < 0) + return ret; + + data->als_enabled = !!enable; + + return 0; +} + +static inline int pa12203001_px_enable(struct pa12203001_data *data, u8 enable) +{ + int ret; + + ret = regmap_update_bits(data->map, PA12203001_REG_CFG0, + PA12203001_PX_EN_MASK, enable); + if (ret < 0) + return ret; + + data->px_enabled = !!enable; + + return 0; +} + +static int pa12203001_set_power_state(struct pa12203001_data *data, bool on, + u8 mask) +{ +#ifdef CONFIG_PM + int ret; + + if (on && (mask & PA12203001_ALS_EN_MASK)) { + mutex_lock(&data->lock); + if (data->px_enabled) { + ret = pa12203001_als_enable(data, + PA12203001_ALS_EN_MASK); + if (ret < 0) + goto err; + } else { + data->als_needs_enable = true; + } + mutex_unlock(&data->lock); + } + + if (on && (mask & PA12203001_PX_EN_MASK)) { + mutex_lock(&data->lock); + if (data->als_enabled) { + ret = pa12203001_px_enable(data, PA12203001_PX_EN_MASK); + if (ret < 0) + goto err; + } else { + data->px_needs_enable = true; + } + mutex_unlock(&data->lock); + } + + if (on) + return pm_runtime_resume_and_get(&data->client->dev); + + return pm_runtime_put_autosuspend(&data->client->dev); + +err: + mutex_unlock(&data->lock); + return ret; + +#endif + return 0; +} + +static int pa12203001_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct pa12203001_data *data = iio_priv(indio_dev); + int ret; + u8 dev_mask; + unsigned int reg_byte; + __le16 reg_word; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_LIGHT: + dev_mask = PA12203001_ALS_EN_MASK; + ret = pa12203001_set_power_state(data, true, dev_mask); + if (ret < 0) + return ret; + /* + * ALS ADC value is stored in registers + * PA12203001_REG_ADL and in PA12203001_REG_ADL + 1. + */ + ret = regmap_bulk_read(data->map, PA12203001_REG_ADL, + ®_word, 2); + if (ret < 0) + goto reg_err; + + *val = le16_to_cpu(reg_word); + ret = pa12203001_set_power_state(data, false, dev_mask); + if (ret < 0) + return ret; + break; + case IIO_PROXIMITY: + dev_mask = PA12203001_PX_EN_MASK; + ret = pa12203001_set_power_state(data, true, dev_mask); + if (ret < 0) + return ret; + ret = regmap_read(data->map, PA12203001_REG_PDH, + ®_byte); + if (ret < 0) + goto reg_err; + + *val = reg_byte; + ret = pa12203001_set_power_state(data, false, dev_mask); + if (ret < 0) + return ret; + break; + default: + return -EINVAL; + } + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = regmap_read(data->map, PA12203001_REG_CFG0, ®_byte); + if (ret < 0) + return ret; + *val = 0; + reg_byte = (reg_byte & PA12203001_AFSR_MASK); + *val2 = pa12203001_scales[reg_byte >> 4]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + +reg_err: + pa12203001_set_power_state(data, false, dev_mask); + return ret; +} + +static int pa12203001_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct pa12203001_data *data = iio_priv(indio_dev); + int i, ret, new_val; + unsigned int reg_byte; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + ret = regmap_read(data->map, PA12203001_REG_CFG0, ®_byte); + if (val != 0 || ret < 0) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(pa12203001_scales); i++) { + if (val2 == pa12203001_scales[i]) { + new_val = i << PA12203001_AFSR_SHIFT; + return regmap_update_bits(data->map, + PA12203001_REG_CFG0, + PA12203001_AFSR_MASK, + new_val); + } + } + break; + default: + break; + } + + return -EINVAL; +} + +static const struct iio_info pa12203001_info = { + .read_raw = pa12203001_read_raw, + .write_raw = pa12203001_write_raw, + .attrs = &pa12203001_attr_group, +}; + +static int pa12203001_init(struct iio_dev *indio_dev) +{ + struct pa12203001_data *data = iio_priv(indio_dev); + int i, ret; + + for (i = 0; i < ARRAY_SIZE(regvals); i++) { + ret = regmap_write(data->map, regvals[i].reg, regvals[i].val); + if (ret < 0) + return ret; + } + + return 0; +} + +static int pa12203001_power_chip(struct iio_dev *indio_dev, u8 state) +{ + struct pa12203001_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->lock); + ret = pa12203001_als_enable(data, state); + if (ret < 0) + goto out; + + ret = pa12203001_px_enable(data, state); + +out: + mutex_unlock(&data->lock); + return ret; +} + +static int pa12203001_probe(struct i2c_client *client) +{ + struct pa12203001_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, + sizeof(struct pa12203001_data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + data->map = devm_regmap_init_i2c(client, &pa12203001_regmap_config); + if (IS_ERR(data->map)) + return PTR_ERR(data->map); + + mutex_init(&data->lock); + + indio_dev->info = &pa12203001_info; + indio_dev->name = PA12203001_DRIVER_NAME; + indio_dev->channels = pa12203001_channels; + indio_dev->num_channels = ARRAY_SIZE(pa12203001_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = pa12203001_init(indio_dev); + if (ret < 0) + return ret; + + ret = pa12203001_power_chip(indio_dev, PA12203001_CHIP_ENABLE); + if (ret < 0) + return ret; + + ret = pm_runtime_set_active(&client->dev); + if (ret < 0) + goto out_err; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, + PA12203001_SLEEP_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto out_err; + + return 0; + +out_err: + pa12203001_power_chip(indio_dev, PA12203001_CHIP_DISABLE); + return ret; +} + +static void pa12203001_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + int ret; + + iio_device_unregister(indio_dev); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + ret = pa12203001_power_chip(indio_dev, PA12203001_CHIP_DISABLE); + if (ret) + dev_warn(&client->dev, "Failed to power down (%pe)\n", + ERR_PTR(ret)); +} + +#if defined(CONFIG_PM_SLEEP) || defined(CONFIG_PM) +static int pa12203001_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + + return pa12203001_power_chip(indio_dev, PA12203001_CHIP_DISABLE); +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int pa12203001_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + + return pa12203001_power_chip(indio_dev, PA12203001_CHIP_ENABLE); +} +#endif + +#ifdef CONFIG_PM +static int pa12203001_runtime_resume(struct device *dev) +{ + struct pa12203001_data *data; + + data = iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + mutex_lock(&data->lock); + if (data->als_needs_enable) { + pa12203001_als_enable(data, PA12203001_ALS_EN_MASK); + data->als_needs_enable = false; + } + if (data->px_needs_enable) { + pa12203001_px_enable(data, PA12203001_PX_EN_MASK); + data->px_needs_enable = false; + } + mutex_unlock(&data->lock); + + return 0; +} +#endif + +static const struct dev_pm_ops pa12203001_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pa12203001_suspend, pa12203001_resume) + SET_RUNTIME_PM_OPS(pa12203001_suspend, pa12203001_runtime_resume, NULL) +}; + +static const struct acpi_device_id pa12203001_acpi_match[] = { + { "TXCPA122", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(acpi, pa12203001_acpi_match); + +static const struct i2c_device_id pa12203001_id[] = { + { .name = "txcpa122" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, pa12203001_id); + +static struct i2c_driver pa12203001_driver = { + .driver = { + .name = PA12203001_DRIVER_NAME, + .pm = &pa12203001_pm_ops, + .acpi_match_table = pa12203001_acpi_match, + }, + .probe = pa12203001_probe, + .remove = pa12203001_remove, + .id_table = pa12203001_id, + +}; +module_i2c_driver(pa12203001_driver); + +MODULE_AUTHOR("Adriana Reus "); +MODULE_DESCRIPTION("Driver for TXC PA12203001 Proximity and Light Sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/rohm-bu27034.c b/drivers/iio/light/rohm-bu27034.c new file mode 100644 index 000000000..28d111ac8 --- /dev/null +++ b/drivers/iio/light/rohm-bu27034.c @@ -0,0 +1,1329 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * BU27034ANUC ROHM Ambient Light Sensor + * + * Copyright (c) 2023, ROHM Semiconductor. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define BU27034_REG_SYSTEM_CONTROL 0x40 +#define BU27034_MASK_SW_RESET BIT(7) +#define BU27034_MASK_PART_ID GENMASK(5, 0) +#define BU27034_ID 0x19 +#define BU27034_REG_MODE_CONTROL1 0x41 +#define BU27034_MASK_MEAS_MODE GENMASK(2, 0) + +#define BU27034_REG_MODE_CONTROL2 0x42 +#define BU27034_MASK_D01_GAIN GENMASK(7, 3) + +#define BU27034_REG_MODE_CONTROL3 0x43 +#define BU27034_REG_MODE_CONTROL4 0x44 +#define BU27034_MASK_MEAS_EN BIT(0) +#define BU27034_MASK_VALID BIT(7) +#define BU27034_NUM_HW_DATA_CHANS 2 +#define BU27034_REG_DATA0_LO 0x50 +#define BU27034_REG_DATA1_LO 0x52 +#define BU27034_REG_DATA1_HI 0x53 +#define BU27034_REG_MANUFACTURER_ID 0x92 +#define BU27034_REG_MAX BU27034_REG_MANUFACTURER_ID + +/* + * The BU27034 does not have interrupt to trigger the data read when a + * measurement has finished. Hence we poll the VALID bit in a thread. We will + * try to wake the thread BU27034_MEAS_WAIT_PREMATURE_MS milliseconds before + * the expected sampling time to prevent the drifting. + * + * If we constantly wake up a bit too late we would eventually skip a sample. + * And because the sleep can't wake up _exactly_ at given time this would be + * inevitable even if the sensor clock would be perfectly phase-locked to CPU + * clock - which we can't say is the case. + * + * This is still fragile. No matter how big advance do we have, we will still + * risk of losing a sample because things can in a rainy-day scenario be + * delayed a lot. Yet, more we reserve the time for polling, more we also lose + * the performance by spending cycles polling the register. So, selecting this + * value is a balancing dance between severity of wasting CPU time and severity + * of losing samples. + * + * In most cases losing the samples is not _that_ crucial because light levels + * tend to change slowly. + * + * Other option that was pointed to me would be always sleeping 1/2 of the + * measurement time, checking the VALID bit and just sleeping again if the bit + * was not set. That should be pretty tolerant against missing samples due to + * the scheduling delays while also not wasting much of cycles for polling. + * Downside is that the time-stamps would be very inaccurate as the wake-up + * would not really be tied to the sensor toggling the valid bit. This would also + * result 'jumps' in the time-stamps when the delay drifted so that wake-up was + * performed during the consecutive wake-ups (Or, when sensor and CPU clocks + * were very different and scheduling the wake-ups was very close to given + * timeout - and when the time-outs were very close to the actual sensor + * sampling, Eg. once in a blue moon, two consecutive time-outs would occur + * without having a sample ready). + */ +#define BU27034_MEAS_WAIT_PREMATURE_MS 5 +#define BU27034_DATA_WAIT_TIME_US 1000 +#define BU27034_TOTAL_DATA_WAIT_TIME_US (BU27034_MEAS_WAIT_PREMATURE_MS * 1000) + +#define BU27034_RETRY_LIMIT 18 + +enum { + BU27034_CHAN_ALS, + BU27034_CHAN_DATA0, + BU27034_CHAN_DATA1, + BU27034_NUM_CHANS +}; + +static const unsigned long bu27034_scan_masks[] = { + GENMASK(BU27034_CHAN_DATA1, BU27034_CHAN_DATA0), + GENMASK(BU27034_CHAN_DATA1, BU27034_CHAN_ALS), 0 +}; + +/* + * Available scales with gain 1x - 1024x, timings 55, 100, 200, 400 mS + * Time impacts to gain: 1x, 2x, 4x, 8x. + * + * => Max total gain is HWGAIN * gain by integration time (8 * 1024) = 8192 + * if 1x gain is scale 1, scale for 2x gain is 0.5, 4x => 0.25, + * ... 8192x => 0.0001220703125 => 122070.3125 nanos + * + * Using NANO precision for scale, we must use scale 16x corresponding gain 1x + * to avoid precision loss. (8x would result scale 976 562.5(nanos). + */ +#define BU27034_SCALE_1X 16 + +/* See the data sheet for the "Gain Setting" table */ +#define BU27034_GSEL_1X 0x00 /* 00000 */ +#define BU27034_GSEL_4X 0x08 /* 01000 */ +#define BU27034_GSEL_32X 0x0b /* 01011 */ +#define BU27034_GSEL_256X 0x18 /* 11000 */ +#define BU27034_GSEL_512X 0x19 /* 11001 */ +#define BU27034_GSEL_1024X 0x1a /* 11010 */ + +/* Available gain settings */ +static const struct iio_gain_sel_pair bu27034_gains[] = { + GAIN_SCALE_GAIN(1, BU27034_GSEL_1X), + GAIN_SCALE_GAIN(4, BU27034_GSEL_4X), + GAIN_SCALE_GAIN(32, BU27034_GSEL_32X), + GAIN_SCALE_GAIN(256, BU27034_GSEL_256X), + GAIN_SCALE_GAIN(512, BU27034_GSEL_512X), + GAIN_SCALE_GAIN(1024, BU27034_GSEL_1024X), +}; + +/* + * Measurement modes are 55, 100, 200 and 400 mS modes - which do have direct + * multiplying impact to the data register values (similar to gain). + * + * This means that if meas-mode is changed for example from 400 => 200, + * the scale is doubled. Eg, time impact to total gain is x1, x2, x4, x8. + */ +#define BU27034_MEAS_MODE_100MS 0 +#define BU27034_MEAS_MODE_55MS 1 +#define BU27034_MEAS_MODE_200MS 2 +#define BU27034_MEAS_MODE_400MS 4 + +static const struct iio_itime_sel_mul bu27034_itimes[] = { + GAIN_SCALE_ITIME_US(400000, BU27034_MEAS_MODE_400MS, 8), + GAIN_SCALE_ITIME_US(200000, BU27034_MEAS_MODE_200MS, 4), + GAIN_SCALE_ITIME_US(100000, BU27034_MEAS_MODE_100MS, 2), + GAIN_SCALE_ITIME_US(55000, BU27034_MEAS_MODE_55MS, 1), +}; + +#define BU27034_CHAN_DATA(_name) \ +{ \ + .type = IIO_INTENSITY, \ + .channel = BU27034_CHAN_##_name, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_HARDWAREGAIN), \ + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .address = BU27034_REG_##_name##_LO, \ + .scan_index = BU27034_CHAN_##_name, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ + .indexed = 1, \ +} + +static const struct iio_chan_spec bu27034_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .channel = BU27034_CHAN_ALS, + .scan_index = BU27034_CHAN_ALS, + .scan_type = { + .sign = 'u', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + /* + * The BU27034 DATA0 and DATA1 channels are both on the visible light + * area (mostly). The data0 sensitivity peaks at 500nm, DATA1 at 600nm. + * These wave lengths are cyan(ish) and orange(ish), making these + * sub-optiomal candidates for R/G/B standardization. Hence the + * colour modifier is omitted. + */ + BU27034_CHAN_DATA(DATA0), + BU27034_CHAN_DATA(DATA1), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +struct bu27034_data { + struct regmap *regmap; + struct device *dev; + /* + * Protect gain and time during scale adjustment and data reading. + * Protect measurement enabling/disabling. + */ + struct mutex mutex; + struct iio_gts gts; + struct task_struct *task; + __le16 raw[BU27034_NUM_HW_DATA_CHANS]; + struct { + u32 mlux; + __le16 channels[BU27034_NUM_HW_DATA_CHANS]; + aligned_s64 ts; + } scan; +}; + +static const struct regmap_range bu27034_volatile_ranges[] = { + { + .range_min = BU27034_REG_SYSTEM_CONTROL, + .range_max = BU27034_REG_SYSTEM_CONTROL, + }, { + .range_min = BU27034_REG_MODE_CONTROL4, + .range_max = BU27034_REG_MODE_CONTROL4, + }, { + .range_min = BU27034_REG_DATA0_LO, + .range_max = BU27034_REG_DATA1_HI, + }, +}; + +static const struct regmap_access_table bu27034_volatile_regs = { + .yes_ranges = &bu27034_volatile_ranges[0], + .n_yes_ranges = ARRAY_SIZE(bu27034_volatile_ranges), +}; + +static const struct regmap_range bu27034_read_only_ranges[] = { + { + .range_min = BU27034_REG_DATA0_LO, + .range_max = BU27034_REG_DATA1_HI, + }, { + .range_min = BU27034_REG_MANUFACTURER_ID, + .range_max = BU27034_REG_MANUFACTURER_ID, + } +}; + +static const struct regmap_access_table bu27034_ro_regs = { + .no_ranges = &bu27034_read_only_ranges[0], + .n_no_ranges = ARRAY_SIZE(bu27034_read_only_ranges), +}; + +static const struct regmap_config bu27034_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = BU27034_REG_MAX, + .cache_type = REGCACHE_RBTREE, + .volatile_table = &bu27034_volatile_regs, + .wr_table = &bu27034_ro_regs, +}; + +struct bu27034_gain_check { + int old_gain; + int new_gain; + int chan; +}; + +static int bu27034_get_gain_sel(struct bu27034_data *data, int chan) +{ + int reg[] = { + [BU27034_CHAN_DATA0] = BU27034_REG_MODE_CONTROL2, + [BU27034_CHAN_DATA1] = BU27034_REG_MODE_CONTROL3, + }; + int ret, val; + + ret = regmap_read(data->regmap, reg[chan], &val); + if (ret) + return ret; + + return FIELD_GET(BU27034_MASK_D01_GAIN, val); +} + +static int bu27034_get_gain(struct bu27034_data *data, int chan, int *gain) +{ + int ret, sel; + + ret = bu27034_get_gain_sel(data, chan); + if (ret < 0) + return ret; + + sel = ret; + + ret = iio_gts_find_gain_by_sel(&data->gts, sel); + if (ret < 0) { + dev_err(data->dev, "chan %u: unknown gain value 0x%x\n", chan, + sel); + + return ret; + } + + *gain = ret; + + return 0; +} + +static int bu27034_get_int_time(struct bu27034_data *data) +{ + int ret, sel; + + ret = regmap_read(data->regmap, BU27034_REG_MODE_CONTROL1, &sel); + if (ret) + return ret; + + return iio_gts_find_int_time_by_sel(&data->gts, + sel & BU27034_MASK_MEAS_MODE); +} + +static int _bu27034_get_scale(struct bu27034_data *data, int channel, int *val, + int *val2) +{ + int gain, ret; + + ret = bu27034_get_gain(data, channel, &gain); + if (ret) + return ret; + + ret = bu27034_get_int_time(data); + if (ret < 0) + return ret; + + return iio_gts_get_scale(&data->gts, gain, ret, val, val2); +} + +static int bu27034_get_scale(struct bu27034_data *data, int channel, int *val, + int *val2) +{ + int ret; + + if (channel == BU27034_CHAN_ALS) { + *val = 0; + *val2 = 1000; + return IIO_VAL_INT_PLUS_MICRO; + } + + mutex_lock(&data->mutex); + ret = _bu27034_get_scale(data, channel, val, val2); + mutex_unlock(&data->mutex); + if (ret) + return ret; + + return IIO_VAL_INT_PLUS_NANO; +} + +/* Caller should hold the lock to protect lux reading */ +static int bu27034_write_gain_sel(struct bu27034_data *data, int chan, int sel) +{ + static const int reg[] = { + [BU27034_CHAN_DATA0] = BU27034_REG_MODE_CONTROL2, + [BU27034_CHAN_DATA1] = BU27034_REG_MODE_CONTROL3, + }; + int mask, val; + + val = FIELD_PREP(BU27034_MASK_D01_GAIN, sel); + mask = BU27034_MASK_D01_GAIN; + + return regmap_update_bits(data->regmap, reg[chan], mask, val); +} + +static int bu27034_set_gain(struct bu27034_data *data, int chan, int gain) +{ + int ret; + + ret = iio_gts_find_sel_by_gain(&data->gts, gain); + if (ret < 0) + return ret; + + return bu27034_write_gain_sel(data, chan, ret); +} + +/* Caller should hold the lock to protect data->int_time */ +static int bu27034_set_int_time(struct bu27034_data *data, int time) +{ + int ret; + + ret = iio_gts_find_sel_by_int_time(&data->gts, time); + if (ret < 0) + return ret; + + return regmap_update_bits(data->regmap, BU27034_REG_MODE_CONTROL1, + BU27034_MASK_MEAS_MODE, ret); +} + +/* + * We try to change the time in such way that the scale is maintained for + * given channels by adjusting gain so that it compensates the time change. + */ +static int bu27034_try_set_int_time(struct bu27034_data *data, int time_us) +{ + struct bu27034_gain_check gains[] = { + { .chan = BU27034_CHAN_DATA0 }, + { .chan = BU27034_CHAN_DATA1 }, + }; + int numg = ARRAY_SIZE(gains); + int ret, int_time_old, i; + + guard(mutex)(&data->mutex); + ret = bu27034_get_int_time(data); + if (ret < 0) + return ret; + + int_time_old = ret; + + if (!iio_gts_valid_time(&data->gts, time_us)) { + dev_err(data->dev, "Unsupported integration time %u\n", + time_us); + return -EINVAL; + } + + if (time_us == int_time_old) + return 0; + + for (i = 0; i < numg; i++) { + ret = bu27034_get_gain(data, gains[i].chan, &gains[i].old_gain); + if (ret) + return 0; + + ret = iio_gts_find_new_gain_by_old_gain_time(&data->gts, + gains[i].old_gain, + int_time_old, time_us, + &gains[i].new_gain); + if (ret) { + int scale1, scale2; + bool ok; + + _bu27034_get_scale(data, gains[i].chan, &scale1, &scale2); + dev_dbg(data->dev, + "chan %u, can't support time %u with scale %u %u\n", + gains[i].chan, time_us, scale1, scale2); + + if (gains[i].new_gain < 0) + return ret; + + /* + * If caller requests for integration time change and we + * can't support the scale - then the caller should be + * prepared to 'pick up the pieces and deal with the + * fact that the scale changed'. + */ + ret = iio_find_closest_gain_low(&data->gts, + gains[i].new_gain, &ok); + + if (!ok) + dev_dbg(data->dev, + "optimal gain out of range for chan %u\n", + gains[i].chan); + + if (ret < 0) { + dev_dbg(data->dev, + "Total gain increase. Risk of saturation"); + ret = iio_gts_get_min_gain(&data->gts); + if (ret < 0) + return ret; + } + dev_dbg(data->dev, "chan %u scale changed\n", + gains[i].chan); + gains[i].new_gain = ret; + dev_dbg(data->dev, "chan %u new gain %u\n", + gains[i].chan, gains[i].new_gain); + } + } + + for (i = 0; i < numg; i++) { + ret = bu27034_set_gain(data, gains[i].chan, gains[i].new_gain); + if (ret) + return ret; + } + + return bu27034_set_int_time(data, time_us); +} + +static int bu27034_set_scale(struct bu27034_data *data, int chan, + int val, int val2) +{ + int ret, time_sel, gain_sel, i; + bool found = false; + + if (chan == BU27034_CHAN_ALS) { + if (val == 0 && val2 == 1000000) + return 0; + + return -EINVAL; + } + + guard(mutex)(&data->mutex); + ret = regmap_read(data->regmap, BU27034_REG_MODE_CONTROL1, &time_sel); + if (ret) + return ret; + + ret = iio_gts_find_gain_sel_for_scale_using_time(&data->gts, time_sel, + val, val2, &gain_sel); + if (ret) { + /* + * Could not support scale with given time. Need to change time. + * We still want to maintain the scale for all channels + */ + struct bu27034_gain_check gain; + int new_time_sel; + + /* + * Populate information for the other channel which should also + * maintain the scale. + */ + if (chan == BU27034_CHAN_DATA0) + gain.chan = BU27034_CHAN_DATA1; + else if (chan == BU27034_CHAN_DATA1) + gain.chan = BU27034_CHAN_DATA0; + + ret = bu27034_get_gain(data, gain.chan, &gain.old_gain); + if (ret) + return ret; + + /* + * Iterate through all the times to see if we find one which + * can support requested scale for requested channel, while + * maintaining the scale for the other channel + */ + for (i = 0; i < data->gts.num_itime; i++) { + new_time_sel = data->gts.itime_table[i].sel; + + if (new_time_sel == time_sel) + continue; + + /* Can we provide requested scale with this time? */ + ret = iio_gts_find_gain_sel_for_scale_using_time( + &data->gts, new_time_sel, val, val2, + &gain_sel); + if (ret) + continue; + + /* Can the other channel maintain scale? */ + ret = iio_gts_find_new_gain_sel_by_old_gain_time( + &data->gts, gain.old_gain, time_sel, + new_time_sel, &gain.new_gain); + if (!ret) { + /* Yes - we found suitable time */ + found = true; + break; + } + } + if (!found) { + dev_dbg(data->dev, + "Can't set scale maintaining other channel\n"); + return -EINVAL; + } + + ret = bu27034_set_gain(data, gain.chan, gain.new_gain); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, BU27034_REG_MODE_CONTROL1, + BU27034_MASK_MEAS_MODE, new_time_sel); + if (ret) + return ret; + } + + return bu27034_write_gain_sel(data, chan, gain_sel); +} + +/* + * for (D1/D0 < 1.5): + * lx = (0.001193 * D0 + (-0.0000747) * D1) * ((D1/D0 – 1.5) * (0.25) + 1) + * + * => -0.000745625 * D0 + 0.0002515625 * D1 + -0.000018675 * D1 * D1 / D0 + * + * => (6.44 * ch1 / gain1 + 19.088 * ch0 / gain0 - + * 0.47808 * ch1 * ch1 * gain0 / gain1 / gain1 / ch0) / + * mt + * + * Else + * lx = 0.001193 * D0 - 0.0000747 * D1 + * + * => (1.91232 * ch1 / gain1 + 30.5408 * ch0 / gain0 + + * [0 * ch1 * ch1 * gain0 / gain1 / gain1 / ch0] ) / + * mt + * + * This can be unified to format: + * lx = [ + * A * ch1 * ch1 * gain0 / (ch0 * gain1 * gain1) + + * B * ch1 / gain1 + + * C * ch0 / gain0 + * ] / mt + * + * For case 1: + * A = -0.47808, + * B = 6.44, + * C = 19.088 + * + * For case 2: + * A = 0 + * B = 1.91232 + * C = 30.5408 + */ + +struct bu27034_lx_coeff { + unsigned int A; + unsigned int B; + unsigned int C; + /* Indicate which of the coefficients above are negative */ + bool is_neg[3]; +}; + +static inline u64 gain_mul_div_helper(u64 val, unsigned int gain, + unsigned int div) +{ + /* + * Max gain for a channel is 4096. The max u64 (0xffffffffffffffffULL) + * divided by 4096 is 0xFFFFFFFFFFFFF (GENMASK_ULL(51, 0)) (floored). + * Thus, the 0xFFFFFFFFFFFFF is the largest value we can safely multiply + * with the gain, no matter what gain is set. + * + * So, multiplication with max gain may overflow if val is greater than + * 0xFFFFFFFFFFFFF (52 bits set).. + * + * If this is the case we divide first. + */ + if (val < GENMASK_ULL(51, 0)) { + val *= gain; + do_div(val, div); + } else { + do_div(val, div); + val *= gain; + } + + return val; +} + +static u64 bu27034_fixp_calc_t1_64bit(unsigned int coeff, unsigned int ch0, + unsigned int ch1, unsigned int gain0, + unsigned int gain1) +{ + unsigned int helper; + u64 helper64; + + helper64 = (u64)coeff * (u64)ch1 * (u64)ch1; + + helper = gain1 * gain1; + if (helper > ch0) { + do_div(helper64, helper); + + return gain_mul_div_helper(helper64, gain0, ch0); + } + + do_div(helper64, ch0); + + return gain_mul_div_helper(helper64, gain0, helper); + +} + +static u64 bu27034_fixp_calc_t1(unsigned int coeff, unsigned int ch0, + unsigned int ch1, unsigned int gain0, + unsigned int gain1) +{ + unsigned int helper, tmp; + + /* + * Here we could overflow even the 64bit value. Hence we + * multiply with gain0 only after the divisions - even though + * it may result loss of accuracy + */ + helper = coeff * ch1 * ch1; + tmp = helper * gain0; + + helper = ch1 * ch1; + + if (check_mul_overflow(helper, coeff, &helper)) + return bu27034_fixp_calc_t1_64bit(coeff, ch0, ch1, gain0, gain1); + + if (check_mul_overflow(helper, gain0, &tmp)) + return bu27034_fixp_calc_t1_64bit(coeff, ch0, ch1, gain0, gain1); + + return tmp / (gain1 * gain1) / ch0; + +} + +static u64 bu27034_fixp_calc_t23(unsigned int coeff, unsigned int ch, + unsigned int gain) +{ + unsigned int helper; + u64 helper64; + + if (!check_mul_overflow(coeff, ch, &helper)) + return helper / gain; + + helper64 = (u64)coeff * (u64)ch; + do_div(helper64, gain); + + return helper64; +} + +static int bu27034_fixp_calc_lx(unsigned int ch0, unsigned int ch1, + unsigned int gain0, unsigned int gain1, + unsigned int meastime, int coeff_idx) +{ + static const struct bu27034_lx_coeff coeff[] = { + { + .A = 4780800, /* -0.47808 */ + .B = 64400000, /* 6.44 */ + .C = 190880000, /* 19.088 */ + .is_neg = { true, false, false }, + }, { + .A = 0, /* 0 */ + .B = 19123200, /* 1.91232 */ + .C = 305408000, /* 30.5408 */ + /* All terms positive */ + }, + }; + const struct bu27034_lx_coeff *c = &coeff[coeff_idx]; + u64 res = 0, terms[3]; + int i; + + if (coeff_idx >= ARRAY_SIZE(coeff)) + return -EINVAL; + + terms[0] = bu27034_fixp_calc_t1(c->A, ch0, ch1, gain0, gain1); + terms[1] = bu27034_fixp_calc_t23(c->B, ch1, gain1); + terms[2] = bu27034_fixp_calc_t23(c->C, ch0, gain0); + + /* First, add positive terms */ + for (i = 0; i < 3; i++) + if (!c->is_neg[i]) + res += terms[i]; + + /* No positive term => zero lux */ + if (!res) + return 0; + + /* Then, subtract negative terms (if any) */ + for (i = 0; i < 3; i++) + if (c->is_neg[i]) { + /* + * If the negative term is greater than positive - then + * the darkness has taken over and we are all doomed! Eh, + * I mean, then we can just return 0 lx and go out + */ + if (terms[i] >= res) + return 0; + + res -= terms[i]; + } + + meastime *= 10; + do_div(res, meastime); + + return (int) res; +} + +static bool bu27034_has_valid_sample(struct bu27034_data *data) +{ + int ret, val; + + ret = regmap_read(data->regmap, BU27034_REG_MODE_CONTROL4, &val); + if (ret) { + dev_err(data->dev, "Read failed %d\n", ret); + + return false; + } + + return val & BU27034_MASK_VALID; +} + +/* + * Reading the register where VALID bit is clears this bit. (So does changing + * any gain / integration time configuration registers) The bit gets + * set when we have acquired new data. We use this bit to indicate data + * validity. + */ +static void bu27034_invalidate_read_data(struct bu27034_data *data) +{ + bu27034_has_valid_sample(data); +} + +static int bu27034_read_result(struct bu27034_data *data, int chan, int *res) +{ + int reg[] = { + [BU27034_CHAN_DATA0] = BU27034_REG_DATA0_LO, + [BU27034_CHAN_DATA1] = BU27034_REG_DATA1_LO, + }; + int valid, ret; + __le16 val; + + ret = regmap_read_poll_timeout(data->regmap, BU27034_REG_MODE_CONTROL4, + valid, (valid & BU27034_MASK_VALID), + BU27034_DATA_WAIT_TIME_US, 0); + if (ret) + return ret; + + ret = regmap_bulk_read(data->regmap, reg[chan], &val, sizeof(val)); + if (ret) + return ret; + + *res = le16_to_cpu(val); + + return 0; +} + +static int bu27034_get_result_unlocked(struct bu27034_data *data, __le16 *res, + int size) +{ + int ret = 0, retry_cnt = 0; + +retry: + /* Get new value from sensor if data is ready */ + if (bu27034_has_valid_sample(data)) { + ret = regmap_bulk_read(data->regmap, BU27034_REG_DATA0_LO, + res, size); + if (ret) + return ret; + + bu27034_invalidate_read_data(data); + } else { + /* No new data in sensor. Wait and retry */ + retry_cnt++; + + if (retry_cnt > BU27034_RETRY_LIMIT) { + dev_err(data->dev, "No data from sensor\n"); + + return -ETIMEDOUT; + } + + msleep(25); + + goto retry; + } + + return ret; +} + +static int bu27034_meas_set(struct bu27034_data *data, bool en) +{ + if (en) + return regmap_set_bits(data->regmap, BU27034_REG_MODE_CONTROL4, + BU27034_MASK_MEAS_EN); + + return regmap_clear_bits(data->regmap, BU27034_REG_MODE_CONTROL4, + BU27034_MASK_MEAS_EN); +} + +static int bu27034_get_single_result(struct bu27034_data *data, int chan, + int *val) +{ + int ret; + + if (chan < BU27034_CHAN_DATA0 || chan > BU27034_CHAN_DATA1) + return -EINVAL; + + ret = bu27034_meas_set(data, true); + if (ret) + return ret; + + ret = bu27034_get_int_time(data); + if (ret < 0) + return ret; + + msleep(ret / 1000); + + return bu27034_read_result(data, chan, val); +} + +/* + * The formula given by vendor for computing luxes out of data0 and data1 + * (in open air) is as follows: + * + * Let's mark: + * D0 = data0/ch0_gain/meas_time_ms * 25600 + * D1 = data1/ch1_gain/meas_time_ms * 25600 + * + * Then: + * If (D1/D0 < 1.5) + * lx = (0.001193 * D0 + (-0.0000747) * D1) * ((D1 / D0 – 1.5) * 0.25 + 1) + * Else + * lx = (0.001193 * D0 + (-0.0000747) * D1) + * + * We use it here. Users who have for example some colored lens + * need to modify the calculation but I hope this gives a starting point for + * those working with such devices. + */ + +static int bu27034_calc_mlux(struct bu27034_data *data, __le16 *res, int *val) +{ + unsigned int gain0, gain1, meastime; + unsigned int d1_d0_ratio_scaled; + u16 ch0, ch1; + u64 helper64; + int ret; + + /* + * We return 0 lux if calculation fails. This should be reasonably + * easy to spot from the buffers especially if raw-data channels show + * valid values + */ + *val = 0; + + ch0 = max_t(u16, 1, le16_to_cpu(res[0])); + ch1 = max_t(u16, 1, le16_to_cpu(res[1])); + + ret = bu27034_get_gain(data, BU27034_CHAN_DATA0, &gain0); + if (ret) + return ret; + + ret = bu27034_get_gain(data, BU27034_CHAN_DATA1, &gain1); + if (ret) + return ret; + + ret = bu27034_get_int_time(data); + if (ret < 0) + return ret; + + meastime = ret; + + d1_d0_ratio_scaled = (unsigned int)ch1 * (unsigned int)gain0 * 100; + helper64 = (u64)ch1 * (u64)gain0 * 100LLU; + + if (helper64 != d1_d0_ratio_scaled) { + unsigned int div = (unsigned int)ch0 * gain1; + + do_div(helper64, div); + d1_d0_ratio_scaled = helper64; + } else { + d1_d0_ratio_scaled /= ch0 * gain1; + } + + if (d1_d0_ratio_scaled < 150) + ret = bu27034_fixp_calc_lx(ch0, ch1, gain0, gain1, meastime, 0); + else + ret = bu27034_fixp_calc_lx(ch0, ch1, gain0, gain1, meastime, 1); + + if (ret < 0) + return ret; + + *val = ret; + + return 0; + +} + +static int bu27034_get_mlux(struct bu27034_data *data, int chan, int *val) +{ + __le16 res[BU27034_NUM_HW_DATA_CHANS]; + int ret; + + ret = bu27034_meas_set(data, true); + if (ret) + return ret; + + ret = bu27034_get_result_unlocked(data, &res[0], sizeof(res)); + if (ret) + return ret; + + ret = bu27034_calc_mlux(data, res, val); + if (ret) + return ret; + + ret = bu27034_meas_set(data, false); + if (ret) + dev_err(data->dev, "failed to disable measurement\n"); + + return 0; +} + +static int bu27034_read_raw(struct iio_dev *idev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bu27034_data *data = iio_priv(idev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = bu27034_get_int_time(data); + if (*val2 < 0) + return *val2; + + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_CHAN_INFO_HARDWAREGAIN: + ret = bu27034_get_gain(data, chan->channel, val); + if (ret) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + return bu27034_get_scale(data, chan->channel, val, val2); + + case IIO_CHAN_INFO_RAW: + { + int (*result_get)(struct bu27034_data *data, int chan, int *val); + + if (chan->type == IIO_INTENSITY) + result_get = bu27034_get_single_result; + else if (chan->type == IIO_LIGHT) + result_get = bu27034_get_mlux; + else + return -EINVAL; + + /* Don't mess with measurement enabling while buffering */ + if (!iio_device_claim_direct(idev)) + return -EBUSY; + + mutex_lock(&data->mutex); + /* + * Reading one channel at a time is inefficient but we + * don't care here. Buffered version should be used if + * performance is an issue. + */ + ret = result_get(data, chan->channel, val); + + mutex_unlock(&data->mutex); + iio_device_release_direct(idev); + + if (ret) + return ret; + + return IIO_VAL_INT; + } + default: + return -EINVAL; + } +} + +static int bu27034_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + struct bu27034_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_INT_TIME: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_HARDWAREGAIN: + dev_dbg(data->dev, + "HARDWAREGAIN is read-only, use scale to set\n"); + return -EINVAL; + default: + return -EINVAL; + } +} + +static int bu27034_write_raw(struct iio_dev *idev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bu27034_data *data = iio_priv(idev); + int ret; + + if (!iio_device_claim_direct(idev)) + return -EBUSY; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + ret = bu27034_set_scale(data, chan->channel, val, val2); + break; + case IIO_CHAN_INFO_INT_TIME: + if (!val) + ret = bu27034_try_set_int_time(data, val2); + else + ret = -EINVAL; + break; + default: + ret = -EINVAL; + break; + } + + iio_device_release_direct(idev); + + return ret; +} + +static int bu27034_read_avail(struct iio_dev *idev, + struct iio_chan_spec const *chan, const int **vals, + int *type, int *length, long mask) +{ + struct bu27034_data *data = iio_priv(idev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return iio_gts_avail_times(&data->gts, vals, type, length); + case IIO_CHAN_INFO_SCALE: + return iio_gts_all_avail_scales(&data->gts, vals, type, length); + default: + return -EINVAL; + } +} + +static const struct iio_info bu27034_info = { + .read_raw = &bu27034_read_raw, + .write_raw = &bu27034_write_raw, + .write_raw_get_fmt = &bu27034_write_raw_get_fmt, + .read_avail = &bu27034_read_avail, +}; + +static int bu27034_chip_init(struct bu27034_data *data) +{ + int ret, sel; + + /* Reset */ + ret = regmap_write_bits(data->regmap, BU27034_REG_SYSTEM_CONTROL, + BU27034_MASK_SW_RESET, BU27034_MASK_SW_RESET); + if (ret) + return dev_err_probe(data->dev, ret, "Sensor reset failed\n"); + + msleep(1); + + ret = regmap_reinit_cache(data->regmap, &bu27034_regmap); + if (ret) { + dev_err(data->dev, "Failed to reinit reg cache\n"); + return ret; + } + + /* + * Read integration time here to ensure it is in regmap cache. We do + * this to speed-up the int-time acquisition in the start of the buffer + * handling thread where longer delays could make it more likely we end + * up skipping a sample, and where the longer delays make timestamps + * less accurate. + */ + ret = regmap_read(data->regmap, BU27034_REG_MODE_CONTROL1, &sel); + if (ret) + dev_err(data->dev, "reading integration time failed\n"); + + return 0; +} + +static int bu27034_wait_for_data(struct bu27034_data *data) +{ + int ret, val; + + ret = regmap_read_poll_timeout(data->regmap, BU27034_REG_MODE_CONTROL4, + val, val & BU27034_MASK_VALID, + BU27034_DATA_WAIT_TIME_US, + BU27034_TOTAL_DATA_WAIT_TIME_US); + if (ret) { + dev_err(data->dev, "data polling %s\n", + !(val & BU27034_MASK_VALID) ? "timeout" : "fail"); + + return ret; + } + + ret = regmap_bulk_read(data->regmap, BU27034_REG_DATA0_LO, + &data->scan.channels[0], + sizeof(data->scan.channels)); + if (ret) + return ret; + + bu27034_invalidate_read_data(data); + + return 0; +} + +static int bu27034_buffer_thread(void *arg) +{ + struct iio_dev *idev = arg; + struct bu27034_data *data; + int wait_ms; + + data = iio_priv(idev); + + wait_ms = bu27034_get_int_time(data); + wait_ms /= 1000; + + wait_ms -= BU27034_MEAS_WAIT_PREMATURE_MS; + + while (!kthread_should_stop()) { + int ret; + int64_t tstamp; + + msleep(wait_ms); + ret = bu27034_wait_for_data(data); + if (ret) + continue; + + tstamp = iio_get_time_ns(idev); + + if (test_bit(BU27034_CHAN_ALS, idev->active_scan_mask)) { + int mlux; + + ret = bu27034_calc_mlux(data, &data->scan.channels[0], + &mlux); + if (ret) + dev_err(data->dev, "failed to calculate lux\n"); + + /* + * The maximum Milli lux value we get with gain 1x time + * 55mS data ch0 = 0xffff ch1 = 0xffff fits in 26 bits + * so there should be no problem returning int from + * computations and casting it to u32 + */ + data->scan.mlux = (u32)mlux; + } + iio_push_to_buffers_with_ts(idev, &data->scan, + sizeof(data->scan), tstamp); + } + + return 0; +} + +static int bu27034_buffer_enable(struct iio_dev *idev) +{ + struct bu27034_data *data = iio_priv(idev); + struct task_struct *task; + int ret; + + guard(mutex)(&data->mutex); + ret = bu27034_meas_set(data, true); + if (ret) + return ret; + + task = kthread_run(bu27034_buffer_thread, idev, + "bu27034-buffering-%u", + iio_device_id(idev)); + if (IS_ERR(task)) + return PTR_ERR(task); + + data->task = task; + + return 0; +} + +static int bu27034_buffer_disable(struct iio_dev *idev) +{ + struct bu27034_data *data = iio_priv(idev); + + guard(mutex)(&data->mutex); + if (data->task) { + kthread_stop(data->task); + data->task = NULL; + } + + return bu27034_meas_set(data, false); +} + +static const struct iio_buffer_setup_ops bu27034_buffer_ops = { + .postenable = &bu27034_buffer_enable, + .predisable = &bu27034_buffer_disable, +}; + +static int bu27034_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct bu27034_data *data; + struct regmap *regmap; + struct iio_dev *idev; + unsigned int part_id, reg; + int ret; + + regmap = devm_regmap_init_i2c(i2c, &bu27034_regmap); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Failed to initialize Regmap\n"); + + idev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!idev) + return -ENOMEM; + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulator\n"); + + data = iio_priv(idev); + + ret = regmap_read(regmap, BU27034_REG_SYSTEM_CONTROL, ®); + if (ret) + return dev_err_probe(dev, ret, "Failed to access sensor\n"); + + part_id = FIELD_GET(BU27034_MASK_PART_ID, reg); + + if (part_id != BU27034_ID) + dev_warn(dev, "unknown device 0x%x\n", part_id); + + ret = devm_iio_init_iio_gts(dev, BU27034_SCALE_1X, 0, bu27034_gains, + ARRAY_SIZE(bu27034_gains), bu27034_itimes, + ARRAY_SIZE(bu27034_itimes), &data->gts); + if (ret) + return ret; + + mutex_init(&data->mutex); + data->regmap = regmap; + data->dev = dev; + + idev->channels = bu27034_channels; + idev->num_channels = ARRAY_SIZE(bu27034_channels); + idev->name = "bu27034"; + idev->info = &bu27034_info; + + idev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + idev->available_scan_masks = bu27034_scan_masks; + + ret = bu27034_chip_init(data); + if (ret) + return ret; + + ret = devm_iio_kfifo_buffer_setup(dev, idev, &bu27034_buffer_ops); + if (ret) + return dev_err_probe(dev, ret, "buffer setup failed\n"); + + ret = devm_iio_device_register(dev, idev); + if (ret < 0) + return dev_err_probe(dev, ret, + "Unable to register iio device\n"); + + return ret; +} + +static const struct of_device_id bu27034_of_match[] = { + { .compatible = "rohm,bu27034anuc" }, + { } +}; +MODULE_DEVICE_TABLE(of, bu27034_of_match); + +static struct i2c_driver bu27034_i2c_driver = { + .driver = { + .name = "bu27034-als", + .of_match_table = bu27034_of_match, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .probe = bu27034_probe, +}; +module_i2c_driver(bu27034_i2c_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Matti Vaittinen "); +MODULE_DESCRIPTION("ROHM BU27034 ambient light sensor driver"); +MODULE_IMPORT_NS("IIO_GTS_HELPER"); diff --git a/drivers/iio/light/rpr0521.c b/drivers/iio/light/rpr0521.c new file mode 100644 index 000000000..7d82efc31 --- /dev/null +++ b/drivers/iio/light/rpr0521.c @@ -0,0 +1,1125 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * RPR-0521 ROHM Ambient Light and Proximity Sensor + * + * Copyright (c) 2015, Intel Corporation. + * + * IIO driver for RPR-0521RS (7-bit I2C slave address 0x38). + * + * TODO: illuminance channel + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define RPR0521_REG_SYSTEM_CTRL 0x40 +#define RPR0521_REG_MODE_CTRL 0x41 +#define RPR0521_REG_ALS_CTRL 0x42 +#define RPR0521_REG_PXS_CTRL 0x43 +#define RPR0521_REG_PXS_DATA 0x44 /* 16-bit, little endian */ +#define RPR0521_REG_ALS_DATA0 0x46 /* 16-bit, little endian */ +#define RPR0521_REG_ALS_DATA1 0x48 /* 16-bit, little endian */ +#define RPR0521_REG_INTERRUPT 0x4A +#define RPR0521_REG_PS_OFFSET_LSB 0x53 +#define RPR0521_REG_ID 0x92 + +#define RPR0521_MODE_ALS_MASK BIT(7) +#define RPR0521_MODE_PXS_MASK BIT(6) +#define RPR0521_MODE_MEAS_TIME_MASK GENMASK(3, 0) +#define RPR0521_ALS_DATA0_GAIN_MASK GENMASK(5, 4) +#define RPR0521_ALS_DATA0_GAIN_SHIFT 4 +#define RPR0521_ALS_DATA1_GAIN_MASK GENMASK(3, 2) +#define RPR0521_ALS_DATA1_GAIN_SHIFT 2 +#define RPR0521_PXS_GAIN_MASK GENMASK(5, 4) +#define RPR0521_PXS_GAIN_SHIFT 4 +#define RPR0521_PXS_PERSISTENCE_MASK GENMASK(3, 0) +#define RPR0521_INTERRUPT_INT_TRIG_PS_MASK BIT(0) +#define RPR0521_INTERRUPT_INT_TRIG_ALS_MASK BIT(1) +#define RPR0521_INTERRUPT_INT_REASSERT_MASK BIT(3) +#define RPR0521_INTERRUPT_ALS_INT_STATUS_MASK BIT(6) +#define RPR0521_INTERRUPT_PS_INT_STATUS_MASK BIT(7) + +#define RPR0521_MODE_ALS_ENABLE BIT(7) +#define RPR0521_MODE_ALS_DISABLE 0x00 +#define RPR0521_MODE_PXS_ENABLE BIT(6) +#define RPR0521_MODE_PXS_DISABLE 0x00 +#define RPR0521_PXS_PERSISTENCE_DRDY 0x00 + +#define RPR0521_INTERRUPT_INT_TRIG_PS_ENABLE BIT(0) +#define RPR0521_INTERRUPT_INT_TRIG_PS_DISABLE 0x00 +#define RPR0521_INTERRUPT_INT_TRIG_ALS_ENABLE BIT(1) +#define RPR0521_INTERRUPT_INT_TRIG_ALS_DISABLE 0x00 +#define RPR0521_INTERRUPT_INT_REASSERT_ENABLE BIT(3) +#define RPR0521_INTERRUPT_INT_REASSERT_DISABLE 0x00 + +#define RPR0521_MANUFACT_ID 0xE0 +#define RPR0521_DEFAULT_MEAS_TIME 0x06 /* ALS - 100ms, PXS - 100ms */ + +#define RPR0521_DRV_NAME "RPR0521" + +#define RPR0521_SLEEP_DELAY_MS 2000 + +#define RPR0521_ALS_SCALE_AVAIL "0.007812 0.015625 0.5 1" +#define RPR0521_PXS_SCALE_AVAIL "0.125 0.5 1" + +struct rpr0521_gain { + int scale; + int uscale; +}; + +static const struct rpr0521_gain rpr0521_als_gain[4] = { + {1, 0}, /* x1 */ + {0, 500000}, /* x2 */ + {0, 15625}, /* x64 */ + {0, 7812}, /* x128 */ +}; + +static const struct rpr0521_gain rpr0521_pxs_gain[3] = { + {1, 0}, /* x1 */ + {0, 500000}, /* x2 */ + {0, 125000}, /* x4 */ +}; + +enum rpr0521_channel { + RPR0521_CHAN_PXS, + RPR0521_CHAN_ALS_DATA0, + RPR0521_CHAN_ALS_DATA1, +}; + +struct rpr0521_reg_desc { + u8 address; + u8 device_mask; +}; + +static const struct rpr0521_reg_desc rpr0521_data_reg[] = { + [RPR0521_CHAN_PXS] = { + .address = RPR0521_REG_PXS_DATA, + .device_mask = RPR0521_MODE_PXS_MASK, + }, + [RPR0521_CHAN_ALS_DATA0] = { + .address = RPR0521_REG_ALS_DATA0, + .device_mask = RPR0521_MODE_ALS_MASK, + }, + [RPR0521_CHAN_ALS_DATA1] = { + .address = RPR0521_REG_ALS_DATA1, + .device_mask = RPR0521_MODE_ALS_MASK, + }, +}; + +static const struct rpr0521_gain_info { + u8 reg; + u8 mask; + u8 shift; + const struct rpr0521_gain *gain; + int size; +} rpr0521_gain[] = { + [RPR0521_CHAN_PXS] = { + .reg = RPR0521_REG_PXS_CTRL, + .mask = RPR0521_PXS_GAIN_MASK, + .shift = RPR0521_PXS_GAIN_SHIFT, + .gain = rpr0521_pxs_gain, + .size = ARRAY_SIZE(rpr0521_pxs_gain), + }, + [RPR0521_CHAN_ALS_DATA0] = { + .reg = RPR0521_REG_ALS_CTRL, + .mask = RPR0521_ALS_DATA0_GAIN_MASK, + .shift = RPR0521_ALS_DATA0_GAIN_SHIFT, + .gain = rpr0521_als_gain, + .size = ARRAY_SIZE(rpr0521_als_gain), + }, + [RPR0521_CHAN_ALS_DATA1] = { + .reg = RPR0521_REG_ALS_CTRL, + .mask = RPR0521_ALS_DATA1_GAIN_MASK, + .shift = RPR0521_ALS_DATA1_GAIN_SHIFT, + .gain = rpr0521_als_gain, + .size = ARRAY_SIZE(rpr0521_als_gain), + }, +}; + +struct rpr0521_samp_freq { + int als_hz; + int als_uhz; + int pxs_hz; + int pxs_uhz; +}; + +static const struct rpr0521_samp_freq rpr0521_samp_freq_i[13] = { +/* {ALS, PXS}, W==currently writable option */ + {0, 0, 0, 0}, /* W0000, 0=standby */ + {0, 0, 100, 0}, /* 0001 */ + {0, 0, 25, 0}, /* 0010 */ + {0, 0, 10, 0}, /* 0011 */ + {0, 0, 2, 500000}, /* 0100 */ + {10, 0, 20, 0}, /* 0101 */ + {10, 0, 10, 0}, /* W0110 */ + {10, 0, 2, 500000}, /* 0111 */ + {2, 500000, 20, 0}, /* 1000, measurement 100ms, sleep 300ms */ + {2, 500000, 10, 0}, /* 1001, measurement 100ms, sleep 300ms */ + {2, 500000, 0, 0}, /* 1010, high sensitivity mode */ + {2, 500000, 2, 500000}, /* W1011, high sensitivity mode */ + {20, 0, 20, 0} /* 1100, ALS_data x 0.5, see specification P.18 */ +}; + +struct rpr0521_data { + struct i2c_client *client; + + /* protect device params updates (e.g state, gain) */ + struct mutex lock; + + /* device active status */ + bool als_dev_en; + bool pxs_dev_en; + + struct iio_trigger *drdy_trigger0; + s64 irq_timestamp; + + /* optimize runtime pm ops - enable/disable device only if needed */ + bool als_ps_need_en; + bool pxs_ps_need_en; + bool als_need_dis; + bool pxs_need_dis; + + struct regmap *regmap; + + /* + * Ensure correct naturally aligned timestamp. + * Note that the read will put garbage data into + * the padding but this should not be a problem + */ + struct { + __le16 channels[3]; + u8 garbage; + aligned_s64 ts; + } scan; +}; + +static IIO_CONST_ATTR(in_intensity_scale_available, RPR0521_ALS_SCALE_AVAIL); +static IIO_CONST_ATTR(in_proximity_scale_available, RPR0521_PXS_SCALE_AVAIL); + +/* + * Start with easy freq first, whole table of freq combinations is more + * complicated. + */ +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("2.5 10"); + +static struct attribute *rpr0521_attributes[] = { + &iio_const_attr_in_intensity_scale_available.dev_attr.attr, + &iio_const_attr_in_proximity_scale_available.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group rpr0521_attribute_group = { + .attrs = rpr0521_attributes, +}; + +/* Order of the channel data in buffer */ +enum rpr0521_scan_index_order { + RPR0521_CHAN_INDEX_PXS, + RPR0521_CHAN_INDEX_BOTH, + RPR0521_CHAN_INDEX_IR, +}; + +static const unsigned long rpr0521_available_scan_masks[] = { + BIT(RPR0521_CHAN_INDEX_PXS) | BIT(RPR0521_CHAN_INDEX_BOTH) | + BIT(RPR0521_CHAN_INDEX_IR), + 0 +}; + +static const struct iio_chan_spec rpr0521_channels[] = { + { + .type = IIO_PROXIMITY, + .address = RPR0521_CHAN_PXS, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = RPR0521_CHAN_INDEX_PXS, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_INTENSITY, + .modified = 1, + .address = RPR0521_CHAN_ALS_DATA0, + .channel2 = IIO_MOD_LIGHT_BOTH, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = RPR0521_CHAN_INDEX_BOTH, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_INTENSITY, + .modified = 1, + .address = RPR0521_CHAN_ALS_DATA1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = RPR0521_CHAN_INDEX_IR, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, +}; + +static int rpr0521_als_enable(struct rpr0521_data *data, u8 status) +{ + int ret; + + ret = regmap_update_bits(data->regmap, RPR0521_REG_MODE_CTRL, + RPR0521_MODE_ALS_MASK, + status); + if (ret < 0) + return ret; + + if (status & RPR0521_MODE_ALS_MASK) + data->als_dev_en = true; + else + data->als_dev_en = false; + + return 0; +} + +static int rpr0521_pxs_enable(struct rpr0521_data *data, u8 status) +{ + int ret; + + ret = regmap_update_bits(data->regmap, RPR0521_REG_MODE_CTRL, + RPR0521_MODE_PXS_MASK, + status); + if (ret < 0) + return ret; + + if (status & RPR0521_MODE_PXS_MASK) + data->pxs_dev_en = true; + else + data->pxs_dev_en = false; + + return 0; +} + +/** + * rpr0521_set_power_state - handles runtime PM state and sensors enabled status + * + * @data: rpr0521 device private data + * @on: state to be set for devices in @device_mask + * @device_mask: bitmask specifying for which device we need to update @on state + * + * Calls for this function must be balanced so that each ON should have matching + * OFF. Otherwise pm usage_count gets out of sync. + */ +static int rpr0521_set_power_state(struct rpr0521_data *data, bool on, + u8 device_mask) +{ +#ifdef CONFIG_PM + int ret; + + if (device_mask & RPR0521_MODE_ALS_MASK) { + data->als_ps_need_en = on; + data->als_need_dis = !on; + } + + if (device_mask & RPR0521_MODE_PXS_MASK) { + data->pxs_ps_need_en = on; + data->pxs_need_dis = !on; + } + + /* + * On: _resume() is called only when we are suspended + * Off: _suspend() is called after delay if _resume() is not + * called before that. + * Note: If either measurement is re-enabled before _suspend(), + * both stay enabled until _suspend(). + */ + if (on) + ret = pm_runtime_resume_and_get(&data->client->dev); + else + ret = pm_runtime_put_autosuspend(&data->client->dev); + if (ret < 0) { + dev_err(&data->client->dev, + "Failed: rpr0521_set_power_state for %d, ret %d\n", + on, ret); + return ret; + } + + if (on) { + /* If _resume() was not called, enable measurement now. */ + if (data->als_ps_need_en) { + ret = rpr0521_als_enable(data, RPR0521_MODE_ALS_ENABLE); + if (ret) + return ret; + data->als_ps_need_en = false; + } + + if (data->pxs_ps_need_en) { + ret = rpr0521_pxs_enable(data, RPR0521_MODE_PXS_ENABLE); + if (ret) + return ret; + data->pxs_ps_need_en = false; + } + } +#endif + return 0; +} + +/* Interrupt register tells if this sensor caused the interrupt or not. */ +static inline bool rpr0521_is_triggered(struct rpr0521_data *data) +{ + int ret; + int reg; + + ret = regmap_read(data->regmap, RPR0521_REG_INTERRUPT, ®); + if (ret < 0) + return false; /* Reg read failed. */ + if (reg & + (RPR0521_INTERRUPT_ALS_INT_STATUS_MASK | + RPR0521_INTERRUPT_PS_INT_STATUS_MASK)) + return true; + else + return false; /* Int not from this sensor. */ +} + +/* IRQ to trigger handler */ +static irqreturn_t rpr0521_drdy_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct rpr0521_data *data = iio_priv(indio_dev); + + data->irq_timestamp = iio_get_time_ns(indio_dev); + /* + * We need to wake the thread to read the interrupt reg. It + * is not possible to do that here because regmap_read takes a + * mutex. + */ + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t rpr0521_drdy_irq_thread(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct rpr0521_data *data = iio_priv(indio_dev); + + if (rpr0521_is_triggered(data)) { + iio_trigger_poll_nested(data->drdy_trigger0); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static irqreturn_t rpr0521_trigger_consumer_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct rpr0521_data *data = iio_priv(indio_dev); + int err; + + /* Use irq timestamp when reasonable. */ + if (iio_trigger_using_own(indio_dev) && data->irq_timestamp) { + pf->timestamp = data->irq_timestamp; + data->irq_timestamp = 0; + } + /* Other chained trigger polls get timestamp only here. */ + if (!pf->timestamp) + pf->timestamp = iio_get_time_ns(indio_dev); + + err = regmap_bulk_read(data->regmap, RPR0521_REG_PXS_DATA, + data->scan.channels, + (3 * 2) + 1); /* 3 * 16-bit + (discarded) int clear reg. */ + if (!err) + iio_push_to_buffers_with_ts(indio_dev, &data->scan, + sizeof(data->scan), pf->timestamp); + else + dev_err(&data->client->dev, + "Trigger consumer can't read from sensor.\n"); + pf->timestamp = 0; + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int rpr0521_write_int_enable(struct rpr0521_data *data) +{ + int err; + + /* Interrupt after each measurement */ + err = regmap_update_bits(data->regmap, RPR0521_REG_PXS_CTRL, + RPR0521_PXS_PERSISTENCE_MASK, + RPR0521_PXS_PERSISTENCE_DRDY); + if (err) { + dev_err(&data->client->dev, "PS control reg write fail.\n"); + return -EBUSY; + } + + /* Ignore latch and mode because of drdy */ + err = regmap_write(data->regmap, RPR0521_REG_INTERRUPT, + RPR0521_INTERRUPT_INT_REASSERT_DISABLE | + RPR0521_INTERRUPT_INT_TRIG_ALS_DISABLE | + RPR0521_INTERRUPT_INT_TRIG_PS_ENABLE + ); + if (err) { + dev_err(&data->client->dev, "Interrupt setup write fail.\n"); + return -EBUSY; + } + + return 0; +} + +static int rpr0521_write_int_disable(struct rpr0521_data *data) +{ + /* Don't care of clearing mode, assert and latch. */ + return regmap_write(data->regmap, RPR0521_REG_INTERRUPT, + RPR0521_INTERRUPT_INT_TRIG_ALS_DISABLE | + RPR0521_INTERRUPT_INT_TRIG_PS_DISABLE + ); +} + +/* + * Trigger producer enable / disable. Note that there will be trigs only when + * measurement data is ready to be read. + */ +static int rpr0521_pxs_drdy_set_state(struct iio_trigger *trigger, + bool enable_drdy) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trigger); + struct rpr0521_data *data = iio_priv(indio_dev); + int err; + + if (enable_drdy) + err = rpr0521_write_int_enable(data); + else + err = rpr0521_write_int_disable(data); + if (err) + dev_err(&data->client->dev, "rpr0521_pxs_drdy_set_state failed\n"); + + return err; +} + +static const struct iio_trigger_ops rpr0521_trigger_ops = { + .set_trigger_state = rpr0521_pxs_drdy_set_state, + }; + + +static int rpr0521_buffer_preenable(struct iio_dev *indio_dev) +{ + int err; + struct rpr0521_data *data = iio_priv(indio_dev); + + mutex_lock(&data->lock); + err = rpr0521_set_power_state(data, true, + (RPR0521_MODE_PXS_MASK | RPR0521_MODE_ALS_MASK)); + mutex_unlock(&data->lock); + if (err) + dev_err(&data->client->dev, "_buffer_preenable fail\n"); + + return err; +} + +static int rpr0521_buffer_postdisable(struct iio_dev *indio_dev) +{ + int err; + struct rpr0521_data *data = iio_priv(indio_dev); + + mutex_lock(&data->lock); + err = rpr0521_set_power_state(data, false, + (RPR0521_MODE_PXS_MASK | RPR0521_MODE_ALS_MASK)); + mutex_unlock(&data->lock); + if (err) + dev_err(&data->client->dev, "_buffer_postdisable fail\n"); + + return err; +} + +static const struct iio_buffer_setup_ops rpr0521_buffer_setup_ops = { + .preenable = rpr0521_buffer_preenable, + .postdisable = rpr0521_buffer_postdisable, +}; + +static int rpr0521_get_gain(struct rpr0521_data *data, int chan, + int *val, int *val2) +{ + int ret, reg, idx; + + ret = regmap_read(data->regmap, rpr0521_gain[chan].reg, ®); + if (ret < 0) + return ret; + + idx = (rpr0521_gain[chan].mask & reg) >> rpr0521_gain[chan].shift; + *val = rpr0521_gain[chan].gain[idx].scale; + *val2 = rpr0521_gain[chan].gain[idx].uscale; + + return 0; +} + +static int rpr0521_set_gain(struct rpr0521_data *data, int chan, + int val, int val2) +{ + int i, idx = -EINVAL; + + /* get gain index */ + for (i = 0; i < rpr0521_gain[chan].size; i++) + if (val == rpr0521_gain[chan].gain[i].scale && + val2 == rpr0521_gain[chan].gain[i].uscale) { + idx = i; + break; + } + + if (idx < 0) + return idx; + + return regmap_update_bits(data->regmap, rpr0521_gain[chan].reg, + rpr0521_gain[chan].mask, + idx << rpr0521_gain[chan].shift); +} + +static int rpr0521_read_samp_freq(struct rpr0521_data *data, + enum iio_chan_type chan_type, + int *val, int *val2) +{ + int reg, ret; + + ret = regmap_read(data->regmap, RPR0521_REG_MODE_CTRL, ®); + if (ret < 0) + return ret; + + reg &= RPR0521_MODE_MEAS_TIME_MASK; + if (reg >= ARRAY_SIZE(rpr0521_samp_freq_i)) + return -EINVAL; + + switch (chan_type) { + case IIO_INTENSITY: + *val = rpr0521_samp_freq_i[reg].als_hz; + *val2 = rpr0521_samp_freq_i[reg].als_uhz; + return 0; + + case IIO_PROXIMITY: + *val = rpr0521_samp_freq_i[reg].pxs_hz; + *val2 = rpr0521_samp_freq_i[reg].pxs_uhz; + return 0; + + default: + return -EINVAL; + } +} + +static int rpr0521_write_samp_freq_common(struct rpr0521_data *data, + enum iio_chan_type chan_type, + int val, int val2) +{ + int i; + + /* + * Ignore channel + * both pxs and als are setup only to same freq because of simplicity + */ + switch (val) { + case 0: + i = 0; + break; + + case 2: + if (val2 != 500000) + return -EINVAL; + + i = 11; + break; + + case 10: + i = 6; + break; + + default: + return -EINVAL; + } + + return regmap_update_bits(data->regmap, + RPR0521_REG_MODE_CTRL, + RPR0521_MODE_MEAS_TIME_MASK, + i); +} + +static int rpr0521_read_ps_offset(struct rpr0521_data *data, int *offset) +{ + int ret; + __le16 buffer; + + ret = regmap_bulk_read(data->regmap, + RPR0521_REG_PS_OFFSET_LSB, &buffer, sizeof(buffer)); + + if (ret < 0) { + dev_err(&data->client->dev, "Failed to read PS OFFSET register\n"); + return ret; + } + *offset = le16_to_cpu(buffer); + + return ret; +} + +static int rpr0521_write_ps_offset(struct rpr0521_data *data, int offset) +{ + int ret; + __le16 buffer; + + buffer = cpu_to_le16(offset & 0x3ff); + ret = regmap_raw_write(data->regmap, + RPR0521_REG_PS_OFFSET_LSB, &buffer, sizeof(buffer)); + + if (ret < 0) { + dev_err(&data->client->dev, "Failed to write PS OFFSET register\n"); + return ret; + } + + return ret; +} + +static int rpr0521_read_info_raw(struct rpr0521_data *data, + struct iio_chan_spec const *chan, + int *val) +{ + u8 device_mask; + __le16 raw_data; + int ret; + + device_mask = rpr0521_data_reg[chan->address].device_mask; + + guard(mutex)(&data->lock); + ret = rpr0521_set_power_state(data, true, device_mask); + if (ret < 0) + return ret; + + ret = regmap_bulk_read(data->regmap, + rpr0521_data_reg[chan->address].address, + &raw_data, sizeof(raw_data)); + if (ret < 0) { + rpr0521_set_power_state(data, false, device_mask); + return ret; + } + + ret = rpr0521_set_power_state(data, false, device_mask); + if (ret < 0) + return ret; + + *val = le16_to_cpu(raw_data); + + return 0; +} + +static int rpr0521_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct rpr0521_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type != IIO_INTENSITY && chan->type != IIO_PROXIMITY) + return -EINVAL; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = rpr0521_read_info_raw(data, chan, val); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + mutex_lock(&data->lock); + ret = rpr0521_get_gain(data, chan->address, val, val2); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_CHAN_INFO_SAMP_FREQ: + mutex_lock(&data->lock); + ret = rpr0521_read_samp_freq(data, chan->type, val, val2); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_CHAN_INFO_OFFSET: + mutex_lock(&data->lock); + ret = rpr0521_read_ps_offset(data, val); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int rpr0521_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct rpr0521_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + mutex_lock(&data->lock); + ret = rpr0521_set_gain(data, chan->address, val, val2); + mutex_unlock(&data->lock); + + return ret; + + case IIO_CHAN_INFO_SAMP_FREQ: + mutex_lock(&data->lock); + ret = rpr0521_write_samp_freq_common(data, chan->type, + val, val2); + mutex_unlock(&data->lock); + + return ret; + + case IIO_CHAN_INFO_OFFSET: + mutex_lock(&data->lock); + ret = rpr0521_write_ps_offset(data, val); + mutex_unlock(&data->lock); + + return ret; + + default: + return -EINVAL; + } +} + +static const struct iio_info rpr0521_info = { + .read_raw = rpr0521_read_raw, + .write_raw = rpr0521_write_raw, + .attrs = &rpr0521_attribute_group, +}; + +static int rpr0521_init(struct rpr0521_data *data) +{ + int ret; + int id; + + ret = regmap_read(data->regmap, RPR0521_REG_ID, &id); + if (ret < 0) { + dev_err(&data->client->dev, "Failed to read REG_ID register\n"); + return ret; + } + + if (id != RPR0521_MANUFACT_ID) { + dev_err(&data->client->dev, "Wrong id, got %x, expected %x\n", + id, RPR0521_MANUFACT_ID); + return -ENODEV; + } + + /* set default measurement time - 100 ms for both ALS and PS */ + ret = regmap_update_bits(data->regmap, RPR0521_REG_MODE_CTRL, + RPR0521_MODE_MEAS_TIME_MASK, + RPR0521_DEFAULT_MEAS_TIME); + if (ret) { + pr_err("regmap_update_bits returned %d\n", ret); + return ret; + } + +#ifndef CONFIG_PM + ret = rpr0521_als_enable(data, RPR0521_MODE_ALS_ENABLE); + if (ret < 0) + return ret; + ret = rpr0521_pxs_enable(data, RPR0521_MODE_PXS_ENABLE); + if (ret < 0) + return ret; +#endif + + data->irq_timestamp = 0; + + return 0; +} + +static int rpr0521_poweroff(struct rpr0521_data *data) +{ + int ret; + int tmp; + + ret = regmap_update_bits(data->regmap, RPR0521_REG_MODE_CTRL, + RPR0521_MODE_ALS_MASK | + RPR0521_MODE_PXS_MASK, + RPR0521_MODE_ALS_DISABLE | + RPR0521_MODE_PXS_DISABLE); + if (ret < 0) + return ret; + + data->als_dev_en = false; + data->pxs_dev_en = false; + + /* + * Int pin keeps state after power off. Set pin to high impedance + * mode to prevent power drain. + */ + ret = regmap_read(data->regmap, RPR0521_REG_INTERRUPT, &tmp); + if (ret) { + dev_err(&data->client->dev, "Failed to reset int pin.\n"); + return ret; + } + + return 0; +} + +static bool rpr0521_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RPR0521_REG_MODE_CTRL: + case RPR0521_REG_ALS_CTRL: + case RPR0521_REG_PXS_CTRL: + return false; + default: + return true; + } +} + +static const struct regmap_config rpr0521_regmap_config = { + .name = "rpr0521_regmap", + + .reg_bits = 8, + .val_bits = 8, + + .max_register = RPR0521_REG_ID, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = rpr0521_is_volatile_reg, +}; + +static int rpr0521_probe(struct i2c_client *client) +{ + struct rpr0521_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, &rpr0521_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "regmap_init failed!\n"); + return PTR_ERR(regmap); + } + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->regmap = regmap; + + mutex_init(&data->lock); + + indio_dev->info = &rpr0521_info; + indio_dev->name = RPR0521_DRV_NAME; + indio_dev->channels = rpr0521_channels; + indio_dev->num_channels = ARRAY_SIZE(rpr0521_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = rpr0521_init(data); + if (ret < 0) { + dev_err(&client->dev, "rpr0521 chip init failed\n"); + return ret; + } + + ret = pm_runtime_set_active(&client->dev); + if (ret < 0) + goto err_poweroff; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, RPR0521_SLEEP_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + + /* + * If sensor write/read is needed in _probe after _use_autosuspend, + * sensor needs to be _resumed first using rpr0521_set_power_state(). + */ + + /* IRQ to trigger setup */ + if (client->irq) { + /* Trigger0 producer setup */ + data->drdy_trigger0 = devm_iio_trigger_alloc( + indio_dev->dev.parent, + "%s-dev%d", indio_dev->name, iio_device_id(indio_dev)); + if (!data->drdy_trigger0) { + ret = -ENOMEM; + goto err_pm_disable; + } + data->drdy_trigger0->ops = &rpr0521_trigger_ops; + indio_dev->available_scan_masks = rpr0521_available_scan_masks; + iio_trigger_set_drvdata(data->drdy_trigger0, indio_dev); + + /* Ties irq to trigger producer handler. */ + ret = devm_request_threaded_irq(&client->dev, client->irq, + rpr0521_drdy_irq_handler, rpr0521_drdy_irq_thread, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "rpr0521_event", indio_dev); + if (ret) + goto err_pm_disable; + + ret = devm_iio_trigger_register(indio_dev->dev.parent, + data->drdy_trigger0); + if (ret) { + dev_err(&client->dev, "iio trigger register failed\n"); + goto err_pm_disable; + } + + /* + * Now whole pipe from physical interrupt (irq defined by + * devicetree to device) to trigger0 output is set up. + */ + + /* Trigger consumer setup */ + ret = devm_iio_triggered_buffer_setup(indio_dev->dev.parent, + indio_dev, + iio_pollfunc_store_time, + rpr0521_trigger_consumer_handler, + &rpr0521_buffer_setup_ops); + if (ret < 0) { + dev_err(&client->dev, "iio triggered buffer setup failed\n"); + goto err_pm_disable; + } + } + + ret = iio_device_register(indio_dev); + if (ret) + goto err_pm_disable; + + return 0; + +err_pm_disable: + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); +err_poweroff: + rpr0521_poweroff(data); + + return ret; +} + +static void rpr0521_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + rpr0521_poweroff(iio_priv(indio_dev)); +} + +static int rpr0521_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct rpr0521_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->lock); + /* If measurements are enabled, enable them on resume */ + if (!data->als_need_dis) + data->als_ps_need_en = data->als_dev_en; + if (!data->pxs_need_dis) + data->pxs_ps_need_en = data->pxs_dev_en; + + /* disable channels and sets {als,pxs}_dev_en to false */ + ret = rpr0521_poweroff(data); + regcache_mark_dirty(data->regmap); + mutex_unlock(&data->lock); + + return ret; +} + +static int rpr0521_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct rpr0521_data *data = iio_priv(indio_dev); + int ret; + + ret = regcache_sync(data->regmap); + if (ret < 0) + return ret; + + if (data->als_ps_need_en) { + ret = rpr0521_als_enable(data, RPR0521_MODE_ALS_ENABLE); + if (ret < 0) + return ret; + data->als_ps_need_en = false; + } + + if (data->pxs_ps_need_en) { + ret = rpr0521_pxs_enable(data, RPR0521_MODE_PXS_ENABLE); + if (ret < 0) + return ret; + data->pxs_ps_need_en = false; + } + msleep(100); //wait for first measurement result + + return 0; +} + +static const struct dev_pm_ops rpr0521_pm_ops = { + RUNTIME_PM_OPS(rpr0521_runtime_suspend, rpr0521_runtime_resume, NULL) +}; + +static const struct acpi_device_id rpr0521_acpi_match[] = { + {"RPR0521", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, rpr0521_acpi_match); + +static const struct i2c_device_id rpr0521_id[] = { + { .name = "rpr0521" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, rpr0521_id); + +static struct i2c_driver rpr0521_driver = { + .driver = { + .name = RPR0521_DRV_NAME, + .pm = pm_ptr(&rpr0521_pm_ops), + .acpi_match_table = rpr0521_acpi_match, + }, + .probe = rpr0521_probe, + .remove = rpr0521_remove, + .id_table = rpr0521_id, +}; + +module_i2c_driver(rpr0521_driver); + +MODULE_AUTHOR("Daniel Baluta "); +MODULE_DESCRIPTION("RPR0521 ROHM Ambient Light and Proximity Sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/si1133.c b/drivers/iio/light/si1133.c new file mode 100644 index 000000000..22073ded8 --- /dev/null +++ b/drivers/iio/light/si1133.c @@ -0,0 +1,1088 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * si1133.c - Support for Silabs SI1133 combined ambient + * light and UV index sensors + * + * Copyright 2018 Maxime Roussin-Belanger + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define SI1133_REG_PART_ID 0x00 +#define SI1133_REG_REV_ID 0x01 +#define SI1133_REG_MFR_ID 0x02 +#define SI1133_REG_INFO0 0x03 +#define SI1133_REG_INFO1 0x04 + +#define SI1133_PART_ID 0x33 + +#define SI1133_REG_HOSTIN0 0x0A +#define SI1133_REG_COMMAND 0x0B +#define SI1133_REG_IRQ_ENABLE 0x0F +#define SI1133_REG_RESPONSE1 0x10 +#define SI1133_REG_RESPONSE0 0x11 +#define SI1133_REG_IRQ_STATUS 0x12 +#define SI1133_REG_MEAS_RATE 0x1A + +#define SI1133_IRQ_CHANNEL_ENABLE 0xF + +#define SI1133_CMD_RESET_CTR 0x00 +#define SI1133_CMD_RESET_SW 0x01 +#define SI1133_CMD_FORCE 0x11 +#define SI1133_CMD_START_AUTONOMOUS 0x13 +#define SI1133_CMD_PARAM_SET 0x80 +#define SI1133_CMD_PARAM_QUERY 0x40 +#define SI1133_CMD_PARAM_MASK 0x3F + +#define SI1133_CMD_ERR_MASK BIT(4) +#define SI1133_CMD_SEQ_MASK 0xF +#define SI1133_MAX_CMD_CTR 0xF + +#define SI1133_PARAM_REG_CHAN_LIST 0x01 +#define SI1133_PARAM_REG_ADCCONFIG(x) (((x) * 4) + 2) +#define SI1133_PARAM_REG_ADCSENS(x) (((x) * 4) + 3) +#define SI1133_PARAM_REG_ADCPOST(x) (((x) * 4) + 4) + +#define SI1133_ADCMUX_MASK 0x1F + +#define SI1133_ADCCONFIG_DECIM_RATE(x) ((x) << 5) + +#define SI1133_ADCSENS_SCALE_MASK 0x70 +#define SI1133_ADCSENS_SCALE_SHIFT 4 +#define SI1133_ADCSENS_HSIG_MASK BIT(7) +#define SI1133_ADCSENS_HSIG_SHIFT 7 +#define SI1133_ADCSENS_HW_GAIN_MASK 0xF +#define SI1133_ADCSENS_NB_MEAS(x) (fls(x) << SI1133_ADCSENS_SCALE_SHIFT) + +#define SI1133_ADCPOST_24BIT_EN BIT(6) +#define SI1133_ADCPOST_POSTSHIFT_BITQTY(x) (((x) & GENMASK(2, 0)) << 3) + +#define SI1133_PARAM_ADCMUX_SMALL_IR 0x0 +#define SI1133_PARAM_ADCMUX_MED_IR 0x1 +#define SI1133_PARAM_ADCMUX_LARGE_IR 0x2 +#define SI1133_PARAM_ADCMUX_WHITE 0xB +#define SI1133_PARAM_ADCMUX_LARGE_WHITE 0xD +#define SI1133_PARAM_ADCMUX_UV 0x18 +#define SI1133_PARAM_ADCMUX_UV_DEEP 0x19 + +#define SI1133_ERR_INVALID_CMD 0x0 +#define SI1133_ERR_INVALID_LOCATION_CMD 0x1 +#define SI1133_ERR_SATURATION_ADC_OR_OVERFLOW_ACCUMULATION 0x2 +#define SI1133_ERR_OUTPUT_BUFFER_OVERFLOW 0x3 + +#define SI1133_COMPLETION_TIMEOUT_MS 500 + +#define SI1133_CMD_MINSLEEP_US_LOW 5000 +#define SI1133_CMD_MINSLEEP_US_HIGH 7500 +#define SI1133_CMD_TIMEOUT_MS 25 + +#define SI1133_REG_HOSTOUT(x) ((x) + 0x13) + +#define SI1133_X_ORDER_MASK 0x0070 +#define SI1133_Y_ORDER_MASK 0x0007 +#define si1133_get_x_order(m) (((m) & SI1133_X_ORDER_MASK) >> 4) +#define si1133_get_y_order(m) ((m) & SI1133_Y_ORDER_MASK) + +#define SI1133_LUX_ADC_MASK 0xE +#define SI1133_ADC_THRESHOLD 16000 +#define SI1133_INPUT_FRACTION_HIGH 7 +#define SI1133_INPUT_FRACTION_LOW 15 +#define SI1133_LUX_OUTPUT_FRACTION 12 +#define SI1133_LUX_BUFFER_SIZE 9 +#define SI1133_MEASURE_BUFFER_SIZE 3 + +static const int si1133_scale_available[] = { + 1, 2, 4, 8, 16, 32, 64, 128}; + +static IIO_CONST_ATTR(scale_available, "1 2 4 8 16 32 64 128"); + +static IIO_CONST_ATTR_INT_TIME_AVAIL("0.0244 0.0488 0.0975 0.195 0.390 0.780 " + "1.560 3.120 6.24 12.48 25.0 50.0"); + +/* A.K.A. HW_GAIN in datasheet */ +enum si1133_int_time { + _24_4_us = 0, + _48_8_us = 1, + _97_5_us = 2, + _195_0_us = 3, + _390_0_us = 4, + _780_0_us = 5, + _1_560_0_us = 6, + _3_120_0_us = 7, + _6_240_0_us = 8, + _12_480_0_us = 9, + _25_ms = 10, + _50_ms = 11, +}; + +/* Integration time in milliseconds, nanoseconds */ +static const int si1133_int_time_table[][2] = { + [_24_4_us] = {0, 24400}, + [_48_8_us] = {0, 48800}, + [_97_5_us] = {0, 97500}, + [_195_0_us] = {0, 195000}, + [_390_0_us] = {0, 390000}, + [_780_0_us] = {0, 780000}, + [_1_560_0_us] = {1, 560000}, + [_3_120_0_us] = {3, 120000}, + [_6_240_0_us] = {6, 240000}, + [_12_480_0_us] = {12, 480000}, + [_25_ms] = {25, 000000}, + [_50_ms] = {50, 000000}, +}; + +static const struct regmap_range si1133_reg_ranges[] = { + regmap_reg_range(0x00, 0x02), + regmap_reg_range(0x0A, 0x0B), + regmap_reg_range(0x0F, 0x0F), + regmap_reg_range(0x10, 0x12), + regmap_reg_range(0x13, 0x2C), +}; + +static const struct regmap_range si1133_reg_ro_ranges[] = { + regmap_reg_range(0x00, 0x02), + regmap_reg_range(0x10, 0x2C), +}; + +static const struct regmap_range si1133_precious_ranges[] = { + regmap_reg_range(0x12, 0x12), +}; + +static const struct regmap_access_table si1133_write_ranges_table = { + .yes_ranges = si1133_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(si1133_reg_ranges), + .no_ranges = si1133_reg_ro_ranges, + .n_no_ranges = ARRAY_SIZE(si1133_reg_ro_ranges), +}; + +static const struct regmap_access_table si1133_read_ranges_table = { + .yes_ranges = si1133_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(si1133_reg_ranges), +}; + +static const struct regmap_access_table si1133_precious_table = { + .yes_ranges = si1133_precious_ranges, + .n_yes_ranges = ARRAY_SIZE(si1133_precious_ranges), +}; + +static const struct regmap_config si1133_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0x2C, + + .wr_table = &si1133_write_ranges_table, + .rd_table = &si1133_read_ranges_table, + + .precious_table = &si1133_precious_table, +}; + +struct si1133_data { + struct regmap *regmap; + struct i2c_client *client; + + /* Lock protecting one command at a time can be processed */ + struct mutex mutex; + + int rsp_seq; + u8 scan_mask; + u8 adc_sens[6]; + u8 adc_config[6]; + + struct completion completion; +}; + +struct si1133_coeff { + s16 info; + u16 mag; +}; + +struct si1133_lux_coeff { + struct si1133_coeff coeff_high[4]; + struct si1133_coeff coeff_low[9]; +}; + +static const struct si1133_lux_coeff lux_coeff = { + { + { 0, 209}, + { 1665, 93}, + { 2064, 65}, + {-2671, 234} + }, + { + { 0, 0}, + { 1921, 29053}, + {-1022, 36363}, + { 2320, 20789}, + { -367, 57909}, + {-1774, 38240}, + { -608, 46775}, + {-1503, 51831}, + {-1886, 58928} + } +}; + +static int si1133_calculate_polynomial_inner(s32 input, u8 fraction, u16 mag, + s8 shift) +{ + return ((input << fraction) / mag) << shift; +} + +static int si1133_calculate_output(s32 x, s32 y, u8 x_order, u8 y_order, + u8 input_fraction, s8 sign, + const struct si1133_coeff *coeffs) +{ + s8 shift; + int x1 = 1; + int x2 = 1; + int y1 = 1; + int y2 = 1; + + shift = ((u16)coeffs->info & 0xFF00) >> 8; + shift ^= 0xFF; + shift += 1; + shift = -shift; + + if (x_order > 0) { + x1 = si1133_calculate_polynomial_inner(x, input_fraction, + coeffs->mag, shift); + if (x_order > 1) + x2 = x1; + } + + if (y_order > 0) { + y1 = si1133_calculate_polynomial_inner(y, input_fraction, + coeffs->mag, shift); + if (y_order > 1) + y2 = y1; + } + + return sign * x1 * x2 * y1 * y2; +} + +/* + * The algorithm is from: + * https://siliconlabs.github.io/Gecko_SDK_Doc/efm32zg/html/si1133_8c_source.html#l00716 + */ +static int si1133_calc_polynomial(s32 x, s32 y, u8 input_fraction, u8 num_coeff, + const struct si1133_coeff *coeffs) +{ + u8 x_order, y_order; + u8 counter; + s8 sign; + int output = 0; + + for (counter = 0; counter < num_coeff; counter++) { + if (coeffs->info < 0) + sign = -1; + else + sign = 1; + + x_order = si1133_get_x_order(coeffs->info); + y_order = si1133_get_y_order(coeffs->info); + + if ((x_order == 0) && (y_order == 0)) + output += + sign * coeffs->mag << SI1133_LUX_OUTPUT_FRACTION; + else + output += si1133_calculate_output(x, y, x_order, + y_order, + input_fraction, sign, + coeffs); + coeffs++; + } + + return abs(output); +} + +static int si1133_cmd_reset_sw(struct si1133_data *data) +{ + struct device *dev = &data->client->dev; + unsigned int resp; + unsigned long timeout; + int err; + + err = regmap_write(data->regmap, SI1133_REG_COMMAND, + SI1133_CMD_RESET_SW); + if (err) + return err; + + timeout = jiffies + msecs_to_jiffies(SI1133_CMD_TIMEOUT_MS); + while (true) { + err = regmap_read(data->regmap, SI1133_REG_RESPONSE0, &resp); + if (err == -ENXIO) { + usleep_range(SI1133_CMD_MINSLEEP_US_LOW, + SI1133_CMD_MINSLEEP_US_HIGH); + continue; + } + + if ((resp & SI1133_MAX_CMD_CTR) == SI1133_MAX_CMD_CTR) + break; + + if (time_after(jiffies, timeout)) { + dev_warn(dev, "Timeout on reset ctr resp: %d\n", resp); + return -ETIMEDOUT; + } + } + + if (!err) + data->rsp_seq = SI1133_MAX_CMD_CTR; + + return err; +} + +static int si1133_parse_response_err(struct device *dev, u32 resp, u8 cmd) +{ + resp &= 0xF; + + switch (resp) { + case SI1133_ERR_OUTPUT_BUFFER_OVERFLOW: + dev_warn(dev, "Output buffer overflow: 0x%02x\n", cmd); + return -EOVERFLOW; + case SI1133_ERR_SATURATION_ADC_OR_OVERFLOW_ACCUMULATION: + dev_warn(dev, "Saturation of the ADC or overflow of accumulation: 0x%02x\n", + cmd); + return -EOVERFLOW; + case SI1133_ERR_INVALID_LOCATION_CMD: + dev_warn(dev, + "Parameter access to an invalid location: 0x%02x\n", + cmd); + return -EINVAL; + case SI1133_ERR_INVALID_CMD: + dev_warn(dev, "Invalid command 0x%02x\n", cmd); + return -EINVAL; + default: + dev_warn(dev, "Unknown error 0x%02x\n", cmd); + return -EINVAL; + } +} + +static int si1133_cmd_reset_counter(struct si1133_data *data) +{ + int err = regmap_write(data->regmap, SI1133_REG_COMMAND, + SI1133_CMD_RESET_CTR); + if (err) + return err; + + data->rsp_seq = 0; + + return 0; +} + +static int si1133_command(struct si1133_data *data, u8 cmd) +{ + unsigned long timeout; + struct device *dev = &data->client->dev; + u32 resp; + int err; + int expected_seq; + + guard(mutex)(&data->mutex); + + expected_seq = (data->rsp_seq + 1) & SI1133_MAX_CMD_CTR; + + if (cmd == SI1133_CMD_FORCE) { + /* Flush pending IRQs from a previous timeout. */ + regmap_read(data->regmap, SI1133_REG_IRQ_STATUS, &resp); + regmap_write(data->regmap, SI1133_REG_IRQ_ENABLE, + SI1133_IRQ_CHANNEL_ENABLE); + + reinit_completion(&data->completion); + } + + err = regmap_write(data->regmap, SI1133_REG_COMMAND, cmd); + if (err) { + dev_warn(dev, "Failed to write command 0x%02x, ret=%d\n", cmd, + err); + return err; + } + + if (cmd == SI1133_CMD_FORCE) { + /* wait for irq */ + timeout = msecs_to_jiffies(SI1133_COMPLETION_TIMEOUT_MS); + if (!wait_for_completion_timeout(&data->completion, timeout)) { + regmap_write(data->regmap, SI1133_REG_IRQ_ENABLE, 0); + return -ETIMEDOUT; + } + err = regmap_read(data->regmap, SI1133_REG_RESPONSE0, &resp); + if (err) + return err; + } else { + err = regmap_read_poll_timeout(data->regmap, + SI1133_REG_RESPONSE0, resp, + (resp & SI1133_CMD_SEQ_MASK) == + expected_seq || + (resp & SI1133_CMD_ERR_MASK), + SI1133_CMD_MINSLEEP_US_LOW, + SI1133_CMD_TIMEOUT_MS * 1000); + if (err) { + dev_warn(dev, + "Failed to read command 0x%02x, ret=%d\n", + cmd, err); + /* + * Reset counter on err to prevent software and hardware + * counters being out of sync. + */ + si1133_cmd_reset_counter(data); + return err; + } + } + + if (resp & SI1133_CMD_ERR_MASK) { + err = si1133_parse_response_err(dev, resp, cmd); + si1133_cmd_reset_counter(data); + } else { + data->rsp_seq = expected_seq; + } + + return err; +} + +static int si1133_param_set(struct si1133_data *data, u8 param, u32 value) +{ + int err = regmap_write(data->regmap, SI1133_REG_HOSTIN0, value); + + if (err) + return err; + + return si1133_command(data, SI1133_CMD_PARAM_SET | + (param & SI1133_CMD_PARAM_MASK)); +} + +static int si1133_param_query(struct si1133_data *data, u8 param, u32 *result) +{ + int err = si1133_command(data, SI1133_CMD_PARAM_QUERY | + (param & SI1133_CMD_PARAM_MASK)); + if (err) + return err; + + return regmap_read(data->regmap, SI1133_REG_RESPONSE1, result); +} + +#define SI1133_CHANNEL(_ch, _type) \ + .type = _type, \ + .channel = _ch, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_HARDWAREGAIN), \ + +static const struct iio_chan_spec si1133_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .channel = 0, + }, + { + SI1133_CHANNEL(SI1133_PARAM_ADCMUX_WHITE, IIO_INTENSITY) + .channel2 = IIO_MOD_LIGHT_BOTH, + }, + { + SI1133_CHANNEL(SI1133_PARAM_ADCMUX_LARGE_WHITE, IIO_INTENSITY) + .channel2 = IIO_MOD_LIGHT_BOTH, + .extend_name = "large", + }, + { + SI1133_CHANNEL(SI1133_PARAM_ADCMUX_SMALL_IR, IIO_INTENSITY) + .extend_name = "small", + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + }, + { + SI1133_CHANNEL(SI1133_PARAM_ADCMUX_MED_IR, IIO_INTENSITY) + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + }, + { + SI1133_CHANNEL(SI1133_PARAM_ADCMUX_LARGE_IR, IIO_INTENSITY) + .extend_name = "large", + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + }, + { + SI1133_CHANNEL(SI1133_PARAM_ADCMUX_UV, IIO_UVINDEX) + }, + { + SI1133_CHANNEL(SI1133_PARAM_ADCMUX_UV_DEEP, IIO_UVINDEX) + .modified = 1, + .channel2 = IIO_MOD_LIGHT_DUV, + } +}; + +static int si1133_get_int_time_index(int milliseconds, int nanoseconds) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(si1133_int_time_table); i++) { + if (milliseconds == si1133_int_time_table[i][0] && + nanoseconds == si1133_int_time_table[i][1]) + return i; + } + return -EINVAL; +} + +static int si1133_set_integration_time(struct si1133_data *data, u8 adc, + int milliseconds, int nanoseconds) +{ + int index; + + index = si1133_get_int_time_index(milliseconds, nanoseconds); + if (index < 0) + return index; + + data->adc_sens[adc] &= 0xF0; + data->adc_sens[adc] |= index; + + return si1133_param_set(data, SI1133_PARAM_REG_ADCSENS(0), + data->adc_sens[adc]); +} + +static int si1133_set_chlist(struct si1133_data *data, u8 scan_mask) +{ + /* channel list already set, no need to reprogram */ + if (data->scan_mask == scan_mask) + return 0; + + data->scan_mask = scan_mask; + + return si1133_param_set(data, SI1133_PARAM_REG_CHAN_LIST, scan_mask); +} + +static int si1133_chan_set_adcconfig(struct si1133_data *data, u8 adc, + u8 adc_config) +{ + int err; + + err = si1133_param_set(data, SI1133_PARAM_REG_ADCCONFIG(adc), + adc_config); + if (err) + return err; + + data->adc_config[adc] = adc_config; + + return 0; +} + +static int si1133_update_adcconfig(struct si1133_data *data, uint8_t adc, + u8 mask, u8 shift, u8 value) +{ + u32 adc_config; + int err; + + err = si1133_param_query(data, SI1133_PARAM_REG_ADCCONFIG(adc), + &adc_config); + if (err) + return err; + + adc_config &= ~mask; + adc_config |= (value << shift); + + return si1133_chan_set_adcconfig(data, adc, adc_config); +} + +static int si1133_set_adcmux(struct si1133_data *data, u8 adc, u8 mux) +{ + if ((mux & data->adc_config[adc]) == mux) + return 0; /* mux already set to correct value */ + + return si1133_update_adcconfig(data, adc, SI1133_ADCMUX_MASK, 0, mux); +} + +static int si1133_force_measurement(struct si1133_data *data) +{ + return si1133_command(data, SI1133_CMD_FORCE); +} + +static int si1133_bulk_read(struct si1133_data *data, u8 start_reg, u8 length, + u8 *buffer) +{ + int err; + + err = si1133_force_measurement(data); + if (err) + return err; + + return regmap_bulk_read(data->regmap, start_reg, buffer, length); +} + +static int si1133_measure(struct si1133_data *data, + struct iio_chan_spec const *chan, + int *val) +{ + int err; + + u8 buffer[SI1133_MEASURE_BUFFER_SIZE]; + + err = si1133_set_adcmux(data, 0, chan->channel); + if (err) + return err; + + /* Deactivate lux measurements if they were active */ + err = si1133_set_chlist(data, BIT(0)); + if (err) + return err; + + err = si1133_bulk_read(data, SI1133_REG_HOSTOUT(0), sizeof(buffer), + buffer); + if (err) + return err; + + *val = sign_extend32(get_unaligned_be24(&buffer[0]), 23); + + return err; +} + +static irqreturn_t si1133_threaded_irq_handler(int irq, void *private) +{ + struct iio_dev *iio_dev = private; + struct si1133_data *data = iio_priv(iio_dev); + u32 irq_status; + int err; + + err = regmap_read(data->regmap, SI1133_REG_IRQ_STATUS, &irq_status); + if (err) { + dev_err_ratelimited(&iio_dev->dev, "Error reading IRQ\n"); + goto out; + } + + if (irq_status != data->scan_mask) + return IRQ_NONE; + +out: + complete(&data->completion); + + return IRQ_HANDLED; +} + +static int si1133_scale_to_swgain(int scale_integer, int scale_fractional) +{ + scale_integer = find_closest(scale_integer, si1133_scale_available, + ARRAY_SIZE(si1133_scale_available)); + if (scale_integer < 0 || + scale_integer > ARRAY_SIZE(si1133_scale_available) || + scale_fractional != 0) + return -EINVAL; + + return scale_integer; +} + +static int si1133_chan_set_adcsens(struct si1133_data *data, u8 adc, + u8 adc_sens) +{ + int err; + + err = si1133_param_set(data, SI1133_PARAM_REG_ADCSENS(adc), adc_sens); + if (err) + return err; + + data->adc_sens[adc] = adc_sens; + + return 0; +} + +static int si1133_update_adcsens(struct si1133_data *data, u8 mask, + u8 shift, u8 value) +{ + int err; + u32 adc_sens; + + err = si1133_param_query(data, SI1133_PARAM_REG_ADCSENS(0), + &adc_sens); + if (err) + return err; + + adc_sens &= ~mask; + adc_sens |= (value << shift); + + return si1133_chan_set_adcsens(data, 0, adc_sens); +} + +static int si1133_get_lux(struct si1133_data *data, int *val) +{ + int err; + int lux; + s32 high_vis; + s32 low_vis; + s32 ir; + u8 buffer[SI1133_LUX_BUFFER_SIZE]; + + /* Activate lux channels */ + err = si1133_set_chlist(data, SI1133_LUX_ADC_MASK); + if (err) + return err; + + err = si1133_bulk_read(data, SI1133_REG_HOSTOUT(0), + SI1133_LUX_BUFFER_SIZE, buffer); + if (err) + return err; + + high_vis = sign_extend32(get_unaligned_be24(&buffer[0]), 23); + + low_vis = sign_extend32(get_unaligned_be24(&buffer[3]), 23); + + ir = sign_extend32(get_unaligned_be24(&buffer[6]), 23); + + if (high_vis > SI1133_ADC_THRESHOLD || ir > SI1133_ADC_THRESHOLD) + lux = si1133_calc_polynomial(high_vis, ir, + SI1133_INPUT_FRACTION_HIGH, + ARRAY_SIZE(lux_coeff.coeff_high), + &lux_coeff.coeff_high[0]); + else + lux = si1133_calc_polynomial(low_vis, ir, + SI1133_INPUT_FRACTION_LOW, + ARRAY_SIZE(lux_coeff.coeff_low), + &lux_coeff.coeff_low[0]); + + *val = lux >> SI1133_LUX_OUTPUT_FRACTION; + + return err; +} + +static int si1133_read_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct si1133_data *data = iio_priv(iio_dev); + u8 adc_sens = data->adc_sens[0]; + int err; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_LIGHT: + err = si1133_get_lux(data, val); + if (err) + return err; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_INTENSITY: + case IIO_UVINDEX: + err = si1133_measure(data, chan, val); + if (err) + return err; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_INTENSITY: + case IIO_UVINDEX: + adc_sens &= SI1133_ADCSENS_HW_GAIN_MASK; + + *val = si1133_int_time_table[adc_sens][0]; + *val2 = si1133_int_time_table[adc_sens][1]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_INTENSITY: + case IIO_UVINDEX: + adc_sens &= SI1133_ADCSENS_SCALE_MASK; + adc_sens >>= SI1133_ADCSENS_SCALE_SHIFT; + + *val = BIT(adc_sens); + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_HARDWAREGAIN: + switch (chan->type) { + case IIO_INTENSITY: + case IIO_UVINDEX: + adc_sens >>= SI1133_ADCSENS_HSIG_SHIFT; + + *val = adc_sens; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int si1133_write_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct si1133_data *data = iio_priv(iio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_INTENSITY: + case IIO_UVINDEX: + val = si1133_scale_to_swgain(val, val2); + if (val < 0) + return val; + + return si1133_update_adcsens(data, + SI1133_ADCSENS_SCALE_MASK, + SI1133_ADCSENS_SCALE_SHIFT, + val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + return si1133_set_integration_time(data, 0, val, val2); + case IIO_CHAN_INFO_HARDWAREGAIN: + switch (chan->type) { + case IIO_INTENSITY: + case IIO_UVINDEX: + if (val != 0 && val != 1) + return -EINVAL; + + return si1133_update_adcsens(data, + SI1133_ADCSENS_HSIG_MASK, + SI1133_ADCSENS_HSIG_SHIFT, + val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static struct attribute *si1133_attributes[] = { + &iio_const_attr_integration_time_available.dev_attr.attr, + &iio_const_attr_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group si1133_attribute_group = { + .attrs = si1133_attributes, +}; + +static const struct iio_info si1133_info = { + .read_raw = si1133_read_raw, + .write_raw = si1133_write_raw, + .attrs = &si1133_attribute_group, +}; + +/* + * si1133_init_lux_channels - Configure 3 different channels(adc) (1,2 and 3) + * The channel configuration for the lux measurement was taken from : + * https://siliconlabs.github.io/Gecko_SDK_Doc/efm32zg/html/si1133_8c_source.html#l00578 + * + * Reserved the channel 0 for the other raw measurements + */ +static int si1133_init_lux_channels(struct si1133_data *data) +{ + int err; + + err = si1133_chan_set_adcconfig(data, 1, + SI1133_ADCCONFIG_DECIM_RATE(1) | + SI1133_PARAM_ADCMUX_LARGE_WHITE); + if (err) + return err; + + err = si1133_param_set(data, SI1133_PARAM_REG_ADCPOST(1), + SI1133_ADCPOST_24BIT_EN | + SI1133_ADCPOST_POSTSHIFT_BITQTY(0)); + if (err) + return err; + err = si1133_chan_set_adcsens(data, 1, SI1133_ADCSENS_HSIG_MASK | + SI1133_ADCSENS_NB_MEAS(64) | _48_8_us); + if (err) + return err; + + err = si1133_chan_set_adcconfig(data, 2, + SI1133_ADCCONFIG_DECIM_RATE(1) | + SI1133_PARAM_ADCMUX_LARGE_WHITE); + if (err) + return err; + + err = si1133_param_set(data, SI1133_PARAM_REG_ADCPOST(2), + SI1133_ADCPOST_24BIT_EN | + SI1133_ADCPOST_POSTSHIFT_BITQTY(2)); + if (err) + return err; + + err = si1133_chan_set_adcsens(data, 2, SI1133_ADCSENS_HSIG_MASK | + SI1133_ADCSENS_NB_MEAS(1) | _3_120_0_us); + if (err) + return err; + + err = si1133_chan_set_adcconfig(data, 3, + SI1133_ADCCONFIG_DECIM_RATE(1) | + SI1133_PARAM_ADCMUX_MED_IR); + if (err) + return err; + + err = si1133_param_set(data, SI1133_PARAM_REG_ADCPOST(3), + SI1133_ADCPOST_24BIT_EN | + SI1133_ADCPOST_POSTSHIFT_BITQTY(2)); + if (err) + return err; + + return si1133_chan_set_adcsens(data, 3, SI1133_ADCSENS_HSIG_MASK | + SI1133_ADCSENS_NB_MEAS(64) | _48_8_us); +} + +static int si1133_initialize(struct si1133_data *data) +{ + int err; + + err = si1133_cmd_reset_sw(data); + if (err) + return err; + + /* Turn off autonomous mode */ + err = si1133_param_set(data, SI1133_REG_MEAS_RATE, 0); + if (err) + return err; + + err = si1133_init_lux_channels(data); + if (err) + return err; + + return regmap_write(data->regmap, SI1133_REG_IRQ_ENABLE, + SI1133_IRQ_CHANNEL_ENABLE); +} + +static int si1133_validate_ids(struct iio_dev *iio_dev) +{ + struct si1133_data *data = iio_priv(iio_dev); + + unsigned int part_id, rev_id, mfr_id; + int err; + + err = regmap_read(data->regmap, SI1133_REG_PART_ID, &part_id); + if (err) + return err; + + err = regmap_read(data->regmap, SI1133_REG_REV_ID, &rev_id); + if (err) + return err; + + err = regmap_read(data->regmap, SI1133_REG_MFR_ID, &mfr_id); + if (err) + return err; + + dev_info(&iio_dev->dev, + "Device ID part 0x%02x rev 0x%02x mfr 0x%02x\n", + part_id, rev_id, mfr_id); + if (part_id != SI1133_PART_ID) { + dev_err(&iio_dev->dev, + "Part ID mismatch got 0x%02x, expected 0x%02x\n", + part_id, SI1133_PART_ID); + return -ENODEV; + } + + return 0; +} + +static int si1133_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct si1133_data *data; + struct iio_dev *iio_dev; + int err; + + iio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!iio_dev) + return -ENOMEM; + + data = iio_priv(iio_dev); + + init_completion(&data->completion); + + data->regmap = devm_regmap_init_i2c(client, &si1133_regmap_config); + if (IS_ERR(data->regmap)) { + err = PTR_ERR(data->regmap); + dev_err(&client->dev, "Failed to initialise regmap: %d\n", err); + return err; + } + + i2c_set_clientdata(client, iio_dev); + data->client = client; + + iio_dev->name = id->name; + iio_dev->channels = si1133_channels; + iio_dev->num_channels = ARRAY_SIZE(si1133_channels); + iio_dev->info = &si1133_info; + iio_dev->modes = INDIO_DIRECT_MODE; + + mutex_init(&data->mutex); + + err = si1133_validate_ids(iio_dev); + if (err) + return err; + + err = si1133_initialize(data); + if (err) { + dev_err(&client->dev, + "Error when initializing chip: %d\n", err); + return err; + } + + if (!client->irq) { + dev_err(&client->dev, + "Required interrupt not provided, cannot proceed\n"); + return -EINVAL; + } + + err = devm_request_threaded_irq(&client->dev, client->irq, + NULL, + si1133_threaded_irq_handler, + IRQF_ONESHOT | IRQF_SHARED, + client->name, iio_dev); + if (err) { + dev_warn(&client->dev, "Request irq %d failed: %i\n", + client->irq, err); + return err; + } + + return devm_iio_device_register(&client->dev, iio_dev); +} + +static const struct i2c_device_id si1133_ids[] = { + { .name = "si1133" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, si1133_ids); + +static struct i2c_driver si1133_driver = { + .driver = { + .name = "si1133", + }, + .probe = si1133_probe, + .id_table = si1133_ids, +}; + +module_i2c_driver(si1133_driver); + +MODULE_AUTHOR("Maxime Roussin-Belanger "); +MODULE_DESCRIPTION("Silabs SI1133, UV index sensor and ambient light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/si1145.c b/drivers/iio/light/si1145.c new file mode 100644 index 000000000..bb7c99041 --- /dev/null +++ b/drivers/iio/light/si1145.c @@ -0,0 +1,1358 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * si1145.c - Support for Silabs SI1132 and SI1141/2/3/5/6/7 combined ambient + * light, UV index and proximity sensors + * + * Copyright 2014-16 Peter Meerwald-Stadler + * Copyright 2016 Crestez Dan Leonard + * + * SI1132 (7-bit I2C slave address 0x60) + * SI1141/2/3 (7-bit I2C slave address 0x5a) + * SI1145/6/6 (7-bit I2C slave address 0x60) + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define SI1145_REG_PART_ID 0x00 +#define SI1145_REG_REV_ID 0x01 +#define SI1145_REG_SEQ_ID 0x02 +#define SI1145_REG_INT_CFG 0x03 +#define SI1145_REG_IRQ_ENABLE 0x04 +#define SI1145_REG_IRQ_MODE 0x05 +#define SI1145_REG_HW_KEY 0x07 +#define SI1145_REG_MEAS_RATE 0x08 +#define SI1145_REG_PS_LED21 0x0f +#define SI1145_REG_PS_LED3 0x10 +#define SI1145_REG_UCOEF1 0x13 +#define SI1145_REG_UCOEF2 0x14 +#define SI1145_REG_UCOEF3 0x15 +#define SI1145_REG_UCOEF4 0x16 +#define SI1145_REG_PARAM_WR 0x17 +#define SI1145_REG_COMMAND 0x18 +#define SI1145_REG_RESPONSE 0x20 +#define SI1145_REG_IRQ_STATUS 0x21 +#define SI1145_REG_ALSVIS_DATA 0x22 +#define SI1145_REG_ALSIR_DATA 0x24 +#define SI1145_REG_PS1_DATA 0x26 +#define SI1145_REG_PS2_DATA 0x28 +#define SI1145_REG_PS3_DATA 0x2a +#define SI1145_REG_AUX_DATA 0x2c +#define SI1145_REG_PARAM_RD 0x2e +#define SI1145_REG_CHIP_STAT 0x30 + +#define SI1145_UCOEF1_DEFAULT 0x7b +#define SI1145_UCOEF2_DEFAULT 0x6b +#define SI1145_UCOEF3_DEFAULT 0x01 +#define SI1145_UCOEF4_DEFAULT 0x00 + +/* Helper to figure out PS_LED register / shift per channel */ +#define SI1145_PS_LED_REG(ch) \ + (((ch) == 2) ? SI1145_REG_PS_LED3 : SI1145_REG_PS_LED21) +#define SI1145_PS_LED_SHIFT(ch) \ + (((ch) == 1) ? 4 : 0) + +/* Parameter offsets */ +#define SI1145_PARAM_CHLIST 0x01 +#define SI1145_PARAM_PSLED12_SELECT 0x02 +#define SI1145_PARAM_PSLED3_SELECT 0x03 +#define SI1145_PARAM_PS_ENCODING 0x05 +#define SI1145_PARAM_ALS_ENCODING 0x06 +#define SI1145_PARAM_PS1_ADC_MUX 0x07 +#define SI1145_PARAM_PS2_ADC_MUX 0x08 +#define SI1145_PARAM_PS3_ADC_MUX 0x09 +#define SI1145_PARAM_PS_ADC_COUNTER 0x0a +#define SI1145_PARAM_PS_ADC_GAIN 0x0b +#define SI1145_PARAM_PS_ADC_MISC 0x0c +#define SI1145_PARAM_ALS_ADC_MUX 0x0d +#define SI1145_PARAM_ALSIR_ADC_MUX 0x0e +#define SI1145_PARAM_AUX_ADC_MUX 0x0f +#define SI1145_PARAM_ALSVIS_ADC_COUNTER 0x10 +#define SI1145_PARAM_ALSVIS_ADC_GAIN 0x11 +#define SI1145_PARAM_ALSVIS_ADC_MISC 0x12 +#define SI1145_PARAM_LED_RECOVERY 0x1c +#define SI1145_PARAM_ALSIR_ADC_COUNTER 0x1d +#define SI1145_PARAM_ALSIR_ADC_GAIN 0x1e +#define SI1145_PARAM_ALSIR_ADC_MISC 0x1f +#define SI1145_PARAM_ADC_OFFSET 0x1a + +/* Channel enable masks for CHLIST parameter */ +#define SI1145_CHLIST_EN_PS1 BIT(0) +#define SI1145_CHLIST_EN_PS2 BIT(1) +#define SI1145_CHLIST_EN_PS3 BIT(2) +#define SI1145_CHLIST_EN_ALSVIS BIT(4) +#define SI1145_CHLIST_EN_ALSIR BIT(5) +#define SI1145_CHLIST_EN_AUX BIT(6) +#define SI1145_CHLIST_EN_UV BIT(7) + +/* Proximity measurement mode for ADC_MISC parameter */ +#define SI1145_PS_ADC_MODE_NORMAL BIT(2) +/* Signal range mask for ADC_MISC parameter */ +#define SI1145_ADC_MISC_RANGE BIT(5) + +/* Commands for REG_COMMAND */ +#define SI1145_CMD_NOP 0x00 +#define SI1145_CMD_RESET 0x01 +#define SI1145_CMD_PS_FORCE 0x05 +#define SI1145_CMD_ALS_FORCE 0x06 +#define SI1145_CMD_PSALS_FORCE 0x07 +#define SI1145_CMD_PS_PAUSE 0x09 +#define SI1145_CMD_ALS_PAUSE 0x0a +#define SI1145_CMD_PSALS_PAUSE 0x0b +#define SI1145_CMD_PS_AUTO 0x0d +#define SI1145_CMD_ALS_AUTO 0x0e +#define SI1145_CMD_PSALS_AUTO 0x0f +#define SI1145_CMD_PARAM_QUERY 0x80 +#define SI1145_CMD_PARAM_SET 0xa0 + +#define SI1145_RSP_INVALID_SETTING 0x80 +#define SI1145_RSP_COUNTER_MASK 0x0F + +/* Minimum sleep after each command to ensure it's received */ +#define SI1145_COMMAND_MINSLEEP_MS 5 +/* Return -ETIMEDOUT after this long */ +#define SI1145_COMMAND_TIMEOUT_MS 25 + +/* Interrupt configuration masks for INT_CFG register */ +#define SI1145_INT_CFG_OE BIT(0) /* enable interrupt */ +#define SI1145_INT_CFG_MODE BIT(1) /* auto reset interrupt pin */ + +/* Interrupt enable masks for IRQ_ENABLE register */ +#define SI1145_MASK_ALL_IE (BIT(4) | BIT(3) | BIT(2) | BIT(0)) + +#define SI1145_MUX_TEMP 0x65 +#define SI1145_MUX_VDD 0x75 + +/* Proximity LED current; see Table 2 in datasheet */ +#define SI1145_LED_CURRENT_45mA 0x04 + +enum { + SI1132, + SI1141, + SI1142, + SI1143, + SI1145, + SI1146, + SI1147, +}; + +struct si1145_part_info { + u8 part; + const struct iio_info *iio_info; + const struct iio_chan_spec *channels; + unsigned int num_channels; + unsigned int num_leds; + bool uncompressed_meas_rate; +}; + +/** + * struct si1145_data - si1145 chip state data + * @client: I2C client + * @lock: mutex to protect shared state. + * @cmdlock: Low-level mutex to protect command execution only + * @rsp_seq: Next expected response number or -1 if counter reset required + * @scan_mask: Saved scan mask to avoid duplicate set_chlist + * @autonomous: If automatic measurements are active (for buffer support) + * @part_info: Part information + * @trig: Pointer to iio trigger + * @meas_rate: Value of MEAS_RATE register. Only set in HW in auto mode + * @buffer: Used to pack data read from sensor. + */ +struct si1145_data { + struct i2c_client *client; + struct mutex lock; + struct mutex cmdlock; + int rsp_seq; + const struct si1145_part_info *part_info; + unsigned long scan_mask; + bool autonomous; + struct iio_trigger *trig; + int meas_rate; + /* + * Ensure timestamp will be naturally aligned if present. + * Maximum buffer size (may be only partly used if not all + * channels are enabled): + * 6*2 bytes channels data + 4 bytes alignment + + * 8 bytes timestamp + */ + u8 buffer[24] __aligned(8); +}; + +/* + * __si1145_command_reset() - Send CMD_NOP and wait for response 0 + * + * Does not modify data->rsp_seq + * + * Return: 0 on success and -errno on error. + */ +static int __si1145_command_reset(struct si1145_data *data) +{ + struct device *dev = &data->client->dev; + unsigned long stop_jiffies; + int ret; + + ret = i2c_smbus_write_byte_data(data->client, SI1145_REG_COMMAND, + SI1145_CMD_NOP); + if (ret < 0) + return ret; + msleep(SI1145_COMMAND_MINSLEEP_MS); + + stop_jiffies = jiffies + SI1145_COMMAND_TIMEOUT_MS * HZ / 1000; + while (true) { + ret = i2c_smbus_read_byte_data(data->client, + SI1145_REG_RESPONSE); + if (ret <= 0) + return ret; + if (time_after(jiffies, stop_jiffies)) { + dev_warn(dev, "timeout on reset\n"); + return -ETIMEDOUT; + } + msleep(SI1145_COMMAND_MINSLEEP_MS); + } +} + +/* + * si1145_command() - Execute a command and poll the response register + * + * All conversion overflows are reported as -EOVERFLOW + * INVALID_SETTING is reported as -EINVAL + * Timeouts are reported as -ETIMEDOUT + * + * Return: 0 on success or -errno on failure + */ +static int si1145_command(struct si1145_data *data, u8 cmd) +{ + struct device *dev = &data->client->dev; + unsigned long stop_jiffies; + int ret; + + mutex_lock(&data->cmdlock); + + if (data->rsp_seq < 0) { + ret = __si1145_command_reset(data); + if (ret < 0) { + dev_err(dev, "failed to reset command counter, ret=%d\n", + ret); + goto out; + } + data->rsp_seq = 0; + } + + ret = i2c_smbus_write_byte_data(data->client, SI1145_REG_COMMAND, cmd); + if (ret) { + dev_warn(dev, "failed to write command, ret=%d\n", ret); + goto out; + } + /* Sleep a little to ensure the command is received */ + msleep(SI1145_COMMAND_MINSLEEP_MS); + + stop_jiffies = jiffies + SI1145_COMMAND_TIMEOUT_MS * HZ / 1000; + while (true) { + ret = i2c_smbus_read_byte_data(data->client, + SI1145_REG_RESPONSE); + if (ret < 0) { + dev_warn(dev, "failed to read response, ret=%d\n", ret); + break; + } + + if ((ret & ~SI1145_RSP_COUNTER_MASK) == 0) { + if (ret == data->rsp_seq) { + if (time_after(jiffies, stop_jiffies)) { + dev_warn(dev, "timeout on command 0x%02x\n", + cmd); + ret = -ETIMEDOUT; + break; + } + msleep(SI1145_COMMAND_MINSLEEP_MS); + continue; + } + if (ret == ((data->rsp_seq + 1) & + SI1145_RSP_COUNTER_MASK)) { + data->rsp_seq = ret; + ret = 0; + break; + } + dev_warn(dev, "unexpected response counter %d instead of %d\n", + ret, (data->rsp_seq + 1) & + SI1145_RSP_COUNTER_MASK); + ret = -EIO; + } else { + if (ret == SI1145_RSP_INVALID_SETTING) { + dev_warn(dev, "INVALID_SETTING error on command 0x%02x\n", + cmd); + ret = -EINVAL; + } else { + /* All overflows are treated identically */ + dev_dbg(dev, "overflow, ret=%d, cmd=0x%02x\n", + ret, cmd); + ret = -EOVERFLOW; + } + } + + /* Force a counter reset next time */ + data->rsp_seq = -1; + break; + } + +out: + mutex_unlock(&data->cmdlock); + + return ret; +} + +static int si1145_param_update(struct si1145_data *data, u8 op, u8 param, + u8 value) +{ + int ret; + + ret = i2c_smbus_write_byte_data(data->client, + SI1145_REG_PARAM_WR, value); + if (ret < 0) + return ret; + + return si1145_command(data, op | (param & 0x1F)); +} + +static int si1145_param_set(struct si1145_data *data, u8 param, u8 value) +{ + return si1145_param_update(data, SI1145_CMD_PARAM_SET, param, value); +} + +/* Set param. Returns negative errno or current value */ +static int si1145_param_query(struct si1145_data *data, u8 param) +{ + int ret; + + ret = si1145_command(data, SI1145_CMD_PARAM_QUERY | (param & 0x1F)); + if (ret < 0) + return ret; + + return i2c_smbus_read_byte_data(data->client, SI1145_REG_PARAM_RD); +} + +/* Expand 8 bit compressed value to 16 bit, see Silabs AN498 */ +static u16 si1145_uncompress(u8 x) +{ + u16 result = 0; + u8 exponent = 0; + + if (x < 8) + return 0; + + exponent = (x & 0xf0) >> 4; + result = 0x10 | (x & 0x0f); + + if (exponent >= 4) + return result << (exponent - 4); + return result >> (4 - exponent); +} + +/* Compress 16 bit value to 8 bit, see Silabs AN498 */ +static u8 si1145_compress(u16 x) +{ + u32 exponent = 0; + u32 significand = 0; + u32 tmp = x; + + if (x == 0x0000) + return 0x00; + if (x == 0x0001) + return 0x08; + + while (1) { + tmp >>= 1; + exponent += 1; + if (tmp == 1) + break; + } + + if (exponent < 5) { + significand = x << (4 - exponent); + return (exponent << 4) | (significand & 0xF); + } + + significand = x >> (exponent - 5); + if (significand & 1) { + significand += 2; + if (significand & 0x0040) { + exponent += 1; + significand >>= 1; + } + } + + return (exponent << 4) | ((significand >> 1) & 0xF); +} + +/* Write meas_rate in hardware */ +static int si1145_set_meas_rate(struct si1145_data *data, int interval) +{ + if (data->part_info->uncompressed_meas_rate) + return i2c_smbus_write_word_data(data->client, + SI1145_REG_MEAS_RATE, interval); + else + return i2c_smbus_write_byte_data(data->client, + SI1145_REG_MEAS_RATE, interval); +} + +static int si1145_read_samp_freq(struct si1145_data *data, int *val, int *val2) +{ + *val = 32000; + if (data->part_info->uncompressed_meas_rate) + *val2 = data->meas_rate; + else + *val2 = si1145_uncompress(data->meas_rate); + return IIO_VAL_FRACTIONAL; +} + +/* Set the samp freq in driver private data */ +static int si1145_store_samp_freq(struct si1145_data *data, int val) +{ + int ret = 0; + int meas_rate; + + if (val <= 0 || val > 32000) + return -ERANGE; + meas_rate = 32000 / val; + + mutex_lock(&data->lock); + if (data->autonomous) { + ret = si1145_set_meas_rate(data, meas_rate); + if (ret) + goto out; + } + if (data->part_info->uncompressed_meas_rate) + data->meas_rate = meas_rate; + else + data->meas_rate = si1145_compress(meas_rate); + +out: + mutex_unlock(&data->lock); + + return ret; +} + +static irqreturn_t si1145_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct si1145_data *data = iio_priv(indio_dev); + int i, j = 0; + int ret; + u8 irq_status = 0; + + if (!data->autonomous) { + ret = si1145_command(data, SI1145_CMD_PSALS_FORCE); + if (ret < 0 && ret != -EOVERFLOW) + goto done; + } else { + irq_status = ret = i2c_smbus_read_byte_data(data->client, + SI1145_REG_IRQ_STATUS); + if (ret < 0) + goto done; + if (!(irq_status & SI1145_MASK_ALL_IE)) + goto done; + } + + iio_for_each_active_channel(indio_dev, i) { + int run = 1; + + while (i + run < iio_get_masklength(indio_dev)) { + if (!test_bit(i + run, indio_dev->active_scan_mask)) + break; + if (indio_dev->channels[i + run].address != + indio_dev->channels[i].address + 2 * run) + break; + run++; + } + + ret = i2c_smbus_read_i2c_block_data_or_emulated( + data->client, indio_dev->channels[i].address, + sizeof(u16) * run, &data->buffer[j]); + if (ret < 0) + goto done; + j += run * sizeof(u16); + i += run - 1; + } + + if (data->autonomous) { + ret = i2c_smbus_write_byte_data(data->client, + SI1145_REG_IRQ_STATUS, + irq_status & SI1145_MASK_ALL_IE); + if (ret < 0) + goto done; + } + + iio_push_to_buffers_with_ts(indio_dev, data->buffer, + sizeof(data->buffer), + iio_get_time_ns(indio_dev)); + +done: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int si1145_set_chlist(struct iio_dev *indio_dev, unsigned long scan_mask) +{ + struct si1145_data *data = iio_priv(indio_dev); + u8 reg = 0, mux; + int ret; + int i; + + /* channel list already set, no need to reprogram */ + if (data->scan_mask == scan_mask) + return 0; + + for_each_set_bit(i, &scan_mask, iio_get_masklength(indio_dev)) { + switch (indio_dev->channels[i].address) { + case SI1145_REG_ALSVIS_DATA: + reg |= SI1145_CHLIST_EN_ALSVIS; + break; + case SI1145_REG_ALSIR_DATA: + reg |= SI1145_CHLIST_EN_ALSIR; + break; + case SI1145_REG_PS1_DATA: + reg |= SI1145_CHLIST_EN_PS1; + break; + case SI1145_REG_PS2_DATA: + reg |= SI1145_CHLIST_EN_PS2; + break; + case SI1145_REG_PS3_DATA: + reg |= SI1145_CHLIST_EN_PS3; + break; + case SI1145_REG_AUX_DATA: + switch (indio_dev->channels[i].type) { + case IIO_UVINDEX: + reg |= SI1145_CHLIST_EN_UV; + break; + default: + reg |= SI1145_CHLIST_EN_AUX; + if (indio_dev->channels[i].type == IIO_TEMP) + mux = SI1145_MUX_TEMP; + else + mux = SI1145_MUX_VDD; + ret = si1145_param_set(data, + SI1145_PARAM_AUX_ADC_MUX, mux); + if (ret < 0) + return ret; + + break; + } + } + } + + data->scan_mask = scan_mask; + ret = si1145_param_set(data, SI1145_PARAM_CHLIST, reg); + + return ret < 0 ? ret : 0; +} + +static int si1145_measure(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + struct si1145_data *data = iio_priv(indio_dev); + u8 cmd; + int ret; + + ret = si1145_set_chlist(indio_dev, BIT(chan->scan_index)); + if (ret < 0) + return ret; + + cmd = (chan->type == IIO_PROXIMITY) ? SI1145_CMD_PS_FORCE : + SI1145_CMD_ALS_FORCE; + ret = si1145_command(data, cmd); + if (ret < 0 && ret != -EOVERFLOW) + return ret; + + return i2c_smbus_read_word_data(data->client, chan->address); +} + +/* + * Conversion between iio scale and ADC_GAIN values + * These could be further adjusted but proximity/intensity are dimensionless + */ +static const int si1145_proximity_scale_available[] = { + 128, 64, 32, 16, 8, 4}; +static const int si1145_intensity_scale_available[] = { + 128, 64, 32, 16, 8, 4, 2, 1}; +static IIO_CONST_ATTR(in_proximity_scale_available, + "128 64 32 16 8 4"); +static IIO_CONST_ATTR(in_intensity_scale_available, + "128 64 32 16 8 4 2 1"); +static IIO_CONST_ATTR(in_intensity_ir_scale_available, + "128 64 32 16 8 4 2 1"); + +static int si1145_scale_from_adcgain(int regval) +{ + return 128 >> regval; +} + +static int si1145_proximity_adcgain_from_scale(int val, int val2) +{ + val = find_closest_descending(val, si1145_proximity_scale_available, + ARRAY_SIZE(si1145_proximity_scale_available)); + if (val < 0 || val > 5 || val2 != 0) + return -EINVAL; + + return val; +} + +static int si1145_intensity_adcgain_from_scale(int val, int val2) +{ + val = find_closest_descending(val, si1145_intensity_scale_available, + ARRAY_SIZE(si1145_intensity_scale_available)); + if (val < 0 || val > 7 || val2 != 0) + return -EINVAL; + + return val; +} + +static int si1145_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct si1145_data *data = iio_priv(indio_dev); + int ret; + u8 reg; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_INTENSITY: + case IIO_PROXIMITY: + case IIO_VOLTAGE: + case IIO_TEMP: + case IIO_UVINDEX: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = si1145_measure(indio_dev, chan); + iio_device_release_direct(indio_dev); + + if (ret < 0) + return ret; + + *val = ret; + + return IIO_VAL_INT; + case IIO_CURRENT: + ret = i2c_smbus_read_byte_data(data->client, + SI1145_PS_LED_REG(chan->channel)); + if (ret < 0) + return ret; + + *val = (ret >> SI1145_PS_LED_SHIFT(chan->channel)) + & 0x0f; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_PROXIMITY: + reg = SI1145_PARAM_PS_ADC_GAIN; + break; + case IIO_INTENSITY: + if (chan->channel2 == IIO_MOD_LIGHT_IR) + reg = SI1145_PARAM_ALSIR_ADC_GAIN; + else + reg = SI1145_PARAM_ALSVIS_ADC_GAIN; + break; + case IIO_TEMP: + *val = 28; + *val2 = 571429; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_UVINDEX: + *val = 0; + *val2 = 10000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + + ret = si1145_param_query(data, reg); + if (ret < 0) + return ret; + + *val = si1145_scale_from_adcgain(ret & 0x07); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + /* + * -ADC offset - ADC counts @ 25°C - + * 35 * ADC counts / °C + */ + *val = -256 - 11136 + 25 * 35; + return IIO_VAL_INT; + default: + /* + * All ADC measurements have are by default offset + * by -256 + * See AN498 5.6.3 + */ + ret = si1145_param_query(data, SI1145_PARAM_ADC_OFFSET); + if (ret < 0) + return ret; + *val = -si1145_uncompress(ret); + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_SAMP_FREQ: + return si1145_read_samp_freq(data, val, val2); + default: + return -EINVAL; + } +} + +static int si1145_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct si1145_data *data = iio_priv(indio_dev); + u8 reg1, reg2, shift; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_PROXIMITY: + val = si1145_proximity_adcgain_from_scale(val, val2); + if (val < 0) + return val; + reg1 = SI1145_PARAM_PS_ADC_GAIN; + reg2 = SI1145_PARAM_PS_ADC_COUNTER; + break; + case IIO_INTENSITY: + val = si1145_intensity_adcgain_from_scale(val, val2); + if (val < 0) + return val; + if (chan->channel2 == IIO_MOD_LIGHT_IR) { + reg1 = SI1145_PARAM_ALSIR_ADC_GAIN; + reg2 = SI1145_PARAM_ALSIR_ADC_COUNTER; + } else { + reg1 = SI1145_PARAM_ALSVIS_ADC_GAIN; + reg2 = SI1145_PARAM_ALSVIS_ADC_COUNTER; + } + break; + default: + return -EINVAL; + } + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = si1145_param_set(data, reg1, val); + if (ret < 0) { + iio_device_release_direct(indio_dev); + return ret; + } + /* Set recovery period to one's complement of gain */ + ret = si1145_param_set(data, reg2, (~val & 0x07) << 4); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_RAW: + if (chan->type != IIO_CURRENT) + return -EINVAL; + + if (val < 0 || val > 15 || val2 != 0) + return -EINVAL; + + reg1 = SI1145_PS_LED_REG(chan->channel); + shift = SI1145_PS_LED_SHIFT(chan->channel); + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = i2c_smbus_read_byte_data(data->client, reg1); + if (ret < 0) { + iio_device_release_direct(indio_dev); + return ret; + } + ret = i2c_smbus_write_byte_data(data->client, reg1, + (ret & ~(0x0f << shift)) | + ((val & 0x0f) << shift)); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + return si1145_store_samp_freq(data, val); + default: + return -EINVAL; + } +} + +#define SI1145_ST { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ +} + +#define SI1145_INTENSITY_CHANNEL(_si) { \ + .type = IIO_INTENSITY, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_type = SI1145_ST, \ + .scan_index = _si, \ + .address = SI1145_REG_ALSVIS_DATA, \ +} + +#define SI1145_INTENSITY_IR_CHANNEL(_si) { \ + .type = IIO_INTENSITY, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .modified = 1, \ + .channel2 = IIO_MOD_LIGHT_IR, \ + .scan_type = SI1145_ST, \ + .scan_index = _si, \ + .address = SI1145_REG_ALSIR_DATA, \ +} + +#define SI1145_TEMP_CHANNEL(_si) { \ + .type = IIO_TEMP, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_type = SI1145_ST, \ + .scan_index = _si, \ + .address = SI1145_REG_AUX_DATA, \ +} + +#define SI1145_UV_CHANNEL(_si) { \ + .type = IIO_UVINDEX, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_type = SI1145_ST, \ + .scan_index = _si, \ + .address = SI1145_REG_AUX_DATA, \ +} + +#define SI1145_PROXIMITY_CHANNEL(_si, _ch) { \ + .type = IIO_PROXIMITY, \ + .indexed = 1, \ + .channel = _ch, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_type = SI1145_ST, \ + .scan_index = _si, \ + .address = SI1145_REG_PS1_DATA + _ch * 2, \ +} + +#define SI1145_VOLTAGE_CHANNEL(_si) { \ + .type = IIO_VOLTAGE, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_type = SI1145_ST, \ + .scan_index = _si, \ + .address = SI1145_REG_AUX_DATA, \ +} + +#define SI1145_CURRENT_CHANNEL(_ch) { \ + .type = IIO_CURRENT, \ + .indexed = 1, \ + .channel = _ch, \ + .output = 1, \ + .scan_index = -1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ +} + +static const struct iio_chan_spec si1132_channels[] = { + SI1145_INTENSITY_CHANNEL(0), + SI1145_INTENSITY_IR_CHANNEL(1), + SI1145_TEMP_CHANNEL(2), + SI1145_VOLTAGE_CHANNEL(3), + SI1145_UV_CHANNEL(4), + IIO_CHAN_SOFT_TIMESTAMP(6), +}; + +static const struct iio_chan_spec si1141_channels[] = { + SI1145_INTENSITY_CHANNEL(0), + SI1145_INTENSITY_IR_CHANNEL(1), + SI1145_PROXIMITY_CHANNEL(2, 0), + SI1145_TEMP_CHANNEL(3), + SI1145_VOLTAGE_CHANNEL(4), + IIO_CHAN_SOFT_TIMESTAMP(5), + SI1145_CURRENT_CHANNEL(0), +}; + +static const struct iio_chan_spec si1142_channels[] = { + SI1145_INTENSITY_CHANNEL(0), + SI1145_INTENSITY_IR_CHANNEL(1), + SI1145_PROXIMITY_CHANNEL(2, 0), + SI1145_PROXIMITY_CHANNEL(3, 1), + SI1145_TEMP_CHANNEL(4), + SI1145_VOLTAGE_CHANNEL(5), + IIO_CHAN_SOFT_TIMESTAMP(6), + SI1145_CURRENT_CHANNEL(0), + SI1145_CURRENT_CHANNEL(1), +}; + +static const struct iio_chan_spec si1143_channels[] = { + SI1145_INTENSITY_CHANNEL(0), + SI1145_INTENSITY_IR_CHANNEL(1), + SI1145_PROXIMITY_CHANNEL(2, 0), + SI1145_PROXIMITY_CHANNEL(3, 1), + SI1145_PROXIMITY_CHANNEL(4, 2), + SI1145_TEMP_CHANNEL(5), + SI1145_VOLTAGE_CHANNEL(6), + IIO_CHAN_SOFT_TIMESTAMP(7), + SI1145_CURRENT_CHANNEL(0), + SI1145_CURRENT_CHANNEL(1), + SI1145_CURRENT_CHANNEL(2), +}; + +static const struct iio_chan_spec si1145_channels[] = { + SI1145_INTENSITY_CHANNEL(0), + SI1145_INTENSITY_IR_CHANNEL(1), + SI1145_PROXIMITY_CHANNEL(2, 0), + SI1145_TEMP_CHANNEL(3), + SI1145_VOLTAGE_CHANNEL(4), + SI1145_UV_CHANNEL(5), + IIO_CHAN_SOFT_TIMESTAMP(6), + SI1145_CURRENT_CHANNEL(0), +}; + +static const struct iio_chan_spec si1146_channels[] = { + SI1145_INTENSITY_CHANNEL(0), + SI1145_INTENSITY_IR_CHANNEL(1), + SI1145_TEMP_CHANNEL(2), + SI1145_VOLTAGE_CHANNEL(3), + SI1145_UV_CHANNEL(4), + SI1145_PROXIMITY_CHANNEL(5, 0), + SI1145_PROXIMITY_CHANNEL(6, 1), + IIO_CHAN_SOFT_TIMESTAMP(7), + SI1145_CURRENT_CHANNEL(0), + SI1145_CURRENT_CHANNEL(1), +}; + +static const struct iio_chan_spec si1147_channels[] = { + SI1145_INTENSITY_CHANNEL(0), + SI1145_INTENSITY_IR_CHANNEL(1), + SI1145_PROXIMITY_CHANNEL(2, 0), + SI1145_PROXIMITY_CHANNEL(3, 1), + SI1145_PROXIMITY_CHANNEL(4, 2), + SI1145_TEMP_CHANNEL(5), + SI1145_VOLTAGE_CHANNEL(6), + SI1145_UV_CHANNEL(7), + IIO_CHAN_SOFT_TIMESTAMP(8), + SI1145_CURRENT_CHANNEL(0), + SI1145_CURRENT_CHANNEL(1), + SI1145_CURRENT_CHANNEL(2), +}; + +static struct attribute *si1132_attributes[] = { + &iio_const_attr_in_intensity_scale_available.dev_attr.attr, + &iio_const_attr_in_intensity_ir_scale_available.dev_attr.attr, + NULL, +}; + +static struct attribute *si114x_attributes[] = { + &iio_const_attr_in_intensity_scale_available.dev_attr.attr, + &iio_const_attr_in_intensity_ir_scale_available.dev_attr.attr, + &iio_const_attr_in_proximity_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group si1132_attribute_group = { + .attrs = si1132_attributes, +}; + +static const struct attribute_group si114x_attribute_group = { + .attrs = si114x_attributes, +}; + + +static const struct iio_info si1132_info = { + .read_raw = si1145_read_raw, + .write_raw = si1145_write_raw, + .attrs = &si1132_attribute_group, +}; + +static const struct iio_info si114x_info = { + .read_raw = si1145_read_raw, + .write_raw = si1145_write_raw, + .attrs = &si114x_attribute_group, +}; + +#define SI1145_PART(id, iio_info, chans, leds, uncompressed_meas_rate) \ + {id, iio_info, chans, ARRAY_SIZE(chans), leds, uncompressed_meas_rate} + +static const struct si1145_part_info si1145_part_info[] = { + [SI1132] = SI1145_PART(0x32, &si1132_info, si1132_channels, 0, true), + [SI1141] = SI1145_PART(0x41, &si114x_info, si1141_channels, 1, false), + [SI1142] = SI1145_PART(0x42, &si114x_info, si1142_channels, 2, false), + [SI1143] = SI1145_PART(0x43, &si114x_info, si1143_channels, 3, false), + [SI1145] = SI1145_PART(0x45, &si114x_info, si1145_channels, 1, true), + [SI1146] = SI1145_PART(0x46, &si114x_info, si1146_channels, 2, true), + [SI1147] = SI1145_PART(0x47, &si114x_info, si1147_channels, 3, true), +}; + +static int si1145_initialize(struct si1145_data *data) +{ + struct i2c_client *client = data->client; + int ret; + + ret = i2c_smbus_write_byte_data(client, SI1145_REG_COMMAND, + SI1145_CMD_RESET); + if (ret < 0) + return ret; + msleep(SI1145_COMMAND_TIMEOUT_MS); + + /* Hardware key, magic value */ + ret = i2c_smbus_write_byte_data(client, SI1145_REG_HW_KEY, 0x17); + if (ret < 0) + return ret; + msleep(SI1145_COMMAND_TIMEOUT_MS); + + /* Turn off autonomous mode */ + ret = si1145_set_meas_rate(data, 0); + if (ret < 0) + return ret; + + /* Initialize sampling freq to 10 Hz */ + ret = si1145_store_samp_freq(data, 10); + if (ret < 0) + return ret; + + /* Set LED currents to 45 mA; have 4 bits, see Table 2 in datasheet */ + switch (data->part_info->num_leds) { + case 3: + ret = i2c_smbus_write_byte_data(client, + SI1145_REG_PS_LED3, + SI1145_LED_CURRENT_45mA); + if (ret < 0) + return ret; + fallthrough; + case 2: + ret = i2c_smbus_write_byte_data(client, + SI1145_REG_PS_LED21, + (SI1145_LED_CURRENT_45mA << 4) | + SI1145_LED_CURRENT_45mA); + break; + case 1: + ret = i2c_smbus_write_byte_data(client, + SI1145_REG_PS_LED21, + SI1145_LED_CURRENT_45mA); + break; + default: + ret = 0; + break; + } + if (ret < 0) + return ret; + + /* Set normal proximity measurement mode */ + ret = si1145_param_set(data, SI1145_PARAM_PS_ADC_MISC, + SI1145_PS_ADC_MODE_NORMAL); + if (ret < 0) + return ret; + + ret = si1145_param_set(data, SI1145_PARAM_PS_ADC_GAIN, 0x01); + if (ret < 0) + return ret; + + /* ADC_COUNTER should be one complement of ADC_GAIN */ + ret = si1145_param_set(data, SI1145_PARAM_PS_ADC_COUNTER, 0x06 << 4); + if (ret < 0) + return ret; + + /* Set ALS visible measurement mode */ + ret = si1145_param_set(data, SI1145_PARAM_ALSVIS_ADC_MISC, + SI1145_ADC_MISC_RANGE); + if (ret < 0) + return ret; + + ret = si1145_param_set(data, SI1145_PARAM_ALSVIS_ADC_GAIN, 0x03); + if (ret < 0) + return ret; + + ret = si1145_param_set(data, SI1145_PARAM_ALSVIS_ADC_COUNTER, + 0x04 << 4); + if (ret < 0) + return ret; + + /* Set ALS IR measurement mode */ + ret = si1145_param_set(data, SI1145_PARAM_ALSIR_ADC_MISC, + SI1145_ADC_MISC_RANGE); + if (ret < 0) + return ret; + + ret = si1145_param_set(data, SI1145_PARAM_ALSIR_ADC_GAIN, 0x01); + if (ret < 0) + return ret; + + ret = si1145_param_set(data, SI1145_PARAM_ALSIR_ADC_COUNTER, + 0x06 << 4); + if (ret < 0) + return ret; + + /* + * Initialize UCOEF to default values in datasheet + * These registers are normally zero on reset + */ + if (data->part_info == &si1145_part_info[SI1132] || + data->part_info == &si1145_part_info[SI1145] || + data->part_info == &si1145_part_info[SI1146] || + data->part_info == &si1145_part_info[SI1147]) { + ret = i2c_smbus_write_byte_data(data->client, + SI1145_REG_UCOEF1, + SI1145_UCOEF1_DEFAULT); + if (ret < 0) + return ret; + ret = i2c_smbus_write_byte_data(data->client, + SI1145_REG_UCOEF2, SI1145_UCOEF2_DEFAULT); + if (ret < 0) + return ret; + ret = i2c_smbus_write_byte_data(data->client, + SI1145_REG_UCOEF3, SI1145_UCOEF3_DEFAULT); + if (ret < 0) + return ret; + ret = i2c_smbus_write_byte_data(data->client, + SI1145_REG_UCOEF4, SI1145_UCOEF4_DEFAULT); + if (ret < 0) + return ret; + } + + return 0; +} + +/* + * Program the channels we want to measure with CMD_PSALS_AUTO. No need for + * _postdisable as we stop with CMD_PSALS_PAUSE; single measurement (direct) + * mode reprograms the channels list anyway... + */ +static int si1145_buffer_preenable(struct iio_dev *indio_dev) +{ + struct si1145_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->lock); + ret = si1145_set_chlist(indio_dev, *indio_dev->active_scan_mask); + mutex_unlock(&data->lock); + + return ret; +} + +static bool si1145_validate_scan_mask(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct si1145_data *data = iio_priv(indio_dev); + unsigned int count = 0; + int i; + + /* Check that at most one AUX channel is enabled */ + for_each_set_bit(i, scan_mask, data->part_info->num_channels) { + if (indio_dev->channels[i].address == SI1145_REG_AUX_DATA) + count++; + } + + return count <= 1; +} + +static const struct iio_buffer_setup_ops si1145_buffer_setup_ops = { + .preenable = si1145_buffer_preenable, + .validate_scan_mask = si1145_validate_scan_mask, +}; + +/* + * si1145_trigger_set_state() - Set trigger state + * + * When not using triggers interrupts are disabled and measurement rate is + * set to zero in order to minimize power consumption. + */ +static int si1145_trigger_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct si1145_data *data = iio_priv(indio_dev); + int err = 0, ret; + + mutex_lock(&data->lock); + + if (state) { + data->autonomous = true; + err = i2c_smbus_write_byte_data(data->client, + SI1145_REG_INT_CFG, SI1145_INT_CFG_OE); + if (err < 0) + goto disable; + err = i2c_smbus_write_byte_data(data->client, + SI1145_REG_IRQ_ENABLE, SI1145_MASK_ALL_IE); + if (err < 0) + goto disable; + err = si1145_set_meas_rate(data, data->meas_rate); + if (err < 0) + goto disable; + err = si1145_command(data, SI1145_CMD_PSALS_AUTO); + if (err < 0) + goto disable; + } else { +disable: + /* Disable as much as possible skipping errors */ + ret = si1145_command(data, SI1145_CMD_PSALS_PAUSE); + if (ret < 0 && !err) + err = ret; + ret = si1145_set_meas_rate(data, 0); + if (ret < 0 && !err) + err = ret; + ret = i2c_smbus_write_byte_data(data->client, + SI1145_REG_IRQ_ENABLE, 0); + if (ret < 0 && !err) + err = ret; + ret = i2c_smbus_write_byte_data(data->client, + SI1145_REG_INT_CFG, 0); + if (ret < 0 && !err) + err = ret; + data->autonomous = false; + } + + mutex_unlock(&data->lock); + return err; +} + +static const struct iio_trigger_ops si1145_trigger_ops = { + .set_trigger_state = si1145_trigger_set_state, +}; + +static int si1145_probe_trigger(struct iio_dev *indio_dev) +{ + struct si1145_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + struct iio_trigger *trig; + int ret; + + trig = devm_iio_trigger_alloc(&client->dev, + "%s-dev%d", indio_dev->name, iio_device_id(indio_dev)); + if (!trig) + return -ENOMEM; + + trig->ops = &si1145_trigger_ops; + iio_trigger_set_drvdata(trig, indio_dev); + + ret = devm_request_irq(&client->dev, client->irq, + iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_FALLING | IRQF_NO_THREAD, + "si1145_irq", + trig); + if (ret) + return ret; + + ret = devm_iio_trigger_register(&client->dev, trig); + if (ret) + return ret; + + data->trig = trig; + indio_dev->trig = iio_trigger_get(data->trig); + + return 0; +} + +static int si1145_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct si1145_data *data; + struct iio_dev *indio_dev; + u8 part_id, rev_id, seq_id; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->part_info = &si1145_part_info[id->driver_data]; + + part_id = ret = i2c_smbus_read_byte_data(data->client, + SI1145_REG_PART_ID); + if (ret < 0) + return ret; + rev_id = ret = i2c_smbus_read_byte_data(data->client, + SI1145_REG_REV_ID); + if (ret < 0) + return ret; + seq_id = ret = i2c_smbus_read_byte_data(data->client, + SI1145_REG_SEQ_ID); + if (ret < 0) + return ret; + dev_info(&client->dev, "device ID part 0x%02x rev 0x%02x seq 0x%02x\n", + part_id, rev_id, seq_id); + if (part_id != data->part_info->part) { + dev_err(&client->dev, "part ID mismatch got 0x%02x, expected 0x%02x\n", + part_id, data->part_info->part); + return -ENODEV; + } + + indio_dev->name = id->name; + indio_dev->channels = data->part_info->channels; + indio_dev->num_channels = data->part_info->num_channels; + indio_dev->info = data->part_info->iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + mutex_init(&data->lock); + mutex_init(&data->cmdlock); + + ret = si1145_initialize(data); + if (ret < 0) + return ret; + + ret = devm_iio_triggered_buffer_setup(&client->dev, + indio_dev, NULL, + si1145_trigger_handler, &si1145_buffer_setup_ops); + if (ret < 0) + return ret; + + if (client->irq) { + ret = si1145_probe_trigger(indio_dev); + if (ret < 0) + return ret; + } else { + dev_info(&client->dev, "no irq, using polling\n"); + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id si1145_ids[] = { + { .name = "si1132", .driver_data = SI1132 }, + { .name = "si1141", .driver_data = SI1141 }, + { .name = "si1142", .driver_data = SI1142 }, + { .name = "si1143", .driver_data = SI1143 }, + { .name = "si1145", .driver_data = SI1145 }, + { .name = "si1146", .driver_data = SI1146 }, + { .name = "si1147", .driver_data = SI1147 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, si1145_ids); + +static struct i2c_driver si1145_driver = { + .driver = { + .name = "si1145", + }, + .probe = si1145_probe, + .id_table = si1145_ids, +}; + +module_i2c_driver(si1145_driver); + +MODULE_AUTHOR("Peter Meerwald-Stadler "); +MODULE_DESCRIPTION("Silabs SI1132 and SI1141/2/3/5/6/7 proximity, ambient light and UV index sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/st_uvis25.h b/drivers/iio/light/st_uvis25.h new file mode 100644 index 000000000..78bc56aad --- /dev/null +++ b/drivers/iio/light/st_uvis25.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * STMicroelectronics uvis25 sensor driver + * + * Copyright 2017 STMicroelectronics Inc. + * + * Lorenzo Bianconi + */ + +#ifndef ST_UVIS25_H +#define ST_UVIS25_H + +#define ST_UVIS25_DEV_NAME "uvis25" + +#include + +/** + * struct st_uvis25_hw - ST UVIS25 sensor instance + * @regmap: Register map of the device. + * @trig: The trigger in use by the driver. + * @enabled: Status of the sensor (false->off, true->on). + * @irq: Device interrupt line (I2C or SPI). + */ +struct st_uvis25_hw { + struct regmap *regmap; + + struct iio_trigger *trig; + bool enabled; + int irq; +}; + +extern const struct dev_pm_ops st_uvis25_pm_ops; + +int st_uvis25_probe(struct device *dev, int irq, struct regmap *regmap); + +#endif /* ST_UVIS25_H */ diff --git a/drivers/iio/light/st_uvis25_core.c b/drivers/iio/light/st_uvis25_core.c new file mode 100644 index 000000000..94d5261b2 --- /dev/null +++ b/drivers/iio/light/st_uvis25_core.c @@ -0,0 +1,354 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics uvis25 sensor driver + * + * Copyright 2017 STMicroelectronics Inc. + * + * Lorenzo Bianconi + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "st_uvis25.h" + +#define ST_UVIS25_REG_WHOAMI_ADDR 0x0f +#define ST_UVIS25_REG_WHOAMI_VAL 0xca +#define ST_UVIS25_REG_CTRL1_ADDR 0x20 +#define ST_UVIS25_REG_ODR_MASK BIT(0) +#define ST_UVIS25_REG_BDU_MASK BIT(1) +#define ST_UVIS25_REG_CTRL2_ADDR 0x21 +#define ST_UVIS25_REG_BOOT_MASK BIT(7) +#define ST_UVIS25_REG_CTRL3_ADDR 0x22 +#define ST_UVIS25_REG_HL_MASK BIT(7) +#define ST_UVIS25_REG_STATUS_ADDR 0x27 +#define ST_UVIS25_REG_UV_DA_MASK BIT(0) +#define ST_UVIS25_REG_OUT_ADDR 0x28 + +static const struct iio_chan_spec st_uvis25_channels[] = { + { + .type = IIO_UVINDEX, + .address = ST_UVIS25_REG_OUT_ADDR, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 8, + .storagebits = 8, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static int st_uvis25_check_whoami(struct st_uvis25_hw *hw) +{ + int err, data; + + err = regmap_read(hw->regmap, ST_UVIS25_REG_WHOAMI_ADDR, &data); + if (err < 0) { + dev_err(regmap_get_device(hw->regmap), + "failed to read whoami register\n"); + return err; + } + + if (data != ST_UVIS25_REG_WHOAMI_VAL) { + dev_err(regmap_get_device(hw->regmap), + "wrong whoami {%02x vs %02x}\n", + data, ST_UVIS25_REG_WHOAMI_VAL); + return -ENODEV; + } + + return 0; +} + +static int st_uvis25_set_enable(struct st_uvis25_hw *hw, bool enable) +{ + int err; + + err = regmap_update_bits(hw->regmap, ST_UVIS25_REG_CTRL1_ADDR, + ST_UVIS25_REG_ODR_MASK, enable); + if (err < 0) + return err; + + hw->enabled = enable; + + return 0; +} + +static int st_uvis25_read_oneshot(struct st_uvis25_hw *hw, u8 addr, int *val) +{ + int err; + + err = st_uvis25_set_enable(hw, true); + if (err < 0) + return err; + + msleep(1500); + + /* + * in order to avoid possible race conditions with interrupt + * generation, disable the sensor first and then poll output + * register. That sequence guarantees the interrupt will be reset + * when irq line is unmasked + */ + err = st_uvis25_set_enable(hw, false); + if (err < 0) + return err; + + err = regmap_read(hw->regmap, addr, val); + + return err < 0 ? err : IIO_VAL_INT; +} + +static int st_uvis25_read_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *ch, + int *val, int *val2, long mask) +{ + int ret; + + if (!iio_device_claim_direct(iio_dev)) + return -EBUSY; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: { + struct st_uvis25_hw *hw = iio_priv(iio_dev); + + /* + * mask irq line during oneshot read since the sensor + * does not export the capability to disable data-ready line + * in the register map and it is enabled by default. + * If the line is unmasked during read_raw() it will be set + * active and never reset since the trigger is disabled + */ + if (hw->irq > 0) + disable_irq(hw->irq); + ret = st_uvis25_read_oneshot(hw, ch->address, val); + if (hw->irq > 0) + enable_irq(hw->irq); + break; + } + default: + ret = -EINVAL; + break; + } + + iio_device_release_direct(iio_dev); + + return ret; +} + +static irqreturn_t st_uvis25_trigger_handler_thread(int irq, void *private) +{ + struct st_uvis25_hw *hw = private; + int err, status; + + err = regmap_read(hw->regmap, ST_UVIS25_REG_STATUS_ADDR, &status); + if (err < 0) + return IRQ_HANDLED; + + if (!(status & ST_UVIS25_REG_UV_DA_MASK)) + return IRQ_NONE; + + iio_trigger_poll_nested(hw->trig); + + return IRQ_HANDLED; +} + +static int st_uvis25_allocate_trigger(struct iio_dev *iio_dev) +{ + struct st_uvis25_hw *hw = iio_priv(iio_dev); + struct device *dev = regmap_get_device(hw->regmap); + bool irq_active_low = false; + unsigned long irq_type; + int err; + + irq_type = irq_get_trigger_type(hw->irq); + switch (irq_type) { + case IRQF_TRIGGER_HIGH: + case IRQF_TRIGGER_RISING: + break; + case IRQF_TRIGGER_LOW: + case IRQF_TRIGGER_FALLING: + irq_active_low = true; + break; + default: + dev_info(dev, "mode %lx unsupported\n", irq_type); + return -EINVAL; + } + + err = regmap_update_bits(hw->regmap, ST_UVIS25_REG_CTRL3_ADDR, + ST_UVIS25_REG_HL_MASK, irq_active_low); + if (err < 0) + return err; + + err = devm_request_threaded_irq(dev, hw->irq, NULL, + st_uvis25_trigger_handler_thread, + irq_type | IRQF_ONESHOT, + iio_dev->name, hw); + if (err) + return err; + + hw->trig = devm_iio_trigger_alloc(dev, "%s-trigger", + iio_dev->name); + if (!hw->trig) + return -ENOMEM; + + iio_trigger_set_drvdata(hw->trig, iio_dev); + + return devm_iio_trigger_register(dev, hw->trig); +} + +static int st_uvis25_buffer_preenable(struct iio_dev *iio_dev) +{ + return st_uvis25_set_enable(iio_priv(iio_dev), true); +} + +static int st_uvis25_buffer_postdisable(struct iio_dev *iio_dev) +{ + return st_uvis25_set_enable(iio_priv(iio_dev), false); +} + +static const struct iio_buffer_setup_ops st_uvis25_buffer_ops = { + .preenable = st_uvis25_buffer_preenable, + .postdisable = st_uvis25_buffer_postdisable, +}; + +static irqreturn_t st_uvis25_buffer_handler_thread(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *iio_dev = pf->indio_dev; + struct st_uvis25_hw *hw = iio_priv(iio_dev); + unsigned int val; + int err; + /* Ensure timestamp is naturally aligned */ + struct { + u8 chan; + aligned_s64 ts; + } scan = { }; + + + err = regmap_read(hw->regmap, ST_UVIS25_REG_OUT_ADDR, &val); + if (err < 0) + goto out; + + scan.chan = val; + + iio_push_to_buffers_with_ts(iio_dev, &scan, sizeof(scan), + iio_get_time_ns(iio_dev)); + +out: + iio_trigger_notify_done(hw->trig); + + return IRQ_HANDLED; +} + +static int st_uvis25_allocate_buffer(struct iio_dev *iio_dev) +{ + struct st_uvis25_hw *hw = iio_priv(iio_dev); + + return devm_iio_triggered_buffer_setup(regmap_get_device(hw->regmap), + iio_dev, NULL, + st_uvis25_buffer_handler_thread, + &st_uvis25_buffer_ops); +} + +static const struct iio_info st_uvis25_info = { + .read_raw = st_uvis25_read_raw, +}; + +static int st_uvis25_init_sensor(struct st_uvis25_hw *hw) +{ + int err; + + err = regmap_update_bits(hw->regmap, ST_UVIS25_REG_CTRL2_ADDR, + ST_UVIS25_REG_BOOT_MASK, 1); + if (err < 0) + return err; + + msleep(2000); + + return regmap_update_bits(hw->regmap, ST_UVIS25_REG_CTRL1_ADDR, + ST_UVIS25_REG_BDU_MASK, 1); +} + +int st_uvis25_probe(struct device *dev, int irq, struct regmap *regmap) +{ + struct st_uvis25_hw *hw; + struct iio_dev *iio_dev; + int err; + + iio_dev = devm_iio_device_alloc(dev, sizeof(*hw)); + if (!iio_dev) + return -ENOMEM; + + dev_set_drvdata(dev, iio_dev); + + hw = iio_priv(iio_dev); + hw->irq = irq; + hw->regmap = regmap; + + err = st_uvis25_check_whoami(hw); + if (err < 0) + return err; + + iio_dev->modes = INDIO_DIRECT_MODE; + iio_dev->channels = st_uvis25_channels; + iio_dev->num_channels = ARRAY_SIZE(st_uvis25_channels); + iio_dev->name = ST_UVIS25_DEV_NAME; + iio_dev->info = &st_uvis25_info; + + err = st_uvis25_init_sensor(hw); + if (err < 0) + return err; + + if (hw->irq > 0) { + err = st_uvis25_allocate_buffer(iio_dev); + if (err < 0) + return err; + + err = st_uvis25_allocate_trigger(iio_dev); + if (err) + return err; + } + + return devm_iio_device_register(dev, iio_dev); +} +EXPORT_SYMBOL_NS(st_uvis25_probe, "IIO_UVIS25"); + +static int st_uvis25_suspend(struct device *dev) +{ + struct iio_dev *iio_dev = dev_get_drvdata(dev); + struct st_uvis25_hw *hw = iio_priv(iio_dev); + + return regmap_clear_bits(hw->regmap, ST_UVIS25_REG_CTRL1_ADDR, + ST_UVIS25_REG_ODR_MASK); +} + +static int st_uvis25_resume(struct device *dev) +{ + struct iio_dev *iio_dev = dev_get_drvdata(dev); + struct st_uvis25_hw *hw = iio_priv(iio_dev); + + if (hw->enabled) + return regmap_update_bits(hw->regmap, ST_UVIS25_REG_CTRL1_ADDR, + ST_UVIS25_REG_ODR_MASK, 1); + + return 0; +} + +EXPORT_NS_SIMPLE_DEV_PM_OPS(st_uvis25_pm_ops, st_uvis25_suspend, st_uvis25_resume, IIO_UVIS25); + +MODULE_AUTHOR("Lorenzo Bianconi "); +MODULE_DESCRIPTION("STMicroelectronics uvis25 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/st_uvis25_i2c.c b/drivers/iio/light/st_uvis25_i2c.c new file mode 100644 index 000000000..d6b5ba139 --- /dev/null +++ b/drivers/iio/light/st_uvis25_i2c.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics uvis25 i2c driver + * + * Copyright 2017 STMicroelectronics Inc. + * + * Lorenzo Bianconi + */ + +#include +#include +#include +#include +#include + +#include "st_uvis25.h" + +#define UVIS25_I2C_AUTO_INCREMENT BIT(7) + +static const struct regmap_config st_uvis25_i2c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .write_flag_mask = UVIS25_I2C_AUTO_INCREMENT, + .read_flag_mask = UVIS25_I2C_AUTO_INCREMENT, +}; + +static int st_uvis25_i2c_probe(struct i2c_client *client) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, &st_uvis25_i2c_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Failed to register i2c regmap %ld\n", + PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + return st_uvis25_probe(&client->dev, client->irq, regmap); +} + +static const struct of_device_id st_uvis25_i2c_of_match[] = { + { .compatible = "st,uvis25", }, + { } +}; +MODULE_DEVICE_TABLE(of, st_uvis25_i2c_of_match); + +static const struct i2c_device_id st_uvis25_i2c_id_table[] = { + { .name = ST_UVIS25_DEV_NAME }, + { } +}; +MODULE_DEVICE_TABLE(i2c, st_uvis25_i2c_id_table); + +static struct i2c_driver st_uvis25_driver = { + .driver = { + .name = "st_uvis25_i2c", + .pm = pm_sleep_ptr(&st_uvis25_pm_ops), + .of_match_table = st_uvis25_i2c_of_match, + }, + .probe = st_uvis25_i2c_probe, + .id_table = st_uvis25_i2c_id_table, +}; +module_i2c_driver(st_uvis25_driver); + +MODULE_AUTHOR("Lorenzo Bianconi "); +MODULE_DESCRIPTION("STMicroelectronics uvis25 i2c driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_UVIS25"); diff --git a/drivers/iio/light/st_uvis25_spi.c b/drivers/iio/light/st_uvis25_spi.c new file mode 100644 index 000000000..0f78a0abf --- /dev/null +++ b/drivers/iio/light/st_uvis25_spi.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics uvis25 spi driver + * + * Copyright 2017 STMicroelectronics Inc. + * + * Lorenzo Bianconi + */ + +#include +#include +#include +#include +#include + +#include "st_uvis25.h" + +#define UVIS25_SENSORS_SPI_READ BIT(7) +#define UVIS25_SPI_AUTO_INCREMENT BIT(6) + +static const struct regmap_config st_uvis25_spi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .read_flag_mask = UVIS25_SENSORS_SPI_READ | UVIS25_SPI_AUTO_INCREMENT, + .write_flag_mask = UVIS25_SPI_AUTO_INCREMENT, +}; + +static int st_uvis25_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_spi(spi, &st_uvis25_spi_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "Failed to register spi regmap %ld\n", + PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + return st_uvis25_probe(&spi->dev, spi->irq, regmap); +} + +static const struct of_device_id st_uvis25_spi_of_match[] = { + { .compatible = "st,uvis25", }, + { } +}; +MODULE_DEVICE_TABLE(of, st_uvis25_spi_of_match); + +static const struct spi_device_id st_uvis25_spi_id_table[] = { + { .name = ST_UVIS25_DEV_NAME }, + { } +}; +MODULE_DEVICE_TABLE(spi, st_uvis25_spi_id_table); + +static struct spi_driver st_uvis25_driver = { + .driver = { + .name = "st_uvis25_spi", + .pm = pm_sleep_ptr(&st_uvis25_pm_ops), + .of_match_table = st_uvis25_spi_of_match, + }, + .probe = st_uvis25_spi_probe, + .id_table = st_uvis25_spi_id_table, +}; +module_spi_driver(st_uvis25_driver); + +MODULE_AUTHOR("Lorenzo Bianconi "); +MODULE_DESCRIPTION("STMicroelectronics uvis25 spi driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_UVIS25"); diff --git a/drivers/iio/light/stk3310.c b/drivers/iio/light/stk3310.c new file mode 100644 index 000000000..7c8a1d2b2 --- /dev/null +++ b/drivers/iio/light/stk3310.c @@ -0,0 +1,811 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Sensortek STK3310/STK3311 Ambient Light and Proximity Sensor + * + * Copyright (c) 2015, Intel Corporation. + * + * IIO driver for STK3310/STK3311. 7-bit I2C address: 0x48. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define STK3310_REG_STATE 0x00 +#define STK3310_REG_PSCTRL 0x01 +#define STK3310_REG_ALSCTRL 0x02 +#define STK3310_REG_INT 0x04 +#define STK3310_REG_THDH_PS 0x06 +#define STK3310_REG_THDL_PS 0x08 +#define STK3310_REG_FLAG 0x10 +#define STK3310_REG_PS_DATA_MSB 0x11 +#define STK3310_REG_PS_DATA_LSB 0x12 +#define STK3310_REG_ALS_DATA_MSB 0x13 +#define STK3310_REG_ALS_DATA_LSB 0x14 +#define STK3310_REG_ID 0x3E +#define STK3310_MAX_REG 0x80 + +#define STK3310_STATE_EN_PS BIT(0) +#define STK3310_STATE_EN_ALS BIT(1) +#define STK3310_STATE_STANDBY 0x00 + +#define STK3013_CHIP_ID_VAL 0x31 +#define STK3310_CHIP_ID_VAL 0x13 +#define STK3311_CHIP_ID_VAL 0x1D +#define STK3311A_CHIP_ID_VAL 0x15 +#define STK3311S34_CHIP_ID_VAL 0x1E +#define STK3311X_CHIP_ID_VAL 0x12 +#define STK3335_CHIP_ID_VAL 0x51 +#define STK3310_PSINT_EN 0x01 +#define STK3310_PS_MAX_VAL 0xFFFF + +#define STK3310_DRIVER_NAME "stk3310" + +#define STK3310_SCALE_AVAILABLE "6.4 1.6 0.4 0.1" + +#define STK3310_IT_AVAILABLE \ + "0.000185 0.000370 0.000741 0.001480 0.002960 0.005920 0.011840 " \ + "0.023680 0.047360 0.094720 0.189440 0.378880 0.757760 1.515520 " \ + "3.031040 6.062080" + +#define STK3310_REGFIELD(name) \ + do { \ + data->reg_##name = \ + devm_regmap_field_alloc(&client->dev, regmap, \ + stk3310_reg_field_##name); \ + if (IS_ERR(data->reg_##name)) { \ + dev_err(&client->dev, "reg field alloc failed.\n"); \ + return PTR_ERR(data->reg_##name); \ + } \ + } while (0) + +static const struct reg_field stk3310_reg_field_state = + REG_FIELD(STK3310_REG_STATE, 0, 2); +static const struct reg_field stk3310_reg_field_als_gain = + REG_FIELD(STK3310_REG_ALSCTRL, 4, 5); +static const struct reg_field stk3310_reg_field_ps_gain = + REG_FIELD(STK3310_REG_PSCTRL, 4, 5); +static const struct reg_field stk3310_reg_field_als_it = + REG_FIELD(STK3310_REG_ALSCTRL, 0, 3); +static const struct reg_field stk3310_reg_field_ps_it = + REG_FIELD(STK3310_REG_PSCTRL, 0, 3); +static const struct reg_field stk3310_reg_field_int_ps = + REG_FIELD(STK3310_REG_INT, 0, 2); +static const struct reg_field stk3310_reg_field_flag_psint = + REG_FIELD(STK3310_REG_FLAG, 4, 4); +static const struct reg_field stk3310_reg_field_flag_nf = + REG_FIELD(STK3310_REG_FLAG, 0, 0); + +static const u8 stk3310_chip_ids[] = { + STK3013_CHIP_ID_VAL, + STK3310_CHIP_ID_VAL, + STK3311A_CHIP_ID_VAL, + STK3311S34_CHIP_ID_VAL, + STK3311X_CHIP_ID_VAL, + STK3311_CHIP_ID_VAL, + STK3335_CHIP_ID_VAL, +}; + +/* Estimate maximum proximity values with regard to measurement scale. */ +static const int stk3310_ps_max[4] = { + STK3310_PS_MAX_VAL / 640, + STK3310_PS_MAX_VAL / 160, + STK3310_PS_MAX_VAL / 40, + STK3310_PS_MAX_VAL / 10 +}; + +static const int stk3310_scale_table[][2] = { + {6, 400000}, {1, 600000}, {0, 400000}, {0, 100000} +}; + +/* Integration time in seconds, microseconds */ +static const int stk3310_it_table[][2] = { + {0, 185}, {0, 370}, {0, 741}, {0, 1480}, + {0, 2960}, {0, 5920}, {0, 11840}, {0, 23680}, + {0, 47360}, {0, 94720}, {0, 189440}, {0, 378880}, + {0, 757760}, {1, 515520}, {3, 31040}, {6, 62080}, +}; + +struct stk3310_data { + struct i2c_client *client; + struct mutex lock; + u64 timestamp; + struct regmap *regmap; + struct regmap_field *reg_state; + struct regmap_field *reg_als_gain; + struct regmap_field *reg_ps_gain; + struct regmap_field *reg_als_it; + struct regmap_field *reg_ps_it; + struct regmap_field *reg_int_ps; + struct regmap_field *reg_flag_psint; + struct regmap_field *reg_flag_nf; + u32 ps_thdl; + u32 ps_thdh; + u32 ps_near_level; + bool als_enabled; + bool ps_enabled; + bool ps_int_enabled; +}; + +static const struct iio_event_spec stk3310_events[] = { + /* Proximity event */ + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + /* Out-of-proximity event */ + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static ssize_t stk3310_read_near_level(struct iio_dev *indio_dev, + uintptr_t priv, + const struct iio_chan_spec *chan, + char *buf) +{ + struct stk3310_data *data = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", data->ps_near_level); +} + +static const struct iio_chan_spec_ext_info stk3310_ext_info[] = { + { + .name = "nearlevel", + .shared = IIO_SEPARATE, + .read = stk3310_read_near_level, + }, + { } +}; + +static const struct iio_chan_spec stk3310_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME), + }, + { + .type = IIO_PROXIMITY, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME), + .event_spec = stk3310_events, + .num_event_specs = ARRAY_SIZE(stk3310_events), + .ext_info = stk3310_ext_info, + } +}; + +static IIO_CONST_ATTR(in_illuminance_scale_available, STK3310_SCALE_AVAILABLE); + +static IIO_CONST_ATTR(in_proximity_scale_available, STK3310_SCALE_AVAILABLE); + +static IIO_CONST_ATTR(in_illuminance_integration_time_available, + STK3310_IT_AVAILABLE); + +static IIO_CONST_ATTR(in_proximity_integration_time_available, + STK3310_IT_AVAILABLE); + +static struct attribute *stk3310_attributes[] = { + &iio_const_attr_in_illuminance_scale_available.dev_attr.attr, + &iio_const_attr_in_proximity_scale_available.dev_attr.attr, + &iio_const_attr_in_illuminance_integration_time_available.dev_attr.attr, + &iio_const_attr_in_proximity_integration_time_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group stk3310_attribute_group = { + .attrs = stk3310_attributes +}; + +static int stk3310_check_chip_id(const u8 chip_id) +{ + for (int i = 0; i < ARRAY_SIZE(stk3310_chip_ids); i++) { + if (chip_id == stk3310_chip_ids[i]) + return 0; + } + + return -ENODEV; +} + +static int stk3310_get_index(const int table[][2], int table_size, + int val, int val2) +{ + int i; + + for (i = 0; i < table_size; i++) { + if (val == table[i][0] && val2 == table[i][1]) + return i; + } + + return -EINVAL; +} + +static int stk3310_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + u8 reg; + __be16 buf; + int ret; + struct stk3310_data *data = iio_priv(indio_dev); + + if (info != IIO_EV_INFO_VALUE) + return -EINVAL; + + /* Only proximity interrupts are implemented at the moment. */ + if (dir == IIO_EV_DIR_RISING) + reg = STK3310_REG_THDH_PS; + else if (dir == IIO_EV_DIR_FALLING) + reg = STK3310_REG_THDL_PS; + else + return -EINVAL; + + mutex_lock(&data->lock); + ret = regmap_bulk_read(data->regmap, reg, &buf, sizeof(buf)); + mutex_unlock(&data->lock); + if (ret < 0) { + dev_err(&data->client->dev, "register read failed\n"); + return ret; + } + *val = be16_to_cpu(buf); + + return IIO_VAL_INT; +} + +static int stk3310_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + u8 reg; + __be16 buf; + int ret; + unsigned int index; + struct stk3310_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + + ret = regmap_field_read(data->reg_ps_gain, &index); + if (ret < 0) + return ret; + + if (val < 0 || val > stk3310_ps_max[index]) + return -EINVAL; + + if (dir == IIO_EV_DIR_RISING) + reg = STK3310_REG_THDH_PS; + else if (dir == IIO_EV_DIR_FALLING) + reg = STK3310_REG_THDL_PS; + else + return -EINVAL; + + buf = cpu_to_be16(val); + ret = regmap_bulk_write(data->regmap, reg, &buf, sizeof(buf)); + if (ret < 0) { + dev_err(&client->dev, "failed to set PS threshold!\n"); + return ret; + } + + if (reg == STK3310_REG_THDH_PS) + data->ps_thdh = val; + else + data->ps_thdl = val; + + return 0; +} + +static int stk3310_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + unsigned int event_val; + int ret; + struct stk3310_data *data = iio_priv(indio_dev); + + ret = regmap_field_read(data->reg_int_ps, &event_val); + if (ret < 0) + return ret; + + return event_val; +} + +static int stk3310_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + int ret; + struct stk3310_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + + /* Set INT_PS value */ + mutex_lock(&data->lock); + ret = regmap_field_write(data->reg_int_ps, state); + if (ret < 0) { + dev_err(&client->dev, "failed to set interrupt mode\n"); + mutex_unlock(&data->lock); + return ret; + } + + data->ps_int_enabled = state; + + mutex_unlock(&data->lock); + + return 0; +} + +static int stk3310_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + u8 reg; + __be16 buf; + int ret; + unsigned int index; + struct stk3310_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + + if (chan->type != IIO_LIGHT && chan->type != IIO_PROXIMITY) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_LIGHT) + reg = STK3310_REG_ALS_DATA_MSB; + else + reg = STK3310_REG_PS_DATA_MSB; + + mutex_lock(&data->lock); + ret = regmap_bulk_read(data->regmap, reg, &buf, sizeof(buf)); + if (ret < 0) { + dev_err(&client->dev, "register read failed\n"); + mutex_unlock(&data->lock); + return ret; + } + *val = be16_to_cpu(buf); + mutex_unlock(&data->lock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + if (chan->type == IIO_LIGHT) + ret = regmap_field_read(data->reg_als_it, &index); + else + ret = regmap_field_read(data->reg_ps_it, &index); + if (ret < 0) + return ret; + + *val = stk3310_it_table[index][0]; + *val2 = stk3310_it_table[index][1]; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_LIGHT) + ret = regmap_field_read(data->reg_als_gain, &index); + else + ret = regmap_field_read(data->reg_ps_gain, &index); + if (ret < 0) + return ret; + + *val = stk3310_scale_table[index][0]; + *val2 = stk3310_scale_table[index][1]; + return IIO_VAL_INT_PLUS_MICRO; + } + + return -EINVAL; +} + +static int stk3310_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret; + int index; + struct stk3310_data *data = iio_priv(indio_dev); + + if (chan->type != IIO_LIGHT && chan->type != IIO_PROXIMITY) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + index = stk3310_get_index(stk3310_it_table, + ARRAY_SIZE(stk3310_it_table), + val, val2); + if (index < 0) + return -EINVAL; + mutex_lock(&data->lock); + if (chan->type == IIO_LIGHT) + ret = regmap_field_write(data->reg_als_it, index); + else + ret = regmap_field_write(data->reg_ps_it, index); + if (ret < 0) + dev_err(&data->client->dev, + "sensor configuration failed\n"); + mutex_unlock(&data->lock); + return ret; + + case IIO_CHAN_INFO_SCALE: + index = stk3310_get_index(stk3310_scale_table, + ARRAY_SIZE(stk3310_scale_table), + val, val2); + if (index < 0) + return -EINVAL; + mutex_lock(&data->lock); + if (chan->type == IIO_LIGHT) + ret = regmap_field_write(data->reg_als_gain, index); + else + ret = regmap_field_write(data->reg_ps_gain, index); + if (ret < 0) + dev_err(&data->client->dev, + "sensor configuration failed\n"); + mutex_unlock(&data->lock); + return ret; + } + + return -EINVAL; +} + +static const struct iio_info stk3310_info = { + .read_raw = stk3310_read_raw, + .write_raw = stk3310_write_raw, + .attrs = &stk3310_attribute_group, + .read_event_value = stk3310_read_event, + .write_event_value = stk3310_write_event, + .read_event_config = stk3310_read_event_config, + .write_event_config = stk3310_write_event_config, +}; + +static int stk3310_set_state(struct stk3310_data *data, u8 state) +{ + int ret; + struct i2c_client *client = data->client; + + /* 3-bit state; 0b100 is not supported. */ + if (state > 7 || state == 4) + return -EINVAL; + + mutex_lock(&data->lock); + ret = regmap_field_write(data->reg_state, state); + if (ret < 0) { + dev_err(&client->dev, "failed to change sensor state\n"); + } else if (state != STK3310_STATE_STANDBY) { + /* Don't reset the 'enabled' flags if we're going in standby */ + data->ps_enabled = !!(state & STK3310_STATE_EN_PS); + data->als_enabled = !!(state & STK3310_STATE_EN_ALS); + } + mutex_unlock(&data->lock); + + return ret; +} + +static int stk3310_init(struct iio_dev *indio_dev) +{ + int ret; + int chipid; + u8 state; + struct stk3310_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + + ret = regmap_read(data->regmap, STK3310_REG_ID, &chipid); + if (ret < 0) + return ret; + + ret = stk3310_check_chip_id(chipid); + if (ret < 0) + dev_info(&client->dev, "new unknown chip id: 0x%x\n", chipid); + + state = STK3310_STATE_EN_ALS | STK3310_STATE_EN_PS; + ret = stk3310_set_state(data, state); + if (ret < 0) { + dev_err(&client->dev, "failed to enable sensor"); + return ret; + } + + /* Enable PS interrupts */ + ret = regmap_field_write(data->reg_int_ps, STK3310_PSINT_EN); + if (ret < 0) { + dev_err(&client->dev, "failed to enable interrupts!\n"); + return ret; + } + + data->ps_int_enabled = true; + data->ps_thdh = STK3310_PS_MAX_VAL; + + return 0; +} + +static bool stk3310_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STK3310_REG_ALS_DATA_MSB: + case STK3310_REG_ALS_DATA_LSB: + case STK3310_REG_PS_DATA_LSB: + case STK3310_REG_PS_DATA_MSB: + case STK3310_REG_FLAG: + return true; + default: + return false; + } +} + +static const struct regmap_config stk3310_regmap_config = { + .name = "stk3310_regmap", + .reg_bits = 8, + .val_bits = 8, + .max_register = STK3310_MAX_REG, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = stk3310_is_volatile_reg, +}; + +static int stk3310_regmap_init(struct stk3310_data *data) +{ + struct regmap *regmap; + struct i2c_client *client; + + client = data->client; + regmap = devm_regmap_init_i2c(client, &stk3310_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "regmap initialization failed.\n"); + return PTR_ERR(regmap); + } + data->regmap = regmap; + + STK3310_REGFIELD(state); + STK3310_REGFIELD(als_gain); + STK3310_REGFIELD(ps_gain); + STK3310_REGFIELD(als_it); + STK3310_REGFIELD(ps_it); + STK3310_REGFIELD(int_ps); + STK3310_REGFIELD(flag_psint); + STK3310_REGFIELD(flag_nf); + + return 0; +} + +static irqreturn_t stk3310_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct stk3310_data *data = iio_priv(indio_dev); + + data->timestamp = iio_get_time_ns(indio_dev); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t stk3310_irq_event_handler(int irq, void *private) +{ + int ret; + unsigned int dir; + u64 event; + + struct iio_dev *indio_dev = private; + struct stk3310_data *data = iio_priv(indio_dev); + + /* Read FLAG_NF to figure out what threshold has been met. */ + mutex_lock(&data->lock); + ret = regmap_field_read(data->reg_flag_nf, &dir); + if (ret < 0) { + dev_err(&data->client->dev, "register read failed: %d\n", ret); + goto out; + } + event = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 1, + IIO_EV_TYPE_THRESH, + (dir ? IIO_EV_DIR_FALLING : + IIO_EV_DIR_RISING)); + iio_push_event(indio_dev, event, data->timestamp); + + /* Reset the interrupt flag */ + ret = regmap_field_write(data->reg_flag_psint, 0); + if (ret < 0) + dev_err(&data->client->dev, "failed to reset interrupts\n"); +out: + mutex_unlock(&data->lock); + + return IRQ_HANDLED; +} + +static int stk3310_probe(struct i2c_client *client) +{ + int ret; + struct iio_dev *indio_dev; + struct stk3310_data *data; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + i2c_set_clientdata(client, indio_dev); + + device_property_read_u32(&client->dev, "proximity-near-level", + &data->ps_near_level); + + mutex_init(&data->lock); + + ret = stk3310_regmap_init(data); + if (ret < 0) + return ret; + + indio_dev->info = &stk3310_info; + indio_dev->name = STK3310_DRIVER_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = stk3310_channels; + indio_dev->num_channels = ARRAY_SIZE(stk3310_channels); + + ret = stk3310_init(indio_dev); + if (ret < 0) + return ret; + + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + stk3310_irq_handler, + stk3310_irq_event_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "stk3310_event", indio_dev); + if (ret) + goto err_standby; + } + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "device_register failed\n"); + goto err_standby; + } + + return 0; + +err_standby: + stk3310_set_state(data, STK3310_STATE_STANDBY); + return ret; +} + +static void stk3310_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + stk3310_set_state(iio_priv(indio_dev), STK3310_STATE_STANDBY); +} + +static int stk3310_suspend(struct device *dev) +{ + struct stk3310_data *data; + int ret; + + data = iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + if (data->ps_int_enabled) { + ret = regmap_field_write(data->reg_int_ps, 0x0); + if (ret < 0) { + dev_err(dev, "failed to disable ps int at suspend.\n"); + return ret; + } + } + + return stk3310_set_state(data, STK3310_STATE_STANDBY); +} + +static int stk3310_resume(struct device *dev) +{ + u8 state = 0; + struct stk3310_data *data; + __be16 buf; + int ret; + + data = iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + if (data->ps_enabled) + state |= STK3310_STATE_EN_PS; + if (data->als_enabled) + state |= STK3310_STATE_EN_ALS; + + ret = stk3310_set_state(data, state); + if (ret < 0) + return ret; + + if (data->ps_thdl != 0x0) { + buf = cpu_to_be16(data->ps_thdl); + ret = regmap_bulk_write(data->regmap, STK3310_REG_THDL_PS, &buf, sizeof(buf)); + if (ret < 0) { + dev_err(dev, "failed to set reg THDL_PS at resume.\n"); + return ret; + } + } + + if (data->ps_thdh != STK3310_PS_MAX_VAL) { + buf = cpu_to_be16(data->ps_thdh); + ret = regmap_bulk_write(data->regmap, STK3310_REG_THDH_PS, &buf, sizeof(buf)); + if (ret < 0) { + dev_err(dev, "failed to set reg THDH_PS at resume.\n"); + return ret; + } + } + + if (data->ps_int_enabled) { + ret = regmap_field_write(data->reg_int_ps, STK3310_PSINT_EN); + if (ret < 0) { + dev_err(dev, "failed to enable ps int at resume.\n"); + return ret; + } + } + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(stk3310_pm_ops, stk3310_suspend, + stk3310_resume); + +static const struct i2c_device_id stk3310_i2c_id[] = { + { .name = "STK3013" }, + { .name = "STK3310" }, + { .name = "STK3311" }, + { .name = "STK3335" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, stk3310_i2c_id); + +static const struct acpi_device_id stk3310_acpi_id[] = { + {"STK3013", 0}, + {"STK3310", 0}, + {"STK3311", 0}, + { } +}; + +MODULE_DEVICE_TABLE(acpi, stk3310_acpi_id); + +static const struct of_device_id stk3310_of_match[] = { + { .compatible = "sensortek,stk3013", }, + { .compatible = "sensortek,stk3310", }, + { .compatible = "sensortek,stk3311", }, + { .compatible = "sensortek,stk3335", }, + { } +}; +MODULE_DEVICE_TABLE(of, stk3310_of_match); + +static struct i2c_driver stk3310_driver = { + .driver = { + .name = "stk3310", + .of_match_table = stk3310_of_match, + .pm = pm_sleep_ptr(&stk3310_pm_ops), + .acpi_match_table = stk3310_acpi_id, + }, + .probe = stk3310_probe, + .remove = stk3310_remove, + .id_table = stk3310_i2c_id, +}; + +module_i2c_driver(stk3310_driver); + +MODULE_AUTHOR("Tiberiu Breana "); +MODULE_DESCRIPTION("STK3310 Ambient Light and Proximity Sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/tcs3414.c b/drivers/iio/light/tcs3414.c new file mode 100644 index 000000000..458178fb6 --- /dev/null +++ b/drivers/iio/light/tcs3414.c @@ -0,0 +1,382 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tcs3414.c - Support for TAOS TCS3414 digital color sensor + * + * Copyright (c) 2014 Peter Meerwald + * + * Digital color sensor with 16-bit channels for red, green, blue, clear); + * 7-bit I2C slave address 0x39 (TCS3414) or 0x29, 0x49, 0x59 (TCS3413, + * TCS3415, TCS3416, resp.) + * + * TODO: sync, interrupt support, thresholds, prescaler + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define TCS3414_DRV_NAME "tcs3414" + +#define TCS3414_COMMAND BIT(7) +#define TCS3414_COMMAND_WORD (TCS3414_COMMAND | BIT(5)) + +#define TCS3414_CONTROL (TCS3414_COMMAND | 0x00) +#define TCS3414_TIMING (TCS3414_COMMAND | 0x01) +#define TCS3414_ID (TCS3414_COMMAND | 0x04) +#define TCS3414_GAIN (TCS3414_COMMAND | 0x07) +#define TCS3414_DATA_GREEN (TCS3414_COMMAND_WORD | 0x10) +#define TCS3414_DATA_RED (TCS3414_COMMAND_WORD | 0x12) +#define TCS3414_DATA_BLUE (TCS3414_COMMAND_WORD | 0x14) +#define TCS3414_DATA_CLEAR (TCS3414_COMMAND_WORD | 0x16) + +#define TCS3414_CONTROL_ADC_VALID BIT(4) +#define TCS3414_CONTROL_ADC_EN BIT(1) +#define TCS3414_CONTROL_POWER BIT(0) + +#define TCS3414_INTEG_MASK GENMASK(1, 0) +#define TCS3414_INTEG_12MS 0x0 +#define TCS3414_INTEG_100MS 0x1 +#define TCS3414_INTEG_400MS 0x2 + +#define TCS3414_GAIN_MASK GENMASK(5, 4) +#define TCS3414_GAIN_SHIFT 4 + +struct tcs3414_data { + struct i2c_client *client; + u8 control; + u8 gain; + u8 timing; +}; + +#define TCS3414_CHANNEL(_color, _si, _addr) { \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .channel2 = IIO_MOD_LIGHT_##_color, \ + .address = _addr, \ + .scan_index = _si, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ +} + +/* scale factors: 1/gain */ +static const int tcs3414_scales[][2] = { + {1, 0}, {0, 250000}, {0, 62500}, {0, 15625} +}; + +/* integration time in ms */ +static const int tcs3414_times[] = { 12, 100, 400 }; + +static const struct iio_chan_spec tcs3414_channels[] = { + TCS3414_CHANNEL(GREEN, 0, TCS3414_DATA_GREEN), + TCS3414_CHANNEL(RED, 1, TCS3414_DATA_RED), + TCS3414_CHANNEL(BLUE, 2, TCS3414_DATA_BLUE), + TCS3414_CHANNEL(CLEAR, 3, TCS3414_DATA_CLEAR), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static int tcs3414_req_data(struct tcs3414_data *data) +{ + int tries = 25; + int ret; + + ret = i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, + data->control | TCS3414_CONTROL_ADC_EN); + if (ret < 0) + return ret; + + while (tries--) { + ret = i2c_smbus_read_byte_data(data->client, TCS3414_CONTROL); + if (ret < 0) + return ret; + if (ret & TCS3414_CONTROL_ADC_VALID) + break; + msleep(20); + } + + ret = i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, + data->control); + if (ret < 0) + return ret; + + if (tries < 0) { + dev_err(&data->client->dev, "data not ready\n"); + return -EIO; + } + + return 0; +} + +static int tcs3414_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct tcs3414_data *data = iio_priv(indio_dev); + int i, ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = tcs3414_req_data(data); + if (ret < 0) { + iio_device_release_direct(indio_dev); + return ret; + } + ret = i2c_smbus_read_word_data(data->client, chan->address); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + i = (data->gain & TCS3414_GAIN_MASK) >> TCS3414_GAIN_SHIFT; + *val = tcs3414_scales[i][0]; + *val2 = tcs3414_scales[i][1]; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = tcs3414_times[data->timing & TCS3414_INTEG_MASK] * 1000; + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static int tcs3414_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct tcs3414_data *data = iio_priv(indio_dev); + int i; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + for (i = 0; i < ARRAY_SIZE(tcs3414_scales); i++) { + if (val == tcs3414_scales[i][0] && + val2 == tcs3414_scales[i][1]) { + data->gain &= ~TCS3414_GAIN_MASK; + data->gain |= i << TCS3414_GAIN_SHIFT; + return i2c_smbus_write_byte_data( + data->client, TCS3414_GAIN, + data->gain); + } + } + return -EINVAL; + case IIO_CHAN_INFO_INT_TIME: + if (val != 0) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(tcs3414_times); i++) { + if (val2 == tcs3414_times[i] * 1000) { + data->timing &= ~TCS3414_INTEG_MASK; + data->timing |= i; + return i2c_smbus_write_byte_data( + data->client, TCS3414_TIMING, + data->timing); + } + } + return -EINVAL; + default: + return -EINVAL; + } +} + +static irqreturn_t tcs3414_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct tcs3414_data *data = iio_priv(indio_dev); + int i, j = 0; + /* Ensure timestamp is naturally aligned */ + struct { + u16 chans[4]; + aligned_s64 timestamp; + } scan = { }; + + + iio_for_each_active_channel(indio_dev, i) { + int ret = i2c_smbus_read_word_data(data->client, + TCS3414_DATA_GREEN + 2*i); + if (ret < 0) + goto done; + + scan.chans[j++] = ret; + } + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static IIO_CONST_ATTR(scale_available, "1 0.25 0.0625 0.015625"); +static IIO_CONST_ATTR_INT_TIME_AVAIL("0.012 0.1 0.4"); + +static struct attribute *tcs3414_attributes[] = { + &iio_const_attr_scale_available.dev_attr.attr, + &iio_const_attr_integration_time_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group tcs3414_attribute_group = { + .attrs = tcs3414_attributes, +}; + +static const struct iio_info tcs3414_info = { + .read_raw = tcs3414_read_raw, + .write_raw = tcs3414_write_raw, + .attrs = &tcs3414_attribute_group, +}; + +static int tcs3414_buffer_postenable(struct iio_dev *indio_dev) +{ + struct tcs3414_data *data = iio_priv(indio_dev); + + data->control |= TCS3414_CONTROL_ADC_EN; + return i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, + data->control); +} + +static int tcs3414_buffer_predisable(struct iio_dev *indio_dev) +{ + struct tcs3414_data *data = iio_priv(indio_dev); + + data->control &= ~TCS3414_CONTROL_ADC_EN; + return i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, + data->control); +} + +static const struct iio_buffer_setup_ops tcs3414_buffer_setup_ops = { + .postenable = tcs3414_buffer_postenable, + .predisable = tcs3414_buffer_predisable, +}; + +static int tcs3414_powerdown(struct tcs3414_data *data) +{ + return i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, + data->control & ~(TCS3414_CONTROL_POWER | + TCS3414_CONTROL_ADC_EN)); +} + +static void tcs3414_powerdown_cleanup(void *data) +{ + tcs3414_powerdown(data); +} + +static int tcs3414_probe(struct i2c_client *client) +{ + struct tcs3414_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (indio_dev == NULL) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + indio_dev->info = &tcs3414_info; + indio_dev->name = TCS3414_DRV_NAME; + indio_dev->channels = tcs3414_channels; + indio_dev->num_channels = ARRAY_SIZE(tcs3414_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = i2c_smbus_read_byte_data(data->client, TCS3414_ID); + if (ret < 0) + return ret; + + switch (ret & 0xf0) { + case 0x00: + dev_info(&client->dev, "TCS3404 found\n"); + break; + case 0x10: + dev_info(&client->dev, "TCS3413/14/15/16 found\n"); + break; + default: + return -ENODEV; + } + + data->control = TCS3414_CONTROL_POWER; + ret = i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, + data->control); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(&client->dev, tcs3414_powerdown_cleanup, + data); + if (ret < 0) + return ret; + + data->timing = TCS3414_INTEG_12MS; /* free running */ + ret = i2c_smbus_write_byte_data(data->client, TCS3414_TIMING, + data->timing); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(data->client, TCS3414_GAIN); + if (ret < 0) + return ret; + data->gain = ret; + + ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, NULL, + tcs3414_trigger_handler, &tcs3414_buffer_setup_ops); + if (ret < 0) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static int tcs3414_suspend(struct device *dev) +{ + struct tcs3414_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + return tcs3414_powerdown(data); +} + +static int tcs3414_resume(struct device *dev) +{ + struct tcs3414_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + return i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, + data->control); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(tcs3414_pm_ops, tcs3414_suspend, + tcs3414_resume); + +static const struct i2c_device_id tcs3414_id[] = { + { .name = "tcs3414" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tcs3414_id); + +static struct i2c_driver tcs3414_driver = { + .driver = { + .name = TCS3414_DRV_NAME, + .pm = pm_sleep_ptr(&tcs3414_pm_ops), + }, + .probe = tcs3414_probe, + .id_table = tcs3414_id, +}; +module_i2c_driver(tcs3414_driver); + +MODULE_AUTHOR("Peter Meerwald "); +MODULE_DESCRIPTION("TCS3414 digital color sensors driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/tcs3472.c b/drivers/iio/light/tcs3472.c new file mode 100644 index 000000000..b392943e5 --- /dev/null +++ b/drivers/iio/light/tcs3472.c @@ -0,0 +1,864 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tcs3472.c - Support for TAOS TCS3472 color light-to-digital converter + * + * Copyright (c) 2013 Peter Meerwald + * + * Color light sensor with 16-bit channels for red, green, blue, clear); + * 7-bit I2C slave address 0x39 (TCS34721, TCS34723) or 0x29 (TCS34725, + * TCS34727) + * + * Datasheet: http://ams.com/eng/content/download/319364/1117183/file/TCS3472_Datasheet_EN_v2.pdf + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define TCS3472_DRV_NAME "tcs3472" + +#define TCS3472_COMMAND BIT(7) +#define TCS3472_AUTO_INCR BIT(5) +#define TCS3472_SPECIAL_FUNC (BIT(5) | BIT(6)) + +#define TCS3472_INTR_CLEAR (TCS3472_COMMAND | TCS3472_SPECIAL_FUNC | 0x06) + +#define TCS3472_ENABLE (TCS3472_COMMAND | 0x00) +#define TCS3472_ATIME (TCS3472_COMMAND | 0x01) +#define TCS3472_WTIME (TCS3472_COMMAND | 0x03) +#define TCS3472_AILT (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x04) +#define TCS3472_AIHT (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x06) +#define TCS3472_PERS (TCS3472_COMMAND | 0x0c) +#define TCS3472_CONFIG (TCS3472_COMMAND | 0x0d) +#define TCS3472_CONTROL (TCS3472_COMMAND | 0x0f) +#define TCS3472_ID (TCS3472_COMMAND | 0x12) +#define TCS3472_STATUS (TCS3472_COMMAND | 0x13) +#define TCS3472_CDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x14) +#define TCS3472_RDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x16) +#define TCS3472_GDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x18) +#define TCS3472_BDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x1a) + +#define TCS3472_STATUS_AINT BIT(4) +#define TCS3472_STATUS_AVALID BIT(0) +#define TCS3472_ENABLE_AIEN BIT(4) +#define TCS3472_ENABLE_WEN BIT(3) +#define TCS3472_ENABLE_AEN BIT(1) +#define TCS3472_ENABLE_PON BIT(0) +#define TCS3472_ENABLE_RUN \ + (TCS3472_ENABLE_AEN | TCS3472_ENABLE_PON | TCS3472_ENABLE_WEN) +#define TCS3472_CONTROL_AGAIN_MASK (BIT(0) | BIT(1)) +#define TCS3472_CONFIG_WLONG BIT(1) + +#define TCS3472_ATIME_TO_US(atime) (((256) - (atime)) * 2400) + +struct tcs3472_data { + struct i2c_client *client; + struct mutex lock; + int target_freq_hz; + int target_freq_uhz; + u16 low_thresh; + u16 high_thresh; + u8 enable; + u8 enable_pre_suspend; + u8 control; + u8 atime; + u8 apers; + u8 wtime; + bool wlong; +}; + +static const struct iio_event_spec tcs3472_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_PERIOD), + }, +}; + +#define TCS3472_CHANNEL(_color, _si, _addr) { \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .channel2 = IIO_MOD_LIGHT_##_color, \ + .address = _addr, \ + .scan_index = _si, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ + .event_spec = _si ? NULL : tcs3472_events, \ + .num_event_specs = _si ? 0 : ARRAY_SIZE(tcs3472_events), \ +} + +static const int tcs3472_agains[] = { 1, 4, 16, 60 }; + +static const struct iio_chan_spec tcs3472_channels[] = { + TCS3472_CHANNEL(CLEAR, 0, TCS3472_CDATA), + TCS3472_CHANNEL(RED, 1, TCS3472_RDATA), + TCS3472_CHANNEL(GREEN, 2, TCS3472_GDATA), + TCS3472_CHANNEL(BLUE, 3, TCS3472_BDATA), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +/* + * The chip's cycle time is the sum of three components: + * - ATIME: the programmable RGBC integration time. + * - The fixed RGBC initialization time (2400 us). + * - WTIME: the wait time, used only if WEN is set. If WLONG is active, + * the wait step is multiplied by 12 (2400 us -> 28800 us). + */ +static unsigned int tcs3472_cycle_time_us(struct tcs3472_data *data) +{ + unsigned int atime_us = TCS3472_ATIME_TO_US(data->atime); + unsigned int init_us = 2400; + unsigned int wtime_us; + + if (!(data->enable & TCS3472_ENABLE_WEN)) + wtime_us = 0; + else if (data->wlong) + wtime_us = (256 - data->wtime) * 28800; + else + wtime_us = (256 - data->wtime) * 2400; + + return atime_us + init_us + wtime_us; +} + +/* + * Convert a cycle time in microseconds to a frequency in Hz and microhertz. + * + * Given cycle_us = T (the cycle period in microseconds), the corresponding + * frequency is: + * f = 1e6 / T [Hz] + * + * The result is split into the IIO_VAL_INT_PLUS_MICRO format: + * val = floor(1e6 / T) [Hz] + * val2 = (1e6 mod T) * 1e6 / T [microhertz] + */ +static void tcs3472_cycle_to_freq(unsigned int cycle_us, int *val, int *val2) +{ + *val = USEC_PER_SEC / cycle_us; + *val2 = div_u64((u64)(USEC_PER_SEC % cycle_us) * USEC_PER_SEC, + cycle_us); +} + +static int tcs3472_req_data(struct tcs3472_data *data) +{ + /* + * The worst-case cycle time is reached with ATIME=0x00, WTIME=0x00 + * and WLONG=1. So: + * 614 ms (Max Integration Time) + * + 2.4 ms (RGBC Init) + * + 7.37 s (Max Wait Time) + * = ~ 8 s (Total Max cycle time). + * Use that as a polling upper bound; in normal operation the loop + * exits as soon as AVALID is set. So the total number of tries in 8 + * seconds considering a polling period of 20 ms is 400. + * Considering a 20% margin due to oscillator tolerance, the total + * duration becomes approximately 9.8 seconds, which corresponds to + * about 480 steps. Therefore, setting it to 500 appears to be a + * reasonable and safe trade-off. + */ + int tries = 500; + int ret; + + while (tries--) { + ret = i2c_smbus_read_byte_data(data->client, TCS3472_STATUS); + if (ret < 0) + return ret; + if (ret & TCS3472_STATUS_AVALID) + break; + msleep(20); + } + + if (tries < 0) { + dev_err(&data->client->dev, "data not ready\n"); + return -EIO; + } + + return 0; +} + +static int tcs3472_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct tcs3472_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = tcs3472_req_data(data); + if (ret < 0) { + iio_device_release_direct(indio_dev); + return ret; + } + ret = i2c_smbus_read_word_data(data->client, chan->address); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + *val = tcs3472_agains[data->control & + TCS3472_CONTROL_AGAIN_MASK]; + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = TCS3472_ATIME_TO_US(data->atime); + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: { + unsigned int cycle_us; + + guard(mutex)(&data->lock); + cycle_us = tcs3472_cycle_time_us(data); + tcs3472_cycle_to_freq(cycle_us, val, val2); + return IIO_VAL_INT_PLUS_MICRO; + } + default: + return -EINVAL; + } +} + +/* + * __tcs3472_set_sampling_freq() - implementation of sampling frequency + * configuration. The caller must hold data->lock. + */ +static int __tcs3472_set_sampling_freq(struct tcs3472_data *data, + int val, int val2) +{ + unsigned int atime_us; + unsigned int init_us = 2400; + u64 cycle_us; + s64 wait_us; + int wtime; + bool wlong = false; + u8 config; + int ret; + + if (val < 0 || val2 < 0 || (val == 0 && val2 == 0)) + return -EINVAL; + + atime_us = TCS3472_ATIME_TO_US(data->atime); + + /* + * cycle_us = 1 / freq, expressed in microseconds. + * Numerator: 1 [s] = PSEC_PER_SEC [ps] + * Denominator: freq [Hz] * MICROHZ_PER_HZ + val2 [uHz] = freq in [uHz] + * Result: ps / uHz = us + */ + cycle_us = div64_u64(PSEC_PER_SEC, (u64)val * MICROHZ_PER_HZ + val2); + + /* + * wait_us can be negative when the requested frequency is too high + * to be reached, or very large when the requested frequency is + * close to zero. Use s64 to cover the full range: + * + * cycle_us = PSEC_PER_SEC / (val * MICROHZ_PER_HZ + val2) + * + * The divisor of the formula above reaches its maximum when + * val = val2 = INT_MAX: + * INT_MAX * MICROHZ_PER_HZ + INT_MAX = ~2.15e18 + * so cycle_us_min = floor(1e12 / 2.15e18) = 0. + * + * The divisor reaches its minimum (1) when val = 0 and val2 = 1, + * so cycle_us_max = 1e12 / 1 = 1e12. + * + * Therefore: + * wait_us_min = 0 - 2400 - 612000 = -616800 + * wait_us_max = 1e12 - 2400 - 2400 = 999999995200 + * + * Both fit comfortably in s64. + */ + wait_us = (s64)cycle_us - init_us - atime_us; + if (wait_us < 2400) { + if (data->enable & TCS3472_ENABLE_WEN) { + u8 enable = data->enable & ~TCS3472_ENABLE_WEN; + + ret = i2c_smbus_write_byte_data(data->client, + TCS3472_ENABLE, enable); + if (ret) + return ret; + + data->enable = enable; + } + + data->target_freq_hz = val; + data->target_freq_uhz = val2; + return 0; + } + + /* + * Wait state is needed: make sure WEN is active before programming + * WTIME (and possibly WLONG). + */ + if (!(data->enable & TCS3472_ENABLE_WEN)) { + u8 enable = data->enable | TCS3472_ENABLE_WEN; + + ret = i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE, + enable); + if (ret) + return ret; + + data->enable = enable; + } + + wtime = 256 - DIV_ROUND_CLOSEST_ULL(wait_us, 2400); + if (wtime < 0) { + /* + * If wait_us is too high (so the requested frequency is too + * low), the resulting wait exceeds what WTIME can represent + * (max 614 ms without WLONG). Enable WLONG, whose step is 12x + * longer (28.8 ms instead of 2.4 ms), and recompute. + */ + wlong = true; + wtime = 256 - DIV_ROUND_CLOSEST_ULL(wait_us, 28800); + } + + if (wlong != data->wlong) { + ret = i2c_smbus_read_byte_data(data->client, TCS3472_CONFIG); + if (ret < 0) + return ret; + + config = ret; + if (wlong) + config |= TCS3472_CONFIG_WLONG; + else + config &= ~TCS3472_CONFIG_WLONG; + + ret = i2c_smbus_write_byte_data(data->client, TCS3472_CONFIG, + config); + if (ret) + return ret; + + data->wlong = wlong; + } + + /* + * If the requested wait is so long that even WLONG cannot + * cover it, wtime may still be negative. Saturate to 0, + * which is the largest possible wait (256 * 28.8 ms = 7.37 s). + */ + wtime = clamp(wtime, 0, 255); + ret = i2c_smbus_write_byte_data(data->client, TCS3472_WTIME, wtime); + if (ret) + return ret; + + data->wtime = wtime; + data->target_freq_hz = val; + data->target_freq_uhz = val2; + + return 0; +} + +static int tcs3472_set_sampling_freq(struct tcs3472_data *data, + int val, int val2) +{ + guard(mutex)(&data->lock); + return __tcs3472_set_sampling_freq(data, val, val2); +} + +static int tcs3472_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct tcs3472_data *data = iio_priv(indio_dev); + int ret; + int i; + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + if (val2 != 0) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(tcs3472_agains); i++) { + if (val == tcs3472_agains[i]) { + data->control &= ~TCS3472_CONTROL_AGAIN_MASK; + data->control |= i; + return i2c_smbus_write_byte_data( + data->client, TCS3472_CONTROL, + data->control); + } + } + return -EINVAL; + case IIO_CHAN_INFO_INT_TIME: + if (val != 0) + return -EINVAL; + for (i = 0; i < 256; i++) { + if (val2 != (256 - i) * 2400) + continue; + + guard(mutex)(&data->lock); + + ret = i2c_smbus_write_byte_data(data->client, + TCS3472_ATIME, i); + if (ret) + return ret; + + data->atime = i; + + /* + * ATIME just changed, so the cycle time changed too. + * Re-run the sampling frequency logic to recompute + * WTIME and preserve the user's last requested + * frequency. Lock is already held. + */ + return __tcs3472_set_sampling_freq(data, + data->target_freq_hz, + data->target_freq_uhz); + } + return -EINVAL; + case IIO_CHAN_INFO_SAMP_FREQ: + return tcs3472_set_sampling_freq(data, val, val2); + default: + return -EINVAL; + } +} + +/* + * Translation from APERS field value to the number of consecutive out-of-range + * clear channel values before an interrupt is generated + */ +static const int tcs3472_intr_pers[] = { + 0, 1, 2, 3, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60 +}; + +static int tcs3472_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, int *val, + int *val2) +{ + struct tcs3472_data *data = iio_priv(indio_dev); + unsigned int period; + + guard(mutex)(&data->lock); + + switch (info) { + case IIO_EV_INFO_VALUE: + *val = (dir == IIO_EV_DIR_RISING) ? + data->high_thresh : data->low_thresh; + return IIO_VAL_INT; + case IIO_EV_INFO_PERIOD: + period = tcs3472_cycle_time_us(data) * + tcs3472_intr_pers[data->apers]; + *val = period / USEC_PER_SEC; + *val2 = period % USEC_PER_SEC; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int tcs3472_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, int val, + int val2) +{ + struct tcs3472_data *data = iio_priv(indio_dev); + int ret; + u8 command; + int period; + int i; + + guard(mutex)(&data->lock); + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + command = TCS3472_AIHT; + break; + case IIO_EV_DIR_FALLING: + command = TCS3472_AILT; + break; + default: + return -EINVAL; + } + ret = i2c_smbus_write_word_data(data->client, command, val); + if (ret) + return ret; + + if (dir == IIO_EV_DIR_RISING) + data->high_thresh = val; + else + data->low_thresh = val; + + return 0; + case IIO_EV_INFO_PERIOD:{ + unsigned int cycle_us; + + period = val * USEC_PER_SEC + val2; + cycle_us = tcs3472_cycle_time_us(data); + for (i = 1; i < ARRAY_SIZE(tcs3472_intr_pers) - 1; i++) { + if (period <= cycle_us * tcs3472_intr_pers[i]) + break; + } + ret = i2c_smbus_write_byte_data(data->client, TCS3472_PERS, i); + if (ret) + return ret; + + data->apers = i; + + return 0; + } + default: + return -EINVAL; + } +} + +static int tcs3472_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir) +{ + struct tcs3472_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + + return (data->enable & TCS3472_ENABLE_AIEN) ? 1 : 0; +} + +static int tcs3472_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct tcs3472_data *data = iio_priv(indio_dev); + int ret = 0; + u8 enable_old; + + guard(mutex)(&data->lock); + + enable_old = data->enable; + + if (state) + data->enable |= TCS3472_ENABLE_AIEN; + else + data->enable &= ~TCS3472_ENABLE_AIEN; + + if (enable_old != data->enable) { + ret = i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE, + data->enable); + if (ret) { + data->enable = enable_old; + return ret; + } + } + + return 0; +} + +static irqreturn_t tcs3472_event_handler(int irq, void *priv) +{ + struct iio_dev *indio_dev = priv; + struct tcs3472_data *data = iio_priv(indio_dev); + int ret; + + ret = i2c_smbus_read_byte_data(data->client, TCS3472_STATUS); + if (ret >= 0 && (ret & TCS3472_STATUS_AINT)) { + iio_push_event(indio_dev, IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + + i2c_smbus_read_byte_data(data->client, TCS3472_INTR_CLEAR); + } + + return IRQ_HANDLED; +} + +static irqreturn_t tcs3472_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct tcs3472_data *data = iio_priv(indio_dev); + int i, j = 0; + /* Ensure timestamp is naturally aligned */ + struct { + u16 chans[4]; + aligned_s64 timestamp; + } scan = { }; + + int ret = tcs3472_req_data(data); + if (ret < 0) + goto done; + + iio_for_each_active_channel(indio_dev, i) { + ret = i2c_smbus_read_word_data(data->client, + TCS3472_CDATA + 2*i); + if (ret < 0) + goto done; + + scan.chans[j++] = ret; + } + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static ssize_t tcs3472_show_int_time_available(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + size_t len = 0; + int i; + + for (i = 1; i <= 256; i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06d ", + 2400 * i); + + /* replace trailing space by newline */ + buf[len - 1] = '\n'; + + return len; +} + +static IIO_CONST_ATTR(calibscale_available, "1 4 16 60"); +static IIO_DEV_ATTR_INT_TIME_AVAIL(tcs3472_show_int_time_available); + +static struct attribute *tcs3472_attributes[] = { + &iio_const_attr_calibscale_available.dev_attr.attr, + &iio_dev_attr_integration_time_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group tcs3472_attribute_group = { + .attrs = tcs3472_attributes, +}; + +static const struct iio_info tcs3472_info = { + .read_raw = tcs3472_read_raw, + .write_raw = tcs3472_write_raw, + .read_event_value = tcs3472_read_event, + .write_event_value = tcs3472_write_event, + .read_event_config = tcs3472_read_event_config, + .write_event_config = tcs3472_write_event_config, + .attrs = &tcs3472_attribute_group, +}; + +static int tcs3472_powerdown(struct tcs3472_data *data) +{ + int ret; + + guard(mutex)(&data->lock); + + data->enable_pre_suspend = data->enable; + + ret = i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE, + data->enable & ~TCS3472_ENABLE_RUN); + if (ret) + return ret; + + return 0; +} + +static void tcs3472_powerdown_action(void *data) +{ + tcs3472_powerdown(data); +} + +static int tcs3472_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct tcs3472_data *data; + struct iio_dev *indio_dev; + unsigned int cycle_us; + int ret; + u8 enable; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; + + indio_dev->info = &tcs3472_info; + indio_dev->name = TCS3472_DRV_NAME; + indio_dev->channels = tcs3472_channels; + indio_dev->num_channels = ARRAY_SIZE(tcs3472_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = i2c_smbus_read_byte_data(data->client, TCS3472_ID); + if (ret < 0) + return ret; + + if (ret == 0x44) + dev_info(dev, "TCS34721/34725 found\n"); + else if (ret == 0x4d) + dev_info(dev, "TCS34723/34727 found\n"); + else + return -ENODEV; + + ret = i2c_smbus_read_byte_data(data->client, TCS3472_CONTROL); + if (ret < 0) + return ret; + data->control = ret; + + ret = i2c_smbus_read_byte_data(data->client, TCS3472_ATIME); + if (ret < 0) + return ret; + data->atime = ret; + + ret = i2c_smbus_read_byte_data(data->client, TCS3472_WTIME); + if (ret < 0) + return ret; + data->wtime = ret; + + ret = i2c_smbus_read_byte_data(data->client, TCS3472_CONFIG); + if (ret < 0) + return ret; + data->wlong = (ret & TCS3472_CONFIG_WLONG) ? 1 : 0; + + ret = i2c_smbus_read_word_data(data->client, TCS3472_AILT); + if (ret < 0) + return ret; + data->low_thresh = ret; + + ret = i2c_smbus_read_word_data(data->client, TCS3472_AIHT); + if (ret < 0) + return ret; + data->high_thresh = ret; + + data->apers = 1; + ret = i2c_smbus_write_byte_data(data->client, TCS3472_PERS, + data->apers); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(data->client, TCS3472_ENABLE); + if (ret < 0) + return ret; + + /* + * Enable the chip in its full running state, including WEN. The + * actual wait time is controlled by the WTIME and WLONG registers, + * which retain their power-on defaults until userspace writes to + * sampling_frequency. + */ + enable = (ret | TCS3472_ENABLE_RUN) & ~TCS3472_ENABLE_AIEN; + + ret = i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE, enable); + if (ret < 0) + return ret; + + data->enable = enable; + + /* + * Initialize target frequency from the chip's current state so that + * subsequent integration_time changes via IIO_CHAN_INFO_INT_TIME can + * preserve a meaningful sampling rate, even before userspace writes + * sampling_frequency for the first time. + */ + cycle_us = tcs3472_cycle_time_us(data); + tcs3472_cycle_to_freq(cycle_us, &data->target_freq_hz, + &data->target_freq_uhz); + + ret = devm_add_action_or_reset(dev, tcs3472_powerdown_action, data); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + tcs3472_trigger_handler, NULL); + if (ret < 0) + return ret; + + if (client->irq) { + ret = devm_request_threaded_irq(dev, client->irq, NULL, + tcs3472_event_handler, + IRQF_TRIGGER_FALLING | + IRQF_SHARED | + IRQF_ONESHOT, + client->name, indio_dev); + if (ret) + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} + +static int tcs3472_suspend(struct device *dev) +{ + struct tcs3472_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + return tcs3472_powerdown(data); +} + +static int tcs3472_resume(struct device *dev) +{ + struct tcs3472_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + int ret; + + guard(mutex)(&data->lock); + + /* + * Restore the full ENABLE register from the snapshot taken in + * tcs3472_powerdown(). This preserves the user's last + * sampling_frequency configuration (in particular the WEN bit) + * across suspend/resume. + */ + ret = i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE, + data->enable_pre_suspend); + if (ret) + return ret; + + data->enable = data->enable_pre_suspend; + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(tcs3472_pm_ops, tcs3472_suspend, + tcs3472_resume); + +static const struct i2c_device_id tcs3472_id[] = { + { .name = "tcs3472" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tcs3472_id); + +static struct i2c_driver tcs3472_driver = { + .driver = { + .name = TCS3472_DRV_NAME, + .pm = pm_sleep_ptr(&tcs3472_pm_ops), + }, + .probe = tcs3472_probe, + .id_table = tcs3472_id, +}; +module_i2c_driver(tcs3472_driver); + +MODULE_AUTHOR("Peter Meerwald "); +MODULE_DESCRIPTION("TCS3472 color light sensors driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/tsl2563.c b/drivers/iio/light/tsl2563.c new file mode 100644 index 000000000..7bef0c311 --- /dev/null +++ b/drivers/iio/light/tsl2563.c @@ -0,0 +1,872 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/iio/light/tsl2563.c + * + * Copyright (C) 2008 Nokia Corporation + * + * Written by Timo O. Karjalainen + * Contact: Amit Kucheria + * + * Converted to IIO driver + * Amit Kucheria + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* Use this many bits for fraction part. */ +#define ADC_FRAC_BITS 14 + +/* Given number of 1/10000's in ADC_FRAC_BITS precision. */ +#define FRAC10K(f) (((f) * BIT(ADC_FRAC_BITS)) / (10000)) + +/* Bits used for fraction in calibration coefficients.*/ +#define CALIB_FRAC_BITS 10 +/* Decimal 10^(digits in sysfs presentation) */ +#define CALIB_BASE_SYSFS 1000 + +#define TSL2563_CMD BIT(7) +#define TSL2563_CLEARINT BIT(6) + +#define TSL2563_REG_CTRL 0x00 +#define TSL2563_REG_TIMING 0x01 +#define TSL2563_REG_LOW 0x02 /* data0 low threshold, 2 bytes */ +#define TSL2563_REG_HIGH 0x04 /* data0 high threshold, 2 bytes */ +#define TSL2563_REG_INT 0x06 +#define TSL2563_REG_ID 0x0a +#define TSL2563_REG_DATA0 0x0c /* broadband sensor value, 2 bytes */ +#define TSL2563_REG_DATA1 0x0e /* infrared sensor value, 2 bytes */ + +#define TSL2563_CMD_POWER_ON 0x03 +#define TSL2563_CMD_POWER_OFF 0x00 +#define TSL2563_CTRL_POWER_MASK GENMASK(1, 0) + +#define TSL2563_TIMING_13MS 0x00 +#define TSL2563_TIMING_100MS 0x01 +#define TSL2563_TIMING_400MS 0x02 +#define TSL2563_TIMING_MASK GENMASK(1, 0) +#define TSL2563_TIMING_GAIN16 0x10 +#define TSL2563_TIMING_GAIN1 0x00 + +#define TSL2563_INT_DISABLED 0x00 +#define TSL2563_INT_LEVEL 0x10 +#define TSL2563_INT_MASK GENMASK(5, 4) +#define TSL2563_INT_PERSIST(n) ((n) & GENMASK(3, 0)) + +struct tsl2563_gainlevel_coeff { + u8 gaintime; + u16 min; + u16 max; +}; + +static const struct tsl2563_gainlevel_coeff tsl2563_gainlevel_table[] = { + { + .gaintime = TSL2563_TIMING_400MS | TSL2563_TIMING_GAIN16, + .min = 0, + .max = 65534, + }, { + .gaintime = TSL2563_TIMING_400MS | TSL2563_TIMING_GAIN1, + .min = 2048, + .max = 65534, + }, { + .gaintime = TSL2563_TIMING_100MS | TSL2563_TIMING_GAIN1, + .min = 4095, + .max = 37177, + }, { + .gaintime = TSL2563_TIMING_13MS | TSL2563_TIMING_GAIN1, + .min = 3000, + .max = 65535, + }, +}; + +struct tsl2563_chip { + struct mutex lock; + struct i2c_client *client; + struct delayed_work poweroff_work; + + /* Remember state for suspend and resume functions */ + bool suspended; + + struct tsl2563_gainlevel_coeff const *gainlevel; + + u16 low_thres; + u16 high_thres; + u8 intr; + bool int_enabled; + + /* Calibration coefficients */ + u32 calib0; + u32 calib1; + int cover_comp_gain; + + /* Cache current values, to be returned while suspended */ + u32 data0; + u32 data1; +}; + +static int tsl2563_set_power(struct tsl2563_chip *chip, int on) +{ + struct i2c_client *client = chip->client; + u8 cmd; + + cmd = on ? TSL2563_CMD_POWER_ON : TSL2563_CMD_POWER_OFF; + return i2c_smbus_write_byte_data(client, + TSL2563_CMD | TSL2563_REG_CTRL, cmd); +} + +/* + * Return value is 0 for off, 1 for on, or a negative error + * code if reading failed. + */ +static int tsl2563_get_power(struct tsl2563_chip *chip) +{ + struct i2c_client *client = chip->client; + int ret; + + ret = i2c_smbus_read_byte_data(client, TSL2563_CMD | TSL2563_REG_CTRL); + if (ret < 0) + return ret; + + return (ret & TSL2563_CTRL_POWER_MASK) == TSL2563_CMD_POWER_ON; +} + +static int tsl2563_configure(struct tsl2563_chip *chip) +{ + int ret; + + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_TIMING, + chip->gainlevel->gaintime); + if (ret) + goto error_ret; + ret = i2c_smbus_write_word_data(chip->client, + TSL2563_CMD | TSL2563_REG_HIGH, + chip->high_thres); + if (ret) + goto error_ret; + ret = i2c_smbus_write_word_data(chip->client, + TSL2563_CMD | TSL2563_REG_LOW, + chip->low_thres); + if (ret) + goto error_ret; +/* + * Interrupt register is automatically written anyway if it is relevant + * so is not here. + */ +error_ret: + return ret; +} + +static void tsl2563_poweroff_work(struct work_struct *work) +{ + struct tsl2563_chip *chip = + container_of(work, struct tsl2563_chip, poweroff_work.work); + tsl2563_set_power(chip, 0); +} + +static int tsl2563_detect(struct tsl2563_chip *chip) +{ + int ret; + + ret = tsl2563_set_power(chip, 1); + if (ret) + return ret; + + ret = tsl2563_get_power(chip); + if (ret < 0) + return ret; + + return ret ? 0 : -ENODEV; +} + +static int tsl2563_read_id(struct tsl2563_chip *chip, u8 *id) +{ + struct i2c_client *client = chip->client; + int ret; + + ret = i2c_smbus_read_byte_data(client, TSL2563_CMD | TSL2563_REG_ID); + if (ret < 0) + return ret; + + *id = ret; + + return 0; +} + +static int tsl2563_configure_irq(struct tsl2563_chip *chip, bool enable) +{ + int ret; + + chip->intr &= ~TSL2563_INT_MASK; + if (enable) + chip->intr |= TSL2563_INT_LEVEL; + + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_INT, + chip->intr); + if (ret < 0) + return ret; + + chip->int_enabled = enable; + return 0; +} + +/* + * "Normalized" ADC value is one obtained with 400ms of integration time and + * 16x gain. This function returns the number of bits of shift needed to + * convert between normalized values and HW values obtained using given + * timing and gain settings. + */ +static int tsl2563_adc_shiftbits(u8 timing) +{ + int shift = 0; + + switch (timing & TSL2563_TIMING_MASK) { + case TSL2563_TIMING_13MS: + shift += 5; + break; + case TSL2563_TIMING_100MS: + shift += 2; + break; + case TSL2563_TIMING_400MS: + /* no-op */ + break; + } + + if (!(timing & TSL2563_TIMING_GAIN16)) + shift += 4; + + return shift; +} + +/* Convert a HW ADC value to normalized scale. */ +static u32 tsl2563_normalize_adc(u16 adc, u8 timing) +{ + return adc << tsl2563_adc_shiftbits(timing); +} + +static void tsl2563_wait_adc(struct tsl2563_chip *chip) +{ + unsigned int delay; + + switch (chip->gainlevel->gaintime & TSL2563_TIMING_MASK) { + case TSL2563_TIMING_13MS: + delay = 14; + break; + case TSL2563_TIMING_100MS: + delay = 101; + break; + default: + delay = 402; + } + /* + * TODO: Make sure that we wait at least required delay but why we + * have to extend it one tick more? + */ + schedule_timeout_interruptible(msecs_to_jiffies(delay) + 2); +} + +static int tsl2563_adjust_gainlevel(struct tsl2563_chip *chip, u16 adc) +{ + struct i2c_client *client = chip->client; + + if (adc > chip->gainlevel->max || adc < chip->gainlevel->min) { + + (adc > chip->gainlevel->max) ? + chip->gainlevel++ : chip->gainlevel--; + + i2c_smbus_write_byte_data(client, + TSL2563_CMD | TSL2563_REG_TIMING, + chip->gainlevel->gaintime); + + tsl2563_wait_adc(chip); + tsl2563_wait_adc(chip); + + return 1; + } else + return 0; +} + +static int tsl2563_get_adc(struct tsl2563_chip *chip) +{ + struct i2c_client *client = chip->client; + u16 adc0, adc1; + int retry = 1; + int ret = 0; + + if (chip->suspended) + goto out; + + if (!chip->int_enabled) { + cancel_delayed_work_sync(&chip->poweroff_work); + + if (!tsl2563_get_power(chip)) { + ret = tsl2563_set_power(chip, 1); + if (ret) + goto out; + ret = tsl2563_configure(chip); + if (ret) + goto out; + tsl2563_wait_adc(chip); + } + } + + while (retry) { + ret = i2c_smbus_read_word_data(client, + TSL2563_CMD | TSL2563_REG_DATA0); + if (ret < 0) + goto out; + adc0 = ret; + + ret = i2c_smbus_read_word_data(client, + TSL2563_CMD | TSL2563_REG_DATA1); + if (ret < 0) + goto out; + adc1 = ret; + + retry = tsl2563_adjust_gainlevel(chip, adc0); + } + + chip->data0 = tsl2563_normalize_adc(adc0, chip->gainlevel->gaintime); + chip->data1 = tsl2563_normalize_adc(adc1, chip->gainlevel->gaintime); + + if (!chip->int_enabled) + schedule_delayed_work(&chip->poweroff_work, 5 * HZ); + + ret = 0; +out: + return ret; +} + +static inline int tsl2563_calib_to_sysfs(u32 calib) +{ + return (int)DIV_ROUND_CLOSEST(calib * CALIB_BASE_SYSFS, BIT(CALIB_FRAC_BITS)); +} + +static inline u32 tsl2563_calib_from_sysfs(int value) +{ + /* Make a fraction from a number n that was multiplied with b. */ + return (((u32) value) << CALIB_FRAC_BITS) / CALIB_BASE_SYSFS; +} + +/* + * Conversions between lux and ADC values. + * + * The basic formula is lux = c0 * adc0 - c1 * adc1, where c0 and c1 are + * appropriate constants. Different constants are needed for different + * kinds of light, determined by the ratio adc1/adc0 (basically the ratio + * of the intensities in infrared and visible wavelengths). lux_table below + * lists the upper threshold of the adc1/adc0 ratio and the corresponding + * constants. + */ + +struct tsl2563_lux_coeff { + unsigned long ch_ratio; + unsigned long ch0_coeff; + unsigned long ch1_coeff; +}; + +static const struct tsl2563_lux_coeff lux_table[] = { + { + .ch_ratio = FRAC10K(1300), + .ch0_coeff = FRAC10K(315), + .ch1_coeff = FRAC10K(262), + }, { + .ch_ratio = FRAC10K(2600), + .ch0_coeff = FRAC10K(337), + .ch1_coeff = FRAC10K(430), + }, { + .ch_ratio = FRAC10K(3900), + .ch0_coeff = FRAC10K(363), + .ch1_coeff = FRAC10K(529), + }, { + .ch_ratio = FRAC10K(5200), + .ch0_coeff = FRAC10K(392), + .ch1_coeff = FRAC10K(605), + }, { + .ch_ratio = FRAC10K(6500), + .ch0_coeff = FRAC10K(229), + .ch1_coeff = FRAC10K(291), + }, { + .ch_ratio = FRAC10K(8000), + .ch0_coeff = FRAC10K(157), + .ch1_coeff = FRAC10K(180), + }, { + .ch_ratio = FRAC10K(13000), + .ch0_coeff = FRAC10K(34), + .ch1_coeff = FRAC10K(26), + }, { + .ch_ratio = ULONG_MAX, + .ch0_coeff = 0, + .ch1_coeff = 0, + }, +}; + +/* Convert normalized, scaled ADC values to lux. */ +static unsigned int tsl2563_adc_to_lux(u32 adc0, u32 adc1) +{ + const struct tsl2563_lux_coeff *lp = lux_table; + unsigned long ratio, lux, ch0 = adc0, ch1 = adc1; + + ratio = ch0 ? ((ch1 << ADC_FRAC_BITS) / ch0) : ULONG_MAX; + + while (lp->ch_ratio < ratio) + lp++; + + lux = ch0 * lp->ch0_coeff - ch1 * lp->ch1_coeff; + + return (unsigned int) (lux >> ADC_FRAC_BITS); +} + +/* Apply calibration coefficient to ADC count. */ +static u32 tsl2563_calib_adc(u32 adc, u32 calib) +{ + unsigned long scaled = adc; + + scaled *= calib; + scaled >>= CALIB_FRAC_BITS; + + return (u32) scaled; +} + +static int tsl2563_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct tsl2563_chip *chip = iio_priv(indio_dev); + + if (mask != IIO_CHAN_INFO_CALIBSCALE) + return -EINVAL; + if (chan->channel2 == IIO_MOD_LIGHT_BOTH) + chip->calib0 = tsl2563_calib_from_sysfs(val); + else if (chan->channel2 == IIO_MOD_LIGHT_IR) + chip->calib1 = tsl2563_calib_from_sysfs(val); + else + return -EINVAL; + + return 0; +} + +static int tsl2563_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + int ret = -EINVAL; + u32 calib0, calib1; + struct tsl2563_chip *chip = iio_priv(indio_dev); + + mutex_lock(&chip->lock); + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_LIGHT: + ret = tsl2563_get_adc(chip); + if (ret) + goto error_ret; + calib0 = tsl2563_calib_adc(chip->data0, chip->calib0) * + chip->cover_comp_gain; + calib1 = tsl2563_calib_adc(chip->data1, chip->calib1) * + chip->cover_comp_gain; + *val = tsl2563_adc_to_lux(calib0, calib1); + ret = IIO_VAL_INT; + break; + case IIO_INTENSITY: + ret = tsl2563_get_adc(chip); + if (ret) + goto error_ret; + if (chan->channel2 == IIO_MOD_LIGHT_BOTH) + *val = chip->data0; + else + *val = chip->data1; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->channel2 == IIO_MOD_LIGHT_BOTH) + *val = tsl2563_calib_to_sysfs(chip->calib0); + else + *val = tsl2563_calib_to_sysfs(chip->calib1); + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + goto error_ret; + } + +error_ret: + mutex_unlock(&chip->lock); + return ret; +} + +static const struct iio_event_spec tsl2563_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec tsl2563_channels[] = { + { + .type = IIO_LIGHT, + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .channel = 0, + }, { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BOTH, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + .event_spec = tsl2563_events, + .num_event_specs = ARRAY_SIZE(tsl2563_events), + }, { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + } +}; + +static int tsl2563_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, int *val, + int *val2) +{ + struct tsl2563_chip *chip = iio_priv(indio_dev); + + switch (dir) { + case IIO_EV_DIR_RISING: + *val = chip->high_thres; + break; + case IIO_EV_DIR_FALLING: + *val = chip->low_thres; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; +} + +static int tsl2563_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, int val, + int val2) +{ + struct tsl2563_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->lock); + + if (dir == IIO_EV_DIR_RISING) + ret = i2c_smbus_write_word_data(chip->client, + TSL2563_CMD | TSL2563_REG_HIGH, val); + else + ret = i2c_smbus_write_word_data(chip->client, + TSL2563_CMD | TSL2563_REG_LOW, val); + if (ret) + goto error_ret; + + if (dir == IIO_EV_DIR_RISING) + chip->high_thres = val; + else + chip->low_thres = val; + +error_ret: + mutex_unlock(&chip->lock); + + return ret; +} + +static irqreturn_t tsl2563_event_handler(int irq, void *private) +{ + struct iio_dev *dev_info = private; + struct tsl2563_chip *chip = iio_priv(dev_info); + + iio_push_event(dev_info, + IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(dev_info)); + + /* clear the interrupt and push the event */ + i2c_smbus_write_byte(chip->client, TSL2563_CMD | TSL2563_CLEARINT); + return IRQ_HANDLED; +} + +static int tsl2563_write_interrupt_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct tsl2563_chip *chip = iio_priv(indio_dev); + int ret = 0; + + mutex_lock(&chip->lock); + if (state && !(chip->intr & TSL2563_INT_MASK)) { + /* ensure the chip is actually on */ + cancel_delayed_work_sync(&chip->poweroff_work); + if (!tsl2563_get_power(chip)) { + ret = tsl2563_set_power(chip, 1); + if (ret) + goto out; + ret = tsl2563_configure(chip); + if (ret) + goto out; + } + ret = tsl2563_configure_irq(chip, true); + } + + if (!state && (chip->intr & TSL2563_INT_MASK)) { + ret = tsl2563_configure_irq(chip, false); + /* now the interrupt is not enabled, we can go to sleep */ + schedule_delayed_work(&chip->poweroff_work, 5 * HZ); + } +out: + mutex_unlock(&chip->lock); + + return ret; +} + +static int tsl2563_read_interrupt_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir) +{ + struct tsl2563_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->lock); + ret = i2c_smbus_read_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_INT); + mutex_unlock(&chip->lock); + if (ret < 0) + return ret; + + return !!(ret & TSL2563_INT_MASK); +} + +static const struct iio_info tsl2563_info_no_irq = { + .read_raw = &tsl2563_read_raw, + .write_raw = &tsl2563_write_raw, +}; + +static const struct iio_info tsl2563_info = { + .read_raw = &tsl2563_read_raw, + .write_raw = &tsl2563_write_raw, + .read_event_value = &tsl2563_read_thresh, + .write_event_value = &tsl2563_write_thresh, + .read_event_config = &tsl2563_read_interrupt_config, + .write_event_config = &tsl2563_write_interrupt_config, +}; + +static int tsl2563_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct tsl2563_chip *chip; + unsigned long irq_flags; + u8 id = 0; + int err; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + + i2c_set_clientdata(client, indio_dev); + chip->client = client; + + err = tsl2563_detect(chip); + if (err) + return dev_err_probe(dev, err, "detect error\n"); + + err = tsl2563_read_id(chip, &id); + if (err) + return dev_err_probe(dev, err, "read id error\n"); + + mutex_init(&chip->lock); + + /* Default values used until userspace says otherwise */ + chip->low_thres = 0x0; + chip->high_thres = 0xffff; + chip->gainlevel = tsl2563_gainlevel_table; + chip->intr = TSL2563_INT_PERSIST(4); + chip->calib0 = tsl2563_calib_from_sysfs(CALIB_BASE_SYSFS); + chip->calib1 = tsl2563_calib_from_sysfs(CALIB_BASE_SYSFS); + + chip->cover_comp_gain = 1; + device_property_read_u32(dev, "amstaos,cover-comp-gain", &chip->cover_comp_gain); + + dev_info(dev, "model %d, rev. %d\n", id >> 4, id & 0x0f); + indio_dev->name = client->name; + indio_dev->channels = tsl2563_channels; + indio_dev->num_channels = ARRAY_SIZE(tsl2563_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + if (client->irq) + indio_dev->info = &tsl2563_info; + else + indio_dev->info = &tsl2563_info_no_irq; + + if (client->irq) { + irq_flags = irq_get_trigger_type(client->irq); + if (irq_flags == IRQF_TRIGGER_NONE) + irq_flags = IRQF_TRIGGER_RISING; + irq_flags |= IRQF_ONESHOT; + + err = devm_request_threaded_irq(dev, client->irq, + NULL, + &tsl2563_event_handler, + irq_flags, + "tsl2563_event", + indio_dev); + if (err) + return err; + } + + err = tsl2563_configure(chip); + if (err) + return dev_err_probe(dev, err, "configure error\n"); + + INIT_DELAYED_WORK(&chip->poweroff_work, tsl2563_poweroff_work); + + /* The interrupt cannot yet be enabled so this is fine without lock */ + schedule_delayed_work(&chip->poweroff_work, 5 * HZ); + + err = iio_device_register(indio_dev); + if (err) { + dev_err_probe(dev, err, "iio registration error\n"); + goto fail; + } + + return 0; + +fail: + cancel_delayed_work_sync(&chip->poweroff_work); + return err; +} + +static void tsl2563_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct tsl2563_chip *chip = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + if (!chip->int_enabled) + cancel_delayed_work_sync(&chip->poweroff_work); + /* Ensure that interrupts are disabled - then flush any bottom halves */ + tsl2563_configure_irq(chip, false); + tsl2563_set_power(chip, 0); +} + +static int tsl2563_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2563_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->lock); + + ret = tsl2563_set_power(chip, 0); + if (ret) + goto out; + + chip->suspended = true; + +out: + mutex_unlock(&chip->lock); + return ret; +} + +static int tsl2563_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2563_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->lock); + + ret = tsl2563_set_power(chip, 1); + if (ret) + goto out; + + ret = tsl2563_configure(chip); + if (ret) + goto out; + + chip->suspended = false; + +out: + mutex_unlock(&chip->lock); + return ret; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(tsl2563_pm_ops, tsl2563_suspend, + tsl2563_resume); + +static const struct i2c_device_id tsl2563_id[] = { + { .name = "tsl2560" }, + { .name = "tsl2561" }, + { .name = "tsl2562" }, + { .name = "tsl2563" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tsl2563_id); + +static const struct of_device_id tsl2563_of_match[] = { + { .compatible = "amstaos,tsl2560" }, + { .compatible = "amstaos,tsl2561" }, + { .compatible = "amstaos,tsl2562" }, + { .compatible = "amstaos,tsl2563" }, + { } +}; +MODULE_DEVICE_TABLE(of, tsl2563_of_match); + +static struct i2c_driver tsl2563_i2c_driver = { + .driver = { + .name = "tsl2563", + .of_match_table = tsl2563_of_match, + .pm = pm_sleep_ptr(&tsl2563_pm_ops), + }, + .probe = tsl2563_probe, + .remove = tsl2563_remove, + .id_table = tsl2563_id, +}; +module_i2c_driver(tsl2563_i2c_driver); + +MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("tsl2563 light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/tsl2583.c b/drivers/iio/light/tsl2583.c new file mode 100644 index 000000000..53fd423aa --- /dev/null +++ b/drivers/iio/light/tsl2583.c @@ -0,0 +1,935 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Device driver for monitoring ambient light intensity (lux) + * within the TAOS tsl258x family of devices (tsl2580, tsl2581, tsl2583). + * + * Copyright (c) 2011, TAOS Corporation. + * Copyright (c) 2016-2017 Brian Masney + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Device Registers and Masks */ +#define TSL2583_CNTRL 0x00 +#define TSL2583_ALS_TIME 0X01 +#define TSL2583_INTERRUPT 0x02 +#define TSL2583_GAIN 0x07 +#define TSL2583_REVID 0x11 +#define TSL2583_CHIPID 0x12 +#define TSL2583_ALS_CHAN0LO 0x14 +#define TSL2583_ALS_CHAN0HI 0x15 +#define TSL2583_ALS_CHAN1LO 0x16 +#define TSL2583_ALS_CHAN1HI 0x17 +#define TSL2583_TMR_LO 0x18 +#define TSL2583_TMR_HI 0x19 + +/* tsl2583 cmd reg masks */ +#define TSL2583_CMD_REG 0x80 +#define TSL2583_CMD_SPL_FN 0x60 +#define TSL2583_CMD_ALS_INT_CLR 0x01 + +/* tsl2583 cntrl reg masks */ +#define TSL2583_CNTL_ADC_ENBL 0x02 +#define TSL2583_CNTL_PWR_OFF 0x00 +#define TSL2583_CNTL_PWR_ON 0x01 + +/* tsl2583 status reg masks */ +#define TSL2583_STA_ADC_VALID 0x01 +#define TSL2583_STA_ADC_INTR 0x10 + +/* Lux calculation constants */ +#define TSL2583_LUX_CALC_OVER_FLOW 65535 + +#define TSL2583_INTERRUPT_DISABLED 0x00 + +#define TSL2583_CHIP_ID 0x90 +#define TSL2583_CHIP_ID_MASK 0xf0 + +#define TSL2583_POWER_OFF_DELAY_MS 2000 + +/* Per-device data */ +struct tsl2583_als_info { + u16 als_ch0; + u16 als_ch1; + u16 lux; +}; + +struct tsl2583_lux { + unsigned int ratio; + unsigned int ch0; + unsigned int ch1; +}; + +static const struct tsl2583_lux tsl2583_default_lux[] = { + { 9830, 8520, 15729 }, + { 12452, 10807, 23344 }, + { 14746, 6383, 11705 }, + { 17695, 4063, 6554 }, + { 0, 0, 0 } /* Termination segment */ +}; + +#define TSL2583_MAX_LUX_TABLE_ENTRIES 11 + +struct tsl2583_settings { + int als_time; + int als_gain; + int als_gain_trim; + int als_cal_target; + + /* + * This structure is intentionally large to accommodate updates via + * sysfs. Sized to 11 = max 10 segments + 1 termination segment. + * Assumption is that one and only one type of glass used. + */ + struct tsl2583_lux als_device_lux[TSL2583_MAX_LUX_TABLE_ENTRIES]; +}; + +struct tsl2583_chip { + struct mutex als_mutex; + struct i2c_client *client; + struct tsl2583_als_info als_cur_info; + struct tsl2583_settings als_settings; + int als_time_scale; + int als_saturation; +}; + +struct gainadj { + s16 ch0; + s16 ch1; + s16 mean; +}; + +/* Index = (0 - 3) Used to validate the gain selection index */ +static const struct gainadj gainadj[] = { + { 1, 1, 1 }, + { 8, 8, 8 }, + { 16, 16, 16 }, + { 107, 115, 111 } +}; + +/* + * Provides initial operational parameter defaults. + * These defaults may be changed through the device's sysfs files. + */ +static void tsl2583_defaults(struct tsl2583_chip *chip) +{ + /* + * The integration time must be a multiple of 50ms and within the + * range [50, 600] ms. + */ + chip->als_settings.als_time = 100; + + /* + * This is an index into the gainadj table. Assume clear glass as the + * default. + */ + chip->als_settings.als_gain = 0; + + /* Default gain trim to account for aperture effects */ + chip->als_settings.als_gain_trim = 1000; + + /* Known external ALS reading used for calibration */ + chip->als_settings.als_cal_target = 130; + + /* Default lux table. */ + memcpy(chip->als_settings.als_device_lux, tsl2583_default_lux, + sizeof(tsl2583_default_lux)); +} + +/* + * Reads and calculates current lux value. + * The raw ch0 and ch1 values of the ambient light sensed in the last + * integration cycle are read from the device. + * Time scale factor array values are adjusted based on the integration time. + * The raw values are multiplied by a scale factor, and device gain is obtained + * using gain index. Limit checks are done next, then the ratio of a multiple + * of ch1 value, to the ch0 value, is calculated. The array als_device_lux[] + * declared above is then scanned to find the first ratio value that is just + * above the ratio we just calculated. The ch0 and ch1 multiplier constants in + * the array are then used along with the time scale factor array values, to + * calculate the lux. + */ +static int tsl2583_get_lux(struct iio_dev *indio_dev) +{ + u16 ch0, ch1; /* separated ch0/ch1 data from device */ + u32 lux; /* raw lux calculated from device data */ + u64 lux64; + u32 ratio; + u8 buf[5]; + struct tsl2583_lux *p; + struct tsl2583_chip *chip = iio_priv(indio_dev); + int i, ret; + + ret = i2c_smbus_read_byte_data(chip->client, TSL2583_CMD_REG); + if (ret < 0) { + dev_err(&chip->client->dev, "%s: failed to read CMD_REG register\n", + __func__); + goto done; + } + + /* is data new & valid */ + if (!(ret & TSL2583_STA_ADC_INTR)) { + dev_err(&chip->client->dev, "%s: data not valid; returning last value\n", + __func__); + ret = chip->als_cur_info.lux; /* return LAST VALUE */ + goto done; + } + + for (i = 0; i < 4; i++) { + int reg = TSL2583_CMD_REG | (TSL2583_ALS_CHAN0LO + i); + + ret = i2c_smbus_read_byte_data(chip->client, reg); + if (ret < 0) { + dev_err(&chip->client->dev, "%s: failed to read register %x\n", + __func__, reg); + goto done; + } + buf[i] = ret; + } + + /* + * Clear the pending interrupt status bit on the chip to allow the next + * integration cycle to start. This has to be done even though this + * driver currently does not support interrupts. + */ + ret = i2c_smbus_write_byte(chip->client, + (TSL2583_CMD_REG | TSL2583_CMD_SPL_FN | + TSL2583_CMD_ALS_INT_CLR)); + if (ret < 0) { + dev_err(&chip->client->dev, "%s: failed to clear the interrupt bit\n", + __func__); + goto done; /* have no data, so return failure */ + } + + /* extract ALS/lux data */ + ch0 = le16_to_cpup((const __le16 *)&buf[0]); + ch1 = le16_to_cpup((const __le16 *)&buf[2]); + + chip->als_cur_info.als_ch0 = ch0; + chip->als_cur_info.als_ch1 = ch1; + + if ((ch0 >= chip->als_saturation) || (ch1 >= chip->als_saturation)) + goto return_max; + + if (!ch0) { + /* + * The sensor appears to be in total darkness so set the + * calculated lux to 0 and return early to avoid a division by + * zero below when calculating the ratio. + */ + ret = 0; + chip->als_cur_info.lux = 0; + goto done; + } + + /* calculate ratio */ + ratio = (ch1 << 15) / ch0; + + /* convert to unscaled lux using the pointer to the table */ + for (p = (struct tsl2583_lux *)chip->als_settings.als_device_lux; + p->ratio != 0 && p->ratio < ratio; p++) + ; + + if (p->ratio == 0) { + lux = 0; + } else { + u32 ch0lux, ch1lux; + + ch0lux = ((ch0 * p->ch0) + + (gainadj[chip->als_settings.als_gain].ch0 >> 1)) + / gainadj[chip->als_settings.als_gain].ch0; + ch1lux = ((ch1 * p->ch1) + + (gainadj[chip->als_settings.als_gain].ch1 >> 1)) + / gainadj[chip->als_settings.als_gain].ch1; + + /* note: lux is 31 bit max at this point */ + if (ch1lux > ch0lux) { + dev_dbg(&chip->client->dev, "%s: No Data - Returning 0\n", + __func__); + ret = 0; + chip->als_cur_info.lux = 0; + goto done; + } + + lux = ch0lux - ch1lux; + } + + /* adjust for active time scale */ + if (chip->als_time_scale == 0) + lux = 0; + else + lux = (lux + (chip->als_time_scale >> 1)) / + chip->als_time_scale; + + /* + * Adjust for active gain scale. + * The tsl2583_default_lux tables above have a factor of 8192 built in, + * so we need to shift right. + * User-specified gain provides a multiplier. + * Apply user-specified gain before shifting right to retain precision. + * Use 64 bits to avoid overflow on multiplication. + * Then go back to 32 bits before division to avoid using div_u64(). + */ + lux64 = lux; + lux64 = lux64 * chip->als_settings.als_gain_trim; + lux64 >>= 13; + lux = lux64; + lux = DIV_ROUND_CLOSEST(lux, 1000); + + if (lux > TSL2583_LUX_CALC_OVER_FLOW) { /* check for overflow */ +return_max: + lux = TSL2583_LUX_CALC_OVER_FLOW; + } + + /* Update the structure with the latest VALID lux. */ + chip->als_cur_info.lux = lux; + ret = lux; + +done: + return ret; +} + +/* + * Obtain single reading and calculate the als_gain_trim (later used + * to derive actual lux). + * Return updated gain_trim value. + */ +static int tsl2583_als_calibrate(struct iio_dev *indio_dev) +{ + struct tsl2583_chip *chip = iio_priv(indio_dev); + unsigned int gain_trim_val; + int ret; + int lux_val; + + ret = i2c_smbus_read_byte_data(chip->client, + TSL2583_CMD_REG | TSL2583_CNTRL); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed to read from the CNTRL register\n", + __func__); + return ret; + } + + if ((ret & (TSL2583_CNTL_ADC_ENBL | TSL2583_CNTL_PWR_ON)) + != (TSL2583_CNTL_ADC_ENBL | TSL2583_CNTL_PWR_ON)) { + dev_err(&chip->client->dev, + "%s: Device is not powered on and/or ADC is not enabled\n", + __func__); + return -EINVAL; + } else if ((ret & TSL2583_STA_ADC_VALID) != TSL2583_STA_ADC_VALID) { + dev_err(&chip->client->dev, + "%s: The two ADC channels have not completed an integration cycle\n", + __func__); + return -ENODATA; + } + + lux_val = tsl2583_get_lux(indio_dev); + if (lux_val < 0) { + dev_err(&chip->client->dev, "%s: failed to get lux\n", + __func__); + return lux_val; + } + + /* Avoid division by zero of lux_value later on */ + if (lux_val == 0) { + dev_err(&chip->client->dev, + "%s: lux_val of 0 will produce out of range trim_value\n", + __func__); + return -ENODATA; + } + + gain_trim_val = (unsigned int)(((chip->als_settings.als_cal_target) + * chip->als_settings.als_gain_trim) / lux_val); + if ((gain_trim_val < 250) || (gain_trim_val > 4000)) { + dev_err(&chip->client->dev, + "%s: trim_val of %d is not within the range [250, 4000]\n", + __func__, gain_trim_val); + return -ENODATA; + } + + chip->als_settings.als_gain_trim = (int)gain_trim_val; + + return 0; +} + +static int tsl2583_set_als_time(struct tsl2583_chip *chip) +{ + int als_count, als_time, ret; + u8 val; + + /* determine als integration register */ + als_count = DIV_ROUND_CLOSEST(chip->als_settings.als_time * 100, 270); + if (!als_count) + als_count = 1; /* ensure at least one cycle */ + + /* convert back to time (encompasses overrides) */ + als_time = DIV_ROUND_CLOSEST(als_count * 27, 10); + + val = 256 - als_count; + ret = i2c_smbus_write_byte_data(chip->client, + TSL2583_CMD_REG | TSL2583_ALS_TIME, + val); + if (ret < 0) { + dev_err(&chip->client->dev, "%s: failed to set the als time to %d\n", + __func__, val); + return ret; + } + + /* set chip struct re scaling and saturation */ + chip->als_saturation = als_count * 922; /* 90% of full scale */ + chip->als_time_scale = DIV_ROUND_CLOSEST(als_time, 50); + + return ret; +} + +static int tsl2583_set_als_gain(struct tsl2583_chip *chip) +{ + int ret; + + /* Set the gain based on als_settings struct */ + ret = i2c_smbus_write_byte_data(chip->client, + TSL2583_CMD_REG | TSL2583_GAIN, + chip->als_settings.als_gain); + if (ret < 0) + dev_err(&chip->client->dev, + "%s: failed to set the gain to %d\n", __func__, + chip->als_settings.als_gain); + + return ret; +} + +static int tsl2583_set_power_state(struct tsl2583_chip *chip, u8 state) +{ + int ret; + + ret = i2c_smbus_write_byte_data(chip->client, + TSL2583_CMD_REG | TSL2583_CNTRL, state); + if (ret < 0) + dev_err(&chip->client->dev, + "%s: failed to set the power state to %d\n", __func__, + state); + + return ret; +} + +/* + * Turn the device on. + * Configuration must be set before calling this function. + */ +static int tsl2583_chip_init_and_power_on(struct iio_dev *indio_dev) +{ + struct tsl2583_chip *chip = iio_priv(indio_dev); + int ret; + + /* Power on the device; ADC off. */ + ret = tsl2583_set_power_state(chip, TSL2583_CNTL_PWR_ON); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(chip->client, + TSL2583_CMD_REG | TSL2583_INTERRUPT, + TSL2583_INTERRUPT_DISABLED); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed to disable interrupts\n", __func__); + return ret; + } + + ret = tsl2583_set_als_time(chip); + if (ret < 0) + return ret; + + ret = tsl2583_set_als_gain(chip); + if (ret < 0) + return ret; + + usleep_range(3000, 3500); + + return tsl2583_set_power_state(chip, + TSL2583_CNTL_PWR_ON | TSL2583_CNTL_ADC_ENBL); +} + +/* Sysfs Interface Functions */ + +static ssize_t in_illuminance_input_target_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->als_mutex); + ret = sysfs_emit(buf, "%d\n", chip->als_settings.als_cal_target); + mutex_unlock(&chip->als_mutex); + + return ret; +} + +static ssize_t in_illuminance_input_target_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + int value; + + if (kstrtoint(buf, 0, &value) || !value) + return -EINVAL; + + mutex_lock(&chip->als_mutex); + chip->als_settings.als_cal_target = value; + mutex_unlock(&chip->als_mutex); + + return len; +} + +static ssize_t in_illuminance_calibrate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + int value, ret; + + if (kstrtoint(buf, 0, &value) || value != 1) + return -EINVAL; + + mutex_lock(&chip->als_mutex); + + ret = tsl2583_als_calibrate(indio_dev); + if (ret < 0) + goto done; + + ret = len; +done: + mutex_unlock(&chip->als_mutex); + + return ret; +} + +static ssize_t in_illuminance_lux_table_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + unsigned int i; + int offset = 0; + + for (i = 0; i < ARRAY_SIZE(chip->als_settings.als_device_lux); i++) { + offset += sysfs_emit_at(buf, offset, "%u,%u,%u,", + chip->als_settings.als_device_lux[i].ratio, + chip->als_settings.als_device_lux[i].ch0, + chip->als_settings.als_device_lux[i].ch1); + if (chip->als_settings.als_device_lux[i].ratio == 0) { + /* + * We just printed the first "0" entry. + * Now get rid of the extra "," and break. + */ + offset--; + break; + } + } + + offset += sysfs_emit_at(buf, offset, "\n"); + + return offset; +} + +static ssize_t in_illuminance_lux_table_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + const unsigned int max_ints = TSL2583_MAX_LUX_TABLE_ENTRIES * 3; + int value[TSL2583_MAX_LUX_TABLE_ENTRIES * 3 + 1]; + int ret = -EINVAL; + unsigned int n; + + mutex_lock(&chip->als_mutex); + + get_options(buf, ARRAY_SIZE(value), value); + + /* + * We now have an array of ints starting at value[1], and + * enumerated by value[0]. + * We expect each group of three ints is one table entry, + * and the last table entry is all 0. + */ + n = value[0]; + if ((n % 3) || n < 6 || n > max_ints) { + dev_err(dev, + "%s: The number of entries in the lux table must be a multiple of 3 and within the range [6, %d]\n", + __func__, max_ints); + goto done; + } + if ((value[n - 2] | value[n - 1] | value[n]) != 0) { + dev_err(dev, "%s: The last 3 entries in the lux table must be zeros.\n", + __func__); + goto done; + } + + memcpy(chip->als_settings.als_device_lux, &value[1], + value[0] * sizeof(value[1])); + + ret = len; + +done: + mutex_unlock(&chip->als_mutex); + + return ret; +} + +static IIO_CONST_ATTR(in_illuminance_calibscale_available, "1 8 16 111"); +static IIO_CONST_ATTR(in_illuminance_integration_time_available, + "0.050 0.100 0.150 0.200 0.250 0.300 0.350 0.400 0.450 0.500 0.550 0.600 0.650"); +static IIO_DEVICE_ATTR_RW(in_illuminance_input_target, 0); +static IIO_DEVICE_ATTR_WO(in_illuminance_calibrate, 0); +static IIO_DEVICE_ATTR_RW(in_illuminance_lux_table, 0); + +static struct attribute *sysfs_attrs_ctrl[] = { + &iio_const_attr_in_illuminance_calibscale_available.dev_attr.attr, + &iio_const_attr_in_illuminance_integration_time_available.dev_attr.attr, + &iio_dev_attr_in_illuminance_input_target.dev_attr.attr, + &iio_dev_attr_in_illuminance_calibrate.dev_attr.attr, + &iio_dev_attr_in_illuminance_lux_table.dev_attr.attr, + NULL +}; + +static const struct attribute_group tsl2583_attribute_group = { + .attrs = sysfs_attrs_ctrl, +}; + +static const struct iio_chan_spec tsl2583_channels[] = { + { + .type = IIO_LIGHT, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, + { + .type = IIO_LIGHT, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BOTH, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_INT_TIME), + }, +}; + +static int tsl2583_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct tsl2583_chip *chip = iio_priv(indio_dev); + int ret, pm_ret; + + ret = pm_runtime_resume_and_get(&chip->client->dev); + if (ret < 0) + return ret; + + mutex_lock(&chip->als_mutex); + + ret = -EINVAL; + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_LIGHT) { + ret = tsl2583_get_lux(indio_dev); + if (ret < 0) + goto read_done; + + /* + * From page 20 of the TSL2581, TSL2583 data + * sheet (TAOS134 − MARCH 2011): + * + * One of the photodiodes (channel 0) is + * sensitive to both visible and infrared light, + * while the second photodiode (channel 1) is + * sensitive primarily to infrared light. + */ + if (chan->channel2 == IIO_MOD_LIGHT_BOTH) + *val = chip->als_cur_info.als_ch0; + else + *val = chip->als_cur_info.als_ch1; + + ret = IIO_VAL_INT; + } + break; + case IIO_CHAN_INFO_PROCESSED: + if (chan->type == IIO_LIGHT) { + ret = tsl2583_get_lux(indio_dev); + if (ret < 0) + goto read_done; + + *val = ret; + ret = IIO_VAL_INT; + } + break; + case IIO_CHAN_INFO_CALIBBIAS: + if (chan->type == IIO_LIGHT) { + *val = chip->als_settings.als_gain_trim; + ret = IIO_VAL_INT; + } + break; + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->type == IIO_LIGHT) { + *val = gainadj[chip->als_settings.als_gain].mean; + ret = IIO_VAL_INT; + } + break; + case IIO_CHAN_INFO_INT_TIME: + if (chan->type == IIO_LIGHT) { + *val = 0; + *val2 = chip->als_settings.als_time; + ret = IIO_VAL_INT_PLUS_MICRO; + } + break; + default: + break; + } + +read_done: + mutex_unlock(&chip->als_mutex); + + if (ret < 0) { + pm_runtime_put_autosuspend(&chip->client->dev); + return ret; + } + + /* + * Preserve the ret variable if the call to + * pm_runtime_put_autosuspend() is successful so the reading + * (if applicable) is returned to user space. + */ + pm_ret = pm_runtime_put_autosuspend(&chip->client->dev); + if (pm_ret < 0) + return pm_ret; + + return ret; +} + +static int tsl2583_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct tsl2583_chip *chip = iio_priv(indio_dev); + int ret; + + ret = pm_runtime_resume_and_get(&chip->client->dev); + if (ret < 0) + return ret; + + mutex_lock(&chip->als_mutex); + + ret = -EINVAL; + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + if (chan->type == IIO_LIGHT) { + chip->als_settings.als_gain_trim = val; + ret = 0; + } + break; + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->type == IIO_LIGHT) { + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(gainadj); i++) { + if (gainadj[i].mean == val) { + chip->als_settings.als_gain = i; + ret = tsl2583_set_als_gain(chip); + break; + } + } + } + break; + case IIO_CHAN_INFO_INT_TIME: + if (chan->type == IIO_LIGHT && !val && val2 >= 50 && + val2 <= 650 && !(val2 % 50)) { + chip->als_settings.als_time = val2; + ret = tsl2583_set_als_time(chip); + } + break; + default: + break; + } + + mutex_unlock(&chip->als_mutex); + + if (ret < 0) { + pm_runtime_put_autosuspend(&chip->client->dev); + return ret; + } + + ret = pm_runtime_put_autosuspend(&chip->client->dev); + if (ret < 0) + return ret; + + return 0; +} + +static const struct iio_info tsl2583_info = { + .attrs = &tsl2583_attribute_group, + .read_raw = tsl2583_read_raw, + .write_raw = tsl2583_write_raw, +}; + +static int tsl2583_probe(struct i2c_client *clientp) +{ + int ret; + struct tsl2583_chip *chip; + struct iio_dev *indio_dev; + + if (!i2c_check_functionality(clientp->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&clientp->dev, "%s: i2c smbus byte data functionality is unsupported\n", + __func__); + return -EOPNOTSUPP; + } + + indio_dev = devm_iio_device_alloc(&clientp->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + chip->client = clientp; + i2c_set_clientdata(clientp, indio_dev); + + mutex_init(&chip->als_mutex); + + ret = i2c_smbus_read_byte_data(clientp, + TSL2583_CMD_REG | TSL2583_CHIPID); + if (ret < 0) { + dev_err(&clientp->dev, + "%s: failed to read the chip ID register\n", __func__); + return ret; + } + + if ((ret & TSL2583_CHIP_ID_MASK) != TSL2583_CHIP_ID) { + dev_err(&clientp->dev, "%s: received an unknown chip ID %x\n", + __func__, ret); + return -EINVAL; + } + + indio_dev->info = &tsl2583_info; + indio_dev->channels = tsl2583_channels; + indio_dev->num_channels = ARRAY_SIZE(tsl2583_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = chip->client->name; + + pm_runtime_enable(&clientp->dev); + pm_runtime_set_autosuspend_delay(&clientp->dev, + TSL2583_POWER_OFF_DELAY_MS); + pm_runtime_use_autosuspend(&clientp->dev); + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&clientp->dev, "%s: iio registration failed\n", + __func__); + return ret; + } + + /* Load up the V2 defaults (these are hard coded defaults for now) */ + tsl2583_defaults(chip); + + dev_info(&clientp->dev, "Light sensor found.\n"); + + return 0; +} + +static void tsl2583_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct tsl2583_chip *chip = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + tsl2583_set_power_state(chip, TSL2583_CNTL_PWR_OFF); +} + +static int tsl2583_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct tsl2583_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->als_mutex); + + ret = tsl2583_set_power_state(chip, TSL2583_CNTL_PWR_OFF); + + mutex_unlock(&chip->als_mutex); + + return ret; +} + +static int tsl2583_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct tsl2583_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->als_mutex); + + ret = tsl2583_chip_init_and_power_on(indio_dev); + + mutex_unlock(&chip->als_mutex); + + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(tsl2583_pm_ops, tsl2583_suspend, + tsl2583_resume, NULL); + +static const struct i2c_device_id tsl2583_idtable[] = { + { .name = "tsl2580" }, + { .name = "tsl2581" }, + { .name = "tsl2583" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tsl2583_idtable); + +static const struct of_device_id tsl2583_of_match[] = { + { .compatible = "amstaos,tsl2580", }, + { .compatible = "amstaos,tsl2581", }, + { .compatible = "amstaos,tsl2583", }, + { } +}; +MODULE_DEVICE_TABLE(of, tsl2583_of_match); + +/* Driver definition */ +static struct i2c_driver tsl2583_driver = { + .driver = { + .name = "tsl2583", + .pm = pm_ptr(&tsl2583_pm_ops), + .of_match_table = tsl2583_of_match, + }, + .id_table = tsl2583_idtable, + .probe = tsl2583_probe, + .remove = tsl2583_remove, +}; +module_i2c_driver(tsl2583_driver); + +MODULE_AUTHOR("J. August Brenner "); +MODULE_AUTHOR("Brian Masney "); +MODULE_DESCRIPTION("TAOS tsl2583 ambient light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/tsl2591.c b/drivers/iio/light/tsl2591.c new file mode 100644 index 000000000..ef3ed9635 --- /dev/null +++ b/drivers/iio/light/tsl2591.c @@ -0,0 +1,1152 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Joe Sandom + * + * Datasheet: https://ams.com/tsl25911#tab/documents + * + * Device driver for the TAOS TSL2591. This is a very-high sensitivity + * light-to-digital converter that transforms light intensity into a digital + * signal. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +/* ADC integration time, field value to time in ms */ +#define TSL2591_FVAL_TO_MSEC(x) (((x) + 1) * 100) +/* ADC integration time, field value to time in seconds */ +#define TSL2591_FVAL_TO_SEC(x) ((x) + 1) +/* ADC integration time, time in seconds to field value */ +#define TSL2591_SEC_TO_FVAL(x) ((x) - 1) + +/* TSL2591 register set */ +#define TSL2591_ENABLE 0x00 +#define TSL2591_CONTROL 0x01 +#define TSL2591_AILTL 0x04 +#define TSL2591_AILTH 0x05 +#define TSL2591_AIHTL 0x06 +#define TSL2591_AIHTH 0x07 +#define TSL2591_NP_AILTL 0x08 +#define TSL2591_NP_AILTH 0x09 +#define TSL2591_NP_AIHTL 0x0A +#define TSL2591_NP_AIHTH 0x0B +#define TSL2591_PERSIST 0x0C +#define TSL2591_PACKAGE_ID 0x11 +#define TSL2591_DEVICE_ID 0x12 +#define TSL2591_STATUS 0x13 +#define TSL2591_C0_DATAL 0x14 +#define TSL2591_C0_DATAH 0x15 +#define TSL2591_C1_DATAL 0x16 +#define TSL2591_C1_DATAH 0x17 + +/* TSL2591 command register definitions */ +#define TSL2591_CMD_NOP 0xA0 +#define TSL2591_CMD_SF_INTSET 0xE4 +#define TSL2591_CMD_SF_CALS_I 0xE5 +#define TSL2591_CMD_SF_CALS_NPI 0xE7 +#define TSL2591_CMD_SF_CNP_ALSI 0xEA + +/* TSL2591 enable register definitions */ +#define TSL2591_PWR_ON 0x01 +#define TSL2591_PWR_OFF 0x00 +#define TSL2591_ENABLE_ALS 0x02 +#define TSL2591_ENABLE_ALS_INT 0x10 +#define TSL2591_ENABLE_SLEEP_INT 0x40 +#define TSL2591_ENABLE_NP_INT 0x80 + +/* TSL2591 control register definitions */ +#define TSL2591_CTRL_ALS_INTEGRATION_100MS 0x00 +#define TSL2591_CTRL_ALS_INTEGRATION_200MS 0x01 +#define TSL2591_CTRL_ALS_INTEGRATION_300MS 0x02 +#define TSL2591_CTRL_ALS_INTEGRATION_400MS 0x03 +#define TSL2591_CTRL_ALS_INTEGRATION_500MS 0x04 +#define TSL2591_CTRL_ALS_INTEGRATION_600MS 0x05 +#define TSL2591_CTRL_ALS_LOW_GAIN 0x00 +#define TSL2591_CTRL_ALS_MED_GAIN 0x10 +#define TSL2591_CTRL_ALS_HIGH_GAIN 0x20 +#define TSL2591_CTRL_ALS_MAX_GAIN 0x30 +#define TSL2591_CTRL_SYS_RESET 0x80 + +/* TSL2591 persist register definitions */ +#define TSL2591_PRST_ALS_INT_CYCLE_0 0x00 +#define TSL2591_PRST_ALS_INT_CYCLE_ANY 0x01 +#define TSL2591_PRST_ALS_INT_CYCLE_2 0x02 +#define TSL2591_PRST_ALS_INT_CYCLE_3 0x03 +#define TSL2591_PRST_ALS_INT_CYCLE_5 0x04 +#define TSL2591_PRST_ALS_INT_CYCLE_10 0x05 +#define TSL2591_PRST_ALS_INT_CYCLE_15 0x06 +#define TSL2591_PRST_ALS_INT_CYCLE_20 0x07 +#define TSL2591_PRST_ALS_INT_CYCLE_25 0x08 +#define TSL2591_PRST_ALS_INT_CYCLE_30 0x09 +#define TSL2591_PRST_ALS_INT_CYCLE_35 0x0A +#define TSL2591_PRST_ALS_INT_CYCLE_40 0x0B +#define TSL2591_PRST_ALS_INT_CYCLE_45 0x0C +#define TSL2591_PRST_ALS_INT_CYCLE_50 0x0D +#define TSL2591_PRST_ALS_INT_CYCLE_55 0x0E +#define TSL2591_PRST_ALS_INT_CYCLE_60 0x0F +#define TSL2591_PRST_ALS_INT_CYCLE_MAX (BIT(4) - 1) + +/* TSL2591 PID register mask */ +#define TSL2591_PACKAGE_ID_MASK GENMASK(5, 4) + +/* TSL2591 ID register mask */ +#define TSL2591_DEVICE_ID_MASK GENMASK(7, 0) + +/* TSL2591 status register masks */ +#define TSL2591_STS_ALS_VALID_MASK BIT(0) +#define TSL2591_STS_ALS_INT_MASK BIT(4) +#define TSL2591_STS_NPERS_INT_MASK BIT(5) +#define TSL2591_STS_VAL_HIGH_MASK BIT(0) + +/* TSL2591 constant values */ +#define TSL2591_PACKAGE_ID_VAL 0x00 +#define TSL2591_DEVICE_ID_VAL 0x50 + +/* Power off suspend delay time MS */ +#define TSL2591_POWER_OFF_DELAY_MS 2000 + +/* TSL2591 default values */ +#define TSL2591_DEFAULT_ALS_INT_TIME TSL2591_CTRL_ALS_INTEGRATION_300MS +#define TSL2591_DEFAULT_ALS_GAIN TSL2591_CTRL_ALS_MED_GAIN +#define TSL2591_DEFAULT_ALS_PERSIST TSL2591_PRST_ALS_INT_CYCLE_ANY +#define TSL2591_DEFAULT_ALS_LOWER_THRESH 100 +#define TSL2591_DEFAULT_ALS_UPPER_THRESH 1500 + +/* TSL2591 number of data registers */ +#define TSL2591_NUM_DATA_REGISTERS 4 + +/* TSL2591 number of valid status reads on ADC complete */ +#define TSL2591_ALS_STS_VALID_COUNT 10 + +/* TSL2591 delay period between polls when checking for ALS valid flag */ +#define TSL2591_DELAY_PERIOD_US 10000 + +/* TSL2591 maximum values */ +#define TSL2591_MAX_ALS_INT_TIME_MS 600 +#define TSL2591_ALS_MAX_VALUE (BIT(16) - 1) + +/* + * LUX calculations; + * AGAIN values from Adafruit's TSL2591 Arduino library + * https://github.com/adafruit/Adafruit_TSL2591_Library + */ +#define TSL2591_CTRL_ALS_LOW_GAIN_MULTIPLIER 1 +#define TSL2591_CTRL_ALS_MED_GAIN_MULTIPLIER 25 +#define TSL2591_CTRL_ALS_HIGH_GAIN_MULTIPLIER 428 +#define TSL2591_CTRL_ALS_MAX_GAIN_MULTIPLIER 9876 +#define TSL2591_LUX_COEFFICIENT 408 + +struct tsl2591_als_settings { + u16 als_lower_thresh; + u16 als_upper_thresh; + u8 als_int_time; + u8 als_persist; + u8 als_gain; +}; + +struct tsl2591_chip { + struct tsl2591_als_settings als_settings; + struct i2c_client *client; + /* + * Keep als_settings in sync with hardware state + * and ensure multiple readers are serialized. + */ + struct mutex als_mutex; + bool events_enabled; +}; + +/* + * Period table is ALS persist cycle x integration time setting + * Integration times: 100ms, 200ms, 300ms, 400ms, 500ms, 600ms + * ALS cycles: 1, 2, 3, 5, 10, 20, 25, 30, 35, 40, 45, 50, 55, 60 + */ +static const char * const tsl2591_als_period_list[] = { + "0.1 0.2 0.3 0.5 1.0 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0", + "0.2 0.4 0.6 1.0 2.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0 12.0", + "0.3 0.6 0.9 1.5 3.0 6.0 7.5 9.0 10.5 12.0 13.5 15.0 16.5 18.0", + "0.4 0.8 1.2 2.0 4.0 8.0 10.0 12.0 14.0 16.0 18.0 20.0 22.0 24.0", + "0.5 1.0 1.5 2.5 5.0 10.0 12.5 15.0 17.5 20.0 22.5 25.0 27.5 30.0", + "0.6 1.2 1.8 3.0 6.0 12.0 15.0 18.0 21.0 24.0 27.0 30.0 33.0 36.0", +}; + +static const int tsl2591_int_time_available[] = { + 1, 2, 3, 4, 5, 6, +}; + +static const int tsl2591_calibscale_available[] = { + 1, 25, 428, 9876, +}; + +static const u8 tsl2591_persist_table[] = { + [TSL2591_PRST_ALS_INT_CYCLE_ANY] = 1, + [TSL2591_PRST_ALS_INT_CYCLE_2] = 2, + [TSL2591_PRST_ALS_INT_CYCLE_3] = 3, + [TSL2591_PRST_ALS_INT_CYCLE_5] = 5, + [TSL2591_PRST_ALS_INT_CYCLE_10] = 10, + [TSL2591_PRST_ALS_INT_CYCLE_15] = 15, + [TSL2591_PRST_ALS_INT_CYCLE_20] = 20, + [TSL2591_PRST_ALS_INT_CYCLE_25] = 25, + [TSL2591_PRST_ALS_INT_CYCLE_30] = 30, + [TSL2591_PRST_ALS_INT_CYCLE_35] = 35, + [TSL2591_PRST_ALS_INT_CYCLE_40] = 40, + [TSL2591_PRST_ALS_INT_CYCLE_45] = 45, + [TSL2591_PRST_ALS_INT_CYCLE_50] = 50, + [TSL2591_PRST_ALS_INT_CYCLE_55] = 55, + [TSL2591_PRST_ALS_INT_CYCLE_60] = 60, +}; + +static int tsl2591_set_als_lower_threshold(struct tsl2591_chip *chip, + u16 als_lower_threshold); +static int tsl2591_set_als_upper_threshold(struct tsl2591_chip *chip, + u16 als_upper_threshold); + +static int tsl2591_gain_to_multiplier(const u8 als_gain) +{ + switch (als_gain) { + case TSL2591_CTRL_ALS_LOW_GAIN: + return TSL2591_CTRL_ALS_LOW_GAIN_MULTIPLIER; + case TSL2591_CTRL_ALS_MED_GAIN: + return TSL2591_CTRL_ALS_MED_GAIN_MULTIPLIER; + case TSL2591_CTRL_ALS_HIGH_GAIN: + return TSL2591_CTRL_ALS_HIGH_GAIN_MULTIPLIER; + case TSL2591_CTRL_ALS_MAX_GAIN: + return TSL2591_CTRL_ALS_MAX_GAIN_MULTIPLIER; + default: + return -EINVAL; + } +} + +static int tsl2591_multiplier_to_gain(const u32 multiplier) +{ + switch (multiplier) { + case TSL2591_CTRL_ALS_LOW_GAIN_MULTIPLIER: + return TSL2591_CTRL_ALS_LOW_GAIN; + case TSL2591_CTRL_ALS_MED_GAIN_MULTIPLIER: + return TSL2591_CTRL_ALS_MED_GAIN; + case TSL2591_CTRL_ALS_HIGH_GAIN_MULTIPLIER: + return TSL2591_CTRL_ALS_HIGH_GAIN; + case TSL2591_CTRL_ALS_MAX_GAIN_MULTIPLIER: + return TSL2591_CTRL_ALS_MAX_GAIN; + default: + return -EINVAL; + } +} + +static int tsl2591_persist_cycle_to_lit(const u8 als_persist) +{ + if (als_persist >= ARRAY_SIZE(tsl2591_persist_table) || + !tsl2591_persist_table[als_persist]) + return -EINVAL; + + return tsl2591_persist_table[als_persist]; +} + +static int tsl2591_persist_lit_to_cycle(const u8 als_persist) +{ + for (unsigned int i = TSL2591_PRST_ALS_INT_CYCLE_ANY; + i < ARRAY_SIZE(tsl2591_persist_table); i++) { + if (als_persist == tsl2591_persist_table[i]) + return i; + } + + return -EINVAL; +} + +static int tsl2591_compatible_int_time(struct tsl2591_chip *chip, + const u32 als_integration_time) +{ + switch (als_integration_time) { + case TSL2591_CTRL_ALS_INTEGRATION_100MS: + case TSL2591_CTRL_ALS_INTEGRATION_200MS: + case TSL2591_CTRL_ALS_INTEGRATION_300MS: + case TSL2591_CTRL_ALS_INTEGRATION_400MS: + case TSL2591_CTRL_ALS_INTEGRATION_500MS: + case TSL2591_CTRL_ALS_INTEGRATION_600MS: + return 0; + default: + return -EINVAL; + } +} + +static int tsl2591_als_time_to_fval(const u32 als_integration_time) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(tsl2591_int_time_available); i++) { + if (als_integration_time == tsl2591_int_time_available[i]) + return TSL2591_SEC_TO_FVAL(als_integration_time); + } + + return -EINVAL; +} + +static int tsl2591_compatible_gain(struct tsl2591_chip *chip, const u8 als_gain) +{ + switch (als_gain) { + case TSL2591_CTRL_ALS_LOW_GAIN: + case TSL2591_CTRL_ALS_MED_GAIN: + case TSL2591_CTRL_ALS_HIGH_GAIN: + case TSL2591_CTRL_ALS_MAX_GAIN: + return 0; + default: + return -EINVAL; + } +} + +static int tsl2591_check_als_valid(struct i2c_client *client) +{ + int ret; + + ret = i2c_smbus_read_byte_data(client, TSL2591_CMD_NOP | TSL2591_STATUS); + if (ret < 0) { + dev_err(&client->dev, "Failed to read register\n"); + return -EINVAL; + } + + return FIELD_GET(TSL2591_STS_ALS_VALID_MASK, ret); +} + +static int tsl2591_wait_adc_complete(struct tsl2591_chip *chip) +{ + struct tsl2591_als_settings settings = chip->als_settings; + struct i2c_client *client = chip->client; + int delay; + int val; + int ret; + + delay = TSL2591_FVAL_TO_MSEC(settings.als_int_time); + if (!delay) + return -EINVAL; + + /* + * Sleep for ALS integration time to allow enough time or an ADC read + * cycle to complete. Check status after delay for ALS valid. + */ + msleep(delay); + + /* Check for status ALS valid flag for up to 100ms */ + ret = readx_poll_timeout(tsl2591_check_als_valid, client, + val, val == TSL2591_STS_VAL_HIGH_MASK, + TSL2591_DELAY_PERIOD_US, + TSL2591_DELAY_PERIOD_US * TSL2591_ALS_STS_VALID_COUNT); + if (ret) + dev_err(&client->dev, "Timed out waiting for valid ALS data\n"); + + return ret; +} + +/* + * tsl2591_read_channel_data - Reads raw channel data and calculates lux + * + * Formula for lux calculation; + * Derived from Adafruit's TSL2591 library + * Link: https://github.com/adafruit/Adafruit_TSL2591_Library + * Counts Per Lux (CPL) = (ATIME_ms * AGAIN) / LUX DF + * lux = ((C0DATA - C1DATA) * (1 - (C1DATA / C0DATA))) / CPL + * + * Scale values to get more representative value of lux i.e. + * lux = ((C0DATA - C1DATA) * (1000 - ((C1DATA * 1000) / C0DATA))) / CPL + * + * Channel 0 = IR + Visible + * Channel 1 = IR only + */ +static int tsl2591_read_channel_data(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + struct tsl2591_chip *chip = iio_priv(indio_dev); + struct tsl2591_als_settings *settings = &chip->als_settings; + struct i2c_client *client = chip->client; + u8 als_data[TSL2591_NUM_DATA_REGISTERS]; + int counts_per_lux, int_time_fval, gain_multi, lux; + u16 als_ch0, als_ch1; + int ret; + + ret = tsl2591_wait_adc_complete(chip); + if (ret < 0) { + dev_err(&client->dev, "No data available. Err: %d\n", ret); + return ret; + } + + ret = i2c_smbus_read_i2c_block_data(client, + TSL2591_CMD_NOP | TSL2591_C0_DATAL, + sizeof(als_data), als_data); + if (ret < 0) { + dev_err(&client->dev, "Failed to read data bytes"); + return ret; + } + + als_ch0 = get_unaligned_le16(&als_data[0]); + als_ch1 = get_unaligned_le16(&als_data[2]); + + switch (chan->type) { + case IIO_INTENSITY: + if (chan->channel2 == IIO_MOD_LIGHT_BOTH) + *val = als_ch0; + else if (chan->channel2 == IIO_MOD_LIGHT_IR) + *val = als_ch1; + else + return -EINVAL; + break; + case IIO_LIGHT: + gain_multi = tsl2591_gain_to_multiplier(settings->als_gain); + if (gain_multi < 0) { + dev_err(&client->dev, "Invalid multiplier"); + return gain_multi; + } + + int_time_fval = TSL2591_FVAL_TO_MSEC(settings->als_int_time); + /* Calculate counts per lux value */ + counts_per_lux = (int_time_fval * gain_multi) / TSL2591_LUX_COEFFICIENT; + + dev_dbg(&client->dev, "Counts Per Lux: %d\n", counts_per_lux); + + /* Calculate lux value */ + lux = ((als_ch0 - als_ch1) * + (1000 - ((als_ch1 * 1000) / als_ch0))) / counts_per_lux; + + dev_dbg(&client->dev, "Raw lux calculation: %d\n", lux); + + /* Divide by 1000 to get real lux value before scaling */ + *val = lux / 1000; + + /* Get the decimal part of lux reading */ + *val2 = (lux - (*val * 1000)) * 1000; + + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tsl2591_set_als_gain_int_time(struct tsl2591_chip *chip) +{ + struct tsl2591_als_settings als_settings = chip->als_settings; + struct i2c_client *client = chip->client; + int ret; + + ret = i2c_smbus_write_byte_data(client, + TSL2591_CMD_NOP | TSL2591_CONTROL, + als_settings.als_int_time | als_settings.als_gain); + if (ret) + dev_err(&client->dev, "Failed to set als gain & int time\n"); + + return ret; +} + +static int tsl2591_set_als_lower_threshold(struct tsl2591_chip *chip, + u16 als_lower_threshold) +{ + struct tsl2591_als_settings als_settings = chip->als_settings; + struct i2c_client *client = chip->client; + u16 als_upper_threshold; + u8 als_lower_l; + u8 als_lower_h; + int ret; + + chip->als_settings.als_lower_thresh = als_lower_threshold; + + /* + * Lower threshold should not be greater or equal to upper. + * If this is the case, then assert upper threshold to new lower + * threshold + 1 to avoid ordering issues when setting thresholds. + */ + if (als_lower_threshold >= als_settings.als_upper_thresh) { + als_upper_threshold = als_lower_threshold + 1; + tsl2591_set_als_upper_threshold(chip, als_upper_threshold); + } + + als_lower_l = als_lower_threshold; + als_lower_h = als_lower_threshold >> 8; + + ret = i2c_smbus_write_byte_data(client, + TSL2591_CMD_NOP | TSL2591_AILTL, + als_lower_l); + if (ret) { + dev_err(&client->dev, "Failed to set als lower threshold\n"); + return ret; + } + + ret = i2c_smbus_write_byte_data(client, + TSL2591_CMD_NOP | TSL2591_AILTH, + als_lower_h); + if (ret) { + dev_err(&client->dev, "Failed to set als lower threshold\n"); + return ret; + } + + return 0; +} + +static int tsl2591_set_als_upper_threshold(struct tsl2591_chip *chip, + u16 als_upper_threshold) +{ + struct tsl2591_als_settings als_settings = chip->als_settings; + struct i2c_client *client = chip->client; + u16 als_lower_threshold; + u8 als_upper_l; + u8 als_upper_h; + int ret; + + if (als_upper_threshold > TSL2591_ALS_MAX_VALUE) + return -EINVAL; + + chip->als_settings.als_upper_thresh = als_upper_threshold; + + /* + * Upper threshold should not be less than lower. If this + * is the case, then assert lower threshold to new upper + * threshold - 1 to avoid ordering issues when setting thresholds. + */ + if (als_upper_threshold < als_settings.als_lower_thresh) { + als_lower_threshold = als_upper_threshold - 1; + tsl2591_set_als_lower_threshold(chip, als_lower_threshold); + } + + als_upper_l = als_upper_threshold; + als_upper_h = als_upper_threshold >> 8; + + ret = i2c_smbus_write_byte_data(client, + TSL2591_CMD_NOP | TSL2591_AIHTL, + als_upper_l); + if (ret) { + dev_err(&client->dev, "Failed to set als upper threshold\n"); + return ret; + } + + ret = i2c_smbus_write_byte_data(client, + TSL2591_CMD_NOP | TSL2591_AIHTH, + als_upper_h); + if (ret) { + dev_err(&client->dev, "Failed to set als upper threshold\n"); + return ret; + } + + return 0; +} + +static int tsl2591_set_als_persist_cycle(struct tsl2591_chip *chip, + u8 als_persist) +{ + struct i2c_client *client = chip->client; + int ret; + + ret = i2c_smbus_write_byte_data(client, + TSL2591_CMD_NOP | TSL2591_PERSIST, + als_persist); + if (ret) + dev_err(&client->dev, "Failed to set als persist cycle\n"); + + chip->als_settings.als_persist = als_persist; + + return ret; +} + +static int tsl2591_set_power_state(struct tsl2591_chip *chip, u8 state) +{ + struct i2c_client *client = chip->client; + int ret; + + ret = i2c_smbus_write_byte_data(client, + TSL2591_CMD_NOP | TSL2591_ENABLE, + state); + if (ret) + dev_err(&client->dev, + "Failed to set the power state to %#04x\n", state); + + return ret; +} + +static ssize_t tsl2591_in_illuminance_period_available_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2591_chip *chip = iio_priv(indio_dev); + + return sysfs_emit(buf, "%s\n", + tsl2591_als_period_list[chip->als_settings.als_int_time]); +} + +static IIO_DEVICE_ATTR_RO(tsl2591_in_illuminance_period_available, 0); + +static struct attribute *tsl2591_event_attrs_ctrl[] = { + &iio_dev_attr_tsl2591_in_illuminance_period_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group tsl2591_event_attribute_group = { + .attrs = tsl2591_event_attrs_ctrl, +}; + +static const struct iio_event_spec tsl2591_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_PERIOD) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec tsl2591_channels[] = { + { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE) + }, + { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BOTH, + .event_spec = tsl2591_events, + .num_event_specs = ARRAY_SIZE(tsl2591_events), + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE) + }, + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE) + }, +}; + +static int tsl2591_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct tsl2591_chip *chip = iio_priv(indio_dev); + struct i2c_client *client = chip->client; + int ret; + + pm_runtime_get_sync(&client->dev); + + mutex_lock(&chip->als_mutex); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type != IIO_INTENSITY) { + ret = -EINVAL; + goto err_unlock; + } + + ret = tsl2591_read_channel_data(indio_dev, chan, val, val2); + if (ret < 0) + goto err_unlock; + + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_PROCESSED: + if (chan->type != IIO_LIGHT) { + ret = -EINVAL; + goto err_unlock; + } + + ret = tsl2591_read_channel_data(indio_dev, chan, val, val2); + if (ret < 0) + break; + + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case IIO_CHAN_INFO_INT_TIME: + if (chan->type != IIO_INTENSITY) { + ret = -EINVAL; + goto err_unlock; + } + + *val = TSL2591_FVAL_TO_SEC(chip->als_settings.als_int_time); + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->type != IIO_INTENSITY) { + ret = -EINVAL; + goto err_unlock; + } + + *val = tsl2591_gain_to_multiplier(chip->als_settings.als_gain); + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + break; + } + +err_unlock: + mutex_unlock(&chip->als_mutex); + + pm_runtime_put_autosuspend(&client->dev); + + return ret; +} + +static int tsl2591_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct tsl2591_chip *chip = iio_priv(indio_dev); + int int_time; + int gain; + int ret; + + mutex_lock(&chip->als_mutex); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + int_time = tsl2591_als_time_to_fval(val); + if (int_time < 0) { + ret = int_time; + goto err_unlock; + } + ret = tsl2591_compatible_int_time(chip, int_time); + if (ret < 0) + goto err_unlock; + + chip->als_settings.als_int_time = int_time; + break; + case IIO_CHAN_INFO_CALIBSCALE: + gain = tsl2591_multiplier_to_gain(val); + if (gain < 0) { + ret = gain; + goto err_unlock; + } + ret = tsl2591_compatible_gain(chip, gain); + if (ret < 0) + goto err_unlock; + + chip->als_settings.als_gain = gain; + break; + default: + ret = -EINVAL; + goto err_unlock; + } + + ret = tsl2591_set_als_gain_int_time(chip); + +err_unlock: + mutex_unlock(&chip->als_mutex); + return ret; +} + +static int tsl2591_read_available(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + *length = ARRAY_SIZE(tsl2591_int_time_available); + *vals = tsl2591_int_time_available; + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + + case IIO_CHAN_INFO_CALIBSCALE: + *length = ARRAY_SIZE(tsl2591_calibscale_available); + *vals = tsl2591_calibscale_available; + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int tsl2591_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, + int *val2) +{ + struct tsl2591_chip *chip = iio_priv(indio_dev); + struct i2c_client *client = chip->client; + int als_persist, int_time, period; + int ret; + + mutex_lock(&chip->als_mutex); + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + *val = chip->als_settings.als_upper_thresh; + break; + case IIO_EV_DIR_FALLING: + *val = chip->als_settings.als_lower_thresh; + break; + default: + ret = -EINVAL; + goto err_unlock; + } + ret = IIO_VAL_INT; + break; + case IIO_EV_INFO_PERIOD: + ret = i2c_smbus_read_byte_data(client, + TSL2591_CMD_NOP | TSL2591_PERSIST); + if (ret <= 0 || ret > TSL2591_PRST_ALS_INT_CYCLE_MAX) + goto err_unlock; + + als_persist = tsl2591_persist_cycle_to_lit(ret); + int_time = TSL2591_FVAL_TO_MSEC(chip->als_settings.als_int_time); + period = als_persist * (int_time * MSEC_PER_SEC); + + *val = period / USEC_PER_SEC; + *val2 = period % USEC_PER_SEC; + + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + ret = -EINVAL; + break; + } + +err_unlock: + mutex_unlock(&chip->als_mutex); + return ret; +} + +static int tsl2591_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, + int val2) +{ + struct tsl2591_chip *chip = iio_priv(indio_dev); + int period, int_time, als_persist; + int ret; + + if (val < 0 || val2 < 0) + return -EINVAL; + + mutex_lock(&chip->als_mutex); + + switch (info) { + case IIO_EV_INFO_VALUE: + if (val > TSL2591_ALS_MAX_VALUE) { + ret = -EINVAL; + goto err_unlock; + } + + switch (dir) { + case IIO_EV_DIR_RISING: + ret = tsl2591_set_als_upper_threshold(chip, val); + if (ret < 0) + goto err_unlock; + break; + case IIO_EV_DIR_FALLING: + ret = tsl2591_set_als_lower_threshold(chip, val); + if (ret < 0) + goto err_unlock; + break; + default: + ret = -EINVAL; + goto err_unlock; + } + break; + case IIO_EV_INFO_PERIOD: + int_time = TSL2591_FVAL_TO_MSEC(chip->als_settings.als_int_time); + + period = ((val * MSEC_PER_SEC) + + (val2 / MSEC_PER_SEC)) / int_time; + + als_persist = tsl2591_persist_lit_to_cycle(period); + if (als_persist < 0) { + ret = -EINVAL; + goto err_unlock; + } + + ret = tsl2591_set_als_persist_cycle(chip, als_persist); + if (ret < 0) + goto err_unlock; + break; + default: + ret = -EINVAL; + break; + } + +err_unlock: + mutex_unlock(&chip->als_mutex); + return ret; +} + +static int tsl2591_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct tsl2591_chip *chip = iio_priv(indio_dev); + + return chip->events_enabled; +} + +static int tsl2591_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct tsl2591_chip *chip = iio_priv(indio_dev); + struct i2c_client *client = chip->client; + + if (state && !chip->events_enabled) { + chip->events_enabled = true; + pm_runtime_get_sync(&client->dev); + } else if (!state && chip->events_enabled) { + chip->events_enabled = false; + pm_runtime_put_autosuspend(&client->dev); + } + + return 0; +} + +static const struct iio_info tsl2591_info = { + .event_attrs = &tsl2591_event_attribute_group, + .read_raw = tsl2591_read_raw, + .write_raw = tsl2591_write_raw, + .read_avail = tsl2591_read_available, + .read_event_value = tsl2591_read_event_value, + .write_event_value = tsl2591_write_event_value, + .read_event_config = tsl2591_read_event_config, + .write_event_config = tsl2591_write_event_config, +}; + +static const struct iio_info tsl2591_info_no_irq = { + .read_raw = tsl2591_read_raw, + .write_raw = tsl2591_write_raw, + .read_avail = tsl2591_read_available, +}; + +static int tsl2591_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2591_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->als_mutex); + ret = tsl2591_set_power_state(chip, TSL2591_PWR_OFF); + mutex_unlock(&chip->als_mutex); + + return ret; +} + +static int tsl2591_resume(struct device *dev) +{ + int power_state = TSL2591_PWR_ON | TSL2591_ENABLE_ALS; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2591_chip *chip = iio_priv(indio_dev); + int ret; + + if (chip->events_enabled) + power_state |= TSL2591_ENABLE_ALS_INT; + + mutex_lock(&chip->als_mutex); + ret = tsl2591_set_power_state(chip, power_state); + mutex_unlock(&chip->als_mutex); + + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(tsl2591_pm_ops, tsl2591_suspend, + tsl2591_resume, NULL); + +static irqreturn_t tsl2591_event_handler(int irq, void *private) +{ + struct iio_dev *dev_info = private; + struct tsl2591_chip *chip = iio_priv(dev_info); + struct i2c_client *client = chip->client; + + if (!chip->events_enabled) + return IRQ_NONE; + + iio_push_event(dev_info, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(dev_info)); + + /* Clear ALS irq */ + i2c_smbus_write_byte(client, TSL2591_CMD_SF_CALS_NPI); + + return IRQ_HANDLED; +} + +static int tsl2591_load_defaults(struct tsl2591_chip *chip) +{ + int ret; + + chip->als_settings.als_int_time = TSL2591_DEFAULT_ALS_INT_TIME; + chip->als_settings.als_gain = TSL2591_DEFAULT_ALS_GAIN; + chip->als_settings.als_lower_thresh = TSL2591_DEFAULT_ALS_LOWER_THRESH; + chip->als_settings.als_upper_thresh = TSL2591_DEFAULT_ALS_UPPER_THRESH; + + ret = tsl2591_set_als_gain_int_time(chip); + if (ret < 0) + return ret; + + ret = tsl2591_set_als_persist_cycle(chip, TSL2591_DEFAULT_ALS_PERSIST); + if (ret < 0) + return ret; + + ret = tsl2591_set_als_lower_threshold(chip, TSL2591_DEFAULT_ALS_LOWER_THRESH); + if (ret < 0) + return ret; + + ret = tsl2591_set_als_upper_threshold(chip, TSL2591_DEFAULT_ALS_UPPER_THRESH); + if (ret < 0) + return ret; + + return 0; +} + +static void tsl2591_chip_off(void *data) +{ + struct iio_dev *indio_dev = data; + struct tsl2591_chip *chip = iio_priv(indio_dev); + struct i2c_client *client = chip->client; + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + pm_runtime_put_noidle(&client->dev); + + tsl2591_set_power_state(chip, TSL2591_PWR_OFF); +} + +static int tsl2591_probe(struct i2c_client *client) +{ + struct tsl2591_chip *chip; + struct iio_dev *indio_dev; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&client->dev, + "I2C smbus byte data functionality is not supported\n"); + return -EOPNOTSUPP; + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + chip->client = client; + i2c_set_clientdata(client, indio_dev); + + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, tsl2591_event_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "tsl2591_irq", indio_dev); + if (ret) + return ret; + indio_dev->info = &tsl2591_info; + } else { + indio_dev->info = &tsl2591_info_no_irq; + } + + mutex_init(&chip->als_mutex); + + ret = i2c_smbus_read_byte_data(client, + TSL2591_CMD_NOP | TSL2591_DEVICE_ID); + if (ret < 0) { + dev_err(&client->dev, + "Failed to read the device ID register\n"); + return ret; + } + ret = FIELD_GET(TSL2591_DEVICE_ID_MASK, ret); + if (ret != TSL2591_DEVICE_ID_VAL) { + dev_err(&client->dev, "Device ID: %#04x unknown\n", ret); + return -EINVAL; + } + + indio_dev->channels = tsl2591_channels; + indio_dev->num_channels = ARRAY_SIZE(tsl2591_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = chip->client->name; + chip->events_enabled = false; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, + TSL2591_POWER_OFF_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + + /* + * Add chip off to automatically managed path and disable runtime + * power management. This ensures that the chip power management + * is handled correctly on driver remove. tsl2591_chip_off() must be + * added to the managed path after pm runtime is enabled and before + * any error exit paths are met to ensure we're not left in a state + * of pm runtime not being disabled properly. + */ + ret = devm_add_action_or_reset(&client->dev, tsl2591_chip_off, + indio_dev); + if (ret < 0) + return -EINVAL; + + ret = tsl2591_load_defaults(chip); + if (ret < 0) { + dev_err(&client->dev, "Failed to load sensor defaults\n"); + return -EINVAL; + } + + ret = i2c_smbus_write_byte(client, TSL2591_CMD_SF_CALS_NPI); + if (ret < 0) { + dev_err(&client->dev, "Failed to clear als irq\n"); + return -EINVAL; + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct of_device_id tsl2591_of_match[] = { + { .compatible = "amstaos,tsl2591"}, + { } +}; +MODULE_DEVICE_TABLE(of, tsl2591_of_match); + +static struct i2c_driver tsl2591_driver = { + .driver = { + .name = "tsl2591", + .pm = pm_ptr(&tsl2591_pm_ops), + .of_match_table = tsl2591_of_match, + }, + .probe = tsl2591_probe +}; +module_i2c_driver(tsl2591_driver); + +MODULE_AUTHOR("Joe Sandom "); +MODULE_DESCRIPTION("TAOS tsl2591 ambient light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/tsl2772.c b/drivers/iio/light/tsl2772.c new file mode 100644 index 000000000..4486a1d9d --- /dev/null +++ b/drivers/iio/light/tsl2772.c @@ -0,0 +1,1956 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Device driver for monitoring ambient light intensity in (lux) and proximity + * detection (prox) within the TAOS TSL2571, TSL2671, TMD2671, TSL2771, TMD2771, + * TSL2572, TSL2672, TMD2672, TSL2772, and TMD2772 devices. + * + * Copyright (c) 2012, TAOS Corporation. + * Copyright (c) 2017-2018 Brian Masney + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* Cal defs */ +#define PROX_STAT_CAL 0 +#define PROX_STAT_SAMP 1 +#define MAX_SAMPLES_CAL 200 + +/* TSL2772 Device ID */ +#define TRITON_ID 0x00 +#define SWORDFISH_ID 0x30 +#define HALIBUT_ID 0x20 + +/* Lux calculation constants */ +#define TSL2772_LUX_CALC_OVER_FLOW 65535 + +/* + * TAOS Register definitions - Note: depending on device, some of these register + * are not used and the register address is benign. + */ + +/* Register offsets */ +#define TSL2772_MAX_CONFIG_REG 16 + +/* Device Registers and Masks */ +#define TSL2772_CNTRL 0x00 +#define TSL2772_ALS_TIME 0X01 +#define TSL2772_PRX_TIME 0x02 +#define TSL2772_WAIT_TIME 0x03 +#define TSL2772_ALS_MINTHRESHLO 0X04 +#define TSL2772_ALS_MINTHRESHHI 0X05 +#define TSL2772_ALS_MAXTHRESHLO 0X06 +#define TSL2772_ALS_MAXTHRESHHI 0X07 +#define TSL2772_PRX_MINTHRESHLO 0X08 +#define TSL2772_PRX_MINTHRESHHI 0X09 +#define TSL2772_PRX_MAXTHRESHLO 0X0A +#define TSL2772_PRX_MAXTHRESHHI 0X0B +#define TSL2772_PERSISTENCE 0x0C +#define TSL2772_ALS_PRX_CONFIG 0x0D +#define TSL2772_PRX_COUNT 0x0E +#define TSL2772_GAIN 0x0F +#define TSL2772_NOTUSED 0x10 +#define TSL2772_REVID 0x11 +#define TSL2772_CHIPID 0x12 +#define TSL2772_STATUS 0x13 +#define TSL2772_ALS_CHAN0LO 0x14 +#define TSL2772_ALS_CHAN0HI 0x15 +#define TSL2772_ALS_CHAN1LO 0x16 +#define TSL2772_ALS_CHAN1HI 0x17 +#define TSL2772_PRX_LO 0x18 +#define TSL2772_PRX_HI 0x19 + +/* tsl2772 cmd reg masks */ +#define TSL2772_CMD_REG 0x80 +#define TSL2772_CMD_SPL_FN 0x60 +#define TSL2772_CMD_REPEAT_PROTO 0x00 +#define TSL2772_CMD_AUTOINC_PROTO 0x20 + +#define TSL2772_CMD_PROX_INT_CLR 0X05 +#define TSL2772_CMD_ALS_INT_CLR 0x06 +#define TSL2772_CMD_PROXALS_INT_CLR 0X07 + +/* tsl2772 cntrl reg masks */ +#define TSL2772_CNTL_ADC_ENBL 0x02 +#define TSL2772_CNTL_PWR_ON 0x01 + +/* tsl2772 status reg masks */ +#define TSL2772_STA_ADC_VALID 0x01 +#define TSL2772_STA_PRX_VALID 0x02 +#define TSL2772_STA_ADC_PRX_VALID (TSL2772_STA_ADC_VALID | \ + TSL2772_STA_PRX_VALID) +#define TSL2772_STA_ALS_INTR 0x10 +#define TSL2772_STA_PRX_INTR 0x20 + +/* tsl2772 cntrl reg masks */ +#define TSL2772_CNTL_REG_CLEAR 0x00 +#define TSL2772_CNTL_PROX_INT_ENBL 0X20 +#define TSL2772_CNTL_ALS_INT_ENBL 0X10 +#define TSL2772_CNTL_WAIT_TMR_ENBL 0X08 +#define TSL2772_CNTL_PROX_DET_ENBL 0X04 +#define TSL2772_CNTL_PWRON 0x01 +#define TSL2772_CNTL_ALSPON_ENBL 0x03 +#define TSL2772_CNTL_INTALSPON_ENBL 0x13 +#define TSL2772_CNTL_PROXPON_ENBL 0x0F +#define TSL2772_CNTL_INTPROXPON_ENBL 0x2F + +#define TSL2772_ALS_GAIN_TRIM_MIN 250 +#define TSL2772_ALS_GAIN_TRIM_MAX 4000 + +#define TSL2772_MAX_PROX_LEDS 2 + +#define TSL2772_BOOT_MIN_SLEEP_TIME 10000 +#define TSL2772_BOOT_MAX_SLEEP_TIME 28000 + +/* Device family members */ +enum { + tsl2571, + tsl2671, + tmd2671, + tsl2771, + tmd2771, + tsl2572, + tsl2672, + tmd2672, + tsl2772, + tmd2772, + apds9900, + apds9930, +}; + +enum { + TSL2772_CHIP_UNKNOWN = 0, + TSL2772_CHIP_WORKING = 1, + TSL2772_CHIP_SUSPENDED = 2 +}; + +enum { + TSL2772_SUPPLY_VDD = 0, + TSL2772_SUPPLY_VDDIO = 1, + TSL2772_NUM_SUPPLIES = 2 +}; + +/* Per-device data */ +struct tsl2772_als_info { + u16 als_ch0; + u16 als_ch1; + u16 lux; +}; + +struct tsl2772_chip_info { + int chan_table_elements; + struct iio_chan_spec channel_with_events[4]; + struct iio_chan_spec channel_without_events[4]; + const struct iio_info *info; +}; + +static const int tsl2772_led_currents[][2] = { + { 100000, TSL2772_100_mA }, + { 50000, TSL2772_50_mA }, + { 25000, TSL2772_25_mA }, + { 13000, TSL2772_13_mA }, + { 0, 0 } +}; + +struct tsl2772_chip { + kernel_ulong_t id; + struct mutex prox_mutex; + struct mutex als_mutex; + struct i2c_client *client; + struct regulator_bulk_data supplies[TSL2772_NUM_SUPPLIES]; + u16 prox_data; + struct tsl2772_als_info als_cur_info; + struct tsl2772_settings settings; + struct tsl2772_platform_data *pdata; + int als_gain_time_scale; + int als_saturation; + int tsl2772_chip_status; + u8 tsl2772_config[TSL2772_MAX_CONFIG_REG]; + const struct tsl2772_chip_info *chip_info; + const struct iio_info *info; + s64 event_timestamp; + /* + * This structure is intentionally large to accommodate + * updates via sysfs. + * Sized to 9 = max 8 segments + 1 termination segment + */ + struct tsl2772_lux tsl2772_device_lux[TSL2772_MAX_LUX_TABLE_SIZE]; +}; + +/* + * Different devices require different coefficients, and these numbers were + * derived from the 'Lux Equation' section of the various device datasheets. + * All of these coefficients assume a Glass Attenuation (GA) factor of 1. + * The coefficients are multiplied by 1000 to avoid floating point operations. + * The two rows in each table correspond to the Lux1 and Lux2 equations from + * the datasheets. + */ +static const struct tsl2772_lux tsl2x71_lux_table[TSL2772_DEF_LUX_TABLE_SZ] = { + { 53000, 106000 }, + { 31800, 53000 }, + { 0, 0 }, +}; + +static const struct tsl2772_lux tmd2x71_lux_table[TSL2772_DEF_LUX_TABLE_SZ] = { + { 24000, 48000 }, + { 14400, 24000 }, + { 0, 0 }, +}; + +static const struct tsl2772_lux tsl2x72_lux_table[TSL2772_DEF_LUX_TABLE_SZ] = { + { 60000, 112200 }, + { 37800, 60000 }, + { 0, 0 }, +}; + +static const struct tsl2772_lux tmd2x72_lux_table[TSL2772_DEF_LUX_TABLE_SZ] = { + { 20000, 35000 }, + { 12600, 20000 }, + { 0, 0 }, +}; + +static const struct tsl2772_lux apds9900_lux_table[TSL2772_DEF_LUX_TABLE_SZ] = { + { 52000, 115960 }, + { 36400, 73840 }, + { 0, 0 }, +}; + +static const struct tsl2772_lux apds9930_lux_table[TSL2772_DEF_LUX_TABLE_SZ] = { + { 52000, 96824 }, + { 38792, 67132 }, + { 0, 0 }, +}; + +static const struct tsl2772_lux *tsl2772_default_lux_table_group[] = { + [tsl2571] = tsl2x71_lux_table, + [tsl2671] = tsl2x71_lux_table, + [tmd2671] = tmd2x71_lux_table, + [tsl2771] = tsl2x71_lux_table, + [tmd2771] = tmd2x71_lux_table, + [tsl2572] = tsl2x72_lux_table, + [tsl2672] = tsl2x72_lux_table, + [tmd2672] = tmd2x72_lux_table, + [tsl2772] = tsl2x72_lux_table, + [tmd2772] = tmd2x72_lux_table, + [apds9900] = apds9900_lux_table, + [apds9930] = apds9930_lux_table, +}; + +static const struct tsl2772_settings tsl2772_default_settings = { + .als_time = 255, /* 2.72 / 2.73 ms */ + .als_gain = 0, + .prox_time = 255, /* 2.72 / 2.73 ms */ + .prox_gain = 0, + .wait_time = 255, + .als_prox_config = 0, + .als_gain_trim = 1000, + .als_cal_target = 150, + .als_persistence = 1, + .als_interrupt_en = false, + .als_thresh_low = 200, + .als_thresh_high = 256, + .prox_persistence = 1, + .prox_interrupt_en = false, + .prox_thres_low = 0, + .prox_thres_high = 512, + .prox_max_samples_cal = 30, + .prox_pulse_count = 8, + .prox_diode = TSL2772_DIODE1, + .prox_power = TSL2772_100_mA +}; + +static const s16 tsl2772_als_gain[] = { + 1, + 8, + 16, + 120 +}; + +static const s16 tsl2772_prox_gain[] = { + 1, + 2, + 4, + 8 +}; + +static const int tsl2772_int_time_avail[][6] = { + [tsl2571] = { 0, 2720, 0, 2720, 0, 696000 }, + [tsl2671] = { 0, 2720, 0, 2720, 0, 696000 }, + [tmd2671] = { 0, 2720, 0, 2720, 0, 696000 }, + [tsl2771] = { 0, 2720, 0, 2720, 0, 696000 }, + [tmd2771] = { 0, 2720, 0, 2720, 0, 696000 }, + [tsl2572] = { 0, 2730, 0, 2730, 0, 699000 }, + [tsl2672] = { 0, 2730, 0, 2730, 0, 699000 }, + [tmd2672] = { 0, 2730, 0, 2730, 0, 699000 }, + [tsl2772] = { 0, 2730, 0, 2730, 0, 699000 }, + [tmd2772] = { 0, 2730, 0, 2730, 0, 699000 }, + [apds9900] = { 0, 2720, 0, 2720, 0, 696000 }, + [apds9930] = { 0, 2730, 0, 2730, 0, 699000 }, +}; + +static int tsl2772_int_calibscale_avail[] = { 1, 8, 16, 120 }; + +static int tsl2772_prox_calibscale_avail[] = { 1, 2, 4, 8 }; + +/* Channel variations */ +enum { + ALS, + PRX, + ALSPRX, + PRX2, + ALSPRX2, +}; + +static const u8 device_channel_config[] = { + [tsl2571] = ALS, + [tsl2671] = PRX, + [tmd2671] = PRX, + [tsl2771] = ALSPRX, + [tmd2771] = ALSPRX, + [tsl2572] = ALS, + [tsl2672] = PRX2, + [tmd2672] = PRX2, + [tsl2772] = ALSPRX2, + [tmd2772] = ALSPRX2, + [apds9900] = ALSPRX, + [apds9930] = ALSPRX2, +}; + +static int tsl2772_read_status(struct tsl2772_chip *chip) +{ + int ret; + + ret = i2c_smbus_read_byte_data(chip->client, + TSL2772_CMD_REG | TSL2772_STATUS); + if (ret < 0) + dev_err(&chip->client->dev, + "%s: failed to read STATUS register: %d\n", __func__, + ret); + + return ret; +} + +static int tsl2772_write_control_reg(struct tsl2772_chip *chip, u8 data) +{ + int ret; + + ret = i2c_smbus_write_byte_data(chip->client, + TSL2772_CMD_REG | TSL2772_CNTRL, data); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed to write to control register %x: %d\n", + __func__, data, ret); + } + + return ret; +} + +static int tsl2772_read_autoinc_regs(struct tsl2772_chip *chip, int lower_reg, + int upper_reg) +{ + u8 buf[2]; + int ret; + + ret = i2c_smbus_write_byte(chip->client, + TSL2772_CMD_REG | TSL2772_CMD_AUTOINC_PROTO | + lower_reg); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed to enable auto increment protocol: %d\n", + __func__, ret); + return ret; + } + + ret = i2c_smbus_read_byte_data(chip->client, + TSL2772_CMD_REG | lower_reg); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed to read from register %x: %d\n", __func__, + lower_reg, ret); + return ret; + } + buf[0] = ret; + + ret = i2c_smbus_read_byte_data(chip->client, + TSL2772_CMD_REG | upper_reg); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed to read from register %x: %d\n", __func__, + upper_reg, ret); + return ret; + } + buf[1] = ret; + + ret = i2c_smbus_write_byte(chip->client, + TSL2772_CMD_REG | TSL2772_CMD_REPEAT_PROTO | + lower_reg); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed to enable repeated byte protocol: %d\n", + __func__, ret); + return ret; + } + + return le16_to_cpup((const __le16 *)&buf[0]); +} + +/** + * tsl2772_get_lux() - Reads and calculates current lux value. + * @indio_dev: pointer to IIO device + * + * The raw ch0 and ch1 values of the ambient light sensed in the last + * integration cycle are read from the device. The raw values are multiplied + * by a device-specific scale factor, and divided by the integration time and + * device gain. The code supports multiple lux equations through the lux table + * coefficients. A lux gain trim is applied to each lux equation, and then the + * maximum lux within the interval 0..65535 is selected. + */ +static int tsl2772_get_lux(struct iio_dev *indio_dev) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + struct tsl2772_lux *p; + int max_lux, ret; + bool overflow; + + mutex_lock(&chip->als_mutex); + + if (chip->tsl2772_chip_status != TSL2772_CHIP_WORKING) { + dev_err(&chip->client->dev, "%s: device is not enabled\n", + __func__); + ret = -EBUSY; + goto out_unlock; + } + + ret = tsl2772_read_status(chip); + if (ret < 0) + goto out_unlock; + + if (!(ret & TSL2772_STA_ADC_VALID)) { + dev_err(&chip->client->dev, + "%s: data not valid yet\n", __func__); + ret = chip->als_cur_info.lux; /* return LAST VALUE */ + goto out_unlock; + } + + ret = tsl2772_read_autoinc_regs(chip, TSL2772_ALS_CHAN0LO, + TSL2772_ALS_CHAN0HI); + if (ret < 0) + goto out_unlock; + chip->als_cur_info.als_ch0 = ret; + + ret = tsl2772_read_autoinc_regs(chip, TSL2772_ALS_CHAN1LO, + TSL2772_ALS_CHAN1HI); + if (ret < 0) + goto out_unlock; + chip->als_cur_info.als_ch1 = ret; + + if (chip->als_cur_info.als_ch0 >= chip->als_saturation) { + max_lux = TSL2772_LUX_CALC_OVER_FLOW; + goto update_struct_with_max_lux; + } + + if (!chip->als_cur_info.als_ch0) { + /* have no data, so return LAST VALUE */ + ret = chip->als_cur_info.lux; + goto out_unlock; + } + + max_lux = 0; + overflow = false; + for (p = (struct tsl2772_lux *)chip->tsl2772_device_lux; p->ch0 != 0; + p++) { + int lux; + + lux = ((chip->als_cur_info.als_ch0 * p->ch0) - + (chip->als_cur_info.als_ch1 * p->ch1)) / + chip->als_gain_time_scale; + + /* + * The als_gain_trim can have a value within the range 250..4000 + * and is a multiplier for the lux. A trim of 1000 makes no + * changes to the lux, less than 1000 scales it down, and + * greater than 1000 scales it up. + */ + lux = (lux * chip->settings.als_gain_trim) / 1000; + + if (lux > TSL2772_LUX_CALC_OVER_FLOW) { + overflow = true; + continue; + } + + max_lux = max(max_lux, lux); + } + + if (overflow && max_lux == 0) + max_lux = TSL2772_LUX_CALC_OVER_FLOW; + +update_struct_with_max_lux: + chip->als_cur_info.lux = max_lux; + ret = max_lux; + +out_unlock: + mutex_unlock(&chip->als_mutex); + + return ret; +} + +/** + * tsl2772_get_prox() - Reads proximity data registers and updates + * chip->prox_data. + * + * @indio_dev: pointer to IIO device + */ +static int tsl2772_get_prox(struct iio_dev *indio_dev) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->prox_mutex); + + ret = tsl2772_read_status(chip); + if (ret < 0) + goto prox_poll_err; + + switch (chip->id) { + case tsl2571: + case tsl2671: + case tmd2671: + case tsl2771: + case tmd2771: + if (!(ret & TSL2772_STA_ADC_VALID)) { + ret = -EINVAL; + goto prox_poll_err; + } + break; + case tsl2572: + case tsl2672: + case tmd2672: + case tsl2772: + case tmd2772: + case apds9900: + case apds9930: + if (!(ret & TSL2772_STA_PRX_VALID)) { + ret = -EINVAL; + goto prox_poll_err; + } + break; + } + + ret = tsl2772_read_autoinc_regs(chip, TSL2772_PRX_LO, TSL2772_PRX_HI); + if (ret < 0) + goto prox_poll_err; + chip->prox_data = ret; + +prox_poll_err: + mutex_unlock(&chip->prox_mutex); + + return ret; +} + +static int tsl2772_read_prox_led_current(struct tsl2772_chip *chip) +{ + struct device *dev = &chip->client->dev; + int ret, tmp, i; + + ret = device_property_read_u32(dev, "led-max-microamp", &tmp); + if (ret < 0) + return ret; + + for (i = 0; tsl2772_led_currents[i][0] != 0; i++) { + if (tmp == tsl2772_led_currents[i][0]) { + chip->settings.prox_power = tsl2772_led_currents[i][1]; + return 0; + } + } + + dev_err(dev, "Invalid value %d for led-max-microamp\n", tmp); + + return -EINVAL; +} + +static int tsl2772_read_prox_diodes(struct tsl2772_chip *chip) +{ + struct device *dev = &chip->client->dev; + int i, ret, num_leds, prox_diode_mask; + u32 leds[TSL2772_MAX_PROX_LEDS]; + + ret = device_property_count_u32(dev, "amstaos,proximity-diodes"); + if (ret < 0) + return ret; + + num_leds = ret; + if (num_leds > TSL2772_MAX_PROX_LEDS) + num_leds = TSL2772_MAX_PROX_LEDS; + + ret = device_property_read_u32_array(dev, "amstaos,proximity-diodes", leds, num_leds); + if (ret < 0) { + dev_err(dev, "Invalid value for amstaos,proximity-diodes: %d.\n", ret); + return ret; + } + + prox_diode_mask = 0; + for (i = 0; i < num_leds; i++) { + if (leds[i] == 0) + prox_diode_mask |= TSL2772_DIODE0; + else if (leds[i] == 1) + prox_diode_mask |= TSL2772_DIODE1; + else { + dev_err(dev, "Invalid value %d in amstaos,proximity-diodes.\n", leds[i]); + return -EINVAL; + } + } + chip->settings.prox_diode = prox_diode_mask; + + return 0; +} + +static void tsl2772_parse_dt(struct tsl2772_chip *chip) +{ + tsl2772_read_prox_led_current(chip); + tsl2772_read_prox_diodes(chip); +} + +/** + * tsl2772_defaults() - Populates the device nominal operating parameters + * with those provided by a 'platform' data struct or + * with prefined defaults. + * + * @chip: pointer to device structure. + */ +static void tsl2772_defaults(struct tsl2772_chip *chip) +{ + /* If Operational settings defined elsewhere.. */ + if (chip->pdata && chip->pdata->platform_default_settings) + memcpy(&chip->settings, chip->pdata->platform_default_settings, + sizeof(tsl2772_default_settings)); + else + memcpy(&chip->settings, &tsl2772_default_settings, + sizeof(tsl2772_default_settings)); + + /* Load up the proper lux table. */ + if (chip->pdata && chip->pdata->platform_lux_table[0].ch0 != 0) + memcpy(chip->tsl2772_device_lux, + chip->pdata->platform_lux_table, + sizeof(chip->pdata->platform_lux_table)); + else + memcpy(chip->tsl2772_device_lux, + tsl2772_default_lux_table_group[chip->id], + TSL2772_DEFAULT_TABLE_BYTES); + + tsl2772_parse_dt(chip); +} + +/** + * tsl2772_als_calibrate() - Obtain single reading and calculate + * the als_gain_trim. + * + * @indio_dev: pointer to IIO device + */ +static int tsl2772_als_calibrate(struct iio_dev *indio_dev) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + int ret, lux_val; + + ret = i2c_smbus_read_byte_data(chip->client, + TSL2772_CMD_REG | TSL2772_CNTRL); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed to read from the CNTRL register\n", + __func__); + return ret; + } + + if ((ret & (TSL2772_CNTL_ADC_ENBL | TSL2772_CNTL_PWR_ON)) + != (TSL2772_CNTL_ADC_ENBL | TSL2772_CNTL_PWR_ON)) { + dev_err(&chip->client->dev, + "%s: Device is not powered on and/or ADC is not enabled\n", + __func__); + return -EINVAL; + } else if ((ret & TSL2772_STA_ADC_VALID) != TSL2772_STA_ADC_VALID) { + dev_err(&chip->client->dev, + "%s: The two ADC channels have not completed an integration cycle\n", + __func__); + return -ENODATA; + } + + lux_val = tsl2772_get_lux(indio_dev); + if (lux_val < 0) { + dev_err(&chip->client->dev, + "%s: failed to get lux\n", __func__); + return lux_val; + } + if (lux_val == 0) + return -ERANGE; + + ret = (chip->settings.als_cal_target * chip->settings.als_gain_trim) / + lux_val; + if (ret < TSL2772_ALS_GAIN_TRIM_MIN || ret > TSL2772_ALS_GAIN_TRIM_MAX) + return -ERANGE; + + chip->settings.als_gain_trim = ret; + + return ret; +} + +static void tsl2772_disable_regulators_action(void *_data) +{ + struct tsl2772_chip *chip = _data; + + regulator_bulk_disable(ARRAY_SIZE(chip->supplies), chip->supplies); +} + +static int tsl2772_chip_on(struct iio_dev *indio_dev) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + int ret, i, als_count, als_time_us; + u8 *dev_reg, reg_val; + + /* Non calculated parameters */ + chip->tsl2772_config[TSL2772_ALS_TIME] = chip->settings.als_time; + chip->tsl2772_config[TSL2772_PRX_TIME] = chip->settings.prox_time; + chip->tsl2772_config[TSL2772_WAIT_TIME] = chip->settings.wait_time; + chip->tsl2772_config[TSL2772_ALS_PRX_CONFIG] = + chip->settings.als_prox_config; + + chip->tsl2772_config[TSL2772_ALS_MINTHRESHLO] = + (chip->settings.als_thresh_low) & 0xFF; + chip->tsl2772_config[TSL2772_ALS_MINTHRESHHI] = + (chip->settings.als_thresh_low >> 8) & 0xFF; + chip->tsl2772_config[TSL2772_ALS_MAXTHRESHLO] = + (chip->settings.als_thresh_high) & 0xFF; + chip->tsl2772_config[TSL2772_ALS_MAXTHRESHHI] = + (chip->settings.als_thresh_high >> 8) & 0xFF; + chip->tsl2772_config[TSL2772_PERSISTENCE] = + (chip->settings.prox_persistence & 0xFF) << 4 | + (chip->settings.als_persistence & 0xFF); + + chip->tsl2772_config[TSL2772_PRX_COUNT] = + chip->settings.prox_pulse_count; + chip->tsl2772_config[TSL2772_PRX_MINTHRESHLO] = + (chip->settings.prox_thres_low) & 0xFF; + chip->tsl2772_config[TSL2772_PRX_MINTHRESHHI] = + (chip->settings.prox_thres_low >> 8) & 0xFF; + chip->tsl2772_config[TSL2772_PRX_MAXTHRESHLO] = + (chip->settings.prox_thres_high) & 0xFF; + chip->tsl2772_config[TSL2772_PRX_MAXTHRESHHI] = + (chip->settings.prox_thres_high >> 8) & 0xFF; + + /* and make sure we're not already on */ + if (chip->tsl2772_chip_status == TSL2772_CHIP_WORKING) { + /* if forcing a register update - turn off, then on */ + dev_info(&chip->client->dev, "device is already enabled\n"); + return -EINVAL; + } + + /* Set the gain based on tsl2772_settings struct */ + chip->tsl2772_config[TSL2772_GAIN] = + (chip->settings.als_gain & 0xFF) | + ((chip->settings.prox_gain & 0xFF) << 2) | + (chip->settings.prox_diode << 4) | + (chip->settings.prox_power << 6); + + /* set chip time scaling and saturation */ + als_count = 256 - chip->settings.als_time; + als_time_us = als_count * tsl2772_int_time_avail[chip->id][3]; + chip->als_saturation = als_count * 768; /* 75% of full scale */ + chip->als_gain_time_scale = als_time_us * + tsl2772_als_gain[chip->settings.als_gain]; + + /* + * TSL2772 Specific power-on / adc enable sequence + * Power on the device 1st. + */ + ret = tsl2772_write_control_reg(chip, TSL2772_CNTL_PWR_ON); + if (ret < 0) + return ret; + + /* + * Use the following shadow copy for our delay before enabling ADC. + * Write all the registers. + */ + for (i = 0, dev_reg = chip->tsl2772_config; + i < TSL2772_MAX_CONFIG_REG; i++) { + int reg = TSL2772_CMD_REG + i; + + ret = i2c_smbus_write_byte_data(chip->client, reg, + *dev_reg++); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed to write to register %x: %d\n", + __func__, reg, ret); + return ret; + } + } + + /* Power-on settling time */ + usleep_range(3000, 3500); + + reg_val = TSL2772_CNTL_PWR_ON | TSL2772_CNTL_ADC_ENBL | + TSL2772_CNTL_PROX_DET_ENBL; + if (chip->settings.als_interrupt_en) + reg_val |= TSL2772_CNTL_ALS_INT_ENBL; + if (chip->settings.prox_interrupt_en) + reg_val |= TSL2772_CNTL_PROX_INT_ENBL; + + ret = tsl2772_write_control_reg(chip, reg_val); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte(chip->client, + TSL2772_CMD_REG | TSL2772_CMD_SPL_FN | + TSL2772_CMD_PROXALS_INT_CLR); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed to clear interrupt status: %d\n", + __func__, ret); + return ret; + } + + chip->tsl2772_chip_status = TSL2772_CHIP_WORKING; + + return ret; +} + +static int tsl2772_chip_off(struct iio_dev *indio_dev) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + + /* turn device off */ + chip->tsl2772_chip_status = TSL2772_CHIP_SUSPENDED; + return tsl2772_write_control_reg(chip, 0x00); +} + +static void tsl2772_chip_off_action(void *data) +{ + struct iio_dev *indio_dev = data; + + tsl2772_chip_off(indio_dev); +} + +/** + * tsl2772_invoke_change - power cycle the device to implement the user + * parameters + * @indio_dev: pointer to IIO device + * + * Obtain and lock both ALS and PROX resources, determine and save device state + * (On/Off), cycle device to implement updated parameter, put device back into + * proper state, and unlock resource. + */ +static int tsl2772_invoke_change(struct iio_dev *indio_dev) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + int device_status = chip->tsl2772_chip_status; + int ret; + + mutex_lock(&chip->als_mutex); + mutex_lock(&chip->prox_mutex); + + if (device_status == TSL2772_CHIP_WORKING) { + ret = tsl2772_chip_off(indio_dev); + if (ret < 0) + goto unlock; + } + + ret = tsl2772_chip_on(indio_dev); + +unlock: + mutex_unlock(&chip->prox_mutex); + mutex_unlock(&chip->als_mutex); + + return ret; +} + +static int tsl2772_prox_cal(struct iio_dev *indio_dev) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + int prox_history[MAX_SAMPLES_CAL + 1]; + int i, ret, mean, max, sample_sum; + + if (chip->settings.prox_max_samples_cal < 1 || + chip->settings.prox_max_samples_cal > MAX_SAMPLES_CAL) + return -EINVAL; + + for (i = 0; i < chip->settings.prox_max_samples_cal; i++) { + usleep_range(15000, 17500); + ret = tsl2772_get_prox(indio_dev); + if (ret < 0) + return ret; + + prox_history[i] = chip->prox_data; + } + + sample_sum = 0; + max = INT_MIN; + for (i = 0; i < chip->settings.prox_max_samples_cal; i++) { + sample_sum += prox_history[i]; + max = max(max, prox_history[i]); + } + mean = sample_sum / chip->settings.prox_max_samples_cal; + + chip->settings.prox_thres_high = (max << 1) - mean; + + return tsl2772_invoke_change(indio_dev); +} + +static int tsl2772_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->type == IIO_INTENSITY) { + *length = ARRAY_SIZE(tsl2772_int_calibscale_avail); + *vals = tsl2772_int_calibscale_avail; + } else { + *length = ARRAY_SIZE(tsl2772_prox_calibscale_avail); + *vals = tsl2772_prox_calibscale_avail; + } + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_INT_TIME: + *length = ARRAY_SIZE(tsl2772_int_time_avail[chip->id]); + *vals = tsl2772_int_time_avail[chip->id]; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_RANGE; + } + + return -EINVAL; +} + +static ssize_t in_illuminance0_target_input_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tsl2772_chip *chip = iio_priv(dev_to_iio_dev(dev)); + + return scnprintf(buf, PAGE_SIZE, "%d\n", chip->settings.als_cal_target); +} + +static ssize_t in_illuminance0_target_input_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2772_chip *chip = iio_priv(indio_dev); + u16 value; + int ret; + + if (kstrtou16(buf, 0, &value)) + return -EINVAL; + + chip->settings.als_cal_target = value; + ret = tsl2772_invoke_change(indio_dev); + if (ret < 0) + return ret; + + return len; +} + +static ssize_t in_illuminance0_calibrate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + bool value; + int ret; + + if (kstrtobool(buf, &value) || !value) + return -EINVAL; + + ret = tsl2772_als_calibrate(indio_dev); + if (ret < 0) + return ret; + + ret = tsl2772_invoke_change(indio_dev); + if (ret < 0) + return ret; + + return len; +} + +static ssize_t in_illuminance0_lux_table_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tsl2772_chip *chip = iio_priv(dev_to_iio_dev(dev)); + int i = 0; + int offset = 0; + + while (i < TSL2772_MAX_LUX_TABLE_SIZE) { + offset += scnprintf(buf + offset, PAGE_SIZE - offset, "%u,%u,", + chip->tsl2772_device_lux[i].ch0, + chip->tsl2772_device_lux[i].ch1); + if (chip->tsl2772_device_lux[i].ch0 == 0) { + /* + * We just printed the first "0" entry. + * Now get rid of the extra "," and break. + */ + offset--; + break; + } + i++; + } + + offset += scnprintf(buf + offset, PAGE_SIZE - offset, "\n"); + return offset; +} + +static ssize_t in_illuminance0_lux_table_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2772_chip *chip = iio_priv(indio_dev); + int value[ARRAY_SIZE(chip->tsl2772_device_lux) * 2 + 1]; + int n, ret; + + get_options(buf, ARRAY_SIZE(value), value); + + /* + * We now have an array of ints starting at value[1], and + * enumerated by value[0]. + * We expect each group of two ints to be one table entry, + * and the last table entry is all 0. + */ + n = value[0]; + if ((n % 2) || n < 4 || + n > ((ARRAY_SIZE(chip->tsl2772_device_lux) - 1) * 2)) + return -EINVAL; + + if ((value[(n - 1)] | value[n]) != 0) + return -EINVAL; + + if (chip->tsl2772_chip_status == TSL2772_CHIP_WORKING) { + ret = tsl2772_chip_off(indio_dev); + if (ret < 0) + return ret; + } + + /* Zero out the table */ + memset(chip->tsl2772_device_lux, 0, sizeof(chip->tsl2772_device_lux)); + memcpy(chip->tsl2772_device_lux, &value[1], (value[0] * 4)); + + ret = tsl2772_invoke_change(indio_dev); + if (ret < 0) + return ret; + + return len; +} + +static ssize_t in_proximity0_calibrate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + bool value; + int ret; + + if (kstrtobool(buf, &value) || !value) + return -EINVAL; + + ret = tsl2772_prox_cal(indio_dev); + if (ret < 0) + return ret; + + ret = tsl2772_invoke_change(indio_dev); + if (ret < 0) + return ret; + + return len; +} + +static int tsl2772_read_interrupt_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + + if (chan->type == IIO_INTENSITY) + return chip->settings.als_interrupt_en; + else + return chip->settings.prox_interrupt_en; +} + +static int tsl2772_write_interrupt_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool val) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + + if (chan->type == IIO_INTENSITY) + chip->settings.als_interrupt_en = val; + else + chip->settings.prox_interrupt_en = val; + + return tsl2772_invoke_change(indio_dev); +} + +static int tsl2772_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + int ret = -EINVAL, count, persistence; + u8 time; + + switch (info) { + case IIO_EV_INFO_VALUE: + if (chan->type == IIO_INTENSITY) { + switch (dir) { + case IIO_EV_DIR_RISING: + chip->settings.als_thresh_high = val; + ret = 0; + break; + case IIO_EV_DIR_FALLING: + chip->settings.als_thresh_low = val; + ret = 0; + break; + default: + break; + } + } else { + switch (dir) { + case IIO_EV_DIR_RISING: + chip->settings.prox_thres_high = val; + ret = 0; + break; + case IIO_EV_DIR_FALLING: + chip->settings.prox_thres_low = val; + ret = 0; + break; + default: + break; + } + } + break; + case IIO_EV_INFO_PERIOD: + if (chan->type == IIO_INTENSITY) + time = chip->settings.als_time; + else + time = chip->settings.prox_time; + + count = 256 - time; + persistence = ((val * 1000000) + val2) / + (count * tsl2772_int_time_avail[chip->id][3]); + + if (chan->type == IIO_INTENSITY) { + /* ALS filter values are 1, 2, 3, 5, 10, 15, ..., 60 */ + if (persistence > 3) + persistence = (persistence / 5) + 3; + + chip->settings.als_persistence = persistence; + } else { + chip->settings.prox_persistence = persistence; + } + + ret = 0; + break; + default: + break; + } + + if (ret < 0) + return ret; + + return tsl2772_invoke_change(indio_dev); +} + +static int tsl2772_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + int filter_delay, persistence; + u8 time; + + switch (info) { + case IIO_EV_INFO_VALUE: + if (chan->type == IIO_INTENSITY) { + switch (dir) { + case IIO_EV_DIR_RISING: + *val = chip->settings.als_thresh_high; + return IIO_VAL_INT; + case IIO_EV_DIR_FALLING: + *val = chip->settings.als_thresh_low; + return IIO_VAL_INT; + default: + return -EINVAL; + } + } else { + switch (dir) { + case IIO_EV_DIR_RISING: + *val = chip->settings.prox_thres_high; + return IIO_VAL_INT; + case IIO_EV_DIR_FALLING: + *val = chip->settings.prox_thres_low; + return IIO_VAL_INT; + default: + return -EINVAL; + } + } + break; + case IIO_EV_INFO_PERIOD: + if (chan->type == IIO_INTENSITY) { + time = chip->settings.als_time; + persistence = chip->settings.als_persistence; + + /* ALS filter values are 1, 2, 3, 5, 10, 15, ..., 60 */ + if (persistence > 3) + persistence = (persistence - 3) * 5; + } else { + time = chip->settings.prox_time; + persistence = chip->settings.prox_persistence; + } + + filter_delay = persistence * (256 - time) * + tsl2772_int_time_avail[chip->id][3]; + + *val = filter_delay / 1000000; + *val2 = filter_delay % 1000000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int tsl2772_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_LIGHT: + tsl2772_get_lux(indio_dev); + *val = chip->als_cur_info.lux; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_INTENSITY: + tsl2772_get_lux(indio_dev); + if (chan->channel == 0) + *val = chip->als_cur_info.als_ch0; + else + *val = chip->als_cur_info.als_ch1; + return IIO_VAL_INT; + case IIO_PROXIMITY: + tsl2772_get_prox(indio_dev); + *val = chip->prox_data; + return IIO_VAL_INT; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->type == IIO_INTENSITY) + *val = tsl2772_als_gain[chip->settings.als_gain]; + else + *val = tsl2772_prox_gain[chip->settings.prox_gain]; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + *val = chip->settings.als_gain_trim; + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = (256 - chip->settings.als_time) * + tsl2772_int_time_avail[chip->id][3]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int tsl2772_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct tsl2772_chip *chip = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->type == IIO_INTENSITY) { + switch (val) { + case 1: + chip->settings.als_gain = 0; + break; + case 8: + chip->settings.als_gain = 1; + break; + case 16: + chip->settings.als_gain = 2; + break; + case 120: + chip->settings.als_gain = 3; + break; + default: + return -EINVAL; + } + } else { + switch (val) { + case 1: + chip->settings.prox_gain = 0; + break; + case 2: + chip->settings.prox_gain = 1; + break; + case 4: + chip->settings.prox_gain = 2; + break; + case 8: + chip->settings.prox_gain = 3; + break; + default: + return -EINVAL; + } + } + break; + case IIO_CHAN_INFO_CALIBBIAS: + if (val < TSL2772_ALS_GAIN_TRIM_MIN || + val > TSL2772_ALS_GAIN_TRIM_MAX) + return -EINVAL; + + chip->settings.als_gain_trim = val; + break; + case IIO_CHAN_INFO_INT_TIME: + if (val != 0 || val2 < tsl2772_int_time_avail[chip->id][1] || + val2 > tsl2772_int_time_avail[chip->id][5]) + return -EINVAL; + + chip->settings.als_time = 256 - + (val2 / tsl2772_int_time_avail[chip->id][3]); + break; + default: + return -EINVAL; + } + + return tsl2772_invoke_change(indio_dev); +} + +static DEVICE_ATTR_RW(in_illuminance0_target_input); + +static DEVICE_ATTR_WO(in_illuminance0_calibrate); + +static DEVICE_ATTR_WO(in_proximity0_calibrate); + +static DEVICE_ATTR_RW(in_illuminance0_lux_table); + +/* Use the default register values to identify the Taos device */ +static int tsl2772_device_id_verif(int id, int target) +{ + switch (target) { + case tsl2571: + case tsl2671: + case tsl2771: + return (id & 0xf0) == TRITON_ID; + case tmd2671: + case tmd2771: + case apds9900: + return (id & 0xf0) == HALIBUT_ID; + case tsl2572: + case tsl2672: + case tmd2672: + case tsl2772: + case tmd2772: + case apds9930: + return (id & 0xf0) == SWORDFISH_ID; + } + + return -EINVAL; +} + +static irqreturn_t tsl2772_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct tsl2772_chip *chip = iio_priv(indio_dev); + s64 timestamp = iio_get_time_ns(indio_dev); + int ret; + + ret = tsl2772_read_status(chip); + if (ret < 0) + return IRQ_HANDLED; + + /* What type of interrupt do we need to process */ + if (ret & TSL2772_STA_PRX_INTR) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + timestamp); + } + + if (ret & TSL2772_STA_ALS_INTR) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + timestamp); + } + + ret = i2c_smbus_write_byte(chip->client, + TSL2772_CMD_REG | TSL2772_CMD_SPL_FN | + TSL2772_CMD_PROXALS_INT_CLR); + if (ret < 0) + dev_err(&chip->client->dev, + "%s: failed to clear interrupt status: %d\n", + __func__, ret); + + return IRQ_HANDLED; +} + +static struct attribute *tsl2772_ALS_device_attrs[] = { + &dev_attr_in_illuminance0_target_input.attr, + &dev_attr_in_illuminance0_calibrate.attr, + &dev_attr_in_illuminance0_lux_table.attr, + NULL +}; + +static struct attribute *tsl2772_PRX_device_attrs[] = { + &dev_attr_in_proximity0_calibrate.attr, + NULL +}; + +static struct attribute *tsl2772_ALSPRX_device_attrs[] = { + &dev_attr_in_illuminance0_target_input.attr, + &dev_attr_in_illuminance0_calibrate.attr, + &dev_attr_in_illuminance0_lux_table.attr, + NULL +}; + +static struct attribute *tsl2772_PRX2_device_attrs[] = { + &dev_attr_in_proximity0_calibrate.attr, + NULL +}; + +static struct attribute *tsl2772_ALSPRX2_device_attrs[] = { + &dev_attr_in_illuminance0_target_input.attr, + &dev_attr_in_illuminance0_calibrate.attr, + &dev_attr_in_illuminance0_lux_table.attr, + &dev_attr_in_proximity0_calibrate.attr, + NULL +}; + +static const struct attribute_group tsl2772_device_attr_group_tbl[] = { + [ALS] = { + .attrs = tsl2772_ALS_device_attrs, + }, + [PRX] = { + .attrs = tsl2772_PRX_device_attrs, + }, + [ALSPRX] = { + .attrs = tsl2772_ALSPRX_device_attrs, + }, + [PRX2] = { + .attrs = tsl2772_PRX2_device_attrs, + }, + [ALSPRX2] = { + .attrs = tsl2772_ALSPRX2_device_attrs, + }, +}; + +#define TSL2772_DEVICE_INFO(type)[type] = \ + { \ + .attrs = &tsl2772_device_attr_group_tbl[type], \ + .read_raw = &tsl2772_read_raw, \ + .read_avail = &tsl2772_read_avail, \ + .write_raw = &tsl2772_write_raw, \ + .read_event_value = &tsl2772_read_event_value, \ + .write_event_value = &tsl2772_write_event_value, \ + .read_event_config = &tsl2772_read_interrupt_config, \ + .write_event_config = &tsl2772_write_interrupt_config, \ + } + +static const struct iio_info tsl2772_device_info[] = { + TSL2772_DEVICE_INFO(ALS), + TSL2772_DEVICE_INFO(PRX), + TSL2772_DEVICE_INFO(ALSPRX), + TSL2772_DEVICE_INFO(PRX2), + TSL2772_DEVICE_INFO(ALSPRX2), +}; + +static const struct iio_event_spec tsl2772_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_PERIOD) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct tsl2772_chip_info tsl2772_chip_info_tbl[] = { + [ALS] = { + .channel_with_events = { + { + .type = IIO_LIGHT, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + .event_spec = tsl2772_events, + .num_event_specs = ARRAY_SIZE(tsl2772_events), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 1, + }, + }, + .channel_without_events = { + { + .type = IIO_LIGHT, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 1, + }, + }, + .chan_table_elements = 3, + .info = &tsl2772_device_info[ALS], + }, + [PRX] = { + .channel_with_events = { + { + .type = IIO_PROXIMITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = tsl2772_events, + .num_event_specs = ARRAY_SIZE(tsl2772_events), + }, + }, + .channel_without_events = { + { + .type = IIO_PROXIMITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, + }, + .chan_table_elements = 1, + .info = &tsl2772_device_info[PRX], + }, + [ALSPRX] = { + .channel_with_events = { + { + .type = IIO_LIGHT, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + .event_spec = tsl2772_events, + .num_event_specs = ARRAY_SIZE(tsl2772_events), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .type = IIO_PROXIMITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = tsl2772_events, + .num_event_specs = ARRAY_SIZE(tsl2772_events), + }, + }, + .channel_without_events = { + { + .type = IIO_LIGHT, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .type = IIO_PROXIMITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, + }, + .chan_table_elements = 4, + .info = &tsl2772_device_info[ALSPRX], + }, + [PRX2] = { + .channel_with_events = { + { + .type = IIO_PROXIMITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_CALIBSCALE), + .event_spec = tsl2772_events, + .num_event_specs = ARRAY_SIZE(tsl2772_events), + }, + }, + .channel_without_events = { + { + .type = IIO_PROXIMITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_CALIBSCALE), + }, + }, + .chan_table_elements = 1, + .info = &tsl2772_device_info[PRX2], + }, + [ALSPRX2] = { + .channel_with_events = { + { + .type = IIO_LIGHT, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + .event_spec = tsl2772_events, + .num_event_specs = ARRAY_SIZE(tsl2772_events), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .type = IIO_PROXIMITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_CALIBSCALE), + .event_spec = tsl2772_events, + .num_event_specs = ARRAY_SIZE(tsl2772_events), + }, + }, + .channel_without_events = { + { + .type = IIO_LIGHT, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .type = IIO_PROXIMITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_CALIBSCALE), + }, + }, + .chan_table_elements = 4, + .info = &tsl2772_device_info[ALSPRX2], + }, +}; + +static int tsl2772_probe(struct i2c_client *clientp) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(clientp); + struct iio_dev *indio_dev; + struct tsl2772_chip *chip; + int ret; + + indio_dev = devm_iio_device_alloc(&clientp->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + chip->client = clientp; + i2c_set_clientdata(clientp, indio_dev); + + chip->supplies[TSL2772_SUPPLY_VDD].supply = "vdd"; + chip->supplies[TSL2772_SUPPLY_VDDIO].supply = "vddio"; + + ret = devm_regulator_bulk_get(&clientp->dev, + ARRAY_SIZE(chip->supplies), + chip->supplies); + if (ret < 0) + return dev_err_probe(&clientp->dev, ret, "Failed to get regulators\n"); + + ret = regulator_bulk_enable(ARRAY_SIZE(chip->supplies), chip->supplies); + if (ret < 0) { + dev_err(&clientp->dev, "Failed to enable regulators: %d\n", + ret); + return ret; + } + + ret = devm_add_action_or_reset(&clientp->dev, + tsl2772_disable_regulators_action, + chip); + if (ret < 0) { + dev_err(&clientp->dev, "Failed to setup regulator cleanup action %d\n", + ret); + return ret; + } + + usleep_range(TSL2772_BOOT_MIN_SLEEP_TIME, TSL2772_BOOT_MAX_SLEEP_TIME); + + ret = i2c_smbus_read_byte_data(chip->client, + TSL2772_CMD_REG | TSL2772_CHIPID); + if (ret < 0) + return ret; + + if (tsl2772_device_id_verif(ret, id->driver_data) <= 0) { + dev_info(&chip->client->dev, + "%s: i2c device found does not match expected id\n", + __func__); + return -EINVAL; + } + + ret = i2c_smbus_write_byte(clientp, TSL2772_CMD_REG | TSL2772_CNTRL); + if (ret < 0) { + dev_err(&clientp->dev, + "%s: Failed to write to CMD register: %d\n", + __func__, ret); + return ret; + } + + mutex_init(&chip->als_mutex); + mutex_init(&chip->prox_mutex); + + chip->tsl2772_chip_status = TSL2772_CHIP_UNKNOWN; + chip->pdata = dev_get_platdata(&clientp->dev); + chip->id = id->driver_data; + chip->chip_info = + &tsl2772_chip_info_tbl[device_channel_config[id->driver_data]]; + + indio_dev->info = chip->chip_info->info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = chip->client->name; + indio_dev->num_channels = chip->chip_info->chan_table_elements; + + if (clientp->irq) { + indio_dev->channels = chip->chip_info->channel_with_events; + + ret = devm_request_threaded_irq(&clientp->dev, clientp->irq, + NULL, + &tsl2772_event_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "TSL2772_event", + indio_dev); + if (ret) + return ret; + } else { + indio_dev->channels = chip->chip_info->channel_without_events; + } + + tsl2772_defaults(chip); + ret = tsl2772_chip_on(indio_dev); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(&clientp->dev, + tsl2772_chip_off_action, + indio_dev); + if (ret < 0) + return ret; + + return devm_iio_device_register(&clientp->dev, indio_dev); +} + +static int tsl2772_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2772_chip *chip = iio_priv(indio_dev); + int ret; + + ret = tsl2772_chip_off(indio_dev); + regulator_bulk_disable(ARRAY_SIZE(chip->supplies), chip->supplies); + + return ret; +} + +static int tsl2772_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2772_chip *chip = iio_priv(indio_dev); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(chip->supplies), chip->supplies); + if (ret < 0) + return ret; + + usleep_range(TSL2772_BOOT_MIN_SLEEP_TIME, TSL2772_BOOT_MAX_SLEEP_TIME); + + return tsl2772_chip_on(indio_dev); +} + +static const struct i2c_device_id tsl2772_idtable[] = { + { .name = "tsl2571", .driver_data = tsl2571 }, + { .name = "tsl2671", .driver_data = tsl2671 }, + { .name = "tmd2671", .driver_data = tmd2671 }, + { .name = "tsl2771", .driver_data = tsl2771 }, + { .name = "tmd2771", .driver_data = tmd2771 }, + { .name = "tsl2572", .driver_data = tsl2572 }, + { .name = "tsl2672", .driver_data = tsl2672 }, + { .name = "tmd2672", .driver_data = tmd2672 }, + { .name = "tsl2772", .driver_data = tsl2772 }, + { .name = "tmd2772", .driver_data = tmd2772 }, + { .name = "apds9900", .driver_data = apds9900 }, + { .name = "apds9901", .driver_data = apds9900 }, + { .name = "apds9930", .driver_data = apds9930 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, tsl2772_idtable); + +static const struct of_device_id tsl2772_of_match[] = { + { .compatible = "amstaos,tsl2571" }, + { .compatible = "amstaos,tsl2671" }, + { .compatible = "amstaos,tmd2671" }, + { .compatible = "amstaos,tsl2771" }, + { .compatible = "amstaos,tmd2771" }, + { .compatible = "amstaos,tsl2572" }, + { .compatible = "amstaos,tsl2672" }, + { .compatible = "amstaos,tmd2672" }, + { .compatible = "amstaos,tsl2772" }, + { .compatible = "amstaos,tmd2772" }, + { .compatible = "avago,apds9900" }, + { .compatible = "avago,apds9901" }, + { .compatible = "avago,apds9930" }, + { } +}; +MODULE_DEVICE_TABLE(of, tsl2772_of_match); + +static const struct dev_pm_ops tsl2772_pm_ops = { + .suspend = tsl2772_suspend, + .resume = tsl2772_resume, +}; + +static struct i2c_driver tsl2772_driver = { + .driver = { + .name = "tsl2772", + .of_match_table = tsl2772_of_match, + .pm = &tsl2772_pm_ops, + }, + .id_table = tsl2772_idtable, + .probe = tsl2772_probe, +}; + +module_i2c_driver(tsl2772_driver); + +MODULE_AUTHOR("J. August Brenner "); +MODULE_AUTHOR("Brian Masney "); +MODULE_DESCRIPTION("TAOS tsl2772 ambient and proximity light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/tsl4531.c b/drivers/iio/light/tsl4531.c new file mode 100644 index 000000000..2f91ed8a9 --- /dev/null +++ b/drivers/iio/light/tsl4531.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tsl4531.c - Support for TAOS TSL4531 ambient light sensor + * + * Copyright 2013 Peter Meerwald + * + * IIO driver for the TSL4531x family + * TSL45311/TSL45313: 7-bit I2C slave address 0x39 + * TSL45315/TSL45317: 7-bit I2C slave address 0x29 + * + * TODO: single cycle measurement + */ + +#include +#include +#include +#include + +#include +#include + +#define TSL4531_DRV_NAME "tsl4531" + +#define TSL4531_COMMAND BIT(7) + +#define TSL4531_CONTROL (TSL4531_COMMAND | 0x00) +#define TSL4531_CONFIG (TSL4531_COMMAND | 0x01) +#define TSL4531_DATA (TSL4531_COMMAND | 0x04) +#define TSL4531_ID (TSL4531_COMMAND | 0x0a) + +/* operating modes in control register */ +#define TSL4531_MODE_POWERDOWN 0x00 +#define TSL4531_MODE_SINGLE_ADC 0x02 +#define TSL4531_MODE_NORMAL 0x03 + +/* integration time control in config register */ +#define TSL4531_TCNTRL_400MS 0x00 +#define TSL4531_TCNTRL_200MS 0x01 +#define TSL4531_TCNTRL_100MS 0x02 + +/* part number in id register */ +#define TSL45311_ID 0x8 +#define TSL45313_ID 0x9 +#define TSL45315_ID 0xa +#define TSL45317_ID 0xb +#define TSL4531_ID_SHIFT 4 + +struct tsl4531_data { + struct i2c_client *client; + struct mutex lock; + int int_time; +}; + +static IIO_CONST_ATTR_INT_TIME_AVAIL("0.1 0.2 0.4"); + +static struct attribute *tsl4531_attributes[] = { + &iio_const_attr_integration_time_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group tsl4531_attribute_group = { + .attrs = tsl4531_attributes, +}; + +static const struct iio_chan_spec tsl4531_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME) + } +}; + +static int tsl4531_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct tsl4531_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = i2c_smbus_read_word_data(data->client, + TSL4531_DATA); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* 0.. 1x, 1 .. 2x, 2 .. 4x */ + *val = 1 << data->int_time; + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + if (data->int_time == 0) + *val2 = 400000; + else if (data->int_time == 1) + *val2 = 200000; + else if (data->int_time == 2) + *val2 = 100000; + else + return -EINVAL; + *val = 0; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int tsl4531_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct tsl4531_data *data = iio_priv(indio_dev); + int int_time, ret; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val != 0) + return -EINVAL; + if (val2 == 400000) + int_time = 0; + else if (val2 == 200000) + int_time = 1; + else if (val2 == 100000) + int_time = 2; + else + return -EINVAL; + mutex_lock(&data->lock); + ret = i2c_smbus_write_byte_data(data->client, + TSL4531_CONFIG, int_time); + if (ret >= 0) + data->int_time = int_time; + mutex_unlock(&data->lock); + return ret; + default: + return -EINVAL; + } +} + +static const struct iio_info tsl4531_info = { + .read_raw = tsl4531_read_raw, + .write_raw = tsl4531_write_raw, + .attrs = &tsl4531_attribute_group, +}; + +static int tsl4531_check_id(struct i2c_client *client) +{ + int ret = i2c_smbus_read_byte_data(client, TSL4531_ID); + if (ret < 0) + return ret; + + switch (ret >> TSL4531_ID_SHIFT) { + case TSL45311_ID: + case TSL45313_ID: + case TSL45315_ID: + case TSL45317_ID: + return 0; + default: + return -ENODEV; + } +} + +static int tsl4531_probe(struct i2c_client *client) +{ + struct tsl4531_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + mutex_init(&data->lock); + + ret = tsl4531_check_id(client); + if (ret) { + dev_err(&client->dev, "no TSL4531 sensor\n"); + return ret; + } + + ret = i2c_smbus_write_byte_data(data->client, TSL4531_CONTROL, + TSL4531_MODE_NORMAL); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(data->client, TSL4531_CONFIG, + TSL4531_TCNTRL_400MS); + if (ret < 0) + return ret; + + indio_dev->info = &tsl4531_info; + indio_dev->channels = tsl4531_channels; + indio_dev->num_channels = ARRAY_SIZE(tsl4531_channels); + indio_dev->name = TSL4531_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + return iio_device_register(indio_dev); +} + +static int tsl4531_powerdown(struct i2c_client *client) +{ + return i2c_smbus_write_byte_data(client, TSL4531_CONTROL, + TSL4531_MODE_POWERDOWN); +} + +static void tsl4531_remove(struct i2c_client *client) +{ + iio_device_unregister(i2c_get_clientdata(client)); + tsl4531_powerdown(client); +} + +static int tsl4531_suspend(struct device *dev) +{ + return tsl4531_powerdown(to_i2c_client(dev)); +} + +static int tsl4531_resume(struct device *dev) +{ + return i2c_smbus_write_byte_data(to_i2c_client(dev), TSL4531_CONTROL, + TSL4531_MODE_NORMAL); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(tsl4531_pm_ops, tsl4531_suspend, + tsl4531_resume); + +static const struct i2c_device_id tsl4531_id[] = { + { .name = "tsl4531" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tsl4531_id); + +static struct i2c_driver tsl4531_driver = { + .driver = { + .name = TSL4531_DRV_NAME, + .pm = pm_sleep_ptr(&tsl4531_pm_ops), + }, + .probe = tsl4531_probe, + .remove = tsl4531_remove, + .id_table = tsl4531_id, +}; + +module_i2c_driver(tsl4531_driver); + +MODULE_AUTHOR("Peter Meerwald "); +MODULE_DESCRIPTION("TAOS TSL4531 ambient light sensors driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/us5182d.c b/drivers/iio/light/us5182d.c new file mode 100644 index 000000000..ab518311f --- /dev/null +++ b/drivers/iio/light/us5182d.c @@ -0,0 +1,979 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015 Intel Corporation + * + * Driver for UPISEMI us5182d Proximity and Ambient Light Sensor. + * + * To do: Interrupt support. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define US5182D_REG_CFG0 0x00 +#define US5182D_CFG0_ONESHOT_EN BIT(6) +#define US5182D_CFG0_SHUTDOWN_EN BIT(7) +#define US5182D_CFG0_WORD_ENABLE BIT(0) +#define US5182D_CFG0_PROX BIT(3) +#define US5182D_CFG0_PX_IRQ BIT(2) + +#define US5182D_REG_CFG1 0x01 +#define US5182D_CFG1_ALS_RES16 BIT(4) +#define US5182D_CFG1_AGAIN_DEFAULT 0x00 + +#define US5182D_REG_CFG2 0x02 +#define US5182D_CFG2_PX_RES16 BIT(4) +#define US5182D_CFG2_PXGAIN_DEFAULT BIT(2) + +#define US5182D_REG_CFG3 0x03 +#define US5182D_CFG3_LED_CURRENT100 (BIT(4) | BIT(5)) +#define US5182D_CFG3_INT_SOURCE_PX BIT(3) + +#define US5182D_REG_CFG4 0x10 + +/* + * Registers for tuning the auto dark current cancelling feature. + * DARK_TH(reg 0x27,0x28) - threshold (counts) for auto dark cancelling. + * when ALS > DARK_TH --> ALS_Code = ALS - Upper(0x2A) * Dark + * when ALS < DARK_TH --> ALS_Code = ALS - Lower(0x29) * Dark + */ +#define US5182D_REG_UDARK_TH 0x27 +#define US5182D_REG_DARK_AUTO_EN 0x2b +#define US5182D_REG_AUTO_LDARK_GAIN 0x29 +#define US5182D_REG_AUTO_HDARK_GAIN 0x2a + +/* Thresholds for events: px low (0x08-l, 0x09-h), px high (0x0a-l 0x0b-h) */ +#define US5182D_REG_PXL_TH 0x08 +#define US5182D_REG_PXH_TH 0x0a + +#define US5182D_REG_PXL_TH_DEFAULT 1000 +#define US5182D_REG_PXH_TH_DEFAULT 30000 + +#define US5182D_OPMODE_ALS 0x01 +#define US5182D_OPMODE_PX 0x02 +#define US5182D_OPMODE_SHIFT 4 + +#define US5182D_REG_DARK_AUTO_EN_DEFAULT 0x80 +#define US5182D_REG_AUTO_LDARK_GAIN_DEFAULT 0x16 +#define US5182D_REG_AUTO_HDARK_GAIN_DEFAULT 0x00 + +#define US5182D_REG_ADL 0x0c +#define US5182D_REG_PDL 0x0e + +#define US5182D_REG_MODE_STORE 0x21 +#define US5182D_STORE_MODE 0x01 + +#define US5182D_REG_CHIPID 0xb2 + +#define US5182D_OPMODE_MASK GENMASK(5, 4) +#define US5182D_AGAIN_MASK 0x07 +#define US5182D_RESET_CHIP 0x01 + +#define US5182D_CHIPID 0x26 +#define US5182D_DRV_NAME "us5182d" + +#define US5182D_GA_RESOLUTION 1000 + +#define US5182D_READ_BYTE 1 +#define US5182D_READ_WORD 2 +#define US5182D_OPSTORE_SLEEP_TIME 20 /* ms */ +#define US5182D_SLEEP_MS 3000 /* ms */ +#define US5182D_PXH_TH_DISABLE 0xffff +#define US5182D_PXL_TH_DISABLE 0x0000 + +/* Available ranges: [12354, 7065, 3998, 2202, 1285, 498, 256, 138] lux */ +static const int us5182d_scales[] = {188500, 107800, 61000, 33600, 19600, 7600, + 3900, 2100}; + +/* + * Experimental thresholds that work with US5182D sensor on evaluation board + * roughly between 12-32 lux + */ +static u16 us5182d_dark_ths_vals[] = {170, 200, 512, 512, 800, 2000, 4000, + 8000}; + +enum mode { + US5182D_ALS_PX, + US5182D_ALS_ONLY, + US5182D_PX_ONLY +}; + +enum pmode { + US5182D_CONTINUOUS, + US5182D_ONESHOT +}; + +struct us5182d_data { + struct i2c_client *client; + struct mutex lock; + + /* Glass attenuation factor */ + u32 ga; + + /* Dark gain tuning */ + u8 lower_dark_gain; + u8 upper_dark_gain; + u16 *us5182d_dark_ths; + + u16 px_low_th; + u16 px_high_th; + + int rising_en; + int falling_en; + + u8 opmode; + u8 power_mode; + + bool als_enabled; + bool px_enabled; + + bool default_continuous; +}; + +static IIO_CONST_ATTR(in_illuminance_scale_available, + "0.0021 0.0039 0.0076 0.0196 0.0336 0.061 0.1078 0.1885"); + +static struct attribute *us5182d_attrs[] = { + &iio_const_attr_in_illuminance_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group us5182d_attr_group = { + .attrs = us5182d_attrs, +}; + +static const struct { + u8 reg; + u8 val; +} us5182d_regvals[] = { + {US5182D_REG_CFG0, US5182D_CFG0_WORD_ENABLE}, + {US5182D_REG_CFG1, US5182D_CFG1_ALS_RES16}, + {US5182D_REG_CFG2, (US5182D_CFG2_PX_RES16 | + US5182D_CFG2_PXGAIN_DEFAULT)}, + {US5182D_REG_CFG3, US5182D_CFG3_LED_CURRENT100 | + US5182D_CFG3_INT_SOURCE_PX}, + {US5182D_REG_CFG4, 0x00}, +}; + +static const struct iio_event_spec us5182d_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec us5182d_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = us5182d_events, + .num_event_specs = ARRAY_SIZE(us5182d_events), + } +}; + +static int us5182d_oneshot_en(struct us5182d_data *data) +{ + int ret; + + ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0); + if (ret < 0) + return ret; + + /* + * In oneshot mode the chip will power itself down after taking the + * required measurement. + */ + ret = ret | US5182D_CFG0_ONESHOT_EN; + + return i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0, ret); +} + +static int us5182d_set_opmode(struct us5182d_data *data, u8 mode) +{ + int ret; + + if (mode == data->opmode) + return 0; + + ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0); + if (ret < 0) + return ret; + + /* update mode */ + ret = ret & ~US5182D_OPMODE_MASK; + ret = ret | (mode << US5182D_OPMODE_SHIFT); + + /* + * After updating the operating mode, the chip requires that + * the operation is stored, by writing 1 in the STORE_MODE + * register (auto-clearing). + */ + ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0, ret); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_MODE_STORE, + US5182D_STORE_MODE); + if (ret < 0) + return ret; + + data->opmode = mode; + msleep(US5182D_OPSTORE_SLEEP_TIME); + + return 0; +} + +static int us5182d_als_enable(struct us5182d_data *data) +{ + int ret; + u8 mode; + + if (data->power_mode == US5182D_ONESHOT) { + ret = us5182d_set_opmode(data, US5182D_ALS_ONLY); + if (ret < 0) + return ret; + data->px_enabled = false; + } + + if (data->als_enabled) + return 0; + + mode = data->px_enabled ? US5182D_ALS_PX : US5182D_ALS_ONLY; + + ret = us5182d_set_opmode(data, mode); + if (ret < 0) + return ret; + + data->als_enabled = true; + + return 0; +} + +static int us5182d_px_enable(struct us5182d_data *data) +{ + int ret; + u8 mode; + + if (data->power_mode == US5182D_ONESHOT) { + ret = us5182d_set_opmode(data, US5182D_PX_ONLY); + if (ret < 0) + return ret; + data->als_enabled = false; + } + + if (data->px_enabled) + return 0; + + mode = data->als_enabled ? US5182D_ALS_PX : US5182D_PX_ONLY; + + ret = us5182d_set_opmode(data, mode); + if (ret < 0) + return ret; + + data->px_enabled = true; + + return 0; +} + +static int us5182d_get_als(struct us5182d_data *data) +{ + int ret; + unsigned long result; + + ret = us5182d_als_enable(data); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_word_data(data->client, + US5182D_REG_ADL); + if (ret < 0) + return ret; + + result = ret * data->ga / US5182D_GA_RESOLUTION; + if (result > 0xffff) + result = 0xffff; + + return result; +} + +static int us5182d_get_px(struct us5182d_data *data) +{ + int ret; + + ret = us5182d_px_enable(data); + if (ret < 0) + return ret; + + return i2c_smbus_read_word_data(data->client, + US5182D_REG_PDL); +} + +static int us5182d_shutdown_en(struct us5182d_data *data, u8 state) +{ + int ret; + + if (data->power_mode == US5182D_ONESHOT) + return 0; + + ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0); + if (ret < 0) + return ret; + + ret = ret & ~US5182D_CFG0_SHUTDOWN_EN; + ret = ret | state; + + ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0, ret); + if (ret < 0) + return ret; + + if (state & US5182D_CFG0_SHUTDOWN_EN) { + data->als_enabled = false; + data->px_enabled = false; + } + + return ret; +} + + +static int us5182d_set_power_state(struct us5182d_data *data, bool on) +{ + if (data->power_mode == US5182D_ONESHOT) + return 0; + + if (on) + return pm_runtime_resume_and_get(&data->client->dev); + + return pm_runtime_put_autosuspend(&data->client->dev); +} + +static int us5182d_read_value(struct us5182d_data *data, + struct iio_chan_spec const *chan) +{ + int ret, value; + + mutex_lock(&data->lock); + + if (data->power_mode == US5182D_ONESHOT) { + ret = us5182d_oneshot_en(data); + if (ret < 0) + goto out_err; + } + + ret = us5182d_set_power_state(data, true); + if (ret < 0) + goto out_err; + + if (chan->type == IIO_LIGHT) + ret = us5182d_get_als(data); + else + ret = us5182d_get_px(data); + if (ret < 0) + goto out_poweroff; + + value = ret; + + ret = us5182d_set_power_state(data, false); + if (ret < 0) + goto out_err; + + mutex_unlock(&data->lock); + return value; + +out_poweroff: + us5182d_set_power_state(data, false); +out_err: + mutex_unlock(&data->lock); + return ret; +} + +static int us5182d_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct us5182d_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = us5182d_read_value(data, chan); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG1); + if (ret < 0) + return ret; + *val = 0; + *val2 = us5182d_scales[ret & US5182D_AGAIN_MASK]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +/** + * us5182d_update_dark_th - update Darh_Th registers + * @data: us5182d_data structure + * @index: index in us5182d_dark_ths array to use for the updated value + * + * Function needs to be called with a lock held because it needs two i2c write + * byte operations as these registers (0x27 0x28) don't work in word mode + * accessing. + */ +static int us5182d_update_dark_th(struct us5182d_data *data, int index) +{ + __be16 dark_th = cpu_to_be16(data->us5182d_dark_ths[index]); + int ret; + + ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_UDARK_TH, + ((u8 *)&dark_th)[0]); + if (ret < 0) + return ret; + + return i2c_smbus_write_byte_data(data->client, US5182D_REG_UDARK_TH + 1, + ((u8 *)&dark_th)[1]); +} + +/** + * us5182d_apply_scale - update the ALS scale + * @data: us5182d_data structure + * @index: index in us5182d_scales array to use for the updated value + * + * Function needs to be called with a lock held as we're having more than one + * i2c operation. + */ +static int us5182d_apply_scale(struct us5182d_data *data, int index) +{ + int ret; + + ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG1); + if (ret < 0) + return ret; + + ret = ret & (~US5182D_AGAIN_MASK); + ret |= index; + + ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG1, ret); + if (ret < 0) + return ret; + + return us5182d_update_dark_th(data, index); +} + +static int us5182d_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct us5182d_data *data = iio_priv(indio_dev); + int ret, i; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (val != 0) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(us5182d_scales); i++) + if (val2 == us5182d_scales[i]) { + mutex_lock(&data->lock); + ret = us5182d_apply_scale(data, i); + mutex_unlock(&data->lock); + return ret; + } + break; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static int us5182d_setup_prox(struct iio_dev *indio_dev, + enum iio_event_direction dir, u16 val) +{ + struct us5182d_data *data = iio_priv(indio_dev); + + if (dir == IIO_EV_DIR_FALLING) + return i2c_smbus_write_word_data(data->client, + US5182D_REG_PXL_TH, val); + else if (dir == IIO_EV_DIR_RISING) + return i2c_smbus_write_word_data(data->client, + US5182D_REG_PXH_TH, val); + + return 0; +} + +static int us5182d_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, int *val, + int *val2) +{ + struct us5182d_data *data = iio_priv(indio_dev); + + switch (dir) { + case IIO_EV_DIR_RISING: + mutex_lock(&data->lock); + *val = data->px_high_th; + mutex_unlock(&data->lock); + break; + case IIO_EV_DIR_FALLING: + mutex_lock(&data->lock); + *val = data->px_low_th; + mutex_unlock(&data->lock); + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; +} + +static int us5182d_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, int val, + int val2) +{ + struct us5182d_data *data = iio_priv(indio_dev); + int ret; + + if (val < 0 || val > USHRT_MAX || val2 != 0) + return -EINVAL; + + switch (dir) { + case IIO_EV_DIR_RISING: + mutex_lock(&data->lock); + if (data->rising_en) { + ret = us5182d_setup_prox(indio_dev, dir, val); + if (ret < 0) + goto err; + } + data->px_high_th = val; + mutex_unlock(&data->lock); + break; + case IIO_EV_DIR_FALLING: + mutex_lock(&data->lock); + if (data->falling_en) { + ret = us5182d_setup_prox(indio_dev, dir, val); + if (ret < 0) + goto err; + } + data->px_low_th = val; + mutex_unlock(&data->lock); + break; + default: + return -EINVAL; + } + + return 0; +err: + mutex_unlock(&data->lock); + return ret; +} + +static int us5182d_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir) +{ + struct us5182d_data *data = iio_priv(indio_dev); + int ret; + + switch (dir) { + case IIO_EV_DIR_RISING: + mutex_lock(&data->lock); + ret = data->rising_en; + mutex_unlock(&data->lock); + break; + case IIO_EV_DIR_FALLING: + mutex_lock(&data->lock); + ret = data->falling_en; + mutex_unlock(&data->lock); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int us5182d_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct us5182d_data *data = iio_priv(indio_dev); + int ret; + u16 new_th; + + mutex_lock(&data->lock); + + switch (dir) { + case IIO_EV_DIR_RISING: + if (data->rising_en == state) { + mutex_unlock(&data->lock); + return 0; + } + new_th = US5182D_PXH_TH_DISABLE; + if (state) { + data->power_mode = US5182D_CONTINUOUS; + ret = us5182d_set_power_state(data, true); + if (ret < 0) + goto err; + ret = us5182d_px_enable(data); + if (ret < 0) + goto err_poweroff; + new_th = data->px_high_th; + } + ret = us5182d_setup_prox(indio_dev, dir, new_th); + if (ret < 0) + goto err_poweroff; + data->rising_en = state; + break; + case IIO_EV_DIR_FALLING: + if (data->falling_en == state) { + mutex_unlock(&data->lock); + return 0; + } + new_th = US5182D_PXL_TH_DISABLE; + if (state) { + data->power_mode = US5182D_CONTINUOUS; + ret = us5182d_set_power_state(data, true); + if (ret < 0) + goto err; + ret = us5182d_px_enable(data); + if (ret < 0) + goto err_poweroff; + new_th = data->px_low_th; + } + ret = us5182d_setup_prox(indio_dev, dir, new_th); + if (ret < 0) + goto err_poweroff; + data->falling_en = state; + break; + default: + ret = -EINVAL; + goto err; + } + + if (!state) { + ret = us5182d_set_power_state(data, false); + if (ret < 0) + goto err; + } + + if (!data->falling_en && !data->rising_en && !data->default_continuous) + data->power_mode = US5182D_ONESHOT; + + mutex_unlock(&data->lock); + return 0; + +err_poweroff: + if (state) + us5182d_set_power_state(data, false); +err: + mutex_unlock(&data->lock); + return ret; +} + +static const struct iio_info us5182d_info = { + .read_raw = us5182d_read_raw, + .write_raw = us5182d_write_raw, + .attrs = &us5182d_attr_group, + .read_event_value = &us5182d_read_thresh, + .write_event_value = &us5182d_write_thresh, + .read_event_config = &us5182d_read_event_config, + .write_event_config = &us5182d_write_event_config, +}; + +static int us5182d_reset(struct iio_dev *indio_dev) +{ + struct us5182d_data *data = iio_priv(indio_dev); + + return i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG3, + US5182D_RESET_CHIP); +} + +static int us5182d_init(struct iio_dev *indio_dev) +{ + struct us5182d_data *data = iio_priv(indio_dev); + int i, ret; + + ret = us5182d_reset(indio_dev); + if (ret < 0) + return ret; + + data->opmode = 0; + data->power_mode = US5182D_CONTINUOUS; + data->px_low_th = US5182D_REG_PXL_TH_DEFAULT; + data->px_high_th = US5182D_REG_PXH_TH_DEFAULT; + + for (i = 0; i < ARRAY_SIZE(us5182d_regvals); i++) { + ret = i2c_smbus_write_byte_data(data->client, + us5182d_regvals[i].reg, + us5182d_regvals[i].val); + if (ret < 0) + return ret; + } + + data->als_enabled = true; + data->px_enabled = true; + + if (!data->default_continuous) { + ret = us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); + if (ret < 0) + return ret; + data->power_mode = US5182D_ONESHOT; + } + + return ret; +} + +static void us5182d_get_platform_data(struct iio_dev *indio_dev) +{ + struct us5182d_data *data = iio_priv(indio_dev); + + if (device_property_read_u32(&data->client->dev, "upisemi,glass-coef", + &data->ga)) + data->ga = US5182D_GA_RESOLUTION; + if (device_property_read_u16_array(&data->client->dev, + "upisemi,dark-ths", + data->us5182d_dark_ths, + ARRAY_SIZE(us5182d_dark_ths_vals))) + data->us5182d_dark_ths = us5182d_dark_ths_vals; + if (device_property_read_u8(&data->client->dev, + "upisemi,upper-dark-gain", + &data->upper_dark_gain)) + data->upper_dark_gain = US5182D_REG_AUTO_HDARK_GAIN_DEFAULT; + if (device_property_read_u8(&data->client->dev, + "upisemi,lower-dark-gain", + &data->lower_dark_gain)) + data->lower_dark_gain = US5182D_REG_AUTO_LDARK_GAIN_DEFAULT; + data->default_continuous = device_property_read_bool(&data->client->dev, + "upisemi,continuous"); +} + +static int us5182d_dark_gain_config(struct iio_dev *indio_dev) +{ + struct us5182d_data *data = iio_priv(indio_dev); + int ret; + + ret = us5182d_update_dark_th(data, US5182D_CFG1_AGAIN_DEFAULT); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(data->client, + US5182D_REG_AUTO_LDARK_GAIN, + data->lower_dark_gain); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(data->client, + US5182D_REG_AUTO_HDARK_GAIN, + data->upper_dark_gain); + if (ret < 0) + return ret; + + return i2c_smbus_write_byte_data(data->client, US5182D_REG_DARK_AUTO_EN, + US5182D_REG_DARK_AUTO_EN_DEFAULT); +} + +static irqreturn_t us5182d_irq_thread_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct us5182d_data *data = iio_priv(indio_dev); + enum iio_event_direction dir; + int ret; + u64 ev; + + ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0); + if (ret < 0) { + dev_err(&data->client->dev, "i2c transfer error in irq\n"); + return IRQ_HANDLED; + } + + dir = ret & US5182D_CFG0_PROX ? IIO_EV_DIR_RISING : IIO_EV_DIR_FALLING; + ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 1, IIO_EV_TYPE_THRESH, dir); + + iio_push_event(indio_dev, ev, iio_get_time_ns(indio_dev)); + + ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0, + ret & ~US5182D_CFG0_PX_IRQ); + if (ret < 0) + dev_err(&data->client->dev, "i2c transfer error in irq\n"); + + return IRQ_HANDLED; +} + +static int us5182d_probe(struct i2c_client *client) +{ + struct us5182d_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + mutex_init(&data->lock); + + indio_dev->info = &us5182d_info; + indio_dev->name = US5182D_DRV_NAME; + indio_dev->channels = us5182d_channels; + indio_dev->num_channels = ARRAY_SIZE(us5182d_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CHIPID); + if (ret != US5182D_CHIPID) { + dev_err(&data->client->dev, + "Failed to detect US5182 light chip\n"); + return (ret < 0) ? ret : -ENODEV; + } + + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, + us5182d_irq_thread_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "us5182d-irq", indio_dev); + if (ret < 0) + return ret; + } else + dev_warn(&client->dev, "no valid irq found\n"); + + us5182d_get_platform_data(indio_dev); + ret = us5182d_init(indio_dev); + if (ret < 0) + return ret; + + ret = us5182d_dark_gain_config(indio_dev); + if (ret < 0) + goto out_err; + + if (data->default_continuous) { + ret = pm_runtime_set_active(&client->dev); + if (ret < 0) + goto out_err; + } + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, + US5182D_SLEEP_MS); + pm_runtime_use_autosuspend(&client->dev); + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto out_err; + + return 0; + +out_err: + us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); + return ret; + +} + +static void us5182d_remove(struct i2c_client *client) +{ + struct us5182d_data *data = iio_priv(i2c_get_clientdata(client)); + int ret; + + iio_device_unregister(i2c_get_clientdata(client)); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + ret = us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); + if (ret) + dev_warn(&client->dev, "Failed to shut down (%pe)\n", + ERR_PTR(ret)); +} + +static int us5182d_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct us5182d_data *data = iio_priv(indio_dev); + + if (data->power_mode == US5182D_CONTINUOUS) + return us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); + + return 0; +} + +static int us5182d_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct us5182d_data *data = iio_priv(indio_dev); + + if (data->power_mode == US5182D_CONTINUOUS) + return us5182d_shutdown_en(data, + ~US5182D_CFG0_SHUTDOWN_EN & 0xff); + + return 0; +} + +static const struct dev_pm_ops us5182d_pm_ops = { + SYSTEM_SLEEP_PM_OPS(us5182d_suspend, us5182d_resume) + RUNTIME_PM_OPS(us5182d_suspend, us5182d_resume, NULL) +}; + +static const struct acpi_device_id us5182d_acpi_match[] = { + { "USD5182", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(acpi, us5182d_acpi_match); + +static const struct i2c_device_id us5182d_id[] = { + { .name = "usd5182" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, us5182d_id); + +static const struct of_device_id us5182d_of_match[] = { + { .compatible = "upisemi,usd5182" }, + { } +}; +MODULE_DEVICE_TABLE(of, us5182d_of_match); + +static struct i2c_driver us5182d_driver = { + .driver = { + .name = US5182D_DRV_NAME, + .pm = pm_ptr(&us5182d_pm_ops), + .of_match_table = us5182d_of_match, + .acpi_match_table = us5182d_acpi_match, + }, + .probe = us5182d_probe, + .remove = us5182d_remove, + .id_table = us5182d_id, + +}; +module_i2c_driver(us5182d_driver); + +MODULE_AUTHOR("Adriana Reus "); +MODULE_DESCRIPTION("Driver for us5182d Proximity and Light Sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/vcnl4000.c b/drivers/iio/light/vcnl4000.c new file mode 100644 index 000000000..336468d59 --- /dev/null +++ b/drivers/iio/light/vcnl4000.c @@ -0,0 +1,2059 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * vcnl4000.c - Support for Vishay VCNL4000/4010/4020/4040/4200 combined ambient + * light and proximity sensor + * + * Copyright 2012 Peter Meerwald + * Copyright 2019 Pursim SPC + * Copyright 2020 Mathieu Othacehe + * + * IIO driver for: + * VCNL4000/10/20 (7-bit I2C slave address 0x13) + * VCNL4040 (7-bit I2C slave address 0x60) + * VCNL4200 (7-bit I2C slave address 0x51) + * + * TODO: + * allow to adjust IR current + * interrupts (VCNL4040, VCNL4200) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define VCNL4000_DRV_NAME "vcnl4000" +#define VCNL4000_PROD_ID 0x01 +#define VCNL4010_PROD_ID 0x02 /* for VCNL4020, VCNL4010 */ +#define VCNL4040_PROD_ID 0x86 +#define VCNL4200_PROD_ID 0x58 + +#define VCNL4000_COMMAND 0x80 /* Command register */ +#define VCNL4000_PROD_REV 0x81 /* Product ID and Revision ID */ +#define VCNL4010_PROX_RATE 0x82 /* Proximity rate */ +#define VCNL4000_LED_CURRENT 0x83 /* IR LED current for proximity mode */ +#define VCNL4000_AL_PARAM 0x84 /* Ambient light parameter register */ +#define VCNL4010_ALS_PARAM 0x84 /* ALS rate */ +#define VCNL4000_AL_RESULT_HI 0x85 /* Ambient light result register, MSB */ +#define VCNL4000_AL_RESULT_LO 0x86 /* Ambient light result register, LSB */ +#define VCNL4000_PS_RESULT_HI 0x87 /* Proximity result register, MSB */ +#define VCNL4000_PS_RESULT_LO 0x88 /* Proximity result register, LSB */ +#define VCNL4000_PS_MEAS_FREQ 0x89 /* Proximity test signal frequency */ +#define VCNL4010_INT_CTRL 0x89 /* Interrupt control */ +#define VCNL4000_PS_MOD_ADJ 0x8a /* Proximity modulator timing adjustment */ +#define VCNL4010_LOW_THR_HI 0x8a /* Low threshold, MSB */ +#define VCNL4010_LOW_THR_LO 0x8b /* Low threshold, LSB */ +#define VCNL4010_HIGH_THR_HI 0x8c /* High threshold, MSB */ +#define VCNL4010_HIGH_THR_LO 0x8d /* High threshold, LSB */ +#define VCNL4010_ISR 0x8e /* Interrupt status */ + +#define VCNL4200_AL_CONF 0x00 /* Ambient light configuration */ +#define VCNL4200_PS_CONF1 0x03 /* Proximity configuration */ +#define VCNL4200_PS_CONF3 0x04 /* Proximity configuration */ +#define VCNL4040_PS_THDL_LM 0x06 /* Proximity threshold low */ +#define VCNL4040_PS_THDH_LM 0x07 /* Proximity threshold high */ +#define VCNL4040_ALS_THDL_LM 0x02 /* Ambient light threshold low */ +#define VCNL4040_ALS_THDH_LM 0x01 /* Ambient light threshold high */ +#define VCNL4200_PS_DATA 0x08 /* Proximity data */ +#define VCNL4200_AL_DATA 0x09 /* Ambient light data */ +#define VCNL4040_INT_FLAGS 0x0b /* Interrupt register */ +#define VCNL4200_INT_FLAGS 0x0d /* Interrupt register */ +#define VCNL4200_DEV_ID 0x0e /* Device ID, slave address and version */ + +#define VCNL4040_DEV_ID 0x0c /* Device ID and version */ + +/* Bit masks for COMMAND register */ +#define VCNL4000_AL_RDY BIT(6) /* ALS data ready? */ +#define VCNL4000_PS_RDY BIT(5) /* proximity data ready? */ +#define VCNL4000_AL_OD BIT(4) /* start on-demand ALS measurement */ +#define VCNL4000_PS_OD BIT(3) /* start on-demand proximity measurement */ +#define VCNL4000_ALS_EN BIT(2) /* start ALS measurement */ +#define VCNL4000_PROX_EN BIT(1) /* start proximity measurement */ +#define VCNL4000_SELF_TIMED_EN BIT(0) /* start self-timed measurement */ + +#define VCNL4040_ALS_CONF_ALS_SHUTDOWN BIT(0) +#define VCNL4040_ALS_CONF_IT GENMASK(7, 6) /* Ambient integration time */ +#define VCNL4040_ALS_CONF_INT_EN BIT(1) /* Ambient light Interrupt enable */ +#define VCNL4040_ALS_CONF_PERS GENMASK(3, 2) /* Ambient interrupt persistence setting */ +#define VCNL4040_PS_CONF1_PS_SHUTDOWN BIT(0) +#define VCNL4040_PS_CONF2_PS_IT GENMASK(3, 1) /* Proximity integration time */ +#define VCNL4040_CONF1_PS_PERS GENMASK(5, 4) /* Proximity interrupt persistence setting */ +#define VCNL4040_PS_CONF2_PS_HD BIT(11) /* Proximity high definition */ +#define VCNL4040_PS_CONF2_PS_INT GENMASK(9, 8) /* Proximity interrupt mode */ +#define VCNL4040_PS_CONF3_MPS GENMASK(6, 5) /* Proximity multi pulse number */ +#define VCNL4040_PS_MS_LED_I GENMASK(10, 8) /* Proximity current */ +#define VCNL4040_PS_IF_AWAY BIT(8) /* Proximity event cross low threshold */ +#define VCNL4040_PS_IF_CLOSE BIT(9) /* Proximity event cross high threshold */ +#define VCNL4040_ALS_RISING BIT(12) /* Ambient Light cross high threshold */ +#define VCNL4040_ALS_FALLING BIT(13) /* Ambient Light cross low threshold */ + +/* Bit masks for interrupt registers. */ +#define VCNL4010_INT_THR_SEL BIT(0) /* Select threshold interrupt source */ +#define VCNL4010_INT_THR_EN BIT(1) /* Threshold interrupt type */ +#define VCNL4010_INT_ALS_EN BIT(2) /* Enable on ALS data ready */ +#define VCNL4010_INT_PROX_EN BIT(3) /* Enable on proximity data ready */ + +#define VCNL4010_INT_THR_HIGH 0 /* High threshold exceeded */ +#define VCNL4010_INT_THR_LOW 1 /* Low threshold exceeded */ +#define VCNL4010_INT_ALS 2 /* ALS data ready */ +#define VCNL4010_INT_PROXIMITY 3 /* Proximity data ready */ + +#define VCNL4010_INT_THR \ + (BIT(VCNL4010_INT_THR_LOW) | BIT(VCNL4010_INT_THR_HIGH)) +#define VCNL4010_INT_DRDY \ + (BIT(VCNL4010_INT_PROXIMITY) | BIT(VCNL4010_INT_ALS)) + +#define VCNL4040_CONF3_PS_MPS_16BITS 3 /* 8 multi pulses */ +#define VCNL4040_CONF3_PS_LED_I_16BITS 3 /* 120 mA */ + +#define VCNL4040_CONF3_PS_SAMPLE_16BITS \ + (FIELD_PREP(VCNL4040_PS_CONF3_MPS, VCNL4040_CONF3_PS_MPS_16BITS) | \ + FIELD_PREP(VCNL4040_PS_MS_LED_I, VCNL4040_CONF3_PS_LED_I_16BITS)) + +static const int vcnl4010_prox_sampling_frequency[][2] = { + {1, 950000}, + {3, 906250}, + {7, 812500}, + {16, 625000}, + {31, 250000}, + {62, 500000}, + {125, 0}, + {250, 0}, +}; + +static const int vcnl4040_ps_it_times[][2] = { + {0, 100}, + {0, 150}, + {0, 200}, + {0, 250}, + {0, 300}, + {0, 350}, + {0, 400}, + {0, 800}, +}; + +static const int vcnl4200_ps_it_times[][2] = { + {0, 96}, + {0, 144}, + {0, 192}, + {0, 384}, + {0, 768}, + {0, 864}, +}; + +static const int vcnl4040_als_it_times[][2] = { + {0, 80000}, + {0, 160000}, + {0, 320000}, + {0, 640000}, +}; + +static const int vcnl4200_als_it_times[][2] = { + {0, 50000}, + {0, 100000}, + {0, 200000}, + {0, 400000}, +}; + +static const int vcnl4040_ps_calibbias_ua[][2] = { + {0, 50000}, + {0, 75000}, + {0, 100000}, + {0, 120000}, + {0, 140000}, + {0, 160000}, + {0, 180000}, + {0, 200000}, +}; + +static const int vcnl4040_als_persistence[] = {1, 2, 4, 8}; +static const int vcnl4040_ps_persistence[] = {1, 2, 3, 4}; +static const int vcnl4040_ps_oversampling_ratio[] = {1, 2, 4, 8}; + +#define VCNL4000_SLEEP_DELAY_MS 2000 /* before we enter pm_runtime_suspend */ + +struct vcnl4200_channel { + u8 reg; + ktime_t last_measurement; + ktime_t sampling_rate; + struct mutex lock; +}; + +struct vcnl4000_data { + struct i2c_client *client; + int rev; + int al_scale; + int ps_scale; + u8 ps_int; /* proximity interrupt mode */ + u8 als_int; /* ambient light interrupt mode*/ + const struct vcnl4000_chip_spec *chip_spec; + struct mutex vcnl4000_lock; + struct vcnl4200_channel vcnl4200_al; + struct vcnl4200_channel vcnl4200_ps; + uint32_t near_level; +}; + +struct vcnl4000_chip_spec { + const char *prod; + struct iio_chan_spec const *channels; + const int num_channels; + const struct iio_info *info; + const struct iio_buffer_setup_ops *buffer_setup_ops; + int (*init)(struct vcnl4000_data *data); + int (*measure_light)(struct vcnl4000_data *data, int *val); + int (*measure_proximity)(struct vcnl4000_data *data, int *val); + int (*set_power_state)(struct vcnl4000_data *data, bool on); + irqreturn_t (*irq_thread)(int irq, void *priv); + irqreturn_t (*trig_buffer_func)(int irq, void *priv); + + u8 int_reg; + const int(*ps_it_times)[][2]; + const int num_ps_it_times; + const int(*als_it_times)[][2]; + const int num_als_it_times; + const unsigned int ulux_step; + const int prod_id; +}; + +static int vcnl4000_set_power_state(struct vcnl4000_data *data, bool on) +{ + /* no suspend op */ + return 0; +} + +static int vcnl4000_init(struct vcnl4000_data *data) +{ + int ret, prod_id; + + ret = i2c_smbus_read_byte_data(data->client, VCNL4000_PROD_REV); + if (ret < 0) + return ret; + + prod_id = ret >> 4; + switch (prod_id) { + case VCNL4000_PROD_ID: + if (data->chip_spec->prod_id != VCNL4000_PROD_ID) + dev_warn(&data->client->dev, + "wrong device id, use vcnl4000"); + break; + case VCNL4010_PROD_ID: + if (data->chip_spec->prod_id != VCNL4010_PROD_ID) + dev_warn(&data->client->dev, + "wrong device id, use vcnl4010/4020"); + break; + default: + return -ENODEV; + } + + data->rev = ret & 0xf; + data->al_scale = 250000; + + return 0; +}; + +static ssize_t vcnl4000_write_als_enable(struct vcnl4000_data *data, bool en) +{ + int ret; + + guard(mutex)(&data->vcnl4000_lock); + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_AL_CONF); + if (ret < 0) + return ret; + + if (en) + ret &= ~VCNL4040_ALS_CONF_ALS_SHUTDOWN; + else + ret |= VCNL4040_ALS_CONF_ALS_SHUTDOWN; + + return i2c_smbus_write_word_data(data->client, VCNL4200_AL_CONF, ret); +} + +static ssize_t vcnl4000_write_ps_enable(struct vcnl4000_data *data, bool en) +{ + int ret; + + guard(mutex)(&data->vcnl4000_lock); + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF1); + if (ret < 0) + return ret; + + if (en) + ret &= ~VCNL4040_PS_CONF1_PS_SHUTDOWN; + else + ret |= VCNL4040_PS_CONF1_PS_SHUTDOWN; + + return i2c_smbus_write_word_data(data->client, VCNL4200_PS_CONF1, ret); +} + +static int vcnl4200_set_power_state(struct vcnl4000_data *data, bool on) +{ + int ret; + + /* Do not power down if interrupts are enabled */ + if (!on && (data->ps_int || data->als_int)) + return 0; + + ret = vcnl4000_write_als_enable(data, on); + if (ret < 0) + return ret; + + ret = vcnl4000_write_ps_enable(data, on); + if (ret < 0) + return ret; + + if (on) { + /* Wait at least one integration cycle before fetching data */ + data->vcnl4200_al.last_measurement = ktime_get(); + data->vcnl4200_ps.last_measurement = ktime_get(); + } + + return 0; +} + +static int vcnl4200_init(struct vcnl4000_data *data) +{ + struct i2c_client *client = data->client; + struct device *dev = &client->dev; + int ret, id; + u16 regval; + + ret = i2c_smbus_read_word_data(client, VCNL4200_DEV_ID); + if (ret < 0) + return ret; + + id = ret & 0xff; + + if (id != VCNL4200_PROD_ID) { + ret = i2c_smbus_read_word_data(client, VCNL4040_DEV_ID); + if (ret < 0) + return ret; + + id = ret & 0xff; + + if (id != VCNL4040_PROD_ID) + return -ENODEV; + } + + dev_dbg(dev, "device id 0x%x", id); + + data->rev = (ret >> 8) & 0xf; + data->ps_int = 0; + data->als_int = 0; + + data->vcnl4200_al.reg = VCNL4200_AL_DATA; + data->vcnl4200_ps.reg = VCNL4200_PS_DATA; + switch (id) { + case VCNL4200_PROD_ID: + /* Default wait time is 50ms, add 20% tolerance. */ + data->vcnl4200_al.sampling_rate = ktime_set(0, 60000 * 1000); + /* Default wait time is 4.8ms, add 20% tolerance. */ + data->vcnl4200_ps.sampling_rate = ktime_set(0, 5760 * 1000); + break; + case VCNL4040_PROD_ID: + /* Default wait time is 80ms, add 20% tolerance. */ + data->vcnl4200_al.sampling_rate = ktime_set(0, 96000 * 1000); + /* Default wait time is 5ms, add 20% tolerance. */ + data->vcnl4200_ps.sampling_rate = ktime_set(0, 6000 * 1000); + break; + } + data->al_scale = data->chip_spec->ulux_step; + data->ps_scale = 16; + + ret = devm_mutex_init(dev, &data->vcnl4200_al.lock); + if (ret) + return ret; + + ret = devm_mutex_init(dev, &data->vcnl4200_ps.lock); + if (ret) + return ret; + + /* Use 16 bits proximity sensor readings */ + ret = i2c_smbus_read_word_data(client, VCNL4200_PS_CONF1); + if (ret < 0) + return ret; + + regval = ret | VCNL4040_PS_CONF2_PS_HD; + ret = i2c_smbus_write_word_data(client, VCNL4200_PS_CONF1, regval); + if (ret < 0) + return ret; + + /* Align proximity sensor sample rate to 16 bits data width */ + ret = i2c_smbus_read_word_data(client, VCNL4200_PS_CONF3); + if (ret < 0) + return ret; + + regval = ret | VCNL4040_CONF3_PS_SAMPLE_16BITS; + ret = i2c_smbus_write_word_data(client, VCNL4200_PS_CONF3, regval); + if (ret < 0) + return ret; + + return 0; +}; + +static int vcnl4000_read_data(struct vcnl4000_data *data, u8 data_reg, int *val) +{ + s32 ret; + + ret = i2c_smbus_read_word_swapped(data->client, data_reg); + if (ret < 0) + return ret; + + *val = ret; + return 0; +} + +static int vcnl4000_write_data(struct vcnl4000_data *data, u8 data_reg, int val) +{ + if (val > U16_MAX) + return -ERANGE; + + return i2c_smbus_write_word_swapped(data->client, data_reg, val); +} + + +static int vcnl4000_measure(struct vcnl4000_data *data, u8 req_mask, + u8 rdy_mask, u8 data_reg, int *val) +{ + int tries = 20; + int ret; + + guard(mutex)(&data->vcnl4000_lock); + + ret = i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND, + req_mask); + if (ret < 0) + return ret; + + /* wait for data to become ready */ + while (tries--) { + ret = i2c_smbus_read_byte_data(data->client, VCNL4000_COMMAND); + if (ret < 0) + return ret; + if (ret & rdy_mask) + break; + msleep(20); /* measurement takes up to 100 ms */ + } + + if (tries < 0) { + dev_err(&data->client->dev, + "vcnl4000_measure() failed, data not ready\n"); + return -EIO; + } + + return vcnl4000_read_data(data, data_reg, val); +} + +static int vcnl4200_measure(struct vcnl4000_data *data, + struct vcnl4200_channel *chan, int *val) +{ + int ret; + s64 delta; + ktime_t next_measurement; + + scoped_guard(mutex, &chan->lock) { + next_measurement = ktime_add(chan->last_measurement, + chan->sampling_rate); + delta = ktime_us_delta(next_measurement, ktime_get()); + if (delta > 0) + usleep_range(delta, delta + 500); + chan->last_measurement = ktime_get(); + } + + ret = i2c_smbus_read_word_data(data->client, chan->reg); + if (ret < 0) + return ret; + + *val = ret; + + return 0; +} + +static int vcnl4000_measure_light(struct vcnl4000_data *data, int *val) +{ + return vcnl4000_measure(data, + VCNL4000_AL_OD, VCNL4000_AL_RDY, + VCNL4000_AL_RESULT_HI, val); +} + +static int vcnl4200_measure_light(struct vcnl4000_data *data, int *val) +{ + return vcnl4200_measure(data, &data->vcnl4200_al, val); +} + +static int vcnl4000_measure_proximity(struct vcnl4000_data *data, int *val) +{ + return vcnl4000_measure(data, + VCNL4000_PS_OD, VCNL4000_PS_RDY, + VCNL4000_PS_RESULT_HI, val); +} + +static int vcnl4200_measure_proximity(struct vcnl4000_data *data, int *val) +{ + return vcnl4200_measure(data, &data->vcnl4200_ps, val); +} + +static int vcnl4010_read_proxy_samp_freq(struct vcnl4000_data *data, int *val, + int *val2) +{ + int ret; + + ret = i2c_smbus_read_byte_data(data->client, VCNL4010_PROX_RATE); + if (ret < 0) + return ret; + + if (ret >= ARRAY_SIZE(vcnl4010_prox_sampling_frequency)) + return -EINVAL; + + *val = vcnl4010_prox_sampling_frequency[ret][0]; + *val2 = vcnl4010_prox_sampling_frequency[ret][1]; + + return 0; +} + +static bool vcnl4010_is_in_periodic_mode(struct vcnl4000_data *data) +{ + int ret; + + ret = i2c_smbus_read_byte_data(data->client, VCNL4000_COMMAND); + if (ret < 0) + return false; + + return !!(ret & VCNL4000_SELF_TIMED_EN); +} + +static int vcnl4040_read_als_it(struct vcnl4000_data *data, int *val, int *val2) +{ + int ret; + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_AL_CONF); + if (ret < 0) + return ret; + + ret = FIELD_GET(VCNL4040_ALS_CONF_IT, ret); + if (ret >= data->chip_spec->num_als_it_times) + return -EINVAL; + + *val = (*data->chip_spec->als_it_times)[ret][0]; + *val2 = (*data->chip_spec->als_it_times)[ret][1]; + + return 0; +} + +static ssize_t vcnl4040_write_als_it(struct vcnl4000_data *data, int val) +{ + unsigned int i; + int ret; + u16 regval; + + for (i = 0; i < data->chip_spec->num_als_it_times; i++) { + if (val == (*data->chip_spec->als_it_times)[i][1]) + break; + } + + if (i == data->chip_spec->num_als_it_times) + return -EINVAL; + + data->vcnl4200_al.sampling_rate = ktime_set(0, val * 1200); + data->al_scale = div_u64(mul_u32_u32(data->chip_spec->ulux_step, + (*data->chip_spec->als_it_times)[0][1]), + val); + + guard(mutex)(&data->vcnl4000_lock); + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_AL_CONF); + if (ret < 0) + return ret; + + regval = FIELD_PREP(VCNL4040_ALS_CONF_IT, i); + regval |= (ret & ~VCNL4040_ALS_CONF_IT); + return i2c_smbus_write_word_data(data->client, VCNL4200_AL_CONF, regval); +} + +static int vcnl4040_read_ps_it(struct vcnl4000_data *data, int *val, int *val2) +{ + int ret; + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF1); + if (ret < 0) + return ret; + + ret = FIELD_GET(VCNL4040_PS_CONF2_PS_IT, ret); + + if (ret >= data->chip_spec->num_ps_it_times) + return -EINVAL; + + *val = (*data->chip_spec->ps_it_times)[ret][0]; + *val2 = (*data->chip_spec->ps_it_times)[ret][1]; + + return 0; +} + +static ssize_t vcnl4040_write_ps_it(struct vcnl4000_data *data, int val) +{ + unsigned int i; + int ret, index = -1; + u16 regval; + + for (i = 0; i < data->chip_spec->num_ps_it_times; i++) { + if (val == (*data->chip_spec->ps_it_times)[i][1]) { + index = i; + break; + } + } + + if (index < 0) + return -EINVAL; + + data->vcnl4200_ps.sampling_rate = ktime_set(0, val * 60 * NSEC_PER_USEC); + + guard(mutex)(&data->vcnl4000_lock); + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF1); + if (ret < 0) + return ret; + + regval = (ret & ~VCNL4040_PS_CONF2_PS_IT) | + FIELD_PREP(VCNL4040_PS_CONF2_PS_IT, index); + return i2c_smbus_write_word_data(data->client, VCNL4200_PS_CONF1, regval); +} + +static ssize_t vcnl4040_read_als_period(struct vcnl4000_data *data, int *val, int *val2) +{ + int ret, ret_pers, it; + int64_t val_c; + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_AL_CONF); + if (ret < 0) + return ret; + + ret_pers = FIELD_GET(VCNL4040_ALS_CONF_PERS, ret); + if (ret_pers >= ARRAY_SIZE(vcnl4040_als_persistence)) + return -EINVAL; + + it = FIELD_GET(VCNL4040_ALS_CONF_IT, ret); + if (it >= data->chip_spec->num_als_it_times) + return -EINVAL; + + val_c = mul_u32_u32((*data->chip_spec->als_it_times)[it][1], + vcnl4040_als_persistence[ret_pers]); + *val = div_u64_rem(val_c, MICRO, val2); + + return IIO_VAL_INT_PLUS_MICRO; +} + +static ssize_t vcnl4040_write_als_period(struct vcnl4000_data *data, int val, int val2) +{ + unsigned int i; + int ret, it; + u16 regval; + u64 val_n = mul_u32_u32(val, MICRO) + val2; + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_AL_CONF); + if (ret < 0) + return ret; + + it = FIELD_GET(VCNL4040_ALS_CONF_IT, ret); + if (it >= data->chip_spec->num_als_it_times) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(vcnl4040_als_persistence) - 1; i++) { + if (val_n < mul_u32_u32(vcnl4040_als_persistence[i], + (*data->chip_spec->als_it_times)[it][1])) + break; + } + + guard(mutex)(&data->vcnl4000_lock); + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_AL_CONF); + if (ret < 0) + return ret; + + regval = FIELD_PREP(VCNL4040_ALS_CONF_PERS, i); + regval |= (ret & ~VCNL4040_ALS_CONF_PERS); + return i2c_smbus_write_word_data(data->client, VCNL4200_AL_CONF, regval); +} + +static ssize_t vcnl4040_read_ps_period(struct vcnl4000_data *data, int *val, int *val2) +{ + int ret, ret_pers, it; + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF1); + if (ret < 0) + return ret; + + ret_pers = FIELD_GET(VCNL4040_CONF1_PS_PERS, ret); + if (ret_pers >= ARRAY_SIZE(vcnl4040_ps_persistence)) + return -EINVAL; + + it = FIELD_GET(VCNL4040_PS_CONF2_PS_IT, ret); + if (it >= data->chip_spec->num_ps_it_times) + return -EINVAL; + + *val = (*data->chip_spec->ps_it_times)[it][0]; + *val2 = (*data->chip_spec->ps_it_times)[it][1] * + vcnl4040_ps_persistence[ret_pers]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static ssize_t vcnl4040_write_ps_period(struct vcnl4000_data *data, int val, int val2) +{ + int ret, it, i; + u16 regval; + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF1); + if (ret < 0) + return ret; + + it = FIELD_GET(VCNL4040_PS_CONF2_PS_IT, ret); + if (it >= data->chip_spec->num_ps_it_times) + return -EINVAL; + + if (val > 0) + i = ARRAY_SIZE(vcnl4040_ps_persistence) - 1; + else { + for (i = 0; i < ARRAY_SIZE(vcnl4040_ps_persistence) - 1; i++) { + if (val2 <= vcnl4040_ps_persistence[i] * + (*data->chip_spec->ps_it_times)[it][1]) + break; + } + } + + guard(mutex)(&data->vcnl4000_lock); + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF1); + if (ret < 0) + return ret; + + regval = FIELD_PREP(VCNL4040_CONF1_PS_PERS, i); + regval |= (ret & ~VCNL4040_CONF1_PS_PERS); + return i2c_smbus_write_word_data(data->client, VCNL4200_PS_CONF1, regval); +} + +static ssize_t vcnl4040_read_ps_oversampling_ratio(struct vcnl4000_data *data, int *val) +{ + int ret; + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF3); + if (ret < 0) + return ret; + + ret = FIELD_GET(VCNL4040_PS_CONF3_MPS, ret); + if (ret >= ARRAY_SIZE(vcnl4040_ps_oversampling_ratio)) + return -EINVAL; + + *val = vcnl4040_ps_oversampling_ratio[ret]; + + return ret; +} + +static ssize_t vcnl4040_write_ps_oversampling_ratio(struct vcnl4000_data *data, int val) +{ + unsigned int i; + int ret; + u16 regval; + + for (i = 0; i < ARRAY_SIZE(vcnl4040_ps_oversampling_ratio); i++) { + if (val == vcnl4040_ps_oversampling_ratio[i]) + break; + } + + if (i >= ARRAY_SIZE(vcnl4040_ps_oversampling_ratio)) + return -EINVAL; + + guard(mutex)(&data->vcnl4000_lock); + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF3); + if (ret < 0) + return ret; + + regval = FIELD_PREP(VCNL4040_PS_CONF3_MPS, i); + regval |= (ret & ~VCNL4040_PS_CONF3_MPS); + return i2c_smbus_write_word_data(data->client, VCNL4200_PS_CONF3, regval); +} + +static ssize_t vcnl4040_read_ps_calibbias(struct vcnl4000_data *data, int *val, int *val2) +{ + int ret; + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF3); + if (ret < 0) + return ret; + + ret = FIELD_GET(VCNL4040_PS_MS_LED_I, ret); + if (ret >= ARRAY_SIZE(vcnl4040_ps_calibbias_ua)) + return -EINVAL; + + *val = vcnl4040_ps_calibbias_ua[ret][0]; + *val2 = vcnl4040_ps_calibbias_ua[ret][1]; + + return ret; +} + +static ssize_t vcnl4040_write_ps_calibbias(struct vcnl4000_data *data, int val) +{ + unsigned int i; + int ret; + u16 regval; + + for (i = 0; i < ARRAY_SIZE(vcnl4040_ps_calibbias_ua); i++) { + if (val == vcnl4040_ps_calibbias_ua[i][1]) + break; + } + + if (i >= ARRAY_SIZE(vcnl4040_ps_calibbias_ua)) + return -EINVAL; + + guard(mutex)(&data->vcnl4000_lock); + + ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF3); + if (ret < 0) + return ret; + + regval = (ret & ~VCNL4040_PS_MS_LED_I); + regval |= FIELD_PREP(VCNL4040_PS_MS_LED_I, i); + return i2c_smbus_write_word_data(data->client, VCNL4200_PS_CONF3, regval); +} + +static int vcnl4000_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct vcnl4000_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = pm_runtime_resume_and_get(&data->client->dev); + if (ret < 0) + return ret; + + switch (chan->type) { + case IIO_LIGHT: + ret = data->chip_spec->measure_light(data, val); + if (!ret) + ret = IIO_VAL_INT; + break; + case IIO_PROXIMITY: + ret = data->chip_spec->measure_proximity(data, val); + *val2 = data->ps_scale; + if (!ret) + ret = IIO_VAL_FRACTIONAL; + break; + default: + ret = -EINVAL; + } + pm_runtime_put_autosuspend(&data->client->dev); + return ret; + case IIO_CHAN_INFO_SCALE: + if (chan->type != IIO_LIGHT) + return -EINVAL; + + *val = 0; + *val2 = data->al_scale; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_LIGHT: + ret = vcnl4040_read_als_it(data, val, val2); + break; + case IIO_PROXIMITY: + ret = vcnl4040_read_ps_it(data, val, val2); + break; + default: + return -EINVAL; + } + if (ret < 0) + return ret; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_PROXIMITY: + ret = vcnl4040_read_ps_oversampling_ratio(data, val); + if (ret < 0) + return ret; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_PROXIMITY: + ret = vcnl4040_read_ps_calibbias(data, val, val2); + if (ret < 0) + return ret; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int vcnl4040_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct vcnl4000_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val != 0) + return -EINVAL; + switch (chan->type) { + case IIO_LIGHT: + return vcnl4040_write_als_it(data, val2); + case IIO_PROXIMITY: + return vcnl4040_write_ps_it(data, val2); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_PROXIMITY: + return vcnl4040_write_ps_oversampling_ratio(data, val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_PROXIMITY: + return vcnl4040_write_ps_calibbias(data, val2); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int vcnl4040_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct vcnl4000_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_LIGHT: + *vals = (int *)(*data->chip_spec->als_it_times); + *length = 2 * data->chip_spec->num_als_it_times; + break; + case IIO_PROXIMITY: + *vals = (int *)(*data->chip_spec->ps_it_times); + *length = 2 * data->chip_spec->num_ps_it_times; + break; + default: + return -EINVAL; + } + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_PROXIMITY: + *vals = (int *)vcnl4040_ps_oversampling_ratio; + *length = ARRAY_SIZE(vcnl4040_ps_oversampling_ratio); + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_PROXIMITY: + *vals = (int *)vcnl4040_ps_calibbias_ua; + *length = 2 * ARRAY_SIZE(vcnl4040_ps_calibbias_ua); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int vcnl4010_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct vcnl4000_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_SCALE: { + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + /* Protect against event capture. */ + if (vcnl4010_is_in_periodic_mode(data)) + return -EBUSY; + + return vcnl4000_read_raw(indio_dev, chan, val, val2, mask); + } + case IIO_CHAN_INFO_SAMP_FREQ: + switch (chan->type) { + case IIO_PROXIMITY: + ret = vcnl4010_read_proxy_samp_freq(data, val, val2); + if (ret < 0) + return ret; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int vcnl4010_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = (int *)vcnl4010_prox_sampling_frequency; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = 2 * ARRAY_SIZE(vcnl4010_prox_sampling_frequency); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int vcnl4010_write_proxy_samp_freq(struct vcnl4000_data *data, int val, + int val2) +{ + unsigned int i; + int index = -1; + + for (i = 0; i < ARRAY_SIZE(vcnl4010_prox_sampling_frequency); i++) { + if (val == vcnl4010_prox_sampling_frequency[i][0] && + val2 == vcnl4010_prox_sampling_frequency[i][1]) { + index = i; + break; + } + } + + if (index < 0) + return -EINVAL; + + return i2c_smbus_write_byte_data(data->client, VCNL4010_PROX_RATE, + index); +} + +static int vcnl4010_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct vcnl4000_data *data = iio_priv(indio_dev); + + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + /* Protect against event capture. */ + if (vcnl4010_is_in_periodic_mode(data)) + return -EBUSY; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + switch (chan->type) { + case IIO_PROXIMITY: + return vcnl4010_write_proxy_samp_freq(data, val, val2); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int vcnl4010_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + int ret; + struct vcnl4000_data *data = iio_priv(indio_dev); + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + ret = vcnl4000_read_data(data, VCNL4010_HIGH_THR_HI, + val); + if (ret < 0) + return ret; + return IIO_VAL_INT; + case IIO_EV_DIR_FALLING: + ret = vcnl4000_read_data(data, VCNL4010_LOW_THR_HI, + val); + if (ret < 0) + return ret; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int vcnl4010_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + int ret; + struct vcnl4000_data *data = iio_priv(indio_dev); + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + ret = vcnl4000_write_data(data, VCNL4010_HIGH_THR_HI, + val); + if (ret < 0) + return ret; + return IIO_VAL_INT; + case IIO_EV_DIR_FALLING: + ret = vcnl4000_write_data(data, VCNL4010_LOW_THR_HI, + val); + if (ret < 0) + return ret; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int vcnl4040_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + int ret; + struct vcnl4000_data *data = iio_priv(indio_dev); + + switch (chan->type) { + case IIO_LIGHT: + switch (info) { + case IIO_EV_INFO_PERIOD: + return vcnl4040_read_als_period(data, val, val2); + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + ret = i2c_smbus_read_word_data(data->client, + VCNL4040_ALS_THDH_LM); + break; + case IIO_EV_DIR_FALLING: + ret = i2c_smbus_read_word_data(data->client, + VCNL4040_ALS_THDL_LM); + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + break; + case IIO_PROXIMITY: + switch (info) { + case IIO_EV_INFO_PERIOD: + return vcnl4040_read_ps_period(data, val, val2); + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + ret = i2c_smbus_read_word_data(data->client, + VCNL4040_PS_THDH_LM); + break; + case IIO_EV_DIR_FALLING: + ret = i2c_smbus_read_word_data(data->client, + VCNL4040_PS_THDL_LM); + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; +} + +static int vcnl4040_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + int ret; + struct vcnl4000_data *data = iio_priv(indio_dev); + + switch (chan->type) { + case IIO_LIGHT: + switch (info) { + case IIO_EV_INFO_PERIOD: + return vcnl4040_write_als_period(data, val, val2); + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + ret = i2c_smbus_write_word_data(data->client, + VCNL4040_ALS_THDH_LM, + val); + break; + case IIO_EV_DIR_FALLING: + ret = i2c_smbus_write_word_data(data->client, + VCNL4040_ALS_THDL_LM, + val); + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + break; + case IIO_PROXIMITY: + switch (info) { + case IIO_EV_INFO_PERIOD: + return vcnl4040_write_ps_period(data, val, val2); + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + ret = i2c_smbus_write_word_data(data->client, + VCNL4040_PS_THDH_LM, + val); + break; + case IIO_EV_DIR_FALLING: + ret = i2c_smbus_write_word_data(data->client, + VCNL4040_PS_THDL_LM, + val); + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + if (ret < 0) + return ret; + return IIO_VAL_INT; +} + +static bool vcnl4010_is_thr_enabled(struct vcnl4000_data *data) +{ + int ret; + + ret = i2c_smbus_read_byte_data(data->client, VCNL4010_INT_CTRL); + if (ret < 0) + return false; + + return !!(ret & VCNL4010_INT_THR_EN); +} + +static int vcnl4010_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct vcnl4000_data *data = iio_priv(indio_dev); + + switch (chan->type) { + case IIO_PROXIMITY: + return vcnl4010_is_thr_enabled(data); + default: + return -EINVAL; + } +} + +static int vcnl4010_config_threshold_enable(struct vcnl4000_data *data) +{ + int ret; + + /* Enable periodic measurement of proximity data. */ + ret = i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND, + VCNL4000_SELF_TIMED_EN | VCNL4000_PROX_EN); + if (ret < 0) + return ret; + + /* + * Enable interrupts on threshold, for proximity data by + * default. + */ + return i2c_smbus_write_byte_data(data->client, VCNL4010_INT_CTRL, + VCNL4010_INT_THR_EN); +} + +static int vcnl4010_config_threshold_disable(struct vcnl4000_data *data) +{ + int ret; + + if (!vcnl4010_is_thr_enabled(data)) + return 0; + + ret = i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND, 0); + if (ret < 0) + return ret; + + return i2c_smbus_write_byte_data(data->client, VCNL4010_INT_CTRL, 0); +} + +static int vcnl4010_config_threshold(struct iio_dev *indio_dev, bool state) +{ + struct vcnl4000_data *data = iio_priv(indio_dev); + + if (state) { + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + return vcnl4010_config_threshold_enable(data); + } else { + return vcnl4010_config_threshold_disable(data); + } +} + +static int vcnl4010_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + switch (chan->type) { + case IIO_PROXIMITY: + return vcnl4010_config_threshold(indio_dev, state); + default: + return -EINVAL; + } +} + +static int vcnl4040_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + int ret; + struct vcnl4000_data *data = iio_priv(indio_dev); + + switch (chan->type) { + case IIO_LIGHT: + ret = i2c_smbus_read_word_data(data->client, VCNL4200_AL_CONF); + if (ret < 0) + return ret; + + data->als_int = FIELD_GET(VCNL4040_ALS_CONF_INT_EN, ret); + + return data->als_int; + case IIO_PROXIMITY: + ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF1); + if (ret < 0) + return ret; + + data->ps_int = FIELD_GET(VCNL4040_PS_CONF2_PS_INT, ret); + + return (dir == IIO_EV_DIR_RISING) ? + FIELD_GET(VCNL4040_PS_IF_AWAY, ret) : + FIELD_GET(VCNL4040_PS_IF_CLOSE, ret); + default: + return -EINVAL; + } +} + +static int vcnl4040_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + int ret; + u16 val, mask; + struct vcnl4000_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->vcnl4000_lock); + + switch (chan->type) { + case IIO_LIGHT: + ret = i2c_smbus_read_word_data(data->client, VCNL4200_AL_CONF); + if (ret < 0) + return ret; + + mask = VCNL4040_ALS_CONF_INT_EN; + if (state) + val = (ret | mask); + else + val = (ret & ~mask); + + data->als_int = FIELD_GET(VCNL4040_ALS_CONF_INT_EN, val); + return i2c_smbus_write_word_data(data->client, VCNL4200_AL_CONF, val); + case IIO_PROXIMITY: + ret = i2c_smbus_read_word_data(data->client, VCNL4200_PS_CONF1); + if (ret < 0) + return ret; + + if (dir == IIO_EV_DIR_RISING) + mask = VCNL4040_PS_IF_AWAY; + else + mask = VCNL4040_PS_IF_CLOSE; + + val = state ? (ret | mask) : (ret & ~mask); + + data->ps_int = FIELD_GET(VCNL4040_PS_CONF2_PS_INT, val); + return i2c_smbus_write_word_data(data->client, VCNL4200_PS_CONF1, val); + default: + return -EINVAL; + } +} + +static irqreturn_t vcnl4040_irq_thread(int irq, void *p) +{ + struct iio_dev *indio_dev = p; + struct vcnl4000_data *data = iio_priv(indio_dev); + int ret; + + ret = i2c_smbus_read_word_data(data->client, data->chip_spec->int_reg); + if (ret < 0) + return IRQ_HANDLED; + + if (ret & VCNL4040_PS_IF_CLOSE) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + } + + if (ret & VCNL4040_PS_IF_AWAY) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns(indio_dev)); + } + + if (ret & VCNL4040_ALS_FALLING) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns(indio_dev)); + } + + if (ret & VCNL4040_ALS_RISING) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + } + + return IRQ_HANDLED; +} + +static ssize_t vcnl4000_read_near_level(struct iio_dev *indio_dev, + uintptr_t priv, + const struct iio_chan_spec *chan, + char *buf) +{ + struct vcnl4000_data *data = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", data->near_level); +} + +static irqreturn_t vcnl4010_irq_thread(int irq, void *p) +{ + struct iio_dev *indio_dev = p; + struct vcnl4000_data *data = iio_priv(indio_dev); + unsigned long isr; + int ret; + + ret = i2c_smbus_read_byte_data(data->client, VCNL4010_ISR); + if (ret < 0) + goto end; + + isr = ret; + + if (isr & VCNL4010_INT_THR) { + if (test_bit(VCNL4010_INT_THR_LOW, &isr)) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE( + IIO_PROXIMITY, + 1, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns(indio_dev)); + } + + if (test_bit(VCNL4010_INT_THR_HIGH, &isr)) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE( + IIO_PROXIMITY, + 1, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + } + + i2c_smbus_write_byte_data(data->client, VCNL4010_ISR, + isr & VCNL4010_INT_THR); + } + + if (isr & VCNL4010_INT_DRDY && iio_buffer_enabled(indio_dev)) + iio_trigger_poll_nested(indio_dev->trig); + +end: + return IRQ_HANDLED; +} + +static irqreturn_t vcnl4010_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct vcnl4000_data *data = iio_priv(indio_dev); + const unsigned long *active_scan_mask = indio_dev->active_scan_mask; + struct { + u16 chan; + aligned_s64 ts; + } scan = { }; + bool data_read = false; + unsigned long isr; + int val = 0; + int ret; + + ret = i2c_smbus_read_byte_data(data->client, VCNL4010_ISR); + if (ret < 0) + goto end; + + isr = ret; + + if (test_bit(0, active_scan_mask)) { + if (test_bit(VCNL4010_INT_PROXIMITY, &isr)) { + ret = vcnl4000_read_data(data, + VCNL4000_PS_RESULT_HI, + &val); + if (ret < 0) + goto end; + + scan.chan = val; + data_read = true; + } + } + + ret = i2c_smbus_write_byte_data(data->client, VCNL4010_ISR, + isr & VCNL4010_INT_DRDY); + if (ret < 0) + goto end; + + if (!data_read) + goto end; + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); + +end: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int vcnl4010_buffer_postenable(struct iio_dev *indio_dev) +{ + struct vcnl4000_data *data = iio_priv(indio_dev); + int ret; + int cmd; + + /* Do not enable the buffer if we are already capturing events. */ + if (vcnl4010_is_in_periodic_mode(data)) + return -EBUSY; + + ret = i2c_smbus_write_byte_data(data->client, VCNL4010_INT_CTRL, + VCNL4010_INT_PROX_EN); + if (ret < 0) + return ret; + + cmd = VCNL4000_SELF_TIMED_EN | VCNL4000_PROX_EN; + return i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND, cmd); +} + +static int vcnl4010_buffer_predisable(struct iio_dev *indio_dev) +{ + struct vcnl4000_data *data = iio_priv(indio_dev); + int ret; + + ret = i2c_smbus_write_byte_data(data->client, VCNL4010_INT_CTRL, 0); + if (ret < 0) + return ret; + + return i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND, 0); +} + +static const struct iio_buffer_setup_ops vcnl4010_buffer_ops = { + .postenable = &vcnl4010_buffer_postenable, + .predisable = &vcnl4010_buffer_predisable, +}; + +static const struct iio_chan_spec_ext_info vcnl4000_ext_info[] = { + { + .name = "nearlevel", + .shared = IIO_SEPARATE, + .read = vcnl4000_read_near_level, + }, + { } +}; + +static const struct iio_event_spec vcnl4000_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + } +}; + +static const struct iio_event_spec vcnl4040_als_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | BIT(IIO_EV_INFO_PERIOD), + }, +}; + +static const struct iio_event_spec vcnl4040_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_PERIOD), + }, +}; + +static const struct iio_chan_spec vcnl4000_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .ext_info = vcnl4000_ext_info, + } +}; + +static const struct iio_chan_spec vcnl4010_channels[] = { + { + .type = IIO_LIGHT, + .scan_index = -1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, { + .type = IIO_PROXIMITY, + .scan_index = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .event_spec = vcnl4000_event_spec, + .num_event_specs = ARRAY_SIZE(vcnl4000_event_spec), + .ext_info = vcnl4000_ext_info, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct iio_chan_spec vcnl4040_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME), + .event_spec = vcnl4040_als_event_spec, + .num_event_specs = ARRAY_SIZE(vcnl4040_als_event_spec), + }, { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .ext_info = vcnl4000_ext_info, + .event_spec = vcnl4040_event_spec, + .num_event_specs = ARRAY_SIZE(vcnl4040_event_spec), + } +}; + +static const struct iio_chan_spec cm36672p_channels[] = { + { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .ext_info = vcnl4000_ext_info, + .event_spec = vcnl4040_event_spec, + .num_event_specs = ARRAY_SIZE(vcnl4040_event_spec), + }, +}; + +static const struct iio_info vcnl4000_info = { + .read_raw = vcnl4000_read_raw, +}; + +static const struct iio_info vcnl4010_info = { + .read_raw = vcnl4010_read_raw, + .read_avail = vcnl4010_read_avail, + .write_raw = vcnl4010_write_raw, + .read_event_value = vcnl4010_read_event, + .write_event_value = vcnl4010_write_event, + .read_event_config = vcnl4010_read_event_config, + .write_event_config = vcnl4010_write_event_config, +}; + +static const struct iio_info vcnl4040_info = { + .read_raw = vcnl4000_read_raw, + .write_raw = vcnl4040_write_raw, + .read_event_value = vcnl4040_read_event, + .write_event_value = vcnl4040_write_event, + .read_event_config = vcnl4040_read_event_config, + .write_event_config = vcnl4040_write_event_config, + .read_avail = vcnl4040_read_avail, +}; + +static const struct vcnl4000_chip_spec cm36672p_spec = { + .prod = "CM36672P", + .init = vcnl4200_init, + .measure_proximity = vcnl4200_measure_proximity, + .set_power_state = vcnl4200_set_power_state, + .channels = cm36672p_channels, + .num_channels = ARRAY_SIZE(cm36672p_channels), + .info = &vcnl4040_info, + .irq_thread = vcnl4040_irq_thread, + .int_reg = VCNL4040_INT_FLAGS, + .ps_it_times = &vcnl4040_ps_it_times, + .num_ps_it_times = ARRAY_SIZE(vcnl4040_ps_it_times), + .prod_id = VCNL4040_PROD_ID, +}; + +static const struct vcnl4000_chip_spec vcnl4000_spec = { + .prod = "VCNL4000", + .init = vcnl4000_init, + .measure_light = vcnl4000_measure_light, + .measure_proximity = vcnl4000_measure_proximity, + .set_power_state = vcnl4000_set_power_state, + .channels = vcnl4000_channels, + .num_channels = ARRAY_SIZE(vcnl4000_channels), + .info = &vcnl4000_info, + .prod_id = VCNL4000_PROD_ID, +}; + +static const struct vcnl4000_chip_spec vcnl4010_spec = { + .prod = "VCNL4010/4020", + .init = vcnl4000_init, + .measure_light = vcnl4000_measure_light, + .measure_proximity = vcnl4000_measure_proximity, + .set_power_state = vcnl4000_set_power_state, + .channels = vcnl4010_channels, + .num_channels = ARRAY_SIZE(vcnl4010_channels), + .info = &vcnl4010_info, + .irq_thread = vcnl4010_irq_thread, + .trig_buffer_func = vcnl4010_trigger_handler, + .buffer_setup_ops = &vcnl4010_buffer_ops, + .prod_id = VCNL4010_PROD_ID, +}; + +static const struct vcnl4000_chip_spec vcnl4040_spec = { + .prod = "VCNL4040", + .init = vcnl4200_init, + .measure_light = vcnl4200_measure_light, + .measure_proximity = vcnl4200_measure_proximity, + .set_power_state = vcnl4200_set_power_state, + .channels = vcnl4040_channels, + .num_channels = ARRAY_SIZE(vcnl4040_channels), + .info = &vcnl4040_info, + .irq_thread = vcnl4040_irq_thread, + .int_reg = VCNL4040_INT_FLAGS, + .ps_it_times = &vcnl4040_ps_it_times, + .num_ps_it_times = ARRAY_SIZE(vcnl4040_ps_it_times), + .als_it_times = &vcnl4040_als_it_times, + .num_als_it_times = ARRAY_SIZE(vcnl4040_als_it_times), + .ulux_step = 100000, + .prod_id = VCNL4040_PROD_ID, +}; + +static const struct vcnl4000_chip_spec vcnl4200_spec = { + .prod = "VCNL4200", + .init = vcnl4200_init, + .measure_light = vcnl4200_measure_light, + .measure_proximity = vcnl4200_measure_proximity, + .set_power_state = vcnl4200_set_power_state, + .channels = vcnl4040_channels, + .num_channels = ARRAY_SIZE(vcnl4000_channels), + .info = &vcnl4040_info, + .irq_thread = vcnl4040_irq_thread, + .int_reg = VCNL4200_INT_FLAGS, + .ps_it_times = &vcnl4200_ps_it_times, + .num_ps_it_times = ARRAY_SIZE(vcnl4200_ps_it_times), + .als_it_times = &vcnl4200_als_it_times, + .num_als_it_times = ARRAY_SIZE(vcnl4200_als_it_times), + .ulux_step = 24000, + .prod_id = VCNL4200_PROD_ID, +}; + +static const struct iio_trigger_ops vcnl4010_trigger_ops = { + .validate_device = iio_trigger_validate_own_device, +}; + +static int vcnl4010_probe_trigger(struct iio_dev *indio_dev) +{ + struct vcnl4000_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + struct iio_trigger *trigger; + + trigger = devm_iio_trigger_alloc(&client->dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!trigger) + return -ENOMEM; + + trigger->ops = &vcnl4010_trigger_ops; + iio_trigger_set_drvdata(trigger, indio_dev); + + return devm_iio_trigger_register(&client->dev, trigger); +} + +static void vcnl4000_cleanup(void *data) +{ + struct iio_dev *indio_dev = data; + struct vcnl4000_data *chip = iio_priv(indio_dev); + struct device *dev = &chip->client->dev; + int ret; + + ret = chip->chip_spec->set_power_state(chip, false); + if (ret) + dev_warn(dev, "Failed to power down (%pe)", ERR_PTR(ret)); +} + +static int vcnl4000_probe(struct i2c_client *client) +{ + static const char * const regulator_names[] = { "vdd", "vio", "vled" }; + struct device *dev = &client->dev; + struct vcnl4000_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->chip_spec = i2c_get_match_data(client); + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulator_names), + regulator_names); + if (ret) + return ret; + + ret = devm_mutex_init(dev, &data->vcnl4000_lock); + if (ret) + return ret; + + ret = data->chip_spec->init(data); + if (ret < 0) + return ret; + + ret = data->chip_spec->set_power_state(data, true); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, vcnl4000_cleanup, indio_dev); + if (ret) + return ret; + + dev_dbg(dev, "%s Ambient light/proximity sensor, Rev: %02x\n", + data->chip_spec->prod, data->rev); + + device_property_read_u32(dev, "proximity-near-level", &data->near_level); + + indio_dev->info = data->chip_spec->info; + indio_dev->channels = data->chip_spec->channels; + indio_dev->num_channels = data->chip_spec->num_channels; + indio_dev->name = VCNL4000_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (data->chip_spec->trig_buffer_func && + data->chip_spec->buffer_setup_ops) { + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + data->chip_spec->trig_buffer_func, + data->chip_spec->buffer_setup_ops); + if (ret < 0) + return ret; + } + + if (client->irq && data->chip_spec->irq_thread) { + ret = devm_request_threaded_irq(dev, client->irq, NULL, + data->chip_spec->irq_thread, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "vcnl4000_irq", + indio_dev); + if (ret < 0) + return ret; + + ret = vcnl4010_probe_trigger(indio_dev); + if (ret < 0) + return ret; + } + + ret = devm_pm_runtime_set_active_enabled(dev); + if (ret) + return ret; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ret; + + pm_runtime_set_autosuspend_delay(dev, VCNL4000_SLEEP_DELAY_MS); + pm_runtime_use_autosuspend(dev); + + return 0; +} + +static const struct of_device_id vcnl_4000_of_match[] = { + { .compatible = "capella,cm36672p", .data = &cm36672p_spec }, + /* Capella CM36686 is fully compatible with Vishay VCNL4040 */ + { .compatible = "capella,cm36686", .data = &vcnl4040_spec }, + { .compatible = "vishay,vcnl4000", .data = &vcnl4000_spec }, + { .compatible = "vishay,vcnl4010", .data = &vcnl4010_spec }, + { .compatible = "vishay,vcnl4020", .data = &vcnl4010_spec }, + { .compatible = "vishay,vcnl4040", .data = &vcnl4040_spec }, + { .compatible = "vishay,vcnl4200", .data = &vcnl4200_spec }, + { } +}; +MODULE_DEVICE_TABLE(of, vcnl_4000_of_match); + +static int vcnl4000_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct vcnl4000_data *data = iio_priv(indio_dev); + + return data->chip_spec->set_power_state(data, false); +} + +static int vcnl4000_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct vcnl4000_data *data = iio_priv(indio_dev); + + return data->chip_spec->set_power_state(data, true); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(vcnl4000_pm_ops, vcnl4000_runtime_suspend, + vcnl4000_runtime_resume, NULL); + +static const struct i2c_device_id vcnl4000_id[] = { + { .name = "cm36672p", .driver_data = (kernel_ulong_t)&cm36672p_spec }, + { .name = "cm36686", .driver_data = (kernel_ulong_t)&vcnl4040_spec }, + { .name = "vcnl4000", .driver_data = (kernel_ulong_t)&vcnl4000_spec }, + { .name = "vcnl4010", .driver_data = (kernel_ulong_t)&vcnl4010_spec }, + { .name = "vcnl4020", .driver_data = (kernel_ulong_t)&vcnl4010_spec }, + { .name = "vcnl4040", .driver_data = (kernel_ulong_t)&vcnl4040_spec }, + { .name = "vcnl4200", .driver_data = (kernel_ulong_t)&vcnl4200_spec }, + { } +}; +MODULE_DEVICE_TABLE(i2c, vcnl4000_id); + +static struct i2c_driver vcnl4000_driver = { + .driver = { + .name = VCNL4000_DRV_NAME, + .pm = pm_ptr(&vcnl4000_pm_ops), + .of_match_table = vcnl_4000_of_match, + }, + .probe = vcnl4000_probe, + .id_table = vcnl4000_id, +}; + +module_i2c_driver(vcnl4000_driver); + +MODULE_AUTHOR("Peter Meerwald "); +MODULE_AUTHOR("Mathieu Othacehe "); +MODULE_DESCRIPTION("Vishay VCNL4000 proximity/ambient light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/vcnl4035.c b/drivers/iio/light/vcnl4035.c new file mode 100644 index 000000000..ce1bd42b0 --- /dev/null +++ b/drivers/iio/light/vcnl4035.c @@ -0,0 +1,684 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * VCNL4035 Ambient Light and Proximity Sensor - 7-bit I2C slave address 0x60 + * + * Copyright (c) 2018, DENX Software Engineering GmbH + * Author: Parthiban Nallathambi + * + * TODO: Proximity + */ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define VCNL4035_DRV_NAME "vcnl4035" + +/* Device registers */ +#define VCNL4035_ALS_CONF 0x00 +#define VCNL4035_ALS_THDH 0x01 +#define VCNL4035_ALS_THDL 0x02 +#define VCNL4035_ALS_DATA 0x0B +#define VCNL4035_WHITE_DATA 0x0C +#define VCNL4035_INT_FLAG 0x0D +#define VCNL4035_DEV_ID 0x0E + +/* Register masks */ +#define VCNL4035_MODE_ALS_MASK BIT(0) +#define VCNL4035_MODE_ALS_WHITE_CHAN BIT(8) +#define VCNL4035_MODE_ALS_INT_MASK BIT(1) +#define VCNL4035_ALS_IT_MASK GENMASK(7, 5) +#define VCNL4035_ALS_PERS_MASK GENMASK(3, 2) +#define VCNL4035_INT_ALS_IF_H_MASK BIT(12) +#define VCNL4035_INT_ALS_IF_L_MASK BIT(13) +#define VCNL4035_DEV_ID_MASK GENMASK(7, 0) + +/* Default values */ +#define VCNL4035_MODE_ALS_ENABLE BIT(0) +#define VCNL4035_MODE_ALS_DISABLE 0x00 +#define VCNL4035_MODE_ALS_INT_ENABLE BIT(1) +#define VCNL4035_MODE_ALS_INT_DISABLE 0 +#define VCNL4035_DEV_ID_VAL 0x80 +#define VCNL4035_ALS_IT_DEFAULT 0x01 +#define VCNL4035_ALS_PERS_DEFAULT 0x00 +#define VCNL4035_ALS_THDH_DEFAULT 5000 +#define VCNL4035_ALS_THDL_DEFAULT 100 +#define VCNL4035_SLEEP_DELAY_MS 2000 + +struct vcnl4035_data { + struct i2c_client *client; + struct regmap *regmap; + unsigned int als_it_val; + unsigned int als_persistence; + unsigned int als_thresh_low; + unsigned int als_thresh_high; + struct iio_trigger *drdy_trigger0; +}; + +static inline bool vcnl4035_is_triggered(struct vcnl4035_data *data) +{ + int ret; + int reg; + + ret = regmap_read(data->regmap, VCNL4035_INT_FLAG, ®); + if (ret < 0) + return false; + + return !!(reg & + (VCNL4035_INT_ALS_IF_H_MASK | VCNL4035_INT_ALS_IF_L_MASK)); +} + +static irqreturn_t vcnl4035_drdy_irq_thread(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct vcnl4035_data *data = iio_priv(indio_dev); + + if (vcnl4035_is_triggered(data)) { + iio_push_event(indio_dev, IIO_UNMOD_EVENT_CODE(IIO_LIGHT, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + iio_trigger_poll_nested(data->drdy_trigger0); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +/* Triggered buffer */ +static irqreturn_t vcnl4035_trigger_consumer_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct vcnl4035_data *data = iio_priv(indio_dev); + /* Ensure naturally aligned timestamp */ + struct { + u16 als_data; + aligned_s64 timestamp; + } buffer = { }; + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, VCNL4035_ALS_DATA, &val); + if (ret < 0) { + dev_err(&data->client->dev, + "Trigger consumer can't read from sensor.\n"); + goto fail_read; + } + + buffer.als_data = val; + iio_push_to_buffers_with_timestamp(indio_dev, &buffer, + iio_get_time_ns(indio_dev)); + +fail_read: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int vcnl4035_als_drdy_set_state(struct iio_trigger *trigger, + bool enable_drdy) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trigger); + struct vcnl4035_data *data = iio_priv(indio_dev); + int val = enable_drdy ? VCNL4035_MODE_ALS_INT_ENABLE : + VCNL4035_MODE_ALS_INT_DISABLE; + + return regmap_update_bits(data->regmap, VCNL4035_ALS_CONF, + VCNL4035_MODE_ALS_INT_MASK, + val); +} + +static const struct iio_trigger_ops vcnl4035_trigger_ops = { + .validate_device = iio_trigger_validate_own_device, + .set_trigger_state = vcnl4035_als_drdy_set_state, +}; + +static int vcnl4035_read_info_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val) +{ + struct vcnl4035_data *data = iio_priv(indio_dev); + int ret; + int raw_data; + unsigned int reg; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + if (chan->channel) + reg = VCNL4035_ALS_DATA; + else + reg = VCNL4035_WHITE_DATA; + ret = regmap_read(data->regmap, reg, &raw_data); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + + *val = raw_data; + + return IIO_VAL_INT; +} + +/* + * Device IT INT Time (ms) Scale (lux/step) + * 000 50 0.064 + * 001 100 0.032 + * 010 200 0.016 + * 100 400 0.008 + * 101 - 111 800 0.004 + * Values are proportional, so ALS INT is selected for input due to + * simplicity reason. Integration time value and scaling is + * calculated based on device INT value + * + * Raw value needs to be scaled using ALS steps + */ +static int vcnl4035_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct vcnl4035_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = pm_runtime_resume_and_get(&data->client->dev); + if (ret < 0) + return ret; + ret = vcnl4035_read_info_raw(indio_dev, chan, val); + pm_runtime_put_autosuspend(&data->client->dev); + return ret; + case IIO_CHAN_INFO_INT_TIME: + *val = 50; + if (data->als_it_val) + *val = data->als_it_val * 100; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 64; + if (!data->als_it_val) + *val2 = 1000; + else + *val2 = data->als_it_val * 2 * 1000; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } +} + +static int vcnl4035_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret; + struct vcnl4035_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val <= 0 || val > 800) + return -EINVAL; + + ret = pm_runtime_resume_and_get(&data->client->dev); + if (ret < 0) + return ret; + + ret = regmap_update_bits(data->regmap, VCNL4035_ALS_CONF, + VCNL4035_ALS_IT_MASK, + val / 100); + if (!ret) + data->als_it_val = val / 100; + + pm_runtime_put_autosuspend(&data->client->dev); + return ret; + default: + return -EINVAL; + } +} + +/* No direct ABI for persistence and threshold, so eventing */ +static int vcnl4035_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, + int *val, int *val2) +{ + struct vcnl4035_data *data = iio_priv(indio_dev); + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + *val = data->als_thresh_high; + return IIO_VAL_INT; + case IIO_EV_DIR_FALLING: + *val = data->als_thresh_low; + return IIO_VAL_INT; + default: + return -EINVAL; + } + break; + case IIO_EV_INFO_PERIOD: + *val = data->als_persistence; + return IIO_VAL_INT; + default: + return -EINVAL; + } + +} + +static int vcnl4035_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, int val, + int val2) +{ + struct vcnl4035_data *data = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + /* 16 bit threshold range 0 - 65535 */ + if (val < 0 || val > 65535) + return -EINVAL; + if (dir == IIO_EV_DIR_RISING) { + if (val < data->als_thresh_low) + return -EINVAL; + ret = regmap_write(data->regmap, VCNL4035_ALS_THDH, + val); + if (ret) + return ret; + data->als_thresh_high = val; + } else { + if (val > data->als_thresh_high) + return -EINVAL; + ret = regmap_write(data->regmap, VCNL4035_ALS_THDL, + val); + if (ret) + return ret; + data->als_thresh_low = val; + } + return ret; + case IIO_EV_INFO_PERIOD: + /* allow only 1 2 4 8 as persistence value */ + if (val < 0 || val > 8 || hweight8(val) != 1) + return -EINVAL; + ret = regmap_update_bits(data->regmap, VCNL4035_ALS_CONF, + VCNL4035_ALS_PERS_MASK, val); + if (!ret) + data->als_persistence = val; + return ret; + default: + return -EINVAL; + } +} + +static IIO_CONST_ATTR_INT_TIME_AVAIL("50 100 200 400 800"); + +static struct attribute *vcnl4035_attributes[] = { + &iio_const_attr_integration_time_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group vcnl4035_attribute_group = { + .attrs = vcnl4035_attributes, +}; + +static const struct iio_info vcnl4035_info = { + .read_raw = vcnl4035_read_raw, + .write_raw = vcnl4035_write_raw, + .read_event_value = vcnl4035_read_thresh, + .write_event_value = vcnl4035_write_thresh, + .attrs = &vcnl4035_attribute_group, +}; + +static const struct iio_event_spec vcnl4035_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_PERIOD), + }, +}; + +enum vcnl4035_scan_index_order { + VCNL4035_CHAN_INDEX_LIGHT, + VCNL4035_CHAN_INDEX_WHITE_LED, +}; + +static const struct iio_buffer_setup_ops iio_triggered_buffer_setup_ops = { + .validate_scan_mask = &iio_validate_scan_mask_onehot, +}; + +static const struct iio_chan_spec vcnl4035_channels[] = { + { + .type = IIO_LIGHT, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .event_spec = vcnl4035_event_spec, + .num_event_specs = ARRAY_SIZE(vcnl4035_event_spec), + .scan_index = VCNL4035_CHAN_INDEX_LIGHT, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_INTENSITY, + .channel = 1, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BOTH, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_index = VCNL4035_CHAN_INDEX_WHITE_LED, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, +}; + +static int vcnl4035_set_als_power_state(struct vcnl4035_data *data, u8 status) +{ + return regmap_update_bits(data->regmap, VCNL4035_ALS_CONF, + VCNL4035_MODE_ALS_MASK, + status); +} + +static int vcnl4035_init(struct vcnl4035_data *data) +{ + int ret; + int id; + + ret = regmap_read(data->regmap, VCNL4035_DEV_ID, &id); + if (ret < 0) { + dev_err(&data->client->dev, "Failed to read DEV_ID register\n"); + return ret; + } + + id = FIELD_GET(VCNL4035_DEV_ID_MASK, id); + if (id != VCNL4035_DEV_ID_VAL) { + dev_err(&data->client->dev, "Wrong id, got %x, expected %x\n", + id, VCNL4035_DEV_ID_VAL); + return -ENODEV; + } + + ret = vcnl4035_set_als_power_state(data, VCNL4035_MODE_ALS_ENABLE); + if (ret < 0) + return ret; + + /* ALS white channel enable */ + ret = regmap_update_bits(data->regmap, VCNL4035_ALS_CONF, + VCNL4035_MODE_ALS_WHITE_CHAN, + 1); + if (ret) { + dev_err(&data->client->dev, "set white channel enable %d\n", + ret); + return ret; + } + + /* set default integration time - 100 ms for ALS */ + ret = regmap_update_bits(data->regmap, VCNL4035_ALS_CONF, + VCNL4035_ALS_IT_MASK, + VCNL4035_ALS_IT_DEFAULT); + if (ret) { + dev_err(&data->client->dev, "set default ALS IT returned %d\n", + ret); + return ret; + } + data->als_it_val = VCNL4035_ALS_IT_DEFAULT; + + /* set default persistence time - 1 for ALS */ + ret = regmap_update_bits(data->regmap, VCNL4035_ALS_CONF, + VCNL4035_ALS_PERS_MASK, + VCNL4035_ALS_PERS_DEFAULT); + if (ret) { + dev_err(&data->client->dev, "set default PERS returned %d\n", + ret); + return ret; + } + data->als_persistence = VCNL4035_ALS_PERS_DEFAULT; + + /* set default HIGH threshold for ALS */ + ret = regmap_write(data->regmap, VCNL4035_ALS_THDH, + VCNL4035_ALS_THDH_DEFAULT); + if (ret) { + dev_err(&data->client->dev, "set default THDH returned %d\n", + ret); + return ret; + } + data->als_thresh_high = VCNL4035_ALS_THDH_DEFAULT; + + /* set default LOW threshold for ALS */ + ret = regmap_write(data->regmap, VCNL4035_ALS_THDL, + VCNL4035_ALS_THDL_DEFAULT); + if (ret) { + dev_err(&data->client->dev, "set default THDL returned %d\n", + ret); + return ret; + } + data->als_thresh_low = VCNL4035_ALS_THDL_DEFAULT; + + return 0; +} + +static bool vcnl4035_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case VCNL4035_ALS_CONF: + case VCNL4035_DEV_ID: + return false; + default: + return true; + } +} + +static const struct regmap_config vcnl4035_regmap_config = { + .name = "vcnl4035_regmap", + .reg_bits = 8, + .val_bits = 16, + .max_register = VCNL4035_DEV_ID, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = vcnl4035_is_volatile_reg, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + +static int vcnl4035_probe_trigger(struct iio_dev *indio_dev) +{ + int ret; + struct vcnl4035_data *data = iio_priv(indio_dev); + + data->drdy_trigger0 = devm_iio_trigger_alloc( + indio_dev->dev.parent, + "%s-dev%d", indio_dev->name, iio_device_id(indio_dev)); + if (!data->drdy_trigger0) + return -ENOMEM; + + data->drdy_trigger0->ops = &vcnl4035_trigger_ops; + iio_trigger_set_drvdata(data->drdy_trigger0, indio_dev); + ret = devm_iio_trigger_register(indio_dev->dev.parent, + data->drdy_trigger0); + if (ret) { + dev_err(&data->client->dev, "iio trigger register failed\n"); + return ret; + } + + /* Trigger setup */ + ret = devm_iio_triggered_buffer_setup(indio_dev->dev.parent, indio_dev, + NULL, vcnl4035_trigger_consumer_handler, + &iio_triggered_buffer_setup_ops); + if (ret < 0) { + dev_err(&data->client->dev, "iio triggered buffer setup failed\n"); + return ret; + } + + /* IRQ to trigger mapping */ + ret = devm_request_threaded_irq(&data->client->dev, data->client->irq, + NULL, vcnl4035_drdy_irq_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "vcnl4035_event", indio_dev); + if (ret < 0) + dev_err(&data->client->dev, "request irq %d for trigger0 failed\n", + data->client->irq); + return ret; +} + +static int vcnl4035_probe(struct i2c_client *client) +{ + struct vcnl4035_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, &vcnl4035_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "regmap_init failed!\n"); + return PTR_ERR(regmap); + } + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->regmap = regmap; + + indio_dev->info = &vcnl4035_info; + indio_dev->name = VCNL4035_DRV_NAME; + indio_dev->channels = vcnl4035_channels; + indio_dev->num_channels = ARRAY_SIZE(vcnl4035_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = vcnl4035_init(data); + if (ret < 0) { + dev_err(&client->dev, "vcnl4035 chip init failed\n"); + return ret; + } + + if (client->irq > 0) { + ret = vcnl4035_probe_trigger(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "vcnl4035 unable init trigger\n"); + goto fail_poweroff; + } + } + + ret = pm_runtime_set_active(&client->dev); + if (ret < 0) + goto fail_poweroff; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto fail_poweroff; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, VCNL4035_SLEEP_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + + return 0; + +fail_poweroff: + vcnl4035_set_als_power_state(data, VCNL4035_MODE_ALS_DISABLE); + return ret; +} + +static void vcnl4035_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + int ret; + + pm_runtime_dont_use_autosuspend(&client->dev); + pm_runtime_disable(&client->dev); + iio_device_unregister(indio_dev); + pm_runtime_set_suspended(&client->dev); + + ret = vcnl4035_set_als_power_state(iio_priv(indio_dev), + VCNL4035_MODE_ALS_DISABLE); + if (ret) + dev_warn(&client->dev, "Failed to put device into standby (%pe)\n", + ERR_PTR(ret)); +} + +static int vcnl4035_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct vcnl4035_data *data = iio_priv(indio_dev); + int ret; + + ret = vcnl4035_set_als_power_state(data, VCNL4035_MODE_ALS_DISABLE); + regcache_mark_dirty(data->regmap); + + return ret; +} + +static int vcnl4035_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct vcnl4035_data *data = iio_priv(indio_dev); + int ret; + + ret = regcache_sync(data->regmap); + if (ret < 0) + return ret; + + ret = vcnl4035_set_als_power_state(data, VCNL4035_MODE_ALS_ENABLE); + if (ret < 0) + return ret; + + /* wait for 1 ALS integration cycle */ + msleep(data->als_it_val * 100); + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(vcnl4035_pm_ops, vcnl4035_runtime_suspend, + vcnl4035_runtime_resume, NULL); + +static const struct i2c_device_id vcnl4035_id[] = { + { .name = "vcnl4035" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, vcnl4035_id); + +static const struct of_device_id vcnl4035_of_match[] = { + { .compatible = "vishay,vcnl4035", }, + { } +}; +MODULE_DEVICE_TABLE(of, vcnl4035_of_match); + +static struct i2c_driver vcnl4035_driver = { + .driver = { + .name = VCNL4035_DRV_NAME, + .pm = pm_ptr(&vcnl4035_pm_ops), + .of_match_table = vcnl4035_of_match, + }, + .probe = vcnl4035_probe, + .remove = vcnl4035_remove, + .id_table = vcnl4035_id, +}; + +module_i2c_driver(vcnl4035_driver); + +MODULE_AUTHOR("Parthiban Nallathambi "); +MODULE_DESCRIPTION("VCNL4035 Ambient Light Sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/veml3235.c b/drivers/iio/light/veml3235.c new file mode 100644 index 000000000..d8a392230 --- /dev/null +++ b/drivers/iio/light/veml3235.c @@ -0,0 +1,547 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * VEML3235 Ambient Light Sensor + * + * Copyright (c) 2024, Javier Carrasco + * + * Datasheet: https://www.vishay.com/docs/80131/veml3235.pdf + * Appnote-80222: https://www.vishay.com/docs/80222/designingveml3235.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define VEML3235_REG_CONF 0x00 +#define VEML3235_REG_WH_DATA 0x04 +#define VEML3235_REG_ALS_DATA 0x05 +#define VEML3235_REG_ID 0x09 + +#define VEML3235_CONF_SD BIT(0) +#define VEML3235_CONF_SD0 BIT(15) + +struct veml3235_rf { + struct regmap_field *it; + struct regmap_field *gain; + struct regmap_field *id; +}; + +struct veml3235_data { + struct i2c_client *client; + struct device *dev; + struct regmap *regmap; + struct veml3235_rf rf; + struct iio_gts gts; +}; + +static const struct iio_itime_sel_mul veml3235_it_sel[] = { + GAIN_SCALE_ITIME_US(50000, 0, 1), + GAIN_SCALE_ITIME_US(100000, 1, 2), + GAIN_SCALE_ITIME_US(200000, 2, 4), + GAIN_SCALE_ITIME_US(400000, 3, 8), + GAIN_SCALE_ITIME_US(800000, 4, 16), +}; + +/* + * The MSB (DG) doubles the value of the rest of the field, which leads to + * two possible combinations to obtain gain = 2 and gain = 4. The gain + * handling can be simplified by restricting DG = 1 to the only gain that + * really requires it, gain = 8. Note that "X10" is a reserved value. + */ +#define VEML3235_SEL_GAIN_X1 0 +#define VEML3235_SEL_GAIN_X2 1 +#define VEML3235_SEL_GAIN_X4 3 +#define VEML3235_SEL_GAIN_X8 7 +static const struct iio_gain_sel_pair veml3235_gain_sel[] = { + GAIN_SCALE_GAIN(1, VEML3235_SEL_GAIN_X1), + GAIN_SCALE_GAIN(2, VEML3235_SEL_GAIN_X2), + GAIN_SCALE_GAIN(4, VEML3235_SEL_GAIN_X4), + GAIN_SCALE_GAIN(8, VEML3235_SEL_GAIN_X8), +}; + +static int veml3235_power_on(struct veml3235_data *data) +{ + int ret; + + ret = regmap_clear_bits(data->regmap, VEML3235_REG_CONF, + VEML3235_CONF_SD | VEML3235_CONF_SD0); + if (ret) + return ret; + + /* Wait 4 ms to let processor & oscillator start correctly */ + fsleep(4000); + + return 0; +} + +static int veml3235_shut_down(struct veml3235_data *data) +{ + return regmap_set_bits(data->regmap, VEML3235_REG_CONF, + VEML3235_CONF_SD | VEML3235_CONF_SD0); +} + +static void veml3235_shut_down_action(void *data) +{ + veml3235_shut_down(data); +} + +enum veml3235_chan { + CH_ALS, + CH_WHITE, +}; + +static const struct iio_chan_spec veml3235_channels[] = { + { + .type = IIO_LIGHT, + .channel = CH_ALS, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_INTENSITY, + .channel = CH_WHITE, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BOTH, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static const struct regmap_range veml3235_readable_ranges[] = { + regmap_reg_range(VEML3235_REG_CONF, VEML3235_REG_ID), +}; + +static const struct regmap_access_table veml3235_readable_table = { + .yes_ranges = veml3235_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(veml3235_readable_ranges), +}; + +static const struct regmap_range veml3235_writable_ranges[] = { + regmap_reg_range(VEML3235_REG_CONF, VEML3235_REG_CONF), +}; + +static const struct regmap_access_table veml3235_writable_table = { + .yes_ranges = veml3235_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(veml3235_writable_ranges), +}; + +static const struct regmap_range veml3235_volatile_ranges[] = { + regmap_reg_range(VEML3235_REG_WH_DATA, VEML3235_REG_ALS_DATA), +}; + +static const struct regmap_access_table veml3235_volatile_table = { + .yes_ranges = veml3235_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(veml3235_volatile_ranges), +}; + +static const struct regmap_config veml3235_regmap_config = { + .name = "veml3235_regmap", + .reg_bits = 8, + .val_bits = 16, + .max_register = VEML3235_REG_ID, + .val_format_endian = REGMAP_ENDIAN_LITTLE, + .rd_table = &veml3235_readable_table, + .wr_table = &veml3235_writable_table, + .volatile_table = &veml3235_volatile_table, + .cache_type = REGCACHE_MAPLE, +}; + +static int veml3235_get_it(struct veml3235_data *data, int *val, int *val2) +{ + int ret, it_idx; + + ret = regmap_field_read(data->rf.it, &it_idx); + if (ret) + return ret; + + ret = iio_gts_find_int_time_by_sel(&data->gts, it_idx); + if (ret < 0) + return ret; + + *val2 = ret; + *val = 0; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int veml3235_set_it(struct iio_dev *indio_dev, int val, int val2) +{ + struct veml3235_data *data = iio_priv(indio_dev); + int ret, gain_idx, it_idx, new_gain, prev_gain, prev_it; + bool in_range; + + if (val || !iio_gts_valid_time(&data->gts, val2)) + return -EINVAL; + + ret = regmap_field_read(data->rf.it, &it_idx); + if (ret) + return ret; + + ret = regmap_field_read(data->rf.gain, &gain_idx); + if (ret) + return ret; + + prev_it = iio_gts_find_int_time_by_sel(&data->gts, it_idx); + if (prev_it < 0) + return prev_it; + + if (prev_it == val2) + return 0; + + prev_gain = iio_gts_find_gain_by_sel(&data->gts, gain_idx); + if (prev_gain < 0) + return prev_gain; + + ret = iio_gts_find_new_gain_by_gain_time_min(&data->gts, prev_gain, prev_it, + val2, &new_gain, &in_range); + if (ret) + return ret; + + if (!in_range) + dev_dbg(data->dev, "Optimal gain out of range\n"); + + ret = iio_gts_find_sel_by_int_time(&data->gts, val2); + if (ret < 0) + return ret; + + ret = regmap_field_write(data->rf.it, ret); + if (ret) + return ret; + + ret = iio_gts_find_sel_by_gain(&data->gts, new_gain); + if (ret < 0) + return ret; + + return regmap_field_write(data->rf.gain, ret); +} + +static int veml3235_set_scale(struct iio_dev *indio_dev, int val, int val2) +{ + struct veml3235_data *data = iio_priv(indio_dev); + int ret, it_idx, gain_sel, time_sel; + + ret = regmap_field_read(data->rf.it, &it_idx); + if (ret) + return ret; + + ret = iio_gts_find_gain_time_sel_for_scale(&data->gts, val, val2, + &gain_sel, &time_sel); + if (ret) + return ret; + + ret = regmap_field_write(data->rf.it, time_sel); + if (ret) + return ret; + + return regmap_field_write(data->rf.gain, gain_sel); +} + +static int veml3235_get_scale(struct veml3235_data *data, int *val, int *val2) +{ + int gain, it, reg, ret; + + ret = regmap_field_read(data->rf.gain, ®); + if (ret) { + dev_err(data->dev, "failed to read gain %d\n", ret); + return ret; + } + + gain = iio_gts_find_gain_by_sel(&data->gts, reg); + if (gain < 0) + return gain; + + ret = regmap_field_read(data->rf.it, ®); + if (ret) { + dev_err(data->dev, "failed to read integration time %d\n", ret); + return ret; + } + + it = iio_gts_find_int_time_by_sel(&data->gts, reg); + if (it < 0) + return it; + + ret = iio_gts_get_scale(&data->gts, gain, it, val, val2); + if (ret) + return ret; + + return IIO_VAL_INT_PLUS_NANO; +} + +static int veml3235_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct veml3235_data *data = iio_priv(indio_dev); + struct regmap *regmap = data->regmap; + int ret, reg; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_LIGHT: + ret = regmap_read(regmap, VEML3235_REG_ALS_DATA, ®); + if (ret < 0) + return ret; + + *val = reg; + return IIO_VAL_INT; + case IIO_INTENSITY: + ret = regmap_read(regmap, VEML3235_REG_WH_DATA, ®); + if (ret < 0) + return ret; + + *val = reg; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + return veml3235_get_it(data, val, val2); + case IIO_CHAN_INFO_SCALE: + return veml3235_get_scale(data, val, val2); + default: + return -EINVAL; + } +} + +static int veml3235_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct veml3235_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return iio_gts_avail_times(&data->gts, vals, type, length); + case IIO_CHAN_INFO_SCALE: + return iio_gts_all_avail_scales(&data->gts, vals, type, length); + default: + return -EINVAL; + } +} + +static int veml3235_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_INT_TIME: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int veml3235_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return veml3235_set_it(indio_dev, val, val2); + case IIO_CHAN_INFO_SCALE: + return veml3235_set_scale(indio_dev, val, val2); + } + + return -EINVAL; +} + +static void veml3235_read_id(struct veml3235_data *data) +{ + int ret, reg; + + ret = regmap_field_read(data->rf.id, ®); + if (ret) { + dev_info(data->dev, "failed to read ID\n"); + return; + } + + if (reg != 0x35) + dev_info(data->dev, "Unknown ID %d\n", reg); +} + +static const struct reg_field veml3235_rf_it = + REG_FIELD(VEML3235_REG_CONF, 4, 6); + +static const struct reg_field veml3235_rf_gain = + REG_FIELD(VEML3235_REG_CONF, 11, 13); + +static const struct reg_field veml3235_rf_id = + REG_FIELD(VEML3235_REG_ID, 0, 7); + +static int veml3235_regfield_init(struct veml3235_data *data) +{ + struct regmap *regmap = data->regmap; + struct device *dev = data->dev; + struct regmap_field *rm_field; + struct veml3235_rf *rf = &data->rf; + + rm_field = devm_regmap_field_alloc(dev, regmap, veml3235_rf_it); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->it = rm_field; + + rm_field = devm_regmap_field_alloc(dev, regmap, veml3235_rf_gain); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->gain = rm_field; + + rm_field = devm_regmap_field_alloc(dev, regmap, veml3235_rf_id); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->id = rm_field; + + return 0; +} + +static int veml3235_hw_init(struct iio_dev *indio_dev) +{ + struct veml3235_data *data = iio_priv(indio_dev); + struct device *dev = data->dev; + int ret; + + ret = devm_iio_init_iio_gts(data->dev, 0, 272640000, + veml3235_gain_sel, ARRAY_SIZE(veml3235_gain_sel), + veml3235_it_sel, ARRAY_SIZE(veml3235_it_sel), + &data->gts); + if (ret) + return dev_err_probe(data->dev, ret, "failed to init iio gts\n"); + + /* Set gain to 1 and integration time to 100 ms */ + ret = regmap_field_write(data->rf.gain, 0x00); + if (ret) + return dev_err_probe(data->dev, ret, "failed to set gain\n"); + + ret = regmap_field_write(data->rf.it, 0x01); + if (ret) + return dev_err_probe(data->dev, ret, + "failed to set integration time\n"); + + ret = veml3235_power_on(data); + if (ret) + return dev_err_probe(dev, ret, "failed to power on\n"); + + return devm_add_action_or_reset(dev, veml3235_shut_down_action, data); +} + +static const struct iio_info veml3235_info = { + .read_raw = veml3235_read_raw, + .read_avail = veml3235_read_avail, + .write_raw = veml3235_write_raw, + .write_raw_get_fmt = veml3235_write_raw_get_fmt, +}; + +static int veml3235_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct veml3235_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + int ret; + + regmap = devm_regmap_init_i2c(client, &veml3235_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "failed to setup regmap\n"); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->dev = dev; + data->regmap = regmap; + + ret = veml3235_regfield_init(data); + if (ret) + return dev_err_probe(dev, ret, "failed to init regfield\n"); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "failed to enable regulator\n"); + + indio_dev->name = "veml3235"; + indio_dev->channels = veml3235_channels; + indio_dev->num_channels = ARRAY_SIZE(veml3235_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &veml3235_info; + + veml3235_read_id(data); + + ret = veml3235_hw_init(indio_dev); + if (ret < 0) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static int veml3235_runtime_suspend(struct device *dev) +{ + struct veml3235_data *data = iio_priv(dev_get_drvdata(dev)); + int ret; + + ret = veml3235_shut_down(data); + if (ret < 0) + dev_err(data->dev, "failed to suspend: %d\n", ret); + + return ret; +} + +static int veml3235_runtime_resume(struct device *dev) +{ + struct veml3235_data *data = iio_priv(dev_get_drvdata(dev)); + int ret; + + ret = veml3235_power_on(data); + if (ret < 0) + dev_err(data->dev, "failed to resume: %d\n", ret); + + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(veml3235_pm_ops, veml3235_runtime_suspend, + veml3235_runtime_resume, NULL); + +static const struct of_device_id veml3235_of_match[] = { + { .compatible = "vishay,veml3235" }, + { } +}; +MODULE_DEVICE_TABLE(of, veml3235_of_match); + +static const struct i2c_device_id veml3235_id[] = { + { .name = "veml3235" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, veml3235_id); + +static struct i2c_driver veml3235_driver = { + .driver = { + .name = "veml3235", + .of_match_table = veml3235_of_match, + .pm = pm_ptr(&veml3235_pm_ops), + }, + .probe = veml3235_probe, + .id_table = veml3235_id, +}; +module_i2c_driver(veml3235_driver); + +MODULE_AUTHOR("Javier Carrasco "); +MODULE_DESCRIPTION("VEML3235 Ambient Light Sensor"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_GTS_HELPER"); diff --git a/drivers/iio/light/veml3328.c b/drivers/iio/light/veml3328.c new file mode 100644 index 000000000..7ff175392 --- /dev/null +++ b/drivers/iio/light/veml3328.c @@ -0,0 +1,422 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Vishay VEML3328 RGBCIR light sensor driver + * + * Copyright (c) 2026 Joshua Crofts + * + * Datasheet: https://www.vishay.com/docs/84968/veml3328.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define VEML3328_REG_CONF 0x00 +#define VEML3328_REG_ID 0x0c +#define VEML3328_REG_DATA_C 0x04 +#define VEML3328_REG_DATA_R 0x05 +#define VEML3328_REG_DATA_G 0x06 +#define VEML3328_REG_DATA_B 0x07 +#define VEML3328_REG_DATA_IR 0x08 + +#define VEML3328_CONF_IT_MASK GENMASK(5, 4) +#define VEML3328_CONF_GAIN_MASK GENMASK(11, 10) + +#define VEML3328_MAX_IT_TIME (400 * USEC_PER_MSEC) + +#define VEML3328_ID_VAL 0x28 + +#define VEML3328_SHUTDOWN (BIT(0) | BIT(15)) + +struct veml3328_data { + struct regmap *regmap; + /* Ensure read-modify-write sequences are not interrupted. */ + struct mutex lock; +}; + +static const struct regmap_config veml3328_regmap_config = { + .name = "veml3328", + .reg_bits = 8, + .val_bits = 16, + .max_register = VEML3328_REG_ID, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + +#define VEML3328_CHAN_SPEC(_color, _addr) { \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .channel2 = IIO_MOD_LIGHT_##_color, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), \ + .address = _addr, \ +} + +static const struct iio_chan_spec veml3328_channels[] = { + { + .type = IIO_LIGHT, + + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), + .address = VEML3328_REG_DATA_G, + }, + VEML3328_CHAN_SPEC(CLEAR, VEML3328_REG_DATA_C), + VEML3328_CHAN_SPEC(RED, VEML3328_REG_DATA_R), + VEML3328_CHAN_SPEC(GREEN, VEML3328_REG_DATA_G), + VEML3328_CHAN_SPEC(BLUE, VEML3328_REG_DATA_B), + VEML3328_CHAN_SPEC(IR, VEML3328_REG_DATA_IR), +}; + +/* + * Precomputed scale values (micro units). + * Formula for calculation: 0.384 * (50000 / IT_us) * (1 / Gain) + * Gain indexes: 0 (x0.5), 1 (x1), 2 (x2), 3 (x4) + * IT indexes: 0 (50ms), 1 (100ms), 2 (200ms), 3 (400ms) + */ +static const int veml3328_scale_vals[4][8] = { + { 0, 768000, 0, 384000, 0, 192000, 0, 96000 }, + { 0, 384000, 0, 192000, 0, 96000, 0, 48000 }, + { 0, 192000, 0, 96000, 0, 48000, 0, 24000 }, + { 0, 96000, 0, 48000, 0, 24000, 0, 12000 }, +}; + +/* integration times in microseconds */ +static const int veml3328_it_times[][2] = { + { 0, 50 * USEC_PER_MSEC }, + { 0, 100 * USEC_PER_MSEC }, + { 0, 200 * USEC_PER_MSEC }, + { 0, 400 * USEC_PER_MSEC }, +}; + +static int veml3328_power_down(struct veml3328_data *data) +{ + return regmap_set_bits(data->regmap, VEML3328_REG_CONF, + VEML3328_SHUTDOWN); +} + +static int veml3328_power_up(struct veml3328_data *data) +{ + int ret; + + ret = regmap_clear_bits(data->regmap, VEML3328_REG_CONF, + VEML3328_SHUTDOWN); + if (ret) + return ret; + + /* + * Sleep for maximum integration time to ensure sensor is powered on + * correctly. + */ + fsleep(VEML3328_MAX_IT_TIME); + + return 0; +} + +static void veml3328_power_down_action(void *data) +{ + veml3328_power_down(data); +} + +static int veml3328_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct veml3328_data *data = iio_priv(indio_dev); + struct regmap *regmap = data->regmap; + struct device *dev = regmap_get_device(regmap); + unsigned int reg_val; + int it_inx, gain_inx; + int ret; + + PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(regmap, chan->address, ®_val); + if (ret) + return ret; + + *val = reg_val; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_INT_TIME: + ret = regmap_read(regmap, VEML3328_REG_CONF, ®_val); + if (ret) + return ret; + + it_inx = FIELD_GET(VEML3328_CONF_IT_MASK, reg_val); + if (it_inx >= ARRAY_SIZE(veml3328_it_times)) + return -EINVAL; + + *val = veml3328_it_times[it_inx][0]; + *val2 = veml3328_it_times[it_inx][1]; + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_CHAN_INFO_SCALE: + ret = regmap_read(regmap, VEML3328_REG_CONF, ®_val); + if (ret) + return ret; + + it_inx = FIELD_GET(VEML3328_CONF_IT_MASK, reg_val); + gain_inx = FIELD_GET(VEML3328_CONF_GAIN_MASK, reg_val); + + if (it_inx >= ARRAY_SIZE(veml3328_it_times) || gain_inx >= 4) + return -EINVAL; + + /* Stride by 2 through the flattened array to match (val, val2) */ + *val = veml3328_scale_vals[it_inx][gain_inx * 2]; + *val2 = veml3328_scale_vals[it_inx][gain_inx * 2 + 1]; + + return IIO_VAL_INT_PLUS_MICRO; + + default: + return -EINVAL; + } +} + +static int veml3328_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct veml3328_data *data = iio_priv(indio_dev); + struct regmap *regmap = data->regmap; + struct device *dev = regmap_get_device(data->regmap); + unsigned int reg_val; + int ret, it_inx; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + *length = ARRAY_SIZE(veml3328_it_times) * 2; + *vals = (const int *)veml3328_it_times; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + + case IIO_CHAN_INFO_SCALE: { + PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + ret = regmap_read(regmap, VEML3328_REG_CONF, ®_val); + if (ret) + return ret; + + it_inx = FIELD_GET(VEML3328_CONF_IT_MASK, reg_val); + if (it_inx >= ARRAY_SIZE(veml3328_it_times)) + return -EINVAL; + + *length = 8; + *vals = (const int *)veml3328_scale_vals[it_inx]; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + } + default: + return -EINVAL; + } +} + +static int veml3328_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct veml3328_data *data = iio_priv(indio_dev); + struct regmap *regmap = data->regmap; + struct device *dev = regmap_get_device(regmap); + unsigned int reg_val; + int i, it_inx; + int ret; + + PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + guard(mutex)(&data->lock); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val != 0) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(veml3328_it_times); i++) { + if (veml3328_it_times[i][1] == val2) + break; + } + + if (i == ARRAY_SIZE(veml3328_it_times)) + return -EINVAL; + + return regmap_update_bits(regmap, VEML3328_REG_CONF, + VEML3328_CONF_IT_MASK, + FIELD_PREP(VEML3328_CONF_IT_MASK, i)); + + case IIO_CHAN_INFO_SCALE: + ret = regmap_read(regmap, VEML3328_REG_CONF, ®_val); + if (ret) + return ret; + + it_inx = FIELD_GET(VEML3328_CONF_IT_MASK, reg_val); + if (it_inx >= ARRAY_SIZE(veml3328_it_times)) + return -EINVAL; + + for (i = 0; i < 4; i++) { + if (val == veml3328_scale_vals[it_inx][i * 2] && + val2 == veml3328_scale_vals[it_inx][i * 2 + 1]) + break; + } + + if (i == 4) + return -EINVAL; + + return regmap_update_bits(regmap, VEML3328_REG_CONF, + VEML3328_CONF_GAIN_MASK, + FIELD_PREP(VEML3328_CONF_GAIN_MASK, i)); + + default: + return -EINVAL; + } +} + +static const struct iio_info veml3328_info = { + .read_raw = veml3328_read_raw, + .write_raw = veml3328_write_raw, + .read_avail = veml3328_read_avail, +}; + +static int veml3328_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct veml3328_data *data; + struct iio_dev *indio_dev; + unsigned int reg_val; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + + data->regmap = devm_regmap_init_i2c(client, &veml3328_regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "Failed to initialize regmap\n"); + + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulator\n"); + + ret = regmap_read(data->regmap, VEML3328_REG_ID, ®_val); + if (ret) + return dev_err_probe(dev, ret, "Failed to read ID register\n"); + + if ((reg_val & 0xff) != VEML3328_ID_VAL) + dev_warn(dev, "Unknown device ID: 0x%02x\n", reg_val & 0xff); + + ret = veml3328_power_up(data); + if (ret) + return dev_err_probe(dev, ret, "Failed to power on sensor\n"); + + ret = devm_add_action_or_reset(dev, veml3328_power_down_action, data); + if (ret) + return dev_err_probe(dev, ret, "Failed to register teardown\n"); + + indio_dev->name = "veml3328"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &veml3328_info; + indio_dev->channels = veml3328_channels; + indio_dev->num_channels = ARRAY_SIZE(veml3328_channels); + + pm_runtime_set_active(dev); + pm_runtime_set_autosuspend_delay(dev, 2000); + pm_runtime_use_autosuspend(dev); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable runtime PM\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static int veml3328_runtime_suspend(struct device *dev) +{ + struct veml3328_data *data = iio_priv(dev_get_drvdata(dev)); + int ret; + + ret = veml3328_power_down(data); + if (ret) + dev_err(dev, "Failed to suspend: %d\n", ret); + + return ret; +} + +static int veml3328_runtime_resume(struct device *dev) +{ + struct veml3328_data *data = iio_priv(dev_get_drvdata(dev)); + int ret; + + ret = veml3328_power_up(data); + if (ret) + dev_err(dev, "Failed to resume: %d\n", ret); + + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(veml3328_pm_ops, + veml3328_runtime_suspend, + veml3328_runtime_resume, + NULL); + +static const struct of_device_id veml3328_of_match[] = { + { .compatible = "vishay,veml3328" }, + { } +}; +MODULE_DEVICE_TABLE(of, veml3328_of_match); + +static const struct i2c_device_id veml3328_id[] = { + { .name = "veml3328" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, veml3328_id); + +static struct i2c_driver veml3328_driver = { + .driver = { + .name = "veml3328", + .of_match_table = veml3328_of_match, + .pm = pm_ptr(&veml3328_pm_ops), + }, + .probe = veml3328_probe, + .id_table = veml3328_id, +}; +module_i2c_driver(veml3328_driver); + +MODULE_AUTHOR("Joshua Crofts "); +MODULE_DESCRIPTION("VEML3328 RGBCIR Light Sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/veml6030.c b/drivers/iio/light/veml6030.c new file mode 100644 index 000000000..ec9c127e7 --- /dev/null +++ b/drivers/iio/light/veml6030.c @@ -0,0 +1,1238 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * VEML6030, VMEL6035 and VEML7700 Ambient Light Sensors + * + * Copyright (c) 2019, Rishi Gupta + * + * VEML6030: + * Datasheet: https://www.vishay.com/docs/84366/veml6030.pdf + * Appnote-84367: https://www.vishay.com/docs/84367/designingveml6030.pdf + * + * VEML6035: + * Datasheet: https://www.vishay.com/docs/84889/veml6035.pdf + * Appnote-84944: https://www.vishay.com/docs/84944/designingveml6035.pdf + * + * VEML7700: + * Datasheet: https://www.vishay.com/docs/84286/veml7700.pdf + * Appnote-84323: https://www.vishay.com/docs/84323/designingveml7700.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Device registers */ +#define VEML6030_REG_ALS_CONF 0x00 +#define VEML6030_REG_ALS_WH 0x01 +#define VEML6030_REG_ALS_WL 0x02 +#define VEML6030_REG_ALS_PSM 0x03 +#define VEML6030_REG_ALS_DATA 0x04 +#define VEML6030_REG_WH_DATA 0x05 +#define VEML6030_REG_ALS_INT 0x06 +#define VEML6030_REG_DATA(ch) (VEML6030_REG_ALS_DATA + (ch)) + +/* Bit masks for specific functionality */ +#define VEML6030_ALS_IT GENMASK(9, 6) +#define VEML6030_PSM GENMASK(2, 1) +#define VEML6030_ALS_PERS GENMASK(5, 4) +#define VEML6030_ALS_GAIN GENMASK(12, 11) +#define VEML6030_PSM_EN BIT(0) +#define VEML6030_INT_TH_LOW BIT(15) +#define VEML6030_INT_TH_HIGH BIT(14) +#define VEML6030_ALS_INT_EN BIT(1) +#define VEML6030_ALS_SD BIT(0) + +#define VEML6035_GAIN_M GENMASK(12, 10) +#define VEML6035_GAIN BIT(10) +#define VEML6035_DG BIT(11) +#define VEML6035_SENS BIT(12) +#define VEML6035_INT_CHAN BIT(3) +#define VEML6035_CHAN_EN BIT(2) + +/* Regfields */ +#define VEML6030_GAIN_RF REG_FIELD(VEML6030_REG_ALS_CONF, 11, 12) +#define VEML6030_IT_RF REG_FIELD(VEML6030_REG_ALS_CONF, 6, 9) + +#define VEML6035_GAIN_RF REG_FIELD(VEML6030_REG_ALS_CONF, 10, 12) + +/* Maximum scales x 10000 to work with integers */ +#define VEML6030_MAX_SCALE 21504 +#define VEML6035_MAX_SCALE 4096 + +enum veml6030_scan { + VEML6030_SCAN_ALS, + VEML6030_SCAN_WH, + VEML6030_SCAN_TIMESTAMP, +}; + +struct veml6030_rf { + struct regmap_field *it; + struct regmap_field *gain; +}; + +struct veml603x_chip { + const char *name; + const struct iio_chan_spec *channels; + const int num_channels; + const struct reg_field gain_rf; + const struct reg_field it_rf; + const int max_scale; + int (*hw_init)(struct iio_dev *indio_dev, struct device *dev); + int (*set_info)(struct iio_dev *indio_dev); +}; + +/* + * The resolution depends on both gain and integration time. The + * cur_resolution stores one of the resolution mentioned in the + * table during startup and gets updated whenever integration time + * or gain is changed. + * + * Table 'resolution and maximum detection range' in the appnotes + * is visualized as a 2D array. The cur_gain stores index of gain + * in this table (0-3 for VEML6030, 0-5 for VEML6035) while the + * cur_integration_time holds index of integration time (0-5). + */ +struct veml6030_data { + struct i2c_client *client; + struct regmap *regmap; + struct veml6030_rf rf; + const struct veml603x_chip *chip; + struct iio_gts gts; + +}; + +#define VEML6030_SEL_IT_25MS 0x0C +#define VEML6030_SEL_IT_50MS 0x08 +#define VEML6030_SEL_IT_100MS 0x00 +#define VEML6030_SEL_IT_200MS 0x01 +#define VEML6030_SEL_IT_400MS 0x02 +#define VEML6030_SEL_IT_800MS 0x03 +static const struct iio_itime_sel_mul veml6030_it_sel[] = { + GAIN_SCALE_ITIME_US(25000, VEML6030_SEL_IT_25MS, 1), + GAIN_SCALE_ITIME_US(50000, VEML6030_SEL_IT_50MS, 2), + GAIN_SCALE_ITIME_US(100000, VEML6030_SEL_IT_100MS, 4), + GAIN_SCALE_ITIME_US(200000, VEML6030_SEL_IT_200MS, 8), + GAIN_SCALE_ITIME_US(400000, VEML6030_SEL_IT_400MS, 16), + GAIN_SCALE_ITIME_US(800000, VEML6030_SEL_IT_800MS, 32), +}; + +/* Gains are multiplied by 8 to work with integers. The values in the + * iio-gts tables don't need corrections because the maximum value of + * the scale refers to GAIN = x1, and the rest of the values are + * obtained from the resulting linear function. + */ +#define VEML6030_SEL_MILLI_GAIN_X125 2 +#define VEML6030_SEL_MILLI_GAIN_X250 3 +#define VEML6030_SEL_MILLI_GAIN_X1000 0 +#define VEML6030_SEL_MILLI_GAIN_X2000 1 +static const struct iio_gain_sel_pair veml6030_gain_sel[] = { + GAIN_SCALE_GAIN(1, VEML6030_SEL_MILLI_GAIN_X125), + GAIN_SCALE_GAIN(2, VEML6030_SEL_MILLI_GAIN_X250), + GAIN_SCALE_GAIN(8, VEML6030_SEL_MILLI_GAIN_X1000), + GAIN_SCALE_GAIN(16, VEML6030_SEL_MILLI_GAIN_X2000), +}; + +#define VEML6035_SEL_MILLI_GAIN_X125 4 +#define VEML6035_SEL_MILLI_GAIN_X250 5 +#define VEML6035_SEL_MILLI_GAIN_X500 7 +#define VEML6035_SEL_MILLI_GAIN_X1000 0 +#define VEML6035_SEL_MILLI_GAIN_X2000 1 +#define VEML6035_SEL_MILLI_GAIN_X4000 3 +static const struct iio_gain_sel_pair veml6035_gain_sel[] = { + GAIN_SCALE_GAIN(1, VEML6035_SEL_MILLI_GAIN_X125), + GAIN_SCALE_GAIN(2, VEML6035_SEL_MILLI_GAIN_X250), + GAIN_SCALE_GAIN(4, VEML6035_SEL_MILLI_GAIN_X500), + GAIN_SCALE_GAIN(8, VEML6035_SEL_MILLI_GAIN_X1000), + GAIN_SCALE_GAIN(16, VEML6035_SEL_MILLI_GAIN_X2000), + GAIN_SCALE_GAIN(32, VEML6035_SEL_MILLI_GAIN_X4000), +}; + +/* + * Persistence = 1/2/4/8 x integration time + * Minimum time for which light readings must stay above configured + * threshold to assert the interrupt. + */ +static const char * const period_values[] = { + "0.1 0.2 0.4 0.8", + "0.2 0.4 0.8 1.6", + "0.4 0.8 1.6 3.2", + "0.8 1.6 3.2 6.4", + "0.05 0.1 0.2 0.4", + "0.025 0.050 0.1 0.2" +}; + +/* + * Return list of valid period values in seconds corresponding to + * the currently active integration time. + */ +static ssize_t in_illuminance_period_available_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct veml6030_data *data = iio_priv(dev_to_iio_dev(dev)); + int ret, reg, x; + + ret = regmap_read(data->regmap, VEML6030_REG_ALS_CONF, ®); + if (ret) { + dev_err(&data->client->dev, + "can't read als conf register %d\n", ret); + return ret; + } + + ret = ((reg >> 6) & 0xF); + switch (ret) { + case 0: + case 1: + case 2: + case 3: + x = ret; + break; + case 8: + x = 4; + break; + case 12: + x = 5; + break; + default: + return -EINVAL; + } + + return sysfs_emit(buf, "%s\n", period_values[x]); +} + +static IIO_DEVICE_ATTR_RO(in_illuminance_period_available, 0); + +static struct attribute *veml6030_event_attributes[] = { + &iio_dev_attr_in_illuminance_period_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group veml6030_event_attr_group = { + .attrs = veml6030_event_attributes, +}; + +static int veml6030_als_pwr_on(struct veml6030_data *data) +{ + int ret; + + ret = regmap_clear_bits(data->regmap, VEML6030_REG_ALS_CONF, + VEML6030_ALS_SD); + if (ret) + return ret; + + /* Wait 4 ms to let processor & oscillator start correctly */ + fsleep(4000); + + return 0; +} + +static int veml6030_als_shut_down(struct veml6030_data *data) +{ + return regmap_set_bits(data->regmap, VEML6030_REG_ALS_CONF, + VEML6030_ALS_SD); +} + +static void veml6030_als_shut_down_action(void *data) +{ + veml6030_als_shut_down(data); +} + +static const struct iio_event_spec veml6030_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_PERIOD) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +/* Channel number */ +enum veml6030_chan { + CH_ALS, + CH_WHITE, +}; + +static const struct iio_chan_spec veml6030_channels[] = { + { + .type = IIO_LIGHT, + .channel = CH_ALS, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .event_spec = veml6030_event_spec, + .num_event_specs = ARRAY_SIZE(veml6030_event_spec), + .scan_index = VEML6030_SCAN_ALS, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_INTENSITY, + .channel = CH_WHITE, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BOTH, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = VEML6030_SCAN_WH, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(VEML6030_SCAN_TIMESTAMP), +}; + +static const struct iio_chan_spec veml7700_channels[] = { + { + .type = IIO_LIGHT, + .channel = CH_ALS, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = VEML6030_SCAN_ALS, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_INTENSITY, + .channel = CH_WHITE, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BOTH, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = VEML6030_SCAN_WH, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(VEML6030_SCAN_TIMESTAMP), +}; + +static const struct regmap_range veml6030_readable_ranges[] = { + regmap_reg_range(VEML6030_REG_ALS_CONF, VEML6030_REG_ALS_INT), +}; + +static const struct regmap_access_table veml6030_readable_table = { + .yes_ranges = veml6030_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(veml6030_readable_ranges), +}; + +static const struct regmap_range veml6030_writable_ranges[] = { + regmap_reg_range(VEML6030_REG_ALS_CONF, VEML6030_REG_ALS_PSM), +}; + +static const struct regmap_access_table veml6030_writable_table = { + .yes_ranges = veml6030_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(veml6030_writable_ranges), +}; + +static const struct regmap_range veml6030_volatile_ranges[] = { + regmap_reg_range(VEML6030_REG_ALS_DATA, VEML6030_REG_WH_DATA), +}; + +static const struct regmap_access_table veml6030_volatile_table = { + .yes_ranges = veml6030_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(veml6030_volatile_ranges), +}; + +static const struct regmap_config veml6030_regmap_config = { + .name = "veml6030_regmap", + .reg_bits = 8, + .val_bits = 16, + .max_register = VEML6030_REG_ALS_INT, + .val_format_endian = REGMAP_ENDIAN_LITTLE, + .rd_table = &veml6030_readable_table, + .wr_table = &veml6030_writable_table, + .volatile_table = &veml6030_volatile_table, + .cache_type = REGCACHE_RBTREE, +}; + +static int veml6030_get_it(struct veml6030_data *data, int *val, int *val2) +{ + int ret, it_idx; + + ret = regmap_field_read(data->rf.it, &it_idx); + if (ret) + return ret; + + ret = iio_gts_find_int_time_by_sel(&data->gts, it_idx); + if (ret < 0) + return ret; + + *val2 = ret; + *val = 0; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int veml6030_set_it(struct iio_dev *indio_dev, int val, int val2) +{ + struct veml6030_data *data = iio_priv(indio_dev); + int ret, gain_idx, it_idx, new_gain, prev_gain, prev_it; + bool in_range; + + if (val || !iio_gts_valid_time(&data->gts, val2)) + return -EINVAL; + + ret = regmap_field_read(data->rf.it, &it_idx); + if (ret) + return ret; + + ret = regmap_field_read(data->rf.gain, &gain_idx); + if (ret) + return ret; + + prev_it = iio_gts_find_int_time_by_sel(&data->gts, it_idx); + if (prev_it < 0) + return prev_it; + + if (prev_it == val2) + return 0; + + prev_gain = iio_gts_find_gain_by_sel(&data->gts, gain_idx); + if (prev_gain < 0) + return prev_gain; + + ret = iio_gts_find_new_gain_by_gain_time_min(&data->gts, prev_gain, prev_it, + val2, &new_gain, &in_range); + if (ret) + return ret; + + if (!in_range) + dev_dbg(&data->client->dev, "Optimal gain out of range\n"); + + ret = iio_gts_find_sel_by_int_time(&data->gts, val2); + if (ret < 0) + return ret; + + ret = regmap_field_write(data->rf.it, ret); + if (ret) + return ret; + + ret = iio_gts_find_sel_by_gain(&data->gts, new_gain); + if (ret < 0) + return ret; + + return regmap_field_write(data->rf.gain, ret); +} + +static int veml6030_read_persistence(struct iio_dev *indio_dev, + int *val, int *val2) +{ + int ret, reg, period, x, y; + struct veml6030_data *data = iio_priv(indio_dev); + + ret = veml6030_get_it(data, &x, &y); + if (ret < 0) + return ret; + + ret = regmap_read(data->regmap, VEML6030_REG_ALS_CONF, ®); + if (ret) { + dev_err(&data->client->dev, + "can't read als conf register %d\n", ret); + } + + /* integration time multiplied by 1/2/4/8 */ + period = y * (1 << ((reg >> 4) & 0x03)); + + *val = period / 1000000; + *val2 = period % 1000000; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int veml6030_write_persistence(struct iio_dev *indio_dev, + int val, int val2) +{ + int ret, period, x, y; + struct veml6030_data *data = iio_priv(indio_dev); + + ret = veml6030_get_it(data, &x, &y); + if (ret < 0) + return ret; + + if (!val) { + period = val2 / y; + } else { + if ((val == 1) && (val2 == 600000)) + period = 1600000 / y; + else if ((val == 3) && (val2 == 200000)) + period = 3200000 / y; + else if ((val == 6) && (val2 == 400000)) + period = 6400000 / y; + else + period = -1; + } + + if (period <= 0 || period > 8 || hweight8(period) != 1) + return -EINVAL; + + ret = regmap_update_bits(data->regmap, VEML6030_REG_ALS_CONF, + VEML6030_ALS_PERS, (ffs(period) - 1) << 4); + if (ret) + dev_err(&data->client->dev, + "can't set persistence value %d\n", ret); + + return ret; +} + +static int veml6030_set_scale(struct iio_dev *indio_dev, int val, int val2) +{ + int ret, gain_sel, it_sel; + struct veml6030_data *data = iio_priv(indio_dev); + + ret = iio_gts_find_gain_time_sel_for_scale(&data->gts, val, val2, + &gain_sel, &it_sel); + if (ret) + return ret; + + ret = regmap_field_write(data->rf.it, it_sel); + if (ret) + return ret; + + ret = regmap_field_write(data->rf.gain, gain_sel); + if (ret) + return ret; + + return 0; +} + +static int veml6030_read_thresh(struct iio_dev *indio_dev, + int *val, int *val2, int dir) +{ + int ret, reg; + struct veml6030_data *data = iio_priv(indio_dev); + + if (dir == IIO_EV_DIR_RISING) + ret = regmap_read(data->regmap, VEML6030_REG_ALS_WH, ®); + else + ret = regmap_read(data->regmap, VEML6030_REG_ALS_WL, ®); + if (ret) { + dev_err(&data->client->dev, + "can't read als threshold value %d\n", ret); + return ret; + } + + *val = reg & 0xffff; + return IIO_VAL_INT; +} + +static int veml6030_write_thresh(struct iio_dev *indio_dev, + int val, int val2, int dir) +{ + int ret; + struct veml6030_data *data = iio_priv(indio_dev); + + if (val > 0xFFFF || val < 0 || val2) + return -EINVAL; + + if (dir == IIO_EV_DIR_RISING) { + ret = regmap_write(data->regmap, VEML6030_REG_ALS_WH, val); + if (ret) + dev_err(&data->client->dev, + "can't set high threshold %d\n", ret); + } else { + ret = regmap_write(data->regmap, VEML6030_REG_ALS_WL, val); + if (ret) + dev_err(&data->client->dev, + "can't set low threshold %d\n", ret); + } + + return ret; +} + +static int veml6030_get_total_gain(struct veml6030_data *data) +{ + int gain, it, reg, ret; + + ret = regmap_field_read(data->rf.gain, ®); + if (ret) + return ret; + + gain = iio_gts_find_gain_by_sel(&data->gts, reg); + if (gain < 0) + return gain; + + ret = regmap_field_read(data->rf.it, ®); + if (ret) + return ret; + + it = iio_gts_find_int_time_by_sel(&data->gts, reg); + if (it < 0) + return it; + + return iio_gts_get_total_gain(&data->gts, gain, it); +} + +static int veml6030_get_scale(struct veml6030_data *data, int *val, int *val2) +{ + int gain, it, reg, ret; + + ret = regmap_field_read(data->rf.gain, ®); + if (ret) + return ret; + + gain = iio_gts_find_gain_by_sel(&data->gts, reg); + if (gain < 0) + return gain; + + ret = regmap_field_read(data->rf.it, ®); + if (ret) + return ret; + + it = iio_gts_find_int_time_by_sel(&data->gts, reg); + if (it < 0) + return it; + + ret = iio_gts_get_scale(&data->gts, gain, it, val, val2); + if (ret) + return ret; + + return IIO_VAL_INT_PLUS_NANO; +} + +static int veml6030_process_als(struct veml6030_data *data, int raw, + int *val, int *val2) +{ + int total_gain; + + total_gain = veml6030_get_total_gain(data); + if (total_gain < 0) + return total_gain; + + *val = raw * data->chip->max_scale / total_gain / 10000; + *val2 = raw * data->chip->max_scale / total_gain % 10000 * 100; + + return IIO_VAL_INT_PLUS_MICRO; +} + +/* + * Provide both raw as well as light reading in lux. + * light (in lux) = resolution * raw reading + */ +static int veml6030_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int ret, reg; + struct veml6030_data *data = iio_priv(indio_dev); + struct regmap *regmap = data->regmap; + struct device *dev = &data->client->dev; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_LIGHT: + ret = regmap_read(regmap, VEML6030_REG_ALS_DATA, ®); + if (ret < 0) { + dev_err(dev, "can't read als data %d\n", ret); + return ret; + } + if (mask == IIO_CHAN_INFO_PROCESSED) + return veml6030_process_als(data, reg, val, val2); + + *val = reg; + return IIO_VAL_INT; + case IIO_INTENSITY: + ret = regmap_read(regmap, VEML6030_REG_WH_DATA, ®); + if (ret < 0) { + dev_err(dev, "can't read white data %d\n", ret); + return ret; + } + *val = reg; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + return veml6030_get_it(data, val, val2); + case IIO_CHAN_INFO_SCALE: + return veml6030_get_scale(data, val, val2); + default: + return -EINVAL; + } +} + +static int veml6030_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct veml6030_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return iio_gts_avail_times(&data->gts, vals, type, length); + case IIO_CHAN_INFO_SCALE: + return iio_gts_all_avail_scales(&data->gts, vals, type, length); + } + + return -EINVAL; +} + +static int veml6030_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return veml6030_set_it(indio_dev, val, val2); + case IIO_CHAN_INFO_SCALE: + return veml6030_set_scale(indio_dev, val, val2); + default: + return -EINVAL; + } +} + +static int veml6030_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_INT_TIME: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int veml6030_read_event_val(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, + int *val, int *val2) +{ + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + case IIO_EV_DIR_FALLING: + return veml6030_read_thresh(indio_dev, val, val2, dir); + default: + return -EINVAL; + } + break; + case IIO_EV_INFO_PERIOD: + return veml6030_read_persistence(indio_dev, val, val2); + default: + return -EINVAL; + } +} + +static int veml6030_write_event_val(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, + int val, int val2) +{ + switch (info) { + case IIO_EV_INFO_VALUE: + return veml6030_write_thresh(indio_dev, val, val2, dir); + case IIO_EV_INFO_PERIOD: + return veml6030_write_persistence(indio_dev, val, val2); + default: + return -EINVAL; + } +} + +static int veml6030_read_interrupt_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir) +{ + int ret, reg; + struct veml6030_data *data = iio_priv(indio_dev); + + ret = regmap_read(data->regmap, VEML6030_REG_ALS_CONF, ®); + if (ret) { + dev_err(&data->client->dev, + "can't read als conf register %d\n", ret); + return ret; + } + + if (reg & VEML6030_ALS_INT_EN) + return 1; + else + return 0; +} + +/* + * Sensor should not be measuring light when interrupt is configured. + * Therefore correct sequence to configure interrupt functionality is: + * shut down -> enable/disable interrupt -> power on + * + * state = 1 enables interrupt, state = 0 disables interrupt + */ +static int veml6030_write_interrupt_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + int ret; + struct veml6030_data *data = iio_priv(indio_dev); + + ret = veml6030_als_shut_down(data); + if (ret < 0) { + dev_err(&data->client->dev, + "can't disable als to configure interrupt %d\n", ret); + return ret; + } + + /* enable interrupt + power on */ + ret = regmap_update_bits(data->regmap, VEML6030_REG_ALS_CONF, + VEML6030_ALS_INT_EN | VEML6030_ALS_SD, state << 1); + if (ret) + dev_err(&data->client->dev, + "can't enable interrupt & poweron als %d\n", ret); + + return ret; +} + +static const struct iio_info veml6030_info = { + .read_raw = veml6030_read_raw, + .read_avail = veml6030_read_avail, + .write_raw = veml6030_write_raw, + .write_raw_get_fmt = veml6030_write_raw_get_fmt, + .read_event_value = veml6030_read_event_val, + .write_event_value = veml6030_write_event_val, + .read_event_config = veml6030_read_interrupt_config, + .write_event_config = veml6030_write_interrupt_config, + .event_attrs = &veml6030_event_attr_group, +}; + +static const struct iio_info veml6030_info_no_irq = { + .read_raw = veml6030_read_raw, + .read_avail = veml6030_read_avail, + .write_raw = veml6030_write_raw, + .write_raw_get_fmt = veml6030_write_raw_get_fmt, +}; + +static irqreturn_t veml6030_event_handler(int irq, void *private) +{ + int ret, reg, evtdir; + struct iio_dev *indio_dev = private; + struct veml6030_data *data = iio_priv(indio_dev); + + ret = regmap_read(data->regmap, VEML6030_REG_ALS_INT, ®); + if (ret) { + dev_err(&data->client->dev, + "can't read als interrupt register %d\n", ret); + return IRQ_HANDLED; + } + + /* Spurious interrupt handling */ + if (!(reg & (VEML6030_INT_TH_HIGH | VEML6030_INT_TH_LOW))) + return IRQ_NONE; + + if (reg & VEML6030_INT_TH_HIGH) + evtdir = IIO_EV_DIR_RISING; + else + evtdir = IIO_EV_DIR_FALLING; + + iio_push_event(indio_dev, IIO_UNMOD_EVENT_CODE(IIO_LIGHT, + 0, + IIO_EV_TYPE_THRESH, + evtdir), + iio_get_time_ns(indio_dev)); + + return IRQ_HANDLED; +} + +static irqreturn_t veml6030_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *iio = pf->indio_dev; + struct veml6030_data *data = iio_priv(iio); + unsigned int reg; + int ch, ret, i = 0; + struct { + u16 chans[2]; + aligned_s64 timestamp; + } scan = { }; + + iio_for_each_active_channel(iio, ch) { + ret = regmap_read(data->regmap, VEML6030_REG_DATA(ch), + ®); + if (ret) + goto done; + + scan.chans[i++] = reg; + } + + iio_push_to_buffers_with_ts(iio, &scan, sizeof(scan), pf->timestamp); + +done: + iio_trigger_notify_done(iio->trig); + + return IRQ_HANDLED; +} + +static int veml6030_set_info(struct iio_dev *indio_dev) +{ + struct veml6030_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + int ret; + + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, veml6030_event_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + indio_dev->name, indio_dev); + if (ret < 0) + return ret; + + indio_dev->info = &veml6030_info; + } else { + indio_dev->info = &veml6030_info_no_irq; + } + + return 0; +} + +static int veml7700_set_info(struct iio_dev *indio_dev) +{ + indio_dev->info = &veml6030_info_no_irq; + + return 0; +} + +static int veml6030_regfield_init(struct iio_dev *indio_dev) +{ + struct veml6030_data *data = iio_priv(indio_dev); + struct regmap *regmap = data->regmap; + struct device *dev = &data->client->dev; + struct regmap_field *rm_field; + struct veml6030_rf *rf = &data->rf; + + rm_field = devm_regmap_field_alloc(dev, regmap, data->chip->it_rf); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->it = rm_field; + + rm_field = devm_regmap_field_alloc(dev, regmap, data->chip->gain_rf); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->gain = rm_field; + + return 0; +} + +/* + * Set ALS gain to 1/8, integration time to 100 ms, PSM to mode 2, + * persistence to 1 x integration time and the threshold + * interrupt disabled by default. First shutdown the sensor, + * update registers and then power on the sensor. + */ +static int veml6030_hw_init(struct iio_dev *indio_dev, struct device *dev) +{ + int ret, val; + struct veml6030_data *data = iio_priv(indio_dev); + + ret = devm_iio_init_iio_gts(dev, 2, 150400000, + veml6030_gain_sel, ARRAY_SIZE(veml6030_gain_sel), + veml6030_it_sel, ARRAY_SIZE(veml6030_it_sel), + &data->gts); + if (ret) + return dev_err_probe(dev, ret, "failed to init iio gts\n"); + + ret = veml6030_als_shut_down(data); + if (ret) + return dev_err_probe(dev, ret, "can't shutdown als\n"); + + ret = regmap_write(data->regmap, VEML6030_REG_ALS_CONF, 0x1001); + if (ret) + return dev_err_probe(dev, ret, "can't setup als configs\n"); + + ret = regmap_update_bits(data->regmap, VEML6030_REG_ALS_PSM, + VEML6030_PSM | VEML6030_PSM_EN, 0x03); + if (ret) + return dev_err_probe(dev, ret, "can't setup default PSM\n"); + + ret = regmap_write(data->regmap, VEML6030_REG_ALS_WH, 0xFFFF); + if (ret) + return dev_err_probe(dev, ret, "can't setup high threshold\n"); + + ret = regmap_write(data->regmap, VEML6030_REG_ALS_WL, 0x0000); + if (ret) + return dev_err_probe(dev, ret, "can't setup low threshold\n"); + + ret = veml6030_als_pwr_on(data); + if (ret) + return dev_err_probe(dev, ret, "can't poweron als\n"); + + ret = devm_add_action_or_reset(dev, veml6030_als_shut_down_action, data); + if (ret < 0) + return ret; + + /* Clear stale interrupt status bits if any during start */ + ret = regmap_read(data->regmap, VEML6030_REG_ALS_INT, &val); + if (ret < 0) + return dev_err_probe(dev, ret, + "can't clear als interrupt status\n"); + + return ret; +} + +/* + * Set ALS gain to 1/8, integration time to 100 ms, ALS and WHITE + * channel enabled, ALS channel interrupt, PSM enabled, + * PSM_WAIT = 0.8 s, persistence to 1 x integration time and the + * threshold interrupt disabled by default. First shutdown the sensor, + * update registers and then power on the sensor. + */ +static int veml6035_hw_init(struct iio_dev *indio_dev, struct device *dev) +{ + int ret, val; + struct veml6030_data *data = iio_priv(indio_dev); + + ret = devm_iio_init_iio_gts(dev, 0, 409600000, + veml6035_gain_sel, ARRAY_SIZE(veml6035_gain_sel), + veml6030_it_sel, ARRAY_SIZE(veml6030_it_sel), + &data->gts); + if (ret) + return dev_err_probe(dev, ret, "failed to init iio gts\n"); + + ret = veml6030_als_shut_down(data); + if (ret) + return dev_err_probe(dev, ret, "can't shutdown als\n"); + + ret = regmap_write(data->regmap, VEML6030_REG_ALS_CONF, + VEML6035_SENS | VEML6035_CHAN_EN | VEML6030_ALS_SD); + if (ret) + return dev_err_probe(dev, ret, "can't setup als configs\n"); + + ret = regmap_update_bits(data->regmap, VEML6030_REG_ALS_PSM, + VEML6030_PSM | VEML6030_PSM_EN, 0x03); + if (ret) + return dev_err_probe(dev, ret, "can't setup default PSM\n"); + + ret = regmap_write(data->regmap, VEML6030_REG_ALS_WH, 0xFFFF); + if (ret) + return dev_err_probe(dev, ret, "can't setup high threshold\n"); + + ret = regmap_write(data->regmap, VEML6030_REG_ALS_WL, 0x0000); + if (ret) + return dev_err_probe(dev, ret, "can't setup low threshold\n"); + + ret = veml6030_als_pwr_on(data); + if (ret) + return dev_err_probe(dev, ret, "can't poweron als\n"); + + ret = devm_add_action_or_reset(dev, veml6030_als_shut_down_action, data); + if (ret < 0) + return ret; + + /* Clear stale interrupt status bits if any during start */ + ret = regmap_read(data->regmap, VEML6030_REG_ALS_INT, &val); + if (ret < 0) + return dev_err_probe(dev, ret, + "can't clear als interrupt status\n"); + + return 0; +} + +static int veml6030_probe(struct i2c_client *client) +{ + int ret; + struct veml6030_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return dev_err_probe(&client->dev, -EOPNOTSUPP, + "i2c adapter doesn't support plain i2c\n"); + + regmap = devm_regmap_init_i2c(client, &veml6030_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(&client->dev, PTR_ERR(regmap), + "can't setup regmap\n"); + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->regmap = regmap; + + ret = devm_regulator_get_enable(&client->dev, "vdd"); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to enable regulator\n"); + + data->chip = i2c_get_match_data(client); + if (!data->chip) + return -EINVAL; + + indio_dev->name = data->chip->name; + indio_dev->channels = data->chip->channels; + indio_dev->num_channels = data->chip->num_channels; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = data->chip->set_info(indio_dev); + if (ret < 0) + return ret; + + ret = veml6030_regfield_init(indio_dev); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to init regfields\n"); + + ret = data->chip->hw_init(indio_dev, &client->dev); + if (ret < 0) + return ret; + + ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, NULL, + veml6030_trigger_handler, NULL); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to register triggered buffer"); + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static int veml6030_runtime_suspend(struct device *dev) +{ + int ret; + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct veml6030_data *data = iio_priv(indio_dev); + + ret = veml6030_als_shut_down(data); + if (ret < 0) + dev_err(&data->client->dev, "can't suspend als %d\n", ret); + + return ret; +} + +static int veml6030_runtime_resume(struct device *dev) +{ + int ret; + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct veml6030_data *data = iio_priv(indio_dev); + + ret = veml6030_als_pwr_on(data); + if (ret < 0) + dev_err(&data->client->dev, "can't resume als %d\n", ret); + + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(veml6030_pm_ops, veml6030_runtime_suspend, + veml6030_runtime_resume, NULL); + +static const struct veml603x_chip veml6030_chip = { + .name = "veml6030", + .channels = veml6030_channels, + .num_channels = ARRAY_SIZE(veml6030_channels), + .gain_rf = VEML6030_GAIN_RF, + .it_rf = VEML6030_IT_RF, + .max_scale = VEML6030_MAX_SCALE, + .hw_init = veml6030_hw_init, + .set_info = veml6030_set_info, +}; + +static const struct veml603x_chip veml6035_chip = { + .name = "veml6035", + .channels = veml6030_channels, + .num_channels = ARRAY_SIZE(veml6030_channels), + .gain_rf = VEML6035_GAIN_RF, + .it_rf = VEML6030_IT_RF, + .max_scale = VEML6035_MAX_SCALE, + .hw_init = veml6035_hw_init, + .set_info = veml6030_set_info, +}; + +static const struct veml603x_chip veml7700_chip = { + .name = "veml7700", + .channels = veml7700_channels, + .num_channels = ARRAY_SIZE(veml7700_channels), + .gain_rf = VEML6030_GAIN_RF, + .it_rf = VEML6030_IT_RF, + .max_scale = VEML6030_MAX_SCALE, + .hw_init = veml6030_hw_init, + .set_info = veml7700_set_info, +}; + +static const struct of_device_id veml6030_of_match[] = { + { + .compatible = "vishay,veml6030", + .data = &veml6030_chip, + }, + { + .compatible = "vishay,veml6035", + .data = &veml6035_chip, + }, + { + .compatible = "vishay,veml7700", + .data = &veml7700_chip, + }, + { } +}; +MODULE_DEVICE_TABLE(of, veml6030_of_match); + +static const struct i2c_device_id veml6030_id[] = { + { .name = "veml6030", .driver_data = (kernel_ulong_t)&veml6030_chip }, + { .name = "veml6035", .driver_data = (kernel_ulong_t)&veml6035_chip }, + { .name = "veml7700", .driver_data = (kernel_ulong_t)&veml7700_chip }, + { } +}; +MODULE_DEVICE_TABLE(i2c, veml6030_id); + +static struct i2c_driver veml6030_driver = { + .driver = { + .name = "veml6030", + .of_match_table = veml6030_of_match, + .pm = pm_ptr(&veml6030_pm_ops), + }, + .probe = veml6030_probe, + .id_table = veml6030_id, +}; +module_i2c_driver(veml6030_driver); + +MODULE_AUTHOR("Rishi Gupta "); +MODULE_DESCRIPTION("VEML6030 Ambient Light Sensor"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_GTS_HELPER"); diff --git a/drivers/iio/light/veml6040.c b/drivers/iio/light/veml6040.c new file mode 100644 index 000000000..0960784b8 --- /dev/null +++ b/drivers/iio/light/veml6040.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Vishay VEML6040 RGBW light sensor driver + * + * Copyright (C) 2024 Sentec AG + * Author: Arthur Becker + * + */ + +#include +#include +#include +#include +#include +#include +#include + +/* VEML6040 Configuration Registers + * + * SD: Shutdown + * AF: Auto / Force Mode (Auto Measurements On:0, Off:1) + * TR: Trigger Measurement (when AF Bit is set) + * IT: Integration Time + */ +#define VEML6040_CONF_REG 0x000 +#define VEML6040_CONF_SD_MSK BIT(0) +#define VEML6040_CONF_AF_MSK BIT(1) +#define VEML6040_CONF_TR_MSK BIT(2) +#define VEML6040_CONF_IT_MSK GENMASK(6, 4) +#define VEML6040_CONF_IT_40_MS 0 +#define VEML6040_CONF_IT_80_MS 1 +#define VEML6040_CONF_IT_160_MS 2 +#define VEML6040_CONF_IT_320_MS 3 +#define VEML6040_CONF_IT_640_MS 4 +#define VEML6040_CONF_IT_1280_MS 5 + +/* VEML6040 Read Only Registers */ +#define VEML6040_REG_R 0x08 +#define VEML6040_REG_G 0x09 +#define VEML6040_REG_B 0x0A +#define VEML6040_REG_W 0x0B + +static const int veml6040_it_ms[] = { 40, 80, 160, 320, 640, 1280 }; + +enum veml6040_chan { + CH_RED, + CH_GREEN, + CH_BLUE, + CH_WHITE, +}; + +struct veml6040_data { + struct i2c_client *client; + struct regmap *regmap; +}; + +static const struct regmap_config veml6040_regmap_config = { + .name = "veml6040_regmap", + .reg_bits = 8, + .val_bits = 16, + .max_register = VEML6040_REG_W, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + +static int veml6040_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int ret, reg, it_index; + struct veml6040_data *data = iio_priv(indio_dev); + struct regmap *regmap = data->regmap; + struct device *dev = &data->client->dev; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(regmap, chan->address, ®); + if (ret) { + dev_err(dev, "Data read failed: %d\n", ret); + return ret; + } + *val = reg; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_INT_TIME: + ret = regmap_read(regmap, VEML6040_CONF_REG, ®); + if (ret) { + dev_err(dev, "Data read failed: %d\n", ret); + return ret; + } + it_index = FIELD_GET(VEML6040_CONF_IT_MSK, reg); + if (it_index >= ARRAY_SIZE(veml6040_it_ms)) { + dev_err(dev, "Invalid Integration Time Set"); + return -EINVAL; + } + *val = veml6040_it_ms[it_index]; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int veml6040_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct veml6040_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + for (int i = 0; i < ARRAY_SIZE(veml6040_it_ms); i++) { + if (veml6040_it_ms[i] != val) + continue; + + return regmap_update_bits(data->regmap, + VEML6040_CONF_REG, + VEML6040_CONF_IT_MSK, + FIELD_PREP(VEML6040_CONF_IT_MSK, i)); + } + return -EINVAL; + default: + return -EINVAL; + } +} + +static int veml6040_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + *length = ARRAY_SIZE(veml6040_it_ms); + *vals = veml6040_it_ms; + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + + default: + return -EINVAL; + } +} + +static const struct iio_info veml6040_info = { + .read_raw = veml6040_read_raw, + .write_raw = veml6040_write_raw, + .read_avail = veml6040_read_avail, +}; + +static const struct iio_chan_spec veml6040_channels[] = { + { + .type = IIO_INTENSITY, + .address = VEML6040_REG_R, + .channel = CH_RED, + .channel2 = IIO_MOD_LIGHT_RED, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_shared_by_type_available = + BIT(IIO_CHAN_INFO_INT_TIME), + }, + { + .type = IIO_INTENSITY, + .address = VEML6040_REG_G, + .channel = CH_GREEN, + .channel2 = IIO_MOD_LIGHT_GREEN, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_shared_by_type_available = + BIT(IIO_CHAN_INFO_INT_TIME), + }, + { + .type = IIO_INTENSITY, + .address = VEML6040_REG_B, + .channel = CH_BLUE, + .channel2 = IIO_MOD_LIGHT_BLUE, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_shared_by_type_available = + BIT(IIO_CHAN_INFO_INT_TIME), + }, + { + .type = IIO_INTENSITY, + .address = VEML6040_REG_W, + .channel = CH_WHITE, + .channel2 = IIO_MOD_LIGHT_CLEAR, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_shared_by_type_available = + BIT(IIO_CHAN_INFO_INT_TIME), + } +}; + +static void veml6040_shutdown_action(void *data) +{ + struct veml6040_data *veml6040_data = data; + + regmap_update_bits(veml6040_data->regmap, VEML6040_CONF_REG, + VEML6040_CONF_SD_MSK, VEML6040_CONF_SD_MSK); +} + +static int veml6040_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct veml6040_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + const int init_config = + FIELD_PREP(VEML6040_CONF_IT_MSK, VEML6040_CONF_IT_40_MS) | + FIELD_PREP(VEML6040_CONF_AF_MSK, 0) | + FIELD_PREP(VEML6040_CONF_SD_MSK, 0); + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return dev_err_probe(dev, -EOPNOTSUPP, + "I2C adapter doesn't support plain I2C\n"); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, &veml6040_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Regmap setup failed\n"); + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->regmap = regmap; + + indio_dev->name = "veml6040"; + indio_dev->info = &veml6040_info; + indio_dev->channels = veml6040_channels; + indio_dev->num_channels = ARRAY_SIZE(veml6040_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return ret; + + ret = regmap_write(regmap, VEML6040_CONF_REG, init_config); + if (ret) + return dev_err_probe(dev, ret, + "Could not set initial config\n"); + + ret = devm_add_action_or_reset(dev, veml6040_shutdown_action, data); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct i2c_device_id veml6040_id_table[] = { + { .name = "veml6040" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, veml6040_id_table); + +static const struct of_device_id veml6040_of_match[] = { + {.compatible = "vishay,veml6040"}, + { } +}; +MODULE_DEVICE_TABLE(of, veml6040_of_match); + +static struct i2c_driver veml6040_driver = { + .probe = veml6040_probe, + .id_table = veml6040_id_table, + .driver = { + .name = "veml6040", + .of_match_table = veml6040_of_match, + }, +}; +module_i2c_driver(veml6040_driver); + +MODULE_DESCRIPTION("veml6040 RGBW light sensor driver"); +MODULE_AUTHOR("Arthur Becker "); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/veml6046x00.c b/drivers/iio/light/veml6046x00.c new file mode 100644 index 000000000..b40b70679 --- /dev/null +++ b/drivers/iio/light/veml6046x00.c @@ -0,0 +1,1029 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * VEML6046X00 High Accuracy RGBIR Color Sensor + * + * Copyright (c) 2025 Andreas Klinger + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +/* + * Device registers + * Those which are accessed as bulk io are omitted + */ +#define VEML6046X00_REG_CONF0 0x00 +#define VEML6046X00_REG_CONF1 0x01 +#define VEML6046X00_REG_THDH 0x04 +#define VEML6046X00_REG_THDL 0x06 +#define VEML6046X00_REG_R 0x10 +#define VEML6046X00_REG_G 0x12 +#define VEML6046X00_REG_B 0x14 +#define VEML6046X00_REG_IR 0x16 +#define VEML6046X00_REG_ID 0x18 +#define VEML6046X00_REG_INT 0x1A +#define VEML6046X00_REG_INT_H 0x1B + +/* Bit masks for specific functionality */ +#define VEML6046X00_CONF0_ON_0 BIT(0) +#define VEML6046X00_CONF0_INT BIT(1) +#define VEML6046X00_CONF0_AF_TRIG BIT(2) +#define VEML6046X00_CONF0_AF BIT(3) +#define VEML6046X00_CONF0_IT GENMASK(6, 4) +#define VEML6046X00_CONF1_CAL BIT(0) +#define VEML6046X00_CONF1_PERS GENMASK(2, 1) +#define VEML6046X00_CONF1_GAIN GENMASK(4, 3) +#define VEML6046X00_CONF1_PD_D2 BIT(6) +#define VEML6046X00_CONF1_ON_1 BIT(7) +#define VEML6046X00_INT_TH_H BIT(1) +#define VEML6046X00_INT_TH_L BIT(2) +#define VEML6046X00_INT_DRDY BIT(3) +#define VEML6046X00_INT_MASK \ + (VEML6046X00_INT_TH_H | VEML6046X00_INT_TH_L | VEML6046X00_INT_DRDY) + +#define VEML6046X00_GAIN_1 0x0 +#define VEML6046X00_GAIN_2 0x1 +#define VEML6046X00_GAIN_0_66 0x2 +#define VEML6046X00_GAIN_0_5 0x3 + +#define VEML6046X00_PD_2_2 0x0 +#define VEML6046X00_PD_1_2 BIT(6) + +/* Autosuspend delay */ +#define VEML6046X00_AUTOSUSPEND_MS (3 * MSEC_PER_SEC) + +enum veml6046x00_scan { + VEML6046X00_SCAN_R, + VEML6046X00_SCAN_G, + VEML6046X00_SCAN_B, + VEML6046X00_SCAN_IR, + VEML6046X00_SCAN_TIMESTAMP, +}; + +/** + * struct veml6046x00_rf - Regmap field of configuration registers. + * @int_en: Interrupt enable of green channel. + * @mode: Mode of operation. + * Driver uses always Active force mode. + * @trig: Trigger to be set in active force mode for starting + * measurement. + * @it: Integration time. + * @pers: Persistense - Number of threshold crossing for triggering + * interrupt. + */ +struct veml6046x00_rf { + struct regmap_field *int_en; + struct regmap_field *mode; + struct regmap_field *trig; + struct regmap_field *it; + struct regmap_field *pers; +}; + +/** + * struct veml6046x00_data - Private data of driver. + * @regmap: Regmap definition of sensor. + * @trig: Industrial-IO trigger. + * @rf: Regmap field of configuration. + */ +struct veml6046x00_data { + struct regmap *regmap; + struct iio_trigger *trig; + struct veml6046x00_rf rf; +}; + +/** + * DOC: Valid integration times (IT) + * + * static const int veml6046x00_it contains the array with valid IT. + * + * Register value to be read or written in regmap_field it on veml6046x00 is + * identical with array index. + * This means there is no separate translation table between valid integration + * times and register values needed. The index of the array is identical with + * the register value. + * + * The array is in the form as expected by the callback of the sysfs attribute + * integration_time_available (IIO_CHAN_INFO_INT_TIME). So there is no + * additional conversion needed. + */ +static const int veml6046x00_it[][2] = { + { 0, 3125 }, + { 0, 6250 }, + { 0, 12500 }, + { 0, 25000 }, + { 0, 50000 }, + { 0, 100000 }, + { 0, 200000 }, + { 0, 400000 }, +}; + +/** + * DOC: Handling of gain and photodiode size (PD) + * + * Gains here in the driver are not exactly the same as in the datasheet of the + * sensor. The gain in the driver is a combination of the gain of the sensor + * with the photodiode size (PD). + * The following combinations are possible: + * gain(driver) = gain(sensor) * PD + * 0.25 = x0.5 * 1/2 + * 0.33 = x0.66 * 1/2 + * 0.5 = x0.5 * 2/2 + * 0.66 = x0.66 * 2/2 + * 1 = x1 * 2/2 + * 2 = x2 * 2/2 + */ + +/** + * struct veml6046x00_gain_pd - Translation of gain and photodiode size (PD). + * @gain_sen: Gain used in the sensor as described in the datasheet of the + * sensor + * @pd: Photodiode size in the sensor + * + * This is the translation table from the gain used in the driver (and also used + * by the userspace interface in sysfs) to the gain and PD used in the sensor + * hardware. + * + * There are six gain values visible to the user (0.25 .. 2) which translate to + * two different gains in the sensor hardware (x0.5 .. x2) and two PD (1/2 and + * 2/2). Theoretical are there eight combinations, but gain values 0.5 and 1 are + * doubled and therefore the combination with the larger PD (2/2) is taken as + * more photodiode cells are supposed to deliver a more precise result. + */ +struct veml6046x00_gain_pd { + unsigned int gain_sen; + unsigned int pd; +}; + +static const struct veml6046x00_gain_pd veml6046x00_gain_pd[] = { + { .gain_sen = VEML6046X00_GAIN_0_5, .pd = VEML6046X00_PD_1_2 }, + { .gain_sen = VEML6046X00_GAIN_0_66, .pd = VEML6046X00_PD_1_2 }, + { .gain_sen = VEML6046X00_GAIN_0_5, .pd = VEML6046X00_PD_2_2 }, + { .gain_sen = VEML6046X00_GAIN_0_66, .pd = VEML6046X00_PD_2_2 }, + { .gain_sen = VEML6046X00_GAIN_1, .pd = VEML6046X00_PD_2_2 }, + { .gain_sen = VEML6046X00_GAIN_2, .pd = VEML6046X00_PD_2_2 }, +}; + +/** + * DOC: Factors for calculation of lux + * + * static const int veml6046x00_it_gains contains the factors for calculation of + * lux. + * + * Depending on the set up integration time (IT), gain and photodiode size (PD) + * the measured raw values are different if the light is constant. As the gain + * and PD are already coupled in the driver (see &struct veml6046x00_gain_pd) + * there are two dimensions remaining: IT and gain(driver). + * + * The array of available factors for a certain IT are grouped together in the + * same form as expected by the callback of scale_available + * (IIO_CHAN_INFO_SCALE). + * + * Factors for lux / raw count are taken directly from the datasheet. + */ +static const int veml6046x00_it_gains[][6][2] = { + /* integration time: 3.125 ms */ + { + { 5, 376000 }, /* gain: x0.25 */ + { 4, 72700 }, /* gain: x0.33 */ + { 2, 688000 }, /* gain: x0.5 */ + { 2, 36400 }, /* gain: x0.66 */ + { 1, 344000 }, /* gain: x1 */ + { 0, 672000 }, /* gain: x2 */ + }, + /* integration time: 6.25 ms */ + { + { 2, 688000 }, /* gain: x0.25 */ + { 2, 36350 }, /* gain: x0.33 */ + { 1, 344000 }, /* gain: x0.5 */ + { 1, 18200 }, /* gain: x0.66 */ + { 0, 672000 }, /* gain: x1 */ + { 0, 336000 }, /* gain: x2 */ + }, + /* integration time: 12.5 ms */ + { + { 1, 344000 }, /* gain: x0.25 */ + { 1, 18175 }, /* gain: x0.33 */ + { 0, 672000 }, /* gain: x0.5 */ + { 0, 509100 }, /* gain: x0.66 */ + { 0, 336000 }, /* gain: x1 */ + { 0, 168000 }, /* gain: x2 */ + }, + /* integration time: 25 ms */ + { + { 0, 672000 }, /* gain: x0.25 */ + { 0, 509087 }, /* gain: x0.33 */ + { 0, 336000 }, /* gain: x0.5 */ + { 0, 254550 }, /* gain: x0.66 */ + { 0, 168000 }, /* gain: x1 */ + { 0, 84000 }, /* gain: x2 */ + }, + /* integration time: 50 ms */ + { + { 0, 336000 }, /* gain: x0.25 */ + { 0, 254543 }, /* gain: x0.33 */ + { 0, 168000 }, /* gain: x0.5 */ + { 0, 127275 }, /* gain: x0.66 */ + { 0, 84000 }, /* gain: x1 */ + { 0, 42000 }, /* gain: x2 */ + }, + /* integration time: 100 ms */ + { + { 0, 168000 }, /* gain: x0.25 */ + { 0, 127271 }, /* gain: x0.33 */ + { 0, 84000 }, /* gain: x0.5 */ + { 0, 63637 }, /* gain: x0.66 */ + { 0, 42000 }, /* gain: x1 */ + { 0, 21000 }, /* gain: x2 */ + }, + /* integration time: 200 ms */ + { + { 0, 84000 }, /* gain: x0.25 */ + { 0, 63635 }, /* gain: x0.33 */ + { 0, 42000 }, /* gain: x0.5 */ + { 0, 31818 }, /* gain: x0.66 */ + { 0, 21000 }, /* gain: x1 */ + { 0, 10500 }, /* gain: x2 */ + }, + /* integration time: 400 ms */ + { + { 0, 42000 }, /* gain: x0.25 */ + { 0, 31817 }, /* gain: x0.33 */ + { 0, 21000 }, /* gain: x0.5 */ + { 0, 15909 }, /* gain: x0.66 */ + { 0, 10500 }, /* gain: x1 */ + { 0, 5250 }, /* gain: x2 */ + }, +}; + +/* + * Two bits (RGB_ON_0 and RGB_ON_1) must be cleared to power on the device. + */ +static int veml6046x00_power_on(struct veml6046x00_data *data) +{ + int ret; + struct device *dev = regmap_get_device(data->regmap); + + ret = regmap_clear_bits(data->regmap, VEML6046X00_REG_CONF0, + VEML6046X00_CONF0_ON_0); + if (ret) { + dev_err(dev, "Failed to set bit for power on %d\n", ret); + return ret; + } + + return regmap_clear_bits(data->regmap, VEML6046X00_REG_CONF1, + VEML6046X00_CONF1_ON_1); +} + +/* + * Two bits (RGB_ON_0 and RGB_ON_1) must be set to power off the device. + */ +static int veml6046x00_shutdown(struct veml6046x00_data *data) +{ + int ret; + struct device *dev = regmap_get_device(data->regmap); + + ret = regmap_set_bits(data->regmap, VEML6046X00_REG_CONF0, + VEML6046X00_CONF0_ON_0); + if (ret) { + dev_err(dev, "Failed to set bit for shutdown %d\n", ret); + return ret; + } + + return regmap_set_bits(data->regmap, VEML6046X00_REG_CONF1, + VEML6046X00_CONF1_ON_1); +} + +static void veml6046x00_shutdown_action(void *data) +{ + veml6046x00_shutdown(data); +} + +static const struct iio_chan_spec veml6046x00_channels[] = { + { + .type = IIO_INTENSITY, + .address = VEML6046X00_REG_R, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_RED, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = VEML6046X00_SCAN_R, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_INTENSITY, + .address = VEML6046X00_REG_G, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_GREEN, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = VEML6046X00_SCAN_G, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_INTENSITY, + .address = VEML6046X00_REG_B, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BLUE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = VEML6046X00_SCAN_B, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_INTENSITY, + .address = VEML6046X00_REG_IR, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = VEML6046X00_SCAN_IR, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(VEML6046X00_SCAN_TIMESTAMP), +}; + +static const struct regmap_config veml6046x00_regmap_config = { + .name = "veml6046x00_regm", + .reg_bits = 8, + .val_bits = 8, + .max_register = VEML6046X00_REG_INT_H, +}; + +static const struct reg_field veml6046x00_rf_int_en = + REG_FIELD(VEML6046X00_REG_CONF0, 1, 1); + +static const struct reg_field veml6046x00_rf_trig = + REG_FIELD(VEML6046X00_REG_CONF0, 2, 2); + +static const struct reg_field veml6046x00_rf_mode = + REG_FIELD(VEML6046X00_REG_CONF0, 3, 3); + +static const struct reg_field veml6046x00_rf_it = + REG_FIELD(VEML6046X00_REG_CONF0, 4, 6); + +static const struct reg_field veml6046x00_rf_pers = + REG_FIELD(VEML6046X00_REG_CONF1, 1, 2); + +static int veml6046x00_regfield_init(struct veml6046x00_data *data) +{ + struct regmap *regmap = data->regmap; + struct device *dev = regmap_get_device(data->regmap); + struct regmap_field *rm_field; + struct veml6046x00_rf *rf = &data->rf; + + rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_int_en); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->int_en = rm_field; + + rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_mode); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->mode = rm_field; + + rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_trig); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->trig = rm_field; + + rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_it); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->it = rm_field; + + rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_pers); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->pers = rm_field; + + return 0; +} + +static int veml6046x00_get_it_index(struct veml6046x00_data *data) +{ + int ret; + unsigned int reg; + + ret = regmap_field_read(data->rf.it, ®); + if (ret) + return ret; + + /* register value is identical with index of array */ + if (reg >= ARRAY_SIZE(veml6046x00_it)) + return -EINVAL; + + return reg; +} + +static int veml6046x00_get_it_usec(struct veml6046x00_data *data, unsigned int *it_usec) +{ + int ret; + unsigned int reg; + + ret = regmap_field_read(data->rf.it, ®); + if (ret) + return ret; + + if (reg >= ARRAY_SIZE(veml6046x00_it)) + return -EINVAL; + + *it_usec = veml6046x00_it[reg][1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int veml6046x00_set_it(struct iio_dev *iio, int val, int val2) +{ + struct veml6046x00_data *data = iio_priv(iio); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(veml6046x00_it); i++) { + if ((veml6046x00_it[i][0] == val) && + (veml6046x00_it[i][1] == val2)) + return regmap_field_write(data->rf.it, i); + } + + return -EINVAL; +} + +static int veml6046x00_get_val_gain_idx(struct veml6046x00_data *data, int val, + int val2) +{ + unsigned int i; + int it_idx; + + it_idx = veml6046x00_get_it_index(data); + if (it_idx < 0) + return it_idx; + + for (i = 0; i < ARRAY_SIZE(veml6046x00_it_gains[it_idx]); i++) { + if ((veml6046x00_it_gains[it_idx][i][0] == val) && + (veml6046x00_it_gains[it_idx][i][1] == val2)) + return i; + } + + return -EINVAL; +} + +static int veml6046x00_get_gain_idx(struct veml6046x00_data *data) +{ + int ret; + unsigned int i, reg, reg_gain, reg_pd; + + ret = regmap_read(data->regmap, VEML6046X00_REG_CONF1, ®); + if (ret) + return ret; + + reg_gain = FIELD_GET(VEML6046X00_CONF1_GAIN, reg); + reg_pd = reg & VEML6046X00_CONF1_PD_D2; + + for (i = 0; i < ARRAY_SIZE(veml6046x00_gain_pd); i++) { + if ((veml6046x00_gain_pd[i].gain_sen == reg_gain) && + (veml6046x00_gain_pd[i].pd == reg_pd)) + return i; + } + + return -EINVAL; +} + +static int veml6046x00_set_scale(struct iio_dev *iio, int val, int val2) +{ + struct veml6046x00_data *data = iio_priv(iio); + unsigned int new_scale; + int gain_idx; + + gain_idx = veml6046x00_get_val_gain_idx(data, val, val2); + if (gain_idx < 0) + return gain_idx; + + new_scale = FIELD_PREP(VEML6046X00_CONF1_GAIN, + veml6046x00_gain_pd[gain_idx].gain_sen) | + veml6046x00_gain_pd[gain_idx].pd; + + return regmap_update_bits(data->regmap, VEML6046X00_REG_CONF1, + VEML6046X00_CONF1_GAIN | + VEML6046X00_CONF1_PD_D2, + new_scale); +} + +static int veml6046x00_get_scale(struct veml6046x00_data *data, + int *val, int *val2) +{ + int gain_idx, it_idx; + + gain_idx = veml6046x00_get_gain_idx(data); + if (gain_idx < 0) + return gain_idx; + + it_idx = veml6046x00_get_it_index(data); + if (it_idx < 0) + return it_idx; + + *val = veml6046x00_it_gains[it_idx][gain_idx][0]; + *val2 = veml6046x00_it_gains[it_idx][gain_idx][1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +/** + * veml6046x00_read_data_ready() - Read data ready bit + * @data: Private data. + * + * Helper function for reading data ready bit from interrupt register. + * + * Return: + * * %1 - Data is available (AF_DATA_READY is set) + * * %0 - No data available + * * %-EIO - Error during bulk read + */ +static int veml6046x00_read_data_ready(struct veml6046x00_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + u8 reg[2]; + + /* + * Note from the vendor, but not explicitly in the datasheet: we + * should always read both registers together. + */ + ret = regmap_bulk_read(data->regmap, VEML6046X00_REG_INT, + ®, sizeof(reg)); + if (ret) { + dev_err(dev, "Failed to read interrupt register %d\n", ret); + return -EIO; + } + + if (reg[1] & VEML6046X00_INT_DRDY) + return 1; + + return 0; +} + +/** + * veml6046x00_wait_data_available() - Wait until data is available + * @iio: Industrial IO. + * @usecs: Microseconds to wait for data. + * + * This function waits for a certain bit in the interrupt register which signals + * that there is data to be read available. + * + * It tries it two times with a waiting time of usecs in between. + * + * Return: + * * %1 - Data is available (AF_DATA_READY is set) + * * %0 - Timeout, no data available after usecs timeout + * * %-EIO - Error during bulk read + */ +static int veml6046x00_wait_data_available(struct iio_dev *iio, unsigned int usecs) +{ + struct veml6046x00_data *data = iio_priv(iio); + int ret; + + ret = veml6046x00_read_data_ready(data); + if (ret) + return ret; + + fsleep(usecs); + return veml6046x00_read_data_ready(data); +} + +static int veml6046x00_single_read(struct iio_dev *iio, + enum iio_modifier modifier, int *val) +{ + struct veml6046x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + unsigned int addr, it_usec; + int ret; + __le16 reg; + + switch (modifier) { + case IIO_MOD_LIGHT_RED: + addr = VEML6046X00_REG_R; + break; + case IIO_MOD_LIGHT_GREEN: + addr = VEML6046X00_REG_G; + break; + case IIO_MOD_LIGHT_BLUE: + addr = VEML6046X00_REG_B; + break; + case IIO_MOD_LIGHT_IR: + addr = VEML6046X00_REG_IR; + break; + default: + return -EINVAL; + } + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = veml6046x00_get_it_usec(data, &it_usec); + if (ret < 0) { + dev_err(dev, "Failed to get integration time ret: %d", ret); + goto out; + } + + ret = regmap_field_write(data->rf.mode, 1); + if (ret) { + dev_err(dev, "Failed to write mode ret: %d", ret); + goto out; + } + + ret = regmap_field_write(data->rf.trig, 1); + if (ret) { + dev_err(dev, "Failed to write trigger ret: %d", ret); + goto out; + } + + /* integration time + 12.5 % to ensure completion */ + fsleep(it_usec + it_usec / 8); + + ret = veml6046x00_wait_data_available(iio, it_usec * 4); + if (ret < 0) + goto out; + if (ret == 0) { + ret = -EAGAIN; + goto out; + } + + if (!iio_device_claim_direct(iio)) { + ret = -EBUSY; + goto out; + } + + ret = regmap_bulk_read(data->regmap, addr, ®, sizeof(reg)); + iio_device_release_direct(iio); + if (ret) + goto out; + + *val = le16_to_cpu(reg); + + ret = IIO_VAL_INT; + +out: + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static int veml6046x00_read_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct veml6046x00_data *data = iio_priv(iio); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type != IIO_INTENSITY) + return -EINVAL; + return veml6046x00_single_read(iio, chan->channel2, val); + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + return veml6046x00_get_it_usec(data, val2); + case IIO_CHAN_INFO_SCALE: + return veml6046x00_get_scale(data, val, val2); + default: + return -EINVAL; + } +} + +static int veml6046x00_read_avail(struct iio_dev *iio, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct veml6046x00_data *data = iio_priv(iio); + int it_idx; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + *vals = (int *)&veml6046x00_it; + *length = 2 * ARRAY_SIZE(veml6046x00_it); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SCALE: + it_idx = veml6046x00_get_it_index(data); + if (it_idx < 0) + return it_idx; + *vals = (int *)&veml6046x00_it_gains[it_idx]; + *length = 2 * ARRAY_SIZE(veml6046x00_it_gains[it_idx]); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int veml6046x00_write_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return veml6046x00_set_it(iio, val, val2); + case IIO_CHAN_INFO_SCALE: + return veml6046x00_set_scale(iio, val, val2); + default: + return -EINVAL; + } +} + +static const struct iio_info veml6046x00_info_no_irq = { + .read_raw = veml6046x00_read_raw, + .read_avail = veml6046x00_read_avail, + .write_raw = veml6046x00_write_raw, +}; + +static int veml6046x00_buffer_preenable(struct iio_dev *iio) +{ + struct veml6046x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = regmap_field_write(data->rf.mode, 0); + if (ret) { + dev_err(dev, "Failed to set mode %d\n", ret); + return ret; + } + + ret = regmap_field_write(data->rf.trig, 0); + if (ret) { + /* + * no unrolling of mode as it is set appropriately with next + * single read. + */ + dev_err(dev, "Failed to set trigger %d\n", ret); + return ret; + } + + return pm_runtime_resume_and_get(dev); +} + +static int veml6046x00_buffer_postdisable(struct iio_dev *iio) +{ + struct veml6046x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = regmap_field_write(data->rf.mode, 1); + if (ret) { + dev_err(dev, "Failed to set mode %d\n", ret); + return ret; + } + + pm_runtime_put_autosuspend(dev); + + return 0; +} + +static const struct iio_buffer_setup_ops veml6046x00_buffer_setup_ops = { + .preenable = veml6046x00_buffer_preenable, + .postdisable = veml6046x00_buffer_postdisable, +}; + +static irqreturn_t veml6046x00_trig_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *iio = pf->indio_dev; + struct veml6046x00_data *data = iio_priv(iio); + int ret; + struct { + __le16 chans[4]; + aligned_s64 timestamp; + } scan; + + ret = regmap_bulk_read(data->regmap, VEML6046X00_REG_R, + &scan.chans, sizeof(scan.chans)); + if (ret) + goto done; + + iio_push_to_buffers_with_ts(iio, &scan, sizeof(scan), + iio_get_time_ns(iio)); + +done: + iio_trigger_notify_done(iio->trig); + + return IRQ_HANDLED; +} + +static int veml6046x00_validate_part_id(struct veml6046x00_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + unsigned int part_id; + int ret; + __le16 reg; + + ret = regmap_bulk_read(data->regmap, VEML6046X00_REG_ID, + ®, sizeof(reg)); + if (ret) + return dev_err_probe(dev, ret, "Failed to read ID\n"); + + part_id = le16_to_cpu(reg); + if (part_id != 0x01) + dev_info(dev, "Unknown ID %#04x\n", part_id); + + return 0; +} + +static int veml6046x00_setup_device(struct iio_dev *iio) +{ + struct veml6046x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + int ret; + __le16 reg16; + + reg16 = cpu_to_le16(VEML6046X00_CONF0_AF); + ret = regmap_bulk_write(data->regmap, VEML6046X00_REG_CONF0, + ®16, sizeof(reg16)); + if (ret) + return dev_err_probe(dev, ret, "Failed to set configuration\n"); + + reg16 = cpu_to_le16(0); + ret = regmap_bulk_write(data->regmap, VEML6046X00_REG_THDL, + ®16, sizeof(reg16)); + if (ret) + return dev_err_probe(dev, ret, "Failed to set low threshold\n"); + + reg16 = cpu_to_le16(U16_MAX); + ret = regmap_bulk_write(data->regmap, VEML6046X00_REG_THDH, + ®16, sizeof(reg16)); + if (ret) + return dev_err_probe(dev, ret, "Failed to set high threshold\n"); + + ret = regmap_bulk_read(data->regmap, VEML6046X00_REG_INT, + ®16, sizeof(reg16)); + if (ret) + return dev_err_probe(dev, ret, "Failed to clear interrupts\n"); + + return 0; +} + +static int veml6046x00_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct veml6046x00_data *data; + struct iio_dev *iio; + struct regmap *regmap; + int ret; + + regmap = devm_regmap_init_i2c(i2c, &veml6046x00_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Failed to set regmap\n"); + + iio = devm_iio_device_alloc(dev, sizeof(*data)); + if (!iio) + return -ENOMEM; + + data = iio_priv(iio); + /* struct iio_dev is retrieved via dev_get_drvdata(). */ + i2c_set_clientdata(i2c, iio); + data->regmap = regmap; + + ret = veml6046x00_regfield_init(data); + if (ret) + return dev_err_probe(dev, ret, "Failed to init regfield\n"); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulator\n"); + + /* bring device in a known state and switch device on */ + ret = veml6046x00_setup_device(iio); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(dev, veml6046x00_shutdown_action, data); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to add shut down action\n"); + + ret = pm_runtime_set_active(dev); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to activate PM runtime\n"); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable PM runtime\n"); + + pm_runtime_get_noresume(dev); + pm_runtime_set_autosuspend_delay(dev, VEML6046X00_AUTOSUSPEND_MS); + pm_runtime_use_autosuspend(dev); + + ret = veml6046x00_validate_part_id(data); + if (ret) + return dev_err_probe(dev, ret, "Failed to validate device ID\n"); + + iio->name = "veml6046x00"; + iio->channels = veml6046x00_channels; + iio->num_channels = ARRAY_SIZE(veml6046x00_channels); + iio->modes = INDIO_DIRECT_MODE; + + iio->info = &veml6046x00_info_no_irq; + + ret = devm_iio_triggered_buffer_setup(dev, iio, NULL, + veml6046x00_trig_handler, + &veml6046x00_buffer_setup_ops); + if (ret) + return dev_err_probe(dev, ret, + "Failed to register triggered buffer"); + + pm_runtime_put_autosuspend(dev); + + ret = devm_iio_device_register(dev, iio); + if (ret) + return dev_err_probe(dev, ret, "Failed to register iio device"); + + return 0; +} + +static int veml6046x00_runtime_suspend(struct device *dev) +{ + struct veml6046x00_data *data = iio_priv(dev_get_drvdata(dev)); + + return veml6046x00_shutdown(data); +} + +static int veml6046x00_runtime_resume(struct device *dev) +{ + struct veml6046x00_data *data = iio_priv(dev_get_drvdata(dev)); + + return veml6046x00_power_on(data); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(veml6046x00_pm_ops, + veml6046x00_runtime_suspend, + veml6046x00_runtime_resume, NULL); + +static const struct of_device_id veml6046x00_of_match[] = { + { .compatible = "vishay,veml6046x00" }, + { } +}; +MODULE_DEVICE_TABLE(of, veml6046x00_of_match); + +static const struct i2c_device_id veml6046x00_id[] = { + { .name = "veml6046x00" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, veml6046x00_id); + +static struct i2c_driver veml6046x00_driver = { + .driver = { + .name = "veml6046x00", + .of_match_table = veml6046x00_of_match, + .pm = pm_ptr(&veml6046x00_pm_ops), + }, + .probe = veml6046x00_probe, + .id_table = veml6046x00_id, +}; +module_i2c_driver(veml6046x00_driver); + +MODULE_AUTHOR("Andreas Klinger "); +MODULE_DESCRIPTION("VEML6046X00 RGBIR Color Sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/veml6070.c b/drivers/iio/light/veml6070.c new file mode 100644 index 000000000..c99336bd9 --- /dev/null +++ b/drivers/iio/light/veml6070.c @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * veml6070.c - Support for Vishay VEML6070 UV A light sensor + * + * Copyright 2016 Peter Meerwald-Stadler + * + * IIO driver for VEML6070 (7-bit I2C slave addresses 0x38 and 0x39) + * + * TODO: ACK signal + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define VEML6070_DRV_NAME "veml6070" + +#define VEML6070_ADDR_CONFIG_DATA_MSB 0x38 /* read: MSB data, write: config */ +#define VEML6070_ADDR_DATA_LSB 0x39 /* LSB data */ + +#define VEML6070_COMMAND_ACK BIT(5) /* raise interrupt when over threshold */ +#define VEML6070_COMMAND_IT GENMASK(3, 2) /* bit mask integration time */ +#define VEML6070_COMMAND_RSRVD BIT(1) /* reserved, set to 1 */ +#define VEML6070_COMMAND_SD BIT(0) /* shutdown mode when set */ + +#define VEML6070_IT_05 0x00 +#define VEML6070_IT_10 0x01 +#define VEML6070_IT_20 0x02 +#define VEML6070_IT_40 0x03 + +#define VEML6070_MIN_RSET_KOHM 75 +#define VEML6070_MIN_IT_US 15625 /* Rset = 75 kohm, IT = 1/2 */ + +struct veml6070_data { + struct i2c_client *client1; + struct i2c_client *client2; + u8 config; + struct mutex lock; + u32 rset; + int it[4][2]; +}; + +static int veml6070_calc_it(struct device *dev, struct veml6070_data *data) +{ + int i, tmp_it; + + data->rset = 270000; + device_property_read_u32(dev, "vishay,rset-ohms", &data->rset); + + if (data->rset < 75000 || data->rset > 1200000) + return dev_err_probe(dev, -EINVAL, "Rset out of range\n"); + + /* + * convert to kohm to avoid overflows and work with the same units as + * in the datasheet and simplify UVI operations. + */ + data->rset /= KILO; + + tmp_it = VEML6070_MIN_IT_US * data->rset / VEML6070_MIN_RSET_KOHM; + for (i = 0; i < ARRAY_SIZE(data->it); i++) { + data->it[i][0] = (tmp_it << i) / MICRO; + data->it[i][1] = (tmp_it << i) % MICRO; + } + + return 0; +} + +static int veml6070_get_it(struct veml6070_data *data, int *val, int *val2) +{ + int it_idx = FIELD_GET(VEML6070_COMMAND_IT, data->config); + + *val = data->it[it_idx][0]; + *val2 = data->it[it_idx][1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int veml6070_set_it(struct veml6070_data *data, int val, int val2) +{ + int it_idx; + + for (it_idx = 0; it_idx < ARRAY_SIZE(data->it); it_idx++) { + if (data->it[it_idx][0] == val && data->it[it_idx][1] == val2) + break; + } + + if (it_idx >= ARRAY_SIZE(data->it)) + return -EINVAL; + + data->config = (data->config & ~VEML6070_COMMAND_IT) | + FIELD_PREP(VEML6070_COMMAND_IT, it_idx); + + return i2c_smbus_write_byte(data->client1, data->config); +} + +static int veml6070_read(struct veml6070_data *data) +{ + int ret, it_ms, val, val2; + u8 msb, lsb; + + guard(mutex)(&data->lock); + + /* disable shutdown */ + ret = i2c_smbus_write_byte(data->client1, + data->config & ~VEML6070_COMMAND_SD); + if (ret < 0) + return ret; + + veml6070_get_it(data, &val, &val2); + it_ms = val * MILLI + val2 / (MICRO / MILLI); + msleep(it_ms + 10); + + ret = i2c_smbus_read_byte(data->client2); /* read MSB, address 0x39 */ + if (ret < 0) + return ret; + + msb = ret; + + ret = i2c_smbus_read_byte(data->client1); /* read LSB, address 0x38 */ + if (ret < 0) + return ret; + + lsb = ret; + + /* shutdown again */ + ret = i2c_smbus_write_byte(data->client1, data->config); + if (ret < 0) + return ret; + + return (msb << 8) | lsb; +} + +static const struct iio_chan_spec veml6070_channels[] = { + { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_UV, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), + }, + { + .type = IIO_UVINDEX, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), + } +}; + +static int veml6070_to_uv_index(struct veml6070_data *data, unsigned int val) +{ + /* + * conversion of raw UV intensity values to UV index depends on + * integration time (IT) and value of the resistor connected to + * the RSET pin. + */ + unsigned int uvi[11] = { + 187, 373, 560, /* low */ + 746, 933, 1120, /* moderate */ + 1308, 1494, /* high */ + 1681, 1868, 2054}; /* very high */ + int i, it_idx; + + it_idx = FIELD_GET(VEML6070_COMMAND_IT, data->config); + + if (!it_idx) + val = (val * 270 / data->rset) << 1; + else + val = (val * 270 / data->rset) >> (it_idx - 1); + + for (i = 0; i < ARRAY_SIZE(uvi); i++) + if (val <= uvi[i]) + return i; + + return 11; /* extreme */ +} + +static int veml6070_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct veml6070_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + ret = veml6070_read(data); + if (ret < 0) + return ret; + if (mask == IIO_CHAN_INFO_PROCESSED) + *val = veml6070_to_uv_index(data, ret); + else + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + return veml6070_get_it(data, val, val2); + default: + return -EINVAL; + } +} + +static int veml6070_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct veml6070_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + *vals = (int *)data->it; + *length = 2 * ARRAY_SIZE(data->it); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int veml6070_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct veml6070_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return veml6070_set_it(data, val, val2); + default: + return -EINVAL; + } +} + +static const struct iio_info veml6070_info = { + .read_raw = veml6070_read_raw, + .read_avail = veml6070_read_avail, + .write_raw = veml6070_write_raw, +}; + +static int veml6070_probe(struct i2c_client *client) +{ + struct veml6070_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client1 = client; + mutex_init(&data->lock); + + indio_dev->info = &veml6070_info; + indio_dev->channels = veml6070_channels; + indio_dev->num_channels = ARRAY_SIZE(veml6070_channels); + indio_dev->name = VEML6070_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = veml6070_calc_it(&client->dev, data); + if (ret < 0) + return ret; + + ret = devm_regulator_get_enable(&client->dev, "vdd"); + if (ret < 0) + return ret; + + data->client2 = devm_i2c_new_dummy_device(&client->dev, client->adapter, + VEML6070_ADDR_DATA_LSB); + if (IS_ERR(data->client2)) + return dev_err_probe(&client->dev, PTR_ERR(data->client2), + "i2c device for second chip address failed\n"); + + data->config = FIELD_PREP(VEML6070_COMMAND_IT, VEML6070_IT_10) | + VEML6070_COMMAND_RSRVD | VEML6070_COMMAND_SD; + ret = i2c_smbus_write_byte(data->client1, data->config); + if (ret < 0) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id veml6070_id[] = { + { .name = "veml6070" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, veml6070_id); + +static const struct of_device_id veml6070_of_match[] = { + { .compatible = "vishay,veml6070" }, + { } +}; +MODULE_DEVICE_TABLE(of, veml6070_of_match); + +static struct i2c_driver veml6070_driver = { + .driver = { + .name = VEML6070_DRV_NAME, + .of_match_table = veml6070_of_match, + }, + .probe = veml6070_probe, + .id_table = veml6070_id, +}; + +module_i2c_driver(veml6070_driver); + +MODULE_AUTHOR("Peter Meerwald-Stadler "); +MODULE_DESCRIPTION("Vishay VEML6070 UV A light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/veml6075.c b/drivers/iio/light/veml6075.c new file mode 100644 index 000000000..59187244a --- /dev/null +++ b/drivers/iio/light/veml6075.c @@ -0,0 +1,482 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Vishay VEML6075 UVA and UVB light sensor + * + * Copyright 2023 Javier Carrasco + * + * 7-bit I2C slave, address 0x10 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define VEML6075_CMD_CONF 0x00 /* configuration register */ +#define VEML6075_CMD_UVA 0x07 /* UVA channel */ +#define VEML6075_CMD_UVB 0x09 /* UVB channel */ +#define VEML6075_CMD_COMP1 0x0A /* visible light compensation */ +#define VEML6075_CMD_COMP2 0x0B /* infrarred light compensation */ +#define VEML6075_CMD_ID 0x0C /* device ID */ + +#define VEML6075_CONF_IT GENMASK(6, 4) /* intregration time */ +#define VEML6075_CONF_HD BIT(3) /* dynamic setting */ +#define VEML6075_CONF_TRIG BIT(2) /* trigger */ +#define VEML6075_CONF_AF BIT(1) /* active force enable */ +#define VEML6075_CONF_SD BIT(0) /* shutdown */ + +#define VEML6075_IT_50_MS 0x00 +#define VEML6075_IT_100_MS 0x01 +#define VEML6075_IT_200_MS 0x02 +#define VEML6075_IT_400_MS 0x03 +#define VEML6075_IT_800_MS 0x04 + +#define VEML6075_AF_DISABLE 0x00 +#define VEML6075_AF_ENABLE 0x01 + +#define VEML6075_SD_DISABLE 0x00 +#define VEML6075_SD_ENABLE 0x01 + +/* Open-air coefficients and responsivity */ +#define VEML6075_A_COEF 2220 +#define VEML6075_B_COEF 1330 +#define VEML6075_C_COEF 2950 +#define VEML6075_D_COEF 1740 +#define VEML6075_UVA_RESP 1461 +#define VEML6075_UVB_RESP 2591 + +static const int veml6075_it_ms[] = { 50, 100, 200, 400, 800 }; + +struct veml6075_data { + struct i2c_client *client; + struct regmap *regmap; + /* + * prevent integration time modification and triggering + * measurements while a measurement is underway. + */ + struct mutex lock; +}; + +/* channel number */ +enum veml6075_chan { + CH_UVA, + CH_UVB, +}; + +static const struct iio_chan_spec veml6075_channels[] = { + { + .type = IIO_INTENSITY, + .channel = CH_UVA, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_UVA, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), + }, + { + .type = IIO_INTENSITY, + .channel = CH_UVB, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_UVB, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), + }, + { + .type = IIO_UVINDEX, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), + }, +}; + +static int veml6075_request_measurement(struct veml6075_data *data) +{ + int ret, conf, int_time, int_index; + + ret = regmap_read(data->regmap, VEML6075_CMD_CONF, &conf); + if (ret < 0) + return ret; + + /* disable shutdown and trigger measurement */ + ret = regmap_write(data->regmap, VEML6075_CMD_CONF, + (conf | VEML6075_CONF_TRIG) & ~VEML6075_CONF_SD); + if (ret < 0) + return ret; + + /* + * A measurement requires between 1.30 and 1.40 times the integration + * time for all possible configurations. Using a 1.50 factor simplifies + * operations and ensures reliability under all circumstances. + */ + int_index = FIELD_GET(VEML6075_CONF_IT, conf); + if (int_index >= ARRAY_SIZE(veml6075_it_ms)) + return -EINVAL; + + int_time = veml6075_it_ms[int_index]; + msleep(int_time + (int_time / 2)); + + /* shutdown again, data registers are still accessible */ + return regmap_update_bits(data->regmap, VEML6075_CMD_CONF, + VEML6075_CONF_SD, VEML6075_CONF_SD); +} + +static int veml6075_uva_comp(int raw_uva, int comp1, int comp2) +{ + int comp1a_c, comp2a_c, uva_comp; + + comp1a_c = (comp1 * VEML6075_A_COEF) / 1000U; + comp2a_c = (comp2 * VEML6075_B_COEF) / 1000U; + uva_comp = raw_uva - comp1a_c - comp2a_c; + + return clamp_val(uva_comp, 0, U16_MAX); +} + +static int veml6075_uvb_comp(int raw_uvb, int comp1, int comp2) +{ + int comp1b_c, comp2b_c, uvb_comp; + + comp1b_c = (comp1 * VEML6075_C_COEF) / 1000U; + comp2b_c = (comp2 * VEML6075_D_COEF) / 1000U; + uvb_comp = raw_uvb - comp1b_c - comp2b_c; + + return clamp_val(uvb_comp, 0, U16_MAX); +} + +static int veml6075_read_comp(struct veml6075_data *data, int *c1, int *c2) +{ + int ret; + + ret = regmap_read(data->regmap, VEML6075_CMD_COMP1, c1); + if (ret < 0) + return ret; + + return regmap_read(data->regmap, VEML6075_CMD_COMP2, c2); +} + +static int veml6075_read_uv_direct(struct veml6075_data *data, int chan, + int *val) +{ + int c1, c2, ret; + + guard(mutex)(&data->lock); + + ret = veml6075_request_measurement(data); + if (ret < 0) + return ret; + + ret = veml6075_read_comp(data, &c1, &c2); + if (ret < 0) + return ret; + + switch (chan) { + case CH_UVA: + ret = regmap_read(data->regmap, VEML6075_CMD_UVA, val); + if (ret < 0) + return ret; + + *val = veml6075_uva_comp(*val, c1, c2); + return IIO_VAL_INT; + case CH_UVB: + ret = regmap_read(data->regmap, VEML6075_CMD_UVB, val); + if (ret < 0) + return ret; + + *val = veml6075_uvb_comp(*val, c1, c2); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int veml6075_read_int_time_index(struct veml6075_data *data) +{ + int ret, conf, int_index; + + ret = regmap_read(data->regmap, VEML6075_CMD_CONF, &conf); + if (ret < 0) + return ret; + + int_index = FIELD_GET(VEML6075_CONF_IT, conf); + if (int_index >= ARRAY_SIZE(veml6075_it_ms)) + return -EINVAL; + + return int_index; +} + +static int veml6075_read_int_time_ms(struct veml6075_data *data, int *val) +{ + int int_index; + + guard(mutex)(&data->lock); + int_index = veml6075_read_int_time_index(data); + if (int_index < 0) + return int_index; + + *val = veml6075_it_ms[int_index]; + + return IIO_VAL_INT; +} + +static int veml6075_get_uvi_micro(struct veml6075_data *data, int uva_comp, + int uvb_comp) +{ + int uvia_micro = uva_comp * VEML6075_UVA_RESP; + int uvib_micro = uvb_comp * VEML6075_UVB_RESP; + int int_index; + + int_index = veml6075_read_int_time_index(data); + if (int_index < 0) + return int_index; + + switch (int_index) { + case VEML6075_IT_50_MS: + return uvia_micro + uvib_micro; + case VEML6075_IT_100_MS: + case VEML6075_IT_200_MS: + case VEML6075_IT_400_MS: + case VEML6075_IT_800_MS: + return (uvia_micro + uvib_micro) / (2 << int_index); + default: + return -EINVAL; + } +} + +static int veml6075_read_uvi(struct veml6075_data *data, int *val, int *val2) +{ + int ret, c1, c2, uva, uvb, uvi_micro; + + guard(mutex)(&data->lock); + + ret = veml6075_request_measurement(data); + if (ret < 0) + return ret; + + ret = veml6075_read_comp(data, &c1, &c2); + if (ret < 0) + return ret; + + ret = regmap_read(data->regmap, VEML6075_CMD_UVA, &uva); + if (ret < 0) + return ret; + + ret = regmap_read(data->regmap, VEML6075_CMD_UVB, &uvb); + if (ret < 0) + return ret; + + uvi_micro = veml6075_get_uvi_micro(data, veml6075_uva_comp(uva, c1, c2), + veml6075_uvb_comp(uvb, c1, c2)); + if (uvi_micro < 0) + return uvi_micro; + + *val = uvi_micro / MICRO; + *val2 = uvi_micro % MICRO; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int veml6075_read_responsivity(int chan, int *val, int *val2) +{ + /* scale = 1 / resp */ + switch (chan) { + case CH_UVA: + /* resp = 0.93 c/uW/cm2: scale = 1.75268817 */ + *val = 1; + *val2 = 75268817; + return IIO_VAL_INT_PLUS_NANO; + case CH_UVB: + /* resp = 2.1 c/uW/cm2: scale = 0.476190476 */ + *val = 0; + *val2 = 476190476; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static int veml6075_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + *length = ARRAY_SIZE(veml6075_it_ms); + *vals = veml6075_it_ms; + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + + default: + return -EINVAL; + } +} + +static int veml6075_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct veml6075_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return veml6075_read_uv_direct(data, chan->channel, val); + case IIO_CHAN_INFO_PROCESSED: + return veml6075_read_uvi(data, val, val2); + case IIO_CHAN_INFO_INT_TIME: + return veml6075_read_int_time_ms(data, val); + case IIO_CHAN_INFO_SCALE: + return veml6075_read_responsivity(chan->channel, val, val2); + default: + return -EINVAL; + } +} + +static int veml6075_write_int_time_ms(struct veml6075_data *data, int val) +{ + int i = ARRAY_SIZE(veml6075_it_ms); + + guard(mutex)(&data->lock); + + while (i-- > 0) { + if (val == veml6075_it_ms[i]) + break; + } + if (i < 0) + return -EINVAL; + + return regmap_update_bits(data->regmap, VEML6075_CMD_CONF, + VEML6075_CONF_IT, + FIELD_PREP(VEML6075_CONF_IT, i)); +} + +static int veml6075_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct veml6075_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return veml6075_write_int_time_ms(data, val); + default: + return -EINVAL; + } +} + +static const struct iio_info veml6075_info = { + .read_avail = veml6075_read_avail, + .read_raw = veml6075_read_raw, + .write_raw = veml6075_write_raw, +}; + +static bool veml6075_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case VEML6075_CMD_CONF: + case VEML6075_CMD_UVA: + case VEML6075_CMD_UVB: + case VEML6075_CMD_COMP1: + case VEML6075_CMD_COMP2: + case VEML6075_CMD_ID: + return true; + default: + return false; + } +} + +static bool veml6075_writable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case VEML6075_CMD_CONF: + return true; + default: + return false; + } +} + +static const struct regmap_config veml6075_regmap_config = { + .name = "veml6075", + .reg_bits = 8, + .val_bits = 16, + .max_register = VEML6075_CMD_ID, + .readable_reg = veml6075_readable_reg, + .writeable_reg = veml6075_writable_reg, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + +static int veml6075_probe(struct i2c_client *client) +{ + struct veml6075_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + int config, ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, &veml6075_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + data = iio_priv(indio_dev); + data->client = client; + data->regmap = regmap; + + mutex_init(&data->lock); + + indio_dev->name = "veml6075"; + indio_dev->info = &veml6075_info; + indio_dev->channels = veml6075_channels; + indio_dev->num_channels = ARRAY_SIZE(veml6075_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_regulator_get_enable(&client->dev, "vdd"); + if (ret < 0) + return ret; + + /* default: 100ms integration time, active force enable, shutdown */ + config = FIELD_PREP(VEML6075_CONF_IT, VEML6075_IT_100_MS) | + FIELD_PREP(VEML6075_CONF_AF, VEML6075_AF_ENABLE) | + FIELD_PREP(VEML6075_CONF_SD, VEML6075_SD_ENABLE); + ret = regmap_write(data->regmap, VEML6075_CMD_CONF, config); + if (ret < 0) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id veml6075_id[] = { + { .name = "veml6075" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, veml6075_id); + +static const struct of_device_id veml6075_of_match[] = { + { .compatible = "vishay,veml6075" }, + { } +}; +MODULE_DEVICE_TABLE(of, veml6075_of_match); + +static struct i2c_driver veml6075_driver = { + .driver = { + .name = "veml6075", + .of_match_table = veml6075_of_match, + }, + .probe = veml6075_probe, + .id_table = veml6075_id, +}; + +module_i2c_driver(veml6075_driver); + +MODULE_AUTHOR("Javier Carrasco "); +MODULE_DESCRIPTION("Vishay VEML6075 UVA and UVB light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/vl6180.c b/drivers/iio/light/vl6180.c new file mode 100644 index 000000000..aef8a5333 --- /dev/null +++ b/drivers/iio/light/vl6180.c @@ -0,0 +1,771 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * vl6180.c - Support for STMicroelectronics VL6180 ALS, range and proximity + * sensor + * + * Copyright 2017 Peter Meerwald-Stadler + * Copyright 2017 Manivannan Sadhasivam + * + * IIO driver for VL6180 (7-bit I2C slave address 0x29) + * + * Range: 0 to 100mm + * ALS: < 1 Lux up to 100 kLux + * IR: 850nm + * + * TODO: irq, threshold events, continuous mode, hardware buffer + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define VL6180_DRV_NAME "vl6180" + +/* Device identification register and value */ +#define VL6180_MODEL_ID 0x000 +#define VL6180_MODEL_ID_VAL 0xb4 + +/* Configuration registers */ +#define VL6180_INTR_CONFIG 0x014 +#define VL6180_INTR_CLEAR 0x015 +#define VL6180_OUT_OF_RESET 0x016 +#define VL6180_HOLD 0x017 +#define VL6180_RANGE_START 0x018 +#define VL6180_RANGE_INTER_MEAS_TIME 0x01b +#define VL6180_ALS_START 0x038 +#define VL6180_ALS_INTER_MEAS_TIME 0x03e +#define VL6180_ALS_GAIN 0x03f +#define VL6180_ALS_IT 0x040 + +/* Status registers */ +#define VL6180_RANGE_STATUS 0x04d +#define VL6180_ALS_STATUS 0x04e +#define VL6180_INTR_STATUS 0x04f + +/* Result value registers */ +#define VL6180_ALS_VALUE 0x050 +#define VL6180_RANGE_VALUE 0x062 +#define VL6180_RANGE_RATE 0x066 + +/* bits of the RANGE_START and ALS_START register */ +#define VL6180_MODE_CONT BIT(1) /* continuous mode */ +#define VL6180_STARTSTOP BIT(0) /* start measurement, auto-reset */ + +/* bits of the INTR_STATUS and INTR_CONFIG register */ +#define VL6180_ALS_READY BIT(5) +#define VL6180_RANGE_READY BIT(2) + +/* bits of the INTR_CLEAR register */ +#define VL6180_CLEAR_ERROR BIT(2) +#define VL6180_CLEAR_ALS BIT(1) +#define VL6180_CLEAR_RANGE BIT(0) + +/* bits of the HOLD register */ +#define VL6180_HOLD_ON BIT(0) + +/* default value for the ALS_IT register */ +#define VL6180_ALS_IT_100 0x63 /* 100 ms */ + +/* values for the ALS_GAIN register */ +#define VL6180_ALS_GAIN_1 0x46 +#define VL6180_ALS_GAIN_1_25 0x45 +#define VL6180_ALS_GAIN_1_67 0x44 +#define VL6180_ALS_GAIN_2_5 0x43 +#define VL6180_ALS_GAIN_5 0x42 +#define VL6180_ALS_GAIN_10 0x41 +#define VL6180_ALS_GAIN_20 0x40 +#define VL6180_ALS_GAIN_40 0x47 + +struct vl6180_data { + struct i2c_client *client; + struct mutex lock; + struct completion completion; + struct iio_trigger *trig; + unsigned int als_gain_milli; + unsigned int als_it_ms; + unsigned int als_meas_rate; + unsigned int range_meas_rate; +}; + +enum { VL6180_ALS, VL6180_RANGE, VL6180_PROX }; + +/** + * struct vl6180_chan_regs - Registers for accessing channels + * @drdy_mask: Data ready bit in status register + * @start_reg: Conversion start register + * @value_reg: Result value register + * @word: Register word length + */ +struct vl6180_chan_regs { + u8 drdy_mask; + u16 start_reg, value_reg; + bool word; +}; + +static const struct vl6180_chan_regs vl6180_chan_regs_table[] = { + [VL6180_ALS] = { + .drdy_mask = VL6180_ALS_READY, + .start_reg = VL6180_ALS_START, + .value_reg = VL6180_ALS_VALUE, + .word = true, + }, + [VL6180_RANGE] = { + .drdy_mask = VL6180_RANGE_READY, + .start_reg = VL6180_RANGE_START, + .value_reg = VL6180_RANGE_VALUE, + .word = false, + }, + [VL6180_PROX] = { + .drdy_mask = VL6180_RANGE_READY, + .start_reg = VL6180_RANGE_START, + .value_reg = VL6180_RANGE_RATE, + .word = true, + }, +}; + +static int vl6180_read(struct i2c_client *client, u16 cmd, void *databuf, + u8 len) +{ + __be16 cmdbuf = cpu_to_be16(cmd); + struct i2c_msg msgs[2] = { + { .addr = client->addr, .len = sizeof(cmdbuf), .buf = (u8 *) &cmdbuf }, + { .addr = client->addr, .len = len, .buf = databuf, + .flags = I2C_M_RD } }; + int ret; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + dev_err(&client->dev, "failed reading register 0x%04x\n", cmd); + + return ret; +} + +static int vl6180_read_byte(struct i2c_client *client, u16 cmd) +{ + u8 data; + int ret; + + ret = vl6180_read(client, cmd, &data, sizeof(data)); + if (ret < 0) + return ret; + + return data; +} + +static int vl6180_read_word(struct i2c_client *client, u16 cmd) +{ + __be16 data; + int ret; + + ret = vl6180_read(client, cmd, &data, sizeof(data)); + if (ret < 0) + return ret; + + return be16_to_cpu(data); +} + +static int vl6180_write_byte(struct i2c_client *client, u16 cmd, u8 val) +{ + u8 buf[3]; + struct i2c_msg msgs[1] = { + { .addr = client->addr, .len = sizeof(buf), .buf = (u8 *) &buf } }; + int ret; + + buf[0] = cmd >> 8; + buf[1] = cmd & 0xff; + buf[2] = val; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) { + dev_err(&client->dev, "failed writing register 0x%04x\n", cmd); + return ret; + } + + return 0; +} + +static int vl6180_write_word(struct i2c_client *client, u16 cmd, u16 val) +{ + __be16 buf[2]; + struct i2c_msg msgs[1] = { + { .addr = client->addr, .len = sizeof(buf), .buf = (u8 *) &buf } }; + int ret; + + buf[0] = cpu_to_be16(cmd); + buf[1] = cpu_to_be16(val); + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) { + dev_err(&client->dev, "failed writing register 0x%04x\n", cmd); + return ret; + } + + return 0; +} + +static int vl6180_measure(struct vl6180_data *data, int addr) +{ + struct i2c_client *client = data->client; + unsigned long time_left; + int tries = 20, ret; + u16 value; + + mutex_lock(&data->lock); + reinit_completion(&data->completion); + + /* Start single shot measurement */ + ret = vl6180_write_byte(client, + vl6180_chan_regs_table[addr].start_reg, VL6180_STARTSTOP); + if (ret < 0) + goto fail; + + if (client->irq) { + time_left = wait_for_completion_timeout(&data->completion, HZ / 10); + if (time_left == 0) { + ret = -ETIMEDOUT; + goto fail; + } + } else { + while (tries--) { + ret = vl6180_read_byte(client, VL6180_INTR_STATUS); + if (ret < 0) + goto fail; + + if (ret & vl6180_chan_regs_table[addr].drdy_mask) + break; + msleep(20); + } + + if (tries < 0) { + ret = -EIO; + goto fail; + } + } + + /* Read result value from appropriate registers */ + ret = vl6180_chan_regs_table[addr].word ? + vl6180_read_word(client, vl6180_chan_regs_table[addr].value_reg) : + vl6180_read_byte(client, vl6180_chan_regs_table[addr].value_reg); + if (ret < 0) + goto fail; + value = ret; + + /* Clear the interrupt flag after data read */ + ret = vl6180_write_byte(client, VL6180_INTR_CLEAR, + VL6180_CLEAR_ERROR | VL6180_CLEAR_ALS | VL6180_CLEAR_RANGE); + if (ret < 0) + goto fail; + + ret = value; + +fail: + mutex_unlock(&data->lock); + + return ret; +} + +static const struct iio_chan_spec vl6180_channels[] = { + { + .type = IIO_LIGHT, + .address = VL6180_ALS, + .scan_index = VL6180_ALS, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + }, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_HARDWAREGAIN) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + }, { + .type = IIO_DISTANCE, + .address = VL6180_RANGE, + .scan_index = VL6180_RANGE, + .scan_type = { + .sign = 'u', + .realbits = 8, + .storagebits = 8, + }, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + }, { + .type = IIO_PROXIMITY, + .address = VL6180_PROX, + .scan_index = VL6180_PROX, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + }, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +/* + * Available Ambient Light Sensor gain settings, 1/1000th, and + * corresponding setting for the VL6180_ALS_GAIN register + */ +static const int vl6180_als_gain_tab[8] = { + 1000, 1250, 1670, 2500, 5000, 10000, 20000, 40000 +}; +static const u8 vl6180_als_gain_tab_bits[8] = { + VL6180_ALS_GAIN_1, VL6180_ALS_GAIN_1_25, + VL6180_ALS_GAIN_1_67, VL6180_ALS_GAIN_2_5, + VL6180_ALS_GAIN_5, VL6180_ALS_GAIN_10, + VL6180_ALS_GAIN_20, VL6180_ALS_GAIN_40 +}; + +static int vl6180_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct vl6180_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = vl6180_measure(data, chan->address); + if (ret < 0) + return ret; + *val = ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + *val = data->als_it_ms; + *val2 = 1000; + + return IIO_VAL_FRACTIONAL; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_LIGHT: + /* one ALS count is 0.32 Lux @ gain 1, IT 100 ms */ + *val = 32000; /* 0.32 * 1000 * 100 */ + *val2 = data->als_gain_milli * data->als_it_ms; + + return IIO_VAL_FRACTIONAL; + + case IIO_DISTANCE: + *val = 0; /* sensor reports mm, scale to meter */ + *val2 = 1000; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_HARDWAREGAIN: + *val = data->als_gain_milli; + *val2 = 1000; + + return IIO_VAL_FRACTIONAL; + + case IIO_CHAN_INFO_SAMP_FREQ: + switch (chan->type) { + case IIO_DISTANCE: + *val = data->range_meas_rate; + return IIO_VAL_INT; + case IIO_LIGHT: + *val = data->als_meas_rate; + return IIO_VAL_INT; + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +static IIO_CONST_ATTR(als_gain_available, "1 1.25 1.67 2.5 5 10 20 40"); + +static struct attribute *vl6180_attributes[] = { + &iio_const_attr_als_gain_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group vl6180_attribute_group = { + .attrs = vl6180_attributes, +}; + +/* HOLD is needed before updating any config registers */ +static int vl6180_hold(struct vl6180_data *data, bool hold) +{ + return vl6180_write_byte(data->client, VL6180_HOLD, + hold ? VL6180_HOLD_ON : 0); +} + +static int vl6180_set_als_gain(struct vl6180_data *data, int val, int val2) +{ + int i, ret, gain; + + if (val < 1 || val > 40) + return -EINVAL; + + gain = (val * 1000000 + val2) / 1000; + if (gain < 1 || gain > 40000) + return -EINVAL; + + i = find_closest(gain, vl6180_als_gain_tab, + ARRAY_SIZE(vl6180_als_gain_tab)); + + mutex_lock(&data->lock); + ret = vl6180_hold(data, true); + if (ret < 0) + goto fail; + + ret = vl6180_write_byte(data->client, VL6180_ALS_GAIN, + vl6180_als_gain_tab_bits[i]); + + if (ret >= 0) + data->als_gain_milli = vl6180_als_gain_tab[i]; + +fail: + vl6180_hold(data, false); + mutex_unlock(&data->lock); + return ret; +} + +static int vl6180_set_it(struct vl6180_data *data, int val, int val2) +{ + int ret, it_ms; + + it_ms = DIV_ROUND_CLOSEST(val2, 1000); /* round to ms */ + if (val != 0 || it_ms < 1 || it_ms > 512) + return -EINVAL; + + mutex_lock(&data->lock); + ret = vl6180_hold(data, true); + if (ret < 0) + goto fail; + + ret = vl6180_write_word(data->client, VL6180_ALS_IT, it_ms - 1); + + if (ret >= 0) + data->als_it_ms = it_ms; + +fail: + vl6180_hold(data, false); + mutex_unlock(&data->lock); + + return ret; +} + +static int vl6180_meas_reg_val_from_mhz(unsigned int mhz) +{ + unsigned int period = DIV_ROUND_CLOSEST(1000 * 1000, mhz); + unsigned int reg_val = 0; + + if (period > 10) + reg_val = period < 2550 ? (DIV_ROUND_CLOSEST(period, 10) - 1) : 254; + + return reg_val; +} + +static int vl6180_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct vl6180_data *data = iio_priv(indio_dev); + unsigned int reg_val; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return vl6180_set_it(data, val, val2); + + case IIO_CHAN_INFO_HARDWAREGAIN: + if (chan->type != IIO_LIGHT) + return -EINVAL; + + return vl6180_set_als_gain(data, val, val2); + + case IIO_CHAN_INFO_SAMP_FREQ: + { + guard(mutex)(&data->lock); + switch (chan->type) { + case IIO_DISTANCE: + data->range_meas_rate = val; + reg_val = vl6180_meas_reg_val_from_mhz(val); + return vl6180_write_byte(data->client, + VL6180_RANGE_INTER_MEAS_TIME, reg_val); + + case IIO_LIGHT: + data->als_meas_rate = val; + reg_val = vl6180_meas_reg_val_from_mhz(val); + return vl6180_write_byte(data->client, + VL6180_ALS_INTER_MEAS_TIME, reg_val); + + default: + return -EINVAL; + } + } + + default: + return -EINVAL; + } +} + +static irqreturn_t vl6180_threaded_irq(int irq, void *priv) +{ + struct iio_dev *indio_dev = priv; + struct vl6180_data *data = iio_priv(indio_dev); + + if (iio_buffer_enabled(indio_dev)) + iio_trigger_poll_nested(indio_dev->trig); + else + complete(&data->completion); + + return IRQ_HANDLED; +} + +static irqreturn_t vl6180_trigger_handler(int irq, void *priv) +{ + struct iio_poll_func *pf = priv; + struct iio_dev *indio_dev = pf->indio_dev; + struct vl6180_data *data = iio_priv(indio_dev); + s64 time_ns = iio_get_time_ns(indio_dev); + int ret, bit, i = 0; + struct { + u16 chan[2]; + aligned_s64 timestamp; + } scan = { }; + + + iio_for_each_active_channel(indio_dev, bit) { + if (vl6180_chan_regs_table[bit].word) + ret = vl6180_read_word(data->client, + vl6180_chan_regs_table[bit].value_reg); + else + ret = vl6180_read_byte(data->client, + vl6180_chan_regs_table[bit].value_reg); + + if (ret < 0) { + dev_err(&data->client->dev, + "failed to read from value regs: %d\n", ret); + return IRQ_HANDLED; + } + + scan.chan[i++] = ret; + } + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), time_ns); + iio_trigger_notify_done(indio_dev->trig); + + /* Clear the interrupt flag after data read */ + ret = vl6180_write_byte(data->client, VL6180_INTR_CLEAR, + VL6180_CLEAR_ERROR | VL6180_CLEAR_ALS | VL6180_CLEAR_RANGE); + if (ret < 0) + dev_err(&data->client->dev, "failed to clear irq: %d\n", ret); + + return IRQ_HANDLED; +} + +static const struct iio_info vl6180_info = { + .read_raw = vl6180_read_raw, + .write_raw = vl6180_write_raw, + .attrs = &vl6180_attribute_group, + .validate_trigger = iio_validate_own_trigger, +}; + +static int vl6180_buffer_postenable(struct iio_dev *indio_dev) +{ + struct vl6180_data *data = iio_priv(indio_dev); + int bit; + + iio_for_each_active_channel(indio_dev, bit) + return vl6180_write_byte(data->client, + vl6180_chan_regs_table[bit].start_reg, + VL6180_MODE_CONT | VL6180_STARTSTOP); + + return -EINVAL; +} + +static int vl6180_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct vl6180_data *data = iio_priv(indio_dev); + int bit; + + iio_for_each_active_channel(indio_dev, bit) + return vl6180_write_byte(data->client, + vl6180_chan_regs_table[bit].start_reg, + VL6180_STARTSTOP); + + return -EINVAL; +} + +static const struct iio_buffer_setup_ops iio_triggered_buffer_setup_ops = { + .postenable = &vl6180_buffer_postenable, + .postdisable = &vl6180_buffer_postdisable, +}; + +static const struct iio_trigger_ops vl6180_trigger_ops = { + .validate_device = iio_trigger_validate_own_device, +}; + +static int vl6180_init(struct vl6180_data *data, struct iio_dev *indio_dev) +{ + struct i2c_client *client = data->client; + int ret; + + ret = vl6180_read_byte(client, VL6180_MODEL_ID); + if (ret < 0) + return ret; + + if (ret != VL6180_MODEL_ID_VAL) { + dev_err(&client->dev, "invalid model ID %02x\n", ret); + return -ENODEV; + } + + ret = vl6180_hold(data, true); + if (ret < 0) + return ret; + + ret = vl6180_read_byte(client, VL6180_OUT_OF_RESET); + if (ret < 0) + return ret; + + /* + * Detect false reset condition here. This bit is always set when the + * system comes out of reset. + */ + if (ret != 0x01) + dev_info(&client->dev, "device is not fresh out of reset\n"); + + /* Enable ALS and Range ready interrupts */ + ret = vl6180_write_byte(client, VL6180_INTR_CONFIG, + VL6180_ALS_READY | VL6180_RANGE_READY); + if (ret < 0) + return ret; + + ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, NULL, + &vl6180_trigger_handler, + &iio_triggered_buffer_setup_ops); + if (ret) + return ret; + + /* Default Range inter-measurement time: 50ms or 20000 mHz */ + ret = vl6180_write_byte(client, VL6180_RANGE_INTER_MEAS_TIME, + vl6180_meas_reg_val_from_mhz(20000)); + if (ret < 0) + return ret; + data->range_meas_rate = 20000; + + /* Default ALS inter-measurement time: 10ms or 100000 mHz */ + ret = vl6180_write_byte(client, VL6180_ALS_INTER_MEAS_TIME, + vl6180_meas_reg_val_from_mhz(100000)); + if (ret < 0) + return ret; + data->als_meas_rate = 100000; + + /* ALS integration time: 100ms */ + data->als_it_ms = 100; + ret = vl6180_write_word(client, VL6180_ALS_IT, VL6180_ALS_IT_100); + if (ret < 0) + return ret; + + /* ALS gain: 1 */ + data->als_gain_milli = 1000; + ret = vl6180_write_byte(client, VL6180_ALS_GAIN, VL6180_ALS_GAIN_1); + if (ret < 0) + return ret; + + ret = vl6180_write_byte(client, VL6180_OUT_OF_RESET, 0x00); + if (ret < 0) + return ret; + + return vl6180_hold(data, false); +} + +static int vl6180_probe(struct i2c_client *client) +{ + struct vl6180_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + mutex_init(&data->lock); + + indio_dev->info = &vl6180_info; + indio_dev->channels = vl6180_channels; + indio_dev->num_channels = ARRAY_SIZE(vl6180_channels); + indio_dev->name = VL6180_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = vl6180_init(data, indio_dev); + if (ret < 0) + return ret; + + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, vl6180_threaded_irq, + IRQF_ONESHOT, + indio_dev->name, indio_dev); + if (ret) + return ret; + + init_completion(&data->completion); + + data->trig = devm_iio_trigger_alloc(&client->dev, "%s-dev%d", + indio_dev->name, iio_device_id(indio_dev)); + if (!data->trig) + return -ENOMEM; + + data->trig->ops = &vl6180_trigger_ops; + iio_trigger_set_drvdata(data->trig, indio_dev); + ret = devm_iio_trigger_register(&client->dev, data->trig); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(data->trig); + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct of_device_id vl6180_of_match[] = { + { .compatible = "st,vl6180", }, + { } +}; +MODULE_DEVICE_TABLE(of, vl6180_of_match); + +static const struct i2c_device_id vl6180_id[] = { + { .name = "vl6180" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, vl6180_id); + +static struct i2c_driver vl6180_driver = { + .driver = { + .name = VL6180_DRV_NAME, + .of_match_table = vl6180_of_match, + }, + .probe = vl6180_probe, + .id_table = vl6180_id, +}; + +module_i2c_driver(vl6180_driver); + +MODULE_AUTHOR("Peter Meerwald-Stadler "); +MODULE_AUTHOR("Manivannan Sadhasivam "); +MODULE_DESCRIPTION("STMicro VL6180 ALS, range and proximity sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/zopt2201.c b/drivers/iio/light/zopt2201.c new file mode 100644 index 000000000..b53be5f73 --- /dev/null +++ b/drivers/iio/light/zopt2201.c @@ -0,0 +1,536 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * zopt2201.c - Support for IDT ZOPT2201 ambient light and UV B sensor + * + * Copyright 2017 Peter Meerwald-Stadler + * + * Datasheet: https://www.idt.com/document/dst/zopt2201-datasheet + * 7-bit I2C slave addresses 0x53 (default) or 0x52 (programmed) + * + * TODO: interrupt support, ALS/UVB raw mode + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define ZOPT2201_DRV_NAME "zopt2201" + +/* Registers */ +#define ZOPT2201_MAIN_CTRL 0x00 +#define ZOPT2201_LS_MEAS_RATE 0x04 +#define ZOPT2201_LS_GAIN 0x05 +#define ZOPT2201_PART_ID 0x06 +#define ZOPT2201_MAIN_STATUS 0x07 +#define ZOPT2201_ALS_DATA 0x0d /* LSB first, 13 to 20 bits */ +#define ZOPT2201_UVB_DATA 0x10 /* LSB first, 13 to 20 bits */ +#define ZOPT2201_UV_COMP_DATA 0x13 /* LSB first, 13 to 20 bits */ +#define ZOPT2201_COMP_DATA 0x16 /* LSB first, 13 to 20 bits */ +#define ZOPT2201_INT_CFG 0x19 +#define ZOPT2201_INT_PST 0x1a + +#define ZOPT2201_MAIN_CTRL_LS_MODE BIT(3) /* 0 .. ALS, 1 .. UV B */ +#define ZOPT2201_MAIN_CTRL_LS_EN BIT(1) + +/* Values for ZOPT2201_LS_MEAS_RATE resolution / bit width */ +#define ZOPT2201_MEAS_RES_20BIT 0 /* takes 400 ms */ +#define ZOPT2201_MEAS_RES_19BIT 1 /* takes 200 ms */ +#define ZOPT2201_MEAS_RES_18BIT 2 /* takes 100 ms, default */ +#define ZOPT2201_MEAS_RES_17BIT 3 /* takes 50 ms */ +#define ZOPT2201_MEAS_RES_16BIT 4 /* takes 25 ms */ +#define ZOPT2201_MEAS_RES_13BIT 5 /* takes 3.125 ms */ +#define ZOPT2201_MEAS_RES_SHIFT 4 + +/* Values for ZOPT2201_LS_MEAS_RATE measurement rate */ +#define ZOPT2201_MEAS_FREQ_25MS 0 +#define ZOPT2201_MEAS_FREQ_50MS 1 +#define ZOPT2201_MEAS_FREQ_100MS 2 /* default */ +#define ZOPT2201_MEAS_FREQ_200MS 3 +#define ZOPT2201_MEAS_FREQ_500MS 4 +#define ZOPT2201_MEAS_FREQ_1000MS 5 +#define ZOPT2201_MEAS_FREQ_2000MS 6 + +/* Values for ZOPT2201_LS_GAIN */ +#define ZOPT2201_LS_GAIN_1 0 +#define ZOPT2201_LS_GAIN_3 1 +#define ZOPT2201_LS_GAIN_6 2 +#define ZOPT2201_LS_GAIN_9 3 +#define ZOPT2201_LS_GAIN_18 4 + +/* Values for ZOPT2201_MAIN_STATUS */ +#define ZOPT2201_MAIN_STATUS_POWERON BIT(5) +#define ZOPT2201_MAIN_STATUS_INT BIT(4) +#define ZOPT2201_MAIN_STATUS_DRDY BIT(3) + +#define ZOPT2201_PART_NUMBER 0xb2 + +struct zopt2201_data { + struct i2c_client *client; + struct mutex lock; + u8 gain; + u8 res; + u8 rate; +}; + +static const struct { + unsigned int gain; /* gain factor */ + unsigned int scale; /* micro lux per count */ +} zopt2201_gain_als[] = { + { 1, 19200000 }, + { 3, 6400000 }, + { 6, 3200000 }, + { 9, 2133333 }, + { 18, 1066666 }, +}; + +static const struct { + unsigned int gain; /* gain factor */ + unsigned int scale; /* micro W/m2 per count */ +} zopt2201_gain_uvb[] = { + { 1, 460800 }, + { 3, 153600 }, + { 6, 76800 }, + { 9, 51200 }, + { 18, 25600 }, +}; + +static const struct { + unsigned int bits; /* sensor resolution in bits */ + unsigned long us; /* measurement time in micro seconds */ +} zopt2201_resolution[] = { + { 20, 400000 }, + { 19, 200000 }, + { 18, 100000 }, + { 17, 50000 }, + { 16, 25000 }, + { 13, 3125 }, +}; + +struct zopt2201_scale { + unsigned int scale, uscale; /* scale factor as integer + micro */ + u8 gain; /* gain register value */ + u8 res; /* resolution register value */ +}; + +static const struct zopt2201_scale zopt2201_scale_als[] = { + { 19, 200000, 0, 5 }, + { 6, 400000, 1, 5 }, + { 3, 200000, 2, 5 }, + { 2, 400000, 0, 4 }, + { 2, 133333, 3, 5 }, + { 1, 200000, 0, 3 }, + { 1, 66666, 4, 5 }, + { 0, 800000, 1, 4 }, + { 0, 600000, 0, 2 }, + { 0, 400000, 2, 4 }, + { 0, 300000, 0, 1 }, + { 0, 266666, 3, 4 }, + { 0, 200000, 2, 3 }, + { 0, 150000, 0, 0 }, + { 0, 133333, 4, 4 }, + { 0, 100000, 2, 2 }, + { 0, 66666, 4, 3 }, + { 0, 50000, 2, 1 }, + { 0, 33333, 4, 2 }, + { 0, 25000, 2, 0 }, + { 0, 16666, 4, 1 }, + { 0, 8333, 4, 0 }, +}; + +static const struct zopt2201_scale zopt2201_scale_uvb[] = { + { 0, 460800, 0, 5 }, + { 0, 153600, 1, 5 }, + { 0, 76800, 2, 5 }, + { 0, 57600, 0, 4 }, + { 0, 51200, 3, 5 }, + { 0, 28800, 0, 3 }, + { 0, 25600, 4, 5 }, + { 0, 19200, 1, 4 }, + { 0, 14400, 0, 2 }, + { 0, 9600, 2, 4 }, + { 0, 7200, 0, 1 }, + { 0, 6400, 3, 4 }, + { 0, 4800, 2, 3 }, + { 0, 3600, 0, 0 }, + { 0, 3200, 4, 4 }, + { 0, 2400, 2, 2 }, + { 0, 1600, 4, 3 }, + { 0, 1200, 2, 1 }, + { 0, 800, 4, 2 }, + { 0, 600, 2, 0 }, + { 0, 400, 4, 1 }, + { 0, 200, 4, 0 }, +}; + +static int zopt2201_enable_mode(struct zopt2201_data *data, bool uvb_mode) +{ + u8 out = ZOPT2201_MAIN_CTRL_LS_EN; + + if (uvb_mode) + out |= ZOPT2201_MAIN_CTRL_LS_MODE; + + return i2c_smbus_write_byte_data(data->client, ZOPT2201_MAIN_CTRL, out); +} + +static int zopt2201_read(struct zopt2201_data *data, u8 reg) +{ + struct i2c_client *client = data->client; + int tries = 10; + u8 buf[3]; + int ret; + + guard(mutex)(&data->lock); + ret = zopt2201_enable_mode(data, reg == ZOPT2201_UVB_DATA); + if (ret < 0) + return ret; + + while (tries--) { + unsigned long t = zopt2201_resolution[data->res].us; + + if (t <= 20000) + usleep_range(t, t + 1000); + else + msleep(t / 1000); + ret = i2c_smbus_read_byte_data(client, ZOPT2201_MAIN_STATUS); + if (ret < 0) + return ret; + if (ret & ZOPT2201_MAIN_STATUS_DRDY) + break; + } + + if (tries < 0) { + ret = -ETIMEDOUT; + return ret; + } + + ret = i2c_smbus_read_i2c_block_data(client, reg, sizeof(buf), buf); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(client, ZOPT2201_MAIN_CTRL, 0x00); + if (ret < 0) + return ret; + + return get_unaligned_le24(&buf[0]); +} + +static const struct iio_chan_spec zopt2201_channels[] = { + { + .type = IIO_LIGHT, + .address = ZOPT2201_ALS_DATA, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + }, + { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_UV, + .address = ZOPT2201_UVB_DATA, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), + }, + { + .type = IIO_UVINDEX, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, +}; + +static int zopt2201_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct zopt2201_data *data = iio_priv(indio_dev); + u64 tmp; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = zopt2201_read(data, chan->address); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_PROCESSED: + ret = zopt2201_read(data, ZOPT2201_UVB_DATA); + if (ret < 0) + return ret; + *val = ret * 18 * + (1 << (20 - zopt2201_resolution[data->res].bits)) / + zopt2201_gain_uvb[data->gain].gain; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->address) { + case ZOPT2201_ALS_DATA: + *val = zopt2201_gain_als[data->gain].scale; + break; + case ZOPT2201_UVB_DATA: + *val = zopt2201_gain_uvb[data->gain].scale; + break; + default: + return -EINVAL; + } + + *val2 = 1000000; + *val2 *= (1 << (zopt2201_resolution[data->res].bits - 13)); + tmp = div_s64(*val * 1000000ULL, *val2); + *val = div_s64_rem(tmp, 1000000, val2); + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = zopt2201_resolution[data->res].us; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int zopt2201_set_resolution(struct zopt2201_data *data, u8 res) +{ + int ret; + + ret = i2c_smbus_write_byte_data(data->client, ZOPT2201_LS_MEAS_RATE, + (res << ZOPT2201_MEAS_RES_SHIFT) | + data->rate); + if (ret < 0) + return ret; + + data->res = res; + + return 0; +} + +static int zopt2201_write_resolution(struct zopt2201_data *data, + int val, int val2) +{ + int i; + + if (val != 0) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(zopt2201_resolution); i++) + if (val2 == zopt2201_resolution[i].us) { + guard(mutex)(&data->lock); + return zopt2201_set_resolution(data, i); + } + + return -EINVAL; +} + +static int zopt2201_set_gain(struct zopt2201_data *data, u8 gain) +{ + int ret; + + ret = i2c_smbus_write_byte_data(data->client, ZOPT2201_LS_GAIN, gain); + if (ret < 0) + return ret; + + data->gain = gain; + + return 0; +} + +static int zopt2201_write_scale_by_idx(struct zopt2201_data *data, int idx, + const struct zopt2201_scale *zopt2201_scale_array) +{ + int ret; + + guard(mutex)(&data->lock); + ret = zopt2201_set_resolution(data, zopt2201_scale_array[idx].res); + if (ret < 0) + return ret; + + return zopt2201_set_gain(data, zopt2201_scale_array[idx].gain); +} + +static int zopt2201_write_scale_als(struct zopt2201_data *data, + int val, int val2) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(zopt2201_scale_als); i++) + if (val == zopt2201_scale_als[i].scale && + val2 == zopt2201_scale_als[i].uscale) + return zopt2201_write_scale_by_idx(data, i, zopt2201_scale_als); + + return -EINVAL; +} + +static int zopt2201_write_scale_uvb(struct zopt2201_data *data, + int val, int val2) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(zopt2201_scale_uvb); i++) + if (val == zopt2201_scale_uvb[i].scale && + val2 == zopt2201_scale_uvb[i].uscale) + return zopt2201_write_scale_by_idx(data, i, zopt2201_scale_uvb); + + return -EINVAL; +} + +static int zopt2201_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct zopt2201_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return zopt2201_write_resolution(data, val, val2); + case IIO_CHAN_INFO_SCALE: + switch (chan->address) { + case ZOPT2201_ALS_DATA: + return zopt2201_write_scale_als(data, val, val2); + case ZOPT2201_UVB_DATA: + return zopt2201_write_scale_uvb(data, val, val2); + default: + return -EINVAL; + } + } + + return -EINVAL; +} + +static ssize_t zopt2201_show_int_time_available(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + size_t len = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(zopt2201_resolution); i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06lu ", + zopt2201_resolution[i].us); + buf[len - 1] = '\n'; + + return len; +} + +static IIO_DEV_ATTR_INT_TIME_AVAIL(zopt2201_show_int_time_available); + +static ssize_t zopt2201_show_als_scale_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t len = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(zopt2201_scale_als); i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06u ", + zopt2201_scale_als[i].scale, + zopt2201_scale_als[i].uscale); + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t zopt2201_show_uvb_scale_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t len = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(zopt2201_scale_uvb); i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06u ", + zopt2201_scale_uvb[i].scale, + zopt2201_scale_uvb[i].uscale); + buf[len - 1] = '\n'; + + return len; +} + +static IIO_DEVICE_ATTR(in_illuminance_scale_available, 0444, + zopt2201_show_als_scale_avail, NULL, 0); +static IIO_DEVICE_ATTR(in_intensity_uv_scale_available, 0444, + zopt2201_show_uvb_scale_avail, NULL, 0); + +static struct attribute *zopt2201_attributes[] = { + &iio_dev_attr_integration_time_available.dev_attr.attr, + &iio_dev_attr_in_illuminance_scale_available.dev_attr.attr, + &iio_dev_attr_in_intensity_uv_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group zopt2201_attribute_group = { + .attrs = zopt2201_attributes, +}; + +static const struct iio_info zopt2201_info = { + .read_raw = zopt2201_read_raw, + .write_raw = zopt2201_write_raw, + .attrs = &zopt2201_attribute_group, +}; + +static int zopt2201_probe(struct i2c_client *client) +{ + struct zopt2201_data *data; + struct iio_dev *indio_dev; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) + return -EOPNOTSUPP; + + ret = i2c_smbus_read_byte_data(client, ZOPT2201_PART_ID); + if (ret < 0) + return ret; + if (ret != ZOPT2201_PART_NUMBER) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + mutex_init(&data->lock); + + indio_dev->info = &zopt2201_info; + indio_dev->channels = zopt2201_channels; + indio_dev->num_channels = ARRAY_SIZE(zopt2201_channels); + indio_dev->name = ZOPT2201_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + data->rate = ZOPT2201_MEAS_FREQ_100MS; + ret = zopt2201_set_resolution(data, ZOPT2201_MEAS_RES_18BIT); + if (ret < 0) + return ret; + + ret = zopt2201_set_gain(data, ZOPT2201_LS_GAIN_3); + if (ret < 0) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id zopt2201_id[] = { + { .name = "zopt2201" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, zopt2201_id); + +static struct i2c_driver zopt2201_driver = { + .driver = { + .name = ZOPT2201_DRV_NAME, + }, + .probe = zopt2201_probe, + .id_table = zopt2201_id, +}; + +module_i2c_driver(zopt2201_driver); + +MODULE_AUTHOR("Peter Meerwald-Stadler "); +MODULE_DESCRIPTION("IDT ZOPT2201 ambient light and UV B sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/magnetometer/Kconfig b/drivers/iio/magnetometer/Kconfig new file mode 100644 index 000000000..d359003b2 --- /dev/null +++ b/drivers/iio/magnetometer/Kconfig @@ -0,0 +1,334 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Magnetometer sensors +# +# When adding new entries keep the list in alphabetical order + +menu "Magnetometer sensors" + +config AF8133J + tristate "Voltafield AF8133J 3-Axis Magnetometer" + depends on I2C + depends on OF + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Voltafield AF8133J I2C-based + 3-axis magnetometer chip. + + To compile this driver as a module, choose M here: the module + will be called af8133j. + +config AK8974 + tristate "Asahi Kasei AK8974 3-Axis Magnetometer" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Asahi Kasei AK8974, AMI305 or + AMI306 I2C-based 3-axis magnetometer chips. + + To compile this driver as a module, choose M here: the module + will be called ak8974. + +config AK8975 + tristate "Asahi Kasei AK8975 3-Axis Magnetometer" + depends on I2C + depends on GPIOLIB || COMPILE_TEST + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Asahi Kasei AK8975, AK8963, + AK09911, AK09912, AK09916 or AK09918 3-Axis Magnetometer. + + To compile this driver as a module, choose M here: the module + will be called ak8975. + +config AK09911 + tristate "Asahi Kasei AK09911 3-axis Compass" + depends on I2C + depends on GPIOLIB || COMPILE_TEST + select AK8975 + help + Deprecated: AK09911 is now supported by AK8975 driver. + +config ALS31300 + tristate "Allegro MicroSystems ALS31300 3-D Linear Hall Effect Sensor" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for the Allegro MicroSystems + ALS31300 Hall Effect Sensor through its I2C interface. + + To compile this driver as a module, choose M here: the + module will be called als31300. + +config BMC150_MAGN + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config BMC150_MAGN_I2C + tristate "Bosch BMC150 I2C Magnetometer Driver" + depends on I2C + select BMC150_MAGN + select REGMAP_I2C + help + Say yes here to build support for the BMC150 magnetometer with + I2C interface. + + This is a combo module with both accelerometer and magnetometer. + This driver is only implementing magnetometer part, which has + its own address and register map. + + This driver also supports I2C Bosch BMC156 and BMM150 chips. + To compile this driver as a module, choose M here: the module will be + called bmc150_magn_i2c. + +config BMC150_MAGN_SPI + tristate "Bosch BMC150 SPI Magnetometer Driver" + depends on SPI + select BMC150_MAGN + select REGMAP_SPI + help + Say yes here to build support for the BMC150 magnetometer with + SPI interface. + + This is a combo module with both accelerometer and magnetometer. + This driver is only implementing magnetometer part, which has + its own address and register map. + + This driver also supports SPI Bosch BMC156 and BMM150 chips. + To compile this driver as a module, choose M here: the module will be + called bmc150_magn_spi. + +config MAG3110 + tristate "Freescale MAG3110 3-Axis Magnetometer" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for the Freescale MAG3110 3-Axis + magnetometer. + + To compile this driver as a module, choose M here: the module + will be called mag3110. + +config HID_SENSOR_MAGNETOMETER_3D + depends on HID_SENSOR_HUB + select IIO_BUFFER + select HID_SENSOR_IIO_COMMON + select HID_SENSOR_IIO_TRIGGER + tristate "HID Magnetometer 3D" + help + Say yes here to build support for the HID SENSOR + Magnetometer 3D. + +config MMC35240 + tristate "MEMSIC MMC35240 3-axis magnetic sensor" + select REGMAP_I2C + depends on I2C + help + Say yes here to build support for the MEMSIC MMC35240 3-axis + magnetic sensor. + + To compile this driver as a module, choose M here: the module + will be called mmc35240. + +config MMC5633 + tristate "MEMSIC MMC5633 3-axis magnetic sensor" + select REGMAP_I2C + select REGMAP_I3C if I3C + depends on I3C_OR_I2C + help + Say yes here to build support for the MEMSIC MMC5633 3-axis + magnetic sensor. + + To compile this driver as a module, choose M here: the module + will be called mmc5633 + +config MMC5983 + tristate "MEMSIC MMC5983 3-axis magnetic sensor" + depends on I2C + select REGMAP_I2C + help + Say yes here to build support for the MEMSIC MMC5983 3-axis + magnetic sensor. + + To compile this driver as a module, choose M here: the module + will be called mmc5983 + +config IIO_ST_MAGN_3AXIS + tristate "STMicroelectronics magnetometers 3-Axis Driver" + depends on (I2C || SPI_MASTER) && SYSFS + select IIO_ST_SENSORS_CORE + select IIO_TRIGGERED_BUFFER if (IIO_BUFFER) + help + Say yes here to build support for STMicroelectronics magnetometers: + LSM303C, LSM303DLHC, LSM303DLM, LIS3MDL. + + Also need to enable at least one of I2C and SPI interface drivers + below. + +config IIO_ST_MAGN_I2C_3AXIS + tristate "STMicroelectronics magnetometers 3-Axis I2C Interface" + depends on I2C && IIO_ST_MAGN_3AXIS + default I2C && IIO_ST_MAGN_3AXIS + select IIO_ST_SENSORS_I2C + help + Build support for STMicroelectronics magnetometers I2C interface. + + To compile this driver as a module, choose M here. The module + will be called st_magn_i2c. + +config IIO_ST_MAGN_SPI_3AXIS + tristate "STMicroelectronics magnetometers 3-Axis SPI Interface" + depends on SPI_MASTER && IIO_ST_MAGN_3AXIS + default SPI_MASTER && IIO_ST_MAGN_3AXIS + select IIO_ST_SENSORS_SPI + help + Build support for STMicroelectronics magnetometers SPI interface. + + To compile this driver as a module, choose M here. The module + will be called st_magn_spi. + +config INFINEON_TLV493D + tristate "Infineon TLV493D Low-Power 3D Magnetic Sensor" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here to add support for the Infineon TLV493D-A1B6 Low- + Power 3D Magnetic Sensor. + + This driver can also be compiled as a module. + To compile this driver as a module, choose M here: the module + will be called tlv493d. + +config QMC5883L + tristate "QST QMC5883L 3-Axis Magnetic Sensor" + depends on I2C + select REGMAP_I2C + help + Say Y here to add support driver for QST QMC5883L 3-Axis + Magnetic Sensor. + + To compile this driver as a module, choose M here: the + module will be called qmc5883l. + +config QMC6308 + tristate "QST QMC6308 3-Axis Magnetic Sensor" + depends on I2C + select REGMAP_I2C + help + Say Y here to add support for the QST QMC6308 3-Axis + Magnetic Sensor. + + To compile this driver as a module, choose M here: the + module will be called qmc6308. + +config SENSORS_HMC5843 + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config SENSORS_HMC5843_I2C + tristate "Honeywell HMC5843/5883/5883L 3-Axis Magnetometer (I2C)" + depends on I2C + select SENSORS_HMC5843 + select REGMAP_I2C + help + Say Y here to add support for the Honeywell HMC5843, HMC5883 and + HMC5883L 3-Axis Magnetometer (digital compass). + + This driver can also be compiled as a set of modules. + If so, these modules will be created: + - hmc5843_core (core functions) + - hmc5843_i2c (support for HMC5843, HMC5883, HMC5883L and HMC5983) + +config SENSORS_HMC5843_SPI + tristate "Honeywell HMC5983 3-Axis Magnetometer (SPI)" + depends on SPI_MASTER + select SENSORS_HMC5843 + select REGMAP_SPI + help + Say Y here to add support for the Honeywell HMC5983 3-Axis Magnetometer + (digital compass). + + This driver can also be compiled as a set of modules. + If so, these modules will be created: + - hmc5843_core (core functions) + - hmc5843_spi (support for HMC5983) + +config SENSORS_RM3100 + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config SENSORS_RM3100_I2C + tristate "PNI RM3100 3-Axis Magnetometer (I2C)" + depends on I2C + select SENSORS_RM3100 + select REGMAP_I2C + help + Say Y here to add support for the PNI RM3100 3-Axis Magnetometer. + + This driver can also be compiled as a module. + To compile this driver as a module, choose M here: the module + will be called rm3100-i2c. + +config SENSORS_RM3100_SPI + tristate "PNI RM3100 3-Axis Magnetometer (SPI)" + depends on SPI_MASTER + select SENSORS_RM3100 + select REGMAP_SPI + help + Say Y here to add support for the PNI RM3100 3-Axis Magnetometer. + + This driver can also be compiled as a module. + To compile this driver as a module, choose M here: the module + will be called rm3100-spi. + +config SI7210 + tristate "SI7210 Hall effect sensor" + depends on I2C + select REGMAP_I2C + help + Say Y here to add support for the SI7210 Hall effect sensor. + + This driver can also be compiled as a module. + To compile this driver as a module, choose M here: the module + will be called si7210. + +config TI_TMAG5273 + tristate "TI TMAG5273 Low-Power Linear 3D Hall-Effect Sensor" + depends on I2C + select REGMAP_I2C + help + Say Y here to add support for the TI TMAG5273 Low-Power + Linear 3D Hall-Effect Sensor. + + This driver can also be compiled as a module. + To compile this driver as a module, choose M here: the module + will be called tmag5273. + +config YAMAHA_YAS530 + tristate "Yamaha YAS530 family of 3-Axis Magnetometers (I2C)" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here to add support for the Yamaha YAS530 series of + 3-Axis Magnetometers. YAS530, YAS532, YAS533 and YAS537 are + supported. + + This driver can also be compiled as a module. + To compile this driver as a module, choose M here: the module + will be called yamaha-yas. + +endmenu diff --git a/drivers/iio/magnetometer/Makefile b/drivers/iio/magnetometer/Makefile new file mode 100644 index 000000000..8f2205975 --- /dev/null +++ b/drivers/iio/magnetometer/Makefile @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for industrial I/O Magnetometer sensor drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_AF8133J) += af8133j.o +obj-$(CONFIG_AK8974) += ak8974.o +obj-$(CONFIG_AK8975) += ak8975.o +obj-$(CONFIG_ALS31300) += als31300.o +obj-$(CONFIG_BMC150_MAGN) += bmc150_magn.o +obj-$(CONFIG_BMC150_MAGN_I2C) += bmc150_magn_i2c.o +obj-$(CONFIG_BMC150_MAGN_SPI) += bmc150_magn_spi.o + +obj-$(CONFIG_MAG3110) += mag3110.o +obj-$(CONFIG_HID_SENSOR_MAGNETOMETER_3D) += hid-sensor-magn-3d.o +obj-$(CONFIG_MMC35240) += mmc35240.o +obj-$(CONFIG_MMC5633) += mmc5633.o +obj-$(CONFIG_MMC5983) += mmc5983.o + +obj-$(CONFIG_IIO_ST_MAGN_3AXIS) += st_magn.o +st_magn-y := st_magn_core.o +st_magn-$(CONFIG_IIO_BUFFER) += st_magn_buffer.o + +obj-$(CONFIG_IIO_ST_MAGN_I2C_3AXIS) += st_magn_i2c.o +obj-$(CONFIG_IIO_ST_MAGN_SPI_3AXIS) += st_magn_spi.o + +obj-$(CONFIG_INFINEON_TLV493D) += tlv493d.o + +obj-$(CONFIG_QMC5883L) += qmc5883l.o +obj-$(CONFIG_QMC6308) += qmc6308.o + +obj-$(CONFIG_SENSORS_HMC5843) += hmc5843_core.o +obj-$(CONFIG_SENSORS_HMC5843_I2C) += hmc5843_i2c.o +obj-$(CONFIG_SENSORS_HMC5843_SPI) += hmc5843_spi.o + +obj-$(CONFIG_SENSORS_RM3100) += rm3100-core.o +obj-$(CONFIG_SENSORS_RM3100_I2C) += rm3100-i2c.o +obj-$(CONFIG_SENSORS_RM3100_SPI) += rm3100-spi.o + +obj-$(CONFIG_SI7210) += si7210.o + +obj-$(CONFIG_TI_TMAG5273) += tmag5273.o + +obj-$(CONFIG_YAMAHA_YAS530) += yamaha-yas530.o diff --git a/drivers/iio/magnetometer/af8133j.c b/drivers/iio/magnetometer/af8133j.c new file mode 100644 index 000000000..352f53eda --- /dev/null +++ b/drivers/iio/magnetometer/af8133j.c @@ -0,0 +1,527 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * af8133j.c - Voltafield AF8133J magnetometer driver + * + * Copyright 2021 Icenowy Zheng + * Copyright 2024 Ondřej Jirman + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define AF8133J_REG_OUT 0x03 +#define AF8133J_REG_PCODE 0x00 +#define AF8133J_REG_PCODE_VAL 0x5e +#define AF8133J_REG_STATUS 0x02 +#define AF8133J_REG_STATUS_ACQ BIT(0) +#define AF8133J_REG_STATE 0x0a +#define AF8133J_REG_STATE_STBY 0x00 +#define AF8133J_REG_STATE_WORK 0x01 +#define AF8133J_REG_RANGE 0x0b +#define AF8133J_REG_RANGE_22G 0x12 +#define AF8133J_REG_RANGE_12G 0x34 +#define AF8133J_REG_SWR 0x11 +#define AF8133J_REG_SWR_PERFORM 0x81 + +static const char * const af8133j_supply_names[] = { + "avdd", + "dvdd", +}; + +struct af8133j_data { + struct i2c_client *client; + struct regmap *regmap; + /* + * Protect device internal state between starting a measurement + * and reading the result. + */ + struct mutex mutex; + struct iio_mount_matrix orientation; + + struct gpio_desc *reset_gpiod; + struct regulator_bulk_data supplies[ARRAY_SIZE(af8133j_supply_names)]; + + u8 range; +}; + +enum af8133j_axis { + AXIS_X = 0, + AXIS_Y, + AXIS_Z, +}; + +static struct iio_mount_matrix * +af8133j_get_mount_matrix(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct af8133j_data *data = iio_priv(indio_dev); + + return &data->orientation; +} + +static const struct iio_chan_spec_ext_info af8133j_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, af8133j_get_mount_matrix), + { } +}; + +#define AF8133J_CHANNEL(_si, _axis) { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## _axis, \ + .address = AXIS_ ## _axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info = af8133j_ext_info, \ + .scan_index = _si, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ +} + +static const struct iio_chan_spec af8133j_channels[] = { + AF8133J_CHANNEL(0, X), + AF8133J_CHANNEL(1, Y), + AF8133J_CHANNEL(2, Z), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static int af8133j_product_check(struct af8133j_data *data) +{ + struct device *dev = &data->client->dev; + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, AF8133J_REG_PCODE, &val); + if (ret) { + dev_err(dev, "Error reading product code (%d)\n", ret); + return ret; + } + + if (val != AF8133J_REG_PCODE_VAL) { + dev_warn(dev, "Invalid product code (0x%02x)\n", val); + return 0; /* Allow unknown ID so fallback compatibles work */ + } + + return 0; +} + +static int af8133j_reset(struct af8133j_data *data) +{ + struct device *dev = &data->client->dev; + int ret; + + if (data->reset_gpiod) { + /* If we have GPIO reset line, use it */ + gpiod_set_value_cansleep(data->reset_gpiod, 1); + udelay(10); + gpiod_set_value_cansleep(data->reset_gpiod, 0); + } else { + /* Otherwise use software reset */ + ret = regmap_write(data->regmap, AF8133J_REG_SWR, + AF8133J_REG_SWR_PERFORM); + if (ret) { + dev_err(dev, "Failed to reset the chip\n"); + return ret; + } + } + + /* Wait for reset to finish */ + usleep_range(1000, 1100); + + /* Restore range setting */ + if (data->range == AF8133J_REG_RANGE_22G) { + ret = regmap_write(data->regmap, AF8133J_REG_RANGE, data->range); + if (ret) + return ret; + } + + return 0; +} + +static void af8133j_power_down(struct af8133j_data *data) +{ + gpiod_set_value_cansleep(data->reset_gpiod, 1); + regulator_bulk_disable(ARRAY_SIZE(data->supplies), data->supplies); +} + +static int af8133j_power_up(struct af8133j_data *data) +{ + struct device *dev = &data->client->dev; + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(data->supplies), data->supplies); + if (ret) { + dev_err(dev, "Could not enable regulators\n"); + return ret; + } + + gpiod_set_value_cansleep(data->reset_gpiod, 0); + + /* Wait for power on reset */ + usleep_range(15000, 16000); + + ret = af8133j_reset(data); + if (ret) { + af8133j_power_down(data); + return ret; + } + + return 0; +} + +static int af8133j_take_measurement(struct af8133j_data *data) +{ + unsigned int val; + int ret; + + ret = regmap_write(data->regmap, + AF8133J_REG_STATE, AF8133J_REG_STATE_WORK); + if (ret) + return ret; + + /* The datasheet says "Mesaure Time <1.5ms" */ + ret = regmap_read_poll_timeout(data->regmap, AF8133J_REG_STATUS, val, + val & AF8133J_REG_STATUS_ACQ, + 500, 1500); + if (ret) + return ret; + + ret = regmap_write(data->regmap, + AF8133J_REG_STATE, AF8133J_REG_STATE_STBY); + if (ret) + return ret; + + return 0; +} + +static int af8133j_read_measurement(struct af8133j_data *data, __le16 buf[3]) +{ + struct device *dev = &data->client->dev; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) { + /* + * Ignore EACCES because that happens when RPM is disabled + * during system sleep, while userspace leave eg. hrtimer + * trigger attached and IIO core keeps trying to do measurements. + */ + if (ret != -EACCES) + dev_err(dev, "Failed to power on (%d)\n", ret); + return ret; + } + + scoped_guard(mutex, &data->mutex) { + ret = af8133j_take_measurement(data); + if (ret) + goto out_rpm_put; + + ret = regmap_bulk_read(data->regmap, AF8133J_REG_OUT, + buf, sizeof(__le16) * 3); + } + +out_rpm_put: + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static const int af8133j_scales[][2] = { + [0] = { 0, 366210 }, /* 12 gauss */ + [1] = { 0, 671386 }, /* 22 gauss */ +}; + +static int af8133j_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct af8133j_data *data = iio_priv(indio_dev); + __le16 buf[3]; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = af8133j_read_measurement(data, buf); + if (ret) + return ret; + + *val = sign_extend32(le16_to_cpu(buf[chan->address]), + chan->scan_type.realbits - 1); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + + if (data->range == AF8133J_REG_RANGE_12G) + *val2 = af8133j_scales[0][1]; + else + *val2 = af8133j_scales[1][1]; + + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static int af8133j_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = (const int *)af8133j_scales; + *length = ARRAY_SIZE(af8133j_scales) * 2; + *type = IIO_VAL_INT_PLUS_NANO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int af8133j_set_scale(struct af8133j_data *data, + unsigned int val, unsigned int val2) +{ + struct device *dev = &data->client->dev; + u8 range; + int ret = 0; + + if (af8133j_scales[0][0] == val && af8133j_scales[0][1] == val2) + range = AF8133J_REG_RANGE_12G; + else if (af8133j_scales[1][0] == val && af8133j_scales[1][1] == val2) + range = AF8133J_REG_RANGE_22G; + else + return -EINVAL; + + pm_runtime_disable(dev); + + /* + * When suspended, just store the new range to data->range to be + * applied later during power up. + */ + if (!pm_runtime_status_suspended(dev)) { + scoped_guard(mutex, &data->mutex) + ret = regmap_write(data->regmap, + AF8133J_REG_RANGE, range); + } + + pm_runtime_enable(dev); + + data->range = range; + return ret; +} + +static int af8133j_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct af8133j_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return af8133j_set_scale(data, val, val2); + default: + return -EINVAL; + } +} + +static int af8133j_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + return IIO_VAL_INT_PLUS_NANO; +} + +static const struct iio_info af8133j_info = { + .read_raw = af8133j_read_raw, + .read_avail = af8133j_read_avail, + .write_raw = af8133j_write_raw, + .write_raw_get_fmt = af8133j_write_raw_get_fmt, +}; + +static irqreturn_t af8133j_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct af8133j_data *data = iio_priv(indio_dev); + s64 timestamp = iio_get_time_ns(indio_dev); + struct { + __le16 values[3]; + aligned_s64 timestamp; + } sample = { }; + int ret; + + ret = af8133j_read_measurement(data, sample.values); + if (ret) + goto out_done; + + iio_push_to_buffers_with_ts(indio_dev, &sample, sizeof(sample), + timestamp); + +out_done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct regmap_config af8133j_regmap_config = { + .name = "af8133j_regmap", + .reg_bits = 8, + .val_bits = 8, + .max_register = AF8133J_REG_SWR, +}; + +static void af8133j_power_down_action(void *ptr) +{ + struct af8133j_data *data = ptr; + + if (!pm_runtime_status_suspended(&data->client->dev)) + af8133j_power_down(data); +} + +static int af8133j_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct af8133j_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + int ret, i; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, &af8133j_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "regmap initialization failed\n"); + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->regmap = regmap; + data->range = AF8133J_REG_RANGE_12G; + mutex_init(&data->mutex); + + data->reset_gpiod = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(data->reset_gpiod)) + return dev_err_probe(dev, PTR_ERR(data->reset_gpiod), + "Failed to get reset gpio\n"); + + for (i = 0; i < ARRAY_SIZE(af8133j_supply_names); i++) + data->supplies[i].supply = af8133j_supply_names[i]; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(data->supplies), + data->supplies); + if (ret) + return ret; + + ret = iio_read_mount_matrix(dev, &data->orientation); + if (ret) + return dev_err_probe(dev, ret, "Failed to read mount matrix\n"); + + ret = af8133j_power_up(data); + if (ret) + return ret; + + pm_runtime_set_active(dev); + + ret = devm_add_action_or_reset(dev, af8133j_power_down_action, data); + if (ret) + return ret; + + ret = af8133j_product_check(data); + if (ret) + return ret; + + pm_runtime_get_noresume(dev); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_autosuspend_delay(dev, 500); + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + pm_runtime_put_autosuspend(dev); + + indio_dev->info = &af8133j_info; + indio_dev->name = "af8133j"; + indio_dev->channels = af8133j_channels; + indio_dev->num_channels = ARRAY_SIZE(af8133j_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + &af8133j_trigger_handler, NULL); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to setup iio triggered buffer\n"); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to register iio device"); + + return 0; +} + +static int af8133j_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct af8133j_data *data = iio_priv(indio_dev); + + af8133j_power_down(data); + + return 0; +} + +static int af8133j_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct af8133j_data *data = iio_priv(indio_dev); + + return af8133j_power_up(data); +} + +static const struct dev_pm_ops af8133j_pm_ops = { + SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) + RUNTIME_PM_OPS(af8133j_runtime_suspend, af8133j_runtime_resume, NULL) +}; + +static const struct of_device_id af8133j_of_match[] = { + { .compatible = "voltafield,af8133j", }, + { } +}; +MODULE_DEVICE_TABLE(of, af8133j_of_match); + +static const struct i2c_device_id af8133j_id[] = { + { .name = "af8133j" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, af8133j_id); + +static struct i2c_driver af8133j_driver = { + .driver = { + .name = "af8133j", + .of_match_table = af8133j_of_match, + .pm = pm_ptr(&af8133j_pm_ops), + }, + .probe = af8133j_probe, + .id_table = af8133j_id, +}; + +module_i2c_driver(af8133j_driver); + +MODULE_AUTHOR("Icenowy Zheng "); +MODULE_AUTHOR("Ondřej Jirman "); +MODULE_DESCRIPTION("Voltafield AF8133J magnetic sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/magnetometer/ak8974.c b/drivers/iio/magnetometer/ak8974.c new file mode 100644 index 000000000..375023db2 --- /dev/null +++ b/drivers/iio/magnetometer/ak8974.c @@ -0,0 +1,1042 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for the Asahi Kasei EMD Corporation AK8974 + * and Aichi Steel AMI305 magnetometer chips. + * Based on a patch from Samu Onkalo and the AK8975 IIO driver. + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + * Copyright (c) 2010 NVIDIA Corporation. + * Copyright (C) 2016 Linaro Ltd. + * + * Author: Samu Onkalo + * Author: Linus Walleij + */ +#include +#include +#include +#include +#include /* For irq_get_irq_data() */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* + * 16-bit registers are little-endian. LSB is at the address defined below + * and MSB is at the next higher address. + */ + +/* These registers are common for AK8974 and AMI30x */ +#define AK8974_SELFTEST 0x0C +#define AK8974_SELFTEST_IDLE 0x55 +#define AK8974_SELFTEST_OK 0xAA + +#define AK8974_INFO 0x0D + +#define AK8974_WHOAMI 0x0F +#define AK8974_WHOAMI_VALUE_AMI306 0x46 +#define AK8974_WHOAMI_VALUE_AMI305 0x47 +#define AK8974_WHOAMI_VALUE_AK8974 0x48 +#define AK8974_WHOAMI_VALUE_HSCDTD008A 0x49 + +#define AK8974_DATA_X 0x10 +#define AK8974_DATA_Y 0x12 +#define AK8974_DATA_Z 0x14 +#define AK8974_INT_SRC 0x16 +#define AK8974_STATUS 0x18 +#define AK8974_INT_CLEAR 0x1A +#define AK8974_CTRL1 0x1B +#define AK8974_CTRL2 0x1C +#define AK8974_CTRL3 0x1D +#define AK8974_INT_CTRL 0x1E +#define AK8974_INT_THRES 0x26 /* Absolute any axis value threshold */ +#define AK8974_PRESET 0x30 + +/* AK8974-specific offsets */ +#define AK8974_OFFSET_X 0x20 +#define AK8974_OFFSET_Y 0x22 +#define AK8974_OFFSET_Z 0x24 +/* AMI305-specific offsets */ +#define AMI305_OFFSET_X 0x6C +#define AMI305_OFFSET_Y 0x72 +#define AMI305_OFFSET_Z 0x78 + +/* Different temperature registers */ +#define AK8974_TEMP 0x31 +#define AMI305_TEMP 0x60 + +/* AMI306-specific control register */ +#define AMI306_CTRL4 0x5C + +/* AMI306 factory calibration data */ + +/* fine axis sensitivity */ +#define AMI306_FINEOUTPUT_X 0x90 +#define AMI306_FINEOUTPUT_Y 0x92 +#define AMI306_FINEOUTPUT_Z 0x94 + +/* axis sensitivity */ +#define AMI306_SENS_X 0x96 +#define AMI306_SENS_Y 0x98 +#define AMI306_SENS_Z 0x9A + +/* axis cross-interference */ +#define AMI306_GAIN_PARA_XZ 0x9C +#define AMI306_GAIN_PARA_XY 0x9D +#define AMI306_GAIN_PARA_YZ 0x9E +#define AMI306_GAIN_PARA_YX 0x9F +#define AMI306_GAIN_PARA_ZY 0xA0 +#define AMI306_GAIN_PARA_ZX 0xA1 + +/* offset at ZERO magnetic field */ +#define AMI306_OFFZERO_X 0xF8 +#define AMI306_OFFZERO_Y 0xFA +#define AMI306_OFFZERO_Z 0xFC + + +#define AK8974_INT_X_HIGH BIT(7) /* Axis over +threshold */ +#define AK8974_INT_Y_HIGH BIT(6) +#define AK8974_INT_Z_HIGH BIT(5) +#define AK8974_INT_X_LOW BIT(4) /* Axis below -threshold */ +#define AK8974_INT_Y_LOW BIT(3) +#define AK8974_INT_Z_LOW BIT(2) +#define AK8974_INT_RANGE BIT(1) /* Range overflow (any axis) */ + +#define AK8974_STATUS_DRDY BIT(6) /* Data ready */ +#define AK8974_STATUS_OVERRUN BIT(5) /* Data overrun */ +#define AK8974_STATUS_INT BIT(4) /* Interrupt occurred */ + +#define AK8974_CTRL1_POWER BIT(7) /* 0 = standby; 1 = active */ +#define AK8974_CTRL1_RATE BIT(4) /* 0 = 10 Hz; 1 = 20 Hz */ +#define AK8974_CTRL1_FORCE_EN BIT(1) /* 0 = normal; 1 = force */ +#define AK8974_CTRL1_MODE2 BIT(0) /* 0 */ + +#define AK8974_CTRL2_INT_EN BIT(4) /* 1 = enable interrupts */ +#define AK8974_CTRL2_DRDY_EN BIT(3) /* 1 = enable data ready signal */ +#define AK8974_CTRL2_DRDY_POL BIT(2) /* 1 = data ready active high */ +#define AK8974_CTRL2_RESDEF (AK8974_CTRL2_DRDY_POL) + +#define AK8974_CTRL3_RESET BIT(7) /* Software reset */ +#define AK8974_CTRL3_FORCE BIT(6) /* Start forced measurement */ +#define AK8974_CTRL3_SELFTEST BIT(4) /* Set selftest register */ +#define AK8974_CTRL3_RESDEF 0x00 + +#define AK8974_INT_CTRL_XEN BIT(7) /* Enable interrupt for this axis */ +#define AK8974_INT_CTRL_YEN BIT(6) +#define AK8974_INT_CTRL_ZEN BIT(5) +#define AK8974_INT_CTRL_XYZEN (BIT(7)|BIT(6)|BIT(5)) +#define AK8974_INT_CTRL_POL BIT(3) /* 0 = active low; 1 = active high */ +#define AK8974_INT_CTRL_PULSE BIT(1) /* 0 = latched; 1 = pulse (50 usec) */ +#define AK8974_INT_CTRL_RESDEF (AK8974_INT_CTRL_XYZEN | AK8974_INT_CTRL_POL) + +/* HSCDTD008A-specific control register */ +#define HSCDTD008A_CTRL4 0x1E +#define HSCDTD008A_CTRL4_MMD BIT(7) /* must be set to 1 */ +#define HSCDTD008A_CTRL4_RANGE BIT(4) /* 0 = 14-bit output; 1 = 15-bit output */ +#define HSCDTD008A_CTRL4_RESDEF (HSCDTD008A_CTRL4_MMD | HSCDTD008A_CTRL4_RANGE) + +/* The AMI305 has elaborate FW version and serial number registers */ +#define AMI305_VER 0xE8 +#define AMI305_SN 0xEA + +#define AK8974_MAX_RANGE 2048 + +#define AK8974_POWERON_DELAY 50 +#define AK8974_ACTIVATE_DELAY 1 +#define AK8974_SELFTEST_DELAY 1 +/* + * Set the autosuspend to two orders of magnitude larger than the poweron + * delay to make sane reasonable power tradeoff savings (5 seconds in + * this case). + */ +#define AK8974_AUTOSUSPEND_DELAY 5000 + +#define AK8974_MEASTIME 3 + +#define AK8974_PWR_ON 1 +#define AK8974_PWR_OFF 0 + +/** + * struct ak8974 - state container for the AK8974 driver + * @i2c: parent I2C client + * @orientation: mounting matrix, flipped axis etc + * @map: regmap to access the AK8974 registers over I2C + * @regs: the avdd and dvdd power regulators + * @name: the name of the part + * @variant: the whoami ID value (for selecting code paths) + * @lock: locks the magnetometer for exclusive use during a measurement + * @drdy_irq: uses the DRDY IRQ line + * @drdy_complete: completion for DRDY + * @drdy_active_low: the DRDY IRQ is active low + * @scan: timestamps + */ +struct ak8974 { + struct i2c_client *i2c; + struct iio_mount_matrix orientation; + struct regmap *map; + struct regulator_bulk_data regs[2]; + const char *name; + u8 variant; + struct mutex lock; + bool drdy_irq; + struct completion drdy_complete; + bool drdy_active_low; + /* Ensure timestamp is naturally aligned */ + struct { + __le16 channels[3]; + aligned_s64 ts; + } scan; +}; + +static const char ak8974_reg_avdd[] = "avdd"; +static const char ak8974_reg_dvdd[] = "dvdd"; + +static int ak8974_get_u16_val(struct ak8974 *ak8974, u8 reg, u16 *val) +{ + int ret; + __le16 bulk; + + ret = regmap_bulk_read(ak8974->map, reg, &bulk, 2); + if (ret) + return ret; + *val = le16_to_cpu(bulk); + + return 0; +} + +static int ak8974_set_u16_val(struct ak8974 *ak8974, u8 reg, u16 val) +{ + __le16 bulk = cpu_to_le16(val); + + return regmap_bulk_write(ak8974->map, reg, &bulk, 2); +} + +static int ak8974_set_power(struct ak8974 *ak8974, bool mode) +{ + int ret; + u8 val; + + val = mode ? AK8974_CTRL1_POWER : 0; + val |= AK8974_CTRL1_FORCE_EN; + ret = regmap_write(ak8974->map, AK8974_CTRL1, val); + if (ret < 0) + return ret; + + if (mode) + msleep(AK8974_ACTIVATE_DELAY); + + return 0; +} + +static int ak8974_reset(struct ak8974 *ak8974) +{ + int ret; + + /* Power on to get register access. Sets CTRL1 reg to reset state */ + ret = ak8974_set_power(ak8974, AK8974_PWR_ON); + if (ret) + return ret; + ret = regmap_write(ak8974->map, AK8974_CTRL2, AK8974_CTRL2_RESDEF); + if (ret) + return ret; + ret = regmap_write(ak8974->map, AK8974_CTRL3, AK8974_CTRL3_RESDEF); + if (ret) + return ret; + if (ak8974->variant != AK8974_WHOAMI_VALUE_HSCDTD008A) { + ret = regmap_write(ak8974->map, AK8974_INT_CTRL, + AK8974_INT_CTRL_RESDEF); + if (ret) + return ret; + } else { + ret = regmap_write(ak8974->map, HSCDTD008A_CTRL4, + HSCDTD008A_CTRL4_RESDEF); + if (ret) + return ret; + } + + /* After reset, power off is default state */ + return ak8974_set_power(ak8974, AK8974_PWR_OFF); +} + +static int ak8974_configure(struct ak8974 *ak8974) +{ + int ret; + + ret = regmap_write(ak8974->map, AK8974_CTRL2, AK8974_CTRL2_DRDY_EN | + AK8974_CTRL2_INT_EN); + if (ret) + return ret; + ret = regmap_write(ak8974->map, AK8974_CTRL3, 0); + if (ret) + return ret; + if (ak8974->variant == AK8974_WHOAMI_VALUE_AMI306) { + /* magic from datasheet: set high-speed measurement mode */ + ret = ak8974_set_u16_val(ak8974, AMI306_CTRL4, 0xA07E); + if (ret) + return ret; + } + if (ak8974->variant == AK8974_WHOAMI_VALUE_HSCDTD008A) + return 0; + ret = regmap_write(ak8974->map, AK8974_INT_CTRL, AK8974_INT_CTRL_POL); + if (ret) + return ret; + + return regmap_write(ak8974->map, AK8974_PRESET, 0); +} + +static int ak8974_trigmeas(struct ak8974 *ak8974) +{ + unsigned int clear; + u8 mask; + u8 val; + int ret; + + /* Clear any previous measurement overflow status */ + ret = regmap_read(ak8974->map, AK8974_INT_CLEAR, &clear); + if (ret) + return ret; + + /* If we have a DRDY IRQ line, use it */ + if (ak8974->drdy_irq) { + mask = AK8974_CTRL2_INT_EN | + AK8974_CTRL2_DRDY_EN | + AK8974_CTRL2_DRDY_POL; + val = AK8974_CTRL2_DRDY_EN; + + if (!ak8974->drdy_active_low) + val |= AK8974_CTRL2_DRDY_POL; + + init_completion(&ak8974->drdy_complete); + ret = regmap_update_bits(ak8974->map, AK8974_CTRL2, + mask, val); + if (ret) + return ret; + } + + /* Force a measurement */ + return regmap_set_bits(ak8974->map, AK8974_CTRL3, AK8974_CTRL3_FORCE); +} + +static int ak8974_await_drdy(struct ak8974 *ak8974) +{ + int timeout = 2; + unsigned int val; + int ret; + + if (ak8974->drdy_irq) { + ret = wait_for_completion_timeout(&ak8974->drdy_complete, + 1 + msecs_to_jiffies(1000)); + if (!ret) { + dev_err(&ak8974->i2c->dev, + "timeout waiting for DRDY IRQ\n"); + return -ETIMEDOUT; + } + return 0; + } + + /* Default delay-based poll loop */ + do { + msleep(AK8974_MEASTIME); + ret = regmap_read(ak8974->map, AK8974_STATUS, &val); + if (ret < 0) + return ret; + if (val & AK8974_STATUS_DRDY) + return 0; + } while (--timeout); + + dev_err(&ak8974->i2c->dev, "timeout waiting for DRDY\n"); + return -ETIMEDOUT; +} + +static int ak8974_getresult(struct ak8974 *ak8974, __le16 *result) +{ + unsigned int src; + int ret; + + ret = ak8974_await_drdy(ak8974); + if (ret) + return ret; + ret = regmap_read(ak8974->map, AK8974_INT_SRC, &src); + if (ret < 0) + return ret; + + /* Out of range overflow! Strong magnet close? */ + if (src & AK8974_INT_RANGE) { + dev_err(&ak8974->i2c->dev, + "range overflow in sensor\n"); + return -ERANGE; + } + + return regmap_bulk_read(ak8974->map, AK8974_DATA_X, result, 6); +} + +static irqreturn_t ak8974_drdy_irq(int irq, void *d) +{ + struct ak8974 *ak8974 = d; + + if (!ak8974->drdy_irq) + return IRQ_NONE; + + /* TODO: timestamp here to get good measurement stamps */ + return IRQ_WAKE_THREAD; +} + +static irqreturn_t ak8974_drdy_irq_thread(int irq, void *d) +{ + struct ak8974 *ak8974 = d; + unsigned int val; + int ret; + + /* Check if this was a DRDY from us */ + ret = regmap_read(ak8974->map, AK8974_STATUS, &val); + if (ret < 0) { + dev_err(&ak8974->i2c->dev, "error reading DRDY status\n"); + return IRQ_HANDLED; + } + if (val & AK8974_STATUS_DRDY) { + /* Yes this was our IRQ */ + complete(&ak8974->drdy_complete); + return IRQ_HANDLED; + } + + /* We may be on a shared IRQ, let the next client check */ + return IRQ_NONE; +} + +static int ak8974_selftest(struct ak8974 *ak8974) +{ + struct device *dev = &ak8974->i2c->dev; + unsigned int val; + int ret; + + ret = regmap_read(ak8974->map, AK8974_SELFTEST, &val); + if (ret) + return ret; + if (val != AK8974_SELFTEST_IDLE) { + dev_err(dev, "selftest not idle before test\n"); + return -EIO; + } + + /* Trigger self-test */ + ret = regmap_set_bits(ak8974->map, AK8974_CTRL3, AK8974_CTRL3_SELFTEST); + if (ret) { + dev_err(dev, "could not write CTRL3\n"); + return ret; + } + + msleep(AK8974_SELFTEST_DELAY); + + ret = regmap_read(ak8974->map, AK8974_SELFTEST, &val); + if (ret) + return ret; + if (val != AK8974_SELFTEST_OK) { + dev_err(dev, "selftest result NOT OK (%02x)\n", val); + return -EIO; + } + + ret = regmap_read(ak8974->map, AK8974_SELFTEST, &val); + if (ret) + return ret; + if (val != AK8974_SELFTEST_IDLE) { + dev_err(dev, "selftest not idle after test (%02x)\n", val); + return -EIO; + } + dev_dbg(dev, "passed self-test\n"); + + return 0; +} + +static void ak8974_read_calib_data(struct ak8974 *ak8974, unsigned int reg, + __le16 *tab, size_t tab_size) +{ + int ret = regmap_bulk_read(ak8974->map, reg, tab, tab_size); + if (ret) { + memset(tab, 0xFF, tab_size); + dev_warn(&ak8974->i2c->dev, + "can't read calibration data (regs %u..%zu): %d\n", + reg, reg + tab_size - 1, ret); + } else { + add_device_randomness(tab, tab_size); + } +} + +static int ak8974_detect(struct ak8974 *ak8974) +{ + unsigned int whoami; + const char *name; + int ret; + unsigned int fw; + u16 sn; + + ret = regmap_read(ak8974->map, AK8974_WHOAMI, &whoami); + if (ret) + return ret; + + name = "ami305"; + + switch (whoami) { + case AK8974_WHOAMI_VALUE_AMI306: + name = "ami306"; + fallthrough; + case AK8974_WHOAMI_VALUE_AMI305: + ret = regmap_read(ak8974->map, AMI305_VER, &fw); + if (ret) + return ret; + fw &= 0x7f; /* only bits 0 thru 6 valid */ + ret = ak8974_get_u16_val(ak8974, AMI305_SN, &sn); + if (ret) + return ret; + add_device_randomness(&sn, sizeof(sn)); + dev_info(&ak8974->i2c->dev, + "detected %s, FW ver %02x, S/N: %04x\n", + name, fw, sn); + break; + case AK8974_WHOAMI_VALUE_AK8974: + name = "ak8974"; + dev_info(&ak8974->i2c->dev, "detected AK8974\n"); + break; + case AK8974_WHOAMI_VALUE_HSCDTD008A: + name = "hscdtd008a"; + dev_info(&ak8974->i2c->dev, "detected hscdtd008a\n"); + break; + default: + dev_err(&ak8974->i2c->dev, "unsupported device (%02x) ", + whoami); + return -ENODEV; + } + + ak8974->name = name; + ak8974->variant = whoami; + + if (whoami == AK8974_WHOAMI_VALUE_AMI306) { + __le16 fab_data1[9], fab_data2[3]; + int i; + + ak8974_read_calib_data(ak8974, AMI306_FINEOUTPUT_X, + fab_data1, sizeof(fab_data1)); + ak8974_read_calib_data(ak8974, AMI306_OFFZERO_X, + fab_data2, sizeof(fab_data2)); + + for (i = 0; i < 3; ++i) { + static const char axis[] = "XYZ"; + static const char pgaxis[] = "ZYZXYX"; + unsigned offz = le16_to_cpu(fab_data2[i]) & 0x7F; + unsigned fine = le16_to_cpu(fab_data1[i]); + unsigned sens = le16_to_cpu(fab_data1[i + 3]); + unsigned pgain1 = le16_to_cpu(fab_data1[i + 6]); + unsigned pgain2 = pgain1 >> 8; + + pgain1 &= 0xFF; + + dev_info(&ak8974->i2c->dev, + "factory calibration for axis %c: offz=%u sens=%u fine=%u pga%c=%u pga%c=%u\n", + axis[i], offz, sens, fine, pgaxis[i * 2], + pgain1, pgaxis[i * 2 + 1], pgain2); + } + } + + return 0; +} + +static int ak8974_measure_channel(struct ak8974 *ak8974, unsigned long address, + int *val) +{ + __le16 hw_values[3]; + int ret; + + pm_runtime_get_sync(&ak8974->i2c->dev); + mutex_lock(&ak8974->lock); + + /* + * We read all axes and discard all but one, for optimized + * reading, use the triggered buffer. + */ + ret = ak8974_trigmeas(ak8974); + if (ret) + goto out_unlock; + ret = ak8974_getresult(ak8974, hw_values); + if (ret) + goto out_unlock; + /* + * This explicit cast to (s16) is necessary as the measurement + * is done in 2's complement with positive and negative values. + * The following assignment to *val will then convert the signed + * s16 value to a signed int value. + */ + *val = (s16)le16_to_cpu(hw_values[address]); +out_unlock: + mutex_unlock(&ak8974->lock); + pm_runtime_put_autosuspend(&ak8974->i2c->dev); + + return ret; +} + +static int ak8974_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct ak8974 *ak8974 = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->address > 2) { + dev_err(&ak8974->i2c->dev, "faulty channel address\n"); + return -EIO; + } + ret = ak8974_measure_channel(ak8974, chan->address, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (ak8974->variant) { + case AK8974_WHOAMI_VALUE_AMI306: + case AK8974_WHOAMI_VALUE_AMI305: + /* + * The datasheet for AMI305 and AMI306, page 6 + * specifies the range of the sensor to be + * +/- 12 Gauss. + */ + *val = 12; + /* + * 12 bits are used, +/- 2^11 + * [ -2048 .. 2047 ] (manual page 20) + * [ 0xf800 .. 0x07ff ] + */ + *val2 = 11; + return IIO_VAL_FRACTIONAL_LOG2; + case AK8974_WHOAMI_VALUE_HSCDTD008A: + /* + * The datasheet for HSCDTF008A, page 3 specifies the + * range of the sensor as +/- 2.4 mT per axis, which + * corresponds to +/- 2400 uT = +/- 24 Gauss. + */ + *val = 24; + /* + * 15 bits are used (set up in CTRL4), +/- 2^14 + * [ -16384 .. 16383 ] (manual page 24) + * [ 0xc000 .. 0x3fff ] + */ + *val2 = 14; + return IIO_VAL_FRACTIONAL_LOG2; + default: + /* GUESSING +/- 12 Gauss */ + *val = 12; + /* GUESSING 12 bits ADC +/- 2^11 */ + *val2 = 11; + return IIO_VAL_FRACTIONAL_LOG2; + } + break; + default: + /* Unknown request */ + break; + } + + return -EINVAL; +} + +static void ak8974_fill_buffer(struct iio_dev *indio_dev) +{ + struct ak8974 *ak8974 = iio_priv(indio_dev); + int ret; + + pm_runtime_get_sync(&ak8974->i2c->dev); + mutex_lock(&ak8974->lock); + + ret = ak8974_trigmeas(ak8974); + if (ret) { + dev_err(&ak8974->i2c->dev, "error triggering measure\n"); + goto out_unlock; + } + ret = ak8974_getresult(ak8974, ak8974->scan.channels); + if (ret) { + dev_err(&ak8974->i2c->dev, "error getting measures\n"); + goto out_unlock; + } + + iio_push_to_buffers_with_ts(indio_dev, &ak8974->scan, sizeof(ak8974->scan), + iio_get_time_ns(indio_dev)); + + out_unlock: + mutex_unlock(&ak8974->lock); + pm_runtime_put_autosuspend(&ak8974->i2c->dev); +} + +static irqreturn_t ak8974_handle_trigger(int irq, void *p) +{ + const struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + + ak8974_fill_buffer(indio_dev); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_mount_matrix * +ak8974_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ak8974 *ak8974 = iio_priv(indio_dev); + + return &ak8974->orientation; +} + +static const struct iio_chan_spec_ext_info ak8974_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, ak8974_get_mount_matrix), + { } +}; + +#define AK8974_AXIS_CHANNEL(axis, index, bits) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info = ak8974_ext_info, \ + .address = index, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = bits, \ + .storagebits = 16, \ + .endianness = IIO_LE \ + }, \ + } + +/* + * We have no datasheet for the AK8974 but we guess that its + * ADC is 12 bits. The AMI305 and AMI306 certainly has 12bit + * ADC. + */ +static const struct iio_chan_spec ak8974_12_bits_channels[] = { + AK8974_AXIS_CHANNEL(X, 0, 12), + AK8974_AXIS_CHANNEL(Y, 1, 12), + AK8974_AXIS_CHANNEL(Z, 2, 12), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +/* + * The HSCDTD008A has 15 bits resolution the way we set it up + * in CTRL4. + */ +static const struct iio_chan_spec ak8974_15_bits_channels[] = { + AK8974_AXIS_CHANNEL(X, 0, 15), + AK8974_AXIS_CHANNEL(Y, 1, 15), + AK8974_AXIS_CHANNEL(Z, 2, 15), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const unsigned long ak8974_scan_masks[] = { 0x7, 0 }; + +static const struct iio_info ak8974_info = { + .read_raw = &ak8974_read_raw, +}; + +static bool ak8974_writeable_reg(struct device *dev, unsigned int reg) +{ + struct i2c_client *i2c = to_i2c_client(dev); + struct iio_dev *indio_dev = i2c_get_clientdata(i2c); + struct ak8974 *ak8974 = iio_priv(indio_dev); + + switch (reg) { + case AK8974_CTRL1: + case AK8974_CTRL2: + case AK8974_CTRL3: + case AK8974_INT_CTRL: + case AK8974_INT_THRES: + case AK8974_INT_THRES + 1: + return true; + case AK8974_PRESET: + case AK8974_PRESET + 1: + return ak8974->variant != AK8974_WHOAMI_VALUE_HSCDTD008A; + case AK8974_OFFSET_X: + case AK8974_OFFSET_X + 1: + case AK8974_OFFSET_Y: + case AK8974_OFFSET_Y + 1: + case AK8974_OFFSET_Z: + case AK8974_OFFSET_Z + 1: + return ak8974->variant == AK8974_WHOAMI_VALUE_AK8974 || + ak8974->variant == AK8974_WHOAMI_VALUE_HSCDTD008A; + case AMI305_OFFSET_X: + case AMI305_OFFSET_X + 1: + case AMI305_OFFSET_Y: + case AMI305_OFFSET_Y + 1: + case AMI305_OFFSET_Z: + case AMI305_OFFSET_Z + 1: + return ak8974->variant == AK8974_WHOAMI_VALUE_AMI305 || + ak8974->variant == AK8974_WHOAMI_VALUE_AMI306; + case AMI306_CTRL4: + case AMI306_CTRL4 + 1: + return ak8974->variant == AK8974_WHOAMI_VALUE_AMI306; + default: + return false; + } +} + +static bool ak8974_precious_reg(struct device *dev, unsigned int reg) +{ + return reg == AK8974_INT_CLEAR; +} + +static const struct regmap_config ak8974_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + .writeable_reg = ak8974_writeable_reg, + .precious_reg = ak8974_precious_reg, +}; + +static int ak8974_probe(struct i2c_client *i2c) +{ + struct iio_dev *indio_dev; + struct ak8974 *ak8974; + unsigned long irq_trig; + int irq = i2c->irq; + int ret; + + /* Register with IIO */ + indio_dev = devm_iio_device_alloc(&i2c->dev, sizeof(*ak8974)); + if (indio_dev == NULL) + return -ENOMEM; + + ak8974 = iio_priv(indio_dev); + i2c_set_clientdata(i2c, indio_dev); + ak8974->i2c = i2c; + mutex_init(&ak8974->lock); + + ret = iio_read_mount_matrix(&i2c->dev, &ak8974->orientation); + if (ret) + return ret; + + ak8974->regs[0].supply = ak8974_reg_avdd; + ak8974->regs[1].supply = ak8974_reg_dvdd; + + ret = devm_regulator_bulk_get(&i2c->dev, + ARRAY_SIZE(ak8974->regs), + ak8974->regs); + if (ret < 0) + return dev_err_probe(&i2c->dev, ret, "cannot get regulators\n"); + + ret = regulator_bulk_enable(ARRAY_SIZE(ak8974->regs), ak8974->regs); + if (ret < 0) { + dev_err(&i2c->dev, "cannot enable regulators\n"); + return ret; + } + + /* Take runtime PM online */ + pm_runtime_get_noresume(&i2c->dev); + pm_runtime_set_active(&i2c->dev); + pm_runtime_enable(&i2c->dev); + + ak8974->map = devm_regmap_init_i2c(i2c, &ak8974_regmap_config); + if (IS_ERR(ak8974->map)) { + dev_err(&i2c->dev, "failed to allocate register map\n"); + pm_runtime_put_noidle(&i2c->dev); + pm_runtime_disable(&i2c->dev); + return PTR_ERR(ak8974->map); + } + + ret = ak8974_set_power(ak8974, AK8974_PWR_ON); + if (ret) { + dev_err(&i2c->dev, "could not power on\n"); + goto disable_pm; + } + + ret = ak8974_detect(ak8974); + if (ret) { + dev_err(&i2c->dev, "neither AK8974 nor AMI30x found\n"); + goto disable_pm; + } + + ret = ak8974_selftest(ak8974); + if (ret) + dev_err(&i2c->dev, "selftest failed (continuing anyway)\n"); + + ret = ak8974_reset(ak8974); + if (ret) { + dev_err(&i2c->dev, "AK8974 reset failed\n"); + goto disable_pm; + } + + switch (ak8974->variant) { + case AK8974_WHOAMI_VALUE_AMI306: + case AK8974_WHOAMI_VALUE_AMI305: + indio_dev->channels = ak8974_12_bits_channels; + indio_dev->num_channels = ARRAY_SIZE(ak8974_12_bits_channels); + break; + case AK8974_WHOAMI_VALUE_HSCDTD008A: + indio_dev->channels = ak8974_15_bits_channels; + indio_dev->num_channels = ARRAY_SIZE(ak8974_15_bits_channels); + break; + default: + indio_dev->channels = ak8974_12_bits_channels; + indio_dev->num_channels = ARRAY_SIZE(ak8974_12_bits_channels); + break; + } + indio_dev->info = &ak8974_info; + indio_dev->available_scan_masks = ak8974_scan_masks; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = ak8974->name; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + ak8974_handle_trigger, + NULL); + if (ret) { + dev_err(&i2c->dev, "triggered buffer setup failed\n"); + goto disable_pm; + } + + /* If we have a valid DRDY IRQ, make use of it */ + if (irq > 0) { + irq_trig = irq_get_trigger_type(irq); + if (irq_trig == IRQF_TRIGGER_RISING) { + dev_info(&i2c->dev, "enable rising edge DRDY IRQ\n"); + } else if (irq_trig == IRQF_TRIGGER_FALLING) { + ak8974->drdy_active_low = true; + dev_info(&i2c->dev, "enable falling edge DRDY IRQ\n"); + } else { + irq_trig = IRQF_TRIGGER_RISING; + } + irq_trig |= IRQF_ONESHOT; + irq_trig |= IRQF_SHARED; + + ret = devm_request_threaded_irq(&i2c->dev, + irq, + ak8974_drdy_irq, + ak8974_drdy_irq_thread, + irq_trig, + ak8974->name, + ak8974); + if (ret) + goto no_irq; + ak8974->drdy_irq = true; + } + +no_irq: + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&i2c->dev, "device register failed\n"); + goto cleanup_buffer; + } + + pm_runtime_set_autosuspend_delay(&i2c->dev, + AK8974_AUTOSUSPEND_DELAY); + pm_runtime_use_autosuspend(&i2c->dev); + pm_runtime_put(&i2c->dev); + + return 0; + +cleanup_buffer: + iio_triggered_buffer_cleanup(indio_dev); +disable_pm: + pm_runtime_put_noidle(&i2c->dev); + pm_runtime_disable(&i2c->dev); + ak8974_set_power(ak8974, AK8974_PWR_OFF); + regulator_bulk_disable(ARRAY_SIZE(ak8974->regs), ak8974->regs); + + return ret; +} + +static void ak8974_remove(struct i2c_client *i2c) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(i2c); + struct ak8974 *ak8974 = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + pm_runtime_get_sync(&i2c->dev); + pm_runtime_put_noidle(&i2c->dev); + pm_runtime_disable(&i2c->dev); + ak8974_set_power(ak8974, AK8974_PWR_OFF); + regulator_bulk_disable(ARRAY_SIZE(ak8974->regs), ak8974->regs); +} + +static int ak8974_runtime_suspend(struct device *dev) +{ + struct ak8974 *ak8974 = + iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + ak8974_set_power(ak8974, AK8974_PWR_OFF); + regulator_bulk_disable(ARRAY_SIZE(ak8974->regs), ak8974->regs); + + return 0; +} + +static int ak8974_runtime_resume(struct device *dev) +{ + struct ak8974 *ak8974 = + iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(ak8974->regs), ak8974->regs); + if (ret) + return ret; + msleep(AK8974_POWERON_DELAY); + ret = ak8974_set_power(ak8974, AK8974_PWR_ON); + if (ret) + goto out_regulator_disable; + + ret = ak8974_configure(ak8974); + if (ret) + goto out_disable_power; + + return 0; + +out_disable_power: + ak8974_set_power(ak8974, AK8974_PWR_OFF); +out_regulator_disable: + regulator_bulk_disable(ARRAY_SIZE(ak8974->regs), ak8974->regs); + + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(ak8974_dev_pm_ops, ak8974_runtime_suspend, + ak8974_runtime_resume, NULL); + +static const struct i2c_device_id ak8974_id[] = { + { .name = "ami305" }, + { .name = "ami306" }, + { .name = "ak8974" }, + { .name = "hscdtd008a" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ak8974_id); + +static const struct of_device_id ak8974_of_match[] = { + { .compatible = "asahi-kasei,ak8974", }, + { .compatible = "alps,hscdtd008a", }, + { } +}; +MODULE_DEVICE_TABLE(of, ak8974_of_match); + +static struct i2c_driver ak8974_driver = { + .driver = { + .name = "ak8974", + .pm = pm_ptr(&ak8974_dev_pm_ops), + .of_match_table = ak8974_of_match, + }, + .probe = ak8974_probe, + .remove = ak8974_remove, + .id_table = ak8974_id, +}; +module_i2c_driver(ak8974_driver); + +MODULE_DESCRIPTION("AK8974 and AMI30x 3-axis magnetometer driver"); +MODULE_AUTHOR("Samu Onkalo"); +MODULE_AUTHOR("Linus Walleij"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/magnetometer/ak8975.c b/drivers/iio/magnetometer/ak8975.c new file mode 100644 index 000000000..8b0c07f82 --- /dev/null +++ b/drivers/iio/magnetometer/ak8975.c @@ -0,0 +1,1175 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * A sensor driver for the magnetometer AK8975. + * + * Magnetic compass sensor driver for monitoring magnetic flux information. + * + * Copyright (c) 2010, NVIDIA Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +/* + * Register definitions, as well as various shifts and masks to get at the + * individual fields of the registers. + */ +#define AK8975_REG_WIA 0x00 +#define AK8975_DEVICE_ID 0x48 + +#define AK8975_REG_INFO 0x01 + +#define AK8975_REG_ST1 0x02 +#define AK8975_REG_ST1_DRDY_SHIFT 0 +#define AK8975_REG_ST1_DRDY_MASK (1 << AK8975_REG_ST1_DRDY_SHIFT) + +#define AK8975_REG_HXL 0x03 +#define AK8975_REG_HXH 0x04 +#define AK8975_REG_HYL 0x05 +#define AK8975_REG_HYH 0x06 +#define AK8975_REG_HZL 0x07 +#define AK8975_REG_HZH 0x08 +#define AK8975_REG_ST2 0x09 +#define AK8975_REG_ST2_DERR_SHIFT 2 +#define AK8975_REG_ST2_DERR_MASK (1 << AK8975_REG_ST2_DERR_SHIFT) + +#define AK8975_REG_ST2_HOFL_SHIFT 3 +#define AK8975_REG_ST2_HOFL_MASK (1 << AK8975_REG_ST2_HOFL_SHIFT) + +#define AK8975_REG_CNTL 0x0A +#define AK8975_REG_CNTL_MODE_SHIFT 0 +#define AK8975_REG_CNTL_MODE_MASK (0xF << AK8975_REG_CNTL_MODE_SHIFT) +#define AK8975_REG_CNTL_MODE_POWER_DOWN 0x00 +#define AK8975_REG_CNTL_MODE_ONCE 0x01 +#define AK8975_REG_CNTL_MODE_SELF_TEST 0x08 +#define AK8975_REG_CNTL_MODE_FUSE_ROM 0x0F + +#define AK8975_REG_RSVC 0x0B +#define AK8975_REG_ASTC 0x0C +#define AK8975_REG_TS1 0x0D +#define AK8975_REG_TS2 0x0E +#define AK8975_REG_I2CDIS 0x0F +#define AK8975_REG_ASAX 0x10 +#define AK8975_REG_ASAY 0x11 +#define AK8975_REG_ASAZ 0x12 + +#define AK8975_MAX_REGS AK8975_REG_ASAZ + +/* + * AK09912 Register definitions + */ +#define AK09912_REG_WIA1 0x00 +#define AK09912_REG_WIA2 0x01 +#define AK09918_DEVICE_ID 0x0C +#define AK09916_DEVICE_ID 0x09 +#define AK09912_DEVICE_ID 0x04 +#define AK09911_DEVICE_ID 0x05 + +#define AK09911_REG_INFO1 0x02 +#define AK09911_REG_INFO2 0x03 + +#define AK09912_REG_ST1 0x10 + +#define AK09912_REG_ST1_DRDY_SHIFT 0 +#define AK09912_REG_ST1_DRDY_MASK (1 << AK09912_REG_ST1_DRDY_SHIFT) + +#define AK09912_REG_HXL 0x11 +#define AK09912_REG_HXH 0x12 +#define AK09912_REG_HYL 0x13 +#define AK09912_REG_HYH 0x14 +#define AK09912_REG_HZL 0x15 +#define AK09912_REG_HZH 0x16 +#define AK09912_REG_TMPS 0x17 + +#define AK09912_REG_ST2 0x18 +#define AK09912_REG_ST2_HOFL_SHIFT 3 +#define AK09912_REG_ST2_HOFL_MASK (1 << AK09912_REG_ST2_HOFL_SHIFT) + +#define AK09912_REG_CNTL1 0x30 + +#define AK09912_REG_CNTL2 0x31 +#define AK09912_REG_CNTL_MODE_POWER_DOWN 0x00 +#define AK09912_REG_CNTL_MODE_ONCE 0x01 +#define AK09912_REG_CNTL_MODE_SELF_TEST 0x10 +#define AK09912_REG_CNTL_MODE_FUSE_ROM 0x1F +#define AK09912_REG_CNTL2_MODE_SHIFT 0 +#define AK09912_REG_CNTL2_MODE_MASK (0x1F << AK09912_REG_CNTL2_MODE_SHIFT) + +#define AK09912_REG_CNTL3 0x32 + +#define AK09912_REG_TS1 0x33 +#define AK09912_REG_TS2 0x34 +#define AK09912_REG_TS3 0x35 +#define AK09912_REG_I2CDIS 0x36 +#define AK09912_REG_TS4 0x37 + +#define AK09912_REG_ASAX 0x60 +#define AK09912_REG_ASAY 0x61 +#define AK09912_REG_ASAZ 0x62 + +#define AK09912_MAX_REGS AK09912_REG_ASAZ + +/* + * Precalculate scale factor (in Gauss units) for each axis and + * store in the device data. + * + * This scale factor is axis-dependent, and is derived from 3 calibration + * factors ASA(x), ASA(y), and ASA(z). + * + * These ASA values are read from the sensor device at start of day, and + * cached in the device context struct. + * + * Adjusting the flux value with the sensitivity adjustment value should be + * done via the following formula: + * + * Hadj = H * ( ( ( (ASA-128)*0.5 ) / 128 ) + 1 ) + * where H is the raw value, ASA is the sensitivity adjustment, and Hadj + * is the resultant adjusted value. + * + * We reduce the formula to: + * + * Hadj = H * (ASA + 128) / 256 + * + * H is in the range of -4096 to 4095. The magnetometer has a range of + * +-1229uT. To go from the raw value to uT is: + * + * HuT = H * 1229/4096, or roughly, 3/10. + * + * Since 1uT = 0.01 gauss, our final scale factor becomes: + * + * Hadj = H * ((ASA + 128) / 256) * 3/10 * 1/100 + * Hadj = H * ((ASA + 128) * 0.003) / 256 + * + * Since ASA doesn't change, we cache the resultant scale factor into the + * device context in ak8975_setup(). + * + * Given we use IIO_VAL_INT_PLUS_MICRO bit when displaying the scale, we + * multiply the stored scale value by 1e6. + */ +static long ak8975_raw_to_gauss(u16 data) +{ + return (((long)data + 128) * 3000) / 256; +} + +/* + * For AK8963 and AK09911, same calculation, but the device is less sensitive: + * + * H is in the range of +-8190. The magnetometer has a range of + * +-4912uT. To go from the raw value to uT is: + * + * HuT = H * 4912/8190, or roughly, 6/10, instead of 3/10. + */ + +static long ak8963_09911_raw_to_gauss(u16 data) +{ + return (((long)data + 128) * 6000) / 256; +} + +/* + * For AK09912, same calculation, except the device is more sensitive: + * + * H is in the range of -32752 to 32752. The magnetometer has a range of + * +-4912uT. To go from the raw value to uT is: + * + * HuT = H * 4912/32752, or roughly, 3/20, instead of 3/10. + */ +static long ak09912_raw_to_gauss(u16 data) +{ + return (((long)data + 128) * 1500) / 256; +} + +/* Compatible Asahi Kasei Compass parts */ +enum asahi_compass_chipset { + AK8975, + AK8963, + AK09911, + AK09912, + AK09916, + AK09918, +}; + +enum ak_ctrl_reg_addr { + ST1, + ST2, + CNTL, + ASA_BASE, + MAX_REGS, + REGS_END, +}; + +enum ak_ctrl_reg_mask { + ST1_DRDY, + ST2_HOFL, + ST2_DERR, + CNTL_MODE, + MASK_END, +}; + +enum ak_ctrl_mode { + POWER_DOWN, + MODE_ONCE, + SELF_TEST, + FUSE_ROM, + MODE_END, +}; + +struct ak_def { + enum asahi_compass_chipset type; + long (*raw_to_gauss)(u16 data); + u16 range; + u8 ctrl_regs[REGS_END]; + u8 ctrl_masks[MASK_END]; + u8 ctrl_modes[MODE_END]; + u8 data_regs[3]; +}; + +static const struct ak_def ak_def_array[] = { + [AK8975] = { + .type = AK8975, + .raw_to_gauss = ak8975_raw_to_gauss, + .range = 4096, + .ctrl_regs = { + AK8975_REG_ST1, + AK8975_REG_ST2, + AK8975_REG_CNTL, + AK8975_REG_ASAX, + AK8975_MAX_REGS}, + .ctrl_masks = { + AK8975_REG_ST1_DRDY_MASK, + AK8975_REG_ST2_HOFL_MASK, + AK8975_REG_ST2_DERR_MASK, + AK8975_REG_CNTL_MODE_MASK}, + .ctrl_modes = { + AK8975_REG_CNTL_MODE_POWER_DOWN, + AK8975_REG_CNTL_MODE_ONCE, + AK8975_REG_CNTL_MODE_SELF_TEST, + AK8975_REG_CNTL_MODE_FUSE_ROM}, + .data_regs = { + AK8975_REG_HXL, + AK8975_REG_HYL, + AK8975_REG_HZL}, + }, + [AK8963] = { + .type = AK8963, + .raw_to_gauss = ak8963_09911_raw_to_gauss, + .range = 8190, + .ctrl_regs = { + AK8975_REG_ST1, + AK8975_REG_ST2, + AK8975_REG_CNTL, + AK8975_REG_ASAX, + AK8975_MAX_REGS}, + .ctrl_masks = { + AK8975_REG_ST1_DRDY_MASK, + AK8975_REG_ST2_HOFL_MASK, + 0, + AK8975_REG_CNTL_MODE_MASK}, + .ctrl_modes = { + AK8975_REG_CNTL_MODE_POWER_DOWN, + AK8975_REG_CNTL_MODE_ONCE, + AK8975_REG_CNTL_MODE_SELF_TEST, + AK8975_REG_CNTL_MODE_FUSE_ROM}, + .data_regs = { + AK8975_REG_HXL, + AK8975_REG_HYL, + AK8975_REG_HZL}, + }, + [AK09911] = { + .type = AK09911, + .raw_to_gauss = ak8963_09911_raw_to_gauss, + .range = 8192, + .ctrl_regs = { + AK09912_REG_ST1, + AK09912_REG_ST2, + AK09912_REG_CNTL2, + AK09912_REG_ASAX, + AK09912_MAX_REGS}, + .ctrl_masks = { + AK09912_REG_ST1_DRDY_MASK, + AK09912_REG_ST2_HOFL_MASK, + 0, + AK09912_REG_CNTL2_MODE_MASK}, + .ctrl_modes = { + AK09912_REG_CNTL_MODE_POWER_DOWN, + AK09912_REG_CNTL_MODE_ONCE, + AK09912_REG_CNTL_MODE_SELF_TEST, + AK09912_REG_CNTL_MODE_FUSE_ROM}, + .data_regs = { + AK09912_REG_HXL, + AK09912_REG_HYL, + AK09912_REG_HZL}, + }, + [AK09912] = { + .type = AK09912, + .raw_to_gauss = ak09912_raw_to_gauss, + .range = 32752, + .ctrl_regs = { + AK09912_REG_ST1, + AK09912_REG_ST2, + AK09912_REG_CNTL2, + AK09912_REG_ASAX, + AK09912_MAX_REGS}, + .ctrl_masks = { + AK09912_REG_ST1_DRDY_MASK, + AK09912_REG_ST2_HOFL_MASK, + 0, + AK09912_REG_CNTL2_MODE_MASK}, + .ctrl_modes = { + AK09912_REG_CNTL_MODE_POWER_DOWN, + AK09912_REG_CNTL_MODE_ONCE, + AK09912_REG_CNTL_MODE_SELF_TEST, + AK09912_REG_CNTL_MODE_FUSE_ROM}, + .data_regs = { + AK09912_REG_HXL, + AK09912_REG_HYL, + AK09912_REG_HZL}, + }, + [AK09916] = { + .type = AK09916, + .raw_to_gauss = ak09912_raw_to_gauss, + .range = 32752, + .ctrl_regs = { + AK09912_REG_ST1, + AK09912_REG_ST2, + AK09912_REG_CNTL2, + AK09912_REG_ASAX, + AK09912_MAX_REGS}, + .ctrl_masks = { + AK09912_REG_ST1_DRDY_MASK, + AK09912_REG_ST2_HOFL_MASK, + 0, + AK09912_REG_CNTL2_MODE_MASK}, + .ctrl_modes = { + AK09912_REG_CNTL_MODE_POWER_DOWN, + AK09912_REG_CNTL_MODE_ONCE, + AK09912_REG_CNTL_MODE_SELF_TEST, + AK09912_REG_CNTL_MODE_FUSE_ROM}, + .data_regs = { + AK09912_REG_HXL, + AK09912_REG_HYL, + AK09912_REG_HZL}, + }, + [AK09918] = { + /* ak09918 is register compatible with ak09912 this is for avoid + * unknown id messages. + */ + .type = AK09918, + .raw_to_gauss = ak09912_raw_to_gauss, + .range = 32752, + .ctrl_regs = { + AK09912_REG_ST1, + AK09912_REG_ST2, + AK09912_REG_CNTL2, + AK09912_REG_ASAX, + AK09912_MAX_REGS}, + .ctrl_masks = { + AK09912_REG_ST1_DRDY_MASK, + AK09912_REG_ST2_HOFL_MASK, + 0, + AK09912_REG_CNTL2_MODE_MASK}, + .ctrl_modes = { + AK09912_REG_CNTL_MODE_POWER_DOWN, + AK09912_REG_CNTL_MODE_ONCE, + AK09912_REG_CNTL_MODE_SELF_TEST, + AK09912_REG_CNTL_MODE_FUSE_ROM}, + .data_regs = { + AK09912_REG_HXL, + AK09912_REG_HYL, + AK09912_REG_HZL}, + } +}; + +/* + * Per-instance context data for the device. + */ +struct ak8975_data { + struct i2c_client *client; + const struct ak_def *def; + struct mutex lock; + u8 asa[3]; + long raw_to_gauss[3]; + struct gpio_desc *eoc_gpiod; + struct gpio_desc *reset_gpiod; + int eoc_irq; + wait_queue_head_t data_ready_queue; + unsigned long flags; + u8 cntl_cache; + struct iio_mount_matrix orientation; + struct regulator *vdd; + struct regulator *vid; + + /* Ensure natural alignment of timestamp */ + struct { + s16 channels[3]; + aligned_s64 ts; + } scan; +}; + +/* Enable attached power regulator if any. */ +static int ak8975_power_on(const struct ak8975_data *data) +{ + int ret; + + ret = regulator_enable(data->vdd); + if (ret) { + dev_warn(&data->client->dev, + "Failed to enable specified Vdd supply\n"); + return ret; + } + ret = regulator_enable(data->vid); + if (ret) { + dev_warn(&data->client->dev, + "Failed to enable specified Vid supply\n"); + regulator_disable(data->vdd); + return ret; + } + + gpiod_set_value_cansleep(data->reset_gpiod, 0); + + /* + * According to the datasheet the power supply rise time is 200us + * and the minimum wait time before mode setting is 100us, in + * total 300us. Add some margin and say minimum 500us here. + */ + fsleep(500); + + return 0; +} + +/* Disable attached power regulator if any. */ +static void ak8975_power_off(const struct ak8975_data *data) +{ + gpiod_set_value_cansleep(data->reset_gpiod, 1); + + regulator_disable(data->vid); + regulator_disable(data->vdd); +} + +/* + * Return 0 if the i2c device is the one we expect. + * return a negative error number otherwise + */ +static int ak8975_who_i_am(const struct ak8975_data *data, + enum asahi_compass_chipset type) +{ + struct i2c_client *client = data->client; + u8 wia_val[2]; + int ret; + + /* + * Signature for each device: + * Device | WIA1 | WIA2 + * AK09918 | DEVICE_ID_| AK09918_DEVICE_ID + * AK09916 | DEVICE_ID_| AK09916_DEVICE_ID + * AK09912 | DEVICE_ID | AK09912_DEVICE_ID + * AK09911 | DEVICE_ID | AK09911_DEVICE_ID + * AK8975 | DEVICE_ID | NA + * AK8963 | DEVICE_ID | NA + */ + ret = i2c_smbus_read_i2c_block_data_or_emulated(client, + AK09912_REG_WIA1, + sizeof(wia_val), + wia_val); + if (ret < 0) { + dev_err(&client->dev, "Error reading WIA\n"); + return ret; + } + if (ret != sizeof(wia_val)) { + dev_err(&client->dev, "Error reading WIA\n"); + return -EIO; + } + + if (wia_val[0] != AK8975_DEVICE_ID) + return -ENODEV; + + switch (type) { + case AK8975: + case AK8963: + return 0; + case AK09911: + if (wia_val[1] == AK09911_DEVICE_ID) + return 0; + break; + case AK09912: + if (wia_val[1] == AK09912_DEVICE_ID) + return 0; + break; + case AK09916: + if (wia_val[1] == AK09916_DEVICE_ID) + return 0; + break; + case AK09918: + if (wia_val[1] == AK09918_DEVICE_ID) + return 0; + break; + } + + dev_info(&client->dev, "Device ID %x is unknown.\n", wia_val[1]); + /* + * Let driver to probe on unknown id for support more register + * compatible variants. + */ + return 0; +} + +/* + * Helper function to write to CNTL register. + */ +static int ak8975_set_mode(struct ak8975_data *data, enum ak_ctrl_mode mode) +{ + u8 regval; + int ret; + + regval = (data->cntl_cache & ~data->def->ctrl_masks[CNTL_MODE]) | + data->def->ctrl_modes[mode]; + ret = i2c_smbus_write_byte_data(data->client, + data->def->ctrl_regs[CNTL], regval); + if (ret < 0) + return ret; + + data->cntl_cache = regval; + /* After mode change wait at least 100us */ + fsleep(100); + + return 0; +} + +/* + * Handle data ready irq + */ +static irqreturn_t ak8975_irq_handler(int irq, void *data) +{ + struct ak8975_data *ak8975 = data; + + set_bit(0, &ak8975->flags); + wake_up(&ak8975->data_ready_queue); + + return IRQ_HANDLED; +} + +/* + * Install data ready interrupt handler + */ +static int ak8975_setup_irq(struct ak8975_data *data) +{ + struct i2c_client *client = data->client; + int irq; + int ret; + + init_waitqueue_head(&data->data_ready_queue); + clear_bit(0, &data->flags); + if (client->irq) + irq = client->irq; + else + irq = gpiod_to_irq(data->eoc_gpiod); + + ret = devm_request_irq(&client->dev, irq, ak8975_irq_handler, + IRQF_TRIGGER_RISING, + dev_name(&client->dev), data); + if (ret) + return ret; + + data->eoc_irq = irq; + + return 0; +} + +/* + * Perform some start-of-day setup, including reading the asa calibration + * values and caching them. + */ +static int ak8975_setup(struct ak8975_data *data) +{ + struct i2c_client *client = data->client; + int ret; + + /* Write the fused rom access mode. */ + ret = ak8975_set_mode(data, FUSE_ROM); + if (ret < 0) { + dev_err(&client->dev, "Error in setting fuse access mode\n"); + return ret; + } + + /* Get asa data and store in the device data. */ + ret = i2c_smbus_read_i2c_block_data_or_emulated(client, + data->def->ctrl_regs[ASA_BASE], + sizeof(data->asa), + data->asa); + if (ret < 0) { + dev_err(&client->dev, "Not able to read asa data\n"); + return ret; + } + if (ret != sizeof(data->asa)) { + dev_err(&client->dev, "Error reading asa data\n"); + return -EIO; + } + + /* After reading fuse ROM data set power-down mode */ + ret = ak8975_set_mode(data, POWER_DOWN); + if (ret < 0) { + dev_err(&client->dev, "Error in setting power-down mode\n"); + return ret; + } + + if (data->eoc_gpiod || client->irq > 0) { + ret = ak8975_setup_irq(data); + if (ret < 0) { + dev_err(&client->dev, + "Error setting data ready interrupt\n"); + return ret; + } + } + + data->raw_to_gauss[0] = data->def->raw_to_gauss(data->asa[0]); + data->raw_to_gauss[1] = data->def->raw_to_gauss(data->asa[1]); + data->raw_to_gauss[2] = data->def->raw_to_gauss(data->asa[2]); + + return 0; +} + +static int wait_conversion_complete_gpio(struct ak8975_data *data, + unsigned int poll_ms, + unsigned int timeout_ms) +{ + struct i2c_client *client = data->client; + int ret; + int val; + + /* Wait for the conversion to complete. */ + ret = readx_poll_timeout(gpiod_get_value, data->eoc_gpiod, val, val != 0, + poll_ms * USEC_PER_MSEC, + timeout_ms * USEC_PER_MSEC); + if (ret) + return ret; + if (val < 0) { + dev_err(&client->dev, "Error in reading GPIOD\n"); + return val; + } + + ret = i2c_smbus_read_byte_data(client, data->def->ctrl_regs[ST1]); + if (ret < 0) + dev_err(&client->dev, "Error in reading ST1\n"); + + return ret; +} + +static int wait_conversion_complete_polled(struct ak8975_data *data, + unsigned int poll_ms, + unsigned int timeout_ms) +{ + struct i2c_client *client = data->client; + int ret; + int val; + + /* Wait for the conversion to complete. */ + ret = read_poll_timeout(i2c_smbus_read_byte_data, val, val != 0, + poll_ms * USEC_PER_MSEC, + timeout_ms * USEC_PER_MSEC, + true, + client, data->def->ctrl_regs[ST1]); + if (ret) + return ret; + if (val < 0) + dev_err(&client->dev, "Error in reading ST1\n"); + + return val; +} + +/* Returns 0 if the end of conversion interrupt occurred or -ETIMEDOUT otherwise */ +static int wait_conversion_complete_interrupt(struct ak8975_data *data, + unsigned int timeout_ms) +{ + int ret; + + ret = wait_event_timeout(data->data_ready_queue, + test_bit(0, &data->flags), + msecs_to_jiffies(timeout_ms)); + clear_bit(0, &data->flags); + + return ret > 0 ? 0 : -ETIMEDOUT; +} + +static int ak8975_start_read_axis(struct ak8975_data *data) +{ + struct i2c_client *client = data->client; + int ret; + + /* Set up the device for taking a sample. */ + ret = ak8975_set_mode(data, MODE_ONCE); + if (ret < 0) { + dev_err(&client->dev, "Error in setting operating mode\n"); + return ret; + } + + /* Wait for the conversion to complete. */ + if (data->eoc_irq) + ret = wait_conversion_complete_interrupt(data, 100); + else if (data->eoc_gpiod) + ret = wait_conversion_complete_gpio(data, 10, 500); + else + ret = wait_conversion_complete_polled(data, 10, 500); + if (ret < 0) + return ret; + + /* Return with zero if the data is ready. */ + return !data->def->ctrl_regs[ST1_DRDY]; +} + +/* Retrieve raw flux value for one of the x, y, or z axis. */ +static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val) +{ + struct ak8975_data *data = iio_priv(indio_dev); + const struct i2c_client *client = data->client; + const struct ak_def *def = data->def; + __le16 rval; + int ret; + + pm_runtime_get_sync(&data->client->dev); + + mutex_lock(&data->lock); + + ret = ak8975_start_read_axis(data); + if (ret) + goto exit; + + ret = i2c_smbus_read_i2c_block_data_or_emulated(client, + def->data_regs[index], + sizeof(rval), + (u8 *)&rval); + if (ret < 0) + goto exit; + if (ret != sizeof(rval)) { + ret = -EIO; + goto exit; + } + + /* Read out ST2 for release lock on measurement data. */ + ret = i2c_smbus_read_byte_data(client, data->def->ctrl_regs[ST2]); + if (ret < 0) { + dev_err(&client->dev, "Error in reading ST2\n"); + goto exit; + } + + if (ret & (data->def->ctrl_masks[ST2_DERR] | + data->def->ctrl_masks[ST2_HOFL])) { + dev_err(&client->dev, "ST2 status error 0x%x\n", ret); + ret = -EINVAL; + goto exit; + } + + mutex_unlock(&data->lock); + + pm_runtime_put_autosuspend(&data->client->dev); + + /* Swap bytes and convert to valid range. */ + *val = clamp_t(s16, le16_to_cpu(rval), -def->range, def->range); + + return IIO_VAL_INT; + +exit: + mutex_unlock(&data->lock); + pm_runtime_put_autosuspend(&data->client->dev); + dev_err(&client->dev, "Error in reading axis\n"); + return ret; +} + +static int ak8975_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct ak8975_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return ak8975_read_axis(indio_dev, chan->address, val); + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = data->raw_to_gauss[chan->address]; + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static const struct iio_mount_matrix * +ak8975_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ak8975_data *data = iio_priv(indio_dev); + + return &data->orientation; +} + +static const struct iio_chan_spec_ext_info ak8975_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, ak8975_get_mount_matrix), + { } +}; + +#define AK8975_CHANNEL(axis, index) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .address = index, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU \ + }, \ + .ext_info = ak8975_ext_info, \ + } + +static const struct iio_chan_spec ak8975_channels[] = { + AK8975_CHANNEL(X, 0), AK8975_CHANNEL(Y, 1), AK8975_CHANNEL(Z, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const unsigned long ak8975_scan_masks[] = { 0x7, 0 }; + +static const struct iio_info ak8975_info = { + .read_raw = &ak8975_read_raw, +}; + +static void ak8975_fill_buffer(struct iio_dev *indio_dev) +{ + struct ak8975_data *data = iio_priv(indio_dev); + const struct i2c_client *client = data->client; + const struct ak_def *def = data->def; + int ret; + __le16 fval[3]; + + mutex_lock(&data->lock); + + ret = ak8975_start_read_axis(data); + if (ret) + goto unlock; + + /* + * For each axis, read the flux value from the appropriate register + * (the register is specified in the iio device attributes). + */ + ret = i2c_smbus_read_i2c_block_data_or_emulated(client, + def->data_regs[0], + sizeof(fval), + (u8 *)fval); + if (ret < 0) + goto unlock; + if (ret != sizeof(fval)) + goto unlock; + + mutex_unlock(&data->lock); + + /* Clamp to valid range. */ + data->scan.channels[0] = clamp_t(s16, le16_to_cpu(fval[0]), -def->range, def->range); + data->scan.channels[1] = clamp_t(s16, le16_to_cpu(fval[1]), -def->range, def->range); + data->scan.channels[2] = clamp_t(s16, le16_to_cpu(fval[2]), -def->range, def->range); + + iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + iio_get_time_ns(indio_dev)); + + return; + +unlock: + mutex_unlock(&data->lock); + dev_err(&client->dev, "Error in reading axes block\n"); +} + +static irqreturn_t ak8975_handle_trigger(int irq, void *p) +{ + const struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + + ak8975_fill_buffer(indio_dev); + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int ak8975_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ak8975_data *data = iio_priv(indio_dev); + struct device *dev = &data->client->dev; + + return pm_runtime_resume_and_get(dev); +} + +static int ak8975_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ak8975_data *data = iio_priv(indio_dev); + struct device *dev = &data->client->dev; + + pm_runtime_put_autosuspend(dev); + + return 0; +} + +static const struct iio_buffer_setup_ops ak8975_buffer_setup_ops = { + .preenable = ak8975_buffer_preenable, + .postdisable = ak8975_buffer_postdisable, +}; +static int ak8975_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct ak8975_data *data; + struct iio_dev *indio_dev; + struct gpio_desc *eoc_gpiod; + struct gpio_desc *reset_gpiod; + const char *name = NULL; + int ret; + + /* + * Grab and set up the supplied GPIO. + * We may not have a GPIO based IRQ to scan, that is fine, we will + * poll if so. + */ + eoc_gpiod = devm_gpiod_get_optional(&client->dev, NULL, GPIOD_IN); + if (IS_ERR(eoc_gpiod)) + return PTR_ERR(eoc_gpiod); + gpiod_set_consumer_name(eoc_gpiod, "ak_8975"); + + /* + * According to AK09911 datasheet, if reset GPIO is provided then + * deassert reset on ak8975_power_on() and assert reset on + * ak8975_power_off(). + */ + reset_gpiod = devm_gpiod_get_optional(&client->dev, + "reset", GPIOD_OUT_HIGH); + if (IS_ERR(reset_gpiod)) + return PTR_ERR(reset_gpiod); + + /* Register with IIO */ + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (indio_dev == NULL) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + + data->client = client; + data->eoc_gpiod = eoc_gpiod; + data->reset_gpiod = reset_gpiod; + data->eoc_irq = 0; + + ret = iio_read_mount_matrix(&client->dev, &data->orientation); + if (ret) + return ret; + + /* id will be NULL when enumerated via ACPI */ + data->def = i2c_get_match_data(client); + if (!data->def) + return -ENODEV; + + /* If enumerated via firmware node, fix the ABI */ + if (dev_fwnode(&client->dev)) + name = dev_name(&client->dev); + else + name = id->name; + + /* Fetch the regulators */ + data->vdd = devm_regulator_get(&client->dev, "vdd"); + if (IS_ERR(data->vdd)) + return PTR_ERR(data->vdd); + data->vid = devm_regulator_get(&client->dev, "vid"); + if (IS_ERR(data->vid)) + return PTR_ERR(data->vid); + + ret = ak8975_power_on(data); + if (ret) + return ret; + + ret = ak8975_who_i_am(data, data->def->type); + if (ret) { + dev_err(&client->dev, "Unexpected device\n"); + goto power_off; + } + dev_dbg(&client->dev, "Asahi compass chip %s\n", name); + + /* Perform some basic start-of-day setup of the device. */ + ret = ak8975_setup(data); + if (ret) { + dev_err(&client->dev, "%s initialization fails\n", name); + goto power_off; + } + + mutex_init(&data->lock); + indio_dev->channels = ak8975_channels; + indio_dev->num_channels = ARRAY_SIZE(ak8975_channels); + indio_dev->info = &ak8975_info; + indio_dev->available_scan_masks = ak8975_scan_masks; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = name; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, ak8975_handle_trigger, + &ak8975_buffer_setup_ops); + if (ret) { + dev_err(&client->dev, "triggered buffer setup failed\n"); + goto power_off; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&client->dev, "device register failed\n"); + goto cleanup_buffer; + } + + /* Enable runtime PM */ + pm_runtime_get_noresume(&client->dev); + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + /* + * The device comes online in 500us, so add two orders of magnitude + * of delay before autosuspending: 50 ms. + */ + pm_runtime_set_autosuspend_delay(&client->dev, 50); + pm_runtime_use_autosuspend(&client->dev); + pm_runtime_put(&client->dev); + + return 0; + +cleanup_buffer: + iio_triggered_buffer_cleanup(indio_dev); +power_off: + ak8975_power_off(data); + return ret; +} + +static void ak8975_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct ak8975_data *data = iio_priv(indio_dev); + + pm_runtime_get_sync(&client->dev); + pm_runtime_put_noidle(&client->dev); + pm_runtime_disable(&client->dev); + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + ak8975_set_mode(data, POWER_DOWN); + ak8975_power_off(data); +} + +static int ak8975_runtime_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct ak8975_data *data = iio_priv(indio_dev); + int ret; + + /* Set the device in power down if it wasn't already */ + ret = ak8975_set_mode(data, POWER_DOWN); + if (ret < 0) { + dev_err(&client->dev, "Error in setting power-down mode\n"); + return ret; + } + /* Next cut the regulators */ + ak8975_power_off(data); + + return 0; +} + +static int ak8975_runtime_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct ak8975_data *data = iio_priv(indio_dev); + int ret; + + /* Take up the regulators */ + ak8975_power_on(data); + /* + * We come up in powered down mode, the reading routines will + * put us in the mode to read values later. + */ + ret = ak8975_set_mode(data, POWER_DOWN); + if (ret < 0) { + dev_err(&client->dev, "Error in setting power-down mode\n"); + return ret; + } + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(ak8975_dev_pm_ops, ak8975_runtime_suspend, + ak8975_runtime_resume, NULL); + +static const struct acpi_device_id ak_acpi_match[] = { + {"AK8963", (kernel_ulong_t)&ak_def_array[AK8963] }, + {"AK8975", (kernel_ulong_t)&ak_def_array[AK8975] }, + {"AK009911", (kernel_ulong_t)&ak_def_array[AK09911] }, + {"AK09911", (kernel_ulong_t)&ak_def_array[AK09911] }, + {"AK09912", (kernel_ulong_t)&ak_def_array[AK09912] }, + {"AKM9911", (kernel_ulong_t)&ak_def_array[AK09911] }, + {"INVN6500", (kernel_ulong_t)&ak_def_array[AK8963] }, + { } +}; +MODULE_DEVICE_TABLE(acpi, ak_acpi_match); + +static const struct i2c_device_id ak8975_id[] = { + { .name = "AK8963", .driver_data = (kernel_ulong_t)&ak_def_array[AK8963] }, + { .name = "ak8963", .driver_data = (kernel_ulong_t)&ak_def_array[AK8963] }, + { .name = "ak8975", .driver_data = (kernel_ulong_t)&ak_def_array[AK8975] }, + { .name = "ak09911", .driver_data = (kernel_ulong_t)&ak_def_array[AK09911] }, + { .name = "ak09912", .driver_data = (kernel_ulong_t)&ak_def_array[AK09912] }, + { .name = "ak09916", .driver_data = (kernel_ulong_t)&ak_def_array[AK09916] }, + { .name = "ak09918", .driver_data = (kernel_ulong_t)&ak_def_array[AK09918] }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ak8975_id); + +static const struct of_device_id ak8975_of_match[] = { + { .compatible = "asahi-kasei,ak8975", .data = &ak_def_array[AK8975] }, + { .compatible = "ak8975", .data = &ak_def_array[AK8975] }, + { .compatible = "asahi-kasei,ak8963", .data = &ak_def_array[AK8963] }, + { .compatible = "ak8963", .data = &ak_def_array[AK8963] }, + { .compatible = "asahi-kasei,ak09911", .data = &ak_def_array[AK09911] }, + { .compatible = "ak09911", .data = &ak_def_array[AK09911] }, + { .compatible = "asahi-kasei,ak09912", .data = &ak_def_array[AK09912] }, + { .compatible = "ak09912", .data = &ak_def_array[AK09912] }, + { .compatible = "asahi-kasei,ak09916", .data = &ak_def_array[AK09916] }, + { .compatible = "asahi-kasei,ak09918", .data = &ak_def_array[AK09918] }, + { } +}; +MODULE_DEVICE_TABLE(of, ak8975_of_match); + +static struct i2c_driver ak8975_driver = { + .driver = { + .name = "ak8975", + .pm = pm_ptr(&ak8975_dev_pm_ops), + .of_match_table = ak8975_of_match, + .acpi_match_table = ak_acpi_match, + }, + .probe = ak8975_probe, + .remove = ak8975_remove, + .id_table = ak8975_id, +}; +module_i2c_driver(ak8975_driver); + +MODULE_AUTHOR("Laxman Dewangan "); +MODULE_DESCRIPTION("AK8975 magnetometer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/magnetometer/als31300.c b/drivers/iio/magnetometer/als31300.c new file mode 100644 index 000000000..2a2677428 --- /dev/null +++ b/drivers/iio/magnetometer/als31300.c @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for the Allegro MicroSystems ALS31300 3-D Linear Hall Effect Sensor + * + * Copyright (c) 2024 Linaro Limited + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* + * The Allegro MicroSystems ALS31300 has an EEPROM space to configure how + * the device works and how the interrupt line behaves. + * Only the default setup with external trigger is supported. + * + * While the bindings supports declaring an interrupt line, those + * events are not supported. + * + * It should be possible to adapt the driver to the current + * device EEPROM setup at runtime. + */ + +#define ALS31300_EEPROM_CONFIG 0x02 +#define ALS31300_EEPROM_INTERRUPT 0x03 +#define ALS31300_EEPROM_CUSTOMER_1 0x0d +#define ALS31300_EEPROM_CUSTOMER_2 0x0e +#define ALS31300_EEPROM_CUSTOMER_3 0x0f +#define ALS31300_VOL_MODE 0x27 +#define ALS31300_VOL_MODE_LPDCM GENMASK(6, 4) +#define ALS31300_LPDCM_INACTIVE_0_5_MS 0 +#define ALS31300_LPDCM_INACTIVE_1_0_MS 1 +#define ALS31300_LPDCM_INACTIVE_5_0_MS 2 +#define ALS31300_LPDCM_INACTIVE_10_0_MS 3 +#define ALS31300_LPDCM_INACTIVE_50_0_MS 4 +#define ALS31300_LPDCM_INACTIVE_100_0_MS 5 +#define ALS31300_LPDCM_INACTIVE_500_0_MS 6 +#define ALS31300_LPDCM_INACTIVE_1000_0_MS 7 +#define ALS31300_VOL_MODE_SLEEP GENMASK(1, 0) +#define ALS31300_VOL_MODE_ACTIVE_MODE 0 +#define ALS31300_VOL_MODE_SLEEP_MODE 1 +#define ALS31300_VOL_MODE_LPDCM_MODE 2 +#define ALS31300_VOL_MSB 0x28 +#define ALS31300_VOL_MSB_TEMPERATURE GENMASK(5, 0) +#define ALS31300_VOL_MSB_INTERRUPT BIT(6) +#define ALS31300_VOL_MSB_NEW_DATA BIT(7) +#define ALS31300_VOL_MSB_Z_AXIS GENMASK(15, 8) +#define ALS31300_VOL_MSB_Y_AXIS GENMASK(23, 16) +#define ALS31300_VOL_MSB_X_AXIS GENMASK(31, 24) +#define ALS31300_VOL_LSB 0x29 +#define ALS31300_VOL_LSB_TEMPERATURE GENMASK(5, 0) +#define ALS31300_VOL_LSB_HALL_STATUS GENMASK(7, 7) +#define ALS31300_VOL_LSB_Z_AXIS GENMASK(11, 8) +#define ALS31300_VOL_LSB_Y_AXIS GENMASK(15, 12) +#define ALS31300_VOL_LSB_X_AXIS GENMASK(19, 16) +#define ALS31300_VOL_LSB_INTERRUPT_WRITE BIT(20) +#define ALS31300_CUSTOMER_ACCESS 0x35 + +#define ALS31300_DATA_X_GET(b) \ + sign_extend32(FIELD_GET(ALS31300_VOL_MSB_X_AXIS, b[0]) << 4 | \ + FIELD_GET(ALS31300_VOL_LSB_X_AXIS, b[1]), 11) +#define ALS31300_DATA_Y_GET(b) \ + sign_extend32(FIELD_GET(ALS31300_VOL_MSB_Y_AXIS, b[0]) << 4 | \ + FIELD_GET(ALS31300_VOL_LSB_Y_AXIS, b[1]), 11) +#define ALS31300_DATA_Z_GET(b) \ + sign_extend32(FIELD_GET(ALS31300_VOL_MSB_Z_AXIS, b[0]) << 4 | \ + FIELD_GET(ALS31300_VOL_LSB_Z_AXIS, b[1]), 11) +#define ALS31300_TEMPERATURE_GET(b) \ + (FIELD_GET(ALS31300_VOL_MSB_TEMPERATURE, b[0]) << 6 | \ + FIELD_GET(ALS31300_VOL_LSB_TEMPERATURE, b[1])) + +enum als31300_channels { + TEMPERATURE = 0, + AXIS_X, + AXIS_Y, + AXIS_Z, +}; + +struct als31300_variant_info { + u8 sensitivity; +}; + +struct als31300_data { + struct device *dev; + /* protects power on/off the device and access HW */ + struct mutex mutex; + const struct als31300_variant_info *variant_info; + struct regmap *map; +}; + +/* The whole measure is split into 2x32-bit registers, we need to read them both at once */ +static int als31300_get_measure(struct als31300_data *data, + u16 *t, s16 *x, s16 *y, s16 *z) +{ + u32 buf[2]; + int ret, err; + + guard(mutex)(&data->mutex); + + ret = pm_runtime_resume_and_get(data->dev); + if (ret) + return ret; + + /* + * Loop until data is valid, new data should have the + * ALS31300_VOL_MSB_NEW_DATA bit set to 1. + * Max update rate is 2KHz, wait up to 1ms. + */ + ret = read_poll_timeout(regmap_bulk_read, err, + err || FIELD_GET(ALS31300_VOL_MSB_NEW_DATA, buf[0]), + 20, USEC_PER_MSEC, false, + data->map, ALS31300_VOL_MSB, buf, ARRAY_SIZE(buf)); + /* Bail out on read_poll_timeout() error */ + if (ret) + goto out; + + /* Bail out on regmap_bulk_read() error */ + if (err) { + dev_err(data->dev, "read data failed, error %d\n", ret); + ret = err; + goto out; + } + + *t = ALS31300_TEMPERATURE_GET(buf); + *x = ALS31300_DATA_X_GET(buf); + *y = ALS31300_DATA_Y_GET(buf); + *z = ALS31300_DATA_Z_GET(buf); + +out: + pm_runtime_put_autosuspend(data->dev); + + return ret; +} + +static int als31300_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, + int *val2, long mask) +{ + struct als31300_data *data = iio_priv(indio_dev); + s16 x, y, z; + u16 t; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = als31300_get_measure(data, &t, &x, &y, &z); + if (ret) + return ret; + + switch (chan->address) { + case TEMPERATURE: + *val = t; + return IIO_VAL_INT; + case AXIS_X: + *val = x; + return IIO_VAL_INT; + case AXIS_Y: + *val = y; + return IIO_VAL_INT; + case AXIS_Z: + *val = z; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + /* + * Fractional part of: + * 1000 * 302 * (value - 1708) + * temp = ---------------------------- + * 4096 + * to convert temperature in millicelcius. + */ + *val = MILLI * 302; + *val2 = 4096; + return IIO_VAL_FRACTIONAL; + case IIO_MAGN: + /* + * Devices are configured in factory + * with different sensitivities: + * - 500 GAUSS <-> 4 LSB/Gauss + * - 1000 GAUSS <-> 2 LSB/Gauss + * - 2000 GAUSS <-> 1 LSB/Gauss + * with translates by a division of the returned + * value to get Gauss value. + * The sensitivity cannot be read at runtime + * so the value depends on the model compatible + * or device id. + */ + *val = 1; + *val2 = data->variant_info->sensitivity; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + *val = -1708; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static irqreturn_t als31300_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct als31300_data *data = iio_priv(indio_dev); + struct { + u16 temperature; + s16 channels[3]; + aligned_s64 timestamp; + } scan; + s16 x, y, z; + int ret; + u16 t; + + ret = als31300_get_measure(data, &t, &x, &y, &z); + if (ret) + goto trigger_out; + + scan.temperature = t; + scan.channels[0] = x; + scan.channels[1] = y; + scan.channels[2] = z; + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), pf->timestamp); + +trigger_out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +#define ALS31300_AXIS_CHANNEL(axis, index) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .address = index, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 12, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ + } + +static const struct iio_chan_spec als31300_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = TEMPERATURE, + .scan_index = TEMPERATURE, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + ALS31300_AXIS_CHANNEL(X, AXIS_X), + ALS31300_AXIS_CHANNEL(Y, AXIS_Y), + ALS31300_AXIS_CHANNEL(Z, AXIS_Z), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const struct iio_info als31300_info = { + .read_raw = als31300_read_raw, +}; + +static int als31300_set_operating_mode(struct als31300_data *data, + unsigned int val) +{ + int ret; + + ret = regmap_update_bits(data->map, ALS31300_VOL_MODE, + ALS31300_VOL_MODE_SLEEP, val); + if (ret) { + dev_err(data->dev, "failed to set operating mode (%pe)\n", ERR_PTR(ret)); + return ret; + } + + /* The time it takes to exit sleep mode is equivalent to Power-On Delay Time */ + if (val == ALS31300_VOL_MODE_ACTIVE_MODE) + fsleep(600); + + return 0; +} + +static void als31300_power_down(void *data) +{ + als31300_set_operating_mode(data, ALS31300_VOL_MODE_SLEEP_MODE); +} + +static const struct iio_buffer_setup_ops als31300_setup_ops = {}; + +static const unsigned long als31300_scan_masks[] = { GENMASK(3, 0), 0 }; + +static bool als31300_volatile_reg(struct device *dev, unsigned int reg) +{ + return reg == ALS31300_VOL_MSB || reg == ALS31300_VOL_LSB; +} + +static const struct regmap_config als31300_regmap_config = { + .reg_bits = 8, + .val_bits = 32, + .max_register = ALS31300_CUSTOMER_ACCESS, + .volatile_reg = als31300_volatile_reg, +}; + +static int als31300_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct als31300_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->dev = dev; + i2c_set_clientdata(i2c, indio_dev); + + ret = devm_mutex_init(dev, &data->mutex); + if (ret) + return ret; + + data->variant_info = i2c_get_match_data(i2c); + if (!data->variant_info) + return -EINVAL; + + data->map = devm_regmap_init_i2c(i2c, &als31300_regmap_config); + if (IS_ERR(data->map)) + return dev_err_probe(dev, PTR_ERR(data->map), + "failed to allocate register map\n"); + + ret = devm_regulator_get_enable(dev, "vcc"); + if (ret) + return dev_err_probe(dev, ret, "failed to enable regulator\n"); + + ret = als31300_set_operating_mode(data, ALS31300_VOL_MODE_ACTIVE_MODE); + if (ret) + return dev_err_probe(dev, ret, "failed to power on device\n"); + + ret = devm_add_action_or_reset(dev, als31300_power_down, data); + if (ret) + return ret; + + indio_dev->info = &als31300_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = i2c->name; + indio_dev->channels = als31300_channels; + indio_dev->num_channels = ARRAY_SIZE(als31300_channels); + indio_dev->available_scan_masks = als31300_scan_masks; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + als31300_trigger_handler, + &als31300_setup_ops); + if (ret < 0) + return dev_err_probe(dev, ret, "iio triggered buffer setup failed\n"); + + ret = pm_runtime_set_active(dev); + if (ret < 0) + return ret; + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + pm_runtime_get_noresume(dev); + pm_runtime_set_autosuspend_delay(dev, 200); + pm_runtime_use_autosuspend(dev); + + pm_runtime_put_autosuspend(dev); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "device register failed\n"); + + return 0; +} + +static int als31300_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct als31300_data *data = iio_priv(indio_dev); + + return als31300_set_operating_mode(data, ALS31300_VOL_MODE_SLEEP_MODE); +} + +static int als31300_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct als31300_data *data = iio_priv(indio_dev); + + return als31300_set_operating_mode(data, ALS31300_VOL_MODE_ACTIVE_MODE); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(als31300_pm_ops, + als31300_runtime_suspend, als31300_runtime_resume, + NULL); + +static const struct als31300_variant_info al31300_variant_500 = { + .sensitivity = 4, +}; + +static const struct als31300_variant_info al31300_variant_1000 = { + .sensitivity = 2, +}; + +static const struct als31300_variant_info al31300_variant_2000 = { + .sensitivity = 1, +}; + +static const struct i2c_device_id als31300_id[] = { + { + .name = "als31300-500", + .driver_data = (kernel_ulong_t)&al31300_variant_500, + }, + { + .name = "als31300-1000", + .driver_data = (kernel_ulong_t)&al31300_variant_1000, + }, + { + .name = "als31300-2000", + .driver_data = (kernel_ulong_t)&al31300_variant_2000, + }, + { } +}; +MODULE_DEVICE_TABLE(i2c, als31300_id); + +static const struct of_device_id als31300_of_match[] = { + { + .compatible = "allegromicro,als31300-500", + .data = &al31300_variant_500, + }, + { + .compatible = "allegromicro,als31300-1000", + .data = &al31300_variant_1000, + }, + { + .compatible = "allegromicro,als31300-2000", + .data = &al31300_variant_2000, + }, + { } +}; +MODULE_DEVICE_TABLE(of, als31300_of_match); + +static struct i2c_driver als31300_driver = { + .driver = { + .name = "als31300", + .of_match_table = als31300_of_match, + .pm = pm_ptr(&als31300_pm_ops), + }, + .probe = als31300_probe, + .id_table = als31300_id, +}; +module_i2c_driver(als31300_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ALS31300 3-D Linear Hall Effect Driver"); +MODULE_AUTHOR("Neil Armstrong "); diff --git a/drivers/iio/magnetometer/bmc150_magn.c b/drivers/iio/magnetometer/bmc150_magn.c new file mode 100644 index 000000000..32dff9cdd --- /dev/null +++ b/drivers/iio/magnetometer/bmc150_magn.c @@ -0,0 +1,1035 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Bosch BMC150 three-axis magnetic field sensor driver + * + * Copyright (c) 2015, Intel Corporation. + * + * This code is based on bmm050_api.c authored by contact@bosch.sensortec.com: + * + * (C) Copyright 2011~2014 Bosch Sensortec GmbH All Rights Reserved + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "bmc150_magn.h" + +#define BMC150_MAGN_REG_CHIP_ID 0x40 +#define BMC150_MAGN_CHIP_ID_VAL 0x32 + +#define BMC150_MAGN_REG_X_L 0x42 +#define BMC150_MAGN_REG_X_M 0x43 +#define BMC150_MAGN_REG_Y_L 0x44 +#define BMC150_MAGN_REG_Y_M 0x45 +#define BMC150_MAGN_SHIFT_XY_L 3 +#define BMC150_MAGN_REG_Z_L 0x46 +#define BMC150_MAGN_REG_Z_M 0x47 +#define BMC150_MAGN_SHIFT_Z_L 1 +#define BMC150_MAGN_REG_RHALL_L 0x48 +#define BMC150_MAGN_REG_RHALL_M 0x49 +#define BMC150_MAGN_SHIFT_RHALL_L 2 + +#define BMC150_MAGN_REG_INT_STATUS 0x4A + +#define BMC150_MAGN_REG_POWER 0x4B +#define BMC150_MAGN_MASK_POWER_CTL BIT(0) + +#define BMC150_MAGN_REG_OPMODE_ODR 0x4C +#define BMC150_MAGN_MASK_OPMODE GENMASK(2, 1) +#define BMC150_MAGN_SHIFT_OPMODE 1 +#define BMC150_MAGN_MODE_NORMAL 0x00 +#define BMC150_MAGN_MODE_FORCED 0x01 +#define BMC150_MAGN_MODE_SLEEP 0x03 +#define BMC150_MAGN_MASK_ODR GENMASK(5, 3) +#define BMC150_MAGN_SHIFT_ODR 3 + +#define BMC150_MAGN_REG_INT 0x4D + +#define BMC150_MAGN_REG_INT_DRDY 0x4E +#define BMC150_MAGN_MASK_DRDY_EN BIT(7) +#define BMC150_MAGN_SHIFT_DRDY_EN 7 +#define BMC150_MAGN_MASK_DRDY_INT3 BIT(6) +#define BMC150_MAGN_MASK_DRDY_Z_EN BIT(5) +#define BMC150_MAGN_MASK_DRDY_Y_EN BIT(4) +#define BMC150_MAGN_MASK_DRDY_X_EN BIT(3) +#define BMC150_MAGN_MASK_DRDY_DR_POLARITY BIT(2) +#define BMC150_MAGN_MASK_DRDY_LATCHING BIT(1) +#define BMC150_MAGN_MASK_DRDY_INT3_POLARITY BIT(0) + +#define BMC150_MAGN_REG_LOW_THRESH 0x4F +#define BMC150_MAGN_REG_HIGH_THRESH 0x50 +#define BMC150_MAGN_REG_REP_XY 0x51 +#define BMC150_MAGN_REG_REP_Z 0x52 +#define BMC150_MAGN_REG_REP_DATAMASK GENMASK(7, 0) + +#define BMC150_MAGN_REG_TRIM_START 0x5D +#define BMC150_MAGN_REG_TRIM_END 0x71 + +#define BMC150_MAGN_XY_OVERFLOW_VAL -4096 +#define BMC150_MAGN_Z_OVERFLOW_VAL -16384 + +/* Time from SUSPEND to SLEEP */ +#define BMC150_MAGN_START_UP_TIME_MS 3 + +#define BMC150_MAGN_AUTO_SUSPEND_DELAY_MS 2000 + +#define BMC150_MAGN_REGVAL_TO_REPXY(regval) (((regval) * 2) + 1) +#define BMC150_MAGN_REGVAL_TO_REPZ(regval) ((regval) + 1) +#define BMC150_MAGN_REPXY_TO_REGVAL(rep) (((rep) - 1) / 2) +#define BMC150_MAGN_REPZ_TO_REGVAL(rep) ((rep) - 1) + +enum bmc150_magn_axis { + AXIS_X, + AXIS_Y, + AXIS_Z, + RHALL, + AXIS_XYZ_MAX = RHALL, + AXIS_XYZR_MAX, +}; + +enum bmc150_magn_power_modes { + BMC150_MAGN_POWER_MODE_SUSPEND, + BMC150_MAGN_POWER_MODE_SLEEP, + BMC150_MAGN_POWER_MODE_NORMAL, +}; + +struct bmc150_magn_trim_regs { + s8 x1; + s8 y1; + __le16 reserved1; + u8 reserved2; + __le16 z4; + s8 x2; + s8 y2; + __le16 reserved3; + __le16 z2; + __le16 z1; + __le16 xyz1; + __le16 z3; + s8 xy2; + u8 xy1; +} __packed; + +struct bmc150_magn_data { + struct device *dev; + /* + * 1. Protect this structure. + * 2. Serialize sequences that power on/off the device and access HW. + */ + struct mutex mutex; + struct regmap *regmap; + struct regulator_bulk_data regulators[2]; + struct iio_mount_matrix orientation; + /* Ensure timestamp is naturally aligned */ + struct { + s32 chans[3]; + aligned_s64 timestamp; + } scan; + struct iio_trigger *dready_trig; + bool dready_trigger_on; + int max_odr; + int irq; +}; + +static const struct { + int freq; + u8 reg_val; +} bmc150_magn_samp_freq_table[] = { + { 2, 0x01 }, + { 6, 0x02 }, + { 8, 0x03 }, + { 10, 0x00 }, + { 15, 0x04 }, + { 20, 0x05 }, + { 25, 0x06 }, + { 30, 0x07 }, +}; + +enum bmc150_magn_presets { + LOW_POWER_PRESET, + REGULAR_PRESET, + ENHANCED_REGULAR_PRESET, + HIGH_ACCURACY_PRESET +}; + +static const struct bmc150_magn_preset { + u8 rep_xy; + u8 rep_z; + u8 odr; +} bmc150_magn_presets_table[] = { + [LOW_POWER_PRESET] = { 3, 3, 10 }, + [REGULAR_PRESET] = { 9, 15, 10 }, + [ENHANCED_REGULAR_PRESET] = { 15, 27, 10 }, + [HIGH_ACCURACY_PRESET] = { 47, 83, 20 }, +}; + +#define BMC150_MAGN_DEFAULT_PRESET REGULAR_PRESET + +static bool bmc150_magn_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMC150_MAGN_REG_POWER: + case BMC150_MAGN_REG_OPMODE_ODR: + case BMC150_MAGN_REG_INT: + case BMC150_MAGN_REG_INT_DRDY: + case BMC150_MAGN_REG_LOW_THRESH: + case BMC150_MAGN_REG_HIGH_THRESH: + case BMC150_MAGN_REG_REP_XY: + case BMC150_MAGN_REG_REP_Z: + return true; + default: + return false; + } +} + +static bool bmc150_magn_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMC150_MAGN_REG_X_L: + case BMC150_MAGN_REG_X_M: + case BMC150_MAGN_REG_Y_L: + case BMC150_MAGN_REG_Y_M: + case BMC150_MAGN_REG_Z_L: + case BMC150_MAGN_REG_Z_M: + case BMC150_MAGN_REG_RHALL_L: + case BMC150_MAGN_REG_RHALL_M: + case BMC150_MAGN_REG_INT_STATUS: + return true; + default: + return false; + } +} + +const struct regmap_config bmc150_magn_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = BMC150_MAGN_REG_TRIM_END, + .cache_type = REGCACHE_RBTREE, + + .writeable_reg = bmc150_magn_is_writeable_reg, + .volatile_reg = bmc150_magn_is_volatile_reg, +}; +EXPORT_SYMBOL_NS(bmc150_magn_regmap_config, "IIO_BMC150_MAGN"); + +static int bmc150_magn_set_power_mode(struct bmc150_magn_data *data, + enum bmc150_magn_power_modes mode, + bool state) +{ + int ret; + + switch (mode) { + case BMC150_MAGN_POWER_MODE_SUSPEND: + ret = regmap_update_bits(data->regmap, BMC150_MAGN_REG_POWER, + BMC150_MAGN_MASK_POWER_CTL, !state); + if (ret < 0) + return ret; + usleep_range(BMC150_MAGN_START_UP_TIME_MS * 1000, 20000); + return 0; + case BMC150_MAGN_POWER_MODE_SLEEP: + return regmap_update_bits(data->regmap, + BMC150_MAGN_REG_OPMODE_ODR, + BMC150_MAGN_MASK_OPMODE, + FIELD_PREP(BMC150_MAGN_MASK_OPMODE, + BMC150_MAGN_MODE_SLEEP)); + case BMC150_MAGN_POWER_MODE_NORMAL: + return regmap_update_bits(data->regmap, + BMC150_MAGN_REG_OPMODE_ODR, + BMC150_MAGN_MASK_OPMODE, + FIELD_PREP(BMC150_MAGN_MASK_OPMODE, + BMC150_MAGN_MODE_NORMAL)); + } + + return -EINVAL; +} + +static int bmc150_magn_set_power_mode_locked(struct bmc150_magn_data *data, + enum bmc150_magn_power_modes mode) +{ + guard(mutex)(&data->mutex); + return bmc150_magn_set_power_mode(data, mode, true); +} + +static int bmc150_magn_set_power_state(struct bmc150_magn_data *data, bool on) +{ + int ret = 0; + + if (on) + ret = pm_runtime_resume_and_get(data->dev); + else + pm_runtime_put_autosuspend(data->dev); + if (ret < 0) { + dev_err(data->dev, + "failed to change power state to %d\n", on); + return ret; + } + + return 0; +} + +static int bmc150_magn_get_odr(struct bmc150_magn_data *data, int *val) +{ + int ret, reg_val; + u8 i, odr_val; + + ret = regmap_read(data->regmap, BMC150_MAGN_REG_OPMODE_ODR, ®_val); + if (ret < 0) + return ret; + odr_val = FIELD_GET(BMC150_MAGN_MASK_ODR, reg_val); + + for (i = 0; i < ARRAY_SIZE(bmc150_magn_samp_freq_table); i++) + if (bmc150_magn_samp_freq_table[i].reg_val == odr_val) { + *val = bmc150_magn_samp_freq_table[i].freq; + return 0; + } + + return -EINVAL; +} + +static int bmc150_magn_set_odr(struct bmc150_magn_data *data, int val) +{ + u8 i; + + for (i = 0; i < ARRAY_SIZE(bmc150_magn_samp_freq_table); i++) { + if (bmc150_magn_samp_freq_table[i].freq != val) + continue; + + return regmap_update_bits(data->regmap, + BMC150_MAGN_REG_OPMODE_ODR, + BMC150_MAGN_MASK_ODR, + FIELD_PREP(BMC150_MAGN_MASK_ODR, + bmc150_magn_samp_freq_table[i].reg_val)); + } + + return -EINVAL; +} + +static int bmc150_magn_set_max_odr(struct bmc150_magn_data *data, int rep_xy, + int rep_z, int odr) +{ + int ret, reg_val, max_odr; + + if (rep_xy <= 0) { + ret = regmap_read(data->regmap, BMC150_MAGN_REG_REP_XY, + ®_val); + if (ret < 0) + return ret; + rep_xy = BMC150_MAGN_REGVAL_TO_REPXY(reg_val); + } + if (rep_z <= 0) { + ret = regmap_read(data->regmap, BMC150_MAGN_REG_REP_Z, + ®_val); + if (ret < 0) + return ret; + rep_z = BMC150_MAGN_REGVAL_TO_REPZ(reg_val); + } + if (odr <= 0) { + ret = bmc150_magn_get_odr(data, &odr); + if (ret < 0) + return ret; + } + /* the maximum selectable read-out frequency from datasheet */ + max_odr = 1000000 / (145 * rep_xy + 500 * rep_z + 980); + if (odr > max_odr) { + dev_err(data->dev, + "Can't set oversampling with sampling freq %d\n", + odr); + return -EINVAL; + } + data->max_odr = max_odr; + + return 0; +} + +static s32 bmc150_magn_compensate_x(struct bmc150_magn_trim_regs *tregs, s16 x, + u16 rhall) +{ + s16 val; + u16 xyz1 = le16_to_cpu(tregs->xyz1); + + if (x == BMC150_MAGN_XY_OVERFLOW_VAL) + return S32_MIN; + + if (!rhall) + rhall = xyz1; + + val = ((s16)(((u16)((((s32)xyz1) << 14) / rhall)) - ((u16)0x4000))); + val = ((s16)((((s32)x) * ((((((((s32)tregs->xy2) * ((((s32)val) * + ((s32)val)) >> 7)) + (((s32)val) * + ((s32)(((s16)tregs->xy1) << 7)))) >> 9) + ((s32)0x100000)) * + ((s32)(((s16)tregs->x2) + ((s16)0xA0)))) >> 12)) >> 13)) + + (((s16)tregs->x1) << 3); + + return (s32)val; +} + +static s32 bmc150_magn_compensate_y(struct bmc150_magn_trim_regs *tregs, s16 y, + u16 rhall) +{ + s16 val; + u16 xyz1 = le16_to_cpu(tregs->xyz1); + + if (y == BMC150_MAGN_XY_OVERFLOW_VAL) + return S32_MIN; + + if (!rhall) + rhall = xyz1; + + val = ((s16)(((u16)((((s32)xyz1) << 14) / rhall)) - ((u16)0x4000))); + val = ((s16)((((s32)y) * ((((((((s32)tregs->xy2) * ((((s32)val) * + ((s32)val)) >> 7)) + (((s32)val) * + ((s32)(((s16)tregs->xy1) << 7)))) >> 9) + ((s32)0x100000)) * + ((s32)(((s16)tregs->y2) + ((s16)0xA0)))) >> 12)) >> 13)) + + (((s16)tregs->y1) << 3); + + return (s32)val; +} + +static s32 bmc150_magn_compensate_z(struct bmc150_magn_trim_regs *tregs, s16 z, + u16 rhall) +{ + s32 val; + u16 xyz1 = le16_to_cpu(tregs->xyz1); + u16 z1 = le16_to_cpu(tregs->z1); + s16 z2 = le16_to_cpu(tregs->z2); + s16 z3 = le16_to_cpu(tregs->z3); + s16 z4 = le16_to_cpu(tregs->z4); + + if (z == BMC150_MAGN_Z_OVERFLOW_VAL) + return S32_MIN; + + val = (((((s32)(z - z4)) << 15) - ((((s32)z3) * ((s32)(((s16)rhall) - + ((s16)xyz1)))) >> 2)) / (z2 + ((s16)(((((s32)z1) * + ((((s16)rhall) << 1))) + (1 << 15)) >> 16)))); + + return val; +} + +static int bmc150_magn_read_xyz(struct bmc150_magn_data *data, s32 *buffer) +{ + int ret; + __le16 values[AXIS_XYZR_MAX]; + s16 raw_x, raw_y, raw_z; + u16 rhall; + struct bmc150_magn_trim_regs tregs; + + ret = regmap_bulk_read(data->regmap, BMC150_MAGN_REG_X_L, + values, sizeof(values)); + if (ret < 0) + return ret; + + raw_x = (s16)le16_to_cpu(values[AXIS_X]) >> BMC150_MAGN_SHIFT_XY_L; + raw_y = (s16)le16_to_cpu(values[AXIS_Y]) >> BMC150_MAGN_SHIFT_XY_L; + raw_z = (s16)le16_to_cpu(values[AXIS_Z]) >> BMC150_MAGN_SHIFT_Z_L; + rhall = le16_to_cpu(values[RHALL]) >> BMC150_MAGN_SHIFT_RHALL_L; + + ret = regmap_bulk_read(data->regmap, BMC150_MAGN_REG_TRIM_START, + &tregs, sizeof(tregs)); + if (ret < 0) + return ret; + + buffer[AXIS_X] = bmc150_magn_compensate_x(&tregs, raw_x, rhall); + buffer[AXIS_Y] = bmc150_magn_compensate_y(&tregs, raw_y, rhall); + buffer[AXIS_Z] = bmc150_magn_compensate_z(&tregs, raw_z, rhall); + + return 0; +} + +static int bmc150_magn_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bmc150_magn_data *data = iio_priv(indio_dev); + int ret, tmp; + s32 values[AXIS_XYZ_MAX]; + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + + guard(mutex)(&data->mutex); + + ret = bmc150_magn_set_power_state(data, true); + if (ret < 0) + return ret; + + ret = bmc150_magn_read_xyz(data, values); + if (ret < 0) { + bmc150_magn_set_power_state(data, false); + return ret; + } + *val = values[chan->scan_index]; + + ret = bmc150_magn_set_power_state(data, false); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_SCALE: + /* + * The API/driver performs an off-chip temperature + * compensation and outputs x/y/z magnetic field data in + * 16 LSB/uT to the upper application layer. + */ + *val = 0; + *val2 = 625; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = bmc150_magn_get_odr(data, val); + if (ret < 0) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->channel2) { + case IIO_MOD_X: + case IIO_MOD_Y: + ret = regmap_read(data->regmap, BMC150_MAGN_REG_REP_XY, + &tmp); + if (ret < 0) + return ret; + *val = BMC150_MAGN_REGVAL_TO_REPXY(tmp); + return IIO_VAL_INT; + case IIO_MOD_Z: + ret = regmap_read(data->regmap, BMC150_MAGN_REG_REP_Z, + &tmp); + if (ret < 0) + return ret; + *val = BMC150_MAGN_REGVAL_TO_REPZ(tmp); + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int bmc150_magn_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bmc150_magn_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: { + if (val > data->max_odr) + return -EINVAL; + guard(mutex)(&data->mutex); + return bmc150_magn_set_odr(data, val); + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->channel2) { + case IIO_MOD_X: + case IIO_MOD_Y: { + if (val < 1 || val > 511) + return -EINVAL; + guard(mutex)(&data->mutex); + ret = bmc150_magn_set_max_odr(data, val, 0, 0); + if (ret < 0) + return ret; + return regmap_update_bits(data->regmap, + BMC150_MAGN_REG_REP_XY, + BMC150_MAGN_REG_REP_DATAMASK, + BMC150_MAGN_REPXY_TO_REGVAL(val)); + } + case IIO_MOD_Z: { + if (val < 1 || val > 256) + return -EINVAL; + guard(mutex)(&data->mutex); + ret = bmc150_magn_set_max_odr(data, 0, val, 0); + if (ret < 0) + return ret; + return regmap_update_bits(data->regmap, + BMC150_MAGN_REG_REP_Z, + BMC150_MAGN_REG_REP_DATAMASK, + BMC150_MAGN_REPZ_TO_REGVAL(val)); + } + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static ssize_t bmc150_magn_show_samp_freq_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct bmc150_magn_data *data = iio_priv(indio_dev); + size_t len = 0; + u8 i; + + for (i = 0; i < ARRAY_SIZE(bmc150_magn_samp_freq_table); i++) { + if (bmc150_magn_samp_freq_table[i].freq > data->max_odr) + break; + len += scnprintf(buf + len, PAGE_SIZE - len, "%d ", + bmc150_magn_samp_freq_table[i].freq); + } + /* replace last space with a newline */ + buf[len - 1] = '\n'; + + return len; +} + +static const struct iio_mount_matrix * +bmc150_magn_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct bmc150_magn_data *data = iio_priv(indio_dev); + + return &data->orientation; +} + +static const struct iio_chan_spec_ext_info bmc150_magn_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, bmc150_magn_get_mount_matrix), + { } +}; + +static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(bmc150_magn_show_samp_freq_avail); + +static struct attribute *bmc150_magn_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group bmc150_magn_attrs_group = { + .attrs = bmc150_magn_attributes, +}; + +#define BMC150_MAGN_CHANNEL(_axis) { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = AXIS_##_axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 32, \ + .storagebits = 32, \ + .endianness = IIO_LE \ + }, \ + .ext_info = bmc150_magn_ext_info, \ +} + +static const struct iio_chan_spec bmc150_magn_channels[] = { + BMC150_MAGN_CHANNEL(X), + BMC150_MAGN_CHANNEL(Y), + BMC150_MAGN_CHANNEL(Z), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_info bmc150_magn_info = { + .attrs = &bmc150_magn_attrs_group, + .read_raw = bmc150_magn_read_raw, + .write_raw = bmc150_magn_write_raw, +}; + +static const unsigned long bmc150_magn_scan_masks[] = { + BIT(AXIS_X) | BIT(AXIS_Y) | BIT(AXIS_Z), + 0 +}; + +static irqreturn_t bmc150_magn_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bmc150_magn_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = bmc150_magn_read_xyz(data, data->scan.chans); + if (ret < 0) + goto err; + + iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + pf->timestamp); + +err: + mutex_unlock(&data->mutex); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int bmc150_magn_init(struct bmc150_magn_data *data) +{ + int ret, chip_id; + struct bmc150_magn_preset preset; + + ret = regulator_bulk_enable(ARRAY_SIZE(data->regulators), + data->regulators); + if (ret < 0) { + dev_err(data->dev, "Failed to enable regulators: %d\n", ret); + return ret; + } + /* + * 3ms power-on time according to datasheet, let's better + * be safe than sorry and set this delay to 5ms. + */ + fsleep(5 * USEC_PER_MSEC); + + ret = bmc150_magn_set_power_mode(data, BMC150_MAGN_POWER_MODE_SUSPEND, + false); + if (ret < 0) { + dev_err(data->dev, + "Failed to bring up device from suspend mode\n"); + goto err_regulator_disable; + } + + ret = regmap_read(data->regmap, BMC150_MAGN_REG_CHIP_ID, &chip_id); + if (ret < 0) { + dev_err(data->dev, "Failed reading chip id\n"); + goto err_poweroff; + } + if (chip_id != BMC150_MAGN_CHIP_ID_VAL) { + dev_err(data->dev, "Invalid chip id 0x%x\n", chip_id); + ret = -ENODEV; + goto err_poweroff; + } + dev_dbg(data->dev, "Chip id %x\n", chip_id); + + preset = bmc150_magn_presets_table[BMC150_MAGN_DEFAULT_PRESET]; + ret = bmc150_magn_set_odr(data, preset.odr); + if (ret < 0) { + dev_err(data->dev, "Failed to set ODR to %d\n", + preset.odr); + goto err_poweroff; + } + + ret = regmap_write(data->regmap, BMC150_MAGN_REG_REP_XY, + BMC150_MAGN_REPXY_TO_REGVAL(preset.rep_xy)); + if (ret < 0) { + dev_err(data->dev, "Failed to set REP XY to %d\n", + preset.rep_xy); + goto err_poweroff; + } + + ret = regmap_write(data->regmap, BMC150_MAGN_REG_REP_Z, + BMC150_MAGN_REPZ_TO_REGVAL(preset.rep_z)); + if (ret < 0) { + dev_err(data->dev, "Failed to set REP Z to %d\n", + preset.rep_z); + goto err_poweroff; + } + + ret = bmc150_magn_set_max_odr(data, preset.rep_xy, preset.rep_z, + preset.odr); + if (ret < 0) + goto err_poweroff; + + ret = bmc150_magn_set_power_mode(data, BMC150_MAGN_POWER_MODE_NORMAL, + true); + if (ret < 0) { + dev_err(data->dev, "Failed to power on device\n"); + goto err_poweroff; + } + + return 0; + +err_poweroff: + bmc150_magn_set_power_mode(data, BMC150_MAGN_POWER_MODE_SUSPEND, true); +err_regulator_disable: + regulator_bulk_disable(ARRAY_SIZE(data->regulators), data->regulators); + return ret; +} + +static int bmc150_magn_reset_intr(struct bmc150_magn_data *data) +{ + int tmp; + + /* + * Data Ready (DRDY) is always cleared after + * readout of data registers ends. + */ + return regmap_read(data->regmap, BMC150_MAGN_REG_X_L, &tmp); +} + +static void bmc150_magn_trig_reen(struct iio_trigger *trig) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct bmc150_magn_data *data = iio_priv(indio_dev); + int ret; + + if (!data->dready_trigger_on) + return; + + guard(mutex)(&data->mutex); + ret = bmc150_magn_reset_intr(data); + if (ret) + dev_err(data->dev, "Failed to reset interrupt\n"); +} + +static int bmc150_magn_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct bmc150_magn_data *data = iio_priv(indio_dev); + int ret; + + guard(mutex)(&data->mutex); + + if (state == data->dready_trigger_on) + return 0; + + ret = regmap_update_bits(data->regmap, BMC150_MAGN_REG_INT_DRDY, + BMC150_MAGN_MASK_DRDY_EN, + FIELD_PREP(BMC150_MAGN_MASK_DRDY_EN, state)); + if (ret < 0) + return ret; + + data->dready_trigger_on = state; + + if (state) { + ret = bmc150_magn_reset_intr(data); + if (ret < 0) + return ret; + } + + return 0; +} + +static const struct iio_trigger_ops bmc150_magn_trigger_ops = { + .set_trigger_state = bmc150_magn_data_rdy_trigger_set_state, + .reenable = bmc150_magn_trig_reen, +}; + +static int bmc150_magn_buffer_preenable(struct iio_dev *indio_dev) +{ + struct bmc150_magn_data *data = iio_priv(indio_dev); + + return bmc150_magn_set_power_state(data, true); +} + +static int bmc150_magn_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct bmc150_magn_data *data = iio_priv(indio_dev); + + return bmc150_magn_set_power_state(data, false); +} + +static const struct iio_buffer_setup_ops bmc150_magn_buffer_setup_ops = { + .preenable = bmc150_magn_buffer_preenable, + .postdisable = bmc150_magn_buffer_postdisable, +}; + +int bmc150_magn_probe(struct device *dev, struct regmap *regmap, + int irq, const char *name) +{ + struct bmc150_magn_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + dev_set_drvdata(dev, indio_dev); + data->regmap = regmap; + data->irq = irq; + data->dev = dev; + + data->regulators[0].supply = "vdd"; + data->regulators[1].supply = "vddio"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(data->regulators), + data->regulators); + if (ret) + return dev_err_probe(dev, ret, "failed to get regulators\n"); + + ret = iio_read_mount_matrix(dev, &data->orientation); + if (ret) + return ret; + + mutex_init(&data->mutex); + + ret = bmc150_magn_init(data); + if (ret < 0) + return ret; + + indio_dev->channels = bmc150_magn_channels; + indio_dev->num_channels = ARRAY_SIZE(bmc150_magn_channels); + indio_dev->available_scan_masks = bmc150_magn_scan_masks; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &bmc150_magn_info; + + if (irq > 0) { + data->dready_trig = devm_iio_trigger_alloc(dev, + "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->dready_trig) { + ret = -ENOMEM; + dev_err(dev, "iio trigger alloc failed\n"); + goto err_poweroff; + } + + data->dready_trig->ops = &bmc150_magn_trigger_ops; + iio_trigger_set_drvdata(data->dready_trig, indio_dev); + ret = iio_trigger_register(data->dready_trig); + if (ret) { + dev_err(dev, "iio trigger register failed\n"); + goto err_poweroff; + } + + ret = request_irq(irq, iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_RISING | IRQF_NO_THREAD, + "bmc150_magn_event", data->dready_trig); + if (ret < 0) { + dev_err(dev, "request irq %d failed\n", irq); + goto err_trigger_unregister; + } + } + + ret = iio_triggered_buffer_setup(indio_dev, + iio_pollfunc_store_time, + bmc150_magn_trigger_handler, + &bmc150_magn_buffer_setup_ops); + if (ret < 0) { + dev_err(dev, "iio triggered buffer setup failed\n"); + goto err_free_irq; + } + + ret = pm_runtime_set_active(dev); + if (ret) + goto err_buffer_cleanup; + + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, + BMC150_MAGN_AUTO_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(dev, "unable to register iio device\n"); + goto err_pm_cleanup; + } + + dev_dbg(dev, "Registered device %s\n", name); + return 0; + +err_pm_cleanup: + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_disable(dev); +err_buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); +err_free_irq: + if (irq > 0) + free_irq(irq, data->dready_trig); +err_trigger_unregister: + if (data->dready_trig) + iio_trigger_unregister(data->dready_trig); +err_poweroff: + bmc150_magn_set_power_mode(data, BMC150_MAGN_POWER_MODE_SUSPEND, true); + return ret; +} +EXPORT_SYMBOL_NS(bmc150_magn_probe, "IIO_BMC150_MAGN"); + +void bmc150_magn_remove(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmc150_magn_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + + iio_triggered_buffer_cleanup(indio_dev); + + if (data->irq > 0) + free_irq(data->irq, data->dready_trig); + + if (data->dready_trig) + iio_trigger_unregister(data->dready_trig); + + bmc150_magn_set_power_mode_locked(data, BMC150_MAGN_POWER_MODE_SUSPEND); + + regulator_bulk_disable(ARRAY_SIZE(data->regulators), data->regulators); +} +EXPORT_SYMBOL_NS(bmc150_magn_remove, "IIO_BMC150_MAGN"); + +#ifdef CONFIG_PM +static int bmc150_magn_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmc150_magn_data *data = iio_priv(indio_dev); + int ret; + + ret = bmc150_magn_set_power_mode_locked(data, + BMC150_MAGN_POWER_MODE_SLEEP); + if (ret < 0) { + dev_err(dev, "powering off device failed\n"); + return ret; + } + return 0; +} + +/* + * Should be called with data->mutex held. + */ +static int bmc150_magn_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmc150_magn_data *data = iio_priv(indio_dev); + + return bmc150_magn_set_power_mode(data, BMC150_MAGN_POWER_MODE_NORMAL, + true); +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int bmc150_magn_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmc150_magn_data *data = iio_priv(indio_dev); + + return bmc150_magn_set_power_mode_locked(data, + BMC150_MAGN_POWER_MODE_SLEEP); +} + +static int bmc150_magn_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmc150_magn_data *data = iio_priv(indio_dev); + + return bmc150_magn_set_power_mode_locked(data, + BMC150_MAGN_POWER_MODE_NORMAL); +} +#endif + +const struct dev_pm_ops bmc150_magn_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(bmc150_magn_suspend, bmc150_magn_resume) + SET_RUNTIME_PM_OPS(bmc150_magn_runtime_suspend, + bmc150_magn_runtime_resume, NULL) +}; +EXPORT_SYMBOL_NS(bmc150_magn_pm_ops, "IIO_BMC150_MAGN"); + +MODULE_AUTHOR("Irina Tirdea "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("BMC150 magnetometer core driver"); diff --git a/drivers/iio/magnetometer/bmc150_magn.h b/drivers/iio/magnetometer/bmc150_magn.h new file mode 100644 index 000000000..98c086d10 --- /dev/null +++ b/drivers/iio/magnetometer/bmc150_magn.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _BMC150_MAGN_H_ +#define _BMC150_MAGN_H_ + +extern const struct regmap_config bmc150_magn_regmap_config; +extern const struct dev_pm_ops bmc150_magn_pm_ops; + +int bmc150_magn_probe(struct device *dev, struct regmap *regmap, int irq, + const char *name); +void bmc150_magn_remove(struct device *dev); + +#endif /* _BMC150_MAGN_H_ */ diff --git a/drivers/iio/magnetometer/bmc150_magn_i2c.c b/drivers/iio/magnetometer/bmc150_magn_i2c.c new file mode 100644 index 000000000..28f2bee21 --- /dev/null +++ b/drivers/iio/magnetometer/bmc150_magn_i2c.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 3-axis magnetometer driver supporting following I2C Bosch-Sensortec chips: + * - BMC150 + * - BMC156 + * - BMM150 + * + * Copyright (c) 2016, Intel Corporation. + */ +#include +#include +#include +#include + +#include "bmc150_magn.h" + +static int bmc150_magn_i2c_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct regmap *regmap; + const char *name = NULL; + + regmap = devm_regmap_init_i2c(client, &bmc150_magn_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Failed to initialize i2c regmap\n"); + return PTR_ERR(regmap); + } + + if (id) + name = id->name; + + return bmc150_magn_probe(&client->dev, regmap, client->irq, name); +} + +static void bmc150_magn_i2c_remove(struct i2c_client *client) +{ + bmc150_magn_remove(&client->dev); +} + +static const struct i2c_device_id bmc150_magn_i2c_id[] = { + { .name = "bmc150_magn" }, + { .name = "bmc156_magn" }, + { .name = "bmm150_magn" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bmc150_magn_i2c_id); + +static const struct of_device_id bmc150_magn_of_match[] = { + { .compatible = "bosch,bmc150_magn" }, + { .compatible = "bosch,bmc156_magn" }, + { .compatible = "bosch,bmm150_magn" }, /* deprecated compatible */ + { .compatible = "bosch,bmm150" }, + { } +}; +MODULE_DEVICE_TABLE(of, bmc150_magn_of_match); + +static struct i2c_driver bmc150_magn_driver = { + .driver = { + .name = "bmc150_magn_i2c", + .of_match_table = bmc150_magn_of_match, + .pm = &bmc150_magn_pm_ops, + }, + .probe = bmc150_magn_i2c_probe, + .remove = bmc150_magn_i2c_remove, + .id_table = bmc150_magn_i2c_id, +}; +module_i2c_driver(bmc150_magn_driver); + +MODULE_AUTHOR("Daniel Baluta +#include +#include + +#include "bmc150_magn.h" + +static int bmc150_magn_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + const struct spi_device_id *id = spi_get_device_id(spi); + + regmap = devm_regmap_init_spi(spi, &bmc150_magn_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "Failed to register spi regmap: %pe\n", + regmap); + return PTR_ERR(regmap); + } + return bmc150_magn_probe(&spi->dev, regmap, spi->irq, id->name); +} + +static void bmc150_magn_spi_remove(struct spi_device *spi) +{ + bmc150_magn_remove(&spi->dev); +} + +static const struct spi_device_id bmc150_magn_spi_id[] = { + { .name = "bmc150_magn" }, + { .name = "bmc156_magn" }, + { .name = "bmm150_magn" }, + { } +}; +MODULE_DEVICE_TABLE(spi, bmc150_magn_spi_id); + +static struct spi_driver bmc150_magn_spi_driver = { + .probe = bmc150_magn_spi_probe, + .remove = bmc150_magn_spi_remove, + .id_table = bmc150_magn_spi_id, + .driver = { + .name = "bmc150_magn_spi", + }, +}; +module_spi_driver(bmc150_magn_spi_driver); + +MODULE_AUTHOR("Daniel Baluta +#include +#include +#include +#include +#include +#include +#include "../common/hid-sensors/hid-sensor-trigger.h" + +enum magn_3d_channel { + CHANNEL_SCAN_INDEX_X, + CHANNEL_SCAN_INDEX_Y, + CHANNEL_SCAN_INDEX_Z, + CHANNEL_SCAN_INDEX_NORTH_MAGN_TILT_COMP, + CHANNEL_SCAN_INDEX_NORTH_TRUE_TILT_COMP, + CHANNEL_SCAN_INDEX_NORTH_MAGN, + CHANNEL_SCAN_INDEX_NORTH_TRUE, + CHANNEL_SCAN_INDEX_TIMESTAMP, + MAGN_3D_CHANNEL_MAX, +}; + +struct common_attributes { + int scale_pre_decml; + int scale_post_decml; + int scale_precision; + int value_offset; +}; + +struct magn_3d_state { + struct hid_sensor_hub_callbacks callbacks; + struct hid_sensor_common magn_flux_attributes; + struct hid_sensor_common rot_attributes; + struct hid_sensor_hub_attribute_info magn[MAGN_3D_CHANNEL_MAX]; + + /* dynamically sized array to hold sensor values */ + u32 *iio_vals; + /* array of pointers to sensor value */ + u32 *magn_val_addr[MAGN_3D_CHANNEL_MAX]; + + struct common_attributes magn_flux_attr; + struct common_attributes rot_attr; + s64 timestamp; +}; + +static const u32 magn_3d_addresses[MAGN_3D_CHANNEL_MAX] = { + HID_USAGE_SENSOR_ORIENT_MAGN_FLUX_X_AXIS, + HID_USAGE_SENSOR_ORIENT_MAGN_FLUX_Y_AXIS, + HID_USAGE_SENSOR_ORIENT_MAGN_FLUX_Z_AXIS, + HID_USAGE_SENSOR_ORIENT_COMP_MAGN_NORTH, + HID_USAGE_SENSOR_ORIENT_COMP_TRUE_NORTH, + HID_USAGE_SENSOR_ORIENT_MAGN_NORTH, + HID_USAGE_SENSOR_ORIENT_TRUE_NORTH, + HID_USAGE_SENSOR_TIME_TIMESTAMP, +}; + +static const u32 magn_3d_sensitivity_addresses[] = { + HID_USAGE_SENSOR_DATA_ORIENTATION, + HID_USAGE_SENSOR_ORIENT_MAGN_FLUX, +}; + +/* Channel definitions */ +static const struct iio_chan_spec magn_3d_channels[] = { + { + .type = IIO_MAGN, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + }, { + .type = IIO_MAGN, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + }, { + .type = IIO_MAGN, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + }, { + .type = IIO_ROT, + .modified = 1, + .channel2 = IIO_MOD_NORTH_MAGN_TILT_COMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + }, { + .type = IIO_ROT, + .modified = 1, + .channel2 = IIO_MOD_NORTH_TRUE_TILT_COMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + }, { + .type = IIO_ROT, + .modified = 1, + .channel2 = IIO_MOD_NORTH_MAGN, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + }, { + .type = IIO_ROT, + .modified = 1, + .channel2 = IIO_MOD_NORTH_TRUE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + }, + IIO_CHAN_SOFT_TIMESTAMP(7) +}; + +/* Channel read_raw handler */ +static int magn_3d_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct magn_3d_state *magn_state = iio_priv(indio_dev); + int report_id = -1; + u32 address; + int ret_type; + s32 min; + + *val = 0; + *val2 = 0; + switch (mask) { + case IIO_CHAN_INFO_RAW: + hid_sensor_power_state(&magn_state->magn_flux_attributes, true); + report_id = magn_state->magn[chan->address].report_id; + min = magn_state->magn[chan->address].logical_minimum; + address = magn_3d_addresses[chan->address]; + if (report_id >= 0) + *val = sensor_hub_input_attr_get_raw_value( + magn_state->magn_flux_attributes.hsdev, + HID_USAGE_SENSOR_COMPASS_3D, address, + report_id, + SENSOR_HUB_SYNC, + min < 0); + else { + *val = 0; + hid_sensor_power_state( + &magn_state->magn_flux_attributes, + false); + return -EINVAL; + } + hid_sensor_power_state(&magn_state->magn_flux_attributes, false); + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_MAGN: + *val = magn_state->magn_flux_attr.scale_pre_decml; + *val2 = magn_state->magn_flux_attr.scale_post_decml; + ret_type = magn_state->magn_flux_attr.scale_precision; + break; + case IIO_ROT: + *val = magn_state->rot_attr.scale_pre_decml; + *val2 = magn_state->rot_attr.scale_post_decml; + ret_type = magn_state->rot_attr.scale_precision; + break; + default: + ret_type = -EINVAL; + } + break; + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_MAGN: + *val = magn_state->magn_flux_attr.value_offset; + ret_type = IIO_VAL_INT; + break; + case IIO_ROT: + *val = magn_state->rot_attr.value_offset; + ret_type = IIO_VAL_INT; + break; + default: + ret_type = -EINVAL; + } + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret_type = hid_sensor_read_samp_freq_value( + &magn_state->magn_flux_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + switch (chan->type) { + case IIO_MAGN: + ret_type = hid_sensor_read_raw_hyst_value( + &magn_state->magn_flux_attributes, val, val2); + break; + case IIO_ROT: + ret_type = hid_sensor_read_raw_hyst_value( + &magn_state->rot_attributes, val, val2); + break; + default: + ret_type = -EINVAL; + } + break; + default: + ret_type = -EINVAL; + break; + } + + return ret_type; +} + +/* Channel write_raw handler */ +static int magn_3d_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct magn_3d_state *magn_state = iio_priv(indio_dev); + int ret = 0; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = hid_sensor_write_samp_freq_value( + &magn_state->magn_flux_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + switch (chan->type) { + case IIO_MAGN: + ret = hid_sensor_write_raw_hyst_value( + &magn_state->magn_flux_attributes, val, val2); + break; + case IIO_ROT: + ret = hid_sensor_write_raw_hyst_value( + &magn_state->rot_attributes, val, val2); + break; + default: + ret = -EINVAL; + } + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info magn_3d_info = { + .read_raw = &magn_3d_read_raw, + .write_raw = &magn_3d_write_raw, +}; + +/* Callback handler to send event after all samples are received and captured */ +static int magn_3d_proc_event(struct hid_sensor_hub_device *hsdev, + u32 usage_id, void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct magn_3d_state *magn_state = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "magn_3d_proc_event\n"); + if (atomic_read(&magn_state->magn_flux_attributes.data_ready)) { + if (!magn_state->timestamp) + magn_state->timestamp = iio_get_time_ns(indio_dev); + + iio_push_to_buffers_with_timestamp(indio_dev, + magn_state->iio_vals, + magn_state->timestamp); + magn_state->timestamp = 0; + } + + return 0; +} + +/* Capture samples in local storage */ +static int magn_3d_capture_sample(struct hid_sensor_hub_device *hsdev, + u32 usage_id, + size_t raw_len, char *raw_data, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct magn_3d_state *magn_state = iio_priv(indio_dev); + int offset; + int ret = 0; + u32 *iio_val = NULL; + + switch (usage_id) { + case HID_USAGE_SENSOR_ORIENT_MAGN_FLUX_X_AXIS: + case HID_USAGE_SENSOR_ORIENT_MAGN_FLUX_Y_AXIS: + case HID_USAGE_SENSOR_ORIENT_MAGN_FLUX_Z_AXIS: + offset = (usage_id - HID_USAGE_SENSOR_ORIENT_MAGN_FLUX_X_AXIS) + + CHANNEL_SCAN_INDEX_X; + break; + case HID_USAGE_SENSOR_ORIENT_COMP_MAGN_NORTH: + case HID_USAGE_SENSOR_ORIENT_COMP_TRUE_NORTH: + case HID_USAGE_SENSOR_ORIENT_MAGN_NORTH: + case HID_USAGE_SENSOR_ORIENT_TRUE_NORTH: + offset = (usage_id - HID_USAGE_SENSOR_ORIENT_COMP_MAGN_NORTH) + + CHANNEL_SCAN_INDEX_NORTH_MAGN_TILT_COMP; + break; + case HID_USAGE_SENSOR_TIME_TIMESTAMP: + magn_state->timestamp = + hid_sensor_convert_timestamp(&magn_state->magn_flux_attributes, + *(s64 *)raw_data); + return ret; + default: + return -EINVAL; + } + + iio_val = magn_state->magn_val_addr[offset]; + + if (iio_val) + *iio_val = *((u32 *)raw_data); + else + ret = -EINVAL; + + return ret; +} + +/* Parse report which is specific to an usage id*/ +static int magn_3d_parse_report(struct platform_device *pdev, + struct hid_sensor_hub_device *hsdev, + struct iio_chan_spec **channels, + int *chan_count, + u32 usage_id, + struct magn_3d_state *st) +{ + int i; + int attr_count = 0; + struct iio_chan_spec *_channels; + + /* Scan for each usage attribute supported */ + for (i = 0; i < MAGN_3D_CHANNEL_MAX; i++) { + int status; + u32 address = magn_3d_addresses[i]; + + /* Check if usage attribute exists in the sensor hub device */ + status = sensor_hub_input_get_attribute_info(hsdev, + HID_INPUT_REPORT, + usage_id, + address, + &(st->magn[i])); + if (!status) + attr_count++; + } + + if (attr_count <= 0) { + dev_err(&pdev->dev, + "failed to find any supported usage attributes in report\n"); + return -EINVAL; + } + + dev_dbg(&pdev->dev, "magn_3d Found %d usage attributes\n", attr_count); + dev_dbg(&pdev->dev, "magn_3d X: %x:%x Y: %x:%x Z: %x:%x\n", + st->magn[0].index, + st->magn[0].report_id, + st->magn[1].index, st->magn[1].report_id, + st->magn[2].index, st->magn[2].report_id); + + /* Setup IIO channel array */ + _channels = devm_kcalloc(&pdev->dev, attr_count, + sizeof(struct iio_chan_spec), + GFP_KERNEL); + if (!_channels) { + dev_err(&pdev->dev, + "failed to allocate space for iio channels\n"); + return -ENOMEM; + } + + /* attr_count include timestamp channel, and the iio_vals should be aligned to 8byte */ + st->iio_vals = devm_kcalloc(&pdev->dev, + ((attr_count + 1) % 2 + (attr_count + 1) / 2) * 2, + sizeof(u32), GFP_KERNEL); + if (!st->iio_vals) { + dev_err(&pdev->dev, + "failed to allocate space for iio values array\n"); + return -ENOMEM; + } + + for (i = 0, *chan_count = 0; + i < MAGN_3D_CHANNEL_MAX && *chan_count < attr_count; + i++){ + if (st->magn[i].index >= 0) { + /* Setup IIO channel struct */ + (_channels[*chan_count]) = magn_3d_channels[i]; + (_channels[*chan_count]).scan_index = *chan_count; + (_channels[*chan_count]).address = i; + + if (i != CHANNEL_SCAN_INDEX_TIMESTAMP) { + /* Set magn_val_addr to iio value address */ + st->magn_val_addr[i] = &st->iio_vals[*chan_count]; + _channels[*chan_count].scan_type = (struct iio_scan_type) { + .format = 's', + .realbits = BYTES_TO_BITS(st->magn[i].size), + .storagebits = BITS_PER_TYPE(u32), + }; + } + (*chan_count)++; + } + } + + if (*chan_count <= 0) { + dev_err(&pdev->dev, + "failed to find any magnetic channels setup\n"); + return -EINVAL; + } + + *channels = _channels; + + dev_dbg(&pdev->dev, "magn_3d Setup %d IIO channels\n", *chan_count); + + st->magn_flux_attr.scale_precision = hid_sensor_format_scale( + HID_USAGE_SENSOR_COMPASS_3D, + &st->magn[CHANNEL_SCAN_INDEX_X], + &st->magn_flux_attr.scale_pre_decml, + &st->magn_flux_attr.scale_post_decml); + st->rot_attr.scale_precision + = hid_sensor_format_scale( + HID_USAGE_SENSOR_ORIENT_COMP_MAGN_NORTH, + &st->magn[CHANNEL_SCAN_INDEX_NORTH_MAGN_TILT_COMP], + &st->rot_attr.scale_pre_decml, + &st->rot_attr.scale_post_decml); + + if (st->rot_attributes.sensitivity.index < 0) { + sensor_hub_input_get_attribute_info(hsdev, + HID_FEATURE_REPORT, usage_id, + HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS | + HID_USAGE_SENSOR_ORIENT_COMP_MAGN_NORTH, + &st->rot_attributes.sensitivity); + dev_dbg(&pdev->dev, "Sensitivity index:report %d:%d\n", + st->rot_attributes.sensitivity.index, + st->rot_attributes.sensitivity.report_id); + } + + return 0; +} + +/* Function to initialize the processing for usage id */ +static int hid_magn_3d_probe(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + int ret = 0; + static char *name = "magn_3d"; + struct iio_dev *indio_dev; + struct magn_3d_state *magn_state; + struct iio_chan_spec *channels; + int chan_count = 0; + + indio_dev = devm_iio_device_alloc(&pdev->dev, + sizeof(struct magn_3d_state)); + if (!indio_dev) + return -ENOMEM; + + platform_set_drvdata(pdev, indio_dev); + + magn_state = iio_priv(indio_dev); + magn_state->magn_flux_attributes.hsdev = hsdev; + magn_state->magn_flux_attributes.pdev = pdev; + + ret = hid_sensor_parse_common_attributes(hsdev, + HID_USAGE_SENSOR_COMPASS_3D, + &magn_state->magn_flux_attributes, + magn_3d_sensitivity_addresses, + ARRAY_SIZE(magn_3d_sensitivity_addresses)); + if (ret) { + dev_err(&pdev->dev, "failed to setup common attributes\n"); + return ret; + } + magn_state->rot_attributes = magn_state->magn_flux_attributes; + /* sensitivity of rot_attribute is not the same as magn_flux_attributes */ + magn_state->rot_attributes.sensitivity.index = -1; + + ret = magn_3d_parse_report(pdev, hsdev, + &channels, &chan_count, + HID_USAGE_SENSOR_COMPASS_3D, magn_state); + if (ret) { + dev_err(&pdev->dev, "failed to parse report\n"); + return ret; + } + + indio_dev->channels = channels; + indio_dev->num_channels = chan_count; + indio_dev->info = &magn_3d_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + atomic_set(&magn_state->magn_flux_attributes.data_ready, 0); + + ret = hid_sensor_setup_trigger(indio_dev, name, + &magn_state->magn_flux_attributes); + if (ret < 0) { + dev_err(&pdev->dev, "trigger setup failed\n"); + return ret; + } + + magn_state->callbacks.send_event = magn_3d_proc_event; + magn_state->callbacks.capture_sample = magn_3d_capture_sample; + magn_state->callbacks.pdev = pdev; + ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_COMPASS_3D, + &magn_state->callbacks); + if (ret < 0) { + dev_err(&pdev->dev, "callback reg failed\n"); + goto error_remove_trigger; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "device register failed\n"); + goto error_remove_callback; + } + + return ret; + +error_remove_callback: + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_COMPASS_3D); +error_remove_trigger: + hid_sensor_remove_trigger(indio_dev, &magn_state->magn_flux_attributes); + return ret; +} + +/* Function to deinitialize the processing for usage id */ +static void hid_magn_3d_remove(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct magn_3d_state *magn_state = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_COMPASS_3D); + hid_sensor_remove_trigger(indio_dev, &magn_state->magn_flux_attributes); +} + +static const struct platform_device_id hid_magn_3d_ids[] = { + { + /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-200083", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, hid_magn_3d_ids); + +static struct platform_driver hid_magn_3d_platform_driver = { + .id_table = hid_magn_3d_ids, + .driver = { + .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, + }, + .probe = hid_magn_3d_probe, + .remove = hid_magn_3d_remove, +}; +module_platform_driver(hid_magn_3d_platform_driver); + +MODULE_DESCRIPTION("HID Sensor Magnetometer 3D"); +MODULE_AUTHOR("Srinivas Pandruvada "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HID"); diff --git a/drivers/iio/magnetometer/hmc5843.h b/drivers/iio/magnetometer/hmc5843.h new file mode 100644 index 000000000..7a3faf7ff --- /dev/null +++ b/drivers/iio/magnetometer/hmc5843.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Header file for hmc5843 driver + * + * Split from hmc5843.c + * Copyright (C) Josef Gajdusek + */ + +#ifndef HMC5843_CORE_H +#define HMC5843_CORE_H + +#include +#include + +#define HMC5843_CONFIG_REG_A 0x00 +#define HMC5843_CONFIG_REG_B 0x01 +#define HMC5843_MODE_REG 0x02 +#define HMC5843_DATA_OUT_MSB_REGS 0x03 +#define HMC5843_STATUS_REG 0x09 +#define HMC5843_ID_REG 0x0a +#define HMC5843_ID_END 0x0c + +enum hmc5843_ids { + HMC5843_ID, + HMC5883_ID, + HMC5883L_ID, + HMC5983_ID, +}; + +/** + * struct hmc5843_data - device specific data + * @dev: actual device + * @lock: update and read regmap data + * @regmap: hardware access register maps + * @variant: describe chip variants + * @scan: buffer to pack data for passing to + * iio_push_to_buffers_with_ts() + */ +struct hmc5843_data { + struct device *dev; + struct mutex lock; + struct regmap *regmap; + const struct hmc5843_chip_info *variant; + struct iio_mount_matrix orientation; + struct { + __be16 chans[3]; + aligned_s64 timestamp; + } scan; +}; + +int hmc5843_common_probe(struct device *dev, struct regmap *regmap, + enum hmc5843_ids id, const char *name); +void hmc5843_common_remove(struct device *dev); + +extern const struct dev_pm_ops hmc5843_pm_ops; +#endif /* HMC5843_CORE_H */ diff --git a/drivers/iio/magnetometer/hmc5843_core.c b/drivers/iio/magnetometer/hmc5843_core.c new file mode 100644 index 000000000..fc16ebd31 --- /dev/null +++ b/drivers/iio/magnetometer/hmc5843_core.c @@ -0,0 +1,688 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Device driver for the HMC5843 multi-chip module designed + * for low field magnetic sensing. + * + * Copyright (C) 2010 Texas Instruments + * + * Author: Shubhrajyoti Datta + * Acknowledgment: Jonathan Cameron for valuable inputs. + * Support for HMC5883 and HMC5883L by Peter Meerwald . + * Split to multiple files by Josef Gajdusek - 2014 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hmc5843.h" + +/* + * Range gain settings in (+-)Ga + * Beware: HMC5843 and HMC5883 have different recommended sensor field + * ranges; default corresponds to +-1.0 Ga and +-1.3 Ga, respectively + */ +#define HMC5843_RANGE_GAIN_OFFSET 0x05 +#define HMC5843_RANGE_GAIN_DEFAULT 0x01 +#define HMC5843_RANGE_GAIN_MASK 0xe0 + +/* Device status */ +#define HMC5843_DATA_READY 0x01 +#define HMC5843_DATA_OUTPUT_LOCK 0x02 + +/* Mode register configuration */ +#define HMC5843_MODE_CONVERSION_CONTINUOUS 0x00 +#define HMC5843_MODE_CONVERSION_SINGLE 0x01 +#define HMC5843_MODE_IDLE 0x02 +#define HMC5843_MODE_SLEEP 0x03 +#define HMC5843_MODE_MASK 0x03 + +/* + * HMC5843: Minimum data output rate + * HMC5883: Typical data output rate + */ +#define HMC5843_RATE_OFFSET 0x02 +#define HMC5843_RATE_DEFAULT 0x04 +#define HMC5843_RATE_MASK 0x1c + +/* Device measurement configuration */ +#define HMC5843_MEAS_CONF_NORMAL 0x00 +#define HMC5843_MEAS_CONF_POSITIVE_BIAS 0x01 +#define HMC5843_MEAS_CONF_NEGATIVE_BIAS 0x02 +#define HMC5843_MEAS_CONF_MASK 0x03 + +/* + * API for setting the measurement configuration to + * Normal, Positive bias and Negative bias + * + * From the datasheet: + * 0 - Normal measurement configuration (default): In normal measurement + * configuration the device follows normal measurement flow. Pins BP + * and BN are left floating and high impedance. + * + * 1 - Positive bias configuration: In positive bias configuration, a + * positive current is forced across the resistive load on pins BP + * and BN. + * + * 2 - Negative bias configuration. In negative bias configuration, a + * negative current is forced across the resistive load on pins BP + * and BN. + * + * 3 - Only available on HMC5983. Magnetic sensor is disabled. + * Temperature sensor is enabled. + */ + +static const char *const hmc5843_meas_conf_modes[] = {"normal", "positivebias", + "negativebias"}; + +static const char *const hmc5983_meas_conf_modes[] = {"normal", "positivebias", + "negativebias", + "disabled"}; +/* Scaling factors: 10000000/Gain */ +static const int hmc5843_regval_to_nanoscale[] = { + 6173, 7692, 10309, 12821, 18868, 21739, 25641, 35714 +}; + +static const int hmc5883_regval_to_nanoscale[] = { + 7812, 9766, 13021, 16287, 24096, 27701, 32573, 45662 +}; + +static const int hmc5883l_regval_to_nanoscale[] = { + 7299, 9174, 12195, 15152, 22727, 25641, 30303, 43478 +}; + +/* + * From the datasheet: + * Value | HMC5843 | HMC5883/HMC5883L + * | Data output rate (Hz) | Data output rate (Hz) + * 0 | 0.5 | 0.75 + * 1 | 1 | 1.5 + * 2 | 2 | 3 + * 3 | 5 | 7.5 + * 4 | 10 (default) | 15 + * 5 | 20 | 30 + * 6 | 50 | 75 + * 7 | Not used | Not used + */ +static const int hmc5843_regval_to_samp_freq[][2] = { + {0, 500000}, {1, 0}, {2, 0}, {5, 0}, {10, 0}, {20, 0}, {50, 0} +}; + +static const int hmc5883_regval_to_samp_freq[][2] = { + {0, 750000}, {1, 500000}, {3, 0}, {7, 500000}, {15, 0}, {30, 0}, + {75, 0} +}; + +static const int hmc5983_regval_to_samp_freq[][2] = { + {0, 750000}, {1, 500000}, {3, 0}, {7, 500000}, {15, 0}, {30, 0}, + {75, 0}, {220, 0} +}; + +/* Describe chip variants */ +struct hmc5843_chip_info { + const struct iio_chan_spec *channels; + const int (*regval_to_samp_freq)[2]; + const int n_regval_to_samp_freq; + const int *regval_to_nanoscale; + const int n_regval_to_nanoscale; +}; + +/* The lower two bits contain the current conversion mode */ +static s32 hmc5843_set_mode(struct hmc5843_data *data, u8 operating_mode) +{ + int ret; + + mutex_lock(&data->lock); + ret = regmap_update_bits(data->regmap, HMC5843_MODE_REG, + HMC5843_MODE_MASK, operating_mode); + mutex_unlock(&data->lock); + + return ret; +} + +static int hmc5843_wait_measurement(struct hmc5843_data *data) +{ + int tries = 150; + unsigned int val; + int ret; + + while (tries-- > 0) { + ret = regmap_read(data->regmap, HMC5843_STATUS_REG, &val); + if (ret < 0) + return ret; + if (val & HMC5843_DATA_READY) + break; + msleep(20); + } + + if (tries < 0) { + dev_err(data->dev, "data not ready\n"); + return -EIO; + } + + return 0; +} + +/* Return the measurement value from the specified channel */ +static int hmc5843_read_measurement(struct hmc5843_data *data, + int idx, int *val) +{ + __be16 values[3]; + int ret; + + mutex_lock(&data->lock); + ret = hmc5843_wait_measurement(data); + if (ret < 0) { + mutex_unlock(&data->lock); + return ret; + } + ret = regmap_bulk_read(data->regmap, HMC5843_DATA_OUT_MSB_REGS, + values, sizeof(values)); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + + *val = sign_extend32(be16_to_cpu(values[idx]), 15); + return IIO_VAL_INT; +} + +static int hmc5843_set_meas_conf(struct hmc5843_data *data, u8 meas_conf) +{ + int ret; + + mutex_lock(&data->lock); + ret = regmap_update_bits(data->regmap, HMC5843_CONFIG_REG_A, + HMC5843_MEAS_CONF_MASK, meas_conf); + mutex_unlock(&data->lock); + + return ret; +} + +static +int hmc5843_show_measurement_configuration(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct hmc5843_data *data = iio_priv(indio_dev); + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, HMC5843_CONFIG_REG_A, &val); + if (ret) + return ret; + + return val & HMC5843_MEAS_CONF_MASK; +} + +static +int hmc5843_set_measurement_configuration(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int meas_conf) +{ + struct hmc5843_data *data = iio_priv(indio_dev); + + return hmc5843_set_meas_conf(data, meas_conf); +} + +static const struct iio_mount_matrix * +hmc5843_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct hmc5843_data *data = iio_priv(indio_dev); + + return &data->orientation; +} + +static const struct iio_enum hmc5843_meas_conf_enum = { + .items = hmc5843_meas_conf_modes, + .num_items = ARRAY_SIZE(hmc5843_meas_conf_modes), + .get = hmc5843_show_measurement_configuration, + .set = hmc5843_set_measurement_configuration, +}; + +static const struct iio_chan_spec_ext_info hmc5843_ext_info[] = { + IIO_ENUM("meas_conf", IIO_SHARED_BY_TYPE, &hmc5843_meas_conf_enum), + IIO_ENUM_AVAILABLE("meas_conf", IIO_SHARED_BY_TYPE, &hmc5843_meas_conf_enum), + IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, hmc5843_get_mount_matrix), + { } +}; + +static const struct iio_enum hmc5983_meas_conf_enum = { + .items = hmc5983_meas_conf_modes, + .num_items = ARRAY_SIZE(hmc5983_meas_conf_modes), + .get = hmc5843_show_measurement_configuration, + .set = hmc5843_set_measurement_configuration, +}; + +static const struct iio_chan_spec_ext_info hmc5983_ext_info[] = { + IIO_ENUM("meas_conf", IIO_SHARED_BY_TYPE, &hmc5983_meas_conf_enum), + IIO_ENUM_AVAILABLE("meas_conf", IIO_SHARED_BY_TYPE, &hmc5983_meas_conf_enum), + IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, hmc5843_get_mount_matrix), + { } +}; + +static +ssize_t hmc5843_show_samp_freq_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hmc5843_data *data = iio_priv(dev_to_iio_dev(dev)); + size_t len = 0; + int i; + + for (i = 0; i < data->variant->n_regval_to_samp_freq; i++) + len += scnprintf(buf + len, PAGE_SIZE - len, + "%d.%d ", data->variant->regval_to_samp_freq[i][0], + data->variant->regval_to_samp_freq[i][1]); + + /* replace trailing space by newline */ + buf[len - 1] = '\n'; + + return len; +} + +static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(hmc5843_show_samp_freq_avail); + +static int hmc5843_set_samp_freq(struct hmc5843_data *data, u8 rate) +{ + int ret; + + mutex_lock(&data->lock); + ret = regmap_update_bits(data->regmap, HMC5843_CONFIG_REG_A, + HMC5843_RATE_MASK, + rate << HMC5843_RATE_OFFSET); + mutex_unlock(&data->lock); + + return ret; +} + +static int hmc5843_get_samp_freq_index(struct hmc5843_data *data, + int val, int val2) +{ + int i; + + for (i = 0; i < data->variant->n_regval_to_samp_freq; i++) + if (val == data->variant->regval_to_samp_freq[i][0] && + val2 == data->variant->regval_to_samp_freq[i][1]) + return i; + + return -EINVAL; +} + +static int hmc5843_set_range_gain(struct hmc5843_data *data, u8 range) +{ + int ret; + + mutex_lock(&data->lock); + ret = regmap_update_bits(data->regmap, HMC5843_CONFIG_REG_B, + HMC5843_RANGE_GAIN_MASK, + range << HMC5843_RANGE_GAIN_OFFSET); + mutex_unlock(&data->lock); + + return ret; +} + +static ssize_t hmc5843_show_scale_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hmc5843_data *data = iio_priv(dev_to_iio_dev(dev)); + + size_t len = 0; + int i; + + for (i = 0; i < data->variant->n_regval_to_nanoscale; i++) + len += scnprintf(buf + len, PAGE_SIZE - len, + "0.%09d ", data->variant->regval_to_nanoscale[i]); + + /* replace trailing space by newline */ + buf[len - 1] = '\n'; + + return len; +} + +static IIO_DEVICE_ATTR(scale_available, S_IRUGO, + hmc5843_show_scale_avail, NULL, 0); + +static int hmc5843_get_scale_index(struct hmc5843_data *data, int val, int val2) +{ + int i; + + if (val) + return -EINVAL; + + for (i = 0; i < data->variant->n_regval_to_nanoscale; i++) + if (val2 == data->variant->regval_to_nanoscale[i]) + return i; + + return -EINVAL; +} + +static int hmc5843_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct hmc5843_data *data = iio_priv(indio_dev); + unsigned int rval; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return hmc5843_read_measurement(data, chan->scan_index, val); + case IIO_CHAN_INFO_SCALE: + ret = regmap_read(data->regmap, HMC5843_CONFIG_REG_B, &rval); + if (ret < 0) + return ret; + rval >>= HMC5843_RANGE_GAIN_OFFSET; + *val = 0; + *val2 = data->variant->regval_to_nanoscale[rval]; + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = regmap_read(data->regmap, HMC5843_CONFIG_REG_A, &rval); + if (ret < 0) + return ret; + rval >>= HMC5843_RATE_OFFSET; + *val = data->variant->regval_to_samp_freq[rval][0]; + *val2 = data->variant->regval_to_samp_freq[rval][1]; + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static int hmc5843_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct hmc5843_data *data = iio_priv(indio_dev); + int rate, range; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + rate = hmc5843_get_samp_freq_index(data, val, val2); + if (rate < 0) + return -EINVAL; + + return hmc5843_set_samp_freq(data, rate); + case IIO_CHAN_INFO_SCALE: + range = hmc5843_get_scale_index(data, val, val2); + if (range < 0) + return -EINVAL; + + return hmc5843_set_range_gain(data, range); + default: + return -EINVAL; + } +} + +static int hmc5843_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static irqreturn_t hmc5843_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct hmc5843_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->lock); + ret = hmc5843_wait_measurement(data); + if (ret < 0) { + mutex_unlock(&data->lock); + goto done; + } + + ret = regmap_bulk_read(data->regmap, HMC5843_DATA_OUT_MSB_REGS, + data->scan.chans, sizeof(data->scan.chans)); + + mutex_unlock(&data->lock); + if (ret < 0) + goto done; + + iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + iio_get_time_ns(indio_dev)); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +#define HMC5843_CHANNEL(axis, idx) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = idx, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + .ext_info = hmc5843_ext_info, \ + } + +#define HMC5983_CHANNEL(axis, idx) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = idx, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + .ext_info = hmc5983_ext_info, \ + } + +static const struct iio_chan_spec hmc5843_channels[] = { + HMC5843_CHANNEL(X, 0), + HMC5843_CHANNEL(Y, 1), + HMC5843_CHANNEL(Z, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +/* Beware: Y and Z are exchanged on HMC5883 and 5983 */ +static const struct iio_chan_spec hmc5883_channels[] = { + HMC5843_CHANNEL(X, 0), + HMC5843_CHANNEL(Z, 1), + HMC5843_CHANNEL(Y, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_chan_spec hmc5983_channels[] = { + HMC5983_CHANNEL(X, 0), + HMC5983_CHANNEL(Z, 1), + HMC5983_CHANNEL(Y, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static struct attribute *hmc5843_attributes[] = { + &iio_dev_attr_scale_available.dev_attr.attr, + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group hmc5843_group = { + .attrs = hmc5843_attributes, +}; + +static const struct hmc5843_chip_info hmc5843_chip_info_tbl[] = { + [HMC5843_ID] = { + .channels = hmc5843_channels, + .regval_to_samp_freq = hmc5843_regval_to_samp_freq, + .n_regval_to_samp_freq = + ARRAY_SIZE(hmc5843_regval_to_samp_freq), + .regval_to_nanoscale = hmc5843_regval_to_nanoscale, + .n_regval_to_nanoscale = + ARRAY_SIZE(hmc5843_regval_to_nanoscale), + }, + [HMC5883_ID] = { + .channels = hmc5883_channels, + .regval_to_samp_freq = hmc5883_regval_to_samp_freq, + .n_regval_to_samp_freq = + ARRAY_SIZE(hmc5883_regval_to_samp_freq), + .regval_to_nanoscale = hmc5883_regval_to_nanoscale, + .n_regval_to_nanoscale = + ARRAY_SIZE(hmc5883_regval_to_nanoscale), + }, + [HMC5883L_ID] = { + .channels = hmc5883_channels, + .regval_to_samp_freq = hmc5883_regval_to_samp_freq, + .n_regval_to_samp_freq = + ARRAY_SIZE(hmc5883_regval_to_samp_freq), + .regval_to_nanoscale = hmc5883l_regval_to_nanoscale, + .n_regval_to_nanoscale = + ARRAY_SIZE(hmc5883l_regval_to_nanoscale), + }, + [HMC5983_ID] = { + .channels = hmc5983_channels, + .regval_to_samp_freq = hmc5983_regval_to_samp_freq, + .n_regval_to_samp_freq = + ARRAY_SIZE(hmc5983_regval_to_samp_freq), + .regval_to_nanoscale = hmc5883l_regval_to_nanoscale, + .n_regval_to_nanoscale = + ARRAY_SIZE(hmc5883l_regval_to_nanoscale), + } +}; + +static int hmc5843_init(struct hmc5843_data *data) +{ + int ret; + u8 id[3]; + + ret = regmap_bulk_read(data->regmap, HMC5843_ID_REG, + id, ARRAY_SIZE(id)); + if (ret < 0) + return ret; + if (id[0] != 'H' || id[1] != '4' || id[2] != '3') { + dev_err(data->dev, "no HMC5843/5883/5883L/5983 sensor\n"); + return -ENODEV; + } + + ret = hmc5843_set_meas_conf(data, HMC5843_MEAS_CONF_NORMAL); + if (ret < 0) + return ret; + ret = hmc5843_set_samp_freq(data, HMC5843_RATE_DEFAULT); + if (ret < 0) + return ret; + ret = hmc5843_set_range_gain(data, HMC5843_RANGE_GAIN_DEFAULT); + if (ret < 0) + return ret; + return hmc5843_set_mode(data, HMC5843_MODE_CONVERSION_CONTINUOUS); +} + +static const struct iio_info hmc5843_info = { + .attrs = &hmc5843_group, + .read_raw = &hmc5843_read_raw, + .write_raw = &hmc5843_write_raw, + .write_raw_get_fmt = &hmc5843_write_raw_get_fmt, +}; + +static const unsigned long hmc5843_scan_masks[] = {0x7, 0}; + +static int hmc5843_common_suspend(struct device *dev) +{ + return hmc5843_set_mode(iio_priv(dev_get_drvdata(dev)), + HMC5843_MODE_SLEEP); +} + +static int hmc5843_common_resume(struct device *dev) +{ + return hmc5843_set_mode(iio_priv(dev_get_drvdata(dev)), + HMC5843_MODE_CONVERSION_CONTINUOUS); +} +EXPORT_NS_SIMPLE_DEV_PM_OPS(hmc5843_pm_ops, hmc5843_common_suspend, + hmc5843_common_resume, IIO_HMC5843); + +int hmc5843_common_probe(struct device *dev, struct regmap *regmap, + enum hmc5843_ids id, const char *name) +{ + struct hmc5843_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + dev_set_drvdata(dev, indio_dev); + + /* default settings at probe */ + data = iio_priv(indio_dev); + data->dev = dev; + data->regmap = regmap; + data->variant = &hmc5843_chip_info_tbl[id]; + mutex_init(&data->lock); + + ret = iio_read_mount_matrix(dev, &data->orientation); + if (ret) + return ret; + + indio_dev->name = name; + indio_dev->info = &hmc5843_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = data->variant->channels; + indio_dev->num_channels = 4; + indio_dev->available_scan_masks = hmc5843_scan_masks; + + ret = hmc5843_init(data); + if (ret < 0) + return ret; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + hmc5843_trigger_handler, NULL); + if (ret < 0) + goto buffer_setup_err; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto buffer_cleanup; + + return 0; + +buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); +buffer_setup_err: + hmc5843_set_mode(iio_priv(indio_dev), HMC5843_MODE_SLEEP); + return ret; +} +EXPORT_SYMBOL_NS(hmc5843_common_probe, "IIO_HMC5843"); + +void hmc5843_common_remove(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + + /* sleep mode to save power */ + hmc5843_set_mode(iio_priv(indio_dev), HMC5843_MODE_SLEEP); +} +EXPORT_SYMBOL_NS(hmc5843_common_remove, "IIO_HMC5843"); + +MODULE_AUTHOR("Shubhrajyoti Datta "); +MODULE_DESCRIPTION("HMC5843/5883/5883L/5983 core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/magnetometer/hmc5843_i2c.c b/drivers/iio/magnetometer/hmc5843_i2c.c new file mode 100644 index 000000000..4c454b005 --- /dev/null +++ b/drivers/iio/magnetometer/hmc5843_i2c.c @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * i2c driver for hmc5843/5843/5883/5883l/5983 + * + * Split from hmc5843.c + * Copyright (C) Josef Gajdusek + */ + +#include +#include +#include +#include +#include + +#include "hmc5843.h" + +static const struct regmap_range hmc5843_readable_ranges[] = { + regmap_reg_range(0, HMC5843_ID_END), +}; + +static const struct regmap_access_table hmc5843_readable_table = { + .yes_ranges = hmc5843_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(hmc5843_readable_ranges), +}; + +static const struct regmap_range hmc5843_writable_ranges[] = { + regmap_reg_range(0, HMC5843_MODE_REG), +}; + +static const struct regmap_access_table hmc5843_writable_table = { + .yes_ranges = hmc5843_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(hmc5843_writable_ranges), +}; + +static const struct regmap_range hmc5843_volatile_ranges[] = { + regmap_reg_range(HMC5843_DATA_OUT_MSB_REGS, HMC5843_STATUS_REG), +}; + +static const struct regmap_access_table hmc5843_volatile_table = { + .yes_ranges = hmc5843_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(hmc5843_volatile_ranges), +}; + +static const struct regmap_config hmc5843_i2c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .rd_table = &hmc5843_readable_table, + .wr_table = &hmc5843_writable_table, + .volatile_table = &hmc5843_volatile_table, + + .cache_type = REGCACHE_RBTREE, +}; + +static int hmc5843_i2c_probe(struct i2c_client *cli) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(cli); + struct regmap *regmap = devm_regmap_init_i2c(cli, + &hmc5843_i2c_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return hmc5843_common_probe(&cli->dev, + regmap, + id->driver_data, id->name); +} + +static void hmc5843_i2c_remove(struct i2c_client *client) +{ + hmc5843_common_remove(&client->dev); +} + +static const struct i2c_device_id hmc5843_id[] = { + { .name = "hmc5843", .driver_data = HMC5843_ID }, + { .name = "hmc5883", .driver_data = HMC5883_ID }, + { .name = "hmc5883l", .driver_data = HMC5883L_ID }, + { .name = "hmc5983", .driver_data = HMC5983_ID }, + { } +}; +MODULE_DEVICE_TABLE(i2c, hmc5843_id); + +static const struct of_device_id hmc5843_of_match[] = { + { .compatible = "honeywell,hmc5843", .data = (void *)HMC5843_ID }, + { .compatible = "honeywell,hmc5883", .data = (void *)HMC5883_ID }, + { .compatible = "honeywell,hmc5883l", .data = (void *)HMC5883L_ID }, + { .compatible = "honeywell,hmc5983", .data = (void *)HMC5983_ID }, + { } +}; +MODULE_DEVICE_TABLE(of, hmc5843_of_match); + +static struct i2c_driver hmc5843_driver = { + .driver = { + .name = "hmc5843", + .pm = pm_sleep_ptr(&hmc5843_pm_ops), + .of_match_table = hmc5843_of_match, + }, + .id_table = hmc5843_id, + .probe = hmc5843_i2c_probe, + .remove = hmc5843_i2c_remove, +}; +module_i2c_driver(hmc5843_driver); + +MODULE_AUTHOR("Josef Gajdusek "); +MODULE_DESCRIPTION("HMC5843/5883/5883L/5983 i2c driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HMC5843"); diff --git a/drivers/iio/magnetometer/hmc5843_spi.c b/drivers/iio/magnetometer/hmc5843_spi.c new file mode 100644 index 000000000..29dee5dce --- /dev/null +++ b/drivers/iio/magnetometer/hmc5843_spi.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * SPI driver for hmc5983 + * + * Copyright (C) Josef Gajdusek + */ + +#include +#include +#include + +#include "hmc5843.h" + +static const struct regmap_range hmc5843_readable_ranges[] = { + regmap_reg_range(0, HMC5843_ID_END), +}; + +static const struct regmap_access_table hmc5843_readable_table = { + .yes_ranges = hmc5843_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(hmc5843_readable_ranges), +}; + +static const struct regmap_range hmc5843_writable_ranges[] = { + regmap_reg_range(0, HMC5843_MODE_REG), +}; + +static const struct regmap_access_table hmc5843_writable_table = { + .yes_ranges = hmc5843_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(hmc5843_writable_ranges), +}; + +static const struct regmap_range hmc5843_volatile_ranges[] = { + regmap_reg_range(HMC5843_DATA_OUT_MSB_REGS, HMC5843_STATUS_REG), +}; + +static const struct regmap_access_table hmc5843_volatile_table = { + .yes_ranges = hmc5843_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(hmc5843_volatile_ranges), +}; + +static const struct regmap_config hmc5843_spi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .rd_table = &hmc5843_readable_table, + .wr_table = &hmc5843_writable_table, + .volatile_table = &hmc5843_volatile_table, + + /* Autoincrement address pointer */ + .read_flag_mask = 0xc0, + + .cache_type = REGCACHE_RBTREE, +}; + +static int hmc5843_spi_probe(struct spi_device *spi) +{ + int ret; + struct regmap *regmap; + + spi->mode = SPI_MODE_3; + spi->max_speed_hz = 8000000; + ret = spi_setup(spi); + if (ret) + return ret; + + regmap = devm_regmap_init_spi(spi, &hmc5843_spi_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return hmc5843_common_probe(&spi->dev, + regmap, HMC5983_ID, "hmc5983"); +} + +static void hmc5843_spi_remove(struct spi_device *spi) +{ + hmc5843_common_remove(&spi->dev); +} + +static const struct spi_device_id hmc5843_id[] = { + { .name = "hmc5983" }, + { } +}; +MODULE_DEVICE_TABLE(spi, hmc5843_id); + +static struct spi_driver hmc5843_driver = { + .driver = { + .name = "hmc5843", + .pm = pm_sleep_ptr(&hmc5843_pm_ops), + }, + .id_table = hmc5843_id, + .probe = hmc5843_spi_probe, + .remove = hmc5843_spi_remove, +}; + +module_spi_driver(hmc5843_driver); + +MODULE_AUTHOR("Josef Gajdusek "); +MODULE_DESCRIPTION("HMC5983 SPI driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HMC5843"); diff --git a/drivers/iio/magnetometer/mag3110.c b/drivers/iio/magnetometer/mag3110.c new file mode 100644 index 000000000..479a12ece --- /dev/null +++ b/drivers/iio/magnetometer/mag3110.c @@ -0,0 +1,647 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * mag3110.c - Support for Freescale MAG3110 magnetometer sensor + * + * Copyright (c) 2013 Peter Meerwald + * + * (7-bit I2C slave address 0x0e) + * + * TODO: irq, user offset, oversampling, continuous mode + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAG3110_STATUS 0x00 +#define MAG3110_OUT_X 0x01 /* MSB first */ +#define MAG3110_OUT_Y 0x03 +#define MAG3110_OUT_Z 0x05 +#define MAG3110_WHO_AM_I 0x07 +#define MAG3110_SYSMOD 0x08 +#define MAG3110_OFF_X 0x09 /* MSB first */ +#define MAG3110_OFF_Y 0x0b +#define MAG3110_OFF_Z 0x0d +#define MAG3110_DIE_TEMP 0x0f +#define MAG3110_CTRL_REG1 0x10 +#define MAG3110_CTRL_REG2 0x11 + +#define MAG3110_STATUS_DRDY (BIT(2) | BIT(1) | BIT(0)) + +#define MAG3110_CTRL_DR_MASK (BIT(7) | BIT(6) | BIT(5)) +#define MAG3110_CTRL_DR_SHIFT 5 +#define MAG3110_CTRL_DR_DEFAULT 0 + +#define MAG3110_SYSMOD_MODE_MASK GENMASK(1, 0) + +#define MAG3110_CTRL_TM BIT(1) /* trigger single measurement */ +#define MAG3110_CTRL_AC BIT(0) /* continuous measurements */ + +#define MAG3110_CTRL_AUTO_MRST_EN BIT(7) /* magnetic auto-reset */ +#define MAG3110_CTRL_RAW BIT(5) /* measurements not user-offset corrected */ + +#define MAG3110_DEVICE_ID 0xc4 + +/* Each client has this additional data */ +struct mag3110_data { + struct i2c_client *client; + struct mutex lock; + u8 ctrl_reg1; + int sleep_val; + struct regulator *vdd_reg; + struct regulator *vddio_reg; + /* Ensure natural alignment of timestamp */ + struct { + __be16 channels[3]; + u8 temperature; + aligned_s64 ts; + } scan; +}; + +static int mag3110_request(struct mag3110_data *data) +{ + int ret, tries = 150; + + if ((data->ctrl_reg1 & MAG3110_CTRL_AC) == 0) { + /* trigger measurement */ + ret = i2c_smbus_write_byte_data(data->client, MAG3110_CTRL_REG1, + data->ctrl_reg1 | MAG3110_CTRL_TM); + if (ret < 0) + return ret; + } + + while (tries-- > 0) { + ret = i2c_smbus_read_byte_data(data->client, MAG3110_STATUS); + if (ret < 0) + return ret; + /* wait for data ready */ + if ((ret & MAG3110_STATUS_DRDY) == MAG3110_STATUS_DRDY) + break; + + if (data->sleep_val <= 20) + usleep_range(data->sleep_val * 250, data->sleep_val * 500); + else + msleep(20); + } + + if (tries < 0) { + dev_err(&data->client->dev, "data not ready\n"); + return -EIO; + } + + return 0; +} + +static int mag3110_read(struct mag3110_data *data, __be16 buf[3]) +{ + int ret; + + guard(mutex)(&data->lock); + ret = mag3110_request(data); + if (ret < 0) + return ret; + return i2c_smbus_read_i2c_block_data(data->client, MAG3110_OUT_X, + 3 * sizeof(__be16), (u8 *) buf); +} + +static ssize_t mag3110_show_int_plus_micros(char *buf, + const int (*vals)[2], int n) +{ + size_t len = 0; + + while (n-- > 0) + len += scnprintf(buf + len, PAGE_SIZE - len, + "%d.%06d ", vals[n][0], vals[n][1]); + + /* replace trailing space by newline */ + buf[len - 1] = '\n'; + + return len; +} + +static int mag3110_get_int_plus_micros_index(const int (*vals)[2], int n, + int val, int val2) +{ + while (n-- > 0) + if (val == vals[n][0] && val2 == vals[n][1]) + return n; + + return -EINVAL; +} + +static const int mag3110_samp_freq[8][2] = { + {80, 0}, {40, 0}, {20, 0}, {10, 0}, {5, 0}, {2, 500000}, + {1, 250000}, {0, 625000} +}; + +static ssize_t mag3110_show_samp_freq_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return mag3110_show_int_plus_micros(buf, mag3110_samp_freq, 8); +} + +static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(mag3110_show_samp_freq_avail); + +static int mag3110_get_samp_freq_index(struct mag3110_data *data, + int val, int val2) +{ + return mag3110_get_int_plus_micros_index(mag3110_samp_freq, 8, val, + val2); +} + +static int mag3110_calculate_sleep(struct mag3110_data *data) +{ + int ret, i = data->ctrl_reg1 >> MAG3110_CTRL_DR_SHIFT; + + if (mag3110_samp_freq[i][0] > 0) + ret = 1000 / mag3110_samp_freq[i][0]; + else + ret = 1000; + + return ret == 0 ? 1 : ret; +} + +static int mag3110_standby(struct mag3110_data *data) +{ + return i2c_smbus_write_byte_data(data->client, MAG3110_CTRL_REG1, + data->ctrl_reg1 & ~MAG3110_CTRL_AC); +} + +static int mag3110_wait_standby(struct mag3110_data *data) +{ + int ret, tries = 30; + + /* + * Takes up to 1/ODR to come out of active mode into stby + * Longest expected period is 12.5seconds. + * We'll sleep for 500ms between checks + */ + while (tries-- > 0) { + ret = i2c_smbus_read_byte_data(data->client, MAG3110_SYSMOD); + if (ret < 0) { + dev_err(&data->client->dev, "i2c error\n"); + return ret; + } + /* wait for standby */ + if ((ret & MAG3110_SYSMOD_MODE_MASK) == 0) + break; + + msleep_interruptible(500); + } + + if (tries < 0) { + dev_err(&data->client->dev, "device not entering standby mode\n"); + return -EIO; + } + + return 0; +} + +static int mag3110_active(struct mag3110_data *data) +{ + return i2c_smbus_write_byte_data(data->client, MAG3110_CTRL_REG1, + data->ctrl_reg1); +} + +/* returns >0 if active, 0 if in standby and <0 on error */ +static int mag3110_is_active(struct mag3110_data *data) +{ + int reg; + + reg = i2c_smbus_read_byte_data(data->client, MAG3110_CTRL_REG1); + if (reg < 0) + return reg; + + return reg & MAG3110_CTRL_AC; +} + +static int mag3110_change_config(struct mag3110_data *data, u8 reg, u8 val) +{ + int ret; + int is_active; + + guard(mutex)(&data->lock); + + is_active = mag3110_is_active(data); + if (is_active < 0) + return is_active; + + /* config can only be changed when in standby */ + if (is_active > 0) { + ret = mag3110_standby(data); + if (ret < 0) + return ret; + } + + /* + * After coming out of active we must wait for the part + * to transition to STBY. This can take up to 1 /ODR to occur + */ + ret = mag3110_wait_standby(data); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(data->client, reg, val); + if (ret < 0) + return ret; + + if (is_active > 0) { + ret = mag3110_active(data); + if (ret < 0) + return ret; + } + + return 0; +} + +static int __mag3110_read_info_raw(struct mag3110_data *data, + struct iio_chan_spec const *chan, + int *val) +{ + __be16 buffer[3]; + int ret; + + switch (chan->type) { + case IIO_MAGN: /* in 0.1 uT / LSB */ + ret = mag3110_read(data, buffer); + if (ret < 0) + return ret; + *val = sign_extend32(be16_to_cpu(buffer[chan->scan_index]), + chan->scan_type.realbits - 1); + return IIO_VAL_INT; + + case IIO_TEMP: { /* in 1 C / LSB */ + guard(mutex)(&data->lock); + ret = mag3110_request(data); + if (ret < 0) + return ret; + ret = i2c_smbus_read_byte_data(data->client, + MAG3110_DIE_TEMP); + if (ret < 0) + return ret; + *val = sign_extend32(ret, chan->scan_type.realbits - 1); + return IIO_VAL_INT; + } + default: + return -EINVAL; + } +} + +static int mag3110_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mag3110_data *data = iio_priv(indio_dev); + int i, ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = __mag3110_read_info_raw(data, chan, val); + iio_device_release_direct(indio_dev); + return ret; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_MAGN: + *val = 0; + *val2 = 1000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = 1000; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + i = data->ctrl_reg1 >> MAG3110_CTRL_DR_SHIFT; + *val = mag3110_samp_freq[i][0]; + *val2 = mag3110_samp_freq[i][1]; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBBIAS: + ret = i2c_smbus_read_word_swapped(data->client, + MAG3110_OFF_X + 2 * chan->scan_index); + if (ret < 0) + return ret; + *val = sign_extend32(ret >> 1, 14); + return IIO_VAL_INT; + } + return -EINVAL; +} + +static int __mag3110_write_raw(struct mag3110_data *data, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int rate; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + rate = mag3110_get_samp_freq_index(data, val, val2); + if (rate < 0) + return -EINVAL; + + data->ctrl_reg1 &= 0xff & ~MAG3110_CTRL_DR_MASK + & ~MAG3110_CTRL_AC; + data->ctrl_reg1 |= rate << MAG3110_CTRL_DR_SHIFT; + data->sleep_val = mag3110_calculate_sleep(data); + if (data->sleep_val < 40) + data->ctrl_reg1 |= MAG3110_CTRL_AC; + + return mag3110_change_config(data, MAG3110_CTRL_REG1, + data->ctrl_reg1); + + case IIO_CHAN_INFO_CALIBBIAS: + if (val < -10000 || val > 10000) + return -EINVAL; + + return i2c_smbus_write_word_swapped(data->client, + MAG3110_OFF_X + 2 * chan->scan_index, val << 1); + default: + return -EINVAL; + } +} + +static int mag3110_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct mag3110_data *data = iio_priv(indio_dev); + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = __mag3110_write_raw(data, chan, val, val2, mask); + iio_device_release_direct(indio_dev); + + return ret; +} + +static irqreturn_t mag3110_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct mag3110_data *data = iio_priv(indio_dev); + int ret; + + ret = mag3110_read(data, data->scan.channels); + if (ret < 0) + goto done; + + if (test_bit(3, indio_dev->active_scan_mask)) { + ret = i2c_smbus_read_byte_data(data->client, + MAG3110_DIE_TEMP); + if (ret < 0) + goto done; + data->scan.temperature = ret; + } + + iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + iio_get_time_ns(indio_dev)); + +done: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +#define MAG3110_CHANNEL(axis, idx) { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = idx, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ +} + +static const struct iio_chan_spec mag3110_channels[] = { + MAG3110_CHANNEL(X, 0), + MAG3110_CHANNEL(Y, 1), + MAG3110_CHANNEL(Z, 2), + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 3, + .scan_type = { + .sign = 's', + .realbits = 8, + .storagebits = 8, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static struct attribute *mag3110_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group mag3110_group = { + .attrs = mag3110_attributes, +}; + +static const struct iio_info mag3110_info = { + .attrs = &mag3110_group, + .read_raw = &mag3110_read_raw, + .write_raw = &mag3110_write_raw, +}; + +static const unsigned long mag3110_scan_masks[] = {0x7, 0xf, 0}; + +static int mag3110_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct mag3110_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + + data->vdd_reg = devm_regulator_get(&client->dev, "vdd"); + if (IS_ERR(data->vdd_reg)) + return dev_err_probe(&client->dev, PTR_ERR(data->vdd_reg), + "failed to get VDD regulator!\n"); + + data->vddio_reg = devm_regulator_get(&client->dev, "vddio"); + if (IS_ERR(data->vddio_reg)) + return dev_err_probe(&client->dev, PTR_ERR(data->vddio_reg), + "failed to get VDDIO regulator!\n"); + + ret = regulator_enable(data->vdd_reg); + if (ret) { + dev_err(&client->dev, "failed to enable VDD regulator!\n"); + return ret; + } + + ret = regulator_enable(data->vddio_reg); + if (ret) { + dev_err(&client->dev, "failed to enable VDDIO regulator!\n"); + goto disable_regulator_vdd; + } + + ret = i2c_smbus_read_byte_data(client, MAG3110_WHO_AM_I); + if (ret < 0) + goto disable_regulators; + if (ret != MAG3110_DEVICE_ID) { + ret = -ENODEV; + goto disable_regulators; + } + + data->client = client; + mutex_init(&data->lock); + + i2c_set_clientdata(client, indio_dev); + indio_dev->info = &mag3110_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = mag3110_channels; + indio_dev->num_channels = ARRAY_SIZE(mag3110_channels); + indio_dev->available_scan_masks = mag3110_scan_masks; + + data->ctrl_reg1 = MAG3110_CTRL_DR_DEFAULT << MAG3110_CTRL_DR_SHIFT; + data->sleep_val = mag3110_calculate_sleep(data); + if (data->sleep_val < 40) + data->ctrl_reg1 |= MAG3110_CTRL_AC; + + ret = mag3110_change_config(data, MAG3110_CTRL_REG1, data->ctrl_reg1); + if (ret < 0) + goto disable_regulators; + + ret = i2c_smbus_write_byte_data(client, MAG3110_CTRL_REG2, + MAG3110_CTRL_AUTO_MRST_EN); + if (ret < 0) + goto standby_on_error; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + mag3110_trigger_handler, NULL); + if (ret < 0) + goto standby_on_error; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto buffer_cleanup; + return 0; + +buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); +standby_on_error: + mag3110_standby(iio_priv(indio_dev)); +disable_regulators: + regulator_disable(data->vddio_reg); +disable_regulator_vdd: + regulator_disable(data->vdd_reg); + + return ret; +} + +static void mag3110_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct mag3110_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + mag3110_standby(iio_priv(indio_dev)); + regulator_disable(data->vddio_reg); + regulator_disable(data->vdd_reg); +} + +static int mag3110_suspend(struct device *dev) +{ + struct mag3110_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + int ret; + + ret = mag3110_standby(iio_priv(i2c_get_clientdata( + to_i2c_client(dev)))); + if (ret) + return ret; + + ret = regulator_disable(data->vddio_reg); + if (ret) { + dev_err(dev, "failed to disable VDDIO regulator\n"); + return ret; + } + + ret = regulator_disable(data->vdd_reg); + if (ret) { + dev_err(dev, "failed to disable VDD regulator\n"); + return ret; + } + + return 0; +} + +static int mag3110_resume(struct device *dev) +{ + struct mag3110_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + int ret; + + ret = regulator_enable(data->vdd_reg); + if (ret) { + dev_err(dev, "failed to enable VDD regulator\n"); + return ret; + } + + ret = regulator_enable(data->vddio_reg); + if (ret) { + dev_err(dev, "failed to enable VDDIO regulator\n"); + regulator_disable(data->vdd_reg); + return ret; + } + + return i2c_smbus_write_byte_data(data->client, MAG3110_CTRL_REG1, + data->ctrl_reg1); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(mag3110_pm_ops, mag3110_suspend, + mag3110_resume); + +static const struct i2c_device_id mag3110_id[] = { + { .name = "mag3110" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mag3110_id); + +static const struct of_device_id mag3110_of_match[] = { + { .compatible = "fsl,mag3110" }, + { } +}; +MODULE_DEVICE_TABLE(of, mag3110_of_match); + +static struct i2c_driver mag3110_driver = { + .driver = { + .name = "mag3110", + .of_match_table = mag3110_of_match, + .pm = pm_sleep_ptr(&mag3110_pm_ops), + }, + .probe = mag3110_probe, + .remove = mag3110_remove, + .id_table = mag3110_id, +}; +module_i2c_driver(mag3110_driver); + +MODULE_AUTHOR("Peter Meerwald "); +MODULE_DESCRIPTION("Freescale MAG3110 magnetometer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/magnetometer/mmc35240.c b/drivers/iio/magnetometer/mmc35240.c new file mode 100644 index 000000000..6f40cba4b --- /dev/null +++ b/drivers/iio/magnetometer/mmc35240.c @@ -0,0 +1,582 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MMC35240 - MEMSIC 3-axis Magnetic Sensor + * + * Copyright (c) 2015, Intel Corporation. + * + * IIO driver for MMC35240 (7-bit I2C slave address 0x30). + * + * TODO: offset, ACPI, continuous measurement mode, PM + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define MMC35240_DRV_NAME "mmc35240" + +#define MMC35240_REG_XOUT_L 0x00 +#define MMC35240_REG_XOUT_H 0x01 +#define MMC35240_REG_YOUT_L 0x02 +#define MMC35240_REG_YOUT_H 0x03 +#define MMC35240_REG_ZOUT_L 0x04 +#define MMC35240_REG_ZOUT_H 0x05 + +#define MMC35240_REG_STATUS 0x06 +#define MMC35240_REG_CTRL0 0x07 +#define MMC35240_REG_CTRL1 0x08 + +#define MMC35240_REG_ID 0x20 + +#define MMC35240_STATUS_MEAS_DONE_BIT BIT(0) + +#define MMC35240_CTRL0_REFILL_BIT BIT(7) +#define MMC35240_CTRL0_RESET_BIT BIT(6) +#define MMC35240_CTRL0_SET_BIT BIT(5) +#define MMC35240_CTRL0_CMM_BIT BIT(1) +#define MMC35240_CTRL0_TM_BIT BIT(0) + +/* output resolution bits */ +#define MMC35240_CTRL1_BW0_BIT BIT(0) +#define MMC35240_CTRL1_BW1_BIT BIT(1) + +#define MMC35240_CTRL1_BW_MASK (MMC35240_CTRL1_BW0_BIT | \ + MMC35240_CTRL1_BW1_BIT) +#define MMC35240_CTRL1_BW_SHIFT 0 + +#define MMC35240_WAIT_CHARGE_PUMP 50000 /* us */ +#define MMC35240_WAIT_SET_RESET 1000 /* us */ + +/* + * Memsic OTP process code piece is put here for reference: + * + * #define OTP_CONVERT(REG) ((float)((REG) >=32 ? (32 - (REG)) : (REG)) * 0.006 + * 1) For X axis, the COEFFICIENT is always 1. + * 2) For Y axis, the COEFFICIENT is as below: + * f_OTP_matrix[4] = OTP_CONVERT(((reg_data[1] & 0x03) << 4) | + * (reg_data[2] >> 4)) + 1.0; + * 3) For Z axis, the COEFFICIENT is as below: + * f_OTP_matrix[8] = (OTP_CONVERT(reg_data[3] & 0x3f) + 1) * 1.35; + * We implemented the OTP logic into driver. + */ + +/* scale = 1000 here for Y otp */ +#define MMC35240_OTP_CONVERT_Y(REG) (((REG) >= 32 ? (32 - (REG)) : (REG)) * 6) + +/* 0.6 * 1.35 = 0.81, scale 10000 for Z otp */ +#define MMC35240_OTP_CONVERT_Z(REG) (((REG) >= 32 ? (32 - (REG)) : (REG)) * 81) + +#define MMC35240_X_COEFF(x) (x) +#define MMC35240_Y_COEFF(y) (y + 1000) +#define MMC35240_Z_COEFF(z) (z + 13500) + +#define MMC35240_OTP_START_ADDR 0x1B + +enum mmc35240_resolution { + MMC35240_16_BITS_SLOW = 0, /* 7.92 ms */ + MMC35240_16_BITS_FAST, /* 4.08 ms */ + MMC35240_14_BITS, /* 2.16 ms */ + MMC35240_12_BITS, /* 1.20 ms */ +}; + +enum mmc35240_axis { + AXIS_X = 0, + AXIS_Y, + AXIS_Z, +}; + +static const struct { + int sens[3]; /* sensitivity per X, Y, Z axis */ + int nfo; /* null field output */ +} mmc35240_props_table[] = { + /* 16 bits, 125Hz ODR */ + { + {1024, 1024, 1024}, + 32768, + }, + /* 16 bits, 250Hz ODR */ + { + {1024, 1024, 770}, + 32768, + }, + /* 14 bits, 450Hz ODR */ + { + {256, 256, 193}, + 8192, + }, + /* 12 bits, 800Hz ODR */ + { + {64, 64, 48}, + 2048, + }, +}; + +struct mmc35240_data { + struct i2c_client *client; + struct mutex mutex; + struct regmap *regmap; + enum mmc35240_resolution res; + + /* OTP compensation */ + int axis_coef[3]; + int axis_scale[3]; +}; + +static const struct { + int val; + int val2; +} mmc35240_samp_freq[] = { {1, 500000}, + {13, 0}, + {25, 0}, + {50, 0} }; + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("1.5 13 25 50"); + +#define MMC35240_CHANNEL(_axis) { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## _axis, \ + .address = AXIS_ ## _axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec mmc35240_channels[] = { + MMC35240_CHANNEL(X), + MMC35240_CHANNEL(Y), + MMC35240_CHANNEL(Z), +}; + +static struct attribute *mmc35240_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group mmc35240_attribute_group = { + .attrs = mmc35240_attributes, +}; + +static int mmc35240_get_samp_freq_index(struct mmc35240_data *data, + int val, int val2) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mmc35240_samp_freq); i++) + if (mmc35240_samp_freq[i].val == val && + mmc35240_samp_freq[i].val2 == val2) + return i; + return -EINVAL; +} + +static int mmc35240_hw_set(struct mmc35240_data *data, bool set) +{ + int ret; + u8 coil_bit; + + /* + * Recharge the capacitor at VCAP pin, requested to be issued + * before a SET/RESET command. + */ + ret = regmap_set_bits(data->regmap, MMC35240_REG_CTRL0, + MMC35240_CTRL0_REFILL_BIT); + if (ret < 0) + return ret; + usleep_range(MMC35240_WAIT_CHARGE_PUMP, MMC35240_WAIT_CHARGE_PUMP + 1); + + if (set) + coil_bit = MMC35240_CTRL0_SET_BIT; + else + coil_bit = MMC35240_CTRL0_RESET_BIT; + + return regmap_set_bits(data->regmap, MMC35240_REG_CTRL0, coil_bit); + +} + +static int mmc35240_init(struct mmc35240_data *data) +{ + int ret, y_convert, z_convert; + unsigned int reg_id; + u8 otp_data[6]; + + ret = regmap_read(data->regmap, MMC35240_REG_ID, ®_id); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading product id\n"); + return ret; + } + + dev_dbg(&data->client->dev, "MMC35240 chip id %x\n", reg_id); + + /* + * make sure we restore sensor characteristics, by doing + * a SET/RESET sequence, the axis polarity being naturally + * aligned after RESET + */ + ret = mmc35240_hw_set(data, true); + if (ret < 0) + return ret; + usleep_range(MMC35240_WAIT_SET_RESET, MMC35240_WAIT_SET_RESET + 1); + + ret = mmc35240_hw_set(data, false); + if (ret < 0) + return ret; + + /* set default sampling frequency */ + ret = regmap_update_bits(data->regmap, MMC35240_REG_CTRL1, + MMC35240_CTRL1_BW_MASK, + data->res << MMC35240_CTRL1_BW_SHIFT); + if (ret < 0) + return ret; + + ret = regmap_bulk_read(data->regmap, MMC35240_OTP_START_ADDR, + otp_data, sizeof(otp_data)); + if (ret < 0) + return ret; + + y_convert = MMC35240_OTP_CONVERT_Y(((otp_data[1] & 0x03) << 4) | + (otp_data[2] >> 4)); + z_convert = MMC35240_OTP_CONVERT_Z(otp_data[3] & 0x3f); + + data->axis_coef[0] = MMC35240_X_COEFF(1); + data->axis_coef[1] = MMC35240_Y_COEFF(y_convert); + data->axis_coef[2] = MMC35240_Z_COEFF(z_convert); + + data->axis_scale[0] = 1; + data->axis_scale[1] = 1000; + data->axis_scale[2] = 10000; + + return 0; +} + +static int mmc35240_take_measurement(struct mmc35240_data *data) +{ + int ret, tries = 100; + unsigned int reg_status; + + ret = regmap_write(data->regmap, MMC35240_REG_CTRL0, + MMC35240_CTRL0_TM_BIT); + if (ret < 0) + return ret; + + while (tries-- > 0) { + ret = regmap_read(data->regmap, MMC35240_REG_STATUS, + ®_status); + if (ret < 0) + return ret; + if (reg_status & MMC35240_STATUS_MEAS_DONE_BIT) + break; + /* minimum wait time to complete measurement is 10 ms */ + usleep_range(10000, 11000); + } + + if (tries < 0) { + dev_err(&data->client->dev, "data not ready\n"); + return -EIO; + } + + return 0; +} + +static int mmc35240_read_measurement(struct mmc35240_data *data, __le16 buf[3]) +{ + int ret; + + ret = mmc35240_take_measurement(data); + if (ret < 0) + return ret; + + return regmap_bulk_read(data->regmap, MMC35240_REG_XOUT_L, buf, + 3 * sizeof(__le16)); +} + +/** + * mmc35240_raw_to_mgauss - convert raw readings to milli gauss. Also apply + * compensation for output value. + * + * @data: device private data + * @index: axis index for which we want the conversion + * @buf: raw data to be converted, 2 bytes in little endian format + * @val: compensated output reading (unit is milli gauss) + * + * Returns: 0 in case of success, -EINVAL when @index is not valid + */ +static int mmc35240_raw_to_mgauss(struct mmc35240_data *data, int index, + __le16 buf[], int *val) +{ + int raw[3]; + int sens[3]; + int nfo; + + raw[AXIS_X] = le16_to_cpu(buf[AXIS_X]); + raw[AXIS_Y] = le16_to_cpu(buf[AXIS_Y]); + raw[AXIS_Z] = le16_to_cpu(buf[AXIS_Z]); + + sens[AXIS_X] = mmc35240_props_table[data->res].sens[AXIS_X]; + sens[AXIS_Y] = mmc35240_props_table[data->res].sens[AXIS_Y]; + sens[AXIS_Z] = mmc35240_props_table[data->res].sens[AXIS_Z]; + + nfo = mmc35240_props_table[data->res].nfo; + + switch (index) { + case AXIS_X: + *val = (raw[AXIS_X] - nfo) * 1000 / sens[AXIS_X]; + break; + case AXIS_Y: + *val = (raw[AXIS_Y] - nfo) * 1000 / sens[AXIS_Y] - + (raw[AXIS_Z] - nfo) * 1000 / sens[AXIS_Z]; + break; + case AXIS_Z: + *val = (raw[AXIS_Y] - nfo) * 1000 / sens[AXIS_Y] + + (raw[AXIS_Z] - nfo) * 1000 / sens[AXIS_Z]; + break; + default: + return -EINVAL; + } + /* apply OTP compensation */ + *val = (*val) * data->axis_coef[index] / data->axis_scale[index]; + + return 0; +} + +static int mmc35240_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct mmc35240_data *data = iio_priv(indio_dev); + int ret, i; + unsigned int reg; + __le16 buf[3]; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&data->mutex); + ret = mmc35240_read_measurement(data, buf); + mutex_unlock(&data->mutex); + if (ret < 0) + return ret; + ret = mmc35240_raw_to_mgauss(data, chan->address, buf, val); + if (ret < 0) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = 1000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + mutex_lock(&data->mutex); + ret = regmap_read(data->regmap, MMC35240_REG_CTRL1, ®); + mutex_unlock(&data->mutex); + if (ret < 0) + return ret; + + i = (reg & MMC35240_CTRL1_BW_MASK) >> MMC35240_CTRL1_BW_SHIFT; + if (i < 0 || i >= ARRAY_SIZE(mmc35240_samp_freq)) + return -EINVAL; + + *val = mmc35240_samp_freq[i].val; + *val2 = mmc35240_samp_freq[i].val2; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int mmc35240_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct mmc35240_data *data = iio_priv(indio_dev); + int i, ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + i = mmc35240_get_samp_freq_index(data, val, val2); + if (i < 0) + return -EINVAL; + mutex_lock(&data->mutex); + ret = regmap_update_bits(data->regmap, MMC35240_REG_CTRL1, + MMC35240_CTRL1_BW_MASK, + i << MMC35240_CTRL1_BW_SHIFT); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } +} + +static const struct iio_info mmc35240_info = { + .read_raw = mmc35240_read_raw, + .write_raw = mmc35240_write_raw, + .attrs = &mmc35240_attribute_group, +}; + +static bool mmc35240_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MMC35240_REG_CTRL0: + case MMC35240_REG_CTRL1: + return true; + default: + return false; + } +} + +static bool mmc35240_is_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MMC35240_REG_XOUT_L: + case MMC35240_REG_XOUT_H: + case MMC35240_REG_YOUT_L: + case MMC35240_REG_YOUT_H: + case MMC35240_REG_ZOUT_L: + case MMC35240_REG_ZOUT_H: + case MMC35240_REG_STATUS: + case MMC35240_REG_ID: + return true; + default: + return false; + } +} + +static bool mmc35240_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MMC35240_REG_CTRL0: + case MMC35240_REG_CTRL1: + return false; + default: + return true; + } +} + +static const struct reg_default mmc35240_reg_defaults[] = { + { MMC35240_REG_CTRL0, 0x00 }, + { MMC35240_REG_CTRL1, 0x00 }, +}; + +static const struct regmap_config mmc35240_regmap_config = { + .name = "mmc35240_regmap", + + .reg_bits = 8, + .val_bits = 8, + + .max_register = MMC35240_REG_ID, + .cache_type = REGCACHE_FLAT, + + .writeable_reg = mmc35240_is_writeable_reg, + .readable_reg = mmc35240_is_readable_reg, + .volatile_reg = mmc35240_is_volatile_reg, + + .reg_defaults = mmc35240_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(mmc35240_reg_defaults), +}; + +static int mmc35240_probe(struct i2c_client *client) +{ + struct mmc35240_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, &mmc35240_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "regmap initialization failed\n"); + return PTR_ERR(regmap); + } + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->regmap = regmap; + data->res = MMC35240_16_BITS_SLOW; + + mutex_init(&data->mutex); + + indio_dev->info = &mmc35240_info; + indio_dev->name = MMC35240_DRV_NAME; + indio_dev->channels = mmc35240_channels; + indio_dev->num_channels = ARRAY_SIZE(mmc35240_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = mmc35240_init(data); + if (ret < 0) { + dev_err(&client->dev, "mmc35240 chip init failed\n"); + return ret; + } + return devm_iio_device_register(&client->dev, indio_dev); +} + +static int mmc35240_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mmc35240_data *data = iio_priv(indio_dev); + + regcache_cache_only(data->regmap, true); + + return 0; +} + +static int mmc35240_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mmc35240_data *data = iio_priv(indio_dev); + int ret; + + regcache_mark_dirty(data->regmap); + ret = regcache_sync_region(data->regmap, MMC35240_REG_CTRL0, + MMC35240_REG_CTRL1); + if (ret < 0) + dev_err(dev, "Failed to restore control registers\n"); + + regcache_cache_only(data->regmap, false); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(mmc35240_pm_ops, mmc35240_suspend, + mmc35240_resume); + +static const struct of_device_id mmc35240_of_match[] = { + { .compatible = "memsic,mmc35240", }, + { } +}; +MODULE_DEVICE_TABLE(of, mmc35240_of_match); + +static const struct acpi_device_id mmc35240_acpi_match[] = { + {"MMC35240", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, mmc35240_acpi_match); + +static const struct i2c_device_id mmc35240_id[] = { + { .name = "mmc35240" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mmc35240_id); + +static struct i2c_driver mmc35240_driver = { + .driver = { + .name = MMC35240_DRV_NAME, + .of_match_table = mmc35240_of_match, + .pm = pm_sleep_ptr(&mmc35240_pm_ops), + .acpi_match_table = mmc35240_acpi_match, + }, + .probe = mmc35240_probe, + .id_table = mmc35240_id, +}; + +module_i2c_driver(mmc35240_driver); + +MODULE_AUTHOR("Daniel Baluta "); +MODULE_DESCRIPTION("MEMSIC MMC35240 magnetic sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/magnetometer/mmc5633.c b/drivers/iio/magnetometer/mmc5633.c new file mode 100644 index 000000000..f4dae7ba1 --- /dev/null +++ b/drivers/iio/magnetometer/mmc5633.c @@ -0,0 +1,585 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MMC5633 - MEMSIC 3-axis Magnetic Sensor + * + * Copyright (c) 2015, Intel Corporation. + * Copyright (c) 2025, NXP + * + * IIO driver for MMC5633, base on mmc35240.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MMC5633_REG_XOUT0 0x00 +#define MMC5633_REG_XOUT1 0x01 +#define MMC5633_REG_YOUT0 0x02 +#define MMC5633_REG_YOUT1 0x03 +#define MMC5633_REG_ZOUT0 0x04 +#define MMC5633_REG_ZOUT1 0x05 +#define MMC5633_REG_XOUT2 0x06 +#define MMC5633_REG_YOUT2 0x07 +#define MMC5633_REG_ZOUT2 0x08 +#define MMC5633_REG_TOUT 0x09 + +#define MMC5633_REG_STATUS1 0x18 +#define MMC5633_REG_STATUS0 0x19 +#define MMC5633_REG_CTRL0 0x1b +#define MMC5633_REG_CTRL1 0x1c +#define MMC5633_REG_CTRL2 0x1d + +#define MMC5633_REG_ID 0x39 + +#define MMC5633_STATUS1_MEAS_T_DONE_BIT BIT(7) +#define MMC5633_STATUS1_MEAS_M_DONE_BIT BIT(6) + +#define MMC5633_CTRL0_CMM_FREQ_EN BIT(7) +#define MMC5633_CTRL0_AUTO_ST_EN BIT(6) +#define MMC5633_CTRL0_AUTO_SR_EN BIT(5) +#define MMC5633_CTRL0_RESET BIT(4) +#define MMC5633_CTRL0_SET BIT(3) +#define MMC5633_CTRL0_MEAS_T BIT(1) +#define MMC5633_CTRL0_MEAS_M BIT(0) + +#define MMC5633_CTRL1_BW_MASK GENMASK(1, 0) + +#define MMC5633_WAIT_SET_RESET_US (1 * USEC_PER_MSEC) + +#define MMC5633_HDR_CTRL0_MEAS_M 0x01 +#define MMC5633_HDR_CTRL0_MEAS_T 0x03 +#define MMC5633_HDR_CTRL0_SET 0x05 +#define MMC5633_HDR_CTRL0_RESET 0x07 + +enum mmc5633_axis { + MMC5633_AXIS_X, + MMC5633_AXIS_Y, + MMC5633_AXIS_Z, + MMC5633_TEMPERATURE, +}; + +struct mmc5633_data { + struct regmap *regmap; + struct i3c_device *i3cdev; + struct mutex mutex; /* protect to finish one whole measurement */ +}; + +static int mmc5633_samp_freq[][2] = { + { 1, 200000 }, + { 2, 0 }, + { 3, 500000 }, + { 6, 600000 }, +}; + +#define MMC5633_CHANNEL(_axis) { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## _axis, \ + .address = MMC5633_AXIS_ ## _axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec mmc5633_channels[] = { + MMC5633_CHANNEL(X), + MMC5633_CHANNEL(Y), + MMC5633_CHANNEL(Z), + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = MMC5633_TEMPERATURE, + }, +}; + +static int mmc5633_get_samp_freq_index(struct mmc5633_data *data, + int val, int val2) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(mmc5633_samp_freq); i++) + if (mmc5633_samp_freq[i][0] == val && + mmc5633_samp_freq[i][1] == val2) + return i; + return -EINVAL; +} + +static int mmc5633_init(struct mmc5633_data *data) +{ + unsigned int reg_id; + int ret; + + ret = regmap_read(data->regmap, MMC5633_REG_ID, ®_id); + if (ret) + return dev_err_probe(regmap_get_device(data->regmap), ret, + "Error reading product id\n"); + + /* + * Make sure we restore sensor characteristics, by doing + * a SET/RESET sequence, the axis polarity being naturally + * aligned after RESET. + */ + ret = regmap_write(data->regmap, MMC5633_REG_CTRL0, MMC5633_CTRL0_SET); + if (ret) + return ret; + + /* + * Minimum time interval between SET or RESET to other operations is + * 1ms according to Operating Timing Diagram in datasheet. + */ + fsleep(MMC5633_WAIT_SET_RESET_US); + + ret = regmap_write(data->regmap, MMC5633_REG_CTRL0, MMC5633_CTRL0_RESET); + if (ret) + return ret; + + /* set default sampling frequency */ + return regmap_update_bits(data->regmap, MMC5633_REG_CTRL1, + MMC5633_CTRL1_BW_MASK, + FIELD_PREP(MMC5633_CTRL1_BW_MASK, 0)); +} + +static int mmc5633_take_measurement(struct mmc5633_data *data, int address) +{ + unsigned int reg_status, val; + int ret; + + val = (address == MMC5633_TEMPERATURE) ? MMC5633_CTRL0_MEAS_T : MMC5633_CTRL0_MEAS_M; + ret = regmap_write(data->regmap, MMC5633_REG_CTRL0, val); + if (ret < 0) + return ret; + + val = (address == MMC5633_TEMPERATURE) ? + MMC5633_STATUS1_MEAS_T_DONE_BIT : MMC5633_STATUS1_MEAS_M_DONE_BIT; + ret = regmap_read_poll_timeout(data->regmap, MMC5633_REG_STATUS1, reg_status, + reg_status & val, + 10 * USEC_PER_MSEC, + 100 * 10 * USEC_PER_MSEC); + if (ret) { + dev_err(regmap_get_device(data->regmap), "data not ready\n"); + return ret; + } + + return 0; +} + +static bool mmc5633_is_support_hdr(struct mmc5633_data *data) +{ + if (!data->i3cdev) + return false; + + return i3c_device_get_supported_xfer_mode(data->i3cdev) & BIT(I3C_HDR_DDR); +} + +static int mmc5633_read_measurement(struct mmc5633_data *data, int address, void *buf, size_t sz) +{ + struct device *dev = regmap_get_device(data->regmap); + u8 data_cmd[2], status[2]; + unsigned int val, ready; + int ret; + + if (mmc5633_is_support_hdr(data)) { + struct i3c_xfer xfers_wr_cmd[] = { + { + .cmd = 0x3b, + .len = 2, + .data.out = data_cmd, + } + }; + struct i3c_xfer xfers_rd_sta_cmd[] = { + { + .cmd = 0x23 | BIT(7), /* RDSTA CMD */ + .len = 2, + .data.in = status, + }, + }; + struct i3c_xfer xfers_rd_data_cmd[] = { + { + .cmd = 0x22 | BIT(7), /* RDLONG CMD */ + .len = sz, + .data.in = buf, + }, + }; + + data_cmd[0] = 0; + data_cmd[1] = (address == MMC5633_TEMPERATURE) ? + MMC5633_HDR_CTRL0_MEAS_T : MMC5633_HDR_CTRL0_MEAS_M; + + ret = i3c_device_do_xfers(data->i3cdev, xfers_wr_cmd, + ARRAY_SIZE(xfers_wr_cmd), I3C_HDR_DDR); + if (ret < 0) + return ret; + + ready = (address == MMC5633_TEMPERATURE) ? + MMC5633_STATUS1_MEAS_T_DONE_BIT : MMC5633_STATUS1_MEAS_M_DONE_BIT; + ret = read_poll_timeout(i3c_device_do_xfers, val, + val || (status[0] & ready), + 10 * USEC_PER_MSEC, + 100 * 10 * USEC_PER_MSEC, 0, + data->i3cdev, xfers_rd_sta_cmd, + ARRAY_SIZE(xfers_rd_sta_cmd), I3C_HDR_DDR); + if (ret) { + dev_err(dev, "data not ready\n"); + return ret; + } + if (val) { + dev_err(dev, "i3c transfer error\n"); + return val; + } + return i3c_device_do_xfers(data->i3cdev, xfers_rd_data_cmd, + ARRAY_SIZE(xfers_rd_data_cmd), I3C_HDR_DDR); + } + + /* Fallback to use SDR/I2C mode */ + ret = mmc5633_take_measurement(data, address); + if (ret < 0) + return ret; + + if (address == MMC5633_TEMPERATURE) + /* + * Put tempeature to last byte of buff to align HDR case. + * I3C will early terminate data read if previous data is not + * available. + */ + return regmap_bulk_read(data->regmap, MMC5633_REG_TOUT, buf + sz - 1, 1); + + return regmap_bulk_read(data->regmap, MMC5633_REG_XOUT0, buf, sz); +} + +/* X,Y,Z 3 channels, each channel has 3 byte and TEMP */ +#define MMC5633_ALL_SIZE (3 * 3 + 1) + +static int mmc5633_get_raw(struct mmc5633_data *data, int index, unsigned char *buf, int *val) +{ + if (index == MMC5633_TEMPERATURE) { + *val = buf[MMC5633_ALL_SIZE - 1]; + return 0; + } + /* + * X[19..12] X[11..4] Y[19..12] Y[11..4] Z[19..12] Z[11..4] X[3..0] Y[3..0] Z[3..0] + */ + *val = get_unaligned_be16(buf + 2 * index) << 4; + *val |= buf[index + 6] >> 4; + + return 0; +} + +static int mmc5633_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct mmc5633_data *data = iio_priv(indio_dev); + char buf[MMC5633_ALL_SIZE]; + unsigned int reg, i; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + scoped_guard(mutex, &data->mutex) { + ret = mmc5633_read_measurement(data, chan->address, buf, MMC5633_ALL_SIZE); + if (ret < 0) + return ret; + } + + ret = mmc5633_get_raw(data, chan->address, buf, val); + if (ret < 0) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_MAGN) { + *val = 0; + *val2 = 62500; + } else { + *val = 0; + *val2 = 800000000; /* 0.8C */ + } + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + if (chan->type == IIO_TEMP) { + *val = -75; + return IIO_VAL_INT; + } + return -EINVAL; + case IIO_CHAN_INFO_SAMP_FREQ: + scoped_guard(mutex, &data->mutex) { + ret = regmap_read(data->regmap, MMC5633_REG_CTRL1, ®); + if (ret < 0) + return ret; + } + + i = FIELD_GET(MMC5633_CTRL1_BW_MASK, reg); + if (i >= ARRAY_SIZE(mmc5633_samp_freq)) + return -EINVAL; + + *val = mmc5633_samp_freq[i][0]; + *val2 = mmc5633_samp_freq[i][1]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int mmc5633_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct mmc5633_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: { + ret = mmc5633_get_samp_freq_index(data, val, val2); + if (ret < 0) + return ret; + + guard(mutex)(&data->mutex); + + return regmap_update_bits(data->regmap, MMC5633_REG_CTRL1, + MMC5633_CTRL1_BW_MASK, + FIELD_PREP(MMC5633_CTRL1_BW_MASK, ret)); + } + default: + return -EINVAL; + } +} + +static int mmc5633_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = (const int *)mmc5633_samp_freq; + *length = ARRAY_SIZE(mmc5633_samp_freq) * 2; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const struct iio_info mmc5633_info = { + .read_raw = mmc5633_read_raw, + .write_raw = mmc5633_write_raw, + .read_avail = mmc5633_read_avail, +}; + +static bool mmc5633_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MMC5633_REG_CTRL0: + case MMC5633_REG_CTRL1: + return true; + default: + return false; + } +} + +static bool mmc5633_is_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MMC5633_REG_XOUT0: + case MMC5633_REG_XOUT1: + case MMC5633_REG_YOUT0: + case MMC5633_REG_YOUT1: + case MMC5633_REG_ZOUT0: + case MMC5633_REG_ZOUT1: + case MMC5633_REG_XOUT2: + case MMC5633_REG_YOUT2: + case MMC5633_REG_ZOUT2: + case MMC5633_REG_TOUT: + case MMC5633_REG_STATUS1: + case MMC5633_REG_ID: + return true; + default: + return false; + } +} + +static bool mmc5633_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MMC5633_REG_CTRL0: + case MMC5633_REG_CTRL1: + return false; + default: + return true; + } +} + +static const struct reg_default mmc5633_reg_defaults[] = { + { MMC5633_REG_CTRL0, 0x00 }, + { MMC5633_REG_CTRL1, 0x00 }, +}; + +static const struct regmap_config mmc5633_regmap_config = { + .name = "mmc5633_regmap", + + .reg_bits = 8, + .val_bits = 8, + + .max_register = MMC5633_REG_ID, + .cache_type = REGCACHE_MAPLE, + + .writeable_reg = mmc5633_is_writeable_reg, + .readable_reg = mmc5633_is_readable_reg, + .volatile_reg = mmc5633_is_volatile_reg, + + .reg_defaults = mmc5633_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(mmc5633_reg_defaults), +}; + +static int mmc5633_common_probe(struct regmap *regmap, char *name, + struct i3c_device *i3cdev) +{ + struct device *dev = regmap_get_device(regmap); + struct mmc5633_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + + data->regmap = regmap; + data->i3cdev = i3cdev; + + ret = devm_mutex_init(dev, &data->mutex); + if (ret) + return ret; + + indio_dev->info = &mmc5633_info; + indio_dev->name = name; + indio_dev->channels = mmc5633_channels; + indio_dev->num_channels = ARRAY_SIZE(mmc5633_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = mmc5633_init(data); + if (ret < 0) + return dev_err_probe(dev, ret, "mmc5633 chip init failed\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static int mmc5633_suspend(struct device *dev) +{ + struct regmap *regmap = dev_get_regmap(dev, NULL); + + regcache_cache_only(regmap, true); + + return 0; +} + +static int mmc5633_resume(struct device *dev) +{ + struct regmap *regmap = dev_get_regmap(dev, NULL); + int ret; + + regcache_mark_dirty(regmap); + ret = regcache_sync_region(regmap, MMC5633_REG_CTRL0, MMC5633_REG_CTRL1); + if (ret) + dev_err(dev, "Failed to restore control registers\n"); + + regcache_cache_only(regmap, false); + + return 0; +} + +static int mmc5633_i2c_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, &mmc5633_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "regmap init failed\n"); + + return mmc5633_common_probe(regmap, client->name, NULL); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(mmc5633_pm_ops, mmc5633_suspend, mmc5633_resume); + +static const struct of_device_id mmc5633_of_match[] = { + { .compatible = "memsic,mmc5603" }, + { .compatible = "memsic,mmc5633" }, + { } +}; +MODULE_DEVICE_TABLE(of, mmc5633_of_match); + +static const struct i2c_device_id mmc5633_i2c_id[] = { + { .name = "mmc5603" }, + { .name = "mmc5633" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mmc5633_i2c_id); + +static struct i2c_driver mmc5633_i2c_driver = { + .driver = { + .name = "mmc5633_i2c", + .of_match_table = mmc5633_of_match, + .pm = pm_sleep_ptr(&mmc5633_pm_ops), + }, + .probe = mmc5633_i2c_probe, + .id_table = mmc5633_i2c_id, +}; + +static const struct i3c_device_id mmc5633_i3c_ids[] = { + I3C_DEVICE(0x0251, 0x0000, NULL), + { } +}; +MODULE_DEVICE_TABLE(i3c, mmc5633_i3c_ids); + +static int mmc5633_i3c_probe(struct i3c_device *i3cdev) +{ + struct device *dev = i3cdev_to_dev(i3cdev); + struct regmap *regmap; + char *name; + + name = devm_kasprintf(dev, GFP_KERNEL, "mmc5633_%s", dev_name(dev)); + if (!name) + return -ENOMEM; + + regmap = devm_regmap_init_i3c(i3cdev, &mmc5633_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Failed to register i3c regmap\n"); + + return mmc5633_common_probe(regmap, name, i3cdev); +} + +static struct i3c_driver mmc5633_i3c_driver = { + .driver = { + .name = "mmc5633_i3c", + }, + .probe = mmc5633_i3c_probe, + .id_table = mmc5633_i3c_ids, +}; +module_i3c_i2c_driver(mmc5633_i3c_driver, &mmc5633_i2c_driver) + +MODULE_AUTHOR("Frank Li "); +MODULE_DESCRIPTION("MEMSIC MMC5633 magnetic sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/magnetometer/mmc5983.c b/drivers/iio/magnetometer/mmc5983.c new file mode 100644 index 000000000..d10a6288e --- /dev/null +++ b/drivers/iio/magnetometer/mmc5983.c @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MMC5983 - MEMSIC 3-axis Magnetic Sensor + * + * Copyright (c) 2026, Vlad Kulikov + * + * IIO driver for MMC5983 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MMC5983_REG_XOUT0 0x00 +#define MMC5983_REG_XOUT1 0x01 +#define MMC5983_REG_YOUT0 0x02 +#define MMC5983_REG_YOUT1 0x03 +#define MMC5983_REG_ZOUT0 0x04 +#define MMC5983_REG_ZOUT1 0x05 +#define MMC5983_REG_XYZOUT2 0x06 + +#define MMC5983_REG_STATUS 0x08 + +#define MMC5983_REG_CTRL0 0x09 +#define MMC5983_REG_CTRL1 0x0A +#define MMC5983_REG_CTRL2 0x0B +#define MMC5983_REG_CTRL3 0x0C + +#define MMC5983_REG_ID 0x2F + +#define MMC5983_PRODUCT_ID 0x30 + +#define MMC5983_STATUS_MEAS_M_DONE_BIT BIT(0) +#define MMC5983_STATUS_OTP_RD_DONE_BIT BIT(4) + +#define MMC5983_CTRL0_TM_M_BIT BIT(0) +#define MMC5983_CTRL0_SET_BIT BIT(3) +#define MMC5983_CTRL0_RESET_BIT BIT(4) +#define MMC5983_CTRL0_OTP_RD_BIT BIT(6) + +#define MMC5983_CTRL1_SW_RST_BIT BIT(7) + +enum mmc5983_axis { + MMC5983_AXIS_X, + MMC5983_AXIS_Y, + MMC5983_AXIS_Z, +}; + +struct mmc5983_data { + struct regmap *regmap; + /* Protects chip access during SET/RESET measurement sequence */ + struct mutex mutex; +}; + +#define MMC5983_CHANNEL(_axis) { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .address = MMC5983_AXIS_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec mmc5983_channels[] = { + MMC5983_CHANNEL(X), + MMC5983_CHANNEL(Y), + MMC5983_CHANNEL(Z), +}; + +static int mmc5983_pulse_coil(struct mmc5983_data *data, unsigned int coil_bit) +{ + int ret; + + ret = regmap_write(data->regmap, MMC5983_REG_CTRL0, coil_bit); + if (ret) + return ret; + + /* + * Datasheet page 15: SET/RESET coil pulse is 500 ns. + * Vendor sample code waits 500 us before the next operation. + */ + fsleep(500); + + return 0; +} + +static int mmc5983_take_measurement(struct mmc5983_data *data, int m[3]) +{ + unsigned int status; + u8 buf[7]; + int ret; + + ret = regmap_write(data->regmap, MMC5983_REG_CTRL0, + MMC5983_CTRL0_TM_M_BIT); + if (ret) + return ret; + + /* + * Datasheet page 15: measurement time is 8 ms at BW=00 (default, + * slowest setting). Use a 50 ms timeout for margin. + */ + ret = regmap_read_poll_timeout(data->regmap, MMC5983_REG_STATUS, + status, + status & MMC5983_STATUS_MEAS_M_DONE_BIT, + 10 * USEC_PER_MSEC, + 50 * USEC_PER_MSEC); + if (ret) + return ret; + + ret = regmap_bulk_read(data->regmap, MMC5983_REG_XOUT0, buf, + sizeof(buf)); + if (ret) + return ret; + + m[0] = (buf[0] << 10) | (buf[1] << 2) | ((buf[6] >> 6) & 0x3); + m[1] = (buf[2] << 10) | (buf[3] << 2) | ((buf[6] >> 4) & 0x3); + m[2] = (buf[4] << 10) | (buf[5] << 2) | ((buf[6] >> 2) & 0x3); + + return 0; +} + +static int mmc5983_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, + int *val2, long mask) +{ + struct mmc5983_data *data = iio_priv(indio_dev); + int m1[3], m2[3]; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + guard(mutex)(&data->mutex); + + /* SET: magnetize sensor elements in forward direction */ + ret = mmc5983_pulse_coil(data, MMC5983_CTRL0_SET_BIT); + if (ret) + return ret; + + ret = mmc5983_take_measurement(data, m1); + if (ret) + return ret; + + /* RESET: magnetize sensor elements in reverse direction */ + ret = mmc5983_pulse_coil(data, MMC5983_CTRL0_RESET_BIT); + if (ret) + return ret; + + ret = mmc5983_take_measurement(data, m2); + if (ret) + return ret; + + *val = (m1[chan->address] - m2[chan->address]) / 2; + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = 61035; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static const struct iio_info mmc5983_info = { + .read_raw = mmc5983_read_raw, +}; + +static bool mmc5983_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MMC5983_REG_CTRL0: + case MMC5983_REG_CTRL1: + case MMC5983_REG_CTRL2: + case MMC5983_REG_CTRL3: + return true; + default: + return false; + } +} + +static bool mmc5983_is_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MMC5983_REG_XOUT0: + case MMC5983_REG_XOUT1: + case MMC5983_REG_YOUT0: + case MMC5983_REG_YOUT1: + case MMC5983_REG_ZOUT0: + case MMC5983_REG_ZOUT1: + case MMC5983_REG_XYZOUT2: + case MMC5983_REG_STATUS: + case MMC5983_REG_CTRL0: + case MMC5983_REG_CTRL1: + case MMC5983_REG_CTRL2: + case MMC5983_REG_CTRL3: + case MMC5983_REG_ID: + return true; + default: + return false; + } +} + +static bool mmc5983_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MMC5983_REG_XOUT0: + case MMC5983_REG_XOUT1: + case MMC5983_REG_YOUT0: + case MMC5983_REG_YOUT1: + case MMC5983_REG_ZOUT0: + case MMC5983_REG_ZOUT1: + case MMC5983_REG_XYZOUT2: + case MMC5983_REG_STATUS: + case MMC5983_REG_CTRL0: + case MMC5983_REG_CTRL1: + return true; + default: + return false; + } +} + +static const struct regmap_config mmc5983_regmap_config = { + .name = "mmc5983_regmap", + .reg_bits = 8, + .val_bits = 8, + .max_register = MMC5983_REG_ID, + .writeable_reg = mmc5983_is_writeable_reg, + .readable_reg = mmc5983_is_readable_reg, + .volatile_reg = mmc5983_is_volatile_reg, +}; + +static int mmc5983_init(struct mmc5983_data *data) +{ + struct regmap *regmap = data->regmap; + struct device *dev = regmap_get_device(regmap); + unsigned int reg_id, status; + int ret; + + ret = regmap_read(regmap, MMC5983_REG_ID, ®_id); + if (ret) + return dev_err_probe(dev, ret, "Error reading product id\n"); + + if (reg_id != MMC5983_PRODUCT_ID) + dev_info(dev, "unexpected product id 0x%02x\n", reg_id); + + ret = regmap_write(regmap, MMC5983_REG_CTRL1, MMC5983_CTRL1_SW_RST_BIT); + if (ret) + return ret; + + /* Datasheet page 15: power-on time after SW_RST is 10 ms */ + fsleep(10 * USEC_PER_MSEC); + + ret = regmap_write(regmap, MMC5983_REG_CTRL0, MMC5983_CTRL0_OTP_RD_BIT); + if (ret) + return ret; + + /* + * Datasheet page 15: OTP read completes and self-clears. No separate + * OTP refresh timeout is specified, so use the 10 ms power-on time as + * a conservative upper bound. + */ + ret = regmap_read_poll_timeout(regmap, MMC5983_REG_STATUS, status, + status & MMC5983_STATUS_OTP_RD_DONE_BIT, + USEC_PER_MSEC, + 10 * USEC_PER_MSEC); + if (ret) + return ret; + + /* SET: magnetize sensor elements in forward direction */ + ret = mmc5983_pulse_coil(data, MMC5983_CTRL0_SET_BIT); + if (ret) + return ret; + + /* RESET: magnetize sensor elements in reverse direction */ + return mmc5983_pulse_coil(data, MMC5983_CTRL0_RESET_BIT); +} + +static int mmc5983_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct mmc5983_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + + ret = devm_mutex_init(dev, &data->mutex); + if (ret) + return ret; + + data->regmap = devm_regmap_init_i2c(i2c, &mmc5983_regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "failed to allocate register map\n"); + + indio_dev->info = &mmc5983_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = "mmc5983"; + indio_dev->channels = mmc5983_channels; + indio_dev->num_channels = ARRAY_SIZE(mmc5983_channels); + + ret = mmc5983_init(data); + if (ret) + return dev_err_probe(dev, ret, "mmc5983 chip init failed\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id mmc5983_of_match[] = { + { .compatible = "memsic,mmc5983" }, + { } +}; +MODULE_DEVICE_TABLE(of, mmc5983_of_match); + +static const struct i2c_device_id mmc5983_id[] = { + { .name = "mmc5983" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mmc5983_id); + +static struct i2c_driver mmc5983_driver = { + .driver = { + .name = "mmc5983", + .of_match_table = mmc5983_of_match, + }, + .probe = mmc5983_probe, + .id_table = mmc5983_id, +}; +module_i2c_driver(mmc5983_driver); + +MODULE_AUTHOR("Vladislav Kulikov "); +MODULE_DESCRIPTION("MEMSIC MMC5983 magnetic sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/magnetometer/qmc5883l.c b/drivers/iio/magnetometer/qmc5883l.c new file mode 100644 index 000000000..c3469715d --- /dev/null +++ b/drivers/iio/magnetometer/qmc5883l.c @@ -0,0 +1,516 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +/* + * Support for QST QMC5883L 3-Axis Magnetic Sensor on I2C bus. + * + * Copyright (C) 2026 Siratul Islam + * + * Datasheet available at + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#define QMC5883L_REG_X_LSB 0x00 +#define QMC5883L_REG_STATUS1 0x06 +#define QMC5883L_REG_CTRL1 0x09 +#define QMC5883L_REG_CTRL2 0x0A +#define QMC5883L_REG_SET_RESET 0x0B +#define QMC5883L_REG_ID 0x0D + +#define QMC5883L_CHIP_ID 0xFF + +#define QMC5883L_MODE_MASK GENMASK(1, 0) +#define QMC5883L_ODR_MASK GENMASK(3, 2) +#define QMC5883L_RNG_MASK GENMASK(5, 4) +#define QMC5883L_OSR_MASK GENMASK(7, 6) + +#define QMC5883L_MODE_STANDBY 0x00 +#define QMC5883L_MODE_CONT 0x01 + +#define QMC5883L_ODR_10HZ 0x00 +#define QMC5883L_ODR_50HZ 0x01 +#define QMC5883L_ODR_100HZ 0x02 +#define QMC5883L_ODR_200HZ 0x03 + +#define QMC5883L_RNG_2G 0x00 +#define QMC5883L_RNG_8G 0x01 + +#define QMC5883L_OSR_512 0x00 +#define QMC5883L_OSR_256 0x01 +#define QMC5883L_OSR_128 0x02 +#define QMC5883L_OSR_64 0x03 + +#define QMC5883L_STATUS_DRDY BIT(0) +#define QMC5883L_STATUS_OVL BIT(1) + +#define QMC5883L_SET_RESET_VAL BIT(0) +#define QMC5883L_INT_DISABLE BIT(0) +#define QMC5883L_SOFT_RESET BIT(7) + +#define QMC5883L_PORT_US 350 + +struct qmc5883l_data { + struct regmap *regmap; + /* + * Protect data->range/odr/osr. + * Protect poll and read during measurement. + */ + struct mutex mutex; + u8 range; + u8 odr; + u8 osr; +}; + +enum qmc5883l_chan { + QMC5883L_AXIS_X, + QMC5883L_AXIS_Y, + QMC5883L_AXIS_Z, +}; + +static const int qmc5883l_odr_avail[] = { 10, 50, 100, 200 }; + +static const int qmc5883l_osr_avail[] = { 512, 256, 128, 64 }; + +static const int qmc5883l_scales[][2] = { + [QMC5883L_RNG_2G] = { 0, 83333 }, + [QMC5883L_RNG_8G] = { 0, 333333 }, +}; + +static int qmc5883l_take_measurement(struct iio_dev *indio_dev, int index, + int *val) +{ + struct qmc5883l_data *data = iio_priv(indio_dev); + struct regmap *map = data->regmap; + unsigned int status; + __le16 buf[3]; + int ret; + + guard(mutex) (&data->mutex); + + /* 50ms headroom over the slowest ODR (10Hz) */ + ret = regmap_read_poll_timeout(map, QMC5883L_REG_STATUS1, + status, (status & QMC5883L_STATUS_DRDY), + 2 * USEC_PER_MSEC, 150 * USEC_PER_MSEC); + if (ret) + return ret; + + ret = regmap_bulk_read(map, QMC5883L_REG_X_LSB, buf, sizeof(buf)); + if (ret) + return ret; + + if (status & QMC5883L_STATUS_OVL) + return -ERANGE; + + *val = (s16)le16_to_cpu(buf[index]); + + return 0; +} + +static int qmc5883l_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct qmc5883l_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = qmc5883l_take_measurement(indio_dev, chan->address, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: { + guard(mutex)(&data->mutex); + + *val = qmc5883l_scales[data->range][0]; + *val2 = qmc5883l_scales[data->range][1]; + + return IIO_VAL_INT_PLUS_NANO; + } + case IIO_CHAN_INFO_SAMP_FREQ: { + guard(mutex)(&data->mutex); + + switch (data->odr) { + case QMC5883L_ODR_200HZ: + *val = 200; + break; + case QMC5883L_ODR_100HZ: + *val = 100; + break; + case QMC5883L_ODR_50HZ: + *val = 50; + break; + case QMC5883L_ODR_10HZ: + *val = 10; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: { + guard(mutex)(&data->mutex); + + switch (data->osr) { + case QMC5883L_OSR_64: + *val = 64; + break; + case QMC5883L_OSR_128: + *val = 128; + break; + case QMC5883L_OSR_256: + *val = 256; + break; + case QMC5883L_OSR_512: + *val = 512; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; + } + default: + return -EINVAL; + } +} + +static int qmc5883l_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int val, int val2, long mask) +{ + struct qmc5883l_data *data = iio_priv(indio_dev); + u8 rng, osr, odr; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: { + if (val != 0) + return -EINVAL; + + if (val2 == qmc5883l_scales[QMC5883L_RNG_2G][1]) + rng = QMC5883L_RNG_2G; + else if (val2 == qmc5883l_scales[QMC5883L_RNG_8G][1]) + rng = QMC5883L_RNG_8G; + else + return -EINVAL; + + guard(mutex)(&data->mutex); + + ret = regmap_update_bits(data->regmap, QMC5883L_REG_CTRL1, + QMC5883L_RNG_MASK, + FIELD_PREP(QMC5883L_RNG_MASK, rng)); + if (ret) + return ret; + + data->range = rng; + + return 0; + } + case IIO_CHAN_INFO_SAMP_FREQ: { + switch (val) { + case 200: + odr = QMC5883L_ODR_200HZ; + break; + case 100: + odr = QMC5883L_ODR_100HZ; + break; + case 50: + odr = QMC5883L_ODR_50HZ; + break; + case 10: + odr = QMC5883L_ODR_10HZ; + break; + default: + return -EINVAL; + } + + guard(mutex)(&data->mutex); + + ret = regmap_update_bits(data->regmap, QMC5883L_REG_CTRL1, + QMC5883L_ODR_MASK, + FIELD_PREP(QMC5883L_ODR_MASK, odr)); + if (ret) + return ret; + + data->odr = odr; + + return 0; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: { + switch (val) { + case 64: + osr = QMC5883L_OSR_64; + break; + case 128: + osr = QMC5883L_OSR_128; + break; + case 256: + osr = QMC5883L_OSR_256; + break; + case 512: + osr = QMC5883L_OSR_512; + break; + default: + return -EINVAL; + } + + guard(mutex)(&data->mutex); + + ret = regmap_update_bits(data->regmap, QMC5883L_REG_CTRL1, + QMC5883L_OSR_MASK, + FIELD_PREP(QMC5883L_OSR_MASK, osr)); + if (ret) + return ret; + + data->osr = osr; + + return 0; + } + default: + return -EINVAL; + } +} + +static int qmc5883l_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = qmc5883l_odr_avail; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(qmc5883l_odr_avail); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = qmc5883l_osr_avail; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(qmc5883l_osr_avail); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SCALE: + *vals = (const int *)qmc5883l_scales; + *type = IIO_VAL_INT_PLUS_NANO; + *length = ARRAY_SIZE(qmc5883l_scales) * 2; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int qmc5883l_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT; + } +} + +static const struct iio_info qmc5883l_info = { + .read_raw = qmc5883l_read_raw, + .write_raw = qmc5883l_write_raw, + .read_avail = qmc5883l_read_avail, + .write_raw_get_fmt = qmc5883l_write_raw_get_fmt, +}; + +static int qmc5883l_init(struct qmc5883l_data *data) +{ + struct regmap *map = data->regmap; + unsigned int reg; + int ret; + + ret = regmap_read(map, QMC5883L_REG_ID, ®); + if (ret) + return ret; + + /* Not failing because rev 1.0 had this register reserved */ + if (reg != QMC5883L_CHIP_ID) + dev_warn(regmap_get_device(map), + "Unknown chip id: 0x%02x, continuing\n", reg); + + ret = regmap_write(map, QMC5883L_REG_CTRL2, QMC5883L_SOFT_RESET); + if (ret) + return ret; + + /* Use POR completion time as a conservative bound */ + fsleep(QMC5883L_PORT_US); + + /* DRDY pin not used in this version of the driver */ + ret = regmap_write(map, QMC5883L_REG_CTRL2, QMC5883L_INT_DISABLE); + if (ret) + return ret; + + ret = regmap_write(map, QMC5883L_REG_SET_RESET, QMC5883L_SET_RESET_VAL); + if (ret) + return ret; + + data->odr = QMC5883L_ODR_50HZ; + data->range = QMC5883L_RNG_2G; + data->osr = QMC5883L_OSR_64; + + return regmap_write(map, QMC5883L_REG_CTRL1, + FIELD_PREP(QMC5883L_MODE_MASK, QMC5883L_MODE_CONT) | + FIELD_PREP(QMC5883L_ODR_MASK, data->odr) | + FIELD_PREP(QMC5883L_RNG_MASK, data->range) | + FIELD_PREP(QMC5883L_OSR_MASK, data->osr)); +} + +static void qmc5883l_power_down_action(void *priv) +{ + struct qmc5883l_data *data = priv; + + regmap_update_bits(data->regmap, QMC5883L_REG_CTRL1, + QMC5883L_MODE_MASK, + FIELD_PREP(QMC5883L_MODE_MASK, QMC5883L_MODE_STANDBY)); +} + +static bool qmc5883l_volatile_reg(struct device *dev, unsigned int reg) +{ + return reg <= QMC5883L_REG_STATUS1; +} + +static bool qmc5883l_writable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case QMC5883L_REG_CTRL1: + case QMC5883L_REG_CTRL2: + case QMC5883L_REG_SET_RESET: + return true; + default: + return false; + } +} + +static const struct regmap_config qmc5883l_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = QMC5883L_REG_ID, + .cache_type = REGCACHE_MAPLE, + .volatile_reg = qmc5883l_volatile_reg, + .writeable_reg = qmc5883l_writable_reg, +}; + +#define QMC5883L_CHANNEL(_axis) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .address = QMC5883L_AXIS_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + } + +static const struct iio_chan_spec qmc5883l_channels[] = { + QMC5883L_CHANNEL(X), + QMC5883L_CHANNEL(Y), + QMC5883L_CHANNEL(Z), +}; + +static int qmc5883l_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct qmc5883l_data *data; + struct iio_dev *indio_dev; + struct regmap *map; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + map = devm_regmap_init_i2c(client, &qmc5883l_regmap_config); + if (IS_ERR(map)) + return dev_err_probe(dev, PTR_ERR(map), + "regmap initialization failed\n"); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable VDD regulator\n"); + + ret = devm_regulator_get_enable(dev, "vddio"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable VDDIO regulator\n"); + + /* POR completion time max per datasheet Table 7 */ + fsleep(QMC5883L_PORT_US); + + data = iio_priv(indio_dev); + data->regmap = map; + + ret = devm_mutex_init(dev, &data->mutex); + if (ret) + return ret; + + indio_dev->name = "qmc5883l"; + indio_dev->info = &qmc5883l_info; + indio_dev->channels = qmc5883l_channels; + indio_dev->num_channels = ARRAY_SIZE(qmc5883l_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = qmc5883l_init(data); + if (ret) + return dev_err_probe(dev, ret, "qmc5883l init failed\n"); + + ret = devm_add_action_or_reset(dev, qmc5883l_power_down_action, data); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id qmc5883l_match[] = { + { .compatible = "qstcorp,qmc5883l" }, + { } +}; +MODULE_DEVICE_TABLE(of, qmc5883l_match); + +static const struct i2c_device_id qmc5883l_id[] = { + { .name = "qmc5883l" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, qmc5883l_id); + +static struct i2c_driver qmc5883l_driver = { + .driver = { + .name = "qmc5883l", + .of_match_table = qmc5883l_match, + }, + .id_table = qmc5883l_id, + .probe = qmc5883l_probe, +}; +module_i2c_driver(qmc5883l_driver); + +MODULE_DESCRIPTION("QST QMC5883L 3-Axis Magnetic Sensor driver"); +MODULE_AUTHOR("Siratul Islam "); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/iio/magnetometer/qmc6308.c b/drivers/iio/magnetometer/qmc6308.c new file mode 100644 index 000000000..72348cd9e --- /dev/null +++ b/drivers/iio/magnetometer/qmc6308.c @@ -0,0 +1,601 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Support for QST QMC6308 3-Axis Magnetic Sensor on I2C bus. + * + * Copyright (C) 2026 Jorijn van der Graaf + * + * Datasheet available at + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#define QMC6308_REG_ID 0x00 +#define QMC6308_REG_X_LSB 0x01 +#define QMC6308_REG_STATUS 0x09 +#define QMC6308_REG_CTRL1 0x0A +#define QMC6308_REG_CTRL2 0x0B +#define QMC6308_REG_CTRL3 0x0D +#define QMC6308_REG_CTRL4 0x29 + +#define QMC6308_CHIP_ID 0x80 + +/* Control register 1 */ +#define QMC6308_MODE_MASK GENMASK(1, 0) +#define QMC6308_ODR_MASK GENMASK(3, 2) +#define QMC6308_OSR1_MASK GENMASK(5, 4) +#define QMC6308_OSR2_MASK GENMASK(7, 6) + +#define QMC6308_MODE_SUSPEND 0x00 +#define QMC6308_MODE_NORMAL 0x01 + +#define QMC6308_ODR_10HZ 0x00 +#define QMC6308_ODR_50HZ 0x01 +#define QMC6308_ODR_100HZ 0x02 +#define QMC6308_ODR_200HZ 0x03 + +#define QMC6308_OSR1_8 0x00 +#define QMC6308_OSR1_4 0x01 +#define QMC6308_OSR1_2 0x02 +#define QMC6308_OSR1_1 0x03 + +/* Control register 2 */ +#define QMC6308_SET_RESET_MASK GENMASK(1, 0) +#define QMC6308_RNG_MASK GENMASK(3, 2) +#define QMC6308_SELF_TEST BIT(6) +#define QMC6308_SOFT_RST BIT(7) + +#define QMC6308_SET_RESET_ON 0x00 + +#define QMC6308_RNG_30G 0x00 +#define QMC6308_RNG_12G 0x01 +#define QMC6308_RNG_8G 0x02 +#define QMC6308_RNG_2G 0x03 + +/* Status register */ +#define QMC6308_STATUS_DRDY BIT(0) +#define QMC6308_STATUS_OVFL BIT(1) + +/* Power-on completion time (datasheet Table 7) */ +#define QMC6308_POR_US 250 + +#define QMC6308_AUTOSUSPEND_DELAY_MS 500 + +struct qmc6308_data { + struct regmap *regmap; + /* Protect data->range/odr/osr and serialize measurements */ + struct mutex mutex; + struct iio_mount_matrix orientation; + u8 range; + u8 odr; + u8 osr; +}; + +enum qmc6308_axis { + QMC6308_AXIS_X, + QMC6308_AXIS_Y, + QMC6308_AXIS_Z, +}; + +static const int qmc6308_odr_avail[] = { + [QMC6308_ODR_10HZ] = 10, + [QMC6308_ODR_50HZ] = 50, + [QMC6308_ODR_100HZ] = 100, + [QMC6308_ODR_200HZ] = 200, +}; + +static const int qmc6308_osr1_avail[] = { + [QMC6308_OSR1_8] = 8, + [QMC6308_OSR1_4] = 4, + [QMC6308_OSR1_2] = 2, + [QMC6308_OSR1_1] = 1, +}; + +/* + * Sensitivity is 1000/2500/3750/15000 LSB/Gauss for the + * +-30/12/8/2 Gauss ranges respectively. + */ +static const int qmc6308_scales[][2] = { + [QMC6308_RNG_30G] = { 0, 1000000 }, + [QMC6308_RNG_12G] = { 0, 400000 }, + [QMC6308_RNG_8G] = { 0, 266667 }, + [QMC6308_RNG_2G] = { 0, 66667 }, +}; + +static int qmc6308_set_mode(struct qmc6308_data *data, unsigned int mode) +{ + return regmap_update_bits(data->regmap, QMC6308_REG_CTRL1, + QMC6308_MODE_MASK, + FIELD_PREP(QMC6308_MODE_MASK, mode)); +} + +static int qmc6308_take_measurement(struct iio_dev *indio_dev, int index, + int *val) +{ + struct qmc6308_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); + unsigned int status; + __le16 buf[3]; + int ret; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) { + dev_err(dev, "Failed to power on (%d)\n", ret); + return ret; + } + + guard(mutex)(&data->mutex); + + /* + * Reading the status register clears DRDY, which is why the poll + * and the data read stay under one mutex hold. A runtime resume + * clears DRDY too, so a sample converted before the last suspend + * is never returned here. + * + * The timeout is 50ms of headroom over the slowest ODR (10Hz). + */ + ret = regmap_read_poll_timeout(data->regmap, QMC6308_REG_STATUS, + status, (status & QMC6308_STATUS_DRDY), + 2 * USEC_PER_MSEC, + 150 * USEC_PER_MSEC); + if (ret) + return ret; + + ret = regmap_bulk_read(data->regmap, QMC6308_REG_X_LSB, buf, + sizeof(buf)); + if (ret) + return ret; + + if (status & QMC6308_STATUS_OVFL) + return -ERANGE; + + *val = (s16)le16_to_cpu(buf[index]); + + return 0; +} + +static int qmc6308_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct qmc6308_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = qmc6308_take_measurement(indio_dev, chan->address, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: { + guard(mutex)(&data->mutex); + + *val = qmc6308_scales[data->range][0]; + *val2 = qmc6308_scales[data->range][1]; + + return IIO_VAL_INT_PLUS_NANO; + } + case IIO_CHAN_INFO_SAMP_FREQ: { + guard(mutex)(&data->mutex); + + *val = qmc6308_odr_avail[data->odr]; + + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: { + guard(mutex)(&data->mutex); + + *val = qmc6308_osr1_avail[data->osr]; + + return IIO_VAL_INT; + } + default: + return -EINVAL; + } +} + +static int qmc6308_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int val, int val2, long mask) +{ + struct qmc6308_data *data = iio_priv(indio_dev); + unsigned int status; + unsigned int i; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: { + if (val != 0) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(qmc6308_scales); i++) { + if (val2 == qmc6308_scales[i][1]) + break; + } + if (i == ARRAY_SIZE(qmc6308_scales)) + return -EINVAL; + + guard(mutex)(&data->mutex); + + ret = regmap_update_bits(data->regmap, QMC6308_REG_CTRL2, + QMC6308_RNG_MASK, + FIELD_PREP(QMC6308_RNG_MASK, i)); + if (ret) + return ret; + + data->range = i; + + /* + * The data registers still hold (and DRDY still + * advertises) a sample converted at the previous range; + * discard it so that a read does not pair old-range data + * with the new scale. A conversion already in flight may + * still complete at the old range, so this narrows the + * window rather than closing it. The range change itself + * took effect, so only log a failure here: an error + * would mislead userspace about an effective write. + */ + ret = regmap_read(data->regmap, QMC6308_REG_STATUS, + &status); + if (ret) + dev_warn(regmap_get_device(data->regmap), + "Failed to discard stale sample (%d)\n", ret); + + return 0; + } + case IIO_CHAN_INFO_SAMP_FREQ: { + for (i = 0; i < ARRAY_SIZE(qmc6308_odr_avail); i++) { + if (val == qmc6308_odr_avail[i]) + break; + } + if (i == ARRAY_SIZE(qmc6308_odr_avail)) + return -EINVAL; + + guard(mutex)(&data->mutex); + + ret = regmap_update_bits(data->regmap, QMC6308_REG_CTRL1, + QMC6308_ODR_MASK, + FIELD_PREP(QMC6308_ODR_MASK, i)); + if (ret) + return ret; + + data->odr = i; + + return 0; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: { + for (i = 0; i < ARRAY_SIZE(qmc6308_osr1_avail); i++) { + if (val == qmc6308_osr1_avail[i]) + break; + } + if (i == ARRAY_SIZE(qmc6308_osr1_avail)) + return -EINVAL; + + guard(mutex)(&data->mutex); + + ret = regmap_update_bits(data->regmap, QMC6308_REG_CTRL1, + QMC6308_OSR1_MASK, + FIELD_PREP(QMC6308_OSR1_MASK, i)); + if (ret) + return ret; + + data->osr = i; + + return 0; + } + default: + return -EINVAL; + } +} + +static int qmc6308_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = qmc6308_odr_avail; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(qmc6308_odr_avail); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = qmc6308_osr1_avail; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(qmc6308_osr1_avail); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SCALE: + *vals = (const int *)qmc6308_scales; + *type = IIO_VAL_INT_PLUS_NANO; + *length = ARRAY_SIZE(qmc6308_scales) * 2; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int qmc6308_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT; + } +} + +static const struct iio_mount_matrix * +qmc6308_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct qmc6308_data *data = iio_priv(indio_dev); + + return &data->orientation; +} + +static const struct iio_chan_spec_ext_info qmc6308_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, qmc6308_get_mount_matrix), + { } +}; + +static const struct iio_info qmc6308_info = { + .read_raw = qmc6308_read_raw, + .write_raw = qmc6308_write_raw, + .read_avail = qmc6308_read_avail, + .write_raw_get_fmt = qmc6308_write_raw_get_fmt, +}; + +static int qmc6308_init(struct qmc6308_data *data) +{ + struct regmap *map = data->regmap; + unsigned int reg; + int ret; + + ret = regmap_read(map, QMC6308_REG_ID, ®); + if (ret) + return ret; + + /* Allow unknown IDs so that fallback compatibles work */ + if (reg != QMC6308_CHIP_ID) + dev_warn(regmap_get_device(map), + "Unknown chip id: 0x%02x, continuing\n", reg); + + /* The SOFT_RST bit is not auto-cleared and must be written back 0 */ + ret = regmap_write(map, QMC6308_REG_CTRL2, QMC6308_SOFT_RST); + if (ret) + return ret; + + /* + * The datasheet gives no soft-reset completion figure; reuse the + * power-on time as a conservative bound. + */ + fsleep(QMC6308_POR_US); + + data->range = QMC6308_RNG_30G; + data->odr = QMC6308_ODR_50HZ; + data->osr = QMC6308_OSR1_8; + + ret = regmap_write(map, QMC6308_REG_CTRL2, + FIELD_PREP(QMC6308_SET_RESET_MASK, + QMC6308_SET_RESET_ON) | + FIELD_PREP(QMC6308_RNG_MASK, data->range)); + if (ret) + return ret; + + /* OSR2 (second-stage filter) set to its power-on default of 0 */ + return regmap_write(map, QMC6308_REG_CTRL1, + FIELD_PREP(QMC6308_MODE_MASK, + QMC6308_MODE_NORMAL) | + FIELD_PREP(QMC6308_ODR_MASK, data->odr) | + FIELD_PREP(QMC6308_OSR1_MASK, data->osr) | + FIELD_PREP(QMC6308_OSR2_MASK, 0)); +} + +static void qmc6308_power_down_action(void *priv) +{ + struct qmc6308_data *data = priv; + + if (!pm_runtime_status_suspended(regmap_get_device(data->regmap))) + qmc6308_set_mode(data, QMC6308_MODE_SUSPEND); +} + +static bool qmc6308_volatile_reg(struct device *dev, unsigned int reg) +{ + return reg >= QMC6308_REG_X_LSB && reg <= QMC6308_REG_STATUS; +} + +static bool qmc6308_writable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case QMC6308_REG_CTRL1: + case QMC6308_REG_CTRL2: + case QMC6308_REG_CTRL3: + case QMC6308_REG_CTRL4: + return true; + default: + return false; + } +} + +static const struct regmap_config qmc6308_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = QMC6308_REG_CTRL4, + .cache_type = REGCACHE_MAPLE, + .volatile_reg = qmc6308_volatile_reg, + .writeable_reg = qmc6308_writable_reg, +}; + +#define QMC6308_CHANNEL(_axis) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .address = QMC6308_AXIS_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .ext_info = qmc6308_ext_info, \ + } + +static const struct iio_chan_spec qmc6308_channels[] = { + QMC6308_CHANNEL(X), + QMC6308_CHANNEL(Y), + QMC6308_CHANNEL(Z), +}; + +static int qmc6308_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct qmc6308_data *data; + struct iio_dev *indio_dev; + struct regmap *map; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + i2c_set_clientdata(client, indio_dev); + + map = devm_regmap_init_i2c(client, &qmc6308_regmap_config); + if (IS_ERR(map)) + return dev_err_probe(dev, PTR_ERR(map), + "regmap initialization failed\n"); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable VDD regulator\n"); + + fsleep(QMC6308_POR_US); + + data = iio_priv(indio_dev); + data->regmap = map; + + ret = devm_mutex_init(dev, &data->mutex); + if (ret) + return ret; + + ret = iio_read_mount_matrix(dev, &data->orientation); + if (ret) + return dev_err_probe(dev, ret, + "Failed to read mount matrix\n"); + + indio_dev->name = "qmc6308"; + indio_dev->info = &qmc6308_info; + indio_dev->channels = qmc6308_channels; + indio_dev->num_channels = ARRAY_SIZE(qmc6308_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = qmc6308_init(data); + if (ret) + return dev_err_probe(dev, ret, "qmc6308 init failed\n"); + + ret = pm_runtime_set_active(dev); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, qmc6308_power_down_action, data); + if (ret) + return ret; + + pm_runtime_use_autosuspend(dev); + pm_runtime_set_autosuspend_delay(dev, QMC6308_AUTOSUSPEND_DELAY_MS); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static int qmc6308_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct qmc6308_data *data = iio_priv(indio_dev); + + return qmc6308_set_mode(data, QMC6308_MODE_SUSPEND); +} + +static int qmc6308_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct qmc6308_data *data = iio_priv(indio_dev); + unsigned int status; + int ret; + + ret = qmc6308_set_mode(data, QMC6308_MODE_NORMAL); + if (ret) + return ret; + + /* + * DRDY may still be set for a sample converted before the last + * suspend; reading the status register clears it so the next + * measurement waits for fresh data. + */ + ret = regmap_read(data->regmap, QMC6308_REG_STATUS, &status); + if (ret) { + /* Best effort to leave the chip in a consistent state */ + qmc6308_set_mode(data, QMC6308_MODE_SUSPEND); + } + + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(qmc6308_pm_ops, qmc6308_runtime_suspend, + qmc6308_runtime_resume, NULL); + +static const struct of_device_id qmc6308_match[] = { + { .compatible = "qstcorp,qmc6308" }, + { } +}; +MODULE_DEVICE_TABLE(of, qmc6308_match); + +static const struct i2c_device_id qmc6308_id[] = { + { .name = "qmc6308" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, qmc6308_id); + +static struct i2c_driver qmc6308_driver = { + .driver = { + .name = "qmc6308", + .of_match_table = qmc6308_match, + .pm = pm_ptr(&qmc6308_pm_ops), + }, + .id_table = qmc6308_id, + .probe = qmc6308_probe, +}; +module_i2c_driver(qmc6308_driver); + +MODULE_DESCRIPTION("QST QMC6308 3-Axis Magnetic Sensor driver"); +MODULE_AUTHOR("Jorijn van der Graaf "); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/magnetometer/rm3100-core.c b/drivers/iio/magnetometer/rm3100-core.c new file mode 100644 index 000000000..46fce2978 --- /dev/null +++ b/drivers/iio/magnetometer/rm3100-core.c @@ -0,0 +1,616 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PNI RM3100 3-axis geomagnetic sensor driver core. + * + * Copyright (C) 2018 Song Qiang + * + * User Manual available at + * + * + * TODO: event generation, pm. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "rm3100.h" + +/* Cycle Count Registers. */ +#define RM3100_REG_CC_X 0x05 +#define RM3100_REG_CC_Y 0x07 +#define RM3100_REG_CC_Z 0x09 + +/* Poll Measurement Mode register. */ +#define RM3100_REG_POLL 0x00 +#define RM3100_POLL_X BIT(4) +#define RM3100_POLL_Y BIT(5) +#define RM3100_POLL_Z BIT(6) + +/* Continuous Measurement Mode register. */ +#define RM3100_REG_CMM 0x01 +#define RM3100_CMM_START BIT(0) +#define RM3100_CMM_X BIT(4) +#define RM3100_CMM_Y BIT(5) +#define RM3100_CMM_Z BIT(6) + +/* TiMe Rate Configuration register. */ +#define RM3100_REG_TMRC 0x0B +#define RM3100_TMRC_OFFSET 0x92 + +/* Result Status register. */ +#define RM3100_REG_STATUS 0x34 +#define RM3100_STATUS_DRDY BIT(7) + +/* Measurement result registers. */ +#define RM3100_REG_MX2 0x24 +#define RM3100_REG_MY2 0x27 +#define RM3100_REG_MZ2 0x2a + +#define RM3100_W_REG_START RM3100_REG_POLL +#define RM3100_W_REG_END RM3100_REG_TMRC +#define RM3100_R_REG_START RM3100_REG_POLL +#define RM3100_R_REG_END RM3100_REG_STATUS +#define RM3100_V_REG_START RM3100_REG_POLL +#define RM3100_V_REG_END RM3100_REG_STATUS + +/* + * This is computed by hand, is the sum of channel storage bits and padding + * bits, which is 4+4+4+12=24 in here. + */ +#define RM3100_SCAN_BYTES 24 + +#define RM3100_CMM_AXIS_SHIFT 4 + +struct rm3100_data { + struct regmap *regmap; + struct completion measuring_done; + bool use_interrupt; + int conversion_time; + int scale; + /* Ensure naturally aligned timestamp */ + u8 buffer[RM3100_SCAN_BYTES] __aligned(8); + struct iio_trigger *drdy_trig; + + /* + * This lock is for protecting the consistency of series of i2c + * operations, that is, to make sure a measurement process will + * not be interrupted by a set frequency operation, which should + * be taken where a series of i2c operation starts, released where + * the operation ends. + */ + struct mutex lock; +}; + +static const struct regmap_range rm3100_readable_ranges[] = { + regmap_reg_range(RM3100_R_REG_START, RM3100_R_REG_END), +}; + +const struct regmap_access_table rm3100_readable_table = { + .yes_ranges = rm3100_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(rm3100_readable_ranges), +}; +EXPORT_SYMBOL_NS_GPL(rm3100_readable_table, "IIO_RM3100"); + +static const struct regmap_range rm3100_writable_ranges[] = { + regmap_reg_range(RM3100_W_REG_START, RM3100_W_REG_END), +}; + +const struct regmap_access_table rm3100_writable_table = { + .yes_ranges = rm3100_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(rm3100_writable_ranges), +}; +EXPORT_SYMBOL_NS_GPL(rm3100_writable_table, "IIO_RM3100"); + +static const struct regmap_range rm3100_volatile_ranges[] = { + regmap_reg_range(RM3100_V_REG_START, RM3100_V_REG_END), +}; + +const struct regmap_access_table rm3100_volatile_table = { + .yes_ranges = rm3100_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(rm3100_volatile_ranges), +}; +EXPORT_SYMBOL_NS_GPL(rm3100_volatile_table, "IIO_RM3100"); + +static irqreturn_t rm3100_thread_fn(int irq, void *d) +{ + struct iio_dev *indio_dev = d; + struct rm3100_data *data = iio_priv(indio_dev); + + /* + * Write operation to any register or read operation + * to first byte of results will clear the interrupt. + */ + regmap_write(data->regmap, RM3100_REG_POLL, 0); + + return IRQ_HANDLED; +} + +static irqreturn_t rm3100_irq_handler(int irq, void *d) +{ + struct iio_dev *indio_dev = d; + struct rm3100_data *data = iio_priv(indio_dev); + + if (!iio_buffer_enabled(indio_dev)) + complete(&data->measuring_done); + else + iio_trigger_poll(data->drdy_trig); + + return IRQ_WAKE_THREAD; +} + +static int rm3100_wait_measurement(struct rm3100_data *data) +{ + struct regmap *regmap = data->regmap; + unsigned int val; + int tries = 20; + int ret; + + /* + * A read cycle of 400kbits i2c bus is about 20us, plus the time + * used for scheduling, a read cycle of fast mode of this device + * can reach 1.7ms, it may be possible for data to arrive just + * after we check the RM3100_REG_STATUS. In this case, irq_handler is + * called before measuring_done is reinitialized, it will wait + * forever for data that has already been ready. + * Reinitialize measuring_done before looking up makes sure we + * will always capture interrupt no matter when it happens. + */ + if (data->use_interrupt) + reinit_completion(&data->measuring_done); + + ret = regmap_read(regmap, RM3100_REG_STATUS, &val); + if (ret < 0) + return ret; + + if ((val & RM3100_STATUS_DRDY) != RM3100_STATUS_DRDY) { + if (data->use_interrupt) { + ret = wait_for_completion_timeout(&data->measuring_done, + msecs_to_jiffies(data->conversion_time)); + if (!ret) + return -ETIMEDOUT; + } else { + do { + usleep_range(1000, 5000); + + ret = regmap_read(regmap, RM3100_REG_STATUS, + &val); + if (ret < 0) + return ret; + + if (val & RM3100_STATUS_DRDY) + break; + } while (--tries); + if (!tries) + return -ETIMEDOUT; + } + } + return 0; +} + +static int rm3100_read_mag(struct rm3100_data *data, int idx, int *val) +{ + struct regmap *regmap = data->regmap; + u8 buffer[3]; + int ret; + + guard(mutex)(&data->lock); + + ret = regmap_write(regmap, RM3100_REG_POLL, BIT(4 + idx)); + if (ret < 0) + return ret; + + ret = rm3100_wait_measurement(data); + if (ret < 0) + return ret; + + ret = regmap_bulk_read(regmap, RM3100_REG_MX2 + 3 * idx, buffer, 3); + if (ret < 0) + return ret; + + *val = sign_extend32(get_unaligned_be24(&buffer[0]), 23); + + return IIO_VAL_INT; +} + +#define RM3100_CHANNEL(axis, idx) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = idx, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 24, \ + .storagebits = 32, \ + .shift = 8, \ + .endianness = IIO_BE, \ + }, \ + } + +static const struct iio_chan_spec rm3100_channels[] = { + RM3100_CHANNEL(X, 0), + RM3100_CHANNEL(Y, 1), + RM3100_CHANNEL(Z, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL( + "600 300 150 75 37 18 9 4.5 2.3 1.2 0.6 0.3 0.015 0.075" +); + +static struct attribute *rm3100_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group rm3100_attribute_group = { + .attrs = rm3100_attributes, +}; + +#define RM3100_SAMP_NUM 14 + +/* + * Frequency : rm3100_samp_rates[][0].rm3100_samp_rates[][1]Hz. + * Time between reading: rm3100_sam_rates[][2]ms. + * The first one is actually 1.7ms. + */ +static const int rm3100_samp_rates[RM3100_SAMP_NUM][3] = { + {600, 0, 2}, {300, 0, 3}, {150, 0, 7}, {75, 0, 13}, {37, 0, 27}, + {18, 0, 55}, {9, 0, 110}, {4, 500000, 220}, {2, 300000, 440}, + {1, 200000, 800}, {0, 600000, 1600}, {0, 300000, 3300}, + {0, 15000, 6700}, {0, 75000, 13000} +}; + +static int rm3100_get_samp_freq(struct rm3100_data *data, int *val, int *val2) +{ + unsigned int tmp; + int ret; + + guard(mutex)(&data->lock); + + ret = regmap_read(data->regmap, RM3100_REG_TMRC, &tmp); + if (ret < 0) + return ret; + + *val = rm3100_samp_rates[tmp - RM3100_TMRC_OFFSET][0]; + *val2 = rm3100_samp_rates[tmp - RM3100_TMRC_OFFSET][1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int rm3100_set_cycle_count(struct rm3100_data *data, int val) +{ + int ret; + u8 i; + + for (i = 0; i < 3; i++) { + ret = regmap_write(data->regmap, RM3100_REG_CC_X + 2 * i, val); + if (ret < 0) + return ret; + } + + /* + * The scale of this sensor depends on the cycle count value, these + * three values are corresponding to the cycle count value 50, 100, + * 200. scale = output / gain * 10^4. + */ + switch (val) { + case 50: + data->scale = 500; + break; + case 100: + data->scale = 263; + break; + /* + * case 200: + * This function will never be called by users' code, so here we + * assume that it will never get a wrong parameter. + */ + default: + data->scale = 133; + } + + return 0; +} + +static int rm3100_set_samp_freq(struct iio_dev *indio_dev, int val, int val2) +{ + struct rm3100_data *data = iio_priv(indio_dev); + struct regmap *regmap = data->regmap; + unsigned int cycle_count; + int ret; + int i; + + guard(mutex)(&data->lock); + + /* All cycle count registers use the same value. */ + ret = regmap_read(regmap, RM3100_REG_CC_X, &cycle_count); + if (ret < 0) + return ret; + + for (i = 0; i < RM3100_SAMP_NUM; i++) { + if (val == rm3100_samp_rates[i][0] && + val2 == rm3100_samp_rates[i][1]) + break; + } + if (i == RM3100_SAMP_NUM) + return -EINVAL; + + ret = regmap_write(regmap, RM3100_REG_TMRC, i + RM3100_TMRC_OFFSET); + if (ret < 0) + return ret; + + /* Checking if cycle count registers need changing. */ + if (val == 600 && cycle_count == 200) { + ret = rm3100_set_cycle_count(data, 100); + if (ret < 0) + return ret; + } else if (val != 600 && cycle_count == 100) { + ret = rm3100_set_cycle_count(data, 200); + if (ret < 0) + return ret; + } + + if (iio_buffer_enabled(indio_dev)) { + /* Writing TMRC registers requires CMM reset. */ + ret = regmap_write(regmap, RM3100_REG_CMM, 0); + if (ret < 0) + return ret; + ret = regmap_write(data->regmap, RM3100_REG_CMM, + (*indio_dev->active_scan_mask & 0x7) << + RM3100_CMM_AXIS_SHIFT | RM3100_CMM_START); + if (ret < 0) + return ret; + } + + data->conversion_time = rm3100_samp_rates[i][2] * 2; + return 0; +} + +static int rm3100_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct rm3100_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = rm3100_read_mag(data, chan->scan_index, val); + iio_device_release_direct(indio_dev); + + return ret; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = data->scale; + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + return rm3100_get_samp_freq(data, val, val2); + default: + return -EINVAL; + } +} + +static int rm3100_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return rm3100_set_samp_freq(indio_dev, val, val2); + default: + return -EINVAL; + } +} + +static const struct iio_info rm3100_info = { + .attrs = &rm3100_attribute_group, + .read_raw = rm3100_read_raw, + .write_raw = rm3100_write_raw, +}; + +static int rm3100_buffer_preenable(struct iio_dev *indio_dev) +{ + struct rm3100_data *data = iio_priv(indio_dev); + + /* Starting channels enabled. */ + return regmap_write(data->regmap, RM3100_REG_CMM, + (*indio_dev->active_scan_mask & 0x7) << RM3100_CMM_AXIS_SHIFT | + RM3100_CMM_START); +} + +static int rm3100_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct rm3100_data *data = iio_priv(indio_dev); + + return regmap_write(data->regmap, RM3100_REG_CMM, 0); +} + +static const struct iio_buffer_setup_ops rm3100_buffer_ops = { + .preenable = rm3100_buffer_preenable, + .postdisable = rm3100_buffer_postdisable, +}; + +/** + * rm3100_regmap_bulk_read_locked() - Wrapper around regmap_bulk_read() with a mutex + * + * @data: Data structure containing regmap and mutex + * @reg: First register to be read from, passed to regmap_bulk_read() + * @val: Pointer to store read value, in native register size for device, + * passed to regmap_bulk_read() + * @val_count: Number of registers to read, passed to regmap_bulk_read() + * + * Intended for use only in rm3100_trigger_handler(). + * + * Return: + * A value of zero on success, a negative errno in error cases. + */ +static int rm3100_regmap_bulk_read_locked(struct rm3100_data *data, unsigned int reg, + void *val, size_t val_count) +{ + guard(mutex)(&data->lock); + return regmap_bulk_read(data->regmap, reg, val, val_count); +} + +static irqreturn_t rm3100_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + unsigned long scan_mask = *indio_dev->active_scan_mask; + unsigned int mask_len = iio_get_masklength(indio_dev); + struct rm3100_data *data = iio_priv(indio_dev); + int ret, i, bit; + + switch (scan_mask) { + case BIT(0) | BIT(1) | BIT(2): + ret = rm3100_regmap_bulk_read_locked(data, RM3100_REG_MX2, + data->buffer, 9); + if (ret < 0) + goto done; + /* Convert XXXYYYZZZxxx to XXXxYYYxZZZx. x for paddings. */ + for (i = 2; i > 0; i--) + memmove(data->buffer + i * 4, data->buffer + i * 3, 3); + break; + case BIT(0) | BIT(1): + ret = rm3100_regmap_bulk_read_locked(data, RM3100_REG_MX2, + data->buffer, 6); + if (ret < 0) + goto done; + memmove(data->buffer + 4, data->buffer + 3, 3); + break; + case BIT(1) | BIT(2): + ret = rm3100_regmap_bulk_read_locked(data, RM3100_REG_MY2, + data->buffer, 6); + if (ret < 0) + goto done; + memmove(data->buffer + 4, data->buffer + 3, 3); + break; + case BIT(0) | BIT(2): + ret = rm3100_regmap_bulk_read_locked(data, RM3100_REG_MX2, + data->buffer, 9); + if (ret < 0) + goto done; + memmove(data->buffer + 4, data->buffer + 6, 3); + break; + default: + for_each_set_bit(bit, &scan_mask, mask_len) { + ret = rm3100_regmap_bulk_read_locked(data, + RM3100_REG_MX2 + 3 * bit, + data->buffer, 3); + if (ret < 0) + goto done; + } + } + /* + * Always using the same buffer so that we wouldn't need to set the + * paddings to 0 in case of leaking any data. + */ + iio_push_to_buffers_with_ts(indio_dev, data->buffer, sizeof(data->buffer), + pf->timestamp); +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +int rm3100_common_probe(struct device *dev, struct regmap *regmap, int irq) +{ + struct iio_dev *indio_dev; + struct rm3100_data *data; + unsigned int tmp; + int ret; + int samp_rate_index; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->regmap = regmap; + + mutex_init(&data->lock); + + indio_dev->name = "rm3100"; + indio_dev->info = &rm3100_info; + indio_dev->channels = rm3100_channels; + indio_dev->num_channels = ARRAY_SIZE(rm3100_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + if (!irq) + data->use_interrupt = false; + else { + data->use_interrupt = true; + + init_completion(&data->measuring_done); + ret = devm_request_threaded_irq(dev, + irq, + rm3100_irq_handler, + rm3100_thread_fn, + IRQF_TRIGGER_HIGH | + IRQF_ONESHOT, + indio_dev->name, + indio_dev); + if (ret) + return ret; + + data->drdy_trig = devm_iio_trigger_alloc(dev, "%s-drdy%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->drdy_trig) + return -ENOMEM; + + ret = devm_iio_trigger_register(dev, data->drdy_trig); + if (ret < 0) + return ret; + } + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + &iio_pollfunc_store_time, + rm3100_trigger_handler, + &rm3100_buffer_ops); + if (ret < 0) + return ret; + + ret = regmap_read(regmap, RM3100_REG_TMRC, &tmp); + if (ret < 0) + return ret; + + samp_rate_index = tmp - RM3100_TMRC_OFFSET; + if (samp_rate_index < 0 || samp_rate_index >= RM3100_SAMP_NUM) { + dev_err(dev, "The value read from RM3100_REG_TMRC is invalid!\n"); + return -EINVAL; + } + /* Initializing max wait time, which is double conversion time. */ + data->conversion_time = rm3100_samp_rates[samp_rate_index][2] * 2; + + /* Cycle count values may not be what we want. */ + if ((tmp - RM3100_TMRC_OFFSET) == 0) + rm3100_set_cycle_count(data, 100); + else + rm3100_set_cycle_count(data, 200); + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS_GPL(rm3100_common_probe, "IIO_RM3100"); + +MODULE_AUTHOR("Song Qiang "); +MODULE_DESCRIPTION("PNI RM3100 3-axis magnetometer i2c driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/magnetometer/rm3100-i2c.c b/drivers/iio/magnetometer/rm3100-i2c.c new file mode 100644 index 000000000..a09a271b6 --- /dev/null +++ b/drivers/iio/magnetometer/rm3100-i2c.c @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Support for PNI RM3100 3-axis geomagnetic sensor on a i2c bus. + * + * Copyright (C) 2018 Song Qiang + * + * i2c slave address: 0x20 + SA1 << 1 + SA0. + */ + +#include +#include + +#include "rm3100.h" + +static const struct regmap_config rm3100_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .rd_table = &rm3100_readable_table, + .wr_table = &rm3100_writable_table, + .volatile_table = &rm3100_volatile_table, + + .cache_type = REGCACHE_RBTREE, +}; + +static int rm3100_probe(struct i2c_client *client) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, &rm3100_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return rm3100_common_probe(&client->dev, regmap, client->irq); +} + +static const struct of_device_id rm3100_dt_match[] = { + { .compatible = "pni,rm3100", }, + { } +}; +MODULE_DEVICE_TABLE(of, rm3100_dt_match); + +static struct i2c_driver rm3100_driver = { + .driver = { + .name = "rm3100-i2c", + .of_match_table = rm3100_dt_match, + }, + .probe = rm3100_probe, +}; +module_i2c_driver(rm3100_driver); + +MODULE_AUTHOR("Song Qiang "); +MODULE_DESCRIPTION("PNI RM3100 3-axis magnetometer i2c driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_RM3100"); diff --git a/drivers/iio/magnetometer/rm3100-spi.c b/drivers/iio/magnetometer/rm3100-spi.c new file mode 100644 index 000000000..2f60a41c0 --- /dev/null +++ b/drivers/iio/magnetometer/rm3100-spi.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Support for PNI RM3100 3-axis geomagnetic sensor on a spi bus. + * + * Copyright (C) 2018 Song Qiang + */ + +#include +#include + +#include "rm3100.h" + +static const struct regmap_config rm3100_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .rd_table = &rm3100_readable_table, + .wr_table = &rm3100_writable_table, + .volatile_table = &rm3100_volatile_table, + + .read_flag_mask = 0x80, + + .cache_type = REGCACHE_RBTREE, +}; + +static int rm3100_probe(struct spi_device *spi) +{ + struct regmap *regmap; + int ret; + + /* Actually this device supports both mode 0 and mode 3. */ + spi->mode = SPI_MODE_0; + /* Data rates cannot exceed 1Mbits. */ + spi->max_speed_hz = 1000000; + ret = spi_setup(spi); + if (ret) + return ret; + + regmap = devm_regmap_init_spi(spi, &rm3100_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return rm3100_common_probe(&spi->dev, regmap, spi->irq); +} + +static const struct of_device_id rm3100_dt_match[] = { + { .compatible = "pni,rm3100", }, + { } +}; +MODULE_DEVICE_TABLE(of, rm3100_dt_match); + +static struct spi_driver rm3100_driver = { + .driver = { + .name = "rm3100-spi", + .of_match_table = rm3100_dt_match, + }, + .probe = rm3100_probe, +}; +module_spi_driver(rm3100_driver); + +MODULE_AUTHOR("Song Qiang "); +MODULE_DESCRIPTION("PNI RM3100 3-axis magnetometer spi driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_RM3100"); diff --git a/drivers/iio/magnetometer/rm3100.h b/drivers/iio/magnetometer/rm3100.h new file mode 100644 index 000000000..c3508218b --- /dev/null +++ b/drivers/iio/magnetometer/rm3100.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2018 Song Qiang + */ + +#ifndef RM3100_CORE_H +#define RM3100_CORE_H + +#include + +extern const struct regmap_access_table rm3100_readable_table; +extern const struct regmap_access_table rm3100_writable_table; +extern const struct regmap_access_table rm3100_volatile_table; + +int rm3100_common_probe(struct device *dev, struct regmap *regmap, int irq); + +#endif /* RM3100_CORE_H */ diff --git a/drivers/iio/magnetometer/si7210.c b/drivers/iio/magnetometer/si7210.c new file mode 100644 index 000000000..e9670d671 --- /dev/null +++ b/drivers/iio/magnetometer/si7210.c @@ -0,0 +1,438 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Silicon Labs Si7210 Hall Effect sensor driver + * + * Copyright (c) 2024 Antoni Pokusinski + * + * Datasheet: + * https://www.silabs.com/documents/public/data-sheets/si7210-datasheet.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Registers offsets and masks */ +#define SI7210_REG_DSPSIGM 0xC1 +#define SI7210_REG_DSPSIGL 0xC2 + +#define SI7210_MASK_DSPSIGSEL GENMASK(2, 0) +#define SI7210_REG_DSPSIGSEL 0xC3 + +#define SI7210_MASK_STOP BIT(1) +#define SI7210_MASK_ONEBURST BIT(2) +#define SI7210_REG_POWER_CTRL 0xC4 + +#define SI7210_MASK_ARAUTOINC BIT(0) +#define SI7210_REG_ARAUTOINC 0xC5 + +#define SI7210_REG_A0 0xCA +#define SI7210_REG_A1 0xCB +#define SI7210_REG_A2 0xCC +#define SI7210_REG_A3 0xCE +#define SI7210_REG_A4 0xCF +#define SI7210_REG_A5 0xD0 + +#define SI7210_REG_OTP_ADDR 0xE1 +#define SI7210_REG_OTP_DATA 0xE2 + +#define SI7210_MASK_OTP_READ_EN BIT(1) +#define SI7210_REG_OTP_CTRL 0xE3 + +/* OTP data registers offsets */ +#define SI7210_OTPREG_TMP_OFF 0x1D +#define SI7210_OTPREG_TMP_GAIN 0x1E + +#define SI7210_OTPREG_A0_20 0x21 +#define SI7210_OTPREG_A1_20 0x22 +#define SI7210_OTPREG_A2_20 0x23 +#define SI7210_OTPREG_A3_20 0x24 +#define SI7210_OTPREG_A4_20 0x25 +#define SI7210_OTPREG_A5_20 0x26 + +#define SI7210_OTPREG_A0_200 0x27 +#define SI7210_OTPREG_A1_200 0x28 +#define SI7210_OTPREG_A2_200 0x29 +#define SI7210_OTPREG_A3_200 0x2A +#define SI7210_OTPREG_A4_200 0x2B +#define SI7210_OTPREG_A5_200 0x2C + +#define A_REGS_COUNT 6 + +static const unsigned int a20_otp_regs[A_REGS_COUNT] = { + SI7210_OTPREG_A0_20, SI7210_OTPREG_A1_20, SI7210_OTPREG_A2_20, + SI7210_OTPREG_A3_20, SI7210_OTPREG_A4_20, SI7210_OTPREG_A5_20, +}; + +static const unsigned int a200_otp_regs[A_REGS_COUNT] = { + SI7210_OTPREG_A0_200, SI7210_OTPREG_A1_200, SI7210_OTPREG_A2_200, + SI7210_OTPREG_A3_200, SI7210_OTPREG_A4_200, SI7210_OTPREG_A5_200, +}; + +static const struct regmap_range si7210_read_reg_ranges[] = { + regmap_reg_range(SI7210_REG_DSPSIGM, SI7210_REG_ARAUTOINC), + regmap_reg_range(SI7210_REG_A0, SI7210_REG_A2), + regmap_reg_range(SI7210_REG_A3, SI7210_REG_A5), + regmap_reg_range(SI7210_REG_OTP_ADDR, SI7210_REG_OTP_CTRL), +}; + +static const struct regmap_access_table si7210_readable_regs = { + .yes_ranges = si7210_read_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(si7210_read_reg_ranges), +}; + +static const struct regmap_range si7210_write_reg_ranges[] = { + regmap_reg_range(SI7210_REG_DSPSIGSEL, SI7210_REG_ARAUTOINC), + regmap_reg_range(SI7210_REG_A0, SI7210_REG_A2), + regmap_reg_range(SI7210_REG_A3, SI7210_REG_A5), + regmap_reg_range(SI7210_REG_OTP_ADDR, SI7210_REG_OTP_CTRL), +}; + +static const struct regmap_access_table si7210_writeable_regs = { + .yes_ranges = si7210_write_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(si7210_write_reg_ranges), +}; + +static const struct regmap_range si7210_volatile_reg_ranges[] = { + regmap_reg_range(SI7210_REG_DSPSIGM, SI7210_REG_DSPSIGL), + regmap_reg_range(SI7210_REG_POWER_CTRL, SI7210_REG_POWER_CTRL), +}; + +static const struct regmap_access_table si7210_volatile_regs = { + .yes_ranges = si7210_volatile_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(si7210_volatile_reg_ranges), +}; + +static const struct regmap_config si7210_regmap_conf = { + .reg_bits = 8, + .val_bits = 8, + .max_register = SI7210_REG_OTP_CTRL, + + .rd_table = &si7210_readable_regs, + .wr_table = &si7210_writeable_regs, + .volatile_table = &si7210_volatile_regs, +}; + +struct si7210_data { + struct regmap *regmap; + struct i2c_client *client; + struct mutex fetch_lock; /* lock for a single measurement fetch */ + unsigned int vdd_uV; + s8 temp_offset; + s8 temp_gain; + s8 scale_20_a[A_REGS_COUNT]; + s8 scale_200_a[A_REGS_COUNT]; + u8 curr_scale; +}; + +static const struct iio_chan_spec si7210_channels[] = { + { + .type = IIO_MAGN, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET), + }, { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, +}; + +static int si7210_fetch_measurement(struct si7210_data *data, + struct iio_chan_spec const *chan, + u16 *buf) +{ + u8 dspsigsel = chan->type == IIO_MAGN ? 0 : 1; + int ret; + __be16 result; + + guard(mutex)(&data->fetch_lock); + + ret = regmap_update_bits(data->regmap, SI7210_REG_DSPSIGSEL, + SI7210_MASK_DSPSIGSEL, dspsigsel); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, SI7210_REG_POWER_CTRL, + SI7210_MASK_ONEBURST | SI7210_MASK_STOP, + SI7210_MASK_ONEBURST & ~SI7210_MASK_STOP); + if (ret) + return ret; + + /* + * Read the contents of the + * registers containing the result: DSPSIGM, DSPSIGL + */ + ret = regmap_bulk_read(data->regmap, SI7210_REG_DSPSIGM, + &result, sizeof(result)); + if (ret) + return ret; + + *buf = be16_to_cpu(result); + + return 0; +} + +static int si7210_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct si7210_data *data = iio_priv(indio_dev); + long long temp; + u16 dspsig; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = si7210_fetch_measurement(data, chan, &dspsig); + if (ret) + return ret; + + *val = dspsig & GENMASK(14, 0); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + if (data->curr_scale == 20) + *val2 = 12500; + else /* data->curr_scale == 200 */ + *val2 = 125000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OFFSET: + *val = -16384; + return IIO_VAL_INT; + case IIO_CHAN_INFO_PROCESSED: + ret = si7210_fetch_measurement(data, chan, &dspsig); + if (ret) + return ret; + + /* temp = 32 * Dspsigm[6:0] + (Dspsigl[7:0] >> 3) */ + temp = FIELD_GET(GENMASK(14, 3), dspsig); + temp = div_s64(-383 * temp * temp, 100) + 160940 * temp - 279800000; + temp *= (1 + (data->temp_gain / 2048)); + temp += (int)(MICRO / 16) * data->temp_offset; + + /* temp -= 0.222 * VDD */ + temp -= 222 * (data->vdd_uV / MILLI); + + *val = div_s64(temp, MILLI); + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int si7210_set_scale(struct si7210_data *data, unsigned int scale) +{ + s8 *a_otp_values; + int ret; + + if (scale == 20) + a_otp_values = data->scale_20_a; + else if (scale == 200) + a_otp_values = data->scale_200_a; + else + return -EINVAL; + + guard(mutex)(&data->fetch_lock); + + /* Write the registers 0xCA - 0xCC */ + ret = regmap_bulk_write(data->regmap, SI7210_REG_A0, a_otp_values, 3); + if (ret) + return ret; + + /* Write the registers 0xCE - 0xD0 */ + ret = regmap_bulk_write(data->regmap, SI7210_REG_A3, &a_otp_values[3], 3); + if (ret) + return ret; + + data->curr_scale = scale; + + return 0; +} + +static int si7210_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct si7210_data *data = iio_priv(indio_dev); + unsigned int scale; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (val == 0 && val2 == 12500) + scale = 20; + else if (val == 0 && val2 == 125000) + scale = 200; + else + return -EINVAL; + + return si7210_set_scale(data, scale); + default: + return -EINVAL; + } +} + +static int si7210_read_otpreg_val(struct si7210_data *data, unsigned int otpreg, u8 *val) +{ + int ret; + unsigned int otpdata; + + ret = regmap_write(data->regmap, SI7210_REG_OTP_ADDR, otpreg); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, SI7210_REG_OTP_CTRL, + SI7210_MASK_OTP_READ_EN, SI7210_MASK_OTP_READ_EN); + if (ret) + return ret; + + ret = regmap_read(data->regmap, SI7210_REG_OTP_DATA, &otpdata); + if (ret) + return ret; + + *val = otpdata; + + return 0; +} + +/* + * According to the datasheet, the primary method to wake up a + * device is to send an empty write. However this is not feasible + * using the current API so we use the other method i.e. read a single + * byte. The device should respond with 0xFF. + */ +static int si7210_device_wake(struct si7210_data *data) +{ + int ret; + + ret = i2c_smbus_read_byte(data->client); + if (ret < 0) + return ret; + + if (ret != 0xFF) + return -EIO; + + return 0; +} + +static int si7210_device_init(struct si7210_data *data) +{ + int ret; + unsigned int i; + + ret = si7210_device_wake(data); + if (ret) + return ret; + + fsleep(1000); + + ret = si7210_read_otpreg_val(data, SI7210_OTPREG_TMP_GAIN, &data->temp_gain); + if (ret) + return ret; + + ret = si7210_read_otpreg_val(data, SI7210_OTPREG_TMP_OFF, &data->temp_offset); + if (ret) + return ret; + + for (i = 0; i < A_REGS_COUNT; i++) { + ret = si7210_read_otpreg_val(data, a20_otp_regs[i], &data->scale_20_a[i]); + if (ret) + return ret; + } + + for (i = 0; i < A_REGS_COUNT; i++) { + ret = si7210_read_otpreg_val(data, a200_otp_regs[i], &data->scale_200_a[i]); + if (ret) + return ret; + } + + ret = regmap_update_bits(data->regmap, SI7210_REG_ARAUTOINC, + SI7210_MASK_ARAUTOINC, SI7210_MASK_ARAUTOINC); + if (ret) + return ret; + + return si7210_set_scale(data, 20); +} + +static const struct iio_info si7210_info = { + .read_raw = si7210_read_raw, + .write_raw = si7210_write_raw, +}; + +static int si7210_probe(struct i2c_client *client) +{ + struct si7210_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + + ret = devm_mutex_init(&client->dev, &data->fetch_lock); + if (ret) + return ret; + + data->regmap = devm_regmap_init_i2c(client, &si7210_regmap_conf); + if (IS_ERR(data->regmap)) + return dev_err_probe(&client->dev, PTR_ERR(data->regmap), + "failed to register regmap\n"); + + ret = devm_regulator_get_enable_read_voltage(&client->dev, "vdd"); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to get vdd regulator\n"); + data->vdd_uV = ret; + + indio_dev->name = dev_name(&client->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &si7210_info; + indio_dev->channels = si7210_channels; + indio_dev->num_channels = ARRAY_SIZE(si7210_channels); + + ret = si7210_device_init(data); + if (ret) + return dev_err_probe(&client->dev, ret, + "device initialization failed\n"); + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id si7210_id[] = { + { .name = "si7210" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, si7210_id); + +static const struct of_device_id si7210_dt_ids[] = { + { .compatible = "silabs,si7210" }, + { } +}; +MODULE_DEVICE_TABLE(of, si7210_dt_ids); + +static struct i2c_driver si7210_driver = { + .driver = { + .name = "si7210", + .of_match_table = si7210_dt_ids, + }, + .probe = si7210_probe, + .id_table = si7210_id, +}; +module_i2c_driver(si7210_driver); + +MODULE_AUTHOR("Antoni Pokusinski "); +MODULE_DESCRIPTION("Silicon Labs Si7210 Hall Effect sensor I2C driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/magnetometer/st_magn.h b/drivers/iio/magnetometer/st_magn.h new file mode 100644 index 000000000..89945984d --- /dev/null +++ b/drivers/iio/magnetometer/st_magn.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * STMicroelectronics magnetometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + * v. 1.0.0 + */ + +#ifndef ST_MAGN_H +#define ST_MAGN_H + +#include +#include + +#define LSM303DLH_MAGN_DEV_NAME "lsm303dlh_magn" +#define LSM303DLHC_MAGN_DEV_NAME "lsm303dlhc_magn" +#define LSM303DLM_MAGN_DEV_NAME "lsm303dlm_magn" +#define LIS3MDL_MAGN_DEV_NAME "lis3mdl" +#define LSM303AGR_MAGN_DEV_NAME "lsm303agr_magn" +#define LIS2MDL_MAGN_DEV_NAME "lis2mdl" +#define LSM9DS1_MAGN_DEV_NAME "lsm9ds1_magn" +#define IIS2MDC_MAGN_DEV_NAME "iis2mdc" +#define LSM303C_MAGN_DEV_NAME "lsm303c_magn" + +#ifdef CONFIG_IIO_BUFFER +int st_magn_allocate_ring(struct iio_dev *indio_dev); +int st_magn_trig_set_state(struct iio_trigger *trig, bool state); +#define ST_MAGN_TRIGGER_SET_STATE (&st_magn_trig_set_state) +#else /* CONFIG_IIO_BUFFER */ +static inline int st_magn_allocate_ring(struct iio_dev *indio_dev) +{ + return 0; +} +#define ST_MAGN_TRIGGER_SET_STATE NULL +#endif /* CONFIG_IIO_BUFFER */ + +#endif /* ST_MAGN_H */ diff --git a/drivers/iio/magnetometer/st_magn_buffer.c b/drivers/iio/magnetometer/st_magn_buffer.c new file mode 100644 index 000000000..79987f42e --- /dev/null +++ b/drivers/iio/magnetometer/st_magn_buffer.c @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics magnetometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include +#include + +#include +#include "st_magn.h" + +int st_magn_trig_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + + return st_sensors_set_dataready_irq(indio_dev, state); +} + +static int st_magn_buffer_postenable(struct iio_dev *indio_dev) +{ + return st_sensors_set_enable(indio_dev, true); +} + +static int st_magn_buffer_predisable(struct iio_dev *indio_dev) +{ + return st_sensors_set_enable(indio_dev, false); +} + +static const struct iio_buffer_setup_ops st_magn_buffer_setup_ops = { + .postenable = &st_magn_buffer_postenable, + .predisable = &st_magn_buffer_predisable, +}; + +int st_magn_allocate_ring(struct iio_dev *indio_dev) +{ + return devm_iio_triggered_buffer_setup(indio_dev->dev.parent, indio_dev, + NULL, &st_sensors_trigger_handler, &st_magn_buffer_setup_ops); +} + diff --git a/drivers/iio/magnetometer/st_magn_core.c b/drivers/iio/magnetometer/st_magn_core.c new file mode 100644 index 000000000..7644bd046 --- /dev/null +++ b/drivers/iio/magnetometer/st_magn_core.c @@ -0,0 +1,665 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics magnetometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "st_magn.h" + +#define ST_MAGN_NUMBER_DATA_CHANNELS 3 + +/* DEFAULT VALUE FOR SENSORS */ +#define ST_MAGN_DEFAULT_OUT_X_H_ADDR 0x03 +#define ST_MAGN_DEFAULT_OUT_Y_H_ADDR 0x07 +#define ST_MAGN_DEFAULT_OUT_Z_H_ADDR 0x05 + +/* FULLSCALE */ +#define ST_MAGN_FS_AVL_1300MG 1300 +#define ST_MAGN_FS_AVL_1900MG 1900 +#define ST_MAGN_FS_AVL_2000MG 2000 +#define ST_MAGN_FS_AVL_2500MG 2500 +#define ST_MAGN_FS_AVL_4000MG 4000 +#define ST_MAGN_FS_AVL_4700MG 4700 +#define ST_MAGN_FS_AVL_5600MG 5600 +#define ST_MAGN_FS_AVL_8000MG 8000 +#define ST_MAGN_FS_AVL_8100MG 8100 +#define ST_MAGN_FS_AVL_12000MG 12000 +#define ST_MAGN_FS_AVL_15000MG 15000 +#define ST_MAGN_FS_AVL_16000MG 16000 + +/* Special L addresses for Sensor 2 */ +#define ST_MAGN_2_OUT_X_L_ADDR 0x28 +#define ST_MAGN_2_OUT_Y_L_ADDR 0x2a +#define ST_MAGN_2_OUT_Z_L_ADDR 0x2c + +/* Special L addresses for sensor 3 */ +#define ST_MAGN_3_OUT_X_L_ADDR 0x68 +#define ST_MAGN_3_OUT_Y_L_ADDR 0x6a +#define ST_MAGN_3_OUT_Z_L_ADDR 0x6c + +/* Special L addresses for sensor 4 */ +#define ST_MAGN_4_OUT_X_L_ADDR 0x08 +#define ST_MAGN_4_OUT_Y_L_ADDR 0x0a +#define ST_MAGN_4_OUT_Z_L_ADDR 0x0c + +static const struct iio_mount_matrix * +st_magn_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct st_sensor_data *mdata = iio_priv(indio_dev); + + return &mdata->mount_matrix; +} + +static const struct iio_chan_spec_ext_info st_magn_mount_matrix_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, st_magn_get_mount_matrix), + { } +}; + +static const struct iio_chan_spec st_magn_16bit_channels[] = { + ST_SENSORS_LSM_CHANNELS_EXT(IIO_MAGN, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_X, 1, IIO_MOD_X, 's', IIO_BE, 16, 16, + ST_MAGN_DEFAULT_OUT_X_H_ADDR, + st_magn_mount_matrix_ext_info), + ST_SENSORS_LSM_CHANNELS_EXT(IIO_MAGN, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Y, 1, IIO_MOD_Y, 's', IIO_BE, 16, 16, + ST_MAGN_DEFAULT_OUT_Y_H_ADDR, + st_magn_mount_matrix_ext_info), + ST_SENSORS_LSM_CHANNELS_EXT(IIO_MAGN, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Z, 1, IIO_MOD_Z, 's', IIO_BE, 16, 16, + ST_MAGN_DEFAULT_OUT_Z_H_ADDR, + st_magn_mount_matrix_ext_info), + IIO_CHAN_SOFT_TIMESTAMP(3) +}; + +static const struct iio_chan_spec st_magn_2_16bit_channels[] = { + ST_SENSORS_LSM_CHANNELS_EXT(IIO_MAGN, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_X, 1, IIO_MOD_X, 's', IIO_LE, 16, 16, + ST_MAGN_2_OUT_X_L_ADDR, + st_magn_mount_matrix_ext_info), + ST_SENSORS_LSM_CHANNELS_EXT(IIO_MAGN, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Y, 1, IIO_MOD_Y, 's', IIO_LE, 16, 16, + ST_MAGN_2_OUT_Y_L_ADDR, + st_magn_mount_matrix_ext_info), + ST_SENSORS_LSM_CHANNELS_EXT(IIO_MAGN, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Z, 1, IIO_MOD_Z, 's', IIO_LE, 16, 16, + ST_MAGN_2_OUT_Z_L_ADDR, + st_magn_mount_matrix_ext_info), + IIO_CHAN_SOFT_TIMESTAMP(3) +}; + +static const struct iio_chan_spec st_magn_3_16bit_channels[] = { + ST_SENSORS_LSM_CHANNELS_EXT(IIO_MAGN, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_X, 1, IIO_MOD_X, 's', IIO_LE, 16, 16, + ST_MAGN_3_OUT_X_L_ADDR, + st_magn_mount_matrix_ext_info), + ST_SENSORS_LSM_CHANNELS_EXT(IIO_MAGN, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Y, 1, IIO_MOD_Y, 's', IIO_LE, 16, 16, + ST_MAGN_3_OUT_Y_L_ADDR, + st_magn_mount_matrix_ext_info), + ST_SENSORS_LSM_CHANNELS_EXT(IIO_MAGN, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Z, 1, IIO_MOD_Z, 's', IIO_LE, 16, 16, + ST_MAGN_3_OUT_Z_L_ADDR, + st_magn_mount_matrix_ext_info), + IIO_CHAN_SOFT_TIMESTAMP(3) +}; + +static const struct iio_chan_spec st_magn_4_16bit_channels[] = { + ST_SENSORS_LSM_CHANNELS(IIO_MAGN, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_X, 1, IIO_MOD_X, 's', IIO_LE, 16, 16, + ST_MAGN_4_OUT_X_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_MAGN, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Y, 1, IIO_MOD_Y, 's', IIO_LE, 16, 16, + ST_MAGN_4_OUT_Y_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_MAGN, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Z, 1, IIO_MOD_Z, 's', IIO_LE, 16, 16, + ST_MAGN_4_OUT_Z_L_ADDR), + IIO_CHAN_SOFT_TIMESTAMP(3) +}; + +static const struct st_sensor_settings st_magn_sensors_settings[] = { + { + .wai = 0, /* This sensor has no valid WhoAmI report 0 */ + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LSM303DLH_MAGN_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_magn_16bit_channels, + .odr = { + .addr = 0x00, + .mask = 0x1c, + .odr_avl = { + { .hz = 1, .value = 0x00 }, + { .hz = 2, .value = 0x01 }, + { .hz = 3, .value = 0x02 }, + { .hz = 8, .value = 0x03 }, + { .hz = 15, .value = 0x04 }, + { .hz = 30, .value = 0x05 }, + { .hz = 75, .value = 0x06 }, + /* 220 Hz, 0x07 reportedly exist */ + }, + }, + .pw = { + .addr = 0x02, + .mask = 0x03, + .value_on = 0x00, + .value_off = 0x03, + }, + .fs = { + .addr = 0x01, + .mask = 0xe0, + .fs_avl = { + [0] = { + .num = ST_MAGN_FS_AVL_1300MG, + .value = 0x01, + .gain = 1100, + .gain2 = 980, + }, + [1] = { + .num = ST_MAGN_FS_AVL_1900MG, + .value = 0x02, + .gain = 855, + .gain2 = 760, + }, + [2] = { + .num = ST_MAGN_FS_AVL_2500MG, + .value = 0x03, + .gain = 670, + .gain2 = 600, + }, + [3] = { + .num = ST_MAGN_FS_AVL_4000MG, + .value = 0x04, + .gain = 450, + .gain2 = 400, + }, + [4] = { + .num = ST_MAGN_FS_AVL_4700MG, + .value = 0x05, + .gain = 400, + .gain2 = 355, + }, + [5] = { + .num = ST_MAGN_FS_AVL_5600MG, + .value = 0x06, + .gain = 330, + .gain2 = 295, + }, + [6] = { + .num = ST_MAGN_FS_AVL_8100MG, + .value = 0x07, + .gain = 230, + .gain2 = 205, + }, + }, + }, + .multi_read_bit = false, + .bootime = 2, + }, + { + .wai = 0x3c, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LSM303DLHC_MAGN_DEV_NAME, + [1] = LSM303DLM_MAGN_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_magn_16bit_channels, + .odr = { + .addr = 0x00, + .mask = 0x1c, + .odr_avl = { + { .hz = 1, .value = 0x00 }, + { .hz = 2, .value = 0x01 }, + { .hz = 3, .value = 0x02 }, + { .hz = 8, .value = 0x03 }, + { .hz = 15, .value = 0x04 }, + { .hz = 30, .value = 0x05 }, + { .hz = 75, .value = 0x06 }, + { .hz = 220, .value = 0x07 }, + }, + }, + .pw = { + .addr = 0x02, + .mask = 0x03, + .value_on = 0x00, + .value_off = 0x03, + }, + .fs = { + .addr = 0x01, + .mask = 0xe0, + .fs_avl = { + [0] = { + .num = ST_MAGN_FS_AVL_1300MG, + .value = 0x01, + .gain = 909, + .gain2 = 1020, + }, + [1] = { + .num = ST_MAGN_FS_AVL_1900MG, + .value = 0x02, + .gain = 1169, + .gain2 = 1315, + }, + [2] = { + .num = ST_MAGN_FS_AVL_2500MG, + .value = 0x03, + .gain = 1492, + .gain2 = 1666, + }, + [3] = { + .num = ST_MAGN_FS_AVL_4000MG, + .value = 0x04, + .gain = 2222, + .gain2 = 2500, + }, + [4] = { + .num = ST_MAGN_FS_AVL_4700MG, + .value = 0x05, + .gain = 2500, + .gain2 = 2816, + }, + [5] = { + .num = ST_MAGN_FS_AVL_5600MG, + .value = 0x06, + .gain = 3030, + .gain2 = 3389, + }, + [6] = { + .num = ST_MAGN_FS_AVL_8100MG, + .value = 0x07, + .gain = 4347, + .gain2 = 4878, + }, + }, + }, + .multi_read_bit = false, + .bootime = 2, + }, + { + .wai = 0x3d, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LIS3MDL_MAGN_DEV_NAME, + [1] = LSM9DS1_MAGN_DEV_NAME, + [2] = LSM303C_MAGN_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_magn_2_16bit_channels, + .odr = { + .addr = 0x20, + .mask = 0x1c, + .odr_avl = { + { .hz = 1, .value = 0x00 }, + { .hz = 2, .value = 0x01 }, + { .hz = 3, .value = 0x02 }, + { .hz = 5, .value = 0x03 }, + { .hz = 10, .value = 0x04 }, + { .hz = 20, .value = 0x05 }, + { .hz = 40, .value = 0x06 }, + { .hz = 80, .value = 0x07 }, + }, + }, + .pw = { + .addr = 0x22, + .mask = 0x03, + .value_on = 0x00, + .value_off = 0x03, + }, + .fs = { + .addr = 0x21, + .mask = 0x60, + .fs_avl = { + [0] = { + .num = ST_MAGN_FS_AVL_4000MG, + .value = 0x00, + .gain = 146, + }, + [1] = { + .num = ST_MAGN_FS_AVL_8000MG, + .value = 0x01, + .gain = 292, + }, + [2] = { + .num = ST_MAGN_FS_AVL_12000MG, + .value = 0x02, + .gain = 438, + }, + [3] = { + .num = ST_MAGN_FS_AVL_16000MG, + .value = 0x03, + .gain = 584, + }, + }, + }, + .bdu = { + .addr = 0x24, + .mask = 0x40, + }, + .drdy_irq = { + /* drdy line is routed drdy pin */ + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x07, + }, + }, + .sim = { + .addr = 0x22, + .value = BIT(2), + }, + .multi_read_bit = true, + .bootime = 2, + }, + { + .wai = 0x40, + .wai_addr = 0x4f, + .sensors_supported = { + [0] = LSM303AGR_MAGN_DEV_NAME, + [1] = LIS2MDL_MAGN_DEV_NAME, + [2] = IIS2MDC_MAGN_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_magn_3_16bit_channels, + .odr = { + .addr = 0x60, + .mask = 0x0c, + .odr_avl = { + { .hz = 10, .value = 0x00 }, + { .hz = 20, .value = 0x01 }, + { .hz = 50, .value = 0x02 }, + { .hz = 100, .value = 0x03 }, + }, + }, + .pw = { + .addr = 0x60, + .mask = 0x03, + .value_on = 0x00, + .value_off = 0x03, + }, + .fs = { + .fs_avl = { + [0] = { + .num = ST_MAGN_FS_AVL_15000MG, + .gain = 1500, + }, + }, + }, + .bdu = { + .addr = 0x62, + .mask = 0x10, + }, + .drdy_irq = { + .int1 = { + .addr = 0x62, + .mask = 0x01, + }, + .stat_drdy = { + .addr = 0x67, + .mask = 0x07, + }, + }, + .multi_read_bit = false, + .bootime = 2, + }, + { + .wai = 0x49, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LSM9DS0_IMU_DEV_NAME, + [1] = LSM303D_IMU_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_magn_4_16bit_channels, + .odr = { + .addr = 0x24, + .mask = GENMASK(4, 2), + .odr_avl = { + { 3, 0x00, }, + { 6, 0x01, }, + { 12, 0x02, }, + { 25, 0x03, }, + { 50, 0x04, }, + { 100, 0x05, }, + }, + }, + .pw = { + .addr = 0x26, + .mask = GENMASK(1, 0), + .value_on = 0x00, + .value_off = 0x03, + }, + .fs = { + .addr = 0x25, + .mask = GENMASK(6, 5), + .fs_avl = { + [0] = { + .num = ST_MAGN_FS_AVL_2000MG, + .value = 0x00, + .gain = 73, + }, + [1] = { + .num = ST_MAGN_FS_AVL_4000MG, + .value = 0x01, + .gain = 146, + }, + [2] = { + .num = ST_MAGN_FS_AVL_8000MG, + .value = 0x02, + .gain = 292, + }, + [3] = { + .num = ST_MAGN_FS_AVL_12000MG, + .value = 0x03, + .gain = 438, + }, + }, + }, + .bdu = { + .addr = 0x20, + .mask = BIT(3), + }, + .drdy_irq = { + .int1 = { + .addr = 0x22, + .mask = BIT(1), + }, + .int2 = { + .addr = 0x23, + .mask = BIT(2), + }, + .stat_drdy = { + .addr = 0x07, + .mask = GENMASK(2, 0), + }, + }, + .sim = { + .addr = 0x21, + .value = BIT(0), + }, + .multi_read_bit = true, + .bootime = 2, + }, +}; + +/* Default magn DRDY is available on INT2 pin */ +static const struct st_sensors_platform_data default_magn_pdata = { + .drdy_int_pin = 2, +}; + +/* LIS2MDL only supports DRDY on INT1 */ +static const struct st_sensors_platform_data alt_magn_pdata = { + .drdy_int_pin = 1, +}; + +static int st_magn_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *ch, int *val, + int *val2, long mask) +{ + int err; + struct st_sensor_data *mdata = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + err = st_sensors_read_info_raw(indio_dev, ch, val); + if (err < 0) + goto read_error; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + if ((ch->scan_index == ST_SENSORS_SCAN_Z) && + (mdata->current_fullscale->gain2 != 0)) + *val2 = mdata->current_fullscale->gain2; + else + *val2 = mdata->current_fullscale->gain; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = mdata->odr; + return IIO_VAL_INT; + default: + return -EINVAL; + } + +read_error: + return err; +} + +static int st_magn_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return st_sensors_set_fullscale_by_gain(indio_dev, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + if (val2) + return -EINVAL; + + return st_sensors_set_odr(indio_dev, val); + default: + return -EINVAL; + } +} + +static ST_SENSORS_DEV_ATTR_SAMP_FREQ_AVAIL(); +static ST_SENSORS_DEV_ATTR_SCALE_AVAIL(in_magn_scale_available); + +static struct attribute *st_magn_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_magn_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group st_magn_attribute_group = { + .attrs = st_magn_attributes, +}; + +static const struct iio_info magn_info = { + .attrs = &st_magn_attribute_group, + .read_raw = &st_magn_read_raw, + .write_raw = &st_magn_write_raw, + .debugfs_reg_access = &st_sensors_debugfs_reg_access, +}; + +#ifdef CONFIG_IIO_TRIGGER +static const struct iio_trigger_ops st_magn_trigger_ops = { + .set_trigger_state = ST_MAGN_TRIGGER_SET_STATE, + .validate_device = st_sensors_validate_device, +}; +#define ST_MAGN_TRIGGER_OPS (&st_magn_trigger_ops) +#else +#define ST_MAGN_TRIGGER_OPS NULL +#endif + +/* + * st_magn_get_settings() - get sensor settings from device name + * @name: device name buffer reference. + * + * Return: valid reference on success, NULL otherwise. + */ +const struct st_sensor_settings *st_magn_get_settings(const char *name) +{ + int index = st_sensors_get_settings_index(name, + st_magn_sensors_settings, + ARRAY_SIZE(st_magn_sensors_settings)); + if (index < 0) + return NULL; + + return &st_magn_sensors_settings[index]; +} +EXPORT_SYMBOL_NS(st_magn_get_settings, "IIO_ST_SENSORS"); + +int st_magn_common_probe(struct iio_dev *indio_dev) +{ + struct st_sensor_data *mdata = iio_priv(indio_dev); + struct device *parent = indio_dev->dev.parent; + struct st_sensors_platform_data *pdata = dev_get_platdata(parent); + int err; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &magn_info; + + err = st_sensors_verify_id(indio_dev); + if (err < 0) + return err; + + mdata->num_data_channels = ST_MAGN_NUMBER_DATA_CHANNELS; + indio_dev->channels = mdata->sensor_settings->ch; + indio_dev->num_channels = ST_SENSORS_NUMBER_ALL_CHANNELS; + + err = iio_read_mount_matrix(parent, &mdata->mount_matrix); + if (err) + return err; + + mdata->current_fullscale = &mdata->sensor_settings->fs.fs_avl[0]; + mdata->odr = mdata->sensor_settings->odr.odr_avl[0].hz; + + if (!pdata) { + if (mdata->sensor_settings->drdy_irq.int2.mask) + pdata = (struct st_sensors_platform_data *)&default_magn_pdata; + else + pdata = (struct st_sensors_platform_data *)&alt_magn_pdata; + } + + err = st_sensors_init_sensor(indio_dev, pdata); + if (err < 0) + return err; + + err = st_magn_allocate_ring(indio_dev); + if (err < 0) + return err; + + if (mdata->irq > 0) { + err = st_sensors_allocate_trigger(indio_dev, + ST_MAGN_TRIGGER_OPS); + if (err < 0) + return err; + } + + return devm_iio_device_register(parent, indio_dev); +} +EXPORT_SYMBOL_NS(st_magn_common_probe, "IIO_ST_SENSORS"); + +MODULE_AUTHOR("Denis Ciocca "); +MODULE_DESCRIPTION("STMicroelectronics magnetometers driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ST_SENSORS"); diff --git a/drivers/iio/magnetometer/st_magn_i2c.c b/drivers/iio/magnetometer/st_magn_i2c.c new file mode 100644 index 000000000..de6601681 --- /dev/null +++ b/drivers/iio/magnetometer/st_magn_i2c.c @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics magnetometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include + +#include +#include +#include "st_magn.h" + +static const struct of_device_id st_magn_of_match[] = { + { + .compatible = "st,lsm303dlh-magn", + .data = LSM303DLH_MAGN_DEV_NAME, + }, + { + .compatible = "st,lsm303dlhc-magn", + .data = LSM303DLHC_MAGN_DEV_NAME, + }, + { + .compatible = "st,lsm303dlm-magn", + .data = LSM303DLM_MAGN_DEV_NAME, + }, + { + .compatible = "st,lis3mdl-magn", + .data = LIS3MDL_MAGN_DEV_NAME, + }, + { + .compatible = "st,lsm303agr-magn", + .data = LSM303AGR_MAGN_DEV_NAME, + }, + { + .compatible = "st,lis2mdl", + .data = LIS2MDL_MAGN_DEV_NAME, + }, + { + .compatible = "st,lsm9ds1-magn", + .data = LSM9DS1_MAGN_DEV_NAME, + }, + { + .compatible = "st,iis2mdc", + .data = IIS2MDC_MAGN_DEV_NAME, + }, + { + .compatible = "st,lsm303c-magn", + .data = LSM303C_MAGN_DEV_NAME, + }, + { } +}; +MODULE_DEVICE_TABLE(of, st_magn_of_match); + +static int st_magn_i2c_probe(struct i2c_client *client) +{ + const struct st_sensor_settings *settings; + struct st_sensor_data *mdata; + struct iio_dev *indio_dev; + int err; + + st_sensors_dev_name_probe(&client->dev, client->name, sizeof(client->name)); + + settings = st_magn_get_settings(client->name); + if (!settings) { + dev_err(&client->dev, "device name %s not recognized.\n", + client->name); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*mdata)); + if (!indio_dev) + return -ENOMEM; + + mdata = iio_priv(indio_dev); + mdata->sensor_settings = (struct st_sensor_settings *)settings; + + err = st_sensors_i2c_configure(indio_dev, client); + if (err < 0) + return err; + + err = st_sensors_power_enable(indio_dev); + if (err) + return err; + + return st_magn_common_probe(indio_dev); +} + +static const struct i2c_device_id st_magn_id_table[] = { + { .name = LSM303DLH_MAGN_DEV_NAME }, + { .name = LSM303DLHC_MAGN_DEV_NAME }, + { .name = LSM303DLM_MAGN_DEV_NAME }, + { .name = LIS3MDL_MAGN_DEV_NAME }, + { .name = LSM303AGR_MAGN_DEV_NAME }, + { .name = LIS2MDL_MAGN_DEV_NAME }, + { .name = LSM9DS1_MAGN_DEV_NAME }, + { .name = IIS2MDC_MAGN_DEV_NAME }, + { .name = LSM303C_MAGN_DEV_NAME }, + { } +}; +MODULE_DEVICE_TABLE(i2c, st_magn_id_table); + +static struct i2c_driver st_magn_driver = { + .driver = { + .name = "st-magn-i2c", + .of_match_table = st_magn_of_match, + }, + .probe = st_magn_i2c_probe, + .id_table = st_magn_id_table, +}; +module_i2c_driver(st_magn_driver); + +MODULE_AUTHOR("Denis Ciocca "); +MODULE_DESCRIPTION("STMicroelectronics magnetometers i2c driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ST_SENSORS"); diff --git a/drivers/iio/magnetometer/st_magn_spi.c b/drivers/iio/magnetometer/st_magn_spi.c new file mode 100644 index 000000000..af4642b6c --- /dev/null +++ b/drivers/iio/magnetometer/st_magn_spi.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics magnetometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include + +#include +#include +#include "st_magn.h" + +/* + * For new single-chip sensors use as compatible string. + * For old single-chip devices keep -magn to maintain + * compatibility + * For multi-chip devices, use -magn to distinguish which + * capability is being used + */ +static const struct of_device_id st_magn_of_match[] = { + { + .compatible = "st,lis3mdl-magn", + .data = LIS3MDL_MAGN_DEV_NAME, + }, + { + .compatible = "st,lsm303agr-magn", + .data = LSM303AGR_MAGN_DEV_NAME, + }, + { + .compatible = "st,lis2mdl", + .data = LIS2MDL_MAGN_DEV_NAME, + }, + { + .compatible = "st,lsm9ds1-magn", + .data = LSM9DS1_MAGN_DEV_NAME, + }, + { + .compatible = "st,iis2mdc", + .data = IIS2MDC_MAGN_DEV_NAME, + }, + { + .compatible = "st,lsm303c-magn", + .data = LSM303C_MAGN_DEV_NAME, + }, + { } +}; +MODULE_DEVICE_TABLE(of, st_magn_of_match); + +static int st_magn_spi_probe(struct spi_device *spi) +{ + const struct st_sensor_settings *settings; + struct st_sensor_data *mdata; + struct iio_dev *indio_dev; + int err; + + st_sensors_dev_name_probe(&spi->dev, spi->modalias, sizeof(spi->modalias)); + + settings = st_magn_get_settings(spi->modalias); + if (!settings) { + dev_err(&spi->dev, "device name %s not recognized.\n", + spi->modalias); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*mdata)); + if (!indio_dev) + return -ENOMEM; + + mdata = iio_priv(indio_dev); + mdata->sensor_settings = (struct st_sensor_settings *)settings; + + err = st_sensors_spi_configure(indio_dev, spi); + if (err < 0) + return err; + + err = st_sensors_power_enable(indio_dev); + if (err) + return err; + + return st_magn_common_probe(indio_dev); +} + +static const struct spi_device_id st_magn_id_table[] = { + { .name = LIS3MDL_MAGN_DEV_NAME }, + { .name = LSM303AGR_MAGN_DEV_NAME }, + { .name = LIS2MDL_MAGN_DEV_NAME }, + { .name = LSM9DS1_MAGN_DEV_NAME }, + { .name = IIS2MDC_MAGN_DEV_NAME }, + { .name = LSM303C_MAGN_DEV_NAME }, + { } +}; +MODULE_DEVICE_TABLE(spi, st_magn_id_table); + +static struct spi_driver st_magn_driver = { + .driver = { + .name = "st-magn-spi", + .of_match_table = st_magn_of_match, + }, + .probe = st_magn_spi_probe, + .id_table = st_magn_id_table, +}; +module_spi_driver(st_magn_driver); + +MODULE_AUTHOR("Denis Ciocca "); +MODULE_DESCRIPTION("STMicroelectronics magnetometers spi driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ST_SENSORS"); diff --git a/drivers/iio/magnetometer/tlv493d.c b/drivers/iio/magnetometer/tlv493d.c new file mode 100644 index 000000000..03415cc2c --- /dev/null +++ b/drivers/iio/magnetometer/tlv493d.c @@ -0,0 +1,525 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for the Infineon TLV493D Low-Power 3D Magnetic Sensor + * + * Copyright (C) 2025 Dixit Parmar + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* + * TLV493D sensor I2C communication note: + * + * The sensor supports only direct byte-stream write starting from the + * register address 0x0. So for any modification to be made to any write + * registers, it must be written starting from the register address 0x0. + * I2C write operation should not contain the register address in the I2C + * frame, it should contain only raw byte stream for the write registers. + * I2C Frame: |S|SlaveAddr Wr|Ack|Byte[0]|Ack|Byte[1]|Ack|.....|Sp| + * + * Same as the write operation, reading from the sensor registers is also + * performed starting from the register address 0x0 for as many bytes as + * need to be read. + * I2C read operation should not contain the register address in the I2C frame. + * I2C Frame: |S|SlaveAddr Rd|Ack|Byte[0]|Ack|Byte[1]|Ack|.....|Sp| + */ + +#define TLV493D_RD_REG_BX 0x00 +#define TLV493D_RD_REG_BY 0x01 +#define TLV493D_RD_REG_BZ 0x02 +#define TLV493D_RD_REG_TEMP 0x03 +#define TLV493D_RD_REG_BX2 0x04 +#define TLV493D_RD_REG_BZ2 0x05 +#define TLV493D_RD_REG_TEMP2 0x06 +#define TLV493D_RD_REG_RES1 0x07 +#define TLV493D_RD_REG_RES2 0x08 +#define TLV493D_RD_REG_RES3 0x09 +#define TLV493D_RD_REG_MAX 0x0a + +#define TLV493D_WR_REG_MODE1 0x01 +#define TLV493D_WR_REG_MODE2 0x03 +#define TLV493D_WR_REG_MAX 0x04 + +#define TLV493D_BX_MAG_X_AXIS_MSB GENMASK(7, 0) +#define TLV493D_BX2_MAG_X_AXIS_LSB GENMASK(7, 4) +#define TLV493D_BY_MAG_Y_AXIS_MSB GENMASK(7, 0) +#define TLV493D_BX2_MAG_Y_AXIS_LSB GENMASK(3, 0) +#define TLV493D_BZ_MAG_Z_AXIS_MSB GENMASK(7, 0) +#define TLV493D_BZ2_MAG_Z_AXIS_LSB GENMASK(3, 0) +#define TLV493D_TEMP_TEMP_MSB GENMASK(7, 4) +#define TLV493D_TEMP2_TEMP_LSB GENMASK(7, 0) +#define TLV493D_TEMP_CHANNEL GENMASK(1, 0) +#define TLV493D_MODE1_MOD_LOWFAST GENMASK(1, 0) +#define TLV493D_MODE2_LP_PERIOD BIT(6) +#define TLV493D_RD_REG_RES1_WR_MASK GENMASK(4, 3) +#define TLV493D_RD_REG_RES2_WR_MASK GENMASK(7, 0) +#define TLV493D_RD_REG_RES3_WR_MASK GENMASK(4, 0) + +enum tlv493d_channels { + TLV493D_AXIS_X, + TLV493D_AXIS_Y, + TLV493D_AXIS_Z, + TLV493D_TEMPERATURE, +}; + +enum tlv493d_op_mode { + TLV493D_OP_MODE_POWERDOWN, + TLV493D_OP_MODE_FAST, + TLV493D_OP_MODE_LOWPOWER, + TLV493D_OP_MODE_ULTRA_LOWPOWER, + TLV493D_OP_MODE_MASTERCONTROLLED, +}; + +struct tlv493d_data { + struct i2c_client *client; + /* protects from simultaneous sensor access and register readings */ + struct mutex lock; + enum tlv493d_op_mode mode; + u8 wr_regs[TLV493D_WR_REG_MAX]; +}; + +/* + * Different mode has different measurement sampling time, this time is + * used in deriving the sleep and timeout while reading the data from + * sensor in polling. + * Power-down mode: No measurement. + * Fast mode: Freq:3.3 KHz. Measurement time:305 usec. + * Low-power mode: Freq:100 Hz. Measurement time:10 msec. + * Ultra low-power mode: Freq:10 Hz. Measurement time:100 msec. + * Master controlled mode: Freq:3.3 Khz. Measurement time:305 usec. + */ +static const u32 tlv493d_sample_rate_us[] = { + [TLV493D_OP_MODE_POWERDOWN] = 0, + [TLV493D_OP_MODE_FAST] = 305, + [TLV493D_OP_MODE_LOWPOWER] = 10 * USEC_PER_MSEC, + [TLV493D_OP_MODE_ULTRA_LOWPOWER] = 100 * USEC_PER_MSEC, + [TLV493D_OP_MODE_MASTERCONTROLLED] = 305, +}; + +static int tlv493d_write_all_regs(struct tlv493d_data *data) +{ + int ret; + struct device *dev = &data->client->dev; + + ret = i2c_master_send(data->client, data->wr_regs, ARRAY_SIZE(data->wr_regs)); + if (ret < 0) { + dev_err(dev, "i2c write registers failed, error: %d\n", ret); + return ret; + } + + return 0; +} + +static int tlv493d_set_operating_mode(struct tlv493d_data *data, enum tlv493d_op_mode mode) +{ + u8 *mode1_cfg = &data->wr_regs[TLV493D_WR_REG_MODE1]; + u8 *mode2_cfg = &data->wr_regs[TLV493D_WR_REG_MODE2]; + + switch (mode) { + case TLV493D_OP_MODE_POWERDOWN: + FIELD_MODIFY(TLV493D_MODE1_MOD_LOWFAST, mode1_cfg, 0); + FIELD_MODIFY(TLV493D_MODE2_LP_PERIOD, mode2_cfg, 0); + break; + + case TLV493D_OP_MODE_FAST: + FIELD_MODIFY(TLV493D_MODE1_MOD_LOWFAST, mode1_cfg, 1); + FIELD_MODIFY(TLV493D_MODE2_LP_PERIOD, mode2_cfg, 0); + break; + + case TLV493D_OP_MODE_LOWPOWER: + FIELD_MODIFY(TLV493D_MODE1_MOD_LOWFAST, mode1_cfg, 2); + FIELD_MODIFY(TLV493D_MODE2_LP_PERIOD, mode2_cfg, 1); + break; + + case TLV493D_OP_MODE_ULTRA_LOWPOWER: + FIELD_MODIFY(TLV493D_MODE1_MOD_LOWFAST, mode1_cfg, 2); + FIELD_MODIFY(TLV493D_MODE2_LP_PERIOD, mode2_cfg, 0); + break; + + case TLV493D_OP_MODE_MASTERCONTROLLED: + FIELD_MODIFY(TLV493D_MODE1_MOD_LOWFAST, mode1_cfg, 3); + FIELD_MODIFY(TLV493D_MODE2_LP_PERIOD, mode2_cfg, 0); + break; + } + + return tlv493d_write_all_regs(data); +} + +static s16 tlv493d_get_channel_data(u8 *b, enum tlv493d_channels ch) +{ + u16 val; + + switch (ch) { + case TLV493D_AXIS_X: + val = FIELD_GET(TLV493D_BX_MAG_X_AXIS_MSB, b[TLV493D_RD_REG_BX]) << 4 | + FIELD_GET(TLV493D_BX2_MAG_X_AXIS_LSB, b[TLV493D_RD_REG_BX2]); + break; + case TLV493D_AXIS_Y: + val = FIELD_GET(TLV493D_BY_MAG_Y_AXIS_MSB, b[TLV493D_RD_REG_BY]) << 4 | + FIELD_GET(TLV493D_BX2_MAG_Y_AXIS_LSB, b[TLV493D_RD_REG_BX2]); + break; + case TLV493D_AXIS_Z: + val = FIELD_GET(TLV493D_BZ_MAG_Z_AXIS_MSB, b[TLV493D_RD_REG_BZ]) << 4 | + FIELD_GET(TLV493D_BZ2_MAG_Z_AXIS_LSB, b[TLV493D_RD_REG_BZ2]); + break; + case TLV493D_TEMPERATURE: + val = FIELD_GET(TLV493D_TEMP_TEMP_MSB, b[TLV493D_RD_REG_TEMP]) << 8 | + FIELD_GET(TLV493D_TEMP2_TEMP_LSB, b[TLV493D_RD_REG_TEMP2]); + break; + } + + return sign_extend32(val, 11); +} + +static int tlv493d_get_measurements(struct tlv493d_data *data, s16 *x, s16 *y, + s16 *z, s16 *t) +{ + u8 buff[7] = {}; + int err, ret; + struct device *dev = &data->client->dev; + u32 sleep_us = tlv493d_sample_rate_us[data->mode]; + + guard(mutex)(&data->lock); + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + /* + * Poll until data is valid. + * For a valid data TLV493D_TEMP_CHANNEL bit of TLV493D_RD_REG_TEMP + * should be set to 0. The sampling time depends on the sensor mode. + * Poll 3x the time of the sampling time. + */ + ret = read_poll_timeout(i2c_master_recv, err, + err || !FIELD_GET(TLV493D_TEMP_CHANNEL, buff[TLV493D_RD_REG_TEMP]), + sleep_us, 3 * sleep_us, false, data->client, buff, + ARRAY_SIZE(buff)); + if (ret) { + dev_err(dev, "i2c read poll timeout, error:%d\n", ret); + goto out_put_autosuspend; + } + if (err < 0) { + dev_err(dev, "i2c read data failed, error:%d\n", err); + ret = err; + goto out_put_autosuspend; + } + + *x = tlv493d_get_channel_data(buff, TLV493D_AXIS_X); + *y = tlv493d_get_channel_data(buff, TLV493D_AXIS_Y); + *z = tlv493d_get_channel_data(buff, TLV493D_AXIS_Z); + *t = tlv493d_get_channel_data(buff, TLV493D_TEMPERATURE); + +out_put_autosuspend: + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int tlv493d_init(struct tlv493d_data *data) +{ + int ret; + u8 buff[TLV493D_RD_REG_MAX]; + struct device *dev = &data->client->dev; + + /* + * The sensor initialization requires below steps to be followed, + * 1. Power-up sensor. + * 2. Read and store read-registers map (0x0-0x9). + * 3. Copy values from read reserved registers to write reserved fields + * (0x0-0x3). + * 4. Set operating mode. + * 5. Write to all registers. + */ + ret = i2c_master_recv(data->client, buff, ARRAY_SIZE(buff)); + if (ret < 0) + return dev_err_probe(dev, ret, "i2c read failed\n"); + + /* Write register 0x0 is reserved. Does not require to be updated.*/ + data->wr_regs[0] = 0; + data->wr_regs[1] = buff[TLV493D_RD_REG_RES1] & TLV493D_RD_REG_RES1_WR_MASK; + data->wr_regs[2] = buff[TLV493D_RD_REG_RES2] & TLV493D_RD_REG_RES2_WR_MASK; + data->wr_regs[3] = buff[TLV493D_RD_REG_RES3] & TLV493D_RD_REG_RES3_WR_MASK; + + ret = tlv493d_set_operating_mode(data, data->mode); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to set operating mode\n"); + + return 0; +} + +static int tlv493d_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, + int *val2, long mask) +{ + struct tlv493d_data *data = iio_priv(indio_dev); + s16 x, y, z, t; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = tlv493d_get_measurements(data, &x, &y, &z, &t); + if (ret) + return ret; + + switch (chan->address) { + case TLV493D_AXIS_X: + *val = x; + return IIO_VAL_INT; + case TLV493D_AXIS_Y: + *val = y; + return IIO_VAL_INT; + case TLV493D_AXIS_Z: + *val = z; + return IIO_VAL_INT; + case TLV493D_TEMPERATURE: + *val = t; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_MAGN: + /* + * Magnetic field scale: 0.0098 mTesla (i.e. 9.8 µT) + * Magnetic field in Gauss: mT * 10 = 0.098. + */ + *val = 98; + *val2 = 1000; + return IIO_VAL_FRACTIONAL; + case IIO_TEMP: + /* + * Temperature scale: 1.1 °C per LSB, expressed as 1100 m°C + * Returned as integer for IIO core to apply: + * temp = (raw + offset) * scale + */ + *val = 1100; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + /* + * Temperature offset includes sensor-specific raw offset + * plus compensation for +25°C bias in formula. + * offset = -raw_offset + (25000 / 1100) + * -340 + 22.72 = -317.28 + */ + *val = -31728; + *val2 = 100; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static irqreturn_t tlv493d_trigger_handler(int irq, void *ptr) +{ + int ret; + s16 x, y, z, t; + struct iio_poll_func *pf = ptr; + struct iio_dev *indio_dev = pf->indio_dev; + struct tlv493d_data *data = iio_priv(indio_dev); + struct device *dev = &data->client->dev; + struct { + s16 channels[3]; + s16 temperature; + aligned_s64 timestamp; + } scan; + + ret = tlv493d_get_measurements(data, &x, &y, &z, &t); + if (ret) { + dev_err(dev, "failed to read sensor data\n"); + goto out_trigger_notify; + } + + scan.channels[0] = x; + scan.channels[1] = y; + scan.channels[2] = z; + scan.temperature = t; + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), pf->timestamp); + +out_trigger_notify: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +#define TLV493D_AXIS_CHANNEL(axis, index) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .address = index, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 12, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ + } + +static const struct iio_chan_spec tlv493d_channels[] = { + TLV493D_AXIS_CHANNEL(X, TLV493D_AXIS_X), + TLV493D_AXIS_CHANNEL(Y, TLV493D_AXIS_Y), + TLV493D_AXIS_CHANNEL(Z, TLV493D_AXIS_Z), + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = TLV493D_TEMPERATURE, + .scan_index = TLV493D_TEMPERATURE, + .scan_type = { + .sign = 's', + .realbits = 12, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const struct iio_info tlv493d_info = { + .read_raw = tlv493d_read_raw, +}; + +static const unsigned long tlv493d_scan_masks[] = { GENMASK(3, 0), 0 }; + +static int tlv493d_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct tlv493d_data *data; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + i2c_set_clientdata(client, indio_dev); + + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "failed to enable regulator\n"); + + /* + * Setting Sensor default operating mode to Master-Controlled mode since + * it performs measurement cycle only on-request and stays in Power-Down + * state until next cycle is initiated. + */ + data->mode = TLV493D_OP_MODE_MASTERCONTROLLED; + ret = tlv493d_init(data); + if (ret) + return dev_err_probe(dev, ret, "failed to initialize\n"); + + indio_dev->info = &tlv493d_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = client->name; + indio_dev->channels = tlv493d_channels; + indio_dev->num_channels = ARRAY_SIZE(tlv493d_channels); + indio_dev->available_scan_masks = tlv493d_scan_masks; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + tlv493d_trigger_handler, + NULL); + if (ret) + return dev_err_probe(dev, ret, "iio triggered buffer setup failed\n"); + + ret = pm_runtime_set_active(dev); + if (ret) + return ret; + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + pm_runtime_get_noresume(dev); + pm_runtime_set_autosuspend_delay(dev, 500); + pm_runtime_use_autosuspend(dev); + + pm_runtime_put_autosuspend(dev); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "iio device register failed\n"); + + return 0; +} + +static int tlv493d_runtime_suspend(struct device *dev) +{ + struct tlv493d_data *data = iio_priv(dev_get_drvdata(dev)); + + return tlv493d_set_operating_mode(data, TLV493D_OP_MODE_POWERDOWN); +} + +static int tlv493d_runtime_resume(struct device *dev) +{ + struct tlv493d_data *data = iio_priv(dev_get_drvdata(dev)); + + return tlv493d_set_operating_mode(data, data->mode); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(tlv493d_pm_ops, tlv493d_runtime_suspend, + tlv493d_runtime_resume, NULL); + +static const struct i2c_device_id tlv493d_id[] = { + { .name = "tlv493d" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tlv493d_id); + +static const struct of_device_id tlv493d_of_match[] = { + { .compatible = "infineon,tlv493d-a1b6" }, + { } +}; +MODULE_DEVICE_TABLE(of, tlv493d_of_match); + +static struct i2c_driver tlv493d_driver = { + .driver = { + .name = "tlv493d", + .of_match_table = tlv493d_of_match, + .pm = pm_ptr(&tlv493d_pm_ops), + }, + .probe = tlv493d_probe, + .id_table = tlv493d_id, +}; +module_i2c_driver(tlv493d_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Infineon TLV493D Low-Power 3D Magnetic Sensor"); +MODULE_AUTHOR("Dixit Parmar "); diff --git a/drivers/iio/magnetometer/tmag5273.c b/drivers/iio/magnetometer/tmag5273.c new file mode 100644 index 000000000..155294b66 --- /dev/null +++ b/drivers/iio/magnetometer/tmag5273.c @@ -0,0 +1,735 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for the TI TMAG5273 Low-Power Linear 3D Hall-Effect Sensor + * + * Copyright (C) 2022 WolfVision GmbH + * + * Author: Gerald Loacker + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define TMAG5273_DEVICE_CONFIG_1 0x00 +#define TMAG5273_DEVICE_CONFIG_2 0x01 +#define TMAG5273_SENSOR_CONFIG_1 0x02 +#define TMAG5273_SENSOR_CONFIG_2 0x03 +#define TMAG5273_X_THR_CONFIG 0x04 +#define TMAG5273_Y_THR_CONFIG 0x05 +#define TMAG5273_Z_THR_CONFIG 0x06 +#define TMAG5273_T_CONFIG 0x07 +#define TMAG5273_INT_CONFIG_1 0x08 +#define TMAG5273_MAG_GAIN_CONFIG 0x09 +#define TMAG5273_MAG_OFFSET_CONFIG_1 0x0A +#define TMAG5273_MAG_OFFSET_CONFIG_2 0x0B +#define TMAG5273_I2C_ADDRESS 0x0C +#define TMAG5273_DEVICE_ID 0x0D +#define TMAG5273_MANUFACTURER_ID_LSB 0x0E +#define TMAG5273_MANUFACTURER_ID_MSB 0x0F +#define TMAG5273_T_MSB_RESULT 0x10 +#define TMAG5273_T_LSB_RESULT 0x11 +#define TMAG5273_X_MSB_RESULT 0x12 +#define TMAG5273_X_LSB_RESULT 0x13 +#define TMAG5273_Y_MSB_RESULT 0x14 +#define TMAG5273_Y_LSB_RESULT 0x15 +#define TMAG5273_Z_MSB_RESULT 0x16 +#define TMAG5273_Z_LSB_RESULT 0x17 +#define TMAG5273_CONV_STATUS 0x18 +#define TMAG5273_ANGLE_RESULT_MSB 0x19 +#define TMAG5273_ANGLE_RESULT_LSB 0x1A +#define TMAG5273_MAGNITUDE_RESULT 0x1B +#define TMAG5273_DEVICE_STATUS 0x1C +#define TMAG5273_MAX_REG TMAG5273_DEVICE_STATUS + +#define TMAG5273_AUTOSLEEP_DELAY_MS 5000 +#define TMAG5273_MAX_AVERAGE 32 + +/* + * bits in the TMAG5273_MANUFACTURER_ID_LSB / MSB register + * 16-bit unique manufacturer ID 0x49 / 0x54 = "TI" + */ +#define TMAG5273_MANUFACTURER_ID 0x5449 + +/* bits in the TMAG5273_DEVICE_CONFIG_1 register */ +#define TMAG5273_AVG_MODE_MASK GENMASK(4, 2) +#define TMAG5273_AVG_1_MODE FIELD_PREP(TMAG5273_AVG_MODE_MASK, 0) +#define TMAG5273_AVG_2_MODE FIELD_PREP(TMAG5273_AVG_MODE_MASK, 1) +#define TMAG5273_AVG_4_MODE FIELD_PREP(TMAG5273_AVG_MODE_MASK, 2) +#define TMAG5273_AVG_8_MODE FIELD_PREP(TMAG5273_AVG_MODE_MASK, 3) +#define TMAG5273_AVG_16_MODE FIELD_PREP(TMAG5273_AVG_MODE_MASK, 4) +#define TMAG5273_AVG_32_MODE FIELD_PREP(TMAG5273_AVG_MODE_MASK, 5) + +/* bits in the TMAG5273_DEVICE_CONFIG_2 register */ +#define TMAG5273_OP_MODE_MASK GENMASK(1, 0) +#define TMAG5273_OP_MODE_STANDBY FIELD_PREP(TMAG5273_OP_MODE_MASK, 0) +#define TMAG5273_OP_MODE_SLEEP FIELD_PREP(TMAG5273_OP_MODE_MASK, 1) +#define TMAG5273_OP_MODE_CONT FIELD_PREP(TMAG5273_OP_MODE_MASK, 2) +#define TMAG5273_OP_MODE_WAKEUP FIELD_PREP(TMAG5273_OP_MODE_MASK, 3) + +/* bits in the TMAG5273_SENSOR_CONFIG_1 register */ +#define TMAG5273_MAG_CH_EN_MASK GENMASK(7, 4) +#define TMAG5273_MAG_CH_EN_X_Y_Z 7 + +/* bits in the TMAG5273_SENSOR_CONFIG_2 register */ +#define TMAG5273_Z_RANGE_MASK BIT(0) +#define TMAG5273_X_Y_RANGE_MASK BIT(1) +#define TMAG5273_ANGLE_EN_MASK GENMASK(3, 2) +#define TMAG5273_ANGLE_EN_OFF 0 +#define TMAG5273_ANGLE_EN_X_Y 1 +#define TMAG5273_ANGLE_EN_Y_Z 2 +#define TMAG5273_ANGLE_EN_X_Z 3 + +/* bits in the TMAG5273_T_CONFIG register */ +#define TMAG5273_T_CH_EN BIT(0) + +/* bits in the TMAG5273_DEVICE_ID register */ +#define TMAG5273_VERSION_MASK GENMASK(1, 0) + +/* bits in the TMAG5273_CONV_STATUS register */ +#define TMAG5273_CONV_STATUS_COMPLETE BIT(0) + +enum tmag5273_channels { + TEMPERATURE = 0, + AXIS_X, + AXIS_Y, + AXIS_Z, + ANGLE, + MAGNITUDE, +}; + +enum tmag5273_scale_index { + MAGN_RANGE_LOW = 0, + MAGN_RANGE_HIGH, + MAGN_RANGE_NUM +}; + +/* state container for the TMAG5273 driver */ +struct tmag5273_data { + struct device *dev; + unsigned int devid; + unsigned int version; + char name[16]; + unsigned int conv_avg; + enum tmag5273_scale_index scale_index; + unsigned int angle_measurement; + struct regmap *map; + + /* + * Locks the sensor for exclusive use during a measurement (which + * involves several register transactions so the regmap lock is not + * enough) so that measurements get serialized in a + * first-come-first-serve manner. + */ + struct mutex lock; +}; + +static const char *const tmag5273_angle_names[] = { "off", "x-y", "y-z", "x-z" }; + +/* + * Averaging enables additional sampling of the sensor data to reduce the noise + * effect, but also increases conversion time. + */ +static const unsigned int tmag5273_avg_table[] = { + 1, 2, 4, 8, 16, 32, +}; + +/* + * Magnetic resolution in Gauss for different TMAG5273 versions. + * Scale[Gauss] = Range[mT] * 1000 / 2^15 * 10, (1 mT = 10 Gauss) + * Only version 1 and 2 are valid, version 0 and 3 are reserved. + */ +static const struct iio_val_int_plus_micro tmag5273_scale[][MAGN_RANGE_NUM] = { + { { 0, 0 }, { 0, 0 } }, + { { 0, 12200 }, { 0, 24400 } }, + { { 0, 40600 }, { 0, 81200 } }, + { { 0, 0 }, { 0, 0 } }, +}; + +static int tmag5273_get_measure(struct tmag5273_data *data, s16 *t, s16 *x, + s16 *y, s16 *z, u16 *angle, u16 *magnitude) +{ + unsigned int status, val; + __be16 reg_data[4]; + int ret; + + mutex_lock(&data->lock); + + /* + * Max. conversion time is 2425 us in 32x averaging mode for all three + * channels. Since we are in continuous measurement mode, a measurement + * may already be there, so poll for completed measurement with + * timeout. + */ + ret = regmap_read_poll_timeout(data->map, TMAG5273_CONV_STATUS, status, + status & TMAG5273_CONV_STATUS_COMPLETE, + 100, 10000); + if (ret) { + dev_err(data->dev, "timeout waiting for measurement\n"); + goto out_unlock; + } + + ret = regmap_bulk_read(data->map, TMAG5273_T_MSB_RESULT, reg_data, + sizeof(reg_data)); + if (ret) + goto out_unlock; + *t = be16_to_cpu(reg_data[0]); + *x = be16_to_cpu(reg_data[1]); + *y = be16_to_cpu(reg_data[2]); + *z = be16_to_cpu(reg_data[3]); + + ret = regmap_bulk_read(data->map, TMAG5273_ANGLE_RESULT_MSB, + ®_data[0], sizeof(reg_data[0])); + if (ret) + goto out_unlock; + /* + * angle has 9 bits integer value and 4 bits fractional part + * 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + * 0 0 0 a a a a a a a a a f f f f + */ + *angle = be16_to_cpu(reg_data[0]); + + ret = regmap_read(data->map, TMAG5273_MAGNITUDE_RESULT, &val); + if (ret < 0) + goto out_unlock; + *magnitude = val; + +out_unlock: + mutex_unlock(&data->lock); + return ret; +} + +static int tmag5273_write_osr(struct tmag5273_data *data, int val) +{ + int i; + + if (val == data->conv_avg) + return 0; + + for (i = 0; i < ARRAY_SIZE(tmag5273_avg_table); i++) { + if (tmag5273_avg_table[i] == val) + break; + } + if (i == ARRAY_SIZE(tmag5273_avg_table)) + return -EINVAL; + data->conv_avg = val; + + return regmap_update_bits(data->map, TMAG5273_DEVICE_CONFIG_1, + TMAG5273_AVG_MODE_MASK, + FIELD_PREP(TMAG5273_AVG_MODE_MASK, i)); +} + +static int tmag5273_write_scale(struct tmag5273_data *data, int scale_micro) +{ + u32 value; + int i; + + for (i = 0; i < ARRAY_SIZE(tmag5273_scale[0]); i++) { + if (tmag5273_scale[data->version][i].micro == scale_micro) + break; + } + if (i == ARRAY_SIZE(tmag5273_scale[0])) + return -EINVAL; + data->scale_index = i; + + if (data->scale_index == MAGN_RANGE_LOW) + value = 0; + else + value = TMAG5273_Z_RANGE_MASK | TMAG5273_X_Y_RANGE_MASK; + + return regmap_update_bits(data->map, TMAG5273_SENSOR_CONFIG_2, + TMAG5273_Z_RANGE_MASK | TMAG5273_X_Y_RANGE_MASK, value); +} + +static int tmag5273_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct tmag5273_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = tmag5273_avg_table; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(tmag5273_avg_table); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_MAGN: + *type = IIO_VAL_INT_PLUS_MICRO; + *vals = (int *)tmag5273_scale[data->version]; + *length = ARRAY_SIZE(tmag5273_scale[data->version]) * + MAGN_RANGE_NUM; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int tmag5273_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, + int *val2, long mask) +{ + struct tmag5273_data *data = iio_priv(indio_dev); + s16 t, x, y, z; + u16 angle, magnitude; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = pm_runtime_resume_and_get(data->dev); + if (ret < 0) + return ret; + + ret = tmag5273_get_measure(data, &t, &x, &y, &z, &angle, &magnitude); + + pm_runtime_put_autosuspend(data->dev); + + if (ret) + return ret; + + switch (chan->address) { + case TEMPERATURE: + *val = t; + return IIO_VAL_INT; + case AXIS_X: + *val = x; + return IIO_VAL_INT; + case AXIS_Y: + *val = y; + return IIO_VAL_INT; + case AXIS_Z: + *val = z; + return IIO_VAL_INT; + case ANGLE: + *val = angle; + return IIO_VAL_INT; + case MAGNITUDE: + *val = magnitude; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + /* + * Convert device specific value to millicelsius. + * Resolution from the sensor is 60.1 LSB/celsius and + * the reference value at 25 celsius is 17508 LSBs. + */ + *val = 10000; + *val2 = 601; + return IIO_VAL_FRACTIONAL; + case IIO_MAGN: + /* Magnetic resolution in uT */ + *val = 0; + *val2 = tmag5273_scale[data->version] + [data->scale_index].micro; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ANGL: + /* + * Angle is in degrees and has four fractional bits, + * therefore use 1/16 * pi/180 to convert to radians. + */ + *val = 1000; + *val2 = 916732; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + *val = -16005; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = data->conv_avg; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int tmag5273_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct tmag5273_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return tmag5273_write_osr(data, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_MAGN: + if (val) + return -EINVAL; + return tmag5273_write_scale(data, val2); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +#define TMAG5273_AXIS_CHANNEL(axis, index) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .address = index, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ + } + +static const struct iio_chan_spec tmag5273_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = TEMPERATURE, + .scan_index = TEMPERATURE, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + TMAG5273_AXIS_CHANNEL(X, AXIS_X), + TMAG5273_AXIS_CHANNEL(Y, AXIS_Y), + TMAG5273_AXIS_CHANNEL(Z, AXIS_Z), + { + .type = IIO_ANGL, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .address = ANGLE, + .scan_index = ANGLE, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_DISTANCE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .address = MAGNITUDE, + .scan_index = MAGNITUDE, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(6), +}; + +static const struct iio_info tmag5273_info = { + .read_avail = tmag5273_read_avail, + .read_raw = tmag5273_read_raw, + .write_raw = tmag5273_write_raw, +}; + +static bool tmag5273_volatile_reg(struct device *dev, unsigned int reg) +{ + return reg >= TMAG5273_T_MSB_RESULT && reg <= TMAG5273_MAGNITUDE_RESULT; +} + +static const struct regmap_config tmag5273_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = TMAG5273_MAX_REG, + .volatile_reg = tmag5273_volatile_reg, +}; + +static int tmag5273_set_operating_mode(struct tmag5273_data *data, + unsigned int val) +{ + return regmap_write(data->map, TMAG5273_DEVICE_CONFIG_2, val); +} + +static void tmag5273_read_device_property(struct tmag5273_data *data) +{ + struct device *dev = data->dev; + int ret; + + data->angle_measurement = TMAG5273_ANGLE_EN_X_Y; + + ret = device_property_match_property_string(dev, "ti,angle-measurement", + tmag5273_angle_names, + ARRAY_SIZE(tmag5273_angle_names)); + if (ret >= 0) + data->angle_measurement = ret; +} + +static void tmag5273_wake_up(struct tmag5273_data *data) +{ + int val; + + /* Wake up the chip by sending a dummy I2C command */ + regmap_read(data->map, TMAG5273_DEVICE_ID, &val); + /* + * Time to go to stand-by mode from sleep mode is 50us + * typically, during this time no I2C access is possible. + */ + usleep_range(80, 200); +} + +static int tmag5273_chip_init(struct tmag5273_data *data) +{ + int ret; + + ret = regmap_write(data->map, TMAG5273_DEVICE_CONFIG_1, + TMAG5273_AVG_32_MODE); + if (ret) + return ret; + data->conv_avg = 32; + + ret = regmap_write(data->map, TMAG5273_DEVICE_CONFIG_2, + TMAG5273_OP_MODE_CONT); + if (ret) + return ret; + + ret = regmap_write(data->map, TMAG5273_SENSOR_CONFIG_1, + FIELD_PREP(TMAG5273_MAG_CH_EN_MASK, + TMAG5273_MAG_CH_EN_X_Y_Z)); + if (ret) + return ret; + + ret = regmap_write(data->map, TMAG5273_SENSOR_CONFIG_2, + FIELD_PREP(TMAG5273_ANGLE_EN_MASK, + data->angle_measurement)); + if (ret) + return ret; + data->scale_index = MAGN_RANGE_LOW; + + return regmap_write(data->map, TMAG5273_T_CONFIG, TMAG5273_T_CH_EN); +} + +static int tmag5273_check_device_id(struct tmag5273_data *data) +{ + __le16 devid; + int val, ret; + + ret = regmap_read(data->map, TMAG5273_DEVICE_ID, &val); + if (ret) + return dev_err_probe(data->dev, ret, "failed to power on device\n"); + data->version = FIELD_PREP(TMAG5273_VERSION_MASK, val); + + ret = regmap_bulk_read(data->map, TMAG5273_MANUFACTURER_ID_LSB, &devid, + sizeof(devid)); + if (ret) + return dev_err_probe(data->dev, ret, "failed to read device ID\n"); + data->devid = le16_to_cpu(devid); + + switch (data->devid) { + case TMAG5273_MANUFACTURER_ID: + /* + * The device name matches the orderable part number. 'x' stands + * for A, B, C or D devices, which have different I2C addresses. + * Versions 1 or 2 (0 and 3 is reserved) stands for different + * magnetic strengths. + */ + snprintf(data->name, sizeof(data->name), "tmag5273x%1u", data->version); + if (data->version < 1 || data->version > 2) + dev_warn(data->dev, "Unsupported device %s\n", data->name); + return 0; + default: + /* + * Only print warning in case of unknown device ID to allow + * fallback compatible in device tree. + */ + dev_warn(data->dev, "Unknown device ID 0x%x\n", data->devid); + return 0; + } +} + +static void tmag5273_power_down(void *data) +{ + tmag5273_set_operating_mode(data, TMAG5273_OP_MODE_SLEEP); +} + +static int tmag5273_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct tmag5273_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->dev = dev; + i2c_set_clientdata(i2c, indio_dev); + + data->map = devm_regmap_init_i2c(i2c, &tmag5273_regmap_config); + if (IS_ERR(data->map)) + return dev_err_probe(dev, PTR_ERR(data->map), + "failed to allocate register map\n"); + + mutex_init(&data->lock); + + ret = devm_regulator_get_enable(dev, "vcc"); + if (ret) + return dev_err_probe(dev, ret, "failed to enable regulator\n"); + + tmag5273_wake_up(data); + + ret = tmag5273_check_device_id(data); + if (ret) + return ret; + + ret = tmag5273_set_operating_mode(data, TMAG5273_OP_MODE_CONT); + if (ret) + return dev_err_probe(dev, ret, "failed to power on device\n"); + + /* + * Register powerdown deferred callback which suspends the chip + * after module unloaded. + * + * TMAG5273 should be in SUSPEND mode in the two cases: + * 1) When driver is loaded, but we do not have any data or + * configuration requests to it (we are solving it using + * autosuspend feature). + * 2) When driver is unloaded and device is not used (devm action is + * used in this case). + */ + ret = devm_add_action_or_reset(dev, tmag5273_power_down, data); + if (ret) + return ret; + + ret = pm_runtime_set_active(dev); + if (ret < 0) + return ret; + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + pm_runtime_get_noresume(dev); + pm_runtime_set_autosuspend_delay(dev, TMAG5273_AUTOSLEEP_DELAY_MS); + pm_runtime_use_autosuspend(dev); + + tmag5273_read_device_property(data); + + ret = tmag5273_chip_init(data); + if (ret) + return dev_err_probe(dev, ret, "failed to init device\n"); + + indio_dev->info = &tmag5273_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = data->name; + indio_dev->channels = tmag5273_channels; + indio_dev->num_channels = ARRAY_SIZE(tmag5273_channels); + + pm_runtime_put_autosuspend(dev); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "device register failed\n"); + + return 0; +} + +static int tmag5273_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tmag5273_data *data = iio_priv(indio_dev); + int ret; + + ret = tmag5273_set_operating_mode(data, TMAG5273_OP_MODE_SLEEP); + if (ret) + dev_err(dev, "failed to power off device (%pe)\n", ERR_PTR(ret)); + + return ret; +} + +static int tmag5273_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tmag5273_data *data = iio_priv(indio_dev); + int ret; + + tmag5273_wake_up(data); + + ret = tmag5273_set_operating_mode(data, TMAG5273_OP_MODE_CONT); + if (ret) + dev_err(dev, "failed to power on device (%pe)\n", ERR_PTR(ret)); + + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(tmag5273_pm_ops, + tmag5273_runtime_suspend, tmag5273_runtime_resume, + NULL); + +static const struct i2c_device_id tmag5273_id[] = { + { .name = "tmag5273" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tmag5273_id); + +static const struct of_device_id tmag5273_of_match[] = { + { .compatible = "ti,tmag5273" }, + { } +}; +MODULE_DEVICE_TABLE(of, tmag5273_of_match); + +static struct i2c_driver tmag5273_driver = { + .driver = { + .name = "tmag5273", + .of_match_table = tmag5273_of_match, + .pm = pm_ptr(&tmag5273_pm_ops), + }, + .probe = tmag5273_probe, + .id_table = tmag5273_id, +}; +module_i2c_driver(tmag5273_driver); + +MODULE_DESCRIPTION("TI TMAG5273 Low-Power Linear 3D Hall-Effect Sensor driver"); +MODULE_AUTHOR("Gerald Loacker "); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/magnetometer/yamaha-yas530.c b/drivers/iio/magnetometer/yamaha-yas530.c new file mode 100644 index 000000000..f9afe9a59 --- /dev/null +++ b/drivers/iio/magnetometer/yamaha-yas530.c @@ -0,0 +1,1620 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for the Yamaha YAS magnetic sensors, often used in Samsung + * mobile phones. While all are not yet handled because of lacking + * hardware, expand this driver to handle the different variants: + * + * YAS530 MS-3E (2011 Samsung Galaxy S Advance) + * YAS532 MS-3R (2011 Samsung Galaxy S4) + * YAS533 MS-3F (Vivo 1633, 1707, V3, Y21L) + * (YAS534 is a magnetic switch, not handled) + * YAS535 MS-6C + * YAS536 MS-3W + * YAS537 MS-3T (2015 Samsung Galaxy S6, Note 5, Galaxy S7) + * YAS539 MS-3S (2018 Samsung Galaxy A7 SM-A750FN) + * + * Code functions found in the MPU3050 YAS530 and YAS532 drivers + * named "inv_compass" in the Tegra Android kernel tree. + * Copyright (C) 2012 InvenSense Corporation + * + * Code functions for YAS537 based on Yamaha Android kernel driver. + * Copyright (c) 2014 Yamaha Corporation + * + * Author: Linus Walleij + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +/* Commonly used registers */ +#define YAS5XX_DEVICE_ID 0x80 +#define YAS5XX_MEASURE_DATA 0xB0 + +/* These registers are used by YAS530, YAS532 and YAS533 */ +#define YAS530_ACTUATE_INIT_COIL 0x81 +#define YAS530_MEASURE 0x82 +#define YAS530_CONFIG 0x83 +#define YAS530_MEASURE_INTERVAL 0x84 +#define YAS530_OFFSET_X 0x85 /* [-31 .. 31] */ +#define YAS530_OFFSET_Y1 0x86 /* [-31 .. 31] */ +#define YAS530_OFFSET_Y2 0x87 /* [-31 .. 31] */ +#define YAS530_TEST1 0x88 +#define YAS530_TEST2 0x89 +#define YAS530_CAL 0x90 + +/* Registers used by YAS537 */ +#define YAS537_MEASURE 0x81 /* Originally YAS537_REG_CMDR */ +#define YAS537_CONFIG 0x82 /* Originally YAS537_REG_CONFR */ +#define YAS537_MEASURE_INTERVAL 0x83 /* Originally YAS537_REG_INTRVLR */ +#define YAS537_OFFSET_X 0x84 /* Originally YAS537_REG_OXR */ +#define YAS537_OFFSET_Y1 0x85 /* Originally YAS537_REG_OY1R */ +#define YAS537_OFFSET_Y2 0x86 /* Originally YAS537_REG_OY2R */ +#define YAS537_AVR 0x87 +#define YAS537_HCK 0x88 +#define YAS537_LCK 0x89 +#define YAS537_SRST 0x90 +#define YAS537_ADCCAL 0x91 +#define YAS537_MTC 0x93 +#define YAS537_OC 0x9E +#define YAS537_TRM 0x9F +#define YAS537_CAL 0xC0 + +/* Bits in the YAS5xx config register */ +#define YAS5XX_CONFIG_INTON BIT(0) /* Interrupt on? */ +#define YAS5XX_CONFIG_INTHACT BIT(1) /* Interrupt active high? */ +#define YAS5XX_CONFIG_CCK_MASK GENMASK(4, 2) +#define YAS5XX_CONFIG_CCK_SHIFT 2 + +/* Bits in the measure command register */ +#define YAS5XX_MEASURE_START BIT(0) +#define YAS5XX_MEASURE_LDTC BIT(1) +#define YAS5XX_MEASURE_FORS BIT(2) +#define YAS5XX_MEASURE_DLYMES BIT(4) +#define YAS5XX_MEASURE_CONT BIT(5) + +/* Bits in the measure data register */ +#define YAS5XX_MEASURE_DATA_BUSY BIT(7) + +#define YAS530_DEVICE_ID 0x01 /* YAS530 (MS-3E) */ +#define YAS530_VERSION_A 0 /* YAS530 (MS-3E A) */ +#define YAS530_VERSION_B 1 /* YAS530B (MS-3E B) */ +#define YAS530_VERSION_A_COEF 380 +#define YAS530_VERSION_B_COEF 550 +#define YAS530_DATA_BITS 12 +#define YAS530_DATA_CENTER BIT(YAS530_DATA_BITS - 1) +#define YAS530_DATA_OVERFLOW (BIT(YAS530_DATA_BITS) - 1) + +#define YAS532_DEVICE_ID 0x02 /* YAS532/YAS533 (MS-3R/F) */ +#define YAS532_VERSION_AB 0 /* YAS532/533 AB (MS-3R/F AB) */ +#define YAS532_VERSION_AC 1 /* YAS532/533 AC (MS-3R/F AC) */ +#define YAS532_VERSION_AB_COEF 1800 +#define YAS532_VERSION_AC_COEF_X 850 +#define YAS532_VERSION_AC_COEF_Y1 750 +#define YAS532_VERSION_AC_COEF_Y2 750 +#define YAS532_DATA_BITS 13 +#define YAS532_DATA_CENTER BIT(YAS532_DATA_BITS - 1) +#define YAS532_DATA_OVERFLOW (BIT(YAS532_DATA_BITS) - 1) + +#define YAS537_DEVICE_ID 0x07 /* YAS537 (MS-3T) */ +#define YAS537_VERSION_0 0 /* Version naming unknown */ +#define YAS537_VERSION_1 1 /* Version naming unknown */ +#define YAS537_MAG_AVERAGE_32_MASK GENMASK(6, 4) +#define YAS537_MEASURE_TIME_WORST_US 1500 +#define YAS537_DEFAULT_SENSOR_DELAY_MS 50 +#define YAS537_MAG_RCOIL_TIME_US 65 +#define YAS537_MTC3_MASK_PREP GENMASK(7, 0) +#define YAS537_MTC3_MASK_GET GENMASK(7, 5) +#define YAS537_MTC3_ADD_BIT BIT(4) +#define YAS537_HCK_MASK_PREP GENMASK(4, 0) +#define YAS537_HCK_MASK_GET GENMASK(7, 4) +#define YAS537_LCK_MASK_PREP GENMASK(4, 0) +#define YAS537_LCK_MASK_GET GENMASK(3, 0) +#define YAS537_OC_MASK_GET GENMASK(5, 0) + +/* Turn off device regulators etc after 5 seconds of inactivity */ +#define YAS5XX_AUTOSUSPEND_DELAY_MS 5000 + +enum chip_ids { + yas530, + yas532, + yas533, + yas537, +}; + +static const int yas530_volatile_reg[] = { + YAS530_ACTUATE_INIT_COIL, + YAS530_MEASURE, +}; + +static const int yas537_volatile_reg[] = { + YAS537_MEASURE, +}; + +struct yas5xx_calibration { + /* Linearization calibration x, y1, y2 */ + s32 r[3]; + u32 f[3]; + /* Temperature compensation calibration */ + s16 Cx, Cy1, Cy2; + /* Misc calibration coefficients */ + s8 a2, a3, a4, a6, a7, a8; + s16 a5, a9; + u8 k; + /* clock divider */ + u8 dck; +}; + +struct yas5xx; + +/** + * struct yas5xx_chip_info - device-specific data and function pointers + * @devid: device ID number + * @product_label: product label used in Linux + * @product_name: product name of the YAS variant + * @version_names: version letters or namings + * @volatile_reg: device-specific volatile registers + * @volatile_reg_qty: quantity of device-specific volatile registers + * @scaling_val2: scaling value for IIO_CHAN_INFO_SCALE + * @t_ref: number of counts at reference temperature 20 °C + * @min_temp_x10: starting point of temperature counting in 1/10:s degrees Celsius + * @get_measure: function pointer to get a measurement + * @get_calibration_data: function pointer to get calibration data + * @dump_calibration: function pointer to dump calibration for debugging + * @measure_offsets: function pointer to measure the offsets + * @power_on: function pointer to power-on procedure + * + * The "t_ref" value for YAS532/533 is known from the Android driver. + * For YAS530 and YAS537 it was approximately measured. + * + * The temperatures "min_temp_x10" are derived from the temperature resolutions + * given in the data sheets. + */ +struct yas5xx_chip_info { + unsigned int devid; + const char *product_label; + const char *product_name; + const char *version_names[2]; + const int *volatile_reg; + int volatile_reg_qty; + u32 scaling_val2; + u16 t_ref; + s16 min_temp_x10; + int (*get_measure)(struct yas5xx *yas5xx, s32 *to, s32 *xo, s32 *yo, s32 *zo); + int (*get_calibration_data)(struct yas5xx *yas5xx); + void (*dump_calibration)(struct yas5xx *yas5xx); + int (*measure_offsets)(struct yas5xx *yas5xx); + int (*power_on)(struct yas5xx *yas5xx); +}; + +/** + * struct yas5xx - state container for the YAS5xx driver + * @dev: parent device pointer + * @chip_info: device-specific data and function pointers + * @version: device version + * @calibration: calibration settings from the OTP storage + * @hard_offsets: offsets for each axis measured with initcoil actuated + * @orientation: mounting matrix, flipped axis etc + * @map: regmap to access the YAX5xx registers over I2C + * @regs: the vdd and vddio power regulators + * @reset: optional GPIO line used for handling RESET + * @lock: locks the magnetometer for exclusive use during a measurement (which + * involves several register transactions so the regmap lock is not enough) + * so that measurements get serialized in a first-come-first serve manner + * @scan: naturally aligned measurements + */ +struct yas5xx { + struct device *dev; + const struct yas5xx_chip_info *chip_info; + unsigned int version; + struct yas5xx_calibration calibration; + s8 hard_offsets[3]; + struct iio_mount_matrix orientation; + struct regmap *map; + struct regulator_bulk_data regs[2]; + struct gpio_desc *reset; + struct mutex lock; + /* + * The scanout is 4 x 32 bits in CPU endianness. + * Ensure timestamp is naturally aligned + */ + struct { + s32 channels[4]; + aligned_s64 ts; + } scan; +}; + +/* On YAS530 the x, y1 and y2 values are 12 bits */ +static u16 yas530_extract_axis(u8 *data) +{ + u16 val; + + /* + * These are the bits used in a 16bit word: + * 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + * x x x x x x x x x x x x + */ + val = get_unaligned_be16(&data[0]); + val = FIELD_GET(GENMASK(14, 3), val); + return val; +} + +/* On YAS532 the x, y1 and y2 values are 13 bits */ +static u16 yas532_extract_axis(u8 *data) +{ + u16 val; + + /* + * These are the bits used in a 16bit word: + * 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + * x x x x x x x x x x x x x + */ + val = get_unaligned_be16(&data[0]); + val = FIELD_GET(GENMASK(14, 2), val); + return val; +} + +/** + * yas530_measure() - Make a measure from the hardware + * @yas5xx: The device state + * @t: the raw temperature measurement + * @x: the raw x axis measurement + * @y1: the y1 axis measurement + * @y2: the y2 axis measurement + * @return: 0 on success or error code + * + * Used by YAS530, YAS532 and YAS533. + */ +static int yas530_measure(struct yas5xx *yas5xx, u16 *t, u16 *x, u16 *y1, u16 *y2) +{ + const struct yas5xx_chip_info *ci = yas5xx->chip_info; + unsigned int busy; + u8 data[8]; + int ret; + u16 val; + + mutex_lock(&yas5xx->lock); + ret = regmap_write(yas5xx->map, YAS530_MEASURE, YAS5XX_MEASURE_START); + if (ret < 0) + goto out_unlock; + + /* + * Typical time to measure 1500 us, max 2000 us so wait min 500 us + * and at most 20000 us (one magnitude more than the datsheet max) + * before timeout. + */ + ret = regmap_read_poll_timeout(yas5xx->map, YAS5XX_MEASURE_DATA, busy, + !(busy & YAS5XX_MEASURE_DATA_BUSY), + 500, 20000); + if (ret) { + dev_err(yas5xx->dev, "timeout waiting for measurement\n"); + goto out_unlock; + } + + ret = regmap_bulk_read(yas5xx->map, YAS5XX_MEASURE_DATA, + data, sizeof(data)); + if (ret) + goto out_unlock; + + mutex_unlock(&yas5xx->lock); + + switch (ci->devid) { + case YAS530_DEVICE_ID: + /* + * The t value is 9 bits in big endian format + * These are the bits used in a 16bit word: + * 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + * x x x x x x x x x + */ + val = get_unaligned_be16(&data[0]); + val = FIELD_GET(GENMASK(14, 6), val); + *t = val; + *x = yas530_extract_axis(&data[2]); + *y1 = yas530_extract_axis(&data[4]); + *y2 = yas530_extract_axis(&data[6]); + break; + case YAS532_DEVICE_ID: + /* + * The t value is 10 bits in big endian format + * These are the bits used in a 16bit word: + * 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + * x x x x x x x x x x + */ + val = get_unaligned_be16(&data[0]); + val = FIELD_GET(GENMASK(14, 5), val); + *t = val; + *x = yas532_extract_axis(&data[2]); + *y1 = yas532_extract_axis(&data[4]); + *y2 = yas532_extract_axis(&data[6]); + break; + default: + dev_err(yas5xx->dev, "unknown data format\n"); + ret = -EINVAL; + break; + } + + return ret; + +out_unlock: + mutex_unlock(&yas5xx->lock); + return ret; +} + +/** + * yas537_measure() - Make a measure from the hardware + * @yas5xx: The device state + * @t: the raw temperature measurement + * @x: the raw x axis measurement + * @y1: the y1 axis measurement + * @y2: the y2 axis measurement + * @return: 0 on success or error code + */ +static int yas537_measure(struct yas5xx *yas5xx, u16 *t, u16 *x, u16 *y1, u16 *y2) +{ + struct yas5xx_calibration *c = &yas5xx->calibration; + unsigned int busy; + u8 data[8]; + u16 xy1y2[3]; + s32 h[3], s[3]; + int half_range = BIT(13); + int i, ret; + + mutex_lock(&yas5xx->lock); + + /* Contrary to YAS530/532, also a "cont" bit is set, meaning unknown */ + ret = regmap_write(yas5xx->map, YAS537_MEASURE, YAS5XX_MEASURE_START | + YAS5XX_MEASURE_CONT); + if (ret < 0) + goto out_unlock; + + /* Use same timeout like YAS530/532 but the bit is in data row 2 */ + ret = regmap_read_poll_timeout(yas5xx->map, YAS5XX_MEASURE_DATA + 2, busy, + !(busy & YAS5XX_MEASURE_DATA_BUSY), + 500, 20000); + if (ret) { + dev_err(yas5xx->dev, "timeout waiting for measurement\n"); + goto out_unlock; + } + + ret = regmap_bulk_read(yas5xx->map, YAS5XX_MEASURE_DATA, + data, sizeof(data)); + if (ret) + goto out_unlock; + + mutex_unlock(&yas5xx->lock); + + *t = get_unaligned_be16(&data[0]); + xy1y2[0] = FIELD_GET(GENMASK(13, 0), get_unaligned_be16(&data[2])); + xy1y2[1] = get_unaligned_be16(&data[4]); + xy1y2[2] = get_unaligned_be16(&data[6]); + + /* The second version of YAS537 needs to include calibration coefficients */ + if (yas5xx->version == YAS537_VERSION_1) { + for (i = 0; i < 3; i++) + s[i] = xy1y2[i] - half_range; + h[0] = (c->k * (128 * s[0] + c->a2 * s[1] + c->a3 * s[2])) / half_range; + h[1] = (c->k * (c->a4 * s[0] + c->a5 * s[1] + c->a6 * s[2])) / half_range; + h[2] = (c->k * (c->a7 * s[0] + c->a8 * s[1] + c->a9 * s[2])) / half_range; + for (i = 0; i < 3; i++) { + h[i] = clamp(h[i], -half_range, half_range - 1); + xy1y2[i] = h[i] + half_range; + } + } + + *x = xy1y2[0]; + *y1 = xy1y2[1]; + *y2 = xy1y2[2]; + + return 0; + +out_unlock: + mutex_unlock(&yas5xx->lock); + return ret; +} + +/* Used by YAS530, YAS532 and YAS533 */ +static s32 yas530_linearize(struct yas5xx *yas5xx, u16 val, int axis) +{ + const struct yas5xx_chip_info *ci = yas5xx->chip_info; + struct yas5xx_calibration *c = &yas5xx->calibration; + static const s32 yas532ac_coef[] = { + YAS532_VERSION_AC_COEF_X, + YAS532_VERSION_AC_COEF_Y1, + YAS532_VERSION_AC_COEF_Y2, + }; + s32 coef; + + /* Select coefficients */ + switch (ci->devid) { + case YAS530_DEVICE_ID: + if (yas5xx->version == YAS530_VERSION_A) + coef = YAS530_VERSION_A_COEF; + else + coef = YAS530_VERSION_B_COEF; + break; + case YAS532_DEVICE_ID: + if (yas5xx->version == YAS532_VERSION_AB) + coef = YAS532_VERSION_AB_COEF; + else + /* Elaborate coefficients */ + coef = yas532ac_coef[axis]; + break; + default: + dev_err(yas5xx->dev, "unknown device type\n"); + return val; + } + /* + * Linearization formula: + * + * x' = x - (3721 + 50 * f) + (xoffset - r) * c + * + * Where f and r are calibration values, c is a per-device + * and sometimes per-axis coefficient. + */ + return val - (3721 + 50 * c->f[axis]) + + (yas5xx->hard_offsets[axis] - c->r[axis]) * coef; +} + +static s32 yas5xx_calc_temperature(struct yas5xx *yas5xx, u16 t) +{ + const struct yas5xx_chip_info *ci = yas5xx->chip_info; + s32 to; + u16 t_ref; + s16 min_temp_x10; + int ref_temp_x10; + + t_ref = ci->t_ref; + min_temp_x10 = ci->min_temp_x10; + ref_temp_x10 = 200; + + to = (min_temp_x10 + ((ref_temp_x10 - min_temp_x10) * t / t_ref)) * 100; + return to; +} + +/** + * yas530_get_measure() - Measure a sample of all axis and process + * @yas5xx: The device state + * @to: Temperature out + * @xo: X axis out + * @yo: Y axis out + * @zo: Z axis out + * @return: 0 on success or error code + * + * Used by YAS530, YAS532 and YAS533. + */ +static int yas530_get_measure(struct yas5xx *yas5xx, s32 *to, s32 *xo, s32 *yo, s32 *zo) +{ + const struct yas5xx_chip_info *ci = yas5xx->chip_info; + struct yas5xx_calibration *c = &yas5xx->calibration; + u16 t_ref, t_comp, t, x, y1, y2; + /* These are signed x, signed y1 etc */ + s32 sx, sy1, sy2, sy, sz; + int ret; + + /* We first get raw data that needs to be translated to [x,y,z] */ + ret = yas530_measure(yas5xx, &t, &x, &y1, &y2); + if (ret) + return ret; + + /* Do some linearization if available */ + sx = yas530_linearize(yas5xx, x, 0); + sy1 = yas530_linearize(yas5xx, y1, 1); + sy2 = yas530_linearize(yas5xx, y2, 2); + + /* + * Set the temperature for compensation (unit: counts): + * YAS532/YAS533 version AC uses the temperature deviation as a + * multiplier. YAS530 and YAS532 version AB use solely the t value. + */ + t_ref = ci->t_ref; + if (ci->devid == YAS532_DEVICE_ID && + yas5xx->version == YAS532_VERSION_AC) { + t_comp = t - t_ref; + } else { + t_comp = t; + } + + /* + * Temperature compensation for x, y1, y2 respectively: + * + * Cx * t_comp + * x' = x - ----------- + * 100 + */ + sx = sx - (c->Cx * t_comp) / 100; + sy1 = sy1 - (c->Cy1 * t_comp) / 100; + sy2 = sy2 - (c->Cy2 * t_comp) / 100; + + /* + * Break y1 and y2 into y and z, y1 and y2 are apparently encoding + * y and z. + */ + sy = sy1 - sy2; + sz = -sy1 - sy2; + + /* Calculate temperature readout */ + *to = yas5xx_calc_temperature(yas5xx, t); + + /* + * Calibrate [x,y,z] with some formulas like this: + * + * 100 * x + a_2 * y + a_3 * z + * x' = k * --------------------------- + * 10 + * + * a_4 * x + a_5 * y + a_6 * z + * y' = k * --------------------------- + * 10 + * + * a_7 * x + a_8 * y + a_9 * z + * z' = k * --------------------------- + * 10 + */ + *xo = c->k * ((100 * sx + c->a2 * sy + c->a3 * sz) / 10); + *yo = c->k * ((c->a4 * sx + c->a5 * sy + c->a6 * sz) / 10); + *zo = c->k * ((c->a7 * sx + c->a8 * sy + c->a9 * sz) / 10); + + return 0; +} + +/** + * yas537_get_measure() - Measure a sample of all axis and process + * @yas5xx: The device state + * @to: Temperature out + * @xo: X axis out + * @yo: Y axis out + * @zo: Z axis out + * @return: 0 on success or error code + */ +static int yas537_get_measure(struct yas5xx *yas5xx, s32 *to, s32 *xo, s32 *yo, s32 *zo) +{ + u16 t, x, y1, y2; + int ret; + + /* We first get raw data that needs to be translated to [x,y,z] */ + ret = yas537_measure(yas5xx, &t, &x, &y1, &y2); + if (ret) + return ret; + + /* Calculate temperature readout */ + *to = yas5xx_calc_temperature(yas5xx, t); + + /* + * Unfortunately, no linearization or temperature compensation formulas + * are known for YAS537. + */ + + /* Calculate x, y, z from x, y1, y2 */ + *xo = (x - BIT(13)) * 300; + *yo = (y1 - y2) * 1732 / 10; + *zo = (-y1 - y2 + BIT(14)) * 300; + + return 0; +} + +static int yas5xx_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct yas5xx *yas5xx = iio_priv(indio_dev); + const struct yas5xx_chip_info *ci = yas5xx->chip_info; + s32 t, x, y, z; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + case IIO_CHAN_INFO_RAW: + pm_runtime_get_sync(yas5xx->dev); + ret = ci->get_measure(yas5xx, &t, &x, &y, &z); + pm_runtime_put_autosuspend(yas5xx->dev); + if (ret) + return ret; + switch (chan->address) { + case 0: + *val = t; + break; + case 1: + *val = x; + break; + case 2: + *val = y; + break; + case 3: + *val = z; + break; + default: + dev_err(yas5xx->dev, "unknown channel\n"); + return -EINVAL; + } + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 1; + *val2 = ci->scaling_val2; + return IIO_VAL_FRACTIONAL; + default: + /* Unknown request */ + return -EINVAL; + } +} + +static void yas5xx_fill_buffer(struct iio_dev *indio_dev) +{ + struct yas5xx *yas5xx = iio_priv(indio_dev); + const struct yas5xx_chip_info *ci = yas5xx->chip_info; + s32 t, x, y, z; + int ret; + + pm_runtime_get_sync(yas5xx->dev); + ret = ci->get_measure(yas5xx, &t, &x, &y, &z); + pm_runtime_put_autosuspend(yas5xx->dev); + if (ret) { + dev_err(yas5xx->dev, "error refilling buffer\n"); + return; + } + yas5xx->scan.channels[0] = t; + yas5xx->scan.channels[1] = x; + yas5xx->scan.channels[2] = y; + yas5xx->scan.channels[3] = z; + iio_push_to_buffers_with_ts(indio_dev, &yas5xx->scan, sizeof(yas5xx->scan), + iio_get_time_ns(indio_dev)); +} + +static irqreturn_t yas5xx_handle_trigger(int irq, void *p) +{ + const struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + + yas5xx_fill_buffer(indio_dev); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + + +static const struct iio_mount_matrix * +yas5xx_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct yas5xx *yas5xx = iio_priv(indio_dev); + + return &yas5xx->orientation; +} + +static const struct iio_chan_spec_ext_info yas5xx_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, yas5xx_get_mount_matrix), + { } +}; + +#define YAS5XX_AXIS_CHANNEL(axis, index) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info = yas5xx_ext_info, \ + .address = index, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 32, \ + .storagebits = 32, \ + .endianness = IIO_CPU, \ + }, \ + } + +static const struct iio_chan_spec yas5xx_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = 0, + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + YAS5XX_AXIS_CHANNEL(X, 1), + YAS5XX_AXIS_CHANNEL(Y, 2), + YAS5XX_AXIS_CHANNEL(Z, 3), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const unsigned long yas5xx_scan_masks[] = { GENMASK(3, 0), 0 }; + +static const struct iio_info yas5xx_info = { + .read_raw = &yas5xx_read_raw, +}; + +static bool yas5xx_volatile_reg(struct device *dev, unsigned int reg) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct yas5xx *yas5xx = iio_priv(indio_dev); + const struct yas5xx_chip_info *ci = yas5xx->chip_info; + int reg_qty; + int i; + + if (reg >= YAS5XX_MEASURE_DATA && reg < YAS5XX_MEASURE_DATA + 8) + return true; + + /* + * YAS versions share different registers on the same address, + * need to differentiate. + */ + reg_qty = ci->volatile_reg_qty; + for (i = 0; i < reg_qty; i++) { + if (reg == ci->volatile_reg[i]) + return true; + } + + return false; +} + +/* TODO: enable regmap cache, using mark dirty and sync at runtime resume */ +static const struct regmap_config yas5xx_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + .volatile_reg = yas5xx_volatile_reg, +}; + +/** + * yas530_extract_calibration() - extracts the a2-a9 and k calibration + * @data: the bitfield to use + * @c: the calibration to populate + * + * Used by YAS530, YAS532 and YAS533. + */ +static void yas530_extract_calibration(u8 *data, struct yas5xx_calibration *c) +{ + u64 val = get_unaligned_be64(data); + + /* + * Bitfield layout for the axis calibration data, for factor + * a2 = 2 etc, k = k, c = clock divider + * + * n 7 6 5 4 3 2 1 0 + * 0 [ 2 2 2 2 2 2 3 3 ] bits 63 .. 56 + * 1 [ 3 3 4 4 4 4 4 4 ] bits 55 .. 48 + * 2 [ 5 5 5 5 5 5 6 6 ] bits 47 .. 40 + * 3 [ 6 6 6 6 7 7 7 7 ] bits 39 .. 32 + * 4 [ 7 7 7 8 8 8 8 8 ] bits 31 .. 24 + * 5 [ 8 9 9 9 9 9 9 9 ] bits 23 .. 16 + * 6 [ 9 k k k k k c c ] bits 15 .. 8 + * 7 [ c x x x x x x x ] bits 7 .. 0 + */ + c->a2 = FIELD_GET(GENMASK_ULL(63, 58), val) - 32; + c->a3 = FIELD_GET(GENMASK_ULL(57, 54), val) - 8; + c->a4 = FIELD_GET(GENMASK_ULL(53, 48), val) - 32; + c->a5 = FIELD_GET(GENMASK_ULL(47, 42), val) + 38; + c->a6 = FIELD_GET(GENMASK_ULL(41, 36), val) - 32; + c->a7 = FIELD_GET(GENMASK_ULL(35, 29), val) - 64; + c->a8 = FIELD_GET(GENMASK_ULL(28, 23), val) - 32; + c->a9 = FIELD_GET(GENMASK_ULL(22, 15), val); + c->k = FIELD_GET(GENMASK_ULL(14, 10), val) + 10; + c->dck = FIELD_GET(GENMASK_ULL(9, 7), val); +} + +static int yas530_get_calibration_data(struct yas5xx *yas5xx) +{ + struct yas5xx_calibration *c = &yas5xx->calibration; + u8 data[16]; + u32 val; + int ret; + + /* Dummy read, first read is ALWAYS wrong */ + ret = regmap_bulk_read(yas5xx->map, YAS530_CAL, data, sizeof(data)); + if (ret) + return ret; + + /* Actual calibration readout */ + ret = regmap_bulk_read(yas5xx->map, YAS530_CAL, data, sizeof(data)); + if (ret) + return ret; + dev_dbg(yas5xx->dev, "calibration data: %16ph\n", data); + + /* Contribute calibration data to the input pool for kernel entropy */ + add_device_randomness(data, sizeof(data)); + + /* Extract version */ + yas5xx->version = data[15] & GENMASK(1, 0); + + /* Extract the calibration from the bitfield */ + c->Cx = data[0] * 6 - 768; + c->Cy1 = data[1] * 6 - 768; + c->Cy2 = data[2] * 6 - 768; + yas530_extract_calibration(&data[3], c); + + /* + * Extract linearization: + * Linearization layout in the 32 bits at byte 11: + * The r factors are 6 bit values where bit 5 is the sign + * + * n 7 6 5 4 3 2 1 0 + * 0 [ xx xx xx r0 r0 r0 r0 r0 ] bits 31 .. 24 + * 1 [ r0 f0 f0 r1 r1 r1 r1 r1 ] bits 23 .. 16 + * 2 [ r1 f1 f1 r2 r2 r2 r2 r2 ] bits 15 .. 8 + * 3 [ r2 f2 f2 xx xx xx xx xx ] bits 7 .. 0 + */ + val = get_unaligned_be32(&data[11]); + c->f[0] = FIELD_GET(GENMASK(22, 21), val); + c->f[1] = FIELD_GET(GENMASK(14, 13), val); + c->f[2] = FIELD_GET(GENMASK(6, 5), val); + c->r[0] = FIELD_GET_SIGNED(GENMASK(28, 23), val); + c->r[1] = FIELD_GET_SIGNED(GENMASK(20, 15), val); + c->r[2] = FIELD_GET_SIGNED(GENMASK(12, 7), val); + + return 0; +} + +static int yas532_get_calibration_data(struct yas5xx *yas5xx) +{ + struct yas5xx_calibration *c = &yas5xx->calibration; + u8 data[14]; + u32 val; + int ret; + + /* Dummy read, first read is ALWAYS wrong */ + ret = regmap_bulk_read(yas5xx->map, YAS530_CAL, data, sizeof(data)); + if (ret) + return ret; + /* Actual calibration readout */ + ret = regmap_bulk_read(yas5xx->map, YAS530_CAL, data, sizeof(data)); + if (ret) + return ret; + dev_dbg(yas5xx->dev, "calibration data: %14ph\n", data); + + /* Sanity check, is this all zeroes? */ + if (!memchr_inv(data, 0x00, 13) && !(data[13] & BIT(7))) + dev_warn(yas5xx->dev, "calibration is blank!\n"); + + /* Contribute calibration data to the input pool for kernel entropy */ + add_device_randomness(data, sizeof(data)); + + /* Only one bit of version info reserved here as far as we know */ + yas5xx->version = data[13] & BIT(0); + + /* Extract calibration from the bitfield */ + c->Cx = data[0] * 10 - 1280; + c->Cy1 = data[1] * 10 - 1280; + c->Cy2 = data[2] * 10 - 1280; + yas530_extract_calibration(&data[3], c); + + /* + * Extract linearization: + * Linearization layout in the 32 bits at byte 10: + * The r factors are 6 bit values where bit 5 is the sign + * + * n 7 6 5 4 3 2 1 0 + * 0 [ xx r0 r0 r0 r0 r0 r0 f0 ] bits 31 .. 24 + * 1 [ f0 r1 r1 r1 r1 r1 r1 f1 ] bits 23 .. 16 + * 2 [ f1 r2 r2 r2 r2 r2 r2 f2 ] bits 15 .. 8 + * 3 [ f2 xx xx xx xx xx xx xx ] bits 7 .. 0 + */ + val = get_unaligned_be32(&data[10]); + c->f[0] = FIELD_GET(GENMASK(24, 23), val); + c->f[1] = FIELD_GET(GENMASK(16, 15), val); + c->f[2] = FIELD_GET(GENMASK(8, 7), val); + c->r[0] = FIELD_GET_SIGNED(GENMASK(30, 25), val); + c->r[1] = FIELD_GET_SIGNED(GENMASK(22, 17), val); + c->r[2] = FIELD_GET_SIGNED(GENMASK(14, 7), val); + + return 0; +} + +static int yas537_get_calibration_data(struct yas5xx *yas5xx) +{ + struct yas5xx_calibration *c = &yas5xx->calibration; + u8 data[17]; + u32 val1, val2, val3, val4; + int i, ret; + + /* Writing SRST register */ + ret = regmap_write(yas5xx->map, YAS537_SRST, BIT(1)); + if (ret) + return ret; + + /* Calibration readout, YAS537 needs one readout only */ + ret = regmap_bulk_read(yas5xx->map, YAS537_CAL, data, sizeof(data)); + if (ret) + return ret; + dev_dbg(yas5xx->dev, "calibration data: %17ph\n", data); + + /* Sanity check, is this all zeroes? */ + if (!memchr_inv(data, 0x00, 16) && !FIELD_GET(GENMASK(5, 0), data[16])) + dev_warn(yas5xx->dev, "calibration is blank!\n"); + + /* Contribute calibration data to the input pool for kernel entropy */ + add_device_randomness(data, sizeof(data)); + + /* Extract version information */ + yas5xx->version = FIELD_GET(GENMASK(7, 6), data[16]); + + /* There are two versions of YAS537 behaving differently */ + switch (yas5xx->version) { + case YAS537_VERSION_0: + /* + * The first version simply writes data back into registers: + * + * data[0] YAS537_MTC 0x93 + * data[1] 0x94 + * data[2] 0x95 + * data[3] 0x96 + * data[4] 0x97 + * data[5] 0x98 + * data[6] 0x99 + * data[7] 0x9a + * data[8] 0x9b + * data[9] 0x9c + * data[10] 0x9d + * data[11] YAS537_OC 0x9e + * + * data[12] YAS537_OFFSET_X 0x84 + * data[13] YAS537_OFFSET_Y1 0x85 + * data[14] YAS537_OFFSET_Y2 0x86 + * + * data[15] YAS537_HCK 0x88 + * data[16] YAS537_LCK 0x89 + */ + for (i = 0; i < 12; i++) { + ret = regmap_write(yas5xx->map, YAS537_MTC + i, + data[i]); + if (ret) + return ret; + } + for (i = 0; i < 3; i++) { + ret = regmap_write(yas5xx->map, YAS537_OFFSET_X + i, + data[i + 12]); + if (ret) + return ret; + yas5xx->hard_offsets[i] = data[i + 12]; + } + for (i = 0; i < 2; i++) { + ret = regmap_write(yas5xx->map, YAS537_HCK + i, + data[i + 15]); + if (ret) + return ret; + } + break; + case YAS537_VERSION_1: + /* + * The second version writes some data into registers but also + * extracts calibration coefficients. + * + * Registers being written: + * + * data[0] YAS537_MTC 0x93 + * data[1] YAS537_MTC+1 0x94 + * data[2] YAS537_MTC+2 0x95 + * data[3] YAS537_MTC+3 (partially) 0x96 + * + * data[12] YAS537_OFFSET_X 0x84 + * data[13] YAS537_OFFSET_Y1 0x85 + * data[14] YAS537_OFFSET_Y2 0x86 + * + * data[15] YAS537_HCK (partially) 0x88 + * YAS537_LCK (partially) 0x89 + * data[16] YAS537_OC (partially) 0x9e + */ + for (i = 0; i < 3; i++) { + ret = regmap_write(yas5xx->map, YAS537_MTC + i, + data[i]); + if (ret) + return ret; + } + for (i = 0; i < 3; i++) { + ret = regmap_write(yas5xx->map, YAS537_OFFSET_X + i, + data[i + 12]); + if (ret) + return ret; + yas5xx->hard_offsets[i] = data[i + 12]; + } + /* + * Visualization of partially taken data: + * + * data[3] n 7 6 5 4 3 2 1 0 + * YAS537_MTC+3 x x x 1 0 0 0 0 + * + * data[15] n 7 6 5 4 3 2 1 0 + * YAS537_HCK x x x x 0 + * + * data[15] n 7 6 5 4 3 2 1 0 + * YAS537_LCK x x x x 0 + * + * data[16] n 7 6 5 4 3 2 1 0 + * YAS537_OC x x x x x x + */ + ret = regmap_write(yas5xx->map, YAS537_MTC + 3, + FIELD_PREP(YAS537_MTC3_MASK_PREP, + FIELD_GET(YAS537_MTC3_MASK_GET, data[3])) | + YAS537_MTC3_ADD_BIT); + if (ret) + return ret; + ret = regmap_write(yas5xx->map, YAS537_HCK, + FIELD_PREP(YAS537_HCK_MASK_PREP, + FIELD_GET(YAS537_HCK_MASK_GET, data[15]))); + if (ret) + return ret; + ret = regmap_write(yas5xx->map, YAS537_LCK, + FIELD_PREP(YAS537_LCK_MASK_PREP, + FIELD_GET(YAS537_LCK_MASK_GET, data[15]))); + if (ret) + return ret; + ret = regmap_write(yas5xx->map, YAS537_OC, + FIELD_GET(YAS537_OC_MASK_GET, data[16])); + if (ret) + return ret; + /* + * For data extraction, build some blocks. Four 32-bit blocks + * look appropriate. + * + * n 7 6 5 4 3 2 1 0 + * data[0] 0 [ Cx Cx Cx Cx Cx Cx Cx Cx ] bits 31 .. 24 + * data[1] 1 [ Cx C1 C1 C1 C1 C1 C1 C1 ] bits 23 .. 16 + * data[2] 2 [ C1 C1 C2 C2 C2 C2 C2 C2 ] bits 15 .. 8 + * data[3] 3 [ C2 C2 C2 ] bits 7 .. 0 + * + * n 7 6 5 4 3 2 1 0 + * data[3] 0 [ a2 a2 a2 a2 a2 ] bits 31 .. 24 + * data[4] 1 [ a2 a2 a3 a3 a3 a3 a3 a3 ] bits 23 .. 16 + * data[5] 2 [ a3 a4 a4 a4 a4 a4 a4 a4 ] bits 15 .. 8 + * data[6] 3 [ a4 ] bits 7 .. 0 + * + * n 7 6 5 4 3 2 1 0 + * data[6] 0 [ a5 a5 a5 a5 a5 a5 a5 ] bits 31 .. 24 + * data[7] 1 [ a5 a5 a6 a6 a6 a6 a6 a6 ] bits 23 .. 16 + * data[8] 2 [ a6 a7 a7 a7 a7 a7 a7 a7 ] bits 15 .. 8 + * data[9] 3 [ a7 ] bits 7 .. 0 + * + * n 7 6 5 4 3 2 1 0 + * data[9] 0 [ a8 a8 a8 a8 a8 a8 a8 ] bits 31 .. 24 + * data[10] 1 [ a9 a9 a9 a9 a9 a9 a9 a9 ] bits 23 .. 16 + * data[11] 2 [ a9 k k k k k k k ] bits 15 .. 8 + * data[12] 3 [ ] bits 7 .. 0 + */ + val1 = get_unaligned_be32(&data[0]); + val2 = get_unaligned_be32(&data[3]); + val3 = get_unaligned_be32(&data[6]); + val4 = get_unaligned_be32(&data[9]); + /* Extract calibration coefficients and modify */ + c->Cx = FIELD_GET(GENMASK(31, 23), val1) - 256; + c->Cy1 = FIELD_GET(GENMASK(22, 14), val1) - 256; + c->Cy2 = FIELD_GET(GENMASK(13, 5), val1) - 256; + c->a2 = FIELD_GET(GENMASK(28, 22), val2) - 64; + c->a3 = FIELD_GET(GENMASK(21, 15), val2) - 64; + c->a4 = FIELD_GET(GENMASK(14, 7), val2) - 128; + c->a5 = FIELD_GET(GENMASK(30, 22), val3) - 112; + c->a6 = FIELD_GET(GENMASK(21, 15), val3) - 64; + c->a7 = FIELD_GET(GENMASK(14, 7), val3) - 128; + c->a8 = FIELD_GET(GENMASK(30, 24), val4) - 64; + c->a9 = FIELD_GET(GENMASK(23, 15), val4) - 112; + c->k = FIELD_GET(GENMASK(14, 8), val4); + break; + default: + dev_err(yas5xx->dev, "unknown version of YAS537\n"); + return -EINVAL; + } + + return 0; +} + +/* Used by YAS530, YAS532 and YAS533 */ +static void yas530_dump_calibration(struct yas5xx *yas5xx) +{ + struct yas5xx_calibration *c = &yas5xx->calibration; + + dev_dbg(yas5xx->dev, "f[] = [%d, %d, %d]\n", + c->f[0], c->f[1], c->f[2]); + dev_dbg(yas5xx->dev, "r[] = [%d, %d, %d]\n", + c->r[0], c->r[1], c->r[2]); + dev_dbg(yas5xx->dev, "Cx = %d\n", c->Cx); + dev_dbg(yas5xx->dev, "Cy1 = %d\n", c->Cy1); + dev_dbg(yas5xx->dev, "Cy2 = %d\n", c->Cy2); + dev_dbg(yas5xx->dev, "a2 = %d\n", c->a2); + dev_dbg(yas5xx->dev, "a3 = %d\n", c->a3); + dev_dbg(yas5xx->dev, "a4 = %d\n", c->a4); + dev_dbg(yas5xx->dev, "a5 = %d\n", c->a5); + dev_dbg(yas5xx->dev, "a6 = %d\n", c->a6); + dev_dbg(yas5xx->dev, "a7 = %d\n", c->a7); + dev_dbg(yas5xx->dev, "a8 = %d\n", c->a8); + dev_dbg(yas5xx->dev, "a9 = %d\n", c->a9); + dev_dbg(yas5xx->dev, "k = %d\n", c->k); + dev_dbg(yas5xx->dev, "dck = %d\n", c->dck); +} + +static void yas537_dump_calibration(struct yas5xx *yas5xx) +{ + struct yas5xx_calibration *c = &yas5xx->calibration; + + if (yas5xx->version == YAS537_VERSION_1) { + dev_dbg(yas5xx->dev, "Cx = %d\n", c->Cx); + dev_dbg(yas5xx->dev, "Cy1 = %d\n", c->Cy1); + dev_dbg(yas5xx->dev, "Cy2 = %d\n", c->Cy2); + dev_dbg(yas5xx->dev, "a2 = %d\n", c->a2); + dev_dbg(yas5xx->dev, "a3 = %d\n", c->a3); + dev_dbg(yas5xx->dev, "a4 = %d\n", c->a4); + dev_dbg(yas5xx->dev, "a5 = %d\n", c->a5); + dev_dbg(yas5xx->dev, "a6 = %d\n", c->a6); + dev_dbg(yas5xx->dev, "a7 = %d\n", c->a7); + dev_dbg(yas5xx->dev, "a8 = %d\n", c->a8); + dev_dbg(yas5xx->dev, "a9 = %d\n", c->a9); + dev_dbg(yas5xx->dev, "k = %d\n", c->k); + } +} + +/* Used by YAS530, YAS532 and YAS533 */ +static int yas530_set_offsets(struct yas5xx *yas5xx, s8 ox, s8 oy1, s8 oy2) +{ + int ret; + + ret = regmap_write(yas5xx->map, YAS530_OFFSET_X, ox); + if (ret) + return ret; + ret = regmap_write(yas5xx->map, YAS530_OFFSET_Y1, oy1); + if (ret) + return ret; + return regmap_write(yas5xx->map, YAS530_OFFSET_Y2, oy2); +} + +/* Used by YAS530, YAS532 and YAS533 */ +static s8 yas530_adjust_offset(s8 old, int bit, u16 center, u16 measure) +{ + if (measure > center) + return old + BIT(bit); + if (measure < center) + return old - BIT(bit); + return old; +} + +/* Used by YAS530, YAS532 and YAS533 */ +static int yas530_measure_offsets(struct yas5xx *yas5xx) +{ + const struct yas5xx_chip_info *ci = yas5xx->chip_info; + int ret; + u16 center; + u16 t, x, y1, y2; + s8 ox, oy1, oy2; + int i; + + /* Actuate the init coil and measure offsets */ + ret = regmap_write(yas5xx->map, YAS530_ACTUATE_INIT_COIL, 0); + if (ret) + return ret; + + /* When the initcoil is active this should be around the center */ + switch (ci->devid) { + case YAS530_DEVICE_ID: + center = YAS530_DATA_CENTER; + break; + case YAS532_DEVICE_ID: + center = YAS532_DATA_CENTER; + break; + default: + dev_err(yas5xx->dev, "unknown device type\n"); + return -EINVAL; + } + + /* + * We set offsets in the interval +-31 by iterating + * +-16, +-8, +-4, +-2, +-1 adjusting the offsets each + * time, then writing the final offsets into the + * registers. + * + * NOTE: these offsets are NOT in the same unit or magnitude + * as the values for [x, y1, y2]. The value is +/-31 + * but the effect on the raw values is much larger. + * The effect of the offset is to bring the measure + * roughly to the center. + */ + ox = 0; + oy1 = 0; + oy2 = 0; + + for (i = 4; i >= 0; i--) { + ret = yas530_set_offsets(yas5xx, ox, oy1, oy2); + if (ret) + return ret; + + ret = yas530_measure(yas5xx, &t, &x, &y1, &y2); + if (ret) + return ret; + dev_dbg(yas5xx->dev, "measurement %d: x=%d, y1=%d, y2=%d\n", + 5-i, x, y1, y2); + + ox = yas530_adjust_offset(ox, i, center, x); + oy1 = yas530_adjust_offset(oy1, i, center, y1); + oy2 = yas530_adjust_offset(oy2, i, center, y2); + } + + /* Needed for calibration algorithm */ + yas5xx->hard_offsets[0] = ox; + yas5xx->hard_offsets[1] = oy1; + yas5xx->hard_offsets[2] = oy2; + ret = yas530_set_offsets(yas5xx, ox, oy1, oy2); + if (ret) + return ret; + + dev_info(yas5xx->dev, "discovered hard offsets: x=%d, y1=%d, y2=%d\n", + ox, oy1, oy2); + return 0; +} + +/* Used by YAS530, YAS532 and YAS533 */ +static int yas530_power_on(struct yas5xx *yas5xx) +{ + unsigned int val; + int ret; + + /* Zero the test registers */ + ret = regmap_write(yas5xx->map, YAS530_TEST1, 0); + if (ret) + return ret; + ret = regmap_write(yas5xx->map, YAS530_TEST2, 0); + if (ret) + return ret; + + /* Set up for no interrupts, calibrated clock divider */ + val = FIELD_PREP(YAS5XX_CONFIG_CCK_MASK, yas5xx->calibration.dck); + ret = regmap_write(yas5xx->map, YAS530_CONFIG, val); + if (ret) + return ret; + + /* Measure interval 0 (back-to-back?) */ + return regmap_write(yas5xx->map, YAS530_MEASURE_INTERVAL, 0); +} + +static int yas537_power_on(struct yas5xx *yas5xx) +{ + __be16 buf; + int ret; + u8 intrvl; + + /* Writing ADCCAL and TRM registers */ + buf = cpu_to_be16(GENMASK(9, 3)); + ret = regmap_bulk_write(yas5xx->map, YAS537_ADCCAL, &buf, sizeof(buf)); + if (ret) + return ret; + ret = regmap_write(yas5xx->map, YAS537_TRM, GENMASK(7, 0)); + if (ret) + return ret; + + /* The interval value is static in regular operation */ + intrvl = (YAS537_DEFAULT_SENSOR_DELAY_MS * MILLI + - YAS537_MEASURE_TIME_WORST_US) / 4100; + ret = regmap_write(yas5xx->map, YAS537_MEASURE_INTERVAL, intrvl); + if (ret) + return ret; + + /* The average value is also static in regular operation */ + ret = regmap_write(yas5xx->map, YAS537_AVR, YAS537_MAG_AVERAGE_32_MASK); + if (ret) + return ret; + + /* Perform the "rcoil" part but skip the "last_after_rcoil" read */ + ret = regmap_write(yas5xx->map, YAS537_CONFIG, BIT(3)); + if (ret) + return ret; + + /* Wait until the coil has ramped up */ + fsleep(YAS537_MAG_RCOIL_TIME_US); + + return 0; +} + +static const struct yas5xx_chip_info yas5xx_chip_info_tbl[] = { + [yas530] = { + .devid = YAS530_DEVICE_ID, + .product_label = "yas530", + .product_name = "YAS530 MS-3E", + .version_names = { "A", "B" }, + .volatile_reg = yas530_volatile_reg, + .volatile_reg_qty = ARRAY_SIZE(yas530_volatile_reg), + .scaling_val2 = 100000000, /* picotesla to Gauss */ + .t_ref = 182, /* counts */ + .min_temp_x10 = -620, /* 1/10:s degrees Celsius */ + .get_measure = yas530_get_measure, + .get_calibration_data = yas530_get_calibration_data, + .dump_calibration = yas530_dump_calibration, + .measure_offsets = yas530_measure_offsets, + .power_on = yas530_power_on, + }, + [yas532] = { + .devid = YAS532_DEVICE_ID, + .product_label = "yas532", + .product_name = "YAS532 MS-3R", + .version_names = { "AB", "AC" }, + .volatile_reg = yas530_volatile_reg, + .volatile_reg_qty = ARRAY_SIZE(yas530_volatile_reg), + .scaling_val2 = 100000, /* nanotesla to Gauss */ + .t_ref = 390, /* counts */ + .min_temp_x10 = -500, /* 1/10:s degrees Celsius */ + .get_measure = yas530_get_measure, + .get_calibration_data = yas532_get_calibration_data, + .dump_calibration = yas530_dump_calibration, + .measure_offsets = yas530_measure_offsets, + .power_on = yas530_power_on, + }, + [yas533] = { + .devid = YAS532_DEVICE_ID, + .product_label = "yas533", + .product_name = "YAS533 MS-3F", + .version_names = { "AB", "AC" }, + .volatile_reg = yas530_volatile_reg, + .volatile_reg_qty = ARRAY_SIZE(yas530_volatile_reg), + .scaling_val2 = 100000, /* nanotesla to Gauss */ + .t_ref = 390, /* counts */ + .min_temp_x10 = -500, /* 1/10:s degrees Celsius */ + .get_measure = yas530_get_measure, + .get_calibration_data = yas532_get_calibration_data, + .dump_calibration = yas530_dump_calibration, + .measure_offsets = yas530_measure_offsets, + .power_on = yas530_power_on, + }, + [yas537] = { + .devid = YAS537_DEVICE_ID, + .product_label = "yas537", + .product_name = "YAS537 MS-3T", + .version_names = { "v0", "v1" }, /* version naming unknown */ + .volatile_reg = yas537_volatile_reg, + .volatile_reg_qty = ARRAY_SIZE(yas537_volatile_reg), + .scaling_val2 = 100000, /* nanotesla to Gauss */ + .t_ref = 8120, /* counts */ + .min_temp_x10 = -3860, /* 1/10:s degrees Celsius */ + .get_measure = yas537_get_measure, + .get_calibration_data = yas537_get_calibration_data, + .dump_calibration = yas537_dump_calibration, + /* .measure_offets is not needed for yas537 */ + .power_on = yas537_power_on, + }, +}; + +static int yas5xx_probe(struct i2c_client *i2c) +{ + struct iio_dev *indio_dev; + struct device *dev = &i2c->dev; + struct yas5xx *yas5xx; + const struct yas5xx_chip_info *ci; + int id_check; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*yas5xx)); + if (!indio_dev) + return -ENOMEM; + + yas5xx = iio_priv(indio_dev); + i2c_set_clientdata(i2c, indio_dev); + yas5xx->dev = dev; + + ret = devm_mutex_init(dev, &yas5xx->lock); + if (ret) + return ret; + + ret = iio_read_mount_matrix(dev, &yas5xx->orientation); + if (ret) + return ret; + + yas5xx->regs[0].supply = "vdd"; + yas5xx->regs[1].supply = "iovdd"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(yas5xx->regs), + yas5xx->regs); + if (ret) + return dev_err_probe(dev, ret, "cannot get regulators\n"); + + ret = regulator_bulk_enable(ARRAY_SIZE(yas5xx->regs), yas5xx->regs); + if (ret) + return dev_err_probe(dev, ret, "cannot enable regulators\n"); + + /* See comment in runtime resume callback */ + fsleep(31 * USEC_PER_MSEC); + + /* This will take the device out of reset if need be */ + yas5xx->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(yas5xx->reset)) { + ret = dev_err_probe(dev, PTR_ERR(yas5xx->reset), "failed to get reset line\n"); + goto reg_off; + } + + yas5xx->map = devm_regmap_init_i2c(i2c, &yas5xx_regmap_config); + if (IS_ERR(yas5xx->map)) { + ret = dev_err_probe(dev, PTR_ERR(yas5xx->map), "failed to allocate register map\n"); + goto assert_reset; + } + + ci = i2c_get_match_data(i2c); + yas5xx->chip_info = ci; + + ret = regmap_read(yas5xx->map, YAS5XX_DEVICE_ID, &id_check); + if (ret) + goto assert_reset; + + if (id_check != ci->devid) { + ret = dev_err_probe(dev, -ENODEV, + "device ID %02x doesn't match %s\n", + id_check, ci->product_label); + goto assert_reset; + } + + ret = ci->get_calibration_data(yas5xx); + if (ret) + goto assert_reset; + + dev_info(dev, "detected %s %s\n", ci->product_name, + ci->version_names[yas5xx->version]); + + ci->dump_calibration(yas5xx); + + ret = ci->power_on(yas5xx); + if (ret) + goto assert_reset; + + if (ci->measure_offsets) { + ret = ci->measure_offsets(yas5xx); + if (ret) + goto assert_reset; + } + + indio_dev->info = &yas5xx_info; + indio_dev->available_scan_masks = yas5xx_scan_masks; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = ci->product_label; + indio_dev->channels = yas5xx_channels; + indio_dev->num_channels = ARRAY_SIZE(yas5xx_channels); + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + yas5xx_handle_trigger, + NULL); + if (ret) { + dev_err_probe(dev, ret, "triggered buffer setup failed\n"); + goto assert_reset; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err_probe(dev, ret, "device register failed\n"); + goto cleanup_buffer; + } + + /* Take runtime PM online */ + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + pm_runtime_set_autosuspend_delay(dev, YAS5XX_AUTOSUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_put(dev); + + return 0; + +cleanup_buffer: + iio_triggered_buffer_cleanup(indio_dev); +assert_reset: + gpiod_set_value_cansleep(yas5xx->reset, 1); +reg_off: + regulator_bulk_disable(ARRAY_SIZE(yas5xx->regs), yas5xx->regs); + + return ret; +} + +static void yas5xx_remove(struct i2c_client *i2c) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(i2c); + struct yas5xx *yas5xx = iio_priv(indio_dev); + struct device *dev = &i2c->dev; + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + /* + * Now we can't get any more reads from the device, which would + * also call pm_runtime* functions and race with our disable + * code. Disable PM runtime in orderly fashion and power down. + */ + pm_runtime_get_sync(dev); + pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); + gpiod_set_value_cansleep(yas5xx->reset, 1); + regulator_bulk_disable(ARRAY_SIZE(yas5xx->regs), yas5xx->regs); +} + +static int yas5xx_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct yas5xx *yas5xx = iio_priv(indio_dev); + + gpiod_set_value_cansleep(yas5xx->reset, 1); + regulator_bulk_disable(ARRAY_SIZE(yas5xx->regs), yas5xx->regs); + + return 0; +} + +static int yas5xx_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct yas5xx *yas5xx = iio_priv(indio_dev); + const struct yas5xx_chip_info *ci = yas5xx->chip_info; + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(yas5xx->regs), yas5xx->regs); + if (ret) { + dev_err(dev, "cannot enable regulators\n"); + return ret; + } + + /* + * The YAS530 datasheet says TVSKW is up to 30 ms, after that 1 ms + * for all voltages to settle. The YAS532 is 10ms then 4ms for the + * I2C to come online. Let's keep it safe and put this at 31ms. + */ + fsleep(31 * USEC_PER_MSEC); + gpiod_set_value_cansleep(yas5xx->reset, 0); + + ret = ci->power_on(yas5xx); + if (ret) { + dev_err(dev, "cannot power on\n"); + goto out_reset; + } + + return 0; + +out_reset: + gpiod_set_value_cansleep(yas5xx->reset, 1); + regulator_bulk_disable(ARRAY_SIZE(yas5xx->regs), yas5xx->regs); + + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(yas5xx_dev_pm_ops, yas5xx_runtime_suspend, + yas5xx_runtime_resume, NULL); + +static const struct i2c_device_id yas5xx_id[] = { + { .name = "yas530", .driver_data = (kernel_ulong_t)&yas5xx_chip_info_tbl[yas530] }, + { .name = "yas532", .driver_data = (kernel_ulong_t)&yas5xx_chip_info_tbl[yas532] }, + { .name = "yas533", .driver_data = (kernel_ulong_t)&yas5xx_chip_info_tbl[yas533] }, + { .name = "yas537", .driver_data = (kernel_ulong_t)&yas5xx_chip_info_tbl[yas537] }, + { } +}; +MODULE_DEVICE_TABLE(i2c, yas5xx_id); + +static const struct of_device_id yas5xx_of_match[] = { + { .compatible = "yamaha,yas530", &yas5xx_chip_info_tbl[yas530] }, + { .compatible = "yamaha,yas532", &yas5xx_chip_info_tbl[yas532] }, + { .compatible = "yamaha,yas533", &yas5xx_chip_info_tbl[yas533] }, + { .compatible = "yamaha,yas537", &yas5xx_chip_info_tbl[yas537] }, + { } +}; +MODULE_DEVICE_TABLE(of, yas5xx_of_match); + +static struct i2c_driver yas5xx_driver = { + .driver = { + .name = "yas5xx", + .of_match_table = yas5xx_of_match, + .pm = pm_ptr(&yas5xx_dev_pm_ops), + }, + .probe = yas5xx_probe, + .remove = yas5xx_remove, + .id_table = yas5xx_id, +}; +module_i2c_driver(yas5xx_driver); + +MODULE_DESCRIPTION("Yamaha YAS53x 3-axis magnetometer driver"); +MODULE_AUTHOR("Linus Walleij"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/multiplexer/Kconfig b/drivers/iio/multiplexer/Kconfig new file mode 100644 index 000000000..928f424a1 --- /dev/null +++ b/drivers/iio/multiplexer/Kconfig @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Multiplexer drivers +# +# When adding new entries keep the list in alphabetical order + +menu "Multiplexers" + +config IIO_MUX + tristate "IIO multiplexer driver" + select MULTIPLEXER + help + Say yes here to build support for the IIO multiplexer. + + To compile this driver as a module, choose M here: the + module will be called iio-mux. + +endmenu diff --git a/drivers/iio/multiplexer/Makefile b/drivers/iio/multiplexer/Makefile new file mode 100644 index 000000000..f069ab781 --- /dev/null +++ b/drivers/iio/multiplexer/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for industrial I/O multiplexer drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_IIO_MUX) += iio-mux.o diff --git a/drivers/iio/multiplexer/iio-mux.c b/drivers/iio/multiplexer/iio-mux.c new file mode 100644 index 000000000..562457a67 --- /dev/null +++ b/drivers/iio/multiplexer/iio-mux.c @@ -0,0 +1,466 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * IIO multiplexer driver + * + * Copyright (C) 2017 Axentia Technologies AB + * + * Author: Peter Rosin + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct mux_ext_info_cache { + char *data; + ssize_t size; +}; + +struct mux_child { + struct mux_ext_info_cache *ext_info_cache; +}; + +struct mux { + int cached_state; + struct mux_control *control; + struct iio_channel *parent; + struct iio_chan_spec *chan; + struct iio_chan_spec_ext_info *ext_info; + struct mux_child *child; + u32 delay_us; +}; + +static int iio_mux_select(struct mux *mux, int idx) +{ + struct mux_child *child = &mux->child[idx]; + struct iio_chan_spec const *chan = &mux->chan[idx]; + int ret; + int i; + + ret = mux_control_select_delay(mux->control, chan->channel, + mux->delay_us); + if (ret < 0) { + mux->cached_state = -1; + return ret; + } + + if (mux->cached_state == chan->channel) + return 0; + + if (chan->ext_info) { + for (i = 0; chan->ext_info[i].name; ++i) { + const char *attr = chan->ext_info[i].name; + struct mux_ext_info_cache *cache; + + cache = &child->ext_info_cache[i]; + + if (cache->size < 0) + continue; + + ret = iio_write_channel_ext_info(mux->parent, attr, + cache->data, + cache->size); + + if (ret < 0) { + mux_control_deselect(mux->control); + mux->cached_state = -1; + return ret; + } + } + } + mux->cached_state = chan->channel; + + return 0; +} + +static void iio_mux_deselect(struct mux *mux) +{ + mux_control_deselect(mux->control); +} + +static int mux_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mux *mux = iio_priv(indio_dev); + int idx = chan - mux->chan; + int ret; + + ret = iio_mux_select(mux, idx); + if (ret < 0) + return ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = iio_read_channel_raw(mux->parent, val); + break; + + case IIO_CHAN_INFO_SCALE: + ret = iio_read_channel_scale(mux->parent, val, val2); + break; + + default: + ret = -EINVAL; + } + + iio_mux_deselect(mux); + + return ret; +} + +static int mux_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct mux *mux = iio_priv(indio_dev); + int idx = chan - mux->chan; + int ret; + + ret = iio_mux_select(mux, idx); + if (ret < 0) + return ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *type = IIO_VAL_INT; + ret = iio_read_avail_channel_raw(mux->parent, vals, length); + break; + + default: + ret = -EINVAL; + } + + iio_mux_deselect(mux); + + return ret; +} + +static int mux_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct mux *mux = iio_priv(indio_dev); + int idx = chan - mux->chan; + int ret; + + ret = iio_mux_select(mux, idx); + if (ret < 0) + return ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = iio_write_channel_raw(mux->parent, val); + break; + + default: + ret = -EINVAL; + } + + iio_mux_deselect(mux); + + return ret; +} + +static const struct iio_info mux_info = { + .read_raw = mux_read_raw, + .read_avail = mux_read_avail, + .write_raw = mux_write_raw, +}; + +static ssize_t mux_read_ext_info(struct iio_dev *indio_dev, uintptr_t private, + struct iio_chan_spec const *chan, char *buf) +{ + struct mux *mux = iio_priv(indio_dev); + int idx = chan - mux->chan; + ssize_t ret; + + ret = iio_mux_select(mux, idx); + if (ret < 0) + return ret; + + ret = iio_read_channel_ext_info(mux->parent, + mux->ext_info[private].name, + buf); + + iio_mux_deselect(mux); + + return ret; +} + +static ssize_t mux_write_ext_info(struct iio_dev *indio_dev, uintptr_t private, + struct iio_chan_spec const *chan, + const char *buf, size_t len) +{ + struct device *dev = indio_dev->dev.parent; + struct mux *mux = iio_priv(indio_dev); + int idx = chan - mux->chan; + char *new; + ssize_t ret; + + if (len >= PAGE_SIZE) + return -EINVAL; + + ret = iio_mux_select(mux, idx); + if (ret < 0) + return ret; + + new = devm_kmemdup(dev, buf, len + 1, GFP_KERNEL); + if (!new) { + iio_mux_deselect(mux); + return -ENOMEM; + } + + new[len] = 0; + + ret = iio_write_channel_ext_info(mux->parent, + mux->ext_info[private].name, + buf, len); + if (ret < 0) { + iio_mux_deselect(mux); + devm_kfree(dev, new); + return ret; + } + + devm_kfree(dev, mux->child[idx].ext_info_cache[private].data); + mux->child[idx].ext_info_cache[private].data = new; + mux->child[idx].ext_info_cache[private].size = len; + + iio_mux_deselect(mux); + + return ret; +} + +static int mux_configure_chan_ext_info(struct device *dev, struct mux *mux, + int idx, int num_ext_info) +{ + struct mux_child *child = &mux->child[idx]; + struct iio_chan_spec const *pchan = mux->parent->channel; + int i; + int ret; + + char *page __free(kfree) = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!page) + return -ENOMEM; + + child->ext_info_cache = devm_kcalloc(dev, + num_ext_info, + sizeof(*child->ext_info_cache), + GFP_KERNEL); + if (!child->ext_info_cache) + return -ENOMEM; + + for (i = 0; i < num_ext_info; ++i) { + child->ext_info_cache[i].size = -1; + + if (!pchan->ext_info[i].write) + continue; + if (!pchan->ext_info[i].read) + continue; + + ret = iio_read_channel_ext_info(mux->parent, + mux->ext_info[i].name, + page); + if (ret < 0) { + dev_err(dev, "failed to get ext_info '%s'\n", + pchan->ext_info[i].name); + return ret; + } + if (ret >= PAGE_SIZE) { + dev_err(dev, "too large ext_info '%s'\n", + pchan->ext_info[i].name); + return -EINVAL; + } + + child->ext_info_cache[i].data = devm_kmemdup(dev, page, ret + 1, + GFP_KERNEL); + if (!child->ext_info_cache[i].data) + return -ENOMEM; + + child->ext_info_cache[i].data[ret] = 0; + child->ext_info_cache[i].size = ret; + } + + return 0; +} + +static int mux_configure_channel(struct device *dev, struct mux *mux, u32 state, + const char *label, int idx) +{ + struct iio_chan_spec *chan = &mux->chan[idx]; + struct iio_chan_spec const *pchan = mux->parent->channel; + int num_ext_info; + int ret; + + chan->indexed = 1; + chan->output = pchan->output; + chan->datasheet_name = label; + chan->ext_info = mux->ext_info; + + ret = iio_get_channel_type(mux->parent, &chan->type); + if (ret < 0) { + dev_err(dev, "failed to get parent channel type\n"); + return ret; + } + + if (iio_channel_has_info(pchan, IIO_CHAN_INFO_RAW)) + chan->info_mask_separate |= BIT(IIO_CHAN_INFO_RAW); + if (iio_channel_has_info(pchan, IIO_CHAN_INFO_SCALE)) + chan->info_mask_separate |= BIT(IIO_CHAN_INFO_SCALE); + + if (iio_channel_has_available(pchan, IIO_CHAN_INFO_RAW)) + chan->info_mask_separate_available |= BIT(IIO_CHAN_INFO_RAW); + + if (state >= mux_control_states(mux->control)) { + dev_err(dev, "too many channels\n"); + return -EINVAL; + } + + chan->channel = state; + + num_ext_info = iio_get_channel_ext_info_count(mux->parent); + if (num_ext_info) + return mux_configure_chan_ext_info(dev, mux, idx, num_ext_info); + + return 0; +} + +static int mux_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct iio_channel *parent; + struct mux *mux; + const char **labels; + int all_children; + int children; + u32 state; + int sizeof_ext_info; + int sizeof_priv; + int i; + int ret; + + parent = devm_iio_channel_get(dev, "parent"); + if (IS_ERR(parent)) + return dev_err_probe(dev, PTR_ERR(parent), + "failed to get parent channel\n"); + + sizeof_ext_info = iio_get_channel_ext_info_count(parent); + if (sizeof_ext_info) { + sizeof_ext_info += 1; /* one extra entry for the sentinel */ + sizeof_ext_info *= sizeof(*mux->ext_info); + } + + all_children = device_property_string_array_count(dev, "channels"); + if (all_children < 0) + return all_children; + + labels = devm_kmalloc_array(dev, all_children, sizeof(*labels), GFP_KERNEL); + if (!labels) + return -ENOMEM; + + ret = device_property_read_string_array(dev, "channels", labels, all_children); + if (ret < 0) + return ret; + + children = 0; + for (state = 0; state < all_children; state++) { + if (*labels[state]) + children++; + } + if (children <= 0) { + dev_err(dev, "not even a single child\n"); + return -EINVAL; + } + + sizeof_priv = sizeof(*mux); + sizeof_priv += sizeof(*mux->child) * children; + sizeof_priv += sizeof(*mux->chan) * children; + sizeof_priv += sizeof_ext_info; + + indio_dev = devm_iio_device_alloc(dev, sizeof_priv); + if (!indio_dev) + return -ENOMEM; + + mux = iio_priv(indio_dev); + mux->child = (struct mux_child *)(mux + 1); + mux->chan = (struct iio_chan_spec *)(mux->child + children); + + platform_set_drvdata(pdev, indio_dev); + + mux->parent = parent; + mux->cached_state = -1; + + mux->delay_us = 0; + device_property_read_u32(dev, "settle-time-us", &mux->delay_us); + + indio_dev->name = dev_name(dev); + indio_dev->info = &mux_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = mux->chan; + indio_dev->num_channels = children; + if (sizeof_ext_info) { + mux->ext_info = devm_kmemdup(dev, + parent->channel->ext_info, + sizeof_ext_info, GFP_KERNEL); + if (!mux->ext_info) + return -ENOMEM; + + for (i = 0; mux->ext_info[i].name; ++i) { + if (parent->channel->ext_info[i].read) + mux->ext_info[i].read = mux_read_ext_info; + if (parent->channel->ext_info[i].write) + mux->ext_info[i].write = mux_write_ext_info; + mux->ext_info[i].private = i; + } + } + + mux->control = devm_mux_control_get(dev, NULL); + if (IS_ERR(mux->control)) + return dev_err_probe(dev, PTR_ERR(mux->control), + "failed to get control-mux\n"); + + i = 0; + for (state = 0; state < all_children; state++) { + if (!*labels[state]) + continue; + + ret = mux_configure_channel(dev, mux, state, labels[state], i++); + if (ret < 0) + return ret; + } + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) { + dev_err(dev, "failed to register iio device\n"); + return ret; + } + + return 0; +} + +static const struct of_device_id mux_match[] = { + { .compatible = "io-channel-mux" }, + { } +}; +MODULE_DEVICE_TABLE(of, mux_match); + +static struct platform_driver mux_driver = { + .probe = mux_probe, + .driver = { + .name = "iio-mux", + .of_match_table = mux_match, + }, +}; +module_platform_driver(mux_driver); + +MODULE_DESCRIPTION("IIO multiplexer driver"); +MODULE_AUTHOR("Peter Rosin "); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_CONSUMER"); diff --git a/drivers/iio/orientation/Kconfig b/drivers/iio/orientation/Kconfig new file mode 100644 index 000000000..396cbbb86 --- /dev/null +++ b/drivers/iio/orientation/Kconfig @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Inclinometer sensors +# +# When adding new entries keep the list in alphabetical order + +menu "Inclinometer sensors" + +config HID_SENSOR_INCLINOMETER_3D + depends on HID_SENSOR_HUB + select IIO_BUFFER + select HID_SENSOR_IIO_COMMON + select HID_SENSOR_IIO_TRIGGER + tristate "HID Inclinometer 3D" + help + Say yes here to build support for the HID SENSOR + Inclinometer 3D. + +config HID_SENSOR_DEVICE_ROTATION + depends on HID_SENSOR_HUB + select IIO_BUFFER + select HID_SENSOR_IIO_COMMON + select HID_SENSOR_IIO_TRIGGER + tristate "HID Device Rotation" + help + Say yes here to build support for the HID SENSOR + device rotation. The output of a device rotation sensor + is presented using quaternion format. + +endmenu diff --git a/drivers/iio/orientation/Makefile b/drivers/iio/orientation/Makefile new file mode 100644 index 000000000..7800ed293 --- /dev/null +++ b/drivers/iio/orientation/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for industrial I/O Inclinometer sensor drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_HID_SENSOR_INCLINOMETER_3D) += hid-sensor-incl-3d.o +obj-$(CONFIG_HID_SENSOR_DEVICE_ROTATION) += hid-sensor-rotation.o diff --git a/drivers/iio/orientation/hid-sensor-incl-3d.c b/drivers/iio/orientation/hid-sensor-incl-3d.c new file mode 100644 index 000000000..aea28321d --- /dev/null +++ b/drivers/iio/orientation/hid-sensor-incl-3d.c @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HID Sensors Driver + * Copyright (c) 2013, Intel Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../common/hid-sensors/hid-sensor-trigger.h" + +enum incl_3d_channel { + CHANNEL_SCAN_INDEX_X, + CHANNEL_SCAN_INDEX_Y, + CHANNEL_SCAN_INDEX_Z, + INCLI_3D_CHANNEL_MAX, +}; + +#define CHANNEL_SCAN_INDEX_TIMESTAMP INCLI_3D_CHANNEL_MAX + +struct incl_3d_state { + struct hid_sensor_hub_callbacks callbacks; + struct hid_sensor_common common_attributes; + struct hid_sensor_hub_attribute_info incl[INCLI_3D_CHANNEL_MAX]; + struct { + u32 incl_val[INCLI_3D_CHANNEL_MAX]; + aligned_s64 timestamp; + } scan; + int scale_pre_decml; + int scale_post_decml; + int scale_precision; + int value_offset; + s64 timestamp; +}; + +static const u32 incl_3d_addresses[INCLI_3D_CHANNEL_MAX] = { + HID_USAGE_SENSOR_ORIENT_TILT_X, + HID_USAGE_SENSOR_ORIENT_TILT_Y, + HID_USAGE_SENSOR_ORIENT_TILT_Z +}; + +static const u32 incl_3d_sensitivity_addresses[] = { + HID_USAGE_SENSOR_DATA_ORIENTATION, + HID_USAGE_SENSOR_ORIENT_TILT, +}; + +/* Channel definitions */ +static const struct iio_chan_spec incl_3d_channels[] = { + { + .type = IIO_INCLI, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + .scan_index = CHANNEL_SCAN_INDEX_X, + }, { + .type = IIO_INCLI, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + .scan_index = CHANNEL_SCAN_INDEX_Y, + }, { + .type = IIO_INCLI, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + .scan_index = CHANNEL_SCAN_INDEX_Z, + }, + IIO_CHAN_SOFT_TIMESTAMP(CHANNEL_SCAN_INDEX_TIMESTAMP), +}; + +/* Adjust channel real bits based on report descriptor */ +static void incl_3d_adjust_channel_bit_mask(struct iio_chan_spec *chan, int size) +{ + chan->scan_type.sign = 's'; + /* Real storage bits will change based on the report desc. */ + chan->scan_type.realbits = size * 8; + /* Maximum size of a sample to capture is u32 */ + chan->scan_type.storagebits = sizeof(u32) * 8; +} + +/* Channel read_raw handler */ +static int incl_3d_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct incl_3d_state *incl_state = iio_priv(indio_dev); + int report_id = -1; + u32 address; + int ret_type; + s32 min; + + *val = 0; + *val2 = 0; + switch (mask) { + case IIO_CHAN_INFO_RAW: + hid_sensor_power_state(&incl_state->common_attributes, true); + report_id = incl_state->incl[chan->scan_index].report_id; + min = incl_state->incl[chan->scan_index].logical_minimum; + address = incl_3d_addresses[chan->scan_index]; + if (report_id >= 0) + *val = sensor_hub_input_attr_get_raw_value( + incl_state->common_attributes.hsdev, + HID_USAGE_SENSOR_INCLINOMETER_3D, address, + report_id, + SENSOR_HUB_SYNC, + min < 0); + else { + hid_sensor_power_state(&incl_state->common_attributes, + false); + return -EINVAL; + } + hid_sensor_power_state(&incl_state->common_attributes, false); + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + *val = incl_state->scale_pre_decml; + *val2 = incl_state->scale_post_decml; + ret_type = incl_state->scale_precision; + break; + case IIO_CHAN_INFO_OFFSET: + *val = incl_state->value_offset; + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret_type = hid_sensor_read_samp_freq_value( + &incl_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret_type = hid_sensor_read_raw_hyst_value( + &incl_state->common_attributes, val, val2); + break; + default: + ret_type = -EINVAL; + break; + } + + return ret_type; +} + +/* Channel write_raw handler */ +static int incl_3d_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct incl_3d_state *incl_state = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = hid_sensor_write_samp_freq_value( + &incl_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret = hid_sensor_write_raw_hyst_value( + &incl_state->common_attributes, val, val2); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info incl_3d_info = { + .read_raw = &incl_3d_read_raw, + .write_raw = &incl_3d_write_raw, +}; + +/* Callback handler to send event after all samples are received and captured */ +static int incl_3d_proc_event(struct hid_sensor_hub_device *hsdev, + u32 usage_id, void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct incl_3d_state *incl_state = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "incl_3d_proc_event\n"); + if (atomic_read(&incl_state->common_attributes.data_ready)) { + if (!incl_state->timestamp) + incl_state->timestamp = iio_get_time_ns(indio_dev); + + iio_push_to_buffers_with_timestamp(indio_dev, + &incl_state->scan, + incl_state->timestamp); + + incl_state->timestamp = 0; + } + + return 0; +} + +/* Capture samples in local storage */ +static int incl_3d_capture_sample(struct hid_sensor_hub_device *hsdev, + u32 usage_id, + size_t raw_len, char *raw_data, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct incl_3d_state *incl_state = iio_priv(indio_dev); + int ret = 0; + + switch (usage_id) { + case HID_USAGE_SENSOR_ORIENT_TILT_X: + incl_state->scan.incl_val[CHANNEL_SCAN_INDEX_X] = *(u32 *)raw_data; + break; + case HID_USAGE_SENSOR_ORIENT_TILT_Y: + incl_state->scan.incl_val[CHANNEL_SCAN_INDEX_Y] = *(u32 *)raw_data; + break; + case HID_USAGE_SENSOR_ORIENT_TILT_Z: + incl_state->scan.incl_val[CHANNEL_SCAN_INDEX_Z] = *(u32 *)raw_data; + break; + case HID_USAGE_SENSOR_TIME_TIMESTAMP: + incl_state->timestamp = + hid_sensor_convert_timestamp(&incl_state->common_attributes, + *(s64 *)raw_data); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +/* Parse report which is specific to an usage id*/ +static int incl_3d_parse_report(struct platform_device *pdev, + struct hid_sensor_hub_device *hsdev, + struct iio_chan_spec *channels, + u32 usage_id, + struct incl_3d_state *st) +{ + int ret; + + ret = sensor_hub_input_get_attribute_info(hsdev, + HID_INPUT_REPORT, + usage_id, + HID_USAGE_SENSOR_ORIENT_TILT_X, + &st->incl[CHANNEL_SCAN_INDEX_X]); + if (ret) + return ret; + incl_3d_adjust_channel_bit_mask(&channels[CHANNEL_SCAN_INDEX_X], + st->incl[CHANNEL_SCAN_INDEX_X].size); + + ret = sensor_hub_input_get_attribute_info(hsdev, + HID_INPUT_REPORT, + usage_id, + HID_USAGE_SENSOR_ORIENT_TILT_Y, + &st->incl[CHANNEL_SCAN_INDEX_Y]); + if (ret) + return ret; + incl_3d_adjust_channel_bit_mask(&channels[CHANNEL_SCAN_INDEX_Y], + st->incl[CHANNEL_SCAN_INDEX_Y].size); + + ret = sensor_hub_input_get_attribute_info(hsdev, + HID_INPUT_REPORT, + usage_id, + HID_USAGE_SENSOR_ORIENT_TILT_Z, + &st->incl[CHANNEL_SCAN_INDEX_Z]); + if (ret) + return ret; + incl_3d_adjust_channel_bit_mask(&channels[CHANNEL_SCAN_INDEX_Z], + st->incl[CHANNEL_SCAN_INDEX_Z].size); + + dev_dbg(&pdev->dev, "incl_3d %x:%x, %x:%x, %x:%x\n", + st->incl[0].index, + st->incl[0].report_id, + st->incl[1].index, st->incl[1].report_id, + st->incl[2].index, st->incl[2].report_id); + + st->scale_precision = hid_sensor_format_scale( + HID_USAGE_SENSOR_INCLINOMETER_3D, + &st->incl[CHANNEL_SCAN_INDEX_X], + &st->scale_pre_decml, &st->scale_post_decml); + + return ret; +} + +/* Function to initialize the processing for usage id */ +static int hid_incl_3d_probe(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + int ret; + static char *name = "incli_3d"; + struct iio_dev *indio_dev; + struct incl_3d_state *incl_state; + + indio_dev = devm_iio_device_alloc(&pdev->dev, + sizeof(struct incl_3d_state)); + if (!indio_dev) + return -ENOMEM; + + platform_set_drvdata(pdev, indio_dev); + + incl_state = iio_priv(indio_dev); + incl_state->common_attributes.hsdev = hsdev; + incl_state->common_attributes.pdev = pdev; + + ret = hid_sensor_parse_common_attributes(hsdev, + HID_USAGE_SENSOR_INCLINOMETER_3D, + &incl_state->common_attributes, + incl_3d_sensitivity_addresses, + ARRAY_SIZE(incl_3d_sensitivity_addresses)); + if (ret) { + dev_err(&pdev->dev, "failed to setup common attributes\n"); + return ret; + } + + indio_dev->channels = devm_kmemdup(&pdev->dev, incl_3d_channels, + sizeof(incl_3d_channels), GFP_KERNEL); + if (!indio_dev->channels) { + dev_err(&pdev->dev, "failed to duplicate channels\n"); + return -ENOMEM; + } + + ret = incl_3d_parse_report(pdev, hsdev, + (struct iio_chan_spec *)indio_dev->channels, + HID_USAGE_SENSOR_INCLINOMETER_3D, + incl_state); + if (ret) { + dev_err(&pdev->dev, "failed to setup attributes\n"); + return ret; + } + + indio_dev->num_channels = ARRAY_SIZE(incl_3d_channels); + indio_dev->info = &incl_3d_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + atomic_set(&incl_state->common_attributes.data_ready, 0); + + ret = hid_sensor_setup_trigger(indio_dev, name, + &incl_state->common_attributes); + if (ret) { + dev_err(&pdev->dev, "trigger setup failed\n"); + return ret; + } + + incl_state->callbacks.send_event = incl_3d_proc_event; + incl_state->callbacks.capture_sample = incl_3d_capture_sample; + incl_state->callbacks.pdev = pdev; + ret = sensor_hub_register_callback(hsdev, + HID_USAGE_SENSOR_INCLINOMETER_3D, + &incl_state->callbacks); + if (ret) { + dev_err(&pdev->dev, "callback reg failed\n"); + goto error_remove_trigger; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "device register failed\n"); + goto error_remove_callback; + } + + return 0; + +error_remove_callback: + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_INCLINOMETER_3D); +error_remove_trigger: + hid_sensor_remove_trigger(indio_dev, &incl_state->common_attributes); + return ret; +} + +/* Function to deinitialize the processing for usage id */ +static void hid_incl_3d_remove(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct incl_3d_state *incl_state = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_INCLINOMETER_3D); + hid_sensor_remove_trigger(indio_dev, &incl_state->common_attributes); +} + +static const struct platform_device_id hid_incl_3d_ids[] = { + { + /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-200086", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, hid_incl_3d_ids); + +static struct platform_driver hid_incl_3d_platform_driver = { + .id_table = hid_incl_3d_ids, + .driver = { + .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, + }, + .probe = hid_incl_3d_probe, + .remove = hid_incl_3d_remove, +}; +module_platform_driver(hid_incl_3d_platform_driver); + +MODULE_DESCRIPTION("HID Sensor Inclinometer 3D"); +MODULE_AUTHOR("Srinivas Pandruvada "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HID"); diff --git a/drivers/iio/orientation/hid-sensor-rotation.c b/drivers/iio/orientation/hid-sensor-rotation.c new file mode 100644 index 000000000..f8c98f866 --- /dev/null +++ b/drivers/iio/orientation/hid-sensor-rotation.c @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HID Sensors Driver + * Copyright (c) 2014, Intel Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../common/hid-sensors/hid-sensor-trigger.h" + +struct dev_rot_state { + struct hid_sensor_hub_callbacks callbacks; + struct hid_sensor_common common_attributes; + struct hid_sensor_hub_attribute_info quaternion; + struct { + IIO_DECLARE_QUATERNION(s32, sampled_vals); + /* + * ABI regression avoidance: There are two copies of the same + * timestamp in case of userspace depending on broken alignment + * from older kernels. + */ + aligned_s64 timestamp[2]; + } scan; + int scale_pre_decml; + int scale_post_decml; + int scale_precision; + int value_offset; + s64 timestamp; +}; + +static const u32 rotation_sensitivity_addresses[] = { + HID_USAGE_SENSOR_DATA_ORIENTATION, + HID_USAGE_SENSOR_ORIENT_QUATERNION, +}; + +enum { + DEV_ROT_SCAN_TYPE_16BIT, + DEV_ROT_SCAN_TYPE_32BIT, +}; + +static const struct iio_scan_type dev_rot_scan_types[] = { + [DEV_ROT_SCAN_TYPE_16BIT] = { + .sign = 's', + .realbits = 16, + /* Storage bits has to stay 32 to not break userspace. */ + .storagebits = 32, + .repeat = 4, + }, + [DEV_ROT_SCAN_TYPE_32BIT] = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + .repeat = 4, + }, +}; + +/* Channel definitions */ +static const struct iio_chan_spec dev_rot_channels[] = { + { + .type = IIO_ROT, + .modified = 1, + .channel2 = IIO_MOD_QUATERNION, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + .scan_index = 0, + .has_ext_scan_type = 1, + .ext_scan_type = dev_rot_scan_types, + .num_ext_scan_type = ARRAY_SIZE(dev_rot_scan_types), + }, + IIO_CHAN_SOFT_TIMESTAMP(1) +}; + +/* Channel read_raw handler */ +static int dev_rot_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int size, int *vals, int *val_len, long mask) +{ + struct dev_rot_state *rot_state = iio_priv(indio_dev); + struct hid_sensor_hub_device *hsdev = rot_state->common_attributes.hsdev; + struct hid_sensor_hub_attribute_info *info = &rot_state->quaternion; + u32 usage_id = HID_USAGE_SENSOR_ORIENT_QUATERNION; + union { + s16 val16[4]; + s32 val32[4]; + } raw_buf; + int ret_type; + int i; + + vals[0] = 0; + vals[1] = 0; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (size >= 4) { + if (info->size <= 0 || info->size > sizeof(raw_buf)) + return -EINVAL; + + hid_sensor_power_state(&rot_state->common_attributes, true); + + ret_type = sensor_hub_input_attr_read_values(hsdev, + hsdev->usage, + usage_id, + info->report_id, + SENSOR_HUB_SYNC, + info->size, + (u8 *)&raw_buf); + + hid_sensor_power_state(&rot_state->common_attributes, false); + + if (ret_type < 0) + return ret_type; + + switch (info->size) { + case sizeof(raw_buf.val16): + for (i = 0; i < ARRAY_SIZE(raw_buf.val16); i++) + vals[i] = raw_buf.val16[i]; + break; + case sizeof(raw_buf.val32): + for (i = 0; i < ARRAY_SIZE(raw_buf.val32); i++) + vals[i] = raw_buf.val32[i]; + break; + default: + return -EINVAL; + } + + ret_type = IIO_VAL_INT_MULTIPLE; + *val_len = 4; + } else + ret_type = -EINVAL; + break; + case IIO_CHAN_INFO_SCALE: + vals[0] = rot_state->scale_pre_decml; + vals[1] = rot_state->scale_post_decml; + return rot_state->scale_precision; + + case IIO_CHAN_INFO_OFFSET: + *vals = rot_state->value_offset; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SAMP_FREQ: + ret_type = hid_sensor_read_samp_freq_value( + &rot_state->common_attributes, &vals[0], &vals[1]); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret_type = hid_sensor_read_raw_hyst_value( + &rot_state->common_attributes, &vals[0], &vals[1]); + break; + default: + ret_type = -EINVAL; + break; + } + + return ret_type; +} + +/* Channel write_raw handler */ +static int dev_rot_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct dev_rot_state *rot_state = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = hid_sensor_write_samp_freq_value( + &rot_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret = hid_sensor_write_raw_hyst_value( + &rot_state->common_attributes, val, val2); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int dev_rot_get_current_scan_type(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct dev_rot_state *rot_state = iio_priv(indio_dev); + + switch (rot_state->quaternion.size / 4) { + case sizeof(s16): + return DEV_ROT_SCAN_TYPE_16BIT; + case sizeof(s32): + return DEV_ROT_SCAN_TYPE_32BIT; + default: + return -EINVAL; + } +} + +static const struct iio_info dev_rot_info = { + .read_raw_multi = &dev_rot_read_raw, + .write_raw = &dev_rot_write_raw, + .get_current_scan_type = &dev_rot_get_current_scan_type, +}; + +/* Callback handler to send event after all samples are received and captured */ +static int dev_rot_proc_event(struct hid_sensor_hub_device *hsdev, + u32 usage_id, void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct dev_rot_state *rot_state = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "dev_rot_proc_event\n"); + if (atomic_read(&rot_state->common_attributes.data_ready)) { + if (!rot_state->timestamp) + rot_state->timestamp = iio_get_time_ns(indio_dev); + + /* + * ABI regression avoidance: IIO previously had an incorrect + * implementation of iio_push_to_buffers_with_timestamp() that + * put the timestamp in the last 8 bytes of the buffer, which + * was incorrect according to the IIO ABI. To avoid breaking + * userspace that may be depending on this broken behavior, we + * put the timestamp in both the correct place [0] and the old + * incorrect place [1]. + */ + rot_state->scan.timestamp[0] = rot_state->timestamp; + rot_state->scan.timestamp[1] = rot_state->timestamp; + + iio_push_to_buffers(indio_dev, &rot_state->scan); + + rot_state->timestamp = 0; + } + + return 0; +} + +/* Capture samples in local storage */ +static int dev_rot_capture_sample(struct hid_sensor_hub_device *hsdev, + u32 usage_id, + size_t raw_len, char *raw_data, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct dev_rot_state *rot_state = iio_priv(indio_dev); + + if (usage_id == HID_USAGE_SENSOR_ORIENT_QUATERNION) { + if (raw_len / 4 == sizeof(s16)) { + rot_state->scan.sampled_vals[0] = ((s16 *)raw_data)[0]; + rot_state->scan.sampled_vals[1] = ((s16 *)raw_data)[1]; + rot_state->scan.sampled_vals[2] = ((s16 *)raw_data)[2]; + rot_state->scan.sampled_vals[3] = ((s16 *)raw_data)[3]; + } else { + memcpy(&rot_state->scan.sampled_vals, raw_data, + sizeof(rot_state->scan.sampled_vals)); + } + + dev_dbg(&indio_dev->dev, "Recd Quat len:%zu::%zu\n", raw_len, + sizeof(rot_state->scan.sampled_vals)); + } else if (usage_id == HID_USAGE_SENSOR_TIME_TIMESTAMP) { + rot_state->timestamp = hid_sensor_convert_timestamp(&rot_state->common_attributes, + *(s64 *)raw_data); + } + + return 0; +} + +/* Parse report which is specific to an usage id*/ +static int dev_rot_parse_report(struct platform_device *pdev, + struct hid_sensor_hub_device *hsdev, + u32 usage_id, + struct dev_rot_state *st) +{ + int ret; + + ret = sensor_hub_input_get_attribute_info(hsdev, + HID_INPUT_REPORT, + usage_id, + HID_USAGE_SENSOR_ORIENT_QUATERNION, + &st->quaternion); + if (ret) + return ret; + + dev_dbg(&pdev->dev, "dev_rot %x:%x\n", st->quaternion.index, + st->quaternion.report_id); + + dev_dbg(&pdev->dev, "dev_rot: attrib size %d\n", + st->quaternion.size); + + st->scale_precision = hid_sensor_format_scale( + hsdev->usage, + &st->quaternion, + &st->scale_pre_decml, &st->scale_post_decml); + + return 0; +} + +/* Function to initialize the processing for usage id */ +static int hid_dev_rot_probe(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + int ret; + char *name; + struct iio_dev *indio_dev; + struct dev_rot_state *rot_state; + + indio_dev = devm_iio_device_alloc(&pdev->dev, + sizeof(struct dev_rot_state)); + if (!indio_dev) + return -ENOMEM; + + platform_set_drvdata(pdev, indio_dev); + + rot_state = iio_priv(indio_dev); + rot_state->common_attributes.hsdev = hsdev; + rot_state->common_attributes.pdev = pdev; + + switch (hsdev->usage) { + case HID_USAGE_SENSOR_DEVICE_ORIENTATION: + name = "dev_rotation"; + break; + case HID_USAGE_SENSOR_RELATIVE_ORIENTATION: + name = "relative_orientation"; + break; + case HID_USAGE_SENSOR_GEOMAGNETIC_ORIENTATION: + name = "geomagnetic_orientation"; + break; + default: + return -EINVAL; + } + + ret = hid_sensor_parse_common_attributes(hsdev, + hsdev->usage, + &rot_state->common_attributes, + rotation_sensitivity_addresses, + ARRAY_SIZE(rotation_sensitivity_addresses)); + if (ret) { + dev_err(&pdev->dev, "failed to setup common attributes\n"); + return ret; + } + + ret = dev_rot_parse_report(pdev, hsdev, hsdev->usage, rot_state); + if (ret) { + dev_err(&pdev->dev, "failed to setup attributes\n"); + return ret; + } + + indio_dev->channels = dev_rot_channels; + indio_dev->num_channels = ARRAY_SIZE(dev_rot_channels); + indio_dev->info = &dev_rot_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + atomic_set(&rot_state->common_attributes.data_ready, 0); + + ret = hid_sensor_setup_trigger(indio_dev, name, + &rot_state->common_attributes); + if (ret) { + dev_err(&pdev->dev, "trigger setup failed\n"); + return ret; + } + + rot_state->callbacks.send_event = dev_rot_proc_event; + rot_state->callbacks.capture_sample = dev_rot_capture_sample; + rot_state->callbacks.pdev = pdev; + ret = sensor_hub_register_callback(hsdev, hsdev->usage, + &rot_state->callbacks); + if (ret) { + dev_err(&pdev->dev, "callback reg failed\n"); + goto error_remove_trigger; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "device register failed\n"); + goto error_remove_callback; + } + + return 0; + +error_remove_callback: + sensor_hub_remove_callback(hsdev, hsdev->usage); +error_remove_trigger: + hid_sensor_remove_trigger(indio_dev, &rot_state->common_attributes); + return ret; +} + +/* Function to deinitialize the processing for usage id */ +static void hid_dev_rot_remove(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct dev_rot_state *rot_state = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + sensor_hub_remove_callback(hsdev, hsdev->usage); + hid_sensor_remove_trigger(indio_dev, &rot_state->common_attributes); +} + +static const struct platform_device_id hid_dev_rot_ids[] = { + { + /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-20008a", + }, + { + /* Relative orientation(AG) sensor */ + .name = "HID-SENSOR-20008e", + }, + { + /* Geomagnetic orientation(AM) sensor */ + .name = "HID-SENSOR-2000c1", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, hid_dev_rot_ids); + +static struct platform_driver hid_dev_rot_platform_driver = { + .id_table = hid_dev_rot_ids, + .driver = { + .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, + }, + .probe = hid_dev_rot_probe, + .remove = hid_dev_rot_remove, +}; +module_platform_driver(hid_dev_rot_platform_driver); + +MODULE_DESCRIPTION("HID Sensor Device Rotation"); +MODULE_AUTHOR("Srinivas Pandruvada "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HID"); diff --git a/drivers/iio/position/Kconfig b/drivers/iio/position/Kconfig new file mode 100644 index 000000000..1576a6380 --- /dev/null +++ b/drivers/iio/position/Kconfig @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Linear and angular position sensors +# +# When adding new entries keep the list in alphabetical order + +menu "Linear and angular position sensors" + +config IQS624_POS + tristate "Azoteq IQS624/625 angular position sensors" + depends on MFD_IQS62X || COMPILE_TEST + help + Say Y here if you want to build support for the Azoteq IQS624 + and IQS625 angular position sensors. + + To compile this driver as a module, choose M here: the module + will be called iqs624-pos. + +config HID_SENSOR_CUSTOM_INTEL_HINGE + depends on HID_SENSOR_HUB + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select HID_SENSOR_IIO_COMMON + select HID_SENSOR_IIO_TRIGGER + tristate "HID Hinge" + help + This sensor present three angles, hinge angel, screen angles + and keyboard angle respect to horizon (ground). + Say yes here to build support for the HID custom + intel hinge sensor. + + To compile this driver as a module, choose M here: the + module will be called hid-sensor-custom-hinge. + +endmenu diff --git a/drivers/iio/position/Makefile b/drivers/iio/position/Makefile new file mode 100644 index 000000000..d70902f29 --- /dev/null +++ b/drivers/iio/position/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for IIO linear and angular position sensors +# + +# When adding new entries keep the list in alphabetical order + +obj-$(CONFIG_HID_SENSOR_CUSTOM_INTEL_HINGE) += hid-sensor-custom-intel-hinge.o +obj-$(CONFIG_IQS624_POS) += iqs624-pos.o diff --git a/drivers/iio/position/hid-sensor-custom-intel-hinge.c b/drivers/iio/position/hid-sensor-custom-intel-hinge.c new file mode 100644 index 000000000..d275bc141 --- /dev/null +++ b/drivers/iio/position/hid-sensor-custom-intel-hinge.c @@ -0,0 +1,379 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HID Sensors Driver + * Copyright (c) 2020, Intel Corporation. + */ +#include +#include +#include +#include +#include + +#include "../common/hid-sensors/hid-sensor-trigger.h" + +enum hinge_channel { + CHANNEL_SCAN_INDEX_HINGE_ANGLE, + CHANNEL_SCAN_INDEX_SCREEN_ANGLE, + CHANNEL_SCAN_INDEX_KEYBOARD_ANGLE, + CHANNEL_SCAN_INDEX_MAX, +}; + +#define CHANNEL_SCAN_INDEX_TIMESTAMP CHANNEL_SCAN_INDEX_MAX + +static const u32 hinge_addresses[CHANNEL_SCAN_INDEX_MAX] = { + HID_USAGE_SENSOR_DATA_FIELD_CUSTOM_VALUE(1), + HID_USAGE_SENSOR_DATA_FIELD_CUSTOM_VALUE(2), + HID_USAGE_SENSOR_DATA_FIELD_CUSTOM_VALUE(3) +}; + +static const char *const hinge_labels[CHANNEL_SCAN_INDEX_MAX] = { "hinge", + "screen", + "keyboard" }; + +struct hinge_state { + struct iio_dev *indio_dev; + struct hid_sensor_hub_attribute_info hinge[CHANNEL_SCAN_INDEX_MAX]; + struct hid_sensor_hub_callbacks callbacks; + struct hid_sensor_common common_attributes; + const char *labels[CHANNEL_SCAN_INDEX_MAX]; + struct { + u32 hinge_val[3]; + aligned_s64 timestamp; + } scan; + + int scale_pre_decml; + int scale_post_decml; + int scale_precision; + int value_offset; + u64 timestamp; +}; + +static const u32 hinge_sensitivity_addresses[] = { + HID_USAGE_SENSOR_DATA_FIELD_CUSTOM_VALUE(1), +}; + +/* Channel definitions */ +static const struct iio_chan_spec hinge_channels[] = { + { + .type = IIO_ANGL, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = + BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | BIT(IIO_CHAN_INFO_HYSTERESIS), + .scan_index = CHANNEL_SCAN_INDEX_HINGE_ANGLE, + .scan_type = { + .sign = 's', + .storagebits = 32, + }, + }, { + .type = IIO_ANGL, + .indexed = 1, + .channel = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = + BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | BIT(IIO_CHAN_INFO_HYSTERESIS), + .scan_index = CHANNEL_SCAN_INDEX_SCREEN_ANGLE, + .scan_type = { + .sign = 's', + .storagebits = 32, + }, + }, { + .type = IIO_ANGL, + .indexed = 1, + .channel = 2, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = + BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | BIT(IIO_CHAN_INFO_HYSTERESIS), + .scan_index = CHANNEL_SCAN_INDEX_KEYBOARD_ANGLE, + .scan_type = { + .sign = 's', + .storagebits = 32, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(CHANNEL_SCAN_INDEX_TIMESTAMP) +}; + +/* Adjust channel real bits based on report descriptor */ +static void hinge_adjust_channel_realbits(struct iio_chan_spec *channels, + int channel, int size) +{ + channels[channel].scan_type.realbits = size * 8; +} + +/* Channel read_raw handler */ +static int hinge_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct hinge_state *st = iio_priv(indio_dev); + struct hid_sensor_hub_device *hsdev; + int report_id; + s32 min; + + hsdev = st->common_attributes.hsdev; + switch (mask) { + case IIO_CHAN_INFO_RAW: + hid_sensor_power_state(&st->common_attributes, true); + report_id = st->hinge[chan->scan_index].report_id; + min = st->hinge[chan->scan_index].logical_minimum; + if (report_id < 0) { + hid_sensor_power_state(&st->common_attributes, false); + return -EINVAL; + } + + *val = sensor_hub_input_attr_get_raw_value(st->common_attributes.hsdev, + hsdev->usage, + hinge_addresses[chan->scan_index], + report_id, + SENSOR_HUB_SYNC, min < 0); + + hid_sensor_power_state(&st->common_attributes, false); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = st->scale_pre_decml; + *val2 = st->scale_post_decml; + return st->scale_precision; + case IIO_CHAN_INFO_OFFSET: + *val = st->value_offset; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + return hid_sensor_read_samp_freq_value(&st->common_attributes, + val, val2); + case IIO_CHAN_INFO_HYSTERESIS: + return hid_sensor_read_raw_hyst_value(&st->common_attributes, + val, val2); + default: + return -EINVAL; + } +} + +/* Channel write_raw handler */ +static int hinge_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct hinge_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return hid_sensor_write_samp_freq_value(&st->common_attributes, + val, val2); + case IIO_CHAN_INFO_HYSTERESIS: + return hid_sensor_write_raw_hyst_value(&st->common_attributes, + val, val2); + default: + return -EINVAL; + } +} + +static int hinge_read_label(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, char *label) +{ + struct hinge_state *st = iio_priv(indio_dev); + + return sysfs_emit(label, "%s\n", st->labels[chan->channel]); +} + +static const struct iio_info hinge_info = { + .read_raw = hinge_read_raw, + .write_raw = hinge_write_raw, + .read_label = hinge_read_label, +}; + +/* + * Callback handler to send event after all samples are received + * and captured. + */ +static int hinge_proc_event(struct hid_sensor_hub_device *hsdev, + u32 usage_id, void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct hinge_state *st = iio_priv(indio_dev); + + if (atomic_read(&st->common_attributes.data_ready)) { + if (!st->timestamp) + st->timestamp = iio_get_time_ns(indio_dev); + + iio_push_to_buffers_with_timestamp(indio_dev, &st->scan, + st->timestamp); + + st->timestamp = 0; + } + return 0; +} + +/* Capture samples in local storage */ +static int hinge_capture_sample(struct hid_sensor_hub_device *hsdev, + u32 usage_id, + size_t raw_len, char *raw_data, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct hinge_state *st = iio_priv(indio_dev); + int offset; + + switch (usage_id) { + case HID_USAGE_SENSOR_DATA_FIELD_CUSTOM_VALUE(1): + case HID_USAGE_SENSOR_DATA_FIELD_CUSTOM_VALUE(2): + case HID_USAGE_SENSOR_DATA_FIELD_CUSTOM_VALUE(3): + offset = usage_id - HID_USAGE_SENSOR_DATA_FIELD_CUSTOM_VALUE(1); + st->scan.hinge_val[offset] = *(u32 *)raw_data; + return 0; + case HID_USAGE_SENSOR_TIME_TIMESTAMP: + st->timestamp = hid_sensor_convert_timestamp(&st->common_attributes, + *(int64_t *)raw_data); + return 0; + default: + return -EINVAL; + } +} + +/* Parse report which is specific to an usage id */ +static int hinge_parse_report(struct platform_device *pdev, + struct hid_sensor_hub_device *hsdev, + struct iio_chan_spec *channels, + u32 usage_id, struct hinge_state *st) +{ + int ret; + int i; + + for (i = 0; i < CHANNEL_SCAN_INDEX_MAX; ++i) { + ret = sensor_hub_input_get_attribute_info(hsdev, + HID_INPUT_REPORT, + usage_id, + hinge_addresses[i], + &st->hinge[i]); + if (ret < 0) + return ret; + + hinge_adjust_channel_realbits(channels, i, st->hinge[i].size); + } + + st->scale_precision = hid_sensor_format_scale(HID_USAGE_SENSOR_HINGE, + &st->hinge[CHANNEL_SCAN_INDEX_HINGE_ANGLE], + &st->scale_pre_decml, &st->scale_post_decml); + + return ret; +} + +/* Function to initialize the processing for usage id */ +static int hid_hinge_probe(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + struct hinge_state *st; + struct iio_dev *indio_dev; + int ret; + int i; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + platform_set_drvdata(pdev, indio_dev); + + st = iio_priv(indio_dev); + st->common_attributes.hsdev = hsdev; + st->common_attributes.pdev = pdev; + st->indio_dev = indio_dev; + for (i = 0; i < CHANNEL_SCAN_INDEX_MAX; i++) + st->labels[i] = hinge_labels[i]; + + ret = hid_sensor_parse_common_attributes(hsdev, hsdev->usage, + &st->common_attributes, + hinge_sensitivity_addresses, + ARRAY_SIZE(hinge_sensitivity_addresses)); + if (ret) { + dev_err(&pdev->dev, "failed to setup common attributes\n"); + return ret; + } + + indio_dev->num_channels = ARRAY_SIZE(hinge_channels); + indio_dev->channels = devm_kmemdup(&pdev->dev, hinge_channels, + sizeof(hinge_channels), GFP_KERNEL); + if (!indio_dev->channels) + return -ENOMEM; + + ret = hinge_parse_report(pdev, hsdev, + (struct iio_chan_spec *)indio_dev->channels, + hsdev->usage, st); + if (ret) { + dev_err(&pdev->dev, "failed to setup attributes\n"); + return ret; + } + + indio_dev->info = &hinge_info; + indio_dev->name = "hinge"; + indio_dev->modes = INDIO_DIRECT_MODE; + + atomic_set(&st->common_attributes.data_ready, 0); + ret = hid_sensor_setup_trigger(indio_dev, indio_dev->name, + &st->common_attributes); + if (ret < 0) { + dev_err(&pdev->dev, "trigger setup failed\n"); + return ret; + } + + st->callbacks.send_event = hinge_proc_event; + st->callbacks.capture_sample = hinge_capture_sample; + st->callbacks.pdev = pdev; + ret = sensor_hub_register_callback(hsdev, hsdev->usage, &st->callbacks); + if (ret < 0) { + dev_err(&pdev->dev, "callback reg failed\n"); + goto error_remove_trigger; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "device register failed\n"); + goto error_remove_callback; + } + + return ret; + +error_remove_callback: + sensor_hub_remove_callback(hsdev, hsdev->usage); +error_remove_trigger: + hid_sensor_remove_trigger(indio_dev, &st->common_attributes); + return ret; +} + +/* Function to deinitialize the processing for usage id */ +static void hid_hinge_remove(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct hinge_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + sensor_hub_remove_callback(hsdev, hsdev->usage); + hid_sensor_remove_trigger(indio_dev, &st->common_attributes); +} + +static const struct platform_device_id hid_hinge_ids[] = { + { + /* Format: HID-SENSOR-INT-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-INT-020b", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, hid_hinge_ids); + +static struct platform_driver hid_hinge_platform_driver = { + .id_table = hid_hinge_ids, + .driver = { + .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, + }, + .probe = hid_hinge_probe, + .remove = hid_hinge_remove, +}; +module_platform_driver(hid_hinge_platform_driver); + +MODULE_DESCRIPTION("HID Sensor INTEL Hinge"); +MODULE_AUTHOR("Ye Xiang "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HID"); diff --git a/drivers/iio/position/iqs624-pos.c b/drivers/iio/position/iqs624-pos.c new file mode 100644 index 000000000..8239239c6 --- /dev/null +++ b/drivers/iio/position/iqs624-pos.c @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS624/625 Angular Position Sensors + * + * Copyright (C) 2019 Jeff LaBundy + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IQS624_POS_DEG_OUT 0x16 + +#define IQS624_POS_SCALE1 (314159 / 180) +#define IQS624_POS_SCALE2 100000 + +struct iqs624_pos_private { + struct iqs62x_core *iqs62x; + struct iio_dev *indio_dev; + struct notifier_block notifier; + struct mutex lock; + bool angle_en; + u16 angle; +}; + +static int iqs624_pos_angle_en(struct iqs62x_core *iqs62x, bool angle_en) +{ + unsigned int event_mask = IQS624_HALL_UI_WHL_EVENT; + + /* + * The IQS625 reports angular position in the form of coarse intervals, + * so only interval change events are unmasked. Conversely, the IQS624 + * reports angular position down to one degree of resolution, so wheel + * movement events are unmasked instead. + */ + if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM) + event_mask = IQS624_HALL_UI_INT_EVENT; + + return regmap_update_bits(iqs62x->regmap, IQS624_HALL_UI, event_mask, + angle_en ? 0 : 0xFF); +} + +static int iqs624_pos_notifier(struct notifier_block *notifier, + unsigned long event_flags, void *context) +{ + struct iqs62x_event_data *event_data = context; + struct iqs624_pos_private *iqs624_pos; + struct iqs62x_core *iqs62x; + struct iio_dev *indio_dev; + u16 angle = event_data->ui_data; + s64 timestamp; + int ret; + + iqs624_pos = container_of(notifier, struct iqs624_pos_private, + notifier); + indio_dev = iqs624_pos->indio_dev; + timestamp = iio_get_time_ns(indio_dev); + + iqs62x = iqs624_pos->iqs62x; + if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM) + angle = event_data->interval; + + mutex_lock(&iqs624_pos->lock); + + if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) { + ret = iqs624_pos_angle_en(iqs62x, iqs624_pos->angle_en); + if (ret) { + dev_err(indio_dev->dev.parent, + "Failed to re-initialize device: %d\n", ret); + ret = NOTIFY_BAD; + } else { + ret = NOTIFY_OK; + } + } else if (iqs624_pos->angle_en && (angle != iqs624_pos->angle)) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_ANGL, 0, + IIO_EV_TYPE_CHANGE, + IIO_EV_DIR_NONE), + timestamp); + + iqs624_pos->angle = angle; + ret = NOTIFY_OK; + } else { + ret = NOTIFY_DONE; + } + + mutex_unlock(&iqs624_pos->lock); + + return ret; +} + +static void iqs624_pos_notifier_unregister(void *context) +{ + struct iqs624_pos_private *iqs624_pos = context; + struct iio_dev *indio_dev = iqs624_pos->indio_dev; + int ret; + + ret = blocking_notifier_chain_unregister(&iqs624_pos->iqs62x->nh, + &iqs624_pos->notifier); + if (ret) + dev_err(indio_dev->dev.parent, + "Failed to unregister notifier: %d\n", ret); +} + +static int iqs624_pos_angle_get(struct iqs62x_core *iqs62x, unsigned int *val) +{ + int ret; + __le16 val_buf; + + if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM) + return regmap_read(iqs62x->regmap, iqs62x->dev_desc->interval, + val); + + ret = regmap_raw_read(iqs62x->regmap, IQS624_POS_DEG_OUT, &val_buf, + sizeof(val_buf)); + if (ret) + return ret; + + *val = le16_to_cpu(val_buf); + + return 0; +} + +static int iqs624_pos_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev); + struct iqs62x_core *iqs62x = iqs624_pos->iqs62x; + unsigned int scale = 1; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = iqs624_pos_angle_get(iqs62x, val); + if (ret) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + if (iqs62x->dev_desc->prod_num == IQS625_PROD_NUM) { + ret = regmap_read(iqs62x->regmap, IQS624_INTERVAL_DIV, + &scale); + if (ret) + return ret; + } + + *val = scale * IQS624_POS_SCALE1; + *val2 = IQS624_POS_SCALE2; + return IIO_VAL_FRACTIONAL; + + default: + return -EINVAL; + } +} + +static int iqs624_pos_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev); + int ret; + + mutex_lock(&iqs624_pos->lock); + ret = iqs624_pos->angle_en; + mutex_unlock(&iqs624_pos->lock); + + return ret; +} + +static int iqs624_pos_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct iqs624_pos_private *iqs624_pos = iio_priv(indio_dev); + struct iqs62x_core *iqs62x = iqs624_pos->iqs62x; + unsigned int val; + int ret; + + mutex_lock(&iqs624_pos->lock); + + ret = iqs624_pos_angle_get(iqs62x, &val); + if (ret) + goto err_mutex; + + ret = iqs624_pos_angle_en(iqs62x, state); + if (ret) + goto err_mutex; + + iqs624_pos->angle = val; + iqs624_pos->angle_en = state; + +err_mutex: + mutex_unlock(&iqs624_pos->lock); + + return ret; +} + +static const struct iio_info iqs624_pos_info = { + .read_raw = &iqs624_pos_read_raw, + .read_event_config = iqs624_pos_read_event_config, + .write_event_config = iqs624_pos_write_event_config, +}; + +static const struct iio_event_spec iqs624_pos_events[] = { + { + .type = IIO_EV_TYPE_CHANGE, + .dir = IIO_EV_DIR_NONE, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec iqs624_pos_channels[] = { + { + .type = IIO_ANGL, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .event_spec = iqs624_pos_events, + .num_event_specs = ARRAY_SIZE(iqs624_pos_events), + }, +}; + +static int iqs624_pos_probe(struct platform_device *pdev) +{ + struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent); + struct iqs624_pos_private *iqs624_pos; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*iqs624_pos)); + if (!indio_dev) + return -ENOMEM; + + iqs624_pos = iio_priv(indio_dev); + iqs624_pos->iqs62x = iqs62x; + iqs624_pos->indio_dev = indio_dev; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = iqs624_pos_channels; + indio_dev->num_channels = ARRAY_SIZE(iqs624_pos_channels); + indio_dev->name = iqs62x->dev_desc->dev_name; + indio_dev->info = &iqs624_pos_info; + + mutex_init(&iqs624_pos->lock); + + iqs624_pos->notifier.notifier_call = iqs624_pos_notifier; + ret = blocking_notifier_chain_register(&iqs624_pos->iqs62x->nh, + &iqs624_pos->notifier); + if (ret) { + dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret); + return ret; + } + + ret = devm_add_action_or_reset(&pdev->dev, + iqs624_pos_notifier_unregister, + iqs624_pos); + if (ret) + return ret; + + return devm_iio_device_register(&pdev->dev, indio_dev); +} + +static struct platform_driver iqs624_pos_platform_driver = { + .driver = { + .name = "iqs624-pos", + }, + .probe = iqs624_pos_probe, +}; +module_platform_driver(iqs624_pos_platform_driver); + +MODULE_AUTHOR("Jeff LaBundy "); +MODULE_DESCRIPTION("Azoteq IQS624/625 Angular Position Sensors"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:iqs624-pos"); diff --git a/drivers/iio/potentiometer/Kconfig b/drivers/iio/potentiometer/Kconfig new file mode 100644 index 000000000..e6a9a3c67 --- /dev/null +++ b/drivers/iio/potentiometer/Kconfig @@ -0,0 +1,149 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Potentiometer drivers +# +# When adding new entries keep the list in alphabetical order + +menu "Digital potentiometers" + +config AD5110 + tristate "Analog Devices AD5110 and similar Digital Potentiometer driver" + depends on I2C + help + Say yes here to build support for the Analog Devices AD5110, AD5112 + and AD5114 digital potentiometer chip. + + To compile this driver as a module, choose M here: the + module will be called ad5110. + +config AD5272 + tristate "Analog Devices AD5272 and similar Digital Potentiometer driver" + depends on I2C + help + Say yes here to build support for the Analog Devices AD5272 and AD5274 + digital potentiometer chip. + + To compile this driver as a module, choose M here: the + module will be called ad5272. + +config DS1803 + tristate "Maxim Integrated DS1803 and similar Digital Potentiometer driver" + depends on I2C + help + Say yes here to build support for the Maxim Integrated DS1803 and + DS3502 digital potentiometer chip. + + To compile this driver as a module, choose M here: the + module will be called ds1803. + +config MAX5432 + tristate "Maxim MAX5432-MAX5435 Digital Potentiometer driver" + depends on I2C + help + Say yes here to build support for the Maxim + MAX5432, MAX5433, MAX5434 and MAX5435 digital + potentiometer chips. + + To compile this driver as a module, choose M here: the + module will be called max5432. + +config MAX5481 + tristate "Maxim MAX5481-MAX5484 Digital Potentiometer driver" + depends on SPI + help + Say yes here to build support for the Maxim + MAX5481, MAX5482, MAX5483, MAX5484 digital potentiometer + chips. + + To compile this driver as a module, choose M here: the + module will be called max5481. + +config MAX5487 + tristate "Maxim MAX5487/MAX5488/MAX5489 Digital Potentiometer driver" + depends on SPI + help + Say yes here to build support for the Maxim + MAX5487, MAX5488, MAX5489 digital potentiometer + chips. + + To compile this driver as a module, choose M here: the + module will be called max5487. + +config MCP4018 + tristate "Microchip MCP4017/18/19 Digital Potentiometer driver" + depends on I2C + help + Say yes here to build support for the Microchip + MCP4017, MCP4018, MCP4019 + digital potentiometer chips. + + To compile this driver as a module, choose M here: the + module will be called mcp4018. + +config MCP4131 + tristate "Microchip MCP413X/414X/415X/416X/423X/424X/425X/426X Digital Potentiometer driver" + depends on SPI + help + Say yes here to build support for the Microchip + MCP4131, MCP4132, + MCP4141, MCP4142, + MCP4151, MCP4152, + MCP4161, MCP4162, + MCP4231, MCP4232, + MCP4241, MCP4242, + MCP4251, MCP4252, + MCP4261, MCP4262, + digital potentiometer chips. + + To compile this driver as a module, choose M here: the + module will be called mcp4131. + +config MCP4531 + tristate "Microchip MCP45xx/MCP46xx Digital Potentiometer driver" + depends on I2C + help + Say yes here to build support for the Microchip + MCP4531, MCP4532, MCP4541, MCP4542, + MCP4551, MCP4552, MCP4561, MCP4562, + MCP4631, MCP4632, MCP4641, MCP4642, + MCP4651, MCP4652, MCP4661, MCP4662 + digital potentiometer chips. + + To compile this driver as a module, choose M here: the + module will be called mcp4531. + +config MCP41010 + tristate "Microchip MCP41xxx/MCP42xxx Digital Potentiometer driver" + depends on SPI + help + Say yes here to build support for the Microchip + MCP41010, MCP41050, MCP41100, + MCP42010, MCP42050, MCP42100 + digital potentiometer chips. + + To compile this driver as a module, choose M here: the + module will be called mcp41010. + +config TPL0102 + tristate "Texas Instruments digital potentiometer driver" + depends on I2C + select REGMAP_I2C + help + Say yes here to build support for the Texas Instruments + TPL0102, TPL0402 + digital potentiometer chips. + + To compile this driver as a module, choose M here: the + module will be called tpl0102. + +config X9250 + tristate "Renesas X9250 quad controlled potentiometers" + depends on SPI + help + Enable support for the Renesas X9250 quad controlled + potentiometers. + + To compile this driver as a module, choose M here: the module + will be called x9250. + +endmenu diff --git a/drivers/iio/potentiometer/Makefile b/drivers/iio/potentiometer/Makefile new file mode 100644 index 000000000..d11fb7391 --- /dev/null +++ b/drivers/iio/potentiometer/Makefile @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for industrial I/O potentiometer drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_AD5110) += ad5110.o +obj-$(CONFIG_AD5272) += ad5272.o +obj-$(CONFIG_DS1803) += ds1803.o +obj-$(CONFIG_MAX5432) += max5432.o +obj-$(CONFIG_MAX5481) += max5481.o +obj-$(CONFIG_MAX5487) += max5487.o +obj-$(CONFIG_MCP4018) += mcp4018.o +obj-$(CONFIG_MCP4131) += mcp4131.o +obj-$(CONFIG_MCP4531) += mcp4531.o +obj-$(CONFIG_MCP41010) += mcp41010.o +obj-$(CONFIG_TPL0102) += tpl0102.o +obj-$(CONFIG_X9250) += x9250.o diff --git a/drivers/iio/potentiometer/ad5110.c b/drivers/iio/potentiometer/ad5110.c new file mode 100644 index 000000000..aaf02cc7a --- /dev/null +++ b/drivers/iio/potentiometer/ad5110.c @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Analog Devices AD5110 digital potentiometer driver + * + * Copyright (C) 2021 Mugilraj Dhavachelvan + * + * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/AD5110_5112_5114.pdf + */ + +#include +#include +#include +#include +#include + +#include +#include + +/* AD5110 commands */ +#define AD5110_EEPROM_WR 1 +#define AD5110_RDAC_WR 2 +#define AD5110_SHUTDOWN 3 +#define AD5110_RESET 4 +#define AD5110_RDAC_RD 5 +#define AD5110_EEPROM_RD 6 + +/* AD5110_EEPROM_RD data */ +#define AD5110_WIPER_POS 0 +#define AD5110_RESISTOR_TOL 1 + +#define AD5110_WIPER_RESISTANCE 70 + +struct ad5110_cfg { + int max_pos; + int kohms; + int shift; +}; + +enum ad5110_type { + AD5110_10, + AD5110_80, + AD5112_05, + AD5112_10, + AD5112_80, + AD5114_10, + AD5114_80, +}; + +static const struct ad5110_cfg ad5110_cfg[] = { + [AD5110_10] = { .max_pos = 128, .kohms = 10 }, + [AD5110_80] = { .max_pos = 128, .kohms = 80 }, + [AD5112_05] = { .max_pos = 64, .kohms = 5, .shift = 1 }, + [AD5112_10] = { .max_pos = 64, .kohms = 10, .shift = 1 }, + [AD5112_80] = { .max_pos = 64, .kohms = 80, .shift = 1 }, + [AD5114_10] = { .max_pos = 32, .kohms = 10, .shift = 2 }, + [AD5114_80] = { .max_pos = 32, .kohms = 80, .shift = 2 }, +}; + +struct ad5110_data { + struct i2c_client *client; + s16 tol; /* resistor tolerance */ + bool enable; + struct mutex lock; + const struct ad5110_cfg *cfg; + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + */ + u8 buf[2] __aligned(IIO_DMA_MINALIGN); +}; + +static const struct iio_chan_spec ad5110_channels[] = { + { + .type = IIO_RESISTANCE, + .output = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_ENABLE), + }, +}; + +static int ad5110_read(struct ad5110_data *data, u8 cmd, int *val) +{ + int ret; + + mutex_lock(&data->lock); + data->buf[0] = cmd; + data->buf[1] = *val; + + ret = i2c_master_send_dmasafe(data->client, data->buf, sizeof(data->buf)); + if (ret < 0) { + goto error; + } else if (ret != sizeof(data->buf)) { + ret = -EIO; + goto error; + } + + ret = i2c_master_recv_dmasafe(data->client, data->buf, 1); + if (ret < 0) { + goto error; + } else if (ret != 1) { + ret = -EIO; + goto error; + } + + *val = data->buf[0]; + ret = 0; + +error: + mutex_unlock(&data->lock); + return ret; +} + +static int ad5110_write(struct ad5110_data *data, u8 cmd, u8 val) +{ + int ret; + + mutex_lock(&data->lock); + data->buf[0] = cmd; + data->buf[1] = val; + + ret = i2c_master_send_dmasafe(data->client, data->buf, sizeof(data->buf)); + if (ret < 0) { + goto error; + } else if (ret != sizeof(data->buf)) { + ret = -EIO; + goto error; + } + + ret = 0; + +error: + mutex_unlock(&data->lock); + return ret; +} + +static int ad5110_resistor_tol(struct ad5110_data *data, u8 cmd, int val) +{ + int ret; + + ret = ad5110_read(data, cmd, &val); + if (ret) + return ret; + + data->tol = data->cfg->kohms * (val & GENMASK(6, 0)) * 10 / 8; + if (!(val & BIT(7))) + data->tol *= -1; + + return 0; +} + +static ssize_t store_eeprom_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad5110_data *data = iio_priv(indio_dev); + int val = AD5110_WIPER_POS; + int ret; + + ret = ad5110_read(data, AD5110_EEPROM_RD, &val); + if (ret) + return ret; + + val = val >> data->cfg->shift; + return iio_format_value(buf, IIO_VAL_INT, 1, &val); +} + +static ssize_t store_eeprom_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad5110_data *data = iio_priv(indio_dev); + int ret; + + ret = ad5110_write(data, AD5110_EEPROM_WR, 0); + if (ret) { + dev_err(&data->client->dev, "RDAC to EEPROM write failed\n"); + return ret; + } + + /* The storing of EEPROM data takes approximately 18 ms. */ + msleep(20); + + return len; +} + +static IIO_DEVICE_ATTR_RW(store_eeprom, 0); + +static struct attribute *ad5110_attributes[] = { + &iio_dev_attr_store_eeprom.dev_attr.attr, + NULL +}; + +static const struct attribute_group ad5110_attribute_group = { + .attrs = ad5110_attributes, +}; + +static int ad5110_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ad5110_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = ad5110_read(data, AD5110_RDAC_RD, val); + if (ret) + return ret; + + *val = *val >> data->cfg->shift; + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + *val = AD5110_WIPER_RESISTANCE * data->cfg->max_pos; + *val2 = 1000 * data->cfg->kohms + data->tol; + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_SCALE: + *val = 1000 * data->cfg->kohms + data->tol; + *val2 = data->cfg->max_pos; + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_ENABLE: + *val = data->enable; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad5110_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ad5110_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val > data->cfg->max_pos || val < 0) + return -EINVAL; + + return ad5110_write(data, AD5110_RDAC_WR, val << data->cfg->shift); + case IIO_CHAN_INFO_ENABLE: + if (val < 0 || val > 1) + return -EINVAL; + if (data->enable == val) + return 0; + ret = ad5110_write(data, AD5110_SHUTDOWN, val ? 0 : 1); + if (ret) + return ret; + data->enable = val; + return 0; + default: + return -EINVAL; + } +} + +static const struct iio_info ad5110_info = { + .read_raw = ad5110_read_raw, + .write_raw = ad5110_write_raw, + .attrs = &ad5110_attribute_group, +}; + +#define AD5110_COMPATIBLE(of_compatible, cfg) { \ + .compatible = of_compatible, \ + .data = &ad5110_cfg[cfg], \ +} + +static const struct of_device_id ad5110_of_match[] = { + AD5110_COMPATIBLE("adi,ad5110-10", AD5110_10), + AD5110_COMPATIBLE("adi,ad5110-80", AD5110_80), + AD5110_COMPATIBLE("adi,ad5112-05", AD5112_05), + AD5110_COMPATIBLE("adi,ad5112-10", AD5112_10), + AD5110_COMPATIBLE("adi,ad5112-80", AD5112_80), + AD5110_COMPATIBLE("adi,ad5114-10", AD5114_10), + AD5110_COMPATIBLE("adi,ad5114-80", AD5114_80), + { } +}; +MODULE_DEVICE_TABLE(of, ad5110_of_match); + +#define AD5110_ID_TABLE(_name, cfg) { \ + .name = _name, \ + .driver_data = (kernel_ulong_t)&ad5110_cfg[cfg], \ +} + +static const struct i2c_device_id ad5110_id[] = { + AD5110_ID_TABLE("ad5110-10", AD5110_10), + AD5110_ID_TABLE("ad5110-80", AD5110_80), + AD5110_ID_TABLE("ad5112-05", AD5112_05), + AD5110_ID_TABLE("ad5112-10", AD5112_10), + AD5110_ID_TABLE("ad5112-80", AD5112_80), + AD5110_ID_TABLE("ad5114-10", AD5114_10), + AD5110_ID_TABLE("ad5114-80", AD5114_80), + { } +}; +MODULE_DEVICE_TABLE(i2c, ad5110_id); + +static int ad5110_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct ad5110_data *data; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + mutex_init(&data->lock); + data->enable = 1; + data->cfg = i2c_get_match_data(client); + + /* refresh RDAC register with EEPROM */ + ret = ad5110_write(data, AD5110_RESET, 0); + if (ret) { + dev_err(dev, "Refresh RDAC with EEPROM failed\n"); + return ret; + } + + ret = ad5110_resistor_tol(data, AD5110_EEPROM_RD, AD5110_RESISTOR_TOL); + if (ret) { + dev_err(dev, "Read resistor tolerance failed\n"); + return ret; + } + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ad5110_info; + indio_dev->channels = ad5110_channels; + indio_dev->num_channels = ARRAY_SIZE(ad5110_channels); + indio_dev->name = client->name; + + return devm_iio_device_register(dev, indio_dev); +} + +static struct i2c_driver ad5110_driver = { + .driver = { + .name = "ad5110", + .of_match_table = ad5110_of_match, + }, + .probe = ad5110_probe, + .id_table = ad5110_id, +}; +module_i2c_driver(ad5110_driver); + +MODULE_AUTHOR("Mugilraj Dhavachelvan "); +MODULE_DESCRIPTION("AD5110 digital potentiometer"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/potentiometer/ad5272.c b/drivers/iio/potentiometer/ad5272.c new file mode 100644 index 000000000..35fe1575e --- /dev/null +++ b/drivers/iio/potentiometer/ad5272.c @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Analog Devices AD5272 digital potentiometer driver + * Copyright (C) 2018 Phil Reid + * + * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/AD5272_5274.pdf + * + * DEVID #Wipers #Positions Resistor Opts (kOhm) i2c address + * ad5272 1 1024 20, 50, 100 01011xx + * ad5274 1 256 20, 100 01011xx + */ + +#include +#include +#include +#include +#include + +#define AD5272_RDAC_WR 1 +#define AD5272_RDAC_RD 2 +#define AD5272_RESET 4 +#define AD5272_CTL 7 + +#define AD5272_RDAC_WR_EN BIT(1) + +struct ad5272_cfg { + int max_pos; + int kohms; + int shift; +}; + +enum ad5272_type { + AD5272_020, + AD5272_050, + AD5272_100, + AD5274_020, + AD5274_100, +}; + +static const struct ad5272_cfg ad5272_cfg[] = { + [AD5272_020] = { .max_pos = 1024, .kohms = 20 }, + [AD5272_050] = { .max_pos = 1024, .kohms = 50 }, + [AD5272_100] = { .max_pos = 1024, .kohms = 100 }, + [AD5274_020] = { .max_pos = 256, .kohms = 20, .shift = 2 }, + [AD5274_100] = { .max_pos = 256, .kohms = 100, .shift = 2 }, +}; + +struct ad5272_data { + struct i2c_client *client; + struct mutex lock; + const struct ad5272_cfg *cfg; + u8 buf[2] __aligned(IIO_DMA_MINALIGN); +}; + +static const struct iio_chan_spec ad5272_channel = { + .type = IIO_RESISTANCE, + .output = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), +}; + +static int ad5272_write(struct ad5272_data *data, int reg, int val) +{ + int ret; + + data->buf[0] = (reg << 2) | ((val >> 8) & 0x3); + data->buf[1] = (u8)val; + + mutex_lock(&data->lock); + ret = i2c_master_send(data->client, data->buf, sizeof(data->buf)); + mutex_unlock(&data->lock); + return ret < 0 ? ret : 0; +} + +static int ad5272_read(struct ad5272_data *data, int reg, int *val) +{ + int ret; + + data->buf[0] = reg << 2; + data->buf[1] = 0; + + mutex_lock(&data->lock); + ret = i2c_master_send(data->client, data->buf, sizeof(data->buf)); + if (ret < 0) + goto error; + + ret = i2c_master_recv(data->client, data->buf, sizeof(data->buf)); + if (ret < 0) + goto error; + + *val = ((data->buf[0] & 0x3) << 8) | data->buf[1]; + ret = 0; +error: + mutex_unlock(&data->lock); + return ret; +} + +static int ad5272_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ad5272_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + ret = ad5272_read(data, AD5272_RDAC_RD, val); + *val = *val >> data->cfg->shift; + return ret ? ret : IIO_VAL_INT; + } + case IIO_CHAN_INFO_SCALE: + *val = 1000 * data->cfg->kohms; + *val2 = data->cfg->max_pos; + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; +} + +static int ad5272_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ad5272_data *data = iio_priv(indio_dev); + + if (mask != IIO_CHAN_INFO_RAW) + return -EINVAL; + + if (val >= data->cfg->max_pos || val < 0 || val2) + return -EINVAL; + + return ad5272_write(data, AD5272_RDAC_WR, val << data->cfg->shift); +} + +static const struct iio_info ad5272_info = { + .read_raw = ad5272_read_raw, + .write_raw = ad5272_write_raw, +}; + +static int ad5272_reset(struct ad5272_data *data) +{ + struct gpio_desc *reset_gpio; + + reset_gpio = devm_gpiod_get_optional(&data->client->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(reset_gpio)) + return PTR_ERR(reset_gpio); + + if (reset_gpio) { + udelay(1); + gpiod_set_value(reset_gpio, 0); + } else { + ad5272_write(data, AD5272_RESET, 0); + } + usleep_range(1000, 2000); + + return 0; +} + +static int ad5272_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct ad5272_data *data; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + i2c_set_clientdata(client, indio_dev); + + data = iio_priv(indio_dev); + data->client = client; + mutex_init(&data->lock); + data->cfg = &ad5272_cfg[id->driver_data]; + + ret = ad5272_reset(data); + if (ret) + return ret; + + ret = ad5272_write(data, AD5272_CTL, AD5272_RDAC_WR_EN); + if (ret < 0) + return -ENODEV; + + indio_dev->info = &ad5272_info; + indio_dev->channels = &ad5272_channel; + indio_dev->num_channels = 1; + indio_dev->name = client->name; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ad5272_dt_ids[] = { + { .compatible = "adi,ad5272-020", .data = (void *)AD5272_020 }, + { .compatible = "adi,ad5272-050", .data = (void *)AD5272_050 }, + { .compatible = "adi,ad5272-100", .data = (void *)AD5272_100 }, + { .compatible = "adi,ad5274-020", .data = (void *)AD5274_020 }, + { .compatible = "adi,ad5274-100", .data = (void *)AD5274_100 }, + { } +}; +MODULE_DEVICE_TABLE(of, ad5272_dt_ids); + +static const struct i2c_device_id ad5272_id[] = { + { .name = "ad5272-020", .driver_data = AD5272_020 }, + { .name = "ad5272-050", .driver_data = AD5272_050 }, + { .name = "ad5272-100", .driver_data = AD5272_100 }, + { .name = "ad5274-020", .driver_data = AD5274_020 }, + { .name = "ad5274-100", .driver_data = AD5274_100 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ad5272_id); + +static struct i2c_driver ad5272_driver = { + .driver = { + .name = "ad5272", + .of_match_table = ad5272_dt_ids, + }, + .probe = ad5272_probe, + .id_table = ad5272_id, +}; + +module_i2c_driver(ad5272_driver); + +MODULE_AUTHOR("Phil Reid "); +MODULE_DESCRIPTION("AD5272 digital potentiometer"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/potentiometer/ds1803.c b/drivers/iio/potentiometer/ds1803.c new file mode 100644 index 000000000..5046119b7 --- /dev/null +++ b/drivers/iio/potentiometer/ds1803.c @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Maxim Integrated DS1803 and similar digital potentiometer driver + * Copyright (c) 2016 Slawomir Stepien + * Copyright (c) 2022 Jagath Jog J + * + * Datasheet: https://datasheets.maximintegrated.com/en/ds/DS1803.pdf + * Datasheet: https://datasheets.maximintegrated.com/en/ds/DS3502.pdf + * + * DEVID #Wipers #Positions Resistor Opts (kOhm) i2c address + * ds1803 2 256 10, 50, 100 0101xxx + * ds3502 1 128 10 01010xx + */ + +#include +#include +#include +#include +#include + +#define DS1803_WIPER_0 0xA9 +#define DS1803_WIPER_1 0xAA +#define DS3502_WR_IVR 0x00 + +enum ds1803_type { + DS1803_010, + DS1803_050, + DS1803_100, + DS3502, +}; + +struct ds1803_cfg { + int wipers; + int avail[3]; + int kohms; + const struct iio_chan_spec *channels; + u8 num_channels; + int (*read)(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val); +}; + +struct ds1803_data { + struct i2c_client *client; + const struct ds1803_cfg *cfg; +}; + +#define DS1803_CHANNEL(ch, addr) { \ + .type = IIO_RESISTANCE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (ch), \ + .address = (addr), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_RAW), \ +} + +static const struct iio_chan_spec ds1803_channels[] = { + DS1803_CHANNEL(0, DS1803_WIPER_0), + DS1803_CHANNEL(1, DS1803_WIPER_1), +}; + +static const struct iio_chan_spec ds3502_channels[] = { + DS1803_CHANNEL(0, DS3502_WR_IVR), +}; + +static int ds1803_read(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct ds1803_data *data = iio_priv(indio_dev); + int ret; + u8 result[ARRAY_SIZE(ds1803_channels)]; + + ret = i2c_master_recv(data->client, result, indio_dev->num_channels); + if (ret < 0) + return ret; + + *val = result[chan->channel]; + return ret; +} + +static int ds3502_read(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct ds1803_data *data = iio_priv(indio_dev); + int ret; + + ret = i2c_smbus_read_byte_data(data->client, chan->address); + if (ret < 0) + return ret; + + *val = ret; + return ret; +} + +static const struct ds1803_cfg ds1803_cfg[] = { + [DS1803_010] = { + .wipers = 2, + .avail = { 0, 1, 255 }, + .kohms = 10, + .channels = ds1803_channels, + .num_channels = ARRAY_SIZE(ds1803_channels), + .read = ds1803_read, + }, + [DS1803_050] = { + .wipers = 2, + .avail = { 0, 1, 255 }, + .kohms = 50, + .channels = ds1803_channels, + .num_channels = ARRAY_SIZE(ds1803_channels), + .read = ds1803_read, + }, + [DS1803_100] = { + .wipers = 2, + .avail = { 0, 1, 255 }, + .kohms = 100, + .channels = ds1803_channels, + .num_channels = ARRAY_SIZE(ds1803_channels), + .read = ds1803_read, + }, + [DS3502] = { + .wipers = 1, + .avail = { 0, 1, 127 }, + .kohms = 10, + .channels = ds3502_channels, + .num_channels = ARRAY_SIZE(ds3502_channels), + .read = ds3502_read, + }, +}; + +static int ds1803_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ds1803_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = data->cfg->read(indio_dev, chan, val); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = 1000 * data->cfg->kohms; + *val2 = data->cfg->avail[2]; /* Max wiper position */ + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; +} + +static int ds1803_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ds1803_data *data = iio_priv(indio_dev); + u8 addr = chan->address; + int max_pos = data->cfg->avail[2]; + + if (val2 != 0) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val > max_pos || val < 0) + return -EINVAL; + break; + default: + return -EINVAL; + } + + return i2c_smbus_write_byte_data(data->client, addr, val); +} + +static int ds1803_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, + int *length, long mask) +{ + struct ds1803_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *vals = data->cfg->avail; + *length = ARRAY_SIZE(data->cfg->avail); + *type = IIO_VAL_INT; + return IIO_AVAIL_RANGE; + } + return -EINVAL; +} + +static const struct iio_info ds1803_info = { + .read_raw = ds1803_read_raw, + .write_raw = ds1803_write_raw, + .read_avail = ds1803_read_avail, +}; + +static int ds1803_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct ds1803_data *data; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + i2c_set_clientdata(client, indio_dev); + + data = iio_priv(indio_dev); + data->client = client; + data->cfg = i2c_get_match_data(client); + + indio_dev->info = &ds1803_info; + indio_dev->channels = data->cfg->channels; + indio_dev->num_channels = data->cfg->num_channels; + indio_dev->name = client->name; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ds1803_dt_ids[] = { + { .compatible = "maxim,ds1803-010", .data = &ds1803_cfg[DS1803_010] }, + { .compatible = "maxim,ds1803-050", .data = &ds1803_cfg[DS1803_050] }, + { .compatible = "maxim,ds1803-100", .data = &ds1803_cfg[DS1803_100] }, + { .compatible = "maxim,ds3502", .data = &ds1803_cfg[DS3502] }, + { } +}; +MODULE_DEVICE_TABLE(of, ds1803_dt_ids); + +static const struct i2c_device_id ds1803_id[] = { + { .name = "ds1803-010", .driver_data = (kernel_ulong_t)&ds1803_cfg[DS1803_010] }, + { .name = "ds1803-050", .driver_data = (kernel_ulong_t)&ds1803_cfg[DS1803_050] }, + { .name = "ds1803-100", .driver_data = (kernel_ulong_t)&ds1803_cfg[DS1803_100] }, + { .name = "ds3502", .driver_data = (kernel_ulong_t)&ds1803_cfg[DS3502] }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ds1803_id); + +static struct i2c_driver ds1803_driver = { + .driver = { + .name = "ds1803", + .of_match_table = ds1803_dt_ids, + }, + .probe = ds1803_probe, + .id_table = ds1803_id, +}; + +module_i2c_driver(ds1803_driver); + +MODULE_AUTHOR("Slawomir Stepien "); +MODULE_AUTHOR("Jagath Jog J "); +MODULE_DESCRIPTION("DS1803 digital potentiometer"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/potentiometer/max5432.c b/drivers/iio/potentiometer/max5432.c new file mode 100644 index 000000000..f6d3ec04f --- /dev/null +++ b/drivers/iio/potentiometer/max5432.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Maxim Integrated MAX5432-MAX5435 digital potentiometer driver + * Copyright (C) 2019 Martin Kaiser + * + * Datasheet: + * https://datasheets.maximintegrated.com/en/ds/MAX5432-MAX5435.pdf + */ + +#include +#include +#include +#include +#include + +/* All chip variants have 32 wiper positions. */ +#define MAX5432_MAX_POS 31 + +#define MAX5432_OHM_50K (50 * 1000) +#define MAX5432_OHM_100K (100 * 1000) + +/* Update the volatile (currently active) setting. */ +#define MAX5432_CMD_VREG 0x11 + +struct max5432_data { + struct i2c_client *client; + unsigned long ohm; +}; + +static const struct iio_chan_spec max5432_channels[] = { + { + .type = IIO_RESISTANCE, + .indexed = 1, + .output = 1, + .channel = 0, + .address = MAX5432_CMD_VREG, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + } +}; + +static int max5432_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max5432_data *data = iio_priv(indio_dev); + + if (mask != IIO_CHAN_INFO_SCALE) + return -EINVAL; + + if (unlikely(data->ohm > INT_MAX)) + return -ERANGE; + + *val = data->ohm; + *val2 = MAX5432_MAX_POS; + + return IIO_VAL_FRACTIONAL; +} + +static int max5432_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct max5432_data *data = iio_priv(indio_dev); + u8 data_byte; + + if (mask != IIO_CHAN_INFO_RAW) + return -EINVAL; + + if (val < 0 || val > MAX5432_MAX_POS) + return -EINVAL; + + if (val2 != 0) + return -EINVAL; + + /* Wiper position is in bits D7-D3. (D2-D0 are don't care bits.) */ + data_byte = val << 3; + return i2c_smbus_write_byte_data(data->client, chan->address, + data_byte); +} + +static const struct iio_info max5432_info = { + .read_raw = max5432_read_raw, + .write_raw = max5432_write_raw, +}; + +static int max5432_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct max5432_data *data; + + indio_dev = devm_iio_device_alloc(dev, sizeof(struct max5432_data)); + if (!indio_dev) + return -ENOMEM; + + i2c_set_clientdata(client, indio_dev); + + data = iio_priv(indio_dev); + data->client = client; + data->ohm = (unsigned long)device_get_match_data(dev); + + indio_dev->info = &max5432_info; + indio_dev->channels = max5432_channels; + indio_dev->num_channels = ARRAY_SIZE(max5432_channels); + indio_dev->name = client->name; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id max5432_dt_ids[] = { + { .compatible = "maxim,max5432", .data = (void *)MAX5432_OHM_50K }, + { .compatible = "maxim,max5433", .data = (void *)MAX5432_OHM_100K }, + { .compatible = "maxim,max5434", .data = (void *)MAX5432_OHM_50K }, + { .compatible = "maxim,max5435", .data = (void *)MAX5432_OHM_100K }, + { } +}; +MODULE_DEVICE_TABLE(of, max5432_dt_ids); + +static struct i2c_driver max5432_driver = { + .driver = { + .name = "max5432", + .of_match_table = max5432_dt_ids, + }, + .probe = max5432_probe, +}; + +module_i2c_driver(max5432_driver); + +MODULE_AUTHOR("Martin Kaiser "); +MODULE_DESCRIPTION("max5432-max5435 digital potentiometers"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/potentiometer/max5481.c b/drivers/iio/potentiometer/max5481.c new file mode 100644 index 000000000..ec1b7bdc7 --- /dev/null +++ b/drivers/iio/potentiometer/max5481.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Maxim Integrated MAX5481-MAX5484 digital potentiometer driver + * Copyright 2016 Rockwell Collins + * + * Datasheet: + * https://datasheets.maximintegrated.com/en/ds/MAX5481-MAX5484.pdf + */ + +#include +#include +#include +#include +#include + +/* write wiper reg */ +#define MAX5481_WRITE_WIPER (0 << 4) +/* copy wiper reg to NV reg */ +#define MAX5481_COPY_AB_TO_NV (2 << 4) +/* copy NV reg to wiper reg */ +#define MAX5481_COPY_NV_TO_AB (3 << 4) + +#define MAX5481_MAX_POS 1023 + +enum max5481_variant { + max5481, + max5482, + max5483, + max5484, +}; + +struct max5481_cfg { + int kohms; +}; + +static const struct max5481_cfg max5481_cfg[] = { + [max5481] = { .kohms = 10, }, + [max5482] = { .kohms = 50, }, + [max5483] = { .kohms = 10, }, + [max5484] = { .kohms = 50, }, +}; + +struct max5481_data { + struct spi_device *spi; + const struct max5481_cfg *cfg; + u8 msg[3] __aligned(IIO_DMA_MINALIGN); +}; + +#define MAX5481_CHANNEL { \ + .type = IIO_RESISTANCE, \ + .indexed = 1, \ + .output = 1, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec max5481_channels[] = { + MAX5481_CHANNEL, +}; + +static int max5481_write_cmd(struct max5481_data *data, u8 cmd, u16 val) +{ + struct spi_device *spi = data->spi; + + data->msg[0] = cmd; + + switch (cmd) { + case MAX5481_WRITE_WIPER: + data->msg[1] = val >> 2; + data->msg[2] = (val & 0x3) << 6; + return spi_write(spi, data->msg, 3); + + case MAX5481_COPY_AB_TO_NV: + case MAX5481_COPY_NV_TO_AB: + return spi_write(spi, data->msg, 1); + + default: + return -EIO; + } +} + +static int max5481_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max5481_data *data = iio_priv(indio_dev); + + if (mask != IIO_CHAN_INFO_SCALE) + return -EINVAL; + + *val = 1000 * data->cfg->kohms; + *val2 = MAX5481_MAX_POS; + + return IIO_VAL_FRACTIONAL; +} + +static int max5481_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct max5481_data *data = iio_priv(indio_dev); + + if (mask != IIO_CHAN_INFO_RAW) + return -EINVAL; + + if (val < 0 || val > MAX5481_MAX_POS) + return -EINVAL; + + return max5481_write_cmd(data, MAX5481_WRITE_WIPER, val); +} + +static const struct iio_info max5481_info = { + .read_raw = max5481_read_raw, + .write_raw = max5481_write_raw, +}; + +static const struct of_device_id max5481_match[] = { + { .compatible = "maxim,max5481", .data = &max5481_cfg[max5481] }, + { .compatible = "maxim,max5482", .data = &max5481_cfg[max5482] }, + { .compatible = "maxim,max5483", .data = &max5481_cfg[max5483] }, + { .compatible = "maxim,max5484", .data = &max5481_cfg[max5484] }, + { } +}; +MODULE_DEVICE_TABLE(of, max5481_match); + +static void max5481_wiper_save(void *data) +{ + max5481_write_cmd(data, MAX5481_COPY_AB_TO_NV, 0); +} + +static int max5481_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct max5481_data *data; + const struct spi_device_id *id = spi_get_device_id(spi); + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + + data->spi = spi; + + data->cfg = device_get_match_data(&spi->dev); + if (!data->cfg) + data->cfg = &max5481_cfg[id->driver_data]; + + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + + /* variant specific configuration */ + indio_dev->info = &max5481_info; + indio_dev->channels = max5481_channels; + indio_dev->num_channels = ARRAY_SIZE(max5481_channels); + + /* restore wiper from NV */ + ret = max5481_write_cmd(data, MAX5481_COPY_NV_TO_AB, 0); + if (ret < 0) + return ret; + + ret = devm_add_action(&spi->dev, max5481_wiper_save, data); + if (ret < 0) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id max5481_id_table[] = { + { .name = "max5481", .driver_data = max5481 }, + { .name = "max5482", .driver_data = max5482 }, + { .name = "max5483", .driver_data = max5483 }, + { .name = "max5484", .driver_data = max5484 }, + { } +}; +MODULE_DEVICE_TABLE(spi, max5481_id_table); + +static struct spi_driver max5481_driver = { + .driver = { + .name = "max5481", + .of_match_table = max5481_match, + }, + .probe = max5481_probe, + .id_table = max5481_id_table, +}; + +module_spi_driver(max5481_driver); + +MODULE_AUTHOR("Maury Anderson "); +MODULE_DESCRIPTION("max5481 SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/potentiometer/max5487.c b/drivers/iio/potentiometer/max5487.c new file mode 100644 index 000000000..1f0d0056f --- /dev/null +++ b/drivers/iio/potentiometer/max5487.c @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * max5487.c - Support for MAX5487, MAX5488, MAX5489 digital potentiometers + * + * Copyright (C) 2016 Cristina-Gabriela Moraru + */ +#include +#include + +#include +#include + +#define MAX5487_WRITE_WIPER_A (0x01 << 8) +#define MAX5487_WRITE_WIPER_B (0x02 << 8) + +/* copy both wiper regs to NV regs */ +#define MAX5487_COPY_AB_TO_NV (0x23 << 8) +/* copy both NV regs to wiper regs */ +#define MAX5487_COPY_NV_TO_AB (0x33 << 8) + +#define MAX5487_MAX_POS 255 + +struct max5487_data { + struct spi_device *spi; + int kohms; +}; + +#define MAX5487_CHANNEL(ch, addr) { \ + .type = IIO_RESISTANCE, \ + .indexed = 1, \ + .output = 1, \ + .channel = ch, \ + .address = addr, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec max5487_channels[] = { + MAX5487_CHANNEL(0, MAX5487_WRITE_WIPER_A), + MAX5487_CHANNEL(1, MAX5487_WRITE_WIPER_B), +}; + +static int max5487_write_cmd(struct spi_device *spi, u16 cmd) +{ + return spi_write(spi, (const void *) &cmd, sizeof(u16)); +} + +static int max5487_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max5487_data *data = iio_priv(indio_dev); + + if (mask != IIO_CHAN_INFO_SCALE) + return -EINVAL; + + *val = 1000 * data->kohms; + *val2 = MAX5487_MAX_POS; + + return IIO_VAL_FRACTIONAL; +} + +static int max5487_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct max5487_data *data = iio_priv(indio_dev); + + if (mask != IIO_CHAN_INFO_RAW) + return -EINVAL; + + if (val < 0 || val > MAX5487_MAX_POS) + return -EINVAL; + + return max5487_write_cmd(data->spi, chan->address | val); +} + +static const struct iio_info max5487_info = { + .read_raw = max5487_read_raw, + .write_raw = max5487_write_raw, +}; + +static int max5487_spi_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct max5487_data *data; + const struct spi_device_id *id = spi_get_device_id(spi); + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + spi_set_drvdata(spi, indio_dev); + data = iio_priv(indio_dev); + + data->spi = spi; + data->kohms = id->driver_data; + + indio_dev->info = &max5487_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = max5487_channels; + indio_dev->num_channels = ARRAY_SIZE(max5487_channels); + + /* restore both wiper regs from NV regs */ + ret = max5487_write_cmd(data->spi, MAX5487_COPY_NV_TO_AB); + if (ret < 0) + return ret; + + return iio_device_register(indio_dev); +} + +static void max5487_spi_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + int ret; + + iio_device_unregister(indio_dev); + + /* save both wiper regs to NV regs */ + ret = max5487_write_cmd(spi, MAX5487_COPY_AB_TO_NV); + if (ret) + dev_warn(&spi->dev, "Failed to save wiper regs to NV regs\n"); +} + +static const struct spi_device_id max5487_id[] = { + { .name = "MAX5487", .driver_data = 10 }, + { .name = "MAX5488", .driver_data = 50 }, + { .name = "MAX5489", .driver_data = 100 }, + { } +}; +MODULE_DEVICE_TABLE(spi, max5487_id); + +static const struct acpi_device_id max5487_acpi_match[] = { + { "MAX5487", 10 }, + { "MAX5488", 50 }, + { "MAX5489", 100 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, max5487_acpi_match); + +static struct spi_driver max5487_driver = { + .driver = { + .name = "max5487", + .acpi_match_table = max5487_acpi_match, + }, + .id_table = max5487_id, + .probe = max5487_spi_probe, + .remove = max5487_spi_remove +}; +module_spi_driver(max5487_driver); + +MODULE_AUTHOR("Cristina-Gabriela Moraru "); +MODULE_DESCRIPTION("max5487 SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/potentiometer/mcp4018.c b/drivers/iio/potentiometer/mcp4018.c new file mode 100644 index 000000000..b10b4b920 --- /dev/null +++ b/drivers/iio/potentiometer/mcp4018.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Industrial I/O driver for Microchip digital potentiometers + * Copyright (c) 2018 Axentia Technologies AB + * Author: Peter Rosin + * + * Datasheet: http://www.microchip.com/downloads/en/DeviceDoc/22147a.pdf + * + * DEVID #Wipers #Positions Resistor Opts (kOhm) + * mcp4017 1 128 5, 10, 50, 100 + * mcp4018 1 128 5, 10, 50, 100 + * mcp4019 1 128 5, 10, 50, 100 + */ + +#include +#include +#include +#include +#include + +#define MCP4018_WIPER_MAX 127 + +struct mcp4018_cfg { + int kohms; +}; + +enum mcp4018_type { + MCP4018_502, + MCP4018_103, + MCP4018_503, + MCP4018_104, +}; + +static const struct mcp4018_cfg mcp4018_cfg[] = { + [MCP4018_502] = { .kohms = 5, }, + [MCP4018_103] = { .kohms = 10, }, + [MCP4018_503] = { .kohms = 50, }, + [MCP4018_104] = { .kohms = 100, }, +}; + +struct mcp4018_data { + struct i2c_client *client; + const struct mcp4018_cfg *cfg; +}; + +static const struct iio_chan_spec mcp4018_channel = { + .type = IIO_RESISTANCE, + .indexed = 1, + .output = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), +}; + +static int mcp4018_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mcp4018_data *data = iio_priv(indio_dev); + s32 ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = i2c_smbus_read_byte(data->client); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 1000 * data->cfg->kohms; + *val2 = MCP4018_WIPER_MAX; + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; +} + +static int mcp4018_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct mcp4018_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val > MCP4018_WIPER_MAX || val < 0) + return -EINVAL; + break; + default: + return -EINVAL; + } + + return i2c_smbus_write_byte(data->client, val); +} + +static const struct iio_info mcp4018_info = { + .read_raw = mcp4018_read_raw, + .write_raw = mcp4018_write_raw, +}; + +#define MCP4018_ID_TABLE(_name, cfg) { \ + .name = _name, \ + .driver_data = (kernel_ulong_t)&mcp4018_cfg[cfg], \ +} + +static const struct i2c_device_id mcp4018_id[] = { + MCP4018_ID_TABLE("mcp4017-502", MCP4018_502), + MCP4018_ID_TABLE("mcp4017-103", MCP4018_103), + MCP4018_ID_TABLE("mcp4017-503", MCP4018_503), + MCP4018_ID_TABLE("mcp4017-104", MCP4018_104), + MCP4018_ID_TABLE("mcp4018-502", MCP4018_502), + MCP4018_ID_TABLE("mcp4018-103", MCP4018_103), + MCP4018_ID_TABLE("mcp4018-503", MCP4018_503), + MCP4018_ID_TABLE("mcp4018-104", MCP4018_104), + MCP4018_ID_TABLE("mcp4019-502", MCP4018_502), + MCP4018_ID_TABLE("mcp4019-103", MCP4018_103), + MCP4018_ID_TABLE("mcp4019-503", MCP4018_503), + MCP4018_ID_TABLE("mcp4019-104", MCP4018_104), + { } +}; +MODULE_DEVICE_TABLE(i2c, mcp4018_id); + +#define MCP4018_COMPATIBLE(of_compatible, cfg) { \ + .compatible = of_compatible, \ + .data = &mcp4018_cfg[cfg], \ +} + +static const struct of_device_id mcp4018_of_match[] = { + MCP4018_COMPATIBLE("microchip,mcp4017-502", MCP4018_502), + MCP4018_COMPATIBLE("microchip,mcp4017-103", MCP4018_103), + MCP4018_COMPATIBLE("microchip,mcp4017-503", MCP4018_503), + MCP4018_COMPATIBLE("microchip,mcp4017-104", MCP4018_104), + MCP4018_COMPATIBLE("microchip,mcp4018-502", MCP4018_502), + MCP4018_COMPATIBLE("microchip,mcp4018-103", MCP4018_103), + MCP4018_COMPATIBLE("microchip,mcp4018-503", MCP4018_503), + MCP4018_COMPATIBLE("microchip,mcp4018-104", MCP4018_104), + MCP4018_COMPATIBLE("microchip,mcp4019-502", MCP4018_502), + MCP4018_COMPATIBLE("microchip,mcp4019-103", MCP4018_103), + MCP4018_COMPATIBLE("microchip,mcp4019-503", MCP4018_503), + MCP4018_COMPATIBLE("microchip,mcp4019-104", MCP4018_104), + { } +}; +MODULE_DEVICE_TABLE(of, mcp4018_of_match); + +static int mcp4018_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct mcp4018_data *data; + struct iio_dev *indio_dev; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE)) { + dev_err(dev, "SMBUS Byte transfers not supported\n"); + return -EOPNOTSUPP; + } + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + data->cfg = i2c_get_match_data(client); + + indio_dev->info = &mcp4018_info; + indio_dev->channels = &mcp4018_channel; + indio_dev->num_channels = 1; + indio_dev->name = client->name; + + return devm_iio_device_register(dev, indio_dev); +} + +static struct i2c_driver mcp4018_driver = { + .driver = { + .name = "mcp4018", + .of_match_table = mcp4018_of_match, + }, + .probe = mcp4018_probe, + .id_table = mcp4018_id, +}; + +module_i2c_driver(mcp4018_driver); + +MODULE_AUTHOR("Peter Rosin "); +MODULE_DESCRIPTION("MCP4018 digital potentiometer"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/potentiometer/mcp41010.c b/drivers/iio/potentiometer/mcp41010.c new file mode 100644 index 000000000..cfd5d0f6d --- /dev/null +++ b/drivers/iio/potentiometer/mcp41010.c @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Industrial I/O driver for Microchip digital potentiometers + * + * Copyright (c) 2018 Chris Coffey + * Based on: Slawomir Stepien's code from mcp4131.c + * + * Datasheet: https://ww1.microchip.com/downloads/en/devicedoc/11195c.pdf + * + * DEVID #Wipers #Positions Resistance (kOhm) + * mcp41010 1 256 10 + * mcp41050 1 256 50 + * mcp41100 1 256 100 + * mcp42010 2 256 10 + * mcp42050 2 256 50 + * mcp42100 2 256 100 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MCP41010_MAX_WIPERS 2 +#define MCP41010_WRITE BIT(4) +#define MCP41010_WIPER_MAX 255 +#define MCP41010_WIPER_CHANNEL BIT(0) + +struct mcp41010_cfg { + char name[16]; + int wipers; + int kohms; +}; + +enum mcp41010_type { + MCP41010, + MCP41050, + MCP41100, + MCP42010, + MCP42050, + MCP42100, +}; + +static const struct mcp41010_cfg mcp41010_cfg[] = { + [MCP41010] = { .name = "mcp41010", .wipers = 1, .kohms = 10, }, + [MCP41050] = { .name = "mcp41050", .wipers = 1, .kohms = 50, }, + [MCP41100] = { .name = "mcp41100", .wipers = 1, .kohms = 100, }, + [MCP42010] = { .name = "mcp42010", .wipers = 2, .kohms = 10, }, + [MCP42050] = { .name = "mcp42050", .wipers = 2, .kohms = 50, }, + [MCP42100] = { .name = "mcp42100", .wipers = 2, .kohms = 100, }, +}; + +struct mcp41010_data { + struct spi_device *spi; + const struct mcp41010_cfg *cfg; + struct mutex lock; /* Protect write sequences */ + unsigned int value[MCP41010_MAX_WIPERS]; /* Cache wiper values */ + u8 buf[2] __aligned(IIO_DMA_MINALIGN); +}; + +#define MCP41010_CHANNEL(ch) { \ + .type = IIO_RESISTANCE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (ch), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec mcp41010_channels[] = { + MCP41010_CHANNEL(0), + MCP41010_CHANNEL(1), +}; + +static int mcp41010_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mcp41010_data *data = iio_priv(indio_dev); + int channel = chan->channel; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val = data->value[channel]; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = 1000 * data->cfg->kohms; + *val2 = MCP41010_WIPER_MAX; + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; +} + +static int mcp41010_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int err; + struct mcp41010_data *data = iio_priv(indio_dev); + int channel = chan->channel; + + if (mask != IIO_CHAN_INFO_RAW) + return -EINVAL; + + if (val > MCP41010_WIPER_MAX || val < 0) + return -EINVAL; + + mutex_lock(&data->lock); + + data->buf[0] = MCP41010_WIPER_CHANNEL << channel; + data->buf[0] |= MCP41010_WRITE; + data->buf[1] = val & 0xff; + + err = spi_write(data->spi, data->buf, sizeof(data->buf)); + if (!err) + data->value[channel] = val; + + mutex_unlock(&data->lock); + + return err; +} + +static const struct iio_info mcp41010_info = { + .read_raw = mcp41010_read_raw, + .write_raw = mcp41010_write_raw, +}; + +static int mcp41010_probe(struct spi_device *spi) +{ + int err; + struct device *dev = &spi->dev; + struct mcp41010_data *data; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + data->spi = spi; + data->cfg = device_get_match_data(&spi->dev); + if (!data->cfg) + data->cfg = &mcp41010_cfg[spi_get_device_id(spi)->driver_data]; + + mutex_init(&data->lock); + + indio_dev->info = &mcp41010_info; + indio_dev->channels = mcp41010_channels; + indio_dev->num_channels = data->cfg->wipers; + indio_dev->name = data->cfg->name; + + err = devm_iio_device_register(dev, indio_dev); + if (err) + dev_info(&spi->dev, "Unable to register %s\n", indio_dev->name); + + return err; +} + +static const struct of_device_id mcp41010_match[] = { + { .compatible = "microchip,mcp41010", .data = &mcp41010_cfg[MCP41010] }, + { .compatible = "microchip,mcp41050", .data = &mcp41010_cfg[MCP41050] }, + { .compatible = "microchip,mcp41100", .data = &mcp41010_cfg[MCP41100] }, + { .compatible = "microchip,mcp42010", .data = &mcp41010_cfg[MCP42010] }, + { .compatible = "microchip,mcp42050", .data = &mcp41010_cfg[MCP42050] }, + { .compatible = "microchip,mcp42100", .data = &mcp41010_cfg[MCP42100] }, + { } +}; +MODULE_DEVICE_TABLE(of, mcp41010_match); + +static const struct spi_device_id mcp41010_id[] = { + { .name = "mcp41010", .driver_data = MCP41010 }, + { .name = "mcp41050", .driver_data = MCP41050 }, + { .name = "mcp41100", .driver_data = MCP41100 }, + { .name = "mcp42010", .driver_data = MCP42010 }, + { .name = "mcp42050", .driver_data = MCP42050 }, + { .name = "mcp42100", .driver_data = MCP42100 }, + { } +}; +MODULE_DEVICE_TABLE(spi, mcp41010_id); + +static struct spi_driver mcp41010_driver = { + .driver = { + .name = "mcp41010", + .of_match_table = mcp41010_match, + }, + .probe = mcp41010_probe, + .id_table = mcp41010_id, +}; + +module_spi_driver(mcp41010_driver); + +MODULE_AUTHOR("Chris Coffey "); +MODULE_DESCRIPTION("MCP41010 digital potentiometer"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/potentiometer/mcp4131.c b/drivers/iio/potentiometer/mcp4131.c new file mode 100644 index 000000000..fadf9fcc1 --- /dev/null +++ b/drivers/iio/potentiometer/mcp4131.c @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Industrial I/O driver for Microchip digital potentiometers + * + * Copyright (c) 2016 Slawomir Stepien + * Based on: Peter Rosin's code from mcp4531.c + * + * Datasheet: https://ww1.microchip.com/downloads/en/DeviceDoc/22060b.pdf + * + * DEVID #Wipers #Positions Resistor Opts (kOhm) + * mcp4131 1 129 5, 10, 50, 100 + * mcp4132 1 129 5, 10, 50, 100 + * mcp4141 1 129 5, 10, 50, 100 + * mcp4142 1 129 5, 10, 50, 100 + * mcp4151 1 257 5, 10, 50, 100 + * mcp4152 1 257 5, 10, 50, 100 + * mcp4161 1 257 5, 10, 50, 100 + * mcp4162 1 257 5, 10, 50, 100 + * mcp4231 2 129 5, 10, 50, 100 + * mcp4232 2 129 5, 10, 50, 100 + * mcp4241 2 129 5, 10, 50, 100 + * mcp4242 2 129 5, 10, 50, 100 + * mcp4251 2 257 5, 10, 50, 100 + * mcp4252 2 257 5, 10, 50, 100 + * mcp4261 2 257 5, 10, 50, 100 + * mcp4262 2 257 5, 10, 50, 100 + */ + +/* + * TODO: + * 1. Write wiper setting to EEPROM for EEPROM capable models. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MCP4131_WRITE (0x00 << 2) +#define MCP4131_READ (0x03 << 2) + +#define MCP4131_WIPER_SHIFT 4 +#define MCP4131_CMDERR(r) ((r[0]) & 0x02) +#define MCP4131_RAW(r) ((r[0]) == 0xff ? 0x100 : (r[1])) + +struct mcp4131_cfg { + int wipers; + int max_pos; + int kohms; +}; + +enum mcp4131_type { + MCP413x_502 = 0, + MCP413x_103, + MCP413x_503, + MCP413x_104, + MCP414x_502, + MCP414x_103, + MCP414x_503, + MCP414x_104, + MCP415x_502, + MCP415x_103, + MCP415x_503, + MCP415x_104, + MCP416x_502, + MCP416x_103, + MCP416x_503, + MCP416x_104, + MCP423x_502, + MCP423x_103, + MCP423x_503, + MCP423x_104, + MCP424x_502, + MCP424x_103, + MCP424x_503, + MCP424x_104, + MCP425x_502, + MCP425x_103, + MCP425x_503, + MCP425x_104, + MCP426x_502, + MCP426x_103, + MCP426x_503, + MCP426x_104, +}; + +static const struct mcp4131_cfg mcp4131_cfg[] = { + [MCP413x_502] = { .wipers = 1, .max_pos = 128, .kohms = 5, }, + [MCP413x_103] = { .wipers = 1, .max_pos = 128, .kohms = 10, }, + [MCP413x_503] = { .wipers = 1, .max_pos = 128, .kohms = 50, }, + [MCP413x_104] = { .wipers = 1, .max_pos = 128, .kohms = 100, }, + [MCP414x_502] = { .wipers = 1, .max_pos = 128, .kohms = 5, }, + [MCP414x_103] = { .wipers = 1, .max_pos = 128, .kohms = 10, }, + [MCP414x_503] = { .wipers = 1, .max_pos = 128, .kohms = 50, }, + [MCP414x_104] = { .wipers = 1, .max_pos = 128, .kohms = 100, }, + [MCP415x_502] = { .wipers = 1, .max_pos = 256, .kohms = 5, }, + [MCP415x_103] = { .wipers = 1, .max_pos = 256, .kohms = 10, }, + [MCP415x_503] = { .wipers = 1, .max_pos = 256, .kohms = 50, }, + [MCP415x_104] = { .wipers = 1, .max_pos = 256, .kohms = 100, }, + [MCP416x_502] = { .wipers = 1, .max_pos = 256, .kohms = 5, }, + [MCP416x_103] = { .wipers = 1, .max_pos = 256, .kohms = 10, }, + [MCP416x_503] = { .wipers = 1, .max_pos = 256, .kohms = 50, }, + [MCP416x_104] = { .wipers = 1, .max_pos = 256, .kohms = 100, }, + [MCP423x_502] = { .wipers = 2, .max_pos = 128, .kohms = 5, }, + [MCP423x_103] = { .wipers = 2, .max_pos = 128, .kohms = 10, }, + [MCP423x_503] = { .wipers = 2, .max_pos = 128, .kohms = 50, }, + [MCP423x_104] = { .wipers = 2, .max_pos = 128, .kohms = 100, }, + [MCP424x_502] = { .wipers = 2, .max_pos = 128, .kohms = 5, }, + [MCP424x_103] = { .wipers = 2, .max_pos = 128, .kohms = 10, }, + [MCP424x_503] = { .wipers = 2, .max_pos = 128, .kohms = 50, }, + [MCP424x_104] = { .wipers = 2, .max_pos = 128, .kohms = 100, }, + [MCP425x_502] = { .wipers = 2, .max_pos = 256, .kohms = 5, }, + [MCP425x_103] = { .wipers = 2, .max_pos = 256, .kohms = 10, }, + [MCP425x_503] = { .wipers = 2, .max_pos = 256, .kohms = 50, }, + [MCP425x_104] = { .wipers = 2, .max_pos = 256, .kohms = 100, }, + [MCP426x_502] = { .wipers = 2, .max_pos = 256, .kohms = 5, }, + [MCP426x_103] = { .wipers = 2, .max_pos = 256, .kohms = 10, }, + [MCP426x_503] = { .wipers = 2, .max_pos = 256, .kohms = 50, }, + [MCP426x_104] = { .wipers = 2, .max_pos = 256, .kohms = 100, }, +}; + +struct mcp4131_data { + struct spi_device *spi; + const struct mcp4131_cfg *cfg; + struct mutex lock; + u8 buf[2] __aligned(IIO_DMA_MINALIGN); +}; + +#define MCP4131_CHANNEL(ch) { \ + .type = IIO_RESISTANCE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (ch), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec mcp4131_channels[] = { + MCP4131_CHANNEL(0), + MCP4131_CHANNEL(1), +}; + +static int mcp4131_read(struct spi_device *spi, void *buf, size_t len) +{ + struct spi_transfer t = { + .tx_buf = buf, /* We need to send addr, cmd and 12 bits */ + .rx_buf = buf, + .len = len, + }; + struct spi_message m; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + return spi_sync(spi, &m); +} + +static int mcp4131_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int err; + struct mcp4131_data *data = iio_priv(indio_dev); + int address = chan->channel; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&data->lock); + + data->buf[0] = (address << MCP4131_WIPER_SHIFT) | MCP4131_READ; + data->buf[1] = 0; + + err = mcp4131_read(data->spi, data->buf, 2); + if (err) { + mutex_unlock(&data->lock); + return err; + } + + /* Error, bad address/command combination */ + if (!MCP4131_CMDERR(data->buf)) { + mutex_unlock(&data->lock); + return -EIO; + } + + *val = MCP4131_RAW(data->buf); + mutex_unlock(&data->lock); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = 1000 * data->cfg->kohms; + *val2 = data->cfg->max_pos; + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; +} + +static int mcp4131_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int err; + struct mcp4131_data *data = iio_priv(indio_dev); + int address = chan->channel << MCP4131_WIPER_SHIFT; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val > data->cfg->max_pos || val < 0) + return -EINVAL; + break; + + default: + return -EINVAL; + } + + mutex_lock(&data->lock); + + data->buf[0] = address; + data->buf[0] |= MCP4131_WRITE | (val >> 8); + data->buf[1] = val & 0xFF; /* 8 bits here */ + + err = spi_write(data->spi, data->buf, 2); + mutex_unlock(&data->lock); + + return err; +} + +static const struct iio_info mcp4131_info = { + .read_raw = mcp4131_read_raw, + .write_raw = mcp4131_write_raw, +}; + +static int mcp4131_probe(struct spi_device *spi) +{ + int err; + struct device *dev = &spi->dev; + unsigned long devid; + struct mcp4131_data *data; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + data->spi = spi; + data->cfg = device_get_match_data(&spi->dev); + if (!data->cfg) { + devid = spi_get_device_id(spi)->driver_data; + data->cfg = &mcp4131_cfg[devid]; + } + + mutex_init(&data->lock); + + indio_dev->info = &mcp4131_info; + indio_dev->channels = mcp4131_channels; + indio_dev->num_channels = data->cfg->wipers; + indio_dev->name = spi_get_device_id(spi)->name; + + err = devm_iio_device_register(dev, indio_dev); + if (err) { + dev_info(&spi->dev, "Unable to register %s\n", indio_dev->name); + return err; + } + + return 0; +} + +static const struct of_device_id mcp4131_dt_ids[] = { + { .compatible = "microchip,mcp4131-502", + .data = &mcp4131_cfg[MCP413x_502] }, + { .compatible = "microchip,mcp4131-103", + .data = &mcp4131_cfg[MCP413x_103] }, + { .compatible = "microchip,mcp4131-503", + .data = &mcp4131_cfg[MCP413x_503] }, + { .compatible = "microchip,mcp4131-104", + .data = &mcp4131_cfg[MCP413x_104] }, + { .compatible = "microchip,mcp4132-502", + .data = &mcp4131_cfg[MCP413x_502] }, + { .compatible = "microchip,mcp4132-103", + .data = &mcp4131_cfg[MCP413x_103] }, + { .compatible = "microchip,mcp4132-503", + .data = &mcp4131_cfg[MCP413x_503] }, + { .compatible = "microchip,mcp4132-104", + .data = &mcp4131_cfg[MCP413x_104] }, + { .compatible = "microchip,mcp4141-502", + .data = &mcp4131_cfg[MCP414x_502] }, + { .compatible = "microchip,mcp4141-103", + .data = &mcp4131_cfg[MCP414x_103] }, + { .compatible = "microchip,mcp4141-503", + .data = &mcp4131_cfg[MCP414x_503] }, + { .compatible = "microchip,mcp4141-104", + .data = &mcp4131_cfg[MCP414x_104] }, + { .compatible = "microchip,mcp4142-502", + .data = &mcp4131_cfg[MCP414x_502] }, + { .compatible = "microchip,mcp4142-103", + .data = &mcp4131_cfg[MCP414x_103] }, + { .compatible = "microchip,mcp4142-503", + .data = &mcp4131_cfg[MCP414x_503] }, + { .compatible = "microchip,mcp4142-104", + .data = &mcp4131_cfg[MCP414x_104] }, + { .compatible = "microchip,mcp4151-502", + .data = &mcp4131_cfg[MCP415x_502] }, + { .compatible = "microchip,mcp4151-103", + .data = &mcp4131_cfg[MCP415x_103] }, + { .compatible = "microchip,mcp4151-503", + .data = &mcp4131_cfg[MCP415x_503] }, + { .compatible = "microchip,mcp4151-104", + .data = &mcp4131_cfg[MCP415x_104] }, + { .compatible = "microchip,mcp4152-502", + .data = &mcp4131_cfg[MCP415x_502] }, + { .compatible = "microchip,mcp4152-103", + .data = &mcp4131_cfg[MCP415x_103] }, + { .compatible = "microchip,mcp4152-503", + .data = &mcp4131_cfg[MCP415x_503] }, + { .compatible = "microchip,mcp4152-104", + .data = &mcp4131_cfg[MCP415x_104] }, + { .compatible = "microchip,mcp4161-502", + .data = &mcp4131_cfg[MCP416x_502] }, + { .compatible = "microchip,mcp4161-103", + .data = &mcp4131_cfg[MCP416x_103] }, + { .compatible = "microchip,mcp4161-503", + .data = &mcp4131_cfg[MCP416x_503] }, + { .compatible = "microchip,mcp4161-104", + .data = &mcp4131_cfg[MCP416x_104] }, + { .compatible = "microchip,mcp4162-502", + .data = &mcp4131_cfg[MCP416x_502] }, + { .compatible = "microchip,mcp4162-103", + .data = &mcp4131_cfg[MCP416x_103] }, + { .compatible = "microchip,mcp4162-503", + .data = &mcp4131_cfg[MCP416x_503] }, + { .compatible = "microchip,mcp4162-104", + .data = &mcp4131_cfg[MCP416x_104] }, + { .compatible = "microchip,mcp4231-502", + .data = &mcp4131_cfg[MCP423x_502] }, + { .compatible = "microchip,mcp4231-103", + .data = &mcp4131_cfg[MCP423x_103] }, + { .compatible = "microchip,mcp4231-503", + .data = &mcp4131_cfg[MCP423x_503] }, + { .compatible = "microchip,mcp4231-104", + .data = &mcp4131_cfg[MCP423x_104] }, + { .compatible = "microchip,mcp4232-502", + .data = &mcp4131_cfg[MCP423x_502] }, + { .compatible = "microchip,mcp4232-103", + .data = &mcp4131_cfg[MCP423x_103] }, + { .compatible = "microchip,mcp4232-503", + .data = &mcp4131_cfg[MCP423x_503] }, + { .compatible = "microchip,mcp4232-104", + .data = &mcp4131_cfg[MCP423x_104] }, + { .compatible = "microchip,mcp4241-502", + .data = &mcp4131_cfg[MCP424x_502] }, + { .compatible = "microchip,mcp4241-103", + .data = &mcp4131_cfg[MCP424x_103] }, + { .compatible = "microchip,mcp4241-503", + .data = &mcp4131_cfg[MCP424x_503] }, + { .compatible = "microchip,mcp4241-104", + .data = &mcp4131_cfg[MCP424x_104] }, + { .compatible = "microchip,mcp4242-502", + .data = &mcp4131_cfg[MCP424x_502] }, + { .compatible = "microchip,mcp4242-103", + .data = &mcp4131_cfg[MCP424x_103] }, + { .compatible = "microchip,mcp4242-503", + .data = &mcp4131_cfg[MCP424x_503] }, + { .compatible = "microchip,mcp4242-104", + .data = &mcp4131_cfg[MCP424x_104] }, + { .compatible = "microchip,mcp4251-502", + .data = &mcp4131_cfg[MCP425x_502] }, + { .compatible = "microchip,mcp4251-103", + .data = &mcp4131_cfg[MCP425x_103] }, + { .compatible = "microchip,mcp4251-503", + .data = &mcp4131_cfg[MCP425x_503] }, + { .compatible = "microchip,mcp4251-104", + .data = &mcp4131_cfg[MCP425x_104] }, + { .compatible = "microchip,mcp4252-502", + .data = &mcp4131_cfg[MCP425x_502] }, + { .compatible = "microchip,mcp4252-103", + .data = &mcp4131_cfg[MCP425x_103] }, + { .compatible = "microchip,mcp4252-503", + .data = &mcp4131_cfg[MCP425x_503] }, + { .compatible = "microchip,mcp4252-104", + .data = &mcp4131_cfg[MCP425x_104] }, + { .compatible = "microchip,mcp4261-502", + .data = &mcp4131_cfg[MCP426x_502] }, + { .compatible = "microchip,mcp4261-103", + .data = &mcp4131_cfg[MCP426x_103] }, + { .compatible = "microchip,mcp4261-503", + .data = &mcp4131_cfg[MCP426x_503] }, + { .compatible = "microchip,mcp4261-104", + .data = &mcp4131_cfg[MCP426x_104] }, + { .compatible = "microchip,mcp4262-502", + .data = &mcp4131_cfg[MCP426x_502] }, + { .compatible = "microchip,mcp4262-103", + .data = &mcp4131_cfg[MCP426x_103] }, + { .compatible = "microchip,mcp4262-503", + .data = &mcp4131_cfg[MCP426x_503] }, + { .compatible = "microchip,mcp4262-104", + .data = &mcp4131_cfg[MCP426x_104] }, + { } +}; +MODULE_DEVICE_TABLE(of, mcp4131_dt_ids); + +static const struct spi_device_id mcp4131_id[] = { + { .name = "mcp4131-502", .driver_data = MCP413x_502 }, + { .name = "mcp4131-103", .driver_data = MCP413x_103 }, + { .name = "mcp4131-503", .driver_data = MCP413x_503 }, + { .name = "mcp4131-104", .driver_data = MCP413x_104 }, + { .name = "mcp4132-502", .driver_data = MCP413x_502 }, + { .name = "mcp4132-103", .driver_data = MCP413x_103 }, + { .name = "mcp4132-503", .driver_data = MCP413x_503 }, + { .name = "mcp4132-104", .driver_data = MCP413x_104 }, + { .name = "mcp4141-502", .driver_data = MCP414x_502 }, + { .name = "mcp4141-103", .driver_data = MCP414x_103 }, + { .name = "mcp4141-503", .driver_data = MCP414x_503 }, + { .name = "mcp4141-104", .driver_data = MCP414x_104 }, + { .name = "mcp4142-502", .driver_data = MCP414x_502 }, + { .name = "mcp4142-103", .driver_data = MCP414x_103 }, + { .name = "mcp4142-503", .driver_data = MCP414x_503 }, + { .name = "mcp4142-104", .driver_data = MCP414x_104 }, + { .name = "mcp4151-502", .driver_data = MCP415x_502 }, + { .name = "mcp4151-103", .driver_data = MCP415x_103 }, + { .name = "mcp4151-503", .driver_data = MCP415x_503 }, + { .name = "mcp4151-104", .driver_data = MCP415x_104 }, + { .name = "mcp4152-502", .driver_data = MCP415x_502 }, + { .name = "mcp4152-103", .driver_data = MCP415x_103 }, + { .name = "mcp4152-503", .driver_data = MCP415x_503 }, + { .name = "mcp4152-104", .driver_data = MCP415x_104 }, + { .name = "mcp4161-502", .driver_data = MCP416x_502 }, + { .name = "mcp4161-103", .driver_data = MCP416x_103 }, + { .name = "mcp4161-503", .driver_data = MCP416x_503 }, + { .name = "mcp4161-104", .driver_data = MCP416x_104 }, + { .name = "mcp4162-502", .driver_data = MCP416x_502 }, + { .name = "mcp4162-103", .driver_data = MCP416x_103 }, + { .name = "mcp4162-503", .driver_data = MCP416x_503 }, + { .name = "mcp4162-104", .driver_data = MCP416x_104 }, + { .name = "mcp4231-502", .driver_data = MCP423x_502 }, + { .name = "mcp4231-103", .driver_data = MCP423x_103 }, + { .name = "mcp4231-503", .driver_data = MCP423x_503 }, + { .name = "mcp4231-104", .driver_data = MCP423x_104 }, + { .name = "mcp4232-502", .driver_data = MCP423x_502 }, + { .name = "mcp4232-103", .driver_data = MCP423x_103 }, + { .name = "mcp4232-503", .driver_data = MCP423x_503 }, + { .name = "mcp4232-104", .driver_data = MCP423x_104 }, + { .name = "mcp4241-502", .driver_data = MCP424x_502 }, + { .name = "mcp4241-103", .driver_data = MCP424x_103 }, + { .name = "mcp4241-503", .driver_data = MCP424x_503 }, + { .name = "mcp4241-104", .driver_data = MCP424x_104 }, + { .name = "mcp4242-502", .driver_data = MCP424x_502 }, + { .name = "mcp4242-103", .driver_data = MCP424x_103 }, + { .name = "mcp4242-503", .driver_data = MCP424x_503 }, + { .name = "mcp4242-104", .driver_data = MCP424x_104 }, + { .name = "mcp4251-502", .driver_data = MCP425x_502 }, + { .name = "mcp4251-103", .driver_data = MCP425x_103 }, + { .name = "mcp4251-503", .driver_data = MCP425x_503 }, + { .name = "mcp4251-104", .driver_data = MCP425x_104 }, + { .name = "mcp4252-502", .driver_data = MCP425x_502 }, + { .name = "mcp4252-103", .driver_data = MCP425x_103 }, + { .name = "mcp4252-503", .driver_data = MCP425x_503 }, + { .name = "mcp4252-104", .driver_data = MCP425x_104 }, + { .name = "mcp4261-502", .driver_data = MCP426x_502 }, + { .name = "mcp4261-103", .driver_data = MCP426x_103 }, + { .name = "mcp4261-503", .driver_data = MCP426x_503 }, + { .name = "mcp4261-104", .driver_data = MCP426x_104 }, + { .name = "mcp4262-502", .driver_data = MCP426x_502 }, + { .name = "mcp4262-103", .driver_data = MCP426x_103 }, + { .name = "mcp4262-503", .driver_data = MCP426x_503 }, + { .name = "mcp4262-104", .driver_data = MCP426x_104 }, + { } +}; +MODULE_DEVICE_TABLE(spi, mcp4131_id); + +static struct spi_driver mcp4131_driver = { + .driver = { + .name = "mcp4131", + .of_match_table = mcp4131_dt_ids, + }, + .probe = mcp4131_probe, + .id_table = mcp4131_id, +}; + +module_spi_driver(mcp4131_driver); + +MODULE_AUTHOR("Slawomir Stepien "); +MODULE_DESCRIPTION("MCP4131 digital potentiometer"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/potentiometer/mcp4531.c b/drivers/iio/potentiometer/mcp4531.c new file mode 100644 index 000000000..7fb1fb5ab --- /dev/null +++ b/drivers/iio/potentiometer/mcp4531.c @@ -0,0 +1,398 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Industrial I/O driver for Microchip digital potentiometers + * Copyright (c) 2015 Axentia Technologies AB + * Author: Peter Rosin + * + * Datasheet: http://www.microchip.com/downloads/en/DeviceDoc/22096b.pdf + * + * DEVID #Wipers #Positions Resistor Opts (kOhm) i2c address + * mcp4531 1 129 5, 10, 50, 100 010111x + * mcp4532 1 129 5, 10, 50, 100 01011xx + * mcp4541 1 129 5, 10, 50, 100 010111x + * mcp4542 1 129 5, 10, 50, 100 01011xx + * mcp4551 1 257 5, 10, 50, 100 010111x + * mcp4552 1 257 5, 10, 50, 100 01011xx + * mcp4561 1 257 5, 10, 50, 100 010111x + * mcp4562 1 257 5, 10, 50, 100 01011xx + * mcp4631 2 129 5, 10, 50, 100 0101xxx + * mcp4632 2 129 5, 10, 50, 100 01011xx + * mcp4641 2 129 5, 10, 50, 100 0101xxx + * mcp4642 2 129 5, 10, 50, 100 01011xx + * mcp4651 2 257 5, 10, 50, 100 0101xxx + * mcp4652 2 257 5, 10, 50, 100 01011xx + * mcp4661 2 257 5, 10, 50, 100 0101xxx + * mcp4662 2 257 5, 10, 50, 100 01011xx + */ + +#include +#include +#include +#include + +#include + +struct mcp4531_cfg { + int wipers; + int avail[3]; + int kohms; +}; + +enum mcp4531_type { + MCP453x_502, + MCP453x_103, + MCP453x_503, + MCP453x_104, + MCP454x_502, + MCP454x_103, + MCP454x_503, + MCP454x_104, + MCP455x_502, + MCP455x_103, + MCP455x_503, + MCP455x_104, + MCP456x_502, + MCP456x_103, + MCP456x_503, + MCP456x_104, + MCP463x_502, + MCP463x_103, + MCP463x_503, + MCP463x_104, + MCP464x_502, + MCP464x_103, + MCP464x_503, + MCP464x_104, + MCP465x_502, + MCP465x_103, + MCP465x_503, + MCP465x_104, + MCP466x_502, + MCP466x_103, + MCP466x_503, + MCP466x_104, +}; + +static const struct mcp4531_cfg mcp4531_cfg[] = { + [MCP453x_502] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 5, }, + [MCP453x_103] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 10, }, + [MCP453x_503] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 50, }, + [MCP453x_104] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 100, }, + [MCP454x_502] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 5, }, + [MCP454x_103] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 10, }, + [MCP454x_503] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 50, }, + [MCP454x_104] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 100, }, + [MCP455x_502] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 5, }, + [MCP455x_103] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 10, }, + [MCP455x_503] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 50, }, + [MCP455x_104] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 100, }, + [MCP456x_502] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 5, }, + [MCP456x_103] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 10, }, + [MCP456x_503] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 50, }, + [MCP456x_104] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 100, }, + [MCP463x_502] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 5, }, + [MCP463x_103] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 10, }, + [MCP463x_503] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 50, }, + [MCP463x_104] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 100, }, + [MCP464x_502] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 5, }, + [MCP464x_103] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 10, }, + [MCP464x_503] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 50, }, + [MCP464x_104] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 100, }, + [MCP465x_502] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 5, }, + [MCP465x_103] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 10, }, + [MCP465x_503] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 50, }, + [MCP465x_104] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 100, }, + [MCP466x_502] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 5, }, + [MCP466x_103] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 10, }, + [MCP466x_503] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 50, }, + [MCP466x_104] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 100, }, +}; + +#define MCP4531_WRITE (0 << 2) +#define MCP4531_INCR (1 << 2) +#define MCP4531_DECR (2 << 2) +#define MCP4531_READ (3 << 2) + +#define MCP4531_WIPER_SHIFT (4) + +struct mcp4531_data { + struct i2c_client *client; + const struct mcp4531_cfg *cfg; +}; + +#define MCP4531_CHANNEL(ch) { \ + .type = IIO_RESISTANCE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (ch), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_RAW), \ +} + +static const struct iio_chan_spec mcp4531_channels[] = { + MCP4531_CHANNEL(0), + MCP4531_CHANNEL(1), +}; + +static int mcp4531_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mcp4531_data *data = iio_priv(indio_dev); + int address = chan->channel << MCP4531_WIPER_SHIFT; + s32 ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = i2c_smbus_read_word_swapped(data->client, + MCP4531_READ | address); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 1000 * data->cfg->kohms; + *val2 = data->cfg->avail[2]; + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; +} + +static int mcp4531_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct mcp4531_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *length = ARRAY_SIZE(data->cfg->avail); + *vals = data->cfg->avail; + *type = IIO_VAL_INT; + return IIO_AVAIL_RANGE; + } + + return -EINVAL; +} + +static int mcp4531_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct mcp4531_data *data = iio_priv(indio_dev); + int address = chan->channel << MCP4531_WIPER_SHIFT; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val > data->cfg->avail[2] || val < 0) + return -EINVAL; + break; + default: + return -EINVAL; + } + + return i2c_smbus_write_byte_data(data->client, + MCP4531_WRITE | address | (val >> 8), + val & 0xff); +} + +static const struct iio_info mcp4531_info = { + .read_raw = mcp4531_read_raw, + .read_avail = mcp4531_read_avail, + .write_raw = mcp4531_write_raw, +}; + +#define MCP4531_ID_TABLE(_name, cfg) { \ + .name = _name, \ + .driver_data = (kernel_ulong_t)&mcp4531_cfg[cfg], \ +} + +static const struct i2c_device_id mcp4531_id[] = { + MCP4531_ID_TABLE("mcp4531-502", MCP453x_502), + MCP4531_ID_TABLE("mcp4531-103", MCP453x_103), + MCP4531_ID_TABLE("mcp4531-503", MCP453x_503), + MCP4531_ID_TABLE("mcp4531-104", MCP453x_104), + MCP4531_ID_TABLE("mcp4532-502", MCP453x_502), + MCP4531_ID_TABLE("mcp4532-103", MCP453x_103), + MCP4531_ID_TABLE("mcp4532-503", MCP453x_503), + MCP4531_ID_TABLE("mcp4532-104", MCP453x_104), + MCP4531_ID_TABLE("mcp4541-502", MCP454x_502), + MCP4531_ID_TABLE("mcp4541-103", MCP454x_103), + MCP4531_ID_TABLE("mcp4541-503", MCP454x_503), + MCP4531_ID_TABLE("mcp4541-104", MCP454x_104), + MCP4531_ID_TABLE("mcp4542-502", MCP454x_502), + MCP4531_ID_TABLE("mcp4542-103", MCP454x_103), + MCP4531_ID_TABLE("mcp4542-503", MCP454x_503), + MCP4531_ID_TABLE("mcp4542-104", MCP454x_104), + MCP4531_ID_TABLE("mcp4551-502", MCP455x_502), + MCP4531_ID_TABLE("mcp4551-103", MCP455x_103), + MCP4531_ID_TABLE("mcp4551-503", MCP455x_503), + MCP4531_ID_TABLE("mcp4551-104", MCP455x_104), + MCP4531_ID_TABLE("mcp4552-502", MCP455x_502), + MCP4531_ID_TABLE("mcp4552-103", MCP455x_103), + MCP4531_ID_TABLE("mcp4552-503", MCP455x_503), + MCP4531_ID_TABLE("mcp4552-104", MCP455x_104), + MCP4531_ID_TABLE("mcp4561-502", MCP456x_502), + MCP4531_ID_TABLE("mcp4561-103", MCP456x_103), + MCP4531_ID_TABLE("mcp4561-503", MCP456x_503), + MCP4531_ID_TABLE("mcp4561-104", MCP456x_104), + MCP4531_ID_TABLE("mcp4562-502", MCP456x_502), + MCP4531_ID_TABLE("mcp4562-103", MCP456x_103), + MCP4531_ID_TABLE("mcp4562-503", MCP456x_503), + MCP4531_ID_TABLE("mcp4562-104", MCP456x_104), + MCP4531_ID_TABLE("mcp4631-502", MCP463x_502), + MCP4531_ID_TABLE("mcp4631-103", MCP463x_103), + MCP4531_ID_TABLE("mcp4631-503", MCP463x_503), + MCP4531_ID_TABLE("mcp4631-104", MCP463x_104), + MCP4531_ID_TABLE("mcp4632-502", MCP463x_502), + MCP4531_ID_TABLE("mcp4632-103", MCP463x_103), + MCP4531_ID_TABLE("mcp4632-503", MCP463x_503), + MCP4531_ID_TABLE("mcp4632-104", MCP463x_104), + MCP4531_ID_TABLE("mcp4641-502", MCP464x_502), + MCP4531_ID_TABLE("mcp4641-103", MCP464x_103), + MCP4531_ID_TABLE("mcp4641-503", MCP464x_503), + MCP4531_ID_TABLE("mcp4641-104", MCP464x_104), + MCP4531_ID_TABLE("mcp4642-502", MCP464x_502), + MCP4531_ID_TABLE("mcp4642-103", MCP464x_103), + MCP4531_ID_TABLE("mcp4642-503", MCP464x_503), + MCP4531_ID_TABLE("mcp4642-104", MCP464x_104), + MCP4531_ID_TABLE("mcp4651-502", MCP465x_502), + MCP4531_ID_TABLE("mcp4651-103", MCP465x_103), + MCP4531_ID_TABLE("mcp4651-503", MCP465x_503), + MCP4531_ID_TABLE("mcp4651-104", MCP465x_104), + MCP4531_ID_TABLE("mcp4652-502", MCP465x_502), + MCP4531_ID_TABLE("mcp4652-103", MCP465x_103), + MCP4531_ID_TABLE("mcp4652-503", MCP465x_503), + MCP4531_ID_TABLE("mcp4652-104", MCP465x_104), + MCP4531_ID_TABLE("mcp4661-502", MCP466x_502), + MCP4531_ID_TABLE("mcp4661-103", MCP466x_103), + MCP4531_ID_TABLE("mcp4661-503", MCP466x_503), + MCP4531_ID_TABLE("mcp4661-104", MCP466x_104), + MCP4531_ID_TABLE("mcp4662-502", MCP466x_502), + MCP4531_ID_TABLE("mcp4662-103", MCP466x_103), + MCP4531_ID_TABLE("mcp4662-503", MCP466x_503), + MCP4531_ID_TABLE("mcp4662-104", MCP466x_104), + { } +}; +MODULE_DEVICE_TABLE(i2c, mcp4531_id); + +#define MCP4531_COMPATIBLE(of_compatible, cfg) { \ + .compatible = of_compatible, \ + .data = &mcp4531_cfg[cfg], \ +} + +static const struct of_device_id mcp4531_of_match[] = { + MCP4531_COMPATIBLE("microchip,mcp4531-502", MCP453x_502), + MCP4531_COMPATIBLE("microchip,mcp4531-103", MCP453x_103), + MCP4531_COMPATIBLE("microchip,mcp4531-503", MCP453x_503), + MCP4531_COMPATIBLE("microchip,mcp4531-104", MCP453x_104), + MCP4531_COMPATIBLE("microchip,mcp4532-502", MCP453x_502), + MCP4531_COMPATIBLE("microchip,mcp4532-103", MCP453x_103), + MCP4531_COMPATIBLE("microchip,mcp4532-503", MCP453x_503), + MCP4531_COMPATIBLE("microchip,mcp4532-104", MCP453x_104), + MCP4531_COMPATIBLE("microchip,mcp4541-502", MCP454x_502), + MCP4531_COMPATIBLE("microchip,mcp4541-103", MCP454x_103), + MCP4531_COMPATIBLE("microchip,mcp4541-503", MCP454x_503), + MCP4531_COMPATIBLE("microchip,mcp4541-104", MCP454x_104), + MCP4531_COMPATIBLE("microchip,mcp4542-502", MCP454x_502), + MCP4531_COMPATIBLE("microchip,mcp4542-103", MCP454x_103), + MCP4531_COMPATIBLE("microchip,mcp4542-503", MCP454x_503), + MCP4531_COMPATIBLE("microchip,mcp4542-104", MCP454x_104), + MCP4531_COMPATIBLE("microchip,mcp4551-502", MCP455x_502), + MCP4531_COMPATIBLE("microchip,mcp4551-103", MCP455x_103), + MCP4531_COMPATIBLE("microchip,mcp4551-503", MCP455x_503), + MCP4531_COMPATIBLE("microchip,mcp4551-104", MCP455x_104), + MCP4531_COMPATIBLE("microchip,mcp4552-502", MCP455x_502), + MCP4531_COMPATIBLE("microchip,mcp4552-103", MCP455x_103), + MCP4531_COMPATIBLE("microchip,mcp4552-503", MCP455x_503), + MCP4531_COMPATIBLE("microchip,mcp4552-104", MCP455x_104), + MCP4531_COMPATIBLE("microchip,mcp4561-502", MCP456x_502), + MCP4531_COMPATIBLE("microchip,mcp4561-103", MCP456x_103), + MCP4531_COMPATIBLE("microchip,mcp4561-503", MCP456x_503), + MCP4531_COMPATIBLE("microchip,mcp4561-104", MCP456x_104), + MCP4531_COMPATIBLE("microchip,mcp4562-502", MCP456x_502), + MCP4531_COMPATIBLE("microchip,mcp4562-103", MCP456x_103), + MCP4531_COMPATIBLE("microchip,mcp4562-503", MCP456x_503), + MCP4531_COMPATIBLE("microchip,mcp4562-104", MCP456x_104), + MCP4531_COMPATIBLE("microchip,mcp4631-502", MCP463x_502), + MCP4531_COMPATIBLE("microchip,mcp4631-103", MCP463x_103), + MCP4531_COMPATIBLE("microchip,mcp4631-503", MCP463x_503), + MCP4531_COMPATIBLE("microchip,mcp4631-104", MCP463x_104), + MCP4531_COMPATIBLE("microchip,mcp4632-502", MCP463x_502), + MCP4531_COMPATIBLE("microchip,mcp4632-103", MCP463x_103), + MCP4531_COMPATIBLE("microchip,mcp4632-503", MCP463x_503), + MCP4531_COMPATIBLE("microchip,mcp4632-104", MCP463x_104), + MCP4531_COMPATIBLE("microchip,mcp4641-502", MCP464x_502), + MCP4531_COMPATIBLE("microchip,mcp4641-103", MCP464x_103), + MCP4531_COMPATIBLE("microchip,mcp4641-503", MCP464x_503), + MCP4531_COMPATIBLE("microchip,mcp4641-104", MCP464x_104), + MCP4531_COMPATIBLE("microchip,mcp4642-502", MCP464x_502), + MCP4531_COMPATIBLE("microchip,mcp4642-103", MCP464x_103), + MCP4531_COMPATIBLE("microchip,mcp4642-503", MCP464x_503), + MCP4531_COMPATIBLE("microchip,mcp4642-104", MCP464x_104), + MCP4531_COMPATIBLE("microchip,mcp4651-502", MCP465x_502), + MCP4531_COMPATIBLE("microchip,mcp4651-103", MCP465x_103), + MCP4531_COMPATIBLE("microchip,mcp4651-503", MCP465x_503), + MCP4531_COMPATIBLE("microchip,mcp4651-104", MCP465x_104), + MCP4531_COMPATIBLE("microchip,mcp4652-502", MCP465x_502), + MCP4531_COMPATIBLE("microchip,mcp4652-103", MCP465x_103), + MCP4531_COMPATIBLE("microchip,mcp4652-503", MCP465x_503), + MCP4531_COMPATIBLE("microchip,mcp4652-104", MCP465x_104), + MCP4531_COMPATIBLE("microchip,mcp4661-502", MCP466x_502), + MCP4531_COMPATIBLE("microchip,mcp4661-103", MCP466x_103), + MCP4531_COMPATIBLE("microchip,mcp4661-503", MCP466x_503), + MCP4531_COMPATIBLE("microchip,mcp4661-104", MCP466x_104), + MCP4531_COMPATIBLE("microchip,mcp4662-502", MCP466x_502), + MCP4531_COMPATIBLE("microchip,mcp4662-103", MCP466x_103), + MCP4531_COMPATIBLE("microchip,mcp4662-503", MCP466x_503), + MCP4531_COMPATIBLE("microchip,mcp4662-104", MCP466x_104), + { } +}; +MODULE_DEVICE_TABLE(of, mcp4531_of_match); + +static int mcp4531_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct mcp4531_data *data; + struct iio_dev *indio_dev; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WORD_DATA)) { + dev_err(dev, "SMBUS Word Data not supported\n"); + return -EOPNOTSUPP; + } + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + data->cfg = i2c_get_match_data(client); + + indio_dev->info = &mcp4531_info; + indio_dev->channels = mcp4531_channels; + indio_dev->num_channels = data->cfg->wipers; + indio_dev->name = client->name; + + return devm_iio_device_register(dev, indio_dev); +} + +static struct i2c_driver mcp4531_driver = { + .driver = { + .name = "mcp4531", + .of_match_table = mcp4531_of_match, + }, + .probe = mcp4531_probe, + .id_table = mcp4531_id, +}; + +module_i2c_driver(mcp4531_driver); + +MODULE_AUTHOR("Peter Rosin "); +MODULE_DESCRIPTION("MCP4531 digital potentiometer"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/potentiometer/tpl0102.c b/drivers/iio/potentiometer/tpl0102.c new file mode 100644 index 000000000..77149908e --- /dev/null +++ b/drivers/iio/potentiometer/tpl0102.c @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * tpl0102.c - Support for Texas Instruments digital potentiometers + * + * Copyright (C) 2016, 2018 + * Author: Matt Ranostay + * + * TODO: enable/disable hi-z output control + */ + +#include +#include +#include +#include + +struct tpl0102_cfg { + int wipers; + int avail[3]; + int kohms; +}; + +enum tpl0102_type { + CAT5140_503, + CAT5140_104, + TPL0102_104, + TPL0401_103, +}; + +static const struct tpl0102_cfg tpl0102_cfg[] = { + /* on-semiconductor parts */ + [CAT5140_503] = { .wipers = 1, .avail = { 0, 1, 255 }, .kohms = 50, }, + [CAT5140_104] = { .wipers = 1, .avail = { 0, 1, 255 }, .kohms = 100, }, + /* ti parts */ + [TPL0102_104] = { .wipers = 2, .avail = { 0, 1, 255 }, .kohms = 100 }, + [TPL0401_103] = { .wipers = 1, .avail = { 0, 1, 127 }, .kohms = 10, }, +}; + +struct tpl0102_data { + struct regmap *regmap; + const struct tpl0102_cfg *cfg; +}; + +static const struct regmap_config tpl0102_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +#define TPL0102_CHANNEL(ch) { \ + .type = IIO_RESISTANCE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (ch), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW), \ +} + +static const struct iio_chan_spec tpl0102_channels[] = { + TPL0102_CHANNEL(0), + TPL0102_CHANNEL(1), +}; + +static int tpl0102_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct tpl0102_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + int ret = regmap_read(data->regmap, chan->channel, val); + + return ret ? ret : IIO_VAL_INT; + } + case IIO_CHAN_INFO_SCALE: + *val = 1000 * data->cfg->kohms; + *val2 = data->cfg->avail[2] + 1; + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; +} + +static int tpl0102_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct tpl0102_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *length = ARRAY_SIZE(data->cfg->avail); + *vals = data->cfg->avail; + *type = IIO_VAL_INT; + return IIO_AVAIL_RANGE; + } + + return -EINVAL; +} + +static int tpl0102_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct tpl0102_data *data = iio_priv(indio_dev); + + if (mask != IIO_CHAN_INFO_RAW) + return -EINVAL; + + if (val > data->cfg->avail[2] || val < 0) + return -EINVAL; + + return regmap_write(data->regmap, chan->channel, val); +} + +static const struct iio_info tpl0102_info = { + .read_raw = tpl0102_read_raw, + .read_avail = tpl0102_read_avail, + .write_raw = tpl0102_write_raw, +}; + +static int tpl0102_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct device *dev = &client->dev; + struct tpl0102_data *data; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + + data->cfg = &tpl0102_cfg[id->driver_data]; + data->regmap = devm_regmap_init_i2c(client, &tpl0102_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(dev, "regmap initialization failed\n"); + return PTR_ERR(data->regmap); + } + + indio_dev->info = &tpl0102_info; + indio_dev->channels = tpl0102_channels; + indio_dev->num_channels = data->cfg->wipers; + indio_dev->name = client->name; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct i2c_device_id tpl0102_id[] = { + { .name = "cat5140-503", .driver_data = CAT5140_503 }, + { .name = "cat5140-104", .driver_data = CAT5140_104 }, + { .name = "tpl0102-104", .driver_data = TPL0102_104 }, + { .name = "tpl0401-103", .driver_data = TPL0401_103 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tpl0102_id); + +static struct i2c_driver tpl0102_driver = { + .driver = { + .name = "tpl0102", + }, + .probe = tpl0102_probe, + .id_table = tpl0102_id, +}; + +module_i2c_driver(tpl0102_driver); + +MODULE_AUTHOR("Matt Ranostay "); +MODULE_DESCRIPTION("TPL0102 digital potentiometer"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/potentiometer/x9250.c b/drivers/iio/potentiometer/x9250.c new file mode 100644 index 000000000..974e4447f --- /dev/null +++ b/drivers/iio/potentiometer/x9250.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * + * x9250.c -- Renesas X9250 potentiometers IIO driver + * + * Copyright 2023 CS GROUP France + * + * Author: Herve Codina + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct x9250_cfg { + const char *name; + int kohms; +}; + +struct x9250 { + struct spi_device *spi; + const struct x9250_cfg *cfg; + struct gpio_desc *wp_gpio; +}; + +#define X9250_ID 0x50 +#define X9250_CMD_RD_WCR(_p) (0x90 | (_p)) +#define X9250_CMD_WR_WCR(_p) (0xa0 | (_p)) + +static int x9250_write8(struct x9250 *x9250, u8 cmd, u8 val) +{ + u8 txbuf[3]; + + txbuf[0] = X9250_ID; + txbuf[1] = cmd; + txbuf[2] = val; + + return spi_write_then_read(x9250->spi, txbuf, ARRAY_SIZE(txbuf), NULL, 0); +} + +static int x9250_read8(struct x9250 *x9250, u8 cmd, u8 *val) +{ + u8 txbuf[2]; + + txbuf[0] = X9250_ID; + txbuf[1] = cmd; + + return spi_write_then_read(x9250->spi, txbuf, ARRAY_SIZE(txbuf), val, 1); +} + +#define X9250_CHANNEL(ch) { \ + .type = IIO_RESISTANCE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (ch), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_RAW), \ +} + +static const struct iio_chan_spec x9250_channels[] = { + X9250_CHANNEL(0), + X9250_CHANNEL(1), + X9250_CHANNEL(2), + X9250_CHANNEL(3), +}; + +static int x9250_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct x9250 *x9250 = iio_priv(indio_dev); + int ch = chan->channel; + int ret; + u8 v; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = x9250_read8(x9250, X9250_CMD_RD_WCR(ch), &v); + if (ret) + return ret; + *val = v; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = 1000 * x9250->cfg->kohms; + *val2 = U8_MAX; + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; +} + +static int x9250_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, long mask) +{ + static const int range[] = {0, 1, 255}; /* min, step, max */ + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *length = ARRAY_SIZE(range); + *vals = range; + *type = IIO_VAL_INT; + return IIO_AVAIL_RANGE; + } + + return -EINVAL; +} + +static int x9250_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct x9250 *x9250 = iio_priv(indio_dev); + int ch = chan->channel; + int ret; + + if (mask != IIO_CHAN_INFO_RAW) + return -EINVAL; + + if (val > U8_MAX || val < 0) + return -EINVAL; + + gpiod_set_value_cansleep(x9250->wp_gpio, 0); + ret = x9250_write8(x9250, X9250_CMD_WR_WCR(ch), val); + gpiod_set_value_cansleep(x9250->wp_gpio, 1); + + return ret; +} + +static const struct iio_info x9250_info = { + .read_raw = x9250_read_raw, + .read_avail = x9250_read_avail, + .write_raw = x9250_write_raw, +}; + +enum x9250_type { + X9250T, + X9250U, +}; + +static const struct x9250_cfg x9250_cfg[] = { + [X9250T] = { .name = "x9250t", .kohms = 100, }, + [X9250U] = { .name = "x9250u", .kohms = 50, }, +}; + +static const char *const x9250_regulator_names[] = { + "vcc", + "avp", + "avn", +}; + +static int x9250_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct x9250 *x9250; + int ret; + + ret = devm_regulator_bulk_get_enable(&spi->dev, ARRAY_SIZE(x9250_regulator_names), + x9250_regulator_names); + if (ret) + return dev_err_probe(&spi->dev, ret, "Failed to get regulators\n"); + + /* + * The x9250 needs a 5ms maximum delay after the power-supplies are set + * before performing the first write (1ms for the first read). + */ + usleep_range(5000, 6000); + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*x9250)); + if (!indio_dev) + return -ENOMEM; + + x9250 = iio_priv(indio_dev); + x9250->spi = spi; + x9250->cfg = spi_get_device_match_data(spi); + x9250->wp_gpio = devm_gpiod_get_optional(&spi->dev, "wp", GPIOD_OUT_LOW); + if (IS_ERR(x9250->wp_gpio)) + return dev_err_probe(&spi->dev, PTR_ERR(x9250->wp_gpio), + "failed to get wp gpio\n"); + + indio_dev->info = &x9250_info; + indio_dev->channels = x9250_channels; + indio_dev->num_channels = ARRAY_SIZE(x9250_channels); + indio_dev->name = x9250->cfg->name; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct of_device_id x9250_of_match[] = { + { .compatible = "renesas,x9250t", .data = &x9250_cfg[X9250T]}, + { .compatible = "renesas,x9250u", .data = &x9250_cfg[X9250U]}, + { } +}; +MODULE_DEVICE_TABLE(of, x9250_of_match); + +static const struct spi_device_id x9250_id_table[] = { + { .name = "x9250t", .driver_data = (kernel_ulong_t)&x9250_cfg[X9250T] }, + { .name = "x9250u", .driver_data = (kernel_ulong_t)&x9250_cfg[X9250U] }, + { } +}; +MODULE_DEVICE_TABLE(spi, x9250_id_table); + +static struct spi_driver x9250_spi_driver = { + .driver = { + .name = "x9250", + .of_match_table = x9250_of_match, + }, + .id_table = x9250_id_table, + .probe = x9250_probe, +}; + +module_spi_driver(x9250_spi_driver); + +MODULE_AUTHOR("Herve Codina "); +MODULE_DESCRIPTION("X9250 ALSA SoC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/potentiostat/Kconfig b/drivers/iio/potentiostat/Kconfig new file mode 100644 index 000000000..72501bf16 --- /dev/null +++ b/drivers/iio/potentiostat/Kconfig @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Potentiostat drivers +# +# When adding new entries keep the list in alphabetical order + +menu "Digital potentiostats" + +config LMP91000 + tristate "Texas Instruments LMP91000 potentiostat driver" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_BUFFER_CB + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for the Texas Instruments + LMP91000 digital potentiostat chip. + + To compile this driver as a module, choose M here: the + module will be called lmp91000 + +endmenu diff --git a/drivers/iio/potentiostat/Makefile b/drivers/iio/potentiostat/Makefile new file mode 100644 index 000000000..be78b46ac --- /dev/null +++ b/drivers/iio/potentiostat/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for industrial I/O potentiostat drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_LMP91000) += lmp91000.o diff --git a/drivers/iio/potentiostat/lmp91000.c b/drivers/iio/potentiostat/lmp91000.c new file mode 100644 index 000000000..00cb3fea9 --- /dev/null +++ b/drivers/iio/potentiostat/lmp91000.c @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * lmp91000.c - Support for Texas Instruments digital potentiostats + * + * Copyright (C) 2016, 2018 + * Author: Matt Ranostay + * + * TODO: bias voltage + polarity control, and multiple chip support + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LMP91000_REG_LOCK 0x01 +#define LMP91000_REG_TIACN 0x10 +#define LMP91000_REG_TIACN_GAIN_SHIFT 2 + +#define LMP91000_REG_REFCN 0x11 +#define LMP91000_REG_REFCN_EXT_REF 0x20 +#define LMP91000_REG_REFCN_50_ZERO 0x80 + +#define LMP91000_REG_MODECN 0x12 +#define LMP91000_REG_MODECN_3LEAD 0x03 +#define LMP91000_REG_MODECN_TEMP 0x07 + +#define LMP91000_DRV_NAME "lmp91000" + +static const int lmp91000_tia_gain[] = { 0, 2750, 3500, 7000, 14000, 35000, + 120000, 350000 }; + +static const int lmp91000_rload[] = { 10, 33, 50, 100 }; + +#define LMP91000_TEMP_BASE -40 + +static const u16 lmp91000_temp_lut[] = { + 1875, 1867, 1860, 1852, 1844, 1836, 1828, 1821, 1813, 1805, + 1797, 1789, 1782, 1774, 1766, 1758, 1750, 1742, 1734, 1727, + 1719, 1711, 1703, 1695, 1687, 1679, 1671, 1663, 1656, 1648, + 1640, 1632, 1624, 1616, 1608, 1600, 1592, 1584, 1576, 1568, + 1560, 1552, 1544, 1536, 1528, 1520, 1512, 1504, 1496, 1488, + 1480, 1472, 1464, 1456, 1448, 1440, 1432, 1424, 1415, 1407, + 1399, 1391, 1383, 1375, 1367, 1359, 1351, 1342, 1334, 1326, + 1318, 1310, 1302, 1293, 1285, 1277, 1269, 1261, 1253, 1244, + 1236, 1228, 1220, 1212, 1203, 1195, 1187, 1179, 1170, 1162, + 1154, 1146, 1137, 1129, 1121, 1112, 1104, 1096, 1087, 1079, + 1071, 1063, 1054, 1046, 1038, 1029, 1021, 1012, 1004, 996, + 987, 979, 971, 962, 954, 945, 937, 929, 920, 912, + 903, 895, 886, 878, 870, 861 }; + +static const struct regmap_config lmp91000_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +struct lmp91000_data { + struct regmap *regmap; + struct device *dev; + + struct iio_trigger *trig; + struct iio_cb_buffer *cb_buffer; + struct iio_channel *adc_chan; + + struct completion completion; + u8 chan_select; + /* 64-bit data + 64-bit naturally aligned timestamp */ + u32 buffer[4] __aligned(8); +}; + +static const struct iio_chan_spec lmp91000_channels[] = { + { /* chemical channel mV */ + .type = IIO_VOLTAGE, + .channel = 0, + .address = LMP91000_REG_MODECN_3LEAD, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), + { /* temperature channel mV */ + .type = IIO_TEMP, + .channel = 1, + .address = LMP91000_REG_MODECN_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = -1, + }, +}; + +static int lmp91000_read(struct lmp91000_data *data, int channel, int *val) +{ + int state, ret; + + ret = regmap_read(data->regmap, LMP91000_REG_MODECN, &state); + if (ret) + return -EINVAL; + + ret = regmap_write(data->regmap, LMP91000_REG_MODECN, channel); + if (ret) + return -EINVAL; + + /* delay till first temperature reading is complete */ + if (state != channel && channel == LMP91000_REG_MODECN_TEMP) + usleep_range(3000, 4000); + + data->chan_select = channel != LMP91000_REG_MODECN_3LEAD; + + iio_trigger_poll_nested(data->trig); + + ret = wait_for_completion_timeout(&data->completion, HZ); + reinit_completion(&data->completion); + + if (!ret) + return -ETIMEDOUT; + + *val = data->buffer[data->chan_select]; + + return 0; +} + +static irqreturn_t lmp91000_buffer_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct lmp91000_data *data = iio_priv(indio_dev); + int ret, val; + + memset(data->buffer, 0, sizeof(data->buffer)); + + ret = lmp91000_read(data, LMP91000_REG_MODECN_3LEAD, &val); + if (!ret) { + data->buffer[0] = val; + iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, + iio_get_time_ns(indio_dev)); + } + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int lmp91000_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct lmp91000_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: { + int ret = iio_channel_start_all_cb(data->cb_buffer); + + if (ret) + return ret; + + ret = lmp91000_read(data, chan->address, val); + + iio_channel_stop_all_cb(data->cb_buffer); + + if (ret) + return ret; + + if (mask == IIO_CHAN_INFO_PROCESSED) { + int tmp, i; + + ret = iio_convert_raw_to_processed(data->adc_chan, + *val, &tmp, 1); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(lmp91000_temp_lut); i++) + if (lmp91000_temp_lut[i] < tmp) + break; + + *val = (LMP91000_TEMP_BASE + i) * 1000; + } + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_OFFSET: + return iio_read_channel_offset(data->adc_chan, val, val2); + case IIO_CHAN_INFO_SCALE: + return iio_read_channel_scale(data->adc_chan, val, val2); + } + + return -EINVAL; +} + +static const struct iio_info lmp91000_info = { + .read_raw = lmp91000_read_raw, +}; + +static int lmp91000_read_config(struct lmp91000_data *data) +{ + struct device *dev = data->dev; + unsigned int reg, val; + int i, ret; + + ret = device_property_read_u32(dev, "ti,tia-gain-ohm", &val); + if (ret) { + if (!device_property_read_bool(dev, "ti,external-tia-resistor")) { + dev_err(dev, "no ti,tia-gain-ohm defined and external resistor not specified\n"); + return ret; + } + val = 0; + } + + ret = -EINVAL; + for (i = 0; i < ARRAY_SIZE(lmp91000_tia_gain); i++) { + if (lmp91000_tia_gain[i] == val) { + reg = i << LMP91000_REG_TIACN_GAIN_SHIFT; + ret = 0; + break; + } + } + + if (ret) { + dev_err(dev, "invalid ti,tia-gain-ohm %d\n", val); + return ret; + } + + ret = device_property_read_u32(dev, "ti,rload-ohm", &val); + if (ret) { + val = 100; + dev_info(dev, "no ti,rload-ohm defined, default to %d\n", val); + } + + ret = -EINVAL; + for (i = 0; i < ARRAY_SIZE(lmp91000_rload); i++) { + if (lmp91000_rload[i] == val) { + reg |= i; + ret = 0; + break; + } + } + + if (ret) { + dev_err(dev, "invalid ti,rload-ohm %d\n", val); + return ret; + } + + regmap_write(data->regmap, LMP91000_REG_LOCK, 0); + regmap_write(data->regmap, LMP91000_REG_TIACN, reg); + regmap_write(data->regmap, LMP91000_REG_REFCN, + LMP91000_REG_REFCN_EXT_REF | LMP91000_REG_REFCN_50_ZERO); + regmap_write(data->regmap, LMP91000_REG_LOCK, 1); + + return 0; +} + +static int lmp91000_buffer_cb(const void *val, void *private) +{ + struct iio_dev *indio_dev = private; + struct lmp91000_data *data = iio_priv(indio_dev); + + data->buffer[data->chan_select] = *((int *)val); + complete_all(&data->completion); + + return 0; +} + +static int lmp91000_buffer_postenable(struct iio_dev *indio_dev) +{ + struct lmp91000_data *data = iio_priv(indio_dev); + + return iio_channel_start_all_cb(data->cb_buffer); +} + +static int lmp91000_buffer_predisable(struct iio_dev *indio_dev) +{ + struct lmp91000_data *data = iio_priv(indio_dev); + + iio_channel_stop_all_cb(data->cb_buffer); + + return 0; +} + +static const struct iio_buffer_setup_ops lmp91000_buffer_setup_ops = { + .postenable = lmp91000_buffer_postenable, + .predisable = lmp91000_buffer_predisable, +}; + +static int lmp91000_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct lmp91000_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + indio_dev->info = &lmp91000_info; + indio_dev->channels = lmp91000_channels; + indio_dev->num_channels = ARRAY_SIZE(lmp91000_channels); + indio_dev->name = LMP91000_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + i2c_set_clientdata(client, indio_dev); + + data = iio_priv(indio_dev); + data->dev = dev; + data->regmap = devm_regmap_init_i2c(client, &lmp91000_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(dev, "regmap initialization failed.\n"); + return PTR_ERR(data->regmap); + } + + data->trig = devm_iio_trigger_alloc(dev, "%s-mux%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->trig) + return -ENOMEM; + + init_completion(&data->completion); + + ret = lmp91000_read_config(data); + if (ret) + return ret; + + ret = iio_trigger_set_immutable(iio_channel_cb_get_iio_dev(data->cb_buffer), + data->trig); + if (ret) { + dev_err(dev, "cannot set immutable trigger.\n"); + return ret; + } + + ret = iio_trigger_register(data->trig); + if (ret) { + dev_err(dev, "cannot register iio trigger.\n"); + return ret; + } + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + &lmp91000_buffer_handler, + &lmp91000_buffer_setup_ops); + if (ret) + goto error_unreg_trigger; + + data->cb_buffer = iio_channel_get_all_cb(dev, &lmp91000_buffer_cb, + indio_dev); + + if (IS_ERR(data->cb_buffer)) { + if (PTR_ERR(data->cb_buffer) == -ENODEV) + ret = -EPROBE_DEFER; + else + ret = PTR_ERR(data->cb_buffer); + + goto error_unreg_buffer; + } + + data->adc_chan = iio_channel_cb_get_channels(data->cb_buffer); + + ret = iio_device_register(indio_dev); + if (ret) + goto error_unreg_cb_buffer; + + return 0; + +error_unreg_cb_buffer: + iio_channel_release_all_cb(data->cb_buffer); + +error_unreg_buffer: + iio_triggered_buffer_cleanup(indio_dev); + +error_unreg_trigger: + iio_trigger_unregister(data->trig); + + return ret; +} + +static void lmp91000_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct lmp91000_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + iio_channel_stop_all_cb(data->cb_buffer); + iio_channel_release_all_cb(data->cb_buffer); + + iio_triggered_buffer_cleanup(indio_dev); + iio_trigger_unregister(data->trig); +} + +static const struct of_device_id lmp91000_of_match[] = { + { .compatible = "ti,lmp91000", }, + { .compatible = "ti,lmp91002", }, + { } +}; +MODULE_DEVICE_TABLE(of, lmp91000_of_match); + +static const struct i2c_device_id lmp91000_id[] = { + { .name = "lmp91000" }, + { .name = "lmp91002" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lmp91000_id); + +static struct i2c_driver lmp91000_driver = { + .driver = { + .name = LMP91000_DRV_NAME, + .of_match_table = lmp91000_of_match, + }, + .probe = lmp91000_probe, + .remove = lmp91000_remove, + .id_table = lmp91000_id, +}; +module_i2c_driver(lmp91000_driver); + +MODULE_AUTHOR("Matt Ranostay "); +MODULE_DESCRIPTION("LMP91000 digital potentiostat"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_CONSUMER"); diff --git a/drivers/iio/pressure/Kconfig b/drivers/iio/pressure/Kconfig new file mode 100644 index 000000000..838a8340c --- /dev/null +++ b/drivers/iio/pressure/Kconfig @@ -0,0 +1,387 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Pressure drivers +# +# When adding new entries keep the list in alphabetical order + +menu "Pressure sensors" + +config ABP060MG + tristate "Honeywell ABP pressure sensor driver" + depends on I2C + help + Say yes here to build support for the Honeywell ABP pressure + sensors. + + To compile this driver as a module, choose M here: the module + will be called abp060mg. + +config ABP2030PA + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config ABP2030PA_I2C + tristate "Honeywell ABP2 pressure sensor series I2C driver" + depends on I2C + select ABP2030PA + help + Say Y here to build I2C bus support for the Honeywell ABP2 + series pressure and temperature digital sensor. + + To compile this driver as a module, choose M here: the module + will be called abp2030pa_i2c and you will also get abp2030pa + for the core module. + +config ABP2030PA_SPI + tristate "Honeywell ABP2 pressure sensor series SPI driver" + depends on SPI_MASTER + select ABP2030PA + help + Say Y here to build SPI bus support for the Honeywell ABP2 + series pressure and temperature digital sensor. + + To compile this driver as a module, choose M here: the module + will be called abp2030pa_spi and you will also get abp2030pa + for the core module. + +config ROHM_BM1390 + tristate "ROHM BM1390GLV-Z pressure sensor driver" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Support for the ROHM BM1390 pressure sensor. The BM1390GLV-Z + can measure pressures ranging from 300 hPa to 1300 hPa with + configurable measurement averaging and internal FIFO. The + sensor does also provide temperature measurements. + +config BMP280 + tristate "Bosch Sensortec BMP180/BMP280/BMP380/BMP580 pressure sensor driver" + depends on (I2C || SPI_MASTER) + select REGMAP + select BMP280_I2C if (I2C) + select BMP280_SPI if (SPI_MASTER) + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Bosch Sensortec BMP180, BMP280, BMP380 + and BMP580 pressure and temperature sensors. Also supports the BME280 with + an additional humidity sensor channel. + + To compile this driver as a module, choose M here: the core module + will be called bmp280 and you will also get bmp280-i2c for I2C + and/or bmp280-spi for SPI support. + +config BMP280_I2C + tristate + depends on BMP280 + depends on I2C + select REGMAP_I2C + +config BMP280_SPI + tristate + depends on BMP280 + depends on SPI_MASTER + select REGMAP + +config IIO_CROS_EC_BARO + tristate "ChromeOS EC Barometer Sensor" + depends on IIO_CROS_EC_SENSORS_CORE + help + Say yes here to build support for the Barometer sensor when + presented by the ChromeOS EC Sensor hub. + + To compile this driver as a module, choose M here: the module + will be called cros_ec_baro. + +config DLHL60D + tristate "All Sensors DLHL60D and DLHL60G low voltage digital pressure sensors" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for the All Sensors DLH series + pressure sensors driver. + + To compile this driver as a module, choose M here: the module + will be called dlhl60d. + +config DPS310 + tristate "Infineon DPS310 pressure and temperature sensor" + depends on I2C + select REGMAP_I2C + help + Support for the Infineon DPS310 digital barometric pressure sensor. + It can be accessed over I2C bus. + + This driver can also be built as a module. If so, the module will be + called dps310. + +config HID_SENSOR_PRESS + depends on HID_SENSOR_HUB + select IIO_BUFFER + select HID_SENSOR_IIO_COMMON + select HID_SENSOR_IIO_TRIGGER + tristate "HID PRESS" + help + Say yes here to build support for the HID SENSOR + Pressure driver + + To compile this driver as a module, choose M here: the module + will be called hid-sensor-press. + +config HP03 + tristate "Hope RF HP03 temperature and pressure sensor driver" + depends on I2C + select REGMAP_I2C + help + Say yes here to build support for Hope RF HP03 pressure and + temperature sensor. + + To compile this driver as a module, choose M here: the module + will be called hp03. + +config HSC030PA + tristate "Honeywell HSC/SSC TruStability pressure sensor series" + depends on (I2C || SPI_MASTER) + select HSC030PA_I2C if I2C + select HSC030PA_SPI if SPI_MASTER + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here to build support for the Honeywell TruStability + HSC and SSC pressure and temperature sensor series. + + To compile this driver as a module, choose M here: the module + will be called hsc030pa. + +config HSC030PA_I2C + tristate + depends on HSC030PA + depends on I2C + +config HSC030PA_SPI + tristate + depends on HSC030PA + depends on SPI_MASTER + +config ICP10100 + tristate "InvenSense ICP-101xx pressure and temperature sensor" + depends on I2C + select CRC8 + help + Say yes here to build support for InvenSense ICP-101xx barometric + pressure and temperature sensor. + + To compile this driver as a module, choose M here: the module + will be called icp10100. + +config MPL115 + tristate + +config MPL115_I2C + tristate "Freescale MPL115A2 pressure sensor driver" + depends on I2C + select MPL115 + help + Say yes here to build support for the Freescale MPL115A2 + pressure sensor connected via I2C. + + To compile this driver as a module, choose M here: the module + will be called mpl115_i2c. + +config MPL115_SPI + tristate "Freescale MPL115A1 pressure sensor driver" + depends on SPI_MASTER + select MPL115 + help + Say yes here to build support for the Freescale MPL115A1 + pressure sensor connected via SPI. + + To compile this driver as a module, choose M here: the module + will be called mpl115_spi. + +config MPL3115 + tristate "Freescale MPL3115A2 pressure sensor driver" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for the Freescale MPL3115A2 + pressure sensor / altimeter. + + To compile this driver as a module, choose M here: the module + will be called mpl3115. + +config MPRLS0025PA + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config MPRLS0025PA_I2C + tristate "Honeywell MPR pressure sensor series I2C driver" + depends on I2C + select MPRLS0025PA + help + Say Y here to build I2C bus support for the Honeywell MicroPressure + series sensor. + + To compile this driver as a module, choose M here: the module + will be called mprls0025pa_i2c and you will also get mprls0025pa + for the core module. + +config MPRLS0025PA_SPI + tristate "Honeywell MPR pressure sensor series SPI driver" + depends on SPI_MASTER + select MPRLS0025PA + help + Say Y here to build SPI bus support for the Honeywell MicroPressure + series sensor. + + To compile this driver as a module, choose M here: the module + will be called mprls0025pa_spi and you will also get mprls0025pa + for the core module. + +config MS5611 + tristate "Measurement Specialties MS5611 pressure sensor driver" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here to build support for the Measurement Specialties + MS5611, MS5607 pressure and temperature sensors. + + To compile this driver as a module, choose M here: the module will + be called ms5611_core. + +config MS5611_I2C + tristate "support I2C bus connection" + depends on I2C && MS5611 + help + Say Y here to build I2C bus support for MS5611. + + To compile this driver as a module, choose M here: the module will + be called ms5611_i2c. + +config MS5611_SPI + tristate "support SPI bus connection" + depends on SPI_MASTER && MS5611 + help + Say Y here to build SPI bus support for MS5611. + + To compile this driver as a module, choose M here: the module will + be called ms5611_spi. + +config MS5637 + tristate "Measurement Specialties MS5637 pressure & temperature sensor" + depends on I2C + select IIO_MS_SENSORS_I2C + help + If you say yes here you get support for the Measurement Specialties + MS5637 pressure and temperature sensor. + This driver is also used for MS8607 temperature, pressure & humidity + sensor + + This driver can also be built as a module. If so, the module will + be called ms5637. + +config SDP500 + tristate "Sensirion SDP500 differential pressure sensor I2C driver" + depends on I2C + select CRC8 + help + Say Y here to build support for Sensirion SDP500 differential pressure + sensor I2C driver. + To compile this driver as a module, choose M here: the core module + will be called sdp500. + +config IIO_ST_PRESS + tristate "STMicroelectronics pressure sensor Driver" + depends on (I2C || SPI_MASTER) && SYSFS + select IIO_ST_SENSORS_CORE + select IIO_TRIGGERED_BUFFER if (IIO_BUFFER) + help + Say yes here to build support for STMicroelectronics pressure + sensors: LPS001WP, LPS25H, LPS331AP, LPS22HB, LPS22HH. + + Also need to enable at least one of I2C and SPI interface drivers + below. + +config IIO_ST_PRESS_I2C + tristate "STMicroelectronics pressure sensor I2C Interface" + depends on I2C && IIO_ST_PRESS + default I2C && IIO_ST_PRESS + select IIO_ST_SENSORS_I2C + help + Build support for STMicroelectronics pressure sensor I2C interface. + + To compile this driver as a module, choose M here. The module + will be called st_pressure_i2c. + +config IIO_ST_PRESS_SPI + tristate "STMicroelectronics pressure sensor SPI Interface" + depends on SPI_MASTER && IIO_ST_PRESS + default SPI_MASTER && IIO_ST_PRESS + select IIO_ST_SENSORS_SPI + help + Build support for STMicroelectronics pressure sensor SPI interface. + + To compile this driver as a module, choose M here. The module + will be called st_pressure_spi. + +config T5403 + tristate "EPCOS T5403 digital barometric pressure sensor driver" + depends on I2C + help + Say yes here to build support for the EPCOS T5403 pressure sensor + connected via I2C. + + To compile this driver as a module, choose M here: the module + will be called t5403. + +config HP206C + tristate "HOPERF HP206C precision barometer and altimeter sensor" + depends on I2C + help + Say yes here to build support for the HOPREF HP206C precision + barometer and altimeter sensor. + + This driver can also be built as a module. If so, the module will + be called hp206c. + +config ZPA2326 + tristate "Murata ZPA2326 pressure sensor driver" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select REGMAP + select ZPA2326_I2C if I2C + select ZPA2326_SPI if SPI_MASTER + help + Say Y here to build support for the Murata ZPA2326 pressure and + temperature sensor. + + To compile this driver as a module, choose M here: the module will + be called zpa2326. + +config ZPA2326_I2C + tristate + select REGMAP_I2C + +config ZPA2326_SPI + tristate + select REGMAP_SPI + +config ADP810 + tristate "Aosong adp810 differential pressure and temperature sensor" + depends on I2C + select CRC8 + help + Say yes here to build adp810 differential pressure and temperature + sensor driver. ADP810 can measure pressure range up to 500Pa. + It supports an I2C interface for data communication. + + To compile this driver as a module, choose M here: the module will + be called adp810 + +endmenu diff --git a/drivers/iio/pressure/Makefile b/drivers/iio/pressure/Makefile new file mode 100644 index 000000000..bc0d11a20 --- /dev/null +++ b/drivers/iio/pressure/Makefile @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for industrial I/O pressure drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_ABP060MG) += abp060mg.o +obj-$(CONFIG_ABP2030PA) += abp2030pa.o +obj-$(CONFIG_ABP2030PA_I2C) += abp2030pa_i2c.o +obj-$(CONFIG_ABP2030PA_SPI) += abp2030pa_spi.o +obj-$(CONFIG_ADP810) += adp810.o +obj-$(CONFIG_ROHM_BM1390) += rohm-bm1390.o +obj-$(CONFIG_BMP280) += bmp280.o +bmp280-objs := bmp280-core.o bmp280-regmap.o +obj-$(CONFIG_BMP280_I2C) += bmp280-i2c.o +obj-$(CONFIG_BMP280_SPI) += bmp280-spi.o +obj-$(CONFIG_DLHL60D) += dlhl60d.o +obj-$(CONFIG_DPS310) += dps310.o +obj-$(CONFIG_IIO_CROS_EC_BARO) += cros_ec_baro.o +obj-$(CONFIG_HID_SENSOR_PRESS) += hid-sensor-press.o +obj-$(CONFIG_HP03) += hp03.o +obj-$(CONFIG_HP206C) += hp206c.o +obj-$(CONFIG_HSC030PA) += hsc030pa.o +obj-$(CONFIG_HSC030PA_I2C) += hsc030pa_i2c.o +obj-$(CONFIG_HSC030PA_SPI) += hsc030pa_spi.o +obj-$(CONFIG_ICP10100) += icp10100.o +obj-$(CONFIG_MPL115) += mpl115.o +obj-$(CONFIG_MPL115_I2C) += mpl115_i2c.o +obj-$(CONFIG_MPL115_SPI) += mpl115_spi.o +obj-$(CONFIG_MPL3115) += mpl3115.o +obj-$(CONFIG_MPRLS0025PA) += mprls0025pa.o +obj-$(CONFIG_MPRLS0025PA_I2C) += mprls0025pa_i2c.o +obj-$(CONFIG_MPRLS0025PA_SPI) += mprls0025pa_spi.o +obj-$(CONFIG_MS5611) += ms5611_core.o +obj-$(CONFIG_MS5611_I2C) += ms5611_i2c.o +obj-$(CONFIG_MS5611_SPI) += ms5611_spi.o +obj-$(CONFIG_MS5637) += ms5637.o +obj-$(CONFIG_SDP500) += sdp500.o +obj-$(CONFIG_IIO_ST_PRESS) += st_pressure.o +st_pressure-y := st_pressure_core.o +st_pressure-$(CONFIG_IIO_BUFFER) += st_pressure_buffer.o +obj-$(CONFIG_IIO_ST_PRESS_I2C) += st_pressure_i2c.o +obj-$(CONFIG_IIO_ST_PRESS_SPI) += st_pressure_spi.o +obj-$(CONFIG_T5403) += t5403.o +obj-$(CONFIG_ZPA2326) += zpa2326.o +obj-$(CONFIG_ZPA2326_I2C) += zpa2326_i2c.o +obj-$(CONFIG_ZPA2326_SPI) += zpa2326_spi.o diff --git a/drivers/iio/pressure/abp060mg.c b/drivers/iio/pressure/abp060mg.c new file mode 100644 index 000000000..fd48bed35 --- /dev/null +++ b/drivers/iio/pressure/abp060mg.c @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2016 - Marcin Malagowski + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define ABP060MG_ERROR_MASK 0xC000 +#define ABP060MG_RESP_TIME_MS 40 +#define ABP060MG_MIN_COUNTS 1638 /* = 0x0666 (10% of u14) */ +#define ABP060MG_MAX_COUNTS 14745 /* = 0x3999 (90% of u14) */ +#define ABP060MG_NUM_COUNTS (ABP060MG_MAX_COUNTS - ABP060MG_MIN_COUNTS) + +enum abp_variant { + /* gage [kPa] */ + ABP006KG, ABP010KG, ABP016KG, ABP025KG, ABP040KG, ABP060KG, ABP100KG, + ABP160KG, ABP250KG, ABP400KG, ABP600KG, ABP001GG, + /* differential [kPa] */ + ABP006KD, ABP010KD, ABP016KD, ABP025KD, ABP040KD, ABP060KD, ABP100KD, + ABP160KD, ABP250KD, ABP400KD, + /* gage [psi] */ + ABP001PG, ABP005PG, ABP015PG, ABP030PG, ABP060PG, ABP100PG, ABP150PG, + /* differential [psi] */ + ABP001PD, ABP005PD, ABP015PD, ABP030PD, ABP060PD, +}; + +struct abp_config { + int min; + int max; +}; + +static const struct abp_config abp_config[] = { + /* mbar & kPa variants */ + [ABP006KG] = { .min = 0, .max = 6000 }, + [ABP010KG] = { .min = 0, .max = 10000 }, + [ABP016KG] = { .min = 0, .max = 16000 }, + [ABP025KG] = { .min = 0, .max = 25000 }, + [ABP040KG] = { .min = 0, .max = 40000 }, + [ABP060KG] = { .min = 0, .max = 60000 }, + [ABP100KG] = { .min = 0, .max = 100000 }, + [ABP160KG] = { .min = 0, .max = 160000 }, + [ABP250KG] = { .min = 0, .max = 250000 }, + [ABP400KG] = { .min = 0, .max = 400000 }, + [ABP600KG] = { .min = 0, .max = 600000 }, + [ABP001GG] = { .min = 0, .max = 1000000 }, + [ABP006KD] = { .min = -6000, .max = 6000 }, + [ABP010KD] = { .min = -10000, .max = 10000 }, + [ABP016KD] = { .min = -16000, .max = 16000 }, + [ABP025KD] = { .min = -25000, .max = 25000 }, + [ABP040KD] = { .min = -40000, .max = 40000 }, + [ABP060KD] = { .min = -60000, .max = 60000 }, + [ABP100KD] = { .min = -100000, .max = 100000 }, + [ABP160KD] = { .min = -160000, .max = 160000 }, + [ABP250KD] = { .min = -250000, .max = 250000 }, + [ABP400KD] = { .min = -400000, .max = 400000 }, + /* psi variants (1 psi ~ 6895 Pa) */ + [ABP001PG] = { .min = 0, .max = 6985 }, + [ABP005PG] = { .min = 0, .max = 34474 }, + [ABP015PG] = { .min = 0, .max = 103421 }, + [ABP030PG] = { .min = 0, .max = 206843 }, + [ABP060PG] = { .min = 0, .max = 413686 }, + [ABP100PG] = { .min = 0, .max = 689476 }, + [ABP150PG] = { .min = 0, .max = 1034214 }, + [ABP001PD] = { .min = -6895, .max = 6895 }, + [ABP005PD] = { .min = -34474, .max = 34474 }, + [ABP015PD] = { .min = -103421, .max = 103421 }, + [ABP030PD] = { .min = -206843, .max = 206843 }, + [ABP060PD] = { .min = -413686, .max = 413686 }, +}; + +struct abp_state { + struct i2c_client *client; + struct mutex lock; + + /* + * bus-dependent MEASURE_REQUEST length. + * If no SMBUS_QUICK support, need to send dummy byte + */ + int mreq_len; + + /* model-dependent values (calculated on probe) */ + int scale; + int offset; +}; + +static const struct iio_chan_spec abp060mg_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static int abp060mg_get_measurement(struct abp_state *state, int *val) +{ + struct i2c_client *client = state->client; + __be16 buf[2]; + u16 pressure; + int ret; + + buf[0] = 0; + ret = i2c_master_send(client, (u8 *)&buf, state->mreq_len); + if (ret < 0) + return ret; + + msleep_interruptible(ABP060MG_RESP_TIME_MS); + + ret = i2c_master_recv(client, (u8 *)&buf, sizeof(buf)); + if (ret < 0) + return ret; + + pressure = be16_to_cpu(buf[0]); + if (pressure & ABP060MG_ERROR_MASK) + return -EIO; + + if (pressure < ABP060MG_MIN_COUNTS || pressure > ABP060MG_MAX_COUNTS) + return -EIO; + + *val = pressure; + + return IIO_VAL_INT; +} + +static int abp060mg_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct abp_state *state = iio_priv(indio_dev); + int ret; + + mutex_lock(&state->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = abp060mg_get_measurement(state, val); + break; + case IIO_CHAN_INFO_OFFSET: + *val = state->offset; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + *val = state->scale; + *val2 = ABP060MG_NUM_COUNTS * 1000; /* to kPa */ + ret = IIO_VAL_FRACTIONAL; + break; + default: + ret = -EINVAL; + break; + } + + mutex_unlock(&state->lock); + return ret; +} + +static const struct iio_info abp060mg_info = { + .read_raw = abp060mg_read_raw, +}; + +static void abp060mg_init_device(struct iio_dev *indio_dev, unsigned long id) +{ + struct abp_state *state = iio_priv(indio_dev); + const struct abp_config *cfg = &abp_config[id]; + + state->scale = cfg->max - cfg->min; + state->offset = -ABP060MG_MIN_COUNTS; + + if (cfg->min < 0) /* differential */ + state->offset -= ABP060MG_NUM_COUNTS >> 1; +} + +static int abp060mg_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct iio_dev *indio_dev; + struct abp_state *state; + unsigned long cfg_id = id->driver_data; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*state)); + if (!indio_dev) + return -ENOMEM; + + state = iio_priv(indio_dev); + i2c_set_clientdata(client, state); + state->client = client; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_QUICK)) + state->mreq_len = 1; + + abp060mg_init_device(indio_dev, cfg_id); + + indio_dev->name = dev_name(&client->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &abp060mg_info; + + indio_dev->channels = abp060mg_channels; + indio_dev->num_channels = ARRAY_SIZE(abp060mg_channels); + + mutex_init(&state->lock); + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id abp060mg_id_table[] = { + /* mbar & kPa variants (abp060m [60 mbar] == abp006k [6 kPa]) */ + /* gage: */ + { .name = "abp060mg", .driver_data = ABP006KG }, + { .name = "abp006kg", .driver_data = ABP006KG }, + { .name = "abp100mg", .driver_data = ABP010KG }, + { .name = "abp010kg", .driver_data = ABP010KG }, + { .name = "abp160mg", .driver_data = ABP016KG }, + { .name = "abp016kg", .driver_data = ABP016KG }, + { .name = "abp250mg", .driver_data = ABP025KG }, + { .name = "abp025kg", .driver_data = ABP025KG }, + { .name = "abp400mg", .driver_data = ABP040KG }, + { .name = "abp040kg", .driver_data = ABP040KG }, + { .name = "abp600mg", .driver_data = ABP060KG }, + { .name = "abp060kg", .driver_data = ABP060KG }, + { .name = "abp001bg", .driver_data = ABP100KG }, + { .name = "abp100kg", .driver_data = ABP100KG }, + { .name = "abp1_6bg", .driver_data = ABP160KG }, + { .name = "abp160kg", .driver_data = ABP160KG }, + { .name = "abp2_5bg", .driver_data = ABP250KG }, + { .name = "abp250kg", .driver_data = ABP250KG }, + { .name = "abp004bg", .driver_data = ABP400KG }, + { .name = "abp400kg", .driver_data = ABP400KG }, + { .name = "abp006bg", .driver_data = ABP600KG }, + { .name = "abp600kg", .driver_data = ABP600KG }, + { .name = "abp010bg", .driver_data = ABP001GG }, + { .name = "abp001gg", .driver_data = ABP001GG }, + /* differential: */ + { .name = "abp060md", .driver_data = ABP006KD }, + { .name = "abp006kd", .driver_data = ABP006KD }, + { .name = "abp100md", .driver_data = ABP010KD }, + { .name = "abp010kd", .driver_data = ABP010KD }, + { .name = "abp160md", .driver_data = ABP016KD }, + { .name = "abp016kd", .driver_data = ABP016KD }, + { .name = "abp250md", .driver_data = ABP025KD }, + { .name = "abp025kd", .driver_data = ABP025KD }, + { .name = "abp400md", .driver_data = ABP040KD }, + { .name = "abp040kd", .driver_data = ABP040KD }, + { .name = "abp600md", .driver_data = ABP060KD }, + { .name = "abp060kd", .driver_data = ABP060KD }, + { .name = "abp001bd", .driver_data = ABP100KD }, + { .name = "abp100kd", .driver_data = ABP100KD }, + { .name = "abp1_6bd", .driver_data = ABP160KD }, + { .name = "abp160kd", .driver_data = ABP160KD }, + { .name = "abp2_5bd", .driver_data = ABP250KD }, + { .name = "abp250kd", .driver_data = ABP250KD }, + { .name = "abp004bd", .driver_data = ABP400KD }, + { .name = "abp400kd", .driver_data = ABP400KD }, + /* psi variants */ + /* gage: */ + { .name = "abp001pg", .driver_data = ABP001PG }, + { .name = "abp005pg", .driver_data = ABP005PG }, + { .name = "abp015pg", .driver_data = ABP015PG }, + { .name = "abp030pg", .driver_data = ABP030PG }, + { .name = "abp060pg", .driver_data = ABP060PG }, + { .name = "abp100pg", .driver_data = ABP100PG }, + { .name = "abp150pg", .driver_data = ABP150PG }, + /* differential: */ + { .name = "abp001pd", .driver_data = ABP001PD }, + { .name = "abp005pd", .driver_data = ABP005PD }, + { .name = "abp015pd", .driver_data = ABP015PD }, + { .name = "abp030pd", .driver_data = ABP030PD }, + { .name = "abp060pd", .driver_data = ABP060PD }, + { } +}; +MODULE_DEVICE_TABLE(i2c, abp060mg_id_table); + +static struct i2c_driver abp060mg_driver = { + .driver = { + .name = "abp060mg", + }, + .probe = abp060mg_probe, + .id_table = abp060mg_id_table, +}; +module_i2c_driver(abp060mg_driver); + +MODULE_AUTHOR("Marcin Malagowski "); +MODULE_DESCRIPTION("Honeywell ABP pressure sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/abp2030pa.c b/drivers/iio/pressure/abp2030pa.c new file mode 100644 index 000000000..41395036e --- /dev/null +++ b/drivers/iio/pressure/abp2030pa.c @@ -0,0 +1,543 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Honeywell ABP2 series pressure sensor driver + * + * Copyright (c) 2025 Petre Rodan + * + * Datasheet: https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/basic-abp2-series/documents/sps-siot-abp2-series-datasheet-32350268-en.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "abp2030pa.h" + +/* Status byte flags */ +#define ABP2_ST_POWER BIT(6) /* 1 if device is powered */ +#define ABP2_ST_BUSY BIT(5) /* 1 if device is busy */ + +#define ABP2_CMD_NOP 0xf0 +#define ABP2_CMD_SYNC 0xaa +#define ABP2_PKT_SYNC_LEN 3 +#define ABP2_PKT_NOP_LEN ABP2_MEASUREMENT_RD_SIZE + +struct abp2_func_spec { + u32 output_min; + u32 output_max; +}; + +/* transfer function A: 10% to 90% of 2^24 */ +static const struct abp2_func_spec abp2_func_spec[] = { + [ABP2_FUNCTION_A] = { .output_min = 1677722, .output_max = 15099494 }, +}; + +enum abp2_variants { + ABP2001BA, ABP21_6BA, ABP22_5BA, ABP2004BA, ABP2006BA, ABP2008BA, + ABP2010BA, ABP2012BA, ABP2001BD, ABP21_6BD, ABP22_5BD, ABP2004BD, + ABP2001BG, ABP21_6BG, ABP22_5BG, ABP2004BG, ABP2006BG, ABP2008BG, + ABP2010BG, ABP2012BG, ABP2001GG, ABP21_2GG, ABP2100KA, ABP2160KA, + ABP2250KA, ABP2001KD, ABP21_6KD, ABP22_5KD, ABP2004KD, ABP2006KD, + ABP2010KD, ABP2016KD, ABP2025KD, ABP2040KD, ABP2060KD, ABP2100KD, + ABP2160KD, ABP2250KD, ABP2400KD, ABP2001KG, ABP21_6KG, ABP22_5KG, + ABP2004KG, ABP2006KG, ABP2010KG, ABP2016KG, ABP2025KG, ABP2040KG, + ABP2060KG, ABP2100KG, ABP2160KG, ABP2250KG, ABP2400KG, ABP2600KG, + ABP2800KG, ABP2250LD, ABP2600LD, ABP2600LG, ABP22_5MD, ABP2006MD, + ABP2010MD, ABP2016MD, ABP2025MD, ABP2040MD, ABP2060MD, ABP2100MD, + ABP2160MD, ABP2250MD, ABP2400MD, ABP2600MD, ABP2006MG, ABP2010MG, + ABP2016MG, ABP2025MG, ABP2040MG, ABP2060MG, ABP2100MG, ABP2160MG, + ABP2250MG, ABP2400MG, ABP2600MG, ABP2001ND, ABP2002ND, ABP2004ND, + ABP2005ND, ABP2010ND, ABP2020ND, ABP2030ND, ABP2002NG, ABP2004NG, + ABP2005NG, ABP2010NG, ABP2020NG, ABP2030NG, ABP2015PA, ABP2030PA, + ABP2060PA, ABP2100PA, ABP2150PA, ABP2175PA, ABP2001PD, ABP2005PD, + ABP2015PD, ABP2030PD, ABP2060PD, ABP2001PG, ABP2005PG, ABP2015PG, + ABP2030PG, ABP2060PG, ABP2100PG, ABP2150PG, ABP2175PG, +}; + +static const char * const abp2_triplet_variants[] = { + [ABP2001BA] = "001BA", [ABP21_6BA] = "1.6BA", [ABP22_5BA] = "2.5BA", + [ABP2004BA] = "004BA", [ABP2006BA] = "006BA", [ABP2008BA] = "008BA", + [ABP2010BA] = "010BA", [ABP2012BA] = "012BA", [ABP2001BD] = "001BD", + [ABP21_6BD] = "1.6BD", [ABP22_5BD] = "2.5BD", [ABP2004BD] = "004BD", + [ABP2001BG] = "001BG", [ABP21_6BG] = "1.6BG", [ABP22_5BG] = "2.5BG", + [ABP2004BG] = "004BG", [ABP2006BG] = "006BG", [ABP2008BG] = "008BG", + [ABP2010BG] = "010BG", [ABP2012BG] = "012BG", [ABP2001GG] = "001GG", + [ABP21_2GG] = "1.2GG", [ABP2100KA] = "100KA", [ABP2160KA] = "160KA", + [ABP2250KA] = "250KA", [ABP2001KD] = "001KD", [ABP21_6KD] = "1.6KD", + [ABP22_5KD] = "2.5KD", [ABP2004KD] = "004KD", [ABP2006KD] = "006KD", + [ABP2010KD] = "010KD", [ABP2016KD] = "016KD", [ABP2025KD] = "025KD", + [ABP2040KD] = "040KD", [ABP2060KD] = "060KD", [ABP2100KD] = "100KD", + [ABP2160KD] = "160KD", [ABP2250KD] = "250KD", [ABP2400KD] = "400KD", + [ABP2001KG] = "001KG", [ABP21_6KG] = "1.6KG", [ABP22_5KG] = "2.5KG", + [ABP2004KG] = "004KG", [ABP2006KG] = "006KG", [ABP2010KG] = "010KG", + [ABP2016KG] = "016KG", [ABP2025KG] = "025KG", [ABP2040KG] = "040KG", + [ABP2060KG] = "060KG", [ABP2100KG] = "100KG", [ABP2160KG] = "160KG", + [ABP2250KG] = "250KG", [ABP2400KG] = "400KG", [ABP2600KG] = "600KG", + [ABP2800KG] = "800KG", [ABP2250LD] = "250LD", [ABP2600LD] = "600LD", + [ABP2600LG] = "600LG", [ABP22_5MD] = "2.5MD", [ABP2006MD] = "006MD", + [ABP2010MD] = "010MD", [ABP2016MD] = "016MD", [ABP2025MD] = "025MD", + [ABP2040MD] = "040MD", [ABP2060MD] = "060MD", [ABP2100MD] = "100MD", + [ABP2160MD] = "160MD", [ABP2250MD] = "250MD", [ABP2400MD] = "400MD", + [ABP2600MD] = "600MD", [ABP2006MG] = "006MG", [ABP2010MG] = "010MG", + [ABP2016MG] = "016MG", [ABP2025MG] = "025MG", [ABP2040MG] = "040MG", + [ABP2060MG] = "060MG", [ABP2100MG] = "100MG", [ABP2160MG] = "160MG", + [ABP2250MG] = "250MG", [ABP2400MG] = "400MG", [ABP2600MG] = "600MG", + [ABP2001ND] = "001ND", [ABP2002ND] = "002ND", [ABP2004ND] = "004ND", + [ABP2005ND] = "005ND", [ABP2010ND] = "010ND", [ABP2020ND] = "020ND", + [ABP2030ND] = "030ND", [ABP2002NG] = "002NG", [ABP2004NG] = "004NG", + [ABP2005NG] = "005NG", [ABP2010NG] = "010NG", [ABP2020NG] = "020NG", + [ABP2030NG] = "030NG", [ABP2015PA] = "015PA", [ABP2030PA] = "030PA", + [ABP2060PA] = "060PA", [ABP2100PA] = "100PA", [ABP2150PA] = "150PA", + [ABP2175PA] = "175PA", [ABP2001PD] = "001PD", [ABP2005PD] = "005PD", + [ABP2015PD] = "015PD", [ABP2030PD] = "030PD", [ABP2060PD] = "060PD", + [ABP2001PG] = "001PG", [ABP2005PG] = "005PG", [ABP2015PG] = "015PG", + [ABP2030PG] = "030PG", [ABP2060PG] = "060PG", [ABP2100PG] = "100PG", + [ABP2150PG] = "150PG", [ABP2175PG] = "175PG", +}; + +/** + * struct abp2_range_config - list of pressure ranges based on nomenclature + * @pmin: lowest pressure that can be measured + * @pmax: highest pressure that can be measured + */ +struct abp2_range_config { + s32 pmin; + s32 pmax; +}; + +/* All min max limits have been converted to pascals */ +static const struct abp2_range_config abp2_range_config[] = { + [ABP2001BA] = { .pmin = 0, .pmax = 100000 }, + [ABP21_6BA] = { .pmin = 0, .pmax = 160000 }, + [ABP22_5BA] = { .pmin = 0, .pmax = 250000 }, + [ABP2004BA] = { .pmin = 0, .pmax = 400000 }, + [ABP2006BA] = { .pmin = 0, .pmax = 600000 }, + [ABP2008BA] = { .pmin = 0, .pmax = 800000 }, + [ABP2010BA] = { .pmin = 0, .pmax = 1000000 }, + [ABP2012BA] = { .pmin = 0, .pmax = 1200000 }, + [ABP2001BD] = { .pmin = -100000, .pmax = 100000 }, + [ABP21_6BD] = { .pmin = -160000, .pmax = 160000 }, + [ABP22_5BD] = { .pmin = -250000, .pmax = 250000 }, + [ABP2004BD] = { .pmin = -400000, .pmax = 400000 }, + [ABP2001BG] = { .pmin = 0, .pmax = 100000 }, + [ABP21_6BG] = { .pmin = 0, .pmax = 160000 }, + [ABP22_5BG] = { .pmin = 0, .pmax = 250000 }, + [ABP2004BG] = { .pmin = 0, .pmax = 400000 }, + [ABP2006BG] = { .pmin = 0, .pmax = 600000 }, + [ABP2008BG] = { .pmin = 0, .pmax = 800000 }, + [ABP2010BG] = { .pmin = 0, .pmax = 1000000 }, + [ABP2012BG] = { .pmin = 0, .pmax = 1200000 }, + [ABP2001GG] = { .pmin = 0, .pmax = 1000000 }, + [ABP21_2GG] = { .pmin = 0, .pmax = 1200000 }, + [ABP2100KA] = { .pmin = 0, .pmax = 100000 }, + [ABP2160KA] = { .pmin = 0, .pmax = 160000 }, + [ABP2250KA] = { .pmin = 0, .pmax = 250000 }, + [ABP2001KD] = { .pmin = -1000, .pmax = 1000 }, + [ABP21_6KD] = { .pmin = -1600, .pmax = 1600 }, + [ABP22_5KD] = { .pmin = -2500, .pmax = 2500 }, + [ABP2004KD] = { .pmin = -4000, .pmax = 4000 }, + [ABP2006KD] = { .pmin = -6000, .pmax = 6000 }, + [ABP2010KD] = { .pmin = -10000, .pmax = 10000 }, + [ABP2016KD] = { .pmin = -16000, .pmax = 16000 }, + [ABP2025KD] = { .pmin = -25000, .pmax = 25000 }, + [ABP2040KD] = { .pmin = -40000, .pmax = 40000 }, + [ABP2060KD] = { .pmin = -60000, .pmax = 60000 }, + [ABP2100KD] = { .pmin = -100000, .pmax = 100000 }, + [ABP2160KD] = { .pmin = -160000, .pmax = 160000 }, + [ABP2250KD] = { .pmin = -250000, .pmax = 250000 }, + [ABP2400KD] = { .pmin = -400000, .pmax = 400000 }, + [ABP2001KG] = { .pmin = 0, .pmax = 1000 }, + [ABP21_6KG] = { .pmin = 0, .pmax = 1600 }, + [ABP22_5KG] = { .pmin = 0, .pmax = 2500 }, + [ABP2004KG] = { .pmin = 0, .pmax = 4000 }, + [ABP2006KG] = { .pmin = 0, .pmax = 6000 }, + [ABP2010KG] = { .pmin = 0, .pmax = 10000 }, + [ABP2016KG] = { .pmin = 0, .pmax = 16000 }, + [ABP2025KG] = { .pmin = 0, .pmax = 25000 }, + [ABP2040KG] = { .pmin = 0, .pmax = 40000 }, + [ABP2060KG] = { .pmin = 0, .pmax = 60000 }, + [ABP2100KG] = { .pmin = 0, .pmax = 100000 }, + [ABP2160KG] = { .pmin = 0, .pmax = 160000 }, + [ABP2250KG] = { .pmin = 0, .pmax = 250000 }, + [ABP2400KG] = { .pmin = 0, .pmax = 400000 }, + [ABP2600KG] = { .pmin = 0, .pmax = 600000 }, + [ABP2800KG] = { .pmin = 0, .pmax = 800000 }, + [ABP2250LD] = { .pmin = -250, .pmax = 250 }, + [ABP2600LD] = { .pmin = -600, .pmax = 600 }, + [ABP2600LG] = { .pmin = 0, .pmax = 600 }, + [ABP22_5MD] = { .pmin = -250, .pmax = 250 }, + [ABP2006MD] = { .pmin = -600, .pmax = 600 }, + [ABP2010MD] = { .pmin = -1000, .pmax = 1000 }, + [ABP2016MD] = { .pmin = -1600, .pmax = 1600 }, + [ABP2025MD] = { .pmin = -2500, .pmax = 2500 }, + [ABP2040MD] = { .pmin = -4000, .pmax = 4000 }, + [ABP2060MD] = { .pmin = -6000, .pmax = 6000 }, + [ABP2100MD] = { .pmin = -10000, .pmax = 10000 }, + [ABP2160MD] = { .pmin = -16000, .pmax = 16000 }, + [ABP2250MD] = { .pmin = -25000, .pmax = 25000 }, + [ABP2400MD] = { .pmin = -40000, .pmax = 40000 }, + [ABP2600MD] = { .pmin = -60000, .pmax = 60000 }, + [ABP2006MG] = { .pmin = 0, .pmax = 600 }, + [ABP2010MG] = { .pmin = 0, .pmax = 1000 }, + [ABP2016MG] = { .pmin = 0, .pmax = 1600 }, + [ABP2025MG] = { .pmin = 0, .pmax = 2500 }, + [ABP2040MG] = { .pmin = 0, .pmax = 4000 }, + [ABP2060MG] = { .pmin = 0, .pmax = 6000 }, + [ABP2100MG] = { .pmin = 0, .pmax = 10000 }, + [ABP2160MG] = { .pmin = 0, .pmax = 16000 }, + [ABP2250MG] = { .pmin = 0, .pmax = 25000 }, + [ABP2400MG] = { .pmin = 0, .pmax = 40000 }, + [ABP2600MG] = { .pmin = 0, .pmax = 60000 }, + [ABP2001ND] = { .pmin = -249, .pmax = 249 }, + [ABP2002ND] = { .pmin = -498, .pmax = 498 }, + [ABP2004ND] = { .pmin = -996, .pmax = 996 }, + [ABP2005ND] = { .pmin = -1245, .pmax = 1245 }, + [ABP2010ND] = { .pmin = -2491, .pmax = 2491 }, + [ABP2020ND] = { .pmin = -4982, .pmax = 4982 }, + [ABP2030ND] = { .pmin = -7473, .pmax = 7473 }, + [ABP2002NG] = { .pmin = 0, .pmax = 498 }, + [ABP2004NG] = { .pmin = 0, .pmax = 996 }, + [ABP2005NG] = { .pmin = 0, .pmax = 1245 }, + [ABP2010NG] = { .pmin = 0, .pmax = 2491 }, + [ABP2020NG] = { .pmin = 0, .pmax = 4982 }, + [ABP2030NG] = { .pmin = 0, .pmax = 7473 }, + [ABP2015PA] = { .pmin = 0, .pmax = 103421 }, + [ABP2030PA] = { .pmin = 0, .pmax = 206843 }, + [ABP2060PA] = { .pmin = 0, .pmax = 413685 }, + [ABP2100PA] = { .pmin = 0, .pmax = 689476 }, + [ABP2150PA] = { .pmin = 0, .pmax = 1034214 }, + [ABP2175PA] = { .pmin = 0, .pmax = 1206583 }, + [ABP2001PD] = { .pmin = -6895, .pmax = 6895 }, + [ABP2005PD] = { .pmin = -34474, .pmax = 34474 }, + [ABP2015PD] = { .pmin = -103421, .pmax = 103421 }, + [ABP2030PD] = { .pmin = -206843, .pmax = 206843 }, + [ABP2060PD] = { .pmin = -413685, .pmax = 413685 }, + [ABP2001PG] = { .pmin = 0, .pmax = 6895 }, + [ABP2005PG] = { .pmin = 0, .pmax = 34474 }, + [ABP2015PG] = { .pmin = 0, .pmax = 103421 }, + [ABP2030PG] = { .pmin = 0, .pmax = 206843 }, + [ABP2060PG] = { .pmin = 0, .pmax = 413685 }, + [ABP2100PG] = { .pmin = 0, .pmax = 689476 }, + [ABP2150PG] = { .pmin = 0, .pmax = 1034214 }, + [ABP2175PG] = { .pmin = 0, .pmax = 1206583 }, +}; + +static_assert(ARRAY_SIZE(abp2_triplet_variants) == ARRAY_SIZE(abp2_range_config)); + +static int abp2_get_measurement(struct abp2_data *data) +{ + struct device *dev = data->dev; + int ret; + + reinit_completion(&data->completion); + + ret = data->ops->write(data, ABP2_CMD_SYNC, ABP2_PKT_SYNC_LEN); + if (ret < 0) + return ret; + + if (data->irq > 0) { + ret = wait_for_completion_timeout(&data->completion, HZ); + if (!ret) { + dev_err(dev, "timeout waiting for EOC interrupt\n"); + return -ETIMEDOUT; + } + } else { + fsleep(5 * USEC_PER_MSEC); + } + + memset(data->rx_buf, 0, sizeof(data->rx_buf)); + ret = data->ops->read(data, ABP2_CMD_NOP, ABP2_PKT_NOP_LEN); + if (ret < 0) + return ret; + + /* + * Status byte flags + * bit7 SANITY_CHK - must always be 0 + * bit6 ABP2_ST_POWER - 1 if device is powered + * bit5 ABP2_ST_BUSY - 1 if device has no new conversion ready + * bit4 SANITY_CHK - must always be 0 + * bit3 SANITY_CHK - must always be 0 + * bit2 MEMORY_ERR - 1 if integrity test has failed + * bit1 SANITY_CHK - must always be 0 + * bit0 MATH_ERR - 1 during internal math saturation error + */ + + if (data->rx_buf[0] == (ABP2_ST_POWER | ABP2_ST_BUSY)) + return -EBUSY; + + /* + * The ABP2 sensor series seem to have a noticeable latch-up sensitivity. + * A partial latch-up condition manifests as either + * - output of invalid status bytes + * - zeroed out conversions (despite a normal status byte) + * - the MOSI line being pulled low randomly in sync with the SCLK + * signal (visible during the ABP2_CMD_NOP command). + * https://e2e.ti.com/support/processors-group/processors/f/processors-forum/1588325/am3358-spi-tx-data-corruption + */ + + if (data->rx_buf[0] != ABP2_ST_POWER) { + dev_err(data->dev, + "unexpected status byte 0x%02x\n", data->rx_buf[0]); + return -EIO; + } + + return 0; +} + +static irqreturn_t abp2_eoc_handler(int irq, void *private) +{ + struct abp2_data *data = private; + + complete(&data->completion); + + return IRQ_HANDLED; +} + +static irqreturn_t abp2_trigger_handler(int irq, void *private) +{ + int ret; + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct abp2_data *data = iio_priv(indio_dev); + + ret = abp2_get_measurement(data); + if (ret < 0) + goto out_notify_done; + + data->scan.chan[0] = get_unaligned_be24(&data->rx_buf[1]); + data->scan.chan[1] = get_unaligned_be24(&data->rx_buf[4]); + + iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + iio_get_time_ns(indio_dev)); + +out_notify_done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +/* + * IIO ABI expects + * value = (conv + offset) * scale + * + * temp[C] = conv * a + b + * where a = 200/16777215; b = -50 + * + * temp[C] = (conv + (b/a)) * a * (1000) + * => + * scale = a * 1000 = .0000119209296 * 1000 = .01192092966562 + * offset = b/a = -50 * 16777215 / 200 = -4194303.75 + * + * pressure = (conv - Omin) * Q + Pmin = + * ((conv - Omin) + Pmin/Q) * Q + * => + * scale = Q = (Pmax - Pmin) / (Omax - Omin) + * offset = Pmin/Q - Omin = Pmin * (Omax - Omin) / (Pmax - Pmin) - Omin + */ +static int abp2_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct abp2_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = abp2_get_measurement(data); + if (ret < 0) + return ret; + + switch (channel->type) { + case IIO_PRESSURE: + *val = get_unaligned_be24(&data->rx_buf[1]); + return IIO_VAL_INT; + case IIO_TEMP: + *val = get_unaligned_be24(&data->rx_buf[4]); + return IIO_VAL_INT; + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_SCALE: + switch (channel->type) { + case IIO_TEMP: + *val = 0; + *val2 = 11920929; + return IIO_VAL_INT_PLUS_NANO; + case IIO_PRESSURE: + *val = data->p_scale; + *val2 = data->p_scale_dec; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_OFFSET: + switch (channel->type) { + case IIO_TEMP: + *val = -4194304; + return IIO_VAL_INT; + case IIO_PRESSURE: + *val = data->p_offset; + return IIO_VAL_INT; + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +static const struct iio_info abp2_info = { + .read_raw = &abp2_read_raw, +}; + +static const unsigned long abp2_scan_masks[] = {0x3, 0}; + +static const struct iio_chan_spec abp2_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = 1, + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +int abp2_common_probe(struct device *dev, const struct abp2_ops *ops, int irq) +{ + int ret; + struct abp2_data *data; + struct iio_dev *indio_dev; + const char *triplet; + s32 tmp; + s64 odelta, pdelta; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->dev = dev; + data->ops = ops; + data->irq = irq; + + init_completion(&data->completion); + + indio_dev->name = "abp2030pa"; + indio_dev->info = &abp2_info; + indio_dev->channels = abp2_channels; + indio_dev->num_channels = ARRAY_SIZE(abp2_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->available_scan_masks = abp2_scan_masks; + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "can't get and enable vdd supply\n"); + + ret = device_property_read_string(dev, "honeywell,pressure-triplet", + &triplet); + if (ret) { + ret = device_property_read_u32(dev, "honeywell,pmin-pascal", + &data->pmin); + if (ret) + return dev_err_probe(dev, ret, + "honeywell,pmin-pascal could not be read\n"); + + ret = device_property_read_u32(dev, "honeywell,pmax-pascal", + &data->pmax); + if (ret) + return dev_err_probe(dev, ret, + "honeywell,pmax-pascal could not be read\n"); + } else { + ret = device_property_match_property_string(dev, + "honeywell,pressure-triplet", + abp2_triplet_variants, + ARRAY_SIZE(abp2_triplet_variants)); + if (ret < 0) + return dev_err_probe(dev, -EINVAL, "honeywell,pressure-triplet is invalid\n"); + + data->pmin = abp2_range_config[ret].pmin; + data->pmax = abp2_range_config[ret].pmax; + } + + if (data->pmin >= data->pmax) + return dev_err_probe(dev, -EINVAL, "pressure limits are invalid\n"); + + data->outmin = abp2_func_spec[data->function].output_min; + data->outmax = abp2_func_spec[data->function].output_max; + + odelta = data->outmax - data->outmin; + pdelta = data->pmax - data->pmin; + + data->p_scale = div_s64_rem(div_s64(pdelta * NANO, odelta), NANO, &tmp); + data->p_scale_dec = tmp; + + data->p_offset = div_s64(odelta * data->pmin, pdelta) - data->outmin; + + if (data->irq > 0) { + ret = devm_request_irq(dev, irq, abp2_eoc_handler, 0, + dev_name(dev), data); + if (ret) + return ret; + } + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + abp2_trigger_handler, NULL); + if (ret) + return dev_err_probe(dev, ret, "iio triggered buffer setup failed\n"); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "unable to register iio device\n"); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(abp2_common_probe, "IIO_HONEYWELL_ABP2030PA"); + +MODULE_AUTHOR("Petre Rodan "); +MODULE_DESCRIPTION("Honeywell ABP2 pressure sensor core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/abp2030pa.h b/drivers/iio/pressure/abp2030pa.h new file mode 100644 index 000000000..57e5ed784 --- /dev/null +++ b/drivers/iio/pressure/abp2030pa.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Honeywell ABP2 series pressure sensor driver + * + * Copyright (c) 2025 Petre Rodan + */ + +#ifndef _ABP2030PA_H +#define _ABP2030PA_H + +#include +#include + +#include + +#define ABP2_MEASUREMENT_RD_SIZE 7 + +struct device; + +struct abp2_data; +struct abp2_ops; + +enum abp2_func_id { + ABP2_FUNCTION_A, +}; + +/** + * struct abp2_data + * @dev: current device structure + * @ops: pointers for bus specific read and write functions + * @pmin: minimal pressure in pascal + * @pmax: maximal pressure in pascal + * @outmin: minimum raw pressure in counts (based on transfer function) + * @outmax: maximum raw pressure in counts (based on transfer function) + * @function: transfer function + * @p_scale: pressure scale + * @p_scale_dec: pressure scale, decimal number + * @p_offset: pressure offset + * @irq: end of conversion - applies only to the i2c sensor + * @completion: handshake from irq to read + * @scan: channel values for buffered mode + * @tx_buf: transmit buffer used during the SPI communication + * @rx_buf: raw data provided by sensor + */ +struct abp2_data { + struct device *dev; + const struct abp2_ops *ops; + s32 pmin; + s32 pmax; + u32 outmin; + u32 outmax; + enum abp2_func_id function; + int p_scale; + int p_scale_dec; + int p_offset; + int irq; + struct completion completion; + struct { + u32 chan[2]; + aligned_s64 timestamp; + } scan; + u8 rx_buf[ABP2_MEASUREMENT_RD_SIZE] __aligned(IIO_DMA_MINALIGN); + u8 tx_buf[ABP2_MEASUREMENT_RD_SIZE]; +}; + +struct abp2_ops { + int (*read)(struct abp2_data *data, u8 cmd, u8 nbytes); + int (*write)(struct abp2_data *data, u8 cmd, u8 nbytes); +}; + +int abp2_common_probe(struct device *dev, const struct abp2_ops *ops, int irq); + +#endif diff --git a/drivers/iio/pressure/abp2030pa_i2c.c b/drivers/iio/pressure/abp2030pa_i2c.c new file mode 100644 index 000000000..fa4d290cf --- /dev/null +++ b/drivers/iio/pressure/abp2030pa_i2c.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Honeywell ABP2 series pressure sensor driver + * + * Copyright (c) 2025 Petre Rodan + */ + +#include +#include +#include +#include +#include + +#include "abp2030pa.h" + +static int abp2_i2c_read(struct abp2_data *data, u8 unused, u8 nbytes) +{ + struct i2c_client *client = to_i2c_client(data->dev); + int ret; + + if (nbytes > ABP2_MEASUREMENT_RD_SIZE) + return -EOVERFLOW; + + ret = i2c_master_recv(client, data->rx_buf, nbytes); + if (ret < 0) + return ret; + if (ret != nbytes) + return -EIO; + + return 0; +} + +static int abp2_i2c_write(struct abp2_data *data, u8 cmd, u8 nbytes) +{ + struct i2c_client *client = to_i2c_client(data->dev); + int ret; + + if (nbytes > ABP2_MEASUREMENT_RD_SIZE) + return -EOVERFLOW; + + data->tx_buf[0] = cmd; + ret = i2c_master_send(client, data->tx_buf, nbytes); + if (ret < 0) + return ret; + if (ret != nbytes) + return -EIO; + + return 0; +} + +static const struct abp2_ops abp2_i2c_ops = { + .read = abp2_i2c_read, + .write = abp2_i2c_write, +}; + +static int abp2_i2c_probe(struct i2c_client *client) +{ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -EOPNOTSUPP; + + return abp2_common_probe(&client->dev, &abp2_i2c_ops, client->irq); +} + +static const struct of_device_id abp2_i2c_match[] = { + { .compatible = "honeywell,abp2030pa" }, + { } +}; +MODULE_DEVICE_TABLE(of, abp2_i2c_match); + +static const struct i2c_device_id abp2_i2c_id[] = { + { .name = "abp2030pa" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, abp2_i2c_id); + +static struct i2c_driver abp2_i2c_driver = { + .driver = { + .name = "abp2030pa", + .of_match_table = abp2_i2c_match, + }, + .probe = abp2_i2c_probe, + .id_table = abp2_i2c_id, +}; +module_i2c_driver(abp2_i2c_driver); + +MODULE_AUTHOR("Petre Rodan "); +MODULE_DESCRIPTION("Honeywell ABP2 pressure sensor i2c driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HONEYWELL_ABP2030PA"); diff --git a/drivers/iio/pressure/abp2030pa_spi.c b/drivers/iio/pressure/abp2030pa_spi.c new file mode 100644 index 000000000..60d078574 --- /dev/null +++ b/drivers/iio/pressure/abp2030pa_spi.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Honeywell ABP2 series pressure sensor driver + * + * Copyright (c) 2025 Petre Rodan + */ + +#include +#include +#include +#include + +#include "abp2030pa.h" + +static int abp2_spi_xfer(struct abp2_data *data, u8 cmd, u8 nbytes) +{ + struct spi_device *spi = to_spi_device(data->dev); + struct spi_transfer xfer = { }; + + if (nbytes > ABP2_MEASUREMENT_RD_SIZE) + return -EOVERFLOW; + + data->tx_buf[0] = cmd; + xfer.tx_buf = data->tx_buf; + xfer.rx_buf = data->rx_buf; + xfer.len = nbytes; + + return spi_sync_transfer(spi, &xfer, 1); +} + +static const struct abp2_ops abp2_spi_ops = { + .read = abp2_spi_xfer, + .write = abp2_spi_xfer, +}; + +static int abp2_spi_probe(struct spi_device *spi) +{ + return abp2_common_probe(&spi->dev, &abp2_spi_ops, spi->irq); +} + +static const struct of_device_id abp2_spi_match[] = { + { .compatible = "honeywell,abp2030pa" }, + { } +}; +MODULE_DEVICE_TABLE(of, abp2_spi_match); + +static const struct spi_device_id abp2_spi_id[] = { + { .name = "abp2030pa" }, + { } +}; +MODULE_DEVICE_TABLE(spi, abp2_spi_id); + +static struct spi_driver abp2_spi_driver = { + .driver = { + .name = "abp2030pa", + .of_match_table = abp2_spi_match, + }, + .probe = abp2_spi_probe, + .id_table = abp2_spi_id, +}; +module_spi_driver(abp2_spi_driver); + +MODULE_AUTHOR("Petre Rodan "); +MODULE_DESCRIPTION("Honeywell ABP2 pressure sensor spi driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HONEYWELL_ABP2030PA"); diff --git a/drivers/iio/pressure/adp810.c b/drivers/iio/pressure/adp810.c new file mode 100644 index 000000000..82aa43354 --- /dev/null +++ b/drivers/iio/pressure/adp810.c @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2025 Akhilesh Patil + * + * Driver for adp810 pressure and temperature sensor + * Datasheet: + * https://aosong.com/userfiles/files/media/Datasheet%20ADP810-Digital.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* + * Refer section 5.4 checksum calculation from datasheet. + * This sensor uses CRC polynomial x^8 + x^5 + x^4 + 1 (0x31) + */ +#define ADP810_CRC8_POLYNOMIAL 0x31 + +DECLARE_CRC8_TABLE(crc_table); + +/* + * Buffer declaration which holds 9 bytes of measurement data read + * from the sensor. Use __packed to avoid any paddings, as data sent + * from the sensor is strictly contiguous 9 bytes. + */ +struct adp810_read_buf { + __be16 dp; + u8 dp_crc; + __be16 tmp; + u8 tmp_crc; + __be16 sf; + u8 sf_crc; +} __packed; + +struct adp810_data { + struct i2c_client *client; + /* Use lock to synchronize access to device during read sequence */ + struct mutex lock; +}; + +static int adp810_measure(struct adp810_data *data, struct adp810_read_buf *buf) +{ + struct i2c_client *client = data->client; + struct device *dev = &client->dev; + int ret; + u8 trig_cmd[2] = {0x37, 0x2d}; + + /* Send trigger command to the sensor for measurement */ + ret = i2c_master_send(client, trig_cmd, sizeof(trig_cmd)); + if (ret < 0) { + dev_err(dev, "Error sending trigger command\n"); + return ret; + } + if (ret != sizeof(trig_cmd)) + return -EIO; + + /* + * Wait for the sensor to acquire data. As per datasheet section 5.3.1, + * at least 10ms delay before reading from the sensor is recommended. + * Here, we wait for 20ms to have some safe margin on the top + * of recommendation and to compensate for any possible variations. + */ + msleep(20); + + /* Read sensor values */ + ret = i2c_master_recv(client, (char *)buf, sizeof(*buf)); + if (ret < 0) { + dev_err(dev, "Error reading from sensor\n"); + return ret; + } + if (ret != sizeof(*buf)) + return -EIO; + + /* CRC checks */ + crc8_populate_msb(crc_table, ADP810_CRC8_POLYNOMIAL); + if (buf->dp_crc != crc8(crc_table, (u8 *)&buf->dp, 0x2, CRC8_INIT_VALUE)) { + dev_err(dev, "CRC error for pressure\n"); + return -EIO; + } + + if (buf->tmp_crc != crc8(crc_table, (u8 *)&buf->tmp, 0x2, CRC8_INIT_VALUE)) { + dev_err(dev, "CRC error for temperature\n"); + return -EIO; + } + + if (buf->sf_crc != crc8(crc_table, (u8 *)&buf->sf, 0x2, CRC8_INIT_VALUE)) { + dev_err(dev, "CRC error for scale\n"); + return -EIO; + } + + return 0; +} + +static int adp810_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct adp810_data *data = iio_priv(indio_dev); + struct device *dev = &data->client->dev; + struct adp810_read_buf buf = { }; + int ret; + + scoped_guard(mutex, &data->lock) { + ret = adp810_measure(data, &buf); + if (ret) { + dev_err(dev, "Failed to read from device\n"); + return ret; + } + } + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_PRESSURE: + *val = get_unaligned_be16(&buf.dp); + return IIO_VAL_INT; + case IIO_TEMP: + *val = get_unaligned_be16(&buf.tmp); + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_PRESSURE: + *val = get_unaligned_be16(&buf.sf); + return IIO_VAL_INT; + case IIO_TEMP: + *val = 200; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static const struct iio_info adp810_info = { + .read_raw = adp810_read_raw, +}; + +static const struct iio_chan_spec adp810_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static int adp810_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct adp810_data *data; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; + + indio_dev->name = "adp810"; + indio_dev->channels = adp810_channels; + indio_dev->num_channels = ARRAY_SIZE(adp810_channels); + indio_dev->info = &adp810_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to register IIO device\n"); + + return 0; +} + +static const struct i2c_device_id adp810_id_table[] = { + { .name = "adp810" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adp810_id_table); + +static const struct of_device_id adp810_of_table[] = { + { .compatible = "aosong,adp810" }, + { } +}; +MODULE_DEVICE_TABLE(of, adp810_of_table); + +static struct i2c_driver adp810_driver = { + .driver = { + .name = "adp810", + .of_match_table = adp810_of_table, + }, + .probe = adp810_probe, + .id_table = adp810_id_table, +}; +module_i2c_driver(adp810_driver); + +MODULE_AUTHOR("Akhilesh Patil "); +MODULE_DESCRIPTION("Driver for Aosong ADP810 sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/bmp280-core.c b/drivers/iio/pressure/bmp280-core.c new file mode 100644 index 000000000..1066792b9 --- /dev/null +++ b/drivers/iio/pressure/bmp280-core.c @@ -0,0 +1,3361 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2010 Christoph Mair + * Copyright (c) 2012 Bosch Sensortec GmbH + * Copyright (c) 2012 Unixphere AB + * Copyright (c) 2014 Intel Corporation + * Copyright (c) 2016 Linus Walleij + * + * Driver for Bosch Sensortec BMP180 and BMP280 digital pressure sensor. + * + * Datasheet: + * https://cdn-shop.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf + * https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf + * https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bme280-ds002.pdf + * https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp388-ds001.pdf + * https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp390-ds002.pdf + * https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp581-ds004.pdf + * + * Sensor API: + * https://github.com/boschsensortec/BME280_SensorAPI + * https://github.com/boschsensortec/BMP3_SensorAPI + * https://github.com/boschsensortec/BMP5_SensorAPI + * + * Notice: + * The link to the bmp180 datasheet points to an outdated version missing these changes: + * - Changed document referral from ANP015 to BST-MPS-AN004-00 on page 26 + * - Updated equation for B3 param on section 3.5 to ((((long)AC1 * 4 + X3) << oss) + 2) / 4 + * - Updated RoHS directive to 2011/65/EU effective 8 June 2011 on page 26 + */ + +#define pr_fmt(fmt) "bmp280: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include /* For irq_get_irq_data() */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "bmp280.h" + +/* + * These enums are used for indexing into the array of calibration + * coefficients for BMP180. + */ +enum { AC1, AC2, AC3, AC4, AC5, AC6, B1, B2, MB, MC, MD }; + +enum bmp380_odr { + BMP380_ODR_200HZ, + BMP380_ODR_100HZ, + BMP380_ODR_50HZ, + BMP380_ODR_25HZ, + BMP380_ODR_12_5HZ, + BMP380_ODR_6_25HZ, + BMP380_ODR_3_125HZ, + BMP380_ODR_1_5625HZ, + BMP380_ODR_0_78HZ, + BMP380_ODR_0_39HZ, + BMP380_ODR_0_2HZ, + BMP380_ODR_0_1HZ, + BMP380_ODR_0_05HZ, + BMP380_ODR_0_02HZ, + BMP380_ODR_0_01HZ, + BMP380_ODR_0_006HZ, + BMP380_ODR_0_003HZ, + BMP380_ODR_0_0015HZ, +}; + +enum bmp580_odr { + BMP580_ODR_240HZ, + BMP580_ODR_218HZ, + BMP580_ODR_199HZ, + BMP580_ODR_179HZ, + BMP580_ODR_160HZ, + BMP580_ODR_149HZ, + BMP580_ODR_140HZ, + BMP580_ODR_129HZ, + BMP580_ODR_120HZ, + BMP580_ODR_110HZ, + BMP580_ODR_100HZ, + BMP580_ODR_89HZ, + BMP580_ODR_80HZ, + BMP580_ODR_70HZ, + BMP580_ODR_60HZ, + BMP580_ODR_50HZ, + BMP580_ODR_45HZ, + BMP580_ODR_40HZ, + BMP580_ODR_35HZ, + BMP580_ODR_30HZ, + BMP580_ODR_25HZ, + BMP580_ODR_20HZ, + BMP580_ODR_15HZ, + BMP580_ODR_10HZ, + BMP580_ODR_5HZ, + BMP580_ODR_4HZ, + BMP580_ODR_3HZ, + BMP580_ODR_2HZ, + BMP580_ODR_1HZ, + BMP580_ODR_0_5HZ, + BMP580_ODR_0_25HZ, + BMP580_ODR_0_125HZ, +}; + +/* + * These enums are used for indexing into the array of compensation + * parameters for BMP280. + */ +enum { T1, T2, T3, P1, P2, P3, P4, P5, P6, P7, P8, P9 }; + +enum { + /* Temperature calib indexes */ + BMP380_T1 = 0, + BMP380_T2 = 2, + BMP380_T3 = 4, + /* Pressure calib indexes */ + BMP380_P1 = 5, + BMP380_P2 = 7, + BMP380_P3 = 9, + BMP380_P4 = 10, + BMP380_P5 = 11, + BMP380_P6 = 13, + BMP380_P7 = 15, + BMP380_P8 = 16, + BMP380_P9 = 17, + BMP380_P10 = 19, + BMP380_P11 = 20, +}; + +enum bmp280_scan { + BMP280_PRESS, + BMP280_TEMP, + BME280_HUMID, +}; + +static const struct iio_chan_spec bmp280_channels[] = { + { + .type = IIO_PRESSURE, + /* PROCESSED maintained for ABI backwards compatibility */ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_TEMP, + /* PROCESSED maintained for ABI backwards compatibility */ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static const struct iio_chan_spec bme280_channels[] = { + { + .type = IIO_PRESSURE, + /* PROCESSED maintained for ABI backwards compatibility */ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_TEMP, + /* PROCESSED maintained for ABI backwards compatibility */ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_HUMIDITYRELATIVE, + /* PROCESSED maintained for ABI backwards compatibility */ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 2, + .scan_type = { + .sign = 'u', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_chan_spec bmp380_channels[] = { + { + .type = IIO_PRESSURE, + /* PROCESSED maintained for ABI backwards compatibility */ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_TEMP, + /* PROCESSED maintained for ABI backwards compatibility */ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static const struct iio_chan_spec bmp580_channels[] = { + { + .type = IIO_PRESSURE, + /* PROCESSED maintained for ABI backwards compatibility */ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_TEMP, + /* PROCESSED maintained for ABI backwards compatibility */ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_LE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static int bmp280_read_calib(struct bmp280_data *data) +{ + struct bmp280_calib *calib = &data->calib.bmp280; + int ret; + + /* Read temperature and pressure calibration values. */ + ret = regmap_bulk_read(data->regmap, BMP280_REG_COMP_TEMP_START, + data->bmp280_cal_buf, + sizeof(data->bmp280_cal_buf)); + if (ret) { + dev_err(data->dev, + "failed to read calibration parameters\n"); + return ret; + } + + /* Toss calibration data into the entropy pool */ + add_device_randomness(data->bmp280_cal_buf, + sizeof(data->bmp280_cal_buf)); + + /* Parse temperature calibration values. */ + calib->T1 = le16_to_cpu(data->bmp280_cal_buf[T1]); + calib->T2 = le16_to_cpu(data->bmp280_cal_buf[T2]); + calib->T3 = le16_to_cpu(data->bmp280_cal_buf[T3]); + + /* Parse pressure calibration values. */ + calib->P1 = le16_to_cpu(data->bmp280_cal_buf[P1]); + calib->P2 = le16_to_cpu(data->bmp280_cal_buf[P2]); + calib->P3 = le16_to_cpu(data->bmp280_cal_buf[P3]); + calib->P4 = le16_to_cpu(data->bmp280_cal_buf[P4]); + calib->P5 = le16_to_cpu(data->bmp280_cal_buf[P5]); + calib->P6 = le16_to_cpu(data->bmp280_cal_buf[P6]); + calib->P7 = le16_to_cpu(data->bmp280_cal_buf[P7]); + calib->P8 = le16_to_cpu(data->bmp280_cal_buf[P8]); + calib->P9 = le16_to_cpu(data->bmp280_cal_buf[P9]); + + return 0; +} + +/* + * These enums are used for indexing into the array of humidity parameters + * for BME280. Due to some weird indexing, unaligned BE/LE accesses co-exist in + * order to prepare the FIELD_{GET/PREP}() fields. Table 16 in Section 4.2.2 of + * the datasheet. + */ +enum { H2 = 0, H3 = 2, H4 = 3, H5 = 4, H6 = 6 }; + +static int bme280_read_calib(struct bmp280_data *data) +{ + struct bmp280_calib *calib = &data->calib.bmp280; + struct device *dev = data->dev; + s16 h4_upper, h4_lower, tmp_1, tmp_2, tmp_3; + unsigned int tmp; + int ret; + + /* Load shared calibration params with bmp280 first */ + ret = bmp280_read_calib(data); + if (ret) + return ret; + + ret = regmap_read(data->regmap, BME280_REG_COMP_H1, &tmp); + if (ret) { + dev_err(dev, "failed to read H1 comp value\n"); + return ret; + } + calib->H1 = tmp; + + ret = regmap_bulk_read(data->regmap, BME280_REG_COMP_H2, + data->bme280_humid_cal_buf, + sizeof(data->bme280_humid_cal_buf)); + if (ret) { + dev_err(dev, "failed to read humidity calibration values\n"); + return ret; + } + + calib->H2 = get_unaligned_le16(&data->bme280_humid_cal_buf[H2]); + calib->H3 = data->bme280_humid_cal_buf[H3]; + tmp_1 = get_unaligned_be16(&data->bme280_humid_cal_buf[H4]); + tmp_2 = FIELD_GET(BME280_COMP_H4_GET_MASK_UP, tmp_1); + h4_upper = FIELD_PREP(BME280_COMP_H4_PREP_MASK_UP, tmp_2); + h4_lower = FIELD_GET(BME280_COMP_H4_MASK_LOW, tmp_1); + calib->H4 = sign_extend32(h4_upper | h4_lower, 11); + tmp_3 = get_unaligned_le16(&data->bme280_humid_cal_buf[H5]); + calib->H5 = FIELD_GET_SIGNED(BME280_COMP_H5_MASK, tmp_3); + calib->H6 = data->bme280_humid_cal_buf[H6]; + + return 0; +} + +static int bme280_read_humid_adc(struct bmp280_data *data, u16 *adc_humidity) +{ + u16 value_humidity; + int ret; + + ret = regmap_bulk_read(data->regmap, BME280_REG_HUMIDITY_MSB, + &data->be16, BME280_NUM_HUMIDITY_BYTES); + if (ret) { + dev_err(data->dev, "failed to read humidity\n"); + return ret; + } + + value_humidity = be16_to_cpu(data->be16); + if (value_humidity == BMP280_HUMIDITY_SKIPPED) { + dev_err(data->dev, "reading humidity skipped\n"); + return -EIO; + } + *adc_humidity = value_humidity; + + return 0; +} + +/* + * Returns humidity in percent, resolution is 0.01 percent. Output value of + * "47445" represents 47445/1024 = 46.333 %RH. + * + * Taken from BME280 datasheet, Section 4.2.3, "Compensation formula". + */ +static u32 bme280_compensate_humidity(struct bmp280_data *data, + u16 adc_humidity, s32 t_fine) +{ + struct bmp280_calib *calib = &data->calib.bmp280; + s32 var; + + var = t_fine - (s32)76800; + var = (((((s32)adc_humidity << 14) - (calib->H4 << 20) - (calib->H5 * var)) + + (s32)16384) >> 15) * (((((((var * calib->H6) >> 10) + * (((var * (s32)calib->H3) >> 11) + (s32)32768)) >> 10) + + (s32)2097152) * calib->H2 + 8192) >> 14); + var -= ((((var >> 15) * (var >> 15)) >> 7) * (s32)calib->H1) >> 4; + + var = clamp_val(var, 0, 419430400); + + return var >> 12; +} + +static int bmp280_read_temp_adc(struct bmp280_data *data, u32 *adc_temp) +{ + u32 value_temp; + int ret; + + ret = regmap_bulk_read(data->regmap, BMP280_REG_TEMP_MSB, + data->buf, BMP280_NUM_TEMP_BYTES); + if (ret) { + dev_err(data->dev, "failed to read temperature\n"); + return ret; + } + + value_temp = FIELD_GET(BMP280_MEAS_TRIM_MASK, get_unaligned_be24(data->buf)); + if (value_temp == BMP280_TEMP_SKIPPED) { + dev_err(data->dev, "reading temperature skipped\n"); + return -EIO; + } + *adc_temp = value_temp; + + return 0; +} + +/* + * Returns temperature in DegC, resolution is 0.01 DegC. Output value of + * "5123" equals 51.23 DegC. t_fine carries fine temperature as global + * value. + * + * Taken from datasheet, Section 3.11.3, "Compensation formula". + */ +static s32 bmp280_calc_t_fine(struct bmp280_data *data, u32 adc_temp) +{ + struct bmp280_calib *calib = &data->calib.bmp280; + s32 var1, var2; + + var1 = (((((s32)adc_temp) >> 3) - ((s32)calib->T1 << 1)) * + ((s32)calib->T2)) >> 11; + var2 = (((((((s32)adc_temp) >> 4) - ((s32)calib->T1)) * + ((((s32)adc_temp >> 4) - ((s32)calib->T1))) >> 12) * + ((s32)calib->T3))) >> 14; + return var1 + var2; /* t_fine = var1 + var2 */ +} + +static int bmp280_get_t_fine(struct bmp280_data *data, s32 *t_fine) +{ + u32 adc_temp; + int ret; + + ret = bmp280_read_temp_adc(data, &adc_temp); + if (ret) + return ret; + + *t_fine = bmp280_calc_t_fine(data, adc_temp); + + return 0; +} + +static s32 bmp280_compensate_temp(struct bmp280_data *data, u32 adc_temp) +{ + return (bmp280_calc_t_fine(data, adc_temp) * 5 + 128) / 256; +} + +static int bmp280_read_press_adc(struct bmp280_data *data, u32 *adc_press) +{ + u32 value_press; + int ret; + + ret = regmap_bulk_read(data->regmap, BMP280_REG_PRESS_MSB, + data->buf, BMP280_NUM_PRESS_BYTES); + if (ret) { + dev_err(data->dev, "failed to read pressure\n"); + return ret; + } + + value_press = FIELD_GET(BMP280_MEAS_TRIM_MASK, get_unaligned_be24(data->buf)); + if (value_press == BMP280_PRESS_SKIPPED) { + dev_err(data->dev, "reading pressure skipped\n"); + return -EIO; + } + *adc_press = value_press; + + return 0; +} + +/* + * Returns pressure in Pa as unsigned 32 bit integer in Q24.8 format (24 + * integer bits and 8 fractional bits). Output value of "24674867" + * represents 24674867/256 = 96386.2 Pa = 963.862 hPa + * + * Taken from datasheet, Section 3.11.3, "Compensation formula". + */ +static u32 bmp280_compensate_press(struct bmp280_data *data, + u32 adc_press, s32 t_fine) +{ + struct bmp280_calib *calib = &data->calib.bmp280; + s64 var1, var2, p; + + var1 = ((s64)t_fine) - 128000; + var2 = var1 * var1 * (s64)calib->P6; + var2 += (var1 * (s64)calib->P5) << 17; + var2 += ((s64)calib->P4) << 35; + var1 = ((var1 * var1 * (s64)calib->P3) >> 8) + + ((var1 * (s64)calib->P2) << 12); + var1 = ((((s64)1) << 47) + var1) * ((s64)calib->P1) >> 33; + + if (var1 == 0) + return 0; + + p = ((((s64)1048576 - (s32)adc_press) << 31) - var2) * 3125; + p = div64_s64(p, var1); + var1 = (((s64)calib->P9) * (p >> 13) * (p >> 13)) >> 25; + var2 = ((s64)(calib->P8) * p) >> 19; + p = ((p + var1 + var2) >> 8) + (((s64)calib->P7) << 4); + + return (u32)p; +} + +static int bmp280_read_temp(struct bmp280_data *data, s32 *comp_temp) +{ + u32 adc_temp; + int ret; + + ret = bmp280_read_temp_adc(data, &adc_temp); + if (ret) + return ret; + + *comp_temp = bmp280_compensate_temp(data, adc_temp); + + return 0; +} + +static int bmp280_read_press(struct bmp280_data *data, u32 *comp_press) +{ + u32 adc_press; + s32 t_fine; + int ret; + + ret = bmp280_get_t_fine(data, &t_fine); + if (ret) + return ret; + + ret = bmp280_read_press_adc(data, &adc_press); + if (ret) + return ret; + + *comp_press = bmp280_compensate_press(data, adc_press, t_fine); + + return 0; +} + +static int bme280_read_humid(struct bmp280_data *data, u32 *comp_humidity) +{ + u16 adc_humidity; + s32 t_fine; + int ret; + + ret = bmp280_get_t_fine(data, &t_fine); + if (ret) + return ret; + + ret = bme280_read_humid_adc(data, &adc_humidity); + if (ret) + return ret; + + *comp_humidity = bme280_compensate_humidity(data, adc_humidity, t_fine); + + return 0; +} + +static int bmp280_read_raw_impl(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bmp280_data *data = iio_priv(indio_dev); + int chan_value; + int ret; + + guard(mutex)(&data->lock); + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ret = data->chip_info->set_mode(data, BMP280_FORCED); + if (ret) + return ret; + + ret = data->chip_info->wait_conv(data); + if (ret) + return ret; + + switch (chan->type) { + case IIO_HUMIDITYRELATIVE: + ret = data->chip_info->read_humid(data, &chan_value); + if (ret) + return ret; + + *val = data->chip_info->humid_coeffs[0] * chan_value; + *val2 = data->chip_info->humid_coeffs[1]; + return data->chip_info->humid_coeffs_type; + case IIO_PRESSURE: + ret = data->chip_info->read_press(data, &chan_value); + if (ret) + return ret; + + *val = data->chip_info->press_coeffs[0] * chan_value; + *val2 = data->chip_info->press_coeffs[1]; + return data->chip_info->press_coeffs_type; + case IIO_TEMP: + ret = data->chip_info->read_temp(data, &chan_value); + if (ret) + return ret; + + *val = data->chip_info->temp_coeffs[0] * chan_value; + *val2 = data->chip_info->temp_coeffs[1]; + return data->chip_info->temp_coeffs_type; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_RAW: + ret = data->chip_info->set_mode(data, BMP280_FORCED); + if (ret) + return ret; + + ret = data->chip_info->wait_conv(data); + if (ret) + return ret; + + switch (chan->type) { + case IIO_HUMIDITYRELATIVE: + ret = data->chip_info->read_humid(data, &chan_value); + if (ret) + return ret; + + *val = chan_value; + return IIO_VAL_INT; + case IIO_PRESSURE: + ret = data->chip_info->read_press(data, &chan_value); + if (ret) + return ret; + + *val = chan_value; + return IIO_VAL_INT; + case IIO_TEMP: + ret = data->chip_info->read_temp(data, &chan_value); + if (ret) + return ret; + + *val = chan_value; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_HUMIDITYRELATIVE: + *val = data->chip_info->humid_coeffs[0]; + *val2 = data->chip_info->humid_coeffs[1]; + return data->chip_info->humid_coeffs_type; + case IIO_PRESSURE: + *val = data->chip_info->press_coeffs[0]; + *val2 = data->chip_info->press_coeffs[1]; + return data->chip_info->press_coeffs_type; + case IIO_TEMP: + *val = data->chip_info->temp_coeffs[0]; + *val2 = data->chip_info->temp_coeffs[1]; + return data->chip_info->temp_coeffs_type; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_HUMIDITYRELATIVE: + *val = 1 << data->oversampling_humid; + return IIO_VAL_INT; + case IIO_PRESSURE: + *val = 1 << data->oversampling_press; + return IIO_VAL_INT; + case IIO_TEMP: + *val = 1 << data->oversampling_temp; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + if (!data->chip_info->sampling_freq_avail) + return -EINVAL; + + *val = data->chip_info->sampling_freq_avail[data->sampling_freq][0]; + *val2 = data->chip_info->sampling_freq_avail[data->sampling_freq][1]; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + if (!data->chip_info->iir_filter_coeffs_avail) + return -EINVAL; + + *val = (1 << data->iir_filter_coeff) - 1; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int bmp280_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bmp280_data *data = iio_priv(indio_dev); + int ret; + + ret = pm_runtime_resume_and_get(data->dev); + if (ret < 0) + return ret; + + ret = bmp280_read_raw_impl(indio_dev, chan, val, val2, mask); + pm_runtime_put_autosuspend(data->dev); + + return ret; +} + +static int bme280_write_oversampling_ratio_humid(struct bmp280_data *data, + int val) +{ + const int *avail = data->chip_info->oversampling_humid_avail; + const int n = data->chip_info->num_oversampling_humid_avail; + int ret, prev; + int i; + + for (i = 0; i < n; i++) { + if (avail[i] == val) { + prev = data->oversampling_humid; + data->oversampling_humid = ilog2(val); + + ret = data->chip_info->chip_config(data); + if (ret) { + data->oversampling_humid = prev; + data->chip_info->chip_config(data); + return ret; + } + return 0; + } + } + return -EINVAL; +} + +static int bmp280_write_oversampling_ratio_temp(struct bmp280_data *data, + int val) +{ + const int *avail = data->chip_info->oversampling_temp_avail; + const int n = data->chip_info->num_oversampling_temp_avail; + int ret, prev; + int i; + + for (i = 0; i < n; i++) { + if (avail[i] == val) { + prev = data->oversampling_temp; + data->oversampling_temp = ilog2(val); + + ret = data->chip_info->chip_config(data); + if (ret) { + data->oversampling_temp = prev; + data->chip_info->chip_config(data); + return ret; + } + return 0; + } + } + return -EINVAL; +} + +static int bmp280_write_oversampling_ratio_press(struct bmp280_data *data, + int val) +{ + const int *avail = data->chip_info->oversampling_press_avail; + const int n = data->chip_info->num_oversampling_press_avail; + int ret, prev; + int i; + + for (i = 0; i < n; i++) { + if (avail[i] == val) { + prev = data->oversampling_press; + data->oversampling_press = ilog2(val); + + ret = data->chip_info->chip_config(data); + if (ret) { + data->oversampling_press = prev; + data->chip_info->chip_config(data); + return ret; + } + return 0; + } + } + return -EINVAL; +} + +static int bmp280_write_sampling_frequency(struct bmp280_data *data, + int val, int val2) +{ + const int (*avail)[2] = data->chip_info->sampling_freq_avail; + const int n = data->chip_info->num_sampling_freq_avail; + int ret, prev; + int i; + + for (i = 0; i < n; i++) { + if (avail[i][0] == val && avail[i][1] == val2) { + prev = data->sampling_freq; + data->sampling_freq = i; + + ret = data->chip_info->chip_config(data); + if (ret) { + data->sampling_freq = prev; + data->chip_info->chip_config(data); + return ret; + } + return 0; + } + } + return -EINVAL; +} + +static int bmp280_write_iir_filter_coeffs(struct bmp280_data *data, int val) +{ + const int *avail = data->chip_info->iir_filter_coeffs_avail; + const int n = data->chip_info->num_iir_filter_coeffs_avail; + int ret, prev; + int i; + + for (i = 0; i < n; i++) { + if (avail[i] - 1 == val) { + prev = data->iir_filter_coeff; + data->iir_filter_coeff = i; + + ret = data->chip_info->chip_config(data); + if (ret) { + data->iir_filter_coeff = prev; + data->chip_info->chip_config(data); + return ret; + + } + return 0; + } + } + return -EINVAL; +} + +static int bmp280_write_raw_impl(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bmp280_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + + /* + * Helper functions to update sensor running configuration. + * If an error happens applying new settings, will try restore + * previous parameters to ensure the sensor is left in a known + * working configuration. + */ + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_HUMIDITYRELATIVE: + return bme280_write_oversampling_ratio_humid(data, val); + case IIO_PRESSURE: + return bmp280_write_oversampling_ratio_press(data, val); + case IIO_TEMP: + return bmp280_write_oversampling_ratio_temp(data, val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + return bmp280_write_sampling_frequency(data, val, val2); + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return bmp280_write_iir_filter_coeffs(data, val); + default: + return -EINVAL; + } +} + +static int bmp280_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bmp280_data *data = iio_priv(indio_dev); + int ret; + + ret = pm_runtime_resume_and_get(data->dev); + if (ret < 0) + return ret; + + ret = bmp280_write_raw_impl(indio_dev, chan, val, val2, mask); + pm_runtime_put_autosuspend(data->dev); + + return ret; +} + +static int bmp280_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct bmp280_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_PRESSURE: + *vals = data->chip_info->oversampling_press_avail; + *length = data->chip_info->num_oversampling_press_avail; + break; + case IIO_TEMP: + *vals = data->chip_info->oversampling_temp_avail; + *length = data->chip_info->num_oversampling_temp_avail; + break; + default: + return -EINVAL; + } + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = (const int *)data->chip_info->sampling_freq_avail; + *type = IIO_VAL_INT_PLUS_MICRO; + /* Values are stored in a 2D matrix */ + *length = data->chip_info->num_sampling_freq_avail; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + *vals = data->chip_info->iir_filter_coeffs_avail; + *type = IIO_VAL_INT; + *length = data->chip_info->num_iir_filter_coeffs_avail; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const struct iio_info bmp280_info = { + .read_raw = &bmp280_read_raw, + .read_avail = &bmp280_read_avail, + .write_raw = &bmp280_write_raw, +}; + +static const unsigned long bmp280_avail_scan_masks[] = { + BIT(BMP280_TEMP) | BIT(BMP280_PRESS), + 0 +}; + +static const unsigned long bme280_avail_scan_masks[] = { + BIT(BME280_HUMID) | BIT(BMP280_TEMP) | BIT(BMP280_PRESS), + 0 +}; + +static int bmp280_preinit(struct bmp280_data *data) +{ + struct device *dev = data->dev; + unsigned int reg; + int ret; + + ret = regmap_write(data->regmap, BMP280_REG_RESET, BMP280_RST_SOFT_CMD); + if (ret) + return dev_err_probe(dev, ret, "Failed to reset device.\n"); + + /* + * According to the datasheet in Chapter 1: Specification, Table 2, + * after resetting, the device uses the complete power-on sequence so + * it needs to wait for the defined start-up time. + */ + fsleep(data->start_up_time_us); + + ret = regmap_read(data->regmap, BMP280_REG_STATUS, ®); + if (ret) + return dev_err_probe(dev, ret, "Failed to read status register.\n"); + + if (reg & BMP280_REG_STATUS_IM_UPDATE) + return dev_err_probe(dev, -EIO, "Failed to copy NVM contents.\n"); + + return 0; +} + +static const u8 bmp280_operation_mode[] = { + [BMP280_SLEEP] = BMP280_MODE_SLEEP, + [BMP280_FORCED] = BMP280_MODE_FORCED, + [BMP280_NORMAL] = BMP280_MODE_NORMAL, +}; + +static int bmp280_set_mode(struct bmp280_data *data, enum bmp280_op_mode mode) +{ + int ret; + + ret = regmap_write_bits(data->regmap, BMP280_REG_CTRL_MEAS, + BMP280_MODE_MASK, bmp280_operation_mode[mode]); + if (ret) { + dev_err(data->dev, "failed to write ctrl_meas register.\n"); + return ret; + } + + data->op_mode = mode; + + return 0; +} + +static int bmp280_wait_conv(struct bmp280_data *data) +{ + unsigned int reg, meas_time_us; + int ret; + + /* Constant part of the measurement time */ + meas_time_us = BMP280_MEAS_OFFSET; + + /* + * Check if we are using a BME280 device, + * Humidity measurement time + */ + if (data->chip_info->oversampling_humid_avail) + meas_time_us += BMP280_PRESS_HUMID_MEAS_OFFSET + + BIT(data->oversampling_humid) * BMP280_MEAS_DUR; + + /* Pressure measurement time */ + meas_time_us += BMP280_PRESS_HUMID_MEAS_OFFSET + + BIT(data->oversampling_press) * BMP280_MEAS_DUR; + + /* Temperature measurement time */ + meas_time_us += BIT(data->oversampling_temp) * BMP280_MEAS_DUR; + + /* Waiting time according to the BM(P/E)2 Sensor API */ + fsleep(meas_time_us); + + ret = regmap_read(data->regmap, BMP280_REG_STATUS, ®); + if (ret) { + dev_err(data->dev, "failed to read status register.\n"); + return ret; + } + + if (reg & BMP280_REG_STATUS_MEAS_BIT) { + dev_err(data->dev, "Measurement cycle didn't complete.\n"); + return -EBUSY; + } + + return 0; +} + +static int bmp280_chip_config(struct bmp280_data *data) +{ + u8 osrs = FIELD_PREP(BMP280_OSRS_TEMP_MASK, data->oversampling_temp + 1) | + FIELD_PREP(BMP280_OSRS_PRESS_MASK, data->oversampling_press + 1); + int ret; + + ret = regmap_write_bits(data->regmap, BMP280_REG_CTRL_MEAS, + BMP280_OSRS_TEMP_MASK | + BMP280_OSRS_PRESS_MASK | + BMP280_MODE_MASK, + osrs | BMP280_MODE_SLEEP); + if (ret) { + dev_err(data->dev, "failed to write ctrl_meas register\n"); + return ret; + } + + ret = regmap_update_bits(data->regmap, BMP280_REG_CONFIG, + BMP280_FILTER_MASK, + BMP280_FILTER_4X); + if (ret) { + dev_err(data->dev, "failed to write config register\n"); + return ret; + } + + return ret; +} + +static irqreturn_t bmp280_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bmp280_data *data = iio_priv(indio_dev); + u32 adc_temp, adc_press; + s32 t_fine; + struct { + u32 comp_press; + s32 comp_temp; + aligned_s64 timestamp; + } buffer; + int ret; + + guard(mutex)(&data->lock); + + /* Burst read data registers */ + ret = regmap_bulk_read(data->regmap, BMP280_REG_PRESS_MSB, + data->buf, BMP280_BURST_READ_BYTES); + if (ret) { + dev_err(data->dev, "failed to burst read sensor data\n"); + goto out; + } + + /* Temperature calculations */ + adc_temp = FIELD_GET(BMP280_MEAS_TRIM_MASK, get_unaligned_be24(&data->buf[3])); + if (adc_temp == BMP280_TEMP_SKIPPED) { + dev_err(data->dev, "reading temperature skipped\n"); + goto out; + } + + buffer.comp_temp = bmp280_compensate_temp(data, adc_temp); + + /* Pressure calculations */ + adc_press = FIELD_GET(BMP280_MEAS_TRIM_MASK, get_unaligned_be24(&data->buf[0])); + if (adc_press == BMP280_PRESS_SKIPPED) { + dev_err(data->dev, "reading pressure skipped\n"); + goto out; + } + + t_fine = bmp280_calc_t_fine(data, adc_temp); + buffer.comp_press = bmp280_compensate_press(data, adc_press, t_fine); + + iio_push_to_buffers_with_ts(indio_dev, &buffer, sizeof(buffer), + iio_get_time_ns(indio_dev)); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const int bmp280_oversampling_avail[] = { 1, 2, 4, 8, 16 }; +static const u8 bmp280_chip_ids[] = { BMP280_CHIP_ID }; +static const int bmp280_temp_coeffs[] = { 10, 1 }; +static const int bmp280_press_coeffs[] = { 1, 256000 }; + +const struct bmp280_chip_info bmp280_chip_info = { + .id_reg = BMP280_REG_ID, + .chip_id = bmp280_chip_ids, + .num_chip_id = ARRAY_SIZE(bmp280_chip_ids), + .regmap_config = &bmp280_regmap_config, + .start_up_time_us = 2000, + .channels = bmp280_channels, + .num_channels = ARRAY_SIZE(bmp280_channels), + .avail_scan_masks = bmp280_avail_scan_masks, + + .oversampling_temp_avail = bmp280_oversampling_avail, + .num_oversampling_temp_avail = ARRAY_SIZE(bmp280_oversampling_avail), + /* + * Oversampling config values on BMx280 have one additional setting + * that other generations of the family don't: + * The value 0 means the measurement is bypassed instead of + * oversampling set to x1. + * + * To account for this difference, and preserve the same common + * config logic, this is handled later on chip_config callback + * incrementing one unit the oversampling setting. + */ + .oversampling_temp_default = BMP280_OSRS_TEMP_2X - 1, + + .oversampling_press_avail = bmp280_oversampling_avail, + .num_oversampling_press_avail = ARRAY_SIZE(bmp280_oversampling_avail), + .oversampling_press_default = BMP280_OSRS_PRESS_16X - 1, + + .temp_coeffs = bmp280_temp_coeffs, + .temp_coeffs_type = IIO_VAL_FRACTIONAL, + .press_coeffs = bmp280_press_coeffs, + .press_coeffs_type = IIO_VAL_FRACTIONAL, + + .chip_config = bmp280_chip_config, + .read_temp = bmp280_read_temp, + .read_press = bmp280_read_press, + .read_calib = bmp280_read_calib, + .set_mode = bmp280_set_mode, + .wait_conv = bmp280_wait_conv, + .preinit = bmp280_preinit, + + .trigger_handler = bmp280_trigger_handler, +}; +EXPORT_SYMBOL_NS(bmp280_chip_info, "IIO_BMP280"); + +static int bme280_chip_config(struct bmp280_data *data) +{ + u8 osrs = FIELD_PREP(BME280_OSRS_HUMIDITY_MASK, data->oversampling_humid + 1); + int ret; + + /* + * Oversampling of humidity must be set before oversampling of + * temperature/pressure is set to become effective. + */ + ret = regmap_update_bits(data->regmap, BME280_REG_CTRL_HUMIDITY, + BME280_OSRS_HUMIDITY_MASK, osrs); + if (ret) { + dev_err(data->dev, "failed to set humidity oversampling"); + return ret; + } + + return bmp280_chip_config(data); +} + +static irqreturn_t bme280_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bmp280_data *data = iio_priv(indio_dev); + u32 adc_temp, adc_press, adc_humidity; + s32 t_fine; + struct { + u32 comp_press; + s32 comp_temp; + u32 comp_humidity; + aligned_s64 timestamp; + } buffer = { }; /* Don't leak uninitialized stack to userspace. */ + int ret; + + guard(mutex)(&data->lock); + + /* Burst read data registers */ + ret = regmap_bulk_read(data->regmap, BMP280_REG_PRESS_MSB, + data->buf, BME280_BURST_READ_BYTES); + if (ret) { + dev_err(data->dev, "failed to burst read sensor data\n"); + goto out; + } + + /* Temperature calculations */ + adc_temp = FIELD_GET(BMP280_MEAS_TRIM_MASK, get_unaligned_be24(&data->buf[3])); + if (adc_temp == BMP280_TEMP_SKIPPED) { + dev_err(data->dev, "reading temperature skipped\n"); + goto out; + } + + buffer.comp_temp = bmp280_compensate_temp(data, adc_temp); + + /* Pressure calculations */ + adc_press = FIELD_GET(BMP280_MEAS_TRIM_MASK, get_unaligned_be24(&data->buf[0])); + if (adc_press == BMP280_PRESS_SKIPPED) { + dev_err(data->dev, "reading pressure skipped\n"); + goto out; + } + + t_fine = bmp280_calc_t_fine(data, adc_temp); + buffer.comp_press = bmp280_compensate_press(data, adc_press, t_fine); + + /* Humidity calculations */ + adc_humidity = get_unaligned_be16(&data->buf[6]); + + if (adc_humidity == BMP280_HUMIDITY_SKIPPED) { + dev_err(data->dev, "reading humidity skipped\n"); + goto out; + } + + buffer.comp_humidity = bme280_compensate_humidity(data, adc_humidity, + t_fine); + + iio_push_to_buffers_with_ts(indio_dev, &buffer, sizeof(buffer), + iio_get_time_ns(indio_dev)); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int __bmp280_trigger_probe(struct iio_dev *indio_dev, + const struct iio_trigger_ops *trigger_ops, + int (*int_pin_config)(struct bmp280_data *data), + irq_handler_t irq_thread_handler) +{ + struct bmp280_data *data = iio_priv(indio_dev); + struct device *dev = data->dev; + u32 irq_type; + int ret, irq; + + irq = fwnode_irq_get(dev_fwnode(dev), 0); + if (irq < 0) + return dev_err_probe(dev, irq, "No interrupt found.\n"); + + irq_type = irq_get_trigger_type(irq); + switch (irq_type) { + case IRQF_TRIGGER_RISING: + data->trig_active_high = true; + break; + case IRQF_TRIGGER_FALLING: + data->trig_active_high = false; + break; + default: + return dev_err_probe(dev, -EINVAL, "Invalid interrupt type specified.\n"); + } + + data->trig_open_drain = + fwnode_property_read_bool(dev_fwnode(dev), "int-open-drain"); + + ret = int_pin_config(data); + if (ret) + return ret; + + data->trig = devm_iio_trigger_alloc(data->dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->trig) + return -ENOMEM; + + data->trig->ops = trigger_ops; + iio_trigger_set_drvdata(data->trig, data); + + ret = devm_request_threaded_irq(data->dev, irq, NULL, + irq_thread_handler, IRQF_ONESHOT, + indio_dev->name, indio_dev); + if (ret) + return ret; + + ret = devm_iio_trigger_register(data->dev, data->trig); + if (ret) + return dev_err_probe(dev, ret, "iio trigger register failed.\n"); + + indio_dev->trig = iio_trigger_get(data->trig); + + return 0; +} + +static const u8 bme280_chip_ids[] = { BME280_CHIP_ID }; +static const int bme280_humid_coeffs[] = { 1000, 1024 }; + +const struct bmp280_chip_info bme280_chip_info = { + .id_reg = BMP280_REG_ID, + .chip_id = bme280_chip_ids, + .num_chip_id = ARRAY_SIZE(bme280_chip_ids), + .regmap_config = &bme280_regmap_config, + .start_up_time_us = 2000, + .channels = bme280_channels, + .num_channels = ARRAY_SIZE(bme280_channels), + .avail_scan_masks = bme280_avail_scan_masks, + + .oversampling_temp_avail = bmp280_oversampling_avail, + .num_oversampling_temp_avail = ARRAY_SIZE(bmp280_oversampling_avail), + .oversampling_temp_default = BMP280_OSRS_TEMP_2X - 1, + + .oversampling_press_avail = bmp280_oversampling_avail, + .num_oversampling_press_avail = ARRAY_SIZE(bmp280_oversampling_avail), + .oversampling_press_default = BMP280_OSRS_PRESS_16X - 1, + + .oversampling_humid_avail = bmp280_oversampling_avail, + .num_oversampling_humid_avail = ARRAY_SIZE(bmp280_oversampling_avail), + .oversampling_humid_default = BME280_OSRS_HUMIDITY_16X - 1, + + .temp_coeffs = bmp280_temp_coeffs, + .temp_coeffs_type = IIO_VAL_FRACTIONAL, + .press_coeffs = bmp280_press_coeffs, + .press_coeffs_type = IIO_VAL_FRACTIONAL, + .humid_coeffs = bme280_humid_coeffs, + .humid_coeffs_type = IIO_VAL_FRACTIONAL, + + .chip_config = bme280_chip_config, + .read_temp = bmp280_read_temp, + .read_press = bmp280_read_press, + .read_humid = bme280_read_humid, + .read_calib = bme280_read_calib, + .set_mode = bmp280_set_mode, + .wait_conv = bmp280_wait_conv, + .preinit = bmp280_preinit, + + .trigger_handler = bme280_trigger_handler, +}; +EXPORT_SYMBOL_NS(bme280_chip_info, "IIO_BMP280"); + +/* + * Helper function to send a command to BMP3XX sensors. + * + * Sensor processes commands written to the CMD register and signals + * execution result through "cmd_rdy" and "cmd_error" flags available on + * STATUS and ERROR registers. + */ +static int bmp380_cmd(struct bmp280_data *data, u8 cmd) +{ + unsigned int reg; + int ret; + + /* Check if device is ready to process a command */ + ret = regmap_read(data->regmap, BMP380_REG_STATUS, ®); + if (ret) { + dev_err(data->dev, "failed to read error register\n"); + return ret; + } + if (!(reg & BMP380_STATUS_CMD_RDY_MASK)) { + dev_err(data->dev, "device is not ready to accept commands\n"); + return -EBUSY; + } + + /* Send command to process */ + ret = regmap_write(data->regmap, BMP380_REG_CMD, cmd); + if (ret) { + dev_err(data->dev, "failed to send command to device\n"); + return ret; + } + /* Wait for 2ms for command to be processed */ + fsleep(data->start_up_time_us); + /* Check for command processing error */ + ret = regmap_read(data->regmap, BMP380_REG_ERROR, ®); + if (ret) { + dev_err(data->dev, "error reading ERROR reg\n"); + return ret; + } + if (reg & BMP380_ERR_CMD_MASK) { + dev_err(data->dev, "error processing command 0x%X\n", cmd); + return -EINVAL; + } + + return 0; +} + +static int bmp380_read_temp_adc(struct bmp280_data *data, u32 *adc_temp) +{ + u32 value_temp; + int ret; + + ret = regmap_bulk_read(data->regmap, BMP380_REG_TEMP_XLSB, + data->buf, BMP280_NUM_TEMP_BYTES); + if (ret) { + dev_err(data->dev, "failed to read temperature\n"); + return ret; + } + + value_temp = get_unaligned_le24(data->buf); + if (value_temp == BMP380_TEMP_SKIPPED) { + dev_err(data->dev, "reading temperature skipped\n"); + return -EIO; + } + *adc_temp = value_temp; + + return 0; +} + +/* + * Returns temperature in Celsius degrees, resolution is 0.01º C. Output value + * of "5123" equals 51.2º C. t_fine carries fine temperature as global value. + * + * Taken from datasheet, Section Appendix 9, "Compensation formula" and repo + * https://github.com/BoschSensortec/BMP3-Sensor-API. + */ +static s32 bmp380_calc_t_fine(struct bmp280_data *data, u32 adc_temp) +{ + s64 var1, var2, var3, var4, var5, var6; + struct bmp380_calib *calib = &data->calib.bmp380; + + var1 = ((s64) adc_temp) - (((s64) calib->T1) << 8); + var2 = var1 * ((s64) calib->T2); + var3 = var1 * var1; + var4 = var3 * ((s64) calib->T3); + var5 = (var2 << 18) + var4; + var6 = var5 >> 32; + return (s32)var6; /* t_fine = var6 */ +} + +static int bmp380_get_t_fine(struct bmp280_data *data, s32 *t_fine) +{ + s32 adc_temp; + int ret; + + ret = bmp380_read_temp_adc(data, &adc_temp); + if (ret) + return ret; + + *t_fine = bmp380_calc_t_fine(data, adc_temp); + + return 0; +} + +static int bmp380_compensate_temp(struct bmp280_data *data, u32 adc_temp) +{ + s64 comp_temp; + s32 var6; + + var6 = bmp380_calc_t_fine(data, adc_temp); + comp_temp = (var6 * 25) >> 14; + + comp_temp = clamp_val(comp_temp, BMP380_MIN_TEMP, BMP380_MAX_TEMP); + return (s32) comp_temp; +} + +static int bmp380_read_press_adc(struct bmp280_data *data, u32 *adc_press) +{ + u32 value_press; + int ret; + + ret = regmap_bulk_read(data->regmap, BMP380_REG_PRESS_XLSB, + data->buf, BMP280_NUM_PRESS_BYTES); + if (ret) { + dev_err(data->dev, "failed to read pressure\n"); + return ret; + } + + value_press = get_unaligned_le24(data->buf); + if (value_press == BMP380_PRESS_SKIPPED) { + dev_err(data->dev, "reading pressure skipped\n"); + return -EIO; + } + *adc_press = value_press; + + return 0; +} + +/* + * Returns pressure in Pa as an unsigned 32 bit integer in fractional Pascal. + * Output value of "9528709" represents 9528709/100 = 95287.09 Pa = 952.8709 hPa. + * + * Taken from datasheet, Section 9.3. "Pressure compensation" and repository + * https://github.com/BoschSensortec/BMP3-Sensor-API. + */ +static u32 bmp380_compensate_press(struct bmp280_data *data, + u32 adc_press, s32 t_fine) +{ + s64 var1, var2, var3, var4, var5, var6, offset, sensitivity; + struct bmp380_calib *calib = &data->calib.bmp380; + u32 comp_press; + + var1 = (s64)t_fine * (s64)t_fine; + var2 = var1 >> 6; + var3 = (var2 * ((s64)t_fine)) >> 8; + var4 = ((s64)calib->P8 * var3) >> 5; + var5 = ((s64)calib->P7 * var1) << 4; + var6 = ((s64)calib->P6 * (s64)t_fine) << 22; + offset = ((s64)calib->P5 << 47) + var4 + var5 + var6; + var2 = ((s64)calib->P4 * var3) >> 5; + var4 = ((s64)calib->P3 * var1) << 2; + var5 = ((s64)calib->P2 - ((s64)1 << 14)) * + ((s64)t_fine << 21); + sensitivity = (((s64) calib->P1 - ((s64) 1 << 14)) << 46) + + var2 + var4 + var5; + var1 = (sensitivity >> 24) * (s64)adc_press; + var2 = (s64)calib->P10 * (s64)t_fine; + var3 = var2 + ((s64)calib->P9 << 16); + var4 = (var3 * (s64)adc_press) >> 13; + + /* + * Dividing by 10 followed by multiplying by 10 to avoid + * possible overflow caused by (uncomp_data->pressure * partial_data4). + */ + var5 = ((s64)adc_press * div_s64(var4, 10)) >> 9; + var5 *= 10; + var6 = (s64)adc_press * (s64)adc_press; + var2 = ((s64)calib->P11 * var6) >> 16; + var3 = (var2 * (s64)adc_press) >> 7; + var4 = (offset >> 2) + var1 + var5 + var3; + comp_press = ((u64)var4 * 25) >> 40; + + comp_press = clamp_val(comp_press, BMP380_MIN_PRES, BMP380_MAX_PRES); + return comp_press; +} + +static int bmp380_read_temp(struct bmp280_data *data, s32 *comp_temp) +{ + u32 adc_temp; + int ret; + + ret = bmp380_read_temp_adc(data, &adc_temp); + if (ret) + return ret; + + *comp_temp = bmp380_compensate_temp(data, adc_temp); + + return 0; +} + +static int bmp380_read_press(struct bmp280_data *data, u32 *comp_press) +{ + u32 adc_press, t_fine; + int ret; + + ret = bmp380_get_t_fine(data, &t_fine); + if (ret) + return ret; + + ret = bmp380_read_press_adc(data, &adc_press); + if (ret) + return ret; + + *comp_press = bmp380_compensate_press(data, adc_press, t_fine); + + return 0; +} + +static int bmp380_read_calib(struct bmp280_data *data) +{ + struct bmp380_calib *calib = &data->calib.bmp380; + int ret; + + /* Read temperature and pressure calibration data */ + ret = regmap_bulk_read(data->regmap, BMP380_REG_CALIB_TEMP_START, + data->bmp380_cal_buf, + sizeof(data->bmp380_cal_buf)); + if (ret) { + dev_err(data->dev, + "failed to read calibration parameters\n"); + return ret; + } + + /* Toss the temperature calibration data into the entropy pool */ + add_device_randomness(data->bmp380_cal_buf, + sizeof(data->bmp380_cal_buf)); + + /* Parse calibration values */ + calib->T1 = get_unaligned_le16(&data->bmp380_cal_buf[BMP380_T1]); + calib->T2 = get_unaligned_le16(&data->bmp380_cal_buf[BMP380_T2]); + calib->T3 = data->bmp380_cal_buf[BMP380_T3]; + calib->P1 = get_unaligned_le16(&data->bmp380_cal_buf[BMP380_P1]); + calib->P2 = get_unaligned_le16(&data->bmp380_cal_buf[BMP380_P2]); + calib->P3 = data->bmp380_cal_buf[BMP380_P3]; + calib->P4 = data->bmp380_cal_buf[BMP380_P4]; + calib->P5 = get_unaligned_le16(&data->bmp380_cal_buf[BMP380_P5]); + calib->P6 = get_unaligned_le16(&data->bmp380_cal_buf[BMP380_P6]); + calib->P7 = data->bmp380_cal_buf[BMP380_P7]; + calib->P8 = data->bmp380_cal_buf[BMP380_P8]; + calib->P9 = get_unaligned_le16(&data->bmp380_cal_buf[BMP380_P9]); + calib->P10 = data->bmp380_cal_buf[BMP380_P10]; + calib->P11 = data->bmp380_cal_buf[BMP380_P11]; + + return 0; +} + +static const int bmp380_odr_table[][2] = { + [BMP380_ODR_200HZ] = {200, 0}, + [BMP380_ODR_100HZ] = {100, 0}, + [BMP380_ODR_50HZ] = {50, 0}, + [BMP380_ODR_25HZ] = {25, 0}, + [BMP380_ODR_12_5HZ] = {12, 500000}, + [BMP380_ODR_6_25HZ] = {6, 250000}, + [BMP380_ODR_3_125HZ] = {3, 125000}, + [BMP380_ODR_1_5625HZ] = {1, 562500}, + [BMP380_ODR_0_78HZ] = {0, 781250}, + [BMP380_ODR_0_39HZ] = {0, 390625}, + [BMP380_ODR_0_2HZ] = {0, 195313}, + [BMP380_ODR_0_1HZ] = {0, 97656}, + [BMP380_ODR_0_05HZ] = {0, 48828}, + [BMP380_ODR_0_02HZ] = {0, 24414}, + [BMP380_ODR_0_01HZ] = {0, 12207}, + [BMP380_ODR_0_006HZ] = {0, 6104}, + [BMP380_ODR_0_003HZ] = {0, 3052}, + [BMP380_ODR_0_0015HZ] = {0, 1526}, +}; + +static int bmp380_preinit(struct bmp280_data *data) +{ + /* BMP3xx requires soft-reset as part of initialization */ + return bmp380_cmd(data, BMP380_CMD_SOFT_RESET); +} + +static const u8 bmp380_operation_mode[] = { + [BMP280_SLEEP] = BMP380_MODE_SLEEP, + [BMP280_FORCED] = BMP380_MODE_FORCED, + [BMP280_NORMAL] = BMP380_MODE_NORMAL, +}; + +static int bmp380_set_mode(struct bmp280_data *data, enum bmp280_op_mode mode) +{ + int ret; + + ret = regmap_write_bits(data->regmap, BMP380_REG_POWER_CONTROL, + BMP380_MODE_MASK, + FIELD_PREP(BMP380_MODE_MASK, + bmp380_operation_mode[mode])); + if (ret) { + dev_err(data->dev, "failed to write power control register.\n"); + return ret; + } + + data->op_mode = mode; + + return 0; +} + +static int bmp380_wait_conv(struct bmp280_data *data) +{ + unsigned int reg; + int ret, meas_time_us; + + /* Offset measurement time */ + meas_time_us = BMP380_MEAS_OFFSET; + + /* Pressure measurement time */ + meas_time_us += BMP380_PRESS_MEAS_OFFSET + + BIT(data->oversampling_press) * BMP380_MEAS_DUR; + + /* Temperature measurement time */ + meas_time_us += BMP380_TEMP_MEAS_OFFSET + + BIT(data->oversampling_temp) * BMP380_MEAS_DUR; + + /* Measurement time defined in Datasheet Section 3.9.2 */ + fsleep(meas_time_us); + + ret = regmap_read(data->regmap, BMP380_REG_STATUS, ®); + if (ret) { + dev_err(data->dev, "failed to read status register.\n"); + return ret; + } + + if (!((reg & BMP380_STATUS_DRDY_PRESS_MASK) && + (reg & BMP380_STATUS_DRDY_TEMP_MASK))) { + dev_err(data->dev, "Measurement cycle didn't complete.\n"); + return -EBUSY; + } + + return 0; +} + +static int bmp380_chip_config(struct bmp280_data *data) +{ + bool change = false, aux; + unsigned int tmp; + u8 osrs; + int ret; + + /* Configure power control register */ + ret = regmap_update_bits(data->regmap, BMP380_REG_POWER_CONTROL, + BMP380_CTRL_SENSORS_MASK, + BMP380_CTRL_SENSORS_PRESS_EN | + BMP380_CTRL_SENSORS_TEMP_EN); + if (ret) { + dev_err(data->dev, + "failed to write operation control register\n"); + return ret; + } + + /* Configure oversampling */ + osrs = FIELD_PREP(BMP380_OSRS_TEMP_MASK, data->oversampling_temp) | + FIELD_PREP(BMP380_OSRS_PRESS_MASK, data->oversampling_press); + + ret = regmap_update_bits_check(data->regmap, BMP380_REG_OSR, + BMP380_OSRS_TEMP_MASK | + BMP380_OSRS_PRESS_MASK, + osrs, &aux); + if (ret) { + dev_err(data->dev, "failed to write oversampling register\n"); + return ret; + } + change = change || aux; + + /* Configure output data rate */ + ret = regmap_update_bits_check(data->regmap, BMP380_REG_ODR, + BMP380_ODRS_MASK, data->sampling_freq, + &aux); + if (ret) { + dev_err(data->dev, "failed to write ODR selection register\n"); + return ret; + } + change = change || aux; + + /* Set filter data */ + ret = regmap_update_bits(data->regmap, BMP380_REG_CONFIG, BMP380_FILTER_MASK, + FIELD_PREP(BMP380_FILTER_MASK, data->iir_filter_coeff)); + if (ret) { + dev_err(data->dev, "failed to write config register\n"); + return ret; + } + + if (change) { + /* + * The configurations errors are detected on the fly during a + * measurement cycle. If the sampling frequency is too low, it's + * faster to reset the measurement loop than wait until the next + * measurement is due. + * + * Resets sensor measurement loop toggling between sleep and + * normal operating modes. + */ + ret = bmp380_set_mode(data, BMP280_SLEEP); + if (ret) { + dev_err(data->dev, "failed to set sleep mode\n"); + return ret; + } + + /* + * According to the BMP3 Sensor API, the sensor needs 5ms + * in order to go to the sleep mode. + */ + fsleep(5 * USEC_PER_MSEC); + + ret = bmp380_set_mode(data, BMP280_NORMAL); + if (ret) { + dev_err(data->dev, "failed to set normal mode\n"); + return ret; + } + /* + * Waits for measurement before checking configuration error + * flag. Selected longest measurement time, calculated from + * formula in datasheet section 3.9.2 with an offset of ~+15% + * as it seen as well in table 3.9.1. + */ + fsleep(150 * USEC_PER_MSEC); + + /* Check config error flag */ + ret = regmap_read(data->regmap, BMP380_REG_ERROR, &tmp); + if (ret) { + dev_err(data->dev, "failed to read error register\n"); + return ret; + } + if (tmp & BMP380_ERR_CONF_MASK) { + dev_warn(data->dev, + "sensor flagged configuration as incompatible\n"); + return -EINVAL; + } + } + + /* Dummy read to empty data registers. */ + ret = bmp380_read_press(data, &tmp); + if (ret) + return ret; + + ret = bmp380_set_mode(data, BMP280_SLEEP); + if (ret) + dev_err(data->dev, "failed to set sleep mode.\n"); + + return ret; +} + +static int bmp380_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct bmp280_data *data = iio_trigger_get_drvdata(trig); + int ret; + + guard(mutex)(&data->lock); + + ret = regmap_update_bits(data->regmap, BMP380_REG_INT_CONTROL, + BMP380_INT_CTRL_DRDY_EN, + FIELD_PREP(BMP380_INT_CTRL_DRDY_EN, !!state)); + if (ret) + dev_err(data->dev, + "Could not %s interrupt.\n", str_enable_disable(state)); + return ret; +} + +static const struct iio_trigger_ops bmp380_trigger_ops = { + .set_trigger_state = &bmp380_data_rdy_trigger_set_state, +}; + +static int bmp380_int_pin_config(struct bmp280_data *data) +{ + int pin_drive_cfg = FIELD_PREP(BMP380_INT_CTRL_OPEN_DRAIN, + data->trig_open_drain); + int pin_level_cfg = FIELD_PREP(BMP380_INT_CTRL_LEVEL, + data->trig_active_high); + int ret, int_pin_cfg = pin_drive_cfg | pin_level_cfg; + + ret = regmap_update_bits(data->regmap, BMP380_REG_INT_CONTROL, + BMP380_INT_CTRL_SETTINGS_MASK, int_pin_cfg); + if (ret) + dev_err(data->dev, "Could not set interrupt settings.\n"); + + return ret; +} + +static irqreturn_t bmp380_irq_thread_handler(int irq, void *p) +{ + struct iio_dev *indio_dev = p; + struct bmp280_data *data = iio_priv(indio_dev); + unsigned int int_ctrl; + int ret; + + ret = regmap_read(data->regmap, BMP380_REG_INT_STATUS, &int_ctrl); + if (ret) + return IRQ_NONE; + + if (FIELD_GET(BMP380_INT_STATUS_DRDY, int_ctrl)) + iio_trigger_poll_nested(data->trig); + + return IRQ_HANDLED; +} + +static int bmp380_trigger_probe(struct iio_dev *indio_dev) +{ + return __bmp280_trigger_probe(indio_dev, &bmp380_trigger_ops, + bmp380_int_pin_config, + bmp380_irq_thread_handler); +} + +static irqreturn_t bmp380_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bmp280_data *data = iio_priv(indio_dev); + u32 adc_temp, adc_press; + s32 t_fine; + struct { + u32 comp_press; + s32 comp_temp; + aligned_s64 timestamp; + } buffer = { }; + int ret; + + guard(mutex)(&data->lock); + + /* Burst read data registers */ + ret = regmap_bulk_read(data->regmap, BMP380_REG_PRESS_XLSB, + data->buf, BMP280_BURST_READ_BYTES); + if (ret) { + dev_err(data->dev, "failed to burst read sensor data\n"); + goto out; + } + + /* Temperature calculations */ + adc_temp = get_unaligned_le24(&data->buf[3]); + if (adc_temp == BMP380_TEMP_SKIPPED) { + dev_err(data->dev, "reading temperature skipped\n"); + goto out; + } + + buffer.comp_temp = bmp380_compensate_temp(data, adc_temp); + + /* Pressure calculations */ + adc_press = get_unaligned_le24(&data->buf[0]); + if (adc_press == BMP380_PRESS_SKIPPED) { + dev_err(data->dev, "reading pressure skipped\n"); + goto out; + } + + t_fine = bmp380_calc_t_fine(data, adc_temp); + buffer.comp_press = bmp380_compensate_press(data, adc_press, t_fine); + + iio_push_to_buffers_with_ts(indio_dev, &buffer, sizeof(buffer), + iio_get_time_ns(indio_dev)); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const int bmp380_oversampling_avail[] = { 1, 2, 4, 8, 16, 32 }; +static const int bmp380_iir_filter_coeffs_avail[] = { 1, 2, 4, 8, 16, 32, 64, 128}; +static const u8 bmp380_chip_ids[] = { BMP380_CHIP_ID, BMP390_CHIP_ID }; +static const int bmp380_temp_coeffs[] = { 10, 1 }; +static const int bmp380_press_coeffs[] = { 1, 100000 }; + +const struct bmp280_chip_info bmp380_chip_info = { + .id_reg = BMP380_REG_ID, + .chip_id = bmp380_chip_ids, + .num_chip_id = ARRAY_SIZE(bmp380_chip_ids), + .regmap_config = &bmp380_regmap_config, + .spi_read_extra_byte = true, + .start_up_time_us = 2000, + .channels = bmp380_channels, + .num_channels = ARRAY_SIZE(bmp380_channels), + .avail_scan_masks = bmp280_avail_scan_masks, + + .oversampling_temp_avail = bmp380_oversampling_avail, + .num_oversampling_temp_avail = ARRAY_SIZE(bmp380_oversampling_avail), + .oversampling_temp_default = ilog2(1), + + .oversampling_press_avail = bmp380_oversampling_avail, + .num_oversampling_press_avail = ARRAY_SIZE(bmp380_oversampling_avail), + .oversampling_press_default = ilog2(4), + + .sampling_freq_avail = bmp380_odr_table, + .num_sampling_freq_avail = ARRAY_SIZE(bmp380_odr_table) * 2, + .sampling_freq_default = BMP380_ODR_50HZ, + + .iir_filter_coeffs_avail = bmp380_iir_filter_coeffs_avail, + .num_iir_filter_coeffs_avail = ARRAY_SIZE(bmp380_iir_filter_coeffs_avail), + .iir_filter_coeff_default = 2, + + .temp_coeffs = bmp380_temp_coeffs, + .temp_coeffs_type = IIO_VAL_FRACTIONAL, + .press_coeffs = bmp380_press_coeffs, + .press_coeffs_type = IIO_VAL_FRACTIONAL, + + .chip_config = bmp380_chip_config, + .read_temp = bmp380_read_temp, + .read_press = bmp380_read_press, + .read_calib = bmp380_read_calib, + .set_mode = bmp380_set_mode, + .wait_conv = bmp380_wait_conv, + .preinit = bmp380_preinit, + + .trigger_probe = bmp380_trigger_probe, + .trigger_handler = bmp380_trigger_handler, +}; +EXPORT_SYMBOL_NS(bmp380_chip_info, "IIO_BMP280"); + +static int bmp580_soft_reset(struct bmp280_data *data) +{ + unsigned int reg; + int ret; + + ret = regmap_write(data->regmap, BMP580_REG_CMD, BMP580_CMD_SOFT_RESET); + if (ret) { + dev_err(data->dev, "failed to send reset command to device\n"); + return ret; + } + /* From datasheet's table 4: electrical characteristics */ + fsleep(2000); + + /* Dummy read of chip_id */ + ret = regmap_read(data->regmap, BMP580_REG_CHIP_ID, ®); + if (ret) { + dev_err(data->dev, "failed to reestablish comms after reset\n"); + return ret; + } + + ret = regmap_read(data->regmap, BMP580_REG_INT_STATUS, ®); + if (ret) { + dev_err(data->dev, "error reading interrupt status register\n"); + return ret; + } + if (!(reg & BMP580_INT_STATUS_POR_MASK)) { + dev_err(data->dev, "error resetting sensor\n"); + return -EINVAL; + } + + return 0; +} + +/** + * bmp580_nvm_operation() - Helper function to commit NVM memory operations + * @data: sensor data struct + * @is_write: flag to signal write operation + */ +static int bmp580_nvm_operation(struct bmp280_data *data, bool is_write) +{ + unsigned long timeout, poll; + unsigned int reg; + int ret; + + /* Check NVM ready flag */ + ret = regmap_read(data->regmap, BMP580_REG_STATUS, ®); + if (ret) { + dev_err(data->dev, "failed to check nvm status\n"); + return ret; + } + if (!(reg & BMP580_STATUS_NVM_RDY_MASK)) { + dev_err(data->dev, "sensor's nvm is not ready\n"); + return -EIO; + } + + /* Start NVM operation sequence */ + ret = regmap_write(data->regmap, BMP580_REG_CMD, + BMP580_CMD_NVM_OP_SEQ_0); + if (ret) { + dev_err(data->dev, + "failed to send nvm operation's first sequence\n"); + return ret; + } + if (is_write) { + /* Send NVM write sequence */ + ret = regmap_write(data->regmap, BMP580_REG_CMD, + BMP580_CMD_NVM_WRITE_SEQ_1); + if (ret) { + dev_err(data->dev, + "failed to send nvm write sequence\n"); + return ret; + } + /* Datasheet says on 4.8.1.2 it takes approximately 10ms */ + poll = 2000; + timeout = 12000; + } else { + /* Send NVM read sequence */ + ret = regmap_write(data->regmap, BMP580_REG_CMD, + BMP580_CMD_NVM_READ_SEQ_1); + if (ret) { + dev_err(data->dev, + "failed to send nvm read sequence\n"); + return ret; + } + /* Datasheet says on 4.8.1.1 it takes approximately 200us */ + poll = 50; + timeout = 400; + } + + /* Wait until NVM is ready again */ + ret = regmap_read_poll_timeout(data->regmap, BMP580_REG_STATUS, reg, + (reg & BMP580_STATUS_NVM_RDY_MASK), + poll, timeout); + if (ret) { + dev_err(data->dev, "error checking nvm operation status\n"); + return ret; + } + + /* Check NVM error flags */ + if ((reg & BMP580_STATUS_NVM_ERR_MASK) || (reg & BMP580_STATUS_NVM_CMD_ERR_MASK)) { + dev_err(data->dev, "error processing nvm operation\n"); + return -EIO; + } + + return 0; +} + +/* + * Contrary to previous sensors families, compensation algorithm is builtin. + * We are only required to read the register raw data and adapt the ranges + * for what is expected on IIO ABI. + */ + +static int bmp580_read_temp(struct bmp280_data *data, s32 *raw_temp) +{ + s32 value_temp; + int ret; + + ret = regmap_bulk_read(data->regmap, BMP580_REG_TEMP_XLSB, + data->buf, BMP280_NUM_TEMP_BYTES); + if (ret) { + dev_err(data->dev, "failed to read temperature\n"); + return ret; + } + + value_temp = get_unaligned_le24(data->buf); + if (value_temp == BMP580_TEMP_SKIPPED) { + dev_err(data->dev, "reading temperature skipped\n"); + return -EIO; + } + *raw_temp = sign_extend32(value_temp, 23); + + return 0; +} + +static int bmp580_read_press(struct bmp280_data *data, u32 *raw_press) +{ + u32 value_press; + int ret; + + ret = regmap_bulk_read(data->regmap, BMP580_REG_PRESS_XLSB, + data->buf, BMP280_NUM_PRESS_BYTES); + if (ret) { + dev_err(data->dev, "failed to read pressure\n"); + return ret; + } + + value_press = get_unaligned_le24(data->buf); + if (value_press == BMP580_PRESS_SKIPPED) { + dev_err(data->dev, "reading pressure skipped\n"); + return -EIO; + } + *raw_press = value_press; + + return 0; +} + +static const int bmp580_odr_table[][2] = { + [BMP580_ODR_240HZ] = {240, 0}, + [BMP580_ODR_218HZ] = {218, 0}, + [BMP580_ODR_199HZ] = {199, 0}, + [BMP580_ODR_179HZ] = {179, 0}, + [BMP580_ODR_160HZ] = {160, 0}, + [BMP580_ODR_149HZ] = {149, 0}, + [BMP580_ODR_140HZ] = {140, 0}, + [BMP580_ODR_129HZ] = {129, 0}, + [BMP580_ODR_120HZ] = {120, 0}, + [BMP580_ODR_110HZ] = {110, 0}, + [BMP580_ODR_100HZ] = {100, 0}, + [BMP580_ODR_89HZ] = {89, 0}, + [BMP580_ODR_80HZ] = {80, 0}, + [BMP580_ODR_70HZ] = {70, 0}, + [BMP580_ODR_60HZ] = {60, 0}, + [BMP580_ODR_50HZ] = {50, 0}, + [BMP580_ODR_45HZ] = {45, 0}, + [BMP580_ODR_40HZ] = {40, 0}, + [BMP580_ODR_35HZ] = {35, 0}, + [BMP580_ODR_30HZ] = {30, 0}, + [BMP580_ODR_25HZ] = {25, 0}, + [BMP580_ODR_20HZ] = {20, 0}, + [BMP580_ODR_15HZ] = {15, 0}, + [BMP580_ODR_10HZ] = {10, 0}, + [BMP580_ODR_5HZ] = {5, 0}, + [BMP580_ODR_4HZ] = {4, 0}, + [BMP580_ODR_3HZ] = {3, 0}, + [BMP580_ODR_2HZ] = {2, 0}, + [BMP580_ODR_1HZ] = {1, 0}, + [BMP580_ODR_0_5HZ] = {0, 500000}, + [BMP580_ODR_0_25HZ] = {0, 250000}, + [BMP580_ODR_0_125HZ] = {0, 125000}, +}; + +static const int bmp580_nvmem_addrs[] = { 0x20, 0x21, 0x22 }; + +static int bmp580_nvmem_read_impl(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct bmp280_data *data = priv; + u16 *dst = val; + int ret, addr; + + guard(mutex)(&data->lock); + + /* Set sensor in standby mode */ + ret = regmap_update_bits(data->regmap, BMP580_REG_ODR_CONFIG, + BMP580_MODE_MASK | BMP580_ODR_DEEPSLEEP_DIS, + BMP580_ODR_DEEPSLEEP_DIS | + FIELD_PREP(BMP580_MODE_MASK, BMP580_MODE_SLEEP)); + if (ret) { + dev_err(data->dev, "failed to change sensor to standby mode\n"); + goto exit; + } + /* Wait standby transition time */ + fsleep(2500); + + while (bytes >= sizeof(*dst)) { + addr = bmp580_nvmem_addrs[offset / sizeof(*dst)]; + + ret = regmap_write(data->regmap, BMP580_REG_NVM_ADDR, + FIELD_PREP(BMP580_NVM_ROW_ADDR_MASK, addr)); + if (ret) { + dev_err(data->dev, "error writing nvm address\n"); + goto exit; + } + + ret = bmp580_nvm_operation(data, false); + if (ret) + goto exit; + + ret = regmap_bulk_read(data->regmap, BMP580_REG_NVM_DATA_LSB, + &data->le16, sizeof(data->le16)); + if (ret) { + dev_err(data->dev, "error reading nvm data regs\n"); + goto exit; + } + + *dst++ = le16_to_cpu(data->le16); + bytes -= sizeof(*dst); + offset += sizeof(*dst); + } +exit: + /* Restore chip config */ + data->chip_info->chip_config(data); + return ret; +} + +static int bmp580_nvmem_read(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct bmp280_data *data = priv; + int ret; + + ret = pm_runtime_resume_and_get(data->dev); + if (ret < 0) + return ret; + + ret = bmp580_nvmem_read_impl(priv, offset, val, bytes); + pm_runtime_put_autosuspend(data->dev); + + return ret; +} + +static int bmp580_nvmem_write_impl(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct bmp280_data *data = priv; + u16 *buf = val; + int ret, addr; + + guard(mutex)(&data->lock); + + /* Set sensor in standby mode */ + ret = regmap_update_bits(data->regmap, BMP580_REG_ODR_CONFIG, + BMP580_MODE_MASK | BMP580_ODR_DEEPSLEEP_DIS, + BMP580_ODR_DEEPSLEEP_DIS | + FIELD_PREP(BMP580_MODE_MASK, BMP580_MODE_SLEEP)); + if (ret) { + dev_err(data->dev, "failed to change sensor to standby mode\n"); + goto exit; + } + /* Wait standby transition time */ + fsleep(2500); + + while (bytes >= sizeof(*buf)) { + addr = bmp580_nvmem_addrs[offset / sizeof(*buf)]; + + ret = regmap_write(data->regmap, BMP580_REG_NVM_ADDR, + BMP580_NVM_PROG_EN | + FIELD_PREP(BMP580_NVM_ROW_ADDR_MASK, addr)); + if (ret) { + dev_err(data->dev, "error writing nvm address\n"); + goto exit; + } + data->le16 = cpu_to_le16(*buf++); + + ret = regmap_bulk_write(data->regmap, BMP580_REG_NVM_DATA_LSB, + &data->le16, sizeof(data->le16)); + if (ret) { + dev_err(data->dev, "error writing LSB NVM data regs\n"); + goto exit; + } + + ret = bmp580_nvm_operation(data, true); + if (ret) + goto exit; + + /* Disable programming mode bit */ + ret = regmap_clear_bits(data->regmap, BMP580_REG_NVM_ADDR, + BMP580_NVM_PROG_EN); + if (ret) { + dev_err(data->dev, "error resetting nvm write\n"); + goto exit; + } + + bytes -= sizeof(*buf); + offset += sizeof(*buf); + } +exit: + /* Restore chip config */ + data->chip_info->chip_config(data); + return ret; +} + +static int bmp580_nvmem_write(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct bmp280_data *data = priv; + int ret; + + ret = pm_runtime_resume_and_get(data->dev); + if (ret < 0) + return ret; + + ret = bmp580_nvmem_write_impl(priv, offset, val, bytes); + pm_runtime_put_autosuspend(data->dev); + + return ret; +} + +static int bmp580_preinit(struct bmp280_data *data) +{ + struct nvmem_config config = { + .dev = data->dev, + .priv = data, + .name = "bmp580_nvmem", + .word_size = sizeof(u16), + .stride = sizeof(u16), + .size = 3 * sizeof(u16), + .reg_read = bmp580_nvmem_read, + .reg_write = bmp580_nvmem_write, + }; + unsigned int reg; + int ret; + + /* Issue soft-reset command */ + ret = bmp580_soft_reset(data); + if (ret) + return ret; + + /* Post powerup sequence */ + ret = regmap_read(data->regmap, BMP580_REG_CHIP_ID, ®); + if (ret) { + dev_err(data->dev, "failed to establish comms with the chip\n"); + return ret; + } + + /* Print warn message if we don't know the chip id */ + if (reg != BMP580_CHIP_ID && reg != BMP580_CHIP_ID_ALT) + dev_warn(data->dev, "unexpected chip_id\n"); + + ret = regmap_read(data->regmap, BMP580_REG_STATUS, ®); + if (ret) { + dev_err(data->dev, "failed to read nvm status\n"); + return ret; + } + + /* Check nvm status */ + if (!(reg & BMP580_STATUS_NVM_RDY_MASK) || (reg & BMP580_STATUS_NVM_ERR_MASK)) { + dev_err(data->dev, "nvm error on powerup sequence\n"); + return -EIO; + } + + /* Register nvmem device */ + return PTR_ERR_OR_ZERO(devm_nvmem_register(config.dev, &config)); +} + +static const u8 bmp580_operation_mode[] = { + [BMP280_SLEEP] = BMP580_MODE_SLEEP, + [BMP280_FORCED] = BMP580_MODE_FORCED, + [BMP280_NORMAL] = BMP580_MODE_NORMAL, +}; + +static int bmp580_set_mode(struct bmp280_data *data, enum bmp280_op_mode mode) +{ + struct device *dev = data->dev; + int ret; + + if (mode == BMP280_FORCED) { + ret = regmap_set_bits(data->regmap, BMP580_REG_DSP_CONFIG, + BMP580_DSP_IIR_FORCED_FLUSH); + if (ret) { + dev_err(dev, "Could not flush IIR filter constants.\n"); + return ret; + } + } + + ret = regmap_write_bits(data->regmap, BMP580_REG_ODR_CONFIG, + BMP580_MODE_MASK, + FIELD_PREP(BMP580_MODE_MASK, + bmp580_operation_mode[mode])); + if (ret) { + dev_err(dev, "failed to write power control register.\n"); + return ret; + } + + data->op_mode = mode; + + return 0; +} + +static int bmp580_wait_conv(struct bmp280_data *data) +{ + /* + * Taken from datasheet, Section 2 "Specification, Table 3 "Electrical + * characteristics. + */ + static const int time_conv_press[] = { + 0, 1050, 1785, 3045, 5670, 10920, 21420, 42420, + 84420, + }; + static const int time_conv_temp[] = { + 0, 1050, 1105, 1575, 2205, 3465, 6090, 11340, + 21840, + }; + int meas_time_us; + + meas_time_us = 4 * USEC_PER_MSEC + + time_conv_temp[data->oversampling_temp] + + time_conv_press[data->oversampling_press]; + + /* + * Measurement time mentioned in Chapter 2, Table 4 of the datasheet. + * The extra 4ms is the required mode change to start of measurement + * time. + */ + fsleep(meas_time_us); + + return 0; +} + +static int bmp580_chip_config(struct bmp280_data *data) +{ + bool change = false, aux; + unsigned int tmp; + u8 reg_val; + int ret; + + /* Sets sensor in standby mode */ + ret = regmap_update_bits(data->regmap, BMP580_REG_ODR_CONFIG, + BMP580_MODE_MASK | BMP580_ODR_DEEPSLEEP_DIS, + BMP580_ODR_DEEPSLEEP_DIS | + FIELD_PREP(BMP580_MODE_MASK, BMP580_MODE_SLEEP)); + if (ret) { + dev_err(data->dev, "failed to change sensor to standby mode\n"); + return ret; + } + /* From datasheet's table 4: electrical characteristics */ + fsleep(2500); + + /* Set default DSP mode settings */ + reg_val = FIELD_PREP(BMP580_DSP_COMP_MASK, BMP580_DSP_PRESS_TEMP_COMP_EN) | + BMP580_DSP_SHDW_IIR_TEMP_EN | BMP580_DSP_SHDW_IIR_PRESS_EN; + + ret = regmap_update_bits(data->regmap, BMP580_REG_DSP_CONFIG, + BMP580_DSP_COMP_MASK | + BMP580_DSP_SHDW_IIR_TEMP_EN | + BMP580_DSP_SHDW_IIR_PRESS_EN, reg_val); + if (ret) { + dev_err(data->dev, "failed to change DSP mode settings\n"); + return ret; + } + + /* Configure oversampling */ + reg_val = FIELD_PREP(BMP580_OSR_TEMP_MASK, data->oversampling_temp) | + FIELD_PREP(BMP580_OSR_PRESS_MASK, data->oversampling_press) | + BMP580_OSR_PRESS_EN; + + ret = regmap_update_bits_check(data->regmap, BMP580_REG_OSR_CONFIG, + BMP580_OSR_TEMP_MASK | + BMP580_OSR_PRESS_MASK | + BMP580_OSR_PRESS_EN, + reg_val, &aux); + if (ret) { + dev_err(data->dev, "failed to write oversampling register\n"); + return ret; + } + change = change || aux; + + /* Configure output data rate */ + ret = regmap_update_bits_check(data->regmap, BMP580_REG_ODR_CONFIG, BMP580_ODR_MASK, + FIELD_PREP(BMP580_ODR_MASK, data->sampling_freq), + &aux); + if (ret) { + dev_err(data->dev, "failed to write ODR configuration register\n"); + return ret; + } + change = change || aux; + + /* Set filter data */ + reg_val = FIELD_PREP(BMP580_DSP_IIR_PRESS_MASK, data->iir_filter_coeff) | + FIELD_PREP(BMP580_DSP_IIR_TEMP_MASK, data->iir_filter_coeff); + + ret = regmap_update_bits(data->regmap, BMP580_REG_DSP_IIR, + BMP580_DSP_IIR_PRESS_MASK | BMP580_DSP_IIR_TEMP_MASK, + reg_val); + if (ret) { + dev_err(data->dev, "failed to write config register\n"); + return ret; + } + + if (change) { + /* + * Check if ODR and OSR settings are valid or we are + * operating in a degraded mode. + */ + ret = regmap_read(data->regmap, BMP580_REG_EFF_OSR, &tmp); + if (ret) { + dev_err(data->dev, + "error reading effective OSR register\n"); + return ret; + } + if (!(tmp & BMP580_EFF_OSR_VALID_ODR)) { + dev_warn(data->dev, "OSR and ODR incompatible settings detected\n"); + /* Set current OSR settings from data on effective OSR */ + data->oversampling_temp = FIELD_GET(BMP580_EFF_OSR_TEMP_MASK, tmp); + data->oversampling_press = FIELD_GET(BMP580_EFF_OSR_PRESS_MASK, tmp); + return -EINVAL; + } + } + + return 0; +} + +static int bmp580_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct bmp280_data *data = iio_trigger_get_drvdata(trig); + int ret; + + guard(mutex)(&data->lock); + + ret = regmap_update_bits(data->regmap, BMP580_REG_INT_CONFIG, + BMP580_INT_CONFIG_INT_EN, + FIELD_PREP(BMP580_INT_CONFIG_INT_EN, !!state)); + if (ret) + dev_err(data->dev, + "Could not %s interrupt.\n", str_enable_disable(state)); + return ret; +} + +static const struct iio_trigger_ops bmp580_trigger_ops = { + .set_trigger_state = &bmp580_data_rdy_trigger_set_state, +}; + +static int bmp580_int_pin_config(struct bmp280_data *data) +{ + int pin_drive_cfg = FIELD_PREP(BMP580_INT_CONFIG_OPEN_DRAIN, + data->trig_open_drain); + int pin_level_cfg = FIELD_PREP(BMP580_INT_CONFIG_LEVEL, + data->trig_active_high); + int ret, int_pin_cfg = pin_drive_cfg | pin_level_cfg; + + ret = regmap_update_bits(data->regmap, BMP580_REG_INT_CONFIG, + BMP580_INT_CONFIG_MASK, int_pin_cfg); + if (ret) { + dev_err(data->dev, "Could not set interrupt settings.\n"); + return ret; + } + + ret = regmap_set_bits(data->regmap, BMP580_REG_INT_SOURCE, + BMP580_INT_SOURCE_DRDY); + if (ret) + dev_err(data->dev, "Could not set interrupt source.\n"); + + return ret; +} + +static irqreturn_t bmp580_irq_thread_handler(int irq, void *p) +{ + struct iio_dev *indio_dev = p; + struct bmp280_data *data = iio_priv(indio_dev); + unsigned int int_ctrl; + int ret; + + ret = regmap_read(data->regmap, BMP580_REG_INT_STATUS, &int_ctrl); + if (ret) + return IRQ_NONE; + + if (FIELD_GET(BMP580_INT_STATUS_DRDY_MASK, int_ctrl)) + iio_trigger_poll_nested(data->trig); + + return IRQ_HANDLED; +} + +static int bmp580_trigger_probe(struct iio_dev *indio_dev) +{ + return __bmp280_trigger_probe(indio_dev, &bmp580_trigger_ops, + bmp580_int_pin_config, + bmp580_irq_thread_handler); +} + +static irqreturn_t bmp580_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bmp280_data *data = iio_priv(indio_dev); + struct { + __le32 comp_temp; + __le32 comp_press; + aligned_s64 timestamp; + } buffer = { }; + int ret; + + guard(mutex)(&data->lock); + + /* Burst read data registers */ + ret = regmap_bulk_read(data->regmap, BMP580_REG_TEMP_XLSB, + data->buf, BMP280_BURST_READ_BYTES); + if (ret) { + dev_err(data->dev, "failed to burst read sensor data\n"); + goto out; + } + + /* Pressure calculations */ + memcpy(&buffer.comp_press, &data->buf[3], 3); + + /* Temperature calculations */ + memcpy(&buffer.comp_temp, &data->buf[0], 3); + + iio_push_to_buffers_with_ts(indio_dev, &buffer, sizeof(buffer), + iio_get_time_ns(indio_dev)); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const int bmp580_oversampling_avail[] = { 1, 2, 4, 8, 16, 32, 64, 128 }; +static const u8 bmp580_chip_ids[] = { BMP580_CHIP_ID, BMP580_CHIP_ID_ALT }; +/* Instead of { 1000, 16 } we do this, to avoid overflow issues */ +static const int bmp580_temp_coeffs[] = { 125, 13 }; +static const int bmp580_press_coeffs[] = { 1, 64000}; + +const struct bmp280_chip_info bmp580_chip_info = { + .id_reg = BMP580_REG_CHIP_ID, + .chip_id = bmp580_chip_ids, + .num_chip_id = ARRAY_SIZE(bmp580_chip_ids), + .regmap_config = &bmp580_regmap_config, + .start_up_time_us = 2000, + .channels = bmp580_channels, + .num_channels = ARRAY_SIZE(bmp580_channels), + .avail_scan_masks = bmp280_avail_scan_masks, + + .oversampling_temp_avail = bmp580_oversampling_avail, + .num_oversampling_temp_avail = ARRAY_SIZE(bmp580_oversampling_avail), + .oversampling_temp_default = ilog2(1), + + .oversampling_press_avail = bmp580_oversampling_avail, + .num_oversampling_press_avail = ARRAY_SIZE(bmp580_oversampling_avail), + .oversampling_press_default = ilog2(4), + + .sampling_freq_avail = bmp580_odr_table, + .num_sampling_freq_avail = ARRAY_SIZE(bmp580_odr_table) * 2, + .sampling_freq_default = BMP580_ODR_50HZ, + + .iir_filter_coeffs_avail = bmp380_iir_filter_coeffs_avail, + .num_iir_filter_coeffs_avail = ARRAY_SIZE(bmp380_iir_filter_coeffs_avail), + .iir_filter_coeff_default = 2, + + .temp_coeffs = bmp580_temp_coeffs, + .temp_coeffs_type = IIO_VAL_FRACTIONAL_LOG2, + .press_coeffs = bmp580_press_coeffs, + .press_coeffs_type = IIO_VAL_FRACTIONAL, + + .chip_config = bmp580_chip_config, + .read_temp = bmp580_read_temp, + .read_press = bmp580_read_press, + .set_mode = bmp580_set_mode, + .wait_conv = bmp580_wait_conv, + .preinit = bmp580_preinit, + + .trigger_probe = bmp580_trigger_probe, + .trigger_handler = bmp580_trigger_handler, +}; +EXPORT_SYMBOL_NS(bmp580_chip_info, "IIO_BMP280"); + +static int bmp180_wait_for_eoc(struct bmp280_data *data, u8 ctrl_meas) +{ + static const int conversion_time_max[] = { 4500, 7500, 13500, 25500 }; + unsigned int delay_us; + unsigned int ctrl; + int ret; + + if (data->use_eoc) + reinit_completion(&data->done); + + ret = regmap_write(data->regmap, BMP280_REG_CTRL_MEAS, ctrl_meas); + if (ret) { + dev_err(data->dev, "failed to write crtl_meas register\n"); + return ret; + } + + if (data->use_eoc) { + /* + * If we have a completion interrupt, use it, wait up to + * 100ms. The longest conversion time listed is 76.5 ms for + * advanced resolution mode. + */ + ret = wait_for_completion_timeout(&data->done, + 1 + msecs_to_jiffies(100)); + if (!ret) + dev_err(data->dev, "timeout waiting for completion\n"); + } else { + if (FIELD_GET(BMP180_MEAS_CTRL_MASK, ctrl_meas) == BMP180_MEAS_TEMP) + delay_us = 4500; + else + delay_us = + conversion_time_max[data->oversampling_press]; + + fsleep(delay_us); + } + + ret = regmap_read(data->regmap, BMP280_REG_CTRL_MEAS, &ctrl); + if (ret) { + dev_err(data->dev, "failed to read ctrl_meas register\n"); + return ret; + } + + /* The value of this bit reset to "0" after conversion is complete */ + if (ctrl & BMP180_MEAS_SCO) { + dev_err(data->dev, "conversion didn't complete\n"); + return -EIO; + } + + return 0; +} + +static int bmp180_read_temp_adc(struct bmp280_data *data, u32 *adc_temp) +{ + int ret; + + ret = bmp180_wait_for_eoc(data, + FIELD_PREP(BMP180_MEAS_CTRL_MASK, BMP180_MEAS_TEMP) | + BMP180_MEAS_SCO); + if (ret) + return ret; + + ret = regmap_bulk_read(data->regmap, BMP180_REG_OUT_MSB, + &data->be16, sizeof(data->be16)); + if (ret) { + dev_err(data->dev, "failed to read temperature\n"); + return ret; + } + + *adc_temp = be16_to_cpu(data->be16); + + return 0; +} + +static int bmp180_read_calib(struct bmp280_data *data) +{ + struct bmp180_calib *calib = &data->calib.bmp180; + int ret; + int i; + + ret = regmap_bulk_read(data->regmap, BMP180_REG_CALIB_START, + data->bmp180_cal_buf, sizeof(data->bmp180_cal_buf)); + if (ret) { + dev_err(data->dev, "failed to read calibration parameters\n"); + return ret; + } + + /* None of the words has the value 0 or 0xFFFF */ + for (i = 0; i < ARRAY_SIZE(data->bmp180_cal_buf); i++) { + if (data->bmp180_cal_buf[i] == cpu_to_be16(0) || + data->bmp180_cal_buf[i] == cpu_to_be16(0xffff)) + return -EIO; + } + + /* Toss the calibration data into the entropy pool */ + add_device_randomness(data->bmp180_cal_buf, + sizeof(data->bmp180_cal_buf)); + + calib->AC1 = be16_to_cpu(data->bmp180_cal_buf[AC1]); + calib->AC2 = be16_to_cpu(data->bmp180_cal_buf[AC2]); + calib->AC3 = be16_to_cpu(data->bmp180_cal_buf[AC3]); + calib->AC4 = be16_to_cpu(data->bmp180_cal_buf[AC4]); + calib->AC5 = be16_to_cpu(data->bmp180_cal_buf[AC5]); + calib->AC6 = be16_to_cpu(data->bmp180_cal_buf[AC6]); + calib->B1 = be16_to_cpu(data->bmp180_cal_buf[B1]); + calib->B2 = be16_to_cpu(data->bmp180_cal_buf[B2]); + calib->MB = be16_to_cpu(data->bmp180_cal_buf[MB]); + calib->MC = be16_to_cpu(data->bmp180_cal_buf[MC]); + calib->MD = be16_to_cpu(data->bmp180_cal_buf[MD]); + + return 0; +} + +/* + * Returns temperature in DegC, resolution is 0.1 DegC. + * t_fine carries fine temperature as global value. + * + * Taken from datasheet, Section 3.5, "Calculating pressure and temperature". + */ + +static s32 bmp180_calc_t_fine(struct bmp280_data *data, u32 adc_temp) +{ + struct bmp180_calib *calib = &data->calib.bmp180; + s32 x1, x2; + + x1 = ((((s32)adc_temp) - calib->AC6) * calib->AC5) >> 15; + x2 = (calib->MC << 11) / (x1 + calib->MD); + return x1 + x2; /* t_fine = x1 + x2; */ +} + +static int bmp180_get_t_fine(struct bmp280_data *data, s32 *t_fine) +{ + s32 adc_temp; + int ret; + + ret = bmp180_read_temp_adc(data, &adc_temp); + if (ret) + return ret; + + *t_fine = bmp180_calc_t_fine(data, adc_temp); + + return 0; +} + +static s32 bmp180_compensate_temp(struct bmp280_data *data, u32 adc_temp) +{ + return (bmp180_calc_t_fine(data, adc_temp) + 8) / 16; +} + +static int bmp180_read_temp(struct bmp280_data *data, s32 *comp_temp) +{ + u32 adc_temp; + int ret; + + ret = bmp180_read_temp_adc(data, &adc_temp); + if (ret) + return ret; + + *comp_temp = bmp180_compensate_temp(data, adc_temp); + + return 0; +} + +static int bmp180_read_press_adc(struct bmp280_data *data, u32 *adc_press) +{ + u8 oss = data->oversampling_press; + int ret; + + ret = bmp180_wait_for_eoc(data, + FIELD_PREP(BMP180_MEAS_CTRL_MASK, BMP180_MEAS_PRESS) | + FIELD_PREP(BMP180_OSRS_PRESS_MASK, oss) | + BMP180_MEAS_SCO); + if (ret) + return ret; + + ret = regmap_bulk_read(data->regmap, BMP180_REG_OUT_MSB, + data->buf, BMP280_NUM_PRESS_BYTES); + if (ret) { + dev_err(data->dev, "failed to read pressure\n"); + return ret; + } + + *adc_press = get_unaligned_be24(data->buf) >> (8 - oss); + + return 0; +} + +/* + * Returns pressure in Pa, resolution is 1 Pa. + * + * Taken from datasheet, Section 3.5, "Calculating pressure and temperature". + */ +static u32 bmp180_compensate_press(struct bmp280_data *data, u32 adc_press, + s32 t_fine) +{ + struct bmp180_calib *calib = &data->calib.bmp180; + s32 oss = data->oversampling_press; + s32 x1, x2, x3, p; + s32 b3, b6; + u32 b4, b7; + + b6 = t_fine - 4000; + x1 = (calib->B2 * (b6 * b6 >> 12)) >> 11; + x2 = calib->AC2 * b6 >> 11; + x3 = x1 + x2; + b3 = ((((s32)calib->AC1 * 4 + x3) << oss) + 2) / 4; + x1 = calib->AC3 * b6 >> 13; + x2 = (calib->B1 * ((b6 * b6) >> 12)) >> 16; + x3 = (x1 + x2 + 2) >> 2; + b4 = calib->AC4 * (u32)(x3 + 32768) >> 15; + b7 = (adc_press - b3) * (50000 >> oss); + if (b7 < 0x80000000) + p = (b7 * 2) / b4; + else + p = (b7 / b4) * 2; + + x1 = (p >> 8) * (p >> 8); + x1 = (x1 * 3038) >> 16; + x2 = (-7357 * p) >> 16; + + return p + ((x1 + x2 + 3791) >> 4); +} + +static int bmp180_read_press(struct bmp280_data *data, u32 *comp_press) +{ + u32 adc_press; + s32 t_fine; + int ret; + + ret = bmp180_get_t_fine(data, &t_fine); + if (ret) + return ret; + + ret = bmp180_read_press_adc(data, &adc_press); + if (ret) + return ret; + + *comp_press = bmp180_compensate_press(data, adc_press, t_fine); + + return 0; +} + +/* Keep compatibility with newer generations of the sensor */ +static int bmp180_set_mode(struct bmp280_data *data, enum bmp280_op_mode mode) +{ + return 0; +} + +/* Keep compatibility with newer generations of the sensor */ +static int bmp180_wait_conv(struct bmp280_data *data) +{ + return 0; +} + +/* Keep compatibility with newer generations of the sensor */ +static int bmp180_chip_config(struct bmp280_data *data) +{ + return 0; +} + +static irqreturn_t bmp180_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bmp280_data *data = iio_priv(indio_dev); + struct { + u32 comp_press; + s32 comp_temp; + aligned_s64 timestamp; + } buffer; + int ret; + + guard(mutex)(&data->lock); + + ret = bmp180_read_temp(data, &buffer.comp_temp); + if (ret) + goto out; + + + ret = bmp180_read_press(data, &buffer.comp_press); + if (ret) + goto out; + + iio_push_to_buffers_with_ts(indio_dev, &buffer, sizeof(buffer), + iio_get_time_ns(indio_dev)); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const int bmp180_oversampling_temp_avail[] = { 1 }; +static const int bmp180_oversampling_press_avail[] = { 1, 2, 4, 8 }; +static const u8 bmp180_chip_ids[] = { BMP180_CHIP_ID }; +static const int bmp180_temp_coeffs[] = { 100, 1 }; +static const int bmp180_press_coeffs[] = { 1, 1000 }; + +const struct bmp280_chip_info bmp180_chip_info = { + .id_reg = BMP280_REG_ID, + .chip_id = bmp180_chip_ids, + .num_chip_id = ARRAY_SIZE(bmp180_chip_ids), + .regmap_config = &bmp180_regmap_config, + .start_up_time_us = 2000, + .channels = bmp280_channels, + .num_channels = ARRAY_SIZE(bmp280_channels), + .avail_scan_masks = bmp280_avail_scan_masks, + + .oversampling_temp_avail = bmp180_oversampling_temp_avail, + .num_oversampling_temp_avail = + ARRAY_SIZE(bmp180_oversampling_temp_avail), + .oversampling_temp_default = 0, + + .oversampling_press_avail = bmp180_oversampling_press_avail, + .num_oversampling_press_avail = + ARRAY_SIZE(bmp180_oversampling_press_avail), + .oversampling_press_default = BMP180_MEAS_PRESS_8X, + + .temp_coeffs = bmp180_temp_coeffs, + .temp_coeffs_type = IIO_VAL_FRACTIONAL, + .press_coeffs = bmp180_press_coeffs, + .press_coeffs_type = IIO_VAL_FRACTIONAL, + + .chip_config = bmp180_chip_config, + .read_temp = bmp180_read_temp, + .read_press = bmp180_read_press, + .read_calib = bmp180_read_calib, + .set_mode = bmp180_set_mode, + .wait_conv = bmp180_wait_conv, + + .trigger_handler = bmp180_trigger_handler, +}; +EXPORT_SYMBOL_NS(bmp180_chip_info, "IIO_BMP280"); + +static irqreturn_t bmp085_eoc_irq(int irq, void *d) +{ + struct bmp280_data *data = d; + + complete(&data->done); + + return IRQ_HANDLED; +} + +static int bmp085_trigger_probe(struct iio_dev *indio_dev) +{ + struct bmp280_data *data = iio_priv(indio_dev); + struct device *dev = data->dev; + unsigned long irq_trig; + int ret, irq; + + irq = fwnode_irq_get(dev_fwnode(dev), 0); + if (irq < 0) + return dev_err_probe(dev, irq, "No interrupt found.\n"); + + irq_trig = irq_get_trigger_type(irq); + if (irq_trig != IRQF_TRIGGER_RISING) { + dev_err(dev, "non-rising trigger given for EOC interrupt, trying to enforce it\n"); + irq_trig = IRQF_TRIGGER_RISING; + } + + init_completion(&data->done); + + ret = devm_request_irq(dev, irq, bmp085_eoc_irq, irq_trig, + indio_dev->name, data); + if (ret) { + /* Bail out without IRQ but keep the driver in place */ + dev_err(dev, "unable to request DRDY IRQ\n"); + return 0; + } + + data->use_eoc = true; + + return 0; +} + +/* Identical to bmp180_chip_info + bmp085_trigger_probe */ +const struct bmp280_chip_info bmp085_chip_info = { + .id_reg = BMP280_REG_ID, + .chip_id = bmp180_chip_ids, + .num_chip_id = ARRAY_SIZE(bmp180_chip_ids), + .regmap_config = &bmp180_regmap_config, + .start_up_time_us = 2000, + .channels = bmp280_channels, + .num_channels = ARRAY_SIZE(bmp280_channels), + .avail_scan_masks = bmp280_avail_scan_masks, + + .oversampling_temp_avail = bmp180_oversampling_temp_avail, + .num_oversampling_temp_avail = + ARRAY_SIZE(bmp180_oversampling_temp_avail), + .oversampling_temp_default = 0, + + .oversampling_press_avail = bmp180_oversampling_press_avail, + .num_oversampling_press_avail = + ARRAY_SIZE(bmp180_oversampling_press_avail), + .oversampling_press_default = BMP180_MEAS_PRESS_8X, + + .temp_coeffs = bmp180_temp_coeffs, + .temp_coeffs_type = IIO_VAL_FRACTIONAL, + .press_coeffs = bmp180_press_coeffs, + .press_coeffs_type = IIO_VAL_FRACTIONAL, + + .chip_config = bmp180_chip_config, + .read_temp = bmp180_read_temp, + .read_press = bmp180_read_press, + .read_calib = bmp180_read_calib, + .set_mode = bmp180_set_mode, + .wait_conv = bmp180_wait_conv, + + .trigger_probe = bmp085_trigger_probe, + .trigger_handler = bmp180_trigger_handler, +}; +EXPORT_SYMBOL_NS(bmp085_chip_info, "IIO_BMP280"); + +static int bmp280_buffer_preenable(struct iio_dev *indio_dev) +{ + struct bmp280_data *data = iio_priv(indio_dev); + int ret; + + ret = pm_runtime_resume_and_get(data->dev); + if (ret < 0) + return ret; + + ret = data->chip_info->set_mode(data, BMP280_NORMAL); + if (ret) + pm_runtime_put_autosuspend(data->dev); + + return ret; +} + +static int bmp280_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct bmp280_data *data = iio_priv(indio_dev); + + pm_runtime_put_autosuspend(data->dev); + + return 0; +} + +static const struct iio_buffer_setup_ops bmp280_buffer_setup_ops = { + .preenable = bmp280_buffer_preenable, + .postdisable = bmp280_buffer_postdisable, +}; + +static void bmp280_pm_disable(void *data) +{ + struct device *dev = data; + + pm_runtime_get_sync(dev); + pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); +} + +static void bmp280_regulators_disable(void *data) +{ + struct regulator_bulk_data *supplies = data; + + regulator_bulk_disable(BMP280_NUM_SUPPLIES, supplies); +} + +int bmp280_common_probe(struct device *dev, + struct regmap *regmap, + const struct bmp280_chip_info *chip_info, + const char *name, + int irq) +{ + struct iio_dev *indio_dev; + struct bmp280_data *data; + struct gpio_desc *gpiod; + unsigned int chip_id; + unsigned int i; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + mutex_init(&data->lock); + data->dev = dev; + + indio_dev->name = name; + indio_dev->info = &bmp280_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + data->chip_info = chip_info; + + /* Apply initial values from chip info structure */ + indio_dev->channels = chip_info->channels; + indio_dev->num_channels = chip_info->num_channels; + indio_dev->available_scan_masks = chip_info->avail_scan_masks; + data->oversampling_press = chip_info->oversampling_press_default; + data->oversampling_humid = chip_info->oversampling_humid_default; + data->oversampling_temp = chip_info->oversampling_temp_default; + data->iir_filter_coeff = chip_info->iir_filter_coeff_default; + data->sampling_freq = chip_info->sampling_freq_default; + data->start_up_time_us = chip_info->start_up_time_us; + + /* Bring up regulators */ + regulator_bulk_set_supply_names(data->supplies, + bmp280_supply_names, + BMP280_NUM_SUPPLIES); + + ret = devm_regulator_bulk_get(dev, + BMP280_NUM_SUPPLIES, data->supplies); + if (ret) { + dev_err(dev, "failed to get regulators\n"); + return ret; + } + + ret = regulator_bulk_enable(BMP280_NUM_SUPPLIES, data->supplies); + if (ret) { + dev_err(dev, "failed to enable regulators\n"); + return ret; + } + + ret = devm_add_action_or_reset(dev, bmp280_regulators_disable, + data->supplies); + if (ret) + return ret; + + /* Wait to make sure we started up properly */ + fsleep(data->start_up_time_us); + + /* Bring chip out of reset if there is an assigned GPIO line */ + gpiod = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(gpiod)) + return dev_err_probe(dev, PTR_ERR(gpiod), "failed to get reset GPIO\n"); + + /* Deassert the signal */ + gpiod_set_value_cansleep(gpiod, 0); + + data->regmap = regmap; + + ret = regmap_read(regmap, data->chip_info->id_reg, &chip_id); + if (ret) { + dev_err(data->dev, "failed to read chip id\n"); + return ret; + } + + for (i = 0; i < data->chip_info->num_chip_id; i++) { + if (chip_id == data->chip_info->chip_id[i]) { + dev_info(dev, "0x%x is a known chip id for %s\n", chip_id, name); + break; + } + } + + if (i == data->chip_info->num_chip_id) + dev_warn(dev, "bad chip id: 0x%x is not a known chip id\n", chip_id); + + if (data->chip_info->preinit) { + ret = data->chip_info->preinit(data); + if (ret) + return dev_err_probe(data->dev, ret, + "error running preinit tasks\n"); + } + + ret = data->chip_info->chip_config(data); + if (ret) + return ret; + + dev_set_drvdata(dev, indio_dev); + + /* + * Some chips have calibration parameters "programmed into the devices' + * non-volatile memory during production". Let's read them out at probe + * time once. They will not change. + */ + + if (data->chip_info->read_calib) { + ret = data->chip_info->read_calib(data); + if (ret) + return dev_err_probe(data->dev, ret, + "failed to read calibration coefficients\n"); + } + + ret = devm_iio_triggered_buffer_setup(data->dev, indio_dev, + iio_pollfunc_store_time, + data->chip_info->trigger_handler, + &bmp280_buffer_setup_ops); + if (ret) + return dev_err_probe(data->dev, ret, + "iio triggered buffer setup failed\n"); + + /* + * Attempt to grab an optional EOC IRQ - only the BMP085 has this + * however as it happens, the BMP085 shares the chip ID of BMP180 + * so we look for an IRQ if we have that. + */ + if (irq > 0) { + if (data->chip_info->trigger_probe) + ret = data->chip_info->trigger_probe(indio_dev); + if (ret) + return ret; + } + + ret = data->chip_info->set_mode(data, BMP280_SLEEP); + if (ret) + return dev_err_probe(dev, ret, "Failed to set sleep mode\n"); + + /* Enable runtime PM */ + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + /* + * Set autosuspend to two orders of magnitude larger than the + * start-up time. + */ + pm_runtime_set_autosuspend_delay(dev, data->start_up_time_us / 10); + pm_runtime_use_autosuspend(dev); + pm_runtime_put(dev); + + ret = devm_add_action_or_reset(dev, bmp280_pm_disable, dev); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS(bmp280_common_probe, "IIO_BMP280"); + +static int bmp280_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmp280_data *data = iio_priv(indio_dev); + + data->chip_info->set_mode(data, BMP280_SLEEP); + + fsleep(data->start_up_time_us); + return regulator_bulk_disable(BMP280_NUM_SUPPLIES, data->supplies); +} + +static int bmp280_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmp280_data *data = iio_priv(indio_dev); + int ret; + + ret = regulator_bulk_enable(BMP280_NUM_SUPPLIES, data->supplies); + if (ret) + return ret; + + fsleep(data->start_up_time_us); + + ret = data->chip_info->chip_config(data); + if (ret) + return ret; + + return data->chip_info->set_mode(data, data->op_mode); +} + +EXPORT_RUNTIME_DEV_PM_OPS(bmp280_dev_pm_ops, bmp280_runtime_suspend, + bmp280_runtime_resume, NULL); + +MODULE_AUTHOR("Vlad Dogaru "); +MODULE_DESCRIPTION("Driver for Bosch Sensortec BMP180/BMP280 pressure and temperature sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/bmp280-i2c.c b/drivers/iio/pressure/bmp280-i2c.c new file mode 100644 index 000000000..3f6e0723a --- /dev/null +++ b/drivers/iio/pressure/bmp280-i2c.c @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include + +#include "bmp280.h" + +static int bmp280_i2c_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + const struct bmp280_chip_info *chip_info; + struct regmap *regmap; + + chip_info = i2c_get_match_data(client); + + regmap = devm_regmap_init_i2c(client, chip_info->regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "failed to allocate register map\n"); + return PTR_ERR(regmap); + } + + return bmp280_common_probe(&client->dev, + regmap, + chip_info, + id->name, + client->irq); +} + +static const struct of_device_id bmp280_of_i2c_match[] = { + { .compatible = "bosch,bmp085", .data = &bmp085_chip_info }, + { .compatible = "bosch,bmp180", .data = &bmp180_chip_info }, + { .compatible = "bosch,bmp280", .data = &bmp280_chip_info }, + { .compatible = "bosch,bme280", .data = &bme280_chip_info }, + { .compatible = "bosch,bmp380", .data = &bmp380_chip_info }, + { .compatible = "bosch,bmp580", .data = &bmp580_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, bmp280_of_i2c_match); + +static const struct i2c_device_id bmp280_i2c_id[] = { + { .name = "bmp085", .driver_data = (kernel_ulong_t)&bmp085_chip_info }, + { .name = "bmp180", .driver_data = (kernel_ulong_t)&bmp180_chip_info }, + { .name = "bmp280", .driver_data = (kernel_ulong_t)&bmp280_chip_info }, + { .name = "bme280", .driver_data = (kernel_ulong_t)&bme280_chip_info }, + { .name = "bmp380", .driver_data = (kernel_ulong_t)&bmp380_chip_info }, + { .name = "bmp580", .driver_data = (kernel_ulong_t)&bmp580_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bmp280_i2c_id); + +static struct i2c_driver bmp280_i2c_driver = { + .driver = { + .name = "bmp280", + .of_match_table = bmp280_of_i2c_match, + .pm = pm_ptr(&bmp280_dev_pm_ops), + }, + .probe = bmp280_i2c_probe, + .id_table = bmp280_i2c_id, +}; +module_i2c_driver(bmp280_i2c_driver); + +MODULE_AUTHOR("Vlad Dogaru "); +MODULE_DESCRIPTION("Driver for Bosch Sensortec BMP180/BMP280 pressure and temperature sensor"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_BMP280"); diff --git a/drivers/iio/pressure/bmp280-regmap.c b/drivers/iio/pressure/bmp280-regmap.c new file mode 100644 index 000000000..b6a7b417c --- /dev/null +++ b/drivers/iio/pressure/bmp280-regmap.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include + +#include "bmp280.h" + +static bool bmp180_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMP280_REG_CTRL_MEAS: + case BMP280_REG_RESET: + return true; + default: + return false; + } +} + +static bool bmp180_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMP180_REG_OUT_XLSB: + case BMP180_REG_OUT_LSB: + case BMP180_REG_OUT_MSB: + case BMP280_REG_CTRL_MEAS: + return true; + default: + return false; + } +} + +const struct regmap_config bmp180_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = BMP180_REG_OUT_XLSB, + .cache_type = REGCACHE_RBTREE, + + .writeable_reg = bmp180_is_writeable_reg, + .volatile_reg = bmp180_is_volatile_reg, +}; +EXPORT_SYMBOL_NS(bmp180_regmap_config, "IIO_BMP280"); + +static bool bme280_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMP280_REG_CONFIG: + case BME280_REG_CTRL_HUMIDITY: + case BMP280_REG_CTRL_MEAS: + case BMP280_REG_RESET: + return true; + default: + return false; + } +} + +static bool bmp280_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMP280_REG_CONFIG: + case BMP280_REG_CTRL_MEAS: + case BMP280_REG_RESET: + return true; + default: + return false; + } +} + +static bool bmp280_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMP280_REG_TEMP_XLSB: + case BMP280_REG_TEMP_LSB: + case BMP280_REG_TEMP_MSB: + case BMP280_REG_PRESS_XLSB: + case BMP280_REG_PRESS_LSB: + case BMP280_REG_PRESS_MSB: + case BMP280_REG_STATUS: + return true; + default: + return false; + } +} + +static bool bme280_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BME280_REG_HUMIDITY_LSB: + case BME280_REG_HUMIDITY_MSB: + case BMP280_REG_TEMP_XLSB: + case BMP280_REG_TEMP_LSB: + case BMP280_REG_TEMP_MSB: + case BMP280_REG_PRESS_XLSB: + case BMP280_REG_PRESS_LSB: + case BMP280_REG_PRESS_MSB: + case BMP280_REG_STATUS: + return true; + default: + return false; + } +} +static bool bmp380_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMP380_REG_CMD: + case BMP380_REG_CONFIG: + case BMP380_REG_FIFO_CONFIG_1: + case BMP380_REG_FIFO_CONFIG_2: + case BMP380_REG_FIFO_WATERMARK_LSB: + case BMP380_REG_FIFO_WATERMARK_MSB: + case BMP380_REG_POWER_CONTROL: + case BMP380_REG_INT_CONTROL: + case BMP380_REG_IF_CONFIG: + case BMP380_REG_ODR: + case BMP380_REG_OSR: + return true; + default: + return false; + } +} + +static bool bmp380_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMP380_REG_TEMP_XLSB: + case BMP380_REG_TEMP_LSB: + case BMP380_REG_TEMP_MSB: + case BMP380_REG_PRESS_XLSB: + case BMP380_REG_PRESS_LSB: + case BMP380_REG_PRESS_MSB: + case BMP380_REG_SENSOR_TIME_XLSB: + case BMP380_REG_SENSOR_TIME_LSB: + case BMP380_REG_SENSOR_TIME_MSB: + case BMP380_REG_INT_STATUS: + case BMP380_REG_FIFO_DATA: + case BMP380_REG_STATUS: + case BMP380_REG_ERROR: + case BMP380_REG_EVENT: + return true; + default: + return false; + } +} + +static bool bmp580_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMP580_REG_NVM_DATA_MSB: + case BMP580_REG_NVM_DATA_LSB: + case BMP580_REG_NVM_ADDR: + case BMP580_REG_ODR_CONFIG: + case BMP580_REG_OSR_CONFIG: + case BMP580_REG_INT_SOURCE: + case BMP580_REG_INT_CONFIG: + case BMP580_REG_OOR_THR_MSB: + case BMP580_REG_OOR_THR_LSB: + case BMP580_REG_OOR_CONFIG: + case BMP580_REG_OOR_RANGE: + case BMP580_REG_IF_CONFIG: + case BMP580_REG_FIFO_CONFIG: + case BMP580_REG_FIFO_SEL: + case BMP580_REG_DSP_CONFIG: + case BMP580_REG_DSP_IIR: + case BMP580_REG_CMD: + return true; + default: + return false; + } +} + +static bool bmp580_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMP580_REG_NVM_DATA_MSB: + case BMP580_REG_NVM_DATA_LSB: + case BMP580_REG_FIFO_COUNT: + case BMP580_REG_INT_STATUS: + case BMP580_REG_PRESS_XLSB: + case BMP580_REG_PRESS_LSB: + case BMP580_REG_PRESS_MSB: + case BMP580_REG_FIFO_DATA: + case BMP580_REG_TEMP_XLSB: + case BMP580_REG_TEMP_LSB: + case BMP580_REG_TEMP_MSB: + case BMP580_REG_EFF_OSR: + case BMP580_REG_STATUS: + return true; + default: + return false; + } +} + +const struct regmap_config bmp280_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = BMP280_REG_TEMP_XLSB, + .cache_type = REGCACHE_RBTREE, + + .writeable_reg = bmp280_is_writeable_reg, + .volatile_reg = bmp280_is_volatile_reg, +}; +EXPORT_SYMBOL_NS(bmp280_regmap_config, "IIO_BMP280"); + +const struct regmap_config bme280_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = BME280_REG_HUMIDITY_LSB, + .cache_type = REGCACHE_RBTREE, + + .writeable_reg = bme280_is_writeable_reg, + .volatile_reg = bme280_is_volatile_reg, +}; +EXPORT_SYMBOL_NS(bme280_regmap_config, "IIO_BMP280"); + +const struct regmap_config bmp380_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = BMP380_REG_CMD, + .cache_type = REGCACHE_RBTREE, + + .writeable_reg = bmp380_is_writeable_reg, + .volatile_reg = bmp380_is_volatile_reg, +}; +EXPORT_SYMBOL_NS(bmp380_regmap_config, "IIO_BMP280"); + +const struct regmap_config bmp580_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = BMP580_REG_CMD, + .cache_type = REGCACHE_RBTREE, + + .writeable_reg = bmp580_is_writeable_reg, + .volatile_reg = bmp580_is_volatile_reg, +}; +EXPORT_SYMBOL_NS(bmp580_regmap_config, "IIO_BMP280"); diff --git a/drivers/iio/pressure/bmp280-spi.c b/drivers/iio/pressure/bmp280-spi.c new file mode 100644 index 000000000..a02a621b8 --- /dev/null +++ b/drivers/iio/pressure/bmp280-spi.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * SPI interface for the BMP280 driver + * + * Inspired by the older BMP085 driver drivers/misc/bmp085-spi.c + */ +#include +#include +#include +#include +#include + +#include "bmp280.h" + +static int bmp280_regmap_spi_write(void *context, const void *data, + size_t count) +{ + struct spi_device *spi = to_spi_device(context); + u8 buf[2]; + + memcpy(buf, data, 2); + /* + * The SPI register address (= full register address without bit 7) and + * the write command (bit7 = RW = '0') + */ + buf[0] &= ~0x80; + + return spi_write_then_read(spi, buf, 2, NULL, 0); +} + +static int bmp280_regmap_spi_read(void *context, const void *reg, + size_t reg_size, void *val, size_t val_size) +{ + struct spi_device *spi = to_spi_device(context); + + return spi_write_then_read(spi, reg, reg_size, val, val_size); +} + +static int bmp380_regmap_spi_read(void *context, const void *reg, + size_t reg_size, void *val, size_t val_size) +{ + struct spi_device *spi = to_spi_device(context); + u8 rx_buf[BME280_BURST_READ_BYTES + 1]; + ssize_t status; + + if (val_size > BME280_BURST_READ_BYTES) + return -EINVAL; + + /* + * According to the BMP3xx datasheets, for a basic SPI read operation, + * the first byte needs to be dropped and the rest are the requested + * data. + */ + status = spi_write_then_read(spi, reg, 1, rx_buf, val_size + 1); + if (status) + return status; + + memcpy(val, rx_buf + 1, val_size); + + return 0; +} + +static const struct regmap_bus bmp280_regmap_bus = { + .write = bmp280_regmap_spi_write, + .read = bmp280_regmap_spi_read, + .reg_format_endian_default = REGMAP_ENDIAN_BIG, + .val_format_endian_default = REGMAP_ENDIAN_BIG, +}; + +static const struct regmap_bus bmp380_regmap_bus = { + .write = bmp280_regmap_spi_write, + .read = bmp380_regmap_spi_read, + .read_flag_mask = BIT(7), + .reg_format_endian_default = REGMAP_ENDIAN_BIG, + .val_format_endian_default = REGMAP_ENDIAN_BIG, +}; + +static int bmp280_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + const struct bmp280_chip_info *chip_info; + struct regmap_bus const *bmp_regmap_bus; + struct regmap *regmap; + + chip_info = spi_get_device_match_data(spi); + + if (chip_info->spi_read_extra_byte) + bmp_regmap_bus = &bmp380_regmap_bus; + else + bmp_regmap_bus = &bmp280_regmap_bus; + + regmap = devm_regmap_init(&spi->dev, + bmp_regmap_bus, + &spi->dev, + chip_info->regmap_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "failed to allocate register map\n"); + return PTR_ERR(regmap); + } + + return bmp280_common_probe(&spi->dev, + regmap, + chip_info, + id->name, + spi->irq); +} + +static const struct of_device_id bmp280_of_spi_match[] = { + { .compatible = "bosch,bmp085", .data = &bmp085_chip_info }, + { .compatible = "bosch,bmp180", .data = &bmp180_chip_info }, + { .compatible = "bosch,bmp181", .data = &bmp180_chip_info }, + { .compatible = "bosch,bmp280", .data = &bmp280_chip_info }, + { .compatible = "bosch,bme280", .data = &bme280_chip_info }, + { .compatible = "bosch,bmp380", .data = &bmp380_chip_info }, + { .compatible = "bosch,bmp580", .data = &bmp580_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, bmp280_of_spi_match); + +static const struct spi_device_id bmp280_spi_id[] = { + { .name = "bmp085", .driver_data = (kernel_ulong_t)&bmp085_chip_info }, + { .name = "bmp180", .driver_data = (kernel_ulong_t)&bmp180_chip_info }, + { .name = "bmp181", .driver_data = (kernel_ulong_t)&bmp180_chip_info }, + { .name = "bmp280", .driver_data = (kernel_ulong_t)&bmp280_chip_info }, + { .name = "bme280", .driver_data = (kernel_ulong_t)&bme280_chip_info }, + { .name = "bmp380", .driver_data = (kernel_ulong_t)&bmp380_chip_info }, + { .name = "bmp580", .driver_data = (kernel_ulong_t)&bmp580_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, bmp280_spi_id); + +static struct spi_driver bmp280_spi_driver = { + .driver = { + .name = "bmp280", + .of_match_table = bmp280_of_spi_match, + .pm = pm_ptr(&bmp280_dev_pm_ops), + }, + .id_table = bmp280_spi_id, + .probe = bmp280_spi_probe, +}; +module_spi_driver(bmp280_spi_driver); + +MODULE_DESCRIPTION("BMP280 SPI bus driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_BMP280"); diff --git a/drivers/iio/pressure/bmp280.h b/drivers/iio/pressure/bmp280.h new file mode 100644 index 000000000..25bb9c743 --- /dev/null +++ b/drivers/iio/pressure/bmp280.h @@ -0,0 +1,551 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include +#include +#include +#include + +#include + +/* BMP580 specific registers */ +#define BMP580_REG_CMD 0x7E +#define BMP580_REG_EFF_OSR 0x38 +#define BMP580_REG_ODR_CONFIG 0x37 +#define BMP580_REG_OSR_CONFIG 0x36 +#define BMP580_REG_IF_CONFIG 0x13 +#define BMP580_REG_REV_ID 0x02 +#define BMP580_REG_CHIP_ID 0x01 +/* OOR allows to configure a pressure alarm */ +#define BMP580_REG_OOR_CONFIG 0x35 +#define BMP580_REG_OOR_RANGE 0x34 +#define BMP580_REG_OOR_THR_MSB 0x33 +#define BMP580_REG_OOR_THR_LSB 0x32 +/* DSP registers (IIR filters) */ +#define BMP580_REG_DSP_IIR 0x31 +#define BMP580_REG_DSP_CONFIG 0x30 +/* NVM access registers */ +#define BMP580_REG_NVM_DATA_MSB 0x2D +#define BMP580_REG_NVM_DATA_LSB 0x2C +#define BMP580_REG_NVM_ADDR 0x2B +/* Status registers */ +#define BMP580_REG_STATUS 0x28 +#define BMP580_REG_INT_STATUS 0x27 +#define BMP580_REG_CHIP_STATUS 0x11 +/* Data registers */ +#define BMP580_REG_FIFO_DATA 0x29 +#define BMP580_REG_PRESS_MSB 0x22 +#define BMP580_REG_PRESS_LSB 0x21 +#define BMP580_REG_PRESS_XLSB 0x20 +#define BMP580_REG_TEMP_MSB 0x1F +#define BMP580_REG_TEMP_LSB 0x1E +#define BMP580_REG_TEMP_XLSB 0x1D +/* FIFO config registers */ +#define BMP580_REG_FIFO_SEL 0x18 +#define BMP580_REG_FIFO_COUNT 0x17 +#define BMP580_REG_FIFO_CONFIG 0x16 +/* Interruptions config registers */ +#define BMP580_REG_INT_SOURCE 0x15 +#define BMP580_REG_INT_CONFIG 0x14 + +#define BMP580_CMD_NOOP 0x00 +#define BMP580_CMD_EXTMODE_SEQ_0 0x73 +#define BMP580_CMD_EXTMODE_SEQ_1 0xB4 +#define BMP580_CMD_EXTMODE_SEQ_2 0x69 +#define BMP580_CMD_NVM_OP_SEQ_0 0x5D +#define BMP580_CMD_NVM_READ_SEQ_1 0xA5 +#define BMP580_CMD_NVM_WRITE_SEQ_1 0xA0 +#define BMP580_CMD_SOFT_RESET 0xB6 + +#define BMP580_INT_STATUS_DRDY_MASK BIT(0) +#define BMP580_INT_STATUS_POR_MASK BIT(4) + +#define BMP580_INT_SOURCE_DRDY BIT(0) + +#define BMP580_INT_CONFIG_MASK GENMASK(3, 0) +#define BMP580_INT_CONFIG_LATCH BIT(0) +#define BMP580_INT_CONFIG_LEVEL BIT(1) +#define BMP580_INT_CONFIG_OPEN_DRAIN BIT(2) +#define BMP580_INT_CONFIG_INT_EN BIT(3) + +#define BMP580_STATUS_CORE_RDY_MASK BIT(0) +#define BMP580_STATUS_NVM_RDY_MASK BIT(1) +#define BMP580_STATUS_NVM_ERR_MASK BIT(2) +#define BMP580_STATUS_NVM_CMD_ERR_MASK BIT(3) + +#define BMP580_OSR_PRESS_MASK GENMASK(5, 3) +#define BMP580_OSR_TEMP_MASK GENMASK(2, 0) +#define BMP580_OSR_PRESS_EN BIT(6) +#define BMP580_EFF_OSR_PRESS_MASK GENMASK(5, 3) +#define BMP580_EFF_OSR_TEMP_MASK GENMASK(2, 0) +#define BMP580_EFF_OSR_VALID_ODR BIT(7) + +#define BMP580_ODR_MASK GENMASK(6, 2) +#define BMP580_MODE_MASK GENMASK(1, 0) +#define BMP580_MODE_SLEEP 0 +#define BMP580_MODE_NORMAL 1 +#define BMP580_MODE_FORCED 2 +#define BMP580_MODE_CONTINOUS 3 +#define BMP580_ODR_DEEPSLEEP_DIS BIT(7) + +#define BMP580_DSP_COMP_MASK GENMASK(1, 0) +#define BMP580_DSP_COMP_DIS 0 +#define BMP580_DSP_TEMP_COMP_EN 1 +/* + * In section 7.27 of datasheet, modes 2 and 3 are technically the same. + * Pressure compensation means also enabling temperature compensation + */ +#define BMP580_DSP_PRESS_COMP_EN 2 +#define BMP580_DSP_PRESS_TEMP_COMP_EN 3 +#define BMP580_DSP_IIR_FORCED_FLUSH BIT(2) +#define BMP580_DSP_SHDW_IIR_TEMP_EN BIT(3) +#define BMP580_DSP_FIFO_IIR_TEMP_EN BIT(4) +#define BMP580_DSP_SHDW_IIR_PRESS_EN BIT(5) +#define BMP580_DSP_FIFO_IIR_PRESS_EN BIT(6) +#define BMP580_DSP_OOR_IIR_PRESS_EN BIT(7) + +#define BMP580_DSP_IIR_PRESS_MASK GENMASK(5, 3) +#define BMP580_DSP_IIR_TEMP_MASK GENMASK(2, 0) +#define BMP580_FILTER_OFF 0 +#define BMP580_FILTER_1X 1 +#define BMP580_FILTER_3X 2 +#define BMP580_FILTER_7X 3 +#define BMP580_FILTER_15X 4 +#define BMP580_FILTER_31X 5 +#define BMP580_FILTER_63X 6 +#define BMP580_FILTER_127X 7 + +#define BMP580_NVM_ROW_ADDR_MASK GENMASK(5, 0) +#define BMP580_NVM_PROG_EN BIT(6) + +#define BMP580_TEMP_SKIPPED 0x7f7f7f +#define BMP580_PRESS_SKIPPED 0x7f7f7f + +/* BMP380 specific registers */ +#define BMP380_REG_CMD 0x7E +#define BMP380_REG_CONFIG 0x1F +#define BMP380_REG_ODR 0x1D +#define BMP380_REG_OSR 0x1C +#define BMP380_REG_POWER_CONTROL 0x1B +#define BMP380_REG_IF_CONFIG 0x1A +#define BMP380_REG_INT_CONTROL 0x19 +#define BMP380_REG_INT_STATUS 0x11 +#define BMP380_REG_EVENT 0x10 +#define BMP380_REG_STATUS 0x03 +#define BMP380_REG_ERROR 0x02 +#define BMP380_REG_ID 0x00 + +#define BMP380_REG_FIFO_CONFIG_1 0x18 +#define BMP380_REG_FIFO_CONFIG_2 0x17 +#define BMP380_REG_FIFO_WATERMARK_MSB 0x16 +#define BMP380_REG_FIFO_WATERMARK_LSB 0x15 +#define BMP380_REG_FIFO_DATA 0x14 +#define BMP380_REG_FIFO_LENGTH_MSB 0x13 +#define BMP380_REG_FIFO_LENGTH_LSB 0x12 + +#define BMP380_REG_SENSOR_TIME_MSB 0x0E +#define BMP380_REG_SENSOR_TIME_LSB 0x0D +#define BMP380_REG_SENSOR_TIME_XLSB 0x0C + +#define BMP380_REG_TEMP_MSB 0x09 +#define BMP380_REG_TEMP_LSB 0x08 +#define BMP380_REG_TEMP_XLSB 0x07 + +#define BMP380_REG_PRESS_MSB 0x06 +#define BMP380_REG_PRESS_LSB 0x05 +#define BMP380_REG_PRESS_XLSB 0x04 + +#define BMP380_REG_CALIB_TEMP_START 0x31 +#define BMP380_CALIB_REG_COUNT 21 + +#define BMP380_FILTER_MASK GENMASK(3, 1) +#define BMP380_FILTER_OFF 0 +#define BMP380_FILTER_1X 1 +#define BMP380_FILTER_3X 2 +#define BMP380_FILTER_7X 3 +#define BMP380_FILTER_15X 4 +#define BMP380_FILTER_31X 5 +#define BMP380_FILTER_63X 6 +#define BMP380_FILTER_127X 7 + +#define BMP380_OSRS_TEMP_MASK GENMASK(5, 3) +#define BMP380_OSRS_PRESS_MASK GENMASK(2, 0) + +#define BMP380_ODRS_MASK GENMASK(4, 0) + +#define BMP380_CTRL_SENSORS_MASK GENMASK(1, 0) +#define BMP380_CTRL_SENSORS_PRESS_EN BIT(0) +#define BMP380_CTRL_SENSORS_TEMP_EN BIT(1) +#define BMP380_MODE_MASK GENMASK(5, 4) +#define BMP380_MODE_SLEEP 0 +#define BMP380_MODE_FORCED 1 +#define BMP380_MODE_NORMAL 3 + +#define BMP380_MEAS_OFFSET 234 +#define BMP380_MEAS_DUR 2020 +#define BMP380_TEMP_MEAS_OFFSET 163 +#define BMP380_PRESS_MEAS_OFFSET 392 + +#define BMP380_INT_STATUS_DRDY BIT(3) + +#define BMP380_INT_CTRL_SETTINGS_MASK GENMASK(2, 0) +#define BMP380_INT_CTRL_OPEN_DRAIN BIT(0) +#define BMP380_INT_CTRL_LEVEL BIT(1) +#define BMP380_INT_CTRL_LATCH BIT(2) +#define BMP380_INT_CTRL_DRDY_EN BIT(6) + +#define BMP380_MIN_TEMP -4000 +#define BMP380_MAX_TEMP 8500 +#define BMP380_MIN_PRES 3000000 +#define BMP380_MAX_PRES 12500000 + +#define BMP380_CMD_NOOP 0x00 +#define BMP380_CMD_EXTMODE_EN_MID 0x34 +#define BMP380_CMD_FIFO_FLUSH 0xB0 +#define BMP380_CMD_SOFT_RESET 0xB6 + +#define BMP380_STATUS_CMD_RDY_MASK BIT(4) +#define BMP380_STATUS_DRDY_PRESS_MASK BIT(5) +#define BMP380_STATUS_DRDY_TEMP_MASK BIT(6) + +#define BMP380_ERR_FATAL_MASK BIT(0) +#define BMP380_ERR_CMD_MASK BIT(1) +#define BMP380_ERR_CONF_MASK BIT(2) + +#define BMP380_TEMP_SKIPPED 0x800000 +#define BMP380_PRESS_SKIPPED 0x800000 + +/* BMP280 specific registers */ +#define BMP280_REG_TEMP_XLSB 0xFC +#define BMP280_REG_TEMP_LSB 0xFB +#define BMP280_REG_TEMP_MSB 0xFA +#define BMP280_REG_PRESS_XLSB 0xF9 +#define BMP280_REG_PRESS_LSB 0xF8 +#define BMP280_REG_PRESS_MSB 0xF7 + +/* Helper mask to truncate excess 4 bits on pressure and temp readings */ +#define BMP280_MEAS_TRIM_MASK GENMASK(24, 4) + +#define BMP280_REG_CONFIG 0xF5 +#define BMP280_REG_CTRL_MEAS 0xF4 +#define BMP280_REG_STATUS 0xF3 +#define BMP280_REG_STATUS_IM_UPDATE BIT(0) +#define BMP280_REG_STATUS_MEAS_BIT BIT(3) +#define BMP280_REG_RESET 0xE0 +#define BMP280_RST_SOFT_CMD 0xB6 + +#define BMP280_REG_COMP_TEMP_START 0x88 +#define BMP280_COMP_TEMP_REG_COUNT 6 + +#define BMP280_REG_COMP_PRESS_START 0x8E +#define BMP280_COMP_PRESS_REG_COUNT 18 + +#define BMP280_CONTIGUOUS_CALIB_REGS (BMP280_COMP_TEMP_REG_COUNT + \ + BMP280_COMP_PRESS_REG_COUNT) + +#define BMP280_FILTER_MASK GENMASK(4, 2) +#define BMP280_FILTER_OFF 0 +#define BMP280_FILTER_2X 1 +#define BMP280_FILTER_4X 2 +#define BMP280_FILTER_8X 3 +#define BMP280_FILTER_16X 4 + +#define BMP280_OSRS_TEMP_MASK GENMASK(7, 5) +#define BMP280_OSRS_TEMP_SKIP 0 +#define BMP280_OSRS_TEMP_1X 1 +#define BMP280_OSRS_TEMP_2X 2 +#define BMP280_OSRS_TEMP_4X 3 +#define BMP280_OSRS_TEMP_8X 4 +#define BMP280_OSRS_TEMP_16X 5 + +#define BMP280_OSRS_PRESS_MASK GENMASK(4, 2) +#define BMP280_OSRS_PRESS_SKIP 0 +#define BMP280_OSRS_PRESS_1X 1 +#define BMP280_OSRS_PRESS_2X 2 +#define BMP280_OSRS_PRESS_4X 3 +#define BMP280_OSRS_PRESS_8X 4 +#define BMP280_OSRS_PRESS_16X 5 + +#define BMP280_MODE_MASK GENMASK(1, 0) +#define BMP280_MODE_SLEEP 0 +#define BMP280_MODE_FORCED 1 +#define BMP280_MODE_NORMAL 3 + +#define BMP280_MEAS_OFFSET 1250 +#define BMP280_MEAS_DUR 2300 +#define BMP280_PRESS_HUMID_MEAS_OFFSET 575 + +/* BME280 specific registers */ +#define BME280_REG_HUMIDITY_LSB 0xFE +#define BME280_REG_HUMIDITY_MSB 0xFD + +#define BME280_REG_CTRL_HUMIDITY 0xF2 + +/* Due to non linear mapping, and data sizes we can't do a bulk read */ +#define BME280_REG_COMP_H1 0xA1 +#define BME280_REG_COMP_H2 0xE1 +#define BME280_REG_COMP_H3 0xE3 +#define BME280_REG_COMP_H4 0xE4 +#define BME280_REG_COMP_H5 0xE5 +#define BME280_REG_COMP_H6 0xE7 + +#define BME280_COMP_H4_GET_MASK_UP GENMASK(15, 8) +#define BME280_COMP_H4_PREP_MASK_UP GENMASK(11, 4) +#define BME280_COMP_H4_MASK_LOW GENMASK(3, 0) +#define BME280_COMP_H5_MASK GENMASK(15, 4) + +#define BME280_CONTIGUOUS_CALIB_REGS 7 + +#define BME280_OSRS_HUMIDITY_MASK GENMASK(2, 0) +#define BME280_OSRS_HUMIDITY_SKIP 0 +#define BME280_OSRS_HUMIDITY_1X 1 +#define BME280_OSRS_HUMIDITY_2X 2 +#define BME280_OSRS_HUMIDITY_4X 3 +#define BME280_OSRS_HUMIDITY_8X 4 +#define BME280_OSRS_HUMIDITY_16X 5 + +/* BMP180 specific registers */ +#define BMP180_REG_OUT_XLSB 0xF8 +#define BMP180_REG_OUT_LSB 0xF7 +#define BMP180_REG_OUT_MSB 0xF6 + +#define BMP180_REG_CALIB_START 0xAA +#define BMP180_REG_CALIB_COUNT 22 + +#define BMP180_MEAS_CTRL_MASK GENMASK(4, 0) +#define BMP180_MEAS_TEMP 0x0E +#define BMP180_MEAS_PRESS 0x14 +#define BMP180_MEAS_SCO BIT(5) +#define BMP180_OSRS_PRESS_MASK GENMASK(7, 6) +#define BMP180_MEAS_PRESS_1X 0 +#define BMP180_MEAS_PRESS_2X 1 +#define BMP180_MEAS_PRESS_4X 2 +#define BMP180_MEAS_PRESS_8X 3 + +/* BMP180 and BMP280 common registers */ +#define BMP280_REG_CTRL_MEAS 0xF4 +#define BMP280_REG_RESET 0xE0 +#define BMP280_REG_ID 0xD0 + +#define BMP380_CHIP_ID 0x50 +#define BMP580_CHIP_ID 0x50 +#define BMP580_CHIP_ID_ALT 0x51 +#define BMP180_CHIP_ID 0x55 +#define BMP280_CHIP_ID 0x58 +#define BMP390_CHIP_ID 0x60 +#define BME280_CHIP_ID 0x60 +#define BMP280_SOFT_RESET_VAL 0xB6 + +/* BMP280 register skipped special values */ +#define BMP280_TEMP_SKIPPED 0x80000 +#define BMP280_PRESS_SKIPPED 0x80000 +#define BMP280_HUMIDITY_SKIPPED 0x8000 + +/* Number of bytes for each value */ +#define BMP280_NUM_PRESS_BYTES 3 +#define BMP280_NUM_TEMP_BYTES 3 +#define BME280_NUM_HUMIDITY_BYTES 2 +#define BMP280_BURST_READ_BYTES (BMP280_NUM_PRESS_BYTES + \ + BMP280_NUM_TEMP_BYTES) +#define BME280_BURST_READ_BYTES (BMP280_NUM_PRESS_BYTES + \ + BMP280_NUM_TEMP_BYTES + \ + BME280_NUM_HUMIDITY_BYTES) + +/* Core exported structs */ + +static const char *const bmp280_supply_names[] = { + "vddd", "vdda" +}; + +#define BMP280_NUM_SUPPLIES ARRAY_SIZE(bmp280_supply_names) + +struct bmp180_calib { + s16 AC1; + s16 AC2; + s16 AC3; + u16 AC4; + u16 AC5; + u16 AC6; + s16 B1; + s16 B2; + s16 MB; + s16 MC; + s16 MD; +}; + +/* See datasheet Section 4.2.2. */ +struct bmp280_calib { + u16 T1; + s16 T2; + s16 T3; + u16 P1; + s16 P2; + s16 P3; + s16 P4; + s16 P5; + s16 P6; + s16 P7; + s16 P8; + s16 P9; + u8 H1; + s16 H2; + u8 H3; + s16 H4; + s16 H5; + s8 H6; +}; + +/* See datasheet Section 3.11.1. */ +struct bmp380_calib { + u16 T1; + u16 T2; + s8 T3; + s16 P1; + s16 P2; + s8 P3; + s8 P4; + u16 P5; + u16 P6; + s8 P7; + s8 P8; + s16 P9; + s8 P10; + s8 P11; +}; + +enum bmp280_op_mode { + BMP280_SLEEP, + BMP280_FORCED, + BMP280_NORMAL, +}; + +struct bmp280_data { + struct device *dev; + struct mutex lock; + struct regmap *regmap; + struct completion done; + bool use_eoc; + bool trig_open_drain; + bool trig_active_high; + struct iio_trigger *trig; + const struct bmp280_chip_info *chip_info; + union { + struct bmp180_calib bmp180; + struct bmp280_calib bmp280; + struct bmp380_calib bmp380; + } calib; + struct regulator_bulk_data supplies[BMP280_NUM_SUPPLIES]; + unsigned int start_up_time_us; + + /* log of base 2 of oversampling rate */ + u8 oversampling_press; + u8 oversampling_temp; + u8 oversampling_humid; + u8 iir_filter_coeff; + + /* + * BMP380 devices introduce sampling frequency configuration. See + * datasheet sections 3.3.3. and 4.3.19 for more details. + * + * BMx280 devices allowed indirect configuration of sampling frequency + * changing the t_standby duration between measurements, as detailed on + * section 3.6.3 of the datasheet. + */ + int sampling_freq; + + /* Value to hold the current operation mode of the device */ + enum bmp280_op_mode op_mode; + + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + */ + union { + /* Sensor data buffer */ + u8 buf[BME280_BURST_READ_BYTES]; + /* Calibration data buffers */ + __le16 bmp280_cal_buf[BMP280_CONTIGUOUS_CALIB_REGS / sizeof(__le16)]; + __be16 bmp180_cal_buf[BMP180_REG_CALIB_COUNT / sizeof(__be16)]; + u8 bme280_humid_cal_buf[BME280_CONTIGUOUS_CALIB_REGS]; + u8 bmp380_cal_buf[BMP380_CALIB_REG_COUNT]; + /* Miscellaneous, endianness-aware data buffers */ + __le16 le16; + __be16 be16; + } __aligned(IIO_DMA_MINALIGN); +}; + +struct bmp280_chip_info { + unsigned int id_reg; + const u8 *chip_id; + int num_chip_id; + + const struct regmap_config *regmap_config; + bool spi_read_extra_byte; + + const struct iio_chan_spec *channels; + int num_channels; + unsigned int start_up_time_us; + const unsigned long *avail_scan_masks; + + const int *oversampling_temp_avail; + int num_oversampling_temp_avail; + int oversampling_temp_default; + + const int *oversampling_press_avail; + int num_oversampling_press_avail; + int oversampling_press_default; + + const int *oversampling_humid_avail; + int num_oversampling_humid_avail; + int oversampling_humid_default; + + const int *iir_filter_coeffs_avail; + int num_iir_filter_coeffs_avail; + int iir_filter_coeff_default; + + const int (*sampling_freq_avail)[2]; + int num_sampling_freq_avail; + int sampling_freq_default; + + const int *temp_coeffs; + const int temp_coeffs_type; + const int *press_coeffs; + const int press_coeffs_type; + const int *humid_coeffs; + const int humid_coeffs_type; + + int (*chip_config)(struct bmp280_data *data); + int (*read_temp)(struct bmp280_data *data, s32 *adc_temp); + int (*read_press)(struct bmp280_data *data, u32 *adc_press); + int (*read_humid)(struct bmp280_data *data, u32 *adc_humidity); + int (*read_calib)(struct bmp280_data *data); + int (*preinit)(struct bmp280_data *data); + int (*set_mode)(struct bmp280_data *data, enum bmp280_op_mode mode); + int (*wait_conv)(struct bmp280_data *data); + + int (*trigger_probe)(struct iio_dev *indio_dev); + irqreturn_t (*trigger_handler)(int irq, void *p); +}; + +/* Chip infos for each variant */ +extern const struct bmp280_chip_info bmp085_chip_info; +extern const struct bmp280_chip_info bmp180_chip_info; +extern const struct bmp280_chip_info bmp280_chip_info; +extern const struct bmp280_chip_info bme280_chip_info; +extern const struct bmp280_chip_info bmp380_chip_info; +extern const struct bmp280_chip_info bmp580_chip_info; + +/* Regmap configurations */ +extern const struct regmap_config bmp180_regmap_config; +extern const struct regmap_config bmp280_regmap_config; +extern const struct regmap_config bme280_regmap_config; +extern const struct regmap_config bmp380_regmap_config; +extern const struct regmap_config bmp580_regmap_config; + +/* Probe called from different transports */ +int bmp280_common_probe(struct device *dev, + struct regmap *regmap, + const struct bmp280_chip_info *chip_info, + const char *name, + int irq); + +/* PM ops */ +extern const struct dev_pm_ops bmp280_dev_pm_ops; diff --git a/drivers/iio/pressure/cros_ec_baro.c b/drivers/iio/pressure/cros_ec_baro.c new file mode 100644 index 000000000..6a567b607 --- /dev/null +++ b/drivers/iio/pressure/cros_ec_baro.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * cros_ec_baro - Driver for barometer sensor behind CrosEC. + * + * Copyright (C) 2017 Google, Inc + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * One channel for pressure, the other for timestamp. + */ +#define CROS_EC_BARO_MAX_CHANNELS (1 + 1) + +/* State data for ec_sensors iio driver. */ +struct cros_ec_baro_state { + /* Shared by all sensors */ + struct cros_ec_sensors_core_state core; + + struct iio_chan_spec channels[CROS_EC_BARO_MAX_CHANNELS]; +}; + +static int cros_ec_baro_read(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cros_ec_baro_state *st = iio_priv(indio_dev); + u16 data = 0; + int ret; + int idx = chan->scan_index; + + mutex_lock(&st->core.cmd_lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = cros_ec_sensors_read_cmd(indio_dev, 1 << idx, + (s16 *)&data); + if (ret) + break; + + *val = data; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE; + st->core.param.sensor_range.data = EC_MOTION_SENSE_NO_VALUE; + + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + if (ret) + break; + + *val = st->core.resp->sensor_range.ret; + + /* scale * in_pressure_raw --> kPa */ + *val2 = 10 << CROS_EC_SENSOR_BITS; + ret = IIO_VAL_FRACTIONAL; + break; + default: + ret = cros_ec_sensors_core_read(&st->core, chan, val, val2, + mask); + break; + } + + mutex_unlock(&st->core.cmd_lock); + + return ret; +} + +static int cros_ec_baro_write(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct cros_ec_baro_state *st = iio_priv(indio_dev); + int ret = 0; + + mutex_lock(&st->core.cmd_lock); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE; + st->core.param.sensor_range.data = val; + + /* Always roundup, so caller gets at least what it asks for. */ + st->core.param.sensor_range.roundup = 1; + + ret = cros_ec_motion_send_host_cmd(&st->core, 0); + if (ret == 0) { + st->core.range_updated = true; + st->core.curr_range = val; + } + break; + default: + ret = cros_ec_sensors_core_write(&st->core, chan, val, val2, + mask); + break; + } + + mutex_unlock(&st->core.cmd_lock); + + return ret; +} + +static const struct iio_info cros_ec_baro_info = { + .read_raw = &cros_ec_baro_read, + .write_raw = &cros_ec_baro_write, + .read_avail = &cros_ec_sensors_core_read_avail, +}; + +static int cros_ec_baro_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent); + struct iio_dev *indio_dev; + struct cros_ec_baro_state *state; + struct iio_chan_spec *channel; + int ret; + + if (!ec_dev || !ec_dev->ec_dev) { + dev_warn(dev, "No CROS EC device found.\n"); + return -EINVAL; + } + + indio_dev = devm_iio_device_alloc(dev, sizeof(*state)); + if (!indio_dev) + return -ENOMEM; + + ret = cros_ec_sensors_core_init(pdev, indio_dev, true, + cros_ec_sensors_capture); + if (ret) + return ret; + + indio_dev->info = &cros_ec_baro_info; + state = iio_priv(indio_dev); + channel = state->channels; + /* Common part */ + channel->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); + channel->info_mask_shared_by_all = + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ); + channel->info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_SAMP_FREQ); + channel->scan_type.realbits = CROS_EC_SENSOR_BITS; + channel->scan_type.storagebits = CROS_EC_SENSOR_BITS; + channel->scan_type.shift = 0; + channel->scan_index = 0; + channel->ext_info = cros_ec_sensors_ext_info; + channel->scan_type.sign = 'u'; + + /* Sensor specific */ + switch (state->core.type) { + case MOTIONSENSE_TYPE_BARO: + channel->type = IIO_PRESSURE; + break; + default: + dev_warn(dev, "Unknown motion sensor\n"); + return -EINVAL; + } + + channel++; + *channel = IIO_CHAN_SOFT_TIMESTAMP(1); + + indio_dev->channels = state->channels; + indio_dev->num_channels = CROS_EC_BARO_MAX_CHANNELS; + + state->core.read_ec_sensors_data = cros_ec_sensors_read_cmd; + + return cros_ec_sensors_core_register(dev, indio_dev, + cros_ec_sensors_push_data); +} + +static const struct platform_device_id cros_ec_baro_ids[] = { + { + .name = "cros-ec-baro", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, cros_ec_baro_ids); + +static struct platform_driver cros_ec_baro_platform_driver = { + .driver = { + .name = "cros-ec-baro", + .pm = &cros_ec_sensors_pm_ops, + }, + .probe = cros_ec_baro_probe, + .id_table = cros_ec_baro_ids, +}; +module_platform_driver(cros_ec_baro_platform_driver); + +MODULE_DESCRIPTION("ChromeOS EC barometer sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/dlhl60d.c b/drivers/iio/pressure/dlhl60d.c new file mode 100644 index 000000000..961888fd0 --- /dev/null +++ b/drivers/iio/pressure/dlhl60d.c @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * All Sensors DLH series low voltage digital pressure sensors + * + * Copyright (c) 2019 AVL DiTEST GmbH + * Tomislav Denis + * + * Datasheet: https://www.allsensors.com/cad/DS-0355_Rev_B.PDF + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Commands */ +#define DLH_START_SINGLE 0xAA + +/* Status bits */ +#define DLH_STATUS_OK 0x40 + +/* DLH data format */ +#define DLH_NUM_READ_BYTES 7 +#define DLH_NUM_DATA_BYTES 3 +#define DLH_NUM_PR_BITS 24 +#define DLH_NUM_TEMP_BITS 24 + +/* DLH timings */ +#define DLH_SINGLE_DUT_MS 5 + +struct dlh_info { + const char *name; /* chip name */ + u8 osdig; /* digital offset factor */ + unsigned int fss; /* full scale span (inch H2O) */ +}; + +struct dlh_state { + struct i2c_client *client; + const struct dlh_info *info; + bool use_interrupt; + struct completion completion; + u8 rx_buf[DLH_NUM_READ_BYTES]; +}; + +static const struct dlh_info dlhl60d_info = { + .name = "dlhl60d", + .osdig = 2, + .fss = 120, +}; + +static const struct dlh_info dlhl60g_info = { + .name = "dlhl60g", + .osdig = 10, + .fss = 60, +}; + +static int dlh_cmd_start_single(struct dlh_state *st) +{ + int ret; + + ret = i2c_smbus_write_byte(st->client, DLH_START_SINGLE); + if (ret) + dev_err(&st->client->dev, + "%s: I2C write byte failed\n", __func__); + + return ret; +} + +static int dlh_cmd_read_data(struct dlh_state *st) +{ + int ret; + + ret = i2c_master_recv(st->client, st->rx_buf, DLH_NUM_READ_BYTES); + if (ret < 0) { + dev_err(&st->client->dev, + "%s: I2C read block failed\n", __func__); + return ret; + } + + if (st->rx_buf[0] != DLH_STATUS_OK) { + dev_err(&st->client->dev, + "%s: invalid status 0x%02x\n", __func__, st->rx_buf[0]); + return -EBUSY; + } + + return 0; +} + +static int dlh_start_capture_and_read(struct dlh_state *st) +{ + int ret; + + if (st->use_interrupt) + reinit_completion(&st->completion); + + ret = dlh_cmd_start_single(st); + if (ret) + return ret; + + if (st->use_interrupt) { + ret = wait_for_completion_timeout(&st->completion, + msecs_to_jiffies(DLH_SINGLE_DUT_MS)); + if (!ret) { + dev_err(&st->client->dev, + "%s: conversion timed out\n", __func__); + return -ETIMEDOUT; + } + } else { + mdelay(DLH_SINGLE_DUT_MS); + } + + return dlh_cmd_read_data(st); +} + +static int dlh_read_direct(struct dlh_state *st, + unsigned int *pressure, unsigned int *temperature) +{ + int ret; + + ret = dlh_start_capture_and_read(st); + if (ret) + return ret; + + *pressure = get_unaligned_be24(&st->rx_buf[1]); + *temperature = get_unaligned_be24(&st->rx_buf[4]); + + return 0; +} + +static int dlh_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *value, + int *value2, long mask) +{ + struct dlh_state *st = iio_priv(indio_dev); + unsigned int pressure, temperature; + int ret; + s64 tmp; + s32 rem; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = dlh_read_direct(st, &pressure, &temperature); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + + switch (channel->type) { + case IIO_PRESSURE: + *value = pressure; + return IIO_VAL_INT; + + case IIO_TEMP: + *value = temperature; + return IIO_VAL_INT; + + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (channel->type) { + case IIO_PRESSURE: + tmp = div_s64(125LL * st->info->fss * 24909 * 100, + 1 << DLH_NUM_PR_BITS); + tmp = div_s64_rem(tmp, 1000000000LL, &rem); + *value = tmp; + *value2 = rem; + return IIO_VAL_INT_PLUS_NANO; + + case IIO_TEMP: + *value = 125 * 1000; + *value2 = DLH_NUM_TEMP_BITS; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (channel->type) { + case IIO_PRESSURE: + *value = -125 * st->info->fss * 24909; + *value2 = 100 * st->info->osdig * 100000; + return IIO_VAL_FRACTIONAL; + + case IIO_TEMP: + *value = -40 * 1000; + return IIO_VAL_INT; + + default: + return -EINVAL; + } + } + + return -EINVAL; +} + +static const struct iio_info dlh_info = { + .read_raw = dlh_read_raw, +}; + +static const struct iio_chan_spec dlh_channels[] = { + { + .type = IIO_PRESSURE, + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = DLH_NUM_PR_BITS, + .storagebits = 32, + .shift = 8, + .endianness = IIO_BE, + }, + }, { + .type = IIO_TEMP, + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = 1, + .scan_type = { + .sign = 'u', + .realbits = DLH_NUM_TEMP_BITS, + .storagebits = 32, + .shift = 8, + .endianness = IIO_BE, + }, + } +}; + +static irqreturn_t dlh_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct dlh_state *st = iio_priv(indio_dev); + int ret; + unsigned int chn, i = 0; + __be32 tmp_buf[2] = { }; + + ret = dlh_start_capture_and_read(st); + if (ret) + goto out; + + iio_for_each_active_channel(indio_dev, chn) { + memcpy(&tmp_buf[i++], + &st->rx_buf[1] + chn * DLH_NUM_DATA_BYTES, + DLH_NUM_DATA_BYTES); + } + + iio_push_to_buffers(indio_dev, tmp_buf); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static irqreturn_t dlh_interrupt(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct dlh_state *st = iio_priv(indio_dev); + + complete(&st->completion); + + return IRQ_HANDLED; +}; + +static int dlh_probe(struct i2c_client *client) +{ + struct dlh_state *st; + struct iio_dev *indio_dev; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_I2C | I2C_FUNC_SMBUS_WRITE_BYTE)) { + dev_err(&client->dev, + "adapter doesn't support required i2c functionality\n"); + return -EOPNOTSUPP; + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + i2c_set_clientdata(client, indio_dev); + + st = iio_priv(indio_dev); + st->info = i2c_get_match_data(client); + st->client = client; + st->use_interrupt = false; + + indio_dev->name = st->info->name; + indio_dev->info = &dlh_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = dlh_channels; + indio_dev->num_channels = ARRAY_SIZE(dlh_channels); + + if (client->irq > 0) { + ret = devm_request_irq(&client->dev, client->irq, dlh_interrupt, + IRQF_TRIGGER_RISING | IRQF_NO_THREAD, + st->info->name, indio_dev); + if (ret) + return ret; + + st->use_interrupt = true; + init_completion(&st->completion); + } + + ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, + NULL, &dlh_trigger_handler, NULL); + if (ret) { + dev_err(&client->dev, "failed to setup iio buffer\n"); + return ret; + } + + ret = devm_iio_device_register(&client->dev, indio_dev); + if (ret) + dev_err(&client->dev, "failed to register iio device\n"); + + return ret; +} + +static const struct of_device_id dlh_of_match[] = { + { .compatible = "asc,dlhl60d", .data = &dlhl60d_info }, + { .compatible = "asc,dlhl60g", .data = &dlhl60g_info }, + { } +}; +MODULE_DEVICE_TABLE(of, dlh_of_match); + +static const struct i2c_device_id dlh_id[] = { + { .name = "dlhl60d", .driver_data = (kernel_ulong_t)&dlhl60d_info }, + { .name = "dlhl60g", .driver_data = (kernel_ulong_t)&dlhl60g_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, dlh_id); + +static struct i2c_driver dlh_driver = { + .driver = { + .name = "dlhl60d", + .of_match_table = dlh_of_match, + }, + .probe = dlh_probe, + .id_table = dlh_id, +}; +module_i2c_driver(dlh_driver); + +MODULE_AUTHOR("Tomislav Denis "); +MODULE_DESCRIPTION("Driver for All Sensors DLH series pressure sensors"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/dps310.c b/drivers/iio/pressure/dps310.c new file mode 100644 index 000000000..45bdb8c76 --- /dev/null +++ b/drivers/iio/pressure/dps310.c @@ -0,0 +1,912 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright IBM Corp 2019 +/* + * The DPS310 is a barometric pressure and temperature sensor. + * Currently only reading a single temperature is supported by + * this driver. + * + * https://www.infineon.com/dgdl/?fileId=5546d462576f34750157750826c42242 + * + * Temperature calculation: + * c0 * 0.5 + c1 * T_raw / kT °C + * + * TODO: + * - Optionally support the FIFO + */ + +#include +#include +#include +#include +#include + +#include +#include + +#define DPS310_DEV_NAME "dps310" + +#define DPS310_PRS_B0 0x00 +#define DPS310_PRS_B1 0x01 +#define DPS310_PRS_B2 0x02 +#define DPS310_TMP_B0 0x03 +#define DPS310_TMP_B1 0x04 +#define DPS310_TMP_B2 0x05 +#define DPS310_PRS_CFG 0x06 +#define DPS310_PRS_RATE_BITS GENMASK(6, 4) +#define DPS310_PRS_PRC_BITS GENMASK(3, 0) +#define DPS310_TMP_CFG 0x07 +#define DPS310_TMP_RATE_BITS GENMASK(6, 4) +#define DPS310_TMP_PRC_BITS GENMASK(3, 0) +#define DPS310_TMP_EXT BIT(7) +#define DPS310_MEAS_CFG 0x08 +#define DPS310_MEAS_CTRL_BITS GENMASK(2, 0) +#define DPS310_PRS_EN BIT(0) +#define DPS310_TEMP_EN BIT(1) +#define DPS310_BACKGROUND BIT(2) +#define DPS310_PRS_RDY BIT(4) +#define DPS310_TMP_RDY BIT(5) +#define DPS310_SENSOR_RDY BIT(6) +#define DPS310_COEF_RDY BIT(7) +#define DPS310_CFG_REG 0x09 +#define DPS310_INT_HL BIT(7) +#define DPS310_TMP_SHIFT_EN BIT(3) +#define DPS310_PRS_SHIFT_EN BIT(4) +#define DPS310_FIFO_EN BIT(5) +#define DPS310_SPI_EN BIT(6) +#define DPS310_RESET 0x0c +#define DPS310_RESET_MAGIC 0x09 +#define DPS310_COEF_BASE 0x10 + +/* Make sure sleep time is <= 30ms for usleep_range */ +#define DPS310_POLL_SLEEP_US(t) min(30000, (t) / 8) +/* Silently handle error in rate value here */ +#define DPS310_POLL_TIMEOUT_US(rc) ((rc) <= 0 ? 1000000 : 1000000 / (rc)) + +#define DPS310_PRS_BASE DPS310_PRS_B0 +#define DPS310_TMP_BASE DPS310_TMP_B0 + +/* + * These values (defined in the spec) indicate how to scale the raw register + * values for each level of precision available. + */ +static const int scale_factors[] = { + 524288, + 1572864, + 3670016, + 7864320, + 253952, + 516096, + 1040384, + 2088960, +}; + +struct dps310_data { + struct i2c_client *client; + struct regmap *regmap; + struct mutex lock; /* Lock for sequential HW access functions */ + + s32 c0, c1; + s32 c00, c10, c20, c30, c01, c11, c21; + s32 pressure_raw; + s32 temp_raw; + bool timeout_recovery_failed; +}; + +static const struct iio_chan_spec dps310_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_PROCESSED), + }, + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_PROCESSED), + }, +}; + +/* To be called after checking the COEF_RDY bit in MEAS_CFG */ +static int dps310_get_coefs(struct dps310_data *data) +{ + int rc; + u8 coef[18]; + u32 c0, c1; + u32 c00, c10, c20, c30, c01, c11, c21; + + /* Read all sensor calibration coefficients from the COEF registers. */ + rc = regmap_bulk_read(data->regmap, DPS310_COEF_BASE, coef, + sizeof(coef)); + if (rc < 0) + return rc; + + /* + * Calculate temperature calibration coefficients c0 and c1. The + * numbers are 12-bit 2's complement numbers. + */ + c0 = (coef[0] << 4) | (coef[1] >> 4); + data->c0 = sign_extend32(c0, 11); + + c1 = ((coef[1] & GENMASK(3, 0)) << 8) | coef[2]; + data->c1 = sign_extend32(c1, 11); + + /* + * Calculate pressure calibration coefficients. c00 and c10 are 20 bit + * 2's complement numbers, while the rest are 16 bit 2's complement + * numbers. + */ + c00 = (coef[3] << 12) | (coef[4] << 4) | (coef[5] >> 4); + data->c00 = sign_extend32(c00, 19); + + c10 = ((coef[5] & GENMASK(3, 0)) << 16) | (coef[6] << 8) | coef[7]; + data->c10 = sign_extend32(c10, 19); + + c01 = (coef[8] << 8) | coef[9]; + data->c01 = sign_extend32(c01, 15); + + c11 = (coef[10] << 8) | coef[11]; + data->c11 = sign_extend32(c11, 15); + + c20 = (coef[12] << 8) | coef[13]; + data->c20 = sign_extend32(c20, 15); + + c21 = (coef[14] << 8) | coef[15]; + data->c21 = sign_extend32(c21, 15); + + c30 = (coef[16] << 8) | coef[17]; + data->c30 = sign_extend32(c30, 15); + + return 0; +} + +/* + * Some versions of the chip will read temperatures in the ~60C range when + * it's actually ~20C. This is the manufacturer recommended workaround + * to correct the issue. The registers used below are undocumented. + */ +static int dps310_temp_workaround(struct dps310_data *data) +{ + int rc; + int reg; + + rc = regmap_read(data->regmap, 0x32, ®); + if (rc < 0) + return rc; + + /* + * If bit 1 is set then the device is okay, and the workaround does not + * need to be applied + */ + if (reg & BIT(1)) + return 0; + + rc = regmap_write(data->regmap, 0x0e, 0xA5); + if (rc) + return rc; + + rc = regmap_write(data->regmap, 0x0f, 0x96); + if (rc) + return rc; + + rc = regmap_write(data->regmap, 0x62, 0x02); + if (rc) + return rc; + + rc = regmap_write(data->regmap, 0x0e, 0x00); + if (rc) + return rc; + + return regmap_write(data->regmap, 0x0f, 0x00); +} + +static int dps310_startup(struct dps310_data *data) +{ + int rc; + int ready; + + /* + * Set up pressure sensor in single sample, one measurement per second + * mode + */ + rc = regmap_write(data->regmap, DPS310_PRS_CFG, 0); + if (rc) + return rc; + + /* + * Set up external (MEMS) temperature sensor in single sample, one + * measurement per second mode + */ + rc = regmap_write(data->regmap, DPS310_TMP_CFG, DPS310_TMP_EXT); + if (rc) + return rc; + + /* Temp and pressure shifts are disabled when PRC <= 8 */ + rc = regmap_write_bits(data->regmap, DPS310_CFG_REG, + DPS310_PRS_SHIFT_EN | DPS310_TMP_SHIFT_EN, 0); + if (rc) + return rc; + + /* MEAS_CFG doesn't update correctly unless first written with 0 */ + rc = regmap_write_bits(data->regmap, DPS310_MEAS_CFG, + DPS310_MEAS_CTRL_BITS, 0); + if (rc) + return rc; + + /* Turn on temperature and pressure measurement in the background */ + rc = regmap_write_bits(data->regmap, DPS310_MEAS_CFG, + DPS310_MEAS_CTRL_BITS, DPS310_PRS_EN | + DPS310_TEMP_EN | DPS310_BACKGROUND); + if (rc) + return rc; + + /* + * Calibration coefficients required for reporting temperature. + * They are available 40ms after the device has started + */ + rc = regmap_read_poll_timeout(data->regmap, DPS310_MEAS_CFG, ready, + ready & DPS310_COEF_RDY, 10000, 40000); + if (rc) + return rc; + + rc = dps310_get_coefs(data); + if (rc) + return rc; + + return dps310_temp_workaround(data); +} + +static int dps310_get_pres_precision(struct dps310_data *data, int *val) +{ + int reg_val, rc; + + rc = regmap_read(data->regmap, DPS310_PRS_CFG, ®_val); + if (rc < 0) + return rc; + + *val = BIT(reg_val & GENMASK(2, 0)); + + return 0; +} + +static int dps310_get_temp_precision(struct dps310_data *data, int *val) +{ + int reg_val, rc; + + rc = regmap_read(data->regmap, DPS310_TMP_CFG, ®_val); + if (rc < 0) + return rc; + + /* + * Scale factor is bottom 4 bits of the register, but 1111 is + * reserved so just grab bottom three + */ + *val = BIT(reg_val & GENMASK(2, 0)); + + return 0; +} + +/* Called with lock held */ +static int dps310_set_pres_precision(struct dps310_data *data, int val) +{ + int rc; + u8 shift_en; + + if (val < 0 || val > 128) + return -EINVAL; + + shift_en = val >= 16 ? DPS310_PRS_SHIFT_EN : 0; + rc = regmap_write_bits(data->regmap, DPS310_CFG_REG, + DPS310_PRS_SHIFT_EN, shift_en); + if (rc) + return rc; + + return regmap_update_bits(data->regmap, DPS310_PRS_CFG, + DPS310_PRS_PRC_BITS, ilog2(val)); +} + +/* Called with lock held */ +static int dps310_set_temp_precision(struct dps310_data *data, int val) +{ + int rc; + u8 shift_en; + + if (val < 0 || val > 128) + return -EINVAL; + + shift_en = val >= 16 ? DPS310_TMP_SHIFT_EN : 0; + rc = regmap_write_bits(data->regmap, DPS310_CFG_REG, + DPS310_TMP_SHIFT_EN, shift_en); + if (rc) + return rc; + + return regmap_update_bits(data->regmap, DPS310_TMP_CFG, + DPS310_TMP_PRC_BITS, ilog2(val)); +} + +/* Called with lock held */ +static int dps310_set_pres_samp_freq(struct dps310_data *data, int freq) +{ + u8 val; + + if (freq < 0 || freq > 128) + return -EINVAL; + + val = ilog2(freq) << 4; + + return regmap_update_bits(data->regmap, DPS310_PRS_CFG, + DPS310_PRS_RATE_BITS, val); +} + +/* Called with lock held */ +static int dps310_set_temp_samp_freq(struct dps310_data *data, int freq) +{ + u8 val; + + if (freq < 0 || freq > 128) + return -EINVAL; + + val = ilog2(freq) << 4; + + return regmap_update_bits(data->regmap, DPS310_TMP_CFG, + DPS310_TMP_RATE_BITS, val); +} + +static int dps310_get_pres_samp_freq(struct dps310_data *data, int *val) +{ + int reg_val, rc; + + rc = regmap_read(data->regmap, DPS310_PRS_CFG, ®_val); + if (rc < 0) + return rc; + + *val = BIT((reg_val & DPS310_PRS_RATE_BITS) >> 4); + + return 0; +} + +static int dps310_get_temp_samp_freq(struct dps310_data *data, int *val) +{ + int reg_val, rc; + + rc = regmap_read(data->regmap, DPS310_TMP_CFG, ®_val); + if (rc < 0) + return rc; + + *val = BIT((reg_val & DPS310_TMP_RATE_BITS) >> 4); + + return 0; +} + +static int dps310_get_pres_k(struct dps310_data *data, int *val) +{ + int reg_val, rc; + + rc = regmap_read(data->regmap, DPS310_PRS_CFG, ®_val); + if (rc < 0) + return rc; + + *val = scale_factors[reg_val & GENMASK(2, 0)]; + + return 0; +} + +static int dps310_get_temp_k(struct dps310_data *data, int *val) +{ + int reg_val, rc; + + rc = regmap_read(data->regmap, DPS310_TMP_CFG, ®_val); + if (rc < 0) + return rc; + + *val = scale_factors[reg_val & GENMASK(2, 0)]; + + return 0; +} + +static int dps310_reset_wait(struct dps310_data *data) +{ + int rc; + + rc = regmap_write(data->regmap, DPS310_RESET, DPS310_RESET_MAGIC); + if (rc) + return rc; + + /* Wait for device chip access: 15ms in specification */ + usleep_range(15000, 55000); + return 0; +} + +static int dps310_reset_reinit(struct dps310_data *data) +{ + int rc; + + rc = dps310_reset_wait(data); + if (rc) + return rc; + + return dps310_startup(data); +} + +static int dps310_ready_status(struct dps310_data *data, int ready_bit, int timeout) +{ + int sleep = DPS310_POLL_SLEEP_US(timeout); + int ready; + + return regmap_read_poll_timeout(data->regmap, DPS310_MEAS_CFG, ready, ready & ready_bit, + sleep, timeout); +} + +static int dps310_ready(struct dps310_data *data, int ready_bit, int timeout) +{ + int rc; + + rc = dps310_ready_status(data, ready_bit, timeout); + if (rc) { + if (rc == -ETIMEDOUT && !data->timeout_recovery_failed) { + /* Reset and reinitialize the chip. */ + if (dps310_reset_reinit(data)) { + data->timeout_recovery_failed = true; + } else { + /* Try again to get sensor ready status. */ + if (dps310_ready_status(data, ready_bit, timeout)) + data->timeout_recovery_failed = true; + else + return 0; + } + } + + return rc; + } + + data->timeout_recovery_failed = false; + return 0; +} + +static int dps310_read_pres_raw(struct dps310_data *data) +{ + int rc; + int rate; + int timeout; + s32 raw; + u8 val[3]; + + if (mutex_lock_interruptible(&data->lock)) + return -EINTR; + + rc = dps310_get_pres_samp_freq(data, &rate); + if (rc) + goto done; + + timeout = DPS310_POLL_TIMEOUT_US(rate); + + /* Poll for sensor readiness; base the timeout upon the sample rate. */ + rc = dps310_ready(data, DPS310_PRS_RDY, timeout); + if (rc) + goto done; + + rc = regmap_bulk_read(data->regmap, DPS310_PRS_BASE, val, sizeof(val)); + if (rc < 0) + goto done; + + raw = (val[0] << 16) | (val[1] << 8) | val[2]; + data->pressure_raw = sign_extend32(raw, 23); + +done: + mutex_unlock(&data->lock); + return rc; +} + +/* Called with lock held */ +static int dps310_read_temp_ready(struct dps310_data *data) +{ + int rc; + u8 val[3]; + s32 raw; + + rc = regmap_bulk_read(data->regmap, DPS310_TMP_BASE, val, sizeof(val)); + if (rc < 0) + return rc; + + raw = (val[0] << 16) | (val[1] << 8) | val[2]; + data->temp_raw = sign_extend32(raw, 23); + + return 0; +} + +static int dps310_read_temp_raw(struct dps310_data *data) +{ + int rc; + int rate; + int timeout; + + if (mutex_lock_interruptible(&data->lock)) + return -EINTR; + + rc = dps310_get_temp_samp_freq(data, &rate); + if (rc) + goto done; + + timeout = DPS310_POLL_TIMEOUT_US(rate); + + /* Poll for sensor readiness; base the timeout upon the sample rate. */ + rc = dps310_ready(data, DPS310_TMP_RDY, timeout); + if (rc) + goto done; + + rc = dps310_read_temp_ready(data); + +done: + mutex_unlock(&data->lock); + return rc; +} + +static bool dps310_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case DPS310_PRS_CFG: + case DPS310_TMP_CFG: + case DPS310_MEAS_CFG: + case DPS310_CFG_REG: + case DPS310_RESET: + /* No documentation available on the registers below */ + case 0x0e: + case 0x0f: + case 0x62: + return true; + default: + return false; + } +} + +static bool dps310_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case DPS310_PRS_B0: + case DPS310_PRS_B1: + case DPS310_PRS_B2: + case DPS310_TMP_B0: + case DPS310_TMP_B1: + case DPS310_TMP_B2: + case DPS310_MEAS_CFG: + case 0x32: /* No documentation available on this register */ + return true; + default: + return false; + } +} + +static int dps310_write_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + int rc; + struct dps310_data *data = iio_priv(iio); + + if (mutex_lock_interruptible(&data->lock)) + return -EINTR; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + switch (chan->type) { + case IIO_PRESSURE: + rc = dps310_set_pres_samp_freq(data, val); + break; + + case IIO_TEMP: + rc = dps310_set_temp_samp_freq(data, val); + break; + + default: + rc = -EINVAL; + break; + } + break; + + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_PRESSURE: + rc = dps310_set_pres_precision(data, val); + break; + + case IIO_TEMP: + rc = dps310_set_temp_precision(data, val); + break; + + default: + rc = -EINVAL; + break; + } + break; + + default: + rc = -EINVAL; + break; + } + + mutex_unlock(&data->lock); + return rc; +} + +static int dps310_calculate_pressure(struct dps310_data *data, int *val) +{ + int i; + int rc; + int t_ready; + int kpi; + int kti; + s64 rem = 0ULL; + s64 pressure = 0ULL; + s64 p; + s64 t; + s64 denoms[7]; + s64 nums[7]; + s64 rems[7]; + s64 kp; + s64 kt; + + rc = dps310_get_pres_k(data, &kpi); + if (rc) + return rc; + + rc = dps310_get_temp_k(data, &kti); + if (rc) + return rc; + + kp = (s64)kpi; + kt = (s64)kti; + + /* Refresh temp if it's ready, otherwise just use the latest value */ + if (mutex_trylock(&data->lock)) { + rc = regmap_read(data->regmap, DPS310_MEAS_CFG, &t_ready); + if (rc >= 0 && t_ready & DPS310_TMP_RDY) + dps310_read_temp_ready(data); + + mutex_unlock(&data->lock); + } + + p = (s64)data->pressure_raw; + t = (s64)data->temp_raw; + + /* Section 4.9.1 of the DPS310 spec; algebra'd to avoid underflow */ + nums[0] = (s64)data->c00; + denoms[0] = 1LL; + nums[1] = p * (s64)data->c10; + denoms[1] = kp; + nums[2] = p * p * (s64)data->c20; + denoms[2] = kp * kp; + nums[3] = p * p * p * (s64)data->c30; + denoms[3] = kp * kp * kp; + nums[4] = t * (s64)data->c01; + denoms[4] = kt; + nums[5] = t * p * (s64)data->c11; + denoms[5] = kp * kt; + nums[6] = t * p * p * (s64)data->c21; + denoms[6] = kp * kp * kt; + + /* Kernel lacks a div64_s64_rem function; denoms are all positive */ + for (i = 0; i < 7; ++i) { + u64 irem; + + if (nums[i] < 0LL) { + pressure -= div64_u64_rem(-nums[i], denoms[i], &irem); + rems[i] = -irem; + } else { + pressure += div64_u64_rem(nums[i], denoms[i], &irem); + rems[i] = (s64)irem; + } + } + + /* Increase precision and calculate the remainder sum */ + for (i = 0; i < 7; ++i) + rem += div64_s64((s64)rems[i] * 1000000000LL, denoms[i]); + + pressure += div_s64(rem, 1000000000LL); + if (pressure < 0LL) + return -ERANGE; + + *val = (int)min_t(s64, pressure, INT_MAX); + + return 0; +} + +static int dps310_read_pressure(struct dps310_data *data, int *val, int *val2, + long mask) +{ + int rc; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + rc = dps310_get_pres_samp_freq(data, val); + if (rc) + return rc; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_PROCESSED: + rc = dps310_read_pres_raw(data); + if (rc) + return rc; + + rc = dps310_calculate_pressure(data, val); + if (rc) + return rc; + + *val2 = 1000; /* Convert Pa to KPa per IIO ABI */ + return IIO_VAL_FRACTIONAL; + + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + rc = dps310_get_pres_precision(data, val); + if (rc) + return rc; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int dps310_calculate_temp(struct dps310_data *data, int *val) +{ + s64 c0; + s64 t; + int kt, rc; + + rc = dps310_get_temp_k(data, &kt); + if (rc) + return rc; + + /* Obtain inverse-scaled offset */ + c0 = div_s64((s64)kt * (s64)data->c0, 2); + + /* Add the offset to the unscaled temperature */ + t = c0 + ((s64)data->temp_raw * (s64)data->c1); + + /* Convert to milliCelsius and scale the temperature */ + *val = (int)div_s64(t * 1000LL, kt); + + return 0; +} + +static int dps310_read_temp(struct dps310_data *data, int *val, int *val2, + long mask) +{ + int rc; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + rc = dps310_get_temp_samp_freq(data, val); + if (rc) + return rc; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_PROCESSED: + rc = dps310_read_temp_raw(data); + if (rc) + return rc; + + rc = dps310_calculate_temp(data, val); + if (rc) + return rc; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + rc = dps310_get_temp_precision(data, val); + if (rc) + return rc; + + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int dps310_read_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct dps310_data *data = iio_priv(iio); + + switch (chan->type) { + case IIO_PRESSURE: + return dps310_read_pressure(data, val, val2, mask); + + case IIO_TEMP: + return dps310_read_temp(data, val, val2, mask); + + default: + return -EINVAL; + } +} + +static void dps310_reset(void *action_data) +{ + struct dps310_data *data = action_data; + + dps310_reset_wait(data); +} + +static const struct regmap_config dps310_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = dps310_is_writeable_reg, + .volatile_reg = dps310_is_volatile_reg, + .cache_type = REGCACHE_RBTREE, + .max_register = 0x62, /* No documentation available on this register */ +}; + +static const struct iio_info dps310_info = { + .read_raw = dps310_read_raw, + .write_raw = dps310_write_raw, +}; + +static int dps310_probe(struct i2c_client *client) +{ + struct dps310_data *data; + struct iio_dev *iio; + int rc; + + iio = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!iio) + return -ENOMEM; + + data = iio_priv(iio); + data->client = client; + mutex_init(&data->lock); + + iio->name = DPS310_DEV_NAME; + iio->channels = dps310_channels; + iio->num_channels = ARRAY_SIZE(dps310_channels); + iio->info = &dps310_info; + iio->modes = INDIO_DIRECT_MODE; + + data->regmap = devm_regmap_init_i2c(client, &dps310_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + + /* Register to run the device reset when the device is removed */ + rc = devm_add_action_or_reset(&client->dev, dps310_reset, data); + if (rc) + return rc; + + rc = dps310_startup(data); + if (rc) + return rc; + + rc = devm_iio_device_register(&client->dev, iio); + if (rc) + return rc; + + i2c_set_clientdata(client, iio); + + return 0; +} + +static const struct i2c_device_id dps310_id[] = { + { .name = DPS310_DEV_NAME }, + { } +}; +MODULE_DEVICE_TABLE(i2c, dps310_id); + +static const struct acpi_device_id dps310_acpi_match[] = { + { "IFX3100" }, + { } +}; +MODULE_DEVICE_TABLE(acpi, dps310_acpi_match); + +static struct i2c_driver dps310_driver = { + .driver = { + .name = DPS310_DEV_NAME, + .acpi_match_table = dps310_acpi_match, + }, + .probe = dps310_probe, + .id_table = dps310_id, +}; +module_i2c_driver(dps310_driver); + +MODULE_AUTHOR("Joel Stanley "); +MODULE_DESCRIPTION("Infineon DPS310 pressure and temperature sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/hid-sensor-press.c b/drivers/iio/pressure/hid-sensor-press.c new file mode 100644 index 000000000..e688b0776 --- /dev/null +++ b/drivers/iio/pressure/hid-sensor-press.c @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HID Sensors Driver + * Copyright (c) 2014, Intel Corporation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "../common/hid-sensors/hid-sensor-trigger.h" + +enum { + CHANNEL_SCAN_INDEX_PRESSURE, + CHANNEL_SCAN_INDEX_TIMESTAMP, +}; + +struct press_state { + struct hid_sensor_hub_callbacks callbacks; + struct hid_sensor_common common_attributes; + struct hid_sensor_hub_attribute_info press_attr; + struct { + u32 press_data; + aligned_s64 timestamp; + } scan; + int scale_pre_decml; + int scale_post_decml; + int scale_precision; + int value_offset; + s64 timestamp; +}; + +static const u32 press_sensitivity_addresses[] = { + HID_USAGE_SENSOR_DATA_ATMOSPHERIC_PRESSURE, + HID_USAGE_SENSOR_ATMOSPHERIC_PRESSURE +}; + +/* Channel definitions */ +static const struct iio_chan_spec press_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + .scan_index = CHANNEL_SCAN_INDEX_PRESSURE, + }, + IIO_CHAN_SOFT_TIMESTAMP(CHANNEL_SCAN_INDEX_TIMESTAMP) + +}; + +/* Channel read_raw handler */ +static int press_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct press_state *press_state = iio_priv(indio_dev); + int report_id = -1; + u32 address; + int ret_type; + s32 min; + + *val = 0; + *val2 = 0; + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->scan_index) { + case CHANNEL_SCAN_INDEX_PRESSURE: + report_id = press_state->press_attr.report_id; + min = press_state->press_attr.logical_minimum; + address = HID_USAGE_SENSOR_ATMOSPHERIC_PRESSURE; + break; + default: + report_id = -1; + break; + } + if (report_id >= 0) { + hid_sensor_power_state(&press_state->common_attributes, + true); + *val = sensor_hub_input_attr_get_raw_value( + press_state->common_attributes.hsdev, + HID_USAGE_SENSOR_PRESSURE, address, + report_id, + SENSOR_HUB_SYNC, + min < 0); + hid_sensor_power_state(&press_state->common_attributes, + false); + } else { + *val = 0; + return -EINVAL; + } + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + *val = press_state->scale_pre_decml; + *val2 = press_state->scale_post_decml; + ret_type = press_state->scale_precision; + break; + case IIO_CHAN_INFO_OFFSET: + *val = press_state->value_offset; + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret_type = hid_sensor_read_samp_freq_value( + &press_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret_type = hid_sensor_read_raw_hyst_value( + &press_state->common_attributes, val, val2); + break; + default: + ret_type = -EINVAL; + break; + } + + return ret_type; +} + +/* Channel write_raw handler */ +static int press_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct press_state *press_state = iio_priv(indio_dev); + int ret = 0; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = hid_sensor_write_samp_freq_value( + &press_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret = hid_sensor_write_raw_hyst_value( + &press_state->common_attributes, val, val2); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info press_info = { + .read_raw = &press_read_raw, + .write_raw = &press_write_raw, +}; + +/* Callback handler to send event after all samples are received and captured */ +static int press_proc_event(struct hid_sensor_hub_device *hsdev, + u32 usage_id, void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct press_state *press_state = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "press_proc_event\n"); + if (atomic_read(&press_state->common_attributes.data_ready)) { + if (!press_state->timestamp) + press_state->timestamp = iio_get_time_ns(indio_dev); + + iio_push_to_buffers_with_ts(indio_dev, &press_state->scan, + sizeof(press_state->scan), + press_state->timestamp); + } + + return 0; +} + +/* Capture samples in local storage */ +static int press_capture_sample(struct hid_sensor_hub_device *hsdev, + u32 usage_id, + size_t raw_len, char *raw_data, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct press_state *press_state = iio_priv(indio_dev); + int ret = -EINVAL; + + switch (usage_id) { + case HID_USAGE_SENSOR_ATMOSPHERIC_PRESSURE: + press_state->scan.press_data = *(u32 *)raw_data; + ret = 0; + break; + case HID_USAGE_SENSOR_TIME_TIMESTAMP: + press_state->timestamp = hid_sensor_convert_timestamp( + &press_state->common_attributes, *(s64 *)raw_data); + break; + default: + break; + } + + return ret; +} + +/* Parse report which is specific to an usage id*/ +static int press_parse_report(struct platform_device *pdev, + struct hid_sensor_hub_device *hsdev, + struct iio_chan_spec *channels, + u32 usage_id, + struct press_state *st) +{ + int ret; + + ret = sensor_hub_input_get_attribute_info(hsdev, HID_INPUT_REPORT, + usage_id, + HID_USAGE_SENSOR_ATMOSPHERIC_PRESSURE, + &st->press_attr); + if (ret < 0) + return ret; + channels[CHANNEL_SCAN_INDEX_PRESSURE].scan_type = (struct iio_scan_type) { + .format = 's', + .realbits = BYTES_TO_BITS(st->press_attr.size), + .storagebits = BITS_PER_TYPE(u32), + }; + + dev_dbg(&pdev->dev, "press %x:%x\n", st->press_attr.index, + st->press_attr.report_id); + + st->scale_precision = hid_sensor_format_scale( + HID_USAGE_SENSOR_PRESSURE, + &st->press_attr, + &st->scale_pre_decml, &st->scale_post_decml); + + return ret; +} + +/* Function to initialize the processing for usage id */ +static int hid_press_probe(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + int ret = 0; + static const char *name = "press"; + struct iio_dev *indio_dev; + struct press_state *press_state; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct press_state)); + if (!indio_dev) + return -ENOMEM; + platform_set_drvdata(pdev, indio_dev); + + press_state = iio_priv(indio_dev); + press_state->common_attributes.hsdev = hsdev; + press_state->common_attributes.pdev = pdev; + + ret = hid_sensor_parse_common_attributes(hsdev, + HID_USAGE_SENSOR_PRESSURE, + &press_state->common_attributes, + press_sensitivity_addresses, + ARRAY_SIZE(press_sensitivity_addresses)); + if (ret) { + dev_err(&pdev->dev, "failed to setup common attributes\n"); + return ret; + } + + indio_dev->channels = devm_kmemdup(&pdev->dev, press_channels, + sizeof(press_channels), GFP_KERNEL); + if (!indio_dev->channels) { + dev_err(&pdev->dev, "failed to duplicate channels\n"); + return -ENOMEM; + } + + ret = press_parse_report(pdev, hsdev, + (struct iio_chan_spec *)indio_dev->channels, + HID_USAGE_SENSOR_PRESSURE, press_state); + if (ret) { + dev_err(&pdev->dev, "failed to setup attributes\n"); + return ret; + } + + indio_dev->num_channels = + ARRAY_SIZE(press_channels); + indio_dev->info = &press_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + atomic_set(&press_state->common_attributes.data_ready, 0); + + ret = hid_sensor_setup_trigger(indio_dev, name, + &press_state->common_attributes); + if (ret) { + dev_err(&pdev->dev, "trigger setup failed\n"); + return ret; + } + + press_state->callbacks.send_event = press_proc_event; + press_state->callbacks.capture_sample = press_capture_sample; + press_state->callbacks.pdev = pdev; + ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_PRESSURE, + &press_state->callbacks); + if (ret < 0) { + dev_err(&pdev->dev, "callback reg failed\n"); + goto error_remove_trigger; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "device register failed\n"); + goto error_remove_callback; + } + + return ret; + +error_remove_callback: + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_PRESSURE); +error_remove_trigger: + hid_sensor_remove_trigger(indio_dev, &press_state->common_attributes); + return ret; +} + +/* Function to deinitialize the processing for usage id */ +static void hid_press_remove(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct press_state *press_state = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_PRESSURE); + hid_sensor_remove_trigger(indio_dev, &press_state->common_attributes); +} + +static const struct platform_device_id hid_press_ids[] = { + { + /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-200031", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, hid_press_ids); + +static struct platform_driver hid_press_platform_driver = { + .id_table = hid_press_ids, + .driver = { + .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, + }, + .probe = hid_press_probe, + .remove = hid_press_remove, +}; +module_platform_driver(hid_press_platform_driver); + +MODULE_DESCRIPTION("HID Sensor Pressure"); +MODULE_AUTHOR("Archana Patni "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HID"); diff --git a/drivers/iio/pressure/hp03.c b/drivers/iio/pressure/hp03.c new file mode 100644 index 000000000..424523345 --- /dev/null +++ b/drivers/iio/pressure/hp03.c @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016 Marek Vasut + * + * Driver for Hope RF HP03 digital temperature and pressure sensor. + */ + +#define pr_fmt(fmt) "hp03: " fmt + +#include +#include +#include +#include +#include +#include +#include + +/* + * The HP03 sensor occupies two fixed I2C addresses: + * 0x50 ... read-only EEPROM with calibration data + * 0x77 ... read-write ADC for pressure and temperature + */ +#define HP03_EEPROM_ADDR 0x50 +#define HP03_ADC_ADDR 0x77 + +#define HP03_EEPROM_CX_OFFSET 0x10 +#define HP03_EEPROM_AB_OFFSET 0x1e +#define HP03_EEPROM_CD_OFFSET 0x20 + +#define HP03_ADC_WRITE_REG 0xff +#define HP03_ADC_READ_REG 0xfd +#define HP03_ADC_READ_PRESSURE 0xf0 /* D1 in datasheet */ +#define HP03_ADC_READ_TEMP 0xe8 /* D2 in datasheet */ + +struct hp03_priv { + struct i2c_client *client; + struct mutex lock; + struct gpio_desc *xclr_gpio; + + struct i2c_client *eeprom_client; + struct regmap *eeprom_regmap; + + s32 pressure; /* kPa */ + s32 temp; /* Deg. C */ +}; + +static const struct iio_chan_spec hp03_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static bool hp03_is_writeable_reg(struct device *dev, unsigned int reg) +{ + return false; +} + +static bool hp03_is_volatile_reg(struct device *dev, unsigned int reg) +{ + return false; +} + +static const struct regmap_config hp03_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = HP03_EEPROM_CD_OFFSET + 1, + .cache_type = REGCACHE_RBTREE, + + .writeable_reg = hp03_is_writeable_reg, + .volatile_reg = hp03_is_volatile_reg, +}; + +static int hp03_get_temp_pressure(struct hp03_priv *priv, const u8 reg) +{ + int ret; + + ret = i2c_smbus_write_byte_data(priv->client, HP03_ADC_WRITE_REG, reg); + if (ret < 0) + return ret; + + msleep(50); /* Wait for conversion to finish */ + + return i2c_smbus_read_word_data(priv->client, HP03_ADC_READ_REG); +} + +static int hp03_update_temp_pressure(struct hp03_priv *priv) +{ + struct device *dev = &priv->client->dev; + u8 coefs[18]; + u16 cx_val[7]; + int ab_val, d1_val, d2_val, diff_val, dut, off, sens, x; + int i, ret; + + /* Sample coefficients from EEPROM */ + ret = regmap_bulk_read(priv->eeprom_regmap, HP03_EEPROM_CX_OFFSET, + coefs, sizeof(coefs)); + if (ret < 0) { + dev_err(dev, "Failed to read EEPROM (reg=%02x)\n", + HP03_EEPROM_CX_OFFSET); + return ret; + } + + /* Sample Temperature and Pressure */ + gpiod_set_value_cansleep(priv->xclr_gpio, 1); + + ret = hp03_get_temp_pressure(priv, HP03_ADC_READ_PRESSURE); + if (ret < 0) { + dev_err(dev, "Failed to read pressure\n"); + goto err_adc; + } + d1_val = ret; + + ret = hp03_get_temp_pressure(priv, HP03_ADC_READ_TEMP); + if (ret < 0) { + dev_err(dev, "Failed to read temperature\n"); + goto err_adc; + } + d2_val = ret; + + gpiod_set_value_cansleep(priv->xclr_gpio, 0); + + /* The Cx coefficients and Temp/Pressure values are MSB first. */ + for (i = 0; i < 7; i++) + cx_val[i] = (coefs[2 * i] << 8) | (coefs[(2 * i) + 1] << 0); + d1_val = ((d1_val >> 8) & 0xff) | ((d1_val & 0xff) << 8); + d2_val = ((d2_val >> 8) & 0xff) | ((d2_val & 0xff) << 8); + + /* Coefficient voodoo from the HP03 datasheet. */ + if (d2_val >= cx_val[4]) + ab_val = coefs[14]; /* A-value */ + else + ab_val = coefs[15]; /* B-value */ + + diff_val = d2_val - cx_val[4]; + dut = (ab_val * (diff_val >> 7) * (diff_val >> 7)) >> coefs[16]; + dut = diff_val - dut; + + off = (cx_val[1] + (((cx_val[3] - 1024) * dut) >> 14)) * 4; + sens = cx_val[0] + ((cx_val[2] * dut) >> 10); + x = ((sens * (d1_val - 7168)) >> 14) - off; + + priv->pressure = ((x * 100) >> 5) + (cx_val[6] * 10); + priv->temp = 250 + ((dut * cx_val[5]) >> 16) - (dut >> coefs[17]); + + return 0; + +err_adc: + gpiod_set_value_cansleep(priv->xclr_gpio, 0); + return ret; +} + +static int hp03_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct hp03_priv *priv = iio_priv(indio_dev); + int ret; + + mutex_lock(&priv->lock); + ret = hp03_update_temp_pressure(priv); + mutex_unlock(&priv->lock); + + if (ret) + return ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_PRESSURE: + *val = priv->pressure; + return IIO_VAL_INT; + case IIO_TEMP: + *val = priv->temp; + return IIO_VAL_INT; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_PRESSURE: + *val = 0; + *val2 = 1000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = 10; + return IIO_VAL_INT; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static const struct iio_info hp03_info = { + .read_raw = &hp03_read_raw, +}; + +static int hp03_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct hp03_priv *priv; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + priv = iio_priv(indio_dev); + priv->client = client; + mutex_init(&priv->lock); + + indio_dev->name = id->name; + indio_dev->channels = hp03_channels; + indio_dev->num_channels = ARRAY_SIZE(hp03_channels); + indio_dev->info = &hp03_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + priv->xclr_gpio = devm_gpiod_get_index(dev, "xclr", 0, GPIOD_OUT_HIGH); + if (IS_ERR(priv->xclr_gpio)) { + dev_err(dev, "Failed to claim XCLR GPIO\n"); + ret = PTR_ERR(priv->xclr_gpio); + return ret; + } + + /* + * Allocate another device for the on-sensor EEPROM, + * which has it's dedicated I2C address and contains + * the calibration constants for the sensor. + */ + priv->eeprom_client = devm_i2c_new_dummy_device(dev, client->adapter, + HP03_EEPROM_ADDR); + if (IS_ERR(priv->eeprom_client)) { + dev_err(dev, "New EEPROM I2C device failed\n"); + return PTR_ERR(priv->eeprom_client); + } + + priv->eeprom_regmap = devm_regmap_init_i2c(priv->eeprom_client, + &hp03_regmap_config); + if (IS_ERR(priv->eeprom_regmap)) { + dev_err(dev, "Failed to allocate EEPROM regmap\n"); + return PTR_ERR(priv->eeprom_regmap); + } + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) { + dev_err(dev, "Failed to register IIO device\n"); + return ret; + } + + return 0; +} + +static const struct i2c_device_id hp03_id[] = { + { .name = "hp03" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, hp03_id); + +static const struct of_device_id hp03_of_match[] = { + { .compatible = "hoperf,hp03" }, + { } +}; +MODULE_DEVICE_TABLE(of, hp03_of_match); + +static struct i2c_driver hp03_driver = { + .driver = { + .name = "hp03", + .of_match_table = hp03_of_match, + }, + .probe = hp03_probe, + .id_table = hp03_id, +}; +module_i2c_driver(hp03_driver); + +MODULE_AUTHOR("Marek Vasut "); +MODULE_DESCRIPTION("Driver for Hope RF HP03 pressure and temperature sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/hp206c.c b/drivers/iio/pressure/hp206c.c new file mode 100644 index 000000000..ee34c8908 --- /dev/null +++ b/drivers/iio/pressure/hp206c.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * hp206c.c - HOPERF HP206C precision barometer and altimeter sensor + * + * Copyright (c) 2016, Intel Corporation. + * + * (7-bit I2C slave address 0x76) + * + * Datasheet: + * http://www.hoperf.com/upload/sensor/HP206C_DataSheet_EN_V2.0.pdf + */ + +#include +#include +#include +#include +#include +#include + +#include + +/* I2C commands: */ +#define HP206C_CMD_SOFT_RST 0x06 + +#define HP206C_CMD_ADC_CVT 0x40 + +#define HP206C_CMD_ADC_CVT_OSR_4096 0x00 +#define HP206C_CMD_ADC_CVT_OSR_2048 0x04 +#define HP206C_CMD_ADC_CVT_OSR_1024 0x08 +#define HP206C_CMD_ADC_CVT_OSR_512 0x0c +#define HP206C_CMD_ADC_CVT_OSR_256 0x10 +#define HP206C_CMD_ADC_CVT_OSR_128 0x14 + +#define HP206C_CMD_ADC_CVT_CHNL_PT 0x00 +#define HP206C_CMD_ADC_CVT_CHNL_T 0x02 + +#define HP206C_CMD_READ_P 0x30 +#define HP206C_CMD_READ_T 0x32 + +#define HP206C_CMD_READ_REG 0x80 +#define HP206C_CMD_WRITE_REG 0xc0 + +#define HP206C_REG_INT_EN 0x0b +#define HP206C_REG_INT_CFG 0x0c + +#define HP206C_REG_INT_SRC 0x0d +#define HP206C_FLAG_DEV_RDY 0x40 + +#define HP206C_REG_PARA 0x0f +#define HP206C_FLAG_CMPS_EN 0x80 + +/* Maximum spin for DEV_RDY */ +#define HP206C_MAX_DEV_RDY_WAIT_COUNT 20 +#define HP206C_DEV_RDY_WAIT_US 20000 + +struct hp206c_data { + struct mutex mutex; + struct i2c_client *client; + int temp_osr_index; + int pres_osr_index; +}; + +struct hp206c_osr_setting { + u8 osr_mask; + unsigned int temp_conv_time_us; + unsigned int pres_conv_time_us; +}; + +/* Data from Table 5 in datasheet. */ +static const struct hp206c_osr_setting hp206c_osr_settings[] = { + { HP206C_CMD_ADC_CVT_OSR_4096, 65600, 131100 }, + { HP206C_CMD_ADC_CVT_OSR_2048, 32800, 65600 }, + { HP206C_CMD_ADC_CVT_OSR_1024, 16400, 32800 }, + { HP206C_CMD_ADC_CVT_OSR_512, 8200, 16400 }, + { HP206C_CMD_ADC_CVT_OSR_256, 4100, 8200 }, + { HP206C_CMD_ADC_CVT_OSR_128, 2100, 4100 }, +}; +static const int hp206c_osr_rates[] = { 4096, 2048, 1024, 512, 256, 128 }; +static const char hp206c_osr_rates_str[] = "4096 2048 1024 512 256 128"; + +static inline int hp206c_read_reg(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, HP206C_CMD_READ_REG | reg); +} + +static inline int hp206c_write_reg(struct i2c_client *client, u8 reg, u8 val) +{ + return i2c_smbus_write_byte_data(client, + HP206C_CMD_WRITE_REG | reg, val); +} + +static int hp206c_read_20bit(struct i2c_client *client, u8 cmd) +{ + int ret; + u8 values[3]; + + ret = i2c_smbus_read_i2c_block_data(client, cmd, sizeof(values), values); + if (ret < 0) + return ret; + if (ret != sizeof(values)) + return -EIO; + return get_unaligned_be24(&values[0]) & GENMASK(19, 0); +} + +/* Spin for max 160ms until DEV_RDY is 1, or return error. */ +static int hp206c_wait_dev_rdy(struct iio_dev *indio_dev) +{ + int ret; + int count = 0; + struct hp206c_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + + while (++count <= HP206C_MAX_DEV_RDY_WAIT_COUNT) { + ret = hp206c_read_reg(client, HP206C_REG_INT_SRC); + if (ret < 0) { + dev_err(&indio_dev->dev, "Failed READ_REG INT_SRC: %d\n", ret); + return ret; + } + if (ret & HP206C_FLAG_DEV_RDY) + return 0; + usleep_range(HP206C_DEV_RDY_WAIT_US, HP206C_DEV_RDY_WAIT_US * 3 / 2); + } + return -ETIMEDOUT; +} + +static int hp206c_set_compensation(struct i2c_client *client, bool enabled) +{ + int val; + + val = hp206c_read_reg(client, HP206C_REG_PARA); + if (val < 0) + return val; + if (enabled) + val |= HP206C_FLAG_CMPS_EN; + else + val &= ~HP206C_FLAG_CMPS_EN; + + return hp206c_write_reg(client, HP206C_REG_PARA, val); +} + +/* Do a soft reset */ +static int hp206c_soft_reset(struct iio_dev *indio_dev) +{ + int ret; + struct hp206c_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + + ret = i2c_smbus_write_byte(client, HP206C_CMD_SOFT_RST); + if (ret) { + dev_err(&client->dev, "Failed to reset device: %d\n", ret); + return ret; + } + + usleep_range(400, 600); + + ret = hp206c_wait_dev_rdy(indio_dev); + if (ret) { + dev_err(&client->dev, "Device not ready after soft reset: %d\n", ret); + return ret; + } + + ret = hp206c_set_compensation(client, true); + if (ret) + dev_err(&client->dev, "Failed to enable compensation: %d\n", ret); + return ret; +} + +static int hp206c_conv_and_read(struct iio_dev *indio_dev, + u8 conv_cmd, u8 read_cmd, + unsigned int sleep_us) +{ + int ret; + struct hp206c_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + + ret = hp206c_wait_dev_rdy(indio_dev); + if (ret < 0) { + dev_err(&indio_dev->dev, "Device not ready: %d\n", ret); + return ret; + } + + ret = i2c_smbus_write_byte(client, conv_cmd); + if (ret < 0) { + dev_err(&indio_dev->dev, "Failed convert: %d\n", ret); + return ret; + } + + usleep_range(sleep_us, sleep_us * 3 / 2); + + ret = hp206c_wait_dev_rdy(indio_dev); + if (ret < 0) { + dev_err(&indio_dev->dev, "Device not ready: %d\n", ret); + return ret; + } + + ret = hp206c_read_20bit(client, read_cmd); + if (ret < 0) + dev_err(&indio_dev->dev, "Failed read: %d\n", ret); + + return ret; +} + +static int hp206c_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int ret; + struct hp206c_data *data = iio_priv(indio_dev); + const struct hp206c_osr_setting *osr_setting; + u8 conv_cmd; + + mutex_lock(&data->mutex); + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_TEMP: + *val = hp206c_osr_rates[data->temp_osr_index]; + ret = IIO_VAL_INT; + break; + + case IIO_PRESSURE: + *val = hp206c_osr_rates[data->pres_osr_index]; + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + } + break; + + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_TEMP: + osr_setting = &hp206c_osr_settings[data->temp_osr_index]; + conv_cmd = HP206C_CMD_ADC_CVT | + osr_setting->osr_mask | + HP206C_CMD_ADC_CVT_CHNL_T; + ret = hp206c_conv_and_read(indio_dev, + conv_cmd, + HP206C_CMD_READ_T, + osr_setting->temp_conv_time_us); + if (ret >= 0) { + /* 20 significant bits are provided. + * Extend sign over the rest. + */ + *val = sign_extend32(ret, 19); + ret = IIO_VAL_INT; + } + break; + + case IIO_PRESSURE: + osr_setting = &hp206c_osr_settings[data->pres_osr_index]; + conv_cmd = HP206C_CMD_ADC_CVT | + osr_setting->osr_mask | + HP206C_CMD_ADC_CVT_CHNL_PT; + ret = hp206c_conv_and_read(indio_dev, + conv_cmd, + HP206C_CMD_READ_P, + osr_setting->pres_conv_time_us); + if (ret >= 0) { + *val = ret; + ret = IIO_VAL_INT; + } + break; + default: + ret = -EINVAL; + } + break; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + *val = 0; + *val2 = 10000; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + + case IIO_PRESSURE: + *val = 0; + *val2 = 1000; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + ret = -EINVAL; + } + break; + + default: + ret = -EINVAL; + } + + mutex_unlock(&data->mutex); + return ret; +} + +static int hp206c_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret = 0; + struct hp206c_data *data = iio_priv(indio_dev); + + if (mask != IIO_CHAN_INFO_OVERSAMPLING_RATIO) + return -EINVAL; + mutex_lock(&data->mutex); + switch (chan->type) { + case IIO_TEMP: + data->temp_osr_index = find_closest_descending(val, + hp206c_osr_rates, ARRAY_SIZE(hp206c_osr_rates)); + break; + case IIO_PRESSURE: + data->pres_osr_index = find_closest_descending(val, + hp206c_osr_rates, ARRAY_SIZE(hp206c_osr_rates)); + break; + default: + ret = -EINVAL; + } + mutex_unlock(&data->mutex); + return ret; +} + +static const struct iio_chan_spec hp206c_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + }, + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + } +}; + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL(hp206c_osr_rates_str); + +static struct attribute *hp206c_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group hp206c_attribute_group = { + .attrs = hp206c_attributes, +}; + +static const struct iio_info hp206c_info = { + .attrs = &hp206c_attribute_group, + .read_raw = hp206c_read_raw, + .write_raw = hp206c_write_raw, +}; + +static int hp206c_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct iio_dev *indio_dev; + struct hp206c_data *data; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { + dev_err(&client->dev, "Adapter does not support " + "all required i2c functionality\n"); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + mutex_init(&data->mutex); + + indio_dev->info = &hp206c_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = hp206c_channels; + indio_dev->num_channels = ARRAY_SIZE(hp206c_channels); + + i2c_set_clientdata(client, indio_dev); + + /* Do a soft reset on probe */ + ret = hp206c_soft_reset(indio_dev); + if (ret) { + dev_err(&client->dev, "Failed to reset on startup: %d\n", ret); + return -ENODEV; + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id hp206c_id[] = { + { .name = "hp206c" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, hp206c_id); + +static const struct acpi_device_id hp206c_acpi_match[] = { + {"HOP206C", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, hp206c_acpi_match); + +static struct i2c_driver hp206c_driver = { + .probe = hp206c_probe, + .id_table = hp206c_id, + .driver = { + .name = "hp206c", + .acpi_match_table = hp206c_acpi_match, + }, +}; + +module_i2c_driver(hp206c_driver); + +MODULE_DESCRIPTION("HOPERF HP206C precision barometer and altimeter sensor"); +MODULE_AUTHOR("Leonard Crestez "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/hsc030pa.c b/drivers/iio/pressure/hsc030pa.c new file mode 100644 index 000000000..0374d406b --- /dev/null +++ b/drivers/iio/pressure/hsc030pa.c @@ -0,0 +1,540 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Honeywell TruStability HSC Series pressure/temperature sensor + * + * Copyright (c) 2023 Petre Rodan + * + * Datasheet: https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/trustability-hsc-series/documents/sps-siot-trustability-hsc-series-high-accuracy-board-mount-pressure-sensors-50099148-a-en-ciid-151133.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "hsc030pa.h" + +/* + * HSC_PRESSURE_TRIPLET_LEN - length for the string that defines the + * pressure range, measurement unit and type as per the part nomenclature. + * Consult honeywell,pressure-triplet in the bindings file for details. + */ +#define HSC_PRESSURE_TRIPLET_LEN 6 +#define HSC_STATUS_MASK GENMASK(7, 6) +#define HSC_TEMPERATURE_MASK GENMASK(15, 5) +#define HSC_PRESSURE_MASK GENMASK(29, 16) + +struct hsc_func_spec { + u32 output_min; + u32 output_max; +}; + +/* + * function A: 10% - 90% of 2^14 + * function B: 5% - 95% of 2^14 + * function C: 5% - 85% of 2^14 + * function F: 4% - 94% of 2^14 + */ +static const struct hsc_func_spec hsc_func_spec[] = { + [HSC_FUNCTION_A] = { .output_min = 1638, .output_max = 14746 }, + [HSC_FUNCTION_B] = { .output_min = 819, .output_max = 15565 }, + [HSC_FUNCTION_C] = { .output_min = 819, .output_max = 13926 }, + [HSC_FUNCTION_F] = { .output_min = 655, .output_max = 15401 }, +}; + +enum hsc_variants { + HSC001BA = 0x00, HSC1_6BA = 0x01, HSC2_5BA = 0x02, HSC004BA = 0x03, + HSC006BA = 0x04, HSC010BA = 0x05, HSC1_6MD = 0x06, HSC2_5MD = 0x07, + HSC004MD = 0x08, HSC006MD = 0x09, HSC010MD = 0x0a, HSC016MD = 0x0b, + HSC025MD = 0x0c, HSC040MD = 0x0d, HSC060MD = 0x0e, HSC100MD = 0x0f, + HSC160MD = 0x10, HSC250MD = 0x11, HSC400MD = 0x12, HSC600MD = 0x13, + HSC001BD = 0x14, HSC1_6BD = 0x15, HSC2_5BD = 0x16, HSC004BD = 0x17, + HSC2_5MG = 0x18, HSC004MG = 0x19, HSC006MG = 0x1a, HSC010MG = 0x1b, + HSC016MG = 0x1c, HSC025MG = 0x1d, HSC040MG = 0x1e, HSC060MG = 0x1f, + HSC100MG = 0x20, HSC160MG = 0x21, HSC250MG = 0x22, HSC400MG = 0x23, + HSC600MG = 0x24, HSC001BG = 0x25, HSC1_6BG = 0x26, HSC2_5BG = 0x27, + HSC004BG = 0x28, HSC006BG = 0x29, HSC010BG = 0x2a, HSC100KA = 0x2b, + HSC160KA = 0x2c, HSC250KA = 0x2d, HSC400KA = 0x2e, HSC600KA = 0x2f, + HSC001GA = 0x30, HSC160LD = 0x31, HSC250LD = 0x32, HSC400LD = 0x33, + HSC600LD = 0x34, HSC001KD = 0x35, HSC1_6KD = 0x36, HSC2_5KD = 0x37, + HSC004KD = 0x38, HSC006KD = 0x39, HSC010KD = 0x3a, HSC016KD = 0x3b, + HSC025KD = 0x3c, HSC040KD = 0x3d, HSC060KD = 0x3e, HSC100KD = 0x3f, + HSC160KD = 0x40, HSC250KD = 0x41, HSC400KD = 0x42, HSC250LG = 0x43, + HSC400LG = 0x44, HSC600LG = 0x45, HSC001KG = 0x46, HSC1_6KG = 0x47, + HSC2_5KG = 0x48, HSC004KG = 0x49, HSC006KG = 0x4a, HSC010KG = 0x4b, + HSC016KG = 0x4c, HSC025KG = 0x4d, HSC040KG = 0x4e, HSC060KG = 0x4f, + HSC100KG = 0x50, HSC160KG = 0x51, HSC250KG = 0x52, HSC400KG = 0x53, + HSC600KG = 0x54, HSC001GG = 0x55, HSC015PA = 0x56, HSC030PA = 0x57, + HSC060PA = 0x58, HSC100PA = 0x59, HSC150PA = 0x5a, HSC0_5ND = 0x5b, + HSC001ND = 0x5c, HSC002ND = 0x5d, HSC004ND = 0x5e, HSC005ND = 0x5f, + HSC010ND = 0x60, HSC020ND = 0x61, HSC030ND = 0x62, HSC001PD = 0x63, + HSC005PD = 0x64, HSC015PD = 0x65, HSC030PD = 0x66, HSC060PD = 0x67, + HSC001NG = 0x68, HSC002NG = 0x69, HSC004NG = 0x6a, HSC005NG = 0x6b, + HSC010NG = 0x6c, HSC020NG = 0x6d, HSC030NG = 0x6e, HSC001PG = 0x6f, + HSC005PG = 0x70, HSC015PG = 0x71, HSC030PG = 0x72, HSC060PG = 0x73, + HSC100PG = 0x74, HSC150PG = 0x75, HSC_VARIANTS_MAX +}; + +static const char * const hsc_triplet_variants[HSC_VARIANTS_MAX] = { + [HSC001BA] = "001BA", [HSC1_6BA] = "1.6BA", [HSC2_5BA] = "2.5BA", + [HSC004BA] = "004BA", [HSC006BA] = "006BA", [HSC010BA] = "010BA", + [HSC1_6MD] = "1.6MD", [HSC2_5MD] = "2.5MD", [HSC004MD] = "004MD", + [HSC006MD] = "006MD", [HSC010MD] = "010MD", [HSC016MD] = "016MD", + [HSC025MD] = "025MD", [HSC040MD] = "040MD", [HSC060MD] = "060MD", + [HSC100MD] = "100MD", [HSC160MD] = "160MD", [HSC250MD] = "250MD", + [HSC400MD] = "400MD", [HSC600MD] = "600MD", [HSC001BD] = "001BD", + [HSC1_6BD] = "1.6BD", [HSC2_5BD] = "2.5BD", [HSC004BD] = "004BD", + [HSC2_5MG] = "2.5MG", [HSC004MG] = "004MG", [HSC006MG] = "006MG", + [HSC010MG] = "010MG", [HSC016MG] = "016MG", [HSC025MG] = "025MG", + [HSC040MG] = "040MG", [HSC060MG] = "060MG", [HSC100MG] = "100MG", + [HSC160MG] = "160MG", [HSC250MG] = "250MG", [HSC400MG] = "400MG", + [HSC600MG] = "600MG", [HSC001BG] = "001BG", [HSC1_6BG] = "1.6BG", + [HSC2_5BG] = "2.5BG", [HSC004BG] = "004BG", [HSC006BG] = "006BG", + [HSC010BG] = "010BG", [HSC100KA] = "100KA", [HSC160KA] = "160KA", + [HSC250KA] = "250KA", [HSC400KA] = "400KA", [HSC600KA] = "600KA", + [HSC001GA] = "001GA", [HSC160LD] = "160LD", [HSC250LD] = "250LD", + [HSC400LD] = "400LD", [HSC600LD] = "600LD", [HSC001KD] = "001KD", + [HSC1_6KD] = "1.6KD", [HSC2_5KD] = "2.5KD", [HSC004KD] = "004KD", + [HSC006KD] = "006KD", [HSC010KD] = "010KD", [HSC016KD] = "016KD", + [HSC025KD] = "025KD", [HSC040KD] = "040KD", [HSC060KD] = "060KD", + [HSC100KD] = "100KD", [HSC160KD] = "160KD", [HSC250KD] = "250KD", + [HSC400KD] = "400KD", [HSC250LG] = "250LG", [HSC400LG] = "400LG", + [HSC600LG] = "600LG", [HSC001KG] = "001KG", [HSC1_6KG] = "1.6KG", + [HSC2_5KG] = "2.5KG", [HSC004KG] = "004KG", [HSC006KG] = "006KG", + [HSC010KG] = "010KG", [HSC016KG] = "016KG", [HSC025KG] = "025KG", + [HSC040KG] = "040KG", [HSC060KG] = "060KG", [HSC100KG] = "100KG", + [HSC160KG] = "160KG", [HSC250KG] = "250KG", [HSC400KG] = "400KG", + [HSC600KG] = "600KG", [HSC001GG] = "001GG", [HSC015PA] = "015PA", + [HSC030PA] = "030PA", [HSC060PA] = "060PA", [HSC100PA] = "100PA", + [HSC150PA] = "150PA", [HSC0_5ND] = "0.5ND", [HSC001ND] = "001ND", + [HSC002ND] = "002ND", [HSC004ND] = "004ND", [HSC005ND] = "005ND", + [HSC010ND] = "010ND", [HSC020ND] = "020ND", [HSC030ND] = "030ND", + [HSC001PD] = "001PD", [HSC005PD] = "005PD", [HSC015PD] = "015PD", + [HSC030PD] = "030PD", [HSC060PD] = "060PD", [HSC001NG] = "001NG", + [HSC002NG] = "002NG", [HSC004NG] = "004NG", [HSC005NG] = "005NG", + [HSC010NG] = "010NG", [HSC020NG] = "020NG", [HSC030NG] = "030NG", + [HSC001PG] = "001PG", [HSC005PG] = "005PG", [HSC015PG] = "015PG", + [HSC030PG] = "030PG", [HSC060PG] = "060PG", [HSC100PG] = "100PG", + [HSC150PG] = "150PG", +}; + +/** + * struct hsc_range_config - list of pressure ranges based on nomenclature + * @pmin: lowest pressure that can be measured + * @pmax: highest pressure that can be measured + */ +struct hsc_range_config { + const s32 pmin; + const s32 pmax; +}; + +/* All min max limits have been converted to pascals */ +static const struct hsc_range_config hsc_range_config[HSC_VARIANTS_MAX] = { + [HSC001BA] = { .pmin = 0, .pmax = 100000 }, + [HSC1_6BA] = { .pmin = 0, .pmax = 160000 }, + [HSC2_5BA] = { .pmin = 0, .pmax = 250000 }, + [HSC004BA] = { .pmin = 0, .pmax = 400000 }, + [HSC006BA] = { .pmin = 0, .pmax = 600000 }, + [HSC010BA] = { .pmin = 0, .pmax = 1000000 }, + [HSC1_6MD] = { .pmin = -160, .pmax = 160 }, + [HSC2_5MD] = { .pmin = -250, .pmax = 250 }, + [HSC004MD] = { .pmin = -400, .pmax = 400 }, + [HSC006MD] = { .pmin = -600, .pmax = 600 }, + [HSC010MD] = { .pmin = -1000, .pmax = 1000 }, + [HSC016MD] = { .pmin = -1600, .pmax = 1600 }, + [HSC025MD] = { .pmin = -2500, .pmax = 2500 }, + [HSC040MD] = { .pmin = -4000, .pmax = 4000 }, + [HSC060MD] = { .pmin = -6000, .pmax = 6000 }, + [HSC100MD] = { .pmin = -10000, .pmax = 10000 }, + [HSC160MD] = { .pmin = -16000, .pmax = 16000 }, + [HSC250MD] = { .pmin = -25000, .pmax = 25000 }, + [HSC400MD] = { .pmin = -40000, .pmax = 40000 }, + [HSC600MD] = { .pmin = -60000, .pmax = 60000 }, + [HSC001BD] = { .pmin = -100000, .pmax = 100000 }, + [HSC1_6BD] = { .pmin = -160000, .pmax = 160000 }, + [HSC2_5BD] = { .pmin = -250000, .pmax = 250000 }, + [HSC004BD] = { .pmin = -400000, .pmax = 400000 }, + [HSC2_5MG] = { .pmin = 0, .pmax = 250 }, + [HSC004MG] = { .pmin = 0, .pmax = 400 }, + [HSC006MG] = { .pmin = 0, .pmax = 600 }, + [HSC010MG] = { .pmin = 0, .pmax = 1000 }, + [HSC016MG] = { .pmin = 0, .pmax = 1600 }, + [HSC025MG] = { .pmin = 0, .pmax = 2500 }, + [HSC040MG] = { .pmin = 0, .pmax = 4000 }, + [HSC060MG] = { .pmin = 0, .pmax = 6000 }, + [HSC100MG] = { .pmin = 0, .pmax = 10000 }, + [HSC160MG] = { .pmin = 0, .pmax = 16000 }, + [HSC250MG] = { .pmin = 0, .pmax = 25000 }, + [HSC400MG] = { .pmin = 0, .pmax = 40000 }, + [HSC600MG] = { .pmin = 0, .pmax = 60000 }, + [HSC001BG] = { .pmin = 0, .pmax = 100000 }, + [HSC1_6BG] = { .pmin = 0, .pmax = 160000 }, + [HSC2_5BG] = { .pmin = 0, .pmax = 250000 }, + [HSC004BG] = { .pmin = 0, .pmax = 400000 }, + [HSC006BG] = { .pmin = 0, .pmax = 600000 }, + [HSC010BG] = { .pmin = 0, .pmax = 1000000 }, + [HSC100KA] = { .pmin = 0, .pmax = 100000 }, + [HSC160KA] = { .pmin = 0, .pmax = 160000 }, + [HSC250KA] = { .pmin = 0, .pmax = 250000 }, + [HSC400KA] = { .pmin = 0, .pmax = 400000 }, + [HSC600KA] = { .pmin = 0, .pmax = 600000 }, + [HSC001GA] = { .pmin = 0, .pmax = 1000000 }, + [HSC160LD] = { .pmin = -160, .pmax = 160 }, + [HSC250LD] = { .pmin = -250, .pmax = 250 }, + [HSC400LD] = { .pmin = -400, .pmax = 400 }, + [HSC600LD] = { .pmin = -600, .pmax = 600 }, + [HSC001KD] = { .pmin = -1000, .pmax = 1000 }, + [HSC1_6KD] = { .pmin = -1600, .pmax = 1600 }, + [HSC2_5KD] = { .pmin = -2500, .pmax = 2500 }, + [HSC004KD] = { .pmin = -4000, .pmax = 4000 }, + [HSC006KD] = { .pmin = -6000, .pmax = 6000 }, + [HSC010KD] = { .pmin = -10000, .pmax = 10000 }, + [HSC016KD] = { .pmin = -16000, .pmax = 16000 }, + [HSC025KD] = { .pmin = -25000, .pmax = 25000 }, + [HSC040KD] = { .pmin = -40000, .pmax = 40000 }, + [HSC060KD] = { .pmin = -60000, .pmax = 60000 }, + [HSC100KD] = { .pmin = -100000, .pmax = 100000 }, + [HSC160KD] = { .pmin = -160000, .pmax = 160000 }, + [HSC250KD] = { .pmin = -250000, .pmax = 250000 }, + [HSC400KD] = { .pmin = -400000, .pmax = 400000 }, + [HSC250LG] = { .pmin = 0, .pmax = 250 }, + [HSC400LG] = { .pmin = 0, .pmax = 400 }, + [HSC600LG] = { .pmin = 0, .pmax = 600 }, + [HSC001KG] = { .pmin = 0, .pmax = 1000 }, + [HSC1_6KG] = { .pmin = 0, .pmax = 1600 }, + [HSC2_5KG] = { .pmin = 0, .pmax = 2500 }, + [HSC004KG] = { .pmin = 0, .pmax = 4000 }, + [HSC006KG] = { .pmin = 0, .pmax = 6000 }, + [HSC010KG] = { .pmin = 0, .pmax = 10000 }, + [HSC016KG] = { .pmin = 0, .pmax = 16000 }, + [HSC025KG] = { .pmin = 0, .pmax = 25000 }, + [HSC040KG] = { .pmin = 0, .pmax = 40000 }, + [HSC060KG] = { .pmin = 0, .pmax = 60000 }, + [HSC100KG] = { .pmin = 0, .pmax = 100000 }, + [HSC160KG] = { .pmin = 0, .pmax = 160000 }, + [HSC250KG] = { .pmin = 0, .pmax = 250000 }, + [HSC400KG] = { .pmin = 0, .pmax = 400000 }, + [HSC600KG] = { .pmin = 0, .pmax = 600000 }, + [HSC001GG] = { .pmin = 0, .pmax = 1000000 }, + [HSC015PA] = { .pmin = 0, .pmax = 103421 }, + [HSC030PA] = { .pmin = 0, .pmax = 206843 }, + [HSC060PA] = { .pmin = 0, .pmax = 413685 }, + [HSC100PA] = { .pmin = 0, .pmax = 689476 }, + [HSC150PA] = { .pmin = 0, .pmax = 1034214 }, + [HSC0_5ND] = { .pmin = -125, .pmax = 125 }, + [HSC001ND] = { .pmin = -249, .pmax = 249 }, + [HSC002ND] = { .pmin = -498, .pmax = 498 }, + [HSC004ND] = { .pmin = -996, .pmax = 996 }, + [HSC005ND] = { .pmin = -1245, .pmax = 1245 }, + [HSC010ND] = { .pmin = -2491, .pmax = 2491 }, + [HSC020ND] = { .pmin = -4982, .pmax = 4982 }, + [HSC030ND] = { .pmin = -7473, .pmax = 7473 }, + [HSC001PD] = { .pmin = -6895, .pmax = 6895 }, + [HSC005PD] = { .pmin = -34474, .pmax = 34474 }, + [HSC015PD] = { .pmin = -103421, .pmax = 103421 }, + [HSC030PD] = { .pmin = -206843, .pmax = 206843 }, + [HSC060PD] = { .pmin = -413685, .pmax = 413685 }, + [HSC001NG] = { .pmin = 0, .pmax = 249 }, + [HSC002NG] = { .pmin = 0, .pmax = 498 }, + [HSC004NG] = { .pmin = 0, .pmax = 996 }, + [HSC005NG] = { .pmin = 0, .pmax = 1245 }, + [HSC010NG] = { .pmin = 0, .pmax = 2491 }, + [HSC020NG] = { .pmin = 0, .pmax = 4982 }, + [HSC030NG] = { .pmin = 0, .pmax = 7473 }, + [HSC001PG] = { .pmin = 0, .pmax = 6895 }, + [HSC005PG] = { .pmin = 0, .pmax = 34474 }, + [HSC015PG] = { .pmin = 0, .pmax = 103421 }, + [HSC030PG] = { .pmin = 0, .pmax = 206843 }, + [HSC060PG] = { .pmin = 0, .pmax = 413685 }, + [HSC100PG] = { .pmin = 0, .pmax = 689476 }, + [HSC150PG] = { .pmin = 0, .pmax = 1034214 }, +}; + +/** + * hsc_measurement_is_valid() - validate last conversion via status bits + * @data: structure containing instantiated sensor data + * Return: true only if both status bits are zero + * + * The two MSB from the first transferred byte contain a status code + * 00 - normal operation, valid data + * 01 - device in factory programming mode + * 10 - stale data + * 11 - diagnostic condition + */ +static bool hsc_measurement_is_valid(struct hsc_data *data) +{ + return !(data->buffer[0] & HSC_STATUS_MASK); +} + +static int hsc_get_measurement(struct hsc_data *data) +{ + const struct hsc_chip_data *chip = data->chip; + int ret; + + ret = data->recv_cb(data); + if (ret < 0) + return ret; + + data->is_valid = chip->valid(data); + if (!data->is_valid) + return -EAGAIN; + + return 0; +} + +static irqreturn_t hsc_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct hsc_data *data = iio_priv(indio_dev); + int ret; + + ret = hsc_get_measurement(data); + if (ret) + goto error; + + memcpy(&data->scan.chan[0], &data->buffer[0], 2); + memcpy(&data->scan.chan[1], &data->buffer[2], 2); + + iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + iio_get_time_ns(indio_dev)); + +error: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +/* + * IIO ABI expects + * value = (conv + offset) * scale + * + * datasheet provides the following formula for determining the temperature + * temp[C] = conv * a + b + * where a = 200/2047; b = -50 + * + * temp[C] = (conv + (b/a)) * a * (1000) + * => + * scale = a * 1000 = .097703957 * 1000 = 97.703957 + * offset = b/a = -50 / .097703957 = -50000000 / 97704 + * + * based on the datasheet + * pressure = (conv - Omin) * Q + Pmin = + * ((conv - Omin) + Pmin/Q) * Q + * => + * scale = Q = (Pmax - Pmin) / (Omax - Omin) + * offset = Pmin/Q - Omin = Pmin * (Omax - Omin) / (Pmax - Pmin) - Omin + */ +static int hsc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct hsc_data *data = iio_priv(indio_dev); + int ret; + u32 recvd; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = hsc_get_measurement(data); + if (ret) + return ret; + + recvd = get_unaligned_be32(data->buffer); + switch (channel->type) { + case IIO_PRESSURE: + *val = FIELD_GET(HSC_PRESSURE_MASK, recvd); + return IIO_VAL_INT; + case IIO_TEMP: + *val = FIELD_GET(HSC_TEMPERATURE_MASK, recvd); + return IIO_VAL_INT; + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_SCALE: + switch (channel->type) { + case IIO_TEMP: + *val = 97; + *val2 = 703957; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_PRESSURE: + *val = data->p_scale; + *val2 = data->p_scale_dec; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_OFFSET: + switch (channel->type) { + case IIO_TEMP: + *val = -50000000; + *val2 = 97704; + return IIO_VAL_FRACTIONAL; + case IIO_PRESSURE: + *val = data->p_offset; + *val2 = data->p_offset_dec; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +static const struct iio_chan_spec hsc_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 14, + .storagebits = 16, + .endianness = IIO_BE, + }, + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = 1, + .scan_type = { + .sign = 'u', + .realbits = 11, + .storagebits = 16, + .shift = 5, + .endianness = IIO_BE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static const struct iio_info hsc_info = { + .read_raw = hsc_read_raw, +}; + +static const struct hsc_chip_data hsc_chip = { + .valid = hsc_measurement_is_valid, + .channels = hsc_channels, + .num_channels = ARRAY_SIZE(hsc_channels), +}; + +int hsc_common_probe(struct device *dev, hsc_recv_fn recv) +{ + struct hsc_data *hsc; + struct iio_dev *indio_dev; + const char *triplet; + s64 tmp; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*hsc)); + if (!indio_dev) + return -ENOMEM; + + hsc = iio_priv(indio_dev); + + hsc->chip = &hsc_chip; + hsc->recv_cb = recv; + hsc->dev = dev; + + ret = device_property_read_u32(dev, "honeywell,transfer-function", + &hsc->function); + if (ret) + return dev_err_probe(dev, ret, + "honeywell,transfer-function could not be read\n"); + if (hsc->function > HSC_FUNCTION_F) + return dev_err_probe(dev, -EINVAL, + "honeywell,transfer-function %d invalid\n", + hsc->function); + + ret = device_property_read_string(dev, "honeywell,pressure-triplet", + &triplet); + if (ret) + return dev_err_probe(dev, ret, + "honeywell,pressure-triplet could not be read\n"); + + if (str_has_prefix(triplet, "NA")) { + ret = device_property_read_u32(dev, "honeywell,pmin-pascal", + &hsc->pmin); + if (ret) + return dev_err_probe(dev, ret, + "honeywell,pmin-pascal could not be read\n"); + + ret = device_property_read_u32(dev, "honeywell,pmax-pascal", + &hsc->pmax); + if (ret) + return dev_err_probe(dev, ret, + "honeywell,pmax-pascal could not be read\n"); + } else { + ret = device_property_match_property_string(dev, + "honeywell,pressure-triplet", + hsc_triplet_variants, + HSC_VARIANTS_MAX); + if (ret < 0) + return dev_err_probe(dev, -EINVAL, + "honeywell,pressure-triplet is invalid\n"); + + hsc->pmin = hsc_range_config[ret].pmin; + hsc->pmax = hsc_range_config[ret].pmax; + } + + if (hsc->pmin >= hsc->pmax) + return dev_err_probe(dev, -EINVAL, + "pressure limits are invalid\n"); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "can't get vdd supply\n"); + + hsc->outmin = hsc_func_spec[hsc->function].output_min; + hsc->outmax = hsc_func_spec[hsc->function].output_max; + + tmp = div_s64(((s64)(hsc->pmax - hsc->pmin)) * MICRO, + hsc->outmax - hsc->outmin); + hsc->p_scale = div_s64_rem(tmp, NANO, &hsc->p_scale_dec); + tmp = div_s64(((s64)hsc->pmin * (s64)(hsc->outmax - hsc->outmin)) * MICRO, + hsc->pmax - hsc->pmin); + tmp -= (s64)hsc->outmin * MICRO; + hsc->p_offset = div_s64_rem(tmp, MICRO, &hsc->p_offset_dec); + + indio_dev->name = "hsc030pa"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &hsc_info; + indio_dev->channels = hsc->chip->channels; + indio_dev->num_channels = hsc->chip->num_channels; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + hsc_trigger_handler, NULL); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS(hsc_common_probe, "IIO_HONEYWELL_HSC030PA"); + +MODULE_AUTHOR("Petre Rodan "); +MODULE_DESCRIPTION("Honeywell HSC and SSC pressure sensor core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/hsc030pa.h b/drivers/iio/pressure/hsc030pa.h new file mode 100644 index 000000000..5db46784f --- /dev/null +++ b/drivers/iio/pressure/hsc030pa.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Honeywell TruStability HSC Series pressure/temperature sensor + * + * Copyright (c) 2023 Petre Rodan + */ + +#ifndef _HSC030PA_H +#define _HSC030PA_H + +#include + +#include + +#define HSC_REG_MEASUREMENT_RD_SIZE 4 +#define HSC_RESP_TIME_MS 2 + +struct device; + +struct iio_chan_spec; +struct iio_dev; + +struct hsc_data; +struct hsc_chip_data; + +typedef int (*hsc_recv_fn)(struct hsc_data *); + +/** + * struct hsc_data + * @dev: current device structure + * @chip: structure containing chip's channel properties + * @recv_cb: function that implements the chip reads + * @is_valid: true if last transfer has been validated + * @pmin: minimum measurable pressure limit + * @pmax: maximum measurable pressure limit + * @outmin: minimum raw pressure in counts (based on transfer function) + * @outmax: maximum raw pressure in counts (based on transfer function) + * @function: transfer function + * @p_scale: pressure scale + * @p_scale_dec: pressure scale, decimal places + * @p_offset: pressure offset + * @p_offset_dec: pressure offset, decimal places + * @buffer: raw conversion data + */ +struct hsc_data { + struct device *dev; + const struct hsc_chip_data *chip; + hsc_recv_fn recv_cb; + bool is_valid; + s32 pmin; + s32 pmax; + u32 outmin; + u32 outmax; + u32 function; + s64 p_scale; + s32 p_scale_dec; + s64 p_offset; + s32 p_offset_dec; + struct { + __be16 chan[2]; + aligned_s64 timestamp; + } scan; + u8 buffer[HSC_REG_MEASUREMENT_RD_SIZE] __aligned(IIO_DMA_MINALIGN); +}; + +struct hsc_chip_data { + bool (*valid)(struct hsc_data *data); + const struct iio_chan_spec *channels; + u8 num_channels; +}; + +enum hsc_func_id { + HSC_FUNCTION_A, + HSC_FUNCTION_B, + HSC_FUNCTION_C, + HSC_FUNCTION_F, +}; + +int hsc_common_probe(struct device *dev, hsc_recv_fn recv); + +#endif diff --git a/drivers/iio/pressure/hsc030pa_i2c.c b/drivers/iio/pressure/hsc030pa_i2c.c new file mode 100644 index 000000000..050cacd9e --- /dev/null +++ b/drivers/iio/pressure/hsc030pa_i2c.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Honeywell TruStability HSC Series pressure/temperature sensor + * + * Copyright (c) 2023 Petre Rodan + * + * Datasheet: https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/common/documents/sps-siot-i2c-comms-digital-output-pressure-sensors-tn-008201-3-en-ciid-45841.pdf + * Datasheet: https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/common/documents/sps-siot-sleep-mode-technical-note-008286-1-en-ciid-155793.pdf + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "hsc030pa.h" + +static int hsc_i2c_recv(struct hsc_data *data) +{ + struct i2c_client *client = to_i2c_client(data->dev); + struct i2c_msg msg; + int ret; + + msleep_interruptible(HSC_RESP_TIME_MS); + + msg.addr = client->addr; + msg.flags = client->flags | I2C_M_RD; + msg.len = HSC_REG_MEASUREMENT_RD_SIZE; + msg.buf = data->buffer; + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) + return ret; + + if (ret != 1) + return -EIO; + + return 0; +} + +static int hsc_i2c_probe(struct i2c_client *client) +{ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -EOPNOTSUPP; + + return hsc_common_probe(&client->dev, hsc_i2c_recv); +} + +static const struct of_device_id hsc_i2c_match[] = { + { .compatible = "honeywell,hsc030pa" }, + { } +}; +MODULE_DEVICE_TABLE(of, hsc_i2c_match); + +static const struct i2c_device_id hsc_i2c_id[] = { + { .name = "hsc030pa" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, hsc_i2c_id); + +static struct i2c_driver hsc_i2c_driver = { + .driver = { + .name = "hsc030pa", + .of_match_table = hsc_i2c_match, + }, + .probe = hsc_i2c_probe, + .id_table = hsc_i2c_id, +}; +module_i2c_driver(hsc_i2c_driver); + +MODULE_AUTHOR("Petre Rodan "); +MODULE_DESCRIPTION("Honeywell HSC and SSC pressure sensor i2c driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HONEYWELL_HSC030PA"); diff --git a/drivers/iio/pressure/hsc030pa_spi.c b/drivers/iio/pressure/hsc030pa_spi.c new file mode 100644 index 000000000..02021b6f7 --- /dev/null +++ b/drivers/iio/pressure/hsc030pa_spi.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Honeywell TruStability HSC Series pressure/temperature sensor + * + * Copyright (c) 2023 Petre Rodan + * + * Datasheet: https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/common/documents/sps-siot-spi-comms-digital-ouptu-pressure-sensors-tn-008202-3-en-ciid-45843.pdf + * Datasheet: https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/common/documents/sps-siot-sleep-mode-technical-note-008286-1-en-ciid-155793.pdf + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "hsc030pa.h" + +static int hsc_spi_recv(struct hsc_data *data) +{ + struct spi_device *spi = to_spi_device(data->dev); + + msleep_interruptible(HSC_RESP_TIME_MS); + return spi_read(spi, data->buffer, HSC_REG_MEASUREMENT_RD_SIZE); +} + +static int hsc_spi_probe(struct spi_device *spi) +{ + return hsc_common_probe(&spi->dev, hsc_spi_recv); +} + +static const struct of_device_id hsc_spi_match[] = { + { .compatible = "honeywell,hsc030pa" }, + { } +}; +MODULE_DEVICE_TABLE(of, hsc_spi_match); + +static const struct spi_device_id hsc_spi_id[] = { + { .name = "hsc030pa" }, + { } +}; +MODULE_DEVICE_TABLE(spi, hsc_spi_id); + +static struct spi_driver hsc_spi_driver = { + .driver = { + .name = "hsc030pa", + .of_match_table = hsc_spi_match, + }, + .probe = hsc_spi_probe, + .id_table = hsc_spi_id, +}; +module_spi_driver(hsc_spi_driver); + +MODULE_AUTHOR("Petre Rodan "); +MODULE_DESCRIPTION("Honeywell HSC and SSC pressure sensor spi driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HONEYWELL_HSC030PA"); diff --git a/drivers/iio/pressure/icp10100.c b/drivers/iio/pressure/icp10100.c new file mode 100644 index 000000000..82824e3e8 --- /dev/null +++ b/drivers/iio/pressure/icp10100.c @@ -0,0 +1,653 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 InvenSense, Inc. + * + * Driver for InvenSense ICP-1010xx barometric pressure and temperature sensor. + * + * Datasheet: + * http://www.invensense.com/wp-content/uploads/2018/01/DS-000186-ICP-101xx-v1.2.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ICP10100_ID_REG_GET(_reg) ((_reg) & 0x003F) +#define ICP10100_ID_REG 0x08 +#define ICP10100_RESPONSE_WORD_LENGTH 3 +#define ICP10100_CRC8_WORD_LENGTH 2 +#define ICP10100_CRC8_POLYNOMIAL 0x31 +#define ICP10100_CRC8_INIT 0xFF + +enum icp10100_mode { + ICP10100_MODE_LP, /* Low power mode: 1x sampling */ + ICP10100_MODE_N, /* Normal mode: 2x sampling */ + ICP10100_MODE_LN, /* Low noise mode: 4x sampling */ + ICP10100_MODE_ULN, /* Ultra low noise mode: 8x sampling */ + ICP10100_MODE_NB, +}; + +struct icp10100_state { + struct mutex lock; + struct i2c_client *client; + struct regulator *vdd; + enum icp10100_mode mode; + int16_t cal[4]; +}; + +struct icp10100_command { + __be16 cmd; + unsigned long wait_us; + unsigned long wait_max_us; + size_t response_word_nb; +}; + +static const struct icp10100_command icp10100_cmd_soft_reset = { + .cmd = cpu_to_be16(0x805D), + .wait_us = 170, + .wait_max_us = 200, + .response_word_nb = 0, +}; + +static const struct icp10100_command icp10100_cmd_read_id = { + .cmd = cpu_to_be16(0xEFC8), + .wait_us = 0, + .response_word_nb = 1, +}; + +static const struct icp10100_command icp10100_cmd_read_otp = { + .cmd = cpu_to_be16(0xC7F7), + .wait_us = 0, + .response_word_nb = 1, +}; + +static const struct icp10100_command icp10100_cmd_measure[] = { + [ICP10100_MODE_LP] = { + .cmd = cpu_to_be16(0x401A), + .wait_us = 1800, + .wait_max_us = 2000, + .response_word_nb = 3, + }, + [ICP10100_MODE_N] = { + .cmd = cpu_to_be16(0x48A3), + .wait_us = 6300, + .wait_max_us = 6500, + .response_word_nb = 3, + }, + [ICP10100_MODE_LN] = { + .cmd = cpu_to_be16(0x5059), + .wait_us = 23800, + .wait_max_us = 24000, + .response_word_nb = 3, + }, + [ICP10100_MODE_ULN] = { + .cmd = cpu_to_be16(0x58E0), + .wait_us = 94500, + .wait_max_us = 94700, + .response_word_nb = 3, + }, +}; + +static const uint8_t icp10100_switch_mode_otp[] = + {0xC5, 0x95, 0x00, 0x66, 0x9c}; + +DECLARE_CRC8_TABLE(icp10100_crc8_table); + +static inline int icp10100_i2c_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + int ret; + + ret = i2c_transfer(adap, msgs, num); + if (ret < 0) + return ret; + + if (ret != num) + return -EIO; + + return 0; +} + +static int icp10100_send_cmd(struct icp10100_state *st, + const struct icp10100_command *cmd, + __be16 *buf, size_t buf_len) +{ + size_t size = cmd->response_word_nb * ICP10100_RESPONSE_WORD_LENGTH; + uint8_t data[16]; + uint8_t *ptr; + uint8_t *buf_ptr = (uint8_t *)buf; + struct i2c_msg msgs[2] = { + { + .addr = st->client->addr, + .flags = 0, + .len = 2, + .buf = (uint8_t *)&cmd->cmd, + }, { + .addr = st->client->addr, + .flags = I2C_M_RD, + .len = size, + .buf = data, + }, + }; + uint8_t crc; + unsigned int i; + int ret; + + if (size > sizeof(data)) + return -EINVAL; + + if (cmd->response_word_nb > 0 && + (buf == NULL || buf_len < (cmd->response_word_nb * 2))) + return -EINVAL; + + dev_dbg(&st->client->dev, "sending cmd %#x\n", be16_to_cpu(cmd->cmd)); + + if (cmd->response_word_nb > 0 && cmd->wait_us == 0) { + /* direct command-response without waiting */ + ret = icp10100_i2c_xfer(st->client->adapter, msgs, + ARRAY_SIZE(msgs)); + if (ret) + return ret; + } else { + /* transfer command write */ + ret = icp10100_i2c_xfer(st->client->adapter, &msgs[0], 1); + if (ret) + return ret; + if (cmd->wait_us > 0) + usleep_range(cmd->wait_us, cmd->wait_max_us); + /* transfer response read if needed */ + if (cmd->response_word_nb > 0) { + ret = icp10100_i2c_xfer(st->client->adapter, &msgs[1], 1); + if (ret) + return ret; + } else { + return 0; + } + } + + /* process read words with crc checking */ + for (i = 0; i < cmd->response_word_nb; ++i) { + ptr = &data[i * ICP10100_RESPONSE_WORD_LENGTH]; + crc = crc8(icp10100_crc8_table, ptr, ICP10100_CRC8_WORD_LENGTH, + ICP10100_CRC8_INIT); + if (crc != ptr[ICP10100_CRC8_WORD_LENGTH]) { + dev_err(&st->client->dev, "crc error recv=%#x calc=%#x\n", + ptr[ICP10100_CRC8_WORD_LENGTH], crc); + return -EIO; + } + *buf_ptr++ = ptr[0]; + *buf_ptr++ = ptr[1]; + } + + return 0; +} + +static int icp10100_read_cal_otp(struct icp10100_state *st) +{ + __be16 val; + int i; + int ret; + + /* switch into OTP read mode */ + ret = i2c_master_send(st->client, icp10100_switch_mode_otp, + ARRAY_SIZE(icp10100_switch_mode_otp)); + if (ret < 0) + return ret; + if (ret != ARRAY_SIZE(icp10100_switch_mode_otp)) + return -EIO; + + /* read 4 calibration values */ + for (i = 0; i < 4; ++i) { + ret = icp10100_send_cmd(st, &icp10100_cmd_read_otp, + &val, sizeof(val)); + if (ret) + return ret; + st->cal[i] = be16_to_cpu(val); + dev_dbg(&st->client->dev, "cal[%d] = %d\n", i, st->cal[i]); + } + + return 0; +} + +static int icp10100_init_chip(struct icp10100_state *st) +{ + __be16 val; + uint16_t id; + int ret; + + /* read and check id */ + ret = icp10100_send_cmd(st, &icp10100_cmd_read_id, &val, sizeof(val)); + if (ret) + return ret; + id = ICP10100_ID_REG_GET(be16_to_cpu(val)); + if (id != ICP10100_ID_REG) { + dev_err(&st->client->dev, "invalid id %#x\n", id); + return -ENODEV; + } + + /* read calibration data from OTP */ + ret = icp10100_read_cal_otp(st); + if (ret) + return ret; + + /* reset chip */ + return icp10100_send_cmd(st, &icp10100_cmd_soft_reset, NULL, 0); +} + +static int icp10100_get_measures(struct icp10100_state *st, + uint32_t *pressure, uint16_t *temperature) +{ + const struct icp10100_command *cmd; + __be16 measures[3]; + int ret; + + ret = pm_runtime_resume_and_get(&st->client->dev); + if (ret < 0) + return ret; + + mutex_lock(&st->lock); + cmd = &icp10100_cmd_measure[st->mode]; + ret = icp10100_send_cmd(st, cmd, measures, sizeof(measures)); + mutex_unlock(&st->lock); + if (ret) + goto error_measure; + + *pressure = (be16_to_cpu(measures[0]) << 8) | + (be16_to_cpu(measures[1]) >> 8); + *temperature = be16_to_cpu(measures[2]); + +error_measure: + pm_runtime_put_autosuspend(&st->client->dev); + return ret; +} + +static uint32_t icp10100_get_pressure(struct icp10100_state *st, + uint32_t raw_pressure, uint16_t raw_temp) +{ + static int32_t p_calib[] = {45000, 80000, 105000}; + static int32_t lut_lower = 3670016; + static int32_t lut_upper = 12058624; + static int32_t inv_quadr_factor = 16777216; + static int32_t offset_factor = 2048; + int64_t val1, val2; + int32_t p_lut[3]; + int32_t t, t_square; + int64_t a, b, c; + uint32_t pressure_mPa; + + dev_dbg(&st->client->dev, "raw: pressure = %u, temp = %u\n", + raw_pressure, raw_temp); + + /* compute p_lut values */ + t = (int32_t)raw_temp - 32768; + t_square = t * t; + val1 = (int64_t)st->cal[0] * (int64_t)t_square; + p_lut[0] = lut_lower + (int32_t)div_s64(val1, inv_quadr_factor); + val1 = (int64_t)st->cal[1] * (int64_t)t_square; + p_lut[1] = offset_factor * st->cal[3] + + (int32_t)div_s64(val1, inv_quadr_factor); + val1 = (int64_t)st->cal[2] * (int64_t)t_square; + p_lut[2] = lut_upper + (int32_t)div_s64(val1, inv_quadr_factor); + dev_dbg(&st->client->dev, "p_lut = [%d, %d, %d]\n", + p_lut[0], p_lut[1], p_lut[2]); + + /* compute a, b, c factors */ + val1 = (int64_t)p_lut[0] * (int64_t)p_lut[1] * + (int64_t)(p_calib[0] - p_calib[1]) + + (int64_t)p_lut[1] * (int64_t)p_lut[2] * + (int64_t)(p_calib[1] - p_calib[2]) + + (int64_t)p_lut[2] * (int64_t)p_lut[0] * + (int64_t)(p_calib[2] - p_calib[0]); + val2 = (int64_t)p_lut[2] * (int64_t)(p_calib[0] - p_calib[1]) + + (int64_t)p_lut[0] * (int64_t)(p_calib[1] - p_calib[2]) + + (int64_t)p_lut[1] * (int64_t)(p_calib[2] - p_calib[0]); + c = div64_s64(val1, val2); + dev_dbg(&st->client->dev, "val1 = %lld, val2 = %lld, c = %lld\n", + val1, val2, c); + val1 = (int64_t)p_calib[0] * (int64_t)p_lut[0] - + (int64_t)p_calib[1] * (int64_t)p_lut[1] - + (int64_t)(p_calib[1] - p_calib[0]) * c; + val2 = (int64_t)p_lut[0] - (int64_t)p_lut[1]; + a = div64_s64(val1, val2); + dev_dbg(&st->client->dev, "val1 = %lld, val2 = %lld, a = %lld\n", + val1, val2, a); + b = ((int64_t)p_calib[0] - a) * ((int64_t)p_lut[0] + c); + dev_dbg(&st->client->dev, "b = %lld\n", b); + + /* + * pressure_Pa = a + (b / (c + raw_pressure)) + * pressure_mPa = 1000 * pressure_Pa + */ + pressure_mPa = 1000LL * a + div64_s64(1000LL * b, c + raw_pressure); + + return pressure_mPa; +} + +static int icp10100_read_raw_measures(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + struct icp10100_state *st = iio_priv(indio_dev); + uint32_t raw_pressure; + uint16_t raw_temp; + uint32_t pressure_mPa; + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = icp10100_get_measures(st, &raw_pressure, &raw_temp); + if (ret) + goto error_release; + + switch (chan->type) { + case IIO_PRESSURE: + pressure_mPa = icp10100_get_pressure(st, raw_pressure, + raw_temp); + /* mPa to kPa */ + *val = pressure_mPa / 1000000; + *val2 = pressure_mPa % 1000000; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case IIO_TEMP: + *val = raw_temp; + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + break; + } + +error_release: + iio_device_release_direct(indio_dev); + return ret; +} + +static int icp10100_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct icp10100_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + return icp10100_read_raw_measures(indio_dev, chan, val, val2); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + /* 1000 * 175°C / 65536 in m°C */ + *val = 2; + *val2 = 670288; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + /* 1000 * -45°C in m°C */ + *val = -45000; + return IIO_VAL_INT; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + mutex_lock(&st->lock); + *val = 1 << st->mode; + mutex_unlock(&st->lock); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int icp10100_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + static int oversamplings[] = {1, 2, 4, 8}; + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = oversamplings; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(oversamplings); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int icp10100_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct icp10100_state *st = iio_priv(indio_dev); + unsigned int mode; + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + /* oversampling is always positive and a power of 2 */ + if (val <= 0 || !is_power_of_2(val)) + return -EINVAL; + mode = ilog2(val); + if (mode >= ICP10100_MODE_NB) + return -EINVAL; + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + mutex_lock(&st->lock); + st->mode = mode; + mutex_unlock(&st->lock); + iio_device_release_direct(indio_dev); + return 0; + default: + return -EINVAL; + } +} + +static int icp10100_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static const struct iio_info icp10100_info = { + .read_raw = icp10100_read_raw, + .read_avail = icp10100_read_avail, + .write_raw = icp10100_write_raw, + .write_raw_get_fmt = icp10100_write_raw_get_fmt, +}; + +static const struct iio_chan_spec icp10100_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_shared_by_all = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + }, { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .info_mask_shared_by_all = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + }, +}; + +static int icp10100_enable_regulator(struct icp10100_state *st) +{ + int ret; + + ret = regulator_enable(st->vdd); + if (ret) + return ret; + msleep(100); + + return 0; +} + +static void icp10100_disable_regulator_action(void *data) +{ + struct icp10100_state *st = data; + int ret; + + ret = regulator_disable(st->vdd); + if (ret) + dev_err(&st->client->dev, "error %d disabling vdd\n", ret); +} + +static void icp10100_pm_disable(void *data) +{ + struct device *dev = data; + + pm_runtime_disable(dev); +} + +static int icp10100_probe(struct i2c_client *client) +{ + struct iio_dev *indio_dev; + struct icp10100_state *st; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "plain i2c transactions not supported\n"); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + i2c_set_clientdata(client, indio_dev); + indio_dev->name = client->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = icp10100_channels; + indio_dev->num_channels = ARRAY_SIZE(icp10100_channels); + indio_dev->info = &icp10100_info; + + st = iio_priv(indio_dev); + mutex_init(&st->lock); + st->client = client; + st->mode = ICP10100_MODE_N; + + st->vdd = devm_regulator_get(&client->dev, "vdd"); + if (IS_ERR(st->vdd)) + return PTR_ERR(st->vdd); + + ret = icp10100_enable_regulator(st); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&client->dev, + icp10100_disable_regulator_action, st); + if (ret) + return ret; + + /* has to be done before the first i2c communication */ + crc8_populate_msb(icp10100_crc8_table, ICP10100_CRC8_POLYNOMIAL); + + ret = icp10100_init_chip(st); + if (ret) { + dev_err(&client->dev, "init chip error %d\n", ret); + return ret; + } + + /* enable runtime pm with autosuspend delay of 2s */ + pm_runtime_get_noresume(&client->dev); + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, 2000); + pm_runtime_use_autosuspend(&client->dev); + pm_runtime_put(&client->dev); + ret = devm_add_action_or_reset(&client->dev, icp10100_pm_disable, + &client->dev); + if (ret) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static int icp10100_suspend(struct device *dev) +{ + struct icp10100_state *st = iio_priv(dev_get_drvdata(dev)); + int ret; + + mutex_lock(&st->lock); + ret = regulator_disable(st->vdd); + mutex_unlock(&st->lock); + + return ret; +} + +static int icp10100_resume(struct device *dev) +{ + struct icp10100_state *st = iio_priv(dev_get_drvdata(dev)); + int ret; + + mutex_lock(&st->lock); + + ret = icp10100_enable_regulator(st); + if (ret) + goto out_unlock; + + /* reset chip */ + ret = icp10100_send_cmd(st, &icp10100_cmd_soft_reset, NULL, 0); + +out_unlock: + mutex_unlock(&st->lock); + return ret; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(icp10100_pm, icp10100_suspend, icp10100_resume, + NULL); + +static const struct of_device_id icp10100_of_match[] = { + { + .compatible = "invensense,icp10100", + }, + { } +}; +MODULE_DEVICE_TABLE(of, icp10100_of_match); + +static const struct i2c_device_id icp10100_id[] = { + { .name = "icp10100" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, icp10100_id); + +static struct i2c_driver icp10100_driver = { + .driver = { + .name = "icp10100", + .pm = pm_ptr(&icp10100_pm), + .of_match_table = icp10100_of_match, + }, + .probe = icp10100_probe, + .id_table = icp10100_id, +}; +module_i2c_driver(icp10100_driver); + +MODULE_AUTHOR("InvenSense, Inc."); +MODULE_DESCRIPTION("InvenSense icp10100 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/mpl115.c b/drivers/iio/pressure/mpl115.c new file mode 100644 index 000000000..2929ea8e4 --- /dev/null +++ b/drivers/iio/pressure/mpl115.c @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * mpl115.c - Support for Freescale MPL115A pressure/temperature sensor + * + * Copyright (c) 2014 Peter Meerwald + * + * TODO: synchronization with system suspend + */ + +#include +#include +#include +#include + +#include "mpl115.h" + +#define MPL115_PADC 0x00 /* pressure ADC output value, MSB first, 10 bit */ +#define MPL115_TADC 0x02 /* temperature ADC output value, MSB first, 10 bit */ +#define MPL115_A0 0x04 /* 12 bit integer, 3 bit fraction */ +#define MPL115_B1 0x06 /* 2 bit integer, 13 bit fraction */ +#define MPL115_B2 0x08 /* 1 bit integer, 14 bit fraction */ +#define MPL115_C12 0x0a /* 0 bit integer, 13 bit fraction */ +#define MPL115_CONVERT 0x12 /* convert temperature and pressure */ + +struct mpl115_data { + struct device *dev; + struct mutex lock; + s16 a0; + s16 b1, b2; + s16 c12; + struct gpio_desc *shutdown; + const struct mpl115_ops *ops; +}; + +static int mpl115_request(struct mpl115_data *data) +{ + int ret = data->ops->write(data->dev, MPL115_CONVERT, 0); + + if (ret < 0) + return ret; + + usleep_range(3000, 4000); + + return 0; +} + +static int mpl115_comp_pressure(struct mpl115_data *data, int *val, int *val2) +{ + int ret; + u16 padc, tadc; + int a1, y1, pcomp; + unsigned kpa; + + mutex_lock(&data->lock); + ret = mpl115_request(data); + if (ret < 0) + goto done; + + ret = data->ops->read(data->dev, MPL115_PADC); + if (ret < 0) + goto done; + padc = ret >> 6; + + ret = data->ops->read(data->dev, MPL115_TADC); + if (ret < 0) + goto done; + tadc = ret >> 6; + + /* see Freescale AN3785 */ + a1 = data->b1 + ((data->c12 * tadc) >> 11); + y1 = (data->a0 << 10) + a1 * padc; + + /* compensated pressure with 4 fractional bits */ + pcomp = (y1 + ((data->b2 * (int) tadc) >> 1)) >> 9; + + kpa = pcomp * (115 - 50) / 1023 + (50 << 4); + *val = kpa >> 4; + *val2 = (kpa & 15) * (1000000 >> 4); +done: + mutex_unlock(&data->lock); + return ret; +} + +static int mpl115_read_temp(struct mpl115_data *data) +{ + int ret; + + mutex_lock(&data->lock); + ret = mpl115_request(data); + if (ret < 0) + goto done; + ret = data->ops->read(data->dev, MPL115_TADC); +done: + mutex_unlock(&data->lock); + return ret; +} + +static int mpl115_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mpl115_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + pm_runtime_get_sync(data->dev); + ret = mpl115_comp_pressure(data, val, val2); + pm_runtime_put_autosuspend(data->dev); + if (ret < 0) + return ret; + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_RAW: + pm_runtime_get_sync(data->dev); + /* temperature -5.35 C / LSB, 472 LSB is 25 C */ + ret = mpl115_read_temp(data); + pm_runtime_put_autosuspend(data->dev); + if (ret < 0) + return ret; + *val = ret >> 6; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + *val = -605; + *val2 = 750000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SCALE: + *val = -186; + *val2 = 915888; + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static const struct iio_chan_spec mpl115_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = + BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static const struct iio_info mpl115_info = { + .read_raw = &mpl115_read_raw, +}; + +int mpl115_probe(struct device *dev, const char *name, + const struct mpl115_ops *ops) +{ + struct mpl115_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->dev = dev; + data->ops = ops; + mutex_init(&data->lock); + + indio_dev->info = &mpl115_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = mpl115_channels; + indio_dev->num_channels = ARRAY_SIZE(mpl115_channels); + + ret = data->ops->init(data->dev); + if (ret) + return ret; + + dev_set_drvdata(dev, indio_dev); + + ret = data->ops->read(data->dev, MPL115_A0); + if (ret < 0) + return ret; + data->a0 = ret; + ret = data->ops->read(data->dev, MPL115_B1); + if (ret < 0) + return ret; + data->b1 = ret; + ret = data->ops->read(data->dev, MPL115_B2); + if (ret < 0) + return ret; + data->b2 = ret; + ret = data->ops->read(data->dev, MPL115_C12); + if (ret < 0) + return ret; + data->c12 = ret; + + data->shutdown = devm_gpiod_get_optional(dev, "shutdown", + GPIOD_OUT_LOW); + if (IS_ERR(data->shutdown)) + return dev_err_probe(dev, PTR_ERR(data->shutdown), + "cannot get shutdown gpio\n"); + + if (data->shutdown) { + /* Enable runtime PM */ + ret = pm_runtime_set_active(dev); + if (ret) + return ret; + + /* + * As the device takes 3 ms to come up with a fresh + * reading after power-on and 5 ms to actually power-on, + * do not shut it down unnecessarily. Set autosuspend to + * 2000 ms. + */ + pm_runtime_set_autosuspend_delay(dev, 2000); + pm_runtime_use_autosuspend(dev); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + dev_dbg(dev, "low-power mode enabled"); + } else + dev_dbg(dev, "low-power mode disabled"); + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS_GPL(mpl115_probe, "IIO_MPL115"); + +static int mpl115_runtime_suspend(struct device *dev) +{ + struct mpl115_data *data = iio_priv(dev_get_drvdata(dev)); + + gpiod_set_value(data->shutdown, 1); + + return 0; +} + +static int mpl115_runtime_resume(struct device *dev) +{ + struct mpl115_data *data = iio_priv(dev_get_drvdata(dev)); + + gpiod_set_value(data->shutdown, 0); + usleep_range(5000, 6000); + + return 0; +} + +EXPORT_NS_RUNTIME_DEV_PM_OPS(mpl115_dev_pm_ops, mpl115_runtime_suspend, + mpl115_runtime_resume, NULL, IIO_MPL115); + +MODULE_AUTHOR("Peter Meerwald "); +MODULE_DESCRIPTION("Freescale MPL115 pressure/temperature driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/mpl115.h b/drivers/iio/pressure/mpl115.h new file mode 100644 index 000000000..78a0068a1 --- /dev/null +++ b/drivers/iio/pressure/mpl115.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Freescale MPL115A pressure/temperature sensor + * + * Copyright (c) 2014 Peter Meerwald + * Copyright (c) 2016 Akinobu Mita + */ + +#include + +#ifndef _MPL115_H_ +#define _MPL115_H_ + +struct mpl115_ops { + int (*init)(struct device *); + int (*read)(struct device *, u8); + int (*write)(struct device *, u8, u8); +}; + +int mpl115_probe(struct device *dev, const char *name, + const struct mpl115_ops *ops); + +/*PM ops */ +extern const struct dev_pm_ops mpl115_dev_pm_ops; + +#endif diff --git a/drivers/iio/pressure/mpl115_i2c.c b/drivers/iio/pressure/mpl115_i2c.c new file mode 100644 index 000000000..4a43eba07 --- /dev/null +++ b/drivers/iio/pressure/mpl115_i2c.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Freescale MPL115A2 pressure/temperature sensor + * + * Copyright (c) 2014 Peter Meerwald + * + * (7-bit I2C slave address 0x60) + * + * Datasheet: http://www.nxp.com/files/sensors/doc/data_sheet/MPL115A2.pdf + */ + +#include +#include + +#include "mpl115.h" + +static int mpl115_i2c_init(struct device *dev) +{ + return 0; +} + +static int mpl115_i2c_read(struct device *dev, u8 address) +{ + return i2c_smbus_read_word_swapped(to_i2c_client(dev), address); +} + +static int mpl115_i2c_write(struct device *dev, u8 address, u8 value) +{ + return i2c_smbus_write_byte_data(to_i2c_client(dev), address, value); +} + +static const struct mpl115_ops mpl115_i2c_ops = { + .init = mpl115_i2c_init, + .read = mpl115_i2c_read, + .write = mpl115_i2c_write, +}; + +static int mpl115_i2c_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -EOPNOTSUPP; + + return mpl115_probe(&client->dev, id->name, &mpl115_i2c_ops); +} + +static const struct i2c_device_id mpl115_i2c_id[] = { + { .name = "mpl115" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mpl115_i2c_id); + +static struct i2c_driver mpl115_i2c_driver = { + .driver = { + .name = "mpl115", + .pm = pm_ptr(&mpl115_dev_pm_ops), + }, + .probe = mpl115_i2c_probe, + .id_table = mpl115_i2c_id, +}; +module_i2c_driver(mpl115_i2c_driver); + +MODULE_AUTHOR("Peter Meerwald "); +MODULE_DESCRIPTION("Freescale MPL115A2 pressure/temperature driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_MPL115"); diff --git a/drivers/iio/pressure/mpl115_spi.c b/drivers/iio/pressure/mpl115_spi.c new file mode 100644 index 000000000..3cff616e8 --- /dev/null +++ b/drivers/iio/pressure/mpl115_spi.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Freescale MPL115A1 pressure/temperature sensor + * + * Copyright (c) 2016 Akinobu Mita + * + * Datasheet: http://www.nxp.com/files/sensors/doc/data_sheet/MPL115A1.pdf + */ + +#include +#include + +#include "mpl115.h" + +#define MPL115_SPI_WRITE(address) ((address) << 1) +#define MPL115_SPI_READ(address) (0x80 | (address) << 1) + +struct mpl115_spi_buf { + u8 tx[4]; + u8 rx[4]; +}; + +static int mpl115_spi_init(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + struct mpl115_spi_buf *buf; + + buf = devm_kzalloc(dev, sizeof(*buf), GFP_KERNEL); + if (!buf) + return -ENOMEM; + + spi_set_drvdata(spi, buf); + + return 0; +} + +static int mpl115_spi_read(struct device *dev, u8 address) +{ + struct spi_device *spi = to_spi_device(dev); + struct mpl115_spi_buf *buf = spi_get_drvdata(spi); + struct spi_transfer xfer = { + .tx_buf = buf->tx, + .rx_buf = buf->rx, + .len = 4, + }; + int ret; + + buf->tx[0] = MPL115_SPI_READ(address); + buf->tx[2] = MPL115_SPI_READ(address + 1); + + ret = spi_sync_transfer(spi, &xfer, 1); + if (ret) + return ret; + + return (buf->rx[1] << 8) | buf->rx[3]; +} + +static int mpl115_spi_write(struct device *dev, u8 address, u8 value) +{ + struct spi_device *spi = to_spi_device(dev); + struct mpl115_spi_buf *buf = spi_get_drvdata(spi); + struct spi_transfer xfer = { + .tx_buf = buf->tx, + .len = 2, + }; + + buf->tx[0] = MPL115_SPI_WRITE(address); + buf->tx[1] = value; + + return spi_sync_transfer(spi, &xfer, 1); +} + +static const struct mpl115_ops mpl115_spi_ops = { + .init = mpl115_spi_init, + .read = mpl115_spi_read, + .write = mpl115_spi_write, +}; + +static int mpl115_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + + return mpl115_probe(&spi->dev, id->name, &mpl115_spi_ops); +} + +static const struct spi_device_id mpl115_spi_ids[] = { + { .name = "mpl115" }, + { } +}; +MODULE_DEVICE_TABLE(spi, mpl115_spi_ids); + +static struct spi_driver mpl115_spi_driver = { + .driver = { + .name = "mpl115", + .pm = pm_ptr(&mpl115_dev_pm_ops), + }, + .probe = mpl115_spi_probe, + .id_table = mpl115_spi_ids, +}; +module_spi_driver(mpl115_spi_driver); + +MODULE_AUTHOR("Akinobu Mita "); +MODULE_DESCRIPTION("Freescale MPL115A1 pressure/temperature driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_MPL115"); diff --git a/drivers/iio/pressure/mpl3115.c b/drivers/iio/pressure/mpl3115.c new file mode 100644 index 000000000..f20fa6ad1 --- /dev/null +++ b/drivers/iio/pressure/mpl3115.c @@ -0,0 +1,811 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * mpl3115.c - Support for Freescale MPL3115A2 pressure/temperature sensor + * + * Copyright (c) 2013 Peter Meerwald + * + * (7-bit I2C slave address 0x60) + * + * TODO: FIFO buffer, altimeter mode, oversampling, continuous mode, + * user offset correction, raw mode + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define MPL3115_STATUS 0x00 +#define MPL3115_OUT_PRESS 0x01 /* MSB first, 20 bit */ +#define MPL3115_OUT_TEMP 0x04 /* MSB first, 12 bit */ +#define MPL3115_WHO_AM_I 0x0c +#define MPL3115_INT_SOURCE 0x12 +#define MPL3115_PT_DATA_CFG 0x13 +#define MPL3115_PRESS_TGT 0x16 /* MSB first, 16 bit */ +#define MPL3115_TEMP_TGT 0x18 +#define MPL3115_CTRL_REG1 0x26 +#define MPL3115_CTRL_REG2 0x27 +#define MPL3115_CTRL_REG3 0x28 +#define MPL3115_CTRL_REG4 0x29 +#define MPL3115_CTRL_REG5 0x2a + +#define MPL3115_DEVICE_ID 0xc4 + +#define MPL3115_STATUS_PRESS_RDY BIT(2) +#define MPL3115_STATUS_TEMP_RDY BIT(1) + +#define MPL3115_INT_SRC_DRDY BIT(7) +#define MPL3115_INT_SRC_PTH BIT(3) +#define MPL3115_INT_SRC_TTH BIT(2) + +#define MPL3115_PT_DATA_EVENT_ALL GENMASK(2, 0) + +#define MPL3115_CTRL1_RESET BIT(2) /* software reset */ +#define MPL3115_CTRL1_OST BIT(1) /* initiate measurement */ +#define MPL3115_CTRL1_ACTIVE BIT(0) /* continuous measurement */ +#define MPL3115_CTRL1_OS_258MS GENMASK(5, 4) /* 64x oversampling */ + +#define MPL3115_CTRL2_ST GENMASK(3, 0) + +#define MPL3115_CTRL3_IPOL1 BIT(5) +#define MPL3115_CTRL3_IPOL2 BIT(1) + +#define MPL3115_CTRL4_INT_EN_DRDY BIT(7) +#define MPL3115_CTRL4_INT_EN_PTH BIT(3) +#define MPL3115_CTRL4_INT_EN_TTH BIT(2) + +#define MPL3115_CTRL5_INT_CFG_DRDY BIT(7) + +static const unsigned int mpl3115_samp_freq_table[][2] = { + { 1, 0 }, + { 0, 500000 }, + { 0, 250000 }, + { 0, 125000 }, + { 0, 62500 }, + { 0, 31250 }, + { 0, 15625 }, + { 0, 7812 }, + { 0, 3906 }, + { 0, 1953 }, + { 0, 976 }, + { 0, 488 }, + { 0, 244 }, + { 0, 122 }, + { 0, 61 }, + { 0, 30 }, +}; + +struct mpl3115_data { + struct i2c_client *client; + struct iio_trigger *drdy_trig; + struct mutex lock; + u8 ctrl_reg1; + u8 ctrl_reg4; +}; + +enum mpl3115_irq_pin { + MPL3115_IRQ_INT1, + MPL3115_IRQ_INT2, +}; + +static int mpl3115_request(struct mpl3115_data *data) +{ + int ret, tries = 15; + + /* trigger measurement */ + ret = i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG1, + data->ctrl_reg1 | MPL3115_CTRL1_OST); + if (ret < 0) + return ret; + + while (tries-- > 0) { + ret = i2c_smbus_read_byte_data(data->client, MPL3115_CTRL_REG1); + if (ret < 0) + return ret; + /* wait for data ready, i.e. OST cleared */ + if (!(ret & MPL3115_CTRL1_OST)) + break; + msleep(20); + } + + if (tries < 0) { + dev_err(&data->client->dev, "data not ready\n"); + return -EIO; + } + + return 0; +} + +static int mpl3115_read_info_raw(struct mpl3115_data *data, + struct iio_chan_spec const *chan, int *val) +{ + int ret; + + switch (chan->type) { + case IIO_PRESSURE: { /* in 0.25 pascal / LSB */ + u8 press_be24[3]; + + guard(mutex)(&data->lock); + ret = mpl3115_request(data); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_i2c_block_data(data->client, + MPL3115_OUT_PRESS, + sizeof(press_be24), + press_be24); + if (ret < 0) + return ret; + + /* + * The pressure channel shift is applied in the case where the + * data (24-bit big endian) is read into a 32-bit buffer. Here + * the data is stored in a 24-bit buffer, so the shift is 4. + */ + *val = get_unaligned_be24(press_be24) >> 4; + return IIO_VAL_INT; + } + case IIO_TEMP: { /* in 0.0625 celsius / LSB */ + __be16 tmp; + + guard(mutex)(&data->lock); + ret = mpl3115_request(data); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_i2c_block_data(data->client, + MPL3115_OUT_TEMP, + 2, (u8 *) &tmp); + if (ret < 0) + return ret; + + *val = sign_extend32(be16_to_cpu(tmp) >> chan->scan_type.shift, + chan->scan_type.realbits - 1); + return IIO_VAL_INT; + } + default: + return -EINVAL; + } +} + +static int mpl3115_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mpl3115_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = mpl3115_read_info_raw(data, chan, val); + iio_device_release_direct(indio_dev); + return ret; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_PRESSURE: + *val = 0; + *val2 = 250; /* want kilopascal */ + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = 0; + *val2 = 62500; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + ret = i2c_smbus_read_byte_data(data->client, MPL3115_CTRL_REG2); + if (ret < 0) + return ret; + + ret = FIELD_GET(MPL3115_CTRL2_ST, ret); + + *val = mpl3115_samp_freq_table[ret][0]; + *val2 = mpl3115_samp_freq_table[ret][1]; + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static int mpl3115_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + if (mask != IIO_CHAN_INFO_SAMP_FREQ) + return -EINVAL; + + *type = IIO_VAL_INT_PLUS_MICRO; + *length = ARRAY_SIZE(mpl3115_samp_freq_table) * 2; + *vals = (int *)mpl3115_samp_freq_table; + return IIO_AVAIL_LIST; +} + +static int mpl3115_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int val, int val2, long mask) +{ + struct mpl3115_data *data = iio_priv(indio_dev); + int i, ret; + + if (mask != IIO_CHAN_INFO_SAMP_FREQ) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(mpl3115_samp_freq_table); i++) + if (val == mpl3115_samp_freq_table[i][0] && + val2 == mpl3115_samp_freq_table[i][1]) + break; + + if (i == ARRAY_SIZE(mpl3115_samp_freq_table)) + return -EINVAL; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG2, + FIELD_PREP(MPL3115_CTRL2_ST, i)); + iio_device_release_direct(indio_dev); + return ret; +} + +static int mpl3115_fill_trig_buffer(struct iio_dev *indio_dev, u8 *buffer) +{ + struct mpl3115_data *data = iio_priv(indio_dev); + int ret, pos = 0; + + if (!(data->ctrl_reg1 & MPL3115_CTRL1_ACTIVE)) { + ret = mpl3115_request(data); + if (ret < 0) + return ret; + } + + if (test_bit(0, indio_dev->active_scan_mask)) { + ret = i2c_smbus_read_i2c_block_data(data->client, + MPL3115_OUT_PRESS, 3, &buffer[pos]); + if (ret < 0) + return ret; + pos += 4; + } + + if (test_bit(1, indio_dev->active_scan_mask)) { + ret = i2c_smbus_read_i2c_block_data(data->client, + MPL3115_OUT_TEMP, 2, &buffer[pos]); + if (ret < 0) + return ret; + } + + return 0; +} + +static irqreturn_t mpl3115_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct mpl3115_data *data = iio_priv(indio_dev); + /* + * 32-bit channel + 16-bit channel + padding + ts + * Note that it is possible for only one of the first 2 + * channels to be enabled. If that happens, the first element + * of the buffer may be either 16 or 32-bits. As such we cannot + * use a simple structure definition to express this data layout. + */ + u8 buffer[16] __aligned(8) = { }; + int ret; + + mutex_lock(&data->lock); + ret = mpl3115_fill_trig_buffer(indio_dev, buffer); + mutex_unlock(&data->lock); + if (ret) + goto done; + + iio_push_to_buffers_with_ts(indio_dev, buffer, sizeof(buffer), + iio_get_time_ns(indio_dev)); + +done: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static const struct iio_event_spec mpl3115_temp_press_event[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE), + }, +}; + +static const struct iio_chan_spec mpl3115_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 20, + .storagebits = 32, + .shift = 12, + .endianness = IIO_BE, + }, + .event_spec = mpl3115_temp_press_event, + .num_event_specs = ARRAY_SIZE(mpl3115_temp_press_event), + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 12, + .storagebits = 16, + .shift = 4, + .endianness = IIO_BE, + }, + .event_spec = mpl3115_temp_press_event, + .num_event_specs = ARRAY_SIZE(mpl3115_temp_press_event), + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static irqreturn_t mpl3115_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct mpl3115_data *data = iio_priv(indio_dev); + int ret; + u8 val_press[3]; + __be16 val_temp; + + ret = i2c_smbus_read_byte_data(data->client, MPL3115_INT_SOURCE); + if (ret < 0) + return IRQ_HANDLED; + + if (!(ret & (MPL3115_INT_SRC_TTH | MPL3115_INT_SRC_PTH | + MPL3115_INT_SRC_DRDY))) + return IRQ_NONE; + + if (ret & MPL3115_INT_SRC_DRDY) + iio_trigger_poll_nested(data->drdy_trig); + + if (ret & MPL3115_INT_SRC_PTH) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PRESSURE, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + + /* Reset the SRC_PTH bit in INT_SOURCE */ + i2c_smbus_read_i2c_block_data(data->client, + MPL3115_OUT_PRESS, + sizeof(val_press), val_press); + } + + if (ret & MPL3115_INT_SRC_TTH) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + + /* Reset the SRC_TTH bit in INT_SOURCE */ + i2c_smbus_read_i2c_block_data(data->client, + MPL3115_OUT_TEMP, + sizeof(val_temp), + (u8 *)&val_temp); + } + + return IRQ_HANDLED; +} + +static int mpl3115_config_interrupt(struct mpl3115_data *data, + u8 ctrl_reg1, u8 ctrl_reg4) +{ + int ret; + + ret = i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG1, + ctrl_reg1); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG4, + ctrl_reg4); + if (ret < 0) + goto reg1_cleanup; + + data->ctrl_reg1 = ctrl_reg1; + data->ctrl_reg4 = ctrl_reg4; + + return 0; + +reg1_cleanup: + i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG1, + data->ctrl_reg1); + return ret; +} + +static int mpl3115_set_trigger_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct mpl3115_data *data = iio_priv(indio_dev); + u8 ctrl_reg1, ctrl_reg4; + + guard(mutex)(&data->lock); + + ctrl_reg1 = data->ctrl_reg1; + ctrl_reg4 = data->ctrl_reg4; + + if (state) { + ctrl_reg1 |= MPL3115_CTRL1_ACTIVE; + ctrl_reg4 |= MPL3115_CTRL4_INT_EN_DRDY; + } else { + ctrl_reg4 &= ~MPL3115_CTRL4_INT_EN_DRDY; + + if (!ctrl_reg4) + ctrl_reg1 &= ~MPL3115_CTRL1_ACTIVE; + } + + return mpl3115_config_interrupt(data, ctrl_reg1, ctrl_reg4); +} + +static const struct iio_trigger_ops mpl3115_trigger_ops = { + .set_trigger_state = mpl3115_set_trigger_state, +}; + +static int mpl3115_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct mpl3115_data *data = iio_priv(indio_dev); + + if (chan->type == IIO_PRESSURE) + return !!(data->ctrl_reg4 & MPL3115_CTRL4_INT_EN_PTH); + + if (chan->type == IIO_TEMP) + return !!(data->ctrl_reg4 & MPL3115_CTRL4_INT_EN_TTH); + + return -EINVAL; +} + +static int mpl3115_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct mpl3115_data *data = iio_priv(indio_dev); + u8 int_en_mask; + u8 ctrl_reg1, ctrl_reg4; + + switch (chan->type) { + case IIO_PRESSURE: + int_en_mask = MPL3115_CTRL4_INT_EN_PTH; + break; + case IIO_TEMP: + int_en_mask = MPL3115_CTRL4_INT_EN_TTH; + break; + default: + return -EINVAL; + } + + guard(mutex)(&data->lock); + + ctrl_reg1 = data->ctrl_reg1; + ctrl_reg4 = data->ctrl_reg4; + + if (state) { + ctrl_reg1 |= MPL3115_CTRL1_ACTIVE; + ctrl_reg4 |= int_en_mask; + } else { + ctrl_reg4 &= ~int_en_mask; + + if (!ctrl_reg4) + ctrl_reg1 &= ~MPL3115_CTRL1_ACTIVE; + } + + return mpl3115_config_interrupt(data, ctrl_reg1, ctrl_reg4); +} + +static int mpl3115_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct mpl3115_data *data = iio_priv(indio_dev); + int ret; + __be16 press_tgt; + + if (info != IIO_EV_INFO_VALUE) + return -EINVAL; + + switch (chan->type) { + case IIO_PRESSURE: + ret = i2c_smbus_read_i2c_block_data(data->client, + MPL3115_PRESS_TGT, + sizeof(press_tgt), + (u8 *)&press_tgt); + if (ret < 0) + return ret; + + /* + * Target value for the pressure is 16-bit unsigned value, + * expressed in 2 Pa units + */ + *val = be16_to_cpu(press_tgt) << 1; + + return IIO_VAL_INT; + case IIO_TEMP: + ret = i2c_smbus_read_byte_data(data->client, MPL3115_TEMP_TGT); + if (ret < 0) + return ret; + + /* Target value for the temperature is 8-bit 2's complement */ + *val = sign_extend32(ret, 7); + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int mpl3115_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct mpl3115_data *data = iio_priv(indio_dev); + __be16 press_tgt; + + if (info != IIO_EV_INFO_VALUE) + return -EINVAL; + + switch (chan->type) { + case IIO_PRESSURE: + val >>= 1; + + if (val < 0 || val > U16_MAX) + return -EINVAL; + + press_tgt = cpu_to_be16(val); + + return i2c_smbus_write_i2c_block_data(data->client, + MPL3115_PRESS_TGT, + sizeof(press_tgt), + (u8 *)&press_tgt); + case IIO_TEMP: + if (val < S8_MIN || val > S8_MAX) + return -EINVAL; + + return i2c_smbus_write_byte_data(data->client, + MPL3115_TEMP_TGT, val); + default: + return -EINVAL; + } +} + +static const struct iio_info mpl3115_info = { + .read_raw = &mpl3115_read_raw, + .read_avail = &mpl3115_read_avail, + .write_raw = &mpl3115_write_raw, + .read_event_config = mpl3115_read_event_config, + .write_event_config = mpl3115_write_event_config, + .read_event_value = mpl3115_read_thresh, + .write_event_value = mpl3115_write_thresh, +}; + +static int mpl3115_trigger_probe(struct mpl3115_data *data, + struct iio_dev *indio_dev) +{ + struct fwnode_handle *fwnode = dev_fwnode(&data->client->dev); + int ret, irq, irq_type, irq_pin = MPL3115_IRQ_INT1; + + irq = fwnode_irq_get_byname(fwnode, "INT1"); + if (irq < 0) { + irq = fwnode_irq_get_byname(fwnode, "INT2"); + if (irq < 0) + return 0; + + irq_pin = MPL3115_IRQ_INT2; + } + + irq_type = irq_get_trigger_type(irq); + if (irq_type != IRQF_TRIGGER_RISING && irq_type != IRQF_TRIGGER_FALLING) + return -EINVAL; + + ret = i2c_smbus_write_byte_data(data->client, MPL3115_PT_DATA_CFG, + MPL3115_PT_DATA_EVENT_ALL); + if (ret < 0) + return ret; + + if (irq_pin == MPL3115_IRQ_INT1) { + ret = i2c_smbus_write_byte_data(data->client, + MPL3115_CTRL_REG5, + MPL3115_CTRL5_INT_CFG_DRDY); + if (ret) + return ret; + + if (irq_type == IRQF_TRIGGER_RISING) { + ret = i2c_smbus_write_byte_data(data->client, + MPL3115_CTRL_REG3, + MPL3115_CTRL3_IPOL1); + if (ret) + return ret; + } + } else if (irq_type == IRQF_TRIGGER_RISING) { + ret = i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG3, + MPL3115_CTRL3_IPOL2); + if (ret) + return ret; + } + + data->drdy_trig = devm_iio_trigger_alloc(&data->client->dev, + "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->drdy_trig) + return -ENOMEM; + + data->drdy_trig->ops = &mpl3115_trigger_ops; + iio_trigger_set_drvdata(data->drdy_trig, indio_dev); + + ret = devm_request_threaded_irq(&data->client->dev, irq, NULL, + mpl3115_interrupt_handler, + IRQF_ONESHOT, + "mpl3115_irq", indio_dev); + if (ret) + return ret; + + ret = devm_iio_trigger_register(&data->client->dev, data->drdy_trig); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(data->drdy_trig); + + return 0; +} + +static int mpl3115_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct mpl3115_data *data; + struct iio_dev *indio_dev; + int ret; + + ret = i2c_smbus_read_byte_data(client, MPL3115_WHO_AM_I); + if (ret < 0) + return ret; + if (ret != MPL3115_DEVICE_ID) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + mutex_init(&data->lock); + + i2c_set_clientdata(client, indio_dev); + indio_dev->info = &mpl3115_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = mpl3115_channels; + indio_dev->num_channels = ARRAY_SIZE(mpl3115_channels); + + /* software reset, I2C transfer is aborted (fails) */ + i2c_smbus_write_byte_data(client, MPL3115_CTRL_REG1, + MPL3115_CTRL1_RESET); + msleep(50); + + data->ctrl_reg1 = MPL3115_CTRL1_OS_258MS; + ret = i2c_smbus_write_byte_data(client, MPL3115_CTRL_REG1, + data->ctrl_reg1); + if (ret < 0) + return ret; + + ret = mpl3115_trigger_probe(data, indio_dev); + if (ret) + return ret; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + mpl3115_trigger_handler, NULL); + if (ret < 0) + return ret; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto buffer_cleanup; + return 0; + +buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); + return ret; +} + +static int mpl3115_standby(struct mpl3115_data *data) +{ + return i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG1, + data->ctrl_reg1 & ~MPL3115_CTRL1_ACTIVE); +} + +static void mpl3115_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + mpl3115_standby(iio_priv(indio_dev)); +} + +static int mpl3115_suspend(struct device *dev) +{ + return mpl3115_standby(iio_priv(i2c_get_clientdata( + to_i2c_client(dev)))); +} + +static int mpl3115_resume(struct device *dev) +{ + struct mpl3115_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + + return i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG1, + data->ctrl_reg1); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(mpl3115_pm_ops, mpl3115_suspend, + mpl3115_resume); + +static const struct i2c_device_id mpl3115_id[] = { + { .name = "mpl3115" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mpl3115_id); + +static const struct of_device_id mpl3115_of_match[] = { + { .compatible = "fsl,mpl3115" }, + { } +}; +MODULE_DEVICE_TABLE(of, mpl3115_of_match); + +static struct i2c_driver mpl3115_driver = { + .driver = { + .name = "mpl3115", + .of_match_table = mpl3115_of_match, + .pm = pm_sleep_ptr(&mpl3115_pm_ops), + }, + .probe = mpl3115_probe, + .remove = mpl3115_remove, + .id_table = mpl3115_id, +}; +module_i2c_driver(mpl3115_driver); + +MODULE_AUTHOR("Peter Meerwald "); +MODULE_DESCRIPTION("Freescale MPL3115 pressure/temperature driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/mprls0025pa.c b/drivers/iio/pressure/mprls0025pa.c new file mode 100644 index 000000000..c21f0a050 --- /dev/null +++ b/drivers/iio/pressure/mprls0025pa.c @@ -0,0 +1,443 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MPRLS0025PA - Honeywell MicroPressure pressure sensor series driver + * + * Copyright (c) Andreas Klinger + * Copyright (c) 2023-2025 Petre Rodan + * + * Data sheet: + * https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/micropressure-mpr-series/documents/sps-siot-mpr-series-datasheet-32332628-ciid-172626.pdf + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include + +#include "mprls0025pa.h" + +/* bits in status byte */ +#define MPR_ST_POWER BIT(6) /* device is powered */ +#define MPR_ST_BUSY BIT(5) /* device is busy */ + +/* + * support _RAW sysfs interface: + * + * Calculation formula from the datasheet: + * pressure = (press_cnt - outputmin) * scale + pmin + * with: + * * pressure - measured pressure in Pascal + * * press_cnt - raw value read from sensor + * * pmin - minimum pressure range value of sensor (data->pmin) + * * pmax - maximum pressure range value of sensor (data->pmax) + * * outputmin - minimum numerical range raw value delivered by sensor + * (mpr_func_spec.output_min) + * * outputmax - maximum numerical range raw value delivered by sensor + * (mpr_func_spec.output_max) + * * scale - (pmax - pmin) / (outputmax - outputmin) + * + * formula of the userspace: + * pressure = (raw + offset) * scale + * + * Values given to the userspace in sysfs interface: + * * raw - press_cnt + * * offset - (-1 * outputmin) + pmin / scale + * note: With all sensors from the datasheet pmin = 0 + * which reduces the offset to (-1 * outputmin) + */ + +/* + * transfer function A: 10% to 90% of 2^24 + * transfer function B: 2.5% to 22.5% of 2^24 + * transfer function C: 20% to 80% of 2^24 + */ +struct mpr_func_spec { + u32 output_min; + u32 output_max; +}; + +static const struct mpr_func_spec mpr_func_spec[] = { + [MPR_FUNCTION_A] = { .output_min = 1677722, .output_max = 15099494 }, + [MPR_FUNCTION_B] = { .output_min = 419430, .output_max = 3774874 }, + [MPR_FUNCTION_C] = { .output_min = 3355443, .output_max = 13421773 }, +}; + +enum mpr_variants { + MPR0001BA = 0x00, MPR01_6BA = 0x01, MPR02_5BA = 0x02, MPR0060MG = 0x03, + MPR0100MG = 0x04, MPR0160MG = 0x05, MPR0250MG = 0x06, MPR0400MG = 0x07, + MPR0600MG = 0x08, MPR0001BG = 0x09, MPR01_6BG = 0x0a, MPR02_5BG = 0x0b, + MPR0100KA = 0x0c, MPR0160KA = 0x0d, MPR0250KA = 0x0e, MPR0006KG = 0x0f, + MPR0010KG = 0x10, MPR0016KG = 0x11, MPR0025KG = 0x12, MPR0040KG = 0x13, + MPR0060KG = 0x14, MPR0100KG = 0x15, MPR0160KG = 0x16, MPR0250KG = 0x17, + MPR0015PA = 0x18, MPR0025PA = 0x19, MPR0030PA = 0x1a, MPR0001PG = 0x1b, + MPR0005PG = 0x1c, MPR0015PG = 0x1d, MPR0030PG = 0x1e, MPR0300YG = 0x1f, + MPR_VARIANTS_MAX +}; + +static const char * const mpr_triplet_variants[MPR_VARIANTS_MAX] = { + [MPR0001BA] = "0001BA", [MPR01_6BA] = "01.6BA", [MPR02_5BA] = "02.5BA", + [MPR0060MG] = "0060MG", [MPR0100MG] = "0100MG", [MPR0160MG] = "0160MG", + [MPR0250MG] = "0250MG", [MPR0400MG] = "0400MG", [MPR0600MG] = "0600MG", + [MPR0001BG] = "0001BG", [MPR01_6BG] = "01.6BG", [MPR02_5BG] = "02.5BG", + [MPR0100KA] = "0100KA", [MPR0160KA] = "0160KA", [MPR0250KA] = "0250KA", + [MPR0006KG] = "0006KG", [MPR0010KG] = "0010KG", [MPR0016KG] = "0016KG", + [MPR0025KG] = "0025KG", [MPR0040KG] = "0040KG", [MPR0060KG] = "0060KG", + [MPR0100KG] = "0100KG", [MPR0160KG] = "0160KG", [MPR0250KG] = "0250KG", + [MPR0015PA] = "0015PA", [MPR0025PA] = "0025PA", [MPR0030PA] = "0030PA", + [MPR0001PG] = "0001PG", [MPR0005PG] = "0005PG", [MPR0015PG] = "0015PG", + [MPR0030PG] = "0030PG", [MPR0300YG] = "0300YG" +}; + +/** + * struct mpr_range_config - list of pressure ranges based on nomenclature + * @pmin: lowest pressure that can be measured + * @pmax: highest pressure that can be measured + */ +struct mpr_range_config { + const s32 pmin; + const s32 pmax; +}; + +/* All min max limits have been converted to pascals */ +static const struct mpr_range_config mpr_range_config[MPR_VARIANTS_MAX] = { + [MPR0001BA] = { .pmin = 0, .pmax = 100000 }, + [MPR01_6BA] = { .pmin = 0, .pmax = 160000 }, + [MPR02_5BA] = { .pmin = 0, .pmax = 250000 }, + [MPR0060MG] = { .pmin = 0, .pmax = 6000 }, + [MPR0100MG] = { .pmin = 0, .pmax = 10000 }, + [MPR0160MG] = { .pmin = 0, .pmax = 16000 }, + [MPR0250MG] = { .pmin = 0, .pmax = 25000 }, + [MPR0400MG] = { .pmin = 0, .pmax = 40000 }, + [MPR0600MG] = { .pmin = 0, .pmax = 60000 }, + [MPR0001BG] = { .pmin = 0, .pmax = 100000 }, + [MPR01_6BG] = { .pmin = 0, .pmax = 160000 }, + [MPR02_5BG] = { .pmin = 0, .pmax = 250000 }, + [MPR0100KA] = { .pmin = 0, .pmax = 100000 }, + [MPR0160KA] = { .pmin = 0, .pmax = 160000 }, + [MPR0250KA] = { .pmin = 0, .pmax = 250000 }, + [MPR0006KG] = { .pmin = 0, .pmax = 6000 }, + [MPR0010KG] = { .pmin = 0, .pmax = 10000 }, + [MPR0016KG] = { .pmin = 0, .pmax = 16000 }, + [MPR0025KG] = { .pmin = 0, .pmax = 25000 }, + [MPR0040KG] = { .pmin = 0, .pmax = 40000 }, + [MPR0060KG] = { .pmin = 0, .pmax = 60000 }, + [MPR0100KG] = { .pmin = 0, .pmax = 100000 }, + [MPR0160KG] = { .pmin = 0, .pmax = 160000 }, + [MPR0250KG] = { .pmin = 0, .pmax = 250000 }, + [MPR0015PA] = { .pmin = 0, .pmax = 103421 }, + [MPR0025PA] = { .pmin = 0, .pmax = 172369 }, + [MPR0030PA] = { .pmin = 0, .pmax = 206843 }, + [MPR0001PG] = { .pmin = 0, .pmax = 6895 }, + [MPR0005PG] = { .pmin = 0, .pmax = 34474 }, + [MPR0015PG] = { .pmin = 0, .pmax = 103421 }, + [MPR0030PG] = { .pmin = 0, .pmax = 206843 }, + [MPR0300YG] = { .pmin = 0, .pmax = 39997 } +}; + +static const struct iio_chan_spec mpr_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static void mpr_reset(struct mpr_data *data) +{ + if (data->gpiod_reset) { + gpiod_set_value(data->gpiod_reset, 0); + udelay(10); + gpiod_set_value(data->gpiod_reset, 1); + } +} + +/** + * mpr_read_pressure() - Read pressure value from sensor + * @data: Pointer to private data struct. + * @press: Output value read from sensor. + * + * Reading from the sensor by sending and receiving telegrams. + * + * If there is an end of conversion (EOC) interrupt registered the function + * waits for a maximum of one second for the interrupt. + * + * Context: The function can sleep and data->lock should be held when calling it + * Return: + * * 0 - OK, the pressure value could be read + * * -EBUSY - Sensor does not have a new conversion ready + * * -ETIMEDOUT - Timeout while waiting for the EOC interrupt + * * -EIO - Invalid status byte received from sensor + */ +static int mpr_read_pressure(struct mpr_data *data, s32 *press) +{ + struct device *dev = data->dev; + int ret; + + reinit_completion(&data->completion); + + ret = data->ops->write(data, MPR_CMD_SYNC, MPR_PKT_SYNC_LEN); + if (ret < 0) { + dev_err(dev, "error while writing ret: %d\n", ret); + return ret; + } + + if (data->irq > 0) { + ret = wait_for_completion_timeout(&data->completion, HZ); + if (!ret) { + dev_err(dev, "timeout while waiting for eoc irq\n"); + return -ETIMEDOUT; + } + } else { + fsleep(5 * USEC_PER_MSEC); + } + + memset(data->rx_buf, 0, sizeof(data->rx_buf)); + ret = data->ops->read(data, MPR_CMD_NOP, MPR_PKT_NOP_LEN); + if (ret < 0) + return ret; + + /* + * Status byte flags + * bit7 SANITY_CHK - must always be 0 + * bit6 MPR_ST_POWER - 1 if device is powered + * bit5 MPR_ST_BUSY - 1 if device has no new conversion ready + * bit4 SANITY_CHK - must always be 0 + * bit3 SANITY_CHK - must always be 0 + * bit2 MEMORY_ERR - 1 if integrity test has failed + * bit1 SANITY_CHK - must always be 0 + * bit0 MATH_ERR - 1 during internal math saturation error + */ + + if (data->rx_buf[0] == (MPR_ST_POWER | MPR_ST_BUSY)) + return -EBUSY; + + if (data->rx_buf[0] != MPR_ST_POWER) { + dev_err(data->dev, + "unexpected status byte 0x%02x\n", data->rx_buf[0]); + return -EIO; + } + + *press = get_unaligned_be24(&data->rx_buf[1]); + + dev_dbg(dev, "received: %*ph cnt: %d\n", ret, data->rx_buf, *press); + + return 0; +} + +static irqreturn_t mpr_eoc_handler(int irq, void *p) +{ + struct mpr_data *data = p; + + complete(&data->completion); + + return IRQ_HANDLED; +} + +static irqreturn_t mpr_trigger_handler(int irq, void *p) +{ + int ret; + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct mpr_data *data = iio_priv(indio_dev); + + mutex_lock(&data->lock); + ret = mpr_read_pressure(data, &data->chan.pres); + if (ret < 0) + goto err; + + iio_push_to_buffers_with_timestamp(indio_dev, &data->chan, + iio_get_time_ns(indio_dev)); + +err: + mutex_unlock(&data->lock); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int mpr_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long mask) +{ + int ret; + s32 pressure; + struct mpr_data *data = iio_priv(indio_dev); + + if (chan->type != IIO_PRESSURE) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&data->lock); + ret = mpr_read_pressure(data, &pressure); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + *val = pressure; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = data->scale; + *val2 = data->scale2; + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + *val = data->offset; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static const struct iio_info mpr_info = { + .read_raw = &mpr_read_raw, +}; + +int mpr_common_probe(struct device *dev, const struct mpr_ops *ops, int irq) +{ + int ret; + struct mpr_data *data; + struct iio_dev *indio_dev; + const char *triplet; + s64 odelta, pdelta; + u32 func; + s32 tmp; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->dev = dev; + data->ops = ops; + data->irq = irq; + + mutex_init(&data->lock); + init_completion(&data->completion); + + indio_dev->name = "mprls0025pa"; + indio_dev->info = &mpr_info; + indio_dev->channels = mpr_channels; + indio_dev->num_channels = ARRAY_SIZE(mpr_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, + "can't get and enable vdd supply\n"); + + ret = device_property_read_u32(dev, + "honeywell,transfer-function", &func); + if (ret) + return dev_err_probe(dev, ret, + "honeywell,transfer-function could not be read\n"); + data->function = func - 1; + if (data->function > MPR_FUNCTION_C) + return dev_err_probe(dev, -EINVAL, + "honeywell,transfer-function %d invalid\n", + data->function); + + ret = device_property_read_string(dev, "honeywell,pressure-triplet", + &triplet); + if (ret) { + ret = device_property_read_u32(dev, "honeywell,pmin-pascal", + &data->pmin); + if (ret) + return dev_err_probe(dev, ret, + "honeywell,pmin-pascal could not be read\n"); + + ret = device_property_read_u32(dev, "honeywell,pmax-pascal", + &data->pmax); + if (ret) + return dev_err_probe(dev, ret, + "honeywell,pmax-pascal could not be read\n"); + } else { + ret = device_property_match_property_string(dev, + "honeywell,pressure-triplet", + mpr_triplet_variants, + MPR_VARIANTS_MAX); + if (ret < 0) + return dev_err_probe(dev, -EINVAL, + "honeywell,pressure-triplet is invalid\n"); + + data->pmin = mpr_range_config[ret].pmin; + data->pmax = mpr_range_config[ret].pmax; + } + + if (data->pmin >= data->pmax) + return dev_err_probe(dev, -EINVAL, + "pressure limits are invalid\n"); + + data->outmin = mpr_func_spec[data->function].output_min; + data->outmax = mpr_func_spec[data->function].output_max; + + odelta = data->outmax - data->outmin; + pdelta = data->pmax - data->pmin; + + data->scale = div_s64_rem(div_s64(pdelta * NANO, odelta), NANO, &tmp); + data->scale2 = tmp; + + data->offset = div_s64(odelta * data->pmin, pdelta) - data->outmin; + + if (data->irq > 0) { + ret = devm_request_irq(dev, data->irq, mpr_eoc_handler, 0, + dev_name(dev), data); + if (ret) + return ret; + } + + data->gpiod_reset = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(data->gpiod_reset)) + return dev_err_probe(dev, PTR_ERR(data->gpiod_reset), + "request reset-gpio failed\n"); + + mpr_reset(data); + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + mpr_trigger_handler, NULL); + if (ret) + return dev_err_probe(dev, ret, + "iio triggered buffer setup failed\n"); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, + "unable to register iio device\n"); + + return 0; +} +EXPORT_SYMBOL_NS(mpr_common_probe, "IIO_HONEYWELL_MPRLS0025PA"); + +MODULE_AUTHOR("Andreas Klinger "); +MODULE_DESCRIPTION("Honeywell MPR pressure sensor core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/mprls0025pa.h b/drivers/iio/pressure/mprls0025pa.h new file mode 100644 index 000000000..9f43273e6 --- /dev/null +++ b/drivers/iio/pressure/mprls0025pa.h @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * MPRLS0025PA - Honeywell MicroPressure pressure sensor series driver + * + * Copyright (c) Andreas Klinger + * + * Data sheet: + * https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/micropressure-mpr-series/documents/sps-siot-mpr-series-datasheet-32332628-ciid-172626.pdf + */ + +#ifndef _MPRLS0025PA_H +#define _MPRLS0025PA_H + +#include +#include +#include + +#include + +#define MPR_MEASUREMENT_RD_SIZE 4 +#define MPR_CMD_NOP 0xf0 +#define MPR_CMD_SYNC 0xaa +#define MPR_PKT_NOP_LEN MPR_MEASUREMENT_RD_SIZE +#define MPR_PKT_SYNC_LEN 3 + +struct device; + +struct mpr_data; +struct mpr_ops; + +enum mpr_func_id { + MPR_FUNCTION_A, + MPR_FUNCTION_B, + MPR_FUNCTION_C, +}; + +/** + * struct mpr_data + * @dev: current device structure + * @ops: functions that implement the sensor reads/writes, bus init + * @lock: access to device during read + * @pmin: minimal pressure in pascal + * @pmax: maximal pressure in pascal + * @function: transfer function + * @outmin: minimum raw pressure in counts (based on transfer function) + * @outmax: maximum raw pressure in counts (based on transfer function) + * @scale: pressure scale + * @scale2: pressure scale, decimal number + * @offset: pressure offset + * @gpiod_reset: reset + * @irq: end of conversion irq. used to distinguish between irq mode and + * reading in a loop until data is ready + * @completion: handshake from irq to read + * @chan: channel values for buffered mode + * @chan.pres: pressure value + * @chan.ts: timestamp + * @rx_buf: raw conversion data + * @tx_buf: output buffer + */ +struct mpr_data { + struct device *dev; + const struct mpr_ops *ops; + struct mutex lock; + u32 pmin; + u32 pmax; + enum mpr_func_id function; + u32 outmin; + u32 outmax; + int scale; + int scale2; + int offset; + struct gpio_desc *gpiod_reset; + int irq; + struct completion completion; + struct { + s32 pres; + aligned_s64 ts; + } chan; + u8 rx_buf[MPR_MEASUREMENT_RD_SIZE] __aligned(IIO_DMA_MINALIGN); + u8 tx_buf[MPR_MEASUREMENT_RD_SIZE]; +}; + +struct mpr_ops { + int (*read)(struct mpr_data *data, const u8 cmd, const u8 cnt); + int (*write)(struct mpr_data *data, const u8 cmd, const u8 cnt); +}; + +int mpr_common_probe(struct device *dev, const struct mpr_ops *ops, int irq); + +#endif diff --git a/drivers/iio/pressure/mprls0025pa_i2c.c b/drivers/iio/pressure/mprls0025pa_i2c.c new file mode 100644 index 000000000..06907b64a --- /dev/null +++ b/drivers/iio/pressure/mprls0025pa_i2c.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MPRLS0025PA - Honeywell MicroPressure pressure sensor series driver + * + * Copyright (c) Andreas Klinger + * + * Data sheet: + * https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/micropressure-mpr-series/documents/sps-siot-mpr-series-datasheet-32332628-ciid-172626.pdf + */ + +#include +#include +#include +#include +#include + +#include "mprls0025pa.h" + +static int mpr_i2c_read(struct mpr_data *data, const u8 unused, const u8 cnt) +{ + int ret; + struct i2c_client *client = to_i2c_client(data->dev); + + if (cnt > MPR_MEASUREMENT_RD_SIZE) + return -EOVERFLOW; + + ret = i2c_master_recv(client, data->rx_buf, cnt); + if (ret < 0) + return ret; + else if (ret != cnt) + return -EIO; + + return 0; +} + +static int mpr_i2c_write(struct mpr_data *data, const u8 cmd, const u8 unused) +{ + int ret; + struct i2c_client *client = to_i2c_client(data->dev); + + data->tx_buf[0] = cmd; + ret = i2c_master_send(client, data->tx_buf, MPR_PKT_SYNC_LEN); + if (ret < 0) + return ret; + else if (ret != MPR_PKT_SYNC_LEN) + return -EIO; + + return 0; +} + +static const struct mpr_ops mpr_i2c_ops = { + .read = mpr_i2c_read, + .write = mpr_i2c_write, +}; + +static int mpr_i2c_probe(struct i2c_client *client) +{ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BYTE)) + return -EOPNOTSUPP; + + return mpr_common_probe(&client->dev, &mpr_i2c_ops, client->irq); +} + +static const struct of_device_id mpr_i2c_match[] = { + { .compatible = "honeywell,mprls0025pa" }, + { } +}; +MODULE_DEVICE_TABLE(of, mpr_i2c_match); + +static const struct i2c_device_id mpr_i2c_id[] = { + { .name = "mprls0025pa" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mpr_i2c_id); + +static struct i2c_driver mpr_i2c_driver = { + .probe = mpr_i2c_probe, + .id_table = mpr_i2c_id, + .driver = { + .name = "mprls0025pa", + .of_match_table = mpr_i2c_match, + }, +}; +module_i2c_driver(mpr_i2c_driver); + +MODULE_AUTHOR("Andreas Klinger "); +MODULE_DESCRIPTION("Honeywell MPR pressure sensor i2c driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HONEYWELL_MPRLS0025PA"); diff --git a/drivers/iio/pressure/mprls0025pa_spi.c b/drivers/iio/pressure/mprls0025pa_spi.c new file mode 100644 index 000000000..627037038 --- /dev/null +++ b/drivers/iio/pressure/mprls0025pa_spi.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MPRLS0025PA - Honeywell MicroPressure MPR series SPI sensor driver + * + * Copyright (c) 2024 Petre Rodan + * + * Data sheet: + * https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/micropressure-mpr-series/documents/sps-siot-mpr-series-datasheet-32332628-ciid-172626.pdf + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mprls0025pa.h" + +static int mpr_spi_xfer(struct mpr_data *data, const u8 cmd, const u8 pkt_len) +{ + struct spi_device *spi = to_spi_device(data->dev); + struct spi_transfer xfers[2] = { }; + + if (pkt_len > MPR_MEASUREMENT_RD_SIZE) + return -EOVERFLOW; + + data->tx_buf[0] = cmd; + + /* + * Dummy transfer with no data, just cause a 2.5us+ delay between the CS assert + * and the first clock edge as per the datasheet tHDSS timing requirement. + */ + xfers[0].delay.value = 2500; + xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS; + + xfers[1].tx_buf = data->tx_buf; + xfers[1].rx_buf = data->rx_buf; + xfers[1].len = pkt_len; + + return spi_sync_transfer(spi, xfers, ARRAY_SIZE(xfers)); +} + +static const struct mpr_ops mpr_spi_ops = { + .read = mpr_spi_xfer, + .write = mpr_spi_xfer, +}; + +static int mpr_spi_probe(struct spi_device *spi) +{ + return mpr_common_probe(&spi->dev, &mpr_spi_ops, spi->irq); +} + +static const struct of_device_id mpr_spi_match[] = { + { .compatible = "honeywell,mprls0025pa" }, + { } +}; +MODULE_DEVICE_TABLE(of, mpr_spi_match); + +static const struct spi_device_id mpr_spi_id[] = { + { .name = "mprls0025pa" }, + { } +}; +MODULE_DEVICE_TABLE(spi, mpr_spi_id); + +static struct spi_driver mpr_spi_driver = { + .driver = { + .name = "mprls0025pa", + .of_match_table = mpr_spi_match, + }, + .probe = mpr_spi_probe, + .id_table = mpr_spi_id, +}; +module_spi_driver(mpr_spi_driver); + +MODULE_AUTHOR("Petre Rodan "); +MODULE_DESCRIPTION("Honeywell MPR pressure sensor spi driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HONEYWELL_MPRLS0025PA"); diff --git a/drivers/iio/pressure/ms5611.h b/drivers/iio/pressure/ms5611.h new file mode 100644 index 000000000..a78acd3fb --- /dev/null +++ b/drivers/iio/pressure/ms5611.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * MS5611 pressure and temperature sensor driver + * + * Copyright (c) Tomasz Duszynski + * + */ + +#ifndef _MS5611_H +#define _MS5611_H + +#include +#include +#include + +#define MS5611_RESET 0x1e +#define MS5611_READ_ADC 0x00 +#define MS5611_READ_PROM_WORD 0xA0 +#define MS5611_PROM_WORDS_NB 8 + +enum { + MS5611, + MS5607, +}; + +/* + * OverSampling Rate descriptor. + * Warning: cmd MUST be kept aligned on a word boundary (see + * m5611_spi_read_adc_temp_and_pressure in ms5611_spi.c). + */ +struct ms5611_osr { + unsigned long conv_usec; + u8 cmd; + unsigned short rate; +}; + +struct ms5611_state { + void *client; + struct mutex lock; + + const struct ms5611_osr *pressure_osr; + const struct ms5611_osr *temp_osr; + + u16 prom[MS5611_PROM_WORDS_NB]; + + int (*reset)(struct ms5611_state *st); + int (*read_prom_word)(struct ms5611_state *st, int index, u16 *word); + int (*read_adc_temp_and_pressure)(struct ms5611_state *st, + s32 *temp, s32 *pressure); + + int (*compensate_temp_and_pressure)(struct ms5611_state *st, s32 *temp, + s32 *pressure); +}; + +int ms5611_probe(struct iio_dev *indio_dev, struct device *dev, + const char *name, int type); + +#endif /* _MS5611_H */ diff --git a/drivers/iio/pressure/ms5611_core.c b/drivers/iio/pressure/ms5611_core.c new file mode 100644 index 000000000..bdac27bd5 --- /dev/null +++ b/drivers/iio/pressure/ms5611_core.c @@ -0,0 +1,454 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MS5611 pressure and temperature sensor driver + * + * Copyright (c) Tomasz Duszynski + * + * Data sheet: + * http://www.meas-spec.com/downloads/MS5611-01BA03.pdf + * http://www.meas-spec.com/downloads/MS5607-02BA03.pdf + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include "ms5611.h" + +#define MS5611_INIT_OSR(_cmd, _conv_usec, _rate) \ + { .cmd = _cmd, .conv_usec = _conv_usec, .rate = _rate } + +static const struct ms5611_osr ms5611_avail_pressure_osr[] = { + MS5611_INIT_OSR(0x40, 600, 256), + MS5611_INIT_OSR(0x42, 1170, 512), + MS5611_INIT_OSR(0x44, 2280, 1024), + MS5611_INIT_OSR(0x46, 4540, 2048), + MS5611_INIT_OSR(0x48, 9040, 4096) +}; + +static const struct ms5611_osr ms5611_avail_temp_osr[] = { + MS5611_INIT_OSR(0x50, 600, 256), + MS5611_INIT_OSR(0x52, 1170, 512), + MS5611_INIT_OSR(0x54, 2280, 1024), + MS5611_INIT_OSR(0x56, 4540, 2048), + MS5611_INIT_OSR(0x58, 9040, 4096) +}; + +static const char ms5611_show_osr[] = "256 512 1024 2048 4096"; + +static IIO_CONST_ATTR(oversampling_ratio_available, ms5611_show_osr); + +static struct attribute *ms5611_attributes[] = { + &iio_const_attr_oversampling_ratio_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ms5611_attribute_group = { + .attrs = ms5611_attributes, +}; + +static bool ms5611_prom_is_valid(u16 *prom, size_t len) +{ + int i, j; + uint16_t crc = 0, crc_orig = prom[7] & 0x000F; + + prom[7] &= 0xFF00; + + for (i = 0; i < len * 2; i++) { + if (i % 2 == 1) + crc ^= prom[i >> 1] & 0x00FF; + else + crc ^= prom[i >> 1] >> 8; + + for (j = 0; j < 8; j++) { + if (crc & 0x8000) + crc = (crc << 1) ^ 0x3000; + else + crc <<= 1; + } + } + + crc = (crc >> 12) & 0x000F; + + return crc == crc_orig; +} + +static int ms5611_read_prom(struct iio_dev *indio_dev) +{ + int ret, i; + struct ms5611_state *st = iio_priv(indio_dev); + + for (i = 0; i < MS5611_PROM_WORDS_NB; i++) { + ret = st->read_prom_word(st, i, &st->prom[i]); + if (ret < 0) { + dev_err(&indio_dev->dev, + "failed to read prom at %d\n", i); + return ret; + } + } + + if (!ms5611_prom_is_valid(st->prom, MS5611_PROM_WORDS_NB)) { + dev_err(&indio_dev->dev, "PROM integrity check failed\n"); + return -ENODEV; + } + + return 0; +} + +static int ms5611_read_temp_and_pressure(struct iio_dev *indio_dev, + s32 *temp, s32 *pressure) +{ + int ret; + struct ms5611_state *st = iio_priv(indio_dev); + + ret = st->read_adc_temp_and_pressure(st, temp, pressure); + if (ret < 0) { + dev_err(&indio_dev->dev, + "failed to read temperature and pressure\n"); + return ret; + } + + return st->compensate_temp_and_pressure(st, temp, pressure); +} + +static int ms5611_temp_and_pressure_compensate(struct ms5611_state *st, + s32 *temp, s32 *pressure) +{ + s32 t = *temp, p = *pressure; + s64 off, sens, dt; + + dt = t - (st->prom[5] << 8); + off = ((s64)st->prom[2] << 16) + ((st->prom[4] * dt) >> 7); + sens = ((s64)st->prom[1] << 15) + ((st->prom[3] * dt) >> 8); + + t = 2000 + ((st->prom[6] * dt) >> 23); + if (t < 2000) { + s64 off2, sens2, t2; + + t2 = (dt * dt) >> 31; + off2 = (5 * (t - 2000) * (t - 2000)) >> 1; + sens2 = off2 >> 1; + + if (t < -1500) { + s64 tmp = (t + 1500) * (t + 1500); + + off2 += 7 * tmp; + sens2 += (11 * tmp) >> 1; + } + + t -= t2; + off -= off2; + sens -= sens2; + } + + *temp = t; + *pressure = (((p * sens) >> 21) - off) >> 15; + + return 0; +} + +static int ms5607_temp_and_pressure_compensate(struct ms5611_state *st, + s32 *temp, s32 *pressure) +{ + s32 t = *temp, p = *pressure; + s64 off, sens, dt; + + dt = t - (st->prom[5] << 8); + off = ((s64)st->prom[2] << 17) + ((st->prom[4] * dt) >> 6); + sens = ((s64)st->prom[1] << 16) + ((st->prom[3] * dt) >> 7); + + t = 2000 + ((st->prom[6] * dt) >> 23); + if (t < 2000) { + s64 off2, sens2, t2, tmp; + + t2 = (dt * dt) >> 31; + tmp = (t - 2000) * (t - 2000); + off2 = (61 * tmp) >> 4; + sens2 = tmp << 1; + + if (t < -1500) { + tmp = (t + 1500) * (t + 1500); + off2 += 15 * tmp; + sens2 += 8 * tmp; + } + + t -= t2; + off -= off2; + sens -= sens2; + } + + *temp = t; + *pressure = (((p * sens) >> 21) - off) >> 15; + + return 0; +} + +static int ms5611_reset(struct iio_dev *indio_dev) +{ + int ret; + struct ms5611_state *st = iio_priv(indio_dev); + + ret = st->reset(st); + if (ret < 0) { + dev_err(&indio_dev->dev, "failed to reset device\n"); + return ret; + } + + usleep_range(3000, 4000); + + return 0; +} + +static irqreturn_t ms5611_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ms5611_state *st = iio_priv(indio_dev); + /* Ensure buffer elements are naturally aligned */ + struct { + s32 channels[2]; + aligned_s64 ts; + } scan; + int ret; + + mutex_lock(&st->lock); + ret = ms5611_read_temp_and_pressure(indio_dev, &scan.channels[1], + &scan.channels[0]); + mutex_unlock(&st->lock); + if (ret < 0) + goto err; + + iio_push_to_buffers_with_timestamp(indio_dev, &scan, + iio_get_time_ns(indio_dev)); + +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ms5611_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + s32 temp, pressure; + struct ms5611_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + mutex_lock(&st->lock); + ret = ms5611_read_temp_and_pressure(indio_dev, + &temp, &pressure); + mutex_unlock(&st->lock); + if (ret < 0) + return ret; + + switch (chan->type) { + case IIO_TEMP: + *val = temp * 10; + return IIO_VAL_INT; + case IIO_PRESSURE: + *val = pressure / 1000; + *val2 = (pressure % 1000) * 1000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + *val = 10; + return IIO_VAL_INT; + case IIO_PRESSURE: + *val = 0; + *val2 = 1000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + if (chan->type != IIO_TEMP && chan->type != IIO_PRESSURE) + break; + mutex_lock(&st->lock); + if (chan->type == IIO_TEMP) + *val = (int)st->temp_osr->rate; + else + *val = (int)st->pressure_osr->rate; + mutex_unlock(&st->lock); + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static const struct ms5611_osr *ms5611_find_osr(int rate, + const struct ms5611_osr *osr, + size_t count) +{ + unsigned int r; + + for (r = 0; r < count; r++) + if ((unsigned short)rate == osr[r].rate) + break; + if (r >= count) + return NULL; + return &osr[r]; +} + +static int ms5611_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ms5611_state *st = iio_priv(indio_dev); + const struct ms5611_osr *osr = NULL; + + if (mask != IIO_CHAN_INFO_OVERSAMPLING_RATIO) + return -EINVAL; + + if (chan->type == IIO_TEMP) + osr = ms5611_find_osr(val, ms5611_avail_temp_osr, + ARRAY_SIZE(ms5611_avail_temp_osr)); + else if (chan->type == IIO_PRESSURE) + osr = ms5611_find_osr(val, ms5611_avail_pressure_osr, + ARRAY_SIZE(ms5611_avail_pressure_osr)); + if (!osr) + return -EINVAL; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + mutex_lock(&st->lock); + + if (chan->type == IIO_TEMP) + st->temp_osr = osr; + else + st->pressure_osr = osr; + + mutex_unlock(&st->lock); + iio_device_release_direct(indio_dev); + + return 0; +} + +static const unsigned long ms5611_scan_masks[] = {0x3, 0}; + +static const struct iio_chan_spec ms5611_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static const struct iio_info ms5611_info = { + .read_raw = &ms5611_read_raw, + .write_raw = &ms5611_write_raw, + .attrs = &ms5611_attribute_group, +}; + +static int ms5611_init(struct iio_dev *indio_dev) +{ + int ret; + + /* Enable attached regulator if any. */ + ret = devm_regulator_get_enable(indio_dev->dev.parent, "vdd"); + if (ret) + return ret; + + ret = ms5611_reset(indio_dev); + if (ret < 0) + return ret; + + ret = ms5611_read_prom(indio_dev); + if (ret < 0) + return ret; + + return 0; +} + +int ms5611_probe(struct iio_dev *indio_dev, struct device *dev, + const char *name, int type) +{ + int ret; + struct ms5611_state *st = iio_priv(indio_dev); + + mutex_init(&st->lock); + + switch (type) { + case MS5611: + st->compensate_temp_and_pressure = + ms5611_temp_and_pressure_compensate; + break; + case MS5607: + st->compensate_temp_and_pressure = + ms5607_temp_and_pressure_compensate; + break; + default: + return -EINVAL; + } + + st->temp_osr = + &ms5611_avail_temp_osr[ARRAY_SIZE(ms5611_avail_temp_osr) - 1]; + st->pressure_osr = + &ms5611_avail_pressure_osr[ARRAY_SIZE(ms5611_avail_pressure_osr) + - 1]; + indio_dev->name = name; + indio_dev->info = &ms5611_info; + indio_dev->channels = ms5611_channels; + indio_dev->num_channels = ARRAY_SIZE(ms5611_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->available_scan_masks = ms5611_scan_masks; + + ret = ms5611_init(indio_dev); + if (ret < 0) + return ret; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + ms5611_trigger_handler, NULL); + if (ret < 0) { + dev_err(dev, "iio triggered buffer setup failed\n"); + return ret; + } + + ret = devm_iio_device_register(dev, indio_dev); + if (ret < 0) { + dev_err(dev, "unable to register iio device\n"); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_NS(ms5611_probe, "IIO_MS5611"); + +MODULE_AUTHOR("Tomasz Duszynski "); +MODULE_DESCRIPTION("MS5611 core driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/ms5611_i2c.c b/drivers/iio/pressure/ms5611_i2c.c new file mode 100644 index 000000000..c57ad473b --- /dev/null +++ b/drivers/iio/pressure/ms5611_i2c.c @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MS5611 pressure and temperature sensor driver (I2C bus) + * + * Copyright (c) Tomasz Duszynski + * + * 7-bit I2C slave addresses: + * + * 0x77 (CSB pin low) + * 0x76 (CSB pin high) + * + */ + +#include +#include +#include + +#include + +#include "ms5611.h" + +static int ms5611_i2c_reset(struct ms5611_state *st) +{ + return i2c_smbus_write_byte(st->client, MS5611_RESET); +} + +static int ms5611_i2c_read_prom_word(struct ms5611_state *st, int index, + u16 *word) +{ + int ret; + + ret = i2c_smbus_read_word_swapped(st->client, + MS5611_READ_PROM_WORD + (index << 1)); + if (ret < 0) + return ret; + + *word = ret; + + return 0; +} + +static int ms5611_i2c_read_adc(struct ms5611_state *st, s32 *val) +{ + int ret; + u8 buf[3]; + + ret = i2c_smbus_read_i2c_block_data(st->client, MS5611_READ_ADC, + 3, buf); + if (ret < 0) + return ret; + + *val = get_unaligned_be24(&buf[0]); + + return 0; +} + +static int ms5611_i2c_read_adc_temp_and_pressure(struct ms5611_state *st, + s32 *temp, s32 *pressure) +{ + int ret; + const struct ms5611_osr *osr = st->temp_osr; + + ret = i2c_smbus_write_byte(st->client, osr->cmd); + if (ret < 0) + return ret; + + usleep_range(osr->conv_usec, osr->conv_usec + (osr->conv_usec / 10UL)); + ret = ms5611_i2c_read_adc(st, temp); + if (ret < 0) + return ret; + + osr = st->pressure_osr; + ret = i2c_smbus_write_byte(st->client, osr->cmd); + if (ret < 0) + return ret; + + usleep_range(osr->conv_usec, osr->conv_usec + (osr->conv_usec / 10UL)); + return ms5611_i2c_read_adc(st, pressure); +} + +static int ms5611_i2c_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct ms5611_state *st; + struct iio_dev *indio_dev; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_BYTE | + I2C_FUNC_SMBUS_READ_WORD_DATA | + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) + return -EOPNOTSUPP; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + st->reset = ms5611_i2c_reset; + st->read_prom_word = ms5611_i2c_read_prom_word; + st->read_adc_temp_and_pressure = ms5611_i2c_read_adc_temp_and_pressure; + st->client = client; + + return ms5611_probe(indio_dev, &client->dev, id->name, id->driver_data); +} + +static const struct of_device_id ms5611_i2c_matches[] = { + { .compatible = "meas,ms5611" }, + { .compatible = "meas,ms5607" }, + { } +}; +MODULE_DEVICE_TABLE(of, ms5611_i2c_matches); + +static const struct i2c_device_id ms5611_id[] = { + { .name = "ms5611", .driver_data = MS5611 }, + { .name = "ms5607", .driver_data = MS5607 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ms5611_id); + +static struct i2c_driver ms5611_driver = { + .driver = { + .name = "ms5611", + .of_match_table = ms5611_i2c_matches, + }, + .id_table = ms5611_id, + .probe = ms5611_i2c_probe, +}; +module_i2c_driver(ms5611_driver); + +MODULE_AUTHOR("Tomasz Duszynski "); +MODULE_DESCRIPTION("MS5611 i2c driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_MS5611"); diff --git a/drivers/iio/pressure/ms5611_spi.c b/drivers/iio/pressure/ms5611_spi.c new file mode 100644 index 000000000..7133e6744 --- /dev/null +++ b/drivers/iio/pressure/ms5611_spi.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MS5611 pressure and temperature sensor driver (SPI bus) + * + * Copyright (c) Tomasz Duszynski + * + */ + +#include +#include +#include + +#include + +#include "ms5611.h" + +static int ms5611_spi_reset(struct ms5611_state *st) +{ + u8 cmd = MS5611_RESET; + + return spi_write_then_read(st->client, &cmd, 1, NULL, 0); +} + +static int ms5611_spi_read_prom_word(struct ms5611_state *st, int index, + u16 *word) +{ + int ret; + + ret = spi_w8r16be(st->client, MS5611_READ_PROM_WORD + (index << 1)); + if (ret < 0) + return ret; + + *word = ret; + + return 0; +} + +static int ms5611_spi_read_adc(struct ms5611_state *st, s32 *val) +{ + int ret; + u8 buf[3] = { MS5611_READ_ADC }; + + ret = spi_write_then_read(st->client, buf, 1, buf, 3); + if (ret < 0) + return ret; + + *val = get_unaligned_be24(&buf[0]); + + return 0; +} + +static int ms5611_spi_read_adc_temp_and_pressure(struct ms5611_state *st, + s32 *temp, s32 *pressure) +{ + int ret; + const struct ms5611_osr *osr = st->temp_osr; + + /* + * Warning: &osr->cmd MUST be aligned on a word boundary since used as + * 2nd argument (void*) of spi_write_then_read. + */ + ret = spi_write_then_read(st->client, &osr->cmd, 1, NULL, 0); + if (ret < 0) + return ret; + + usleep_range(osr->conv_usec, osr->conv_usec + (osr->conv_usec / 10UL)); + ret = ms5611_spi_read_adc(st, temp); + if (ret < 0) + return ret; + + osr = st->pressure_osr; + ret = spi_write_then_read(st->client, &osr->cmd, 1, NULL, 0); + if (ret < 0) + return ret; + + usleep_range(osr->conv_usec, osr->conv_usec + (osr->conv_usec / 10UL)); + return ms5611_spi_read_adc(st, pressure); +} + +static int ms5611_spi_probe(struct spi_device *spi) +{ + int ret; + struct ms5611_state *st; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + spi_set_drvdata(spi, indio_dev); + + spi->mode = SPI_MODE_0; + spi->max_speed_hz = min(spi->max_speed_hz, 20000000U); + ret = spi_setup(spi); + if (ret < 0) + return ret; + + st = iio_priv(indio_dev); + st->reset = ms5611_spi_reset; + st->read_prom_word = ms5611_spi_read_prom_word; + st->read_adc_temp_and_pressure = ms5611_spi_read_adc_temp_and_pressure; + st->client = spi; + + return ms5611_probe(indio_dev, &spi->dev, spi_get_device_id(spi)->name, + spi_get_device_id(spi)->driver_data); +} + +static const struct of_device_id ms5611_spi_matches[] = { + { .compatible = "meas,ms5611" }, + { .compatible = "meas,ms5607" }, + { } +}; +MODULE_DEVICE_TABLE(of, ms5611_spi_matches); + +static const struct spi_device_id ms5611_id[] = { + { .name = "ms5611", .driver_data = MS5611 }, + { .name = "ms5607", .driver_data = MS5607 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ms5611_id); + +static struct spi_driver ms5611_driver = { + .driver = { + .name = "ms5611", + .of_match_table = ms5611_spi_matches + }, + .id_table = ms5611_id, + .probe = ms5611_spi_probe, +}; +module_spi_driver(ms5611_driver); + +MODULE_AUTHOR("Tomasz Duszynski "); +MODULE_DESCRIPTION("MS5611 spi driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_MS5611"); diff --git a/drivers/iio/pressure/ms5637.c b/drivers/iio/pressure/ms5637.c new file mode 100644 index 000000000..be8921644 --- /dev/null +++ b/drivers/iio/pressure/ms5637.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ms5637.c - Support for Measurement-Specialties MS5637, MS5805 + * MS5837 and MS8607 pressure & temperature sensor + * + * Copyright (c) 2015 Measurement-Specialties + * + * (7-bit I2C slave address 0x76) + * + * Datasheet: + * http://www.meas-spec.com/downloads/MS5637-02BA03.pdf + * Datasheet: + * http://www.meas-spec.com/downloads/MS5805-02BA01.pdf + * Datasheet: + * http://www.meas-spec.com/downloads/MS5837-30BA.pdf + * Datasheet: + * http://www.meas-spec.com/downloads/MS8607-02BA01.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../common/ms_sensors/ms_sensors_i2c.h" + +struct ms_tp_data { + const char *name; + const struct ms_tp_hw_data *hw; +}; + +static const int ms5637_samp_freq[6] = { 960, 480, 240, 120, 60, 30 }; + +static ssize_t ms5637_show_samp_freq(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ms_tp_dev *dev_data = iio_priv(indio_dev); + int i, len = 0; + + for (i = 0; i <= dev_data->hw->max_res_index; i++) + len += sysfs_emit_at(buf, len, "%u ", ms5637_samp_freq[i]); + sysfs_emit_at(buf, len - 1, "\n"); + + return len; +} + +static int ms5637_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + int ret; + int temperature; + unsigned int pressure; + struct ms_tp_dev *dev_data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ret = ms_sensors_read_temp_and_pressure(dev_data, + &temperature, + &pressure); + if (ret) + return ret; + + switch (channel->type) { + case IIO_TEMP: /* in milli °C */ + *val = temperature; + + return IIO_VAL_INT; + case IIO_PRESSURE: /* in kPa */ + *val = pressure / 1000; + *val2 = (pressure % 1000) * 1000; + + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + *val = ms5637_samp_freq[dev_data->res_index]; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ms5637_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ms_tp_dev *dev_data = iio_priv(indio_dev); + int i; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + i = ARRAY_SIZE(ms5637_samp_freq); + while (i-- > 0) + if (val == ms5637_samp_freq[i]) + break; + if (i < 0) + return -EINVAL; + dev_data->res_index = i; + + return 0; + default: + return -EINVAL; + } +} + +static const struct iio_chan_spec ms5637_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + }, + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + } +}; + +static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(ms5637_show_samp_freq); + +static struct attribute *ms5637_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ms5637_attribute_group = { + .attrs = ms5637_attributes, +}; + +static const struct iio_info ms5637_info = { + .read_raw = ms5637_read_raw, + .write_raw = ms5637_write_raw, + .attrs = &ms5637_attribute_group, +}; + +static int ms5637_probe(struct i2c_client *client) +{ + const struct ms_tp_data *data; + struct ms_tp_dev *dev_data; + struct iio_dev *indio_dev; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_WORD_DATA | + I2C_FUNC_SMBUS_WRITE_BYTE | + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { + dev_err(&client->dev, + "Adapter does not support some i2c transaction\n"); + return -EOPNOTSUPP; + } + + data = i2c_get_match_data(client); + if (!data) + return -EINVAL; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*dev_data)); + if (!indio_dev) + return -ENOMEM; + + dev_data = iio_priv(indio_dev); + dev_data->client = client; + dev_data->res_index = data->hw->max_res_index; + dev_data->hw = data->hw; + mutex_init(&dev_data->lock); + + indio_dev->info = &ms5637_info; + indio_dev->name = data->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ms5637_channels; + indio_dev->num_channels = ARRAY_SIZE(ms5637_channels); + + i2c_set_clientdata(client, indio_dev); + + ret = ms_sensors_reset(client, 0x1E, 3000); + if (ret) + return ret; + + ret = ms_sensors_tp_read_prom(dev_data); + if (ret) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct ms_tp_hw_data ms5637_hw_data = { + .prom_len = 7, + .max_res_index = 5 +}; + +static const struct ms_tp_hw_data ms5803_hw_data = { + .prom_len = 8, + .max_res_index = 4 +}; + +static const struct ms_tp_data ms5637_data = { .name = "ms5637", .hw = &ms5637_hw_data }; + +static const struct ms_tp_data ms5803_data = { .name = "ms5803", .hw = &ms5803_hw_data }; + +static const struct ms_tp_data ms5805_data = { .name = "ms5805", .hw = &ms5637_hw_data }; + +static const struct ms_tp_data ms5837_data = { .name = "ms5837", .hw = &ms5637_hw_data }; + +static const struct ms_tp_data ms8607_data = { + .name = "ms8607-temppressure", + .hw = &ms5637_hw_data, +}; + +static const struct i2c_device_id ms5637_id[] = { + { .name = "ms5637", .driver_data = (kernel_ulong_t)&ms5637_data }, + { .name = "ms5805", .driver_data = (kernel_ulong_t)&ms5805_data }, + { .name = "ms5837", .driver_data = (kernel_ulong_t)&ms5837_data }, + { .name = "ms8607-temppressure", .driver_data = (kernel_ulong_t)&ms8607_data }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ms5637_id); + +static const struct of_device_id ms5637_of_match[] = { + { .compatible = "meas,ms5637", .data = &ms5637_data }, + { .compatible = "meas,ms5803", .data = &ms5803_data }, + { .compatible = "meas,ms5805", .data = &ms5805_data }, + { .compatible = "meas,ms5837", .data = &ms5837_data }, + { .compatible = "meas,ms8607-temppressure", .data = &ms8607_data }, + { } +}; +MODULE_DEVICE_TABLE(of, ms5637_of_match); + +static struct i2c_driver ms5637_driver = { + .probe = ms5637_probe, + .id_table = ms5637_id, + .driver = { + .name = "ms5637", + .of_match_table = ms5637_of_match, + }, +}; + +module_i2c_driver(ms5637_driver); + +MODULE_DESCRIPTION("Measurement-Specialties ms5637 temperature & pressure driver"); +MODULE_AUTHOR("William Markezana "); +MODULE_AUTHOR("Ludovic Tancerel "); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_MEAS_SPEC_SENSORS"); diff --git a/drivers/iio/pressure/rohm-bm1390.c b/drivers/iio/pressure/rohm-bm1390.c new file mode 100644 index 000000000..57941fb4a --- /dev/null +++ b/drivers/iio/pressure/rohm-bm1390.c @@ -0,0 +1,922 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * BM1390 ROHM pressure sensor + * + * Copyright (c) 2023, ROHM Semiconductor. + * https://fscdn.rohm.com/en/products/databook/datasheet/ic/sensor/pressure/bm1390glv-z-e.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define BM1390_REG_MANUFACT_ID 0x0f +#define BM1390_REG_PART_ID 0x10 +#define BM1390_REG_POWER 0x12 +#define BM1390_MASK_POWER BIT(0) +#define BM1390_POWER_ON BM1390_MASK_POWER +#define BM1390_POWER_OFF 0x00 +#define BM1390_REG_RESET 0x13 +#define BM1390_MASK_RESET BIT(0) +#define BM1390_RESET_RELEASE BM1390_MASK_RESET +#define BM1390_RESET 0x00 +#define BM1390_REG_MODE_CTRL 0x14 +#define BM1390_MASK_MEAS_MODE GENMASK(1, 0) +#define BM1390_MASK_DRDY_EN BIT(4) +#define BM1390_MASK_WMI_EN BIT(2) +#define BM1390_MASK_AVE_NUM GENMASK(7, 5) + +/* + * Data-sheet states that when the IIR is used, the AVE_NUM must be set to + * value 110b + */ +#define BM1390_IIR_AVE_NUM 0x06 +#define BM1390_REG_FIFO_CTRL 0x15 +#define BM1390_MASK_IIR_MODE GENMASK(1, 0) +#define BM1390_IIR_MODE_OFF 0x0 +#define BM1390_IIR_MODE_WEAK 0x1 +#define BM1390_IIR_MODE_MID 0x2 +#define BM1390_IIR_MODE_STRONG 0x3 + +#define BM1390_MASK_FIFO_LEN BIT(6) +#define BM1390_MASK_FIFO_EN BIT(7) +#define BM1390_WMI_MIN 2 +#define BM1390_WMI_MAX 3 + +#define BM1390_REG_FIFO_LVL 0x18 +#define BM1390_MASK_FIFO_LVL GENMASK(2, 0) +#define BM1390_REG_STATUS 0x19 +#define BM1390_REG_PRESSURE_BASE 0x1a +#define BM1390_REG_TEMP_HI 0x1d +#define BM1390_REG_TEMP_LO 0x1e +#define BM1390_MAX_REGISTER BM1390_REG_TEMP_LO + +#define BM1390_ID 0x34 + +/* Regmap configs */ +static const struct regmap_range bm1390_volatile_ranges[] = { + { + .range_min = BM1390_REG_STATUS, + .range_max = BM1390_REG_STATUS, + }, + { + .range_min = BM1390_REG_FIFO_LVL, + .range_max = BM1390_REG_TEMP_LO, + }, +}; + +static const struct regmap_access_table bm1390_volatile_regs = { + .yes_ranges = &bm1390_volatile_ranges[0], + .n_yes_ranges = ARRAY_SIZE(bm1390_volatile_ranges), +}; + +static const struct regmap_range bm1390_precious_ranges[] = { + { + .range_min = BM1390_REG_STATUS, + .range_max = BM1390_REG_STATUS, + }, +}; + +static const struct regmap_access_table bm1390_precious_regs = { + .yes_ranges = &bm1390_precious_ranges[0], + .n_yes_ranges = ARRAY_SIZE(bm1390_precious_ranges), +}; + +static const struct regmap_range bm1390_read_only_ranges[] = { + { + .range_min = BM1390_REG_MANUFACT_ID, + .range_max = BM1390_REG_PART_ID, + }, { + .range_min = BM1390_REG_FIFO_LVL, + .range_max = BM1390_REG_TEMP_LO, + }, +}; + +static const struct regmap_access_table bm1390_ro_regs = { + .no_ranges = &bm1390_read_only_ranges[0], + .n_no_ranges = ARRAY_SIZE(bm1390_read_only_ranges), +}; + +static const struct regmap_range bm1390_noinc_read_ranges[] = { + { + .range_min = BM1390_REG_PRESSURE_BASE, + .range_max = BM1390_REG_TEMP_LO, + }, +}; + +static const struct regmap_access_table bm1390_nir_regs = { + .yes_ranges = &bm1390_noinc_read_ranges[0], + .n_yes_ranges = ARRAY_SIZE(bm1390_noinc_read_ranges), +}; + +static const struct regmap_config bm1390_regmap = { + .reg_bits = 8, + .val_bits = 8, + .volatile_table = &bm1390_volatile_regs, + .wr_table = &bm1390_ro_regs, + .rd_noinc_table = &bm1390_nir_regs, + .precious_table = &bm1390_precious_regs, + .max_register = BM1390_MAX_REGISTER, + .cache_type = REGCACHE_RBTREE, + .disable_locking = true, +}; + +enum { + BM1390_STATE_SAMPLE, + BM1390_STATE_FIFO, +}; + +struct bm1390_data_buf { + u32 pressure; + __be16 temp; + aligned_s64 ts; +}; + +/* BM1390 has FIFO for 4 pressure samples */ +#define BM1390_FIFO_LENGTH 4 + +struct bm1390_data { + s64 timestamp, old_timestamp; + struct iio_trigger *trig; + struct regmap *regmap; + struct device *dev; + struct bm1390_data_buf buf; + int irq; + unsigned int state; + bool trigger_enabled; + u8 watermark; + + /* Prevent accessing sensor during FIFO read sequence */ + struct mutex mutex; +}; + +enum { + BM1390_CHAN_PRESSURE, + BM1390_CHAN_TEMP, +}; + +static const struct iio_chan_spec bm1390_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + /* + * When IIR is used, we must fix amount of averaged samples. + * Thus we don't allow setting oversampling ratio. + */ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .scan_index = BM1390_CHAN_PRESSURE, + .scan_type = { + .sign = 'u', + .realbits = 22, + .storagebits = 32, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .scan_index = BM1390_CHAN_TEMP, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +/* + * We can't skip reading the pressure because the watermark IRQ is acked + * only when the pressure data is read from the FIFO. + */ +static const unsigned long bm1390_scan_masks[] = { + BIT(BM1390_CHAN_PRESSURE), + BIT(BM1390_CHAN_PRESSURE) | BIT(BM1390_CHAN_TEMP), + 0 +}; + +static int bm1390_read_temp(struct bm1390_data *data, int *temp) +{ + __be16 temp_raw; + int ret; + + ret = regmap_bulk_read(data->regmap, BM1390_REG_TEMP_HI, &temp_raw, + sizeof(temp_raw)); + if (ret) + return ret; + + *temp = be16_to_cpu(temp_raw); + + return 0; +} + +static int bm1390_pressure_read(struct bm1390_data *data, u32 *pressure) +{ + /* Pressure data is in 3 8-bit registers */ + u8 raw[3]; + int ret; + + ret = regmap_bulk_read(data->regmap, BM1390_REG_PRESSURE_BASE, + raw, sizeof(raw)); + if (ret < 0) + return ret; + + *pressure = (u32)(raw[2] >> 2 | raw[1] << 6 | raw[0] << 14); + + return 0; +} + + /* The enum values map directly to register bits */ +enum bm1390_meas_mode { + BM1390_MEAS_MODE_STOP = 0x0, + BM1390_MEAS_MODE_1SHOT = 0x1, + BM1390_MEAS_MODE_CONTINUOUS = 0x2, +}; + +static int bm1390_meas_set(struct bm1390_data *data, enum bm1390_meas_mode mode) +{ + return regmap_update_bits(data->regmap, BM1390_REG_MODE_CTRL, + BM1390_MASK_MEAS_MODE, mode); +} + +/* + * If the trigger is not used we just wait until the measurement has + * completed. The data-sheet says maximum measurement cycle (regardless + * the AVE_NUM) is 200 mS so let's just sleep at least that long. If speed + * is needed the trigger should be used. + */ +#define BM1390_MAX_MEAS_TIME_MS 205 + +static int bm1390_read_data(struct bm1390_data *data, + struct iio_chan_spec const *chan, int *val, int *val2) +{ + int ret, warn; + + guard(mutex)(&data->mutex); + /* + * We use 'continuous mode' even for raw read because according to the + * data-sheet an one-shot mode can't be used with IIR filter. + */ + ret = bm1390_meas_set(data, BM1390_MEAS_MODE_CONTINUOUS); + if (ret) + return ret; + + switch (chan->type) { + case IIO_PRESSURE: + msleep(BM1390_MAX_MEAS_TIME_MS); + ret = bm1390_pressure_read(data, val); + break; + case IIO_TEMP: + msleep(BM1390_MAX_MEAS_TIME_MS); + ret = bm1390_read_temp(data, val); + break; + default: + ret = -EINVAL; + } + warn = bm1390_meas_set(data, BM1390_MEAS_MODE_STOP); + if (warn) + dev_warn(data->dev, "Failed to stop measurement (%d)\n", warn); + + return 0; +} + +static int bm1390_read_raw(struct iio_dev *idev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bm1390_data *data = iio_priv(idev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_TEMP) { + *val = 31; + *val2 = 250000; + + return IIO_VAL_INT_PLUS_MICRO; + } else if (chan->type == IIO_PRESSURE) { + /* + * pressure in hPa is register value divided by 2048. + * This means kPa is 1/20480 times the register value, + */ + *val = 1; + *val2 = 2048; + + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(idev)) + return -EBUSY; + + ret = bm1390_read_data(data, chan, val, val2); + iio_device_release_direct(idev); + if (ret) + return ret; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int __bm1390_fifo_flush(struct iio_dev *idev, unsigned int samples, + s64 timestamp) +{ + /* BM1390_FIFO_LENGTH is small so we shouldn't run out of stack */ + struct bm1390_data_buf buffer[BM1390_FIFO_LENGTH]; + struct bm1390_data *data = iio_priv(idev); + int smp_lvl, ret, i, warn, dummy; + u64 sample_period; + __be16 temp = 0; + + ret = regmap_read(data->regmap, BM1390_REG_FIFO_LVL, &smp_lvl); + if (ret) + return ret; + + smp_lvl = FIELD_GET(BM1390_MASK_FIFO_LVL, smp_lvl); + if (!smp_lvl) + return 0; + + if (smp_lvl > BM1390_FIFO_LENGTH) { + /* + * The fifo holds maximum of 4 samples so valid values + * should be 0, 1, 2, 3, 4 - rest are probably bit errors + * in I2C line. Don't overflow if this happens. + */ + dev_err(data->dev, "bad FIFO level %d\n", smp_lvl); + smp_lvl = BM1390_FIFO_LENGTH; + } + + sample_period = timestamp - data->old_timestamp; + do_div(sample_period, smp_lvl); + + if (samples && smp_lvl > samples) + smp_lvl = samples; + + + /* + * After some testing it appears that the temperature is not readable + * until the FIFO access has been done after the WMI. Thus, we need + * to read the all pressure values to memory and read the temperature + * only after that. + */ + for (i = 0; i < smp_lvl; i++) { + /* + * When we start reading data from the FIFO the sensor goes to + * special FIFO reading mode. If any other register is accessed + * during the FIFO read, samples can be dropped. Prevent access + * until FIFO_LVL is read. We have mutex locked and we do also + * go performing reading of FIFO_LVL even if this read fails. + */ + if (test_bit(BM1390_CHAN_PRESSURE, idev->active_scan_mask)) { + ret = bm1390_pressure_read(data, &buffer[i].pressure); + if (ret) + break; + } + + /* + * Old timestamp is either the previous sample IRQ time, + * previous flush-time or, if this was first sample, the enable + * time. When we add a sample period to that we should get the + * best approximation of the time-stamp we are handling. + * + * Idea is to always keep the "old_timestamp" matching the + * timestamp which we are currently handling. + */ + data->old_timestamp += sample_period; + buffer[i].ts = data->old_timestamp; + } + /* Reading the FIFO_LVL closes the FIFO access sequence */ + warn = regmap_read(data->regmap, BM1390_REG_FIFO_LVL, &dummy); + if (warn) + dev_warn(data->dev, "Closing FIFO sequence failed\n"); + + if (ret) + return ret; + + if (test_bit(BM1390_CHAN_TEMP, idev->active_scan_mask)) { + ret = regmap_bulk_read(data->regmap, BM1390_REG_TEMP_HI, &temp, + sizeof(temp)); + if (ret) + return ret; + } + + for (i = 0; i < smp_lvl; i++) { + buffer[i].temp = temp; + iio_push_to_buffers(idev, &buffer[i]); + } + + return smp_lvl; +} + +static int bm1390_fifo_flush(struct iio_dev *idev, unsigned int samples) +{ + struct bm1390_data *data = iio_priv(idev); + s64 timestamp; + int ret; + + /* + * If fifo_flush is being called from IRQ handler we know the stored + * timestamp is fairly accurate for the last stored sample. If we are + * called as a result of a read operation from userspace and hence + * before the watermark interrupt was triggered, take a timestamp + * now. We can fall anywhere in between two samples so the error in this + * case is at most one sample period. + * We need to have the IRQ disabled or we risk of messing-up + * the timestamps. If we are ran from IRQ, then the + * IRQF_ONESHOT has us covered - but if we are ran by the + * user-space read we need to disable the IRQ to be on a safe + * side. We do this using synchronous disable so that if the + * IRQ thread is being ran on other CPU we wait for it to be + * finished. + */ + + timestamp = iio_get_time_ns(idev); + mutex_lock(&data->mutex); + ret = __bm1390_fifo_flush(idev, samples, timestamp); + mutex_unlock(&data->mutex); + + return ret; +} + +static int bm1390_set_watermark(struct iio_dev *idev, unsigned int val) +{ + struct bm1390_data *data = iio_priv(idev); + + if (val < BM1390_WMI_MIN || val > BM1390_WMI_MAX) + return -EINVAL; + + mutex_lock(&data->mutex); + data->watermark = val; + mutex_unlock(&data->mutex); + + return 0; +} + +static const struct iio_info bm1390_noirq_info = { + .read_raw = &bm1390_read_raw, +}; + +static const struct iio_info bm1390_info = { + .read_raw = &bm1390_read_raw, + .hwfifo_set_watermark = bm1390_set_watermark, + .hwfifo_flush_to_buffer = bm1390_fifo_flush, +}; + +static int bm1390_chip_init(struct bm1390_data *data) +{ + int ret; + + ret = regmap_write_bits(data->regmap, BM1390_REG_POWER, + BM1390_MASK_POWER, BM1390_POWER_ON); + if (ret) + return ret; + + msleep(1); + + ret = regmap_write_bits(data->regmap, BM1390_REG_RESET, + BM1390_MASK_RESET, BM1390_RESET); + if (ret) + return ret; + + msleep(1); + + ret = regmap_write_bits(data->regmap, BM1390_REG_RESET, + BM1390_MASK_RESET, BM1390_RESET_RELEASE); + if (ret) + return ret; + + msleep(1); + + ret = regmap_reinit_cache(data->regmap, &bm1390_regmap); + if (ret) { + dev_err(data->dev, "Failed to reinit reg cache\n"); + return ret; + } + + /* + * Default to use IIR filter in "middle" mode. Also the AVE_NUM must + * be fixed when IIR is in use. + */ + ret = regmap_update_bits(data->regmap, BM1390_REG_MODE_CTRL, + BM1390_MASK_AVE_NUM, BM1390_IIR_AVE_NUM); + if (ret) + return ret; + + return regmap_update_bits(data->regmap, BM1390_REG_FIFO_CTRL, + BM1390_MASK_IIR_MODE, BM1390_IIR_MODE_MID); +} + +static int bm1390_fifo_set_wmi(struct bm1390_data *data) +{ + u8 regval; + + regval = FIELD_PREP(BM1390_MASK_FIFO_LEN, + data->watermark - BM1390_WMI_MIN); + + return regmap_update_bits(data->regmap, BM1390_REG_FIFO_CTRL, + BM1390_MASK_FIFO_LEN, regval); +} + +static int bm1390_fifo_enable(struct iio_dev *idev) +{ + struct bm1390_data *data = iio_priv(idev); + int ret; + + /* We can't do buffered stuff without IRQ as we never get WMI */ + if (data->irq <= 0) + return -EINVAL; + + guard(mutex)(&data->mutex); + + if (data->trigger_enabled) + return -EBUSY; + + /* Update watermark to HW */ + ret = bm1390_fifo_set_wmi(data); + if (ret) + return ret; + + /* Enable WMI_IRQ */ + ret = regmap_set_bits(data->regmap, BM1390_REG_MODE_CTRL, + BM1390_MASK_WMI_EN); + if (ret) + return ret; + + /* Enable FIFO */ + ret = regmap_set_bits(data->regmap, BM1390_REG_FIFO_CTRL, + BM1390_MASK_FIFO_EN); + if (ret) + return ret; + + data->state = BM1390_STATE_FIFO; + + data->old_timestamp = iio_get_time_ns(idev); + + return bm1390_meas_set(data, BM1390_MEAS_MODE_CONTINUOUS); +} + +static int bm1390_fifo_disable(struct iio_dev *idev) +{ + struct bm1390_data *data = iio_priv(idev); + int ret; + + msleep(1); + + guard(mutex)(&data->mutex); + ret = bm1390_meas_set(data, BM1390_MEAS_MODE_STOP); + if (ret) + return ret; + + /* Disable FIFO */ + ret = regmap_clear_bits(data->regmap, BM1390_REG_FIFO_CTRL, + BM1390_MASK_FIFO_EN); + if (ret) + return ret; + + data->state = BM1390_STATE_SAMPLE; + + /* Disable WMI_IRQ */ + return regmap_clear_bits(data->regmap, BM1390_REG_MODE_CTRL, + BM1390_MASK_WMI_EN); +} + +static int bm1390_buffer_postenable(struct iio_dev *idev) +{ + /* + * If we use data-ready trigger, then the IRQ masks should be handled by + * trigger enable and the hardware buffer is not used but we just update + * results to the IIO FIFO when data-ready triggers. + */ + if (iio_device_get_current_mode(idev) == INDIO_BUFFER_TRIGGERED) + return 0; + + return bm1390_fifo_enable(idev); +} + +static int bm1390_buffer_predisable(struct iio_dev *idev) +{ + if (iio_device_get_current_mode(idev) == INDIO_BUFFER_TRIGGERED) + return 0; + + return bm1390_fifo_disable(idev); +} + +static const struct iio_buffer_setup_ops bm1390_buffer_ops = { + .postenable = bm1390_buffer_postenable, + .predisable = bm1390_buffer_predisable, +}; + +static bool bm1390_handle_trigger(struct iio_dev *idev) +{ + struct bm1390_data *data = iio_priv(idev); + int ret, status; + + /* DRDY is acked by reading status reg */ + ret = regmap_read(data->regmap, BM1390_REG_STATUS, &status); + if (ret || !status) + return false; + + dev_dbg(data->dev, "DRDY trig status 0x%x\n", status); + + if (test_bit(BM1390_CHAN_PRESSURE, idev->active_scan_mask)) { + ret = bm1390_pressure_read(data, &data->buf.pressure); + if (ret) { + dev_warn(data->dev, "sample read failed %d\n", ret); + return false; + } + } + + if (test_bit(BM1390_CHAN_TEMP, idev->active_scan_mask)) { + ret = regmap_bulk_read(data->regmap, BM1390_REG_TEMP_HI, + &data->buf.temp, sizeof(data->buf.temp)); + if (ret) { + dev_warn(data->dev, "temp read failed %d\n", ret); + return true; + } + } + + iio_push_to_buffers_with_ts(idev, &data->buf, sizeof(data->buf), + data->timestamp); + + return true; +} + +static irqreturn_t bm1390_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *idev = pf->indio_dev; + bool result; + + result = bm1390_handle_trigger(idev); + iio_trigger_notify_done(idev->trig); + + return IRQ_RETVAL(result); +} + +/* Get timestamps and wake the thread if we need to read data */ +static irqreturn_t bm1390_irq_handler(int irq, void *private) +{ + struct iio_dev *idev = private; + struct bm1390_data *data = iio_priv(idev); + + data->timestamp = iio_get_time_ns(idev); + + if (data->state == BM1390_STATE_FIFO || data->trigger_enabled) + return IRQ_WAKE_THREAD; + + return IRQ_NONE; +} + +static irqreturn_t bm1390_irq_thread_handler(int irq, void *private) +{ + struct iio_dev *idev = private; + struct bm1390_data *data = iio_priv(idev); + + guard(mutex)(&data->mutex); + + if (data->trigger_enabled) { + iio_trigger_poll_nested(data->trig); + return IRQ_HANDLED; + } + + if (data->state == BM1390_STATE_FIFO) { + int ok; + + ok = __bm1390_fifo_flush(idev, BM1390_FIFO_LENGTH, + data->timestamp); + if (ok > 0) + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static int bm1390_set_drdy_irq(struct bm1390_data *data, bool en) +{ + if (en) + return regmap_set_bits(data->regmap, BM1390_REG_MODE_CTRL, + BM1390_MASK_DRDY_EN); + return regmap_clear_bits(data->regmap, BM1390_REG_MODE_CTRL, + BM1390_MASK_DRDY_EN); +} + +static int bm1390_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct bm1390_data *data = iio_trigger_get_drvdata(trig); + int ret; + + guard(mutex)(&data->mutex); + + if (data->trigger_enabled == state) + return 0; + + if (data->state == BM1390_STATE_FIFO) { + dev_warn(data->dev, "Can't set trigger when FIFO enabled\n"); + return -EBUSY; + } + + data->trigger_enabled = state; + + if (state) { + ret = bm1390_meas_set(data, BM1390_MEAS_MODE_CONTINUOUS); + if (ret) + return ret; + } else { + int dummy; + + ret = bm1390_meas_set(data, BM1390_MEAS_MODE_STOP); + if (ret) + return ret; + + /* + * We need to read the status register in order to ACK the + * data-ready which may have been generated just before we + * disabled the measurement. + */ + ret = regmap_read(data->regmap, BM1390_REG_STATUS, &dummy); + if (ret) + dev_warn(data->dev, "status read failed\n"); + } + + return bm1390_set_drdy_irq(data, state); +} + +static const struct iio_trigger_ops bm1390_trigger_ops = { + .set_trigger_state = bm1390_trigger_set_state, +}; + +static int bm1390_setup_buffer(struct bm1390_data *data, struct iio_dev *idev) +{ + int ret; + + ret = devm_iio_triggered_buffer_setup(data->dev, idev, + &iio_pollfunc_store_time, + &bm1390_trigger_handler, + &bm1390_buffer_ops); + + if (ret) + return dev_err_probe(data->dev, ret, + "iio_triggered_buffer_setup FAIL\n"); + + idev->available_scan_masks = bm1390_scan_masks; + + return 0; +} + +static int bm1390_setup_trigger(struct bm1390_data *data, struct iio_dev *idev, + int irq) +{ + struct iio_trigger *itrig; + char *name; + int ret; + + itrig = devm_iio_trigger_alloc(data->dev, "%sdata-rdy-dev%d", idev->name, + iio_device_id(idev)); + if (!itrig) + return -ENOMEM; + + data->trig = itrig; + + itrig->ops = &bm1390_trigger_ops; + iio_trigger_set_drvdata(itrig, data); + + name = devm_kasprintf(data->dev, GFP_KERNEL, "%s-bm1390", + dev_name(data->dev)); + if (name == NULL) + return -ENOMEM; + + ret = devm_request_threaded_irq(data->dev, irq, bm1390_irq_handler, + &bm1390_irq_thread_handler, + IRQF_ONESHOT, name, idev); + if (ret) + return ret; + + + ret = devm_iio_trigger_register(data->dev, itrig); + if (ret) + return dev_err_probe(data->dev, ret, + "Trigger registration failed\n"); + + return 0; +} + +static int bm1390_probe(struct i2c_client *i2c) +{ + struct bm1390_data *data; + struct regmap *regmap; + struct iio_dev *idev; + struct device *dev; + unsigned int part_id; + int ret; + + dev = &i2c->dev; + + regmap = devm_regmap_init_i2c(i2c, &bm1390_regmap); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Failed to initialize Regmap\n"); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulator\n"); + + ret = regmap_read(regmap, BM1390_REG_PART_ID, &part_id); + if (ret) + return dev_err_probe(dev, ret, "Failed to access sensor\n"); + + if (part_id != BM1390_ID) + dev_warn(dev, "unknown device 0x%x\n", part_id); + + idev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!idev) + return -ENOMEM; + + data = iio_priv(idev); + data->regmap = regmap; + data->dev = dev; + data->irq = i2c->irq; + /* + * For now we just allow BM1390_WMI_MIN to BM1390_WMI_MAX and + * discard every other configuration when triggered mode is not used. + */ + data->watermark = BM1390_WMI_MAX; + mutex_init(&data->mutex); + + idev->channels = bm1390_channels; + idev->num_channels = ARRAY_SIZE(bm1390_channels); + idev->name = "bm1390"; + idev->modes = INDIO_DIRECT_MODE; + + ret = bm1390_chip_init(data); + if (ret) + return dev_err_probe(dev, ret, "sensor init failed\n"); + + ret = bm1390_setup_buffer(data, idev); + if (ret) + return ret; + + /* No trigger if we don't have IRQ for data-ready and WMI */ + if (i2c->irq > 0) { + idev->info = &bm1390_info; + idev->modes |= INDIO_BUFFER_SOFTWARE; + ret = bm1390_setup_trigger(data, idev, i2c->irq); + if (ret) + return ret; + } else { + idev->info = &bm1390_noirq_info; + } + + ret = devm_iio_device_register(dev, idev); + if (ret < 0) + return dev_err_probe(dev, ret, + "Unable to register iio device\n"); + + return 0; +} + +static const struct of_device_id bm1390_of_match[] = { + { .compatible = "rohm,bm1390glv-z" }, + { } +}; +MODULE_DEVICE_TABLE(of, bm1390_of_match); + +static const struct i2c_device_id bm1390_id[] = { + { .name = "bm1390glv-z" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bm1390_id); + +static struct i2c_driver bm1390_driver = { + .driver = { + .name = "bm1390", + .of_match_table = bm1390_of_match, + /* + * Probing explicitly requires a few millisecond of sleep. + * Enabling the VDD regulator may include ramp up rates. + */ + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .probe = bm1390_probe, + .id_table = bm1390_id, +}; +module_i2c_driver(bm1390_driver); + +MODULE_AUTHOR("Matti Vaittinen "); +MODULE_DESCRIPTION("Driver for ROHM BM1390 pressure sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/sdp500.c b/drivers/iio/pressure/sdp500.c new file mode 100644 index 000000000..153f335eb --- /dev/null +++ b/drivers/iio/pressure/sdp500.c @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Sensirion sdp500 and sdp510 pressure sensors + * + * Datasheet: https://sensirion.com/resource/datasheet/sdp600 + */ + +#include +#include +#include +#include +#include + +#define SDP500_CRC8_POLYNOMIAL 0x31 /* x8+x5+x4+1 (normalized to 0x31) */ +#define SDP500_READ_SIZE 3 + +#define SDP500_I2C_START_MEAS 0xF1 + +struct sdp500_data { + struct device *dev; +}; + +DECLARE_CRC8_TABLE(sdp500_crc8_table); + +static int sdp500_start_measurement(struct sdp500_data *data) +{ + struct i2c_client *client = to_i2c_client(data->dev); + + return i2c_smbus_write_byte(client, SDP500_I2C_START_MEAS); +} + +static const struct iio_chan_spec sdp500_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static int sdp500_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + u8 rxbuf[SDP500_READ_SIZE]; + u8 received_crc, calculated_crc; + struct sdp500_data *data = iio_priv(indio_dev); + struct i2c_client *client = to_i2c_client(data->dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = i2c_master_recv(client, rxbuf, SDP500_READ_SIZE); + if (ret < 0) { + dev_err(data->dev, "Failed to receive data"); + return ret; + } + if (ret != SDP500_READ_SIZE) { + dev_err(data->dev, "Data is received wrongly"); + return -EIO; + } + + received_crc = rxbuf[2]; + calculated_crc = crc8(sdp500_crc8_table, rxbuf, + sizeof(rxbuf) - 1, 0x00); + if (received_crc != calculated_crc) { + dev_err(data->dev, + "calculated crc = 0x%.2X, received 0x%.2X", + calculated_crc, received_crc); + return -EIO; + } + + *val = get_unaligned_be16(rxbuf); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 1; + *val2 = 60; + + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } +} + +static const struct iio_info sdp500_info = { + .read_raw = &sdp500_read_raw, +}; + +static int sdp500_probe(struct i2c_client *client) +{ + struct iio_dev *indio_dev; + struct sdp500_data *data; + struct device *dev = &client->dev; + int ret; + u8 rxbuf[SDP500_READ_SIZE]; + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get and enable regulator\n"); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + /* has to be done before the first i2c communication */ + crc8_populate_msb(sdp500_crc8_table, SDP500_CRC8_POLYNOMIAL); + + data = iio_priv(indio_dev); + data->dev = dev; + + indio_dev->name = "sdp500"; + indio_dev->channels = sdp500_channels; + indio_dev->info = &sdp500_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = ARRAY_SIZE(sdp500_channels); + + ret = sdp500_start_measurement(data); + if (ret) + return dev_err_probe(dev, ret, "Failed to start measurement"); + + /* First measurement is not correct, read it out to get rid of it */ + i2c_master_recv(client, rxbuf, SDP500_READ_SIZE); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to register indio_dev"); + + return 0; +} + +static const struct i2c_device_id sdp500_id[] = { + { .name = "sdp500" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sdp500_id); + +static const struct of_device_id sdp500_of_match[] = { + { .compatible = "sensirion,sdp500" }, + { } +}; +MODULE_DEVICE_TABLE(of, sdp500_of_match); + +static struct i2c_driver sdp500_driver = { + .driver = { + .name = "sensirion,sdp500", + .of_match_table = sdp500_of_match, + }, + .probe = sdp500_probe, + .id_table = sdp500_id, +}; +module_i2c_driver(sdp500_driver); + +MODULE_AUTHOR("Thomas Sioutas "); +MODULE_DESCRIPTION("Driver for Sensirion SDP500 differential pressure sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/st_pressure.h b/drivers/iio/pressure/st_pressure.h new file mode 100644 index 000000000..6e11bea78 --- /dev/null +++ b/drivers/iio/pressure/st_pressure.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * STMicroelectronics pressures driver + * + * Copyright 2013 STMicroelectronics Inc. + * + * Denis Ciocca + * v. 1.0.0 + */ + +#ifndef ST_PRESS_H +#define ST_PRESS_H + +#include +#include + +enum st_press_type { + LPS001WP, + LPS25H, + LPS331AP, + LPS22HB, + LPS33HW, + LPS35HW, + LPS22HH, + LPS22DF, + ST_PRESS_MAX, +}; + +#define LPS001WP_PRESS_DEV_NAME "lps001wp" +#define LPS25H_PRESS_DEV_NAME "lps25h" +#define LPS331AP_PRESS_DEV_NAME "lps331ap" +#define LPS22HB_PRESS_DEV_NAME "lps22hb" +#define LPS33HW_PRESS_DEV_NAME "lps33hw" +#define LPS35HW_PRESS_DEV_NAME "lps35hw" +#define LPS22HH_PRESS_DEV_NAME "lps22hh" +#define LPS22DF_PRESS_DEV_NAME "lps22df" + +/** + * struct st_sensors_platform_data - default press platform data + * @drdy_int_pin: default press DRDY is available on INT1 pin. + */ +static __maybe_unused const struct st_sensors_platform_data default_press_pdata = { + .drdy_int_pin = 1, +}; + +#ifdef CONFIG_IIO_BUFFER +int st_press_allocate_ring(struct iio_dev *indio_dev); +int st_press_trig_set_state(struct iio_trigger *trig, bool state); +#define ST_PRESS_TRIGGER_SET_STATE (&st_press_trig_set_state) +#else /* CONFIG_IIO_BUFFER */ +static inline int st_press_allocate_ring(struct iio_dev *indio_dev) +{ + return 0; +} +#define ST_PRESS_TRIGGER_SET_STATE NULL +#endif /* CONFIG_IIO_BUFFER */ + +#endif /* ST_PRESS_H */ diff --git a/drivers/iio/pressure/st_pressure_buffer.c b/drivers/iio/pressure/st_pressure_buffer.c new file mode 100644 index 000000000..0dbf357c2 --- /dev/null +++ b/drivers/iio/pressure/st_pressure_buffer.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics pressures driver + * + * Copyright 2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include +#include + +#include +#include "st_pressure.h" + +int st_press_trig_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + + return st_sensors_set_dataready_irq(indio_dev, state); +} + +static int st_press_buffer_postenable(struct iio_dev *indio_dev) +{ + return st_sensors_set_enable(indio_dev, true); +} + +static int st_press_buffer_predisable(struct iio_dev *indio_dev) +{ + return st_sensors_set_enable(indio_dev, false); +} + +static const struct iio_buffer_setup_ops st_press_buffer_setup_ops = { + .postenable = &st_press_buffer_postenable, + .predisable = &st_press_buffer_predisable, +}; + +int st_press_allocate_ring(struct iio_dev *indio_dev) +{ + return devm_iio_triggered_buffer_setup(indio_dev->dev.parent, indio_dev, + NULL, &st_sensors_trigger_handler, &st_press_buffer_setup_ops); +} diff --git a/drivers/iio/pressure/st_pressure_core.c b/drivers/iio/pressure/st_pressure_core.c new file mode 100644 index 000000000..b70d1cee8 --- /dev/null +++ b/drivers/iio/pressure/st_pressure_core.c @@ -0,0 +1,798 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics pressures driver + * + * Copyright 2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "st_pressure.h" + +/* + * About determining pressure scaling factors + * ------------------------------------------ + * + * Datasheets specify typical pressure sensitivity so that pressure is computed + * according to the following equation : + * pressure[mBar] = raw / sensitivity + * where : + * raw the 24 bits long raw sampled pressure + * sensitivity a scaling factor specified by the datasheet in LSB/mBar + * + * IIO ABI expects pressure to be expressed as kPascal, hence pressure should be + * computed according to : + * pressure[kPascal] = pressure[mBar] / 10 + * = raw / (sensitivity * 10) (1) + * + * Finally, st_press_read_raw() returns pressure scaling factor as an + * IIO_VAL_INT_PLUS_NANO with a zero integral part and "gain" as decimal part. + * Therefore, from (1), "gain" becomes : + * gain = 10^9 / (sensitivity * 10) + * = 10^8 / sensitivity + * + * About determining temperature scaling factors and offsets + * --------------------------------------------------------- + * + * Datasheets specify typical temperature sensitivity and offset so that + * temperature is computed according to the following equation : + * temp[Celsius] = offset[Celsius] + (raw / sensitivity) + * where : + * raw the 16 bits long raw sampled temperature + * offset a constant specified by the datasheet in degree Celsius + * (sometimes zero) + * sensitivity a scaling factor specified by the datasheet in LSB/Celsius + * + * IIO ABI expects temperature to be expressed as milli degree Celsius such as + * user space should compute temperature according to : + * temp[mCelsius] = temp[Celsius] * 10^3 + * = (offset[Celsius] + (raw / sensitivity)) * 10^3 + * = ((offset[Celsius] * sensitivity) + raw) * + * (10^3 / sensitivity) (2) + * + * IIO ABI expects user space to apply offset and scaling factors to raw samples + * according to : + * temp[mCelsius] = (OFFSET + raw) * SCALE + * where : + * OFFSET an arbitrary constant exposed by device + * SCALE an arbitrary scaling factor exposed by device + * + * Matching OFFSET and SCALE with members of (2) gives : + * OFFSET = offset[Celsius] * sensitivity (3) + * SCALE = 10^3 / sensitivity (4) + * + * st_press_read_raw() returns temperature scaling factor as an + * IIO_VAL_FRACTIONAL with a 10^3 numerator and "gain2" as denominator. + * Therefore, from (3), "gain2" becomes : + * gain2 = sensitivity + * + * When declared within channel, i.e. for a non zero specified offset, + * st_press_read_raw() will return the latter as an IIO_VAL_FRACTIONAL such as : + * numerator = OFFSET * 10^3 + * denominator = 10^3 + * giving from (4): + * numerator = offset[Celsius] * 10^3 * sensitivity + * = offset[mCelsius] * gain2 + */ + +#define MCELSIUS_PER_CELSIUS 1000 + +/* Default pressure sensitivity */ +#define ST_PRESS_LSB_PER_MBAR 4096UL +#define ST_PRESS_KPASCAL_NANO_SCALE (100000000UL / \ + ST_PRESS_LSB_PER_MBAR) + +/* Default temperature sensitivity */ +#define ST_PRESS_LSB_PER_CELSIUS 480UL +#define ST_PRESS_MILLI_CELSIUS_OFFSET 42500UL + +/* FULLSCALE */ +#define ST_PRESS_FS_AVL_1100MB 1100 +#define ST_PRESS_FS_AVL_1260MB 1260 + +#define ST_PRESS_1_OUT_XL_ADDR 0x28 +#define ST_TEMP_1_OUT_L_ADDR 0x2b + +/* LPS001WP pressure resolution */ +#define ST_PRESS_LPS001WP_LSB_PER_MBAR 16UL +/* LPS001WP temperature resolution */ +#define ST_PRESS_LPS001WP_LSB_PER_CELSIUS 64UL +/* LPS001WP pressure gain */ +#define ST_PRESS_LPS001WP_FS_AVL_PRESS_GAIN \ + (100000000UL / ST_PRESS_LPS001WP_LSB_PER_MBAR) +/* LPS001WP pressure and temp L addresses */ +#define ST_PRESS_LPS001WP_OUT_L_ADDR 0x28 +#define ST_TEMP_LPS001WP_OUT_L_ADDR 0x2a + +/* LPS25H pressure and temp L addresses */ +#define ST_PRESS_LPS25H_OUT_XL_ADDR 0x28 +#define ST_TEMP_LPS25H_OUT_L_ADDR 0x2b + +/* LPS22HB temperature sensitivity */ +#define ST_PRESS_LPS22HB_LSB_PER_CELSIUS 100UL + +static const struct iio_chan_spec st_press_1_channels[] = { + { + .type = IIO_PRESSURE, + .address = ST_PRESS_1_OUT_XL_ADDR, + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_LE, + }, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + }, + { + .type = IIO_TEMP, + .address = ST_TEMP_1_OUT_L_ADDR, + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + }, + IIO_CHAN_SOFT_TIMESTAMP(2) +}; + +static const struct iio_chan_spec st_press_lps001wp_channels[] = { + { + .type = IIO_PRESSURE, + .address = ST_PRESS_LPS001WP_OUT_L_ADDR, + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_TEMP, + .address = ST_TEMP_LPS001WP_OUT_L_ADDR, + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, + IIO_CHAN_SOFT_TIMESTAMP(2) +}; + +static const struct iio_chan_spec st_press_lps22hb_channels[] = { + { + .type = IIO_PRESSURE, + .address = ST_PRESS_1_OUT_XL_ADDR, + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_LE, + }, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + }, + { + .type = IIO_TEMP, + .address = ST_TEMP_1_OUT_L_ADDR, + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + }, + IIO_CHAN_SOFT_TIMESTAMP(2) +}; + +static const struct st_sensor_settings st_press_sensors_settings[] = { + { + /* + * CUSTOM VALUES FOR LPS331AP SENSOR + * See LPS331AP datasheet: + * http://www2.st.com/resource/en/datasheet/lps331ap.pdf + */ + .wai = 0xbb, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LPS331AP_PRESS_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_press_1_channels, + .num_ch = ARRAY_SIZE(st_press_1_channels), + .odr = { + .addr = 0x20, + .mask = 0x70, + .odr_avl = { + { .hz = 1, .value = 0x01 }, + { .hz = 7, .value = 0x05 }, + { .hz = 13, .value = 0x06 }, + { .hz = 25, .value = 0x07 }, + }, + }, + .pw = { + .addr = 0x20, + .mask = 0x80, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .fs = { + .addr = 0x23, + .mask = 0x30, + .fs_avl = { + /* + * Pressure and temperature sensitivity values + * as defined in table 3 of LPS331AP datasheet. + */ + [0] = { + .num = ST_PRESS_FS_AVL_1260MB, + .gain = ST_PRESS_KPASCAL_NANO_SCALE, + .gain2 = ST_PRESS_LSB_PER_CELSIUS, + }, + }, + }, + .bdu = { + .addr = 0x20, + .mask = 0x04, + }, + .drdy_irq = { + .int1 = { + .addr = 0x22, + .mask = 0x04, + .addr_od = 0x22, + .mask_od = 0x40, + }, + .int2 = { + .addr = 0x22, + .mask = 0x20, + .addr_od = 0x22, + .mask_od = 0x40, + }, + .addr_ihl = 0x22, + .mask_ihl = 0x80, + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x03, + }, + }, + .sim = { + .addr = 0x20, + .value = BIT(0), + }, + .multi_read_bit = true, + .bootime = 2, + }, + { + /* + * CUSTOM VALUES FOR LPS001WP SENSOR + */ + .wai = 0xba, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LPS001WP_PRESS_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_press_lps001wp_channels, + .num_ch = ARRAY_SIZE(st_press_lps001wp_channels), + .odr = { + .addr = 0x20, + .mask = 0x30, + .odr_avl = { + { .hz = 1, .value = 0x01 }, + { .hz = 7, .value = 0x02 }, + { .hz = 13, .value = 0x03 }, + }, + }, + .pw = { + .addr = 0x20, + .mask = 0x40, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .fs = { + .fs_avl = { + /* + * Pressure and temperature resolution values + * as defined in table 3 of LPS001WP datasheet. + */ + [0] = { + .num = ST_PRESS_FS_AVL_1100MB, + .gain = ST_PRESS_LPS001WP_FS_AVL_PRESS_GAIN, + .gain2 = ST_PRESS_LPS001WP_LSB_PER_CELSIUS, + }, + }, + }, + .bdu = { + .addr = 0x20, + .mask = 0x04, + }, + .sim = { + .addr = 0x20, + .value = BIT(0), + }, + .multi_read_bit = true, + .bootime = 2, + }, + { + /* + * CUSTOM VALUES FOR LPS25H SENSOR + * See LPS25H datasheet: + * http://www2.st.com/resource/en/datasheet/lps25h.pdf + */ + .wai = 0xbd, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LPS25H_PRESS_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_press_1_channels, + .num_ch = ARRAY_SIZE(st_press_1_channels), + .odr = { + .addr = 0x20, + .mask = 0x70, + .odr_avl = { + { .hz = 1, .value = 0x01 }, + { .hz = 7, .value = 0x02 }, + { .hz = 13, .value = 0x03 }, + { .hz = 25, .value = 0x04 }, + }, + }, + .pw = { + .addr = 0x20, + .mask = 0x80, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .fs = { + .fs_avl = { + /* + * Pressure and temperature sensitivity values + * as defined in table 3 of LPS25H datasheet. + */ + [0] = { + .num = ST_PRESS_FS_AVL_1260MB, + .gain = ST_PRESS_KPASCAL_NANO_SCALE, + .gain2 = ST_PRESS_LSB_PER_CELSIUS, + }, + }, + }, + .bdu = { + .addr = 0x20, + .mask = 0x04, + }, + .drdy_irq = { + .int1 = { + .addr = 0x23, + .mask = 0x01, + .addr_od = 0x22, + .mask_od = 0x40, + }, + .addr_ihl = 0x22, + .mask_ihl = 0x80, + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x03, + }, + }, + .sim = { + .addr = 0x20, + .value = BIT(0), + }, + .multi_read_bit = true, + .bootime = 2, + }, + { + /* + * CUSTOM VALUES FOR LPS22HB SENSOR + * See LPS22HB datasheet: + * http://www2.st.com/resource/en/datasheet/lps22hb.pdf + */ + .wai = 0xb1, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LPS22HB_PRESS_DEV_NAME, + [1] = LPS33HW_PRESS_DEV_NAME, + [2] = LPS35HW_PRESS_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_press_lps22hb_channels, + .num_ch = ARRAY_SIZE(st_press_lps22hb_channels), + .odr = { + .addr = 0x10, + .mask = 0x70, + .odr_avl = { + { .hz = 1, .value = 0x01 }, + { .hz = 10, .value = 0x02 }, + { .hz = 25, .value = 0x03 }, + { .hz = 50, .value = 0x04 }, + { .hz = 75, .value = 0x05 }, + }, + }, + .pw = { + .addr = 0x10, + .mask = 0x70, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .fs = { + .fs_avl = { + /* + * Pressure and temperature sensitivity values + * as defined in table 3 of LPS22HB datasheet. + */ + [0] = { + .num = ST_PRESS_FS_AVL_1260MB, + .gain = ST_PRESS_KPASCAL_NANO_SCALE, + .gain2 = ST_PRESS_LPS22HB_LSB_PER_CELSIUS, + }, + }, + }, + .bdu = { + .addr = 0x10, + .mask = 0x02, + }, + .drdy_irq = { + .int1 = { + .addr = 0x12, + .mask = 0x04, + .addr_od = 0x12, + .mask_od = 0x40, + }, + .addr_ihl = 0x12, + .mask_ihl = 0x80, + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x03, + }, + }, + .sim = { + .addr = 0x10, + .value = BIT(0), + }, + .multi_read_bit = false, + .bootime = 2, + }, + { + /* + * CUSTOM VALUES FOR LPS22HH SENSOR + * See LPS22HH datasheet: + * http://www2.st.com/resource/en/datasheet/lps22hh.pdf + */ + .wai = 0xb3, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LPS22HH_PRESS_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_press_lps22hb_channels, + .num_ch = ARRAY_SIZE(st_press_lps22hb_channels), + .odr = { + .addr = 0x10, + .mask = 0x70, + .odr_avl = { + { .hz = 1, .value = 0x01 }, + { .hz = 10, .value = 0x02 }, + { .hz = 25, .value = 0x03 }, + { .hz = 50, .value = 0x04 }, + { .hz = 75, .value = 0x05 }, + { .hz = 100, .value = 0x06 }, + { .hz = 200, .value = 0x07 }, + }, + }, + .pw = { + .addr = 0x10, + .mask = 0x70, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .fs = { + .fs_avl = { + /* + * Pressure and temperature sensitivity values + * as defined in table 3 of LPS22HH datasheet. + */ + [0] = { + .num = ST_PRESS_FS_AVL_1260MB, + .gain = ST_PRESS_KPASCAL_NANO_SCALE, + .gain2 = ST_PRESS_LPS22HB_LSB_PER_CELSIUS, + }, + }, + }, + .bdu = { + .addr = 0x10, + .mask = BIT(1), + }, + .drdy_irq = { + .int1 = { + .addr = 0x12, + .mask = BIT(2), + .addr_od = 0x11, + .mask_od = BIT(5), + }, + .addr_ihl = 0x11, + .mask_ihl = BIT(6), + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x03, + }, + }, + .sim = { + .addr = 0x10, + .value = BIT(0), + }, + .multi_read_bit = false, + .bootime = 2, + }, + { + /* + * CUSTOM VALUES FOR LPS22DF SENSOR + * See LPS22DF datasheet: + * http://www.st.com/resource/en/datasheet/lps22df.pdf + */ + .wai = 0xb4, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = LPS22DF_PRESS_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_press_lps22hb_channels, + .num_ch = ARRAY_SIZE(st_press_lps22hb_channels), + .odr = { + .addr = 0x10, + .mask = 0x78, + .odr_avl = { + { .hz = 1, .value = 0x01 }, + { .hz = 4, .value = 0x02 }, + { .hz = 10, .value = 0x03 }, + { .hz = 25, .value = 0x04 }, + { .hz = 50, .value = 0x05 }, + { .hz = 75, .value = 0x06 }, + { .hz = 100, .value = 0x07 }, + { .hz = 200, .value = 0x08 }, + }, + }, + .pw = { + .addr = 0x10, + .mask = 0x78, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .fs = { + .fs_avl = { + /* + * Pressure and temperature sensitivity values + * as defined in table 2 of LPS22DF datasheet. + */ + [0] = { + .num = ST_PRESS_FS_AVL_1260MB, + .gain = ST_PRESS_KPASCAL_NANO_SCALE, + .gain2 = ST_PRESS_LPS22HB_LSB_PER_CELSIUS, + }, + }, + }, + .bdu = { + .addr = 0x11, + .mask = BIT(3), + }, + .drdy_irq = { + .int1 = { + .addr = 0x13, + .mask = BIT(5), + .addr_od = 0x12, + .mask_od = BIT(1), + }, + .addr_ihl = 0x12, + .mask_ihl = BIT(3), + .stat_drdy = { + .addr = ST_SENSORS_DEFAULT_STAT_ADDR, + .mask = 0x03, + }, + }, + .sim = { + .addr = 0x0E, + .value = BIT(5), + }, + .multi_read_bit = false, + .bootime = 2, + }, +}; + +static int st_press_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *ch, + int val, + int val2, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (val2) + return -EINVAL; + + return st_sensors_set_odr(indio_dev, val); + default: + return -EINVAL; + } +} + +static int st_press_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *ch, int *val, + int *val2, long mask) +{ + int err; + struct st_sensor_data *press_data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + err = st_sensors_read_info_raw(indio_dev, ch, val); + if (err < 0) + goto read_error; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (ch->type) { + case IIO_PRESSURE: + *val = 0; + *val2 = press_data->current_fullscale->gain; + return IIO_VAL_INT_PLUS_NANO; + case IIO_TEMP: + *val = MCELSIUS_PER_CELSIUS; + *val2 = press_data->current_fullscale->gain2; + return IIO_VAL_FRACTIONAL; + default: + err = -EINVAL; + goto read_error; + } + + case IIO_CHAN_INFO_OFFSET: + switch (ch->type) { + case IIO_TEMP: + *val = ST_PRESS_MILLI_CELSIUS_OFFSET * + press_data->current_fullscale->gain2; + *val2 = MCELSIUS_PER_CELSIUS; + break; + default: + err = -EINVAL; + goto read_error; + } + + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = press_data->odr; + return IIO_VAL_INT; + default: + return -EINVAL; + } + +read_error: + return err; +} + +static ST_SENSORS_DEV_ATTR_SAMP_FREQ_AVAIL(); + +static struct attribute *st_press_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group st_press_attribute_group = { + .attrs = st_press_attributes, +}; + +static const struct iio_info press_info = { + .attrs = &st_press_attribute_group, + .read_raw = &st_press_read_raw, + .write_raw = &st_press_write_raw, + .debugfs_reg_access = &st_sensors_debugfs_reg_access, +}; + +#ifdef CONFIG_IIO_TRIGGER +static const struct iio_trigger_ops st_press_trigger_ops = { + .set_trigger_state = ST_PRESS_TRIGGER_SET_STATE, + .validate_device = st_sensors_validate_device, +}; +#define ST_PRESS_TRIGGER_OPS (&st_press_trigger_ops) +#else +#define ST_PRESS_TRIGGER_OPS NULL +#endif + +/* + * st_press_get_settings() - get sensor settings from device name + * @name: device name buffer reference. + * + * Return: valid reference on success, NULL otherwise. + */ +const struct st_sensor_settings *st_press_get_settings(const char *name) +{ + int index = st_sensors_get_settings_index(name, + st_press_sensors_settings, + ARRAY_SIZE(st_press_sensors_settings)); + if (index < 0) + return NULL; + + return &st_press_sensors_settings[index]; +} +EXPORT_SYMBOL_NS(st_press_get_settings, "IIO_ST_SENSORS"); + +int st_press_common_probe(struct iio_dev *indio_dev) +{ + struct st_sensor_data *press_data = iio_priv(indio_dev); + struct device *parent = indio_dev->dev.parent; + struct st_sensors_platform_data *pdata = dev_get_platdata(parent); + int err; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &press_info; + + err = st_sensors_verify_id(indio_dev); + if (err < 0) + return err; + + /* + * Skip timestamping channel while declaring available channels to + * common st_sensor layer. Look at st_sensors_get_buffer_element() to + * see how timestamps are explicitly pushed as last samples block + * element. + */ + press_data->num_data_channels = press_data->sensor_settings->num_ch - 1; + indio_dev->channels = press_data->sensor_settings->ch; + indio_dev->num_channels = press_data->sensor_settings->num_ch; + + press_data->current_fullscale = &press_data->sensor_settings->fs.fs_avl[0]; + + press_data->odr = press_data->sensor_settings->odr.odr_avl[0].hz; + + /* Some devices don't support a data ready pin. */ + if (!pdata && (press_data->sensor_settings->drdy_irq.int1.addr || + press_data->sensor_settings->drdy_irq.int2.addr)) + pdata = (struct st_sensors_platform_data *)&default_press_pdata; + + err = st_sensors_init_sensor(indio_dev, pdata); + if (err < 0) + return err; + + err = st_press_allocate_ring(indio_dev); + if (err < 0) + return err; + + if (press_data->irq > 0) { + err = st_sensors_allocate_trigger(indio_dev, + ST_PRESS_TRIGGER_OPS); + if (err < 0) + return err; + } + + return devm_iio_device_register(parent, indio_dev); +} +EXPORT_SYMBOL_NS(st_press_common_probe, "IIO_ST_SENSORS"); + +MODULE_AUTHOR("Denis Ciocca "); +MODULE_DESCRIPTION("STMicroelectronics pressures driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ST_SENSORS"); diff --git a/drivers/iio/pressure/st_pressure_i2c.c b/drivers/iio/pressure/st_pressure_i2c.c new file mode 100644 index 000000000..2b7c84b7e --- /dev/null +++ b/drivers/iio/pressure/st_pressure_i2c.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics pressures driver + * + * Copyright 2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include + +#include +#include +#include "st_pressure.h" + +static const struct of_device_id st_press_of_match[] = { + { + .compatible = "st,lps001wp-press", + .data = LPS001WP_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps25h-press", + .data = LPS25H_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps331ap-press", + .data = LPS331AP_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps22hb-press", + .data = LPS22HB_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps33hw", + .data = LPS33HW_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps35hw", + .data = LPS35HW_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps22hh", + .data = LPS22HH_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps22df", + .data = LPS22DF_PRESS_DEV_NAME, + }, + { } +}; +MODULE_DEVICE_TABLE(of, st_press_of_match); + +static const struct acpi_device_id st_press_acpi_match[] = { + {"SNO9210", LPS22HB}, + { } +}; +MODULE_DEVICE_TABLE(acpi, st_press_acpi_match); + +static const struct i2c_device_id st_press_id_table[] = { + { .name = LPS001WP_PRESS_DEV_NAME, .driver_data = LPS001WP }, + { .name = LPS25H_PRESS_DEV_NAME, .driver_data = LPS25H }, + { .name = LPS331AP_PRESS_DEV_NAME, .driver_data = LPS331AP }, + { .name = LPS22HB_PRESS_DEV_NAME, .driver_data = LPS22HB }, + { .name = LPS33HW_PRESS_DEV_NAME, .driver_data = LPS33HW }, + { .name = LPS35HW_PRESS_DEV_NAME, .driver_data = LPS35HW }, + { .name = LPS22HH_PRESS_DEV_NAME, .driver_data = LPS22HH }, + { .name = LPS22DF_PRESS_DEV_NAME, .driver_data = LPS22DF }, + { } +}; +MODULE_DEVICE_TABLE(i2c, st_press_id_table); + +static int st_press_i2c_probe(struct i2c_client *client) +{ + const struct st_sensor_settings *settings; + struct st_sensor_data *press_data; + struct iio_dev *indio_dev; + int ret; + + st_sensors_dev_name_probe(&client->dev, client->name, sizeof(client->name)); + + settings = st_press_get_settings(client->name); + if (!settings) { + dev_err(&client->dev, "device name %s not recognized.\n", + client->name); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*press_data)); + if (!indio_dev) + return -ENOMEM; + + press_data = iio_priv(indio_dev); + press_data->sensor_settings = (struct st_sensor_settings *)settings; + + ret = st_sensors_i2c_configure(indio_dev, client); + if (ret < 0) + return ret; + + ret = st_sensors_power_enable(indio_dev); + if (ret) + return ret; + + return st_press_common_probe(indio_dev); +} + +static struct i2c_driver st_press_driver = { + .driver = { + .name = "st-press-i2c", + .of_match_table = st_press_of_match, + .acpi_match_table = st_press_acpi_match, + }, + .probe = st_press_i2c_probe, + .id_table = st_press_id_table, +}; +module_i2c_driver(st_press_driver); + +MODULE_AUTHOR("Denis Ciocca "); +MODULE_DESCRIPTION("STMicroelectronics pressures i2c driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ST_SENSORS"); diff --git a/drivers/iio/pressure/st_pressure_spi.c b/drivers/iio/pressure/st_pressure_spi.c new file mode 100644 index 000000000..8add0c392 --- /dev/null +++ b/drivers/iio/pressure/st_pressure_spi.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * STMicroelectronics pressures driver + * + * Copyright 2013 STMicroelectronics Inc. + * + * Denis Ciocca + */ + +#include +#include +#include +#include + +#include +#include +#include "st_pressure.h" + +/* + * For new single-chip sensors use as compatible string. + * For old single-chip devices keep -press to maintain + * compatibility + */ +static const struct of_device_id st_press_of_match[] = { + { + .compatible = "st,lps001wp-press", + .data = LPS001WP_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps25h-press", + .data = LPS25H_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps331ap-press", + .data = LPS331AP_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps22hb-press", + .data = LPS22HB_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps33hw", + .data = LPS33HW_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps35hw", + .data = LPS35HW_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps22hh", + .data = LPS22HH_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps22df", + .data = LPS22DF_PRESS_DEV_NAME, + }, + { } +}; +MODULE_DEVICE_TABLE(of, st_press_of_match); + +static int st_press_spi_probe(struct spi_device *spi) +{ + const struct st_sensor_settings *settings; + struct st_sensor_data *press_data; + struct iio_dev *indio_dev; + int err; + + st_sensors_dev_name_probe(&spi->dev, spi->modalias, sizeof(spi->modalias)); + + settings = st_press_get_settings(spi->modalias); + if (!settings) { + dev_err(&spi->dev, "device name %s not recognized.\n", + spi->modalias); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*press_data)); + if (!indio_dev) + return -ENOMEM; + + press_data = iio_priv(indio_dev); + press_data->sensor_settings = (struct st_sensor_settings *)settings; + + err = st_sensors_spi_configure(indio_dev, spi); + if (err < 0) + return err; + + err = st_sensors_power_enable(indio_dev); + if (err) + return err; + + return st_press_common_probe(indio_dev); +} + +static const struct spi_device_id st_press_id_table[] = { + { .name = LPS001WP_PRESS_DEV_NAME }, + { .name = LPS25H_PRESS_DEV_NAME }, + { .name = LPS331AP_PRESS_DEV_NAME }, + { .name = LPS22HB_PRESS_DEV_NAME }, + { .name = LPS33HW_PRESS_DEV_NAME }, + { .name = LPS35HW_PRESS_DEV_NAME }, + { .name = LPS22HH_PRESS_DEV_NAME }, + { .name = LPS22DF_PRESS_DEV_NAME }, + { .name = "lps001wp-press" }, + { .name = "lps25h-press" }, + { .name = "lps331ap-press" }, + { .name = "lps22hb-press" }, + { } +}; +MODULE_DEVICE_TABLE(spi, st_press_id_table); + +static struct spi_driver st_press_driver = { + .driver = { + .name = "st-press-spi", + .of_match_table = st_press_of_match, + }, + .probe = st_press_spi_probe, + .id_table = st_press_id_table, +}; +module_spi_driver(st_press_driver); + +MODULE_AUTHOR("Denis Ciocca "); +MODULE_DESCRIPTION("STMicroelectronics pressures spi driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ST_SENSORS"); diff --git a/drivers/iio/pressure/t5403.c b/drivers/iio/pressure/t5403.c new file mode 100644 index 000000000..ab30f6745 --- /dev/null +++ b/drivers/iio/pressure/t5403.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * t5403.c - Support for EPCOS T5403 pressure/temperature sensor + * + * Copyright (c) 2014 Peter Meerwald + * + * (7-bit I2C slave address 0x77) + * + * TODO: end-of-conversion irq + */ + +#include +#include +#include +#include +#include + +#define T5403_DATA 0xf5 /* data, LSB first, 16 bit */ +#define T5403_CALIB_DATA 0x8e /* 10 calibration coeff., LSB first, 16 bit */ +#define T5403_SLAVE_ADDR 0x88 /* I2C slave address, 0x77 */ +#define T5403_COMMAND 0xf1 + +/* command bits */ +#define T5403_MODE_SHIFT 3 /* conversion time: 2, 8, 16, 66 ms */ +#define T5403_PT BIT(1) /* 0 .. pressure, 1 .. temperature measurement */ +#define T5403_SCO BIT(0) /* start conversion */ + +#define T5403_MODE_LOW 0 +#define T5403_MODE_STANDARD 1 +#define T5403_MODE_HIGH 2 +#define T5403_MODE_ULTRA_HIGH 3 + +#define T5403_I2C_MASK (~BIT(7)) +#define T5403_I2C_ADDR 0x77 + +static const int t5403_pressure_conv_ms[] = {2, 8, 16, 66}; + +struct t5403_data { + struct i2c_client *client; + struct mutex lock; + int mode; + __le16 c[10]; +}; + +#define T5403_C_U16(i) le16_to_cpu(data->c[(i) - 1]) +#define T5403_C(i) sign_extend32(T5403_C_U16(i), 15) + +static int t5403_read(struct t5403_data *data, bool pressure) +{ + int wait_time = 3; /* wakeup time in ms */ + + int ret = i2c_smbus_write_byte_data(data->client, T5403_COMMAND, + (pressure ? (data->mode << T5403_MODE_SHIFT) : T5403_PT) | + T5403_SCO); + if (ret < 0) + return ret; + + wait_time += pressure ? t5403_pressure_conv_ms[data->mode] : 2; + + msleep(wait_time); + + return i2c_smbus_read_word_data(data->client, T5403_DATA); +} + +static int t5403_comp_pressure(struct t5403_data *data, int *val, int *val2) +{ + int ret; + s16 t_r; + u16 p_r; + s32 S, O, X; + + mutex_lock(&data->lock); + + ret = t5403_read(data, false); + if (ret < 0) + goto done; + t_r = ret; + + ret = t5403_read(data, true); + if (ret < 0) + goto done; + p_r = ret; + + /* see EPCOS application note */ + S = T5403_C_U16(3) + (s32) T5403_C_U16(4) * t_r / 0x20000 + + T5403_C(5) * t_r / 0x8000 * t_r / 0x80000 + + T5403_C(9) * t_r / 0x8000 * t_r / 0x8000 * t_r / 0x10000; + + O = T5403_C(6) * 0x4000 + T5403_C(7) * t_r / 8 + + T5403_C(8) * t_r / 0x8000 * t_r / 16 + + T5403_C(9) * t_r / 0x8000 * t_r / 0x10000 * t_r; + + X = (S * p_r + O) / 0x4000; + + X += ((X - 75000) * (X - 75000) / 0x10000 - 9537) * + T5403_C(10) / 0x10000; + + *val = X / 1000; + *val2 = (X % 1000) * 1000; + +done: + mutex_unlock(&data->lock); + return ret; +} + +static int t5403_comp_temp(struct t5403_data *data, int *val) +{ + int ret; + s16 t_r; + + mutex_lock(&data->lock); + ret = t5403_read(data, false); + if (ret < 0) + goto done; + t_r = ret; + + /* see EPCOS application note */ + *val = ((s32) T5403_C_U16(1) * t_r / 0x100 + + (s32) T5403_C_U16(2) * 0x40) * 1000 / 0x10000; + +done: + mutex_unlock(&data->lock); + return ret; +} + +static int t5403_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct t5403_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_PRESSURE: + ret = t5403_comp_pressure(data, val, val2); + if (ret < 0) + return ret; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + ret = t5403_comp_temp(data, val); + if (ret < 0) + return ret; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = t5403_pressure_conv_ms[data->mode] * 1000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int t5403_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct t5403_data *data = iio_priv(indio_dev); + int i; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val != 0) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(t5403_pressure_conv_ms); i++) + if (val2 == t5403_pressure_conv_ms[i] * 1000) { + mutex_lock(&data->lock); + data->mode = i; + mutex_unlock(&data->lock); + return 0; + } + return -EINVAL; + default: + return -EINVAL; + } +} + +static const struct iio_chan_spec t5403_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_INT_TIME), + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, +}; + +static IIO_CONST_ATTR_INT_TIME_AVAIL("0.002 0.008 0.016 0.066"); + +static struct attribute *t5403_attributes[] = { + &iio_const_attr_integration_time_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group t5403_attribute_group = { + .attrs = t5403_attributes, +}; + +static const struct iio_info t5403_info = { + .read_raw = &t5403_read_raw, + .write_raw = &t5403_write_raw, + .attrs = &t5403_attribute_group, +}; + +static int t5403_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct t5403_data *data; + struct iio_dev *indio_dev; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_I2C_BLOCK)) + return -EOPNOTSUPP; + + ret = i2c_smbus_read_byte_data(client, T5403_SLAVE_ADDR); + if (ret < 0) + return ret; + if ((ret & T5403_I2C_MASK) != T5403_I2C_ADDR) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + mutex_init(&data->lock); + + i2c_set_clientdata(client, indio_dev); + indio_dev->info = &t5403_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = t5403_channels; + indio_dev->num_channels = ARRAY_SIZE(t5403_channels); + + data->mode = T5403_MODE_STANDARD; + + ret = i2c_smbus_read_i2c_block_data(data->client, T5403_CALIB_DATA, + sizeof(data->c), (u8 *) data->c); + if (ret < 0) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id t5403_id[] = { + { .name = "t5403" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, t5403_id); + +static struct i2c_driver t5403_driver = { + .driver = { + .name = "t5403", + }, + .probe = t5403_probe, + .id_table = t5403_id, +}; +module_i2c_driver(t5403_driver); + +MODULE_AUTHOR("Peter Meerwald "); +MODULE_DESCRIPTION("EPCOS T5403 pressure/temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/zpa2326.c b/drivers/iio/pressure/zpa2326.c new file mode 100644 index 000000000..b38493ff3 --- /dev/null +++ b/drivers/iio/pressure/zpa2326.c @@ -0,0 +1,1708 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Murata ZPA2326 pressure and temperature sensor IIO driver + * + * Copyright (c) 2016 Parrot S.A. + * + * Author: Gregor Boirie + */ + +/** + * DOC: ZPA2326 theory of operations + * + * This driver supports %INDIO_DIRECT_MODE and %INDIO_BUFFER_TRIGGERED IIO + * modes. + * A internal hardware trigger is also implemented to dispatch registered IIO + * trigger consumers upon "sample ready" interrupts. + * + * ZPA2326 hardware supports 2 sampling mode: one shot and continuous. + * + * A complete one shot sampling cycle gets device out of low power mode, + * performs pressure and temperature measurements, then automatically switches + * back to low power mode. It is meant for on demand sampling with optimal power + * saving at the cost of lower sampling rate and higher software overhead. + * This is a natural candidate for IIO read_raw hook implementation + * (%INDIO_DIRECT_MODE). It is also used for triggered buffering support to + * ensure explicit synchronization with external trigger events + * (%INDIO_BUFFER_TRIGGERED). + * + * The continuous mode works according to a periodic hardware measurement + * process continuously pushing samples into an internal hardware FIFO (for + * pressure samples only). Measurement cycle completion may be signaled by a + * "sample ready" interrupt. + * Typical software sequence of operations : + * - get device out of low power mode, + * - setup hardware sampling period, + * - at end of period, upon data ready interrupt: pop pressure samples out of + * hardware FIFO and fetch temperature sample + * - when no longer needed, stop sampling process by putting device into + * low power mode. + * This mode is used to implement %INDIO_BUFFER_TRIGGERED mode if device tree + * declares a valid interrupt line. In this case, the internal hardware trigger + * drives acquisition. + * + * Note that hardware sampling frequency is taken into account only when + * internal hardware trigger is attached as the highest sampling rate seems to + * be the most energy efficient. + * + * TODO: + * preset pressure threshold crossing / IIO events ; + * differential pressure sampling ; + * hardware samples averaging. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "zpa2326.h" + +/* 200 ms should be enough for the longest conversion time in one-shot mode. */ +#define ZPA2326_CONVERSION_JIFFIES (HZ / 5) + +/* There should be a 1 ms delay (Tpup) after getting out of reset. */ +#define ZPA2326_TPUP_USEC_MIN (1000) +#define ZPA2326_TPUP_USEC_MAX (2000) + +/** + * struct zpa2326_frequency - Hardware sampling frequency descriptor + * @hz : Frequency in Hertz. + * @odr: Output Data Rate word as expected by %ZPA2326_CTRL_REG3_REG. + */ +struct zpa2326_frequency { + int hz; + u16 odr; +}; + +/* + * Keep these in strict ascending order: last array entry is expected to + * correspond to the highest sampling frequency. + */ +static const struct zpa2326_frequency zpa2326_sampling_frequencies[] = { + { .hz = 1, .odr = 1 << ZPA2326_CTRL_REG3_ODR_SHIFT }, + { .hz = 5, .odr = 5 << ZPA2326_CTRL_REG3_ODR_SHIFT }, + { .hz = 11, .odr = 6 << ZPA2326_CTRL_REG3_ODR_SHIFT }, + { .hz = 23, .odr = 7 << ZPA2326_CTRL_REG3_ODR_SHIFT }, +}; + +/* Return the highest hardware sampling frequency available. */ +static const struct zpa2326_frequency *zpa2326_highest_frequency(void) +{ + return &zpa2326_sampling_frequencies[ + ARRAY_SIZE(zpa2326_sampling_frequencies) - 1]; +} + +/** + * struct zpa2326_private - Per-device internal private state + * @timestamp: Buffered samples ready datum. + * @regmap: Underlying I2C / SPI bus adapter used to abstract slave register + * accesses. + * @result: Allows sampling logic to get completion status of operations + * that interrupt handlers perform asynchronously. + * @data_ready: Interrupt handler uses this to wake user context up at sampling + * operation completion. + * @trigger: Optional hardware / interrupt driven trigger used to notify + * external devices a new sample is ready. + * @waken: Flag indicating whether or not device has just been powered on. + * @irq: Optional interrupt line: negative or zero if not declared into + * DT, in which case sampling logic keeps polling status register + * to detect completion. + * @frequency: Current hardware sampling frequency. + * @vref: Power / voltage reference. + * @vdd: Power supply. + */ +struct zpa2326_private { + s64 timestamp; + struct regmap *regmap; + int result; + struct completion data_ready; + struct iio_trigger *trigger; + bool waken; + int irq; + const struct zpa2326_frequency *frequency; + struct regulator *vref; + struct regulator *vdd; +}; + +#define zpa2326_err(idev, fmt, ...) \ + dev_err(idev->dev.parent, fmt "\n", ##__VA_ARGS__) + +#define zpa2326_warn(idev, fmt, ...) \ + dev_warn(idev->dev.parent, fmt "\n", ##__VA_ARGS__) + +#define zpa2326_dbg(idev, fmt, ...) \ + dev_dbg(idev->dev.parent, fmt "\n", ##__VA_ARGS__) + +bool zpa2326_isreg_writeable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ZPA2326_REF_P_XL_REG: + case ZPA2326_REF_P_L_REG: + case ZPA2326_REF_P_H_REG: + case ZPA2326_RES_CONF_REG: + case ZPA2326_CTRL_REG0_REG: + case ZPA2326_CTRL_REG1_REG: + case ZPA2326_CTRL_REG2_REG: + case ZPA2326_CTRL_REG3_REG: + case ZPA2326_THS_P_LOW_REG: + case ZPA2326_THS_P_HIGH_REG: + return true; + + default: + return false; + } +} +EXPORT_SYMBOL_NS_GPL(zpa2326_isreg_writeable, "IIO_ZPA2326"); + +bool zpa2326_isreg_readable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ZPA2326_REF_P_XL_REG: + case ZPA2326_REF_P_L_REG: + case ZPA2326_REF_P_H_REG: + case ZPA2326_DEVICE_ID_REG: + case ZPA2326_RES_CONF_REG: + case ZPA2326_CTRL_REG0_REG: + case ZPA2326_CTRL_REG1_REG: + case ZPA2326_CTRL_REG2_REG: + case ZPA2326_CTRL_REG3_REG: + case ZPA2326_INT_SOURCE_REG: + case ZPA2326_THS_P_LOW_REG: + case ZPA2326_THS_P_HIGH_REG: + case ZPA2326_STATUS_REG: + case ZPA2326_PRESS_OUT_XL_REG: + case ZPA2326_PRESS_OUT_L_REG: + case ZPA2326_PRESS_OUT_H_REG: + case ZPA2326_TEMP_OUT_L_REG: + case ZPA2326_TEMP_OUT_H_REG: + return true; + + default: + return false; + } +} +EXPORT_SYMBOL_NS_GPL(zpa2326_isreg_readable, "IIO_ZPA2326"); + +bool zpa2326_isreg_precious(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ZPA2326_INT_SOURCE_REG: + case ZPA2326_PRESS_OUT_H_REG: + return true; + + default: + return false; + } +} +EXPORT_SYMBOL_NS_GPL(zpa2326_isreg_precious, "IIO_ZPA2326"); + +/** + * zpa2326_enable_device() - Enable device, i.e. get out of low power mode. + * @indio_dev: The IIO device associated with the hardware to enable. + * + * Required to access complete register space and to perform any sampling + * or control operations. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_enable_device(const struct iio_dev *indio_dev) +{ + int err; + + err = regmap_write(((struct zpa2326_private *) + iio_priv(indio_dev))->regmap, + ZPA2326_CTRL_REG0_REG, ZPA2326_CTRL_REG0_ENABLE); + if (err) { + zpa2326_err(indio_dev, "failed to enable device (%d)", err); + return err; + } + + zpa2326_dbg(indio_dev, "enabled"); + + return 0; +} + +/** + * zpa2326_sleep() - Disable device, i.e. switch to low power mode. + * @indio_dev: The IIO device associated with the hardware to disable. + * + * Only %ZPA2326_DEVICE_ID_REG and %ZPA2326_CTRL_REG0_REG registers may be + * accessed once device is in the disabled state. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_sleep(const struct iio_dev *indio_dev) +{ + int err; + + err = regmap_write(((struct zpa2326_private *) + iio_priv(indio_dev))->regmap, + ZPA2326_CTRL_REG0_REG, 0); + if (err) { + zpa2326_err(indio_dev, "failed to sleep (%d)", err); + return err; + } + + zpa2326_dbg(indio_dev, "sleeping"); + + return 0; +} + +/** + * zpa2326_reset_device() - Reset device to default hardware state. + * @indio_dev: The IIO device associated with the hardware to reset. + * + * Disable sampling and empty hardware FIFO. + * Device must be enabled before reset, i.e. not in low power mode. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_reset_device(const struct iio_dev *indio_dev) +{ + int err; + + err = regmap_write(((struct zpa2326_private *) + iio_priv(indio_dev))->regmap, + ZPA2326_CTRL_REG2_REG, ZPA2326_CTRL_REG2_SWRESET); + if (err) { + zpa2326_err(indio_dev, "failed to reset device (%d)", err); + return err; + } + + usleep_range(ZPA2326_TPUP_USEC_MIN, ZPA2326_TPUP_USEC_MAX); + + zpa2326_dbg(indio_dev, "reset"); + + return 0; +} + +/** + * zpa2326_start_oneshot() - Start a single sampling cycle, i.e. in one shot + * mode. + * @indio_dev: The IIO device associated with the sampling hardware. + * + * Device must have been previously enabled and configured for one shot mode. + * Device will be switched back to low power mode at end of cycle. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_start_oneshot(const struct iio_dev *indio_dev) +{ + int err; + + err = regmap_write(((struct zpa2326_private *) + iio_priv(indio_dev))->regmap, + ZPA2326_CTRL_REG0_REG, + ZPA2326_CTRL_REG0_ENABLE | + ZPA2326_CTRL_REG0_ONE_SHOT); + if (err) { + zpa2326_err(indio_dev, "failed to start one shot cycle (%d)", + err); + return err; + } + + zpa2326_dbg(indio_dev, "one shot cycle started"); + + return 0; +} + +/** + * zpa2326_power_on() - Power on device to allow subsequent configuration. + * @indio_dev: The IIO device associated with the sampling hardware. + * @private: Internal private state related to @indio_dev. + * + * Sampling will be disabled, preventing strange things from happening in our + * back. Hardware FIFO content will be cleared. + * When successful, device will be left in the enabled state to allow further + * configuration. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_power_on(const struct iio_dev *indio_dev, + const struct zpa2326_private *private) +{ + int err; + + err = regulator_enable(private->vref); + if (err) + return err; + + err = regulator_enable(private->vdd); + if (err) + goto vref; + + zpa2326_dbg(indio_dev, "powered on"); + + err = zpa2326_enable_device(indio_dev); + if (err) + goto vdd; + + err = zpa2326_reset_device(indio_dev); + if (err) + goto sleep; + + return 0; + +sleep: + zpa2326_sleep(indio_dev); +vdd: + regulator_disable(private->vdd); +vref: + regulator_disable(private->vref); + + zpa2326_dbg(indio_dev, "powered off"); + + return err; +} + +/** + * zpa2326_power_off() - Power off device, i.e. disable attached power + * regulators. + * @indio_dev: The IIO device associated with the sampling hardware. + * @private: Internal private state related to @indio_dev. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static void zpa2326_power_off(const struct iio_dev *indio_dev, + const struct zpa2326_private *private) +{ + regulator_disable(private->vdd); + regulator_disable(private->vref); + + zpa2326_dbg(indio_dev, "powered off"); +} + +/** + * zpa2326_config_oneshot() - Setup device for one shot / on demand mode. + * @indio_dev: The IIO device associated with the sampling hardware. + * @irq: Optional interrupt line the hardware uses to notify new data + * samples are ready. Negative or zero values indicate no interrupts + * are available, meaning polling is required. + * + * Output Data Rate is configured for the highest possible rate so that + * conversion time and power consumption are reduced to a minimum. + * Note that hardware internal averaging machinery (not implemented in this + * driver) is not applicable in this mode. + * + * Device must have been previously enabled before calling + * zpa2326_config_oneshot(). + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_config_oneshot(const struct iio_dev *indio_dev, + int irq) +{ + struct regmap *regs = ((struct zpa2326_private *) + iio_priv(indio_dev))->regmap; + const struct zpa2326_frequency *freq = zpa2326_highest_frequency(); + int err; + + /* Setup highest available Output Data Rate for one shot mode. */ + err = regmap_write(regs, ZPA2326_CTRL_REG3_REG, freq->odr); + if (err) + return err; + + if (irq > 0) { + /* Request interrupt when new sample is available. */ + err = regmap_write(regs, ZPA2326_CTRL_REG1_REG, + (u8)~ZPA2326_CTRL_REG1_MASK_DATA_READY); + + if (err) { + dev_err(indio_dev->dev.parent, + "failed to setup one shot mode (%d)", err); + return err; + } + } + + zpa2326_dbg(indio_dev, "one shot mode setup @%dHz", freq->hz); + + return 0; +} + +/** + * zpa2326_clear_fifo() - Clear remaining entries in hardware FIFO. + * @indio_dev: The IIO device associated with the sampling hardware. + * @min_count: Number of samples present within hardware FIFO. + * + * @min_count argument is a hint corresponding to the known minimum number of + * samples currently living in the FIFO. This allows to reduce the number of bus + * accesses by skipping status register read operation as long as we know for + * sure there are still entries left. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_clear_fifo(const struct iio_dev *indio_dev, + unsigned int min_count) +{ + struct regmap *regs = ((struct zpa2326_private *) + iio_priv(indio_dev))->regmap; + int err; + unsigned int val; + + if (!min_count) { + /* + * No hint: read status register to determine whether FIFO is + * empty or not. + */ + err = regmap_read(regs, ZPA2326_STATUS_REG, &val); + + if (err < 0) + goto err; + + if (val & ZPA2326_STATUS_FIFO_E) + /* Fifo is empty: nothing to trash. */ + return 0; + } + + /* Clear FIFO. */ + do { + /* + * A single fetch from pressure MSB register is enough to pop + * values out of FIFO. + */ + err = regmap_read(regs, ZPA2326_PRESS_OUT_H_REG, &val); + if (err < 0) + goto err; + + if (min_count) { + /* + * We know for sure there are at least min_count entries + * left in FIFO. Skip status register read. + */ + min_count--; + continue; + } + + err = regmap_read(regs, ZPA2326_STATUS_REG, &val); + if (err < 0) + goto err; + + } while (!(val & ZPA2326_STATUS_FIFO_E)); + + zpa2326_dbg(indio_dev, "FIFO cleared"); + + return 0; + +err: + zpa2326_err(indio_dev, "failed to clear FIFO (%d)", err); + + return err; +} + +/** + * zpa2326_dequeue_pressure() - Retrieve the most recent pressure sample from + * hardware FIFO. + * @indio_dev: The IIO device associated with the sampling hardware. + * @pressure: Sampled pressure output. + * + * Note that ZPA2326 hardware FIFO stores pressure samples only. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_dequeue_pressure(const struct iio_dev *indio_dev, + u32 *pressure) +{ + struct regmap *regs = ((struct zpa2326_private *) + iio_priv(indio_dev))->regmap; + unsigned int val; + int err; + int cleared = -1; + + err = regmap_read(regs, ZPA2326_STATUS_REG, &val); + if (err < 0) + return err; + + *pressure = 0; + + if (val & ZPA2326_STATUS_P_OR) { + /* + * Fifo overrun : first sample dequeued from FIFO is the + * newest. + */ + zpa2326_warn(indio_dev, "FIFO overflow"); + + err = regmap_bulk_read(regs, ZPA2326_PRESS_OUT_XL_REG, pressure, + 3); + if (err) + return err; + +#define ZPA2326_FIFO_DEPTH (16U) + /* Hardware FIFO may hold no more than 16 pressure samples. */ + return zpa2326_clear_fifo(indio_dev, ZPA2326_FIFO_DEPTH - 1); + } + + /* + * Fifo has not overflown : retrieve newest sample. We need to pop + * values out until FIFO is empty : last fetched pressure is the newest. + * In nominal cases, we should find a single queued sample only. + */ + do { + err = regmap_bulk_read(regs, ZPA2326_PRESS_OUT_XL_REG, pressure, + 3); + if (err) + return err; + + err = regmap_read(regs, ZPA2326_STATUS_REG, &val); + if (err < 0) + return err; + + cleared++; + } while (!(val & ZPA2326_STATUS_FIFO_E)); + + if (cleared) + /* + * Samples were pushed by hardware during previous rounds but we + * didn't consume them fast enough: inform user. + */ + zpa2326_dbg(indio_dev, "cleared %d FIFO entries", cleared); + + return 0; +} + +/** + * zpa2326_fill_sample_buffer() - Enqueue new channel samples to IIO buffer. + * @indio_dev: The IIO device associated with the sampling hardware. + * @private: Internal private state related to @indio_dev. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_fill_sample_buffer(struct iio_dev *indio_dev, + const struct zpa2326_private *private) +{ + struct { + u32 pressure; + u16 temperature; + aligned_s64 timestamp; + } sample = { }; + int err; + + if (test_bit(0, indio_dev->active_scan_mask)) { + /* Get current pressure from hardware FIFO. */ + err = zpa2326_dequeue_pressure(indio_dev, &sample.pressure); + if (err) { + zpa2326_warn(indio_dev, "failed to fetch pressure (%d)", + err); + return err; + } + } + + if (test_bit(1, indio_dev->active_scan_mask)) { + /* Get current temperature. */ + err = regmap_bulk_read(private->regmap, ZPA2326_TEMP_OUT_L_REG, + &sample.temperature, 2); + if (err) { + zpa2326_warn(indio_dev, + "failed to fetch temperature (%d)", err); + return err; + } + } + + /* + * Now push samples using timestamp stored either : + * - by hardware interrupt handler if interrupt is available: see + * zpa2326_handle_irq(), + * - or oneshot completion polling machinery : see + * zpa2326_trigger_handler(). + */ + zpa2326_dbg(indio_dev, "filling raw samples buffer"); + + iio_push_to_buffers_with_ts(indio_dev, &sample, sizeof(sample), + private->timestamp); + + return 0; +} + +#ifdef CONFIG_PM +static int zpa2326_runtime_suspend(struct device *parent) +{ + const struct iio_dev *indio_dev = dev_get_drvdata(parent); + + if (pm_runtime_autosuspend_expiration(parent)) + /* Userspace changed autosuspend delay. */ + return -EAGAIN; + + zpa2326_power_off(indio_dev, iio_priv(indio_dev)); + + return 0; +} + +static int zpa2326_runtime_resume(struct device *parent) +{ + const struct iio_dev *indio_dev = dev_get_drvdata(parent); + + return zpa2326_power_on(indio_dev, iio_priv(indio_dev)); +} + +const struct dev_pm_ops zpa2326_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(zpa2326_runtime_suspend, zpa2326_runtime_resume, + NULL) +}; +EXPORT_SYMBOL_NS_GPL(zpa2326_pm_ops, "IIO_ZPA2326"); + +/** + * zpa2326_resume() - Request the PM layer to power supply the device. + * @indio_dev: The IIO device associated with the sampling hardware. + * + * Return: + * < 0 - a negative error code meaning failure ; + * 0 - success, device has just been powered up ; + * 1 - success, device was already powered. + */ +static int zpa2326_resume(const struct iio_dev *indio_dev) +{ + int err; + + err = pm_runtime_get_sync(indio_dev->dev.parent); + if (err < 0) { + pm_runtime_put(indio_dev->dev.parent); + return err; + } + + if (err > 0) { + /* + * Device was already power supplied: get it out of low power + * mode and inform caller. + */ + zpa2326_enable_device(indio_dev); + return 1; + } + + /* Inform caller device has just been brought back to life. */ + return 0; +} + +/** + * zpa2326_suspend() - Schedule a power down using autosuspend feature of PM + * layer. + * @indio_dev: The IIO device associated with the sampling hardware. + * + * Device is switched to low power mode at first to save power even when + * attached regulator is a "dummy" one. + */ +static void zpa2326_suspend(struct iio_dev *indio_dev) +{ + struct device *parent = indio_dev->dev.parent; + + zpa2326_sleep(indio_dev); + + pm_runtime_put_autosuspend(parent); +} + +static void zpa2326_init_runtime(struct device *parent) +{ + pm_runtime_get_noresume(parent); + pm_runtime_set_active(parent); + pm_runtime_enable(parent); + pm_runtime_set_autosuspend_delay(parent, 1000); + pm_runtime_use_autosuspend(parent); + pm_runtime_put_autosuspend(parent); +} + +static void zpa2326_fini_runtime(struct device *parent) +{ + pm_runtime_disable(parent); + pm_runtime_set_suspended(parent); +} +#else /* !CONFIG_PM */ +static int zpa2326_resume(const struct iio_dev *indio_dev) +{ + zpa2326_enable_device(indio_dev); + + return 0; +} + +static void zpa2326_suspend(struct iio_dev *indio_dev) +{ + zpa2326_sleep(indio_dev); +} + +#define zpa2326_init_runtime(_parent) +#define zpa2326_fini_runtime(_parent) +#endif /* !CONFIG_PM */ + +/** + * zpa2326_handle_irq() - Process hardware interrupts. + * @irq: Interrupt line the hardware uses to notify new data has arrived. + * @data: The IIO device associated with the sampling hardware. + * + * Timestamp buffered samples as soon as possible then schedule threaded bottom + * half. + * + * Return: Always successful. + */ +static irqreturn_t zpa2326_handle_irq(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + + if (iio_buffer_enabled(indio_dev)) { + /* Timestamping needed for buffered sampling only. */ + ((struct zpa2326_private *) + iio_priv(indio_dev))->timestamp = iio_get_time_ns(indio_dev); + } + + return IRQ_WAKE_THREAD; +} + +/** + * zpa2326_handle_threaded_irq() - Interrupt bottom-half handler. + * @irq: Interrupt line the hardware uses to notify new data has arrived. + * @data: The IIO device associated with the sampling hardware. + * + * Mainly ensures interrupt is caused by a real "new sample available" + * condition. This relies upon the ability to perform blocking / sleeping bus + * accesses to slave's registers. This is why zpa2326_handle_threaded_irq() is + * called from within a thread, i.e. not called from hard interrupt context. + * + * When device is using its own internal hardware trigger in continuous sampling + * mode, data are available into hardware FIFO once interrupt has occurred. All + * we have to do is to dispatch the trigger, which in turn will fetch data and + * fill IIO buffer. + * + * When not using its own internal hardware trigger, the device has been + * configured in one-shot mode either by an external trigger or the IIO read_raw + * hook. This means one of the latter is currently waiting for sampling + * completion, in which case we must simply wake it up. + * + * See zpa2326_trigger_handler(). + * + * Return: + * %IRQ_NONE - no consistent interrupt happened ; + * %IRQ_HANDLED - there was new samples available. + */ +static irqreturn_t zpa2326_handle_threaded_irq(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct zpa2326_private *priv = iio_priv(indio_dev); + unsigned int val; + bool cont; + irqreturn_t ret = IRQ_NONE; + + /* + * Are we using our own internal trigger in triggered buffer mode, i.e., + * currently working in continuous sampling mode ? + */ + cont = (iio_buffer_enabled(indio_dev) && + iio_trigger_using_own(indio_dev)); + + /* + * Device works according to a level interrupt scheme: reading interrupt + * status de-asserts interrupt line. + */ + priv->result = regmap_read(priv->regmap, ZPA2326_INT_SOURCE_REG, &val); + if (priv->result < 0) { + if (cont) + return IRQ_NONE; + + goto complete; + } + + /* Data ready is the only interrupt source we requested. */ + if (!(val & ZPA2326_INT_SOURCE_DATA_READY)) { + /* + * Interrupt happened but no new sample available: likely caused + * by spurious interrupts, in which case, returning IRQ_NONE + * allows to benefit from the generic spurious interrupts + * handling. + */ + zpa2326_warn(indio_dev, "unexpected interrupt status %02x", + val); + + if (cont) + return IRQ_NONE; + + priv->result = -ENODATA; + goto complete; + } + + /* New sample available: dispatch internal trigger consumers. */ + iio_trigger_poll_nested(priv->trigger); + + if (cont) + /* + * Internal hardware trigger has been scheduled above : it will + * fetch data on its own. + */ + return IRQ_HANDLED; + + ret = IRQ_HANDLED; + +complete: + /* + * Wake up direct or externally triggered buffer mode waiters: see + * zpa2326_sample_oneshot() and zpa2326_trigger_handler(). + */ + complete(&priv->data_ready); + + return ret; +} + +/** + * zpa2326_wait_oneshot_completion() - Wait for oneshot data ready interrupt. + * @indio_dev: The IIO device associated with the sampling hardware. + * @private: Internal private state related to @indio_dev. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_wait_oneshot_completion(const struct iio_dev *indio_dev, + struct zpa2326_private *private) +{ + unsigned int val; + long time_left; + + zpa2326_dbg(indio_dev, "waiting for one shot completion interrupt"); + + time_left = wait_for_completion_interruptible_timeout( + &private->data_ready, ZPA2326_CONVERSION_JIFFIES); + if (time_left > 0) + /* + * Interrupt handler completed before timeout: return operation + * status. + */ + return private->result; + + /* Clear all interrupts just to be sure. */ + regmap_read(private->regmap, ZPA2326_INT_SOURCE_REG, &val); + + if (!time_left) { + /* Timed out. */ + zpa2326_warn(indio_dev, "no one shot interrupt occurred (%ld)", + time_left); + return -ETIME; + } + + zpa2326_warn(indio_dev, "wait for one shot interrupt cancelled"); + return -ERESTARTSYS; +} + +static int zpa2326_init_managed_irq(struct device *parent, + struct iio_dev *indio_dev, + struct zpa2326_private *private, + int irq) +{ + int err; + + private->irq = irq; + + if (irq <= 0) { + /* + * Platform declared no interrupt line: device will be polled + * for data availability. + */ + dev_info(parent, "no interrupt found, running in polling mode"); + return 0; + } + + init_completion(&private->data_ready); + + /* Request handler to be scheduled into threaded interrupt context. */ + err = devm_request_threaded_irq(parent, irq, zpa2326_handle_irq, + zpa2326_handle_threaded_irq, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + dev_name(parent), indio_dev); + if (err) + return err; + + dev_info(parent, "using interrupt %d", irq); + + return 0; +} + +/** + * zpa2326_poll_oneshot_completion() - Actively poll for one shot data ready. + * @indio_dev: The IIO device associated with the sampling hardware. + * + * Loop over registers content to detect end of sampling cycle. Used when DT + * declared no valid interrupt lines. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_poll_oneshot_completion(const struct iio_dev *indio_dev) +{ + unsigned long tmout = jiffies + ZPA2326_CONVERSION_JIFFIES; + struct regmap *regs = ((struct zpa2326_private *) + iio_priv(indio_dev))->regmap; + unsigned int val; + int err; + + zpa2326_dbg(indio_dev, "polling for one shot completion"); + + /* + * At least, 100 ms is needed for the device to complete its one-shot + * cycle. + */ + if (msleep_interruptible(100)) + return -ERESTARTSYS; + + /* Poll for conversion completion in hardware. */ + while (true) { + err = regmap_read(regs, ZPA2326_CTRL_REG0_REG, &val); + if (err < 0) + goto err; + + if (!(val & ZPA2326_CTRL_REG0_ONE_SHOT)) + /* One-shot bit self clears at conversion end. */ + break; + + if (time_after(jiffies, tmout)) { + /* Prevent from waiting forever : let's time out. */ + err = -ETIME; + goto err; + } + + usleep_range(10000, 20000); + } + + /* + * In oneshot mode, pressure sample availability guarantees that + * temperature conversion has also completed : just check pressure + * status bit to keep things simple. + */ + err = regmap_read(regs, ZPA2326_STATUS_REG, &val); + if (err < 0) + goto err; + + if (!(val & ZPA2326_STATUS_P_DA)) { + /* No sample available. */ + err = -ENODATA; + goto err; + } + + return 0; + +err: + zpa2326_warn(indio_dev, "failed to poll one shot completion (%d)", err); + + return err; +} + +/** + * zpa2326_fetch_raw_sample() - Retrieve a raw sample and convert it to CPU + * endianness. + * @indio_dev: The IIO device associated with the sampling hardware. + * @type: Type of measurement / channel to fetch from. + * @value: Sample output. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_fetch_raw_sample(const struct iio_dev *indio_dev, + enum iio_chan_type type, + int *value) +{ + struct regmap *regs = ((struct zpa2326_private *) + iio_priv(indio_dev))->regmap; + int err; + u8 v[3]; + + switch (type) { + case IIO_PRESSURE: + zpa2326_dbg(indio_dev, "fetching raw pressure sample"); + + err = regmap_bulk_read(regs, ZPA2326_PRESS_OUT_XL_REG, v, sizeof(v)); + if (err) { + zpa2326_warn(indio_dev, "failed to fetch pressure (%d)", + err); + return err; + } + + *value = get_unaligned_le24(&v[0]); + + return IIO_VAL_INT; + + case IIO_TEMP: + zpa2326_dbg(indio_dev, "fetching raw temperature sample"); + + err = regmap_bulk_read(regs, ZPA2326_TEMP_OUT_L_REG, value, 2); + if (err) { + zpa2326_warn(indio_dev, + "failed to fetch temperature (%d)", err); + return err; + } + + /* Temperature is a 16 bits wide little-endian signed int. */ + *value = (int)le16_to_cpup((__le16 *)value); + + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +/** + * zpa2326_sample_oneshot() - Perform a complete one shot sampling cycle. + * @indio_dev: The IIO device associated with the sampling hardware. + * @type: Type of measurement / channel to fetch from. + * @value: Sample output. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_sample_oneshot(struct iio_dev *indio_dev, + enum iio_chan_type type, + int *value) +{ + int ret; + struct zpa2326_private *priv; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = zpa2326_resume(indio_dev); + if (ret < 0) + goto release; + + priv = iio_priv(indio_dev); + + if (ret > 0) { + /* + * We were already power supplied. Just clear hardware FIFO to + * get rid of samples acquired during previous rounds (if any). + * Sampling operation always generates both temperature and + * pressure samples. The latter are always enqueued into + * hardware FIFO. This may lead to situations were pressure + * samples still sit into FIFO when previous cycle(s) fetched + * temperature data only. + * Hence, we need to clear hardware FIFO content to prevent from + * getting outdated values at the end of current cycle. + */ + if (type == IIO_PRESSURE) { + ret = zpa2326_clear_fifo(indio_dev, 0); + if (ret) + goto suspend; + } + } else { + /* + * We have just been power supplied, i.e. device is in default + * "out of reset" state, meaning we need to reconfigure it + * entirely. + */ + ret = zpa2326_config_oneshot(indio_dev, priv->irq); + if (ret) + goto suspend; + } + + /* Start a sampling cycle in oneshot mode. */ + ret = zpa2326_start_oneshot(indio_dev); + if (ret) + goto suspend; + + /* Wait for sampling cycle to complete. */ + if (priv->irq > 0) + ret = zpa2326_wait_oneshot_completion(indio_dev, priv); + else + ret = zpa2326_poll_oneshot_completion(indio_dev); + + if (ret) + goto suspend; + + /* Retrieve raw sample value and convert it to CPU endianness. */ + ret = zpa2326_fetch_raw_sample(indio_dev, type, value); + +suspend: + zpa2326_suspend(indio_dev); +release: + iio_device_release_direct(indio_dev); + + return ret; +} + +/** + * zpa2326_trigger_handler() - Perform an IIO buffered sampling round in one + * shot mode. + * @irq: The software interrupt assigned to @data + * @data: The IIO poll function dispatched by external trigger our device is + * attached to. + * + * Bottom-half handler called by the IIO trigger to which our device is + * currently attached. Allows us to synchronize this device buffered sampling + * either with external events (such as timer expiration, external device sample + * ready, etc...) or with its own interrupt (internal hardware trigger). + * + * When using an external trigger, basically run the same sequence of operations + * as for zpa2326_sample_oneshot() with the following hereafter. Hardware FIFO + * is not cleared since already done at buffering enable time and samples + * dequeueing always retrieves the most recent value. + * + * Otherwise, when internal hardware trigger has dispatched us, just fetch data + * from hardware FIFO. + * + * Fetched data will pushed unprocessed to IIO buffer since samples conversion + * is delegated to userspace in buffered mode (endianness, etc...). + * + * Return: + * %IRQ_NONE - no consistent interrupt happened ; + * %IRQ_HANDLED - there was new samples available. + */ +static irqreturn_t zpa2326_trigger_handler(int irq, void *data) +{ + struct iio_dev *indio_dev = ((struct iio_poll_func *) + data)->indio_dev; + struct zpa2326_private *priv = iio_priv(indio_dev); + bool cont; + + /* + * We have been dispatched, meaning we are in triggered buffer mode. + * Using our own internal trigger implies we are currently in continuous + * hardware sampling mode. + */ + cont = iio_trigger_using_own(indio_dev); + + if (!cont) { + /* On demand sampling : start a one shot cycle. */ + if (zpa2326_start_oneshot(indio_dev)) + goto out; + + /* Wait for sampling cycle to complete. */ + if (priv->irq <= 0) { + /* No interrupt available: poll for completion. */ + if (zpa2326_poll_oneshot_completion(indio_dev)) + goto out; + + /* Only timestamp sample once it is ready. */ + priv->timestamp = iio_get_time_ns(indio_dev); + } else { + /* Interrupt handlers will timestamp for us. */ + if (zpa2326_wait_oneshot_completion(indio_dev, priv)) + goto out; + } + } + + /* Enqueue to IIO buffer / userspace. */ + zpa2326_fill_sample_buffer(indio_dev, priv); + +out: + if (!cont) + /* Don't switch to low power if sampling continuously. */ + zpa2326_sleep(indio_dev); + + /* Inform attached trigger we are done. */ + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +/** + * zpa2326_preenable_buffer() - Prepare device for configuring triggered + * sampling + * modes. + * @indio_dev: The IIO device associated with the sampling hardware. + * + * Basically power up device. + * Called with IIO device's lock held. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_preenable_buffer(struct iio_dev *indio_dev) +{ + int ret = zpa2326_resume(indio_dev); + + if (ret < 0) + return ret; + + /* Tell zpa2326_postenable_buffer() if we have just been powered on. */ + ((struct zpa2326_private *) + iio_priv(indio_dev))->waken = iio_priv(indio_dev); + + return 0; +} + +/** + * zpa2326_postenable_buffer() - Configure device for triggered sampling. + * @indio_dev: The IIO device associated with the sampling hardware. + * + * Basically setup one-shot mode if plugging external trigger. + * Otherwise, let internal trigger configure continuous sampling : + * see zpa2326_set_trigger_state(). + * + * If an error is returned, IIO layer will call our postdisable hook for us, + * i.e. no need to explicitly power device off here. + * Called with IIO device's lock held. + * + * Called with IIO device's lock held. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_postenable_buffer(struct iio_dev *indio_dev) +{ + const struct zpa2326_private *priv = iio_priv(indio_dev); + int err; + + if (!priv->waken) { + /* + * We were already power supplied. Just clear hardware FIFO to + * get rid of samples acquired during previous rounds (if any). + */ + err = zpa2326_clear_fifo(indio_dev, 0); + if (err) { + zpa2326_err(indio_dev, + "failed to enable buffering (%d)", err); + return err; + } + } + + if (!iio_trigger_using_own(indio_dev) && priv->waken) { + /* + * We are using an external trigger and we have just been + * powered up: reconfigure one-shot mode. + */ + err = zpa2326_config_oneshot(indio_dev, priv->irq); + if (err) { + zpa2326_err(indio_dev, + "failed to enable buffering (%d)", err); + return err; + } + } + + return 0; +} + +static int zpa2326_postdisable_buffer(struct iio_dev *indio_dev) +{ + zpa2326_suspend(indio_dev); + + return 0; +} + +static const struct iio_buffer_setup_ops zpa2326_buffer_setup_ops = { + .preenable = zpa2326_preenable_buffer, + .postenable = zpa2326_postenable_buffer, + .postdisable = zpa2326_postdisable_buffer +}; + +/** + * zpa2326_set_trigger_state() - Start / stop continuous sampling. + * @trig: The trigger being attached to IIO device associated with the sampling + * hardware. + * @state: Tell whether to start (true) or stop (false) + * + * Basically enable / disable hardware continuous sampling mode. + * + * Called with IIO device's lock held at postenable() or predisable() time. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_set_trigger_state(struct iio_trigger *trig, bool state) +{ + const struct iio_dev *indio_dev = dev_get_drvdata( + trig->dev.parent); + const struct zpa2326_private *priv = iio_priv(indio_dev); + int err; + + if (!state) { + /* + * Switch trigger off : in case of failure, interrupt is left + * disabled in order to prevent handler from accessing released + * resources. + */ + unsigned int val; + + /* + * As device is working in continuous mode, handlers may be + * accessing resources we are currently freeing... + * Prevent this by disabling interrupt handlers and ensure + * the device will generate no more interrupts unless explicitly + * required to, i.e. by restoring back to default one shot mode. + */ + disable_irq(priv->irq); + + /* + * Disable continuous sampling mode to restore settings for + * one shot / direct sampling operations. + */ + err = regmap_write(priv->regmap, ZPA2326_CTRL_REG3_REG, + zpa2326_highest_frequency()->odr); + if (err) + return err; + + /* + * Now that device won't generate interrupts on its own, + * acknowledge any currently active interrupts (may happen on + * rare occasions while stopping continuous mode). + */ + err = regmap_read(priv->regmap, ZPA2326_INT_SOURCE_REG, &val); + if (err < 0) + return err; + + /* + * Re-enable interrupts only if we can guarantee the device will + * generate no more interrupts to prevent handlers from + * accessing released resources. + */ + enable_irq(priv->irq); + + zpa2326_dbg(indio_dev, "continuous mode stopped"); + } else { + /* + * Switch trigger on : start continuous sampling at required + * frequency. + */ + + if (priv->waken) { + /* Enable interrupt if getting out of reset. */ + err = regmap_write(priv->regmap, ZPA2326_CTRL_REG1_REG, + (u8) + ~ZPA2326_CTRL_REG1_MASK_DATA_READY); + if (err) + return err; + } + + /* Enable continuous sampling at specified frequency. */ + err = regmap_write(priv->regmap, ZPA2326_CTRL_REG3_REG, + ZPA2326_CTRL_REG3_ENABLE_MEAS | + priv->frequency->odr); + if (err) + return err; + + zpa2326_dbg(indio_dev, "continuous mode setup @%dHz", + priv->frequency->hz); + } + + return 0; +} + +static const struct iio_trigger_ops zpa2326_trigger_ops = { + .set_trigger_state = zpa2326_set_trigger_state, +}; + +/** + * zpa2326_init_managed_trigger() - Create interrupt driven / hardware trigger + * allowing to notify external devices a new sample is + * ready. + * @parent: Hardware sampling device @indio_dev is a child of. + * @indio_dev: The IIO device associated with the sampling hardware. + * @private: Internal private state related to @indio_dev. + * @irq: Optional interrupt line the hardware uses to notify new data + * samples are ready. Negative or zero values indicate no interrupts + * are available, meaning polling is required. + * + * Only relevant when DT declares a valid interrupt line. + * + * Return: Zero when successful, a negative error code otherwise. + */ +static int zpa2326_init_managed_trigger(struct device *parent, + struct iio_dev *indio_dev, + struct zpa2326_private *private, + int irq) +{ + struct iio_trigger *trigger; + int ret; + + if (irq <= 0) + return 0; + + trigger = devm_iio_trigger_alloc(parent, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!trigger) + return -ENOMEM; + + /* Basic setup. */ + trigger->ops = &zpa2326_trigger_ops; + + private->trigger = trigger; + + /* Register to triggers space. */ + ret = devm_iio_trigger_register(parent, trigger); + if (ret) + dev_err(parent, "failed to register hardware trigger (%d)", + ret); + + return ret; +} + +static int zpa2326_get_frequency(const struct iio_dev *indio_dev) +{ + return ((struct zpa2326_private *)iio_priv(indio_dev))->frequency->hz; +} + +static int zpa2326_set_frequency(struct iio_dev *indio_dev, int hz) +{ + struct zpa2326_private *priv = iio_priv(indio_dev); + int freq; + + /* Check if requested frequency is supported. */ + for (freq = 0; freq < ARRAY_SIZE(zpa2326_sampling_frequencies); freq++) + if (zpa2326_sampling_frequencies[freq].hz == hz) + break; + if (freq == ARRAY_SIZE(zpa2326_sampling_frequencies)) + return -EINVAL; + + /* Don't allow changing frequency if buffered sampling is ongoing. */ + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + priv->frequency = &zpa2326_sampling_frequencies[freq]; + + iio_device_release_direct(indio_dev); + + return 0; +} + +/* Expose supported hardware sampling frequencies (Hz) through sysfs. */ +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("1 5 11 23"); + +static struct attribute *zpa2326_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group zpa2326_attribute_group = { + .attrs = zpa2326_attributes, +}; + +static int zpa2326_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_RAW: + return zpa2326_sample_oneshot(indio_dev, chan->type, val); + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_PRESSURE: + /* + * Pressure resolution is 1/64 Pascal. Scale to kPascal + * as required by IIO ABI. + */ + *val = 1; + *val2 = 64000; + return IIO_VAL_FRACTIONAL; + + case IIO_TEMP: + /* + * Temperature follows the equation: + * Temp[degC] = Tempcode * 0.00649 - 176.83 + * where: + * Tempcode is composed the raw sampled 16 bits. + * + * Hence, to produce a temperature in milli-degrees + * Celsius according to IIO ABI, we need to apply the + * following equation to raw samples: + * Temp[milli degC] = (Tempcode + Offset) * Scale + * where: + * Offset = -176.83 / 0.00649 + * Scale = 0.00649 * 1000 + */ + *val = 6; + *val2 = 490000; + return IIO_VAL_INT_PLUS_MICRO; + + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + *val = -17683000; + *val2 = 649; + return IIO_VAL_FRACTIONAL; + + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = zpa2326_get_frequency(indio_dev); + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int zpa2326_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int val, + int val2, + long mask) +{ + if ((mask != IIO_CHAN_INFO_SAMP_FREQ) || val2) + return -EINVAL; + + return zpa2326_set_frequency(indio_dev, val); +} + +static const struct iio_chan_spec zpa2326_channels[] = { + [0] = { + .type = IIO_PRESSURE, + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_LE, + }, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + }, + [1] = { + .type = IIO_TEMP, + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + }, + [2] = IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static const struct iio_info zpa2326_info = { + .attrs = &zpa2326_attribute_group, + .read_raw = zpa2326_read_raw, + .write_raw = zpa2326_write_raw, +}; + +static struct iio_dev *zpa2326_create_managed_iiodev(struct device *device, + const char *name, + struct regmap *regmap) +{ + struct iio_dev *indio_dev; + + /* Allocate space to hold IIO device internal state. */ + indio_dev = devm_iio_device_alloc(device, + sizeof(struct zpa2326_private)); + if (!indio_dev) + return NULL; + + /* Setup for userspace synchronous on demand sampling. */ + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = zpa2326_channels; + indio_dev->num_channels = ARRAY_SIZE(zpa2326_channels); + indio_dev->name = name; + indio_dev->info = &zpa2326_info; + + return indio_dev; +} + +int zpa2326_probe(struct device *parent, + const char *name, + int irq, + unsigned int hwid, + struct regmap *regmap) +{ + struct iio_dev *indio_dev; + struct zpa2326_private *priv; + int err; + unsigned int id; + + indio_dev = zpa2326_create_managed_iiodev(parent, name, regmap); + if (!indio_dev) + return -ENOMEM; + + priv = iio_priv(indio_dev); + + priv->vref = devm_regulator_get(parent, "vref"); + if (IS_ERR(priv->vref)) + return PTR_ERR(priv->vref); + + priv->vdd = devm_regulator_get(parent, "vdd"); + if (IS_ERR(priv->vdd)) + return PTR_ERR(priv->vdd); + + /* Set default hardware sampling frequency to highest rate supported. */ + priv->frequency = zpa2326_highest_frequency(); + + /* + * Plug device's underlying bus abstraction : this MUST be set before + * registering interrupt handlers since an interrupt might happen if + * power up sequence is not properly applied. + */ + priv->regmap = regmap; + + err = devm_iio_triggered_buffer_setup(parent, indio_dev, NULL, + zpa2326_trigger_handler, + &zpa2326_buffer_setup_ops); + if (err) + return err; + + err = zpa2326_init_managed_trigger(parent, indio_dev, priv, irq); + if (err) + return err; + + err = zpa2326_init_managed_irq(parent, indio_dev, priv, irq); + if (err) + return err; + + /* Power up to check device ID and perform initial hardware setup. */ + err = zpa2326_power_on(indio_dev, priv); + if (err) + return err; + + /* Read id register to check we are talking to the right slave. */ + err = regmap_read(regmap, ZPA2326_DEVICE_ID_REG, &id); + if (err) + goto sleep; + + if (id != hwid) { + dev_err(parent, "found device with unexpected id %02x", id); + err = -ENODEV; + goto sleep; + } + + err = zpa2326_config_oneshot(indio_dev, irq); + if (err) + goto sleep; + + /* Setup done : go sleeping. Device will be awaken upon user request. */ + err = zpa2326_sleep(indio_dev); + if (err) + goto poweroff; + + dev_set_drvdata(parent, indio_dev); + + zpa2326_init_runtime(parent); + + err = iio_device_register(indio_dev); + if (err) { + zpa2326_fini_runtime(parent); + goto poweroff; + } + + return 0; + +sleep: + /* Put to sleep just in case power regulators are "dummy" ones. */ + zpa2326_sleep(indio_dev); +poweroff: + zpa2326_power_off(indio_dev, priv); + + return err; +} +EXPORT_SYMBOL_NS_GPL(zpa2326_probe, "IIO_ZPA2326"); + +void zpa2326_remove(const struct device *parent) +{ + struct iio_dev *indio_dev = dev_get_drvdata(parent); + + iio_device_unregister(indio_dev); + zpa2326_fini_runtime(indio_dev->dev.parent); + zpa2326_sleep(indio_dev); + zpa2326_power_off(indio_dev, iio_priv(indio_dev)); +} +EXPORT_SYMBOL_NS_GPL(zpa2326_remove, "IIO_ZPA2326"); + +MODULE_AUTHOR("Gregor Boirie "); +MODULE_DESCRIPTION("Core driver for Murata ZPA2326 pressure sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/zpa2326.h b/drivers/iio/pressure/zpa2326.h new file mode 100644 index 000000000..45bd79009 --- /dev/null +++ b/drivers/iio/pressure/zpa2326.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Murata ZPA2326 pressure and temperature sensor IIO driver + * + * Copyright (c) 2016 Parrot S.A. + * + * Author: Gregor Boirie + */ + +#ifndef _ZPA2326_H +#define _ZPA2326_H + +/* Register map. */ +#define ZPA2326_REF_P_XL_REG (0x8) +#define ZPA2326_REF_P_L_REG (0x9) +#define ZPA2326_REF_P_H_REG (0xa) +#define ZPA2326_DEVICE_ID_REG (0xf) +#define ZPA2326_DEVICE_ID (0xb9) +#define ZPA2326_RES_CONF_REG (0x10) +#define ZPA2326_CTRL_REG0_REG (0x20) +#define ZPA2326_CTRL_REG0_ONE_SHOT BIT(0) +#define ZPA2326_CTRL_REG0_ENABLE BIT(1) +#define ZPA2326_CTRL_REG1_REG (0x21) +#define ZPA2326_CTRL_REG1_MASK_DATA_READY BIT(2) +#define ZPA2326_CTRL_REG2_REG (0x22) +#define ZPA2326_CTRL_REG2_SWRESET BIT(2) +#define ZPA2326_CTRL_REG3_REG (0x23) +#define ZPA2326_CTRL_REG3_ODR_SHIFT (4) +#define ZPA2326_CTRL_REG3_ENABLE_MEAS BIT(7) +#define ZPA2326_INT_SOURCE_REG (0x24) +#define ZPA2326_INT_SOURCE_DATA_READY BIT(2) +#define ZPA2326_THS_P_LOW_REG (0x25) +#define ZPA2326_THS_P_HIGH_REG (0x26) +#define ZPA2326_STATUS_REG (0x27) +#define ZPA2326_STATUS_P_DA BIT(1) +#define ZPA2326_STATUS_FIFO_E BIT(2) +#define ZPA2326_STATUS_P_OR BIT(5) +#define ZPA2326_PRESS_OUT_XL_REG (0x28) +#define ZPA2326_PRESS_OUT_L_REG (0x29) +#define ZPA2326_PRESS_OUT_H_REG (0x2a) +#define ZPA2326_TEMP_OUT_L_REG (0x2b) +#define ZPA2326_TEMP_OUT_H_REG (0x2c) + +struct device; +struct regmap; + +bool zpa2326_isreg_writeable(struct device *dev, unsigned int reg); +bool zpa2326_isreg_readable(struct device *dev, unsigned int reg); +bool zpa2326_isreg_precious(struct device *dev, unsigned int reg); + +/** + * zpa2326_probe() - Instantiate and register core ZPA2326 IIO device + * @parent: Hardware sampling device the created IIO device will be a child of. + * @name: Arbitrary name to identify the device. + * @irq: Interrupt line, negative if none. + * @hwid: Expected device hardware id. + * @regmap: Registers map used to abstract underlying bus accesses. + * + * Return: Zero when successful, a negative error code otherwise. + */ +int zpa2326_probe(struct device *parent, + const char *name, + int irq, + unsigned int hwid, + struct regmap *regmap); + +/** + * zpa2326_remove() - Unregister and destroy core ZPA2326 IIO device. + * @parent: Hardware sampling device the IIO device to remove is a child of. + */ +void zpa2326_remove(const struct device *parent); + +#ifdef CONFIG_PM +#include +extern const struct dev_pm_ops zpa2326_pm_ops; +#define ZPA2326_PM_OPS (&zpa2326_pm_ops) +#else +#define ZPA2326_PM_OPS (NULL) +#endif + +#endif diff --git a/drivers/iio/pressure/zpa2326_i2c.c b/drivers/iio/pressure/zpa2326_i2c.c new file mode 100644 index 000000000..e04a61c23 --- /dev/null +++ b/drivers/iio/pressure/zpa2326_i2c.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Murata ZPA2326 I2C pressure and temperature sensor driver + * + * Copyright (c) 2016 Parrot S.A. + * + * Author: Gregor Boirie + */ + +#include +#include +#include +#include "zpa2326.h" + +/* + * read_flag_mask: + * - address bit 7 must be set to request a register read operation + */ +static const struct regmap_config zpa2326_regmap_i2c_config = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = zpa2326_isreg_writeable, + .readable_reg = zpa2326_isreg_readable, + .precious_reg = zpa2326_isreg_precious, + .max_register = ZPA2326_TEMP_OUT_H_REG, + .read_flag_mask = BIT(7), +}; + +static unsigned int zpa2326_i2c_hwid(const struct i2c_client *client) +{ +#define ZPA2326_SA0(_addr) (_addr & BIT(0)) +#define ZPA2326_DEVICE_ID_SA0_SHIFT (1) + + /* Identification register bit 1 mirrors device address bit 0. */ + return (ZPA2326_DEVICE_ID | + (ZPA2326_SA0(client->addr) << ZPA2326_DEVICE_ID_SA0_SHIFT)); +} + +static int zpa2326_probe_i2c(struct i2c_client *client) +{ + const struct i2c_device_id *i2c_id = i2c_client_get_device_id(client); + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, &zpa2326_regmap_i2c_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "failed to init registers map"); + return PTR_ERR(regmap); + } + + return zpa2326_probe(&client->dev, i2c_id->name, client->irq, + zpa2326_i2c_hwid(client), regmap); +} + +static void zpa2326_remove_i2c(struct i2c_client *client) +{ + zpa2326_remove(&client->dev); +} + +static const struct i2c_device_id zpa2326_i2c_ids[] = { + { .name = "zpa2326" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, zpa2326_i2c_ids); + +static const struct of_device_id zpa2326_i2c_matches[] = { + { .compatible = "murata,zpa2326" }, + { } +}; +MODULE_DEVICE_TABLE(of, zpa2326_i2c_matches); + +static struct i2c_driver zpa2326_i2c_driver = { + .driver = { + .name = "zpa2326-i2c", + .of_match_table = zpa2326_i2c_matches, + .pm = ZPA2326_PM_OPS, + }, + .probe = zpa2326_probe_i2c, + .remove = zpa2326_remove_i2c, + .id_table = zpa2326_i2c_ids, +}; +module_i2c_driver(zpa2326_i2c_driver); + +MODULE_AUTHOR("Gregor Boirie "); +MODULE_DESCRIPTION("I2C driver for Murata ZPA2326 pressure sensor"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ZPA2326"); diff --git a/drivers/iio/pressure/zpa2326_spi.c b/drivers/iio/pressure/zpa2326_spi.c new file mode 100644 index 000000000..dfbb74a58 --- /dev/null +++ b/drivers/iio/pressure/zpa2326_spi.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Murata ZPA2326 SPI pressure and temperature sensor driver + * + * Copyright (c) 2016 Parrot S.A. + * + * Author: Gregor Boirie + */ + +#include +#include +#include +#include "zpa2326.h" + +/* + * read_flag_mask: + * - address bit 7 must be set to request a register read operation + * - address bit 6 must be set to request register address auto increment + */ +static const struct regmap_config zpa2326_regmap_spi_config = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = zpa2326_isreg_writeable, + .readable_reg = zpa2326_isreg_readable, + .precious_reg = zpa2326_isreg_precious, + .max_register = ZPA2326_TEMP_OUT_H_REG, + .read_flag_mask = BIT(7) | BIT(6), +}; + +static int zpa2326_probe_spi(struct spi_device *spi) +{ + struct regmap *regmap; + int err; + + regmap = devm_regmap_init_spi(spi, &zpa2326_regmap_spi_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "failed to init registers map"); + return PTR_ERR(regmap); + } + + /* + * Enforce SPI slave settings to prevent from DT misconfiguration. + * + * Clock is idle high. Sampling happens on trailing edge, i.e., rising + * edge. Maximum bus frequency is 1 MHz. Registers are 8 bits wide. + */ + spi->mode = SPI_MODE_3; + spi->max_speed_hz = min(spi->max_speed_hz, 1000000U); + err = spi_setup(spi); + if (err < 0) + return err; + + return zpa2326_probe(&spi->dev, spi_get_device_id(spi)->name, + spi->irq, ZPA2326_DEVICE_ID, regmap); +} + +static void zpa2326_remove_spi(struct spi_device *spi) +{ + zpa2326_remove(&spi->dev); +} + +static const struct spi_device_id zpa2326_spi_ids[] = { + { .name = "zpa2326" }, + { } +}; +MODULE_DEVICE_TABLE(spi, zpa2326_spi_ids); + +static const struct of_device_id zpa2326_spi_matches[] = { + { .compatible = "murata,zpa2326" }, + { } +}; +MODULE_DEVICE_TABLE(of, zpa2326_spi_matches); + +static struct spi_driver zpa2326_spi_driver = { + .driver = { + .name = "zpa2326-spi", + .of_match_table = zpa2326_spi_matches, + .pm = ZPA2326_PM_OPS, + }, + .probe = zpa2326_probe_spi, + .remove = zpa2326_remove_spi, + .id_table = zpa2326_spi_ids, +}; +module_spi_driver(zpa2326_spi_driver); + +MODULE_AUTHOR("Gregor Boirie "); +MODULE_DESCRIPTION("SPI driver for Murata ZPA2326 pressure sensor"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_ZPA2326"); diff --git a/drivers/iio/proximity/Kconfig b/drivers/iio/proximity/Kconfig new file mode 100644 index 000000000..bb77fad2a --- /dev/null +++ b/drivers/iio/proximity/Kconfig @@ -0,0 +1,273 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Proximity sensors +# + +menu "Lightning sensors" + +config AS3935 + tristate "AS3935 Franklin lightning sensor" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + depends on SPI + help + Say Y here to build SPI interface support for the Austrian + Microsystems AS3935 lightning detection sensor. + + To compile this driver as a module, choose M here: the + module will be called as3935 + +endmenu + +menu "Proximity and distance sensors" + +config CROS_EC_MKBP_PROXIMITY + tristate "ChromeOS EC MKBP Proximity sensor" + depends on CROS_EC + help + Say Y here to enable the proximity sensor implemented via the ChromeOS EC MKBP + switches protocol. You must enable one bus option (CROS_EC_I2C or CROS_EC_SPI) + to use this. + + To compile this driver as a module, choose M here: the + module will be called cros_ec_mkbp_proximity. + +config D3323AA + tristate "Nicera (Nippon Ceramic Co.) D3-323-AA PIR sensor" + depends on GPIOLIB + help + Say Y here to build a driver for the Nicera D3-323-AA PIR sensor. + + To compile this driver as a module, choose M here: the module will be + called d3323aa. + +config HX9023S + tristate "TYHX HX9023S SAR sensor" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select REGMAP_I2C + depends on I2C + help + Say Y here to build a driver for TYHX HX9023S capacitive SAR sensor. + This driver supports the TYHX HX9023S capacitive + SAR sensors. This sensors is used for proximity detection applications. + + To compile this driver as a module, choose M here: the + module will be called hx9023s. + +config IRSD200 + tristate "Murata IRS-D200 PIR sensor" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select REGMAP_I2C + depends on I2C + help + Say Y here to build a driver for the Murata IRS-D200 PIR sensor. + + To compile this driver as a module, choose M here: the module will be + called irsd200. + +config ISL29501 + tristate "Intersil ISL29501 Time Of Flight sensor" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select IIO_KFIFO_BUF + help + Say Y here if you want to build a driver for the Intersil ISL29501 + Time of Flight sensor. + + To compile this driver as a module, choose M here: the module will be + called isl29501. + +config LIDAR_LITE_V2 + tristate "PulsedLight LIDAR sensor" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + depends on I2C + help + Say Y to build a driver for PulsedLight LIDAR range finding + sensor. + + To compile this driver as a module, choose M here: the + module will be called pulsedlight-lite-v2 + +config MB1232 + tristate "MaxSonar I2CXL family ultrasonic sensors" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y to build a driver for the ultrasonic sensors I2CXL of + MaxBotix which have an i2c interface. It can be used to measure + the distance of objects. Supported types are mb1202, mb1212, + mb1222, mb1232, mb1242, mb7040, mb7137 + + To compile this driver as a module, choose M here: the + module will be called mb1232. + +config PING + tristate "Parallax GPIO bitbanged ranger sensors" + depends on GPIOLIB + help + Say Y here to build a driver for GPIO bitbanged ranger sensors + with just one GPIO for the trigger and echo. This driver can be + used to measure the distance of objects. + + Actually supported are: + - Parallax PING))) (ultrasonic) + - Parallax LaserPING (time-of-flight) + + To compile this driver as a module, choose M here: the + module will be called ping. + +config RFD77402 + tristate "RFD77402 ToF sensor" + depends on I2C + help + Say Y to build a driver for the RFD77402 Time-of-Flight (distance) + sensor module with I2C interface. + + To compile this driver as a module, choose M here: the + module will be called rfd77402. + +config SRF04 + tristate "GPIO bitbanged ultrasonic ranger sensor (SRF04, MB1000)" + depends on GPIOLIB + help + Say Y here to build a driver for GPIO bitbanged ultrasonic + ranger sensor. This driver can be used to measure the distance + of objects. It is using two GPIOs. + Actually Supported types are: + - Devantech SRF04 + - Maxbotix mb1000 + - Maxbotix mb1010 + - Maxbotix mb1020 + - Maxbotix mb1030 + - Maxbotix mb1040 + + To compile this driver as a module, choose M here: the + module will be called srf04. + +config SX_COMMON + tristate + help + Common Semtech proximity sensor code. + +config SX9310 + tristate "SX9310/SX9311 Semtech proximity sensor" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select REGMAP_I2C + select SX_COMMON + depends on I2C + help + Say Y here to build a driver for Semtech's SX9310/SX9311 capacitive + proximity/button sensor. + + To compile this driver as a module, choose M here: the + module will be called sx9310. + +config SX9324 + tristate "SX9324 Semtech proximity sensor" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select REGMAP_I2C + select SX_COMMON + depends on I2C + help + Say Y here to build a driver for Semtech's SX9324 + proximity/button sensor. + + To compile this driver as a module, choose M here: the + module will be called sx9324. + +config SX9360 + tristate "SX9360 Semtech proximity sensor" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select REGMAP_I2C + select SX_COMMON + depends on I2C + help + Say Y here to build a driver for Semtech's SX9360 + proximity/button sensor, a simplified SX9324. + + To compile this driver as a module, choose M here: the + module will be called sx9360. + +config SX9500 + tristate "SX9500 Semtech proximity sensor" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select REGMAP_I2C + depends on I2C + help + Say Y here to build a driver for Semtech's SX9500 capacitive + proximity/button sensor. + + To compile this driver as a module, choose M here: the + module will be called sx9500. + +config SRF08 + tristate "Devantech SRF02/SRF08/SRF10 ultrasonic ranger sensor" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + depends on I2C + help + Say Y here to build a driver for Devantech SRF02/SRF08/SRF10 + ultrasonic ranger sensors with i2c interface. + This driver can be used to measure the distance of objects. + + To compile this driver as a module, choose M here: the + module will be called srf08. + +config VCNL3020 + tristate "VCNL3020 proximity sensor" + select REGMAP_I2C + depends on I2C + help + Say Y here if you want to build a driver for the Vishay VCNL3020 + proximity sensor. + + To compile this driver as a module, choose M here: the + module will be called vcnl3020. + +config VL53L0X_I2C + tristate "STMicroelectronics VL53L0X ToF ranger sensor (I2C)" + depends on I2C + help + Say Y here to build a driver for STMicroelectronics VL53L0X + ToF ranger sensors with i2c interface. + This driver can be used to measure the distance of objects. + + To compile this driver as a module, choose M here: the + module will be called vl53l0x-i2c. + +config VL53L1X_I2C + tristate "STMicroelectronics VL53L1X ToF ranger sensor (I2C)" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select REGMAP_I2C + select RESET_CONTROLLER + help + Say Y here to build a driver for STMicroelectronics VL53L1X + ToF ranger sensors with i2c interface. + This driver can be used to measure the distance of objects. + + To compile this driver as a module, choose M here: the + module will be called vl53l1x-i2c. + +config AW96103 + tristate "AW96103/AW96105 Awinic proximity sensor" + select REGMAP_I2C + depends on I2C + help + Say Y here to build a driver for Awinic's AW96103/AW96105 capacitive + proximity sensor. + + To compile this driver as a module, choose M here: the + module will be called aw96103. + +endmenu diff --git a/drivers/iio/proximity/Makefile b/drivers/iio/proximity/Makefile new file mode 100644 index 000000000..4352833dd --- /dev/null +++ b/drivers/iio/proximity/Makefile @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for IIO proximity sensors +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_AS3935) += as3935.o +obj-$(CONFIG_CROS_EC_MKBP_PROXIMITY) += cros_ec_mkbp_proximity.o +obj-$(CONFIG_D3323AA) += d3323aa.o +obj-$(CONFIG_HX9023S) += hx9023s.o +obj-$(CONFIG_IRSD200) += irsd200.o +obj-$(CONFIG_ISL29501) += isl29501.o +obj-$(CONFIG_LIDAR_LITE_V2) += pulsedlight-lidar-lite-v2.o +obj-$(CONFIG_MB1232) += mb1232.o +obj-$(CONFIG_PING) += ping.o +obj-$(CONFIG_RFD77402) += rfd77402.o +obj-$(CONFIG_SRF04) += srf04.o +obj-$(CONFIG_SRF08) += srf08.o +obj-$(CONFIG_SX9310) += sx9310.o +obj-$(CONFIG_SX9324) += sx9324.o +obj-$(CONFIG_SX9360) += sx9360.o +obj-$(CONFIG_SX_COMMON) += sx_common.o +obj-$(CONFIG_SX9500) += sx9500.o +obj-$(CONFIG_VCNL3020) += vcnl3020.o +obj-$(CONFIG_VL53L0X_I2C) += vl53l0x-i2c.o +obj-$(CONFIG_VL53L1X_I2C) += vl53l1x-i2c.o +obj-$(CONFIG_AW96103) += aw96103.o + diff --git a/drivers/iio/proximity/as3935.c b/drivers/iio/proximity/as3935.c new file mode 100644 index 000000000..d7f43c3af --- /dev/null +++ b/drivers/iio/proximity/as3935.c @@ -0,0 +1,467 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * as3935.c - Support for AS3935 Franklin lightning sensor + * + * Copyright (C) 2014, 2017-2018 + * Author: Matt Ranostay + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AS3935_AFE_GAIN 0x00 +#define AS3935_AFE_MASK 0x3F +#define AS3935_AFE_GAIN_MAX 0x1F +#define AS3935_AFE_PWR_BIT BIT(0) + +#define AS3935_NFLWDTH 0x01 +#define AS3935_NFLWDTH_MASK 0x7f + +#define AS3935_INT 0x03 +#define AS3935_INT_MASK 0x0f +#define AS3935_DISTURB_INT BIT(2) +#define AS3935_EVENT_INT BIT(3) +#define AS3935_NOISE_INT BIT(0) + +#define AS3935_DATA 0x07 +#define AS3935_DATA_MASK 0x3F + +#define AS3935_TUNE_CAP 0x08 +#define AS3935_DEFAULTS 0x3C +#define AS3935_CALIBRATE 0x3D + +#define AS3935_READ_DATA BIT(14) +#define AS3935_ADDRESS(x) ((x) << 8) + +#define MAX_PF_CAP 120 +#define TUNE_CAP_DIV 8 + +struct as3935_state { + struct spi_device *spi; + struct iio_trigger *trig; + struct mutex lock; + struct delayed_work work; + + unsigned long noise_tripped; + u32 tune_cap; + u32 nflwdth_reg; + /* Ensure timestamp is naturally aligned */ + struct { + u8 chan; + aligned_s64 timestamp; + } scan; + u8 buf[2] __aligned(IIO_DMA_MINALIGN); +}; + +static const struct iio_chan_spec as3935_channels[] = { + { + .type = IIO_PROXIMITY, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 6, + .storagebits = 8, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static int as3935_read(struct as3935_state *st, unsigned int reg, int *val) +{ + u8 cmd; + int ret; + + cmd = (AS3935_READ_DATA | AS3935_ADDRESS(reg)) >> 8; + ret = spi_w8r8(st->spi, cmd); + if (ret < 0) + return ret; + *val = ret; + + return 0; +} + +static int as3935_write(struct as3935_state *st, + unsigned int reg, + unsigned int val) +{ + u8 *buf = st->buf; + + buf[0] = AS3935_ADDRESS(reg) >> 8; + buf[1] = val; + + return spi_write(st->spi, buf, 2); +} + +static ssize_t as3935_sensor_sensitivity_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct as3935_state *st = iio_priv(dev_to_iio_dev(dev)); + int val, ret; + + ret = as3935_read(st, AS3935_AFE_GAIN, &val); + if (ret) + return ret; + val = (val & AS3935_AFE_MASK) >> 1; + + return sysfs_emit(buf, "%d\n", val); +} + +static ssize_t as3935_sensor_sensitivity_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct as3935_state *st = iio_priv(dev_to_iio_dev(dev)); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return -EINVAL; + + if (val > AS3935_AFE_GAIN_MAX) + return -EINVAL; + + as3935_write(st, AS3935_AFE_GAIN, val << 1); + + return len; +} + +static ssize_t as3935_noise_level_tripped_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct as3935_state *st = iio_priv(dev_to_iio_dev(dev)); + int ret; + + mutex_lock(&st->lock); + ret = sysfs_emit(buf, "%d\n", !time_after(jiffies, st->noise_tripped + HZ)); + mutex_unlock(&st->lock); + + return ret; +} + +static IIO_DEVICE_ATTR(sensor_sensitivity, S_IRUGO | S_IWUSR, + as3935_sensor_sensitivity_show, as3935_sensor_sensitivity_store, 0); + +static IIO_DEVICE_ATTR(noise_level_tripped, S_IRUGO, + as3935_noise_level_tripped_show, NULL, 0); + +static struct attribute *as3935_attributes[] = { + &iio_dev_attr_sensor_sensitivity.dev_attr.attr, + &iio_dev_attr_noise_level_tripped.dev_attr.attr, + NULL, +}; + +static const struct attribute_group as3935_attribute_group = { + .attrs = as3935_attributes, +}; + +static int as3935_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct as3935_state *st = iio_priv(indio_dev); + int ret; + + + switch (m) { + case IIO_CHAN_INFO_PROCESSED: + case IIO_CHAN_INFO_RAW: + *val2 = 0; + ret = as3935_read(st, AS3935_DATA, val); + if (ret) + return ret; + + /* storm out of range */ + if (*val == AS3935_DATA_MASK) + return -EINVAL; + + if (m == IIO_CHAN_INFO_RAW) + return IIO_VAL_INT; + + if (m == IIO_CHAN_INFO_PROCESSED) + *val *= 1000; + break; + case IIO_CHAN_INFO_SCALE: + *val = 1000; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; +} + +static const struct iio_info as3935_info = { + .attrs = &as3935_attribute_group, + .read_raw = &as3935_read_raw, +}; + +static irqreturn_t as3935_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct as3935_state *st = iio_priv(indio_dev); + int val, ret; + + ret = as3935_read(st, AS3935_DATA, &val); + if (ret) + goto err_read; + + st->scan.chan = val & AS3935_DATA_MASK; + iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan), + iio_get_time_ns(indio_dev)); +err_read: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static void as3935_event_work(struct work_struct *work) +{ + struct as3935_state *st; + int val; + int ret; + + st = container_of(work, struct as3935_state, work.work); + + ret = as3935_read(st, AS3935_INT, &val); + if (ret) { + dev_warn(&st->spi->dev, "read error\n"); + return; + } + + val &= AS3935_INT_MASK; + + switch (val) { + case AS3935_EVENT_INT: + iio_trigger_poll_nested(st->trig); + break; + case AS3935_DISTURB_INT: + case AS3935_NOISE_INT: + mutex_lock(&st->lock); + st->noise_tripped = jiffies; + mutex_unlock(&st->lock); + dev_warn(&st->spi->dev, "noise level is too high\n"); + break; + } +} + +static irqreturn_t as3935_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct as3935_state *st = iio_priv(indio_dev); + + /* + * Delay work for >2 milliseconds after an interrupt to allow + * estimated distance to recalculated. + */ + + schedule_delayed_work(&st->work, msecs_to_jiffies(3)); + + return IRQ_HANDLED; +} + +static void calibrate_as3935(struct as3935_state *st) +{ + as3935_write(st, AS3935_DEFAULTS, 0x96); + as3935_write(st, AS3935_CALIBRATE, 0x96); + as3935_write(st, AS3935_TUNE_CAP, + BIT(5) | (st->tune_cap / TUNE_CAP_DIV)); + + mdelay(2); + as3935_write(st, AS3935_TUNE_CAP, (st->tune_cap / TUNE_CAP_DIV)); + as3935_write(st, AS3935_NFLWDTH, st->nflwdth_reg); +} + +static int as3935_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct as3935_state *st = iio_priv(indio_dev); + int val, ret; + + mutex_lock(&st->lock); + ret = as3935_read(st, AS3935_AFE_GAIN, &val); + if (ret) + goto err_suspend; + val |= AS3935_AFE_PWR_BIT; + + ret = as3935_write(st, AS3935_AFE_GAIN, val); + +err_suspend: + mutex_unlock(&st->lock); + + return ret; +} + +static int as3935_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct as3935_state *st = iio_priv(indio_dev); + int val, ret; + + mutex_lock(&st->lock); + ret = as3935_read(st, AS3935_AFE_GAIN, &val); + if (ret) + goto err_resume; + val &= ~AS3935_AFE_PWR_BIT; + ret = as3935_write(st, AS3935_AFE_GAIN, val); + + calibrate_as3935(st); + +err_resume: + mutex_unlock(&st->lock); + + return ret; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(as3935_pm_ops, as3935_suspend, as3935_resume); + +static int as3935_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct iio_trigger *trig; + struct as3935_state *st; + int ret; + + /* Be sure lightning event interrupt is specified */ + if (!spi->irq) { + dev_err(dev, "unable to get event interrupt\n"); + return -EINVAL; + } + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + + spi_set_drvdata(spi, indio_dev); + mutex_init(&st->lock); + + ret = device_property_read_u32(dev, + "ams,tuning-capacitor-pf", &st->tune_cap); + if (ret) { + st->tune_cap = 0; + dev_warn(dev, "no tuning-capacitor-pf set, defaulting to %d", + st->tune_cap); + } + + if (st->tune_cap > MAX_PF_CAP) { + dev_err(dev, "wrong tuning-capacitor-pf setting of %d\n", + st->tune_cap); + return -EINVAL; + } + + ret = device_property_read_u32(dev, + "ams,nflwdth", &st->nflwdth_reg); + if (!ret && st->nflwdth_reg > AS3935_NFLWDTH_MASK) { + dev_err(dev, "invalid nflwdth setting of %d\n", + st->nflwdth_reg); + return -EINVAL; + } + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->channels = as3935_channels; + indio_dev->num_channels = ARRAY_SIZE(as3935_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &as3935_info; + + trig = devm_iio_trigger_alloc(dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + + if (!trig) + return -ENOMEM; + + st->trig = trig; + st->noise_tripped = jiffies - HZ; + iio_trigger_set_drvdata(trig, indio_dev); + + ret = devm_iio_trigger_register(dev, trig); + if (ret) { + dev_err(dev, "failed to register trigger\n"); + return ret; + } + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + as3935_trigger_handler, NULL); + + if (ret) { + dev_err(dev, "cannot setup iio trigger\n"); + return ret; + } + + calibrate_as3935(st); + + ret = devm_delayed_work_autocancel(dev, &st->work, as3935_event_work); + if (ret) + return ret; + + ret = devm_request_irq(dev, spi->irq, + &as3935_interrupt_handler, + IRQF_TRIGGER_RISING, + dev_name(dev), + indio_dev); + + if (ret) + return ret; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret < 0) { + dev_err(dev, "unable to register device\n"); + return ret; + } + return 0; +} + +static const struct of_device_id as3935_of_match[] = { + { .compatible = "ams,as3935", }, + { } +}; +MODULE_DEVICE_TABLE(of, as3935_of_match); + +static const struct spi_device_id as3935_id[] = { + { .name = "as3935" }, + { } +}; +MODULE_DEVICE_TABLE(spi, as3935_id); + +static struct spi_driver as3935_driver = { + .driver = { + .name = "as3935", + .of_match_table = as3935_of_match, + .pm = pm_sleep_ptr(&as3935_pm_ops), + }, + .probe = as3935_probe, + .id_table = as3935_id, +}; +module_spi_driver(as3935_driver); + +MODULE_AUTHOR("Matt Ranostay "); +MODULE_DESCRIPTION("AS3935 lightning sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/proximity/aw96103.c b/drivers/iio/proximity/aw96103.c new file mode 100644 index 000000000..8fbb755dc --- /dev/null +++ b/drivers/iio/proximity/aw96103.c @@ -0,0 +1,846 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AWINIC aw96103 proximity sensor driver + * + * Author: Wang Shuaijie + * + * Copyright (c) 2024 awinic Technology CO., LTD + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AW_DATA_PROCESS_FACTOR 1024 +#define AW96103_CHIP_ID 0xa961 +#define AW96103_BIN_VALID_DATA_OFFSET 64 +#define AW96103_BIN_DATA_LEN_OFFSET 16 +#define AW96103_BIN_DATA_REG_NUM_SIZE 4 +#define AW96103_BIN_CHIP_TYPE_SIZE 8 +#define AW96103_BIN_CHIP_TYPE_OFFSET 24 + +#define AW96103_REG_SCANCTRL0 0x0000 +#define AW96103_REG_STAT0 0x0090 +#define AW96103_REG_BLFILT_CH0 0x00A8 +#define AW96103_REG_BLRSTRNG_CH0 0x00B4 +#define AW96103_REG_DIFF_CH0 0x0240 +#define AW96103_REG_FWVER2 0x0410 +#define AW96103_REG_CMD 0xF008 +#define AW96103_REG_IRQSRC 0xF080 +#define AW96103_REG_IRQEN 0xF084 +#define AW96103_REG_RESET 0xFF0C +#define AW96103_REG_CHIPID 0xFF10 +#define AW96103_REG_EEDA0 0x0408 +#define AW96103_REG_EEDA1 0x040C +#define AW96103_REG_PROXCTRL_CH0 0x00B0 +#define AW96103_REG_PROXTH0_CH0 0x00B8 +#define AW96103_PROXTH_CH_STEP 0x3C +#define AW96103_THHYST_MASK GENMASK(13, 12) +#define AW96103_INDEB_MASK GENMASK(11, 10) +#define AW96103_OUTDEB_MASK GENMASK(9, 8) +#define AW96103_INITOVERIRQ_MASK BIT(0) +#define AW96103_BLFILT_CH_STEP 0x3C +#define AW96103_BLRSTRNG_MASK GENMASK(5, 0) +#define AW96103_CHIPID_MASK GENMASK(31, 16) +#define AW96103_BLERRTRIG_MASK BIT(25) +#define AW96103_CHAN_EN_MASK GENMASK(5, 0) +#define AW96103_REG_PROXCTRL_CH(x) \ + (AW96103_REG_PROXCTRL_CH0 + (x) * AW96103_PROXTH_CH_STEP) + +#define AW96103_REG_PROXTH0_CH(x) \ + (AW96103_REG_PROXTH0_CH0 + (x) * AW96103_PROXTH_CH_STEP) + +/** + * struct aw_bin - Store the data obtained from parsing the configuration file. + * @chip_type: Frame header information-chip type + * @valid_data_len: Length of valid data obtained after parsing + * @valid_data_addr: The offset address of the valid data obtained + * after parsing relative to info + * @len: The size of the bin file obtained from the firmware + * @data: Store the bin file obtained from the firmware + */ +struct aw_bin { + unsigned char chip_type[8]; + unsigned int valid_data_len; + unsigned int valid_data_addr; + unsigned int len; + unsigned char data[] __counted_by(len); +}; + +enum aw96103_sar_vers { + AW96103 = 2, + AW96103A = 6, + AW96103B = 0xa, +}; + +enum aw96103_operation_mode { + AW96103_ACTIVE_MODE = 1, + AW96103_SLEEP_MODE = 2, + AW96103_DEEPSLEEP_MODE = 3, + AW96103B_DEEPSLEEP_MODE = 4, +}; + +enum aw96103_sensor_type { + AW96103_VAL, + AW96105_VAL, +}; + +struct aw_channels_info { + bool used; + unsigned int old_irq_status; +}; + +struct aw_chip_info { + const char *name; + struct iio_chan_spec const *channels; + int num_channels; +}; + +struct aw96103 { + unsigned int hostirqen; + struct regmap *regmap; + struct device *dev; + /* + * There is one more logical channel than the actual channels, + * and the extra logical channel is used for temperature detection + * but not for status detection. The specific channel used for + * temperature detection is determined by the register configuration. + */ + struct aw_channels_info channels_arr[6]; + unsigned int max_channels; + unsigned int chan_en; +}; + +static const unsigned int aw96103_reg_default[] = { + 0x0000, 0x00003f3f, 0x0004, 0x00000064, 0x0008, 0x0017c11e, + 0x000c, 0x05000000, 0x0010, 0x00093ffd, 0x0014, 0x19240009, + 0x0018, 0xd81c0207, 0x001c, 0xff000000, 0x0020, 0x00241900, + 0x0024, 0x00093ff7, 0x0028, 0x58020009, 0x002c, 0xd81c0207, + 0x0030, 0xff000000, 0x0034, 0x00025800, 0x0038, 0x00093fdf, + 0x003c, 0x7d3b0009, 0x0040, 0xd81c0207, 0x0044, 0xff000000, + 0x0048, 0x003b7d00, 0x004c, 0x00093f7f, 0x0050, 0xe9310009, + 0x0054, 0xd81c0207, 0x0058, 0xff000000, 0x005c, 0x0031e900, + 0x0060, 0x00093dff, 0x0064, 0x1a0c0009, 0x0068, 0xd81c0207, + 0x006c, 0xff000000, 0x0070, 0x000c1a00, 0x0074, 0x80093fff, + 0x0078, 0x043d0009, 0x007c, 0xd81c0207, 0x0080, 0xff000000, + 0x0084, 0x003d0400, 0x00a0, 0xe6400000, 0x00a4, 0x00000000, + 0x00a8, 0x010408d2, 0x00ac, 0x00000000, 0x00b0, 0x00000000, + 0x00b8, 0x00005fff, 0x00bc, 0x00000000, 0x00c0, 0x00000000, + 0x00c4, 0x00000000, 0x00c8, 0x00000000, 0x00cc, 0x00000000, + 0x00d0, 0x00000000, 0x00d4, 0x00000000, 0x00d8, 0x00000000, + 0x00dc, 0xe6447800, 0x00e0, 0x78000000, 0x00e4, 0x010408d2, + 0x00e8, 0x00000000, 0x00ec, 0x00000000, 0x00f4, 0x00005fff, + 0x00f8, 0x00000000, 0x00fc, 0x00000000, 0x0100, 0x00000000, + 0x0104, 0x00000000, 0x0108, 0x00000000, 0x010c, 0x02000000, + 0x0110, 0x00000000, 0x0114, 0x00000000, 0x0118, 0xe6447800, + 0x011c, 0x78000000, 0x0120, 0x010408d2, 0x0124, 0x00000000, + 0x0128, 0x00000000, 0x0130, 0x00005fff, 0x0134, 0x00000000, + 0x0138, 0x00000000, 0x013c, 0x00000000, 0x0140, 0x00000000, + 0x0144, 0x00000000, 0x0148, 0x02000000, 0x014c, 0x00000000, + 0x0150, 0x00000000, 0x0154, 0xe6447800, 0x0158, 0x78000000, + 0x015c, 0x010408d2, 0x0160, 0x00000000, 0x0164, 0x00000000, + 0x016c, 0x00005fff, 0x0170, 0x00000000, 0x0174, 0x00000000, + 0x0178, 0x00000000, 0x017c, 0x00000000, 0x0180, 0x00000000, + 0x0184, 0x02000000, 0x0188, 0x00000000, 0x018c, 0x00000000, + 0x0190, 0xe6447800, 0x0194, 0x78000000, 0x0198, 0x010408d2, + 0x019c, 0x00000000, 0x01a0, 0x00000000, 0x01a8, 0x00005fff, + 0x01ac, 0x00000000, 0x01b0, 0x00000000, 0x01b4, 0x00000000, + 0x01b8, 0x00000000, 0x01bc, 0x00000000, 0x01c0, 0x02000000, + 0x01c4, 0x00000000, 0x01c8, 0x00000000, 0x01cc, 0xe6407800, + 0x01d0, 0x78000000, 0x01d4, 0x010408d2, 0x01d8, 0x00000000, + 0x01dc, 0x00000000, 0x01e4, 0x00005fff, 0x01e8, 0x00000000, + 0x01ec, 0x00000000, 0x01f0, 0x00000000, 0x01f4, 0x00000000, + 0x01f8, 0x00000000, 0x01fc, 0x02000000, 0x0200, 0x00000000, + 0x0204, 0x00000000, 0x0208, 0x00000008, 0x020c, 0x0000000d, + 0x41fc, 0x00000000, 0x4400, 0x00000000, 0x4410, 0x00000000, + 0x4420, 0x00000000, 0x4430, 0x00000000, 0x4440, 0x00000000, + 0x4450, 0x00000000, 0x4460, 0x00000000, 0x4470, 0x00000000, + 0xf080, 0x00003018, 0xf084, 0x00000fff, 0xf800, 0x00000000, + 0xf804, 0x00002e00, 0xf8d0, 0x00000001, 0xf8d4, 0x00000000, + 0xff00, 0x00000301, 0xff0c, 0x01000000, 0xffe0, 0x00000000, + 0xfff4, 0x00004011, 0x0090, 0x00000000, 0x0094, 0x00000000, + 0x0098, 0x00000000, 0x009c, 0x3f3f3f3f, +}; + +static const struct iio_event_spec aw_common_events[3] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_PERIOD), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_PERIOD), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_HYSTERESIS) | + BIT(IIO_EV_INFO_VALUE), + } +}; + +#define AW_IIO_CHANNEL(idx) \ +{ \ + .type = IIO_PROXIMITY, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .indexed = 1, \ + .channel = idx, \ + .event_spec = aw_common_events, \ + .num_event_specs = ARRAY_SIZE(aw_common_events), \ +} \ + +static const struct iio_chan_spec aw96103_channels[] = { + AW_IIO_CHANNEL(0), + AW_IIO_CHANNEL(1), + AW_IIO_CHANNEL(2), + AW_IIO_CHANNEL(3), +}; + +static const struct iio_chan_spec aw96105_channels[] = { + AW_IIO_CHANNEL(0), + AW_IIO_CHANNEL(1), + AW_IIO_CHANNEL(2), + AW_IIO_CHANNEL(3), + AW_IIO_CHANNEL(4), + AW_IIO_CHANNEL(5), +}; + +static const struct aw_chip_info aw_chip_info_tbl[] = { + [AW96103_VAL] = { + .name = "aw96103_sensor", + .channels = aw96103_channels, + .num_channels = ARRAY_SIZE(aw96103_channels), + }, + [AW96105_VAL] = { + .name = "aw96105_sensor", + .channels = aw96105_channels, + .num_channels = ARRAY_SIZE(aw96105_channels), + }, +}; + +static void aw96103_parsing_bin_file(struct aw_bin *bin) +{ + bin->valid_data_addr = AW96103_BIN_VALID_DATA_OFFSET; + bin->valid_data_len = + *(unsigned int *)(bin->data + AW96103_BIN_DATA_LEN_OFFSET) - + AW96103_BIN_DATA_REG_NUM_SIZE; + memcpy(bin->chip_type, bin->data + AW96103_BIN_CHIP_TYPE_OFFSET, + AW96103_BIN_CHIP_TYPE_SIZE); +} + +static const struct regmap_config aw96103_regmap_confg = { + .reg_bits = 16, + .val_bits = 32, +}; + +static int aw96103_get_diff_raw(struct aw96103 *aw96103, unsigned int chan, + int *buf) +{ + u32 data; + int ret; + + ret = regmap_read(aw96103->regmap, + AW96103_REG_DIFF_CH0 + chan * 4, &data); + if (ret) + return ret; + *buf = (int)(data / AW_DATA_PROCESS_FACTOR); + + return 0; +} + +static int aw96103_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct aw96103 *aw96103 = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = aw96103_get_diff_raw(aw96103, chan->channel, val); + if (ret) + return ret; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int aw96103_read_thresh(struct aw96103 *aw96103, + const struct iio_chan_spec *chan, int *val) +{ + int ret; + + ret = regmap_read(aw96103->regmap, + AW96103_REG_PROXTH0_CH(chan->channel), val); + if (ret) + return ret; + + return IIO_VAL_INT; +} + +static int aw96103_read_out_debounce(struct aw96103 *aw96103, + const struct iio_chan_spec *chan, + int *val) +{ + unsigned int reg_val; + int ret; + + ret = regmap_read(aw96103->regmap, + AW96103_REG_PROXCTRL_CH(chan->channel), ®_val); + if (ret) + return ret; + *val = FIELD_GET(AW96103_OUTDEB_MASK, reg_val); + + return IIO_VAL_INT; +} + +static int aw96103_read_in_debounce(struct aw96103 *aw96103, + const struct iio_chan_spec *chan, int *val) +{ + unsigned int reg_val; + int ret; + + ret = regmap_read(aw96103->regmap, + AW96103_REG_PROXCTRL_CH(chan->channel), ®_val); + if (ret) + return ret; + *val = FIELD_GET(AW96103_INDEB_MASK, reg_val); + + return IIO_VAL_INT; +} + +static int aw96103_read_hysteresis(struct aw96103 *aw96103, + const struct iio_chan_spec *chan, int *val) +{ + unsigned int reg_val; + int ret; + + ret = regmap_read(aw96103->regmap, + AW96103_REG_PROXCTRL_CH(chan->channel), ®_val); + if (ret) + return ret; + *val = FIELD_GET(AW96103_THHYST_MASK, reg_val); + + return IIO_VAL_INT; +} + +static int aw96103_read_event_val(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct aw96103 *aw96103 = iio_priv(indio_dev); + + if (chan->type != IIO_PROXIMITY) + return -EINVAL; + + switch (info) { + case IIO_EV_INFO_VALUE: + return aw96103_read_thresh(aw96103, chan, val); + case IIO_EV_INFO_PERIOD: + switch (dir) { + case IIO_EV_DIR_RISING: + return aw96103_read_out_debounce(aw96103, chan, val); + case IIO_EV_DIR_FALLING: + return aw96103_read_in_debounce(aw96103, chan, val); + default: + return -EINVAL; + } + case IIO_EV_INFO_HYSTERESIS: + return aw96103_read_hysteresis(aw96103, chan, val); + default: + return -EINVAL; + } +} + +static int aw96103_write_event_val(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, int val2) +{ + struct aw96103 *aw96103 = iio_priv(indio_dev); + + if (chan->type != IIO_PROXIMITY) + return -EINVAL; + + switch (info) { + case IIO_EV_INFO_VALUE: + return regmap_write(aw96103->regmap, + AW96103_REG_PROXTH0_CH(chan->channel), val); + case IIO_EV_INFO_PERIOD: + switch (dir) { + case IIO_EV_DIR_RISING: + return regmap_update_bits(aw96103->regmap, + AW96103_REG_PROXCTRL_CH(chan->channel), + AW96103_OUTDEB_MASK, + FIELD_PREP(AW96103_OUTDEB_MASK, val)); + + case IIO_EV_DIR_FALLING: + return regmap_update_bits(aw96103->regmap, + AW96103_REG_PROXCTRL_CH(chan->channel), + AW96103_INDEB_MASK, + FIELD_PREP(AW96103_INDEB_MASK, val)); + default: + return -EINVAL; + } + case IIO_EV_INFO_HYSTERESIS: + return regmap_update_bits(aw96103->regmap, + AW96103_REG_PROXCTRL_CH(chan->channel), + AW96103_THHYST_MASK, + FIELD_PREP(AW96103_THHYST_MASK, val)); + default: + return -EINVAL; + } +} + +static int aw96103_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct aw96103 *aw96103 = iio_priv(indio_dev); + + return aw96103->channels_arr[chan->channel].used; +} + +static int aw96103_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct aw96103 *aw96103 = iio_priv(indio_dev); + + aw96103->channels_arr[chan->channel].used = !!state; + + return regmap_update_bits(aw96103->regmap, AW96103_REG_SCANCTRL0, + BIT(chan->channel), + state ? BIT(chan->channel) : 0); +} + +static const struct iio_info iio_info = { + .read_raw = aw96103_read_raw, + .read_event_value = aw96103_read_event_val, + .write_event_value = aw96103_write_event_val, + .read_event_config = aw96103_read_event_config, + .write_event_config = aw96103_write_event_config, +}; + +static int aw96103_channel_scan_start(struct aw96103 *aw96103) +{ + int ret; + + ret = regmap_write(aw96103->regmap, AW96103_REG_CMD, + AW96103_ACTIVE_MODE); + if (ret) + return ret; + + return regmap_write(aw96103->regmap, AW96103_REG_IRQEN, + aw96103->hostirqen); +} + +static int aw96103_reg_version_comp(struct aw96103 *aw96103, + struct aw_bin *aw_bin) +{ + u32 blfilt1_data, fw_ver; + unsigned char i; + int ret; + + ret = regmap_read(aw96103->regmap, AW96103_REG_FWVER2, &fw_ver); + if (ret) + return ret; + /* + * If the chip version is AW96103A and the loaded register + * configuration file is for AW96103, special handling of the + * AW96103_REG_BLRSTRNG_CH0 register is required. + */ + if ((fw_ver != AW96103A) || (aw_bin->chip_type[7] != '\0')) + return 0; + + for (i = 0; i < aw96103->max_channels; i++) { + ret = regmap_read(aw96103->regmap, + AW96103_REG_BLFILT_CH0 + (AW96103_BLFILT_CH_STEP * i), + &blfilt1_data); + if (ret) + return ret; + if (FIELD_GET(AW96103_BLERRTRIG_MASK, blfilt1_data) != 1) + return 0; + + ret = regmap_update_bits(aw96103->regmap, + AW96103_REG_BLRSTRNG_CH0 + (AW96103_BLFILT_CH_STEP * i), + AW96103_BLRSTRNG_MASK, 1 << i); + if (ret) + return ret; + } + + return 0; +} + +static int aw96103_bin_valid_loaded(struct aw96103 *aw96103, + struct aw_bin *aw_bin_data_s) +{ + unsigned int start_addr = aw_bin_data_s->valid_data_addr; + u32 i, reg_data; + u16 reg_addr; + int ret; + + for (i = 0; i < aw_bin_data_s->valid_data_len; + i += 6, start_addr += 6) { + reg_addr = get_unaligned_le16(aw_bin_data_s->data + start_addr); + reg_data = get_unaligned_le32(aw_bin_data_s->data + + start_addr + 2); + if ((reg_addr == AW96103_REG_EEDA0) || + (reg_addr == AW96103_REG_EEDA1)) + continue; + if (reg_addr == AW96103_REG_IRQEN) { + aw96103->hostirqen = reg_data; + continue; + } + if (reg_addr == AW96103_REG_SCANCTRL0) + aw96103->chan_en = FIELD_GET(AW96103_CHAN_EN_MASK, + reg_data); + + ret = regmap_write(aw96103->regmap, reg_addr, reg_data); + if (ret < 0) + return ret; + } + + ret = aw96103_reg_version_comp(aw96103, aw_bin_data_s); + if (ret) + return ret; + + return aw96103_channel_scan_start(aw96103); +} + +static int aw96103_para_loaded(struct aw96103 *aw96103) +{ + int i, ret; + + for (i = 0; i < ARRAY_SIZE(aw96103_reg_default); i += 2) { + ret = regmap_write(aw96103->regmap, + (u16)aw96103_reg_default[i], + (u32)aw96103_reg_default[i + 1]); + if (ret) + return ret; + if (aw96103_reg_default[i] == AW96103_REG_IRQEN) + aw96103->hostirqen = aw96103_reg_default[i + 1]; + else if (aw96103_reg_default[i] == AW96103_REG_SCANCTRL0) + aw96103->chan_en = FIELD_GET(AW96103_CHAN_EN_MASK, + aw96103_reg_default[i + 1]); + } + + return aw96103_channel_scan_start(aw96103); +} + +static int aw96103_cfg_all_loaded(const struct firmware *cont, + struct aw96103 *aw96103) +{ + if (!cont) + return -EINVAL; + + struct aw_bin *aw_bin __free(kfree) = + kzalloc(cont->size + sizeof(*aw_bin), GFP_KERNEL); + if (!aw_bin) + return -ENOMEM; + + aw_bin->len = cont->size; + memcpy(aw_bin->data, cont->data, cont->size); + release_firmware(cont); + aw96103_parsing_bin_file(aw_bin); + + return aw96103_bin_valid_loaded(aw96103, aw_bin); +} + +static void aw96103_cfg_update(const struct firmware *fw, void *data) +{ + struct aw96103 *aw96103 = data; + int ret, i; + + if (!fw || !fw->data) { + dev_err(aw96103->dev, "No firmware.\n"); + return; + } + + ret = aw96103_cfg_all_loaded(fw, aw96103); + /* + * If loading the register configuration file fails, + * load the default register configuration in the driver to + * ensure the basic functionality of the device. + */ + if (ret) { + ret = aw96103_para_loaded(aw96103); + if (ret) { + dev_err(aw96103->dev, "load param error.\n"); + return; + } + } + + for (i = 0; i < aw96103->max_channels; i++) { + if ((aw96103->chan_en >> i) & 0x01) + aw96103->channels_arr[i].used = true; + else + aw96103->channels_arr[i].used = false; + } +} + +static int aw96103_sw_reset(struct aw96103 *aw96103) +{ + int ret; + + ret = regmap_write(aw96103->regmap, AW96103_REG_RESET, 0); + /* + * After reset, the initialization process starts to perform and + * it will last for a bout 20ms. + */ + msleep(20); + + return ret; +} + +enum aw96103_irq_trigger_position { + FAR = 0, + TRIGGER_TH0 = 0x01, + TRIGGER_TH1 = 0x03, + TRIGGER_TH2 = 0x07, + TRIGGER_TH3 = 0x0f, +}; + +static irqreturn_t aw96103_irq(int irq, void *data) +{ + unsigned int irq_status, curr_status_val, curr_status; + struct iio_dev *indio_dev = data; + struct aw96103 *aw96103 = iio_priv(indio_dev); + int ret, i; + + ret = regmap_read(aw96103->regmap, AW96103_REG_IRQSRC, &irq_status); + if (ret) + return IRQ_HANDLED; + + ret = regmap_read(aw96103->regmap, AW96103_REG_STAT0, &curr_status_val); + if (ret) + return IRQ_HANDLED; + + /* + * Iteratively analyze the interrupt status of different channels, + * with each channel having 4 interrupt states. + */ + for (i = 0; i < aw96103->max_channels; i++) { + if (!aw96103->channels_arr[i].used) + continue; + + curr_status = (((curr_status_val >> (24 + i)) & 0x1)) | + (((curr_status_val >> (16 + i)) & 0x1) << 1) | + (((curr_status_val >> (8 + i)) & 0x1) << 2) | + (((curr_status_val >> i) & 0x1) << 3); + if (aw96103->channels_arr[i].old_irq_status == curr_status) + continue; + + switch (curr_status) { + case FAR: + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, i, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + break; + case TRIGGER_TH0: + case TRIGGER_TH1: + case TRIGGER_TH2: + case TRIGGER_TH3: + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, i, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns(indio_dev)); + break; + default: + return IRQ_HANDLED; + } + aw96103->channels_arr[i].old_irq_status = curr_status; + } + + return IRQ_HANDLED; +} + +static int aw96103_interrupt_init(struct iio_dev *indio_dev, + struct i2c_client *i2c) +{ + struct aw96103 *aw96103 = iio_priv(indio_dev); + unsigned int irq_status; + int ret; + + ret = regmap_write(aw96103->regmap, AW96103_REG_IRQEN, 0); + if (ret) + return ret; + ret = regmap_read(aw96103->regmap, AW96103_REG_IRQSRC, &irq_status); + if (ret) + return ret; + ret = devm_request_threaded_irq(aw96103->dev, i2c->irq, NULL, + aw96103_irq, IRQF_ONESHOT, + "aw96103_irq", indio_dev); + if (ret) + return ret; + + return regmap_write(aw96103->regmap, AW96103_REG_IRQEN, + aw96103->hostirqen); +} + +static int aw96103_wait_chip_init(struct aw96103 *aw96103) +{ + unsigned int cnt = 20; + u32 reg_data; + int ret; + + while (cnt--) { + /* + * The device should generate an initialization completion + * interrupt within 20ms. + */ + ret = regmap_read(aw96103->regmap, AW96103_REG_IRQSRC, + ®_data); + if (ret) + return ret; + + if (FIELD_GET(AW96103_INITOVERIRQ_MASK, reg_data)) + return 0; + fsleep(1000); + } + + return -ETIMEDOUT; +} + +static int aw96103_read_chipid(struct aw96103 *aw96103) +{ + unsigned char cnt = 0; + u32 reg_val = 0; + int ret; + + while (cnt < 3) { + /* + * This retry mechanism and the subsequent delay are just + * attempts to read the chip ID as much as possible, + * preventing occasional communication failures from causing + * the chip ID read to fail. + */ + ret = regmap_read(aw96103->regmap, AW96103_REG_CHIPID, + ®_val); + if (ret < 0) { + cnt++; + fsleep(2000); + continue; + } + break; + } + if (cnt == 3) + return -ETIMEDOUT; + + if (FIELD_GET(AW96103_CHIPID_MASK, reg_val) != AW96103_CHIP_ID) + dev_info(aw96103->dev, + "unexpected chipid, id=0x%08X\n", reg_val); + + return 0; +} + +static int aw96103_i2c_probe(struct i2c_client *i2c) +{ + const struct aw_chip_info *chip_info; + struct iio_dev *indio_dev; + struct aw96103 *aw96103; + int ret; + + indio_dev = devm_iio_device_alloc(&i2c->dev, sizeof(*aw96103)); + if (!indio_dev) + return -ENOMEM; + + aw96103 = iio_priv(indio_dev); + aw96103->dev = &i2c->dev; + chip_info = i2c_get_match_data(i2c); + aw96103->max_channels = chip_info->num_channels; + + aw96103->regmap = devm_regmap_init_i2c(i2c, &aw96103_regmap_confg); + if (IS_ERR(aw96103->regmap)) + return PTR_ERR(aw96103->regmap); + + ret = devm_regulator_get_enable(aw96103->dev, "vcc"); + if (ret < 0) + return ret; + + ret = aw96103_read_chipid(aw96103); + if (ret) + return ret; + + ret = aw96103_sw_reset(aw96103); + if (ret) + return ret; + + ret = aw96103_wait_chip_init(aw96103); + if (ret) + return ret; + + ret = request_firmware_nowait(THIS_MODULE, true, "aw96103_0.bin", + aw96103->dev, GFP_KERNEL, aw96103, + aw96103_cfg_update); + if (ret) + return ret; + + ret = aw96103_interrupt_init(indio_dev, i2c); + if (ret) + return ret; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = chip_info->num_channels; + indio_dev->channels = chip_info->channels; + indio_dev->info = &iio_info; + indio_dev->name = chip_info->name; + + return devm_iio_device_register(aw96103->dev, indio_dev); +} + +static const struct of_device_id aw96103_dt_match[] = { + { + .compatible = "awinic,aw96103", + .data = &aw_chip_info_tbl[AW96103_VAL] + }, + { + .compatible = "awinic,aw96105", + .data = &aw_chip_info_tbl[AW96105_VAL] + }, + { } +}; +MODULE_DEVICE_TABLE(of, aw96103_dt_match); + +static const struct i2c_device_id aw96103_i2c_id[] = { + { .name = "aw96103", .driver_data = (kernel_ulong_t)&aw_chip_info_tbl[AW96103_VAL] }, + { .name = "aw96105", .driver_data = (kernel_ulong_t)&aw_chip_info_tbl[AW96105_VAL] }, + { } +}; +MODULE_DEVICE_TABLE(i2c, aw96103_i2c_id); + +static struct i2c_driver aw96103_i2c_driver = { + .driver = { + .name = "aw96103_sensor", + .of_match_table = aw96103_dt_match, + }, + .probe = aw96103_i2c_probe, + .id_table = aw96103_i2c_id, +}; +module_i2c_driver(aw96103_i2c_driver); + +MODULE_AUTHOR("Wang Shuaijie "); +MODULE_DESCRIPTION("Driver for Awinic AW96103 proximity sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/proximity/cros_ec_mkbp_proximity.c b/drivers/iio/proximity/cros_ec_mkbp_proximity.c new file mode 100644 index 000000000..63f9b45be --- /dev/null +++ b/drivers/iio/proximity/cros_ec_mkbp_proximity.c @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for cros-ec proximity sensor exposed through MKBP switch + * + * Copyright 2021 Google LLC. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +struct cros_ec_mkbp_proximity_data { + struct cros_ec_device *ec; + struct iio_dev *indio_dev; + struct mutex lock; + struct notifier_block notifier; + int last_proximity; + bool enabled; +}; + +static const struct iio_event_spec cros_ec_mkbp_proximity_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec cros_ec_mkbp_proximity_chan_spec[] = { + { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = cros_ec_mkbp_proximity_events, + .num_event_specs = ARRAY_SIZE(cros_ec_mkbp_proximity_events), + }, +}; + +static int cros_ec_mkbp_proximity_parse_state(const void *data) +{ + u32 switches = get_unaligned_le32(data); + + return !!(switches & BIT(EC_MKBP_FRONT_PROXIMITY)); +} + +static int cros_ec_mkbp_proximity_query(struct cros_ec_device *ec_dev, + int *state) +{ + DEFINE_RAW_FLEX(struct cros_ec_command, buf, data, + MAX(sizeof(u32), sizeof(struct ec_params_mkbp_info))); + struct ec_params_mkbp_info *params = (struct ec_params_mkbp_info *)buf->data; + struct cros_ec_command *msg = buf; + u32 *switches = (u32 *)buf->data; + size_t insize = sizeof(*switches); + int ret; + + msg->command = EC_CMD_MKBP_INFO; + msg->version = 1; + msg->outsize = sizeof(*params); + msg->insize = insize; + + params->info_type = EC_MKBP_INFO_CURRENT; + params->event_type = EC_MKBP_EVENT_SWITCH; + + ret = cros_ec_cmd_xfer_status(ec_dev, msg); + if (ret < 0) + return ret; + + if (ret != insize) { + dev_warn(ec_dev->dev, "wrong result size: %d != %zu\n", ret, + insize); + return -EPROTO; + } + + *state = cros_ec_mkbp_proximity_parse_state(switches); + return IIO_VAL_INT; +} + +static void cros_ec_mkbp_proximity_push_event(struct cros_ec_mkbp_proximity_data *data, int state) +{ + s64 timestamp; + u64 ev; + int dir; + struct iio_dev *indio_dev = data->indio_dev; + struct cros_ec_device *ec = data->ec; + + mutex_lock(&data->lock); + if (state != data->last_proximity) { + if (data->enabled) { + timestamp = ktime_to_ns(ec->last_event_time); + if (iio_device_get_clock(indio_dev) != CLOCK_BOOTTIME) + timestamp = iio_get_time_ns(indio_dev); + + dir = state ? IIO_EV_DIR_FALLING : IIO_EV_DIR_RISING; + ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, dir); + iio_push_event(indio_dev, ev, timestamp); + } + data->last_proximity = state; + } + mutex_unlock(&data->lock); +} + +static int cros_ec_mkbp_proximity_notify(struct notifier_block *nb, + unsigned long queued_during_suspend, + void *_ec) +{ + struct cros_ec_mkbp_proximity_data *data; + struct cros_ec_device *ec = _ec; + u8 event_type = ec->event_data.event_type & EC_MKBP_EVENT_TYPE_MASK; + void *switches; + int state; + + if (event_type == EC_MKBP_EVENT_SWITCH) { + data = container_of(nb, struct cros_ec_mkbp_proximity_data, + notifier); + + switches = &ec->event_data.data.switches; + state = cros_ec_mkbp_proximity_parse_state(switches); + cros_ec_mkbp_proximity_push_event(data, state); + } + + return NOTIFY_OK; +} + +static int cros_ec_mkbp_proximity_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, + int *val2, long mask) +{ + struct cros_ec_mkbp_proximity_data *data = iio_priv(indio_dev); + struct cros_ec_device *ec = data->ec; + + if (chan->type == IIO_PROXIMITY && mask == IIO_CHAN_INFO_RAW) + return cros_ec_mkbp_proximity_query(ec, val); + + return -EINVAL; +} + +static int cros_ec_mkbp_proximity_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct cros_ec_mkbp_proximity_data *data = iio_priv(indio_dev); + + return data->enabled; +} + +static int cros_ec_mkbp_proximity_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct cros_ec_mkbp_proximity_data *data = iio_priv(indio_dev); + + mutex_lock(&data->lock); + data->enabled = state; + mutex_unlock(&data->lock); + + return 0; +} + +static const struct iio_info cros_ec_mkbp_proximity_info = { + .read_raw = cros_ec_mkbp_proximity_read_raw, + .read_event_config = cros_ec_mkbp_proximity_read_event_config, + .write_event_config = cros_ec_mkbp_proximity_write_event_config, +}; + +static int cros_ec_mkbp_proximity_resume(struct device *dev) +{ + struct cros_ec_mkbp_proximity_data *data = dev_get_drvdata(dev); + struct cros_ec_device *ec = data->ec; + int ret, state; + + ret = cros_ec_mkbp_proximity_query(ec, &state); + if (ret < 0) { + dev_warn(dev, "failed to fetch proximity state on resume: %d\n", + ret); + } else { + cros_ec_mkbp_proximity_push_event(data, state); + } + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(cros_ec_mkbp_proximity_pm_ops, NULL, + cros_ec_mkbp_proximity_resume); + +static int cros_ec_mkbp_proximity_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cros_ec_device *ec = dev_get_drvdata(dev->parent); + struct iio_dev *indio_dev; + struct cros_ec_mkbp_proximity_data *data; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->ec = ec; + data->indio_dev = indio_dev; + data->last_proximity = -1; /* Unknown to start */ + mutex_init(&data->lock); + platform_set_drvdata(pdev, data); + + indio_dev->name = dev->driver->name; + indio_dev->info = &cros_ec_mkbp_proximity_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = cros_ec_mkbp_proximity_chan_spec; + indio_dev->num_channels = ARRAY_SIZE(cros_ec_mkbp_proximity_chan_spec); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ret; + + data->notifier.notifier_call = cros_ec_mkbp_proximity_notify; + blocking_notifier_chain_register(&ec->event_notifier, &data->notifier); + + return 0; +} + +static void cros_ec_mkbp_proximity_remove(struct platform_device *pdev) +{ + struct cros_ec_mkbp_proximity_data *data = platform_get_drvdata(pdev); + struct cros_ec_device *ec = data->ec; + + blocking_notifier_chain_unregister(&ec->event_notifier, + &data->notifier); +} + +static const struct of_device_id cros_ec_mkbp_proximity_of_match[] = { + { .compatible = "google,cros-ec-mkbp-proximity" }, + { } +}; +MODULE_DEVICE_TABLE(of, cros_ec_mkbp_proximity_of_match); + +static struct platform_driver cros_ec_mkbp_proximity_driver = { + .driver = { + .name = "cros-ec-mkbp-proximity", + .of_match_table = cros_ec_mkbp_proximity_of_match, + .pm = pm_sleep_ptr(&cros_ec_mkbp_proximity_pm_ops), + }, + .probe = cros_ec_mkbp_proximity_probe, + .remove = cros_ec_mkbp_proximity_remove, +}; +module_platform_driver(cros_ec_mkbp_proximity_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ChromeOS EC MKBP proximity sensor driver"); diff --git a/drivers/iio/proximity/d3323aa.c b/drivers/iio/proximity/d3323aa.c new file mode 100644 index 000000000..1f43c1fed --- /dev/null +++ b/drivers/iio/proximity/d3323aa.c @@ -0,0 +1,814 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Nicera D3-323-AA PIR sensor. + * + * Copyright (C) 2025 Axis Communications AB + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* + * Register bitmap. + * For some reason the first bit is denoted as F37 in the datasheet, the second + * as F38 and so on. Note the gap between F60 and F64. + */ +#define D3323AA_REG_BIT_SLAVEA1 0 /* F37. */ +#define D3323AA_REG_BIT_SLAVEA2 1 /* F38. */ +#define D3323AA_REG_BIT_SLAVEA3 2 /* F39. */ +#define D3323AA_REG_BIT_SLAVEA4 3 /* F40. */ +#define D3323AA_REG_BIT_SLAVEA5 4 /* F41. */ +#define D3323AA_REG_BIT_SLAVEA6 5 /* F42. */ +#define D3323AA_REG_BIT_SLAVEA7 6 /* F43. */ +#define D3323AA_REG_BIT_SLAVEA8 7 /* F44. */ +#define D3323AA_REG_BIT_SLAVEA9 8 /* F45. */ +#define D3323AA_REG_BIT_SLAVEA10 9 /* F46. */ +#define D3323AA_REG_BIT_DETLVLABS0 10 /* F47. */ +#define D3323AA_REG_BIT_DETLVLABS1 11 /* F48. */ +#define D3323AA_REG_BIT_DETLVLABS2 12 /* F49. */ +#define D3323AA_REG_BIT_DETLVLABS3 13 /* F50. */ +#define D3323AA_REG_BIT_DETLVLABS4 14 /* F51. */ +#define D3323AA_REG_BIT_DETLVLABS5 15 /* F52. */ +#define D3323AA_REG_BIT_DETLVLABS6 16 /* F53. */ +#define D3323AA_REG_BIT_DETLVLABS7 17 /* F54. */ +#define D3323AA_REG_BIT_DSLP 18 /* F55. */ +#define D3323AA_REG_BIT_FSTEP0 19 /* F56. */ +#define D3323AA_REG_BIT_FSTEP1 20 /* F57. */ +#define D3323AA_REG_BIT_FILSEL0 21 /* F58. */ +#define D3323AA_REG_BIT_FILSEL1 22 /* F59. */ +#define D3323AA_REG_BIT_FILSEL2 23 /* F60. */ +#define D3323AA_REG_BIT_FDSET 24 /* F64. */ +#define D3323AA_REG_BIT_F65 25 +#define D3323AA_REG_BIT_F87 (D3323AA_REG_BIT_F65 + (87 - 65)) + +#define D3323AA_REG_NR_BITS (D3323AA_REG_BIT_F87 - D3323AA_REG_BIT_SLAVEA1 + 1) +#define D3323AA_THRESH_REG_NR_BITS \ + (D3323AA_REG_BIT_DETLVLABS7 - D3323AA_REG_BIT_DETLVLABS0 + 1) +#define D3323AA_FILTER_TYPE_NR_BITS \ + (D3323AA_REG_BIT_FILSEL2 - D3323AA_REG_BIT_FILSEL0 + 1) +#define D3323AA_FILTER_GAIN_REG_NR_BITS \ + (D3323AA_REG_BIT_FSTEP1 - D3323AA_REG_BIT_FSTEP0 + 1) + +#define D3323AA_THRESH_DEFAULT_VAL 56 +#define D3323AA_FILTER_GAIN_DEFAULT_IDX 1 +#define D3323AA_LP_FILTER_FREQ_DEFAULT_IDX 1 + +/* + * The pattern is 0b01101, but store it reversed (0b10110) due to writing from + * LSB on the wire (c.f. d3323aa_write_settings()). + */ +#define D3323AA_SETTING_END_PATTERN 0x16 +#define D3323AA_SETTING_END_PATTERN_NR_BITS 5 + +/* + * Device should be ready for configuration after this many milliseconds. + * Datasheet mentions "approx. 1.2 s". Measurements show around 1.23 s, + * therefore add 100 ms of slack. + */ +#define D3323AA_RESET_TIMEOUT (1200 + 100) + +/* + * The configuration of the device (write and read) should be done within this + * many milliseconds. + */ +#define D3323AA_CONFIG_TIMEOUT 1400 + +/* Number of IRQs needed for configuration stage after reset. */ +#define D3323AA_IRQ_RESET_COUNT 2 + +/* + * High-pass filter cutoff frequency for the band-pass filter. There is a + * corresponding low-pass cutoff frequency for each of the filter types + * (denoted A, B, C and D in the datasheet). The index in this array matches + * that corresponding value in d3323aa_lp_filter_freq. + * Note that this represents a fractional value (e.g. the first value + * corresponds to 40 / 100 = 0.4 Hz). + */ +static const int d3323aa_hp_filter_freq[][2] = { + { 40, 100 }, + { 30, 100 }, + { 30, 100 }, + { 1, 100 }, +}; + +/* + * Low-pass filter cutoff frequency for the band-pass filter. There is a + * corresponding high-pass cutoff frequency for each of the filter types + * (denoted A, B, C and D in the datasheet). The index in this array matches + * that corresponding value in d3323aa_hp_filter_freq. + * Note that this represents a fractional value (e.g. the first value + * corresponds to 27 / 10 = 2.7 Hz). + */ +static const int d3323aa_lp_filter_freq[][2] = { + { 27, 10 }, + { 15, 10 }, + { 5, 1 }, + { 100, 1 }, +}; + +/* + * Register bitmap values for filter types (denoted A, B, C and D in the + * datasheet). The index in this array matches the corresponding value in + * d3323aa_lp_filter_freq (which in turn matches d3323aa_hp_filter_freq). For + * example, the first value 7 corresponds to 2.7 Hz low-pass and 0.4 Hz + * high-pass cutoff frequency. + */ +static const int d3323aa_lp_filter_regval[] = { + 7, + 0, + 1, + 2, +}; + +/* + * This is denoted as "step" in datasheet and corresponds to the gain at peak + * for the band-pass filter. The index in this array is the corresponding index + * in d3323aa_filter_gain_regval for the register bitmap value. + */ +static const int d3323aa_filter_gain[] = { 1, 2, 3 }; + +/* + * Register bitmap values for the filter gain. The index in this array is the + * corresponding index in d3323aa_filter_gain for the gain value. + */ +static const u8 d3323aa_filter_gain_regval[] = { 1, 3, 0 }; + +struct d3323aa_data { + struct completion reset_completion; + /* + * Since the setup process always requires a complete write of _all_ + * the state variables, we need to synchronize them with a lock. + */ + struct mutex statevar_lock; + + struct device *dev; + + /* Supply voltage. */ + struct regulator *regulator_vdd; + /* Input clock or output detection signal (Vout). */ + struct gpio_desc *gpiod_clkin_detectout; + /* Input (setting) or output data. */ + struct gpio_desc *gpiod_data; + + /* + * We only need the low-pass cutoff frequency to unambiguously choose + * the type of band-pass filter. For example, both filter type B and C + * have 0.3 Hz as high-pass cutoff frequency (see + * d3323aa_hp_filter_freq). + */ + size_t lp_filter_freq_idx; + size_t filter_gain_idx; + u8 detect_thresh; + u8 irq_reset_count; + + /* Indicator for operational mode (configuring or detecting). */ + bool detecting; +}; + +static int d3323aa_read_settings(struct iio_dev *indio_dev, + unsigned long *regbitmap) +{ + struct d3323aa_data *data = iio_priv(indio_dev); + size_t i; + int ret; + + /* Bit bang the clock and data pins. */ + ret = gpiod_direction_output(data->gpiod_clkin_detectout, 0); + if (ret) + return ret; + + ret = gpiod_direction_input(data->gpiod_data); + if (ret) + return ret; + + dev_dbg(data->dev, "Reading settings...\n"); + + for (i = 0; i < D3323AA_REG_NR_BITS; ++i) { + /* Clock frequency needs to be 1 kHz. */ + gpiod_set_value(data->gpiod_clkin_detectout, 1); + udelay(500); + + /* The data seems to change when clock signal is high. */ + if (gpiod_get_value(data->gpiod_data)) + set_bit(i, regbitmap); + + gpiod_set_value(data->gpiod_clkin_detectout, 0); + udelay(500); + } + + /* The first bit (F37) is just dummy data. Discard it. */ + clear_bit(0, regbitmap); + + /* Datasheet says to wait 30 ms after reading the settings. */ + msleep(30); + + return 0; +} + +static int d3323aa_write_settings(struct iio_dev *indio_dev, + unsigned long *written_regbitmap) +{ +#define REGBITMAP_LEN \ + (D3323AA_REG_NR_BITS + D3323AA_SETTING_END_PATTERN_NR_BITS) + DECLARE_BITMAP(regbitmap, REGBITMAP_LEN); + struct d3323aa_data *data = iio_priv(indio_dev); + size_t i; + int ret; + + /* Build the register bitmap. */ + bitmap_zero(regbitmap, REGBITMAP_LEN); + bitmap_write(regbitmap, data->detect_thresh, D3323AA_REG_BIT_DETLVLABS0, + D3323AA_REG_BIT_DETLVLABS7 - D3323AA_REG_BIT_DETLVLABS0 + + 1); + bitmap_write(regbitmap, + d3323aa_filter_gain_regval[data->filter_gain_idx], + D3323AA_REG_BIT_FSTEP0, + D3323AA_REG_BIT_FSTEP1 - D3323AA_REG_BIT_FSTEP0 + 1); + bitmap_write(regbitmap, + d3323aa_lp_filter_regval[data->lp_filter_freq_idx], + D3323AA_REG_BIT_FILSEL0, + D3323AA_REG_BIT_FILSEL2 - D3323AA_REG_BIT_FILSEL0 + 1); + /* Compulsory end pattern. */ + bitmap_write(regbitmap, D3323AA_SETTING_END_PATTERN, + D3323AA_REG_NR_BITS, D3323AA_SETTING_END_PATTERN_NR_BITS); + + /* Bit bang the clock and data pins. */ + ret = gpiod_direction_output(data->gpiod_clkin_detectout, 0); + if (ret) + return ret; + + ret = gpiod_direction_output(data->gpiod_data, 0); + if (ret) + return ret; + + dev_dbg(data->dev, "Writing settings...\n"); + + /* First bit (F37) is not used when writing the register bitmap. */ + for (i = 1; i < REGBITMAP_LEN; ++i) { + gpiod_set_value(data->gpiod_data, test_bit(i, regbitmap)); + + /* Clock frequency needs to be 1 kHz. */ + gpiod_set_value(data->gpiod_clkin_detectout, 1); + udelay(500); + gpiod_set_value(data->gpiod_clkin_detectout, 0); + udelay(500); + } + + /* Datasheet says to wait 30 ms after writing the settings. */ + msleep(30); + + bitmap_copy(written_regbitmap, regbitmap, D3323AA_REG_NR_BITS); + + return 0; +} + +static irqreturn_t d3323aa_irq_handler(int irq, void *dev_id) +{ + struct iio_dev *indio_dev = dev_id; + struct d3323aa_data *data = iio_priv(indio_dev); + enum iio_event_direction dir; + int val; + + val = gpiod_get_value(data->gpiod_clkin_detectout); + if (val < 0) { + dev_err_ratelimited(data->dev, + "Could not read from GPIO vout-clk (%d)\n", + val); + return IRQ_HANDLED; + } + + if (!data->detecting) { + /* Reset interrupt counting falling edges. */ + if (!val && ++data->irq_reset_count == D3323AA_IRQ_RESET_COUNT) + complete(&data->reset_completion); + + return IRQ_HANDLED; + } + + /* Detection interrupt. */ + dir = val ? IIO_EV_DIR_RISING : IIO_EV_DIR_FALLING; + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, dir), + iio_get_time_ns(indio_dev)); + + return IRQ_HANDLED; +} + +static int d3323aa_reset(struct iio_dev *indio_dev) +{ + struct d3323aa_data *data = iio_priv(indio_dev); + long time; + int ret; + + /* During probe() the regulator may already be disabled. */ + if (regulator_is_enabled(data->regulator_vdd)) { + ret = regulator_disable(data->regulator_vdd); + if (ret) + return ret; + } + + /* + * Datasheet says VDD needs to be low at least for 30 ms. Let's add a + * couple more to allow VDD to completely discharge as well. + */ + fsleep((30 + 5) * USEC_PER_MSEC); + + /* + * When later enabling VDD, the device will signal with + * D3323AA_IRQ_RESET_COUNT falling edges on Vout/CLK that it is now + * ready for configuration. Datasheet says that this should happen + * within D3323AA_RESET_TIMEOUT ms. Count these two edges within that + * timeout. + */ + data->irq_reset_count = 0; + reinit_completion(&data->reset_completion); + data->detecting = false; + + ret = gpiod_direction_input(data->gpiod_clkin_detectout); + if (ret) + return ret; + + dev_dbg(data->dev, "Resetting...\n"); + + ret = regulator_enable(data->regulator_vdd); + if (ret) + return ret; + + /* + * Wait for VDD to completely charge up. Measurements have shown that + * Vout/CLK signal slowly ramps up during this period. Thus, the digital + * signal will have bogus values. It is therefore necessary to wait + * before we can count the "real" falling edges. + */ + fsleep(2000); + + time = wait_for_completion_killable_timeout( + &data->reset_completion, + msecs_to_jiffies(D3323AA_RESET_TIMEOUT)); + if (time == 0) { + return -ETIMEDOUT; + } else if (time < 0) { + /* Got interrupted. */ + return time; + } + + dev_dbg(data->dev, "Reset completed\n"); + + return 0; +} + +static int d3323aa_setup(struct iio_dev *indio_dev, size_t lp_filter_freq_idx, + size_t filter_gain_idx, u8 detect_thresh) +{ + DECLARE_BITMAP(write_regbitmap, D3323AA_REG_NR_BITS); + DECLARE_BITMAP(read_regbitmap, D3323AA_REG_NR_BITS); + struct d3323aa_data *data = iio_priv(indio_dev); + unsigned long start_time; + int ret; + + ret = d3323aa_reset(indio_dev); + if (ret) { + if (ret != -ERESTARTSYS) + dev_err(data->dev, "Could not reset device (%d)\n", + ret); + + return ret; + } + + /* + * Datasheet says to wait 10 us before setting the configuration. + * Moreover, the total configuration should be done within + * D3323AA_CONFIG_TIMEOUT ms. Clock it. + */ + fsleep(10); + start_time = jiffies; + + ret = d3323aa_write_settings(indio_dev, write_regbitmap); + if (ret) { + dev_err(data->dev, "Could not write settings (%d)\n", ret); + return ret; + } + + ret = d3323aa_read_settings(indio_dev, read_regbitmap); + if (ret) { + dev_err(data->dev, "Could not read settings (%d)\n", ret); + return ret; + } + + if (time_is_before_jiffies(start_time + + msecs_to_jiffies(D3323AA_CONFIG_TIMEOUT))) { + dev_err(data->dev, "Could not set up configuration in time\n"); + return -EAGAIN; + } + + /* Check if settings were set successfully. */ + if (!bitmap_equal(write_regbitmap, read_regbitmap, + D3323AA_REG_NR_BITS)) { + dev_err(data->dev, "Settings data mismatch\n"); + return -EIO; + } + + /* Now in operational mode. */ + ret = gpiod_direction_input(data->gpiod_clkin_detectout); + if (ret) { + dev_err(data->dev, + "Could not set GPIO vout-clk as input (%d)\n", ret); + return ret; + } + + ret = gpiod_direction_input(data->gpiod_data); + if (ret) { + dev_err(data->dev, "Could not set GPIO data as input (%d)\n", + ret); + return ret; + } + + data->lp_filter_freq_idx = lp_filter_freq_idx; + data->filter_gain_idx = filter_gain_idx; + data->detect_thresh = detect_thresh; + data->detecting = true; + + dev_dbg(data->dev, "Setup done\n"); + + return 0; +} + +static int d3323aa_set_lp_filter_freq(struct iio_dev *indio_dev, const int val, + int val2) +{ + struct d3323aa_data *data = iio_priv(indio_dev); + size_t idx; + + /* Truncate fractional part to one digit. */ + val2 /= 100000; + + for (idx = 0; idx < ARRAY_SIZE(d3323aa_lp_filter_freq); ++idx) { + int integer = d3323aa_lp_filter_freq[idx][0] / + d3323aa_lp_filter_freq[idx][1]; + int fract = d3323aa_lp_filter_freq[idx][0] % + d3323aa_lp_filter_freq[idx][1]; + + if (val == integer && val2 == fract) + break; + } + + if (idx == ARRAY_SIZE(d3323aa_lp_filter_freq)) + return -EINVAL; + + return d3323aa_setup(indio_dev, idx, data->filter_gain_idx, + data->detect_thresh); +} + +static int d3323aa_set_hp_filter_freq(struct iio_dev *indio_dev, const int val, + int val2) +{ + struct d3323aa_data *data = iio_priv(indio_dev); + size_t idx; + + /* Truncate fractional part to two digits. */ + val2 /= 10000; + + for (idx = 0; idx < ARRAY_SIZE(d3323aa_hp_filter_freq); ++idx) { + int integer = d3323aa_hp_filter_freq[idx][0] / + d3323aa_hp_filter_freq[idx][1]; + int fract = d3323aa_hp_filter_freq[idx][0] % + d3323aa_hp_filter_freq[idx][1]; + + if (val == integer && val2 == fract) + break; + } + + if (idx == ARRAY_SIZE(d3323aa_hp_filter_freq)) + return -EINVAL; + + if (idx == data->lp_filter_freq_idx) { + /* Corresponding filter frequency already set. */ + return 0; + } + + if (idx == 1 && data->lp_filter_freq_idx == 2) { + /* + * The low-pass cutoff frequency is the only way to + * unambiguously choose the type of band-pass filter. For + * example, both filter type B (index 1) and C (index 2) have + * 0.3 Hz as high-pass cutoff frequency (see + * d3323aa_hp_filter_freq). Therefore, if one of these are + * requested _and_ the corresponding low-pass filter frequency + * is already set, we can't know which filter type is the wanted + * one. The low-pass filter frequency is the decider (i.e. in + * this case index 2). + */ + return 0; + } + + return d3323aa_setup(indio_dev, idx, data->filter_gain_idx, + data->detect_thresh); +} + +static int d3323aa_set_filter_gain(struct iio_dev *indio_dev, const int val) +{ + struct d3323aa_data *data = iio_priv(indio_dev); + size_t idx; + + for (idx = 0; idx < ARRAY_SIZE(d3323aa_filter_gain); ++idx) { + if (d3323aa_filter_gain[idx] == val) + break; + } + + if (idx == ARRAY_SIZE(d3323aa_filter_gain)) + return -EINVAL; + + return d3323aa_setup(indio_dev, data->lp_filter_freq_idx, idx, + data->detect_thresh); +} + +static int d3323aa_set_threshold(struct iio_dev *indio_dev, const int val) +{ + struct d3323aa_data *data = iio_priv(indio_dev); + + if (val > ((1 << D3323AA_THRESH_REG_NR_BITS) - 1)) + return -EINVAL; + + return d3323aa_setup(indio_dev, data->lp_filter_freq_idx, + data->filter_gain_idx, val); +} + +static int d3323aa_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: + *vals = (int *)d3323aa_hp_filter_freq; + *type = IIO_VAL_FRACTIONAL; + *length = 2 * ARRAY_SIZE(d3323aa_hp_filter_freq); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + *vals = (int *)d3323aa_lp_filter_freq; + *type = IIO_VAL_FRACTIONAL; + *length = 2 * ARRAY_SIZE(d3323aa_lp_filter_freq); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_HARDWAREGAIN: + *vals = (int *)d3323aa_filter_gain; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(d3323aa_filter_gain); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int d3323aa_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct d3323aa_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->statevar_lock); + + switch (mask) { + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: + *val = d3323aa_hp_filter_freq[data->lp_filter_freq_idx][0]; + *val2 = d3323aa_hp_filter_freq[data->lp_filter_freq_idx][1]; + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + *val = d3323aa_lp_filter_freq[data->lp_filter_freq_idx][0]; + *val2 = d3323aa_lp_filter_freq[data->lp_filter_freq_idx][1]; + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_HARDWAREGAIN: + *val = d3323aa_filter_gain[data->filter_gain_idx]; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int d3323aa_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct d3323aa_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->statevar_lock); + + switch (mask) { + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: + return d3323aa_set_hp_filter_freq(indio_dev, val, val2); + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return d3323aa_set_lp_filter_freq(indio_dev, val, val2); + case IIO_CHAN_INFO_HARDWAREGAIN: + return d3323aa_set_filter_gain(indio_dev, val); + default: + return -EINVAL; + } +} + +static int d3323aa_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, int *val2) +{ + struct d3323aa_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->statevar_lock); + + switch (info) { + case IIO_EV_INFO_VALUE: + *val = data->detect_thresh; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int d3323aa_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, int val2) +{ + struct d3323aa_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->statevar_lock); + + switch (info) { + case IIO_EV_INFO_VALUE: + return d3323aa_set_threshold(indio_dev, val); + default: + return -EINVAL; + } +} + +static const struct iio_info d3323aa_info = { + .read_avail = d3323aa_read_avail, + .read_raw = d3323aa_read_raw, + .write_raw = d3323aa_write_raw, + .read_event_value = d3323aa_read_event, + .write_event_value = d3323aa_write_event, +}; + +static const struct iio_event_spec d3323aa_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, +}; + +static const struct iio_chan_spec d3323aa_channels[] = { + { + .type = IIO_PROXIMITY, + .info_mask_separate = + BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY) | + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | + BIT(IIO_CHAN_INFO_HARDWAREGAIN), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY) | + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | + BIT(IIO_CHAN_INFO_HARDWAREGAIN), + .event_spec = d3323aa_event_spec, + .num_event_specs = ARRAY_SIZE(d3323aa_event_spec), + }, +}; + +static void d3323aa_disable_regulator(void *indata) +{ + struct d3323aa_data *data = indata; + int ret; + + /* + * During probe() the regulator may be disabled. It is enabled during + * device setup (in d3323aa_reset(), where it is also briefly disabled). + * The check is therefore needed in order to have balanced + * regulator_enable/disable() calls. + */ + if (!regulator_is_enabled(data->regulator_vdd)) + return; + + ret = regulator_disable(data->regulator_vdd); + if (ret) + dev_err(data->dev, "Could not disable regulator (%d)\n", ret); +} + +static int d3323aa_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct d3323aa_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->dev = dev; + + init_completion(&data->reset_completion); + + ret = devm_mutex_init(dev, &data->statevar_lock); + if (ret) + return dev_err_probe(dev, ret, "Could not initialize mutex\n"); + + data->regulator_vdd = devm_regulator_get_exclusive(dev, "vdd"); + if (IS_ERR(data->regulator_vdd)) + return dev_err_probe(dev, PTR_ERR(data->regulator_vdd), + "Could not get regulator\n"); + + /* + * The regulator will be enabled for the first time during the + * device setup below (in d3323aa_reset()). However parameter changes + * from userspace can require a temporary disable of the regulator. + * To avoid complex handling of state, use a callback that will disable + * the regulator if it happens to be enabled at time of devm unwind. + */ + ret = devm_add_action_or_reset(dev, d3323aa_disable_regulator, data); + if (ret) + return ret; + + data->gpiod_clkin_detectout = + devm_gpiod_get(dev, "vout-clk", GPIOD_OUT_LOW); + if (IS_ERR(data->gpiod_clkin_detectout)) + return dev_err_probe(dev, PTR_ERR(data->gpiod_clkin_detectout), + "Could not get GPIO vout-clk\n"); + + data->gpiod_data = devm_gpiod_get(dev, "data", GPIOD_OUT_LOW); + if (IS_ERR(data->gpiod_data)) + return dev_err_probe(dev, PTR_ERR(data->gpiod_data), + "Could not get GPIO data\n"); + + ret = gpiod_to_irq(data->gpiod_clkin_detectout); + if (ret < 0) + return dev_err_probe(dev, ret, "Could not get IRQ\n"); + + /* + * Device signals with a rising or falling detection signal when the + * proximity data is above or below the threshold, respectively. + */ + ret = devm_request_irq(dev, ret, d3323aa_irq_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + dev_name(dev), indio_dev); + if (ret) + return ret; + + ret = d3323aa_setup(indio_dev, D3323AA_LP_FILTER_FREQ_DEFAULT_IDX, + D3323AA_FILTER_GAIN_DEFAULT_IDX, + D3323AA_THRESH_DEFAULT_VAL); + if (ret) + return ret; + + indio_dev->info = &d3323aa_info; + indio_dev->name = "d3323aa"; + indio_dev->channels = d3323aa_channels; + indio_dev->num_channels = ARRAY_SIZE(d3323aa_channels); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, + "Could not register iio device\n"); + + return 0; +} + +static const struct of_device_id d3323aa_of_match[] = { + { + .compatible = "nicera,d3323aa", + }, + { } +}; +MODULE_DEVICE_TABLE(of, d3323aa_of_match); + +static struct platform_driver d3323aa_driver = { + .probe = d3323aa_probe, + .driver = { + .name = "d3323aa", + .of_match_table = d3323aa_of_match, + }, +}; +module_platform_driver(d3323aa_driver); + +MODULE_AUTHOR("Waqar Hameed "); +MODULE_DESCRIPTION("Nicera D3-323-AA PIR sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/proximity/hx9023s.c b/drivers/iio/proximity/hx9023s.c new file mode 100644 index 000000000..0199a6351 --- /dev/null +++ b/drivers/iio/proximity/hx9023s.c @@ -0,0 +1,1226 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024 NanjingTianyihexin Electronics Ltd. + * http://www.tianyihexin.com + * + * Driver for NanjingTianyihexin HX9023S Cap Sensor. + * Datasheet available at: + * http://www.tianyihexin.com/ueditor/php/upload/file/20240614/1718336303992081.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define HX9023S_CHIP_ID 0x1D +#define HX9023S_CH_NUM 5 +#define HX9023S_POS 0x03 +#define HX9023S_NEG 0x02 +#define HX9023S_NOT_CONNECTED 16 + +#define HX9023S_GLOBAL_CTRL0 0x00 +#define HX9023S_PRF_CFG 0x02 +#define HX9023S_CH0_CFG_7_0 0x03 +#define HX9023S_CH4_CFG_9_8 0x0C +#define HX9023S_RANGE_7_0 0x0D +#define HX9023S_RANGE_9_8 0x0E +#define HX9023S_RANGE_18_16 0x0F +#define HX9023S_AVG0_NOSR0_CFG 0x10 +#define HX9023S_NOSR12_CFG 0x11 +#define HX9023S_NOSR34_CFG 0x12 +#define HX9023S_AVG12_CFG 0x13 +#define HX9023S_AVG34_CFG 0x14 +#define HX9023S_OFFSET_DAC0_7_0 0x15 +#define HX9023S_OFFSET_DAC4_9_8 0x1E +#define HX9023S_SAMPLE_NUM_7_0 0x1F +#define HX9023S_INTEGRATION_NUM_7_0 0x21 +#define HX9023S_CH_NUM_CFG 0x24 +#define HX9023S_LP_ALP_4_CFG 0x29 +#define HX9023S_LP_ALP_1_0_CFG 0x2A +#define HX9023S_LP_ALP_3_2_CFG 0x2B +#define HX9023S_UP_ALP_1_0_CFG 0x2C +#define HX9023S_UP_ALP_3_2_CFG 0x2D +#define HX9023S_DN_UP_ALP_0_4_CFG 0x2E +#define HX9023S_DN_ALP_2_1_CFG 0x2F +#define HX9023S_DN_ALP_4_3_CFG 0x30 +#define HX9023S_RAW_BL_RD_CFG 0x38 +#define HX9023S_INTERRUPT_CFG 0x39 +#define HX9023S_INTERRUPT_CFG1 0x3A +#define HX9023S_CALI_DIFF_CFG 0x3B +#define HX9023S_DITHER_CFG 0x3C +#define HX9023S_DEVICE_ID 0x60 +#define HX9023S_PROX_STATUS 0x6B +#define HX9023S_PROX_INT_HIGH_CFG 0x6C +#define HX9023S_PROX_INT_LOW_CFG 0x6D +#define HX9023S_PROX_HIGH_DIFF_CFG_CH0_0 0x80 +#define HX9023S_PROX_LOW_DIFF_CFG_CH0_0 0x88 +#define HX9023S_PROX_LOW_DIFF_CFG_CH3_1 0x8F +#define HX9023S_PROX_HIGH_DIFF_CFG_CH4_0 0x9E +#define HX9023S_PROX_HIGH_DIFF_CFG_CH4_1 0x9F +#define HX9023S_PROX_LOW_DIFF_CFG_CH4_0 0xA2 +#define HX9023S_PROX_LOW_DIFF_CFG_CH4_1 0xA3 +#define HX9023S_CAP_INI_CH4_0 0xB3 +#define HX9023S_LP_DIFF_CH4_2 0xBA +#define HX9023S_RAW_BL_CH4_0 0xB5 +#define HX9023S_LP_DIFF_CH4_0 0xB8 +#define HX9023S_DSP_CONFIG_CTRL1 0xC8 +#define HX9023S_CAP_INI_CH0_0 0xE0 +#define HX9023S_RAW_BL_CH0_0 0xE8 +#define HX9023S_LP_DIFF_CH0_0 0xF4 +#define HX9023S_LP_DIFF_CH3_2 0xFF + +#define HX9023S_DATA_LOCK_MASK BIT(4) +#define HX9023S_INTERRUPT_MASK GENMASK(9, 0) +#define HX9023S_PROX_DEBOUNCE_MASK GENMASK(3, 0) + +#define FW_VER_OFFSET 2 +#define FW_REG_CNT_OFFSET 3 +#define FW_DATA_OFFSET 16 + +struct hx9023s_bin { + u16 reg_count; + u16 fw_size; + u8 fw_ver; + u8 data[] __counted_by(fw_size); +}; + +struct hx9023s_ch_data { + s16 raw; /* Raw Data*/ + s16 lp; /* Low Pass Filter Data*/ + s16 bl; /* Base Line Data */ + s16 diff; /* Difference of Low Pass Data and Base Line Data */ + + struct { + unsigned int near; + unsigned int far; + } thres; + + u16 dac; + u8 channel_positive; + u8 channel_negative; + bool sel_bl; + bool sel_raw; + bool sel_diff; + bool sel_lp; + bool enable; +}; + +struct hx9023s_data { + struct iio_trigger *trig; + struct regmap *regmap; + unsigned long chan_prox_stat; + unsigned long chan_read; + unsigned long chan_event; + unsigned long ch_en_stat; + unsigned long chan_in_use; + unsigned int prox_state_reg; + bool trigger_enabled; + + struct { + __le16 channels[HX9023S_CH_NUM]; + aligned_s64 ts; + } buffer; + + /* + * Serialize access to registers below: + * HX9023S_PROX_INT_LOW_CFG, + * HX9023S_PROX_INT_HIGH_CFG, + * HX9023S_INTERRUPT_CFG, + * HX9023S_CH_NUM_CFG + * Serialize access to channel configuration in + * hx9023s_push_events and hx9023s_trigger_handler. + */ + struct mutex mutex; + struct hx9023s_ch_data ch_data[HX9023S_CH_NUM]; +}; + +static const struct reg_sequence hx9023s_reg_init_list[] = { + /* scan period */ + REG_SEQ0(HX9023S_PRF_CFG, 0x17), + + /* full scale of conversion phase of each channel */ + REG_SEQ0(HX9023S_RANGE_7_0, 0x11), + REG_SEQ0(HX9023S_RANGE_9_8, 0x02), + REG_SEQ0(HX9023S_RANGE_18_16, 0x00), + + /* ADC average number and OSR number of each channel */ + REG_SEQ0(HX9023S_AVG0_NOSR0_CFG, 0x71), + REG_SEQ0(HX9023S_NOSR12_CFG, 0x44), + REG_SEQ0(HX9023S_NOSR34_CFG, 0x00), + REG_SEQ0(HX9023S_AVG12_CFG, 0x33), + REG_SEQ0(HX9023S_AVG34_CFG, 0x00), + + /* sample & integration frequency of the ADC */ + REG_SEQ0(HX9023S_SAMPLE_NUM_7_0, 0x65), + REG_SEQ0(HX9023S_INTEGRATION_NUM_7_0, 0x65), + + /* coefficient of the first order low pass filter during each channel */ + REG_SEQ0(HX9023S_LP_ALP_1_0_CFG, 0x22), + REG_SEQ0(HX9023S_LP_ALP_3_2_CFG, 0x22), + REG_SEQ0(HX9023S_LP_ALP_4_CFG, 0x02), + + /* up coefficient of the first order low pass filter during each channel */ + REG_SEQ0(HX9023S_UP_ALP_1_0_CFG, 0x88), + REG_SEQ0(HX9023S_UP_ALP_3_2_CFG, 0x88), + REG_SEQ0(HX9023S_DN_UP_ALP_0_4_CFG, 0x18), + + /* down coefficient of the first order low pass filter during each channel */ + REG_SEQ0(HX9023S_DN_ALP_2_1_CFG, 0x11), + REG_SEQ0(HX9023S_DN_ALP_4_3_CFG, 0x11), + + /* selection of data for the Data Mux Register to output data */ + REG_SEQ0(HX9023S_RAW_BL_RD_CFG, 0xF0), + + /* enable the interrupt function */ + REG_SEQ0(HX9023S_INTERRUPT_CFG, 0xFF), + REG_SEQ0(HX9023S_INTERRUPT_CFG1, 0x3B), + REG_SEQ0(HX9023S_DITHER_CFG, 0x21), + + /* threshold of the offset compensation */ + REG_SEQ0(HX9023S_CALI_DIFF_CFG, 0x07), + + /* proximity persistency number(near & far) */ + REG_SEQ0(HX9023S_PROX_INT_HIGH_CFG, 0x01), + REG_SEQ0(HX9023S_PROX_INT_LOW_CFG, 0x01), + + /* disable the data lock */ + REG_SEQ0(HX9023S_DSP_CONFIG_CTRL1, 0x00), +}; + +static const struct iio_event_spec hx9023s_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_all = BIT(IIO_EV_INFO_PERIOD), + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_shared_by_all = BIT(IIO_EV_INFO_PERIOD), + .mask_separate = BIT(IIO_EV_INFO_VALUE), + + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +#define HX9023S_CHANNEL(idx) \ +{ \ + .type = IIO_PROXIMITY, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\ + .indexed = 1, \ + .channel = idx, \ + .address = 0, \ + .event_spec = hx9023s_events, \ + .num_event_specs = ARRAY_SIZE(hx9023s_events), \ + .scan_index = idx, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ +} + +static const struct iio_chan_spec hx9023s_channels[] = { + HX9023S_CHANNEL(0), + HX9023S_CHANNEL(1), + HX9023S_CHANNEL(2), + HX9023S_CHANNEL(3), + HX9023S_CHANNEL(4), + IIO_CHAN_SOFT_TIMESTAMP(5), +}; + +static const unsigned int hx9023s_samp_freq_table[] = { + 2, 2, 4, 6, 8, 10, 14, 18, 22, 26, + 30, 34, 38, 42, 46, 50, 56, 62, 68, 74, + 80, 90, 100, 200, 300, 400, 600, 800, 1000, 2000, + 3000, 4000, +}; + +static const struct regmap_range hx9023s_rd_reg_ranges[] = { + regmap_reg_range(HX9023S_GLOBAL_CTRL0, HX9023S_LP_DIFF_CH3_2), +}; + +static const struct regmap_range hx9023s_wr_reg_ranges[] = { + regmap_reg_range(HX9023S_GLOBAL_CTRL0, HX9023S_LP_DIFF_CH3_2), +}; + +static const struct regmap_range hx9023s_volatile_reg_ranges[] = { + regmap_reg_range(HX9023S_CAP_INI_CH4_0, HX9023S_LP_DIFF_CH4_2), + regmap_reg_range(HX9023S_CAP_INI_CH0_0, HX9023S_LP_DIFF_CH3_2), + regmap_reg_range(HX9023S_PROX_STATUS, HX9023S_PROX_STATUS), +}; + +static const struct regmap_access_table hx9023s_rd_regs = { + .yes_ranges = hx9023s_rd_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(hx9023s_rd_reg_ranges), +}; + +static const struct regmap_access_table hx9023s_wr_regs = { + .yes_ranges = hx9023s_wr_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(hx9023s_wr_reg_ranges), +}; + +static const struct regmap_access_table hx9023s_volatile_regs = { + .yes_ranges = hx9023s_volatile_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(hx9023s_volatile_reg_ranges), +}; + +static const struct regmap_config hx9023s_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_MAPLE, + .rd_table = &hx9023s_rd_regs, + .wr_table = &hx9023s_wr_regs, + .volatile_table = &hx9023s_volatile_regs, +}; + +static int hx9023s_interrupt_enable(struct hx9023s_data *data) +{ + return regmap_update_bits(data->regmap, HX9023S_INTERRUPT_CFG, + HX9023S_INTERRUPT_MASK, HX9023S_INTERRUPT_MASK); +} + +static int hx9023s_interrupt_disable(struct hx9023s_data *data) +{ + return regmap_update_bits(data->regmap, HX9023S_INTERRUPT_CFG, + HX9023S_INTERRUPT_MASK, 0x00); +} + +static int hx9023s_data_lock(struct hx9023s_data *data, bool locked) +{ + if (locked) + return regmap_update_bits(data->regmap, + HX9023S_DSP_CONFIG_CTRL1, + HX9023S_DATA_LOCK_MASK, + HX9023S_DATA_LOCK_MASK); + else + return regmap_update_bits(data->regmap, + HX9023S_DSP_CONFIG_CTRL1, + HX9023S_DATA_LOCK_MASK, 0); +} + +static int hx9023s_ch_cfg(struct hx9023s_data *data) +{ + __le16 reg_list[HX9023S_CH_NUM]; + u8 ch_pos[HX9023S_CH_NUM]; + u8 ch_neg[HX9023S_CH_NUM]; + /* Bit positions corresponding to input pin connections */ + u8 conn_cs[HX9023S_CH_NUM] = { 0, 2, 4, 6, 8 }; + unsigned int i; + u16 reg; + + for (i = 0; i < HX9023S_CH_NUM; i++) { + ch_pos[i] = data->ch_data[i].channel_positive == HX9023S_NOT_CONNECTED ? + HX9023S_NOT_CONNECTED : conn_cs[data->ch_data[i].channel_positive]; + ch_neg[i] = data->ch_data[i].channel_negative == HX9023S_NOT_CONNECTED ? + HX9023S_NOT_CONNECTED : conn_cs[data->ch_data[i].channel_negative]; + + reg = (HX9023S_POS << ch_pos[i]) | (HX9023S_NEG << ch_neg[i]); + reg_list[i] = cpu_to_le16(reg); + } + + return regmap_bulk_write(data->regmap, HX9023S_CH0_CFG_7_0, reg_list, + sizeof(reg_list)); +} + +static int hx9023s_write_far_debounce(struct hx9023s_data *data, int val) +{ + guard(mutex)(&data->mutex); + return regmap_update_bits(data->regmap, HX9023S_PROX_INT_LOW_CFG, + HX9023S_PROX_DEBOUNCE_MASK, + FIELD_GET(HX9023S_PROX_DEBOUNCE_MASK, val)); +} + +static int hx9023s_write_near_debounce(struct hx9023s_data *data, int val) +{ + guard(mutex)(&data->mutex); + return regmap_update_bits(data->regmap, HX9023S_PROX_INT_HIGH_CFG, + HX9023S_PROX_DEBOUNCE_MASK, + FIELD_GET(HX9023S_PROX_DEBOUNCE_MASK, val)); +} + +static int hx9023s_read_far_debounce(struct hx9023s_data *data, int *val) +{ + int ret; + + ret = regmap_read(data->regmap, HX9023S_PROX_INT_LOW_CFG, val); + if (ret) + return ret; + + *val = FIELD_GET(HX9023S_PROX_DEBOUNCE_MASK, *val); + + return IIO_VAL_INT; +} + +static int hx9023s_read_near_debounce(struct hx9023s_data *data, int *val) +{ + int ret; + + ret = regmap_read(data->regmap, HX9023S_PROX_INT_HIGH_CFG, val); + if (ret) + return ret; + + *val = FIELD_GET(HX9023S_PROX_DEBOUNCE_MASK, *val); + + return IIO_VAL_INT; +} + +static int hx9023s_get_thres_near(struct hx9023s_data *data, u8 ch, int *val) +{ + int ret; + __le16 buf; + unsigned int reg, tmp; + + reg = (ch == 4) ? HX9023S_PROX_HIGH_DIFF_CFG_CH4_0 : + HX9023S_PROX_HIGH_DIFF_CFG_CH0_0 + (ch * 2); + + ret = regmap_bulk_read(data->regmap, reg, &buf, sizeof(buf)); + if (ret) + return ret; + + tmp = (le16_to_cpu(buf) & GENMASK(9, 0)) * 32; + data->ch_data[ch].thres.near = tmp; + *val = tmp; + + return IIO_VAL_INT; +} + +static int hx9023s_get_thres_far(struct hx9023s_data *data, u8 ch, int *val) +{ + int ret; + __le16 buf; + unsigned int reg, tmp; + + reg = (ch == 4) ? HX9023S_PROX_LOW_DIFF_CFG_CH4_0 : + HX9023S_PROX_LOW_DIFF_CFG_CH0_0 + (ch * 2); + + ret = regmap_bulk_read(data->regmap, reg, &buf, sizeof(buf)); + if (ret) + return ret; + + tmp = (le16_to_cpu(buf) & GENMASK(9, 0)) * 32; + data->ch_data[ch].thres.far = tmp; + *val = tmp; + + return IIO_VAL_INT; +} + +static int hx9023s_set_thres_near(struct hx9023s_data *data, u8 ch, int val) +{ + __le16 val_le16 = cpu_to_le16((val / 32) & GENMASK(9, 0)); + unsigned int reg; + + data->ch_data[ch].thres.near = ((val / 32) & GENMASK(9, 0)) * 32; + reg = (ch == 4) ? HX9023S_PROX_HIGH_DIFF_CFG_CH4_0 : + HX9023S_PROX_HIGH_DIFF_CFG_CH0_0 + (ch * 2); + + return regmap_bulk_write(data->regmap, reg, &val_le16, sizeof(val_le16)); +} + +static int hx9023s_set_thres_far(struct hx9023s_data *data, u8 ch, int val) +{ + __le16 val_le16 = cpu_to_le16((val / 32) & GENMASK(9, 0)); + unsigned int reg; + + data->ch_data[ch].thres.far = ((val / 32) & GENMASK(9, 0)) * 32; + reg = (ch == 4) ? HX9023S_PROX_LOW_DIFF_CFG_CH4_0 : + HX9023S_PROX_LOW_DIFF_CFG_CH0_0 + (ch * 2); + + return regmap_bulk_write(data->regmap, reg, &val_le16, sizeof(val_le16)); +} + +static int hx9023s_get_prox_state(struct hx9023s_data *data) +{ + return regmap_read(data->regmap, HX9023S_PROX_STATUS, &data->prox_state_reg); +} + +static int hx9023s_data_select(struct hx9023s_data *data) +{ + int ret; + unsigned int i, buf; + unsigned long tmp; + + ret = regmap_read(data->regmap, HX9023S_RAW_BL_RD_CFG, &buf); + if (ret) + return ret; + + tmp = buf; + for (i = 0; i < 4; i++) { + data->ch_data[i].sel_diff = test_bit(i, &tmp); + data->ch_data[i].sel_lp = !data->ch_data[i].sel_diff; + data->ch_data[i].sel_bl = test_bit(i + 4, &tmp); + data->ch_data[i].sel_raw = !data->ch_data[i].sel_bl; + } + + ret = regmap_read(data->regmap, HX9023S_INTERRUPT_CFG1, &buf); + if (ret) + return ret; + + tmp = buf; + data->ch_data[4].sel_diff = test_bit(2, &tmp); + data->ch_data[4].sel_lp = !data->ch_data[4].sel_diff; + data->ch_data[4].sel_bl = test_bit(3, &tmp); + data->ch_data[4].sel_raw = !data->ch_data[4].sel_bl; + + return 0; +} + +static int hx9023s_sample(struct hx9023s_data *data) +{ + int ret; + unsigned int i; + u8 buf[HX9023S_CH_NUM * 3]; + u16 value; + + ret = hx9023s_data_lock(data, true); + if (ret) + return ret; + + ret = hx9023s_data_select(data); + if (ret) + goto err; + + /* 3 bytes for each of channels 0 to 3 which have contiguous registers */ + ret = regmap_bulk_read(data->regmap, HX9023S_RAW_BL_CH0_0, buf, 12); + if (ret) + goto err; + + /* 3 bytes for channel 4 */ + ret = regmap_bulk_read(data->regmap, HX9023S_RAW_BL_CH4_0, buf + 12, 3); + if (ret) + goto err; + + for (i = 0; i < HX9023S_CH_NUM; i++) { + value = get_unaligned_le16(&buf[i * 3 + 1]); + data->ch_data[i].raw = 0; + data->ch_data[i].bl = 0; + if (data->ch_data[i].sel_raw) + data->ch_data[i].raw = value; + if (data->ch_data[i].sel_bl) + data->ch_data[i].bl = value; + } + + /* 3 bytes for each of channels 0 to 3 which have contiguous registers */ + ret = regmap_bulk_read(data->regmap, HX9023S_LP_DIFF_CH0_0, buf, 12); + if (ret) + goto err; + + /* 3 bytes for channel 4 */ + ret = regmap_bulk_read(data->regmap, HX9023S_LP_DIFF_CH4_0, buf + 12, 3); + if (ret) + goto err; + + for (i = 0; i < HX9023S_CH_NUM; i++) { + value = get_unaligned_le16(&buf[i * 3 + 1]); + data->ch_data[i].lp = 0; + data->ch_data[i].diff = 0; + if (data->ch_data[i].sel_lp) + data->ch_data[i].lp = value; + if (data->ch_data[i].sel_diff) + data->ch_data[i].diff = value; + } + + for (i = 0; i < HX9023S_CH_NUM; i++) { + if (data->ch_data[i].sel_lp && data->ch_data[i].sel_bl) + data->ch_data[i].diff = data->ch_data[i].lp - data->ch_data[i].bl; + } + + /* 2 bytes for each of channels 0 to 4 which have contiguous registers */ + ret = regmap_bulk_read(data->regmap, HX9023S_OFFSET_DAC0_7_0, buf, 10); + if (ret) + goto err; + + for (i = 0; i < HX9023S_CH_NUM; i++) { + value = get_unaligned_le16(&buf[i * 2]); + value = FIELD_GET(GENMASK(11, 0), value); + data->ch_data[i].dac = value; + } + +err: + return hx9023s_data_lock(data, false); +} + +static int hx9023s_ch_en(struct hx9023s_data *data, u8 ch_id, bool en) +{ + int ret; + unsigned int buf; + + ret = regmap_read(data->regmap, HX9023S_CH_NUM_CFG, &buf); + if (ret) + return ret; + + data->ch_en_stat = buf; + if (en && data->ch_en_stat == 0) + data->prox_state_reg = 0; + + data->ch_data[ch_id].enable = en; + __assign_bit(ch_id, &data->ch_en_stat, en); + + return regmap_write(data->regmap, HX9023S_CH_NUM_CFG, data->ch_en_stat); +} + +static int hx9023s_property_get(struct hx9023s_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + u32 array[2]; + u32 i, reg, temp; + int ret; + + data->chan_in_use = 0; + for (i = 0; i < HX9023S_CH_NUM; i++) { + data->ch_data[i].channel_positive = HX9023S_NOT_CONNECTED; + data->ch_data[i].channel_negative = HX9023S_NOT_CONNECTED; + } + + device_for_each_child_node_scoped(dev, child) { + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret || reg >= HX9023S_CH_NUM) + return dev_err_probe(dev, ret < 0 ? ret : -EINVAL, + "Failed to read reg\n"); + __set_bit(reg, &data->chan_in_use); + + ret = fwnode_property_read_u32(child, "single-channel", &temp); + if (ret == 0) { + data->ch_data[reg].channel_positive = temp; + data->ch_data[reg].channel_negative = HX9023S_NOT_CONNECTED; + } else { + ret = fwnode_property_read_u32_array(child, "diff-channels", + array, ARRAY_SIZE(array)); + if (ret == 0) { + data->ch_data[reg].channel_positive = array[0]; + data->ch_data[reg].channel_negative = array[1]; + } else { + return dev_err_probe(dev, ret, + "Property read failed: %d\n", + reg); + } + } + } + + return 0; +} + +static int hx9023s_update_chan_en(struct hx9023s_data *data, + unsigned long chan_read, + unsigned long chan_event) +{ + unsigned int i; + unsigned long channels = chan_read | chan_event; + + if ((data->chan_read | data->chan_event) != channels) { + for_each_set_bit(i, &channels, HX9023S_CH_NUM) + hx9023s_ch_en(data, i, test_bit(i, &data->chan_in_use)); + for_each_clear_bit(i, &channels, HX9023S_CH_NUM) + hx9023s_ch_en(data, i, false); + } + + data->chan_read = chan_read; + data->chan_event = chan_event; + + return 0; +} + +static int hx9023s_get_proximity(struct hx9023s_data *data, + const struct iio_chan_spec *chan, + int *val) +{ + int ret; + + ret = hx9023s_sample(data); + if (ret) + return ret; + + ret = hx9023s_get_prox_state(data); + if (ret) + return ret; + + *val = data->ch_data[chan->channel].diff; + return IIO_VAL_INT; +} + +static int hx9023s_get_samp_freq(struct hx9023s_data *data, int *val, int *val2) +{ + int ret; + unsigned int odr, index; + + ret = regmap_read(data->regmap, HX9023S_PRF_CFG, &index); + if (ret) + return ret; + + odr = hx9023s_samp_freq_table[index]; + *val = KILO / odr; + *val2 = div_u64((KILO % odr) * MICRO, odr); + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int hx9023s_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct hx9023s_data *data = iio_priv(indio_dev); + int ret; + + if (chan->type != IIO_PROXIMITY) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = hx9023s_get_proximity(data, chan, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + return hx9023s_get_samp_freq(data, val, val2); + default: + return -EINVAL; + } +} + +static int hx9023s_set_samp_freq(struct hx9023s_data *data, int val, int val2) +{ + struct device *dev = regmap_get_device(data->regmap); + unsigned int i, period_ms; + + if (!val && !val2) + return -EINVAL; + + period_ms = div_u64(NANO, (val * MEGA + val2)); + + for (i = 0; i < ARRAY_SIZE(hx9023s_samp_freq_table); i++) { + if (period_ms == hx9023s_samp_freq_table[i]) + break; + } + if (i == ARRAY_SIZE(hx9023s_samp_freq_table)) { + dev_err(dev, "Period:%dms NOT found!\n", period_ms); + return -EINVAL; + } + + return regmap_write(data->regmap, HX9023S_PRF_CFG, i); +} + +static int hx9023s_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int val, int val2, long mask) +{ + struct hx9023s_data *data = iio_priv(indio_dev); + + if (chan->type != IIO_PROXIMITY) + return -EINVAL; + + if (mask != IIO_CHAN_INFO_SAMP_FREQ) + return -EINVAL; + + return hx9023s_set_samp_freq(data, val, val2); +} + +static irqreturn_t hx9023s_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct hx9023s_data *data = iio_priv(indio_dev); + + if (data->trigger_enabled) + iio_trigger_poll(data->trig); + + return IRQ_WAKE_THREAD; +} + +static void hx9023s_push_events(struct iio_dev *indio_dev) +{ + struct hx9023s_data *data = iio_priv(indio_dev); + s64 timestamp = iio_get_time_ns(indio_dev); + unsigned long prox_changed; + unsigned int chan; + int ret; + + ret = hx9023s_sample(data); + if (ret) + return; + + ret = hx9023s_get_prox_state(data); + if (ret) + return; + + prox_changed = (data->chan_prox_stat ^ data->prox_state_reg) & data->chan_event; + for_each_set_bit(chan, &prox_changed, HX9023S_CH_NUM) { + unsigned int dir; + + dir = (data->prox_state_reg & BIT(chan)) ? + IIO_EV_DIR_FALLING : IIO_EV_DIR_RISING; + + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, chan, + IIO_EV_TYPE_THRESH, dir), + timestamp); + } + data->chan_prox_stat = data->prox_state_reg; +} + +static irqreturn_t hx9023s_irq_thread_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct hx9023s_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->mutex); + hx9023s_push_events(indio_dev); + + return IRQ_HANDLED; +} + +static int hx9023s_read_event_val(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, int *val2) +{ + struct hx9023s_data *data = iio_priv(indio_dev); + + if (chan->type != IIO_PROXIMITY) + return -EINVAL; + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + return hx9023s_get_thres_far(data, chan->channel, val); + case IIO_EV_DIR_FALLING: + return hx9023s_get_thres_near(data, chan->channel, val); + default: + return -EINVAL; + } + case IIO_EV_INFO_PERIOD: + switch (dir) { + case IIO_EV_DIR_RISING: + return hx9023s_read_far_debounce(data, val); + case IIO_EV_DIR_FALLING: + return hx9023s_read_near_debounce(data, val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int hx9023s_write_event_val(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, int val2) +{ + struct hx9023s_data *data = iio_priv(indio_dev); + + if (chan->type != IIO_PROXIMITY) + return -EINVAL; + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + return hx9023s_set_thres_far(data, chan->channel, val); + case IIO_EV_DIR_FALLING: + return hx9023s_set_thres_near(data, chan->channel, val); + default: + return -EINVAL; + } + case IIO_EV_INFO_PERIOD: + switch (dir) { + case IIO_EV_DIR_RISING: + return hx9023s_write_far_debounce(data, val); + case IIO_EV_DIR_FALLING: + return hx9023s_write_near_debounce(data, val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int hx9023s_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct hx9023s_data *data = iio_priv(indio_dev); + + return test_bit(chan->channel, &data->chan_event); +} + +static int hx9023s_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct hx9023s_data *data = iio_priv(indio_dev); + + if (test_bit(chan->channel, &data->chan_in_use)) { + hx9023s_ch_en(data, chan->channel, state); + __assign_bit(chan->channel, &data->chan_event, + data->ch_data[chan->channel].enable); + } + + return 0; +} + +static const struct iio_info hx9023s_info = { + .read_raw = hx9023s_read_raw, + .write_raw = hx9023s_write_raw, + .read_event_value = hx9023s_read_event_val, + .write_event_value = hx9023s_write_event_val, + .read_event_config = hx9023s_read_event_config, + .write_event_config = hx9023s_write_event_config, +}; + +static int hx9023s_set_trigger_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct hx9023s_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->mutex); + if (state) + hx9023s_interrupt_enable(data); + else if (!data->chan_read) + hx9023s_interrupt_disable(data); + data->trigger_enabled = state; + + return 0; +} + +static const struct iio_trigger_ops hx9023s_trigger_ops = { + .set_trigger_state = hx9023s_set_trigger_state, +}; + +static irqreturn_t hx9023s_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct hx9023s_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); + unsigned int bit, index, i = 0; + int ret; + + guard(mutex)(&data->mutex); + ret = hx9023s_sample(data); + if (ret) { + dev_warn(dev, "sampling failed\n"); + goto out; + } + + ret = hx9023s_get_prox_state(data); + if (ret) { + dev_warn(dev, "get prox failed\n"); + goto out; + } + + iio_for_each_active_channel(indio_dev, bit) { + index = indio_dev->channels[bit].channel; + data->buffer.channels[i++] = cpu_to_le16(data->ch_data[index].diff); + } + + iio_push_to_buffers_with_ts(indio_dev, &data->buffer, + sizeof(data->buffer), pf->timestamp); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int hx9023s_buffer_preenable(struct iio_dev *indio_dev) +{ + struct hx9023s_data *data = iio_priv(indio_dev); + unsigned long channels = 0; + unsigned int bit; + + guard(mutex)(&data->mutex); + iio_for_each_active_channel(indio_dev, bit) + __set_bit(indio_dev->channels[bit].channel, &channels); + + hx9023s_update_chan_en(data, channels, data->chan_event); + + return 0; +} + +static int hx9023s_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct hx9023s_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->mutex); + hx9023s_update_chan_en(data, 0, data->chan_event); + + return 0; +} + +static const struct iio_buffer_setup_ops hx9023s_buffer_setup_ops = { + .preenable = hx9023s_buffer_preenable, + .postdisable = hx9023s_buffer_postdisable, +}; + +static int hx9023s_id_check(struct iio_dev *indio_dev) +{ + struct hx9023s_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); + unsigned int id; + int ret; + + ret = regmap_read(data->regmap, HX9023S_DEVICE_ID, &id); + if (ret) + return ret; + + if (id != HX9023S_CHIP_ID) + dev_warn(dev, "Unexpected chip ID, assuming compatible\n"); + + return 0; +} + +static int hx9023s_bin_load(struct hx9023s_data *data, struct hx9023s_bin *bin) +{ + u8 *cfg_start = bin->data + FW_DATA_OFFSET; + u8 addr, val; + u16 i; + int ret; + + for (i = 0; i < bin->reg_count; i++) { + addr = cfg_start[i * 2]; + val = cfg_start[i * 2 + 1]; + ret = regmap_write(data->regmap, addr, val); + if (ret < 0) + return ret; + } + + return 0; +} + +static int hx9023s_send_cfg(const struct firmware *fw, struct hx9023s_data *data) +{ + struct hx9023s_bin *bin __free(kfree) = + kzalloc(fw->size + sizeof(*bin), GFP_KERNEL); + if (!bin) + return -ENOMEM; + + bin->fw_size = fw->size; + memcpy(bin->data, fw->data, bin->fw_size); + bin->fw_ver = bin->data[FW_VER_OFFSET]; + bin->reg_count = get_unaligned_le16(bin->data + FW_REG_CNT_OFFSET); + + release_firmware(fw); + + return hx9023s_bin_load(data, bin); +} + +static void hx9023s_cfg_update(const struct firmware *fw, void *context) +{ + struct hx9023s_data *data = context; + struct device *dev = regmap_get_device(data->regmap); + int ret; + + if (!fw || !fw->data) { + dev_warn(dev, "No firmware\n"); + goto no_fw; + } + + ret = hx9023s_send_cfg(fw, data); + if (ret) { + dev_warn(dev, "Firmware update failed: %d\n", ret); + goto no_fw; + } + + ret = regcache_sync(data->regmap); + if (ret) + dev_err(dev, "regcache sync failed\n"); + + return; + +no_fw: + ret = regmap_multi_reg_write(data->regmap, hx9023s_reg_init_list, + ARRAY_SIZE(hx9023s_reg_init_list)); + if (ret) { + dev_err(dev, "Error loading default configuration\n"); + return; + } + + ret = regcache_sync(data->regmap); + if (ret) + dev_err(dev, "regcache sync failed\n"); +} + +static int hx9023s_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct hx9023s_data *data; + const char *fw_name; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + mutex_init(&data->mutex); + + data->regmap = devm_regmap_init_i2c(client, &hx9023s_regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "regmap init failed\n"); + + ret = hx9023s_property_get(data); + if (ret) + return dev_err_probe(dev, ret, "dts phase failed\n"); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "regulator get failed\n"); + + ret = hx9023s_id_check(indio_dev); + if (ret) + return dev_err_probe(dev, ret, "id check failed\n"); + + indio_dev->name = "hx9023s"; + indio_dev->channels = hx9023s_channels; + indio_dev->num_channels = ARRAY_SIZE(hx9023s_channels); + indio_dev->info = &hx9023s_info; + indio_dev->modes = INDIO_DIRECT_MODE; + i2c_set_clientdata(client, indio_dev); + + ret = hx9023s_ch_cfg(data); + if (ret) + return dev_err_probe(dev, ret, "channel config failed\n"); + + fw_name = "hx9023s.bin"; + device_property_read_string(dev, "firmware-name", &fw_name); + ret = request_firmware_nowait(THIS_MODULE, true, fw_name, dev, + GFP_KERNEL, data, hx9023s_cfg_update); + if (ret) + return dev_err_probe(dev, ret, "reg config failed\n"); + + if (client->irq) { + ret = devm_request_threaded_irq(dev, client->irq, + hx9023s_irq_handler, + hx9023s_irq_thread_handler, + IRQF_ONESHOT, + "hx9023s_event", indio_dev); + if (ret) + return ret; + + data->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->trig) + return -ENOMEM; + + data->trig->ops = &hx9023s_trigger_ops; + iio_trigger_set_drvdata(data->trig, indio_dev); + + ret = devm_iio_trigger_register(dev, data->trig); + if (ret) + return dev_err_probe(dev, ret, + "iio trigger register failed\n"); + } + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + hx9023s_trigger_handler, + &hx9023s_buffer_setup_ops); + if (ret) + return dev_err_probe(dev, ret, + "iio triggered buffer setup failed\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static int hx9023s_suspend(struct device *dev) +{ + struct hx9023s_data *data = iio_priv(dev_get_drvdata(dev)); + + guard(mutex)(&data->mutex); + hx9023s_interrupt_disable(data); + + return 0; +} + +static int hx9023s_resume(struct device *dev) +{ + struct hx9023s_data *data = iio_priv(dev_get_drvdata(dev)); + + guard(mutex)(&data->mutex); + if (data->trigger_enabled) + hx9023s_interrupt_enable(data); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(hx9023s_pm_ops, hx9023s_suspend, + hx9023s_resume); + +static const struct of_device_id hx9023s_of_match[] = { + { .compatible = "tyhx,hx9023s" }, + { } +}; +MODULE_DEVICE_TABLE(of, hx9023s_of_match); + +static const struct i2c_device_id hx9023s_id[] = { + { .name = "hx9023s" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, hx9023s_id); + +static struct i2c_driver hx9023s_driver = { + .driver = { + .name = "hx9023s", + .of_match_table = hx9023s_of_match, + .pm = &hx9023s_pm_ops, + + /* + * The I2C operations in hx9023s_reg_init() and hx9023s_ch_cfg() + * are time-consuming. Prefer async so we don't delay boot + * if we're builtin to the kernel. + */ + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .probe = hx9023s_probe, + .id_table = hx9023s_id, +}; +module_i2c_driver(hx9023s_driver); + +MODULE_AUTHOR("Yasin Lee "); +MODULE_DESCRIPTION("Driver for TYHX HX9023S SAR sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/proximity/irsd200.c b/drivers/iio/proximity/irsd200.c new file mode 100644 index 000000000..b3db7f3a4 --- /dev/null +++ b/drivers/iio/proximity/irsd200.c @@ -0,0 +1,955 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Murata IRS-D200 PIR sensor. + * + * Copyright (C) 2023 Axis Communications AB + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define IRS_DRV_NAME "irsd200" + +/* Registers. */ +#define IRS_REG_OP 0x00 /* Operation mode. */ +#define IRS_REG_DATA_LO 0x02 /* Sensor data LSB. */ +#define IRS_REG_DATA_HI 0x03 /* Sensor data MSB. */ +#define IRS_REG_STATUS 0x04 /* Interrupt status. */ +#define IRS_REG_COUNT 0x05 /* Count of exceeding threshold. */ +#define IRS_REG_DATA_RATE 0x06 /* Output data rate. */ +#define IRS_REG_FILTER 0x07 /* High-pass and low-pass filter. */ +#define IRS_REG_INTR 0x09 /* Interrupt mode. */ +#define IRS_REG_NR_COUNT 0x0a /* Number of counts before interrupt. */ +#define IRS_REG_THR_HI 0x0b /* Upper threshold. */ +#define IRS_REG_THR_LO 0x0c /* Lower threshold. */ +#define IRS_REG_TIMER_LO 0x0d /* Timer setting LSB. */ +#define IRS_REG_TIMER_HI 0x0e /* Timer setting MSB. */ + +/* Interrupt status bits. */ +#define IRS_INTR_DATA 0 /* Data update. */ +#define IRS_INTR_TIMER 1 /* Timer expiration. */ +#define IRS_INTR_COUNT_THR_AND 2 /* Count "AND" threshold. */ +#define IRS_INTR_COUNT_THR_OR 3 /* Count "OR" threshold. */ + +/* Operation states. */ +#define IRS_OP_ACTIVE 0x00 +#define IRS_OP_SLEEP 0x01 + +/* + * Quantization scale value for threshold. Used for conversion from/to register + * value. + */ +#define IRS_THR_QUANT_SCALE 128 + +#define IRS_UPPER_COUNT(count) FIELD_GET(GENMASK(7, 4), count) +#define IRS_LOWER_COUNT(count) FIELD_GET(GENMASK(3, 0), count) + +/* Index corresponds to the value of IRS_REG_DATA_RATE register. */ +static const int irsd200_data_rates[] = { + 50, + 100, +}; + +/* Index corresponds to the (field) value of IRS_REG_FILTER register. */ +static const unsigned int irsd200_lp_filter_freq[] = { + 10, + 7, +}; + +/* + * Index corresponds to the (field) value of IRS_REG_FILTER register. Note that + * this represents a fractional value (e.g the first value corresponds to 3 / 10 + * = 0.3 Hz). + */ +static const unsigned int irsd200_hp_filter_freq[][2] = { + { 3, 10 }, + { 5, 10 }, +}; + +/* Register fields. */ +enum irsd200_regfield { + /* Data interrupt. */ + IRS_REGF_INTR_DATA, + /* Timer interrupt. */ + IRS_REGF_INTR_TIMER, + /* AND count threshold interrupt. */ + IRS_REGF_INTR_COUNT_THR_AND, + /* OR count threshold interrupt. */ + IRS_REGF_INTR_COUNT_THR_OR, + + /* Low-pass filter frequency. */ + IRS_REGF_LP_FILTER, + /* High-pass filter frequency. */ + IRS_REGF_HP_FILTER, + + /* Sentinel value. */ + IRS_REGF_MAX +}; + +static const struct reg_field irsd200_regfields[] = { + [IRS_REGF_INTR_DATA] = + REG_FIELD(IRS_REG_INTR, IRS_INTR_DATA, IRS_INTR_DATA), + [IRS_REGF_INTR_TIMER] = + REG_FIELD(IRS_REG_INTR, IRS_INTR_TIMER, IRS_INTR_TIMER), + [IRS_REGF_INTR_COUNT_THR_AND] = REG_FIELD( + IRS_REG_INTR, IRS_INTR_COUNT_THR_AND, IRS_INTR_COUNT_THR_AND), + [IRS_REGF_INTR_COUNT_THR_OR] = REG_FIELD( + IRS_REG_INTR, IRS_INTR_COUNT_THR_OR, IRS_INTR_COUNT_THR_OR), + + [IRS_REGF_LP_FILTER] = REG_FIELD(IRS_REG_FILTER, 1, 1), + [IRS_REGF_HP_FILTER] = REG_FIELD(IRS_REG_FILTER, 0, 0), +}; + +static const struct regmap_config irsd200_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = IRS_REG_TIMER_HI, +}; + +struct irsd200_data { + struct regmap *regmap; + struct regmap_field *regfields[IRS_REGF_MAX]; + struct device *dev; +}; + +static int irsd200_setup(struct irsd200_data *data) +{ + unsigned int val; + int ret; + + /* Disable all interrupt sources. */ + ret = regmap_write(data->regmap, IRS_REG_INTR, 0); + if (ret) { + dev_err(data->dev, "Could not set interrupt sources (%d)\n", + ret); + return ret; + } + + /* Set operation to active. */ + ret = regmap_write(data->regmap, IRS_REG_OP, IRS_OP_ACTIVE); + if (ret) { + dev_err(data->dev, "Could not set operation mode (%d)\n", ret); + return ret; + } + + /* Clear threshold count. */ + ret = regmap_read(data->regmap, IRS_REG_COUNT, &val); + if (ret) { + dev_err(data->dev, "Could not clear threshold count (%d)\n", + ret); + return ret; + } + + /* Clear status. */ + ret = regmap_write(data->regmap, IRS_REG_STATUS, 0x0f); + if (ret) { + dev_err(data->dev, "Could not clear status (%d)\n", ret); + return ret; + } + + return 0; +} + +static int irsd200_read_threshold(struct irsd200_data *data, + enum iio_event_direction dir, int *val) +{ + unsigned int regval; + unsigned int reg; + int scale; + int ret; + + /* Set quantization scale. */ + if (dir == IIO_EV_DIR_RISING) { + scale = IRS_THR_QUANT_SCALE; + reg = IRS_REG_THR_HI; + } else if (dir == IIO_EV_DIR_FALLING) { + scale = -IRS_THR_QUANT_SCALE; + reg = IRS_REG_THR_LO; + } else { + return -EINVAL; + } + + ret = regmap_read(data->regmap, reg, ®val); + if (ret) { + dev_err(data->dev, "Could not read threshold (%d)\n", ret); + return ret; + } + + *val = ((int)regval) * scale; + + return 0; +} + +static int irsd200_write_threshold(struct irsd200_data *data, + enum iio_event_direction dir, int val) +{ + unsigned int regval; + unsigned int reg; + int scale; + int ret; + + /* Set quantization scale. */ + if (dir == IIO_EV_DIR_RISING) { + if (val < 0) + return -ERANGE; + + scale = IRS_THR_QUANT_SCALE; + reg = IRS_REG_THR_HI; + } else if (dir == IIO_EV_DIR_FALLING) { + if (val > 0) + return -ERANGE; + + scale = -IRS_THR_QUANT_SCALE; + reg = IRS_REG_THR_LO; + } else { + return -EINVAL; + } + + regval = val / scale; + + if (regval >= BIT(8)) + return -ERANGE; + + ret = regmap_write(data->regmap, reg, regval); + if (ret) { + dev_err(data->dev, "Could not write threshold (%d)\n", ret); + return ret; + } + + return 0; +} + +static int irsd200_read_data(struct irsd200_data *data, s16 *val) +{ + __le16 buf; + int ret; + + ret = regmap_bulk_read(data->regmap, IRS_REG_DATA_LO, &buf, + sizeof(buf)); + if (ret) { + dev_err(data->dev, "Could not bulk read data (%d)\n", ret); + return ret; + } + + *val = le16_to_cpu(buf); + + return 0; +} + +static int irsd200_read_data_rate(struct irsd200_data *data, int *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, IRS_REG_DATA_RATE, ®val); + if (ret) { + dev_err(data->dev, "Could not read data rate (%d)\n", ret); + return ret; + } + + if (regval >= ARRAY_SIZE(irsd200_data_rates)) + return -ERANGE; + + *val = irsd200_data_rates[regval]; + + return 0; +} + +static int irsd200_write_data_rate(struct irsd200_data *data, int val) +{ + size_t idx; + int ret; + + for (idx = 0; idx < ARRAY_SIZE(irsd200_data_rates); ++idx) { + if (irsd200_data_rates[idx] == val) + break; + } + + if (idx == ARRAY_SIZE(irsd200_data_rates)) + return -ERANGE; + + ret = regmap_write(data->regmap, IRS_REG_DATA_RATE, idx); + if (ret) { + dev_err(data->dev, "Could not write data rate (%d)\n", ret); + return ret; + } + + /* + * Data sheet says the device needs 3 seconds of settling time. The + * device operates normally during this period though. This is more of a + * "guarantee" than trying to prevent other user space reads/writes. + */ + ssleep(3); + + return 0; +} + +static int irsd200_read_timer(struct irsd200_data *data, int *val, int *val2) +{ + __le16 buf; + int ret; + + ret = regmap_bulk_read(data->regmap, IRS_REG_TIMER_LO, &buf, + sizeof(buf)); + if (ret) { + dev_err(data->dev, "Could not bulk read timer (%d)\n", ret); + return ret; + } + + ret = irsd200_read_data_rate(data, val2); + if (ret) + return ret; + + *val = le16_to_cpu(buf); + + return 0; +} + +static int irsd200_write_timer(struct irsd200_data *data, int val, int val2) +{ + unsigned int regval; + int data_rate; + __le16 buf; + int ret; + + if (val < 0 || val2 < 0) + return -ERANGE; + + ret = irsd200_read_data_rate(data, &data_rate); + if (ret) + return ret; + + /* Quantize from seconds. */ + regval = val * data_rate + (val2 * data_rate) / 1000000; + + /* Value is 10 bits. */ + if (regval >= BIT(10)) + return -ERANGE; + + buf = cpu_to_le16((u16)regval); + + ret = regmap_bulk_write(data->regmap, IRS_REG_TIMER_LO, &buf, + sizeof(buf)); + if (ret) { + dev_err(data->dev, "Could not bulk write timer (%d)\n", ret); + return ret; + } + + return 0; +} + +static int irsd200_read_nr_count(struct irsd200_data *data, int *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, IRS_REG_NR_COUNT, ®val); + if (ret) { + dev_err(data->dev, "Could not read nr count (%d)\n", ret); + return ret; + } + + *val = regval; + + return 0; +} + +static int irsd200_write_nr_count(struct irsd200_data *data, int val) +{ + unsigned int regval; + int ret; + + /* A value of zero means that IRS_REG_STATUS is never set. */ + if (val <= 0 || val >= 8) + return -ERANGE; + + regval = val; + + if (regval >= 2) { + /* + * According to the data sheet, timer must be also set in this + * case (i.e. be non-zero). Check and enforce that. + */ + ret = irsd200_read_timer(data, &val, &val); + if (ret) + return ret; + + if (val == 0) { + dev_err(data->dev, + "Timer must be non-zero when nr count is %u\n", + regval); + return -EPERM; + } + } + + ret = regmap_write(data->regmap, IRS_REG_NR_COUNT, regval); + if (ret) { + dev_err(data->dev, "Could not write nr count (%d)\n", ret); + return ret; + } + + return 0; +} + +static int irsd200_read_lp_filter(struct irsd200_data *data, int *val) +{ + unsigned int regval; + int ret; + + ret = regmap_field_read(data->regfields[IRS_REGF_LP_FILTER], ®val); + if (ret) { + dev_err(data->dev, "Could not read lp filter frequency (%d)\n", + ret); + return ret; + } + + *val = irsd200_lp_filter_freq[regval]; + + return 0; +} + +static int irsd200_write_lp_filter(struct irsd200_data *data, int val) +{ + size_t idx; + int ret; + + for (idx = 0; idx < ARRAY_SIZE(irsd200_lp_filter_freq); ++idx) { + if (irsd200_lp_filter_freq[idx] == val) + break; + } + + if (idx == ARRAY_SIZE(irsd200_lp_filter_freq)) + return -ERANGE; + + ret = regmap_field_write(data->regfields[IRS_REGF_LP_FILTER], idx); + if (ret) { + dev_err(data->dev, "Could not write lp filter frequency (%d)\n", + ret); + return ret; + } + + return 0; +} + +static int irsd200_read_hp_filter(struct irsd200_data *data, int *val, + int *val2) +{ + unsigned int regval; + int ret; + + ret = regmap_field_read(data->regfields[IRS_REGF_HP_FILTER], ®val); + if (ret) { + dev_err(data->dev, "Could not read hp filter frequency (%d)\n", + ret); + return ret; + } + + *val = irsd200_hp_filter_freq[regval][0]; + *val2 = irsd200_hp_filter_freq[regval][1]; + + return 0; +} + +static int irsd200_write_hp_filter(struct irsd200_data *data, int val, int val2) +{ + size_t idx; + int ret; + + /* Truncate fractional part to one digit. */ + val2 /= 100000; + + for (idx = 0; idx < ARRAY_SIZE(irsd200_hp_filter_freq); ++idx) { + if (irsd200_hp_filter_freq[idx][0] == val2) + break; + } + + if (idx == ARRAY_SIZE(irsd200_hp_filter_freq) || val != 0) + return -ERANGE; + + ret = regmap_field_write(data->regfields[IRS_REGF_HP_FILTER], idx); + if (ret) { + dev_err(data->dev, "Could not write hp filter frequency (%d)\n", + ret); + return ret; + } + + return 0; +} + +static int irsd200_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct irsd200_data *data = iio_priv(indio_dev); + int ret; + s16 buf; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = irsd200_read_data(data, &buf); + if (ret) + return ret; + + *val = buf; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = irsd200_read_data_rate(data, val); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + ret = irsd200_read_lp_filter(data, val); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: + ret = irsd200_read_hp_filter(data, val, val2); + if (ret) + return ret; + + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } +} + +static int irsd200_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = irsd200_data_rates; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(irsd200_data_rates); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + *vals = irsd200_lp_filter_freq; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(irsd200_lp_filter_freq); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: + *vals = (int *)irsd200_hp_filter_freq; + *type = IIO_VAL_FRACTIONAL; + *length = 2 * ARRAY_SIZE(irsd200_hp_filter_freq); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int irsd200_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct irsd200_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return irsd200_write_data_rate(data, val); + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return irsd200_write_lp_filter(data, val); + case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: + return irsd200_write_hp_filter(data, val, val2); + default: + return -EINVAL; + } +} + +static int irsd200_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, int *val2) +{ + struct irsd200_data *data = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + ret = irsd200_read_threshold(data, dir, val); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_EV_INFO_RUNNING_PERIOD: + ret = irsd200_read_timer(data, val, val2); + if (ret) + return ret; + + return IIO_VAL_FRACTIONAL; + case IIO_EV_INFO_RUNNING_COUNT: + ret = irsd200_read_nr_count(data, val); + if (ret) + return ret; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int irsd200_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, int val2) +{ + struct irsd200_data *data = iio_priv(indio_dev); + + switch (info) { + case IIO_EV_INFO_VALUE: + return irsd200_write_threshold(data, dir, val); + case IIO_EV_INFO_RUNNING_PERIOD: + return irsd200_write_timer(data, val, val2); + case IIO_EV_INFO_RUNNING_COUNT: + return irsd200_write_nr_count(data, val); + default: + return -EINVAL; + } +} + +static int irsd200_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct irsd200_data *data = iio_priv(indio_dev); + unsigned int val; + int ret; + + switch (type) { + case IIO_EV_TYPE_THRESH: + ret = regmap_field_read( + data->regfields[IRS_REGF_INTR_COUNT_THR_OR], &val); + if (ret) + return ret; + + return val; + default: + return -EINVAL; + } +} + +static int irsd200_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct irsd200_data *data = iio_priv(indio_dev); + unsigned int tmp; + int ret; + + switch (type) { + case IIO_EV_TYPE_THRESH: + /* Clear the count register (by reading from it). */ + ret = regmap_read(data->regmap, IRS_REG_COUNT, &tmp); + if (ret) + return ret; + + return regmap_field_write( + data->regfields[IRS_REGF_INTR_COUNT_THR_OR], state); + default: + return -EINVAL; + } +} + +static irqreturn_t irsd200_irq_thread(int irq, void *dev_id) +{ + struct iio_dev *indio_dev = dev_id; + struct irsd200_data *data = iio_priv(indio_dev); + enum iio_event_direction dir; + unsigned int lower_count; + unsigned int upper_count; + unsigned int status = 0; + unsigned int source = 0; + unsigned int clear = 0; + unsigned int count = 0; + int ret; + + ret = regmap_read(data->regmap, IRS_REG_INTR, &source); + if (ret) { + dev_err(data->dev, "Could not read interrupt source (%d)\n", + ret); + return IRQ_HANDLED; + } + + ret = regmap_read(data->regmap, IRS_REG_STATUS, &status); + if (ret) { + dev_err(data->dev, "Could not acknowledge interrupt (%d)\n", + ret); + return IRQ_HANDLED; + } + + if (status & BIT(IRS_INTR_DATA) && iio_buffer_enabled(indio_dev)) { + iio_trigger_poll_nested(indio_dev->trig); + clear |= BIT(IRS_INTR_DATA); + } + + if (status & BIT(IRS_INTR_COUNT_THR_OR) && + source & BIT(IRS_INTR_COUNT_THR_OR)) { + /* + * The register value resets to zero after reading. We therefore + * need to read once and manually extract the lower and upper + * count register fields. + */ + ret = regmap_read(data->regmap, IRS_REG_COUNT, &count); + if (ret) + dev_err(data->dev, "Could not read count (%d)\n", ret); + + upper_count = IRS_UPPER_COUNT(count); + lower_count = IRS_LOWER_COUNT(count); + + /* + * We only check the OR mode to be able to push events for + * rising and falling thresholds. AND mode is covered when both + * upper and lower count is non-zero, and is signaled with + * IIO_EV_DIR_EITHER. + */ + if (upper_count && !lower_count) + dir = IIO_EV_DIR_RISING; + else if (!upper_count && lower_count) + dir = IIO_EV_DIR_FALLING; + else + dir = IIO_EV_DIR_EITHER; + + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, dir), + iio_get_time_ns(indio_dev)); + + /* + * The OR mode will always trigger when the AND mode does, but + * not vice versa. However, it seems like the AND bit needs to + * be cleared if data capture _and_ threshold count interrupts + * are desirable, even though it hasn't explicitly been selected + * (with IRS_REG_INTR). Either way, it doesn't hurt... + */ + clear |= BIT(IRS_INTR_COUNT_THR_OR) | + BIT(IRS_INTR_COUNT_THR_AND); + } + + if (!clear) + return IRQ_NONE; + + ret = regmap_write(data->regmap, IRS_REG_STATUS, clear); + if (ret) + dev_err(data->dev, + "Could not clear interrupt status (%d)\n", ret); + + return IRQ_HANDLED; +} + +static irqreturn_t irsd200_trigger_handler(int irq, void *pollf) +{ + struct iio_dev *indio_dev = ((struct iio_poll_func *)pollf)->indio_dev; + struct irsd200_data *data = iio_priv(indio_dev); + struct { + s16 channel; + aligned_s64 ts; + } scan = { }; + int ret; + + ret = irsd200_read_data(data, &scan.channel); + if (ret) + goto end; + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); + +end: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int irsd200_set_trigger_state(struct iio_trigger *trig, bool state) +{ + struct irsd200_data *data = iio_trigger_get_drvdata(trig); + int ret; + + ret = regmap_field_write(data->regfields[IRS_REGF_INTR_DATA], state); + if (ret) { + dev_err(data->dev, "Could not %s data interrupt source (%d)\n", + str_enable_disable(state), ret); + } + + return ret; +} + +static const struct iio_info irsd200_info = { + .read_raw = irsd200_read_raw, + .read_avail = irsd200_read_avail, + .write_raw = irsd200_write_raw, + .read_event_value = irsd200_read_event, + .write_event_value = irsd200_write_event, + .read_event_config = irsd200_read_event_config, + .write_event_config = irsd200_write_event_config, +}; + +static const struct iio_trigger_ops irsd200_trigger_ops = { + .set_trigger_state = irsd200_set_trigger_state, + .validate_device = iio_trigger_validate_own_device, +}; + +static const struct iio_event_spec irsd200_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = + BIT(IIO_EV_INFO_RUNNING_PERIOD) | + BIT(IIO_EV_INFO_RUNNING_COUNT) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec irsd200_channels[] = { + { + .type = IIO_PROXIMITY, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | + BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | + BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY), + .event_spec = irsd200_event_spec, + .num_event_specs = ARRAY_SIZE(irsd200_event_spec), + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, +}; + +static int irsd200_probe(struct i2c_client *client) +{ + struct iio_trigger *trigger; + struct irsd200_data *data; + struct iio_dev *indio_dev; + size_t i; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->dev = &client->dev; + + data->regmap = devm_regmap_init_i2c(client, &irsd200_regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(data->dev, PTR_ERR(data->regmap), + "Could not initialize regmap\n"); + + for (i = 0; i < IRS_REGF_MAX; ++i) { + data->regfields[i] = devm_regmap_field_alloc( + data->dev, data->regmap, irsd200_regfields[i]); + if (IS_ERR(data->regfields[i])) + return dev_err_probe( + data->dev, PTR_ERR(data->regfields[i]), + "Could not allocate register field %zu\n", i); + } + + ret = devm_regulator_get_enable(data->dev, "vdd"); + if (ret) + return dev_err_probe(data->dev, ret, + "Could not get and enable regulator\n"); + + ret = irsd200_setup(data); + if (ret) + return ret; + + indio_dev->info = &irsd200_info; + indio_dev->name = IRS_DRV_NAME; + indio_dev->channels = irsd200_channels; + indio_dev->num_channels = ARRAY_SIZE(irsd200_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + if (!client->irq) + return dev_err_probe(data->dev, -ENXIO, "No irq available\n"); + + ret = devm_iio_triggered_buffer_setup(data->dev, indio_dev, NULL, + irsd200_trigger_handler, NULL); + if (ret) + return dev_err_probe(data->dev, ret, + "Could not setup iio triggered buffer\n"); + + ret = devm_request_threaded_irq(data->dev, client->irq, NULL, + irsd200_irq_thread, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + NULL, indio_dev); + if (ret) + return ret; + + trigger = devm_iio_trigger_alloc(data->dev, "%s-dev%d", indio_dev->name, + iio_device_id(indio_dev)); + if (!trigger) + return -ENOMEM; + + trigger->ops = &irsd200_trigger_ops; + iio_trigger_set_drvdata(trigger, data); + + ret = devm_iio_trigger_register(data->dev, trigger); + if (ret) + return dev_err_probe(data->dev, ret, + "Could not register iio trigger\n"); + + ret = devm_iio_device_register(data->dev, indio_dev); + if (ret) + return dev_err_probe(data->dev, ret, + "Could not register iio device\n"); + + return 0; +} + +static const struct of_device_id irsd200_of_match[] = { + { + .compatible = "murata,irsd200", + }, + { } +}; +MODULE_DEVICE_TABLE(of, irsd200_of_match); + +static struct i2c_driver irsd200_driver = { + .driver = { + .name = IRS_DRV_NAME, + .of_match_table = irsd200_of_match, + }, + .probe = irsd200_probe, +}; +module_i2c_driver(irsd200_driver); + +MODULE_AUTHOR("Waqar Hameed "); +MODULE_DESCRIPTION("Murata IRS-D200 PIR sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/proximity/isl29501.c b/drivers/iio/proximity/isl29501.c new file mode 100644 index 000000000..95fb7238f --- /dev/null +++ b/drivers/iio/proximity/isl29501.c @@ -0,0 +1,1021 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * isl29501.c: ISL29501 Time of Flight sensor driver. + * + * Copyright (C) 2018 + * Author: Mathieu Othacehe + * + * 7-bit I2C slave address: 0x57 + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* Control, setting and status registers */ +#define ISL29501_DEVICE_ID 0x00 +#define ISL29501_ID 0x0A + +/* Sampling control registers */ +#define ISL29501_INTEGRATION_PERIOD 0x10 +#define ISL29501_SAMPLE_PERIOD 0x11 + +/* Closed loop calibration registers */ +#define ISL29501_CROSSTALK_I_MSB 0x24 +#define ISL29501_CROSSTALK_I_LSB 0x25 +#define ISL29501_CROSSTALK_I_EXPONENT 0x26 +#define ISL29501_CROSSTALK_Q_MSB 0x27 +#define ISL29501_CROSSTALK_Q_LSB 0x28 +#define ISL29501_CROSSTALK_Q_EXPONENT 0x29 +#define ISL29501_CROSSTALK_GAIN_MSB 0x2A +#define ISL29501_CROSSTALK_GAIN_LSB 0x2B +#define ISL29501_MAGNITUDE_REF_EXP 0x2C +#define ISL29501_MAGNITUDE_REF_MSB 0x2D +#define ISL29501_MAGNITUDE_REF_LSB 0x2E +#define ISL29501_PHASE_OFFSET_MSB 0x2F +#define ISL29501_PHASE_OFFSET_LSB 0x30 + +/* Analog control registers */ +#define ISL29501_DRIVER_RANGE 0x90 +#define ISL29501_EMITTER_DAC 0x91 + +#define ISL29501_COMMAND_REGISTER 0xB0 + +/* Commands */ +#define ISL29501_EMUL_SAMPLE_START_PIN 0x49 +#define ISL29501_RESET_ALL_REGISTERS 0xD7 +#define ISL29501_RESET_INT_SM 0xD1 + +/* Ambiant light and temperature corrections */ +#define ISL29501_TEMP_REFERENCE 0x31 +#define ISL29501_PHASE_EXPONENT 0x33 +#define ISL29501_TEMP_COEFF_A 0x34 +#define ISL29501_TEMP_COEFF_B 0x39 +#define ISL29501_AMBIANT_COEFF_A 0x36 +#define ISL29501_AMBIANT_COEFF_B 0x3B + +/* Data output registers */ +#define ISL29501_DISTANCE_MSB_DATA 0xD1 +#define ISL29501_DISTANCE_LSB_DATA 0xD2 +#define ISL29501_PRECISION_MSB 0xD3 +#define ISL29501_PRECISION_LSB 0xD4 +#define ISL29501_MAGNITUDE_EXPONENT 0xD5 +#define ISL29501_MAGNITUDE_MSB 0xD6 +#define ISL29501_MAGNITUDE_LSB 0xD7 +#define ISL29501_PHASE_MSB 0xD8 +#define ISL29501_PHASE_LSB 0xD9 +#define ISL29501_I_RAW_EXPONENT 0xDA +#define ISL29501_I_RAW_MSB 0xDB +#define ISL29501_I_RAW_LSB 0xDC +#define ISL29501_Q_RAW_EXPONENT 0xDD +#define ISL29501_Q_RAW_MSB 0xDE +#define ISL29501_Q_RAW_LSB 0xDF +#define ISL29501_DIE_TEMPERATURE 0xE2 +#define ISL29501_AMBIENT_LIGHT 0xE3 +#define ISL29501_GAIN_MSB 0xE6 +#define ISL29501_GAIN_LSB 0xE7 + +#define ISL29501_MAX_EXP_VAL 15 + +#define ISL29501_INT_TIME_AVAILABLE \ + "0.00007 0.00014 0.00028 0.00057 0.00114 " \ + "0.00228 0.00455 0.00910 0.01820 0.03640 " \ + "0.07281 0.14561" + +#define ISL29501_CURRENT_SCALE_AVAILABLE \ + "0.0039 0.0078 0.0118 0.0157 0.0196 " \ + "0.0235 0.0275 0.0314 0.0352 0.0392 " \ + "0.0431 0.0471 0.0510 0.0549 0.0588" + +enum isl29501_correction_coeff { + COEFF_TEMP_A, + COEFF_TEMP_B, + COEFF_LIGHT_A, + COEFF_LIGHT_B, + COEFF_MAX, +}; + +struct isl29501_private { + struct i2c_client *client; + struct mutex lock; + /* Exact representation of correction coefficients. */ + unsigned int shadow_coeffs[COEFF_MAX]; +}; + +enum isl29501_register_name { + REG_DISTANCE, + REG_PHASE, + REG_TEMPERATURE, + REG_AMBIENT_LIGHT, + REG_GAIN, + REG_GAIN_BIAS, + REG_PHASE_EXP, + REG_CALIB_PHASE_TEMP_A, + REG_CALIB_PHASE_TEMP_B, + REG_CALIB_PHASE_LIGHT_A, + REG_CALIB_PHASE_LIGHT_B, + REG_DISTANCE_BIAS, + REG_TEMPERATURE_BIAS, + REG_INT_TIME, + REG_SAMPLE_TIME, + REG_DRIVER_RANGE, + REG_EMITTER_DAC, +}; + +struct isl29501_register_desc { + u8 msb; + u8 lsb; +}; + +static const struct isl29501_register_desc isl29501_registers[] = { + [REG_DISTANCE] = { + .msb = ISL29501_DISTANCE_MSB_DATA, + .lsb = ISL29501_DISTANCE_LSB_DATA, + }, + [REG_PHASE] = { + .msb = ISL29501_PHASE_MSB, + .lsb = ISL29501_PHASE_LSB, + }, + [REG_TEMPERATURE] = { + .lsb = ISL29501_DIE_TEMPERATURE, + }, + [REG_AMBIENT_LIGHT] = { + .lsb = ISL29501_AMBIENT_LIGHT, + }, + [REG_GAIN] = { + .msb = ISL29501_GAIN_MSB, + .lsb = ISL29501_GAIN_LSB, + }, + [REG_GAIN_BIAS] = { + .msb = ISL29501_CROSSTALK_GAIN_MSB, + .lsb = ISL29501_CROSSTALK_GAIN_LSB, + }, + [REG_PHASE_EXP] = { + .lsb = ISL29501_PHASE_EXPONENT, + }, + [REG_CALIB_PHASE_TEMP_A] = { + .lsb = ISL29501_TEMP_COEFF_A, + }, + [REG_CALIB_PHASE_TEMP_B] = { + .lsb = ISL29501_TEMP_COEFF_B, + }, + [REG_CALIB_PHASE_LIGHT_A] = { + .lsb = ISL29501_AMBIANT_COEFF_A, + }, + [REG_CALIB_PHASE_LIGHT_B] = { + .lsb = ISL29501_AMBIANT_COEFF_B, + }, + [REG_DISTANCE_BIAS] = { + .msb = ISL29501_PHASE_OFFSET_MSB, + .lsb = ISL29501_PHASE_OFFSET_LSB, + }, + [REG_TEMPERATURE_BIAS] = { + .lsb = ISL29501_TEMP_REFERENCE, + }, + [REG_INT_TIME] = { + .lsb = ISL29501_INTEGRATION_PERIOD, + }, + [REG_SAMPLE_TIME] = { + .lsb = ISL29501_SAMPLE_PERIOD, + }, + [REG_DRIVER_RANGE] = { + .lsb = ISL29501_DRIVER_RANGE, + }, + [REG_EMITTER_DAC] = { + .lsb = ISL29501_EMITTER_DAC, + }, +}; + +static int isl29501_register_read(struct isl29501_private *isl29501, + enum isl29501_register_name name, + u32 *val) +{ + const struct isl29501_register_desc *reg = &isl29501_registers[name]; + u8 msb = 0, lsb = 0; + s32 ret; + + mutex_lock(&isl29501->lock); + if (reg->msb) { + ret = i2c_smbus_read_byte_data(isl29501->client, reg->msb); + if (ret < 0) + goto err; + msb = ret; + } + + if (reg->lsb) { + ret = i2c_smbus_read_byte_data(isl29501->client, reg->lsb); + if (ret < 0) + goto err; + lsb = ret; + } + mutex_unlock(&isl29501->lock); + + *val = (msb << 8) + lsb; + + return 0; +err: + mutex_unlock(&isl29501->lock); + + return ret; +} + +static u32 isl29501_register_write(struct isl29501_private *isl29501, + enum isl29501_register_name name, + u32 value) +{ + const struct isl29501_register_desc *reg = &isl29501_registers[name]; + int ret; + + if (!reg->msb && value > U8_MAX) + return -ERANGE; + + if (value > U16_MAX) + return -ERANGE; + + mutex_lock(&isl29501->lock); + if (reg->msb) { + ret = i2c_smbus_write_byte_data(isl29501->client, + reg->msb, value >> 8); + if (ret < 0) + goto err; + } + + ret = i2c_smbus_write_byte_data(isl29501->client, reg->lsb, value); + +err: + mutex_unlock(&isl29501->lock); + return ret; +} + +static ssize_t isl29501_read_ext(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct isl29501_private *isl29501 = iio_priv(indio_dev); + enum isl29501_register_name reg = private; + int ret; + u32 value, gain, coeff, exp; + + switch (reg) { + case REG_GAIN: + case REG_GAIN_BIAS: + ret = isl29501_register_read(isl29501, reg, &gain); + if (ret < 0) + return ret; + + value = gain; + break; + case REG_CALIB_PHASE_TEMP_A: + case REG_CALIB_PHASE_TEMP_B: + case REG_CALIB_PHASE_LIGHT_A: + case REG_CALIB_PHASE_LIGHT_B: + ret = isl29501_register_read(isl29501, REG_PHASE_EXP, &exp); + if (ret < 0) + return ret; + + ret = isl29501_register_read(isl29501, reg, &coeff); + if (ret < 0) + return ret; + + value = coeff << exp; + break; + default: + return -EINVAL; + } + + return sprintf(buf, "%u\n", value); +} + +static int isl29501_set_shadow_coeff(struct isl29501_private *isl29501, + enum isl29501_register_name reg, + unsigned int val) +{ + enum isl29501_correction_coeff coeff; + + switch (reg) { + case REG_CALIB_PHASE_TEMP_A: + coeff = COEFF_TEMP_A; + break; + case REG_CALIB_PHASE_TEMP_B: + coeff = COEFF_TEMP_B; + break; + case REG_CALIB_PHASE_LIGHT_A: + coeff = COEFF_LIGHT_A; + break; + case REG_CALIB_PHASE_LIGHT_B: + coeff = COEFF_LIGHT_B; + break; + default: + return -EINVAL; + } + isl29501->shadow_coeffs[coeff] = val; + + return 0; +} + +static int isl29501_write_coeff(struct isl29501_private *isl29501, + enum isl29501_correction_coeff coeff, + int val) +{ + enum isl29501_register_name reg; + + switch (coeff) { + case COEFF_TEMP_A: + reg = REG_CALIB_PHASE_TEMP_A; + break; + case COEFF_TEMP_B: + reg = REG_CALIB_PHASE_TEMP_B; + break; + case COEFF_LIGHT_A: + reg = REG_CALIB_PHASE_LIGHT_A; + break; + case COEFF_LIGHT_B: + reg = REG_CALIB_PHASE_LIGHT_B; + break; + default: + return -EINVAL; + } + + return isl29501_register_write(isl29501, reg, val); +} + +static unsigned int isl29501_find_corr_exp(unsigned int val, + unsigned int max_exp, + unsigned int max_mantissa) +{ + unsigned int exp = 1; + + /* + * Correction coefficients are represented under + * mantissa * 2^exponent form, where mantissa and exponent + * are stored in two separate registers of the sensor. + * + * Compute and return the lowest exponent such as: + * mantissa = value / 2^exponent + * + * where mantissa < max_mantissa. + */ + if (val <= max_mantissa) + return 0; + + while ((val >> exp) > max_mantissa) { + exp++; + + if (exp > max_exp) + return max_exp; + } + + return exp; +} + +static ssize_t isl29501_write_ext(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct isl29501_private *isl29501 = iio_priv(indio_dev); + enum isl29501_register_name reg = private; + unsigned int val; + int max_exp = 0; + int ret; + int i; + + ret = kstrtouint(buf, 10, &val); + if (ret) + return ret; + + switch (reg) { + case REG_GAIN_BIAS: + if (val > U16_MAX) + return -ERANGE; + + ret = isl29501_register_write(isl29501, reg, val); + if (ret < 0) + return ret; + + break; + case REG_CALIB_PHASE_TEMP_A: + case REG_CALIB_PHASE_TEMP_B: + case REG_CALIB_PHASE_LIGHT_A: + case REG_CALIB_PHASE_LIGHT_B: + + if (val > (U8_MAX << ISL29501_MAX_EXP_VAL)) + return -ERANGE; + + /* Store the correction coefficient under its exact form. */ + ret = isl29501_set_shadow_coeff(isl29501, reg, val); + if (ret < 0) + return ret; + + /* + * Find the highest exponent needed to represent + * correction coefficients. + */ + for (i = 0; i < COEFF_MAX; i++) { + int corr; + int corr_exp; + + corr = isl29501->shadow_coeffs[i]; + corr_exp = isl29501_find_corr_exp(corr, + ISL29501_MAX_EXP_VAL, + U8_MAX / 2); + dev_dbg(&isl29501->client->dev, + "found exp of corr(%d) = %d\n", corr, corr_exp); + + max_exp = max(max_exp, corr_exp); + } + + /* + * Represent every correction coefficient under + * mantissa * 2^max_exponent form and force the + * writing of those coefficients on the sensor. + */ + for (i = 0; i < COEFF_MAX; i++) { + int corr; + int mantissa; + + corr = isl29501->shadow_coeffs[i]; + if (!corr) + continue; + + mantissa = corr >> max_exp; + + ret = isl29501_write_coeff(isl29501, i, mantissa); + if (ret < 0) + return ret; + } + + ret = isl29501_register_write(isl29501, REG_PHASE_EXP, max_exp); + if (ret < 0) + return ret; + + break; + default: + return -EINVAL; + } + + return len; +} + +#define _ISL29501_EXT_INFO(_name, _ident) { \ + .name = _name, \ + .read = isl29501_read_ext, \ + .write = isl29501_write_ext, \ + .private = _ident, \ + .shared = IIO_SEPARATE, \ +} + +static const struct iio_chan_spec_ext_info isl29501_ext_info[] = { + _ISL29501_EXT_INFO("agc_gain", REG_GAIN), + _ISL29501_EXT_INFO("agc_gain_bias", REG_GAIN_BIAS), + _ISL29501_EXT_INFO("calib_phase_temp_a", REG_CALIB_PHASE_TEMP_A), + _ISL29501_EXT_INFO("calib_phase_temp_b", REG_CALIB_PHASE_TEMP_B), + _ISL29501_EXT_INFO("calib_phase_light_a", REG_CALIB_PHASE_LIGHT_A), + _ISL29501_EXT_INFO("calib_phase_light_b", REG_CALIB_PHASE_LIGHT_B), + { } +}; + +#define ISL29501_DISTANCE_SCAN_INDEX 0 +#define ISL29501_TIMESTAMP_SCAN_INDEX 1 + +static const struct iio_chan_spec isl29501_channels[] = { + { + .type = IIO_PROXIMITY, + .scan_index = ISL29501_DISTANCE_SCAN_INDEX, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .ext_info = isl29501_ext_info, + }, + { + .type = IIO_PHASE, + .scan_index = -1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_CURRENT, + .scan_index = -1, + .output = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_TEMP, + .scan_index = -1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + }, + { + .type = IIO_INTENSITY, + .scan_index = -1, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_CLEAR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, + IIO_CHAN_SOFT_TIMESTAMP(ISL29501_TIMESTAMP_SCAN_INDEX), +}; + +static int isl29501_reset_registers(struct isl29501_private *isl29501) +{ + int ret; + + ret = i2c_smbus_write_byte_data(isl29501->client, + ISL29501_COMMAND_REGISTER, + ISL29501_RESET_ALL_REGISTERS); + if (ret < 0) { + dev_err(&isl29501->client->dev, + "cannot reset registers %d\n", ret); + return ret; + } + + ret = i2c_smbus_write_byte_data(isl29501->client, + ISL29501_COMMAND_REGISTER, + ISL29501_RESET_INT_SM); + if (ret < 0) + dev_err(&isl29501->client->dev, + "cannot reset state machine %d\n", ret); + + return ret; +} + +static int isl29501_begin_acquisition(struct isl29501_private *isl29501) +{ + int ret; + + ret = i2c_smbus_write_byte_data(isl29501->client, + ISL29501_COMMAND_REGISTER, + ISL29501_EMUL_SAMPLE_START_PIN); + if (ret < 0) + dev_err(&isl29501->client->dev, + "cannot begin acquisition %d\n", ret); + + return ret; +} + +static IIO_CONST_ATTR_INT_TIME_AVAIL(ISL29501_INT_TIME_AVAILABLE); +static IIO_CONST_ATTR(out_current_scale_available, + ISL29501_CURRENT_SCALE_AVAILABLE); + +static struct attribute *isl29501_attributes[] = { + &iio_const_attr_integration_time_available.dev_attr.attr, + &iio_const_attr_out_current_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group isl29501_attribute_group = { + .attrs = isl29501_attributes, +}; + +static const int isl29501_current_scale_table[][2] = { + {0, 3900}, {0, 7800}, {0, 11800}, {0, 15700}, + {0, 19600}, {0, 23500}, {0, 27500}, {0, 31400}, + {0, 35200}, {0, 39200}, {0, 43100}, {0, 47100}, + {0, 51000}, {0, 54900}, {0, 58800}, +}; + +static const int isl29501_int_time[][2] = { + {0, 70}, /* 0.07 ms */ + {0, 140}, /* 0.14 ms */ + {0, 280}, /* 0.28 ms */ + {0, 570}, /* 0.57 ms */ + {0, 1140}, /* 1.14 ms */ + {0, 2280}, /* 2.28 ms */ + {0, 4550}, /* 4.55 ms */ + {0, 9100}, /* 9.11 ms */ + {0, 18200}, /* 18.2 ms */ + {0, 36400}, /* 36.4 ms */ + {0, 72810}, /* 72.81 ms */ + {0, 145610} /* 145.28 ms */ +}; + +static int isl29501_get_raw(struct isl29501_private *isl29501, + const struct iio_chan_spec *chan, + int *raw) +{ + int ret; + + switch (chan->type) { + case IIO_PROXIMITY: + ret = isl29501_register_read(isl29501, REG_DISTANCE, raw); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + case IIO_INTENSITY: + ret = isl29501_register_read(isl29501, + REG_AMBIENT_LIGHT, + raw); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + case IIO_PHASE: + ret = isl29501_register_read(isl29501, REG_PHASE, raw); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + case IIO_CURRENT: + ret = isl29501_register_read(isl29501, REG_EMITTER_DAC, raw); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + case IIO_TEMP: + ret = isl29501_register_read(isl29501, REG_TEMPERATURE, raw); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int isl29501_get_scale(struct isl29501_private *isl29501, + const struct iio_chan_spec *chan, + int *val, int *val2) +{ + int ret; + u32 current_scale; + + switch (chan->type) { + case IIO_PROXIMITY: + /* distance = raw_distance * 33.31 / 65536 (m) */ + *val = 3331; + *val2 = 6553600; + + return IIO_VAL_FRACTIONAL; + case IIO_PHASE: + /* phase = raw_phase * 2pi / 65536 (rad) */ + *val = 0; + *val2 = 95874; + + return IIO_VAL_INT_PLUS_NANO; + case IIO_INTENSITY: + /* light = raw_light * 35 / 10000 (mA) */ + *val = 35; + *val2 = 10000; + + return IIO_VAL_FRACTIONAL; + case IIO_CURRENT: + ret = isl29501_register_read(isl29501, + REG_DRIVER_RANGE, + ¤t_scale); + if (ret < 0) + return ret; + + if (current_scale > ARRAY_SIZE(isl29501_current_scale_table)) + return -EINVAL; + + if (!current_scale) { + *val = 0; + *val2 = 0; + return IIO_VAL_INT; + } + + *val = isl29501_current_scale_table[current_scale - 1][0]; + *val2 = isl29501_current_scale_table[current_scale - 1][1]; + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + /* temperature = raw_temperature * 125 / 100000 (milli °C) */ + *val = 125; + *val2 = 100000; + + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } +} + +static int isl29501_get_calibbias(struct isl29501_private *isl29501, + const struct iio_chan_spec *chan, + int *bias) +{ + switch (chan->type) { + case IIO_PROXIMITY: + return isl29501_register_read(isl29501, + REG_DISTANCE_BIAS, + bias); + case IIO_TEMP: + return isl29501_register_read(isl29501, + REG_TEMPERATURE_BIAS, + bias); + default: + return -EINVAL; + } +} + +static int isl29501_get_inttime(struct isl29501_private *isl29501, + int *val, int *val2) +{ + int ret; + u32 inttime; + + ret = isl29501_register_read(isl29501, REG_INT_TIME, &inttime); + if (ret < 0) + return ret; + + if (inttime >= ARRAY_SIZE(isl29501_int_time)) + return -EINVAL; + + *val = isl29501_int_time[inttime][0]; + *val2 = isl29501_int_time[inttime][1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int isl29501_get_freq(struct isl29501_private *isl29501, + int *val, int *val2) +{ + int ret; + int sample_time; + unsigned long long freq; + u32 temp; + + ret = isl29501_register_read(isl29501, REG_SAMPLE_TIME, &sample_time); + if (ret < 0) + return ret; + + /* freq = 1 / (0.000450 * (sample_time + 1) * 10^-6) */ + freq = 1000000ULL * 1000000ULL; + + do_div(freq, 450 * (sample_time + 1)); + + temp = do_div(freq, 1000000); + *val = freq; + *val2 = temp; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int isl29501_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct isl29501_private *isl29501 = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return isl29501_get_raw(isl29501, chan, val); + case IIO_CHAN_INFO_SCALE: + return isl29501_get_scale(isl29501, chan, val, val2); + case IIO_CHAN_INFO_INT_TIME: + return isl29501_get_inttime(isl29501, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return isl29501_get_freq(isl29501, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + return isl29501_get_calibbias(isl29501, chan, val); + default: + return -EINVAL; + } +} + +static int isl29501_set_raw(struct isl29501_private *isl29501, + const struct iio_chan_spec *chan, + int raw) +{ + switch (chan->type) { + case IIO_CURRENT: + return isl29501_register_write(isl29501, REG_EMITTER_DAC, raw); + default: + return -EINVAL; + } +} + +static int isl29501_set_inttime(struct isl29501_private *isl29501, + int val, int val2) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(isl29501_int_time); i++) { + if (isl29501_int_time[i][0] == val && + isl29501_int_time[i][1] == val2) { + return isl29501_register_write(isl29501, + REG_INT_TIME, + i); + } + } + + return -EINVAL; +} + +static int isl29501_set_scale(struct isl29501_private *isl29501, + const struct iio_chan_spec *chan, + int val, int val2) +{ + int i; + + if (chan->type != IIO_CURRENT) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(isl29501_current_scale_table); i++) { + if (isl29501_current_scale_table[i][0] == val && + isl29501_current_scale_table[i][1] == val2) { + return isl29501_register_write(isl29501, + REG_DRIVER_RANGE, + i + 1); + } + } + + return -EINVAL; +} + +static int isl29501_set_calibbias(struct isl29501_private *isl29501, + const struct iio_chan_spec *chan, + int bias) +{ + switch (chan->type) { + case IIO_PROXIMITY: + return isl29501_register_write(isl29501, + REG_DISTANCE_BIAS, + bias); + case IIO_TEMP: + return isl29501_register_write(isl29501, + REG_TEMPERATURE_BIAS, + bias); + default: + return -EINVAL; + } +} + +static int isl29501_set_freq(struct isl29501_private *isl29501, + int val, int val2) +{ + int freq; + unsigned long long sample_time; + + /* sample_freq = 1 / (0.000450 * (sample_time + 1) * 10^-6) */ + freq = val * 1000000 + val2 % 1000000; + sample_time = 2222ULL * 1000000ULL; + do_div(sample_time, freq); + + sample_time -= 1; + + if (sample_time > 255) + return -ERANGE; + + return isl29501_register_write(isl29501, REG_SAMPLE_TIME, sample_time); +} + +static int isl29501_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct isl29501_private *isl29501 = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return isl29501_set_raw(isl29501, chan, val); + case IIO_CHAN_INFO_INT_TIME: + return isl29501_set_inttime(isl29501, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return isl29501_set_freq(isl29501, val, val2); + case IIO_CHAN_INFO_SCALE: + return isl29501_set_scale(isl29501, chan, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + return isl29501_set_calibbias(isl29501, chan, val); + default: + return -EINVAL; + } +} + +static const struct iio_info isl29501_info = { + .read_raw = &isl29501_read_raw, + .write_raw = &isl29501_write_raw, + .attrs = &isl29501_attribute_group, +}; + +static int isl29501_init_chip(struct isl29501_private *isl29501) +{ + int ret; + + ret = i2c_smbus_read_byte_data(isl29501->client, ISL29501_DEVICE_ID); + if (ret < 0) { + dev_err(&isl29501->client->dev, "Error reading device id\n"); + return ret; + } + + if (ret != ISL29501_ID) { + dev_err(&isl29501->client->dev, + "Wrong chip id, got %x expected %x\n", + ret, ISL29501_DEVICE_ID); + return -ENODEV; + } + + ret = isl29501_reset_registers(isl29501); + if (ret < 0) + return ret; + + return isl29501_begin_acquisition(isl29501); +} + +static irqreturn_t isl29501_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct isl29501_private *isl29501 = iio_priv(indio_dev); + const unsigned long *active_mask = indio_dev->active_scan_mask; + u32 value; + struct { + u16 data; + aligned_s64 ts; + } scan = { }; + + if (test_bit(ISL29501_DISTANCE_SCAN_INDEX, active_mask)) { + isl29501_register_read(isl29501, REG_DISTANCE, &value); + scan.data = value; + } + + iio_push_to_buffers_with_timestamp(indio_dev, &scan, pf->timestamp); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int isl29501_probe(struct i2c_client *client) +{ + struct iio_dev *indio_dev; + struct isl29501_private *isl29501; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*isl29501)); + if (!indio_dev) + return -ENOMEM; + + isl29501 = iio_priv(indio_dev); + + i2c_set_clientdata(client, indio_dev); + isl29501->client = client; + + mutex_init(&isl29501->lock); + + ret = isl29501_init_chip(isl29501); + if (ret < 0) + return ret; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = isl29501_channels; + indio_dev->num_channels = ARRAY_SIZE(isl29501_channels); + indio_dev->name = client->name; + indio_dev->info = &isl29501_info; + + ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, + iio_pollfunc_store_time, + isl29501_trigger_handler, + NULL); + if (ret < 0) { + dev_err(&client->dev, "unable to setup iio triggered buffer\n"); + return ret; + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id isl29501_id[] = { + { .name = "isl29501" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, isl29501_id); + +static const struct of_device_id isl29501_i2c_matches[] = { + { .compatible = "renesas,isl29501" }, + { } +}; +MODULE_DEVICE_TABLE(of, isl29501_i2c_matches); + +static struct i2c_driver isl29501_driver = { + .driver = { + .name = "isl29501", + .of_match_table = isl29501_i2c_matches, + }, + .id_table = isl29501_id, + .probe = isl29501_probe, +}; +module_i2c_driver(isl29501_driver); + +MODULE_AUTHOR("Mathieu Othacehe "); +MODULE_DESCRIPTION("ISL29501 Time of Flight sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/proximity/mb1232.c b/drivers/iio/proximity/mb1232.c new file mode 100644 index 000000000..accfc296e --- /dev/null +++ b/drivers/iio/proximity/mb1232.c @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * mb1232.c - Support for MaxBotix I2CXL-MaxSonar-EZ series ultrasonic + * ranger with i2c interface + * actually tested with mb1232 type + * + * Copyright (c) 2019 Andreas Klinger + * + * For details about the device see: + * https://www.maxbotix.com/documents/I2CXL-MaxSonar-EZ_Datasheet.pdf + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* registers of MaxSonar device */ +#define MB1232_RANGE_COMMAND 0x51 /* Command for reading range */ +#define MB1232_ADDR_UNLOCK_1 0xAA /* Command 1 for changing address */ +#define MB1232_ADDR_UNLOCK_2 0xA5 /* Command 2 for changing address */ + +struct mb1232_data { + struct i2c_client *client; + + struct mutex lock; + + /* + * optionally a gpio can be used to announce when ranging has + * finished + * since we are just using the falling trigger of it we request + * only the interrupt for announcing when data is ready to be read + */ + struct completion ranging; + int irqnr; +}; + +static irqreturn_t mb1232_handle_irq(int irq, void *dev_id) +{ + struct iio_dev *indio_dev = dev_id; + struct mb1232_data *data = iio_priv(indio_dev); + + complete(&data->ranging); + + return IRQ_HANDLED; +} + +static s16 mb1232_read_distance(struct mb1232_data *data) +{ + struct i2c_client *client = data->client; + int ret; + s16 distance; + __be16 buf; + + mutex_lock(&data->lock); + + reinit_completion(&data->ranging); + + ret = i2c_smbus_write_byte(client, MB1232_RANGE_COMMAND); + if (ret < 0) { + dev_err(&client->dev, "write command - err: %d\n", ret); + goto error_unlock; + } + + if (data->irqnr > 0) { + /* it cannot take more than 100 ms */ + ret = wait_for_completion_killable_timeout(&data->ranging, + HZ/10); + if (ret < 0) + goto error_unlock; + else if (ret == 0) { + ret = -ETIMEDOUT; + goto error_unlock; + } + } else { + /* use simple sleep if announce irq is not connected */ + msleep(15); + } + + ret = i2c_master_recv(client, (char *)&buf, sizeof(buf)); + if (ret < 0) { + dev_err(&client->dev, "i2c_master_recv: ret=%d\n", ret); + goto error_unlock; + } + + distance = __be16_to_cpu(buf); + /* check for not returning misleading error codes */ + if (distance < 0) { + dev_err(&client->dev, "distance=%d\n", distance); + ret = -EINVAL; + goto error_unlock; + } + + mutex_unlock(&data->lock); + + return distance; + +error_unlock: + mutex_unlock(&data->lock); + + return ret; +} + +static irqreturn_t mb1232_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct mb1232_data *data = iio_priv(indio_dev); + struct { + s16 distance; + aligned_s64 ts; + } scan = { }; + + scan.distance = mb1232_read_distance(data); + if (scan.distance < 0) + goto err; + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + pf->timestamp); + +err: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int mb1232_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct mb1232_data *data = iio_priv(indio_dev); + int ret; + + if (channel->type != IIO_DISTANCE) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = mb1232_read_distance(data); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* 1 LSB is 1 cm */ + *val = 0; + *val2 = 10000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static const struct iio_chan_spec mb1232_channels[] = { + { + .type = IIO_DISTANCE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct iio_info mb1232_info = { + .read_raw = mb1232_read_raw, +}; + +static int mb1232_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct iio_dev *indio_dev; + struct mb1232_data *data; + int ret; + struct device *dev = &client->dev; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BYTE | + I2C_FUNC_SMBUS_WRITE_BYTE)) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + indio_dev->info = &mb1232_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = mb1232_channels; + indio_dev->num_channels = ARRAY_SIZE(mb1232_channels); + + mutex_init(&data->lock); + + init_completion(&data->ranging); + + data->irqnr = fwnode_irq_get(dev_fwnode(&client->dev), 0); + if (data->irqnr > 0) { + ret = devm_request_irq(dev, data->irqnr, mb1232_handle_irq, + IRQF_TRIGGER_FALLING, id->name, indio_dev); + if (ret) + return ret; + } + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, mb1232_trigger_handler, NULL); + if (ret < 0) { + dev_err(dev, "setup of iio triggered buffer failed\n"); + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id of_mb1232_match[] = { + { .compatible = "maxbotix,mb1202", }, + { .compatible = "maxbotix,mb1212", }, + { .compatible = "maxbotix,mb1222", }, + { .compatible = "maxbotix,mb1232", }, + { .compatible = "maxbotix,mb1242", }, + { .compatible = "maxbotix,mb7040", }, + { .compatible = "maxbotix,mb7137", }, + { } +}; + +MODULE_DEVICE_TABLE(of, of_mb1232_match); + +static const struct i2c_device_id mb1232_id[] = { + { .name = "maxbotix-mb1202" }, + { .name = "maxbotix-mb1212" }, + { .name = "maxbotix-mb1222" }, + { .name = "maxbotix-mb1232" }, + { .name = "maxbotix-mb1242" }, + { .name = "maxbotix-mb7040" }, + { .name = "maxbotix-mb7137" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mb1232_id); + +static struct i2c_driver mb1232_driver = { + .driver = { + .name = "maxbotix-mb1232", + .of_match_table = of_mb1232_match, + }, + .probe = mb1232_probe, + .id_table = mb1232_id, +}; +module_i2c_driver(mb1232_driver); + +MODULE_AUTHOR("Andreas Klinger "); +MODULE_DESCRIPTION("Maxbotix I2CXL-MaxSonar i2c ultrasonic ranger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/proximity/ping.c b/drivers/iio/proximity/ping.c new file mode 100644 index 000000000..1e646b858 --- /dev/null +++ b/drivers/iio/proximity/ping.c @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PING: ultrasonic sensor for distance measuring by using only one GPIOs + * + * Copyright (c) 2019 Andreas Klinger + * + * For details about the devices see: + * http://parallax.com/sites/default/files/downloads/28041-LaserPING-2m-Rangefinder-Guide.pdf + * http://parallax.com/sites/default/files/downloads/28015-PING-Documentation-v1.6.pdf + * + * the measurement cycle as timing diagram looks like: + * + * GPIO ___ ________________________ + * ping: __/ \____________/ \________________ + * ^ ^ ^ ^ + * |<->| interrupt interrupt + * udelay(5) (ts_rising) (ts_falling) + * |<---------------------->| + * . pulse time measured . + * . --> one round trip of ultra sonic waves + * ultra . . + * sonic _ _ _. . + * burst: _________/ \_/ \_/ \_________________________________________ + * . + * ultra . + * sonic _ _ _. + * echo: __________________________________/ \_/ \_/ \________________ + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct ping_cfg { + unsigned long trigger_pulse_us; /* length of trigger pulse */ + int laserping_error; /* support error code in */ + /* pulse width of laser */ + /* ping sensors */ + s64 timeout_ns; /* timeout in ns */ +}; + +struct ping_data { + struct device *dev; + struct gpio_desc *gpiod_ping; + struct mutex lock; + int irqnr; + ktime_t ts_rising; + ktime_t ts_falling; + struct completion rising; + struct completion falling; + const struct ping_cfg *cfg; +}; + +static const struct ping_cfg pa_ping_cfg = { + .trigger_pulse_us = 5, + .laserping_error = 0, + .timeout_ns = 18500000, /* 3 meters */ +}; + +static const struct ping_cfg pa_laser_ping_cfg = { + .trigger_pulse_us = 5, + .laserping_error = 1, + .timeout_ns = 15500000, /* 2 meters plus error codes */ +}; + +static irqreturn_t ping_handle_irq(int irq, void *dev_id) +{ + struct iio_dev *indio_dev = dev_id; + struct ping_data *data = iio_priv(indio_dev); + ktime_t now = ktime_get(); + + if (gpiod_get_value(data->gpiod_ping)) { + data->ts_rising = now; + complete(&data->rising); + } else { + data->ts_falling = now; + complete(&data->falling); + } + + return IRQ_HANDLED; +} + +static int ping_read(struct iio_dev *indio_dev) +{ + struct ping_data *data = iio_priv(indio_dev); + int ret; + ktime_t ktime_dt; + s64 dt_ns; + u32 time_ns, distance_mm; + struct platform_device *pdev = to_platform_device(data->dev); + + /* + * just one read-echo-cycle can take place at a time + * ==> lock against concurrent reading calls + */ + mutex_lock(&data->lock); + + reinit_completion(&data->rising); + reinit_completion(&data->falling); + + gpiod_set_value(data->gpiod_ping, 1); + udelay(data->cfg->trigger_pulse_us); + gpiod_set_value(data->gpiod_ping, 0); + + ret = gpiod_direction_input(data->gpiod_ping); + if (ret < 0) { + mutex_unlock(&data->lock); + return ret; + } + + data->irqnr = gpiod_to_irq(data->gpiod_ping); + if (data->irqnr < 0) { + dev_err(data->dev, "gpiod_to_irq: %d\n", data->irqnr); + mutex_unlock(&data->lock); + return data->irqnr; + } + + ret = request_irq(data->irqnr, ping_handle_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + pdev->name, indio_dev); + if (ret < 0) { + dev_err(data->dev, "request_irq: %d\n", ret); + mutex_unlock(&data->lock); + return ret; + } + + /* it should not take more than 20 ms until echo is rising */ + ret = wait_for_completion_killable_timeout(&data->rising, HZ/50); + if (ret < 0) + goto err_reset_direction; + else if (ret == 0) { + ret = -ETIMEDOUT; + goto err_reset_direction; + } + + /* it cannot take more than 50 ms until echo is falling */ + ret = wait_for_completion_killable_timeout(&data->falling, HZ/20); + if (ret < 0) + goto err_reset_direction; + else if (ret == 0) { + ret = -ETIMEDOUT; + goto err_reset_direction; + } + + ktime_dt = ktime_sub(data->ts_falling, data->ts_rising); + + free_irq(data->irqnr, indio_dev); + + ret = gpiod_direction_output(data->gpiod_ping, GPIOD_OUT_LOW); + if (ret < 0) { + mutex_unlock(&data->lock); + return ret; + } + + mutex_unlock(&data->lock); + + dt_ns = ktime_to_ns(ktime_dt); + if (dt_ns > data->cfg->timeout_ns) { + dev_dbg(data->dev, "distance out of range: dt=%lldns\n", + dt_ns); + return -EIO; + } + + time_ns = dt_ns; + + /* + * read error code of laser ping sensor and give users chance to + * figure out error by using dynamic debugging + */ + if (data->cfg->laserping_error) { + if ((time_ns > 12500000) && (time_ns <= 13500000)) { + dev_dbg(data->dev, "target too close or to far\n"); + return -EIO; + } + if ((time_ns > 13500000) && (time_ns <= 14500000)) { + dev_dbg(data->dev, "internal sensor error\n"); + return -EIO; + } + if ((time_ns > 14500000) && (time_ns <= 15500000)) { + dev_dbg(data->dev, "internal sensor timeout\n"); + return -EIO; + } + } + + /* + * the speed as function of the temperature is approximately: + * + * speed = 331,5 + 0,6 * Temp + * with Temp in °C + * and speed in m/s + * + * use 343,5 m/s as ultrasonic speed at 20 °C here in absence of the + * temperature + * + * therefore: + * time 343,5 time * 232 + * distance = ------ * ------- = ------------ + * 10^6 2 1350800 + * with time in ns + * and distance in mm (one way) + * + * because we limit to 3 meters the multiplication with 232 just + * fits into 32 bit + */ + distance_mm = time_ns * 232 / 1350800; + + return distance_mm; + +err_reset_direction: + free_irq(data->irqnr, indio_dev); + mutex_unlock(&data->lock); + + if (gpiod_direction_output(data->gpiod_ping, GPIOD_OUT_LOW)) + dev_dbg(data->dev, "error in gpiod_direction_output\n"); + return ret; +} + +static int ping_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long info) +{ + int ret; + + if (channel->type != IIO_DISTANCE) + return -EINVAL; + + switch (info) { + case IIO_CHAN_INFO_RAW: + ret = ping_read(indio_dev); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* + * maximum resolution in datasheet is 1 mm + * 1 LSB is 1 mm + */ + *val = 0; + *val2 = 1000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static const struct iio_info ping_iio_info = { + .read_raw = ping_read_raw, +}; + +static const struct iio_chan_spec ping_chan_spec[] = { + { + .type = IIO_DISTANCE, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static const struct of_device_id of_ping_match[] = { + { .compatible = "parallax,ping", .data = &pa_ping_cfg }, + { .compatible = "parallax,laserping", .data = &pa_laser_ping_cfg }, + { } +}; + +MODULE_DEVICE_TABLE(of, of_ping_match); + +static int ping_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ping_data *data; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(dev, sizeof(struct ping_data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->dev = dev; + data->cfg = device_get_match_data(dev); + + mutex_init(&data->lock); + init_completion(&data->rising); + init_completion(&data->falling); + + data->gpiod_ping = devm_gpiod_get(dev, "ping", GPIOD_OUT_LOW); + if (IS_ERR(data->gpiod_ping)) { + dev_err(dev, "failed to get ping-gpios: err=%ld\n", + PTR_ERR(data->gpiod_ping)); + return PTR_ERR(data->gpiod_ping); + } + + if (gpiod_cansleep(data->gpiod_ping)) { + dev_err(data->dev, "cansleep-GPIOs not supported\n"); + return -ENODEV; + } + + platform_set_drvdata(pdev, indio_dev); + + indio_dev->name = "ping"; + indio_dev->info = &ping_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ping_chan_spec; + indio_dev->num_channels = ARRAY_SIZE(ping_chan_spec); + + return devm_iio_device_register(dev, indio_dev); +} + +static struct platform_driver ping_driver = { + .probe = ping_probe, + .driver = { + .name = "ping-gpio", + .of_match_table = of_ping_match, + }, +}; + +module_platform_driver(ping_driver); + +MODULE_AUTHOR("Andreas Klinger "); +MODULE_DESCRIPTION("PING sensors for distance measuring using one GPIOs"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ping"); diff --git a/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c new file mode 100644 index 000000000..400477b4c --- /dev/null +++ b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * pulsedlight-lidar-lite-v2.c - Support for PulsedLight LIDAR sensor + * + * Copyright (C) 2015, 2017-2018 + * Author: Matt Ranostay + * + * TODO: interrupt mode, and signal strength reporting + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LIDAR_REG_CONTROL 0x00 +#define LIDAR_REG_CONTROL_ACQUIRE BIT(2) + +#define LIDAR_REG_STATUS 0x01 +#define LIDAR_REG_STATUS_INVALID BIT(3) +#define LIDAR_REG_STATUS_READY BIT(0) + +#define LIDAR_REG_DATA_HBYTE 0x0f +#define LIDAR_REG_DATA_LBYTE 0x10 +#define LIDAR_REG_DATA_WORD_READ BIT(7) + +#define LIDAR_REG_PWR_CONTROL 0x65 + +#define LIDAR_DRV_NAME "lidar" + +struct lidar_data { + struct iio_dev *indio_dev; + struct i2c_client *client; + + int (*xfer)(struct lidar_data *data, u8 reg, u8 *val, int len); + int i2c_enabled; +}; + +static const struct iio_chan_spec lidar_channels[] = { + { + .type = IIO_DISTANCE, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static int lidar_i2c_xfer(struct lidar_data *data, u8 reg, u8 *val, int len) +{ + struct i2c_client *client = data->client; + struct i2c_msg msg[2]; + int ret; + + msg[0].addr = client->addr; + msg[0].flags = client->flags | I2C_M_STOP; + msg[0].len = 1; + msg[0].buf = (char *) ® + + msg[1].addr = client->addr; + msg[1].flags = client->flags | I2C_M_RD; + msg[1].len = len; + msg[1].buf = (char *) val; + + ret = i2c_transfer(client->adapter, msg, 2); + + return (ret == 2) ? 0 : -EIO; +} + +static int lidar_smbus_xfer(struct lidar_data *data, u8 reg, u8 *val, int len) +{ + struct i2c_client *client = data->client; + int ret; + + /* + * Device needs a STOP condition between address write, and data read + * so in turn i2c_smbus_read_byte_data cannot be used + */ + + while (len--) { + ret = i2c_smbus_write_byte(client, reg++); + if (ret < 0) { + dev_err(&client->dev, "cannot write addr value"); + return ret; + } + + ret = i2c_smbus_read_byte(client); + if (ret < 0) { + dev_err(&client->dev, "cannot read data value"); + return ret; + } + + *(val++) = ret; + } + + return 0; +} + +static int lidar_read_byte(struct lidar_data *data, u8 reg) +{ + int ret; + u8 val; + + ret = data->xfer(data, reg, &val, 1); + if (ret < 0) + return ret; + + return val; +} + +static inline int lidar_write_control(struct lidar_data *data, int val) +{ + return i2c_smbus_write_byte_data(data->client, LIDAR_REG_CONTROL, val); +} + +static inline int lidar_write_power(struct lidar_data *data, int val) +{ + return i2c_smbus_write_byte_data(data->client, + LIDAR_REG_PWR_CONTROL, val); +} + +static int lidar_read_measurement(struct lidar_data *data, u16 *reg) +{ + __be16 value; + int ret = data->xfer(data, LIDAR_REG_DATA_HBYTE | + (data->i2c_enabled ? LIDAR_REG_DATA_WORD_READ : 0), + (u8 *) &value, 2); + + if (!ret) + *reg = be16_to_cpu(value); + + return ret; +} + +static int lidar_get_measurement(struct lidar_data *data, u16 *reg) +{ + struct i2c_client *client = data->client; + int tries = 10; + int ret; + + ret = pm_runtime_resume_and_get(&client->dev); + if (ret < 0) + return ret; + + /* start sample */ + ret = lidar_write_control(data, LIDAR_REG_CONTROL_ACQUIRE); + if (ret < 0) { + dev_err(&client->dev, "cannot send start measurement command"); + pm_runtime_put_noidle(&client->dev); + return ret; + } + + while (tries--) { + usleep_range(1000, 2000); + + ret = lidar_read_byte(data, LIDAR_REG_STATUS); + if (ret < 0) + break; + + /* return -EINVAL since laser is likely pointed out of range */ + if (ret & LIDAR_REG_STATUS_INVALID) { + *reg = 0; + ret = -EINVAL; + break; + } + + /* sample ready to read */ + if (!(ret & LIDAR_REG_STATUS_READY)) { + ret = lidar_read_measurement(data, reg); + break; + } + ret = -EIO; + } + pm_runtime_put_autosuspend(&client->dev); + + return ret; +} + +static int lidar_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct lidar_data *data = iio_priv(indio_dev); + int ret = -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + u16 reg; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = lidar_get_measurement(data, ®); + if (!ret) { + *val = reg; + ret = IIO_VAL_INT; + } + iio_device_release_direct(indio_dev); + break; + } + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = 10000; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + } + + return ret; +} + +static irqreturn_t lidar_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct lidar_data *data = iio_priv(indio_dev); + int ret; + struct { + u16 chan; + aligned_s64 timestamp; + } scan = { }; + + ret = lidar_get_measurement(data, &scan.chan); + if (!ret) { + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); + } else if (ret != -EINVAL) { + dev_err(&data->client->dev, "cannot read LIDAR measurement"); + } + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_info lidar_info = { + .read_raw = lidar_read_raw, +}; + +static int lidar_probe(struct i2c_client *client) +{ + struct lidar_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + data = iio_priv(indio_dev); + + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + data->xfer = lidar_i2c_xfer; + data->i2c_enabled = 1; + } else if (i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE)) + data->xfer = lidar_smbus_xfer; + else + return -EOPNOTSUPP; + + indio_dev->info = &lidar_info; + indio_dev->name = LIDAR_DRV_NAME; + indio_dev->channels = lidar_channels; + indio_dev->num_channels = ARRAY_SIZE(lidar_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + i2c_set_clientdata(client, indio_dev); + + data->client = client; + data->indio_dev = indio_dev; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + lidar_trigger_handler, NULL); + if (ret) + return ret; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_unreg_buffer; + + pm_runtime_set_autosuspend_delay(&client->dev, 1000); + pm_runtime_use_autosuspend(&client->dev); + + ret = pm_runtime_set_active(&client->dev); + if (ret) + goto error_unreg_buffer; + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +error_unreg_buffer: + iio_triggered_buffer_cleanup(indio_dev); + + return ret; +} + +static void lidar_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); +} + +static const struct i2c_device_id lidar_id[] = { + { .name = "lidar-lite-v2" }, + { .name = "lidar-lite-v3" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lidar_id); + +static const struct of_device_id lidar_dt_ids[] = { + { .compatible = "pulsedlight,lidar-lite-v2" }, + { .compatible = "grmn,lidar-lite-v3" }, + { } +}; +MODULE_DEVICE_TABLE(of, lidar_dt_ids); + +static int lidar_pm_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct lidar_data *data = iio_priv(indio_dev); + + return lidar_write_power(data, 0x0f); +} + +static int lidar_pm_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct lidar_data *data = iio_priv(indio_dev); + int ret = lidar_write_power(data, 0); + + /* regulator and FPGA needs settling time */ + usleep_range(15000, 20000); + + return ret; +} + +static const struct dev_pm_ops lidar_pm_ops = { + RUNTIME_PM_OPS(lidar_pm_runtime_suspend, lidar_pm_runtime_resume, NULL) +}; + +static struct i2c_driver lidar_driver = { + .driver = { + .name = LIDAR_DRV_NAME, + .of_match_table = lidar_dt_ids, + .pm = pm_ptr(&lidar_pm_ops), + }, + .probe = lidar_probe, + .remove = lidar_remove, + .id_table = lidar_id, +}; +module_i2c_driver(lidar_driver); + +MODULE_AUTHOR("Matt Ranostay "); +MODULE_DESCRIPTION("PulsedLight LIDAR sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/proximity/rfd77402.c b/drivers/iio/proximity/rfd77402.c new file mode 100644 index 000000000..db1e94fc5 --- /dev/null +++ b/drivers/iio/proximity/rfd77402.c @@ -0,0 +1,461 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * rfd77402.c - Support for RF Digital RFD77402 Time-of-Flight (distance) sensor + * + * Copyright 2017 Peter Meerwald-Stadler + * + * 7-bit I2C slave address 0x4c + * + * https://media.digikey.com/pdf/Data%20Sheets/RF%20Digital%20PDFs/RFD77402.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define RFD77402_DRV_NAME "rfd77402" + +#define RFD77402_ICSR 0x00 /* Interrupt Control Status Register */ +#define RFD77402_ICSR_CLR_CFG BIT(0) +#define RFD77402_ICSR_CLR_TYPE BIT(1) +#define RFD77402_ICSR_INT_MODE BIT(2) +#define RFD77402_ICSR_INT_POL BIT(3) +#define RFD77402_ICSR_RESULT BIT(4) +#define RFD77402_ICSR_M2H_MSG BIT(5) +#define RFD77402_ICSR_H2M_MSG BIT(6) +#define RFD77402_ICSR_RESET BIT(7) + +#define RFD77402_IER 0x02 +#define RFD77402_IER_RESULT BIT(0) +#define RFD77402_IER_M2H_MSG BIT(1) +#define RFD77402_IER_H2M_MSG BIT(2) +#define RFD77402_IER_RESET BIT(3) + +#define RFD77402_CMD_R 0x04 +#define RFD77402_CMD_SINGLE 0x01 +#define RFD77402_CMD_STANDBY 0x10 +#define RFD77402_CMD_MCPU_OFF 0x11 +#define RFD77402_CMD_MCPU_ON 0x12 +#define RFD77402_CMD_RESET BIT(6) +#define RFD77402_CMD_VALID BIT(7) + +#define RFD77402_STATUS_R 0x06 +#define RFD77402_STATUS_PM_MASK GENMASK(4, 0) +#define RFD77402_STATUS_STANDBY 0x00 +#define RFD77402_STATUS_MCPU_OFF 0x10 +#define RFD77402_STATUS_MCPU_ON 0x18 + +#define RFD77402_RESULT_R 0x08 +#define RFD77402_RESULT_DIST_MASK GENMASK(12, 2) +#define RFD77402_RESULT_ERR_MASK GENMASK(14, 13) +#define RFD77402_RESULT_VALID BIT(15) + +#define RFD77402_PMU_CFG 0x14 +#define RFD77402_PMU_MCPU_INIT BIT(9) + +#define RFD77402_I2C_INIT_CFG 0x1c +#define RFD77402_I2C_ADDR_INCR BIT(0) +#define RFD77402_I2C_DATA_INCR BIT(2) +#define RFD77402_I2C_HOST_DEBUG BIT(5) +#define RFD77402_I2C_MCPU_DEBUG BIT(6) + +#define RFD77402_CMD_CFGR_A 0x0c +#define RFD77402_CMD_CFGR_B 0x0e +#define RFD77402_HFCFG_0 0x20 +#define RFD77402_HFCFG_1 0x22 +#define RFD77402_HFCFG_2 0x24 +#define RFD77402_HFCFG_3 0x26 + +#define RFD77402_MOD_CHIP_ID 0x28 + +/* magic configuration values from datasheet */ +static const struct { + u8 reg; + u16 val; +} rf77402_tof_config[] = { + {RFD77402_CMD_CFGR_A, 0xe100}, + {RFD77402_CMD_CFGR_B, 0x10ff}, + {RFD77402_HFCFG_0, 0x07d0}, + {RFD77402_HFCFG_1, 0x5008}, + {RFD77402_HFCFG_2, 0xa041}, + {RFD77402_HFCFG_3, 0x45d4}, +}; + +/** + * struct rfd77402_data - device-specific data for the RFD77402 sensor + * @client: I2C client handle + * @lock: mutex to serialize sensor reads + * @completion: completion used for interrupt-driven measurements + * @irq_en: indicates whether interrupt mode is enabled + */ +struct rfd77402_data { + struct i2c_client *client; + struct mutex lock; + struct completion completion; + bool irq_en; +}; + +static const struct iio_chan_spec rfd77402_channels[] = { + { + .type = IIO_DISTANCE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static irqreturn_t rfd77402_interrupt_handler(int irq, void *pdata) +{ + struct rfd77402_data *data = pdata; + int ret; + + ret = i2c_smbus_read_byte_data(data->client, RFD77402_ICSR); + if (ret < 0) + return IRQ_NONE; + + /* Check if the interrupt is from our device */ + if (!(ret & RFD77402_ICSR_RESULT)) + return IRQ_NONE; + + /* Signal completion of measurement */ + complete(&data->completion); + return IRQ_HANDLED; +} + +static int rfd77402_wait_for_irq(struct rfd77402_data *data) +{ + int ret; + + /* + * According to RFD77402 Datasheet v1.8, + * Section 3.1.1 "Single Measure" (Figure: Single Measure Flow Chart), + * the suggested timeout for single measure is 100 ms. + */ + ret = wait_for_completion_timeout(&data->completion, + msecs_to_jiffies(100)); + if (ret == 0) + return -ETIMEDOUT; + + return 0; +} + +static int rfd77402_set_state(struct i2c_client *client, u8 state, u16 check) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, RFD77402_CMD_R, + state | RFD77402_CMD_VALID); + if (ret < 0) + return ret; + + usleep_range(10000, 20000); + + ret = i2c_smbus_read_word_data(client, RFD77402_STATUS_R); + if (ret < 0) + return ret; + if ((ret & RFD77402_STATUS_PM_MASK) != check) + return -ENODEV; + + return 0; +} + +static int rfd77402_wait_for_result(struct rfd77402_data *data) +{ + struct i2c_client *client = data->client; + int val, ret; + + if (data->irq_en) + return rfd77402_wait_for_irq(data); + + /* + * As per RFD77402 datasheet section '3.1.1 Single Measure', the + * suggested timeout value for single measure is 100ms. + */ + ret = read_poll_timeout(i2c_smbus_read_byte_data, val, + (val < 0) || (val & RFD77402_ICSR_RESULT), + 10 * USEC_PER_MSEC, + 10 * 10 * USEC_PER_MSEC, + false, + client, RFD77402_ICSR); + if (val < 0) + return val; + + return ret; +} + +static int rfd77402_measure(struct rfd77402_data *data) +{ + struct i2c_client *client = data->client; + int ret; + + ret = rfd77402_set_state(client, RFD77402_CMD_MCPU_ON, + RFD77402_STATUS_MCPU_ON); + if (ret < 0) + return ret; + + if (data->irq_en) + reinit_completion(&data->completion); + + ret = i2c_smbus_write_byte_data(client, RFD77402_CMD_R, + RFD77402_CMD_SINGLE | + RFD77402_CMD_VALID); + if (ret < 0) + goto err; + + ret = rfd77402_wait_for_result(data); + if (ret < 0) + goto err; + + ret = i2c_smbus_read_word_data(client, RFD77402_RESULT_R); + if (ret < 0) + goto err; + + if ((ret & RFD77402_RESULT_ERR_MASK) || + !(ret & RFD77402_RESULT_VALID)) { + ret = -EIO; + goto err; + } + + return (ret & RFD77402_RESULT_DIST_MASK) >> 2; + +err: + rfd77402_set_state(client, RFD77402_CMD_MCPU_OFF, + RFD77402_STATUS_MCPU_OFF); + return ret; +} + +static int rfd77402_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct rfd77402_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&data->lock); + ret = rfd77402_measure(data); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* 1 LSB is 1 mm */ + *val = 0; + *val2 = 1000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static const struct iio_info rfd77402_info = { + .read_raw = rfd77402_read_raw, +}; + +static int rfd77402_config_irq(struct i2c_client *client, u8 csr, u8 ier) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, RFD77402_ICSR, csr); + if (ret) + return ret; + + return i2c_smbus_write_byte_data(client, RFD77402_IER, ier); +} + +static int rfd77402_init(struct rfd77402_data *data) +{ + struct i2c_client *client = data->client; + int ret, i; + + ret = rfd77402_set_state(client, RFD77402_CMD_STANDBY, + RFD77402_STATUS_STANDBY); + if (ret < 0) + return ret; + + if (data->irq_en) { + /* + * Enable interrupt mode: + * - Configure ICSR for auto-clear on read and + * push-pull output + * - Enable "result ready" interrupt in IER + */ + ret = rfd77402_config_irq(client, + RFD77402_ICSR_CLR_CFG | + RFD77402_ICSR_INT_MODE, + RFD77402_IER_RESULT); + } else { + /* + * Disable all interrupts: + * - Clear ICSR configuration + * - Disable all interrupts in IER + */ + ret = rfd77402_config_irq(client, 0, 0); + } + if (ret) + return ret; + + /* I2C configuration */ + ret = i2c_smbus_write_word_data(client, RFD77402_I2C_INIT_CFG, + RFD77402_I2C_ADDR_INCR | + RFD77402_I2C_DATA_INCR | + RFD77402_I2C_HOST_DEBUG | + RFD77402_I2C_MCPU_DEBUG); + if (ret < 0) + return ret; + + /* set initialization */ + ret = i2c_smbus_write_word_data(client, RFD77402_PMU_CFG, 0x0500); + if (ret < 0) + return ret; + + ret = rfd77402_set_state(client, RFD77402_CMD_MCPU_OFF, + RFD77402_STATUS_MCPU_OFF); + if (ret < 0) + return ret; + + /* set initialization */ + ret = i2c_smbus_write_word_data(client, RFD77402_PMU_CFG, 0x0600); + if (ret < 0) + return ret; + + ret = rfd77402_set_state(client, RFD77402_CMD_MCPU_ON, + RFD77402_STATUS_MCPU_ON); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(rf77402_tof_config); i++) { + ret = i2c_smbus_write_word_data(client, + rf77402_tof_config[i].reg, + rf77402_tof_config[i].val); + if (ret < 0) + return ret; + } + + ret = rfd77402_set_state(client, RFD77402_CMD_STANDBY, + RFD77402_STATUS_STANDBY); + + return ret; +} + +static int rfd77402_powerdown(struct i2c_client *client) +{ + return rfd77402_set_state(client, RFD77402_CMD_STANDBY, + RFD77402_STATUS_STANDBY); +} + +static void rfd77402_disable(void *client) +{ + rfd77402_powerdown(client); +} + +static int rfd77402_probe(struct i2c_client *client) +{ + struct rfd77402_data *data; + struct iio_dev *indio_dev; + int ret; + + ret = i2c_smbus_read_word_data(client, RFD77402_MOD_CHIP_ID); + if (ret < 0) + return ret; + if (ret != 0xad01 && ret != 0xad02) /* known chip ids */ + return -ENODEV; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + + ret = devm_mutex_init(&client->dev, &data->lock); + if (ret) + return ret; + + init_completion(&data->completion); + + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, rfd77402_interrupt_handler, + IRQF_ONESHOT, + "rfd77402", data); + if (ret) + return ret; + + data->irq_en = true; + dev_dbg(&client->dev, "Using interrupt mode\n"); + } else { + dev_dbg(&client->dev, "Using polling mode\n"); + } + + indio_dev->info = &rfd77402_info; + indio_dev->channels = rfd77402_channels; + indio_dev->num_channels = ARRAY_SIZE(rfd77402_channels); + indio_dev->name = RFD77402_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = rfd77402_init(data); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(&client->dev, rfd77402_disable, client); + if (ret) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static int rfd77402_suspend(struct device *dev) +{ + return rfd77402_powerdown(to_i2c_client(dev)); +} + +static int rfd77402_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct rfd77402_data *data = iio_priv(indio_dev); + + return rfd77402_init(data); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(rfd77402_pm_ops, rfd77402_suspend, + rfd77402_resume); + +static const struct i2c_device_id rfd77402_id[] = { + { .name = "rfd77402" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, rfd77402_id); + +static const struct of_device_id rfd77402_of_match[] = { + { .compatible = "rfdigital,rfd77402" }, + { } +}; +MODULE_DEVICE_TABLE(of, rfd77402_of_match); + +static struct i2c_driver rfd77402_driver = { + .driver = { + .name = RFD77402_DRV_NAME, + .pm = pm_sleep_ptr(&rfd77402_pm_ops), + .of_match_table = rfd77402_of_match, + }, + .probe = rfd77402_probe, + .id_table = rfd77402_id, +}; + +module_i2c_driver(rfd77402_driver); + +MODULE_AUTHOR("Peter Meerwald-Stadler "); +MODULE_DESCRIPTION("RFD77402 Time-of-Flight sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/proximity/srf04.c b/drivers/iio/proximity/srf04.c new file mode 100644 index 000000000..4e042214d --- /dev/null +++ b/drivers/iio/proximity/srf04.c @@ -0,0 +1,399 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * SRF04: ultrasonic sensor for distance measuring by using GPIOs + * + * Copyright (c) 2017 Andreas Klinger + * + * For details about the device see: + * https://www.robot-electronics.co.uk/htm/srf04tech.htm + * + * the measurement cycle as timing diagram looks like: + * + * +---+ + * GPIO | | + * trig: --+ +------------------------------------------------------ + * ^ ^ + * |<->| + * udelay(trigger_pulse_us) + * + * ultra +-+ +-+ +-+ + * sonic | | | | | | + * burst: ---------+ +-+ +-+ +----------------------------------------- + * . + * ultra . +-+ +-+ +-+ + * sonic . | | | | | | + * echo: ----------------------------------+ +-+ +-+ +---------------- + * . . + * +------------------------+ + * GPIO | | + * echo: -------------------+ +--------------- + * ^ ^ + * interrupt interrupt + * (ts_rising) (ts_falling) + * |<---------------------->| + * pulse time measured + * --> one round trip of ultra sonic waves + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct srf04_cfg { + unsigned long trigger_pulse_us; +}; + +struct srf04_data { + struct device *dev; + struct gpio_desc *gpiod_trig; + struct gpio_desc *gpiod_echo; + struct gpio_desc *gpiod_power; + struct mutex lock; + int irqnr; + ktime_t ts_rising; + ktime_t ts_falling; + struct completion rising; + struct completion falling; + const struct srf04_cfg *cfg; + int startup_time_ms; +}; + +static const struct srf04_cfg srf04_cfg = { + .trigger_pulse_us = 10, +}; + +static const struct srf04_cfg mb_lv_cfg = { + .trigger_pulse_us = 20, +}; + +static irqreturn_t srf04_handle_irq(int irq, void *dev_id) +{ + struct iio_dev *indio_dev = dev_id; + struct srf04_data *data = iio_priv(indio_dev); + ktime_t now = ktime_get(); + + if (gpiod_get_value(data->gpiod_echo)) { + data->ts_rising = now; + complete(&data->rising); + } else { + data->ts_falling = now; + complete(&data->falling); + } + + return IRQ_HANDLED; +} + +static int srf04_read(struct srf04_data *data) +{ + int ret; + ktime_t ktime_dt; + u64 dt_ns; + u32 time_ns, distance_mm; + + if (data->gpiod_power) { + ret = pm_runtime_resume_and_get(data->dev); + if (ret < 0) + return ret; + } + /* + * just one read-echo-cycle can take place at a time + * ==> lock against concurrent reading calls + */ + mutex_lock(&data->lock); + + reinit_completion(&data->rising); + reinit_completion(&data->falling); + + gpiod_set_value(data->gpiod_trig, 1); + udelay(data->cfg->trigger_pulse_us); + gpiod_set_value(data->gpiod_trig, 0); + + if (data->gpiod_power) + pm_runtime_put_autosuspend(data->dev); + + /* it should not take more than 20 ms until echo is rising */ + ret = wait_for_completion_killable_timeout(&data->rising, HZ/50); + if (ret < 0) { + mutex_unlock(&data->lock); + return ret; + } else if (ret == 0) { + mutex_unlock(&data->lock); + return -ETIMEDOUT; + } + + /* it cannot take more than 50 ms until echo is falling */ + ret = wait_for_completion_killable_timeout(&data->falling, HZ/20); + if (ret < 0) { + mutex_unlock(&data->lock); + return ret; + } else if (ret == 0) { + mutex_unlock(&data->lock); + return -ETIMEDOUT; + } + + ktime_dt = ktime_sub(data->ts_falling, data->ts_rising); + + mutex_unlock(&data->lock); + + dt_ns = ktime_to_ns(ktime_dt); + /* + * measuring more than 6,45 meters is beyond the capabilities of + * the supported sensors + * ==> filter out invalid results for not measuring echos of + * another us sensor + * + * formula: + * distance 6,45 * 2 m + * time = ---------- = ------------ = 40438871 ns + * speed 319 m/s + * + * using a minimum speed at -20 °C of 319 m/s + */ + if (dt_ns > 40438871) + return -EIO; + + time_ns = dt_ns; + + /* + * the speed as function of the temperature is approximately: + * + * speed = 331,5 + 0,6 * Temp + * with Temp in °C + * and speed in m/s + * + * use 343,5 m/s as ultrasonic speed at 20 °C here in absence of the + * temperature + * + * therefore: + * time 343,5 time * 106 + * distance = ------ * ------- = ------------ + * 10^6 2 617176 + * with time in ns + * and distance in mm (one way) + * + * because we limit to 6,45 meters the multiplication with 106 just + * fits into 32 bit + */ + distance_mm = time_ns * 106 / 617176; + + return distance_mm; +} + +static int srf04_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long info) +{ + struct srf04_data *data = iio_priv(indio_dev); + int ret; + + if (channel->type != IIO_DISTANCE) + return -EINVAL; + + switch (info) { + case IIO_CHAN_INFO_RAW: + ret = srf04_read(data); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* + * theoretical maximum resolution is 3 mm + * 1 LSB is 1 mm + */ + *val = 0; + *val2 = 1000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static const struct iio_info srf04_iio_info = { + .read_raw = srf04_read_raw, +}; + +static const struct iio_chan_spec srf04_chan_spec[] = { + { + .type = IIO_DISTANCE, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static const struct of_device_id of_srf04_match[] = { + { .compatible = "devantech,srf04", .data = &srf04_cfg }, + { .compatible = "maxbotix,mb1000", .data = &mb_lv_cfg }, + { .compatible = "maxbotix,mb1010", .data = &mb_lv_cfg }, + { .compatible = "maxbotix,mb1020", .data = &mb_lv_cfg }, + { .compatible = "maxbotix,mb1030", .data = &mb_lv_cfg }, + { .compatible = "maxbotix,mb1040", .data = &mb_lv_cfg }, + { } +}; + +MODULE_DEVICE_TABLE(of, of_srf04_match); + +static int srf04_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct srf04_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(struct srf04_data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->dev = dev; + data->cfg = device_get_match_data(dev); + + mutex_init(&data->lock); + init_completion(&data->rising); + init_completion(&data->falling); + + data->gpiod_trig = devm_gpiod_get(dev, "trig", GPIOD_OUT_LOW); + if (IS_ERR(data->gpiod_trig)) { + dev_err(dev, "failed to get trig-gpios: err=%ld\n", + PTR_ERR(data->gpiod_trig)); + return PTR_ERR(data->gpiod_trig); + } + + data->gpiod_echo = devm_gpiod_get(dev, "echo", GPIOD_IN); + if (IS_ERR(data->gpiod_echo)) { + dev_err(dev, "failed to get echo-gpios: err=%ld\n", + PTR_ERR(data->gpiod_echo)); + return PTR_ERR(data->gpiod_echo); + } + + data->gpiod_power = devm_gpiod_get_optional(dev, "power", + GPIOD_OUT_LOW); + if (IS_ERR(data->gpiod_power)) { + dev_err(dev, "failed to get power-gpios: err=%ld\n", + PTR_ERR(data->gpiod_power)); + return PTR_ERR(data->gpiod_power); + } + if (data->gpiod_power) { + data->startup_time_ms = 100; + device_property_read_u32(dev, "startup-time-ms", &data->startup_time_ms); + dev_dbg(dev, "using power gpio: startup-time-ms=%d\n", + data->startup_time_ms); + } + + if (gpiod_cansleep(data->gpiod_echo)) { + dev_err(data->dev, "cansleep-GPIOs not supported\n"); + return -ENODEV; + } + + data->irqnr = gpiod_to_irq(data->gpiod_echo); + if (data->irqnr < 0) { + dev_err(data->dev, "gpiod_to_irq: %d\n", data->irqnr); + return data->irqnr; + } + + ret = devm_request_irq(dev, data->irqnr, srf04_handle_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + pdev->name, indio_dev); + if (ret) + return ret; + + platform_set_drvdata(pdev, indio_dev); + + indio_dev->name = "srf04"; + indio_dev->info = &srf04_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = srf04_chan_spec; + indio_dev->num_channels = ARRAY_SIZE(srf04_chan_spec); + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(data->dev, "iio_device_register: %d\n", ret); + return ret; + } + + if (data->gpiod_power) { + pm_runtime_set_autosuspend_delay(data->dev, 1000); + pm_runtime_use_autosuspend(data->dev); + + ret = pm_runtime_set_active(data->dev); + if (ret) { + dev_err(data->dev, "pm_runtime_set_active: %d\n", ret); + iio_device_unregister(indio_dev); + return ret; + } + + pm_runtime_enable(data->dev); + pm_runtime_idle(data->dev); + } + + return ret; +} + +static void srf04_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct srf04_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + if (data->gpiod_power) { + pm_runtime_disable(data->dev); + pm_runtime_set_suspended(data->dev); + } +} + +static int srf04_pm_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = container_of(dev, + struct platform_device, dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct srf04_data *data = iio_priv(indio_dev); + + gpiod_set_value(data->gpiod_power, 0); + + return 0; +} + +static int srf04_pm_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = container_of(dev, + struct platform_device, dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct srf04_data *data = iio_priv(indio_dev); + + gpiod_set_value(data->gpiod_power, 1); + msleep(data->startup_time_ms); + + return 0; +} + +static const struct dev_pm_ops srf04_pm_ops = { + RUNTIME_PM_OPS(srf04_pm_runtime_suspend, + srf04_pm_runtime_resume, NULL) +}; + +static struct platform_driver srf04_driver = { + .probe = srf04_probe, + .remove = srf04_remove, + .driver = { + .name = "srf04-gpio", + .of_match_table = of_srf04_match, + .pm = pm_ptr(&srf04_pm_ops), + }, +}; + +module_platform_driver(srf04_driver); + +MODULE_AUTHOR("Andreas Klinger "); +MODULE_DESCRIPTION("SRF04 ultrasonic sensor for distance measuring using GPIOs"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:srf04"); diff --git a/drivers/iio/proximity/srf08.c b/drivers/iio/proximity/srf08.c new file mode 100644 index 000000000..35eb3fd7e --- /dev/null +++ b/drivers/iio/proximity/srf08.c @@ -0,0 +1,552 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * srf08.c - Support for Devantech SRFxx ultrasonic ranger + * with i2c interface + * actually supported are srf02, srf08, srf10 + * + * Copyright (c) 2016, 2017 Andreas Klinger + * + * For details about the device see: + * https://www.robot-electronics.co.uk/htm/srf08tech.html + * https://www.robot-electronics.co.uk/htm/srf10tech.htm + * https://www.robot-electronics.co.uk/htm/srf02tech.htm + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* registers of SRF08 device */ +#define SRF08_WRITE_COMMAND 0x00 /* Command Register */ +#define SRF08_WRITE_MAX_GAIN 0x01 /* Max Gain Register: 0 .. 31 */ +#define SRF08_WRITE_RANGE 0x02 /* Range Register: 0 .. 255 */ +#define SRF08_READ_SW_REVISION 0x00 /* Software Revision */ +#define SRF08_READ_LIGHT 0x01 /* Light Sensor during last echo */ +#define SRF08_READ_ECHO_1_HIGH 0x02 /* Range of first echo received */ +#define SRF08_READ_ECHO_1_LOW 0x03 /* Range of first echo received */ + +#define SRF08_CMD_RANGING_CM 0x51 /* Ranging Mode - Result in cm */ + +enum srf08_sensor_type { + SRF02, + SRF08, + SRF10, + SRF_MAX_TYPE +}; + +struct srf08_chip_info { + const int *sensitivity_avail; + int num_sensitivity_avail; + int sensitivity_default; + + /* default value of Range in mm */ + int range_default; +}; + +struct srf08_data { + struct i2c_client *client; + + /* + * Gain in the datasheet is called sensitivity here to distinct it + * from the gain used with amplifiers of adc's + */ + int sensitivity; + + /* max. Range in mm */ + int range_mm; + struct mutex lock; + + /* Sensor-Type */ + enum srf08_sensor_type sensor_type; + + /* Chip-specific information */ + const struct srf08_chip_info *chip_info; +}; + +/* + * in the documentation one can read about the "Gain" of the device + * which is used here for amplifying the signal and filtering out unwanted + * ones. + * But with ADC's this term is already used differently and that's why it + * is called "Sensitivity" here. + */ +static const struct srf08_chip_info srf02_chip_info = { + .sensitivity_avail = NULL, + .num_sensitivity_avail = 0, + .sensitivity_default = 0, + + .range_default = 0, +}; + +static const int srf08_sensitivity_avail[] = { + 94, 97, 100, 103, 107, 110, 114, 118, + 123, 128, 133, 139, 145, 152, 159, 168, + 177, 187, 199, 212, 227, 245, 265, 288, + 317, 352, 395, 450, 524, 626, 777, 1025 + }; + +static const struct srf08_chip_info srf08_chip_info = { + .sensitivity_avail = srf08_sensitivity_avail, + .num_sensitivity_avail = ARRAY_SIZE(srf08_sensitivity_avail), + .sensitivity_default = 1025, + + .range_default = 6020, +}; + +static const int srf10_sensitivity_avail[] = { + 40, 40, 50, 60, 70, 80, 100, 120, + 140, 200, 250, 300, 350, 400, 500, 600, + 700, + }; + +static const struct srf08_chip_info srf10_chip_info = { + .sensitivity_avail = srf10_sensitivity_avail, + .num_sensitivity_avail = ARRAY_SIZE(srf10_sensitivity_avail), + .sensitivity_default = 700, + + .range_default = 6020, +}; + +static int srf08_read_ranging(struct srf08_data *data) +{ + struct i2c_client *client = data->client; + int ret, i; + int waittime; + + mutex_lock(&data->lock); + + ret = i2c_smbus_write_byte_data(data->client, + SRF08_WRITE_COMMAND, SRF08_CMD_RANGING_CM); + if (ret < 0) { + dev_err(&client->dev, "write command - err: %d\n", ret); + mutex_unlock(&data->lock); + return ret; + } + + /* + * we read here until a correct version number shows up as + * suggested by the documentation + * + * with an ultrasonic speed of 343 m/s and a roundtrip of it + * sleep the expected duration and try to read from the device + * if nothing useful is read try it in a shorter grid + * + * polling for not more than 20 ms should be enough + */ + waittime = 1 + data->range_mm / 172; + msleep(waittime); + for (i = 0; i < 4; i++) { + ret = i2c_smbus_read_byte_data(data->client, + SRF08_READ_SW_REVISION); + + /* check if a valid version number is read */ + if (ret < 255 && ret > 0) + break; + msleep(5); + } + + if (ret >= 255 || ret <= 0) { + dev_err(&client->dev, "device not ready\n"); + mutex_unlock(&data->lock); + return -EIO; + } + + ret = i2c_smbus_read_word_swapped(data->client, + SRF08_READ_ECHO_1_HIGH); + if (ret < 0) { + dev_err(&client->dev, "cannot read distance: ret=%d\n", ret); + mutex_unlock(&data->lock); + return ret; + } + + mutex_unlock(&data->lock); + + return ret; +} + +static irqreturn_t srf08_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct srf08_data *data = iio_priv(indio_dev); + struct { + s16 chan; + aligned_s64 timestamp; + } scan = { }; + + scan.chan = srf08_read_ranging(data); + if (scan.chan < 0) + goto err; + + mutex_lock(&data->lock); + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + pf->timestamp); + + mutex_unlock(&data->lock); +err: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int srf08_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct srf08_data *data = iio_priv(indio_dev); + int ret; + + if (channel->type != IIO_DISTANCE) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = srf08_read_ranging(data); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* 1 LSB is 1 cm */ + *val = 0; + *val2 = 10000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static ssize_t srf08_show_range_mm_available(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "[0.043 0.043 11.008]\n"); +} + +static IIO_DEVICE_ATTR(sensor_max_range_available, S_IRUGO, + srf08_show_range_mm_available, NULL, 0); + +static ssize_t srf08_show_range_mm(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct srf08_data *data = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d.%03d\n", + data->range_mm / 1000, data->range_mm % 1000); +} + +/* + * set the range of the sensor to an even multiple of 43 mm + * which corresponds to 1 LSB in the register + * + * register value corresponding range + * 0x00 43 mm + * 0x01 86 mm + * 0x02 129 mm + * ... + * 0xFF 11008 mm + */ +static ssize_t srf08_write_range_mm(struct srf08_data *data, unsigned int val) +{ + int ret; + struct i2c_client *client = data->client; + unsigned int mod; + u8 regval; + + ret = val / 43 - 1; + mod = val % 43; + + if (mod || (ret < 0) || (ret > 255)) + return -EINVAL; + + regval = ret; + + mutex_lock(&data->lock); + + ret = i2c_smbus_write_byte_data(client, SRF08_WRITE_RANGE, regval); + if (ret < 0) { + dev_err(&client->dev, "write_range - err: %d\n", ret); + mutex_unlock(&data->lock); + return ret; + } + + data->range_mm = val; + + mutex_unlock(&data->lock); + + return 0; +} + +static ssize_t srf08_store_range_mm(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct srf08_data *data = iio_priv(indio_dev); + int ret; + int integer, fract; + + ret = iio_str_to_fixpoint(buf, 100, &integer, &fract); + if (ret) + return ret; + + ret = srf08_write_range_mm(data, integer * 1000 + fract); + if (ret < 0) + return ret; + + return len; +} + +static IIO_DEVICE_ATTR(sensor_max_range, S_IRUGO | S_IWUSR, + srf08_show_range_mm, srf08_store_range_mm, 0); + +static ssize_t srf08_show_sensitivity_available(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, len = 0; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct srf08_data *data = iio_priv(indio_dev); + + for (i = 0; i < data->chip_info->num_sensitivity_avail; i++) + if (data->chip_info->sensitivity_avail[i]) + len += sysfs_emit_at(buf, len, "%d ", + data->chip_info->sensitivity_avail[i]); + + len += sysfs_emit_at(buf, len, "\n"); + + return len; +} + +static IIO_DEVICE_ATTR(sensor_sensitivity_available, S_IRUGO, + srf08_show_sensitivity_available, NULL, 0); + +static ssize_t srf08_show_sensitivity(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct srf08_data *data = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", data->sensitivity); +} + +static ssize_t srf08_write_sensitivity(struct srf08_data *data, + unsigned int val) +{ + struct i2c_client *client = data->client; + int ret, i; + u8 regval; + + if (!val) + return -EINVAL; + + for (i = 0; i < data->chip_info->num_sensitivity_avail; i++) + if (val == data->chip_info->sensitivity_avail[i]) { + regval = i; + break; + } + + if (i >= data->chip_info->num_sensitivity_avail) + return -EINVAL; + + mutex_lock(&data->lock); + + ret = i2c_smbus_write_byte_data(client, SRF08_WRITE_MAX_GAIN, regval); + if (ret < 0) { + dev_err(&client->dev, "write_sensitivity - err: %d\n", ret); + mutex_unlock(&data->lock); + return ret; + } + + data->sensitivity = val; + + mutex_unlock(&data->lock); + + return 0; +} + +static ssize_t srf08_store_sensitivity(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct srf08_data *data = iio_priv(indio_dev); + int ret; + unsigned int val; + + ret = kstrtouint(buf, 10, &val); + if (ret) + return ret; + + ret = srf08_write_sensitivity(data, val); + if (ret < 0) + return ret; + + return len; +} + +static IIO_DEVICE_ATTR(sensor_sensitivity, S_IRUGO | S_IWUSR, + srf08_show_sensitivity, srf08_store_sensitivity, 0); + +static struct attribute *srf08_attributes[] = { + &iio_dev_attr_sensor_max_range.dev_attr.attr, + &iio_dev_attr_sensor_max_range_available.dev_attr.attr, + &iio_dev_attr_sensor_sensitivity.dev_attr.attr, + &iio_dev_attr_sensor_sensitivity_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group srf08_attribute_group = { + .attrs = srf08_attributes, +}; + +static const struct iio_chan_spec srf08_channels[] = { + { + .type = IIO_DISTANCE, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct iio_info srf08_info = { + .read_raw = srf08_read_raw, + .attrs = &srf08_attribute_group, +}; + +/* + * srf02 don't have an adjustable range or sensitivity, + * so we don't need attributes at all + */ +static const struct iio_info srf02_info = { + .read_raw = srf08_read_raw, +}; + +static int srf08_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct iio_dev *indio_dev; + struct srf08_data *data; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA | + I2C_FUNC_SMBUS_WRITE_BYTE_DATA | + I2C_FUNC_SMBUS_READ_WORD_DATA)) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->sensor_type = (enum srf08_sensor_type)id->driver_data; + + switch (data->sensor_type) { + case SRF02: + data->chip_info = &srf02_chip_info; + indio_dev->info = &srf02_info; + break; + case SRF08: + data->chip_info = &srf08_chip_info; + indio_dev->info = &srf08_info; + break; + case SRF10: + data->chip_info = &srf10_chip_info; + indio_dev->info = &srf08_info; + break; + default: + return -EINVAL; + } + + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = srf08_channels; + indio_dev->num_channels = ARRAY_SIZE(srf08_channels); + + mutex_init(&data->lock); + + ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, + iio_pollfunc_store_time, srf08_trigger_handler, NULL); + if (ret < 0) { + dev_err(&client->dev, "setup of iio triggered buffer failed\n"); + return ret; + } + + if (data->chip_info->range_default) { + /* + * set default range of device in mm here + * these register values cannot be read from the hardware + * therefore set driver specific default values + * + * srf02 don't have a default value so it'll be omitted + */ + ret = srf08_write_range_mm(data, + data->chip_info->range_default); + if (ret < 0) + return ret; + } + + if (data->chip_info->sensitivity_default) { + /* + * set default sensitivity of device here + * these register values cannot be read from the hardware + * therefore set driver specific default values + * + * srf02 don't have a default value so it'll be omitted + */ + ret = srf08_write_sensitivity(data, + data->chip_info->sensitivity_default); + if (ret < 0) + return ret; + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct of_device_id of_srf08_match[] = { + { .compatible = "devantech,srf02", (void *)SRF02 }, + { .compatible = "devantech,srf08", (void *)SRF08 }, + { .compatible = "devantech,srf10", (void *)SRF10 }, + { } +}; + +MODULE_DEVICE_TABLE(of, of_srf08_match); + +static const struct i2c_device_id srf08_id[] = { + { .name = "srf02", .driver_data = SRF02 }, + { .name = "srf08", .driver_data = SRF08 }, + { .name = "srf10", .driver_data = SRF10 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, srf08_id); + +static struct i2c_driver srf08_driver = { + .driver = { + .name = "srf08", + .of_match_table = of_srf08_match, + }, + .probe = srf08_probe, + .id_table = srf08_id, +}; +module_i2c_driver(srf08_driver); + +MODULE_AUTHOR("Andreas Klinger "); +MODULE_DESCRIPTION("Devantech SRF02/SRF08/SRF10 i2c ultrasonic ranger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/proximity/sx9310.c b/drivers/iio/proximity/sx9310.c new file mode 100644 index 000000000..79ba46d8a --- /dev/null +++ b/drivers/iio/proximity/sx9310.c @@ -0,0 +1,1038 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2018 Google LLC. + * + * Driver for Semtech's SX9310/SX9311 capacitive proximity/button solution. + * Based on SX9500 driver and Semtech driver using the input framework + * . + * Reworked in April 2019 by Evan Green + * and in January 2020 by Daniel Campello . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sx_common.h" + +/* Register definitions. */ +#define SX9310_REG_IRQ_SRC SX_COMMON_REG_IRQ_SRC +#define SX9310_REG_STAT0 0x01 +#define SX9310_REG_STAT1 0x02 +#define SX9310_REG_STAT1_COMPSTAT_MASK GENMASK(3, 0) +#define SX9310_REG_IRQ_MSK 0x03 +#define SX9310_CONVDONE_IRQ BIT(3) +#define SX9310_FAR_IRQ BIT(5) +#define SX9310_CLOSE_IRQ BIT(6) +#define SX9310_REG_IRQ_FUNC 0x04 + +#define SX9310_REG_PROX_CTRL0 0x10 +#define SX9310_REG_PROX_CTRL0_SENSOREN_MASK GENMASK(3, 0) +#define SX9310_REG_PROX_CTRL0_SCANPERIOD_MASK GENMASK(7, 4) +#define SX9310_REG_PROX_CTRL0_SCANPERIOD_15MS 0x01 +#define SX9310_REG_PROX_CTRL1 0x11 +#define SX9310_REG_PROX_CTRL2 0x12 +#define SX9310_REG_PROX_CTRL2_COMBMODE_MASK GENMASK(7, 6) +#define SX9310_REG_PROX_CTRL2_COMBMODE_CS0_CS1_CS2_CS3 (0x03 << 6) +#define SX9310_REG_PROX_CTRL2_COMBMODE_CS1_CS2 (0x02 << 6) +#define SX9310_REG_PROX_CTRL2_COMBMODE_CS0_CS1 (0x01 << 6) +#define SX9310_REG_PROX_CTRL2_COMBMODE_CS3 (0x00 << 6) +#define SX9310_REG_PROX_CTRL2_SHIELDEN_MASK GENMASK(3, 2) +#define SX9310_REG_PROX_CTRL2_SHIELDEN_DYNAMIC (0x01 << 2) +#define SX9310_REG_PROX_CTRL2_SHIELDEN_GROUND (0x02 << 2) +#define SX9310_REG_PROX_CTRL3 0x13 +#define SX9310_REG_PROX_CTRL3_GAIN0_MASK GENMASK(3, 2) +#define SX9310_REG_PROX_CTRL3_GAIN0_X8 (0x03 << 2) +#define SX9310_REG_PROX_CTRL3_GAIN12_MASK GENMASK(1, 0) +#define SX9310_REG_PROX_CTRL3_GAIN12_X4 0x02 +#define SX9310_REG_PROX_CTRL4 0x14 +#define SX9310_REG_PROX_CTRL4_RESOLUTION_MASK GENMASK(2, 0) +#define SX9310_REG_PROX_CTRL4_RESOLUTION_FINEST 0x07 +#define SX9310_REG_PROX_CTRL4_RESOLUTION_VERY_FINE 0x06 +#define SX9310_REG_PROX_CTRL4_RESOLUTION_FINE 0x05 +#define SX9310_REG_PROX_CTRL4_RESOLUTION_MEDIUM 0x04 +#define SX9310_REG_PROX_CTRL4_RESOLUTION_MEDIUM_COARSE 0x03 +#define SX9310_REG_PROX_CTRL4_RESOLUTION_COARSE 0x02 +#define SX9310_REG_PROX_CTRL4_RESOLUTION_VERY_COARSE 0x01 +#define SX9310_REG_PROX_CTRL4_RESOLUTION_COARSEST 0x00 +#define SX9310_REG_PROX_CTRL5 0x15 +#define SX9310_REG_PROX_CTRL5_RANGE_SMALL (0x03 << 6) +#define SX9310_REG_PROX_CTRL5_STARTUPSENS_MASK GENMASK(3, 2) +#define SX9310_REG_PROX_CTRL5_STARTUPSENS_CS1 (0x01 << 2) +#define SX9310_REG_PROX_CTRL5_RAWFILT_MASK GENMASK(1, 0) +#define SX9310_REG_PROX_CTRL5_RAWFILT_SHIFT 0 +#define SX9310_REG_PROX_CTRL5_RAWFILT_1P25 0x02 +#define SX9310_REG_PROX_CTRL6 0x16 +#define SX9310_REG_PROX_CTRL6_AVGTHRESH_DEFAULT 0x20 +#define SX9310_REG_PROX_CTRL7 0x17 +#define SX9310_REG_PROX_CTRL7_AVGNEGFILT_2 (0x01 << 3) +#define SX9310_REG_PROX_CTRL7_AVGPOSFILT_MASK GENMASK(2, 0) +#define SX9310_REG_PROX_CTRL7_AVGPOSFILT_SHIFT 0 +#define SX9310_REG_PROX_CTRL7_AVGPOSFILT_512 0x05 +#define SX9310_REG_PROX_CTRL8 0x18 +#define SX9310_REG_PROX_CTRL8_9_PTHRESH_MASK GENMASK(7, 3) +#define SX9310_REG_PROX_CTRL9 0x19 +#define SX9310_REG_PROX_CTRL8_9_PTHRESH_28 (0x08 << 3) +#define SX9310_REG_PROX_CTRL8_9_PTHRESH_96 (0x11 << 3) +#define SX9310_REG_PROX_CTRL8_9_BODYTHRESH_900 0x03 +#define SX9310_REG_PROX_CTRL8_9_BODYTHRESH_1500 0x05 +#define SX9310_REG_PROX_CTRL10 0x1a +#define SX9310_REG_PROX_CTRL10_HYST_MASK GENMASK(5, 4) +#define SX9310_REG_PROX_CTRL10_HYST_6PCT (0x01 << 4) +#define SX9310_REG_PROX_CTRL10_CLOSE_DEBOUNCE_MASK GENMASK(3, 2) +#define SX9310_REG_PROX_CTRL10_FAR_DEBOUNCE_MASK GENMASK(1, 0) +#define SX9310_REG_PROX_CTRL10_FAR_DEBOUNCE_2 0x01 +#define SX9310_REG_PROX_CTRL11 0x1b +#define SX9310_REG_PROX_CTRL12 0x1c +#define SX9310_REG_PROX_CTRL13 0x1d +#define SX9310_REG_PROX_CTRL14 0x1e +#define SX9310_REG_PROX_CTRL15 0x1f +#define SX9310_REG_PROX_CTRL16 0x20 +#define SX9310_REG_PROX_CTRL17 0x21 +#define SX9310_REG_PROX_CTRL18 0x22 +#define SX9310_REG_PROX_CTRL19 0x23 +#define SX9310_REG_SAR_CTRL0 0x2a +#define SX9310_REG_SAR_CTRL0_SARDEB_4_SAMPLES (0x02 << 5) +#define SX9310_REG_SAR_CTRL0_SARHYST_8 (0x02 << 3) +#define SX9310_REG_SAR_CTRL1 0x2b +/* Each increment of the slope register is 0.0078125. */ +#define SX9310_REG_SAR_CTRL1_SLOPE(_hnslope) (_hnslope / 78125) +#define SX9310_REG_SAR_CTRL2 0x2c +#define SX9310_REG_SAR_CTRL2_SAROFFSET_DEFAULT 0x3c + +#define SX9310_REG_SENSOR_SEL 0x30 +#define SX9310_REG_USE_MSB 0x31 +#define SX9310_REG_USE_LSB 0x32 +#define SX9310_REG_AVG_MSB 0x33 +#define SX9310_REG_AVG_LSB 0x34 +#define SX9310_REG_DIFF_MSB 0x35 +#define SX9310_REG_DIFF_LSB 0x36 +#define SX9310_REG_OFFSET_MSB 0x37 +#define SX9310_REG_OFFSET_LSB 0x38 +#define SX9310_REG_SAR_MSB 0x39 +#define SX9310_REG_SAR_LSB 0x3a +#define SX9310_REG_I2C_ADDR 0x40 +#define SX9310_REG_PAUSE 0x41 +#define SX9310_REG_WHOAMI 0x42 +#define SX9310_WHOAMI_VALUE 0x01 +#define SX9311_WHOAMI_VALUE 0x02 +#define SX9310_REG_RESET 0x7f + + +/* 4 hardware channels, as defined in STAT0: COMB, CS2, CS1 and CS0. */ +#define SX9310_NUM_CHANNELS 4 +static_assert(SX9310_NUM_CHANNELS <= SX_COMMON_MAX_NUM_CHANNELS); + +#define SX9310_NAMED_CHANNEL(idx, name) \ +{ \ + .type = IIO_PROXIMITY, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_HARDWAREGAIN), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_separate_available = \ + BIT(IIO_CHAN_INFO_HARDWAREGAIN), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .indexed = 1, \ + .channel = idx, \ + .extend_name = name, \ + .address = SX9310_REG_DIFF_MSB, \ + .event_spec = sx_common_events, \ + .num_event_specs = ARRAY_SIZE(sx_common_events), \ + .scan_index = idx, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 12, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ +} +#define SX9310_CHANNEL(idx) SX9310_NAMED_CHANNEL(idx, NULL) + +struct sx931x_info { + const char *name; + unsigned int whoami; +}; + +static const struct iio_chan_spec sx9310_channels[] = { + SX9310_CHANNEL(0), /* CS0 */ + SX9310_CHANNEL(1), /* CS1 */ + SX9310_CHANNEL(2), /* CS2 */ + SX9310_NAMED_CHANNEL(3, "comb"), /* COMB */ + + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +/* + * Each entry contains the integer part (val) and the fractional part, in micro + * seconds. It conforms to the IIO output IIO_VAL_INT_PLUS_MICRO. + */ +static const struct { + int val; + int val2; +} sx9310_samp_freq_table[] = { + { 500, 0 }, /* 0000: Min (no idle time) */ + { 66, 666666 }, /* 0001: 15 ms */ + { 33, 333333 }, /* 0010: 30 ms (Typ.) */ + { 22, 222222 }, /* 0011: 45 ms */ + { 16, 666666 }, /* 0100: 60 ms */ + { 11, 111111 }, /* 0101: 90 ms */ + { 8, 333333 }, /* 0110: 120 ms */ + { 5, 0 }, /* 0111: 200 ms */ + { 2, 500000 }, /* 1000: 400 ms */ + { 1, 666666 }, /* 1001: 600 ms */ + { 1, 250000 }, /* 1010: 800 ms */ + { 1, 0 }, /* 1011: 1 s */ + { 0, 500000 }, /* 1100: 2 s */ + { 0, 333333 }, /* 1101: 3 s */ + { 0, 250000 }, /* 1110: 4 s */ + { 0, 200000 }, /* 1111: 5 s */ +}; +static const unsigned int sx9310_scan_period_table[] = { + 2, 15, 30, 45, 60, 90, 120, 200, + 400, 600, 800, 1000, 2000, 3000, 4000, 5000, +}; + +static const struct regmap_range sx9310_writable_reg_ranges[] = { + regmap_reg_range(SX9310_REG_IRQ_MSK, SX9310_REG_IRQ_FUNC), + regmap_reg_range(SX9310_REG_PROX_CTRL0, SX9310_REG_PROX_CTRL19), + regmap_reg_range(SX9310_REG_SAR_CTRL0, SX9310_REG_SAR_CTRL2), + regmap_reg_range(SX9310_REG_SENSOR_SEL, SX9310_REG_SENSOR_SEL), + regmap_reg_range(SX9310_REG_OFFSET_MSB, SX9310_REG_OFFSET_LSB), + regmap_reg_range(SX9310_REG_PAUSE, SX9310_REG_PAUSE), + regmap_reg_range(SX9310_REG_RESET, SX9310_REG_RESET), +}; + +static const struct regmap_access_table sx9310_writeable_regs = { + .yes_ranges = sx9310_writable_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(sx9310_writable_reg_ranges), +}; + +static const struct regmap_range sx9310_readable_reg_ranges[] = { + regmap_reg_range(SX9310_REG_IRQ_SRC, SX9310_REG_IRQ_FUNC), + regmap_reg_range(SX9310_REG_PROX_CTRL0, SX9310_REG_PROX_CTRL19), + regmap_reg_range(SX9310_REG_SAR_CTRL0, SX9310_REG_SAR_CTRL2), + regmap_reg_range(SX9310_REG_SENSOR_SEL, SX9310_REG_SAR_LSB), + regmap_reg_range(SX9310_REG_I2C_ADDR, SX9310_REG_WHOAMI), + regmap_reg_range(SX9310_REG_RESET, SX9310_REG_RESET), +}; + +static const struct regmap_access_table sx9310_readable_regs = { + .yes_ranges = sx9310_readable_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(sx9310_readable_reg_ranges), +}; + +static const struct regmap_range sx9310_volatile_reg_ranges[] = { + regmap_reg_range(SX9310_REG_IRQ_SRC, SX9310_REG_STAT1), + regmap_reg_range(SX9310_REG_USE_MSB, SX9310_REG_DIFF_LSB), + regmap_reg_range(SX9310_REG_SAR_MSB, SX9310_REG_SAR_LSB), + regmap_reg_range(SX9310_REG_RESET, SX9310_REG_RESET), +}; + +static const struct regmap_access_table sx9310_volatile_regs = { + .yes_ranges = sx9310_volatile_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(sx9310_volatile_reg_ranges), +}; + +static const struct regmap_config sx9310_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = SX9310_REG_RESET, + .cache_type = REGCACHE_RBTREE, + + .wr_table = &sx9310_writeable_regs, + .rd_table = &sx9310_readable_regs, + .volatile_table = &sx9310_volatile_regs, +}; + +static int sx9310_read_prox_data(struct sx_common_data *data, + const struct iio_chan_spec *chan, __be16 *val) +{ + int ret; + + ret = regmap_write(data->regmap, SX9310_REG_SENSOR_SEL, chan->channel); + if (ret) + return ret; + + return regmap_bulk_read(data->regmap, chan->address, val, sizeof(*val)); +} + +/* + * If we have no interrupt support, we have to wait for a scan period + * after enabling a channel to get a result. + */ +static int sx9310_wait_for_sample(struct sx_common_data *data) +{ + int ret; + unsigned int val; + + ret = regmap_read(data->regmap, SX9310_REG_PROX_CTRL0, &val); + if (ret) + return ret; + + val = FIELD_GET(SX9310_REG_PROX_CTRL0_SCANPERIOD_MASK, val); + + msleep(sx9310_scan_period_table[val]); + + return 0; +} + +static int sx9310_read_gain(struct sx_common_data *data, + const struct iio_chan_spec *chan, int *val) +{ + unsigned int regval, gain; + int ret; + + ret = regmap_read(data->regmap, SX9310_REG_PROX_CTRL3, ®val); + if (ret) + return ret; + + switch (chan->channel) { + case 0: + case 3: + gain = FIELD_GET(SX9310_REG_PROX_CTRL3_GAIN0_MASK, regval); + break; + case 1: + case 2: + gain = FIELD_GET(SX9310_REG_PROX_CTRL3_GAIN12_MASK, regval); + break; + default: + return -EINVAL; + } + + *val = 1 << gain; + + return IIO_VAL_INT; +} + +static int sx9310_read_samp_freq(struct sx_common_data *data, int *val, int *val2) +{ + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, SX9310_REG_PROX_CTRL0, ®val); + if (ret) + return ret; + + regval = FIELD_GET(SX9310_REG_PROX_CTRL0_SCANPERIOD_MASK, regval); + *val = sx9310_samp_freq_table[regval].val; + *val2 = sx9310_samp_freq_table[regval].val2; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int sx9310_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, + int *val2, long mask) +{ + struct sx_common_data *data = iio_priv(indio_dev); + int ret; + + if (chan->type != IIO_PROXIMITY) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = sx_common_read_proximity(data, chan, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_HARDWAREGAIN: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = sx9310_read_gain(data, chan, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + return sx9310_read_samp_freq(data, val, val2); + default: + return -EINVAL; + } +} + +static const int sx9310_gain_vals[] = { 1, 2, 4, 8 }; + +static int sx9310_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + if (chan->type != IIO_PROXIMITY) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(sx9310_gain_vals); + *vals = sx9310_gain_vals; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *type = IIO_VAL_INT_PLUS_MICRO; + *length = ARRAY_SIZE(sx9310_samp_freq_table) * 2; + *vals = (int *)sx9310_samp_freq_table; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const unsigned int sx9310_pthresh_codes[] = { + 2, 4, 6, 8, 12, 16, 20, 24, 28, 32, 40, 48, 56, 64, 72, 80, 88, 96, 112, + 128, 144, 160, 192, 224, 256, 320, 384, 512, 640, 768, 1024, 1536 +}; + +static int sx9310_get_thresh_reg(unsigned int channel) +{ + switch (channel) { + case 0: + case 3: + return SX9310_REG_PROX_CTRL8; + case 1: + case 2: + return SX9310_REG_PROX_CTRL9; + default: + return -EINVAL; + } +} + +static int sx9310_read_thresh(struct sx_common_data *data, + const struct iio_chan_spec *chan, int *val) +{ + unsigned int reg; + unsigned int regval; + int ret; + + reg = ret = sx9310_get_thresh_reg(chan->channel); + if (ret < 0) + return ret; + + ret = regmap_read(data->regmap, reg, ®val); + if (ret) + return ret; + + regval = FIELD_GET(SX9310_REG_PROX_CTRL8_9_PTHRESH_MASK, regval); + if (regval >= ARRAY_SIZE(sx9310_pthresh_codes)) + return -EINVAL; + + *val = sx9310_pthresh_codes[regval]; + return IIO_VAL_INT; +} + +static int sx9310_read_hysteresis(struct sx_common_data *data, + const struct iio_chan_spec *chan, int *val) +{ + unsigned int regval, pthresh; + int ret; + + ret = sx9310_read_thresh(data, chan, &pthresh); + if (ret < 0) + return ret; + + ret = regmap_read(data->regmap, SX9310_REG_PROX_CTRL10, ®val); + if (ret) + return ret; + + regval = FIELD_GET(SX9310_REG_PROX_CTRL10_HYST_MASK, regval); + if (!regval) + regval = 5; + + /* regval is at most 5 */ + *val = pthresh >> (5 - regval); + + return IIO_VAL_INT; +} + +static int sx9310_read_far_debounce(struct sx_common_data *data, int *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, SX9310_REG_PROX_CTRL10, ®val); + if (ret) + return ret; + + regval = FIELD_GET(SX9310_REG_PROX_CTRL10_FAR_DEBOUNCE_MASK, regval); + if (regval) + *val = 1 << regval; + else + *val = 0; + + return IIO_VAL_INT; +} + +static int sx9310_read_close_debounce(struct sx_common_data *data, int *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, SX9310_REG_PROX_CTRL10, ®val); + if (ret) + return ret; + + regval = FIELD_GET(SX9310_REG_PROX_CTRL10_CLOSE_DEBOUNCE_MASK, regval); + if (regval) + *val = 1 << regval; + else + *val = 0; + + return IIO_VAL_INT; +} + +static int sx9310_read_event_val(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, int *val2) +{ + struct sx_common_data *data = iio_priv(indio_dev); + + if (chan->type != IIO_PROXIMITY) + return -EINVAL; + + switch (info) { + case IIO_EV_INFO_VALUE: + return sx9310_read_thresh(data, chan, val); + case IIO_EV_INFO_PERIOD: + switch (dir) { + case IIO_EV_DIR_RISING: + return sx9310_read_far_debounce(data, val); + case IIO_EV_DIR_FALLING: + return sx9310_read_close_debounce(data, val); + default: + return -EINVAL; + } + case IIO_EV_INFO_HYSTERESIS: + return sx9310_read_hysteresis(data, chan, val); + default: + return -EINVAL; + } +} + +static int sx9310_write_thresh(struct sx_common_data *data, + const struct iio_chan_spec *chan, int val) +{ + unsigned int reg; + unsigned int regval; + int ret, i; + + reg = ret = sx9310_get_thresh_reg(chan->channel); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(sx9310_pthresh_codes); i++) { + if (sx9310_pthresh_codes[i] == val) { + regval = i; + break; + } + } + + if (i == ARRAY_SIZE(sx9310_pthresh_codes)) + return -EINVAL; + + regval = FIELD_PREP(SX9310_REG_PROX_CTRL8_9_PTHRESH_MASK, regval); + + guard(mutex)(&data->mutex); + return regmap_update_bits(data->regmap, reg, + SX9310_REG_PROX_CTRL8_9_PTHRESH_MASK, regval); +} + +static int sx9310_write_hysteresis(struct sx_common_data *data, + const struct iio_chan_spec *chan, int _val) +{ + unsigned int hyst, val = _val; + int ret, pthresh; + + ret = sx9310_read_thresh(data, chan, &pthresh); + if (ret < 0) + return ret; + + if (val == 0) + hyst = 0; + else if (val == pthresh >> 2) + hyst = 3; + else if (val == pthresh >> 3) + hyst = 2; + else if (val == pthresh >> 4) + hyst = 1; + else + return -EINVAL; + + hyst = FIELD_PREP(SX9310_REG_PROX_CTRL10_HYST_MASK, hyst); + + guard(mutex)(&data->mutex); + return regmap_update_bits(data->regmap, SX9310_REG_PROX_CTRL10, + SX9310_REG_PROX_CTRL10_HYST_MASK, hyst); +} + +static int sx9310_write_far_debounce(struct sx_common_data *data, int val) +{ + unsigned int regval; + + if (val > 0) + val = ilog2(val); + if (!FIELD_FIT(SX9310_REG_PROX_CTRL10_FAR_DEBOUNCE_MASK, val)) + return -EINVAL; + + regval = FIELD_PREP(SX9310_REG_PROX_CTRL10_FAR_DEBOUNCE_MASK, val); + + guard(mutex)(&data->mutex); + return regmap_update_bits(data->regmap, SX9310_REG_PROX_CTRL10, + SX9310_REG_PROX_CTRL10_FAR_DEBOUNCE_MASK, + regval); +} + +static int sx9310_write_close_debounce(struct sx_common_data *data, int val) +{ + unsigned int regval; + + if (val > 0) + val = ilog2(val); + if (!FIELD_FIT(SX9310_REG_PROX_CTRL10_CLOSE_DEBOUNCE_MASK, val)) + return -EINVAL; + + regval = FIELD_PREP(SX9310_REG_PROX_CTRL10_CLOSE_DEBOUNCE_MASK, val); + + guard(mutex)(&data->mutex); + return regmap_update_bits(data->regmap, SX9310_REG_PROX_CTRL10, + SX9310_REG_PROX_CTRL10_CLOSE_DEBOUNCE_MASK, + regval); +} + +static int sx9310_write_event_val(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, int val2) +{ + struct sx_common_data *data = iio_priv(indio_dev); + + if (chan->type != IIO_PROXIMITY) + return -EINVAL; + + switch (info) { + case IIO_EV_INFO_VALUE: + return sx9310_write_thresh(data, chan, val); + case IIO_EV_INFO_PERIOD: + switch (dir) { + case IIO_EV_DIR_RISING: + return sx9310_write_far_debounce(data, val); + case IIO_EV_DIR_FALLING: + return sx9310_write_close_debounce(data, val); + default: + return -EINVAL; + } + case IIO_EV_INFO_HYSTERESIS: + return sx9310_write_hysteresis(data, chan, val); + default: + return -EINVAL; + } +} + +static int sx9310_set_samp_freq(struct sx_common_data *data, int val, int val2) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sx9310_samp_freq_table); i++) + if (val == sx9310_samp_freq_table[i].val && + val2 == sx9310_samp_freq_table[i].val2) + break; + + if (i == ARRAY_SIZE(sx9310_samp_freq_table)) + return -EINVAL; + + guard(mutex)(&data->mutex); + return regmap_update_bits( + data->regmap, SX9310_REG_PROX_CTRL0, + SX9310_REG_PROX_CTRL0_SCANPERIOD_MASK, + FIELD_PREP(SX9310_REG_PROX_CTRL0_SCANPERIOD_MASK, i)); +} + +static int sx9310_write_gain(struct sx_common_data *data, + const struct iio_chan_spec *chan, int val) +{ + unsigned int gain, mask; + + gain = ilog2(val); + + switch (chan->channel) { + case 0: + case 3: + mask = SX9310_REG_PROX_CTRL3_GAIN0_MASK; + gain = FIELD_PREP(SX9310_REG_PROX_CTRL3_GAIN0_MASK, gain); + break; + case 1: + case 2: + mask = SX9310_REG_PROX_CTRL3_GAIN12_MASK; + gain = FIELD_PREP(SX9310_REG_PROX_CTRL3_GAIN12_MASK, gain); + break; + default: + return -EINVAL; + } + + guard(mutex)(&data->mutex); + return regmap_update_bits(data->regmap, SX9310_REG_PROX_CTRL3, mask, + gain); +} + +static int sx9310_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int val, int val2, + long mask) +{ + struct sx_common_data *data = iio_priv(indio_dev); + + if (chan->type != IIO_PROXIMITY) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return sx9310_set_samp_freq(data, val, val2); + case IIO_CHAN_INFO_HARDWAREGAIN: + return sx9310_write_gain(data, chan, val); + default: + return -EINVAL; + } +} + +static const struct sx_common_reg_default sx9310_default_regs[] = { + { SX9310_REG_IRQ_MSK, 0x00 }, + { SX9310_REG_IRQ_FUNC, 0x00 }, + /* + * The lower 4 bits should not be set as it enable sensors measurements. + * Turning the detection on before the configuration values are set to + * good values can cause the device to return erroneous readings. + */ + { SX9310_REG_PROX_CTRL0, SX9310_REG_PROX_CTRL0_SCANPERIOD_15MS }, + { SX9310_REG_PROX_CTRL1, 0x00 }, + { SX9310_REG_PROX_CTRL2, SX9310_REG_PROX_CTRL2_COMBMODE_CS1_CS2 | + SX9310_REG_PROX_CTRL2_SHIELDEN_DYNAMIC }, + { SX9310_REG_PROX_CTRL3, SX9310_REG_PROX_CTRL3_GAIN0_X8 | + SX9310_REG_PROX_CTRL3_GAIN12_X4 }, + { SX9310_REG_PROX_CTRL4, SX9310_REG_PROX_CTRL4_RESOLUTION_FINEST }, + { SX9310_REG_PROX_CTRL5, SX9310_REG_PROX_CTRL5_RANGE_SMALL | + SX9310_REG_PROX_CTRL5_STARTUPSENS_CS1 | + SX9310_REG_PROX_CTRL5_RAWFILT_1P25 }, + { SX9310_REG_PROX_CTRL6, SX9310_REG_PROX_CTRL6_AVGTHRESH_DEFAULT }, + { SX9310_REG_PROX_CTRL7, SX9310_REG_PROX_CTRL7_AVGNEGFILT_2 | + SX9310_REG_PROX_CTRL7_AVGPOSFILT_512 }, + { SX9310_REG_PROX_CTRL8, SX9310_REG_PROX_CTRL8_9_PTHRESH_96 | + SX9310_REG_PROX_CTRL8_9_BODYTHRESH_1500 }, + { SX9310_REG_PROX_CTRL9, SX9310_REG_PROX_CTRL8_9_PTHRESH_28 | + SX9310_REG_PROX_CTRL8_9_BODYTHRESH_900 }, + { SX9310_REG_PROX_CTRL10, SX9310_REG_PROX_CTRL10_HYST_6PCT | + SX9310_REG_PROX_CTRL10_FAR_DEBOUNCE_2 }, + { SX9310_REG_PROX_CTRL11, 0x00 }, + { SX9310_REG_PROX_CTRL12, 0x00 }, + { SX9310_REG_PROX_CTRL13, 0x00 }, + { SX9310_REG_PROX_CTRL14, 0x00 }, + { SX9310_REG_PROX_CTRL15, 0x00 }, + { SX9310_REG_PROX_CTRL16, 0x00 }, + { SX9310_REG_PROX_CTRL17, 0x00 }, + { SX9310_REG_PROX_CTRL18, 0x00 }, + { SX9310_REG_PROX_CTRL19, 0x00 }, + { SX9310_REG_SAR_CTRL0, SX9310_REG_SAR_CTRL0_SARDEB_4_SAMPLES | + SX9310_REG_SAR_CTRL0_SARHYST_8 }, + { SX9310_REG_SAR_CTRL1, SX9310_REG_SAR_CTRL1_SLOPE(10781250) }, + { SX9310_REG_SAR_CTRL2, SX9310_REG_SAR_CTRL2_SAROFFSET_DEFAULT }, +}; + +/* Activate all channels and perform an initial compensation. */ +static int sx9310_init_compensation(struct iio_dev *indio_dev) +{ + struct sx_common_data *data = iio_priv(indio_dev); + int ret; + unsigned int val; + unsigned int ctrl0; + + ret = regmap_read(data->regmap, SX9310_REG_PROX_CTRL0, &ctrl0); + if (ret) + return ret; + + /* run the compensation phase on all channels */ + ret = regmap_write(data->regmap, SX9310_REG_PROX_CTRL0, + ctrl0 | SX9310_REG_PROX_CTRL0_SENSOREN_MASK); + if (ret) + return ret; + + ret = regmap_read_poll_timeout(data->regmap, SX9310_REG_STAT1, val, + !(val & SX9310_REG_STAT1_COMPSTAT_MASK), + 20000, 2000000); + if (ret) + return ret; + + regmap_write(data->regmap, SX9310_REG_PROX_CTRL0, ctrl0); + return ret; +} + +static const struct sx_common_reg_default * +sx9310_get_default_reg(struct device *dev, int idx, + struct sx_common_reg_default *reg_def) +{ + u32 combined[SX9310_NUM_CHANNELS]; + u32 start = 0, raw = 0, pos = 0; + unsigned long comb_mask = 0; + int ret, i, count; + const char *res; + + memcpy(reg_def, &sx9310_default_regs[idx], sizeof(*reg_def)); + switch (reg_def->reg) { + case SX9310_REG_PROX_CTRL2: + if (device_property_read_bool(dev, "semtech,cs0-ground")) { + reg_def->def &= ~SX9310_REG_PROX_CTRL2_SHIELDEN_MASK; + reg_def->def |= SX9310_REG_PROX_CTRL2_SHIELDEN_GROUND; + } + + count = device_property_count_u32(dev, "semtech,combined-sensors"); + if (count < 0 || count > ARRAY_SIZE(combined)) + break; + ret = device_property_read_u32_array(dev, "semtech,combined-sensors", + combined, count); + if (ret) + break; + + for (i = 0; i < count; i++) + comb_mask |= BIT(combined[i]); + + reg_def->def &= ~SX9310_REG_PROX_CTRL2_COMBMODE_MASK; + if (comb_mask == (BIT(3) | BIT(2) | BIT(1) | BIT(0))) + reg_def->def |= SX9310_REG_PROX_CTRL2_COMBMODE_CS0_CS1_CS2_CS3; + else if (comb_mask == (BIT(1) | BIT(2))) + reg_def->def |= SX9310_REG_PROX_CTRL2_COMBMODE_CS1_CS2; + else if (comb_mask == (BIT(0) | BIT(1))) + reg_def->def |= SX9310_REG_PROX_CTRL2_COMBMODE_CS0_CS1; + else if (comb_mask == BIT(3)) + reg_def->def |= SX9310_REG_PROX_CTRL2_COMBMODE_CS3; + + break; + case SX9310_REG_PROX_CTRL4: + ret = device_property_read_string(dev, "semtech,resolution", &res); + if (ret) + break; + + reg_def->def &= ~SX9310_REG_PROX_CTRL4_RESOLUTION_MASK; + if (!strcmp(res, "coarsest")) + reg_def->def |= SX9310_REG_PROX_CTRL4_RESOLUTION_COARSEST; + else if (!strcmp(res, "very-coarse")) + reg_def->def |= SX9310_REG_PROX_CTRL4_RESOLUTION_VERY_COARSE; + else if (!strcmp(res, "coarse")) + reg_def->def |= SX9310_REG_PROX_CTRL4_RESOLUTION_COARSE; + else if (!strcmp(res, "medium-coarse")) + reg_def->def |= SX9310_REG_PROX_CTRL4_RESOLUTION_MEDIUM_COARSE; + else if (!strcmp(res, "medium")) + reg_def->def |= SX9310_REG_PROX_CTRL4_RESOLUTION_MEDIUM; + else if (!strcmp(res, "fine")) + reg_def->def |= SX9310_REG_PROX_CTRL4_RESOLUTION_FINE; + else if (!strcmp(res, "very-fine")) + reg_def->def |= SX9310_REG_PROX_CTRL4_RESOLUTION_VERY_FINE; + else if (!strcmp(res, "finest")) + reg_def->def |= SX9310_REG_PROX_CTRL4_RESOLUTION_FINEST; + + break; + case SX9310_REG_PROX_CTRL5: + ret = device_property_read_u32(dev, "semtech,startup-sensor", &start); + if (ret) { + start = FIELD_GET(SX9310_REG_PROX_CTRL5_STARTUPSENS_MASK, + reg_def->def); + } + + reg_def->def &= ~SX9310_REG_PROX_CTRL5_STARTUPSENS_MASK; + reg_def->def |= FIELD_PREP(SX9310_REG_PROX_CTRL5_STARTUPSENS_MASK, + start); + + ret = device_property_read_u32(dev, "semtech,proxraw-strength", &raw); + if (ret) { + raw = FIELD_GET(SX9310_REG_PROX_CTRL5_RAWFILT_MASK, + reg_def->def); + } else { + raw = ilog2(raw); + } + + reg_def->def &= ~SX9310_REG_PROX_CTRL5_RAWFILT_MASK; + reg_def->def |= FIELD_PREP(SX9310_REG_PROX_CTRL5_RAWFILT_MASK, + raw); + break; + case SX9310_REG_PROX_CTRL7: + ret = device_property_read_u32(dev, "semtech,avg-pos-strength", &pos); + if (ret) + break; + + /* Powers of 2, except for a gap between 16 and 64 */ + pos = clamp(ilog2(pos), 3, 11) - (pos >= 32 ? 4 : 3); + reg_def->def &= ~SX9310_REG_PROX_CTRL7_AVGPOSFILT_MASK; + reg_def->def |= FIELD_PREP(SX9310_REG_PROX_CTRL7_AVGPOSFILT_MASK, + pos); + break; + } + + return reg_def; +} + +static int sx9310_check_whoami(struct device *dev, + struct iio_dev *indio_dev) +{ + struct sx_common_data *data = iio_priv(indio_dev); + const struct sx931x_info *ddata; + unsigned int whoami; + int ret; + + ret = regmap_read(data->regmap, SX9310_REG_WHOAMI, &whoami); + if (ret) + return ret; + + ddata = device_get_match_data(dev); + if (ddata->whoami != whoami) + return -ENODEV; + + indio_dev->name = ddata->name; + + return 0; +} + +static const struct sx_common_chip_info sx9310_chip_info = { + .reg_stat = SX9310_REG_STAT0, + .reg_irq_msk = SX9310_REG_IRQ_MSK, + .reg_enable_chan = SX9310_REG_PROX_CTRL0, + .reg_reset = SX9310_REG_RESET, + + .mask_enable_chan = SX9310_REG_STAT1_COMPSTAT_MASK, + .irq_msk_offset = 3, + .num_channels = SX9310_NUM_CHANNELS, + .num_default_regs = ARRAY_SIZE(sx9310_default_regs), + + .ops = { + .read_prox_data = sx9310_read_prox_data, + .check_whoami = sx9310_check_whoami, + .init_compensation = sx9310_init_compensation, + .wait_for_sample = sx9310_wait_for_sample, + .get_default_reg = sx9310_get_default_reg, + }, + + .iio_channels = sx9310_channels, + .num_iio_channels = ARRAY_SIZE(sx9310_channels), + .iio_info = { + .read_raw = sx9310_read_raw, + .read_avail = sx9310_read_avail, + .read_event_value = sx9310_read_event_val, + .write_event_value = sx9310_write_event_val, + .write_raw = sx9310_write_raw, + .read_event_config = sx_common_read_event_config, + .write_event_config = sx_common_write_event_config, + }, +}; + +static int sx9310_probe(struct i2c_client *client) +{ + return sx_common_probe(client, &sx9310_chip_info, &sx9310_regmap_config); +} + +static int sx9310_suspend(struct device *dev) +{ + struct sx_common_data *data = iio_priv(dev_get_drvdata(dev)); + u8 ctrl0; + int ret; + + disable_irq_nosync(data->client->irq); + + guard(mutex)(&data->mutex); + ret = regmap_read(data->regmap, SX9310_REG_PROX_CTRL0, + &data->suspend_ctrl); + if (ret) + return ret; + + ctrl0 = data->suspend_ctrl & ~SX9310_REG_PROX_CTRL0_SENSOREN_MASK; + ret = regmap_write(data->regmap, SX9310_REG_PROX_CTRL0, ctrl0); + if (ret) + return ret; + + return regmap_write(data->regmap, SX9310_REG_PAUSE, 0); +} + +static int sx9310_resume(struct device *dev) +{ + struct sx_common_data *data = iio_priv(dev_get_drvdata(dev)); + int ret; + + scoped_guard(mutex, &data->mutex) { + ret = regmap_write(data->regmap, SX9310_REG_PAUSE, 1); + if (ret) + return ret; + + ret = regmap_write(data->regmap, SX9310_REG_PROX_CTRL0, + data->suspend_ctrl); + if (ret) + return ret; + } + + enable_irq(data->client->irq); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(sx9310_pm_ops, sx9310_suspend, sx9310_resume); + +static const struct sx931x_info sx9310_info = { + .name = "sx9310", + .whoami = SX9310_WHOAMI_VALUE, +}; + +static const struct sx931x_info sx9311_info = { + .name = "sx9311", + .whoami = SX9311_WHOAMI_VALUE, +}; + +static const struct acpi_device_id sx9310_acpi_match[] = { + { "STH9310", (kernel_ulong_t)&sx9310_info }, + { "STH9311", (kernel_ulong_t)&sx9311_info }, + { } +}; +MODULE_DEVICE_TABLE(acpi, sx9310_acpi_match); + +static const struct of_device_id sx9310_of_match[] = { + { .compatible = "semtech,sx9310", &sx9310_info }, + { .compatible = "semtech,sx9311", &sx9311_info }, + { } +}; +MODULE_DEVICE_TABLE(of, sx9310_of_match); + +static const struct i2c_device_id sx9310_id[] = { + { .name = "sx9310", .driver_data = (kernel_ulong_t)&sx9310_info }, + { .name = "sx9311", .driver_data = (kernel_ulong_t)&sx9311_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sx9310_id); + +static struct i2c_driver sx9310_driver = { + .driver = { + .name = "sx9310", + .acpi_match_table = sx9310_acpi_match, + .of_match_table = sx9310_of_match, + .pm = pm_sleep_ptr(&sx9310_pm_ops), + + /* + * Lots of i2c transfers in probe + over 200 ms waiting in + * sx9310_init_compensation() mean a slow probe; prefer async + * so we don't delay boot if we're builtin to the kernel. + */ + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .probe = sx9310_probe, + .id_table = sx9310_id, +}; +module_i2c_driver(sx9310_driver); + +MODULE_AUTHOR("Gwendal Grignou "); +MODULE_AUTHOR("Daniel Campello "); +MODULE_DESCRIPTION("Driver for Semtech SX9310/SX9311 proximity sensor"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("SEMTECH_PROX"); diff --git a/drivers/iio/proximity/sx9324.c b/drivers/iio/proximity/sx9324.c new file mode 100644 index 000000000..13b4ef289 --- /dev/null +++ b/drivers/iio/proximity/sx9324.c @@ -0,0 +1,1164 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2021 Google LLC. + * + * Driver for Semtech's SX9324 capacitive proximity/button solution. + * Based on SX9324 driver and copy of datasheet at: + * https://edit.wpgdadawant.com/uploads/news_file/program/2019/30184/tech_files/program_30184_suggest_other_file.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "sx_common.h" + +/* Register definitions. */ +#define SX9324_REG_IRQ_SRC SX_COMMON_REG_IRQ_SRC +#define SX9324_REG_STAT0 0x01 +#define SX9324_REG_STAT1 0x02 +#define SX9324_REG_STAT2 0x03 +#define SX9324_REG_STAT2_COMPSTAT_MASK GENMASK(3, 0) +#define SX9324_REG_STAT3 0x04 +#define SX9324_REG_IRQ_MSK 0x05 +#define SX9324_CONVDONE_IRQ BIT(3) +#define SX9324_FAR_IRQ BIT(5) +#define SX9324_CLOSE_IRQ BIT(6) +#define SX9324_REG_IRQ_CFG0 0x06 +#define SX9324_REG_IRQ_CFG1 0x07 +#define SX9324_REG_IRQ_CFG1_FAILCOND 0x80 +#define SX9324_REG_IRQ_CFG2 0x08 + +#define SX9324_REG_GNRL_CTRL0 0x10 +#define SX9324_REG_GNRL_CTRL0_SCANPERIOD_MASK GENMASK(4, 0) +#define SX9324_REG_GNRL_CTRL0_SCANPERIOD_100MS 0x16 +#define SX9324_REG_GNRL_CTRL1 0x11 +#define SX9324_REG_GNRL_CTRL1_PHEN_MASK GENMASK(3, 0) +#define SX9324_REG_GNRL_CTRL1_PAUSECTRL 0x20 + +#define SX9324_REG_I2C_ADDR 0x14 +#define SX9324_REG_CLK_SPRD 0x15 + +#define SX9324_REG_AFE_CTRL0 0x20 +#define SX9324_REG_AFE_CTRL0_RINT_SHIFT 6 +#define SX9324_REG_AFE_CTRL0_RINT_MASK \ + GENMASK(SX9324_REG_AFE_CTRL0_RINT_SHIFT + 1, \ + SX9324_REG_AFE_CTRL0_RINT_SHIFT) +#define SX9324_REG_AFE_CTRL0_RINT_LOWEST 0x00 +#define SX9324_REG_AFE_CTRL0_CSIDLE_SHIFT 4 +#define SX9324_REG_AFE_CTRL0_CSIDLE_MASK \ + GENMASK(SX9324_REG_AFE_CTRL0_CSIDLE_SHIFT + 1, \ + SX9324_REG_AFE_CTRL0_CSIDLE_SHIFT) +#define SX9324_REG_AFE_CTRL0_RINT_LOWEST 0x00 +#define SX9324_REG_AFE_CTRL1 0x21 +#define SX9324_REG_AFE_CTRL2 0x22 +#define SX9324_REG_AFE_CTRL3 0x23 +#define SX9324_REG_AFE_CTRL4 0x24 +#define SX9324_REG_AFE_CTRL4_FREQ_83_33HZ 0x40 +#define SX9324_REG_AFE_CTRL4_RESOLUTION_MASK GENMASK(2, 0) +#define SX9324_REG_AFE_CTRL4_RES_100 0x04 +#define SX9324_REG_AFE_CTRL5 0x25 +#define SX9324_REG_AFE_CTRL6 0x26 +#define SX9324_REG_AFE_CTRL7 0x27 +#define SX9324_REG_AFE_PH0 0x28 +#define SX9324_REG_AFE_PH0_PIN_MASK(_pin) \ + GENMASK(2 * (_pin) + 1, 2 * (_pin)) + +#define SX9324_REG_AFE_PH1 0x29 +#define SX9324_REG_AFE_PH2 0x2a +#define SX9324_REG_AFE_PH3 0x2b +#define SX9324_REG_AFE_CTRL8 0x2c +#define SX9324_REG_AFE_CTRL8_RESERVED 0x10 +#define SX9324_REG_AFE_CTRL8_RESFILTIN_4KOHM 0x02 +#define SX9324_REG_AFE_CTRL8_RESFILTIN_MASK GENMASK(3, 0) +#define SX9324_REG_AFE_CTRL9 0x2d +#define SX9324_REG_AFE_CTRL9_AGAIN_MASK GENMASK(3, 0) +#define SX9324_REG_AFE_CTRL9_AGAIN_1 0x08 + +#define SX9324_REG_PROX_CTRL0 0x30 +#define SX9324_REG_PROX_CTRL0_GAIN_MASK GENMASK(5, 3) +#define SX9324_REG_PROX_CTRL0_GAIN_SHIFT 3 +#define SX9324_REG_PROX_CTRL0_GAIN_RSVD 0x0 +#define SX9324_REG_PROX_CTRL0_GAIN_1 0x1 +#define SX9324_REG_PROX_CTRL0_GAIN_8 0x4 +#define SX9324_REG_PROX_CTRL0_RAWFILT_MASK GENMASK(2, 0) +#define SX9324_REG_PROX_CTRL0_RAWFILT_1P50 0x01 +#define SX9324_REG_PROX_CTRL1 0x31 +#define SX9324_REG_PROX_CTRL2 0x32 +#define SX9324_REG_PROX_CTRL2_AVGNEG_THRESH_16K 0x20 +#define SX9324_REG_PROX_CTRL3 0x33 +#define SX9324_REG_PROX_CTRL3_AVGDEB_2SAMPLES 0x40 +#define SX9324_REG_PROX_CTRL3_AVGPOS_THRESH_16K 0x20 +#define SX9324_REG_PROX_CTRL4 0x34 +#define SX9324_REG_PROX_CTRL4_AVGNEGFILT_MASK GENMASK(5, 3) +#define SX9324_REG_PROX_CTRL4_AVGNEG_FILT_2 0x08 +#define SX9324_REG_PROX_CTRL4_AVGPOSFILT_MASK GENMASK(2, 0) +#define SX9324_REG_PROX_CTRL4_AVGPOS_FILT_256 0x04 +#define SX9324_REG_PROX_CTRL5 0x35 +#define SX9324_REG_PROX_CTRL5_HYST_MASK GENMASK(5, 4) +#define SX9324_REG_PROX_CTRL5_CLOSE_DEBOUNCE_MASK GENMASK(3, 2) +#define SX9324_REG_PROX_CTRL5_FAR_DEBOUNCE_MASK GENMASK(1, 0) +#define SX9324_REG_PROX_CTRL6 0x36 +#define SX9324_REG_PROX_CTRL6_PROXTHRESH_32 0x08 +#define SX9324_REG_PROX_CTRL7 0x37 + +#define SX9324_REG_ADV_CTRL0 0x40 +#define SX9324_REG_ADV_CTRL1 0x41 +#define SX9324_REG_ADV_CTRL2 0x42 +#define SX9324_REG_ADV_CTRL3 0x43 +#define SX9324_REG_ADV_CTRL4 0x44 +#define SX9324_REG_ADV_CTRL5 0x45 +#define SX9324_REG_ADV_CTRL5_STARTUPSENS_MASK GENMASK(3, 2) +#define SX9324_REG_ADV_CTRL5_STARTUP_SENSOR_1 0x04 +#define SX9324_REG_ADV_CTRL5_STARTUP_METHOD_1 0x01 +#define SX9324_REG_ADV_CTRL6 0x46 +#define SX9324_REG_ADV_CTRL7 0x47 +#define SX9324_REG_ADV_CTRL8 0x48 +#define SX9324_REG_ADV_CTRL9 0x49 +#define SX9324_REG_ADV_CTRL10 0x4a +#define SX9324_REG_ADV_CTRL11 0x4b +#define SX9324_REG_ADV_CTRL12 0x4c +#define SX9324_REG_ADV_CTRL13 0x4d +#define SX9324_REG_ADV_CTRL14 0x4e +#define SX9324_REG_ADV_CTRL15 0x4f +#define SX9324_REG_ADV_CTRL16 0x50 +#define SX9324_REG_ADV_CTRL17 0x51 +#define SX9324_REG_ADV_CTRL18 0x52 +#define SX9324_REG_ADV_CTRL19 0x53 +#define SX9324_REG_ADV_CTRL20 0x54 +#define SX9324_REG_ADV_CTRL19_HIGHT_FAILURE_THRESH_SATURATION 0xf0 + +#define SX9324_REG_PHASE_SEL 0x60 + +#define SX9324_REG_USEFUL_MSB 0x61 +#define SX9324_REG_USEFUL_LSB 0x62 + +#define SX9324_REG_AVG_MSB 0x63 +#define SX9324_REG_AVG_LSB 0x64 + +#define SX9324_REG_DIFF_MSB 0x65 +#define SX9324_REG_DIFF_LSB 0x66 + +#define SX9324_REG_OFFSET_MSB 0x67 +#define SX9324_REG_OFFSET_LSB 0x68 + +#define SX9324_REG_SAR_MSB 0x69 +#define SX9324_REG_SAR_LSB 0x6a + +#define SX9324_REG_RESET 0x9f +/* Write this to REG_RESET to do a soft reset. */ +#define SX9324_SOFT_RESET 0xde + +#define SX9324_REG_WHOAMI 0xfa +#define SX9324_WHOAMI_VALUE 0x23 + +#define SX9324_REG_REVISION 0xfe + +/* 4 channels, as defined in STAT0: PH0, PH1, PH2 and PH3. */ +#define SX9324_NUM_CHANNELS 4 +/* 3 CS pins: CS0, CS1, CS2. */ +#define SX9324_NUM_PINS 3 + +static const char * const sx9324_cs_pin_usage[] = { "HZ", "MI", "DS", "GD" }; + +static ssize_t sx9324_phase_configuration_show(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct sx_common_data *data = iio_priv(indio_dev); + unsigned int val; + int i, ret, pin_idx; + size_t len = 0; + + ret = regmap_read(data->regmap, SX9324_REG_AFE_PH0 + chan->channel, &val); + if (ret < 0) + return ret; + + for (i = 0; i < SX9324_NUM_PINS; i++) { + pin_idx = (val & SX9324_REG_AFE_PH0_PIN_MASK(i)) >> (2 * i); + len += sysfs_emit_at(buf, len, "%s,", + sx9324_cs_pin_usage[pin_idx]); + } + buf[len - 1] = '\n'; + return len; +} + +static const struct iio_chan_spec_ext_info sx9324_channel_ext_info[] = { + { + .name = "setup", + .shared = IIO_SEPARATE, + .read = sx9324_phase_configuration_show, + }, + { } +}; + +#define SX9324_CHANNEL(idx) \ +{ \ + .type = IIO_PROXIMITY, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_HARDWAREGAIN), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_separate_available = \ + BIT(IIO_CHAN_INFO_HARDWAREGAIN), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .indexed = 1, \ + .channel = idx, \ + .address = SX9324_REG_DIFF_MSB, \ + .event_spec = sx_common_events, \ + .num_event_specs = ARRAY_SIZE(sx_common_events), \ + .scan_index = idx, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 12, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + .ext_info = sx9324_channel_ext_info, \ +} + +static const struct iio_chan_spec sx9324_channels[] = { + SX9324_CHANNEL(0), /* Phase 0 */ + SX9324_CHANNEL(1), /* Phase 1 */ + SX9324_CHANNEL(2), /* Phase 2 */ + SX9324_CHANNEL(3), /* Phase 3 */ + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +/* + * Each entry contains the integer part (val) and the fractional part, in micro + * seconds. It conforms to the IIO output IIO_VAL_INT_PLUS_MICRO. + */ +static const struct { + int val; + int val2; +} sx9324_samp_freq_table[] = { + { 1000, 0 }, /* 00000: Min (no idle time) */ + { 500, 0 }, /* 00001: 2 ms */ + { 250, 0 }, /* 00010: 4 ms */ + { 166, 666666 }, /* 00011: 6 ms */ + { 125, 0 }, /* 00100: 8 ms */ + { 100, 0 }, /* 00101: 10 ms */ + { 71, 428571 }, /* 00110: 14 ms */ + { 55, 555556 }, /* 00111: 18 ms */ + { 45, 454545 }, /* 01000: 22 ms */ + { 38, 461538 }, /* 01001: 26 ms */ + { 33, 333333 }, /* 01010: 30 ms */ + { 29, 411765 }, /* 01011: 34 ms */ + { 26, 315789 }, /* 01100: 38 ms */ + { 23, 809524 }, /* 01101: 42 ms */ + { 21, 739130 }, /* 01110: 46 ms */ + { 20, 0 }, /* 01111: 50 ms */ + { 17, 857143 }, /* 10000: 56 ms */ + { 16, 129032 }, /* 10001: 62 ms */ + { 14, 705882 }, /* 10010: 68 ms */ + { 13, 513514 }, /* 10011: 74 ms */ + { 12, 500000 }, /* 10100: 80 ms */ + { 11, 111111 }, /* 10101: 90 ms */ + { 10, 0 }, /* 10110: 100 ms (Typ.) */ + { 5, 0 }, /* 10111: 200 ms */ + { 3, 333333 }, /* 11000: 300 ms */ + { 2, 500000 }, /* 11001: 400 ms */ + { 1, 666667 }, /* 11010: 600 ms */ + { 1, 250000 }, /* 11011: 800 ms */ + { 1, 0 }, /* 11100: 1 s */ + { 0, 500000 }, /* 11101: 2 s */ + { 0, 333333 }, /* 11110: 3 s */ + { 0, 250000 }, /* 11111: 4 s */ +}; + +static const unsigned int sx9324_scan_period_table[] = { + 2, 15, 30, 45, 60, 90, 120, 200, + 400, 600, 800, 1000, 2000, 3000, 4000, 5000, +}; + +static const struct regmap_range sx9324_writable_reg_ranges[] = { + /* + * To set COMPSTAT for compensation, even if datasheet says register is + * RO. + */ + regmap_reg_range(SX9324_REG_STAT2, SX9324_REG_STAT2), + regmap_reg_range(SX9324_REG_IRQ_MSK, SX9324_REG_IRQ_CFG2), + regmap_reg_range(SX9324_REG_GNRL_CTRL0, SX9324_REG_GNRL_CTRL1), + /* Leave i2c and clock spreading as unavailable */ + regmap_reg_range(SX9324_REG_AFE_CTRL0, SX9324_REG_AFE_CTRL9), + regmap_reg_range(SX9324_REG_PROX_CTRL0, SX9324_REG_PROX_CTRL7), + regmap_reg_range(SX9324_REG_ADV_CTRL0, SX9324_REG_ADV_CTRL20), + regmap_reg_range(SX9324_REG_PHASE_SEL, SX9324_REG_PHASE_SEL), + regmap_reg_range(SX9324_REG_OFFSET_MSB, SX9324_REG_OFFSET_LSB), + regmap_reg_range(SX9324_REG_RESET, SX9324_REG_RESET), +}; + +static const struct regmap_access_table sx9324_writeable_regs = { + .yes_ranges = sx9324_writable_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(sx9324_writable_reg_ranges), +}; + +/* + * All allocated registers are readable, so we just list unallocated + * ones. + */ +static const struct regmap_range sx9324_non_readable_reg_ranges[] = { + regmap_reg_range(SX9324_REG_IRQ_CFG2 + 1, SX9324_REG_GNRL_CTRL0 - 1), + regmap_reg_range(SX9324_REG_GNRL_CTRL1 + 1, SX9324_REG_AFE_CTRL0 - 1), + regmap_reg_range(SX9324_REG_AFE_CTRL9 + 1, SX9324_REG_PROX_CTRL0 - 1), + regmap_reg_range(SX9324_REG_PROX_CTRL7 + 1, SX9324_REG_ADV_CTRL0 - 1), + regmap_reg_range(SX9324_REG_ADV_CTRL20 + 1, SX9324_REG_PHASE_SEL - 1), + regmap_reg_range(SX9324_REG_SAR_LSB + 1, SX9324_REG_RESET - 1), + regmap_reg_range(SX9324_REG_RESET + 1, SX9324_REG_WHOAMI - 1), + regmap_reg_range(SX9324_REG_WHOAMI + 1, SX9324_REG_REVISION - 1), +}; + +static const struct regmap_access_table sx9324_readable_regs = { + .no_ranges = sx9324_non_readable_reg_ranges, + .n_no_ranges = ARRAY_SIZE(sx9324_non_readable_reg_ranges), +}; + +static const struct regmap_range sx9324_volatile_reg_ranges[] = { + regmap_reg_range(SX9324_REG_IRQ_SRC, SX9324_REG_STAT3), + regmap_reg_range(SX9324_REG_USEFUL_MSB, SX9324_REG_DIFF_LSB), + regmap_reg_range(SX9324_REG_SAR_MSB, SX9324_REG_SAR_LSB), + regmap_reg_range(SX9324_REG_WHOAMI, SX9324_REG_WHOAMI), + regmap_reg_range(SX9324_REG_REVISION, SX9324_REG_REVISION), +}; + +static const struct regmap_access_table sx9324_volatile_regs = { + .yes_ranges = sx9324_volatile_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(sx9324_volatile_reg_ranges), +}; + +static const struct regmap_config sx9324_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = SX9324_REG_REVISION, + .cache_type = REGCACHE_RBTREE, + + .wr_table = &sx9324_writeable_regs, + .rd_table = &sx9324_readable_regs, + .volatile_table = &sx9324_volatile_regs, +}; + +static int sx9324_read_prox_data(struct sx_common_data *data, + const struct iio_chan_spec *chan, + __be16 *val) +{ + int ret; + + ret = regmap_write(data->regmap, SX9324_REG_PHASE_SEL, chan->channel); + if (ret < 0) + return ret; + + return regmap_bulk_read(data->regmap, chan->address, val, sizeof(*val)); +} + +/* + * If we have no interrupt support, we have to wait for a scan period + * after enabling a channel to get a result. + */ +static int sx9324_wait_for_sample(struct sx_common_data *data) +{ + int ret; + unsigned int val; + + ret = regmap_read(data->regmap, SX9324_REG_GNRL_CTRL0, &val); + if (ret < 0) + return ret; + val = FIELD_GET(SX9324_REG_GNRL_CTRL0_SCANPERIOD_MASK, val); + + msleep(sx9324_scan_period_table[val]); + + return 0; +} + +static int sx9324_read_gain(struct sx_common_data *data, + const struct iio_chan_spec *chan, int *val) +{ + unsigned int reg, regval; + int ret; + + reg = SX9324_REG_PROX_CTRL0 + chan->channel / 2; + ret = regmap_read(data->regmap, reg, ®val); + if (ret) + return ret; + + regval = FIELD_GET(SX9324_REG_PROX_CTRL0_GAIN_MASK, regval); + if (regval) + regval--; + else if (regval == SX9324_REG_PROX_CTRL0_GAIN_RSVD || + regval > SX9324_REG_PROX_CTRL0_GAIN_8) + return -EINVAL; + + *val = 1 << regval; + + return IIO_VAL_INT; +} + +static int sx9324_read_samp_freq(struct sx_common_data *data, + int *val, int *val2) +{ + int ret; + unsigned int regval; + + ret = regmap_read(data->regmap, SX9324_REG_GNRL_CTRL0, ®val); + if (ret) + return ret; + + regval = FIELD_GET(SX9324_REG_GNRL_CTRL0_SCANPERIOD_MASK, regval); + *val = sx9324_samp_freq_table[regval].val; + *val2 = sx9324_samp_freq_table[regval].val2; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int sx9324_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct sx_common_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = sx_common_read_proximity(data, chan, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_HARDWAREGAIN: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = sx9324_read_gain(data, chan, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + return sx9324_read_samp_freq(data, val, val2); + default: + return -EINVAL; + } +} + +static const int sx9324_gain_vals[] = { 1, 2, 4, 8 }; + +static int sx9324_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + if (chan->type != IIO_PROXIMITY) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(sx9324_gain_vals); + *vals = sx9324_gain_vals; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *type = IIO_VAL_INT_PLUS_MICRO; + *length = ARRAY_SIZE(sx9324_samp_freq_table) * 2; + *vals = (int *)sx9324_samp_freq_table; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int sx9324_set_samp_freq(struct sx_common_data *data, + int val, int val2) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sx9324_samp_freq_table); i++) + if (val == sx9324_samp_freq_table[i].val && + val2 == sx9324_samp_freq_table[i].val2) + break; + + if (i == ARRAY_SIZE(sx9324_samp_freq_table)) + return -EINVAL; + + guard(mutex)(&data->mutex); + + return regmap_update_bits(data->regmap, + SX9324_REG_GNRL_CTRL0, + SX9324_REG_GNRL_CTRL0_SCANPERIOD_MASK, i); +} + +static int sx9324_read_thresh(struct sx_common_data *data, + const struct iio_chan_spec *chan, int *val) +{ + unsigned int regval; + unsigned int reg; + int ret; + + /* + * TODO(gwendal): Depending on the phase function + * (proximity/table/body), retrieve the right threshold. + * For now, return the proximity threshold. + */ + reg = SX9324_REG_PROX_CTRL6 + chan->channel / 2; + ret = regmap_read(data->regmap, reg, ®val); + if (ret) + return ret; + + if (regval <= 1) + *val = regval; + else + *val = (regval * regval) / 2; + + return IIO_VAL_INT; +} + +static int sx9324_read_hysteresis(struct sx_common_data *data, + const struct iio_chan_spec *chan, int *val) +{ + unsigned int regval, pthresh; + int ret; + + ret = sx9324_read_thresh(data, chan, &pthresh); + if (ret < 0) + return ret; + + ret = regmap_read(data->regmap, SX9324_REG_PROX_CTRL5, ®val); + if (ret) + return ret; + + regval = FIELD_GET(SX9324_REG_PROX_CTRL5_HYST_MASK, regval); + if (!regval) + *val = 0; + else + *val = pthresh >> (5 - regval); + + return IIO_VAL_INT; +} + +static int sx9324_read_far_debounce(struct sx_common_data *data, int *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, SX9324_REG_PROX_CTRL5, ®val); + if (ret) + return ret; + + regval = FIELD_GET(SX9324_REG_PROX_CTRL5_FAR_DEBOUNCE_MASK, regval); + if (regval) + *val = 1 << regval; + else + *val = 0; + + return IIO_VAL_INT; +} + +static int sx9324_read_close_debounce(struct sx_common_data *data, int *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, SX9324_REG_PROX_CTRL5, ®val); + if (ret) + return ret; + + regval = FIELD_GET(SX9324_REG_PROX_CTRL5_CLOSE_DEBOUNCE_MASK, regval); + if (regval) + *val = 1 << regval; + else + *val = 0; + + return IIO_VAL_INT; +} + +static int sx9324_read_event_val(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, int *val2) +{ + struct sx_common_data *data = iio_priv(indio_dev); + + if (chan->type != IIO_PROXIMITY) + return -EINVAL; + + switch (info) { + case IIO_EV_INFO_VALUE: + return sx9324_read_thresh(data, chan, val); + case IIO_EV_INFO_PERIOD: + switch (dir) { + case IIO_EV_DIR_RISING: + return sx9324_read_far_debounce(data, val); + case IIO_EV_DIR_FALLING: + return sx9324_read_close_debounce(data, val); + default: + return -EINVAL; + } + case IIO_EV_INFO_HYSTERESIS: + return sx9324_read_hysteresis(data, chan, val); + default: + return -EINVAL; + } +} + +static int sx9324_write_thresh(struct sx_common_data *data, + const struct iio_chan_spec *chan, int _val) +{ + unsigned int reg, val = _val; + + reg = SX9324_REG_PROX_CTRL6 + chan->channel / 2; + + if (val >= 1) + val = int_sqrt(2 * val); + + if (val > 0xff) + return -EINVAL; + + guard(mutex)(&data->mutex); + + return regmap_write(data->regmap, reg, val); +} + +static int sx9324_write_hysteresis(struct sx_common_data *data, + const struct iio_chan_spec *chan, int _val) +{ + unsigned int hyst, val = _val; + int ret, pthresh; + + ret = sx9324_read_thresh(data, chan, &pthresh); + if (ret < 0) + return ret; + + if (val == 0) + hyst = 0; + else if (val >= pthresh >> 2) + hyst = 3; + else if (val >= pthresh >> 3) + hyst = 2; + else if (val >= pthresh >> 4) + hyst = 1; + else + return -EINVAL; + + hyst = FIELD_PREP(SX9324_REG_PROX_CTRL5_HYST_MASK, hyst); + guard(mutex)(&data->mutex); + + return regmap_update_bits(data->regmap, SX9324_REG_PROX_CTRL5, + SX9324_REG_PROX_CTRL5_HYST_MASK, hyst); +} + +static int sx9324_write_far_debounce(struct sx_common_data *data, int _val) +{ + unsigned int regval, val = _val; + + if (val > 0) + val = ilog2(val); + if (!FIELD_FIT(SX9324_REG_PROX_CTRL5_FAR_DEBOUNCE_MASK, val)) + return -EINVAL; + + regval = FIELD_PREP(SX9324_REG_PROX_CTRL5_FAR_DEBOUNCE_MASK, val); + + guard(mutex)(&data->mutex); + + return regmap_update_bits(data->regmap, SX9324_REG_PROX_CTRL5, + SX9324_REG_PROX_CTRL5_FAR_DEBOUNCE_MASK, + regval); +} + +static int sx9324_write_close_debounce(struct sx_common_data *data, int _val) +{ + unsigned int regval, val = _val; + + if (val > 0) + val = ilog2(val); + if (!FIELD_FIT(SX9324_REG_PROX_CTRL5_CLOSE_DEBOUNCE_MASK, val)) + return -EINVAL; + + regval = FIELD_PREP(SX9324_REG_PROX_CTRL5_CLOSE_DEBOUNCE_MASK, val); + + guard(mutex)(&data->mutex); + + return regmap_update_bits(data->regmap, SX9324_REG_PROX_CTRL5, + SX9324_REG_PROX_CTRL5_CLOSE_DEBOUNCE_MASK, + regval); +} + +static int sx9324_write_event_val(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, int val2) +{ + struct sx_common_data *data = iio_priv(indio_dev); + + if (chan->type != IIO_PROXIMITY) + return -EINVAL; + + switch (info) { + case IIO_EV_INFO_VALUE: + return sx9324_write_thresh(data, chan, val); + case IIO_EV_INFO_PERIOD: + switch (dir) { + case IIO_EV_DIR_RISING: + return sx9324_write_far_debounce(data, val); + case IIO_EV_DIR_FALLING: + return sx9324_write_close_debounce(data, val); + default: + return -EINVAL; + } + case IIO_EV_INFO_HYSTERESIS: + return sx9324_write_hysteresis(data, chan, val); + default: + return -EINVAL; + } +} + +static int sx9324_write_gain(struct sx_common_data *data, + const struct iio_chan_spec *chan, int val) +{ + unsigned int gain, reg; + + reg = SX9324_REG_PROX_CTRL0 + chan->channel / 2; + + gain = ilog2(val) + 1; + if (val <= 0 || gain > SX9324_REG_PROX_CTRL0_GAIN_8) + return -EINVAL; + + gain = FIELD_PREP(SX9324_REG_PROX_CTRL0_GAIN_MASK, gain); + + guard(mutex)(&data->mutex); + + return regmap_update_bits(data->regmap, reg, + SX9324_REG_PROX_CTRL0_GAIN_MASK, + gain); +} + +static int sx9324_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int val, int val2, + long mask) +{ + struct sx_common_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return sx9324_set_samp_freq(data, val, val2); + case IIO_CHAN_INFO_HARDWAREGAIN: + return sx9324_write_gain(data, chan, val); + default: + return -EINVAL; + } +} + +static const struct sx_common_reg_default sx9324_default_regs[] = { + { SX9324_REG_IRQ_MSK, 0x00 }, + { SX9324_REG_IRQ_CFG0, 0x00, "irq_cfg0" }, + { SX9324_REG_IRQ_CFG1, SX9324_REG_IRQ_CFG1_FAILCOND, "irq_cfg1" }, + { SX9324_REG_IRQ_CFG2, 0x00, "irq_cfg2" }, + { SX9324_REG_GNRL_CTRL0, SX9324_REG_GNRL_CTRL0_SCANPERIOD_100MS, "gnrl_ctrl0" }, + /* + * The lower 4 bits should not be set as it enable sensors measurements. + * Turning the detection on before the configuration values are set to + * good values can cause the device to return erroneous readings. + */ + { SX9324_REG_GNRL_CTRL1, SX9324_REG_GNRL_CTRL1_PAUSECTRL, "gnrl_ctrl1" }, + + { SX9324_REG_AFE_CTRL0, SX9324_REG_AFE_CTRL0_RINT_LOWEST, "afe_ctrl0" }, + { SX9324_REG_AFE_CTRL3, 0x00, "afe_ctrl3" }, + { SX9324_REG_AFE_CTRL4, SX9324_REG_AFE_CTRL4_FREQ_83_33HZ | + SX9324_REG_AFE_CTRL4_RES_100, "afe_ctrl4" }, + { SX9324_REG_AFE_CTRL6, 0x00, "afe_ctrl6" }, + { SX9324_REG_AFE_CTRL7, SX9324_REG_AFE_CTRL4_FREQ_83_33HZ | + SX9324_REG_AFE_CTRL4_RES_100, "afe_ctrl7" }, + + /* TODO(gwendal): PHx use chip default or all grounded? */ + { SX9324_REG_AFE_PH0, 0x29, "afe_ph0" }, + { SX9324_REG_AFE_PH1, 0x26, "afe_ph1" }, + { SX9324_REG_AFE_PH2, 0x1a, "afe_ph2" }, + { SX9324_REG_AFE_PH3, 0x16, "afe_ph3" }, + + { SX9324_REG_AFE_CTRL8, SX9324_REG_AFE_CTRL8_RESERVED | + SX9324_REG_AFE_CTRL8_RESFILTIN_4KOHM, "afe_ctrl8" }, + { SX9324_REG_AFE_CTRL9, SX9324_REG_AFE_CTRL9_AGAIN_1, "afe_ctrl9" }, + + { SX9324_REG_PROX_CTRL0, + SX9324_REG_PROX_CTRL0_GAIN_1 << SX9324_REG_PROX_CTRL0_GAIN_SHIFT | + SX9324_REG_PROX_CTRL0_RAWFILT_1P50, "prox_ctrl0" }, + { SX9324_REG_PROX_CTRL1, + SX9324_REG_PROX_CTRL0_GAIN_1 << SX9324_REG_PROX_CTRL0_GAIN_SHIFT | + SX9324_REG_PROX_CTRL0_RAWFILT_1P50, "prox_ctrl1" }, + { SX9324_REG_PROX_CTRL2, SX9324_REG_PROX_CTRL2_AVGNEG_THRESH_16K, "prox_ctrl2" }, + { SX9324_REG_PROX_CTRL3, SX9324_REG_PROX_CTRL3_AVGDEB_2SAMPLES | + SX9324_REG_PROX_CTRL3_AVGPOS_THRESH_16K, "prox_ctrl3" }, + { SX9324_REG_PROX_CTRL4, SX9324_REG_PROX_CTRL4_AVGNEG_FILT_2 | + SX9324_REG_PROX_CTRL4_AVGPOS_FILT_256, "prox_ctrl4" }, + { SX9324_REG_PROX_CTRL5, 0x00, "prox_ctrl5" }, + { SX9324_REG_PROX_CTRL6, SX9324_REG_PROX_CTRL6_PROXTHRESH_32, "prox_ctrl6" }, + { SX9324_REG_PROX_CTRL7, SX9324_REG_PROX_CTRL6_PROXTHRESH_32, "prox_ctrl7" }, + { SX9324_REG_ADV_CTRL0, 0x00, "adv_ctrl0" }, + { SX9324_REG_ADV_CTRL1, 0x00, "adv_ctrl1" }, + { SX9324_REG_ADV_CTRL2, 0x00, "adv_ctrl2" }, + { SX9324_REG_ADV_CTRL3, 0x00, "adv_ctrl3" }, + { SX9324_REG_ADV_CTRL4, 0x00, "adv_ctrl4" }, + { SX9324_REG_ADV_CTRL5, SX9324_REG_ADV_CTRL5_STARTUP_SENSOR_1 | + SX9324_REG_ADV_CTRL5_STARTUP_METHOD_1, "adv_ctrl5" }, + { SX9324_REG_ADV_CTRL6, 0x00, "adv_ctrl6" }, + { SX9324_REG_ADV_CTRL7, 0x00, "adv_ctrl7" }, + { SX9324_REG_ADV_CTRL8, 0x00, "adv_ctrl8" }, + { SX9324_REG_ADV_CTRL9, 0x00, "adv_ctrl9" }, + /* Body/Table threshold */ + { SX9324_REG_ADV_CTRL10, 0x00, "adv_ctrl10" }, + { SX9324_REG_ADV_CTRL11, 0x00, "adv_ctrl11" }, + { SX9324_REG_ADV_CTRL12, 0x00, "adv_ctrl12" }, + /* TODO(gwendal): SAR currently disabled */ + { SX9324_REG_ADV_CTRL13, 0x00, "adv_ctrl13" }, + { SX9324_REG_ADV_CTRL14, 0x00, "adv_ctrl14" }, + { SX9324_REG_ADV_CTRL15, 0x00, "adv_ctrl15" }, + { SX9324_REG_ADV_CTRL16, 0x00, "adv_ctrl16" }, + { SX9324_REG_ADV_CTRL17, 0x00, "adv_ctrl17" }, + { SX9324_REG_ADV_CTRL18, 0x00, "adv_ctrl18" }, + { SX9324_REG_ADV_CTRL19, + SX9324_REG_ADV_CTRL19_HIGHT_FAILURE_THRESH_SATURATION, "adv_ctrl19" }, + { SX9324_REG_ADV_CTRL20, + SX9324_REG_ADV_CTRL19_HIGHT_FAILURE_THRESH_SATURATION, "adv_ctrl20" }, +}; + +/* Activate all channels and perform an initial compensation. */ +static int sx9324_init_compensation(struct iio_dev *indio_dev) +{ + struct sx_common_data *data = iio_priv(indio_dev); + unsigned int val; + int ret; + + /* run the compensation phase on all channels */ + ret = regmap_set_bits(data->regmap, SX9324_REG_STAT2, + SX9324_REG_STAT2_COMPSTAT_MASK); + if (ret) + return ret; + + return regmap_read_poll_timeout(data->regmap, SX9324_REG_STAT2, val, + !(val & SX9324_REG_STAT2_COMPSTAT_MASK), + 20000, 2000000); +} + +static u8 sx9324_parse_phase_prop(struct device *dev, + struct sx_common_reg_default *reg_def, + const char *prop) +{ + unsigned int pin_defs[SX9324_NUM_PINS]; + int count, ret, pin; + u32 raw = 0; + + count = device_property_count_u32(dev, prop); + if (count != ARRAY_SIZE(pin_defs)) + return reg_def->def; + ret = device_property_read_u32_array(dev, prop, pin_defs, + ARRAY_SIZE(pin_defs)); + if (ret) + return reg_def->def; + + for (pin = 0; pin < SX9324_NUM_PINS; pin++) + raw |= (pin_defs[pin] << (2 * pin)) & + SX9324_REG_AFE_PH0_PIN_MASK(pin); + + return raw; +} + +static void sx_common_get_raw_register_config(struct device *dev, + struct sx_common_reg_default *reg_def) +{ +#ifdef CONFIG_ACPI + struct acpi_device *adev = ACPI_COMPANION(dev); + u32 raw = 0, ret; + char prop[80]; + + if (!reg_def->property || !adev) + return; + + snprintf(prop, ARRAY_SIZE(prop), "%s,reg_%s", acpi_device_hid(adev), reg_def->property); + ret = device_property_read_u32(dev, prop, &raw); + if (ret) + return; + + reg_def->def = raw; +#endif +} + +static const struct sx_common_reg_default * +sx9324_get_default_reg(struct device *dev, int idx, + struct sx_common_reg_default *reg_def) +{ + static const char * const sx9324_rints[] = { "lowest", "low", "high", + "highest" }; + static const char * const sx9324_csidle[] = { "hi-z", "hi-z", "gnd", + "vdd" }; + u32 start = 0, raw = 0, pos = 0; + const char *prop; + int ret; + + memcpy(reg_def, &sx9324_default_regs[idx], sizeof(*reg_def)); + + sx_common_get_raw_register_config(dev, reg_def); + switch (reg_def->reg) { + case SX9324_REG_AFE_PH0: + reg_def->def = sx9324_parse_phase_prop(dev, reg_def, + "semtech,ph0-pin"); + break; + case SX9324_REG_AFE_PH1: + reg_def->def = sx9324_parse_phase_prop(dev, reg_def, + "semtech,ph1-pin"); + break; + case SX9324_REG_AFE_PH2: + reg_def->def = sx9324_parse_phase_prop(dev, reg_def, + "semtech,ph2-pin"); + break; + case SX9324_REG_AFE_PH3: + reg_def->def = sx9324_parse_phase_prop(dev, reg_def, + "semtech,ph3-pin"); + break; + case SX9324_REG_AFE_CTRL0: + ret = device_property_match_property_string(dev, "semtech,cs-idle-sleep", + sx9324_csidle, + ARRAY_SIZE(sx9324_csidle)); + if (ret >= 0) { + reg_def->def &= ~SX9324_REG_AFE_CTRL0_CSIDLE_MASK; + reg_def->def |= ret << SX9324_REG_AFE_CTRL0_CSIDLE_SHIFT; + } + + ret = device_property_match_property_string(dev, "semtech,int-comp-resistor", + sx9324_rints, + ARRAY_SIZE(sx9324_rints)); + if (ret >= 0) { + reg_def->def &= ~SX9324_REG_AFE_CTRL0_RINT_MASK; + reg_def->def |= ret << SX9324_REG_AFE_CTRL0_RINT_SHIFT; + } + break; + case SX9324_REG_AFE_CTRL4: + case SX9324_REG_AFE_CTRL7: + if (reg_def->reg == SX9324_REG_AFE_CTRL4) + prop = "semtech,ph01-resolution"; + else + prop = "semtech,ph23-resolution"; + + ret = device_property_read_u32(dev, prop, &raw); + if (ret) + break; + + raw = ilog2(raw) - 3; + + reg_def->def &= ~SX9324_REG_AFE_CTRL4_RESOLUTION_MASK; + reg_def->def |= FIELD_PREP(SX9324_REG_AFE_CTRL4_RESOLUTION_MASK, + raw); + break; + case SX9324_REG_AFE_CTRL8: + ret = device_property_read_u32(dev, + "semtech,input-precharge-resistor-ohms", + &raw); + if (ret) + break; + + reg_def->def &= ~SX9324_REG_AFE_CTRL8_RESFILTIN_MASK; + reg_def->def |= FIELD_PREP(SX9324_REG_AFE_CTRL8_RESFILTIN_MASK, + raw / 2000); + break; + + case SX9324_REG_AFE_CTRL9: + ret = device_property_read_u32(dev, + "semtech,input-analog-gain", &raw); + if (ret) + break; + /* + * The analog gain has the following setting: + * +---------+----------------+----------------+ + * | dt(raw) | physical value | register value | + * +---------+----------------+----------------+ + * | 0 | x1.247 | 6 | + * | 1 | x1 | 8 | + * | 2 | x0.768 | 11 | + * | 3 | x0.552 | 15 | + * +---------+----------------+----------------+ + */ + reg_def->def &= ~SX9324_REG_AFE_CTRL9_AGAIN_MASK; + reg_def->def |= FIELD_PREP(SX9324_REG_AFE_CTRL9_AGAIN_MASK, + 6 + raw * (raw + 3) / 2); + break; + + case SX9324_REG_ADV_CTRL5: + ret = device_property_read_u32(dev, "semtech,startup-sensor", + &start); + if (ret) + break; + + reg_def->def &= ~SX9324_REG_ADV_CTRL5_STARTUPSENS_MASK; + reg_def->def |= FIELD_PREP(SX9324_REG_ADV_CTRL5_STARTUPSENS_MASK, + start); + break; + case SX9324_REG_PROX_CTRL4: + ret = device_property_read_u32(dev, "semtech,avg-pos-strength", + &pos); + if (ret) + break; + + /* Powers of 2, except for a gap between 16 and 64 */ + raw = clamp(ilog2(pos), 3, 11) - (pos >= 32 ? 4 : 3); + + reg_def->def &= ~SX9324_REG_PROX_CTRL4_AVGPOSFILT_MASK; + reg_def->def |= FIELD_PREP(SX9324_REG_PROX_CTRL4_AVGPOSFILT_MASK, + raw); + break; + case SX9324_REG_PROX_CTRL0: + case SX9324_REG_PROX_CTRL1: + if (reg_def->reg == SX9324_REG_PROX_CTRL0) + prop = "semtech,ph01-proxraw-strength"; + else + prop = "semtech,ph23-proxraw-strength"; + ret = device_property_read_u32(dev, prop, &raw); + if (ret) + break; + + reg_def->def &= ~SX9324_REG_PROX_CTRL0_RAWFILT_MASK; + reg_def->def |= FIELD_PREP(SX9324_REG_PROX_CTRL0_RAWFILT_MASK, + raw); + break; + } + return reg_def; +} + +static int sx9324_check_whoami(struct device *dev, + struct iio_dev *indio_dev) +{ + /* + * Only one sensor for this driver. Assuming the device tree + * is correct, just set the sensor name. + */ + indio_dev->name = "sx9324"; + return 0; +} + +static const struct sx_common_chip_info sx9324_chip_info = { + .reg_stat = SX9324_REG_STAT0, + .reg_irq_msk = SX9324_REG_IRQ_MSK, + .reg_enable_chan = SX9324_REG_GNRL_CTRL1, + .reg_reset = SX9324_REG_RESET, + + .mask_enable_chan = SX9324_REG_GNRL_CTRL1_PHEN_MASK, + .irq_msk_offset = 3, + .num_channels = SX9324_NUM_CHANNELS, + .num_default_regs = ARRAY_SIZE(sx9324_default_regs), + + .ops = { + .read_prox_data = sx9324_read_prox_data, + .check_whoami = sx9324_check_whoami, + .init_compensation = sx9324_init_compensation, + .wait_for_sample = sx9324_wait_for_sample, + .get_default_reg = sx9324_get_default_reg, + }, + + .iio_channels = sx9324_channels, + .num_iio_channels = ARRAY_SIZE(sx9324_channels), + .iio_info = { + .read_raw = sx9324_read_raw, + .read_avail = sx9324_read_avail, + .read_event_value = sx9324_read_event_val, + .write_event_value = sx9324_write_event_val, + .write_raw = sx9324_write_raw, + .read_event_config = sx_common_read_event_config, + .write_event_config = sx_common_write_event_config, + }, +}; + +static int sx9324_probe(struct i2c_client *client) +{ + return sx_common_probe(client, &sx9324_chip_info, &sx9324_regmap_config); +} + +static int sx9324_suspend(struct device *dev) +{ + struct sx_common_data *data = iio_priv(dev_get_drvdata(dev)); + unsigned int regval; + int ret; + + disable_irq_nosync(data->client->irq); + + guard(mutex)(&data->mutex); + ret = regmap_read(data->regmap, SX9324_REG_GNRL_CTRL1, ®val); + if (ret < 0) + return ret; + + data->suspend_ctrl = + FIELD_GET(SX9324_REG_GNRL_CTRL1_PHEN_MASK, regval); + + + /* Disable all phases, send the device to sleep. */ + return regmap_write(data->regmap, SX9324_REG_GNRL_CTRL1, 0); +} + +static int sx9324_resume(struct device *dev) +{ + struct sx_common_data *data = iio_priv(dev_get_drvdata(dev)); + + scoped_guard(mutex, &data->mutex) { + int ret = regmap_write(data->regmap, SX9324_REG_GNRL_CTRL1, + data->suspend_ctrl | + SX9324_REG_GNRL_CTRL1_PAUSECTRL); + if (ret) + return ret; + } + + enable_irq(data->client->irq); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(sx9324_pm_ops, sx9324_suspend, sx9324_resume); + +static const struct acpi_device_id sx9324_acpi_match[] = { + { .id = "STH9324" }, + { } +}; +MODULE_DEVICE_TABLE(acpi, sx9324_acpi_match); + +static const struct of_device_id sx9324_of_match[] = { + { .compatible = "semtech,sx9324" }, + { } +}; +MODULE_DEVICE_TABLE(of, sx9324_of_match); + +static const struct i2c_device_id sx9324_id[] = { + { .name = "sx9324" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sx9324_id); + +static struct i2c_driver sx9324_driver = { + .driver = { + .name = "sx9324", + .acpi_match_table = sx9324_acpi_match, + .of_match_table = sx9324_of_match, + .pm = pm_sleep_ptr(&sx9324_pm_ops), + + /* + * Lots of i2c transfers in probe + over 200 ms waiting in + * sx9324_init_compensation() mean a slow probe; prefer async + * so we don't delay boot if we're builtin to the kernel. + */ + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .probe = sx9324_probe, + .id_table = sx9324_id, +}; +module_i2c_driver(sx9324_driver); + +MODULE_AUTHOR("Gwendal Grignou "); +MODULE_DESCRIPTION("Driver for Semtech SX9324 proximity sensor"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("SEMTECH_PROX"); diff --git a/drivers/iio/proximity/sx9360.c b/drivers/iio/proximity/sx9360.c new file mode 100644 index 000000000..ed83d809e --- /dev/null +++ b/drivers/iio/proximity/sx9360.c @@ -0,0 +1,874 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2021 Google LLC. + * + * Driver for Semtech's SX9360 capacitive proximity/button solution. + * Based on SX9360 driver and copy of datasheet at: + * https://edit.wpgdadawant.com/uploads/news_file/program/2019/30184/tech_files/program_30184_suggest_other_file.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "sx_common.h" + +/* Nominal Oscillator Frequency. */ +#define SX9360_FOSC_MHZ 4 +#define SX9360_FOSC_HZ (SX9360_FOSC_MHZ * 1000000) + +/* Register definitions. */ +#define SX9360_REG_IRQ_SRC SX_COMMON_REG_IRQ_SRC +#define SX9360_REG_STAT 0x01 +#define SX9360_REG_STAT_COMPSTAT_MASK GENMASK(2, 1) +#define SX9360_REG_IRQ_MSK 0x02 +#define SX9360_CONVDONE_IRQ BIT(0) +#define SX9360_FAR_IRQ BIT(2) +#define SX9360_CLOSE_IRQ BIT(3) +#define SX9360_REG_IRQ_CFG 0x03 + +#define SX9360_REG_GNRL_CTRL0 0x10 +#define SX9360_REG_GNRL_CTRL0_PHEN_MASK GENMASK(1, 0) +#define SX9360_REG_GNRL_CTRL1 0x11 +#define SX9360_REG_GNRL_CTRL1_SCANPERIOD_MASK GENMASK(2, 0) +#define SX9360_REG_GNRL_CTRL2 0x12 +#define SX9360_REG_GNRL_CTRL2_PERIOD_102MS 0x32 +#define SX9360_REG_GNRL_REG_2_PERIOD_MS(_r) \ + (((_r) * 8192) / (SX9360_FOSC_HZ / 1000)) +#define SX9360_REG_GNRL_FREQ_2_REG(_f) (((_f) * 8192) / SX9360_FOSC_HZ) +#define SX9360_REG_GNRL_REG_2_FREQ(_r) (SX9360_FOSC_HZ / ((_r) * 8192)) + +#define SX9360_REG_AFE_CTRL1 0x21 +#define SX9360_REG_AFE_CTRL1_RESFILTIN_MASK GENMASK(3, 0) +#define SX9360_REG_AFE_CTRL1_RESFILTIN_0OHMS 0 +#define SX9360_REG_AFE_PARAM0_PHR 0x22 +#define SX9360_REG_AFE_PARAM1_PHR 0x23 +#define SX9360_REG_AFE_PARAM0_PHM 0x24 +#define SX9360_REG_AFE_PARAM0_RSVD 0x08 +#define SX9360_REG_AFE_PARAM0_RESOLUTION_MASK GENMASK(2, 0) +#define SX9360_REG_AFE_PARAM0_RESOLUTION_128 0x02 +#define SX9360_REG_AFE_PARAM1_PHM 0x25 +#define SX9360_REG_AFE_PARAM1_AGAIN_PHM_6PF 0x40 +#define SX9360_REG_AFE_PARAM1_FREQ_83_33HZ 0x06 + +#define SX9360_REG_PROX_CTRL0_PHR 0x40 +#define SX9360_REG_PROX_CTRL0_PHM 0x41 +#define SX9360_REG_PROX_CTRL0_GAIN_MASK GENMASK(5, 3) +#define SX9360_REG_PROX_CTRL0_GAIN_1 0x80 +#define SX9360_REG_PROX_CTRL0_RAWFILT_MASK GENMASK(2, 0) +#define SX9360_REG_PROX_CTRL0_RAWFILT_1P50 0x01 +#define SX9360_REG_PROX_CTRL1 0x42 +#define SX9360_REG_PROX_CTRL1_AVGNEG_THRESH_MASK GENMASK(5, 3) +#define SX9360_REG_PROX_CTRL1_AVGNEG_THRESH_16K 0x20 +#define SX9360_REG_PROX_CTRL2 0x43 +#define SX9360_REG_PROX_CTRL2_AVGDEB_MASK GENMASK(7, 6) +#define SX9360_REG_PROX_CTRL2_AVGDEB_2SAMPLES 0x40 +#define SX9360_REG_PROX_CTRL2_AVGPOS_THRESH_16K 0x20 +#define SX9360_REG_PROX_CTRL3 0x44 +#define SX9360_REG_PROX_CTRL3_AVGNEG_FILT_MASK GENMASK(5, 3) +#define SX9360_REG_PROX_CTRL3_AVGNEG_FILT_2 0x08 +#define SX9360_REG_PROX_CTRL3_AVGPOS_FILT_MASK GENMASK(2, 0) +#define SX9360_REG_PROX_CTRL3_AVGPOS_FILT_256 0x04 +#define SX9360_REG_PROX_CTRL4 0x45 +#define SX9360_REG_PROX_CTRL4_HYST_MASK GENMASK(5, 4) +#define SX9360_REG_PROX_CTRL4_CLOSE_DEBOUNCE_MASK GENMASK(3, 2) +#define SX9360_REG_PROX_CTRL4_FAR_DEBOUNCE_MASK GENMASK(1, 0) +#define SX9360_REG_PROX_CTRL5 0x46 +#define SX9360_REG_PROX_CTRL5_PROXTHRESH_32 0x08 + +#define SX9360_REG_REF_CORR0 0x60 +#define SX9360_REG_REF_CORR1 0x61 + +#define SX9360_REG_USEFUL_PHR_MSB 0x90 +#define SX9360_REG_USEFUL_PHR_LSB 0x91 + +#define SX9360_REG_OFFSET_PMR_MSB 0x92 +#define SX9360_REG_OFFSET_PMR_LSB 0x93 + +#define SX9360_REG_USEFUL_PHM_MSB 0x94 +#define SX9360_REG_USEFUL_PHM_LSB 0x95 + +#define SX9360_REG_AVG_PHM_MSB 0x96 +#define SX9360_REG_AVG_PHM_LSB 0x97 + +#define SX9360_REG_DIFF_PHM_MSB 0x98 +#define SX9360_REG_DIFF_PHM_LSB 0x99 + +#define SX9360_REG_OFFSET_PHM_MSB 0x9a +#define SX9360_REG_OFFSET_PHM_LSB 0x9b + +#define SX9360_REG_USE_FILTER_MSB 0x9a +#define SX9360_REG_USE_FILTER_LSB 0x9b + +#define SX9360_REG_RESET 0xcf +/* Write this to REG_RESET to do a soft reset. */ +#define SX9360_SOFT_RESET 0xde + +#define SX9360_REG_WHOAMI 0xfa +#define SX9360_WHOAMI_VALUE 0x60 + +#define SX9360_REG_REVISION 0xfe + +/* 2 channels, Phase Reference and Measurement. */ +#define SX9360_NUM_CHANNELS 2 + +static const struct iio_chan_spec sx9360_channels[] = { + { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_HARDWAREGAIN), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_HARDWAREGAIN), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .indexed = 1, + .address = SX9360_REG_USEFUL_PHR_MSB, + .channel = 0, + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 12, + .storagebits = 16, + .endianness = IIO_BE, + }, + }, + { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_HARDWAREGAIN), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_HARDWAREGAIN), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .indexed = 1, + .address = SX9360_REG_USEFUL_PHM_MSB, + .event_spec = sx_common_events, + .num_event_specs = ARRAY_SIZE(sx_common_events), + .channel = 1, + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 12, + .storagebits = 16, + .endianness = IIO_BE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +/* + * Each entry contains the integer part (val) and the fractional part, in micro + * seconds. It conforms to the IIO output IIO_VAL_INT_PLUS_MICRO. + * + * The frequency control register holds the period, with a ~2ms increment. + * Therefore the smallest frequency is 4MHz / (2047 * 8192), + * The fastest is 4MHz / 8192. + * The interval is not linear, but given there is 2047 possible value, + * Returns the fake increment of (Max-Min)/2047 + */ +static const struct { + int val; + int val2; +} sx9360_samp_freq_interval[] = { + { 0, 281250 }, /* 4MHz / (8192 * 2047) */ + { 0, 281250 }, + { 448, 281250 }, /* 4MHz / 8192 */ +}; + +static const struct regmap_range sx9360_writable_reg_ranges[] = { + /* + * To set COMPSTAT for compensation, even if datasheet says register is + * RO. + */ + regmap_reg_range(SX9360_REG_STAT, SX9360_REG_IRQ_CFG), + regmap_reg_range(SX9360_REG_GNRL_CTRL0, SX9360_REG_GNRL_CTRL2), + regmap_reg_range(SX9360_REG_AFE_CTRL1, SX9360_REG_AFE_PARAM1_PHM), + regmap_reg_range(SX9360_REG_PROX_CTRL0_PHR, SX9360_REG_PROX_CTRL5), + regmap_reg_range(SX9360_REG_REF_CORR0, SX9360_REG_REF_CORR1), + regmap_reg_range(SX9360_REG_OFFSET_PMR_MSB, SX9360_REG_OFFSET_PMR_LSB), + regmap_reg_range(SX9360_REG_RESET, SX9360_REG_RESET), +}; + +static const struct regmap_access_table sx9360_writeable_regs = { + .yes_ranges = sx9360_writable_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(sx9360_writable_reg_ranges), +}; + +/* + * All allocated registers are readable, so we just list unallocated + * ones. + */ +static const struct regmap_range sx9360_non_readable_reg_ranges[] = { + regmap_reg_range(SX9360_REG_IRQ_CFG + 1, SX9360_REG_GNRL_CTRL0 - 1), + regmap_reg_range(SX9360_REG_GNRL_CTRL2 + 1, SX9360_REG_AFE_CTRL1 - 1), + regmap_reg_range(SX9360_REG_AFE_PARAM1_PHM + 1, + SX9360_REG_PROX_CTRL0_PHR - 1), + regmap_reg_range(SX9360_REG_PROX_CTRL5 + 1, SX9360_REG_REF_CORR0 - 1), + regmap_reg_range(SX9360_REG_REF_CORR1 + 1, + SX9360_REG_USEFUL_PHR_MSB - 1), + regmap_reg_range(SX9360_REG_USE_FILTER_LSB + 1, SX9360_REG_RESET - 1), + regmap_reg_range(SX9360_REG_RESET + 1, SX9360_REG_WHOAMI - 1), + regmap_reg_range(SX9360_REG_WHOAMI + 1, SX9360_REG_REVISION - 1), +}; + +static const struct regmap_access_table sx9360_readable_regs = { + .no_ranges = sx9360_non_readable_reg_ranges, + .n_no_ranges = ARRAY_SIZE(sx9360_non_readable_reg_ranges), +}; + +static const struct regmap_range sx9360_volatile_reg_ranges[] = { + regmap_reg_range(SX9360_REG_IRQ_SRC, SX9360_REG_STAT), + regmap_reg_range(SX9360_REG_USEFUL_PHR_MSB, SX9360_REG_USE_FILTER_LSB), + regmap_reg_range(SX9360_REG_WHOAMI, SX9360_REG_WHOAMI), + regmap_reg_range(SX9360_REG_REVISION, SX9360_REG_REVISION), +}; + +static const struct regmap_access_table sx9360_volatile_regs = { + .yes_ranges = sx9360_volatile_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(sx9360_volatile_reg_ranges), +}; + +static const struct regmap_config sx9360_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = SX9360_REG_REVISION, + .cache_type = REGCACHE_RBTREE, + + .wr_table = &sx9360_writeable_regs, + .rd_table = &sx9360_readable_regs, + .volatile_table = &sx9360_volatile_regs, +}; + +static int sx9360_read_prox_data(struct sx_common_data *data, + const struct iio_chan_spec *chan, + __be16 *val) +{ + return regmap_bulk_read(data->regmap, chan->address, val, sizeof(*val)); +} + +/* + * If we have no interrupt support, we have to wait for a scan period + * after enabling a channel to get a result. + */ +static int sx9360_wait_for_sample(struct sx_common_data *data) +{ + int ret; + __be16 buf; + + ret = regmap_bulk_read(data->regmap, SX9360_REG_GNRL_CTRL1, + &buf, sizeof(buf)); + if (ret < 0) + return ret; + msleep(SX9360_REG_GNRL_REG_2_PERIOD_MS(be16_to_cpu(buf))); + + return 0; +} + +static int sx9360_read_gain(struct sx_common_data *data, + const struct iio_chan_spec *chan, int *val) +{ + unsigned int reg, regval; + int ret; + + reg = SX9360_REG_PROX_CTRL0_PHR + chan->channel; + ret = regmap_read(data->regmap, reg, ®val); + if (ret) + return ret; + + *val = 1 << FIELD_GET(SX9360_REG_PROX_CTRL0_GAIN_MASK, regval); + + return IIO_VAL_INT; +} + +static int sx9360_read_samp_freq(struct sx_common_data *data, + int *val, int *val2) +{ + int ret, divisor; + __be16 buf; + + ret = regmap_bulk_read(data->regmap, SX9360_REG_GNRL_CTRL1, + &buf, sizeof(buf)); + if (ret < 0) + return ret; + divisor = be16_to_cpu(buf); + if (divisor == 0) { + *val = 0; + return IIO_VAL_INT; + } + + *val = SX9360_FOSC_HZ; + *val2 = divisor * 8192; + + return IIO_VAL_FRACTIONAL; +} + +static int sx9360_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct sx_common_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = sx_common_read_proximity(data, chan, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_HARDWAREGAIN: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = sx9360_read_gain(data, chan, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + return sx9360_read_samp_freq(data, val, val2); + default: + return -EINVAL; + } +} + +static const char *sx9360_channel_labels[SX9360_NUM_CHANNELS] = { + "reference", "main", +}; + +static int sx9360_read_label(struct iio_dev *iio_dev, const struct iio_chan_spec *chan, + char *label) +{ + return sysfs_emit(label, "%s\n", sx9360_channel_labels[chan->channel]); +} + +static const int sx9360_gain_vals[] = { 1, 2, 4, 8 }; + +static int sx9360_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + if (chan->type != IIO_PROXIMITY) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(sx9360_gain_vals); + *vals = sx9360_gain_vals; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *type = IIO_VAL_INT_PLUS_MICRO; + *length = ARRAY_SIZE(sx9360_samp_freq_interval) * 2; + *vals = (int *)sx9360_samp_freq_interval; + return IIO_AVAIL_RANGE; + default: + return -EINVAL; + } +} + +static int sx9360_set_samp_freq(struct sx_common_data *data, + int val, int val2) +{ + int reg; + __be16 buf; + + reg = val * 8192 / SX9360_FOSC_HZ + val2 * 8192 / (SX9360_FOSC_MHZ); + buf = cpu_to_be16(reg); + guard(mutex)(&data->mutex); + + return regmap_bulk_write(data->regmap, SX9360_REG_GNRL_CTRL1, &buf, + sizeof(buf)); +} + +static int sx9360_read_thresh(struct sx_common_data *data, int *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, SX9360_REG_PROX_CTRL5, ®val); + if (ret) + return ret; + + if (regval <= 1) + *val = regval; + else + *val = (regval * regval) / 2; + + return IIO_VAL_INT; +} + +static int sx9360_read_hysteresis(struct sx_common_data *data, int *val) +{ + unsigned int regval, pthresh; + int ret; + + ret = sx9360_read_thresh(data, &pthresh); + if (ret < 0) + return ret; + + ret = regmap_read(data->regmap, SX9360_REG_PROX_CTRL4, ®val); + if (ret) + return ret; + + regval = FIELD_GET(SX9360_REG_PROX_CTRL4_HYST_MASK, regval); + if (!regval) + *val = 0; + else + *val = pthresh >> (5 - regval); + + return IIO_VAL_INT; +} + +static int sx9360_read_far_debounce(struct sx_common_data *data, int *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, SX9360_REG_PROX_CTRL4, ®val); + if (ret) + return ret; + + regval = FIELD_GET(SX9360_REG_PROX_CTRL4_FAR_DEBOUNCE_MASK, regval); + if (regval) + *val = 1 << regval; + else + *val = 0; + + return IIO_VAL_INT; +} + +static int sx9360_read_close_debounce(struct sx_common_data *data, int *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, SX9360_REG_PROX_CTRL4, ®val); + if (ret) + return ret; + + regval = FIELD_GET(SX9360_REG_PROX_CTRL4_CLOSE_DEBOUNCE_MASK, regval); + if (regval) + *val = 1 << regval; + else + *val = 0; + + return IIO_VAL_INT; +} + +static int sx9360_read_event_val(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, int *val2) +{ + struct sx_common_data *data = iio_priv(indio_dev); + + if (chan->type != IIO_PROXIMITY) + return -EINVAL; + + switch (info) { + case IIO_EV_INFO_VALUE: + return sx9360_read_thresh(data, val); + case IIO_EV_INFO_PERIOD: + switch (dir) { + case IIO_EV_DIR_RISING: + return sx9360_read_far_debounce(data, val); + case IIO_EV_DIR_FALLING: + return sx9360_read_close_debounce(data, val); + default: + return -EINVAL; + } + case IIO_EV_INFO_HYSTERESIS: + return sx9360_read_hysteresis(data, val); + default: + return -EINVAL; + } +} + +static int sx9360_write_thresh(struct sx_common_data *data, int _val) +{ + unsigned int val = _val; + + if (val >= 1) + val = int_sqrt(2 * val); + + if (val > 0xff) + return -EINVAL; + + guard(mutex)(&data->mutex); + return regmap_write(data->regmap, SX9360_REG_PROX_CTRL5, val); +} + +static int sx9360_write_hysteresis(struct sx_common_data *data, int _val) +{ + unsigned int hyst, val = _val; + int ret, pthresh; + + ret = sx9360_read_thresh(data, &pthresh); + if (ret < 0) + return ret; + + if (val == 0) + hyst = 0; + else if (val >= pthresh >> 2) + hyst = 3; + else if (val >= pthresh >> 3) + hyst = 2; + else if (val >= pthresh >> 4) + hyst = 1; + else + return -EINVAL; + + hyst = FIELD_PREP(SX9360_REG_PROX_CTRL4_HYST_MASK, hyst); + guard(mutex)(&data->mutex); + return regmap_update_bits(data->regmap, SX9360_REG_PROX_CTRL4, + SX9360_REG_PROX_CTRL4_HYST_MASK, hyst); +} + +static int sx9360_write_far_debounce(struct sx_common_data *data, int _val) +{ + unsigned int regval, val = _val; + + if (val > 0) + val = ilog2(val); + if (!FIELD_FIT(SX9360_REG_PROX_CTRL4_FAR_DEBOUNCE_MASK, val)) + return -EINVAL; + + regval = FIELD_PREP(SX9360_REG_PROX_CTRL4_FAR_DEBOUNCE_MASK, val); + + guard(mutex)(&data->mutex); + return regmap_update_bits(data->regmap, SX9360_REG_PROX_CTRL4, + SX9360_REG_PROX_CTRL4_FAR_DEBOUNCE_MASK, + regval); +} + +static int sx9360_write_close_debounce(struct sx_common_data *data, int _val) +{ + unsigned int regval, val = _val; + + if (val > 0) + val = ilog2(val); + if (!FIELD_FIT(SX9360_REG_PROX_CTRL4_CLOSE_DEBOUNCE_MASK, val)) + return -EINVAL; + + regval = FIELD_PREP(SX9360_REG_PROX_CTRL4_CLOSE_DEBOUNCE_MASK, val); + + guard(mutex)(&data->mutex); + return regmap_update_bits(data->regmap, SX9360_REG_PROX_CTRL4, + SX9360_REG_PROX_CTRL4_CLOSE_DEBOUNCE_MASK, + regval); +} + +static int sx9360_write_event_val(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, int val2) +{ + struct sx_common_data *data = iio_priv(indio_dev); + + if (chan->type != IIO_PROXIMITY) + return -EINVAL; + + switch (info) { + case IIO_EV_INFO_VALUE: + return sx9360_write_thresh(data, val); + case IIO_EV_INFO_PERIOD: + switch (dir) { + case IIO_EV_DIR_RISING: + return sx9360_write_far_debounce(data, val); + case IIO_EV_DIR_FALLING: + return sx9360_write_close_debounce(data, val); + default: + return -EINVAL; + } + case IIO_EV_INFO_HYSTERESIS: + return sx9360_write_hysteresis(data, val); + default: + return -EINVAL; + } +} + +static int sx9360_write_gain(struct sx_common_data *data, + const struct iio_chan_spec *chan, int val) +{ + unsigned int gain, reg; + + gain = ilog2(val); + reg = SX9360_REG_PROX_CTRL0_PHR + chan->channel; + gain = FIELD_PREP(SX9360_REG_PROX_CTRL0_GAIN_MASK, gain); + + guard(mutex)(&data->mutex); + return regmap_update_bits(data->regmap, reg, + SX9360_REG_PROX_CTRL0_GAIN_MASK, + gain); +} + +static int sx9360_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int val, int val2, + long mask) +{ + struct sx_common_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return sx9360_set_samp_freq(data, val, val2); + case IIO_CHAN_INFO_HARDWAREGAIN: + return sx9360_write_gain(data, chan, val); + default: + return -EINVAL; + } +} + +static const struct sx_common_reg_default sx9360_default_regs[] = { + { SX9360_REG_IRQ_MSK, 0x00 }, + { SX9360_REG_IRQ_CFG, 0x00, "irq_cfg" }, + /* + * The lower 2 bits should not be set as it enable sensors measurements. + * Turning the detection on before the configuration values are set to + * good values can cause the device to return erroneous readings. + */ + { SX9360_REG_GNRL_CTRL0, 0x00, "gnrl_ctrl0" }, + { SX9360_REG_GNRL_CTRL1, 0x00, "gnrl_ctrl1" }, + { SX9360_REG_GNRL_CTRL2, SX9360_REG_GNRL_CTRL2_PERIOD_102MS, "gnrl_ctrl2" }, + + { SX9360_REG_AFE_CTRL1, SX9360_REG_AFE_CTRL1_RESFILTIN_0OHMS, "afe_ctrl0" }, + { SX9360_REG_AFE_PARAM0_PHR, SX9360_REG_AFE_PARAM0_RSVD | + SX9360_REG_AFE_PARAM0_RESOLUTION_128, "afe_param0_phr" }, + { SX9360_REG_AFE_PARAM1_PHR, SX9360_REG_AFE_PARAM1_AGAIN_PHM_6PF | + SX9360_REG_AFE_PARAM1_FREQ_83_33HZ, "afe_param1_phr" }, + { SX9360_REG_AFE_PARAM0_PHM, SX9360_REG_AFE_PARAM0_RSVD | + SX9360_REG_AFE_PARAM0_RESOLUTION_128, "afe_param0_phm" }, + { SX9360_REG_AFE_PARAM1_PHM, SX9360_REG_AFE_PARAM1_AGAIN_PHM_6PF | + SX9360_REG_AFE_PARAM1_FREQ_83_33HZ, "afe_param1_phm" }, + + { SX9360_REG_PROX_CTRL0_PHR, SX9360_REG_PROX_CTRL0_GAIN_1 | + SX9360_REG_PROX_CTRL0_RAWFILT_1P50, "prox_ctrl0_phr" }, + { SX9360_REG_PROX_CTRL0_PHM, SX9360_REG_PROX_CTRL0_GAIN_1 | + SX9360_REG_PROX_CTRL0_RAWFILT_1P50, "prox_ctrl0_phm" }, + { SX9360_REG_PROX_CTRL1, SX9360_REG_PROX_CTRL1_AVGNEG_THRESH_16K, "prox_ctrl1" }, + { SX9360_REG_PROX_CTRL2, SX9360_REG_PROX_CTRL2_AVGDEB_2SAMPLES | + SX9360_REG_PROX_CTRL2_AVGPOS_THRESH_16K, "prox_ctrl2" }, + { SX9360_REG_PROX_CTRL3, SX9360_REG_PROX_CTRL3_AVGNEG_FILT_2 | + SX9360_REG_PROX_CTRL3_AVGPOS_FILT_256, "prox_ctrl3" }, + { SX9360_REG_PROX_CTRL4, 0x00, "prox_ctrl4" }, + { SX9360_REG_PROX_CTRL5, SX9360_REG_PROX_CTRL5_PROXTHRESH_32, "prox_ctrl5" }, +}; + +/* Activate all channels and perform an initial compensation. */ +static int sx9360_init_compensation(struct iio_dev *indio_dev) +{ + struct sx_common_data *data = iio_priv(indio_dev); + unsigned int val; + int ret; + + /* run the compensation phase on all channels */ + ret = regmap_set_bits(data->regmap, SX9360_REG_STAT, + SX9360_REG_STAT_COMPSTAT_MASK); + if (ret) + return ret; + + return regmap_read_poll_timeout(data->regmap, SX9360_REG_STAT, val, + !(val & SX9360_REG_STAT_COMPSTAT_MASK), + 20000, 2000000); +} + +static const struct sx_common_reg_default * +sx9360_get_default_reg(struct device *dev, int idx, + struct sx_common_reg_default *reg_def) +{ + u32 raw = 0, pos = 0; + int ret; + + memcpy(reg_def, &sx9360_default_regs[idx], sizeof(*reg_def)); + switch (reg_def->reg) { + case SX9360_REG_AFE_CTRL1: + ret = device_property_read_u32(dev, + "semtech,input-precharge-resistor-ohms", + &raw); + if (ret) + break; + + reg_def->def &= ~SX9360_REG_AFE_CTRL1_RESFILTIN_MASK; + reg_def->def |= FIELD_PREP(SX9360_REG_AFE_CTRL1_RESFILTIN_MASK, + raw / 2000); + break; + case SX9360_REG_AFE_PARAM0_PHR: + case SX9360_REG_AFE_PARAM0_PHM: + ret = device_property_read_u32(dev, "semtech,resolution", &raw); + if (ret) + break; + + raw = ilog2(raw) - 3; + + reg_def->def &= ~SX9360_REG_AFE_PARAM0_RESOLUTION_MASK; + reg_def->def |= FIELD_PREP(SX9360_REG_AFE_PARAM0_RESOLUTION_MASK, raw); + break; + case SX9360_REG_PROX_CTRL0_PHR: + case SX9360_REG_PROX_CTRL0_PHM: + ret = device_property_read_u32(dev, "semtech,proxraw-strength", &raw); + if (ret) + break; + + reg_def->def &= ~SX9360_REG_PROX_CTRL0_RAWFILT_MASK; + reg_def->def |= FIELD_PREP(SX9360_REG_PROX_CTRL0_RAWFILT_MASK, raw); + break; + case SX9360_REG_PROX_CTRL3: + ret = device_property_read_u32(dev, "semtech,avg-pos-strength", + &pos); + if (ret) + break; + + /* Powers of 2, except for a gap between 16 and 64 */ + raw = clamp(ilog2(pos), 3, 11) - (pos >= 32 ? 4 : 3); + reg_def->def &= ~SX9360_REG_PROX_CTRL3_AVGPOS_FILT_MASK; + reg_def->def |= FIELD_PREP(SX9360_REG_PROX_CTRL3_AVGPOS_FILT_MASK, raw); + break; + } + + return reg_def; +} + +static int sx9360_check_whoami(struct device *dev, struct iio_dev *indio_dev) +{ + /* + * Only one sensor for this driver. Assuming the device tree + * is correct, just set the sensor name. + */ + indio_dev->name = "sx9360"; + return 0; +} + +static const struct sx_common_chip_info sx9360_chip_info = { + .reg_stat = SX9360_REG_STAT, + .reg_irq_msk = SX9360_REG_IRQ_MSK, + .reg_enable_chan = SX9360_REG_GNRL_CTRL0, + .reg_reset = SX9360_REG_RESET, + + .mask_enable_chan = SX9360_REG_GNRL_CTRL0_PHEN_MASK, + .stat_offset = 2, + .num_channels = SX9360_NUM_CHANNELS, + .num_default_regs = ARRAY_SIZE(sx9360_default_regs), + + .ops = { + .read_prox_data = sx9360_read_prox_data, + .check_whoami = sx9360_check_whoami, + .init_compensation = sx9360_init_compensation, + .wait_for_sample = sx9360_wait_for_sample, + .get_default_reg = sx9360_get_default_reg, + }, + + .iio_channels = sx9360_channels, + .num_iio_channels = ARRAY_SIZE(sx9360_channels), + .iio_info = { + .read_raw = sx9360_read_raw, + .read_avail = sx9360_read_avail, + .read_label = sx9360_read_label, + .read_event_value = sx9360_read_event_val, + .write_event_value = sx9360_write_event_val, + .write_raw = sx9360_write_raw, + .read_event_config = sx_common_read_event_config, + .write_event_config = sx_common_write_event_config, + }, +}; + +static int sx9360_probe(struct i2c_client *client) +{ + return sx_common_probe(client, &sx9360_chip_info, &sx9360_regmap_config); +} + +static int sx9360_suspend(struct device *dev) +{ + struct sx_common_data *data = iio_priv(dev_get_drvdata(dev)); + unsigned int regval; + int ret; + + disable_irq_nosync(data->client->irq); + + guard(mutex)(&data->mutex); + ret = regmap_read(data->regmap, SX9360_REG_GNRL_CTRL0, ®val); + if (ret < 0) + return ret; + + data->suspend_ctrl = + FIELD_GET(SX9360_REG_GNRL_CTRL0_PHEN_MASK, regval); + + + /* Disable all phases, send the device to sleep. */ + return regmap_write(data->regmap, SX9360_REG_GNRL_CTRL0, 0); +} + +static int sx9360_resume(struct device *dev) +{ + struct sx_common_data *data = iio_priv(dev_get_drvdata(dev)); + + scoped_guard(mutex, &data->mutex) { + int ret = regmap_update_bits(data->regmap, + SX9360_REG_GNRL_CTRL0, + SX9360_REG_GNRL_CTRL0_PHEN_MASK, + data->suspend_ctrl); + if (ret) + return ret; + } + enable_irq(data->client->irq); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(sx9360_pm_ops, sx9360_suspend, sx9360_resume); + +static const struct acpi_device_id sx9360_acpi_match[] = { + { .id = "STH9360" }, + { .id = "SAMM0208" }, + { } +}; +MODULE_DEVICE_TABLE(acpi, sx9360_acpi_match); + +static const struct of_device_id sx9360_of_match[] = { + { .compatible = "semtech,sx9360" }, + { } +}; +MODULE_DEVICE_TABLE(of, sx9360_of_match); + +static const struct i2c_device_id sx9360_id[] = { + { .name = "sx9360" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sx9360_id); + +static struct i2c_driver sx9360_driver = { + .driver = { + .name = "sx9360", + .acpi_match_table = sx9360_acpi_match, + .of_match_table = sx9360_of_match, + .pm = pm_sleep_ptr(&sx9360_pm_ops), + + /* + * Lots of i2c transfers in probe + over 200 ms waiting in + * sx9360_init_compensation() mean a slow probe; prefer async + * so we don't delay boot if we're builtin to the kernel. + */ + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .probe = sx9360_probe, + .id_table = sx9360_id, +}; +module_i2c_driver(sx9360_driver); + +MODULE_AUTHOR("Gwendal Grignou "); +MODULE_DESCRIPTION("Driver for Semtech SX9360 proximity sensor"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("SEMTECH_PROX"); diff --git a/drivers/iio/proximity/sx9500.c b/drivers/iio/proximity/sx9500.c new file mode 100644 index 000000000..dadce9b44 --- /dev/null +++ b/drivers/iio/proximity/sx9500.c @@ -0,0 +1,1048 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014 Intel Corporation + * + * Driver for Semtech's SX9500 capacitive proximity/button solution. + * Datasheet available at + * . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define SX9500_DRIVER_NAME "sx9500" + +/* Register definitions. */ +#define SX9500_REG_IRQ_SRC 0x00 +#define SX9500_REG_STAT 0x01 +#define SX9500_REG_IRQ_MSK 0x03 + +#define SX9500_REG_PROX_CTRL0 0x06 +#define SX9500_REG_PROX_CTRL1 0x07 +#define SX9500_REG_PROX_CTRL2 0x08 +#define SX9500_REG_PROX_CTRL3 0x09 +#define SX9500_REG_PROX_CTRL4 0x0a +#define SX9500_REG_PROX_CTRL5 0x0b +#define SX9500_REG_PROX_CTRL6 0x0c +#define SX9500_REG_PROX_CTRL7 0x0d +#define SX9500_REG_PROX_CTRL8 0x0e + +#define SX9500_REG_SENSOR_SEL 0x20 +#define SX9500_REG_USE_MSB 0x21 +#define SX9500_REG_USE_LSB 0x22 +#define SX9500_REG_AVG_MSB 0x23 +#define SX9500_REG_AVG_LSB 0x24 +#define SX9500_REG_DIFF_MSB 0x25 +#define SX9500_REG_DIFF_LSB 0x26 +#define SX9500_REG_OFFSET_MSB 0x27 +#define SX9500_REG_OFFSET_LSB 0x28 + +#define SX9500_REG_RESET 0x7f + +/* Write this to REG_RESET to do a soft reset. */ +#define SX9500_SOFT_RESET 0xde + +#define SX9500_SCAN_PERIOD_MASK GENMASK(6, 4) +#define SX9500_SCAN_PERIOD_SHIFT 4 + +/* + * These serve for identifying IRQ source in the IRQ_SRC register, and + * also for masking the IRQs in the IRQ_MSK register. + */ +#define SX9500_CLOSE_IRQ BIT(6) +#define SX9500_FAR_IRQ BIT(5) +#define SX9500_CONVDONE_IRQ BIT(3) + +#define SX9500_PROXSTAT_SHIFT 4 +#define SX9500_COMPSTAT_MASK GENMASK(3, 0) + +#define SX9500_NUM_CHANNELS 4 +#define SX9500_CHAN_MASK GENMASK(SX9500_NUM_CHANNELS - 1, 0) + +struct sx9500_data { + struct mutex mutex; + struct i2c_client *client; + struct iio_trigger *trig; + struct regmap *regmap; + struct gpio_desc *gpiod_rst; + /* + * Last reading of the proximity status for each channel. We + * only send an event to user space when this changes. + */ + bool prox_stat[SX9500_NUM_CHANNELS]; + bool event_enabled[SX9500_NUM_CHANNELS]; + bool trigger_enabled; + /* Remember enabled channels and sample rate during suspend. */ + unsigned int suspend_ctrl0; + struct completion completion; + int data_rdy_users, close_far_users; + int channel_users[SX9500_NUM_CHANNELS]; +}; + +static const struct iio_event_spec sx9500_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +#define SX9500_CHANNEL(idx) \ + { \ + .type = IIO_PROXIMITY, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .indexed = 1, \ + .channel = idx, \ + .event_spec = sx9500_events, \ + .num_event_specs = ARRAY_SIZE(sx9500_events), \ + .scan_index = idx, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .shift = 0, \ + }, \ + } + +static const struct iio_chan_spec sx9500_channels[] = { + SX9500_CHANNEL(0), + SX9500_CHANNEL(1), + SX9500_CHANNEL(2), + SX9500_CHANNEL(3), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const struct { + int val; + int val2; +} sx9500_samp_freq_table[] = { + {33, 333333}, + {16, 666666}, + {11, 111111}, + {8, 333333}, + {6, 666666}, + {5, 0}, + {3, 333333}, + {2, 500000}, +}; + +static const unsigned int sx9500_scan_period_table[] = { + 30, 60, 90, 120, 150, 200, 300, 400, +}; + +static const struct regmap_range sx9500_writable_reg_ranges[] = { + regmap_reg_range(SX9500_REG_IRQ_MSK, SX9500_REG_IRQ_MSK), + regmap_reg_range(SX9500_REG_PROX_CTRL0, SX9500_REG_PROX_CTRL8), + regmap_reg_range(SX9500_REG_SENSOR_SEL, SX9500_REG_SENSOR_SEL), + regmap_reg_range(SX9500_REG_OFFSET_MSB, SX9500_REG_OFFSET_LSB), + regmap_reg_range(SX9500_REG_RESET, SX9500_REG_RESET), +}; + +static const struct regmap_access_table sx9500_writeable_regs = { + .yes_ranges = sx9500_writable_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(sx9500_writable_reg_ranges), +}; + +/* + * All allocated registers are readable, so we just list unallocated + * ones. + */ +static const struct regmap_range sx9500_non_readable_reg_ranges[] = { + regmap_reg_range(SX9500_REG_STAT + 1, SX9500_REG_STAT + 1), + regmap_reg_range(SX9500_REG_IRQ_MSK + 1, SX9500_REG_PROX_CTRL0 - 1), + regmap_reg_range(SX9500_REG_PROX_CTRL8 + 1, SX9500_REG_SENSOR_SEL - 1), + regmap_reg_range(SX9500_REG_OFFSET_LSB + 1, SX9500_REG_RESET - 1), +}; + +static const struct regmap_access_table sx9500_readable_regs = { + .no_ranges = sx9500_non_readable_reg_ranges, + .n_no_ranges = ARRAY_SIZE(sx9500_non_readable_reg_ranges), +}; + +static const struct regmap_range sx9500_volatile_reg_ranges[] = { + regmap_reg_range(SX9500_REG_IRQ_SRC, SX9500_REG_STAT), + regmap_reg_range(SX9500_REG_USE_MSB, SX9500_REG_OFFSET_LSB), + regmap_reg_range(SX9500_REG_RESET, SX9500_REG_RESET), +}; + +static const struct regmap_access_table sx9500_volatile_regs = { + .yes_ranges = sx9500_volatile_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(sx9500_volatile_reg_ranges), +}; + +static const struct regmap_config sx9500_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = SX9500_REG_RESET, + .cache_type = REGCACHE_RBTREE, + + .wr_table = &sx9500_writeable_regs, + .rd_table = &sx9500_readable_regs, + .volatile_table = &sx9500_volatile_regs, +}; + +static int sx9500_inc_users(struct sx9500_data *data, int *counter, + unsigned int reg, unsigned int bitmask) +{ + (*counter)++; + if (*counter != 1) + /* Bit is already active, nothing to do. */ + return 0; + + return regmap_set_bits(data->regmap, reg, bitmask); +} + +static int sx9500_dec_users(struct sx9500_data *data, int *counter, + unsigned int reg, unsigned int bitmask) +{ + (*counter)--; + if (*counter != 0) + /* There are more users, do not deactivate. */ + return 0; + + return regmap_clear_bits(data->regmap, reg, bitmask); +} + +static int sx9500_inc_chan_users(struct sx9500_data *data, int chan) +{ + return sx9500_inc_users(data, &data->channel_users[chan], + SX9500_REG_PROX_CTRL0, BIT(chan)); +} + +static int sx9500_dec_chan_users(struct sx9500_data *data, int chan) +{ + return sx9500_dec_users(data, &data->channel_users[chan], + SX9500_REG_PROX_CTRL0, BIT(chan)); +} + +static int sx9500_inc_data_rdy_users(struct sx9500_data *data) +{ + return sx9500_inc_users(data, &data->data_rdy_users, + SX9500_REG_IRQ_MSK, SX9500_CONVDONE_IRQ); +} + +static int sx9500_dec_data_rdy_users(struct sx9500_data *data) +{ + return sx9500_dec_users(data, &data->data_rdy_users, + SX9500_REG_IRQ_MSK, SX9500_CONVDONE_IRQ); +} + +static int sx9500_inc_close_far_users(struct sx9500_data *data) +{ + return sx9500_inc_users(data, &data->close_far_users, + SX9500_REG_IRQ_MSK, + SX9500_CLOSE_IRQ | SX9500_FAR_IRQ); +} + +static int sx9500_dec_close_far_users(struct sx9500_data *data) +{ + return sx9500_dec_users(data, &data->close_far_users, + SX9500_REG_IRQ_MSK, + SX9500_CLOSE_IRQ | SX9500_FAR_IRQ); +} + +static int sx9500_read_prox_data(struct sx9500_data *data, + const struct iio_chan_spec *chan, + int *val) +{ + int ret; + __be16 regval; + + ret = regmap_write(data->regmap, SX9500_REG_SENSOR_SEL, chan->channel); + if (ret < 0) + return ret; + + ret = regmap_bulk_read(data->regmap, SX9500_REG_USE_MSB, ®val, 2); + if (ret < 0) + return ret; + + *val = be16_to_cpu(regval); + + return IIO_VAL_INT; +} + +/* + * If we have no interrupt support, we have to wait for a scan period + * after enabling a channel to get a result. + */ +static int sx9500_wait_for_sample(struct sx9500_data *data) +{ + int ret; + unsigned int val; + + ret = regmap_read(data->regmap, SX9500_REG_PROX_CTRL0, &val); + if (ret < 0) + return ret; + + val = (val & SX9500_SCAN_PERIOD_MASK) >> SX9500_SCAN_PERIOD_SHIFT; + + msleep(sx9500_scan_period_table[val]); + + return 0; +} + +static int sx9500_read_proximity(struct sx9500_data *data, + const struct iio_chan_spec *chan, + int *val) +{ + int ret; + + mutex_lock(&data->mutex); + + ret = sx9500_inc_chan_users(data, chan->channel); + if (ret < 0) + goto out; + + ret = sx9500_inc_data_rdy_users(data); + if (ret < 0) + goto out_dec_chan; + + mutex_unlock(&data->mutex); + + if (data->client->irq > 0) + ret = wait_for_completion_interruptible(&data->completion); + else + ret = sx9500_wait_for_sample(data); + + mutex_lock(&data->mutex); + + if (ret < 0) + goto out_dec_data_rdy; + + ret = sx9500_read_prox_data(data, chan, val); + if (ret < 0) + goto out_dec_data_rdy; + + ret = sx9500_dec_data_rdy_users(data); + if (ret < 0) + goto out_dec_chan; + + ret = sx9500_dec_chan_users(data, chan->channel); + if (ret < 0) + goto out; + + ret = IIO_VAL_INT; + + goto out; + +out_dec_data_rdy: + sx9500_dec_data_rdy_users(data); +out_dec_chan: + sx9500_dec_chan_users(data, chan->channel); +out: + mutex_unlock(&data->mutex); + reinit_completion(&data->completion); + + return ret; +} + +static int sx9500_read_samp_freq(struct sx9500_data *data, + int *val, int *val2) +{ + int ret; + unsigned int regval; + + mutex_lock(&data->mutex); + ret = regmap_read(data->regmap, SX9500_REG_PROX_CTRL0, ®val); + mutex_unlock(&data->mutex); + + if (ret < 0) + return ret; + + regval = (regval & SX9500_SCAN_PERIOD_MASK) >> SX9500_SCAN_PERIOD_SHIFT; + *val = sx9500_samp_freq_table[regval].val; + *val2 = sx9500_samp_freq_table[regval].val2; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int sx9500_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct sx9500_data *data = iio_priv(indio_dev); + int ret; + + switch (chan->type) { + case IIO_PROXIMITY: + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = sx9500_read_proximity(data, chan, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + return sx9500_read_samp_freq(data, val, val2); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int sx9500_set_samp_freq(struct sx9500_data *data, + int val, int val2) +{ + int i, ret; + + for (i = 0; i < ARRAY_SIZE(sx9500_samp_freq_table); i++) + if (val == sx9500_samp_freq_table[i].val && + val2 == sx9500_samp_freq_table[i].val2) + break; + + if (i == ARRAY_SIZE(sx9500_samp_freq_table)) + return -EINVAL; + + mutex_lock(&data->mutex); + + ret = regmap_update_bits(data->regmap, SX9500_REG_PROX_CTRL0, + SX9500_SCAN_PERIOD_MASK, + i << SX9500_SCAN_PERIOD_SHIFT); + + mutex_unlock(&data->mutex); + + return ret; +} + +static int sx9500_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int val, int val2, long mask) +{ + struct sx9500_data *data = iio_priv(indio_dev); + + switch (chan->type) { + case IIO_PROXIMITY: + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return sx9500_set_samp_freq(data, val, val2); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static irqreturn_t sx9500_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct sx9500_data *data = iio_priv(indio_dev); + + if (data->trigger_enabled) + iio_trigger_poll(data->trig); + + /* + * Even if no event is enabled, we need to wake the thread to + * clear the interrupt state by reading SX9500_REG_IRQ_SRC. It + * is not possible to do that here because regmap_read takes a + * mutex. + */ + return IRQ_WAKE_THREAD; +} + +static void sx9500_push_events(struct iio_dev *indio_dev) +{ + int ret; + unsigned int val, chan; + struct sx9500_data *data = iio_priv(indio_dev); + + ret = regmap_read(data->regmap, SX9500_REG_STAT, &val); + if (ret < 0) { + dev_err(&data->client->dev, "i2c transfer error in irq\n"); + return; + } + + val >>= SX9500_PROXSTAT_SHIFT; + for (chan = 0; chan < SX9500_NUM_CHANNELS; chan++) { + int dir; + u64 ev; + bool new_prox = val & BIT(chan); + + if (!data->event_enabled[chan]) + continue; + if (new_prox == data->prox_stat[chan]) + /* No change on this channel. */ + continue; + + dir = new_prox ? IIO_EV_DIR_FALLING : IIO_EV_DIR_RISING; + ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, chan, + IIO_EV_TYPE_THRESH, dir); + iio_push_event(indio_dev, ev, iio_get_time_ns(indio_dev)); + data->prox_stat[chan] = new_prox; + } +} + +static irqreturn_t sx9500_irq_thread_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct sx9500_data *data = iio_priv(indio_dev); + int ret; + unsigned int val; + + mutex_lock(&data->mutex); + + ret = regmap_read(data->regmap, SX9500_REG_IRQ_SRC, &val); + if (ret < 0) { + dev_err(&data->client->dev, "i2c transfer error in irq\n"); + goto out; + } + + if (val & (SX9500_CLOSE_IRQ | SX9500_FAR_IRQ)) + sx9500_push_events(indio_dev); + + if (val & SX9500_CONVDONE_IRQ) + complete(&data->completion); + +out: + mutex_unlock(&data->mutex); + + return IRQ_HANDLED; +} + +static int sx9500_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct sx9500_data *data = iio_priv(indio_dev); + + if (chan->type != IIO_PROXIMITY || type != IIO_EV_TYPE_THRESH || + dir != IIO_EV_DIR_EITHER) + return -EINVAL; + + return data->event_enabled[chan->channel]; +} + +static int sx9500_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct sx9500_data *data = iio_priv(indio_dev); + int ret; + + if (chan->type != IIO_PROXIMITY || type != IIO_EV_TYPE_THRESH || + dir != IIO_EV_DIR_EITHER) + return -EINVAL; + + mutex_lock(&data->mutex); + + if (state) { + ret = sx9500_inc_chan_users(data, chan->channel); + if (ret < 0) + goto out_unlock; + ret = sx9500_inc_close_far_users(data); + if (ret < 0) + goto out_undo_chan; + } else { + ret = sx9500_dec_chan_users(data, chan->channel); + if (ret < 0) + goto out_unlock; + ret = sx9500_dec_close_far_users(data); + if (ret < 0) + goto out_undo_chan; + } + + data->event_enabled[chan->channel] = state; + goto out_unlock; + +out_undo_chan: + if (state) + sx9500_dec_chan_users(data, chan->channel); + else + sx9500_inc_chan_users(data, chan->channel); +out_unlock: + mutex_unlock(&data->mutex); + return ret; +} + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL( + "2.500000 3.333333 5 6.666666 8.333333 11.111111 16.666666 33.333333"); + +static struct attribute *sx9500_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group sx9500_attribute_group = { + .attrs = sx9500_attributes, +}; + +static const struct iio_info sx9500_info = { + .attrs = &sx9500_attribute_group, + .read_raw = &sx9500_read_raw, + .write_raw = &sx9500_write_raw, + .read_event_config = &sx9500_read_event_config, + .write_event_config = &sx9500_write_event_config, +}; + +static int sx9500_set_trigger_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct sx9500_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + + if (state) + ret = sx9500_inc_data_rdy_users(data); + else + ret = sx9500_dec_data_rdy_users(data); + if (ret < 0) + goto out; + + data->trigger_enabled = state; + +out: + mutex_unlock(&data->mutex); + + return ret; +} + +static const struct iio_trigger_ops sx9500_trigger_ops = { + .set_trigger_state = sx9500_set_trigger_state, +}; + +static irqreturn_t sx9500_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct sx9500_data *data = iio_priv(indio_dev); + int val, bit, ret, i = 0; + struct { + u16 chan[SX9500_NUM_CHANNELS]; + aligned_s64 timestamp; + } scan = { }; + + mutex_lock(&data->mutex); + + iio_for_each_active_channel(indio_dev, bit) { + ret = sx9500_read_prox_data(data, &indio_dev->channels[bit], + &val); + if (ret < 0) + goto out; + + scan.chan[i++] = val; + } + + iio_push_to_buffers_with_timestamp(indio_dev, &scan, + iio_get_time_ns(indio_dev)); + +out: + mutex_unlock(&data->mutex); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int sx9500_buffer_postenable(struct iio_dev *indio_dev) +{ + struct sx9500_data *data = iio_priv(indio_dev); + int ret = 0, i; + + mutex_lock(&data->mutex); + + for (i = 0; i < SX9500_NUM_CHANNELS; i++) + if (test_bit(i, indio_dev->active_scan_mask)) { + ret = sx9500_inc_chan_users(data, i); + if (ret) + break; + } + + if (ret) + for (i = i - 1; i >= 0; i--) + if (test_bit(i, indio_dev->active_scan_mask)) + sx9500_dec_chan_users(data, i); + + mutex_unlock(&data->mutex); + + return ret; +} + +static int sx9500_buffer_predisable(struct iio_dev *indio_dev) +{ + struct sx9500_data *data = iio_priv(indio_dev); + int ret = 0, i; + + mutex_lock(&data->mutex); + + for (i = 0; i < SX9500_NUM_CHANNELS; i++) + if (test_bit(i, indio_dev->active_scan_mask)) { + ret = sx9500_dec_chan_users(data, i); + if (ret) + break; + } + + if (ret) + for (i = i - 1; i >= 0; i--) + if (test_bit(i, indio_dev->active_scan_mask)) + sx9500_inc_chan_users(data, i); + + mutex_unlock(&data->mutex); + + return ret; +} + +static const struct iio_buffer_setup_ops sx9500_buffer_setup_ops = { + .postenable = sx9500_buffer_postenable, + .predisable = sx9500_buffer_predisable, +}; + +struct sx9500_reg_default { + u8 reg; + u8 def; +}; + +static const struct sx9500_reg_default sx9500_default_regs[] = { + { + .reg = SX9500_REG_PROX_CTRL1, + /* Shield enabled, small range. */ + .def = 0x43, + }, + { + .reg = SX9500_REG_PROX_CTRL2, + /* x8 gain, 167kHz frequency, finest resolution. */ + .def = 0x77, + }, + { + .reg = SX9500_REG_PROX_CTRL3, + /* Doze enabled, 2x scan period doze, no raw filter. */ + .def = 0x40, + }, + { + .reg = SX9500_REG_PROX_CTRL4, + /* Average threshold. */ + .def = 0x30, + }, + { + .reg = SX9500_REG_PROX_CTRL5, + /* + * Debouncer off, lowest average negative filter, + * highest average positive filter. + */ + .def = 0x0f, + }, + { + .reg = SX9500_REG_PROX_CTRL6, + /* Proximity detection threshold: 280 */ + .def = 0x0e, + }, + { + .reg = SX9500_REG_PROX_CTRL7, + /* + * No automatic compensation, compensate each pin + * independently, proximity hysteresis: 32, close + * debouncer off, far debouncer off. + */ + .def = 0x00, + }, + { + .reg = SX9500_REG_PROX_CTRL8, + /* No stuck timeout, no periodic compensation. */ + .def = 0x00, + }, + { + .reg = SX9500_REG_PROX_CTRL0, + /* Scan period: 30ms, all sensors disabled. */ + .def = 0x00, + }, +}; + +/* Activate all channels and perform an initial compensation. */ +static int sx9500_init_compensation(struct iio_dev *indio_dev) +{ + struct sx9500_data *data = iio_priv(indio_dev); + int i, ret; + unsigned int val; + + ret = regmap_set_bits(data->regmap, SX9500_REG_PROX_CTRL0, + SX9500_CHAN_MASK); + if (ret < 0) + return ret; + + for (i = 10; i >= 0; i--) { + usleep_range(10000, 20000); + ret = regmap_read(data->regmap, SX9500_REG_STAT, &val); + if (ret < 0) + goto out; + if (!(val & SX9500_COMPSTAT_MASK)) + break; + } + + if (i < 0) { + dev_err(&data->client->dev, "initial compensation timed out"); + ret = -ETIMEDOUT; + } + +out: + regmap_clear_bits(data->regmap, SX9500_REG_PROX_CTRL0, + SX9500_CHAN_MASK); + return ret; +} + +static int sx9500_init_device(struct iio_dev *indio_dev) +{ + struct sx9500_data *data = iio_priv(indio_dev); + int ret, i; + unsigned int val; + + if (data->gpiod_rst) { + gpiod_set_value_cansleep(data->gpiod_rst, 0); + usleep_range(1000, 2000); + gpiod_set_value_cansleep(data->gpiod_rst, 1); + usleep_range(1000, 2000); + } + + ret = regmap_write(data->regmap, SX9500_REG_IRQ_MSK, 0); + if (ret < 0) + return ret; + + ret = regmap_write(data->regmap, SX9500_REG_RESET, + SX9500_SOFT_RESET); + if (ret < 0) + return ret; + + ret = regmap_read(data->regmap, SX9500_REG_IRQ_SRC, &val); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(sx9500_default_regs); i++) { + ret = regmap_write(data->regmap, + sx9500_default_regs[i].reg, + sx9500_default_regs[i].def); + if (ret < 0) + return ret; + } + + return sx9500_init_compensation(indio_dev); +} + +static const struct acpi_gpio_params reset_gpios = { 0, 0, false }; +static const struct acpi_gpio_params interrupt_gpios = { 2, 0, false }; + +static const struct acpi_gpio_mapping acpi_sx9500_gpios[] = { + { "reset-gpios", &reset_gpios, 1 }, + /* + * Some platforms have a bug in ACPI GPIO description making IRQ + * GPIO to be output only. Ask the GPIO core to ignore this limit. + */ + { "interrupt-gpios", &interrupt_gpios, 1, ACPI_GPIO_QUIRK_NO_IO_RESTRICTION }, + { } +}; + +static void sx9500_gpio_probe(struct i2c_client *client, + struct sx9500_data *data) +{ + struct gpio_desc *gpiod_int; + struct device *dev; + int ret; + + if (!client) + return; + + dev = &client->dev; + + ret = devm_acpi_dev_add_driver_gpios(dev, acpi_sx9500_gpios); + if (ret) + dev_dbg(dev, "Unable to add GPIO mapping table\n"); + + if (client->irq <= 0) { + gpiod_int = devm_gpiod_get(dev, "interrupt", GPIOD_IN); + if (IS_ERR(gpiod_int)) + dev_err(dev, "gpio get irq failed\n"); + else + client->irq = gpiod_to_irq(gpiod_int); + } + + data->gpiod_rst = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(data->gpiod_rst)) { + dev_warn(dev, "gpio get reset pin failed\n"); + data->gpiod_rst = NULL; + } +} + +static int sx9500_probe(struct i2c_client *client) +{ + int ret; + struct iio_dev *indio_dev; + struct sx9500_data *data; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (indio_dev == NULL) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + mutex_init(&data->mutex); + init_completion(&data->completion); + data->trigger_enabled = false; + + data->regmap = devm_regmap_init_i2c(client, &sx9500_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + + indio_dev->name = SX9500_DRIVER_NAME; + indio_dev->channels = sx9500_channels; + indio_dev->num_channels = ARRAY_SIZE(sx9500_channels); + indio_dev->info = &sx9500_info; + indio_dev->modes = INDIO_DIRECT_MODE; + i2c_set_clientdata(client, indio_dev); + + sx9500_gpio_probe(client, data); + + ret = sx9500_init_device(indio_dev); + if (ret < 0) + return ret; + + if (client->irq <= 0) + dev_warn(&client->dev, "no valid irq found\n"); + else { + ret = devm_request_threaded_irq(&client->dev, client->irq, + sx9500_irq_handler, sx9500_irq_thread_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "sx9500_event", indio_dev); + if (ret < 0) + return ret; + + data->trig = devm_iio_trigger_alloc(&client->dev, + "%s-dev%d", indio_dev->name, iio_device_id(indio_dev)); + if (!data->trig) + return -ENOMEM; + + data->trig->ops = &sx9500_trigger_ops; + iio_trigger_set_drvdata(data->trig, indio_dev); + + ret = iio_trigger_register(data->trig); + if (ret) + return ret; + } + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + sx9500_trigger_handler, + &sx9500_buffer_setup_ops); + if (ret < 0) + goto out_trigger_unregister; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto out_buffer_cleanup; + + return 0; + +out_buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); +out_trigger_unregister: + if (client->irq > 0) + iio_trigger_unregister(data->trig); + + return ret; +} + +static void sx9500_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct sx9500_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + if (client->irq > 0) + iio_trigger_unregister(data->trig); +} + +static int sx9500_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct sx9500_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = regmap_read(data->regmap, SX9500_REG_PROX_CTRL0, + &data->suspend_ctrl0); + if (ret < 0) + goto out; + + /* + * Scan period doesn't matter because when all the sensors are + * deactivated the device is in sleep mode. + */ + ret = regmap_write(data->regmap, SX9500_REG_PROX_CTRL0, 0); + +out: + mutex_unlock(&data->mutex); + return ret; +} + +static int sx9500_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct sx9500_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = regmap_write(data->regmap, SX9500_REG_PROX_CTRL0, + data->suspend_ctrl0); + mutex_unlock(&data->mutex); + + return ret; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(sx9500_pm_ops, sx9500_suspend, sx9500_resume); + +static const struct acpi_device_id sx9500_acpi_match[] = { + {"SSX9500", 0}, + {"SASX9500", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, sx9500_acpi_match); + +static const struct of_device_id sx9500_of_match[] = { + { .compatible = "semtech,sx9500", }, + { } +}; +MODULE_DEVICE_TABLE(of, sx9500_of_match); + +static const struct i2c_device_id sx9500_id[] = { + { .name = "sx9500" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sx9500_id); + +static struct i2c_driver sx9500_driver = { + .driver = { + .name = SX9500_DRIVER_NAME, + .acpi_match_table = sx9500_acpi_match, + .of_match_table = sx9500_of_match, + .pm = pm_sleep_ptr(&sx9500_pm_ops), + }, + .probe = sx9500_probe, + .remove = sx9500_remove, + .id_table = sx9500_id, +}; +module_i2c_driver(sx9500_driver); + +MODULE_AUTHOR("Vlad Dogaru "); +MODULE_DESCRIPTION("Driver for Semtech SX9500 proximity sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/proximity/sx_common.c b/drivers/iio/proximity/sx_common.c new file mode 100644 index 000000000..4d4937395 --- /dev/null +++ b/drivers/iio/proximity/sx_common.c @@ -0,0 +1,549 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2021 Google LLC. + * + * Common part of most Semtech SAR sensor. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "sx_common.h" + +/* All Semtech SAR sensors have IRQ bit in the same order. */ +#define SX_COMMON_CONVDONE_IRQ BIT(0) +#define SX_COMMON_FAR_IRQ BIT(2) +#define SX_COMMON_CLOSE_IRQ BIT(3) + +const struct iio_event_spec sx_common_events[3] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_all = BIT(IIO_EV_INFO_PERIOD), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_shared_by_all = BIT(IIO_EV_INFO_PERIOD), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_HYSTERESIS) | + BIT(IIO_EV_INFO_VALUE), + }, +}; +EXPORT_SYMBOL_NS_GPL(sx_common_events, "SEMTECH_PROX"); + +static irqreturn_t sx_common_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct sx_common_data *data = iio_priv(indio_dev); + + if (data->trigger_enabled) + iio_trigger_poll(data->trig); + + /* + * Even if no event is enabled, we need to wake the thread to clear the + * interrupt state by reading SX_COMMON_REG_IRQ_SRC. + * It is not possible to do that here because regmap_read takes a mutex. + */ + return IRQ_WAKE_THREAD; +} + +static void sx_common_push_events(struct iio_dev *indio_dev) +{ + int ret; + unsigned int val, chan; + struct sx_common_data *data = iio_priv(indio_dev); + s64 timestamp = iio_get_time_ns(indio_dev); + unsigned long prox_changed; + + /* Read proximity state on all channels */ + ret = regmap_read(data->regmap, data->chip_info->reg_stat, &val); + if (ret) { + dev_err(&data->client->dev, "i2c transfer error in irq\n"); + return; + } + + val >>= data->chip_info->stat_offset; + + /* + * Only iterate over channels with changes on proximity status that have + * events enabled. + */ + prox_changed = (data->chan_prox_stat ^ val) & data->chan_event; + + for_each_set_bit(chan, &prox_changed, data->chip_info->num_channels) { + int dir; + u64 ev; + + dir = (val & BIT(chan)) ? IIO_EV_DIR_FALLING : IIO_EV_DIR_RISING; + ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, chan, + IIO_EV_TYPE_THRESH, dir); + + iio_push_event(indio_dev, ev, timestamp); + } + data->chan_prox_stat = val; +} + +static int sx_common_enable_irq(struct sx_common_data *data, unsigned int irq) +{ + if (!data->client->irq) + return 0; + return regmap_set_bits(data->regmap, data->chip_info->reg_irq_msk, + irq << data->chip_info->irq_msk_offset); +} + +static int sx_common_disable_irq(struct sx_common_data *data, unsigned int irq) +{ + if (!data->client->irq) + return 0; + return regmap_clear_bits(data->regmap, data->chip_info->reg_irq_msk, + irq << data->chip_info->irq_msk_offset); +} + +static int sx_common_update_chan_en(struct sx_common_data *data, + unsigned long chan_read, + unsigned long chan_event) +{ + int ret; + unsigned long channels = chan_read | chan_event; + + if ((data->chan_read | data->chan_event) != channels) { + ret = regmap_update_bits(data->regmap, + data->chip_info->reg_enable_chan, + data->chip_info->mask_enable_chan, + channels); + if (ret) + return ret; + } + data->chan_read = chan_read; + data->chan_event = chan_event; + return 0; +} + +static int sx_common_get_read_channel(struct sx_common_data *data, int channel) +{ + return sx_common_update_chan_en(data, data->chan_read | BIT(channel), + data->chan_event); +} + +static int sx_common_put_read_channel(struct sx_common_data *data, int channel) +{ + return sx_common_update_chan_en(data, data->chan_read & ~BIT(channel), + data->chan_event); +} + +static int sx_common_get_event_channel(struct sx_common_data *data, int channel) +{ + return sx_common_update_chan_en(data, data->chan_read, + data->chan_event | BIT(channel)); +} + +static int sx_common_put_event_channel(struct sx_common_data *data, int channel) +{ + return sx_common_update_chan_en(data, data->chan_read, + data->chan_event & ~BIT(channel)); +} + +/** + * sx_common_read_proximity() - Read raw proximity value. + * @data: Internal data + * @chan: Channel to read + * @val: pointer to return read value. + * + * Request a conversion, wait for the sensor to be ready and + * return the raw proximity value. + */ +int sx_common_read_proximity(struct sx_common_data *data, + const struct iio_chan_spec *chan, int *val) +{ + int ret; + __be16 rawval; + + mutex_lock(&data->mutex); + + ret = sx_common_get_read_channel(data, chan->channel); + if (ret) + goto out; + + ret = sx_common_enable_irq(data, SX_COMMON_CONVDONE_IRQ); + if (ret) + goto out_put_channel; + + mutex_unlock(&data->mutex); + + if (data->client->irq) { + ret = wait_for_completion_interruptible(&data->completion); + reinit_completion(&data->completion); + } else { + ret = data->chip_info->ops.wait_for_sample(data); + } + + mutex_lock(&data->mutex); + + if (ret) + goto out_disable_irq; + + ret = data->chip_info->ops.read_prox_data(data, chan, &rawval); + if (ret) + goto out_disable_irq; + + *val = sign_extend32(be16_to_cpu(rawval), chan->scan_type.realbits - 1); + + ret = sx_common_disable_irq(data, SX_COMMON_CONVDONE_IRQ); + if (ret) + goto out_put_channel; + + ret = sx_common_put_read_channel(data, chan->channel); + if (ret) + goto out; + + mutex_unlock(&data->mutex); + + return IIO_VAL_INT; + +out_disable_irq: + sx_common_disable_irq(data, SX_COMMON_CONVDONE_IRQ); +out_put_channel: + sx_common_put_read_channel(data, chan->channel); +out: + mutex_unlock(&data->mutex); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(sx_common_read_proximity, "SEMTECH_PROX"); + +/** + * sx_common_read_event_config() - Configure event setting. + * @indio_dev: iio device object + * @chan: Channel to read + * @type: Type of event (unused) + * @dir: Direction of event (unused) + * + * return if the given channel is used for event gathering. + */ +int sx_common_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct sx_common_data *data = iio_priv(indio_dev); + + return !!(data->chan_event & BIT(chan->channel)); +} +EXPORT_SYMBOL_NS_GPL(sx_common_read_event_config, "SEMTECH_PROX"); + +/** + * sx_common_write_event_config() - Configure event setting. + * @indio_dev: iio device object + * @chan: Channel to enable + * @type: Type of event (unused) + * @dir: Direction of event (unused) + * @state: State of the event. + * + * Enable/Disable event on a given channel. + */ +int sx_common_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct sx_common_data *data = iio_priv(indio_dev); + unsigned int eventirq = SX_COMMON_FAR_IRQ | SX_COMMON_CLOSE_IRQ; + int ret; + + /* If the state hasn't changed, there's nothing to do. */ + if (!!(data->chan_event & BIT(chan->channel)) == state) + return 0; + + mutex_lock(&data->mutex); + if (state) { + ret = sx_common_get_event_channel(data, chan->channel); + if (ret) + goto out_unlock; + if (!(data->chan_event & ~BIT(chan->channel))) { + ret = sx_common_enable_irq(data, eventirq); + if (ret) + sx_common_put_event_channel(data, chan->channel); + } + } else { + ret = sx_common_put_event_channel(data, chan->channel); + if (ret) + goto out_unlock; + if (!data->chan_event) { + ret = sx_common_disable_irq(data, eventirq); + if (ret) + sx_common_get_event_channel(data, chan->channel); + } + } + +out_unlock: + mutex_unlock(&data->mutex); + return ret; +} +EXPORT_SYMBOL_NS_GPL(sx_common_write_event_config, "SEMTECH_PROX"); + +static int sx_common_set_trigger_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct sx_common_data *data = iio_priv(indio_dev); + int ret = 0; + + mutex_lock(&data->mutex); + + if (state) + ret = sx_common_enable_irq(data, SX_COMMON_CONVDONE_IRQ); + else if (!data->chan_read) + ret = sx_common_disable_irq(data, SX_COMMON_CONVDONE_IRQ); + if (ret) + goto out; + + data->trigger_enabled = state; + +out: + mutex_unlock(&data->mutex); + + return ret; +} + +static const struct iio_trigger_ops sx_common_trigger_ops = { + .set_trigger_state = sx_common_set_trigger_state, +}; + +static irqreturn_t sx_common_irq_thread_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct sx_common_data *data = iio_priv(indio_dev); + int ret; + unsigned int val; + + mutex_lock(&data->mutex); + + ret = regmap_read(data->regmap, SX_COMMON_REG_IRQ_SRC, &val); + if (ret) { + dev_err(&data->client->dev, "i2c transfer error in irq\n"); + goto out; + } + + if (val & ((SX_COMMON_FAR_IRQ | SX_COMMON_CLOSE_IRQ) << data->chip_info->irq_msk_offset)) + sx_common_push_events(indio_dev); + + if (val & (SX_COMMON_CONVDONE_IRQ << data->chip_info->irq_msk_offset)) + complete(&data->completion); + +out: + mutex_unlock(&data->mutex); + + return IRQ_HANDLED; +} + +static irqreturn_t sx_common_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct sx_common_data *data = iio_priv(indio_dev); + __be16 val; + int bit, ret, i = 0; + + mutex_lock(&data->mutex); + + iio_for_each_active_channel(indio_dev, bit) { + ret = data->chip_info->ops.read_prox_data(data, + &indio_dev->channels[bit], + &val); + if (ret) + goto out; + + data->buffer.channels[i++] = val; + } + + iio_push_to_buffers_with_ts(indio_dev, &data->buffer, + sizeof(data->buffer), pf->timestamp); + +out: + mutex_unlock(&data->mutex); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int sx_common_buffer_preenable(struct iio_dev *indio_dev) +{ + struct sx_common_data *data = iio_priv(indio_dev); + unsigned long channels = 0; + int bit, ret; + + mutex_lock(&data->mutex); + iio_for_each_active_channel(indio_dev, bit) + __set_bit(indio_dev->channels[bit].channel, &channels); + + ret = sx_common_update_chan_en(data, channels, data->chan_event); + mutex_unlock(&data->mutex); + return ret; +} + +static int sx_common_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct sx_common_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = sx_common_update_chan_en(data, 0, data->chan_event); + mutex_unlock(&data->mutex); + return ret; +} + +static const struct iio_buffer_setup_ops sx_common_buffer_setup_ops = { + .preenable = sx_common_buffer_preenable, + .postdisable = sx_common_buffer_postdisable, +}; + +#define SX_COMMON_SOFT_RESET 0xde + +static int sx_common_init_device(struct device *dev, struct iio_dev *indio_dev) +{ + struct sx_common_data *data = iio_priv(indio_dev); + struct sx_common_reg_default tmp; + const struct sx_common_reg_default *initval; + int ret; + unsigned int i, val; + + ret = regmap_write(data->regmap, data->chip_info->reg_reset, + SX_COMMON_SOFT_RESET); + if (ret) + return ret; + + usleep_range(1000, 2000); /* power-up time is ~1ms. */ + + /* Clear reset interrupt state by reading SX_COMMON_REG_IRQ_SRC. */ + ret = regmap_read(data->regmap, SX_COMMON_REG_IRQ_SRC, &val); + if (ret) + return ret; + + /* Program defaults from constant or BIOS. */ + for (i = 0; i < data->chip_info->num_default_regs; i++) { + initval = data->chip_info->ops.get_default_reg(dev, i, &tmp); + ret = regmap_write(data->regmap, initval->reg, initval->def); + if (ret) + return ret; + } + + return data->chip_info->ops.init_compensation(indio_dev); +} + +/** + * sx_common_probe() - Common setup for Semtech SAR sensor + * @client: I2C client object + * @chip_info: Semtech sensor chip information. + * @regmap_config: Sensor registers map configuration. + */ +int sx_common_probe(struct i2c_client *client, + const struct sx_common_chip_info *chip_info, + const struct regmap_config *regmap_config) +{ + static const char * const regulator_names[] = { "vdd", "svdd" }; + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct sx_common_data *data; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + + data->chip_info = chip_info; + data->client = client; + mutex_init(&data->mutex); + init_completion(&data->completion); + + data->regmap = devm_regmap_init_i2c(client, regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "Could init register map\n"); + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulator_names), + regulator_names); + if (ret) + return dev_err_probe(dev, ret, "Unable to get regulators\n"); + + /* Must wait for Tpor time after initial power up */ + usleep_range(1000, 1100); + + ret = data->chip_info->ops.check_whoami(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "error reading WHOAMI\n"); + + indio_dev->modes = INDIO_DIRECT_MODE; + + indio_dev->channels = data->chip_info->iio_channels; + indio_dev->num_channels = data->chip_info->num_iio_channels; + indio_dev->info = &data->chip_info->iio_info; + + i2c_set_clientdata(client, indio_dev); + + ret = sx_common_init_device(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Unable to initialize sensor\n"); + + if (client->irq) { + ret = devm_request_threaded_irq(dev, client->irq, + sx_common_irq_handler, + sx_common_irq_thread_handler, + IRQF_ONESHOT, + "sx_event", indio_dev); + if (ret) + return ret; + + data->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->trig) + return -ENOMEM; + + data->trig->ops = &sx_common_trigger_ops; + iio_trigger_set_drvdata(data->trig, indio_dev); + + ret = devm_iio_trigger_register(dev, data->trig); + if (ret) + return ret; + } + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + sx_common_trigger_handler, + &sx_common_buffer_setup_ops); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS_GPL(sx_common_probe, "SEMTECH_PROX"); + +MODULE_AUTHOR("Gwendal Grignou "); +MODULE_DESCRIPTION("Common functions and structures for Semtech sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/proximity/sx_common.h b/drivers/iio/proximity/sx_common.h new file mode 100644 index 000000000..259b5c695 --- /dev/null +++ b/drivers/iio/proximity/sx_common.h @@ -0,0 +1,155 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2021 Google LLC. + * + * Code shared between most Semtech SAR sensor driver. + */ + +#ifndef IIO_SX_COMMON_H +#define IIO_SX_COMMON_H + +#include +#include +#include +#include + +struct device; +struct i2c_client; +struct regmap_config; +struct sx_common_data; + +#define SX_COMMON_REG_IRQ_SRC 0x00 + +#define SX_COMMON_MAX_NUM_CHANNELS 4 +static_assert(SX_COMMON_MAX_NUM_CHANNELS < BITS_PER_LONG); + +struct sx_common_reg_default { + u8 reg; + u8 def; + const char *property; +}; + +/** + * struct sx_common_ops: function pointers needed by common code + * + * List functions needed by common code to gather information or configure + * the sensor. + * + * @read_prox_data: Function to read raw proximity data. + * @check_whoami: Set device name based on whoami register. + * @init_compensation: Function to set initial compensation. + * @wait_for_sample: When there are no physical IRQ, function to wait for a + * sample to be ready. + * @get_default_reg: Populate the initial value for a given register. + */ +struct sx_common_ops { + int (*read_prox_data)(struct sx_common_data *data, + const struct iio_chan_spec *chan, __be16 *val); + int (*check_whoami)(struct device *dev, struct iio_dev *indio_dev); + int (*init_compensation)(struct iio_dev *indio_dev); + int (*wait_for_sample)(struct sx_common_data *data); + const struct sx_common_reg_default * + (*get_default_reg)(struct device *dev, int idx, + struct sx_common_reg_default *reg_def); +}; + +/** + * struct sx_common_chip_info: Semtech Sensor private chip information + * + * @reg_stat: Main status register address. + * @reg_irq_msk: IRQ mask register address. + * @reg_enable_chan: Address to enable/disable channels. + * Each phase presented by the sensor is an IIO channel.. + * @reg_reset: Reset register address. + * @mask_enable_chan: Mask over the channels bits in the enable channel + * register. + * @stat_offset: Offset to check phase status. + * @irq_msk_offset: Offset to enable interrupt in the IRQ mask + * register. + * @num_channels: Number of channels. + * @num_default_regs: Number of internal registers that can be configured. + * + * @ops: Private functions pointers. + * @iio_channels: Description of exposed iio channels. + * @num_iio_channels: Number of iio_channels. + * @iio_info: iio_info structure for this driver. + */ +struct sx_common_chip_info { + unsigned int reg_stat; + unsigned int reg_irq_msk; + unsigned int reg_enable_chan; + unsigned int reg_reset; + + unsigned int mask_enable_chan; + unsigned int stat_offset; + unsigned int irq_msk_offset; + unsigned int num_channels; + int num_default_regs; + + struct sx_common_ops ops; + + const struct iio_chan_spec *iio_channels; + int num_iio_channels; + struct iio_info iio_info; +}; + +/** + * struct sx_common_data: Semtech Sensor private data structure. + * + * @chip_info: Structure defining sensor internals. + * @mutex: Serialize access to registers and channel configuration. + * @completion: completion object to wait for data acquisition. + * @client: I2C client structure. + * @trig: IIO trigger object. + * @regmap: Register map. + * @chan_prox_stat: Last reading of the proximity status for each channel. + * We only send an event to user space when this changes. + * @trigger_enabled: True when the device trigger is enabled. + * @buffer: Buffer to store raw samples. + * @suspend_ctrl: Remember enabled channels and sample rate during suspend. + * @chan_read: Bit field for each raw channel enabled. + * @chan_event: Bit field for each event enabled. + */ +struct sx_common_data { + const struct sx_common_chip_info *chip_info; + + struct mutex mutex; + struct completion completion; + struct i2c_client *client; + struct iio_trigger *trig; + struct regmap *regmap; + + unsigned long chan_prox_stat; + bool trigger_enabled; + + /* Ensure correct alignment of timestamp when present. */ + struct { + __be16 channels[SX_COMMON_MAX_NUM_CHANNELS]; + aligned_s64 ts; + } buffer; + + unsigned int suspend_ctrl; + unsigned long chan_read; + unsigned long chan_event; +}; + +int sx_common_read_proximity(struct sx_common_data *data, + const struct iio_chan_spec *chan, int *val); + +int sx_common_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir); +int sx_common_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, bool state); + +int sx_common_probe(struct i2c_client *client, + const struct sx_common_chip_info *chip_info, + const struct regmap_config *regmap_config); + +/* 3 is the number of events defined by a single phase. */ +extern const struct iio_event_spec sx_common_events[3]; + +#endif /* IIO_SX_COMMON_H */ diff --git a/drivers/iio/proximity/vcnl3020.c b/drivers/iio/proximity/vcnl3020.c new file mode 100644 index 000000000..6b0d7a903 --- /dev/null +++ b/drivers/iio/proximity/vcnl3020.c @@ -0,0 +1,667 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Support for Vishay VCNL3020 proximity sensor on i2c bus. + * Based on Vishay VCNL4000 driver code. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define VCNL3020_PROD_ID 0x21 + +#define VCNL_COMMAND 0x80 /* Command register */ +#define VCNL_PROD_REV 0x81 /* Product ID and Revision ID */ +#define VCNL_PROXIMITY_RATE 0x82 /* Rate of Proximity Measurement */ +#define VCNL_LED_CURRENT 0x83 /* IR LED current for proximity mode */ +#define VCNL_PS_RESULT_HI 0x87 /* Proximity result register, MSB */ +#define VCNL_PS_RESULT_LO 0x88 /* Proximity result register, LSB */ +#define VCNL_PS_ICR 0x89 /* Interrupt Control Register */ +#define VCNL_PS_LO_THR_HI 0x8a /* High byte of low threshold value */ +#define VCNL_PS_LO_THR_LO 0x8b /* Low byte of low threshold value */ +#define VCNL_PS_HI_THR_HI 0x8c /* High byte of high threshold value */ +#define VCNL_PS_HI_THR_LO 0x8d /* Low byte of high threshold value */ +#define VCNL_ISR 0x8e /* Interrupt Status Register */ +#define VCNL_PS_MOD_ADJ 0x8f /* Proximity Modulator Timing Adjustment */ + +/* Bit masks for COMMAND register */ +#define VCNL_PS_RDY BIT(5) /* proximity data ready? */ +#define VCNL_PS_OD BIT(3) /* start on-demand proximity + * measurement + */ + +/* Enables periodic proximity measurement */ +#define VCNL_PS_EN BIT(1) + +/* Enables state machine and LP oscillator for self timed measurements */ +#define VCNL_PS_SELFTIMED_EN BIT(0) + +/* Bit masks for ICR */ + +/* Enable interrupts on low or high thresholds */ +#define VCNL_ICR_THRES_EN BIT(1) + +/* Bit masks for ISR */ +#define VCNL_INT_TH_HI BIT(0) /* High threshold hit */ +#define VCNL_INT_TH_LOW BIT(1) /* Low threshold hit */ + +#define VCNL_ON_DEMAND_TIMEOUT_US 100000 +#define VCNL_POLL_US 20000 + +static const int vcnl3020_prox_sampling_frequency[][2] = { + {1, 950000}, + {3, 906250}, + {7, 812500}, + {16, 625000}, + {31, 250000}, + {62, 500000}, + {125, 0}, + {250, 0}, +}; + +/** + * struct vcnl3020_data - vcnl3020 specific data. + * @regmap: device register map. + * @dev: vcnl3020 device. + * @rev: revision id. + * @lock: lock for protecting access to device hardware registers. + * @buf: __be16 buffer. + */ +struct vcnl3020_data { + struct regmap *regmap; + struct device *dev; + u8 rev; + struct mutex lock; + __be16 buf; +}; + +/** + * struct vcnl3020_property - vcnl3020 property. + * @name: property name. + * @reg: i2c register offset. + * @conversion_func: conversion function. + */ +struct vcnl3020_property { + const char *name; + u32 reg; + u32 (*conversion_func)(u32 *val); +}; + +static u32 microamp_to_reg(u32 *val) +{ + /* + * An example of conversion from uA to reg val: + * 200000 uA == 200 mA == 20 + */ + return *val /= 10000; +}; + +static const struct vcnl3020_property vcnl3020_led_current_property = { + .name = "vishay,led-current-microamp", + .reg = VCNL_LED_CURRENT, + .conversion_func = microamp_to_reg, +}; + +static int vcnl3020_get_and_apply_property(struct vcnl3020_data *data, + const struct vcnl3020_property *prop) +{ + int rc; + u32 val; + + rc = device_property_read_u32(data->dev, prop->name, &val); + if (rc) + return 0; + + if (prop->conversion_func) + prop->conversion_func(&val); + + rc = regmap_write(data->regmap, prop->reg, val); + if (rc) { + dev_err(data->dev, "Error (%d) setting property (%s)\n", + rc, prop->name); + } + + return rc; +} + +static int vcnl3020_init(struct vcnl3020_data *data) +{ + int rc; + unsigned int reg; + + rc = regmap_read(data->regmap, VCNL_PROD_REV, ®); + if (rc) { + dev_err(data->dev, + "Error (%d) reading product revision\n", rc); + return rc; + } + + if (reg != VCNL3020_PROD_ID) { + dev_err(data->dev, + "Product id (%x) did not match vcnl3020 (%x)\n", reg, + VCNL3020_PROD_ID); + return -ENODEV; + } + + data->rev = reg; + mutex_init(&data->lock); + + return vcnl3020_get_and_apply_property(data, + &vcnl3020_led_current_property); +}; + +static bool vcnl3020_is_in_periodic_mode(struct vcnl3020_data *data) +{ + int rc; + unsigned int cmd; + + rc = regmap_read(data->regmap, VCNL_COMMAND, &cmd); + if (rc) { + dev_err(data->dev, + "Error (%d) reading command register\n", rc); + return false; + } + + return !!(cmd & VCNL_PS_SELFTIMED_EN); +} + +static int vcnl3020_measure_proximity(struct vcnl3020_data *data, int *val) +{ + int rc; + unsigned int reg; + + mutex_lock(&data->lock); + + /* Protect against event capture. */ + if (vcnl3020_is_in_periodic_mode(data)) { + rc = -EBUSY; + goto err_unlock; + } + + rc = regmap_write(data->regmap, VCNL_COMMAND, VCNL_PS_OD); + if (rc) + goto err_unlock; + + /* wait for data to become ready */ + rc = regmap_read_poll_timeout(data->regmap, VCNL_COMMAND, reg, + reg & VCNL_PS_RDY, VCNL_POLL_US, + VCNL_ON_DEMAND_TIMEOUT_US); + if (rc) { + dev_err(data->dev, + "Error (%d) reading vcnl3020 command register\n", rc); + goto err_unlock; + } + + /* high & low result bytes read */ + rc = regmap_bulk_read(data->regmap, VCNL_PS_RESULT_HI, &data->buf, + sizeof(data->buf)); + if (rc) + goto err_unlock; + + *val = be16_to_cpu(data->buf); + +err_unlock: + mutex_unlock(&data->lock); + + return rc; +} + +static int vcnl3020_read_proxy_samp_freq(struct vcnl3020_data *data, int *val, + int *val2) +{ + int rc; + unsigned int prox_rate; + + rc = regmap_read(data->regmap, VCNL_PROXIMITY_RATE, &prox_rate); + if (rc) + return rc; + + if (prox_rate >= ARRAY_SIZE(vcnl3020_prox_sampling_frequency)) + return -EINVAL; + + *val = vcnl3020_prox_sampling_frequency[prox_rate][0]; + *val2 = vcnl3020_prox_sampling_frequency[prox_rate][1]; + + return 0; +} + +static int vcnl3020_write_proxy_samp_freq(struct vcnl3020_data *data, int val, + int val2) +{ + unsigned int i; + int index = -1; + int rc; + + mutex_lock(&data->lock); + + /* Protect against event capture. */ + if (vcnl3020_is_in_periodic_mode(data)) { + rc = -EBUSY; + goto err_unlock; + } + + for (i = 0; i < ARRAY_SIZE(vcnl3020_prox_sampling_frequency); i++) { + if (val == vcnl3020_prox_sampling_frequency[i][0] && + val2 == vcnl3020_prox_sampling_frequency[i][1]) { + index = i; + break; + } + } + + if (index < 0) { + rc = -EINVAL; + goto err_unlock; + } + + rc = regmap_write(data->regmap, VCNL_PROXIMITY_RATE, index); + if (rc) + dev_err(data->dev, + "Error (%d) writing proximity rate register\n", rc); + +err_unlock: + mutex_unlock(&data->lock); + + return rc; +} + +static bool vcnl3020_is_thr_enabled(struct vcnl3020_data *data) +{ + int rc; + unsigned int icr; + + rc = regmap_read(data->regmap, VCNL_PS_ICR, &icr); + if (rc) { + dev_err(data->dev, + "Error (%d) reading ICR register\n", rc); + return false; + } + + return !!(icr & VCNL_ICR_THRES_EN); +} + +static int vcnl3020_read_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + int rc; + struct vcnl3020_data *data = iio_priv(indio_dev); + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + rc = regmap_bulk_read(data->regmap, VCNL_PS_HI_THR_HI, + &data->buf, sizeof(data->buf)); + if (rc < 0) + return rc; + *val = be16_to_cpu(data->buf); + return IIO_VAL_INT; + case IIO_EV_DIR_FALLING: + rc = regmap_bulk_read(data->regmap, VCNL_PS_LO_THR_HI, + &data->buf, sizeof(data->buf)); + if (rc < 0) + return rc; + *val = be16_to_cpu(data->buf); + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int vcnl3020_write_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + int rc; + struct vcnl3020_data *data = iio_priv(indio_dev); + + mutex_lock(&data->lock); + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + /* 16 bit word/ low * high */ + data->buf = cpu_to_be16(val); + rc = regmap_bulk_write(data->regmap, VCNL_PS_HI_THR_HI, + &data->buf, sizeof(data->buf)); + if (rc < 0) + goto err_unlock; + rc = IIO_VAL_INT; + goto err_unlock; + case IIO_EV_DIR_FALLING: + data->buf = cpu_to_be16(val); + rc = regmap_bulk_write(data->regmap, VCNL_PS_LO_THR_HI, + &data->buf, sizeof(data->buf)); + if (rc < 0) + goto err_unlock; + rc = IIO_VAL_INT; + goto err_unlock; + default: + rc = -EINVAL; + goto err_unlock; + } + default: + rc = -EINVAL; + goto err_unlock; + } +err_unlock: + mutex_unlock(&data->lock); + + return rc; +} + +static int vcnl3020_enable_periodic(struct iio_dev *indio_dev, + struct vcnl3020_data *data) +{ + int rc; + int cmd; + + mutex_lock(&data->lock); + + /* Enable periodic measurement of proximity data. */ + cmd = VCNL_PS_EN | VCNL_PS_SELFTIMED_EN; + + rc = regmap_write(data->regmap, VCNL_COMMAND, cmd); + if (rc) { + dev_err(data->dev, + "Error (%d) writing command register\n", rc); + goto err_unlock; + } + + /* + * Enable interrupts on threshold, for proximity data by + * default. + */ + rc = regmap_write(data->regmap, VCNL_PS_ICR, VCNL_ICR_THRES_EN); + if (rc) + dev_err(data->dev, + "Error (%d) reading ICR register\n", rc); + +err_unlock: + mutex_unlock(&data->lock); + + return rc; +} + +static int vcnl3020_disable_periodic(struct iio_dev *indio_dev, + struct vcnl3020_data *data) +{ + int rc; + + mutex_lock(&data->lock); + + rc = regmap_write(data->regmap, VCNL_COMMAND, 0); + if (rc) { + dev_err(data->dev, + "Error (%d) writing command register\n", rc); + goto err_unlock; + } + + rc = regmap_write(data->regmap, VCNL_PS_ICR, 0); + if (rc) { + dev_err(data->dev, + "Error (%d) writing ICR register\n", rc); + goto err_unlock; + } + + /* Clear interrupt flag bit */ + rc = regmap_write(data->regmap, VCNL_ISR, 0); + if (rc) + dev_err(data->dev, + "Error (%d) writing ISR register\n", rc); + +err_unlock: + mutex_unlock(&data->lock); + + return rc; +} + +static int vcnl3020_config_threshold(struct iio_dev *indio_dev, bool state) +{ + struct vcnl3020_data *data = iio_priv(indio_dev); + + if (state) { + return vcnl3020_enable_periodic(indio_dev, data); + } else { + if (!vcnl3020_is_thr_enabled(data)) + return 0; + return vcnl3020_disable_periodic(indio_dev, data); + } +} + +static int vcnl3020_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + switch (chan->type) { + case IIO_PROXIMITY: + return vcnl3020_config_threshold(indio_dev, state); + default: + return -EINVAL; + } +} + +static int vcnl3020_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct vcnl3020_data *data = iio_priv(indio_dev); + + switch (chan->type) { + case IIO_PROXIMITY: + return vcnl3020_is_thr_enabled(data); + default: + return -EINVAL; + } +} + +static const struct iio_event_spec vcnl3020_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec vcnl3020_channels[] = { + { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .event_spec = vcnl3020_event_spec, + .num_event_specs = ARRAY_SIZE(vcnl3020_event_spec), + }, +}; + +static int vcnl3020_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int rc; + struct vcnl3020_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + rc = vcnl3020_measure_proximity(data, val); + if (rc) + return rc; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + rc = vcnl3020_read_proxy_samp_freq(data, val, val2); + if (rc < 0) + return rc; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int vcnl3020_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct vcnl3020_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return vcnl3020_write_proxy_samp_freq(data, val, val2); + default: + return -EINVAL; + } +} + +static int vcnl3020_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = (int *)vcnl3020_prox_sampling_frequency; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = 2 * ARRAY_SIZE(vcnl3020_prox_sampling_frequency); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const struct iio_info vcnl3020_info = { + .read_raw = vcnl3020_read_raw, + .write_raw = vcnl3020_write_raw, + .read_avail = vcnl3020_read_avail, + .read_event_value = vcnl3020_read_event, + .write_event_value = vcnl3020_write_event, + .read_event_config = vcnl3020_read_event_config, + .write_event_config = vcnl3020_write_event_config, +}; + +static const struct regmap_config vcnl3020_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = VCNL_PS_MOD_ADJ, +}; + +static irqreturn_t vcnl3020_handle_irq_thread(int irq, void *p) +{ + struct iio_dev *indio_dev = p; + struct vcnl3020_data *data = iio_priv(indio_dev); + unsigned int isr; + int rc; + + rc = regmap_read(data->regmap, VCNL_ISR, &isr); + if (rc) { + dev_err(data->dev, "Error (%d) reading reg (0x%x)\n", + rc, VCNL_ISR); + return IRQ_HANDLED; + } + + if (!(isr & VCNL_ICR_THRES_EN)) + return IRQ_NONE; + + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 1, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + + rc = regmap_write(data->regmap, VCNL_ISR, isr & VCNL_ICR_THRES_EN); + if (rc) + dev_err(data->dev, "Error (%d) writing in reg (0x%x)\n", + rc, VCNL_ISR); + + return IRQ_HANDLED; +} + +static int vcnl3020_probe(struct i2c_client *client) +{ + struct vcnl3020_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + int rc; + + regmap = devm_regmap_init_i2c(client, &vcnl3020_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "regmap_init failed\n"); + return PTR_ERR(regmap); + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->regmap = regmap; + data->dev = &client->dev; + + rc = vcnl3020_init(data); + if (rc) + return rc; + + indio_dev->info = &vcnl3020_info; + indio_dev->channels = vcnl3020_channels; + indio_dev->num_channels = ARRAY_SIZE(vcnl3020_channels); + indio_dev->name = "vcnl3020"; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (client->irq) { + rc = devm_request_threaded_irq(&client->dev, client->irq, + NULL, vcnl3020_handle_irq_thread, + IRQF_ONESHOT, indio_dev->name, + indio_dev); + if (rc) + return rc; + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct of_device_id vcnl3020_of_match[] = { + { + .compatible = "vishay,vcnl3020", + }, + { } +}; +MODULE_DEVICE_TABLE(of, vcnl3020_of_match); + +static struct i2c_driver vcnl3020_driver = { + .driver = { + .name = "vcnl3020", + .of_match_table = vcnl3020_of_match, + }, + .probe = vcnl3020_probe, +}; +module_i2c_driver(vcnl3020_driver); + +MODULE_AUTHOR("Ivan Mikhaylov "); +MODULE_DESCRIPTION("Vishay VCNL3020 proximity sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/proximity/vl53l0x-i2c.c b/drivers/iio/proximity/vl53l0x-i2c.c new file mode 100644 index 000000000..9fe14ceb8 --- /dev/null +++ b/drivers/iio/proximity/vl53l0x-i2c.c @@ -0,0 +1,416 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Support for ST VL53L0X FlightSense ToF Ranging Sensor on a i2c bus. + * + * Copyright (C) 2016 STMicroelectronics Imaging Division. + * Copyright (C) 2018 Song Qiang + * Copyright (C) 2020 Ivan Drobyshevskyi + * + * Datasheet available at + * + * + * Default 7-bit i2c slave address 0x29. + * + * TODO: FIFO buffer, continuous mode, range selection, sensor ID check. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define VL_REG_SYSRANGE_START 0x00 + +#define VL_REG_SYSRANGE_MODE_MASK GENMASK(3, 0) +#define VL_REG_SYSRANGE_MODE_SINGLESHOT 0x00 +#define VL_REG_SYSRANGE_MODE_START_STOP BIT(0) +#define VL_REG_SYSRANGE_MODE_BACKTOBACK BIT(1) +#define VL_REG_SYSRANGE_MODE_TIMED BIT(2) +#define VL_REG_SYSRANGE_MODE_HISTOGRAM BIT(3) + +#define VL_REG_SYSTEM_INTERRUPT_CONFIG_GPIO 0x0A +#define VL_REG_SYSTEM_INTERRUPT_GPIO_NEW_SAMPLE_READY BIT(2) + +#define VL_REG_SYSTEM_INTERRUPT_CLEAR 0x0B + +#define VL_REG_RESULT_INT_STATUS 0x13 +#define VL_REG_RESULT_RANGE_STATUS 0x14 +#define VL_REG_IDENTIFICATION_MODEL_ID 0xC0 +#define VL_REG_RESULT_RANGE_STATUS_COMPLETE BIT(0) + +#define VL53L0X_MODEL_ID_VAL 0xEE +#define VL53L0X_CONTINUOUS_MODE 0x02 +#define VL53L0X_SINGLE_MODE 0x01 + +struct vl53l0x_data { + struct i2c_client *client; + struct completion completion; + struct regulator *vdd_supply; + struct gpio_desc *reset_gpio; + struct iio_trigger *trig; +}; + +static int vl53l0x_clear_irq(struct vl53l0x_data *data) +{ int ret; + + ret = i2c_smbus_write_byte_data(data->client, + VL_REG_SYSTEM_INTERRUPT_CLEAR, 1); + if (ret < 0) { + dev_err(&data->client->dev, "failed to clear irq: %d\n", ret); + return -EINVAL; + } + + return 0; +} + +static irqreturn_t vl53l0x_trigger_handler(int irq, void *priv) +{ + struct iio_poll_func *pf = priv; + struct iio_dev *indio_dev = pf->indio_dev; + struct vl53l0x_data *data = iio_priv(indio_dev); + u8 buffer[12]; + int ret; + struct { + u16 chan; + aligned_s64 timestamp; + } scan = { }; + + ret = i2c_smbus_read_i2c_block_data(data->client, + VL_REG_RESULT_RANGE_STATUS, + sizeof(buffer), buffer); + if (ret != 12) + goto done; + + scan.chan = get_unaligned_be16(&buffer[10]); + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); + +done: + iio_trigger_notify_done(indio_dev->trig); + vl53l0x_clear_irq(data); + + return IRQ_HANDLED; +} + +static irqreturn_t vl53l0x_threaded_irq(int irq, void *priv) +{ + struct iio_dev *indio_dev = priv; + struct vl53l0x_data *data = iio_priv(indio_dev); + + if (iio_buffer_enabled(indio_dev)) + iio_trigger_poll_nested(indio_dev->trig); + else + complete(&data->completion); + + return IRQ_HANDLED; +} + +static int vl53l0x_configure_irq(struct i2c_client *client, + struct iio_dev *indio_dev) +{ + int irq_flags = irq_get_trigger_type(client->irq); + struct vl53l0x_data *data = iio_priv(indio_dev); + int ret; + + if (!irq_flags) + irq_flags = IRQF_TRIGGER_FALLING; + + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, vl53l0x_threaded_irq, + irq_flags | IRQF_ONESHOT, indio_dev->name, indio_dev); + if (ret) + return ret; + + ret = i2c_smbus_write_byte_data(data->client, + VL_REG_SYSTEM_INTERRUPT_CONFIG_GPIO, + VL_REG_SYSTEM_INTERRUPT_GPIO_NEW_SAMPLE_READY); + if (ret < 0) + dev_err(&client->dev, "failed to configure IRQ: %d\n", ret); + + return ret; +} + +static int vl53l0x_read_proximity(struct vl53l0x_data *data, + const struct iio_chan_spec *chan, + int *val) +{ + struct i2c_client *client = data->client; + u16 tries = 20; + u8 buffer[12]; + int ret; + unsigned long time_left; + + ret = i2c_smbus_write_byte_data(client, VL_REG_SYSRANGE_START, 1); + if (ret < 0) + return ret; + + if (data->client->irq) { + reinit_completion(&data->completion); + + time_left = wait_for_completion_timeout(&data->completion, HZ/10); + if (time_left == 0) + return -ETIMEDOUT; + + ret = vl53l0x_clear_irq(data); + if (ret < 0) + return ret; + } else { + do { + ret = i2c_smbus_read_byte_data(client, + VL_REG_RESULT_RANGE_STATUS); + if (ret < 0) + return ret; + + if (ret & VL_REG_RESULT_RANGE_STATUS_COMPLETE) + break; + + usleep_range(1000, 5000); + } while (--tries); + if (!tries) + return -ETIMEDOUT; + } + + ret = i2c_smbus_read_i2c_block_data(client, VL_REG_RESULT_RANGE_STATUS, + 12, buffer); + if (ret < 0) + return ret; + else if (ret != 12) + return -EREMOTEIO; + + /* Values should be between 30~1200 in millimeters. */ + *val = get_unaligned_be16(&buffer[10]); + + return 0; +} + +static const struct iio_chan_spec vl53l0x_channels[] = { + { + .type = IIO_DISTANCE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 12, + .storagebits = 16, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static int vl53l0x_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct vl53l0x_data *data = iio_priv(indio_dev); + int ret; + + if (chan->type != IIO_DISTANCE) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = vl53l0x_read_proximity(data, chan, val); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = 1000; + + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int vl53l0x_validate_trigger(struct iio_dev *indio_dev, struct iio_trigger *trig) +{ + struct vl53l0x_data *data = iio_priv(indio_dev); + + return data->trig == trig ? 0 : -EINVAL; +} + +static const struct iio_info vl53l0x_info = { + .read_raw = vl53l0x_read_raw, + .validate_trigger = vl53l0x_validate_trigger, +}; + +static void vl53l0x_power_off(void *_data) +{ + struct vl53l0x_data *data = _data; + + gpiod_set_value_cansleep(data->reset_gpio, 1); + + regulator_disable(data->vdd_supply); +} + +static int vl53l0x_power_on(struct vl53l0x_data *data) +{ + int ret; + + ret = regulator_enable(data->vdd_supply); + if (ret) + return ret; + + gpiod_set_value_cansleep(data->reset_gpio, 0); + + usleep_range(3200, 5000); + + return 0; +} + +static int vl53l0x_buffer_postenable(struct iio_dev *indio_dev) +{ + struct vl53l0x_data *data = iio_priv(indio_dev); + + return i2c_smbus_write_byte_data(data->client, VL_REG_SYSRANGE_START, + VL53L0X_CONTINUOUS_MODE); +} + +static int vl53l0x_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct vl53l0x_data *data = iio_priv(indio_dev); + int ret; + + ret = i2c_smbus_write_byte_data(data->client, VL_REG_SYSRANGE_START, + VL53L0X_SINGLE_MODE); + if (ret < 0) + return ret; + + /* Let the ongoing reading finish */ + reinit_completion(&data->completion); + wait_for_completion_timeout(&data->completion, HZ / 10); + + return vl53l0x_clear_irq(data); +} + +static const struct iio_buffer_setup_ops iio_triggered_buffer_setup_ops = { + .postenable = &vl53l0x_buffer_postenable, + .postdisable = &vl53l0x_buffer_postdisable, +}; + +static const struct iio_trigger_ops vl53l0x_trigger_ops = { + .validate_device = iio_trigger_validate_own_device, +}; + +static int vl53l0x_probe(struct i2c_client *client) +{ + struct vl53l0x_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + i2c_set_clientdata(client, indio_dev); + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_I2C_BLOCK | + I2C_FUNC_SMBUS_BYTE_DATA)) + return -EOPNOTSUPP; + + ret = i2c_smbus_read_byte_data(data->client, VL_REG_IDENTIFICATION_MODEL_ID); + if (ret < 0) + return -EINVAL; + + if (ret != VL53L0X_MODEL_ID_VAL) + dev_info(&client->dev, "Unknown model id: 0x%x", ret); + + data->vdd_supply = devm_regulator_get(&client->dev, "vdd"); + if (IS_ERR(data->vdd_supply)) + return dev_err_probe(&client->dev, PTR_ERR(data->vdd_supply), + "Unable to get VDD regulator\n"); + + data->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(data->reset_gpio)) + return dev_err_probe(&client->dev, PTR_ERR(data->reset_gpio), + "Cannot get reset GPIO\n"); + + ret = vl53l0x_power_on(data); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to power on the chip\n"); + + ret = devm_add_action_or_reset(&client->dev, vl53l0x_power_off, data); + if (ret) + return ret; + + indio_dev->name = "vl53l0x"; + indio_dev->info = &vl53l0x_info; + indio_dev->channels = vl53l0x_channels; + indio_dev->num_channels = ARRAY_SIZE(vl53l0x_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + /* usage of interrupt is optional */ + if (client->irq) { + init_completion(&data->completion); + + data->trig = devm_iio_trigger_alloc(&client->dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->trig) + return -ENOMEM; + + data->trig->ops = &vl53l0x_trigger_ops; + iio_trigger_set_drvdata(data->trig, indio_dev); + ret = devm_iio_trigger_register(&client->dev, data->trig); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(data->trig); + + ret = vl53l0x_configure_irq(client, indio_dev); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(&client->dev, + indio_dev, + NULL, + &vl53l0x_trigger_handler, + &iio_triggered_buffer_setup_ops); + if (ret) + return ret; + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id vl53l0x_id[] = { + { .name = "vl53l0x" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, vl53l0x_id); + +static const struct of_device_id st_vl53l0x_dt_match[] = { + { .compatible = "st,vl53l0x", }, + { } +}; +MODULE_DEVICE_TABLE(of, st_vl53l0x_dt_match); + +static struct i2c_driver vl53l0x_driver = { + .driver = { + .name = "vl53l0x-i2c", + .of_match_table = st_vl53l0x_dt_match, + }, + .probe = vl53l0x_probe, + .id_table = vl53l0x_id, +}; +module_i2c_driver(vl53l0x_driver); + +MODULE_AUTHOR("Song Qiang "); +MODULE_DESCRIPTION("ST vl53l0x ToF ranging sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/proximity/vl53l1x-i2c.c b/drivers/iio/proximity/vl53l1x-i2c.c new file mode 100644 index 000000000..bce238d1b --- /dev/null +++ b/drivers/iio/proximity/vl53l1x-i2c.c @@ -0,0 +1,756 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +/* + * Support for ST VL53L1X FlightSense ToF Ranging Sensor on a i2c bus. + * + * Copyright (C) 2026 Siratul Islam + * + * Datasheet available at + * + * + * Default 7-bit i2c slave address 0x29. + * + * The VL53L1X requires a firmware configuration blob to be loaded at boot. + * Register values for the default configuration are taken from + * ST's VL53L1X Ultra Lite Driver (STSW-IMG009). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#define VL53L1X_REG_SOFT_RESET 0x0000 +#define VL53L1X_REG_VHV_CONFIG__TIMEOUT_MACROP_LOOP_BOUND 0x0008 +#define VL53L1X_REG_VHV_CONFIG__INIT 0x000B +#define VL53L1X_REG_DEFAULT_CONFIG 0x002D +#define VL53L1X_REG_GPIO_HV_MUX__CTRL 0x0030 +#define VL53L1X_REG_GPIO__TIO_HV_STATUS 0x0031 +#define VL53L1X_REG_SYSTEM__INTERRUPT_CONFIG_GPIO 0x0046 +#define VL53L1X_REG_PHASECAL_CONFIG__TIMEOUT_MACROP 0x004B +#define VL53L1X_REG_RANGE_CONFIG__TIMEOUT_MACROP_A 0x005E +#define VL53L1X_REG_RANGE_CONFIG__VCSEL_PERIOD_A 0x0060 +#define VL53L1X_REG_RANGE_CONFIG__TIMEOUT_MACROP_B 0x0061 +#define VL53L1X_REG_RANGE_CONFIG__VCSEL_PERIOD_B 0x0063 +#define VL53L1X_REG_RANGE_CONFIG__VALID_PHASE_HIGH 0x0069 +#define VL53L1X_REG_SYSTEM__INTERMEASUREMENT_PERIOD 0x006C +#define VL53L1X_REG_SD_CONFIG__WOI_SD0 0x0078 +#define VL53L1X_REG_SD_CONFIG__WOI_SD1 0x0079 +#define VL53L1X_REG_SD_CONFIG__INITIAL_PHASE_SD0 0x007A +#define VL53L1X_REG_SD_CONFIG__INITIAL_PHASE_SD1 0x007B +#define VL53L1X_REG_SYSTEM__INTERRUPT_CLEAR 0x0086 +#define VL53L1X_REG_SYSTEM__MODE_START 0x0087 +#define VL53L1X_REG_RESULT__RANGE_STATUS 0x0089 +#define VL53L1X_REG_RESULT__FINAL_CROSSTALK_CORRECTED_RANGE_MM_SD0 0x0096 +#define VL53L1X_REG_RESULT__OSC_CALIBRATE_VAL 0x00DE +#define VL53L1X_REG_FIRMWARE__SYSTEM_STATUS 0x00E5 +#define VL53L1X_REG_IDENTIFICATION__MODEL_ID 0x010F + +#define VL53L1X_MODEL_ID_VAL 0xEACC + +#define VL53L1X_MODE_START_TIMED 0x40 +#define VL53L1X_MODE_START_STOP 0x00 + +#define VL53L1X_INT_NEW_SAMPLE_READY 0x02 + +#define VL53L1X_GPIO_HV_MUX_POLARITY BIT(4) + +#define VL53L1X_VHV_LOOP_BOUND_TWO 0x09 + +#define VL53L1X_RANGE_STATUS_MASK GENMASK(4, 0) +#define VL53L1X_RANGE_STATUS_VALID 9 + +#define VL53L1X_OSC_CALIBRATE_MASK GENMASK(9, 0) + +#define VL53L1X_FIRMWARE__SYSTEM_STATUS_BOOTED BIT(0) +#define VL53L1X_GPIO__TIO_HV_STATUS_DATA_READY BIT(0) + +/* Inter-measurement period uses PLL divider with 1.075 oscillator correction */ +static const struct u32_fract vl53l1x_osc_correction = { + .numerator = 1075, + .denominator = 1000, +}; + +enum vl53l1x_distance_mode { + VL53L1X_SHORT, + VL53L1X_LONG, +}; + +struct vl53l1x_data { + struct regmap *regmap; + struct completion completion; + struct reset_control *xshut_reset; + enum vl53l1x_distance_mode distance_mode; + u8 gpio_polarity; + int irq; +}; + +static const struct regmap_range vl53l1x_volatile_ranges[] = { + regmap_reg_range(VL53L1X_REG_GPIO__TIO_HV_STATUS, + VL53L1X_REG_GPIO__TIO_HV_STATUS), + regmap_reg_range(VL53L1X_REG_RESULT__RANGE_STATUS, + VL53L1X_REG_RESULT__RANGE_STATUS), + regmap_reg_range(VL53L1X_REG_RESULT__FINAL_CROSSTALK_CORRECTED_RANGE_MM_SD0, + VL53L1X_REG_RESULT__FINAL_CROSSTALK_CORRECTED_RANGE_MM_SD0 + 1), + regmap_reg_range(VL53L1X_REG_RESULT__OSC_CALIBRATE_VAL, + VL53L1X_REG_RESULT__OSC_CALIBRATE_VAL + 1), + regmap_reg_range(VL53L1X_REG_FIRMWARE__SYSTEM_STATUS, + VL53L1X_REG_FIRMWARE__SYSTEM_STATUS), +}; + +static const struct regmap_access_table vl53l1x_volatile_table = { + .yes_ranges = vl53l1x_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(vl53l1x_volatile_ranges), +}; + +static const struct regmap_range vl53l1x_write_only_ranges[] = { + regmap_reg_range(VL53L1X_REG_SOFT_RESET, VL53L1X_REG_SOFT_RESET), + regmap_reg_range(VL53L1X_REG_SYSTEM__INTERRUPT_CLEAR, + VL53L1X_REG_SYSTEM__MODE_START), +}; + +static const struct regmap_access_table vl53l1x_readable_table = { + .no_ranges = vl53l1x_write_only_ranges, + .n_no_ranges = ARRAY_SIZE(vl53l1x_write_only_ranges), +}; + +static const struct regmap_config vl53l1x_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + /* MODEL_ID is 16-bit. +1 covers the second byte at 0x0110 */ + .max_register = VL53L1X_REG_IDENTIFICATION__MODEL_ID + 1, + .cache_type = REGCACHE_MAPLE, + .volatile_table = &vl53l1x_volatile_table, + .rd_table = &vl53l1x_readable_table, +}; + +static int vl53l1x_read_u16(struct vl53l1x_data *data, u16 reg, u16 *val) +{ + __be16 buf; + int ret; + + ret = regmap_bulk_read(data->regmap, reg, &buf, sizeof(buf)); + if (ret) + return ret; + + *val = be16_to_cpu(buf); + return 0; +} + +static int vl53l1x_write_u16(struct vl53l1x_data *data, u16 reg, u16 val) +{ + __be16 buf = cpu_to_be16(val); + + return regmap_bulk_write(data->regmap, reg, &buf, sizeof(buf)); +} + +static int vl53l1x_write_u32(struct vl53l1x_data *data, u16 reg, u32 val) +{ + __be32 buf = cpu_to_be32(val); + + return regmap_bulk_write(data->regmap, reg, &buf, sizeof(buf)); +} + +static int vl53l1x_clear_irq(struct vl53l1x_data *data) +{ + return regmap_write(data->regmap, VL53L1X_REG_SYSTEM__INTERRUPT_CLEAR, 0x01); +} + +static int vl53l1x_start_ranging(struct vl53l1x_data *data) +{ + int ret; + + ret = vl53l1x_clear_irq(data); + if (ret) + return ret; + + return regmap_write(data->regmap, VL53L1X_REG_SYSTEM__MODE_START, + VL53L1X_MODE_START_TIMED); +} + +static int vl53l1x_stop_ranging(struct vl53l1x_data *data) +{ + return regmap_write(data->regmap, VL53L1X_REG_SYSTEM__MODE_START, + VL53L1X_MODE_START_STOP); +} + +static int vl53l1x_wait_data_ready(struct vl53l1x_data *data) +{ + unsigned int val; + + /* 1ms poll, 1s timeout covers max timing budgets (per ST Ultra Lite Driver) */ + return regmap_read_poll_timeout(data->regmap, + VL53L1X_REG_GPIO__TIO_HV_STATUS, val, + (val & VL53L1X_GPIO__TIO_HV_STATUS_DATA_READY) != data->gpio_polarity, + 1 * USEC_PER_MSEC, 1 * USEC_PER_SEC); +} + +/* + * Default configuration blob from ST's VL53L1X Ultra Lite Driver + * (STSW-IMG009). + */ +static const u8 vl53l1x_default_config[] = { + 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x02, 0x08, /* reg 0x2d..0x34 */ + 0x00, 0x08, 0x10, 0x01, 0x01, 0x00, 0x00, 0x00, /* reg 0x35..0x3c */ + 0x00, 0xFF, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, /* reg 0x3d..0x44 */ + 0x00, 0x20, 0x0B, 0x00, 0x00, 0x02, 0x0A, 0x21, /* reg 0x45..0x4c */ + 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0xC8, /* reg 0x4d..0x54 */ + 0x00, 0x00, 0x38, 0xFF, 0x01, 0x00, 0x08, 0x00, /* reg 0x55..0x5c */ + 0x00, 0x01, 0xCC, 0x0F, 0x01, 0xF1, 0x0D, 0x01, /* reg 0x5d..0x64 */ + 0x68, 0x00, 0x80, 0x08, 0xB8, 0x00, 0x00, 0x00, /* reg 0x65..0x6c */ + 0x00, 0x0F, 0x89, 0x00, 0x00, 0x00, 0x00, 0x00, /* reg 0x6d..0x74 */ + 0x00, 0x00, 0x01, 0x0F, 0x0D, 0x0E, 0x0E, 0x00, /* reg 0x75..0x7c */ + 0x00, 0x02, 0xC7, 0xFF, 0x9B, 0x00, 0x00, 0x00, /* reg 0x7d..0x84 */ + 0x01, 0x00, 0x00, /* reg 0x85..0x87 */ +}; + +static int vl53l1x_chip_init(struct vl53l1x_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + unsigned int val; + u16 model_id; + int ret; + + if (!data->xshut_reset) { + ret = regmap_write(data->regmap, VL53L1X_REG_SOFT_RESET, 0x00); + if (ret) + return ret; + fsleep(100); /* conservative reset pulse, no spec */ + + ret = regmap_write(data->regmap, VL53L1X_REG_SOFT_RESET, 0x01); + if (ret) + return ret; + fsleep(1000); /* conservative boot wait, no spec */ + } + + ret = regmap_read_poll_timeout(data->regmap, + VL53L1X_REG_FIRMWARE__SYSTEM_STATUS, + val, val & VL53L1X_FIRMWARE__SYSTEM_STATUS_BOOTED, + 1 * USEC_PER_MSEC, 100 * USEC_PER_MSEC); + if (ret) + return dev_err_probe(dev, ret, "firmware boot timeout\n"); + + ret = vl53l1x_read_u16(data, VL53L1X_REG_IDENTIFICATION__MODEL_ID, + &model_id); + if (ret) + return ret; + + if (model_id != VL53L1X_MODEL_ID_VAL) + dev_info(dev, "unknown model id: 0x%04x, continuing\n", model_id); + + ret = regmap_bulk_write(data->regmap, VL53L1X_REG_DEFAULT_CONFIG, + vl53l1x_default_config, + sizeof(vl53l1x_default_config)); + if (ret) + return ret; + + ret = regmap_read(data->regmap, VL53L1X_REG_GPIO_HV_MUX__CTRL, &val); + if (ret) + return ret; + data->gpio_polarity = !!(val & VL53L1X_GPIO_HV_MUX_POLARITY); + + /* Initial ranging cycle for VHV calibration */ + ret = vl53l1x_start_ranging(data); + if (ret) + return ret; + + ret = vl53l1x_wait_data_ready(data); + if (ret) + return ret; + + ret = vl53l1x_clear_irq(data); + if (ret) + return ret; + + ret = vl53l1x_stop_ranging(data); + if (ret) + return ret; + + ret = regmap_write(data->regmap, + VL53L1X_REG_VHV_CONFIG__TIMEOUT_MACROP_LOOP_BOUND, + VL53L1X_VHV_LOOP_BOUND_TWO); + if (ret) + return ret; + + return regmap_write(data->regmap, VL53L1X_REG_VHV_CONFIG__INIT, 0x00); +} + +static const struct reg_sequence vl53l1x_mode_short[] = { + { VL53L1X_REG_PHASECAL_CONFIG__TIMEOUT_MACROP, 0x14 }, + { VL53L1X_REG_RANGE_CONFIG__VCSEL_PERIOD_A, 0x07 }, + { VL53L1X_REG_RANGE_CONFIG__VCSEL_PERIOD_B, 0x05 }, + { VL53L1X_REG_RANGE_CONFIG__VALID_PHASE_HIGH, 0x38 }, + { VL53L1X_REG_SD_CONFIG__WOI_SD0, 0x07 }, + { VL53L1X_REG_SD_CONFIG__WOI_SD1, 0x05 }, + { VL53L1X_REG_SD_CONFIG__INITIAL_PHASE_SD0, 0x06 }, + { VL53L1X_REG_SD_CONFIG__INITIAL_PHASE_SD1, 0x06 }, +}; + +static const struct reg_sequence vl53l1x_mode_long[] = { + { VL53L1X_REG_PHASECAL_CONFIG__TIMEOUT_MACROP, 0x0A }, + { VL53L1X_REG_RANGE_CONFIG__VCSEL_PERIOD_A, 0x0F }, + { VL53L1X_REG_RANGE_CONFIG__VCSEL_PERIOD_B, 0x0D }, + { VL53L1X_REG_RANGE_CONFIG__VALID_PHASE_HIGH, 0xB8 }, + { VL53L1X_REG_SD_CONFIG__WOI_SD0, 0x0F }, + { VL53L1X_REG_SD_CONFIG__WOI_SD1, 0x0D }, + { VL53L1X_REG_SD_CONFIG__INITIAL_PHASE_SD0, 0x0E }, + { VL53L1X_REG_SD_CONFIG__INITIAL_PHASE_SD1, 0x0E }, +}; + +static const struct { + const struct reg_sequence *regs; + size_t num_regs; +} vl53l1x_mode_configs[] = { + [VL53L1X_SHORT] = { vl53l1x_mode_short, ARRAY_SIZE(vl53l1x_mode_short) }, + [VL53L1X_LONG] = { vl53l1x_mode_long, ARRAY_SIZE(vl53l1x_mode_long) }, +}; + +static int vl53l1x_set_distance_mode(struct vl53l1x_data *data, + enum vl53l1x_distance_mode mode) +{ + int ret; + + if (mode >= ARRAY_SIZE(vl53l1x_mode_configs)) + return -EINVAL; + + ret = regmap_multi_reg_write(data->regmap, + vl53l1x_mode_configs[mode].regs, + vl53l1x_mode_configs[mode].num_regs); + if (ret) + return ret; + + data->distance_mode = mode; + return 0; +} + +/* + * The timing budget controls how long the sensor spends collecting + * a single range measurement. Pre-computed TIMEOUT_MACROP register + * values from ST's VL53L1X Ultra Lite Driver. + */ +static int vl53l1x_set_timing_budget(struct vl53l1x_data *data, u16 budget_ms) +{ + u16 timeout_a, timeout_b; + int ret; + + switch (data->distance_mode) { + case VL53L1X_SHORT: + switch (budget_ms) { + case 15: + timeout_a = 0x001D; + timeout_b = 0x0027; + break; + case 20: + timeout_a = 0x0051; + timeout_b = 0x006E; + break; + case 33: + timeout_a = 0x00D6; + timeout_b = 0x006E; + break; + case 50: + timeout_a = 0x01AE; + timeout_b = 0x01E8; + break; + case 100: + timeout_a = 0x02E1; + timeout_b = 0x0388; + break; + case 200: + timeout_a = 0x03E1; + timeout_b = 0x0496; + break; + case 500: + timeout_a = 0x0591; + timeout_b = 0x05C1; + break; + default: + return -EINVAL; + } + break; + case VL53L1X_LONG: + switch (budget_ms) { + case 20: + timeout_a = 0x001E; + timeout_b = 0x0022; + break; + case 33: + timeout_a = 0x0060; + timeout_b = 0x006E; + break; + case 50: + timeout_a = 0x00AD; + timeout_b = 0x00C6; + break; + case 100: + timeout_a = 0x01CC; + timeout_b = 0x01EA; + break; + case 200: + timeout_a = 0x02D9; + timeout_b = 0x02F8; + break; + case 500: + timeout_a = 0x048F; + timeout_b = 0x04A4; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + ret = vl53l1x_write_u16(data, VL53L1X_REG_RANGE_CONFIG__TIMEOUT_MACROP_A, + timeout_a); + if (ret) + return ret; + + return vl53l1x_write_u16(data, VL53L1X_REG_RANGE_CONFIG__TIMEOUT_MACROP_B, + timeout_b); +} + +static int vl53l1x_set_inter_measurement_ms(struct vl53l1x_data *data, + u16 period_ms) +{ + u16 osc_calibrate_val; + u16 clock_pll; + u32 inter_meas; + int ret; + + ret = vl53l1x_read_u16(data, VL53L1X_REG_RESULT__OSC_CALIBRATE_VAL, + &osc_calibrate_val); + if (ret) + return ret; + + clock_pll = osc_calibrate_val & VL53L1X_OSC_CALIBRATE_MASK; + inter_meas = (clock_pll * period_ms * vl53l1x_osc_correction.numerator) / + vl53l1x_osc_correction.denominator; + + return vl53l1x_write_u32(data, + VL53L1X_REG_SYSTEM__INTERMEASUREMENT_PERIOD, + inter_meas); +} + +static int vl53l1x_read_proximity(struct vl53l1x_data *data, int *val) +{ + unsigned int range_status; + u16 distance; + int ret; + + if (data->irq) { + reinit_completion(&data->completion); + + ret = vl53l1x_clear_irq(data); + if (ret) + return ret; + + if (!wait_for_completion_timeout(&data->completion, HZ)) + return -ETIMEDOUT; + } else { + ret = vl53l1x_wait_data_ready(data); + if (ret) + return ret; + } + + ret = regmap_read(data->regmap, VL53L1X_REG_RESULT__RANGE_STATUS, + &range_status); + if (ret) + goto clear_irq; + + if (FIELD_GET(VL53L1X_RANGE_STATUS_MASK, range_status) != + VL53L1X_RANGE_STATUS_VALID) { + ret = -EIO; + goto clear_irq; + } + + ret = vl53l1x_read_u16(data, + VL53L1X_REG_RESULT__FINAL_CROSSTALK_CORRECTED_RANGE_MM_SD0, + &distance); + if (ret) + goto clear_irq; + + *val = distance; + +clear_irq: + vl53l1x_clear_irq(data); + return ret; +} + +static const struct iio_chan_spec vl53l1x_channels[] = { + { + .type = IIO_DISTANCE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static int vl53l1x_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct vl53l1x_data *data = iio_priv(indio_dev); + int ret; + + if (chan->type != IIO_DISTANCE) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = vl53l1x_read_proximity(data, val); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = 1000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static const struct iio_info vl53l1x_info = { + .read_raw = vl53l1x_read_raw, + .validate_trigger = iio_validate_own_trigger, +}; + +static irqreturn_t vl53l1x_trigger_handler(int irq, void *priv) +{ + struct iio_poll_func *pf = priv; + struct iio_dev *indio_dev = pf->indio_dev; + struct vl53l1x_data *data = iio_priv(indio_dev); + struct { + u16 distance; + aligned_s64 timestamp; + } scan = { }; + unsigned int range_status; + int ret; + + ret = regmap_read(data->regmap, VL53L1X_REG_RESULT__RANGE_STATUS, + &range_status); + if (ret) + goto notify_and_clear_irq; + if (FIELD_GET(VL53L1X_RANGE_STATUS_MASK, range_status) != + VL53L1X_RANGE_STATUS_VALID) + goto notify_and_clear_irq; + + ret = vl53l1x_read_u16(data, + VL53L1X_REG_RESULT__FINAL_CROSSTALK_CORRECTED_RANGE_MM_SD0, + &scan.distance); + if (ret) + goto notify_and_clear_irq; + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); + +notify_and_clear_irq: + iio_trigger_notify_done(indio_dev->trig); + vl53l1x_clear_irq(data); + + return IRQ_HANDLED; +} + +static irqreturn_t vl53l1x_irq_handler(int irq, void *priv) +{ + struct iio_dev *indio_dev = priv; + struct vl53l1x_data *data = iio_priv(indio_dev); + + if (iio_buffer_enabled(indio_dev)) + iio_trigger_poll(indio_dev->trig); + else + complete(&data->completion); + + return IRQ_HANDLED; +} + +static const struct iio_trigger_ops vl53l1x_trigger_ops = { + .validate_device = iio_trigger_validate_own_device, +}; + +static void vl53l1x_stop_ranging_action(void *priv) +{ + vl53l1x_stop_ranging(priv); +} + +static int vl53l1x_configure_irq(struct device *dev, int irq, + struct iio_dev *indio_dev) +{ + struct vl53l1x_data *data = iio_priv(indio_dev); + int ret; + + ret = devm_request_irq(dev, irq, vl53l1x_irq_handler, IRQF_NO_THREAD, + indio_dev->name, indio_dev); + if (ret) + return ret; + + ret = regmap_write(data->regmap, VL53L1X_REG_SYSTEM__INTERRUPT_CONFIG_GPIO, + VL53L1X_INT_NEW_SAMPLE_READY); + if (ret) + return dev_err_probe(dev, ret, "failed to configure IRQ\n"); + + return 0; +} + +static int vl53l1x_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct vl53l1x_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->irq = client->irq; + + data->regmap = devm_regmap_init_i2c(client, &vl53l1x_regmap_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), + "regmap initialization failed\n"); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable VDD regulator\n"); + + /* + * XSHUT held low puts the chip in hardware standby. All register + * state is lost on de-assert so this is functionally a reset. + */ + data->xshut_reset = devm_reset_control_get_optional_exclusive_deasserted(dev, NULL); + if (IS_ERR(data->xshut_reset)) + return dev_err_probe(dev, PTR_ERR(data->xshut_reset), + "Cannot get reset control\n"); + + /* + * 1.2 ms max boot duration. + * Datasheet Section 3.6 "Power up and boot sequence". + */ + fsleep(1200); + + ret = vl53l1x_chip_init(data); + if (ret) + return ret; + + ret = vl53l1x_set_distance_mode(data, VL53L1X_LONG); + if (ret) + return ret; + + /* 50 ms timing budget (per ST Ultra Lite Driver) */ + ret = vl53l1x_set_timing_budget(data, 50); + if (ret) + return ret; + + /* 50 ms inter-measurement period (per ST Ultra Lite Driver) */ + ret = vl53l1x_set_inter_measurement_ms(data, 50); + if (ret) + return ret; + + /* + * The hardware only supports "autonomous" continuous ranging mode. + * Start ranging here and leave it running for the lifetime of + * the device. Both direct reads and the buffer path rely on this. + */ + ret = vl53l1x_start_ranging(data); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, vl53l1x_stop_ranging_action, data); + if (ret) + return ret; + + indio_dev->name = "vl53l1x"; + indio_dev->info = &vl53l1x_info; + indio_dev->channels = vl53l1x_channels; + indio_dev->num_channels = ARRAY_SIZE(vl53l1x_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + if (client->irq) { + struct iio_trigger *trig; + + init_completion(&data->completion); + + trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, + iio_device_id(indio_dev)); + if (!trig) + return -ENOMEM; + + trig->ops = &vl53l1x_trigger_ops; + iio_trigger_set_drvdata(trig, indio_dev); + ret = devm_iio_trigger_register(dev, trig); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(trig); + + ret = vl53l1x_configure_irq(dev, client->irq, indio_dev); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + &vl53l1x_trigger_handler, + NULL); + if (ret) + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct i2c_device_id vl53l1x_id[] = { + { .name = "vl53l1x" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, vl53l1x_id); + +static const struct of_device_id st_vl53l1x_dt_match[] = { + { .compatible = "st,vl53l1x" }, + { } +}; +MODULE_DEVICE_TABLE(of, st_vl53l1x_dt_match); + +static struct i2c_driver vl53l1x_driver = { + .driver = { + .name = "vl53l1x-i2c", + .of_match_table = st_vl53l1x_dt_match, + }, + .probe = vl53l1x_probe, + .id_table = vl53l1x_id, +}; +module_i2c_driver(vl53l1x_driver); + +MODULE_AUTHOR("Siratul Islam "); +MODULE_DESCRIPTION("ST VL53L1X ToF ranging sensor driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/iio/resolver/Kconfig b/drivers/iio/resolver/Kconfig new file mode 100644 index 000000000..de2dee383 --- /dev/null +++ b/drivers/iio/resolver/Kconfig @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Resolver/Synchro drivers +# +menu "Resolver to digital converters" + +config AD2S90 + tristate "Analog Devices ad2s90 driver" + depends on SPI + help + Say yes here to build support for Analog Devices spi resolver + to digital converters, ad2s90, provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called ad2s90. + +config AD2S1200 + tristate "Analog Devices ad2s1200/ad2s1205 driver" + depends on SPI + depends on GPIOLIB || COMPILE_TEST + help + Say yes here to build support for Analog Devices spi resolver + to digital converters, ad2s1200 and ad2s1205, provides direct access + via sysfs. + + To compile this driver as a module, choose M here: the + module will be called ad2s1200. + +config AD2S1210 + tristate "Analog Devices ad2s1210 driver" + depends on SPI + depends on COMMON_CLK + depends on GPIOLIB || COMPILE_TEST + select REGMAP + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Analog Devices spi resolver + to digital converters, ad2s1210, provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called ad2s1210. + +endmenu diff --git a/drivers/iio/resolver/Makefile b/drivers/iio/resolver/Makefile new file mode 100644 index 000000000..7f6c876c3 --- /dev/null +++ b/drivers/iio/resolver/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for Resolver/Synchro drivers +# + +obj-$(CONFIG_AD2S90) += ad2s90.o +obj-$(CONFIG_AD2S1200) += ad2s1200.o +obj-$(CONFIG_AD2S1210) += ad2s1210.o diff --git a/drivers/iio/resolver/ad2s1200.c b/drivers/iio/resolver/ad2s1200.c new file mode 100644 index 000000000..27ecc434e --- /dev/null +++ b/drivers/iio/resolver/ad2s1200.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ad2s1200.c simple support for the ADI Resolver to Digital Converters: + * AD2S1200/1205 + * + * Copyright (c) 2018-2018 David Veenstra + * Copyright (c) 2010-2010 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +/* input clock on serial interface */ +#define AD2S1200_HZ 8192000 +/* clock period in nano second */ +#define AD2S1200_TSCLK (1000000000 / AD2S1200_HZ) + +/** + * struct ad2s1200_state - driver instance specific data. + * @lock: protects both the GPIO pins and the rx buffer. + * @sdev: spi device. + * @sample: GPIO pin SAMPLE. + * @rdvel: GPIO pin RDVEL. + * @rx: buffer for spi transfers. + */ +struct ad2s1200_state { + struct mutex lock; + struct spi_device *sdev; + struct gpio_desc *sample; + struct gpio_desc *rdvel; + __be16 rx __aligned(IIO_DMA_MINALIGN); +}; + +static int ad2s1200_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad2s1200_state *st = iio_priv(indio_dev); + int ret; + + switch (m) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL: + /* 2 * Pi / (2^12 - 1) ~= 0.001534355 */ + *val = 0; + *val2 = 1534355; + return IIO_VAL_INT_PLUS_NANO; + case IIO_ANGL_VEL: + /* 2 * Pi ~= 6.283185 */ + *val = 6; + *val2 = 283185; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_RAW: + mutex_lock(&st->lock); + gpiod_set_value(st->sample, 0); + + /* delay (6 * AD2S1200_TSCLK + 20) nano seconds */ + udelay(1); + gpiod_set_value(st->sample, 1); + gpiod_set_value(st->rdvel, !!(chan->type == IIO_ANGL)); + + ret = spi_read(st->sdev, &st->rx, 2); + if (ret < 0) { + mutex_unlock(&st->lock); + return ret; + } + + switch (chan->type) { + case IIO_ANGL: + *val = be16_to_cpup(&st->rx) >> 4; + break; + case IIO_ANGL_VEL: + *val = sign_extend32(be16_to_cpup(&st->rx) >> 4, 11); + break; + default: + mutex_unlock(&st->lock); + return -EINVAL; + } + + /* delay (2 * AD2S1200_TSCLK + 20) ns for sample pulse */ + udelay(1); + mutex_unlock(&st->lock); + + return IIO_VAL_INT; + default: + break; + } + + return -EINVAL; +} + +static const struct iio_chan_spec ad2s1200_channels[] = { + { + .type = IIO_ANGL, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + }, { + .type = IIO_ANGL_VEL, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + } +}; + +static const struct iio_info ad2s1200_info = { + .read_raw = ad2s1200_read_raw, +}; + +static int ad2s1200_probe(struct spi_device *spi) +{ + struct ad2s1200_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + spi_set_drvdata(spi, indio_dev); + st = iio_priv(indio_dev); + mutex_init(&st->lock); + st->sdev = spi; + + st->sample = devm_gpiod_get(&spi->dev, "adi,sample", GPIOD_OUT_LOW); + if (IS_ERR(st->sample)) + return dev_err_probe(&spi->dev, PTR_ERR(st->sample), + "Failed to claim SAMPLE gpio\n"); + + st->rdvel = devm_gpiod_get(&spi->dev, "adi,rdvel", GPIOD_OUT_LOW); + if (IS_ERR(st->rdvel)) + return dev_err_probe(&spi->dev, PTR_ERR(st->rdvel), + "Failed to claim RDVEL gpio\n"); + + indio_dev->info = &ad2s1200_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad2s1200_channels; + indio_dev->num_channels = ARRAY_SIZE(ad2s1200_channels); + indio_dev->name = spi_get_device_id(spi)->name; + + spi->max_speed_hz = AD2S1200_HZ; + spi->mode = SPI_MODE_3; + ret = spi_setup(spi); + + if (ret < 0) { + dev_err(&spi->dev, "spi_setup failed!\n"); + return ret; + } + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct of_device_id ad2s1200_of_match[] = { + { .compatible = "adi,ad2s1200", }, + { .compatible = "adi,ad2s1205", }, + { } +}; +MODULE_DEVICE_TABLE(of, ad2s1200_of_match); + +static const struct spi_device_id ad2s1200_id[] = { + { .name = "ad2s1200" }, + { .name = "ad2s1205" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad2s1200_id); + +static struct spi_driver ad2s1200_driver = { + .driver = { + .name = "ad2s1200", + .of_match_table = ad2s1200_of_match, + }, + .probe = ad2s1200_probe, + .id_table = ad2s1200_id, +}; +module_spi_driver(ad2s1200_driver); + +MODULE_AUTHOR("David Veenstra "); +MODULE_AUTHOR("Graff Yang "); +MODULE_DESCRIPTION("Analog Devices AD2S1200/1205 Resolver to Digital SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/resolver/ad2s1210.c b/drivers/iio/resolver/ad2s1210.c new file mode 100644 index 000000000..3b5ec21e3 --- /dev/null +++ b/drivers/iio/resolver/ad2s1210.c @@ -0,0 +1,1621 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ad2s1210.c support for the ADI Resolver to Digital Converters: AD2S1210 + * + * Copyright (c) 2010-2010 Analog Devices Inc. + * Copyright (c) 2023 BayLibre, SAS + * + * Device register to IIO ABI mapping: + * + * Register | Addr | IIO ABI (sysfs) + * ----------------------------|------|------------------------------------------- + * DOS Overrange Threshold | 0x89 | events/in_altvoltage0_thresh_rising_value + * DOS Mismatch Threshold | 0x8A | events/in_altvoltage0_mag_rising_value + * DOS Reset Maximum Threshold | 0x8B | events/in_altvoltage0_mag_rising_reset_max + * DOS Reset Minimum Threshold | 0x8C | events/in_altvoltage0_mag_rising_reset_min + * LOT High Threshold | 0x8D | events/in_angl1_thresh_rising_value + * LOT Low Threshold [1] | 0x8E | events/in_angl1_thresh_rising_hysteresis + * Excitation Frequency | 0x91 | out_altvoltage0_frequency + * Control | 0x92 | *as bit fields* + * Phase lock range | D5 | events/in_phase0_mag_rising_value + * Hysteresis | D4 | in_angl0_hysteresis + * Encoder resolution | D3:2 | *not implemented* + * Resolution | D1:0 | *device tree: assigned-resolution-bits* + * Soft Reset | 0xF0 | [2] + * Fault | 0xFF | *not implemented* + * + * [1]: The value written to the LOT low register is high value minus the + * hysteresis. + * [2]: Soft reset is performed when `out_altvoltage0_frequency` is written. + * + * Fault to event mapping: + * + * Fault | | Channel | Type | Direction + * ----------------------------------------|----|--------------------------------- + * Sine/cosine inputs clipped [3] | D7 | altvoltage1 | mag | either + * Sine/cosine inputs below LOS | D6 | altvoltage0 | thresh | falling + * Sine/cosine inputs exceed DOS overrange | D5 | altvoltage0 | thresh | rising + * Sine/cosine inputs exceed DOS mismatch | D4 | altvoltage0 | mag | rising + * Tracking error exceeds LOT | D3 | angl1 | thresh | rising + * Velocity exceeds maximum tracking rate | D2 | anglvel0 | mag | rising + * Phase error exceeds phase lock range | D1 | phase0 | mag | rising + * Configuration parity error | D0 | *writes to kernel log* + * + * [3]: The chip does not differentiate between fault on sine vs. cosine so + * there will also be an event on the altvoltage2 channel. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* control register flags */ +#define AD2S1210_ADDRESS_DATA BIT(7) +#define AD2S1210_PHASE_LOCK_RANGE_44 BIT(5) +#define AD2S1210_ENABLE_HYSTERESIS BIT(4) +#define AD2S1210_SET_ENRES GENMASK(3, 2) +#define AD2S1210_SET_RES GENMASK(1, 0) + +/* fault register flags */ +#define AD2S1210_FAULT_CLIP BIT(7) +#define AD2S1210_FAULT_LOS BIT(6) +#define AD2S1210_FAULT_DOS_OVR BIT(5) +#define AD2S1210_FAULT_DOS_MIS BIT(4) +#define AD2S1210_FAULT_LOT BIT(3) +#define AD2S1210_FAULT_VELOCITY BIT(2) +#define AD2S1210_FAULT_PHASE BIT(1) +#define AD2S1210_FAULT_CONFIG_PARITY BIT(0) + +#define AD2S1210_REG_POSITION_MSB 0x80 +#define AD2S1210_REG_POSITION_LSB 0x81 +#define AD2S1210_REG_VELOCITY_MSB 0x82 +#define AD2S1210_REG_VELOCITY_LSB 0x83 +#define AD2S1210_REG_LOS_THRD 0x88 +#define AD2S1210_REG_DOS_OVR_THRD 0x89 +#define AD2S1210_REG_DOS_MIS_THRD 0x8A +#define AD2S1210_REG_DOS_RST_MAX_THRD 0x8B +#define AD2S1210_REG_DOS_RST_MIN_THRD 0x8C +#define AD2S1210_REG_LOT_HIGH_THRD 0x8D +#define AD2S1210_REG_LOT_LOW_THRD 0x8E +#define AD2S1210_REG_EXCIT_FREQ 0x91 +#define AD2S1210_REG_CONTROL 0x92 +#define AD2S1210_REG_SOFT_RESET 0xF0 +#define AD2S1210_REG_FAULT 0xFF + +#define AD2S1210_MIN_CLKIN 6144000 +#define AD2S1210_MAX_CLKIN 10240000 +#define AD2S1210_MIN_EXCIT 2000 +#define AD2S1210_DEF_EXCIT 10000 +#define AD2S1210_MAX_EXCIT 20000 +#define AD2S1210_MIN_FCW 0x4 +#define AD2S1210_MAX_FCW 0x50 + +/* 44 degrees ~= 0.767945 radians */ +#define PHASE_44_DEG_TO_RAD_INT 0 +#define PHASE_44_DEG_TO_RAD_MICRO 767945 +/* 360 degrees ~= 6.283185 radians */ +#define PHASE_360_DEG_TO_RAD_INT 6 +#define PHASE_360_DEG_TO_RAD_MICRO 283185 + +/* Threshold voltage registers have 1 LSB == 38 mV */ +#define THRESHOLD_MILLIVOLT_PER_LSB 38 +/* max voltage for threshold registers is 0x7F * 38 mV */ +#define THRESHOLD_RANGE_STR "[0 38 4826]" + +#define FAULT_ONESHOT(bit, new, old) (new & bit && !(old & bit)) + +enum ad2s1210_mode { + MOD_POS = 0b00, + MOD_VEL = 0b01, + MOD_RESERVED = 0b10, + MOD_CONFIG = 0b11, +}; + +enum ad2s1210_resolution { + AD2S1210_RES_10 = 0b00, + AD2S1210_RES_12 = 0b01, + AD2S1210_RES_14 = 0b10, + AD2S1210_RES_16 = 0b11, +}; + +struct ad2s1210_state { + struct mutex lock; + struct spi_device *sdev; + /** GPIO pin connected to SAMPLE line. */ + struct gpio_desc *sample_gpio; + /** GPIO pins connected to A0 and A1 lines (optional). */ + struct gpio_descs *mode_gpios; + /** Used to access config registers. */ + struct regmap *regmap; + /** The external oscillator frequency in Hz. */ + unsigned long clkin_hz; + /** Available raw hysteresis values based on resolution. */ + int hysteresis_available[2]; + /* adi,fixed-mode property - only valid when mode_gpios == NULL. */ + enum ad2s1210_mode fixed_mode; + /** The selected resolution */ + enum ad2s1210_resolution resolution; + /** Copy of fault register from the previous read. */ + u8 prev_fault_flags; + /** For reading raw sample value via SPI. */ + struct { + __be16 raw; + u8 fault; + } sample __aligned(IIO_DMA_MINALIGN); + /** Scan buffer */ + struct { + __be16 chan[2]; + /* Ensure timestamp is naturally aligned. */ + aligned_s64 timestamp; + } scan; + /** SPI transmit buffer. */ + u8 rx[2]; + /** SPI receive buffer. */ + u8 tx[2]; +}; + +static int ad2s1210_set_mode(struct ad2s1210_state *st, enum ad2s1210_mode mode) +{ + struct gpio_descs *gpios = st->mode_gpios; + DECLARE_BITMAP(bitmap, 2) = { }; + + if (!gpios) + return mode == st->fixed_mode ? 0 : -EOPNOTSUPP; + + bitmap_write(bitmap, mode, 0, 2); + + return gpiod_multi_set_value_cansleep(gpios, bitmap); +} + +/* + * Writes the given data to the given register address. + * + * If the mode is configurable, the device will first be placed in + * configuration mode. + */ +static int ad2s1210_regmap_reg_write(void *context, unsigned int reg, + unsigned int val) +{ + struct ad2s1210_state *st = context; + struct spi_transfer xfers[] = { + { + .len = 1, + .rx_buf = &st->rx[0], + .tx_buf = &st->tx[0], + .cs_change = 1, + }, { + .len = 1, + .rx_buf = &st->rx[1], + .tx_buf = &st->tx[1], + }, + }; + int ret; + + /* values can only be 7 bits, the MSB indicates an address */ + if (val & ~0x7F) + return -EINVAL; + + st->tx[0] = reg; + st->tx[1] = val; + + ret = ad2s1210_set_mode(st, MOD_CONFIG); + if (ret < 0) + return ret; + + ret = spi_sync_transfer(st->sdev, xfers, ARRAY_SIZE(xfers)); + if (ret < 0) + return ret; + + /* soft reset also clears the fault register */ + if (reg == AD2S1210_REG_SOFT_RESET) + st->prev_fault_flags = 0; + + return 0; +} + +/* + * Reads value from one of the registers. + * + * If the mode is configurable, the device will first be placed in + * configuration mode. + */ +static int ad2s1210_regmap_reg_read(void *context, unsigned int reg, + unsigned int *val) +{ + struct ad2s1210_state *st = context; + struct spi_transfer xfers[] = { + { + .len = 1, + .rx_buf = &st->rx[0], + .tx_buf = &st->tx[0], + .cs_change = 1, + }, { + .len = 1, + .rx_buf = &st->rx[1], + .tx_buf = &st->tx[1], + }, + }; + int ret; + + ret = ad2s1210_set_mode(st, MOD_CONFIG); + if (ret < 0) + return ret; + + st->tx[0] = reg; + /* + * Must be valid register address here otherwise this could write data. + * It doesn't matter which one as long as reading doesn't have side- + * effects. + */ + st->tx[1] = AD2S1210_REG_CONTROL; + + ret = spi_sync_transfer(st->sdev, xfers, ARRAY_SIZE(xfers)); + if (ret < 0) + return ret; + + /* reading the fault register also clears it */ + if (reg == AD2S1210_REG_FAULT) + st->prev_fault_flags = 0; + + /* + * If the D7 bit is set on any read/write register, it indicates a + * parity error. The fault register is read-only and the D7 bit means + * something else there. + */ + if ((reg > AD2S1210_REG_VELOCITY_LSB && reg != AD2S1210_REG_FAULT) + && st->rx[1] & AD2S1210_ADDRESS_DATA) + return -EBADMSG; + + *val = st->rx[1]; + + return 0; +} + +/* + * Toggles the SAMPLE line on the AD2S1210 to latch in the current position, + * velocity, and faults. + * + * Must be called with lock held. + */ +static void ad2s1210_toggle_sample_line(struct ad2s1210_state *st) +{ + /* + * Datasheet specifies minimum hold time t16 = 2 * tck + 20 ns. So the + * longest time needed is when CLKIN is 6.144 MHz, in which case t16 + * ~= 350 ns. The same delay is also needed before re-asserting the + * SAMPLE line. + */ + gpiod_set_value(st->sample_gpio, 1); + ndelay(350); + gpiod_set_value(st->sample_gpio, 0); + ndelay(350); +} + +/* + * Sets the excitation frequency and performs software reset. + * + * Must be called with lock held. + */ +static int ad2s1210_reinit_excitation_frequency(struct ad2s1210_state *st, + u16 fexcit) +{ + /* Map resolution to settle time in milliseconds. */ + static const int track_time_ms[] = { 10, 20, 25, 60 }; + unsigned int ignored; + int ret; + u8 fcw; + + fcw = fexcit * (1 << 15) / st->clkin_hz; + if (fcw < AD2S1210_MIN_FCW || fcw > AD2S1210_MAX_FCW) + return -ERANGE; + + ret = regmap_write(st->regmap, AD2S1210_REG_EXCIT_FREQ, fcw); + if (ret < 0) + return ret; + + /* + * Software reset reinitializes the excitation frequency output. + * It does not reset any of the configuration registers. + */ + ret = regmap_write(st->regmap, AD2S1210_REG_SOFT_RESET, 0); + if (ret < 0) + return ret; + + /* + * Soft reset always triggers some faults due the change in the output + * signal so clear the faults too. We need to delay for some time + * (what datasheet calls t[track]) to allow things to settle before + * clearing the faults. + */ + msleep(track_time_ms[st->resolution] * 8192000 / st->clkin_hz); + + /* Reading the fault register clears the faults. */ + ret = regmap_read(st->regmap, AD2S1210_REG_FAULT, &ignored); + if (ret < 0) + return ret; + + /* Have to toggle sample line to get fault output pins to reset. */ + ad2s1210_toggle_sample_line(st); + + return 0; +} + +static void ad2s1210_push_events(struct iio_dev *indio_dev, + u8 flags, s64 timestamp) +{ + struct ad2s1210_state *st = iio_priv(indio_dev); + + /* Sine/cosine inputs clipped */ + if (FAULT_ONESHOT(AD2S1210_FAULT_CLIP, flags, st->prev_fault_flags)) { + /* + * The chip does not differentiate between fault on sine vs. + * cosine channel so we just send an event on both channels. + */ + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_ALTVOLTAGE, 1, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_EITHER), + timestamp); + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_ALTVOLTAGE, 2, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_EITHER), + timestamp); + } + + /* Sine/cosine inputs below LOS threshold */ + if (FAULT_ONESHOT(AD2S1210_FAULT_LOS, flags, st->prev_fault_flags)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_ALTVOLTAGE, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + + /* Sine/cosine inputs exceed DOS overrange threshold */ + if (FAULT_ONESHOT(AD2S1210_FAULT_DOS_OVR, flags, st->prev_fault_flags)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_ALTVOLTAGE, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + + /* Sine/cosine inputs exceed DOS mismatch threshold */ + if (FAULT_ONESHOT(AD2S1210_FAULT_DOS_MIS, flags, st->prev_fault_flags)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_ALTVOLTAGE, 0, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_RISING), + timestamp); + + /* Tracking error exceeds LOT threshold */ + if (FAULT_ONESHOT(AD2S1210_FAULT_LOT, flags, st->prev_fault_flags)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_ANGL, 1, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + + /* Velocity exceeds maximum tracking rate */ + if (FAULT_ONESHOT(AD2S1210_FAULT_VELOCITY, flags, st->prev_fault_flags)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_ANGL_VEL, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + + /* Phase error exceeds phase lock range */ + if (FAULT_ONESHOT(AD2S1210_FAULT_PHASE, flags, st->prev_fault_flags)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PHASE, 0, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_RISING), + timestamp); + + /* Configuration parity error */ + if (FAULT_ONESHOT(AD2S1210_FAULT_CONFIG_PARITY, flags, + st->prev_fault_flags)) + /* + * Userspace should also get notified of this via error return + * when trying to write to any attribute that writes a register. + */ + dev_err_ratelimited(&indio_dev->dev, + "Configuration parity error\n"); + + st->prev_fault_flags = flags; +} + +static int ad2s1210_single_conversion(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct ad2s1210_state *st = iio_priv(indio_dev); + s64 timestamp; + int ret; + + guard(mutex)(&st->lock); + + ad2s1210_toggle_sample_line(st); + timestamp = iio_get_time_ns(indio_dev); + + if (st->fixed_mode == MOD_CONFIG) { + unsigned int reg_val; + + switch (chan->type) { + case IIO_ANGL: + ret = regmap_bulk_read(st->regmap, + AD2S1210_REG_POSITION_MSB, + &st->sample.raw, 2); + if (ret < 0) + return ret; + + break; + case IIO_ANGL_VEL: + ret = regmap_bulk_read(st->regmap, + AD2S1210_REG_VELOCITY_MSB, + &st->sample.raw, 2); + if (ret < 0) + return ret; + + break; + default: + return -EINVAL; + } + + ret = regmap_read(st->regmap, AD2S1210_REG_FAULT, ®_val); + if (ret < 0) + return ret; + + st->sample.fault = reg_val; + } else { + switch (chan->type) { + case IIO_ANGL: + ret = ad2s1210_set_mode(st, MOD_POS); + break; + case IIO_ANGL_VEL: + ret = ad2s1210_set_mode(st, MOD_VEL); + break; + default: + return -EINVAL; + } + if (ret < 0) + return ret; + + ret = spi_read(st->sdev, &st->sample, 3); + if (ret < 0) + return ret; + } + + switch (chan->type) { + case IIO_ANGL: + *val = be16_to_cpu(st->sample.raw); + ret = IIO_VAL_INT; + break; + case IIO_ANGL_VEL: + *val = (s16)be16_to_cpu(st->sample.raw); + ret = IIO_VAL_INT; + break; + default: + return -EINVAL; + } + + ad2s1210_push_events(indio_dev, st->sample.fault, timestamp); + + return ret; +} + +static int ad2s1210_get_hysteresis(struct ad2s1210_state *st, int *val) +{ + int ret; + + guard(mutex)(&st->lock); + ret = regmap_test_bits(st->regmap, AD2S1210_REG_CONTROL, + AD2S1210_ENABLE_HYSTERESIS); + if (ret < 0) + return ret; + + *val = ret << (2 * (AD2S1210_RES_16 - st->resolution)); + return IIO_VAL_INT; +} + +static int ad2s1210_set_hysteresis(struct ad2s1210_state *st, int val) +{ + guard(mutex)(&st->lock); + return regmap_update_bits(st->regmap, AD2S1210_REG_CONTROL, + AD2S1210_ENABLE_HYSTERESIS, + val ? AD2S1210_ENABLE_HYSTERESIS : 0); +} + +static int ad2s1210_get_phase_lock_range(struct ad2s1210_state *st, + int *val, int *val2) +{ + int ret; + + guard(mutex)(&st->lock); + ret = regmap_test_bits(st->regmap, AD2S1210_REG_CONTROL, + AD2S1210_PHASE_LOCK_RANGE_44); + if (ret < 0) + return ret; + + if (ret) { + /* 44 degrees as radians */ + *val = PHASE_44_DEG_TO_RAD_INT; + *val2 = PHASE_44_DEG_TO_RAD_MICRO; + } else { + /* 360 degrees as radians */ + *val = PHASE_360_DEG_TO_RAD_INT; + *val2 = PHASE_360_DEG_TO_RAD_MICRO; + } + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int ad2s1210_set_phase_lock_range(struct ad2s1210_state *st, + int val, int val2) +{ + int deg; + + /* convert radians to degrees - only two allowable values */ + if (val == PHASE_44_DEG_TO_RAD_INT && val2 == PHASE_44_DEG_TO_RAD_MICRO) + deg = 44; + else if (val == PHASE_360_DEG_TO_RAD_INT && + val2 == PHASE_360_DEG_TO_RAD_MICRO) + deg = 360; + else + return -EINVAL; + + guard(mutex)(&st->lock); + return regmap_update_bits(st->regmap, AD2S1210_REG_CONTROL, + AD2S1210_PHASE_LOCK_RANGE_44, + deg == 44 ? AD2S1210_PHASE_LOCK_RANGE_44 : 0); +} + +/* map resolution to microradians/LSB for LOT registers */ +static const int ad2s1210_lot_threshold_urad_per_lsb[] = { + 6184, /* 10-bit: ~0.35 deg/LSB, 45 deg max */ + 2473, /* 12-bit: ~0.14 deg/LSB, 18 deg max */ + 1237, /* 14-bit: ~0.07 deg/LSB, 9 deg max */ + 1237, /* 16-bit: same as 14-bit */ +}; + +static int ad2s1210_get_voltage_threshold(struct ad2s1210_state *st, + unsigned int reg, int *val) +{ + unsigned int reg_val; + int ret; + + guard(mutex)(&st->lock); + ret = regmap_read(st->regmap, reg, ®_val); + if (ret < 0) + return ret; + + *val = reg_val * THRESHOLD_MILLIVOLT_PER_LSB; + return IIO_VAL_INT; +} + +static int ad2s1210_set_voltage_threshold(struct ad2s1210_state *st, + unsigned int reg, int val) +{ + unsigned int reg_val; + + reg_val = val / THRESHOLD_MILLIVOLT_PER_LSB; + + guard(mutex)(&st->lock); + return regmap_write(st->regmap, reg, reg_val); +} + +static int ad2s1210_get_lot_high_threshold(struct ad2s1210_state *st, + int *val, int *val2) +{ + unsigned int reg_val; + int ret; + + guard(mutex)(&st->lock); + ret = regmap_read(st->regmap, AD2S1210_REG_LOT_HIGH_THRD, ®_val); + if (ret < 0) + return ret; + + *val = 0; + *val2 = reg_val * ad2s1210_lot_threshold_urad_per_lsb[st->resolution]; + return IIO_VAL_INT_PLUS_MICRO; +} + +static int ad2s1210_set_lot_high_threshold(struct ad2s1210_state *st, + int val, int val2) +{ + unsigned int high_reg_val, low_reg_val, hysteresis; + int ret; + + /* all valid values are between 0 and pi/4 radians */ + if (val != 0) + return -EINVAL; + + guard(mutex)(&st->lock); + /* + * We need to read both high and low registers first so we can preserve + * the hysteresis. + */ + ret = regmap_read(st->regmap, AD2S1210_REG_LOT_HIGH_THRD, &high_reg_val); + if (ret < 0) + return ret; + + ret = regmap_read(st->regmap, AD2S1210_REG_LOT_LOW_THRD, &low_reg_val); + if (ret < 0) + return ret; + + hysteresis = high_reg_val - low_reg_val; + high_reg_val = val2 / ad2s1210_lot_threshold_urad_per_lsb[st->resolution]; + low_reg_val = high_reg_val - hysteresis; + + ret = regmap_write(st->regmap, AD2S1210_REG_LOT_HIGH_THRD, high_reg_val); + if (ret < 0) + return ret; + + return regmap_write(st->regmap, AD2S1210_REG_LOT_LOW_THRD, low_reg_val); +} + +static int ad2s1210_get_lot_low_threshold(struct ad2s1210_state *st, + int *val, int *val2) +{ + unsigned int high_reg_val, low_reg_val; + int ret; + + guard(mutex)(&st->lock); + + ret = regmap_read(st->regmap, AD2S1210_REG_LOT_HIGH_THRD, &high_reg_val); + if (ret < 0) + return ret; + + ret = regmap_read(st->regmap, AD2S1210_REG_LOT_LOW_THRD, &low_reg_val); + if (ret < 0) + return ret; + + /* sysfs value is hysteresis rather than actual low value */ + *val = 0; + *val2 = (high_reg_val - low_reg_val) * + ad2s1210_lot_threshold_urad_per_lsb[st->resolution]; + return IIO_VAL_INT_PLUS_MICRO; +} + +static int ad2s1210_set_lot_low_threshold(struct ad2s1210_state *st, + int val, int val2) +{ + unsigned int reg_val, hysteresis; + int ret; + + /* all valid values are between 0 and pi/4 radians */ + if (val != 0) + return -EINVAL; + + hysteresis = val2 / ad2s1210_lot_threshold_urad_per_lsb[st->resolution]; + + guard(mutex)(&st->lock); + + ret = regmap_read(st->regmap, AD2S1210_REG_LOT_HIGH_THRD, ®_val); + if (ret < 0) + return ret; + + return regmap_write(st->regmap, AD2S1210_REG_LOT_LOW_THRD, + reg_val - hysteresis); +} + +static int ad2s1210_get_excitation_frequency(struct ad2s1210_state *st, int *val) +{ + unsigned int reg_val; + int ret; + + guard(mutex)(&st->lock); + + ret = regmap_read(st->regmap, AD2S1210_REG_EXCIT_FREQ, ®_val); + if (ret < 0) + return ret; + + *val = reg_val * st->clkin_hz / (1 << 15); + return IIO_VAL_INT; +} + +static int ad2s1210_set_excitation_frequency(struct ad2s1210_state *st, int val) +{ + if (val < AD2S1210_MIN_EXCIT || val > AD2S1210_MAX_EXCIT) + return -EINVAL; + + guard(mutex)(&st->lock); + return ad2s1210_reinit_excitation_frequency(st, val); +} + +static const int ad2s1210_velocity_scale[] = { + 17089132, /* 8.192MHz / (2*pi * 2500 / 2^15) */ + 42722830, /* 8.192MHz / (2*pi * 1000 / 2^15) */ + 85445659, /* 8.192MHz / (2*pi * 500 / 2^15) */ + 341782638, /* 8.192MHz / (2*pi * 125 / 2^15) */ +}; + +static int ad2s1210_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct ad2s1210_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return ad2s1210_single_conversion(indio_dev, chan, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL: + /* approx 0.3 arc min converted to radians */ + *val = 0; + *val2 = 95874; + return IIO_VAL_INT_PLUS_NANO; + case IIO_ANGL_VEL: + *val = st->clkin_hz; + *val2 = ad2s1210_velocity_scale[st->resolution]; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_FREQUENCY: + switch (chan->type) { + case IIO_ALTVOLTAGE: + return ad2s1210_get_excitation_frequency(st, val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_HYSTERESIS: + switch (chan->type) { + case IIO_ANGL: + return ad2s1210_get_hysteresis(st, val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ad2s1210_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, + int *length, long mask) +{ + static const int excitation_frequency_available[] = { + AD2S1210_MIN_EXCIT, + 250, /* step */ + AD2S1210_MAX_EXCIT, + }; + + struct ad2s1210_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_FREQUENCY: + switch (chan->type) { + case IIO_ALTVOLTAGE: + *type = IIO_VAL_INT; + *vals = excitation_frequency_available; + return IIO_AVAIL_RANGE; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_HYSTERESIS: + switch (chan->type) { + case IIO_ANGL: + *vals = st->hysteresis_available; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(st->hysteresis_available); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ad2s1210_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ad2s1210_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_FREQUENCY: + switch (chan->type) { + case IIO_ALTVOLTAGE: + return ad2s1210_set_excitation_frequency(st, val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_HYSTERESIS: + switch (chan->type) { + case IIO_ANGL: + return ad2s1210_set_hysteresis(st, val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static const struct iio_event_spec ad2s1210_position_event_spec[] = { + { + /* Tracking error exceeds LOT threshold fault. */ + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = + /* Loss of tracking high threshold. */ + BIT(IIO_EV_INFO_VALUE) | + /* Loss of tracking low threshold. */ + BIT(IIO_EV_INFO_HYSTERESIS), + }, +}; + +static const struct iio_event_spec ad2s1210_velocity_event_spec[] = { + { + /* Velocity exceeds maximum tracking rate fault. */ + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_RISING, + }, +}; + +static const struct iio_event_spec ad2s1210_phase_event_spec[] = { + { + /* Phase error fault. */ + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_RISING, + /* Phase lock range. */ + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, +}; + +static const struct iio_event_spec ad2s1210_monitor_signal_event_spec[] = { + { + /* Sine/cosine below LOS threshold fault. */ + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + /* Loss of signal threshold. */ + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + /* Sine/cosine DOS overrange fault. */ + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + /* Degradation of signal overrange threshold. */ + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, + { + /* Sine/cosine DOS mismatch fault. */ + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, +}; + +static const struct iio_event_spec ad2s1210_sin_cos_event_spec[] = { + { + /* Sine/cosine clipping fault. */ + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_EITHER, + }, +}; + +static const struct iio_chan_spec ad2s1210_channels[] = { + { + .type = IIO_ANGL, + .indexed = 1, + .channel = 0, + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, + }, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_HYSTERESIS), + }, { + .type = IIO_ANGL_VEL, + .indexed = 1, + .channel = 0, + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, + }, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .event_spec = ad2s1210_velocity_event_spec, + .num_event_specs = ARRAY_SIZE(ad2s1210_velocity_event_spec), + }, + IIO_CHAN_SOFT_TIMESTAMP(2), + { + /* used to configure LOT thresholds and get tracking error */ + .type = IIO_ANGL, + .indexed = 1, + .channel = 1, + .scan_index = -1, + .event_spec = ad2s1210_position_event_spec, + .num_event_specs = ARRAY_SIZE(ad2s1210_position_event_spec), + }, + { + /* used to configure phase lock range and get phase lock error */ + .type = IIO_PHASE, + .indexed = 1, + .channel = 0, + .scan_index = -1, + .event_spec = ad2s1210_phase_event_spec, + .num_event_specs = ARRAY_SIZE(ad2s1210_phase_event_spec), + }, { + /* excitation frequency output */ + .type = IIO_ALTVOLTAGE, + .indexed = 1, + .channel = 0, + .output = 1, + .scan_index = -1, + .info_mask_separate = BIT(IIO_CHAN_INFO_FREQUENCY), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_FREQUENCY), + }, { + /* monitor signal */ + .type = IIO_ALTVOLTAGE, + .indexed = 1, + .channel = 0, + .scan_index = -1, + .event_spec = ad2s1210_monitor_signal_event_spec, + .num_event_specs = ARRAY_SIZE(ad2s1210_monitor_signal_event_spec), + }, { + /* sine input */ + .type = IIO_ALTVOLTAGE, + .indexed = 1, + .channel = 1, + .scan_index = -1, + .event_spec = ad2s1210_sin_cos_event_spec, + .num_event_specs = ARRAY_SIZE(ad2s1210_sin_cos_event_spec), + }, { + /* cosine input */ + .type = IIO_ALTVOLTAGE, + .indexed = 1, + .channel = 2, + .scan_index = -1, + .event_spec = ad2s1210_sin_cos_event_spec, + .num_event_specs = ARRAY_SIZE(ad2s1210_sin_cos_event_spec), + }, +}; + +static ssize_t event_attr_voltage_reg_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev)); + struct iio_dev_attr *iattr = to_iio_dev_attr(attr); + unsigned int value; + int ret; + + guard(mutex)(&st->lock); + ret = regmap_read(st->regmap, iattr->address, &value); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", value * THRESHOLD_MILLIVOLT_PER_LSB); +} + +static ssize_t event_attr_voltage_reg_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev)); + struct iio_dev_attr *iattr = to_iio_dev_attr(attr); + u16 data; + int ret; + + ret = kstrtou16(buf, 10, &data); + if (ret) + return -EINVAL; + + guard(mutex)(&st->lock); + ret = regmap_write(st->regmap, iattr->address, + data / THRESHOLD_MILLIVOLT_PER_LSB); + if (ret < 0) + return ret; + + return len; +} + +static ssize_t +in_angl1_thresh_rising_value_available_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev)); + int step = ad2s1210_lot_threshold_urad_per_lsb[st->resolution]; + + return sysfs_emit(buf, "[0 0.%06d 0.%06d]\n", step, step * 0x7F); +} + +static ssize_t +in_angl1_thresh_rising_hysteresis_available_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev)); + int step = ad2s1210_lot_threshold_urad_per_lsb[st->resolution]; + + return sysfs_emit(buf, "[0 0.%06d 0.%06d]\n", step, step * 0x7F); +} + +static IIO_CONST_ATTR(in_phase0_mag_rising_value_available, + __stringify(PHASE_44_DEG_TO_RAD_INT) "." + __stringify(PHASE_44_DEG_TO_RAD_MICRO) " " + __stringify(PHASE_360_DEG_TO_RAD_INT) "." + __stringify(PHASE_360_DEG_TO_RAD_MICRO)); +static IIO_CONST_ATTR(in_altvoltage0_thresh_falling_value_available, + THRESHOLD_RANGE_STR); +static IIO_CONST_ATTR(in_altvoltage0_thresh_rising_value_available, + THRESHOLD_RANGE_STR); +static IIO_CONST_ATTR(in_altvoltage0_mag_rising_value_available, + THRESHOLD_RANGE_STR); +static IIO_DEVICE_ATTR(in_altvoltage0_mag_rising_reset_max, 0644, + event_attr_voltage_reg_show, event_attr_voltage_reg_store, + AD2S1210_REG_DOS_RST_MAX_THRD); +static IIO_CONST_ATTR(in_altvoltage0_mag_rising_reset_max_available, THRESHOLD_RANGE_STR); +static IIO_DEVICE_ATTR(in_altvoltage0_mag_rising_reset_min, 0644, + event_attr_voltage_reg_show, event_attr_voltage_reg_store, + AD2S1210_REG_DOS_RST_MIN_THRD); +static IIO_CONST_ATTR(in_altvoltage0_mag_rising_reset_min_available, THRESHOLD_RANGE_STR); +static IIO_DEVICE_ATTR_RO(in_angl1_thresh_rising_value_available, 0); +static IIO_DEVICE_ATTR_RO(in_angl1_thresh_rising_hysteresis_available, 0); + +static struct attribute *ad2s1210_event_attributes[] = { + &iio_const_attr_in_phase0_mag_rising_value_available.dev_attr.attr, + &iio_const_attr_in_altvoltage0_thresh_falling_value_available.dev_attr.attr, + &iio_const_attr_in_altvoltage0_thresh_rising_value_available.dev_attr.attr, + &iio_const_attr_in_altvoltage0_mag_rising_value_available.dev_attr.attr, + &iio_dev_attr_in_altvoltage0_mag_rising_reset_max.dev_attr.attr, + &iio_const_attr_in_altvoltage0_mag_rising_reset_max_available.dev_attr.attr, + &iio_dev_attr_in_altvoltage0_mag_rising_reset_min.dev_attr.attr, + &iio_const_attr_in_altvoltage0_mag_rising_reset_min_available.dev_attr.attr, + &iio_dev_attr_in_angl1_thresh_rising_value_available.dev_attr.attr, + &iio_dev_attr_in_angl1_thresh_rising_hysteresis_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad2s1210_event_attribute_group = { + .attrs = ad2s1210_event_attributes, +}; + +static int ad2s1210_initial(struct ad2s1210_state *st) +{ + unsigned int data; + int ret; + + guard(mutex)(&st->lock); + + /* Use default config register value plus resolution from devicetree. */ + data = FIELD_PREP(AD2S1210_PHASE_LOCK_RANGE_44, 1); + data |= FIELD_PREP(AD2S1210_ENABLE_HYSTERESIS, 1); + data |= FIELD_PREP(AD2S1210_SET_ENRES, 0x3); + data |= FIELD_PREP(AD2S1210_SET_RES, st->resolution); + + ret = regmap_write(st->regmap, AD2S1210_REG_CONTROL, data); + if (ret < 0) + return ret; + + return ad2s1210_reinit_excitation_frequency(st, AD2S1210_DEF_EXCIT); +} + +static int ad2s1210_read_label(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + char *label) +{ + if (chan->type == IIO_ANGL) { + if (chan->channel == 0) + return sysfs_emit(label, "position\n"); + if (chan->channel == 1) + return sysfs_emit(label, "tracking error\n"); + } + if (chan->type == IIO_ANGL_VEL) + return sysfs_emit(label, "velocity\n"); + if (chan->type == IIO_PHASE) + return sysfs_emit(label, "synthetic reference\n"); + if (chan->type == IIO_ALTVOLTAGE) { + if (chan->output) + return sysfs_emit(label, "excitation\n"); + if (chan->channel == 0) + return sysfs_emit(label, "monitor signal\n"); + if (chan->channel == 1) + return sysfs_emit(label, "cosine\n"); + if (chan->channel == 2) + return sysfs_emit(label, "sine\n"); + } + + return -EINVAL; +} + +static int ad2s1210_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct ad2s1210_state *st = iio_priv(indio_dev); + + switch (chan->type) { + case IIO_ANGL: + switch (info) { + case IIO_EV_INFO_VALUE: + return ad2s1210_get_lot_high_threshold(st, val, val2); + case IIO_EV_INFO_HYSTERESIS: + return ad2s1210_get_lot_low_threshold(st, val, val2); + default: + return -EINVAL; + } + case IIO_ALTVOLTAGE: + if (chan->output) + return -EINVAL; + if (type == IIO_EV_TYPE_THRESH && dir == IIO_EV_DIR_FALLING) + return ad2s1210_get_voltage_threshold(st, + AD2S1210_REG_LOS_THRD, val); + if (type == IIO_EV_TYPE_THRESH && dir == IIO_EV_DIR_RISING) + return ad2s1210_get_voltage_threshold(st, + AD2S1210_REG_DOS_OVR_THRD, val); + if (type == IIO_EV_TYPE_MAG) + return ad2s1210_get_voltage_threshold(st, + AD2S1210_REG_DOS_MIS_THRD, val); + return -EINVAL; + case IIO_PHASE: + return ad2s1210_get_phase_lock_range(st, val, val2); + default: + return -EINVAL; + } +} + +static int ad2s1210_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct ad2s1210_state *st = iio_priv(indio_dev); + + switch (chan->type) { + case IIO_ANGL: + switch (info) { + case IIO_EV_INFO_VALUE: + return ad2s1210_set_lot_high_threshold(st, val, val2); + case IIO_EV_INFO_HYSTERESIS: + return ad2s1210_set_lot_low_threshold(st, val, val2); + default: + return -EINVAL; + } + case IIO_ALTVOLTAGE: + if (chan->output) + return -EINVAL; + if (type == IIO_EV_TYPE_THRESH && dir == IIO_EV_DIR_FALLING) + return ad2s1210_set_voltage_threshold(st, + AD2S1210_REG_LOS_THRD, val); + if (type == IIO_EV_TYPE_THRESH && dir == IIO_EV_DIR_RISING) + return ad2s1210_set_voltage_threshold(st, + AD2S1210_REG_DOS_OVR_THRD, val); + if (type == IIO_EV_TYPE_MAG) + return ad2s1210_set_voltage_threshold(st, + AD2S1210_REG_DOS_MIS_THRD, val); + return -EINVAL; + case IIO_PHASE: + return ad2s1210_set_phase_lock_range(st, val, val2); + default: + return -EINVAL; + } +} + +static int ad2s1210_read_event_label(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + enum iio_event_type type, + enum iio_event_direction dir, + char *label) +{ + if (chan->type == IIO_ANGL) + return sysfs_emit(label, "LOT\n"); + if (chan->type == IIO_ANGL_VEL) + return sysfs_emit(label, "max tracking rate\n"); + if (chan->type == IIO_PHASE) + return sysfs_emit(label, "phase lock\n"); + if (chan->type == IIO_ALTVOLTAGE) { + if (chan->channel == 0) { + if (type == IIO_EV_TYPE_THRESH && + dir == IIO_EV_DIR_FALLING) + return sysfs_emit(label, "LOS\n"); + if (type == IIO_EV_TYPE_THRESH && + dir == IIO_EV_DIR_RISING) + return sysfs_emit(label, "DOS overrange\n"); + if (type == IIO_EV_TYPE_MAG) + return sysfs_emit(label, "DOS mismatch\n"); + } + if (chan->channel == 1 || chan->channel == 2) + return sysfs_emit(label, "clipped\n"); + } + + return -EINVAL; +} + +static int ad2s1210_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + struct ad2s1210_state *st = iio_priv(indio_dev); + + guard(mutex)(&st->lock); + + if (readval) + return regmap_read(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); +} + +static void ad2s1210_scan_to_buffers(struct iio_dev *indio_dev, s64 timestamp) +{ + struct ad2s1210_state *st = iio_priv(indio_dev); + size_t chan = 0; + int ret; + + guard(mutex)(&st->lock); + + memset(&st->scan, 0, sizeof(st->scan)); + ad2s1210_toggle_sample_line(st); + + if (test_bit(0, indio_dev->active_scan_mask)) { + if (st->fixed_mode == MOD_CONFIG) { + ret = regmap_bulk_read(st->regmap, + AD2S1210_REG_POSITION_MSB, + &st->sample.raw, 2); + if (ret < 0) + return; + } else { + ret = ad2s1210_set_mode(st, MOD_POS); + if (ret < 0) + return; + + ret = spi_read(st->sdev, &st->sample, 3); + if (ret < 0) + return; + } + + memcpy(&st->scan.chan[chan++], &st->sample.raw, 2); + } + + if (test_bit(1, indio_dev->active_scan_mask)) { + if (st->fixed_mode == MOD_CONFIG) { + ret = regmap_bulk_read(st->regmap, + AD2S1210_REG_VELOCITY_MSB, + &st->sample.raw, 2); + if (ret < 0) + return; + } else { + ret = ad2s1210_set_mode(st, MOD_VEL); + if (ret < 0) + return; + + ret = spi_read(st->sdev, &st->sample, 3); + if (ret < 0) + return; + } + + memcpy(&st->scan.chan[chan++], &st->sample.raw, 2); + } + + if (st->fixed_mode == MOD_CONFIG) { + unsigned int reg_val; + + ret = regmap_read(st->regmap, AD2S1210_REG_FAULT, ®_val); + if (ret < 0) + return; + + st->sample.fault = reg_val; + } + + ad2s1210_push_events(indio_dev, st->sample.fault, timestamp); + iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan), + timestamp); +} + +static irqreturn_t ad2s1210_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + + ad2s1210_scan_to_buffers(indio_dev, pf->timestamp); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_info ad2s1210_info = { + .event_attrs = &ad2s1210_event_attribute_group, + .read_raw = ad2s1210_read_raw, + .read_avail = ad2s1210_read_avail, + .write_raw = ad2s1210_write_raw, + .read_label = ad2s1210_read_label, + .read_event_value = ad2s1210_read_event_value, + .write_event_value = ad2s1210_write_event_value, + .read_event_label = ad2s1210_read_event_label, + .debugfs_reg_access = &ad2s1210_debugfs_reg_access, +}; + +static int ad2s1210_setup_properties(struct ad2s1210_state *st) +{ + struct device *dev = &st->sdev->dev; + const char *str_val; + u32 val; + int ret; + + ret = device_property_read_string(dev, "adi,fixed-mode", &str_val); + if (ret == -EINVAL) + st->fixed_mode = -1; + else if (ret < 0) + return dev_err_probe(dev, ret, + "failed to read adi,fixed-mode property\n"); + else { + if (strcmp(str_val, "config")) + return dev_err_probe(dev, -EINVAL, + "only adi,fixed-mode=\"config\" is supported\n"); + + st->fixed_mode = MOD_CONFIG; + } + + ret = device_property_read_u32(dev, "assigned-resolution-bits", &val); + if (ret < 0) + return dev_err_probe(dev, ret, + "failed to read assigned-resolution-bits property\n"); + + if (val < 10 || val > 16) + return dev_err_probe(dev, -EINVAL, + "resolution out of range: %u\n", val); + + st->resolution = (val - 10) >> 1; + /* + * These are values that correlate to the hysteresis bit in the Control + * register. 0 = disabled, 1 = enabled. When enabled, the actual + * hysteresis is +/- 1 LSB of the raw position value. Which bit is the + * LSB depends on the specified resolution. + */ + st->hysteresis_available[0] = 0; + st->hysteresis_available[1] = 1 << (2 * (AD2S1210_RES_16 - + st->resolution)); + + return 0; +} + +static int ad2s1210_setup_clocks(struct ad2s1210_state *st) +{ + struct device *dev = &st->sdev->dev; + struct clk *clk; + + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "failed to get clock\n"); + + st->clkin_hz = clk_get_rate(clk); + if (st->clkin_hz < AD2S1210_MIN_CLKIN || st->clkin_hz > AD2S1210_MAX_CLKIN) + return dev_err_probe(dev, -EINVAL, + "clock frequency out of range: %lu\n", + st->clkin_hz); + + return 0; +} + +static int ad2s1210_setup_gpios(struct ad2s1210_state *st) +{ + struct device *dev = &st->sdev->dev; + struct gpio_descs *resolution_gpios; + struct gpio_desc *reset_gpio; + DECLARE_BITMAP(bitmap, 2) = { }; + int ret; + + /* should not be sampling on startup */ + st->sample_gpio = devm_gpiod_get(dev, "sample", GPIOD_OUT_LOW); + if (IS_ERR(st->sample_gpio)) + return dev_err_probe(dev, PTR_ERR(st->sample_gpio), + "failed to request sample GPIO\n"); + + /* both pins high means that we start in config mode */ + st->mode_gpios = devm_gpiod_get_array_optional(dev, "mode", + GPIOD_OUT_HIGH); + if (IS_ERR(st->mode_gpios)) + return dev_err_probe(dev, PTR_ERR(st->mode_gpios), + "failed to request mode GPIOs\n"); + + if (!st->mode_gpios && st->fixed_mode == -1) + return dev_err_probe(dev, -EINVAL, + "must specify either adi,fixed-mode or mode-gpios\n"); + + if (st->mode_gpios && st->fixed_mode != -1) + return dev_err_probe(dev, -EINVAL, + "must specify only one of adi,fixed-mode or mode-gpios\n"); + + if (st->mode_gpios && st->mode_gpios->ndescs != 2) + return dev_err_probe(dev, -EINVAL, + "requires exactly 2 mode-gpios\n"); + + /* + * If resolution gpios are provided, they get set to the required + * resolution, otherwise it is assumed the RES0 and RES1 pins are + * hard-wired to match the resolution indicated in the devicetree. + */ + resolution_gpios = devm_gpiod_get_array_optional(dev, "resolution", + GPIOD_ASIS); + if (IS_ERR(resolution_gpios)) + return dev_err_probe(dev, PTR_ERR(resolution_gpios), + "failed to request resolution GPIOs\n"); + + if (resolution_gpios) { + if (resolution_gpios->ndescs != 2) + return dev_err_probe(dev, -EINVAL, + "requires exactly 2 resolution-gpios\n"); + + bitmap_write(bitmap, st->resolution, 0, 2); + + ret = gpiod_multi_set_value_cansleep(resolution_gpios, bitmap); + if (ret < 0) + return dev_err_probe(dev, ret, + "failed to set resolution gpios\n"); + } + + /* If the optional reset GPIO is present, toggle it to do a hard reset. */ + reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(reset_gpio)) + return dev_err_probe(dev, PTR_ERR(reset_gpio), + "failed to request reset GPIO\n"); + + if (reset_gpio) { + udelay(10); + gpiod_set_value(reset_gpio, 0); + } + + return 0; +} + +static const struct regmap_range ad2s1210_regmap_readable_ranges[] = { + regmap_reg_range(AD2S1210_REG_POSITION_MSB, AD2S1210_REG_VELOCITY_LSB), + regmap_reg_range(AD2S1210_REG_LOS_THRD, AD2S1210_REG_LOT_LOW_THRD), + regmap_reg_range(AD2S1210_REG_EXCIT_FREQ, AD2S1210_REG_CONTROL), + regmap_reg_range(AD2S1210_REG_FAULT, AD2S1210_REG_FAULT), +}; + +static const struct regmap_access_table ad2s1210_regmap_rd_table = { + .yes_ranges = ad2s1210_regmap_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(ad2s1210_regmap_readable_ranges), +}; + +static const struct regmap_range ad2s1210_regmap_writeable_ranges[] = { + regmap_reg_range(AD2S1210_REG_LOS_THRD, AD2S1210_REG_LOT_LOW_THRD), + regmap_reg_range(AD2S1210_REG_EXCIT_FREQ, AD2S1210_REG_CONTROL), + regmap_reg_range(AD2S1210_REG_SOFT_RESET, AD2S1210_REG_SOFT_RESET), + regmap_reg_range(AD2S1210_REG_FAULT, AD2S1210_REG_FAULT), +}; + +static const struct regmap_access_table ad2s1210_regmap_wr_table = { + .yes_ranges = ad2s1210_regmap_writeable_ranges, + .n_yes_ranges = ARRAY_SIZE(ad2s1210_regmap_writeable_ranges), +}; + +static int ad2s1210_setup_regmap(struct ad2s1210_state *st) +{ + struct device *dev = &st->sdev->dev; + const struct regmap_config config = { + .reg_bits = 8, + .val_bits = 8, + .disable_locking = true, + .reg_read = ad2s1210_regmap_reg_read, + .reg_write = ad2s1210_regmap_reg_write, + .rd_table = &ad2s1210_regmap_rd_table, + .wr_table = &ad2s1210_regmap_wr_table, + .can_sleep = true, + }; + + st->regmap = devm_regmap_init(dev, NULL, st, &config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "failed to allocate register map\n"); + + return 0; +} + +static int ad2s1210_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct ad2s1210_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + st = iio_priv(indio_dev); + + mutex_init(&st->lock); + st->sdev = spi; + + ret = ad2s1210_setup_properties(st); + if (ret < 0) + return ret; + + ret = ad2s1210_setup_clocks(st); + if (ret < 0) + return ret; + + ret = ad2s1210_setup_gpios(st); + if (ret < 0) + return ret; + + ret = ad2s1210_setup_regmap(st); + if (ret < 0) + return ret; + + ret = ad2s1210_initial(st); + if (ret < 0) + return ret; + + indio_dev->info = &ad2s1210_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad2s1210_channels; + indio_dev->num_channels = ARRAY_SIZE(ad2s1210_channels); + indio_dev->name = spi_get_device_id(spi)->name; + + ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, + &iio_pollfunc_store_time, + &ad2s1210_trigger_handler, NULL); + if (ret < 0) + return dev_err_probe(&spi->dev, ret, + "iio triggered buffer setup failed\n"); + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct of_device_id ad2s1210_of_match[] = { + { .compatible = "adi,ad2s1210", }, + { } +}; +MODULE_DEVICE_TABLE(of, ad2s1210_of_match); + +static const struct spi_device_id ad2s1210_id[] = { + { .name = "ad2s1210" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad2s1210_id); + +static struct spi_driver ad2s1210_driver = { + .driver = { + .name = "ad2s1210", + .of_match_table = ad2s1210_of_match, + }, + .probe = ad2s1210_probe, + .id_table = ad2s1210_id, +}; +module_spi_driver(ad2s1210_driver); + +MODULE_AUTHOR("Graff Yang "); +MODULE_DESCRIPTION("Analog Devices AD2S1210 Resolver to Digital SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/resolver/ad2s90.c b/drivers/iio/resolver/ad2s90.c new file mode 100644 index 000000000..b7a2dcc42 --- /dev/null +++ b/drivers/iio/resolver/ad2s90.c @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ad2s90.c simple support for the ADI Resolver to Digital Converters: AD2S90 + * + * Copyright (c) 2010-2010 Analog Devices Inc. + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* + * Although chip's max frequency is 2Mhz, it needs 600ns between CS and the + * first falling edge of SCLK, so frequency should be at most 1 / (2 * 6e-7) + */ +#define AD2S90_MAX_SPI_FREQ_HZ 830000 + +struct ad2s90_state { + struct mutex lock; /* lock to protect rx buffer */ + struct spi_device *sdev; + u8 rx[2] __aligned(IIO_DMA_MINALIGN); +}; + +static int ad2s90_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret; + struct ad2s90_state *st = iio_priv(indio_dev); + + if (chan->type != IIO_ANGL) + return -EINVAL; + + switch (m) { + case IIO_CHAN_INFO_SCALE: + /* 2 * Pi / 2^12 */ + *val = 6283; /* mV */ + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_RAW: + mutex_lock(&st->lock); + ret = spi_read(st->sdev, st->rx, 2); + if (ret < 0) { + mutex_unlock(&st->lock); + return ret; + } + *val = (((u16)(st->rx[0])) << 4) | ((st->rx[1] & 0xF0) >> 4); + + mutex_unlock(&st->lock); + + return IIO_VAL_INT; + default: + break; + } + + return -EINVAL; +} + +static const struct iio_info ad2s90_info = { + .read_raw = ad2s90_read_raw, +}; + +static const struct iio_chan_spec ad2s90_chan = { + .type = IIO_ANGL, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), +}; + +static int ad2s90_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct ad2s90_state *st; + + if (spi->max_speed_hz > AD2S90_MAX_SPI_FREQ_HZ) { + dev_err(&spi->dev, "SPI CLK, %d Hz exceeds %d Hz\n", + spi->max_speed_hz, AD2S90_MAX_SPI_FREQ_HZ); + return -EINVAL; + } + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + mutex_init(&st->lock); + st->sdev = spi; + indio_dev->info = &ad2s90_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = &ad2s90_chan; + indio_dev->num_channels = 1; + indio_dev->name = spi_get_device_id(spi)->name; + + return devm_iio_device_register(indio_dev->dev.parent, indio_dev); +} + +static const struct of_device_id ad2s90_of_match[] = { + { .compatible = "adi,ad2s90", }, + { } +}; +MODULE_DEVICE_TABLE(of, ad2s90_of_match); + +static const struct spi_device_id ad2s90_id[] = { + { .name = "ad2s90" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad2s90_id); + +static struct spi_driver ad2s90_driver = { + .driver = { + .name = "ad2s90", + .of_match_table = ad2s90_of_match, + }, + .probe = ad2s90_probe, + .id_table = ad2s90_id, +}; +module_spi_driver(ad2s90_driver); + +MODULE_AUTHOR("Graff Yang "); +MODULE_DESCRIPTION("Analog Devices AD2S90 Resolver to Digital SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/temperature/Kconfig b/drivers/iio/temperature/Kconfig new file mode 100644 index 000000000..9328b2250 --- /dev/null +++ b/drivers/iio/temperature/Kconfig @@ -0,0 +1,187 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Temperature sensor drivers +# +menu "Temperature sensors" + +config IQS620AT_TEMP + tristate "Azoteq IQS620AT temperature sensor" + depends on MFD_IQS62X || COMPILE_TEST + help + Say Y here if you want to build support for the Azoteq IQS620AT + temperature sensor. + + To compile this driver as a module, choose M here: the module + will be called iqs620at-temp. + +config LTC2983 + tristate "Analog Devices Multi-Sensor Digital Temperature Measurement System" + depends on SPI + select REGMAP_SPI + help + Say yes here to build support for the LTC2983 Multi-Sensor + high accuracy digital temperature measurement system. + + To compile this driver as a module, choose M here: the module + will be called ltc2983. + +config MAXIM_THERMOCOUPLE + tristate "Maxim thermocouple sensors" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for the Maxim series of + thermocouple sensors connected via SPI. + + Supported sensors: + * MAX6675 + * MAX31855 + + This driver can also be built as a module. If so, the module will + be called maxim_thermocouple. + +config HID_SENSOR_TEMP + tristate "HID Environmental temperature sensor" + depends on HID_SENSOR_HUB + select IIO_BUFFER + select HID_SENSOR_IIO_COMMON + select HID_SENSOR_IIO_TRIGGER + help + Say yes here to build support for the HID SENSOR + temperature driver + + To compile this driver as a module, choose M here: the module + will be called hid-sensor-temperature. + +config MLX90614 + tristate "MLX90614 contact-less infrared sensor" + depends on I2C + help + If you say yes here you get support for the Melexis + MLX90614 contact-less infrared sensor connected with I2C. + + This driver can also be built as a module. If so, the module will + be called mlx90614. + +config MLX90632 + tristate "MLX90632 contact-less infrared sensor with medical accuracy" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for the Melexis + MLX90632 contact-less infrared sensor with medical accuracy + connected with I2C. + + This driver can also be built as a module. If so, the module will + be called mlx90632. + +config MLX90635 + tristate "MLX90635 contact-less infrared sensor with medical accuracy" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for the Melexis + MLX90635 contact-less infrared sensor with medical accuracy + connected with I2C. + + This driver can also be built as a module. If so, the module will + be called mlx90635. + +config TMP006 + tristate "TMP006 infrared thermopile sensor" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for the Texas Instruments + TMP006 infrared thermopile sensor. + + This driver can also be built as a module. If so, the module will + be called tmp006. + +config TMP007 + tristate "TMP007 infrared thermopile sensor with Integrated Math Engine" + depends on I2C + help + If you say yes here you get support for the Texas Instruments + TMP007 infrared thermopile sensor with Integrated Math Engine. + + This driver can also be built as a module. If so, the module will + be called tmp007. + +config TMP117 + tristate "TMP117 Digital temperature sensor with integrated NV memory" + depends on I2C + help + If you say yes here you get support for the Texas Instruments + TMP117 Digital temperature sensor with integrated NV memory. + + This driver can also be built as a module. If so, the module will + be called tmp117. + +config TSYS01 + tristate "Measurement Specialties TSYS01 temperature sensor using I2C bus connection" + depends on I2C + select IIO_MS_SENSORS_I2C + help + If you say yes here you get support for the Measurement Specialties + TSYS01 I2C temperature sensor. + + This driver can also be built as a module. If so, the module will + be called tsys01. + +config TSYS02D + tristate "Measurement Specialties TSYS02D temperature sensor" + depends on I2C + select IIO_MS_SENSORS_I2C + help + If you say yes here you get support for the Measurement Specialties + TSYS02D temperature sensor. + + This driver can also be built as a module. If so, the module will + be called tsys02d. + +config MAX30208 + tristate "Maxim MAX30208 digital temperature sensor" + depends on I2C + help + If you say yes here you get support for Maxim MAX30208 + digital temperature sensor connected via I2C. + + This driver can also be built as a module. If so, the module + will be called max30208. + +config MAX31856 + tristate "MAX31856 thermocouple sensor" + depends on SPI + help + If you say yes here you get support for MAX31856 + thermocouple sensor chip connected via SPI. + + This driver can also be built as a module. If so, the module + will be called max31856. + +config MAX31865 + tristate "MAX31865 RTD to Digital converter" + depends on SPI + help + If you say yes here you get support for MAX31865 + thermocouple sensor chip connected via SPI. + + This driver can also be build as a module. If so, the module + will be called max31865. + +config MCP9600 + tristate "MCP9600 and similar thermocouple EMF converters" + depends on I2C + help + If you say yes here you get support for... + - MCP9600 + - MCP9601 + ...thermocouple EMF converters connected via I2C. + + This driver can also be built as a module. If so, the module + will be called mcp9600. + +endmenu diff --git a/drivers/iio/temperature/Makefile b/drivers/iio/temperature/Makefile new file mode 100644 index 000000000..0850bf691 --- /dev/null +++ b/drivers/iio/temperature/Makefile @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for industrial I/O temperature drivers +# + +obj-$(CONFIG_IQS620AT_TEMP) += iqs620at-temp.o +obj-$(CONFIG_LTC2983) += ltc2983.o +obj-$(CONFIG_HID_SENSOR_TEMP) += hid-sensor-temperature.o +obj-$(CONFIG_MAXIM_THERMOCOUPLE) += maxim_thermocouple.o +obj-$(CONFIG_MAX30208) += max30208.o +obj-$(CONFIG_MAX31856) += max31856.o +obj-$(CONFIG_MAX31865) += max31865.o +obj-$(CONFIG_MCP9600) += mcp9600.o +obj-$(CONFIG_MLX90614) += mlx90614.o +obj-$(CONFIG_MLX90632) += mlx90632.o +obj-$(CONFIG_MLX90635) += mlx90635.o +obj-$(CONFIG_TMP006) += tmp006.o +obj-$(CONFIG_TMP007) += tmp007.o +obj-$(CONFIG_TMP117) += tmp117.o +obj-$(CONFIG_TSYS01) += tsys01.o +obj-$(CONFIG_TSYS02D) += tsys02d.o diff --git a/drivers/iio/temperature/hid-sensor-temperature.c b/drivers/iio/temperature/hid-sensor-temperature.c new file mode 100644 index 000000000..5e3262e46 --- /dev/null +++ b/drivers/iio/temperature/hid-sensor-temperature.c @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HID Sensors Driver + * Copyright (c) 2017, Intel Corporation. + */ +#include +#include +#include +#include +#include +#include + +#include "../common/hid-sensors/hid-sensor-trigger.h" + +struct temperature_state { + struct hid_sensor_common common_attributes; + struct hid_sensor_hub_attribute_info temperature_attr; + struct { + s32 temperature_data; + aligned_s64 timestamp; + } scan; + int scale_pre_decml; + int scale_post_decml; + int scale_precision; + int value_offset; +}; + +static const u32 temperature_sensitivity_addresses[] = { + HID_USAGE_SENSOR_DATA_ENVIRONMENTAL_TEMPERATURE, +}; + +/* Channel definitions */ +static const struct iio_chan_spec temperature_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +/* Adjust channel real bits based on report descriptor */ +static void temperature_adjust_channel_bit_mask(struct iio_chan_spec *channels, + int channel, int size) +{ + channels[channel].scan_type.sign = 's'; + /* Real storage bits will change based on the report desc. */ + channels[channel].scan_type.realbits = size * 8; + /* Maximum size of a sample to capture is s32 */ + channels[channel].scan_type.storagebits = sizeof(s32) * 8; +} + +static int temperature_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct temperature_state *temp_st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type != IIO_TEMP) + return -EINVAL; + hid_sensor_power_state( + &temp_st->common_attributes, true); + *val = sensor_hub_input_attr_get_raw_value( + temp_st->common_attributes.hsdev, + HID_USAGE_SENSOR_TEMPERATURE, + HID_USAGE_SENSOR_DATA_ENVIRONMENTAL_TEMPERATURE, + temp_st->temperature_attr.report_id, + SENSOR_HUB_SYNC, + temp_st->temperature_attr.logical_minimum < 0); + hid_sensor_power_state( + &temp_st->common_attributes, + false); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = temp_st->scale_pre_decml; + *val2 = temp_st->scale_post_decml; + return temp_st->scale_precision; + + case IIO_CHAN_INFO_OFFSET: + *val = temp_st->value_offset; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SAMP_FREQ: + return hid_sensor_read_samp_freq_value( + &temp_st->common_attributes, val, val2); + + case IIO_CHAN_INFO_HYSTERESIS: + return hid_sensor_read_raw_hyst_value( + &temp_st->common_attributes, val, val2); + default: + return -EINVAL; + } +} + +static int temperature_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct temperature_state *temp_st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return hid_sensor_write_samp_freq_value( + &temp_st->common_attributes, val, val2); + case IIO_CHAN_INFO_HYSTERESIS: + return hid_sensor_write_raw_hyst_value( + &temp_st->common_attributes, val, val2); + default: + return -EINVAL; + } +} + +static const struct iio_info temperature_info = { + .read_raw = &temperature_read_raw, + .write_raw = &temperature_write_raw, +}; + +/* Callback handler to send event after all samples are received and captured */ +static int temperature_proc_event(struct hid_sensor_hub_device *hsdev, + u32 usage_id, void *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct temperature_state *temp_st = iio_priv(indio_dev); + + if (atomic_read(&temp_st->common_attributes.data_ready)) + iio_push_to_buffers_with_ts(indio_dev, &temp_st->scan, + sizeof(temp_st->scan), + iio_get_time_ns(indio_dev)); + + return 0; +} + +/* Capture samples in local storage */ +static int temperature_capture_sample(struct hid_sensor_hub_device *hsdev, + u32 usage_id, + size_t raw_len, char *raw_data, + void *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct temperature_state *temp_st = iio_priv(indio_dev); + + switch (usage_id) { + case HID_USAGE_SENSOR_DATA_ENVIRONMENTAL_TEMPERATURE: + temp_st->scan.temperature_data = *(s32 *)raw_data; + return 0; + default: + return -EINVAL; + } +} + +/* Parse report which is specific to an usage id*/ +static int temperature_parse_report(struct platform_device *pdev, + struct hid_sensor_hub_device *hsdev, + struct iio_chan_spec *channels, + u32 usage_id, + struct temperature_state *st) +{ + int ret; + + ret = sensor_hub_input_get_attribute_info(hsdev, HID_INPUT_REPORT, + usage_id, + HID_USAGE_SENSOR_DATA_ENVIRONMENTAL_TEMPERATURE, + &st->temperature_attr); + if (ret < 0) + return ret; + + temperature_adjust_channel_bit_mask(channels, 0, st->temperature_attr.size); + + st->scale_precision = hid_sensor_format_scale( + HID_USAGE_SENSOR_TEMPERATURE, + &st->temperature_attr, + &st->scale_pre_decml, &st->scale_post_decml); + + return ret; +} + +static struct hid_sensor_hub_callbacks temperature_callbacks = { + .send_event = &temperature_proc_event, + .capture_sample = &temperature_capture_sample, +}; + +/* Function to initialize the processing for usage id */ +static int hid_temperature_probe(struct platform_device *pdev) +{ + static const char *name = "temperature"; + struct iio_dev *indio_dev; + struct temperature_state *temp_st; + struct iio_chan_spec *temp_chans; + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*temp_st)); + if (!indio_dev) + return -ENOMEM; + + temp_st = iio_priv(indio_dev); + temp_st->common_attributes.hsdev = hsdev; + temp_st->common_attributes.pdev = pdev; + + ret = hid_sensor_parse_common_attributes(hsdev, + HID_USAGE_SENSOR_TEMPERATURE, + &temp_st->common_attributes, + temperature_sensitivity_addresses, + ARRAY_SIZE(temperature_sensitivity_addresses)); + if (ret) + return ret; + + temp_chans = devm_kmemdup(&pdev->dev, temperature_channels, + sizeof(temperature_channels), GFP_KERNEL); + if (!temp_chans) + return -ENOMEM; + + ret = temperature_parse_report(pdev, hsdev, temp_chans, + HID_USAGE_SENSOR_TEMPERATURE, temp_st); + if (ret) + return ret; + + indio_dev->channels = temp_chans; + indio_dev->num_channels = ARRAY_SIZE(temperature_channels); + indio_dev->info = &temperature_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + atomic_set(&temp_st->common_attributes.data_ready, 0); + + ret = hid_sensor_setup_trigger(indio_dev, name, + &temp_st->common_attributes); + if (ret) + return ret; + + platform_set_drvdata(pdev, indio_dev); + + temperature_callbacks.pdev = pdev; + ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_TEMPERATURE, + &temperature_callbacks); + if (ret) + goto error_remove_trigger; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_remove_callback; + + return ret; + +error_remove_callback: + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_TEMPERATURE); +error_remove_trigger: + hid_sensor_remove_trigger(indio_dev, &temp_st->common_attributes); + return ret; +} + +/* Function to deinitialize the processing for usage id */ +static void hid_temperature_remove(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = dev_get_platdata(&pdev->dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct temperature_state *temp_st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_TEMPERATURE); + hid_sensor_remove_trigger(indio_dev, &temp_st->common_attributes); +} + +static const struct platform_device_id hid_temperature_ids[] = { + { + /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-200033", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, hid_temperature_ids); + +static struct platform_driver hid_temperature_platform_driver = { + .id_table = hid_temperature_ids, + .driver = { + .name = "temperature-sensor", + .pm = &hid_sensor_pm_ops, + }, + .probe = hid_temperature_probe, + .remove = hid_temperature_remove, +}; +module_platform_driver(hid_temperature_platform_driver); + +MODULE_DESCRIPTION("HID Environmental temperature sensor"); +MODULE_AUTHOR("Song Hongyan "); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_HID"); diff --git a/drivers/iio/temperature/iqs620at-temp.c b/drivers/iio/temperature/iqs620at-temp.c new file mode 100644 index 000000000..e2f878d57 --- /dev/null +++ b/drivers/iio/temperature/iqs620at-temp.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Azoteq IQS620AT Temperature Sensor + * + * Copyright (C) 2019 Jeff LaBundy + */ + +#include +#include +#include +#include +#include +#include +#include + +#define IQS620_TEMP_UI_OUT 0x1A + +#define IQS620_TEMP_SCALE 1000 +#define IQS620_TEMP_OFFSET (-100) +#define IQS620_TEMP_OFFSET_V3 (-40) + +static int iqs620_temp_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct iqs62x_core *iqs62x = iio_device_get_drvdata(indio_dev); + int ret; + __le16 val_buf; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_raw_read(iqs62x->regmap, IQS620_TEMP_UI_OUT, + &val_buf, sizeof(val_buf)); + if (ret) + return ret; + + *val = le16_to_cpu(val_buf); + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = IQS620_TEMP_SCALE; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_OFFSET: + *val = iqs62x->hw_num < IQS620_HW_NUM_V3 ? IQS620_TEMP_OFFSET + : IQS620_TEMP_OFFSET_V3; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static const struct iio_info iqs620_temp_info = { + .read_raw = &iqs620_temp_read_raw, +}; + +static const struct iio_chan_spec iqs620_temp_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + }, +}; + +static int iqs620_temp_probe(struct platform_device *pdev) +{ + struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent); + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(&pdev->dev, 0); + if (!indio_dev) + return -ENOMEM; + + iio_device_set_drvdata(indio_dev, iqs62x); + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = iqs620_temp_channels; + indio_dev->num_channels = ARRAY_SIZE(iqs620_temp_channels); + indio_dev->name = iqs62x->dev_desc->dev_name; + indio_dev->info = &iqs620_temp_info; + + return devm_iio_device_register(&pdev->dev, indio_dev); +} + +static struct platform_driver iqs620_temp_platform_driver = { + .driver = { + .name = "iqs620at-temp", + }, + .probe = iqs620_temp_probe, +}; +module_platform_driver(iqs620_temp_platform_driver); + +MODULE_AUTHOR("Jeff LaBundy "); +MODULE_DESCRIPTION("Azoteq IQS620AT Temperature Sensor"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:iqs620at-temp"); diff --git a/drivers/iio/temperature/ltc2983.c b/drivers/iio/temperature/ltc2983.c new file mode 100644 index 000000000..6efb5252a --- /dev/null +++ b/drivers/iio/temperature/ltc2983.c @@ -0,0 +1,2106 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices LTC2983 Multi-Sensor Digital Temperature Measurement System + * driver + * + * Copyright 2019 Analog Devices Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* register map */ +#define LTC2983_STATUS_REG 0x0000 +#define LTC2983_TEMP_RES_START_REG 0x0010 +#define LTC2983_TEMP_RES_END_REG 0x005F +#define ADT7604_RES_RES_START_REG 0x0060 +#define ADT7604_RES_RES_END_REG 0x00AF +#define LTC2983_EEPROM_KEY_REG 0x00B0 +#define LTC2983_EEPROM_READ_STATUS_REG 0x00D0 +#define LTC2983_GLOBAL_CONFIG_REG 0x00F0 +#define LTC2983_MULT_CHANNEL_START_REG 0x00F4 +#define LTC2983_MULT_CHANNEL_END_REG 0x00F7 +#define LTC2986_EEPROM_STATUS_REG 0x00F9 +#define LTC2983_MUX_CONFIG_REG 0x00FF +#define LTC2983_CHAN_ASSIGN_START_REG 0x0200 +#define LTC2983_CHAN_ASSIGN_END_REG 0x024F +#define LTC2983_CUST_SENS_TBL_START_REG 0x0250 +#define LTC2983_CUST_SENS_TBL_END_REG 0x03CF + +#define LTC2983_DIFFERENTIAL_CHAN_MIN 2 +#define LTC2983_MIN_CHANNELS_NR 1 +#define LTC2983_SLEEP 0x97 +#define LTC2983_CUSTOM_STEINHART_SIZE 24 +#define LTC2983_CUSTOM_SENSOR_ENTRY_SZ 6 +#define LTC2983_CUSTOM_STEINHART_ENTRY_SZ 4 + +#define LTC2983_EEPROM_KEY 0xA53C0F5A +#define LTC2983_EEPROM_WRITE_CMD 0x15 +#define LTC2983_EEPROM_READ_CMD 0x16 +#define LTC2983_EEPROM_STATUS_FAILURE_MASK GENMASK(3, 1) +#define LTC2983_EEPROM_READ_FAILURE_MASK GENMASK(7, 0) + +#define LTC2983_EEPROM_WRITE_TIME_MS 2600 +#define LTC2983_EEPROM_READ_TIME_MS 20 + +#define LTC2983_CHAN_ASSIGN_ADDR(chan) \ + ((((chan) - 1) * 4) + LTC2983_CHAN_ASSIGN_START_REG) +#define LTC2983_RESULT_ADDR(chan, base) \ + ((((chan) - 1) * 4) + (base)) +#define LTC2983_THERMOCOUPLE_DIFF_MASK BIT(3) +#define LTC2983_THERMOCOUPLE_SGL(x) \ + FIELD_PREP(LTC2983_THERMOCOUPLE_DIFF_MASK, x) +#define LTC2983_THERMOCOUPLE_OC_CURR_MASK GENMASK(1, 0) +#define LTC2983_THERMOCOUPLE_OC_CURR(x) \ + FIELD_PREP(LTC2983_THERMOCOUPLE_OC_CURR_MASK, x) +#define LTC2983_THERMOCOUPLE_OC_CHECK_MASK BIT(2) +#define LTC2983_THERMOCOUPLE_OC_CHECK(x) \ + FIELD_PREP(LTC2983_THERMOCOUPLE_OC_CHECK_MASK, x) + +#define LTC2983_THERMISTOR_DIFF_MASK BIT(2) +#define LTC2983_THERMISTOR_SGL(x) \ + FIELD_PREP(LTC2983_THERMISTOR_DIFF_MASK, x) +#define LTC2983_THERMISTOR_R_SHARE_MASK BIT(1) +#define LTC2983_THERMISTOR_R_SHARE(x) \ + FIELD_PREP(LTC2983_THERMISTOR_R_SHARE_MASK, x) +#define LTC2983_THERMISTOR_C_ROTATE_MASK BIT(0) +#define LTC2983_THERMISTOR_C_ROTATE(x) \ + FIELD_PREP(LTC2983_THERMISTOR_C_ROTATE_MASK, x) + +#define LTC2983_DIODE_DIFF_MASK BIT(2) +#define LTC2983_DIODE_SGL(x) \ + FIELD_PREP(LTC2983_DIODE_DIFF_MASK, x) +#define LTC2983_DIODE_3_CONV_CYCLE_MASK BIT(1) +#define LTC2983_DIODE_3_CONV_CYCLE(x) \ + FIELD_PREP(LTC2983_DIODE_3_CONV_CYCLE_MASK, x) +#define LTC2983_DIODE_AVERAGE_ON_MASK BIT(0) +#define LTC2983_DIODE_AVERAGE_ON(x) \ + FIELD_PREP(LTC2983_DIODE_AVERAGE_ON_MASK, x) + +#define LTC2983_RTD_4_WIRE_MASK BIT(3) +#define LTC2983_RTD_ROTATION_MASK BIT(1) +#define LTC2983_RTD_C_ROTATE(x) \ + FIELD_PREP(LTC2983_RTD_ROTATION_MASK, x) +#define LTC2983_RTD_KELVIN_R_SENSE_MASK GENMASK(3, 2) +#define LTC2983_RTD_N_WIRES_MASK GENMASK(3, 2) +#define LTC2983_RTD_N_WIRES(x) \ + FIELD_PREP(LTC2983_RTD_N_WIRES_MASK, x) +#define LTC2983_RTD_R_SHARE_MASK BIT(0) +#define LTC2983_RTD_R_SHARE(x) \ + FIELD_PREP(LTC2983_RTD_R_SHARE_MASK, 1) + +#define LTC2983_COMMON_HARD_FAULT_MASK GENMASK(31, 30) +#define LTC2983_COMMON_SOFT_FAULT_MASK GENMASK(27, 25) + +#define LTC2983_STATUS_START_MASK BIT(7) +#define LTC2983_STATUS_START(x) FIELD_PREP(LTC2983_STATUS_START_MASK, x) +#define LTC2983_STATUS_UP_MASK GENMASK(7, 6) +#define LTC2983_STATUS_UP(reg) FIELD_GET(LTC2983_STATUS_UP_MASK, reg) + +#define LTC2983_STATUS_CHAN_SEL_MASK GENMASK(4, 0) +#define LTC2983_STATUS_CHAN_SEL(x) \ + FIELD_PREP(LTC2983_STATUS_CHAN_SEL_MASK, x) + +#define LTC2983_TEMP_UNITS_MASK BIT(2) +#define LTC2983_TEMP_UNITS(x) FIELD_PREP(LTC2983_TEMP_UNITS_MASK, x) + +#define LTC2983_NOTCH_FREQ_MASK GENMASK(1, 0) +#define LTC2983_NOTCH_FREQ(x) FIELD_PREP(LTC2983_NOTCH_FREQ_MASK, x) + +#define LTC2983_RES_VALID_MASK BIT(24) +#define LTC2983_DATA_MASK GENMASK(23, 0) +#define LTC2983_DATA_SIGN_BIT 23 + +#define LTC2983_CHAN_TYPE_MASK GENMASK(31, 27) +#define LTC2983_CHAN_TYPE(x) FIELD_PREP(LTC2983_CHAN_TYPE_MASK, x) + +/* cold junction for thermocouples and rsense for rtd's and thermistor's */ +#define LTC2983_CHAN_ASSIGN_MASK GENMASK(26, 22) +#define LTC2983_CHAN_ASSIGN(x) FIELD_PREP(LTC2983_CHAN_ASSIGN_MASK, x) + +#define LTC2983_CUSTOM_LEN_MASK GENMASK(5, 0) +#define LTC2983_CUSTOM_LEN(x) FIELD_PREP(LTC2983_CUSTOM_LEN_MASK, x) + +#define LTC2983_CUSTOM_ADDR_MASK GENMASK(11, 6) +#define LTC2983_CUSTOM_ADDR(x) FIELD_PREP(LTC2983_CUSTOM_ADDR_MASK, x) + +#define LTC2983_THERMOCOUPLE_CFG_MASK GENMASK(21, 18) +#define LTC2983_THERMOCOUPLE_CFG(x) \ + FIELD_PREP(LTC2983_THERMOCOUPLE_CFG_MASK, x) +#define LTC2983_THERMOCOUPLE_HARD_FAULT_MASK GENMASK(31, 29) +#define LTC2983_THERMOCOUPLE_SOFT_FAULT_MASK GENMASK(28, 25) + +#define LTC2983_RTD_CFG_MASK GENMASK(21, 18) +#define LTC2983_RTD_CFG(x) FIELD_PREP(LTC2983_RTD_CFG_MASK, x) +#define LTC2983_RTD_EXC_CURRENT_MASK GENMASK(17, 14) +#define LTC2983_RTD_EXC_CURRENT(x) \ + FIELD_PREP(LTC2983_RTD_EXC_CURRENT_MASK, x) +#define LTC2983_RTD_CURVE_MASK GENMASK(13, 12) +#define LTC2983_RTD_CURVE(x) FIELD_PREP(LTC2983_RTD_CURVE_MASK, x) + +#define LTC2983_THERMISTOR_CFG_MASK GENMASK(21, 19) +#define LTC2983_THERMISTOR_CFG(x) \ + FIELD_PREP(LTC2983_THERMISTOR_CFG_MASK, x) +#define LTC2983_THERMISTOR_EXC_CURRENT_MASK GENMASK(18, 15) +#define LTC2983_THERMISTOR_EXC_CURRENT(x) \ + FIELD_PREP(LTC2983_THERMISTOR_EXC_CURRENT_MASK, x) + +#define LTC2983_DIODE_CFG_MASK GENMASK(26, 24) +#define LTC2983_DIODE_CFG(x) FIELD_PREP(LTC2983_DIODE_CFG_MASK, x) +#define LTC2983_DIODE_EXC_CURRENT_MASK GENMASK(23, 22) +#define LTC2983_DIODE_EXC_CURRENT(x) \ + FIELD_PREP(LTC2983_DIODE_EXC_CURRENT_MASK, x) +#define LTC2983_DIODE_IDEAL_FACTOR_MASK GENMASK(21, 0) +#define LTC2983_DIODE_IDEAL_FACTOR(x) \ + FIELD_PREP(LTC2983_DIODE_IDEAL_FACTOR_MASK, x) + +#define LTC2983_R_SENSE_VAL_MASK GENMASK(26, 0) +#define LTC2983_R_SENSE_VAL(x) FIELD_PREP(LTC2983_R_SENSE_VAL_MASK, x) + +#define LTC2983_ADC_SINGLE_ENDED_MASK BIT(26) +#define LTC2983_ADC_SINGLE_ENDED(x) \ + FIELD_PREP(LTC2983_ADC_SINGLE_ENDED_MASK, x) + +enum { + LTC2983_SENSOR_THERMOCOUPLE = 1, + LTC2983_SENSOR_THERMOCOUPLE_CUSTOM = 9, + LTC2983_SENSOR_RTD = 10, + LTC2983_SENSOR_RTD_CUSTOM = 18, + LTC2983_SENSOR_THERMISTOR = 19, + LTC2983_SENSOR_THERMISTOR_STEINHART = 26, + LTC2983_SENSOR_THERMISTOR_CUSTOM = 27, + LTC2983_SENSOR_DIODE = 28, + LTC2983_SENSOR_SENSE_RESISTOR = 29, + LTC2983_SENSOR_DIRECT_ADC = 30, + LTC2983_SENSOR_ACTIVE_TEMP = 31, + /* Sensor types for some parts only; map to RTD_CUSTOM/THERMISTOR_CUSTOM in HW */ + LTC2983_SENSOR_COPPER_TRACE = 32, + LTC2983_SENSOR_LEAK_DETECTOR = 33, + LTC2983_SENSOR_NUM +}; + +/* Bitmask of sensor types supported by LTC2983/LTC2984 and derivatives */ +#define LTC2983_COMMON_SENSORS \ + (GENMASK_ULL(LTC2983_SENSOR_THERMOCOUPLE_CUSTOM, LTC2983_SENSOR_THERMOCOUPLE) | \ + GENMASK_ULL(LTC2983_SENSOR_RTD_CUSTOM, LTC2983_SENSOR_RTD) | \ + GENMASK_ULL(LTC2983_SENSOR_THERMISTOR_CUSTOM, LTC2983_SENSOR_THERMISTOR) | \ + BIT_ULL(LTC2983_SENSOR_DIODE) | \ + BIT_ULL(LTC2983_SENSOR_SENSE_RESISTOR) | \ + BIT_ULL(LTC2983_SENSOR_DIRECT_ADC)) + +/* Bitmask of sensor types supported by ADT7604 */ +#define ADT7604_SENSORS \ + (GENMASK_ULL(LTC2983_SENSOR_RTD_CUSTOM - 1, LTC2983_SENSOR_RTD) | \ + GENMASK_ULL(LTC2983_SENSOR_THERMISTOR_CUSTOM - 1, LTC2983_SENSOR_THERMISTOR) | \ + BIT_ULL(LTC2983_SENSOR_SENSE_RESISTOR) | \ + BIT_ULL(LTC2983_SENSOR_COPPER_TRACE) | \ + BIT_ULL(LTC2983_SENSOR_LEAK_DETECTOR)) + +#define to_thermocouple(_sensor) \ + container_of(_sensor, struct ltc2983_thermocouple, sensor) + +#define to_rtd(_sensor) \ + container_of(_sensor, struct ltc2983_rtd, sensor) + +#define to_copper_trace(_sensor) \ + container_of(_sensor, struct ltc2983_copper_trace, sensor) + +#define to_thermistor(_sensor) \ + container_of(_sensor, struct ltc2983_thermistor, sensor) + +#define to_leak_detector(_sensor) \ + container_of(_sensor, struct ltc2983_leak_detector, sensor) + +#define to_diode(_sensor) \ + container_of(_sensor, struct ltc2983_diode, sensor) + +#define to_rsense(_sensor) \ + container_of(_sensor, struct ltc2983_rsense, sensor) + +#define to_adc(_sensor) \ + container_of(_sensor, struct ltc2983_adc, sensor) + +#define to_temp(_sensor) \ + container_of(_sensor, struct ltc2983_temp, sensor) + +struct ltc2983_chip_info { + const char *name; + unsigned int max_channels_nr; + u64 supported_sensors; + bool has_eeprom; +}; + +struct ltc2983_data { + const struct ltc2983_chip_info *info; + struct regmap *regmap; + struct spi_device *spi; + struct mutex lock; + struct completion completion; + struct iio_chan_spec *iio_chan; + struct ltc2983_sensor **sensors; + u32 mux_delay_config; + u32 filter_notch_freq; + u16 custom_table_size; + u8 num_channels; + u8 iio_channels; + /* + * DMA (thus cache coherency maintenance) may require the + * transfer buffers to live in their own cache lines. + * Holds the converted temperature + */ + __be32 temp __aligned(IIO_DMA_MINALIGN); + __be32 chan_val; + __be32 eeprom_key; +}; + +struct ltc2983_sensor { + int (*fault_handler)(const struct ltc2983_data *st, const u32 result); + int (*assign_chan)(struct ltc2983_data *st, + const struct ltc2983_sensor *sensor); + /* specifies the sensor channel */ + u32 chan; + /* sensor type */ + u32 type; + /* number of IIO channels this sensor produces */ + u8 n_iio_chan; +}; + +struct ltc2983_custom_sensor { + /* raw table sensor data */ + void *table; + size_t size; + /* address offset */ + s8 offset; + bool is_steinhart; +}; + +struct ltc2983_thermocouple { + struct ltc2983_sensor sensor; + struct ltc2983_custom_sensor *custom; + u32 sensor_config; + u32 cold_junction_chan; +}; + +struct ltc2983_rtd { + struct ltc2983_sensor sensor; + struct ltc2983_custom_sensor *custom; + u32 sensor_config; + u32 r_sense_chan; + u32 excitation_current; + u32 rtd_curve; +}; + +struct ltc2983_copper_trace { + struct ltc2983_sensor sensor; + struct ltc2983_custom_sensor *custom; + u32 r_sense_chan; + u32 excitation_current; + /* selects the <1Ω variant: bits 17:0 of the channel word are zeroed, + * disabling excitation current and custom table fields (ADT7604 + * datasheet Table 26) + */ + bool is_sub_ohm; +}; + +struct ltc2983_leak_detector { + struct ltc2983_sensor sensor; + struct ltc2983_custom_sensor *custom; + u32 r_sense_chan; + u32 excitation_current; +}; + +struct ltc2983_thermistor { + struct ltc2983_sensor sensor; + struct ltc2983_custom_sensor *custom; + u32 sensor_config; + u32 r_sense_chan; + u32 excitation_current; +}; + +struct ltc2983_diode { + struct ltc2983_sensor sensor; + u32 sensor_config; + u32 excitation_current; + u32 ideal_factor_value; +}; + +struct ltc2983_rsense { + struct ltc2983_sensor sensor; + u32 r_sense_val; +}; + +struct ltc2983_adc { + struct ltc2983_sensor sensor; + bool single_ended; +}; + +struct ltc2983_temp { + struct ltc2983_sensor sensor; + struct ltc2983_custom_sensor *custom; + bool single_ended; +}; + +/* + * Convert to Q format numbers. These number's are integers where + * the number of integer and fractional bits are specified. The resolution + * is given by 1/@resolution and tell us the number of fractional bits. For + * instance a resolution of 2^-10 means we have 10 fractional bits. + */ +static u32 __convert_to_raw(const u64 val, const u32 resolution) +{ + u64 __res = val * resolution; + + /* all values are multiplied by 1000000 to remove the fraction */ + do_div(__res, 1000000); + + return __res; +} + +static u32 __convert_to_raw_sign(const u64 val, const u32 resolution) +{ + s64 __res = -(s32)val; + + __res = __convert_to_raw(__res, resolution); + + return (u32)-__res; +} + +static int __ltc2983_fault_handler(const struct ltc2983_data *st, + const u32 result, const u32 hard_mask, + const u32 soft_mask) +{ + const struct device *dev = &st->spi->dev; + + if (result & hard_mask) { + dev_err(dev, "Invalid conversion: Sensor HARD fault\n"); + return -EIO; + } else if (result & soft_mask) { + /* just print a warning */ + dev_warn(dev, "Suspicious conversion: Sensor SOFT fault\n"); + } + + return 0; +} + +static int __ltc2983_chan_assign_common(struct ltc2983_data *st, + const struct ltc2983_sensor *sensor, + u32 chan_val) +{ + struct device *dev = &st->spi->dev; + u32 reg = LTC2983_CHAN_ASSIGN_ADDR(sensor->chan); + u32 hw_type = sensor->type; + + if (hw_type == LTC2983_SENSOR_COPPER_TRACE) + hw_type = LTC2983_SENSOR_RTD_CUSTOM; + else if (hw_type == LTC2983_SENSOR_LEAK_DETECTOR) + hw_type = LTC2983_SENSOR_THERMISTOR_CUSTOM; + + chan_val |= LTC2983_CHAN_TYPE(hw_type); + dev_dbg(dev, "Assign reg:0x%04X, val:0x%08X\n", reg, chan_val); + st->chan_val = cpu_to_be32(chan_val); + return regmap_bulk_write(st->regmap, reg, &st->chan_val, + sizeof(st->chan_val)); +} + +static int __ltc2983_chan_custom_sensor_assign(struct ltc2983_data *st, + struct ltc2983_custom_sensor *custom, + u32 *chan_val) +{ + u32 reg; + u8 mult = custom->is_steinhart ? LTC2983_CUSTOM_STEINHART_ENTRY_SZ : + LTC2983_CUSTOM_SENSOR_ENTRY_SZ; + const struct device *dev = &st->spi->dev; + /* + * custom->size holds the raw size of the table. However, when + * configuring the sensor channel, we must write the number of + * entries of the table minus 1. For steinhart sensors 0 is written + * since the size is constant! + */ + const u8 len = custom->is_steinhart ? 0 : + (custom->size / LTC2983_CUSTOM_SENSOR_ENTRY_SZ) - 1; + /* + * Check if the offset was assigned already. It should be for steinhart + * sensors. When coming from sleep, it should be assigned for all. + */ + if (custom->offset < 0) { + /* + * This needs to be done again here because, from the moment + * when this test was done (successfully) for this custom + * sensor, a steinhart sensor might have been added changing + * custom_table_size... + */ + if (st->custom_table_size + custom->size > + (LTC2983_CUST_SENS_TBL_END_REG - + LTC2983_CUST_SENS_TBL_START_REG) + 1) { + dev_err(dev, + "Not space left(%d) for new custom sensor(%zu)", + st->custom_table_size, + custom->size); + return -EINVAL; + } + + custom->offset = st->custom_table_size / + LTC2983_CUSTOM_SENSOR_ENTRY_SZ; + st->custom_table_size += custom->size; + } + + reg = (custom->offset * mult) + LTC2983_CUST_SENS_TBL_START_REG; + + *chan_val |= LTC2983_CUSTOM_LEN(len); + *chan_val |= LTC2983_CUSTOM_ADDR(custom->offset); + dev_dbg(dev, "Assign custom sensor, reg:0x%04X, off:%d, sz:%zu", + reg, custom->offset, + custom->size); + /* write custom sensor table */ + return regmap_bulk_write(st->regmap, reg, custom->table, custom->size); +} + +static struct ltc2983_custom_sensor * +__ltc2983_custom_sensor_new(struct ltc2983_data *st, const struct fwnode_handle *fn, + const char *propname, const bool is_steinhart, + const u32 resolution, const bool has_signed) +{ + struct ltc2983_custom_sensor *new_custom; + struct device *dev = &st->spi->dev; + /* + * For custom steinhart, the full u32 is taken. For all the others + * the MSB is discarded. + */ + const u8 n_size = is_steinhart ? 4 : 3; + u8 index, n_entries; + int ret; + + if (is_steinhart) + n_entries = fwnode_property_count_u32(fn, propname); + else + n_entries = fwnode_property_count_u64(fn, propname); + /* n_entries must be an even number */ + if (!n_entries || (n_entries % 2) != 0) + return dev_err_ptr_probe(dev, -EINVAL, + "Number of entries either 0 or not even\n"); + + new_custom = devm_kzalloc(dev, sizeof(*new_custom), GFP_KERNEL); + if (!new_custom) + return ERR_PTR(-ENOMEM); + + new_custom->size = n_entries * n_size; + /* check Steinhart size */ + if (is_steinhart && new_custom->size != LTC2983_CUSTOM_STEINHART_SIZE) + return dev_err_ptr_probe(dev, -EINVAL, + "Steinhart sensors size(%zu) must be %u\n", + new_custom->size, LTC2983_CUSTOM_STEINHART_SIZE); + + /* Check space on the table. */ + if (st->custom_table_size + new_custom->size > + (LTC2983_CUST_SENS_TBL_END_REG - LTC2983_CUST_SENS_TBL_START_REG) + 1) + return dev_err_ptr_probe(dev, -EINVAL, + "No space left(%d) for new custom sensor(%zu)\n", + st->custom_table_size, new_custom->size); + + /* allocate the table */ + if (is_steinhart) + new_custom->table = devm_kcalloc(dev, n_entries, sizeof(u32), GFP_KERNEL); + else + new_custom->table = devm_kcalloc(dev, n_entries, sizeof(u64), GFP_KERNEL); + if (!new_custom->table) + return ERR_PTR(-ENOMEM); + + /* + * Steinhart sensors are configured with raw values in the firmware + * node. For the other sensors we must convert the value to raw. + * The odd index's correspond to temperatures and always have 1/1024 + * of resolution. Temperatures also come in Kelvin, so signed values + * are not possible. + */ + if (is_steinhart) { + ret = fwnode_property_read_u32_array(fn, propname, new_custom->table, n_entries); + if (ret < 0) + return ERR_PTR(ret); + + cpu_to_be32_array(new_custom->table, new_custom->table, n_entries); + } else { + ret = fwnode_property_read_u64_array(fn, propname, new_custom->table, n_entries); + if (ret < 0) + return ERR_PTR(ret); + + for (index = 0; index < n_entries; index++) { + u64 temp = ((u64 *)new_custom->table)[index]; + + /* + * Users specify plain coverage percentage (0-100). Convert + * to µK so __convert_to_raw() produces the correct hardware + * encoding: P + 273.15 K. + */ + if ((index % 2) != 0 && !strcmp(propname, "adi,custom-leak-detector")) + temp = temp * 1000000 + 273150000; + + if ((index % 2) != 0) + temp = __convert_to_raw(temp, 1024); + else if (has_signed && (s64)temp < 0) + temp = __convert_to_raw_sign(temp, resolution); + else + temp = __convert_to_raw(temp, resolution); + + put_unaligned_be24(temp, new_custom->table + index * 3); + } + } + + new_custom->is_steinhart = is_steinhart; + /* + * This is done to first add all the steinhart sensors to the table, + * in order to maximize the table usage. If we mix adding steinhart + * with the other sensors, we might have to do some roundup to make + * sure that sensor_addr - 0x250(start address) is a multiple of 4 + * (for steinhart), and a multiple of 6 for all the other sensors. + * Since we have const 24 bytes for steinhart sensors and 24 is + * also a multiple of 6, we guarantee that the first non-steinhart + * sensor will sit in a correct address without the need of filling + * addresses. + */ + if (is_steinhart) { + new_custom->offset = st->custom_table_size / + LTC2983_CUSTOM_STEINHART_ENTRY_SZ; + st->custom_table_size += new_custom->size; + } else { + /* mark as unset. This is checked later on the assign phase */ + new_custom->offset = -1; + } + + return new_custom; +} + +static int ltc2983_thermocouple_fault_handler(const struct ltc2983_data *st, + const u32 result) +{ + return __ltc2983_fault_handler(st, result, + LTC2983_THERMOCOUPLE_HARD_FAULT_MASK, + LTC2983_THERMOCOUPLE_SOFT_FAULT_MASK); +} + +static int ltc2983_common_fault_handler(const struct ltc2983_data *st, + const u32 result) +{ + return __ltc2983_fault_handler(st, result, + LTC2983_COMMON_HARD_FAULT_MASK, + LTC2983_COMMON_SOFT_FAULT_MASK); +} + +static int ltc2983_thermocouple_assign_chan(struct ltc2983_data *st, + const struct ltc2983_sensor *sensor) +{ + struct ltc2983_thermocouple *thermo = to_thermocouple(sensor); + u32 chan_val; + + chan_val = LTC2983_CHAN_ASSIGN(thermo->cold_junction_chan); + chan_val |= LTC2983_THERMOCOUPLE_CFG(thermo->sensor_config); + + if (thermo->custom) { + int ret; + + ret = __ltc2983_chan_custom_sensor_assign(st, thermo->custom, + &chan_val); + if (ret) + return ret; + } + return __ltc2983_chan_assign_common(st, sensor, chan_val); +} + +static int ltc2983_rtd_assign_chan(struct ltc2983_data *st, + const struct ltc2983_sensor *sensor) +{ + struct ltc2983_rtd *rtd = to_rtd(sensor); + u32 chan_val; + + chan_val = LTC2983_CHAN_ASSIGN(rtd->r_sense_chan); + chan_val |= LTC2983_RTD_CFG(rtd->sensor_config); + chan_val |= LTC2983_RTD_EXC_CURRENT(rtd->excitation_current); + chan_val |= LTC2983_RTD_CURVE(rtd->rtd_curve); + + if (rtd->custom) { + int ret; + + ret = __ltc2983_chan_custom_sensor_assign(st, rtd->custom, + &chan_val); + if (ret) + return ret; + } + return __ltc2983_chan_assign_common(st, sensor, chan_val); +} + +static int ltc2983_copper_trace_assign_chan(struct ltc2983_data *st, + const struct ltc2983_sensor *sensor) +{ + struct ltc2983_copper_trace *ct = to_copper_trace(sensor); + u32 chan_val; + + chan_val = LTC2983_CHAN_ASSIGN(ct->r_sense_chan); + /* Sensor config bits 21:18 must be 0b1001 (ADT7604 datasheet Table 26) */ + chan_val |= LTC2983_RTD_CFG(0x9); + + if (ct->is_sub_ohm) { + chan_val &= ~GENMASK(17, 0); + } else { + int ret; + + chan_val |= LTC2983_RTD_EXC_CURRENT(ct->excitation_current); + ret = __ltc2983_chan_custom_sensor_assign(st, ct->custom, + &chan_val); + if (ret) + return ret; + } + + return __ltc2983_chan_assign_common(st, sensor, chan_val); +} + +static int ltc2983_thermistor_assign_chan(struct ltc2983_data *st, + const struct ltc2983_sensor *sensor) +{ + struct ltc2983_thermistor *thermistor = to_thermistor(sensor); + u32 chan_val; + + chan_val = LTC2983_CHAN_ASSIGN(thermistor->r_sense_chan); + chan_val |= LTC2983_THERMISTOR_CFG(thermistor->sensor_config); + chan_val |= + LTC2983_THERMISTOR_EXC_CURRENT(thermistor->excitation_current); + + if (thermistor->custom) { + int ret; + + ret = __ltc2983_chan_custom_sensor_assign(st, + thermistor->custom, + &chan_val); + if (ret) + return ret; + } + return __ltc2983_chan_assign_common(st, sensor, chan_val); +} + +static int ltc2983_leak_detector_assign_chan(struct ltc2983_data *st, + const struct ltc2983_sensor *sensor) +{ + struct ltc2983_leak_detector *ld = to_leak_detector(sensor); + u32 chan_val; + int ret; + + chan_val = LTC2983_CHAN_ASSIGN(ld->r_sense_chan); + /* bits 21:19 must be 0b001 (ADT7604 datasheet Table 38) */ + chan_val |= LTC2983_THERMISTOR_CFG(1); + chan_val |= LTC2983_THERMISTOR_EXC_CURRENT(ld->excitation_current); + + ret = __ltc2983_chan_custom_sensor_assign(st, ld->custom, &chan_val); + if (ret) + return ret; + + return __ltc2983_chan_assign_common(st, sensor, chan_val); +} + +static int ltc2983_diode_assign_chan(struct ltc2983_data *st, + const struct ltc2983_sensor *sensor) +{ + struct ltc2983_diode *diode = to_diode(sensor); + u32 chan_val; + + chan_val = LTC2983_DIODE_CFG(diode->sensor_config); + chan_val |= LTC2983_DIODE_EXC_CURRENT(diode->excitation_current); + chan_val |= LTC2983_DIODE_IDEAL_FACTOR(diode->ideal_factor_value); + + return __ltc2983_chan_assign_common(st, sensor, chan_val); +} + +static int ltc2983_r_sense_assign_chan(struct ltc2983_data *st, + const struct ltc2983_sensor *sensor) +{ + struct ltc2983_rsense *rsense = to_rsense(sensor); + u32 chan_val; + + chan_val = LTC2983_R_SENSE_VAL(rsense->r_sense_val); + + return __ltc2983_chan_assign_common(st, sensor, chan_val); +} + +static int ltc2983_adc_assign_chan(struct ltc2983_data *st, + const struct ltc2983_sensor *sensor) +{ + struct ltc2983_adc *adc = to_adc(sensor); + u32 chan_val; + + chan_val = LTC2983_ADC_SINGLE_ENDED(adc->single_ended); + + return __ltc2983_chan_assign_common(st, sensor, chan_val); +} + +static int ltc2983_temp_assign_chan(struct ltc2983_data *st, + const struct ltc2983_sensor *sensor) +{ + struct ltc2983_temp *temp = to_temp(sensor); + u32 chan_val; + int ret; + + chan_val = LTC2983_ADC_SINGLE_ENDED(temp->single_ended); + + ret = __ltc2983_chan_custom_sensor_assign(st, temp->custom, &chan_val); + if (ret) + return ret; + + return __ltc2983_chan_assign_common(st, sensor, chan_val); +} + +static struct ltc2983_sensor * +ltc2983_thermocouple_new(const struct fwnode_handle *child, struct ltc2983_data *st, + const struct ltc2983_sensor *sensor) +{ + struct device *dev = &st->spi->dev; + struct ltc2983_thermocouple *thermo; + u32 oc_current; + int ret; + + thermo = devm_kzalloc(dev, sizeof(*thermo), GFP_KERNEL); + if (!thermo) + return ERR_PTR(-ENOMEM); + + if (fwnode_property_read_bool(child, "adi,single-ended")) + thermo->sensor_config = LTC2983_THERMOCOUPLE_SGL(1); + + if (fwnode_property_present(child, "adi,sensor-oc-current-microamp")) { + ret = fwnode_property_read_u32(child, + "adi,sensor-oc-current-microamp", + &oc_current); + if (ret) + return dev_err_ptr_probe(dev, ret, + "Failed to read adi,sensor-oc-current-microamp\n"); + + switch (oc_current) { + case 10: + thermo->sensor_config |= + LTC2983_THERMOCOUPLE_OC_CURR(0); + break; + case 100: + thermo->sensor_config |= + LTC2983_THERMOCOUPLE_OC_CURR(1); + break; + case 500: + thermo->sensor_config |= + LTC2983_THERMOCOUPLE_OC_CURR(2); + break; + case 1000: + thermo->sensor_config |= + LTC2983_THERMOCOUPLE_OC_CURR(3); + break; + default: + return dev_err_ptr_probe(dev, -EINVAL, + "Invalid open circuit current:%u\n", + oc_current); + } + + thermo->sensor_config |= LTC2983_THERMOCOUPLE_OC_CHECK(1); + } + /* validate channel index */ + if (!(thermo->sensor_config & LTC2983_THERMOCOUPLE_DIFF_MASK) && + sensor->chan < LTC2983_DIFFERENTIAL_CHAN_MIN) + return dev_err_ptr_probe(dev, -EINVAL, + "Invalid channel %d for differential thermocouple\n", + sensor->chan); + + struct fwnode_handle *ref __free(fwnode_handle) = + fwnode_find_reference(child, "adi,cold-junction-handle", 0); + if (IS_ERR(ref)) { + ref = NULL; + } else { + ret = fwnode_property_read_u32(ref, "reg", &thermo->cold_junction_chan); + if (ret) + /* + * This would be caught later but we can just return + * the error right away. + */ + return dev_err_ptr_probe(dev, ret, + "Property reg must be given\n"); + } + + /* check custom sensor */ + if (sensor->type == LTC2983_SENSOR_THERMOCOUPLE_CUSTOM) { + const char *propname = "adi,custom-thermocouple"; + + thermo->custom = __ltc2983_custom_sensor_new(st, child, + propname, false, + 16384, true); + if (IS_ERR(thermo->custom)) + return ERR_CAST(thermo->custom); + } + + /* set common parameters */ + thermo->sensor.fault_handler = ltc2983_thermocouple_fault_handler; + thermo->sensor.assign_chan = ltc2983_thermocouple_assign_chan; + + return &thermo->sensor; +} + +static struct ltc2983_sensor * +ltc2983_rtd_new(const struct fwnode_handle *child, struct ltc2983_data *st, + const struct ltc2983_sensor *sensor) +{ + struct ltc2983_rtd *rtd; + int ret = 0; + struct device *dev = &st->spi->dev; + u32 excitation_current = 0, n_wires = 2; + + rtd = devm_kzalloc(dev, sizeof(*rtd), GFP_KERNEL); + if (!rtd) + return ERR_PTR(-ENOMEM); + + struct fwnode_handle *ref __free(fwnode_handle) = + fwnode_find_reference(child, "adi,rsense-handle", 0); + if (IS_ERR(ref)) + return dev_err_cast_probe(dev, ref, + "Property adi,rsense-handle missing or invalid\n"); + + ret = fwnode_property_read_u32(ref, "reg", &rtd->r_sense_chan); + if (ret) + return dev_err_ptr_probe(dev, ret, + "Property reg must be given\n"); + + if (fwnode_property_present(child, "adi,number-of-wires")) { + ret = fwnode_property_read_u32(child, "adi,number-of-wires", &n_wires); + if (ret) + return dev_err_ptr_probe(dev, ret, + "Failed to read adi,number-of-wires\n"); + + switch (n_wires) { + case 2: + rtd->sensor_config = LTC2983_RTD_N_WIRES(0); + break; + case 3: + rtd->sensor_config = LTC2983_RTD_N_WIRES(1); + break; + case 4: + rtd->sensor_config = LTC2983_RTD_N_WIRES(2); + break; + case 5: + /* 4 wires, Kelvin Rsense */ + rtd->sensor_config = LTC2983_RTD_N_WIRES(3); + break; + default: + return dev_err_ptr_probe(dev, -EINVAL, + "Invalid number of wires:%u\n", + n_wires); + } + } + + if (fwnode_property_read_bool(child, "adi,rsense-share")) { + /* Current rotation is only available with rsense sharing */ + if (fwnode_property_read_bool(child, "adi,current-rotate")) { + if (n_wires == 2 || n_wires == 3) + return dev_err_ptr_probe(dev, -EINVAL, + "Rotation not allowed for 2/3 Wire RTDs\n"); + + rtd->sensor_config |= LTC2983_RTD_C_ROTATE(1); + } else { + rtd->sensor_config |= LTC2983_RTD_R_SHARE(1); + } + } + /* + * rtd channel indexes are a bit more complicated to validate. + * For 4wire RTD with rotation, the channel selection cannot be + * >=19 since the channel + 1 is used in this configuration. + * For 4wire RTDs with kelvin rsense, the rsense channel cannot be + * <=1 since channel - 1 and channel - 2 are used. + */ + if (rtd->sensor_config & LTC2983_RTD_4_WIRE_MASK) { + /* 4-wire */ + u8 min = LTC2983_DIFFERENTIAL_CHAN_MIN, + max = st->info->max_channels_nr; + + if (rtd->sensor_config & LTC2983_RTD_ROTATION_MASK) + max = st->info->max_channels_nr - 1; + + if (((rtd->sensor_config & LTC2983_RTD_KELVIN_R_SENSE_MASK) + == LTC2983_RTD_KELVIN_R_SENSE_MASK) && + (rtd->r_sense_chan <= min)) + /* kelvin rsense*/ + return dev_err_ptr_probe(dev, -EINVAL, + "Invalid channel %d for kelvin rsense\n", + rtd->r_sense_chan); + + if (sensor->chan < min || sensor->chan > max) + return dev_err_ptr_probe(dev, -EINVAL, + "Invalid channel %d for RTD config\n", + sensor->chan); + } else { + /* same as differential case */ + if (sensor->chan < LTC2983_DIFFERENTIAL_CHAN_MIN) + return dev_err_ptr_probe(dev, -EINVAL, + "Invalid channel %d for RTD\n", + sensor->chan); + } + + /* check custom sensor */ + if (sensor->type == LTC2983_SENSOR_RTD_CUSTOM) { + rtd->custom = __ltc2983_custom_sensor_new(st, child, + "adi,custom-rtd", + false, 2048, false); + if (IS_ERR(rtd->custom)) + return ERR_CAST(rtd->custom); + } + + /* set common parameters */ + rtd->sensor.fault_handler = ltc2983_common_fault_handler; + rtd->sensor.assign_chan = ltc2983_rtd_assign_chan; + + if (fwnode_property_present(child, "adi,excitation-current-microamp")) { + ret = fwnode_property_read_u32(child, "adi,excitation-current-microamp", + &excitation_current); + if (ret) + return dev_err_ptr_probe(dev, ret, + "Failed to read adi,excitation-current-microamp\n"); + + switch (excitation_current) { + case 5: + rtd->excitation_current = 0x01; + break; + case 10: + rtd->excitation_current = 0x02; + break; + case 25: + rtd->excitation_current = 0x03; + break; + case 50: + rtd->excitation_current = 0x04; + break; + case 100: + rtd->excitation_current = 0x05; + break; + case 250: + rtd->excitation_current = 0x06; + break; + case 500: + rtd->excitation_current = 0x07; + break; + case 1000: + rtd->excitation_current = 0x08; + break; + default: + return dev_err_ptr_probe(dev, -EINVAL, + "Invalid value for excitation current(%u)\n", + excitation_current); + } + } else { + /* default to 5uA */ + rtd->excitation_current = 1; + } + + if (fwnode_property_present(child, "adi,rtd-curve")) { + ret = fwnode_property_read_u32(child, "adi,rtd-curve", &rtd->rtd_curve); + if (ret) + return dev_err_ptr_probe(dev, ret, + "Failed to read adi,rtd-curve\n"); + } + + return &rtd->sensor; +} + +static struct ltc2983_sensor * +ltc2983_thermistor_new(const struct fwnode_handle *child, struct ltc2983_data *st, + const struct ltc2983_sensor *sensor) +{ + struct ltc2983_thermistor *thermistor; + struct device *dev = &st->spi->dev; + u32 excitation_current = 0; + int ret = 0; + + thermistor = devm_kzalloc(dev, sizeof(*thermistor), GFP_KERNEL); + if (!thermistor) + return ERR_PTR(-ENOMEM); + + struct fwnode_handle *ref __free(fwnode_handle) = + fwnode_find_reference(child, "adi,rsense-handle", 0); + if (IS_ERR(ref)) + return dev_err_cast_probe(dev, ref, + "Property adi,rsense-handle missing or invalid\n"); + + ret = fwnode_property_read_u32(ref, "reg", &thermistor->r_sense_chan); + if (ret) + return dev_err_ptr_probe(dev, ret, + "rsense channel must be configured...\n"); + + if (fwnode_property_read_bool(child, "adi,single-ended")) { + thermistor->sensor_config = LTC2983_THERMISTOR_SGL(1); + } else if (fwnode_property_read_bool(child, "adi,rsense-share")) { + /* rotation is only possible if sharing rsense */ + if (fwnode_property_read_bool(child, "adi,current-rotate")) + thermistor->sensor_config = + LTC2983_THERMISTOR_C_ROTATE(1); + else + thermistor->sensor_config = + LTC2983_THERMISTOR_R_SHARE(1); + } + /* validate channel index */ + if (!(thermistor->sensor_config & LTC2983_THERMISTOR_DIFF_MASK) && + sensor->chan < LTC2983_DIFFERENTIAL_CHAN_MIN) + return dev_err_ptr_probe(dev, -EINVAL, + "Invalid channel %d for differential thermistor\n", + sensor->chan); + + /* check custom sensor */ + if (sensor->type >= LTC2983_SENSOR_THERMISTOR_STEINHART) { + bool steinhart = false; + const char *propname; + + if (sensor->type == LTC2983_SENSOR_THERMISTOR_STEINHART) { + steinhart = true; + propname = "adi,custom-steinhart"; + } else { + propname = "adi,custom-thermistor"; + } + + thermistor->custom = __ltc2983_custom_sensor_new(st, child, + propname, + steinhart, + 64, false); + if (IS_ERR(thermistor->custom)) + return ERR_CAST(thermistor->custom); + } + /* set common parameters */ + thermistor->sensor.fault_handler = ltc2983_common_fault_handler; + thermistor->sensor.assign_chan = ltc2983_thermistor_assign_chan; + + if (fwnode_property_present(child, "adi,excitation-current-nanoamp")) { + ret = fwnode_property_read_u32(child, "adi,excitation-current-nanoamp", + &excitation_current); + if (ret) + return dev_err_ptr_probe(dev, ret, + "Failed to read adi,excitation-current-nanoamp\n"); + + switch (excitation_current) { + case 0: + /* auto range */ + if (sensor->type >= LTC2983_SENSOR_THERMISTOR_STEINHART) + return dev_err_ptr_probe(dev, -EINVAL, + "Auto Range not allowed for custom sensors\n"); + + thermistor->excitation_current = 0x0c; + break; + case 250: + thermistor->excitation_current = 0x01; + break; + case 500: + thermistor->excitation_current = 0x02; + break; + case 1000: + thermistor->excitation_current = 0x03; + break; + case 5000: + thermistor->excitation_current = 0x04; + break; + case 10000: + thermistor->excitation_current = 0x05; + break; + case 25000: + thermistor->excitation_current = 0x06; + break; + case 50000: + thermistor->excitation_current = 0x07; + break; + case 100000: + thermistor->excitation_current = 0x08; + break; + case 250000: + thermistor->excitation_current = 0x09; + break; + case 500000: + thermistor->excitation_current = 0x0a; + break; + case 1000000: + thermistor->excitation_current = 0x0b; + break; + default: + return dev_err_ptr_probe(dev, -EINVAL, + "Invalid value for excitation current(%u)\n", + excitation_current); + } + } else { + /* Auto range is not allowed for custom sensors */ + if (sensor->type >= LTC2983_SENSOR_THERMISTOR_STEINHART) + /* default to 1uA */ + thermistor->excitation_current = 0x03; + else + /* default to auto-range */ + thermistor->excitation_current = 0x0c; + } + + return &thermistor->sensor; +} + +static struct ltc2983_sensor * +ltc2983_copper_trace_new(const struct fwnode_handle *child, struct ltc2983_data *st, + const struct ltc2983_sensor *sensor) +{ + struct device *dev = &st->spi->dev; + struct ltc2983_copper_trace *ct; + int ret; + + if (sensor->chan < LTC2983_DIFFERENTIAL_CHAN_MIN) + return dev_err_ptr_probe(dev, -EINVAL, + "Invalid channel %d for copper trace\n", + sensor->chan); + + ct = devm_kzalloc(dev, sizeof(*ct), GFP_KERNEL); + if (!ct) + return ERR_PTR(-ENOMEM); + + struct fwnode_handle *ref __free(fwnode_handle) = + fwnode_find_reference(child, "adi,rsense-handle", 0); + if (IS_ERR(ref)) + return dev_err_cast_probe(dev, ref, + "Property adi,rsense-handle missing or invalid\n"); + + ret = fwnode_property_read_u32(ref, "reg", &ct->r_sense_chan); + if (ret) + return dev_err_ptr_probe(dev, ret, "Property reg must be given\n"); + + ct->is_sub_ohm = fwnode_property_read_bool(child, "adi,copper-trace-sub-ohm"); + + if (ct->is_sub_ohm && fwnode_property_present(child, "adi,custom-copper-trace")) + return dev_err_ptr_probe(dev, -EINVAL, + "sub-ohm copper trace cannot have a custom table\n"); + + if (!ct->is_sub_ohm) { + u32 excitation_current = 0; + + if (!fwnode_property_present(child, "adi,custom-copper-trace")) + return dev_err_ptr_probe(dev, -EINVAL, + "adi,custom-copper-trace is required for >1 ohm copper trace\n"); + + ct->custom = __ltc2983_custom_sensor_new(st, child, "adi,custom-copper-trace", + false, 2048, false); + if (IS_ERR(ct->custom)) + return ERR_CAST(ct->custom); + + if (fwnode_property_present(child, "adi,excitation-current-microamp")) { + ret = fwnode_property_read_u32(child, "adi,excitation-current-microamp", + &excitation_current); + if (ret) + return dev_err_ptr_probe(dev, ret, + "Failed to read adi,excitation-current-microamp\n"); + + switch (excitation_current) { + case 5: + ct->excitation_current = 0x01; + break; + case 10: + ct->excitation_current = 0x02; + break; + case 25: + ct->excitation_current = 0x03; + break; + case 50: + ct->excitation_current = 0x04; + break; + case 100: + ct->excitation_current = 0x05; + break; + case 250: + ct->excitation_current = 0x06; + break; + case 500: + ct->excitation_current = 0x07; + break; + case 1000: + ct->excitation_current = 0x08; + break; + default: + return dev_err_ptr_probe(dev, -EINVAL, + "Invalid value for excitation current(%u)\n", + excitation_current); + } + } else { + /* default to 1mA per datasheet recommendation for copper trace */ + ct->excitation_current = 0x08; + } + } + + ct->sensor.fault_handler = ltc2983_common_fault_handler; + ct->sensor.assign_chan = ltc2983_copper_trace_assign_chan; + if (ct->is_sub_ohm) + ct->sensor.n_iio_chan = 1; + else + ct->sensor.n_iio_chan = 2; + + return &ct->sensor; +} + +static struct ltc2983_sensor * +ltc2983_leak_detector_new(const struct fwnode_handle *child, struct ltc2983_data *st, + const struct ltc2983_sensor *sensor) +{ + struct device *dev = &st->spi->dev; + struct ltc2983_leak_detector *ld; + int ret; + u32 excitation_current = 0; + + if (sensor->chan < LTC2983_DIFFERENTIAL_CHAN_MIN) + return dev_err_ptr_probe(dev, -EINVAL, + "Invalid channel %d for leak detector\n", + sensor->chan); + + ld = devm_kzalloc(dev, sizeof(*ld), GFP_KERNEL); + if (!ld) + return ERR_PTR(-ENOMEM); + + struct fwnode_handle *ref __free(fwnode_handle) = + fwnode_find_reference(child, "adi,rsense-handle", 0); + if (IS_ERR(ref)) + return dev_err_cast_probe(dev, ref, + "Property adi,rsense-handle missing or invalid\n"); + + ret = fwnode_property_read_u32(ref, "reg", &ld->r_sense_chan); + if (ret) + return dev_err_ptr_probe(dev, ret, + "rsense channel must be configured\n"); + + if (!fwnode_property_present(child, "adi,custom-leak-detector")) + return dev_err_ptr_probe(dev, -EINVAL, + "adi,custom-leak-detector is required for leak detectors\n"); + + ld->custom = __ltc2983_custom_sensor_new(st, child, "adi,custom-leak-detector", + false, 16, false); + if (IS_ERR(ld->custom)) + return ERR_CAST(ld->custom); + + ret = fwnode_property_read_u32(child, "adi,excitation-current-nanoamp", + &excitation_current); + if (ret) + return dev_err_ptr_probe(dev, ret, + "adi,excitation-current-nanoamp is required for leak detectors\n"); + + switch (excitation_current) { + case 250: + ld->excitation_current = 0x01; + break; + case 500: + ld->excitation_current = 0x02; + break; + case 1000: + ld->excitation_current = 0x03; + break; + case 5000: + ld->excitation_current = 0x04; + break; + case 10000: + ld->excitation_current = 0x05; + break; + case 25000: + ld->excitation_current = 0x06; + break; + case 50000: + ld->excitation_current = 0x07; + break; + case 100000: + ld->excitation_current = 0x08; + break; + case 250000: + ld->excitation_current = 0x09; + break; + case 500000: + ld->excitation_current = 0x0a; + break; + case 1000000: + ld->excitation_current = 0x0b; + break; + default: + return dev_err_ptr_probe(dev, -EINVAL, + "Invalid value for excitation current(%u)\n", + excitation_current); + } + + ld->sensor.fault_handler = ltc2983_common_fault_handler; + ld->sensor.assign_chan = ltc2983_leak_detector_assign_chan; + ld->sensor.n_iio_chan = 2; + + return &ld->sensor; +} + +static struct ltc2983_sensor * +ltc2983_diode_new(const struct fwnode_handle *child, const struct ltc2983_data *st, + const struct ltc2983_sensor *sensor) +{ + struct device *dev = &st->spi->dev; + struct ltc2983_diode *diode; + u32 temp = 0, excitation_current = 0; + int ret; + + diode = devm_kzalloc(dev, sizeof(*diode), GFP_KERNEL); + if (!diode) + return ERR_PTR(-ENOMEM); + + if (fwnode_property_read_bool(child, "adi,single-ended")) + diode->sensor_config = LTC2983_DIODE_SGL(1); + + if (fwnode_property_read_bool(child, "adi,three-conversion-cycles")) + diode->sensor_config |= LTC2983_DIODE_3_CONV_CYCLE(1); + + if (fwnode_property_read_bool(child, "adi,average-on")) + diode->sensor_config |= LTC2983_DIODE_AVERAGE_ON(1); + + /* validate channel index */ + if (!(diode->sensor_config & LTC2983_DIODE_DIFF_MASK) && + sensor->chan < LTC2983_DIFFERENTIAL_CHAN_MIN) + return dev_err_ptr_probe(dev, -EINVAL, + "Invalid channel %d for differential diode\n", + sensor->chan); + + /* set common parameters */ + diode->sensor.fault_handler = ltc2983_common_fault_handler; + diode->sensor.assign_chan = ltc2983_diode_assign_chan; + + if (fwnode_property_present(child, "adi,excitation-current-microamp")) { + ret = fwnode_property_read_u32(child, "adi,excitation-current-microamp", + &excitation_current); + if (ret) + return dev_err_ptr_probe(dev, ret, + "Failed to read adi,excitation-current-microamp\n"); + + switch (excitation_current) { + case 10: + diode->excitation_current = 0x00; + break; + case 20: + diode->excitation_current = 0x01; + break; + case 40: + diode->excitation_current = 0x02; + break; + case 80: + diode->excitation_current = 0x03; + break; + default: + return dev_err_ptr_probe(dev, -EINVAL, + "Invalid value for excitation current(%u)\n", + excitation_current); + } + } + + if (fwnode_property_present(child, "adi,ideal-factor-value")) { + ret = fwnode_property_read_u32(child, "adi,ideal-factor-value", &temp); + if (ret) + return dev_err_ptr_probe(dev, ret, + "Failed to read adi,ideal-factor-value\n"); + } + + /* 2^20 resolution */ + diode->ideal_factor_value = __convert_to_raw(temp, 1048576); + + return &diode->sensor; +} + +static struct ltc2983_sensor *ltc2983_r_sense_new(struct fwnode_handle *child, + struct ltc2983_data *st, + const struct ltc2983_sensor *sensor) +{ + struct device *dev = &st->spi->dev; + struct ltc2983_rsense *rsense; + int ret; + u32 temp; + + rsense = devm_kzalloc(dev, sizeof(*rsense), GFP_KERNEL); + if (!rsense) + return ERR_PTR(-ENOMEM); + + /* validate channel index */ + if (sensor->chan < LTC2983_DIFFERENTIAL_CHAN_MIN) + return dev_err_ptr_probe(dev, -EINVAL, + "Invalid channel %d for r_sense\n", + sensor->chan); + + ret = fwnode_property_read_u32(child, "adi,rsense-val-milli-ohms", &temp); + if (ret) + return dev_err_ptr_probe(dev, -EINVAL, + "Property adi,rsense-val-milli-ohms missing\n"); + /* + * Times 1000 because we have milli-ohms and __convert_to_raw + * expects scales of 1000000 which are used for all other + * properties. + * 2^10 resolution + */ + rsense->r_sense_val = __convert_to_raw((u64)temp * 1000, 1024); + + /* set common parameters */ + rsense->sensor.assign_chan = ltc2983_r_sense_assign_chan; + + return &rsense->sensor; +} + +static struct ltc2983_sensor *ltc2983_adc_new(struct fwnode_handle *child, + struct ltc2983_data *st, + const struct ltc2983_sensor *sensor) +{ + struct device *dev = &st->spi->dev; + struct ltc2983_adc *adc; + + adc = devm_kzalloc(dev, sizeof(*adc), GFP_KERNEL); + if (!adc) + return ERR_PTR(-ENOMEM); + + if (fwnode_property_read_bool(child, "adi,single-ended")) + adc->single_ended = true; + + if (!adc->single_ended && sensor->chan < LTC2983_DIFFERENTIAL_CHAN_MIN) + return dev_err_ptr_probe(dev, -EINVAL, + "Invalid channel %d for differential ADC\n", + sensor->chan); + + /* set common parameters */ + adc->sensor.assign_chan = ltc2983_adc_assign_chan; + adc->sensor.fault_handler = ltc2983_common_fault_handler; + + return &adc->sensor; +} + +static struct ltc2983_sensor *ltc2983_temp_new(struct fwnode_handle *child, + struct ltc2983_data *st, + const struct ltc2983_sensor *sensor) +{ + struct device *dev = &st->spi->dev; + struct ltc2983_temp *temp; + + temp = devm_kzalloc(dev, sizeof(*temp), GFP_KERNEL); + if (!temp) + return ERR_PTR(-ENOMEM); + + if (fwnode_property_read_bool(child, "adi,single-ended")) + temp->single_ended = true; + + if (!temp->single_ended && sensor->chan < LTC2983_DIFFERENTIAL_CHAN_MIN) + return dev_err_ptr_probe(dev, -EINVAL, + "Invalid channel %d for differential temp\n", + sensor->chan); + + temp->custom = __ltc2983_custom_sensor_new(st, child, "adi,custom-temp", + false, 4096, true); + if (IS_ERR(temp->custom)) + return ERR_CAST(temp->custom); + + /* set common parameters */ + temp->sensor.assign_chan = ltc2983_temp_assign_chan; + temp->sensor.fault_handler = ltc2983_common_fault_handler; + + return &temp->sensor; +} + +static int ltc2983_chan_read(struct ltc2983_data *st, + const struct ltc2983_sensor *sensor, + u32 base_reg, int *val) +{ + struct device *dev = &st->spi->dev; + u32 start_conversion = 0; + int ret; + unsigned long time; + + start_conversion = LTC2983_STATUS_START(true); + start_conversion |= LTC2983_STATUS_CHAN_SEL(sensor->chan); + dev_dbg(dev, "Start conversion on channel:%d, status:%02X\n", + sensor->chan, start_conversion); + reinit_completion(&st->completion); + /* start conversion */ + ret = regmap_write(st->regmap, LTC2983_STATUS_REG, start_conversion); + if (ret) + return ret; + /* + * wait for conversion to complete. + * 300 ms should be more than enough to complete the conversion. + * Depending on the sensor configuration, there are 2/3 conversions + * cycles of 82ms. + */ + time = wait_for_completion_timeout(&st->completion, + msecs_to_jiffies(300)); + if (!time) { + dev_warn(dev, "Conversion timed out\n"); + return -ETIMEDOUT; + } + + /* read the converted data */ + ret = regmap_bulk_read(st->regmap, LTC2983_RESULT_ADDR(sensor->chan, base_reg), + &st->temp, sizeof(st->temp)); + if (ret) + return ret; + + *val = __be32_to_cpu(st->temp); + + if (base_reg == ADT7604_RES_RES_START_REG) { + /* + * Resistance result register gives a plain unsigned value, + * D31 is always 0, no valid bit, no fault bits. Read bits[30:0] + * directly — the temperature result format does not apply here. + */ + *val &= GENMASK(30, 0); + return 0; + } + + if (!(LTC2983_RES_VALID_MASK & *val)) { + dev_err(dev, "Invalid conversion detected\n"); + return -EIO; + } + + ret = sensor->fault_handler(st, *val); + if (ret) + return ret; + + *val = sign_extend32((*val) & LTC2983_DATA_MASK, LTC2983_DATA_SIGN_BIT); + return 0; +} + +static int ltc2983_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ltc2983_data *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + int ret; + + /* sanity check */ + if (chan->address >= st->num_channels) { + dev_err(dev, "Invalid channel address: %ld\n", chan->address); + return -EINVAL; + } + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&st->lock); + switch (chan->type) { + case IIO_RESISTANCE: + ret = ltc2983_chan_read(st, st->sensors[chan->address], + ADT7604_RES_RES_START_REG, val); + break; + default: + ret = ltc2983_chan_read(st, st->sensors[chan->address], + LTC2983_TEMP_RES_START_REG, val); + break; + } + mutex_unlock(&st->lock); + return ret ?: IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + /* value in milli degrees */ + *val = 1000; + /* 2^10 */ + *val2 = 1024; + return IIO_VAL_FRACTIONAL; + case IIO_VOLTAGE: + /* value in millivolt */ + *val = 1000; + /* 2^21 */ + *val2 = 2097152; + return IIO_VAL_FRACTIONAL; + case IIO_RESISTANCE: + case IIO_COVERAGE: + /* value in ohm/percent */ + *val = 1; + /* 2^10 */ + *val2 = 1024; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + } + + return -EINVAL; +} + +static int ltc2983_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int writeval, + unsigned int *readval) +{ + struct ltc2983_data *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); +} + +static irqreturn_t ltc2983_irq_handler(int irq, void *data) +{ + struct ltc2983_data *st = data; + + complete(&st->completion); + return IRQ_HANDLED; +} + +#define LTC2983_CHAN(__type, index, __address) ({ \ + struct iio_chan_spec __chan = { \ + .type = __type, \ + .indexed = 1, \ + .channel = index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .address = __address, \ + }; \ + __chan; \ +}) + +static int ltc2983_parse_fw(struct ltc2983_data *st) +{ + struct device *dev = &st->spi->dev; + int ret, chan = 0, channel_avail_mask = 0; + + device_property_read_u32(dev, "adi,mux-delay-config-us", &st->mux_delay_config); + + device_property_read_u32(dev, "adi,filter-notch-freq", &st->filter_notch_freq); + + st->num_channels = device_get_child_node_count(dev); + if (!st->num_channels) + return dev_err_probe(dev, -EINVAL, + "At least one channel must be given!\n"); + + st->sensors = devm_kcalloc(dev, st->num_channels, sizeof(*st->sensors), + GFP_KERNEL); + if (!st->sensors) + return -ENOMEM; + + st->iio_channels = 0; + device_for_each_child_node_scoped(dev, child) { + struct ltc2983_sensor sensor; + + ret = fwnode_property_read_u32(child, "reg", &sensor.chan); + if (ret) + return dev_err_probe(dev, ret, + "reg property must given for child nodes\n"); + + /* check if we have a valid channel */ + if (sensor.chan < LTC2983_MIN_CHANNELS_NR || + sensor.chan > st->info->max_channels_nr) + return dev_err_probe(dev, -EINVAL, + "channel:%d must be from %u to %u\n", + sensor.chan, + LTC2983_MIN_CHANNELS_NR, + st->info->max_channels_nr); + + if (channel_avail_mask & BIT(sensor.chan)) + return dev_err_probe(dev, -EINVAL, + "channel:%d already in use\n", + sensor.chan); + + ret = fwnode_property_read_u32(child, "adi,sensor-type", &sensor.type); + if (ret) + return dev_err_probe(dev, ret, + "adi,sensor-type property must given for child nodes\n"); + + if (sensor.type >= LTC2983_SENSOR_NUM || + !(st->info->supported_sensors & BIT_ULL(sensor.type))) + return dev_err_probe(dev, -EINVAL, + "sensor type %d not supported on %s\n", + sensor.type, st->info->name); + + dev_dbg(dev, "Create new sensor, type %u, channel %u", + sensor.type, sensor.chan); + + if (sensor.type >= LTC2983_SENSOR_THERMOCOUPLE && + sensor.type <= LTC2983_SENSOR_THERMOCOUPLE_CUSTOM) { + st->sensors[chan] = ltc2983_thermocouple_new(child, st, + &sensor); + } else if (sensor.type >= LTC2983_SENSOR_RTD && + sensor.type <= LTC2983_SENSOR_RTD_CUSTOM) { + st->sensors[chan] = ltc2983_rtd_new(child, st, &sensor); + } else if (sensor.type >= LTC2983_SENSOR_THERMISTOR && + sensor.type <= LTC2983_SENSOR_THERMISTOR_CUSTOM) { + st->sensors[chan] = ltc2983_thermistor_new(child, st, + &sensor); + } else if (sensor.type == LTC2983_SENSOR_DIODE) { + st->sensors[chan] = ltc2983_diode_new(child, st, + &sensor); + } else if (sensor.type == LTC2983_SENSOR_SENSE_RESISTOR) { + st->sensors[chan] = ltc2983_r_sense_new(child, st, + &sensor); + } else if (sensor.type == LTC2983_SENSOR_DIRECT_ADC) { + st->sensors[chan] = ltc2983_adc_new(child, st, &sensor); + } else if (sensor.type == LTC2983_SENSOR_ACTIVE_TEMP) { + st->sensors[chan] = ltc2983_temp_new(child, st, &sensor); + } else if (sensor.type == LTC2983_SENSOR_COPPER_TRACE) { + st->sensors[chan] = ltc2983_copper_trace_new(child, st, &sensor); + } else if (sensor.type == LTC2983_SENSOR_LEAK_DETECTOR) { + st->sensors[chan] = ltc2983_leak_detector_new(child, st, &sensor); + } else { + return dev_err_probe(dev, -EINVAL, + "Unknown sensor type %d\n", + sensor.type); + } + + if (IS_ERR(st->sensors[chan])) + return dev_err_probe(dev, PTR_ERR(st->sensors[chan]), + "Failed to create sensor\n"); + + /* set generic sensor parameters */ + st->sensors[chan]->chan = sensor.chan; + st->sensors[chan]->type = sensor.type; + + /* + * Dedicated functions set n_iio_chan themselves; for all other + * sensor types rsense produces 0 channels, everything else 1. + */ + if (!st->sensors[chan]->n_iio_chan) { + if (sensor.type != LTC2983_SENSOR_SENSE_RESISTOR) + st->sensors[chan]->n_iio_chan = 1; + } + st->iio_channels += st->sensors[chan]->n_iio_chan; + + channel_avail_mask |= BIT(sensor.chan); + chan++; + } + + return 0; +} + +static int ltc2983_eeprom_cmd(struct ltc2983_data *st, unsigned int cmd, + unsigned int wait_time, unsigned int status_reg, + unsigned long status_fail_mask) +{ + struct device *dev = &st->spi->dev; + unsigned long time; + unsigned int val; + int ret; + + ret = regmap_bulk_write(st->regmap, LTC2983_EEPROM_KEY_REG, + &st->eeprom_key, sizeof(st->eeprom_key)); + if (ret) + return ret; + + reinit_completion(&st->completion); + + ret = regmap_write(st->regmap, LTC2983_STATUS_REG, + LTC2983_STATUS_START(true) | cmd); + if (ret) + return ret; + + time = wait_for_completion_timeout(&st->completion, + msecs_to_jiffies(wait_time)); + if (!time) + return dev_err_probe(dev, -ETIMEDOUT, + "EEPROM command timed out\n"); + + ret = regmap_read(st->regmap, status_reg, &val); + if (ret) + return ret; + + if (val & status_fail_mask) + return dev_err_probe(dev, -EINVAL, + "EEPROM command failed: 0x%02X\n", val); + + return 0; +} + +static int ltc2983_setup(struct ltc2983_data *st, bool assign_iio) +{ + struct device *dev = &st->spi->dev; + u32 iio_chan_t = 0, iio_chan_v = 0, iio_chan_r = 0, iio_chan_c = 0; + u32 chan, iio_idx = 0, status; + int ret; + + /* make sure the device is up: start bit (7) is 0 and done bit (6) is 1 */ + ret = regmap_read_poll_timeout(st->regmap, LTC2983_STATUS_REG, status, + LTC2983_STATUS_UP(status) == 1, 25000, + 25000 * 10); + if (ret) + return dev_err_probe(dev, ret, "Device startup timed out\n"); + + ret = regmap_update_bits(st->regmap, LTC2983_GLOBAL_CONFIG_REG, + LTC2983_NOTCH_FREQ_MASK, + LTC2983_NOTCH_FREQ(st->filter_notch_freq)); + if (ret) + return ret; + + ret = regmap_write(st->regmap, LTC2983_MUX_CONFIG_REG, + st->mux_delay_config); + if (ret) + return ret; + + if (st->info->has_eeprom && !assign_iio) { + ret = ltc2983_eeprom_cmd(st, LTC2983_EEPROM_READ_CMD, + LTC2983_EEPROM_READ_TIME_MS, + LTC2983_EEPROM_READ_STATUS_REG, + LTC2983_EEPROM_READ_FAILURE_MASK); + if (!ret) + return 0; + } + + for (chan = 0; chan < st->num_channels; chan++) { + u32 chan_type = 0, *iio_chan; + + ret = st->sensors[chan]->assign_chan(st, st->sensors[chan]); + if (ret) + return ret; + /* + * The assign_iio flag is necessary for when the device is + * coming out of sleep. In that case, we just need to + * re-configure the device channels. + * We also don't assign iio channels for rsense. + */ + if (st->sensors[chan]->type == LTC2983_SENSOR_SENSE_RESISTOR || + !assign_iio) + continue; + + /* assign iio channel */ + switch (st->sensors[chan]->type) { + case LTC2983_SENSOR_COPPER_TRACE: + if (st->sensors[chan]->n_iio_chan == 1) { + /* sub-ohm copper traces produce only a resistance result */ + st->iio_chan[iio_idx++] = + LTC2983_CHAN(IIO_RESISTANCE, iio_chan_r++, chan); + } else { + st->iio_chan[iio_idx++] = + LTC2983_CHAN(IIO_TEMP, iio_chan_t++, chan); + st->iio_chan[iio_idx++] = + LTC2983_CHAN(IIO_RESISTANCE, iio_chan_r++, chan); + } + continue; + case LTC2983_SENSOR_LEAK_DETECTOR: + st->iio_chan[iio_idx++] = + LTC2983_CHAN(IIO_COVERAGE, iio_chan_c++, chan); + st->iio_chan[iio_idx++] = + LTC2983_CHAN(IIO_RESISTANCE, iio_chan_r++, chan); + continue; + case LTC2983_SENSOR_DIRECT_ADC: + chan_type = IIO_VOLTAGE; + iio_chan = &iio_chan_v; + break; + default: + chan_type = IIO_TEMP; + iio_chan = &iio_chan_t; + break; + } + + /* + * add chan as the iio .address so that, we can directly + * reference the sensor given the iio_chan_spec + */ + st->iio_chan[iio_idx++] = LTC2983_CHAN(chan_type, (*iio_chan)++, + chan); + } + + return 0; +} + +static const struct regmap_range ltc2983_reg_ranges[] = { + regmap_reg_range(LTC2983_STATUS_REG, LTC2983_STATUS_REG), + regmap_reg_range(LTC2983_TEMP_RES_START_REG, LTC2983_TEMP_RES_END_REG), + regmap_reg_range(ADT7604_RES_RES_START_REG, ADT7604_RES_RES_END_REG), + regmap_reg_range(LTC2983_EEPROM_KEY_REG, LTC2983_EEPROM_KEY_REG), + regmap_reg_range(LTC2983_EEPROM_READ_STATUS_REG, + LTC2983_EEPROM_READ_STATUS_REG), + regmap_reg_range(LTC2983_GLOBAL_CONFIG_REG, LTC2983_GLOBAL_CONFIG_REG), + regmap_reg_range(LTC2983_MULT_CHANNEL_START_REG, + LTC2983_MULT_CHANNEL_END_REG), + regmap_reg_range(LTC2986_EEPROM_STATUS_REG, LTC2986_EEPROM_STATUS_REG), + regmap_reg_range(LTC2983_MUX_CONFIG_REG, LTC2983_MUX_CONFIG_REG), + regmap_reg_range(LTC2983_CHAN_ASSIGN_START_REG, + LTC2983_CHAN_ASSIGN_END_REG), + regmap_reg_range(LTC2983_CUST_SENS_TBL_START_REG, + LTC2983_CUST_SENS_TBL_END_REG), +}; + +static const struct regmap_access_table ltc2983_reg_table = { + .yes_ranges = ltc2983_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(ltc2983_reg_ranges), +}; + +/* + * The reg_bits are actually 12 but the device needs the first *complete* + * byte for the command (R/W). + */ +static const struct regmap_config ltc2983_regmap_config = { + .reg_bits = 24, + .val_bits = 8, + .wr_table = <c2983_reg_table, + .rd_table = <c2983_reg_table, + .read_flag_mask = GENMASK(1, 0), + .write_flag_mask = BIT(1), +}; + +static const struct iio_info ltc2983_iio_info = { + .read_raw = ltc2983_read_raw, + .debugfs_reg_access = ltc2983_reg_access, +}; + +static int ltc2983_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct ltc2983_data *st; + struct iio_dev *indio_dev; + struct gpio_desc *gpio; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->info = spi_get_device_match_data(spi); + if (!st->info) + return -ENODEV; + + st->regmap = devm_regmap_init_spi(spi, <c2983_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "Failed to initialize regmap\n"); + + mutex_init(&st->lock); + init_completion(&st->completion); + st->spi = spi; + st->eeprom_key = cpu_to_be32(LTC2983_EEPROM_KEY); + spi_set_drvdata(spi, st); + + ret = ltc2983_parse_fw(st); + if (ret) + return ret; + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return ret; + + gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(gpio)) + return PTR_ERR(gpio); + + if (gpio) { + /* bring the device out of reset */ + usleep_range(1000, 1200); + gpiod_set_value_cansleep(gpio, 0); + } + + st->iio_chan = devm_kzalloc(dev, + st->iio_channels * sizeof(*st->iio_chan), + GFP_KERNEL); + if (!st->iio_chan) + return -ENOMEM; + + ret = ltc2983_setup(st, true); + if (ret) + return ret; + + ret = devm_request_irq(dev, spi->irq, ltc2983_irq_handler, + IRQF_TRIGGER_RISING, st->info->name, st); + if (ret) + return ret; + + if (st->info->has_eeprom) { + ret = ltc2983_eeprom_cmd(st, LTC2983_EEPROM_WRITE_CMD, + LTC2983_EEPROM_WRITE_TIME_MS, + LTC2986_EEPROM_STATUS_REG, + LTC2983_EEPROM_STATUS_FAILURE_MASK); + if (ret) + return ret; + } + + indio_dev->name = st->info->name; + indio_dev->num_channels = st->iio_channels; + indio_dev->channels = st->iio_chan; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = <c2983_iio_info; + + return devm_iio_device_register(dev, indio_dev); +} + +static int ltc2983_resume(struct device *dev) +{ + struct ltc2983_data *st = spi_get_drvdata(to_spi_device(dev)); + int dummy; + + /* dummy read to bring the device out of sleep */ + regmap_read(st->regmap, LTC2983_STATUS_REG, &dummy); + /* we need to re-assign the channels */ + return ltc2983_setup(st, false); +} + +static int ltc2983_suspend(struct device *dev) +{ + struct ltc2983_data *st = spi_get_drvdata(to_spi_device(dev)); + + return regmap_write(st->regmap, LTC2983_STATUS_REG, LTC2983_SLEEP); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ltc2983_pm_ops, ltc2983_suspend, + ltc2983_resume); + +static const struct ltc2983_chip_info adt7604_chip_info_data = { + .name = "adt7604", + .max_channels_nr = 20, + .has_eeprom = true, + .supported_sensors = ADT7604_SENSORS, +}; + +static const struct ltc2983_chip_info ltc2983_chip_info_data = { + .name = "ltc2983", + .max_channels_nr = 20, + .supported_sensors = LTC2983_COMMON_SENSORS, +}; + +static const struct ltc2983_chip_info ltc2984_chip_info_data = { + .name = "ltc2984", + .max_channels_nr = 20, + .has_eeprom = true, + .supported_sensors = LTC2983_COMMON_SENSORS, +}; + +static const struct ltc2983_chip_info ltc2986_chip_info_data = { + .name = "ltc2986", + .max_channels_nr = 10, + .has_eeprom = true, + .supported_sensors = LTC2983_COMMON_SENSORS | BIT_ULL(LTC2983_SENSOR_ACTIVE_TEMP), +}; + +static const struct ltc2983_chip_info ltm2985_chip_info_data = { + .name = "ltm2985", + .max_channels_nr = 10, + .has_eeprom = true, + .supported_sensors = LTC2983_COMMON_SENSORS | BIT_ULL(LTC2983_SENSOR_ACTIVE_TEMP), +}; + +static const struct spi_device_id ltc2983_id_table[] = { + { .name = "adt7604", .driver_data = (kernel_ulong_t)&adt7604_chip_info_data }, + { .name = "ltc2983", .driver_data = (kernel_ulong_t)<c2983_chip_info_data }, + { .name = "ltc2984", .driver_data = (kernel_ulong_t)<c2984_chip_info_data }, + { .name = "ltc2986", .driver_data = (kernel_ulong_t)<c2986_chip_info_data }, + { .name = "ltm2985", .driver_data = (kernel_ulong_t)<m2985_chip_info_data }, + { } +}; +MODULE_DEVICE_TABLE(spi, ltc2983_id_table); + +static const struct of_device_id ltc2983_of_match[] = { + { .compatible = "adi,adt7604", .data = &adt7604_chip_info_data }, + { .compatible = "adi,ltc2983", .data = <c2983_chip_info_data }, + { .compatible = "adi,ltc2984", .data = <c2984_chip_info_data }, + { .compatible = "adi,ltc2986", .data = <c2986_chip_info_data }, + { .compatible = "adi,ltm2985", .data = <m2985_chip_info_data }, + { } +}; +MODULE_DEVICE_TABLE(of, ltc2983_of_match); + +static struct spi_driver ltc2983_driver = { + .driver = { + .name = "ltc2983", + .of_match_table = ltc2983_of_match, + .pm = pm_sleep_ptr(<c2983_pm_ops), + }, + .probe = ltc2983_probe, + .id_table = ltc2983_id_table, +}; + +module_spi_driver(ltc2983_driver); + +MODULE_AUTHOR("Nuno Sa "); +MODULE_DESCRIPTION("Analog Devices LTC2983 SPI Temperature sensors"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/temperature/max30208.c b/drivers/iio/temperature/max30208.c new file mode 100644 index 000000000..6172abc6d --- /dev/null +++ b/drivers/iio/temperature/max30208.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* + * Copyright (c) Rajat Khandelwal + * + * Maxim MAX30208 digital temperature sensor with 0.1°C accuracy + * (7-bit I2C slave address (0x50 - 0x53)) + */ + +#include +#include +#include +#include +#include +#include + +#define MAX30208_STATUS 0x00 +#define MAX30208_STATUS_TEMP_RDY BIT(0) +#define MAX30208_INT_ENABLE 0x01 +#define MAX30208_INT_ENABLE_TEMP_RDY BIT(0) + +#define MAX30208_FIFO_OVF_CNTR 0x06 +#define MAX30208_FIFO_DATA_CNTR 0x07 +#define MAX30208_FIFO_DATA 0x08 + +#define MAX30208_FIFO_CONFIG 0x0a +#define MAX30208_FIFO_CONFIG_RO BIT(1) + +#define MAX30208_SYSTEM_CTRL 0x0c +#define MAX30208_SYSTEM_CTRL_RESET 0x01 + +#define MAX30208_TEMP_SENSOR_SETUP 0x14 +#define MAX30208_TEMP_SENSOR_SETUP_CONV BIT(0) + +struct max30208_data { + struct i2c_client *client; + struct mutex lock; /* Lock to prevent concurrent reads of temperature readings */ +}; + +static const struct iio_chan_spec max30208_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +/** + * max30208_request() - Request a reading + * @data: Struct comprising member elements of the device + * + * Requests a reading from the device and waits until the conversion is ready. + */ +static int max30208_request(struct max30208_data *data) +{ + /* + * Sensor can take up to 500 ms to respond so execute a total of + * 10 retries to give the device sufficient time. + */ + int retries = 10; + u8 regval; + int ret; + + ret = i2c_smbus_read_byte_data(data->client, MAX30208_TEMP_SENSOR_SETUP); + if (ret < 0) + return ret; + + regval = ret | MAX30208_TEMP_SENSOR_SETUP_CONV; + + ret = i2c_smbus_write_byte_data(data->client, MAX30208_TEMP_SENSOR_SETUP, regval); + if (ret) + return ret; + + while (retries--) { + ret = i2c_smbus_read_byte_data(data->client, MAX30208_STATUS); + if (ret < 0) + return ret; + + if (ret & MAX30208_STATUS_TEMP_RDY) + return 0; + + msleep(50); + } + dev_err(&data->client->dev, "Temperature conversion failed\n"); + + return -ETIMEDOUT; +} + +static int max30208_update_temp(struct max30208_data *data) +{ + u8 data_count; + int ret; + + mutex_lock(&data->lock); + + ret = max30208_request(data); + if (ret) + goto unlock; + + ret = i2c_smbus_read_byte_data(data->client, MAX30208_FIFO_OVF_CNTR); + if (ret < 0) + goto unlock; + else if (!ret) { + ret = i2c_smbus_read_byte_data(data->client, MAX30208_FIFO_DATA_CNTR); + if (ret < 0) + goto unlock; + + data_count = ret; + } else + data_count = 1; + + while (data_count) { + ret = i2c_smbus_read_word_swapped(data->client, MAX30208_FIFO_DATA); + if (ret < 0) + goto unlock; + + data_count--; + } + +unlock: + mutex_unlock(&data->lock); + return ret; +} + +/** + * max30208_config_setup() - Set up FIFO configuration register + * @data: Struct comprising member elements of the device + * + * Sets the rollover bit to '1' to enable overwriting FIFO during overflow. + */ +static int max30208_config_setup(struct max30208_data *data) +{ + u8 regval; + int ret; + + ret = i2c_smbus_read_byte_data(data->client, MAX30208_FIFO_CONFIG); + if (ret < 0) + return ret; + + regval = ret | MAX30208_FIFO_CONFIG_RO; + + ret = i2c_smbus_write_byte_data(data->client, MAX30208_FIFO_CONFIG, regval); + if (ret) + return ret; + + return 0; +} + +static int max30208_read(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max30208_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = max30208_update_temp(data); + if (ret < 0) + return ret; + + *val = sign_extend32(ret, 15); + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = 5; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static const struct iio_info max30208_info = { + .read_raw = max30208_read, +}; + +static int max30208_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct max30208_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = i2c; + mutex_init(&data->lock); + + indio_dev->name = "max30208"; + indio_dev->channels = max30208_channels; + indio_dev->num_channels = ARRAY_SIZE(max30208_channels); + indio_dev->info = &max30208_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = i2c_smbus_write_byte_data(data->client, MAX30208_SYSTEM_CTRL, + MAX30208_SYSTEM_CTRL_RESET); + if (ret) { + dev_err(dev, "Failure in performing reset\n"); + return ret; + } + + msleep(50); + + ret = max30208_config_setup(data); + if (ret) + return ret; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) { + dev_err(dev, "Failed to register IIO device\n"); + return ret; + } + + return 0; +} + +static const struct i2c_device_id max30208_id_table[] = { + { .name = "max30208" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max30208_id_table); + +static const struct acpi_device_id max30208_acpi_match[] = { + { "MAX30208" }, + { } +}; +MODULE_DEVICE_TABLE(acpi, max30208_acpi_match); + +static const struct of_device_id max30208_of_match[] = { + { .compatible = "maxim,max30208" }, + { } +}; +MODULE_DEVICE_TABLE(of, max30208_of_match); + +static struct i2c_driver max30208_driver = { + .driver = { + .name = "max30208", + .of_match_table = max30208_of_match, + .acpi_match_table = max30208_acpi_match, + }, + .probe = max30208_probe, + .id_table = max30208_id_table, +}; +module_i2c_driver(max30208_driver); + +MODULE_AUTHOR("Rajat Khandelwal "); +MODULE_DESCRIPTION("Maxim MAX30208 digital temperature sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/temperature/max31856.c b/drivers/iio/temperature/max31856.c new file mode 100644 index 000000000..061f6fe6d --- /dev/null +++ b/drivers/iio/temperature/max31856.c @@ -0,0 +1,488 @@ +// SPDX-License-Identifier: GPL-2.0 +/* max31856.c + * + * Maxim MAX31856 thermocouple sensor driver + * + * Copyright (C) 2018-2019 Rockwell Collins + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* + * The MSB of the register value determines whether the following byte will + * be written or read. If it is 0, one or more byte reads will follow. + */ +#define MAX31856_RD_WR_BIT BIT(7) + +#define MAX31856_CR0_AUTOCONVERT BIT(7) +#define MAX31856_CR0_1SHOT BIT(6) +#define MAX31856_CR0_OCFAULT BIT(4) +#define MAX31856_CR0_OCFAULT_MASK GENMASK(5, 4) +#define MAX31856_CR0_FILTER_50HZ BIT(0) +#define MAX31856_AVERAGING_MASK GENMASK(6, 4) +#define MAX31856_AVERAGING_SHIFT 4 +#define MAX31856_TC_TYPE_MASK GENMASK(3, 0) +#define MAX31856_FAULT_OVUV BIT(1) +#define MAX31856_FAULT_OPEN BIT(0) + +/* The MAX31856 registers */ +#define MAX31856_CR0_REG 0x00 +#define MAX31856_CR1_REG 0x01 +#define MAX31856_MASK_REG 0x02 +#define MAX31856_CJHF_REG 0x03 +#define MAX31856_CJLF_REG 0x04 +#define MAX31856_LTHFTH_REG 0x05 +#define MAX31856_LTHFTL_REG 0x06 +#define MAX31856_LTLFTH_REG 0x07 +#define MAX31856_LTLFTL_REG 0x08 +#define MAX31856_CJTO_REG 0x09 +#define MAX31856_CJTH_REG 0x0A +#define MAX31856_CJTL_REG 0x0B +#define MAX31856_LTCBH_REG 0x0C +#define MAX31856_LTCBM_REG 0x0D +#define MAX31856_LTCBL_REG 0x0E +#define MAX31856_SR_REG 0x0F + +static const struct iio_chan_spec max31856_channels[] = { + { /* Thermocouple Temperature */ + .type = IIO_TEMP, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_THERMOCOUPLE_TYPE), + .info_mask_shared_by_type = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) + }, + { /* Cold Junction Temperature */ + .type = IIO_TEMP, + .channel2 = IIO_MOD_TEMP_AMBIENT, + .modified = 1, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_type = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) + }, +}; + +struct max31856_data { + struct spi_device *spi; + u32 thermocouple_type; + bool filter_50hz; + int averaging; +}; + +static const char max31856_tc_types[] = { + 'B', 'E', 'J', 'K', 'N', 'R', 'S', 'T' +}; + +static int max31856_read(struct max31856_data *data, u8 reg, + u8 val[], unsigned int read_size) +{ + return spi_write_then_read(data->spi, ®, 1, val, read_size); +} + +static int max31856_write(struct max31856_data *data, u8 reg, + unsigned int val) +{ + u8 buf[2]; + + buf[0] = reg | (MAX31856_RD_WR_BIT); + buf[1] = val; + + return spi_write(data->spi, buf, 2); +} + +static int max31856_init(struct max31856_data *data) +{ + int ret; + u8 reg_cr0_val, reg_cr1_val; + + /* Start by changing to Off mode before making changes as + * some settings are recommended to be set only when the device + * is off + */ + ret = max31856_read(data, MAX31856_CR0_REG, ®_cr0_val, 1); + if (ret) + return ret; + + reg_cr0_val &= ~MAX31856_CR0_AUTOCONVERT; + ret = max31856_write(data, MAX31856_CR0_REG, reg_cr0_val); + if (ret) + return ret; + + /* Set thermocouple type based on dts property */ + ret = max31856_read(data, MAX31856_CR1_REG, ®_cr1_val, 1); + if (ret) + return ret; + + reg_cr1_val &= ~MAX31856_TC_TYPE_MASK; + reg_cr1_val |= data->thermocouple_type; + + reg_cr1_val &= ~MAX31856_AVERAGING_MASK; + reg_cr1_val |= data->averaging << MAX31856_AVERAGING_SHIFT; + + ret = max31856_write(data, MAX31856_CR1_REG, reg_cr1_val); + if (ret) + return ret; + + /* + * Enable Open circuit fault detection + * Read datasheet for more information: Table 4. + * Value 01 means : Enabled (Once every 16 conversions) + */ + reg_cr0_val &= ~MAX31856_CR0_OCFAULT_MASK; + reg_cr0_val |= MAX31856_CR0_OCFAULT; + + /* Set Auto Conversion Mode */ + reg_cr0_val &= ~MAX31856_CR0_1SHOT; + reg_cr0_val |= MAX31856_CR0_AUTOCONVERT; + + if (data->filter_50hz) + reg_cr0_val |= MAX31856_CR0_FILTER_50HZ; + else + reg_cr0_val &= ~MAX31856_CR0_FILTER_50HZ; + + return max31856_write(data, MAX31856_CR0_REG, reg_cr0_val); +} + +static int max31856_thermocouple_read(struct max31856_data *data, + struct iio_chan_spec const *chan, + int *val) +{ + int ret, offset_cjto; + u8 reg_val[3]; + + switch (chan->channel2) { + case IIO_NO_MOD: + /* + * Multibyte Read + * MAX31856_LTCBH_REG, MAX31856_LTCBM_REG, MAX31856_LTCBL_REG + */ + ret = max31856_read(data, MAX31856_LTCBH_REG, reg_val, 3); + if (ret) + return ret; + /* Skip last 5 dead bits of LTCBL */ + *val = get_unaligned_be24(®_val[0]) >> 5; + /* Check 7th bit of LTCBH reg. value for sign*/ + if (reg_val[0] & 0x80) + *val -= 0x80000; + break; + + case IIO_MOD_TEMP_AMBIENT: + /* + * Multibyte Read + * MAX31856_CJTO_REG, MAX31856_CJTH_REG, MAX31856_CJTL_REG + */ + ret = max31856_read(data, MAX31856_CJTO_REG, reg_val, 3); + if (ret) + return ret; + /* Get Cold Junction Temp. offset register value */ + offset_cjto = reg_val[0]; + /* Get CJTH and CJTL value and skip last 2 dead bits of CJTL */ + *val = get_unaligned_be16(®_val[1]) >> 2; + /* As per datasheet add offset into CJTH and CJTL */ + *val += offset_cjto; + /* Check 7th bit of CJTH reg. value for sign */ + if (reg_val[1] & 0x80) + *val -= 0x4000; + break; + + default: + return -EINVAL; + } + + ret = max31856_read(data, MAX31856_SR_REG, reg_val, 1); + if (ret) + return ret; + /* Check for over/under voltage or open circuit fault */ + if (reg_val[0] & (MAX31856_FAULT_OVUV | MAX31856_FAULT_OPEN)) + return -EIO; + + return ret; +} + +static int max31856_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max31856_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = max31856_thermocouple_read(data, chan, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->channel2) { + case IIO_MOD_TEMP_AMBIENT: + /* Cold junction Temp. Data resolution is 0.015625 */ + *val = 15; + *val2 = 625000; /* 1000 * 0.015625 */ + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + /* Thermocouple Temp. Data resolution is 0.0078125 */ + *val = 7; + *val2 = 812500; /* 1000 * 0.0078125) */ + return IIO_VAL_INT_PLUS_MICRO; + } + break; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = 1 << data->averaging; + return IIO_VAL_INT; + case IIO_CHAN_INFO_THERMOCOUPLE_TYPE: + *val = max31856_tc_types[data->thermocouple_type]; + return IIO_VAL_CHAR; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int max31856_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_THERMOCOUPLE_TYPE: + return IIO_VAL_CHAR; + default: + return IIO_VAL_INT; + } +} + +static int max31856_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct max31856_data *data = iio_priv(indio_dev); + int msb; + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + if (val > 16 || val < 1) + return -EINVAL; + msb = fls(val) - 1; + /* Round up to next 2pow if needed */ + if (BIT(msb) < val) + msb++; + + data->averaging = msb; + max31856_init(data); + break; + case IIO_CHAN_INFO_THERMOCOUPLE_TYPE: + { + int tc_type = -1; + int i; + + for (i = 0; i < ARRAY_SIZE(max31856_tc_types); i++) { + if (max31856_tc_types[i] == toupper(val)) { + tc_type = i; + break; + } + } + if (tc_type < 0) + return -EINVAL; + + data->thermocouple_type = tc_type; + max31856_init(data); + break; + } + default: + return -EINVAL; + } + + return 0; +} + +static ssize_t show_fault(struct device *dev, u8 faultbit, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct max31856_data *data = iio_priv(indio_dev); + u8 reg_val; + int ret; + bool fault; + + ret = max31856_read(data, MAX31856_SR_REG, ®_val, 1); + if (ret) + return ret; + + fault = reg_val & faultbit; + + return sysfs_emit(buf, "%d\n", fault); +} + +static ssize_t show_fault_ovuv(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return show_fault(dev, MAX31856_FAULT_OVUV, buf); +} + +static ssize_t show_fault_oc(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return show_fault(dev, MAX31856_FAULT_OPEN, buf); +} + +static ssize_t show_filter(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct max31856_data *data = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", data->filter_50hz ? 50 : 60); +} + +static ssize_t set_filter(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct max31856_data *data = iio_priv(indio_dev); + unsigned int freq; + int ret; + + ret = kstrtouint(buf, 10, &freq); + if (ret) + return ret; + + switch (freq) { + case 50: + data->filter_50hz = true; + break; + case 60: + data->filter_50hz = false; + break; + default: + return -EINVAL; + } + + max31856_init(data); + return len; +} + +static IIO_DEVICE_ATTR(fault_ovuv, 0444, show_fault_ovuv, NULL, 0); +static IIO_DEVICE_ATTR(fault_oc, 0444, show_fault_oc, NULL, 0); +static IIO_DEVICE_ATTR(in_temp_filter_notch_center_frequency, 0644, + show_filter, set_filter, 0); + +static struct attribute *max31856_attributes[] = { + &iio_dev_attr_fault_ovuv.dev_attr.attr, + &iio_dev_attr_fault_oc.dev_attr.attr, + &iio_dev_attr_in_temp_filter_notch_center_frequency.dev_attr.attr, + NULL, +}; + +static const struct attribute_group max31856_group = { + .attrs = max31856_attributes, +}; + +static const struct iio_info max31856_info = { + .read_raw = max31856_read_raw, + .write_raw = max31856_write_raw, + .write_raw_get_fmt = max31856_write_raw_get_fmt, + .attrs = &max31856_group, +}; + +static int max31856_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct iio_dev *indio_dev; + struct max31856_data *data; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->spi = spi; + data->filter_50hz = false; + + spi_set_drvdata(spi, indio_dev); + + indio_dev->info = &max31856_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = max31856_channels; + indio_dev->num_channels = ARRAY_SIZE(max31856_channels); + + ret = device_property_read_u32(&spi->dev, "thermocouple-type", &data->thermocouple_type); + if (ret) { + dev_info(&spi->dev, + "Could not read thermocouple type DT property, configuring as a K-Type\n"); + data->thermocouple_type = THERMOCOUPLE_TYPE_K; + } + + /* + * no need to translate values as the supported types + * have the same value as the #defines + */ + switch (data->thermocouple_type) { + case THERMOCOUPLE_TYPE_B: + case THERMOCOUPLE_TYPE_E: + case THERMOCOUPLE_TYPE_J: + case THERMOCOUPLE_TYPE_K: + case THERMOCOUPLE_TYPE_N: + case THERMOCOUPLE_TYPE_R: + case THERMOCOUPLE_TYPE_S: + case THERMOCOUPLE_TYPE_T: + break; + default: + dev_err(&spi->dev, + "error: thermocouple-type %u not supported by max31856\n" + , data->thermocouple_type); + return -EINVAL; + } + + ret = max31856_init(data); + if (ret) { + dev_err(&spi->dev, "error: Failed to configure max31856\n"); + return ret; + } + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id max31856_id[] = { + { .name = "max31856" }, + { } +}; +MODULE_DEVICE_TABLE(spi, max31856_id); + +static const struct of_device_id max31856_of_match[] = { + { .compatible = "maxim,max31856" }, + { } +}; +MODULE_DEVICE_TABLE(of, max31856_of_match); + +static struct spi_driver max31856_driver = { + .driver = { + .name = "max31856", + .of_match_table = max31856_of_match, + }, + .probe = max31856_probe, + .id_table = max31856_id, +}; +module_spi_driver(max31856_driver); + +MODULE_AUTHOR("Paresh Chaudhary "); +MODULE_AUTHOR("Patrick Havelange "); +MODULE_DESCRIPTION("Maxim MAX31856 thermocouple sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/temperature/max31865.c b/drivers/iio/temperature/max31865.c new file mode 100644 index 000000000..f9c93f95d --- /dev/null +++ b/drivers/iio/temperature/max31865.c @@ -0,0 +1,350 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* + * Copyright (c) Linumiz 2021 + * + * max31865.c - Maxim MAX31865 RTD-to-Digital Converter sensor driver + * + * Author: Navin Sankar Velliangiri + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * The MSB of the register value determines whether the following byte will + * be written or read. If it is 0, read will follow and if it is 1, write + * will follow. + */ +#define MAX31865_RD_WR_BIT BIT(7) + +#define MAX31865_CFG_VBIAS BIT(7) +#define MAX31865_CFG_1SHOT BIT(5) +#define MAX31865_3WIRE_RTD BIT(4) +#define MAX31865_FAULT_STATUS_CLEAR BIT(1) +#define MAX31865_FILTER_50HZ BIT(0) + +/* The MAX31865 registers */ +#define MAX31865_CFG_REG 0x00 +#define MAX31865_RTD_MSB 0x01 +#define MAX31865_FAULT_STATUS 0x07 + +#define MAX31865_FAULT_OVUV BIT(2) + +static const char max31865_show_samp_freq[] = "50 60"; + +static const struct iio_chan_spec max31865_channels[] = { + { /* RTD Temperature */ + .type = IIO_TEMP, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE) + }, +}; + +struct max31865_data { + struct spi_device *spi; + struct mutex lock; + bool filter_50hz; + bool three_wire; + u8 buf[2] __aligned(IIO_DMA_MINALIGN); +}; + +static int max31865_read(struct max31865_data *data, u8 reg, + unsigned int read_size) +{ + return spi_write_then_read(data->spi, ®, 1, data->buf, read_size); +} + +static int max31865_write(struct max31865_data *data, size_t len) +{ + return spi_write(data->spi, data->buf, len); +} + +static int enable_bias(struct max31865_data *data) +{ + u8 cfg; + int ret; + + ret = max31865_read(data, MAX31865_CFG_REG, 1); + if (ret) + return ret; + + cfg = data->buf[0]; + + data->buf[0] = MAX31865_CFG_REG | MAX31865_RD_WR_BIT; + data->buf[1] = cfg | MAX31865_CFG_VBIAS; + + return max31865_write(data, 2); +} + +static int disable_bias(struct max31865_data *data) +{ + u8 cfg; + int ret; + + ret = max31865_read(data, MAX31865_CFG_REG, 1); + if (ret) + return ret; + + cfg = data->buf[0]; + cfg &= ~MAX31865_CFG_VBIAS; + + data->buf[0] = MAX31865_CFG_REG | MAX31865_RD_WR_BIT; + data->buf[1] = cfg; + + return max31865_write(data, 2); +} + +static int max31865_rtd_read(struct max31865_data *data, int *val) +{ + u8 reg; + int ret; + + /* Enable BIAS to start the conversion */ + ret = enable_bias(data); + if (ret) + return ret; + + /* wait 10.5ms before initiating the conversion */ + msleep(11); + + ret = max31865_read(data, MAX31865_CFG_REG, 1); + if (ret) + return ret; + + reg = data->buf[0]; + reg |= MAX31865_CFG_1SHOT | MAX31865_FAULT_STATUS_CLEAR; + data->buf[0] = MAX31865_CFG_REG | MAX31865_RD_WR_BIT; + data->buf[1] = reg; + + ret = max31865_write(data, 2); + if (ret) + return ret; + + if (data->filter_50hz) { + /* 50Hz filter mode requires 62.5ms to complete */ + msleep(63); + } else { + /* 60Hz filter mode requires 52ms to complete */ + msleep(52); + } + + ret = max31865_read(data, MAX31865_RTD_MSB, 2); + if (ret) + return ret; + + *val = get_unaligned_be16(&data->buf) >> 1; + + return disable_bias(data); +} + +static int max31865_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max31865_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&data->lock); + ret = max31865_rtd_read(data, val); + mutex_unlock(&data->lock); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* Temp. Data resolution is 0.03125 degree centigrade */ + *val = 31; + *val2 = 250000; /* 1000 * 0.03125 */ + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int max31865_init(struct max31865_data *data) +{ + u8 cfg; + int ret; + + ret = max31865_read(data, MAX31865_CFG_REG, 1); + if (ret) + return ret; + + cfg = data->buf[0]; + + if (data->three_wire) + /* 3-wire RTD connection */ + cfg |= MAX31865_3WIRE_RTD; + + if (data->filter_50hz) + /* 50Hz noise rejection filter */ + cfg |= MAX31865_FILTER_50HZ; + + data->buf[0] = MAX31865_CFG_REG | MAX31865_RD_WR_BIT; + data->buf[1] = cfg; + + return max31865_write(data, 2); +} + +static ssize_t show_fault(struct device *dev, u8 faultbit, char *buf) +{ + int ret; + bool fault; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct max31865_data *data = iio_priv(indio_dev); + + ret = max31865_read(data, MAX31865_FAULT_STATUS, 1); + if (ret) + return ret; + + fault = data->buf[0] & faultbit; + + return sysfs_emit(buf, "%d\n", fault); +} + +static ssize_t show_fault_ovuv(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return show_fault(dev, MAX31865_FAULT_OVUV, buf); +} + +static ssize_t show_filter(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct max31865_data *data = iio_priv(indio_dev); + + return sysfs_emit(buf, "%d\n", data->filter_50hz ? 50 : 60); +} + +static ssize_t set_filter(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct max31865_data *data = iio_priv(indio_dev); + unsigned int freq; + int ret; + + ret = kstrtouint(buf, 10, &freq); + if (ret) + return ret; + + switch (freq) { + case 50: + data->filter_50hz = true; + break; + case 60: + data->filter_50hz = false; + break; + default: + return -EINVAL; + } + + mutex_lock(&data->lock); + ret = max31865_init(data); + mutex_unlock(&data->lock); + if (ret) + return ret; + + return len; +} + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL(max31865_show_samp_freq); +static IIO_DEVICE_ATTR(fault_ovuv, 0444, show_fault_ovuv, NULL, 0); +static IIO_DEVICE_ATTR(in_filter_notch_center_frequency, 0644, + show_filter, set_filter, 0); + +static struct attribute *max31865_attributes[] = { + &iio_dev_attr_fault_ovuv.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_filter_notch_center_frequency.dev_attr.attr, + NULL, +}; + +static const struct attribute_group max31865_group = { + .attrs = max31865_attributes, +}; + +static const struct iio_info max31865_info = { + .read_raw = max31865_read_raw, + .attrs = &max31865_group, +}; + +static int max31865_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct iio_dev *indio_dev; + struct max31865_data *data; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->spi = spi; + data->filter_50hz = false; + mutex_init(&data->lock); + + indio_dev->info = &max31865_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = max31865_channels; + indio_dev->num_channels = ARRAY_SIZE(max31865_channels); + + if (device_property_read_bool(&spi->dev, "maxim,3-wire")) { + /* select 3 wire */ + data->three_wire = 1; + } else { + /* select 2 or 4 wire */ + data->three_wire = 0; + } + + ret = max31865_init(data); + if (ret) { + dev_err(&spi->dev, "error: Failed to configure max31865\n"); + return ret; + } + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id max31865_id[] = { + { .name = "max31865" }, + { } +}; +MODULE_DEVICE_TABLE(spi, max31865_id); + +static const struct of_device_id max31865_of_match[] = { + { .compatible = "maxim,max31865" }, + { } +}; +MODULE_DEVICE_TABLE(of, max31865_of_match); + +static struct spi_driver max31865_driver = { + .driver = { + .name = "max31865", + .of_match_table = max31865_of_match, + }, + .probe = max31865_probe, + .id_table = max31865_id, +}; +module_spi_driver(max31865_driver); + +MODULE_AUTHOR("Navin Sankar Velliangiri "); +MODULE_DESCRIPTION("Maxim MAX31865 RTD-to-Digital Converter sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/temperature/maxim_thermocouple.c b/drivers/iio/temperature/maxim_thermocouple.c new file mode 100644 index 000000000..5f216e238 --- /dev/null +++ b/drivers/iio/temperature/maxim_thermocouple.c @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * maxim_thermocouple.c - Support for Maxim thermocouple chips + * + * Copyright (C) 2016-2018 Matt Ranostay + * Author: + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAXIM_THERMOCOUPLE_DRV_NAME "maxim_thermocouple" + +enum { + MAX6675, + MAX31855, + MAX31855K, + MAX31855J, + MAX31855N, + MAX31855S, + MAX31855T, + MAX31855E, + MAX31855R, +}; + +static const char maxim_tc_types[] = { + 'K', '?', 'K', 'J', 'N', 'S', 'T', 'E', 'R' +}; + +static const struct iio_chan_spec max6675_channels[] = { + { /* thermocouple temperature */ + .type = IIO_TEMP, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_THERMOCOUPLE_TYPE), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 13, + .storagebits = 16, + .shift = 3, + .endianness = IIO_BE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct iio_chan_spec max31855_channels[] = { + { /* thermocouple temperature */ + .type = IIO_TEMP, + .address = 2, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_THERMOCOUPLE_TYPE), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 14, + .storagebits = 16, + .shift = 2, + .endianness = IIO_BE, + }, + }, + { /* cold junction temperature */ + .type = IIO_TEMP, + .address = 0, + .channel2 = IIO_MOD_TEMP_AMBIENT, + .modified = 1, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 12, + .storagebits = 16, + .shift = 4, + .endianness = IIO_BE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static const unsigned long max31855_scan_masks[] = {0x3, 0}; + +struct maxim_thermocouple_chip { + const struct iio_chan_spec *channels; + const unsigned long *scan_masks; + u8 num_channels; + u8 read_size; + + /* bit-check for valid input */ + u32 status_bit; +}; + +static const struct maxim_thermocouple_chip maxim_thermocouple_chips[] = { + [MAX6675] = { + .channels = max6675_channels, + .num_channels = ARRAY_SIZE(max6675_channels), + .read_size = 2, + .status_bit = BIT(2), + }, + [MAX31855] = { + .channels = max31855_channels, + .num_channels = ARRAY_SIZE(max31855_channels), + .read_size = 4, + .scan_masks = max31855_scan_masks, + .status_bit = BIT(16), + }, +}; + +struct maxim_thermocouple_data { + struct spi_device *spi; + const struct maxim_thermocouple_chip *chip; + char tc_type; + /* Buffer for reading up to 2 hardware channels. */ + struct { + union { + __be16 raw16; + __be32 raw32; + __be16 raw[2]; + }; + aligned_s64 timestamp; + } buffer __aligned(IIO_DMA_MINALIGN); +}; + +static int maxim_thermocouple_read(struct maxim_thermocouple_data *data, + struct iio_chan_spec const *chan, int *val) +{ + unsigned int storage_bytes = data->chip->read_size; + unsigned int shift = chan->scan_type.shift + (chan->address * 8); + int ret; + + switch (storage_bytes) { + case 2: + ret = spi_read(data->spi, &data->buffer.raw16, storage_bytes); + *val = be16_to_cpu(data->buffer.raw16); + break; + case 4: + ret = spi_read(data->spi, &data->buffer.raw32, storage_bytes); + *val = be32_to_cpu(data->buffer.raw32); + break; + default: + ret = -EINVAL; + } + + if (ret) + return ret; + + /* check to be sure this is a valid reading */ + if (*val & data->chip->status_bit) + return -EINVAL; + + *val = sign_extend32(*val >> shift, chan->scan_type.realbits - 1); + + return 0; +} + +static irqreturn_t maxim_thermocouple_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct maxim_thermocouple_data *data = iio_priv(indio_dev); + int ret; + + ret = spi_read(data->spi, data->buffer.raw, data->chip->read_size); + if (!ret) { + iio_push_to_buffers_with_ts(indio_dev, &data->buffer, + sizeof(data->buffer), + iio_get_time_ns(indio_dev)); + } + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int maxim_thermocouple_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct maxim_thermocouple_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = maxim_thermocouple_read(data, chan, val); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->channel2) { + case IIO_MOD_TEMP_AMBIENT: + *val = 62; + *val2 = 500000; /* 1000 * 0.0625 */ + return IIO_VAL_INT_PLUS_MICRO; + default: + *val = 250; /* 1000 * 0.25 */ + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_THERMOCOUPLE_TYPE: + *val = data->tc_type; + return IIO_VAL_CHAR; + default: + return -EINVAL; + } +} + +static const struct iio_info maxim_thermocouple_info = { + .read_raw = maxim_thermocouple_read_raw, +}; + +static int maxim_thermocouple_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct iio_dev *indio_dev; + struct maxim_thermocouple_data *data; + const int chip_type = (id->driver_data == MAX6675) ? MAX6675 : MAX31855; + const struct maxim_thermocouple_chip *chip = + &maxim_thermocouple_chips[chip_type]; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + indio_dev->info = &maxim_thermocouple_info; + indio_dev->name = MAXIM_THERMOCOUPLE_DRV_NAME; + indio_dev->channels = chip->channels; + indio_dev->available_scan_masks = chip->scan_masks; + indio_dev->num_channels = chip->num_channels; + indio_dev->modes = INDIO_DIRECT_MODE; + + data = iio_priv(indio_dev); + data->spi = spi; + data->chip = chip; + data->tc_type = maxim_tc_types[id->driver_data]; + + ret = devm_iio_triggered_buffer_setup(&spi->dev, + indio_dev, NULL, + maxim_thermocouple_trigger_handler, NULL); + if (ret) + return ret; + + if (id->driver_data == MAX31855) + dev_warn(&spi->dev, "generic max31855 ID is deprecated\nplease use more specific part type"); + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id maxim_thermocouple_id[] = { + { .name = "max6675", .driver_data = MAX6675 }, + { .name = "max31855", .driver_data = MAX31855 }, + { .name = "max31855k", .driver_data = MAX31855K }, + { .name = "max31855j", .driver_data = MAX31855J }, + { .name = "max31855n", .driver_data = MAX31855N }, + { .name = "max31855s", .driver_data = MAX31855S }, + { .name = "max31855t", .driver_data = MAX31855T }, + { .name = "max31855e", .driver_data = MAX31855E }, + { .name = "max31855r", .driver_data = MAX31855R }, + { } +}; +MODULE_DEVICE_TABLE(spi, maxim_thermocouple_id); + +static const struct of_device_id maxim_thermocouple_of_match[] = { + { .compatible = "maxim,max6675" }, + { .compatible = "maxim,max31855" }, + { .compatible = "maxim,max31855k" }, + { .compatible = "maxim,max31855j" }, + { .compatible = "maxim,max31855n" }, + { .compatible = "maxim,max31855s" }, + { .compatible = "maxim,max31855t" }, + { .compatible = "maxim,max31855e" }, + { .compatible = "maxim,max31855r" }, + { }, +}; +MODULE_DEVICE_TABLE(of, maxim_thermocouple_of_match); + +static struct spi_driver maxim_thermocouple_driver = { + .driver = { + .name = MAXIM_THERMOCOUPLE_DRV_NAME, + .of_match_table = maxim_thermocouple_of_match, + }, + .probe = maxim_thermocouple_probe, + .id_table = maxim_thermocouple_id, +}; +module_spi_driver(maxim_thermocouple_driver); + +MODULE_AUTHOR("Matt Ranostay "); +MODULE_DESCRIPTION("Maxim thermocouple sensors"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/temperature/mcp9600.c b/drivers/iio/temperature/mcp9600.c new file mode 100644 index 000000000..b0ff0c7b4 --- /dev/null +++ b/drivers/iio/temperature/mcp9600.c @@ -0,0 +1,579 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * mcp9600.c - Support for Microchip MCP9600 thermocouple EMF converter + * + * Copyright (c) 2022 Andrew Hepp + * Author: + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +/* MCP9600 registers */ +#define MCP9600_HOT_JUNCTION 0x00 +#define MCP9600_COLD_JUNCTION 0x02 +#define MCP9600_STATUS 0x04 +#define MCP9600_STATUS_ALERT(x) BIT(x) +#define MCP9600_SENSOR_CFG 0x05 +#define MCP9600_SENSOR_TYPE_MASK GENMASK(6, 4) +#define MCP9600_ALERT_CFG1 0x08 +#define MCP9600_ALERT_CFG(x) (MCP9600_ALERT_CFG1 + (x - 1)) +#define MCP9600_ALERT_CFG_ENABLE BIT(0) +#define MCP9600_ALERT_CFG_ACTIVE_HIGH BIT(2) +#define MCP9600_ALERT_CFG_FALLING BIT(3) +#define MCP9600_ALERT_CFG_COLD_JUNCTION BIT(4) +#define MCP9600_ALERT_HYSTERESIS1 0x0c +#define MCP9600_ALERT_HYSTERESIS(x) (MCP9600_ALERT_HYSTERESIS1 + (x - 1)) +#define MCP9600_ALERT_LIMIT1 0x10 +#define MCP9600_ALERT_LIMIT(x) (MCP9600_ALERT_LIMIT1 + (x - 1)) +#define MCP9600_ALERT_LIMIT_MASK GENMASK(15, 2) +#define MCP9600_DEVICE_ID 0x20 + +/* MCP9600 device id value */ +#define MCP9600_DEVICE_ID_MCP9600 0x40 +#define MCP9600_DEVICE_ID_MCP9601 0x41 + +#define MCP9600_ALERT_COUNT 4 + +#define MCP9600_MIN_TEMP_HOT_JUNCTION_MICRO -200000000 +#define MCP9600_MAX_TEMP_HOT_JUNCTION_MICRO 1800000000 + +#define MCP9600_MIN_TEMP_COLD_JUNCTION_MICRO -40000000 +#define MCP9600_MAX_TEMP_COLD_JUNCTION_MICRO 125000000 + +enum mcp9600_alert { + MCP9600_ALERT1, + MCP9600_ALERT2, + MCP9600_ALERT3, + MCP9600_ALERT4 +}; + +static const char * const mcp9600_alert_name[MCP9600_ALERT_COUNT] = { + [MCP9600_ALERT1] = "alert1", + [MCP9600_ALERT2] = "alert2", + [MCP9600_ALERT3] = "alert3", + [MCP9600_ALERT4] = "alert4", +}; + +/* Map between dt-bindings enum and the chip's type value */ +static const unsigned int mcp9600_type_map[] = { + [THERMOCOUPLE_TYPE_K] = 0, + [THERMOCOUPLE_TYPE_J] = 1, + [THERMOCOUPLE_TYPE_T] = 2, + [THERMOCOUPLE_TYPE_N] = 3, + [THERMOCOUPLE_TYPE_S] = 4, + [THERMOCOUPLE_TYPE_E] = 5, + [THERMOCOUPLE_TYPE_B] = 6, + [THERMOCOUPLE_TYPE_R] = 7, +}; + +/* Map thermocouple type to a char for iio info in sysfs */ +static const int mcp9600_tc_types[] = { + [THERMOCOUPLE_TYPE_K] = 'K', + [THERMOCOUPLE_TYPE_J] = 'J', + [THERMOCOUPLE_TYPE_T] = 'T', + [THERMOCOUPLE_TYPE_N] = 'N', + [THERMOCOUPLE_TYPE_S] = 'S', + [THERMOCOUPLE_TYPE_E] = 'E', + [THERMOCOUPLE_TYPE_B] = 'B', + [THERMOCOUPLE_TYPE_R] = 'R', +}; + +static const struct iio_event_spec mcp9600_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_HYSTERESIS), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_HYSTERESIS), + }, +}; + +struct mcp_chip_info { + u8 chip_id; + const char *chip_name; +}; + +struct mcp9600_data { + struct i2c_client *client; + u32 thermocouple_type; +}; + +static int mcp9600_config(struct mcp9600_data *data) +{ + struct i2c_client *client = data->client; + int ret; + u8 cfg; + + cfg = FIELD_PREP(MCP9600_SENSOR_TYPE_MASK, + mcp9600_type_map[data->thermocouple_type]); + + ret = i2c_smbus_write_byte_data(client, MCP9600_SENSOR_CFG, cfg); + if (ret < 0) { + dev_err(&client->dev, "Failed to set sensor configuration\n"); + return ret; + } + + return 0; +} + +#define MCP9600_CHANNELS(hj_num_ev, hj_ev_spec_off, cj_num_ev, cj_ev_spec_off) \ + { \ + { \ + .type = IIO_TEMP, \ + .address = MCP9600_HOT_JUNCTION, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_THERMOCOUPLE_TYPE) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .event_spec = &mcp9600_events[hj_ev_spec_off], \ + .num_event_specs = hj_num_ev, \ + }, \ + { \ + .type = IIO_TEMP, \ + .address = MCP9600_COLD_JUNCTION, \ + .channel2 = IIO_MOD_TEMP_AMBIENT, \ + .modified = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .event_spec = &mcp9600_events[cj_ev_spec_off], \ + .num_event_specs = cj_num_ev, \ + }, \ + } + +static const struct iio_chan_spec mcp9600_channels[][2] = { + MCP9600_CHANNELS(0, 0, 0, 0), /* Alerts: - - - - */ + MCP9600_CHANNELS(1, 0, 0, 0), /* Alerts: 1 - - - */ + MCP9600_CHANNELS(1, 1, 0, 0), /* Alerts: - 2 - - */ + MCP9600_CHANNELS(2, 0, 0, 0), /* Alerts: 1 2 - - */ + MCP9600_CHANNELS(0, 0, 1, 0), /* Alerts: - - 3 - */ + MCP9600_CHANNELS(1, 0, 1, 0), /* Alerts: 1 - 3 - */ + MCP9600_CHANNELS(1, 1, 1, 0), /* Alerts: - 2 3 - */ + MCP9600_CHANNELS(2, 0, 1, 0), /* Alerts: 1 2 3 - */ + MCP9600_CHANNELS(0, 0, 1, 1), /* Alerts: - - - 4 */ + MCP9600_CHANNELS(1, 0, 1, 1), /* Alerts: 1 - - 4 */ + MCP9600_CHANNELS(1, 1, 1, 1), /* Alerts: - 2 - 4 */ + MCP9600_CHANNELS(2, 0, 1, 1), /* Alerts: 1 2 - 4 */ + MCP9600_CHANNELS(0, 0, 2, 0), /* Alerts: - - 3 4 */ + MCP9600_CHANNELS(1, 0, 2, 0), /* Alerts: 1 - 3 4 */ + MCP9600_CHANNELS(1, 1, 2, 0), /* Alerts: - 2 3 4 */ + MCP9600_CHANNELS(2, 0, 2, 0), /* Alerts: 1 2 3 4 */ +}; + +static int mcp9600_read(struct mcp9600_data *data, + struct iio_chan_spec const *chan, int *val) +{ + int ret; + + ret = i2c_smbus_read_word_swapped(data->client, chan->address); + + if (ret < 0) + return ret; + + *val = sign_extend32(ret, 15); + + return 0; +} + +static int mcp9600_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct mcp9600_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = mcp9600_read(data, chan, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 62; + *val2 = 500000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_THERMOCOUPLE_TYPE: + *val = mcp9600_tc_types[data->thermocouple_type]; + return IIO_VAL_CHAR; + default: + return -EINVAL; + } +} + +static int mcp9600_get_alert_index(int channel2, enum iio_event_direction dir) +{ + if (channel2 == IIO_MOD_TEMP_AMBIENT) { + if (dir == IIO_EV_DIR_RISING) + return MCP9600_ALERT3; + else + return MCP9600_ALERT4; + } else { + if (dir == IIO_EV_DIR_RISING) + return MCP9600_ALERT1; + else + return MCP9600_ALERT2; + } +} + +static int mcp9600_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct mcp9600_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + int i, ret; + + i = mcp9600_get_alert_index(chan->channel2, dir); + ret = i2c_smbus_read_byte_data(client, MCP9600_ALERT_CFG(i + 1)); + if (ret < 0) + return ret; + + return FIELD_GET(MCP9600_ALERT_CFG_ENABLE, ret); +} + +static int mcp9600_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct mcp9600_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + int i, ret; + + i = mcp9600_get_alert_index(chan->channel2, dir); + ret = i2c_smbus_read_byte_data(client, MCP9600_ALERT_CFG(i + 1)); + if (ret < 0) + return ret; + + if (state) + ret |= MCP9600_ALERT_CFG_ENABLE; + else + ret &= ~MCP9600_ALERT_CFG_ENABLE; + + return i2c_smbus_write_byte_data(client, MCP9600_ALERT_CFG(i + 1), ret); +} + +static int mcp9600_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, int *val2) +{ + struct mcp9600_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + s32 ret; + int i; + + i = mcp9600_get_alert_index(chan->channel2, dir); + switch (info) { + case IIO_EV_INFO_VALUE: + ret = i2c_smbus_read_word_swapped(client, MCP9600_ALERT_LIMIT(i + 1)); + if (ret < 0) + return ret; + /* + * Temperature is stored in two’s complement format in + * bits(15:2), LSB is 0.25 degree celsius. + */ + *val = FIELD_GET_SIGNED(MCP9600_ALERT_LIMIT_MASK, ret); + *val2 = 4; + return IIO_VAL_FRACTIONAL; + case IIO_EV_INFO_HYSTERESIS: + ret = i2c_smbus_read_byte_data(client, MCP9600_ALERT_HYSTERESIS(i + 1)); + if (ret < 0) + return ret; + + *val = ret; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int mcp9600_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, int val2) +{ + struct mcp9600_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + int s_val, i; + s16 thresh; + u8 hyst; + + i = mcp9600_get_alert_index(chan->channel2, dir); + switch (info) { + case IIO_EV_INFO_VALUE: + /* Scale value to include decimal part into calculations */ + s_val = (val < 0) ? ((val * 1000000) - val2) : + ((val * 1000000) + val2); + if (chan->channel2 == IIO_MOD_TEMP_AMBIENT) { + s_val = max(s_val, MCP9600_MIN_TEMP_COLD_JUNCTION_MICRO); + s_val = min(s_val, MCP9600_MAX_TEMP_COLD_JUNCTION_MICRO); + } else { + s_val = max(s_val, MCP9600_MIN_TEMP_HOT_JUNCTION_MICRO); + s_val = min(s_val, MCP9600_MAX_TEMP_HOT_JUNCTION_MICRO); + } + + /* + * Shift length 4 bits = 2(15:2) + 2(0.25 LSB), temperature is + * stored in two’s complement format. + */ + thresh = (s16)(s_val / (1000000 >> 4)); + return i2c_smbus_write_word_swapped(client, + MCP9600_ALERT_LIMIT(i + 1), + thresh); + case IIO_EV_INFO_HYSTERESIS: + hyst = min(abs(val), 255); + return i2c_smbus_write_byte_data(client, + MCP9600_ALERT_HYSTERESIS(i + 1), + hyst); + default: + return -EINVAL; + } +} + +static const struct iio_info mcp9600_info = { + .read_raw = mcp9600_read_raw, + .read_event_config = mcp9600_read_event_config, + .write_event_config = mcp9600_write_event_config, + .read_event_value = mcp9600_read_thresh, + .write_event_value = mcp9600_write_thresh, +}; + +static irqreturn_t mcp9600_alert_handler(void *private, + enum mcp9600_alert alert, + enum iio_modifier mod, + enum iio_event_direction dir) +{ + struct iio_dev *indio_dev = private; + struct mcp9600_data *data = iio_priv(indio_dev); + int ret; + + ret = i2c_smbus_read_byte_data(data->client, MCP9600_STATUS); + if (ret < 0) + return IRQ_HANDLED; + + if (!(ret & MCP9600_STATUS_ALERT(alert))) + return IRQ_NONE; + + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_TEMP, 0, mod, IIO_EV_TYPE_THRESH, + dir), + iio_get_time_ns(indio_dev)); + + return IRQ_HANDLED; +} + +static irqreturn_t mcp9600_alert1_handler(int irq, void *private) +{ + return mcp9600_alert_handler(private, MCP9600_ALERT1, IIO_NO_MOD, + IIO_EV_DIR_RISING); +} + +static irqreturn_t mcp9600_alert2_handler(int irq, void *private) +{ + return mcp9600_alert_handler(private, MCP9600_ALERT2, IIO_NO_MOD, + IIO_EV_DIR_FALLING); +} + +static irqreturn_t mcp9600_alert3_handler(int irq, void *private) +{ + return mcp9600_alert_handler(private, MCP9600_ALERT3, + IIO_MOD_TEMP_AMBIENT, IIO_EV_DIR_RISING); +} + +static irqreturn_t mcp9600_alert4_handler(int irq, void *private) +{ + return mcp9600_alert_handler(private, MCP9600_ALERT4, + IIO_MOD_TEMP_AMBIENT, IIO_EV_DIR_FALLING); +} + +static irqreturn_t (*mcp9600_alert_handler_func[MCP9600_ALERT_COUNT]) (int, void *) = { + mcp9600_alert1_handler, + mcp9600_alert2_handler, + mcp9600_alert3_handler, + mcp9600_alert4_handler, +}; + +static int mcp9600_probe_alerts(struct iio_dev *indio_dev) +{ + struct mcp9600_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + struct device *dev = &client->dev; + struct fwnode_handle *fwnode = dev_fwnode(dev); + unsigned int irq_type; + int ret, irq, i; + u8 val, ch_sel; + + /* + * alert1: hot junction, rising temperature + * alert2: hot junction, falling temperature + * alert3: cold junction, rising temperature + * alert4: cold junction, falling temperature + */ + ch_sel = 0; + for (i = 0; i < MCP9600_ALERT_COUNT; i++) { + irq = fwnode_irq_get_byname(fwnode, mcp9600_alert_name[i]); + if (irq <= 0) + continue; + + val = 0; + irq_type = irq_get_trigger_type(irq); + if (irq_type == IRQ_TYPE_EDGE_RISING) + val |= MCP9600_ALERT_CFG_ACTIVE_HIGH; + + if (i == MCP9600_ALERT2 || i == MCP9600_ALERT4) + val |= MCP9600_ALERT_CFG_FALLING; + + if (i == MCP9600_ALERT3 || i == MCP9600_ALERT4) + val |= MCP9600_ALERT_CFG_COLD_JUNCTION; + + ret = i2c_smbus_write_byte_data(client, + MCP9600_ALERT_CFG(i + 1), + val); + if (ret < 0) + return ret; + + ret = devm_request_threaded_irq(dev, irq, NULL, + mcp9600_alert_handler_func[i], + IRQF_ONESHOT, "mcp9600", + indio_dev); + if (ret) + return ret; + + ch_sel |= BIT(i); + } + + return ch_sel; +} + +static int mcp9600_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + const struct mcp_chip_info *chip_info; + struct iio_dev *indio_dev; + struct mcp9600_data *data; + int ch_sel, dev_id, ret; + + chip_info = i2c_get_match_data(client); + if (!chip_info) + return dev_err_probe(dev, -ENODEV, + "No chip-info found for device\n"); + + dev_id = i2c_smbus_read_byte_data(client, MCP9600_DEVICE_ID); + if (dev_id < 0) + return dev_err_probe(dev, dev_id, "Failed to read device ID\n"); + + switch (dev_id) { + case MCP9600_DEVICE_ID_MCP9600: + case MCP9600_DEVICE_ID_MCP9601: + if (dev_id != chip_info->chip_id) + dev_warn(dev, + "Expected id %02x, but device responded with %02x\n", + chip_info->chip_id, dev_id); + break; + + default: + dev_warn(dev, "Unknown id %x, using %x\n", dev_id, + chip_info->chip_id); + } + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + + /* Accept type from dt with default of Type-K. */ + data->thermocouple_type = THERMOCOUPLE_TYPE_K; + ret = device_property_read_u32(dev, "thermocouple-type", + &data->thermocouple_type); + if (ret && ret != -EINVAL) + return dev_err_probe(dev, ret, + "Error reading thermocouple-type property\n"); + + if (data->thermocouple_type >= ARRAY_SIZE(mcp9600_type_map)) + return dev_err_probe(dev, -EINVAL, + "Invalid thermocouple-type property %u.\n", + data->thermocouple_type); + + /* Set initial config. */ + ret = mcp9600_config(data); + if (ret) + return ret; + + ch_sel = mcp9600_probe_alerts(indio_dev); + if (ch_sel < 0) + return ch_sel; + + indio_dev->info = &mcp9600_info; + indio_dev->name = chip_info->chip_name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = mcp9600_channels[ch_sel]; + indio_dev->num_channels = ARRAY_SIZE(mcp9600_channels[ch_sel]); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct mcp_chip_info mcp9600_chip_info = { + .chip_id = MCP9600_DEVICE_ID_MCP9600, + .chip_name = "mcp9600", +}; + +static const struct mcp_chip_info mcp9601_chip_info = { + .chip_id = MCP9600_DEVICE_ID_MCP9601, + .chip_name = "mcp9601", +}; + +static const struct i2c_device_id mcp9600_id[] = { + { .name = "mcp9600", .driver_data = (kernel_ulong_t)&mcp9600_chip_info }, + { .name = "mcp9601", .driver_data = (kernel_ulong_t)&mcp9601_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mcp9600_id); + +static const struct of_device_id mcp9600_of_match[] = { + { .compatible = "microchip,mcp9600", .data = &mcp9600_chip_info }, + { .compatible = "microchip,mcp9601", .data = &mcp9601_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, mcp9600_of_match); + +static struct i2c_driver mcp9600_driver = { + .driver = { + .name = "mcp9600", + .of_match_table = mcp9600_of_match, + }, + .probe = mcp9600_probe, + .id_table = mcp9600_id +}; +module_i2c_driver(mcp9600_driver); + +MODULE_AUTHOR("Dimitri Fedrau "); +MODULE_AUTHOR("Andrew Hepp "); +MODULE_DESCRIPTION("Microchip MCP9600 thermocouple EMF converter driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/temperature/mlx90614.c b/drivers/iio/temperature/mlx90614.c new file mode 100644 index 000000000..27d6ab5f5 --- /dev/null +++ b/drivers/iio/temperature/mlx90614.c @@ -0,0 +1,782 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * mlx90614.c - Support for Melexis MLX90614/MLX90615 contactless IR temperature sensor + * + * Copyright (c) 2014 Peter Meerwald + * Copyright (c) 2015 Essensium NV + * Copyright (c) 2015 Melexis + * + * Driver for the Melexis MLX90614/MLX90615 I2C 16-bit IR thermopile sensor + * + * MLX90614 - 17-bit ADC + MLX90302 DSP + * MLX90615 - 16-bit ADC + MLX90325 DSP + * + * (7-bit I2C slave address 0x5a, 100KHz bus speed only!) + * + * To wake up from sleep mode, the SDA line must be held low while SCL is high + * for at least 33ms. This is achieved with an extra GPIO that can be connected + * directly to the SDA line. In normal operation, the GPIO is set as input and + * will not interfere in I2C communication. While the GPIO is driven low, the + * i2c adapter is locked since it cannot be used by other clients. The SCL line + * always has a pull-up so we do not need an extra GPIO to drive it high. If + * the "wakeup" GPIO is not given, power management will be disabled. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define MLX90614_OP_RAM 0x00 +#define MLX90614_OP_EEPROM 0x20 +#define MLX90614_OP_SLEEP 0xff + +#define MLX90615_OP_EEPROM 0x10 +#define MLX90615_OP_RAM 0x20 +#define MLX90615_OP_SLEEP 0xc6 + +/* Control bits in configuration register */ +#define MLX90614_CONFIG_IIR_SHIFT 0 /* IIR coefficient */ +#define MLX90614_CONFIG_IIR_MASK (0x7 << MLX90614_CONFIG_IIR_SHIFT) +#define MLX90614_CONFIG_DUAL_SHIFT 6 /* single (0) or dual (1) IR sensor */ +#define MLX90614_CONFIG_DUAL_MASK (1 << MLX90614_CONFIG_DUAL_SHIFT) +#define MLX90614_CONFIG_FIR_SHIFT 8 /* FIR coefficient */ +#define MLX90614_CONFIG_FIR_MASK (0x7 << MLX90614_CONFIG_FIR_SHIFT) + +#define MLX90615_CONFIG_IIR_SHIFT 12 /* IIR coefficient */ +#define MLX90615_CONFIG_IIR_MASK (0x7 << MLX90615_CONFIG_IIR_SHIFT) + +/* Timings (in ms) */ +#define MLX90614_TIMING_EEPROM 20 /* time for EEPROM write/erase to complete */ +#define MLX90614_TIMING_WAKEUP 34 /* time to hold SDA low for wake-up */ +#define MLX90614_TIMING_STARTUP 250 /* time before first data after wake-up */ + +#define MLX90615_TIMING_WAKEUP 22 /* time to hold SCL low for wake-up */ + +#define MLX90614_AUTOSLEEP_DELAY 5000 /* default autosleep delay */ + +/* Magic constants */ +#define MLX90614_CONST_OFFSET_DEC -13657 /* decimal part of the Kelvin offset */ +#define MLX90614_CONST_OFFSET_REM 500000 /* remainder of offset (273.15*50) */ +#define MLX90614_CONST_SCALE 20 /* Scale in milliKelvin (0.02 * 1000) */ +#define MLX90614_CONST_FIR 0x7 /* Fixed value for FIR part of low pass filter */ + +struct mlx_chip_info { + /* EEPROM offsets with 16-bit data, MSB first */ + /* emissivity correction coefficient */ + u8 op_eeprom_emissivity; + u8 op_eeprom_config1; + /* RAM offsets with 16-bit data, MSB first */ + /* ambient temperature */ + u8 op_ram_ta; + /* object 1 temperature */ + u8 op_ram_tobj1; + /* object 2 temperature */ + u8 op_ram_tobj2; + u8 op_sleep; + /* support for two input channels (MLX90614 only) */ + u8 dual_channel; + u8 wakeup_delay_ms; + u16 emissivity_max; + u16 fir_config_mask; + u16 iir_config_mask; + int iir_valid_offset; + u16 iir_values[8]; + int iir_freqs[8][2]; +}; + +struct mlx90614_data { + struct i2c_client *client; + struct mutex lock; /* for EEPROM access only */ + struct gpio_desc *wakeup_gpio; /* NULL to disable sleep/wake-up */ + const struct mlx_chip_info *chip_info; /* Chip hardware details */ + unsigned long ready_timestamp; /* in jiffies */ +}; + +/* + * Erase an address and write word. + * The mutex must be locked before calling. + */ +static s32 mlx90614_write_word(const struct i2c_client *client, u8 command, + u16 value) +{ + /* + * Note: The mlx90614 requires a PEC on writing but does not send us a + * valid PEC on reading. Hence, we cannot set I2C_CLIENT_PEC in + * i2c_client.flags. As a workaround, we use i2c_smbus_xfer here. + */ + union i2c_smbus_data data; + s32 ret; + + dev_dbg(&client->dev, "Writing 0x%x to address 0x%x", value, command); + + data.word = 0x0000; /* erase command */ + ret = i2c_smbus_xfer(client->adapter, client->addr, + client->flags | I2C_CLIENT_PEC, + I2C_SMBUS_WRITE, command, + I2C_SMBUS_WORD_DATA, &data); + if (ret < 0) + return ret; + + msleep(MLX90614_TIMING_EEPROM); + + data.word = value; /* actual write */ + ret = i2c_smbus_xfer(client->adapter, client->addr, + client->flags | I2C_CLIENT_PEC, + I2C_SMBUS_WRITE, command, + I2C_SMBUS_WORD_DATA, &data); + + msleep(MLX90614_TIMING_EEPROM); + + return ret; +} + +/* + * Find the IIR value inside iir_values array and return its position + * which is equivalent to the bit value in sensor register + */ +static inline s32 mlx90614_iir_search(const struct i2c_client *client, + int value) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct mlx90614_data *data = iio_priv(indio_dev); + const struct mlx_chip_info *chip_info = data->chip_info; + int i; + s32 ret; + + for (i = chip_info->iir_valid_offset; + i < ARRAY_SIZE(chip_info->iir_values); + i++) { + if (value == chip_info->iir_values[i]) + break; + } + + if (i == ARRAY_SIZE(chip_info->iir_values)) + return -EINVAL; + + /* + * CONFIG register values must not be changed so + * we must read them before we actually write + * changes + */ + ret = i2c_smbus_read_word_data(client, chip_info->op_eeprom_config1); + if (ret < 0) + return ret; + + /* Modify FIR on parts which have configurable FIR filter */ + if (chip_info->fir_config_mask) { + ret &= ~chip_info->fir_config_mask; + ret |= field_prep(chip_info->fir_config_mask, MLX90614_CONST_FIR); + } + + ret &= ~chip_info->iir_config_mask; + ret |= field_prep(chip_info->iir_config_mask, i); + + /* Write changed values */ + ret = mlx90614_write_word(client, chip_info->op_eeprom_config1, ret); + return ret; +} + +#ifdef CONFIG_PM +/* + * If @startup is true, make sure MLX90614_TIMING_STARTUP ms have elapsed since + * the last wake-up. This is normally only needed to get a valid temperature + * reading. EEPROM access does not need such delay. + * Return 0 on success, <0 on error. + */ +static int mlx90614_power_get(struct mlx90614_data *data, bool startup) +{ + unsigned long now; + int ret; + + if (!data->wakeup_gpio) + return 0; + + ret = pm_runtime_resume_and_get(&data->client->dev); + if (ret < 0) + return ret; + + if (startup) { + now = jiffies; + if (time_before(now, data->ready_timestamp) && + msleep_interruptible(jiffies_to_msecs( + data->ready_timestamp - now)) != 0) { + pm_runtime_put_autosuspend(&data->client->dev); + return -EINTR; + } + } + + return 0; +} + +static void mlx90614_power_put(struct mlx90614_data *data) +{ + if (!data->wakeup_gpio) + return; + + pm_runtime_put_autosuspend(&data->client->dev); +} +#else +static inline int mlx90614_power_get(struct mlx90614_data *data, bool startup) +{ + return 0; +} + +static inline void mlx90614_power_put(struct mlx90614_data *data) +{ +} +#endif + +static int mlx90614_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct mlx90614_data *data = iio_priv(indio_dev); + const struct mlx_chip_info *chip_info = data->chip_info; + u8 cmd, idx; + s32 ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: /* 0.02K / LSB */ + switch (channel->channel2) { + case IIO_MOD_TEMP_AMBIENT: + cmd = chip_info->op_ram_ta; + break; + case IIO_MOD_TEMP_OBJECT: + if (chip_info->dual_channel && channel->channel) + return -EINVAL; + + switch (channel->channel) { + case 0: + cmd = chip_info->op_ram_tobj1; + break; + case 1: + cmd = chip_info->op_ram_tobj2; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + ret = mlx90614_power_get(data, true); + if (ret < 0) + return ret; + ret = i2c_smbus_read_word_data(data->client, cmd); + mlx90614_power_put(data); + + if (ret < 0) + return ret; + + /* MSB is an error flag */ + if (ret & 0x8000) + return -EIO; + + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + *val = MLX90614_CONST_OFFSET_DEC; + *val2 = MLX90614_CONST_OFFSET_REM; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SCALE: + *val = MLX90614_CONST_SCALE; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBEMISSIVITY: /* 1/emissivity_max / LSB */ + ret = mlx90614_power_get(data, false); + if (ret < 0) + return ret; + + mutex_lock(&data->lock); + ret = i2c_smbus_read_word_data(data->client, + chip_info->op_eeprom_emissivity); + mutex_unlock(&data->lock); + mlx90614_power_put(data); + + if (ret < 0) + return ret; + + if (ret == chip_info->emissivity_max) { + *val = 1; + *val2 = 0; + } else { + *val = 0; + *val2 = ret * NSEC_PER_SEC / chip_info->emissivity_max; + } + return IIO_VAL_INT_PLUS_NANO; + /* IIR setting with FIR=1024 (MLX90614) or FIR=65536 (MLX90615) */ + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + ret = mlx90614_power_get(data, false); + if (ret < 0) + return ret; + + mutex_lock(&data->lock); + ret = i2c_smbus_read_word_data(data->client, + chip_info->op_eeprom_config1); + mutex_unlock(&data->lock); + mlx90614_power_put(data); + + if (ret < 0) + return ret; + + idx = field_get(chip_info->iir_config_mask, ret) - + chip_info->iir_valid_offset; + + *val = chip_info->iir_values[idx] / 100; + *val2 = (chip_info->iir_values[idx] % 100) * 10000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int mlx90614_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int val, + int val2, long mask) +{ + struct mlx90614_data *data = iio_priv(indio_dev); + const struct mlx_chip_info *chip_info = data->chip_info; + s32 ret; + + switch (mask) { + case IIO_CHAN_INFO_CALIBEMISSIVITY: /* 1/emissivity_max / LSB */ + if (val < 0 || val2 < 0 || val > 1 || (val == 1 && val2 != 0)) + return -EINVAL; + val = val * chip_info->emissivity_max + + val2 * chip_info->emissivity_max / NSEC_PER_SEC; + + ret = mlx90614_power_get(data, false); + if (ret < 0) + return ret; + + mutex_lock(&data->lock); + ret = mlx90614_write_word(data->client, + chip_info->op_eeprom_emissivity, val); + mutex_unlock(&data->lock); + mlx90614_power_put(data); + + return ret; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: /* IIR Filter setting */ + if (val < 0 || val2 < 0) + return -EINVAL; + + ret = mlx90614_power_get(data, false); + if (ret < 0) + return ret; + + mutex_lock(&data->lock); + ret = mlx90614_iir_search(data->client, + val * 100 + val2 / 10000); + mutex_unlock(&data->lock); + mlx90614_power_put(data); + + return ret; + default: + return -EINVAL; + } +} + +static int mlx90614_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_CALIBEMISSIVITY: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int mlx90614_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct mlx90614_data *data = iio_priv(indio_dev); + const struct mlx_chip_info *chip_info = data->chip_info; + + switch (mask) { + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + *vals = (int *)chip_info->iir_freqs; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = 2 * (ARRAY_SIZE(chip_info->iir_freqs) - + chip_info->iir_valid_offset); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const struct iio_chan_spec mlx90614_channels[] = { + { + .type = IIO_TEMP, + .modified = 1, + .channel2 = IIO_MOD_TEMP_AMBIENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_TEMP, + .modified = 1, + .channel2 = IIO_MOD_TEMP_OBJECT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBEMISSIVITY) | + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_TEMP, + .indexed = 1, + .modified = 1, + .channel = 1, + .channel2 = IIO_MOD_TEMP_OBJECT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBEMISSIVITY) | + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + .info_mask_separate_available = + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static const struct iio_info mlx90614_info = { + .read_raw = mlx90614_read_raw, + .write_raw = mlx90614_write_raw, + .write_raw_get_fmt = mlx90614_write_raw_get_fmt, + .read_avail = mlx90614_read_avail, +}; + +#ifdef CONFIG_PM +static int mlx90614_sleep(struct mlx90614_data *data) +{ + const struct mlx_chip_info *chip_info = data->chip_info; + s32 ret; + + if (!data->wakeup_gpio) { + dev_dbg(&data->client->dev, "Sleep disabled"); + return -ENOSYS; + } + + dev_dbg(&data->client->dev, "Requesting sleep"); + + mutex_lock(&data->lock); + ret = i2c_smbus_xfer(data->client->adapter, data->client->addr, + data->client->flags | I2C_CLIENT_PEC, + I2C_SMBUS_WRITE, chip_info->op_sleep, + I2C_SMBUS_BYTE, NULL); + mutex_unlock(&data->lock); + + return ret; +} + +static int mlx90614_wakeup(struct mlx90614_data *data) +{ + const struct mlx_chip_info *chip_info = data->chip_info; + + if (!data->wakeup_gpio) { + dev_dbg(&data->client->dev, "Wake-up disabled"); + return -ENOSYS; + } + + dev_dbg(&data->client->dev, "Requesting wake-up"); + + i2c_lock_bus(data->client->adapter, I2C_LOCK_ROOT_ADAPTER); + gpiod_direction_output(data->wakeup_gpio, 0); + msleep(chip_info->wakeup_delay_ms); + gpiod_direction_input(data->wakeup_gpio); + i2c_unlock_bus(data->client->adapter, I2C_LOCK_ROOT_ADAPTER); + + data->ready_timestamp = jiffies + + msecs_to_jiffies(MLX90614_TIMING_STARTUP); + + /* + * Quirk: the i2c controller may get confused right after the + * wake-up signal has been sent. As a workaround, do a dummy read. + * If the read fails, the controller will probably be reset so that + * further reads will work. + */ + i2c_smbus_read_word_data(data->client, chip_info->op_eeprom_config1); + + return 0; +} + +/* Return wake-up GPIO or NULL if sleep functionality should be disabled. */ +static struct gpio_desc *mlx90614_probe_wakeup(struct i2c_client *client) +{ + struct gpio_desc *gpio; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_BYTE)) { + dev_info(&client->dev, + "i2c adapter does not support SMBUS_WRITE_BYTE, sleep disabled"); + return NULL; + } + + gpio = devm_gpiod_get_optional(&client->dev, "wakeup", GPIOD_IN); + + if (IS_ERR(gpio)) { + dev_warn(&client->dev, + "gpio acquisition failed with error %ld, sleep disabled", + PTR_ERR(gpio)); + return NULL; + } else if (!gpio) { + dev_info(&client->dev, + "wakeup-gpio not found, sleep disabled"); + } + + return gpio; +} +#else +static inline int mlx90614_sleep(struct mlx90614_data *data) +{ + return -ENOSYS; +} +static inline int mlx90614_wakeup(struct mlx90614_data *data) +{ + return -ENOSYS; +} +static inline struct gpio_desc *mlx90614_probe_wakeup(struct i2c_client *client) +{ + return NULL; +} +#endif + +/* Return 0 for single sensor, 1 for dual sensor, <0 on error. */ +static int mlx90614_probe_num_ir_sensors(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct mlx90614_data *data = iio_priv(indio_dev); + const struct mlx_chip_info *chip_info = data->chip_info; + s32 ret; + + if (chip_info->dual_channel) + return 0; + + ret = i2c_smbus_read_word_data(client, chip_info->op_eeprom_config1); + + if (ret < 0) + return ret; + + return (ret & MLX90614_CONFIG_DUAL_MASK) ? 1 : 0; +} + +static int mlx90614_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct iio_dev *indio_dev; + struct mlx90614_data *data; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -EOPNOTSUPP; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + mutex_init(&data->lock); + data->wakeup_gpio = mlx90614_probe_wakeup(client); + data->chip_info = i2c_get_match_data(client); + + mlx90614_wakeup(data); + + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &mlx90614_info; + + ret = mlx90614_probe_num_ir_sensors(client); + switch (ret) { + case 0: + dev_dbg(&client->dev, "Found single sensor"); + indio_dev->channels = mlx90614_channels; + indio_dev->num_channels = 2; + break; + case 1: + dev_dbg(&client->dev, "Found dual sensor"); + indio_dev->channels = mlx90614_channels; + indio_dev->num_channels = 3; + break; + default: + return ret; + } + + if (data->wakeup_gpio) { + pm_runtime_set_autosuspend_delay(&client->dev, + MLX90614_AUTOSLEEP_DELAY); + pm_runtime_use_autosuspend(&client->dev); + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + } + + return iio_device_register(indio_dev); +} + +static void mlx90614_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct mlx90614_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + if (data->wakeup_gpio) { + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + mlx90614_sleep(data); + pm_runtime_set_suspended(&client->dev); + } +} + +static const struct mlx_chip_info mlx90614_chip_info = { + .op_eeprom_emissivity = MLX90614_OP_EEPROM | 0x04, + .op_eeprom_config1 = MLX90614_OP_EEPROM | 0x05, + .op_ram_ta = MLX90614_OP_RAM | 0x06, + .op_ram_tobj1 = MLX90614_OP_RAM | 0x07, + .op_ram_tobj2 = MLX90614_OP_RAM | 0x08, + .op_sleep = MLX90614_OP_SLEEP, + .dual_channel = true, + .wakeup_delay_ms = MLX90614_TIMING_WAKEUP, + .emissivity_max = 65535, + .fir_config_mask = MLX90614_CONFIG_FIR_MASK, + .iir_config_mask = MLX90614_CONFIG_IIR_MASK, + .iir_valid_offset = 0, + .iir_values = { 77, 31, 20, 15, 723, 153, 110, 86 }, + .iir_freqs = { + { 0, 150000 }, /* 13% ~= 0.15 Hz */ + { 0, 200000 }, /* 17% ~= 0.20 Hz */ + { 0, 310000 }, /* 25% ~= 0.31 Hz */ + { 0, 770000 }, /* 50% ~= 0.77 Hz */ + { 0, 860000 }, /* 57% ~= 0.86 Hz */ + { 1, 100000 }, /* 67% ~= 1.10 Hz */ + { 1, 530000 }, /* 80% ~= 1.53 Hz */ + { 7, 230000 } /* 100% ~= 7.23 Hz */ + }, +}; + +static const struct mlx_chip_info mlx90615_chip_info = { + .op_eeprom_emissivity = MLX90615_OP_EEPROM | 0x03, + .op_eeprom_config1 = MLX90615_OP_EEPROM | 0x02, + .op_ram_ta = MLX90615_OP_RAM | 0x06, + .op_ram_tobj1 = MLX90615_OP_RAM | 0x07, + .op_ram_tobj2 = MLX90615_OP_RAM | 0x08, + .op_sleep = MLX90615_OP_SLEEP, + .dual_channel = false, + .wakeup_delay_ms = MLX90615_TIMING_WAKEUP, + .emissivity_max = 16383, + .fir_config_mask = 0, /* MLX90615 FIR is fixed */ + .iir_config_mask = MLX90615_CONFIG_IIR_MASK, + /* IIR value 0 is FORBIDDEN COMBINATION on MLX90615 */ + .iir_valid_offset = 1, + .iir_values = { 500, 50, 30, 20, 15, 13, 10 }, + .iir_freqs = { + { 0, 100000 }, /* 14% ~= 0.10 Hz */ + { 0, 130000 }, /* 17% ~= 0.13 Hz */ + { 0, 150000 }, /* 20% ~= 0.15 Hz */ + { 0, 200000 }, /* 25% ~= 0.20 Hz */ + { 0, 300000 }, /* 33% ~= 0.30 Hz */ + { 0, 500000 }, /* 50% ~= 0.50 Hz */ + { 5, 000000 }, /* 100% ~= 5.00 Hz */ + }, +}; + +static const struct i2c_device_id mlx90614_id[] = { + { .name = "mlx90614", .driver_data = (kernel_ulong_t)&mlx90614_chip_info }, + { .name = "mlx90615", .driver_data = (kernel_ulong_t)&mlx90615_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mlx90614_id); + +static const struct of_device_id mlx90614_of_match[] = { + { .compatible = "melexis,mlx90614", .data = &mlx90614_chip_info }, + { .compatible = "melexis,mlx90615", .data = &mlx90615_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, mlx90614_of_match); + +static int mlx90614_pm_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mlx90614_data *data = iio_priv(indio_dev); + + if (data->wakeup_gpio && pm_runtime_active(dev)) + return mlx90614_sleep(data); + + return 0; +} + +static int mlx90614_pm_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mlx90614_data *data = iio_priv(indio_dev); + int err; + + if (data->wakeup_gpio) { + err = mlx90614_wakeup(data); + if (err < 0) + return err; + + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + } + + return 0; +} + +static int mlx90614_pm_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mlx90614_data *data = iio_priv(indio_dev); + + return mlx90614_sleep(data); +} + +static int mlx90614_pm_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mlx90614_data *data = iio_priv(indio_dev); + + return mlx90614_wakeup(data); +} + +static const struct dev_pm_ops mlx90614_pm_ops = { + SYSTEM_SLEEP_PM_OPS(mlx90614_pm_suspend, mlx90614_pm_resume) + RUNTIME_PM_OPS(mlx90614_pm_runtime_suspend, + mlx90614_pm_runtime_resume, NULL) +}; + +static struct i2c_driver mlx90614_driver = { + .driver = { + .name = "mlx90614", + .of_match_table = mlx90614_of_match, + .pm = pm_ptr(&mlx90614_pm_ops), + }, + .probe = mlx90614_probe, + .remove = mlx90614_remove, + .id_table = mlx90614_id, +}; +module_i2c_driver(mlx90614_driver); + +MODULE_AUTHOR("Peter Meerwald "); +MODULE_AUTHOR("Vianney le Clément de Saint-Marcq "); +MODULE_AUTHOR("Crt Mori "); +MODULE_DESCRIPTION("Melexis MLX90614 contactless IR temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/temperature/mlx90632.c b/drivers/iio/temperature/mlx90632.c new file mode 100644 index 000000000..2fde48f33 --- /dev/null +++ b/drivers/iio/temperature/mlx90632.c @@ -0,0 +1,1343 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mlx90632.c - Melexis MLX90632 contactless IR temperature sensor + * + * Copyright (c) 2017 Melexis + * + * Driver for the Melexis MLX90632 I2C 16-bit IR thermopile sensor + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* Memory sections addresses */ +#define MLX90632_ADDR_RAM 0x4000 /* Start address of ram */ +#define MLX90632_ADDR_EEPROM 0x2480 /* Start address of user eeprom */ + +/* EEPROM addresses - used at startup */ +#define MLX90632_EE_CTRL 0x24d4 /* Control register initial value */ +#define MLX90632_EE_I2C_ADDR 0x24d5 /* I2C address register initial value */ +#define MLX90632_EE_VERSION 0x240b /* EEPROM version reg address */ +#define MLX90632_EE_P_R 0x240c /* P_R calibration register 32bit */ +#define MLX90632_EE_P_G 0x240e /* P_G calibration register 32bit */ +#define MLX90632_EE_P_T 0x2410 /* P_T calibration register 32bit */ +#define MLX90632_EE_P_O 0x2412 /* P_O calibration register 32bit */ +#define MLX90632_EE_Aa 0x2414 /* Aa calibration register 32bit */ +#define MLX90632_EE_Ab 0x2416 /* Ab calibration register 32bit */ +#define MLX90632_EE_Ba 0x2418 /* Ba calibration register 32bit */ +#define MLX90632_EE_Bb 0x241a /* Bb calibration register 32bit */ +#define MLX90632_EE_Ca 0x241c /* Ca calibration register 32bit */ +#define MLX90632_EE_Cb 0x241e /* Cb calibration register 32bit */ +#define MLX90632_EE_Da 0x2420 /* Da calibration register 32bit */ +#define MLX90632_EE_Db 0x2422 /* Db calibration register 32bit */ +#define MLX90632_EE_Ea 0x2424 /* Ea calibration register 32bit */ +#define MLX90632_EE_Eb 0x2426 /* Eb calibration register 32bit */ +#define MLX90632_EE_Fa 0x2428 /* Fa calibration register 32bit */ +#define MLX90632_EE_Fb 0x242a /* Fb calibration register 32bit */ +#define MLX90632_EE_Ga 0x242c /* Ga calibration register 32bit */ + +#define MLX90632_EE_Gb 0x242e /* Gb calibration register 16bit */ +#define MLX90632_EE_Ka 0x242f /* Ka calibration register 16bit */ + +#define MLX90632_EE_Ha 0x2481 /* Ha customer calib value reg 16bit */ +#define MLX90632_EE_Hb 0x2482 /* Hb customer calib value reg 16bit */ + +#define MLX90632_EE_MEDICAL_MEAS1 0x24E1 /* Medical measurement 1 16bit */ +#define MLX90632_EE_MEDICAL_MEAS2 0x24E2 /* Medical measurement 2 16bit */ +#define MLX90632_EE_EXTENDED_MEAS1 0x24F1 /* Extended measurement 1 16bit */ +#define MLX90632_EE_EXTENDED_MEAS2 0x24F2 /* Extended measurement 2 16bit */ +#define MLX90632_EE_EXTENDED_MEAS3 0x24F3 /* Extended measurement 3 16bit */ + +/* Register addresses - volatile */ +#define MLX90632_REG_I2C_ADDR 0x3000 /* Chip I2C address register */ + +/* Control register address - volatile */ +#define MLX90632_REG_CONTROL 0x3001 /* Control Register address */ +#define MLX90632_CFG_PWR_MASK GENMASK(2, 1) /* PowerMode Mask */ +#define MLX90632_CFG_MTYP_MASK GENMASK(8, 4) /* Meas select Mask */ +#define MLX90632_CFG_SOB_MASK BIT(11) + +/* PowerModes statuses */ +#define MLX90632_PWR_STATUS(ctrl_val) (ctrl_val << 1) +#define MLX90632_PWR_STATUS_HALT MLX90632_PWR_STATUS(0) /* hold */ +#define MLX90632_PWR_STATUS_SLEEP_STEP MLX90632_PWR_STATUS(1) /* sleep step */ +#define MLX90632_PWR_STATUS_STEP MLX90632_PWR_STATUS(2) /* step */ +#define MLX90632_PWR_STATUS_CONTINUOUS MLX90632_PWR_STATUS(3) /* continuous */ + +#define MLX90632_EE_RR GENMASK(10, 8) /* Only Refresh Rate bits */ +#define MLX90632_REFRESH_RATE(ee_val) FIELD_GET(MLX90632_EE_RR, ee_val) + /* Extract Refresh Rate from ee register */ +#define MLX90632_REFRESH_RATE_STATUS(refresh_rate) (refresh_rate << 8) + +/* Measurement types */ +#define MLX90632_MTYP_MEDICAL 0 +#define MLX90632_MTYP_EXTENDED 17 + +/* Measurement type select*/ +#define MLX90632_MTYP_STATUS(ctrl_val) (ctrl_val << 4) +#define MLX90632_MTYP_STATUS_MEDICAL MLX90632_MTYP_STATUS(MLX90632_MTYP_MEDICAL) +#define MLX90632_MTYP_STATUS_EXTENDED MLX90632_MTYP_STATUS(MLX90632_MTYP_EXTENDED) + +/* I2C command register - volatile */ +#define MLX90632_REG_I2C_CMD 0x3005 /* I2C command Register address */ + +/* Device status register - volatile */ +#define MLX90632_REG_STATUS 0x3fff /* Device status register */ +#define MLX90632_STAT_BUSY BIT(10) /* Device busy indicator */ +#define MLX90632_STAT_EE_BUSY BIT(9) /* EEPROM busy indicator */ +#define MLX90632_STAT_BRST BIT(8) /* Brown out reset indicator */ +#define MLX90632_STAT_CYCLE_POS GENMASK(6, 2) /* Data position */ +#define MLX90632_STAT_DATA_RDY BIT(0) /* Data ready indicator */ + +/* RAM_MEAS address-es for each channel */ +#define MLX90632_RAM_1(meas_num) (MLX90632_ADDR_RAM + 3 * meas_num) +#define MLX90632_RAM_2(meas_num) (MLX90632_ADDR_RAM + 3 * meas_num + 1) +#define MLX90632_RAM_3(meas_num) (MLX90632_ADDR_RAM + 3 * meas_num + 2) + +/* Name important RAM_MEAS channels */ +#define MLX90632_RAM_DSP5_EXTENDED_AMBIENT_1 MLX90632_RAM_3(17) +#define MLX90632_RAM_DSP5_EXTENDED_AMBIENT_2 MLX90632_RAM_3(18) +#define MLX90632_RAM_DSP5_EXTENDED_OBJECT_1 MLX90632_RAM_1(17) +#define MLX90632_RAM_DSP5_EXTENDED_OBJECT_2 MLX90632_RAM_2(17) +#define MLX90632_RAM_DSP5_EXTENDED_OBJECT_3 MLX90632_RAM_1(18) +#define MLX90632_RAM_DSP5_EXTENDED_OBJECT_4 MLX90632_RAM_2(18) +#define MLX90632_RAM_DSP5_EXTENDED_OBJECT_5 MLX90632_RAM_1(19) +#define MLX90632_RAM_DSP5_EXTENDED_OBJECT_6 MLX90632_RAM_2(19) + +/* Magic constants */ +#define MLX90632_ID_MEDICAL 0x0105 /* EEPROM DSPv5 Medical device id */ +#define MLX90632_ID_CONSUMER 0x0205 /* EEPROM DSPv5 Consumer device id */ +#define MLX90632_ID_EXTENDED 0x0505 /* EEPROM DSPv5 Extended range device id */ +#define MLX90632_ID_MASK GENMASK(14, 0) /* DSP version and device ID in EE_VERSION */ +#define MLX90632_DSP_VERSION 5 /* DSP version */ +#define MLX90632_DSP_MASK GENMASK(7, 0) /* DSP version in EE_VERSION */ +#define MLX90632_RESET_CMD 0x0006 /* Reset sensor (address or global) */ +#define MLX90632_REF_12 12LL /* ResCtrlRef value of Ch 1 or Ch 2 */ +#define MLX90632_REF_3 12LL /* ResCtrlRef value of Channel 3 */ +#define MLX90632_MAX_MEAS_NUM 31 /* Maximum measurements in list */ +#define MLX90632_SLEEP_DELAY_MS 6000 /* Autosleep delay */ +#define MLX90632_EXTENDED_LIMIT 27000 /* Extended mode raw value limit */ +#define MLX90632_MEAS_MAX_TIME 2000 /* Max measurement time in ms for the lowest refresh rate */ + +/** + * struct mlx90632_data - private data for the MLX90632 device + * @client: I2C client of the device + * @lock: Internal mutex for multiple reads for single measurement + * @regmap: Regmap of the device + * @emissivity: Object emissivity from 0 to 1000 where 1000 = 1. + * @mtyp: Measurement type physical sensor configuration for extended range + * calculations + * @object_ambient_temperature: Ambient temperature at object (might differ of + * the ambient temperature of sensor. + * @regulator: Regulator of the device + * @powerstatus: Current POWER status of the device + * @interaction_ts: Timestamp of the last temperature read that is used + * for power management in jiffies + */ +struct mlx90632_data { + struct i2c_client *client; + struct mutex lock; + struct regmap *regmap; + u16 emissivity; + u8 mtyp; + u32 object_ambient_temperature; + struct regulator *regulator; + int powerstatus; + unsigned long interaction_ts; +}; + +static const struct regmap_range mlx90632_volatile_reg_range[] = { + regmap_reg_range(MLX90632_REG_I2C_ADDR, MLX90632_REG_CONTROL), + regmap_reg_range(MLX90632_REG_I2C_CMD, MLX90632_REG_I2C_CMD), + regmap_reg_range(MLX90632_REG_STATUS, MLX90632_REG_STATUS), + regmap_reg_range(MLX90632_RAM_1(0), + MLX90632_RAM_3(MLX90632_MAX_MEAS_NUM)), +}; + +static const struct regmap_access_table mlx90632_volatile_regs_tbl = { + .yes_ranges = mlx90632_volatile_reg_range, + .n_yes_ranges = ARRAY_SIZE(mlx90632_volatile_reg_range), +}; + +static const struct regmap_range mlx90632_read_reg_range[] = { + regmap_reg_range(MLX90632_EE_VERSION, MLX90632_EE_Ka), + regmap_reg_range(MLX90632_EE_CTRL, MLX90632_EE_I2C_ADDR), + regmap_reg_range(MLX90632_EE_Ha, MLX90632_EE_Hb), + regmap_reg_range(MLX90632_EE_MEDICAL_MEAS1, MLX90632_EE_MEDICAL_MEAS2), + regmap_reg_range(MLX90632_EE_EXTENDED_MEAS1, MLX90632_EE_EXTENDED_MEAS3), + regmap_reg_range(MLX90632_REG_I2C_ADDR, MLX90632_REG_CONTROL), + regmap_reg_range(MLX90632_REG_I2C_CMD, MLX90632_REG_I2C_CMD), + regmap_reg_range(MLX90632_REG_STATUS, MLX90632_REG_STATUS), + regmap_reg_range(MLX90632_RAM_1(0), + MLX90632_RAM_3(MLX90632_MAX_MEAS_NUM)), +}; + +static const struct regmap_access_table mlx90632_readable_regs_tbl = { + .yes_ranges = mlx90632_read_reg_range, + .n_yes_ranges = ARRAY_SIZE(mlx90632_read_reg_range), +}; + +static const struct regmap_range mlx90632_no_write_reg_range[] = { + regmap_reg_range(MLX90632_EE_VERSION, MLX90632_EE_Ka), + regmap_reg_range(MLX90632_RAM_1(0), + MLX90632_RAM_3(MLX90632_MAX_MEAS_NUM)), +}; + +static const struct regmap_access_table mlx90632_writeable_regs_tbl = { + .no_ranges = mlx90632_no_write_reg_range, + .n_no_ranges = ARRAY_SIZE(mlx90632_no_write_reg_range), +}; + +static const struct regmap_config mlx90632_regmap = { + .reg_bits = 16, + .val_bits = 16, + + .volatile_table = &mlx90632_volatile_regs_tbl, + .rd_table = &mlx90632_readable_regs_tbl, + .wr_table = &mlx90632_writeable_regs_tbl, + + .use_single_read = true, + .use_single_write = true, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_BIG, + .cache_type = REGCACHE_RBTREE, +}; + +static int mlx90632_pwr_set_sleep_step(struct regmap *regmap) +{ + struct mlx90632_data *data = + iio_priv(dev_get_drvdata(regmap_get_device(regmap))); + int ret; + + if (data->powerstatus == MLX90632_PWR_STATUS_SLEEP_STEP) + return 0; + + ret = regmap_write_bits(regmap, MLX90632_REG_CONTROL, MLX90632_CFG_PWR_MASK, + MLX90632_PWR_STATUS_SLEEP_STEP); + if (ret < 0) + return ret; + + data->powerstatus = MLX90632_PWR_STATUS_SLEEP_STEP; + return 0; +} + +static int mlx90632_pwr_continuous(struct regmap *regmap) +{ + struct mlx90632_data *data = + iio_priv(dev_get_drvdata(regmap_get_device(regmap))); + int ret; + + if (data->powerstatus == MLX90632_PWR_STATUS_CONTINUOUS) + return 0; + + ret = regmap_write_bits(regmap, MLX90632_REG_CONTROL, MLX90632_CFG_PWR_MASK, + MLX90632_PWR_STATUS_CONTINUOUS); + if (ret < 0) + return ret; + + data->powerstatus = MLX90632_PWR_STATUS_CONTINUOUS; + return 0; +} + +/** + * mlx90632_reset_delay() - Give the mlx90632 some time to reset properly + * If this is not done, the following I2C command(s) will not be accepted. + */ +static void mlx90632_reset_delay(void) +{ + usleep_range(150, 200); +} + +static int mlx90632_get_measurement_time(struct regmap *regmap, u16 meas) +{ + unsigned int reg; + int ret; + + ret = regmap_read(regmap, meas, ®); + if (ret < 0) + return ret; + + return MLX90632_MEAS_MAX_TIME >> FIELD_GET(MLX90632_EE_RR, reg); +} + +static int mlx90632_calculate_dataset_ready_time(struct mlx90632_data *data) +{ + unsigned int refresh_time; + int ret; + + if (data->mtyp == MLX90632_MTYP_MEDICAL) { + ret = mlx90632_get_measurement_time(data->regmap, + MLX90632_EE_MEDICAL_MEAS1); + if (ret < 0) + return ret; + + refresh_time = ret; + + ret = mlx90632_get_measurement_time(data->regmap, + MLX90632_EE_MEDICAL_MEAS2); + if (ret < 0) + return ret; + + refresh_time += ret; + } else { + ret = mlx90632_get_measurement_time(data->regmap, + MLX90632_EE_EXTENDED_MEAS1); + if (ret < 0) + return ret; + + refresh_time = ret; + + ret = mlx90632_get_measurement_time(data->regmap, + MLX90632_EE_EXTENDED_MEAS2); + if (ret < 0) + return ret; + + refresh_time += ret; + + ret = mlx90632_get_measurement_time(data->regmap, + MLX90632_EE_EXTENDED_MEAS3); + if (ret < 0) + return ret; + + refresh_time += ret; + } + + return refresh_time; +} + +/** + * mlx90632_perform_measurement() - Trigger and retrieve current measurement cycle + * @data: pointer to mlx90632_data object containing regmap information + * + * Perform a measurement and return latest measurement cycle position reported + * by sensor. This is a blocking function for 500ms, as that is default sensor + * refresh rate. + */ +static int mlx90632_perform_measurement(struct mlx90632_data *data) +{ + unsigned int reg_status; + int ret; + + ret = regmap_clear_bits(data->regmap, MLX90632_REG_STATUS, + MLX90632_STAT_DATA_RDY); + if (ret < 0) + return ret; + + ret = regmap_read_poll_timeout(data->regmap, MLX90632_REG_STATUS, reg_status, + !(reg_status & MLX90632_STAT_DATA_RDY), 10000, + 100 * 10000); + + if (ret < 0) { + dev_err(&data->client->dev, "data not ready"); + return -ETIMEDOUT; + } + + return (reg_status & MLX90632_STAT_CYCLE_POS) >> 2; +} + +/** + * mlx90632_perform_measurement_burst() - Trigger and retrieve current measurement + * cycle in step sleep mode + * @data: pointer to mlx90632_data object containing regmap information + * + * Perform a measurement and return 2 as measurement cycle position reported + * by sensor. This is a blocking function for amount dependent on the sensor + * refresh rate. + */ +static int mlx90632_perform_measurement_burst(struct mlx90632_data *data) +{ + unsigned int reg_status; + int ret; + + ret = regmap_write_bits(data->regmap, MLX90632_REG_CONTROL, + MLX90632_CFG_SOB_MASK, MLX90632_CFG_SOB_MASK); + if (ret < 0) + return ret; + + ret = mlx90632_calculate_dataset_ready_time(data); + if (ret < 0) + return ret; + + msleep(ret); /* Wait minimum time for dataset to be ready */ + + ret = regmap_read_poll_timeout(data->regmap, MLX90632_REG_STATUS, + reg_status, + (reg_status & MLX90632_STAT_BUSY) == 0, + 10000, 100 * 10000); + if (ret < 0) { + dev_err(&data->client->dev, "data not ready"); + return -ETIMEDOUT; + } + + return 2; +} + +static int mlx90632_set_meas_type(struct mlx90632_data *data, u8 type) +{ + int current_powerstatus; + int ret; + + if (data->mtyp == type) + return 0; + + current_powerstatus = data->powerstatus; + ret = mlx90632_pwr_continuous(data->regmap); + if (ret < 0) + return ret; + + ret = regmap_write(data->regmap, MLX90632_REG_I2C_CMD, MLX90632_RESET_CMD); + if (ret < 0) + return ret; + + mlx90632_reset_delay(); + + ret = regmap_update_bits(data->regmap, MLX90632_REG_CONTROL, + (MLX90632_CFG_MTYP_MASK | MLX90632_CFG_PWR_MASK), + (MLX90632_MTYP_STATUS(type) | MLX90632_PWR_STATUS_HALT)); + if (ret < 0) + return ret; + + data->mtyp = type; + data->powerstatus = MLX90632_PWR_STATUS_HALT; + + if (current_powerstatus == MLX90632_PWR_STATUS_SLEEP_STEP) + return mlx90632_pwr_set_sleep_step(data->regmap); + + return mlx90632_pwr_continuous(data->regmap); +} + +static int mlx90632_channel_new_select(int perform_ret, uint8_t *channel_new, + uint8_t *channel_old) +{ + switch (perform_ret) { + case 1: + *channel_new = 1; + *channel_old = 2; + break; + case 2: + *channel_new = 2; + *channel_old = 1; + break; + default: + return -ECHRNG; + } + + return 0; +} + +static int mlx90632_read_ambient_raw(struct regmap *regmap, + s16 *ambient_new_raw, s16 *ambient_old_raw) +{ + unsigned int read_tmp; + int ret; + + ret = regmap_read(regmap, MLX90632_RAM_3(1), &read_tmp); + if (ret < 0) + return ret; + *ambient_new_raw = (s16)read_tmp; + + ret = regmap_read(regmap, MLX90632_RAM_3(2), &read_tmp); + if (ret < 0) + return ret; + *ambient_old_raw = (s16)read_tmp; + + return ret; +} + +static int mlx90632_read_object_raw(struct regmap *regmap, + int perform_measurement_ret, + s16 *object_new_raw, s16 *object_old_raw) +{ + unsigned int read_tmp; + u8 channel_old = 0; + u8 channel = 0; + s16 read; + int ret; + + ret = mlx90632_channel_new_select(perform_measurement_ret, &channel, + &channel_old); + if (ret != 0) + return ret; + + ret = regmap_read(regmap, MLX90632_RAM_2(channel), &read_tmp); + if (ret < 0) + return ret; + + read = (s16)read_tmp; + + ret = regmap_read(regmap, MLX90632_RAM_1(channel), &read_tmp); + if (ret < 0) + return ret; + *object_new_raw = (read + (s16)read_tmp) / 2; + + ret = regmap_read(regmap, MLX90632_RAM_2(channel_old), &read_tmp); + if (ret < 0) + return ret; + read = (s16)read_tmp; + + ret = regmap_read(regmap, MLX90632_RAM_1(channel_old), &read_tmp); + if (ret < 0) + return ret; + *object_old_raw = (read + (s16)read_tmp) / 2; + + return ret; +} + +static int mlx90632_read_all_channel(struct mlx90632_data *data, + s16 *ambient_new_raw, s16 *ambient_old_raw, + s16 *object_new_raw, s16 *object_old_raw) +{ + s32 measurement; + int ret; + + mutex_lock(&data->lock); + ret = mlx90632_set_meas_type(data, MLX90632_MTYP_MEDICAL); + if (ret < 0) + goto read_unlock; + + switch (data->powerstatus) { + case MLX90632_PWR_STATUS_CONTINUOUS: + ret = mlx90632_perform_measurement(data); + if (ret < 0) + goto read_unlock; + + break; + case MLX90632_PWR_STATUS_SLEEP_STEP: + ret = mlx90632_perform_measurement_burst(data); + if (ret < 0) + goto read_unlock; + + break; + default: + ret = -EOPNOTSUPP; + goto read_unlock; + } + + measurement = ret; /* If we came here ret holds the measurement position */ + + ret = mlx90632_read_ambient_raw(data->regmap, ambient_new_raw, + ambient_old_raw); + if (ret < 0) + goto read_unlock; + + ret = mlx90632_read_object_raw(data->regmap, measurement, + object_new_raw, object_old_raw); +read_unlock: + mutex_unlock(&data->lock); + return ret; +} + +static int mlx90632_read_ambient_raw_extended(struct regmap *regmap, + s16 *ambient_new_raw, s16 *ambient_old_raw) +{ + unsigned int read_tmp; + int ret; + + ret = regmap_read(regmap, MLX90632_RAM_DSP5_EXTENDED_AMBIENT_1, &read_tmp); + if (ret < 0) + return ret; + *ambient_new_raw = (s16)read_tmp; + + ret = regmap_read(regmap, MLX90632_RAM_DSP5_EXTENDED_AMBIENT_2, &read_tmp); + if (ret < 0) + return ret; + *ambient_old_raw = (s16)read_tmp; + + return 0; +} + +static int mlx90632_read_object_raw_extended(struct regmap *regmap, s16 *object_new_raw) +{ + unsigned int read_tmp; + s32 read; + int ret; + + ret = regmap_read(regmap, MLX90632_RAM_DSP5_EXTENDED_OBJECT_1, &read_tmp); + if (ret < 0) + return ret; + read = (s16)read_tmp; + + ret = regmap_read(regmap, MLX90632_RAM_DSP5_EXTENDED_OBJECT_2, &read_tmp); + if (ret < 0) + return ret; + read = read - (s16)read_tmp; + + ret = regmap_read(regmap, MLX90632_RAM_DSP5_EXTENDED_OBJECT_3, &read_tmp); + if (ret < 0) + return ret; + read = read - (s16)read_tmp; + + ret = regmap_read(regmap, MLX90632_RAM_DSP5_EXTENDED_OBJECT_4, &read_tmp); + if (ret < 0) + return ret; + read = (read + (s16)read_tmp) / 2; + + ret = regmap_read(regmap, MLX90632_RAM_DSP5_EXTENDED_OBJECT_5, &read_tmp); + if (ret < 0) + return ret; + read = read + (s16)read_tmp; + + ret = regmap_read(regmap, MLX90632_RAM_DSP5_EXTENDED_OBJECT_6, &read_tmp); + if (ret < 0) + return ret; + read = read + (s16)read_tmp; + + if (read > S16_MAX || read < S16_MIN) + return -ERANGE; + + *object_new_raw = read; + + return 0; +} + +static int mlx90632_read_all_channel_extended(struct mlx90632_data *data, s16 *object_new_raw, + s16 *ambient_new_raw, s16 *ambient_old_raw) +{ + s32 ret, meas; + + mutex_lock(&data->lock); + ret = mlx90632_set_meas_type(data, MLX90632_MTYP_EXTENDED); + if (ret < 0) + goto read_unlock; + + switch (data->powerstatus) { + case MLX90632_PWR_STATUS_CONTINUOUS: + ret = read_poll_timeout(mlx90632_perform_measurement, meas, meas == 19, + 50000, 800000, false, data); + if (ret) + goto read_unlock; + break; + case MLX90632_PWR_STATUS_SLEEP_STEP: + ret = mlx90632_perform_measurement_burst(data); + if (ret < 0) + goto read_unlock; + break; + default: + ret = -EOPNOTSUPP; + goto read_unlock; + } + + ret = mlx90632_read_object_raw_extended(data->regmap, object_new_raw); + if (ret < 0) + goto read_unlock; + + ret = mlx90632_read_ambient_raw_extended(data->regmap, ambient_new_raw, ambient_old_raw); + +read_unlock: + mutex_unlock(&data->lock); + return ret; +} + +static int mlx90632_read_ee_register(struct regmap *regmap, u16 reg_lsb, + s32 *reg_value) +{ + unsigned int read; + u32 value; + int ret; + + ret = regmap_read(regmap, reg_lsb, &read); + if (ret < 0) + return ret; + + value = read; + + ret = regmap_read(regmap, reg_lsb + 1, &read); + if (ret < 0) + return ret; + + *reg_value = (read << 16) | (value & 0xffff); + + return 0; +} + +static s64 mlx90632_preprocess_temp_amb(s16 ambient_new_raw, + s16 ambient_old_raw, s16 Gb) +{ + s64 VR_Ta, kGb, tmp; + + kGb = ((s64)Gb * 1000LL) >> 10ULL; + VR_Ta = (s64)ambient_old_raw * 1000000LL + + kGb * div64_s64(((s64)ambient_new_raw * 1000LL), + (MLX90632_REF_3)); + tmp = div64_s64( + div64_s64(((s64)ambient_new_raw * 1000000000000LL), + (MLX90632_REF_3)), VR_Ta); + return div64_s64(tmp << 19ULL, 1000LL); +} + +static s64 mlx90632_preprocess_temp_obj(s16 object_new_raw, s16 object_old_raw, + s16 ambient_new_raw, + s16 ambient_old_raw, s16 Ka) +{ + s64 VR_IR, kKa, tmp; + + kKa = ((s64)Ka * 1000LL) >> 10ULL; + VR_IR = (s64)ambient_old_raw * 1000000LL + + kKa * div64_s64(((s64)ambient_new_raw * 1000LL), + (MLX90632_REF_3)); + tmp = div64_s64( + div64_s64(((s64)((object_new_raw + object_old_raw) / 2) + * 1000000000000LL), (MLX90632_REF_12)), + VR_IR); + return div64_s64((tmp << 19ULL), 1000LL); +} + +static s64 mlx90632_preprocess_temp_obj_extended(s16 object_new_raw, s16 ambient_new_raw, + s16 ambient_old_raw, s16 Ka) +{ + s64 VR_IR, kKa, tmp; + + kKa = ((s64)Ka * 1000LL) >> 10ULL; + VR_IR = (s64)ambient_old_raw * 1000000LL + + kKa * div64_s64((s64)ambient_new_raw * 1000LL, + MLX90632_REF_3); + tmp = div64_s64( + div64_s64((s64) object_new_raw * 1000000000000LL, MLX90632_REF_12), + VR_IR); + return div64_s64(tmp << 19ULL, 1000LL); +} + +static s32 mlx90632_calc_temp_ambient(s16 ambient_new_raw, s16 ambient_old_raw, + s32 P_T, s32 P_R, s32 P_G, s32 P_O, s16 Gb) +{ + s64 Asub, Bsub, Ablock, Bblock, Cblock, AMB, sum; + + AMB = mlx90632_preprocess_temp_amb(ambient_new_raw, ambient_old_raw, + Gb); + Asub = ((s64)P_T * 10000000000LL) >> 44ULL; + Bsub = AMB - (((s64)P_R * 1000LL) >> 8ULL); + Ablock = Asub * (Bsub * Bsub); + Bblock = (div64_s64(Bsub * 10000000LL, P_G)) << 20ULL; + Cblock = ((s64)P_O * 10000000000LL) >> 8ULL; + + sum = div64_s64(Ablock, 1000000LL) + Bblock + Cblock; + + return div64_s64(sum, 10000000LL); +} + +static s32 mlx90632_calc_temp_object_iteration(s32 prev_object_temp, s64 object, + s64 TAdut, s64 TAdut4, s32 Fa, s32 Fb, + s32 Ga, s16 Ha, s16 Hb, + u16 emissivity) +{ + s64 calcedKsTO, calcedKsTA, ir_Alpha, Alpha_corr; + s64 Ha_customer, Hb_customer; + + Ha_customer = ((s64)Ha * 1000000LL) >> 14ULL; + Hb_customer = ((s64)Hb * 100) >> 10ULL; + + calcedKsTO = ((s64)((s64)Ga * (prev_object_temp - 25 * 1000LL) + * 1000LL)) >> 36LL; + calcedKsTA = ((s64)(Fb * (TAdut - 25 * 1000000LL))) >> 36LL; + Alpha_corr = div64_s64((((s64)(Fa * 10000000000LL) >> 46LL) + * Ha_customer), 1000LL); + Alpha_corr *= ((s64)(1 * 1000000LL + calcedKsTO + calcedKsTA)); + Alpha_corr = emissivity * div64_s64(Alpha_corr, 100000LL); + Alpha_corr = div64_s64(Alpha_corr, 1000LL); + ir_Alpha = div64_s64((s64)object * 10000000LL, Alpha_corr); + + return (int_sqrt64(int_sqrt64(ir_Alpha * 1000000000000LL + TAdut4)) + - 27315 - Hb_customer) * 10; +} + +static s64 mlx90632_calc_ta4(s64 TAdut, s64 scale) +{ + return (div64_s64(TAdut, scale) + 27315) * + (div64_s64(TAdut, scale) + 27315) * + (div64_s64(TAdut, scale) + 27315) * + (div64_s64(TAdut, scale) + 27315); +} + +static s32 mlx90632_calc_temp_object(s64 object, s64 ambient, s32 Ea, s32 Eb, + s32 Fa, s32 Fb, s32 Ga, s16 Ha, s16 Hb, + u16 tmp_emi) +{ + s64 kTA, kTA0, TAdut, TAdut4; + s64 temp = 25000; + s8 i; + + kTA = (Ea * 1000LL) >> 16LL; + kTA0 = (Eb * 1000LL) >> 8LL; + TAdut = div64_s64(((ambient - kTA0) * 1000000LL), kTA) + 25 * 1000000LL; + TAdut4 = mlx90632_calc_ta4(TAdut, 10000LL); + + /* Iterations of calculation as described in datasheet */ + for (i = 0; i < 5; ++i) { + temp = mlx90632_calc_temp_object_iteration(temp, object, TAdut, TAdut4, + Fa, Fb, Ga, Ha, Hb, + tmp_emi); + } + return temp; +} + +static s32 mlx90632_calc_temp_object_extended(s64 object, s64 ambient, s64 reflected, + s32 Ea, s32 Eb, s32 Fa, s32 Fb, s32 Ga, + s16 Ha, s16 Hb, u16 tmp_emi) +{ + s64 kTA, kTA0, TAdut, TAdut4, Tr4, TaTr4; + s64 temp = 25000; + s8 i; + + kTA = (Ea * 1000LL) >> 16LL; + kTA0 = (Eb * 1000LL) >> 8LL; + TAdut = div64_s64((ambient - kTA0) * 1000000LL, kTA) + 25 * 1000000LL; + Tr4 = mlx90632_calc_ta4(reflected, 10); + TAdut4 = mlx90632_calc_ta4(TAdut, 10000LL); + TaTr4 = Tr4 - div64_s64(Tr4 - TAdut4, tmp_emi) * 1000; + + /* Iterations of calculation as described in datasheet */ + for (i = 0; i < 5; ++i) { + temp = mlx90632_calc_temp_object_iteration(temp, object, TAdut, TaTr4, + Fa / 2, Fb, Ga, Ha, Hb, + tmp_emi); + } + + return temp; +} + +static int mlx90632_calc_object_dsp105(struct mlx90632_data *data, int *val) +{ + s16 ambient_new_raw, ambient_old_raw, object_new_raw, object_old_raw; + s32 Ea, Eb, Fa, Fb, Ga; + unsigned int read_tmp; + s64 object, ambient; + s16 Ha, Hb, Gb, Ka; + int ret; + + ret = mlx90632_read_ee_register(data->regmap, MLX90632_EE_Ea, &Ea); + if (ret < 0) + return ret; + ret = mlx90632_read_ee_register(data->regmap, MLX90632_EE_Eb, &Eb); + if (ret < 0) + return ret; + ret = mlx90632_read_ee_register(data->regmap, MLX90632_EE_Fa, &Fa); + if (ret < 0) + return ret; + ret = mlx90632_read_ee_register(data->regmap, MLX90632_EE_Fb, &Fb); + if (ret < 0) + return ret; + ret = mlx90632_read_ee_register(data->regmap, MLX90632_EE_Ga, &Ga); + if (ret < 0) + return ret; + ret = regmap_read(data->regmap, MLX90632_EE_Ha, &read_tmp); + if (ret < 0) + return ret; + Ha = (s16)read_tmp; + ret = regmap_read(data->regmap, MLX90632_EE_Hb, &read_tmp); + if (ret < 0) + return ret; + Hb = (s16)read_tmp; + ret = regmap_read(data->regmap, MLX90632_EE_Gb, &read_tmp); + if (ret < 0) + return ret; + Gb = (s16)read_tmp; + ret = regmap_read(data->regmap, MLX90632_EE_Ka, &read_tmp); + if (ret < 0) + return ret; + Ka = (s16)read_tmp; + + ret = mlx90632_read_all_channel(data, + &ambient_new_raw, &ambient_old_raw, + &object_new_raw, &object_old_raw); + if (ret < 0) + return ret; + + if (object_new_raw > MLX90632_EXTENDED_LIMIT && + data->mtyp == MLX90632_MTYP_EXTENDED) { + ret = mlx90632_read_all_channel_extended(data, &object_new_raw, + &ambient_new_raw, &ambient_old_raw); + if (ret < 0) + return ret; + + /* Use extended mode calculations */ + ambient = mlx90632_preprocess_temp_amb(ambient_new_raw, + ambient_old_raw, Gb); + object = mlx90632_preprocess_temp_obj_extended(object_new_raw, + ambient_new_raw, + ambient_old_raw, Ka); + *val = mlx90632_calc_temp_object_extended(object, ambient, + data->object_ambient_temperature, + Ea, Eb, Fa, Fb, Ga, + Ha, Hb, data->emissivity); + return 0; + } + + ambient = mlx90632_preprocess_temp_amb(ambient_new_raw, + ambient_old_raw, Gb); + object = mlx90632_preprocess_temp_obj(object_new_raw, + object_old_raw, + ambient_new_raw, + ambient_old_raw, Ka); + + *val = mlx90632_calc_temp_object(object, ambient, Ea, Eb, Fa, Fb, Ga, + Ha, Hb, data->emissivity); + return 0; +} + +static int mlx90632_calc_ambient_dsp105(struct mlx90632_data *data, int *val) +{ + s16 ambient_new_raw, ambient_old_raw; + unsigned int read_tmp; + s32 PT, PR, PG, PO; + int ret; + s16 Gb; + + ret = mlx90632_read_ee_register(data->regmap, MLX90632_EE_P_R, &PR); + if (ret < 0) + return ret; + ret = mlx90632_read_ee_register(data->regmap, MLX90632_EE_P_G, &PG); + if (ret < 0) + return ret; + ret = mlx90632_read_ee_register(data->regmap, MLX90632_EE_P_T, &PT); + if (ret < 0) + return ret; + ret = mlx90632_read_ee_register(data->regmap, MLX90632_EE_P_O, &PO); + if (ret < 0) + return ret; + ret = regmap_read(data->regmap, MLX90632_EE_Gb, &read_tmp); + if (ret < 0) + return ret; + Gb = (s16)read_tmp; + + ret = mlx90632_read_ambient_raw(data->regmap, &ambient_new_raw, + &ambient_old_raw); + if (ret < 0) + return ret; + *val = mlx90632_calc_temp_ambient(ambient_new_raw, ambient_old_raw, + PT, PR, PG, PO, Gb); + return ret; +} + +static int mlx90632_get_refresh_rate(struct mlx90632_data *data, + int *refresh_rate) +{ + unsigned int meas1; + int ret; + + ret = regmap_read(data->regmap, MLX90632_EE_MEDICAL_MEAS1, &meas1); + if (ret < 0) + return ret; + + *refresh_rate = MLX90632_REFRESH_RATE(meas1); + + return ret; +} + +static const int mlx90632_freqs[][2] = { + {0, 500000}, + {1, 0}, + {2, 0}, + {4, 0}, + {8, 0}, + {16, 0}, + {32, 0}, + {64, 0} +}; + +/** + * mlx90632_pm_interraction_wakeup() - Measure time between user interactions to change powermode + * @data: pointer to mlx90632_data object containing interaction_ts information + * + * Switch to continuous mode when interaction is faster than MLX90632_MEAS_MAX_TIME. Update the + * interaction_ts for each function call with the jiffies to enable measurement between function + * calls. Initial value of the interaction_ts needs to be set before this function call. + */ +static int mlx90632_pm_interraction_wakeup(struct mlx90632_data *data) +{ + unsigned long now; + int ret; + + now = jiffies; + if (time_in_range(now, data->interaction_ts, + data->interaction_ts + + msecs_to_jiffies(MLX90632_MEAS_MAX_TIME + 100))) { + if (data->powerstatus == MLX90632_PWR_STATUS_SLEEP_STEP) { + ret = mlx90632_pwr_continuous(data->regmap); + if (ret < 0) + return ret; + } + } + + data->interaction_ts = now; + + return 0; +} + +static int mlx90632_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct mlx90632_data *data = iio_priv(indio_dev); + int ret; + int cr; + + pm_runtime_get_sync(&data->client->dev); + ret = mlx90632_pm_interraction_wakeup(data); + if (ret < 0) + goto mlx90632_read_raw_pm; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (channel->channel2) { + case IIO_MOD_TEMP_AMBIENT: + ret = mlx90632_calc_ambient_dsp105(data, val); + if (ret < 0) + goto mlx90632_read_raw_pm; + + ret = IIO_VAL_INT; + break; + case IIO_MOD_TEMP_OBJECT: + ret = mlx90632_calc_object_dsp105(data, val); + if (ret < 0) + goto mlx90632_read_raw_pm; + + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + break; + } + break; + case IIO_CHAN_INFO_CALIBEMISSIVITY: + if (data->emissivity == 1000) { + *val = 1; + *val2 = 0; + } else { + *val = 0; + *val2 = data->emissivity * 1000; + } + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case IIO_CHAN_INFO_CALIBAMBIENT: + *val = data->object_ambient_temperature; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = mlx90632_get_refresh_rate(data, &cr); + if (ret < 0) + goto mlx90632_read_raw_pm; + + *val = mlx90632_freqs[cr][0]; + *val2 = mlx90632_freqs[cr][1]; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + ret = -EINVAL; + break; + } + +mlx90632_read_raw_pm: + pm_runtime_put_autosuspend(&data->client->dev); + return ret; +} + +static int mlx90632_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int val, + int val2, long mask) +{ + struct mlx90632_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_CALIBEMISSIVITY: + /* Confirm we are within 0 and 1.0 */ + if (val < 0 || val2 < 0 || val > 1 || + (val == 1 && val2 != 0)) + return -EINVAL; + data->emissivity = val * 1000 + val2 / 1000; + return 0; + case IIO_CHAN_INFO_CALIBAMBIENT: + data->object_ambient_temperature = val; + return 0; + default: + return -EINVAL; + } +} + +static int mlx90632_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = (int *)mlx90632_freqs; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = 2 * ARRAY_SIZE(mlx90632_freqs); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const struct iio_chan_spec mlx90632_channels[] = { + { + .type = IIO_TEMP, + .modified = 1, + .channel2 = IIO_MOD_TEMP_AMBIENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), + }, + { + .type = IIO_TEMP, + .modified = 1, + .channel2 = IIO_MOD_TEMP_OBJECT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_CALIBEMISSIVITY) | BIT(IIO_CHAN_INFO_CALIBAMBIENT), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), + }, +}; + +static const struct iio_info mlx90632_info = { + .read_raw = mlx90632_read_raw, + .write_raw = mlx90632_write_raw, + .read_avail = mlx90632_read_avail, +}; + +static void mlx90632_sleep(void *_data) +{ + struct mlx90632_data *data = _data; + + mlx90632_pwr_set_sleep_step(data->regmap); +} + +static int mlx90632_suspend(struct mlx90632_data *data) +{ + regcache_mark_dirty(data->regmap); + + dev_dbg(&data->client->dev, "Requesting suspend"); + return mlx90632_pwr_set_sleep_step(data->regmap); +} + +static int mlx90632_wakeup(struct mlx90632_data *data) +{ + int ret; + + ret = regcache_sync(data->regmap); + if (ret < 0) { + dev_err(&data->client->dev, + "Failed to sync regmap registers: %d\n", ret); + return ret; + } + + dev_dbg(&data->client->dev, "Requesting wake-up\n"); + return mlx90632_pwr_continuous(data->regmap); +} + +static void mlx90632_disable_regulator(void *_data) +{ + struct mlx90632_data *data = _data; + int ret; + + ret = regulator_disable(data->regulator); + if (ret < 0) + dev_err(regmap_get_device(data->regmap), + "Failed to disable power regulator: %d\n", ret); +} + +static int mlx90632_enable_regulator(struct mlx90632_data *data) +{ + int ret; + + ret = regulator_enable(data->regulator); + if (ret < 0) { + dev_err(regmap_get_device(data->regmap), "Failed to enable power regulator!\n"); + return ret; + } + + mlx90632_reset_delay(); + + return ret; +} + +static int mlx90632_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct mlx90632_data *mlx90632; + struct iio_dev *indio_dev; + struct regmap *regmap; + unsigned int read; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*mlx90632)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, &mlx90632_regmap); + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + dev_err(&client->dev, "Failed to allocate regmap: %d\n", ret); + return ret; + } + + mlx90632 = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + mlx90632->client = client; + mlx90632->regmap = regmap; + mlx90632->mtyp = MLX90632_MTYP_MEDICAL; + mlx90632->powerstatus = MLX90632_PWR_STATUS_HALT; + + mutex_init(&mlx90632->lock); + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &mlx90632_info; + indio_dev->channels = mlx90632_channels; + indio_dev->num_channels = ARRAY_SIZE(mlx90632_channels); + + mlx90632->regulator = devm_regulator_get(&client->dev, "vdd"); + if (IS_ERR(mlx90632->regulator)) + return dev_err_probe(&client->dev, PTR_ERR(mlx90632->regulator), + "failed to get vdd regulator"); + + ret = mlx90632_enable_regulator(mlx90632); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(&client->dev, mlx90632_disable_regulator, + mlx90632); + if (ret < 0) { + dev_err(&client->dev, "Failed to setup regulator cleanup action %d\n", + ret); + return ret; + } + + ret = mlx90632_wakeup(mlx90632); + if (ret < 0) { + dev_err(&client->dev, "Wakeup failed: %d\n", ret); + return ret; + } + + ret = devm_add_action_or_reset(&client->dev, mlx90632_sleep, mlx90632); + if (ret < 0) { + dev_err(&client->dev, "Failed to setup low power cleanup action %d\n", + ret); + return ret; + } + + ret = regmap_read(mlx90632->regmap, MLX90632_EE_VERSION, &read); + if (ret < 0) { + dev_err(&client->dev, "read of version failed: %d\n", ret); + return ret; + } + read = read & MLX90632_ID_MASK; + if (read == MLX90632_ID_MEDICAL) { + dev_dbg(&client->dev, + "Detected Medical EEPROM calibration %x\n", read); + } else if (read == MLX90632_ID_CONSUMER) { + dev_dbg(&client->dev, + "Detected Consumer EEPROM calibration %x\n", read); + } else if (read == MLX90632_ID_EXTENDED) { + dev_dbg(&client->dev, + "Detected Extended range EEPROM calibration %x\n", read); + mlx90632->mtyp = MLX90632_MTYP_EXTENDED; + } else if ((read & MLX90632_DSP_MASK) == MLX90632_DSP_VERSION) { + dev_dbg(&client->dev, + "Detected Unknown EEPROM calibration %x\n", read); + } else { + dev_err(&client->dev, + "Wrong DSP version %x (expected %x)\n", + read, MLX90632_DSP_VERSION); + return -EPROTONOSUPPORT; + } + + mlx90632->emissivity = 1000; + mlx90632->object_ambient_temperature = 25000; /* 25 degrees milliCelsius */ + mlx90632->interaction_ts = jiffies; /* Set initial value */ + + pm_runtime_get_noresume(&client->dev); + pm_runtime_set_active(&client->dev); + + ret = devm_pm_runtime_enable(&client->dev); + if (ret) + return ret; + + pm_runtime_set_autosuspend_delay(&client->dev, MLX90632_SLEEP_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id mlx90632_id[] = { + { .name = "mlx90632" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mlx90632_id); + +static const struct of_device_id mlx90632_of_match[] = { + { .compatible = "melexis,mlx90632" }, + { } +}; +MODULE_DEVICE_TABLE(of, mlx90632_of_match); + +static int mlx90632_pm_suspend(struct device *dev) +{ + struct mlx90632_data *data = iio_priv(dev_get_drvdata(dev)); + int ret; + + ret = mlx90632_suspend(data); + if (ret < 0) + return ret; + + ret = regulator_disable(data->regulator); + if (ret < 0) + dev_err(regmap_get_device(data->regmap), + "Failed to disable power regulator: %d\n", ret); + + return ret; +} + +static int mlx90632_pm_resume(struct device *dev) +{ + struct mlx90632_data *data = iio_priv(dev_get_drvdata(dev)); + int ret; + + ret = mlx90632_enable_regulator(data); + if (ret < 0) + return ret; + + return mlx90632_wakeup(data); +} + +static int mlx90632_pm_runtime_suspend(struct device *dev) +{ + struct mlx90632_data *data = iio_priv(dev_get_drvdata(dev)); + + return mlx90632_pwr_set_sleep_step(data->regmap); +} + +static const struct dev_pm_ops mlx90632_pm_ops = { + SYSTEM_SLEEP_PM_OPS(mlx90632_pm_suspend, mlx90632_pm_resume) + RUNTIME_PM_OPS(mlx90632_pm_runtime_suspend, NULL, NULL) +}; + +static struct i2c_driver mlx90632_driver = { + .driver = { + .name = "mlx90632", + .of_match_table = mlx90632_of_match, + .pm = pm_ptr(&mlx90632_pm_ops), + }, + .probe = mlx90632_probe, + .id_table = mlx90632_id, +}; +module_i2c_driver(mlx90632_driver); + +MODULE_AUTHOR("Crt Mori "); +MODULE_DESCRIPTION("Melexis MLX90632 contactless Infra Red temperature sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/temperature/mlx90635.c b/drivers/iio/temperature/mlx90635.c new file mode 100644 index 000000000..0f31879a4 --- /dev/null +++ b/drivers/iio/temperature/mlx90635.c @@ -0,0 +1,1093 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mlx90635.c - Melexis MLX90635 contactless IR temperature sensor + * + * Copyright (c) 2023 Melexis + * + * Driver for the Melexis MLX90635 I2C 16-bit IR thermopile sensor + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Memory sections addresses */ +#define MLX90635_ADDR_RAM 0x0000 /* Start address of ram */ +#define MLX90635_ADDR_EEPROM 0x0018 /* Start address of user eeprom */ + +/* EEPROM addresses - used at startup */ +#define MLX90635_EE_I2C_CFG 0x0018 /* I2C address register initial value */ +#define MLX90635_EE_CTRL1 0x001A /* Control register1 initial value */ +#define MLX90635_EE_CTRL2 0x001C /* Control register2 initial value */ + +#define MLX90635_EE_Ha 0x001E /* Ha customer calib value reg 16bit */ +#define MLX90635_EE_Hb 0x0020 /* Hb customer calib value reg 16bit */ +#define MLX90635_EE_Fa 0x0026 /* Fa calibration register 32bit */ +#define MLX90635_EE_FASCALE 0x002A /* Scaling coefficient for Fa register 16bit */ +#define MLX90635_EE_Ga 0x002C /* Ga calibration register 16bit */ +#define MLX90635_EE_Fb 0x002E /* Fb calibration register 16bit */ +#define MLX90635_EE_Ea 0x0030 /* Ea calibration register 32bit */ +#define MLX90635_EE_Eb 0x0034 /* Eb calibration register 32bit */ +#define MLX90635_EE_P_G 0x0038 /* P_G calibration register 16bit */ +#define MLX90635_EE_P_O 0x003A /* P_O calibration register 16bit */ +#define MLX90635_EE_Aa 0x003C /* Aa calibration register 16bit */ +#define MLX90635_EE_VERSION 0x003E /* Version bits 4:7 and 12:15 */ +#define MLX90635_EE_Gb 0x0040 /* Gb calibration register 16bit */ + +/* Device status register - volatile */ +#define MLX90635_REG_STATUS 0x0000 +#define MLX90635_STAT_BUSY BIT(6) /* Device busy indicator */ +#define MLX90635_STAT_BRST BIT(5) /* Brown out reset indicator */ +#define MLX90635_STAT_CYCLE_POS GENMASK(4, 2) /* Data position */ +#define MLX90635_STAT_END_CONV BIT(1) /* End of conversion indicator */ +#define MLX90635_STAT_DATA_RDY BIT(0) /* Data ready indicator */ + +/* EEPROM control register address - volatile */ +#define MLX90635_REG_EE 0x000C +#define MLX90635_EE_ACTIVE BIT(4) /* Power-on EEPROM */ +#define MLX90635_EE_BUSY_MASK BIT(15) + +#define MLX90635_REG_CMD 0x0010 /* Command register address */ + +/* Control register1 address - volatile */ +#define MLX90635_REG_CTRL1 0x0014 +#define MLX90635_CTRL1_REFRESH_RATE_MASK GENMASK(2, 0) +#define MLX90635_CTRL1_RES_CTRL_MASK GENMASK(4, 3) +#define MLX90635_CTRL1_TABLE_MASK BIT(15) /* Table select */ + +/* Control register2 address - volatile */ +#define MLX90635_REG_CTRL2 0x0016 +#define MLX90635_CTRL2_BURST_CNT_MASK GENMASK(10, 6) /* Burst count */ +#define MLX90635_CTRL2_MODE_MASK GENMASK(12, 11) /* Power mode */ +#define MLX90635_CTRL2_SOB_MASK BIT(15) + +/* PowerModes statuses */ +#define MLX90635_PWR_STATUS_HALT 0 +#define MLX90635_PWR_STATUS_SLEEP_STEP 1 +#define MLX90635_PWR_STATUS_STEP 2 +#define MLX90635_PWR_STATUS_CONTINUOUS 3 + +/* Measurement data addresses */ +#define MLX90635_RESULT_1 0x0002 +#define MLX90635_RESULT_2 0x0004 +#define MLX90635_RESULT_3 0x0006 +#define MLX90635_RESULT_4 0x0008 +#define MLX90635_RESULT_5 0x000A + +/* Timings (ms) */ +#define MLX90635_TIMING_RST_MIN 200 /* Minimum time after addressed reset command */ +#define MLX90635_TIMING_RST_MAX 250 /* Maximum time after addressed reset command */ +#define MLX90635_TIMING_POLLING 10000 /* Time between bit polling*/ +#define MLX90635_TIMING_EE_ACTIVE_MIN 100 /* Minimum time after activating the EEPROM for read */ +#define MLX90635_TIMING_EE_ACTIVE_MAX 150 /* Maximum time after activating the EEPROM for read */ + +/* Magic constants */ +#define MLX90635_ID_DSPv1 0x01 /* EEPROM DSP version */ +#define MLX90635_RESET_CMD 0x0006 /* Reset sensor (address or global) */ +#define MLX90635_MAX_MEAS_NUM 31 /* Maximum number of measurements in list */ +#define MLX90635_PTAT_DIV 12 /* Used to divide the PTAT value in pre-processing */ +#define MLX90635_IR_DIV 24 /* Used to divide the IR value in pre-processing */ +#define MLX90635_SLEEP_DELAY_MS 6000 /* Autosleep delay */ +#define MLX90635_MEAS_MAX_TIME 2000 /* Max measurement time in ms for the lowest refresh rate */ +#define MLX90635_READ_RETRIES 100 /* Number of read retries before quitting with timeout error */ +#define MLX90635_VERSION_MASK (GENMASK(15, 12) | GENMASK(7, 4)) +#define MLX90635_DSP_VERSION(reg) (((reg & GENMASK(14, 12)) >> 9) | ((reg & GENMASK(6, 4)) >> 4)) +#define MLX90635_DSP_FIXED BIT(15) + + +/** + * struct mlx90635_data - private data for the MLX90635 device + * @client: I2C client of the device + * @lock: Internal mutex because multiple reads are needed for single triggered + * measurement to ensure data consistency + * @regmap: Regmap of the device registers + * @regmap_ee: Regmap of the device EEPROM which can be cached + * @emissivity: Object emissivity from 0 to 1000 where 1000 = 1 + * @regulator: Regulator of the device + * @powerstatus: Current POWER status of the device + * @interaction_ts: Timestamp of the last temperature read that is used + * for power management in jiffies + */ +struct mlx90635_data { + struct i2c_client *client; + struct mutex lock; + struct regmap *regmap; + struct regmap *regmap_ee; + u16 emissivity; + struct regulator *regulator; + int powerstatus; + unsigned long interaction_ts; +}; + +static const struct regmap_range mlx90635_volatile_reg_range[] = { + regmap_reg_range(MLX90635_REG_STATUS, MLX90635_REG_STATUS), + regmap_reg_range(MLX90635_RESULT_1, MLX90635_RESULT_5), + regmap_reg_range(MLX90635_REG_EE, MLX90635_REG_EE), + regmap_reg_range(MLX90635_REG_CMD, MLX90635_REG_CMD), + regmap_reg_range(MLX90635_REG_CTRL1, MLX90635_REG_CTRL2), +}; + +static const struct regmap_access_table mlx90635_volatile_regs_tbl = { + .yes_ranges = mlx90635_volatile_reg_range, + .n_yes_ranges = ARRAY_SIZE(mlx90635_volatile_reg_range), +}; + +static const struct regmap_range mlx90635_read_reg_range[] = { + regmap_reg_range(MLX90635_REG_STATUS, MLX90635_REG_STATUS), + regmap_reg_range(MLX90635_RESULT_1, MLX90635_RESULT_5), + regmap_reg_range(MLX90635_REG_EE, MLX90635_REG_EE), + regmap_reg_range(MLX90635_REG_CMD, MLX90635_REG_CMD), + regmap_reg_range(MLX90635_REG_CTRL1, MLX90635_REG_CTRL2), +}; + +static const struct regmap_access_table mlx90635_readable_regs_tbl = { + .yes_ranges = mlx90635_read_reg_range, + .n_yes_ranges = ARRAY_SIZE(mlx90635_read_reg_range), +}; + +static const struct regmap_range mlx90635_no_write_reg_range[] = { + regmap_reg_range(MLX90635_RESULT_1, MLX90635_RESULT_5), +}; + +static const struct regmap_access_table mlx90635_writeable_regs_tbl = { + .no_ranges = mlx90635_no_write_reg_range, + .n_no_ranges = ARRAY_SIZE(mlx90635_no_write_reg_range), +}; + +static const struct regmap_config mlx90635_regmap = { + .name = "mlx90635-registers", + .reg_stride = 1, + .reg_bits = 16, + .val_bits = 16, + + .volatile_table = &mlx90635_volatile_regs_tbl, + .rd_table = &mlx90635_readable_regs_tbl, + .wr_table = &mlx90635_writeable_regs_tbl, + + .use_single_read = true, + .use_single_write = true, + .can_multi_write = false, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_BIG, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct regmap_range mlx90635_read_ee_range[] = { + regmap_reg_range(MLX90635_EE_I2C_CFG, MLX90635_EE_CTRL2), + regmap_reg_range(MLX90635_EE_Ha, MLX90635_EE_Gb), +}; + +static const struct regmap_access_table mlx90635_readable_ees_tbl = { + .yes_ranges = mlx90635_read_ee_range, + .n_yes_ranges = ARRAY_SIZE(mlx90635_read_ee_range), +}; + +static const struct regmap_range mlx90635_no_write_ee_range[] = { + regmap_reg_range(MLX90635_ADDR_EEPROM, MLX90635_EE_Gb), +}; + +static const struct regmap_access_table mlx90635_writeable_ees_tbl = { + .no_ranges = mlx90635_no_write_ee_range, + .n_no_ranges = ARRAY_SIZE(mlx90635_no_write_ee_range), +}; + +static const struct regmap_config mlx90635_regmap_ee = { + .name = "mlx90635-eeprom", + .reg_stride = 1, + .reg_bits = 16, + .val_bits = 16, + + .volatile_table = NULL, + .rd_table = &mlx90635_readable_ees_tbl, + .wr_table = &mlx90635_writeable_ees_tbl, + + .use_single_read = true, + .use_single_write = true, + .can_multi_write = false, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_BIG, + .cache_type = REGCACHE_RBTREE, +}; + +/** + * mlx90635_reset_delay() - Give the mlx90635 some time to reset properly + * If this is not done, the following I2C command(s) will not be accepted. + */ +static void mlx90635_reset_delay(void) +{ + usleep_range(MLX90635_TIMING_RST_MIN, MLX90635_TIMING_RST_MAX); +} + +static int mlx90635_pwr_sleep_step(struct mlx90635_data *data) +{ + int ret; + + if (data->powerstatus == MLX90635_PWR_STATUS_SLEEP_STEP) + return 0; + + ret = regmap_write_bits(data->regmap, MLX90635_REG_CTRL2, MLX90635_CTRL2_MODE_MASK, + FIELD_PREP(MLX90635_CTRL2_MODE_MASK, MLX90635_PWR_STATUS_SLEEP_STEP)); + if (ret < 0) + return ret; + + data->powerstatus = MLX90635_PWR_STATUS_SLEEP_STEP; + return 0; +} + +static int mlx90635_pwr_continuous(struct mlx90635_data *data) +{ + int ret; + + if (data->powerstatus == MLX90635_PWR_STATUS_CONTINUOUS) + return 0; + + ret = regmap_write_bits(data->regmap, MLX90635_REG_CTRL2, MLX90635_CTRL2_MODE_MASK, + FIELD_PREP(MLX90635_CTRL2_MODE_MASK, MLX90635_PWR_STATUS_CONTINUOUS)); + if (ret < 0) + return ret; + + data->powerstatus = MLX90635_PWR_STATUS_CONTINUOUS; + return 0; +} + +static int mlx90635_read_ee_register(struct regmap *regmap, u16 reg_lsb, + s32 *reg_value) +{ + unsigned int read; + u32 value; + int ret; + + ret = regmap_read(regmap, reg_lsb + 2, &read); + if (ret < 0) + return ret; + + value = read; + + ret = regmap_read(regmap, reg_lsb, &read); + if (ret < 0) + return ret; + + *reg_value = (read << 16) | (value & 0xffff); + + return 0; +} + +static int mlx90635_read_ee_ambient(struct regmap *regmap, s16 *PG, s16 *PO, s16 *Gb) +{ + unsigned int read_tmp; + int ret; + + ret = regmap_read(regmap, MLX90635_EE_P_O, &read_tmp); + if (ret < 0) + return ret; + *PO = (s16)read_tmp; + + ret = regmap_read(regmap, MLX90635_EE_P_G, &read_tmp); + if (ret < 0) + return ret; + *PG = (s16)read_tmp; + + ret = regmap_read(regmap, MLX90635_EE_Gb, &read_tmp); + if (ret < 0) + return ret; + *Gb = (u16)read_tmp; + + return 0; +} + +static int mlx90635_read_ee_object(struct regmap *regmap, u32 *Ea, u32 *Eb, u32 *Fa, s16 *Fb, + s16 *Ga, s16 *Gb, s16 *Ha, s16 *Hb, u16 *Fa_scale) +{ + unsigned int read_tmp; + int ret; + + ret = mlx90635_read_ee_register(regmap, MLX90635_EE_Ea, Ea); + if (ret < 0) + return ret; + + ret = mlx90635_read_ee_register(regmap, MLX90635_EE_Eb, Eb); + if (ret < 0) + return ret; + + ret = mlx90635_read_ee_register(regmap, MLX90635_EE_Fa, Fa); + if (ret < 0) + return ret; + + ret = regmap_read(regmap, MLX90635_EE_Ha, &read_tmp); + if (ret < 0) + return ret; + *Ha = (s16)read_tmp; + + ret = regmap_read(regmap, MLX90635_EE_Hb, &read_tmp); + if (ret < 0) + return ret; + *Hb = (s16)read_tmp; + + ret = regmap_read(regmap, MLX90635_EE_Ga, &read_tmp); + if (ret < 0) + return ret; + *Ga = (s16)read_tmp; + + ret = regmap_read(regmap, MLX90635_EE_Gb, &read_tmp); + if (ret < 0) + return ret; + *Gb = (s16)read_tmp; + + ret = regmap_read(regmap, MLX90635_EE_Fb, &read_tmp); + if (ret < 0) + return ret; + *Fb = (s16)read_tmp; + + ret = regmap_read(regmap, MLX90635_EE_FASCALE, &read_tmp); + if (ret < 0) + return ret; + *Fa_scale = (u16)read_tmp; + + return 0; +} + +static int mlx90635_calculate_dataset_ready_time(struct mlx90635_data *data, int *refresh_time) +{ + unsigned int reg; + int ret; + + ret = regmap_read(data->regmap, MLX90635_REG_CTRL1, ®); + if (ret < 0) + return ret; + + *refresh_time = 2 * (MLX90635_MEAS_MAX_TIME >> FIELD_GET(MLX90635_CTRL1_REFRESH_RATE_MASK, reg)) + 80; + + return 0; +} + +static int mlx90635_perform_measurement_burst(struct mlx90635_data *data) +{ + unsigned int reg_status; + int refresh_time; + int ret; + + ret = regmap_write_bits(data->regmap, MLX90635_REG_STATUS, + MLX90635_STAT_END_CONV, MLX90635_STAT_END_CONV); + if (ret < 0) + return ret; + + ret = mlx90635_calculate_dataset_ready_time(data, &refresh_time); + if (ret < 0) + return ret; + + ret = regmap_write_bits(data->regmap, MLX90635_REG_CTRL2, + FIELD_PREP(MLX90635_CTRL2_SOB_MASK, 1), + FIELD_PREP(MLX90635_CTRL2_SOB_MASK, 1)); + if (ret < 0) + return ret; + + msleep(refresh_time); /* Wait minimum time for dataset to be ready */ + + ret = regmap_read_poll_timeout(data->regmap, MLX90635_REG_STATUS, reg_status, + (!(reg_status & MLX90635_STAT_END_CONV)) == 0, + MLX90635_TIMING_POLLING, MLX90635_READ_RETRIES * 10000); + if (ret < 0) { + dev_err(&data->client->dev, "data not ready"); + return -ETIMEDOUT; + } + + return 0; +} + +static int mlx90635_read_ambient_raw(struct regmap *regmap, + s16 *ambient_new_raw, s16 *ambient_old_raw) +{ + unsigned int read_tmp; + int ret; + + ret = regmap_read(regmap, MLX90635_RESULT_2, &read_tmp); + if (ret < 0) + return ret; + *ambient_new_raw = (s16)read_tmp; + + ret = regmap_read(regmap, MLX90635_RESULT_3, &read_tmp); + if (ret < 0) + return ret; + *ambient_old_raw = (s16)read_tmp; + + return 0; +} + +static int mlx90635_read_object_raw(struct regmap *regmap, s16 *object_raw) +{ + unsigned int read_tmp; + s16 read; + int ret; + + ret = regmap_read(regmap, MLX90635_RESULT_1, &read_tmp); + if (ret < 0) + return ret; + + read = (s16)read_tmp; + + ret = regmap_read(regmap, MLX90635_RESULT_4, &read_tmp); + if (ret < 0) + return ret; + *object_raw = (read - (s16)read_tmp) / 2; + + return 0; +} + +static int mlx90635_read_all_channel(struct mlx90635_data *data, + s16 *ambient_new_raw, s16 *ambient_old_raw, + s16 *object_raw) +{ + int ret; + + mutex_lock(&data->lock); + if (data->powerstatus == MLX90635_PWR_STATUS_SLEEP_STEP) { + /* Trigger measurement in Sleep Step mode */ + ret = mlx90635_perform_measurement_burst(data); + if (ret < 0) + goto read_unlock; + } + + ret = mlx90635_read_ambient_raw(data->regmap, ambient_new_raw, + ambient_old_raw); + if (ret < 0) + goto read_unlock; + + ret = mlx90635_read_object_raw(data->regmap, object_raw); +read_unlock: + mutex_unlock(&data->lock); + return ret; +} + +static s64 mlx90635_preprocess_temp_amb(s16 ambient_new_raw, + s16 ambient_old_raw, s16 Gb) +{ + s64 VR_Ta, kGb, tmp; + + kGb = ((s64)Gb * 1000LL) >> 10ULL; + VR_Ta = (s64)ambient_old_raw * 1000000LL + + kGb * div64_s64(((s64)ambient_new_raw * 1000LL), + (MLX90635_PTAT_DIV)); + tmp = div64_s64( + div64_s64(((s64)ambient_new_raw * 1000000000000LL), + (MLX90635_PTAT_DIV)), VR_Ta); + return div64_s64(tmp << 19ULL, 1000LL); +} + +static s64 mlx90635_preprocess_temp_obj(s16 object_raw, + s16 ambient_new_raw, + s16 ambient_old_raw, s16 Gb) +{ + s64 VR_IR, kGb, tmp; + + kGb = ((s64)Gb * 1000LL) >> 10ULL; + VR_IR = (s64)ambient_old_raw * 1000000LL + + kGb * (div64_s64((s64)ambient_new_raw * 1000LL, + MLX90635_PTAT_DIV)); + tmp = div64_s64( + div64_s64((s64)(object_raw * 1000000LL), + MLX90635_IR_DIV) * 1000000LL, + VR_IR); + return div64_s64((tmp << 19ULL), 1000LL); +} + +static s32 mlx90635_calc_temp_ambient(s16 ambient_new_raw, s16 ambient_old_raw, + u16 P_G, u16 P_O, s16 Gb) +{ + s64 kPG, kPO, AMB; + + AMB = mlx90635_preprocess_temp_amb(ambient_new_raw, ambient_old_raw, + Gb); + kPG = ((s64)P_G * 1000000LL) >> 9ULL; + kPO = AMB - (((s64)P_O * 1000LL) >> 1ULL); + + return 30 * 1000LL + div64_s64(kPO * 1000000LL, kPG); +} + +static s32 mlx90635_calc_temp_object_iteration(s32 prev_object_temp, s64 object, + s64 TAdut, s64 TAdut4, s16 Ga, + u32 Fa, u16 Fa_scale, s16 Fb, + s16 Ha, s16 Hb, u16 emissivity) +{ + s64 calcedGa, calcedGb, calcedFa, Alpha_corr; + s64 Ha_customer, Hb_customer; + + Ha_customer = ((s64)Ha * 1000000LL) >> 14ULL; + Hb_customer = ((s64)Hb * 100) >> 10ULL; + + calcedGa = ((s64)((s64)Ga * (prev_object_temp - 35 * 1000LL) + * 1000LL)) >> 24LL; + calcedGb = ((s64)(Fb * (TAdut - 30 * 1000000LL))) >> 24LL; + + Alpha_corr = ((s64)((s64)Fa * Ha_customer * 10000LL) >> Fa_scale); + Alpha_corr *= ((s64)(1 * 1000000LL + calcedGa + calcedGb)); + + Alpha_corr = div64_s64(Alpha_corr, 1000LL); + Alpha_corr *= emissivity; + Alpha_corr = div64_s64(Alpha_corr, 100LL); + calcedFa = div64_s64((s64)object * 100000000000LL, Alpha_corr); + + return (int_sqrt64(int_sqrt64(calcedFa * 100000000LL + TAdut4)) + - 27315 - Hb_customer) * 10; +} + +static s64 mlx90635_calc_ta4(s64 TAdut, s64 scale) +{ + return (div64_s64(TAdut, scale) + 27315) * + (div64_s64(TAdut, scale) + 27315) * + (div64_s64(TAdut, scale) + 27315) * + (div64_s64(TAdut, scale) + 27315); +} + +static s32 mlx90635_calc_temp_object(s64 object, s64 ambient, u32 Ea, u32 Eb, + s16 Ga, u32 Fa, u16 Fa_scale, s16 Fb, s16 Ha, s16 Hb, + u16 tmp_emi) +{ + s64 kTA, kTA0, TAdut, TAdut4; + s64 temp = 35000; + s8 i; + + kTA = (Ea * 1000LL) >> 16LL; + kTA0 = (Eb * 1000LL) >> 8LL; + TAdut = div64_s64(((ambient - kTA0) * 1000000LL), kTA) + 30 * 1000000LL; + TAdut4 = mlx90635_calc_ta4(TAdut, 10000LL); + + /* Iterations of calculation as described in datasheet */ + for (i = 0; i < 5; ++i) { + temp = mlx90635_calc_temp_object_iteration(temp, object, TAdut, TAdut4, + Ga, Fa, Fa_scale, Fb, Ha, Hb, + tmp_emi); + } + return temp; +} + +static int mlx90635_calc_object(struct mlx90635_data *data, int *val) +{ + s16 ambient_new_raw, ambient_old_raw, object_raw; + s16 Fb, Ga, Gb, Ha, Hb; + s64 object, ambient; + u32 Ea, Eb, Fa; + u16 Fa_scale; + int ret; + + ret = mlx90635_read_ee_object(data->regmap_ee, &Ea, &Eb, &Fa, &Fb, &Ga, &Gb, &Ha, &Hb, &Fa_scale); + if (ret < 0) + return ret; + + ret = mlx90635_read_all_channel(data, + &ambient_new_raw, &ambient_old_raw, + &object_raw); + if (ret < 0) + return ret; + + ambient = mlx90635_preprocess_temp_amb(ambient_new_raw, + ambient_old_raw, Gb); + object = mlx90635_preprocess_temp_obj(object_raw, + ambient_new_raw, + ambient_old_raw, Gb); + + *val = mlx90635_calc_temp_object(object, ambient, Ea, Eb, Ga, Fa, Fa_scale, Fb, + Ha, Hb, data->emissivity); + return 0; +} + +static int mlx90635_calc_ambient(struct mlx90635_data *data, int *val) +{ + s16 ambient_new_raw, ambient_old_raw; + s16 PG, PO, Gb; + int ret; + + ret = mlx90635_read_ee_ambient(data->regmap_ee, &PG, &PO, &Gb); + if (ret < 0) + return ret; + + mutex_lock(&data->lock); + if (data->powerstatus == MLX90635_PWR_STATUS_SLEEP_STEP) { + ret = mlx90635_perform_measurement_burst(data); + if (ret < 0) + goto read_ambient_unlock; + } + + ret = mlx90635_read_ambient_raw(data->regmap, &ambient_new_raw, + &ambient_old_raw); +read_ambient_unlock: + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + + *val = mlx90635_calc_temp_ambient(ambient_new_raw, ambient_old_raw, + PG, PO, Gb); + return ret; +} + +static int mlx90635_get_refresh_rate(struct mlx90635_data *data, + unsigned int *refresh_rate) +{ + unsigned int reg; + int ret; + + ret = regmap_read(data->regmap, MLX90635_REG_CTRL1, ®); + if (ret < 0) + return ret; + + *refresh_rate = FIELD_GET(MLX90635_CTRL1_REFRESH_RATE_MASK, reg); + + return 0; +} + +static const struct { + int val; + int val2; +} mlx90635_freqs[] = { + { 0, 200000 }, + { 0, 500000 }, + { 0, 900000 }, + { 1, 700000 }, + { 3, 0 }, + { 4, 800000 }, + { 6, 900000 }, + { 8, 900000 } +}; + +/** + * mlx90635_pm_interaction_wakeup() - Measure time between user interactions to change powermode + * @data: pointer to mlx90635_data object containing interaction_ts information + * + * Switch to continuous mode when interaction is faster than MLX90635_MEAS_MAX_TIME. Update the + * interaction_ts for each function call with the jiffies to enable measurement between function + * calls. Initial value of the interaction_ts needs to be set before this function call. + */ +static int mlx90635_pm_interaction_wakeup(struct mlx90635_data *data) +{ + unsigned long now; + int ret; + + now = jiffies; + if (time_in_range(now, data->interaction_ts, + data->interaction_ts + + msecs_to_jiffies(MLX90635_MEAS_MAX_TIME + 100))) { + ret = mlx90635_pwr_continuous(data); + if (ret < 0) + return ret; + } + + data->interaction_ts = now; + + return 0; +} + +static int mlx90635_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct mlx90635_data *data = iio_priv(indio_dev); + int ret; + int cr; + + pm_runtime_get_sync(&data->client->dev); + ret = mlx90635_pm_interaction_wakeup(data); + if (ret < 0) + goto mlx90635_read_raw_pm; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (channel->channel2) { + case IIO_MOD_TEMP_AMBIENT: + ret = mlx90635_calc_ambient(data, val); + if (ret < 0) + goto mlx90635_read_raw_pm; + + ret = IIO_VAL_INT; + break; + case IIO_MOD_TEMP_OBJECT: + ret = mlx90635_calc_object(data, val); + if (ret < 0) + goto mlx90635_read_raw_pm; + + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + break; + } + break; + case IIO_CHAN_INFO_CALIBEMISSIVITY: + if (data->emissivity == 1000) { + *val = 1; + *val2 = 0; + } else { + *val = 0; + *val2 = data->emissivity * 1000; + } + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = mlx90635_get_refresh_rate(data, &cr); + if (ret < 0) + goto mlx90635_read_raw_pm; + + *val = mlx90635_freqs[cr].val; + *val2 = mlx90635_freqs[cr].val2; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + ret = -EINVAL; + break; + } + +mlx90635_read_raw_pm: + pm_runtime_put_autosuspend(&data->client->dev); + return ret; +} + +static int mlx90635_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int val, + int val2, long mask) +{ + struct mlx90635_data *data = iio_priv(indio_dev); + int ret; + int i; + + switch (mask) { + case IIO_CHAN_INFO_CALIBEMISSIVITY: + /* Confirm we are within 0 and 1.0 */ + if (val < 0 || val2 < 0 || val > 1 || + (val == 1 && val2 != 0)) + return -EINVAL; + data->emissivity = val * 1000 + val2 / 1000; + return 0; + case IIO_CHAN_INFO_SAMP_FREQ: + for (i = 0; i < ARRAY_SIZE(mlx90635_freqs); i++) { + if (val == mlx90635_freqs[i].val && + val2 == mlx90635_freqs[i].val2) + break; + } + if (i == ARRAY_SIZE(mlx90635_freqs)) + return -EINVAL; + + ret = regmap_write_bits(data->regmap, MLX90635_REG_CTRL1, + MLX90635_CTRL1_REFRESH_RATE_MASK, i); + + return ret; + default: + return -EINVAL; + } +} + +static int mlx90635_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = (int *)mlx90635_freqs; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = 2 * ARRAY_SIZE(mlx90635_freqs); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const struct iio_chan_spec mlx90635_channels[] = { + { + .type = IIO_TEMP, + .modified = 1, + .channel2 = IIO_MOD_TEMP_AMBIENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), + }, + { + .type = IIO_TEMP, + .modified = 1, + .channel2 = IIO_MOD_TEMP_OBJECT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_CALIBEMISSIVITY), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), + }, +}; + +static const struct iio_info mlx90635_info = { + .read_raw = mlx90635_read_raw, + .write_raw = mlx90635_write_raw, + .read_avail = mlx90635_read_avail, +}; + +static void mlx90635_sleep(void *_data) +{ + struct mlx90635_data *data = _data; + + mlx90635_pwr_sleep_step(data); +} + +static int mlx90635_suspend(struct mlx90635_data *data) +{ + return mlx90635_pwr_sleep_step(data); +} + +static int mlx90635_wakeup(struct mlx90635_data *data) +{ + s16 Fb, Ga, Gb, Ha, Hb, PG, PO; + unsigned int dsp_version; + u32 Ea, Eb, Fa; + u16 Fa_scale; + int ret; + + regcache_cache_bypass(data->regmap_ee, false); + regcache_cache_only(data->regmap_ee, false); + regcache_cache_only(data->regmap, false); + + ret = mlx90635_pwr_continuous(data); + if (ret < 0) { + dev_err(&data->client->dev, "Switch to continuous mode failed\n"); + return ret; + } + ret = regmap_write_bits(data->regmap, MLX90635_REG_EE, + MLX90635_EE_ACTIVE, MLX90635_EE_ACTIVE); + if (ret < 0) { + dev_err(&data->client->dev, "Powering EEPROM failed\n"); + return ret; + } + usleep_range(MLX90635_TIMING_EE_ACTIVE_MIN, MLX90635_TIMING_EE_ACTIVE_MAX); + + regcache_mark_dirty(data->regmap_ee); + + ret = regcache_sync(data->regmap_ee); + if (ret < 0) { + dev_err(&data->client->dev, + "Failed to sync cache: %d\n", ret); + return ret; + } + + ret = mlx90635_read_ee_ambient(data->regmap_ee, &PG, &PO, &Gb); + if (ret < 0) { + dev_err(&data->client->dev, + "Failed to read to cache Ambient coefficients EEPROM region: %d\n", ret); + return ret; + } + + ret = mlx90635_read_ee_object(data->regmap_ee, &Ea, &Eb, &Fa, &Fb, &Ga, &Gb, &Ha, &Hb, &Fa_scale); + if (ret < 0) { + dev_err(&data->client->dev, + "Failed to read to cache Object coefficients EEPROM region: %d\n", ret); + return ret; + } + + ret = regmap_read(data->regmap_ee, MLX90635_EE_VERSION, &dsp_version); + if (ret < 0) { + dev_err(&data->client->dev, + "Failed to read to cache of EEPROM version: %d\n", ret); + return ret; + } + + regcache_cache_only(data->regmap_ee, true); + + return ret; +} + +static void mlx90635_disable_regulator(void *_data) +{ + struct mlx90635_data *data = _data; + int ret; + + ret = regulator_disable(data->regulator); + if (ret < 0) + dev_err(regmap_get_device(data->regmap), + "Failed to disable power regulator: %d\n", ret); +} + +static int mlx90635_enable_regulator(struct mlx90635_data *data) +{ + int ret; + + ret = regulator_enable(data->regulator); + if (ret < 0) { + dev_err(regmap_get_device(data->regmap), "Failed to enable power regulator!\n"); + return ret; + } + + mlx90635_reset_delay(); + + return ret; +} + +static int mlx90635_probe(struct i2c_client *client) +{ + struct mlx90635_data *mlx90635; + struct iio_dev *indio_dev; + unsigned int dsp_version; + struct regmap *regmap; + struct regmap *regmap_ee; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*mlx90635)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, &mlx90635_regmap); + if (IS_ERR(regmap)) + return dev_err_probe(&client->dev, PTR_ERR(regmap), + "failed to allocate regmap\n"); + + regmap_ee = devm_regmap_init_i2c(client, &mlx90635_regmap_ee); + if (IS_ERR(regmap_ee)) + return dev_err_probe(&client->dev, PTR_ERR(regmap_ee), + "failed to allocate EEPROM regmap\n"); + + mlx90635 = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + mlx90635->client = client; + mlx90635->regmap = regmap; + mlx90635->regmap_ee = regmap_ee; + mlx90635->powerstatus = MLX90635_PWR_STATUS_SLEEP_STEP; + + mutex_init(&mlx90635->lock); + indio_dev->name = "mlx90635"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &mlx90635_info; + indio_dev->channels = mlx90635_channels; + indio_dev->num_channels = ARRAY_SIZE(mlx90635_channels); + + mlx90635->regulator = devm_regulator_get(&client->dev, "vdd"); + if (IS_ERR(mlx90635->regulator)) + return dev_err_probe(&client->dev, PTR_ERR(mlx90635->regulator), + "failed to get vdd regulator"); + + ret = mlx90635_enable_regulator(mlx90635); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(&client->dev, mlx90635_disable_regulator, + mlx90635); + if (ret < 0) + return ret; + + ret = mlx90635_wakeup(mlx90635); + if (ret < 0) + return dev_err_probe(&client->dev, ret, "wakeup failed\n"); + + ret = devm_add_action_or_reset(&client->dev, mlx90635_sleep, mlx90635); + if (ret < 0) + return ret; + + ret = regmap_read(mlx90635->regmap_ee, MLX90635_EE_VERSION, &dsp_version); + if (ret < 0) + return dev_err_probe(&client->dev, ret, "read of version failed\n"); + + dsp_version = dsp_version & MLX90635_VERSION_MASK; + + if (FIELD_GET(MLX90635_DSP_FIXED, dsp_version)) { + if (MLX90635_DSP_VERSION(dsp_version) == MLX90635_ID_DSPv1) { + dev_dbg(&client->dev, + "Detected DSP v1 calibration %x\n", dsp_version); + } else { + dev_dbg(&client->dev, + "Detected Unknown EEPROM calibration %lx\n", + MLX90635_DSP_VERSION(dsp_version)); + } + } else { + return dev_err_probe(&client->dev, -EPROTONOSUPPORT, + "Wrong fixed top bit %x (expected 0x8X0X)\n", + dsp_version); + } + + mlx90635->emissivity = 1000; + mlx90635->interaction_ts = jiffies; /* Set initial value */ + + pm_runtime_get_noresume(&client->dev); + pm_runtime_set_active(&client->dev); + + ret = devm_pm_runtime_enable(&client->dev); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to enable powermanagement\n"); + + pm_runtime_set_autosuspend_delay(&client->dev, MLX90635_SLEEP_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id mlx90635_id[] = { + { .name = "mlx90635" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mlx90635_id); + +static const struct of_device_id mlx90635_of_match[] = { + { .compatible = "melexis,mlx90635" }, + { } +}; +MODULE_DEVICE_TABLE(of, mlx90635_of_match); + +static int mlx90635_pm_suspend(struct device *dev) +{ + struct mlx90635_data *data = iio_priv(dev_get_drvdata(dev)); + int ret; + + ret = mlx90635_suspend(data); + if (ret < 0) + return ret; + + ret = regulator_disable(data->regulator); + if (ret < 0) + dev_err(regmap_get_device(data->regmap), + "Failed to disable power regulator: %d\n", ret); + + return ret; +} + +static int mlx90635_pm_resume(struct device *dev) +{ + struct mlx90635_data *data = iio_priv(dev_get_drvdata(dev)); + int ret; + + ret = mlx90635_enable_regulator(data); + if (ret < 0) + return ret; + + return mlx90635_wakeup(data); +} + +static int mlx90635_pm_runtime_suspend(struct device *dev) +{ + struct mlx90635_data *data = iio_priv(dev_get_drvdata(dev)); + + return mlx90635_pwr_sleep_step(data); +} + +static const struct dev_pm_ops mlx90635_pm_ops = { + SYSTEM_SLEEP_PM_OPS(mlx90635_pm_suspend, mlx90635_pm_resume) + RUNTIME_PM_OPS(mlx90635_pm_runtime_suspend, NULL, NULL) +}; + +static struct i2c_driver mlx90635_driver = { + .driver = { + .name = "mlx90635", + .of_match_table = mlx90635_of_match, + .pm = pm_ptr(&mlx90635_pm_ops), + }, + .probe = mlx90635_probe, + .id_table = mlx90635_id, +}; +module_i2c_driver(mlx90635_driver); + +MODULE_AUTHOR("Crt Mori "); +MODULE_DESCRIPTION("Melexis MLX90635 contactless Infra Red temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/temperature/tmp006.c b/drivers/iio/temperature/tmp006.c new file mode 100644 index 000000000..d9f6449ec --- /dev/null +++ b/drivers/iio/temperature/tmp006.c @@ -0,0 +1,411 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tmp006.c - Support for TI TMP006 IR thermopile sensor + * + * Copyright (c) 2013 Peter Meerwald + * + * Driver for the Texas Instruments I2C 16-bit IR thermopile sensor + * + * (7-bit I2C slave address 0x40, changeable via ADR pins) + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define TMP006_VOBJECT 0x00 +#define TMP006_TAMBIENT 0x01 +#define TMP006_CONFIG 0x02 +#define TMP006_MANUFACTURER_ID 0xfe +#define TMP006_DEVICE_ID 0xff + +#define TMP006_TAMBIENT_SHIFT 2 + +#define TMP006_CONFIG_RESET BIT(15) +#define TMP006_CONFIG_DRDY_EN BIT(8) +#define TMP006_CONFIG_DRDY BIT(7) + +#define TMP006_CONFIG_MOD_MASK GENMASK(14, 12) + +#define TMP006_CONFIG_CR_MASK GENMASK(11, 9) +#define TMP006_CONFIG_CR_SHIFT 9 + +#define TMP006_MANUFACTURER_MAGIC 0x5449 +#define TMP006_DEVICE_MAGIC 0x0067 + +struct tmp006_data { + struct i2c_client *client; + u16 config; + struct iio_trigger *drdy_trig; +}; + +static int tmp006_read_measurement(struct tmp006_data *data, u8 reg) +{ + s32 ret; + int tries = 50; + + while (tries-- > 0) { + ret = i2c_smbus_read_word_swapped(data->client, + TMP006_CONFIG); + if (ret < 0) + return ret; + if (ret & TMP006_CONFIG_DRDY) + break; + msleep(100); + } + + if (tries < 0) + return -EIO; + + return i2c_smbus_read_word_swapped(data->client, reg); +} + +static const int tmp006_freqs[5][2] = { {4, 0}, {2, 0}, {1, 0}, + {0, 500000}, {0, 250000} }; + +static int tmp006_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct tmp006_data *data = iio_priv(indio_dev); + s32 ret; + int cr; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (channel->type == IIO_VOLTAGE) { + /* LSB is 156.25 nV */ + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = tmp006_read_measurement(data, TMP006_VOBJECT); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + + *val = sign_extend32(ret, 15); + } else if (channel->type == IIO_TEMP) { + /* LSB is 0.03125 degrees Celsius */ + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = tmp006_read_measurement(data, TMP006_TAMBIENT); + iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + + *val = sign_extend32(ret, 15) >> TMP006_TAMBIENT_SHIFT; + } else { + break; + } + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (channel->type == IIO_VOLTAGE) { + *val = 0; + *val2 = 156250; + } else if (channel->type == IIO_TEMP) { + *val = 31; + *val2 = 250000; + } else { + break; + } + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + cr = (data->config & TMP006_CONFIG_CR_MASK) + >> TMP006_CONFIG_CR_SHIFT; + *val = tmp006_freqs[cr][0]; + *val2 = tmp006_freqs[cr][1]; + return IIO_VAL_INT_PLUS_MICRO; + default: + break; + } + + return -EINVAL; +} + +static int tmp006_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct tmp006_data *data = iio_priv(indio_dev); + int ret, i; + + if (mask != IIO_CHAN_INFO_SAMP_FREQ) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(tmp006_freqs); i++) + if ((val == tmp006_freqs[i][0]) && + (val2 == tmp006_freqs[i][1])) { + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + data->config &= ~TMP006_CONFIG_CR_MASK; + data->config |= i << TMP006_CONFIG_CR_SHIFT; + + ret = i2c_smbus_write_word_swapped(data->client, + TMP006_CONFIG, + data->config); + + iio_device_release_direct(indio_dev); + return ret; + } + return -EINVAL; +} + +static IIO_CONST_ATTR(sampling_frequency_available, "4 2 1 0.5 0.25"); + +static struct attribute *tmp006_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group tmp006_attribute_group = { + .attrs = tmp006_attributes, +}; + +static const struct iio_chan_spec tmp006_channels[] = { + { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, + } + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 14, + .storagebits = 16, + .shift = TMP006_TAMBIENT_SHIFT, + .endianness = IIO_BE, + } + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static const struct iio_info tmp006_info = { + .read_raw = tmp006_read_raw, + .write_raw = tmp006_write_raw, + .attrs = &tmp006_attribute_group, +}; + +static bool tmp006_check_identification(struct i2c_client *client) +{ + int mid, did; + + mid = i2c_smbus_read_word_swapped(client, TMP006_MANUFACTURER_ID); + if (mid < 0) + return false; + + did = i2c_smbus_read_word_swapped(client, TMP006_DEVICE_ID); + if (did < 0) + return false; + + return mid == TMP006_MANUFACTURER_MAGIC && did == TMP006_DEVICE_MAGIC; +} + +static int tmp006_power(struct device *dev, bool up) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct tmp006_data *data = iio_priv(indio_dev); + + if (up) + data->config |= TMP006_CONFIG_MOD_MASK; + else + data->config &= ~TMP006_CONFIG_MOD_MASK; + + return i2c_smbus_write_word_swapped(data->client, TMP006_CONFIG, + data->config); +} + +static void tmp006_powerdown_cleanup(void *dev) +{ + tmp006_power(dev, false); +} + +static irqreturn_t tmp006_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct tmp006_data *data = iio_priv(indio_dev); + struct { + s16 channels[2]; + aligned_s64 ts; + } scan = { }; + s32 ret; + + ret = i2c_smbus_read_word_data(data->client, TMP006_VOBJECT); + if (ret < 0) + goto err; + scan.channels[0] = ret; + + ret = i2c_smbus_read_word_data(data->client, TMP006_TAMBIENT); + if (ret < 0) + goto err; + scan.channels[1] = ret; + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); +err: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int tmp006_set_trigger_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct tmp006_data *data = iio_priv(indio_dev); + + if (state) + data->config |= TMP006_CONFIG_DRDY_EN; + else + data->config &= ~TMP006_CONFIG_DRDY_EN; + + return i2c_smbus_write_word_swapped(data->client, TMP006_CONFIG, + data->config); +} + +static const struct iio_trigger_ops tmp006_trigger_ops = { + .set_trigger_state = tmp006_set_trigger_state, +}; + +static const unsigned long tmp006_scan_masks[] = { 0x3, 0 }; + +static int tmp006_probe(struct i2c_client *client) +{ + struct iio_dev *indio_dev; + struct tmp006_data *data; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -EOPNOTSUPP; + + if (!tmp006_check_identification(client)) { + dev_err(&client->dev, "no TMP006 sensor\n"); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + indio_dev->name = dev_name(&client->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &tmp006_info; + + indio_dev->channels = tmp006_channels; + indio_dev->num_channels = ARRAY_SIZE(tmp006_channels); + indio_dev->available_scan_masks = tmp006_scan_masks; + + ret = i2c_smbus_read_word_swapped(data->client, TMP006_CONFIG); + if (ret < 0) + return ret; + data->config = ret; + + if ((ret & TMP006_CONFIG_MOD_MASK) != TMP006_CONFIG_MOD_MASK) { + ret = tmp006_power(&client->dev, true); + if (ret < 0) + return ret; + } + + ret = devm_add_action_or_reset(&client->dev, tmp006_powerdown_cleanup, + &client->dev); + if (ret < 0) + return ret; + + if (client->irq > 0) { + data->drdy_trig = devm_iio_trigger_alloc(&client->dev, + "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->drdy_trig) + return -ENOMEM; + + data->drdy_trig->ops = &tmp006_trigger_ops; + iio_trigger_set_drvdata(data->drdy_trig, indio_dev); + ret = devm_iio_trigger_register(&client->dev, data->drdy_trig); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(data->drdy_trig); + + ret = devm_request_irq(&client->dev, client->irq, + iio_trigger_generic_data_rdy_poll, + IRQF_NO_THREAD, "tmp006_irq", + data->drdy_trig); + if (ret < 0) + return ret; + } + + ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, NULL, + tmp006_trigger_handler, NULL); + if (ret < 0) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static int tmp006_suspend(struct device *dev) +{ + return tmp006_power(dev, false); +} + +static int tmp006_resume(struct device *dev) +{ + return tmp006_power(dev, true); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(tmp006_pm_ops, tmp006_suspend, tmp006_resume); + +static const struct of_device_id tmp006_of_match[] = { + { .compatible = "ti,tmp006" }, + { } +}; +MODULE_DEVICE_TABLE(of, tmp006_of_match); + +static const struct i2c_device_id tmp006_id[] = { + { .name = "tmp006" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tmp006_id); + +static struct i2c_driver tmp006_driver = { + .driver = { + .name = "tmp006", + .of_match_table = tmp006_of_match, + .pm = pm_sleep_ptr(&tmp006_pm_ops), + }, + .probe = tmp006_probe, + .id_table = tmp006_id, +}; +module_i2c_driver(tmp006_driver); + +MODULE_AUTHOR("Peter Meerwald "); +MODULE_DESCRIPTION("TI TMP006 IR thermopile sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/temperature/tmp007.c b/drivers/iio/temperature/tmp007.c new file mode 100644 index 000000000..4e743424f --- /dev/null +++ b/drivers/iio/temperature/tmp007.c @@ -0,0 +1,581 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tmp007.c - Support for TI TMP007 IR thermopile sensor with integrated math engine + * + * Copyright (c) 2017 Manivannan Sadhasivam + * + * Driver for the Texas Instruments I2C 16-bit IR thermopile sensor + * + * (7-bit I2C slave address (0x40 - 0x47), changeable via ADR pins) + * + * Note: + * 1. This driver assumes that the sensor has been calibrated beforehand + * 2. Limit threshold events are enabled at the start + * 3. Operating mode: INT + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define TMP007_TDIE 0x01 +#define TMP007_CONFIG 0x02 +#define TMP007_TOBJECT 0x03 +#define TMP007_STATUS 0x04 +#define TMP007_STATUS_MASK 0x05 +#define TMP007_TOBJ_HIGH_LIMIT 0x06 +#define TMP007_TOBJ_LOW_LIMIT 0x07 +#define TMP007_TDIE_HIGH_LIMIT 0x08 +#define TMP007_TDIE_LOW_LIMIT 0x09 +#define TMP007_MANUFACTURER_ID 0x1e +#define TMP007_DEVICE_ID 0x1f + +#define TMP007_CONFIG_CONV_EN BIT(12) +#define TMP007_CONFIG_TC_EN BIT(6) +#define TMP007_CONFIG_CR_MASK GENMASK(11, 9) +#define TMP007_CONFIG_ALERT_EN BIT(8) +#define TMP007_CONFIG_CR_SHIFT 9 + +/* Status register flags */ +#define TMP007_STATUS_ALERT BIT(15) +#define TMP007_STATUS_CONV_READY BIT(14) +#define TMP007_STATUS_OHF BIT(13) +#define TMP007_STATUS_OLF BIT(12) +#define TMP007_STATUS_LHF BIT(11) +#define TMP007_STATUS_LLF BIT(10) +#define TMP007_STATUS_DATA_VALID BIT(9) + +#define TMP007_MANUFACTURER_MAGIC 0x5449 +#define TMP007_DEVICE_MAGIC 0x0078 + +#define TMP007_TEMP_SHIFT 2 + +struct tmp007_data { + struct i2c_client *client; + struct mutex lock; + u16 config; + u16 status_mask; +}; + +static const int tmp007_avgs[5][2] = { {4, 0}, {2, 0}, {1, 0}, + {0, 500000}, {0, 250000} }; + +static int tmp007_read_temperature(struct tmp007_data *data, u8 reg) +{ + s32 ret; + int tries = 50; + + while (tries-- > 0) { + ret = i2c_smbus_read_word_swapped(data->client, + TMP007_STATUS); + if (ret < 0) + return ret; + if ((ret & TMP007_STATUS_CONV_READY) && + !(ret & TMP007_STATUS_DATA_VALID)) + break; + msleep(100); + } + + if (tries < 0) + return -EIO; + + return i2c_smbus_read_word_swapped(data->client, reg); +} + +static int tmp007_powerdown(struct tmp007_data *data) +{ + return i2c_smbus_write_word_swapped(data->client, TMP007_CONFIG, + data->config & ~TMP007_CONFIG_CONV_EN); +} + +static int tmp007_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct tmp007_data *data = iio_priv(indio_dev); + s32 ret; + int conv_rate; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (channel->channel2) { + case IIO_MOD_TEMP_AMBIENT: /* LSB: 0.03125 degree Celsius */ + ret = i2c_smbus_read_word_swapped(data->client, TMP007_TDIE); + if (ret < 0) + return ret; + break; + case IIO_MOD_TEMP_OBJECT: + ret = tmp007_read_temperature(data, TMP007_TOBJECT); + if (ret < 0) + return ret; + break; + default: + return -EINVAL; + } + + *val = sign_extend32(ret, 15) >> TMP007_TEMP_SHIFT; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 31; + *val2 = 250000; + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + conv_rate = (data->config & TMP007_CONFIG_CR_MASK) + >> TMP007_CONFIG_CR_SHIFT; + *val = tmp007_avgs[conv_rate][0]; + *val2 = tmp007_avgs[conv_rate][1]; + + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int tmp007_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int val, + int val2, long mask) +{ + struct tmp007_data *data = iio_priv(indio_dev); + int i; + u16 tmp; + + if (mask == IIO_CHAN_INFO_SAMP_FREQ) { + for (i = 0; i < ARRAY_SIZE(tmp007_avgs); i++) { + if ((val == tmp007_avgs[i][0]) && + (val2 == tmp007_avgs[i][1])) { + tmp = data->config & ~TMP007_CONFIG_CR_MASK; + tmp |= (i << TMP007_CONFIG_CR_SHIFT); + + return i2c_smbus_write_word_swapped(data->client, + TMP007_CONFIG, + data->config = tmp); + } + } + } + + return -EINVAL; +} + +static irqreturn_t tmp007_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct tmp007_data *data = iio_priv(indio_dev); + int ret; + + ret = i2c_smbus_read_word_swapped(data->client, TMP007_STATUS); + if ((ret < 0) || !(ret & (TMP007_STATUS_OHF | TMP007_STATUS_OLF | + TMP007_STATUS_LHF | TMP007_STATUS_LLF))) + return IRQ_NONE; + + if (ret & TMP007_STATUS_OHF) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_TEMP, 0, + IIO_MOD_TEMP_OBJECT, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + + if (ret & TMP007_STATUS_OLF) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_TEMP, 0, + IIO_MOD_TEMP_OBJECT, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns(indio_dev)); + + if (ret & TMP007_STATUS_LHF) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_TEMP, 0, + IIO_MOD_TEMP_AMBIENT, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + + if (ret & TMP007_STATUS_LLF) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_TEMP, 0, + IIO_MOD_TEMP_AMBIENT, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns(indio_dev)); + + return IRQ_HANDLED; +} + +static int tmp007_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct tmp007_data *data = iio_priv(indio_dev); + unsigned int status_mask; + int ret; + + switch (chan->channel2) { + case IIO_MOD_TEMP_AMBIENT: + if (dir == IIO_EV_DIR_RISING) + status_mask = TMP007_STATUS_LHF; + else + status_mask = TMP007_STATUS_LLF; + break; + case IIO_MOD_TEMP_OBJECT: + if (dir == IIO_EV_DIR_RISING) + status_mask = TMP007_STATUS_OHF; + else + status_mask = TMP007_STATUS_OLF; + break; + default: + return -EINVAL; + } + + mutex_lock(&data->lock); + ret = i2c_smbus_read_word_swapped(data->client, TMP007_STATUS_MASK); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + + if (state) + ret |= status_mask; + else + ret &= ~status_mask; + + return i2c_smbus_write_word_swapped(data->client, TMP007_STATUS_MASK, + data->status_mask = ret); +} + +static int tmp007_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir) +{ + struct tmp007_data *data = iio_priv(indio_dev); + unsigned int mask; + + switch (chan->channel2) { + case IIO_MOD_TEMP_AMBIENT: + if (dir == IIO_EV_DIR_RISING) + mask = TMP007_STATUS_LHF; + else + mask = TMP007_STATUS_LLF; + break; + case IIO_MOD_TEMP_OBJECT: + if (dir == IIO_EV_DIR_RISING) + mask = TMP007_STATUS_OHF; + else + mask = TMP007_STATUS_OLF; + break; + default: + return -EINVAL; + } + + return !!(data->status_mask & mask); +} + +static int tmp007_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, + int *val, int *val2) +{ + struct tmp007_data *data = iio_priv(indio_dev); + int ret; + u8 reg; + + switch (chan->channel2) { + case IIO_MOD_TEMP_AMBIENT: /* LSB: 0.5 degree Celsius */ + if (dir == IIO_EV_DIR_RISING) + reg = TMP007_TDIE_HIGH_LIMIT; + else + reg = TMP007_TDIE_LOW_LIMIT; + break; + case IIO_MOD_TEMP_OBJECT: + if (dir == IIO_EV_DIR_RISING) + reg = TMP007_TOBJ_HIGH_LIMIT; + else + reg = TMP007_TOBJ_LOW_LIMIT; + break; + default: + return -EINVAL; + } + + ret = i2c_smbus_read_word_swapped(data->client, reg); + if (ret < 0) + return ret; + + /* Shift length 7 bits = 6(15:6) + 1(0.5 LSB) */ + *val = sign_extend32(ret, 15) >> 7; + + return IIO_VAL_INT; +} + +static int tmp007_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, + int val, int val2) +{ + struct tmp007_data *data = iio_priv(indio_dev); + u8 reg; + + switch (chan->channel2) { + case IIO_MOD_TEMP_AMBIENT: + if (dir == IIO_EV_DIR_RISING) + reg = TMP007_TDIE_HIGH_LIMIT; + else + reg = TMP007_TDIE_LOW_LIMIT; + break; + case IIO_MOD_TEMP_OBJECT: + if (dir == IIO_EV_DIR_RISING) + reg = TMP007_TOBJ_HIGH_LIMIT; + else + reg = TMP007_TOBJ_LOW_LIMIT; + break; + default: + return -EINVAL; + } + + /* Full scale threshold value is +/- 256 degree Celsius */ + if (val < -256 || val > 255) + return -EINVAL; + + /* Shift length 7 bits = 6(15:6) + 1(0.5 LSB) */ + return i2c_smbus_write_word_swapped(data->client, reg, (val << 7)); +} + +static IIO_CONST_ATTR(sampling_frequency_available, "4 2 1 0.5 0.25"); + +static struct attribute *tmp007_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group tmp007_attribute_group = { + .attrs = tmp007_attributes, +}; + +static const struct iio_event_spec tmp007_obj_event[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_event_spec tmp007_die_event[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec tmp007_channels[] = { + { + .type = IIO_TEMP, + .modified = 1, + .channel2 = IIO_MOD_TEMP_AMBIENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .event_spec = tmp007_die_event, + .num_event_specs = ARRAY_SIZE(tmp007_die_event), + }, + { + .type = IIO_TEMP, + .modified = 1, + .channel2 = IIO_MOD_TEMP_OBJECT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .event_spec = tmp007_obj_event, + .num_event_specs = ARRAY_SIZE(tmp007_obj_event), + } +}; + +static const struct iio_info tmp007_info = { + .read_raw = tmp007_read_raw, + .write_raw = tmp007_write_raw, + .read_event_config = tmp007_read_event_config, + .write_event_config = tmp007_write_event_config, + .read_event_value = tmp007_read_thresh, + .write_event_value = tmp007_write_thresh, + .attrs = &tmp007_attribute_group, +}; + +static bool tmp007_identify(struct i2c_client *client) +{ + int manf_id, dev_id; + + manf_id = i2c_smbus_read_word_swapped(client, TMP007_MANUFACTURER_ID); + if (manf_id < 0) + return false; + + dev_id = i2c_smbus_read_word_swapped(client, TMP007_DEVICE_ID); + if (dev_id < 0) + return false; + + return (manf_id == TMP007_MANUFACTURER_MAGIC && dev_id == TMP007_DEVICE_MAGIC); +} + +static void tmp007_powerdown_action_cb(void *priv) +{ + struct tmp007_data *data = priv; + + tmp007_powerdown(data); +} + +static int tmp007_probe(struct i2c_client *client) +{ + const struct i2c_device_id *tmp007_id = i2c_client_get_device_id(client); + struct tmp007_data *data; + struct iio_dev *indio_dev; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -EOPNOTSUPP; + + if (!tmp007_identify(client)) { + dev_err(&client->dev, "TMP007 not found\n"); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + mutex_init(&data->lock); + + indio_dev->name = "tmp007"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &tmp007_info; + + indio_dev->channels = tmp007_channels; + indio_dev->num_channels = ARRAY_SIZE(tmp007_channels); + + /* + * Set Configuration register: + * 1. Conversion ON + * 2. ALERT enable + * 3. Transient correction enable + */ + + ret = i2c_smbus_read_word_swapped(data->client, TMP007_CONFIG); + if (ret < 0) + return ret; + + data->config = ret; + data->config |= (TMP007_CONFIG_CONV_EN | TMP007_CONFIG_ALERT_EN | TMP007_CONFIG_TC_EN); + + ret = i2c_smbus_write_word_swapped(data->client, TMP007_CONFIG, + data->config); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(&client->dev, tmp007_powerdown_action_cb, data); + if (ret) + return ret; + + /* + * Only the following flags can activate ALERT pin. Data conversion/validity flags + * flags can still be polled for getting temperature data + * + * Set Status Mask register: + * 1. Object temperature high limit enable + * 2. Object temperature low limit enable + * 3. TDIE temperature high limit enable + * 4. TDIE temperature low limit enable + */ + + ret = i2c_smbus_read_word_swapped(data->client, TMP007_STATUS_MASK); + if (ret < 0) + return ret; + + data->status_mask = ret; + data->status_mask |= (TMP007_STATUS_OHF | TMP007_STATUS_OLF + | TMP007_STATUS_LHF | TMP007_STATUS_LLF); + + ret = i2c_smbus_write_word_swapped(data->client, TMP007_STATUS_MASK, data->status_mask); + if (ret < 0) + return ret; + + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, tmp007_interrupt_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + tmp007_id->name, indio_dev); + if (ret) + return ret; + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static int tmp007_suspend(struct device *dev) +{ + struct tmp007_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + + return tmp007_powerdown(data); +} + +static int tmp007_resume(struct device *dev) +{ + struct tmp007_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + + return i2c_smbus_write_word_swapped(data->client, TMP007_CONFIG, + data->config | TMP007_CONFIG_CONV_EN); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(tmp007_pm_ops, tmp007_suspend, tmp007_resume); + +static const struct of_device_id tmp007_of_match[] = { + { .compatible = "ti,tmp007", }, + { } +}; +MODULE_DEVICE_TABLE(of, tmp007_of_match); + +static const struct i2c_device_id tmp007_id[] = { + { .name = "tmp007" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tmp007_id); + +static struct i2c_driver tmp007_driver = { + .driver = { + .name = "tmp007", + .of_match_table = tmp007_of_match, + .pm = pm_sleep_ptr(&tmp007_pm_ops), + }, + .probe = tmp007_probe, + .id_table = tmp007_id, +}; +module_i2c_driver(tmp007_driver); + +MODULE_AUTHOR("Manivannan Sadhasivam "); +MODULE_DESCRIPTION("TI TMP007 IR thermopile sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/temperature/tmp117.c b/drivers/iio/temperature/tmp117.c new file mode 100644 index 000000000..74cb8d62b --- /dev/null +++ b/drivers/iio/temperature/tmp117.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital temperature sensor with integrated Non-volatile memory + * Copyright (c) 2021 Puranjay Mohan + * + * Driver for the Texas Instruments TMP117 Temperature Sensor + * (7-bit I2C slave address (0x48 - 0x4B), changeable via ADD pins) + * + * Note: This driver assumes that the sensor has been calibrated beforehand. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define TMP117_REG_TEMP 0x0 +#define TMP117_REG_CFGR 0x1 +#define TMP117_REG_HIGH_LIM 0x2 +#define TMP117_REG_LOW_LIM 0x3 +#define TMP117_REG_EEPROM_UL 0x4 +#define TMP117_REG_EEPROM1 0x5 +#define TMP117_REG_EEPROM2 0x6 +#define TMP117_REG_TEMP_OFFSET 0x7 +#define TMP117_REG_EEPROM3 0x8 +#define TMP117_REG_DEVICE_ID 0xF + +#define TMP117_RESOLUTION_10UC 78125 +#define MICRODEGREE_PER_10MILLIDEGREE 10000 + +#define TMP116_DEVICE_ID 0x1116 +#define TMP117_DEVICE_ID 0x0117 +#define TMP119_DEVICE_ID 0x2117 + +struct tmp117_data { + struct i2c_client *client; + s16 calibbias; +}; + +struct tmp11x_info { + const char *name; + struct iio_chan_spec const *channels; + int num_channels; +}; + +static int tmp117_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct tmp117_data *data = iio_priv(indio_dev); + s32 ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = i2c_smbus_read_word_swapped(data->client, + TMP117_REG_TEMP); + if (ret < 0) + return ret; + *val = sign_extend32(ret, 15); + return IIO_VAL_INT; + + case IIO_CHAN_INFO_CALIBBIAS: + ret = i2c_smbus_read_word_swapped(data->client, + TMP117_REG_TEMP_OFFSET); + if (ret < 0) + return ret; + *val = sign_extend32(ret, 15); + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + /* + * Conversion from 10s of uC to mC + * as IIO reports temperature in mC + */ + *val = TMP117_RESOLUTION_10UC / MICRODEGREE_PER_10MILLIDEGREE; + *val2 = (TMP117_RESOLUTION_10UC % + MICRODEGREE_PER_10MILLIDEGREE) * 100; + + return IIO_VAL_INT_PLUS_MICRO; + + default: + return -EINVAL; + } +} + +static int tmp117_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec + const *channel, int val, int val2, long mask) +{ + struct tmp117_data *data = iio_priv(indio_dev); + s16 off; + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + off = clamp_t(int, val, S16_MIN, S16_MAX); + if (off == data->calibbias) + return 0; + data->calibbias = off; + return i2c_smbus_write_word_swapped(data->client, + TMP117_REG_TEMP_OFFSET, off); + + default: + return -EINVAL; + } +} + +static const struct iio_chan_spec tmp117_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static const struct iio_chan_spec tmp116_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static const struct tmp11x_info tmp116_channels_info = { + .name = "tmp116", + .channels = tmp116_channels, + .num_channels = ARRAY_SIZE(tmp116_channels) +}; + +static const struct tmp11x_info tmp117_channels_info = { + .name = "tmp117", + .channels = tmp117_channels, + .num_channels = ARRAY_SIZE(tmp117_channels) +}; + +static const struct tmp11x_info tmp119_channels_info = { + .name = "tmp119", + .channels = tmp117_channels, + .num_channels = ARRAY_SIZE(tmp117_channels) +}; + +static const struct iio_info tmp117_info = { + .read_raw = tmp117_read_raw, + .write_raw = tmp117_write_raw, +}; + +static int tmp117_probe(struct i2c_client *client) +{ + const struct tmp11x_info *match_data; + struct tmp117_data *data; + struct iio_dev *indio_dev; + int dev_id; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -EOPNOTSUPP; + + ret = devm_regulator_get_enable(&client->dev, "vcc"); + if (ret) + return ret; + + fsleep(1500); + + dev_id = i2c_smbus_read_word_swapped(client, TMP117_REG_DEVICE_ID); + if (dev_id < 0) + return dev_id; + + switch (dev_id) { + case TMP116_DEVICE_ID: + match_data = &tmp116_channels_info; + break; + case TMP117_DEVICE_ID: + match_data = &tmp117_channels_info; + break; + case TMP119_DEVICE_ID: + match_data = &tmp119_channels_info; + break; + default: + dev_info(&client->dev, + "Unknown device id (0x%x), use fallback compatible\n", + dev_id); + match_data = i2c_get_match_data(client); + } + + if (!match_data) + return dev_err_probe(&client->dev, -ENODEV, + "Failed to identify unsupported device\n"); + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + data->calibbias = 0; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &tmp117_info; + indio_dev->channels = match_data->channels; + indio_dev->num_channels = match_data->num_channels; + indio_dev->name = match_data->name; + + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct of_device_id tmp117_of_match[] = { + { .compatible = "ti,tmp116", .data = &tmp116_channels_info }, + { .compatible = "ti,tmp117", .data = &tmp117_channels_info }, + { .compatible = "ti,tmp119", .data = &tmp119_channels_info }, + { } +}; +MODULE_DEVICE_TABLE(of, tmp117_of_match); + +static const struct i2c_device_id tmp117_id[] = { + { .name = "tmp116", .driver_data = (kernel_ulong_t)&tmp116_channels_info }, + { .name = "tmp117", .driver_data = (kernel_ulong_t)&tmp117_channels_info }, + { .name = "tmp119", .driver_data = (kernel_ulong_t)&tmp119_channels_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tmp117_id); + +static struct i2c_driver tmp117_driver = { + .driver = { + .name = "tmp117", + .of_match_table = tmp117_of_match, + }, + .probe = tmp117_probe, + .id_table = tmp117_id, +}; +module_i2c_driver(tmp117_driver); + +MODULE_AUTHOR("Puranjay Mohan "); +MODULE_DESCRIPTION("TI TMP117 Temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/temperature/tsys01.c b/drivers/iio/temperature/tsys01.c new file mode 100644 index 000000000..28b3ce022 --- /dev/null +++ b/drivers/iio/temperature/tsys01.c @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tsys01.c - Support for Measurement-Specialties tsys01 temperature sensor + * + * Copyright (c) 2015 Measurement-Specialties + * + * Datasheet: + * http://www.meas-spec.com/downloads/TSYS01_Digital_Temperature_Sensor.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../common/ms_sensors/ms_sensors_i2c.h" + +/* TSYS01 Commands */ +#define TSYS01_RESET 0x1E +#define TSYS01_CONVERSION_START 0x48 +#define TSYS01_ADC_READ 0x00 +#define TSYS01_PROM_READ 0xA0 + +#define TSYS01_PROM_WORDS_NB 8 + +struct tsys01_dev { + void *client; + struct mutex lock; /* lock during conversion */ + + int (*reset)(void *cli, u8 cmd, unsigned int delay); + int (*convert_and_read)(void *cli, u8 conv, u8 rd, + unsigned int delay, u32 *adc); + int (*read_prom_word)(void *cli, int cmd, u16 *word); + + u16 prom[TSYS01_PROM_WORDS_NB]; +}; + +/* Multiplication coefficients for temperature computation */ +static const int coeff_mul[] = { -1500000, 1000000, -2000000, + 4000000, -2000000 }; + +static int tsys01_read_temperature(struct iio_dev *indio_dev, + s32 *temperature) +{ + int ret, i; + u32 adc; + s64 temp = 0; + struct tsys01_dev *dev_data = iio_priv(indio_dev); + + mutex_lock(&dev_data->lock); + ret = dev_data->convert_and_read(dev_data->client, + TSYS01_CONVERSION_START, + TSYS01_ADC_READ, 9000, &adc); + mutex_unlock(&dev_data->lock); + if (ret) + return ret; + + adc >>= 8; + + /* Temperature algorithm */ + for (i = 4; i > 0; i--) { + temp += coeff_mul[i] * + (s64)dev_data->prom[5 - i]; + temp *= (s64)adc; + temp = div64_s64(temp, 100000); + } + temp *= 10; + temp += coeff_mul[0] * (s64)dev_data->prom[5]; + temp = div64_s64(temp, 100000); + + *temperature = temp; + + return 0; +} + +static int tsys01_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + int ret; + s32 temperature; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (channel->type) { + case IIO_TEMP: /* in milli °C */ + ret = tsys01_read_temperature(indio_dev, &temperature); + if (ret) + return ret; + *val = temperature; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static const struct iio_chan_spec tsys01_channels[] = { + { + .type = IIO_TEMP, + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_PROCESSED), + } +}; + +static const struct iio_info tsys01_info = { + .read_raw = tsys01_read_raw, +}; + +static bool tsys01_crc_valid(u16 *n_prom) +{ + u8 cnt; + u8 sum = 0; + + for (cnt = 0; cnt < TSYS01_PROM_WORDS_NB; cnt++) + sum += ((n_prom[cnt] >> 8) + (n_prom[cnt] & 0xFF)); + + return (sum == 0); +} + +static int tsys01_read_prom(struct iio_dev *indio_dev) +{ + int i, ret; + struct tsys01_dev *dev_data = iio_priv(indio_dev); + char buf[7 * TSYS01_PROM_WORDS_NB + 1]; + char *ptr = buf; + + for (i = 0; i < TSYS01_PROM_WORDS_NB; i++) { + ret = dev_data->read_prom_word(dev_data->client, + TSYS01_PROM_READ + (i << 1), + &dev_data->prom[i]); + if (ret) + return ret; + + ret = sprintf(ptr, "0x%04x ", dev_data->prom[i]); + ptr += ret; + } + + if (!tsys01_crc_valid(dev_data->prom)) { + dev_err(&indio_dev->dev, "prom crc check error\n"); + return -ENODEV; + } + *ptr = 0; + dev_info(&indio_dev->dev, "PROM coefficients : %s\n", buf); + + return 0; +} + +static int tsys01_probe(struct iio_dev *indio_dev, struct device *dev) +{ + int ret; + struct tsys01_dev *dev_data = iio_priv(indio_dev); + + mutex_init(&dev_data->lock); + + indio_dev->info = &tsys01_info; + indio_dev->name = dev->driver->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = tsys01_channels; + indio_dev->num_channels = ARRAY_SIZE(tsys01_channels); + + ret = dev_data->reset(dev_data->client, TSYS01_RESET, 3000); + if (ret) + return ret; + + ret = tsys01_read_prom(indio_dev); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static int tsys01_i2c_probe(struct i2c_client *client) +{ + struct tsys01_dev *dev_data; + struct iio_dev *indio_dev; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_WRITE_BYTE | + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { + dev_err(&client->dev, + "Adapter does not support some i2c transaction\n"); + return -EOPNOTSUPP; + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*dev_data)); + if (!indio_dev) + return -ENOMEM; + + dev_data = iio_priv(indio_dev); + dev_data->client = client; + dev_data->reset = ms_sensors_reset; + dev_data->read_prom_word = ms_sensors_read_prom_word; + dev_data->convert_and_read = ms_sensors_convert_and_read; + + i2c_set_clientdata(client, indio_dev); + + return tsys01_probe(indio_dev, &client->dev); +} + +static const struct i2c_device_id tsys01_id[] = { + { .name = "tsys01" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tsys01_id); + +static const struct of_device_id tsys01_of_match[] = { + { .compatible = "meas,tsys01", }, + { } +}; +MODULE_DEVICE_TABLE(of, tsys01_of_match); + +static struct i2c_driver tsys01_driver = { + .probe = tsys01_i2c_probe, + .id_table = tsys01_id, + .driver = { + .name = "tsys01", + .of_match_table = tsys01_of_match, + }, +}; + +module_i2c_driver(tsys01_driver); + +MODULE_DESCRIPTION("Measurement-Specialties tsys01 temperature driver"); +MODULE_AUTHOR("William Markezana "); +MODULE_AUTHOR("Ludovic Tancerel "); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_MEAS_SPEC_SENSORS"); diff --git a/drivers/iio/temperature/tsys02d.c b/drivers/iio/temperature/tsys02d.c new file mode 100644 index 000000000..3ef723474 --- /dev/null +++ b/drivers/iio/temperature/tsys02d.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tsys02d.c - Support for Measurement-Specialties tsys02d temperature sensor + * + * Copyright (c) 2015 Measurement-Specialties + * + * (7-bit I2C slave address 0x40) + * + * Datasheet: + * http://www.meas-spec.com/downloads/Digital_Sensor_TSYS02D.pdf + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "../common/ms_sensors/ms_sensors_i2c.h" + +#define TSYS02D_RESET 0xFE + +static const int tsys02d_samp_freq[4] = { 20, 40, 70, 140 }; +/* String copy of the above const for readability purpose */ +static const char tsys02d_show_samp_freq[] = "20 40 70 140"; + +static int tsys02d_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + int ret; + s32 temperature; + struct ms_ht_dev *dev_data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (channel->type) { + case IIO_TEMP: /* in milli °C */ + ret = ms_sensors_ht_read_temperature(dev_data, + &temperature); + if (ret) + return ret; + *val = temperature; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + *val = tsys02d_samp_freq[dev_data->res_index]; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int tsys02d_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ms_ht_dev *dev_data = iio_priv(indio_dev); + int i, ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + i = ARRAY_SIZE(tsys02d_samp_freq); + while (i-- > 0) + if (val == tsys02d_samp_freq[i]) + break; + if (i < 0) + return -EINVAL; + mutex_lock(&dev_data->lock); + dev_data->res_index = i; + ret = ms_sensors_write_resolution(dev_data, i); + mutex_unlock(&dev_data->lock); + + return ret; + default: + return -EINVAL; + } +} + +static const struct iio_chan_spec tsys02d_channels[] = { + { + .type = IIO_TEMP, + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + } +}; + +static ssize_t tsys02_read_battery_low(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ms_ht_dev *dev_data = iio_priv(indio_dev); + + return ms_sensors_show_battery_low(dev_data, buf); +} + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL(tsys02d_show_samp_freq); +static IIO_DEVICE_ATTR(battery_low, S_IRUGO, + tsys02_read_battery_low, NULL, 0); + +static struct attribute *tsys02d_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_battery_low.dev_attr.attr, + NULL, +}; + +static const struct attribute_group tsys02d_attribute_group = { + .attrs = tsys02d_attributes, +}; + +static const struct iio_info tsys02d_info = { + .read_raw = tsys02d_read_raw, + .write_raw = tsys02d_write_raw, + .attrs = &tsys02d_attribute_group, +}; + +static int tsys02d_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + struct ms_ht_dev *dev_data; + struct iio_dev *indio_dev; + int ret; + u64 serial_number; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_BYTE_DATA | + I2C_FUNC_SMBUS_WRITE_BYTE | + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { + dev_err(&client->dev, + "Adapter does not support some i2c transaction\n"); + return -EOPNOTSUPP; + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*dev_data)); + if (!indio_dev) + return -ENOMEM; + + dev_data = iio_priv(indio_dev); + dev_data->client = client; + dev_data->res_index = 0; + mutex_init(&dev_data->lock); + + indio_dev->info = &tsys02d_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = tsys02d_channels; + indio_dev->num_channels = ARRAY_SIZE(tsys02d_channels); + + i2c_set_clientdata(client, indio_dev); + + ret = ms_sensors_reset(client, TSYS02D_RESET, 15000); + if (ret) + return ret; + + ret = ms_sensors_read_serial(client, &serial_number); + if (ret) + return ret; + dev_info(&client->dev, "Serial number : %llx", serial_number); + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id tsys02d_id[] = { + { .name = "tsys02d" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tsys02d_id); + +static struct i2c_driver tsys02d_driver = { + .probe = tsys02d_probe, + .id_table = tsys02d_id, + .driver = { + .name = "tsys02d", + }, +}; + +module_i2c_driver(tsys02d_driver); + +MODULE_DESCRIPTION("Measurement-Specialties tsys02d temperature driver"); +MODULE_AUTHOR("William Markezana "); +MODULE_AUTHOR("Ludovic Tancerel "); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_MEAS_SPEC_SENSORS"); diff --git a/drivers/iio/test/Kconfig b/drivers/iio/test/Kconfig new file mode 100644 index 000000000..4fc17dd0d --- /dev/null +++ b/drivers/iio/test/Kconfig @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Industrial I/O subsystem unit tests configuration +# + +# Keep in alphabetical order +config IIO_GTS_KUNIT_TEST + tristate "Test IIO gain-time-scale helpers" if !KUNIT_ALL_TESTS + depends on KUNIT + select IIO_GTS_HELPER + default KUNIT_ALL_TESTS + help + build unit tests for the IIO light sensor gain-time-scale helpers. + + For more information on KUnit and unit tests in general, please refer + to the KUnit documentation in Documentation/dev-tools/kunit/. + + If unsure, say N. Keep in alphabetical order + +config IIO_RESCALE_KUNIT_TEST + tristate "Test IIO rescale conversion functions" if !KUNIT_ALL_TESTS + depends on KUNIT && IIO_RESCALE + default KUNIT_ALL_TESTS + help + Build unit tests for the iio-rescale code. + + For more information on KUnit and unit tests in general, please refer + to the KUnit documentation in Documentation/dev-tools/kunit/. + + If unsure, say N. + +config IIO_FORMAT_KUNIT_TEST + tristate "Test IIO formatting functions" if !KUNIT_ALL_TESTS + depends on KUNIT + default KUNIT_ALL_TESTS + help + build unit tests for the IIO formatting functions. + + For more information on KUnit and unit tests in general, please refer + to the KUnit documentation in Documentation/dev-tools/kunit/. + + If unsure, say N. + +config IIO_MULTIPLY_KUNIT_TEST + tristate "Test IIO multiply functions" if !KUNIT_ALL_TESTS + depends on KUNIT + default KUNIT_ALL_TESTS + help + build unit tests for the IIO multiply functions. + + For more information on KUnit and unit tests in general, please refer + to the KUnit documentation in Documentation/dev-tools/kunit/. + + If unsure, say N. diff --git a/drivers/iio/test/Makefile b/drivers/iio/test/Makefile new file mode 100644 index 000000000..0c846bc21 --- /dev/null +++ b/drivers/iio/test/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the industrial I/O unit tests. +# + +# Keep in alphabetical order +obj-$(CONFIG_IIO_RESCALE_KUNIT_TEST) += iio-test-rescale.o +obj-$(CONFIG_IIO_FORMAT_KUNIT_TEST) += iio-test-format.o +obj-$(CONFIG_IIO_GTS_KUNIT_TEST) += iio-test-gts.o +obj-$(CONFIG_IIO_MULTIPLY_KUNIT_TEST) += iio-test-multiply.o +CFLAGS_iio-test-format.o += $(DISABLE_STRUCTLEAK_PLUGIN) diff --git a/drivers/iio/test/iio-test-format.c b/drivers/iio/test/iio-test-format.c new file mode 100644 index 000000000..e5d0a2ac4 --- /dev/null +++ b/drivers/iio/test/iio-test-format.c @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Unit tests for IIO formatting functions + * + * Copyright (c) 2020 Lars-Peter Clausen + */ + +#include +#include + +#define IIO_TEST_FORMAT_EXPECT_EQ(_test, _buf, _ret, _val) do { \ + KUNIT_EXPECT_EQ(_test, strlen(_buf), _ret); \ + KUNIT_EXPECT_STREQ(_test, (_buf), (_val)); \ + } while (0) + +static void iio_test_iio_format_value_integer(struct kunit *test) +{ + char *buf; + int val; + int ret; + + buf = kunit_kmalloc(test, PAGE_SIZE, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + val = 42; + ret = iio_format_value(buf, IIO_VAL_INT, 1, &val); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "42\n"); + + val = -23; + ret = iio_format_value(buf, IIO_VAL_INT, 1, &val); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-23\n"); + + val = 0; + ret = iio_format_value(buf, IIO_VAL_INT, 1, &val); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "0\n"); + + val = INT_MAX; + ret = iio_format_value(buf, IIO_VAL_INT, 1, &val); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "2147483647\n"); + + val = INT_MIN; + ret = iio_format_value(buf, IIO_VAL_INT, 1, &val); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-2147483648\n"); +} + +static void iio_test_iio_format_value_fixedpoint(struct kunit *test) +{ + int values[2]; + char *buf; + int ret; + + buf = kunit_kmalloc(test, PAGE_SIZE, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + /* positive >= 1 */ + values[0] = 1; + values[1] = 10; + + ret = iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "1.000010\n"); + + ret = iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO_DB, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "1.000010 dB\n"); + + ret = iio_format_value(buf, IIO_VAL_INT_PLUS_NANO, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "1.000000010\n"); + + /* positive < 1 */ + values[0] = 0; + values[1] = 12; + + ret = iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "0.000012\n"); + + ret = iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO_DB, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "0.000012 dB\n"); + + ret = iio_format_value(buf, IIO_VAL_INT_PLUS_NANO, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "0.000000012\n"); + + /* negative <= -1 */ + values[0] = -1; + values[1] = 10; + + ret = iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-1.000010\n"); + + ret = iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO_DB, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-1.000010 dB\n"); + + ret = iio_format_value(buf, IIO_VAL_INT_PLUS_NANO, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-1.000000010\n"); + + /* negative > -1 */ + values[0] = 0; + values[1] = -123; + ret = iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-0.000123\n"); + + ret = iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO_DB, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-0.000123 dB\n"); + + ret = iio_format_value(buf, IIO_VAL_INT_PLUS_NANO, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-0.000000123\n"); +} + +static void iio_test_iio_format_value_fractional(struct kunit *test) +{ + int values[2]; + char *buf; + int ret; + + buf = kunit_kmalloc(test, PAGE_SIZE, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + /* positive < 1 */ + values[0] = 1; + values[1] = 10; + ret = iio_format_value(buf, IIO_VAL_FRACTIONAL, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "0.100000000\n"); + + /* positive >= 1 */ + values[0] = 100; + values[1] = 3; + ret = iio_format_value(buf, IIO_VAL_FRACTIONAL, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "33.333333333\n"); + + /* negative > -1 */ + values[0] = -1; + values[1] = 1000000000; + ret = iio_format_value(buf, IIO_VAL_FRACTIONAL, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-0.000000001\n"); + + /* negative <= -1 */ + values[0] = -200; + values[1] = 3; + ret = iio_format_value(buf, IIO_VAL_FRACTIONAL, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-66.666666666\n"); + + /* Zero */ + values[0] = 0; + values[1] = -10; + ret = iio_format_value(buf, IIO_VAL_FRACTIONAL, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "0.000000000\n"); +} + +static void iio_test_iio_format_value_fractional_log2(struct kunit *test) +{ + int values[2]; + char *buf; + int ret; + + buf = kunit_kmalloc(test, PAGE_SIZE, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + /* positive < 1 */ + values[0] = 123; + values[1] = 10; + ret = iio_format_value(buf, IIO_VAL_FRACTIONAL_LOG2, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "0.120117187\n"); + + /* positive >= 1 */ + values[0] = 1234567; + values[1] = 10; + ret = iio_format_value(buf, IIO_VAL_FRACTIONAL_LOG2, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "1205.631835937\n"); + + /* negative > -1 */ + values[0] = -123; + values[1] = 10; + ret = iio_format_value(buf, IIO_VAL_FRACTIONAL_LOG2, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-0.120117187\n"); + + /* negative <= -1 */ + values[0] = -1234567; + values[1] = 10; + ret = iio_format_value(buf, IIO_VAL_FRACTIONAL_LOG2, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-1205.631835937\n"); + + /* Zero */ + values[0] = 0; + values[1] = 10; + ret = iio_format_value(buf, IIO_VAL_FRACTIONAL_LOG2, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "0.000000000\n"); +} + +static void iio_test_iio_format_value_multiple(struct kunit *test) +{ + int values[] = {1, -2, 3, -4, 5}; + char *buf; + int ret; + + buf = kunit_kmalloc(test, PAGE_SIZE, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + ret = iio_format_value(buf, IIO_VAL_INT_MULTIPLE, + ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "1 -2 3 -4 5 \n"); +} + +static void iio_test_iio_format_value_integer_64(struct kunit *test) +{ + int values[2]; + char *buf; + int ret; + + buf = kunit_kmalloc(test, PAGE_SIZE, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + iio_val_s64_decompose(24, &values[0], &values[1]); + ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "24\n"); + + iio_val_s64_decompose(-24, &values[0], &values[1]); + ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-24\n"); + + iio_val_s64_decompose(0, &values[0], &values[1]); + ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "0\n"); + + iio_val_s64_decompose(UINT_MAX, &values[0], &values[1]); + ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "4294967295\n"); + + iio_val_s64_decompose(-((s64)UINT_MAX), &values[0], &values[1]); + ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-4294967295\n"); + + iio_val_s64_decompose(LLONG_MAX, &values[0], &values[1]); + ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "9223372036854775807\n"); + + iio_val_s64_decompose(LLONG_MIN, &values[0], &values[1]); + ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-9223372036854775808\n"); +} + +static void iio_test_iio_format_value_decimal_64(struct kunit *test) +{ + int values[2]; + char *buf; + int ret; + + buf = kunit_kmalloc(test, PAGE_SIZE, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + /* DECIMAL64_MILLI: positive >= 1, value 1.234 */ + iio_val_s64_decompose(1234, &values[0], &values[1]); + ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MILLI, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "1.234\n"); + + /* DECIMAL64_MICRO: positive >= 1, value 3.141592 */ + iio_val_s64_decompose(3141592, &values[0], &values[1]); + ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MICRO, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "3.141592\n"); + + /* DECIMAL64_MILLI: positive < 1, value 0.042 */ + iio_val_s64_decompose(42, &values[0], &values[1]); + ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MILLI, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "0.042\n"); + + /* DECIMAL64_MILLI: negative <= -1, value -1.234 */ + iio_val_s64_decompose(-1234, &values[0], &values[1]); + ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MILLI, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-1.234\n"); + + /* DECIMAL64_MILLI: negative > -1, value -0.123 */ + iio_val_s64_decompose(-123, &values[0], &values[1]); + ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MILLI, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-0.123\n"); + + /* DECIMAL64_MILLI: zero */ + iio_val_s64_decompose(0, &values[0], &values[1]); + ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MILLI, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "0.000\n"); + + /* DECIMAL64_NANO: value 1.000000001 */ + iio_val_s64_decompose(1000000001, &values[0], &values[1]); + ret = iio_format_value(buf, IIO_VAL_DECIMAL64_NANO, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "1.000000001\n"); + + /* DECIMAL64_MICRO: large value using upper 32 bits */ + iio_val_s64_decompose(5000000000000042LL, &values[0], &values[1]); + ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MICRO, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "5000000000.000042\n"); + + /* limits */ + iio_val_s64_decompose(LLONG_MAX, &values[0], &values[1]); + ret = iio_format_value(buf, IIO_VAL_DECIMAL64_PICO, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "9223372.036854775807\n"); + ret = iio_format_value(buf, IIO_VAL_DECIMAL64_NANO, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "9223372036.854775807\n"); + ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MICRO, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "9223372036854.775807\n"); + + iio_val_s64_decompose(LLONG_MIN, &values[0], &values[1]); + ret = iio_format_value(buf, IIO_VAL_DECIMAL64_PICO, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-9223372.036854775808\n"); + ret = iio_format_value(buf, IIO_VAL_DECIMAL64_NANO, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-9223372036.854775808\n"); + ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MICRO, ARRAY_SIZE(values), values); + IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-9223372036854.775808\n"); +} + +static struct kunit_case iio_format_test_cases[] = { + KUNIT_CASE(iio_test_iio_format_value_integer), + KUNIT_CASE(iio_test_iio_format_value_fixedpoint), + KUNIT_CASE(iio_test_iio_format_value_fractional), + KUNIT_CASE(iio_test_iio_format_value_fractional_log2), + KUNIT_CASE(iio_test_iio_format_value_multiple), + KUNIT_CASE(iio_test_iio_format_value_integer_64), + KUNIT_CASE(iio_test_iio_format_value_decimal_64), + { } +}; + +static struct kunit_suite iio_format_test_suite = { + .name = "iio-format", + .test_cases = iio_format_test_cases, +}; +kunit_test_suite(iio_format_test_suite); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Test IIO formatting functions"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/test/iio-test-gts.c b/drivers/iio/test/iio-test-gts.c new file mode 100644 index 000000000..6ffff85ba --- /dev/null +++ b/drivers/iio/test/iio-test-gts.c @@ -0,0 +1,515 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Unit tests for IIO light sensor gain-time-scale helpers + * + * Copyright (c) 2023 Matti Vaittinen + */ + +#include +#include +#include +#include +#include + +/* + * Please, read the "rant" from the top of the lib/test_linear_ranges.c if + * you see a line of helper code which is not being tested. + * + * Then, please look at the line which is not being tested. Is this line + * somehow unusually complex? If answer is "no", then chances are that the + * "development inertia" caused by adding a test exceeds the benefits. + * + * If yes, then adding a test is probably a good idea but please stop for a + * moment and consider the effort of changing all the tests when code gets + * refactored. Eventually it needs to be. + */ + +#define TEST_TSEL_50 1 +#define TEST_TSEL_X_MIN TEST_TSEL_50 +#define TEST_TSEL_100 0 +#define TEST_TSEL_200 2 +#define TEST_TSEL_400 4 +#define TEST_TSEL_X_MAX TEST_TSEL_400 + +#define TEST_GSEL_1 0x00 +#define TEST_GSEL_X_MIN TEST_GSEL_1 +#define TEST_GSEL_4 0x08 +#define TEST_GSEL_16 0x0a +#define TEST_GSEL_32 0x0b +#define TEST_GSEL_64 0x0c +#define TEST_GSEL_256 0x18 +#define TEST_GSEL_512 0x19 +#define TEST_GSEL_1024 0x1a +#define TEST_GSEL_2048 0x1b +#define TEST_GSEL_4096 0x1c +#define TEST_GSEL_X_MAX TEST_GSEL_4096 + +#define TEST_SCALE_1X 64 +#define TEST_SCALE_MIN_X TEST_SCALE_1X +#define TEST_SCALE_2X 32 +#define TEST_SCALE_4X 16 +#define TEST_SCALE_8X 8 +#define TEST_SCALE_16X 4 +#define TEST_SCALE_32X 2 +#define TEST_SCALE_64X 1 + +#define TEST_SCALE_NANO_128X 500000000 +#define TEST_SCALE_NANO_256X 250000000 +#define TEST_SCALE_NANO_512X 125000000 +#define TEST_SCALE_NANO_1024X 62500000 +#define TEST_SCALE_NANO_2048X 31250000 +#define TEST_SCALE_NANO_4096X 15625000 +#define TEST_SCALE_NANO_4096X2 7812500 +#define TEST_SCALE_NANO_4096X4 3906250 +#define TEST_SCALE_NANO_4096X8 1953125 + +#define TEST_SCALE_NANO_MAX_X TEST_SCALE_NANO_4096X8 + +/* + * Can't have this allocated from stack because the kunit clean-up will + * happen only after the test function has already gone + */ +static struct iio_gts gts; + +/* Keep the gain and time tables unsorted to test the sorting */ +static const struct iio_gain_sel_pair gts_test_gains[] = { + GAIN_SCALE_GAIN(1, TEST_GSEL_1), + GAIN_SCALE_GAIN(4, TEST_GSEL_4), + GAIN_SCALE_GAIN(16, TEST_GSEL_16), + GAIN_SCALE_GAIN(32, TEST_GSEL_32), + GAIN_SCALE_GAIN(64, TEST_GSEL_64), + GAIN_SCALE_GAIN(256, TEST_GSEL_256), + GAIN_SCALE_GAIN(512, TEST_GSEL_512), + GAIN_SCALE_GAIN(1024, TEST_GSEL_1024), + GAIN_SCALE_GAIN(4096, TEST_GSEL_4096), + GAIN_SCALE_GAIN(2048, TEST_GSEL_2048), +#define HWGAIN_MAX 4096 +}; + +static const struct iio_itime_sel_mul gts_test_itimes[] = { + GAIN_SCALE_ITIME_US(100 * 1000, TEST_TSEL_100, 2), + GAIN_SCALE_ITIME_US(400 * 1000, TEST_TSEL_400, 8), + GAIN_SCALE_ITIME_US(400 * 1000, TEST_TSEL_400, 8), + GAIN_SCALE_ITIME_US(50 * 1000, TEST_TSEL_50, 1), + GAIN_SCALE_ITIME_US(200 * 1000, TEST_TSEL_200, 4), +#define TIMEGAIN_MAX 8 +}; +#define TOTAL_GAIN_MAX (HWGAIN_MAX * TIMEGAIN_MAX) +#define IIO_GTS_TEST_DEV "iio-gts-test-dev" + +static struct device *__test_init_iio_gain_scale(struct kunit *test, + struct iio_gts *gts, const struct iio_gain_sel_pair *g_table, + int num_g, const struct iio_itime_sel_mul *i_table, int num_i) +{ + struct device *dev; + int ret; + + dev = kunit_device_register(test, IIO_GTS_TEST_DEV); + + KUNIT_EXPECT_NOT_ERR_OR_NULL(test, dev); + if (IS_ERR_OR_NULL(dev)) + return NULL; + + ret = devm_iio_init_iio_gts(dev, TEST_SCALE_1X, 0, g_table, num_g, + i_table, num_i, gts); + KUNIT_EXPECT_EQ(test, 0, ret); + if (ret) + return NULL; + + return dev; +} + +#define test_init_iio_gain_scale(test, gts) \ + __test_init_iio_gain_scale(test, gts, gts_test_gains, \ + ARRAY_SIZE(gts_test_gains), gts_test_itimes, \ + ARRAY_SIZE(gts_test_itimes)) + +static void test_init_iio_gts_invalid(struct kunit *test) +{ + struct device *dev; + int ret; + const struct iio_itime_sel_mul itimes_neg[] = { + GAIN_SCALE_ITIME_US(-10, TEST_TSEL_400, 8), + GAIN_SCALE_ITIME_US(200 * 1000, TEST_TSEL_200, 4), + }; + const struct iio_gain_sel_pair gains_neg[] = { + GAIN_SCALE_GAIN(1, TEST_GSEL_1), + GAIN_SCALE_GAIN(2, TEST_GSEL_4), + GAIN_SCALE_GAIN(-2, TEST_GSEL_16), + }; + /* 55555 * 38656 = 2147534080 => overflows 32bit int */ + const struct iio_itime_sel_mul itimes_overflow[] = { + GAIN_SCALE_ITIME_US(400 * 1000, TEST_TSEL_400, 55555), + GAIN_SCALE_ITIME_US(200 * 1000, TEST_TSEL_200, 4), + }; + const struct iio_gain_sel_pair gains_overflow[] = { + GAIN_SCALE_GAIN(1, TEST_GSEL_1), + GAIN_SCALE_GAIN(2, TEST_GSEL_4), + GAIN_SCALE_GAIN(38656, TEST_GSEL_16), + }; + + dev = kunit_device_register(test, IIO_GTS_TEST_DEV); + KUNIT_EXPECT_NOT_ERR_OR_NULL(test, dev); + if (!dev) + return; + + /* Ok gains, negative time */ + ret = devm_iio_init_iio_gts(dev, TEST_SCALE_1X, 0, gts_test_gains, + ARRAY_SIZE(gts_test_gains), itimes_neg, + ARRAY_SIZE(itimes_neg), >s); + KUNIT_EXPECT_EQ(test, -EINVAL, ret); + + /* Ok times, negative gain */ + ret = devm_iio_init_iio_gts(dev, TEST_SCALE_1X, 0, gains_neg, + ARRAY_SIZE(gains_neg), gts_test_itimes, + ARRAY_SIZE(gts_test_itimes), >s); + KUNIT_EXPECT_EQ(test, -EINVAL, ret); + + /* gain * time overflow int */ + ret = devm_iio_init_iio_gts(dev, TEST_SCALE_1X, 0, gains_overflow, + ARRAY_SIZE(gains_overflow), itimes_overflow, + ARRAY_SIZE(itimes_overflow), >s); + KUNIT_EXPECT_EQ(test, -EOVERFLOW, ret); +} + +static void test_iio_gts_find_gain_for_scale_using_time(struct kunit *test) +{ + struct device *dev; + int ret, gain_sel; + + dev = test_init_iio_gain_scale(test, >s); + if (!dev) + return; + + ret = iio_gts_find_gain_sel_for_scale_using_time(>s, TEST_TSEL_100, + TEST_SCALE_8X, 0, &gain_sel); + /* + * Meas time 100 => gain by time 2x + * TEST_SCALE_8X matches total gain 8x + * => required HWGAIN 4x + */ + KUNIT_EXPECT_EQ(test, 0, ret); + KUNIT_EXPECT_EQ(test, TEST_GSEL_4, gain_sel); + + ret = iio_gts_find_gain_sel_for_scale_using_time(>s, TEST_TSEL_200, 0, + TEST_SCALE_NANO_256X, &gain_sel); + /* + * Meas time 200 => gain by time 4x + * TEST_SCALE_256X matches total gain 256x + * => required HWGAIN 256/4 => 64x + */ + KUNIT_EXPECT_EQ(test, 0, ret); + KUNIT_EXPECT_EQ(test, TEST_GSEL_64, gain_sel); + + /* Min time, Min gain */ + ret = iio_gts_find_gain_sel_for_scale_using_time(>s, TEST_TSEL_X_MIN, + TEST_SCALE_MIN_X, 0, &gain_sel); + KUNIT_EXPECT_EQ(test, 0, ret); + KUNIT_EXPECT_EQ(test, TEST_GSEL_1, gain_sel); + + /* Max time, Max gain */ + ret = iio_gts_find_gain_sel_for_scale_using_time(>s, TEST_TSEL_X_MAX, + 0, TEST_SCALE_NANO_MAX_X, &gain_sel); + KUNIT_EXPECT_EQ(test, 0, ret); + KUNIT_EXPECT_EQ(test, TEST_GSEL_4096, gain_sel); + + ret = iio_gts_find_gain_sel_for_scale_using_time(>s, TEST_TSEL_100, 0, + TEST_SCALE_NANO_256X, &gain_sel); + /* + * Meas time 100 => gain by time 2x + * TEST_SCALE_256X matches total gain 256x + * => required HWGAIN 256/2 => 128x (not in gain-table - unsupported) + */ + KUNIT_EXPECT_NE(test, 0, ret); + + ret = iio_gts_find_gain_sel_for_scale_using_time(>s, TEST_TSEL_200, 0, + TEST_SCALE_NANO_MAX_X, &gain_sel); + /* We can't reach the max gain with integration time smaller than MAX */ + KUNIT_EXPECT_NE(test, 0, ret); + + ret = iio_gts_find_gain_sel_for_scale_using_time(>s, TEST_TSEL_50, 0, + TEST_SCALE_NANO_MAX_X, &gain_sel); + /* We can't reach the max gain with integration time smaller than MAX */ + KUNIT_EXPECT_NE(test, 0, ret); +} + +static void test_iio_gts_find_new_gain_sel_by_old_gain_time(struct kunit *test) +{ + struct device *dev; + int ret, old_gain, new_gain, old_time_sel, new_time_sel; + + dev = test_init_iio_gain_scale(test, >s); + if (!dev) + return; + + old_gain = 32; + old_time_sel = TEST_TSEL_200; + new_time_sel = TEST_TSEL_400; + + ret = iio_gts_find_new_gain_sel_by_old_gain_time(>s, old_gain, + old_time_sel, new_time_sel, &new_gain); + KUNIT_EXPECT_EQ(test, 0, ret); + /* + * Doubling the integration time doubles the total gain - so old + * (hw)gain must be divided by two to compensate. => 32 / 2 => 16 + */ + KUNIT_EXPECT_EQ(test, 16, new_gain); + + old_gain = 4; + old_time_sel = TEST_TSEL_50; + new_time_sel = TEST_TSEL_200; + ret = iio_gts_find_new_gain_sel_by_old_gain_time(>s, old_gain, + old_time_sel, new_time_sel, &new_gain); + KUNIT_EXPECT_EQ(test, 0, ret); + /* + * gain by time 1x => 4x - (hw)gain 4x => 1x + */ + KUNIT_EXPECT_EQ(test, 1, new_gain); + + old_gain = 512; + old_time_sel = TEST_TSEL_400; + new_time_sel = TEST_TSEL_50; + ret = iio_gts_find_new_gain_sel_by_old_gain_time(>s, old_gain, + old_time_sel, new_time_sel, &new_gain); + KUNIT_EXPECT_EQ(test, 0, ret); + /* + * gain by time 8x => 1x - (hw)gain 512x => 4096x) + */ + KUNIT_EXPECT_EQ(test, 4096, new_gain); + + /* Unsupported gain 2x */ + old_gain = 4; + old_time_sel = TEST_TSEL_200; + new_time_sel = TEST_TSEL_400; + ret = iio_gts_find_new_gain_sel_by_old_gain_time(>s, old_gain, + old_time_sel, new_time_sel, &new_gain); + KUNIT_EXPECT_NE(test, 0, ret); + + /* Too small gain */ + old_gain = 4; + old_time_sel = TEST_TSEL_50; + new_time_sel = TEST_TSEL_400; + ret = iio_gts_find_new_gain_sel_by_old_gain_time(>s, old_gain, + old_time_sel, new_time_sel, &new_gain); + KUNIT_EXPECT_NE(test, 0, ret); + + /* Too big gain */ + old_gain = 1024; + old_time_sel = TEST_TSEL_400; + new_time_sel = TEST_TSEL_50; + ret = iio_gts_find_new_gain_sel_by_old_gain_time(>s, old_gain, + old_time_sel, new_time_sel, &new_gain); + KUNIT_EXPECT_NE(test, 0, ret); + +} + +static void test_iio_find_closest_gain_low(struct kunit *test) +{ + struct device *dev; + bool in_range; + int ret; + + const struct iio_gain_sel_pair gts_test_gains_gain_low[] = { + GAIN_SCALE_GAIN(4, TEST_GSEL_4), + GAIN_SCALE_GAIN(16, TEST_GSEL_16), + GAIN_SCALE_GAIN(32, TEST_GSEL_32), + }; + + dev = test_init_iio_gain_scale(test, >s); + if (!dev) + return; + + ret = iio_find_closest_gain_low(>s, 2, &in_range); + KUNIT_EXPECT_EQ(test, 1, ret); + KUNIT_EXPECT_EQ(test, true, in_range); + + ret = iio_find_closest_gain_low(>s, 1, &in_range); + KUNIT_EXPECT_EQ(test, 1, ret); + KUNIT_EXPECT_EQ(test, true, in_range); + + ret = iio_find_closest_gain_low(>s, 4095, &in_range); + KUNIT_EXPECT_EQ(test, 2048, ret); + KUNIT_EXPECT_EQ(test, true, in_range); + + ret = iio_find_closest_gain_low(>s, 4097, &in_range); + KUNIT_EXPECT_EQ(test, 4096, ret); + KUNIT_EXPECT_EQ(test, false, in_range); + + kunit_device_unregister(test, dev); + + dev = __test_init_iio_gain_scale(test, >s, gts_test_gains_gain_low, + ARRAY_SIZE(gts_test_gains_gain_low), + gts_test_itimes, ARRAY_SIZE(gts_test_itimes)); + if (!dev) + return; + + ret = iio_find_closest_gain_low(>s, 3, &in_range); + KUNIT_EXPECT_EQ(test, -EINVAL, ret); + KUNIT_EXPECT_EQ(test, false, in_range); +} + +static void test_iio_gts_total_gain_to_scale(struct kunit *test) +{ + struct device *dev; + int ret, scale_int, scale_nano; + + dev = test_init_iio_gain_scale(test, >s); + if (!dev) + return; + + ret = iio_gts_total_gain_to_scale(>s, 1, &scale_int, &scale_nano); + KUNIT_EXPECT_EQ(test, 0, ret); + KUNIT_EXPECT_EQ(test, TEST_SCALE_1X, scale_int); + KUNIT_EXPECT_EQ(test, 0, scale_nano); + + ret = iio_gts_total_gain_to_scale(>s, 1, &scale_int, &scale_nano); + KUNIT_EXPECT_EQ(test, 0, ret); + KUNIT_EXPECT_EQ(test, TEST_SCALE_1X, scale_int); + KUNIT_EXPECT_EQ(test, 0, scale_nano); + + ret = iio_gts_total_gain_to_scale(>s, 4096 * 8, &scale_int, + &scale_nano); + KUNIT_EXPECT_EQ(test, 0, ret); + KUNIT_EXPECT_EQ(test, 0, scale_int); + KUNIT_EXPECT_EQ(test, TEST_SCALE_NANO_4096X8, scale_nano); +} + +static void test_iio_gts_chk_times(struct kunit *test, const int *vals) +{ + static const int expected[] = {0, 50000, 0, 100000, 0, 200000, 0, 400000}; + int i; + + for (i = 0; i < ARRAY_SIZE(expected); i++) + KUNIT_EXPECT_EQ(test, expected[i], vals[i]); +} + +static void test_iio_gts_chk_scales_all(struct kunit *test, struct iio_gts *gts, + const int *vals, int len) +{ + static const int gains[] = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, + 1024, 2048, 4096, 4096 * 2, 4096 * 4, + 4096 * 8}; + int expected[ARRAY_SIZE(gains) * 2]; + int i, ret; + int exp_len = ARRAY_SIZE(gains) * 2; + + KUNIT_EXPECT_EQ(test, exp_len, len); + if (len != exp_len) + return; + + for (i = 0; i < ARRAY_SIZE(gains); i++) { + ret = iio_gts_total_gain_to_scale(gts, gains[i], + &expected[2 * i], + &expected[2 * i + 1]); + KUNIT_EXPECT_EQ(test, 0, ret); + if (ret) + return; + } + + for (i = 0; i < ARRAY_SIZE(expected); i++) + KUNIT_EXPECT_EQ(test, expected[i], vals[i]); +} + +static void test_iio_gts_chk_scales_t200(struct kunit *test, struct iio_gts *gts, + const int *vals, int len) +{ + /* The gain caused by time 200 is 4x */ + static const int gains[] = { + 1 * 4, + 4 * 4, + 16 * 4, + 32 * 4, + 64 * 4, + 256 * 4, + 512 * 4, + 1024 * 4, + 2048 * 4, + 4096 * 4 + }; + int expected[ARRAY_SIZE(gains) * 2]; + int i, ret; + + KUNIT_EXPECT_EQ(test, 2 * ARRAY_SIZE(gains), len); + if (len < 2 * ARRAY_SIZE(gains)) + return; + + for (i = 0; i < ARRAY_SIZE(gains); i++) { + ret = iio_gts_total_gain_to_scale(gts, gains[i], + &expected[2 * i], + &expected[2 * i + 1]); + KUNIT_EXPECT_EQ(test, 0, ret); + if (ret) + return; + } + + for (i = 0; i < ARRAY_SIZE(expected); i++) + KUNIT_EXPECT_EQ(test, expected[i], vals[i]); +} + +static void test_iio_gts_avail_test(struct kunit *test) +{ + struct device *dev; + int ret; + int type, len; + const int *vals; + + dev = test_init_iio_gain_scale(test, >s); + if (!dev) + return; + + /* test table building for times and iio_gts_avail_times() */ + ret = iio_gts_avail_times(>s, &vals, &type, &len); + KUNIT_EXPECT_EQ(test, IIO_AVAIL_LIST, ret); + if (ret) + return; + + KUNIT_EXPECT_EQ(test, IIO_VAL_INT_PLUS_MICRO, type); + KUNIT_EXPECT_EQ(test, 8, len); + if (len < 8) + return; + + test_iio_gts_chk_times(test, vals); + + /* Test table building for all scales and iio_gts_all_avail_scales() */ + ret = iio_gts_all_avail_scales(>s, &vals, &type, &len); + KUNIT_EXPECT_EQ(test, IIO_AVAIL_LIST, ret); + if (ret) + return; + + KUNIT_EXPECT_EQ(test, IIO_VAL_INT_PLUS_NANO, type); + + test_iio_gts_chk_scales_all(test, >s, vals, len); + + /* + * Test table building for scales/time and + * iio_gts_avail_scales_for_time() + */ + ret = iio_gts_avail_scales_for_time(>s, 200000, &vals, &type, &len); + KUNIT_EXPECT_EQ(test, IIO_AVAIL_LIST, ret); + if (ret) + return; + + KUNIT_EXPECT_EQ(test, IIO_VAL_INT_PLUS_NANO, type); + test_iio_gts_chk_scales_t200(test, >s, vals, len); +} + +static struct kunit_case iio_gts_test_cases[] = { + KUNIT_CASE(test_init_iio_gts_invalid), + KUNIT_CASE(test_iio_gts_find_gain_for_scale_using_time), + KUNIT_CASE(test_iio_gts_find_new_gain_sel_by_old_gain_time), + KUNIT_CASE(test_iio_find_closest_gain_low), + KUNIT_CASE(test_iio_gts_total_gain_to_scale), + KUNIT_CASE(test_iio_gts_avail_test), + { } +}; + +static struct kunit_suite iio_gts_test_suite = { + .name = "iio-gain-time-scale", + .test_cases = iio_gts_test_cases, +}; + +kunit_test_suite(iio_gts_test_suite); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Matti Vaittinen "); +MODULE_DESCRIPTION("Test IIO light sensor gain-time-scale helpers"); +MODULE_IMPORT_NS("IIO_GTS_HELPER"); diff --git a/drivers/iio/test/iio-test-multiply.c b/drivers/iio/test/iio-test-multiply.c new file mode 100644 index 000000000..432e279ff --- /dev/null +++ b/drivers/iio/test/iio-test-multiply.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Unit tests for IIO multiply functions + * + * Copyright (c) 2025 Hans de Goede + * Based on iio-test-format.c which is: + * Copyright (c) 2020 Lars-Peter Clausen + */ + +#include +#include +#include +#include + +static void __iio_test_iio_multiply_value_integer(struct kunit *test, s64 multiplier) +{ + int ret, result, val; + + val = 42; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT, val, 0); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, multiplier * val); + + val = -23; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT, val, 0); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, multiplier * val); + + val = 0; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT, val, 0); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, multiplier * val); +} + +static void iio_test_iio_multiply_value_integer(struct kunit *test) +{ + __iio_test_iio_multiply_value_integer(test, 20); + __iio_test_iio_multiply_value_integer(test, -20); +} + +static void __iio_test_iio_multiply_value_fixedpoint(struct kunit *test, s64 multiplier) +{ + int ret, result, val, val2; + + /* positive >= 1 (1.5) */ + val = 1; + val2 = 500000; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_MICRO, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * 15, 10)); + + val = 1; + val2 = 500000000; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_NANO, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * 15, 10)); + + /* positive < 1 (0.5) */ + val = 0; + val2 = 500000; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_MICRO, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * 5, 10)); + + val = 0; + val2 = 500000000; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_NANO, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * 5, 10)); + + /* negative <= -1 (-1.5) */ + val = -1; + val2 = 500000; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_MICRO, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * -15, 10)); + + val = -1; + val2 = 500000000; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_NANO, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * -15, 10)); + + /* negative > -1 (-0.5) */ + val = 0; + val2 = -500000; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_MICRO, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * -5, 10)); + + val = 0; + val2 = -500000000; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_NANO, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * -5, 10)); +} + +static void iio_test_iio_multiply_value_fixedpoint(struct kunit *test) +{ + __iio_test_iio_multiply_value_fixedpoint(test, 20); + __iio_test_iio_multiply_value_fixedpoint(test, -20); +} + +static void __iio_test_iio_multiply_value_fractional(struct kunit *test, s64 multiplier) +{ + int ret, result, val, val2; + + /* positive < 1 (1/10)*/ + val = 1; + val2 = 10; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * val, val2)); + + /* positive >= 1 (100/3)*/ + val = 100; + val2 = 3; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * val, val2)); + + /* negative > -1 (-1/10) */ + val = -1; + val2 = 10; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * val, val2)); + + /* negative <= -1 (-200/3)*/ + val = -200; + val2 = 3; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * val, val2)); + + /* Zero (0/-10) */ + val = 0; + val2 = -10; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * val, val2)); +} + +static void iio_test_iio_multiply_value_fractional(struct kunit *test) +{ + __iio_test_iio_multiply_value_fractional(test, 20); + __iio_test_iio_multiply_value_fractional(test, -20); +} + +static void __iio_test_iio_multiply_value_fractional_log2(struct kunit *test, s64 multiplier) +{ + int ret, result, val, val2; + + /* positive < 1 (123/1024) */ + val = 123; + val2 = 10; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL_LOG2, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, (multiplier * val) >> val2); + + /* positive >= 1 (1234567/1024) */ + val = 1234567; + val2 = 10; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL_LOG2, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, (multiplier * val) >> val2); + + /* negative > -1 (-123/1024) */ + val = -123; + val2 = 10; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL_LOG2, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, (multiplier * val) >> val2); + + /* negative <= -1 (-1234567/1024) */ + val = -1234567; + val2 = 10; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL_LOG2, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, (multiplier * val) >> val2); + + /* Zero (0/1024) */ + val = 0; + val2 = 10; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL_LOG2, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, (multiplier * val) >> val2); +} + +static void iio_test_iio_multiply_value_fractional_log2(struct kunit *test) +{ + __iio_test_iio_multiply_value_fractional_log2(test, 20); + __iio_test_iio_multiply_value_fractional_log2(test, -20); +} + +static struct kunit_case iio_multiply_test_cases[] = { + KUNIT_CASE(iio_test_iio_multiply_value_integer), + KUNIT_CASE(iio_test_iio_multiply_value_fixedpoint), + KUNIT_CASE(iio_test_iio_multiply_value_fractional), + KUNIT_CASE(iio_test_iio_multiply_value_fractional_log2), + { } +}; + +static struct kunit_suite iio_multiply_test_suite = { + .name = "iio-multiply", + .test_cases = iio_multiply_test_cases, +}; +kunit_test_suite(iio_multiply_test_suite); + +MODULE_AUTHOR("Hans de Goede "); +MODULE_DESCRIPTION("Test IIO multiply functions"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_UNIT_TEST"); diff --git a/drivers/iio/test/iio-test-rescale.c b/drivers/iio/test/iio-test-rescale.c new file mode 100644 index 000000000..bcd4a607b --- /dev/null +++ b/drivers/iio/test/iio-test-rescale.c @@ -0,0 +1,719 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Kunit tests for IIO rescale conversions + * + * Copyright (c) 2021 Liam Beguin + */ + +#include +#include + +#include +#include + +#include + +struct rescale_tc_data { + const char *name; + + const s32 numerator; + const s32 denominator; + const s32 offset; + + const int schan_val; + const int schan_val2; + const int schan_off; + const int schan_scale_type; + + const char *expected; + const char *expected_off; +}; + +static const struct rescale_tc_data scale_cases[] = { + /* + * Typical use cases + */ + { + .name = "typical IIO_VAL_INT, positive", + .numerator = 1000000, + .denominator = 8060, + .schan_scale_type = IIO_VAL_INT, + .schan_val = 42, + .expected = "5210.918114143", + }, + { + .name = "typical IIO_VAL_INT, negative", + .numerator = -1000000, + .denominator = 8060, + .schan_scale_type = IIO_VAL_INT, + .schan_val = 42, + .expected = "-5210.918114143", + }, + { + .name = "typical IIO_VAL_FRACTIONAL, positive", + .numerator = 1000000, + .denominator = 8060, + .schan_scale_type = IIO_VAL_FRACTIONAL, + .schan_val = 42, + .schan_val2 = 20, + .expected = "260.545905707", + }, + { + .name = "typical IIO_VAL_FRACTIONAL, negative", + .numerator = -1000000, + .denominator = 8060, + .schan_scale_type = IIO_VAL_FRACTIONAL, + .schan_val = 42, + .schan_val2 = 20, + .expected = "-260.545905707", + }, + { + .name = "typical IIO_VAL_FRACTIONAL_LOG2, positive", + .numerator = 42, + .denominator = 53, + .schan_scale_type = IIO_VAL_FRACTIONAL_LOG2, + .schan_val = 4096, + .schan_val2 = 16, + .expected = "0.049528301", + }, + { + .name = "typical IIO_VAL_FRACTIONAL_LOG2, negative", + .numerator = -42, + .denominator = 53, + .schan_scale_type = IIO_VAL_FRACTIONAL_LOG2, + .schan_val = 4096, + .schan_val2 = 16, + .expected = "-0.049528301", + }, + { + .name = "typical IIO_VAL_INT_PLUS_NANO, positive", + .numerator = 1000000, + .denominator = 8060, + .schan_scale_type = IIO_VAL_INT_PLUS_NANO, + .schan_val = 10, + .schan_val2 = 123456, + .expected = "1240.710106203", + }, + { + .name = "typical IIO_VAL_INT_PLUS_NANO, negative", + .numerator = -1000000, + .denominator = 8060, + .schan_scale_type = IIO_VAL_INT_PLUS_NANO, + .schan_val = 10, + .schan_val2 = 123456, + .expected = "-1240.710106203", + }, + { + .name = "typical IIO_VAL_INT_PLUS_MICRO, positive", + .numerator = 1000000, + .denominator = 8060, + .schan_scale_type = IIO_VAL_INT_PLUS_MICRO, + .schan_val = 10, + .schan_val2 = 1234, + .expected = "1240.84789", + }, + { + .name = "typical IIO_VAL_INT_PLUS_MICRO, negative", + .numerator = -1000000, + .denominator = 8060, + .schan_scale_type = IIO_VAL_INT_PLUS_MICRO, + .schan_val = 10, + .schan_val2 = 1234, + .expected = "-1240.84789", + }, + /* + * Use cases with small scales involving divisions + */ + { + .name = "small IIO_VAL_FRACTIONAL, 261/509 scaled by 90/1373754273", + .numerator = 261, + .denominator = 509, + .schan_scale_type = IIO_VAL_FRACTIONAL, + .schan_val = 90, + .schan_val2 = 1373754273, + .expected = "0.000000033594", + }, + { + .name = "small IIO_VAL_FRACTIONAL, 90/1373754273 scaled by 261/509", + .numerator = 90, + .denominator = 1373754273, + .schan_scale_type = IIO_VAL_FRACTIONAL, + .schan_val = 261, + .schan_val2 = 509, + .expected = "0.000000033594", + }, + { + .name = "small IIO_VAL_FRACTIONAL, 760/1373754273 scaled by 427/2727", + .numerator = 760, + .denominator = 1373754273, + .schan_scale_type = IIO_VAL_FRACTIONAL, + .schan_val = 427, + .schan_val2 = 2727, + .expected = "0.000000086626", + }, + { + .name = "small IIO_VAL_FRACTIONAL, 761/1373754273 scaled by 427/2727", + .numerator = 761, + .denominator = 1373754273, + .schan_scale_type = IIO_VAL_FRACTIONAL, + .schan_val = 427, + .schan_val2 = 2727, + .expected = "0.000000086740", + }, + { + .name = "small IIO_VAL_FRACTIONAL, 5/32768 scaled by 3/10000", + .numerator = 5, + .denominator = 32768, + .schan_scale_type = IIO_VAL_FRACTIONAL, + .schan_val = 3, + .schan_val2 = 10000, + .expected = "0.0000000457763671875", + }, + { + .name = "small IIO_VAL_FRACTIONAL, 0 < scale < 1", + .numerator = 6, + .denominator = 6, + .schan_scale_type = IIO_VAL_FRACTIONAL, + .schan_val = 1, + .schan_val2 = 3, + .expected = "0.3333333333333333", + }, + { + .name = "small IIO_VAL_FRACTIONAL, -1 < scale < 0", + .numerator = -6, + .denominator = 6, + .schan_scale_type = IIO_VAL_FRACTIONAL, + .schan_val = 1, + .schan_val2 = 3, + .expected = "-0.3333333333333333", + }, + { + .name = "small IIO_VAL_FRACTIONAL, 0 < scale < 2", + .numerator = 8, + .denominator = 2, + .schan_scale_type = IIO_VAL_FRACTIONAL, + .schan_val = 1, + .schan_val2 = 3, + .expected = "1.3333333333333333", + }, + { + .name = "small IIO_VAL_FRACTIONAL, -2 < scale < 0", + .numerator = -8, + .denominator = 2, + .schan_scale_type = IIO_VAL_FRACTIONAL, + .schan_val = 1, + .schan_val2 = 3, + .expected = "-1.3333333333333333", + }, + { + .name = "small IIO_VAL_FRACTIONAL_LOG2, 760/32768 scaled by 15/22", + .numerator = 760, + .denominator = 32768, + .schan_scale_type = IIO_VAL_FRACTIONAL_LOG2, + .schan_val = 15, + .schan_val2 = 22, + .expected = "0.000000082946", + }, + { + .name = "small IIO_VAL_FRACTIONAL_LOG2, 761/32768 scaled by 15/22", + .numerator = 761, + .denominator = 32768, + .schan_scale_type = IIO_VAL_FRACTIONAL_LOG2, + .schan_val = 15, + .schan_val2 = 22, + .expected = "0.000000083055", + }, + { + .name = "small IIO_VAL_FRACTIONAL_LOG2, 0 < scale < 1", + .numerator = 16, + .denominator = 3, + .schan_scale_type = IIO_VAL_FRACTIONAL_LOG2, + .schan_val = 1, + .schan_val2 = 4, + .expected = "0.3333333333333333", + }, + { + .name = "small IIO_VAL_FRACTIONAL_LOG2, -1 < scale < 0", + .numerator = -16, + .denominator = 3, + .schan_scale_type = IIO_VAL_FRACTIONAL_LOG2, + .schan_val = 1, + .schan_val2 = 4, + .expected = "-0.3333333333333333", + }, + { + .name = "small IIO_VAL_FRACTIONAL_LOG2, 0 < scale < 2", + .numerator = 8, + .denominator = 3, + .schan_scale_type = IIO_VAL_FRACTIONAL_LOG2, + .schan_val = 1, + .schan_val2 = 1, + .expected = "1.3333333333333333", + }, + { + .name = "small IIO_VAL_FRACTIONAL_LOG2, -2 < scale < 0", + .numerator = -8, + .denominator = 3, + .schan_scale_type = IIO_VAL_FRACTIONAL_LOG2, + .schan_val = 1, + .schan_val2 = 1, + .expected = "-1.3333333333333333", + }, + { + .name = "small IIO_VAL_INT_PLUS_MICRO, positive", + .numerator = 1, + .denominator = 2, + .schan_scale_type = IIO_VAL_INT_PLUS_MICRO, + .schan_val = 5, + .schan_val2 = 1234, + .expected = "2.500617", + }, + { + .name = "small IIO_VAL_INT_PLUS_MICRO, negative", + .numerator = -1, + .denominator = 2, + .schan_scale_type = IIO_VAL_INT_PLUS_MICRO, + .schan_val = 5, + .schan_val2 = 1234, + .expected = "-2.500617", + }, + /* + * INT_PLUS_{MICRO,NANO} positive/negative corner cases + */ + { + .name = "negative IIO_VAL_INT_PLUS_NANO, negative schan", + .numerator = 1000000, + .denominator = 8060, + .schan_scale_type = IIO_VAL_INT_PLUS_NANO, + .schan_val = -10, + .schan_val2 = 123456, + .expected = "-1240.710106203", + }, + { + .name = "negative IIO_VAL_INT_PLUS_NANO, both negative", + .numerator = -1000000, + .denominator = 8060, + .schan_scale_type = IIO_VAL_INT_PLUS_NANO, + .schan_val = -10, + .schan_val2 = 123456, + .expected = "1240.710106203", + }, + { + .name = "negative IIO_VAL_INT_PLUS_NANO, 3 negative", + .numerator = -1000000, + .denominator = -8060, + .schan_scale_type = IIO_VAL_INT_PLUS_NANO, + .schan_val = -10, + .schan_val2 = 123456, + .expected = "-1240.710106203", + }, + { + .name = "negative IIO_VAL_INT_PLUS_NANO, 4 negative", + .numerator = -1000000, + .denominator = -8060, + .schan_scale_type = IIO_VAL_INT_PLUS_NANO, + .schan_val = -10, + .schan_val2 = -123456, + .expected = "-1240.710106203", + }, + { + .name = "negative IIO_VAL_INT_PLUS_NANO, negative, *val = 0", + .numerator = 1, + .denominator = -10, + .schan_scale_type = IIO_VAL_INT_PLUS_NANO, + .schan_val = 0, + .schan_val2 = 123456789, + .expected = "-0.012345678", + }, + /* + * INT_PLUS_{MICRO,NANO} decimal part overflow + */ + { + .name = "decimal overflow IIO_VAL_INT_PLUS_NANO, positive", + .numerator = 1000000, + .denominator = 8060, + .schan_scale_type = IIO_VAL_INT_PLUS_NANO, + .schan_val = 10, + .schan_val2 = 123456789, + .expected = "1256.01200856", + }, + { + .name = "decimal overflow IIO_VAL_INT_PLUS_NANO, negative", + .numerator = -1000000, + .denominator = 8060, + .schan_scale_type = IIO_VAL_INT_PLUS_NANO, + .schan_val = 10, + .schan_val2 = 123456789, + .expected = "-1256.01200856", + }, + { + .name = "decimal overflow IIO_VAL_INT_PLUS_NANO, negative schan", + .numerator = 1000000, + .denominator = 8060, + .schan_scale_type = IIO_VAL_INT_PLUS_NANO, + .schan_val = -10, + .schan_val2 = 123456789, + .expected = "-1256.01200856", + }, + { + .name = "decimal overflow IIO_VAL_INT_PLUS_MICRO, positive", + .numerator = 1000000, + .denominator = 8060, + .schan_scale_type = IIO_VAL_INT_PLUS_MICRO, + .schan_val = 10, + .schan_val2 = 123456789, + .expected = "16557.914267", + }, + { + .name = "decimal overflow IIO_VAL_INT_PLUS_MICRO, negative", + .numerator = -1000000, + .denominator = 8060, + .schan_scale_type = IIO_VAL_INT_PLUS_MICRO, + .schan_val = 10, + .schan_val2 = 123456789, + .expected = "-16557.914267", + }, + { + .name = "decimal overflow IIO_VAL_INT_PLUS_MICRO, negative schan", + .numerator = 1000000, + .denominator = 8060, + .schan_scale_type = IIO_VAL_INT_PLUS_MICRO, + .schan_val = -10, + .schan_val2 = 123456789, + .expected = "-16557.914267", + }, + /* + * 32-bit overflow conditions + */ + { + .name = "overflow IIO_VAL_FRACTIONAL, positive", + .numerator = 2, + .denominator = 20, + .schan_scale_type = IIO_VAL_FRACTIONAL, + .schan_val = S32_MAX, + .schan_val2 = 1, + .expected = "214748364.7", + }, + { + .name = "overflow IIO_VAL_FRACTIONAL, negative", + .numerator = -2, + .denominator = 20, + .schan_scale_type = IIO_VAL_FRACTIONAL, + .schan_val = S32_MAX, + .schan_val2 = 1, + .expected = "-214748364.7", + }, + { + .name = "overflow IIO_VAL_FRACTIONAL_LOG2, positive", + .numerator = S32_MAX, + .denominator = 4096, + .schan_scale_type = IIO_VAL_FRACTIONAL_LOG2, + .schan_val = 4096, + .schan_val2 = 16, + .expected = "32767.99998474121", + }, + { + .name = "overflow IIO_VAL_FRACTIONAL_LOG2, negative", + .numerator = S32_MAX, + .denominator = 4096, + .schan_scale_type = IIO_VAL_FRACTIONAL_LOG2, + .schan_val = -4096, + .schan_val2 = 16, + .expected = "-32767.99998474121", + }, + { + .name = "overflow IIO_VAL_INT_PLUS_NANO, positive", + .numerator = 2, + .denominator = 20, + .schan_scale_type = IIO_VAL_INT_PLUS_NANO, + .schan_val = 10, + .schan_val2 = S32_MAX, + .expected = "1.214748364", + }, + { + .name = "overflow IIO_VAL_INT_PLUS_NANO, negative", + .numerator = -2, + .denominator = 20, + .schan_scale_type = IIO_VAL_INT_PLUS_NANO, + .schan_val = 10, + .schan_val2 = S32_MAX, + .expected = "-1.214748364", + }, + { + .name = "overflow IIO_VAL_INT_PLUS_NANO, negative schan", + .numerator = 2, + .denominator = 20, + .schan_scale_type = IIO_VAL_INT_PLUS_NANO, + .schan_val = -10, + .schan_val2 = S32_MAX, + .expected = "-1.214748364", + }, + { + .name = "overflow IIO_VAL_INT_PLUS_MICRO, positive", + .numerator = 2, + .denominator = 20, + .schan_scale_type = IIO_VAL_INT_PLUS_MICRO, + .schan_val = 10, + .schan_val2 = S32_MAX, + .expected = "215.748364", + }, + { + .name = "overflow IIO_VAL_INT_PLUS_MICRO, negative", + .numerator = -2, + .denominator = 20, + .schan_scale_type = IIO_VAL_INT_PLUS_MICRO, + .schan_val = 10, + .schan_val2 = S32_MAX, + .expected = "-215.748364", + }, + { + .name = "overflow IIO_VAL_INT_PLUS_MICRO, negative schan", + .numerator = 2, + .denominator = 20, + .schan_scale_type = IIO_VAL_INT_PLUS_MICRO, + .schan_val = -10, + .schan_val2 = S32_MAX, + .expected = "-215.748364", + }, +}; + +static const struct rescale_tc_data offset_cases[] = { + /* + * Typical use cases + */ + { + .name = "typical IIO_VAL_INT, positive", + .offset = 1234, + .schan_scale_type = IIO_VAL_INT, + .schan_val = 123, + .schan_val2 = 0, + .schan_off = 14, + .expected_off = "24", /* 23.872 */ + }, + { + .name = "typical IIO_VAL_INT, negative", + .offset = -1234, + .schan_scale_type = IIO_VAL_INT, + .schan_val = 12, + .schan_val2 = 0, + .schan_off = 14, + .expected_off = "-88", /* -88.83333333333333 */ + }, + { + .name = "typical IIO_VAL_FRACTIONAL, positive", + .offset = 1234, + .schan_scale_type = IIO_VAL_FRACTIONAL, + .schan_val = 12, + .schan_val2 = 34, + .schan_off = 14, + .expected_off = "3510", /* 3510.333333333333 */ + }, + { + .name = "typical IIO_VAL_FRACTIONAL, negative", + .offset = -1234, + .schan_scale_type = IIO_VAL_FRACTIONAL, + .schan_val = 12, + .schan_val2 = 34, + .schan_off = 14, + .expected_off = "-3482", /* -3482.333333333333 */ + }, + { + .name = "typical IIO_VAL_FRACTIONAL_LOG2, positive", + .offset = 1234, + .schan_scale_type = IIO_VAL_FRACTIONAL_LOG2, + .schan_val = 12, + .schan_val2 = 16, + .schan_off = 14, + .expected_off = "6739299", /* 6739299.333333333 */ + }, + { + .name = "typical IIO_VAL_FRACTIONAL_LOG2, negative", + .offset = -1234, + .schan_scale_type = IIO_VAL_FRACTIONAL_LOG2, + .schan_val = 12, + .schan_val2 = 16, + .schan_off = 14, + .expected_off = "-6739271", /* -6739271.333333333 */ + }, + { + .name = "typical IIO_VAL_INT_PLUS_NANO, positive", + .offset = 1234, + .schan_scale_type = IIO_VAL_INT_PLUS_NANO, + .schan_val = 10, + .schan_val2 = 123456789, + .schan_off = 14, + .expected_off = "135", /* 135.8951219647469 */ + }, + { + .name = "typical IIO_VAL_INT_PLUS_NANO, negative", + .offset = -1234, + .schan_scale_type = IIO_VAL_INT_PLUS_NANO, + .schan_val = 10, + .schan_val2 = 123456789, + .schan_off = 14, + .expected_off = "-107", /* -107.89512196474689 */ + }, + { + .name = "typical IIO_VAL_INT_PLUS_MICRO, positive", + .offset = 1234, + .schan_scale_type = IIO_VAL_INT_PLUS_MICRO, + .schan_val = 10, + .schan_val2 = 123456789, + .schan_off = 14, + .expected_off = "23", /* 23.246438560723952 */ + }, + { + .name = "typical IIO_VAL_INT_PLUS_MICRO, negative", + .offset = -12345, + .schan_scale_type = IIO_VAL_INT_PLUS_MICRO, + .schan_val = 10, + .schan_val2 = 123456789, + .schan_off = 14, + .expected_off = "-78", /* -78.50185091745313 */ + }, +}; + +static void case_to_desc(const struct rescale_tc_data *t, char *desc) +{ + strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE); +} + +KUNIT_ARRAY_PARAM(iio_rescale_scale, scale_cases, case_to_desc); +KUNIT_ARRAY_PARAM(iio_rescale_offset, offset_cases, case_to_desc); + +/** + * iio_str_to_nano() - Parse a fixed-point string to get an + * IIO_VAL_INT_PLUS_NANO value + * @str: The string to parse + * @nano: The number as an integer + * + * Returns 0 on success, or a negative error code if the string cound not be + * parsed. + */ +static int iio_str_to_nano(const char *str, s64 *nano) +{ + int tmp, tmp2; + int ret = 0; + + /* + * iio_str_to_fixpoint() uses 10^8 here instead of 10^9 as fract_mult is + * the multiplier for the first decimal place. + */ + ret = iio_str_to_fixpoint(str, 100000000, &tmp, &tmp2); + if (ret < 0) + return ret; + + if (tmp < 0) + tmp2 *= -1; + + *nano = (s64)tmp * 1000000000UL + tmp2; + + return ret; +} + +/** + * iio_test_relative_error_ppm() - Compute relative error (in parts-per-million) + * between two fixed-point strings + * @real_str: The real value as a string + * @exp_str: The expected value as a string + * + * Returns a negative error code if the strings cound not be parsed, or the + * relative error in parts-per-million. + */ +static int iio_test_relative_error_ppm(const char *real_str, const char *exp_str) +{ + s64 real, exp, err; + int ret; + + ret = iio_str_to_nano(real_str, &real); + if (ret < 0) + return ret; + + ret = iio_str_to_nano(exp_str, &exp); + if (ret < 0) + return ret; + + if (!exp) { + pr_err("Expected value is null, relative error is undefined\n"); + return -EINVAL; + } + + err = 1000000UL * abs(exp - real); + + return (int)div64_u64(err, abs(exp)); +} + +static void iio_rescale_test_scale(struct kunit *test) +{ + struct rescale_tc_data *t = (struct rescale_tc_data *)test->param_value; + char *buff = kunit_kmalloc(test, PAGE_SIZE, GFP_KERNEL); + struct rescale rescale; + int values[2]; + int rel_ppm; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buff); + + rescale.numerator = t->numerator; + rescale.denominator = t->denominator; + rescale.offset = t->offset; + values[0] = t->schan_val; + values[1] = t->schan_val2; + + ret = rescale_process_scale(&rescale, t->schan_scale_type, + &values[0], &values[1]); + + ret = iio_format_value(buff, ret, 2, values); + KUNIT_EXPECT_EQ(test, (int)strlen(buff), ret); + + rel_ppm = iio_test_relative_error_ppm(buff, t->expected); + KUNIT_EXPECT_GE_MSG(test, rel_ppm, 0, "failed to compute ppm\n"); + + KUNIT_EXPECT_EQ_MSG(test, rel_ppm, 0, + "\t real=%s" + "\texpected=%s\n", + buff, t->expected); +} + +static void iio_rescale_test_offset(struct kunit *test) +{ + struct rescale_tc_data *t = (struct rescale_tc_data *)test->param_value; + char *buff_off = kunit_kmalloc(test, PAGE_SIZE, GFP_KERNEL); + struct rescale rescale; + int values[2]; + int ret; + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buff_off); + + rescale.numerator = t->numerator; + rescale.denominator = t->denominator; + rescale.offset = t->offset; + values[0] = t->schan_val; + values[1] = t->schan_val2; + + ret = rescale_process_offset(&rescale, t->schan_scale_type, + t->schan_val, t->schan_val2, t->schan_off, + &values[0], &values[1]); + + ret = iio_format_value(buff_off, ret, 2, values); + KUNIT_EXPECT_EQ(test, (int)strlen(buff_off), ret); + + KUNIT_EXPECT_STREQ(test, strim(buff_off), t->expected_off); +} + +static struct kunit_case iio_rescale_test_cases[] = { + KUNIT_CASE_PARAM(iio_rescale_test_scale, iio_rescale_scale_gen_params), + KUNIT_CASE_PARAM(iio_rescale_test_offset, iio_rescale_offset_gen_params), + { } +}; + +static struct kunit_suite iio_rescale_test_suite = { + .name = "iio-rescale", + .test_cases = iio_rescale_test_cases, +}; +kunit_test_suite(iio_rescale_test_suite); + +MODULE_AUTHOR("Liam Beguin "); +MODULE_DESCRIPTION("Test IIO rescale conversion functions"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("IIO_RESCALE"); diff --git a/drivers/iio/trigger/Kconfig b/drivers/iio/trigger/Kconfig new file mode 100644 index 000000000..52899c22d --- /dev/null +++ b/drivers/iio/trigger/Kconfig @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Industrial I/O standalone triggers +# +# When adding new entries keep the list in alphabetical order + +menu "Triggers - standalone" + +config IIO_HRTIMER_TRIGGER + tristate "High resolution timer trigger" + depends on IIO_SW_TRIGGER + help + Provides a frequency based IIO trigger using high resolution + timers as interrupt source. + + To compile this driver as a module, choose M here: the + module will be called iio-trig-hrtimer. + +config IIO_STM32_LPTIMER_TRIGGER + tristate "STM32 Low-Power Timer Trigger" + depends on MFD_STM32_LPTIMER || COMPILE_TEST + help + Select this option to enable STM32 Low-Power Timer Trigger. + This can be used as trigger source for STM32 internal ADC + and/or DAC. + + To compile this driver as a module, choose M here: the + module will be called stm32-lptimer-trigger. + +config IIO_STM32_TIMER_TRIGGER + tristate "STM32 Timer Trigger" + depends on (ARCH_STM32 && MFD_STM32_TIMERS) || COMPILE_TEST + help + Select this option to enable STM32 Timer Trigger + + To compile this driver as a module, choose M here: the + module will be called stm32-timer-trigger. + +config IIO_TIGHTLOOP_TRIGGER + tristate "A kthread based hammering loop trigger" + depends on IIO_SW_TRIGGER + help + An experimental trigger, used to allow sensors to be sampled as fast + as possible under the limitations of whatever else is going on. + Uses a tight loop in a kthread. Will only work with lower half only + trigger consumers. + + To compile this driver as a module, choose M here: the + module will be called iio-trig-loop. + +config IIO_SYSFS_TRIGGER + tristate "SYSFS trigger" + depends on SYSFS + select IRQ_WORK + help + Provides support for using SYSFS entries as IIO triggers. + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called iio-trig-sysfs. + +endmenu diff --git a/drivers/iio/trigger/Makefile b/drivers/iio/trigger/Makefile new file mode 100644 index 000000000..fc1fd5661 --- /dev/null +++ b/drivers/iio/trigger/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for triggers not associated with iio-devices +# + +# When adding new entries keep the list in alphabetical order + +obj-$(CONFIG_IIO_HRTIMER_TRIGGER) += iio-trig-hrtimer.o +obj-$(CONFIG_IIO_STM32_LPTIMER_TRIGGER) += stm32-lptimer-trigger.o +obj-$(CONFIG_IIO_STM32_TIMER_TRIGGER) += stm32-timer-trigger.o +obj-$(CONFIG_IIO_SYSFS_TRIGGER) += iio-trig-sysfs.o +obj-$(CONFIG_IIO_TIGHTLOOP_TRIGGER) += iio-trig-loop.o diff --git a/drivers/iio/trigger/iio-trig-hrtimer.c b/drivers/iio/trigger/iio-trig-hrtimer.c new file mode 100644 index 000000000..57550b581 --- /dev/null +++ b/drivers/iio/trigger/iio-trig-hrtimer.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * The industrial I/O periodic hrtimer trigger driver + * + * Copyright (C) Intuitive Aerial AB + * Written by Marten Svanfeldt, marten@intuitiveaerial.com + * Copyright (C) 2012, Analog Devices Inc. + * Author: Lars-Peter Clausen + * Copyright (C) 2015, Intel Corporation + */ +#include +#include +#include + +#include +#include +#include + +/* Defined locally, not in time64.h yet. */ +#define PSEC_PER_SEC 1000000000000LL + +/* default sampling frequency - 100Hz */ +#define HRTIMER_DEFAULT_SAMPLING_FREQUENCY 100 + +struct iio_hrtimer_info { + struct iio_sw_trigger swt; + struct hrtimer timer; + int sampling_frequency[2]; + ktime_t period; +}; + +static const struct config_item_type iio_hrtimer_type = { + .ct_owner = THIS_MODULE, +}; + +static +ssize_t iio_hrtimer_show_sampling_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct iio_hrtimer_info *info = iio_trigger_get_drvdata(trig); + + return iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, + ARRAY_SIZE(info->sampling_frequency), + info->sampling_frequency); +} + +static +ssize_t iio_hrtimer_store_sampling_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct iio_hrtimer_info *info = iio_trigger_get_drvdata(trig); + unsigned long long val; + u64 period; + int integer, fract, ret; + + ret = iio_str_to_fixpoint(buf, 100, &integer, &fract); + if (ret) + return ret; + if (integer < 0 || fract < 0) + return -ERANGE; + + val = fract + 1000ULL * integer; /* mHz */ + + if (!val || val > UINT_MAX) + return -EINVAL; + + info->sampling_frequency[0] = integer; /* Hz */ + info->sampling_frequency[1] = fract * 1000; /* uHz */ + period = PSEC_PER_SEC; + do_div(period, val); + info->period = period; /* nS */ + + return len; +} + +static DEVICE_ATTR(sampling_frequency, S_IRUGO | S_IWUSR, + iio_hrtimer_show_sampling_frequency, + iio_hrtimer_store_sampling_frequency); + +static struct attribute *iio_hrtimer_attrs[] = { + &dev_attr_sampling_frequency.attr, + NULL +}; + +static const struct attribute_group iio_hrtimer_attr_group = { + .attrs = iio_hrtimer_attrs, +}; + +static const struct attribute_group *iio_hrtimer_attr_groups[] = { + &iio_hrtimer_attr_group, + NULL +}; + +static enum hrtimer_restart iio_hrtimer_trig_handler(struct hrtimer *timer) +{ + struct iio_hrtimer_info *info; + + info = container_of(timer, struct iio_hrtimer_info, timer); + + hrtimer_forward_now(timer, info->period); + iio_trigger_poll(info->swt.trigger); + + return HRTIMER_RESTART; +} + +static int iio_trig_hrtimer_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_hrtimer_info *trig_info; + + trig_info = iio_trigger_get_drvdata(trig); + + if (state) + hrtimer_start(&trig_info->timer, trig_info->period, + HRTIMER_MODE_REL_HARD); + else + hrtimer_cancel(&trig_info->timer); + + return 0; +} + +static const struct iio_trigger_ops iio_hrtimer_trigger_ops = { + .set_trigger_state = iio_trig_hrtimer_set_state, +}; + +static struct iio_sw_trigger *iio_trig_hrtimer_probe(const char *name) +{ + struct iio_hrtimer_info *trig_info; + int ret; + + trig_info = kzalloc_obj(*trig_info); + if (!trig_info) + return ERR_PTR(-ENOMEM); + + trig_info->swt.trigger = iio_trigger_alloc(NULL, "%s", name); + if (!trig_info->swt.trigger) { + ret = -ENOMEM; + goto err_free_trig_info; + } + + iio_trigger_set_drvdata(trig_info->swt.trigger, trig_info); + trig_info->swt.trigger->ops = &iio_hrtimer_trigger_ops; + trig_info->swt.trigger->dev.groups = iio_hrtimer_attr_groups; + + hrtimer_setup(&trig_info->timer, iio_hrtimer_trig_handler, CLOCK_MONOTONIC, + HRTIMER_MODE_REL_HARD); + + trig_info->sampling_frequency[0] = HRTIMER_DEFAULT_SAMPLING_FREQUENCY; + trig_info->period = NSEC_PER_SEC / trig_info->sampling_frequency[0]; + + ret = iio_trigger_register(trig_info->swt.trigger); + if (ret) + goto err_free_trigger; + + iio_swt_group_init_type_name(&trig_info->swt, name, &iio_hrtimer_type); + return &trig_info->swt; +err_free_trigger: + iio_trigger_free(trig_info->swt.trigger); +err_free_trig_info: + kfree(trig_info); + + return ERR_PTR(ret); +} + +static int iio_trig_hrtimer_remove(struct iio_sw_trigger *swt) +{ + struct iio_hrtimer_info *trig_info; + + trig_info = iio_trigger_get_drvdata(swt->trigger); + + iio_trigger_unregister(swt->trigger); + + /* cancel the timer after unreg to make sure no one rearms it */ + hrtimer_cancel(&trig_info->timer); + iio_trigger_free(swt->trigger); + kfree(trig_info); + + return 0; +} + +static const struct iio_sw_trigger_ops iio_trig_hrtimer_ops = { + .probe = iio_trig_hrtimer_probe, + .remove = iio_trig_hrtimer_remove, +}; + +static struct iio_sw_trigger_type iio_trig_hrtimer = { + .name = "hrtimer", + .owner = THIS_MODULE, + .ops = &iio_trig_hrtimer_ops, +}; + +module_iio_sw_trigger_driver(iio_trig_hrtimer); + +MODULE_AUTHOR("Marten Svanfeldt "); +MODULE_AUTHOR("Daniel Baluta "); +MODULE_DESCRIPTION("Periodic hrtimer trigger for the IIO subsystem"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/trigger/iio-trig-loop.c b/drivers/iio/trigger/iio-trig-loop.c new file mode 100644 index 000000000..580a46503 --- /dev/null +++ b/drivers/iio/trigger/iio-trig-loop.c @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2016 Jonathan Cameron + * + * Based on a mashup of the hrtimer trigger and continuous sampling proposal of + * Gregor Boirie + * + * Note this is still rather experimental and may eat babies. + * + * Todo + * * Protect against connection of devices that 'need' the top half + * handler. + * * Work out how to run top half handlers in this context if it is + * safe to do so (timestamp grabbing for example) + * + * Tested against a max1363. Used about 33% cpu for the thread and 20% + * for generic_buffer piping to /dev/null. Watermark set at 64 on a 128 + * element kfifo buffer. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +struct iio_loop_info { + struct iio_sw_trigger swt; + struct task_struct *task; +}; + +static const struct config_item_type iio_loop_type = { + .ct_owner = THIS_MODULE, +}; + +static int iio_loop_thread(void *data) +{ + struct iio_trigger *trig = data; + + set_freezable(); + + do { + iio_trigger_poll_nested(trig); + } while (likely(!kthread_freezable_should_stop(NULL))); + + return 0; +} + +static int iio_loop_trigger_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_loop_info *loop_trig = iio_trigger_get_drvdata(trig); + + if (state) { + loop_trig->task = kthread_run(iio_loop_thread, + trig, trig->name); + if (IS_ERR(loop_trig->task)) { + dev_err(&trig->dev, + "failed to create trigger loop thread\n"); + return PTR_ERR(loop_trig->task); + } + } else { + kthread_stop(loop_trig->task); + } + + return 0; +} + +static const struct iio_trigger_ops iio_loop_trigger_ops = { + .set_trigger_state = iio_loop_trigger_set_state, +}; + +static struct iio_sw_trigger *iio_trig_loop_probe(const char *name) +{ + struct iio_loop_info *trig_info; + int ret; + + trig_info = kzalloc_obj(*trig_info); + if (!trig_info) + return ERR_PTR(-ENOMEM); + + trig_info->swt.trigger = iio_trigger_alloc(NULL, "%s", name); + if (!trig_info->swt.trigger) { + ret = -ENOMEM; + goto err_free_trig_info; + } + + iio_trigger_set_drvdata(trig_info->swt.trigger, trig_info); + trig_info->swt.trigger->ops = &iio_loop_trigger_ops; + + ret = iio_trigger_register(trig_info->swt.trigger); + if (ret) + goto err_free_trigger; + + iio_swt_group_init_type_name(&trig_info->swt, name, &iio_loop_type); + + return &trig_info->swt; + +err_free_trigger: + iio_trigger_free(trig_info->swt.trigger); +err_free_trig_info: + kfree(trig_info); + + return ERR_PTR(ret); +} + +static int iio_trig_loop_remove(struct iio_sw_trigger *swt) +{ + struct iio_loop_info *trig_info; + + trig_info = iio_trigger_get_drvdata(swt->trigger); + + iio_trigger_unregister(swt->trigger); + iio_trigger_free(swt->trigger); + kfree(trig_info); + + return 0; +} + +static const struct iio_sw_trigger_ops iio_trig_loop_ops = { + .probe = iio_trig_loop_probe, + .remove = iio_trig_loop_remove, +}; + +static struct iio_sw_trigger_type iio_trig_loop = { + .name = "loop", + .owner = THIS_MODULE, + .ops = &iio_trig_loop_ops, +}; + +module_iio_sw_trigger_driver(iio_trig_loop); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("Loop based trigger for the iio subsystem"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:iio-trig-loop"); diff --git a/drivers/iio/trigger/iio-trig-sysfs.c b/drivers/iio/trigger/iio-trig-sysfs.c new file mode 100644 index 000000000..b242b050b --- /dev/null +++ b/drivers/iio/trigger/iio-trig-sysfs.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2011 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +struct iio_sysfs_trig { + struct iio_trigger *trig; + struct irq_work work; + int id; + struct list_head l; +}; + +static LIST_HEAD(iio_sysfs_trig_list); +static DEFINE_MUTEX(iio_sysfs_trig_list_mut); + +static int iio_sysfs_trigger_probe(int id); +static ssize_t iio_sysfs_trig_add(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret; + unsigned long input; + + ret = kstrtoul(buf, 10, &input); + if (ret) + return ret; + ret = iio_sysfs_trigger_probe(input); + if (ret) + return ret; + return len; +} +static DEVICE_ATTR(add_trigger, S_IWUSR, NULL, &iio_sysfs_trig_add); + +static int iio_sysfs_trigger_remove(int id); +static ssize_t iio_sysfs_trig_remove(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret; + unsigned long input; + + ret = kstrtoul(buf, 10, &input); + if (ret) + return ret; + ret = iio_sysfs_trigger_remove(input); + if (ret) + return ret; + return len; +} + +static DEVICE_ATTR(remove_trigger, S_IWUSR, NULL, &iio_sysfs_trig_remove); + +static struct attribute *iio_sysfs_trig_attrs[] = { + &dev_attr_add_trigger.attr, + &dev_attr_remove_trigger.attr, + NULL, +}; + +static const struct attribute_group iio_sysfs_trig_group = { + .attrs = iio_sysfs_trig_attrs, +}; + +static const struct attribute_group *iio_sysfs_trig_groups[] = { + &iio_sysfs_trig_group, + NULL +}; + + +/* Nothing to actually do upon release */ +static void iio_trigger_sysfs_release(struct device *dev) +{ +} + +static struct device iio_sysfs_trig_dev = { + .bus = &iio_bus_type, + .groups = iio_sysfs_trig_groups, + .release = &iio_trigger_sysfs_release, +}; + +static void iio_sysfs_trigger_work(struct irq_work *work) +{ + struct iio_sysfs_trig *trig = container_of(work, struct iio_sysfs_trig, + work); + + iio_trigger_poll(trig->trig); +} + +static ssize_t iio_sysfs_trigger_poll(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct iio_sysfs_trig *sysfs_trig = iio_trigger_get_drvdata(trig); + + irq_work_queue(&sysfs_trig->work); + + return count; +} + +static DEVICE_ATTR(trigger_now, S_IWUSR, NULL, iio_sysfs_trigger_poll); + +static struct attribute *iio_sysfs_trigger_attrs[] = { + &dev_attr_trigger_now.attr, + NULL, +}; + +static const struct attribute_group iio_sysfs_trigger_attr_group = { + .attrs = iio_sysfs_trigger_attrs, +}; + +static const struct attribute_group *iio_sysfs_trigger_attr_groups[] = { + &iio_sysfs_trigger_attr_group, + NULL +}; + +static int iio_sysfs_trigger_probe(int id) +{ + struct iio_sysfs_trig *t; + int ret; + bool foundit = false; + + mutex_lock(&iio_sysfs_trig_list_mut); + list_for_each_entry(t, &iio_sysfs_trig_list, l) + if (id == t->id) { + foundit = true; + break; + } + if (foundit) { + ret = -EINVAL; + goto err_unlock; + } + t = kmalloc_obj(*t); + if (t == NULL) { + ret = -ENOMEM; + goto err_unlock; + } + t->id = id; + t->trig = iio_trigger_alloc(&iio_sysfs_trig_dev, "sysfstrig%d", id); + if (!t->trig) { + ret = -ENOMEM; + goto err_free_sys_trig; + } + + t->trig->dev.groups = iio_sysfs_trigger_attr_groups; + iio_trigger_set_drvdata(t->trig, t); + + t->work = IRQ_WORK_INIT_HARD(iio_sysfs_trigger_work); + + ret = iio_trigger_register(t->trig); + if (ret) + goto err_free_trig; + list_add(&t->l, &iio_sysfs_trig_list); + __module_get(THIS_MODULE); + mutex_unlock(&iio_sysfs_trig_list_mut); + return 0; + +err_free_trig: + iio_trigger_free(t->trig); +err_free_sys_trig: + kfree(t); +err_unlock: + mutex_unlock(&iio_sysfs_trig_list_mut); + return ret; +} + +static int iio_sysfs_trigger_remove(int id) +{ + struct iio_sysfs_trig *t = NULL, *iter; + + mutex_lock(&iio_sysfs_trig_list_mut); + list_for_each_entry(iter, &iio_sysfs_trig_list, l) + if (id == iter->id) { + t = iter; + break; + } + if (!t) { + mutex_unlock(&iio_sysfs_trig_list_mut); + return -EINVAL; + } + + iio_trigger_unregister(t->trig); + irq_work_sync(&t->work); + iio_trigger_free(t->trig); + + list_del(&t->l); + kfree(t); + module_put(THIS_MODULE); + mutex_unlock(&iio_sysfs_trig_list_mut); + return 0; +} + + +static int __init iio_sysfs_trig_init(void) +{ + int ret; + device_initialize(&iio_sysfs_trig_dev); + dev_set_name(&iio_sysfs_trig_dev, "iio_sysfs_trigger"); + ret = device_add(&iio_sysfs_trig_dev); + if (ret) + put_device(&iio_sysfs_trig_dev); + return ret; +} +module_init(iio_sysfs_trig_init); + +static void __exit iio_sysfs_trig_exit(void) +{ + device_unregister(&iio_sysfs_trig_dev); +} +module_exit(iio_sysfs_trig_exit); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Sysfs based trigger for the iio subsystem"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:iio-trig-sysfs"); diff --git a/drivers/iio/trigger/stm32-lptimer-trigger.c b/drivers/iio/trigger/stm32-lptimer-trigger.c new file mode 100644 index 000000000..828890fe3 --- /dev/null +++ b/drivers/iio/trigger/stm32-lptimer-trigger.c @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * STM32 Low-Power Timer Trigger driver + * + * Copyright (C) STMicroelectronics 2017 + * + * Author: Fabrice Gasnier . + * + * Inspired by Benjamin Gaignard's stm32-timer-trigger driver + */ + +#include +#include +#include +#include +#include +#include + +/* Maximum triggers + one trailing null entry to indicate the end of array */ +#define MAX_TRIGGERS 3 + +struct stm32_lptim_cfg { + const char * const (*triggers)[MAX_TRIGGERS]; + unsigned int nb_triggers; +}; + +/* List Low-Power Timer triggers for H7, MP13, MP15 */ +static const char * const stm32_lptim_triggers[][MAX_TRIGGERS] = { + { LPTIM1_OUT,}, + { LPTIM2_OUT,}, + { LPTIM3_OUT,}, +}; + +/* List Low-Power Timer triggers for STM32MP25 */ +static const char * const stm32mp25_lptim_triggers[][MAX_TRIGGERS] = { + { LPTIM1_CH1, LPTIM1_CH2, }, + { LPTIM2_CH1, LPTIM2_CH2, }, + { LPTIM3_CH1,}, + { LPTIM4_CH1,}, + { LPTIM5_OUT,}, +}; + +static const struct stm32_lptim_cfg stm32mp15_lptim_cfg = { + .triggers = stm32_lptim_triggers, + .nb_triggers = ARRAY_SIZE(stm32_lptim_triggers), +}; + +static const struct stm32_lptim_cfg stm32mp25_lptim_cfg = { + .triggers = stm32mp25_lptim_triggers, + .nb_triggers = ARRAY_SIZE(stm32mp25_lptim_triggers), +}; + +struct stm32_lptim_trigger { + struct device *dev; + const char * const *triggers; +}; + +static int stm32_lptim_validate_device(struct iio_trigger *trig, + struct iio_dev *indio_dev) +{ + if (indio_dev->modes & INDIO_HARDWARE_TRIGGERED) + return 0; + + return -EINVAL; +} + +static const struct iio_trigger_ops stm32_lptim_trigger_ops = { + .validate_device = stm32_lptim_validate_device, +}; + +/** + * is_stm32_lptim_trigger + * @trig: trigger to be checked + * + * return true if the trigger is a valid STM32 IIO Low-Power Timer Trigger + * either return false + */ +bool is_stm32_lptim_trigger(struct iio_trigger *trig) +{ + return (trig->ops == &stm32_lptim_trigger_ops); +} +EXPORT_SYMBOL(is_stm32_lptim_trigger); + +static int stm32_lptim_setup_trig(struct stm32_lptim_trigger *priv) +{ + const char * const *cur = priv->triggers; + int ret; + + while (cur && *cur) { + struct iio_trigger *trig; + + trig = devm_iio_trigger_alloc(priv->dev, "%s", *cur); + if (!trig) + return -ENOMEM; + + trig->dev.parent = priv->dev->parent; + trig->ops = &stm32_lptim_trigger_ops; + iio_trigger_set_drvdata(trig, priv); + + ret = devm_iio_trigger_register(priv->dev, trig); + if (ret) + return ret; + cur++; + } + + return 0; +} + +static int stm32_lptim_trigger_probe(struct platform_device *pdev) +{ + struct stm32_lptim_trigger *priv; + struct stm32_lptim_cfg const *lptim_cfg; + u32 index; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + if (device_property_read_u32(&pdev->dev, "reg", &index)) + return -EINVAL; + + lptim_cfg = device_get_match_data(&pdev->dev); + + if (index >= lptim_cfg->nb_triggers) + return -EINVAL; + + priv->dev = &pdev->dev; + priv->triggers = lptim_cfg->triggers[index]; + + return stm32_lptim_setup_trig(priv); +} + +static const struct of_device_id stm32_lptim_trig_of_match[] = { + { .compatible = "st,stm32-lptimer-trigger", .data = &stm32mp15_lptim_cfg }, + { .compatible = "st,stm32mp25-lptimer-trigger", .data = &stm32mp25_lptim_cfg}, + { } +}; +MODULE_DEVICE_TABLE(of, stm32_lptim_trig_of_match); + +static struct platform_driver stm32_lptim_trigger_driver = { + .probe = stm32_lptim_trigger_probe, + .driver = { + .name = "stm32-lptimer-trigger", + .of_match_table = stm32_lptim_trig_of_match, + }, +}; +module_platform_driver(stm32_lptim_trigger_driver); + +MODULE_AUTHOR("Fabrice Gasnier "); +MODULE_ALIAS("platform:stm32-lptimer-trigger"); +MODULE_DESCRIPTION("STMicroelectronics STM32 LPTIM trigger driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/trigger/stm32-timer-trigger.c b/drivers/iio/trigger/stm32-timer-trigger.c new file mode 100644 index 000000000..4f6ff1e72 --- /dev/null +++ b/drivers/iio/trigger/stm32-timer-trigger.c @@ -0,0 +1,941 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) STMicroelectronics 2016 + * + * Author: Benjamin Gaignard + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_TRIGGERS 7 +#define MAX_VALIDS 5 + +/* List the triggers created by each timer */ +static const void *triggers_table[][MAX_TRIGGERS] = { + { TIM1_TRGO, TIM1_TRGO2, TIM1_CH1, TIM1_CH2, TIM1_CH3, TIM1_CH4,}, + { TIM2_TRGO, TIM2_CH1, TIM2_CH2, TIM2_CH3, TIM2_CH4,}, + { TIM3_TRGO, TIM3_CH1, TIM3_CH2, TIM3_CH3, TIM3_CH4,}, + { TIM4_TRGO, TIM4_CH1, TIM4_CH2, TIM4_CH3, TIM4_CH4,}, + { TIM5_TRGO, TIM5_CH1, TIM5_CH2, TIM5_CH3, TIM5_CH4,}, + { TIM6_TRGO,}, + { TIM7_TRGO,}, + { TIM8_TRGO, TIM8_TRGO2, TIM8_CH1, TIM8_CH2, TIM8_CH3, TIM8_CH4,}, + { TIM9_TRGO, TIM9_CH1, TIM9_CH2,}, + { TIM10_OC1,}, + { TIM11_OC1,}, + { TIM12_TRGO, TIM12_CH1, TIM12_CH2,}, + { TIM13_OC1,}, + { TIM14_OC1,}, + { TIM15_TRGO,}, + { TIM16_OC1,}, + { TIM17_OC1,}, + { }, /* timer 18 */ + { }, /* timer 19 */ + { TIM20_TRGO, TIM20_TRGO2, TIM20_OC1, TIM20_OC2, TIM20_OC3, }, +}; + +/* List the triggers accepted by each timer */ +static const void *valids_table[][MAX_VALIDS] = { + { TIM5_TRGO, TIM2_TRGO, TIM3_TRGO, TIM4_TRGO,}, + { TIM1_TRGO, TIM8_TRGO, TIM3_TRGO, TIM4_TRGO,}, + { TIM1_TRGO, TIM2_TRGO, TIM5_TRGO, TIM4_TRGO,}, + { TIM1_TRGO, TIM2_TRGO, TIM3_TRGO, TIM8_TRGO,}, + { TIM2_TRGO, TIM3_TRGO, TIM4_TRGO, TIM8_TRGO,}, + { }, /* timer 6 */ + { }, /* timer 7 */ + { TIM1_TRGO, TIM2_TRGO, TIM4_TRGO, TIM5_TRGO,}, + { TIM2_TRGO, TIM3_TRGO, TIM10_OC1, TIM11_OC1,}, + { }, /* timer 10 */ + { }, /* timer 11 */ + { TIM4_TRGO, TIM5_TRGO, TIM13_OC1, TIM14_OC1,}, +}; + +static const void *stm32h7_valids_table[][MAX_VALIDS] = { + { TIM15_TRGO, TIM2_TRGO, TIM3_TRGO, TIM4_TRGO,}, + { TIM1_TRGO, TIM8_TRGO, TIM3_TRGO, TIM4_TRGO,}, + { TIM1_TRGO, TIM2_TRGO, TIM15_TRGO, TIM4_TRGO,}, + { TIM1_TRGO, TIM2_TRGO, TIM3_TRGO, TIM8_TRGO,}, + { TIM1_TRGO, TIM8_TRGO, TIM3_TRGO, TIM4_TRGO,}, + { }, /* timer 6 */ + { }, /* timer 7 */ + { TIM1_TRGO, TIM2_TRGO, TIM4_TRGO, TIM5_TRGO,}, + { }, /* timer 9 */ + { }, /* timer 10 */ + { }, /* timer 11 */ + { TIM4_TRGO, TIM5_TRGO, TIM13_OC1, TIM14_OC1,}, + { }, /* timer 13 */ + { }, /* timer 14 */ + { TIM1_TRGO, TIM3_TRGO, TIM16_OC1, TIM17_OC1,}, + { }, /* timer 16 */ + { }, /* timer 17 */ +}; + +struct stm32_timer_trigger_regs { + u32 cr1; + u32 cr2; + u32 psc; + u32 arr; + u32 cnt; + u32 smcr; +}; + +struct stm32_timer_trigger { + struct device *dev; + struct regmap *regmap; + struct clk *clk; + bool enabled; + u32 max_arr; + const void *triggers; + const void *valids; + bool has_trgo2; + struct mutex lock; /* concurrent sysfs configuration */ + struct list_head tr_list; + struct stm32_timer_trigger_regs bak; +}; + +struct stm32_timer_trigger_cfg { + const void *(*valids_table)[MAX_VALIDS]; + const unsigned int num_valids_table; +}; + +static bool stm32_timer_is_trgo2_name(const char *name) +{ + return !!strstr(name, "trgo2"); +} + +static bool stm32_timer_is_trgo_name(const char *name) +{ + return (!!strstr(name, "trgo") && !strstr(name, "trgo2")); +} + +static int stm32_timer_start(struct stm32_timer_trigger *priv, + struct iio_trigger *trig, + unsigned int frequency) +{ + unsigned long long prd, div; + int prescaler = 0, ret; + u32 ccer; + + /* Period and prescaler values depends of clock rate */ + div = (unsigned long long)clk_get_rate(priv->clk); + + do_div(div, frequency); + + prd = div; + + /* + * Increase prescaler value until we get a result that fit + * with auto reload register maximum value. + */ + while (div > priv->max_arr) { + prescaler++; + div = prd; + do_div(div, (prescaler + 1)); + } + prd = div; + + if (prescaler > MAX_TIM_PSC) { + dev_err(priv->dev, "prescaler exceeds the maximum value\n"); + return -EINVAL; + } + + /* Check if nobody else use the timer */ + regmap_read(priv->regmap, TIM_CCER, &ccer); + if (ccer & TIM_CCER_CCXE) + return -EBUSY; + + guard(mutex)(&priv->lock); + if (!priv->enabled) { + priv->enabled = true; + ret = clk_enable(priv->clk); + if (ret) + return ret; + } + + regmap_write(priv->regmap, TIM_PSC, prescaler); + regmap_write(priv->regmap, TIM_ARR, prd - 1); + regmap_set_bits(priv->regmap, TIM_CR1, TIM_CR1_ARPE); + + /* Force master mode to update mode */ + if (stm32_timer_is_trgo2_name(trig->name)) + regmap_update_bits(priv->regmap, TIM_CR2, TIM_CR2_MMS2, + 0x2 << TIM_CR2_MMS2_SHIFT); + else + regmap_update_bits(priv->regmap, TIM_CR2, TIM_CR2_MMS, + 0x2 << TIM_CR2_MMS_SHIFT); + + /* Make sure that registers are updated */ + regmap_set_bits(priv->regmap, TIM_EGR, TIM_EGR_UG); + + /* Enable controller */ + regmap_set_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN); + + return 0; +} + +static void stm32_timer_stop(struct stm32_timer_trigger *priv, + struct iio_trigger *trig) +{ + u32 ccer; + + regmap_read(priv->regmap, TIM_CCER, &ccer); + if (ccer & TIM_CCER_CCXE) + return; + + mutex_lock(&priv->lock); + /* Stop timer */ + regmap_clear_bits(priv->regmap, TIM_CR1, TIM_CR1_ARPE); + regmap_clear_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN); + regmap_write(priv->regmap, TIM_PSC, 0); + regmap_write(priv->regmap, TIM_ARR, 0); + + /* Force disable master mode */ + if (stm32_timer_is_trgo2_name(trig->name)) + regmap_clear_bits(priv->regmap, TIM_CR2, TIM_CR2_MMS2); + else + regmap_clear_bits(priv->regmap, TIM_CR2, TIM_CR2_MMS); + + /* Make sure that registers are updated */ + regmap_set_bits(priv->regmap, TIM_EGR, TIM_EGR_UG); + + if (priv->enabled) { + priv->enabled = false; + clk_disable(priv->clk); + } + mutex_unlock(&priv->lock); +} + +static ssize_t stm32_tt_store_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct stm32_timer_trigger *priv = iio_trigger_get_drvdata(trig); + unsigned int freq; + int ret; + + ret = kstrtouint(buf, 10, &freq); + if (ret) + return ret; + + if (freq == 0) { + stm32_timer_stop(priv, trig); + } else { + ret = stm32_timer_start(priv, trig, freq); + if (ret) + return ret; + } + + return len; +} + +static ssize_t stm32_tt_read_frequency(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct stm32_timer_trigger *priv = iio_trigger_get_drvdata(trig); + u32 psc, arr, cr1; + unsigned long long freq = 0; + + regmap_read(priv->regmap, TIM_CR1, &cr1); + regmap_read(priv->regmap, TIM_PSC, &psc); + regmap_read(priv->regmap, TIM_ARR, &arr); + + if (cr1 & TIM_CR1_CEN) { + freq = (unsigned long long)clk_get_rate(priv->clk); + do_div(freq, psc + 1); + do_div(freq, arr + 1); + } + + return sprintf(buf, "%d\n", (unsigned int)freq); +} + +static IIO_DEV_ATTR_SAMP_FREQ(0660, + stm32_tt_read_frequency, + stm32_tt_store_frequency); + +#define MASTER_MODE_MAX 7 +#define MASTER_MODE2_MAX 15 + +static char *master_mode_table[] = { + "reset", + "enable", + "update", + "compare_pulse", + "OC1REF", + "OC2REF", + "OC3REF", + "OC4REF", + /* Master mode selection 2 only */ + "OC5REF", + "OC6REF", + "compare_pulse_OC4REF", + "compare_pulse_OC6REF", + "compare_pulse_OC4REF_r_or_OC6REF_r", + "compare_pulse_OC4REF_r_or_OC6REF_f", + "compare_pulse_OC5REF_r_or_OC6REF_r", + "compare_pulse_OC5REF_r_or_OC6REF_f", +}; + +static ssize_t stm32_tt_show_master_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct stm32_timer_trigger *priv = dev_get_drvdata(dev); + struct iio_trigger *trig = to_iio_trigger(dev); + u32 cr2; + + regmap_read(priv->regmap, TIM_CR2, &cr2); + + if (stm32_timer_is_trgo2_name(trig->name)) + cr2 = (cr2 & TIM_CR2_MMS2) >> TIM_CR2_MMS2_SHIFT; + else + cr2 = (cr2 & TIM_CR2_MMS) >> TIM_CR2_MMS_SHIFT; + + return sysfs_emit(buf, "%s\n", master_mode_table[cr2]); +} + +static ssize_t stm32_tt_store_master_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct stm32_timer_trigger *priv = dev_get_drvdata(dev); + struct iio_trigger *trig = to_iio_trigger(dev); + u32 mask, shift, master_mode_max; + int i, ret; + + if (stm32_timer_is_trgo2_name(trig->name)) { + mask = TIM_CR2_MMS2; + shift = TIM_CR2_MMS2_SHIFT; + master_mode_max = MASTER_MODE2_MAX; + } else { + mask = TIM_CR2_MMS; + shift = TIM_CR2_MMS_SHIFT; + master_mode_max = MASTER_MODE_MAX; + } + + for (i = 0; i <= master_mode_max; i++) { + if (!strncmp(master_mode_table[i], buf, + strlen(master_mode_table[i]))) { + guard(mutex)(&priv->lock); + if (!priv->enabled) { + /* Clock should be enabled first */ + priv->enabled = true; + ret = clk_enable(priv->clk); + if (ret) + return ret; + } + regmap_update_bits(priv->regmap, TIM_CR2, mask, + i << shift); + return len; + } + } + + return -EINVAL; +} + +static ssize_t stm32_tt_show_master_mode_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + unsigned int i, master_mode_max; + size_t len = 0; + + if (stm32_timer_is_trgo2_name(trig->name)) + master_mode_max = MASTER_MODE2_MAX; + else + master_mode_max = MASTER_MODE_MAX; + + for (i = 0; i <= master_mode_max; i++) + len += scnprintf(buf + len, PAGE_SIZE - len, + "%s ", master_mode_table[i]); + + /* replace trailing space by newline */ + buf[len - 1] = '\n'; + + return len; +} + +static IIO_DEVICE_ATTR(master_mode_available, 0444, + stm32_tt_show_master_mode_avail, NULL, 0); + +static IIO_DEVICE_ATTR(master_mode, 0660, + stm32_tt_show_master_mode, + stm32_tt_store_master_mode, + 0); + +static struct attribute *stm32_trigger_attrs[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_dev_attr_master_mode.dev_attr.attr, + &iio_dev_attr_master_mode_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group stm32_trigger_attr_group = { + .attrs = stm32_trigger_attrs, +}; + +static const struct attribute_group *stm32_trigger_attr_groups[] = { + &stm32_trigger_attr_group, + NULL, +}; + +static const struct iio_trigger_ops timer_trigger_ops = { +}; + +static void stm32_unregister_iio_triggers(struct stm32_timer_trigger *priv) +{ + struct iio_trigger *tr; + + list_for_each_entry(tr, &priv->tr_list, alloc_list) + iio_trigger_unregister(tr); +} + +static int stm32_register_iio_triggers(struct stm32_timer_trigger *priv) +{ + int ret; + const char * const *cur = priv->triggers; + + INIT_LIST_HEAD(&priv->tr_list); + + while (cur && *cur) { + struct iio_trigger *trig; + bool cur_is_trgo = stm32_timer_is_trgo_name(*cur); + bool cur_is_trgo2 = stm32_timer_is_trgo2_name(*cur); + + if (cur_is_trgo2 && !priv->has_trgo2) { + cur++; + continue; + } + + trig = devm_iio_trigger_alloc(priv->dev, "%s", *cur); + if (!trig) + return -ENOMEM; + + trig->dev.parent = priv->dev->parent; + trig->ops = &timer_trigger_ops; + + /* + * sampling frequency and master mode attributes + * should only be available on trgo/trgo2 triggers + */ + if (cur_is_trgo || cur_is_trgo2) + trig->dev.groups = stm32_trigger_attr_groups; + + iio_trigger_set_drvdata(trig, priv); + + ret = iio_trigger_register(trig); + if (ret) { + stm32_unregister_iio_triggers(priv); + return ret; + } + + list_add_tail(&trig->alloc_list, &priv->tr_list); + cur++; + } + + return 0; +} + +static int stm32_counter_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + u32 dat; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + regmap_read(priv->regmap, TIM_CNT, &dat); + *val = dat; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_ENABLE: + regmap_read(priv->regmap, TIM_CR1, &dat); + *val = (dat & TIM_CR1_CEN) ? 1 : 0; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + regmap_read(priv->regmap, TIM_SMCR, &dat); + dat &= TIM_SMCR_SMS; + + *val = 1; + *val2 = 0; + + /* in quadrature case scale = 0.25 */ + if (dat == 3) + *val2 = 2; + + return IIO_VAL_FRACTIONAL_LOG2; + } + + return -EINVAL; +} + +static int stm32_counter_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return regmap_write(priv->regmap, TIM_CNT, val); + + case IIO_CHAN_INFO_SCALE: + /* fixed scale */ + return -EINVAL; + + case IIO_CHAN_INFO_ENABLE: { + guard(mutex)(&priv->lock); + if (val) { + if (!priv->enabled) { + priv->enabled = true; + ret = clk_enable(priv->clk); + if (ret) + return ret; + } + regmap_set_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN); + } else { + regmap_clear_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN); + if (priv->enabled) { + priv->enabled = false; + clk_disable(priv->clk); + } + } + + return 0; + } + default: + return -EINVAL; + } +} + +static int stm32_counter_validate_trigger(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + const char * const *cur = priv->valids; + unsigned int i = 0; + + if (!is_stm32_timer_trigger(trig)) + return -EINVAL; + + while (cur && *cur) { + if (!strncmp(trig->name, *cur, strlen(trig->name))) { + regmap_update_bits(priv->regmap, + TIM_SMCR, TIM_SMCR_TS, + i << TIM_SMCR_TS_SHIFT); + return 0; + } + cur++; + i++; + } + + return -EINVAL; +} + +static const struct iio_info stm32_trigger_info = { + .validate_trigger = stm32_counter_validate_trigger, + .read_raw = stm32_counter_read_raw, + .write_raw = stm32_counter_write_raw +}; + +static const char *const stm32_trigger_modes[] = { + "trigger", +}; + +static int stm32_set_trigger_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + + regmap_set_bits(priv->regmap, TIM_SMCR, TIM_SMCR_SMS); + + return 0; +} + +static int stm32_get_trigger_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + u32 smcr; + + regmap_read(priv->regmap, TIM_SMCR, &smcr); + + return (smcr & TIM_SMCR_SMS) == TIM_SMCR_SMS ? 0 : -EINVAL; +} + +static const struct iio_enum stm32_trigger_mode_enum = { + .items = stm32_trigger_modes, + .num_items = ARRAY_SIZE(stm32_trigger_modes), + .set = stm32_set_trigger_mode, + .get = stm32_get_trigger_mode +}; + +static const char *const stm32_enable_modes[] = { + "always", + "gated", + "triggered", +}; + +static int stm32_enable_mode2sms(int mode) +{ + switch (mode) { + case 0: + return 0; + case 1: + return 5; + case 2: + return 6; + } + + return -EINVAL; +} + +static int stm32_set_enable_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + int sms = stm32_enable_mode2sms(mode); + int ret; + + if (sms < 0) + return sms; + /* + * Triggered mode sets CEN bit automatically by hardware. So, first + * enable counter clock, so it can use it. Keeps it in sync with CEN. + */ + scoped_guard(mutex, &priv->lock) { + if (sms == 6 && !priv->enabled) { + ret = clk_enable(priv->clk); + if (ret) + return ret; + + priv->enabled = true; + } + } + + regmap_update_bits(priv->regmap, TIM_SMCR, TIM_SMCR_SMS, sms); + + return 0; +} + +static int stm32_sms2enable_mode(int mode) +{ + switch (mode) { + case 0: + return 0; + case 5: + return 1; + case 6: + return 2; + } + + return -EINVAL; +} + +static int stm32_get_enable_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + u32 smcr; + + regmap_read(priv->regmap, TIM_SMCR, &smcr); + smcr &= TIM_SMCR_SMS; + + return stm32_sms2enable_mode(smcr); +} + +static const struct iio_enum stm32_enable_mode_enum = { + .items = stm32_enable_modes, + .num_items = ARRAY_SIZE(stm32_enable_modes), + .set = stm32_set_enable_mode, + .get = stm32_get_enable_mode +}; + +static ssize_t stm32_count_get_preset(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + u32 arr; + + regmap_read(priv->regmap, TIM_ARR, &arr); + + return snprintf(buf, PAGE_SIZE, "%u\n", arr); +} + +static ssize_t stm32_count_set_preset(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + unsigned int preset; + int ret; + + ret = kstrtouint(buf, 0, &preset); + if (ret) + return ret; + + /* TIMx_ARR register shouldn't be buffered (ARPE=0) */ + regmap_clear_bits(priv->regmap, TIM_CR1, TIM_CR1_ARPE); + regmap_write(priv->regmap, TIM_ARR, preset); + + return len; +} + +static const struct iio_chan_spec_ext_info stm32_trigger_count_info[] = { + { + .name = "preset", + .shared = IIO_SEPARATE, + .read = stm32_count_get_preset, + .write = stm32_count_set_preset + }, + IIO_ENUM("enable_mode", IIO_SEPARATE, &stm32_enable_mode_enum), + IIO_ENUM_AVAILABLE("enable_mode", IIO_SHARED_BY_TYPE, &stm32_enable_mode_enum), + IIO_ENUM("trigger_mode", IIO_SEPARATE, &stm32_trigger_mode_enum), + IIO_ENUM_AVAILABLE("trigger_mode", IIO_SHARED_BY_TYPE, &stm32_trigger_mode_enum), + { } +}; + +static const struct iio_chan_spec stm32_trigger_channel = { + .type = IIO_COUNT, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_SCALE), + .ext_info = stm32_trigger_count_info, + .indexed = 1 +}; + +static struct stm32_timer_trigger *stm32_setup_counter_device(struct device *dev) +{ + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, + sizeof(struct stm32_timer_trigger)); + if (!indio_dev) + return NULL; + + indio_dev->name = dev_name(dev); + indio_dev->info = &stm32_trigger_info; + indio_dev->modes = INDIO_HARDWARE_TRIGGERED; + indio_dev->num_channels = 1; + indio_dev->channels = &stm32_trigger_channel; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return NULL; + + return iio_priv(indio_dev); +} + +/** + * is_stm32_timer_trigger + * @trig: trigger to be checked + * + * return true if the trigger is a valid stm32 iio timer trigger + * either return false + */ +bool is_stm32_timer_trigger(struct iio_trigger *trig) +{ + return (trig->ops == &timer_trigger_ops); +} +EXPORT_SYMBOL(is_stm32_timer_trigger); + +static void stm32_timer_detect_trgo2(struct stm32_timer_trigger *priv) +{ + u32 val; + + /* + * Master mode selection 2 bits can only be written and read back when + * timer supports it. + */ + regmap_set_bits(priv->regmap, TIM_CR2, TIM_CR2_MMS2); + regmap_read(priv->regmap, TIM_CR2, &val); + regmap_clear_bits(priv->regmap, TIM_CR2, TIM_CR2_MMS2); + priv->has_trgo2 = !!val; +} + +static int stm32_timer_trigger_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct stm32_timer_trigger *priv; + struct stm32_timers *ddata = dev_get_drvdata(pdev->dev.parent); + const struct stm32_timer_trigger_cfg *cfg; + unsigned int index; + int ret; + + ret = device_property_read_u32(dev, "reg", &index); + if (ret) + return ret; + + cfg = device_get_match_data(dev); + + if (index >= ARRAY_SIZE(triggers_table) || + index >= cfg->num_valids_table) + return -EINVAL; + + /* Create an IIO device only if we have triggers to be validated */ + if (cfg->valids_table && *cfg->valids_table[index]) + priv = stm32_setup_counter_device(dev); + else + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->regmap = ddata->regmap; + priv->clk = ddata->clk; + priv->max_arr = ddata->max_arr; + priv->triggers = triggers_table[index]; + if (cfg->valids_table && *cfg->valids_table[index]) + priv->valids = cfg->valids_table[index]; + stm32_timer_detect_trgo2(priv); + mutex_init(&priv->lock); + + ret = stm32_register_iio_triggers(priv); + if (ret) + return ret; + + platform_set_drvdata(pdev, priv); + + return 0; +} + +static void stm32_timer_trigger_remove(struct platform_device *pdev) +{ + struct stm32_timer_trigger *priv = platform_get_drvdata(pdev); + u32 val; + + /* Unregister triggers before everything can be safely turned off */ + stm32_unregister_iio_triggers(priv); + + /* Check if nobody else use the timer, then disable it */ + regmap_read(priv->regmap, TIM_CCER, &val); + if (!(val & TIM_CCER_CCXE)) + regmap_clear_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN); + + if (priv->enabled) + clk_disable(priv->clk); +} + +static int stm32_timer_trigger_suspend(struct device *dev) +{ + struct stm32_timer_trigger *priv = dev_get_drvdata(dev); + + /* Only take care of enabled timer: don't disturb other MFD child */ + if (priv->enabled) { + /* Backup registers that may get lost in low power mode */ + regmap_read(priv->regmap, TIM_CR1, &priv->bak.cr1); + regmap_read(priv->regmap, TIM_CR2, &priv->bak.cr2); + regmap_read(priv->regmap, TIM_PSC, &priv->bak.psc); + regmap_read(priv->regmap, TIM_ARR, &priv->bak.arr); + regmap_read(priv->regmap, TIM_CNT, &priv->bak.cnt); + regmap_read(priv->regmap, TIM_SMCR, &priv->bak.smcr); + + /* Disable the timer */ + regmap_clear_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN); + clk_disable(priv->clk); + } + + return 0; +} + +static int stm32_timer_trigger_resume(struct device *dev) +{ + struct stm32_timer_trigger *priv = dev_get_drvdata(dev); + int ret; + + if (priv->enabled) { + ret = clk_enable(priv->clk); + if (ret) + return ret; + + /* restore master/slave modes */ + regmap_write(priv->regmap, TIM_SMCR, priv->bak.smcr); + regmap_write(priv->regmap, TIM_CR2, priv->bak.cr2); + + /* restore sampling_frequency (trgo / trgo2 triggers) */ + regmap_write(priv->regmap, TIM_PSC, priv->bak.psc); + regmap_write(priv->regmap, TIM_ARR, priv->bak.arr); + regmap_write(priv->regmap, TIM_CNT, priv->bak.cnt); + + /* Also re-enables the timer */ + regmap_write(priv->regmap, TIM_CR1, priv->bak.cr1); + } + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(stm32_timer_trigger_pm_ops, + stm32_timer_trigger_suspend, + stm32_timer_trigger_resume); + +static const struct stm32_timer_trigger_cfg stm32_timer_trg_cfg = { + .valids_table = valids_table, + .num_valids_table = ARRAY_SIZE(valids_table), +}; + +static const struct stm32_timer_trigger_cfg stm32h7_timer_trg_cfg = { + .valids_table = stm32h7_valids_table, + .num_valids_table = ARRAY_SIZE(stm32h7_valids_table), +}; + +static const struct stm32_timer_trigger_cfg stm32mp25_timer_trg_cfg = { + /* + * valids_table not used: counter framework is now superseding the deprecated IIO + * counter interface (IIO_COUNT), so don't support it. num_valids_table is only + * kept here to register the IIO HW triggers. valids_table should be moved at some + * point to the stm32-timer-cnt driver instead. + */ + .num_valids_table = ARRAY_SIZE(triggers_table), +}; + +static const struct of_device_id stm32_trig_of_match[] = { + { + .compatible = "st,stm32-timer-trigger", + .data = (void *)&stm32_timer_trg_cfg, + }, { + .compatible = "st,stm32h7-timer-trigger", + .data = (void *)&stm32h7_timer_trg_cfg, + }, { + .compatible = "st,stm32mp25-timer-trigger", + .data = (void *)&stm32mp25_timer_trg_cfg, + }, + { } +}; +MODULE_DEVICE_TABLE(of, stm32_trig_of_match); + +static struct platform_driver stm32_timer_trigger_driver = { + .probe = stm32_timer_trigger_probe, + .remove = stm32_timer_trigger_remove, + .driver = { + .name = "stm32-timer-trigger", + .of_match_table = stm32_trig_of_match, + .pm = pm_sleep_ptr(&stm32_timer_trigger_pm_ops), + }, +}; +module_platform_driver(stm32_timer_trigger_driver); + +MODULE_ALIAS("platform:stm32-timer-trigger"); +MODULE_DESCRIPTION("STMicroelectronics STM32 Timer Trigger driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.3.1